2 次代码提交 970f3de5db ... 14a2e23529

作者 SHA1 备注 提交日期
  xiongxing 14a2e23529 Merge branch 'master' of http://47.107.245.0:3000/xiongxing/kyj-yanglao-web-new 4 周之前
  xiongxing abe5167ea4 重新设计血糖记录模块内容 4 周之前

+ 105 - 21
src/views/elderly/assess/blood-glucose/component/form.vue

@@ -12,7 +12,7 @@
       v-loading="loading"
       ref="formRef"
       :model="dataForm"
-      :rules="isDetail ? [] : dataRule"
+      :rules="isDetail ? {} : dataRule"
       :label-width="labelWidth"
     >
       <div class="info-wrap">
@@ -102,7 +102,20 @@
         </el-row>
 
         <el-row>
-          <el-col :xs="24" :sm="24" :md="24" :lg="12" :xl="12">
+          <el-col :xs="24" :sm="24" :md="24" :lg="8" :xl="8">
+            <el-form-item label="记录时间" prop="recordTime">
+              <el-time-picker
+                v-if="!isDetail"
+                v-model="dataForm.recordTime"
+                style="width: 100%"
+                format="HH:mm"
+                value-format="HH:mm"
+                placeholder="选择记录时间"
+              />
+              <el-text v-else>{{ dataForm.recordTime }}</el-text>
+            </el-form-item>
+          </el-col>
+          <el-col :xs="24" :sm="24" :md="24" :lg="8" :xl="8">
             <el-form-item label="血糖值" prop="bloodSugar">
               <el-input-number
                 v-if="!isDetail"
@@ -116,6 +129,47 @@
               <el-text v-else>{{ dataForm.bloodSugar }}</el-text>
             </el-form-item>
           </el-col>
+          <el-col :xs="24" :sm="24" :md="24" :lg="8" :xl="8">
+            <el-form-item label="护士签名" prop="nurseSign">
+              <el-input
+                v-if="!isDetail"
+                v-model="dataForm.nurseSign"
+                :maxlength="50"
+                placeholder="请输入护士签名"
+              />
+              <el-text v-else>{{ dataForm.nurseSign }}</el-text>
+            </el-form-item>
+          </el-col>
+        </el-row>
+
+        <el-divider content-position="left">分时段血糖记录</el-divider>
+
+        <el-row v-for="item in bloodSugarItems" :key="item.valueKey">
+          <el-col :xs="24" :sm="24" :md="24" :lg="12" :xl="12">
+            <el-form-item :label="`${item.label}血糖值`" :prop="item.valueKey">
+              <el-input-number
+                v-if="!isDetail"
+                v-model="dataForm[item.valueKey]"
+                :min="0"
+                :max="999"
+                :precision="2"
+                style="width: 100%"
+                :placeholder="`请输入${item.label}血糖值`"
+              />
+              <el-text v-else>{{ dataForm[item.valueKey] }}</el-text>
+            </el-form-item>
+          </el-col>
+          <el-col :xs="24" :sm="24" :md="24" :lg="12" :xl="12">
+            <el-form-item :label="`${item.label}护士签名`" :prop="item.signKey">
+              <el-input
+                v-if="!isDetail"
+                v-model="dataForm[item.signKey]"
+                :maxlength="50"
+                :placeholder="`请输入${item.label}护士签名`"
+              />
+              <el-text v-else>{{ dataForm[item.signKey] }}</el-text>
+            </el-form-item>
+          </el-col>
         </el-row>
 
         <el-row>
@@ -170,7 +224,19 @@ const labelWidth = computed(() => {
   return currentWidth.value ? '130px' : '130px'
 })
 
