ソースを参照

增加特殊照护模块喝日志模块

xiongxing 1 ヶ月 前
コミット
bccadb394e

+ 266 - 0
src/views/elderly/nursing/special-nurse-log/Detail.vue

@@ -0,0 +1,266 @@
+<template>
+  <Dialog v-model="dialogVisible" width="90%" @close="handleClosed" title="护理日志详情" class="form-tag-dialog" scroll>
+    <el-form class="nursing-log-detail" inline>
+    <!-- <template #content> -->
+      <div class="info-title">长者信息</div>
+      <div class="info-wrap">
+        <el-row :gutter="20">
+          <el-col :span="8" :xs="24" class="header-item">长者名称:{{ header.elderName || '-' }}</el-col>
+          <el-col :span="8" :xs="24" class="header-item">护理等级:{{ header.nurseLevelName || '-' }}</el-col>
+          <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="图片">
+          <template #default="scope">
+            <div v-if="scope.row.image" class="image-list">
+              <el-image
+                v-for="(img, index) in scope.row.image.split(',').filter((item: string) => item)"
+                :key="index"
+                :src="img"
+                :preview-src-list="scope.row.image.split(',').filter((item: string) => item)"
+                :initial-index="Number(index)"
+                preview-teleported
+                fit="cover"
+                class="image-item"
+              />
+            </div>
+            <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']">
+          <template #default="scope">
+            <el-button link type="danger" @click="handleDelete(scope.row.id)">删除</el-button>
+          </template>
+        </el-table-column>
+      </el-table>
+    </el-form>
+    <template #footer>
+      <el-button @click="handleClosed">关闭</el-button>
+    </template>
+  </Dialog>
+</template>
+<script setup lang="ts">
+import { getNursingLogDetail, deleteNursingLog } from '@/api/elderly/nursing'
+import { tableHeaderColor } from '@/utils/table'
+import { ElMessageBox } from 'element-plus'
+defineOptions({ name: 'NurseLogDetail' })
+const dialogVisible = ref(false)
+const message = useMessage()
+
+const state = reactive({
+  header: {
+    elderName: '',
+    bedName: '',
+    nurseLevelName: ''
+  },
+  records: [],
+})
+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 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 || []
+}
+
+defineExpose({ open }) // 提供 open 方法,用于打开弹窗
+
+const handleDelete = (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)
+        }
+      }
+    })
+    .catch(() => {})
+}
+
+const handleClosed = () => {
+  header.value = { ...resetHeader }
+  records.value = []
+  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;
+  }
+}
+.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;
+  }
+  &.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;
+  }
+  .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%;
+      }
+    }
+  }
+  .info-title{
+    max-width: 300px;
+  }
+  
+  .progress{
+    .el-progress__text{
+      min-width: auto !important;
+    }
+  }
+}
+</style>

+ 116 - 0
src/views/elderly/nursing/special-nurse-log/index.vue

@@ -0,0 +1,116 @@
+<template>
+  <!-- 搜索 -->
+  <ContentWrap>
+    <!-- 搜索工作栏 -->
+    <el-form
+      class="-mb-15px"
+      :model="queryParams"
+      ref="queryFormRef"
+      :inline="true"
+      label-width="80px"
+    >
+      <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>
+      <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>
+      </el-form-item>
+    </el-form>
+  </ContentWrap>
+  <ContentWrap>
+    <Table2
+      v-loading="loading"
+      :data="list"
+      :columns="NurseLogColumns"
+      :queryParams="queryParams"
+    >
+      <template #pre="{ scope }">
+        <el-button v-hasPermi="['nursing-log-list:detail']" 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" />
+  </ContentWrap>
+</template>
+<script setup lang="ts">
+import { getNursingLogPage } from '@/api/elderly/nursing'
+import Detail from './Detail.vue';
+import { NurseLogColumns } 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)]
+}
+const queryParams = reactive({
+  pageNo: 1,
+  pageSize: 10,
+  elderName: '',
+  tenantIds: userStore.orgTenantId,
+  nurseDate: getDefaultMonthRange()
+})
+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()
+  handleQuery()
+}
+
+/** 查询列表 */
+const getList = async () => {
+  loading.value = true
+  try {
+    const data = await getNursingLogPage(queryParams)
+    list.value = data.list
+    total.value = data.total
+  } finally {
+    loading.value = false
+  }
+}
+
+const detailRef = ref()
+const openDetail = (row:{}) => {
+  detailRef.value.open(row,queryParams.nurseDate)
+}
+
+onMounted(()=>{
+  getList()
+})
+</script>

