Browse Source

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

han 2 năm trước cách đây
mục cha
commit
9546e23f43
71 tập tin đã thay đổi với 5147 bổ sung265 xóa
  1. 7 0
      src/api/biz/README.md
  2. 31 1
      src/api/biz/archives/patientBasicApi.ts
  3. 69 0
      src/api/biz/engineer/bedApi.ts
  4. 69 0
      src/api/biz/engineer/dialysisDeviceApi.ts
  5. 65 0
      src/api/biz/inventory/pharmaceuticalsApi.ts
  6. 66 0
      src/api/biz/inventory/suppliesApi.ts
  7. 7 3
      src/api/biz/management/wardInfo.ts
  8. 1 1
      src/api/biz/visit/transferApi.ts
  9. 0 6
      src/api/common/index.ts
  10. 49 50
      src/assets/iconfont/demo.css
  11. 163 3
      src/assets/iconfont/demo_index.html
  12. 35 3
      src/assets/iconfont/iconfont.css
  13. 0 0
      src/assets/iconfont/iconfont.js
  14. 56 0
      src/assets/iconfont/iconfont.json
  15. BIN
      src/assets/iconfont/iconfont.ttf
  16. BIN
      src/assets/iconfont/iconfont.woff
  17. BIN
      src/assets/iconfont/iconfont.woff2
  18. 1 0
      src/components/Modal/src/BasicModal.vue
  19. 20 3
      src/components/Modal/src/components/ModalHeader.vue
  20. 2 0
      src/components/Modal/src/props.ts
  21. 1 0
      src/components/XTCard/index.ts
  22. 133 0
      src/components/XTCard/src/SimpleCard.vue
  23. 1 0
      src/components/XTImport/index.ts
  24. 319 0
      src/components/XTImport/src/ImportModal.vue
  25. 1 1
      src/components/XTTab/src/XTTab.vue
  26. 2 0
      src/components/XTTitle/src/Title.vue
  27. 1 1
      src/components/XTUpload/src/XTUpload.vue
  28. 7 0
      src/design/index.less
  29. 1 1
      src/design/theme.less
  30. 0 2
      src/utils/file/base64Conver.ts
  31. 0 1
      src/utils/http/axios/Axios.ts
  32. 5 1
      src/utils/http/axios/index.ts
  33. 15 0
      src/utils/index.ts
  34. 44 44
      src/views/README.md
  35. 3 0
      src/views/biz/README.md
  36. 1 1
      src/views/biz/archives/detail/data.ts
  37. 4 2
      src/views/biz/archives/diagnosisHistory/FormModal.vue
  38. 1 1
      src/views/biz/archives/diagnosisHistory/index.vue
  39. 1 1
      src/views/biz/archives/medicalDocuments/FormModal.vue
  40. 2 1
      src/views/biz/archives/medicalDocuments/index.vue
  41. 4 0
      src/views/biz/archives/patientReturn/index.vue
  42. 4 0
      src/views/biz/archives/patrolward/index.vue
  43. 2 2
      src/views/biz/archives/vascularAccess/ViewDrawerComplication.vue
  44. 90 0
      src/views/biz/engineer/bed/FormModal.vue
  45. 194 0
      src/views/biz/engineer/bed/data.ts
  46. 75 0
      src/views/biz/engineer/dialysis/FormDrawerSift.vue
  47. 94 0
      src/views/biz/engineer/dialysis/FormModal.vue
  48. 296 0
      src/views/biz/engineer/dialysis/data.ts
  49. 369 3
      src/views/biz/engineer/dialysis/index.vue
  50. 85 0
      src/views/biz/inventory/pharmaceuticals/data.ts
  51. 94 0
      src/views/biz/inventory/pharmaceuticals/formModal.vue
  52. 306 0
      src/views/biz/inventory/pharmaceuticals/index.vue
  53. 85 0
      src/views/biz/inventory/supplies/data.ts
  54. 90 0
      src/views/biz/inventory/supplies/formModal.vue
  55. 323 0
      src/views/biz/inventory/supplies/index.vue
  56. 12 12
      src/views/biz/management/ward/data.ts
  57. 82 113
      src/views/biz/management/ward/index.vue
  58. 0 0
      src/views/demo/card/data.ts
  59. 307 0
      src/views/demo/card/index.vue
  60. 258 0
      src/views/demo/form/data.ts
  61. 211 0
      src/views/demo/form/index.vue
  62. 0 0
      src/views/demo/list/data.ts
  63. 73 0
      src/views/demo/list/index.vue
  64. 0 0
      src/views/demo/tab/data.ts
  65. 269 0
      src/views/demo/tab/index.vue
  66. 0 0
      src/views/demo/table/data.ts
  67. 7 0
      src/views/demo/table/index.vue
  68. 169 0
      src/views/demo/title/ShortcutModal.vue
  69. 258 0
      src/views/demo/title/data.ts
  70. 198 0
      src/views/demo/title/index.vue
  71. 9 8
      src/views/monitor/loginLog/index.vue

+ 7 - 0
src/api/biz/README.md

@@ -12,3 +12,10 @@
     - vitalsHistoryApi 历史体征数据
   - bed 预约排床
     - scheduledMemoApi 排床备忘录
+  - inventory 库存管理
+  - pharmaceuticalsApi 药品管理
+  - suppliesApi 耗材管理
+  - visit 今日就诊
+  - engineer 工程师端
+    - bedApi 床位管理
+    - dialysisDeviceApi 透析设备

+ 31 - 1
src/api/biz/archives/patientBasicApi.ts

@@ -1,13 +1,18 @@
+import { UploadFileParams } from '#/axios';
 import { defHttp } from '/@/utils/http/axios';
 import { setParams } from '/@/utils/index';
+import { useGlobSetting } from '/@/hooks/setting';
+const globSetting = useGlobSetting();
 
