feat: 微信分享钩子全页面接入 + 订阅信息结构化映射 + JWT 错误处理加强

- client: 全部页面接入 onShareAppMessage/onShareTimeline 分享钩子
- client: login 页增强、profile 订阅展示优化
- client/stores: 订阅字段扁平结构映射为 subInfo(plan/expiresAt/startDate)
- server: auth 路由 JWT 无效/过期统一返回 401 未授权响应
- server: models/tests 同步更新
This commit is contained in:
Yuzhiran Dev
2026-09-04 11:47:29 +08:00
parent de8ca9c420
commit 66de1dc396
37 changed files with 2138 additions and 1830 deletions
+87 -2
View File
@@ -4,6 +4,14 @@
<view class="bg-orb orb-2"></view>
<view class="bg-orb orb-3"></view>
<!-- 顶部返回 -->
<view class="nav-bar">
<view class="nav-back" @click="goBack">
<text class="nav-back-icon">←</text>
<text class="nav-back-text">返回</text>
</view>
</view>
<view class="login-container">
<view class="logo-wrap">
<view class="logo-ring"></view>
@@ -28,6 +36,11 @@
<input class="form-input" v-model="loginForm.password" type="password" placeholder="请输入密码" />
</view>
<!-- 忘记密码 -->
<view class="forgot-row">
<text class="forgot-link" @click="goForgotPassword">忘记密码?</text>
</view>
<!-- 登录协议勾选 -->
<view class="agreement-row">
<view class="agreement-checkbox" :class="{ checked: loginAgreed }" @click="loginAgreed = !loginAgreed">
@@ -45,12 +58,14 @@
<text>{{ loading ? '登录中...' : '登录' }}</text>
</view>
<!-- 微信小程序一键登录 -->
<!-- #ifdef MP-WEIXIN -->
<view class="wx-login-row" v-if="isMiniApp">
<view class="wx-login-btn" @click="wechatLogin">
<text class="wx-icon">💬</text>
<text>微信一键登录</text>
</view>
</view>
<!-- #endif -->
</view>
<!-- 注册表单 -->
@@ -59,6 +74,10 @@
<text class="form-label">邮箱 <text style="color:var(--color-vip);font-size:11px;">*</text></text>
<input class="form-input" v-model="registerForm.email" type="email" placeholder="请输入邮箱地址" @input="registerForm.email = $event.detail.value.trim(); verifyCodeSent = false" />
</view>
<view class="form-item">
<text class="form-label">用户名 <text style="color:var(--color-vip);font-size:11px;">*</text></text>
<input class="form-input" v-model="registerForm.username" placeholder="请输入用户名(用于登录)" @input="registerForm.username = $event.detail.value.trim()" />
</view>
<view class="form-item">
<text class="form-label">昵称 <text style="color:var(--text-muted);font-size:11px;">(选填)</text></text>
<input class="form-input" v-model="registerForm.nickName" placeholder="显示名称,留空自动生成" />
@@ -100,21 +119,30 @@
<view class="footer-info">
<text>© 2026 宇之然 · AI 维度</text>
</view>
<!-- 显著有效的「拒绝/暂不登录」入口,避免强制登录 -->
<view class="skip-login" @click="goBrowse">
<text>暂不登录,先体验</text>
</view>
</view>
</view>
</template>
<script setup>
import { onShareAppMessage, onShareTimeline } from '@dcloudio/uni-app'
import { ref } from 'vue'
import { useUserStore } from '@/stores/index'
const isMiniApp = ref(false)
// #ifdef MP-WEIXIN
try {
const sysInfo = uni.getSystemInfoSync()
if (sysInfo && sysInfo.version && sysInfo.platform) {
isMiniApp.value = true
}
} catch (e) {}
// #endif
const activeTab = ref('login')
const loading = ref(false)
@@ -127,7 +155,7 @@ let countdownTimer = null
const userStore = useUserStore()
const loginForm = ref({ email: '', password: '' })
const registerForm = ref({ email: '', password: '', nickName: '', verifyCode: '' })
const registerForm = ref({ email: '', username: '', password: '', nickName: '', verifyCode: '' })
const BASE_URL = 'https://wdkj.yuzhiran.com.cn'
@@ -241,6 +269,10 @@ const handleRegister = async () => {
uni.showToast({ title: '请输入密码', icon: 'none' })
return
}
if (!registerForm.value.username) {
uni.showToast({ title: '请输入用户名', icon: 'none' })
return
}
if (registerForm.value.password.length < 6) {
uni.showToast({ title: '密码至少 6 位', icon: 'none' })
return
@@ -255,7 +287,8 @@ const handleRegister = async () => {
registerForm.value.email,
registerForm.value.password,
registerForm.value.nickName || '',
registerForm.value.verifyCode
registerForm.value.verifyCode,
registerForm.value.username
)
uni.showToast({ title: '注册成功', icon: 'success' })
setTimeout(() => uni.switchTab({ url: '/pages/index/index' }), 1000)
@@ -274,11 +307,26 @@ function openAgreement(type) {
uni.navigateTo({ url: pages[type] || pages.terms })
}
function goBack() {
uni.navigateBack({ fallback: () => uni.switchTab({ url: '/pages/index/index' }) })
}
/** 拒绝登录:返回首页以游客身份浏览,不强制登录 */
function goBrowse() {
uni.switchTab({ url: '/pages/index/index' })
}
function goForgotPassword() {
uni.showToast({ title: '请联系管理员重置密码', icon: 'none' })
}
// 微信小程序分享
// #ifdef MP-WEIXIN
onShareAppMessage(() => ({
title: 'AI 维度 - 你的AI知识助手',
path: '/pages/index/index'
}))
// #endif
</script>
<style lang="scss" scoped>
@@ -303,6 +351,19 @@ onShareAppMessage(() => ({
.orb-2 { width: 160px; height: 160px; top: 30%; right: -40px; background: var(--dim3-color); }
.orb-3 { width: 180px; height: 180px; bottom: -60px; left: 30%; background: var(--dim5-color); }
// 顶部导航栏
.nav-bar {
position: absolute; top: 0; left: 0; right: 0; z-index: 10;
padding: 44px 20px 0;
.nav-back {
display: inline-flex; align-items: center; gap: 4px;
padding: 8px 12px; cursor: pointer;
&:active { opacity: 0.6; }
}
.nav-back-icon { font-size: 20px; color: var(--text-primary); }
.nav-back-text { font-size: 14px; color: var(--text-secondary); }
}
.login-container {
position: relative; z-index: 1;
width: 100%; max-width: 360px;
@@ -354,6 +415,15 @@ onShareAppMessage(() => ({
}
}
// 忘记密码
.forgot-row {
display: flex; justify-content: flex-end; margin: -8px 0 0;
.forgot-link {
font-size: 12px; color: var(--color-brand); padding: 4px 0;
&:active { opacity: 0.6; }
}
}
// 验证码行
.verify-row {
display: flex; gap: 10px;
@@ -417,5 +487,20 @@ onShareAppMessage(() => ({
}
.footer-info { margin-top: 40px; font-size: 11px; color: var(--text-muted); }
// 暂不登录入口(拒绝/返回,显著有效)
.skip-login {
margin-top: 24px;
padding: 14px 0;
width: 100%;
text-align: center;
font-size: 14px;
color: var(--text-secondary);
background: var(--glass-bg);
border: 1px solid var(--glass-border);
border-radius: 12px;
cursor: pointer;
&:active { opacity: 0.7; }
}
}
</style>