瀏覽代碼

feat(medicine): 新增药品信息管理和用药管理功能

- 在 pages.json 中添加药品信息管理和用药管理页面路径配置
- 实现医生端药品信息管理页面,支持药品的增删改查操作
- 实现患者端用药管理页面,支持用药记录的查看、添加、编辑和删除
- 添加药品搜索功能,支持按名称和拼音首字母搜索
- 实现扫码添加药品功能(待完善)
- 添加时间选择器,方便设置用药时间
- 设计药品选择器模态框,提升用户体验
- 添加空状态提示和浮动操作按钮
- 实现用药频率预设功能,提高录入效率
mcbaiyun 1 月之前
父節點
當前提交
1b08ed1102
共有 4 個文件被更改,包括 1527 次插入1 次删除
  1. 12 0
      src/pages.json
  2. 13 1
      src/pages/doctor/manage/index.vue
  3. 499 0
      src/pages/doctor/manage/medicine.vue
  4. 1003 0
      src/pages/patient/health/medication.vue

+ 12 - 0
src/pages.json

@@ -84,6 +84,12 @@
 				"navigationBarTitleText": "健康提醒"
 			}
 		},
+		{
+			"path": "pages/patient/health/medication",
+			"style": {
+				"navigationBarTitleText": "用药管理"
+			}
+		},
 		{
 			"path": "pages/patient/profile/index",
 			"style": {
@@ -186,6 +192,12 @@
 				"navigationBarTitleText": "复诊管理"
 			}
 		},
