2026-09-09 22:05:00 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 用户资料详情页 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');
|
2026-09-23 11:19:17 +08:00
|
|
|
|
const { to, DANGER_ACTION, RELATION_STATUS, BUSINESS_ERROR_CODE, profileLabelOf, yesNoLabel, futureDanyangLabel } = require('../../constants');
|
2026-09-09 22:05:00 +08:00
|
|
|
|
|
|
|
|
|
|
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);
|
2026-09-23 11:19:17 +08:00
|
|
|
|
const eduLabel = profileLabelOf('educationCode', user.educationCode);
|
|
|
|
|
|
if (eduLabel) metaParts.push(eduLabel);
|
|
|
|
|
|
const occLabel = profileLabelOf('occupationCode', user.occupationCode);
|
|
|
|
|
|
if (occLabel) metaParts.push(occLabel);
|
2026-09-09 22:05:00 +08:00
|
|
|
|
|
|
|
|
|
|
const basicInfo = [];
|
2026-09-23 11:19:17 +08:00
|
|
|
|
const maritalLabel = profileLabelOf('maritalStatus', user.maritalStatus);
|
|
|
|
|
|
if (maritalLabel) basicInfo.push({ label: '婚姻状况', value: maritalLabel });
|
2026-09-09 22:05:00 +08:00
|
|
|
|
if (user.childrenStatus || user.hasChildren !== undefined) {
|
2026-09-23 11:19:17 +08:00
|
|
|
|
basicInfo.push({ label: '子女情况', value: user.childrenStatus ? profileLabelOf('childrenStatus', user.childrenStatus) : (user.hasChildren ? '有孩' : '无孩') });
|
2026-09-09 22:05:00 +08:00
|
|
|
|
}
|
2026-09-23 11:19:17 +08:00
|
|
|
|
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 });
|
2026-09-09 22:05:00 +08:00
|
|
|
|
|
|
|
|
|
|
const attitudeInfo = [];
|
2026-09-23 11:19:17 +08:00
|
|
|
|
// relationshipGoal / marriagePlan 由后端返回中文文案(结婚 / 恋爱 等),未知码原样透传
|
2026-09-09 22:05:00 +08:00
|
|
|
|
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, '拉黑失败');
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
});
|