Bläddra i källkod

Merge branch 'master' of http://192.168.100.32:3000/fanfan/xt-front

han 2 år sedan
förälder
incheckning
b1489879d3

+ 22 - 0
src/api/biz/visit/prepareApi.ts

@@ -0,0 +1,22 @@
+import { defHttp } from '/@/utils/http/axios';
+enum Api {
+  readyPrepare = '/visit/prePrepare/query/personList',
+  prePrepareSailing = '/visit/prePrepare/query/sailings',
+  wardInfo = '/biz/sys/wardInfo/query/list',
+}
+
+/**
+ * @description: 获取透前准备页面人员信息
+ * @method: POET
+ */
+export const readyPrepare = (params?: object) => {
+  return defHttp.post({ url: Api.readyPrepare, params: params });
+};
+
+export const wardInfo = (params?: object) => {
+  return defHttp.post({ url: Api.wardInfo, params: params });
+};
+
+export const prePrepareSailing = (params?: object) => {
+  return defHttp.get({ url: Api.prePrepareSailing, params: params });
+};

+ 41 - 0
src/api/biz/visit/transferApi.ts

@@ -0,0 +1,41 @@
+import { defHttp } from '/@/utils/http/axios';
+enum Api {
+  typeNumber = '/visit/handoverShifts/queryPersonNumber',
+  transfetList = '/visit/handoverShifts/query/page',
+  transferDel = '/visit/handoverShifts/removeByIds',
+  transferAdd = '/visit/handoverShifts/add',
+  transferEdit = '/visit/handoverShifts/edit',
+  transferById = '/visit/handoverShifts/detail',
+}
+
+/**
+ * @description: 获取透前准备页面人员信息
+ * @method: POET
+ */
+export const getTypeNumber = () => {
+  return defHttp.get({ url: Api.typeNumber });
+};
+
+export const getTransgerList = (params?: object) => {
+  return defHttp.post({ url: Api.transfetList, params: params });
+};
+
+export const transferDel = (params?: object) => {
+  return defHttp.post({ url: Api.transferDel, params: params });
+};
+
+export const transferExport = (params?: object) => {
+  return defHttp.post({ url: Api.transferDel, params: params });
+};
+
+export const transferAdd = (params?: object) => {
+  return defHttp.post({ url: Api.transferAdd, params: params });
+};
+
+export const transferEdit = (params?: object) => {
+  return defHttp.post({ url: Api.transferEdit, params: params });
+};
+
+export const transferById = (id: string) => {
+  return defHttp.get({ url: Api.transferById + '/' + id });
+};

+ 20 - 6
src/components/XTUpload/src/XTUpload.vue

