Quellcode durchsuchen

feat(medicine): 实现药品管理功能

- 新增药品信息实体定义及API接口
- 实现药品增删改查功能
- 药品列表支持分页和搜索
- 药品ID使用字符串类型避免精度丢失
- 更新药品管理页面交互逻辑
- 优化药品选择器搜索功能
mcbaiyun vor 1 Monat
Ursprung
Commit
fe5ae2d570
3 geänderte Dateien mit 327 neuen und 61 gelöschten Zeilen
  1. 177 0
      src/api/medicine.ts
  2. 89 42
      src/pages/doctor/manage/medicine.vue
  3. 61 19
      src/pages/patient/health/medication.vue

+ 177 - 0
src/api/medicine.ts

@@ -0,0 +1,177 @@
+import request from './request'
+
+// 药品信息实体
+export interface Medicine {
+  id: string // Snowflake 64位ID 使用字符串保存以避免 JS Number 精度丢失
+  name: string // 药品名称
+  pinyinFirstLetters: string // 拼音首字母,用于快速搜索
+  barcode?: string // 条形码
+  createTime: string // 创建时间
+  updateTime: string // 更新时间
+}
+
+// 分页查询参数
+export interface MedicineQueryParams {
+  pageNum?: number
+  pageSize?: number
+  startTime?: string
+  endTime?: string
+  keyword?: string // 搜索关键字(药品名称或拼音首字母)
+}
+
+// 创建药品请求参数
+export interface CreateMedicineRequest {
+  name: string
+  pinyinFirstLetters: string
+  barcode?: string
+}
+
+// 更新药品请求参数
+export interface UpdateMedicineRequest {
+  id: string | number
+  name: string
+  pinyinFirstLetters: string
+  barcode?: string
+}
+
+// 分页响应
+export interface MedicinePageResponse {
+  records: Medicine[]
+  total: number
+  size: number
+  current: number
+  orders: Array<{ column: string; asc: boolean }>
+  optimizeCountSql: boolean
+  searchCount: boolean
+  optimizeJoinOfCountSql: boolean
+  maxLimit: number
+  countId: string
+  pages: number
+}
+
+/**
+ * 创建药品信息
+ * @param payload 创建药品请求参数
+ */
+export async function createMedicine(payload: CreateMedicineRequest) {
+  const res: any = await request({
+    url: 'https://wx.baiyun.work/medicine/create',
+    method: 'POST',
+    header: { 'Content-Type': 'application/json' },
+    data: payload
+  })
+  
+  // 强制把 Snowflake ID 字段转换为字符串,避免后续使用 Number 导致精度丢失
+  try {
+    const parsed = res?.data as any
+    if (parsed && parsed.code === 200 && parsed.data) {
+      parsed.data = {
+        ...parsed.data,
+        id: String(parsed.data.id)
+      }
+      res.data = parsed
+    }
+  } catch (e) {
+    // ignore
+  }
+  
+  return res
+}
+
+/**
+ * 更新药品信息
+ * @param id 药品ID
+ * @param payload 更新药品请求参数
+ */
+export async function updateMedicine(id: string | number, payload: UpdateMedicineRequest) {
+  const res: any = await request({
+    url: `https://wx.baiyun.work/medicine/${encodeURIComponent(String(id))}`,
+    method: 'PUT',
+    header: { 'Content-Type': 'application/json' },
+    data: payload
+  })
+  
+  // 强制把 Snowflake ID 字段转换为字符串,避免后续使用 Number 导致精度丢失
+  try {
+    const parsed = res?.data as any
+    if (parsed && parsed.code === 200 && parsed.data) {
+      parsed.data = {
+        ...parsed.data,
+        id: String(parsed.data.id)
+      }
+      res.data = parsed
+    }
+  } catch (e) {
+    // ignore
+  }
+  
+  return res
+}
+
+/**
+ * 分页查询药品信息
+ * @param query 查询参数
+ */
+export async function getMedicineList(query: MedicineQueryParams) {
+  const res: any = await request({
+    url: 'https://wx.baiyun.work/medicine/list',
+    method: 'POST',
+    header: { 'Content-Type': 'application/json' },
+    data: query
+  })
+  
+  // 强制把 Snowflake ID 字段转换为字符串,避免后续使用 Number 导致精度丢失
+  try {
+    const parsed = res?.data as any
+    if (parsed && parsed.code === 200 && parsed.data && Array.isArray(parsed.data.records)) {
+      parsed.data.records = parsed.data.records.map((r: any) => ({
+        ...r,
+        id: String(r.id)
+      }))
+      res.data = parsed
+    }
+  } catch (e) {
+    // ignore
+  }
+  
+  return res
+}
+
+/**
+ * 删除药品信息
+ * @param id 药品ID
+ */
+export async function deleteMedicine(id: string | number) {
+  const res: any = await request({
+    url: `https://wx.baiyun.work/medicine/${encodeURIComponent(String(id))}`,
+    method: 'DELETE'
+  })
+  return res
+}
+
+/**
+ * 获取药品详情
+ * @param id 药品ID
+ */
+export async function getMedicineById(id: string | number) {
+  const res: any = await request({
+    url: `https://wx.baiyun.work/medicine/${encodeURIComponent(String(id))}`,
+    method: 'GET'
+  })
+  
+  // 强制把 Snowflake ID 字段转换为字符串,避免后续使用 Number 导致精度丢失
+  try {
+    const parsed = res?.data as any
+    if (parsed && parsed.code === 200 && parsed.data) {
+      parsed.data = {
+        ...parsed.data,
+        id: String(parsed.data.id)
+      }
+      res.data = parsed
+    }
+  } catch (e) {
+    // ignore
+  }
+  
+  return res
+}