-const dataForm = ref<any>({
+// 分时段血糖:血糖值字段 + 对应护士签名字段
+const bloodSugarItems = [
+  { label: '空腹', valueKey: 'fastingBloodSugar', signKey: 'fastingNurseSign' },
+  { label: '早餐后', valueKey: 'afterBreakfastBloodSugar', signKey: 'afterBreakfastNurseSign' },
+  { label: '晚餐前', valueKey: 'beforeDinnerBloodSugar', signKey: 'beforeDinnerNurseSign' },
+  { label: '晚餐后', valueKey: 'afterDinnerBloodSugar', signKey: 'afterDinnerNurseSign' },
+  { label: '睡前', valueKey: 'beforeSleepBloodSugar', signKey: 'beforeSleepNurseSign' }
+]
+
+// 所有血糖值字段(含通用血糖值),用于「至少录入一个」的校验
+const allBloodSugarKeys = ['bloodSugar', ...bloodSugarItems.map((item) => item.valueKey)]
+
+const createDefaultForm = () => ({
   id: undefined,
   tenantId: undefined,
   elderId: '',
@@ -182,14 +248,27 @@ const dataForm = ref<any>({
   bedName: '',
   nurseLevelName: '',
   recordDate: dayjs().format('YYYY-MM-DD'),
+  recordTime: '',
   bloodSugar: undefined,
+  nurseSign: '',
+  fastingBloodSugar: undefined,
+  fastingNurseSign: '',
+  afterBreakfastBloodSugar: undefined,
+  afterBreakfastNurseSign: '',
+  beforeDinnerBloodSugar: undefined,
+  beforeDinnerNurseSign: '',
+  afterDinnerBloodSugar: undefined,
+  afterDinnerNurseSign: '',
+  beforeSleepBloodSugar: undefined,
+  beforeSleepNurseSign: '',
   remark: ''
 })
 
+const dataForm = ref<any>(createDefaultForm())
+
 const dataRule = reactive<FormRules>({
   elderId: [{ required: true, message: '长者姓名不能为空', trigger: 'blur' }],
-  recordDate: [{ required: true, message: '记录日期不能为空', trigger: 'change' }],
-  bloodSugar: [{ required: true, message: '血糖值不能为空', trigger: 'blur' }]
+  recordDate: [{ required: true, message: '记录日期不能为空', trigger: 'change' }]
 })
 
 const elderUp = (e: any) => {
@@ -204,21 +283,7 @@ const elderUp = (e: any) => {
 }
 
 const resetForm = () => {
-  dataForm.value = {
-    id: undefined,
-    tenantId: undefined,
-    elderId: '',
-    elderName: '',
-    fileNumber: '',
-    elderSex: '',
-    elderAge: '',
-    checkInTime: '',
-    bedName: '',
-    nurseLevelName: '',
-    recordDate: dayjs().format('YYYY-MM-DD'),
-    bloodSugar: undefined,
-    remark: ''
-  }
+  dataForm.value = createDefaultForm()
   formRef.value?.resetFields()
 }
 
@@ -270,7 +335,26 @@ const submitForm = async () => {
     const valid = await formRef.value.validate()
     if (!valid) return
 
-    const params = { ...dataForm.value }
+    // 血糖值(含分时段)至少录入一个
+    const hasBloodSugar = allBloodSugarKeys.some(
+      (key) => dataForm.value[key] !== undefined && dataForm.value[key] !== null && dataForm.value[key] !== ''
+    )
+    if (!hasBloodSugar) {
+      message.warning('请至少录入一个血糖值')
+      return
+    }
+
+    const params: any = { ...dataForm.value }
+    // 未填写的字段统一提交空字符串,避免提交 null
+    Object.keys(params).forEach((key) => {
+      if (params[key] === null || params[key] === undefined) {
+        if (key === 'id') {
+          delete params[key]
+        } else {
+          params[key] = ''
+        }
+      }
+    })
     if (params.id) {
       const res = await updateBloodGlucose(params)
       if (res) {

+ 175 - 2
src/views/elderly/assess/blood-glucose/index.vue

@@ -42,6 +42,10 @@
           <Icon icon="ep:download" class="mr-5px" />
           导出
         </el-button>
+        <el-button type="warning" plain @click="handleSingleExport" v-hasPermi="['elderly:blood-glucose:export']">
+          <Icon icon="ep:download" class="mr-5px" />
+          单人导出
+        </el-button>
       </div>
     </div>
 
@@ -79,6 +83,9 @@ import {
   exportBloodGlucoseExcel
 } from '@/api/elderly/assess/blood-glucose'
 import download from '@/utils/download'
+import { ElderlyBloodSugarRecordVO } from '../types'
+import * as XLSX from 'xlsx-js-style'
+import { formatTime } from '@/utils'
 
 defineOptions({ name: 'BloodGlucose' })
 
@@ -88,7 +95,7 @@ const { t } = useI18n()
 
 const loading = ref(true)
 const total = ref(0)
-const list = ref<any[]>([])
+const list = ref<ElderlyBloodSugarRecordVO[]>([])
 
 const queryParams = reactive({
   pageNo: 1,
@@ -106,8 +113,14 @@ const columns = reactive([
   { label: '床位号', field: 'bedName' },
   { label: '性别', field: 'elderSex', type: '1', dictArr: DICT_TYPE.SYSTEM_USER_SEX },
   { label: '记录日期', field: 'recordDate' },
+  { label: '记录时间', field: 'recordTime' },
+  { label: '空腹血糖值', field: 'fastingBloodSugar' },
+  { label: '早餐后血糖值', field: 'afterBreakfastBloodSugar' },
+  { label: '晚餐前血糖值', field: 'beforeDinnerBloodSugar' },
+  { label: '晚餐后血糖值', field: 'afterDinnerBloodSugar' },
+  { label: '睡前血糖值', field: 'beforeSleepBloodSugar' },
   { label: '血糖值', field: 'bloodSugar' },
-  { label: '备注', field: 'remark' },
+  { label: '备注', field: 'remark' }
 ])
 
 const handleDateClose = () => {
@@ -166,6 +179,166 @@ const exportExcel = async () => {
   }
 }
 
+/** 单人导出:长者血糖监测表 */
+const handleSingleExport = async () => {
+  if (!queryParams.elderName) {
+    message.warning('请先输入长者姓名')
+    return
+  }
+  const wb = XLSX.utils.book_new()
+  // 分组表头:label + 占用列数
+  const groups = [
+    { label: '日期', span: 1 },
+    { label: '空腹', span: 2 },
+    { label: '早餐', span: 2 },
+    { label: '晚餐', span: 4 },
+    { label: '睡前', span: 2 },
+    { label: '随机', span: 3 }
+  ]
+  const subHeaders = [
+    '日期',
+    '空腹',
+    '护士签名',
+    '早饭后',
+    '护士签名',
+    '晚餐前',
+    '护士签名',
+    '晚餐后',
+    '护士签名',
+    '睡前',
+    '护士签名',
+    '时间',
+    '血糖值',
+    '护士签名'
+  ]
+  const colCount = subHeaders.length
+
+  // 查询该长者数据(默认最多 200 条)
+  const data = await getBloodGlucosePage({ ...queryParams, pageNo: 1, pageSize: 200 })
+  const records: ElderlyBloodSugarRecordVO[] = Array.isArray(data?.list) ? data.list : []
+  if (records.length === 0) {
+    message.warning(`未查询到长者「${queryParams.elderName}」的血糖记录,无法导出`)
+    return
+  }
+
+  // 标题行
+  const titleRow: string[] = new Array(colCount).fill('')
+  titleRow[0] = '长者血糖监测表'
+
+  // 基本信息行:姓名 / 房号 / 单位
+  const first = records[0]
+  const roomName = first.roomName || ''
+  const bedName = first.bedName || ''
+  const roomText = roomName && bedName ? `${roomName}(${bedName})` : roomName || bedName
+  const infoItems = [
+    { label: '姓名:', value: first.elderName || '', start: 0, span: 4 },
+    { label: '房号:', value: roomText, start: 4, span: 4 },
+    { label: '单位:', value: '毫摩尔/升', start: 8, span: 6 }
+  ]
+  const infoRow: string[] = new Array(colCount).fill('')
+  infoItems.forEach((item) => {
+    infoRow[item.start] = item.label + item.value
+  })
+
+  // 分组表头行
+  const groupRow: string[] = new Array(colCount).fill('')
+  let cursor = 0
+  groups.forEach((group) => {
+    groupRow[cursor] = group.label
+    cursor += group.span
+  })
+
+  // 数据行
+  const dataRows = records.map((row) => [
+    row.recordDate ? formatTime(row.recordDate, 'yyyy-MM-dd') : '',
+    row.fastingBloodSugar ?? '',
+    row.fastingNurseSign || '',
+    row.afterBreakfastBloodSugar ?? '',
+    row.afterBreakfastNurseSign || '',
+    row.beforeDinnerBloodSugar ?? '',
+    row.beforeDinnerNurseSign || '',
+    row.afterDinnerBloodSugar ?? '',
+    row.afterDinnerNurseSign || '',
+    row.beforeSleepBloodSugar ?? '',
+    row.beforeSleepNurseSign || '',
+    row.recordTime || '',
+    row.bloodSugar ?? '',
+    row.nurseSign || ''
+  ])
+
+  const ws = XLSX.utils.aoa_to_sheet([titleRow, infoRow, groupRow, subHeaders, ...dataRows])
+
+  // 合并单元格
+  const merges: any[] = [
+    { s: { r: 0, c: 0 }, e: { r: 0, c: colCount - 1 } },
+    ...infoItems.map((item) => ({ s: { r: 1, c: item.start }, e: { r: 1, c: item.start + item.span - 1 } }))
+  ]
+  // 分组合并:多列横向合并;单列(日期)纵向合并两行表头
+  let startCol = 0
+  groups.forEach((group) => {
+    if (group.span > 1) {
+      merges.push({ s: { r: 2, c: startCol }, e: { r: 2, c: startCol + group.span - 1 } })
+    } else {
+      merges.push({ s: { r: 2, c: startCol }, e: { r: 3, c: startCol } })
+    }
+    startCol += group.span
+  })
+  ws['!merges'] = merges
+
+  // 列宽
+  ws['!cols'] = [{ wch: 14 }, ...new Array(colCount - 1).fill({ wch: 10 })]
+
+  // 行高
+  ws['!rows'] = [
+    { hpt: 38 },
+    { hpt: 28 },
+    { hpt: 28 },
+    { hpt: 28 },
+    ...dataRows.map(() => ({ hpt: 24 }))
+  ]
+
+  // 样式
+  const range = XLSX.utils.decode_range(ws['!ref'] || '')
+  const headerFill = {
+    patternType: 'solid',
+    fgColor: { rgb: 'FFD9D9D9' }
+  }
+  const border = {
+    top: { style: 'thin', color: { rgb: 'FF000000' } },
+    bottom: { style: 'thin', color: { rgb: 'FF000000' } },
+    left: { style: 'thin', color: { rgb: 'FF000000' } },
+    right: { style: 'thin', color: { rgb: 'FF000000' } }
+  }
+
+  for (let R = range.s.r; R <= range.e.r; R++) {
+    for (let C = range.s.c; C <= range.e.c; C++) {
+      const cellRef = XLSX.utils.encode_cell({ r: R, c: C })
+      if (!ws[cellRef]) {
+        ws[cellRef] = { v: '', t: 's' }
+      }
+      const cell = ws[cellRef]
+      cell.s = cell.s || {}
+      cell.s.border = border
+      cell.s.alignment = { vertical: 'center', horizontal: 'center', wrapText: true }
+
+      if (R === 0) {
+        cell.s.font = { bold: true, sz: 18, name: '宋体' }
+      } else if (R === 1) {
+        cell.s.alignment = { vertical: 'center', horizontal: 'left', wrapText: true }
+        cell.s.font = { bold: true, sz: 11, name: '宋体' }
+      } else if (R === 2 || R === 3) {
+        cell.s.fill = headerFill
+        cell.s.font = { bold: true, sz: 11, name: '宋体' }
+      } else {
+        cell.s.font = { sz: 11, name: '宋体' }
+      }
+    }
+  }
+
+  XLSX.utils.book_append_sheet(wb, ws, '长者血糖监测表')
+  XLSX.writeFile(wb, `长者血糖监测表_${queryParams.elderName}.xlsx`)
+}
+
 onMounted(() => {
   getList()
 })

+ 31 - 0
src/views/elderly/assess/types.ts

@@ -207,3 +207,34 @@ export interface vitalSignFormType {
     list: { id: string; elderName: string; idCard: string }[]
   }[]
 }
+
+// ======================血糖记录=======================
+// ElderlyBloodSugarRecordRespVO
+export interface ElderlyBloodSugarRecordVO {
+  id?: number
+  elderId?: number
+  elderName?: string
+  roomName?: string
+  bedName?: string
+  elderSex?: number
+  recordDate?: string
+  recordTime?: string
+  fastingBloodSugar?: number
+  fastingNurseSign?: string
+  afterBreakfastBloodSugar?: number
+  afterBreakfastNurseSign?: string
+  beforeDinnerBloodSugar?: number
+  beforeDinnerNurseSign?: string
+  afterDinnerBloodSugar?: number
+  afterDinnerNurseSign?: string
+  beforeSleepBloodSugar?: number
+  beforeSleepNurseSign?: string
+  bloodSugar?: number
+  nurseSign?: string
+  remark?: string
+  tenantId?: number
+  createTime?: string
+  updateTime?: string
+  creator?: string
+  updater?: string
+}