chore: establish V1.1 recovery baseline

This commit is contained in:
2026-09-09 22:05:00 +08:00
commit b46b48eb33
947 changed files with 155471 additions and 0 deletions
@@ -0,0 +1,228 @@
/**
* 职业与生活页 U07(Phase 04 P04-T22)
*
* 重构要点(遵循 AGENTS.md 不变量):
* - 数据:profileService.getProfile + userStore.updateProfile(第 9 条)
* - 草稿恢复:onLoad 读 ONBOARDING_DRAFT.lifestyle,保存前写回(验收条件"Draft退出后可恢复")
* - 下一步:跳 profile-photos
*/
const { userStore, appStore } = require('../../stores');
const { profileService } = require('../../services');
const { ROUTE, STORAGE_KEYS } = require('../../constants');
const OCCUPATION_CATEGORY_OPTIONS = [
'机关事业单位', '教育', '医疗', '企业职员',
'技术人员', '个体经营', '自由职业', '服务行业',
'农业', '其他',
];
const WORK_REGION_OPTIONS = ['丹阳', '镇江市区', '镇江其他辖区', '南京', '不限'];
const SMOKING_OPTIONS = [
{ value: 'never', label: '不吸烟' },
{ value: 'occasional', label: '偶尔' },
{ value: 'often', label: '经常' },
];
const DRINKING_OPTIONS = [
{ value: 'never', label: '不饮酒' },
{ value: 'occasional', label: '偶尔' },
{ value: 'often', label: '经常' },
];
const LONG_TERM_OPTIONS = [
{ value: 'yes', label: '是' },
{ value: 'no', label: '否' },
];
const FUTURE_DANYANG_OPTIONS = [
{ value: 'willing', label: '愿意' },
{ value: 'uncertain', label: '不确定' },
{ value: 'unwilling', label: '不愿意' },
];
Page({
data: {
statusBarHeight: 20,
navBarHeight: 44,
loading: false,
saving: false,
occupationCategory: '',
workRegion: '',
smokingStatus: '',
drinkingStatus: '',
longTermDanyang: '',
futureDanyang: '',
// 选项
occupationCategoryOptions: OCCUPATION_CATEGORY_OPTIONS,
workRegionOptions: WORK_REGION_OPTIONS,
smokingOptions: SMOKING_OPTIONS,
drinkingOptions: DRINKING_OPTIONS,
longTermOptions: LONG_TERM_OPTIONS,
futureDanyangOptions: FUTURE_DANYANG_OPTIONS,
occupationCategoryIndex: -1,
workRegionIndex: -1,
smokingIndex: -1,
drinkingIndex: -1,
longTermIndex: -1,
futureDanyangIndex: -1,
},
onLoad() {
this._initSystemInfo();
this._applyDraft();
this.loadProfile();
},
_initSystemInfo() {
try {
const sysInfo = wx.getSystemInfoSync();
this.setData({
statusBarHeight: sysInfo.statusBarHeight || 20,
navBarHeight: 44,
});
} catch (e) { /* 使用默认值 */ }
},
async loadProfile() {
this.setData({ loading: true });
try {
const profile = await profileService.getProfile();
this._mergeProfile(profile);
} catch (err) { /* 首次填写无资料 */ } finally {
this.setData({ loading: false });
}
},
_mergeProfile(profile) {
if (!profile) return;
const d = this._readDraft();
const occupationCategory = d.occupationCategory || profile.occupationCategory || '';
const workRegion = d.workRegion || profile.workRegion || '';
const smokingStatus = d.smokingStatus || profile.smokingStatus || '';
const drinkingStatus = d.drinkingStatus || profile.drinkingStatus || '';
const longTermDanyang = d.longTermDanyang || profile.longTermDanyang || '';
const futureDanyang = d.futureDanyang || profile.futureDanyang || '';
this.setData({
occupationCategory,
workRegion,
smokingStatus,
drinkingStatus,
longTermDanyang,
futureDanyang,
occupationCategoryIndex: OCCUPATION_CATEGORY_OPTIONS.indexOf(occupationCategory),
workRegionIndex: WORK_REGION_OPTIONS.indexOf(workRegion),
smokingIndex: SMOKING_OPTIONS.findIndex((o) => o.value === smokingStatus),
drinkingIndex: DRINKING_OPTIONS.findIndex((o) => o.value === drinkingStatus),
longTermIndex: LONG_TERM_OPTIONS.findIndex((o) => o.value === longTermDanyang),
futureDanyangIndex: FUTURE_DANYANG_OPTIONS.findIndex((o) => o.value === futureDanyang),
});
},
// ============ 草稿 ============
_readDraft() {
try {
const draft = wx.getStorageSync(STORAGE_KEYS.ONBOARDING_DRAFT) || {};
return draft.lifestyle || {};
} catch (e) {
return {};
}
},
_applyDraft() {
const d = this._readDraft();
if (!d || Object.keys(d).length === 0) return;
this.setData({
occupationCategory: d.occupationCategory || '',
workRegion: d.workRegion || '',
smokingStatus: d.smokingStatus || '',
drinkingStatus: d.drinkingStatus || '',
longTermDanyang: d.longTermDanyang || '',
futureDanyang: d.futureDanyang || '',
occupationCategoryIndex: OCCUPATION_CATEGORY_OPTIONS.indexOf(d.occupationCategory || ''),
workRegionIndex: WORK_REGION_OPTIONS.indexOf(d.workRegion || ''),
smokingIndex: SMOKING_OPTIONS.findIndex((o) => o.value === (d.smokingStatus || '')),
drinkingIndex: DRINKING_OPTIONS.findIndex((o) => o.value === (d.drinkingStatus || '')),
longTermIndex: LONG_TERM_OPTIONS.findIndex((o) => o.value === (d.longTermDanyang || '')),
futureDanyangIndex: FUTURE_DANYANG_OPTIONS.findIndex((o) => o.value === (d.futureDanyang || '')),
});
},
_writeDraft() {
try {
const draft = wx.getStorageSync(STORAGE_KEYS.ONBOARDING_DRAFT) || {};
draft.lifestyle = {
occupationCategory: this.data.occupationCategory,
workRegion: this.data.workRegion,
smokingStatus: this.data.smokingStatus,
drinkingStatus: this.data.drinkingStatus,
longTermDanyang: this.data.longTermDanyang,
futureDanyang: this.data.futureDanyang,
};
draft.currentStep = 'lifestyle';
wx.setStorageSync(STORAGE_KEYS.ONBOARDING_DRAFT, draft);
} catch (e) { /* ignore */ }
},
// ============ 表单交互 ============
onOccupationCategoryChange(e) {
const idx = Number(e.detail.value);
this.setData({ occupationCategoryIndex: idx, occupationCategory: OCCUPATION_CATEGORY_OPTIONS[idx] });
},
onWorkRegionChange(e) {
const idx = Number(e.detail.value);
this.setData({ workRegionIndex: idx, workRegion: WORK_REGION_OPTIONS[idx] });
},
onSmokingChange(e) {
const idx = Number(e.detail.value);
this.setData({ smokingIndex: idx, smokingStatus: SMOKING_OPTIONS[idx].value });
},
onDrinkingChange(e) {
const idx = Number(e.detail.value);
this.setData({ drinkingIndex: idx, drinkingStatus: DRINKING_OPTIONS[idx].value });
},
onLongTermChange(e) {
const idx = Number(e.detail.value);
this.setData({ longTermIndex: idx, longTermDanyang: LONG_TERM_OPTIONS[idx].value });
},
onFutureDanyangChange(e) {
const idx = Number(e.detail.value);
this.setData({ futureDanyangIndex: idx, futureDanyang: FUTURE_DANYANG_OPTIONS[idx].value });
},
// ============ 导航 ============
onBack() {
this._writeDraft();
wx.navigateBack();
},
async onNext() {
if (this.data.saving) return;
const { occupationCategory, workRegion, smokingStatus, drinkingStatus, longTermDanyang, futureDanyang } = this.data;
if (!occupationCategory) return appStore.toast('请选择职业类别');
if (!workRegion) return appStore.toast('请选择工作区域');
this.setData({ saving: true });
this._writeDraft();
try {
await userStore.updateProfile({
occupationCategory,
workRegion,
smokingStatus,
drinkingStatus,
longTermDanyang,
futureDanyang,
});
this.setData({ saving: false });
wx.navigateTo({ url: ROUTE.ONBOARDING.PROFILE_PHOTOS });
} catch (err) {
this.setData({ saving: false });
appStore.toastError(err, '保存失败');
}
},
});
@@ -0,0 +1,9 @@
{
"navigationBarTitleText": "职业与生活",
"navigationStyle": "custom",
"usingComponents": {
"ui-nav-bar": "/components/nav-bar/index",
"ui-section-header": "/components/section-header/index",
"ui-button": "/components/button/index"
}
}
@@ -0,0 +1,74 @@
<!-- 职业与生活页 U07 -->
<view class="pl-page">
<ui-nav-bar
title="职业与生活"
status-bar-height="{{statusBarHeight}}"
nav-bar-height="{{navBarHeight}}"
bind:back="onBack"
/>
<view class="pl-body">
<view class="pl-section card-base">
<ui-section-header title="职业与生活" subtitle="完善你的生活状态" />
<view class="pl-form">
<picker mode="selector" range="{{occupationCategoryOptions}}" value="{{occupationCategoryIndex < 0 ? 0 : occupationCategoryIndex}}" bindchange="onOccupationCategoryChange">
<view class="pl-field">
<text class="pl-field__label">职业类别</text>
<view class="pl-field__value {{occupationCategoryIndex >= 0 ? '' : 'pl-field__value--placeholder'}}">
{{occupationCategoryIndex >= 0 ? occupationCategoryOptions[occupationCategoryIndex] : '请选择'}}
</view>
</view>
</picker>
<picker mode="selector" range="{{workRegionOptions}}" value="{{workRegionIndex < 0 ? 0 : workRegionIndex}}" bindchange="onWorkRegionChange">
<view class="pl-field">
<text class="pl-field__label">工作区域</text>
<view class="pl-field__value {{workRegionIndex >= 0 ? '' : 'pl-field__value--placeholder'}}">
{{workRegionIndex >= 0 ? workRegionOptions[workRegionIndex] : '请选择'}}
</view>
</view>
</picker>
<picker mode="selector" range="{{smokingOptions}}" range-key="label" value="{{smokingIndex < 0 ? 0 : smokingIndex}}" bindchange="onSmokingChange">
<view class="pl-field">
<text class="pl-field__label">是否吸烟</text>
<view class="pl-field__value {{smokingIndex >= 0 ? '' : 'pl-field__value--placeholder'}}">
{{smokingIndex >= 0 ? smokingOptions[smokingIndex].label : '请选择'}}
</view>
</view>
</picker>
<picker mode="selector" range="{{drinkingOptions}}" range-key="label" value="{{drinkingIndex < 0 ? 0 : drinkingIndex}}" bindchange="onDrinkingChange">
<view class="pl-field">
<text class="pl-field__label">是否饮酒</text>
<view class="pl-field__value {{drinkingIndex >= 0 ? '' : 'pl-field__value--placeholder'}}">
{{drinkingIndex >= 0 ? drinkingOptions[drinkingIndex].label : '请选择'}}
</view>
</view>
</picker>
<picker mode="selector" range="{{longTermOptions}}" range-key="label" value="{{longTermIndex < 0 ? 0 : longTermIndex}}" bindchange="onLongTermChange">
<view class="pl-field">
<text class="pl-field__label">是否长期居住丹阳</text>
<view class="pl-field__value {{longTermIndex >= 0 ? '' : 'pl-field__value--placeholder'}}">
{{longTermIndex >= 0 ? longTermOptions[longTermIndex].label : '请选择'}}
</view>
</view>
</picker>
<picker mode="selector" range="{{futureDanyangOptions}}" range-key="label" value="{{futureDanyangIndex < 0 ? 0 : futureDanyangIndex}}" bindchange="onFutureDanyangChange">
<view class="pl-field">
<text class="pl-field__label">未来是否愿意继续在丹阳</text>
<view class="pl-field__value {{futureDanyangIndex >= 0 ? '' : 'pl-field__value--placeholder'}}">
{{futureDanyangIndex >= 0 ? futureDanyangOptions[futureDanyangIndex].label : '请选择'}}
</view>
</view>
</picker>
</view>
</view>
</view>
<view class="pl-actions">
<ui-button variant="primary" size="lg" block loading="{{saving}}" bind:tap="onNext">下一步</ui-button>
</view>
</view>
@@ -0,0 +1,64 @@
/**
* 职业与生活页 U07
*/
.pl-page {
min-height: 100vh;
background: var(--color-bg-page);
padding-bottom: calc(160rpx + env(safe-area-inset-bottom));
}
.pl-body {
padding: var(--spacing-2) var(--spacing-page-x);
}
.pl-section {
margin-bottom: var(--spacing-3);
}
.pl-form {
margin-top: var(--spacing-2);
}
.pl-field {
display: flex;
align-items: center;
justify-content: space-between;
padding: var(--spacing-3) 0;
border-bottom: var(--border-width) solid var(--color-divider);
min-height: 88rpx;
}
.pl-field:last-child {
border-bottom: none;
}
.pl-field__label {
font-size: var(--font-size-md);
color: var(--color-text-primary);
flex-shrink: 0;
width: 280rpx;
}
.pl-field__value {
flex: 1;
text-align: right;
font-size: var(--font-size-md);
color: var(--color-text-primary);
}
.pl-field__value--placeholder {
color: var(--color-text-placeholder);
}
.pl-actions {
position: fixed;
bottom: 0;
left: 0;
right: 0;
padding: var(--spacing-2) var(--spacing-page-x);
padding-bottom: calc(var(--spacing-2) + env(safe-area-inset-bottom));
background: var(--color-bg-card);
border-top: var(--border-width) solid var(--color-divider);
z-index: var(--z-sticky);
}