+ 89 - 42
src/pages/doctor/manage/medicine.vue

@@ -24,7 +24,7 @@
         </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>
+          <button class="action-btn delete-btn" @click="deleteMedicineHandler(medicine.id)">删除</button>
         </view>
       </view>
     </view>
@@ -89,27 +89,24 @@
 <script setup lang="ts">
 import { ref, computed, onMounted } from 'vue'
 import CustomNav from '@/components/custom-nav.vue'
+import { 
+  getMedicineList, 
+  createMedicine, 
+  updateMedicine, 
+  deleteMedicine 
+} from '@/api/medicine'
 
 interface Medicine {
   id: string
   name: string
   pinyinFirstLetters: string
   barcode?: string // 添加条形码字段,可选
+  createTime?: string
+  updateTime?: 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 medicines = ref<Medicine[]>([])
 
 // 搜索关键词
 const searchKeyword = ref('')
@@ -125,6 +122,13 @@ const currentMedicine = ref<Medicine>({
   pinyinFirstLetters: ''
 })
 
+// 分页参数
+const pagination = ref({
+  pageNum: 1,
+  pageSize: 10,
+  total: 0
+})
+
 // 过滤后的药品列表
 const filteredMedicines = computed(() => {
   if (!searchKeyword.value.trim()) {
@@ -140,7 +144,7 @@ const filteredMedicines = computed(() => {
 
 // 处理搜索
 const handleSearch = () => {
-  // 实际逻辑在 computed 属性中处理
+  loadMedicineList()
 }
 
 // 显示添加模态框
@@ -163,18 +167,30 @@ const editMedicine = (medicine: Medicine) => {
 }
 
 // 删除药品
-const deleteMedicine = (id: string) => {
+const deleteMedicineHandler = (id: string) => {
   uni.showModal({
     title: '确认删除',
     content: '确定要删除这个药品吗?',
-    success: (res) => {
+    success: async (res) => {
       if (res.confirm) {
-        const index = medicines.value.findIndex(m => m.id === id)
-        if (index !== -1) {
-          medicines.value.splice(index, 1)
+        try {
+          const result: any = await deleteMedicine(id)
+          if (result?.data?.code === 200) {
+            uni.showToast({
+              title: '删除成功',
+              icon: 'success'
+            })
+            loadMedicineList()
+          } else {
+            uni.showToast({
+              title: result?.data?.message || '删除失败',
+              icon: 'none'
+            })
+          }
+        } catch (error) {
           uni.showToast({
-            title: '删除成功',
-            icon: 'success'
+            title: '删除失败',
+            icon: 'none'
           })
         }
       }
@@ -183,7 +199,7 @@ const deleteMedicine = (id: string) => {
 }
 
 // 保存药品(添加或更新)
-const saveMedicine = () => {
+const saveMedicine = async () => {
   if (!currentMedicine.value.name.trim()) {
     uni.showToast({
       title: '请输入药品名称',
@@ -200,32 +216,36 @@ const saveMedicine = () => {
     return
   }
 
-  if (isEditing.value) {
-    // 更新药品
-    const index = medicines.value.findIndex(m => m.id === currentMedicine.value.id)
-    if (index !== -1) {
-      medicines.value[index] = { ...currentMedicine.value }
+  try {
+    let result: any;
+    if (isEditing.value) {
+      // 更新药品
+      result = await updateMedicine(currentMedicine.value.id, currentMedicine.value)
+    } else {
+      // 添加药品
+      const { id, ...createData } = currentMedicine.value
+      result = await createMedicine(createData)
+    }
+
+    if (result?.data?.code === 200) {
       uni.showToast({
-        title: '更新成功',
+        title: isEditing.value ? '更新成功' : '添加成功',
         icon: 'success'
       })
+      closeModal()
+      loadMedicineList()
+    } else {
+      uni.showToast({
+        title: result?.data?.message || (isEditing.value ? '更新失败' : '添加失败'),
+        icon: 'none'
+      })
     }
-  } 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)
+  } catch (error) {
     uni.showToast({
-      title: '添加成功',
-      icon: 'success'
+      title: isEditing.value ? '更新失败' : '添加失败',
+      icon: 'none'
     })
   }
-
-  closeModal()
 }
 
 // 关闭模态框
@@ -263,9 +283,36 @@ const scanBarcode = () => {
   // #endif
 }
 
+// 加载药品列表
+const loadMedicineList = async () => {
+  try {
+    const params = {
+      pageNum: pagination.value.pageNum,
+      pageSize: pagination.value.pageSize,
+      keyword: searchKeyword.value.trim() || undefined
+    }
+    
+    const result: any = await getMedicineList(params)
+    if (result?.data?.code === 200) {
+      medicines.value = result.data.data.records
+      pagination.value.total = result.data.data.total
+    } else {
+      uni.showToast({
+        title: '获取药品列表失败',
+        icon: 'none'
+      })
+    }
+  } catch (error) {
+    uni.showToast({
+      title: '获取药品列表失败',
+      icon: 'none'
+    })
+  }
+}
+
 // 页面加载时
 onMounted(() => {
-  // 可以在这里从服务器获取药品列表数据
+  loadMedicineList()
 })
 </script>
 

+ 61 - 19
src/pages/patient/health/medication.vue

@@ -68,7 +68,7 @@
           <view class="form-group">
             <view class="form-row">
               <text class="form-label">药品名称</text>
-              <view class="medicine-selector" @click="showMedicinePicker = true">
+              <view class="medicine-selector" @click="openMedicinePicker">
                 <text class="selector-text" :class="{ placeholder: !selectedMedicine }">{{ selectedMedicine ? selectedMedicine.name : '请选择药品' }}</text>
                 <uni-icons type="arrowdown" size="16" color="#999" />
               </view>
@@ -147,7 +147,7 @@
             @click="selectMedicine(medicine)"
           >
             <text class="medicine-name">{{ medicine.name }}</text>
-            <text class="medicine-pinyin">{{ medicine.pinyin }}</text>
+            <text class="medicine-pinyin">{{ medicine.pinyinFirstLetters }}</text>
           </view>
         </scroll-view>
       </view>
@@ -192,6 +192,9 @@
 <script setup lang="ts">
 import { ref, computed, onMounted, watch } from 'vue'
 import CustomNav from '@/components/custom-nav.vue'
+import { getMedicineList } from '@/api/medicine'
+
+console.log('Medication component loaded')
 
 interface Medication {
   id: string
@@ -205,7 +208,7 @@ interface Medication {
 interface Medicine {
   id: string
   name: string
-  pinyin: string
+  pinyinFirstLetters: string
 }
 
 const medications = ref<Medication[]>([
@@ -254,18 +257,7 @@ 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 allMedicines = ref<Medicine[]>([])
 
 // 过滤后的药品列表
 const filteredMedicines = computed(() => {
@@ -275,8 +267,8 @@ const filteredMedicines = computed(() => {
   const search = medicineSearch.value.toLowerCase()
   return allMedicines.value.filter(medicine =>
     medicine.name.includes(search) ||
-    medicine.pinyin.toLowerCase().includes(search) ||
-    medicine.pinyin.toLowerCase().startsWith(search)
+    medicine.pinyinFirstLetters.toLowerCase().includes(search) ||
+    medicine.pinyinFirstLetters.toLowerCase().startsWith(search)
   )
 })
 
@@ -291,6 +283,7 @@ const filteredMedicines = computed(() => {
 
 // 打开添加模态框
 const openAdd = () => {
+  console.log('openAdd called')
   showAdd.value = true
 }
 
@@ -345,7 +338,11 @@ const closeAdd = () => {
 
 // 打开药品选择器
 const openMedicinePicker = () => {
+  console.log('openMedicinePicker called')
   showMedicinePicker.value = true
+  medicineSearch.value = ''
+  // 加载药品列表
+  loadMedicineList()
 }
 
 // 关闭药品选择器
@@ -356,7 +353,52 @@ const closeMedicinePicker = () => {
 
 // 药品搜索
 const onMedicineSearch = () => {
-  // 搜索逻辑在 computed 中处理
+  console.log('onMedicineSearch called, search value:', medicineSearch.value)
+  // 搜索时重新加载药品列表
+  loadMedicineList()
+}
+
+// 加载药品列表
+const loadMedicineList = async () => {
+  try {
+    // 构建参数对象
+    const params: any = {
+      pageNum: 1,
+      pageSize: 100 // 获取足够多的药品数据
+    }
+    
+    // 只有当搜索词非空时才添加keyword参数
+    const trimmedKeyword = medicineSearch.value.trim()
+    if (trimmedKeyword) {
+      params.keyword = trimmedKeyword
+    }
+    
+    console.log('loadMedicineList called with params:', params)
+    
+    const result: any = await getMedicineList(params)
+    console.log('API response:', result)
+    
+    if (result?.data?.code === 200) {
+      // 转换字段名以适配组件
+      allMedicines.value = result.data.data.records.map((item: any) => ({
+        id: item.id,
+        name: item.name,
+        pinyinFirstLetters: item.pinyinFirstLetters
+      }))
+      console.log('Medicine list loaded, count:', result.data.data.records.length)
+    } else {
+      uni.showToast({
+        title: '获取药品列表失败',
+        icon: 'none'
+      })
+    }
+  } catch (error) {
+    console.error('Load medicine list error:', error)
+    uni.showToast({
+      title: '获取药品列表失败',
+      icon: 'none'
+    })
+  }
 }
 
 // 选择药品
@@ -428,7 +470,7 @@ const editMedication = (id: string) => {
     selectedMedicine.value = {
       id: '',
       name: medication.name,
-      pinyin: ''
+      pinyinFirstLetters: ''
     }
     
     addDosage.value = medication.dosage