|
|
|
<template>
|
|
|
|
<view class="profile-page">
|
|
|
|
<up-navbar bgColor="transparent" :z-index="999" :autoBack="true">
|
|
|
|
<template #left>
|
|
|
|
<view class="u-nav-slot">
|
|
|
|
<up-icon name="arrow-left" color="#333" size="15"></up-icon>
|
|
|
|
</view>
|
|
|
|
</template>
|
|
|
|
</up-navbar>
|
|
|
|
|
|
|
|
<view class="header-section">
|
|
|
|
<view class="header-content">
|
|
|
|
<view class="title">我的运动资料</view>
|
|
|
|
<view class="subtitle">匹配专属训练计划</view>
|
|
|
|
</view>
|
|
|
|
<view class="header-card">
|
|
|
|
<view class="line"></view>
|
|
|
|
<view class="line"></view>
|
|
|
|
<view class="line"></view>
|
|
|
|
<view class="line"></view>
|
|
|
|
</view>
|
|
|
|
</view>
|
|
|
|
|
|
|
|
<view class="form-card-container">
|
|
|
|
<view class="form-list">
|
|
|
|
<view class="form-item" @click="openRadioPopup('gender', '性别')">
|
|
|
|
<text class="label">性别</text>
|
|
|
|
<view class="right">
|
|
|
|
<text class="value" v-if="pdata.gender">{{ pdata.gender == 1 ? '男' : '女' }}</text>
|
|
|
|
<text class="placeholder-text" v-else>请选择</text>
|
|
|
|
<up-icon name="arrow-right" size="12" color="#cccccc"></up-icon>
|
|
|
|
</view>
|
|
|
|
</view>
|
|
|
|
|
|
|
|
<view class="form-item" @click="Dateshow = true">
|
|
|
|
<text class="label">生日</text>
|
|
|
|
<view class="right">
|
|
|
|
<text class="value" v-if="pdata.birthday">{{ formatDate(pdata.birthday) }}</text>
|
|
|
|
<text class="placeholder-text" v-else>请选择</text>
|
|
|
|
<up-icon name="arrow-right" size="12" color="#cccccc"></up-icon>
|
|
|
|
</view>
|
|
|
|
</view>
|
|
|
|
|
|
|
|
<view class="form-item" @click="openInputPopup('height', '身高')">
|
|
|
|
<text class="label">身高</text>
|
|
|
|
<view class="right">
|
|
|
|
<text class="value" v-if="pdata.height">{{ pdata.height }} cm</text>
|
|
|
|
<text class="placeholder-text" v-else>未填写</text>
|
|
|
|
<up-icon name="arrow-right" size="12" color="#cccccc"></up-icon>
|
|
|
|
</view>
|
|
|
|
</view>
|
|
|
|
|
|
|
|
<view class="form-item" @click="openInputPopup('weight', '当前体重')">
|
|
|
|
<text class="label">当前体重</text>
|
|
|
|
<view class="right">
|
|
|
|
<text class="value" v-if="pdata.weight">{{ pdata.weight }} kg</text>
|
|
|
|
<text class="placeholder-text" v-else>未填写</text>
|
|
|
|
<up-icon name="arrow-right" size="12" color="#cccccc"></up-icon>
|
|
|
|
</view>
|
|
|
|
</view>
|
|
|
|
|
|
|
|
<view class="form-item" @click="openInputPopup('targetWeight', '目标体重')">
|
|
|
|
<text class="label">目标体重</text>
|
|
|
|
<view class="right">
|
|
|
|
<text class="value" v-if="pdata.targetWeight">{{ pdata.targetWeight }} kg</text>
|
|
|
|
<text class="placeholder-text" v-else>未填写</text>
|
|
|
|
<up-icon name="arrow-right" size="12" color="#cccccc"></up-icon>
|
|
|
|
</view>
|
|
|
|
</view>
|
|
|
|
|
|
|
|
<view class="form-item" @click="openRadioPopup('hasFitnessFoundation', '健身基础')">
|
|
|
|
<text class="label">健身基础</text>
|
|
|
|
<view class="right">
|
|
|
|
<text class="value" v-if="pdata.hasFitnessFoundation">{{
|
|
|
|
pdata.hasFitnessFoundation == 1 ? '有' : '无'
|
|
|
|
}}</text>
|
|
|
|
<text class="placeholder-text" v-else>请选择</text>
|
|
|
|
<up-icon name="arrow-right" size="12" color="#cccccc"></up-icon>
|
|
|
|
</view>
|
|
|
|
</view>
|
|
|
|
|
|
|
|
<view class="form-item" @click="openRadioPopup('acceptableTrainingFrequency', '可接受的训练频次')">
|
|
|
|
<text class="label">可接受的训练频次</text>
|
|
|
|
<view class="right">
|
|
|
|
<text class="value" v-if="pdata.acceptableTrainingFrequency">
|
|
|
|
{{ pdata.acceptableTrainingFrequency == 1 ? '1练/2练/3练' : '4练/5练/6练' }}
|
|
|
|
</text>
|
|
|
|
<text class="placeholder-text" v-else>请选择</text>
|
|
|
|
<up-icon name="arrow-right" size="12" color="#cccccc"></up-icon>
|
|
|
|
</view>
|
|
|
|
</view>
|
|
|
|
|
|
|
|
<view class="form-item" @click="openCheckPopup('targetMuscleParts', '想锻炼的肌肉部位')">
|
|
|
|
<text class="label">想锻炼的肌肉部位</text>
|
|
|
|
<view class="right">
|
|
|
|
<text class="value" v-if="pdata.targetMuscleParts && pdata.targetMuscleParts.length">
|
|
|
|
{{ pdata.targetMuscleParts.join('、') }}
|
|
|
|
</text>
|
|
|
|
<text class="placeholder-text" v-else>请选择(多选)</text>
|
|
|
|
<up-icon name="arrow-right" size="12" color="#cccccc"></up-icon>
|
|
|
|
</view>
|
|
|
|
</view>
|
|
|
|
|
|
|
|
<view class="form-item" @click="openRadioPopup('trainingGoal', '训练目标')">
|
|
|
|
<text class="label">训练目标</text>
|
|
|
|
<view class="right">
|
|
|
|
<text class="value" v-if="pdata.trainingGoal">{{
|
|
|
|
formatGoal(pdata.trainingGoal)
|
|
|
|
}}</text>
|
|
|
|
<text class="placeholder-text" v-else>请选择</text>
|
|
|
|
<up-icon name="arrow-right" size="12" color="#cccccc"></up-icon>
|
|
|
|
</view>
|
|
|
|
</view>
|
|
|
|
|
|
|
|
<view class="form-item" @click="openCheckPopup('painAreas', '身体疼痛部位')">
|
|
|
|
<text class="label">身体疼痛部位</text>
|
|
|
|
<view class="right">
|
|
|
|
<text class="value" v-if="pdata.painAreas && pdata.painAreas.length">
|
|
|
|
{{ pdata.painAreas.join('、') }}
|
|
|
|
</text>
|
|
|
|
<text class="placeholder-text" v-else>无明显疼痛</text>
|
|
|
|
<up-icon name="arrow-right" size="12" color="#cccccc"></up-icon>
|
|
|
|
</view>
|
|
|
|
</view>
|
|
|
|
|
|
|
|
<view class="form-item" @click="openRadioPopup('hasDisease', '当前是否存在疾病')">
|
|
|
|
<text class="label">当前是否存在疾病</text>
|
|
|
|
<view class="right">
|
|
|
|
<text class="value" v-if="pdata.hasDisease">{{
|
|
|
|
pdata.hasDisease == 1 ? '有' : '无'
|
|
|
|
}}</text>
|
|
|
|
<text class="placeholder-text" v-else>请选择</text>
|
|
|
|
<up-icon name="arrow-right" size="12" color="#cccccc"></up-icon>
|
|
|
|
</view>
|
|
|
|
</view>
|
|
|
|
|
|
|
|
<view class="form-item" @click="openRadioPopup('isTakingMedication', '当前是否在服药')">
|
|
|
|
<text class="label">当前是否在服药</text>
|
|
|
|
<view class="right">
|
|
|
|
<text class="value" v-if="pdata.isTakingMedication">{{
|
|
|
|
pdata.isTakingMedication == 1 ? '是' : '否'
|
|
|
|
}}</text>
|
|
|
|
<text class="placeholder-text" v-else>请选择</text>
|
|
|
|
<up-icon name="arrow-right" size="12" color="#cccccc"></up-icon>
|
|
|
|
</view>
|
|
|
|
</view>
|
|
|
|
|
|
|
|
<view class="form-item" @click="openRadioPopup('rewardMethod', '达成目标如何奖励自己')">
|
|
|
|
<text class="label">达成目标如何奖励自己</text>
|
|
|
|
<view class="right">
|
|
|
|
<text class="value" v-if="pdata.rewardMethod">{{
|
|
|
|
formatReward(pdata.rewardMethod)
|
|
|
|
}}</text>
|
|
|
|
<text class="placeholder-text" v-else>请选择</text>
|
|
|
|
<up-icon name="arrow-right" size="12" color="#cccccc"></up-icon>
|
|
|
|
</view>
|
|
|
|
</view>
|
|
|
|
|
|
|
|
<view class="form-item" @click="openRadioPopup('fitnessScene', '平常在哪里健身')">
|
|
|
|
<text class="label">平常在哪里健身</text>
|
|
|
|
<view class="right">
|
|
|
|
<text class="value" v-if="pdata.fitnessScene">{{
|
|
|
|
formatScene(pdata.fitnessScene)
|
|
|
|
}}</text>
|
|
|
|
<text class="placeholder-text" v-else>请选择</text>
|
|
|
|
<up-icon name="arrow-right" size="12" color="#cccccc"></up-icon>
|
|
|
|
</view>
|
|
|
|
</view>
|
|
|
|
</view>
|
|
|
|
</view>
|
|
|
|
|
|
|
|
<view class="footer-action-bar">
|
|
|
|
<button class="confirm-btn" :loading="isSaving" @click="saveHealthInfo">
|
|
|
|
生成专属训练计划
|
|
|
|
</button>
|
|
|
|
</view>
|
|
|
|
|
|
|
|
<up-popup :show="radioPopup" @close="closeRadioPopup" round="16rpx" mode="bottom" closeable>
|
|
|
|
<view class="popup-box">
|
|
|
|
<view class="popup-header">{{ currentTitle }}</view>
|
|
|
|
<scroll-view scroll-y class="popup-scroll-area">
|
|
|
|
<view class="select-list">
|
|
|
|
<view class="select-item" :class="{ active: item.value === bufferValue }"
|
|
|
|
v-for="(item, index) in formOptions[currentKey]" :key="index" @click="bufferValue = item.value">
|
|
|
|
<text class="item-text">{{ item.label }}</text>
|
|
|
|
<up-icon v-if="item.value === bufferValue" name="checkbox-mark" color="#ffde00" size="18"></up-icon>
|
|
|
|
</view>
|
|
|
|
</view>
|
|
|
|
</scroll-view>
|
|
|
|
<view class="popup-footer">
|
|
|
|
<button class="action-btn" @click="submitRadio">确 定</button>
|
|
|
|
</view>
|
|
|
|
</view>
|
|
|
|
</up-popup>
|
|
|
|
|
|
|
|
<up-popup :show="inputPopup" @close="closeInputPopup" round="16rpx" mode="bottom" closeable>
|
|
|
|
<view class="popup-box">
|
|
|
|
<view class="popup-header">{{ currentTitle }}</view>
|
|
|
|
<view class="input-content-area">
|
|
|
|
<up-input placeholder="请输入有效数字" border="surround" type="digit" v-model="bufferValue" clearable
|
|
|
|
customStyle="background-color: #f9f9f9; padding: 24rpx;"></up-input>
|
|
|
|
<text class="unit-text">{{ currentKey === 'height' ? 'cm' : 'kg' }}</text>
|
|
|
|
</view>
|
|
|
|
<view class="popup-footer">
|
|
|
|
<button class="action-btn" @click="submitInput">确 定</button>
|
|
|
|
</view>
|
|
|
|
</view>
|
|
|
|
</up-popup>
|
|
|
|
|
|
|
|
<up-popup :show="checkPopup" @close="closeCheckPopup" round="16rpx" mode="bottom" closeable>
|
|
|
|
<view class="popup-box">
|
|
|
|
<view class="popup-header">{{ currentTitle }}</view>
|
|
|
|
<scroll-view scroll-y class="popup-scroll-area">
|
|
|
|
<view class="select-list grid-layout">
|
|
|
|
<view class="select-item-chip" :class="{ active: bufferArray.includes(item.value) }"
|
|
|
|
v-for="(item, index) in formOptions[currentKey]" :key="index" @click="toggleCheckItem(item.value)">
|
|
|
|
{{ item.label }}
|
|
|
|
</view>
|
|
|
|
</view>
|
|
|
|
</scroll-view>
|
|
|
|
<view class="popup-footer">
|
|
|
|
<button class="action-btn" @click="submitCheck">确 定</button>
|
|
|
|
</view>
|
|
|
|
</view>
|
|
|
|
</up-popup>
|
|
|
|
|
|
|
|
<up-datetime-picker :show="Dateshow" v-model="datePickerValue" mode="date" title="选择生日" @cancel="Dateshow = false"
|
|
|
|
@confirm="handleSelectDate" :minDate="-1577836800000" :maxDate="Date.now()"></up-datetime-picker>
|
|
|
|
</view>
|
|
|
|
</template>
|
|
|
|
|
|
|
|
<script setup>
|
|
|
|
import { ref, reactive, toRaw } from 'vue';
|
|
|
|
import { onShow } from '@dcloudio/uni-app';
|
|
|
|
import UserApi from '@/sheep/api/member/user';
|
|
|
|
import dayjs from 'dayjs';
|
|
|
|
|
|
|
|
// ========================================================
|
|
|
|
// 响应式单态核心数据集
|
|
|
|
// ========================================================
|
|
|
|
|
|
|
|
const Dateshow = ref(false);
|
|
|
|
const radioPopup = ref(false);
|
|
|
|
const inputPopup = ref(false);
|
|
|
|
const checkPopup = ref(false);
|
|
|
|
const isSaving = ref(false);
|
|
|
|
|
|
|
|
// 状态机寄存指针管理
|
|
|
|
const currentKey = ref('');
|
|
|
|
const currentTitle = ref('');
|
|
|
|
const bufferValue = ref(null); // 纯量缓冲区
|
|
|
|
const bufferArray = ref([]); // 数组队列多选缓冲区
|
|
|
|
const datePickerValue = ref(Date.now());
|
|
|
|
|
|
|
|
const pdata = reactive({
|
|
|
|
id: '',
|
|
|
|
birthday: '',
|
|
|
|
gender: 0,
|
|
|
|
height: '',
|
|
|
|
weight: '',
|
|
|
|
targetWeight: '',
|
|
|
|
hasFitnessFoundation: 0,
|
|
|
|
acceptableTrainingFrequency: 0,
|
|
|
|
targetMuscleParts: [],
|
|
|
|
trainingGoal: 0,
|
|
|
|
painAreas: [],
|
|
|
|
hasDisease: 0,
|
|
|
|
isTakingMedication: 0,
|
|
|
|
rewardMethod: 0,
|
|
|
|
fitnessScene: 0,
|
|
|
|
});
|
|
|
|
|
|
|
|
// 数据字典隔离配置
|
|
|
|
const formOptions = {
|
|
|
|
gender: [
|
|
|
|
{ label: '男', value: 1 },
|
|
|
|
{ label: '女', value: 2 },
|
|
|
|
],
|
|
|
|
hasFitnessFoundation: [
|
|
|
|
{ label: '有', value: 1 },
|
|
|
|
{ label: '无', value: 2 },
|
|
|
|
],
|
|
|
|
acceptableTrainingFrequency: [
|
|
|
|
{ label: '1练/2练/3练', value: 1 },
|
|
|
|
{ label: '4练/5练/6练', value: 2 },
|
|
|
|
],
|
|
|
|
targetMuscleParts: [
|
|
|
|
{ label: '肩颈', value: '肩颈' },
|
|
|
|
{ label: '斜方肌', value: '斜方肌' },
|
|
|
|
{ label: '手臂', value: '手臂' },
|
|
|
|
{ label: '胸部', value: '胸部' },
|
|
|
|
{ label: '背部', value: '背部' },
|
|
|
|
{ label: '腹部', value: '腹部' },
|
|
|
|
{ label: '臀部', value: '臀部' },
|
|
|
|
{ label: '腿部', value: '腿部' },
|
|
|
|
],
|
|
|
|
trainingGoal: [
|
|
|
|
{ label: '减脂', value: 1 },
|
|
|
|
{ label: '增肌', value: 2 },
|
|
|
|
{ label: '塑形', value: 3 },
|
|
|
|
{ label: '拉伸/体态调整', value: 4 },
|
|
|
|
],
|
|
|
|
painAreas: [
|
|
|
|
{ label: '肩颈', value: '肩颈' },
|
|
|
|
{ label: '手腕', value: '手腕' },
|
|
|
|
{ label: '腰部', value: '腰部' },
|
|
|
|
{ label: '脚踝', value: '脚踝' },
|
|
|
|
{ label: '膝盖', value: '膝盖' },
|
|
|
|
],
|
|
|
|
hasDisease: [
|
|
|
|
{ label: '有', value: 1 },
|
|
|
|
{ label: '无', value: 2 },
|
|
|
|
],
|
|
|
|
isTakingMedication: [
|
|
|
|
{ label: '是', value: 1 },
|
|
|
|
{ label: '否', value: 2 },
|
|
|
|
],
|
|
|
|
rewardMethod: [
|
|
|
|
{ label: '买件新衣服/装备', value: 1 },
|
|
|
|
{ label: '去旅行', value: 2 },
|
|
|
|
{ label: '和朋友聚会', value: 3 },
|
|
|
|
{ label: '其他', value: 4 },
|
|
|
|
],
|
|
|
|
fitnessScene: [
|
|
|
|
{ label: '健身房', value: 1 },
|
|
|
|
{ label: '家', value: 2 },
|
|
|
|
{ label: '宿舍', value: 3 },
|
|
|
|
],
|
|
|
|
};
|
|
|
|
|
|
|
|
// ========================================================
|
|
|
|
// 文本高阶转义映射器
|
|
|
|
// ========================================================
|
|
|
|
const formatDate = (val) => (val ? dayjs(val).format('YYYY-MM-DD') : '');
|
|
|
|
const formatGoal = (val) => ({ 1: '减脂', 2: '增肌', 3: '塑形', 4: '拉伸/体态调整' }[val] || '');
|
|
|
|
const formatReward = (val) =>
|
|
|
|
({ 1: '买件新衣服/装备', 2: '去旅行', 3: '和朋友聚会', 4: '其他' }[val] || '');
|
|
|
|
const formatScene = (val) => ({ 1: '健身房', 2: '家', 3: '宿舍' }[val] || '');
|
|
|
|
|
|
|
|
// ========================================================
|
|
|
|
// 核心弹窗调度器(精细化防污染管控)
|
|
|
|
// ========================================================
|
|
|
|
|
|
|
|
const openRadioPopup = (key, title) => {
|
|
|
|
currentKey.value = key;
|
|
|
|
currentTitle.value = title;
|
|
|
|
bufferValue.value = pdata[key] || null; // 载入安全缓冲区
|
|
|
|
radioPopup.value = true;
|
|
|
|
};
|
|
|
|
|
|
|
|
const closeRadioPopup = () => {
|
|
|
|
radioPopup.value = false;
|
|
|
|
};
|
|
|
|
|
|
|
|
const submitRadio = () => {
|
|
|
|
if (bufferValue.value !== null) {
|
|
|
|
pdata[currentKey.value] = bufferValue.value; // 仅在此处准许回填修改
|
|
|
|
}
|
|
|
|
radioPopup.value = false;
|
|
|
|
};
|
|
|
|
|
|
|
|
const openInputPopup = (key, title) => {
|
|
|
|
currentKey.value = key;
|
|
|
|
currentTitle.value = title;
|
|
|
|
bufferValue.value = pdata[key] !== '' ? String(pdata[key]) : '';
|
|
|
|
inputPopup.value = true;
|
|
|
|
};
|
|
|
|
|
|
|
|
const closeInputPopup = () => {
|
|
|
|
inputPopup.value = false;
|
|
|
|
};
|
|
|
|
|
|
|
|
const submitInput = () => {
|
|
|
|
const num = parseFloat(bufferValue.value);
|
|
|
|
// 安全阈值边界拦截校验
|
|
|
|
if (isNaN(num) || num <= 0 || num > 300) {
|
|
|
|
uni.showToast({ title: '请输入合理区间值', icon: 'none' });
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
pdata[currentKey.value] = num.toFixed(1);
|
|
|
|
inputPopup.value = false;
|
|
|
|
};
|
|
|
|
|
|
|
|
const openCheckPopup = (key, title) => {
|
|
|
|
currentKey.value = key;
|
|
|
|
currentTitle.value = title;
|
|
|
|
bufferArray.value = Array.isArray(pdata[key]) ? [...pdata[key]] : []; // 解耦深拷贝
|
|
|
|
checkPopup.value = true;
|
|
|
|
};
|
|
|
|
|
|
|
|
const toggleCheckItem = (val) => {
|
|
|
|
const idx = bufferArray.value.indexOf(val);
|
|
|
|
if (idx > -1) {
|
|
|
|
bufferArray.value.splice(idx, 1);
|
|
|
|
} else {
|
|
|
|
bufferArray.value.push(val);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
const closeCheckPopup = () => {
|
|
|
|
checkPopup.value = false;
|
|
|
|
};
|
|
|
|
|
|
|
|
const submitCheck = () => {
|
|
|
|
pdata[currentKey.value] = [...bufferArray.value];
|
|
|
|
checkPopup.value = false;
|
|
|
|
};
|
|
|
|
|
|
|
|
const handleSelectDate = (e) => {
|
|
|
|
Dateshow.value = false;
|
|
|
|
pdata.birthday = dayjs(e.value).format('YYYY-MM-DD');
|
|
|
|
};
|
|
|
|
|
|
|
|
// ========================================================
|
|
|
|
// 网络数据交互编排
|
|
|
|
// ========================================================
|
|
|
|
|
|
|
|
const getHealthData = async () => {
|
|
|
|
try {
|
|
|
|
const res = await UserApi.getHealthInfo();
|
|
|
|
const data = res?.data || {};
|
|
|
|
|
|
|
|
Object.keys(pdata).forEach((key) => {
|
|
|
|
if (key === 'hasDisease' || key === 'isTakingMedication') {
|
|
|
|
pdata[key] = data[key] === true ? 1 : data[key] === false ? 2 : 0;
|
|
|
|
} else if (key === 'targetMuscleParts' || key === 'painAreas') {
|
|
|
|
pdata[key] = Array.isArray(data[key]) ? data[key] : [];
|
|
|
|
} else {
|
|
|
|
pdata[key] = data[key] !== undefined && data[key] !== null ? data[key] : '';
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
if (pdata.birthday) {
|
|
|
|
datePickerValue.value = dayjs(pdata.birthday).valueOf();
|
|
|
|
}
|
|
|
|
} catch (error) {
|
|
|
|
console.error('拉取档案失败:', error);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
const saveHealthInfo = async () => {
|
|
|
|
// 基础必填拦截防空检测
|
|
|
|
if (!pdata.gender || !pdata.birthday || !pdata.height || !pdata.weight) {
|
|
|
|
uni.showToast({ title: '请完整填写真实核心资料', icon: 'none' });
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (isSaving.value) return;
|
|
|
|
isSaving.value = true;
|
|
|
|
|
|
|
|
try {
|
|
|
|
const payload = {
|
|
|
|
...toRaw(pdata),
|
|
|
|
hasDisease: pdata.hasDisease === 1,
|
|
|
|
isTakingMedication: pdata.isTakingMedication === 1,
|
|
|
|
};
|
|
|
|
|
|
|
|
if (pdata.id) {
|
|
|
|
await UserApi.UpdateHealthInfo(payload);
|
|
|
|
} else {
|
|
|
|
await UserApi.createHealthInfo(payload);
|
|
|
|
}
|
|
|
|
|
|
|
|
uni.showToast({ title: '专属计划已生成', icon: 'success' });
|
|
|
|
setTimeout(() => {
|
|
|
|
uni.navigateBack();
|
|
|
|
}, 1500);
|
|
|
|
} catch (error) {
|
|
|
|
console.error('提交健康资料异常:', error);
|
|
|
|
uni.showToast({ title: '保存失败,请重试', icon: 'none' });
|
|
|
|
} finally {
|
|
|
|
isSaving.value = false;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
onShow(() => {
|
|
|
|
getHealthData();
|
|
|
|
});
|
|
|
|
</script>
|
|
|
|
|
|
|
|
<style lang="scss" scoped>
|
|
|
|
.profile-page {
|
|
|
|
background-color: #f7f8fa;
|
|
|
|
min-height: 100vh;
|
|
|
|
/* 解决长页面底部被悬浮动作条覆盖的问题 */
|
|
|
|
padding-bottom: 180rpx;
|
|
|
|
box-sizing: border-box;
|
|
|
|
|
|
|
|
.u-nav-slot {
|
|
|
|
background-color: rgba(255, 255, 255, 0.8);
|
|
|
|
backdrop-filter: blur(4px);
|
|
|
|
padding: 12rpx;
|
|
|
|
border-radius: 50%;
|
|
|
|
display: flex;
|
|
|
|
align-items: center;
|
|
|
|
justify-content: center;
|
|
|
|
}
|
|
|
|
|
|
|
|
.header-section {
|
|
|
|
width: 100%;
|
|
|
|
height: 22vh;
|
|
|
|
/* #ifdef MP-WEIXIN */
|
|
|
|
height: 26vh;
|
|
|
|
/* #endif */
|
|
|
|
background: linear-gradient(135deg, #ffd166 0%, #ffb74d 100%);
|
|
|
|
position: relative;
|
|
|
|
padding-left: 44rpx;
|
|
|
|
padding-bottom: 50rpx;
|
|
|
|
display: flex;
|
|
|
|
align-items: flex-end;
|
|
|
|
box-sizing: border-box;
|
|
|
|
|
|
|
|
.header-content {
|
|
|
|
display: flex;
|
|
|
|
flex-direction: column;
|
|
|
|
z-index: 2;
|
|
|
|
|
|
|
|
.title {
|
|
|
|
font-size: 46rpx;
|
|
|
|
font-weight: bold;
|
|
|
|
color: #4a2c00;
|
|
|
|
margin-bottom: 8rpx;
|
|
|
|
}
|
|
|
|
|
|
|
|
.subtitle {
|
|
|
|
font-size: 26rpx;
|
|
|
|
color: rgba(74, 44, 0, 0.7);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
.header-card {
|
|
|
|
position: absolute;
|
|
|
|
right: 30rpx;
|
|
|
|
bottom: -10rpx;
|
|
|
|
width: 150rpx;
|
|
|
|
height: 180rpx;
|
|
|
|
background: linear-gradient(135deg, #ff6b6b, #ff8e53);
|
|
|
|
border-radius: 20rpx;
|
|
|
|
display: flex;
|
|
|
|
flex-direction: column;
|
|
|
|
align-items: center;
|
|
|
|
justify-content: center;
|
|
|
|
gap: 16rpx;
|
|
|
|
transform: rotate(12deg);
|
|
|
|
box-shadow: 0 12rpx 24rpx rgba(255, 107, 107, 0.25);
|
|
|
|
z-index: 1;
|
|
|
|
|
|
|
|
.line {
|
|
|
|
width: 55%;
|
|
|
|
height: 8rpx;
|
|
|
|
background-color: rgba(255, 255, 255, 0.9);
|
|
|
|
border-radius: 6rpx;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* 修复:移除有隐患的全局 translateY 移动,改用规范的卡片间距布局 */
|
|
|
|
.form-card-container {
|
|
|
|
padding: 0 30rpx;
|
|
|
|
margin-top: 30rpx;
|
|
|
|
|
|
|
|
.form-list {
|
|
|
|
background-color: #ffffff;
|
|
|
|
border-radius: 24rpx;
|
|
|
|
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.015);
|
|
|
|
overflow: hidden;
|
|
|
|
/* 完美约束子项按圆角外接圆渲染 */
|
|
|
|
|
|
|
|
.form-item {
|
|
|
|
display: flex;
|
|
|
|
align-items: center;
|
|
|
|
justify-content: space-between;
|
|
|
|
padding: 36rpx 30rpx;
|
|
|
|
border-bottom: 1rpx solid #f2f3f5;
|
|
|
|
transition: background-color 0.2s;
|
|
|
|
|
|
|
|
&:active {
|
|
|
|
background-color: #f9f9f9;
|
|
|
|
}
|
|
|
|
|
|
|
|
&:last-child {
|
|
|
|
border-bottom: none;
|
|
|
|
}
|
|
|
|
|
|
|
|
.label {
|
|
|
|
font-size: 28rpx;
|
|
|
|
color: #323233;
|
|
|
|
font-weight: 600;
|
|
|
|
}
|
|
|
|
|
|
|
|
.right {
|
|
|
|
display: flex;
|
|
|
|
align-items: center;
|
|
|
|
gap: 12rpx;
|
|
|
|
max-width: 65%;
|
|
|
|
|
|
|
|
.value {
|
|
|
|
font-size: 28rpx;
|
|
|
|
color: #323233;
|
|
|
|
text-align: right;
|
|
|
|
overflow: hidden;
|
|
|
|
text-overflow: ellipsis;
|
|
|
|
white-space: nowrap;
|
|
|
|
}
|
|
|
|
|
|
|
|
.placeholder-text {
|
|
|
|
font-size: 28rpx;
|
|
|
|
color: #c8c9cc;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* 固定悬浮底部动作栏 */
|
|
|
|
.footer-action-bar {
|
|
|
|
position: fixed;
|
|
|
|
bottom: 0;
|
|
|
|
left: 0;
|
|
|
|
width: 100vw;
|
|
|
|
background-color: #ffffff;
|
|
|
|
padding: 24rpx 40rpx calc(24rpx + env(safe-area-inset-bottom));
|
|
|
|
box-sizing: border-box;
|
|
|
|
box-shadow: 0 -4rpx 24rpx rgba(0, 0, 0, 0.04);
|
|
|
|
z-index: 99;
|
|
|
|
|
|
|
|
.confirm-btn {
|
|
|
|
width: 100%;
|
|
|
|
height: 88rpx;
|
|
|
|
line-height: 88rpx;
|
|
|
|
background: linear-gradient(90deg, #ffe100 0%, #ffc400 100%);
|
|
|
|
color: #323233;
|
|
|
|
font-size: 32rpx;
|
|
|
|
font-weight: bold;
|
|
|
|
border-radius: 44rpx;
|
|
|
|
border: none;
|
|
|
|
|
|
|
|
&::after {
|
|
|
|
border: none;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* 弹出层统一样式规范 */
|
|
|
|
.popup-box {
|
|
|
|
background-color: #ffffff;
|
|
|
|
border-radius: 24rpx 24rpx 0 0;
|
|
|
|
padding: 30rpx 40rpx calc(30rpx + env(safe-area-inset-bottom));
|
|
|
|
box-sizing: border-box;
|
|
|
|
|
|
|
|
.popup-header {
|
|
|
|
font-size: 32rpx;
|
|
|
|
font-weight: bold;
|
|
|
|
color: #323233;
|
|
|
|
text-align: center;
|
|
|
|
padding-bottom: 30rpx;
|
|
|
|
}
|
|
|
|
|
|
|
|
.popup-scroll-area {
|
|
|
|
max-height: 40vh;
|
|
|
|
}
|
|
|
|
|
|
|
|
.input-content-area {
|
|
|
|
display: flex;
|
|
|
|
align-items: center;
|
|
|
|
gap: 20rpx;
|
|
|
|
padding: 20rpx 0 40rpx;
|
|
|
|
|
|
|
|
.unit-text {
|
|
|
|
font-size: 30rpx;
|
|
|
|
color: #323233;
|
|
|
|
font-weight: bold;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
.select-list {
|
|
|
|
display: flex;
|
|
|
|
flex-direction: column;
|
|
|
|
gap: 16rpx;
|
|
|
|
|
|
|
|
&.grid-layout {
|
|
|
|
flex-direction: row;
|
|
|
|
flex-wrap: wrap;
|
|
|
|
gap: 20rpx;
|
|
|
|
}
|
|
|
|
|
|
|
|
.select-item {
|
|
|
|
display: flex;
|
|
|
|
justify-content: space-between;
|
|
|
|
align-items: center;
|
|
|
|
background-color: #f7f8fa;
|
|
|
|
padding: 30rpx 40rpx;
|
|
|
|
border-radius: 16rpx;
|
|
|
|
transition: all 0.2s;
|
|
|
|
|
|
|
|
&.active {
|
|
|
|
background-color: #fffdf0;
|
|
|
|
border: 2rpx solid #ffe100;
|
|
|
|
}
|
|
|
|
|
|
|
|
.item-text {
|
|
|
|
font-size: 28rpx;
|
|
|
|
color: #323233;
|
|
|
|
font-weight: 500;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* 多选网格纸片样式 */
|
|
|
|
.select-item-chip {
|
|
|
|
padding: 20rpx 36rpx;
|
|
|
|
background-color: #f7f8fa;
|
|
|
|
border-radius: 40rpx;
|
|
|
|
font-size: 26rpx;
|
|
|
|
color: #646566;
|
|
|
|
transition: all 0.2s;
|
|
|
|
border: 2rpx solid transparent;
|
|
|
|
|
|
|
|
&.active {
|
|
|
|
background-color: #fffdf0;
|
|
|
|
color: #323233;
|
|
|
|
font-weight: bold;
|
|
|
|
border-color: #ffe100;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
.popup-footer {
|
|
|
|
margin-top: 40rpx;
|
|
|
|
|
|
|
|
.action-btn {
|
|
|
|
width: 100%;
|
|
|
|
height: 80rpx;
|
|
|
|
line-height: 80rpx;
|
|
|
|
background-color: #ffe100;
|
|
|
|
color: #323233;
|
|
|
|
font-size: 28rpx;
|
|
|
|
font-weight: bold;
|
|
|
|
border-radius: 40rpx;
|
|
|
|
border: none;
|
|
|
|
|
|
|
|
&::after {
|
|
|
|
border: none;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
</style> |
...
|
...
|
|