+		{
+			"path": "pages/doctor/manage/medicine",
+			"style": {
+				"navigationBarTitleText": "药品信息管理"
+			}
+		},
 		{
 			"path": "pages/patient/profile/infos/followup-request",
 			"style": {

+ 13 - 1
src/pages/doctor/manage/index.vue

@@ -38,7 +38,19 @@ onShow(() => {
 })
 
 function onItemClick(type: string) {
-  uni.showToast({ title: `${type} 功能正在开发中`, icon: 'none' })
+  switch (type) {
+    case '健康资讯管理':
+      uni.showToast({ title: `${type} 功能正在开发中`, icon: 'none' })
+      break
+    case '快捷问题管理':
+      uni.showToast({ title: `${type} 功能正在开发中`, icon: 'none' })
+      break
+    case '药品信息管理':
+      uni.navigateTo({ url: '/pages/doctor/manage/medicine' })
+      break
+    default:
+      uni.showToast({ title: `${type} 功能正在开发中`, icon: 'none' })
+  }
 }
 </script>
 

+ 499 - 0
src/pages/doctor/manage/medicine.vue

@@ -0,0 +1,499 @@
+<template>
+  <CustomNav title="药品信息管理" leftType="back" />
+  <view class="page-container">
+    <view class="search-container">
+      <input 
+        type="text" 
+        v-model="searchKeyword" 
+        class="search-input" 
+        placeholder="搜索药品名称或拼音首字母"
+        @input="handleSearch"
+      />
+      <button class="add-btn" @click="showAddModal">添加药品</button>
+    </view>
+
+    <view class="medicine-list">
+      <view 
+        class="medicine-item" 
+        v-for="medicine in filteredMedicines" 
+        :key="medicine.id"
+      >
+        <view class="medicine-info">
+          <text class="medicine-name">{{ medicine.name }}</text>
+          <text class="medicine-pinyin">{{ medicine.pinyinFirstLetters }}</text>
+        </view>
+        <view class="medicine-actions">
+          <button class="action-btn edit-btn" @click="editMedicine(medicine)">编辑</button>
+          <button class="action-btn delete-btn" @click="deleteMedicine(medicine.id)">删除</button>
+        </view>
+      </view>
+    </view>
+
+    <!-- 添加/编辑药品模态框 -->
+    <view class="modal" v-if="showModal">
+      <view class="modal-backdrop" @click="closeModal"></view>
+      <view class="modal-panel">
+        <view class="drag-handle"></view>
+        <view class="modal-header">
+          <text class="modal-title">{{ isEditing ? '编辑药品' : '添加药品' }}</text>
+        </view>
+
+        <view class="modal-body">
+          <view class="form-group">
+            <view class="form-row">
+              <text class="form-label">药品名称</text>
+              <input 
+                type="text" 
+                v-model="currentMedicine.name" 
+                class="form-input" 
+                placeholder="请输入药品名称"
+              />
+            </view>
+            
+            <view class="form-row">
+              <text class="form-label">拼音首字母</text>
+              <input 
+                type="text" 
+                v-model="currentMedicine.pinyinFirstLetters" 
+                class="form-input" 
+                placeholder="请输入拼音首字母,用于快速搜索"
+              />
+            </view>
+            
+            <view class="form-row">
+              <text class="form-label">条形码</text>
+              <view class="barcode-row">
+                <input 
+                  type="text" 
+                  v-model="currentMedicine.barcode" 
+                  class="form-input barcode-input" 
+                  placeholder="请输入或扫描条形码"
+                />
+                <button class="scan-btn" @click="scanBarcode">
+                  <uni-icons type="scan" size="20" color="#fff"></uni-icons>
+                </button>
+              </view>
+            </view>
+          </view>
+        </view>
+
+        <view class="modal-footer">
+          <button class="btn-secondary" @click="closeModal">取消</button>
+          <button class="btn-primary" @click="saveMedicine">{{ isEditing ? '更新' : '添加' }}</button>
+        </view>
+      </view>
+    </view>
+  </view>
+</template>
+
+<script setup lang="ts">
+import { ref, computed, onMounted } from 'vue'
+import CustomNav from '@/components/custom-nav.vue'
+
+interface Medicine {
+  id: string
+  name: string
+  pinyinFirstLetters: string
+  barcode?: string // 添加条形码字段,可选
+}
+
+// 药品列表
+const medicines = ref<Medicine[]>([
+  { id: '1', name: '阿司匹林', pinyinFirstLetters: 'asp', barcode: '6920224840072' },
+  { id: '2', name: '硝苯地平', pinyinFirstLetters: 'xbd', barcode: '6920224840089' },
+  { id: '3', name: '盐酸二甲双胍片', pinyinFirstLetters: 'ysejsg', barcode: '6920224840096' },
+  { id: '4', name: '格列美脲', pinyinFirstLetters: 'glm', barcode: '6920224840102' },
+  { id: '5', name: '瑞格列奈', pinyinFirstLetters: 'rgl', barcode: '6920224840119' },
+  { id: '6', name: '胰岛素', pinyinFirstLetters: 'yds', barcode: '6920224840126' },
+  { id: '7', name: '美托洛尔', pinyinFirstLetters: 'mtl', barcode: '6920224840133' },
+  { id: '8', name: '氨氯地平', pinyinFirstLetters: 'ald', barcode: '6920224840140' },
+  { id: '9', name: '辛伐他汀', pinyinFirstLetters: 'xfd', barcode: '6920224840157' },
+  { id: '10', name: '阿托伐他汀', pinyinFirstLetters: 'atf', barcode: '6920224840164' }
+])
+
+// 搜索关键词
+const searchKeyword = ref('')
+
+// 模态框控制
+const showModal = ref(false)
+const isEditing = ref(false)
+
+// 当前编辑的药品
+const currentMedicine = ref<Medicine>({
+  id: '',
+  name: '',
+  pinyinFirstLetters: ''
+})
+
+// 过滤后的药品列表
+const filteredMedicines = computed(() => {
+  if (!searchKeyword.value.trim()) {
+    return medicines.value
+  }
+  
+  const keyword = searchKeyword.value.toLowerCase()
+  return medicines.value.filter(medicine => 
+    medicine.name.toLowerCase().includes(keyword) || 
+    medicine.pinyinFirstLetters?.toLowerCase().includes(keyword)
+  )
+})
+
+// 处理搜索
+const handleSearch = () => {
+  // 实际逻辑在 computed 属性中处理
+}
+
+// 显示添加模态框
+const showAddModal = () => {
+  isEditing.value = false
+  currentMedicine.value = {
+    id: '',
+    name: '',
+    pinyinFirstLetters: '',
+    barcode: ''
+  }
+  showModal.value = true
+}
+
+// 编辑药品
+const editMedicine = (medicine: Medicine) => {
+  isEditing.value = true
+  currentMedicine.value = { ...medicine }
+  showModal.value = true
+}
+
+// 删除药品
+const deleteMedicine = (id: string) => {
+  uni.showModal({
+    title: '确认删除',
+    content: '确定要删除这个药品吗?',
+    success: (res) => {
+      if (res.confirm) {
+        const index = medicines.value.findIndex(m => m.id === id)
+        if (index !== -1) {
+          medicines.value.splice(index, 1)
+          uni.showToast({
+            title: '删除成功',
+            icon: 'success'
+          })
+        }
+      }
+    }
+  })
+}
+
+// 保存药品(添加或更新)
+const saveMedicine = () => {
+  if (!currentMedicine.value.name.trim()) {
+    uni.showToast({
+      title: '请输入药品名称',
+      icon: 'none'
+    })
+    return
+  }
+
+  if (!currentMedicine.value.pinyinFirstLetters?.trim()) {
+    uni.showToast({
+      title: '请输入拼音首字母',
+      icon: 'none'
+    })
+    return
+  }
+
+  if (isEditing.value) {
+    // 更新药品
+    const index = medicines.value.findIndex(m => m.id === currentMedicine.value.id)
+    if (index !== -1) {
+      medicines.value[index] = { ...currentMedicine.value }
+      uni.showToast({
+        title: '更新成功',
+        icon: 'success'
+      })
+    }
+  } else {
+    // 添加药品
+    const newMedicine: Medicine = {
+      id: Date.now().toString(),
+      name: currentMedicine.value.name.trim(),
+      pinyinFirstLetters: currentMedicine.value.pinyinFirstLetters?.trim().toLowerCase(),
+      barcode: currentMedicine.value.barcode?.trim()
+    }
+    medicines.value.push(newMedicine)
+    uni.showToast({
+      title: '添加成功',
+      icon: 'success'
+    })
+  }
+
+  closeModal()
+}
+
+// 关闭模态框
+const closeModal = () => {
+  showModal.value = false
+}
+
+// 扫描条形码
+const scanBarcode = () => {
+  // 检查是否支持扫码功能
+  // #ifdef H5
+  uni.showToast({
+    title: '当前环境不支持扫码',
+    icon: 'none'
+  })
+  // #endif
+  
+  // #ifndef H5
+  uni.scanCode({
+    success: (res) => {
+      currentMedicine.value.barcode = res.result
+      uni.showToast({
+        title: '扫描成功',
+        icon: 'success'
+      })
+    },
+    fail: (err) => {
+      console.error('扫描失败', err)
+      uni.showToast({
+        title: '扫描失败,请重试',
+        icon: 'none'
+      })
+    }
+  })
+  // #endif
+}
+
+// 页面加载时
+onMounted(() => {
+  // 可以在这里从服务器获取药品列表数据
+})
+</script>
+
+<style scoped>
+.page-container {
+  min-height: 100vh;
+  background-color: #f5f5f5;
+  padding-top: calc(var(--status-bar-height) + 44px);
+  padding-bottom: 40rpx;
+}
+
+.search-container {
+  display: flex;
+  padding: 20rpx;
+  gap: 20rpx;
+  background-color: #fff;
+  margin-bottom: 20rpx;
+}
+
+.search-input {
+  flex: 1;
+  padding: 16rpx 20rpx;
+  border-radius: 10rpx;
+  border: 1rpx solid #eee;
+  font-size: 28rpx;
+}
+
+.add-btn {
+  padding: 16rpx 24rpx;
+  background-color: #3742fa;
+  color: #fff;
+  border-radius: 10rpx;
+  font-size: 28rpx;
+  white-space: nowrap;
+}
+
+.medicine-list {
+  padding: 0 20rpx;
+}
+
+.medicine-item {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  padding: 30rpx;
+  background-color: #fff;
+  border-radius: 16rpx;
+  margin-bottom: 20rpx;
+  box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.1);
+}
+
+.medicine-info {
+  display: flex;
+  flex-direction: column;
+  flex: 1;
+}
+
+.medicine-name {
+  font-size: 32rpx;
+  color: #333;
+  margin-bottom: 10rpx;
+}
+
+.medicine-pinyin {
+  font-size: 24rpx;
+  color: #999;
+}
+
+.medicine-actions {
+  display: flex;
+  gap: 20rpx;
+}
+
+.action-btn {
+  padding: 12rpx 24rpx;
+  border-radius: 8rpx;
+  font-size: 24rpx;
+}
+
+.edit-btn {
+  background-color: #3742fa;
+  color: #fff;
+}
+
+.delete-btn {
+  background-color: #fff;
+  color: #ff4757;
+  border: 1rpx solid #ff4757;
+}
+
+/* Modal styles */
+.modal {
+  position: fixed;
+  left: 0;
+  right: 0;
+  top: 0;
+  bottom: 0;
+  display: flex;
+  align-items: flex-end;
+  justify-content: center;
+  z-index: 1300;
+}
+
+.modal-backdrop {
+  position: absolute;
+  left: 0;
+  right: 0;
+  top: 0;
+  bottom: 0;
+  background: rgba(0, 0, 0, 0.4);
+}
+
+.modal-panel {
+  position: relative;
+  width: 100%;
+  background: #fff;
+  border-top-left-radius: 18rpx;
+  border-top-right-radius: 18rpx;
+  padding: 28rpx 24rpx 40rpx 24rpx;
+  box-shadow: 0 -8rpx 30rpx rgba(0, 0, 0, 0.12);
+}
+
+.drag-handle {
+  width: 64rpx;
+  height: 6rpx;
+  background: rgba(0, 0, 0, 0.08);
+  border-radius: 999px;
+  margin: 10rpx auto 14rpx auto;
+}
+
+.modal-header {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  gap: 12rpx;
+  margin-bottom: 30rpx;
+}
+
+.modal-title {
+  font-size: 36rpx;
+  color: #222;
+  font-weight: 700;
+  letter-spacing: 1rpx;
+}
+
+.modal-body {
+  padding: 0 20rpx;
+  margin-bottom: 30rpx;
+}
+
+.form-group {
+  background-color: #f9f9f9;
+  border-radius: 16rpx;
+  padding: 20rpx;
+}
+
+.form-row {
+  display: flex;
+  flex-direction: column;
+  margin-bottom: 24rpx;
+}
+
+.form-row:last-child {
+  margin-bottom: 0;
+}
+
+.form-label {
+  font-size: 28rpx;
+  color: #666;
+  margin-bottom: 12rpx;
+  align-self: flex-start;
+}
+
+.form-input {
+  width: 100%;
+  text-align: left;
+  padding: 16rpx;
+  border-radius: 14rpx;
+  border: 1rpx solid #eee;
+  background: #fff;
+  font-size: 28rpx;
+}
+
+.barcode-row {
+  display: flex;
+  gap: 10rpx;
+}
+
+.barcode-input {
+  flex: 1;
+}
+
+.scan-btn {
+  width: 80rpx;
+  height: 70rpx;
+  background-color: #3742fa;
+  border-radius: 14rpx;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+.modal-footer {
+  display: flex;
+  justify-content: space-between;
+  padding: 0 24rpx;
+}
+
+.btn-primary {
+  background: #ff6a00;
+  color: #fff;
+  padding: 18rpx 22rpx;
+  border-radius: 16rpx;
+  text-align: center;
+  flex: 1;
+  margin: 0 10rpx;
+  box-shadow: 0 10rpx 28rpx rgba(255, 106, 0, 0.18);
+  font-size: 30rpx;
+}
+
+.btn-secondary {
+  background: #f0f0f0;
+  color: #666;
+  padding: 18rpx 22rpx;
+  border-radius: 16rpx;
+  text-align: center;
+  flex: 1;
+  margin: 0 10rpx;
+  font-size: 30rpx;
+}
+</style>

+ 1003 - 0
src/pages/patient/health/medication.vue

@@ -0,0 +1,1003 @@
+<template>
+  <CustomNav title="用药管理" leftType="back" />
+  <view class="page-container">
+    <view class="medication-list" v-if="medications.length > 0">
+      <view
+        class="medication-card"
+        v-for="medication in medications"
+        :key="medication.id"
+      >
+        <view class="card-header">
+          <view class="medication-info">
+            <text class="medication-name">{{ medication.name }}</text>
+          </view>
+        </view>
+
+        <view class="card-body">
+          <view class="info-row">
+            <text class="label">剂量:</text>
+            <text class="value">{{ medication.dosage }}</text>
+          </view>
+          <view class="info-row">
+            <text class="label">频率:</text>
+            <text class="value">{{ medication.frequency }}</text>
+          </view>
+          <view class="info-row">
+            <text class="label">时间:</text>
+            <text class="value">{{ medication.time }}</text>
+          </view>
+          <view class="info-row">
+            <text class="label">备注:</text>
+            <text class="value">{{ medication.note || '无' }}</text>
+          </view>
+        </view>
+
+        <view class="card-footer">
+          <button class="action-btn edit" @click="editMedication(medication.id)">编辑</button>
+          <button class="action-btn delete" @click="deleteMedication(medication.id)">删除</button>
+        </view>
+      </view>
+    </view>
+
+    <view class="empty-state" v-else>
+      <image class="empty-icon" src="/static/icons/remixicon/capsule-fill.svg" />
+      <text class="empty-text">暂无用药记录</text>
+      <text class="empty-hint">点击右下角 + 添加</text>
+    </view>
+
+    <view class="fab-container">
+      <view class="scan-fab" @click="scanMedication">
+        <view class="fab-inner">
+          <uni-icons type="scan" size="24" color="#fff"></uni-icons>
+        </view>
+      </view>
+      <view class="fab" @click="openAdd">
+        <view class="fab-inner">+</view>
+      </view>
+    </view>
+
+    <view class="modal" v-if="showAdd">
+      <view class="modal-backdrop" @click="closeAdd"></view>
+      <view class="modal-panel add-medication-modal">
+        <view class="drag-handle"></view>
+        <view class="modal-header">
+          <text class="modal-title">添加用药</text>
+        </view>
+
+        <view class="modal-inner">
+          <view class="form-group">
+            <view class="form-row">
+              <text class="form-label">药品名称</text>
+              <view class="medicine-selector" @click="showMedicinePicker = true">
+                <text class="selector-text" :class="{ placeholder: !selectedMedicine }">{{ selectedMedicine ? selectedMedicine.name : '请选择药品' }}</text>
+                <uni-icons type="arrowdown" size="16" color="#999" />
+              </view>
+            </view>
+
+            <view class="form-row">
+              <text class="form-label">剂量规格</text>
+              <input type="text" v-model="addDosage" class="form-input" placeholder="例如:0.85g" />
+            </view>
+
+            <view class="form-row">
+              <text class="form-label">用法用量</text>
+              <view class="dosage-selector">
+                <view class="dosage-row">
+                  <text class="dosage-label">每日</text>
+                  <input type="number" v-model.number="dailyTimes" class="small-input" />
+                  <text class="dosage-label">次,每次</text>
+                  <input type="number" v-model.number="eachTime" class="small-input" />
+                  <text class="dosage-label">粒</text>
+                </view>
+              </view>
+            </view>
+
+            <view class="form-row">
+              <text class="form-label">服用时间</text>
+              <view class="time-list">
+                <view 
+                  class="time-item" 
+                  v-for="(time, index) in addTimes" 
+                  :key="index"
+                >
+                  <text class="time-text">{{ time }}</text>
+                  <view class="delete-time" @click="removeTime(index)">
+                    <uni-icons type="closeempty" size="16" color="#ff4757"></uni-icons>
+                  </view>
+                </view>
+                <view class="add-time-btn" @click="showTimePicker = true">
+                  <uni-icons type="plusempty" size="20" color="#3742fa"></uni-icons>
+                  <text class="add-time-text">添加时间点</text>
+                </view>
+              </view>
+            </view>
+
+            <view class="form-row">
+              <text class="form-label">备注</text>
+              <input type="text" v-model="addNote" class="form-input" placeholder="可选备注信息" />
+            </view>
+          </view>
+        </view>
+
+        <view class="modal-footer">
+          <button class="btn-secondary" @click="closeAdd">取消</button>
+          <button class="btn-primary" @click="confirmAdd">保存</button>
+        </view>
+      </view>
+    </view>
+
+    <!-- 药品选择器模态框 -->
+    <view class="modal" v-if="showMedicinePicker">
+      <view class="modal-backdrop" @click="closeMedicinePicker"></view>
+      <view class="modal-panel medicine-picker-panel">
+        <view class="drag-handle"></view>
+        <view class="modal-header">
+          <text class="modal-title">选择药品</text>
+        </view>
+
+        <view class="search-container">
+          <input type="text" v-model="medicineSearch" class="search-input" placeholder="搜索药品(支持拼音首字母)" @input="onMedicineSearch" />
+        </view>
+
+        <scroll-view class="medicine-list" scroll-y="true">
+          <view
+            class="medicine-item"
+            v-for="medicine in filteredMedicines"
+            :key="medicine.id"
+            @click="selectMedicine(medicine)"
+          >
+            <text class="medicine-name">{{ medicine.name }}</text>
+            <text class="medicine-pinyin">{{ medicine.pinyin }}</text>
+          </view>
+        </scroll-view>
+      </view>
+    </view>
+
+    <!-- 时间选择器模态框 -->
+    <view class="modal" v-if="showTimePicker">
+      <view class="modal-backdrop" @click="closeTimePicker"></view>
+      <view class="modal-panel time-picker-panel">
+        <view class="drag-handle"></view>
+        <view class="modal-header">
+          <text class="modal-title">选择时间</text>
+        </view>
+
+        <view class="time-picker-container">
+          <view class="time-picker-row">
+            <view class="time-unit">
+              <text class="time-unit-label">时</text>
+              <picker mode="selector" :range="hoursRange" @change="onHourChange">
+                <view class="time-value">{{ selectedHour }}</view>
+              </picker>
+            </view>
+            
+            <view class="time-unit">
+              <text class="time-unit-label">分</text>
+              <picker mode="selector" :range="minutesRange" @change="onMinuteChange">
+                <view class="time-value">{{ selectedMinute }}</view>
+              </picker>
+            </view>
+          </view>
+        </view>
+
+        <view class="modal-footer">
+          <button class="btn-secondary" @click="closeTimePicker">取消</button>
+          <button class="btn-primary" @click="confirmTime">确定</button>
+        </view>
+      </view>
+    </view>
+  </view>
+</template>
+
+<script setup lang="ts">
+import { ref, computed, onMounted, watch } from 'vue'
+import CustomNav from '@/components/custom-nav.vue'
+
+interface Medication {
+  id: string
+  name: string
+  dosage: string
+  frequency: string
+  time: string
+  note?: string
+}
+
+interface Medicine {
+  id: string
+  name: string
+  pinyin: string
+}
+
+const medications = ref<Medication[]>([
+  {
+    id: '1',
+    name: '阿司匹林',
+    dosage: '100mg',
+    frequency: '每日1次,每次1粒',
+    time: '08:00',
+    note: '饭后服用'
+  },
+  {
+    id: '2',
+    name: '硝苯地平',
+    dosage: '10mg',
+    frequency: '每日3次,每次1粒',
+    time: '07:00, 15:00, 21:00'
+  }
+])
+
+// 模态框控制
+const showAdd = ref(false)
+const showMedicinePicker = ref(false)
+
+// 添加用药表单数据
+const selectedMedicine = ref<Medicine | null>(null)
+const addDosage = ref('')
+const dailyTimes = ref(1)
+const eachTime = ref(1)
+const addTimes = ref<string[]>([])
+const addNote = ref('')
+
+// 预设时间配置
+const presetTimes: Record<number, string[]> = {
+  1: ['08:00'],
+  2: ['08:00', '20:00'],
+  3: ['07:00', '15:00', '21:00']
+}
+
+// 时间选择器相关
+const showTimePicker = ref(false)
+const selectedHour = ref('08')
+const selectedMinute = ref('00')
+const hoursRange = Array.from({length: 24}, (_, i) => i.toString().padStart(2, '0'))
+const minutesRange = ['00', '30']
+
+// 药品搜索
+const medicineSearch = ref('')
+const allMedicines = ref<Medicine[]>([
+  { id: '1', name: '阿司匹林', pinyin: 'aspirin' },
+  { id: '2', name: '硝苯地平', pinyin: 'xiaobendiping' },
+  { id: '3', name: '盐酸二甲双胍片', pinyin: 'yansuanerjiashuanggua' },
+  { id: '4', name: '格列美脲', pinyin: 'gelimeiwo' },
+  { id: '5', name: '瑞格列奈', pinyin: 'ruigelienai' },
+  { id: '6', name: '胰岛素', pinyin: 'yidaosu' },
+  { id: '7', name: '美托洛尔', pinyin: 'meituoluoer' },
+  { id: '8', name: '氨氯地平', pinyin: 'anlviping' },
+  { id: '9', name: '辛伐他汀', pinyin: 'xinfatating' },
+  { id: '10', name: '阿托伐他汀', pinyin: 'atuofatating' }
+])
+
+// 过滤后的药品列表
+const filteredMedicines = computed(() => {
+  if (!medicineSearch.value.trim()) {
+    return allMedicines.value
+  }
+  const search = medicineSearch.value.toLowerCase()
+  return allMedicines.value.filter(medicine =>
+    medicine.name.includes(search) ||
+    medicine.pinyin.toLowerCase().includes(search) ||
+    medicine.pinyin.toLowerCase().startsWith(search)
+  )
+})
+
+// 获取状态文本
+// const getStatusText = (status: string) => {
+//   switch (status) {
+//     case 'active': return '进行中'
+//     case 'inactive': return '已停用'
+//     default: return status
+//   }
+// }
+
+// 打开添加模态框
+const openAdd = () => {
+  showAdd.value = true
+}
+
+// 打开时间选择器
+const openTimePicker = () => {
+  showTimePicker.value = true
+}
+
+// 关闭时间选择器
+const closeTimePicker = () => {
+  showTimePicker.value = false
+}
+
+// 小时改变
+const onHourChange = (e: any) => {
+  selectedHour.value = hoursRange[e.detail.value]
+}
+
+// 分钟改变
+const onMinuteChange = (e: any) => {
+  selectedMinute.value = minutesRange[e.detail.value]
+}
+
+// 确认选择时间
+const confirmTime = () => {
+  const time = `${selectedHour.value}:${selectedMinute.value}`
+  // 检查是否已存在该时间点
+  if (!addTimes.value.includes(time)) {
+    addTimes.value.push(time)
+    // 按时间排序
+    addTimes.value.sort()
+  }
+  closeTimePicker()
+}
+
+// 删除时间点
+const removeTime = (index: number) => {
+  addTimes.value.splice(index, 1)
+}
+
+// 关闭添加模态框
+const closeAdd = () => {
+  showAdd.value = false
+  // 重置表单
+  selectedMedicine.value = null
+  addDosage.value = ''
+  dailyTimes.value = 1
+  eachTime.value = 1
+  addTimes.value = []
+  addNote.value = ''
+}
+
+// 打开药品选择器
+const openMedicinePicker = () => {
+  showMedicinePicker.value = true
+}
+
+// 关闭药品选择器
+const closeMedicinePicker = () => {
+  showMedicinePicker.value = false
+  medicineSearch.value = ''
+}
+
+// 药品搜索
+const onMedicineSearch = () => {
+  // 搜索逻辑在 computed 中处理
+}
+
+// 选择药品
+const selectMedicine = (medicine: Medicine) => {
+  selectedMedicine.value = medicine
+  closeMedicinePicker()
+}
+
+// 确认添加用药
+const confirmAdd = () => {
+  if (!selectedMedicine.value) {
+    uni.showToast({
+      title: '请选择药品',
+      icon: 'none'
+    })
+    return
+  }
+
+  if (!addDosage.value.trim()) {
+    uni.showToast({
+      title: '请输入剂量规格',
+      icon: 'none'
+    })
+    return
+  }
+
+  if (addTimes.value.length === 0) {
+    uni.showToast({
+      title: '请添加至少一个服用时间',
+      icon: 'none'
+    })
+    return
+  }
+
+  const frequency = `每日${dailyTimes.value}次,每次${eachTime.value}粒`
+  const timeStr = addTimes.value.join(', ')
+
+  const newMedication: Medication = {
+    id: Date.now().toString(),
+    name: selectedMedicine.value.name,
+    dosage: addDosage.value.trim(),
+    frequency: frequency,
+    time: timeStr,
+    note: addNote.value.trim() || undefined
+  }
+
+  medications.value.unshift(newMedication)
+  uni.showToast({
+    title: '添加成功',
+    icon: 'success'
+  })
+
+  closeAdd()
+}
+
+// 扫码录入药品
+const scanMedication = () => {
+  uni.showToast({
+    title: '功能开发中',
+    icon: 'none'
+  })
+}
+
+// 编辑用药
+const editMedication = (id: string) => {
+  const medication = medications.value.find(m => m.id === id)
+  if (medication) {
+    // 加载选中药物的信息到编辑表单
+    selectedMedicine.value = {
+      id: '',
+      name: medication.name,
+      pinyin: ''
+    }
+    
+    addDosage.value = medication.dosage
+    
+    // 解析频率信息
+    const frequencyMatch = medication.frequency.match(/每日(\d+)次,每次(\d+)粒/)
+    if (frequencyMatch) {
+      dailyTimes.value = parseInt(frequencyMatch[1])
+      eachTime.value = parseInt(frequencyMatch[2])
+    }
+    
+    // 解析时间信息
+    addTimes.value = medication.time.split(', ').filter(t => t.trim() !== '')
+    
+    // 加载备注
+    addNote.value = medication.note || ''
+    
+    // 显示编辑模态框
+    showAdd.value = true
+  }
+}
+
+// 删除用药
+const deleteMedication = (id: string) => {
+  uni.showModal({
+    title: '确认删除',
+    content: '确定要删除这个用药记录吗?',
+    success: (res) => {
+      if (res.confirm) {
+        const index = medications.value.findIndex(m => m.id === id)
+        if (index !== -1) {
+          medications.value.splice(index, 1)
+          uni.showToast({ title: '删除成功', icon: 'success' })
+        }
+      }
+    }
+  })
+}
+
+// 监听每日次数变化,自动设置预设时间
+watch(dailyTimes, (newVal) => {
+  if (presetTimes[newVal]) {
+    addTimes.value = [...presetTimes[newVal]]
+  } else {
+    // 如果没有预设,则清空时间列表
+    addTimes.value = []
+  }
+})
+
+onMounted(() => {
+  // 这里可以调用API获取用药数据
+})
+</script>
+
+<style scoped>
+.page-container {
+  min-height: 100vh;
+  background-color: #f5f5f5;
+  padding-top: calc(var(--status-bar-height) + 44px);
+  padding-bottom: 40rpx;
+}
+
+.add-button-container {
+  padding: 20rpx;
+}
+
+.add-btn {
+  width: 100%;
+  height: 80rpx;
+  background-color: #3742fa;
+  color: #fff;
+  border-radius: 10rpx;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  font-size: 32rpx;
+}
+
+.add-btn uni-icons {
+  margin-right: 10rpx;
+}
+
+.medication-list {
+  padding: 20rpx;
+}
+
+.medication-card {
+  background-color: #fff;
+  border-radius: 20rpx;
+  margin-bottom: 20rpx;
+  box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.1);
+  overflow: hidden;
+}
+
+.card-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  padding: 30rpx;
+  border-bottom: 1rpx solid #eee;
+}
+
+.medication-info {
+  display: flex;
+  flex-direction: column;
+}
+
+.medication-name {
+  font-size: 32rpx;
+  font-weight: bold;
+  color: #333;
+  margin-bottom: 10rpx;
+}
+
+.medication-time {
+  font-size: 24rpx;
+  color: #999;
+}
+
+.status-badge {
+  padding: 8rpx 16rpx;
+  border-radius: 10rpx;
+  font-size: 24rpx;
+  color: #fff;
+}
+
+.status-badge.active {
+  background-color: #2ed573;
+}
+
+.status-badge.inactive {
+  background-color: #ccc;
+}
+
+.card-body {
+  padding: 30rpx;
+  border-bottom: 1rpx solid #eee;
+}
+
+.info-row {
+  display: flex;
+  margin-bottom: 20rpx;
+}
+
+.info-row:last-child {
+  margin-bottom: 0;
+}
+
+.label {
+  font-size: 28rpx;
+  color: #666;
+  width: 120rpx;
+}
+
+.value {
+  flex: 1;
+  font-size: 28rpx;
+  color: #333;
+}
+
+.card-footer {
+  display: flex;
+  padding: 30rpx;
+  justify-content: flex-end;
+}
+
+.action-btn {
+  padding: 0 30rpx;
+  height: 60rpx;
+  line-height: 60rpx;
+  border-radius: 10rpx;
+  font-size: 28rpx;
+  margin-left: 20rpx;
+}
+
+.edit {
+  background-color: #3742fa;
+  color: #fff;
+}
+
+.delete {
+  background-color: #fff;
+  color: #ff4757;
+  border: 1rpx solid #ff4757;
+}
+
+.empty-state {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  padding: 100rpx 40rpx;
+}
+
+.empty-icon {
+  width: 120rpx;
+  height: 120rpx;
+  margin-bottom: 30rpx;
+  opacity: 0.5;
+}
+
+.empty-text {
+  font-size: 32rpx;
+  color: #666;
+  margin-bottom: 20rpx;
+}
+
+.empty-hint {
+  font-size: 26rpx;
+  color: #999;
+}
+
+.scan-fab {
+  position: fixed;
+  right: 28rpx;
+  bottom: 240rpx;
+  width: 110rpx;
+  height: 110rpx;
+  border-radius: 999px;
+  background: linear-gradient(180deg, #00b4d8, #0077b6);
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  box-shadow: 0 6rpx 18rpx rgba(0, 0, 0, 0.2);
+  z-index: 1200
+}
+
+.fab {
+  position: fixed;
+  right: 28rpx;
+  bottom: 100rpx;
+  width: 110rpx;
+  height: 110rpx;
+  border-radius: 999px;
+  background: linear-gradient(180deg, #ff7a00, #ff4a00);
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  box-shadow: 0 6rpx 18rpx rgba(0, 0, 0, 0.2);
+  z-index: 1200
+}
+
+.fab-container {
+  display: flex;
+  flex-direction: column;
+  gap: 30rpx;
+}
+
+.fab-inner {
+  color: #fff;
+  font-size: 56rpx;
+  line-height: 56rpx
+}
+
+.modal {
+  position: fixed;
+  left: 0;
+  right: 0;
+  top: 0;
+  bottom: 0;
+  display: flex;
+  align-items: flex-end;
+  justify-content: center;
+  z-index: 1300
+}
+
+.modal-backdrop {
+  position: absolute;
+  left: 0;
+  right: 0;
+  top: 0;
+  bottom: 0;
+  background: rgba(0, 0, 0, 0.4)
+}
+
+.modal-panel {
+  position: relative;
+  width: 100%;
+  background: #fff;
+  border-top-left-radius: 18rpx;
+  border-top-right-radius: 18rpx;
+  padding: 28rpx 24rpx 40rpx 24rpx;
+  box-shadow: 0 -8rpx 30rpx rgba(0,0,0,0.12)
+}
+
+.add-medication-modal {
+  padding-bottom: 40rpx;
+}
+
+.drag-handle {
+  width: 64rpx;
+  height: 6rpx;
+  background: rgba(0,0,0,0.08);
+  border-radius: 999px;
+  margin: 10rpx auto 14rpx auto
+}
+
+.modal-header {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  gap: 12rpx;
+  margin-bottom: 30rpx;
+}
+
+.modal-title {
+  font-size: 36rpx;
+  color: #222;
+  font-weight: 700;
+  letter-spacing: 1rpx
+}
+
+.modal-inner {
+  max-width: 100%;
+  margin: 0 auto;
+  padding: 0 20rpx;
+}
+
+.form-group {
+  background-color: #f9f9f9;
+  border-radius: 16rpx;
+  padding: 20rpx;
+  margin-bottom: 20rpx;
+}
+
+.form-row {
+  display: flex;
+  flex-direction: column;
+  margin-bottom: 24rpx;
+}
+
+.form-row:last-child {
+  margin-bottom: 0;
+}
+
+.form-label {
+  font-size: 28rpx;
+  color: #666;
+  margin-bottom: 12rpx;
+  align-self: flex-start;
+}
+
+.form-input {
+  width: 100%;
+  text-align: left;
+  padding: 16rpx;
+  border-radius: 14rpx;
+  border: 1rpx solid #eee;
+  background: #fff;
+  font-size: 28rpx;
+}
+
+.picker-display {
+  color: #333;
+  padding: 16rpx;
+  border-radius: 14rpx;
+  border: 1rpx solid #eee;
+  background: #fff7f0;
+  min-width: 200rpx;
+  text-align: left
+}
+
+.btn-primary {
+  background: #ff6a00;
+  color: #fff;
+  padding: 18rpx 22rpx;
+  border-radius: 16rpx;
+  text-align: center;
+  flex: 1;
+  margin: 0 10rpx;
+  box-shadow: 0 10rpx 28rpx rgba(255,106,0,0.18);
+  font-size: 30rpx;
+}
+
+.btn-secondary {
+  background: #f0f0f0;
+  color: #666;
+  padding: 18rpx 22rpx;
+  border-radius: 16rpx;
+  text-align: center;
+  flex: 1;
+  margin: 0 10rpx;
+  font-size: 30rpx;
+}
+
+.modal-footer {
+  display: flex;
+  justify-content: space-between;
+  padding: 0 24rpx;
+  margin-top: 20rpx;
+}
+
+.label {
+  color: #666
+}
+
+.medicine-selector {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  padding: 16rpx;
+  border-radius: 14rpx;
+  border: 1rpx solid #eee;
+  background: #fff;
+  width: 100%;
+}
+
+.selector-text {
+  color: #333;
+  flex: 1;
+}
+
+.selector-text.placeholder {
+  color: #999;
+}
+
+.dosage-selector {
+  width: 100%;
+}
+
+.dosage-row {
+  display: flex;
+  align-items: center;
+  gap: 16rpx;
+  padding: 16rpx;
+  border-radius: 14rpx;
+  border: 1rpx solid #eee;
+  background: #fff;
+}
+
+.dosage-label {
+  font-size: 28rpx;
+  color: #333;
+}
+
+.small-input {
+  width: 80rpx;
+  text-align: center;
+  padding: 8rpx;
+  border-radius: 8rpx;
+  border: 1rpx solid #eee;
+  background: #fff;
+  margin: 0 4rpx;
+}
+
+.medicine-picker-panel {
+  height: 600rpx;
+}
+
+.search-container {
+  padding: 0 24rpx 20rpx 24rpx;
+}
+
+.search-input {
+  width: 100%;
+  padding: 16rpx;
+  border-radius: 14rpx;
+  border: 1rpx solid #eee;
+  background: #fff7f0;
+  font-size: 28rpx;
+}
+
+.medicine-list {
+  max-height: 400rpx;
+  padding: 0 24rpx;
+}
+
+.medicine-item {
+  padding: 20rpx 16rpx;
+  border-bottom: 1rpx solid #f0f0f0;
+  display: flex;
+  flex-direction: column;
+  gap: 8rpx;
+}
+
+.medicine-item:last-child {
+  border-bottom: none;
+}
+
+.medicine-name {
+  font-size: 32rpx;
+  color: #333;
+}
+
+.medicine-pinyin {
+  font-size: 24rpx;
+  color: #999;
+}
+
+.time-list {
+  width: 100%;
+  background: #fff;
+  border-radius: 14rpx;
+  border: 1rpx solid #eee;
+  padding: 16rpx;
+}
+
+.time-item {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  padding: 12rpx 16rpx;
+  background-color: #f0f5ff;
+  border-radius: 10rpx;
+  margin-bottom: 12rpx;
+}
+
+.time-item:last-child {
+  margin-bottom: 0;
+}
+
+.time-text {
+  font-size: 28rpx;
+  color: #333;
+}
+
+.delete-time {
+  padding: 8rpx;
+}
+
+.add-time-btn {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  padding: 20rpx;
+  border: 1rpx dashed #3742fa;
+  border-radius: 10rpx;
+  margin-top: 12rpx;
+}
+
+.add-time-text {
+  color: #3742fa;
+  font-size: 28rpx;
+  margin-left: 10rpx;
+}
+
+.time-picker-panel {
+  height: 400rpx;
+}
+
+.time-picker-container {
+  padding: 40rpx 24rpx;
+}
+
+.time-picker-row {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  gap: 60rpx;
+  margin-bottom: 40rpx;
+}
+
+.time-unit {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+}
+
+.time-unit-label {
+  font-size: 24rpx;
+  color: #666;
+  margin-bottom: 20rpx;
+}
+
+.time-value {
+  font-size: 48rpx;
+  color: #333;
+  padding: 10rpx 20rpx;
+  background-color: #f5f5f5;
+  border-radius: 10rpx;
+}
+</style>