文件
OmniCard/mpuniapp/pages/common/login.uvue

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>