fix(theme): 修复门票页导航栏主题脱节与个人中心双身份卡片流光动画,加固活动参与人画像与大神隐私
这个提交包含在:
@@ -836,6 +836,22 @@ export async function initDatabase(): Promise<void> {
|
||||
// 检查并插入初始种子数据(若表为空)
|
||||
await seedInitialDataIfEmpty(conn);
|
||||
|
||||
// 数据修复:历史订单 expert_id 指向已不存在的档案(如 101)时,按 companion_name 回填真实档案 id
|
||||
// 避免完单结算时按旧的 101→1 兼容映射把钱结算给错误的大神
|
||||
// 放在种子数据之后执行,保证全新数据库的种子订单也能被修复
|
||||
try {
|
||||
await conn.query(`
|
||||
UPDATE orders o
|
||||
JOIN companions c ON c.name = o.companion_name
|
||||
SET o.expert_id = c.id
|
||||
WHERE o.expert_id >= 100 AND o.expert_id < 200
|
||||
AND o.expert_id NOT IN (SELECT id FROM companions)
|
||||
AND o.companion_name IS NOT NULL AND o.companion_name <> ''
|
||||
`);
|
||||
} catch (_) {
|
||||
// 表不存在或数据不满足条件:忽略
|
||||
}
|
||||
|
||||
console.log('[MySQL] peiwan 数据库核心表及种子数据已完整就绪');
|
||||
} finally {
|
||||
conn.release();
|
||||
|
||||
@@ -155,11 +155,13 @@ export class MysqlRepository {
|
||||
|
||||
const [ptRows]: any = await pool.query(`
|
||||
SELECT
|
||||
id, activity_id as activityId, user_id as userId,
|
||||
pay_amount_cents as payAmountCents, verify_code as verifyCode,
|
||||
checkin_status as checkinStatus, verified_at as verifiedAt,
|
||||
verified_by as verifiedBy, created_at as createdAt
|
||||
FROM participants
|
||||
p.id, p.activity_id as activityId, p.user_id as userId,
|
||||
u.nickname as nickname, u.avatar as avatar,
|
||||
p.pay_amount_cents as payAmountCents, p.verify_code as verifyCode,
|
||||
p.checkin_status as checkinStatus, p.verified_at as verifiedAt,
|
||||
p.verified_by as verifiedBy, p.created_at as createdAt
|
||||
FROM participants p
|
||||
LEFT JOIN users u ON u.id = p.user_id
|
||||
`);
|
||||
|
||||
return actRows.map((act: any) => {
|
||||
@@ -172,6 +174,8 @@ export class MysqlRepository {
|
||||
id: p.id,
|
||||
activityId: p.activityId,
|
||||
userId: p.userId,
|
||||
nickname: p.nickname || '',
|
||||
avatar: p.avatar || '',
|
||||
payAmountCents: p.payAmountCents,
|
||||
checkinStatus: Boolean(p.checkinStatus),
|
||||
verifiedAt: p.verifiedAt,
|
||||
@@ -220,13 +224,15 @@ export class MysqlRepository {
|
||||
|
||||
const [ptRows]: any = await pool.query(`
|
||||
SELECT
|
||||
id, activity_id as activityId, user_id as userId,
|
||||
pay_amount_cents as payAmountCents, verify_code as verifyCode,
|
||||
checkin_status as checkinStatus, verified_at as verifiedAt,
|
||||
verified_by as verifiedBy, created_at as createdAt
|
||||
FROM participants
|
||||
WHERE activity_id = ?
|
||||
ORDER BY created_at ASC
|
||||
p.id, p.activity_id as activityId, p.user_id as userId,
|
||||
u.nickname as nickname, u.avatar as avatar,
|
||||
p.pay_amount_cents as payAmountCents, p.verify_code as verifyCode,
|
||||
p.checkin_status as checkinStatus, p.verified_at as verifiedAt,
|
||||
p.verified_by as verifiedBy, p.created_at as createdAt
|
||||
FROM participants p
|
||||
LEFT JOIN users u ON u.id = p.user_id
|
||||
WHERE p.activity_id = ?
|
||||
ORDER BY p.created_at ASC
|
||||
`, [id]);
|
||||
|
||||
return {
|
||||
@@ -872,7 +878,8 @@ export class MysqlRepository {
|
||||
id, user_id as userId, name, avatar, gender, age, track, rank_name as \`rank\`,
|
||||
hourly_rate as hourlyRate, voice_duration as voiceDuration,
|
||||
voice_title as voiceTitle, voice_intro_url as voiceIntroUrl, rating, order_count as orderCount,
|
||||
status, audit_status as auditStatus, COALESCE(reject_reason, '') as rejectReason,
|
||||
status, (status = '接单中' AND last_active_at >= DATE_SUB(NOW(), INTERVAL 10 MINUTE)) as isOnline,
|
||||
audit_status as auditStatus, COALESCE(reject_reason, '') as rejectReason,
|
||||
id_card_masked as idCardMasked, tags, apply_time as applyTime
|
||||
FROM companions
|
||||
`;
|
||||
@@ -893,6 +900,7 @@ export class MysqlRepository {
|
||||
} catch (_) {}
|
||||
return {
|
||||
...r,
|
||||
isOnline: Boolean(r.isOnline),
|
||||
tags: tagsArr
|
||||
};
|
||||
});
|
||||
@@ -914,7 +922,11 @@ export class MysqlRepository {
|
||||
[nLat, nLng, id]
|
||||
);
|
||||
} else {
|
||||
await pool.query('UPDATE companions SET last_active_at = NOW() WHERE id = ?', [id]);
|
||||
// 本次没有有效定位:清空旧坐标,避免用过期位置参与 5km 匹配
|
||||
await pool.query(
|
||||
'UPDATE companions SET last_active_at = NOW(), latitude = NULL, longitude = NULL WHERE id = ?',
|
||||
[id]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -961,7 +973,6 @@ export class MysqlRepository {
|
||||
const distanceKm = hasFrom && hasGeo ? haversineKm(fromLat, fromLng, lat as number, lng as number) : null;
|
||||
return {
|
||||
id: String(r.id),
|
||||
userId: Number(r.userId) || 0,
|
||||
nickname: r.name || '',
|
||||
avatar: r.avatar || '',
|
||||
gender: r.gender || '',
|
||||
@@ -1622,8 +1633,8 @@ export class MysqlRepository {
|
||||
FROM orders
|
||||
WHERE (
|
||||
buyer_id = ?
|
||||
OR expert_id = ?
|
||||
OR claimed_by = ?
|
||||
OR expert_id IN (SELECT id FROM companions WHERE user_id = ?)
|
||||
OR companion_name IN (SELECT name FROM companions WHERE user_id = ?)
|
||||
)
|
||||
AND ${statusWhere}
|
||||
@@ -1642,8 +1653,8 @@ export class MysqlRepository {
|
||||
FROM orders
|
||||
WHERE (
|
||||
buyer_id = ?
|
||||
OR expert_id = ?
|
||||
OR claimed_by = ?
|
||||
OR expert_id IN (SELECT id FROM companions WHERE user_id = ?)
|
||||
OR companion_name IN (SELECT name FROM companions WHERE user_id = ?)
|
||||
)
|
||||
AND ${statusWhere}
|
||||
@@ -2359,7 +2370,11 @@ export class MysqlRepository {
|
||||
if (raw <= 0) return { companionId: 0, userId: 0 };
|
||||
const comp = await this.resolveCompanionRow(raw);
|
||||
if (comp) {
|
||||
return { companionId: Number(comp.id) || raw, userId: Number(comp.user_id) || raw };
|
||||
// 未绑定登录用户的种子档案不能把「档案 id」当「用户 id」去匹配 claimed_by,避免 id 撞号
|
||||
return {
|
||||
companionId: Number(comp.id) || raw,
|
||||
userId: comp.user_id != null ? Number(comp.user_id) : 0,
|
||||
};
|
||||
}
|
||||
return { companionId: raw, userId: raw };
|
||||
}
|
||||
@@ -2394,7 +2409,7 @@ export class MysqlRepository {
|
||||
}> {
|
||||
const scope = await this.resolveExpertOrderScope(expertId);
|
||||
const eId = scope.companionId || scope.userId;
|
||||
const eUid = scope.userId || scope.companionId;
|
||||
const eUid = scope.userId;
|
||||
const now = new Date();
|
||||
|
||||
// 1. 近 7 天走势
|
||||
|
||||
@@ -1276,7 +1276,8 @@ app.get('/api/companions', async (req: Request, res: Response) => {
|
||||
const publicList = companions
|
||||
.filter((c: any) => c.auditStatus === '已通过')
|
||||
.map((c: any) => {
|
||||
const { rejectReason, ...rest } = c;
|
||||
// 公开目录不返回内部审核备注、用户 id 与证件脱敏串
|
||||
const { rejectReason, userId, idCardMasked, ...rest } = c;
|
||||
return rest;
|
||||
});
|
||||
res.json({
|
||||
@@ -1360,7 +1361,7 @@ app.get('/api/companions/:id', async (req: Request, res: Response) => {
|
||||
if (isVerifiedAdminRequest(req)) {
|
||||
return res.json({ code: 20000, data: companion });
|
||||
}
|
||||
const { rejectReason, ...publicCompanion } = companion;
|
||||
const { rejectReason, userId, idCardMasked, ...publicCompanion } = companion;
|
||||
res.json({
|
||||
code: 20000,
|
||||
data: publicCompanion
|
||||
|
||||
在新工单中引用
屏蔽一个用户