喜喜页面的个人画像。

This commit is contained in:
zhonghua.li
2026-04-06 09:19:29 +08:00
parent b768d52f4b
commit 1dd87b4283
2 changed files with 431 additions and 20 deletions

View File

@@ -1,5 +1,179 @@
<template>
<scroll-view class="panel-scroll" scroll-y>
<view class="card">
<text class="section-title">基本信息</text>
<view class="field">
<text class="label">手机号码</text>
<input
class="input"
v-model="local.myPhone"
type="number"
maxlength="11"
placeholder="请输入手机号码"
placeholder-class="ph"
/>
</view>
<view class="field">
<text class="label">名字</text>
<input class="input" v-model="local.name" placeholder="请输入名字" placeholder-class="ph" />
</view>
<view class="field">
<text class="label">称呼</text>
<input class="input" v-model="local.salutation" placeholder="例如:小王" placeholder-class="ph" />
</view>
<view class="field">
<text class="label">性别</text>
<radio-group class="gender-group" @change="onGenderRadioChange">
<label class="gender-item">
<radio value="男" :checked="local.gender === '男'" color="#e91e8c" />
<text class="gender-text"></text>
</label>
<label class="gender-item">
<radio value="女" :checked="local.gender === '女'" color="#e91e8c" />
<text class="gender-text"></text>
</label>
</radio-group>
</view>
<view class="field">
<text class="label">年龄</text>
<input
class="input"
v-model="local.age"
type="number"
placeholder="请输入年龄"
placeholder-class="ph"
/>
</view>
</view>
<view class="card">
<text class="section-title">职业与居住</text>
<view class="field">
<text class="label">行业</text>
<input class="input" v-model="local.industry" placeholder="所在行业" placeholder-class="ph" />
</view>
<view class="field">
<text class="label">收入水平</text>
<input class="input" v-model="local.incomeLevel" placeholder="收入水平" placeholder-class="ph" />
</view>
<view class="field">
<text class="label">居住区域</text>
<input class="input" v-model="local.residentialArea" placeholder="居住区域" placeholder-class="ph" />
</view>
</view>
<view class="card">
<text class="section-title">兴趣与关系</text>
<view class="field">
<text class="label">性格</text>
<textarea
class="area area--sm"
v-model="local.personality"
placeholder="性格特点"
maxlength="500"
:show-confirm-bar="false"
/>
</view>
<view class="field">
<text class="label">兴趣爱好</text>
<textarea
class="area area--sm"
v-model="local.hobbies"
placeholder="兴趣爱好"
maxlength="500"
:show-confirm-bar="false"
/>
</view>
<view class="field">
<text class="label">喜欢的美食</text>
<input class="input" v-model="local.favoriteFood" placeholder="喜欢的美食" placeholder-class="ph" />
</view>
<view class="field">
<text class="label">喜欢的电影类型</text>
<input class="input" v-model="local.favoriteMovieGenres" placeholder="例如:科幻、文艺" placeholder-class="ph" />
</view>
<view class="field">
<text class="label">最近关系的小事</text>
<textarea
class="area area--sm"
v-model="local.recentRelationshipNote"
placeholder="最近与感情/相处相关的一件小事"
maxlength="1000"
:show-confirm-bar="false"
/>
</view>
</view>
<view class="card">
<text class="section-title">教育与健康</text>
<view class="field">
<text class="label">学历</text>
<input class="input" v-model="local.education" placeholder="学历" placeholder-class="ph" />
</view>
<view class="field">
<text class="label">专业</text>
<input class="input" v-model="local.major" placeholder="专业" placeholder-class="ph" />
</view>
<view class="field">
<text class="label">身高厘米</text>
<input
class="input"
v-model="local.heightCm"
type="number"
placeholder="例如 170"
placeholder-class="ph"
/>
</view>
<view class="field">
<text class="label">体重千克</text>
<input class="input" v-model="local.weightKg" placeholder="例如 65.5" placeholder-class="ph" />
</view>
<view class="field">
<text class="label">健康状况</text>
<input class="input" v-model="local.healthStatus" placeholder="健康状况" placeholder-class="ph" />
</view>
<view class="field">
<text class="label">对自己的评价</text>
<textarea
class="area"
v-model="local.selfEvaluation"
placeholder="对自己的评价"
maxlength="2000"
:show-confirm-bar="false"
/>
</view>
</view>
<view class="card">
<text class="section-title">家庭与物质条件</text>
<view class="field">
<text class="label">父亲职业</text>
<input class="input" v-model="local.fatherOccupation" placeholder="父亲职业" placeholder-class="ph" />
</view>
<view class="field">
<text class="label">母亲职业</text>
<input class="input" v-model="local.motherOccupation" placeholder="母亲职业" placeholder-class="ph" />
</view>
<view class="field">
<text class="label">兄妹几个</text>
<input class="input" v-model="local.siblingsDesc" placeholder="例如:独生子女 / 有一个哥哥" placeholder-class="ph" />
</view>
<view class="field">
<text class="label">房子有无贷款等</text>
<textarea
class="area area--sm"
v-model="local.houseSituation"
placeholder="房产情况"
maxlength="500"
:show-confirm-bar="false"
/>
</view>
<view class="field">
<text class="label">车子有无等</text>
<input class="input" v-model="local.carSituation" placeholder="车辆情况" placeholder-class="ph" />
</view>
</view>
<view class="card">
<text class="label">我是谁恋爱相关的自我描述</text>
<textarea
@@ -20,45 +194,239 @@
:show-confirm-bar="false"
/>
</view>
<button class="btn-save" type="primary" @click="save">保存</button>
<view class="card">
<text class="label">备注</text>
<textarea
class="area area--sm"
v-model="local.remark"
placeholder="其他备注"
maxlength="1000"
:show-confirm-bar="false"
/>
</view>
<button class="btn-save" type="primary" :loading="saveSubmitting" :disabled="saveSubmitting" @click="save">
保存
</button>
</scroll-view>
</template>
<script>
import { post } from '@/common/request.js';
const KEY = 'xixi_who_am_i';
function trimStr(v) {
if (v == null) return '';
return String(v).trim();
}
/** 中国大陆手机号1 开头第二位 3-9共 11 位 */
function isValidChinaMobile(phone) {
return /^1[3-9]\d{9}$/.test(phone);
}
/**
* 保存前校验:未填写的项不校验;填写了则必须符合规则
* @returns {{ ok: true } | { ok: false, message: string }}
*/
function validateLoveWhoIFields(local) {
const phone = trimStr(local.myPhone);
if (phone !== '') {
if (!isValidChinaMobile(phone)) {
return { ok: false, message: '请输入正确的11位手机号' };
}
}
const ageStr = trimStr(local.age);
if (ageStr !== '') {
if (!/^\d+$/.test(ageStr)) {
return { ok: false, message: '年龄请填写正整数' };
}
const age = parseInt(ageStr, 10);
if (age < 1 || age > 120) {
return { ok: false, message: '年龄需在 1120 岁' };
}
}
const heightStr = trimStr(local.heightCm);
if (heightStr !== '') {
if (!/^\d+$/.test(heightStr)) {
return { ok: false, message: '身高请填写正整数(厘米)' };
}
const h = parseInt(heightStr, 10);
if (h < 50 || h > 250) {
return { ok: false, message: '身高需在 50250 厘米' };
}
}
const weightStr = trimStr(local.weightKg);
if (weightStr !== '') {
const normalized = weightStr.replace(/,/g, '');
if (!/^\d+(\.\d{1,2})?$/.test(normalized)) {
return { ok: false, message: '体重请填写数字,最多两位小数(千克)' };
}
const w = parseFloat(normalized);
if (!Number.isFinite(w) || w < 15 || w > 400) {
return { ok: false, message: '体重需在 15400 千克' };
}
}
return { ok: true };
}
/** 提交 /api/loveWhoI/add 的请求体camelCase与 Spring 默认一致) */
function buildLoveWhoIAddPayload(local) {
const ageStr = trimStr(local.age);
const heightStr = trimStr(local.heightCm);
const weightStr = trimStr(local.weightKg);
const payload = {
myPhone: trimStr(local.myPhone),
name: trimStr(local.name),
salutation: trimStr(local.salutation),
gender: trimStr(local.gender),
industry: trimStr(local.industry),
incomeLevel: trimStr(local.incomeLevel),
residentialArea: trimStr(local.residentialArea),
personality: trimStr(local.personality),
hobbies: trimStr(local.hobbies),
favoriteFood: trimStr(local.favoriteFood),
favoriteMovieGenres: trimStr(local.favoriteMovieGenres),
recentRelationshipNote: trimStr(local.recentRelationshipNote),
education: trimStr(local.education),
major: trimStr(local.major),
healthStatus: trimStr(local.healthStatus),
selfEvaluation: trimStr(local.selfEvaluation),
fatherOccupation: trimStr(local.fatherOccupation),
motherOccupation: trimStr(local.motherOccupation),
siblingsDesc: trimStr(local.siblingsDesc),
houseSituation: trimStr(local.houseSituation),
carSituation: trimStr(local.carSituation),
remark: trimStr(local.remark),
};
if (ageStr !== '') {
const n = parseInt(ageStr, 10);
if (Number.isFinite(n)) payload.age = n;
}
if (heightStr !== '') {
const n = parseInt(heightStr, 10);
if (Number.isFinite(n)) payload.heightCm = n;
}
if (weightStr !== '') {
const w = parseFloat(weightStr);
if (Number.isFinite(w)) payload.weightKg = w;
}
return payload;
}
function defaultLocal() {
return {
loveWhoIId: '',
myPhone: '',
name: '',
salutation: '',
gender: '',
age: '',
industry: '',
incomeLevel: '',
residentialArea: '',
personality: '',
hobbies: '',
favoriteFood: '',
favoriteMovieGenres: '',
recentRelationshipNote: '',
education: '',
major: '',
heightCm: '',
weightKg: '',
healthStatus: '',
selfEvaluation: '',
fatherOccupation: '',
motherOccupation: '',
siblingsDesc: '',
houseSituation: '',
carSituation: '',
remark: '',
selfIntro: '',
partnerExpect: '',
};
}
export default {
name: 'XixiWhoAmI',
data() {
return {
local: {
selfIntro: '',
partnerExpect: '',
},
local: defaultLocal(),
saveSubmitting: false,
};
},
mounted() {
this.load();
},
methods: {
onGenderRadioChange(e) {
this.local.gender = e.detail.value || '';
},
load() {
const base = defaultLocal();
try {
const raw = uni.getStorageSync(KEY);
if (raw && typeof raw === 'object') {
this.local.selfIntro = raw.selfIntro || '';
this.local.partnerExpect = raw.partnerExpect || '';
this.local = { ...base, ...raw };
} else {
this.local = base;
}
} catch (e) {
/* ignore */
this.local = base;
}
},
save() {
async save() {
if (this.saveSubmitting) return;
const check = validateLoveWhoIFields(this.local);
if (!check.ok) {
uni.showToast({ title: check.message, icon: 'none' });
return;
}
this.saveSubmitting = true;
try {
uni.setStorageSync(KEY, { ...this.local });
uni.showToast({ title: '已保存', icon: 'success' });
this.$emit('saved');
const requestData = buildLoveWhoIAddPayload(this.local);
const res = await post('/api/loveWhoI/add', requestData);
const payload = res.data && typeof res.data === 'object' ? res.data : {};
const ok = res.statusCode === 200 && payload.success === true;
if (ok) {
const row = payload.data;
if (row && typeof row === 'object' && row.id) {
this.local.loveWhoIId = String(row.id);
}
try {
uni.setStorageSync(KEY, { ...this.local });
} catch (e) {
/* ignore */
}
uni.showToast({ title: payload.message || '已保存', icon: 'success' });
this.$emit('saved');
} else {
const msg = payload.message || `服务端保存失败(${res.statusCode || '?'})`;
try {
uni.setStorageSync(KEY, { ...this.local });
} catch (e) {
/* ignore */
}
uni.showToast({ title: msg, icon: 'none' });
}
} catch (e) {
uni.showToast({ title: '保存失败', icon: 'none' });
console.error('[XixiWhoAmI] save failed:', e);
try {
uni.setStorageSync(KEY, { ...this.local });
} catch (err) {
/* ignore */
}
const msg = e?.message || '网络异常,已暂存本地';
uni.showToast({ title: msg, icon: 'none' });
} finally {
this.saveSubmitting = false;
}
},
},
@@ -78,12 +446,57 @@ export default {
margin-bottom: 24rpx;
box-shadow: 0 4rpx 24rpx rgba(233, 30, 140, 0.06);
}
.section-title {
display: block;
font-size: 30rpx;
font-weight: 700;
color: #e91e8c;
margin-bottom: 20rpx;
}
.field {
margin-bottom: 20rpx;
}
.field:last-child {
margin-bottom: 0;
}
.label {
display: block;
font-size: 28rpx;
font-weight: 600;
color: #333;
margin-bottom: 16rpx;
margin-bottom: 12rpx;
}
.input {
width: 100%;
min-height: 72rpx;
font-size: 28rpx;
line-height: 1.5;
color: #333;
box-sizing: border-box;
padding: 16rpx;
background: #fff8fa;
border-radius: 12rpx;
}
.gender-group {
display: flex;
flex-direction: row;
align-items: center;
flex-wrap: wrap;
gap: 40rpx;
padding: 8rpx 4rpx;
}
.gender-item {
display: flex;
flex-direction: row;
align-items: center;
}
.gender-text {
margin-left: 12rpx;
font-size: 28rpx;
color: #333;
}
.ph {
color: #b0b0b0;
}
.area {
width: 100%;
@@ -96,6 +509,9 @@ export default {
background: #fff8fa;
border-radius: 12rpx;
}
.area--sm {
min-height: 140rpx;
}
.btn-save {
margin-top: 12rpx;
border-radius: 999rpx;