-enum Api {
+export enum Api {
   archivesPatientBasicQueryPage = '/archives/patientBasic/query/page',
   archivesPatientBasicDetail = '/archives/patientBasic/detail',
   archivesPatientBasicAdd = '/archives/patientBasic/add',
   archivesPatientBasicEdit = '/archives/patientBasic/edit',
   archivesPatientBasicRemove = '/archives/patientBasic/removeByIds',
   archivesPatientBasicStats = '/archives/patientBasic/stats',
+  archivesPatientBasicImportBatch = '/archives/patientBasic/import/batch',
+  archivesPatientBasicExport = '/archives/patientBasic/export',
 }
 
 /**
@@ -151,3 +156,28 @@ export const archivesPatientBasicEdit = (params?: object) => {
 export const archivesPatientBasicRemove = (params: Array<string | number>) => {
   return defHttp.post({ url: Api.archivesPatientBasicRemove, params: params });
 };
+
+/**
+ * @description: 批量导入,权限 - archives:patientBasic:import
+ * @method: POST
+ */
+export function archivesPatientBasicImportBatch(
+  params: UploadFileParams,
+  onUploadProgress: (progressEvent: any) => void,
+) {
+  return defHttp.uploadFile(
+    {
+      url: globSetting.apiUrl + Api.archivesPatientBasicImportBatch,
+      onUploadProgress,
+    },
+    params,
+  );
+}
+
+/**
+ * @description: 导出,权限 - archives:patientBasic:export
+ * @method: POST
+ */
+export const archivesPatientBasicExport = (params: Array<string | number>) => {
+  return defHttp.post({ url: Api.archivesPatientBasicExport, params: params });
+};

+ 69 - 0
src/api/biz/engineer/bedApi.ts

@@ -0,0 +1,69 @@
+import { defHttp } from '/@/utils/http/axios';
+import { setParams } from '/@/utils/index';
+
+enum Api {
+  engineerBedQueryPage = '/device/bed/query/page',
+  engineerBedDetail = '/device/bed/detail',
+  engineerBedAdd = '/device/bed/add',
+  engineerBedEdit = '/device/bed/edit',
+  engineerBedRemove = '/device/bed/removeByIds',
+}
+
+/**
+ *
+ * @author fan
+ * @date  2023/06/30 17:39
+ * @description: 根据条件查询病床列表,权限 - device:dialysisDevice:query
+ * @method: POST
+ */
+
+export const engineerBedQueryPage = (params?: object) => {
+  return defHttp.post({ url: Api.engineerBedQueryPage, params: setParams(params) });
+};
+/**
+ *
+ * @author fan
+ * @date  2023/06/30 17:39
+ * @description: 根据id查询病床详细信息,权限 - device:dialysisDevice:query
+ * @method: GET
+ * @param:  id 病床主键id
+ */
+export const engineerBedDetail = (id: string) => {
+  return defHttp.get({ url: Api.engineerBedDetail + '/' + id });
+};
+
+/**
+ *
+ * @author fan
+ * @date  2023/06/30 17:39
+ * @description: 添加病床,权限 - device:dialysisDevice:add
+ * @method: POST
+ * @return:
+ *       0 添加失败
+ *       1 添加成功
+ */
+export const engineerBedAdd = (params?: object) => {
+  return defHttp.post({ url: Api.engineerBedAdd, params: params });
+};
+
+/**
+ *
+ * @author fan
+ * @date  2023/06/30 17:39
+ * @description: 通过主键id编辑病床,权限 - device:dialysisDevice:edit
+ * @method: POST
+ * @return:
+ *       0 编辑失败
+ *       1 编辑成功
+ */
+export const engineerBedEdit = (params?: object) => {
+  return defHttp.post({ url: Api.engineerBedEdit, params: params });
+};
+
+/**
+ * @description: 删除,权限 - device:dialysisDevice:remove
+ * @method: POST
+ */
+export const engineerBedRemove = (params: Array<string | number>) => {
+  return defHttp.post({ url: Api.engineerBedRemove, params: params });
+};

+ 69 - 0
src/api/biz/engineer/dialysisDeviceApi.ts

@@ -0,0 +1,69 @@
+import { defHttp } from '/@/utils/http/axios';
+import { setParams } from '/@/utils/index';
+
+enum Api {
+  engineerDialysisDeviceQueryPage = '/device/dialysisDevice/query/page',
+  engineerDialysisDeviceDetail = '/device/dialysisDevice/detail',
+  engineerDialysisDeviceAdd = '/device/dialysisDevice/add',
+  engineerDialysisDeviceEdit = '/device/dialysisDevice/edit',
+  engineerDialysisDeviceRemove = '/device/dialysisDevice/removeByIds',
+}
+
+/**
+ *
+ * @author fan
+ * @date  2023/06/30 17:39
+ * @description: 根据条件查询病床列表,权限 - device:dialysisDevice:query
+ * @method: POST
+ */
+
+export const engineerDialysisDeviceQueryPage = (params?: object) => {
+  return defHttp.post({ url: Api.engineerDialysisDeviceQueryPage, params: setParams(params) });
+};
+/**
+ *
+ * @author fan
+ * @date  2023/06/30 17:39
+ * @description: 根据id查询病床详细信息,权限 - device:dialysisDevice:query
+ * @method: GET
+ * @param:  id 病床主键id
+ */
+export const engineerDialysisDeviceDetail = (id: string) => {
+  return defHttp.get({ url: Api.engineerDialysisDeviceDetail + '/' + id });
+};
+
+/**
+ *
+ * @author fan
+ * @date  2023/06/30 17:39
+ * @description: 添加病床,权限 - device:dialysisDevice:add
+ * @method: POST
+ * @return:
+ *       0 添加失败
+ *       1 添加成功
+ */
+export const engineerDialysisDeviceAdd = (params?: object) => {
+  return defHttp.post({ url: Api.engineerDialysisDeviceAdd, params: params });
+};
+
+/**
+ *
+ * @author fan
+ * @date  2023/06/30 17:39
+ * @description: 通过主键id编辑病床,权限 - device:dialysisDevice:edit
+ * @method: POST
+ * @return:
+ *       0 编辑失败
+ *       1 编辑成功
+ */
+export const engineerDialysisDeviceEdit = (params?: object) => {
+  return defHttp.post({ url: Api.engineerDialysisDeviceEdit, params: params });
+};
+
+/**
+ * @description: 删除,权限 - device:dialysisDevice:remove
+ * @method: POST
+ */
+export const engineerDialysisDeviceRemove = (params: Array<string | number>) => {
+  return defHttp.post({ url: Api.engineerDialysisDeviceRemove, params: params });
+};

+ 65 - 0
src/api/biz/inventory/pharmaceuticalsApi.ts

@@ -0,0 +1,65 @@
+import { defHttp } from '/@/utils/http/axios';
+import { UploadFileParams } from '#/axios';
+import { setParams } from '/@/utils/index';
+import { useGlobSetting } from '/@/hooks/setting';
+const globSetting = useGlobSetting();
+
+enum Api {
+  pharmaceuticalsNumber = '/biz/drug/number',
+  pharmaceuticalsList = '/biz/drug/query/page',
+  pharmaceuticalsDel = '/biz/drug/status',
+  pharmaceuticalsAdd = '/biz/drug/add',
+  pharmaceuticalsEdit = '/biz/drug/edit',
+  pharmaceuticalsById = '/biz/drug/detail',
+  pharmaceuticalsExport = '/biz/supplies/drug/export',
+  pharmaceuticalsImport = '/biz/supplies/drug/import',
+}
+
+export const getpharmaceuticalsList = (params?: object) => {
+  return defHttp.post({ url: Api.pharmaceuticalsList, params: setParams(params) });
+};
+
+export const pharmaceuticalsDel = (id?: String) => {
+  return defHttp.post({ url: Api.pharmaceuticalsDel + '/' + id });
+};
+
+export const getStatusNumber = () => {
+  return defHttp.get({ url: Api.pharmaceuticalsNumber });
+};
+
+export const pharmaceuticalsAdd = (params?: object) => {
+  return defHttp.post({ url: Api.pharmaceuticalsAdd, params: params });
+};
+
+export const pharmaceuticalsEdit = (params?: object) => {
+  return defHttp.post({ url: Api.pharmaceuticalsEdit, params: params });
+};
+
+export const pharmaceuticalsById = (id: string) => {
+  return defHttp.get({ url: Api.pharmaceuticalsById + '/' + id });
+};
+
+/**
+ * @description: 批量导入,权限 - archives:patientBasic:import
+ * @method: POST
+ */
+export function pharmaceuticalsImportBatch(
+  params: UploadFileParams,
+  onUploadProgress: (progressEvent: any) => void,
+) {
+  return defHttp.uploadFile(
+    {
+      url: globSetting.apiUrl + Api.pharmaceuticalsImport,
+      onUploadProgress,
+    },
+    params,
+  );
+}
+
+/**
+ * @description: 导出,权限 - archives:patientBasic:export
+ * @method: POST
+ */
+export const pharmaceuticalsExport = (params: Array<string | number>) => {
+  return defHttp.post({ url: Api.pharmaceuticalsExport, params: params });
+};

+ 66 - 0
src/api/biz/inventory/suppliesApi.ts

@@ -0,0 +1,66 @@
+import { defHttp } from '/@/utils/http/axios';
+import { UploadFileParams } from '#/axios';
+import { setParams } from '/@/utils/index';
+import { useGlobSetting } from '/@/hooks/setting';
+const globSetting = useGlobSetting();
+
+enum Api {
+  suppliesNumber = '/biz/consumable/number',
+  suppliesList = '/biz/consumable/query/page',
+
+  suppliesDel = '/biz/consumable/status',
+  suppliesAdd = '/biz/consumable/add',
+  suppliesEdit = '/biz/consumable/edit',
+  suppliesById = '/biz/consumable/detail',
+  suppliesExport = '/biz/supplies/consumable/export',
+  suppliesImport = '/biz/supplies/consumable/import',
+}
+
+export const getsuppliesList = (params?: object) => {
+  return defHttp.post({ url: Api.suppliesList, params: setParams(params) });
+};
+
+export const suppliesDel = (id?: String) => {
+  return defHttp.post({ url: Api.suppliesDel + '/' + id });
+};
+
+export const getStatusNumber = () => {
+  return defHttp.get({ url: Api.suppliesNumber });
+};
+
+export const suppliesAdd = (params?: object) => {
+  return defHttp.post({ url: Api.suppliesAdd, params: params });
+};
+
+export const suppliesEdit = (params?: object) => {
+  return defHttp.post({ url: Api.suppliesEdit, params: params });
+};
+
+export const suppliesById = (id: string) => {
+  return defHttp.get({ url: Api.suppliesById + '/' + id });
+};
+
+/**
+ * @description: 批量导入,权限 - archives:patientBasic:import
+ * @method: POST
+ */
+export function suppliesImportBatch(
+  params: UploadFileParams,
+  onUploadProgress: (progressEvent: any) => void,
+) {
+  return defHttp.uploadFile(
+    {
+      url: globSetting.apiUrl + Api.suppliesImport,
+      onUploadProgress,
+    },
+    params,
+  );
+}
+
+/**
+ * @description: 导出,权限 - archives:patientBasic:export
+ * @method: POST
+ */
+export const suppliesExport = (params: Array<string | number>) => {
+  return defHttp.post({ url: Api.suppliesExport, params: params });
+};

+ 7 - 3
src/api/biz/management/wardInfo.ts

@@ -1,15 +1,15 @@
 import { defHttp } from '/@/utils/http/axios';
-import { setParams } from '/@/utils/index';
 enum Api {
-  getWardInfo = '/biz/sys/wardInfo/query/page',
+  getWardInfo = '/biz/sys/wardInfo/query/list',
   wardInfoAdd = '/biz/sys/wardInfo/add',
   wardInfoEdit = '/biz/sys/wardInfo/edit',
   changeStatus = '/biz/sys/wardInfo/modify/status/',
   WardInfoById = '/biz/sys/wardInfo/detail',
+  delWard = '/biz/sys/wardInfo/removeByIds',
 }
 
 export const getWardInfo = (params?: object) => {
-  return defHttp.post({ url: Api.getWardInfo, params: setParams(params) });
+  return defHttp.post({ url: Api.getWardInfo, params: params });
 };
 
 export const wardInfoAdd = (params?: object) => {
@@ -24,6 +24,10 @@ export const changeStatus = (id: string) => {
   return defHttp.post({ url: Api.changeStatus + id });
 };
 
+export const delWard = (params?: object) => {
+  return defHttp.post({ url: Api.delWard, params: params });
+};
+
 export const wardInfoById = (id?: string | number, params?: object) => {
   return defHttp.get({ url: Api.WardInfoById + '/' + id, params: params });
 };

+ 1 - 1
src/api/biz/visit/transferApi.ts

@@ -9,7 +9,7 @@ enum Api {
 }
 
 /**
- * @description: 获取透前准备页面人员信息
+ * @description: 获取透前选项卡数量
  * @method: POET
  */
 export const getTypeNumber = () => {

+ 0 - 6
src/api/common/index.ts

@@ -13,7 +13,6 @@ enum Api {
   getDownloadUrl = '/sys/storage/file/download/',
   getPreviewUrl = '/sys/storage/file/preview/',
   excelSheetDetail = '/excel/sheet/detail',
-  tempDownload = '/archives/patientBasic/export',
   postFileRemoveByIds = '/sys/storage/file/removeByIds',
 }
 
@@ -69,8 +68,3 @@ export function getPreviewUrl(id) {
 export const excelSheetDetail = (id: string) => {
   return defHttp.get({ url: Api.excelSheetDetail + '/' + id });
 };
-
-// 下载模板
-export const tempDownload = (params?: Array<string | number>) => {
-  return defHttp.post({ url: Api.tempDownload, params: params });
-};

+ 49 - 50
src/assets/iconfont/demo.css

@@ -1,15 +1,17 @@
 /* Logo 字体 */
 @font-face {
-  font-family: "iconfont logo";
+  font-family: 'iconfont logo';
   src: url('https://at.alicdn.com/t/font_985780_km7mi63cihi.eot?t=1545807318834');
-  src: url('https://at.alicdn.com/t/font_985780_km7mi63cihi.eot?t=1545807318834#iefix') format('embedded-opentype'),
+  src: url('https://at.alicdn.com/t/font_985780_km7mi63cihi.eot?t=1545807318834#iefix')
+      format('embedded-opentype'),
     url('https://at.alicdn.com/t/font_985780_km7mi63cihi.woff?t=1545807318834') format('woff'),
     url('https://at.alicdn.com/t/font_985780_km7mi63cihi.ttf?t=1545807318834') format('truetype'),
-    url('https://at.alicdn.com/t/font_985780_km7mi63cihi.svg?t=1545807318834#iconfont') format('svg');
+    url('https://at.alicdn.com/t/font_985780_km7mi63cihi.svg?t=1545807318834#iconfont')
+      format('svg');
 }
 
 .logo {
-  font-family: "iconfont logo";
+  font-family: 'iconfont logo';
   font-size: 160px;
   font-style: normal;
   -webkit-font-smoothing: antialiased;
@@ -48,7 +50,6 @@
   color: #666;
 }
 
-
 #tabs .active {
   border-bottom-color: #f00;
   color: #222;
@@ -215,35 +216,35 @@
   margin: 1em 0;
 }
 
-.markdown>p,
-.markdown>blockquote,
-.markdown>.highlight,
-.markdown>ol,
-.markdown>ul {
+.markdown > p,
+.markdown > blockquote,
+.markdown > .highlight,
+.markdown > ol,
+.markdown > ul {
   width: 80%;
 }
 
-.markdown ul>li {
+.markdown ul > li {
   list-style: circle;
 }
 
-.markdown>ul li,
-.markdown blockquote ul>li {
+.markdown > ul li,
+.markdown blockquote ul > li {
   margin-left: 20px;
   padding-left: 4px;
 }
 
-.markdown>ul li p,
-.markdown>ol li p {
+.markdown > ul li p,
+.markdown > ol li p {
   margin: 0.6em 0;
 }
 
-.markdown ol>li {
+.markdown ol > li {
   list-style: decimal;
 }
 
-.markdown>ol li,
-.markdown blockquote ol>li {
+.markdown > ol li,
+.markdown blockquote ol > li {
   margin-left: 20px;
   padding-left: 4px;
 }
@@ -260,7 +261,7 @@
   font-weight: 600;
 }
 
-.markdown>table {
+.markdown > table {
   border-collapse: collapse;
   border-spacing: 0px;
   empty-cells: show;
@@ -269,21 +270,21 @@
   margin-bottom: 24px;
 }
 
-.markdown>table th {
+.markdown > table th {
   white-space: nowrap;
   color: #333;
   font-weight: 600;
 }
 
-.markdown>table th,
-.markdown>table td {
+.markdown > table th,
+.markdown > table td {
   border: 1px solid #e9e9e9;
   padding: 8px 16px;
   text-align: left;
 }
 
-.markdown>table th {
-  background: #F7F7F7;
+.markdown > table th {
+  background: #f7f7f7;
 }
 
 .markdown blockquote {
@@ -318,12 +319,11 @@
   display: inline-block;
 }
 
-.markdown>br,
-.markdown>p>br {
+.markdown > br,
+.markdown > p > br {
   clear: both;
 }
 
-
 .hljs {
   display: block;
   background: white;
@@ -399,8 +399,8 @@ https://prismjs.com/download.html#themes=prism&languages=markup+css+clike+javasc
  * Based on dabblet (http://dabblet.com)
  * @author Lea Verou
  */
-code[class*="language-"],
-pre[class*="language-"] {
+code[class*='language-'],
+pre[class*='language-'] {
   color: black;
   background: none;
   text-shadow: 0 1px white;
@@ -422,46 +422,45 @@ pre[class*="language-"] {
   hyphens: none;
 }
 
-pre[class*="language-"]::-moz-selection,
-pre[class*="language-"] ::-moz-selection,
-code[class*="language-"]::-moz-selection,
-code[class*="language-"] ::-moz-selection {
+pre[class*='language-']::-moz-selection,
+pre[class*='language-'] ::-moz-selection,
+code[class*='language-']::-moz-selection,
+code[class*='language-'] ::-moz-selection {
   text-shadow: none;
   background: #b3d4fc;
 }
 
-pre[class*="language-"]::selection,
-pre[class*="language-"] ::selection,
-code[class*="language-"]::selection,
-code[class*="language-"] ::selection {
+pre[class*='language-']::selection,
+pre[class*='language-'] ::selection,
+code[class*='language-']::selection,
+code[class*='language-'] ::selection {
   text-shadow: none;
   background: #b3d4fc;
 }
 
 @media print {
-
-  code[class*="language-"],
-  pre[class*="language-"] {
+  code[class*='language-'],
+  pre[class*='language-'] {
     text-shadow: none;
   }
 }
 
 /* Code blocks */
-pre[class*="language-"] {
+pre[class*='language-'] {
   padding: 1em;
-  margin: .5em 0;
+  margin: 0.5em 0;
   overflow: auto;
 }
 
-:not(pre)>code[class*="language-"],
-pre[class*="language-"] {
+:not(pre) > code[class*='language-'],
+pre[class*='language-'] {
   background: #f5f2f0;
 }
 
 /* Inline code */
-:not(pre)>code[class*="language-"] {
-  padding: .1em;
-  border-radius: .3em;
+:not(pre) > code[class*='language-'] {
+  padding: 0.1em;
+  border-radius: 0.3em;
   white-space: normal;
 }
 
@@ -477,7 +476,7 @@ pre[class*="language-"] {
 }
 
 .namespace {
-  opacity: .7;
+  opacity: 0.7;
 }
 
 .token.property,
@@ -505,7 +504,7 @@ pre[class*="language-"] {
 .language-css .token.string,
 .style .token.string {
   color: #9a6e3a;
-  background: hsla(0, 0%, 100%, .5);
+  background: hsla(0, 0%, 100%, 0.5);
 }
 
 .token.atrule,
@@ -516,7 +515,7 @@ pre[class*="language-"] {
 
 .token.function,
 .token.class-name {
-  color: #DD4A68;
+  color: #dd4a68;
 }
 
 .token.regex,

+ 163 - 3
src/assets/iconfont/demo_index.html

@@ -68,6 +68,54 @@
       <div class="tab-container">
         <div class="content unicode" style="display: block">
           <ul class="icon_lists dib-box">
+            <li class="dib">
+              <span class="icon iconfont">&#xe60e;</span>
+              <div class="name">xt-停用-默认</div>
+              <div class="code-name">&amp;#xe60e;</div>
+            </li>
+
+            <li class="dib">
+              <span class="icon iconfont">&#xe60f;</span>
+              <div class="name">xt-停用-鼠标上移</div>
+              <div class="code-name">&amp;#xe60f;</div>
+            </li>
+
+            <li class="dib">
+              <span class="icon iconfont">&#xe73d;</span>
+              <div class="name">xt-导入成功</div>
+              <div class="code-name">&amp;#xe73d;</div>
+            </li>
+
+            <li class="dib">
+              <span class="icon iconfont">&#xe607;</span>
+              <div class="name">xt-盘点-默认</div>
+              <div class="code-name">&amp;#xe607;</div>
+            </li>
+
+            <li class="dib">
+              <span class="icon iconfont">&#xe608;</span>
+              <div class="name">xt-盘点-禁用</div>
+              <div class="code-name">&amp;#xe608;</div>
+            </li>
+
+            <li class="dib">
+              <span class="icon iconfont">&#xe609;</span>
+              <div class="name">xt-盘点-鼠标上移</div>
+              <div class="code-name">&amp;#xe609;</div>
+            </li>
+
+            <li class="dib">
+              <span class="icon iconfont">&#xe60a;</span>
+              <div class="name">xt-撤销-默认</div>
+              <div class="code-name">&amp;#xe60a;</div>
+            </li>
+
+            <li class="dib">
+              <span class="icon iconfont">&#xe60b;</span>
+              <div class="name">xt-撤销-鼠标上移</div>
+              <div class="code-name">&amp;#xe60b;</div>
+            </li>
+
             <li class="dib">
               <span class="icon iconfont">&#xe73f;</span>
               <div class="name">xt-双泵</div>
@@ -2356,9 +2404,9 @@
             <pre><code class="language-css"
 >@font-face {
   font-family: 'iconfont';
-  src: url('iconfont.woff2?t=1686992005532') format('woff2'),
-       url('iconfont.woff?t=1686992005532') format('woff'),
-       url('iconfont.ttf?t=1686992005532') format('truetype');
+  src: url('iconfont.woff2?t=1695089019525') format('woff2'),
+       url('iconfont.woff?t=1695089019525') format('woff'),
+       url('iconfont.ttf?t=1695089019525') format('truetype');
 }
 </code></pre>
             <h3 id="-iconfont-">第二步:定义使用 iconfont 的样式</h3>
@@ -2383,6 +2431,54 @@
         </div>
         <div class="content font-class">
           <ul class="icon_lists dib-box">
+            <li class="dib">
+              <span class="icon iconfont icon-tingyong-moren"></span>
+              <div class="name"> xt-停用-默认 </div>
+              <div class="code-name">.icon-tingyong-moren </div>
+            </li>
+
+            <li class="dib">
+              <span class="icon iconfont icon-tingyong-jinyong"></span>
+              <div class="name"> xt-停用-鼠标上移 </div>
+              <div class="code-name">.icon-tingyong-jinyong </div>
+            </li>
+
+            <li class="dib">
+              <span class="icon iconfont icon-xt-daoruchenggong"></span>
+              <div class="name"> xt-导入成功 </div>
+              <div class="code-name">.icon-xt-daoruchenggong </div>
+            </li>
+
+            <li class="dib">
+              <span class="icon iconfont icon-xt-Inventory_default"></span>
+              <div class="name"> xt-盘点-默认 </div>
+              <div class="code-name">.icon-xt-Inventory_default </div>
+            </li>
+
+            <li class="dib">
+              <span class="icon iconfont icon-xt-Inventory_disable"></span>
+              <div class="name"> xt-盘点-禁用 </div>
+              <div class="code-name">.icon-xt-Inventory_disable </div>
+            </li>
+
+            <li class="dib">
+              <span class="icon iconfont icon-xt-Inventory_selected"></span>
+              <div class="name"> xt-盘点-鼠标上移 </div>
+              <div class="code-name">.icon-xt-Inventory_selected </div>
+            </li>
+
+            <li class="dib">
+              <span class="icon iconfont icon-xt-revocation_default"></span>
+              <div class="name"> xt-撤销-默认 </div>
+              <div class="code-name">.icon-xt-revocation_default </div>
+            </li>
+
+            <li class="dib">
+              <span class="icon iconfont icon-xt-revocation_selected"></span>
+              <div class="name"> xt-撤销-鼠标上移 </div>
+              <div class="code-name">.icon-xt-revocation_selected </div>
+            </li>
+
             <li class="dib">
               <span class="icon iconfont icon-xt-dual-pump_default"></span>
               <div class="name"> xt-双泵 </div>
@@ -4681,6 +4777,70 @@
         </div>
         <div class="content symbol">
           <ul class="icon_lists dib-box">
+            <li class="dib">
+              <svg class="icon svg-icon" aria-hidden="true">
+                <use xlink:href="#icon-tingyong-moren"></use>
+              </svg>
+              <div class="name">xt-停用-默认</div>
+              <div class="code-name">#icon-tingyong-moren</div>
+            </li>
+
+            <li class="dib">
+              <svg class="icon svg-icon" aria-hidden="true">
+                <use xlink:href="#icon-tingyong-jinyong"></use>
+              </svg>
+              <div class="name">xt-停用-鼠标上移</div>
+              <div class="code-name">#icon-tingyong-jinyong</div>
+            </li>
+
+            <li class="dib">
+              <svg class="icon svg-icon" aria-hidden="true">
+                <use xlink:href="#icon-xt-daoruchenggong"></use>
+              </svg>
+              <div class="name">xt-导入成功</div>
+              <div class="code-name">#icon-xt-daoruchenggong</div>
+            </li>
+
+            <li class="dib">
+              <svg class="icon svg-icon" aria-hidden="true">
+                <use xlink:href="#icon-xt-Inventory_default"></use>
+              </svg>
+              <div class="name">xt-盘点-默认</div>
+              <div class="code-name">#icon-xt-Inventory_default</div>
+            </li>
+
+            <li class="dib">
+              <svg class="icon svg-icon" aria-hidden="true">
+                <use xlink:href="#icon-xt-Inventory_disable"></use>
+              </svg>
+              <div class="name">xt-盘点-禁用</div>
+              <div class="code-name">#icon-xt-Inventory_disable</div>
+            </li>
+
+            <li class="dib">
+              <svg class="icon svg-icon" aria-hidden="true">
+                <use xlink:href="#icon-xt-Inventory_selected"></use>
+              </svg>
+              <div class="name">xt-盘点-鼠标上移</div>
+              <div class="code-name">#icon-xt-Inventory_selected</div>
+            </li>
+
+            <li class="dib">
+              <svg class="icon svg-icon" aria-hidden="true">
+                <use xlink:href="#icon-xt-revocation_default"></use>
+              </svg>
+              <div class="name">xt-撤销-默认</div>
+              <div class="code-name">#icon-xt-revocation_default</div>
+            </li>
+
+            <li class="dib">
+              <svg class="icon svg-icon" aria-hidden="true">
+                <use xlink:href="#icon-xt-revocation_selected"></use>
+              </svg>
+              <div class="name">xt-撤销-鼠标上移</div>
+              <div class="code-name">#icon-xt-revocation_selected</div>
+            </li>
+
             <li class="dib">
               <svg class="icon svg-icon" aria-hidden="true">
                 <use xlink:href="#icon-xt-dual-pump_default"></use>

+ 35 - 3
src/assets/iconfont/iconfont.css

@@ -1,8 +1,8 @@
 @font-face {
   font-family: 'iconfont'; /* Project id 3806176 */
-  src: url('//at.alicdn.com/t/c/font_3806176_em9e5cie965.woff2?t=1685580968021') format('woff2'),
-    url('//at.alicdn.com/t/c/font_3806176_em9e5cie965.woff?t=1685580968021') format('woff'),
-    url('//at.alicdn.com/t/c/font_3806176_em9e5cie965.ttf?t=1685580968021') format('truetype');
+  src: url('iconfont.woff2?t=1695089019525') format('woff2'),
+    url('iconfont.woff?t=1695089019525') format('woff'),
+    url('iconfont.ttf?t=1695089019525') format('truetype');
 }
 
 .iconfont {
@@ -13,6 +13,38 @@
   -moz-osx-font-smoothing: grayscale;
 }
 
+.icon-tingyong-moren:before {
+  content: '\e60e';
+}
+
+.icon-tingyong-jinyong:before {
+  content: '\e60f';
+}
+
+.icon-xt-daoruchenggong:before {
+  content: '\e73d';
+}
+
+.icon-xt-Inventory_default:before {
+  content: '\e607';
+}
+
+.icon-xt-Inventory_disable:before {
+  content: '\e608';
+}
+
+.icon-xt-Inventory_selected:before {
+  content: '\e609';
+}
+
+.icon-xt-revocation_default:before {
+  content: '\e60a';
+}
+
+.icon-xt-revocation_selected:before {
+  content: '\e60b';
+}
+
 .icon-xt-dual-pump_default:before {
   content: '\e73f';
 }

Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 0 - 0
src/assets/iconfont/iconfont.js


+ 56 - 0
src/assets/iconfont/iconfont.json

@@ -5,6 +5,62 @@
   "css_prefix_text": "icon-",
   "description": "",
   "glyphs": [
+    {
+      "icon_id": "37422611",
+      "name": "xt-停用-默认",
+      "font_class": "tingyong-moren",
+      "unicode": "e60e",
+      "unicode_decimal": 58894
+    },
+    {
+      "icon_id": "37422728",
+      "name": "xt-停用-鼠标上移",
+      "font_class": "tingyong-jinyong",
+      "unicode": "e60f",
+      "unicode_decimal": 58895
+    },
+    {
+      "icon_id": "37383981",
+      "name": "xt-导入成功",
+      "font_class": "xt-daoruchenggong",
+      "unicode": "e73d",
+      "unicode_decimal": 59197
+    },
+    {
+      "icon_id": "36904889",
+      "name": "xt-盘点-默认",
+      "font_class": "xt-Inventory_default",
+      "unicode": "e607",
+      "unicode_decimal": 58887
+    },
+    {
+      "icon_id": "36904945",
+      "name": "xt-盘点-禁用",
+      "font_class": "xt-Inventory_disable",
+      "unicode": "e608",
+      "unicode_decimal": 58888
+    },
+    {
+      "icon_id": "36904963",
+      "name": "xt-盘点-鼠标上移",
+      "font_class": "xt-Inventory_selected",
+      "unicode": "e609",
+      "unicode_decimal": 58889
+    },
+    {
+      "icon_id": "36905035",
+      "name": "xt-撤销-默认",
+      "font_class": "xt-revocation_default",
+      "unicode": "e60a",
+      "unicode_decimal": 58890
+    },
+    {
+      "icon_id": "36905074",
+      "name": "xt-撤销-鼠标上移",
+      "font_class": "xt-revocation_selected",
+      "unicode": "e60b",
+      "unicode_decimal": 58891
+    },
     {
       "icon_id": "35780043",
       "name": "xt-双泵",

BIN
src/assets/iconfont/iconfont.ttf


BIN
src/assets/iconfont/iconfont.woff


BIN
src/assets/iconfont/iconfont.woff2


+ 1 - 0
src/components/Modal/src/BasicModal.vue

@@ -13,6 +13,7 @@
       <ModalHeader
         :helpMessage="getProps.helpMessage"
         :title="getMergeProps.title"
+        :subTitle="getProps.subTitle"
         @dblclick="handleTitleDbClick"
       />
     </template>

+ 20 - 3
src/components/Modal/src/components/ModalHeader.vue

@@ -1,7 +1,10 @@
 <template>
-  <BasicTitle :helpMessage="helpMessage" class="!font-semibold">
-    {{ title }}
-  </BasicTitle>
+  <span class="title-wrap">
+    <BasicTitle :helpMessage="helpMessage" class="!font-semibold">
+      {{ title }}
+    </BasicTitle>
+    <span v-if="!helpMessage && subTitle" class="subTitle">{{ subTitle }}</span>
+  </span>
 </template>
 <script lang="ts">
   import type { PropType } from 'vue';
@@ -16,7 +19,21 @@
         type: [String, Array] as PropType<string | string[]>,
       },
       title: { type: String },
+      subTitle: { type: String },
     },
     emits: ['dblclick'],
   });
 </script>
+<style scoped>
+  .title-wrap {
+    display: inline-flex;
+    align-items: baseline;
+  }
+
+  .subTitle {
+    font-size: 14px;
+    font-weight: 400;
+    color: #8290ac;
+    margin-left: 4px;
+  }
+</style>

+ 2 - 0
src/components/Modal/src/props.ts

@@ -71,6 +71,8 @@ export const basicProps = Object.assign({}, modalProps, {
 
   title: { type: String },
 
+  subTitle: { type: String },
+
   visible: { type: Boolean },
 
   width: [String, Number] as PropType<string | number>,

+ 1 - 0
src/components/XTCard/index.ts

@@ -1,3 +1,4 @@
 export { default as XTCard } from './src/XTCard.vue';
 export { default as DescCard } from './src/DescCard.vue';
 export { default as DescMultiCard } from './src/DescMultiCard.vue';
+export { default as SimpleCard } from './src/SimpleCard.vue';

+ 133 - 0
src/components/XTCard/src/SimpleCard.vue

@@ -0,0 +1,133 @@
+<template>
+  <div class="card">
+    <div class="card-head" v-if="showHead">
+      <div class="card-head_label"> {{ title }}</div>
+      <div class="flex card-head_value">
+        <slot name="headRight" />
+        <i :class="['iconfont', icon]" v-if="icon" @click="handleIcon" />
+      </div>
+    </div>
+    <div class="card-body">
+      <slot name="body" />
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+  interface Props {
+    id?: string;
+    // 标题
+    title: string;
+    // 是否显示head 部分
+    showHead?: boolean;
+    // icon
+    icon?: string | unknown;
+    iconType?: string;
+    // 每行宽度
+    wrapSpan?: number;
+    type?: string;
+    right?: {
+      show: boolean;
+      date?: string;
+      doctor?: string;
+      edit?: boolean;
+      delete?: boolean;
+      span?: number;
+    };
+    // 数据集
+    data: Array<{
+      filed?: string;
+      label: string;
+      value?: string;
+      span?: number | unknown;
+      dict?: boolean | unknown;
+      dictName?: string | unknown;
+      dictField?: string | unknown;
+      hidden?: boolean | unknown;
+      tags?:
+        | Array<{
+            id: string;
+            label: string;
+            type: string | 'warning' | 'error' | 'success' | 'primary' | 'muted';
+            fontColor: string;
+            bgColor: string;
+          }>
+        | unknown
+        | any;
+    }>;
+  }
+  const props = withDefaults(defineProps<Props>(), {
+    title: '标题',
+    showHead: true,
+    wrapSpan: 6,
+    type: '',
+    data: () => [],
+    right: () => {
+      return {
+        show: false,
+        span: 4,
+        edit: true,
+        delete: false,
+      };
+    },
+  });
+  const emit = defineEmits(['icon']);
+
+  function handleIcon() {
+    const info = { id: props.id, type: props.type, action: props.iconType || 'icon' };
+    emit('icon', info);
+  }
+</script>
+
+<style lang="less" scoped>
+  .card {
+    border-radius: 4px;
+    border: 1px solid #efefef;
+    background-color: #fff;
+
+    &-head {
+      display: flex;
+      align-items: center;
+      justify-content: space-between;
+      height: 56px;
+      background: #f6f8fa;
+      box-shadow: 0 1px 0 0 #efefef;
+      border-radius: 3px 3px 0 0;
+      padding: 0 20px;
+
+      &_label {
+        font-size: 18px;
+        font-weight: 600;
+        color: #000a18;
+      }
+
+      &_value {
+        & .iconfont {
+          display: block;
+          margin-right: 16px;
+          background: #fff;
+          border-radius: 50%;
+          width: 32px;
+          height: 32px;
+          text-align: center;
+          line-height: 32px;
+          cursor: pointer;
+
+          &:last-child {
+            margin-right: 0;
+          }
+
+          &:hover {
+            color: #0075ff;
+          }
+        }
+      }
+    }
+
+    &-body {
+      display: flex;
+      align-items: center;
+      padding: 20px;
+    }
+  }
+</style>

+ 1 - 0
src/components/XTImport/index.ts

@@ -0,0 +1 @@
+export { default as ImportModal } from './src/ImportModal.vue';

+ 319 - 0
src/components/XTImport/src/ImportModal.vue

@@ -0,0 +1,319 @@
+<template>
+  <BasicModal
+    :title="getTitle"
+    :canFullscreen="false"
+    :maskClosable="false"
+    :showOkBtn="showOkBtn"
+    @register="registerModal"
+    @ok="handleSubmit"
+    @cancel="handleSubmit"
+    width="580px"
+  >
+    <div
+      class="wrap"
+      :style="{
+        borderColor: importInfo[importStatus].borderColor,
+        borderStyle: importInfo[importStatus].borderStyle,
+      }"
+    >
+      <div class="wrap-cnt" :style="{ backgroundColor: importInfo[importStatus].backgroundColor }">
+        <div>
+          <div class="cnt-tit">
+            <i
+              :class="[
+                'iconfont cnt-icon',
+                'cnt-icon_' + importStatus,
+                importInfo[importStatus].icon,
+              ]"
+            />
+            {{ importInfo[importStatus].title }}
+          </div>
+          <div v-if="importStatus == 'default'">
+            <div class="cnt-desc">
+              若是您第一次上传文件,可先下载
+              <span :class="['pointer', 'cnt-desc_link']" @click="downloadFile"> 文件模板</span
+              >,文件大小不超过5M
+            </div>
+            <div class="cnt-detail">
+              <Upload
+                class="button-style"
+                name="file"
+                :before-upload="handleBeforeUpload"
+                :show-upload-list="false"
+              >
+                <a-button type="primary"> 导入文件 </a-button>
+              </Upload>
+            </div>
+          </div>
+        </div>
+        <div v-if="importStatus == 'progress'">
+          <div class="cnt-desc">
+            {{ importInfo[importStatus].desc }}
+          </div>
+          <div class="cnt-detail">
+            <Progress
+              :stroke-color="strokeColor"
+              :percent="importProgress"
+              :showInfo="false"
+              status="active"
+            />
+          </div>
+        </div>
+        <div v-if="importStatus == 'success'">
+          <div :class="['cnt-desc pointer', 'cnt-desc_link']" @click="importStatus = 'default'">
+            继续导入数据
+          </div>
+        </div>
+        <div v-if="importStatus == 'fail'">
+          <div class="cnt-desc">
+            <span class="mr-3">
+              成功:
+              <span
+                :style="{
+                  color: '#19BE6B',
+                }"
+              >
+                {{ importFail.success }}
+              </span></span
+            >
+            <span class="mr-3">
+              失败:
+              <span
+                :style="{
+                  color: importInfo[importStatus].borderColor,
+                }"
+              >
+                {{ importFail.fail }}
+              </span>
+            </span>
+            <span> 总数: {{ importFail.total }}</span>
+          </div>
+          <div class="cnt-detail">
+            下载失败结果 <a class="cnt-desc_link pointer" @click="exportExcel"> 失败文件</a>
+          </div>
+        </div>
+      </div>
+    </div>
+  </BasicModal>
+</template>
+
+<script setup lang="ts">
+  import { BasicModal, useModalInner } from '@/components/Modal';
+  import { excelSheetDetail, getDownloadUrl } from '/@/api/common/index';
+  import { downloadByBase64, downloadByUrl } from '/@/utils/file/download';
+  import { reactive, ref } from 'vue';
+  import { Progress, Upload } from 'ant-design-vue';
+  import { useMessage } from '/@/hooks/web/useMessage';
+
+  const { createMessage } = useMessage();
+  const uploadApi = ref<any>();
+  const exportApi = ref<any>();
+  const getTitle = ref('导入数据');
+  const isComplete = ref(0);
+  const exportFileId = ref('');
+  const timer = ref(null);
+  const showOkBtn = ref(false);
+  const strokeColor = {
+    '0%': '#00A8FF',
+    '100%': '#0072FF',
+  };
+  const importStatus = ref<'default' | 'progress' | 'success' | 'fail'>('default');
+  const importInfo = {
+    default: {
+      icon: '',
+      title: '选择需要导入的文件',
+      desc: '',
+      borderColor: '#DCDEE2',
+      borderStyle: 'dashed',
+      backgroundColor: '#F4F6F9',
+    },
+    progress: {
+      icon: '',
+      title: '数据导入中…',
+      desc: '请稍等, 不要离开此页面',
+      borderColor: '#DCDEE2',
+      borderStyle: 'dashed',
+      backgroundColor: '#F4F6F9',
+    },
+    success: {
+      icon: 'icon-xt-success',
+      title: '数据导入成功',
+      desc: '',
+      borderColor: '#DCDEE2',
+      borderStyle: 'solid',
+      backgroundColor: '#F4F6F9',
+    },
+    fail: {
+      icon: 'icon-xt-fail',
+      title: '数据导入失败',
+      desc: '',
+      borderColor: '#FF5D39',
+      borderStyle: 'solid',
+      backgroundColor: '#FFF0ED',
+    },
+  };
+  const importFail = reactive({
+    success: 0,
+    fail: 0,
+    total: 0,
+  });
+  const importProgress = ref(30);
+  const [registerModal, { setModalProps, closeModal }] = useModalInner(async data => {
+    console.log('🚀 ~ file: code.vue:21 ~ data:', data);
+    setModalProps({ confirmLoading: false });
+    getTitle.value = data?.title;
+    uploadApi.value = data.importUrl;
+    exportApi.value = data.exportUrl;
+  });
+
+  async function handleSubmit() {
+    showOkBtn.value = false;
+    clearInterval(timer.value);
+    timer.value = null;
+    importProgress.value = 30;
+    if (importStatus.value == 'success' || importStatus.value == 'fail') {
+      importStatus.value = 'default';
+      await closeModal();
+    } else {
+      importStatus.value = 'default';
+    }
+  }
+  async function startUpload(id) {
+    if (id) {
+      console.log('🚀 ~ file: ImportModal.vue:197 ~ onChange ~ id:', id);
+      timer.value = setInterval(async function () {
+        const importStats = await excelSheetDetail(id);
+        console.log('importStats', importStats);
+        // const percent = (importStats.failCount + importStats.successCount) / importStats.totalCount;
+        // 进度条
+        console.log('进度条:', importProgress.value);
+        if (
+          importStats.status == 'parsing' ||
+          importStats.status == 'parsingSuccess' ||
+          importStats.status == 'importing'
+        ) {
+          importProgress.value = 90;
+          return;
+        }
+        if (importStats.status == 'importFail') {
+          clearInterval(timer.value);
+          importStatus.value = 'fail';
+          createMessage.error('取消导入');
+          return;
+        }
+        if (importStats.status == 'importSuccess') {
+          importProgress.value = 100;
+          setTimeout(function () {
+            importStatus.value = 'success';
+            showOkBtn.value = true;
+            clearInterval(timer.value);
+            exportFileId.value = importStats.exportFileId;
+            importFail.fail = importStats.failCount;
+            importFail.success = importStats.successCount;
+            importFail.total = importStats.totalCount;
+            importProgress.value = 30;
+          }, 1000 * 1);
+        }
+      }, 1000 * 3);
+    }
+  }
+  async function handleBeforeUpload(file) {
+    console.log('file', file);
+    importStatus.value = 'progress';
+    const res = await uploadApi.value(
+      {
+        data: {},
+        file,
+        filename: file.name,
+      },
+      async function onUploadProgress(progressEvent: ProgressEvent) {
+        isComplete.value = ((progressEvent.loaded / progressEvent.total) * 100) | 0;
+      },
+    );
+    if (isComplete.value == 100) {
+      importProgress.value = 50;
+      await startUpload(res.data.data);
+      isComplete.value = 0;
+    }
+    return false;
+  }
+  async function exportExcel() {
+    const res = await getDownloadUrl(exportFileId.value);
+    // console.log('res', res);
+    downloadByUrl({ url: res });
+  }
+  async function downloadFile() {
+    const res = await exportApi.value([]);
+    downloadByBase64(res.base64, res.fileName);
+  }
+</script>
+
+<style lang="less" scoped>
+  .wrap {
+    width: 480px;
+    height: 180px;
+    background: #fff;
+    border-radius: 2px;
+    border-width: 1px;
+    padding: 8px;
+    margin: 20px auto;
+
+    &-cnt {
+      height: 100%;
+      padding-top: 25px;
+    }
+  }
+
+  .cnt {
+    &-icon {
+      font-size: 24px;
+      margin-right: 4px;
+
+      &_success {
+        color: #19be6b;
+      }
+
+      &_fail {
+        color: #ff5d39;
+      }
+    }
+
+    &-tit {
+      display: flex;
+      justify-content: center;
+      align-items: center;
+      font-size: 18px;
+      font-weight: 500;
+      color: #000a18;
+      line-height: 25px;
+      text-align: center;
+      margin-bottom: 10px;
+    }
+
+    &-desc {
+      font-size: 14px;
+      color: #515a6e;
+      line-height: 20px;
+      text-align: center;
+      margin-bottom: 24px;
+
+      &_link {
+        font-size: 14px;
+        color: #006dff;
+        text-decoration-line: underline;
+      }
+    }
+
+    &-detail {
+      text-align: center;
+    }
+  }
+
+  ::v-deep(.ant-progress-line) {
+    width: 152px;
+  }
+
+  ::v-deep(.ant-progress-inner) {
+    background-color: #fff;
+  }
+</style>

+ 1 - 1
src/components/XTTab/src/XTTab.vue

@@ -10,7 +10,7 @@
         v-for="item in data"
         :key="item.key"
         @click="handleClick(item)"
-        :style="{ width: width + 'px' }"
+        :style="{ minWidth: width + 'px' }"
       >
         <div
           v-if="item.prefixColor"

+ 2 - 0
src/components/XTTitle/src/Title.vue

@@ -15,6 +15,7 @@
         <div
           v-for="item in rightData"
           :key="item.type"
+          v-auth="item.auth"
           :class="['xt-title_item', item.icon ? 'xt-title_item--icon' : 'xt-title_item--btn']"
         >
           <a-upload
@@ -57,6 +58,7 @@
       btnText?: string;
       btnType?: string | 'primary' | 'ghost' | 'dashed' | 'link' | 'text' | 'default';
       btnIcon?: string;
+      auth?: Array<string>;
     }>;
     upload?: {
       accept: string;

+ 1 - 1
src/components/XTUpload/src/XTUpload.vue

@@ -133,7 +133,7 @@
         () => props.value,
         (value = []) => {
           fileList.value = isArray(value) ? value : [];
-          console.log('🚀 ~ file: XTUpload.vue:80 ~ setup ~ fileList.value :', fileList.value);
+          // console.log('🚀 ~ file: XTUpload.vue:80 ~ setup ~ fileList.value :', fileList.value);
         },
         { immediate: true },
       );

+ 7 - 0
src/design/index.less

@@ -185,3 +185,10 @@ span {
 .ant-back-top {
   right: 30px !important;
 }
+
+.table-avatar {
+  display: inline-flex;
+  width: 28px;
+  height: 28px;
+  border-radius: 50%;
+}

+ 1 - 1
src/design/theme.less

@@ -57,7 +57,7 @@ html[data-theme='light'] {
 }
 .ant-table-tbody > tr > td {
   border-bottom: 1px solid #ebedf0 !important;
-  border-right: 1px solid #ebedf0 !important;
+  border-right: 0 solid #ebedf0 !important;
 }
 .fan-basic-table-row__striped td {
   background-color: #f7f7fa !important;

+ 0 - 2
src/utils/file/base64Conver.ts

@@ -6,7 +6,6 @@ export function dataURLtoBlob(base64Buf: string): Blob {
   const arr = base64Buf.split(',');
   if (arr.length == 1) {
     const typeItem = arr[0];
-    debugger;
     // const mime = typeItem.match(/:(.*?);/)![1];
     const bstr = window.atob(typeItem);
     let n = bstr.length;
@@ -19,7 +18,6 @@ export function dataURLtoBlob(base64Buf: string): Blob {
     });
   } else {
     const typeItem = arr[0];
-    debugger;
     const mime = typeItem.match(/:(.*?);/)![1];
     const bstr = window.atob(arr[1]);
     let n = bstr.length;

+ 0 - 1
src/utils/http/axios/Axios.ts

@@ -143,7 +143,6 @@ export class VAxios {
         formData.append(key, params.data![key]);
       });
     }
-
     return this.axiosInstance.request<T>({
       ...config,
       method: 'POST',

+ 5 - 1
src/utils/http/axios/index.ts

@@ -77,11 +77,15 @@ const transform: AxiosTransform = {
       const expired = parseInt(userStore.getExpiresIn) - now <= 0;
       console.log('🚀 ~ file: index.ts:78 ~ transformResponseHook: ~ expired:', expired);
       if (expired) {
-        const res = await userStore.refreshToken();
+        const res = await userStore.refreshToken;
         if (res?.access_token) {
+          console.log('🚀 ~ file: index.ts:82 ~ transformResponseHook: ~ expired res:', res);
           const retryRequest = new AxiosRetry();
           // @ts-ignore
           retryRequest.retryRefresh(res, options);
+        } else {
+          userStore.setToken(undefined);
+          userStore.logout(true);
         }
       } else {
         userStore.setToken(undefined);

+ 15 - 0
src/utils/index.ts

@@ -187,6 +187,21 @@ export function formatDictFontColor(options: Array<any>, value: string) {
   return matchItem && matchItem['fontColor'] ? matchItem['fontColor'] : '';
 }
 
+// 获取字段前缀颜色
+export function formatDictPreColor(options: Array<any>, value: string) {
+  if (!value) {
+    if (value != '0') {
+      return '';
+    }
+  }
+  if (!options) {
+    return '';
+  }
+  let matchItem = '';
+  matchItem = options.find(item => item['value'] == value);
+  return matchItem && matchItem['prefixColor'] ? matchItem['prefixColor'] : '';
+}
+
 // 获取字典数组
 export function formatDictTags(options: Array<any>, value: Array<any>) {
   if (!value.length) {

+ 44 - 44
src/views/README.md

@@ -1,49 +1,49 @@
-
 base 基本页面
 
-monitor 系统监控
-  logLogin    登录日志
-  logOper     操作日志
-  job         定时任务
-  server      系统监控
-  front       前端依赖
-  druid       连接监控
-
-tool 基础工具
-  num         编号策略
-  oss        文件管理
-  gen         代码生成
-  bpmn        流程管理
-  print       打印管理
-  butted      设备对接
-
-sys 系统管理
-  dict        字典管理
-  setting     系统配置
-  menu        菜单管理
-  dept        部门管理
-  user        用户管理
-  position    岗位管理
-  role        角色管理
-  portal      门户管理
-  args        参数管理
-  constant    常量管理
-  notice      系统通知
-
-bus 业务组件
-  info
-    hospital  医院设置
-    article   宣教管理
-    supplies  耗材管理
-    bed       床位管理
-    ward      病区管理
-  xxx
-    xxx
-    xxx
-
+- monitor 系统监控
+
+  - logLogin 登录日志
+  - logOper 操作日志
+  - job 定时任务
+  - server 系统监控
+  - front 前端依赖
+  - druid 连接监控
+
+- tool 基础工
+
+  - num 编号策略
+  - oss 文件管理
+  - gen 代码生成
+  - bpmn 流程管理
+  - print 打印管理
+  - butted 设备对接
+
+- sys 系统管理
+
+  - dict 字典管理
+  - setting 系统配置
+  - menu 菜单管理
+  - dept 部门管理
+  - user 用户管理
+  - position 岗位管理
+  - role 角色管理
+  - portal 门户管理
+  - args 参数管理
+  - constant 常量管理
+  - notice 系统通知
+
+- bus 业务组件
+  - info
+    - hospital 医院设置
+    - article 宣教管理
+    - supplies 耗材管理
+    - bed 床位管理
+    - ward 病区管理
+  - xxx
+    - xxx
+    - xxx
+- demo 组件样例
 
 # api
 
-api 统一前缀: 
-  sys  系统接口
-  biz  业务接口
+api 统一前缀: sys 系统接口 biz 业务接口

+ 3 - 0
src/views/biz/README.md

@@ -21,6 +21,9 @@
     - dialysis 透析设备
     - bio 生化设备
     - dics 业务字典
+  - inventory 库存管理
+    - pharmaceuticals 药品管理
+    - supplies 耗材管理
   - mission 宣教管理
     - article 宣教库
     - recycling 回收站

+ 1 - 1
src/views/biz/archives/detail/data.ts

@@ -5,7 +5,7 @@ export const BasicTab = [
   { key: 'patientBasic2', value: 3, title: '透析记录' },
   { key: 'patientBasic3', value: 4, title: '医嘱记录' },
   { key: 'patrolward', value: 5, title: '查房记录' },
-  { key: 'patientBasic5', value: 6, title: '诊断记录' },
+  { key: 'DiagnosisHistory', value: 6, title: '诊断记录' },
   { key: 'patientBasic6', value: 7, title: '化验项' },
   { key: 'medicalDocuments', value: 8, title: '医疗文书' },
   { key: 'patientReturn', value: 9, title: '转归' },

+ 4 - 2
src/views/biz/archives/diagnosisHistory/FormModal.vue

@@ -281,8 +281,10 @@
     // 首次透析方式
     if (type.value == DiagnosisEnum.firstDialysis_field) {
       resData.firstDialysisType = data.content[0]?.contentType;
-      resData.firstDialysisDate = dayjs().format('YYYY-MM-DD');
-      resData.recordTime = dayjs(data.content[0]?.recordTime).format('YYYY-MM-DD');
+      resData.firstDialysisDate = dayjs(data.content[0]?.recordTime || Date.now()).format(
+        'YYYY-MM-DD',
+      );
+      resData.recordTime = dayjs(data.updateTime || Date.now()).format('YYYY-MM-DD');
     }
     await setFieldsValue({
       ...resData,

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

@@ -356,7 +356,7 @@
       iconType: 'warning',
       onOk: async () => {
         await archivesDiagnosisHistoryMultiRemoveByIds([id]);
-        createMessage.success('删除成功');
+        createMessage.success('删除成功');
         await getData();
       },
     });

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

@@ -29,7 +29,7 @@
   const emit = defineEmits(['success', 'register']);
 
   const getTitle = computed(() => (!unref(isUpdate) ? '新增文书' : '编辑文书'));
-  const width = '35%';
+  const width = '600px';
   const isUpdate = ref(false);
   const rowId = ref();
   const patientBasicId = ref();

+ 2 - 1
src/views/biz/archives/medicalDocuments/index.vue

@@ -160,7 +160,7 @@
       iconType: 'warning',
       onOk: async () => {
         await archivesMedicalDocumentsRemove([data.id]);
-        createMessage.success('删除成功');
+        createMessage.success('删除成功');
         await getData();
         if (listData.value.length) {
           await callItemClick(listData.value[0]);
@@ -191,6 +191,7 @@
     top: -16px;
 
     &-nav {
+      min-width: 320px;
       border-right: 1px solid #f6f8fa;
     }
 

+ 4 - 0
src/views/biz/archives/patientReturn/index.vue

@@ -9,6 +9,10 @@
     </div>
     <BasicTable @register="registerTable">
       <template #bodyCell="{ column, record }">
+        <template v-if="column.key === 'updatorName'">
+          <img :src="record.updateAvatar" class="table-avatar" />
+          {{ record.updatorName }}
+        </template>
         <template v-if="column.key === 'type'">
           {{ formatDictValue(bizDictOptions.type, record.type) }}
         </template>

+ 4 - 0
src/views/biz/archives/patrolward/index.vue

@@ -10,6 +10,10 @@
     </div>
     <BasicTable @register="registerTable">
       <template #bodyCell="{ column, record }">
+        <template v-if="column.key === 'updatorName'">
+          <img :src="record.updateAvatar" class="table-avatar" />
+          {{ record.updatorName }}
+        </template>
         <template v-if="column.key === 'action'">
           <TableAction
             :actions="[

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

@@ -185,9 +185,9 @@
     if (res) {
       tableData.value.splice(index, 1);
       setTableData(tableData.value);
-      createMessage.success('删除成功');
+      createMessage.success('删除成功!');
     } else {
-      createMessage.error('删除失败');
+      createMessage.error('删除失败!');
     }
   }
   async function handleSave(record: EditRecordRow) {

+ 90 - 0
src/views/biz/engineer/bed/FormModal.vue

@@ -0,0 +1,90 @@
+<template>
+  <BasicModal
+    v-bind="$attrs"
+    destroyOnClose
+    @register="registerModal"
+    :title="getTitle"
+    :width="width"
+    @ok="handleSubmit"
+    :showFooter="true"
+  >
+    <div class="!pl-8 !pt-2">
+      <BasicForm @register="registerForm" layout="vertical" />
+    </div>
+  </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 { engineerBedAdd, engineerBedEdit, engineerBedDetail } from '@/api/biz/engineer/bedApi';
+
+  const emit = defineEmits(['success', 'register']);
+
+  const getTitle = computed(() => (!unref(isUpdate) ? '新增床位' : '编辑床位'));
+  const width = '600px';
+  const isUpdate = ref(false);
+  const rowId = ref();
+
+  const { createMessage } = useMessage();
+  const [registerForm, { setFieldsValue, resetFields, validate }] = useForm({
+    labelWidth: 150,
+    schemas: dataFormSchema,
+    showActionButtonGroup: false,
+    baseColProps: {
+      span: 24,
+    },
+    wrapperCol: {
+      span: 22,
+    },
+  });
+  const [registerModal, { setModalProps, closeModal }] = useModalInner(async data => {
+    await resetFields();
+    setModalProps({ confirmLoading: false });
+    isUpdate.value = !!data?.isUpdate;
+
+    if (unref(isUpdate)) {
+      const resData = await engineerBedDetail(data.record.id);
+      rowId.value = resData.id;
+      resData.cardNo = {
+        input: resData.cardNo,
+        dictValue: resData.cardType,
+      };
+      await setFieldsValue({
+        ...resData,
+      });
+    } else {
+      await setFieldsValue({
+        cardNo: {
+          input: '',
+          dictValue: 'pb_card_sfz',
+        },
+      });
+    }
+  });
+
+  // 提交按钮事件
+  async function handleSubmit() {
+    try {
+      const values = await validate();
+      setModalProps({ confirmLoading: true });
+      const cardInfo = values.cardNo;
+      values.cardNo = cardInfo?.input;
+      values.cardType = cardInfo?.dictValue;
+      !unref(isUpdate)
+        ? await engineerBedAdd({ ...values })
+        : await engineerBedEdit({ ...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 });
+    }
+  }
+</script>

+ 194 - 0
src/views/biz/engineer/bed/data.ts

@@ -0,0 +1,194 @@
+import { BasicColumn } from '@/components/Table';
+import { FormSchema } from '/@/components/Form';
+import { listDictModel } from '@/api/common';
+
+export const columnsBed: BasicColumn[] = [
+  {
+    title: '病区',
+    dataIndex: 'wardName',
+    align: 'left',
+    width: 90,
+  },
+  {
+    title: '病区属性',
+    dataIndex: 'wardPropertyName',
+    align: 'left',
+    width: 90,
+  },
+  {
+    title: '床位',
+    dataIndex: 'bedName',
+    align: 'left',
+  },
+  {
+    title: '设备编号',
+    dataIndex: 'deviceId',
+    align: 'left',
+  },
+  {
+    title: '设备信息',
+    dataIndex: 'deviceName',
+    align: 'left',
+  },
+];
+
+export const columnsDevice: BasicColumn[] = [
+  {
+    title: '操作时间',
+    dataIndex: 'createTime',
+    align: 'left',
+  },
+  {
+    title: '操作类型',
+    dataIndex: 'bindDevice',
+    align: 'left',
+  },
+  {
+    title: '设备类型',
+    dataIndex: 'deviceType',
+    align: 'left',
+  },
+  {
+    title: '设备编号',
+    dataIndex: 'deviceId',
+    align: 'left',
+  },
+  {
+    title: '设备备注',
+    dataIndex: 'remark',
+    align: 'left',
+  },
+];
+
+// 表单新增编辑
+export const dataFormSchema: FormSchema[] = [
+  {
+    field: 'PlainTitle',
+    component: 'PlainTitle',
+    defaultValue: '新增床位',
+    colProps: {
+      span: 24,
+    },
+  },
+  {
+    label: '病区属性',
+    field: 'wardPropertyName',
+    required: true,
+    component: 'ApiSelect',
+    componentProps: {
+      api: listDictModel,
+      placeholder: '请选择病区属性',
+      params: {
+        dictCode: 'pb_blood',
+      },
+    },
+  },
+  {
+    label: '病区',
+    field: 'wardId',
+    required: true,
+    component: 'ApiSelect',
+    componentProps: {
+      api: listDictModel,
+      placeholder: '请选择病区',
+      params: {
+        dictCode: 'pb_blood',
+      },
+    },
+  },
+  {
+    label: '床位',
+    field: 'bedName',
+    required: true,
+    component: 'Input',
+    componentProps: {
+      placeholder: '请输入床位',
+      maxLength: 60,
+    },
+  },
+  {
+    field: 'PlainTitle1',
+    component: 'PlainTitle',
+    defaultValue: '绑定设备',
+    colProps: {
+      span: 24,
+    },
+  },
+  {
+    label: '设备编号',
+    field: 'deviceId',
+    required: true,
+    component: 'ApiSelect',
+    componentProps: {
+      api: listDictModel,
+      placeholder: '请选择设备编号',
+    },
+  },
+  {
+    label: '设备信息',
+    field: 'deviceName',
+    component: 'PlainText',
+    colProps: {
+      span: 12,
+    },
+  },
+  {
+    label: '设备类型',
+    field: 'deviceModel',
+    component: 'PlainText',
+    colProps: {
+      span: 12,
+    },
+  },
+  {
+    label: '设备备注',
+    field: 'remark',
+    required: true,
+    component: 'Input',
+    componentProps: {
+      placeholder: '请输入设备备注',
+    },
+  },
+];
+
+// 床位信息搜索
+export const searchFormSchema = {
+  bed: [
+    {
+      name: 'wardId',
+      componentType: 'Select',
+      placeholder: '请选择病区',
+      width: 150,
+      defaultValue: '1',
+      dicts: [{ label: '全部', value: '1' }],
+    },
+    {
+      name: 'bindDevice',
+      componentType: 'Select',
+      placeholder: '请选择',
+      width: 150,
+      defaultValue: null,
+      dicts: [
+        { label: '全部', value: null },
+        { label: '未绑定', value: 0 },
+        { label: '已绑定', value: 1 },
+      ],
+    },
+  ],
+  device: [
+    {
+      name: 'createTime',
+      componentType: 'RangePicker',
+      placeholder: '请输入',
+      format: 'YYYY-MM-DD',
+      valueFormat: 'YYYY-MM-DD',
+    },
+    {
+      name: 'deviceId',
+      componentType: 'Input',
+      placeholder: '请输入',
+      prefix: 'icon-xt-search',
+      width: 200,
+    },
+  ],
+};

+ 75 - 0
src/views/biz/engineer/dialysis/FormDrawerSift.vue

@@ -0,0 +1,75 @@
+<template>
+  <BasicDrawer
+    v-bind="$attrs"
+    destroyOnClose
+    @register="registerDrawer"
+    :title="getTitle"
+    :width="width"
+    @ok="handleSubmit"
+    :showFooter="true"
+    cancelText=""
+    okText="筛选"
+    :showCancelBtn="false"
+  >
+    <BasicForm @register="registerForm" layout="vertical" class="!px-6 !pt-2" />
+    <template #insertFooter>
+      <a-button @click="handleCancel" class="mr-2"> 重置 </a-button>
+    </template>
+  </BasicDrawer>
+</template>
+<script lang="ts" setup>
+  import { ref } from 'vue';
+  import { BasicDrawer, useDrawerInner } from '/@/components/Drawer';
+  import { BasicForm, useForm } from '/@/components/Form';
+  import { siftFormSchema } from './data';
+
+  const emit = defineEmits(['success', 'register']);
+
+  const getTitle = ref(`筛选条件`);
+  const width = '25%';
+  const [registerForm, { setFieldsValue, resetFields, validate, getFieldsValue }] = useForm({
+    schemas: siftFormSchema,
+    showActionButtonGroup: false,
+    baseColProps: {
+      span: 24,
+    },
+  });
+  const [registerDrawer, { setDrawerProps, closeDrawer }] = useDrawerInner(async data => {
+    await resetFields();
+    setDrawerProps({ confirmLoading: false });
+    console.log('🚀 ~ file: FormDrawer.vue:49 ~ data:', data);
+    const resData = {} as any;
+    (data.record.length &&
+      data.record.map(ele => {
+        console.log('🚀 ~ file: FormDrawerSift.vue:46 ~ ele:', ele);
+        resData[ele.field] = ele.value;
+      })) ||
+      [];
+    console.log('🚀 ~ file: FormDrawerSift.vue:44 ~ resData:', resData);
+    await setFieldsValue({ ...resData });
+  });
+
+  // 提交按钮事件
+  async function handleSubmit() {
+    try {
+      const values = await validate();
+      setDrawerProps({ confirmLoading: true });
+      closeDrawer();
+      emit('success', values);
+    } finally {
+      setDrawerProps({ confirmLoading: false });
+    }
+  }
+
+  async function handleCancel() {
+    console.log('关闭', await getFieldsValue());
+    await resetFields();
+  }
+</script>
+<style lang="less" scoped>
+  ::v-deep(.ant-picker) {
+    border-top: 0;
+    border-left: 0;
+    border-right: 0;
+  }
+</style>

+ 94 - 0
src/views/biz/engineer/dialysis/FormModal.vue

@@ -0,0 +1,94 @@
+<template>
+  <BasicModal
+    v-bind="$attrs"
+    destroyOnClose
+    @register="registerModal"
+    :title="getTitle"
+    :width="width"
+    @ok="handleSubmit"
+    :showFooter="true"
+  >
+    <div class="!pl-8 !pt-4">
+      <BasicForm @register="registerForm" layout="vertical" />
+    </div>
+  </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 {
+    engineerDialysisDeviceAdd,
+    engineerDialysisDeviceEdit,
+    engineerDialysisDeviceDetail,
+  } from '@/api/biz/engineer/dialysisDeviceApi';
+
+  const emit = defineEmits(['success', 'register']);
+
+  const getTitle = computed(() => (!unref(isUpdate) ? '新增设备' : '编辑设备'));
+  const width = '800px';
+  const isUpdate = ref(false);
+  const rowId = ref();
+
+  const { createMessage } = useMessage();
+  const [registerForm, { setFieldsValue, resetFields, validate }] = useForm({
+    labelWidth: 150,
+    schemas: dataFormSchema,
+    showActionButtonGroup: false,
+    baseColProps: {
+      span: 12,
+    },
+    wrapperCol: {
+      span: 22,
+    },
+  });
+  const [registerModal, { setModalProps, closeModal }] = useModalInner(async data => {
+    await resetFields();
+    setModalProps({ confirmLoading: false });
+    isUpdate.value = !!data?.isUpdate;
+
+    if (unref(isUpdate)) {
+      const resData = await engineerDialysisDeviceDetail(data.record.id);
+      rowId.value = resData.id;
+      resData.cardNo = {
+        input: resData.cardNo,
+        dictValue: resData.cardType,
+      };
+      await setFieldsValue({
+        ...resData,
+      });
+    } else {
+      await setFieldsValue({
+        cardNo: {
+          input: '',
+          dictValue: 'pb_card_sfz',
+        },
+      });
+    }
+  });
+
+  // 提交按钮事件
+  async function handleSubmit() {
+    try {
+      const values = await validate();
+      setModalProps({ confirmLoading: true });
+      const cardInfo = values.cardNo;
+      values.cardNo = cardInfo?.input;
+      values.cardType = cardInfo?.dictValue;
+      !unref(isUpdate)
+        ? await engineerDialysisDeviceAdd({ ...values })
+        : await engineerDialysisDeviceEdit({ ...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 });
+    }
+  }
+</script>

+ 296 - 0
src/views/biz/engineer/dialysis/data.ts

@@ -0,0 +1,296 @@
+import { BasicColumn } from '@/components/Table';
+import { FormSchema } from '/@/components/Form';
+import { listDictModel } from '@/api/common';
+import dayjs from 'dayjs';
+
+export const BasicTab = [
+  {
+    key: '0',
+    label: '全部',
+    value: 128,
+    hasValue: true,
+    hasBracket: true,
+  },
+  {
+    key: '1',
+    label: '正常使用',
+    value: 12,
+    hasValue: true,
+    prefixColor: '#1BC1B3',
+    hasBracket: true,
+  },
+  {
+    key: '2',
+    label: '备用机',
+    value: 18,
+    hasValue: true,
+    prefixColor: '#2D5AFF',
+    hasBracket: true,
+  },
+  {
+    key: '3',
+    label: '报废机',
+    value: 18,
+    hasValue: true,
+    prefixColor: '#D3D8DD',
+    hasBracket: true,
+  },
+];
+
+export const BasicTabActive = BasicTab[0].key;
+
+// 抽屉搜索条件
+export const siftFormSchema: FormSchema[] = [
+  {
+    label: '设备厂家',
+    field: 'manufacturer',
+    component: 'ApiSelect',
+    componentProps: {
+      api: listDictModel,
+      params: {
+        dictCode: 'pb_sex',
+      },
+    },
+  },
+  {
+    label: '泵类型',
+    field: 'pumpType',
+    component: 'ApiSelect',
+    componentProps: {
+      api: listDictModel,
+      params: {
+        dictCode: 'bm_pump',
+      },
+    },
+  },
+  {
+    label: '传染标识',
+    field: 'infectiousDiseases',
+    component: 'ApiSelect',
+    componentProps: {
+      api: listDictModel,
+      params: {
+        dictCode: 'pb_epidemic',
+      },
+    },
+  },
+];
+
+export const columns: BasicColumn[] = [
+  {
+    title: '设备编号',
+    dataIndex: 'unique',
+    align: 'left',
+  },
+  {
+    title: '设备厂家',
+    dataIndex: 'manufacturer',
+    align: 'left',
+  },
+  {
+    title: '设备型号',
+    dataIndex: 'model',
+    align: 'left',
+  },
+  {
+    title: '序列号',
+    dataIndex: 'serialNumber',
+    align: 'left',
+  },
+  {
+    title: '设备类型',
+    dataIndex: 'deviceType',
+    align: 'left',
+  },
+  {
+    title: '泵类型',
+    dataIndex: 'pumpType',
+    align: 'left',
+  },
+  {
+    title: '传染标识',
+    dataIndex: 'infectiousDiseases',
+    align: 'left',
+  },
+  {
+    title: '使用时间',
+    dataIndex: 'useDate',
+    align: 'left',
+    width: 180,
+    sorter: true,
+  },
+  {
+    title: '生产时间',
+    dataIndex: 'produceDate',
+    align: 'left',
+    width: 180,
+    sorter: true,
+  },
+  {
+    title: '保修期限(年)',
+    dataIndex: 'warrantyPeriod',
+    align: 'left',
+  },
+];
+
+// 表单新增编辑
+export const dataFormSchema: FormSchema[] = [
+  {
+    label: '设备编号',
+    field: 'unique',
+    required: true,
+    component: 'Input',
+    componentProps: {
+      placeholder: '请输入设备编号',
+      maxLength: 60,
+    },
+  },
+  {
+    label: '设备类型',
+    field: 'deviceType',
+    required: true,
+    component: 'ApiSelect',
+    componentProps: {
+      api: listDictModel,
+      params: {
+        dictCode: 'pb_blood',
+      },
+    },
+  },
+  {
+    label: '传染标识',
+    field: 'infectiousDiseases',
+    required: true,
+    component: 'ApiSelect',
+    componentProps: {
+      api: listDictModel,
+      mode: 'multiple',
+      params: {
+        dictCode: 'pb_epidemic',
+      },
+    },
+  },
+  {
+    label: '序列号',
+    field: 'serialNumber',
+    required: true,
+    component: 'Input',
+    componentProps: {
+      placeholder: '请输入序列号',
+      maxLength: 60,
+    },
+  },
+
+  {
+    label: '备注',
+    field: 'remark',
+    required: true,
+    component: 'Input',
+    componentProps: {
+      placeholder: '请输入备注',
+    },
+  },
+  {
+    label: '泵类型',
+    field: 'pumpType',
+    required: true,
+    component: 'ApiRadioGroup',
+    componentProps: {
+      api: listDictModel,
+      params: {
+        dictCode: 'bm_pump',
+      },
+    },
+  },
+  {
+    label: '设备厂家',
+    field: 'manufacturer',
+    required: true,
+    component: 'Input',
+    componentProps: {
+      placeholder: '请输入设备厂家',
+      maxLength: 60,
+    },
+  },
+  {
+    label: '型号',
+    field: 'model',
+    required: true,
+    component: 'Input',
+    componentProps: {
+      placeholder: '请输入型号',
+    },
+  },
+  {
+    label: '产地',
+    field: 'model',
+    required: true,
+    component: 'Input',
+    componentProps: {
+      placeholder: '请输入产地',
+    },
+  },
+  {
+    label: '采购金额(万元)',
+    field: 'price',
+    required: true,
+    component: 'InputNumber',
+    componentProps: {
+      placeholder: '请输入采购金额(万元)',
+    },
+  },
+  {
+    label: '购入日期',
+    field: 'birthday',
+    required: true,
+    component: 'DatePicker',
+    componentProps: {
+      format: 'YYYY-MM-DD',
+      placeholder: '请输入购入日期',
+      getPopupContainer: () => document.body,
+      valueFormat: 'YYYY-MM-DD',
+      disabledDate: current => {
+        return current > dayjs().endOf('day');
+      },
+    },
+  },
+  {
+    label: '使用日期',
+    field: 'birthday',
+    required: true,
+    component: 'DatePicker',
+    componentProps: {
+      format: 'YYYY-MM-DD',
+      placeholder: '请输入使用日期',
+      getPopupContainer: () => document.body,
+      valueFormat: 'YYYY-MM-DD',
+      disabledDate: current => {
+        return current > dayjs().endOf('day');
+      },
+    },
+  },
+  {
+    label: '生产日期',
+    field: 'birthday',
+    required: true,
+    component: 'DatePicker',
+    componentProps: {
+      format: 'YYYY-MM-DD',
+      placeholder: '请输入生产日期',
+      getPopupContainer: () => document.body,
+      valueFormat: 'YYYY-MM-DD',
+      disabledDate: current => {
+        return current > dayjs().endOf('day');
+      },
+    },
+  },
+  {
+    label: '保修期限(年)',
+    field: 'price',
+    required: true,
+    component: 'InputNumber',
+    componentProps: {
+      placeholder: '请输入保修期限(年)',
+    },
+  },
+];

+ 369 - 3
src/views/biz/engineer/dialysis/index.vue

@@ -1,7 +1,373 @@
 <template>
-  <div> 占位符 </div>
+  <div class="m-4">
+    <div>
+      <XTTitle title="透析设备" :right-data="titleData" @click="callTitleClick" />
+      <div class="flex justify-between my-4">
+        <XTTab
+          type="illness"
+          :width="136"
+          :selected="activeKey"
+          :data="tabData"
+          @item-click="callTab"
+        />
+        <XTForm :form-data="formData" @change="callFormChange" @click="callFormClick" />
+      </div>
+      <div class="flex mb-2" v-if="siftData.length">
+        <Sift :data="siftData" @close="callClose" />
+      </div>
+      <BasicTable @register="registerTable">
+        <template #bodyCell="{ column, record }">
+          <template v-if="column.key === 'name'">
+            <span :class="['table-dot', 'table-dot--' + record.status]" />
+            <span>{{ record.name }}</span>
+          </template>
+          <template v-if="column.key === 'birthday'">
+            {{ record.birthday ? dayjs(record.birthday).format('YYYY-MM-DD') : '' }}
+          </template>
+          <template v-if="column.key === 'firstDialysisTime'">
+            {{
+              record.firstDialysisTime ? dayjs(record.firstDialysisTime).format('YYYY-MM-DD') : ''
+            }}
+          </template>
+          <template v-if="column.key === 'gender'">
+            <span
+              :style="{
+                backgroundColor: formatDictColor(bizDictOptions.gender, record.gender),
+                color: formatDictFontColor(bizDictOptions.gender, record.gender),
+                padding: '1px 6px',
+                borderRadius: '2px',
+                marginRight: '4px',
+              }"
+            >
+              {{ formatDictValue(bizDictOptions.gender, record.gender) }}
+            </span>
+          </template>
+          <template v-if="column.key === 'firstDialysisType'">
+            {{ formatDictValue(bizDictOptions.firstDialysisType, record.firstDialysisType) }}
+          </template>
+          <template v-if="column.key === 'type'">
+            {{ formatDictValue(bizDictOptions.type, record.type) }}
+          </template>
+          <template v-if="column.key === 'infectiousDiseases'">
+            <div class="flex">
+              <div
+                v-for="item in record.infectiousDiseases"
+                :key="item"
+                :style="{
+                  backgroundColor: formatDictColor(bizDictOptions.infectiousDiseases, item),
+                  color: formatDictFontColor(bizDictOptions.infectiousDiseases, item),
+                  padding: '1px 6px',
+                  borderRadius: '2px',
+                  marginRight: '4px',
+                }"
+              >
+                <!-- {{ record.infectiousDiseases }} -->
+                {{ formatDictValue(bizDictOptions.infectiousDiseases, item) }}
+              </div>
+            </div>
+          </template>
+          <template v-if="column.key === 'vascularAccess'">
+            {{ formatDictValue(bizDictOptions.vascularAccess, record.vascularAccess) }}
+          </template>
+          <template v-if="column.key === 'patientReturn'">
+            {{ formatDictValue(bizDictOptions.patientReturn, record.patientReturn) }}
+          </template>
+          <template v-if="column.key === 'action'">
+            <TableAction
+              :actions="[
+                {
+                  auth: 'archives:patientBasic:query',
+                  icon: 'icon-xt-medical_default|iconfont',
+                  tooltip: '详情',
+                  label: '详情',
+                  onClick: handleView.bind(null, record),
+                },
+              ]"
+            />
+          </template>
+        </template>
+      </BasicTable>
+    </div>
+    <FormModal @register="registerModal" @success="callSuccess" />
+    <FormDrawerSift @register="registerDrawer" @success="callSift" />
+  </div>
 </template>
 
-<script setup lang="ts"></script>
+<script setup lang="ts">
+  import { XTTitle } from '/@/components/XTTitle/index';
+  import { XTTab } from '/@/components/XTTab/index';
+  import { XTForm } from '/@/components/XTForm/index';
+  import { Sift } from '/@/components/XTList/index';
+  import { BasicTable, useTable, TableAction } from '/@/components/TableCard';
+  import { BasicTab, BasicTabActive, columns, siftFormSchema } from './data';
+  import { ref } from 'vue';
+  import { useRouter } from 'vue-router';
+  import { archivesPatientBasicStats } from '/@/api/biz/archives/patientBasicApi';
+  import { engineerDialysisDeviceQueryPage } from '@/api/biz/engineer/dialysisDeviceApi';
+  import { listDictModelBatch } from '@/api/common';
+  import { formatDictColor, formatDictFontColor, formatDictValue } from '/@/utils';
+  import { onMounted, reactive } from 'vue';
+  import dayjs from 'dayjs';
+  import { useModal } from '/@/components/Modal';
+  import FormModal from './FormModal.vue';
+  // 筛选条件
+  import FormDrawerSift from './FormDrawerSift.vue';
+  import { useDrawer } from '@/components/Drawer';
 
-<style lang="less" scoped></style>
+  const bizDictOptions = reactive<any>({});
+  const bizDictData = ref([
+    // 血管材料
+    { key: 'gender', dictCode: 'pb_sex' },
+    // 转归类型
+    { key: 'patientReturn', dictCode: 'pb_return' },
+    // 传染病
+    { key: 'infectiousDiseases', dictCode: 'pb_epidemic' },
+    // 患者类型
+    { key: 'type', dictCode: 'pb_type' },
+    // 通路类型
+    { key: 'vascularAccess', dictCode: 'va_type' },
+    // 转归原因
+    { key: 'return', dictCode: 'va_return' },
+    // 首次透析方式
+    { key: 'firstDialysisType', dictCode: 'dt' },
+  ]);
+  // 路由跳转
+  const router = useRouter();
+  const activeKey = ref(BasicTabActive);
+  const tabData = ref(BasicTab);
+  const [registerModal, { openModal }] = useModal();
+  const [registerDrawer, { openDrawer }] = useDrawer();
+
+  onMounted(async () => {
+    const res = await listDictModelBatch(bizDictData.value.map(ele => ele.dictCode));
+    for (const i in res) {
+      const filter = bizDictData.value.filter(ele => ele.dictCode == i)[0];
+      bizDictOptions[filter.key] = res[i];
+    }
+    const stats = await archivesPatientBasicStats();
+    console.log('🚀 ~ file: index.vue:104 ~ onMounted ~ stats:', stats);
+    tabData.value = tabData.value.map(ele => {
+      if (ele.key == '0') {
+        ele.value = stats.all;
+      }
+      if (ele.key == '1') {
+        ele.value = stats.newPatient;
+      }
+      if (ele.key == '2') {
+        ele.value = stats.noneFormulate;
+      }
+      if (ele.key == '3') {
+        ele.value = stats.positive;
+      }
+      return ele;
+    });
+    console.log('🚀 ~ file: index.vue:118 ~ onMounted ~ tabData.value:', tabData.value);
+  });
+  const [registerTable, { reload }] = useTable({
+    api: engineerDialysisDeviceQueryPage,
+    exportAuthList: ['sys:log:export'],
+    rowKey: 'id',
+    columns,
+    showIndexColumn: false,
+    bordered: true,
+    actionColumn: {
+      width: 100,
+      title: '操作',
+      dataIndex: 'action',
+    },
+    beforeFetch: handleBeforeFetch,
+    afterFetch: handleAfterFetch,
+  });
+  // 筛选数据
+  const siftData = ref([]);
+  // 标题数据
+  const titleData = [
+    {
+      type: 'import',
+      icon: 'icon-xt-import_default',
+    },
+    {
+      type: 'add',
+      btnIcon: 'icon-xt-add_default',
+      btnText: '新增设备',
+    },
+  ];
+
+  const formData = ref([
+    {
+      name: 'name',
+      componentType: 'Input',
+      placeholder: '请输入设备编号',
+      prefix: 'icon-xt-search',
+      width: 240,
+    },
+    {
+      name: 'filter',
+      componentType: 'IconBtn',
+      count: 0,
+    },
+  ]);
+
+  const formValue = reactive({
+    name: '',
+  }) as any;
+
+  // const tableSort = ref([
+  //   {
+  //     field: 'create_time',
+  //     direction: 'DESC',
+  //   },
+  // ]) as any;
+  // 表格请求之前,对参数进行处理, 添加默认 排序
+  function handleBeforeFetch(params) {
+    // return { ...params, orders: tableSort.value };
+    const sift = {};
+    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,
+      name: formValue.name,
+      ...sift,
+    };
+  }
+
+  function handleAfterFetch(data) {
+    return data;
+  }
+
+  // 详情按钮事件
+  function handleView(record: Recordable) {
+    router.push({
+      path: '/bizArchives/detail',
+      query: {
+        id: record.id,
+        accessId: record.accessId,
+        name: record.name,
+        gender: formatDictValue(bizDictOptions.gender, record.gender),
+        age: record.age,
+      },
+    });
+  }
+
+  // 弹窗回调事件
+  async function callSuccess({ isUpdate, values }) {
+    console.log(isUpdate);
+    console.log(values);
+    await reload();
+  }
+  // 回调
+  async function callTab(data) {
+    activeKey.value = data.value;
+    await reload();
+  }
+
+  function callTitleClick(data) {
+    if (data.type == 'add') {
+      openModal(true, {
+        isUpdate: false,
+        record: data,
+      });
+    }
+  }
+
+  async function callFormChange(data) {
+    formValue.name = data.name ? data.name : '';
+    await reload();
+  }
+
+  async function callFormClick(data) {
+    if (data.name == 'filter') {
+      const record = [];
+      siftData.value.forEach(ele => {
+        const obj = {
+          field: ele.field,
+          value: ele.value,
+        } as any;
+        if (ele.isDict) {
+          obj.value = ele.dict;
+        }
+        record.push(obj);
+      });
+      openDrawer(true, {
+        record,
+      });
+    }
+  }
+  // 筛选条件回调
+  async function callSift(data) {
+    siftData.value = [];
+    for (const i in data) {
+      if (data[i]) {
+        siftFormSchema.forEach(ele => {
+          // console.log('🚀 ~ file: index.vue:280 ~ obj ~ ele:', ele);
+          if (ele.field == i) {
+            siftData.value.push({
+              field: ele.field,
+              label: ele.label,
+              value: ele.component.includes('Api')
+                ? formatDictValue(bizDictOptions.gender, data[i])
+                : data[i],
+              isDict: ele.component.includes('Api'),
+              dict: ele.component.includes('Api') ? data[i] : '',
+            });
+          }
+          formData.value[formData.value.length - 1]['count'] = siftData.value.length;
+        });
+      }
+    }
+    await reload();
+  }
+
+  async function callClose(data) {
+    if (data.type == 'clear') {
+      console.log('清空全部');
+      siftData.value = [];
+    }
+    if (data.type == 'close') {
+      console.log('删除部分条件');
+      siftData.value = siftData.value.filter(ele => {
+        return ele.field != data.item?.field;
+      });
+    }
+    formData.value[formData.value.length - 1]['count'] = siftData.value.length;
+    await reload();
+  }
+</script>
+
+<style lang="less" scoped>
+  .table-dot {
+    display: inline-block;
+    width: 10px;
+    height: 10px;
+    margin-right: 6px;
+    border-radius: 50%;
+
+    &--1 {
+      background-color: #1bc1b3;
+    }
+
+    &--2 {
+      background-color: #d3d8dd;
+    }
+
+    &--3 {
+      background-color: #f7b500;
+    }
+  }
+
+  ::v-deep(.ant-btn-link) {
+    color: rgb(61 65 85 / 100%);
+  }
+</style>

+ 85 - 0
src/views/biz/inventory/pharmaceuticals/data.ts

@@ -0,0 +1,85 @@
+import { BasicColumn, FormSchema } from '/@/components/Table';
+import { listDictModel } from '/@/api/common';
+
+export const columns: BasicColumn[] = [
+  {
+    title: '药品类型',
+    dataIndex: 'supplierCategory',
+  },
+  {
+    title: '药品名称',
+    dataIndex: 'name',
+  },
+  {
+    title: '助记码',
+    dataIndex: 'helpCode',
+  },
+  {
+    title: '规格',
+    dataIndex: 'supplierModel',
+  },
+  {
+    title: '单位',
+    dataIndex: 'unit',
+  },
+  {
+    title: '生产厂商',
+    dataIndex: 'makers',
+  },
+  {
+    title: '药品状态',
+    dataIndex: 'disable',
+  },
+  {
+    title: '使用量',
+    dataIndex: 'usageAmount',
+  },
+];
+// 表单新增编辑
+export const dataFormSchema: FormSchema[] = [
+  {
+    label: '药品类型',
+    field: 'supplierCategory',
+    required: true,
+    component: 'ApiSelect',
+    componentProps: {
+      api: listDictModel,
+      params: {
+        dictCode: 'pht',
+      },
+      placeholder: '请选择药品类型',
+    },
+  },
+  {
+    label: '药品名称',
+    field: 'name',
+    component: 'Input',
+    componentProps: {},
+  },
+  {
+    label: '助记码',
+    field: 'helpCode',
+    component: 'Input',
+  },
+  {
+    label: '规格',
+    field: 'supplierModel',
+    component: 'Input',
+  },
+  {
+    label: '生产商',
+    field: 'makers',
+    component: 'Input',
+    componentProps: {
+      placeholder: '请输入生产商',
+    },
+  },
+  {
+    label: '单位',
+    field: 'unit',
+    component: 'Input',
+    componentProps: {
+      placeholder: '请输入单位',
+    },
+  },
+];

+ 94 - 0
src/views/biz/inventory/pharmaceuticals/formModal.vue

@@ -0,0 +1,94 @@
+<template>
+  <div class="modals">
+    <BasicModal
+      v-bind="$attrs"
+      destroyOnClose
+      @register="registerModal"
+      :title="getTitle"
+      @ok="handleSubmit"
+      :width="880"
+      @cancel="handleCancel"
+    >
+      <div class="!pl-8 !pt-4">
+        <BasicForm @register="registerForm" />
+      </div>
+    </BasicModal>
+  </div>
+</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 {
+    pharmaceuticalsAdd,
+    pharmaceuticalsById,
+    pharmaceuticalsEdit,
+  } from '/@/api/biz/inventory/pharmaceuticalsApi';
+
+  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: 24,
+    },
+    baseColProps: {
+      span: 12,
+    },
+    wrapperCol: {
+      span: 22,
+    },
+  });
+  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 pharmaceuticalsById(data.record.id);
+      console.log('resData::::', resData);
+      await setFieldsValue({
+        ...resData,
+      });
+    }
+  });
+
+  // 提交按钮事件
+  async function handleSubmit() {
+    try {
+      const values = await validate();
+      setModalProps({ confirmLoading: true });
+      !unref(isUpdate)
+        ? await pharmaceuticalsAdd({ ...values })
+        : await pharmaceuticalsEdit({ ...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>
+<style lang="less" scoped>
+  ::v-deep(.ant-modal) {
+    background-color: #000;
+  }
+</style>

+ 306 - 0
src/views/biz/inventory/pharmaceuticals/index.vue

@@ -0,0 +1,306 @@
+<template>
+  <div class="m-4 modals">
+    <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 === 'disable'">
+          <span
+            :class="['table-dot']"
+            :style="{ backgroundColor: formatDictPreColor(responseTypeOptions, record.disable) }"
+          />
+          <span> {{ formatDictValue(responseTypeOptions, record.disable) }}</span>
+        </template>
+        <template v-if="column.key === 'supplierCategory'">
+          <span>
+            {{ formatDictValue(responsesupplierCategoryOptions, record.supplierCategory) }}</span
+          >
+        </template>
+        <template v-if="column.key === 'action'">
+          <TableAction
+            :actions="[
+              {
+                auth: 'biz:drug:edit',
+                icon: 'icon-xt-details_edit_default|iconfont',
+                tooltip: '编辑',
+                onClick: handleEdit.bind(null, record),
+              },
+              {
+                auth: 'biz:drug:updateStatus',
+                icon: 'icon-tingyong-moren|iconfont',
+                tooltip: '停用',
+                ifShow: record.disable === 0,
+                popConfirm: {
+                  title: '是否确认停用',
+                  placement: 'left',
+                  confirm: handleDelete.bind(null, record),
+                },
+              },
+              {
+                auth: 'biz:drug:updateStatus',
+                icon: 'icon-xt-revocation_default|iconfont',
+                tooltip: '启用',
+                ifShow: record.disable === 1,
+                popConfirm: {
+                  title: '是否确认启用',
+                  placement: 'left',
+                  confirm: handleDelete.bind(null, record),
+                },
+              },
+            ]"
+          />
+        </template>
+      </template>
+    </BasicTable>
+    <FormModal @register="registerModal" @success="callSuccess" @cancel="handleCancel" />
+    <ImportModal @register="registerImpModal" />
+  </div>
+</template>
+<script lang="ts" setup>
+  import { onBeforeMount, ref } from 'vue';
+  import { BasicTable, useTable, TableAction } from '/@/components/TableCard';
+  import { useMessage } from '/@/hooks/web/useMessage';
+  import FormModal from './formModal.vue';
+  import { ImportModal } from '/@/components/XTImport/index';
+
+  import { formatDictValue, formatDictPreColor } from '/@/utils';
+  import { columns } from './data';
+
+  import {
+    getpharmaceuticalsList,
+    pharmaceuticalsDel,
+    getStatusNumber,
+    pharmaceuticalsExport,
+    pharmaceuticalsImportBatch,
+  } from '/@/api/biz/inventory/pharmaceuticalsApi';
+  import { listDictModel } from '/@/api/common';
+  import { useModal } from '/@/components/Modal';
+  import { XTTitle } from '/@/components/XTTitle/index';
+  import { XTTab } from '/@/components/XTTab/index';
+  import { XTForm } from '/@/components/XTForm/index';
+
+  // 标题数据
+  const titleData = [
+    {
+      type: 'print',
+      icon: 'icon-xt-print_default',
+    },
+    {
+      type: 'import',
+      auth: ['archives:drug:import'],
+      icon: 'icon-xt-import_default',
+    },
+    {
+      type: 'add',
+      auth: ['biz:drug:add'],
+      btnIcon: 'icon-xt-add_default',
+      btnText: '新增药品',
+    },
+  ];
+  // formdata
+  const formData = [
+    {
+      name: 'shiftDate',
+      componentType: 'RangePicker',
+      format: 'YYYY-MM-DD',
+      valueFormat: 'YYYY-MM-DD',
+      placeholder: '请选择日期',
+      width: 240,
+    },
+    {
+      name: 'searchNames',
+      componentType: 'Input',
+      prefix: 'icon-xt-search',
+      placeholder: '请输入药品名称',
+      width: 240,
+    },
+  ];
+  // tab 切换选中
+  const tabSelected = ref();
+  // 操作名称
+  const searchNames = ref('');
+  const shiftDate = ref([]);
+
+  const typeOptions = ref();
+  const responseTypeOptions = ref();
+  const responsesupplierCategoryOptions = ref();
+  onBeforeMount(async () => {
+    responseTypeOptions.value = await listDictModel({ dictCode: 'sys_disable_type' });
+    responsesupplierCategoryOptions.value = await listDictModel({ dictCode: 'pht' });
+    getTab();
+  });
+
+  const { createMessage } = useMessage();
+  const [registerModal, { openModal }] = useModal();
+  const [registerImpModal, { openModal: openImpModal }] = useModal();
+
+  // const tableSort = ref([
+  //   {
+  //     field: 'create_time',
+  //     direction: 'DESC',
+  //   },
+  // ]) as any;
+
+  const [registerTable, { reload, clearSelectedRowKeys }] = useTable({
+    api: getpharmaceuticalsList,
+    batchDelApi: pharmaceuticalsDel,
+    // batchExportApi: pharmaceuticalsExport,
+    delAuthList: ['biz:consumable:remove'],
+    rowKey: 'id',
+    columns,
+    showIndexColumn: true,
+    bordered: true,
+    actionColumn: {
+      width: 200,
+      title: '操作',
+      dataIndex: 'action',
+    },
+    beforeFetch: handleBeforeFetch,
+  });
+  // 详情按钮事件
+  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('打印中...');
+    } else if (data.type == 'import') {
+      openImpModal(true, {
+        title: '导入药品数据',
+        importUrl: pharmaceuticalsImportBatch,
+        exportUrl: pharmaceuticalsExport,
+      });
+    }
+  }
+
+  // 删除按钮事件
+  async function handleDelete(record: Recordable) {
+    if (record) {
+      await pharmaceuticalsDel(record.id);
+      createMessage.success('停用成功!');
+      clearSelectedRowKeys();
+      await reload();
+      await getTab();
+    }
+  }
+
+  // 表格请求之前,对参数进行处理, 添加默认 排序
+  async function handleBeforeFetch(params) {
+    console.log('searchNames:::', searchNames.value);
+    return {
+      ...params,
+      // orders: tableSort.value,
+      name: searchNames.value == '' ? undefined : searchNames.value,
+      status: tabSelected.value == '' ? undefined : tabSelected.value,
+      time: shiftDate.value.length <= 0 ? undefined : shiftDate.value,
+    };
+  }
+
+  async function getTab() {
+    typeOptions.value = await listDictModel({ dictCode: 'sys_disable_type' });
+    const typeNums = await getStatusNumber(); // 获取各类型数量
+    let 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 = typeList.reverse();
+    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 ? data.shiftDate : '';
+    searchNames.value = data.searchNames ? data.searchNames : '';
+    console.log('callForm:::', searchNames.value);
+    await reload();
+  }
+</script>
+<style lang="less" scoped>
+  .table-dot {
+    display: inline-block;
+    width: 10px;
+    height: 10px;
+    margin-right: 6px;
+    border-radius: 50%;
+  }
+
+  ::v-deep(.ant-btn-link) {
+    color: #3d4155;
+  }
+
+  .colUpdateAvatar {
+    display: flex;
+    justify-content: center;
+    text-align: center;
+    line-height: 28px;
+  }
+
+  .colImg {
+    width: 28px;
+    height: 28px;
+    margin-right: 5px;
+  }
+</style>

+ 85 - 0
src/views/biz/inventory/supplies/data.ts

@@ -0,0 +1,85 @@
+import { BasicColumn, FormSchema } from '/@/components/Table';
+import { listDictModel } from '/@/api/common';
+
+export const columns: BasicColumn[] = [
+  {
+    title: '耗材类型',
+    dataIndex: 'supplierCategory',
+  },
+  {
+    title: '耗材名称',
+    dataIndex: 'name',
+  },
+  {
+    title: '助记码',
+    dataIndex: 'helpCode',
+  },
+  {
+    title: '规格',
+    dataIndex: 'supplierModel',
+  },
+  {
+    title: '单位',
+    dataIndex: 'unit',
+  },
+  {
+    title: '生产厂商',
+    dataIndex: 'makers',
+  },
+  {
+    title: '耗材状态',
+    dataIndex: 'disable',
+  },
+  {
+    title: '使用量',
+    dataIndex: 'usageAmount',
+  },
+];
+// 表单新增编辑
+export const dataFormSchema: FormSchema[] = [
+  {
+    label: '耗材类型',
+    field: 'supplierCategory',
+    required: true,
+    component: 'ApiSelect',
+    componentProps: {
+      api: listDictModel,
+      params: {
+        dictCode: 'pht',
+      },
+      placeholder: '请选择耗材类型',
+    },
+  },
+  {
+    label: '耗材名称',
+    field: 'name',
+    component: 'Input',
+    componentProps: {},
+  },
+  {
+    label: '助记码',
+    field: 'helpCode',
+    component: 'Input',
+  },
+  {
+    label: '规格',
+    field: 'supplierModel',
+    component: 'Input',
+  },
+  {
+    label: '生产商',
+    field: 'makers',
+    component: 'Input',
+    componentProps: {
+      placeholder: '请输入生产商',
+    },
+  },
+  {
+    label: '单位',
+    field: 'unit',
+    component: 'Input',
+    componentProps: {
+      placeholder: '请输入单位',
+    },
+  },
+];

+ 90 - 0
src/views/biz/inventory/supplies/formModal.vue

@@ -0,0 +1,90 @@
+<template>
+  <div class="modals">
+    <BasicModal
+      v-bind="$attrs"
+      destroyOnClose
+      @register="registerModal"
+      :title="getTitle"
+      @ok="handleSubmit"
+      :width="880"
+      @cancel="handleCancel"
+    >
+      <div class="!pl-8 !pt-4">
+        <BasicForm @register="registerForm" />
+      </div>
+    </BasicModal>
+  </div>
+</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 { suppliesAdd, suppliesById, suppliesEdit } from '/@/api/biz/inventory/suppliesApi';
+
+  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: 24,
+    },
+    baseColProps: {
+      span: 12,
+    },
+    wrapperCol: {
+      span: 22,
+    },
+  });
+  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 suppliesById(data.record.id);
+      console.log('resData::::', resData);
+      await setFieldsValue({
+        ...resData,
+      });
+    }
+  });
+
+  // 提交按钮事件
+  async function handleSubmit() {
+    try {
+      const values = await validate();
+      setModalProps({ confirmLoading: true });
+      !unref(isUpdate)
+        ? await suppliesAdd({ ...values })
+        : await suppliesEdit({ ...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>
+<style lang="less" scoped>
+  ::v-deep(.ant-modal) {
+    background-color: #000;
+  }
+</style>

+ 323 - 0
src/views/biz/inventory/supplies/index.vue

@@ -0,0 +1,323 @@
+<template>
+  <div class="m-4 modals">
+    <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 === 'disable'">
+          <span
+            :class="['table-dot']"
+            :style="{ backgroundColor: formatDictPreColor(responseTypeOptions, record.disable) }"
+          />
+          <span> {{ formatDictValue(responseTypeOptions, record.disable) }}</span>
+        </template>
+        <template v-if="column.key === 'supplierCategory'">
+          <span>
+            {{ formatDictValue(responsesupplierCategoryOptions, record.supplierCategory) }}</span
+          >
+        </template>
+        <template v-if="column.key === 'action'">
+          <TableAction
+            :actions="[
+              {
+                auth: 'biz:consumable:edit',
+                icon: 'icon-xt-details_edit_default|iconfont',
+                tooltip: '编辑',
+                onClick: handleEdit.bind(null, record),
+              },
+              {
+                auth: 'biz:consumable:updateStatus',
+                icon: 'icon-tingyong-moren|iconfont',
+                tooltip: '停用',
+                ifShow: record.disable === 0,
+                popConfirm: {
+                  title: '是否确认停用',
+                  placement: 'left',
+                  confirm: handleDelete.bind(null, record),
+                },
+              },
+              {
+                auth: 'biz:consumable:updateStatus',
+                icon: 'icon-xt-revocation_default|iconfont',
+                tooltip: '启用',
+                ifShow: record.disable === 1,
+                popConfirm: {
+                  title: '是否确认启用',
+                  placement: 'left',
+                  confirm: handleDelete.bind(null, record),
+                },
+              },
+            ]"
+          />
+        </template>
+      </template>
+    </BasicTable>
+    <FormModal @register="registerModal" @success="callSuccess" @cancel="handleCancel" />
+    <ImportModal @register="registerImpModal" />
+  </div>
+</template>
+<script lang="ts" setup>
+  import { onBeforeMount, ref } from 'vue';
+  import { BasicTable, useTable, TableAction } from '/@/components/TableCard';
+  import { useMessage } from '/@/hooks/web/useMessage';
+  import FormModal from './formModal.vue';
+  import { ImportModal } from '/@/components/XTImport/index';
+
+  import { formatDictValue, formatDictPreColor } from '/@/utils';
+  import { columns } from './data';
+
+  import {
+    getsuppliesList,
+    suppliesDel,
+    getStatusNumber,
+    suppliesExport,
+    suppliesImportBatch,
+  } from '/@/api/biz/inventory/suppliesApi';
+  import { listDictModel } from '/@/api/common';
+  import { useModal } from '/@/components/Modal';
+  import { XTTitle } from '/@/components/XTTitle/index';
+  import { XTTab } from '/@/components/XTTab/index';
+  import { XTForm } from '/@/components/XTForm/index';
+
+  // 标题数据
+  const titleData = [
+    {
+      type: 'print',
+      icon: 'icon-xt-print_default',
+    },
+    {
+      type: 'import',
+      auth: ['archives:consumable:import'],
+      icon: 'icon-xt-import_default',
+    },
+    {
+      type: 'add',
+      btnIcon: 'icon-xt-add_default',
+      auth: ['biz:consumable:add'],
+      btnText: '新增耗材',
+    },
+  ];
+  // formdata
+  const formData = [
+    {
+      name: 'shiftDate',
+      componentType: 'RangePicker',
+      format: 'YYYY-MM-DD',
+      valueFormat: 'YYYY-MM-DD',
+      placeholder: '请选择日期',
+      width: 240,
+    },
+    {
+      name: 'searchNames',
+      componentType: 'Input',
+      prefix: 'icon-xt-search',
+      placeholder: '请输入耗材名称',
+      width: 240,
+    },
+  ];
+  // tab 切换选中
+  const tabSelected = ref();
+  // 操作名称
+  const searchNames = ref('');
+  const shiftDate = ref([]);
+
+  const typeOptions = ref();
+  const responseTypeOptions = ref();
+  const responsesupplierCategoryOptions = ref();
+  onBeforeMount(async () => {
+    responseTypeOptions.value = await listDictModel({ dictCode: 'sys_disable_type' });
+    responsesupplierCategoryOptions.value = await listDictModel({ dictCode: 'pht' });
+    getTab();
+  });
+
+  const { createMessage } = useMessage();
+  const [registerModal, { openModal }] = useModal();
+  const [registerImpModal, { openModal: openImpModal }] = useModal();
+
+  const tableSort = ref([
+    {
+      field: 'create_time',
+      direction: 'DESC',
+    },
+  ]) as any;
+
+  const [registerTable, { reload, clearSelectedRowKeys }] = useTable({
+    api: getsuppliesList,
+    batchDelApi: suppliesDel,
+    // batchExportApi: suppliesExport,
+    delAuthList: ['biz:consumable:remove'],
+    rowKey: 'id',
+    columns,
+    showIndexColumn: true,
+    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('打印中...');
+    } else if (data.type == 'import') {
+      openImpModal(true, {
+        title: '导入耗材数据',
+        importUrl: suppliesImportBatch,
+        exportUrl: suppliesExport,
+      });
+    }
+  }
+
+  // 删除按钮事件
+  async function handleDelete(record: Recordable) {
+    if (record) {
+      await suppliesDel(record.id);
+      createMessage.success('停用成功!');
+      clearSelectedRowKeys();
+      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) {
+    console.log('searchNames:::', searchNames.value);
+    return {
+      ...params,
+      orders: tableSort.value,
+      name: searchNames.value == '' ? undefined : searchNames.value,
+      status: tabSelected.value == '' ? undefined : tabSelected.value,
+      time: shiftDate.value.length <= 0 ? undefined : shiftDate.value,
+    };
+  }
+
+  async function getTab() {
+    typeOptions.value = await listDictModel({ dictCode: 'sys_disable_type' });
+    const typeNums = await getStatusNumber(); // 获取各类型数量
+    let 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 = typeList.reverse();
+    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 ? data.shiftDate : '';
+    searchNames.value = data.searchNames ? data.searchNames : '';
+    console.log('callForm:::', searchNames.value);
+    await reload();
+  }
+</script>
+<style lang="less" scoped>
+  .table-dot {
+    display: inline-block;
+    width: 10px;
+    height: 10px;
+    margin-right: 6px;
+    border-radius: 50%;
+  }
+
+  ::v-deep(.ant-btn-link) {
+    color: #3d4155;
+  }
+
+  ::v-deep(.ant-input-prefix) {
+    color: #8a99ac;
+  }
+
+  .colUpdateAvatar {
+    display: flex;
+    justify-content: center;
+    text-align: center;
+    line-height: 28px;
+  }
+
+  .colImg {
+    width: 28px;
+    height: 28px;
+    margin-right: 5px;
+  }
+</style>

+ 12 - 12
src/views/biz/management/ward/data.ts

@@ -1,5 +1,5 @@
 import { BasicColumn, FormSchema } from '/@/components/Table';
-import { getAttrList } from '/@/api/biz/management/wardType';
+import { listDictModel } from '@/api/common';
 
 export const wardInfoColumns: BasicColumn[] = [
   {
@@ -7,9 +7,9 @@ export const wardInfoColumns: BasicColumn[] = [
     dataIndex: 'name',
   },
   {
-    title: '病区属性',
-    dataIndex: 'propertiesName',
-    width: 260,
+    title: '传染病',
+    dataIndex: 'infectiousDiseases',
+    width: 560,
   },
   {
     title: '状态',
@@ -53,17 +53,17 @@ export const wardTypeDataFormSchema: FormSchema[] = [
 // 表单信息新增编辑
 export const wardInfoDataFormSchema: FormSchema[] = [
   {
-    label: '病区属性',
-    field: 'propertiesId',
+    label: '传染病',
+    field: 'infectiousDiseases',
     component: 'ApiSelect',
     required: true,
     componentProps: {
-      api: getAttrList,
-      mode: 'single',
-      labelField: 'name',
-      valueField: 'id',
-      resultField: 'data',
-      placeholder: '请输入存储配置',
+      api: listDictModel,
+      mode: 'multiple',
+      params: {
+        dictCode: 'pb_epidemic',
+      },
+      placeholder: '请选择病区属性',
     },
   },
   {

+ 82 - 113
src/views/biz/management/ward/index.vue

@@ -16,7 +16,7 @@
         >
           <p :key="0" v-if="activeKey == 0">
             <Row>
-              <Col :span="6">
+              <!-- <Col :span="6">
                 <Card title="病区属性" style="height: 690px">
                   <template #extra
                     ><Button
@@ -53,8 +53,8 @@
                   </Row>
                 </Card>
               </Col>
-              <Col :span="1" />
-              <Col :span="17">
+              <Col :span="1" /> -->
+              <Col :span="24">
                 <Card title="病区信息" style="height: 690px">
                   <template #extra
                     ><Button shape="circle" v-auth="['bizSys:wardInfo:add']" @click="handleAddInfo"
@@ -70,6 +70,14 @@
                           {{ formatDictValue(disableOptions, record.disable) }}
                         </Tag>
                       </template>
+                      <template v-if="column.key === 'infectiousDiseases'">
+                        <div
+                          style="display: inline-block"
+                          v-for="(item, index) in record.infectiousDiseases"
+                          :key="index"
+                          >{{ formatDictValue(pb_epidemic, item) }}&nbsp;
+                        </div>
+                      </template>
                       <template v-if="column.key === 'action'">
                         <TableAction
                           :actions="[
@@ -80,28 +88,27 @@
                               onClick: handleEdit.bind(null, record),
                             },
                             {
-                              auth: 'bizSys:wardInfo:status',
+                              auth: 'bizSys:wardInfo:remove',
                               icon: 'icon-xt-details_delete_default|iconfont',
-                              tooltip: '停用',
-                              ifShow: record.disable == 0,
+                              tooltip: '删除',
                               popConfirm: {
-                                title: '是否确认停用',
+                                title: '是否确认删除',
                                 placement: 'left',
-                                confirm: handleChangeState.bind(null, record),
-                              },
-                            },
-                            {
-                              auth: 'bizSys:wardInfo:status',
-                              icon: 'icon-plus|iconfont',
-                              tooltip: '启用',
-                              label: '',
-                              ifShow: record.disable == 1,
-                              popConfirm: {
-                                title: '是否确认启用',
-                                placement: 'left',
-                                confirm: handleChangeState.bind(null, record),
+                                confirm: handleDelWard.bind(null, record),
                               },
                             },
+                            // {
+                            //   auth: 'bizSys:wardInfo:status',
+                            //   icon: 'icon-plus|iconfont',
+                            //   tooltip: '启用',
+                            //   label: '',
+                            //   ifShow: record.disable == 1,
+                            //   popConfirm: {
+                            //     title: '是否确认启用',
+                            //     placement: 'left',
+                            //     confirm: handleChangeState.bind(null, record),
+                            //   },
+                            // },
                           ]"
                         />
                       </template>
@@ -133,7 +140,7 @@
                 <Card title="班次配置" style="height: 690px">
                   <template #extra
                     ><Button
-                      v-auth="['bizSys:wardInfo:add']"
+                      v-auth="['bizSys:sailings:add']"
                       @click="handleAddSailing"
                       shape="circle"
                       ><Icon icon="icon-xt-details_edit_default|iconfont" :size="14" /></Button
@@ -145,45 +152,6 @@
                           {{ formatDictValue(disableOptions, record.disable) }}
                         </Tag>
                       </template>
-                      <template v-if="column.key === 'action'">
-                        <TableAction
-                          :actions="[
-                            {
-                              auth: 'bizSys:wardInfo:edit',
-                              icon: 'icon-xt-details_edit_default|iconfont',
-                              tooltip: '编辑',
-                              label: '',
-                              onClick: handleEdit.bind(null, record),
-                            },
-                            {
-                              auth: 'bizSys:wardInfo:status',
-                              icon: 'icon-minus-square|iconfont',
-                              tooltip: '停用',
-                              label: '',
-                              color: 'error',
-                              ifShow: record.disable == 0,
-                              popConfirm: {
-                                title: '是否确认停用',
-                                placement: 'left',
-                                confirm: handleChangeState.bind(null, record),
-                              },
-                            },
-                            {
-                              auth: 'bizSys:wardInfo:status',
-                              icon: 'icon-plus|iconfont',
-                              tooltip: '启用',
-                              label: '',
-                              ifShow: record.disable == 1,
-                              color: 'error',
-                              popConfirm: {
-                                title: '是否确认启用',
-                                placement: 'left',
-                                confirm: handleChangeState.bind(null, record),
-                              },
-                            },
-                          ]"
-                        />
-                      </template>
                     </template>
                   </BasicTable>
                 </Card>
@@ -193,7 +161,7 @@
         </Card>
       </PageWrapper>
     </div>
-    <WardTypeFormModal @register="registerWardTypeModal" @success="handleWardTypeSuccess" />
+    <!-- <WardTypeFormModal @register="registerWardTypeModal" @success="handleWardTypeSuccess" /> -->
     <WardInfoFormModal @register="registerWardInfoModal" @success="handleWardInfoSuccess" />
     <WorkDayFormModal @register="registerWorkDayModal" @success="handleWorkDaySuccess" />
     <SailingEdit @register="registerSailingEditModal" @success="handleSailingSuccess" />
@@ -208,30 +176,32 @@
   import Icon from '/@/components/Icon/src/Icon.vue';
   import { BasicTable, useTable, TableAction } from '/@/components/Table';
   import { wardInfoColumns, sailingsColumns } from './data';
-  import WardTypeFormModal from './wardTypeFormModal.vue';
+  // import WardTypeFormModal from './wardTypeFormModal.vue';
   import WardInfoFormModal from './wardInfoFormModal.vue';
   import WorkDayFormModal from './workDayFormModal.vue';
   import SailingEdit from './sailingsEditFormModal.vue';
-  import { getWardInfo, changeStatus } from '/@/api/biz/management/wardInfo';
-  import { getAttrList, deleteWardType } from '/@/api/biz/management/wardType';
+  import { getWardInfo, delWard } from '/@/api/biz/management/wardInfo';
+  //   import { getAttrList } from '/@/api/biz/management/wardType'; // , deleteWardType
   import { getWorkingDay, getSailings } from '/@/api/biz/management/working';
   import { useModal } from '/@/components/Modal';
   import { listDictModel } from '/@/api/common';
   import { useMessage } from '/@/hooks/web/useMessage';
   import { formatDictColor, formatDictFontColor, formatDictValue } from '/@/utils';
-  const { createConfirm, createMessage } = useMessage();
+  const { createMessage } = useMessage(); // createConfirm,
   const tabList = ref([
     { key: 0, tab: '病区管理', type: 'WARD' },
     { key: 1, tab: '工作日班次', type: 'WORK' },
   ]);
   const activeKey = ref(0);
-  const attributePositiveList = ref([]); // 阳性列表
-  const attributeFeminineList = ref([]); // 阴性列表
+  // const attributePositiveList = ref([]); // 阳性列表
+  // const attributeFeminineList = ref([]); // 阴性列表
   const workDays = ref([]);
   const disableOptions = ref();
+  const pb_epidemic = ref();
   onBeforeMount(async () => {
     disableOptions.value = await listDictModel({ dictCode: 'sys_disable_type' });
-    await getWardType();
+    pb_epidemic.value = await listDictModel({ dictCode: 'pb_epidemic' });
+    // await getWardType();
     await getWordDay();
   });
   const [registerTable, { reload }] = useTable({
@@ -273,7 +243,7 @@
     useSearchForm: false,
   });
 
-  const [registerWardTypeModal, { openModal: openWardTypeModal }] = useModal();
+  // const [registerWardTypeModal, { openModal: openWardTypeModal }] = useModal();
   const [registerWardInfoModal, { openModal: openWardInfoModal }] = useModal();
   const [registerWorkDayModal, { openModal: openWorkDayModal }] = useModal();
   const [registerSailingEditModal, { openModal: openSailingModal }] = useModal();
@@ -291,28 +261,28 @@
     return { ...params, propertiesId: selectType.value, orders: tableSort.value };
   }
   // 打开新增属性方法
-  function handleAddAttr() {
-    openWardTypeModal(true, {
-      isUpdate: false,
-    });
-  }
+  // function handleAddAttr() {
+  //   openWardTypeModal(true, {
+  //     isUpdate: false,
+  //   });
+  // }
   // 打开删除属性方法
-  async function handleDeleteAttr(id) {
-    createConfirm({
-      content: '你确定要删除?',
-      iconType: 'warning',
-      onOk: async () => {
-        await deleteWardType([id]);
-        createMessage.success('属性删除成功!');
-        await getWardType();
-      },
-      onCancel: async () => {
-        attributePositiveList.value = [];
-        attributeFeminineList.value = [];
-        await getWardType();
-      },
-    });
-  }
+  // async function handleDeleteAttr(id) {
+  //   createConfirm({
+  //     content: '你确定要删除?',
+  //     iconType: 'warning',
+  //     onOk: async () => {
+  //       await deleteWardType([id]);
+  //       createMessage.success('属性删除成功!');
+  //       await getWardType();
+  //     },
+  //     onCancel: async () => {
+  //       attributePositiveList.value = [];
+  //       attributeFeminineList.value = [];
+  //       await getWardType();
+  //     },
+  //   });
+  // }
   //打开新增病区信息方法
   function handleAddInfo() {
     openWardInfoModal(true, {
@@ -336,40 +306,39 @@
     });
   }
   // 停用启用病区方法
-  async function handleChangeState(record) {
-    const notes = record.disable == 0 ? '停用' : '启用';
-    await changeStatus(record.id);
-    createMessage.success('病区' + notes + '成功!');
+  async function handleDelWard(record) {
+    await delWard([record.id]);
+    createMessage.success('病区删除成功!');
     reload();
   }
 
   // 保存成功回调事件
-  async function handleWardTypeSuccess() {
-    await getWardType();
-  }
+  // async function handleWardTypeSuccess() {
+  //   await getWardType();
+  // }
 
   function handleWardInfoSuccess() {
     reload();
     SailingsReload();
   }
 
-  async function getWardType() {
-    const attrList = await getAttrList();
-    attributePositiveList.value = []; // 阳性列表
-    attributeFeminineList.value = [];
-    attrList.forEach(item => {
-      if (item.positive) {
-        attributePositiveList.value.push({ label: item.name, key: item.id });
-      } else {
-        attributeFeminineList.value.push({ label: item.name, key: item.id });
-      }
-    });
-  }
+  // async function getWardType() {
+  //   const attrList = await getAttrList();
+  //   attributePositiveList.value = []; // 阳性列表
+  //   attributeFeminineList.value = [];
+  //   attrList.forEach(item => {
+  //     if (item.positive) {
+  //       attributePositiveList.value.push({ label: item.name, key: item.id });
+  //     } else {
+  //       attributeFeminineList.value.push({ label: item.name, key: item.id });
+  //     }
+  //   });
+  // }
   // 通过病区属性搜索病区信息
-  function handleSelectWard(e) {
-    selectType.value = e.key;
-    reload();
-  }
+  // function handleSelectWard(e) {
+  //   selectType.value = e.key;
+  //   reload();
+  // }
 
   // 获取工作日
   async function getWordDay() {

+ 0 - 0
src/views/demo/card/data.ts


+ 307 - 0
src/views/demo/card/index.vue

@@ -0,0 +1,307 @@
+<template>
+  <div class="p-2 m-4 round-md">
+    <DescCard
+      id="1"
+      icon="icon-xt-add_default"
+      title="透析测量"
+      type="touxi"
+      :data="descData"
+      :right="descRight"
+    />
+    <div class="my-2">
+      <SimpleCard id="1" title="透析测量" type="touxi">
+        <template #headRight>
+          <div> <XTForm :form-data="formData" /> </div>
+        </template>
+      </SimpleCard>
+    </div>
+    <div class="my-4">
+      <ChartsCard
+        title="透前血压趋势"
+        :has-safe="true"
+        :colors="chartData.colors"
+        :safe-range="chartData.safeRange"
+      />
+    </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" />
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+  import { XTCard, SimpleCard } from '@/components/XTCard/index';
+  import DescCard from '@/components/XTCard/src/DescCard.vue';
+  import ChartsCard from '/@/components/XTCard/src/ChartsCard.vue';
+
+  import { ColorEnum } from '@/enums/colorEnum';
+  import { XTForm } from '/@/components/XTForm/index';
+
+  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,
+  };
+  // formdata
+  const formData = [
+    {
+      name: 'text',
+      componentType: 'Select',
+      placeholder: '请选择',
+      width: 150,
+      defaultValue: '1',
+      dicts: [
+        { label: '全部', value: '1' },
+        { label: '未称量', value: '2', prefixColor: '#1BC1B3' },
+        { label: '待确认', value: '3', prefixColor: '#854AFF' },
+        { label: '可打印', value: '4', prefixColor: '#1BC1B3' },
+        { label: '完成', value: '5', prefixColor: '#854AFF' },
+      ],
+    },
+    {
+      name: 'text22',
+      componentType: 'Select',
+      placeholder: '请选择',
+      width: 150,
+      defaultValue: '1',
+      dicts: [
+        { label: '第一班', value: '1' },
+        { label: '第二班', value: '2' },
+        { label: '第三班', value: '3' },
+      ],
+    },
+    {
+      name: 'text1',
+      componentType: 'Input',
+      placeholder: '请输入',
+      prefix: 'icon-xt-search',
+      width: 200,
+    },
+    {
+      name: 'text233',
+      componentType: 'RangePicker',
+      placeholder: '请输入',
+      format: 'YYYY-MM-DD',
+      valueFormat: 'YYYY-MM-DD',
+    },
+  ];
+  // 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' },
+          ],
+        },
+      ],
+    },
+  ];
+
+  const chartData = {
+    colors: [
+      {
+        color: 'rgba(0, 117, 255, 1)',
+        label: '化验值',
+        dot: 'rgba(0, 117, 255, 1)',
+      },
+    ],
+    safeRange: [
+      {
+        // name: '60分到80分',
+        yAxis: 0,
+      },
+      {
+        yAxis: 20,
+      },
+    ],
+  };
+
+  function cellCard(data) {
+    console.log('🚀 ~ file: index.vue:106 ~ cellCard ~ data:', data);
+  }
+</script>
+
+<style lang="less" scoped>
+  // ::v-deep(.ant-input) {
+  //   border-color: #fff;
+  //   border-radius: 10px;
+  // }
+
+  // ::v-deep(.ant-input-affix-wrapper) {
+  //   border-color: #fff;
+  //   border-radius: 10px;
+  // }
+
+  // ::v-deep(.ant-picker) {
+  //   border-color: #fff;
+  //   border-radius: 10px;
+  // }
+
+  ::v-deep(.ant-form-item-control-input) {
+    border-color: #fff;
+    background-color: transparent;
+  }
+
+  ::v-deep(.ant-select:not(.ant-select-customize-input) .ant-select-selector) {
+    border-color: #fff;
+    border-radius: 18px;
+  }
+
+  ::v-deep(.ant-input) {
+    border-color: #fff;
+    border-radius: 18px;
+  }
+
+  ::v-deep(.ant-input-affix-wrapper) {
+    border-color: #fff;
+    border-radius: 18px;
+  }
+
+  ::v-deep(.ant-picker) {
+    border-color: #fff;
+    border-radius: 18px;
+  }
+
+  ::v-deep(.ant-select-selector) {
+    border-color: #fff;
+    border-radius: 18px;
+  }
+</style>

+ 258 - 0
src/views/demo/form/data.ts

@@ -0,0 +1,258 @@
+import { BasicColumn } from '/@/components/Table';
+import { FormSchema } from '/@/components/Form';
+import { radioBoolean } from '/@/utils/filters';
+import { listDictModel } from '/@/api/common';
+
+export const dataFormSchema: FormSchema[] = [
+  {
+    field: 'PlainTitle',
+    component: 'PlainTitle',
+    defaultValue: '检测时间',
+    colProps: {
+      span: 24,
+    },
+  },
+  {
+    field: 'PlainText1111',
+    component: 'RadioDescGroup',
+    label: '1、干体重在过去3~6个月总的变化',
+    componentProps: {
+      options: [
+        { label: '干体重没有减少或体重丢失 <0.5kg (0分)', value: 0 },
+        { label: '体重丢失 ≥0.5kg,但 <1kg (1分)', value: 1 },
+      ],
+    },
+    colProps: {
+      span: 24,
+    },
+    defaultValue: 1,
+  },
+  {
+    field: 'configName',
+    label: '参数名称',
+    labelColor: '#818694',
+    component: 'Input',
+    componentProps: {
+      placeholder: '请输入参数名称',
+      extra: '123123',
+    },
+  },
+  {
+    field: 'configName1',
+    label: '参数名称',
+    labelColor: '#818694',
+    component: 'Input',
+    componentProps: {
+      placeholder: '请输入参数名称',
+      extra: '123123',
+    },
+  },
+  {
+    field: 'configName22',
+    label: '参数名称',
+    labelColor: '#818694',
+    component: 'Input',
+    componentProps: {
+      placeholder: '请输入参数名称',
+      extra: '123123',
+    },
+  },
+  {
+    field: 'configName333',
+    label: '有无异常',
+    component: 'ApiComplex',
+    componentProps: ({ formModel }) => {
+      return {
+        placeholder: '请输入异常内容',
+        api: listDictModel,
+        params: {
+          dictCode: 'sys_dict_type',
+        },
+        onChange: e => {
+          console.log('🚀 ~ file: data.ts:81 ~ e:', e);
+          return (formModel['configName333'] = e);
+        },
+      };
+    },
+  },
+  {
+    field: 'configKey',
+    label: '参数键名',
+    labelColor: '#818694',
+    subLabel: '测试',
+    subLabelColor: '#FF5D39',
+    component: 'Input',
+    required: true,
+    componentProps: {
+      placeholder: '请输入参数键名',
+      // style: { width: '200px' },
+    },
+  },
+  {
+    field: 'configValue',
+    label: '参数键值',
+    component: 'InputNumberGroup',
+    componentProps: ({ formModel }) => {
+      return {
+        options: [
+          { min: 0, max: 200, placeholder: '最小值 0', field: 'min' },
+          { min: 0, max: 200, placeholder: '最大值 200', field: 'max' },
+        ],
+        onChange: e => {
+          return (formModel['configValue'] = e);
+        },
+      };
+    },
+  },
+  {
+    field: 'idCard',
+    label: '证件号码',
+    component: 'ApiInputDict',
+    componentProps: ({ formModel }) => {
+      return {
+        placeholder: '请输入异常内容',
+        api: listDictModel,
+        params: {
+          dictCode: 'sys_dict_type',
+          dictSort: true,
+        },
+        onChange: e => {
+          console.log('🚀 ~ file: data.ts:81 ~ e:', e);
+          return (formModel['idCard'] = e);
+        },
+      };
+    },
+  },
+  {
+    field: 'sort',
+    label: '排序',
+    component: 'InputNumber',
+    required: true,
+    defaultValue: '1',
+    componentProps: {
+      placeholder: '请输入排序',
+      min: 1,
+    },
+  },
+  {
+    field: 'sysInner',
+    label: '系统内置',
+    component: 'RadioGroup',
+    componentProps: {
+      options: radioBoolean,
+      disabled: true,
+    },
+    defaultValue: '1',
+  },
+  {
+    field: 'apiCheck',
+    label: '系统内置',
+    component: 'ApiCheckboxGroup',
+    componentProps: {
+      api: listDictModel,
+      params: {
+        dictCode: 'bm_pump',
+      },
+    },
+    // defaultValue: ['pump_single'],
+  },
+  // {
+  //   field: 'disable',
+  //   label: '状态',
+  //   component: 'RadioGroup',
+  //   required: true,
+  //   componentProps: {
+  //     options: radioSwitch,
+  //   },
+  //   defaultValue: '0',
+  // },
+  {
+    label: '备注',
+    field: 'remark',
+    component: 'InputTextArea',
+    componentProps: {
+      placeholder: '请输入备注',
+    },
+  },
+];
+export const columns: BasicColumn[] = [
+  {
+    title: '名称',
+    dataIndex: 'name',
+    width: 100,
+    edit: true,
+    editable: true,
+    editComponent: 'Input',
+  },
+  {
+    title: '类型',
+    dataIndex: 'type',
+    width: 150,
+  },
+  {
+    title: '联系人',
+    dataIndex: 'contractUser',
+    width: 150,
+  },
+  {
+    title: '联系人电话',
+    dataIndex: 'contactMobile',
+    width: 150,
+  },
+  {
+    title: '名称',
+    dataIndex: 'name',
+    width: 100,
+  },
+  {
+    title: '类型',
+    dataIndex: 'type',
+    width: 150,
+  },
+  {
+    title: '联系人',
+    dataIndex: 'contractUser',
+    width: 150,
+  },
+  {
+    title: '联系人电话',
+    dataIndex: 'contactMobile',
+    width: 150,
+  },
+  {
+    title: '管理账号',
+    dataIndex: 'username',
+    width: 150,
+  },
+  {
+    title: '套餐名称',
+    dataIndex: 'packageName',
+    width: 150,
+  },
+  {
+    title: '联系人',
+    dataIndex: 'contractUser',
+    width: 150,
+  },
+  {
+    title: '联系人电话',
+    dataIndex: 'contactMobile',
+    width: 150,
+  },
+  {
+    title: '管理账号',
+    dataIndex: 'username',
+    width: 150,
+  },
+  {
+    title: '套餐名称',
+    dataIndex: 'packageName',
+    width: 150,
+    fixed: 'right',
+  },
+  {
+    title: '状态',
+    dataIndex: 'disable',
+    fixed: 'right',
+  },
+];

+ 211 - 0
src/views/demo/form/index.vue

@@ -0,0 +1,211 @@
+<template>
+  <div class="m-4">
+    <div class="flex justify-between my-4">
+      <XTTab
+        type="illness"
+        :width="120"
+        :selected="tabSelected"
+        :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" />
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+  import { ref } from 'vue';
+  import { XTTab } from '/@/components/XTTab/index';
+  import { XTForm } from '/@/components/XTForm/index';
+  import { BasicForm, useForm } from '/@/components/Form';
+  import { dataFormSchema } from './data';
+  import { onMounted } from 'vue';
+  import { Sift } from '/@/components/XTList/index';
+
+  const tabSelected = ref('0');
+  const dataSource = ref([]);
+  const siftData = [
+    {
+      field: 'gender',
+      label: '性别',
+      value: '男',
+    },
+    {
+      field: 'age',
+      label: '年龄区间',
+      value: '25岁-50岁',
+    },
+  ];
+
+  onMounted(async () => {
+    for (let i = 0; i < 10; i++) {
+      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,
+      };
+      dataSource.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 [registerForm, { setFieldsValue }] = useForm({
+    layout: 'vertical',
+    labelWidth: '100%',
+    baseColProps: {
+      span: 8,
+    },
+    baseRowGutter: [16, 8],
+    schemas: dataFormSchema,
+    showActionButtonGroup: false,
+    actionColOptions: {
+      span: 8,
+    },
+    showResetButton: false,
+    submitButtonOptions: {
+      text: '添加',
+    },
+  });
+
+  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,
+    },
+  ];
+  // formdata
+  const formData = [
+    {
+      name: 'text',
+      label: '全部',
+      componentType: 'Select',
+      placeholder: '请选择',
+      width: 80,
+      defaultValue: '1',
+      dicts: [
+        { label: '全部', value: '1' },
+        { label: '未称量', value: '2', prefixColor: '#1BC1B3' },
+        { label: '待确认', value: '3', prefixColor: '#854AFF' },
+        { label: '可打印', value: '4', prefixColor: '#1BC1B3' },
+        { label: '完成', value: '5', prefixColor: '#854AFF' },
+      ],
+    },
+    {
+      name: 'text22',
+      componentType: 'Select',
+      placeholder: '请选择',
+      width: 120,
+      defaultValue: '1',
+      label: '班次',
+      dicts: [
+        { label: '第一班', value: '1' },
+        { label: '第二班', value: '2' },
+        { label: '第三班', value: '3' },
+      ],
+    },
+    {
+      name: 'text1',
+      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,
+    },
+  ];
+  // 回调
+  function callTab(data) {
+    console.log('🚀 ~ file: index.vue:41 ~ callTab ~ data:', data);
+    tabSelected.value = data.value;
+  }
+</script>
+
+<style lang="less" scoped>
+  ::v-deep(.ant-form-item-label > label) {
+    width: 100% !important;
+  }
+
+  .timeline-outer {
+    display: flex;
+    margin-left: 20px;
+    transition: all 0.3s ease-in-out;
+
+    &_item {
+      padding: 0 30px;
+      height: 40px;
+      line-height: 40px;
+      border-radius: 30px;
+      color: #fff;
+      background: #0075ff;
+      margin-right: 20px;
+      cursor: pointer;
+    }
+  }
+</style>

+ 0 - 0
src/views/demo/list/data.ts


+ 73 - 0
src/views/demo/list/index.vue

@@ -0,0 +1,73 @@
+<template>
+  <div class="m-4">
+    <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>
+</template>
+
+<script setup lang="ts">
+  import { onMounted } from 'vue';
+  import List from '@/components/XTList/src/List.vue';
+
+  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',
+    },
+  ];
+
+  onMounted(async () => {});
+</script>
+
+<style lang="less" scoped>
+  ::v-deep(.ant-form-item-label > label) {
+    width: 100% !important;
+  }
+
+  .timeline-outer {
+    display: flex;
+    margin-left: 20px;
+    transition: all 0.3s ease-in-out;
+
+    &_item {
+      padding: 0 30px;
+      height: 40px;
+      line-height: 40px;
+      border-radius: 30px;
+      color: #fff;
+      background: #0075ff;
+      margin-right: 20px;
+      cursor: pointer;
+    }
+  }
+</style>

+ 0 - 0
src/views/demo/tab/data.ts


+ 269 - 0
src/views/demo/tab/index.vue

@@ -0,0 +1,269 @@
+<template>
+  <div class="m-4">
+    <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" />
+    <div class="flex justify-between my-4">
+      <XTTab
+        type="illness"
+        :width="120"
+        :selected="tabSelected"
+        :data="tabData"
+        @item-click="callTab"
+      />
+      <XTForm :form-data="formData" />
+    </div>
+    <div class="flex">
+      <Sift :data="siftData" />
+    </div>
+    <div class="custome-tab">
+      <a-tabs v-model:activeKey="activeKey">
+        <a-tab-pane key="1" tab="Tab 1">Content of Tab Pane 1</a-tab-pane>
+        <a-tab-pane key="2" tab="Tab 2" force-render>Content of Tab Pane 2</a-tab-pane>
+        <a-tab-pane key="3" tab="Tab 3">Content of Tab Pane 3</a-tab-pane>
+      </a-tabs>
+    </div>
+    <div class="tab">
+      <div class="tab-item tab-item-first--selected"> 业务报价 </div>
+      <div class="tab-item"> 业务报价 </div>
+      <div class="tab-item"> 其他测试 </div>
+      <div class="tab-item"> 其他测试 </div>
+      <div class="tab-item tab-item--selected"> 其他测试 </div>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+  import { ref } from 'vue';
+  import { XTTab } from '/@/components/XTTab/index';
+  import { XTForm } from '/@/components/XTForm/index';
+  import { onMounted } from 'vue';
+  import { Sift } from '/@/components/XTList/index';
+
+  const tabSelected = ref('0');
+  const dataSource = ref([]);
+  const activeKey = ref('1');
+  const siftData = [
+    {
+      field: 'gender',
+      label: '性别',
+      value: '男',
+    },
+    {
+      field: 'age',
+      label: '年龄区间',
+      value: '25岁-50岁',
+    },
+  ];
+  onMounted(async () => {
+    for (let i = 0; i < 10; i++) {
+      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,
+      };
+      dataSource.value.push(obj);
+    }
+  });
+  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',
+      label: '全部',
+      componentType: 'Select',
+      placeholder: '请选择',
+      width: 80,
+      defaultValue: '1',
+      dicts: [
+        { label: '全部', value: '1' },
+        { label: '未称量', value: '2', prefixColor: '#1BC1B3' },
+        { label: '待确认', value: '3', prefixColor: '#854AFF' },
+        { label: '可打印', value: '4', prefixColor: '#1BC1B3' },
+        { label: '完成', value: '5', prefixColor: '#854AFF' },
+      ],
+    },
+    {
+      name: 'text22',
+      componentType: 'Select',
+      placeholder: '请选择',
+      width: 120,
+      defaultValue: '1',
+      label: '班次',
+      dicts: [
+        { label: '第一班', value: '1' },
+        { label: '第二班', value: '2' },
+        { label: '第三班', value: '3' },
+      ],
+    },
+    {
+      name: 'text1',
+      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,
+    },
+  ];
+
+  // 回调
+  function callTab(data) {
+    console.log('🚀 ~ file: index.vue:41 ~ callTab ~ data:', data);
+    tabSelected.value = data.value;
+  }
+</script>
+
+<style lang="less" scoped>
+  ::v-deep(.ant-form-item-label > label) {
+    width: 100% !important;
+  }
+
+  .timeline-outer {
+    display: flex;
+    margin-left: 20px;
+    transition: all 0.3s ease-in-out;
+
+    &_item {
+      padding: 0 30px;
+      height: 40px;
+      line-height: 40px;
+      border-radius: 30px;
+      color: #fff;
+      background: #0075ff;
+      margin-right: 20px;
+      cursor: pointer;
+    }
+  }
+
+  .tab {
+    background-color: #f6f7fb;
+    // height: 48px;
+    display: flex;
+
+    &-item {
+      width: 200px;
+      height: 60px;
+      text-align: center;
+      line-height: 4.8;
+      font-size: 16px;
+      color: #222a3d;
+      cursor: pointer;
+      background-image: url('/@/assets/images/tabm.png');
+      background-size: cover;
+      margin-left: -28px;
+      transition: all 0.32s ease-in-out;
+
+      &:first-child {
+        background-image: url('/@/assets/images/tab.png');
+        margin-left: -14px;
+
+        &:hover {
+          background-image: url('/@/assets/images/tab-selected.png') !important;
+          z-index: 29;
+        }
+      }
+
+      &:last-child {
+        background-image: url('/@/assets/images/tabl.png');
+      }
+
+      &-first--selected {
+        background-image: url('/@/assets/images/tab-selected.png') !important;
+        z-index: 19;
+      }
+
+      &--selected {
+        background-image: url('/@/assets/images/tabm-selected.png') !important;
+        z-index: 19;
+      }
+
+      &:hover {
+        background-image: url('/@/assets/images/tabm-selected.png') !important;
+        z-index: 29;
+      }
+    }
+  }
+</style>

+ 0 - 0
src/views/demo/table/data.ts


+ 7 - 0
src/views/demo/table/index.vue

@@ -0,0 +1,7 @@
+<template>
+  <div> 占位符 </div>
+</template>
+
+<script setup lang="ts"></script>
+
+<style lang="less" scoped></style>

+ 169 - 0
src/views/demo/title/ShortcutModal.vue

@@ -0,0 +1,169 @@
+<template>
+  <BasicModal
+    :title="getTitle"
+    :subTitle="getSubTitle"
+    :canFullscreen="false"
+    @register="registerModal"
+    @ok="handleSubmit"
+    @cancel="handleSubmit"
+    width="580px"
+  >
+    <div class="wrap shortcut">
+      <div
+        :class="[
+          'shortcut-item',
+          item.selected ? 'shortcut-item--seleted' : '',
+          !item.selected && disabled ? 'shortcut-item--disabled' : '',
+        ]"
+        v-for="item in shortcutData"
+        :key="item.name"
+        @click="selectedShortcut(item)"
+      >
+        <i :class="['iconfont shortcut-item_icon', 'icon-xt-fail']" />
+        <div class="shortcut-item_name">{{ item.name }}</div>
+      </div>
+    </div>
+  </BasicModal>
+</template>
+
+<script setup lang="ts">
+  import { BasicModal, useModalInner } from '@/components/Modal';
+  import { computed } from 'vue';
+  import { reactive } from 'vue';
+  import { ref } from 'vue';
+  const emit = defineEmits(['success']);
+  const getTitle = computed(() => '快捷入口');
+  const count = reactive({
+    total: 4,
+    curr: 1,
+  });
+  const getSubTitle = computed(() => '(已选' + count.curr + '/' + count.total + ')');
+  const disabled = ref<boolean>(false);
+  const shortcutData = ref([
+    {
+      icon: 'icon-xt-fail',
+      name: '透前准备',
+      route: '/bizArchives/index',
+      selected: false,
+    },
+    {
+      icon: 'icon-xt-fail',
+      name: '透析室',
+      route: '/bizArchives/index',
+      selected: false,
+    },
+    {
+      icon: 'icon-xt-fail',
+      name: '查房',
+      route: '/bizVisit/check',
+      selected: true,
+    },
+    {
+      icon: 'icon-xt-fail',
+      name: '交班记录',
+      route: '/bizArchives/handover_shifts',
+      selected: false,
+    },
+    {
+      icon: 'icon-xt-fail',
+      name: '透前准备1',
+      route: '/bizArchives/index',
+      selected: false,
+    },
+    {
+      icon: 'icon-xt-fail',
+      name: '透析室1',
+      route: '/bizArchives/index',
+      selected: false,
+    },
+    {
+      icon: 'icon-xt-fail',
+      name: '查房1',
+      route: '/bizVisit/check',
+      selected: true,
+    },
+    {
+      icon: 'icon-xt-fail',
+      name: '交班记录1s',
+      route: '/bizArchives/handover_shifts',
+      selected: false,
+    },
+  ]);
+  const [registerModal, { setModalProps, closeModal }] = useModalInner(async data => {
+    console.log('🚀 ~ file: code.vue:21 ~ data:', data);
+    setModalProps({ confirmLoading: false });
+    disabledShortcut();
+  });
+
+  function selectedShortcut(data) {
+    console.log('🚀 ~ file: ShortcutModal.vue:67 ~ data:', data);
+    if (count.curr == count.total && !data.selected) return;
+    shortcutData.value = shortcutData.value.map(ele => {
+      if (ele.name == data.name) {
+        ele.selected = !ele.selected;
+      }
+      return ele;
+    });
+    disabledShortcut();
+  }
+  function disabledShortcut() {
+    count.curr = shortcutData.value.filter(ele => ele.selected).length;
+    if (count.curr == count.total) {
+      disabled.value = true;
+    } else {
+      disabled.value = false;
+    }
+  }
+  async function handleSubmit() {
+    console.log('提交请求');
+    emit('success');
+    closeModal();
+  }
+</script>
+
+<style lang="less" scoped>
+  .wrap {
+    padding: 8px;
+  }
+
+  .shortcut {
+    display: flex;
+    justify-content: space-between;
+    flex-wrap: wrap;
+
+    &-item {
+      width: 120px;
+      height: 120px;
+      border-radius: 8px;
+      cursor: pointer;
+      margin-bottom: 18px;
+
+      &_icon {
+        display: block;
+        text-align: center;
+        padding: 20px 10px;
+        font-size: 24px;
+        margin-right: 4px;
+      }
+
+      &_name {
+        text-align: center;
+      }
+
+      &--seleted {
+        border: 1px solid #006dff;
+        background: #ecf0f5;
+      }
+
+      &--disabled {
+        color: #d3dadf;
+        background: #f4f5f7;
+        cursor: not-allowed;
+      }
+
+      &:hover {
+        box-shadow: 0 0 18px 0 rgb(0 37 74 / 12%);
+      }
+    }
+  }
+</style>

+ 258 - 0
src/views/demo/title/data.ts

@@ -0,0 +1,258 @@
+import { BasicColumn } from '/@/components/Table';
+import { FormSchema } from '/@/components/Form';
+import { radioBoolean } from '/@/utils/filters';
+import { listDictModel } from '/@/api/common';
+
+export const dataFormSchema: FormSchema[] = [
+  {
+    field: 'PlainTitle',
+    component: 'PlainTitle',
+    defaultValue: '检测时间',
+    colProps: {
+      span: 24,
+    },
+  },
+  {
+    field: 'PlainText1111',
+    component: 'RadioDescGroup',
+    label: '1、干体重在过去3~6个月总的变化',
+    componentProps: {
+      options: [
+        { label: '干体重没有减少或体重丢失 <0.5kg (0分)', value: 0 },
+        { label: '体重丢失 ≥0.5kg,但 <1kg (1分)', value: 1 },
+      ],
+    },
+    colProps: {
+      span: 24,
+    },
+    defaultValue: 1,
+  },
+  {
+    field: 'configName',
+    label: '参数名称',
+    labelColor: '#818694',
+    component: 'Input',
+    componentProps: {
+      placeholder: '请输入参数名称',
+      extra: '123123',
+    },
+  },
+  {
+    field: 'configName1',
+    label: '参数名称',
+    labelColor: '#818694',
+    component: 'Input',
+    componentProps: {
+      placeholder: '请输入参数名称',
+      extra: '123123',
+    },
+  },
+  {
+    field: 'configName22',
+    label: '参数名称',
+    labelColor: '#818694',
+    component: 'Input',
+    componentProps: {
+      placeholder: '请输入参数名称',
+      extra: '123123',
+    },
+  },
+  {
+    field: 'configName333',
+    label: '有无异常',
+    component: 'ApiComplex',
+    componentProps: ({ formModel }) => {
+      return {
+        placeholder: '请输入异常内容',
+        api: listDictModel,
+        params: {
+          dictCode: 'sys_dict_type',
+        },
+        onChange: e => {
+          console.log('🚀 ~ file: data.ts:81 ~ e:', e);
+          return (formModel['configName333'] = e);
+        },
+      };
+    },
+  },
+  {
+    field: 'configKey',
+    label: '参数键名',
+    labelColor: '#818694',
+    subLabel: '测试',
+    subLabelColor: '#FF5D39',
+    component: 'Input',
+    required: true,
+    componentProps: {
+      placeholder: '请输入参数键名',
+      // style: { width: '200px' },
+    },
+  },
+  {
+    field: 'configValue',
+    label: '参数键值',
+    component: 'InputNumberGroup',
+    componentProps: ({ formModel }) => {
+      return {
+        options: [
+          { min: 0, max: 200, placeholder: '最小值 0', field: 'min' },
+          { min: 0, max: 200, placeholder: '最大值 200', field: 'max' },
+        ],
+        onChange: e => {
+          return (formModel['configValue'] = e);
+        },
+      };
+    },
+  },
+  {
+    field: 'idCard',
+    label: '证件号码',
+    component: 'ApiInputDict',
+    componentProps: ({ formModel }) => {
+      return {
+        placeholder: '请输入异常内容',
+        api: listDictModel,
+        params: {
+          dictCode: 'sys_dict_type',
+          dictSort: true,
+        },
+        onChange: e => {
+          console.log('🚀 ~ file: data.ts:81 ~ e:', e);
+          return (formModel['idCard'] = e);
+        },
+      };
+    },
+  },
+  {
+    field: 'sort',
+    label: '排序',
+    component: 'InputNumber',
+    required: true,
+    defaultValue: '1',
+    componentProps: {
+      placeholder: '请输入排序',
+      min: 1,
+    },
+  },
+  {
+    field: 'sysInner',
+    label: '系统内置',
+    component: 'RadioGroup',
+    componentProps: {
+      options: radioBoolean,
+      disabled: true,
+    },
+    defaultValue: '1',
+  },
+  {
+    field: 'apiCheck',
+    label: '系统内置',
+    component: 'ApiCheckboxGroup',
+    componentProps: {
+      api: listDictModel,
+      params: {
+        dictCode: 'bm_pump',
+      },
+    },
+    // defaultValue: ['pump_single'],
+  },
+  // {
+  //   field: 'disable',
+  //   label: '状态',
+  //   component: 'RadioGroup',
+  //   required: true,
+  //   componentProps: {
+  //     options: radioSwitch,
+  //   },
+  //   defaultValue: '0',
+  // },
+  {
+    label: '备注',
+    field: 'remark',
+    component: 'InputTextArea',
+    componentProps: {
+      placeholder: '请输入备注',
+    },
+  },
+];
+export const columns: BasicColumn[] = [
+  {
+    title: '名称',
+    dataIndex: 'name',
+    width: 100,
+    edit: true,
+    editable: true,
+    editComponent: 'Input',
+  },
+  {
+    title: '类型',
+    dataIndex: 'type',
+    width: 150,
+  },
+  {
+    title: '联系人',
+    dataIndex: 'contractUser',
+    width: 150,
+  },
+  {
+    title: '联系人电话',
+    dataIndex: 'contactMobile',
+    width: 150,
+  },
+  {
+    title: '名称',
+    dataIndex: 'name',
+    width: 100,
+  },
+  {
+    title: '类型',
+    dataIndex: 'type',
+    width: 150,
+  },
+  {
+    title: '联系人',
+    dataIndex: 'contractUser',
+    width: 150,
+  },
+  {
+    title: '联系人电话',
+    dataIndex: 'contactMobile',
+    width: 150,
+  },
+  {
+    title: '管理账号',
+    dataIndex: 'username',
+    width: 150,
+  },
+  {
+    title: '套餐名称',
+    dataIndex: 'packageName',
+    width: 150,
+  },
+  {
+    title: '联系人',
+    dataIndex: 'contractUser',
+    width: 150,
+  },
+  {
+    title: '联系人电话',
+    dataIndex: 'contactMobile',
+    width: 150,
+  },
+  {
+    title: '管理账号',
+    dataIndex: 'username',
+    width: 150,
+  },
+  {
+    title: '套餐名称',
+    dataIndex: 'packageName',
+    width: 150,
+    fixed: 'right',
+  },
+  {
+    title: '状态',
+    dataIndex: 'disable',
+    fixed: 'right',
+  },
+];

+ 198 - 0
src/views/demo/title/index.vue

@@ -0,0 +1,198 @@
+<template>
+  <div class="m-4">
+    <div>
+      <BasicTable @register="registerTable">
+        <template #headerTop>
+          <span>headerTop</span>
+        </template>
+        <template #toolbar>
+          <span>toolbar</span>
+        </template>
+      </BasicTable>
+    </div>
+
+    <div class="mx-6 my-2">
+      <TimeLine :data="timeLineData" @hover="callHover">
+        <template #head>
+          <div 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>
+          </div>
+        </template>
+      </TimeLine>
+    </div>
+    <div class="mx-6 my-4">
+      <a-button type="primary" @click="showImport"> 显示导入</a-button>
+    </div>
+    <div class="mx-6 my-4">
+      <a-button type="primary" @click="showShortcut"> 显示快捷方式</a-button>
+    </div>
+    <ImportModal @register="registerModal" />
+    <ShortcutModal @register="registerModalShortcut" />
+  </div>
+</template>
+
+<script setup lang="ts">
+  import { ref } from 'vue';
+  import { BasicTable, useTable } from '/@/components/Table';
+  import { onMounted } from 'vue';
+  import { columns } from './data';
+  import TimeLine from '/@/components/XTTimeLine/src/TimeLine.vue';
+  import { ImportModal } from '/@/components/XTImport/index';
+  // import { TransitionPresets, useTransition } from '@vueuse/core';
+  import { useModal } from '/@/components/Modal';
+  import {
+    archivesPatientBasicExport,
+    archivesPatientBasicImportBatch,
+  } from '/@/api/biz/archives/patientBasicApi';
+  import ShortcutModal from './ShortcutModal.vue';
+
+  const [registerModal, { openModal }] = useModal();
+  const [registerModalShortcut, { openModal: openModalShortcut }] = useModal();
+  const dataSource = ref([]);
+
+  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 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 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,
+      };
+      dataSource.value.push(obj);
+    }
+  });
+  // 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,
+  });
+
+  function handleAdd() {
+    timeOuter.value = false;
+  }
+
+  function showImport() {
+    openModal(true, {
+      title: '导入数据111',
+      importUrl: archivesPatientBasicImportBatch,
+      exportUrl: archivesPatientBasicExport,
+    });
+  }
+  function showShortcut() {
+    console.log('显示快捷方式');
+    openModalShortcut(true, {
+      title: '导入数据111',
+    });
+  }
+  // 回调
+
+  function callHover() {
+    timeOuter.value = true;
+  }
+</script>
+
+<style lang="less" scoped>
+  ::v-deep(.ant-form-item-label > label) {
+    width: 100% !important;
+  }
+
+  .timeline-outer {
+    display: flex;
+    margin-left: 20px;
+    transition: all 0.3s ease-in-out;
+
+    &_item {
+      padding: 0 30px;
+      height: 40px;
+      line-height: 40px;
+      border-radius: 30px;
+      color: #fff;
+      background: #0075ff;
+      margin-right: 20px;
+      cursor: pointer;
+    }
+  }
+</style>

+ 9 - 8
src/views/monitor/loginLog/index.vue

@@ -1,7 +1,8 @@
 <template>
   <div class="m-4">
     <div>
-      <XTTitle title="登录日志" :go-back="true" :right-data="titleData" @click="handleImport" />
+      <XTTitle title="登录日志" :go-back="true" :right-data="titleData" />
+      <!-- @click="handleImport"  -->
       <div class="flex items-center justify-between my-4">
         <XTTab
           type="opLog"
@@ -60,7 +61,7 @@
     </BasicTable>
     <FormDrawer @register="registerDrawer" @success="handleSuccess" />
     <ViewDrawer @register="registerDrawerView" @success="handleSuccess" />
-    <importView @register="registerModal" />
+    <!-- <importView @register="registerModal" /> -->
   </div>
 </template>
 <script lang="ts" setup>
@@ -70,7 +71,7 @@
   import { useMessage } from '/@/hooks/web/useMessage';
   import FormDrawer from './formDrawer.vue';
   import ViewDrawer from './viewDrawer.vue';
-  import importView from './importModal/importView.vue';
+  // import importView from './importModal/importView.vue';
   import { columns } from './data';
 
   import { LogQueryPage, LogRemove, LogLoginExport } from '/@/api/monitor/LogApi';
@@ -81,7 +82,7 @@
   import { XTTitle } from '/@/components/XTTitle/index';
   import { XTTab } from '/@/components/XTTab/index';
   import { XTForm } from '/@/components/XTForm/index';
-  import { useModal } from '/@/components/Modal';
+  // import { useModal } from '/@/components/Modal';
 
   // 标题组件右侧按钮
   const titleData = [
@@ -112,7 +113,7 @@
     resultJsonOptions.value = await listDictModel({ dictCode: 'sys_response_type' });
   });
   const { createConfirm, createMessage } = useMessage();
-  const [registerModal, { openModal }] = useModal();
+  // const [registerModal, { openModal }] = useModal();
   const [registerDrawer] = useDrawer(); // , { openDrawer }
   const [registerDrawerView, { openDrawer: openDrawerView }] = useDrawer();
 
@@ -164,9 +165,9 @@
   }
 
   // 导入按钮
-  function handleImport() {
-    openModal(true);
-  }
+  // function handleImport() {
+  //   openModal(true);
+  // }
 
   // 删除按钮事件
   async function handleDelete(record: Recordable) {

Một số tệp đã không được hiển thị bởi vì quá nhiều tập tin thay đổi trong này khác