Files
danyang-match/miniapp/pages/profile-detail/profile-detail.js
T
WorkBuddy c0298c27a5 fix(miniapp): 小程序界面全量中文化,消除英文/非中文枚举码上屏
- 新增 miniapp/constants/profileLabels.js 作为「枚举码→中文简体」唯一字典
  (婚姻/子女/学历/职业/吸烟饮酒/择偶子女/红娘状态),覆盖表单值与种子
  实际值两套并集,保证详情页无论后端返回哪种码都显示中文
- 修复「择偶条件-子女」下拉英文选项(any/accept/reject→不限/可接受/不接受),
  改用 value/label 数组 + range-key="label"
- 修复 recommend-card / user-card / profile-detail / profile-confirm 直接渲染
  后端枚举码(educationCode/maritalStatus/occupationCode…)的问题,改由
  observers 派生中文 label 上屏
- 清理残留英文文案:Required 条件→必须符合的条件、喜欢TA/向Ta→喜欢对方/向对方、
  U51 服务预约→服务预约、U64 公益服务记录→公益服务记录
- 修复 7 个英文窗口标题与 3 个英文占位页正文
- matchmaker 历史服务状态消除原始码兜底,统一走字典
- 加固 scripts/audit-miniapp-language.mjs(A 整行先剥离 mustache、B 排除
  bind:action 处理器、F 窗口标题、JPG/PNG 入白名单)
- 新增 ui-tests/miniapp-language-contracts.mjs 语种守卫(复用审计脚本,断言 0 命中),
  UI 契约门禁 MIN_GUARDS 21→22;已做负向注入验证判别力
2026-09-23 11:19:17 +08:00

