From 16f6419cba2fb09ac2457c0d52466adec9519368 Mon Sep 17 00:00:00 2001 From: xcbyy <2578898740@qq.com> Date: Wed, 21 Oct 2026 16:21:10 +0800 Subject: [PATCH] =?UTF-8?q?refactor(data):=20=E5=85=A8=E9=9D=A2=E7=A7=BB?= =?UTF-8?q?=E9=99=A4=E5=B0=8F=E7=A8=8B=E5=BA=8F=E5=86=99=E6=AD=BB=E7=9A=84?= =?UTF-8?q?=E6=BC=94=E7=A4=BA/=E5=85=9C=E5=BA=95=E6=95=B0=E6=8D=AE?= =?UTF-8?q?=EF=BC=8C=E6=B5=8B=E8=AF=95=E6=95=B0=E6=8D=AE=E7=BB=9F=E4=B8=80?= =?UTF-8?q?=E8=90=BD=E5=BA=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 删除无引用的 common/mock.js;登录页与个人中心的演示账号改为 DB 配置 demo_accounts + /api/auth/demo-accounts、/api/auth/demo-login(签发真实 token) - 移除订单/活动/大神/动态/推广/工作台/门票/充值/举报等页面的演示列表与假兜底;新增真实空态 - 修复伪造成功:活动调价、评价回复、核销、海报保存、图片上传失败、提现/核销固定 1001、随机开黑房号、随机即时匹配、假语音播放等 - 服务端移除 101/2006 合成用户与合成大神兜底、移除默认服务包虚构价格;演示账号清单可在后台关闭 - 111 套件/733 用例全绿;已部署 20261021_162041,demo-accounts/demo-login 实测通过 --- pwuniapp/common/api.js | 103 +++-- pwuniapp/common/mock.js | 416 ------------------ pwuniapp/pages/activity/create.vue | 13 +- pwuniapp/pages/activity/detail.vue | 90 ++-- pwuniapp/pages/activity/list.vue | 6 +- pwuniapp/pages/activity/my-ticket.vue | 175 ++++---- pwuniapp/pages/auth/login.vue | 85 ++-- pwuniapp/pages/community/detail.vue | 30 +- pwuniapp/pages/community/index.vue | 159 +++---- pwuniapp/pages/expert/detail.vue | 90 ++-- pwuniapp/pages/expert/hall.vue | 19 +- pwuniapp/pages/expert/workspace.vue | 134 +----- pwuniapp/pages/index/index.vue | 57 ++- pwuniapp/pages/order/create.vue | 56 +-- pwuniapp/pages/order/list.vue | 28 +- pwuniapp/pages/user/index.vue | 44 +- pwuniapp/pages/user/promote.vue | 59 +-- pwuniapp/pages/user/recharge.vue | 31 +- pwuniapp/pages/user/reports.vue | 10 +- pwuniapp/pages/user/withdraw-records.vue | 2 +- pwuniapp/pages/user/withdraw.vue | 2 +- server/src/db/mysql.ts | 17 + server/src/db/repository.ts | 99 +---- server/src/index.ts | 135 ++++-- server/tests/activity-list-content.spec.ts | 134 ++---- server/tests/content-security.spec.ts | 12 +- server/tests/no-emoji.spec.ts | 11 +- server/tests/null-service-guard.spec.ts | 6 +- server/tests/order-activity-integrity.spec.ts | 2 +- server/tests/public-config-privacy.spec.ts | 2 +- 30 files changed, 661 insertions(+), 1366 deletions(-) delete mode 100644 pwuniapp/common/mock.js diff --git a/pwuniapp/common/api.js b/pwuniapp/common/api.js index 02f3ab8..fbba9df 100644 --- a/pwuniapp/common/api.js +++ b/pwuniapp/common/api.js @@ -11,7 +11,7 @@ export function formatActivityData(raw) { const payTypeStr = raw.payType === 2 || raw.payType === 'PROJECT' ? 'PROJECT' : 'AA'; const totalFee = raw.totalFeeCents != null ? raw.totalFeeCents / 100 : (raw.totalFee || 0); - const perPerson = raw.perPersonFeeCents != null ? raw.perPersonFeeCents / 100 : (raw.perPersonFee || (payTypeStr === 'AA' ? totalFee / (raw.maxParticipants || 4) : 500)); + const perPerson = raw.perPersonFeeCents != null ? raw.perPersonFeeCents / 100 : (raw.perPersonFee || (payTypeStr === 'AA' ? totalFee / (raw.maxParticipants || 4) : 0)); const participants = (raw.participants || []).map((p) => ({ id: p.id, userId: p.userId, @@ -26,20 +26,20 @@ export function formatActivityData(raw) { return { id: String(raw.id), - title: raw.title || '同城生活组局', - game: raw.gameCategory || raw.game || '美食陪吃', + title: raw.title || '', + game: raw.gameCategory || raw.game || '', track: raw.track || 'OFFLINE_MEAL', payType: payTypeStr, totalFee, perPersonFee: perPerson, aaFee: raw.aaFee != null ? raw.aaFee : perPerson, - maxParticipants: raw.maxParticipants || 4, - currentParticipants: joinedCount || 1, - startTime: raw.startTime || '今天 19:30', - location: raw.location || '北京市朝阳区公开商业街区', - creatorId: raw.creatorId != null ? Number(raw.creatorId) : (raw.creator_id != null ? Number(raw.creator_id) : 1001), - creatorNickname: raw.creatorNickname || '同城局长', - creatorAvatar: raw.creatorAvatar || 'https://images.unsplash.com/photo-1534528741775-53994a69daeb?w=100', + maxParticipants: raw.maxParticipants || 0, + currentParticipants: joinedCount, + startTime: raw.startTime || '', + location: raw.location || '', + creatorId: raw.creatorId != null ? Number(raw.creatorId) : (raw.creator_id != null ? Number(raw.creator_id) : 0), + creatorNickname: raw.creatorNickname || '', + creatorAvatar: raw.creatorAvatar || '', status: raw.status || (joinedCount >= (raw.maxParticipants || 4) ? 'FULL' : 'RECRUITING'), allowDynamicPrice: Boolean(raw.allowDynamicPrice), priceAdjustRule: raw.priceAdjustRule || 'UNIFIED_HIGH', @@ -203,23 +203,23 @@ export async function getCompanions(keyword) { .filter((c) => !c.auditStatus || c.auditStatus === '已通过') .map((c) => ({ id: String(c.id), - nickname: c.name || c.nickname || '同城大神', - avatar: c.avatar || 'https://images.unsplash.com/photo-1534528741775-53994a69daeb?w=300', - gender: c.gender === '男' ? 'male' : 'female', - age: Number(c.age) || 22, - game: c.track === 'OFFLINE_MEAL' ? '美食陪吃' : (c.track === 'OFFLINE_SING' ? '同城陪唱' : (c.track === 'OFFLINE_DRINK' ? '下午茶陪喝' : (c.game || '同城陪伴'))), + nickname: c.name || c.nickname || '', + avatar: c.avatar || '', + gender: c.gender === '男' ? 'male' : (c.gender === '女' ? 'female' : ''), + age: Number(c.age) || 0, + game: c.track === 'OFFLINE_MEAL' ? '美食陪吃' : (c.track === 'OFFLINE_SING' ? '同城陪唱' : (c.track === 'OFFLINE_DRINK' ? '下午茶陪喝' : (c.game || ''))), track: c.track || 'OFFLINE_MEAL', - city: '北京市 · 朝阳/海淀', - rankTag: c.rank || '同城认证陪伴师', - levelTag: '认证大神', - price: Number(c.hourlyRate) || 68, - rating: Number(c.rating) || 5.0, - orderCount: Number(c.orderCount) || 120, + city: c.city || c.customCity || '', + rankTag: c.rank || '', + levelTag: c.auditStatus === '已通过' ? '认证大神' : '', + price: Number(c.hourlyRate) || 0, + rating: Number(c.rating) || 0, + orderCount: Number(c.orderCount) || 0, isOnline: c.status === '接单中', - voiceDuration: Number(c.voiceDuration) || 15, + voiceDuration: Number(c.voiceDuration) || 0, voiceType: 'voice', - voiceTitle: c.voiceTitle || '语音招呼', - bio: '专业同城生活陪伴,阳光健康公开。' + voiceTitle: c.voiceTitle || '', + bio: c.bio || '' })); } } catch (err) { @@ -269,34 +269,33 @@ export function formatOrderData(raw) { ) statusKey = 'REFUNDING'; else if (statusKey === '已取消') statusKey = 'CANCELLED'; - const companionAvatar = raw.expertAvatar || raw.expert_avatar || raw.avatar || - 'https://images.unsplash.com/photo-1534528741775-53994a69daeb?w=120'; + const companionAvatar = raw.expertAvatar || raw.expert_avatar || raw.avatar || ''; - const companionNickname = raw.companionName || raw.companion_name || raw.expertNickname || (raw.expert && raw.expert.nickname) || '同城大神'; - const game = raw.track || (raw.expert && raw.expert.game) || '同城陪伴'; + const companionNickname = raw.companionName || raw.companion_name || raw.expertNickname || (raw.expert && raw.expert.nickname) || ''; + const game = raw.track || (raw.expert && raw.expert.game) || ''; return { id: String(raw.id), orderSn: raw.orderNo || raw.order_no || raw.orderSn || String(raw.id), expert: { - id: String(raw.expertId || raw.expert_id || (raw.expert && raw.expert.id) || 'exp_101'), + id: String(raw.expertId || raw.expert_id || (raw.expert && raw.expert.id) || ''), nickname: companionNickname, avatar: companionAvatar, game: game, }, - serviceTitle: raw.serviceTitle || raw.service_title || `${game} · 专业陪伴服务`, - quantity: Number(raw.durationHours || raw.duration_hours || raw.quantity || 2), + serviceTitle: raw.serviceTitle || raw.service_title || '', + quantity: Number(raw.durationHours || raw.duration_hours || raw.quantity || 0), totalAmount: Number(raw.amount || raw.totalAmount || 0), refundAmount: Number(raw.refundAmount || raw.refund_amount || 0), status: statusKey, - rawStatus: raw.status || '履约中', - meetVenue: raw.location || raw.meetVenue || '北京市公开商圈', - expenseBurden: raw.expenseBurden || raw.expense_burden || 'AA制各自付', - payType: raw.payType || raw.pay_type || 'WECHAT', + rawStatus: raw.status || '', + meetVenue: raw.location || raw.meetVenue || '', + expenseBurden: raw.expenseBurden || raw.expense_burden || '', + payType: raw.payType || raw.pay_type || '', couponDiscount: Number(raw.couponDiscount || raw.coupon_discount || 0), verifyCode: raw.verifyCode || raw.verify_code || '', remark: raw.remark || '', - createdAt: raw.createdAt || raw.created_at || '刚刚' + createdAt: raw.createdAt || raw.created_at || '' }; } @@ -475,13 +474,13 @@ export async function getPosts(category, userId) { return { id: p.id, userId: p.userId || p.user_id || 0, - author: p.author || '神秘同好', - avatar: p.avatar || 'https://images.unsplash.com/photo-1534528741775-53994a69daeb?w=150', - tag: p.tag || '同城认证', + author: p.author || '', + avatar: p.avatar || '', + tag: p.tag || '', tagType: p.tagType || 'warm-tag', - location: p.location || '同城热门商圈', - distance: p.distance || '1km内', - time: p.createdAt ? '刚刚' : '近期', + location: p.location || '', + distance: p.distance || '', + time: p.time || (p.createdAt ? '' : ''), content: p.content || '', topics: Array.isArray(p.topics) ? p.topics : [], images: Array.isArray(p.images) ? p.images : [], @@ -1528,3 +1527,23 @@ export async function loginWithWechatPhone({ phoneCode, wxCode } = {}) { wxCode }); } + +/** + * 体验账号列表(后台配置,DB 驱动;小程序端不再写死) + */ +export async function getDemoAccounts() { + try { + const res = await http.get('/auth/demo-accounts'); + return Array.isArray(res) ? res : []; + } catch (err) { + console.warn('[API] 获取体验账号异常:', err.message); + return []; + } +} + +/** + * 体验登录:服务端校验账号在配置清单内后签发真实会话 token + */ +export async function demoLogin(userId) { + return http.post('/auth/demo-login', { userId: Number(userId) }); +} diff --git a/pwuniapp/common/mock.js b/pwuniapp/common/mock.js deleted file mode 100644 index 16ad764..0000000 --- a/pwuniapp/common/mock.js +++ /dev/null @@ -1,416 +0,0 @@ -import { ICONS } from './icons.js'; - -export const GAMES_LIST = [ - { id: 'all', name: '全部', icon: ICONS.gameAll, hot: false }, - { id: 'offline_meal', name: '美食陪吃', icon: ICONS.utensils, hot: true }, - { id: 'offline_drink', name: '下午茶陪喝', icon: ICONS.coffee, hot: true }, - { id: 'offline_sing', name: '同城陪唱', icon: ICONS.micVocal, hot: true }, - { id: 'offline_play', name: '同城陪玩', icon: ICONS.dice, hot: false }, - { id: 'wzry', name: '王者荣耀', icon: ICONS.gameCrown, hot: false }, - { id: 'hpjy', name: '和平精英', icon: ICONS.gameCrosshair, hot: false }, - { id: 'lol', name: '英雄联盟', icon: ICONS.gameSwords, hot: false }, -]; - -export const BANNERS = [ - { id: '1', title: '同城聚会狂欢 | 网红火锅探店与量贩KTV嗨唱', tag: '热门陪伴', bg: 'linear-gradient(135deg, #F43F5E 0%, #FB7185 100%)' }, - { id: '2', title: '新用户专享 | 线下陪吃陪喝立减30元', tag: '新人特惠', bg: 'linear-gradient(135deg, #059669 0%, #10B981 100%)' }, - { id: '3', title: '天籁歌喉进驻 | 音乐学院陪唱达人歌声试听', tag: 'K歌对唱', bg: 'linear-gradient(135deg, #7C3AED 0%, #6366F1 100%)' }, -]; - -export const EXPERTS_LIST = [ - { - id: 'exp_105', - nickname: '橙子小七', - avatar: 'https://images.unsplash.com/photo-1534528741775-53994a69daeb?w=300&h=300&fit=crop&crop=faces', - coverImage: 'https://images.unsplash.com/photo-1517248135467-4c7edcad34c4?w=800&h=400&fit=crop', - gender: 'female', - age: 22, - game: '美食陪吃', - track: 'OFFLINE_MEAL', - city: '北京市 · 朝阳/三里屯', - rankTag: '美食探店达人 · 火锅烤肉搭子', - levelTag: '同城红人', - price: 68, - rating: 5.0, - orderCount: 420, - isOnline: true, - voiceDuration: 14, - voiceType: 'voice', - bio: '资深美食探店博主,性格随和开朗,陪你吃遍同城网红餐厅与地道市井美味,消费约定AA或请客~', - photos: ['https://images.unsplash.com/photo-1517248135467-4c7edcad34c4?w=500&fit=crop'], - services: [ - { id: 'srv_7', title: '同城美食探店 · 火锅/烤肉饭搭子', price: 68, unit: '小时', desc: '公开商场餐厅,绝不踩雷,现场AA或买单' }, - { id: 'srv_8', title: '商圈早午餐/轻食下午茶陪伴', price: 58, unit: '小时', desc: '轻松闲聊,氛围愉快,阳光公开场所' }, - ], - reviews: [ - { id: 'r4', userNickname: '吃货小张', userAvatar: 'https://images.unsplash.com/photo-1535713875002-d1d0cf377fde?w=100', rating: 5, tags: ['随和健谈', '推荐靠谱'], content: '火锅店推荐的太好吃了,聊得很投缘!', time: '昨天' }, - ], - }, - { - id: 'exp_107', - nickname: '苏苏学姐', - avatar: 'https://images.unsplash.com/photo-1524504388940-b1c1722653e1?w=300&h=300&fit=crop&crop=faces', - coverImage: 'https://images.unsplash.com/photo-1516450360452-9312f5e86fc7?w=800&h=400&fit=crop', - gender: 'female', - age: 21, - game: '同城陪唱', - track: 'OFFLINE_SING', - city: '北京市 · 工体/五道口', - rankTag: '音乐学院在读 · 甜美情歌对唱', - levelTag: '天籁歌喉', - price: 88, - rating: 5.0, - orderCount: 560, - isOnline: true, - voiceDuration: 18, - voiceType: 'song', - voiceTitle: '歌声 · 《晴天》', - bio: '音乐学院流行演唱在读,精通流行热门情歌与粤语金曲,KTV对唱超好听,包厢气氛组担当~限正规公开量贩KTV。', - photos: ['https://images.unsplash.com/photo-1516450360452-9312f5e86fc7?w=500&fit=crop'], - services: [ - { id: 'srv_11', title: '量贩KTV麦霸对唱与包厢气氛', price: 88, unit: '小时', desc: '正规公开KTV,包厢房费与酒水消费约定AA或请客' }, - { id: 'srv_12', title: '同城Livehouse/音乐节同游陪伴', price: 98, unit: '小时', desc: '现场沉浸听歌,音乐狂欢好搭子' }, - ], - reviews: [ - { id: 'r5', userNickname: 'K歌达人阿伟', userAvatar: 'https://images.unsplash.com/photo-1570295999919-56ceb5ecca61?w=100', rating: 5, tags: ['歌声惊艳', '超高情商'], content: '歌声真的太好听了,完全是专业级水平,气氛特别好!', time: '3小时前' }, - ], - }, - { - id: 'exp_106', - nickname: '莫吉托学长', - avatar: 'https://images.unsplash.com/photo-1506794778202-cad84cf45f1d?w=300&h=300&fit=crop&crop=faces', - coverImage: 'https://images.unsplash.com/photo-1501339847302-ac426a4a7cbb?w=800&h=400&fit=crop', - gender: 'male', - age: 24, - game: '下午茶陪喝', - track: 'OFFLINE_DRINK', - city: '北京市 · 国贸/望京', - rankTag: '手冲咖啡师 · 清吧倾听解压', - levelTag: '暖心导师', - price: 58, - rating: 4.9, - orderCount: 310, - isOnline: true, - voiceDuration: 16, - voiceType: 'voice', - bio: 'SCA认证咖啡师与调酒师,善于倾听情绪解压,阳光透明,约定公开正规咖啡馆或清吧。', - photos: ['https://images.unsplash.com/photo-1501339847302-ac426a4a7cbb?w=500&fit=crop'], - services: [ - { id: 'srv_9', title: '精品咖啡品鉴 · 聊天解压倾听', price: 58, unit: '小时', desc: '星巴克臻选/独立咖啡馆,温和交流' }, - { id: 'srv_10', title: '正规清吧微醺特调陪伴', price: 78, unit: '小时', desc: '限公开正规商业清吧,严禁过量饮酒' }, - ], - reviews: [], - }, - { - id: 'exp_108', - nickname: '阿泽学长', - avatar: 'https://images.unsplash.com/photo-1539571696357-5a69c17a67c6?w=300&h=300&fit=crop&crop=faces', - coverImage: 'https://images.unsplash.com/photo-1511671782779-c97d3d27a1d4?w=800&h=400&fit=crop', - gender: 'male', - age: 23, - game: '同城陪唱', - track: 'OFFLINE_SING', - city: '北京市 · 海淀/中关村', - rankTag: '流行摇滚主唱 · 吉他伴奏对唱', - levelTag: '全能麦霸', - price: 78, - rating: 4.9, - orderCount: 480, - isOnline: true, - voiceDuration: 20, - voiceType: 'song', - voiceTitle: '歌声 · 摇滚高音', - bio: '高校乐队主唱,吉他弹唱/流行摇滚,KTV气氛直接拉满,绝不冷场,带你体验演唱会包厢~', - photos: ['https://images.unsplash.com/photo-1511671782779-c97d3d27a1d4?w=500&fit=crop'], - services: [ - { id: 'srv_13', title: 'KTV流行摇滚对唱与吉他弹唱', price: 78, unit: '小时', desc: '气氛担当,带动全场,限公开正规量贩KTV' }, - ], - reviews: [], - }, - { - id: 'exp_101', - nickname: '月色浅浅', - avatar: 'https://images.unsplash.com/photo-1534528741775-53994a69daeb?w=300&h=300&fit=crop&crop=faces', - coverImage: 'https://images.unsplash.com/photo-1542751371-adc38448a05e?w=800&h=400&fit=crop', - gender: 'female', - age: 21, - game: '王者荣耀', - rankTag: '百星王者 · 国服瑶妹', - levelTag: '金牌导师', - price: 35, - rating: 4.9, - orderCount: 1280, - isOnline: true, - voiceDuration: 12, - bio: '全天在线,心态超好,技术过硬,带你轻松摘星~', - photos: [ - 'https://images.unsplash.com/photo-1534528741775-53994a69daeb?w=500&fit=crop', - 'https://images.unsplash.com/photo-1517841905240-472988babdf9?w=500&fit=crop', - ], - services: [ - { id: 'srv_1', title: '王者荣耀 · 排位包胜率', price: 35, unit: '小时', desc: '不包赢不退,高效稳定冲星' }, - { id: 'srv_2', title: '王者荣耀 · 娱乐开黑陪练', price: 28, unit: '小时', desc: '温柔互动,欢乐开黑' }, - ], - reviews: [ - { id: 'r1', userNickname: '电竞刺客', userAvatar: 'https://images.unsplash.com/photo-1535713875002-d1d0cf377fde?w=100', rating: 5, tags: ['声音甜美', '技术过硬'], content: '小姐姐声音太好听了,操作也很稳,连赢5把!', time: '2小时前' }, - { id: 'r2', userNickname: '峡谷追梦人', userAvatar: 'https://images.unsplash.com/photo-1570295999919-56ceb5ecca61?w=100', rating: 5, tags: ['准时上号', '耐心教学'], content: '很耐心教辅助意识,体验绝佳。', time: '昨天' }, - ], - }, - { - id: 'exp_102', - nickname: '夜鹰狙神', - avatar: 'https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?w=300&h=300&fit=crop&crop=faces', - coverImage: 'https://images.unsplash.com/photo-1511512578047-dfb367046420?w=800&h=400&fit=crop', - gender: 'male', - age: 23, - game: '和平精英', - rankTag: '无敌战神 · 15KD', - levelTag: '顶流大神', - price: 45, - rating: 5.0, - orderCount: 2360, - isOnline: true, - voiceDuration: 18, - bio: '前职业青训选手,意识拉满,带粉乱杀,绝不白给!', - photos: [ - 'https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?w=500&fit=crop', - 'https://images.unsplash.com/photo-1500648767791-00dcc994a43e?w=500&fit=crop', - ], - services: [ - { id: 'srv_3', title: '和平精英 · 战神四排车队', price: 45, unit: '小时', desc: '战神指引,全程高能护航' }, - { id: 'srv_4', title: '吃鸡指导 · 枪法与身法特训', price: 60, unit: '小时', desc: '1对1定制身法特训' }, - ], - reviews: [ - { id: 'r3', userNickname: 'AWM战狂', userAvatar: 'https://images.unsplash.com/photo-1535713875002-d1d0cf377fde?w=100', rating: 5, tags: ['枪法如神', '落地包稳'], content: '真的太猛了,连吃三把鸡,KD直接飙升!', time: '前天' }, - ], - }, - { - id: 'exp_103', - nickname: '软软喵', - avatar: 'https://images.unsplash.com/photo-1494790108377-be9c29b29330?w=300&h=300&fit=crop&crop=faces', - coverImage: 'https://images.unsplash.com/photo-1538481199705-c710c4e965fc?w=800&h=400&fit=crop', - gender: 'female', - age: 20, - game: '英雄联盟', - rankTag: '傲世宗师 · 中单法王', - levelTag: '新星大神', - price: 30, - rating: 4.8, - orderCount: 650, - isOnline: false, - voiceDuration: 9, - bio: '峡谷高分妹子,阿狸/辛德拉本命,能C能辅,幽默开心果~', - photos: ['https://images.unsplash.com/photo-1494790108377-be9c29b29330?w=500&fit=crop'], - services: [ - { id: 'srv_5', title: 'LOL峡谷排位双排', price: 30, unit: '小时', desc: '双排上分,稳定Carry' }, - ], - reviews: [], - }, - { - id: 'exp_104', - nickname: '瞬移者雷克', - avatar: 'https://images.unsplash.com/photo-1500648767791-00dcc994a43e?w=300&h=300&fit=crop&crop=faces', - coverImage: 'https://images.unsplash.com/photo-1550745165-9bc0b252726f?w=800&h=400&fit=crop', - gender: 'male', - age: 22, - game: '无畏契约', - rankTag: '赋能战魂 · 芮娜绝活', - levelTag: '优质陪练', - price: 40, - rating: 4.9, - orderCount: 890, - isOnline: true, - voiceDuration: 15, - bio: '瓦罗兰特高分车头,定位准身法快,报点清晰,带你上神话!', - photos: ['https://images.unsplash.com/photo-1500648767791-00dcc994a43e?w=500&fit=crop'], - services: [ - { id: 'srv_6', title: '无畏契约冲分排位', price: 40, unit: '小时', desc: '带队突破,绝不拖泥带水' }, - ], - reviews: [], - }, -]; - -export const ACTIVITIES_LIST = [ - { - id: 'act_101', - title: '【陪吃·铜锅涮肉】周末三里屯地道老北京火锅探店AA局', - game: '美食陪吃', - track: 'OFFLINE_MEAL', - payType: 'AA', - totalFee: 240, - aaFee: 60, - maxParticipants: 4, - currentParticipants: 3, - startTime: '周六 18:30', - location: '北京市朝阳区三里屯聚宝源铜锅涮肉', - creatorNickname: '橙子小七', - creatorAvatar: 'https://images.unsplash.com/photo-1534528741775-53994a69daeb?w=100', - status: 'RECRUITING', - participants: [ - { id: 'p1', nickname: '涮肉小行家', avatar: 'https://images.unsplash.com/photo-1535713875002-d1d0cf377fde?w=100', verifyCode: '8392', checkedIn: false }, - { id: 'p2', nickname: '快乐干饭人', avatar: 'https://images.unsplash.com/photo-1570295999919-56ceb5ecca61?w=100', verifyCode: '6147', checkedIn: true }, - { id: 'p3', nickname: '胡同老饕', avatar: 'https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?w=100', verifyCode: '9025', checkedIn: false }, - ], - }, - { - id: 'act_102', - title: '【同城组队·高额悬赏】工体欢唱减压局·急招欢唱搭子', - game: '同城陪唱', - track: 'OFFLINE_SING', - payType: 'PROJECT', - pricingMode: 'PER_PERSON', - perPersonFee: 500, - totalFee: 2000, - aaFee: 0, - maxParticipants: 4, - currentParticipants: 3, - allowDynamicPrice: true, - priceAdjustRule: 'UNIFIED_HIGH', - priceAdjustHistory: [], - startTime: '周五 20:00', - location: '北京市朝阳区工人体育场纯K 888豪华大包', - creatorNickname: '苏苏学姐', - creatorAvatar: 'https://images.unsplash.com/photo-1524504388940-b1c1722653e1?w=100', - status: 'RECRUITING', - participants: [ - { id: 'p4', nickname: '情歌王子', avatar: 'https://images.unsplash.com/photo-1494790108377-be9c29b29330?w=100', verifyCode: '2481', checkedIn: true, priceAtJoin: 500, diffCompensated: 0 }, - { id: 'p5', nickname: '高音小王子', avatar: 'https://images.unsplash.com/photo-1500648767791-00dcc994a43e?w=100', verifyCode: '7390', checkedIn: false, priceAtJoin: 500, diffCompensated: 0 }, - ], - }, - { - id: 'act_103', - title: '【陪喝·高空下午茶】国贸云端景观双人下午茶拍照闲聊', - game: '下午茶陪喝', - track: 'OFFLINE_DRINK', - payType: 'AA', - totalFee: 196, - aaFee: 98, - maxParticipants: 2, - currentParticipants: 1, - startTime: '周日 14:30', - location: '北京市朝阳区国贸商城高空云端景观咖啡', - creatorNickname: '清芷', - creatorAvatar: 'https://images.unsplash.com/photo-1517841905240-472988babdf9?w=100', - status: 'RECRUITING', - participants: [ - { id: 'p6', nickname: '拿铁爱好者', avatar: 'https://images.unsplash.com/photo-1534528741775-53994a69daeb?w=100', verifyCode: '5821', checkedIn: false }, - ], - }, - { - id: 'act_104', - title: '【陪吃·炭火烤肉】望京韩国街地道厚切五花肉深夜食堂搭子', - game: '美食陪吃', - track: 'OFFLINE_MEAL', - payType: 'AA', - totalFee: 260, - aaFee: 65, - maxParticipants: 4, - currentParticipants: 2, - startTime: '周六 19:00', - location: '北京市朝阳区望京新天地地道炭火烤肉', - creatorNickname: '桃子汽水', - creatorAvatar: 'https://images.unsplash.com/photo-1544005313-94ddf0286df2?w=100', - status: 'RECRUITING', - participants: [ - { id: 'p7', nickname: '烤肉达人', avatar: 'https://images.unsplash.com/photo-1535713875002-d1d0cf377fde?w=100', verifyCode: '3192', checkedIn: false }, - ], - }, - { - id: 'act_105', - title: '【陪喝·精酿微醺】五道营胡同屋顶露台精酿清吧品酒局', - game: '同城微醺', - track: 'OFFLINE_DRINK', - payType: 'PROJECT', - totalFee: 480, - aaFee: 0, - maxParticipants: 4, - currentParticipants: 3, - startTime: '周六 21:00', - location: '北京市东城区五道营胡同露台精酿餐吧', - creatorNickname: '夜鹰', - creatorAvatar: 'https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?w=100', - status: 'RECRUITING', - participants: [ - { id: 'p8', nickname: '微醺客', avatar: 'https://images.unsplash.com/photo-1500648767791-00dcc994a43e?w=100', verifyCode: '4912', checkedIn: true }, - ], - }, - { - id: 'act_106', - title: '【陪唱·音乐清吧】五道口民谣现场驻唱与合唱拼桌', - game: '同城陪唱', - track: 'OFFLINE_SING', - payType: 'AA', - totalFee: 200, - aaFee: 50, - maxParticipants: 4, - currentParticipants: 4, - startTime: '周日 20:30', - location: '北京市海淀区五道口民谣音乐清吧', - creatorNickname: '月色浅浅', - creatorAvatar: 'https://images.unsplash.com/photo-1534528741775-53994a69daeb?w=100', - status: 'RUNNING', - participants: [ - { id: 'p9', nickname: '吉他手阿杰', avatar: 'https://images.unsplash.com/photo-1570295999919-56ceb5ecca61?w=100', verifyCode: '7721', checkedIn: true }, - { id: 'p10', nickname: '民谣小调', avatar: 'https://images.unsplash.com/photo-1494790108377-be9c29b29330?w=100', verifyCode: '8832', checkedIn: true }, - ], - }, -]; - -export const ORDERS_LIST = [ - { - id: 'ord_1', - orderSn: 'PW20260916001', - expert: { id: 'exp_101', nickname: '月色浅浅', avatar: 'https://images.unsplash.com/photo-1534528741775-53994a69daeb?w=100', game: '王者荣耀' }, - serviceTitle: '王者荣耀 · 排位包胜率', - quantity: 2, - totalAmount: 70, - status: 'IN_SERVICE', - createdAt: '2026-09-16 15:30', - }, - { - id: 'ord_4', - orderSn: 'PW20260916099', - expert: { id: 'exp_105', nickname: '橙子小七', avatar: 'https://images.unsplash.com/photo-1534528741775-53994a69daeb?w=100', game: '美食陪吃' }, - serviceTitle: '同城美食探店 · 火锅/烤肉饭搭子', - quantity: 2, - totalAmount: 136, - status: 'IN_SERVICE', - meetVenue: '朝阳大悦城海底捞', - expenseBurden: 'AA制各自付', - verifyCode: '6829', - createdAt: '2026-09-16 18:00', - }, - { - id: 'ord_2', - orderSn: 'PW20260915088', - expert: { id: 'exp_102', nickname: '夜鹰狙神', avatar: 'https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?w=100', game: '和平精英' }, - serviceTitle: '和平精英 · 战神四排车队', - quantity: 1, - totalAmount: 45, - status: 'COMPLETED', - createdAt: '2026-09-15 19:00', - }, - { - id: 'ord_3', - orderSn: 'PW20260914032', - expert: { id: 'exp_103', nickname: '软软喵', avatar: 'https://images.unsplash.com/photo-1494790108377-be9c29b29330?w=100', game: '英雄联盟' }, - serviceTitle: 'LOL峡谷排位双排', - quantity: 3, - totalAmount: 90, - status: 'PENDING_PAY', - createdAt: '2026-09-14 21:10', - }, -]; - -export const USER_PROFILE = { - id: 'u_888', - nickname: '超能电竞玩家', - avatar: 'https://images.unsplash.com/photo-1535713875002-d1d0cf377fde?w=200&h=200&fit=crop&crop=faces', - gender: 'male', - role: 'PLAYER', - balance: 380.5, - frozenBalance: 0, - commission: 128.0, - invitedCount: 16, - isCertifiedExpert: false, -}; diff --git a/pwuniapp/pages/activity/create.vue b/pwuniapp/pages/activity/create.vue index f403374..2e4c6ca 100644 --- a/pwuniapp/pages/activity/create.vue +++ b/pwuniapp/pages/activity/create.vue @@ -491,20 +491,13 @@ const handleChooseLocation = () => { }, fail: (err) => { console.warn('[ActivityCreate] 地图选点取消或未授权:', err); - // 若未授权或在H5/开发环境,平滑引导点选推荐公开商圈 - if (recommendPublicZones.value.length > 0) { - selectZone(recommendPublicZones.value[0]); - uni.showToast({ title: '已填入推荐商圈', icon: 'none' }); - } + uni.showToast({ title: '未选择地点,可手动填写', icon: 'none' }); } }); // #endif // #ifndef MP-WEIXIN || APP-PLUS - if (recommendPublicZones.value.length > 0) { - selectZone(recommendPublicZones.value[0]); - uni.showToast({ title: '已为您填入示范商圈', icon: 'none' }); - } + uni.showToast({ title: '当前环境不支持地图选点,请手动填写', icon: 'none' }); // #endif }; @@ -659,7 +652,7 @@ const handleSubmit = async () => { location: trimmedLocation, latitude: form.value.latitude, longitude: form.value.longitude, - creatorNickname: cachedUser.nickname || '同城局长', + creatorNickname: cachedUser.nickname || '同城用户', creatorAvatar: cachedUser.avatar || 'https://images.unsplash.com/photo-1535713875002-d1d0cf377fde?w=150&h=150&fit=crop', }; diff --git a/pwuniapp/pages/activity/detail.vue b/pwuniapp/pages/activity/detail.vue index 9920e75..208c79a 100644 --- a/pwuniapp/pages/activity/detail.vue +++ b/pwuniapp/pages/activity/detail.vue @@ -52,7 +52,7 @@ - 当前单人出价 ¥{{ activity.perPersonFee || 500 }}/人 · 招募{{ activity.maxParticipants }}人 · 已报名{{ joinedCount }}人 + 当前单人出价 ¥{{ activity.perPersonFee || 0 }}/人 · 招募{{ activity.maxParticipants }}人 · 已报名{{ joinedCount }}人 @@ -76,9 +76,9 @@ {{ activity.creatorNickname }} - 同城局长 + 发起人 - 同城组局召集人 · 履约守约率 100% + 同城组局召集人 @@ -418,7 +418,8 @@ import { adjustActivityPrice, verifyActivityCheckin, cancelActivity, - leaveActivity + leaveActivity, + normalizeUserId } from '@/common/api.js'; import { ICONS } from '@/common/icons.js'; import { getCurrentThemeKey, applyThemeToNativeUI } from '@/common/theme.js'; @@ -489,18 +490,18 @@ const stopPropagation = (e) => { const activity = ref({ id: '', - title: '同城生活组局', - game: '同城陪伴', + title: '', + game: '', payType: 'AA', totalFee: 0, perPersonFee: 0, - maxParticipants: 4, - currentParticipants: 1, - startTime: '根据预约安排', - location: '同城公开商业场所', - creatorId: 1001, - creatorNickname: '局长', - creatorAvatar: 'https://images.unsplash.com/photo-1534528741775-53994a69daeb?w=100', + maxParticipants: 0, + currentParticipants: 0, + startTime: '', + location: '', + creatorId: 0, + creatorNickname: '', + creatorAvatar: '', participants: [], priceAdjustHistory: [] }); @@ -510,9 +511,12 @@ const loadLiveDetail = async (id) => { const detail = await getActivityDetail(id); if (detail) { activity.value = detail; + } else { + uni.showToast({ title: '组局不存在或已下架', icon: 'none' }); } } catch (err) { console.warn('[ActivityDetail] 获取详情失败:', err); + uni.showToast({ title: err.message || '组局加载失败', icon: 'none' }); } }; @@ -559,14 +563,14 @@ const setQuickPrice = (val) => { }; const joinedCount = computed(() => { - if (activity.value && activity.value.participants) { + if (activity.value && Array.isArray(activity.value.participants)) { return activity.value.participants.length; } - return 2; + return 0; }); const currentPerPerson = computed(() => { - return Number(activity.value.perPersonFee) || 500; + return Number(activity.value.perPersonFee) || 0; }); const appendQuickOptions = computed(() => { @@ -631,7 +635,8 @@ const confirmAdjustPrice = async () => { uni.hideLoading(); } catch (err) { uni.hideLoading(); - console.warn('[ActivityDetail] 远程调价异常,执行本地同步:', err.message); + uni.showToast({ title: err.message || '调价失败,请稍后重试', icon: 'none' }); + return; } activity.value.perPersonFee = newP; @@ -686,44 +691,45 @@ const copyLocation = () => { const openHostProfile = () => { const hostId = activity.value.creatorId || 1001; - const hostName = activity.value.creatorNickname || '同城局长'; + const hostName = activity.value.creatorNickname || '同城用户'; const hostAvatar = activity.value.creatorAvatar || 'https://images.unsplash.com/photo-1534528741775-53994a69daeb?w=150'; const hostSnapshot = { id: hostId, userId: hostId, nickname: hostName, avatar: hostAvatar, - rankTag: '同城局长 · 守约率100%', - levelTag: '实名认证', - game: activity.value.game || '同城陪伴', - city: activity.value.location || '北京市 · 朝阳商圈', - bio: `【${activity.value.title || '同城生活组局'}】发起人,历史组局履约守约率 100%,热情好客,欢迎同城搭子一起加入!`, - photos: [hostAvatar] + rankTag: '发起人', + levelTag: '', + game: activity.value.game || '', + city: activity.value.location || '', + bio: activity.value.title ? `【${activity.value.title}】发起人` : '', + photos: hostAvatar ? [hostAvatar] : [] }; uni.setStorageSync('ACTIVE_AUTHOR_PROFILE', hostSnapshot); uni.navigateTo({ - url: `/pages/expert/detail?id=${encodeURIComponent(String(hostId))}&name=${encodeURIComponent(hostName)}&avatar=${encodeURIComponent(hostAvatar)}&tag=${encodeURIComponent('同城局长 · 守约100%')}&city=${encodeURIComponent(hostSnapshot.city)}` + url: `/pages/expert/detail?id=${encodeURIComponent(String(hostId))}&name=${encodeURIComponent(hostName)}&avatar=${encodeURIComponent(hostAvatar)}&tag=${encodeURIComponent('发起人')}&city=${encodeURIComponent(hostSnapshot.city)}` }); }; const chatWithHost = () => { uni.showActionSheet({ itemList: [ - '复制组局联络专属口令', - '查看局长主页及守约信誉' + '复制组局信息', + '查看发起人主页' ], success: (res) => { if (res.tapIndex === 0) { - const hidePhone = uni.getStorageSync('privacy_hide_phone') === '1'; - const rawPhone = activity.value.contactPhone || '13888886666'; - const displayContact = hidePhone - ? rawPhone.replace(/(\d{3})\d{4}(\d{4})/, '$1****$2') + '(隐私保护已脱敏)' - : rawPhone; - const tokenText = `【同城组局联络口令】${activity.value.title || '同城组局'} | 时间:${activity.value.startTime || '今天 19:30'} | 地点:${activity.value.location || '公开商圈'} | 联络方式:${displayContact} | 专属核验口令:#JU-${activity.value.id || '101'}-PW`; + const parts = [ + `【组局】${activity.value.title || ''}`, + `时间:${activity.value.startTime || '待定'}`, + `地点:${activity.value.location || '待定'}` + ]; + const contact = String(activity.value.contactPhone || '').trim(); + if (contact) parts.push(`联络方式:${contact}`); uni.setClipboardData({ - data: tokenText, + data: parts.join(' | '), success: () => { - uni.showToast({ title: '专属组局口令已复制', icon: 'success' }); + uni.showToast({ title: '组局信息已复制', icon: 'success' }); } }); } else if (res.tapIndex === 1) { @@ -737,9 +743,9 @@ const goToMyTicket = (code) => { if (!ensureLoginOrRedirect(`/pages/activity/detail?id=${currentActId.value}`)) { return; } - const c = code || (activity.value.participants && activity.value.participants[0] ? activity.value.participants[0].verifyCode : '8392'); + const c = code || ((activity.value.participants || []).find(p => Number(p.userId) === Number(currentUserId.value)) || {}).verifyCode || ''; uni.navigateTo({ - url: `/pages/activity/my-ticket?id=${activity.value.id}&code=${c}` + url: `/pages/activity/my-ticket?id=${activity.value.id}${c ? `&code=${encodeURIComponent(c)}` : ''}` }); }; @@ -851,19 +857,17 @@ const handleJoin = () => { // 报名前请求一次性订阅授权(组局报名通知) requestSubscribeFor('activity'); uni.showLoading({ title: '正在报名生成核销码...' }); - let code = '8392'; + let code = ''; try { const fee = activity.value.payType === 'AA' ? activity.value.aaFee : 0; - const activeUid = currentUserId.value || Number(uni.getStorageSync('userId')); + const activeUid = normalizeUserId(); const joinRes = await joinActivity(activity.value.id, { userId: activeUid, payAmount: fee, }); - if (joinRes && joinRes.verifyCode) { - code = joinRes.verifyCode; - } + code = joinRes && joinRes.verifyCode ? joinRes.verifyCode : ''; uni.hideLoading(); - uni.showToast({ title: '报名成功!已生成4位核销码', icon: 'success' }); + uni.showToast({ title: '报名成功', icon: 'success' }); setTimeout(() => { goToMyTicket(code); }, 1000); diff --git a/pwuniapp/pages/activity/list.vue b/pwuniapp/pages/activity/list.vue index aceeb36..03febc8 100644 --- a/pwuniapp/pages/activity/list.vue +++ b/pwuniapp/pages/activity/list.vue @@ -176,12 +176,12 @@ import { ref, computed, onMounted, onUnmounted } from 'vue'; import { onShow, onLoad } from '@dcloudio/uni-app'; import AppTabBar from '@/components/CustomTabBar.vue'; import SkeletonScreen from '@/components/SkeletonScreen.vue'; -import { getActivities, getNavCategories } from '@/common/api.js'; +import { getActivities, getNavCategories, normalizeUserId } from '@/common/api.js'; import { ICONS } from '@/common/icons.js'; import { getCurrentThemeKey, applyThemeToNativeUI } from '@/common/theme.js'; import { getNavBarInfo } from '@/common/nav.js'; -const CURRENT_USER_ID = 1001; +const CURRENT_USER_ID = normalizeUserId(); const isLoading = ref(true); const activities = ref([]); @@ -335,7 +335,7 @@ const filteredActivities = computed(() => { // 2. 支付模式或发单人筛选 let matchTab = true; if (currentTab.value === 'MINE') { - matchTab = Number(a.creatorId) === CURRENT_USER_ID || a.creatorNickname === '同城局长'; + matchTab = Number(a.creatorId) === Number(CURRENT_USER_ID); } else if (currentTab.value !== 'ALL') { matchTab = a.payType === currentTab.value; } diff --git a/pwuniapp/pages/activity/my-ticket.vue b/pwuniapp/pages/activity/my-ticket.vue index 24eeb30..b496fbb 100644 --- a/pwuniapp/pages/activity/my-ticket.vue +++ b/pwuniapp/pages/activity/my-ticket.vue @@ -1,6 +1,6 @@