+ 343 - 0
src/views/elderly/nursing/special-nurse/Form.vue

@@ -0,0 +1,343 @@
+<template>
+  <Dialog
+    v-model="dialogVisible"
+    :title="itemTitle"
+    width="70%"
+    class="form-tag-dialog life-care-plan-form"
+    @close="handleClosed"
+    scroll
+  >
+    <el-form
+      ref="formRef"
+      :model="dataForm"
+      :rules="dataRule"
+      label-width="80px"
+      :toggleType="isDetail"
+    >
+      <div class="info-title">基本信息</div>
+      <div class="info-wrap">
+        <el-row :gutter="20">
+          <el-col :span="12" :xs="24">
+            <el-form-item label="长者姓名" prop="elderId">
+              <SelectElder
+                v-model="dataForm.elderId"
+                @elder="handleElder"
+                :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-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-form-item>
+          </el-col>
+        </el-row>
+      </div>
+      <div class="info">
+        <div class="info-title">护理项目</div>
+        <span v-show="dataForm.elderId">
+          <el-button type="primary" class="left" @click="handleAdd" v-if="!isDetail">
+            <Icon icon="ep:zoom-in" class="mr-5px" />添加项目
+          </el-button>
+        </span>
+      </div>
+      <div class="info-wrap">
+        <el-row>
+          <el-col
+            :xs="24"
+            :sm="24"
+            :md="24"
+            :lg="24"
+            class="mb5"
+            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>
+          </el-col>
+        </el-row>
+      </div>
+    </el-form>
+    <template #footer>
+      <el-button @click="handleClosed">取消</el-button>
+      <el-button v-loading="formLoading" type="primary" @click="submitForm" v-if="!isDetail"
+        >确定</el-button
+      >
+    </template>
+  </Dialog>
+  <lifeItem ref="itemRef" @success="getItemList" />
+</template>
+
+<script setup lang="ts">
+import {
+  createNursingPlan,
+  updateNursingPlan,
+  getNursingPlanById,
+  findNurseItemListByName
+} from '@/api/elderly/nursing'
+import lifeItem from './life-item-dialog.vue'
+import { planType } from '../types'
+
+defineOptions({ name: 'LifeCarePlanForm' })
+
+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, 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)
+  }
+}
+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 handleClosed = () => {
+  dataForm.value = {...resetFormField}
+  dialogVisible.value = false
+}
+
+const itemRef = ref()
+const handleAdd = () => {
+  itemRef.value.open(dataForm.value.items)
+}
+
+const getItemList = (val) => {
+  // 添加的内容放到护理项目中
+  val.map((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,
+    })
+  })
+}
+
+// 选择长者
+const handleElder = async (item) => {
+  dataForm.value.elderName = item.elderName
+  dataForm.value.bedName = item.bedName
+  dataForm.value.nurseLevelName = item.nurseLevelName
+}
+
+const handleChangeCheckBox = (val, item) => {
+  if (!val) {
+    const index = dataForm.value.items.findIndex((p) => item.nurseItemId == p.nurseItemId)
+    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
+
+
+  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)
+    if (res) {
+      message.success(t('common.updateSuccess'))
+      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));
+  // }
+  .item-row {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    gap: 12px;
+    flex-wrap: wrap;
+  }
+  .item-left {
+    display: flex;
+    align-items: center;
+    min-height: 32px;
+    padding: 1px 11px;
+    flex: 1;
+    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;
+  }
+  .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>

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

@@ -0,0 +1,255 @@
+<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>

+ 139 - 0
src/views/elderly/nursing/special-nurse/index.vue

