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;已做负向注入验证判别力
This commit is contained in:
WorkBuddy
2026-09-23 11:19:17 +08:00
parent 1fc34f1bcd
commit c0298c27a5
31 changed files with 585 additions and 71 deletions
+1 -1
View File
@@ -1 +1 @@
{"navigationBarTitleText":"about","usingComponents":{}}
{"navigationBarTitleText":"关于我们","usingComponents":{}}
@@ -20,7 +20,7 @@
<view class="status-section" wx:else>
<view class="pending-icon">💬</view>
<text class="pending-title">向Ta表达兴趣</text>
<text class="pending-title">向对方表达兴趣</text>
<text class="pending-hint">双方确认后才能开启线上沟通</text>
<button class="confirm-btn" bindtap="onConfirm">确认开启沟通</button>
</view>
@@ -1 +1 @@
{"navigationBarTitleText":"activity-register","usingComponents":{}}
{"navigationBarTitleText":"活动报名","usingComponents":{}}
@@ -1 +1 @@
<view class="page"><text>页面: activity-register</text></view>
<view class="page"><text>活动报名(建设中)</text></view>
+1 -1
View File
@@ -1 +1 @@
{"navigationBarTitleText":"bind-phone","usingComponents":{}}
{"navigationBarTitleText":"绑定手机","usingComponents":{}}
+1 -1
View File
@@ -1 +1 @@
<view class="page"><text>bind-phone</text></view>
<view class="page"><text>手机号绑定(建设中)</text></view>
+1 -1
View File
@@ -242,7 +242,7 @@ Page({
const { otherUserId } = this.data;
if (!otherUserId) { appStore.toast('暂无更多信息'); return; }
wx.showActionSheet({
itemList: ['查看 Ta 的资料', '举报 Ta', '拉黑 Ta'],
itemList: ['查看对方的资料', '举报对方', '拉黑对方'],
success: (res) => {
if (res.tapIndex === 0) wx.navigateTo({ url: to.detail(otherUserId) });
else if (res.tapIndex === 1) wx.navigateTo({ url: to.report(otherUserId) });
+1 -1
View File
@@ -107,7 +107,7 @@
<view wx:elif="{{!loading}}" class="discover-empty">
<image src="/assets/brand/logo-mark.png" mode="aspectFit" />
<text class="empty-title">暂无符合条件的人选</text>
<text class="empty-desc">Required 条件不会自动放宽,可以调整筛选后再看看</text>
<text class="empty-desc">必须符合的条件不会自动放宽,可以调整筛选后再看看</text>
<view class="empty-action" bindtap="onResetFilter">清空筛选</view>
</view>
</view>
+1 -1
View File
@@ -85,7 +85,7 @@
catchtap="onLikeBack"
data-user-id="{{item.userId}}"
disabled="{{actionLoadingUserId === item.userId}}"
>喜欢TA</button>
>喜欢对方</button>
</view>
</ui-user-card>
</view>
+1 -1
View File
@@ -1 +1 @@
{"navigationBarTitleText":"help-center","usingComponents":{}}
{"navigationBarTitleText":"帮助中心","usingComponents":{}}
+1 -1
View File
@@ -72,7 +72,7 @@
</view>
<view wx:else class="recommend-empty">
<image class="empty-logo" src="/assets/brand/logo-mark.png" mode="aspectFit" />
<view class="empty-copy"><text class="empty-title">暂时没有完全符合双方条件的人</text><text class="empty-desc">我们坚持 Required 条件,不会为了数量降低筛选标准</text></view>
<view class="empty-copy"><text class="empty-title">暂时没有完全符合双方条件的人</text><text class="empty-desc">我们坚持必须符合的条件,不会为了数量降低筛选标准</text></view>
<view class="empty-actions"><view class="empty-btn secondary" bindtap="onEmptyAction" data-event="preference">调整条件</view><view class="empty-btn primary" bindtap="onEmptyAction" data-event="matchmaker">公益红娘</view></view>
</view>
</view>
+1 -1
View File
@@ -1 +1 @@
{"navigationBarTitleText":"login","usingComponents":{}}
{"navigationBarTitleText":"登录","usingComponents":{}}
+11 -4
View File
@@ -8,7 +8,7 @@
* - 推荐:红娘推荐列表跳 fate-center 页(AGENTS 第 5 条:红娘不可替用户操作)
*/
import { matchmakerStore, appStore } from '../../stores';
import { ROUTE, to, buildPath, MATCHMAKER_SERVICE_STATUS } from '../../constants';
import { ROUTE, to, buildPath, MATCHMAKER_SERVICE_STATUS, MATCHMAKER_SERVICE_STATUS_LABEL } from '../../constants';
Page({
data: {
@@ -59,10 +59,17 @@ Page({
activeService.status === MATCHMAKER_SERVICE_STATUS.IN_SERVICE ||
activeService.status === MATCHMAKER_SERVICE_STATUS.WAITING_USER
);
const historyServices = activeService
const withStatusText = (s: any) => ({
...s,
statusText: MATCHMAKER_SERVICE_STATUS_LABEL[s.status] || s.statusText || s.status || '',
})
const historyServices = (activeService
? myServices.filter((s: any) => s.serviceId !== activeService.serviceId)
: myServices;
this.setData({ loading: false, firstLoaded: true, activeService, historyServices, showRecommendations });
: myServices).map(withStatusText)
const activeServiceWithText = activeService
? { ...activeService, statusText: MATCHMAKER_SERVICE_STATUS_LABEL[activeService.status] || activeService.statusText || activeService.status || '' }
: null
this.setData({ loading: false, firstLoaded: true, activeService: activeServiceWithText, historyServices, showRecommendations });
} catch (err: any) {
this.setData({ loading: false, firstLoaded: true });
appStore.toastError(err, '加载红娘服务失败');
@@ -1 +1 @@
{"navigationBarTitleText":"my-activities","usingComponents":{}}
{"navigationBarTitleText":"我的活动","usingComponents":{}}
@@ -1 +1 @@
<view class="page"><text>页面: my-activities</text></view>
<view class="page"><text>我的活动(建设中)</text></view>
@@ -12,7 +12,7 @@
*/
const { userStore, appStore } = require('../../stores');
const { profileService } = require('../../services');
const { ROUTE, STORAGE_KEYS, PROFILE_REVIEW } = require('../../constants');
const { ROUTE, STORAGE_KEYS, PROFILE_REVIEW, profileLabelOf } = require('../../constants');
// 审核状态 → StatusBadge kind 映射
const REVIEW_BADGE = {
@@ -23,28 +23,6 @@ const REVIEW_BADGE = {
[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: '有子女',
};
/**
* 计算资料完整度(客户端派生展示)
*
@@ -165,9 +143,9 @@ Page({
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')] || '-',
maritalLabel: profileLabelOf('maritalStatus', profile && profile.maritalStatus) || '-',
educationLabel: profileLabelOf('educationCode', profile && profile.educationCode) || '-',
childrenLabel: profile && (profile.hasChildren ? '有子女' : '无子女'),
});
} catch (err) {
appStore.toastError(err, '加载失败');
+19 -11
View File
@@ -11,7 +11,7 @@
*/
const { matchStore, appStore, blockUser } = require('../../stores');
const { recommendationService } = require('../../services');
const { to, DANGER_ACTION, RELATION_STATUS, BUSINESS_ERROR_CODE } = require('../../constants');
const { to, DANGER_ACTION, RELATION_STATUS, BUSINESS_ERROR_CODE, profileLabelOf, yesNoLabel, futureDanyangLabel } = require('../../constants');
Page({
data: {
@@ -96,22 +96,30 @@ Page({
const metaParts = [];
if (user.heightCm) metaParts.push(`${user.heightCm}cm`);
if (user.regionName) metaParts.push(user.regionName);
if (user.educationCode) metaParts.push(user.educationCode);
if (user.occupationCode) metaParts.push(user.occupationCode);
const eduLabel = profileLabelOf('educationCode', user.educationCode);
if (eduLabel) metaParts.push(eduLabel);
const occLabel = profileLabelOf('occupationCode', user.occupationCode);
if (occLabel) metaParts.push(occLabel);
const basicInfo = [];
if (user.maritalStatus) basicInfo.push({ label: '婚姻状况', value: user.maritalStatus });
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 || (user.hasChildren ? '有孩' : '无孩') });
basicInfo.push({ label: '子女情况', value: user.childrenStatus ? profileLabelOf('childrenStatus', user.childrenStatus) : (user.hasChildren ? '有孩' : '无孩') });
}
if (user.educationCode) basicInfo.push({ label: '学历', value: user.educationCode });
if (user.occupationCode) basicInfo.push({ label: '职业', value: user.occupationCode });
if (user.smokingStatus) basicInfo.push({ label: '吸烟', value: user.smokingStatus });
if (user.drinkingStatus) basicInfo.push({ label: '饮酒', value: user.drinkingStatus });
if (user.longTermDanyang) basicInfo.push({ label: '定居意愿', value: user.longTermDanyang, full: true });
if (user.futureDanyang) basicInfo.push({ label: '未来规划', value: user.futureDanyang, full: true });
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 });
@@ -1 +1 @@
<view class="service-page"><view class="hero"><text class="eyebrow">U51 服务预约</text><text class="hero-title">预约到站服务</text><text class="hero-desc">选择合适的时间和服务项目,提交后等待工作人员确认。</text></view><view class="content"><view class="card"><view class="field" wx:if="{{serviceLocationId}}"><text class="label">服务站点</text><view class="select-box is-readonly">{{serviceLocationName || '已选服务站点'}}</view></view><view class="field"><text class="label">服务项目</text><picker range="{{services}}" bindchange="chooseService"><view class="select-box">{{service}}<text class="arrow" style="margin-left:auto">›</text></view></picker></view><view class="field"><text class="label">预约日期</text><picker mode="date" bindchange="chooseDate"><view class="select-box">{{date || '请选择日期'}}</view></picker></view><view class="field"><text class="label">希望沟通的事项(选填)</text><textarea class="textarea" value="{{note}}" bindinput="onNoteInput" placeholder="请简要说明,避免填写身份证号等敏感信息" maxlength="200"/></view><view class="notice">提交预约不代表已确认。工作人员确认后,预约状态会更新为“已确认”。</view></view></view><view class="fixed-action"><view class="primary-btn {{submitting ? 'is-disabled' : ''}}" bindtap="submit">{{submitting ? '提交中…' : '提交预约'}}</view></view></view>
<view class="service-page"><view class="hero"><text class="eyebrow">服务预约</text><text class="hero-title">预约到站服务</text><text class="hero-desc">选择合适的时间和服务项目,提交后等待工作人员确认。</text></view><view class="content"><view class="card"><view class="field" wx:if="{{serviceLocationId}}"><text class="label">服务站点</text><view class="select-box is-readonly">{{serviceLocationName || '已选服务站点'}}</view></view><view class="field"><text class="label">服务项目</text><picker range="{{services}}" bindchange="chooseService"><view class="select-box">{{service}}<text class="arrow" style="margin-left:auto">›</text></view></picker></view><view class="field"><text class="label">预约日期</text><picker mode="date" bindchange="chooseDate"><view class="select-box">{{date || '请选择日期'}}</view></picker></view><view class="field"><text class="label">希望沟通的事项(选填)</text><textarea class="textarea" value="{{note}}" bindinput="onNoteInput" placeholder="请简要说明,避免填写身份证号等敏感信息" maxlength="200"/></view><view class="notice">提交预约不代表已确认。工作人员确认后,预约状态会更新为“已确认”。</view></view></view><view class="fixed-action"><view class="primary-btn {{submitting ? 'is-disabled' : ''}}" bindtap="submit">{{submitting ? '提交中…' : '提交预约'}}</view></view></view>
@@ -1 +1 @@
<view class="service-page"><view class="hero"><text class="eyebrow">U64 公益服务记录</text><text class="hero-title">每一次服务,都清楚可查</text><text class="hero-desc">这里只展示与你有关的公益服务事实,不展示咨询正文。</text></view><view class="content"><view wx:if="{{loading}}" class="card empty-panel"><view class="empty-symbol">…</view><text class="empty-title">正在加载</text></view><view wx:elif="{{error}}" class="card empty-panel"><view class="empty-symbol">!</view><text class="empty-title">{{error}}</text></view><view wx:elif="{{records.length}}" class="block"><view class="card"><view wx:for="{{records}}" wx:key="key" class="record-row"><view class="feature-icon">{{item.icon}}</view><view class="feature-copy"><text class="feature-title">{{item.title}}</text><text class="feature-desc">{{item.date}}</text></view><text class="tag">{{item.status}}</text></view></view></view><view wx:else class="card empty-panel"><view class="empty-symbol">记</view><text class="empty-title">还没有公益服务记录</text><text class="meta">预约到站服务或申请公益咨询后,会在这里留下时间线。</text></view></view><view class="notice">咨询与公益红娘记录彼此分离;工作人员只能查看职责范围内的信息。</view></view>
<view class="service-page"><view class="hero"><text class="eyebrow">公益服务记录</text><text class="hero-title">每一次服务,都清楚可查</text><text class="hero-desc">这里只展示与你有关的公益服务事实,不展示咨询正文。</text></view><view class="content"><view wx:if="{{loading}}" class="card empty-panel"><view class="empty-symbol">…</view><text class="empty-title">正在加载</text></view><view wx:elif="{{error}}" class="card empty-panel"><view class="empty-symbol">!</view><text class="empty-title">{{error}}</text></view><view wx:elif="{{records.length}}" class="block"><view class="card"><view wx:for="{{records}}" wx:key="key" class="record-row"><view class="feature-icon">{{item.icon}}</view><view class="feature-copy"><text class="feature-title">{{item.title}}</text><text class="feature-desc">{{item.date}}</text></view><text class="tag">{{item.status}}</text></view></view></view><view wx:else class="card empty-panel"><view class="empty-symbol">记</view><text class="empty-title">还没有公益服务记录</text><text class="meta">预约到站服务或申请公益咨询后,会在这里留下时间线。</text></view></view><view class="notice">咨询与公益红娘记录彼此分离;工作人员只能查看职责范围内的信息。</view></view>
+1 -1
View File
@@ -1 +1 @@
{"navigationBarTitleText":"settings","usingComponents":{}}
{"navigationBarTitleText":"设置","usingComponents":{}}