diff --git a/pwuniapp/pages/expert/detail.vue b/pwuniapp/pages/expert/detail.vue
index b666bc6..0b17c5e 100644
--- a/pwuniapp/pages/expert/detail.vue
+++ b/pwuniapp/pages/expert/detail.vue
@@ -70,8 +70,8 @@
{{ expert.bio }}
-
-
+
+
服务项目
举报
-
- 当前选定:
- ¥{{ currentSelectedService ? currentSelectedService.price : expert.price }}
+
+
+ 当前选定:
+ ¥{{ currentSelectedService ? currentSelectedService.price : expert.price }}
+
+
+
+
+ 该用户尚未认证为大神,暂不可预约
-
@@ -297,23 +302,25 @@ const expert = ref({
nickname: '同城陪伴师',
avatar: 'https://images.unsplash.com/photo-1534528741775-53994a69daeb?w=300',
gender: 'female',
- levelTag: '平台认证',
+ levelTag: '',
game: '同城陪伴',
- rankTag: '专业级',
- city: '北京市 · 朝阳/海淀',
- rating: 5.0,
- orderCount: 86,
- isOnline: true,
- bio: '专业同城生活陪伴,健康阳光公开。',
- voiceDuration: 15,
- price: 68,
- services: buildDefaultServices('同城陪伴', 68),
+ rankTag: '',
+ city: '',
+ rating: 0,
+ orderCount: 0,
+ isOnline: false,
+ bio: '',
+ voiceDuration: 0,
+ price: 0,
+ services: [],
photos: [
'https://images.unsplash.com/photo-1534528741775-53994a69daeb?w=400',
'https://images.unsplash.com/photo-1517841905240-472988babdf9?w=400'
],
reviews: []
});
+// 是否为真实大神档案(非合成的普通用户资料)
+const isCompanion = computed(() => expert.value.isCompanion !== false);
const selectedServiceId = ref('srv_1');
const isPlayingVoice = ref(false);
let currentExpertTargetId = null;
diff --git a/server/src/db/repository.ts b/server/src/db/repository.ts
index 0bd354e..658da31 100644
--- a/server/src/db/repository.ts
+++ b/server/src/db/repository.ts
@@ -770,6 +770,8 @@ export class MysqlRepository {
async getCompanionById(id: number | string): Promise {
let c = await this.resolveCompanionRow(id);
+ // 是否为"非真实大神档案"的合成资料(用于对其隐藏虚构评分/价格/服务等)
+ let synthetic = false;
if (!c) {
const rawId = String(id).trim();
const numId = Number(rawId);
@@ -822,30 +824,31 @@ export class MysqlRepository {
c = {
id: userRow?.id || postRow?.user_id || cmtRow?.user_id || (!isNaN(numId) && numId > 0 ? numId : 2005),
user_id: userRow?.id || postRow?.user_id || cmtRow?.user_id || null,
- name: postRow?.author || userRow?.nickname || cmtRow?.author || '同城认证达人',
+ name: postRow?.author || userRow?.nickname || cmtRow?.author || '同城用户',
avatar: postRow?.avatar || userRow?.avatar || cmtRow?.avatar || 'https://images.unsplash.com/photo-1534528741775-53994a69daeb?w=300',
- gender: userRow?.gender === '男' ? '男' : '女',
- age: 22,
+ gender: userRow?.gender === '男' ? '男' : (userRow?.gender === '女' ? '女' : '保密'),
+ age: 0,
track: postRow?.category === 'game' ? '线上电竞' : '同城陪吃',
- rank_name: postRow?.tag || userRow?.role || '同城认证达人',
- hourly_rate: postRow?.category === 'game' ? 88 : 128,
- voice_duration: 16,
- voice_title: '真实原声介绍',
- rating: 4.96,
- order_count: Number(userRow?.order_count) || 86,
- status: '接单中',
- audit_status: '已通过',
- tags: postRow?.topics || JSON.stringify(['同城约伴', '随和健谈', '准时守信']),
- custom_bio: postRow?.content || userRow?.signature || cmtRow?.content || '专业同城生活陪伴,热情礼貌,守时诚信。',
- custom_city: postRow?.location || '北京市 · 朝阳/海淀',
+ rank_name: postRow?.tag || userRow?.role || '',
+ hourly_rate: 0,
+ voice_duration: 0,
+ voice_title: '',
+ rating: 0,
+ order_count: 0,
+ status: '休息中',
+ audit_status: '未申请',
+ tags: postRow?.topics || JSON.stringify([]),
+ custom_bio: postRow?.content || userRow?.signature || cmtRow?.content || '',
+ custom_city: postRow?.location || '',
custom_photos: postImages.length > 0 ? postImages : null
};
+ synthetic = true;
}
if (!c) return null;
const game = c.track === 'OFFLINE_MEAL' ? '美食陪吃' : (c.track === 'OFFLINE_SING' ? '同城陪唱' : (c.track === 'OFFLINE_DRINK' ? '下午茶陪喝' : (c.track === 'OFFLINE_PLAY' ? '同城陪玩' : (c.track || c.game || '同城生活陪伴'))));
const price = Number(c.hourly_rate) || 68;
- let parsedTags = ['高颜值', '性格好', '秒回消息', '文明健康'];
+ let parsedTags = synthetic ? [] : ['高颜值', '性格好', '秒回消息', '文明健康'];
if (c.tags) {
try {
const t = typeof c.tags === 'string' ? JSON.parse(c.tags) : c.tags;
@@ -905,32 +908,35 @@ export class MysqlRepository {
avatar: c.avatar || 'https://images.unsplash.com/photo-1534528741775-53994a69daeb?w=300',
coverImage: (c.custom_photos && c.custom_photos[0]) || c.avatar || 'https://images.unsplash.com/photo-1517248135467-4c7edcad34c4?w=800',
gender: c.gender === '男' ? 'male' : 'female',
- age: Number(c.age) || 22,
+ age: synthetic ? 0 : (Number(c.age) || 22),
game: game,
track: c.track || 'OFFLINE_MEAL',
- city: c.custom_city || '北京市 · 朝阳/海淀',
- rankTag: c.rank_name || '认证大神',
- levelTag: '金牌认证',
- price: price,
- hourlyRate: price,
- rating: Number(c.rating) || 5.0,
- orderCount: Number(c.order_count) || 120,
- status: c.status || '接单中',
- auditStatus,
- certStatus,
+ city: synthetic ? (c.custom_city || '') : (c.custom_city || '北京市 · 朝阳/海淀'),
+ rankTag: c.rank_name || (synthetic ? '' : '认证大神'),
+ levelTag: synthetic ? '普通用户' : '金牌认证',
+ price: synthetic ? 0 : price,
+ hourlyRate: synthetic ? 0 : price,
+ rating: synthetic ? 0 : (Number(c.rating) || 5.0),
+ orderCount: synthetic ? 0 : (Number(c.order_count) || 120),
+ status: synthetic ? '休息中' : (c.status || '接单中'),
+ auditStatus: synthetic ? '未申请' : auditStatus,
+ certStatus: synthetic ? 0 : certStatus,
rejectReason: c.reject_reason || '',
- isOnline: c.status === '接单中',
- voiceDuration: Number(c.voice_duration) || 15,
+ isOnline: synthetic ? false : c.status === '接单中',
+ isCompanion: !synthetic,
+ voiceDuration: synthetic ? 0 : (Number(c.voice_duration) || 15),
voiceType: 'voice',
- voiceTitle: c.voice_title || '语音招呼',
- bio: c.custom_bio || '专业同城生活陪伴,热情礼貌,守时诚信。',
+ voiceTitle: synthetic ? '' : (c.voice_title || '语音招呼'),
+ bio: c.custom_bio || (synthetic ? '' : '专业同城生活陪伴,热情礼貌,守时诚信。'),
tags: parsedTags,
- photos: c.custom_photos || [
- c.avatar || 'https://images.unsplash.com/photo-1534528741775-53994a69daeb?w=400',
- 'https://images.unsplash.com/photo-1517841905240-472988babdf9?w=400',
- 'https://images.unsplash.com/photo-1524504388940-b1c1722653e1?w=400'
- ],
- services: customServices.length > 0 ? customServices : defaultServices
+ photos: c.custom_photos || (synthetic
+ ? [c.avatar || 'https://images.unsplash.com/photo-1534528741775-53994a69daeb?w=400']
+ : [
+ c.avatar || 'https://images.unsplash.com/photo-1534528741775-53994a69daeb?w=400',
+ 'https://images.unsplash.com/photo-1517841905240-472988babdf9?w=400',
+ 'https://images.unsplash.com/photo-1524504388940-b1c1722653e1?w=400'
+ ]),
+ services: synthetic ? [] : (customServices.length > 0 ? customServices : defaultServices)
};
}
diff --git a/server/tests/companion-profile-honesty.spec.ts b/server/tests/companion-profile-honesty.spec.ts
new file mode 100644
index 0000000..ff91347
--- /dev/null
+++ b/server/tests/companion-profile-honesty.spec.ts
@@ -0,0 +1,42 @@
+// 已读取AGENTS.md
+import * as fs from 'fs';
+import * as path from 'path';
+
+/**
+ * 第六十六批回归闸门:去"合成虚拟大神"假数据
+ */
+describe('第六十六批大神档案真实性闸门', () => {
+ const rootDir = path.resolve(__dirname, '../../');
+ const repoCode = fs.readFileSync(path.join(rootDir, 'server/src/db/repository.ts'), 'utf-8');
+ const detail = fs.readFileSync(path.join(rootDir, 'pwuniapp/pages/expert/detail.vue'), 'utf-8');
+
+ const synthBlock = () => {
+ const start = repoCode.indexOf('async getCompanionById(');
+ expect(start).toBeGreaterThan(-1);
+ return repoCode.slice(start, start + 9000);
+ };
+
+ it('合成档案不再虚构评分/单量/价格/语音/状态', () => {
+ const b = synthBlock();
+ expect(b).toContain('let synthetic = false;');
+ expect(b).toContain('synthetic = true;');
+ expect(b).toContain('rating: 0');
+ expect(b).toContain('order_count: 0');
+ expect(b).toContain('hourly_rate: 0');
+ expect(b).toContain("status: '休息中'");
+ expect(b).toContain("audit_status: '未申请'");
+ // 合成档案不返回服务包
+ expect(b).toContain('services: synthetic ? []');
+ // 标记是否为真实大神
+ expect(b).toContain('isCompanion: !synthetic');
+ });
+
+ it('小程序对非大神档案隐藏服务与预约并去除假默认值', () => {
+ expect(detail).toContain('const isCompanion = computed(');
+ expect(detail).toContain('v-if="isCompanion"');
+ expect(detail).toContain('该用户尚未认证为大神,暂不可预约');
+ expect(detail).not.toContain('rating: 5.0');
+ expect(detail).not.toContain('orderCount: 86');
+ expect(detail).not.toContain('price: 68');
+ });
+});
diff --git a/smoke-batch66.ts b/smoke-batch66.ts
new file mode 100644
index 0000000..c145f22
--- /dev/null
+++ b/smoke-batch66.ts
@@ -0,0 +1,45 @@
+import { initDatabase, pool } from './server/src/db/mysql';
+import { repo } from './server/src/db/repository';
+
+async function main() {
+ await initDatabase();
+ const stamp = Date.now();
+ const uid = (stamp % 800000000) + 100000001;
+
+ const results: Array<[string, boolean, string]> = [];
+ const check = (name: string, ok: boolean, detail = '') => results.push([name, ok, detail]);
+
+ try {
+ await pool.query(
+ `INSERT INTO users (id, user_no, nickname, avatar, phone, gender, role, balance, order_count, status)
+ VALUES (?, ?, 'smoke-plain-user', '', '13100001111', '保密', '普通玩家', 0, 5, '正常')`,
+ [uid, `SMPU${stamp}`]
+ );
+
+ const p: any = await repo.getCompanionById(uid);
+ check('普通用户档案标记 isCompanion=false', p && p.isCompanion === false, JSON.stringify({ isCompanion: p && p.isCompanion }));
+ check('评分不再虚构(0)', Number(p.rating) === 0, String(p && p.rating));
+ check('单量不再虚构(0)', Number(p.orderCount) === 0, String(p && p.orderCount));
+ check('价格不再虚构(0)', Number(p.price) === 0, String(p && p.price));
+ check('不返回虚构服务包', Array.isArray(p.services) && p.services.length === 0, JSON.stringify(p && p.services));
+ check('状态为休息中', p.status === '休息中', String(p && p.status));
+ check('审核状态为未申请', p.auditStatus === '未申请', String(p && p.auditStatus));
+ } finally {
+ await pool.query('DELETE FROM companions WHERE user_id = ?', [uid]);
+ await pool.query('DELETE FROM users WHERE id = ?', [uid]);
+ }
+
+ let allPass = true;
+ for (const [n, ok, detail] of results) {
+ if (!ok) allPass = false;
+ console.log(`${ok ? 'PASS' : 'FAIL'} ${n} ${detail}`);
+ }
+ console.log(allPass ? '\nBATCH66 SMOKE: ALL PASS' : '\nBATCH66 SMOKE: FAILED');
+ await pool.end();
+ process.exit(allPass ? 0 : 1);
+}
+
+main().catch((e) => {
+ console.error('SMOKE ERROR', e);
+ process.exit(2);
+});