@@ -0,0 +1,139 @@
+<template>
+  <ContentWrap>
+    <!-- 搜索工作栏 -->
+    <el-form
+      class="-mb-15px"
+      :model="queryParams"
+      ref="queryFormRef"
+      :inline="true"
+      label-width="80px"
+    >
+      <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>
+      </el-form-item>
+    </el-form>
+  </ContentWrap>
+
+  <!-- 列表 -->
+  <ContentWrap>
+    <TabBarBtn @add="openForm()" />
+    <Table2
+      v-loading="loading"
+      :data="list"
+      :columns="LifeCarePlanColumns"
+      :queryParams="queryParams"
+      @row-click="handleRowClick"
+      @edit="openForm"
+      @detail="(arg) => openForm(arg, true)"
+    />
+
+    <!-- 分页 -->
+    <Pagination
+      :total="total"
+      v-model:page="queryParams.pageNo"
+      v-model:limit="queryParams.pageSize"
+      @pagination="getList"
+    />
+  </ContentWrap>
+
+  <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 Form from './Form.vue'
+
+defineOptions({ name: 'LifeCarePlan' })
+
+const userStore = useUserStore()
+const route = useRoute()
+
+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 生活护理
+})
+const queryFormRef = ref() // 搜索的表单
+
+/** 查询列表 */
+const getList = async () => {
+  loading.value = true
+  try {
+    const data = await getnursingPlanPage(queryParams)
+    list.value = data.list
+    total.value = data.total
+  } finally {
+    loading.value = false
+  }
+}
+
+/** 搜索按钮操作 */
+const handleQuery = () => {
+  queryParams.pageNo = 1
+  getList()
+}
+
+/** 重置按钮操作 */
+const resetQuery = () => {
+  queryFormRef.value.resetFields()
+  handleQuery()
+}
+
+/** 添加/修改操作 */
+const formRef = ref()
+const openForm = (row: any = {}, isDetail: boolean = false) => {
+  formRef.value.open(row.id, isDetail, false)
+}
+
+// 复制
+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){
+    queryParams.elderName = route.query.elderName as string
+  }
+  getList()
+  getNurseLevelList()
+})
+</script>

+ 124 - 0
src/views/elderly/nursing/special-nurse/life-item-dialog.vue

@@ -0,0 +1,124 @@
+<template>
+  <Dialog v-model="dialogVisible" title="选择护理项目" @close="handleClosed" width="60%">
+    <el-form ref="formRef" :model="dataForm" label-width="80px" inline>
+      <el-form-item label="项目名称" prop="nurseItemName">
+        <TgInput v-model="dataForm.nurseItemName" />
+      </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>
+      </el-form-item>
+
+      <el-table :data="list" height="400" @selection-change="handleSelectionChange">
+        <el-table-column type="selection" width="55" />
+        <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) }}
+          </template>
+        </el-table-column>
+        <el-table-column prop="price" label="收费金额(元)" align="center">
+          <template #default="scope">
+            {{ formatNum(scope.row.price) }}
+          </template>
+        </el-table-column>
+        <el-table-column prop="frequency" label="收费单位" align="center">
+          <template #default="scope">
+            {{ getDictLabel(DICT_TYPE.NURSING_FREQUENCY_TYPE, scope.row.frequency) }}
+          </template>
+        </el-table-column>
+      </el-table>
+    </el-form>
+    <template #footer>
+      <el-button @click="handleClosed">取消</el-button>
+      <el-button v-loading="formLoading" type="primary" @click="submitForm">确定</el-button>
+    </template>
+  </Dialog>
+</template>
+<script setup lang="ts">
+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' })
+const dialogVisible = ref(false)
+const list = ref([])
+const formLoading = ref(false)
+const route = useRoute()
+const dataForm = reactive({
+  categoryTypeName: route.path.indexOf('medical-care-plan') > -1 ? '医疗护理' : '生活护理',
+  nurseItemName: ''
+})
+const exitArr = ref<itemsType[]>([])
+/** 打开弹窗 */
+const open = async (arr) => {
+  dialogVisible.value = true
+  exitArr.value = arr
+  handleQuery()
+}
+defineExpose({ open }) // 提供 open 方法,用于打开弹窗
+
+const handleClosed = () => {
+  list.value = []
+  dataForm.nurseItemName = ''
+  dialogVisible.value = false
+}
+
+const loading = ref(false)
+
+const formRef = ref()
+const resetQuery = () => {
+  formRef.value.resetFields()
+  handleQuery()
+}
+
+const handleQuery = async () => {
+  loading.value = true
+  try {
+    const res = await findNurseItemListByName(dataForm)
+    // 做比较显示没有相同添加的数据
+    exitArr.value.map((item) => {
+      res.map((r) => {
+        if (r.id == item.nurseItemId) {
+          r.same = true
+        }
+      })
+    })
+    const arr: any = []
+    res.map((item) => {
+      if (!item.same) {
+        arr.push(item)
+      }
+    })
+    arr.map((item) => {
+      ;(item.cName = dataForm.categoryTypeName),
+        // item.fee = false // 额外收费
+        (item.flag = false) // 用于判断这个值是否加到护理项目中
+    })
+    list.value = arr
+  } finally {
+    // table中也添加多少条数据
+    loading.value = false
+  }
+}
+
+const multipleSelection = ref([])
+const handleSelectionChange = (val) => {
+  multipleSelection.value = val
+}
+
+const emit = defineEmits(['success'])
+const submitForm = async () => {
+  emit('success', multipleSelection.value)
+  handleClosed()
+}
+</script>