Files
jiapuapp/pages/auth/a02-login.vue
T
2026-07-14 17:36:43 +08:00

447 lines
11 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
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.
<!-- 页面编号A-02用途账号登录当前仅保留已确认的本地登录界面 -->
<template>
<view class="auth-page login-page">
<!-- A01 共用的登录页底图层先不透明纸纹后透明山水 -->
<image class="paper-background" src="/static/assets/foundation/opaque/auth-page-paper.jpg" mode="aspectFill" />
<image class="scenery-background" src="/static/assets/foundation/transparent/auth-ink-scenery.png" mode="aspectFill" />
<view class="auth-content">
<view class="auth-header">
<image class="header-background" src="/static/assets/foundation/opaque/auth-header.png" mode="aspectFill" />
<image class="header-seal" src="/static/assets/foundation/transparent/brand-seal.png" mode="aspectFit" />
</view>
<view class="login-panel">
<image class="login-panel__skin" src="/static/assets/modules/auth/opaque/a02-login-panel.png" mode="scaleToFill" />
<view class="login-panel__content">
<view class="page-heading">
<image class="login-title-cloud" src="/static/assets/foundation/transparent/auth-title-cloud.png" mode="aspectFit" />
<view class="back-button" hover-class="back-hover" @click="goBack">
<image class="back-button__icon" src="/static/assets/foundation/transparent/chevron-right.png" mode="aspectFit" />
</view>
<view class="page-heading__copy">
<text class="page-title">登录家谱</text>
<text class="page-subtitle">欢迎回到家族记忆</text>
</view>
</view>
<view class="login-tabs">
<view class="login-tab" :class="{ active: activeTab === 'password' }" @click="activeTab = 'password'">
<text>账号密码登录</text>
</view>
<view class="login-tab" :class="{ active: activeTab === 'sms' }" @click="activeTab = 'sms'">
<text>手机验证码登录</text>
</view>
</view>
<view v-if="activeTab === 'password'" class="form-content">
<view class="input-row">
<text class="input-label">账号</text>
<input v-model.trim="account" class="auth-input" placeholder="请输入账号或手机号" placeholder-class="placeholder" />
</view>
<view class="input-row">
<text class="input-label">密码</text>
<input v-model="password" class="auth-input" password placeholder="请输入密码" placeholder-class="placeholder" />
</view>
<view class="secondary-action" hover-class="text-hover" @click="prepareForgotPassword">忘记密码</view>
</view>
<view v-else class="form-content">
<view class="input-row">
<text class="input-label">手机</text>
<input v-model.trim="smsPhone" class="auth-input" type="number" maxlength="11" placeholder="请输入手机号" placeholder-class="placeholder" />
</view>
<view class="input-row code-row">
<text class="input-label">验证码</text>
<input v-model.trim="verificationCode" class="auth-input" type="number" maxlength="6" placeholder="请输入验证码" placeholder-class="placeholder" />
<view class="get-code" hover-class="text-hover" @click="prepareGetCode">获取验证码</view>
</view>
</view>
<view class="login-submit" hover-class="button-hover" @click="submitLogin">
<image class="login-submit__skin" src="/static/assets/foundation/opaque/a01-primary-button.png" mode="scaleToFill" />
<text class="login-submit__content">登录</text>
</view>
<view class="agreement-row" @click="toggleAgreement">
<image class="agreement-icon" :src="agreed ? '/static/assets/modules/auth/transparent/a02-agreement-checked.png' : '/static/assets/modules/auth/transparent/a02-agreement-unchecked.png'" mode="aspectFit" />
<view class="agreement-copy">
<text>我已阅读并同意</text>
<text class="agreement-link" @click.stop="prepareAgreement">用户协议</text>
<text></text>
<text class="agreement-link" @click.stop="prepareAgreement">隐私政策</text>
</view>
</view>
<view class="register-entry">
<text>还没有账号</text>
<view class="register-link" hover-class="text-hover" @click="prepareRegister">注册账号</view>
</view>
</view>
</view>
</view>
</view>
</template>
<script setup>
import { ref } from 'vue'
import { onLoad, onShow } from '@dcloudio/uni-app'
const activeTab = ref('password')
const account = ref('')
const password = ref('')
const smsPhone = ref('')
const verificationCode = ref('')
const agreed = ref(false)
const isAgreedRoute = () => {
if (typeof location !== 'undefined') {
const query = new URLSearchParams(location.hash.split('?')[1] || '')
return query.get('agreed') === '1'
}
const pages = getCurrentPages()
const currentPage = pages[pages.length - 1]
return currentPage?.options?.agreed === '1'
}
const syncAgreementFromRoute = () => {
agreed.value = isAgreedRoute()
}
onLoad(() => {
syncAgreementFromRoute()
})
onShow(() => {
syncAgreementFromRoute()
})
const goBack = () => {
uni.navigateBack()
}
const prepareRegister = () => uni.navigateTo({ url: '/pages/auth/a04-register' })
const prepareForgotPassword = () => uni.navigateTo({ url: '/pages/auth/a05-reset-password' })
const prepareGetCode = () => {
uni.showToast({ title: '获取验证码功能待接入', icon: 'none' })
}
const toggleAgreement = () => {
agreed.value = !agreed.value
}
const prepareAgreement = () => {
uni.showToast({ title: '协议页面准备中', icon: 'none' })
}
const canSubmit = () => {
if (agreed.value) return true
uni.showToast({ title: '请先阅读并同意相关协议', icon: 'none' })
return false
}
const submitLogin = () => {
if (!canSubmit()) return
if (activeTab.value === 'password') {
if (!account.value) {
uni.showToast({ title: '请输入账号或手机号', icon: 'none' })
return
}
if (!password.value) {
uni.showToast({ title: '请输入密码', icon: 'none' })
return
}
} else {
if (!/^1\d{10}$/.test(smsPhone.value)) {
uni.showToast({ title: '请输入正确手机号', icon: 'none' })
return
}
if (!verificationCode.value) {
uni.showToast({ title: '请输入验证码', icon: 'none' })
return
}
}
uni.showToast({ title: '登录接口待接入', icon: 'none' })
}
</script>
<style scoped lang="scss">
.auth-page {
position: relative;
min-height: 100vh;
background: #f7f1e6;
}
.paper-background,
.scenery-background {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
}
.paper-background { opacity: 0.96; }
.scenery-background { opacity: 0.76; }
.auth-content {
position: relative;
z-index: 1;
box-sizing: border-box;
min-height: 100vh;
padding-bottom: calc(44rpx + env(safe-area-inset-bottom));
}
.auth-header {
position: relative;
height: 253rpx;
overflow: hidden;
}
.header-background {
width: 100%;
height: 100%;
}
.header-seal {
position: absolute;
top: 70rpx;
left: 50%;
width: 136rpx;
height: 136rpx;
transform: translateX(-50%);
}
.login-panel {
position: relative;
min-height: 1184rpx;
margin: 32rpx 42rpx 0;
}
.login-panel__skin {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
pointer-events: none;
}
.login-panel__content {
position: relative;
z-index: 1;
box-sizing: border-box;
min-height: 1184rpx;
padding: 66rpx 50rpx 76rpx;
}
.page-heading {
position: relative;
display: flex;
align-items: center;
}
.login-title-cloud {
position: absolute;
top: -26rpx;
right: 2rpx;
width: 154rpx;
height: 92rpx;
opacity: 0.62;
}
.page-heading__copy {
position: relative;
z-index: 1;
}
.back-button {
display: flex;
align-items: center;
justify-content: center;
width: 56rpx;
height: 56rpx;
margin-right: 18rpx;
}
.back-hover { opacity: 0.62; }
.back-button__icon {
width: 42rpx;
height: 42rpx;
transform: scaleX(-1);
}
.page-title,
.page-subtitle {
display: block;
}
.page-title {
color: #3f2c1d;
font-family: STKaiti, KaiTi, serif;
font-size: 50rpx;
font-weight: 700;
letter-spacing: 4rpx;
}
.page-subtitle {
margin-top: 10rpx;
color: #8c7864;
font-size: 24rpx;
letter-spacing: 2rpx;
}
.login-tabs {
display: flex;
margin-top: 62rpx;
border-bottom: 1rpx solid #dcc29a;
}
.login-tab {
position: relative;
flex: 1;
padding: 0 0 28rpx;
color: #89715a;
font-family: STKaiti, KaiTi, serif;
font-size: 30rpx;
text-align: center;
}
.login-tab.active {
color: #a7160c;
font-weight: 700;
}
.login-tab.active::after {
position: absolute;
right: 28rpx;
bottom: -1rpx;
left: 28rpx;
height: 4rpx;
border-radius: 4rpx;
background: #b7170d;
content: '';
}
.form-content { margin-top: 36rpx; }
.input-row {
display: flex;
align-items: center;
min-height: 112rpx;
border-bottom: 1rpx solid #e5d4b7;
}
.input-label {
width: 100rpx;
color: #674b35;
font-family: STKaiti, KaiTi, serif;
font-size: 31rpx;
}
.auth-input {
flex: 1;
height: 104rpx;
color: #3f2c1d;
font-size: 29rpx;
}
.placeholder { color: #b3a895; }
.code-row .auth-input { min-width: 0; }
.get-code {
flex: 0 0 auto;
padding-left: 20rpx;
border-left: 1rpx solid #ddc7a3;
color: #a7160c;
font-size: 25rpx;
}
.secondary-action {
margin-top: 34rpx;
color: #a7160c;
font-size: 25rpx;
text-align: right;
}
.text-hover { opacity: 0.64; }
.login-submit {
position: relative;
display: flex;
align-items: center;
justify-content: center;
height: 128rpx;
margin-top: 76rpx;
overflow: hidden;
}
.login-submit__skin {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
}
.login-submit__content {
position: relative;
z-index: 1;
color: #fffaf0;
font-family: STKaiti, KaiTi, serif;
font-size: 36rpx;
letter-spacing: 6rpx;
}
.button-hover { opacity: 0.84; }
.agreement-row {
display: flex;
align-items: center;
justify-content: center;
min-height: 88rpx;
margin-top: 22rpx;
padding: 0 4rpx;
color: #6b5a4a;
font-size: 24rpx;
line-height: 32rpx;
}
.agreement-icon {
flex: 0 0 auto;
width: 32rpx;
height: 32rpx;
margin-right: 12rpx;
}
.agreement-copy {
display: flex;
align-items: center;
text-align: center;
white-space: nowrap;
}
.agreement-link {
color: #a7160c;
}
.register-entry {
display: flex;
align-items: center;
justify-content: center;
margin-top: 2rpx;
color: #6b5a4a;
font-size: 24rpx;
}
.register-link {
display: flex;
align-items: center;
min-height: 88rpx;
margin-left: 8rpx;
color: #a7160c;
}
</style>