251 lines
8.7 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 用户资料详情页 U21
*
* 重构要点(遵循 AGENTS.md 不变量):
* - 数据获取:recommendationService.getUserDetail(API_INVENTORY 行 43),不再直连 api 模块
* - 喜欢:matchStore.like(mutualMatch=true 时跳转会话页,AGENTS 第 3/6 条)
* - 拉黑:blockUser 跨 Store 编排(MatchStore + MessageStore 同步停用,AGENTS 第 4 条)
* - 举报:跳转 report 页,不在本页处理
* - 敏感数据:profile.birthday/身份证/手机号 仅服务端持有,前端不缓存(AGENTS 第 8 条)
* - Required 不可放宽:本页只读展示,无任何放宽择偶条件的入口(AGENTS 第 2 条)
*/
const { matchStore, appStore, blockUser } = require('../../stores');
const { recommendationService } = require('../../services');
const { to, DANGER_ACTION, RELATION_STATUS, BUSINESS_ERROR_CODE, profileLabelOf, yesNoLabel, futureDanyangLabel } = require('../../constants');
Page({
data: {
userId: '',
profile: null,
loading: true,
loadError: '',
/** @type {import('../../types').RelationStatus} */
relationStatus: RELATION_STATUS.NEUTRAL,
isLiked: false,
isMutual: false,
canChat: false,
actionLoading: false,
statusBarHeight: 20,
navBarHeight: 44,
showBlockDialog: false,
blockAction: DANGER_ACTION.BLOCK,
/** 派生的展示字段 */
displayName: '',
ageText: '',
metaLine: '',
badges: [],
basicInfo: [],
attitudeInfo: [],
},
onLoad(options) {
const userId = options.id || '';
if (!userId) {
this.setData({ loading: false, loadError: '用户不存在' });
return;
}
// 状态栏高度由 app.js 注入到全局,这里读一次
try {
const sysInfo = wx.getSystemInfoSync ? wx.getSystemInfoSync() : null;
if (sysInfo) {
this.setData({
statusBarHeight: sysInfo.statusBarHeight || 20,
navBarHeight: 44,
});
}
} catch (e) { /* 使用默认值 */ }
this.setData({ userId });
this.loadProfile();
},
async loadProfile() {
this.setData({ loading: true, loadError: '' });
try {
const user = await recommendationService.getUserDetail(this.data.userId);
this.setData({ loading: false, profile: user });
this._deriveDisplay(user);
} catch (err) {
this.setData({
loading: false,
loadError: (err && err.message) || '加载失败',
});
appStore.toastError(err, '加载用户资料失败');
}
},
/**
* 把后端 User 字段派生为展示用结构,避免 wxml 内做条件拼接
*/
_deriveDisplay(user) {
if (!user) return;
const relationStatus = user.relationStatus || RELATION_STATUS.NEUTRAL;
const isLiked = relationStatus === RELATION_STATUS.LIKED || relationStatus === RELATION_STATUS.MUTUAL_MATCH;
const isMutual = relationStatus === RELATION_STATUS.MUTUAL_MATCH;
// AGENTS 第 3 条:只有 Mutual 后才能聊天
const canChat = isMutual && relationStatus !== RELATION_STATUS.BLOCKED;
const badges = [];
if (user.verified || user.isRealNameVerified) {
badges.push({ kind: 'success', label: '已实名' });
}
if (user.reviewed || user.profileReviewStatus === 'approved') {
badges.push({ kind: 'info', label: '资料已审核' });
}
const metaParts = [];
if (user.heightCm) metaParts.push(`${user.heightCm}cm`);
if (user.regionName) metaParts.push(user.regionName);
const eduLabel = profileLabelOf('educationCode', user.educationCode);
if (eduLabel) metaParts.push(eduLabel);
const occLabel = profileLabelOf('occupationCode', user.occupationCode);
if (occLabel) metaParts.push(occLabel);
const basicInfo = [];
const maritalLabel = profileLabelOf('maritalStatus', user.maritalStatus);
if (maritalLabel) basicInfo.push({ label: '婚姻状况', value: maritalLabel });
if (user.childrenStatus || user.hasChildren !== undefined) {
basicInfo.push({ label: '子女情况', value: user.childrenStatus ? profileLabelOf('childrenStatus', user.childrenStatus) : (user.hasChildren ? '有孩' : '无孩') });
}
if (eduLabel) basicInfo.push({ label: '学历', value: eduLabel });
if (occLabel) basicInfo.push({ label: '职业', value: occLabel });
const smokingLabel = profileLabelOf('smokingStatus', user.smokingStatus);
if (smokingLabel) basicInfo.push({ label: '吸烟', value: smokingLabel });
const drinkingLabel = profileLabelOf('drinkingStatus', user.drinkingStatus);
if (drinkingLabel) basicInfo.push({ label: '饮酒', value: drinkingLabel });
const longTermLabel = yesNoLabel(user.longTermDanyang);
if (longTermLabel) basicInfo.push({ label: '定居意愿', value: longTermLabel, full: true });
const futureLabel = futureDanyangLabel(user.futureDanyang);
if (futureLabel) basicInfo.push({ label: '未来规划', value: futureLabel, full: true });
const attitudeInfo = [];
// relationshipGoal / marriagePlan 由后端返回中文文案(结婚 / 恋爱 等),未知码原样透传
if (user.relationshipGoal) attitudeInfo.push({ label: '寻找', value: user.relationshipGoal, full: true });
if (user.marriagePlan) attitudeInfo.push({ label: '结婚计划', value: user.marriagePlan, full: true });
this.setData({
relationStatus,
isLiked,
isMutual,
canChat,
displayName: user.displayName || '用户',
ageText: user.age ? `${user.age}岁` : '',
metaLine: metaParts.join(' · '),
badges,
basicInfo,
attitudeInfo,
});
},
onBack() {
wx.navigateBack();
},
onRetry() {
this.loadProfile();
},
/**
* 喜欢(AGENTS 第 3/5/6 条:用户本人操作,mutualMatch 时跳聊天)
*/
async onLike() {
const { userId, actionLoading, isLiked } = this.data;
if (!userId || actionLoading || isLiked) return;
this.setData({ actionLoading: true });
try {
const result = await matchStore.like(userId);
// 本地派生状态更新
if (result && result.mutualMatch) {
this.setData({
isLiked: true,
isMutual: true,
canChat: true,
relationStatus: RELATION_STATUS.MUTUAL_MATCH,
actionLoading: false,
});
appStore.toastSuccess('双向喜欢!可以开始聊天了');
if (result.matchedConversationId) {
wx.navigateTo({ url: to.chat(result.matchedConversationId) });
}
} else {
this.setData({
isLiked: true,
relationStatus: RELATION_STATUS.LIKED,
actionLoading: false,
});
appStore.toastSuccess('已表达喜欢');
}
} catch (err) {
this.setData({ actionLoading: false });
if (err && err.code === BUSINESS_ERROR_CODE.DUPLICATE_ACTION) {
appStore.toast('已表达过喜欢');
} else {
appStore.toastError(err, '操作失败');
}
}
},
/**
* 开始聊天(仅 Mutual 后可见,AGENTS 第 3 条)
*/
onGoChat() {
const { userId, canChat, profile } = this.data;
if (!canChat) {
appStore.toast('双方互相喜欢后才能开始聊天');
return;
}
// matchedConversationId 优先来自最近 like 结果;无则用 userId 占位由 chat 页拉取
const conversationId = (profile && profile.matchedConversationId) || userId;
wx.navigateTo({ url: to.chat(conversationId) });
},
/**
* 举报:跳转 report 页,不在本页处理(AGENTS 第 7 条:举报内容不在此暴露)
*/
onReport() {
const { userId } = this.data;
if (!userId) return;
wx.navigateTo({ url: to.report(userId) });
},
/**
* 拉黑入口:弹出 DangerActionDialog 二次确认(AGENTS 第 4 条)
*/
onBlock() {
if (!this.data.userId) return;
this.setData({ showBlockDialog: true });
},
onConfirmBlock() {
this.setData({ showBlockDialog: false });
this._doBlock();
},
onCancelBlock() {
this.setData({ showBlockDialog: false });
},
/**
* 执行拉黑:调用跨 Store 编排 blockUser
* - MatchStore:从推荐/Mutual 列表移除
* - MessageStore:会话立即标记不可发消息
*/
async _doBlock() {
const { userId, actionLoading } = this.data;
if (!userId || actionLoading) return;
this.setData({ actionLoading: true });
try {
appStore.showLoading('处理中…');
await blockUser(userId, '用户主动拉黑');
appStore.hideLoading();
appStore.toastSuccess('已拉黑');
// 拉黑后离开当前页(AGENTS 第 4 条:Block 即时禁止互动)
setTimeout(() => wx.navigateBack(), 800);
} catch (err) {
appStore.hideLoading();
this.setData({ actionLoading: false });
appStore.toastError(err, '拉黑失败');
}
},
});