Files
intelligentProject-phone/pages/light-theme/light-theme.vue
2026-01-14 14:24:58 +08:00

815 lines
17 KiB
Vue
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div class="main-view">
<div class="container">
<div class="logo">
<image src="/static/logo.png" alt="YXD"></image>
</div>
<h1>登录 YXD 智能应用</h1>
<div class="login-form">
<div class="form-group">
<label for="username">账号</label>
<input id="username" v-model="username" type="text" placeholder="请输入账号/邮箱/手机号"
:class="{ 'error': usernameError }" @input="clearError('username')" />
<div v-if="usernameError" class="error-message">{{ usernameError }}</div>
</div>
<div class="form-group">
<label for="password">密码</label>
<div class="password-input">
<input id="password" v-model="password" :type="showPassword ? 'text' : 'password'"
placeholder="请输入密码" :class="{ 'error': passwordError }" @input="clearError('password')"
@keyup.enter="handleLogin" />
<button type="button" class="toggle-password" @click="showPassword = !showPassword">
<span v-if="showPassword">🙈</span>
<span v-else>👁</span>
</button>
</div>
<div v-if="passwordError" class="error-message">{{ passwordError }}</div>
</div>
<div class="form-options">
<!-- <label class="remember-me">
<checkbox-group @change="toggleRememberMe">
<checkbox :checked="rememberMe"></checkbox>
</checkbox-group>
<span>记住账号</span>
</label> -->
<!-- <a href="#" class="forgot-password">忘记密码</a> -->
<label class="remember-me" @click="toggleRememberMe">
<!-- 自定义勾选框 -->
<div class="custom-checkbox-wrapper">
<div class="custom-checkbox" :class="{ 'checked': rememberMe }">
<span v-if="rememberMe"></span>
</div>
</div>
<span>记住账号</span>
</label>
</div>
<button class="login-btn" @click="handleLogin" :disabled="isLoggingIn">
<span v-if="isLoggingIn"></span>
<span v-else>🔑</span>
{{ isLoggingIn ? '登录中...' : '立即登录' }}
</button>
<div v-if="loginError" class="login-error">
<span class="error-icon"></span>
{{ loginError }}
</div>
<!-- 登录成功后的网址显示 -->
<div v-if="redirectUrl" class="redirect-section">
<div class="success-message">
<span class="success-icon"></span>
登录成功正在为您跳转...
</div>
<!-- <div class="url-display">
<input :value="redirectUrl" readonly />
<button class="copy-btn" @click="copyToClipboard" :title="copySuccess ? '已复制' : '复制链接'">
<span :class="copySuccess ? 'copied-icon' : 'copy-icon'">
{{ copySuccess ? '✓' : '📋' }}
</span>
</button>
</div>
<button class="access-btn" @click="openExternalLink">
<span class="external-icon">🔗</span> 立即访问
</button> -->
</div>
</div>
</div>
</div>
</template>
<script setup>
import {
ref,
onMounted
} from 'vue';
// 表单数据
const username = ref('');
const password = ref('');
const rememberMe = ref(false);
const showPassword = ref(false);
// 状态
const isLoggingIn = ref(false);
const redirectUrl = ref('');
const copySuccess = ref(false);
// 错误信息
const usernameError = ref('');
const passwordError = ref('');
const loginError = ref('');
// 切换记住账号状态
const toggleRememberMe = () => {
rememberMe.value = !rememberMe.value;
};
// 模拟登录API - 实际项目中替换为真实API
const mockLoginApi = (user, pwd) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
// 模拟登录验证
if (user && pwd) {
// 模拟返回的跳转URL
resolve({
success: true,
redirectUrl: 'https://ws.yuxindazhineng.com',
userInfo: {
username: user,
name: '用户' + user.substring(0, 3)
}
});
} else {
reject({
success: false,
message: '账号或密码错误'
});
}
}, 1500);
});
};
// 表单验证
const validateForm = () => {
let isValid = true;
usernameError.value = '';
passwordError.value = '';
if (!username.value.trim()) {
usernameError.value = '请输入账号';
isValid = false;
}
if (!password.value) {
passwordError.value = '请输入密码';
isValid = false;
} else if (password.value.length < 6) {
passwordError.value = '密码至少6位字符';
isValid = false;
}
return isValid;
};
// 清除错误信息
const clearError = (field) => {
if (field === 'username') {
usernameError.value = '';
} else if (field === 'password') {
passwordError.value = '';
}
loginError.value = '';
};
// 处理登录
const handleLogin = async () => {
if (!validateForm()) {
return;
}
isLoggingIn.value = true;
loginError.value = '';s
try {
// 发起实际API请求
const res = await uni.request({
url: 'http://cloud_test.yuxindazhineng.com/cloud_api/app/verify_domain',
method: 'POST',
header: {
'Content-Type': 'application/json'
},
data: {
username: username.value,
password: password.value
}
});
// 网络层面的错误判断
if (res.statusCode !== 200) {
throw new Error(`接口请求失败,状态码:${res.statusCode}`);
}
// 解析接口响应数据
const responseData = res.data;
// 业务层面的成功判断
if (responseData.success) {
// 存储用户信息
if (rememberMe.value) {
uni.setStorageSync('yxd_username', username.value);
} else {
uni.removeStorageSync('yxd_username');
}
// 从响应的data字段中获取域名数据
const domainData = responseData.data;
console.log('完整域名:', domainData.full_domain);
console.log('域名前缀:', domainData.domain_prefix);
// 构建跳转URL
redirectUrl.value = `https://${domainData.full_domain}`;
// 自动跳转
setTimeout(() => {
openExternalLink();
}, 2000);
} else {
// 接口返回业务失败
throw new Error(responseData.message || '登录失败,请检查账号密码');
}
} catch (error) {
loginError.value = error.message || '登录失败,请检查账号密码';
} finally {
isLoggingIn.value = false;
}
};
// 打开外部链接的通用方法
function openExternalLink() {
if (!redirectUrl.value) return;
// #ifdef H5
window.open(redirectUrl.value, '_blank')
// #endif
// #ifdef APP-PLUS
plus.runtime.openURL(redirectUrl.value)
// #endif
// #ifdef MP-WEIXIN
uni.setClipboardData({
data: redirectUrl.value,
success: () => {
uni.showModal({
content: '链接已复制,请在浏览器中打开',
showCancel: false
})
}
})
// #endif
}
// 复制到剪贴板
const copyToClipboard = () => {
if (!redirectUrl.value) return;
// #ifdef H5
navigator.clipboard.writeText(redirectUrl.value).then(() => {
copySuccess.value = true;
setTimeout(() => {
copySuccess.value = false;
}, 2000);
});
// #endif
// #ifdef MP-WEIXIN || APP-PLUS
uni.setClipboardData({
data: redirectUrl.value,
success: () => {
copySuccess.value = true;
setTimeout(() => {
copySuccess.value = false;
}, 2000);
}
});
// #endif
};
// 在组件挂载后执行的操作
onMounted(() => {
// 检查是否记住账号 - 修改这里
const savedUsername = uni.getStorageSync('yxd_username');
if (savedUsername) {
username.value = savedUsername;
rememberMe.value = true;
}
console.log('登录组件已挂载');
});
</script>
<style scoped>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: 'PingFang SC', 'Helvetica Neue', Arial, sans-serif;
}
body {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: -1;
background: linear-gradient(135deg, #f8fcff 0%, #f0f8ff 100%);
}
.main-view {
z-index: 0;
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
padding: 1rem;
position: relative;
overflow: hidden;
background: linear-gradient(135deg, #f8fcff 0%, #f0f8ff 100%);
color: #333;
}
.container {
display: flex;
flex-direction: column;
width: 100%;
max-width: 500px;
background: rgba(255, 255, 255, 0.95);
border-radius: 20px;
overflow: hidden;
box-shadow:
0 15px 35px rgba(33, 150, 243, 0.08),
0 5px 15px rgba(0, 0, 0, 0.05),
inset 0 0 0 1px rgba(255, 255, 255, 0.9);
text-align: center;
align-items: center;
justify-content: center;
padding: 45px 35px;
margin: 10px;
backdrop-filter: blur(10px);
}
.logo {
width: 110px;
height: 110px;
margin: 0 auto 30px;
background: linear-gradient(135deg, rgba(33, 150, 243, 0.1), rgba(100, 181, 246, 0.15));
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 40px;
box-shadow:
0 8px 25px rgba(33, 150, 243, 0.12),
0 0 0 1px rgba(255, 255, 255, 0.9),
inset 0 0 15px rgba(255, 255, 255, 0.8);
overflow: hidden;
padding: 12px;
position: relative;
}
.logo::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
border-radius: 50%;
background: linear-gradient(135deg, rgba(255, 255, 255, 0.9), rgba(255, 255, 255, 0.6));
z-index: 1;
}
.logo image {
max-width: 100%;
max-height: 100%;
object-fit: contain;
display: block;
position: relative;
z-index: 2;
filter: drop-shadow(0 2px 5px rgba(33, 150, 243, 0.3));
}
h1 {
font-size: 2.1rem;
font-weight: 700;
color: #1565c0;
margin-bottom: 18px;
text-shadow: 0 1px 2px rgba(255, 255, 255, 0.8);
letter-spacing: -0.3px;
}
.description {
color: #5d7b9f;
font-size: 0.95rem;
margin: 0 0 30px 0;
line-height: 1.6;
font-weight: 400;
}
.login-form {
width: 100%;
}
.form-group {
margin-bottom: 22px;
text-align: left;
}
.form-group label {
display: block;
margin-bottom: 10px;
font-weight: 500;
color: #2c5282;
font-size: 14px;
}
.form-group uni-input {
border: 1px solid rgba(59, 130, 246, 0.3);
border-radius: 12px;
width: 100%;
height: 30px;
padding-left: 8px;
}
.form-group uni-input .uni-input-input {
width: 100%;
padding: 15px 18px;
border: 1px solid #e1f5fe;
border-radius: 12px;
font-size: 15px;
transition: all 0.3s ease;
background: rgba(255, 255, 255, 0.9);
box-shadow:
inset 0 2px 4px rgba(0, 0, 0, 0.04),
0 1px 0 rgba(255, 255, 255, 0.8);
color: #2c3e50 !important;
}
.form-group uni-input .uni-input-input:focus {
outline: none;
border-color: #4fc3f7;
background: white;
box-shadow:
0 0 0 3px rgba(79, 195, 247, 0.2),
inset 0 2px 4px rgba(0, 0, 0, 0.02);
}
.form-group uni-input .uni-input-input::placeholder {
color: #90a4ae !important;
}
.form-group uni-input .uni-input-input.error {
border-color: #ff7043;
background: rgba(255, 112, 67, 0.03);
}
.form-group uni-input:focus {
outline: none;
border-color: #4fc3f7;
background: white;
box-shadow:
0 0 0 3px rgba(79, 195, 247, 0.2),
inset 0 2px 4px rgba(0, 0, 0, 0.02);
}
.form-group uni-input::placeholder {
color: #90a4ae !important;
}
.form-group uni-input.error {
border-color: #ff7043;
background: rgba(255, 112, 67, 0.03);
}
.error-message {
color: #ff7043;
font-size: 13px;
margin-top: 8px;
font-weight: 500;
}
.password-input {
position: relative;
}
.password-input uni-button::after,
.password-input uni-button::before {
border: none
}
.toggle-password {
position: absolute;
right: 15px;
top: 50%;
transform: translateY(-50%);
background: transparent;
color: #93c5fd;
cursor: pointer;
font-size: 15px;
transition: all 0.2s ease;
z-index: 2;
}
/* .toggle-password:hover {
color: #2196f3;
border-color: #4fc3f7;
background: white;
box-shadow: 0 2px 5px rgba(33, 150, 243, 0.15);
} */
.toggle-password span {
font-size: 16px;
display: inline-block;
vertical-align: middle;
}
.form-options {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 28px;
font-size: 14px;
}
.remember-me {
display: flex;
align-items: center;
gap: 10px;
color: #5d7b9f;
cursor: pointer;
font-weight: 500;
}
.custom-checkbox {
width: 18px;
height: 18px;
border: 1px solid rgba(59, 130, 246, 0.3);
border-radius: 4px;
background: transparent;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.2s ease;
}
.custom-checkbox.checked {
/* background: #3b82f6; */
border-color: #3b82f6;
}
.custom-checkbox.checked i {
color: white;
font-size: 12px;
}
/* uni-checkbox样式 */
.remember-me uni-checkbox {
width: 18px;
height: 18px;
margin: 0;
padding: 0;
background: transparent;
border: 1px solid rgba(59, 130, 246, 0.3);
border-radius: 4px;
appearance: none;
-webkit-appearance: none;
-moz-appearance: none;
position: relative;
cursor: pointer;
transition: all 0.2s ease;
}
.remember-me uni-checkbox:checked {
background: #3b82f6;
border-color: #3b82f6;
}
.remember-me uni-checkbox:checked::after {
content: '✓';
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
color: white;
font-size: 12px;
font-weight: bold;
}
.remember-me uni-checkbox:focus {
outline: none;
box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.3);
}
.forgot-password {
color: #29b6f6;
text-decoration: none;
font-weight: 500;
transition: all 0.2s ease;
}
.forgot-password:hover {
color: #0288d1;
text-decoration: underline;
}
.login-btn {
background: linear-gradient(135deg, #29b6f6, #0288d1);
color: white;
border: none;
width: 100%;
padding: 18px;
border-radius: 14px;
font-size: 16px;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
box-shadow:
0 6px 20px rgba(41, 182, 246, 0.3),
0 0 0 1px rgba(255, 255, 255, 0.2);
display: flex;
align-items: center;
justify-content: center;
gap: 10px;
letter-spacing: 0.5px;
position: relative;
overflow: hidden;
}
.login-btn::before {
content: '';
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
transition: left 0.5s;
}
.login-btn:hover:not(:disabled)::before {
left: 100%;
}
.login-btn:hover:not(:disabled) {
transform: translateY(-3px);
box-shadow:
0 10px 25px rgba(41, 182, 246, 0.4),
0 0 0 1px rgba(255, 255, 255, 0.3);
background: linear-gradient(135deg, #4fc3f7, #039be5);
}
.login-btn:active:not(:disabled) {
transform: translateY(0);
box-shadow:
0 4px 15px rgba(41, 182, 246, 0.3),
0 0 0 1px rgba(255, 255, 255, 0.2);
}
.login-btn:disabled {
opacity: 0.7;
cursor: not-allowed;
transform: none !important;
}
.login-error {
margin-top: 18px;
padding: 14px;
background: linear-gradient(135deg, rgba(255, 112, 67, 0.08), rgba(255, 112, 67, 0.05));
border: 1px solid #ffccbc;
border-radius: 12px;
color: #e64a19;
font-size: 14px;
display: flex;
align-items: center;
justify-content: center;
gap: 10px;
font-weight: 500;
box-shadow: 0 2px 8px rgba(255, 112, 67, 0.08);
}
.redirect-section {
margin-top: 30px;
padding-top: 30px;
border-top: 1px solid #e1f5fe;
animation: fadeIn 0.5s ease;
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(15px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.success-message {
color: #00c853;
font-weight: 600;
margin-bottom: 18px;
display: flex;
align-items: center;
justify-content: center;
gap: 10px;
font-size: 15px;
}
.url-display {
background: linear-gradient(135deg, #f8fdff, #e3f2fd);
padding: 15px;
border-radius: 12px;
margin-bottom: 18px;
border: 1px solid #bbdefb;
display: flex;
align-items: center;
gap: 12px;
box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.05);
}
.url-display input {
flex: 1;
border: none;
background: transparent;
font-size: 14px;
color: #1565c0;
outline: none;
font-family: 'Monaco', 'Consolas', monospace;
font-weight: 500;
}
.copy-btn {
background: white;
border: 1px solid #bbdefb;
border-radius: 8px;
padding: 9px 14px;
color: #29b6f6;
cursor: pointer;
transition: all 0.2s ease;
font-size: 14px;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.05);
}
.copy-btn:hover {
background: #e3f2fd;
color: #0288d1;
border-color: #90caf9;
box-shadow: 0 3px 8px rgba(0, 0, 0, 0.08);
}
.access-btn {
background: linear-gradient(135deg, #26c6da, #0097a7);
color: white;
border: none;
width: 100%;
padding: 16px;
border-radius: 12px;
font-size: 15px;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
box-shadow:
0 6px 20px rgba(38, 198, 218, 0.25),
0 0 0 1px rgba(255, 255, 255, 0.2);
display: flex;
align-items: center;
justify-content: center;
gap: 10px;
letter-spacing: 0.5px;
}
.access-btn:hover {
transform: translateY(-2px);
box-shadow:
0 10px 25px rgba(38, 198, 218, 0.35),
0 0 0 1px rgba(255, 255, 255, 0.3);
background: linear-gradient(135deg, #4dd0e1, #00acc1);
}
.access-btn:active {
transform: translateY(0);
box-shadow:
0 4px 15px rgba(38, 198, 218, 0.25),
0 0 0 1px rgba(255, 255, 255, 0.2);
}
@media (max-width: 600px) {
.container {
padding: 35px 25px;
}
h1 {
font-size: 1.9rem;
}
.form-options {
flex-direction: column;
align-items: flex-start;
gap: 15px;
}
.logo {
width: 95px;
height: 95px;
}
}
</style>