fix(miniapp): 清理剩余身份兜底并修正待接单状态/客服入口

- 修复 user/index 使用 normalizeUserId 未导入(分享按钮会报错)

- 待接单独立为 PENDING_ACCEPT:新增待接单 tab/状态色/取消订单与联系客服操作,不再显示为进行中

- 移除 hall/bill-records/reports/settings/favorites/order-create/activity-create/activity-detail/apply 的 1001/2006 身份兜底

- 线下订单(含指定大神)必须填写碰面地点;再次预约不再兜底到 101 号大神;评价不预选标签

- 接单大厅去掉「已实名认证/信誉积分100」等伪造信任文案;客服按钮统一为「联系客服」

- 114 套件/745 用例全绿
这个提交包含在:
xcbyy
2026-10-22 09:25:27 +08:00
父节点 be1c7be5d3
当前提交 d941a16d28
共有 16 个文件被更改,包括 99 次插入 和 46 次删除

查看文件

@@ -251,8 +251,8 @@ export function formatOrderData(raw) {
let statusKey = raw.status || 'IN_SERVICE';
if (statusKey === '待支付' || statusKey === '待付款') statusKey = 'PENDING_PAY';
else if (statusKey === '待接单') statusKey = 'PENDING_ACCEPT';
else if (
statusKey === '待接单' ||
statusKey === '已接单' ||
statusKey === '已到场核销' ||
statusKey === '履约中' ||

查看文件

@@ -658,7 +658,7 @@ const submitDispatch = async (title, startTime, location) => {
latitude: form.value.latitude,
longitude: form.value.longitude,
expenseBurden: 'AA制各自付',
remark: title,
remark: startTime ? `${title}|期望:${startTime}` : title,
});
} catch (err) {
uni.hideLoading();
@@ -765,7 +765,7 @@ const handleSubmit = async () => {
uni.showToast({ title: '账号已被封禁,无法发起组局', icon: 'none' });
return;
}
const currentUserId = Number(uni.getStorageSync('userId')) || Number(cachedUser.id) || 1001;
const currentUserId = normalizeUserId();
const newActivityPayload = {
creatorId: currentUserId,
game: form.value.game,

查看文件

@@ -691,7 +691,11 @@ const copyLocation = () => {
};
const openHostProfile = () => {
const hostId = activity.value.creatorId || 1001;
const hostId = Number(activity.value.creatorId) || 0;
if (!hostId) {
uni.showToast({ title: '发起人信息不完整', icon: 'none' });
return;
}
const hostName = activity.value.creatorNickname || '同城用户';
const hostAvatar = activity.value.creatorAvatar || 'https://images.unsplash.com/photo-1534528741775-53994a69daeb?w=150';
const hostSnapshot = {

查看文件

@@ -182,7 +182,8 @@ import {
applyPostParty,
getPostApplies,
handlePostApply,
getUserProfile
getUserProfile,
normalizeUserId
} from '@/common/api.js';
const DEFAULT_USER_AVATAR = 'https://images.unsplash.com/photo-1535713875002-d1d0cf377fde?w=120';
@@ -199,7 +200,7 @@ const currentUserProfile = ref({});
const readCachedUserInfo = () => {
const pwInfo = uni.getStorageSync('PW_USER_INFO') || {};
const uInfo = uni.getStorageSync('userInfo') || {};
const uid = Number(uni.getStorageSync('userId')) || Number(pwInfo.id) || Number(uInfo.id) || 1001;
const uid = normalizeUserId();
const rawName = pwInfo.nickname || uInfo.nickname || '';
const nickname = (rawName && !['点击登录 / 注册', '电竞玩家 (我)'].includes(rawName)) ? rawName : '';
const avatar = pwInfo.avatar || uInfo.avatar || '';
@@ -239,7 +240,7 @@ const normalizeCommentItem = (item) => {
if (!item) return item;
const cur = currentUserProfile.value.nickname ? currentUserProfile.value : readCachedUserInfo();
const isMyComment =
(item.userId && Number(item.userId) === Number(cur.id || 1001)) ||
(item.userId && Number(item.userId) === Number(cur.id || 0)) ||
item.author === '电竞玩家 (我)';
return {
...item,
@@ -275,7 +276,7 @@ const getPostCategoryName = (catKey) => {
const isPostOwner = (target) => {
const cur = currentUserProfile.value.nickname ? currentUserProfile.value : readCachedUserInfo();
return target && (
Number(target.userId) === Number(cur.id || 1001) ||
Number(target.userId) === Number(cur.id || 0) ||
target.author === '电竞玩家 (我)' ||
(cur.nickname && target.author === cur.nickname) ||
target.isMine === true
@@ -466,7 +467,7 @@ const sendComment = async () => {
const profile = currentUserProfile.value.nickname
? currentUserProfile.value
: await syncCurrentUserProfile();
const uid = Number(profile.id || uni.getStorageSync('userId')) || 1001;
const uid = normalizeUserId();
const authorName = profile.nickname || '同城用户';
const authorAvatar = profile.avatar || DEFAULT_USER_AVATAR;
@@ -522,7 +523,7 @@ const handleJoinParty = () => {
const profile = currentUserProfile.value.nickname
? currentUserProfile.value
: await syncCurrentUserProfile();
const uid = Number(profile.id || uni.getStorageSync('userId')) || 1001;
const uid = normalizeUserId();
const applicantName = profile.nickname || '同城用户';
const applicantAvatar = profile.avatar || DEFAULT_USER_AVATAR;
try {

查看文件

@@ -414,7 +414,7 @@ const currentUserProfile = ref({});
const readCachedUserInfo = () => {
const pwInfo = uni.getStorageSync('PW_USER_INFO') || {};
const uInfo = uni.getStorageSync('userInfo') || {};
const uid = Number(uni.getStorageSync('userId')) || Number(pwInfo.id) || Number(uInfo.id) || 1001;
const uid = normalizeUserId();
const rawName = pwInfo.nickname || uInfo.nickname || '';
const nickname = (rawName && !['点击登录 / 注册', '电竞玩家 (我)'].includes(rawName)) ? rawName : '';
const avatar = pwInfo.avatar || uInfo.avatar || '';
@@ -788,7 +788,7 @@ const sendComment = async () => {
const profile = currentUserProfile.value.nickname
? currentUserProfile.value
: await syncCurrentUserProfile();
const uid = Number(profile.id || uni.getStorageSync('userId')) || 1001;
const uid = normalizeUserId();
const authorName = profile.nickname || '同城用户';
const authorAvatar = profile.avatar || DEFAULT_USER_AVATAR;
@@ -824,7 +824,7 @@ const sendComment = async () => {
const isPostOwner = (post) => {
const cur = currentUserProfile.value.nickname ? currentUserProfile.value : readCachedUserInfo();
return post && (
Number(post.userId) === Number(cur.id || 1001) ||
Number(post.userId) === Number(cur.id || 0) ||
post.author === '电竞玩家 (我)' ||
(cur.nickname && post.author === cur.nickname) ||
post.isMine === true
@@ -932,7 +932,7 @@ const handleJoinParty = (post) => {
const profile = currentUserProfile.value.nickname
? currentUserProfile.value
: await syncCurrentUserProfile();
const uid = Number(profile.id || uni.getStorageSync('userId')) || 1001;
const uid = normalizeUserId();
const applicantName = profile.nickname || '同城用户';
const applicantAvatar = profile.avatar || DEFAULT_USER_AVATAR;
try {

查看文件

@@ -454,7 +454,7 @@ const submitApply = async () => {
uni.showLoading({ title: '正在提交入驻材料...' });
// 身份与登录态保持一致:有 token 时由服务端按 token 识别(缺失 userId 传 0)
const uid = normalizeUserId();
const profileUid = uid || 1001;
const profileUid = uid;
try {
let avatarUrl = '';
@@ -555,7 +555,8 @@ const rejectReason = ref('');
const syncRemoteCertStatus = async () => {
try {
const cachedUser = uni.getStorageSync('PW_USER_INFO') || {};
const uid = Number(cachedUser.id) || 1001;
const uid = normalizeUserId();
if (!uid) return;
const profile = await getUserProfile(uid);
if (profile && profile.certStatus !== undefined) {
const remoteCert = Number(profile.certStatus);

查看文件

@@ -218,9 +218,9 @@
<view class="client-info">
<view class="client-name-row">
<text class="client-name">{{ selectedOrder.userName }}</text>
<text class="client-verified-tag">已实名认证买家</text>
<text class="client-verified-tag">平台注册买家</text>
</view>
<text class="client-trust-sub">信誉积分 100 · 资金全额托管</text>
<text class="client-trust-sub">资金由平台担保托管</text>
</view>
</view>
@@ -251,7 +251,7 @@ import AppTabBar from '@/components/CustomTabBar.vue';
import { ICONS } from '@/common/icons.js';
import { getCurrentThemeKey, applyThemeToNativeUI } from '@/common/theme.js';
import { isExpertCertified } from '@/common/role.js';
import { claimOrder, getOrdersPool, getExpertStats, updateCompanionProfile, getUserProfile, getActivities } from '@/common/api.js';
import { claimOrder, getOrdersPool, getExpertStats, updateCompanionProfile, getUserProfile, getActivities, normalizeUserId } from '@/common/api.js';
import { getUserLocation } from '@/common/location.js';
import { getNavBarInfo } from '@/common/nav.js';
@@ -316,8 +316,11 @@ let isHallLoading = false;
const loadHallData = async () => {
if (isHallLoading) return;
isHallLoading = true;
let uid = uni.getStorageSync('userId') || expert.value.id || 2006;
if (uid == 101) uid = 2006;
let uid = normalizeUserId();
if (!uid) {
isHallLoading = false;
return;
}
expert.value.id = uid;
// 1. 同步最新在线接单状态
@@ -469,8 +472,13 @@ const filteredOrders = computed(() => {
const toggleOnlineStatus = async () => {
isOnline.value = !isOnline.value;
uni.setStorageSync('EXPERT_RECEIVING_STATUS', isOnline.value ? '1' : '0');
const eid = Number(expert.value.id) || normalizeUserId();
if (!eid) {
uni.showToast({ title: '请先登录后再操作', icon: 'none' });
return;
}
try {
await updateCompanionProfile(expert.value.id || 2006, {
await updateCompanionProfile(eid, {
status: isOnline.value ? '接单中' : '休息中'
});
} catch (_) {}
@@ -493,7 +501,7 @@ const handleGrabOrder = (order) => {
uni.showLoading({ title: '正在锁定订单...' });
try {
await claimOrder(order.id, {
expertId: expert.value.id || 2006,
expertId: expert.value.id || 0,
companionName: expert.value.nickname || '接单大神',
expertAvatar: expert.value.avatar || DEFAULT_EXPERT_AVATAR
});

查看文件

@@ -360,7 +360,7 @@
<view class="order-progress-row">
<text class="prog-text">状态:{{ order.statusText }}</text>
<view class="order-btns">
<button class="btn-chat btn-active" @click="chatWithBuyer(order)">联系老板</button>
<button class="btn-chat btn-active" @click="chatWithBuyer(order)">联系客服</button>
<button class="btn-complete btn-active" @click="completeOrder(order.id)">
申请验收结算
</button>

查看文件

@@ -285,7 +285,7 @@ import { getCurrentThemeKey, applyThemeToNativeUI } from '@/common/theme.js';
import { ensureLoginOrRedirect } from '@/common/role.js';
const currentThemeKey = ref(getCurrentThemeKey());
const currentUserId = ref(Number(uni.getStorageSync('userId') || 1001));
const currentUserId = ref(normalizeUserId());
const currentUserName = ref((uni.getStorageSync('userInfo') && uni.getStorageSync('userInfo').nickname) || '同城用户');
const userBalance = ref('0.00');
const payMethod = ref('WECHAT'); // 'WECHAT' | 'BALANCE'
@@ -324,7 +324,7 @@ const stopPropagation = (e) => {
const loadUserWallet = async () => {
try {
const uid = Number(uni.getStorageSync('userId') || 1001);
const uid = normalizeUserId();
currentUserId.value = uid;
const cachedInfo = uni.getStorageSync('userInfo');
if (cachedInfo && cachedInfo.nickname) {
@@ -567,6 +567,10 @@ const handlePay = async () => {
uni.showToast({ title: '该大神暂未配置服务包,无法下单', icon: 'none' });
return;
}
if (isOffline.value && !String(meetVenue.value || '').trim()) {
uni.showToast({ title: '请填写或选择碰面地点', icon: 'none' });
return;
}
// 平台派单校验:品类预算与碰面地点
if (isPlatformMode.value) {
if (!(Number(platformBudget.value) > 0)) {

查看文件

@@ -86,14 +86,19 @@
<button class="action-btn primary-btn btn-active" :disabled="getOrderCountdown(order) === '已超时'" @click="payOrder(order.id)">立即付款</button>
</template>
<template v-else-if="order.status === 'PENDING_ACCEPT'">
<button class="action-btn cancel-btn btn-active" @click="cancelOrder(order.id)">取消订单</button>
<button class="action-btn chat-btn btn-active" @click="contactExpert(order)">联系客服</button>
</template>
<template v-else-if="order.status === 'IN_SERVICE'">
<button class="action-btn chat-btn btn-active" @click="contactExpert(order)">联系大神</button>
<button class="action-btn chat-btn btn-active" @click="contactExpert(order)">联系客服</button>
<button class="action-btn refund-btn btn-active" @click="openRefundModal(order)">申请退款</button>
<button class="action-btn success-btn btn-active" @click="confirmComplete(order.id)">确认验收</button>
</template>
<template v-else-if="order.status === 'COMPLETED'">
<button class="action-btn chat-btn btn-active" @click="reBook(order.expert ? order.expert.id : (order.expertId || 101))">再次预约</button>
<button class="action-btn chat-btn btn-active" @click="reBook(order.expert ? order.expert.id : (order.expertId || 0))">再次预约</button>
<button class="action-btn rate-btn btn-active" :class="{ 'rated-btn': order.isReviewed }" @click="openReviewModal(order)">
{{ order.isReviewed ? '已评价' : '评价大神' }}
</button>
@@ -450,6 +455,7 @@ onUnmounted(() => {
const tabs = [
{ key: 'ALL', name: '全部' },
{ key: 'IN_SERVICE', name: '进行中' },
{ key: 'PENDING_ACCEPT', name: '待接单' },
{ key: 'PENDING_PAY', name: '待付款' },
{ key: 'COMPLETED', name: '已完成' },
{ key: 'REFUNDING', name: '退款/售后' },
@@ -480,6 +486,7 @@ const getStatusText = (status, order) => {
if (order && order.rawStatus === '待确认') return '待确认验收';
const map = {
PENDING_PAY: '待付款',
PENDING_ACCEPT: '等待大神接单',
IN_SERVICE: '服务中',
COMPLETED: '已完成',
REFUNDING: '售后处理中',
@@ -515,7 +522,7 @@ const payOrder = async (id) => {
const o = orders.value.find(item => item.id === id);
const amount = o ? Number(o.totalAmount || o.amount || 0) : 0;
const title = o ? (o.gameName || o.skillName || '陪玩订单支付') : '陪玩订单支付';
const uid = Number(uni.getStorageSync('userId') || 1001);
const uid = normalizeUserId();
uni.showLoading({ title: '正在拉起微信支付...' });
try {
@@ -791,8 +798,13 @@ const confirmComplete = (id) => {
};
const reBook = (expertId) => {
const id = Number(expertId) || 0;
if (!id) {
uni.showToast({ title: '该订单未绑定大神,请前往发现大厅', icon: 'none' });
return;
}
uni.navigateTo({
url: `/pages/expert/detail?id=${expertId}`
url: `/pages/expert/detail?id=${id}`
});
};
@@ -803,7 +815,7 @@ const submittingReview = ref(false);
const presetTags = ['技术过硬', '声音甜美', '准时守约', '态度温柔', '带飞吃鸡', '幽默风趣'];
const reviewForm = ref({
rating: 5,
selectedTags: ['技术过硬', '准时守约'],
selectedTags: [],
content: ''
});
@@ -1059,6 +1071,10 @@ const submitReview = async () => {
background: rgba(245, 158, 11, 0.15);
color: #F59E0B;
}
&.PENDING_ACCEPT {
background: rgba(99, 102, 241, 0.16);
color: #818CF8;
}
&.IN_SERVICE {
background: rgba(16, 185, 129, 0.15);
color: #10B981;

查看文件

@@ -182,7 +182,7 @@ import { onShow } from '@dcloudio/uni-app';
import { ICONS } from '@/common/icons.js';
import { getCurrentThemeKey, applyThemeToNativeUI } from '@/common/theme.js';
import { getNavBarInfo } from '@/common/nav.js';
import { getUserFinanceRecords, getUserProfile } from '@/common/api.js';
import { getUserFinanceRecords, getUserProfile, normalizeUserId } from '@/common/api.js';
const currentThemeKey = ref(getCurrentThemeKey());
const navInfo = ref(getNavBarInfo());
@@ -313,7 +313,7 @@ const getTypeIconClass = (type = '') => {
};
const loadFinanceData = async () => {
const uid = uni.getStorageSync('userId') || 2006;
const uid = normalizeUserId();
try {
const [profile, list] = await Promise.all([
getUserProfile(uid).catch(() => null),

查看文件

@@ -123,7 +123,7 @@ import { onShow } from '@dcloudio/uni-app';
import { ICONS } from '@/common/icons.js';
import { getCurrentThemeKey, applyThemeToNativeUI } from '@/common/theme.js';
import { getNavBarInfo } from '@/common/nav.js';
import { getFavorites, removeFavorite } from '@/common/api.js';
import { getFavorites, removeFavorite, normalizeUserId } from '@/common/api.js';
const currentThemeKey = ref(getCurrentThemeKey());
const navInfo = ref(getNavBarInfo());
@@ -154,7 +154,7 @@ const loadFavoritesList = async (showLoadingTip = false) => {
isLoading.value = true;
}
try {
const userId = uni.getStorageSync('userId') || 1001;
const userId = normalizeUserId();
const list = await getFavorites(userId);
allFavorites.value = Array.isArray(list) ? list : [];
} catch (err) {
@@ -205,7 +205,7 @@ const handleCancelFavorite = (item) => {
if (res.confirm) {
try {
uni.showLoading({ title: '正在移除...' });
const userId = uni.getStorageSync('userId') || 1001;
const userId = normalizeUserId();
await removeFavorite(item.id, userId);
allFavorites.value = allFavorites.value.filter(fav => fav.id !== item.id);
uni.hideLoading();

查看文件

@@ -363,7 +363,7 @@ import { ICONS } from '@/common/icons.js';
import { getCurrentThemeKey, setTheme, THEME_KEYS, applyThemeToNativeUI } from '@/common/theme.js';
import { getCurrentRole, setCurrentRole, ROLE_TYPES, toggleRole as toggleUserRole, isExpertCertified, getExpertCertStatus, setExpertCertStatus, CERT_STATUS } from '@/common/role.js';
import { getNavBarInfo } from '@/common/nav.js';
import { getUserProfile, rechargeBalance, withdrawCommission, getUserFinanceRecords, updateUserProfile, uploadImage, getRechargeTiers, getPlatformConfigs, applyExpertCert, getUserWithdrawals, getFavorites, removeFavorite, getDistributionTeam, getDistributionRecords, getMyReports, bindInviteCode, getNotifications, getMyCoupons } from '@/common/api.js';
import { getUserProfile, rechargeBalance, withdrawCommission, getUserFinanceRecords, updateUserProfile, uploadImage, getRechargeTiers, getPlatformConfigs, applyExpertCert, getUserWithdrawals, getFavorites, removeFavorite, getDistributionTeam, getDistributionRecords, getMyReports, bindInviteCode, getNotifications, getMyCoupons, normalizeUserId } from '@/common/api.js';
import { preloadSubscribeTemplates, requestSubscribeAll } from '@/common/subscribe.js';
const DEFAULT_GUEST_AVATAR = 'https://images.unsplash.com/photo-1535713875002-d1d0cf377fde?w=200';
@@ -903,7 +903,7 @@ const saveProfile = async () => {
}
uni.showLoading({ title: '正在保存资料...' });
try {
const updated = await updateUserProfile(user.value.id || 1001, {
const updated = await updateUserProfile(normalizeUserId(), {
nickname: finalNickname,
avatar: editForm.value.avatar,
phone: finalPhone,
@@ -1093,7 +1093,7 @@ const showInvitePoster = () => {
};
const loadDistributionData = async () => {
const uid = user.value.id || 1001;
const uid = normalizeUserId();
try {
const [teamData, records] = await Promise.all([
getDistributionTeam(uid),
@@ -1105,7 +1105,7 @@ const loadDistributionData = async () => {
};
const copyInviteCode = () => {
const code = `PW${user.value.id || 1001}`;
const code = `PW${normalizeUserId()}`;
uni.setClipboardData({
data: code,
success: () => {
@@ -1115,7 +1115,7 @@ const copyInviteCode = () => {
};
const copyInviteLink = () => {
const link = `https://peiww.gynhc.com/invite?code=PW${user.value.id || 1001}`;
const link = `https://peiww.gynhc.com/invite?code=PW${normalizeUserId()}`;
uni.setClipboardData({
data: link,
success: () => {
@@ -1153,7 +1153,7 @@ const submitDistWithdraw = async () => {
submittingDistWithdraw.value = true;
uni.showLoading({ title: '正在提交申请...' });
try {
await withdrawCommission(user.value.id || 1001, amount, { channel: 'WECHAT' });
await withdrawCommission(normalizeUserId(), amount, { channel: 'WECHAT' });
user.value.commission = (currentComm - amount).toFixed(2);
distWithdrawAmount.value = '';
uni.hideLoading();

查看文件

@@ -183,7 +183,7 @@ import { onShow } from '@dcloudio/uni-app';
import { ICONS } from '@/common/icons.js';
import { getCurrentThemeKey, applyThemeToNativeUI } from '@/common/theme.js';
import { getNavBarInfo } from '@/common/nav.js';
import { getMyReports, getPlatformConfigs } from '@/common/api.js';
import { getMyReports, getPlatformConfigs, normalizeUserId } from '@/common/api.js';
const currentThemeKey = ref(getCurrentThemeKey());
const navInfo = ref(getNavBarInfo());
@@ -323,7 +323,7 @@ const handleBack = () => {
};
const loadReportsData = async () => {
const uid = uni.getStorageSync('userId') || 1001;
const uid = normalizeUserId();
try {
const list = await getMyReports(uid);
allReports.value = Array.isArray(list) ? list : [];

查看文件

@@ -186,7 +186,7 @@ import { onShow } from '@dcloudio/uni-app';
import { ICONS } from '@/common/icons.js';
import { getCurrentThemeKey, applyThemeToNativeUI } from '@/common/theme.js';
import { getNavBarInfo } from '@/common/nav.js';
import { getUserSettings, updateUserSettings, getSubscribeTemplates } from '@/common/api.js';
import { getUserSettings, updateUserSettings, getSubscribeTemplates, normalizeUserId } from '@/common/api.js';
const currentThemeKey = ref(getCurrentThemeKey());
const navInfo = ref(getNavBarInfo());
@@ -212,7 +212,7 @@ const checkIsLoggedIn = () => {
};
const currentUserId = computed(() => {
return Number(uni.getStorageSync('userId') || 1001);
return normalizeUserId();
});
// 本地与云端同步设置状态

查看文件

@@ -50,4 +50,23 @@ describe('第九十九批派单实时化与客服沟通约束', () => {
expect(workspace).toContain('openSupportSheet');
expect(orderList).not.toContain('pages/order/chat');
});
it('待接单必须是独立状态,不能伪装成进行中', () => {
const apiCode = read('pwuniapp/common/api.js');
expect(apiCode).toContain("statusKey === '待接单') statusKey = 'PENDING_ACCEPT'");
expect(orderList).toContain("order.status === 'PENDING_ACCEPT'");
expect(orderList).toContain('PENDING_ACCEPT:');
});
it('剩余演示身份兜底必须清理', () => {
expect(hall).not.toContain('|| 2006');
expect(hall).toContain('normalizeUserId()');
const bill = read('pwuniapp/pages/user/bill-records.vue');
expect(bill).not.toContain('|| 2006');
});
it('线下下单必须选择碰面地点', () => {
const createPage = read('pwuniapp/pages/order/create.vue');
expect(createPage).toContain('请填写或选择碰面地点');
});
});