@@ -146,12 +146,10 @@
         if (fileList.length == 1) {
           handleFile(file);
           await handleStartUpload();
-          console.log(
-            '🚀 ~ file: XTUpload.vue:150 ~ beforeUpload ~ fileListRef.value:',
-            fileListRef.value,
-          );
         } else {
           isMultiCount.value += 1;
+          // console.log('🚀 ~ file: XTUpload.vue:155 ~ beforeUpload ~ isMultiCount:', isMultiCount);
+          // console.log('🚀 ~ file: XTUpload.vue:157 ~ beforeUpload ~ fileList:', fileList);
           if (isMultiCount.value == fileList.length) {
             console.log('上传操作');
             fileList.forEach(ele => {
@@ -246,13 +244,28 @@
           item.status =
             data.code == ResultEnum.SUCCESS ? UploadResultStatus.SUCCESS : UploadResultStatus.ERROR;
           item.responseData = data;
-          if (idx + 1 == len) {
+          // if (idx + 1 == len) {
+          if (idx <= len) {
+            // console.log('🚀 ~ file: XTUpload.vue:250 ~ uploadApiByItem ~ len:', len);
+            // console.log(
+            //   '🚀 ~ file: XTUpload.vue:259 ~ uploadApiByItem ~ fileListRef.value:',
+            //   fileListRef.value,
+            // );
+            isMultiCount.value = 0;
             fileListRef.value.forEach(ele => {
               const responseData = ele.responseData as any;
+              // console.log(
+              //   '🚀 ~ file: XTUpload.vue:258 ~ uploadApiByItem ~ responseData:',
+              //   responseData,
+              // );
               if (responseData?.code == ResultEnum.SUCCESS) {
                 fileList.value.push(responseData.data);
+                // console.log(
+                //   '🚀 ~ file: XTUpload.vue:256 ~ uploadApiByItem ~ fileList.value.push:',
+                //   fileList.value,
+                // );
               } else {
-                createMessage.error(responseData?.errorMsg);
+                responseData ? createMessage.error(responseData?.errorMsg) : '';
               }
             });
             fileListRef.value = fileListRef.value.filter(ele => {
@@ -279,6 +292,7 @@
       // 点击开始上传
       async function handleStartUpload() {
         const { maxNumber } = props;
+        console.log('🚀 ~ file: XTUpload.vue:282 ~ handleStartUpload ~ props:', props);
         if ((fileListRef.value.length + fileList.value.length ?? 0) > maxNumber) {
           return createMessage.warning(`最多只能上传${maxNumber}个文件`);
         }

+ 1 - 1
src/views/biz/archives/formula/FormDrawer.vue

@@ -112,7 +112,7 @@
     isUpdate.value = data.isUpdate;
     getTitle.value = `${isUpdate.value ? '编辑透析处方模板' : '新建透析处方模板'} ( ${
       data.record.name
-    } | ${data.record.gender} | ${data.record.age} )`;
+    } | ${data.record.gender} | ${data.record.age} )`;
     bizDictOptions.accessType = await listDictModel({ dictCode: 'va_type' });
     patientBasicId.value = data.record.patientBasicId;
     rowId.value = data.record?.id;

+ 3 - 1
src/views/biz/archives/index/data.ts

@@ -59,7 +59,7 @@ export const siftFormSchema: FormSchema[] = [
     componentProps: {
       placeholder: '请输入年龄',
       max: 99,
-      min: 1,
+      min: 0,
       range: true,
       tooltipPlacement: 'bottom',
       tooltipVisible: true,
@@ -134,6 +134,7 @@ export const columns: BasicColumn[] = [
     title: '出生日期',
     dataIndex: 'birthday',
     align: 'left',
+    sorter: true,
   },
   {
     title: '年龄',
@@ -150,6 +151,7 @@ export const columns: BasicColumn[] = [
     dataIndex: 'createTime',
     align: 'left',
     width: 180,
+    sorter: true,
   },
   {
     title: '类型',

+ 18 - 4
src/views/biz/archives/index/index.vue

@@ -76,8 +76,8 @@
             <TableAction
               :actions="[
                 {
-                  auth: 'sys:log:query',
-                  icon: 'icon-eye|iconfont',
+                  auth: 'archives:patientBasic:query',
+                  icon: 'icon-xt-medical_default|iconfont',
                   tooltip: '详情',
                   label: '详情',
                   onClick: handleView.bind(null, record),
@@ -173,7 +173,7 @@
     showIndexColumn: true,
     bordered: true,
     actionColumn: {
-      width: 200,
+      width: 100,
       title: '操作',
       dataIndex: 'action',
     },
@@ -231,6 +231,16 @@
     siftData.value.forEach(ele => {
       sift[ele.field] = ele.isDict ? ele.dict : ele.value;
     });
+    if (params?.order) {
+      params.orders = [
+        {
+          field: params.field,
+          direction: params.order.substring(0, params.order.length - 3).toUpperCase(),
+        },
+      ];
+      delete params.order;
+      delete params.field;
+    }
     return {
       ...params,
       queryType: activeKey.value == '0' ? '0' : activeKey.value,
@@ -252,7 +262,7 @@
         accessId: record.accessId,
         name: record.name,
         gender: formatDictValue(bizDictOptions.gender, record.gender),
-        age: dayjs().diff(record.birthday, 'year'),
+        age: record.age,
       },
     });
   }
@@ -362,4 +372,8 @@
       background-color: #f7b500;
     }
   }
+
+  ::v-deep(.ant-btn-link) {
+    color: rgb(61 65 85 / 100%);
+  }
 </style>

+ 1 - 1
src/views/biz/archives/patientBasic/index.vue

@@ -188,7 +188,7 @@
     const firstDialysisResContent = firstDialysisRes.firstDialysis?.content[0];
     basicData.value[2].data.unshift({
       field: 'first',
-      label: '首次透析111',
+      label: '首次透析',
       value: firstDialysisResContent?.recordTime
         ? dayjs(firstDialysisResContent?.recordTime).format('YYYY-MM-DD')
         : '',

+ 1 - 1
src/views/biz/archives/vascularAccess/FormDrawer.vue

@@ -68,7 +68,7 @@
     }
     getTitle.value = `${isUpdate.value ? '编辑' : '新建'}${accessType.value}   ( ${
       data.record.name
-    } | ${data.record.gender} | ${data.record.age} )`;
+    } | ${data.record.gender} | ${data.record.age} )`;
   });
 
   // 提交按钮事件

+ 1 - 1
src/views/biz/archives/vascularAccess/FormModal.vue

@@ -73,7 +73,7 @@
     }
     getTitle.value = `${isUpdate.value ? '编辑' : '新建'}${accessType.value}   ( ${
       data.record.name
-    } | ${data.record.gender} | ${data.record.age} )`;
+    } | ${data.record.gender} | ${data.record.age} )`;
   });
 
   // 提交按钮事件

+ 1 - 1
src/views/biz/archives/vascularAccess/FormModalReturn.vue

@@ -50,7 +50,7 @@
     setModalProps({ confirmLoading: false });
     bizDictOptions.accessType = await listDictModel({ dictCode: 'va_type' });
     isUpdate.value = !!data?.isUpdate;
-    getTitle.value = `通路转归 ( ${data.record.name} | ${data.record.gender} | ${data.record.age} )`;
+    getTitle.value = `通路转归 ( ${data.record.name} | ${data.record.gender} | ${data.record.age} )`;
     vascularAccessId.value = data.accessId;
     console.log('🚀 ~ file: FormModalReturn.vue:56 ~  data.record:', data.record);
     if (unref(isUpdate)) {

+ 6 - 4
src/views/biz/archives/vascularAccess/ViewDrawerComplication.vue

@@ -11,7 +11,7 @@
     cancel-text="关闭"
   >
     <div class="!px-4 !pt-4">
-      <div class="mb-4">
+      <div class="mb-4" v-auth="'archives:accessComplication:add'">
         <a-button type="primary" shape="round" @click="handleAdd">
           <template #icon>
             <PlusOutlined />
@@ -72,7 +72,7 @@
 
   const [registerDrawer, { setDrawerProps, closeDrawer }] = useDrawerInner(async data => {
     setDrawerProps({ confirmLoading: false });
-    getTitle.value = `通路并发症 ( ${data.record.name} | ${data.record.gender} | ${data.record.age} )`;
+    getTitle.value = `通路并发症 ( ${data.record.name} | ${data.record.gender} | ${data.record.age} )`;
     isUpdate.value = !!data?.isUpdate;
     bizDictOptions.accessType = await listDictModel({ dictCode: 'va_type' });
     bizDictOptions.vaComp = await listDictModel({ dictCode: 'va_comp' });
@@ -98,13 +98,13 @@
     if (!record.editable) {
       return [
         {
-          auth: 'sys:log:query',
+          auth: 'archives:accessComplication:add',
           icon: 'icon-xt-details_edit_default|iconfont',
           tooltip: '编辑',
           onClick: handleEdit.bind(null, record),
         },
         {
-          auth: 'sys:log:query',
+          auth: 'archives:accessComplication:remove',
           icon: 'icon-xt-details_delete_default|iconfont',
           tooltip: '删除',
           popConfirm: {
@@ -138,6 +138,7 @@
       return ele;
     });
     setTableData(tableData.value);
+    await nextTick();
   }
 
   // 提交按钮事件
@@ -184,6 +185,7 @@
     if (res) {
       tableData.value.splice(index, 1);
       setTableData(tableData.value);
+      createMessage.success('删除成功');
     } else {
       createMessage.error('删除失败');
     }

+ 2 - 2
src/views/biz/archives/vascularAccess/index.vue

@@ -232,8 +232,8 @@
           if (ele.returnBack) {
             eleC.value = `于${dayjs(ele[eleC.field]['returnTime']).format(
               'YYYY-MM-DD',
-            )}转归; 使用${dayjs().diff(
-              ele[eleC.field]['returnTime'],
+            )}转归; 使用${dayjs(ele[eleC.field]['returnTime']).diff(
+              dayjs(ele[eleC.field]['updateTime']).format('YYYY-MM-DD'),
               'day',
             )}天; ${formatDictValue(bizDictOptions.return, ele[eleC.field]['returnCause'])}; ${
               ele['updatorName']

+ 5 - 3
src/views/biz/management/complication/index.vue

@@ -18,7 +18,7 @@
                 <template #overlay>
                   <a-menu @click="handleMenuClick">
                     <a-menu-item key="1">添加并发症</a-menu-item>
-                    <a-menu-item key="2">批量导入</a-menu-item>
+                    <!-- <a-menu-item key="2">批量导入</a-menu-item> -->
                     <a-menu-item key="3">批量导出</a-menu-item>
                   </a-menu>
                 </template>
@@ -43,12 +43,12 @@
           <Col :span="18">
             <Card :title="detailName">
               <template #extra>
-                <Button shape="circle" v-auth="['bizSys:wardInfo:add']" @click="handleEditInfo"
+                <Button shape="circle" v-auth="['bizSys:complication:edit']" @click="handleEditInfo"
                   ><Icon icon="icon-xt-details_edit_default|iconfont" :size="14"
                 /></Button>
                 <Button
                   shape="circle"
-                  v-auth="['bizSys:wardInfo:add']"
+                  v-auth="['bizSys:complication:remove']"
                   @click="handleDel(detailForm.id)"
                   style="margin-left: 20px"
                   ><Icon icon="icon-xt-details_delete_default|iconfont" :size="14"
@@ -200,6 +200,7 @@
   async function getDataList(params?: object) {
     const res = await getComplicationList({
       ...params,
+      page: { current: 1, size: 999 },
       orders: [{ field: 'sort', direction: 'DESC' }],
     });
     complicationList.value = res.data;
@@ -373,6 +374,7 @@
 
   ::v-deep(.ant-card) {
     height: 800px;
+    overflow: auto;
   }
 
   ::v-deep(.ant-table-body) {

+ 1 - 0
src/views/biz/management/complication/noteFormModal.vue

@@ -94,6 +94,7 @@
       component: 'InputNumber',
       componentProps: {
         placeholder: '请输入排序',
+        min: 0,
         onFocus: function () {
           treeDis.value = true;
         },

+ 35 - 1
src/views/biz/management/parameter/index.vue

@@ -7,6 +7,14 @@
       </Input> -->
       <BasicTable @register="registerTable">
         <template #bodyCell="{ column, record }">
+          <template v-if="column.key === 'typeName' && record.typeName.indexOf('天') != -1">
+            <div style="display: flex">
+              {{ record.typeName }}
+              <a-tooltip color="orange" :title="record.typeName + ': 不可超过360天'">
+                <div class="tips">?</div>
+              </a-tooltip>
+            </div>
+          </template>
           <template
             v-if="
               column.key === 'contents' &&
@@ -16,10 +24,21 @@
               record.key != 4
             "
           >
-            <div>
+            <div v-if="record.typeName.indexOf('天') != -1">
+              <a-input-number
+                v-if="editableData[record.key]"
+                v-model:value="editableData[record.key][column.dataIndex]"
+                :min="0"
+                :max="365"
+                placeholder="请输入参数内容"
+              />
+              <template v-else> {{ record.contents }} </template>
+            </div>
+            <div v-else>
               <a-input-number
                 v-if="editableData[record.key]"
                 v-model:value="editableData[record.key][column.dataIndex]"
+                :min="0"
                 placeholder="请输入参数内容"
               />
               <template v-else> {{ record.contents }} </template>
@@ -399,6 +418,8 @@
       editObj[editableData[key].typeIndex] = Number(
         parseFloat(editableData[key].contents).toFixed(2),
       );
+    } else {
+      editObj[editableData[key].typeIndex] = editableData[key].contents;
     }
     await editParameter(resObj.value);
     delete editableData[key];
@@ -443,4 +464,17 @@
   ::v-deep(.fan-basic-table-action.center) {
     justify-content: left !important;
   }
+
+  .tips {
+    width: 14px;
+    height: 14px;
+    margin: 5px 0 0 8px;
+    line-height: 14px;
+    text-align: center;
+    border: solid 1px #696969;
+    border-radius: 50%;
+    background-color: #696969;
+    color: #fff;
+    font-size: 3px;
+  }
 </style>

+ 3 - 0
src/views/biz/management/ward/data.ts

@@ -9,14 +9,17 @@ export const wardInfoColumns: BasicColumn[] = [
   {
     title: '病区属性',
     dataIndex: 'propertiesName',
+    width: 260,
   },
   {
     title: '状态',
     dataIndex: 'disable',
+    width: 150,
   },
   {
     title: '排序',
     dataIndex: 'sort',
+    width: 120,
   },
 ];
 

+ 9 - 3
src/views/biz/management/ward/index.vue

@@ -63,7 +63,10 @@
                   <BasicTable @register="registerTable">
                     <template #bodyCell="{ column, record }">
                       <template v-if="column.key === 'disable'">
-                        <Tag :color="formatDictColor(disableOptions, record.disable)">
+                        <Tag
+                          :color="formatDictColor(disableOptions, record.disable)"
+                          :style="'color:' + formatDictFontColor(disableOptions, record.disable)"
+                        >
                           {{ formatDictValue(disableOptions, record.disable) }}
                         </Tag>
                       </template>
@@ -215,7 +218,7 @@
   import { useModal } from '/@/components/Modal';
   import { listDictModel } from '/@/api/common';
   import { useMessage } from '/@/hooks/web/useMessage';
-  import { formatDictColor, formatDictValue } from '/@/utils';
+  import { formatDictColor, formatDictFontColor, formatDictValue } from '/@/utils';
   const { createConfirm, createMessage } = useMessage();
   const tabList = ref([
     { key: 0, tab: '病区管理', type: 'WARD' },
@@ -250,7 +253,7 @@
     },
     useSearchForm: false,
     actionColumn: {
-      width: 320,
+      width: 120,
       title: '操作',
       dataIndex: 'action',
     },
@@ -466,6 +469,9 @@
 <style lang="less" scoped>
   ::v-deep(.ant-card-head) {
     background-color: #f6f8fa !important;
+    color: #000a18;
+    font-weight: 600;
+    font-size: 18px;
   }
 
   .type-title {

+ 19 - 2
src/views/biz/visit/ready/data.ts

@@ -2,6 +2,22 @@ import { BasicColumn } from '/@/components/Table';
 import { FormSchema } from '/@/components/Form';
 import { radioBoolean } from '/@/utils/filters';
 import { listDictModel } from '/@/api/common';
+import { ColorEnum } from '@/enums/colorEnum';
+
+export enum TitleEnum {
+  finish = '已完成',
+  finishColor = ColorEnum.MUTED,
+  finishBg = ColorEnum.MUTED_BG,
+  printable = '可打印',
+  printableColor = ColorEnum.SUCCESS,
+  printableBg = ColorEnum.SUCCESS_BG,
+  unWeighed = '未称量',
+  unWeighedColor = ColorEnum.PRIMARY,
+  unWeighedBg = ColorEnum.PRIMARY_BG,
+  unconfirmed = '待确认',
+  unconfirmedColor = ColorEnum.WARNING,
+  unconfirmedBg = ColorEnum.WARNING_BG,
+}
 
 export const dataFormSchema: FormSchema[] = [
   {
@@ -13,7 +29,7 @@ export const dataFormSchema: FormSchema[] = [
     },
   },
   {
-    field: 'PlainText1111',
+    field: 'PlainText',
     component: 'RadioDescGroup',
     label: '1、干体重在过去3~6个月总的变化',
     componentProps: {
@@ -78,7 +94,7 @@ export const dataFormSchema: FormSchema[] = [
   {
     field: 'configKey',
     label: '参数键名',
-    labelColor: '#818694',
+    labelColor: '#81869 4',
     subLabel: '测试',
     subLabelColor: '#FF5D39',
     component: 'Input',
@@ -175,6 +191,7 @@ export const dataFormSchema: FormSchema[] = [
     },
   },
 ];
+
 export const columns: BasicColumn[] = [
   {
     title: '名称',

+ 150 - 0
src/views/biz/visit/ready/importView.vue

@@ -0,0 +1,150 @@
+<template>
+  <BasicModal
+    @register="registerModal"
+    width="500px"
+    title="导入数据"
+    @ok="handleSubmit"
+    @cancel="handleCancel"
+  >
+    <Card style="background-color: rgb(240 240 240)" v-if="statsUpload == 'done'">
+      <h2 style="text-align: center">数据导入中...</h2>
+      <h5 style="text-align: center">请不要离开此页面</h5>
+      <Progress :percent="percentProgress" :showInfo="false" status="active" />
+    </Card>
+    <Card style="background-color: rgb(240 240 240)" v-else-if="statsUpload == 'success'">
+      <h2 style="text-align: center">成功</h2>
+      <h2 style="text-align: center"><a @click="goOnImport">继续导入</a></h2>
+    </Card>
+    <Card style="background-color: rgb(240 240 240)" v-else-if="statsUpload == 'fail'">
+      <h2 style="text-align: center">失败</h2>
+      <h5 style="text-align: center"><a @click="getExportExcel">下载失败文件</a></h5>
+      <div style="text-align: center">
+        <span> 失败: {{ importExcelInfo.fail }}</span>
+        <span> 成功: {{ importExcelInfo.success }}</span>
+        <span> 总数: {{ importExcelInfo.total }}</span>
+      </div>
+    </Card>
+    <Card style="background-color: rgb(240 240 240)" v-else>
+      <h2 style="text-align: center">选择需要导入的文件</h2>
+      <h5 style="text-align: center"
+        >若您是第一次上传文件,可先下载<a @click="downloadFile">文件模板</a>,文件大小不超过5M</h5
+      >
+      <div class="flex flex-col justify-center">
+        <Upload
+          class="button-style"
+          name="file"
+          :beforeUpload="handleBeforeUpload"
+          :maxCount="1"
+          :action="uploadApi"
+          @change="onChange"
+        >
+          <Button type="primary"> 点击上传 </Button>
+        </Upload>
+      </div>
+      <!-- //v-auth="['dialysis:patientbasic:import']" -->
+    </Card>
+  </BasicModal>
+</template>
+<script lang="ts" setup>
+  import { onBeforeMount, onUnmounted, reactive, ref } from 'vue';
+  import { Card } from 'ant-design-vue';
+  import { Progress } from 'ant-design-vue';
+  import { Upload } from 'ant-design-vue';
+  import { Button } from '/@/components/Button';
+  import { BasicModal, useModalInner } from '/@/components/Modal';
+  import { useGlobSetting } from '/@/hooks/setting';
+  import { excelSheetDetail, getDownloadUrl, tempDownload } from '/@/api/common/index';
+  const globSetting = useGlobSetting();
+  import { listDictModel } from '/@/api/common';
+  import { downloadByBase64, downloadByUrl } from '/@/utils/file/download';
+  const uploadApi = globSetting.apiUrl + '/archives/patientBasic/import/batch';
+  const fileUpload = ref();
+  const typeOptions = ref();
+  const statsUpload = ref('default');
+  const timer = ref(null);
+  const exportFileId = ref('');
+  const percentProgress = ref(0);
+  const importExcelInfo = reactive({
+    success: 0,
+    fail: 0,
+    total: 0,
+  });
+
+  onBeforeMount(async () => {
+    typeOptions.value = await listDictModel({ dictCode: 'sys_login_log_type' });
+    fileUpload.value = '';
+  });
+  // closeModal
+  const [registerModal, { setModalProps }] = useModalInner(async data => {
+    console.log('🚀 ~ file: code.vue:21 ~ data:', data);
+    setModalProps({ confirmLoading: false });
+  });
+  async function handleBeforeUpload(file) {
+    console.log('file', file);
+    fileUpload.value = file;
+  }
+  async function handleSubmit() {
+    console.log('11111');
+  }
+  function handleCancel() {
+    statsUpload.value = 'default';
+  }
+  function goOnImport() {
+    statsUpload.value = 'default';
+  }
+  async function onChange(file) {
+    if (file.file.status === 'done') {
+      statsUpload.value = 'done';
+      const id = file.file.response.data;
+      if (id) {
+        timer.value = setInterval(async function () {
+          const importStats = await excelSheetDetail(id);
+          console.log('importStats', importStats);
+          importExcelInfo.fail = importStats.failCount;
+          importExcelInfo.success = importStats.successCount;
+          importExcelInfo.total = importStats.totalCount;
+          // 进度条
+          percentProgress.value =
+            ((importStats.failCount + importStats.successCount) / importStats.totalCount) * 100;
+          console.log('进度条:', percentProgress.value);
+          const isFinish =
+            importStats.failCount + importStats.successCount == importStats.totalCount;
+          if (isFinish) {
+            clearInterval(timer.value);
+            setTimeout(() => {
+              if (importStats.failCount) {
+                statsUpload.value = 'fail';
+                exportFileId.value = importStats.exportFileId;
+                percentProgress.value = 0;
+              } else {
+                statsUpload.value = 'success';
+                percentProgress.value = 0;
+              }
+            }, 3000);
+          }
+        }, 1000);
+      }
+    }
+  }
+
+  async function getExportExcel() {
+    const res = await getDownloadUrl(exportFileId.value);
+    console.log('res', res);
+    downloadByUrl({ url: res });
+  }
+  async function downloadFile() {
+    const res = await tempDownload([]);
+    downloadByBase64(res.base64, res.fileName + '.xlsx');
+  }
+  onUnmounted(() => {
+    timer.value ? clearInterval(timer.value) : null;
+  });
+</script>
+<style lang="less" scoped>
+  .button-style {
+    display: flex;
+    justify-content: center;
+    flex-direction: column;
+    align-items: center;
+  }
+</style>

+ 197 - 491
src/views/biz/visit/ready/index.vue

@@ -4,14 +4,7 @@
     <a-button type="primary" @click="plusFn">
       {{ countRef }}
     </a-button> -->
-    <XTTitle title="透析病历" :right-data="titleData" />
-
-    <div class="mt-6" />
-    <XTTab type="illness11" :selected="tabSelected" :data="tabData1" @item-click="callTab" />
-    <div class="mt-6" />
-    <div class="mt-6" />
-    <XTTab type="illness11" :selected="tabSelected" :data="tabData2" @item-click="callTab" />
-    <div class="mt-6" />
+    <XTTitle title="透析病历" :right-data="titleData" @click="cellTitle" />
     <div class="flex justify-between my-4">
       <XTTab
         type="illness"
@@ -20,67 +13,12 @@
         :data="tabData"
         @item-click="callTab"
       />
-      <XTForm :form-data="formData" />
-    </div>
-    <div class="flex">
-      <Sift :data="siftData" />
-    </div>
-    <div class="m-6">
-      <BasicForm @register="registerForm" @field-value-change="filedChange" />
+      <XTForm :form-data="formData" @change="cellFrom" />
     </div>
     <div>
-      <BasicTable @register="registerTable">
-        <template #headerTop>
-          <span>headerTop</span>
-        </template>
-        <template #toolbar>
-          <span>toolbar</span>
-        </template>
-      </BasicTable>
-    </div>
-    <div class="mx-6 my-2">
-      <ChartsCard
-        title="透前血压趋势"
-        :has-safe="true"
-        :colors="chartData.colors"
-        :safe-range="chartData.safeRange"
-      />
-    </div>
-    <div class="mx-6 my-2">
-      <DescCard
-        id="1"
-        icon="icon-xt-add_default"
-        title="透析测量"
-        type="touxi"
-        :data="descData"
-        :right="descRight"
-      />
-    </div>
-    <div class="mx-6 my-2">
-      <TimeLine :data="timeLineData" @hover="callHover">
-        <template #head>
-          <transition class="animate__animated animate__slideInLeft">
-            <div class="timeline-outer" v-if="timeOuter">
-              <div class="timeline-outer_item" @click="handleAdd">AVF</div>
-              <div class="timeline-outer_item">AVH</div>
-              <div class="timeline-outer_item">TCG</div>
-            </div>
-          </transition>
-        </template>
-      </TimeLine>
-    </div>
-    <div class="mx-6 my-2">
-      <List type="default" :data="listData" selected="0" />
-    </div>
-    <div class="mx-6 my-2">
-      <List type="attachment" :data="listData1" selected="0" :width="320" />
-    </div>
-    <div class="flex justify-between">
-      <XTCard class="m-2" :data="cardData1" @item-click="cellCard" />
-    </div>
-    <div class="flex justify-between">
-      <XTCard class="m-2" :data="cardData" @item-click="cellCard" />
+      <XTCard class="flex justify-around my-2" :data="dataCard" @item-click="cellCard" />
     </div>
+    <importView @register="registerModal" />
   </div>
 </template>
 
@@ -90,20 +28,28 @@
   import { XTCard } from '/@/components/XTCard/index';
   import { XTForm } from '/@/components/XTForm/index';
   import { XTTitle } from '/@/components/XTTitle/index';
-  import { ColorEnum } from '/@/enums/colorEnum';
-  import { BasicForm, useForm } from '/@/components/Form';
-  import { dataFormSchema, columns } from './data';
-  import { BasicTable, useTable } from '/@/components/Table';
   import { onMounted } from 'vue';
-  import ChartsCard from '/@/components/XTCard/src/ChartsCard.vue';
-  import DescCard from '/@/components/XTCard/src/DescCard.vue';
-  import TimeLine from '/@/components/XTTimeLine/src/TimeLine.vue';
-  import List from '/@/components/XTList/src/List.vue';
-  import { Sift } from '/@/components/XTList/index';
-  // import { TransitionPresets, useTransition } from '@vueuse/core';
+  import { readyPrepare, prePrepareSailing, wardInfo } from '/@/api/biz/visit/prepareApi';
+  import { TitleEnum } from './data';
+  import { nanoid } from 'nanoid';
+  import { useModal } from '/@/components/Modal';
+  import importView from './importView.vue';
+  const dataCard = ref([]) as any; // 卡片数据
+  const tabSelected = ref('0'); // 默认病区 // 病区id
+  const SailingList = ref([]) as any; // 班次列表
+  const tabData = ref([]);
+  const Status = ref(''); // 状态
+  const Name = ref('');
+  const Sailing = ref('1'); //班次
+  // const wardId = ref('');
+  // const abcdefg = [
+  //   { key: '0', label: '全部' },
+  //   { key: '1', label: 'A区' },
+  //   { key: '2', label: 'B区' },
+  // ];
+  // 默认状态
 
-  const tabSelected = ref('0');
-  const dataSource = ref([]);
+  const [registerModal, { openModal }] = useModal();
   const titleData = [
     {
       type: 'import',
@@ -119,457 +65,217 @@
       btnText: '患者建档',
     },
   ];
-  const siftData = [
-    {
-      field: 'gender',
-      label: '性别',
-      value: '男',
-    },
-    {
-      field: 'age',
-      label: '年龄区间',
-      value: '25岁-50岁',
-    },
-  ];
-  const chartData = {
-    colors: [
-      {
-        color: 'rgba(0, 117, 255, 1)',
-        label: '化验值',
-        dot: 'rgba(0, 117, 255, 1)',
-      },
-    ],
-    safeRange: [
-      {
-        // name: '60分到80分',
-        yAxis: 0,
-      },
-      {
-        yAxis: 20,
-      },
-    ],
-  };
-  const listData = [
-    {
-      id: '0',
-      title: '红细胞笔迹测定',
-      startTime: '2023-11-11 12:00',
-      endTime: '2023-12-11 12:00',
-      status: 'default',
-    },
-    {
-      id: '2',
-      title: '红细胞笔迹测定2',
-      startTime: '2023-11-11 12:00',
-      endTime: '2023-12-11 12:00',
-      status: 'default',
-    },
-  ];
-  const listData1 = [
-    {
-      id: '0',
-      title: '红细胞笔迹测定',
-      startTime: '2023-11-11 12:00',
-      endTime: '2023-12-11 12:00',
-      status: 'default',
-    },
-    {
-      id: '2',
-      title: '红细胞笔迹测定2',
-      startTime: '2023-11-11 12:00',
-      endTime: '2023-12-11 12:00',
-      status: 'default',
-    },
-  ];
 
-  const descData = [
-    {
-      label: '诊断名称/病史/状态',
-      value: '乙肝 / 2022-01-19 ~',
-      tags: [{ id: '12', label: '活动中', type: 'error' }],
-    },
-    {
-      label: '备注',
-      value: '药物治疗或其他治疗',
-    },
-    {
-      label: '测试',
-      value: '药物治疗或其他治疗',
-    },
-    {
-      label: '诊断名称/病史/状态备份',
-      value: '结核 / 2022-02-10 ~ 20',
-      tags: [
-        { id: '1', label: '未活动', type: 'muted' },
-        { id: '2', label: '未活动1', type: 'primary' },
-      ],
-    },
-  ];
-  const descRight = {
-    show: true,
-    date: '2023-04-23',
-    doctor: '张医生',
-    edit: true,
-    delete: true,
-  };
-
-  const timeLineData = [
-    {
-      id: '1',
-      dot: '王医生',
-      date: '2023-05-20',
-      cnt: {
-        title: '测试' + Math.round(Math.random() * 1000),
-        id: '123',
-        type: '123',
-        data: descData,
-      },
-    },
-    {
-      id: '12',
-      dot: '范医生',
-      date: '2023-03-20',
-      cnt: {
-        title: '测试' + Math.round(Math.random() * 1000),
-        id: '123',
-        type: '123',
-        data: descData,
-      },
-    },
-  ];
-  const timeOuter = ref(false);
   onMounted(async () => {
-    for (let i = 0; i < 10; i++) {
+    const PrepareData = await readyPrepare({ sailingSort: '1' });
+    const SailingData = await prePrepareSailing();
+    const wardData = await wardInfo();
+    for (const i of SailingData) {
       const obj = {
-        createTime: '2023-05-23 10:09:48',
-        updateTime: '2023-05-23 19:00:32',
-        id: '1660830352886149125' + Math.round(Math.random() * 10000),
-        name: '驼人',
-        packageId: '1655202440997244930',
-        packageName: '测试套餐',
-        username: 'tuoren',
-        type: 'custom',
-        contractUser: 'Lf',
-        contactMobile: '18339543638',
-        remark: null,
-        disable: 0,
+        label: i.name,
+        value: i.sort,
       };
-      dataSource.value.push(obj);
+      SailingList.value.push(obj);
     }
-
-    await setFieldsValue({
-      configName333: {
-        bool: 1,
-        remark: '321321dsada',
-        dictValues: ['DIC_BIZ', '-1'],
-      },
-      configValue: [100, 150],
-      idCard: {
-        input: '测试',
-        dictValue: 'DICT_SYS',
-      },
-      apiCheck: ['pump_single'],
-    });
-  });
-  // const count = ref(0);
-  // const countRef = useTransition(count, {
-  //   duration: 1000,
-  //   transition: TransitionPresets.easeInCirc,
-  // });
-  // function plusFn() {
-  //   count.value = Math.round(Math.random() * 100);
-  // }
-  // const [registerTable, { reload, getCacheColumns, setColumns }] = useTable({
-  const [registerTable] = useTable({
-    id: 'sys_config',
-    title: '参数列表2121',
-    columns,
-    dataSource: dataSource,
-    useSearchForm: false,
-    titleStyle: {
-      fontSize: '14px',
-      color: '#000a18',
-      cursor: 'default',
-    },
-    titleLined: true,
-    bordered: false,
-    showIndexColumn: false,
-    pagination: false,
-    // maxHeight: 350,
-    canResize: false,
-  });
-
-  const [registerForm, { setFieldsValue }] = useForm({
-    layout: 'vertical',
-    labelWidth: '100%',
-    baseColProps: {
-      span: 8,
-    },
-    baseRowGutter: [16, 8],
-    schemas: dataFormSchema,
-    showActionButtonGroup: false,
-    actionColOptions: {
-      span: 8,
-    },
-    showResetButton: false,
-    submitButtonOptions: {
-      text: '添加',
-    },
+    for (const i of wardData) {
+      if (!i.disable) {
+        const obj = {
+          key: i.id,
+          label: i.name,
+        };
+        tabData.value.push(obj);
+      }
+    }
+    console.log('PrepareData', PrepareData);
+    for (const i in PrepareData) {
+      // console.log('i', i, PrepareData[i]);
+      if (PrepareData[i] == null) continue;
+      const obj = {
+        groupKey: PrepareData[i] ? i : nanoid(),
+        groupTit: TitleEnum[i],
+        groupMode: 'simple',
+        groupValue: [
+          {
+            value: PrepareData[i] ? PrepareData[i].length : 0,
+            color: TitleEnum[i + 'Color'],
+            background: TitleEnum[i + 'Bg'],
+          },
+        ],
+        groupValueShow: true,
+        groupData: PrepareData[i].map(ele => {
+          return {
+            id: ele.patientBasicId,
+            type: '1',
+            borderLeftColor: TitleEnum[i + 'Color'],
+            ward: ele.inpatientWard,
+            bed: ele.bed,
+            cure: 'HDF/AVF',
+            name: ele.name,
+            age: ele.age,
+            gender: ele.gender,
+            infoShow: false,
+            way: [
+              { label: '透前称量', type: '2' },
+              { label: '确认配方', type: '3' },
+            ],
+          };
+        }),
+      };
+      dataCard.value.push(obj);
+    }
+    console.log(' dataCard.value', dataCard.value);
   });
 
-  function filedChange(key, value) {
-    console.log('🚀 ~ file: index.vue:47 ~ filedChange ~ value:', value);
-    console.log('🚀 ~ file: index.vue:47 ~ filedChange ~ key:', key);
-  }
-  const tabData = [
-    {
-      key: '0',
-      label: '全部',
-      value: 128,
-      hasValue: true,
-      hasBracket: true,
-    },
-    {
-      key: '1',
-      label: 'A区',
-      value: 12,
-      hasValue: true,
-      prefixColor: '#1BC1B3',
-      valueColor: 'red',
-      hasBracket: true,
-    },
-    {
-      key: '2',
-      label: 'B区',
-      value: 18,
-      hasValue: true,
-      prefixColor: '#854AFF',
-      hasBracket: true,
-    },
-  ];
-  const tabData2 = [
-    {
-      key: '0',
-      label: 'A1',
-    },
-    {
-      key: '1',
-      label: 'A2',
-      disabled: true,
-    },
-    {
-      key: '2',
-      label: 'B3',
-    },
-  ];
-  const tabData1 = [
-    {
-      key: '0',
-      label: '全部',
-    },
-    {
-      key: '1',
-      label: 'A区',
-    },
-    {
-      key: '2',
-      label: 'B区',
-    },
-  ];
   // formdata
   const formData = [
     {
-      name: 'text',
+      name: 'prepareStatus',
       label: '全部',
       componentType: 'Select',
       placeholder: '请选择',
-      width: 80,
-      defaultValue: '1',
+      width: 100,
+      defaultValue: '',
       dicts: [
-        { label: '全部', value: '1' },
-        { label: '未称量', value: '2', prefixColor: '#1BC1B3' },
-        { label: '待确认', value: '3', prefixColor: '#854AFF' },
-        { label: '可打印', value: '4', prefixColor: '#1BC1B3' },
-        { label: '完成', value: '5', prefixColor: '#854AFF' },
+        { label: '全部', value: '' },
+        { label: '未称量', value: 'pds_unWeighed', prefixColor: TitleEnum.unWeighedColor },
+        { label: '待确认', value: 'pds_unconfirmed', prefixColor: TitleEnum.unconfirmedColor },
+        { label: '可打印', value: 'pds_printable', prefixColor: TitleEnum.printableColor },
+        { label: '已完成', value: 'pds_finish', prefixColor: TitleEnum.finishColor },
       ],
     },
     {
-      name: 'text22',
+      name: 'sailingSort',
       componentType: 'Select',
       placeholder: '请选择',
       width: 120,
-      defaultValue: '1',
+      defaultValue: '1班',
       label: '班次',
-      dicts: [
-        { label: '第一班', value: '1' },
-        { label: '第二班', value: '2' },
-        { label: '第三班', value: '3' },
-      ],
+      dicts: SailingList.value,
     },
     {
-      name: 'text1',
+      name: 'name',
       componentType: 'Input',
       placeholder: '请输入',
       width: 200,
     },
-    {
-      name: 'text233',
-      componentType: 'DatePicker',
-      placeholder: '请输入',
-      format: 'YYYY-MM-DD',
-      valueFormat: 'YYYY-MM-DD',
-    },
-    {
-      name: 'text233',
-      componentType: 'RangePicker',
-      placeholder: '请输入',
-      format: 'YYYY-MM-DD',
-      valueFormat: 'YYYY-MM-DD',
-    },
-    {
-      name: 'filter',
-      componentType: 'IconBtn',
-      count: 4,
-    },
-  ];
-  // card 标签组
-  const cardData = [
-    {
-      groupKey: '123',
-      groupTit: '待确认/测试/待核对',
-      groupMode: 'default',
-      groupValue: [
-        { value: 10, color: ColorEnum.BLUE, background: ColorEnum.BLUE_BG },
-        { value: 1, color: ColorEnum.MUTED, background: ColorEnum.MUTED_BG },
-        { value: 4, color: ColorEnum.PRIMARY, background: ColorEnum.PRIMARY_BG },
-      ],
-      groupValueShow: true,
-      groupData: [
-        {
-          id: '1',
-          type: '1',
-          borderLeftColor: ColorEnum.BLUE,
-          ward: 'A区',
-          bed: '99',
-          cure: 'HDF/AVF',
-          name: '范了饭饭饭',
-          age: 20,
-          gender: '1',
-          infoShow: true,
-          info: [
-            { label: '时间', value: '4:00', span: 12 },
-            { label: '超滤量', value: '2.1', suffix: 'kg', span: 12 },
-            { label: '血流量', value: '300', span: 12 },
-            { label: '透析器', value: 'fx60', span: 12 },
-            { label: '抗凝剂', value: '低分子肝素1配上低分子肝素2', span: 24 },
-          ],
-          way: [
-            { label: '下机', type: '0' },
-            { label: '医嘱执行', type: '1', badge: 12 },
-            { label: '记录并发症', type: '2', badge: 2 },
-            { label: '交叉核对', type: '3' },
-          ],
-        },
-        {
-          id: '12',
-          type: '2',
-          borderLeftColor: ColorEnum.PRIMARY,
-          ward: 'C区',
-          bed: '99',
-          cure: 'HDF/AVF',
-          name: '范了饭饭饭',
-          age: 20,
-          gender: '1',
-          infoShow: true,
-          info: [
-            { label: '时间', value: '4:00', span: 12 },
-            { label: '超滤量', value: '2.1', suffix: 'kg', span: 12 },
-            { label: '血流量', value: '300', span: 12 },
-            { label: '透析器', value: 'fx60', span: 12 },
-            { label: '抗凝剂', value: '低分子肝素1配上低分子肝素2', span: 24 },
-          ],
-          way: [
-            { label: '下机', type: '0' },
-            { label: '医嘱执行', type: '1', badge: 12 },
-            { label: '记录并发症', type: '2', badge: 2 },
-            { label: '交叉核对', type: '3' },
-          ],
-        },
-        {
-          id: '3',
-          type: '3',
-          borderLeftColor: ColorEnum.MUTED,
-          ward: 'C区',
-          bed: '99',
-          cure: 'HDF/AVF',
-          name: '范了饭饭饭',
-          age: 20,
-          gender: '1',
-          infoShow: true,
-          info: [
-            { label: '时间', value: '4:00', span: 12 },
-            { label: '超滤量', value: '2.1', suffix: 'kg', span: 12 },
-            { label: '血流量', value: '300', span: 12 },
-            { label: '透析器', value: 'fx60', span: 12 },
-            { label: '抗凝剂', value: '低分子肝素1配上低分子肝素2', span: 24 },
-          ],
-          way: [
-            { label: '下机', type: '0' },
-            { label: '医嘱执行', type: '1', badge: 12 },
-            { label: '记录并发症', type: '2', badge: 2 },
-            { label: '交叉核对', type: '3' },
-          ],
-        },
-      ],
-    },
-  ];
-  const cardData1 = [
-    {
-      groupKey: '123',
-      groupTit: '待确认/测试',
-      groupMode: 'simple',
-      groupValue: [
-        { value: 10, color: ColorEnum.BLUE, background: ColorEnum.BLUE_BG },
-        { value: 1, color: ColorEnum.MUTED, background: ColorEnum.MUTED_BG },
-      ],
-      groupValueShow: true,
-      groupData: [
-        {
-          id: '1',
-          type: '1',
-          borderLeftColor: ColorEnum.BLUE,
-          ward: 'A区',
-          bed: '99',
-          cure: 'HDF/AVF',
-          name: '范了饭饭饭',
-          age: 20,
-          gender: '1',
-          infoShow: false,
-          way: [
-            { label: '透前称量', type: '2' },
-            { label: '确认配方', type: '3' },
-          ],
-        },
-      ],
-    },
   ];
 
-  function handleAdd() {
-    timeOuter.value = false;
-  }
   // 回调
-  function callTab(data) {
+  async function callTab(data) {
     console.log('🚀 ~ file: index.vue:41 ~ callTab ~ data:', data);
     tabSelected.value = data.value;
+    const formData = {
+      prepareStatus: Status.value,
+      sailingSort: Sailing.value,
+      name: Name.value,
+      inpatientWardId: tabSelected.value,
+    };
+    const PrepareData = await readyPrepare(formData);
+    console.log('PrepareData', PrepareData);
+    dataCard.value = [];
+    for (const i in PrepareData) {
+      // console.log('i', i, PrepareData[i]);
+      if (PrepareData[i] == null) continue;
+      const obj = {
+        groupKey: PrepareData[i] ? i : nanoid(),
+        groupTit: TitleEnum[i],
+        groupMode: 'simple',
+        groupValue: [
+          {
+            value: PrepareData[i] ? PrepareData[i].length : 0,
+            color: TitleEnum[i + 'Color'],
+            background: TitleEnum[i + 'Bg'],
+          },
+        ],
+        groupValueShow: true,
+        groupData: PrepareData[i].map(ele => {
+          return {
+            id: ele.patientBasicId,
+            type: '1',
+            borderLeftColor: TitleEnum[i + 'Color'],
+            ward: ele.inpatientWard,
+            bed: ele.bed,
+            cure: 'HDF/AVF',
+            name: ele.name,
+            age: ele.age,
+            gender: ele.gender,
+            infoShow: false,
+            way: [
+              { label: '透前称量', type: '2' },
+              { label: '确认配方', type: '3' },
+            ],
+          };
+        }),
+      };
+      dataCard.value.push(obj);
+    }
   }
 
   function cellCard(data) {
     console.log('🚀 ~ file: index.vue:106 ~ cellCard ~ data:', data);
   }
-
-  function callHover() {
-    timeOuter.value = true;
+  //  导入导出回调
+  function cellTitle(data) {
+    console.log('🚀 ~ file: index.vue:106 ~ cellTitle ~ data:', data);
+    if (data.type == 'import') {
+      openModal(true);
+    }
+  }
+  async function cellFrom(data) {
+    Status.value = data.prepareStatus;
+    if (data.sailingSort) {
+      Sailing.value = data.sailingSort;
+    }
+    Name.value = data.name;
+    data.inpatientWardId = tabSelected.value;
+    console.log('🚀 ~ file: index.vue:106 ~ cellFrom ~ data:', data);
+    if (!data.sailingSort) {
+      data.sailingSort = '1';
+    }
+    const PrepareData = await readyPrepare(data);
+    console.log('PrepareData', PrepareData);
+    dataCard.value = [];
+    for (const i in PrepareData) {
+      // console.log('i', i, PrepareData[i]);
+      if (PrepareData[i] == null) continue;
+      const obj = {
+        groupKey: PrepareData[i] ? i : nanoid(),
+        groupTit: TitleEnum[i],
+        groupMode: 'simple',
+        groupValue: [
+          {
+            value: PrepareData[i] ? PrepareData[i].length : 0,
+            color: TitleEnum[i + 'Color'],
+            background: TitleEnum[i + 'Bg'],
+          },
+        ],
+        groupValueShow: true,
+        groupData: PrepareData[i].map(ele => {
+          return {
+            id: ele.patientBasicId,
+            type: '1',
+            borderLeftColor: TitleEnum[i + 'Color'],
+            ward: ele.inpatientWard,
+            bed: ele.bed,
+            cure: 'HDF/AVF',
+            name: ele.name,
+            age: ele.age,
+            gender: ele.gender,
+            infoShow: false,
+            way: [
+              { label: '透前称量', type: '2' },
+              { label: '确认配方', type: '3' },
+            ],
+          };
+        }),
+      };
+      dataCard.value.push(obj);
+    }
   }
+
+  // function callHover() {
+  //   timeOuter.value = true;
+  // }
 </script>
 
 <style lang="less" scoped>

+ 124 - 0
src/views/biz/visit/transfer/data.ts

@@ -0,0 +1,124 @@
+import { BasicColumn, FormSchema } from '/@/components/Table';
+import {
+  archivesPatientBasicQueryPage,
+  archivesPatientBasicDetail,
+} from '@/api/biz/archives/patientBasicApi';
+import { listDictModel } from '/@/api/common';
+
+export const columns: BasicColumn[] = [
+  {
+    title: '患者姓名',
+    dataIndex: 'name',
+  },
+  {
+    title: '信息',
+    dataIndex: 'message',
+  },
+  {
+    title: '交班类型',
+    dataIndex: 'type',
+  },
+  {
+    title: '交班重点内容',
+    dataIndex: 'content',
+  },
+  {
+    title: '记录日期',
+    dataIndex: 'shiftTime',
+  },
+  {
+    title: '记录人',
+    dataIndex: 'updatorName',
+  },
+];
+// 表单新增编辑
+export const dataFormSchema: FormSchema[] = [
+  {
+    label: '患者姓名',
+    field: 'patientBasicId',
+    required: true,
+    ifShow: ({ values }) => {
+      return values.id ? false : true;
+    },
+    component: 'ApiSelect',
+    componentProps: ({ formModel }) => {
+      return {
+        api: archivesPatientBasicQueryPage,
+        params: {
+          name: formModel.patientBasicName,
+          pageSize: 999,
+        },
+        labelField: 'name',
+        valueField: 'id',
+        resultField: 'data',
+        placeholder: '请输入患者姓名',
+        getPopupContainer: () => document.body,
+        showSearch: true,
+        filterOption: false,
+        onSearch: e => {
+          formModel.patientBasicName = e;
+        },
+        onChange: async e => {
+          const detail = await archivesPatientBasicDetail(e);
+          formModel.message = (detail.gender == 'pb_sex_man' ? '男' : '女') + detail.age;
+        },
+      };
+    },
+  },
+  {
+    label: '患者姓名',
+    field: 'name',
+    component: 'Input',
+    componentProps: {
+      disabled: true,
+    },
+    ifShow: ({ values }) => {
+      return values.name ? true : false;
+    },
+  },
+  {
+    label: '患者信息',
+    field: 'message',
+    component: 'Input',
+    componentProps: {
+      disabled: true,
+    },
+  },
+  {
+    label: 'ID',
+    field: 'id',
+    component: 'Input',
+    ifShow: false,
+  },
+  {
+    label: '交班日期',
+    field: 'shiftTime',
+    component: 'DatePicker',
+    componentProps: {
+      format: 'YYYY-MM-DD',
+      placeholder: '请输入上传日期',
+      getPopupContainer: () => document.body,
+      valueFormat: 'YYYY-MM-DD',
+    },
+  },
+  {
+    label: '交班类型',
+    field: 'type',
+    component: 'ApiSelect',
+    componentProps: {
+      placeholder: '请选择交班类型',
+      api: listDictModel,
+      params: {
+        dictCode: 'hb',
+      },
+    },
+  },
+  {
+    label: '交班内容',
+    field: 'content',
+    component: 'InputTextArea',
+    componentProps: {
+      placeholder: '请输入交班内容',
+    },
+  },
+];

+ 79 - 0
src/views/biz/visit/transfer/formModal.vue

@@ -0,0 +1,79 @@
+<template>
+  <BasicModal
+    v-bind="$attrs"
+    destroyOnClose
+    @register="registerModal"
+    :title="getTitle"
+    @ok="handleSubmit"
+    @cancel="handleCancel"
+  >
+    <BasicForm @register="registerForm">
+      <template #messageSlot="{ model, field }">
+        <div>{{ model[field] }}</div>
+      </template>
+    </BasicForm>
+  </BasicModal>
+</template>
+<script lang="ts" setup>
+  import { ref, computed, unref } from 'vue';
+  import { BasicModal, useModalInner } from '/@/components/Modal';
+  import { BasicForm, useForm } from '/@/components/Form';
+  import { useMessage } from '/@/hooks/web/useMessage';
+  import { dataFormSchema } from './data';
+
+  import { transferAdd, transferById, transferEdit } from '/@/api/biz/visit/transferApi';
+
+  const emit = defineEmits(['success', 'cancel', 'register']);
+
+  const getTitle = computed(() => (!unref(isUpdate) ? '新增记录' : '编辑记录'));
+  const isUpdate = ref(false);
+  const rowId = ref();
+
+  const { createMessage } = useMessage();
+  const [registerForm, { resetFields, validate, setFieldsValue }] = useForm({
+    layout: 'vertical',
+    showResetButton: true,
+    labelWidth: 100,
+    schemas: dataFormSchema,
+    showActionButtonGroup: false,
+    actionColOptions: {
+      span: 23,
+    },
+  });
+  const [registerModal, { setModalProps, closeModal }] = useModalInner(async data => {
+    await resetFields();
+    setModalProps({ confirmLoading: false });
+    isUpdate.value = !!data?.isUpdate;
+
+    if (unref(isUpdate)) {
+      rowId.value = data.record.id;
+      const resData = await transferById(data.record.id);
+      console.log('resData::::', resData);
+      resData.message = (resData.gender == 'pb_sex_man' ? '男' : '女') + resData.age;
+      await setFieldsValue({
+        ...resData,
+      });
+    }
+  });
+
+  // 提交按钮事件
+  async function handleSubmit() {
+    try {
+      const values = await validate();
+      setModalProps({ confirmLoading: true });
+      !unref(isUpdate)
+        ? await transferAdd({ ...values })
+        : await transferEdit({ ...values, id: rowId.value });
+      !unref(isUpdate) ? createMessage.success('新增成功!') : createMessage.success('编辑成功!');
+      closeModal();
+      emit('success', { isUpdate: unref(isUpdate), values: { ...values, id: rowId.value } });
+    } finally {
+      setModalProps({ confirmLoading: false, canFullscreen: false });
+    }
+  }
+
+  async function handleCancel() {
+    closeModal();
+    emit('cancel');
+  }
+</script>

+ 321 - 3
src/views/biz/visit/transfer/index.vue

@@ -1,7 +1,325 @@
 <template>
-  <div> 占位符 </div>
+  <div class="m-4">
+    <div>
+      <XTTitle title="交班记录" :right-data="titleData" @click="callTitleClick" />
+      <div class="flex items-center justify-between my-4">
+        <XTTab
+          type="illness"
+          :width="180"
+          :selected="tabSelected"
+          :data="typeOptions"
+          @item-click="callTab"
+        />
+        <XTForm :form-data="formData" @change="callForm" />
+      </div>
+    </div>
+    <BasicTable @register="registerTable">
+      <template #bodyCell="{ column, record }">
+        <template v-if="column.key === 'type'">
+          <span :class="['table-dot', 'table-dot--' + record.type]" />
+          <span> {{ formatDictValue(responseTypeOptions, record.type) }}</span>
+        </template>
+        <template v-if="column.key === 'shiftTime'">
+          {{ dayjs(record.shiftTime).format('YYYY-MM-DD') }}
+        </template>
+        <template v-if="column.key === 'message'">
+          <Icon
+            v-if="record.gender == 'pb_sex_man'"
+            icon="icon-xt-male_sm|iconfont"
+            color="#0057FF"
+          />
+          <Icon
+            v-if="record.gender == 'pb_sex_woman'"
+            icon="icon-xt-female_sm|iconfont"
+            color="#FF0066"
+          />({{ record.age }})</template
+        >
+        <template v-if="column.key === 'updatorName'">
+          <div class="colUpdateAvatar">
+            <img class="colImg" :src="record.updateAvatar" />
+            {{ record.updatorName }}
+          </div>
+        </template>
+        <template v-if="column.key === 'action'">
+          <TableAction
+            :actions="[
+              {
+                auth: 'visit:handoverShifts:edit',
+                icon: 'icon-xt-details_edit_default|iconfont',
+                tooltip: '编辑',
+                onClick: handleEdit.bind(null, record),
+              },
+              {
+                auth: 'visit:handoverShifts:edit',
+                icon: 'icon-xt-details_delete_default|iconfont',
+                tooltip: '删除',
+                popConfirm: {
+                  title: '是否确认删除',
+                  placement: 'left',
+                  confirm: handleDelete.bind(null, record),
+                },
+              },
+            ]"
+          />
+        </template>
+      </template>
+    </BasicTable>
+    <FormModal @register="registerModal" @success="callSuccess" @cancel="handleCancel" />
+  </div>
 </template>
+<script lang="ts" setup>
+  import { onBeforeMount, ref } from 'vue';
+  import { BasicTable, useTable, TableAction } from '/@/components/TableCard';
+  import { Icon } from '/@/components/Icon';
+  import dayjs from 'dayjs';
+  import { useMessage } from '/@/hooks/web/useMessage';
+  import FormModal from './formModal.vue';
+  import { columns } from './data';
 
-<script setup lang="ts"></script>
+  import {
+    getTypeNumber,
+    getTransgerList,
+    transferDel,
+    transferExport,
+  } from '/@/api/biz/visit/transferApi';
+  import { listDictModel } from '/@/api/common';
+  import { formatDictValue } from '/@/utils';
+  import { useModal } from '/@/components/Modal';
+  import { XTTitle } from '/@/components/XTTitle/index';
+  import { XTTab } from '/@/components/XTTab/index';
+  import { XTForm } from '/@/components/XTForm/index';
 
-<style lang="less" scoped></style>
+  // 标题数据
+  const titleData = [
+    {
+      type: 'print',
+      icon: 'icon-xt-print_default',
+    },
+    {
+      type: 'import',
+      icon: 'icon-xt-import_default',
+    },
+    {
+      type: 'add',
+      btnIcon: 'icon-xt-add_default',
+      btnText: '新增记录',
+    },
+  ];
+  // formdata
+  const formData = [
+    {
+      name: 'shiftDate',
+      componentType: 'DatePicker',
+      format: 'YYYY-MM-DD',
+      placeholder: '请选择日期',
+      width: 220,
+    },
+  ];
+  // tab 切换选中
+  const tabSelected = ref();
+  // 操作名称
+  const shiftDate = ref('');
+
+  const typeOptions = ref();
+  const responseTypeOptions = ref();
+  onBeforeMount(async () => {
+    responseTypeOptions.value = await listDictModel({ dictCode: 'hb' });
+    getTab();
+  });
+
+  const { createConfirm, createMessage } = useMessage();
+  const [registerModal, { openModal }] = useModal();
+
+  const tableSort = ref([
+    {
+      field: 'create_time',
+      direction: 'DESC',
+    },
+  ]) as any;
+
+  const [registerTable, { reload, getSelectRowKeys, clearSelectedRowKeys }] = useTable({
+    api: getTransgerList,
+    batchDelApi: transferDel,
+    batchExportApi: transferExport,
+    delAuthList: ['visit:handoverShifts:remove'],
+    rowKey: 'id',
+    columns,
+    showIndexColumn: true,
+    rowSelection: { type: 'checkbox' },
+    bordered: true,
+    actionColumn: {
+      width: 200,
+      title: '操作',
+      dataIndex: 'action',
+    },
+    beforeFetch: handleBeforeFetch,
+    sortFn: handleSortFn,
+  });
+  // 详情按钮事件
+  function handleEdit(record) {
+    openModal(true, {
+      record,
+      isUpdate: true,
+    });
+  }
+
+  // 新增按钮事件
+  function callTitleClick(data) {
+    if (data.type == 'add') {
+      openModal(true, {
+        isUpdate: false,
+        record: data,
+      });
+    } else if (data.type == 'print') {
+      console.log('打印中...');
+    }
+  }
+
+  // 删除按钮事件
+  async function handleDelete(record: Recordable) {
+    if (record) {
+      await transferDel([record.id]);
+      createMessage.success('删除成功!');
+      clearSelectedRowKeys();
+      await reload();
+      await getTab();
+    } else {
+      createConfirm({
+        content: '你确定要删除?',
+        iconType: 'warning',
+        onOk: async () => {
+          const keys = getSelectRowKeys();
+          await transferDel(keys);
+          createMessage.success('删除成功!');
+          await reload();
+          await getTab();
+        },
+      });
+    }
+  }
+  // 表格点击字段排序
+  function handleSortFn(sortInfo) {
+    if (sortInfo?.order && sortInfo?.columnKey) {
+      // 默认单列排序
+      tableSort.value = [
+        {
+          field: sortInfo.columnKey,
+          direction: sortInfo.order.replace(/(\w+)(end)/g, '$1').toUpperCase(),
+        },
+      ];
+    }
+  }
+
+  // 表格请求之前,对参数进行处理, 添加默认 排序
+  async function handleBeforeFetch(params) {
+    return {
+      ...params,
+      orders: tableSort.value,
+      shiftType: tabSelected.value,
+      shiftTime: shiftDate.value == '' ? undefined : shiftDate.value,
+    };
+  }
+
+  async function getTab() {
+    typeOptions.value = await listDictModel({ dictCode: 'hb' });
+    const typeNums = await getTypeNumber(); // 获取各类型数量
+    const typeList = [];
+    typeOptions.value.forEach(ele => {
+      // 变量各类型放置对应数量
+      let typeData = {};
+      Object.keys(typeNums).forEach(numKey => {
+        if (ele.value == numKey) {
+          typeData = {
+            key: ele.value,
+            label: ele.label,
+            value: typeNums[numKey],
+            hasValue: true,
+            prefixColor: ele.prefixColor,
+            hasBracket: true,
+          };
+          typeList.push(typeData);
+        }
+      });
+    });
+    typeList.splice(0, 0, {
+      key: '',
+      label: '全部',
+      value: typeNums.total,
+      hasValue: true,
+      hasBracket: true,
+    });
+    typeOptions.value = typeList;
+    tabSelected.value = typeOptions.value[0].key;
+  }
+
+  //取消按钮事件
+  async function handleCancel() {
+    clearSelectedRowKeys();
+    await reload();
+    await getTab();
+  }
+
+  // 弹窗回调事件
+  async function callSuccess({ isUpdate, values }) {
+    console.log(isUpdate);
+    console.log(values);
+    await reload();
+    await getTab();
+  }
+
+  // 组件回调
+  async function callTab(data) {
+    tabSelected.value = data.value;
+    await reload();
+  }
+  async function callForm(data) {
+    shiftDate.value = data.shiftDate == null ? '' : data.shiftDate;
+    await reload();
+  }
+</script>
+<style lang="less" scoped>
+  .table-dot {
+    display: inline-block;
+    width: 10px;
+    height: 10px;
+    margin-right: 6px;
+    border-radius: 50%;
+
+    &--hb_new_complication {
+      background-color: #f7b500;
+    }
+
+    &--hb_new_patient {
+      background-color: #1bc1b3;
+    }
+
+    &--hb_stop_dialysis {
+      background-color: #d3d8dd;
+    }
+
+    &--hb_else {
+      background-color: #854aff;
+    }
+
+    &--hb_none_heparin {
+      background-color: #2d5aff;
+    }
+  }
+
+  ::v-deep(.ant-btn-link) {
+    color: #8a99ac;
+  }
+
+  .colUpdateAvatar {
+    display: flex;
+    justify-content: center;
+    text-align: center;
+    line-height: 28px;
+  }
+
+  .colImg {
+    width: 28px;
+    height: 28px;
+    margin-right: 5px;
+  }
+</style>

+ 17 - 0
src/views/sys/sysDict/category/data.ts

@@ -24,6 +24,11 @@ export const columns: BasicColumn[] = [
     dataIndex: 'bgColor',
     width: 120,
   },
+  {
+    title: '前缀颜色',
+    dataIndex: 'prefixColor',
+    width: 120,
+  },
   {
     title: '排序',
     dataIndex: 'sort',
@@ -143,6 +148,18 @@ export const dataFormSchema: FormSchema[] = [
       };
     },
   },
+  {
+    field: 'prefixColor',
+    label: '前缀颜色',
+    component: 'FormColorPicker',
+    componentProps: ({ formModel }) => {
+      return {
+        onChange: e => {
+          formModel.prefixColor = e;
+        },
+      };
+    },
+  },
   {
     field: 'sort',
     label: '排序',

+ 5 - 0
src/views/sys/sysDict/category/index.vue

@@ -28,6 +28,11 @@
               {{ record.bgColor }}
             </Tag>
           </template>
+          <template v-if="column.key === 'prefixColor'">
+            <Tag :color="record.prefixColor || '#000'">
+              {{ record.prefixColor }}
+            </Tag>
+          </template>
           <template v-if="column.key === 'action'">
             <TableAction
               :actions="[