Parcourir la source

部分接口对接

xiongxing il y a 3 semaines
Parent
commit
3b747dcce9

+ 39 - 0
src/api/elderly/nursing/special-nursing-log/index.ts

@@ -0,0 +1,39 @@
+import request from '@/config/axios'
+
+/** 特殊照护完成日志(单行) */
+export interface ElderlySpecialNursingLogRespVO {
+  id?: number
+  specialPlanItemId?: number
+  elderId?: number
+  elderName?: string
+  bedName?: string
+  nurseLevelName?: string
+  finishTime?: string
+  remark?: string
+  /** 多图时逗号分隔等,与列表展示约定一致 */
+  imageUrl?: string
+  tenantId?: number
+}
+
+/** 分页列表 */
+export const getSpecialNursingLogPage = (params: Recordable) => {
+  return request.get({
+    url: 'nursing/special-nursing-log/page',
+    params
+  })
+}
+
+/**
+ * 某长者在完成日期区间内的详情日志(服务端返回列表,前端可做分页切片)
+ * finishTime:完成日期范围,如 ['2024-01-01','2024-01-31']
+ */
+export const getSpecialNursingLogListByElderAndFinishTime = (params: {
+  elderId: number | string
+  finishTime: string[]
+  tenantId?: number
+}) => {
+  return request.get<ElderlySpecialNursingLogRespVO[]>({
+    url: 'nursing/special-nursing-log/list-by-elder-and-finish-time',
+    params
+  })
+}

+ 104 - 0
src/api/elderly/nursing/special-plan/index.ts

@@ -0,0 +1,104 @@
+import request from '@/config/axios'
+
+/** 明细(详情接口返回) */
+export interface SpecialNursingPlanItemRespVO {
+  id?: number
+  specialNursingPlanId?: number
+  nurseItemId?: number
+  nurseItemName?: string
+  /** 0:未完成,1:已完成(任务侧) */
+  status?: number
+  tenantId?: number
+  createTime?: string
+  updateTime?: string
+  creator?: string
+  updater?: string
+}
+
+/** 详情 / 分页列表单行:特殊护理计划 */
+export interface SpecialNursingPlanRespVO {
+  id?: number
+  elderId?: number
+  elderName?: string
+  tenantId?: number
+  /** 以下为扩展展示字段(部分环境详情接口会与长者快照一并返回) */
+  bedName?: string
+  nurseLevelName?: string
+  createTime?: string
+  updateTime?: string
+  creator?: string
+  updater?: string
+  items?: SpecialNursingPlanItemRespVO[]
+}
+
+/** 保存项:与普通护理计划区分开,后端字段见 SpecialNursingPlanItemSaveReqVO */
+export interface SpecialNursingPlanItemSaveReqVO {
+  /** 明细主键,修改明细时传递 */
+  id?: number
+  /** 所属特殊护理计划 ID,更新必填 */
+  specialNursingPlanId?: number
+  nurseItemId: number
+  nurseItemName?: string
+  /** 0:未完成,1:已完成(任务侧维护,本模块表单不传) */
+  status?: number
+  tenantId: number
+}
+
+export interface SpecialNursingPlanSaveReqVO {
+  /** 护理计划主键,修改时必传 */
+  id?: number
+  elderId: number
+  elderName?: string
+  tenantId: number
+  items?: SpecialNursingPlanItemSaveReqVO[]
+}
+
+/** 分页查询 */
+export const getSpecialNursingPlanPage = (params: Recordable) => {
+  return request.get({
+    url: 'nursing/special-plan/page',
+    params
+  })
+}
+
+export const createSpecialNursingPlan = (data: SpecialNursingPlanSaveReqVO) => {
+  return request.post({
+    url: 'nursing/special-plan/create',
+    data
+  })
+}
+
+export const updateSpecialNursingPlan = (data: SpecialNursingPlanSaveReqVO) => {
+  return request.put({
+    url: 'nursing/special-plan/update',
+    data
+  })
+}
+
+export const deleteSpecialNursingPlan = (id: number | string) => {
+  return request.delete({
+    url: `nursing/special-plan/delete`,
+    params: { id }
+  })
+}
+
+/**
+ * 查看详情 / 编辑前拉取完整数据
+ * GET `/nursing/special-plan/get`
+ * @param id 护理计划主键 ID(列表行 id,即接口文档中的「任务 id」所指业务主键)
+ * 通用响应:`{ code, data: SpecialNursingPlanRespVO, msg }`,request 封装后返回 `data` 即为 `SpecialNursingPlanRespVO`
+ */
+export const getSpecialNursingPlan = (id: number | string) => {
+  return request.get<SpecialNursingPlanRespVO>({
+    url: 'nursing/special-plan/get',
+    params: { id }
+  })
+}
+
+/** 更新特殊护理计划明细(如撤销完成状态:status 传 0) */
+export const updateSpecialPlanItem = (data: { id: number; status?: number }) => {
+  return request.put({
+    url: 'nursing/special-plan-item/update',
+    data
+  })
+}

+ 54 - 0
src/views/elderly/nursing/column.ts

@@ -118,6 +118,60 @@ export const LifeCarePlanColumns = reactive([
   // }
 ])
 
