Files
danyang-match/miniapp/pages/profile-confirm/profile-confirm.js
T

245 lines
8.2 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 } = 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' },
};
// 婚姻状况字典(前端展示用,权威值仍由服务端 Profile.maritalStatus 给出)
const MARITAL_LABEL = {
single: '未婚',
divorced: '离异',
widowed: '丧偶',
};
// 学历字典
const EDUCATION_LABEL = {
high_school: '高中/中专',
college: '大专',
bachelor: '本科',
master: '硕士',
doctor: '博士',
};
// 子女情况字典
const CHILDREN_LABEL = {
none: '无子女',
has: '有子女',
};
/**
* 计算资料完整度(客户端派生展示)
*
* 注意:仅用于 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: MARITAL_LABEL[profile && profile.maritalStatus] || (profile && profile.maritalStatus) || '-',
educationLabel: EDUCATION_LABEL[profile && profile.educationCode] || (profile && profile.educationCode) || '-',
childrenLabel: CHILDREN_LABEL[profile && (profile.hasChildren ? 'has' : 'none')] || '-',
});
} 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 });
},
});