chore: establish V1.1 recovery baseline
This commit is contained in:
@@ -0,0 +1,124 @@
|
||||
/**
|
||||
* 个人介绍页 U11(Phase 04 P04-T26)
|
||||
*
|
||||
* 重构要点(遵循 AGENTS.md 不变量):
|
||||
* - 数据:profileService.getProfile + userStore.updateProfile(第 9 条)
|
||||
* - 字数限制:BUSINESS_LIMITS.MIN_INTRO_LENGTH / MAX_INTRO_LENGTH(第 12 条,不硬编码)
|
||||
* - 审核规则提示:禁止手机号/微信号/二维码/广告等(功能清单 §4.6)
|
||||
* - 草稿恢复:onLoad 读 ONBOARDING_DRAFT.intro,保存前写回
|
||||
* - 下一步:跳 profile-preference
|
||||
*/
|
||||
const { userStore, appStore } = require('../../stores');
|
||||
const { profileService } = require('../../services');
|
||||
const { ROUTE, STORAGE_KEYS, BUSINESS_LIMITS } = require('../../constants');
|
||||
|
||||
Page({
|
||||
data: {
|
||||
statusBarHeight: 20,
|
||||
navBarHeight: 44,
|
||||
loading: false,
|
||||
saving: false,
|
||||
selfIntro: '',
|
||||
minLen: BUSINESS_LIMITS.MIN_INTRO_LENGTH,
|
||||
maxLen: BUSINESS_LIMITS.MAX_INTRO_LENGTH,
|
||||
currentLen: 0,
|
||||
guidelines: [
|
||||
'可以介绍工作、性格、兴趣、生活状态',
|
||||
'以及你期待怎样的感情',
|
||||
'禁止填写手机号、微信号、二维码',
|
||||
'禁止广告、色情低俗、投资引流内容',
|
||||
],
|
||||
},
|
||||
|
||||
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();
|
||||
const d = this._readDraft();
|
||||
const selfIntro = d.selfIntro || (profile && profile.selfIntro) || '';
|
||||
this.setData({
|
||||
selfIntro,
|
||||
currentLen: selfIntro.length,
|
||||
loading: false,
|
||||
});
|
||||
} catch (err) {
|
||||
this.setData({ loading: false });
|
||||
}
|
||||
},
|
||||
|
||||
// ============ 草稿 ============
|
||||
_readDraft() {
|
||||
try {
|
||||
const draft = wx.getStorageSync(STORAGE_KEYS.ONBOARDING_DRAFT) || {};
|
||||
return draft.intro || {};
|
||||
} catch (e) {
|
||||
return {};
|
||||
}
|
||||
},
|
||||
|
||||
_applyDraft() {
|
||||
const d = this._readDraft();
|
||||
if (!d || Object.keys(d).length === 0) return;
|
||||
const selfIntro = d.selfIntro || '';
|
||||
this.setData({ selfIntro, currentLen: selfIntro.length });
|
||||
},
|
||||
|
||||
_writeDraft() {
|
||||
try {
|
||||
const draft = wx.getStorageSync(STORAGE_KEYS.ONBOARDING_DRAFT) || {};
|
||||
draft.intro = { selfIntro: this.data.selfIntro };
|
||||
draft.currentStep = 'intro';
|
||||
wx.setStorageSync(STORAGE_KEYS.ONBOARDING_DRAFT, draft);
|
||||
} catch (e) { /* ignore */ }
|
||||
},
|
||||
|
||||
// ============ 交互 ============
|
||||
onIntroInput(e) {
|
||||
const value = e.detail.value || '';
|
||||
this.setData({ selfIntro: value, currentLen: value.length });
|
||||
},
|
||||
|
||||
// ============ 导航 ============
|
||||
onBack() {
|
||||
this._writeDraft();
|
||||
wx.navigateBack();
|
||||
},
|
||||
|
||||
async onNext() {
|
||||
if (this.data.saving) return;
|
||||
const { selfIntro, minLen, maxLen } = this.data;
|
||||
if (selfIntro.length < minLen) {
|
||||
return appStore.toast(`至少填写 ${minLen} 字`);
|
||||
}
|
||||
if (selfIntro.length > maxLen) {
|
||||
return appStore.toast(`最多填写 ${maxLen} 字`);
|
||||
}
|
||||
|
||||
this.setData({ saving: true });
|
||||
this._writeDraft();
|
||||
try {
|
||||
await userStore.updateProfile({ selfIntro });
|
||||
this.setData({ saving: false });
|
||||
wx.navigateTo({ url: ROUTE.ONBOARDING.PROFILE_PREFERENCE });
|
||||
} catch (err) {
|
||||
this.setData({ saving: false });
|
||||
appStore.toastError(err, '保存失败');
|
||||
}
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"navigationBarTitleText": "个人介绍",
|
||||
"navigationStyle": "custom",
|
||||
"usingComponents": {
|
||||
"ui-nav-bar": "/components/nav-bar/index",
|
||||
"ui-section-header": "/components/section-header/index",
|
||||
"ui-textarea": "/components/textarea/index",
|
||||
"ui-button": "/components/button/index"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
<!-- 个人介绍页 U11 -->
|
||||
<view class="pri-page">
|
||||
<ui-nav-bar
|
||||
title="个人介绍"
|
||||
status-bar-height="{{statusBarHeight}}"
|
||||
nav-bar-height="{{navBarHeight}}"
|
||||
bind:back="onBack"
|
||||
/>
|
||||
|
||||
<view class="pri-body">
|
||||
<view class="pri-section card-base">
|
||||
<ui-section-header title="简单介绍一下自己" subtitle="{{minLen}}~{{maxLen}} 字" />
|
||||
<view class="pri-textarea-wrap">
|
||||
<textarea
|
||||
class="pri-textarea"
|
||||
value="{{selfIntro}}"
|
||||
placeholder="可以介绍工作、性格、兴趣、生活状态,以及你期待怎样的感情"
|
||||
placeholder-class="pri-textarea__placeholder"
|
||||
maxlength="{{maxLen}}"
|
||||
bindinput="onIntroInput"
|
||||
/>
|
||||
<view class="pri-counter {{currentLen > maxLen ? 'pri-counter--over' : ''}}">
|
||||
{{currentLen}}/{{maxLen}}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 审核规则 -->
|
||||
<view class="pri-section card-base">
|
||||
<ui-section-header title="填写须知" />
|
||||
<view class="pri-guidelines">
|
||||
<view wx:for="{{guidelines}}" wx:key="*this" class="pri-guideline">
|
||||
<view class="pri-guideline__dot"></view>
|
||||
<text class="pri-guideline__text">{{item}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="pri-actions">
|
||||
<ui-button variant="primary" size="lg" block loading="{{saving}}" bind:tap="onNext">下一步</ui-button>
|
||||
</view>
|
||||
</view>
|
||||
@@ -0,0 +1,93 @@
|
||||
/**
|
||||
* 个人介绍页 U11
|
||||
*/
|
||||
|
||||
.pri-page {
|
||||
min-height: 100vh;
|
||||
background: var(--color-bg-page);
|
||||
padding-bottom: calc(160rpx + env(safe-area-inset-bottom));
|
||||
}
|
||||
|
||||
.pri-body {
|
||||
padding: var(--spacing-2) var(--spacing-page-x);
|
||||
}
|
||||
|
||||
.pri-section {
|
||||
margin-bottom: var(--spacing-3);
|
||||
}
|
||||
|
||||
/* ============ 文本框 ============ */
|
||||
.pri-textarea-wrap {
|
||||
margin-top: var(--spacing-2);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.pri-textarea {
|
||||
width: 100%;
|
||||
min-height: 320rpx;
|
||||
padding: var(--spacing-3);
|
||||
font-size: var(--font-size-md);
|
||||
color: var(--color-text-primary);
|
||||
line-height: var(--line-height-relaxed);
|
||||
background: var(--color-bg-input);
|
||||
border-radius: var(--radius-md);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.pri-textarea__placeholder {
|
||||
color: var(--color-text-placeholder);
|
||||
font-size: var(--font-size-sm);
|
||||
}
|
||||
|
||||
.pri-counter {
|
||||
position: absolute;
|
||||
right: var(--spacing-2);
|
||||
bottom: var(--spacing-2);
|
||||
font-size: var(--font-size-xs);
|
||||
color: var(--color-text-placeholder);
|
||||
}
|
||||
|
||||
.pri-counter--over {
|
||||
color: var(--color-danger);
|
||||
}
|
||||
|
||||
/* ============ 须知 ============ */
|
||||
.pri-guidelines {
|
||||
margin-top: var(--spacing-2);
|
||||
}
|
||||
|
||||
.pri-guideline {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: var(--spacing-2);
|
||||
padding: var(--spacing-1) 0;
|
||||
}
|
||||
|
||||
.pri-guideline__dot {
|
||||
width: 10rpx;
|
||||
height: 10rpx;
|
||||
margin-top: 14rpx;
|
||||
border-radius: 50%;
|
||||
background: var(--color-text-placeholder);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.pri-guideline__text {
|
||||
flex: 1;
|
||||
font-size: var(--font-size-sm);
|
||||
color: var(--color-text-tertiary);
|
||||
line-height: var(--line-height-relaxed);
|
||||
}
|
||||
|
||||
/* ============ 底部 ============ */
|
||||
.pri-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);
|
||||
}
|
||||
Reference in New Issue
Block a user