Kaynağa Gözat

feat(critical-values): 添加危急值管理页面及相关功能

mcbaiyun 2 hafta önce
ebeveyn
işleme
6f3167d4ba

+ 6 - 0
src/pages.json

@@ -212,6 +212,12 @@
                 "navigationBarTitleText": "健康资讯管理"
             }
         },
+		{
+			"path": "pages/doctor/manage/critical-values",
+			"style": {
+				"navigationBarTitleText": "危急值管理"
+			}
+		},
 		{
 			"path": "pages/doctor/manage/news-edit",
 			"style": {

+ 340 - 0
src/pages/doctor/manage/critical-values.vue

@@ -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>

+ 8 - 0
src/pages/doctor/manage/index.vue

@@ -13,6 +13,11 @@
           <text class="menu-text">健康资讯管理</text>
           <uni-icons class="menu-arrow" type="arrowright" size="20" color="#c0c0c0" />
         </view>
+        <view class="menu-item" @click="onItemClick('危急值管理')">
+          <image src="/static/icons/remixicon/notification-line.svg" class="menu-icon" mode="widthFix" />
+          <text class="menu-text">危急值管理</text>
+          <uni-icons class="menu-arrow" type="arrowright" size="20" color="#c0c0c0" />
+        </view>
       </view>
     </view>
   </view>
@@ -40,6 +45,9 @@ function onItemClick(type: string) {
     case '健康资讯管理':
       uni.navigateTo({ url: '/pages/doctor/manage/news' })
       break
+    case '危急值管理':
+      uni.navigateTo({ url: '/pages/doctor/manage/critical-values' })
+      break
     default:
       uni.showToast({ title: `${type} 功能正在开发中`, icon: 'none' })
   }