+// ===============特殊护理计划=====================
+export const SpecialNursingPlanColumns = reactive([
+  {
+    label: '长者姓名',
+    field: 'elderName'
+  },
+  {
+    label: '创建时间',
+    field: 'createTime',
+    type: '9',
+    width: 170
+  },
+  {
+    label: '更新时间',
+    field: 'updateTime',
+    type: '9',
+    width: 170
+  },
+  {
+    label: '创建人',
+    field: 'creator'
+  },
+  {
+    label: '更新人',
+    field: 'updater'
+  }
+])
+
+// =============特殊照护完成日志=====================
+export const SpecialNursingLogColumns = reactive([
+  {
+    label: '长者姓名',
+    field: 'elderName'
+  },
+  {
+    label: '床位',
+    field: 'bedName'
+  },
+  {
+    label: '护理等级',
+    field: 'nurseLevelName'
+  },
+  {
+    label: '完成时间',
+    field: 'finishTime',
+    type: '9',
+    width: 170
+  },
+  {
+    label: '备注',
+    field: 'remark'
+  }
+])
+
 // =============护理日志=====================
 export const NurseLogColumns = reactive([
   {

+ 97 - 64
src/views/elderly/nursing/life-care-plan/Form.vue

@@ -32,16 +32,16 @@
           </el-col>
           <el-col :span="12" :xs="24">
             <el-form-item label="床位号" prop="bedName">
-             <el-input v-if="!isDetail" v-model="dataForm.bedName" disabled/>
-              <el-text v-else>{{dataForm.bedName}}</el-text>
+              <el-input v-if="!isDetail" v-model="dataForm.bedName" disabled />
+              <el-text v-else>{{ dataForm.bedName }}</el-text>
             </el-form-item>
           </el-col>
         </el-row>
         <el-row :gutter="20">
           <el-col :span="12" :xs="24">
             <el-form-item label="护理等级" prop="nurseLevelName">
-              <el-input v-if="!isDetail" v-model="dataForm. nurseLevelName" disabled/>
-              <el-text v-else>{{dataForm.nurseLevelName}}</el-text>
+              <el-input v-if="!isDetail" v-model="dataForm.nurseLevelName" disabled />
+              <el-text v-else>{{ dataForm.nurseLevelName }}</el-text>
             </el-form-item>
           </el-col>
           <el-col :span="12" :xs="24">
@@ -70,31 +70,40 @@
             v-for="(p, i) in dataForm.items"
             :key="i"
           >
-            <div class="border item-row">
-              <div class="item-left">
-                <el-checkbox
-                  :label="p.nurseItemName"
-                  v-model="p.checked"
-                  @change="(arg) => handleChangeCheckBox(arg, p)"
-                  v-if="!isDetail"
-                />
-                <div v-else class="itemName">{{ p.nurseItemName }}</div>
-              </div>
-              <div class="item-time" v-if="!isDetail">
-                <span class="time-label required">护理开始时间</span>
-                <el-date-picker
-                  v-model="p.startTime"
-                  type="datetime"
-                  value-format="YYYY-MM-DD HH:mm:ss"
-                  format="YYYY-MM-DD HH:mm:ss"
-                  placeholder="请选择开始时间"
-                />
-              </div>
-              <div v-else class="item-time detail-time">
-                <span class="time-label">护理开始时间</span>
-                <span>{{ p.startTime || '-' }}</span>
-              </div>
-            </div>
+            <el-row :gutter="20">
+              <el-col :span="16">
+                <div class="item-left">
+                  <el-checkbox
+                    :label="`${p.nurseItemName}(${p.frequencyCategory})`"
+                    v-model="p.checked"
+                    @change="(arg) => handleChangeCheckBox(arg, p)"
+                    v-if="!isDetail"
+                  />
+                  <div v-else class="itemName"
+                    >{{ p.nurseItemName }}({{ p.frequencyCategory }})</div
+                  >
+                </div>
+              </el-col>
+              <el-col :span="8">
+                <div class="item-time" v-if="!isDetail">
+                  <span class="time-label required" v-if="p.frequencyCategoryType == 1"
+                    >护理开始时间</span
+                  >
+                  <el-time-picker
+                    v-if="p.frequencyCategoryType == 1"
+                    v-model="p.beginTime"
+                    value-format="HH:mm:ss"
+                    placeholder="请选择开始时间"
+                  />
+                </div>
+                <div v-else class="item-time detail-time">
+                  <span class="time-label" v-if="p.frequencyCategoryType == 1">护理开始时间</span>
+                  <span v-if="p.frequencyCategoryType == 1">{{
+                    formatBeginTimeDetail(p.beginTime)
+                  }}</span>
+                </div>
+              </el-col>
+            </el-row>
           </el-col>
         </el-row>
       </div>
@@ -133,15 +142,15 @@ const state = reactive<planType>({
     nurseLevelName: '',
     effectiveDate: '',
     items: [],
-    extraItems: [],
+    extraItems: []
   },
   dataRule: {
     elderId: [{ required: true, message: '长者姓名不能为空', trigger: 'blur' }],
     effectiveDate: [{ required: true, message: '生效日期不能为空', trigger: 'blur' }]
-  },
+  }
 })
 const { dataForm, dataRule } = toRefs(state)
-const resetFormField =  reactive({ ...dataForm.value })
+const resetFormField = reactive({ ...dataForm.value })
 const route = useRoute()
 const formLoading = ref(false)
 const isDetail = ref(false)
@@ -151,9 +160,40 @@ const itemTitle = computed(() => {
   return isDetail.value ? '详情' : !dataForm.value.id ? '新增' : '修改'
 })
 
+/** 详情接口可能返回时间戳或整段日期时间,转为时间选择器用的 HH:mm:ss */
+const toTimePickerValue = (beginTime: unknown): string => {
+  if (beginTime == null || beginTime === '') return ''
+  if (typeof beginTime === 'number') {
+    const d = new Date(beginTime)
+    if (Number.isNaN(d.getTime())) return ''
+    const pad = (n: number) => String(n).padStart(2, '0')
+    return `${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`
+  }
+  if (typeof beginTime === 'string') {
+    if (/^\d+$/.test(beginTime)) return toTimePickerValue(Number(beginTime))
+    if (beginTime.includes(' ')) return beginTime.split(' ')[1] || ''
+  }
+  return String(beginTime)
+}
+
+/** 详情展示:时间戳或日期时间转为可读时间 */
+const formatBeginTimeDetail = (beginTime: unknown): string => {
+  if (beginTime == null || beginTime === '') return '-'
+  if (typeof beginTime === 'number' || (typeof beginTime === 'string' && /^\d+$/.test(beginTime))) {
+    const d = new Date(Number(beginTime))
+    if (Number.isNaN(d.getTime())) return '-'
+    const pad = (n: number) => String(n).padStart(2, '0')
+    return `${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`
+  }
+  if (typeof beginTime === 'string' && beginTime.includes(' ')) {
+    return beginTime.split(' ')[1] || '-'
+  }
+  return String(beginTime)
+}
+
 /** 打开弹窗 */
 const open = async (id, detail) => {
-  dataForm.value.id=''
+  dataForm.value.id = ''
   dialogVisible.value = true
   isDetail.value = detail
   if (id) {
@@ -169,7 +209,11 @@ const open = async (id, detail) => {
     dataForm.value = res
     await formatExtraItem()
     dataForm.value.items = res.items?.length > 0 ? res.items : res.extraItems
-    console.log('dataForm.value', dataForm.value)
+    dataForm.value.items?.forEach((item) => {
+      if (item.frequencyCategoryType === 1 && item.beginTime != null && item.beginTime !== '') {
+        item.beginTime = toTimePickerValue(item.beginTime)
+      }
+    })
   }
 }
 defineExpose({ open }) // 提供 open 方法,用于打开弹窗
@@ -191,7 +235,7 @@ const formatExtraItem = async () => {
 }
 
 const handleClosed = () => {
-  dataForm.value = {...resetFormField}
+  dataForm.value = { ...resetFormField }
   dialogVisible.value = false
 }
 
@@ -201,18 +245,20 @@ const handleAdd = () => {
 }
 
 const getItemList = (val) => {
+  console.log('val', val)
   // 添加的内容放到护理项目中
   val.map((item) => {
     dataForm.value.items.push({
       nurseItemId: item.id,
       nurseItemName: item.itemName,
-      frequency: 1,
-      frequencyUnit: item.frequency,
-      operatingMode: item.operatingMode,
+      frequencyType: item.frequencyType,
+      frequency: item.frequency,
+      frequencyCategory: item.frequencyCategory,
+      frequencyCategoryType: item.frequencyCategoryType,
       checked: true,
       price: item.price,
       isExtra: item.type == 1 ? 1 : 0,
-      startTime: '',
+      beginTime: ''
     })
   })
 }
@@ -233,28 +279,31 @@ const handleChangeCheckBox = (val, item) => {
 
 const formRef = ref()
 const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
+
 const submitForm = async () => {
   // 校验表单
   if (!formRef.value) return
   const valid = await formRef.value.validate()
   if (!valid) return
 
-  const missingTime = dataForm.value.items.find((item) => item.checked && !item.startTime)
+  const missingTime = dataForm.value.items.find(
+    (item) => item.checked && !item.beginTime && item.frequencyCategoryType == 1
+  )
   if (missingTime) {
     message.warning(`请先选择【${missingTime.nurseItemName}】的护理开始时间`)
     return
   }
 
-
   try {
     formLoading.value = true
-    const params = {
-      type: route.path.indexOf('medical-care-plan') > -1 ? 2 : 1, // 2:医疗护理 1 生活护理,
-      ...dataForm.value,
-    }
-    const res = params.id
-      ? await updateNursingPlan(params)
-      : await createNursingPlan(params)
+    let params = JSON.parse(JSON.stringify(dataForm.value))
+    ;(params.type = route.path.indexOf('medical-care-plan') > -1 ? 2 : 1), // 2:医疗护理 1 生活护理,
+      params.items.forEach((item) => {
+        if (item.frequencyCategoryType == 1 && item.beginTime) {
+          item.beginTime = new Date(`${params.effectiveDate} ${item.beginTime}`).getTime()
+        }
+      })
+    const res = params.id ? await updateNursingPlan(params) : await createNursingPlan(params)
     if (res) {
       message.success(t('common.updateSuccess'))
       handleClosed()
@@ -277,18 +326,6 @@ const submitForm = async () => {
   .itemName {
     line-height: 30px;
   }
-  // .border {
-  //   padding: 1px 11px;
-  //   box-shadow: 0 0 0 1px var(--el-input-border-color, var(--el-border-color)) inset;
-  //   border-radius: var(--el-input-border-radius, var(--el-border-radius-base));
-  // }
-  .item-row {
-    display: flex;
-    justify-content: space-between;
-    align-items: center;
-    gap: 12px;
-    flex-wrap: wrap;
-  }
   .item-left {
     display: flex;
     align-items: center;
@@ -298,10 +335,6 @@ const submitForm = async () => {
     box-shadow: 0 0 0 1px var(--el-input-border-color, var(--el-border-color)) inset;
     border-radius: var(--el-input-border-radius, var(--el-border-radius-base));
   }
-  .item-meta {
-    display: flex;
-    gap: 8px;
-  }
   .item-time {
     display: flex;
     align-items: center;

+ 4 - 9
src/views/elderly/nursing/life-care-plan/life-item-dialog.vue

@@ -14,13 +14,8 @@
         <el-table-column prop="itemName" label="项目名称" />
         <el-table-column prop="cName" label="护理分类" align="center" />
         <el-table-column prop="categoryName" label="项目分类" align="center" />
-        <el-table-column prop="frequencyUnit" label="频次" align="center" />
-        <el-table-column prop="frequencyType" label="频次类型" align="center" />
-        <!-- <el-table-column prop="fee" label="是否额外收费" align="center">
-          <template #default="scope">
-            <TgSwitch v-model="scope.row.fee"/>
-          </template>
-        </el-table-column> -->
+        <el-table-column prop="frequency" label="频次" align="center" />
+        <el-table-column prop="frequencyCategory" label="频次类型" align="center" />
         <el-table-column prop="type" label="收费项目" align="center">
           <template #default="scope">
             {{ getDictLabel(DICT_TYPE.COMMON_STATUS6, scope.row.type) }}
@@ -31,9 +26,9 @@
             {{ formatNum(scope.row.price) }}
           </template>
         </el-table-column>
-        <el-table-column prop="frequency" label="收费单位" align="center">
+        <el-table-column prop="frequencyType" label="频次计量单位" align="center">
           <template #default="scope">
-            {{ getDictLabel(DICT_TYPE.NURSING_FREQUENCY_TYPE, scope.row.frequency) }}
+            {{ getDictLabel(DICT_TYPE.NURSING_FREQUENCY_TYPE, scope.row.frequencyType) }}
           </template>
         </el-table-column>
       </el-table>

+ 25 - 16
src/views/elderly/nursing/nurse-item/Form.vue

@@ -29,23 +29,23 @@
           >
         </el-radio-group>
       </el-form-item>
-      <el-form-item label="频次" prop="frequencyUnit">
-        <el-input v-model="dataForm.frequencyUnit" placeholder="请输入频次(例:2h/次)" />
-      </el-form-item>
-      <el-form-item label="频次类型" prop="frequencyType">
-        <el-select v-model="dataForm.frequencyType" placeholder="请选择频次类型(高/中低)">
+      <el-form-item label="频次类型" prop="frequencyCategoryType" :rules="[{ required: true, message: '频次类型不能为空', trigger: 'blur' }]">
+        <el-select v-model="dataForm.frequencyCategoryType" placeholder="请选择频次类型(高/中低)">
           <el-option
             label="高"
-            value="1"
+            :value="1"
           />
           <el-option
-            label="中低"
-            value="2"
+            label="中/低"
+            :value="2"
           />
         </el-select>
       </el-form-item>
-      <el-form-item label="计量方式" prop="frequency">
-        <el-select v-model="dataForm.frequency" placeholder="请选择计量方式">
+      <el-form-item label="频次" prop="frequency" v-if="dataForm.frequencyCategoryType == 1" :rules="dataForm.frequencyCategoryType == 1 ? [{ required: true, message: '频次不能为空', trigger: 'blur' }] : []">
+        <el-input v-model="dataForm.frequency" type="number" placeholder="请输入频次(例:2h/次)" />
+      </el-form-item>
+      <el-form-item label="计量方式" prop="frequencyType">
+        <el-select v-model="dataForm.frequencyType" placeholder="请选择计量方式">
           <el-option
             v-for="(dict, index) in getIntDictOptions(DICT_TYPE.NURSING_FREQUENCY_TYPE)"
             :key="index"
@@ -54,7 +54,7 @@
           />
         </el-select>
       </el-form-item>
-      <el-form-item label="超时提醒" prop="timeout">
+      <el-form-item label="超时提醒" prop="timeout" v-if="dataForm.frequencyCategoryType == 1" :rules="dataForm.frequencyCategoryType == 1 ? [{ required: true, message: '频次不能为空', trigger: 'blur' }] : []">
         <el-input v-model.num="dataForm.timeout" placeholder="距上次护理时间超过设定值后提醒">
           <template #append>小时</template>
         </el-input>
@@ -110,11 +110,14 @@ const dataForm = ref({
   image: '',
   type: '',
   price: undefined,
-  frequency: '',
+  frequencyType: '',
   timeout: '',
   status: 1,
   remark: '',
-  tenantId: ''
+  tenantId: '',
+  frequency:'',
+  frequencyCategory:'',
+  frequencyCategoryType:0 as number
 })
 const superiorsName = ref('') // 费用分类名称
 
@@ -147,6 +150,7 @@ const open = async (tenantId, category, id, detail) => {
 }
 defineExpose({ open }) // 提供 open 方法,用于打开弹窗
 
+
 /** 提交表单 */
 const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
 const submitForm = async () => {
@@ -157,9 +161,11 @@ const submitForm = async () => {
   // 提交请求
   formLoading.value = true
   try {
+    let frequencyCategory = dataForm.value.frequencyCategoryType == 1 ? '高' : '中/低'
     let params = {
       ...dataForm.value,
-      timeout: Number(dataForm.value.timeout)
+      timeout: Number(dataForm.value.timeout),
+      frequencyCategory:frequencyCategory
     }
     const res = dataForm.value.id ? await editNurseItem(params) : await addNurseItem(params)
     if (res) {
@@ -182,11 +188,14 @@ const resetForm = () => {
     image: '',
     type: '',
     price: undefined,
-    frequency: '',
+    frequencyType: '',
     timeout: '',
     status: 1,
     remark: '',
-    tenantId: ''
+    tenantId: '',
+    frequency:'',
+    frequencyCategory:'',
+    frequencyCategoryType:0 as number
   }
   formRef.value?.resetFields()
 }

+ 177 - 208
src/views/elderly/nursing/special-nurse-log/Detail.vue

@@ -1,7 +1,13 @@
 <template>
-  <Dialog v-model="dialogVisible" width="90%" @close="handleClosed" title="护理日志详情" class="form-tag-dialog" scroll>
-    <el-form class="nursing-log-detail" inline>
-    <!-- <template #content> -->
+  <Dialog
+    v-model="dialogVisible"
+    width="90%"
+    @close="handleClosed"
+    title="特殊照护任务完成日志"
+    class="form-tag-dialog"
+    scroll
+  >
+    <div class="special-nursing-log-detail">
       <div class="info-title">长者信息</div>
       <div class="info-wrap">
         <el-row :gutter="20">
@@ -10,18 +16,30 @@
           <el-col :span="8" :xs="24" class="header-item">床位号:{{ header.bedName || '-' }}</el-col>
         </el-row>
       </div>
-      <el-table :data="records" height="60vh" :header-cell-style="(data) => tableHeaderColor(data) || {}">
-        <el-table-column label="护理日期" prop="nurseDate" />
-        <el-table-column label="护理员" prop="nurse" />
-        <el-table-column label="护理项目" prop="nursingItemName" />
-        <el-table-column label="图片">
+      <div class="detail-toolbar mb-15px flex flex-wrap items-center gap-12px">
+        <span class="text-sm text-secondary">完成时间范围</span>
+        <el-date-picker
+          v-model="finishTimeRange"
+          type="daterange"
+          range-separator="至"
+          start-placeholder="开始日期"
+          end-placeholder="结束日期"
+          value-format="YYYY-MM-DD"
+        />
+        <el-button type="primary" :loading="detailLoading" @click="reloadDetail">查询</el-button>
+      </div>
+
+      <el-table v-loading="detailLoading" :data="pagedList" height="55vh" :header-cell-style="(d) => tableHeaderColor(d) || {}">
+        <el-table-column label="完成时间" prop="finishTime" min-width="160" show-overflow-tooltip />
+        <el-table-column label="备注" prop="remark" min-width="140" show-overflow-tooltip />
+        <el-table-column label="图片" min-width="200">
           <template #default="scope">
-            <div v-if="scope.row.image" class="image-list">
+            <div v-if="splitImages(scope.row.imageUrl).length" class="image-list">
               <el-image
-                v-for="(img, index) in scope.row.image.split(',').filter((item: string) => item)"
-                :key="index"
+                v-for="(img, index) in splitImages(scope.row.imageUrl)"
+                :key="`${img}-${index}`"
                 :src="img"
-                :preview-src-list="scope.row.image.split(',').filter((item: string) => item)"
+                :preview-src-list="splitImages(scope.row.imageUrl)"
                 :initial-index="Number(index)"
                 preview-teleported
                 fit="cover"
@@ -31,236 +49,187 @@
             <span v-else>-</span>
           </template>
         </el-table-column>
-        <el-table-column label="备注" prop="remark" />
-        <el-table-column label="创建时间" prop="createTime" />
-        <el-table-column label="操作" width="80" v-hasPermi="['nursing-log-list:delete']">
+        <el-table-column label="操作" width="90" align="center" fixed="right">
           <template #default="scope">
-            <el-button v-hasPermi="['special-nurse-log:revocation']" link type="danger" @click="openRevocation(scope.row.id)">撤销</el-button>
+            <el-button
+              v-hasPermi="['special-nurse-log:revocation']"
+              link
+              type="danger"
+              :disabled="planItemId(scope.row) == null"
+              :loading="revokeLoadingId === planItemId(scope.row)"
+              @click="handleRevoke(scope.row)"
+            >
+              撤销
+            </el-button>
           </template>
         </el-table-column>
       </el-table>
-    </el-form>
+      <div class="flex justify-end mt-12px">
+        <el-pagination
+          v-if="records.length > 0"
+          layout="total, sizes, prev, pager, next"
+          :total="records.length"
+          v-model:page-size="detailPageSize"
+          v-model:current-page="detailPageNo"
+          :page-sizes="[10, 20, 50, 100]"
+          background
+        />
+      </div>
+    </div>
     <template #footer>
       <el-button @click="handleClosed">关闭</el-button>
     </template>
   </Dialog>
 </template>
 <script setup lang="ts">
-import { getNursingLogDetail, deleteNursingLog } from '@/api/elderly/nursing'
+import { getSpecialNursingLogListByElderAndFinishTime } from '@/api/elderly/nursing/special-nursing-log'
+import type { ElderlySpecialNursingLogRespVO } from '@/api/elderly/nursing/special-nursing-log'
+import { updateSpecialPlanItem } from '@/api/elderly/nursing/special-plan'
+import { getTenantId } from '@/utils/auth'
 import { tableHeaderColor } from '@/utils/table'
-import { ElMessageBox } from 'element-plus'
-defineOptions({ name: 'NurseLogDetail' })
-const dialogVisible = ref(false)
+
+defineOptions({ name: 'SpecialNursingLogDetail' })
+
 const message = useMessage()
+const { t } = useI18n()
+const emit = defineEmits(['refresh'])
 
-const state = reactive({
-  header: {
-    elderName: '',
-    bedName: '',
-    nurseLevelName: ''
-  },
-  records: [],
+const dialogVisible = ref(false)
+const detailLoading = ref(false)
+const revokeLoadingId = ref<number | string | null>(null)
+
+const header = reactive({
+  elderName: '',
+  bedName: '',
+  nurseLevelName: ''
 })
-const { header, records } = toRefs(state)
-const resetHeader = reactive({ ...header.value })
-const currentElderId = ref<number | null>(null)
-const dateRange = ref<string[]>([] as string[])
 
-/** 打开弹窗 */
-const open = async (row: { elderId: number }, nurseDate: string[]) => {
-  dialogVisible.value = true
-  currentElderId.value = row.elderId
-  dateRange.value = nurseDate as string[]
-  await getDetail(row.elderId, dateRange.value)
+const records = ref<ElderlySpecialNursingLogRespVO[]>([])
+const finishTimeRange = ref<string[]>([])
+
+/** 详情内分页(接口为整列表,前端分页) */
+const detailPageNo = ref(1)
+const detailPageSize = ref(10)
+
+const defaultMonthRange = () => {
+  const now = new Date()
+  const start = new Date(now.getFullYear(), now.getMonth(), 1)
+  const end = new Date(now.getFullYear(), now.getMonth() + 1, 0)
+  const fmt = (d: Date) => d.toISOString().slice(0, 10)
+  return [fmt(start), fmt(end)]
 }
 
-const getDetail = async (elderId: number, nurseDate: string[]) => {
-  const res = await getNursingLogDetail({ elderId, start: nurseDate[0], end: nurseDate[1] })
-  header.value = res.header || { ...resetHeader }
-  records.value = res.records || []
+let currentElderId: number | string | undefined
+
+/** 撤销时更新计划明细,后端主键为特殊护理计划明细 id */
+const planItemId = (row: ElderlySpecialNursingLogRespVO) =>
+  row.specialPlanItemId != null ? Number(row.specialPlanItemId) : null
+
+const splitImages = (raw?: string) => {
+  if (!raw) return []
+  return String(raw)
+    .split(',')
+    .map((s) => s.trim())
+    .filter(Boolean)
 }
 
-defineExpose({ open }) // 提供 open 方法,用于打开弹窗
+const pagedList = computed(() => {
+  const start = (detailPageNo.value - 1) * detailPageSize.value
+  return records.value.slice(start, start + detailPageSize.value)
+})
+
+watch(
+  () => records.value.length,
+  () => {
+    detailPageNo.value = 1
+  }
+)
 
-const openRevocation = (id: number) => {
-  ElMessageBox.confirm('确定删除该护理记录?', '提示', {
-    confirmButtonText: '确 认',
-    cancelButtonText: '取 消'
-  })
-    .then(async () => {
-      const res = await deleteNursingLog(id)
-      if (res) {
-        message.success('删除成功')
-        if (currentElderId.value) {
-          await getDetail(currentElderId.value, dateRange.value)
-        }
-      }
+const reloadDetail = async () => {
+  if (currentElderId == null) return
+  if (!finishTimeRange.value?.length || finishTimeRange.value.length < 2) {
+    finishTimeRange.value = defaultMonthRange()
+  }
+  detailLoading.value = true
+  try {
+    const data = await getSpecialNursingLogListByElderAndFinishTime({
+      elderId: currentElderId,
+      finishTime: [finishTimeRange.value[0], finishTimeRange.value[1]],
+      tenantId: Number(getTenantId())
     })
-    .catch(() => {})
+    records.value = Array.isArray(data) ? data : []
+  } finally {
+    detailLoading.value = false
+  }
+}
+
+const handleRevoke = async (row: ElderlySpecialNursingLogRespVO) => {
+  const id = planItemId(row)
+  if (id == null) {
+    message.warning('缺少明细主键,无法撤销')
+    return
+  }
+  try {
+    await message.delConfirm('确认撤销该条完成记录?将恢复为未完成状态。')
+    revokeLoadingId.value = id
+    await updateSpecialPlanItem({ id, status: 0 })
+    message.success(t('common.operationSuccess'))
+    emit('refresh')
+    await reloadDetail()
+  } catch {
+    //
+  } finally {
+    revokeLoadingId.value = null
+  }
+}
+
+const open = async (row: Recordable) => {
+  currentElderId = row.elderId
+  header.elderName = row.elderName ?? ''
+  header.bedName = row.bedName ?? ''
+  header.nurseLevelName = row.nurseLevelName ?? ''
+  finishTimeRange.value = defaultMonthRange()
+  dialogVisible.value = true
+  await reloadDetail()
 }
 
+defineExpose({ open })
+
 const handleClosed = () => {
-  header.value = { ...resetHeader }
   records.value = []
+  header.elderName = ''
+  header.bedName = ''
+  header.nurseLevelName = ''
+  currentElderId = undefined
   dialogVisible.value = false
-  currentElderId.value = null
 }
-
 </script>
 <style lang="scss" scoped>
-.info-title{
-  font-weight: 600;
-  color: #303133;
-  margin: 10px 0 6px;
-}
-.info-wrap{
-  margin-bottom: 10px;
-  .time-round{
-    padding: 10px 0;
-    margin: 8px;
-    margin-left: 0;
-    background-color: #f7f7f7;
-    border-radius: 20px;
-    text-align: center;
+.special-nursing-log-detail {
+  .info-title {
+    font-weight: 600;
+    color: #303133;
+    margin: 10px 0 6px;
   }
-}
-.header-item{
-  color: #606266;
-  padding-bottom: 10px;
-}
-.image-list{
-  display: flex;
-  flex-wrap: wrap;
-  gap: 6px;
-}
-.image-item{
-  width: 48px;
-  height: 48px;
-  border-radius: 4px;
-}
-.el-card{
-  position: relative;
-  .header{
-    display: flex;
-    justify-content: space-between;
-    align-items: center;
-    margin-bottom: 8px;
-    .primary{
-      color: var(--el-color-primary) !important;
-    }
-  }
-    .no-bg{
-      background-color: var(--el-color-success-light-9);
-      color: var(--el-color-primary);
-      padding: 2px 5px;
-      border-radius: 50%;
-    }
-    .bg{
-      background-color: #b3b3b3;
-      color: #fff;
-      padding: 2px 5px;
-      border-radius: 50%;
-    .text{
-      font-weight: bold;
-    }
-    .f15{
-      font-size: 17px;
-    }
-  }
-  .grey{
-    background-color: #b3b3b3;
+  .info-wrap {
+    margin-bottom: 10px;
   }
-  &.success{
-    position: relative;
-    border: 1px solid var(--el-color-success-light-3);
-    &:before {
-        content: "";
-        position: absolute;
-        right: 0;
-        bottom: 0;
-        border: 25px solid var(--el-color-success-light-3);
-        border-top-color: transparent;
-        border-left-color: transparent;
-      }
-      &:after {
-        content: "已完成";
-        width: 36px;
-        height: 12px;
-        position: absolute;
-        right: 0px;
-        bottom: 10px;
-        transform: rotate(-45deg);
-        color: #fff;
-        font-size: 12px;
-      }
-  }
-}
-</style>
-<style lang="scss">
-.nursing-log-detail{
-  .el-card{
-    border-radius: 10px;
+  .header-item {
+    color: #606266;
+    padding-bottom: 10px;
   }
-  .card-item{
-    .el-card__body{
-      padding: 15px 0 15px 10px;
-      .title{
-        font-size: 18px;
-        font-weight: bold;
-        margin-bottom: 10px;
-      }
-      .left{
-        display: inline-block;
-        width: 10%;
-        height: 38vh;
-
-        .scene-item {
-          position: relative;
-          padding-left: 20px;
-          font-weight: 400;
-          font-size: 14px;
-          color: #7c7c7c;
-          // padding-bottom: 12px;
-          height: 46px;
-
-          &:last-child {
-            padding-bottom: 0px;
-          }
-          .timeline-item__tail {
-            position: absolute;
-            top: -46px;
-            left: 6px;
-            height: 100%;
-            border-left: 1px dotted var(--el-color-primary);
-          }
-  
-          .line::before {
-            content: '';
-            position: absolute;
-            top: -2px;
-            left: 2px;
-            width: 10px;
-            height: 10px;
-            border-radius: 50%;
-            background: var(--el-color-primary);
-          }
-        }
-
-      }
-      .right{
-        display: inline-block;
-        width: 90%;
-      }
-    }
+  .detail-toolbar .text-secondary {
+    color: var(--el-text-color-secondary);
   }
-  .info-title{
-    max-width: 300px;
+  .image-list {
+    display: flex;
+    flex-wrap: wrap;
+    gap: 6px;
   }
-  
-  .progress{
-    .el-progress__text{
-      min-width: auto !important;
-    }
+  .image-item {
+    width: 48px;
+    height: 48px;
+    border-radius: 4px;
   }
 }
 </style>

+ 37 - 50
src/views/elderly/nursing/special-nurse-log/index.vue

@@ -1,7 +1,5 @@
 <template>
-  <!-- 搜索 -->
   <ContentWrap>
-    <!-- 搜索工作栏 -->
     <el-form
       class="-mb-15px"
       :model="queryParams"
@@ -12,17 +10,12 @@
       <el-form-item prop="tenantIds">
         <TenantSelect v-model="queryParams.tenantIds" placeholder="请选择机构名称" prop="tenantIds" />
       </el-form-item>
-      <el-form-item label="长者名称" prop="elderName">
-        <TgInput @keyup.enter="handleQuery" v-model="queryParams.elderName" class="!w-240px" />
-      </el-form-item>
-      <el-form-item label="护理日期" prop="nurseDate">
-        <el-date-picker
-          end-placeholder="结束日期"
-          start-placeholder="开始日期"
-          type="daterange"
-          value-format="YYYY-MM-DD"
-          range-separator="至"
-          v-model="queryParams.nurseDate"
+      <el-form-item label="长者姓名" prop="elderName">
+        <TgInput
+          @keyup.enter="handleQuery"
+          v-model="queryParams.elderName"
+          placeholder="模糊搜索"
+          class="!w-240px"
         />
       </el-form-item>
       <el-form-item>
@@ -31,86 +24,80 @@
       </el-form-item>
     </el-form>
   </ContentWrap>
+
   <ContentWrap>
-    <Table2
-      v-loading="loading"
-      :data="list"
-      :columns="NurseLogColumns"
-      :queryParams="queryParams"
-    >
+    <Table2 v-loading="loading" :list="list" :columns="SpecialNursingLogColumns" :queryParams="queryParams">
       <template #pre="{ scope }">
-        <el-button v-hasPermi="['nursing-log-list:details']" link type="primary" @click="openDetail(scope)">查看</el-button>
+        <el-button
+          v-hasPermi="['nursing-log-list:details']"
+          link
+          type="primary"
+          @click="openDetail(scope)"
+        >
+          查看
+        </el-button>
       </template>
     </Table2>
-    <!-- 分页 -->
     <Pagination
       :total="total"
       v-model:page="queryParams.pageNo"
       v-model:limit="queryParams.pageSize"
       @pagination="getList"
     />
-
-    <Detail ref="detailRef" />
+    <Detail ref="detailRef" @refresh="getList" />
   </ContentWrap>
 </template>
+
 <script setup lang="ts">
-import { getNursingLogPage } from '@/api/elderly/nursing'
-import Detail from './Detail.vue';
-import { NurseLogColumns } from '../column'
+import { getSpecialNursingLogPage } from '@/api/elderly/nursing/special-nursing-log'
+import Detail from './Detail.vue'
+import { SpecialNursingLogColumns } from '../column'
 import { useUserStore } from '@/store/modules/user'
+
 const userStore = useUserStore()
 
-defineOptions({ name: 'NursingLog' })
-const getDefaultMonthRange = () => {
-  const now = new Date()
-  const start = new Date(now.getFullYear(), now.getMonth(), 1)
-  const end = new Date(now.getFullYear(), now.getMonth() + 1, 0)
-  const format = (date: Date) => date.toISOString().slice(0, 10)
-  return [format(start), format(end)]
-}
+defineOptions({ name: 'SpecialNursingLog' })
+
 const queryParams = reactive({
   pageNo: 1,
   pageSize: 10,
   elderName: '',
-  tenantIds: userStore.orgTenantId,
-  nurseDate: getDefaultMonthRange()
+  tenantIds: userStore.orgTenantId
 })
-const loading = ref(true) // 列表的加载中
-const total = ref(0) // 列表的总页数
-const list = ref([]) // 列表的数据
+const loading = ref(true)
+const total = ref(0)
+const list = ref([])
 const queryFormRef = ref()
 
-/** 搜索按钮操作 */
 const handleQuery = () => {
   queryParams.pageNo = 1
   getList()
 }
 
-/** 重置按钮操作 */
 const resetQuery = () => {
-  queryFormRef.value.resetFields()
-  queryParams.nurseDate = getDefaultMonthRange()
+  queryFormRef.value?.resetFields?.()
   handleQuery()
 }
 
-/** 查询列表 */
 const getList = async () => {
   loading.value = true
   try {
-    const data = await getNursingLogPage(queryParams)
-    list.value = data.list
-    total.value = data.total
+    const data = await getSpecialNursingLogPage(queryParams)
+    list.value = data.list ?? []
+    total.value = data.total ?? 0
   } finally {
     loading.value = false
   }
 }
 
 const detailRef = ref()
-const openDetail = (row:{}) => {
-  detailRef.value.open(row,queryParams.nurseDate)
+
+/** Table2 slot #pre:传入当前行 */
+const openDetail = (row: Recordable) => {
+  detailRef.value?.open(row)
 }
 
-onMounted(()=>{
+onMounted(() => {
   getList()
 })
 </script>

+ 143 - 180
src/views/elderly/nursing/special-nurse/Form.vue

@@ -3,7 +3,7 @@
     v-model="dialogVisible"
     :title="itemTitle"
     width="70%"
-    class="form-tag-dialog life-care-plan-form"
+    class="form-tag-dialog life-care-plan-form special-nursing-plan-form"
     @close="handleClosed"
     scroll
   >
@@ -25,28 +25,20 @@
                 :style="[{ width: dataForm.id ? '30%' : '100%' }]"
                 :toggleType="!!dataForm.id"
               />
-              <span v-show="dataForm.id">
-                {{ dataForm.bedName }}&nbsp;&nbsp;&nbsp;&nbsp;{{ dataForm.nurseLevelName }}
-              </span>
             </el-form-item>
           </el-col>
           <el-col :span="12" :xs="24">
             <el-form-item label="床位号" prop="bedName">
-             <el-input v-if="!isDetail" v-model="dataForm.bedName" disabled/>
-              <el-text v-else>{{dataForm.bedName}}</el-text>
+              <el-input v-if="!isDetail" v-model="dataForm.bedName" disabled />
+              <el-text v-else>{{ dataForm.bedName }}</el-text>
             </el-form-item>
           </el-col>
         </el-row>
         <el-row :gutter="20">
           <el-col :span="12" :xs="24">
             <el-form-item label="护理等级" prop="nurseLevelName">
-              <el-input v-if="!isDetail" v-model="dataForm. nurseLevelName" disabled/>
-              <el-text v-else>{{dataForm.nurseLevelName}}</el-text>
-            </el-form-item>
-          </el-col>
-          <el-col :span="12" :xs="24">
-            <el-form-item label="生效日期" prop="effectiveDate">
-              <TgDatePicker v-model="dataForm.effectiveDate" />
+              <el-input v-if="!isDetail" v-model="dataForm.nurseLevelName" disabled />
+              <el-text v-else>{{ dataForm.nurseLevelName }}</el-text>
             </el-form-item>
           </el-col>
         </el-row>
@@ -75,7 +67,7 @@
                 <el-checkbox
                   :label="p.nurseItemName"
                   v-model="p.checked"
-                  @change="(arg) => handleChangeCheckBox(arg, p)"
+                  @change="(v) => handleChangeCheckBox(!!v, p)"
                   v-if="!isDetail"
                 />
                 <div v-else class="itemName">{{ p.nurseItemName }}</div>
@@ -92,92 +84,120 @@
       >
     </template>
   </Dialog>
-  <lifeItem ref="itemRef" @success="getItemList" />
+  <lifeItem ref="itemRef" category-type-name="生活护理" @success="getItemList" />
 </template>
 
 <script setup lang="ts">
 import {
-  createNursingPlan,
-  updateNursingPlan,
-  getNursingPlanById,
-  findNurseItemListByName
-} from '@/api/elderly/nursing'
+  createSpecialNursingPlan,
+  updateSpecialNursingPlan,
+  getSpecialNursingPlan
+} from '@/api/elderly/nursing/special-plan'
+import type {
+  SpecialNursingPlanItemRespVO,
+  SpecialNursingPlanItemSaveReqVO,
+  SpecialNursingPlanSaveReqVO
+} from '@/api/elderly/nursing/special-plan'
+import { getTenantId } from '@/utils/auth'
+import { getElderInfoById } from '@/api/elderly/elder/elderly-Info'
 import lifeItem from './life-item-dialog.vue'
-import { planType } from '../types'
 
-defineOptions({ name: 'LifeCarePlanForm' })
+defineOptions({ name: 'SpecialNursingPlanForm' })
+
+interface PlanItemRow {
+  /** 明细表主键,编辑时回填 */
+  id?: number
+  nurseItemId: number | string
+  nurseItemName: string
+  checked?: boolean
+}
 
-const { t } = useI18n() // 国际化
-const message = useMessage() // 消息弹窗
+const { t } = useI18n()
+const message = useMessage()
 const dialogVisible = ref(false)
-const state = reactive<planType>({
-  dataForm: {
-    id: '',
-    elderId: '',
-    elderName: '',
-    bedName: '',
-    nurseLevelName: '',
-    effectiveDate: '',
-    items: [],
-    extraItems: [],
-  },
-  dataRule: {
-    elderId: [{ required: true, message: '长者姓名不能为空', trigger: 'blur' }],
-    effectiveDate: [{ required: true, message: '生效日期不能为空', trigger: 'blur' }]
-  },
+const dataForm = ref({
+  id: '' as number | string | '',
+  elderId: '' as number | string | '',
+  elderName: '',
+  bedName: '',
+  nurseLevelName: '',
+  items: [] as PlanItemRow[],
+  tenantId: undefined as number | undefined
+})
+const resetSnapshot = reactive({
+  id: '',
+  elderId: '',
+  elderName: '',
+  bedName: '',
+  nurseLevelName: '',
+  items: [] as PlanItemRow[],
+  tenantId: undefined as number | undefined
+})
+const dataRule = ref({
+  elderId: [{ required: true, message: '长者姓名不能为空', trigger: 'blur' }]
 })
-const { dataForm, dataRule } = toRefs(state)
-const resetFormField =  reactive({ ...dataForm.value })
-const route = useRoute()
 const formLoading = ref(false)
 const isDetail = ref(false)
 
-// 弹窗标题
 const itemTitle = computed(() => {
   return isDetail.value ? '详情' : !dataForm.value.id ? '新增' : '修改'
 })
 
-/** 打开弹窗 */
-const open = async (id, detail) => {
-  dataForm.value.id=''
-  dialogVisible.value = true
-  isDetail.value = detail
-  if (id) {
-    const res = await getNursingPlanById(id)
-    // 默认勾选护理项目
-    res.items.map((item) => {
-      item.checked = true
-    })
-    res.extraItems.map((item) => {
-      item.checked = true
-    })
-    // 匹配额外项目价格
-    dataForm.value = res
-    await formatExtraItem()
-    dataForm.value.items = res.items?.length > 0 ? res.items : res.extraItems
-    console.log('dataForm.value', dataForm.value)
+const normalizeItemsFromDetail = (list?: SpecialNursingPlanItemRespVO[]): PlanItemRow[] =>
+  (list || []).map((row) => ({
+    id: row.id != null ? Number(row.id) : undefined,
+    nurseItemId: row.nurseItemId as number | string,
+    nurseItemName: row.nurseItemName || '',
+    checked: true
+  }))
+
+/** 编辑/详情时计划接口不包含床位与护理等级,根据长者详情补全 */
+const fillElderBedAndNurseLevel = async (elderId?: number | string) => {
+  if (elderId == null || elderId === '') return
+  try {
+    const elder = (await getElderInfoById(elderId)) as Recordable
+    if (!elder) return
+    const bed = elder.bedName ?? elder.bed
+    const level = elder.nurseLevelName ?? elder.nursingLevelName ?? elder.nursingLevel ?? ''
+    if (bed !== undefined && bed !== null && bed !== '') {
+      dataForm.value.bedName = String(bed)
+    }
+    if (level !== undefined && level !== null && String(level).length) {
+      dataForm.value.nurseLevelName = String(level)
+    }
+  } catch {
+    // 静默:仍展示计划中已有或空白
   }
 }
-defineExpose({ open }) // 提供 open 方法,用于打开弹窗
 
-const formatExtraItem = async () => {
-  if (dataForm.value.extraItems.length) {
-    const res = await findNurseItemListByName({
-      categoryTypeName: '生活护理',
-      nurseItemName: ''
-    })
-    dataForm.value.extraItems.map((e) => {
-      res.map((r) => {
-        if (e.nurseItemId == r.id) {
-          e.price = r.price
-        }
-      })
-    })
+const open = async (id?: number | string, detail?: boolean, prefetch?: Recordable) => {
+  dataForm.value = JSON.parse(JSON.stringify(resetSnapshot))
+  dataForm.value.id = ''
+  isDetail.value = !!detail
+  dialogVisible.value = true
+  if (!id) return
+  try {
+    const res = await getSpecialNursingPlan(id)
+    dataForm.value.id = res.id ?? id
+    dataForm.value.elderId = res.elderId ?? ''
+    dataForm.value.elderName = res.elderName ?? ''
+    dataForm.value.tenantId = res.tenantId
+    dataForm.value.bedName = res.bedName ?? prefetch?.bedName ?? ''
+    dataForm.value.nurseLevelName = res.nurseLevelName ?? prefetch?.nurseLevelName ?? ''
+    await fillElderBedAndNurseLevel(res.elderId)
+    dataForm.value.items = normalizeItemsFromDetail(res.items || [])
+    if (!dataForm.value.items.length && prefetch?.elderId) {
+      message.warning('该计划暂未返回明细,请补充护理项目后保存')
+    }
+  } catch {
+    dialogVisible.value = false
+    message.error('获取特殊护理计划详情失败')
   }
 }
+defineExpose({ open })
 
 const handleClosed = () => {
-  dataForm.value = {...resetFormField}
+  dataForm.value = JSON.parse(JSON.stringify(resetSnapshot))
   dialogVisible.value = false
 }
 
@@ -186,87 +206,81 @@ const handleAdd = () => {
   itemRef.value.open(dataForm.value.items)
 }
 
-const getItemList = (val) => {
-  // 添加的内容放到护理项目中
-  val.map((item) => {
+const getItemList = (val: Recordable[]) => {
+  val.forEach((item) => {
     dataForm.value.items.push({
       nurseItemId: item.id,
       nurseItemName: item.itemName,
-      frequency: 1,
-      frequencyUnit: item.frequency,
-      operatingMode: item.operatingMode,
-      checked: true,
-      price: item.price,
-      isExtra: item.type == 1 ? 1 : 0,
+      checked: true
     })
   })
 }
 
-// 选择长者
-const handleElder = async (item) => {
+const handleElder = (item: Recordable) => {
   dataForm.value.elderName = item.elderName
   dataForm.value.bedName = item.bedName
   dataForm.value.nurseLevelName = item.nurseLevelName
 }
 
-const handleChangeCheckBox = (val, item) => {
+const handleChangeCheckBox = (val: boolean, item: PlanItemRow) => {
   if (!val) {
-    const index = dataForm.value.items.findIndex((p) => item.nurseItemId == p.nurseItemId)
-    dataForm.value.items.splice(index, 1)
+    const index = dataForm.value.items.findIndex((p) => p.nurseItemId == item.nurseItemId)
+    if (index > -1) dataForm.value.items.splice(index, 1)
   }
 }
 
 const formRef = ref()
-const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
-const submitForm = async () => {
-  // 校验表单
-  if (!formRef.value) return
-  const valid = await formRef.value.validate()
-  if (!valid) return
+const emit = defineEmits(['success'])
 
+const buildTenantId = () => Number(getTenantId())
 
+const submitForm = async () => {
+  if (!formRef.value) return
   try {
-    formLoading.value = true
-    const params = {
-      type: route.path.indexOf('medical-care-plan') > -1 ? 2 : 1, // 2:医疗护理 1 生活护理,
-      ...dataForm.value,
+    await formRef.value.validate()
+  } catch {
+    return
+  }
+  const tenantId = buildTenantId()
+  const planId = dataForm.value.id ? Number(dataForm.value.id) : undefined
+  const itemsPayload: SpecialNursingPlanItemSaveReqVO[] = []
+  ;(dataForm.value.items || []).forEach((row) => {
+    if (!row.checked && row.checked !== undefined) return
+    const one: SpecialNursingPlanItemSaveReqVO = {
+      nurseItemId: Number(row.nurseItemId),
+      nurseItemName: row.nurseItemName,
+      tenantId
     }
-    const res = params.id
-      ? await updateNursingPlan(params)
-      : await createNursingPlan(params)
-    if (res) {
-      message.success(t('common.updateSuccess'))
-      handleClosed()
-      // 发送操作成功的事件
-      emit('success')
+    if (planId) {
+      one.specialNursingPlanId = planId
+      if (row.id != null) one.id = row.id
     }
+    itemsPayload.push(one)
+  })
+  const payload: SpecialNursingPlanSaveReqVO = {
+    elderId: Number(dataForm.value.elderId),
+    elderName: dataForm.value.elderName || undefined,
+    tenantId,
+    items: itemsPayload
+  }
+  if (planId) payload.id = planId
+
+  try {
+    formLoading.value = true
+    await (planId ? updateSpecialNursingPlan(payload) : createSpecialNursingPlan(payload))
+    message.success(planId ? t('common.updateSuccess') : t('common.createSuccess'))
+    handleClosed()
+    emit('success')
   } finally {
     formLoading.value = false
   }
 }
 </script>
 <style lang="scss" scoped>
-.life-care-plan-form {
-  .sInput {
-    border: none;
-    border-bottom: 1px solid #999;
-    outline: none;
-    background-color: transparent;
-  }
-  .itemName {
-    line-height: 30px;
-  }
-  // .border {
-  //   padding: 1px 11px;
-  //   box-shadow: 0 0 0 1px var(--el-input-border-color, var(--el-border-color)) inset;
-  //   border-radius: var(--el-input-border-radius, var(--el-border-radius-base));
-  // }
+.special-nursing-plan-form {
   .item-row {
     display: flex;
-    justify-content: space-between;
     align-items: center;
-    gap: 12px;
-    flex-wrap: wrap;
   }
   .item-left {
     display: flex;
@@ -277,67 +291,16 @@ const submitForm = async () => {
     box-shadow: 0 0 0 1px var(--el-input-border-color, var(--el-border-color)) inset;
     border-radius: var(--el-input-border-radius, var(--el-border-radius-base));
   }
-  .item-meta {
-    display: flex;
-    gap: 8px;
-  }
-  .item-time {
-    display: flex;
-    align-items: center;
-    gap: 8px;
-  }
-  .time-label {
-    white-space: nowrap;
-    color: var(--el-text-color-secondary);
-  }
-  .detail-time {
-    color: var(--el-text-color-regular);
-  }
-  .border-warning {
-    border-radius: var(--el-input-border-radius, var(--el-border-radius-base));
-    box-shadow: 0 0 0 1px var(--el-input-border-color, var(--el-color-warning)) inset;
-  }
-  .warning {
-    background-color: var(--el-color-warning-light-9);
-    border-radius: var(--el-input-border-radius, var(--el-border-radius-base));
-    box-shadow: 0 0 0 1px var(--el-input-border-color, var(--el-color-warning)) inset;
+  .itemName {
+    line-height: 30px;
   }
   .info {
     position: relative;
-
     .left {
       position: absolute;
       left: 150px;
       top: 8px;
     }
-
-    .right {
-      position: absolute;
-      right: 10px;
-      top: 10px;
-    }
-
-    .el-dropdown-link {
-      cursor: pointer;
-      color: var(--el-color-primary);
-      display: flex;
-      align-items: center;
-    }
-  }
-}
-</style>
-<style lang="scss">
-.life-care-plan-form {
-  .extra {
-    .el-select__wrapper {
-      box-shadow: 0 0 0 1px var(--el-color-warning) inset;
-      background-color: var(--el-color-warning-light-9);
-    }
-    .el-input-number {
-      .el-input__wrapper {
-        box-shadow: 0 0 0 1px var(--el-color-warning) inset;
-      }
-    }
   }
 }
 </style>

+ 0 - 255
src/views/elderly/nursing/special-nurse/Print.vue

@@ -1,255 +0,0 @@
-<template>
-  <Dialog v-model="dialogVisible" title="打印" width="70%" @close="handleClosed">
-    <div class="life-care-print" id="printArea">
-      <div class="title">{{ tenantName }}</div>
-      <div class="desc">护理信息</div>
-      <el-row :gutter="20">
-        <el-col :span="8" class="pb4">长者姓名:{{ dataForm.elderName }}</el-col>
-        <el-col :span="8" class="pb4"
-          >性别:{{ getDictLabel(DICT_TYPE.SYSTEM_USER_SEX, dataForm.elderSex) }}</el-col
-        >
-        <el-col :span="8" class="pb4">床位号:{{ dataForm.bedName }}</el-col>
-        <el-col :span="8" class="pb4">饮食类型:{{ dataForm.dietaryType }}</el-col>
-        <el-col :span="8" class="pb4">饮食方式:{{ dataForm.diet }}</el-col>
-        <el-col :span="8" class="pb4">饮食禁忌:{{ dataForm.dietaryTaboo }}</el-col>
-        <el-col class="pb4">风险防范:{{ dataForm.riskPrevention }}</el-col>
-        <el-col class="pb4">照护特点:{{ dataForm.nursingCharacteristics }}</el-col>
-      </el-row>
-      <p class="content-title">生活护理</p>
-      <el-row :gutter="30">
-        <el-col
-          :xs="24"
-          :sm="24"
-          :md="24"
-          :lg="12"
-          class="mb5"
-          v-for="(p, i) in dataForm.items"
-          :key="i"
-        >
-          <el-row>
-            <el-col :span="8">
-              <div :class="['itemName', { warning: p.isExtra }]">
-                <span>{{ p.nurseItemName }}</span>
-              </div>
-            </el-col>
-            <el-col :span="4">
-              <TgInputNumber
-                placeholder="请输入频次"
-                v-model="p.frequencyUnit"
-                :class="['itemName', { warning: p.isExtra }]"
-              />
-            </el-col>
-            <el-col :span="6">
-              <TgSelect
-                placeholder="请选择频次单位"
-                v-model="p.frequency"
-                :list="getDictOptions(DICT_TYPE.NURSING_FREQUENCY_TYPE)"
-                :class="['itemName', { warning: p.isExtra }]"
-              >
-                <el-option
-                  v-for="(dict, d) in getIntDictOptions(DICT_TYPE.NURSING_FREQUENCY_TYPE)"
-                  :key="d"
-                  :label="dict.label"
-                  :value="dict.value"
-                />
-              </TgSelect>
-            </el-col>
-            <el-col :span="6">
-              <TgSelect
-                placeholder="请选择操作方式"
-                v-model="p.operatingMode"
-                :list="getDictOptions(DICT_TYPE.NURSING_OPERATING)"
-                :class="['itemName', { warning: p.isExtra }]"
-              >
-                <el-option
-                  v-for="(dict, d) in getStrDictOptions(DICT_TYPE.NURSING_OPERATING)"
-                  :key="d"
-                  :label="dict.label"
-                  :value="dict.value"
-                />
-              </TgSelect>
-            </el-col>
-          </el-row>
-        </el-col>
-      </el-row>
-      <p class="content-title">医疗护理</p>
-      <el-row :gutter="30">
-        <el-col
-          :xs="24"
-          :sm="24"
-          :md="24"
-          :lg="12"
-          class="mb5"
-          v-for="(p, i) in dataForm.medicalItem"
-          :key="i"
-        >
-          <el-row>
-            <el-col :span="8">
-              <div :class="['itemName', { warning: p.isExtra }]">
-                <span>{{ p.nurseItemName }}</span>
-              </div>
-            </el-col>
-            <el-col :span="4">
-              <TgInputNumber
-                placeholder="请输入频次"
-                v-model="p.frequencyUnit"
-                :class="['itemName', { warning: p.isExtra }]"
-              />
-            </el-col>
-            <el-col :span="6">
-              <TgSelect
-                placeholder="请选择频次单位"
-                v-model="p.frequency"
-                :list="getDictOptions(DICT_TYPE.NURSING_FREQUENCY_TYPE)"
-                :class="['itemName', { warning: p.isExtra }]"
-              >
-                <el-option
-                  v-for="(dict, d) in getIntDictOptions(DICT_TYPE.NURSING_FREQUENCY_TYPE)"
-                  :key="d"
-                  :label="dict.label"
-                  :value="dict.value"
-                />
-              </TgSelect>
-            </el-col>
-            <el-col :span="6">
-              <TgSelect
-                placeholder="请选择操作方式"
-                v-model="p.operatingMode"
-                :list="getDictOptions(DICT_TYPE.NURSING_OPERATING)"
-                :class="['itemName', { warning: p.isExtra }]"
-              >
-                <el-option
-                  v-for="(dict, d) in getStrDictOptions(DICT_TYPE.NURSING_OPERATING)"
-                  :key="d"
-                  :label="dict.label"
-                  :value="dict.value"
-                />
-              </TgSelect>
-            </el-col>
-          </el-row>
-        </el-col>
-      </el-row>
-    </div>
-    <template #footer>
-      <el-button @click="handleClosed">关闭</el-button>
-      <el-button v-print="print" type="primary">打印</el-button>
-    </template>
-
-    <Print ref="printRef" />
-  </Dialog>
-</template>
-<script setup lang="ts">
-import { useUserStore } from '@/store/modules/user'
-import {
-  DICT_TYPE,
-  getDictLabel,
-  getDictOptions,
-  getStrDictOptions,
-  getIntDictOptions
-} from '@/utils/dict'
-import Print from './Print.vue'
-defineOptions({ name: 'lifeCarePlanPrint' })
-const tenantName = useUserStore().getTenantName // 当前登录的编号
-const dialogVisible = ref(false)
-const state = reactive<{
-  dataForm: {
-    elderName?: string
-    elderSex?: number
-    bedName?: string
-    dietaryType?: string
-    diet?: string
-    riskPrevention?: string
-    dietaryTaboo?: string
-    nursingCharacteristics?: string
-    medicalItem?: {
-      isExtra: number
-      nurseItemName: string
-      frequencyUnit: string
-      frequency: string
-      operatingMode: string
-    }[]
-    items?: {
-      isExtra: number
-      nurseItemName: string
-      frequencyUnit: string
-      frequency: string
-      operatingMode: string
-    }[]
-  }
-}>({
-  dataForm: {}
-})
-const { dataForm } = toRefs(state)
-const resetFormField =  reactive({ ...dataForm.value })
-
-/** 打开弹窗 */
-const open = async (item) => {
-  dialogVisible.value = true
-  dataForm.value = item
-}
-defineExpose({ open }) // 提供 open 方法,用于打开弹窗
-
-const handleClosed = () => {
-  dataForm.value = {...resetFormField}
-  dialogVisible.value = false
-}
-
-// 打印
-const print = {
-  id: 'printArea',
-  extraCss: '',
-  popTitle: '', // 打印配置页上方的标题
-  extraHead: '', // 最上方的头部文字,附加在head标签上的额外标签,使用逗号分割
-  preview: false, // 是否启动预览模式,默认是false
-  previewTitle: '对账单预览', // 打印预览的标题
-  previewPrintBtnLabel: '预览结束,开始打印', // 打印预览的标题下方的按钮文本,点击可进入打印
-  zIndex: 20002, // 预览窗口的z-index,默认是20002,最好比默认值更高
-  previewBeforeOpenCallback() {
-    console.log('正在加载预览窗口!')
-  }, // 预览窗口打开之前的callback
-  previewOpenCallback() {
-    console.log('已经加载完预览窗口,预览打开了!')
-  }, // 预览窗口打开时的callback
-  beforeOpenCallback() {
-    console.log('开始打印之前!')
-  }, // 开始打印之前的callback
-  openCallback() {
-    console.log('执行打印了!')
-  }, // 调用打印时的callback
-  closeCallback() {
-    console.log('关闭了打印工具!')
-  }, // 关闭打印的callback(无法区分确认or取消)
-  clickMounted() {
-    console.log('点击v-print绑定的按钮了!')
-  },
-  standard: ''
-}
-</script>
-<style lang="scss" scoped>
-.life-care-print {
-  .title {
-    text-align: center;
-    font-size: 22px;
-    font-weight: bolder;
-    margin: 15px 0;
-  }
-  .desc {
-    font-size: 18px;
-    text-align: center;
-    margin: 10px 0;
-  }
-  .content-title {
-    font-size: 15px;
-  }
-  .itemName {
-    line-height: 30px;
-    padding: 1px 11px;
-    box-shadow: 0 0 0 1px var(--el-input-border-color, var(--el-border-color)) inset;
-    border-radius: var(--el-input-border-radius, var(--el-border-radius-base));
-  }
-  .warning {
-    box-shadow: 0 0 0 1px var(--el-color-warning) inset;
-    background-color: var(--el-color-warning-light-9);
-  }
-}
-</style>

+ 30 - 47
src/views/elderly/nursing/special-nurse/index.vue

@@ -11,19 +11,6 @@
       <el-form-item label="长者姓名" prop="elderName">
         <el-input @keyup.enter="handleQuery" v-model="queryParams.elderName" placeholder="请输入长者姓名" class="!w-240px" />
       </el-form-item>
-      <el-form-item label="床位" prop="bedId">
-        <SelectRoom v-model="queryParams.bedId" class="!w-240px" :tId="queryParams.tenantIds" />
-      </el-form-item>
-      <el-form-item label="护理等级" prop="nurseLevelId">
-        <TgSelect v-model="queryParams.nurseLevelId" class="!w-240px">
-          <el-option
-            v-for="(n, i) in nurseLevelList"
-            :key="i"
-            :label="n.nurseLevelName"
-            :value="n.id"
-          />
-        </TgSelect>
-      </el-form-item>
       <el-form-item>
         <el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
         <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
@@ -37,11 +24,12 @@
     <Table2
       v-loading="loading"
       :data="list"
-      :columns="LifeCarePlanColumns"
+      :columns="SpecialNursingPlanColumns"
       :queryParams="queryParams"
       @row-click="handleRowClick"
       @edit="openForm"
       @detail="(arg) => openForm(arg, true)"
+      @del="handleDelete"
     />
 
     <!-- 分页 -->
@@ -56,35 +44,34 @@
   <Form ref="formRef" @success="getList" />
 </template>
 <script lang="ts" setup>
-import { getnursingPlanPage, getNursingLevelPage } from '@/api/elderly/nursing'
-import { LifeCarePlanColumns } from '../column'
-import {useUserStore} from "@/store/modules/user";
+import { getSpecialNursingPlanPage, deleteSpecialNursingPlan } from '@/api/elderly/nursing/special-plan'
+import { SpecialNursingPlanColumns } from '../column'
+import { useUserStore } from '@/store/modules/user'
 import Form from './Form.vue'
 
-defineOptions({ name: 'LifeCarePlan' })
+defineOptions({ name: 'SpecialNursingPlan' })
 
+const message = useMessage()
+const { t } = useI18n()
 const userStore = useUserStore()
 const route = useRoute()
 
-const loading = ref(true) // 列表的加载中
-const total = ref(0) // 列表的总页数
+const loading = ref(true)
+const total = ref(0)
 const list = ref([]) // 列表的数据
 const queryParams = reactive({
   pageNo: 1,
   pageSize: 10,
-  nurseLevelId: undefined,
-  bedId: undefined,
-  elderName: "",
-  tenantIds: userStore.orgTenantId,
-  type: route.path.indexOf('medical-care-plan') > -1 ? 2 : 1, // 2:医疗护理 1 生活护理
+  elderName: '',
+  tenantIds: userStore.orgTenantId
 })
-const queryFormRef = ref() // 搜索的表单
+const queryFormRef = ref()
 
 /** 查询列表 */
 const getList = async () => {
   loading.value = true
   try {
-    const data = await getnursingPlanPage(queryParams)
+    const data = await getSpecialNursingPlanPage(queryParams)
     list.value = data.list
     total.value = data.total
   } finally {
@@ -100,40 +87,36 @@ const handleQuery = () => {
 
 /** 重置按钮操作 */
 const resetQuery = () => {
-  queryFormRef.value.resetFields()
+  queryFormRef.value?.resetFields?.()
   handleQuery()
 }
 
-/** 添加/修改操作 */
+/** 添加/修改操作 — Table2 @edit/@detail 传入整行 */
 const formRef = ref()
-const openForm = (row: any = {}, isDetail: boolean = false) => {
-  formRef.value.open(row.id, isDetail, false)
+const openForm = (row: Recordable = {}, isDetail: boolean = false) => {
+  const r = row && typeof row === 'object' && 'id' in row ? row : {}
+  formRef.value?.open(r.id, isDetail, r)
+}
+
+/** 删除 */
+const handleDelete = async (id: string | number) => {
+  try {
+    await message.delConfirm()
+    await deleteSpecialNursingPlan(id)
+    message.success(t('common.delSuccess'))
+    await getList()
+  } catch {}
 }
 
-// 复制
 const currItem = ref({})
 const handleRowClick = (val) => {
   currItem.value = val
 }
 
-const nurseLevelList = ref<{ nurseLevelName: string; id: string | number }[]>([])
-const getNurseLevelList = async () => {
-  try {
-    const data = await getNursingLevelPage({
-      pageNum: 1,
-      pageSize: 200
-    })
-    nurseLevelList.value = data.list
-  } finally {
-  }
-}
-
-/** 初始化 **/
 onMounted(() => {
-  if(route.query && route.query.elderName){
+  if (route.query?.elderName) {
     queryParams.elderName = route.query.elderName as string
   }
   getList()
-  getNurseLevelList()
 })
 </script>

+ 32 - 26
src/views/elderly/nursing/special-nurse/life-item-dialog.vue

@@ -14,13 +14,6 @@
         <el-table-column prop="itemName" label="项目名称" />
         <el-table-column prop="cName" label="护理分类" align="center" />
         <el-table-column prop="categoryName" label="项目分类" align="center" />
-        <el-table-column prop="frequencyUnit" label="频次" align="center" />
-        <el-table-column prop="frequencyType" label="频次类型" align="center" />
-        <!-- <el-table-column prop="fee" label="是否额外收费" align="center">
-          <template #default="scope">
-            <TgSwitch v-model="scope.row.fee"/>
-          </template>
-        </el-table-column> -->
         <el-table-column prop="type" label="收费项目" align="center">
           <template #default="scope">
             {{ getDictLabel(DICT_TYPE.COMMON_STATUS6, scope.row.type) }}
@@ -31,9 +24,9 @@
             {{ formatNum(scope.row.price) }}
           </template>
         </el-table-column>
-        <el-table-column prop="frequency" label="收费单位" align="center">
+        <el-table-column prop="frequencyType" label="频次计量单位" align="center">
           <template #default="scope">
-            {{ getDictLabel(DICT_TYPE.NURSING_FREQUENCY_TYPE, scope.row.frequency) }}
+            {{ getDictLabel(DICT_TYPE.NURSING_FREQUENCY_TYPE, scope.row.frequencyType) }}
           </template>
         </el-table-column>
       </el-table>
@@ -49,23 +42,38 @@ import { findNurseItemListByName } from '@/api/elderly/nursing'
 import { DICT_TYPE, getDictLabel } from '@/utils/dict'
 import { formatNum } from '@/utils/formatter'
 import { itemsType } from '../types'
-defineOptions({ name: 'LifeCarePlanNurseItemForm' })
+defineOptions({ name: 'SpecialCarePlanNurseItemForm' })
+
+const props = defineProps({
+  categoryTypeName: {
+    type: String,
+    default: ''
+  }
+})
+
 const dialogVisible = ref(false)
-const list = ref([])
+const list = ref<any[]>([])
 const formLoading = ref(false)
 const route = useRoute()
+const resolvedCategoryTypeName = computed(
+  () =>
+    props.categoryTypeName ||
+    (route.path.indexOf('medical-care-plan') > -1 ? '医疗护理' : '生活护理')
+)
 const dataForm = reactive({
-  categoryTypeName: route.path.indexOf('medical-care-plan') > -1 ? '医疗护理' : '生活护理',
+  categoryTypeName: '生活护理',
   nurseItemName: ''
 })
+
 const exitArr = ref<itemsType[]>([])
 /** 打开弹窗 */
-const open = async (arr) => {
+const open = async (arr: itemsType[]) => {
+  dataForm.categoryTypeName = resolvedCategoryTypeName.value
   dialogVisible.value = true
-  exitArr.value = arr
+  exitArr.value = arr || []
   handleQuery()
 }
-defineExpose({ open }) // 提供 open 方法,用于打开弹窗
+defineExpose({ open })
 
 const handleClosed = () => {
   list.value = []
@@ -77,36 +85,34 @@ const loading = ref(false)
 
 const formRef = ref()
 const resetQuery = () => {
-  formRef.value.resetFields()
+  formRef.value?.resetFields?.()
   handleQuery()
 }
 
 const handleQuery = async () => {
   loading.value = true
   try {
+    dataForm.categoryTypeName = resolvedCategoryTypeName.value
     const res = await findNurseItemListByName(dataForm)
-    // 做比较显示没有相同添加的数据
-    exitArr.value.map((item) => {
-      res.map((r) => {
+    exitArr.value?.forEach((item) => {
+      res?.forEach((r) => {
         if (r.id == item.nurseItemId) {
           r.same = true
         }
       })
     })
-    const arr: any = []
-    res.map((item) => {
+    const arr: any[] = []
+    res?.forEach((item) => {
       if (!item.same) {
         arr.push(item)
       }
     })
-    arr.map((item) => {
-      ;(item.cName = dataForm.categoryTypeName),
-        // item.fee = false // 额外收费
-        (item.flag = false) // 用于判断这个值是否加到护理项目中
+    arr.forEach((item) => {
+      item.cName = dataForm.categoryTypeName
+      item.flag = false
     })
     list.value = arr
   } finally {
-    // table中也添加多少条数据
     loading.value = false
   }
 }

+ 4 - 12
src/views/elderly/nursing/types.ts

@@ -86,30 +86,22 @@ export interface planType {
     bedName: string
     nurseLevelName: string
     effectiveDate: string
-    // diet: string
-    // dietaryType: string
-    // riskPrevention: string
-    // nursingCharacteristics: string
-    // dietaryTaboo: string[]
     items: itemsType[]
     extraItems: itemsType[]
   }
   dataRule: FormRules
-  // tempCopyForm: {
-  //   items: itemsType[]
-  //   extraItems: itemsType[]
-  // }
 }
 
 export interface itemsType {
   isExtra: number
   nurseItemName: string
-  frequencyUnit: string
   frequency: number
-  operatingMode: string
   price?: number
   checked?: boolean
   nurseItemId: string
-  startTime?: string
+  beginTime?: string | number
   nursingPlanId?: string
+  frequencyType: number
+  frequencyCategory: string
+  frequencyCategoryType: number
 }