Files
danyang-match/miniapp/pages/profile-confirm/profile-confirm.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

223 lines
7.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.
/**
* 资料确认页 U15(Phase 04 P04-T28)
*
* 重构要点(遵循 AGENTS.md 不变量):
* - 数据:userStore.fetchProfile / fetchPreferences / fetchReviewStatus(第 9 条)
* - 提交:userStore.submitReview(POST /me/profile/submit-review + Idempotency-Key)
* - 草稿:提交成功后清除 ONBOARDING_DRAFT,避免回填污染(第 9 条:DB 是 Source of Truth)
* - 审核状态展示:not_submitted / pending / approved / need_edit / rejected
* - 资料完整度:客户端派生展示,最终由服务端校验,前端不放宽 Required(第 2 条)
* - 路由:提交成功后 redirectTo 至 REVIEW.PENDING(Phase 05 已实现);
* 审核中页 onShow 守卫会在状态变化时引导至合适页面。
*/
const { userStore, appStore } = require('../../stores');
const { profileService } = require('../../services');
const { ROUTE, STORAGE_KEYS, PROFILE_REVIEW, profileLabelOf } = require('../../constants');
// 审核状态 → StatusBadge kind 映射
const REVIEW_BADGE = {
[PROFILE_REVIEW.NOT_SUBMITTED]: { label: '未提交', kind: 'disabled' },
[PROFILE_REVIEW.PENDING]: { label: '审核中', kind: 'warning' },
[PROFILE_REVIEW.APPROVED]: { label: '已通过', kind: 'success' },
[PROFILE_REVIEW.NEED_EDIT]: { label: '需修改', kind: 'warning' },
[PROFILE_REVIEW.REJECTED]: { label: '未通过', kind: 'danger' },
};
/**
* 计算资料完整度(客户端派生展示)
*
* 注意:仅用于 UI 进度条展示,最终是否可提交由服务端校验决定
* (AGENTS 第 9 条:PostgreSQL 是 Source of Truth)。
* @param {object} profile
* @param {object} preferences
* @param {number} photoCount
* @returns {number} 0-100
*/
function calcCompletion(profile, preferences, photoCount) {
if (!profile) return 0;
let filled = 0;
let total = 0;
const checks = [
!!profile.regionName,
!!profile.heightCm,
!!profile.maritalStatus,
profile.hasChildren !== undefined && profile.hasChildren !== null,
!!profile.educationCode,
!!profile.occupationCode,
!!profile.occupationCategory,
!!profile.workRegion,
profile.smokingStatus !== undefined,
profile.drinkingStatus !== undefined,
profile.longTermDanyang !== undefined,
profile.futureDanyang !== undefined,
!!profile.relationshipGoal,
!!profile.marriagePlan,
!!profile.activityWillingness,
!!profile.matchmakerWillingness,
!!profile.selfIntro && profile.selfIntro.length > 0,
photoCount > 0,
!!profile.interests && profile.interests.length > 0,
!!preferences && !!preferences.ageMin && !!preferences.ageMax,
!!preferences && !!preferences.region && preferences.region.length > 0,
!!preferences && !!preferences.maritalStatusAccepted && preferences.maritalStatusAccepted.length > 0,
];
total = checks.length;
filled = checks.filter(Boolean).length;
return Math.round((filled / total) * 100);
}
Page({
data: {
statusBarHeight: 20,
navBarHeight: 44,
loading: true,
submitting: false,
profile: null,
preferences: null,
photoCount: 0,
reviewStatus: PROFILE_REVIEW.NOT_SUBMITTED,
reviewComment: '',
resubmitAllowed: true,
completion: 0,
reviewBadge: REVIEW_BADGE[PROFILE_REVIEW.NOT_SUBMITTED],
// 派生展示字段
maritalLabel: '',
educationLabel: '',
childrenLabel: '',
},
onLoad() {
this._initSystemInfo();
this.loadAll();
},
_initSystemInfo() {
try {
const sysInfo = wx.getSystemInfoSync();
this.setData({
statusBarHeight: sysInfo.statusBarHeight || 20,
navBarHeight: 44,
});
} catch (e) { /* 使用默认值 */ }
},
// ============ 数据加载 ============
async loadAll() {
this.setData({ loading: true });
try {
// 并行拉取资料/择偶条件/审核状态,照片列表用于计数
const [profile, preferences, review, photosRes] = await Promise.all([
userStore.fetchProfile().catch(() => null),
userStore.fetchPreferences().catch(() => null),
userStore.fetchReviewStatus().catch(() => null),
profileService.getPhotos().catch(() => ({ photos: [] })),
]);
const photoCount = (photosRes && photosRes.photos && photosRes.photos.length) || 0;
// REST §39:服务端返回 { status, items[] }
const reviewStatus = (review && review.status) || PROFILE_REVIEW.NOT_SUBMITTED;
// 派生驳回原因摘要:取所有 need_edit/rejected 项的 reasonText 拼接
const rejectedItems = (review && review.items || []).filter(
(i) => i.status === PROFILE_REVIEW.NEED_EDIT || i.status === PROFILE_REVIEW.REJECTED
);
const reviewComment = rejectedItems
.map((i) => i.reasonText)
.filter(Boolean)
.join(';');
// 仅 NEED_EDIT / REJECTED 允许重新提交(修改后再次提交)
const resubmitAllowed =
reviewStatus === PROFILE_REVIEW.NOT_SUBMITTED ||
reviewStatus === PROFILE_REVIEW.NEED_EDIT ||
reviewStatus === PROFILE_REVIEW.REJECTED;
const completion = calcCompletion(profile, preferences, photoCount);
this.setData({
profile,
preferences,
photoCount,
reviewStatus,
reviewComment,
resubmitAllowed,
completion,
reviewBadge: REVIEW_BADGE[reviewStatus] || REVIEW_BADGE[PROFILE_REVIEW.NOT_SUBMITTED],
maritalLabel: profileLabelOf('maritalStatus', profile && profile.maritalStatus) || '-',
educationLabel: profileLabelOf('educationCode', profile && profile.educationCode) || '-',
childrenLabel: profile && (profile.hasChildren ? '有子女' : '无子女'),
});
} catch (err) {
appStore.toastError(err, '加载失败');
} finally {
this.setData({ loading: false });
}
},
onRefresh() {
this.loadAll();
},
// ============ 提交审核 ============
/**
* 提交资料审核
*
* AGENTS 第 2 条:Required 不可自动放宽——本页只读展示,不修改任何择偶条件,
* 不存在前端放宽约束的路径。
* AGENTS 第 9 条:DB 是 Source of Truth——提交成功立即清除本地草稿,
* 后续以服务端审核状态为准,本地不再回填。
*/
async onSubmit() {
if (this.data.submitting) return;
if (!this.data.resubmitAllowed) {
return appStore.toast('当前状态不可提交,请稍后或修改后再试');
}
const ok = await appStore.confirm({
title: '确认提交审核?',
content: '提交后资料将进入人工审核,审核期间您可继续修改,但需重新提交审核。',
});
if (!ok) return;
this.setData({ submitting: true });
try {
await userStore.submitReview();
// 提交成功:清除新手草稿(避免再次进入回填旧数据)
this._clearDraft();
appStore.toastSuccess('已提交审核');
// 跳转审核中页(Phase 05 已实现)
setTimeout(() => {
wx.redirectTo({ url: ROUTE.REVIEW.PENDING });
}, 800);
} catch (err) {
appStore.toastError(err, '提交失败');
} finally {
this.setData({ submitting: false });
}
},
// ============ 草稿清理 ============
_clearDraft() {
try {
wx.removeStorageSync(STORAGE_KEYS.ONBOARDING_DRAFT);
wx.removeStorageSync(STORAGE_KEYS.ONBOARDING_CURRENT_STEP);
} catch (e) { /* ignore */ }
},
// ============ 导航 ============
onBack() {
wx.navigateBack();
},
// 跳回指定资料页继续修改(审核驳回/需修改场景)
onEditBasic() {
wx.navigateTo({ url: ROUTE.ONBOARDING.PROFILE_BASIC });
},
onEditIntro() {
wx.navigateTo({ url: ROUTE.ONBOARDING.PROFILE_INTRO });
},
onEditPreference() {
wx.navigateTo({ url: ROUTE.ONBOARDING.PROFILE_PREFERENCE });
},
});