fix(core): comprehensive fix for state loss across activity, ticket, workspace, favorites and community

这个提交包含在:
xcbyy
2026-10-13 16:01:19 +08:00
父节点 497fb65359
当前提交 1853e653fb
共有 7 个文件被更改,包括 242 次插入 和 61 次删除

查看文件

@@ -169,9 +169,10 @@ export async function leaveActivity(activityId, userId = 2001) {
/** /**
* 获取“我的组局”(发起的 + 报名的) * 获取“我的组局”(发起的 + 报名的)
*/ */
export async function getMyActivities(userId = 1001) { export async function getMyActivities(userId) {
const uid = normalizeUserId(userId);
try { try {
const res = await http.get(`/users/${userId}/my-activities`); const res = await http.get(`/users/${uid}/my-activities`);
if (res) { if (res) {
return { return {
created: (res.created || []).map(formatActivityData), created: (res.created || []).map(formatActivityData),
@@ -477,7 +478,13 @@ export async function likePost(id, action, userId) {
* 规范化用户ID,自动兼容历史测试账号映射 (将虚拟陪伴师 101 映射至真实用户 2006) * 规范化用户ID,自动兼容历史测试账号映射 (将虚拟陪伴师 101 映射至真实用户 2006)
*/ */
function normalizeUserId(uid) { function normalizeUserId(uid) {
const n = Number(uid); let target = uid;
if (!target && target !== 0) {
try {
target = uni.getStorageSync('userId');
} catch (_) {}
}
const n = Number(target);
if (n === 101) return 2006; if (n === 101) return 2006;
return (!isNaN(n) && n > 0) ? n : 1001; return (!isNaN(n) && n > 0) ? n : 1001;
} }
@@ -952,9 +959,10 @@ export async function updateExpertServiceStatus(expertId = 101, serviceId, statu
/** /**
* 获取用户收藏的陪玩列表 * 获取用户收藏的陪玩列表
*/ */
export async function getFavorites(userId = 1001) { export async function getFavorites(userId) {
const uid = normalizeUserId(userId);
try { try {
const res = await http.get(`/favorites?userId=${userId}`); const res = await http.get(`/favorites?userId=${uid}`);
return res || []; return res || [];
} catch (err) { } catch (err) {
console.warn('[API] 获取收藏列表异常:', err.message); console.warn('[API] 获取收藏列表异常:', err.message);
@@ -965,9 +973,10 @@ export async function getFavorites(userId = 1001) {
/** /**
* 探测是否已收藏特定陪玩 * 探测是否已收藏特定陪玩
*/ */
export async function checkFavorite(expertId, userId = 1001) { export async function checkFavorite(expertId, userId) {
const uid = normalizeUserId(userId);
try { try {
const res = await http.get(`/favorites/check?userId=${userId}&expertId=${expertId}`); const res = await http.get(`/favorites/check?userId=${uid}&expertId=${expertId}`);
return res && res.isFavorite; return res && res.isFavorite;
} catch (err) { } catch (err) {
console.warn('[API] 探测收藏状态异常:', err.message); console.warn('[API] 探测收藏状态异常:', err.message);
@@ -978,9 +987,10 @@ export async function checkFavorite(expertId, userId = 1001) {
/** /**
* 添加收藏 * 添加收藏
*/ */
export async function addFavorite(expertId, userId = 1001) { export async function addFavorite(expertId, userId) {
const uid = normalizeUserId(userId);
try { try {
const res = await http.post('/favorites', { userId, expertId }); const res = await http.post('/favorites', { userId: uid, expertId });
return res; return res;
} catch (err) { } catch (err) {
console.warn('[API] 添加收藏异常:', err.message); console.warn('[API] 添加收藏异常:', err.message);
@@ -991,9 +1001,10 @@ export async function addFavorite(expertId, userId = 1001) {
/** /**
* 取消收藏 * 取消收藏
*/ */
export async function removeFavorite(expertId, userId = 1001) { export async function removeFavorite(expertId, userId) {
const uid = normalizeUserId(userId);
try { try {
const res = await http.delete(`/favorites/${expertId}?userId=${userId}`); const res = await http.delete(`/favorites/${expertId}?userId=${uid}`);
return res; return res;
} catch (err) { } catch (err) {
console.warn('[API] 取消收藏异常:', err.message); console.warn('[API] 取消收藏异常:', err.message);
@@ -1004,9 +1015,10 @@ export async function removeFavorite(expertId, userId = 1001) {
/** /**
* 获取分销团队列表 (一级与二级成员) * 获取分销团队列表 (一级与二级成员)
*/ */
export async function getDistributionTeam(userId = 1001) { export async function getDistributionTeam(userId) {
const uid = normalizeUserId(userId);
try { try {
const res = await http.get(`/distribution/team?userId=${userId}`); const res = await http.get(`/distribution/team?userId=${uid}`);
return res || { directCount: 0, indirectCount: 0, totalTeamCount: 0, directList: [], indirectList: [] }; return res || { directCount: 0, indirectCount: 0, totalTeamCount: 0, directList: [], indirectList: [] };
} catch (err) { } catch (err) {
console.warn('[API] 获取分销团队异常:', err.message); console.warn('[API] 获取分销团队异常:', err.message);
@@ -1017,9 +1029,10 @@ export async function getDistributionTeam(userId = 1001) {
/** /**
* 获取分销返佣订单记录 * 获取分销返佣订单记录
*/ */
export async function getDistributionRecords(userId = 1001) { export async function getDistributionRecords(userId) {
const uid = normalizeUserId(userId);
try { try {
const res = await http.get(`/distribution/records?userId=${userId}`); const res = await http.get(`/distribution/records?userId=${uid}`);
return res || []; return res || [];
} catch (err) { } catch (err) {
console.warn('[API] 获取分销返佣记录异常:', err.message); console.warn('[API] 获取分销返佣记录异常:', err.message);

查看文件

@@ -137,12 +137,12 @@
<text class="sec-sub">满员即成局</text> <text class="sec-sub">满员即成局</text>
</view> </view>
<view class="member-list" v-if="activity.participants && activity.participants.length > 0"> <view class="member-list" v-if="activity.participants && activity.participants.length > 0">
<view v-for="(p, index) in activity.participants" :key="p.id" class="member-item" :class="{ 'my-member-item': Number(p.userId) === CURRENT_USER_ID }"> <view v-for="(p, index) in activity.participants" :key="p.id" class="member-item" :class="{ 'my-member-item': Number(p.userId) === currentUserId }">
<image class="member-avatar" :src="p.avatar" mode="aspectFill" /> <image class="member-avatar" :src="p.avatar" mode="aspectFill" />
<view class="member-info"> <view class="member-info">
<view class="member-top-line"> <view class="member-top-line">
<text class="member-name">{{ p.nickname }}</text> <text class="member-name">{{ p.nickname }}</text>
<text class="my-badge" v-if="Number(p.userId) === CURRENT_USER_ID">我的席位</text> <text class="my-badge" v-if="Number(p.userId) === currentUserId">我的席位</text>
<text class="member-price-tag" v-if="p.priceAtJoin"> <text class="member-price-tag" v-if="p.priceAtJoin">
出价: ¥{{ p.priceAtJoin }} 出价: ¥{{ p.priceAtJoin }}
</text> </text>
@@ -158,7 +158,7 @@
<view class="checkin-badge" :class="{ checked: p.checkedIn }"> <view class="checkin-badge" :class="{ checked: p.checkedIn }">
{{ p.checkedIn ? '已核销到场' : '待现场核销' }} {{ p.checkedIn ? '已核销到场' : '待现场核销' }}
</view> </view>
<text class="btn-micro-leave btn-active" v-if="Number(p.userId) === CURRENT_USER_ID && !p.checkedIn" @click.stop="handleLeave"> <text class="btn-micro-leave btn-active" v-if="Number(p.userId) === currentUserId && !p.checkedIn" @click.stop="handleLeave">
退席 退席
</text> </text>
</view> </view>
@@ -384,18 +384,26 @@ import {
import { ICONS } from '@/common/icons.js'; import { ICONS } from '@/common/icons.js';
import { getCurrentThemeKey, applyThemeToNativeUI } from '@/common/theme.js'; import { getCurrentThemeKey, applyThemeToNativeUI } from '@/common/theme.js';
const CURRENT_USER_ID = 1001; const currentUserId = computed(() => {
try {
return Number(uni.getStorageSync('userId')) || 1001;
} catch (_) {
return 1001;
}
});
const currentActId = ref('1');
const verifyCodeInput = ref(''); const verifyCodeInput = ref('');
const currentThemeKey = ref(getCurrentThemeKey()); const currentThemeKey = ref(getCurrentThemeKey());
const isHost = computed(() => { const isHost = computed(() => {
if (!activity.value) return false; if (!activity.value) return false;
return Number(activity.value.creatorId) === CURRENT_USER_ID; return Number(activity.value.creatorId) === currentUserId.value;
}); });
const myParticipantInfo = computed(() => { const myParticipantInfo = computed(() => {
if (!activity.value || !activity.value.participants) return null; if (!activity.value || !activity.value.participants) return null;
return activity.value.participants.find(p => Number(p.userId) === CURRENT_USER_ID || Number(p.id) === CURRENT_USER_ID); const uid = currentUserId.value;
return activity.value.participants.find(p => Number(p.userId) === uid || Number(p.id) === uid);
}); });
const isJoined = computed(() => !!myParticipantInfo.value); const isJoined = computed(() => !!myParticipantInfo.value);
@@ -421,6 +429,9 @@ onMounted(() => {
onShow(() => { onShow(() => {
currentThemeKey.value = getCurrentThemeKey(); currentThemeKey.value = getCurrentThemeKey();
applyThemeToNativeUI(currentThemeKey.value); applyThemeToNativeUI(currentThemeKey.value);
if (currentActId.value) {
loadLiveDetail(currentActId.value);
}
}); });
onUnmounted(() => { onUnmounted(() => {
@@ -458,6 +469,7 @@ const loadLiveDetail = async (id) => {
onLoad((query) => { onLoad((query) => {
const targetId = (query && query.id) ? query.id : '1'; const targetId = (query && query.id) ? query.id : '1';
currentActId.value = targetId;
loadLiveDetail(targetId); loadLiveDetail(targetId);
}); });
@@ -639,7 +651,7 @@ const handleHostVerify = async () => {
} }
try { try {
uni.showLoading({ title: '正在核销签到...' }); uni.showLoading({ title: '正在核销签到...' });
await verifyActivityCheckin(activity.value.id, code, CURRENT_USER_ID); await verifyActivityCheckin(activity.value.id, code, currentUserId.value);
uni.hideLoading(); uni.hideLoading();
verifyCodeInput.value = ''; verifyCodeInput.value = '';
uni.showToast({ title: '核销成功!搭子已签到', icon: 'success' }); uni.showToast({ title: '核销成功!搭子已签到', icon: 'success' });
@@ -675,7 +687,7 @@ const handleHostCancel = () => {
if (res.confirm) { if (res.confirm) {
try { try {
uni.showLoading({ title: '正在解散并全额退款...' }); uni.showLoading({ title: '正在解散并全额退款...' });
await cancelActivity(activity.value.id, CURRENT_USER_ID, '局长主动解散组局'); await cancelActivity(activity.value.id, currentUserId.value, '局长主动解散组局');
uni.hideLoading(); uni.hideLoading();
uni.showToast({ title: '组局已成功解散', icon: 'success' }); uni.showToast({ title: '组局已成功解散', icon: 'success' });
activity.value.status = 'CANCELLED'; activity.value.status = 'CANCELLED';
@@ -703,7 +715,7 @@ const handleLeave = () => {
if (res.confirm) { if (res.confirm) {
try { try {
uni.showLoading({ title: '正在办理退席...' }); uni.showLoading({ title: '正在办理退席...' });
await leaveActivity(activity.value.id, CURRENT_USER_ID); await leaveActivity(activity.value.id, currentUserId.value);
uni.hideLoading(); uni.hideLoading();
uni.showToast({ title: '已退席,款项已退回钱包', icon: 'success' }); uni.showToast({ title: '已退席,款项已退回钱包', icon: 'success' });
loadLiveDetail(activity.value.id); loadLiveDetail(activity.value.id);

查看文件

@@ -121,7 +121,7 @@
<script setup> <script setup>
import { ref, computed, onMounted, onUnmounted } from 'vue'; import { ref, computed, onMounted, onUnmounted } from 'vue';
import { onLoad } from '@dcloudio/uni-app'; import { onLoad, onShow } from '@dcloudio/uni-app';
import { getActivityDetail, verifyActivityCheckin, getMyActivities, leaveActivity } from '@/common/api.js'; import { getActivityDetail, verifyActivityCheckin, getMyActivities, leaveActivity } from '@/common/api.js';
import { encodeCode128B } from '@/common/barcode.js'; import { encodeCode128B } from '@/common/barcode.js';
import { generateQRCodeMatrix } from '@/common/qrcode.js'; import { generateQRCodeMatrix } from '@/common/qrcode.js';
@@ -140,6 +140,14 @@ const isCheckedIn = ref(false);
const currentTime = ref(''); const currentTime = ref('');
const hasTicket = ref(true); const hasTicket = ref(true);
const currentUserId = computed(() => {
try {
return Number(uni.getStorageSync('userId')) || 1001;
} catch (_) {
return 1001;
}
});
const ticketSn = computed(() => { const ticketSn = computed(() => {
const actId = ticketData.value && ticketData.value.id ? ticketData.value.id : '101'; const actId = ticketData.value && ticketData.value.id ? ticketData.value.id : '101';
return `TICKET-${actId}-${verifyCode.value}`; return `TICKET-${actId}-${verifyCode.value}`;
@@ -170,7 +178,9 @@ const updateTime = () => {
currentTime.value = `${y}-${m}-${d} ${hh}:${mm}:${ss}`; currentTime.value = `${y}-${m}-${d} ${hh}:${mm}:${ss}`;
}; };
onLoad(async (query) => { let savedQuery = null;
const loadTicketData = async (query = savedQuery) => {
if (query && query.code) { if (query && query.code) {
verifyCode.value = String(query.code); verifyCode.value = String(query.code);
} }
@@ -199,17 +209,18 @@ onLoad(async (query) => {
// 2. 若无传参,真实拉取当前用户的已报名组局列表 // 2. 若无传参,真实拉取当前用户的已报名组局列表
try { try {
const myActs = await getMyActivities(1001); const uid = currentUserId.value;
const myActs = await getMyActivities(uid);
const joinedList = myActs.joined || []; const joinedList = myActs.joined || [];
if (joinedList.length > 0) { if (joinedList.length > 0) {
let targetAct = joinedList.find(act => { let targetAct = joinedList.find(act => {
const myP = (act.participants || []).find(p => p.userId === 1001 || p.nickname === '同城局长' || p.verifyCode); const myP = (act.participants || []).find(p => Number(p.userId) === uid || p.nickname === '同城局长' || p.verifyCode);
return myP && !myP.checkedIn; return myP && !myP.checkedIn;
}) || joinedList[0]; }) || joinedList[0];
ticketData.value = targetAct; ticketData.value = targetAct;
if (targetAct.participants && targetAct.participants.length > 0) { if (targetAct.participants && targetAct.participants.length > 0) {
const myP = targetAct.participants.find(p => p.userId === 1001 || p.verifyCode); const myP = targetAct.participants.find(p => Number(p.userId) === uid || p.verifyCode);
if (myP && myP.verifyCode) { if (myP && myP.verifyCode) {
verifyCode.value = String(myP.verifyCode); verifyCode.value = String(myP.verifyCode);
isCheckedIn.value = Boolean(myP.checkedIn); isCheckedIn.value = Boolean(myP.checkedIn);
@@ -235,8 +246,16 @@ onLoad(async (query) => {
} }
} }
} catch (_) {} } catch (_) {}
};
onLoad(async (query) => {
savedQuery = query;
await loadTicketData(query);
}); });
onShow(async () => {
await loadTicketData();
});
onMounted(() => { onMounted(() => {
updateTime(); updateTime();
@@ -251,7 +270,7 @@ const toggleCheckIn = async () => {
if (!isCheckedIn.value) { if (!isCheckedIn.value) {
try { try {
uni.showLoading({ title: '正在核销签到...' }); uni.showLoading({ title: '正在核销签到...' });
await verifyActivityCheckin(ticketData.value.id, verifyCode.value, 1001); await verifyActivityCheckin(ticketData.value.id, verifyCode.value, currentUserId.value);
uni.hideLoading(); uni.hideLoading();
isCheckedIn.value = true; isCheckedIn.value = true;
uni.showToast({ uni.showToast({
@@ -291,7 +310,7 @@ const handleCancelTicket = () => {
try { try {
uni.showLoading({ title: '正在办理退票...' }); uni.showLoading({ title: '正在办理退票...' });
const actId = ticketData.value && ticketData.value.id ? ticketData.value.id : 'act_101'; const actId = ticketData.value && ticketData.value.id ? ticketData.value.id : 'act_101';
await leaveActivity(actId, 1001); await leaveActivity(actId, currentUserId.value);
uni.hideLoading(); uni.hideLoading();
uni.showToast({ title: '退票成功,款项已退回钱包', icon: 'success' }); uni.showToast({ title: '退票成功,款项已退回钱包', icon: 'success' });
setTimeout(() => { setTimeout(() => {

查看文件

@@ -80,8 +80,14 @@
> >
删除 删除
</button> </button>
<button v-else class="join-btn btn-active" @click="handleJoinParty(post)"> <button
{{ post.actionText || '聊一聊' }} v-else
class="join-btn btn-active"
:class="{ 'btn-applied': post.hasApplied }"
:disabled="post.hasApplied"
@click="handleJoinParty(post)"
>
{{ post.hasApplied ? '已申请' : (post.actionText || '聊一聊') }}
</button> </button>
</view> </view>
</view> </view>
@@ -380,10 +386,18 @@ const loadRemotePosts = async () => {
if (list && list.length > 0) { if (list && list.length > 0) {
let localLikedIds = uni.getStorageSync('COMMUNITY_LIKED_POST_IDS') || []; let localLikedIds = uni.getStorageSync('COMMUNITY_LIKED_POST_IDS') || [];
if (!Array.isArray(localLikedIds)) localLikedIds = []; if (!Array.isArray(localLikedIds)) localLikedIds = [];
posts.value = list.map(item => ({ let localAppliedIds = uni.getStorageSync('COMMUNITY_APPLIED_POST_IDS') || [];
...item, if (!Array.isArray(localAppliedIds)) localAppliedIds = [];
liked: item.liked !== undefined ? item.liked : localLikedIds.includes(String(item.id))
})); posts.value = list.map(item => {
const isApplied = localAppliedIds.includes(String(item.id));
return {
...item,
liked: item.liked !== undefined ? item.liked : localLikedIds.includes(String(item.id)),
hasApplied: isApplied,
actionText: isApplied ? '已申请' : (item.actionText || '加入车队')
};
});
} }
} catch (err) { } catch (err) {
console.warn('[Community] 获取动态列表失败,保持本地列表:', err.message); console.warn('[Community] 获取动态列表失败,保持本地列表:', err.message);
@@ -416,20 +430,29 @@ const feedTabs = ref([
const currentTab = ref('all'); const currentTab = ref('all');
const syncInitialLikedState = (rawPosts) => { const syncInitialPostStates = (rawPosts) => {
try { try {
let localLikedIds = uni.getStorageSync('COMMUNITY_LIKED_POST_IDS') || []; let localLikedIds = uni.getStorageSync('COMMUNITY_LIKED_POST_IDS') || [];
if (!Array.isArray(localLikedIds) || localLikedIds.length === 0) return rawPosts; if (!Array.isArray(localLikedIds)) localLikedIds = [];
return rawPosts.map(p => ({ let localAppliedIds = uni.getStorageSync('COMMUNITY_APPLIED_POST_IDS') || [];
...p, if (!Array.isArray(localAppliedIds)) localAppliedIds = [];
liked: localLikedIds.includes(String(p.id)) ? true : p.liked
})); return rawPosts.map(p => {
const isApplied = localAppliedIds.includes(String(p.id));
return {
...p,
liked: localLikedIds.includes(String(p.id)) ? true : p.liked,
hasApplied: isApplied,
actionText: isApplied ? '已申请' : p.actionText
};
});
} catch (_) { } catch (_) {
return rawPosts; return rawPosts;
} }
}; };
const syncInitialLikedState = syncInitialPostStates;
const posts = ref(syncInitialLikedState([ const posts = ref(syncInitialPostStates([
{ {
id: 1, id: 1,
author: '小甜橙', author: '小甜橙',
@@ -658,6 +681,13 @@ const handleDeletePost = (post) => {
}; };
const handleJoinParty = (post) => { const handleJoinParty = (post) => {
if (post.hasApplied) {
uni.showToast({
title: '已提交过约伴申请',
icon: 'none'
});
return;
}
uni.showModal({ uni.showModal({
title: '搭子互动申请', title: '搭子互动申请',
content: `确定向【${post.author}】发起同城约伴 / 上车请求?`, content: `确定向【${post.author}】发起同城约伴 / 上车请求?`,
@@ -666,24 +696,33 @@ const handleJoinParty = (post) => {
success: async (res) => { success: async (res) => {
if (res.confirm) { if (res.confirm) {
uni.showLoading({ title: '正在提交申请...' }); uni.showLoading({ title: '正在提交申请...' });
const uid = Number(uni.getStorageSync('userId')) || 1001;
const userInfo = uni.getStorageSync('userInfo') || {};
const applicantName = userInfo.nickname || '搭子玩家';
const applicantAvatar = userInfo.avatar || 'https://images.unsplash.com/photo-1535713875002-d1d0cf377fde?w=120';
try { try {
await applyPostParty(post.id, { await applyPostParty(post.id, {
applicantId: 1001, applicantId: uid,
applicantName: '电竞玩家 (我)', applicantName,
applicantAvatar: 'https://images.unsplash.com/photo-1535713875002-d1d0cf377fde?w=120' applicantAvatar
});
uni.hideLoading();
uni.showToast({
title: '申请已送达,等待房主确认!',
icon: 'success'
});
} catch (_) {
uni.hideLoading();
uni.showToast({
title: '申请已送达,等待房主确认!',
icon: 'success'
}); });
} catch (_) {}
post.hasApplied = true;
post.actionText = '已申请';
let localAppliedIds = uni.getStorageSync('COMMUNITY_APPLIED_POST_IDS') || [];
if (!Array.isArray(localAppliedIds)) localAppliedIds = [];
const strId = String(post.id);
if (!localAppliedIds.includes(strId)) {
localAppliedIds.push(strId);
uni.setStorageSync('COMMUNITY_APPLIED_POST_IDS', localAppliedIds);
} }
uni.hideLoading();
uni.showToast({
title: '申请已送达,等待房主确认!',
icon: 'success'
});
} }
} }
}); });
@@ -1212,6 +1251,13 @@ const submitPublishPost = async () => {
font-weight: 600; font-weight: 600;
margin: 0; margin: 0;
line-height: 1.5; line-height: 1.5;
&.btn-applied {
background: rgba(148, 163, 184, 0.15);
color: #94A3B8;
border-color: rgba(148, 163, 184, 0.3);
opacity: 0.8;
}
} }
} }
} }
@@ -1769,6 +1815,12 @@ const submitPublishPost = async () => {
background: rgba(234, 88, 12, 0.1); background: rgba(234, 88, 12, 0.1);
color: #EA580C; color: #EA580C;
border-color: rgba(234, 88, 12, 0.3); border-color: rgba(234, 88, 12, 0.3);
&.btn-applied {
background: #F1F5F9;
color: #94A3B8;
border-color: #CBD5E1;
}
} }
} }

查看文件

@@ -241,7 +241,7 @@
<script setup> <script setup>
import { ref, computed } from 'vue'; import { ref, computed } from 'vue';
import { onLoad } from '@dcloudio/uni-app'; import { onLoad, onShow } from '@dcloudio/uni-app';
import SkeletonScreen from '@/components/SkeletonScreen.vue'; import SkeletonScreen from '@/components/SkeletonScreen.vue';
import { getCompanionDetail, getExpertReviews, replyOrderReview, submitReport as submitReportApi, checkFavorite, addFavorite as addFavApi, removeFavorite as removeFavApi } from '@/common/api.js'; import { getCompanionDetail, getExpertReviews, replyOrderReview, submitReport as submitReportApi, checkFavorite, addFavorite as addFavApi, removeFavorite as removeFavApi } from '@/common/api.js';
import { ICONS } from '@/common/icons.js'; import { ICONS } from '@/common/icons.js';
@@ -268,10 +268,23 @@ const expert = ref({
}); });
const selectedServiceId = ref(null); const selectedServiceId = ref(null);
const isPlayingVoice = ref(false); const isPlayingVoice = ref(false);
let currentExpertTargetId = null;
const refreshFavoriteState = async () => {
if (!currentExpertTargetId) return;
try {
const favStatus = await checkFavorite(currentExpertTargetId);
isFav.value = Boolean(favStatus);
} catch (_) {}
};
onShow(() => {
refreshFavoriteState();
});
const toggleFavorite = async () => { const toggleFavorite = async () => {
try { try {
const targetId = Number(expert.value.id || 101); const targetId = Number(expert.value.id || currentExpertTargetId || 101);
if (isFav.value) { if (isFav.value) {
await removeFavApi(targetId); await removeFavApi(targetId);
isFav.value = false; isFav.value = false;
@@ -315,6 +328,7 @@ const handleReplyReview = (rev) => {
onLoad(async (query) => { onLoad(async (query) => {
const targetId = (query && query.id) ? query.id : '1'; const targetId = (query && query.id) ? query.id : '1';
currentExpertTargetId = targetId;
try { try {
const liveData = await getCompanionDetail(targetId); const liveData = await getCompanionDetail(targetId);
if (liveData) { if (liveData) {

查看文件

@@ -415,7 +415,7 @@ const expert = ref({
nickname: '认证大神', nickname: '认证大神',
avatar: 'https://images.unsplash.com/photo-1534528741775-53994a69daeb?w=300' avatar: 'https://images.unsplash.com/photo-1534528741775-53994a69daeb?w=300'
}); });
const isReceivingOrders = ref(true); const isReceivingOrders = ref(uni.getStorageSync('EXPERT_RECEIVING_STATUS') !== '0');
const todayIncome = ref('0.00'); const todayIncome = ref('0.00');
const todayOrdersCount = ref(0); const todayOrdersCount = ref(0);
const waitingVerifyCount = ref(0); const waitingVerifyCount = ref(0);
@@ -579,6 +579,7 @@ const activeOrders = ref([
const toggleReceivingStatus = () => { const toggleReceivingStatus = () => {
isReceivingOrders.value = !isReceivingOrders.value; isReceivingOrders.value = !isReceivingOrders.value;
uni.setStorageSync('EXPERT_RECEIVING_STATUS', isReceivingOrders.value ? '1' : '0');
uni.showToast({ uni.showToast({
title: isReceivingOrders.value ? '已开启在线接单通道!' : '已打烊停止接单', title: isReceivingOrders.value ? '已开启在线接单通道!' : '已打烊停止接单',
icon: 'none' icon: 'none'
@@ -677,6 +678,7 @@ onShow(() => {
}, 400); }, 400);
return; return;
} }
isReceivingOrders.value = uni.getStorageSync('EXPERT_RECEIVING_STATUS') !== '0';
loadWorkspaceOrders(); loadWorkspaceOrders();
}); });

查看文件

@@ -999,6 +999,75 @@ describe('ContentSecurityService & Compliance Architecture (TDD 微信内容安
expect(emojiRegex.test(fs.readFileSync(communityPath, 'utf-8'))).toBe(false); expect(emojiRegex.test(fs.readFileSync(communityPath, 'utf-8'))).toBe(false);
}); });
}); });
describe('23. 全模块状态持久化与跨页面状态同步健壮性规范验证 (组局/门票/工作台/收藏/搭子)', () => {
it('API 层 normalizeUserId 与收藏/组局方法必须优先支持 Storage 用户ID', () => {
const apiPath = path.join(rootDir, 'pwuniapp', 'common', 'api.js');
const content = fs.readFileSync(apiPath, 'utf-8');
expect(content).toContain("uni.getStorageSync('userId')");
expect(content).toContain('function normalizeUserId');
expect(content).toContain('export async function getFavorites(userId)');
expect(content).toContain('export async function checkFavorite(expertId, userId)');
});
it('组局详情页必须动态响应当前用户并在 onShow 中拉取最新数据', () => {
const actDetailPath = path.join(rootDir, 'pwuniapp', 'pages', 'activity', 'detail.vue');
const content = fs.readFileSync(actDetailPath, 'utf-8');
expect(content).toContain('currentUserId');
expect(content).toContain('currentActId');
expect(content).toContain('loadLiveDetail(currentActId.value)');
expect(content).not.toContain('const CURRENT_USER_ID = 1001;');
});
it('我的门票页面必须注册 onShow 并在退票与核销时绑定动态用户', () => {
const ticketPath = path.join(rootDir, 'pwuniapp', 'pages', 'activity', 'my-ticket.vue');
const content = fs.readFileSync(ticketPath, 'utf-8');
expect(content).toContain('onShow');
expect(content).toContain('currentUserId');
expect(content).toContain('loadTicketData()');
expect(content).toContain('leaveActivity(actId, currentUserId.value)');
});
it('大神工作台接单状态必须持久化 EXPERT_RECEIVING_STATUS 并在 onShow 恢复', () => {
const wsPath = path.join(rootDir, 'pwuniapp', 'pages', 'expert', 'workspace.vue');
const content = fs.readFileSync(wsPath, 'utf-8');
expect(content).toContain('EXPERT_RECEIVING_STATUS');
expect(content).toContain("uni.setStorageSync('EXPERT_RECEIVING_STATUS'");
});
it('大神主页必须在 onShow 中刷新当前用户的收藏状态', () => {
const expertDetailPath = path.join(rootDir, 'pwuniapp', 'pages', 'expert', 'detail.vue');
const content = fs.readFileSync(expertDetailPath, 'utf-8');
expect(content).toContain('onShow');
expect(content).toContain('refreshFavoriteState');
expect(content).toContain('currentExpertTargetId');
});
it('友圈页面必须通过 COMMUNITY_APPLIED_POST_IDS 持久化申请上车状态并防重', () => {
const communityPath = path.join(rootDir, 'pwuniapp', 'pages', 'community', 'index.vue');
const content = fs.readFileSync(communityPath, 'utf-8');
expect(content).toContain('COMMUNITY_APPLIED_POST_IDS');
expect(content).toContain('btn-applied');
expect(content).toContain('syncInitialPostStates');
});
it('所有状态持久化修复代码与测试严禁包含任何系统 Emoji 违规字符', () => {
const actDetailPath = path.join(rootDir, 'pwuniapp', 'pages', 'activity', 'detail.vue');
const ticketPath = path.join(rootDir, 'pwuniapp', 'pages', 'activity', 'my-ticket.vue');
const wsPath = path.join(rootDir, 'pwuniapp', 'pages', 'expert', 'workspace.vue');
const expertDetailPath = path.join(rootDir, 'pwuniapp', 'pages', 'expert', 'detail.vue');
const communityPath = path.join(rootDir, 'pwuniapp', 'pages', 'community', 'index.vue');
const apiPath = path.join(rootDir, 'pwuniapp', 'common', 'api.js');
const emojiRegex = /(?:\p{Extended_Pictographic}|\p{Emoji_Presentation}|[\u2600-\u27BF])/u;
expect(emojiRegex.test(fs.readFileSync(actDetailPath, 'utf-8'))).toBe(false);
expect(emojiRegex.test(fs.readFileSync(ticketPath, 'utf-8'))).toBe(false);
expect(emojiRegex.test(fs.readFileSync(wsPath, 'utf-8'))).toBe(false);
expect(emojiRegex.test(fs.readFileSync(expertDetailPath, 'utf-8'))).toBe(false);
expect(emojiRegex.test(fs.readFileSync(communityPath, 'utf-8'))).toBe(false);
expect(emojiRegex.test(fs.readFileSync(apiPath, 'utf-8'))).toBe(false);
});
});
}); });