259 行
5.2 KiB
Plaintext
259 行
5.2 KiB
Plaintext
<template>
|
|
<view class="login-container">
|
|
<view class="login-content">
|
|
<!-- 1. 极简品牌 Header -->
|
|
<view class="header-section">
|
|
<image class="brand-logo" src="/static/logo.svg" mode="aspectFit" />
|
|
<text class="brand-title">登录 OmniCard</text>
|
|
<text class="brand-desc">全球智能数字名片 & 移动微官网</text>
|
|
</view>
|
|
|
|
<!-- 2. 极简核心表单 (账号密码直接呈现,前后端真实对齐) -->
|
|
<view class="form-section">
|
|
<view class="input-wrap">
|
|
<input
|
|
class="input-item"
|
|
type="text"
|
|
v-model="state.username"
|
|
placeholder="用户名或手机号"
|
|
:disabled="state.submitting"
|
|
/>
|
|
</view>
|
|
|
|
<view class="input-wrap">
|
|
<input
|
|
class="input-item"
|
|
type="password"
|
|
v-model="state.password"
|
|
placeholder="登录密码"
|
|
:disabled="state.submitting"
|
|
/>
|
|
</view>
|
|
|
|
<!-- 极轻量测试辅助链接 -->
|
|
<view class="test-hint-wrap" role="button" @click="handleAutofillTest">
|
|
<text class="test-hint-text">测试账号: admin / 123456 (点击一键填入)</text>
|
|
</view>
|
|
|
|
<!-- 错误提示 -->
|
|
<view v-if="state.errorMessage !== ''" class="error-wrap">
|
|
<text class="error-text">{{ state.errorMessage }}</text>
|
|
</view>
|
|
|
|
<!-- 单一主 CTA 按钮 -->
|
|
<button
|
|
class="primary-btn"
|
|
:class="{ 'primary-btn-loading': state.submitting }"
|
|
:disabled="state.submitting"
|
|
role="button"
|
|
aria-label="登录"
|
|
@click="handlePrimarySubmit"
|
|
>
|
|
<text class="primary-btn-text">{{ state.submitting ? '正在登录...' : '登录' }}</text>
|
|
</button>
|
|
</view>
|
|
|
|
<!-- 3. 极轻底部条款声明 -->
|
|
<view class="footer-section">
|
|
<text class="footer-text">登录即代表同意</text>
|
|
<text class="footer-link" @click="handleOpenTerms">服务条款</text>
|
|
<text class="footer-text">与</text>
|
|
<text class="footer-link" @click="handleOpenPrivacy">隐私政策</text>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
</template>
|
|
|
|
<script setup lang="uts">
|
|
import {
|
|
createLoginState,
|
|
submitPasswordLogin,
|
|
fillTestAccount,
|
|
resolveLoginText,
|
|
} from './useLogin.uts';
|
|
|
|
const state = reactive(createLoginState());
|
|
|
|
function handleAutofillTest(): void {
|
|
fillTestAccount(state);
|
|
}
|
|
|
|
function handlePrimarySubmit(): void {
|
|
submitPasswordLogin(state);
|
|
}
|
|
|
|
function handleOpenTerms(): void {
|
|
uni.showToast({
|
|
title: resolveLoginText('termsOfService'),
|
|
icon: 'none',
|
|
});
|
|
}
|
|
|
|
function handleOpenPrivacy(): void {
|
|
uni.showToast({
|
|
title: resolveLoginText('privacyPolicy'),
|
|
icon: 'none',
|
|
});
|
|
}
|
|
</script>
|
|
|
|
<style scoped>
|
|
.login-container {
|
|
min-height: 100vh;
|
|
background-color: #FFFFFF;
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
justify-content: center;
|
|
padding: 40px 24px 32px;
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
.login-content {
|
|
width: 100%;
|
|
max-width: 340px;
|
|
display: flex;
|
|
flex-direction: column;
|
|
}
|
|
|
|
/* 1. Header */
|
|
.header-section {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
margin-bottom: 32px;
|
|
}
|
|
|
|
.brand-logo {
|
|
width: 36px;
|
|
height: 36px;
|
|
margin-bottom: 16px;
|
|
}
|
|
|
|
.brand-title {
|
|
font-size: 22px;
|
|
font-weight: 700;
|
|
color: #101828;
|
|
line-height: 1.3;
|
|
margin-bottom: 6px;
|
|
letter-spacing: -0.02em;
|
|
}
|
|
|
|
.brand-desc {
|
|
font-size: 13px;
|
|
color: #667085;
|
|
line-height: 1.5;
|
|
}
|
|
|
|
/* 2. Form */
|
|
.form-section {
|
|
display: flex;
|
|
flex-direction: column;
|
|
}
|
|
|
|
.input-wrap {
|
|
margin-bottom: 14px;
|
|
}
|
|
|
|
.input-item {
|
|
width: 100%;
|
|
height: 44px;
|
|
padding: 0 14px;
|
|
font-size: 14px;
|
|
color: #101828;
|
|
background-color: #FFFFFF;
|
|
border: 1px solid #E3E7ED;
|
|
border-radius: 10px;
|
|
box-sizing: border-box;
|
|
transition: border-color 150ms cubic-bezier(0.2, 0.8, 0.2, 1);
|
|
}
|
|
|
|
.input-item:focus {
|
|
border-color: #0B5CFF;
|
|
}
|
|
|
|
.test-hint-wrap {
|
|
display: flex;
|
|
justify-content: flex-start;
|
|
margin-top: -2px;
|
|
margin-bottom: 16px;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.test-hint-text {
|
|
font-size: 12px;
|
|
color: #0B5CFF;
|
|
line-height: 1.4;
|
|
}
|
|
|
|
.error-wrap {
|
|
margin-bottom: 12px;
|
|
}
|
|
|
|
.error-text {
|
|
font-size: 12px;
|
|
color: #F04438;
|
|
line-height: 1.4;
|
|
}
|
|
|
|
/* 3. Button */
|
|
.primary-btn {
|
|
width: 100%;
|
|
height: 44px;
|
|
line-height: 44px;
|
|
background-color: #0B5CFF;
|
|
border-radius: 10px;
|
|
border: none;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
cursor: pointer;
|
|
margin-top: 4px;
|
|
margin-bottom: 24px;
|
|
transition: opacity 150ms cubic-bezier(0.2, 0.8, 0.2, 1);
|
|
}
|
|
|
|
.primary-btn::after {
|
|
border: none;
|
|
}
|
|
|
|
.primary-btn:active {
|
|
opacity: 0.88;
|
|
}
|
|
|
|
.primary-btn-loading {
|
|
opacity: 0.7;
|
|
cursor: not-allowed;
|
|
}
|
|
|
|
.primary-btn-text {
|
|
color: #FFFFFF;
|
|
font-size: 15px;
|
|
font-weight: 600;
|
|
letter-spacing: 0.02em;
|
|
}
|
|
|
|
/* 4. Footer */
|
|
.footer-section {
|
|
display: flex;
|
|
flex-direction: row;
|
|
justify-content: center;
|
|
align-items: center;
|
|
flex-wrap: wrap;
|
|
}
|
|
|
|
.footer-text {
|
|
font-size: 12px;
|
|
color: #98A2B3;
|
|
line-height: 1.5;
|
|
}
|
|
|
|
.footer-link {
|
|
font-size: 12px;
|
|
color: #667085;
|
|
text-decoration: underline;
|
|
margin: 0 3px;
|
|
cursor: pointer;
|
|
}
|
|
</style>
|