feat(mini-program): 优化登录页视觉设计,提升用户体验

This commit is contained in:
2026-05-01 22:58:58 +08:00
parent 2ed66d8500
commit 0b795cc340
2 changed files with 183 additions and 95 deletions
+4
View File
@@ -1,5 +1,9 @@
# 开发环境配置(本地开发调试) # 开发环境配置(本地开发调试)
VITE_APP_ENV=dev VITE_APP_ENV=dev
# 本地环境
# VITE_API_BASE_URL=http://localhost:19089/api
# VITE_WS_URL=ws://localhost:19089/ws
# 测试环境
VITE_API_BASE_URL=https://lifescript.happylifeos.com/api VITE_API_BASE_URL=https://lifescript.happylifeos.com/api
VITE_WS_URL=wss://lifescript.happylifeos.com/ws VITE_WS_URL=wss://lifescript.happylifeos.com/ws
VITE_DEBUG=true VITE_DEBUG=true
+179 -95
View File
@@ -1,19 +1,25 @@
<template> <template>
<view class="login-page"> <view class="login-page">
<view class="status-bar" :style="{ height: statusBarHeight + 'px' }"></view> <view class="status-bar" :style="{ height: statusBarHeight + 'px' }"></view>
<view class="bg-decoration"> <view class="bg-decoration">
<view class="aurora-top"></view> <view class="orb orb-primary"></view>
<view class="aurora-bottom"></view> <view class="orb orb-secondary"></view>
<view class="star-field"></view>
</view> </view>
<view class="content" :style="{ paddingTop: safeAreaTop + 20 + 'px' }"> <view class="content" :style="{ paddingTop: safeAreaTop + 'px', paddingBottom: safeAreaBottom + 24 + 'px' }">
<view class="brand-lockup">
<text class="brand-kicker">EMOTION MUSEUM</text>
<text class="brand-title">数字生命档案</text>
</view>
<view class="login-card"> <view class="login-card">
<view class="header"> <view class="header">
<text class="title font-serif">欢迎回来</text> <text class="title font-serif">欢迎回来</text>
<text class="subtitle">开启你的数字生命档案</text> <text class="subtitle">开启你的数字生命档案</text>
</view> </view>
<view class="form"> <view class="form">
<view class="input-group"> <view class="input-group">
<text class="input-label">手机号码</text> <text class="input-label">手机号码</text>
@@ -22,10 +28,11 @@
type="number" type="number"
maxlength="11" maxlength="11"
placeholder="输入手机号" placeholder="输入手机号"
placeholder-class="input-placeholder"
v-model="phone" v-model="phone"
/> />
</view> </view>
<view class="input-group"> <view class="input-group">
<text class="input-label">验证码</text> <text class="input-label">验证码</text>
<view class="code-row"> <view class="code-row">
@@ -34,10 +41,11 @@
type="number" type="number"
maxlength="6" maxlength="6"
placeholder="请输入验证码" placeholder="请输入验证码"
placeholder-class="input-placeholder"
v-model="code" v-model="code"
/> />
<view <view
class="code-btn" class="code-btn"
:class="{ disabled: isCountingDown || phone.length !== 11 }" :class="{ disabled: isCountingDown || phone.length !== 11 }"
@click="handleGetCode" @click="handleGetCode"
> >
@@ -47,16 +55,16 @@
</view> </view>
</view> </view>
</view> </view>
<view <view
class="btn-primary login-btn" class="btn-primary login-btn"
:class="{ disabled: !canSubmit || loading }" :class="{ disabled: !canSubmit || loading }"
@click="handleLogin" @click="handleLogin"
> >
<text v-if="loading">登录中...</text> <text v-if="loading">登录中...</text>
<text v-else>开启旅程</text> <text v-else>开启旅程</text>
</view> </view>
<text class="agreement"> <text class="agreement">
登录即代表同意用户协议隐私政策 登录即代表同意用户协议隐私政策
</text> </text>
@@ -77,7 +85,6 @@ const safeAreaTop = ref(uni.getStorageSync('safeAreaTop') || 20)
const safeAreaBottom = ref(uni.getStorageSync('safeAreaBottom') || 0) const safeAreaBottom = ref(uni.getStorageSync('safeAreaBottom') || 0)
onMounted(() => { onMounted(() => {
// 使用新的推荐 API 替代已弃用的 getSystemInfoSync
const windowInfo = uni.getWindowInfo() const windowInfo = uni.getWindowInfo()
statusBarHeight.value = windowInfo.statusBarHeight || 20 statusBarHeight.value = windowInfo.statusBarHeight || 20
safeAreaTop.value = windowInfo.safeAreaInsets?.top || windowInfo.statusBarHeight || 20 safeAreaTop.value = windowInfo.safeAreaInsets?.top || windowInfo.statusBarHeight || 20
@@ -96,18 +103,18 @@ const canSubmit = computed(() => {
const handleGetCode = async () => { const handleGetCode = async () => {
if (isCountingDown.value || loading.value) return if (isCountingDown.value || loading.value) return
if (phone.value.length !== 11) { if (phone.value.length !== 11) {
uni.showToast({ title: '请输入正确的手机号', icon: 'none' }) uni.showToast({ title: '请输入正确的手机号', icon: 'none' })
return return
} }
try { try {
await getSmsCode(phone.value) await getSmsCode(phone.value)
uni.showToast({ title: '验证码已发送', icon: 'success' }) uni.showToast({ title: '验证码已发送', icon: 'success' })
startCountdown() startCountdown()
} catch (error) { } catch (error) {
uni.showToast({ title: '验证码已发送 (模拟: 888888)', icon: 'none' }) uni.showToast({ title: '验证码已发送模拟: 888888', icon: 'none' })
startCountdown() startCountdown()
} }
} }
@@ -115,7 +122,7 @@ const handleGetCode = async () => {
const startCountdown = () => { const startCountdown = () => {
isCountingDown.value = true isCountingDown.value = true
countdown.value = 60 countdown.value = 60
const timer = setInterval(() => { const timer = setInterval(() => {
countdown.value-- countdown.value--
if (countdown.value <= 0) { if (countdown.value <= 0) {
@@ -127,12 +134,12 @@ const startCountdown = () => {
const handleLogin = async () => { const handleLogin = async () => {
if (!canSubmit.value || loading.value) return if (!canSubmit.value || loading.value) return
loading.value = true loading.value = true
try { try {
const result = await store.login(phone.value, code.value) const result = await store.login(phone.value, code.value)
if (result.success) { if (result.success) {
if (result.hasProfile) { if (result.hasProfile) {
uni.redirectTo({ url: '/pages/main/index' }) uni.redirectTo({ url: '/pages/main/index' })
@@ -153,14 +160,16 @@ const handleLogin = async () => {
<style scoped> <style scoped>
.login-page { .login-page {
min-height: 100vh; min-height: 100vh;
background: linear-gradient(180deg, #0F071A 0%, #1A0B2E 50%, #0F071A 100%); background:
radial-gradient(circle at 50% -8%, rgba(180, 129, 255, 0.28), transparent 28%),
linear-gradient(180deg, #13091f 0%, #1b0b31 46%, #100719 100%);
position: relative; position: relative;
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; overflow: hidden;
font-family: -apple-system, BlinkMacSystemFont, 'PingFang SC', 'Microsoft YaHei', sans-serif;
} }
/* 标题字体 - Cinzel (原型标准) */
.title.font-serif { .title.font-serif {
font-family: 'Cinzel', 'Inter', serif; font-family: 'Songti SC', 'STSong', 'Noto Serif SC', serif;
} }
.status-bar { .status-bar {
@@ -177,26 +186,39 @@ const handleLogin = async () => {
pointer-events: none; pointer-events: none;
} }
.aurora-top { .orb {
position: absolute; position: absolute;
top: -10%; border-radius: 999rpx;
left: -10%; filter: blur(72rpx);
width: 120%;
height: 60%;
background: rgba(168, 85, 247, 0.08);
filter: blur(120rpx);
border-radius: 50%;
} }
.aurora-bottom { .orb-primary {
top: 96rpx;
right: -120rpx;
width: 360rpx;
height: 360rpx;
background: rgba(162, 91, 255, 0.24);
}
.orb-secondary {
left: -180rpx;
bottom: 18%;
width: 420rpx;
height: 420rpx;
background: rgba(62, 98, 255, 0.14);
}
.star-field {
position: absolute; position: absolute;
bottom: -10%; top: 132rpx;
right: -10%; left: 64rpx;
width: 100%; right: 64rpx;
height: 50%; height: 360rpx;
background: rgba(139, 92, 246, 0.05); opacity: 0.42;
filter: blur(100rpx); background-image:
border-radius: 50%; radial-gradient(circle, rgba(255, 255, 255, 0.5) 0 1rpx, transparent 2rpx),
radial-gradient(circle, rgba(219, 196, 255, 0.38) 0 1rpx, transparent 2rpx);
background-size: 108rpx 96rpx, 148rpx 132rpx;
} }
.content { .content {
@@ -204,77 +226,110 @@ const handleLogin = async () => {
z-index: 1; z-index: 1;
min-height: 100vh; min-height: 100vh;
display: flex; display: flex;
align-items: center; flex-direction: column;
justify-content: center; justify-content: flex-start;
padding: 40rpx; padding-left: 32rpx;
padding-top: calc(40rpx + constant(safe-area-inset-top)); padding-right: 32rpx;
padding-top: calc(40rpx + env(safe-area-inset-top)); box-sizing: border-box;
}
.brand-lockup {
margin-top: 136rpx;
margin-bottom: 50rpx;
text-align: center;
}
.brand-kicker {
display: block;
margin-bottom: 14rpx;
color: rgba(219, 200, 255, 0.58);
font-size: 18rpx;
font-weight: 600;
letter-spacing: 5rpx;
}
.brand-title {
display: block;
color: rgba(255, 255, 255, 0.9);
font-size: 34rpx;
font-weight: 500;
letter-spacing: 3rpx;
} }
.login-card { .login-card {
width: 100%; width: 100%;
max-width: 720rpx; max-width: 686rpx;
background: rgba(168, 85, 247, 0.05); margin: 0 auto;
backdrop-filter: blur(40rpx); padding: 46rpx 32rpx 36rpx;
border: 1px solid rgba(168, 85, 247, 0.15);
border-radius: 48rpx;
padding: 48rpx 32rpx;
box-sizing: border-box; box-sizing: border-box;
transform: translateY(-12%); background:
linear-gradient(180deg, rgba(58, 29, 91, 0.76), rgba(31, 15, 52, 0.82)),
rgba(34, 15, 57, 0.86);
border: 1rpx solid rgba(217, 190, 255, 0.22);
border-radius: 40rpx;
box-shadow:
0 28rpx 72rpx rgba(0, 0, 0, 0.32),
inset 0 1rpx 0 rgba(255, 255, 255, 0.12);
backdrop-filter: blur(34rpx);
} }
.header { .header {
text-align: center; text-align: center;
margin-bottom: 48rpx; margin-bottom: 42rpx;
} }
.title { .title {
display: block; display: block;
font-size: 42rpx;
font-weight: 300;
color: rgba(255, 255, 255, 0.9);
letter-spacing: 6rpx;
margin-bottom: 12rpx; margin-bottom: 12rpx;
color: rgba(255, 255, 255, 0.94);
font-size: 42rpx;
font-weight: 500;
letter-spacing: 5rpx;
line-height: 1.25;
} }
.subtitle { .subtitle {
display: block; display: block;
color: rgba(225, 209, 255, 0.66);
font-size: 24rpx; font-size: 24rpx;
color: rgba(255, 255, 255, 0.4); line-height: 1.45;
font-style: italic;
} }
.form { .form {
margin-bottom: 40rpx; margin-bottom: 36rpx;
} }
.input-group { .input-group {
margin-bottom: 24rpx; margin-bottom: 24rpx;
} }
.input-group:last-child {
margin-bottom: 0;
}
.input-label { .input-label {
display: block; display: block;
font-size: 18rpx;
color: rgba(255, 255, 255, 0.35);
margin-bottom: 12rpx; margin-bottom: 12rpx;
letter-spacing: 4rpx; color: rgba(234, 223, 255, 0.64);
text-transform: uppercase; font-size: 22rpx;
font-weight: 500;
letter-spacing: 1rpx;
} }
.glass-input { .glass-input {
width: 100%; width: 100%;
height: 80rpx; height: 84rpx;
background: rgba(255, 255, 255, 0.05);
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 24rpx;
padding: 0 28rpx; padding: 0 28rpx;
color: #F3E8FF;
font-size: 26rpx;
box-sizing: border-box; box-sizing: border-box;
color: #fff7ff;
font-size: 27rpx;
background: rgba(255, 255, 255, 0.085);
border: 1rpx solid rgba(255, 255, 255, 0.14);
border-radius: 24rpx;
} }
.glass-input::placeholder { .input-placeholder {
color: rgba(255, 255, 255, 0.3); color: rgba(230, 213, 255, 0.38);
} }
.code-row { .code-row {
@@ -293,64 +348,93 @@ const handleLogin = async () => {
} }
.code-btn { .code-btn {
width: 200rpx; width: 196rpx;
height: 80rpx; height: 84rpx;
background: linear-gradient(135deg, rgba(147, 51, 234, 0.5), rgba(124, 58, 237, 0.4)); color: rgba(255, 255, 255, 0.94);
border: 1px solid rgba(168, 85, 247, 0.4); font-size: 22rpx;
border-radius: 24rpx; font-weight: 600;
color: rgba(255, 255, 255, 0.95);
font-size: 20rpx;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
flex-shrink: 0; flex-shrink: 0;
letter-spacing: 2rpx; border-radius: 24rpx;
background: rgba(132, 72, 204, 0.58);
border: 1rpx solid rgba(217, 190, 255, 0.28);
box-sizing: border-box;
transition: all 0.2s ease; transition: all 0.2s ease;
} }
.code-btn:active { .code-btn:active {
transform: scale(0.97); transform: scale(0.97);
opacity: 0.85; opacity: 0.86;
} }
.code-btn.disabled { .code-btn.disabled {
background: rgba(255, 255, 255, 0.05); color: rgba(226, 213, 255, 0.36);
border-color: rgba(255, 255, 255, 0.1); background: rgba(255, 255, 255, 0.06);
color: rgba(255, 255, 255, 0.3); border-color: rgba(255, 255, 255, 0.09);
} }
.countdown { .countdown {
color: rgba(235, 224, 255, 0.7);
font-size: 26rpx; font-size: 26rpx;
color: rgba(255, 255, 255, 0.6); font-weight: 500;
} }
.btn-primary { .btn-primary {
width: 100%; width: 100%;
height: 88rpx; height: 88rpx;
background: linear-gradient(135deg, #9333EA 0%, #7C3AED 100%); margin-bottom: 24rpx;
border-radius: 32rpx; padding: 0;
box-sizing: border-box;
color: white; color: white;
font-weight: 600; font-weight: 700;
font-size: 28rpx; font-size: 28rpx;
letter-spacing: 1rpx;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
border: none; border: none;
box-shadow: 0 8rpx 32rpx rgba(168, 85, 247, 0.3); border-radius: 28rpx;
margin-bottom: 24rpx; background: linear-gradient(135deg, #a855f7 0%, #6d3be8 100%);
padding: 0; box-shadow:
box-sizing: border-box; 0 14rpx 36rpx rgba(133, 68, 255, 0.34),
inset 0 1rpx 0 rgba(255, 255, 255, 0.22);
transition: all 0.2s ease;
}
.btn-primary:active {
transform: scale(0.98);
box-shadow:
0 8rpx 24rpx rgba(133, 68, 255, 0.24),
inset 0 1rpx 0 rgba(255, 255, 255, 0.18);
} }
.btn-primary.disabled { .btn-primary.disabled {
opacity: 0.5; opacity: 0.48;
} }
.agreement { .agreement {
display: block; display: block;
text-align: center; text-align: center;
color: rgba(220, 205, 246, 0.44);
font-size: 22rpx; font-size: 22rpx;
color: rgba(255, 255, 255, 0.25);
line-height: 1.6; line-height: 1.6;
} }
@media screen and (max-height: 700px) {
.brand-lockup {
margin-top: 76rpx;
margin-bottom: 34rpx;
}
.login-card {
padding-top: 38rpx;
padding-bottom: 30rpx;
}
.header {
margin-bottom: 32rpx;
}
}
</style> </style>