refactor: 重构项目分包结构与资源管理
1. 调整页面路由,将业务页面迁移到对应分包,主包仅保留首页 2. 拆分公共服务模块,将账户、融资相关逻辑移入对应分包 3. 统一资源路径,将专属静态资源放入各分包static目录,更新导航栏图标资源 4. 修复启动引导逻辑,实现仅首次冷启动显示引导页 5. 更新README文档,补充分包配置与开发规范说明
This commit is contained in:
@@ -0,0 +1,126 @@
|
||||
<template>
|
||||
<view class="login-page page-white" :style="pageStyle()">
|
||||
<view :style="statusStyle(44)"></view>
|
||||
<scroll-view scroll-y :style="contentStyle(44, false)">
|
||||
<view class="login-content">
|
||||
<BrandLogo />
|
||||
<text class="welcome">欢迎登录 AI融资</text>
|
||||
<text class="login-subtitle">智能评估,让融资更简单</text>
|
||||
<text class="form-heading">{{ mode == 'phone' ? '手机号登录 / 注册' : '密码登录' }}</text>
|
||||
<view class="input-box"><text class="input-prefix">+86</text><input v-model="phone" :disabled="loading" type="number" maxlength="11" placeholder="请输入手机号" class="field-input" /></view>
|
||||
<view v-if="mode == 'password'" class="input-box"><input v-model="password" :disabled="loading" password placeholder="请输入登录密码" class="field-input" /></view>
|
||||
<view v-if="mode == 'phone' && smsSent" class="input-box"><input v-model="smsCode" type="number" maxlength="6" placeholder="请输入 6 位验证码" class="field-input" /><text class="countdown" @click="resendSms">{{ countdown > 0 ? countdown.toString() + 's 后重试' : '重新获取' }}</text></view>
|
||||
<text v-if="errorMessage.length > 0" class="error-message">{{ errorMessage }}</text>
|
||||
<button class="login-button" :disabled="loading" @click="login">{{ loading ? '处理中…' : (mode == 'phone' ? (smsSent ? '登录' : '获取验证码') : '登录') }}</button>
|
||||
<view class="mode-row"><text class="mode-link" @click="switchMode">{{ mode == 'phone' ? '密码登录' : '使用验证码登录' }}</text><text v-if="isDemo" class="demo-label">演示模式</text></view>
|
||||
<text v-if="isDemo && smsSent" class="demo-hint">未发送真实短信,可输入任意 6 位数字体验。</text>
|
||||
<view class="other-login"><view class="line"></view><text class="other-text">其他登录方式</text><view class="line"></view></view>
|
||||
<view class="social-row"><view class="social-button" @click="socialLogin('微信')"><image class="social-icon" src="/packages/account/static/wechat.svg" mode="aspectFit" /><text class="social-label">微信</text></view><view class="social-button" @click="socialLogin('一键登录')"><image class="social-icon" src="/packages/account/static/quick-login.svg" mode="aspectFit" /><text class="social-label">一键登录</text></view></view>
|
||||
<view class="agreement"><view class="check-hit" @click="agreed = !agreed"><view :class="agreed ? 'check checked' : 'check'"><text v-if="agreed" class="check-mark">✓</text></view></view><text class="agreement-copy">我已阅读并同意<text class="agreement-link" @click="showAgreement('用户协议')">《用户协议》</text>和<text class="agreement-link" @click="showAgreement('隐私政策')">《隐私政策》</text></text></view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import { usePageLayout } from '@/services/layout.uts'
|
||||
import BrandLogo from '@/components/BrandLogo.uvue'
|
||||
import { API_BASE_URL, requestSmsCode, loginWithSms, loginWithPassword, saveSession } from '@/packages/account/services/auth.uts'
|
||||
import { showAgreementNotice } from '@/services/assessment.uts'
|
||||
import { consumeInitialLaunch } from '@/services/launch.uts'
|
||||
onLoad(() => { consumeInitialLaunch() })
|
||||
const { statusStyle, contentStyle, pageStyle } = usePageLayout()
|
||||
const mode = ref('phone')
|
||||
const phone = ref('')
|
||||
const password = ref('')
|
||||
const smsCode = ref('')
|
||||
const smsSent = ref(false)
|
||||
const countdown = ref(0)
|
||||
const loading = ref(false)
|
||||
const errorMessage = ref('')
|
||||
const agreed = ref(false)
|
||||
const isDemo = API_BASE_URL.length == 0
|
||||
const showAgreement = (title: string) => { showAgreementNotice(title) }
|
||||
let countdownTimer = 0
|
||||
watch(phone, () => {
|
||||
smsSent.value = false; smsCode.value = ''; countdown.value = 0; errorMessage.value = ''
|
||||
clearInterval(countdownTimer)
|
||||
})
|
||||
const switchMode = () => { if (loading.value) return; mode.value = mode.value == 'phone' ? 'password' : 'phone'; errorMessage.value = '' }
|
||||
const socialLogin = (provider: string) => { uni.showModal({ title: provider + '暂未开放', content: '请先使用手机号登录。该登录方式将在正式服务接入后开放。', showCancel: false, confirmText: '我知道了' }) }
|
||||
const validPhone = (): boolean => {
|
||||
if (!/^1[3-9][0-9]{9}$/.test(phone.value)) { errorMessage.value = '请输入正确的 11 位手机号'; return false }
|
||||
return true
|
||||
}
|
||||
const startCountdown = () => {
|
||||
countdown.value = 60
|
||||
clearInterval(countdownTimer)
|
||||
countdownTimer = setInterval(() => {
|
||||
if (countdown.value <= 1) { countdown.value = 0; clearInterval(countdownTimer) } else countdown.value -= 1
|
||||
}, 1000)
|
||||
}
|
||||
const finishLogin = (token: string) => { saveSession(token, phone.value); uni.redirectTo({ url: '/pages/home/home' }) }
|
||||
const resendSms = async () => {
|
||||
if (loading.value || countdown.value > 0 || !validPhone()) return
|
||||
if (!agreed.value) { errorMessage.value = '请先阅读并勾选用户协议与隐私政策'; return }
|
||||
loading.value = true; errorMessage.value = ''
|
||||
try {
|
||||
if (!(await requestSmsCode(phone.value))) throw new Error('验证码发送失败,请稍后重试')
|
||||
startCountdown()
|
||||
uni.showToast({ title: isDemo ? '演示验证码已就绪' : '验证码已重新发送', icon: 'none' })
|
||||
} catch (error) { errorMessage.value = error instanceof Error ? error.message : '验证码发送失败,请稍后重试' } finally { loading.value = false }
|
||||
}
|
||||
const login = async () => {
|
||||
errorMessage.value = ''
|
||||
if (!agreed.value) { errorMessage.value = '请先阅读并勾选用户协议与隐私政策'; return }
|
||||
if (!validPhone() || loading.value) return
|
||||
loading.value = true
|
||||
try {
|
||||
if (mode.value == 'phone') {
|
||||
if (!smsSent.value) {
|
||||
if (!(await requestSmsCode(phone.value))) throw new Error('验证码发送失败,请稍后重试')
|
||||
smsSent.value = true; startCountdown()
|
||||
uni.showToast({ title: isDemo ? '演示验证码已就绪' : '验证码已发送', icon: 'none' })
|
||||
} else {
|
||||
if (!/^[0-9]{6}$/.test(smsCode.value)) throw new Error('请输入 6 位数字验证码')
|
||||
finishLogin(await loginWithSms(phone.value, smsCode.value))
|
||||
}
|
||||
} else {
|
||||
if (password.value.length < 6) throw new Error('密码至少需要 6 位')
|
||||
finishLogin(await loginWithPassword(phone.value, password.value))
|
||||
}
|
||||
} catch (error) { errorMessage.value = error instanceof Error ? error.message : '登录失败,请稍后重试' } finally { loading.value = false }
|
||||
}
|
||||
onUnmounted(() => { clearInterval(countdownTimer) })
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.login-content { padding: 20rpx 40rpx 48rpx; }
|
||||
.welcome { font-size: 42rpx; line-height: 60rpx; font-weight: 800; margin-top: 38rpx; color: #152443; }
|
||||
.login-subtitle { font-size: 26rpx; color: #748198; line-height: 40rpx; margin-top: 8rpx; }
|
||||
.form-heading { margin-top: 54rpx; font-size: 28rpx; font-weight: 600; color: #22324a; }
|
||||
.input-box { height: 96rpx; margin-top: 22rpx; border: 1rpx solid #dce4ef; border-radius: 16rpx; padding: 0 24rpx; flex-direction: row; align-items: center; background-color: #fafcff; }
|
||||
.input-prefix { font-size: 28rpx; color: #31415a; margin-right: 22rpx; flex-shrink: 0; }
|
||||
.field-input { flex: 1; min-width: 0; font-size: 28rpx; color: #17243c; }
|
||||
.countdown { text-align: right; color: #0868f7; font-size: 24rpx; flex-shrink: 0; margin-left: 12rpx; }
|
||||
.error-message { margin-top: 16rpx; color: #d64040; font-size: 24rpx; line-height: 36rpx; }
|
||||
.login-button { height: 92rpx; line-height: 92rpx; margin-top: 28rpx; border-radius: 16rpx; color: #fff; font-size: 30rpx; font-weight: 600; background-color: #0868f7; }
|
||||
.mode-row { flex-direction: row; justify-content: space-between; align-items: center; margin-top: 12rpx; }
|
||||
.mode-link { padding: 16rpx 0; color: #31415a; font-size: 26rpx; }
|
||||
.demo-label { color: #8591a2; font-size: 22rpx; }
|
||||
.demo-hint { color: #65748a; font-size: 24rpx; line-height: 36rpx; margin-top: 8rpx; }
|
||||
.other-login { margin-top: 66rpx; flex-direction: row; align-items: center; justify-content: center; }
|
||||
.other-text { font-size: 24rpx; color: #8490a3; flex-shrink: 0; }
|
||||
.line { height: 1rpx; background-color: #e5eaf1; flex: 1; margin: 0 20rpx; }
|
||||
.social-row { flex-direction: row; justify-content: center; margin-top: 28rpx; }
|
||||
.social-button { width: 150rpx; min-height: 112rpx; align-items: center; justify-content: center; margin: 0 26rpx; }
|
||||
.social-icon { width: 62rpx; height: 62rpx; flex-shrink: 0; }
|
||||
.social-label { color: #58677e; font-size: 24rpx; margin-top: 12rpx; }
|
||||
.agreement { margin-top: 52rpx; flex-direction: row; align-items: flex-start; }
|
||||
.check-hit { width: 44rpx; min-height: 44rpx; flex-shrink: 0; justify-content: center; }
|
||||
.check { width: 30rpx; height: 30rpx; border: 2rpx solid #9aa8ba; border-radius: 6rpx; align-items: center; justify-content: center; }
|
||||
.checked { background-color: #0868f7; border-color: #0868f7; }
|
||||
.check-mark { font-size: 23rpx; color: #fff; }
|
||||
.agreement-copy { flex: 1; min-width: 0; color: #748198; font-size: 24rpx; line-height: 42rpx; }
|
||||
.agreement-link { color: #0868f7; font-size: 24rpx; line-height: 42rpx; }
|
||||
</style>
|
||||
Reference in New Issue
Block a user