feat(user): add logout, demo account switcher and guest state support
这个提交包含在:
@@ -1,18 +1,19 @@
|
||||
<template>
|
||||
<view class="user-container" :class="{ 'theme-warm': currentThemeKey === 'warm' }" :style="navStyle">
|
||||
<!-- 用户头部个人资料卡 -->
|
||||
<view class="profile-card">
|
||||
<image class="user-avatar" :src="user.avatar" mode="aspectFill" />
|
||||
<view class="profile-card" @click="!isLoggedIn && openLoginModal()">
|
||||
<image class="user-avatar" :src="isLoggedIn && user.avatar ? user.avatar : DEFAULT_GUEST_AVATAR" mode="aspectFill" />
|
||||
<view class="user-info">
|
||||
<!-- 顶层水平对齐行:昵称/VIP 与 月亮太阳切换/设置 在同一水平线 -->
|
||||
<view class="user-top-row">
|
||||
<view class="name-row">
|
||||
<text class="user-name">{{ user.nickname }}</text>
|
||||
<text class="vip-badge">VIP 黄金玩家</text>
|
||||
<text class="user-name">{{ isLoggedIn ? user.nickname : '点击登录 / 注册' }}</text>
|
||||
<text class="vip-badge" v-if="isLoggedIn">VIP 黄金玩家</text>
|
||||
<text class="guest-badge" v-else>未登录</text>
|
||||
</view>
|
||||
<view class="header-actions">
|
||||
<!-- 核心优化:月亮与太阳快捷切换胶囊 (与左侧昵称在同一水平线上) -->
|
||||
<view class="theme-quick-switch btn-active" @click="toggleThemeDirectly" title="切换界面风格">
|
||||
<view class="theme-quick-switch btn-active" @click.stop="toggleThemeDirectly" title="切换界面风格">
|
||||
<view
|
||||
class="switch-icon-btn moon-btn"
|
||||
:class="{ active: currentThemeKey === 'cool' }"
|
||||
@@ -28,12 +29,12 @@
|
||||
<image class="theme-icon-svg" :src="ICONS.sun" mode="aspectFit" />
|
||||
</view>
|
||||
</view>
|
||||
<view class="edit-btn btn-active" @click="editProfile">
|
||||
<view class="edit-btn btn-active" v-if="isLoggedIn" @click.stop="editProfile">
|
||||
<image class="edit-svg" :src="ICONS.settings" mode="aspectFit" />
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<text class="user-uid">UID: {{ user.id }}</text>
|
||||
<text class="user-uid">{{ isLoggedIn ? `UID: ${user.id}` : '登录后享受完整陪伴服务' }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@@ -68,13 +69,13 @@
|
||||
<view class="asset-grid">
|
||||
<view class="asset-col">
|
||||
<text class="asset-label">可用余额 (元)</text>
|
||||
<text class="asset-val green-val">¥{{ user.balance }}</text>
|
||||
<text class="asset-val green-val">{{ isLoggedIn ? `¥${user.balance}` : '--' }}</text>
|
||||
<button class="asset-btn recharge btn-active" @click="handleRecharge">充值</button>
|
||||
</view>
|
||||
<view class="asset-divider"></view>
|
||||
<view class="asset-col">
|
||||
<text class="asset-label">分销佣金收益 (元)</text>
|
||||
<text class="asset-val gold-val">¥{{ user.commission }}</text>
|
||||
<text class="asset-val gold-val">{{ isLoggedIn ? `¥${user.commission}` : '--' }}</text>
|
||||
<button class="asset-btn withdraw btn-active" @click="handleWithdraw">提现</button>
|
||||
</view>
|
||||
</view>
|
||||
@@ -141,6 +142,55 @@
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 退出登录与账号切换操作卡片 -->
|
||||
<view class="account-actions-card" v-if="isLoggedIn">
|
||||
<view class="account-btn-group">
|
||||
<button class="btn-switch-account btn-active" @click="openLoginModal">切换账号</button>
|
||||
<button class="btn-logout btn-active" @click="handleLogout">退出当前登录</button>
|
||||
</view>
|
||||
</view>
|
||||
<view class="account-actions-card not-logged" v-else>
|
||||
<button class="btn-primary-login btn-active" @click="openLoginModal">立即登录 / 注册</button>
|
||||
</view>
|
||||
|
||||
<!-- 登录与账号切换快捷弹窗 -->
|
||||
<view class="login-modal-mask" v-if="showLoginModal" @click="showLoginModal = false">
|
||||
<view class="login-modal" @click.stop>
|
||||
<view class="modal-header">
|
||||
<text class="modal-title">账号登录与快捷切换</text>
|
||||
<view class="btn-close btn-active" @click="showLoginModal = false">
|
||||
<image class="close-svg" :src="ICONS.close" mode="aspectFit" />
|
||||
</view>
|
||||
</view>
|
||||
<view class="modal-body">
|
||||
<button class="btn-wechat-auth btn-active" @click="handleWechatLogin">
|
||||
微信一键授权快捷登录
|
||||
</button>
|
||||
|
||||
<view class="demo-accounts-wrap">
|
||||
<text class="demo-tips">支持一键切换指定身份体验账号:</text>
|
||||
<view
|
||||
v-for="acc in DEMO_ACCOUNTS"
|
||||
:key="acc.id"
|
||||
class="demo-account-item btn-active"
|
||||
:class="{ current: Number(user.id) === acc.id && isLoggedIn }"
|
||||
@click="switchDemoAccount(acc)"
|
||||
>
|
||||
<image class="demo-avatar" :src="acc.avatar" mode="aspectFill" />
|
||||
<view class="demo-info">
|
||||
<view class="demo-title-row">
|
||||
<text class="demo-name">{{ acc.nickname }}</text>
|
||||
<text class="demo-tag" :class="acc.roleTag">{{ acc.roleDesc }}</text>
|
||||
</view>
|
||||
<text class="demo-desc">UID: {{ acc.id }} · {{ acc.desc }}</text>
|
||||
</view>
|
||||
<text class="demo-action-btn">{{ Number(user.id) === acc.id && isLoggedIn ? '当前登录' : '切换至此号' }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 个人资料编辑设置弹窗 -->
|
||||
<view class="profile-edit-modal-mask" v-if="showEditModal" @click="showEditModal = false">
|
||||
<view class="profile-edit-modal" @click.stop>
|
||||
@@ -293,6 +343,37 @@ import { getCurrentRole, setCurrentRole, ROLE_TYPES, toggleRole as toggleUserRol
|
||||
import { getNavBarInfo } from '@/common/nav.js';
|
||||
import { getUserProfile, rechargeBalance, withdrawCommission, getUserFinanceRecords, updateUserProfile, uploadImage, getRechargeTiers, getPlatformConfigs } from '@/common/api.js';
|
||||
|
||||
const DEFAULT_GUEST_AVATAR = 'https://images.unsplash.com/photo-1535713875002-d1d0cf377fde?w=200';
|
||||
const isLoggedIn = ref(true);
|
||||
const showLoginModal = ref(false);
|
||||
|
||||
const DEMO_ACCOUNTS = [
|
||||
{
|
||||
id: 1001,
|
||||
nickname: '同城局长',
|
||||
roleTag: 'host',
|
||||
roleDesc: '房主/局长',
|
||||
desc: '余额充足 · 组局发单',
|
||||
avatar: 'https://images.unsplash.com/photo-1535713875002-d1d0cf377fde?w=200'
|
||||
},
|
||||
{
|
||||
id: 2001,
|
||||
nickname: '超能电竞玩家',
|
||||
roleTag: 'buyer',
|
||||
roleDesc: '普通买家',
|
||||
desc: '搭子报名 · 预约下单',
|
||||
avatar: 'https://images.unsplash.com/photo-1534528741775-53994a69daeb?w=200'
|
||||
},
|
||||
{
|
||||
id: 101,
|
||||
nickname: '苏苏学姐',
|
||||
roleTag: 'expert',
|
||||
roleDesc: '金牌陪伴师',
|
||||
desc: '接单工作台 · 4位核销',
|
||||
avatar: 'https://images.unsplash.com/photo-1494790108377-be9c29b29330?w=200'
|
||||
}
|
||||
];
|
||||
|
||||
const user = ref({
|
||||
id: 1001,
|
||||
nickname: '电竞玩家',
|
||||
@@ -359,9 +440,99 @@ const onRoleChanged = (role) => {
|
||||
currentRole.value = role;
|
||||
};
|
||||
|
||||
const loadUserProfile = async () => {
|
||||
const openLoginModal = () => {
|
||||
showLoginModal.value = true;
|
||||
};
|
||||
|
||||
const handleWechatLogin = () => {
|
||||
uni.showLoading({ title: '正在授权登录...' });
|
||||
setTimeout(() => {
|
||||
uni.hideLoading();
|
||||
showLoginModal.value = false;
|
||||
isLoggedIn.value = true;
|
||||
uni.removeStorageSync('user_has_logged_out');
|
||||
uni.setStorageSync('userId', 1001);
|
||||
loadUserProfile(1001);
|
||||
uni.showToast({ title: '登录成功', icon: 'success' });
|
||||
}, 400);
|
||||
};
|
||||
|
||||
const switchDemoAccount = async (acc) => {
|
||||
showLoginModal.value = false;
|
||||
uni.showLoading({ title: '正在切换账号...' });
|
||||
isLoggedIn.value = true;
|
||||
uni.removeStorageSync('user_has_logged_out');
|
||||
uni.setStorageSync('userId', acc.id);
|
||||
user.value = {
|
||||
...user.value,
|
||||
id: acc.id,
|
||||
nickname: acc.nickname,
|
||||
avatar: acc.avatar
|
||||
};
|
||||
await loadUserProfile(acc.id);
|
||||
uni.hideLoading();
|
||||
uni.showToast({
|
||||
title: `已切换至【${acc.nickname}】`,
|
||||
icon: 'success'
|
||||
});
|
||||
};
|
||||
|
||||
const handleLogout = () => {
|
||||
uni.showModal({
|
||||
title: '确认退出登录?',
|
||||
content: '退出登录后将清除本地会话,切换为未登录游客态。是否确认退出?',
|
||||
confirmText: '退出登录',
|
||||
confirmColor: '#EF4444',
|
||||
cancelText: '取消',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
uni.removeStorageSync('userInfo');
|
||||
uni.removeStorageSync('userId');
|
||||
uni.setStorageSync('user_has_logged_out', '1');
|
||||
isLoggedIn.value = false;
|
||||
user.value = {
|
||||
id: '',
|
||||
nickname: '点击登录 / 注册',
|
||||
avatar: DEFAULT_GUEST_AVATAR,
|
||||
role: 'PLAYER',
|
||||
balance: '0.00',
|
||||
commission: '0.00',
|
||||
orderCount: 0,
|
||||
invitedCount: 0,
|
||||
phone: ''
|
||||
};
|
||||
uni.showToast({
|
||||
title: '已退出登录',
|
||||
icon: 'none',
|
||||
duration: 1500
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const loadUserProfile = async (targetId) => {
|
||||
const hasLoggedOut = uni.getStorageSync('user_has_logged_out');
|
||||
if (hasLoggedOut && !targetId) {
|
||||
isLoggedIn.value = false;
|
||||
user.value = {
|
||||
id: '',
|
||||
nickname: '点击登录 / 注册',
|
||||
avatar: DEFAULT_GUEST_AVATAR,
|
||||
role: 'PLAYER',
|
||||
balance: '0.00',
|
||||
commission: '0.00',
|
||||
orderCount: 0,
|
||||
invitedCount: 0,
|
||||
phone: ''
|
||||
};
|
||||
return;
|
||||
}
|
||||
|
||||
isLoggedIn.value = true;
|
||||
const uid = targetId || uni.getStorageSync('userId') || 1001;
|
||||
try {
|
||||
const profile = await getUserProfile(1001);
|
||||
const profile = await getUserProfile(uid);
|
||||
if (profile) {
|
||||
user.value = {
|
||||
...user.value,
|
||||
@@ -420,6 +591,7 @@ const handleSwitchTheme = () => {
|
||||
};
|
||||
|
||||
const toggleRole = () => {
|
||||
if (!ensureLoggedIn()) return;
|
||||
if (!isExpertCertified()) {
|
||||
uni.showModal({
|
||||
title: '大神实名认证提醒',
|
||||
@@ -468,6 +640,7 @@ const editForm = ref({
|
||||
});
|
||||
|
||||
const editProfile = () => {
|
||||
if (!ensureLoggedIn()) return;
|
||||
editForm.value = {
|
||||
nickname: user.value.nickname || '',
|
||||
avatar: user.value.avatar || PRESET_AVATARS[0],
|
||||
@@ -535,9 +708,19 @@ const saveProfile = async () => {
|
||||
}
|
||||
};
|
||||
|
||||
const ensureLoggedIn = () => {
|
||||
if (!isLoggedIn.value) {
|
||||
uni.showToast({ title: '请先登录账号', icon: 'none' });
|
||||
openLoginModal();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
const viewWalletLog = async () => {
|
||||
if (!ensureLoggedIn()) return;
|
||||
try {
|
||||
const list = await getUserFinanceRecords(1001);
|
||||
const list = await getUserFinanceRecords(user.value.id || 1001);
|
||||
if (!list || list.length === 0) {
|
||||
uni.showModal({
|
||||
title: '账单明细',
|
||||
@@ -563,6 +746,7 @@ const viewWalletLog = async () => {
|
||||
};
|
||||
|
||||
const handleRecharge = async () => {
|
||||
if (!ensureLoggedIn()) return;
|
||||
let tiers = [];
|
||||
try {
|
||||
const list = await getRechargeTiers();
|
||||
@@ -613,6 +797,7 @@ const handleRecharge = async () => {
|
||||
};
|
||||
|
||||
const handleWithdraw = () => {
|
||||
if (!ensureLoggedIn()) return;
|
||||
const comm = Number(user.value.commission || 0);
|
||||
const finRules = platformConfigs.value.finance_rules || {};
|
||||
const minWithdraw = Number(finRules.minWithdrawAmount) || 50;
|
||||
@@ -635,7 +820,7 @@ const handleWithdraw = () => {
|
||||
success: async (res) => {
|
||||
if (res.confirm) {
|
||||
try {
|
||||
const result = await withdrawCommission(1001, comm);
|
||||
const result = await withdrawCommission(user.value.id || 1001, comm);
|
||||
if (result && result.commission !== undefined) {
|
||||
user.value.commission = result.commission;
|
||||
}
|
||||
@@ -656,6 +841,7 @@ const handleWithdraw = () => {
|
||||
};
|
||||
|
||||
const showInvitePoster = () => {
|
||||
if (!ensureLoggedIn()) return;
|
||||
const finRules = platformConfigs.value.finance_rules || {};
|
||||
const firstRate = finRules.firstReferralPercent != null ? finRules.firstReferralPercent : 5.0;
|
||||
const secondRate = finRules.secondReferralPercent != null ? finRules.secondReferralPercent : 2.0;
|
||||
@@ -751,6 +937,7 @@ const submitExpertCertForm = async () => {
|
||||
};
|
||||
|
||||
const applyExpert = () => {
|
||||
if (!ensureLoggedIn()) return;
|
||||
const certStatus = getExpertCertStatus();
|
||||
if (certStatus === CERT_STATUS.APPROVED) {
|
||||
uni.showToast({
|
||||
@@ -872,6 +1059,7 @@ const openAbout = () => {
|
||||
};
|
||||
|
||||
const navTo = (url) => {
|
||||
if (!ensureLoggedIn()) return;
|
||||
if (url.startsWith('/pages/activity/list')) {
|
||||
if (url.includes('tab=')) {
|
||||
const tab = url.split('tab=')[1].split('&')[0];
|
||||
@@ -1693,6 +1881,274 @@ const navTo = (url) => {
|
||||
}
|
||||
}
|
||||
|
||||
.guest-badge {
|
||||
font-size: 20rpx;
|
||||
color: #94A3B8;
|
||||
background: rgba(148, 163, 184, 0.15);
|
||||
border: 1px solid rgba(148, 163, 184, 0.3);
|
||||
padding: 4rpx 14rpx;
|
||||
border-radius: 20rpx;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.account-actions-card {
|
||||
margin-top: 24rpx;
|
||||
margin-bottom: 30rpx;
|
||||
|
||||
.account-btn-group {
|
||||
display: flex;
|
||||
gap: 20rpx;
|
||||
|
||||
.btn-switch-account {
|
||||
flex: 1;
|
||||
height: 84rpx;
|
||||
line-height: 84rpx;
|
||||
background: rgba(99, 102, 241, 0.12);
|
||||
border: 1px solid rgba(99, 102, 241, 0.3);
|
||||
color: #818CF8;
|
||||
font-size: 26rpx;
|
||||
font-weight: 600;
|
||||
border-radius: 24rpx;
|
||||
}
|
||||
|
||||
.btn-logout {
|
||||
flex: 1;
|
||||
height: 84rpx;
|
||||
line-height: 84rpx;
|
||||
background: rgba(239, 68, 68, 0.12);
|
||||
border: 1px solid rgba(239, 68, 68, 0.3);
|
||||
color: #F87171;
|
||||
font-size: 26rpx;
|
||||
font-weight: 600;
|
||||
border-radius: 24rpx;
|
||||
}
|
||||
}
|
||||
|
||||
&.not-logged {
|
||||
.btn-primary-login {
|
||||
width: 100%;
|
||||
height: 88rpx;
|
||||
line-height: 88rpx;
|
||||
background: linear-gradient(135deg, #6366F1, #8B5CF6);
|
||||
color: #FFFFFF;
|
||||
font-size: 28rpx;
|
||||
font-weight: 700;
|
||||
border-radius: 24rpx;
|
||||
box-shadow: 0 4rpx 20rpx rgba(99, 102, 241, 0.35);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.login-modal-mask {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0, 0, 0, 0.75);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 1000;
|
||||
padding: 40rpx;
|
||||
backdrop-filter: blur(6px);
|
||||
}
|
||||
|
||||
.login-modal {
|
||||
width: 100%;
|
||||
max-width: 640rpx;
|
||||
background: #151D30;
|
||||
border-radius: 32rpx;
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
padding: 36rpx;
|
||||
box-shadow: 0 20rpx 60rpx rgba(0, 0, 0, 0.6);
|
||||
|
||||
.modal-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 24rpx;
|
||||
|
||||
.modal-title {
|
||||
font-size: 30rpx;
|
||||
font-weight: 700;
|
||||
color: #F8FAFC;
|
||||
}
|
||||
|
||||
.btn-close .close-svg {
|
||||
width: 32rpx;
|
||||
height: 32rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.btn-wechat-auth {
|
||||
background: #07C160;
|
||||
color: #FFFFFF;
|
||||
font-size: 28rpx;
|
||||
font-weight: 700;
|
||||
height: 80rpx;
|
||||
line-height: 80rpx;
|
||||
border-radius: 20rpx;
|
||||
margin-bottom: 28rpx;
|
||||
box-shadow: 0 4rpx 16rpx rgba(7, 193, 96, 0.35);
|
||||
}
|
||||
|
||||
.demo-accounts-wrap {
|
||||
.demo-tips {
|
||||
font-size: 22rpx;
|
||||
color: #94A3B8;
|
||||
display: block;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.demo-account-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16rpx;
|
||||
padding: 16rpx;
|
||||
border-radius: 18rpx;
|
||||
background: rgba(255, 255, 255, 0.04);
|
||||
border: 1px solid rgba(255, 255, 255, 0.06);
|
||||
margin-bottom: 12rpx;
|
||||
|
||||
&.current {
|
||||
border-color: #6366F1;
|
||||
background: rgba(99, 102, 241, 0.12);
|
||||
}
|
||||
|
||||
.demo-avatar {
|
||||
width: 68rpx;
|
||||
height: 68rpx;
|
||||
border-radius: 50%;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.demo-info {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
|
||||
.demo-title-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12rpx;
|
||||
|
||||
.demo-name {
|
||||
font-size: 24rpx;
|
||||
font-weight: 700;
|
||||
color: #F8FAFC;
|
||||
}
|
||||
|
||||
.demo-tag {
|
||||
font-size: 18rpx;
|
||||
padding: 2rpx 8rpx;
|
||||
border-radius: 6rpx;
|
||||
|
||||
&.host {
|
||||
background: rgba(245, 158, 11, 0.2);
|
||||
color: #F59E0B;
|
||||
}
|
||||
&.buyer {
|
||||
background: rgba(99, 102, 241, 0.2);
|
||||
color: #818CF8;
|
||||
}
|
||||
&.expert {
|
||||
background: rgba(16, 185, 129, 0.2);
|
||||
color: #10B981;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.demo-desc {
|
||||
font-size: 20rpx;
|
||||
color: #94A3B8;
|
||||
margin-top: 4rpx;
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
.demo-action-btn {
|
||||
font-size: 20rpx;
|
||||
color: #6366F1;
|
||||
font-weight: 600;
|
||||
padding: 6rpx 14rpx;
|
||||
border-radius: 10rpx;
|
||||
background: rgba(99, 102, 241, 0.15);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.theme-warm {
|
||||
.guest-badge {
|
||||
color: #78716C;
|
||||
background: #F5F2EC;
|
||||
border-color: #E7E5E4;
|
||||
}
|
||||
|
||||
.account-actions-card {
|
||||
.account-btn-group {
|
||||
.btn-switch-account {
|
||||
background: rgba(234, 88, 12, 0.08);
|
||||
border-color: rgba(234, 88, 12, 0.25);
|
||||
color: #EA580C;
|
||||
}
|
||||
|
||||
.btn-logout {
|
||||
background: #FEE2E2;
|
||||
border-color: #FCA5A5;
|
||||
color: #DC2626;
|
||||
}
|
||||
}
|
||||
|
||||
&.not-logged {
|
||||
.btn-primary-login {
|
||||
background: linear-gradient(135deg, #F97316 0%, #EA580C 100%);
|
||||
box-shadow: 0 4rpx 20rpx rgba(234, 88, 12, 0.35);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.login-modal {
|
||||
background: #FFFFFF;
|
||||
border-color: #F0ECE4;
|
||||
box-shadow: 0 20rpx 60rpx rgba(28, 25, 23, 0.15);
|
||||
|
||||
.modal-header .modal-title {
|
||||
color: #1C1917;
|
||||
}
|
||||
|
||||
.demo-accounts-wrap {
|
||||
.demo-tips {
|
||||
color: #78716C;
|
||||
}
|
||||
|
||||
.demo-account-item {
|
||||
background: #FAFAF9;
|
||||
border-color: #E7E5E4;
|
||||
|
||||
&.current {
|
||||
border-color: #EA580C;
|
||||
background: rgba(234, 88, 12, 0.08);
|
||||
}
|
||||
|
||||
.demo-info {
|
||||
.demo-title-row .demo-name {
|
||||
color: #1C1917;
|
||||
}
|
||||
.demo-desc {
|
||||
color: #78716C;
|
||||
}
|
||||
}
|
||||
|
||||
.demo-action-btn {
|
||||
color: #EA580C;
|
||||
background: rgba(234, 88, 12, 0.12);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes glowSpin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
|
||||
@@ -375,5 +375,35 @@ describe('ContentSecurityService & Compliance Architecture (TDD 微信内容安
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('10. 个人中心未登录游客态、退出登录及切换账号门禁验证 (用户体验闭环与安全防范)', () => {
|
||||
it('个人中心页面必须包含退出登录、切换账号与登录弹窗入口', () => {
|
||||
const userIndexPath = path.join(rootDir, 'pwuniapp', 'pages', 'user', 'index.vue');
|
||||
const content = fs.readFileSync(userIndexPath, 'utf-8');
|
||||
expect(content).toContain('handleLogout');
|
||||
expect(content).toContain('switchDemoAccount');
|
||||
expect(content).toContain('openLoginModal');
|
||||
expect(content).toContain('isLoggedIn');
|
||||
expect(content).toContain('account-actions-card');
|
||||
expect(content).toContain('login-modal-mask');
|
||||
expect(content).toContain('退出当前登录');
|
||||
expect(content).toContain('切换账号');
|
||||
expect(content).toContain('微信一键授权快捷登录');
|
||||
});
|
||||
|
||||
it('个人中心核心交互必须包含未登录拦截守护 ensureLoggedIn', () => {
|
||||
const userIndexPath = path.join(rootDir, 'pwuniapp', 'pages', 'user', 'index.vue');
|
||||
const content = fs.readFileSync(userIndexPath, 'utf-8');
|
||||
expect(content).toContain('const ensureLoggedIn = () =>');
|
||||
expect(content).toContain('if (!ensureLoggedIn()) return;');
|
||||
});
|
||||
|
||||
it('所有个人中心改造代码严禁包含任何系统 Emoji', () => {
|
||||
const userIndexPath = path.join(rootDir, 'pwuniapp', 'pages', 'user', 'index.vue');
|
||||
const text = fs.readFileSync(userIndexPath, 'utf-8');
|
||||
const emojiRegex = /(?:\p{Extended_Pictographic}|\p{Emoji_Presentation}|[\u2600-\u27BF])/u;
|
||||
expect(emojiRegex.test(text)).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
在新工单中引用
屏蔽一个用户