|
|
@@ -0,0 +1,340 @@
|
|
|
+<template>
|
|
|
+ <CustomNav title="危急值管理" leftType="back" />
|
|
|
+ <view class="page-container">
|
|
|
+ <view class="card">
|
|
|
+ <view class="card-header">体格(身高 / 体重 / BMI)</view>
|
|
|
+ <view class="card-body">
|
|
|
+ <view class="row">
|
|
|
+ <text class="label">身高(cm)</text>
|
|
|
+ <view class="inputs">
|
|
|
+ <view class="input-group">
|
|
|
+ <input type="number" class="num-input" v-model.number="form.physical.heightMin" placeholder="下限 例如 140" />
|
|
|
+ </view>
|
|
|
+ <view class="input-group">
|
|
|
+ <input type="number" class="num-input" v-model.number="form.physical.heightMax" placeholder="上限 例如 200" />
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <view class="row">
|
|
|
+ <text class="label">体重(kg)</text>
|
|
|
+ <view class="inputs">
|
|
|
+ <view class="input-group">
|
|
|
+ <input type="number" class="num-input" v-model.number="form.physical.weightMin" placeholder="下限 例如 40" />
|
|
|
+ </view>
|
|
|
+ <view class="input-group">
|
|
|
+ <input type="number" class="num-input" v-model.number="form.physical.weightMax" placeholder="上限 例如 100" />
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <view class="row">
|
|
|
+ <text class="label">BMI</text>
|
|
|
+ <view class="inputs">
|
|
|
+ <view class="input-group">
|
|
|
+ <input type="number" class="num-input" v-model.number="form.bmi.min" placeholder="下限 例如 18.5" />
|
|
|
+ </view>
|
|
|
+ <view class="input-group">
|
|
|
+ <input type="number" class="num-input" v-model.number="form.bmi.max" placeholder="上限 例如 24" />
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <view class="card">
|
|
|
+ <view class="card-header">血压(mmHg)</view>
|
|
|
+ <view class="card-body">
|
|
|
+ <view class="row">
|
|
|
+ <text class="label">收缩压(高压)</text>
|
|
|
+ <view class="inputs">
|
|
|
+ <view class="input-group">
|
|
|
+ <input type="number" class="num-input" v-model.number="form.bloodPressure.systolicMin" placeholder="下限 例如 90" />
|
|
|
+ </view>
|
|
|
+ <view class="input-group">
|
|
|
+ <input type="number" class="num-input" v-model.number="form.bloodPressure.systolicMax" placeholder="上限 例如 140" />
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <view class="row">
|
|
|
+ <text class="label">舒张压(低压)</text>
|
|
|
+ <view class="inputs">
|
|
|
+ <view class="input-group">
|
|
|
+ <input type="number" class="num-input" v-model.number="form.bloodPressure.diastolicMin" placeholder="下限 例如 60" />
|
|
|
+ </view>
|
|
|
+ <view class="input-group">
|
|
|
+ <input type="number" class="num-input" v-model.number="form.bloodPressure.diastolicMax" placeholder="上限 例如 90" />
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <view class="card">
|
|
|
+ <view class="card-header">血糖(mmol/L)</view>
|
|
|
+ <view class="card-body">
|
|
|
+ <view class="row">
|
|
|
+ <text class="label">空腹</text>
|
|
|
+ <view class="inputs">
|
|
|
+ <view class="input-group">
|
|
|
+ <input type="number" class="num-input" step="0.1" v-model.number="form.bloodGlucose.fastingMin" placeholder="下限 例如 3.9" />
|
|
|
+ </view>
|
|
|
+ <view class="input-group">
|
|
|
+ <input type="number" class="num-input" step="0.1" v-model.number="form.bloodGlucose.fastingMax" placeholder="上限 例如 7.0" />
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <view class="row">
|
|
|
+ <text class="label">随机</text>
|
|
|
+ <view class="inputs">
|
|
|
+ <view class="input-group">
|
|
|
+ <input type="number" class="num-input" step="0.1" v-model.number="form.bloodGlucose.randomMin" placeholder="下限 例如 4.4" />
|
|
|
+ </view>
|
|
|
+ <view class="input-group">
|
|
|
+ <input type="number" class="num-input" step="0.1" v-model.number="form.bloodGlucose.randomMax" placeholder="上限 例如 11.1" />
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <view class="card">
|
|
|
+ <view class="card-header">心率(次/分)</view>
|
|
|
+ <view class="card-body">
|
|
|
+ <view class="row">
|
|
|
+ <text class="label">心率</text>
|
|
|
+ <view class="inputs">
|
|
|
+ <view class="input-group">
|
|
|
+ <input type="number" class="num-input" v-model.number="form.heartRate.min" placeholder="下限 例如 50" />
|
|
|
+ </view>
|
|
|
+ <view class="input-group">
|
|
|
+ <input type="number" class="num-input" v-model.number="form.heartRate.max" placeholder="上限 例如 120" />
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 按钮已移至页面底部全局操作区 -->
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 全局操作区:仅保留保存 -->
|
|
|
+ <view class="global-actions" style="padding: 20rpx 20rpx 40rpx; background: transparent;">
|
|
|
+ <view class="actions">
|
|
|
+ <button class="btn-primary" @click="save">保存设置</button>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script setup lang="ts">
|
|
|
+import { ref, onMounted } from 'vue'
|
|
|
+import CustomNav from '@/components/custom-nav.vue'
|
|
|
+import TabBar from '@/components/tab-bar.vue'
|
|
|
+
|
|
|
+interface PhysicalCritical {
|
|
|
+ heightMin: number | null
|
|
|
+ heightMax: number | null
|
|
|
+ weightMin: number | null
|
|
|
+ weightMax: number | null
|
|
|
+}
|
|
|
+
|
|
|
+interface BloodPressureCritical {
|
|
|
+ systolicMin: number | null
|
|
|
+ systolicMax: number | null
|
|
|
+ diastolicMin: number | null
|
|
|
+ diastolicMax: number | null
|
|
|
+}
|
|
|
+
|
|
|
+interface BloodGlucoseCritical {
|
|
|
+ fastingMin: number | null
|
|
|
+ fastingMax: number | null
|
|
|
+ randomMin: number | null
|
|
|
+ randomMax: number | null
|
|
|
+}
|
|
|
+
|
|
|
+interface CriticalValues {
|
|
|
+ physical: PhysicalCritical
|
|
|
+ bmi: { min: number | null; max: number | null }
|
|
|
+ bloodPressure: BloodPressureCritical
|
|
|
+ bloodGlucose: BloodGlucoseCritical
|
|
|
+ heartRate: { min: number | null; max: number | null }
|
|
|
+}
|
|
|
+
|
|
|
+// 新的保存键,包含多模块
|
|
|
+const STORAGE_KEY = 'criticalValues_v1'
|
|
|
+const LEGACY_PHYSICAL_KEY = 'criticalValues_physical'
|
|
|
+
|
|
|
+const form = ref<CriticalValues>({
|
|
|
+ physical: { heightMin: null, heightMax: null, weightMin: null, weightMax: null },
|
|
|
+ bmi: { min: null, max: null },
|
|
|
+ bloodPressure: { systolicMin: null, systolicMax: null, diastolicMin: null, diastolicMax: null },
|
|
|
+ bloodGlucose: { fastingMin: null, fastingMax: null, randomMin: null, randomMax: null },
|
|
|
+ heartRate: { min: null, max: null }
|
|
|
+})
|
|
|
+
|
|
|
+onMounted(() => {
|
|
|
+ // 优先读取新键
|
|
|
+ const savedNew: any = uni.getStorageSync(STORAGE_KEY)
|
|
|
+ if (savedNew) {
|
|
|
+ try {
|
|
|
+ const parsed = typeof savedNew === 'string' ? JSON.parse(savedNew) : savedNew
|
|
|
+ form.value = { ...form.value, ...parsed }
|
|
|
+ } catch (e) {
|
|
|
+ // ignore
|
|
|
+ }
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ // 兼容老的 physical 键,如果存在则迁移
|
|
|
+ const savedLegacy: any = uni.getStorageSync(LEGACY_PHYSICAL_KEY)
|
|
|
+ if (savedLegacy) {
|
|
|
+ try {
|
|
|
+ const parsed = typeof savedLegacy === 'string' ? JSON.parse(savedLegacy) : savedLegacy
|
|
|
+ form.value.physical = {
|
|
|
+ heightMin: parsed.heightMin ?? null,
|
|
|
+ heightMax: parsed.heightMax ?? null,
|
|
|
+ weightMin: parsed.weightMin ?? null,
|
|
|
+ weightMax: parsed.weightMax ?? null
|
|
|
+ }
|
|
|
+ // 保存到新键以完成迁移
|
|
|
+ uni.setStorageSync(STORAGE_KEY, JSON.stringify(form.value))
|
|
|
+ } catch (e) {
|
|
|
+ // ignore
|
|
|
+ }
|
|
|
+ }
|
|
|
+})
|
|
|
+
|
|
|
+const validatePairs = (min: number | null, max: number | null, label: string) => {
|
|
|
+ if (min != null && max != null && min > max) {
|
|
|
+ return `${label}:下限不能大于上限`
|
|
|
+ }
|
|
|
+ return ''
|
|
|
+}
|
|
|
+
|
|
|
+const validateForm = (): { ok: boolean; message?: string } => {
|
|
|
+ const f = form.value
|
|
|
+ let msg = ''
|
|
|
+ msg = msg || validatePairs(f.physical.heightMin, f.physical.heightMax, '身高')
|
|
|
+ msg = msg || validatePairs(f.physical.weightMin, f.physical.weightMax, '体重')
|
|
|
+ msg = msg || validatePairs(f.bmi.min, f.bmi.max, 'BMI')
|
|
|
+ msg = msg || validatePairs(f.bloodPressure.systolicMin, f.bloodPressure.systolicMax, '收缩压(高压)')
|
|
|
+ msg = msg || validatePairs(f.bloodPressure.diastolicMin, f.bloodPressure.diastolicMax, '舒张压(低压)')
|
|
|
+ msg = msg || validatePairs(f.bloodGlucose.fastingMin, f.bloodGlucose.fastingMax, '血糖(空腹)')
|
|
|
+ msg = msg || validatePairs(f.bloodGlucose.randomMin, f.bloodGlucose.randomMax, '血糖(随机)')
|
|
|
+ msg = msg || validatePairs(f.heartRate.min, f.heartRate.max, '心率')
|
|
|
+
|
|
|
+ if (msg) return { ok: false, message: msg }
|
|
|
+ return { ok: true }
|
|
|
+}
|
|
|
+
|
|
|
+const save = () => {
|
|
|
+ const v = validateForm()
|
|
|
+ if (!v.ok) {
|
|
|
+ uni.showToast({ title: v.message || '校验失败', icon: 'none' })
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ uni.setStorageSync(STORAGE_KEY, JSON.stringify(form.value))
|
|
|
+ uni.showToast({ title: '保存成功', icon: 'success' })
|
|
|
+ } catch (e) {
|
|
|
+ uni.showToast({ title: '保存失败', icon: 'none' })
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+const resetForm = () => {
|
|
|
+ form.value = {
|
|
|
+ physical: { heightMin: null, heightMax: null, weightMin: null, weightMax: null },
|
|
|
+ bmi: { min: null, max: null },
|
|
|
+ bloodPressure: { systolicMin: null, systolicMax: null, diastolicMin: null, diastolicMax: null },
|
|
|
+ bloodGlucose: { fastingMin: null, fastingMax: null, randomMin: null, randomMax: null },
|
|
|
+ heartRate: { min: null, max: null }
|
|
|
+ }
|
|
|
+ try {
|
|
|
+ uni.removeStorageSync(STORAGE_KEY)
|
|
|
+ } catch (e) {
|
|
|
+ // ignore
|
|
|
+ }
|
|
|
+ uni.showToast({ title: '已重置', icon: 'success' })
|
|
|
+}
|
|
|
+</script>
|
|
|
+
|
|
|
+<style scoped>
|
|
|
+.page-container {
|
|
|
+ min-height: 100vh;
|
|
|
+ background-color: #f5f5f5;
|
|
|
+ padding-top: calc(var(--status-bar-height) + 44px);
|
|
|
+ box-sizing: border-box;
|
|
|
+ padding-bottom: 40rpx;
|
|
|
+}
|
|
|
+.card {
|
|
|
+ margin: 40rpx 20rpx;
|
|
|
+ background: #fff;
|
|
|
+ border-radius: 12rpx;
|
|
|
+ overflow: hidden;
|
|
|
+}
|
|
|
+.card-header {
|
|
|
+ font-size: 32rpx;
|
|
|
+ padding: 30rpx 36rpx;
|
|
|
+ border-bottom: 1rpx solid #f0f0f0;
|
|
|
+ color: #000;
|
|
|
+}
|
|
|
+.card-body {
|
|
|
+ padding: 30rpx 36rpx 40rpx;
|
|
|
+}
|
|
|
+.row {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ margin-bottom: 24rpx;
|
|
|
+}
|
|
|
+.label {
|
|
|
+ width: 220rpx; /* 增大 label 宽度以便显示更长描述 */
|
|
|
+ min-width: 160rpx;
|
|
|
+ font-size: 28rpx;
|
|
|
+ color: #333;
|
|
|
+}
|
|
|
+.inputs {
|
|
|
+ flex: 1;
|
|
|
+ display: flex;
|
|
|
+ gap: 18rpx;
|
|
|
+}
|
|
|
+.input-group {
|
|
|
+ flex: 1;
|
|
|
+}
|
|
|
+.input-label {
|
|
|
+ font-size: 24rpx;
|
|
|
+ color: #666;
|
|
|
+ margin-bottom: 8rpx;
|
|
|
+}
|
|
|
+.num-input {
|
|
|
+ width: 100%;
|
|
|
+ height: 64rpx;
|
|
|
+ border: 1rpx solid #e6e6e6;
|
|
|
+ border-radius: 10rpx;
|
|
|
+ padding: 0 18rpx;
|
|
|
+ font-size: 28rpx;
|
|
|
+ box-sizing: border-box;
|
|
|
+}
|
|
|
+.actions {
|
|
|
+ display: flex;
|
|
|
+ justify-content: flex-end;
|
|
|
+ gap: 16rpx;
|
|
|
+ margin-top: 10rpx;
|
|
|
+}
|
|
|
+.btn-primary {
|
|
|
+ background-color: #007aff;
|
|
|
+ color: #fff;
|
|
|
+ padding: 12rpx 28rpx;
|
|
|
+ border-radius: 10rpx;
|
|
|
+ font-size: 28rpx;
|
|
|
+}
|
|
|
+.btn-secondary {
|
|
|
+ background-color: #f5f5f5;
|
|
|
+ color: #333;
|
|
|
+ padding: 12rpx 28rpx;
|
|
|
+ border-radius: 10rpx;
|
|
|
+ font-size: 28rpx;
|
|
|
+}
|
|
|
+</style>
|