Explorar el Código

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

lxz hace 1 año
padre
commit
a5395a413d
Se han modificado 100 ficheros con 10986 adiciones y 1232 borrados
  1. 21 0
      src/api/biz/bio/chemicalPollutantDetectionApi.ts
  2. 26 0
      src/api/biz/bio/microbialDetectionDialysateApi.ts
  3. 26 0
      src/api/biz/bio/microbialDetectionWaterApi.ts
  4. 6 8
      src/api/biz/engineer/bedApi.ts
  5. 37 0
      src/api/biz/engineer/dialysisDeviceApi.ts
  6. 31 0
      src/api/biz/engineer/maintenanceApi.ts
  7. 36 0
      src/api/biz/engineer/otherApi.ts
  8. 31 0
      src/api/biz/engineer/otherUpkeepApi.ts
  9. 36 0
      src/api/biz/engineer/waterApi.ts
  10. 16 0
      src/api/biz/engineer/waterTreatmentApi.ts
  11. 3 3
      src/api/biz/mission/recordApi.ts
  12. 28 0
      src/api/biz/stats/suppliesStatsApi.ts
  13. 10 0
      src/api/sys/sysUserApi.ts
  14. 504 480
      src/assets/iconfont/demo_index.html
  15. 140 132
      src/assets/iconfont/iconfont.css
  16. 0 1
      src/assets/iconfont/iconfont.js
  17. 240 226
      src/assets/iconfont/iconfont.json
  18. BIN
      src/assets/iconfont/iconfont.ttf
  19. BIN
      src/assets/iconfont/iconfont.woff
  20. BIN
      src/assets/iconfont/iconfont.woff2
  21. 1 1
      src/views/biz/archives/index/data.ts
  22. 87 27
      src/views/biz/engineer/bed/FormModal.vue
  23. 161 0
      src/views/biz/engineer/bed/changeFormModal.vue
  24. 201 91
      src/views/biz/engineer/bed/data.ts
  25. 316 3
      src/views/biz/engineer/bed/index.vue
  26. 449 0
      src/views/biz/engineer/bio/chemicalPollutantDetection/data.ts
  27. 89 0
      src/views/biz/engineer/bio/chemicalPollutantDetection/formDrawer.vue
  28. 283 0
      src/views/biz/engineer/bio/chemicalPollutantDetection/index.vue
  29. 7 0
      src/views/biz/engineer/bio/data.ts
  30. 60 3
      src/views/biz/engineer/bio/index.vue
  31. 173 0
      src/views/biz/engineer/bio/microbialDetectionDialysate/data.ts
  32. 96 0
      src/views/biz/engineer/bio/microbialDetectionDialysate/formModal.vue
  33. 272 0
      src/views/biz/engineer/bio/microbialDetectionDialysate/index.vue
  34. 168 0
      src/views/biz/engineer/bio/microbialDetectionWater/data.ts
  35. 96 0
      src/views/biz/engineer/bio/microbialDetectionWater/formModal.vue
  36. 272 0
      src/views/biz/engineer/bio/microbialDetectionWater/index.vue
  37. 38 27
      src/views/biz/engineer/dialysis/data.ts
  38. 9 0
      src/views/biz/engineer/dialysis/detail/data.ts
  39. 67 0
      src/views/biz/engineer/dialysis/detail/index.vue
  40. 29 0
      src/views/biz/engineer/dialysis/deviceInfo/data.ts
  41. 54 0
      src/views/biz/engineer/dialysis/deviceInfo/index.vue
  42. 143 0
      src/views/biz/engineer/dialysis/disinfectants/data.ts
  43. 114 0
      src/views/biz/engineer/dialysis/disinfectants/disinfectantsFormModal.vue
  44. 259 0
      src/views/biz/engineer/dialysis/disinfectants/index.vue
  45. 33 60
      src/views/biz/engineer/dialysis/index.vue
  46. 210 0
      src/views/biz/engineer/dialysis/maintenanceList/data.ts
  47. 133 0
      src/views/biz/engineer/dialysis/maintenanceList/formDrawer.vue
  48. 266 0
      src/views/biz/engineer/dialysis/maintenanceList/index.vue
  49. 133 0
      src/views/biz/engineer/dialysis/upkeepList/data.ts
  50. 259 0
      src/views/biz/engineer/dialysis/upkeepList/index.vue
  51. 114 0
      src/views/biz/engineer/dialysis/upkeepList/upkeepFormModal.vue
  52. 368 0
      src/views/biz/engineer/other/data.ts
  53. 7 0
      src/views/biz/engineer/other/detail/data.ts
  54. 64 0
      src/views/biz/engineer/other/detail/index.vue
  55. 17 0
      src/views/biz/engineer/other/deviceBasic/data.ts
  56. 50 0
      src/views/biz/engineer/other/deviceBasic/index.vue
  57. 90 0
      src/views/biz/engineer/other/formModal.vue
  58. 286 3
      src/views/biz/engineer/other/index.vue
  59. 111 0
      src/views/biz/engineer/other/maintenanceFormModal.vue
  60. 39 0
      src/views/biz/engineer/other/maintenanceList/data.ts
  61. 266 0
      src/views/biz/engineer/other/maintenanceList/index.vue
  62. 106 0
      src/views/biz/engineer/other/upkeepFormModal.vue
  63. 24 0
      src/views/biz/engineer/other/upkeepList/data.ts
  64. 265 0
      src/views/biz/engineer/other/upkeepList/index.vue
  65. 4 0
      src/views/biz/engineer/upkeep/data.ts
  66. 1 1
      src/views/biz/engineer/upkeep/formDrawer.vue
  67. 21 7
      src/views/biz/engineer/upkeep/index.vue
  68. 395 0
      src/views/biz/engineer/water/data.ts
  69. 8 0
      src/views/biz/engineer/water/detail/data.ts
  70. 69 0
      src/views/biz/engineer/water/detail/index.vue
  71. 20 0
      src/views/biz/engineer/water/deviceBasic/data.ts
  72. 50 0
      src/views/biz/engineer/water/deviceBasic/index.vue
  73. 85 0
      src/views/biz/engineer/water/formModal.vue
  74. 237 3
      src/views/biz/engineer/water/index.vue
  75. 111 0
      src/views/biz/engineer/water/maintenanceFormModal.vue
  76. 39 0
      src/views/biz/engineer/water/maintenanceList/data.ts
  77. 266 0
      src/views/biz/engineer/water/maintenanceList/index.vue
  78. 577 0
      src/views/biz/engineer/water/treatmentList/data.ts
  79. 112 0
      src/views/biz/engineer/water/treatmentList/formDrawer.vue
  80. 170 0
      src/views/biz/engineer/water/treatmentList/index.vue
  81. 106 0
      src/views/biz/engineer/water/upkeepFormModal.vue
  82. 24 0
      src/views/biz/engineer/water/upkeepList/data.ts
  83. 265 0
      src/views/biz/engineer/water/upkeepList/index.vue
  84. 19 7
      src/views/biz/inventory/pharmaceuticals/index.vue
  85. 19 7
      src/views/biz/inventory/supplies/index.vue
  86. 1 6
      src/views/biz/management/complication/index.vue
  87. 1 1
      src/views/biz/management/parameter/index.vue
  88. 9 9
      src/views/biz/mission/article/data.ts
  89. 5 1
      src/views/biz/mission/article/formDrawer.vue
  90. 32 21
      src/views/biz/mission/article/index.vue
  91. 50 31
      src/views/biz/mission/record/index.vue
  92. 133 0
      src/views/biz/stats/chart/pie.vue
  93. 6 0
      src/views/biz/stats/supplies/data.ts
  94. 51 3
      src/views/biz/stats/supplies/index.vue
  95. 37 0
      src/views/biz/stats/supplies/pharmaceuticalsStats/data.ts
  96. 192 0
      src/views/biz/stats/supplies/pharmaceuticalsStats/index.vue
  97. 55 0
      src/views/biz/stats/supplies/suppliesStats/data.ts
  98. 252 0
      src/views/biz/stats/supplies/suppliesStats/index.vue
  99. 12 11
      src/views/biz/visit/check/data.ts
  100. 76 59
      src/views/biz/visit/check/index.vue

+ 21 - 0
src/api/biz/bio/chemicalPollutantDetectionApi.ts

@@ -0,0 +1,21 @@
+import { defHttp } from '/@/utils/http/axios';
+import { setParams } from '/@/utils/index';
+
+enum Api {
+  chemicalPollutantDetectionQueryPage = '/biz/device/chemicalPollutantDetection/query/page',
+  chemicalPollutantDetectionAdd = '/biz/device/chemicalPollutantDetection/add',
+  chemicalPollutantDetectionRemove = '/biz/device/chemicalPollutantDetection/removeByIds',
+}
+
+// 查询设备列表
+export const chemicalPollutantDetectionQueryPage = (params?: object) => {
+  return defHttp.post({ url: Api.chemicalPollutantDetectionQueryPage, params: setParams(params) });
+};
+// 添加设备
+export const chemicalPollutantDetectionAdd = (params?: object) => {
+  return defHttp.post({ url: Api.chemicalPollutantDetectionAdd, params: params });
+};
+
+export const chemicalPollutantDetectionRemove = (params?: object) => {
+  return defHttp.post({ url: Api.chemicalPollutantDetectionRemove, params: params });
+};

+ 26 - 0
src/api/biz/bio/microbialDetectionDialysateApi.ts

@@ -0,0 +1,26 @@
+import { defHttp } from '/@/utils/http/axios';
+import { setParams } from '/@/utils/index';
+
+enum Api {
+  microbialDetectionDialysateQueryPage = '/biz/device/microbialDetection/dialysate/page',
+  microbialDetectionDialysateDetail = '/biz/device/microbialDetection/dialysate/detail',
+  microbialDetectionDialysateAdd = '/biz/device/microbialDetection/dialysate/add',
+  microbialDetectionDialysateEdit = '/biz/device/microbialDetection/dialysate/edit',
+}
+
+// 查询设备列表
+export const microbialDetectionDialysateQueryPage = (params?: object) => {
+  return defHttp.post({ url: Api.microbialDetectionDialysateQueryPage, params: setParams(params) });
+};
+// 添加设备
+export const microbialDetectionDialysateAdd = (params?: object) => {
+  return defHttp.post({ url: Api.microbialDetectionDialysateAdd, params: params });
+};
+// 编辑设备信息
+export const microbialDetectionDialysateEdit = (params?: object) => {
+  return defHttp.post({ url: Api.microbialDetectionDialysateEdit, params: params });
+};
+// 其他设备详情
+export const microbialDetectionDialysateDetail = (id: string) => {
+  return defHttp.get({ url: Api.microbialDetectionDialysateDetail + '/' + id });
+};

+ 26 - 0
src/api/biz/bio/microbialDetectionWaterApi.ts

@@ -0,0 +1,26 @@
+import { defHttp } from '/@/utils/http/axios';
+import { setParams } from '/@/utils/index';
+
+enum Api {
+  microbialDetectionWaterQueryPage = '/biz/device/microbialDetection/dialysisWater/page',
+  microbialDetectionWaterDetail = '/biz/device/microbialDetection/dialysisWater/detail',
+  microbialDetectionWaterAdd = '/biz/device/microbialDetection/dialysisWater/add',
+  microbialDetectionWaterEdit = '/biz/device/microbialDetection/dialysisWater/edit',
+}
+
+// 查询设备列表
+export const microbialDetectionWaterQueryPage = (params?: object) => {
+  return defHttp.post({ url: Api.microbialDetectionWaterQueryPage, params: setParams(params) });
+};
+// 添加设备
+export const microbialDetectionWaterAdd = (params?: object) => {
+  return defHttp.post({ url: Api.microbialDetectionWaterAdd, params: params });
+};
+// 编辑设备信息
+export const microbialDetectionWaterEdit = (params?: object) => {
+  return defHttp.post({ url: Api.microbialDetectionWaterEdit, params: params });
+};
+// 其他设备详情
+export const microbialDetectionWaterDetail = (id: string) => {
+  return defHttp.get({ url: Api.microbialDetectionWaterDetail + '/' + id });
+};

+ 6 - 8
src/api/biz/engineer/bedApi.ts

@@ -7,12 +7,12 @@ enum Api {
   engineerBedAdd = '/device/bed/add',
   engineerBedEdit = '/device/bed/edit',
   engineerBedRemove = '/device/bed/removeByIds',
+
+  bedDeviceReplacePageQuery = '/biz/device/bedDeviceReplace/query/page',
 }
 
 /**
  *
- * @author fan
- * @date  2023/06/30 17:39
  * @description: 根据条件查询病床列表,权限 - device:dialysisDevice:query
  * @method: POST
  */
@@ -22,8 +22,6 @@ export const engineerBedQueryPage = (params?: object) => {
 };
 /**
  *
- * @author fan
- * @date  2023/06/30 17:39
  * @description: 根据id查询病床详细信息,权限 - device:dialysisDevice:query
  * @method: GET
  * @param:  id 病床主键id
@@ -34,8 +32,6 @@ export const engineerBedDetail = (id: string) => {
 
 /**
  *
- * @author fan
- * @date  2023/06/30 17:39
  * @description: 添加病床,权限 - device:dialysisDevice:add
  * @method: POST
  * @return:
@@ -48,8 +44,6 @@ export const engineerBedAdd = (params?: object) => {
 
 /**
  *
- * @author fan
- * @date  2023/06/30 17:39
  * @description: 通过主键id编辑病床,权限 - device:dialysisDevice:edit
  * @method: POST
  * @return:
@@ -67,3 +61,7 @@ export const engineerBedEdit = (params?: object) => {
 export const engineerBedRemove = (params: Array<string | number>) => {
   return defHttp.post({ url: Api.engineerBedRemove, params: params });
 };
+
+export const bedDeviceReplacePageQuery = (params?: object) => {
+  return defHttp.post({ url: Api.bedDeviceReplacePageQuery, params: setParams(params) });
+};

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

@@ -7,6 +7,14 @@ enum Api {
   engineerDialysisDeviceAdd = '/device/dialysisDevice/add',
   engineerDialysisDeviceEdit = '/device/dialysisDevice/edit',
   engineerDialysisDeviceRemove = '/device/dialysisDevice/removeByIds',
+  engineerDialysisDeviceList = '/device/dialysisDevice/query/noPage',
+  engineerDilysisDeviceByBed = '/device/dialysisDevice/query/byBed',
+
+  upkeepQueryPage = '/biz/device/dialysisMaintain/detailByDevice',
+  upkeepDetail = '/biz/device/dialysisMaintain/detail',
+  upkeepAdd = '/biz/device/dialysisMaintain/add',
+  upkeepEdit = '/biz/device/dialysisMaintain/edit',
+  upkeepRemove = '/biz/device/dialysisMaintain/removeByIds',
 }
 
 /**
@@ -67,3 +75,32 @@ export const engineerDialysisDeviceEdit = (params?: object) => {
 export const engineerDialysisDeviceRemove = (params: Array<string | number>) => {
   return defHttp.post({ url: Api.engineerDialysisDeviceRemove, params: params });
 };
+// 不分页设备列表
+export const engineerDialysisDeviceList = (params?: object) => {
+  return defHttp.post({ url: Api.engineerDialysisDeviceList, params: params });
+};
+//床位管理设备列表(查询未绑定床位的设备列表)
+export const engineerDilysisDeviceByBed = (params?: object) => {
+  return defHttp.post({ url: Api.engineerDilysisDeviceByBed, params: params });
+};
+
+// 透析设备保养记录相关接口
+export const upkeepQueryPage = (params?: object) => {
+  return defHttp.post({ url: Api.upkeepQueryPage, params: params });
+};
+
+export const upkeepDetail = (id: string) => {
+  return defHttp.get({ url: Api.upkeepDetail + '/' + id });
+};
+
+export const upkeepAdd = (params?: object) => {
+  return defHttp.post({ url: Api.upkeepAdd, params: params });
+};
+
+export const upkeepEdit = (params?: object) => {
+  return defHttp.post({ url: Api.upkeepEdit, params: params });
+};
+
+export const upkeepRemove = (params: Array<string | number>) => {
+  return defHttp.post({ url: Api.upkeepRemove, params: params });
+};

+ 31 - 0
src/api/biz/engineer/maintenanceApi.ts

@@ -0,0 +1,31 @@
+import { defHttp } from '/@/utils/http/axios';
+import { setParams } from '/@/utils/index';
+
+enum Api {
+  maintenanceQueryPage = '/biz/deviceMaintenance/query/page',
+  maintenanceDetail = '/biz/deviceMaintenance/detail/other',
+  maintenanceAdd = '/biz/deviceMaintenance/add',
+  maintenanceEdit = '/biz/deviceMaintenance/edit',
+  maintenanceRemove = '/biz/deviceMaintenance/removeByIds',
+}
+
+// 查询设备列表
+export const maintenanceQueryPage = (params?: object) => {
+  return defHttp.post({ url: Api.maintenanceQueryPage, params: setParams(params) });
+};
+// 删除设备
+export const maintenanceRemove = (params: Array<string | number>) => {
+  return defHttp.post({ url: Api.maintenanceRemove, params: params });
+};
+// 添加设备
+export const maintenanceAdd = (params?: object) => {
+  return defHttp.post({ url: Api.maintenanceAdd, params: params });
+};
+// 编辑设备信息
+export const maintenanceEdit = (params?: object) => {
+  return defHttp.post({ url: Api.maintenanceEdit, params: params });
+};
+// 其他设备详情
+export const maintenanceDetail = (id: string) => {
+  return defHttp.get({ url: Api.maintenanceDetail + '/' + id });
+};

+ 36 - 0
src/api/biz/engineer/otherApi.ts

@@ -0,0 +1,36 @@
+import { defHttp } from '/@/utils/http/axios';
+import { setParams } from '/@/utils/index';
+
+enum Api {
+  otherQueryPage = '/biz/otherDevice/query/page',
+  otherDetail = '/biz/otherDevice/detail',
+  otherAdd = '/biz/otherDevice/add',
+  otherEdit = '/biz/otherDevice/edit',
+  otherRemove = '/biz/device/dialysisMaintain/removeByIds/',
+  otherTypeCount = '/biz/otherDevice/typeNumber',
+}
+
+// 获取Tab各方数量
+export function otherTypeCount() {
+  return defHttp.get({ url: Api.otherTypeCount });
+}
+// 查询设备列表
+export const otherQueryPage = (params?: object) => {
+  return defHttp.post({ url: Api.otherQueryPage, params: setParams(params) });
+};
+// 删除设备
+export const otherRemove = (params: Array<string | number>) => {
+  return defHttp.post({ url: Api.otherRemove, params: params });
+};
+// 添加设备
+export const otherAdd = (params?: object) => {
+  return defHttp.post({ url: Api.otherAdd, params: params });
+};
+// 编辑设备信息
+export const otherEdit = (params?: object) => {
+  return defHttp.post({ url: Api.otherEdit, params: params });
+};
+// 其他设备详情
+export const otherDetail = (id: string) => {
+  return defHttp.get({ url: Api.otherDetail + '/' + id });
+};

+ 31 - 0
src/api/biz/engineer/otherUpkeepApi.ts

@@ -0,0 +1,31 @@
+import { defHttp } from '/@/utils/http/axios';
+import { setParams } from '/@/utils/index';
+
+enum Api {
+  upkeepQueryPage = '/biz/device/otherMaintain/query',
+  upkeepDetail = '/biz/device/otherMaintain/query',
+  upkeepAdd = '/biz/device/otherMaintain/add',
+  upkeepEdit = '/biz/device/otherMaintain/edit',
+  upkeepRemove = '/biz/device/otherMaintain/removeByIds',
+}
+
+// 查询设备列表
+export const upkeepQueryPage = (params?: object) => {
+  return defHttp.post({ url: Api.upkeepQueryPage, params: setParams(params) });
+};
+// 删除设备
+export const upkeepRemove = (params: Array<string | number>) => {
+  return defHttp.post({ url: Api.upkeepRemove, params: params });
+};
+// 添加设备
+export const upkeepAdd = (params?: object) => {
+  return defHttp.post({ url: Api.upkeepAdd, params: params });
+};
+// 编辑设备信息
+export const upkeepEdit = (params?: object) => {
+  return defHttp.post({ url: Api.upkeepEdit, params: params });
+};
+// 其他设备详情
+export const upkeepDetail = (id: string) => {
+  return defHttp.get({ url: Api.upkeepDetail + '/' + id });
+};

+ 36 - 0
src/api/biz/engineer/waterApi.ts

@@ -0,0 +1,36 @@
+import { defHttp } from '/@/utils/http/axios';
+import { setParams } from '/@/utils/index';
+
+enum Api {
+  waterQueryPage = '/biz/waterTreatmentDevice/query/page',
+  waterDetail = '/biz/waterTreatmentDevice/detail',
+  waterAdd = '/biz/waterTreatmentDevice/add',
+  waterEdit = '/biz/waterTreatmentDevice/edit',
+  waterRemove = '/biz/device/dialysisMaintain/removeByIds/',
+  waterList = '/biz/waterTreatmentDevice/query/noPage',
+}
+
+// 查询设备列表
+export const waterQueryPage = (params?: object) => {
+  return defHttp.post({ url: Api.waterQueryPage, params: setParams(params) });
+};
+// 删除设备
+export const waterRemove = (params: Array<string | number>) => {
+  return defHttp.post({ url: Api.waterRemove, params: params });
+};
+// 添加设备
+export const waterAdd = (params?: object) => {
+  return defHttp.post({ url: Api.waterAdd, params: params });
+};
+// 编辑设备信息
+export const waterEdit = (params?: object) => {
+  return defHttp.post({ url: Api.waterEdit, params: params });
+};
+// 其他设备详情
+export const waterDetail = (id: string) => {
+  return defHttp.get({ url: Api.waterDetail + '/' + id });
+};
+
+export const waterList = (params?: object) => {
+  return defHttp.post({ url: Api.waterList, params: params });
+};

+ 16 - 0
src/api/biz/engineer/waterTreatmentApi.ts

@@ -0,0 +1,16 @@
+import { defHttp } from '/@/utils/http/axios';
+import { setParams } from '/@/utils/index';
+
+enum Api {
+  waterTreatmentQueryPage = '/biz/deviceInspect/waterTreatment/query/page',
+  waterTreatmentAdd = '/biz/deviceInspect/waterTreatment/add',
+}
+
+// 查询设备列表
+export const waterTreatmentQueryPage = (params?: object) => {
+  return defHttp.post({ url: Api.waterTreatmentQueryPage, params: setParams(params) });
+};
+// 添加设备
+export const waterTreatmentAdd = (params?: object) => {
+  return defHttp.post({ url: Api.waterTreatmentAdd, params: params });
+};

+ 3 - 3
src/api/biz/mission/recordApi.ts

@@ -9,12 +9,12 @@ enum Api {
   recordAdd = 'biz/educationRecord/add',
 }
 
-export const getrecordList = (params?: object) => {
+export const recordList = (params?: object) => {
   return defHttp.post({ url: Api.recordList, params: setParams(params) });
 };
 
-export const recordDel = (id?: String) => {
-  return defHttp.post({ url: Api.recordDel + '/' + id });
+export const recordDel = (params?: object) => {
+  return defHttp.post({ url: Api.recordDel, params: params });
 };
 
 export const recordNumber = () => {

+ 28 - 0
src/api/biz/stats/suppliesStatsApi.ts

@@ -0,0 +1,28 @@
+import { defHttp } from '/@/utils/http/axios';
+
+enum Api {
+  drugStats = '/biz/SuppliesStats/drugStats',
+  suppliesTypeStats = '/biz/SuppliesStats/typeStats',
+  suppliesMarksStats = '/biz/SuppliesStats/makersStats',
+}
+// 查询药品用量API
+export const getDrugStats = (params?: object) => {
+  return defHttp.post({
+    url: Api.drugStats,
+    params,
+  });
+};
+
+export const getSuppliesTypeStats = (params?: object) => {
+  return defHttp.post({
+    url: Api.suppliesTypeStats,
+    params,
+  });
+};
+
+export const getSuppliesMarksStats = (params?: object) => {
+  return defHttp.post({
+    url: Api.suppliesMarksStats,
+    params,
+  });
+};

+ 10 - 0
src/api/sys/sysUserApi.ts

@@ -13,6 +13,8 @@ enum Api {
   userListMenus = '/user/listMenus-',
   userPermCode = '/user/getPermissionInfo',
   validatorUsername = '/sys/user/username/valid',
+
+  sysUserList = '/sys/user/query/list',
 }
 
 /**
@@ -92,3 +94,11 @@ export const userPermCode = (params?: object) => {
 export const validatorUsername = (params?: object) => {
   return defHttp.post({ url: Api.validatorUsername, params: params });
 };
+
+/**
+ * @description: 查询用户列表,不分页
+ * @method: POST
+ */
+export const sysUserList = (params?: object) => {
+  return defHttp.post({ url: Api.sysUserList, params: setParams(params) });
+};

La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 504 - 480
src/assets/iconfont/demo_index.html


+ 140 - 132
src/assets/iconfont/iconfont.css

@@ -1,8 +1,8 @@
 @font-face {
-  font-family: 'iconfont'; /* Project id 3806176 */
-  src: url('iconfont.woff2?t=1695089019525') format('woff2'),
-    url('iconfont.woff?t=1695089019525') format('woff'),
-    url('iconfont.ttf?t=1695089019525') format('truetype');
+  font-family: 'iconfont'; /* Project id 4279382 */
+  src: url('iconfont.woff2?t=1703296062271') format('woff2'),
+    url('iconfont.woff?t=1703296062271') format('woff'),
+    url('iconfont.ttf?t=1703296062271') format('truetype');
 }
 
 .iconfont {
@@ -13,6 +13,14 @@
   -moz-osx-font-smoothing: grayscale;
 }
 
+.icon-xt-download-download_default:before {
+  content: '\e621';
+}
+
+.icon-xt-download-download_selected:before {
+  content: '\e622';
+}
+
 .icon-tingyong-moren:before {
   content: '\e60e';
 }
@@ -485,134 +493,6 @@
   content: '\e64a';
 }
 
-.icon-unlock:before {
-  content: '\e808';
-}
-
-.icon-lock:before {
-  content: '\e809';
-}
-
-.icon-customerservice:before {
-  content: '\e80a';
-}
-
-.icon-medicinebox:before {
-  content: '\e80b';
-}
-
-.icon-shop:before {
-  content: '\e80c';
-}
-
-.icon-rocket:before {
-  content: '\e80d';
-}
-
-.icon-shopping:before {
-  content: '\e80e';
-}
-
-.icon-folder:before {
-  content: '\e80f';
-}
-
-.icon-folder-open:before {
-  content: '\e810';
-}
-
-.icon-folder-add:before {
-  content: '\e811';
-}
-
-.icon-accountbook:before {
-  content: '\e812';
-}
-
-.icon-contacts:before {
-  content: '\e813';
-}
-
-.icon-carryout:before {
-  content: '\e814';
-}
-
-.icon-calendar-check:before {
-  content: '\e815';
-}
-
-.icon-calendar:before {
-  content: '\e816';
-}
-
-.icon-scan:before {
-  content: '\e817';
-}
-
-.icon-select:before {
-  content: '\e818';
-}
-
-.icon-laptop:before {
-  content: '\e819';
-}
-
-.icon-barcode:before {
-  content: '\e81a';
-}
-
-.icon-camera:before {
-  content: '\e81b';
-}
-
-.icon-cluster:before {
-  content: '\e81c';
-}
-
-.icon-gateway:before {
-  content: '\e81d';
-}
-
-.icon-car:before {
-  content: '\e81e';
-}
-
-.icon-printer:before {
-  content: '\e81f';
-}
-
-.icon-read:before {
-  content: '\e820';
-}
-
-.icon-cloud-server:before {
-  content: '\e821';
-}
-
-.icon-cloud-upload:before {
-  content: '\e822';
-}
-
-.icon-cloud:before {
-  content: '\e823';
-}
-
-.icon-cloud-download:before {
-  content: '\e824';
-}
-
-.icon-cloud-sync:before {
-  content: '\e825';
-}
-
-.icon-video:before {
-  content: '\e826';
-}
-
-.icon-notification:before {
-  content: '\e827';
-}
-
 .icon-sound:before {
   content: '\e828';
 }
@@ -1393,6 +1273,134 @@
   content: '\e807';
 }
 
+.icon-unlock:before {
+  content: '\e808';
+}
+
+.icon-lock:before {
+  content: '\e809';
+}
+
+.icon-customerservice:before {
+  content: '\e80a';
+}
+
+.icon-medicinebox:before {
+  content: '\e80b';
+}
+
+.icon-shop:before {
+  content: '\e80c';
+}
+
+.icon-rocket:before {
+  content: '\e80d';
+}
+
+.icon-shopping:before {
+  content: '\e80e';
+}
+
+.icon-folder:before {
+  content: '\e80f';
+}
+
+.icon-folder-open:before {
+  content: '\e810';
+}
+
+.icon-folder-add:before {
+  content: '\e811';
+}
+
+.icon-accountbook:before {
+  content: '\e812';
+}
+
+.icon-contacts:before {
+  content: '\e813';
+}
+
+.icon-carryout:before {
+  content: '\e814';
+}
+
+.icon-calendar-check:before {
+  content: '\e815';
+}
+
+.icon-calendar:before {
+  content: '\e816';
+}
+
+.icon-scan:before {
+  content: '\e817';
+}
+
+.icon-select:before {
+  content: '\e818';
+}
+
+.icon-laptop:before {
+  content: '\e819';
+}
+
+.icon-barcode:before {
+  content: '\e81a';
+}
+
+.icon-camera:before {
+  content: '\e81b';
+}
+
+.icon-cluster:before {
+  content: '\e81c';
+}
+
+.icon-gateway:before {
+  content: '\e81d';
+}
+
+.icon-car:before {
+  content: '\e81e';
+}
+
+.icon-printer:before {
+  content: '\e81f';
+}
+
+.icon-read:before {
+  content: '\e820';
+}
+
+.icon-cloud-server:before {
+  content: '\e821';
+}
+
+.icon-cloud-upload:before {
+  content: '\e822';
+}
+
+.icon-cloud:before {
+  content: '\e823';
+}
+
+.icon-cloud-download:before {
+  content: '\e824';
+}
+
+.icon-cloud-sync:before {
+  content: '\e825';
+}
+
+.icon-video:before {
+  content: '\e826';
+}
+
+.icon-notification:before {
+  content: '\e827';
+}
+
 .icon-close-circle:before {
   content: '\e782';
 }

La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 0 - 1
src/assets/iconfont/iconfont.js


+ 240 - 226
src/assets/iconfont/iconfont.json

@@ -1,10 +1,24 @@
 {
-  "id": "3806176",
-  "name": "ant Design 官方",
+  "id": "4279382",
+  "name": "ant Design 官方-copy",
   "font_family": "iconfont",
   "css_prefix_text": "icon-",
   "description": "",
   "glyphs": [
+    {
+      "icon_id": "38691369",
+      "name": "xt-下载-默认",
+      "font_class": "xt-download-download_default",
+      "unicode": "e621",
+      "unicode_decimal": 58913
+    },
+    {
+      "icon_id": "38691368",
+      "name": "xt-下载-鼠标上移",
+      "font_class": "xt-download-download_selected",
+      "unicode": "e622",
+      "unicode_decimal": 58914
+    },
     {
       "icon_id": "37422611",
       "name": "xt-停用-默认",
@@ -831,230 +845,6 @@
       "unicode": "e64a",
       "unicode_decimal": 58954
     },
-    {
-      "icon_id": "4766694",
-      "name": "unlock",
-      "font_class": "unlock",
-      "unicode": "e808",
-      "unicode_decimal": 59400
-    },
-    {
-      "icon_id": "4766695",
-      "name": "lock",
-      "font_class": "lock",
-      "unicode": "e809",
-      "unicode_decimal": 59401
-    },
-    {
-      "icon_id": "4766762",
-      "name": "customerservice",
-      "font_class": "customerservice",
-      "unicode": "e80a",
-      "unicode_decimal": 59402
-    },
-    {
-      "icon_id": "4766774",
-      "name": "medicinebox",
-      "font_class": "medicinebox",
-      "unicode": "e80b",
-      "unicode_decimal": 59403
-    },
-    {
-      "icon_id": "4766775",
-      "name": "shop",
-      "font_class": "shop",
-      "unicode": "e80c",
-      "unicode_decimal": 59404
-    },
-    {
-      "icon_id": "4766778",
-      "name": "rocket",
-      "font_class": "rocket",
-      "unicode": "e80d",
-      "unicode_decimal": 59405
-    },
-    {
-      "icon_id": "4766779",
-      "name": "shopping",
-      "font_class": "shopping",
-      "unicode": "e80e",
-      "unicode_decimal": 59406
-    },
-    {
-      "icon_id": "4766846",
-      "name": "folder",
-      "font_class": "folder",
-      "unicode": "e80f",
-      "unicode_decimal": 59407
-    },
-    {
-      "icon_id": "4766847",
-      "name": "folder-open",
-      "font_class": "folder-open",
-      "unicode": "e810",
-      "unicode_decimal": 59408
-    },
-    {
-      "icon_id": "4766848",
-      "name": "folder-add",
-      "font_class": "folder-add",
-      "unicode": "e811",
-      "unicode_decimal": 59409
-    },
-    {
-      "icon_id": "4766854",
-      "name": "account book",
-      "font_class": "accountbook",
-      "unicode": "e812",
-      "unicode_decimal": 59410
-    },
-    {
-      "icon_id": "4766855",
-      "name": "contacts",
-      "font_class": "contacts",
-      "unicode": "e813",
-      "unicode_decimal": 59411
-    },
-    {
-      "icon_id": "4766856",
-      "name": "carry out",
-      "font_class": "carryout",
-      "unicode": "e814",
-      "unicode_decimal": 59412
-    },
-    {
-      "icon_id": "4766857",
-      "name": "calendar-check",
-      "font_class": "calendar-check",
-      "unicode": "e815",
-      "unicode_decimal": 59413
-    },
-    {
-      "icon_id": "4766858",
-      "name": "calendar",
-      "font_class": "calendar",
-      "unicode": "e816",
-      "unicode_decimal": 59414
-    },
-    {
-      "icon_id": "4766861",
-      "name": "scan",
-      "font_class": "scan",
-      "unicode": "e817",
-      "unicode_decimal": 59415
-    },
-    {
-      "icon_id": "4766862",
-      "name": "select",
-      "font_class": "select",
-      "unicode": "e818",
-      "unicode_decimal": 59416
-    },
-    {
-      "icon_id": "4766881",
-      "name": "laptop",
-      "font_class": "laptop",
-      "unicode": "e819",
-      "unicode_decimal": 59417
-    },
-    {
-      "icon_id": "4766882",
-      "name": "barcode",
-      "font_class": "barcode",
-      "unicode": "e81a",
-      "unicode_decimal": 59418
-    },
-    {
-      "icon_id": "4766883",
-      "name": "camera",
-      "font_class": "camera",
-      "unicode": "e81b",
-      "unicode_decimal": 59419
-    },
-    {
-      "icon_id": "4766884",
-      "name": "cluster",
-      "font_class": "cluster",
-      "unicode": "e81c",
-      "unicode_decimal": 59420
-    },
-    {
-      "icon_id": "4766885",
-      "name": "gateway",
-      "font_class": "gateway",
-      "unicode": "e81d",
-      "unicode_decimal": 59421
-    },
-    {
-      "icon_id": "4766886",
-      "name": "car",
-      "font_class": "car",
-      "unicode": "e81e",
-      "unicode_decimal": 59422
-    },
-    {
-      "icon_id": "4766887",
-      "name": "printer",
-      "font_class": "printer",
-      "unicode": "e81f",
-      "unicode_decimal": 59423
-    },
-    {
-      "icon_id": "4766888",
-      "name": "read",
-      "font_class": "read",
-      "unicode": "e820",
-      "unicode_decimal": 59424
-    },
-    {
-      "icon_id": "4766900",
-      "name": "cloud-server",
-      "font_class": "cloud-server",
-      "unicode": "e821",
-      "unicode_decimal": 59425
-    },
-    {
-      "icon_id": "4766901",
-      "name": "cloud-upload",
-      "font_class": "cloud-upload",
-      "unicode": "e822",
-      "unicode_decimal": 59426
-    },
-    {
-      "icon_id": "4766902",
-      "name": "cloud",
-      "font_class": "cloud",
-      "unicode": "e823",
-      "unicode_decimal": 59427
-    },
-    {
-      "icon_id": "4766903",
-      "name": "cloud-download",
-      "font_class": "cloud-download",
-      "unicode": "e824",
-      "unicode_decimal": 59428
-    },
-    {
-      "icon_id": "4766904",
-      "name": "cloud-sync",
-      "font_class": "cloud-sync",
-      "unicode": "e825",
-      "unicode_decimal": 59429
-    },
-    {
-      "icon_id": "4766905",
-      "name": "video",
-      "font_class": "video",
-      "unicode": "e826",
-      "unicode_decimal": 59430
-    },
-    {
-      "icon_id": "4766906",
-      "name": "notification",
-      "font_class": "notification",
-      "unicode": "e827",
-      "unicode_decimal": 59431
-    },
     {
       "icon_id": "4766907",
       "name": "sound",
@@ -2420,6 +2210,230 @@
       "unicode": "e807",
       "unicode_decimal": 59399
     },
+    {
+      "icon_id": "4766694",
+      "name": "unlock",
+      "font_class": "unlock",
+      "unicode": "e808",
+      "unicode_decimal": 59400
+    },
+    {
+      "icon_id": "4766695",
+      "name": "lock",
+      "font_class": "lock",
+      "unicode": "e809",
+      "unicode_decimal": 59401
+    },
+    {
+      "icon_id": "4766762",
+      "name": "customerservice",
+      "font_class": "customerservice",
+      "unicode": "e80a",
+      "unicode_decimal": 59402
+    },
+    {
+      "icon_id": "4766774",
+      "name": "medicinebox",
+      "font_class": "medicinebox",
+      "unicode": "e80b",
+      "unicode_decimal": 59403
+    },
+    {
+      "icon_id": "4766775",
+      "name": "shop",
+      "font_class": "shop",
+      "unicode": "e80c",
+      "unicode_decimal": 59404
+    },
+    {
+      "icon_id": "4766778",
+      "name": "rocket",
+      "font_class": "rocket",
+      "unicode": "e80d",
+      "unicode_decimal": 59405
+    },
+    {
+      "icon_id": "4766779",
+      "name": "shopping",
+      "font_class": "shopping",
+      "unicode": "e80e",
+      "unicode_decimal": 59406
+    },
+    {
+      "icon_id": "4766846",
+      "name": "folder",
+      "font_class": "folder",
+      "unicode": "e80f",
+      "unicode_decimal": 59407
+    },
+    {
+      "icon_id": "4766847",
+      "name": "folder-open",
+      "font_class": "folder-open",
+      "unicode": "e810",
+      "unicode_decimal": 59408
+    },
+    {
+      "icon_id": "4766848",
+      "name": "folder-add",
+      "font_class": "folder-add",
+      "unicode": "e811",
+      "unicode_decimal": 59409
+    },
+    {
+      "icon_id": "4766854",
+      "name": "account book",
+      "font_class": "accountbook",
+      "unicode": "e812",
+      "unicode_decimal": 59410
+    },
+    {
+      "icon_id": "4766855",
+      "name": "contacts",
+      "font_class": "contacts",
+      "unicode": "e813",
+      "unicode_decimal": 59411
+    },
+    {
+      "icon_id": "4766856",
+      "name": "carry out",
+      "font_class": "carryout",
+      "unicode": "e814",
+      "unicode_decimal": 59412
+    },
+    {
+      "icon_id": "4766857",
+      "name": "calendar-check",
+      "font_class": "calendar-check",
+      "unicode": "e815",
+      "unicode_decimal": 59413
+    },
+    {
+      "icon_id": "4766858",
+      "name": "calendar",
+      "font_class": "calendar",
+      "unicode": "e816",
+      "unicode_decimal": 59414
+    },
+    {
+      "icon_id": "4766861",
+      "name": "scan",
+      "font_class": "scan",
+      "unicode": "e817",
+      "unicode_decimal": 59415
+    },
+    {
+      "icon_id": "4766862",
+      "name": "select",
+      "font_class": "select",
+      "unicode": "e818",
+      "unicode_decimal": 59416
+    },
+    {
+      "icon_id": "4766881",
+      "name": "laptop",
+      "font_class": "laptop",
+      "unicode": "e819",
+      "unicode_decimal": 59417
+    },
+    {
+      "icon_id": "4766882",
+      "name": "barcode",
+      "font_class": "barcode",
+      "unicode": "e81a",
+      "unicode_decimal": 59418
+    },
+    {
+      "icon_id": "4766883",
+      "name": "camera",
+      "font_class": "camera",
+      "unicode": "e81b",
+      "unicode_decimal": 59419
+    },
+    {
+      "icon_id": "4766884",
+      "name": "cluster",
+      "font_class": "cluster",
+      "unicode": "e81c",
+      "unicode_decimal": 59420
+    },
+    {
+      "icon_id": "4766885",
+      "name": "gateway",
+      "font_class": "gateway",
+      "unicode": "e81d",
+      "unicode_decimal": 59421
+    },
+    {
+      "icon_id": "4766886",
+      "name": "car",
+      "font_class": "car",
+      "unicode": "e81e",
+      "unicode_decimal": 59422
+    },
+    {
+      "icon_id": "4766887",
+      "name": "printer",
+      "font_class": "printer",
+      "unicode": "e81f",
+      "unicode_decimal": 59423
+    },
+    {
+      "icon_id": "4766888",
+      "name": "read",
+      "font_class": "read",
+      "unicode": "e820",
+      "unicode_decimal": 59424
+    },
+    {
+      "icon_id": "4766900",
+      "name": "cloud-server",
+      "font_class": "cloud-server",
+      "unicode": "e821",
+      "unicode_decimal": 59425
+    },
+    {
+      "icon_id": "4766901",
+      "name": "cloud-upload",
+      "font_class": "cloud-upload",
+      "unicode": "e822",
+      "unicode_decimal": 59426
+    },
+    {
+      "icon_id": "4766902",
+      "name": "cloud",
+      "font_class": "cloud",
+      "unicode": "e823",
+      "unicode_decimal": 59427
+    },
+    {
+      "icon_id": "4766903",
+      "name": "cloud-download",
+      "font_class": "cloud-download",
+      "unicode": "e824",
+      "unicode_decimal": 59428
+    },
+    {
+      "icon_id": "4766904",
+      "name": "cloud-sync",
+      "font_class": "cloud-sync",
+      "unicode": "e825",
+      "unicode_decimal": 59429
+    },
+    {
+      "icon_id": "4766905",
+      "name": "video",
+      "font_class": "video",
+      "unicode": "e826",
+      "unicode_decimal": 59430
+    },
+    {
+      "icon_id": "4766906",
+      "name": "notification",
+      "font_class": "notification",
+      "unicode": "e827",
+      "unicode_decimal": 59431
+    },
     {
       "icon_id": "4765725",
       "name": "close-circle",

BIN
src/assets/iconfont/iconfont.ttf


BIN
src/assets/iconfont/iconfont.woff


BIN
src/assets/iconfont/iconfont.woff2


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

@@ -44,7 +44,7 @@ export const siftFormSchema: FormSchema[] = [
   {
     label: '性别',
     field: 'gender',
-    component: 'ApiRadioGroup',
+    component: 'RadioGroup',
     componentProps: {
       api: listDictModel,
       params: {

+ 87 - 27
src/views/biz/engineer/bed/FormModal.vue

@@ -9,28 +9,75 @@
     :showFooter="true"
   >
     <div class="!pl-8 !pt-2">
-      <BasicForm @register="registerForm" layout="vertical" />
+      <BasicForm @register="registerForm" layout="vertical">
+        <template #wardInfo="{ model, field }">
+          <div class="flex">
+            <div
+              v-for="item in model[field]"
+              :key="item"
+              :style="{
+                backgroundColor: formatDictColor(bizDictOptions.wardType, item),
+                color: formatDictFontColor(bizDictOptions.wardType, item),
+                padding: '1px 6px',
+                borderRadius: '2px',
+                marginRight: '4px',
+              }"
+            >
+              {{ formatDictValue(bizDictOptions.wardType, item) }}
+            </div>
+          </div>
+        </template>
+        <template #deviceInfo="{ model, field }">
+          {{ model[field] }}
+        </template>
+        <template #deviceType="{ model, field }">
+          <span
+            :class="['table-dot']"
+            :style="{
+              backgroundColor: formatDictPreColor(bizDictOptions.deviceType, model[field]),
+            }"
+          />
+          <span>{{ formatDictValue(bizDictOptions.deviceType, model[field]) }}</span>
+        </template>
+      </BasicForm>
     </div>
   </BasicModal>
 </template>
 <script lang="ts" setup>
-  import { ref, computed, unref } from 'vue';
+  import { onBeforeMount, ref, computed, reactive, 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';
-
+  import { listDictModelBatch } from '/@/api/common';
+  import { engineerBedAdd, engineerBedDetail, engineerBedEdit } from '@/api/biz/engineer/bedApi';
+  import {
+    formatDictColor,
+    formatDictValue,
+    formatDictPreColor,
+    formatDictFontColor,
+  } from '@/utils';
+  const bizDictData = ref([
+    { key: 'wardType', dictCode: 'pb_epidemic' },
+    { key: 'deviceType', dictCode: 'bm_det' },
+  ]);
+  const bizDictOptions = reactive<any>({});
+  // 组件加载前事件
+  onBeforeMount(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 emit = defineEmits(['success', 'register']);
-
-  const getTitle = computed(() => (!unref(isUpdate) ? '新增床位' : '编辑床位'));
-  const width = '600px';
   const isUpdate = ref(false);
+  const getTitle = computed(() => (!unref(isUpdate) ? '新增床位' : '绑定床位'));
+  const width = '600px';
   const rowId = ref();
 
   const { createMessage } = useMessage();
-  const [registerForm, { setFieldsValue, resetFields, validate }] = useForm({
+  const [registerForm, { setFieldsValue, resetFields, validate, updateSchema }] = useForm({
     labelWidth: 150,
     schemas: dataFormSchema,
     showActionButtonGroup: false,
@@ -45,22 +92,26 @@
     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,
+        name: resData.bedName,
+        wardInfo: resData.infectiousDiseases,
       });
-    } else {
-      await setFieldsValue({
-        cardNo: {
-          input: '',
-          dictValue: 'pb_card_sfz',
+      await updateSchema([
+        {
+          field: 'wardId',
+          componentProps: {
+            disabled: true,
+          },
+        },
+      ]);
+      await updateSchema({
+        field: 'name',
+        componentProps: {
+          disabled: true,
         },
       });
     }
@@ -71,16 +122,16 @@
     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('编辑成功!');
+      if (!isUpdate.value) {
+        await engineerBedAdd({ ...values });
+        createMessage.success('新增成功!');
+      } else {
+        await engineerBedEdit({ ...values, id: rowId.value });
+        console.log(values);
+        createMessage.success('绑定成功!');
+      }
       closeModal();
       emit('success', {
-        isUpdate: unref(isUpdate),
         values: { ...values, id: rowId.value },
       });
     } finally {
@@ -88,3 +139,12 @@
     }
   }
 </script>
+<style lang="less" scoped>
+  .table-dot {
+    display: inline-block;
+    width: 10px;
+    height: 10px;
+    margin-right: 6px;
+    border-radius: 50%;
+  }
+</style>

+ 161 - 0
src/views/biz/engineer/bed/changeFormModal.vue

@@ -0,0 +1,161 @@
+<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">
+        <template #infectiousDiseases="{ model, field }">
+          <div class="flex">
+            <div
+              v-for="item in model[field]"
+              :key="item"
+              :style="{
+                backgroundColor: formatDictColor(bizDictOptions.infectiousDiseases, item),
+                color: formatDictFontColor(bizDictOptions.infectiousDiseases, item),
+                padding: '1px 6px',
+                borderRadius: '2px',
+                marginRight: '4px',
+              }"
+            >
+              {{ formatDictValue(bizDictOptions.infectiousDiseases, item) }}
+            </div>
+          </div>
+        </template>
+        <template #wardName="{ model, field }">
+          {{ model[field] }}
+        </template>
+        <template #name="{ model, field }">
+          {{ model[field] }}
+        </template>
+        <template #deviceUniqueCode="{ model, field }">
+          {{ model[field] }}
+        </template>
+        <template #oldDeviceUniqueCode="{ model, field }">
+          {{ model[field] }}
+        </template>
+        <template #oldDeviceInfo="{ model, field }">
+          {{ model[field] }}
+        </template>
+        <template #oldDeviceRemark="{ model, field }">
+          {{ model[field] }}
+        </template>
+        <template #oldDeviceType="{ model, field }">
+          <span
+            :class="['table-dot']"
+            :style="{
+              backgroundColor: formatDictPreColor(bizDictOptions.deviceType, model[field]),
+            }"
+          />
+          <span>{{ formatDictValue(bizDictOptions.deviceType, model[field]) }}</span>
+        </template>
+
+        <template #deviceInfo="{ model, field }">
+          {{ model[field] }}
+        </template>
+
+        <template #deviceType="{ model, field }">
+          <span
+            :class="['table-dot']"
+            :style="{
+              backgroundColor: formatDictPreColor(bizDictOptions.deviceType, model[field]),
+            }"
+          />
+          <span>{{ formatDictValue(bizDictOptions.deviceType, model[field]) }}</span>
+        </template>
+      </BasicForm>
+    </div>
+  </BasicModal>
+</template>
+<script lang="ts" setup>
+  import { onBeforeMount, ref, computed, reactive } from 'vue';
+  import { BasicModal, useModalInner } from '/@/components/Modal';
+  import { BasicForm, useForm } from '/@/components/Form';
+  import { useMessage } from '/@/hooks/web/useMessage';
+  import { changeDataFormSchema } from './data';
+  import { listDictModelBatch } from '/@/api/common';
+  import { engineerBedDetail, engineerBedEdit } from '@/api/biz/engineer/bedApi';
+  import {
+    formatDictColor,
+    formatDictValue,
+    formatDictPreColor,
+    formatDictFontColor,
+  } from '@/utils';
+  const bizDictData = ref([
+    { key: 'infectiousDiseases', dictCode: 'pb_epidemic' },
+    { key: 'deviceType', dictCode: 'bm_det' },
+  ]);
+  const bizDictOptions = reactive<any>({});
+  // 组件加载前事件
+  onBeforeMount(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 emit = defineEmits(['success', 'register']);
+  const getTitle = computed(() => '更换设备');
+  const width = '900px';
+  const rowId = ref();
+
+  const { createMessage } = useMessage();
+  const [registerForm, { setFieldsValue, resetFields, validate }] = useForm({
+    labelWidth: 150,
+    schemas: changeDataFormSchema,
+    showActionButtonGroup: false,
+    baseColProps: {
+      span: 6,
+    },
+    wrapperCol: {
+      span: 22,
+    },
+  });
+  const [registerModal, { setModalProps, closeModal }] = useModalInner(async data => {
+    await resetFields();
+    setModalProps({ confirmLoading: false });
+    const resData = await engineerBedDetail(data.record.id);
+    rowId.value = resData.id;
+    await setFieldsValue({
+      wardId: resData.wardId,
+      wardName: resData.wardName,
+      infectiousDiseases: resData.infectiousDiseases,
+      name: resData.bedName,
+      oldDeviceUniqueCode: resData.deviceUniqueCode,
+      oldDeviceInfo: resData.deviceName + resData.deviceModel,
+      oldDeviceType: resData.deviceType,
+      oldDeviceRemark: resData.deviceRemark,
+    });
+  });
+
+  // 提交按钮事件
+  async function handleSubmit() {
+    try {
+      const values = await validate();
+      setModalProps({ confirmLoading: true });
+      await engineerBedEdit({ ...values, id: rowId.value });
+      console.log(values);
+      createMessage.success('设备更换成功!');
+      closeModal();
+      emit('success', {
+        values: { ...values, id: rowId.value },
+      });
+    } finally {
+      setModalProps({ confirmLoading: false });
+    }
+  }
+</script>
+<style lang="less" scoped>
+  .table-dot {
+    display: inline-block;
+    width: 10px;
+    height: 10px;
+    margin-right: 6px;
+    border-radius: 50%;
+  }
+</style>

+ 201 - 91
src/views/biz/engineer/bed/data.ts

@@ -1,8 +1,12 @@
 import { BasicColumn } from '@/components/Table';
 import { FormSchema } from '/@/components/Form';
-import { listDictModel } from '@/api/common';
+import {
+  engineerDilysisDeviceByBed,
+  engineerDialysisDeviceDetail,
+} from '@/api/biz/engineer/dialysisDeviceApi';
+import { getWardInfo, wardInfoById } from '/@/api/biz/management/wardInfo';
 
-export const columnsBed: BasicColumn[] = [
+export const bedInfoColumns: BasicColumn[] = [
   {
     title: '病区',
     dataIndex: 'wardName',
@@ -11,7 +15,7 @@ export const columnsBed: BasicColumn[] = [
   },
   {
     title: '病区属性',
-    dataIndex: 'wardPropertyName',
+    dataIndex: 'infectiousDiseases',
     align: 'left',
     width: 90,
   },
@@ -22,7 +26,7 @@ export const columnsBed: BasicColumn[] = [
   },
   {
     title: '设备编号',
-    dataIndex: 'deviceId',
+    dataIndex: 'deviceUniqueCode',
     align: 'left',
   },
   {
@@ -32,15 +36,15 @@ export const columnsBed: BasicColumn[] = [
   },
 ];
 
-export const columnsDevice: BasicColumn[] = [
+export const recordingColumns: BasicColumn[] = [
   {
     title: '操作时间',
-    dataIndex: 'createTime',
+    dataIndex: 'updateTime',
     align: 'left',
   },
   {
     title: '操作类型',
-    dataIndex: 'bindDevice',
+    dataIndex: 'operationType',
     align: 'left',
   },
   {
@@ -50,145 +54,251 @@ export const columnsDevice: BasicColumn[] = [
   },
   {
     title: '设备编号',
-    dataIndex: 'deviceId',
+    dataIndex: 'deviceUniqueCode',
     align: 'left',
   },
   {
     title: '设备备注',
-    dataIndex: 'remark',
+    dataIndex: 'deviceRemark',
     align: 'left',
   },
 ];
-
-// 表单新增编辑
 export const dataFormSchema: FormSchema[] = [
   {
-    field: 'PlainTitle',
+    label: '新增床位',
+    field: 'addBed',
     component: 'PlainTitle',
     defaultValue: '新增床位',
-    colProps: {
-      span: 24,
-    },
   },
   {
-    label: '病区属性',
-    field: 'wardPropertyName',
+    label: '病区',
+    field: 'wardId',
     required: true,
     component: 'ApiSelect',
-    componentProps: {
-      api: listDictModel,
-      placeholder: '请选择病区属性',
-      params: {
-        dictCode: 'pb_blood',
-      },
+    componentProps: ({ formModel }) => {
+      return {
+        api: getWardInfo,
+        labelField: 'name',
+        valueField: 'id',
+        resultField: 'data',
+        getPopupContainer: () => document.body,
+        onChange: async e => {
+          const resData = await wardInfoById(e);
+          formModel['wardInfo'] = resData?.infectiousDiseases;
+        },
+      };
+    },
+    colProps: {
+      span: 12,
     },
   },
   {
-    label: '病区',
-    field: 'wardId',
-    required: true,
-    component: 'ApiSelect',
+    label: '病区属性',
+    field: 'wardInfo',
+    component: 'Input',
+    slot: 'wardInfo',
     componentProps: {
-      api: listDictModel,
-      placeholder: '请选择病区',
-      params: {
-        dictCode: 'pb_blood',
-      },
+      placeholder: '请输入病区属性',
+    },
+    colProps: {
+      span: 12,
     },
   },
   {
     label: '床位',
-    field: 'bedName',
+    field: 'name',
     required: true,
     component: 'Input',
     componentProps: {
       placeholder: '请输入床位',
-      maxLength: 60,
     },
   },
   {
-    field: 'PlainTitle1',
+    label: '绑定设备',
+    field: 'devices',
     component: 'PlainTitle',
     defaultValue: '绑定设备',
+  },
+  {
+    label: '设备编号',
+    field: 'deviceId',
+    component: 'ApiSelect',
+    componentProps: ({ formModel }) => {
+      return {
+        api: engineerDilysisDeviceByBed,
+        labelField: 'uniqueCode',
+        valueField: 'id',
+        resultField: 'data',
+        getPopupContainer: () => document.body,
+        onChange: async e => {
+          const resData = await engineerDialysisDeviceDetail(e);
+          formModel['deviceInfo'] = resData?.name;
+          formModel['deviceType'] = resData?.deviceType;
+        },
+      };
+    },
+  },
+  {
+    label: '设备信息',
+    field: 'deviceInfo',
+    component: 'Input',
+    slot: 'deviceInfo',
+    componentProps: {
+      placeholder: '请输入设备信息',
+    },
     colProps: {
-      span: 24,
+      span: 12,
     },
   },
+  {
+    label: '设备类型',
+    field: 'deviceType',
+    component: 'Input',
+    slot: 'deviceType',
+    componentProps: {
+      placeholder: '请输入设备类型',
+    },
+    colProps: {
+      span: 12,
+    },
+  },
+  {
+    label: '绑定设备备注',
+    field: 'deviceRemark',
+    component: 'InputTextArea',
+    componentProps: {
+      placeholder: '请输入绑定设备备注',
+    },
+  },
+];
+
+export const changeDataFormSchema: FormSchema[] = [
+  // 床位信息
+  {
+    label: '床位信息',
+    field: 'bedInfo',
+    component: 'PlainTitle',
+    defaultValue: '床位信息',
+  },
+  {
+    label: '病区Id',
+    field: 'wardId',
+    component: 'Input',
+    slot: 'wardId',
+    show: false,
+  },
+  {
+    label: '病区',
+    field: 'wardName',
+    component: 'Input',
+    slot: 'wardName',
+  },
+  {
+    label: '病区属性',
+    field: 'infectiousDiseases',
+    component: 'Input',
+    slot: 'infectiousDiseases',
+  },
+  {
+    label: '床位',
+    field: 'name',
+    component: 'Input',
+    slot: 'name',
+  },
+  // 原绑定设备信息
+  {
+    label: '原绑定设备',
+    field: 'oldDevice',
+    component: 'PlainTitle',
+    defaultValue: '原绑定设备',
+  },
+  {
+    label: '设备编号',
+    field: 'oldDeviceUniqueCode',
+    component: 'Input',
+    slot: 'oldDeviceUniqueCode',
+  },
+  {
+    label: '设备信息',
+    field: 'oldDeviceInfo',
+    component: 'Input',
+    slot: 'oldDeviceInfo',
+  },
+  {
+    label: '设备类型',
+    field: 'oldDeviceType',
+    component: 'Input',
+    slot: 'oldDeviceType',
+  },
+  {
+    label: '备注',
+    field: 'oldDeviceRemark',
+    component: 'Input',
+    slot: 'oldDeviceRemark',
+  },
+
+  // 换绑信息
+  {
+    label: '换绑信息',
+    field: 'changeDevice',
+    component: 'PlainTitle',
+    defaultValue: '换绑信息',
+  },
   {
     label: '设备编号',
     field: 'deviceId',
-    required: true,
     component: 'ApiSelect',
-    componentProps: {
-      api: listDictModel,
-      placeholder: '请选择设备编号',
+    componentProps: ({ formModel }) => {
+      return {
+        api: engineerDilysisDeviceByBed,
+        labelField: 'uniqueCode',
+        valueField: 'id',
+        resultField: 'data',
+        getPopupContainer: () => document.body,
+        onChange: async e => {
+          const resData = await engineerDialysisDeviceDetail(e);
+          formModel['deviceInfo'] = resData?.name;
+          formModel['deviceType'] = resData?.deviceType;
+        },
+      };
+    },
+    colProps: {
+      span: 12,
     },
   },
   {
     label: '设备信息',
-    field: 'deviceName',
-    component: 'PlainText',
+    field: 'deviceInfo',
+    component: 'Input',
+    slot: 'deviceInfo',
+    componentProps: {
+      placeholder: '请输入设备信息',
+    },
     colProps: {
       span: 12,
     },
   },
   {
     label: '设备类型',
-    field: 'deviceModel',
-    component: 'PlainText',
+    field: 'deviceType',
+    component: 'Input',
+    slot: 'deviceType',
+    componentProps: {
+      placeholder: '请输入设备类型',
+    },
     colProps: {
       span: 12,
     },
   },
   {
-    label: '设备备注',
-    field: 'remark',
-    required: true,
-    component: 'Input',
+    label: '绑定设备备注',
+    field: 'deviceRemark',
+    component: 'InputTextArea',
     componentProps: {
-      placeholder: '请输入设备备注',
+      placeholder: '请输入绑定设备备注',
+    },
+    colProps: {
+      span: 24,
     },
   },
 ];
-
-// 床位信息搜索
-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,
-    },
-  ],
-};

+ 316 - 3
src/views/biz/engineer/bed/index.vue

@@ -1,7 +1,320 @@
 <template>
-  <div> 占位符 </div>
+  <SimpleCard title="床位管理">
+    <template #headRight>
+      <Button type="primary" @click="handleCreate"
+        ><Icon icon="icon-xt-add_default|iconfont" :size="12" /> 新增床位
+      </Button>
+    </template>
+    <template #body>
+      <Row>
+        <Col :span="12">
+          <SimpleCard title="床位信息">
+            <template #headRight>
+              <div> <XTForm :form-data="formData" /> </div>
+            </template>
+            <template #body>
+              <BasicTable @register="bedInfoRegister">
+                <template #bodyCell="{ column, record }">
+                  <template v-if="column.key === 'infectiousDiseases'">
+                    <div class="flex">
+                      <div
+                        v-for="item in record.infectiousDiseases"
+                        :key="item"
+                        :style="{
+                          backgroundColor: formatDictColor(bizDictOptions.wardType, item),
+                          color: formatDictFontColor(bizDictOptions.wardType, item),
+                          padding: '1px 6px',
+                          borderRadius: '2px',
+                          marginRight: '4px',
+                        }"
+                      >
+                        {{ formatDictValue(bizDictOptions.wardType, item) }}
+                      </div>
+                    </div>
+                  </template>
+                  <template v-if="column.key === 'action'">
+                    <TableAction
+                      :actions="[
+                        {
+                          label: '更换',
+                          tooltip: '更换',
+                          disabled: !record.deviceId,
+                          onClick: handleChangeDeivice.bind(null, record, column),
+                        },
+                        {
+                          ifShow: record?.deviceId != null,
+                          label: '解绑',
+                          tooltip: '解绑',
+                          popConfirm: {
+                            title: '是否确定' + (record.deviceId ? '解绑' : '绑定') + '此设备?',
+                            placement: 'left',
+                            confirm: handleBinding.bind(null, record, column),
+                          },
+                        },
+                        {
+                          ifShow: record?.deviceId == null,
+                          label: '绑定',
+                          tooltip: '绑定',
+                          onClick: handleBinding.bind(null, record, column),
+                        },
+                        {
+                          label: '删除',
+                          tooltip: '删除',
+                          popConfirm: {
+                            title: '是否取消删除?',
+                            placement: 'left',
+                            confirm: handleDelete.bind(null, record, column),
+                          },
+                        },
+                      ]"
+                    />
+                  </template>
+                </template>
+              </BasicTable>
+            </template>
+          </SimpleCard>
+        </Col>
+        <Col :span="12">
+          <SimpleCard title="设备更换记录">
+            <template #headRight>
+              <div> <XTForm :form-Data="recordingFormData" /> </div>
+            </template>
+            <template #body>
+              <BasicTable @register="recordingRegister">
+                <template #bodyCell="{ column, record }">
+                  <template v-if="column.key === 'operationType'">
+                    <span>{{
+                      formatDictValue(bizDictOptions.operationType, record.operationType)
+                    }}</span>
+                  </template>
+                  <template v-if="column.key === 'deviceType'">
+                    <span
+                      :class="['table-dot']"
+                      :style="{
+                        backgroundColor: formatDictPreColor(
+                          bizDictOptions.deviceType,
+                          record.deviceType,
+                        ),
+                      }"
+                    />
+                    <span>{{ formatDictValue(bizDictOptions.deviceType, record.deviceType) }}</span>
+                  </template>
+                </template>
+              </BasicTable>
+            </template>
+          </SimpleCard>
+        </Col>
+      </Row>
+    </template>
+  </SimpleCard>
+  <FormModal @register="registerModal" @success="callSuccess" />
+  <ChangeFormModal @register="registerChangeModal" @success="callSuccess" />
 </template>
+<script setup lang="ts">
+  import { onBeforeMount, ref, reactive } from 'vue';
+  import { BasicTable, useTable, TableAction } from '@/components/Table';
+  import { Row, Col, Button } from 'ant-design-vue';
+  import { useMessage } from '/@/hooks/web/useMessage';
+  import { bedInfoColumns, recordingColumns } from './data';
+  import FormModal from './formModal.vue';
+  import ChangeFormModal from './changeFormModal.vue';
+  import { useModal } from '/@/components/Modal';
+  import { SimpleCard } from '@/components/XTCard/index';
+  import { XTForm } from '/@/components/XTForm/index';
+  import Icon from '/@/components/Icon/src/Icon.vue';
+  import {
+    engineerBedQueryPage,
+    bedDeviceReplacePageQuery,
+    engineerBedRemove,
+    engineerBedDetail,
+    engineerBedEdit,
+  } from '/@/api/biz/engineer/bedApi';
+  import {
+    formatDictColor,
+    formatDictValue,
+    formatDictFontColor,
+    formatDictPreColor,
+  } from '@/utils';
+  import { listDictModelBatch } from '/@/api/common';
+  import dayjs from 'dayjs';
+  const [registerModal, { openModal }] = useModal();
+  const [registerChangeModal, { openModal: openChangeFormModal }] = useModal();
+  const { createMessage } = useMessage();
+  const bizDictData = ref([
+    { key: 'wardType', dictCode: 'pb_epidemic' },
+    { key: 'operationType', dictCode: 'beot' },
+    { key: 'deviceType', dictCode: 'bm_det' },
+  ]);
+  const bizDictOptions = reactive<any>({});
+  // 组件加载前事件
+  onBeforeMount(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 [bedInfoRegister, { reload }] = useTable({
+    api: engineerBedQueryPage,
+    rowKey: 'id',
+    columns: bedInfoColumns,
+    showIndexColumn: false,
+    striped: false,
+    formConfig: {
+      labelWidth: 120,
+      autoSubmitOnEnter: true,
+      baseColProps: { xs: 24, sm: 12, md: 12, lg: 8 },
+      resetButtonOptions: {
+        preIcon: 'icon-delete|iconfont',
+      },
+      submitButtonOptions: {
+        preIcon: 'icon-search|iconfont',
+      },
+    },
+    useSearchForm: false,
+    actionColumn: {
+      width: 150,
+      title: '操作',
+      dataIndex: 'action',
+    },
+    beforeFetch: handleBeforeFetch,
+  });
+  // 设备更换记录列表
+  const [recordingRegister, { reload: reloadRecording }] = useTable({
+    api: bedDeviceReplacePageQuery,
+    rowKey: 'id',
+    columns: recordingColumns,
+    showIndexColumn: false,
+    striped: false,
+    formConfig: {
+      labelWidth: 120,
+      autoSubmitOnEnter: true,
+      baseColProps: { xs: 24, sm: 12, md: 12, lg: 8 },
+      resetButtonOptions: {
+        preIcon: 'icon-delete|iconfont',
+      },
+      submitButtonOptions: {
+        preIcon: 'icon-search|iconfont',
+      },
+    },
+    useSearchForm: false,
+    beforeFetch: handlebedDevuceBeforeFetch,
+  });
+  // 床位信息标头条件查询
+  const formData = [
+    {
+      name: 'text22',
+      componentType: 'Select',
+      placeholder: '请选择',
+      width: 150,
+      defaultValue: '1',
+      dicts: [
+        { label: '第一班', value: '1' },
+        { label: '第二班', value: '2' },
+        { label: '第三班', value: '3' },
+      ],
+    },
+    {
+      name: 'text22',
+      componentType: 'Select',
+      placeholder: '请选择',
+      width: 150,
+      defaultValue: '1',
+      dicts: [
+        { label: '第一班', value: '1' },
+        { label: '第二班', value: '2' },
+        { label: '第三班', value: '3' },
+      ],
+    },
+  ];
 
-<script setup lang="ts"></script>
+  const recordingFormData = [
+    {
+      name: 'createTime',
+      componentType: 'RangePicker',
+      placeholder: '请选择',
+      format: 'YYYY-MM-DD',
+      valueFormat: 'YYYY-MM-DD',
+      defaultValue: [
+        dayjs().subtract(3, 'month').format('YYYY-MM-DD'),
+        dayjs().format('YYYY-MM-DD'),
+      ],
+    },
+    {
+      name: 'searchNames',
+      componentType: 'Input',
+      prefix: 'icon-xt-search',
+      placeholder: '请输入设备编号',
+      width: 240,
+    },
+  ];
 
-<style lang="less" scoped></style>
+  // 请求床位信息前事件
+  function handleBeforeFetch(params) {
+    return { ...params };
+  }
+  // 请求更换记录列表前事件
+  function handlebedDevuceBeforeFetch(params) {
+    return { ...params, orders: [{ field: 'updateTime', direction: 'DESC' }] };
+  }
+  // 删除床位信息
+  async function handleDelete(record) {
+    if (record) {
+      await engineerBedRemove([record.id]);
+      await reload();
+      await reloadRecording();
+    }
+  }
+  // 新增床位信息
+  function handleCreate() {
+    openModal(true, {});
+  }
+  // 新增返回刷新
+  function callSuccess() {
+    reload();
+    reloadRecording();
+  }
+
+  // 绑定设备、解绑方法
+  async function handleBinding(record) {
+    if (record && record.id) {
+      const detailRes = await engineerBedDetail(record.id);
+      if (detailRes && detailRes.deviceId) {
+        detailRes.deviceId = undefined;
+        const editParams = {
+          id: detailRes.id,
+          wardId: detailRes.wardId,
+          name: detailRes.bedName,
+          deviceId: null,
+        };
+        await engineerBedEdit(editParams);
+        createMessage.success('设备解绑成功');
+        await reload();
+        await reloadRecording();
+      } else {
+        openModal(true, {
+          isUpdate: true,
+          record,
+        });
+      }
+    }
+  }
+  // 更换设备方法
+  function handleChangeDeivice(record) {
+    console.log('record:::::::', record);
+    openChangeFormModal(true, {
+      record,
+    });
+  }
+</script>
+
+<style lang="less" scoped>
+  .table-dot {
+    display: inline-block;
+    width: 10px;
+    height: 10px;
+    margin-right: 6px;
+    border-radius: 50%;
+  }
+</style>

+ 449 - 0
src/views/biz/engineer/bio/chemicalPollutantDetection/data.ts

@@ -0,0 +1,449 @@
+import { BasicColumn, FormSchema } from '/@/components/Table';
+import { sysUserQueryPage } from '/@/api/sys/sysUserApi';
+import { waterList, waterDetail } from '/@/api/biz/engineer/waterApi';
+import dayjs from 'dayjs';
+export const columns: BasicColumn[] = [
+  {
+    title: '设备编号',
+    dataIndex: 'deviceUniqueCode',
+    width: 120,
+  },
+  {
+    title: '设备信息',
+    dataIndex: 'deviceInfo',
+    width: 160,
+  },
+  {
+    title: '检测时间',
+    dataIndex: 'detectionTime',
+    width: 160,
+  },
+  {
+    title: '透析用水中的有毒化学物质',
+    dataIndex: 'toxicChemicals',
+    children: [
+      {
+        title: '铝(mg/L) 正常值≤0.01',
+        dataIndex: 'aluminum',
+        width: 120,
+      },
+      {
+        title: '总氯(mg/L) 正常值≤0.1',
+        dataIndex: 'totalChlorine',
+        width: 120,
+      },
+      {
+        title: '铜(mg/L) 正常值≤0.01',
+        dataIndex: 'copper',
+        width: 120,
+      },
+      {
+        title: '氟化物 (mg/L) 正常值≤0.2',
+        dataIndex: 'fluoride',
+        width: 120,
+      },
+      {
+        title: '铅(mg/L) 正常值≤ 0.005',
+        dataIndex: 'lead',
+        width: 120,
+      },
+      {
+        title: '硝酸盐(氮)(mg/L)正常值≤2',
+        dataIndex: 'nitrate',
+        width: 130,
+      },
+      {
+        title: '硫酸盐 (mg/L) 正常值≤100',
+        dataIndex: 'sulfate',
+        width: 120,
+      },
+      {
+        title: '锌(mg/L) 正常值≤0.1',
+        dataIndex: 'zinc',
+        width: 120,
+      },
+    ],
+  },
+  {
+    title: '透析溶液中的电解质',
+    dataIndex: 'electrolyte',
+    children: [
+      {
+        title: '钙(mmol/L) 正常值≤2 (0.05mmol/L)',
+        dataIndex: 'calcium',
+        width: 120,
+      },
+      {
+        title: '镁(mmol/L) 正常值≤4 (0.15mmol/L)',
+        dataIndex: 'magnesium',
+        width: 120,
+      },
+      {
+        title: '钾(mmol/L) 正常值≤8 (0.2mmol/L)',
+        dataIndex: 'potassium',
+        width: 120,
+      },
+      {
+        title: '钠(mmol/L) 正常值≤70 (3.0mmol/L)',
+        dataIndex: 'sodium',
+        width: 120,
+      },
+    ],
+  },
+  {
+    title: '透析用水中的微量元素',
+    dataIndex: 'traceElement',
+    children: [
+      {
+        title: '锑(mg/L) 正常值≤0.006',
+        dataIndex: 'antimony',
+        width: 120,
+      },
+      {
+        title: '砷(mg/L) 正常值≤0.005',
+        dataIndex: 'arsenic',
+        width: 120,
+      },
+      {
+        title: '钡(mg/L) 正常值≤0.1',
+        dataIndex: 'barium',
+        width: 120,
+      },
+      {
+        title: '铍(mg/L) 正常值≤0.0004',
+        dataIndex: 'beryllium',
+        width: 120,
+      },
+      {
+        title: '镉(mg/L) 正常值≤0.001',
+        dataIndex: 'cadmium',
+        width: 120,
+      },
+      {
+        title: '铬(mg/L) 正常值≤0.014',
+        dataIndex: 'chromium',
+        width: 120,
+      },
+      {
+        title: '汞(mg/L) 正常值≤0.0002',
+        dataIndex: 'mercury',
+        width: 120,
+      },
+      {
+        title: '硒(mg/L) 正常值≤0.09',
+        dataIndex: 'selenium',
+        width: 120,
+      },
+      {
+        title: '银(mg/L) 正常值≤0.005',
+        dataIndex: 'silver',
+        width: 120,
+      },
+      {
+        title: '铊(mg/L) 正常值≤0.02',
+        dataIndex: 'thallium',
+        width: 120,
+      },
+    ],
+  },
+];
+// 表单新增编辑
+export const dataFormSchema: FormSchema[] = [
+  {
+    label: '设备编号搜索',
+    field: 'uniqueCodes',
+    component: 'Input',
+    ifShow: false,
+  },
+  {
+    field: 'selDervice',
+    component: 'PlainTitle',
+    defaultValue: '选择设备',
+  },
+  {
+    label: '设备编号',
+    field: 'deviceId',
+    required: true,
+    component: 'ApiSelect',
+    componentProps: ({ formModel }) => {
+      return {
+        api: waterList,
+        params: {
+          uniqueCode: formModel.uniqueCodes,
+        },
+        labelField: 'uniqueCode',
+        valueField: 'id',
+        resultField: 'data',
+        placeholder: '请选择设备编号',
+        getPopupContainer: () => document.body,
+        showSearch: true,
+        filterOption: false,
+        onSearch: e => {
+          formModel.uniqueCodes = e;
+        },
+        onChange: async e => {
+          const resData = await waterDetail(e);
+          formModel['deviceManufacturer'] = resData?.manufacturer;
+          formModel['deviceModel'] = resData?.model;
+        },
+      };
+    },
+  },
+  {
+    label: '设备厂家',
+    field: 'deviceManufacturer',
+    required: true,
+    component: 'Input',
+    slot: 'deviceManufacturer',
+    componentProps: {
+      disabled: true,
+    },
+  },
+  {
+    label: '设备型号',
+    field: 'deviceModel',
+    required: true,
+    component: 'Input',
+    slot: 'deviceModel',
+    componentProps: {
+      disabled: true,
+    },
+  },
+  {
+    field: 'deteRecords',
+    component: 'PlainTitle',
+    defaultValue: '检测记录',
+  },
+  {
+    label: '检测时间',
+    field: 'detectionTime',
+    component: 'DatePicker',
+    componentProps: () => {
+      return {
+        placeholder: '请选择检测时间',
+        getPopupContainer: () => document.body,
+        valueFormat: 'YYYY-MM-DD HH:mm:ss',
+        showTime: true,
+      };
+    },
+    defaultValue: dayjs().format('YYYY-MM-DD HH:mm:ss'),
+  },
+  {
+    label: '检测人',
+    field: 'inspectorId',
+    required: true,
+    component: 'ApiSelect',
+    componentProps: {
+      api: sysUserQueryPage,
+      params: {
+        pageNum: 1,
+        pageSize: 999,
+        disable: '0',
+      },
+      mode: 'single',
+      labelField: 'nickname',
+      valueField: 'id',
+      resultField: 'data',
+      placeholder: '请选择检测人',
+    },
+  },
+  {
+    label: '铝(mg/L)',
+    field: 'to_aluminum',
+    required: true,
+    component: 'InputNumber',
+    componentProps: {
+      placeholder: '请输入氟化物(mg/L)',
+    },
+  },
+  {
+    label: '总氯(mg/L)',
+    field: 'to_totalChlorine',
+    required: true,
+    component: 'InputNumber',
+    componentProps: {
+      placeholder: '请输入总氯(mg/L)',
+    },
+  },
+  {
+    label: '铜(mg/L)',
+    field: 'to_copper',
+    required: true,
+    component: 'InputNumber',
+    componentProps: {
+      placeholder: '请输入铜(mg/L)',
+    },
+  },
+  {
+    label: '氟化物(mg/L)',
+    field: 'to_fluoride',
+    required: true,
+    component: 'InputNumber',
+    componentProps: {
+      placeholder: '请输入氟化物(mg/L)',
+    },
+  },
+  {
+    label: '铅(mg/L)',
+    field: 'to_lead',
+    required: true,
+    component: 'InputNumber',
+    componentProps: {
+      placeholder: '请输入铅(mg/L)',
+    },
+  },
+
+  {
+    label: '硝酸盐(氮)(mg/L)',
+    field: 'to_nitrate',
+    required: true,
+    component: 'InputNumber',
+    componentProps: {
+      placeholder: '请输入硝酸盐(氮)(mg/L)',
+    },
+  },
+  {
+    label: '锌(mg/L)',
+    field: 'to_zinc',
+    required: true,
+    component: 'InputNumber',
+    componentProps: {
+      placeholder: '请输入锌(mg/L)',
+    },
+  },
+  {
+    label: '硫酸盐(mg/L)',
+    field: 'to_sulfate',
+    required: true,
+    component: 'InputNumber',
+    componentProps: {
+      placeholder: '请输入硫酸盐(mg/L)',
+    },
+  },
+  {
+    label: '钙(mmol/L)',
+    field: 'el_calcium',
+    required: true,
+    component: 'InputNumber',
+    componentProps: {
+      placeholder: '请输入钙(mmol/L)',
+    },
+  },
+  {
+    label: '镁(mmol/L)',
+    field: 'el_magnesium',
+    required: true,
+    component: 'InputNumber',
+    componentProps: {
+      placeholder: '请输入镁(mmol/L)',
+    },
+  },
+  {
+    label: '钠(mmol/L)',
+    field: 'el_sodium',
+    required: true,
+    component: 'InputNumber',
+    componentProps: {
+      placeholder: '请输入钠(mmol/L)',
+    },
+  },
+  {
+    label: '钾(mmol/L)',
+    field: 'el_potassium',
+    required: true,
+    component: 'InputNumber',
+    componentProps: {
+      placeholder: '请输入钾(mmol/L)',
+    },
+  },
+  {
+    label: '砷(mg/L)',
+    field: 'tr_arsenic',
+    required: true,
+    component: 'InputNumber',
+    componentProps: {
+      placeholder: '请输入砷(mg/L)',
+    },
+  },
+  {
+    label: '锑(mg/L)',
+    field: 'tr_antimony',
+    required: true,
+    component: 'InputNumber',
+    componentProps: {
+      placeholder: '请输入锑(mg/L)',
+    },
+  },
+  {
+    label: '铍(mg/L)',
+    field: 'tr_beryllium',
+    required: true,
+    component: 'InputNumber',
+    componentProps: {
+      placeholder: '请输入铍(mg/L)',
+    },
+  },
+  {
+    label: '钡(mg/L)',
+    field: 'tr_barium',
+    required: true,
+    component: 'InputNumber',
+    componentProps: {
+      placeholder: '请输入钡(mg/L)',
+    },
+  },
+  {
+    label: '铬(mg/L)',
+    field: 'tr_chromium',
+    required: true,
+    component: 'InputNumber',
+    componentProps: {
+      placeholder: '请输入铬(mg/L)',
+    },
+  },
+  {
+    label: '镉(mg/L)',
+    field: 'tr_cadmium',
+    required: true,
+    component: 'InputNumber',
+    componentProps: {
+      placeholder: '请输入镉(mg/L)',
+    },
+  },
+  {
+    label: '硒(mg/L)',
+    field: 'tr_selenium',
+    required: true,
+    component: 'InputNumber',
+    componentProps: {
+      placeholder: '请输入硒(mg/L)',
+    },
+  },
+  {
+    label: '汞(mg/L)',
+    field: 'tr_mercury',
+    required: true,
+    component: 'InputNumber',
+    componentProps: {
+      placeholder: '请输入汞(mg/L)',
+    },
+  },
+  {
+    label: '银(mg/L)',
+    field: 'tr_silver',
+    required: true,
+    component: 'InputNumber',
+    componentProps: {
+      placeholder: '请输入银(mg/L)',
+    },
+  },
+  {
+    label: '铊(mg/L)',
+    field: 'tr_thallium',
+    required: true,
+    component: 'InputNumber',
+    componentProps: {
+      placeholder: '请输入铊(mg/L)',
+    },
+  },
+];

+ 89 - 0
src/views/biz/engineer/bio/chemicalPollutantDetection/formDrawer.vue

@@ -0,0 +1,89 @@
+template<template>
+  <BasicDrawer
+    v-bind="$attrs"
+    destroyOnClose
+    @register="registerDrawer"
+    :title="getTitle"
+    :width="width"
+    @ok="handleSubmit"
+    :showFooter="true"
+    okText="确定"
+  >
+    <BasicForm @register="registerForm" layout="vertical" class="!px-6 !pt-2">
+      <template #deviceManufacturer="{ model, field }">
+        <span>&nbsp;&nbsp;&nbsp;&nbsp;{{ model[field] }}</span>
+      </template>
+      <template #deviceModel="{ model, field }">
+        <span>&nbsp;&nbsp;&nbsp;&nbsp;{{ model[field] }}</span>
+      </template>
+    </BasicForm>
+  </BasicDrawer>
+</template>
+<script lang="ts" setup>
+  import { ref } from 'vue';
+  import { BasicDrawer, useDrawerInner } from '/@/components/Drawer';
+  import { BasicForm, useForm } from '/@/components/Form';
+  import { dataFormSchema } from './data';
+  import { chemicalPollutantDetectionAdd } from '/@/api/biz/bio/chemicalPollutantDetectionApi';
+  const emit = defineEmits(['success', 'register']);
+  const getTitle = ref(`筛选条件`);
+  const width = '35%';
+  const [registerForm, { resetFields, validate }] = useForm({
+    schemas: dataFormSchema,
+    showActionButtonGroup: false,
+    baseColProps: {
+      span: 12,
+    },
+  });
+  const [registerDrawer, { setDrawerProps, closeDrawer }] = useDrawerInner(async data => {
+    await resetFields();
+    setDrawerProps({ confirmLoading: false });
+    console.log('data::::::::::', data);
+  });
+
+  // 提交按钮事件
+  async function handleSubmit() {
+    try {
+      const values = await validate();
+      setDrawerProps({ confirmLoading: true });
+      const toxicChemicals = {};
+      const electrolyte = {};
+      const traceElement = {};
+      Object.keys(values).forEach(key => {
+        if (key.indexOf('_') !== -1) {
+          const vals = key.split('_');
+          switch (vals[0]) {
+            case 'to':
+              toxicChemicals[vals[1]] = values[key];
+              delete values[key];
+              break;
+            case 'el':
+              electrolyte[vals[1]] = values[key];
+              delete values[key];
+              break;
+            case 'tr':
+              traceElement[vals[1]] = values[key];
+              delete values[key];
+              break;
+          }
+        }
+      });
+      values.toxicChemicals = toxicChemicals;
+      values.electrolyte = electrolyte;
+      values.traceElement = traceElement;
+      console.log('values:::::::::', values);
+      await chemicalPollutantDetectionAdd(values);
+      closeDrawer();
+      emit('success', values);
+    } finally {
+      setDrawerProps({ confirmLoading: false });
+    }
+  }
+</script>
+<style lang="less" scoped>
+  ::v-deep(.ant-picker) {
+    border-top: 0;
+    border-left: 0;
+    border-right: 0;
+  }
+</style>

+ 283 - 0
src/views/biz/engineer/bio/chemicalPollutantDetection/index.vue

@@ -0,0 +1,283 @@
+<template>
+  <div class="m-4 modals">
+    <div>
+      <div class="flex items-center justify-between my-4">
+        <div>
+          <a-button type="primary" size="large" class="btn-add" @click="handleCreate"
+            ><template #icon> <Icon icon="icon-xt-add_default|iconfont" /> </template
+            >新增检测</a-button
+          >
+        </div>
+        <div>
+          <XTForm :form-data="formData" @change="callForm" />
+        </div>
+      </div>
+    </div>
+    <BasicTable @register="registerTable">
+      <template #headerCell="{ column }">
+        <template v-if="column.key === 'deviceUniqueCode'"> 设备编号 </template>
+        <template v-if="column.key === 'deviceInfo'"> 设备信息 </template>
+        <template v-if="column.key === 'detectionTime'"> 检测时间 </template>
+        <template v-if="column.key === 'toxicChemicals'"> 透析用水中的有毒化学物质 </template>
+        <template v-if="column.key === 'aluminum'">铝(mg/L)<br />正常值≤0.01</template>
+        <template v-if="column.key === 'totalChlorine'"> 总氯(mg/L)<br />正常值≤0.1 </template>
+        <template v-if="column.key === 'copper'"> 铜(mg/L)<br />正常值≤0.01 </template>
+        <template v-if="column.key === 'fluoride'"> 氟化物 (mg/L)<br />正常值≤0.2 </template>
+        <template v-if="column.key === 'lead'"> 铅(mg/L)<br />正常值≤0.005 </template>
+        <template v-if="column.key === 'nitrate'">
+          硝酸盐(氮)<br />
+          (mg/L)正常值≤2</template
+        >
+        <template v-if="column.key === 'sulfate'">
+          硫酸盐 (mg/L)<br />
+          正常值≤100</template
+        >
+        <template v-if="column.key === 'zinc'"> 锌(mg/L)<br />正常值≤0.1 </template>
+        <template v-if="column.key === 'electrolyte'"> 透析溶液中的电解质 </template>
+        <template v-if="column.key === 'calcium'">
+          钙(mmol/L)<br />正常值≤2<br />(0.05mmol/L)
+        </template>
+        <template v-if="column.key === 'magnesium'">
+          镁(mmol/L)<br />正常值≤4<br />(0.15mmol/L)
+        </template>
+        <template v-if="column.key === 'potassium'">
+          钾(mmol/L)<br />正常值≤8<br />(0.2mmol/L)
+        </template>
+        <template v-if="column.key === 'sodium'">
+          钠(mmol/L)<br />正常值≤70<br />(3.0mmol/L)
+        </template>
+        <template v-if="column.key === 'traceElement'"> 透析用水中的微量元素 </template>
+        <template v-if="column.key === 'antimony'"> 锑(mg/L)<br />正常值≤0.006 </template>
+        <template v-if="column.key === 'arsenic'">砷(mg/L)<br />正常值≤0.005</template>
+        <template v-if="column.key === 'barium'"> 钡(mg/L)<br />正常值≤0.1 </template>
+        <template v-if="column.key === 'beryllium'"> 铍(mg/L)<br />正常值≤0.0004 </template>
+        <template v-if="column.key === 'cadmium'"> 镉(mg/L)<br />正常值≤0.001 </template>
+        <template v-if="column.key === 'chromium'"> 铬(mg/L)<br />正常值≤0.014 </template>
+        <template v-if="column.key === 'mercury'"> 汞(mg/L)<br />正常值≤0.0002 </template>
+        <template v-if="column.key === 'selenium'"> 硒(mg/L)<br />正常值≤0.09 </template>
+        <template v-if="column.key === 'silver'"> 银(mg/L)<br />正常值≤0.005 </template>
+        <template v-if="column.key === 'thallium'"> 铊(mg/L)<br />正常值≤0.02 </template>
+        <template v-if="column.key === 'action'"> 操作 </template>
+      </template>
+      <template #bodyCell="{ column, record }">
+        <template v-if="column.key === 'deviceInfo'">{{
+          record.deviceName + '(' + record.deviceModel + ')'
+        }}</template>
+        <template v-if="column.key === 'aluminum'">{{ record.toxicChemicals.aluminum }}</template>
+        <template v-if="column.key === 'totalChlorine'">{{
+          record.toxicChemicals.totalChlorine
+        }}</template>
+        <template v-if="column.key === 'copper'">{{ record.toxicChemicals.copper }}</template>
+        <template v-if="column.key === 'fluoride'">{{ record.toxicChemicals.fluoride }}</template>
+        <template v-if="column.key === 'lead'">{{ record.toxicChemicals.lead }}</template>
+        <template v-if="column.key === 'nitrate'">{{ record.toxicChemicals.nitrate }}</template>
+        <template v-if="column.key === 'sulfate'">{{ record.toxicChemicals.sulfate }}</template>
+        <template v-if="column.key === 'zinc'">{{ record.toxicChemicals.zinc }}</template>
+
+        <template v-if="column.key === 'calcium'">{{ record.electrolyte.calcium }}</template>
+        <template v-if="column.key === 'magnesium'">{{ record.electrolyte.magnesium }}</template>
+        <template v-if="column.key === 'potassium'">{{ record.electrolyte.potassium }}</template>
+        <template v-if="column.key === 'sodium'">{{ record.electrolyte.sodium }}</template>
+
+        <template v-if="column.key === 'antimony'">{{ record.traceElement.antimony }}</template>
+        <template v-if="column.key === 'arsenic'">{{ record.traceElement.arsenic }}</template>
+        <template v-if="column.key === 'barium'">{{ record.traceElement.barium }}</template>
+        <template v-if="column.key === 'beryllium'">{{ record.traceElement.beryllium }}</template>
+        <template v-if="column.key === 'cadmium'">{{ record.traceElement.cadmium }}</template>
+        <template v-if="column.key === 'chromium'">{{ record.traceElement.chromium }}</template>
+        <template v-if="column.key === 'mercury'">{{ record.traceElement.mercury }}</template>
+        <template v-if="column.key === 'selenium'">{{ record.traceElement.selenium }}</template>
+        <template v-if="column.key === 'silver'">{{ record.traceElement.silver }}</template>
+        <template v-if="column.key === 'thallium'">{{ record.traceElement.thallium }}</template>
+        <template v-if="column.key === 'action'">
+          <TableAction
+            :actions="[
+              {
+                auth: 'archives:patientReturn:remove',
+                icon: 'icon-xt-details_delete_default|iconfont',
+                tooltip: '删除',
+                popConfirm: {
+                  title: '是否取消删除',
+                  placement: 'left',
+                  confirm: handleDelete.bind(null, record, column),
+                },
+              },
+            ]"
+          />
+        </template>
+      </template>
+    </BasicTable>
+  </div>
+  <FormDrawer @register="registerDrawer" @success="callSuccess" />
+</template>
+<script lang="ts" setup>
+  import { onBeforeMount, ref } from 'vue';
+  import { BasicTable, useTable, TableAction } from '/@/components/TableCard';
+  import Icon from '/@/components/Icon';
+  import { columns } from './data';
+  import {
+    chemicalPollutantDetectionQueryPage,
+    chemicalPollutantDetectionRemove,
+  } from '/@/api/biz/bio/chemicalPollutantDetectionApi';
+  import { listDictModel } from '/@/api/common';
+  import { XTForm } from '/@/components/XTForm/index';
+  import dayjs from 'dayjs';
+  import { useMessage } from '/@/hooks/web/useMessage';
+  import { useDrawer } from '@/components/Drawer';
+  import FormDrawer from './formDrawer.vue';
+  const { createMessage } = useMessage();
+
+  // formdata
+  const formData = [
+    {
+      label: '检测时间:',
+      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 responseTypeOptions = ref();
+  const responsesupplierCategoryOptions = ref();
+  const [registerDrawer, { openDrawer }] = useDrawer();
+  onBeforeMount(async () => {
+    responseTypeOptions.value = await listDictModel({ dictCode: 'sys_disable_type' });
+    responsesupplierCategoryOptions.value = await listDictModel({ dictCode: 'pht' });
+  });
+
+  const tableSort = ref([
+    {
+      field: 'create_time',
+      direction: 'DESC',
+    },
+  ]) as any;
+
+  const [registerTable, { reload }] = useTable({
+    api: chemicalPollutantDetectionQueryPage,
+    rowKey: 'id',
+    columns,
+    showIndexColumn: false,
+    bordered: true,
+    actionColumn: {
+      width: 200,
+      title: '操作',
+      dataIndex: 'action',
+    },
+    beforeFetch: handleBeforeFetch,
+    sortFn: handleSortFn,
+  });
+  // 详情按钮事件
+  async function handleDelete(record) {
+    await chemicalPollutantDetectionRemove([record.id]);
+    createMessage.success('删除成功!');
+    await reload();
+  }
+
+  // 表格点击字段排序
+  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) {
+    const shiftTimes = [];
+    if (shiftDate.value && shiftDate.value.length > 0) {
+      shiftTimes.push(shiftDate.value[0]);
+      shiftTimes.push(shiftDate.value[1]);
+      shiftTimes[1] = dayjs(shiftTimes[1]).add(1, 'day').format('YYYY-MM-DD');
+    }
+    return {
+      ...params,
+      orders: tableSort.value,
+      name: searchNames.value == '' ? undefined : searchNames.value,
+      status: tabSelected.value == '' ? undefined : tabSelected.value,
+      time: shiftTimes.length <= 0 ? undefined : shiftTimes,
+    };
+  }
+
+  // 新增按钮事件
+  function handleCreate() {
+    openDrawer(true, {
+      isUpdate: false,
+    });
+  }
+
+  //抽屉回返事件
+  // async function handleCancel() {
+  //   clearSelectedRowKeys();
+  //   await reload();
+  // }
+
+  async function callSuccess() {
+    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;
+  }
+
+  .upStatus {
+    color: #ff5d39;
+  }
+
+  .downStatus {
+    color: #ffad26;
+  }
+</style>

+ 7 - 0
src/views/biz/engineer/bio/data.ts

@@ -0,0 +1,7 @@
+export const BasicTab = [
+  { key: 'microbialDetectionDialysate', value: 0, title: '透析液-微生物检测' },
+  { key: 'microbialDetectionWater', value: 1, title: '透析用水-微生物检测' },
+  { key: 'upkeepList', value: 2, title: '透析用水-化学污染物检测' },
+];
+
+export const BasicTabActive = BasicTab[0].key;

+ 60 - 3
src/views/biz/engineer/bio/index.vue

@@ -1,7 +1,64 @@
 <template>
-  <div> 占位符 </div>
+  <div class="m-4">
+    <div class="mb-4">
+      <XTTitle :title="title" :go-back="true" />
+    </div>
+    <div class="py-2 bg-white">
+      <a-tabs v-model:activeKey="activeKey" :destroyInactiveTabPane="true">
+        <a-tab-pane
+          v-for="item in tabData"
+          :key="item.key"
+          :tab="item.title"
+          :class="'tab-' + item.key"
+        >
+          <!-- 透析液-微生物检测 0 -->
+          <div v-if="item.value == 0 && info.id">
+            <MicrobialDetectionDialysate :info="info" />
+          </div>
+          <!-- 透析用水-微生物检测 1 -->
+          <div v-if="item.value == 1 && info.id">
+            <MicrobialDetectionWater :info="info" />
+          </div>
+          <!-- 透析用水-化学污染物检测 2 -->
+          <div v-if="item.value == 2 && info.id">
+            <ChemicalPollutantDetection :info="info" />
+          </div>
+        </a-tab-pane>
+      </a-tabs>
+    </div>
+  </div>
 </template>
 
-<script setup lang="ts"></script>
+<script setup lang="ts">
+  import { ref } from 'vue';
+  import { BasicTab, BasicTabActive } from './data';
+  import MicrobialDetectionDialysate from './microbialDetectionDialysate/index.vue';
+  import MicrobialDetectionWater from './microbialDetectionWater/index.vue';
+  import ChemicalPollutantDetection from './chemicalPollutantDetection/index.vue';
+  import { useRoute } from 'vue-router';
+  import { XTTitle } from '/@/components/XTTitle/index';
 
-<style lang="less" scoped></style>
+  const route = useRoute();
+  console.log('🚀 ~ file: index.vue:25 ~ route:', route);
+  const info = ref({
+    id: String(route.query?.id),
+    name: route.query?.name,
+  });
+  const activeKey = ref(BasicTabActive);
+  const tabData = ref(BasicTab);
+  const title = '生化管理';
+</script>
+
+<style lang="less" scoped>
+  ::v-deep(.ant-tabs-tab) {
+    padding: 12px;
+  }
+
+  ::v-deep(.ant-tabs-tabpane) {
+    padding: 12px;
+  }
+
+  .tab-medicalDocuments {
+    padding: 0;
+  }
+</style>

+ 173 - 0
src/views/biz/engineer/bio/microbialDetectionDialysate/data.ts

@@ -0,0 +1,173 @@
+import { BasicColumn, FormSchema } from '/@/components/Table';
+import { sysUserQueryPage } from '/@/api/sys/sysUserApi';
+import {
+  engineerDialysisDeviceList,
+  engineerDialysisDeviceDetail,
+} from '/@/api/biz/engineer/dialysisDeviceApi';
+import dayjs from 'dayjs';
+// import { useMessage } from '@/hooks/web/useMessage';
+// const { createMessage } = useMessage();
+
+export const columns: BasicColumn[] = [
+  {
+    title: '设备编号',
+    dataIndex: 'deviceUniqueCode',
+    width: 120,
+  },
+  {
+    title: '设备厂家',
+    dataIndex: 'deviceManufacturer',
+    width: 120,
+  },
+  {
+    title: '设备型号',
+    dataIndex: 'deviceSerialNumber',
+  },
+  {
+    title: '检测时间',
+    dataIndex: 'detectionTime',
+  },
+  {
+    title: '进水口细菌(cfu/ml) 正常值≤100 干预值>50',
+    dataIndex: 'intakeBacteria',
+  },
+  {
+    title: '进水口内毒素(Eu/ml) 正常值≤0.5 干预值>0.25',
+    dataIndex: 'intakeEndotoxin',
+  },
+  {
+    title: '出水口细菌(cfu/ml) 正常值≤100 干预值>50',
+    dataIndex: 'outletBacteria',
+  },
+  {
+    title: '出水口内毒素(Eu/ml) 正常值≤0.5 干预值>0.25',
+    dataIndex: 'outletEndotoxin',
+  },
+];
+// 表单新增编辑
+export const dataFormSchema: FormSchema[] = [
+  {
+    field: 'id',
+    component: 'Input',
+    ifShow: false,
+  },
+  {
+    label: '设备编号搜索',
+    field: 'uniqueCodes',
+    component: 'Input',
+    ifShow: false,
+  },
+  {
+    label: '设备编号',
+    field: 'deviceId',
+    required: true,
+    component: 'ApiSelect',
+    componentProps: ({ formModel }) => {
+      return {
+        api: engineerDialysisDeviceList,
+        params: {
+          uniqueCode: formModel.uniqueCodes,
+        },
+        labelField: 'uniqueCode',
+        valueField: 'id',
+        resultField: 'data',
+        placeholder: '请选择设备编号',
+        getPopupContainer: () => document.body,
+        showSearch: true,
+        filterOption: false,
+        onSearch: e => {
+          formModel.uniqueCodes = e;
+        },
+        onChange: async e => {
+          console.log('e::::::::::::', e);
+          const resData = await engineerDialysisDeviceDetail(e);
+          formModel['deviceInfo'] = resData?.name + '(' + resData?.model + ')';
+        },
+        disabled: formModel['id'],
+      };
+    },
+  },
+  {
+    label: '设备信息',
+    field: 'deviceInfo',
+    required: true,
+    component: 'Input',
+    slot: 'deviceInfo',
+    componentProps: {
+      disabled: true,
+    },
+  },
+  {
+    label: '检测时间',
+    field: 'detectionTime',
+    required: true,
+    component: 'DatePicker',
+    componentProps: () => {
+      return {
+        placeholder: '请选择检测时间',
+        getPopupContainer: () => document.body,
+        valueFormat: 'YYYY-MM-DD HH:mm:ss',
+        showTime: true,
+      };
+    },
+    defaultValue: dayjs().format('YYYY-MM-DD HH:mm:ss'),
+  },
+  {
+    label: '检测人',
+    field: 'inspectorId',
+    required: true,
+    component: 'ApiSelect',
+    componentProps: ({ formModel }) => {
+      return {
+        api: sysUserQueryPage,
+        params: {
+          pageNum: 1,
+          pageSize: 999,
+          disable: '0',
+        },
+        mode: 'single',
+        labelField: 'nickname',
+        valueField: 'id',
+        resultField: 'data',
+        placeholder: '请选择检测人',
+        disabled: formModel['id'],
+      };
+    },
+  },
+  {
+    label: '进水口细菌(cfu/ml)  正常值≤100  干预值>50',
+    field: 'intakeBacteria',
+    required: true,
+    component: 'InputNumber',
+    componentProps: {
+      placeholder: '请输入进水口细菌(cfu/ml)',
+    },
+  },
+  {
+    label: '进水口内毒素(Eu/ml)  正常值≤0.5  干预值>0.25',
+    field: 'intakeEndotoxin',
+    required: true,
+    component: 'InputNumber',
+    componentProps: {
+      placeholder: '请输入进水口内毒素(Eu/ml)',
+    },
+  },
+  {
+    label: '出水口细菌(cfu/ml)  正常值≤100  干预值>50',
+    field: 'outletBacteria',
+    required: true,
+    component: 'InputNumber',
+    componentProps: {
+      placeholder: '请输入出水口细菌(cfu/ml)',
+    },
+  },
+  {
+    label: '出水口内毒素(Eu/ml)  正常值≤0.5  干预值>0.25',
+    field: 'outletEndotoxin',
+    required: true,
+    component: 'InputNumber',
+    componentProps: {
+      placeholder: '请输入出水口内毒素(Eu/ml)',
+    },
+  },
+];

+ 96 - 0
src/views/biz/engineer/bio/microbialDetectionDialysate/formModal.vue

@@ -0,0 +1,96 @@
+<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">
+          <template #deviceInfo="{ model, field }">
+            <span>&nbsp;&nbsp;&nbsp;&nbsp;{{ model[field] }}</span>
+          </template>
+        </BasicForm>
+      </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 {
+    microbialDetectionDialysateAdd,
+    microbialDetectionDialysateEdit,
+    microbialDetectionDialysateDetail,
+  } from '/@/api/biz/bio/microbialDetectionDialysateApi';
+  import { engineerDialysisDeviceDetail } from '/@/api/biz/engineer/dialysisDeviceApi';
+  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: 320,
+    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 microbialDetectionDialysateDetail(data.record.id);
+      const deviceInfos = await engineerDialysisDeviceDetail(resData?.deviceId);
+      resData.deviceInfo = deviceInfos?.name + '(' + deviceInfos?.model + ')';
+      console.log('resData::::', resData);
+      await setFieldsValue({
+        ...resData,
+      });
+    }
+  });
+
+  // 提交按钮事件
+  async function handleSubmit() {
+    try {
+      const values = await validate();
+      setModalProps({ confirmLoading: true });
+      !unref(isUpdate)
+        ? await microbialDetectionDialysateAdd({ ...values })
+        : await microbialDetectionDialysateEdit({ ...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>

+ 272 - 0
src/views/biz/engineer/bio/microbialDetectionDialysate/index.vue

@@ -0,0 +1,272 @@
+<template>
+  <div class="m-4 modals">
+    <div>
+      <div class="flex items-center justify-between my-4">
+        <div>
+          <a-button type="primary" size="large" class="btn-add" @click="handleCreate"
+            ><template #icon> <Icon icon="icon-xt-add_default|iconfont" /> </template
+            >新增检测</a-button
+          >
+        </div>
+        <div>
+          <XTForm :form-data="formData" @change="callForm" />
+        </div>
+      </div>
+    </div>
+    <BasicTable @register="registerTable">
+      <template #headerCell="{ column }">
+        <template v-if="column.key === 'deviceUniqueCode'">
+          <span>设备编号</span>
+        </template>
+        <template v-if="column.key === 'deviceManufacturer'">
+          <span>设备厂家</span>
+        </template>
+        <template v-if="column.key === 'deviceSerialNumber'">
+          <span>设备型号</span>
+        </template>
+        <template v-if="column.key === 'detectionTime'">
+          <span>检测时间</span>
+        </template>
+
+        <template v-if="column.key === 'intakeBacteria'">
+          <span>进水口细菌(cfu/ml)<br />正常值≤100<br />干预值>50</span>
+        </template>
+        <template v-if="column.key === 'intakeEndotoxin'">
+          <span>进水口内毒素(Eu/ml)<br />正常值≤0.5<br />干预值>0.25</span>
+        </template>
+        <template v-if="column.key === 'outletBacteria'">
+          <span>出水口细菌(cfu/ml)<br />正常值≤100<br />干预值>50</span>
+        </template>
+        <template v-if="column.key === 'outletEndotoxin'">
+          <span>出水口内毒素(Eu/ml)<br />正常值≤0.5<br />干预值>0.25</span>
+        </template>
+        <template v-if="column.key === 'action'">
+          <span>操作</span>
+        </template>
+      </template>
+      <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 === 'intakeBacteria'">
+          <span v-if="record.intakeBacteriaStatus === '0'">{{ record.intakeBacteria }}</span>
+          <span v-else :class="record.intakeBacteriaStatus === '2' ? 'upStatus' : 'downStatus'">{{
+            record.intakeBacteria
+          }}</span>
+        </template>
+        <template v-if="column.key === 'intakeEndotoxin'">
+          <span v-if="record.intakeEndotoxinStatus === '0'">{{ record.intakeEndotoxin }}</span>
+          <span v-else :class="record.intakeEndotoxinStatus === '2' ? 'upStatus' : 'downStatus'">{{
+            record.intakeEndotoxin
+          }}</span>
+        </template>
+        <template v-if="column.key === 'outletBacteria'">
+          <span v-if="record.outletBacteriaStatus === '0'">{{ record.outletBacteria }}</span>
+          <span v-else :class="record.outletBacteriaStatus === '2' ? 'upStatus' : 'downStatus'">{{
+            record.outletBacteria
+          }}</span>
+        </template>
+        <template v-if="column.key === 'outletEndotoxin'">
+          <span v-if="record.outletEndotoxinStatus === '0'">{{ record.outletEndotoxin }}</span>
+          <span v-else :class="record.outletEndotoxinStatus === '2' ? 'upStatus' : 'downStatus'">{{
+            record.outletEndotoxin
+          }}</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),
+              },
+            ]"
+          />
+        </template>
+      </template>
+    </BasicTable>
+    <FormModal @register="registerModal" @success="callSuccess" @cancel="handleCancel" />
+  </div>
+</template>
+<script lang="ts" setup>
+  import { onBeforeMount, ref } from 'vue';
+  import { BasicTable, useTable, TableAction } from '/@/components/TableCard';
+  import FormModal from './formModal.vue';
+  import Icon from '/@/components/Icon';
+  import { formatDictValue, formatDictPreColor } from '/@/utils';
+  import { columns } from './data';
+  import { microbialDetectionDialysateQueryPage } from '/@/api/biz/bio/microbialDetectionDialysateApi';
+  import { listDictModel } from '/@/api/common';
+  import { useModal } from '/@/components/Modal';
+  import { XTForm } from '/@/components/XTForm/index';
+  import dayjs from 'dayjs';
+
+  // formdata
+  const formData = [
+    {
+      label: '检测时间:',
+      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 responseTypeOptions = ref();
+  const responsesupplierCategoryOptions = ref();
+  onBeforeMount(async () => {
+    responseTypeOptions.value = await listDictModel({ dictCode: 'sys_disable_type' });
+    responsesupplierCategoryOptions.value = await listDictModel({ dictCode: 'pht' });
+  });
+
+  const [registerModal, { openModal }] = useModal();
+
+  const tableSort = ref([
+    {
+      field: 'create_time',
+      direction: 'DESC',
+    },
+  ]) as any;
+
+  const [registerTable, { reload, clearSelectedRowKeys }] = useTable({
+    api: microbialDetectionDialysateQueryPage,
+    rowKey: 'id',
+    columns,
+    showIndexColumn: false,
+    bordered: true,
+    actionColumn: {
+      width: 200,
+      title: '操作',
+      dataIndex: 'action',
+    },
+    beforeFetch: handleBeforeFetch,
+    sortFn: handleSortFn,
+  });
+  // 详情按钮事件
+  function handleEdit(record) {
+    openModal(true, {
+      isUpdate: true,
+      record,
+    });
+  }
+
+  // 表格点击字段排序
+  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) {
+    const shiftTimes = [];
+    if (shiftDate.value && shiftDate.value.length > 0) {
+      shiftTimes.push(shiftDate.value[0]);
+      shiftTimes.push(shiftDate.value[1]);
+      shiftTimes[1] = dayjs(shiftTimes[1]).add(1, 'day').format('YYYY-MM-DD');
+    }
+    return {
+      ...params,
+      orders: tableSort.value,
+      name: searchNames.value == '' ? undefined : searchNames.value,
+      status: tabSelected.value == '' ? undefined : tabSelected.value,
+      time: shiftTimes.length <= 0 ? undefined : shiftTimes,
+    };
+  }
+
+  // 新增按钮事件
+  function handleCreate() {
+    openModal(true, {
+      isUpdate: false,
+    });
+  }
+
+  //取消按钮事件
+  async function handleCancel() {
+    clearSelectedRowKeys();
+    await reload();
+  }
+
+  // 弹窗回调事件
+  async function callSuccess({ isUpdate, values }) {
+    console.log(isUpdate);
+    console.log(values);
+    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;
+  }
+
+  .upStatus {
+    color: #ff5d39;
+  }
+
+  .downStatus {
+    color: #ffad26;
+  }
+</style>

+ 168 - 0
src/views/biz/engineer/bio/microbialDetectionWater/data.ts

@@ -0,0 +1,168 @@
+import { BasicColumn, FormSchema } from '/@/components/Table';
+import { sysUserQueryPage } from '/@/api/sys/sysUserApi';
+import { waterList, waterDetail } from '/@/api/biz/engineer/waterApi';
+import dayjs from 'dayjs';
+// import { useMessage } from '@/hooks/web/useMessage';
+// const { createMessage } = useMessage();
+
+export const columns: BasicColumn[] = [
+  {
+    title: '设备编号',
+    dataIndex: 'deviceUniqueCode',
+    width: 120,
+  },
+  {
+    title: '设备厂家',
+    dataIndex: 'deviceManufacturer',
+    width: 120,
+  },
+  {
+    title: '设备型号',
+    dataIndex: 'deviceSerialNumber',
+  },
+  {
+    title: '检测时间',
+    dataIndex: 'detectionTime',
+  },
+  {
+    title: '进水口细菌(cfu/ml) 正常值≤100 干预值>50',
+    dataIndex: 'intakeBacteria',
+  },
+  {
+    title: '进水口内毒素(Eu/ml) 正常值≤0.5 干预值>0.25',
+    dataIndex: 'intakeEndotoxin',
+  },
+  {
+    title: '出水口细菌(cfu/ml) 正常值≤100 干预值>50',
+    dataIndex: 'outletBacteria',
+  },
+  {
+    title: '出水口内毒素(Eu/ml) 正常值≤0.5 干预值>0.25',
+    dataIndex: 'outletEndotoxin',
+  },
+];
+// 表单新增编辑
+export const dataFormSchema: FormSchema[] = [
+  {
+    field: 'id',
+    component: 'Input',
+    ifShow: false,
+  },
+  {
+    label: '设备编号搜索',
+    field: 'uniqueCodes',
+    component: 'Input',
+    ifShow: false,
+  },
+  {
+    label: '设备编号',
+    field: 'deviceId',
+    required: true,
+    component: 'ApiSelect',
+    componentProps: ({ formModel }) => {
+      return {
+        api: waterList,
+        params: {
+          uniqueCode: formModel.uniqueCodes,
+        },
+        labelField: 'uniqueCode',
+        valueField: 'id',
+        resultField: 'data',
+        placeholder: '请选择设备编号',
+        getPopupContainer: () => document.body,
+        showSearch: true,
+        filterOption: false,
+        onSearch: e => {
+          formModel.uniqueCodes = e;
+        },
+        onChange: async e => {
+          const resData = await waterDetail(e);
+          formModel['deviceInfo'] = resData?.name + '(' + resData?.model + ')';
+        },
+        disabled: formModel['id'],
+      };
+    },
+  },
+  {
+    label: '设备信息',
+    field: 'deviceInfo',
+    required: true,
+    component: 'Input',
+    slot: 'deviceInfo',
+    componentProps: {
+      disabled: true,
+    },
+  },
+  {
+    label: '检测时间',
+    field: 'detectionTime',
+    component: 'DatePicker',
+    componentProps: () => {
+      return {
+        placeholder: '请选择检测时间',
+        getPopupContainer: () => document.body,
+        valueFormat: 'YYYY-MM-DD HH:mm:ss',
+        showTime: true,
+      };
+    },
+    defaultValue: dayjs().format('YYYY-MM-DD HH:mm:ss'),
+  },
+  {
+    label: '检测人',
+    field: 'inspectorId',
+    required: true,
+    component: 'ApiSelect',
+    componentProps: ({ formModel }) => {
+      return {
+        api: sysUserQueryPage,
+        params: {
+          pageNum: 1,
+          pageSize: 999,
+          disable: '0',
+        },
+        mode: 'single',
+        labelField: 'nickname',
+        valueField: 'id',
+        resultField: 'data',
+        placeholder: '请选择检测人',
+        disabled: formModel['id'],
+      };
+    },
+  },
+  {
+    label: '进水口细菌(cfu/ml)  正常值≤100  干预值>50',
+    field: 'intakeBacteria',
+    required: true,
+    component: 'InputNumber',
+    componentProps: {
+      placeholder: '请输入进水口细菌(cfu/ml)',
+    },
+  },
+  {
+    label: '进水口内毒素(Eu/ml)  正常值≤0.5  干预值>0.25',
+    field: 'intakeEndotoxin',
+    required: true,
+    component: 'InputNumber',
+    componentProps: {
+      placeholder: '请输入进水口内毒素(Eu/ml)',
+    },
+  },
+  {
+    label: '出水口细菌(cfu/ml)  正常值≤100  干预值>50',
+    field: 'outletBacteria',
+    required: true,
+    component: 'InputNumber',
+    componentProps: {
+      placeholder: '请输入出水口细菌(cfu/ml)',
+    },
+  },
+  {
+    label: '出水口内毒素(Eu/ml)  正常值≤0.5  干预值>0.25',
+    field: 'outletEndotoxin',
+    required: true,
+    component: 'InputNumber',
+    componentProps: {
+      placeholder: '请输入出水口内毒素(Eu/ml)',
+    },
+  },
+];

+ 96 - 0
src/views/biz/engineer/bio/microbialDetectionWater/formModal.vue

@@ -0,0 +1,96 @@
+<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">
+          <template #deviceInfo="{ model, field }">
+            <span>&nbsp;&nbsp;&nbsp;&nbsp;{{ model[field] }}</span>
+          </template>
+        </BasicForm>
+      </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 {
+    microbialDetectionWaterAdd,
+    microbialDetectionWaterEdit,
+    microbialDetectionWaterDetail,
+  } from '/@/api/biz/bio/microbialDetectionWaterApi';
+  import { waterDetail } from '@/api/biz/engineer/waterApi';
+  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: 320,
+    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 microbialDetectionWaterDetail(data.record.id);
+      const deviceInfos = await waterDetail(resData?.deviceId);
+      resData.deviceInfo = deviceInfos?.name + '(' + deviceInfos?.model + ')';
+      console.log('resData::::', resData);
+      await setFieldsValue({
+        ...resData,
+      });
+    }
+  });
+
+  // 提交按钮事件
+  async function handleSubmit() {
+    try {
+      const values = await validate();
+      setModalProps({ confirmLoading: true });
+      !unref(isUpdate)
+        ? await microbialDetectionWaterAdd({ ...values })
+        : await microbialDetectionWaterEdit({ ...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>

+ 272 - 0
src/views/biz/engineer/bio/microbialDetectionWater/index.vue

@@ -0,0 +1,272 @@
+<template>
+  <div class="m-4 modals">
+    <div>
+      <div class="flex items-center justify-between my-4">
+        <div>
+          <a-button type="primary" size="large" class="btn-add" @click="handleCreate"
+            ><template #icon> <Icon icon="icon-xt-add_default|iconfont" /> </template
+            >新增检测</a-button
+          >
+        </div>
+        <div>
+          <XTForm :form-data="formData" @change="callForm" />
+        </div>
+      </div>
+    </div>
+    <BasicTable @register="registerTable">
+      <template #headerCell="{ column }">
+        <template v-if="column.key === 'deviceUniqueCode'">
+          <span>设备编号</span>
+        </template>
+        <template v-if="column.key === 'deviceManufacturer'">
+          <span>设备厂家</span>
+        </template>
+        <template v-if="column.key === 'deviceSerialNumber'">
+          <span>设备型号</span>
+        </template>
+        <template v-if="column.key === 'detectionTime'">
+          <span>检测时间</span>
+        </template>
+
+        <template v-if="column.key === 'intakeBacteria'">
+          <span>进水口细菌(cfu/ml)<br />正常值≤100<br />干预值>50</span>
+        </template>
+        <template v-if="column.key === 'intakeEndotoxin'">
+          <span>进水口内毒素(Eu/ml)<br />正常值≤0.5<br />干预值>0.25</span>
+        </template>
+        <template v-if="column.key === 'outletBacteria'">
+          <span>出水口细菌(cfu/ml)<br />正常值≤100<br />干预值>50</span>
+        </template>
+        <template v-if="column.key === 'outletEndotoxin'">
+          <span>出水口内毒素(Eu/ml)<br />正常值≤0.5<br />干预值>0.25</span>
+        </template>
+        <template v-if="column.key === 'action'">
+          <span>操作</span>
+        </template>
+      </template>
+      <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 === 'intakeBacteria'">
+          <span v-if="record.intakeBacteriaStatus === '0'">{{ record.intakeBacteria }}</span>
+          <span v-else :class="record.intakeBacteriaStatus === '2' ? 'upStatus' : 'downStatus'">{{
+            record.intakeBacteria
+          }}</span>
+        </template>
+        <template v-if="column.key === 'intakeEndotoxin'">
+          <span v-if="record.intakeEndotoxinStatus === '0'">{{ record.intakeEndotoxin }}</span>
+          <span v-else :class="record.intakeEndotoxinStatus === '2' ? 'upStatus' : 'downStatus'">{{
+            record.intakeEndotoxin
+          }}</span>
+        </template>
+        <template v-if="column.key === 'outletBacteria'">
+          <span v-if="record.outletBacteriaStatus === '0'">{{ record.outletBacteria }}</span>
+          <span v-else :class="record.outletBacteriaStatus === '2' ? 'upStatus' : 'downStatus'">{{
+            record.outletBacteria
+          }}</span>
+        </template>
+        <template v-if="column.key === 'outletEndotoxin'">
+          <span v-if="record.outletEndotoxinStatus === '0'">{{ record.outletEndotoxin }}</span>
+          <span v-else :class="record.outletEndotoxinStatus === '2' ? 'upStatus' : 'downStatus'">{{
+            record.outletEndotoxin
+          }}</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),
+              },
+            ]"
+          />
+        </template>
+      </template>
+    </BasicTable>
+    <FormModal @register="registerModal" @success="callSuccess" @cancel="handleCancel" />
+  </div>
+</template>
+<script lang="ts" setup>
+  import { onBeforeMount, ref } from 'vue';
+  import { BasicTable, useTable, TableAction } from '/@/components/TableCard';
+  import FormModal from './formModal.vue';
+  import Icon from '/@/components/Icon';
+  import { formatDictValue, formatDictPreColor } from '/@/utils';
+  import { columns } from './data';
+  import { microbialDetectionWaterQueryPage } from '/@/api/biz/bio/microbialDetectionWaterApi';
+  import { listDictModel } from '/@/api/common';
+  import { useModal } from '/@/components/Modal';
+  import { XTForm } from '/@/components/XTForm/index';
+  import dayjs from 'dayjs';
+
+  // formdata
+  const formData = [
+    {
+      label: '检测时间:',
+      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 responseTypeOptions = ref();
+  const responsesupplierCategoryOptions = ref();
+  onBeforeMount(async () => {
+    responseTypeOptions.value = await listDictModel({ dictCode: 'sys_disable_type' });
+    responsesupplierCategoryOptions.value = await listDictModel({ dictCode: 'pht' });
+  });
+
+  const [registerModal, { openModal }] = useModal();
+
+  const tableSort = ref([
+    {
+      field: 'create_time',
+      direction: 'DESC',
+    },
+  ]) as any;
+
+  const [registerTable, { reload, clearSelectedRowKeys }] = useTable({
+    api: microbialDetectionWaterQueryPage,
+    rowKey: 'id',
+    columns,
+    showIndexColumn: false,
+    bordered: true,
+    actionColumn: {
+      width: 200,
+      title: '操作',
+      dataIndex: 'action',
+    },
+    beforeFetch: handleBeforeFetch,
+    sortFn: handleSortFn,
+  });
+  // 详情按钮事件
+  function handleEdit(record) {
+    openModal(true, {
+      isUpdate: true,
+      record,
+    });
+  }
+
+  // 表格点击字段排序
+  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) {
+    const shiftTimes = [];
+    if (shiftDate.value && shiftDate.value.length > 0) {
+      shiftTimes.push(shiftDate.value[0]);
+      shiftTimes.push(shiftDate.value[1]);
+      shiftTimes[1] = dayjs(shiftTimes[1]).add(1, 'day').format('YYYY-MM-DD');
+    }
+    return {
+      ...params,
+      orders: tableSort.value,
+      name: searchNames.value == '' ? undefined : searchNames.value,
+      status: tabSelected.value == '' ? undefined : tabSelected.value,
+      time: shiftTimes.length <= 0 ? undefined : shiftTimes,
+    };
+  }
+
+  // 新增按钮事件
+  function handleCreate() {
+    openModal(true, {
+      isUpdate: false,
+    });
+  }
+
+  //取消按钮事件
+  async function handleCancel() {
+    clearSelectedRowKeys();
+    await reload();
+  }
+
+  // 弹窗回调事件
+  async function callSuccess({ isUpdate, values }) {
+    console.log(isUpdate);
+    console.log(values);
+    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;
+  }
+
+  .upStatus {
+    color: #ff5d39;
+  }
+
+  .downStatus {
+    color: #ffad26;
+  }
+</style>

+ 38 - 27
src/views/biz/engineer/dialysis/data.ts

@@ -79,7 +79,7 @@ export const siftFormSchema: FormSchema[] = [
 export const columns: BasicColumn[] = [
   {
     title: '设备编号',
-    dataIndex: 'unique',
+    dataIndex: 'uniqueCode',
     align: 'left',
   },
   {
@@ -117,14 +117,12 @@ export const columns: BasicColumn[] = [
     dataIndex: 'useDate',
     align: 'left',
     width: 180,
-    sorter: true,
   },
   {
     title: '生产时间',
     dataIndex: 'produceDate',
     align: 'left',
     width: 180,
-    sorter: true,
   },
   {
     title: '保修期限(年)',
@@ -137,7 +135,7 @@ export const columns: BasicColumn[] = [
 export const dataFormSchema: FormSchema[] = [
   {
     label: '设备编号',
-    field: 'unique',
+    field: 'uniqueCode',
     required: true,
     component: 'Input',
     componentProps: {
@@ -157,6 +155,16 @@ export const dataFormSchema: FormSchema[] = [
       },
     },
   },
+  {
+    label: '设备名称',
+    field: 'name',
+    required: true,
+    component: 'Input',
+    componentProps: {
+      placeholder: '请输入设备名称',
+      maxLength: 60,
+    },
+  },
   {
     label: '传染标识',
     field: 'infectiousDiseases',
@@ -164,7 +172,7 @@ export const dataFormSchema: FormSchema[] = [
     component: 'ApiSelect',
     componentProps: {
       api: listDictModel,
-      mode: 'multiple',
+      mode: 'single',
       params: {
         dictCode: 'pb_epidemic',
       },
@@ -181,15 +189,6 @@ export const dataFormSchema: FormSchema[] = [
     },
   },
 
-  {
-    label: '备注',
-    field: 'remark',
-    required: true,
-    component: 'Input',
-    componentProps: {
-      placeholder: '请输入备注',
-    },
-  },
   {
     label: '泵类型',
     field: 'pumpType',
@@ -221,15 +220,15 @@ export const dataFormSchema: FormSchema[] = [
       placeholder: '请输入型号',
     },
   },
-  {
-    label: '产地',
-    field: 'model',
-    required: true,
-    component: 'Input',
-    componentProps: {
-      placeholder: '请输入产地',
-    },
-  },
+  // {
+  //   label: '产地',
+  //   field: 'model',
+  //   required: true,
+  //   component: 'Input',
+  //   componentProps: {
+  //     placeholder: '请输入产地',
+  //   },
+  // },
   {
     label: '采购金额(万元)',
     field: 'price',
@@ -241,7 +240,7 @@ export const dataFormSchema: FormSchema[] = [
   },
   {
     label: '购入日期',
-    field: 'birthday',
+    field: 'purchaseDate',
     required: true,
     component: 'DatePicker',
     componentProps: {
@@ -256,7 +255,7 @@ export const dataFormSchema: FormSchema[] = [
   },
   {
     label: '使用日期',
-    field: 'birthday',
+    field: 'useDate',
     required: true,
     component: 'DatePicker',
     componentProps: {
@@ -271,7 +270,7 @@ export const dataFormSchema: FormSchema[] = [
   },
   {
     label: '生产日期',
-    field: 'birthday',
+    field: 'produceDate',
     required: true,
     component: 'DatePicker',
     componentProps: {
@@ -286,11 +285,23 @@ export const dataFormSchema: FormSchema[] = [
   },
   {
     label: '保修期限(年)',
-    field: 'price',
+    field: 'warrantyPeriod',
     required: true,
     component: 'InputNumber',
     componentProps: {
       placeholder: '请输入保修期限(年)',
     },
   },
+  {
+    label: '备注',
+    field: 'remark',
+    required: true,
+    component: 'InputTextArea',
+    componentProps: {
+      placeholder: '请输入备注',
+    },
+    colProps: {
+      span: 24,
+    },
+  },
 ];

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

@@ -0,0 +1,9 @@
+export const BasicTab = [
+  { key: 'deviceBasic', value: 0, title: '设备信息' },
+  { key: 'runList', value: 1, title: '运行记录' },
+  { key: 'maintenanceList', value: 2, title: '维修记录' },
+  { key: 'upkeepList', value: 3, title: '保养记录' },
+  { key: 'treatmentList', value: 4, title: '消毒记录' },
+];
+
+export const BasicTabActive = BasicTab[0].key;

+ 67 - 0
src/views/biz/engineer/dialysis/detail/index.vue

@@ -0,0 +1,67 @@
+<template>
+  <div class="m-4">
+    <div class="mb-4">
+      <XTTitle :title="title" :go-back="true" />
+    </div>
+    <div class="py-2 bg-white">
+      <a-tabs v-model:activeKey="activeKey" :destroyInactiveTabPane="true">
+        <a-tab-pane
+          v-for="item in tabData"
+          :key="item.key"
+          :tab="item.title"
+          :class="'tab-' + item.key"
+        >
+          <!-- 基础信息 0 -->
+          <div v-if="item.value == 0 && info.id">
+            <DeviceInfo :info="info" />
+          </div>
+          <!--  运行记录1 -->
+          <div v-if="item.value == 1 && info.id"> 运行记录 </div>
+          <!-- 维修记录 2 -->
+          <div v-if="item.value == 2 && info.id">
+            <MaintenanceList :info="info" />
+          </div>
+          <!-- 保养记录 3 -->
+          <div v-if="item.value == 3 && info.id">
+            <UpkeepList :info="info" />
+          </div>
+          <!-- 消毒记录 4 -->
+          <div v-if="item.value == 4 && info.id"> 消毒记录 </div>
+        </a-tab-pane>
+      </a-tabs>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+  import { ref } from 'vue';
+  import { BasicTab, BasicTabActive } from './data';
+  import { useRoute } from 'vue-router';
+  import { XTTitle } from '/@/components/XTTitle/index';
+  import DeviceInfo from '../deviceInfo/index.vue';
+  import UpkeepList from '../upkeepList/index.vue';
+  import MaintenanceList from '../maintenanceList/index.vue';
+  const route = useRoute();
+  console.log('🚀 ~ file: index.vue:25 ~ route:', route);
+  const info = ref({
+    id: String(route.query?.id),
+    name: route.query?.name,
+  });
+  const activeKey = ref(BasicTabActive);
+  const tabData = ref(BasicTab);
+  const title = `设备详情 — ${info.value.name}`;
+</script>
+
+<style lang="less" scoped>
+  ::v-deep(.ant-tabs-tab) {
+    padding: 12px;
+  }
+
+  ::v-deep(.ant-tabs-tabpane) {
+    padding: 12px;
+  }
+
+  .tab-medicalDocuments {
+    padding: 0;
+  }
+</style>

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

@@ -0,0 +1,29 @@
+export const BasicData = [
+  {
+    title: '设备信息',
+    icon: 'icon-xt-edit_default',
+    type: 'basic',
+    data: [
+      { field: 'uniqueCode', label: '设备编号', value: '' },
+      { field: 'deviceType', label: '设备类型', value: '', dict: true },
+      { field: 'infectiousDiseases', label: '传染标识', value: '', dict: true },
+      { field: 'serialNumber', label: '序列号', value: '' },
+      { field: 'pumpType', label: '泵类型', value: '', dict: true },
+      { field: 'manufacturer', label: '厂家', value: '' },
+      { field: 'model', label: '型号', value: '' },
+
+      { field: '', label: '产地', value: '' },
+
+      { field: 'price', label: '采购金额(元)', value: '' },
+      { field: 'purchaseDate', label: '入账时间', value: '' },
+      { field: 'useDate', label: '使用日期', value: '' },
+      { field: 'produceDate', label: '生产日期', value: '' },
+      { field: 'warrantyPeriod', label: '保修期限(年)', value: '' },
+
+      { field: '', label: '联机设备', value: '' },
+      { field: '', label: '联机状态', value: '' },
+
+      { field: 'remark', label: '备注', value: '' },
+    ],
+  },
+];

+ 54 - 0
src/views/biz/engineer/dialysis/deviceInfo/index.vue

@@ -0,0 +1,54 @@
+<template>
+  <div>
+    <div class="mx-2 mb-6" v-for="item in basicData" :key="item.type">
+      <DescCard :title="item.title" :type="item.type" :data="item.data" :id="info.id" />
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+  import { ref, reactive } from 'vue';
+  import DescCard from '/@/components/XTCard/src/DescCard.vue';
+  import { BasicData } from './data';
+  import { onMounted } from 'vue';
+  import { formatDictValue } from '/@/utils';
+  import { engineerDialysisDeviceDetail } from '/@/api/biz/engineer/dialysisDeviceApi';
+  import { listDictModelBatch } from '/@/api/common';
+
+  const props = defineProps({
+    info: {
+      type: Object,
+      default: () => {},
+    } as any,
+  });
+  const bizDictOptions = reactive({
+    deviceType: [],
+  });
+  const bizDictData = ref([
+    { key: 'deviceType', dictCode: 'bm_det' },
+    { key: 'pumpType', dictCode: 'bm_pump' },
+    { key: 'infectiousDiseases', dictCode: 'pb_epidemic' },
+  ]);
+  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];
+    }
+    await getData();
+  });
+  const basicData = ref(BasicData as any);
+  // 获取数据
+  async function getData() {
+    const res = await engineerDialysisDeviceDetail(props.info?.id);
+    basicData.value[0].data.forEach(ele => {
+      if (ele.dict) {
+        ele.value = formatDictValue(bizDictOptions[ele.field], res[ele.field]);
+      } else {
+        ele.value = res[ele.field];
+      }
+    });
+  }
+</script>
+
+<style lang="less" scoped></style>

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

@@ -0,0 +1,143 @@
+import { BasicColumn, FormSchema } from '/@/components/Table';
+import { listDictModel, uploadApi } from '/@/api/common/index';
+import dayjs from 'dayjs';
+
+export const columns: BasicColumn[] = [
+  {
+    title: '开始时间',
+    dataIndex: 'maintainTime',
+    align: 'left',
+  },
+  {
+    title: '结束时间',
+    dataIndex: 'maintainCompany',
+    align: 'left',
+  },
+  {
+    title: '内部消毒类型',
+    dataIndex: 'content',
+    align: 'left',
+  },
+  {
+    title: '内部消毒剂',
+    dataIndex: 'costYuan',
+    align: 'left',
+  },
+  {
+    title: '物表消毒剂',
+    dataIndex: 'picture',
+    align: 'left',
+  },
+  {
+    title: '细菌过滤器',
+    dataIndex: 'picture',
+    align: 'left',
+  },
+  {
+    title: '消毒液',
+    dataIndex: 'picture',
+    align: 'left',
+  },
+];
+
+export const upkeepDataFormSchema: FormSchema[] = [
+  {
+    field: 'deviceInfo',
+    component: 'PlainTitle',
+    defaultValue: '设备信息',
+  },
+  {
+    label: '设备编号',
+    field: 'uniqueCode',
+    component: 'Input',
+    slot: 'uniqueCode',
+    colProps: {
+      span: 12,
+    },
+  },
+  {
+    label: '设备厂家',
+    field: 'manufacturer',
+    component: 'Input',
+    slot: 'manufacturer',
+    colProps: {
+      span: 12,
+    },
+  },
+  {
+    label: '设备型号',
+    field: 'model',
+    component: 'Input',
+    slot: 'model',
+  },
+
+  {
+    field: 'maintenanceInfo',
+    component: 'PlainTitle',
+    defaultValue: '保养记录',
+  },
+
+  {
+    label: '保养时间',
+    field: 'maintainTime',
+    required: true,
+    component: 'DatePicker',
+    componentProps: {
+      format: 'YYYY-MM-DD HH:mm:ss',
+      placeholder: '请选择维修时间',
+      showTime: true,
+      getPopupContainer: () => document.body,
+      valueFormat: 'YYYY-MM-DD HH:mm:ss',
+    },
+    defaultValue: dayjs().format('YYYY-MM-DD HH:mm:ss'),
+  },
+  {
+    label: '保养方',
+    required: true,
+    field: 'maintainCompany',
+    component: 'ApiSelect',
+    componentProps: {
+      api: listDictModel,
+      params: {
+        dictCode: 'dmc',
+      },
+      placeholder: '请选择保养方',
+    },
+  },
+  {
+    label: '保养内容',
+    field: 'content',
+    required: true,
+    component: 'Input',
+    componentProps: {
+      placeholder: '请输入维修内容',
+    },
+  },
+  {
+    label: '保养费用(元)',
+    field: 'costYuan',
+    required: true,
+    component: 'InputNumber',
+    componentProps: {
+      placeholder: '请输入维修内容',
+      min: 0,
+    },
+  },
+  {
+    label: '保养图片',
+    field: 'files',
+    component: 'XTUpload',
+    componentProps: ({ formModel, schema }) => {
+      return {
+        api: uploadApi,
+        maxSize: 1,
+        maxNumber: 1,
+        helpText: '仅支持上传jpg/png图片,图片大小不超过1M',
+        accept: ['image/*'],
+        onChange: data => {
+          formModel[schema.field] = data;
+        },
+      };
+    },
+  },
+];

+ 114 - 0
src/views/biz/engineer/dialysis/disinfectants/disinfectantsFormModal.vue

@@ -0,0 +1,114 @@
+<template>
+  <div class="modals">
+    <BasicModal
+      v-bind="$attrs"
+      destroyOnClose
+      @register="registerModal"
+      :title="getTitle"
+      @ok="handleSubmit"
+      :width="800"
+      @cancel="handleCancel"
+    >
+      <div class="!pl-8 !pt-4">
+        <BasicForm @register="registerForm">
+          <template #uniqueCode>
+            <span>{{ deviceInfo?.uniqueCode }}</span>
+          </template>
+          <template #manufacturer>
+            <span>{{ deviceInfo?.manufacturer }}</span>
+          </template>
+          <template #model>
+            <span>{{ deviceInfo?.model }}</span>
+          </template>
+        </BasicForm>
+      </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 { upkeepDataFormSchema } from './data';
+  import {
+    upkeepAdd,
+    upkeepDetail,
+    upkeepEdit,
+    engineerDialysisDeviceDetail,
+  } from '/@/api/biz/engineer/dialysisDeviceApi';
+  const emit = defineEmits(['success', 'cancel', 'register']);
+
+  const getTitle = computed(() => (!unref(isUpdate) ? '保养设备' : '保养设备'));
+  const isUpdate = ref(false);
+  const deviceInfo = ref();
+  const rowId = ref();
+
+  const { createMessage } = useMessage();
+  const [registerForm, { resetFields, validate, setFieldsValue }] = useForm({
+    layout: 'vertical',
+    showResetButton: true,
+    labelWidth: 100,
+    schemas: upkeepDataFormSchema,
+    showActionButtonGroup: false,
+    actionColOptions: {
+      span: 12,
+    },
+    baseColProps: {
+      span: 12,
+    },
+    wrapperCol: {
+      span: 22,
+    },
+  });
+  const [registerModal, { setModalProps, closeModal }] = useModalInner(async data => {
+    await resetFields();
+    setModalProps({ confirmLoading: false });
+    isUpdate.value = !!data?.isUpdate;
+    if (!data.record.name) {
+      deviceInfo.value = await engineerDialysisDeviceDetail(data.record?.id);
+    } else {
+      deviceInfo.value = data.record;
+    }
+    if (unref(isUpdate)) {
+      rowId.value = data.upkeepRecord.id;
+      const resData = await upkeepDetail(data.upkeepRecord.id);
+      console.log('resData::::', resData);
+      await setFieldsValue({
+        ...resData,
+      });
+    }
+  });
+
+  // 提交按钮事件
+  async function handleSubmit() {
+    try {
+      const values = await validate();
+      setModalProps({ confirmLoading: true });
+      values.picture = values.files && values.files.map(ele => ele.id);
+      if (!isUpdate.value) {
+        values.deviceIds = [deviceInfo.value.id];
+      } else {
+        values.deviceId = deviceInfo.value.id;
+      }
+      !unref(isUpdate)
+        ? await upkeepAdd({ ...values })
+        : await upkeepEdit({ ...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>

+ 259 - 0
src/views/biz/engineer/dialysis/disinfectants/index.vue

@@ -0,0 +1,259 @@
+<template>
+  <div>
+    <div class="flex justify-between mb-4">
+      <div>
+        <a-button type="primary" size="large" class="btn-add" @click="handleCreate"
+          ><template #icon> <Icon icon="icon-xt-add_default|iconfont" /> </template
+          >新增消毒</a-button
+        >
+      </div>
+      <div>
+        <XTForm :form-data="formData" @change="callForm" />
+      </div>
+    </div>
+    <BasicTable @register="registerTable">
+      <template #bodyCell="{ column, record }">
+        <template v-if="column.key === 'maintainCompany'">
+          {{ formatDictValue(bizDictOptions.dmc, record.maintainCompany) }}
+        </template>
+        <template v-if="column.key === 'picture'">
+          <Image
+            v-if="record.files && record.files.length > 0"
+            :src="record.files[0].absolutePath"
+            :width="60"
+          />
+        </template>
+        <template v-if="column.key === 'action'">
+          <TableAction
+            :actions="[
+              {
+                auth: 'archives:patrolWard:edit',
+                icon: 'icon-xt-details_edit_default|iconfont',
+                tooltip: '编辑',
+                onClick: handleEdit.bind(null, record),
+              },
+              {
+                auth: 'archives:patrolWard:remove',
+                icon: 'icon-xt-details_delete_default|iconfont',
+                tooltip: '删除',
+                popConfirm: {
+                  title: '是否取消删除',
+                  placement: 'left',
+                  confirm: handleDelete.bind(null, record, column),
+                },
+              },
+            ]"
+          />
+        </template>
+      </template>
+    </BasicTable>
+    <FormModal @register="registerModal" @success="callSuccess" />
+  </div>
+</template>
+<script lang="ts" setup>
+  import { onBeforeMount, onMounted, reactive, ref } from 'vue';
+  import { XTForm } from '/@/components/XTForm/index';
+  import { BasicTable, useTable, TableAction } from '/@/components/Table';
+  import { useMessage } from '/@/hooks/web/useMessage';
+  import FormModal from './upkeepFormModal.vue';
+  import Icon from '/@/components/Icon/index';
+  import { columns } from './data';
+  import { listDictModelBatch } from '/@/api/common';
+  import { upkeepQueryPage, upkeepRemove } from '/@/api/biz/engineer/dialysisDeviceApi';
+  import { useModal } from '/@/components/Modal';
+  import { Image } from 'ant-design-vue';
+  import dayjs from 'dayjs';
+  import { formatDictValue } from '/@/utils';
+
+  const props = defineProps({
+    info: {
+      type: Object,
+      default: () => {},
+    },
+  });
+
+  const { createMessage } = useMessage();
+  const [registerModal, { openModal }] = useModal();
+  const tableSort = ref([
+    {
+      field: 'create_time',
+      direction: 'DESC',
+    },
+  ]) as any;
+
+  const dictsOption = ref([]) as any;
+  // formdata
+  const formData = [
+    {
+      label: '保养厂商',
+      name: 'maintainCompany',
+      componentType: 'Select',
+      dicts: dictsOption.value,
+      placeholder: '请选择维修方',
+      prefix: 'icon-xt-search',
+      width: 280,
+    },
+    {
+      name: 'patrolTime',
+      label: '保养时间',
+      componentType: 'RangePicker',
+      placeholder: '请选择保养时间',
+      format: 'YYYY-MM-DD',
+      valueFormat: 'YYYY-MM-DD',
+    },
+  ];
+  const formValue = reactive({
+    patrolTime: [],
+    maintainCompany: '',
+  });
+
+  const [registerTable, { reload }] = useTable({
+    api: upkeepQueryPage,
+    rowKey: 'id',
+    columns,
+    showIndexColumn: false,
+    bordered: true,
+    striped: false,
+    actionColumn: {
+      width: 200,
+      title: '操作',
+      dataIndex: 'action',
+    },
+    beforeFetch: handleBeforeFetch,
+    sortFn: handleSortFn,
+  });
+  const bizDictData = ref([{ key: 'dmc', dictCode: 'dmc' }]);
+
+  const bizDictOptions = reactive<any>({});
+  onBeforeMount(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 types = bizDictOptions.dmc;
+    dictsOption.value.push({
+      label: '全部',
+      value: '',
+    });
+    types.forEach(item => {
+      dictsOption.value.push({
+        label: item.label,
+        value: item.value,
+      });
+    });
+  });
+  onMounted(async () => {
+    // callForm({
+    //   patrolTime: [
+    //     dayjs().subtract(3, 'month').format('YYYY-MM-DD'),
+    //     dayjs().add(1, 'day').format('YYYY-MM-DD'),
+    //   ],
+    // });
+  });
+  // 新增按钮事件
+  function handleCreate() {
+    openModal(true, {
+      isUpdate: false,
+      record: { id: props.info.id },
+    });
+  }
+
+  // 编辑按钮事件
+  function handleEdit(record: Recordable) {
+    openModal(true, {
+      record: { id: props.info.id },
+      upkeepRecord: record,
+      isUpdate: true,
+    });
+  }
+
+  // 删除按钮事件
+  async function handleDelete(record: Recordable) {
+    await upkeepRemove([record.id]);
+    createMessage.success('删除成功!');
+    await reload();
+  }
+  // 表格点击字段排序
+  function handleSortFn(sortInfo) {
+    if (sortInfo?.order && sortInfo?.columnKey) {
+      // 默认单列排序
+      tableSort.value = [
+        {
+          field: sortInfo.columnKey,
+          direction: sortInfo.order.replace(/(\w+)(end)/g, '$1').toUpperCase(),
+        },
+      ];
+    }
+  }
+
+  // 表格请求之前,对参数进行处理, 添加默认 排序
+  function handleBeforeFetch(params) {
+    return {
+      ...params,
+      orders: tableSort.value,
+      deviceId: props.info?.id,
+      maintainTime:
+        formValue.patrolTime && formValue.patrolTime.length > 0
+          ? [
+              formValue.patrolTime[0],
+              dayjs(formValue.patrolTime[1]).add(1, 'day').format('YYYY-MM-DD'),
+            ]
+          : undefined,
+      maintainCompany: formValue.maintainCompany,
+    };
+  }
+
+  // 弹窗回调事件
+  async function callSuccess() {
+    await reload();
+  }
+
+  // 查询回调
+  async function callForm(data) {
+    formValue.maintainCompany = data.maintainCompany || '';
+    formValue.patrolTime = data.patrolTime || [];
+    await reload();
+  }
+</script>
+<style lang="less" scoped>
+  ::v-deep(.ant-btn-link) {
+    color: rgb(61 65 85 / 100%);
+  }
+
+  ::v-deep(.ant-input) {
+    border-color: #c2ccd4;
+    border-radius: 4px;
+  }
+
+  ::v-deep(.ant-input-affix-wrapper) {
+    border-color: #c2ccd4;
+    border-radius: 4px;
+  }
+
+  ::v-deep(.ant-picker) {
+    border-color: #c2ccd4;
+    border-radius: 4px;
+  }
+
+  ::v-deep(.ant-table-tbody > tr > td) {
+    border-right: 0 !important;
+    border-left: 0 !important;
+    border-bottom: 1px solid #f0f0f0;
+  }
+
+  ::v-deep(.ant-table-wrapper table) {
+    border: 0;
+  }
+
+  ::v-deep(.ant-table-cell-fix-right-first::after, .ant-table-cell-fix-right-last::after) {
+    content: '';
+    top: auto;
+    width: 0;
+  }
+
+  .btn-add {
+    width: 120px;
+    border-radius: 4px;
+  }
+</style>

+ 33 - 60
src/views/biz/engineer/dialysis/index.vue

@@ -17,52 +17,42 @@
       </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 v-if="column.key === 'useDate'">
+            {{ record.useDate ? dayjs(record.useDate).format('YYYY-MM-DD') : '' }}
           </template>
-          <template v-if="column.key === 'birthday'">
-            {{ record.birthday ? dayjs(record.birthday).format('YYYY-MM-DD') : '' }}
+          <template v-if="column.key === 'produceDate'">
+            {{ record.produceDate ? dayjs(record.produceDate).format('YYYY-MM-DD') : '' }}
           </template>
-          <template v-if="column.key === 'firstDialysisTime'">
-            {{
-              record.firstDialysisTime ? dayjs(record.firstDialysisTime).format('YYYY-MM-DD') : ''
-            }}
+          <template v-if="column.key === 'pumpType'">
+            {{ formatDictValue(bizDictOptions.pumpType, record.pumpType) }}
           </template>
-          <template v-if="column.key === 'gender'">
+          <template v-if="column.key === 'deviceType'">
             <span
+              :class="['table-dot']"
               :style="{
-                backgroundColor: formatDictColor(bizDictOptions.gender, record.gender),
-                color: formatDictFontColor(bizDictOptions.gender, record.gender),
-                padding: '1px 6px',
-                borderRadius: '2px',
-                marginRight: '4px',
+                backgroundColor: formatDictPreColor(bizDictOptions.deviceType, record.deviceType),
               }"
-            >
-              {{ 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) }}
+            />
+            {{ formatDictValue(bizDictOptions.deviceType, record.deviceType) }}
           </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),
+                  backgroundColor: formatDictColor(
+                    bizDictOptions.infectiousDiseases,
+                    record.infectiousDiseases,
+                  ),
+                  color: formatDictFontColor(
+                    bizDictOptions.infectiousDiseases,
+                    record.infectiousDiseases,
+                  ),
                   padding: '1px 6px',
                   borderRadius: '2px',
                   marginRight: '4px',
                 }"
               >
-                <!-- {{ record.infectiousDiseases }} -->
-                {{ formatDictValue(bizDictOptions.infectiousDiseases, item) }}
+                {{ formatDictValue(bizDictOptions.infectiousDiseases, record.infectiousDiseases) }}
               </div>
             </div>
           </template>
@@ -105,7 +95,12 @@
   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 {
+    formatDictColor,
+    formatDictFontColor,
+    formatDictValue,
+    formatDictPreColor,
+  } from '/@/utils';
   import { onMounted, reactive } from 'vue';
   import dayjs from 'dayjs';
   import { useModal } from '/@/components/Modal';
@@ -116,20 +111,12 @@
 
   const bizDictOptions = reactive<any>({});
   const bizDictData = ref([
-    // 血管材料
-    { key: 'gender', dictCode: 'pb_sex' },
-    // 转归类型
-    { key: 'patientReturn', dictCode: 'pb_return' },
-    // 传染病
+    // 泵类型
+    { key: 'pumpType', dictCode: 'bm_pump' },
+    // 设备类型
+    { key: 'deviceType', dictCode: 'bm_det' },
+    // 传染病类型
     { 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();
@@ -249,14 +236,12 @@
 
   // 详情按钮事件
   function handleView(record: Recordable) {
+    //
     router.push({
-      path: '/bizArchives/detail',
+      path: '/bizEngineer/dialysisDevices/details',
       query: {
         id: record.id,
-        accessId: record.accessId,
         name: record.name,
-        gender: formatDictValue(bizDictOptions.gender, record.gender),
-        age: record.age,
       },
     });
   }
@@ -353,18 +338,6 @@
     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) {

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

@@ -0,0 +1,210 @@
+import { BasicColumn } from '/@/components/Table';
+import { FormSchema } from '/@/components/Form';
+import { listDictModel, uploadApi } from '/@/api/common';
+import dayjs from 'dayjs';
+
+export const columns: BasicColumn[] = [
+  {
+    title: '维修时间',
+    dataIndex: 'maintenanceTime',
+    align: 'left',
+  },
+  {
+    title: '维修方',
+    dataIndex: 'maintenanceCompany',
+    align: 'left',
+  },
+  {
+    title: '故障阶段',
+    dataIndex: 'malfunctionStage',
+    align: 'left',
+  },
+  {
+    title: '故障内容',
+    dataIndex: 'malfunctionMessage',
+    align: 'left',
+  },
+  {
+    title: '维修内容',
+    dataIndex: 'repairContent',
+    align: 'left',
+  },
+  {
+    title: '解决措施',
+    dataIndex: 'countermeasure',
+    align: 'left',
+  },
+  {
+    title: '解决进度',
+    dataIndex: 'schedule',
+    align: 'left',
+  },
+  {
+    title: '维修费用(元)',
+    dataIndex: 'costYuan',
+    align: 'left',
+  },
+  {
+    title: '维修图片',
+    dataIndex: 'picture',
+    align: 'left',
+  },
+];
+
+export const maintenanceDataFormSchema: FormSchema[] = [
+  {
+    field: 'deviceInfo',
+    component: 'PlainTitle',
+    defaultValue: '设备信息',
+  },
+  {
+    label: '设备编号',
+    field: 'uniqueCode',
+    component: 'Input',
+    slot: 'uniqueCode',
+    colProps: {
+      span: 12,
+    },
+  },
+  {
+    label: '设备厂家',
+    field: 'manufacturer',
+    component: 'Input',
+    slot: 'manufacturer',
+    colProps: {
+      span: 12,
+    },
+  },
+  {
+    label: '设备型号',
+    field: 'model',
+    component: 'Input',
+    slot: 'model',
+    colProps: {
+      span: 12,
+    },
+  },
+
+  {
+    field: 'maintenanceInfo',
+    component: 'PlainTitle',
+    defaultValue: '维修记录',
+  },
+
+  {
+    label: '维修时间',
+    field: 'maintenanceTime',
+    required: true,
+    component: 'DatePicker',
+    componentProps: {
+      format: 'YYYY-MM-DD HH:mm:ss',
+      placeholder: '请选择维修时间',
+      showTime: true,
+      getPopupContainer: () => document.body,
+      valueFormat: 'YYYY-MM-DD HH:mm:ss',
+    },
+    defaultValue: dayjs().format('YYYY-MM-DD HH:mm:ss'),
+    colProps: {
+      span: 12,
+    },
+  },
+
+  {
+    label: '维修方',
+    field: 'maintenanceCompany',
+    component: 'ApiSelect',
+    componentProps: {
+      api: listDictModel,
+      params: {
+        dictCode: 'dmc',
+      },
+      placeholder: '请选择维修方',
+    },
+    colProps: {
+      span: 12,
+    },
+  },
+  {
+    label: '故障阶段',
+    field: 'maintenanceCompany',
+    component: 'ApiSelect',
+    componentProps: {
+      api: listDictModel,
+      params: {
+        dictCode: 'fs',
+      },
+      placeholder: '请选择故障阶段',
+    },
+  },
+  {
+    label: '故障内容',
+    field: 'malfunctionMessage',
+    component: 'Input',
+    componentProps: {
+      placeholder: '请输入故障内容',
+    },
+  },
+  {
+    label: '维修内容',
+    field: 'repairContent',
+    component: 'Input',
+    componentProps: {
+      placeholder: '请输入维修内容',
+    },
+  },
+  {
+    label: '解决措施',
+    field: 'repairContent',
+    component: 'Input',
+    componentProps: {
+      placeholder: '请输入维修内容',
+    },
+  },
+  {
+    label: '解决进度',
+    field: 'schedule',
+    component: 'ApiSelect',
+    componentProps: {
+      api: listDictModel,
+      params: {
+        dictCode: 'rp',
+      },
+      placeholder: '请选择解决进度',
+    },
+    colProps: {
+      span: 12,
+    },
+  },
+  {
+    label: '维修费用(元)',
+    field: 'costYuan',
+    component: 'Input',
+    componentProps: {
+      placeholder: '请输入维修费用(元)',
+    },
+    colProps: {
+      span: 12,
+    },
+  },
+
+  {
+    label: '维修图片',
+    field: 'files',
+    component: 'XTUpload',
+    componentProps: ({ formModel, schema }) => {
+      return {
+        api: uploadApi,
+        maxSize: 1,
+        maxNumber: 1,
+        helpText: '仅支持上传jpg/png图片,图片大小不超过1M',
+        accept: ['image/*'],
+        onChange: data => {
+          formModel[schema.field] = data;
+        },
+      };
+    },
+    colProps: {
+      span: 24,
+    },
+  },
+];

+ 133 - 0
src/views/biz/engineer/dialysis/maintenanceList/formDrawer.vue

@@ -0,0 +1,133 @@
+<template>
+  <BasicDrawer
+    v-bind="$attrs"
+    destroyOnClose
+    @register="registerDrawer"
+    :title="getTitle"
+    :width="width"
+    @ok="handleSubmit"
+    :showFooter="true"
+  >
+    <BasicForm @register="registerForm" layout="vertical" />
+  </BasicDrawer>
+</template>
+<script lang="ts" setup>
+  import { ref, computed, unref } from 'vue';
+  import { BasicDrawer, useDrawerInner } from '/@/components/Drawer';
+  import { BasicForm, useForm } from '/@/components/Form';
+  import { useMessage } from '/@/hooks/web/useMessage';
+  import { maintenanceDataFormSchema } from './data';
+
+  import {
+    maintenanceAdd,
+    maintenanceEdit,
+    maintenanceDetail,
+  } from '/@/api/biz/engineer/maintenanceApi';
+  import { engineerDialysisDeviceDetail } from '/@/api/biz/engineer/dialysisDeviceApi';
+  const emit = defineEmits(['success', 'register']);
+  const getTitle = computed(() => (!unref(isUpdate) ? '新增保养记录' : '编辑保养记录'));
+  const width = '35%';
+  const isUpdate = ref(false);
+  const deviceDetail = ref({
+    uniqueCode: null,
+    manufacturer: null,
+    deviceType: null,
+  });
+  const detailData = ref();
+  const rowId = ref();
+  const deviceSelIds = ref([]);
+  const { createMessage } = useMessage();
+  const [registerForm, { setFieldsValue, resetFields, validate }] = useForm({
+    labelWidth: 100,
+    schemas: maintenanceDataFormSchema,
+    showActionButtonGroup: false,
+    actionColOptions: {
+      span: 23,
+    },
+  });
+  const [registerDrawer, { setDrawerProps, closeDrawer }] = useDrawerInner(async data => {
+    await resetFields();
+    setDrawerProps({ confirmLoading: false });
+    isUpdate.value = !!data?.isUpdate;
+    if (unref(isUpdate)) {
+      const resData = await maintenanceDetail(data.record.id);
+      rowId.value = resData.id;
+      detailData.value = resData;
+      await setFieldsValue({
+        ...resData,
+      });
+    } else {
+      deviceDetail.value = await engineerDialysisDeviceDetail(data.record.deviceId);
+    }
+  });
+
+  // 提交按钮事件
+  async function handleSubmit() {
+    try {
+      const values = await validate();
+      let updateVal = {};
+      setDrawerProps({ confirmLoading: true });
+      values.picture = values.files && values.files.map(ele => ele.id);
+      if (!isUpdate.value) {
+        if (deviceSelIds.value && deviceSelIds.value.length > 0) {
+          values.deviceIds = deviceSelIds.value;
+        } else {
+          createMessage.error('请选择需要保养的设备');
+          return false;
+        }
+      } else {
+        updateVal = { ...detailData.value, ...values };
+      }
+      !unref(isUpdate)
+        ? await maintenanceAdd({ ...values })
+        : await maintenanceEdit({ ...updateVal, id: rowId.value });
+      !unref(isUpdate) ? createMessage.success('新增成功!') : createMessage.success('编辑成功!');
+      closeDrawer();
+      emit('success', {
+        isUpdate: unref(isUpdate),
+        values: { ...values, id: rowId.value },
+      });
+    } finally {
+      deviceSelIds.value = [];
+      setDrawerProps({ confirmLoading: false });
+    }
+  }
+</script>
+<style lang="less" scoped>
+  .device-info {
+    width: 100%;
+    max-height: 520px;
+    margin-top: -30px;
+    overflow: auto;
+
+    .device-card {
+      display: inline-block;
+      width: 270px;
+      height: 40px;
+      margin: 10px 20px;
+      background: #f4f6f9;
+      border-radius: 4px;
+      font-size: 14px;
+      font-weight: 500;
+      color: #000a18;
+      text-align: center;
+      line-height: 40px;
+      cursor: pointer;
+    }
+
+    .card-select {
+      background: #fff;
+      border-radius: 4px;
+      border: 1px solid #006dff;
+      color: #006dff;
+    }
+
+    .card-disable {
+      background: #fff;
+      border-radius: 4px;
+      border: 1px solid #a5a5a5;
+      color: #a5a5a5;
+      cursor: default;
+    }
+  }
+</style>

+ 266 - 0
src/views/biz/engineer/dialysis/maintenanceList/index.vue

@@ -0,0 +1,266 @@
+<template>
+  <div>
+    <div class="flex justify-between mb-4">
+      <div>
+        <a-button type="primary" size="large" class="btn-add" @click="handleCreate"
+          ><template #icon> <Icon icon="icon-xt-add_default|iconfont" /> </template
+          >新增维修</a-button
+        >
+      </div>
+      <div>
+        <XTForm :form-data="formData" @change="callForm" />
+      </div>
+    </div>
+    <BasicTable @register="registerTable">
+      <template #bodyCell="{ column, record }">
+        <template v-if="column.key === 'maintenanceCompany'">
+          <span>{{ formatDictValue(bizDictOptions.dmc, record.maintenanceCompany) }}</span>
+        </template>
+        <template v-if="column.key === 'schedule'">
+          <span>{{ formatDictValue(bizDictOptions.rp, record.schedule) }}</span>
+        </template>
+        <template v-if="column.key === 'picture'">
+          <Image
+            v-if="record.files && record.files.length > 0"
+            :src="record.files[0].absolutePath"
+            :width="60"
+          />
+        </template>
+        <template v-if="column.key === 'action'">
+          <TableAction
+            :actions="[
+              {
+                auth: 'archives:patrolWard:edit',
+                icon: 'icon-xt-details_edit_default|iconfont',
+                tooltip: '编辑',
+                onClick: handleEdit.bind(null, record),
+              },
+              {
+                auth: 'archives:patrolWard:remove',
+                icon: 'icon-xt-details_delete_default|iconfont',
+                tooltip: '删除',
+                popConfirm: {
+                  title: '是否取消删除',
+                  placement: 'left',
+                  confirm: handleDelete.bind(null, record, column),
+                },
+              },
+            ]"
+          />
+        </template>
+      </template>
+    </BasicTable>
+    <FormDrawer @register="registerDrawer" @success="callSuccess" />
+  </div>
+</template>
+<script lang="ts" setup>
+  import { onBeforeMount, onMounted, reactive, ref } from 'vue';
+  import { XTForm } from '/@/components/XTForm/index';
+  import { BasicTable, useTable, TableAction } from '/@/components/Table';
+  import { useMessage } from '/@/hooks/web/useMessage';
+  import FormDrawer from './formDrawer.vue';
+
+  import Icon from '/@/components/Icon/index';
+  import { columns } from './data';
+  import { listDictModelBatch } from '/@/api/common';
+  import { maintenanceQueryPage, maintenanceRemove } from '/@/api/biz/engineer/maintenanceApi';
+  import { Image } from 'ant-design-vue';
+  import dayjs from 'dayjs';
+  import { formatDictValue } from '/@/utils';
+  import { useDrawer } from '@/components/Drawer';
+
+  const props = defineProps({
+    info: {
+      type: Object,
+      default: () => {},
+    },
+  });
+
+  const { createMessage } = useMessage();
+  const [registerDrawer, { openDrawer }] = useDrawer();
+  const tableSort = ref([
+    {
+      field: 'create_time',
+      direction: 'DESC',
+    },
+  ]) as any;
+
+  const dictsOption = ref([]) as any;
+  // formdata
+  const formData = [
+    {
+      label: '维修方',
+      name: 'maintenanceCompany',
+      componentType: 'Select',
+      dicts: dictsOption.value,
+      placeholder: '请选择维修方',
+      prefix: 'icon-xt-search',
+      width: 280,
+    },
+    {
+      name: 'patrolTime',
+      label: '维修时间',
+      componentType: 'RangePicker',
+      placeholder: '请选择维修时间',
+      format: 'YYYY-MM-DD',
+      valueFormat: 'YYYY-MM-DD',
+    },
+  ];
+  const formValue = reactive({
+    patrolTime: [],
+    maintenanceCompany: '',
+  });
+
+  const [registerTable, { reload }] = useTable({
+    api: maintenanceQueryPage,
+    rowKey: 'id',
+    columns,
+    showIndexColumn: false,
+    bordered: true,
+    striped: false,
+    actionColumn: {
+      width: 200,
+      title: '操作',
+      dataIndex: 'action',
+    },
+    beforeFetch: handleBeforeFetch,
+    sortFn: handleSortFn,
+  });
+  const bizDictData = ref([
+    { key: 'dmc', dictCode: 'dmc' },
+    { key: 'rp', dictCode: 'rp' },
+  ]);
+
+  const bizDictOptions = reactive<any>({});
+  onBeforeMount(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 types = bizDictOptions.dmc;
+    dictsOption.value.push({
+      label: '全部',
+      value: '',
+    });
+    types.forEach(item => {
+      dictsOption.value.push({
+        label: item.label,
+        value: item.value,
+      });
+    });
+  });
+  onMounted(async () => {
+    // callForm({
+    //   patrolTime: [
+    //     dayjs().subtract(3, 'month').format('YYYY-MM-DD'),
+    //     dayjs().add(1, 'day').format('YYYY-MM-DD'),
+    //   ],
+    // });
+  });
+  // 新增按钮事件
+  function handleCreate() {
+    openDrawer(true, {
+      isUpdate: false,
+      record: { id: props.info.id },
+    });
+  }
+
+  // 编辑按钮事件
+  function handleEdit(record: Recordable) {
+    openDrawer(true, {
+      record: { id: props.info.id },
+      maintenanceRecord: record,
+      isUpdate: true,
+    });
+  }
+
+  // 删除按钮事件
+  async function handleDelete(record: Recordable) {
+    await maintenanceRemove([record.id]);
+    createMessage.success('删除成功!');
+    await reload();
+  }
+  // 表格点击字段排序
+  function handleSortFn(sortInfo) {
+    if (sortInfo?.order && sortInfo?.columnKey) {
+      // 默认单列排序
+      tableSort.value = [
+        {
+          field: sortInfo.columnKey,
+          direction: sortInfo.order.replace(/(\w+)(end)/g, '$1').toUpperCase(),
+        },
+      ];
+    }
+  }
+
+  // 表格请求之前,对参数进行处理, 添加默认 排序
+  function handleBeforeFetch(params) {
+    return {
+      ...params,
+      orders: tableSort.value,
+      deviceId: props.info?.id,
+      maintenanceTime:
+        formValue.patrolTime && formValue.patrolTime.length > 0
+          ? [
+              formValue.patrolTime[0],
+              dayjs(formValue.patrolTime[1]).add(1, 'day').format('YYYY-MM-DD'),
+            ]
+          : undefined,
+      maintenanceCompany: formValue.maintenanceCompany,
+    };
+  }
+
+  // 弹窗回调事件
+  async function callSuccess() {
+    await reload();
+  }
+
+  // 查询回调
+  async function callForm(data) {
+    formValue.maintenanceCompany = data.maintenanceCompany || '';
+    formValue.patrolTime = data.patrolTime || [];
+    await reload();
+  }
+</script>
+<style lang="less" scoped>
+  ::v-deep(.ant-btn-link) {
+    color: rgb(61 65 85 / 100%);
+  }
+
+  ::v-deep(.ant-input) {
+    border-color: #c2ccd4;
+    border-radius: 4px;
+  }
+
+  ::v-deep(.ant-input-affix-wrapper) {
+    border-color: #c2ccd4;
+    border-radius: 4px;
+  }
+
+  ::v-deep(.ant-picker) {
+    border-color: #c2ccd4;
+    border-radius: 4px;
+  }
+
+  ::v-deep(.ant-table-tbody > tr > td) {
+    border-right: 0 !important;
+    border-left: 0 !important;
+    border-bottom: 1px solid #f0f0f0;
+  }
+
+  ::v-deep(.ant-table-wrapper table) {
+    border: 0;
+  }
+
+  ::v-deep(.ant-table-cell-fix-right-first::after, .ant-table-cell-fix-right-last::after) {
+    content: '';
+    top: auto;
+    width: 0;
+  }
+
+  .btn-add {
+    width: 120px;
+    border-radius: 4px;
+  }
+</style>

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

@@ -0,0 +1,133 @@
+import { BasicColumn, FormSchema } from '/@/components/Table';
+import { listDictModel, uploadApi } from '/@/api/common/index';
+import dayjs from 'dayjs';
+
+export const columns: BasicColumn[] = [
+  {
+    title: '保养时间',
+    dataIndex: 'maintainTime',
+    align: 'left',
+  },
+  {
+    title: '保养方',
+    dataIndex: 'maintainCompany',
+    align: 'left',
+  },
+  {
+    title: '保养内容',
+    dataIndex: 'content',
+    align: 'left',
+  },
+  {
+    title: '保养费用(元)',
+    dataIndex: 'costYuan',
+    align: 'left',
+  },
+  {
+    title: '保养图片',
+    dataIndex: 'picture',
+    align: 'left',
+  },
+];
+
+export const upkeepDataFormSchema: FormSchema[] = [
+  {
+    field: 'deviceInfo',
+    component: 'PlainTitle',
+    defaultValue: '设备信息',
+  },
+  {
+    label: '设备编号',
+    field: 'uniqueCode',
+    component: 'Input',
+    slot: 'uniqueCode',
+    colProps: {
+      span: 12,
+    },
+  },
+  {
+    label: '设备厂家',
+    field: 'manufacturer',
+    component: 'Input',
+    slot: 'manufacturer',
+    colProps: {
+      span: 12,
+    },
+  },
+  {
+    label: '设备型号',
+    field: 'model',
+    component: 'Input',
+    slot: 'model',
+  },
+
+  {
+    field: 'maintenanceInfo',
+    component: 'PlainTitle',
+    defaultValue: '保养记录',
+  },
+
+  {
+    label: '保养时间',
+    field: 'maintainTime',
+    required: true,
+    component: 'DatePicker',
+    componentProps: {
+      format: 'YYYY-MM-DD HH:mm:ss',
+      placeholder: '请选择维修时间',
+      showTime: true,
+      getPopupContainer: () => document.body,
+      valueFormat: 'YYYY-MM-DD HH:mm:ss',
+    },
+    defaultValue: dayjs().format('YYYY-MM-DD HH:mm:ss'),
+  },
+  {
+    label: '保养方',
+    required: true,
+    field: 'maintainCompany',
+    component: 'ApiSelect',
+    componentProps: {
+      api: listDictModel,
+      params: {
+        dictCode: 'dmc',
+      },
+      placeholder: '请选择保养方',
+    },
+  },
+  {
+    label: '保养内容',
+    field: 'content',
+    required: true,
+    component: 'Input',
+    componentProps: {
+      placeholder: '请输入维修内容',
+    },
+  },
+  {
+    label: '保养费用(元)',
+    field: 'costYuan',
+    required: true,
+    component: 'InputNumber',
+    componentProps: {
+      placeholder: '请输入维修内容',
+      min: 0,
+    },
+  },
+  {
+    label: '保养图片',
+    field: 'files',
+    component: 'XTUpload',
+    componentProps: ({ formModel, schema }) => {
+      return {
+        api: uploadApi,
+        maxSize: 1,
+        maxNumber: 1,
+        helpText: '仅支持上传jpg/png图片,图片大小不超过1M',
+        accept: ['image/*'],
+        onChange: data => {
+          formModel[schema.field] = data;
+        },
+      };
+    },
+  },
+];

+ 259 - 0
src/views/biz/engineer/dialysis/upkeepList/index.vue

@@ -0,0 +1,259 @@
+<template>
+  <div>
+    <div class="flex justify-between mb-4">
+      <div>
+        <a-button type="primary" size="large" class="btn-add" @click="handleCreate"
+          ><template #icon> <Icon icon="icon-xt-add_default|iconfont" /> </template
+          >新增保养</a-button
+        >
+      </div>
+      <div>
+        <XTForm :form-data="formData" @change="callForm" />
+      </div>
+    </div>
+    <BasicTable @register="registerTable">
+      <template #bodyCell="{ column, record }">
+        <template v-if="column.key === 'maintainCompany'">
+          {{ formatDictValue(bizDictOptions.dmc, record.maintainCompany) }}
+        </template>
+        <template v-if="column.key === 'picture'">
+          <Image
+            v-if="record.files && record.files.length > 0"
+            :src="record.files[0].absolutePath"
+            :width="60"
+          />
+        </template>
+        <template v-if="column.key === 'action'">
+          <TableAction
+            :actions="[
+              {
+                auth: 'archives:patrolWard:edit',
+                icon: 'icon-xt-details_edit_default|iconfont',
+                tooltip: '编辑',
+                onClick: handleEdit.bind(null, record),
+              },
+              {
+                auth: 'archives:patrolWard:remove',
+                icon: 'icon-xt-details_delete_default|iconfont',
+                tooltip: '删除',
+                popConfirm: {
+                  title: '是否取消删除',
+                  placement: 'left',
+                  confirm: handleDelete.bind(null, record, column),
+                },
+              },
+            ]"
+          />
+        </template>
+      </template>
+    </BasicTable>
+    <FormModal @register="registerModal" @success="callSuccess" />
+  </div>
+</template>
+<script lang="ts" setup>
+  import { onBeforeMount, onMounted, reactive, ref } from 'vue';
+  import { XTForm } from '/@/components/XTForm/index';
+  import { BasicTable, useTable, TableAction } from '/@/components/Table';
+  import { useMessage } from '/@/hooks/web/useMessage';
+  import FormModal from './upkeepFormModal.vue';
+  import Icon from '/@/components/Icon/index';
+  import { columns } from './data';
+  import { listDictModelBatch } from '/@/api/common';
+  import { upkeepQueryPage, upkeepRemove } from '/@/api/biz/engineer/dialysisDeviceApi';
+  import { useModal } from '/@/components/Modal';
+  import { Image } from 'ant-design-vue';
+  import dayjs from 'dayjs';
+  import { formatDictValue } from '/@/utils';
+
+  const props = defineProps({
+    info: {
+      type: Object,
+      default: () => {},
+    },
+  });
+
+  const { createMessage } = useMessage();
+  const [registerModal, { openModal }] = useModal();
+  const tableSort = ref([
+    {
+      field: 'create_time',
+      direction: 'DESC',
+    },
+  ]) as any;
+
+  const dictsOption = ref([]) as any;
+  // formdata
+  const formData = [
+    {
+      label: '保养厂商',
+      name: 'maintainCompany',
+      componentType: 'Select',
+      dicts: dictsOption.value,
+      placeholder: '请选择维修方',
+      prefix: 'icon-xt-search',
+      width: 280,
+    },
+    {
+      name: 'patrolTime',
+      label: '保养时间',
+      componentType: 'RangePicker',
+      placeholder: '请选择保养时间',
+      format: 'YYYY-MM-DD',
+      valueFormat: 'YYYY-MM-DD',
+    },
+  ];
+  const formValue = reactive({
+    patrolTime: [],
+    maintainCompany: '',
+  });
+
+  const [registerTable, { reload }] = useTable({
+    api: upkeepQueryPage,
+    rowKey: 'id',
+    columns,
+    showIndexColumn: false,
+    bordered: true,
+    striped: false,
+    actionColumn: {
+      width: 200,
+      title: '操作',
+      dataIndex: 'action',
+    },
+    beforeFetch: handleBeforeFetch,
+    sortFn: handleSortFn,
+  });
+  const bizDictData = ref([{ key: 'dmc', dictCode: 'dmc' }]);
+
+  const bizDictOptions = reactive<any>({});
+  onBeforeMount(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 types = bizDictOptions.dmc;
+    dictsOption.value.push({
+      label: '全部',
+      value: '',
+    });
+    types.forEach(item => {
+      dictsOption.value.push({
+        label: item.label,
+        value: item.value,
+      });
+    });
+  });
+  onMounted(async () => {
+    // callForm({
+    //   patrolTime: [
+    //     dayjs().subtract(3, 'month').format('YYYY-MM-DD'),
+    //     dayjs().add(1, 'day').format('YYYY-MM-DD'),
+    //   ],
+    // });
+  });
+  // 新增按钮事件
+  function handleCreate() {
+    openModal(true, {
+      isUpdate: false,
+      record: { id: props.info.id },
+    });
+  }
+
+  // 编辑按钮事件
+  function handleEdit(record: Recordable) {
+    openModal(true, {
+      record: { id: props.info.id },
+      upkeepRecord: record,
+      isUpdate: true,
+    });
+  }
+
+  // 删除按钮事件
+  async function handleDelete(record: Recordable) {
+    await upkeepRemove([record.id]);
+    createMessage.success('删除成功!');
+    await reload();
+  }
+  // 表格点击字段排序
+  function handleSortFn(sortInfo) {
+    if (sortInfo?.order && sortInfo?.columnKey) {
+      // 默认单列排序
+      tableSort.value = [
+        {
+          field: sortInfo.columnKey,
+          direction: sortInfo.order.replace(/(\w+)(end)/g, '$1').toUpperCase(),
+        },
+      ];
+    }
+  }
+
+  // 表格请求之前,对参数进行处理, 添加默认 排序
+  function handleBeforeFetch(params) {
+    return {
+      ...params,
+      orders: tableSort.value,
+      deviceId: props.info?.id,
+      maintainTime:
+        formValue.patrolTime && formValue.patrolTime.length > 0
+          ? [
+              formValue.patrolTime[0],
+              dayjs(formValue.patrolTime[1]).add(1, 'day').format('YYYY-MM-DD'),
+            ]
+          : undefined,
+      maintainCompany: formValue.maintainCompany,
+    };
+  }
+
+  // 弹窗回调事件
+  async function callSuccess() {
+    await reload();
+  }
+
+  // 查询回调
+  async function callForm(data) {
+    formValue.maintainCompany = data.maintainCompany || '';
+    formValue.patrolTime = data.patrolTime || [];
+    await reload();
+  }
+</script>
+<style lang="less" scoped>
+  ::v-deep(.ant-btn-link) {
+    color: rgb(61 65 85 / 100%);
+  }
+
+  ::v-deep(.ant-input) {
+    border-color: #c2ccd4;
+    border-radius: 4px;
+  }
+
+  ::v-deep(.ant-input-affix-wrapper) {
+    border-color: #c2ccd4;
+    border-radius: 4px;
+  }
+
+  ::v-deep(.ant-picker) {
+    border-color: #c2ccd4;
+    border-radius: 4px;
+  }
+
+  ::v-deep(.ant-table-tbody > tr > td) {
+    border-right: 0 !important;
+    border-left: 0 !important;
+    border-bottom: 1px solid #f0f0f0;
+  }
+
+  ::v-deep(.ant-table-wrapper table) {
+    border: 0;
+  }
+
+  ::v-deep(.ant-table-cell-fix-right-first::after, .ant-table-cell-fix-right-last::after) {
+    content: '';
+    top: auto;
+    width: 0;
+  }
+
+  .btn-add {
+    width: 120px;
+    border-radius: 4px;
+  }
+</style>

+ 114 - 0
src/views/biz/engineer/dialysis/upkeepList/upkeepFormModal.vue

@@ -0,0 +1,114 @@
+<template>
+  <div class="modals">
+    <BasicModal
+      v-bind="$attrs"
+      destroyOnClose
+      @register="registerModal"
+      :title="getTitle"
+      @ok="handleSubmit"
+      :width="800"
+      @cancel="handleCancel"
+    >
+      <div class="!pl-8 !pt-4">
+        <BasicForm @register="registerForm">
+          <template #uniqueCode>
+            <span>{{ deviceInfo?.uniqueCode }}</span>
+          </template>
+          <template #manufacturer>
+            <span>{{ deviceInfo?.manufacturer }}</span>
+          </template>
+          <template #model>
+            <span>{{ deviceInfo?.model }}</span>
+          </template>
+        </BasicForm>
+      </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 { upkeepDataFormSchema } from './data';
+  import {
+    upkeepAdd,
+    upkeepDetail,
+    upkeepEdit,
+    engineerDialysisDeviceDetail,
+  } from '/@/api/biz/engineer/dialysisDeviceApi';
+  const emit = defineEmits(['success', 'cancel', 'register']);
+
+  const getTitle = computed(() => (!unref(isUpdate) ? '保养设备' : '保养设备'));
+  const isUpdate = ref(false);
+  const deviceInfo = ref();
+  const rowId = ref();
+
+  const { createMessage } = useMessage();
+  const [registerForm, { resetFields, validate, setFieldsValue }] = useForm({
+    layout: 'vertical',
+    showResetButton: true,
+    labelWidth: 100,
+    schemas: upkeepDataFormSchema,
+    showActionButtonGroup: false,
+    actionColOptions: {
+      span: 12,
+    },
+    baseColProps: {
+      span: 12,
+    },
+    wrapperCol: {
+      span: 22,
+    },
+  });
+  const [registerModal, { setModalProps, closeModal }] = useModalInner(async data => {
+    await resetFields();
+    setModalProps({ confirmLoading: false });
+    isUpdate.value = !!data?.isUpdate;
+    if (!data.record.name) {
+      deviceInfo.value = await engineerDialysisDeviceDetail(data.record?.id);
+    } else {
+      deviceInfo.value = data.record;
+    }
+    if (unref(isUpdate)) {
+      rowId.value = data.upkeepRecord.id;
+      const resData = await upkeepDetail(data.upkeepRecord.id);
+      console.log('resData::::', resData);
+      await setFieldsValue({
+        ...resData,
+      });
+    }
+  });
+
+  // 提交按钮事件
+  async function handleSubmit() {
+    try {
+      const values = await validate();
+      setModalProps({ confirmLoading: true });
+      values.picture = values.files && values.files.map(ele => ele.id);
+      if (!isUpdate.value) {
+        values.deviceIds = [deviceInfo.value.id];
+      } else {
+        values.deviceId = deviceInfo.value.id;
+      }
+      !unref(isUpdate)
+        ? await upkeepAdd({ ...values })
+        : await upkeepEdit({ ...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>

+ 368 - 0
src/views/biz/engineer/other/data.ts

@@ -0,0 +1,368 @@
+import { BasicColumn, FormSchema } from '/@/components/Table';
+import { listDictModel, uploadApi } from '/@/api/common';
+import dayjs from 'dayjs';
+
+export const columns: BasicColumn[] = [
+  {
+    title: '设备编号',
+    dataIndex: 'uniqueCode',
+  },
+  {
+    title: '设备类型',
+    dataIndex: 'deviceType',
+  },
+  {
+    title: '设备名称',
+    dataIndex: 'name',
+  },
+  {
+    title: '设备厂家',
+    dataIndex: 'manufacturer',
+  },
+  {
+    title: '设备型号',
+    dataIndex: 'model',
+  },
+  {
+    title: '入账日期',
+    dataIndex: 'purchaseDate',
+  },
+  {
+    title: '采购金额(元)',
+    dataIndex: 'priceYuan',
+  },
+  {
+    title: '生产日期',
+    dataIndex: 'produceDate',
+  },
+  {
+    title: '保修期限',
+    dataIndex: 'warrantyPeriod',
+    width: 100,
+  },
+  {
+    title: '是否在保',
+    dataIndex: 'isWarranty',
+    width: 100,
+  },
+];
+// 表单新增编辑
+export const dataFormSchema: FormSchema[] = [
+  {
+    label: '设备编号',
+    field: 'uniqueCode',
+    required: true,
+    component: 'Input',
+    componentProps: {
+      placeholder: '请输入设备编码',
+    },
+  },
+  {
+    label: '设备名称',
+    field: 'name',
+    required: true,
+    component: 'Input',
+    componentProps: {
+      placeholder: '请输入设备名称',
+    },
+  },
+  {
+    label: '设备类型',
+    field: 'deviceType',
+    required: true,
+    component: 'ApiSelect',
+    componentProps: {
+      api: listDictModel,
+      params: {
+        dictCode: 'od',
+      },
+      placeholder: '请选择设备类型',
+    },
+  },
+  {
+    label: '厂家',
+    field: 'manufacturer',
+    required: true,
+    component: 'Input',
+    componentProps: {
+      placeholder: '请输入设备厂家',
+    },
+  },
+  {
+    label: '型号',
+    field: 'model',
+    required: true,
+    component: 'Input',
+    componentProps: {
+      placeholder: '请输入型号',
+    },
+  },
+  {
+    label: '入账日期',
+    field: 'purchaseDate',
+    component: 'DatePicker',
+    required: true,
+    componentProps: {
+      format: 'YYYY-MM-DD',
+      placeholder: '请选择入账日期',
+      getPopupContainer: () => document.body,
+      valueFormat: 'YYYY-MM-DD',
+    },
+  },
+  {
+    label: '生产日期',
+    field: 'produceDate',
+    component: 'DatePicker',
+    required: true,
+    componentProps: {
+      format: 'YYYY-MM-DD',
+      placeholder: '请选择生产日期',
+      getPopupContainer: () => document.body,
+      valueFormat: 'YYYY-MM-DD',
+    },
+  },
+  {
+    label: '采购金额(元)',
+    field: 'priceYuan',
+    component: 'Input',
+    componentProps: {
+      placeholder: '请输入采购金额',
+    },
+  },
+  {
+    label: '保修期限(年)',
+    field: 'warrantyPeriod',
+    component: 'InputNumber',
+    componentProps: {
+      placeholder: '请输入保修期限',
+      min: 0,
+      step: 1,
+    },
+  },
+];
+
+export const maintenanceDataFormSchema: FormSchema[] = [
+  {
+    field: 'deviceInfo',
+    component: 'PlainTitle',
+    defaultValue: '设备信息',
+  },
+  {
+    label: '设备编号',
+    field: 'uniqueCode',
+    component: 'Input',
+    slot: 'uniqueCode',
+    colProps: {
+      span: 8,
+    },
+  },
+  {
+    label: '设备厂家',
+    field: 'manufacturer',
+    component: 'Input',
+    slot: 'manufacturer',
+    colProps: {
+      span: 8,
+    },
+  },
+  {
+    label: '设备型号',
+    field: 'model',
+    component: 'Input',
+    slot: 'model',
+    colProps: {
+      span: 8,
+    },
+  },
+
+  {
+    field: 'maintenanceInfo',
+    component: 'PlainTitle',
+    defaultValue: '维修记录',
+  },
+
+  {
+    label: '维修时间',
+    field: 'maintenanceTime',
+    required: true,
+    component: 'DatePicker',
+    componentProps: {
+      format: 'YYYY-MM-DD HH:mm:ss',
+      placeholder: '请选择维修时间',
+      showTime: true,
+      getPopupContainer: () => document.body,
+      valueFormat: 'YYYY-MM-DD HH:mm:ss',
+    },
+    defaultValue: dayjs().format('YYYY-MM-DD HH:mm:ss'),
+  },
+
+  {
+    label: '维修方',
+    field: 'maintenanceCompany',
+    component: 'ApiSelect',
+    componentProps: {
+      api: listDictModel,
+      params: {
+        dictCode: 'dmc',
+      },
+      placeholder: '请选择维修方',
+    },
+  },
+  {
+    label: '异常信息',
+    field: 'malfunctionMessage',
+    component: 'Input',
+    componentProps: {
+      placeholder: '请输入异常信息',
+    },
+    colProps: {
+      span: 12,
+    },
+  },
+  {
+    label: '维修内容',
+    field: 'repairContent',
+    component: 'Input',
+    componentProps: {
+      placeholder: '请输入维修内容',
+    },
+    colProps: {
+      span: 12,
+    },
+  },
+  {
+    label: '维修费用(元)',
+    field: 'costYuan',
+    component: 'Input',
+    componentProps: {
+      placeholder: '请输入维修费用(元)',
+    },
+    colProps: {
+      span: 12,
+    },
+  },
+  {
+    label: '解决进度',
+    field: 'schedule',
+    component: 'ApiSelect',
+    componentProps: {
+      api: listDictModel,
+      params: {
+        dictCode: 'rp',
+      },
+      placeholder: '请选择解决进度',
+    },
+  },
+  {
+    label: '维修图片',
+    field: 'files',
+    component: 'XTUpload',
+    componentProps: ({ formModel, schema }) => {
+      return {
+        api: uploadApi,
+        maxSize: 1,
+        maxNumber: 1,
+        helpText: '仅支持上传jpg/png图片,图片大小不超过1M',
+        accept: ['image/*'],
+        onChange: data => {
+          formModel[schema.field] = data;
+        },
+      };
+    },
+    colProps: {
+      span: 24,
+    },
+  },
+];
+
+export const upkeepDataFormSchema: FormSchema[] = [
+  {
+    field: 'deviceInfo',
+    component: 'PlainTitle',
+    defaultValue: '设备信息',
+  },
+  {
+    label: '设备编号',
+    field: 'uniqueCode',
+    component: 'Input',
+    slot: 'uniqueCode',
+    colProps: {
+      span: 12,
+    },
+  },
+  {
+    label: '设备厂家',
+    field: 'manufacturer',
+    component: 'Input',
+    slot: 'manufacturer',
+    colProps: {
+      span: 12,
+    },
+  },
+  {
+    label: '设备型号',
+    field: 'model',
+    component: 'Input',
+    slot: 'model',
+  },
+
+  {
+    field: 'maintenanceInfo',
+    component: 'PlainTitle',
+    defaultValue: '保养记录',
+  },
+
+  {
+    label: '保养时间',
+    field: 'maintainTime',
+    required: true,
+    component: 'DatePicker',
+    componentProps: {
+      format: 'YYYY-MM-DD HH:mm:ss',
+      placeholder: '请选择维修时间',
+      showTime: true,
+      getPopupContainer: () => document.body,
+      valueFormat: 'YYYY-MM-DD HH:mm:ss',
+    },
+    defaultValue: dayjs().format('YYYY-MM-DD HH:mm:ss'),
+  },
+  {
+    label: '保养内容',
+    field: 'content',
+    required: true,
+    component: 'Input',
+    componentProps: {
+      placeholder: '请输入维修内容',
+    },
+  },
+  {
+    label: '保养方',
+    required: true,
+    field: 'maintainCompany',
+    component: 'ApiSelect',
+    componentProps: {
+      api: listDictModel,
+      params: {
+        dictCode: 'dmc',
+      },
+      placeholder: '请选择保养方',
+    },
+  },
+  {
+    label: '保养图片',
+    field: 'files',
+    component: 'XTUpload',
+    componentProps: ({ formModel, schema }) => {
+      return {
+        api: uploadApi,
+        maxSize: 1,
+        maxNumber: 1,
+        helpText: '仅支持上传jpg/png图片,图片大小不超过1M',
+        accept: ['image/*'],
+        onChange: data => {
+          formModel[schema.field] = data;
+        },
+      };
+    },
+  },
+];

+ 7 - 0
src/views/biz/engineer/other/detail/data.ts

@@ -0,0 +1,7 @@
+export const BasicTab = [
+  { key: 'deviceBasic', value: 0, title: '设备信息' },
+  { key: 'maintenanceList', value: 1, title: '维修记录' },
+  { key: 'upkeepList', value: 2, title: '保养记录' },
+];
+
+export const BasicTabActive = BasicTab[0].key;

+ 64 - 0
src/views/biz/engineer/other/detail/index.vue

@@ -0,0 +1,64 @@
+<template>
+  <div class="m-4">
+    <div class="mb-4">
+      <XTTitle :title="title" :go-back="true" />
+    </div>
+    <div class="py-2 bg-white">
+      <a-tabs v-model:activeKey="activeKey" :destroyInactiveTabPane="true">
+        <a-tab-pane
+          v-for="item in tabData"
+          :key="item.key"
+          :tab="item.title"
+          :class="'tab-' + item.key"
+        >
+          <!-- 基础信息 0 -->
+          <div v-if="item.value == 0 && info.id">
+            <DeviceBasic :info="info" />
+          </div>
+          <!-- 维修记录 1 -->
+          <div v-if="item.value == 1 && info.id">
+            <MaintenanceList :info="info" />
+          </div>
+          <!-- 保养记录 2 -->
+          <div v-if="item.value == 2 && info.id">
+            <UpkeepList :info="info" />
+          </div>
+        </a-tab-pane>
+      </a-tabs>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+  import { ref } from 'vue';
+  import { BasicTab, BasicTabActive } from './data';
+  import DeviceBasic from '../deviceBasic/index.vue';
+  import MaintenanceList from '../maintenanceList/index.vue';
+  import UpkeepList from '../upkeepList/index.vue';
+  import { useRoute } from 'vue-router';
+  import { XTTitle } from '/@/components/XTTitle/index';
+
+  const route = useRoute();
+  console.log('🚀 ~ file: index.vue:25 ~ route:', route);
+  const info = ref({
+    id: String(route.query?.id),
+    name: route.query?.name,
+  });
+  const activeKey = ref(BasicTabActive);
+  const tabData = ref(BasicTab);
+  const title = `设备详情 — ${info.value.name}`;
+</script>
+
+<style lang="less" scoped>
+  ::v-deep(.ant-tabs-tab) {
+    padding: 12px;
+  }
+
+  ::v-deep(.ant-tabs-tabpane) {
+    padding: 12px;
+  }
+
+  .tab-medicalDocuments {
+    padding: 0;
+  }
+</style>

+ 17 - 0
src/views/biz/engineer/other/deviceBasic/data.ts

@@ -0,0 +1,17 @@
+export const BasicData = [
+  {
+    title: '设备信息',
+    icon: 'icon-xt-edit_default',
+    type: 'basic',
+    data: [
+      { field: 'uniqueCode', label: '设备编号', value: '' },
+      { field: 'deviceType', label: '设备类型', value: '', dict: true },
+      { field: 'manufacturer', label: '厂家', value: '' },
+      { field: 'model', label: '型号', value: '' },
+      { field: 'purchaseDate', label: '入账日期', value: '' },
+      { field: 'produceDate', label: '生产日期', value: '' },
+      { field: 'priceYuan', label: '采购金额(元)', value: '' },
+      { field: 'warrantyPeriod', label: '保修期限(年)', value: '' },
+    ],
+  },
+];

+ 50 - 0
src/views/biz/engineer/other/deviceBasic/index.vue

@@ -0,0 +1,50 @@
+<template>
+  <div>
+    <div class="mx-2 mb-6" v-for="item in basicData" :key="item.type">
+      <DescCard :title="item.title" :type="item.type" :data="item.data" :id="info.id" />
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+  import { ref, reactive } from 'vue';
+  import DescCard from '/@/components/XTCard/src/DescCard.vue';
+  import { BasicData } from './data';
+  import { onMounted } from 'vue';
+  import { formatDictValue } from '/@/utils';
+  import { otherDetail } from '/@/api/biz/engineer/otherApi';
+  import { listDictModelBatch } from '/@/api/common';
+
+  const props = defineProps({
+    info: {
+      type: Object,
+      default: () => {},
+    } as any,
+  });
+  const bizDictOptions = reactive({
+    deviceType: [],
+  });
+  const bizDictData = ref([{ key: 'deviceType', dictCode: 'od' }]);
+  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];
+    }
+    await getData();
+  });
+  const basicData = ref(BasicData as any);
+  // 获取数据
+  async function getData() {
+    const res = await otherDetail(props.info?.id);
+    basicData.value[0].data.forEach(ele => {
+      if (ele.dict) {
+        ele.value = formatDictValue(bizDictOptions[ele.field], res[ele.field]);
+      } else {
+        ele.value = res[ele.field];
+      }
+    });
+  }
+</script>
+
+<style lang="less" scoped></style>

+ 90 - 0
src/views/biz/engineer/other/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 { otherAdd, otherDetail, otherEdit } from '/@/api/biz/engineer/otherApi';
+
+  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 otherDetail(data.record.id);
+      console.log('resData::::', resData);
+      await setFieldsValue({
+        ...resData,
+      });
+    }
+  });
+
+  // 提交按钮事件
+  async function handleSubmit() {
+    try {
+      const values = await validate();
+      setModalProps({ confirmLoading: true });
+      !unref(isUpdate)
+        ? await otherAdd({ ...values })
+        : await otherEdit({ ...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>

+ 286 - 3
src/views/biz/engineer/other/index.vue

@@ -1,7 +1,290 @@
 <template>
-  <div> 占位符 </div>
+  <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 === 'isWarranty'">
+          <span> {{ commonDict(record.isWarranty, 0) }}</span>
+        </template>
+        <template v-if="column.key === 'name'">
+          <a @click="goDetail(record)">{{ record.name }}</a>
+        </template>
+        <template v-if="column.key === 'action'">
+          <TableAction
+            :actions="[
+              {
+                label: '编辑',
+                auth: 'biz:consumable:edit',
+                tooltip: '编辑',
+                onClick: handleEdit.bind(null, record),
+              },
+              {
+                label: '维修',
+                auth: 'biz:consumable:edit',
+                tooltip: '维修',
+                onClick: handleMaintenance.bind(null, record),
+              },
+              {
+                label: '保养',
+                auth: 'biz:consumable:edit',
+                tooltip: '保养',
+                onClick: handleUpkeep.bind(null, record),
+              },
+            ]"
+          />
+        </template>
+      </template>
+    </BasicTable>
+    <FormModal @register="registerModal" @success="callSuccess" @cancel="handleCancel" />
+    <MaintenanceFormModal
+      @register="registerMaintenanceModal"
+      @success="callSuccess"
+      @cancel="handleCancel"
+    />
+    <UpkeepFormModal
+      @register="registerUpkeepModal"
+      @success="callSuccess"
+      @cancel="handleCancel"
+    />
+  </div>
 </template>
+<script lang="ts" setup>
+  import { onBeforeMount, ref } from 'vue';
+  import { BasicTable, useTable, TableAction } from '/@/components/TableCard';
+  import FormModal from './formModal.vue';
+  import MaintenanceFormModal from './maintenanceFormModal.vue';
+  import UpkeepFormModal from './upkeepFormModal.vue';
+  import { commonDict } from '/@/utils';
+  import { columns } from './data';
+  import { otherQueryPage, otherRemove, otherTypeCount } from '/@/api/biz/engineer/otherApi';
+  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';
 
-<script setup lang="ts"></script>
+  import { useRouter } from 'vue-router';
 
-<style lang="less" scoped></style>
+  // 路由跳转
+  const router = useRouter();
+  // 标题数据
+  const titleData = [
+    {
+      type: 'add',
+      btnIcon: 'icon-xt-add_default',
+      auth: ['biz:consumable:add'],
+      btnText: '新增设备',
+    },
+  ];
+  // formdata
+  const formData = [
+    {
+      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 responsesupplierCategoryOptions = ref();
+  onBeforeMount(async () => {
+    responsesupplierCategoryOptions.value = await listDictModel({ dictCode: 'pht' });
+    getTab();
+  });
+
+  const [registerModal, { openModal }] = useModal();
+  const [registerMaintenanceModal, { openModal: openMaintenanceModal }] = useModal();
+  const [registerUpkeepModal, { openModal: openUpkeepModal }] = useModal();
+  const tableSort = ref([
+    {
+      field: 'create_time',
+      direction: 'DESC',
+    },
+  ]) as any;
+
+  const [registerTable, { reload, clearSelectedRowKeys }] = useTable({
+    api: otherQueryPage,
+    batchDelApi: otherRemove,
+    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,
+      });
+    }
+  }
+
+  // 表格点击字段排序
+  function handleSortFn(sortInfo) {
+    if (sortInfo?.order && sortInfo?.columnKey) {
+      // 默认单列排序
+      tableSort.value = [
+        {
+          field: sortInfo.columnKey,
+          direction: sortInfo.order.replace(/(\w+)(end)/g, '$1').toUpperCase(),
+        },
+      ];
+    }
+  }
+  // 打开维修弹框
+  function handleMaintenance(record) {
+    openMaintenanceModal(true, {
+      record,
+    });
+  }
+  // 打开保养弹框
+  function handleUpkeep(record) {
+    openUpkeepModal(true, {
+      record,
+    });
+  }
+
+  // 表格请求之前,对参数进行处理, 添加默认 排序
+  async function handleBeforeFetch(params) {
+    return {
+      ...params,
+      orders: tableSort.value,
+      uniqueCode: searchNames.value == '' ? undefined : searchNames.value,
+      deviceType: tabSelected.value == '' ? undefined : tabSelected.value,
+    };
+  }
+
+  async function getTab() {
+    typeOptions.value = await listDictModel({ dictCode: 'od' });
+    const typeNums = await otherTypeCount(); // 获取各类型数量
+    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 : '';
+    await reload();
+  }
+
+  // 打开详情页面
+  function goDetail(record) {
+    console.log('record::', record);
+    router.push({
+      path: '/bizEngineer/otherDetail',
+      query: {
+        id: record.id,
+        name: record.name,
+      },
+    });
+  }
+</script>
+<style lang="less" scoped>
+  .table-dot {
+    display: inline-block;
+    width: 10px;
+    height: 10px;
+    margin-right: 6px;
+    border-radius: 50%;
+  }
+
+  ::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>

+ 111 - 0
src/views/biz/engineer/other/maintenanceFormModal.vue

@@ -0,0 +1,111 @@
+<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">
+          <template #uniqueCode>
+            <span>{{ deviceInfo?.uniqueCode }}</span>
+          </template>
+          <template #manufacturer>
+            <span>{{ deviceInfo?.manufacturer }}</span>
+          </template>
+          <template #model>
+            <span>{{ deviceInfo?.model }}</span>
+          </template>
+        </BasicForm>
+      </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 { maintenanceDataFormSchema } from './data';
+  import { otherDetail } from '@/api/biz/engineer/otherApi';
+  import {
+    maintenanceAdd,
+    maintenanceDetail,
+    maintenanceEdit,
+  } from '/@/api/biz/engineer/maintenanceApi';
+
+  const emit = defineEmits(['success', 'cancel', 'register']);
+
+  const getTitle = computed(() => (!unref(isUpdate) ? '维修设备' : '编辑设备'));
+  const isUpdate = ref(false);
+  const deviceInfo = ref();
+  const rowId = ref();
+
+  const { createMessage } = useMessage();
+  const [registerForm, { resetFields, validate, setFieldsValue }] = useForm({
+    layout: 'vertical',
+    showResetButton: true,
+    labelWidth: 100,
+    schemas: maintenanceDataFormSchema,
+    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 (!data.record.name) {
+      deviceInfo.value = await otherDetail(data.record?.id);
+    } else {
+      deviceInfo.value = data.record;
+    }
+    if (unref(isUpdate)) {
+      rowId.value = data.maintenanceRecord.id;
+      const resData = await maintenanceDetail(data.maintenanceRecord.id);
+      console.log('resData::::', resData);
+      await setFieldsValue({
+        ...resData,
+      });
+    }
+  });
+
+  // 提交按钮事件
+  async function handleSubmit() {
+    try {
+      const values = await validate();
+      setModalProps({ confirmLoading: true });
+      values.picture = values.files && values.files.map(ele => ele.id);
+      values.deviceId = deviceInfo.value.id;
+      !unref(isUpdate)
+        ? await maintenanceAdd({ ...values })
+        : await maintenanceEdit({ ...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>

+ 39 - 0
src/views/biz/engineer/other/maintenanceList/data.ts

@@ -0,0 +1,39 @@
+import { BasicColumn } from '/@/components/Table';
+
+export const columns: BasicColumn[] = [
+  {
+    title: '维修时间',
+    dataIndex: 'maintenanceTime',
+    align: 'left',
+  },
+  {
+    title: '维修方',
+    dataIndex: 'maintenanceCompany',
+    align: 'left',
+  },
+  {
+    title: '异常信息',
+    dataIndex: 'malfunctionMessage',
+    align: 'left',
+  },
+  {
+    title: '维修内容',
+    dataIndex: 'repairContent',
+    align: 'left',
+  },
+  {
+    title: '解决进度',
+    dataIndex: 'schedule',
+    align: 'left',
+  },
+  {
+    title: '维修费用(元)',
+    dataIndex: 'costYuan',
+    align: 'left',
+  },
+  {
+    title: '维修图片',
+    dataIndex: 'picture',
+    align: 'left',
+  },
+];

+ 266 - 0
src/views/biz/engineer/other/maintenanceList/index.vue

@@ -0,0 +1,266 @@
+<template>
+  <div>
+    <div class="flex justify-between mb-4">
+      <div>
+        <a-button type="primary" size="large" class="btn-add" @click="handleCreate"
+          ><template #icon> <Icon icon="icon-xt-add_default|iconfont" /> </template
+          >新增维修</a-button
+        >
+      </div>
+      <div>
+        <XTForm :form-data="formData" @change="callForm" />
+      </div>
+    </div>
+    <BasicTable @register="registerTable">
+      <template #bodyCell="{ column, record }">
+        <template v-if="column.key === 'maintenanceCompany'">
+          <span>{{ formatDictValue(bizDictOptions.dmc, record.maintenanceCompany) }}</span>
+        </template>
+        <template v-if="column.key === 'schedule'">
+          <span>{{ formatDictValue(bizDictOptions.rp, record.schedule) }}</span>
+        </template>
+        <template v-if="column.key === 'picture'">
+          <Image
+            v-if="record.files && record.files.length > 0"
+            :src="record.files[0].absolutePath"
+            :width="60"
+          />
+        </template>
+        <template v-if="column.key === 'action'">
+          <TableAction
+            :actions="[
+              {
+                auth: 'archives:patrolWard:edit',
+                icon: 'icon-xt-details_edit_default|iconfont',
+                tooltip: '编辑',
+                onClick: handleEdit.bind(null, record),
+              },
+              {
+                auth: 'archives:patrolWard:remove',
+                icon: 'icon-xt-details_delete_default|iconfont',
+                tooltip: '删除',
+                popConfirm: {
+                  title: '是否取消删除',
+                  placement: 'left',
+                  confirm: handleDelete.bind(null, record, column),
+                },
+              },
+            ]"
+          />
+        </template>
+      </template>
+    </BasicTable>
+    <FormModal @register="registerModal" @success="callSuccess" />
+  </div>
+</template>
+<script lang="ts" setup>
+  import { onBeforeMount, onMounted, reactive, ref } from 'vue';
+  import { XTForm } from '/@/components/XTForm/index';
+  import { BasicTable, useTable, TableAction } from '/@/components/Table';
+  import { useMessage } from '/@/hooks/web/useMessage';
+  import FormModal from '../maintenanceFormModal.vue';
+
+  import Icon from '/@/components/Icon/index';
+  import { columns } from './data';
+  import { listDictModelBatch } from '/@/api/common';
+  import { maintenanceQueryPage, maintenanceRemove } from '/@/api/biz/engineer/maintenanceApi';
+  import { useModal } from '/@/components/Modal';
+  import { Image } from 'ant-design-vue';
+  import dayjs from 'dayjs';
+  import { formatDictValue } from '/@/utils';
+
+  const props = defineProps({
+    info: {
+      type: Object,
+      default: () => {},
+    },
+  });
+
+  const { createMessage } = useMessage();
+  const [registerModal, { openModal }] = useModal();
+  const tableSort = ref([
+    {
+      field: 'create_time',
+      direction: 'DESC',
+    },
+  ]) as any;
+
+  const dictsOption = ref([]) as any;
+  // formdata
+  const formData = [
+    {
+      label: '维修方',
+      name: 'maintenanceCompany',
+      componentType: 'Select',
+      dicts: dictsOption.value,
+      placeholder: '请选择维修方',
+      prefix: 'icon-xt-search',
+      width: 280,
+    },
+    {
+      name: 'patrolTime',
+      label: '维修时间',
+      componentType: 'RangePicker',
+      placeholder: '请选择维修时间',
+      format: 'YYYY-MM-DD',
+      valueFormat: 'YYYY-MM-DD',
+    },
+  ];
+  const formValue = reactive({
+    patrolTime: [],
+    maintenanceCompany: '',
+  });
+
+  const [registerTable, { reload }] = useTable({
+    api: maintenanceQueryPage,
+    rowKey: 'id',
+    columns,
+    showIndexColumn: false,
+    bordered: true,
+    striped: false,
+    actionColumn: {
+      width: 200,
+      title: '操作',
+      dataIndex: 'action',
+    },
+    beforeFetch: handleBeforeFetch,
+    sortFn: handleSortFn,
+  });
+  const bizDictData = ref([
+    { key: 'dmc', dictCode: 'dmc' },
+    { key: 'rp', dictCode: 'rp' },
+  ]);
+
+  const bizDictOptions = reactive<any>({});
+  onBeforeMount(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 types = bizDictOptions.dmc;
+    dictsOption.value.push({
+      label: '全部',
+      value: '',
+    });
+    types.forEach(item => {
+      dictsOption.value.push({
+        label: item.label,
+        value: item.value,
+      });
+    });
+  });
+  onMounted(async () => {
+    // callForm({
+    //   patrolTime: [
+    //     dayjs().subtract(3, 'month').format('YYYY-MM-DD'),
+    //     dayjs().add(1, 'day').format('YYYY-MM-DD'),
+    //   ],
+    // });
+  });
+  // 新增按钮事件
+  function handleCreate() {
+    openModal(true, {
+      isUpdate: false,
+      record: { id: props.info.id },
+    });
+  }
+
+  // 编辑按钮事件
+  function handleEdit(record: Recordable) {
+    openModal(true, {
+      record: { id: props.info.id },
+      maintenanceRecord: record,
+      isUpdate: true,
+    });
+  }
+
+  // 删除按钮事件
+  async function handleDelete(record: Recordable) {
+    await maintenanceRemove([record.id]);
+    createMessage.success('删除成功!');
+    await reload();
+  }
+  // 表格点击字段排序
+  function handleSortFn(sortInfo) {
+    if (sortInfo?.order && sortInfo?.columnKey) {
+      // 默认单列排序
+      tableSort.value = [
+        {
+          field: sortInfo.columnKey,
+          direction: sortInfo.order.replace(/(\w+)(end)/g, '$1').toUpperCase(),
+        },
+      ];
+    }
+  }
+
+  // 表格请求之前,对参数进行处理, 添加默认 排序
+  function handleBeforeFetch(params) {
+    return {
+      ...params,
+      orders: tableSort.value,
+      deviceId: props.info?.id,
+      maintenanceTime:
+        formValue.patrolTime && formValue.patrolTime.length > 0
+          ? [
+              formValue.patrolTime[0],
+              dayjs(formValue.patrolTime[1]).add(1, 'day').format('YYYY-MM-DD'),
+            ]
+          : undefined,
+      maintenanceCompany: formValue.maintenanceCompany,
+    };
+  }
+
+  // 弹窗回调事件
+  async function callSuccess() {
+    await reload();
+  }
+
+  // 查询回调
+  async function callForm(data) {
+    formValue.maintenanceCompany = data.maintenanceCompany || '';
+    formValue.patrolTime = data.patrolTime || [];
+    await reload();
+  }
+</script>
+<style lang="less" scoped>
+  ::v-deep(.ant-btn-link) {
+    color: rgb(61 65 85 / 100%);
+  }
+
+  ::v-deep(.ant-input) {
+    border-color: #c2ccd4;
+    border-radius: 4px;
+  }
+
+  ::v-deep(.ant-input-affix-wrapper) {
+    border-color: #c2ccd4;
+    border-radius: 4px;
+  }
+
+  ::v-deep(.ant-picker) {
+    border-color: #c2ccd4;
+    border-radius: 4px;
+  }
+
+  ::v-deep(.ant-table-tbody > tr > td) {
+    border-right: 0 !important;
+    border-left: 0 !important;
+    border-bottom: 1px solid #f0f0f0;
+  }
+
+  ::v-deep(.ant-table-wrapper table) {
+    border: 0;
+  }
+
+  ::v-deep(.ant-table-cell-fix-right-first::after, .ant-table-cell-fix-right-last::after) {
+    content: '';
+    top: auto;
+    width: 0;
+  }
+
+  .btn-add {
+    width: 120px;
+    border-radius: 4px;
+  }
+</style>

+ 106 - 0
src/views/biz/engineer/other/upkeepFormModal.vue

@@ -0,0 +1,106 @@
+<template>
+  <div class="modals">
+    <BasicModal
+      v-bind="$attrs"
+      destroyOnClose
+      @register="registerModal"
+      :title="getTitle"
+      @ok="handleSubmit"
+      :width="600"
+      @cancel="handleCancel"
+    >
+      <div class="!pl-8 !pt-4">
+        <BasicForm @register="registerForm">
+          <template #uniqueCode>
+            <span>{{ deviceInfo?.uniqueCode }}</span>
+          </template>
+          <template #manufacturer>
+            <span>{{ deviceInfo?.manufacturer }}</span>
+          </template>
+          <template #model>
+            <span>{{ deviceInfo?.model }}</span>
+          </template>
+        </BasicForm>
+      </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 { upkeepDataFormSchema } from './data';
+  import { otherDetail } from '@/api/biz/engineer/otherApi';
+  import { upkeepAdd, upkeepDetail, upkeepEdit } from '/@/api/biz/engineer/otherUpkeepApi';
+  const emit = defineEmits(['success', 'cancel', 'register']);
+
+  const getTitle = computed(() => (!unref(isUpdate) ? '保养设备' : '保养设备'));
+  const isUpdate = ref(false);
+  const deviceInfo = ref();
+  const rowId = ref();
+
+  const { createMessage } = useMessage();
+  const [registerForm, { resetFields, validate, setFieldsValue }] = useForm({
+    layout: 'vertical',
+    showResetButton: true,
+    labelWidth: 100,
+    schemas: upkeepDataFormSchema,
+    showActionButtonGroup: false,
+    actionColOptions: {
+      span: 24,
+    },
+    baseColProps: {
+      span: 24,
+    },
+    wrapperCol: {
+      span: 22,
+    },
+  });
+  const [registerModal, { setModalProps, closeModal }] = useModalInner(async data => {
+    await resetFields();
+    setModalProps({ confirmLoading: false });
+    isUpdate.value = !!data?.isUpdate;
+    if (!data.record.name) {
+      deviceInfo.value = await otherDetail(data.record?.id);
+    } else {
+      deviceInfo.value = data.record;
+    }
+    if (unref(isUpdate)) {
+      rowId.value = data.upkeepRecord.id;
+      const resData = await upkeepDetail(data.upkeepRecord.id);
+      console.log('resData::::', resData);
+      await setFieldsValue({
+        ...resData,
+      });
+    }
+  });
+
+  // 提交按钮事件
+  async function handleSubmit() {
+    try {
+      const values = await validate();
+      setModalProps({ confirmLoading: true });
+      values.picture = values.files && values.files.map(ele => ele.id);
+      values.deviceId = deviceInfo.value.id;
+      !unref(isUpdate)
+        ? await upkeepAdd({ ...values })
+        : await upkeepEdit({ ...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>

+ 24 - 0
src/views/biz/engineer/other/upkeepList/data.ts

@@ -0,0 +1,24 @@
+import { BasicColumn } from '/@/components/Table';
+
+export const columns: BasicColumn[] = [
+  {
+    title: '保养时间',
+    dataIndex: 'maintainTime',
+    align: 'left',
+  },
+  {
+    title: '保养方',
+    dataIndex: 'maintainCompany',
+    align: 'left',
+  },
+  {
+    title: '保养内容',
+    dataIndex: 'content',
+    align: 'left',
+  },
+  {
+    title: '保养图片',
+    dataIndex: 'picture',
+    align: 'left',
+  },
+];

+ 265 - 0
src/views/biz/engineer/other/upkeepList/index.vue

@@ -0,0 +1,265 @@
+<template>
+  <div>
+    <div class="flex justify-between mb-4">
+      <div>
+        <a-button type="primary" size="large" class="btn-add" @click="handleCreate"
+          ><template #icon> <Icon icon="icon-xt-add_default|iconfont" /> </template
+          >新增保养</a-button
+        >
+      </div>
+      <div>
+        <XTForm :form-data="formData" @change="callForm" />
+      </div>
+    </div>
+    <BasicTable @register="registerTable">
+      <template #bodyCell="{ column, record }">
+        <template v-if="column.key === 'maintainCompany'">
+          <span>{{ formatDictValue(bizDictOptions.dmc, record.maintainCompany) }}</span>
+        </template>
+        <template v-if="column.key === 'schedule'">
+          <span>{{ formatDictValue(bizDictOptions.rp, record.schedule) }}</span>
+        </template>
+        <template v-if="column.key === 'picture'">
+          <Image
+            v-if="record.files && record.files.length > 0"
+            :src="record.files[0].absolutePath"
+            :width="60"
+          />
+        </template>
+        <template v-if="column.key === 'action'">
+          <TableAction
+            :actions="[
+              {
+                auth: 'archives:patrolWard:edit',
+                icon: 'icon-xt-details_edit_default|iconfont',
+                tooltip: '编辑',
+                onClick: handleEdit.bind(null, record),
+              },
+              {
+                auth: 'archives:patrolWard:remove',
+                icon: 'icon-xt-details_delete_default|iconfont',
+                tooltip: '删除',
+                popConfirm: {
+                  title: '是否取消删除',
+                  placement: 'left',
+                  confirm: handleDelete.bind(null, record, column),
+                },
+              },
+            ]"
+          />
+        </template>
+      </template>
+    </BasicTable>
+    <FormModal @register="registerModal" @success="callSuccess" />
+  </div>
+</template>
+<script lang="ts" setup>
+  import { onBeforeMount, onMounted, reactive, ref } from 'vue';
+  import { XTForm } from '/@/components/XTForm/index';
+  import { BasicTable, useTable, TableAction } from '/@/components/Table';
+  import { useMessage } from '/@/hooks/web/useMessage';
+  import FormModal from '../upkeepFormModal.vue';
+  import Icon from '/@/components/Icon/index';
+  import { columns } from './data';
+  import { listDictModelBatch } from '/@/api/common';
+  import { upkeepQueryPage, upkeepRemove } from '/@/api/biz/engineer/otherUpkeepApi';
+  import { useModal } from '/@/components/Modal';
+  import { Image } from 'ant-design-vue';
+  import dayjs from 'dayjs';
+  import { formatDictValue } from '/@/utils';
+
+  const props = defineProps({
+    info: {
+      type: Object,
+      default: () => {},
+    },
+  });
+
+  const { createMessage } = useMessage();
+  const [registerModal, { openModal }] = useModal();
+  const tableSort = ref([
+    {
+      field: 'create_time',
+      direction: 'DESC',
+    },
+  ]) as any;
+
+  const dictsOption = ref([]) as any;
+  // formdata
+  const formData = [
+    {
+      label: '保养厂商',
+      name: 'maintainCompany',
+      componentType: 'Select',
+      dicts: dictsOption.value,
+      placeholder: '请选择维修方',
+      prefix: 'icon-xt-search',
+      width: 280,
+    },
+    {
+      name: 'patrolTime',
+      label: '保养时间',
+      componentType: 'RangePicker',
+      placeholder: '请选择保养时间',
+      format: 'YYYY-MM-DD',
+      valueFormat: 'YYYY-MM-DD',
+    },
+  ];
+  const formValue = reactive({
+    patrolTime: [],
+    maintainCompany: '',
+  });
+
+  const [registerTable, { reload }] = useTable({
+    api: upkeepQueryPage,
+    rowKey: 'id',
+    columns,
+    showIndexColumn: false,
+    bordered: true,
+    striped: false,
+    actionColumn: {
+      width: 200,
+      title: '操作',
+      dataIndex: 'action',
+    },
+    beforeFetch: handleBeforeFetch,
+    sortFn: handleSortFn,
+  });
+  const bizDictData = ref([
+    { key: 'dmc', dictCode: 'dmc' },
+    { key: 'rp', dictCode: 'rp' },
+  ]);
+
+  const bizDictOptions = reactive<any>({});
+  onBeforeMount(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 types = bizDictOptions.dmc;
+    dictsOption.value.push({
+      label: '全部',
+      value: '',
+    });
+    types.forEach(item => {
+      dictsOption.value.push({
+        label: item.label,
+        value: item.value,
+      });
+    });
+  });
+  onMounted(async () => {
+    // callForm({
+    //   patrolTime: [
+    //     dayjs().subtract(3, 'month').format('YYYY-MM-DD'),
+    //     dayjs().add(1, 'day').format('YYYY-MM-DD'),
+    //   ],
+    // });
+  });
+  // 新增按钮事件
+  function handleCreate() {
+    openModal(true, {
+      isUpdate: false,
+      record: { id: props.info.id },
+    });
+  }
+
+  // 编辑按钮事件
+  function handleEdit(record: Recordable) {
+    openModal(true, {
+      record: { id: props.info.id },
+      upkeepRecord: record,
+      isUpdate: true,
+    });
+  }
+
+  // 删除按钮事件
+  async function handleDelete(record: Recordable) {
+    await upkeepRemove([record.id]);
+    createMessage.success('删除成功!');
+    await reload();
+  }
+  // 表格点击字段排序
+  function handleSortFn(sortInfo) {
+    if (sortInfo?.order && sortInfo?.columnKey) {
+      // 默认单列排序
+      tableSort.value = [
+        {
+          field: sortInfo.columnKey,
+          direction: sortInfo.order.replace(/(\w+)(end)/g, '$1').toUpperCase(),
+        },
+      ];
+    }
+  }
+
+  // 表格请求之前,对参数进行处理, 添加默认 排序
+  function handleBeforeFetch(params) {
+    return {
+      ...params,
+      orders: tableSort.value,
+      deviceId: props.info?.id,
+      maintainTime:
+        formValue.patrolTime && formValue.patrolTime.length > 0
+          ? [
+              formValue.patrolTime[0],
+              dayjs(formValue.patrolTime[1]).add(1, 'day').format('YYYY-MM-DD'),
+            ]
+          : undefined,
+      maintainCompany: formValue.maintainCompany,
+    };
+  }
+
+  // 弹窗回调事件
+  async function callSuccess() {
+    await reload();
+  }
+
+  // 查询回调
+  async function callForm(data) {
+    formValue.maintainCompany = data.maintainCompany || '';
+    formValue.patrolTime = data.patrolTime || [];
+    await reload();
+  }
+</script>
+<style lang="less" scoped>
+  ::v-deep(.ant-btn-link) {
+    color: rgb(61 65 85 / 100%);
+  }
+
+  ::v-deep(.ant-input) {
+    border-color: #c2ccd4;
+    border-radius: 4px;
+  }
+
+  ::v-deep(.ant-input-affix-wrapper) {
+    border-color: #c2ccd4;
+    border-radius: 4px;
+  }
+
+  ::v-deep(.ant-picker) {
+    border-color: #c2ccd4;
+    border-radius: 4px;
+  }
+
+  ::v-deep(.ant-table-tbody > tr > td) {
+    border-right: 0 !important;
+    border-left: 0 !important;
+    border-bottom: 1px solid #f0f0f0;
+  }
+
+  ::v-deep(.ant-table-wrapper table) {
+    border: 0;
+  }
+
+  ::v-deep(.ant-table-cell-fix-right-first::after, .ant-table-cell-fix-right-last::after) {
+    content: '';
+    top: auto;
+    width: 0;
+  }
+
+  .btn-add {
+    width: 120px;
+    border-radius: 4px;
+  }
+</style>

+ 4 - 0
src/views/biz/engineer/upkeep/data.ts

@@ -1,5 +1,6 @@
 import { BasicColumn, FormSchema } from '/@/components/Table';
 import { listDictModel, uploadApi } from '/@/api/common';
+import dayjs from 'dayjs';
 
 export const columns: BasicColumn[] = [
   {
@@ -57,6 +58,7 @@ export const dataFormSchema: FormSchema[] = [
   {
     label: '保养时间',
     field: 'maintainTime',
+    required: true,
     component: 'DatePicker',
     componentProps: {
       placeholder: '请输入上传日期',
@@ -65,6 +67,7 @@ export const dataFormSchema: FormSchema[] = [
       format: 'YYYY-MM-DD HH:mm:ss',
       showTime: true,
     },
+    defaultValue: dayjs().format('YYYY-MM-DD HH:mm:ss'),
     colProps: {
       span: 12,
     },
@@ -72,6 +75,7 @@ export const dataFormSchema: FormSchema[] = [
   {
     label: '保养方',
     field: 'maintainCompany',
+    required: true,
     component: 'ApiSelect',
     componentProps: {
       api: listDictModel,

+ 1 - 1
src/views/biz/engineer/upkeep/formDrawer.vue

@@ -109,7 +109,6 @@
       } else {
         updateVal = { ...detailData.value, ...values };
       }
-      debugger;
       !unref(isUpdate)
         ? await upkeepAdd({ ...values })
         : await upkeepEdit({ ...updateVal, id: rowId.value });
@@ -120,6 +119,7 @@
         values: { ...values, id: rowId.value },
       });
     } finally {
+      deviceSelIds.value = [];
       setDrawerProps({ confirmLoading: false });
     }
   }

+ 21 - 7
src/views/biz/engineer/upkeep/index.vue

@@ -16,7 +16,11 @@
     <BasicTable @register="registerTable">
       <template #bodyCell="{ column, record }">
         <template v-if="column.key === 'picture'">
-          <Image :width="80" :src="record.files ? record.files[0].absolutePath : null" />
+          <Image
+            v-if="record.files && record.files.length > 0"
+            :width="80"
+            :src="record.files ? record.files[0]?.absolutePath : null"
+          />
         </template>
         <template v-if="column.key === 'maintainCompany'">
           <span> {{ formatDictValue(typeCategoryOptions, record.maintainCompany) }}</span>
@@ -69,6 +73,8 @@
   import { XTTab } from '/@/components/XTTab/index';
   import { XTForm } from '/@/components/XTForm/index';
 
+  import dayjs from 'dayjs';
+
   // 标题数据
   const titleData = [
     {
@@ -86,6 +92,7 @@
       format: 'YYYY-MM-DD',
       valueFormat: 'YYYY-MM-DD',
       placeholder: '请选择日期',
+      defaultValue: dayjs().format('YYYY-MM-DD'),
       width: 280,
     },
     {
@@ -163,8 +170,8 @@
       await upkeepRemove([record.id]);
       createMessage.success('记录删除成功!');
       clearSelectedRowKeys();
-      await reload();
       await getTab();
+      await reload();
     }
   }
   // 表格点击字段排序
@@ -182,13 +189,18 @@
 
   // 表格请求之前,对参数进行处理, 添加默认 排序
   async function handleBeforeFetch(params) {
-    console.log('searchNames:::', searchNames.value);
+    const shiftTimes = [];
+    if (shiftDate.value && shiftDate.value.length > 0) {
+      shiftTimes.push(shiftDate.value[0]);
+      shiftTimes.push(shiftDate.value[1]);
+      shiftTimes[1] = dayjs(shiftTimes[1]).add(1, 'day').format('YYYY-MM-DD');
+    }
     return {
       ...params,
       orders: tableSort.value,
       uniqueCode: searchNames.value == '' ? undefined : searchNames.value,
       maintainCompany: tabSelected.value == '' ? undefined : tabSelected.value,
-      maintainTime: shiftDate.value.length <= 0 ? undefined : shiftDate.value,
+      maintainTime: shiftTimes.length <= 0 ? undefined : shiftTimes,
     };
   }
 
@@ -222,22 +234,24 @@
       hasBracket: true,
     });
     typeOptions.value = typeList;
-    tabSelected.value = typeOptions.value[0].key;
+    if (tabSelected.value === undefined) {
+      tabSelected.value = typeOptions.value[0].key;
+    }
   }
 
   //取消按钮事件
   async function handleCancel() {
     clearSelectedRowKeys();
-    await reload();
     await getTab();
+    await reload();
   }
 
   // 弹窗回调事件
   async function callSuccess({ isUpdate, values }) {
     console.log(isUpdate);
     console.log(values);
-    await reload();
     await getTab();
+    await reload();
   }
   // 选项卡组件回调
   async function callTab(data) {

+ 395 - 0
src/views/biz/engineer/water/data.ts

@@ -0,0 +1,395 @@
+import { BasicColumn, FormSchema } from '/@/components/Table';
+import { listDictModel, uploadApi } from '/@/api/common';
+import dayjs from 'dayjs';
+
+export const columns: BasicColumn[] = [
+  {
+    title: '设备编号',
+    dataIndex: 'uniqueCode',
+  },
+  {
+    title: '设备名称',
+    dataIndex: 'name',
+  },
+  {
+    title: '设备厂家',
+    dataIndex: 'manufacturer',
+  },
+  {
+    title: '设备型号',
+    dataIndex: 'model',
+  },
+  {
+    title: '序列号',
+    dataIndex: 'serialNumber',
+  },
+  {
+    title: '入账日期',
+    dataIndex: 'purchaseDate',
+  },
+  {
+    title: '使用日期',
+    dataIndex: 'useDate',
+  },
+  {
+    title: '采购金额',
+    dataIndex: 'priceWan',
+    width: 100,
+  },
+  {
+    title: '生产日期',
+    dataIndex: 'produceDate',
+    width: 100,
+  },
+  {
+    title: '保修期限',
+    dataIndex: 'warrantyPeriod',
+    width: 100,
+  },
+];
+// 表单新增编辑
+export const dataFormSchema: FormSchema[] = [
+  {
+    label: '设备编号',
+    field: 'uniqueCode',
+    required: true,
+    component: 'Input',
+    componentProps: {
+      placeholder: '请输入设备编码',
+    },
+  },
+  {
+    label: '设备名称',
+    field: 'name',
+    required: true,
+    component: 'Input',
+    componentProps: {
+      placeholder: '请输入设备编码',
+    },
+  },
+  {
+    label: '厂家',
+    field: 'manufacturer',
+    required: true,
+    component: 'Input',
+    componentProps: {
+      placeholder: '请输入设备厂家',
+    },
+  },
+  {
+    label: '型号',
+    field: 'model',
+    required: true,
+    component: 'Input',
+    componentProps: {
+      placeholder: '请输入型号',
+    },
+  },
+  {
+    label: '序列号',
+    field: 'serialNumber',
+    required: true,
+    component: 'Input',
+    componentProps: {
+      placeholder: '请输入型号',
+    },
+  },
+  {
+    label: '产地',
+    field: 'producer',
+    component: 'Input',
+    componentProps: {
+      placeholder: '请输入产地',
+    },
+  },
+  {
+    label: '使用日期',
+    field: 'useDate',
+    component: 'DatePicker',
+    componentProps: {
+      format: 'YYYY-MM-DD',
+      placeholder: '请选择生产日期',
+      getPopupContainer: () => document.body,
+      valueFormat: 'YYYY-MM-DD',
+    },
+  },
+  {
+    label: '生产日期',
+    field: 'produceDate',
+    component: 'DatePicker',
+    required: true,
+    componentProps: {
+      format: 'YYYY-MM-DD',
+      placeholder: '请选择生产日期',
+      getPopupContainer: () => document.body,
+      valueFormat: 'YYYY-MM-DD',
+    },
+  },
+  {
+    label: '入账日期',
+    field: 'purchaseDate',
+    component: 'DatePicker',
+    required: true,
+    componentProps: {
+      format: 'YYYY-MM-DD',
+      placeholder: '请选择生产日期',
+      getPopupContainer: () => document.body,
+      valueFormat: 'YYYY-MM-DD',
+    },
+  },
+  {
+    label: '保修期限(年)',
+    field: 'warrantyPeriod',
+    component: 'InputNumber',
+    componentProps: {
+      placeholder: '请输入保修期限',
+      min: 0,
+      step: 1,
+    },
+  },
+  {
+    label: '采购金额(元)',
+    field: 'priceWan',
+    component: 'Input',
+    componentProps: {
+      placeholder: '请输入采购金额',
+    },
+  },
+];
+
+export const maintenanceDataFormSchema: FormSchema[] = [
+  {
+    field: 'deviceInfo',
+    component: 'PlainTitle',
+    defaultValue: '设备信息',
+  },
+  {
+    label: '设备编号',
+    field: 'uniqueCode',
+    component: 'Input',
+    slot: 'uniqueCode',
+    colProps: {
+      span: 8,
+    },
+  },
+  {
+    label: '设备厂家',
+    field: 'manufacturer',
+    component: 'Input',
+    slot: 'manufacturer',
+    colProps: {
+      span: 8,
+    },
+  },
+  {
+    label: '设备型号',
+    field: 'model',
+    component: 'Input',
+    slot: 'model',
+    colProps: {
+      span: 8,
+    },
+  },
+
+  {
+    field: 'maintenanceInfo',
+    component: 'PlainTitle',
+    defaultValue: '维修记录',
+  },
+
+  {
+    label: '维修时间',
+    field: 'maintenanceTime',
+    required: true,
+    component: 'DatePicker',
+    componentProps: {
+      format: 'YYYY-MM-DD HH:mm:ss',
+      placeholder: '请选择维修时间',
+      showTime: true,
+      getPopupContainer: () => document.body,
+      valueFormat: 'YYYY-MM-DD HH:mm:ss',
+    },
+  },
+
+  {
+    label: '维修方',
+    field: 'maintenanceCompany',
+    required: true,
+    component: 'ApiSelect',
+    componentProps: {
+      api: listDictModel,
+      params: {
+        dictCode: 'dmc',
+      },
+      placeholder: '请选择维修方',
+    },
+  },
+  {
+    label: '异常信息',
+    field: 'malfunctionMessage',
+    component: 'Input',
+    componentProps: {
+      placeholder: '请输入异常信息',
+    },
+    colProps: {
+      span: 12,
+    },
+  },
+  {
+    label: '维修内容',
+    field: 'repairContent',
+    component: 'Input',
+    componentProps: {
+      placeholder: '请输入维修内容',
+    },
+    colProps: {
+      span: 12,
+    },
+  },
+  {
+    label: '维修费用(元)',
+    field: 'costYuan',
+    component: 'Input',
+    componentProps: {
+      placeholder: '请输入维修费用(元)',
+    },
+    colProps: {
+      span: 12,
+    },
+  },
+  {
+    label: '解决进度',
+    field: 'schedule',
+    component: 'ApiSelect',
+    componentProps: {
+      api: listDictModel,
+      params: {
+        dictCode: 'rp',
+      },
+      placeholder: '请选择解决进度',
+    },
+  },
+  {
+    label: '原因',
+    field: 'cause',
+    component: 'Input',
+    componentProps: {
+      placeholder: '请输入原因',
+    },
+    colProps: {
+      span: 12,
+    },
+  },
+  {
+    label: '维修图片',
+    field: 'files',
+    component: 'XTUpload',
+    componentProps: ({ formModel, schema }) => {
+      return {
+        api: uploadApi,
+        maxSize: 1,
+        maxNumber: 1,
+        helpText: '仅支持上传jpg/png图片,图片大小不超过1M',
+        accept: ['image/*'],
+        onChange: data => {
+          formModel[schema.field] = data;
+        },
+      };
+    },
+    colProps: {
+      span: 24,
+    },
+  },
+];
+
+export const upkeepDataFormSchema: FormSchema[] = [
+  {
+    field: 'deviceInfo',
+    component: 'PlainTitle',
+    defaultValue: '设备信息',
+  },
+  {
+    label: '设备编号',
+    field: 'uniqueCode',
+    component: 'Input',
+    slot: 'uniqueCode',
+    colProps: {
+      span: 12,
+    },
+  },
+  {
+    label: '设备厂家',
+    field: 'manufacturer',
+    component: 'Input',
+    slot: 'manufacturer',
+    colProps: {
+      span: 12,
+    },
+  },
+  {
+    label: '设备型号',
+    field: 'model',
+    component: 'Input',
+    slot: 'model',
+  },
+
+  {
+    field: 'maintenanceInfo',
+    component: 'PlainTitle',
+    defaultValue: '保养记录',
+  },
+
+  {
+    label: '保养时间',
+    field: 'maintainTime',
+    required: true,
+    component: 'DatePicker',
+    componentProps: {
+      format: 'YYYY-MM-DD HH:mm:ss',
+      placeholder: '请选择维修时间',
+      showTime: true,
+      getPopupContainer: () => document.body,
+      valueFormat: 'YYYY-MM-DD HH:mm:ss',
+    },
+    defaultValue: dayjs(new Date()).format('YYYY-MM-DD HH:mm:ss'),
+  },
+  {
+    label: '保养内容',
+    field: 'content',
+    required: true,
+    component: 'Input',
+    componentProps: {
+      placeholder: '请输入维修内容',
+    },
+  },
+  {
+    label: '保养方',
+    required: true,
+    field: 'maintainCompany',
+    component: 'ApiSelect',
+    componentProps: {
+      api: listDictModel,
+      params: {
+        dictCode: 'dmc',
+      },
+      placeholder: '请选择保养方',
+    },
+  },
+  {
+    label: '保养图片',
+    field: 'files',
+    component: 'XTUpload',
+    componentProps: ({ formModel, schema }) => {
+      return {
+        api: uploadApi,
+        maxSize: 1,
+        maxNumber: 1,
+        helpText: '仅支持上传jpg/png图片,图片大小不超过1M',
+        accept: ['image/*'],
+        onChange: data => {
+          formModel[schema.field] = data;
+        },
+      };
+    },
+  },
+];

+ 8 - 0
src/views/biz/engineer/water/detail/data.ts

@@ -0,0 +1,8 @@
+export const BasicTab = [
+  { key: 'deviceBasic', value: 0, title: '设备信息' },
+  { key: 'maintenanceList', value: 1, title: '维修记录' },
+  { key: 'upkeepList', value: 2, title: '保养记录' },
+  { key: 'treatmentList', value: 3, title: '检查记录' },
+];
+
+export const BasicTabActive = BasicTab[0].key;

+ 69 - 0
src/views/biz/engineer/water/detail/index.vue

@@ -0,0 +1,69 @@
+<template>
+  <div class="m-4">
+    <div class="mb-4">
+      <XTTitle :title="title" :go-back="true" />
+    </div>
+    <div class="py-2 bg-white">
+      <a-tabs v-model:activeKey="activeKey" :destroyInactiveTabPane="true">
+        <a-tab-pane
+          v-for="item in tabData"
+          :key="item.key"
+          :tab="item.title"
+          :class="'tab-' + item.key"
+        >
+          <!-- 基础信息 0 -->
+          <div v-if="item.value == 0 && info.id">
+            <DeviceBasic :info="info" />
+          </div>
+          <!-- 维修记录 1 -->
+          <div v-if="item.value == 1 && info.id">
+            <MaintenanceList :info="info" />
+          </div>
+          <!-- 保养记录 2 -->
+          <div v-if="item.value == 2 && info.id">
+            <UpkeepList :info="info" />
+          </div>
+          <!-- 检查记录 3 -->
+          <div v-if="item.value == 3 && info.id">
+            <TreatmentList :info="info" />
+          </div>
+        </a-tab-pane>
+      </a-tabs>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+  import { ref } from 'vue';
+  import { BasicTab, BasicTabActive } from './data';
+  import DeviceBasic from '../deviceBasic/index.vue';
+  import MaintenanceList from '../maintenanceList/index.vue';
+  import UpkeepList from '../upkeepList/index.vue';
+  import TreatmentList from '../treatmentList/index.vue';
+  import { useRoute } from 'vue-router';
+  import { XTTitle } from '/@/components/XTTitle/index';
+
+  const route = useRoute();
+  console.log('🚀 ~ file: index.vue:25 ~ route:', route);
+  const info = ref({
+    id: String(route.query?.id),
+    name: route.query?.name,
+  });
+  const activeKey = ref(BasicTabActive);
+  const tabData = ref(BasicTab);
+  const title = `设备详情 — ${info.value.name}`;
+</script>
+
+<style lang="less" scoped>
+  ::v-deep(.ant-tabs-tab) {
+    padding: 12px;
+  }
+
+  ::v-deep(.ant-tabs-tabpane) {
+    padding: 12px;
+  }
+
+  .tab-medicalDocuments {
+    padding: 0;
+  }
+</style>

+ 20 - 0
src/views/biz/engineer/water/deviceBasic/data.ts

@@ -0,0 +1,20 @@
+export const BasicData = [
+  {
+    title: '设备信息',
+    icon: 'icon-xt-edit_default',
+    type: 'basic',
+    data: [
+      { field: 'uniqueCode', label: '设备编号', value: '' },
+      { field: 'name', label: '设备名称', value: '' },
+      { field: 'manufacturer', label: '厂家', value: '' },
+      { field: 'model', label: '型号', value: '' },
+      { field: 'serialNumber', label: '序列号', value: '' },
+      { field: 'producer', label: '产地', value: '' },
+      { field: 'useDate', label: '使用日期', value: '' },
+      { field: 'produceDate', label: '生产日期', value: '' },
+      { field: 'purchaseDate', label: '入账日期', value: '' },
+      { field: 'warrantyPeriod', label: '保修年限(年)', value: '' },
+      { field: 'priceWan', label: '采购金额(万元)', value: '' },
+    ],
+  },
+];

+ 50 - 0
src/views/biz/engineer/water/deviceBasic/index.vue

@@ -0,0 +1,50 @@
+<template>
+  <div>
+    <div class="mx-2 mb-6" v-for="item in basicData" :key="item.type">
+      <DescCard :title="item.title" :type="item.type" :data="item.data" :id="info.id" />
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+  import { ref, reactive } from 'vue';
+  import DescCard from '/@/components/XTCard/src/DescCard.vue';
+  import { BasicData } from './data';
+  import { onMounted } from 'vue';
+  import { formatDictValue } from '/@/utils';
+  import { waterDetail } from '/@/api/biz/engineer/waterApi';
+  import { listDictModelBatch } from '/@/api/common';
+
+  const props = defineProps({
+    info: {
+      type: Object,
+      default: () => {},
+    } as any,
+  });
+  const bizDictOptions = reactive({
+    deviceType: [],
+  });
+  const bizDictData = ref([{ key: 'deviceType', dictCode: 'od' }]);
+  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];
+    }
+    await getData();
+  });
+  const basicData = ref(BasicData as any);
+  // 获取数据
+  async function getData() {
+    const res = await waterDetail(props.info?.id);
+    basicData.value[0].data.forEach(ele => {
+      if (ele.dict) {
+        ele.value = formatDictValue(bizDictOptions[ele.field], res[ele.field]);
+      } else {
+        ele.value = res[ele.field];
+      }
+    });
+  }
+</script>
+
+<style lang="less" scoped></style>

+ 85 - 0
src/views/biz/engineer/water/formModal.vue

@@ -0,0 +1,85 @@
+<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 { waterAdd, waterDetail, waterEdit } from '/@/api/biz/engineer/waterApi';
+  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 waterDetail(data.record.id);
+      await setFieldsValue({
+        ...resData,
+      });
+    }
+  });
+
+  // 提交按钮事件
+  async function handleSubmit() {
+    try {
+      const values = await validate();
+      setModalProps({ confirmLoading: true });
+      !unref(isUpdate)
+        ? await waterAdd({ ...values })
+        : await waterEdit({ ...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>

+ 237 - 3
src/views/biz/engineer/water/index.vue

@@ -1,7 +1,241 @@
 <template>
-  <div> 占位符 </div>
+  <div class="m-4 modals">
+    <div>
+      <XTTitle title="水处理设备" :right-data="titleData" @click="callTitleClick" />
+      <div class="flex items-center justify-between my-4">
+        <div />
+        <XTForm :form-data="formData" @change="callForm" />
+      </div>
+    </div>
+    <BasicTable @register="registerTable">
+      <template #bodyCell="{ column, record }">
+        <template v-if="column.key === 'purchaseDate'">
+          <span> {{ dayjs(record.purchaseDate).format('YYYY-MM-DD') }}</span>
+        </template>
+        <template v-if="column.key === 'useDate'">
+          <span> {{ record.useDate ? dayjs(record.useDate).format('YYYY-MM-DD') : '' }}</span>
+        </template>
+        <template v-if="column.key === 'produceDate'">
+          <span> {{ dayjs(record.produceDate).format('YYYY-MM-DD') }}</span>
+        </template>
+        <template v-if="column.key === 'name'">
+          <a @click="goDetail(record)">{{ record.name }}</a>
+        </template>
+        <template v-if="column.key === 'action'">
+          <TableAction
+            :actions="[
+              {
+                label: '编辑',
+                auth: 'biz:consumable:edit',
+                tooltip: '编辑',
+                onClick: handleEdit.bind(null, record),
+              },
+              {
+                label: '维修',
+                auth: 'biz:consumable:edit',
+                tooltip: '维修',
+                onClick: handleMaintenance.bind(null, record),
+              },
+              {
+                label: '保养',
+                auth: 'biz:consumable:edit',
+                tooltip: '保养',
+                onClick: handleUpkeep.bind(null, record),
+              },
+            ]"
+          />
+        </template>
+      </template>
+    </BasicTable>
+    <FormModal @register="registerModal" @success="callSuccess" @cancel="handleCancel" />
+    <MaintenanceFormModal
+      @register="registerMaintenanceModal"
+      @success="callSuccess"
+      @cancel="handleCancel"
+    />
+    <UpkeepFormModal
+      @register="registerUpkeepModal"
+      @success="callSuccess"
+      @cancel="handleCancel"
+    />
+  </div>
 </template>
+<script lang="ts" setup>
+  import { onBeforeMount, ref } from 'vue';
+  import { BasicTable, useTable, TableAction } from '/@/components/TableCard';
+  import FormModal from './formModal.vue';
+  import MaintenanceFormModal from './maintenanceFormModal.vue';
+  import UpkeepFormModal from './upkeepFormModal.vue';
+  import { columns } from './data';
+  import { waterQueryPage } from '/@/api/biz/engineer/waterApi';
+  import { listDictModel } from '/@/api/common';
+  import { useModal } from '/@/components/Modal';
+  import { XTTitle } from '/@/components/XTTitle/index';
+  import { XTForm } from '/@/components/XTForm/index';
+  import { useRouter } from 'vue-router';
+  import dayjs from 'dayjs';
 
-<script setup lang="ts"></script>
+  // 路由跳转
+  const router = useRouter();
+  // 标题数据
+  const titleData = [
+    {
+      type: 'add',
+      btnIcon: 'icon-xt-add_default',
+      auth: ['biz:consumable:add'],
+      btnText: '新增设备',
+    },
+  ];
+  // formdata
+  const formData = [
+    {
+      name: 'searchNames',
+      componentType: 'Input',
+      prefix: 'icon-xt-search',
+      placeholder: '请输入设备编号',
+      width: 240,
+    },
+  ];
+  // 操作名称
+  const searchNames = ref('');
+  const shiftDate = ref([]);
 
-<style lang="less" scoped></style>
+  const responsesupplierCategoryOptions = ref();
+  onBeforeMount(async () => {
+    responsesupplierCategoryOptions.value = await listDictModel({ dictCode: 'pht' });
+  });
+
+  const [registerModal, { openModal }] = useModal();
+  const [registerMaintenanceModal, { openModal: openMaintenanceModal }] = useModal();
+  const [registerUpkeepModal, { openModal: openUpkeepModal }] = useModal();
+  const tableSort = ref([
+    {
+      field: 'create_time',
+      direction: 'DESC',
+    },
+  ]) as any;
+
+  const [registerTable, { reload, clearSelectedRowKeys }] = useTable({
+    api: waterQueryPage,
+    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,
+      });
+    }
+  }
+
+  // 表格点击字段排序
+  function handleSortFn(sortInfo) {
+    if (sortInfo?.order && sortInfo?.columnKey) {
+      // 默认单列排序
+      tableSort.value = [
+        {
+          field: sortInfo.columnKey,
+          direction: sortInfo.order.replace(/(\w+)(end)/g, '$1').toUpperCase(),
+        },
+      ];
+    }
+  }
+  // 打开维修弹框
+  function handleMaintenance(record) {
+    openMaintenanceModal(true, {
+      record,
+    });
+  }
+  // 打开保养弹框
+  function handleUpkeep(record) {
+    openUpkeepModal(true, {
+      record,
+    });
+  }
+
+  // 表格请求之前,对参数进行处理, 添加默认 排序
+  async function handleBeforeFetch(params) {
+    return {
+      ...params,
+      orders: tableSort.value,
+      uniqueCode: searchNames.value == '' ? undefined : searchNames.value,
+    };
+  }
+
+  //取消按钮事件
+  async function handleCancel() {
+    clearSelectedRowKeys();
+    await reload();
+  }
+
+  // 弹窗回调事件
+  async function callSuccess({ isUpdate, values }) {
+    console.log(isUpdate);
+    console.log(values);
+    await reload();
+  }
+
+  // 查询组件回调
+  async function callForm(data) {
+    shiftDate.value = data.shiftDate ? data.shiftDate : '';
+    searchNames.value = data.searchNames ? data.searchNames : '';
+    await reload();
+  }
+
+  // 打开详情页面
+  function goDetail(record) {
+    console.log('record::', record);
+    router.push({
+      path: '/bizEngineer/waterDetail',
+      query: {
+        id: record.id,
+        name: record.name,
+      },
+    });
+  }
+</script>
+<style lang="less" scoped>
+  .table-dot {
+    display: inline-block;
+    width: 10px;
+    height: 10px;
+    margin-right: 6px;
+    border-radius: 50%;
+  }
+
+  ::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>

+ 111 - 0
src/views/biz/engineer/water/maintenanceFormModal.vue

@@ -0,0 +1,111 @@
+<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">
+          <template #uniqueCode>
+            <span>{{ deviceInfo?.uniqueCode }}</span>
+          </template>
+          <template #manufacturer>
+            <span>{{ deviceInfo?.manufacturer }}</span>
+          </template>
+          <template #model>
+            <span>{{ deviceInfo?.model }}</span>
+          </template>
+        </BasicForm>
+      </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 { maintenanceDataFormSchema } from './data';
+  import { otherDetail } from '@/api/biz/engineer/otherApi';
+  import {
+    maintenanceAdd,
+    maintenanceDetail,
+    maintenanceEdit,
+  } from '/@/api/biz/engineer/maintenanceApi';
+
+  const emit = defineEmits(['success', 'cancel', 'register']);
+
+  const getTitle = computed(() => (!unref(isUpdate) ? '维修设备' : '编辑设备'));
+  const isUpdate = ref(false);
+  const deviceInfo = ref();
+  const rowId = ref();
+
+  const { createMessage } = useMessage();
+  const [registerForm, { resetFields, validate, setFieldsValue }] = useForm({
+    layout: 'vertical',
+    showResetButton: true,
+    labelWidth: 100,
+    schemas: maintenanceDataFormSchema,
+    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 (!data.record.name) {
+      deviceInfo.value = await otherDetail(data.record?.id);
+    } else {
+      deviceInfo.value = data.record;
+    }
+    if (unref(isUpdate)) {
+      rowId.value = data.maintenanceRecord.id;
+      const resData = await maintenanceDetail(data.maintenanceRecord.id);
+      console.log('resData::::', resData);
+      await setFieldsValue({
+        ...resData,
+      });
+    }
+  });
+
+  // 提交按钮事件
+  async function handleSubmit() {
+    try {
+      const values = await validate();
+      setModalProps({ confirmLoading: true });
+      values.picture = values.files && values.files.map(ele => ele.id);
+      values.deviceId = deviceInfo.value.id;
+      !unref(isUpdate)
+        ? await maintenanceAdd({ ...values })
+        : await maintenanceEdit({ ...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>

+ 39 - 0
src/views/biz/engineer/water/maintenanceList/data.ts

@@ -0,0 +1,39 @@
+import { BasicColumn } from '/@/components/Table';
+
+export const columns: BasicColumn[] = [
+  {
+    title: '维修时间',
+    dataIndex: 'maintenanceTime',
+    align: 'left',
+  },
+  {
+    title: '维修方',
+    dataIndex: 'maintenanceCompany',
+    align: 'left',
+  },
+  {
+    title: '异常信息',
+    dataIndex: 'malfunctionMessage',
+    align: 'left',
+  },
+  {
+    title: '维修内容',
+    dataIndex: 'repairContent',
+    align: 'left',
+  },
+  {
+    title: '解决进度',
+    dataIndex: 'schedule',
+    align: 'left',
+  },
+  {
+    title: '维修费用(元)',
+    dataIndex: 'costYuan',
+    align: 'left',
+  },
+  {
+    title: '维修图片',
+    dataIndex: 'picture',
+    align: 'left',
+  },
+];

+ 266 - 0
src/views/biz/engineer/water/maintenanceList/index.vue

@@ -0,0 +1,266 @@
+<template>
+  <div>
+    <div class="flex justify-between mb-4">
+      <div>
+        <a-button type="primary" size="large" class="btn-add" @click="handleCreate"
+          ><template #icon> <Icon icon="icon-xt-add_default|iconfont" /> </template
+          >新增维修</a-button
+        >
+      </div>
+      <div>
+        <XTForm :form-data="formData" @change="callForm" />
+      </div>
+    </div>
+    <BasicTable @register="registerTable">
+      <template #bodyCell="{ column, record }">
+        <template v-if="column.key === 'maintenanceCompany'">
+          <span>{{ formatDictValue(bizDictOptions.dmc, record.maintenanceCompany) }}</span>
+        </template>
+        <template v-if="column.key === 'schedule'">
+          <span>{{ formatDictValue(bizDictOptions.rp, record.schedule) }}</span>
+        </template>
+        <template v-if="column.key === 'picture'">
+          <Image
+            v-if="record.files && record.files.length > 0"
+            :src="record.files[0].absolutePath"
+            :width="60"
+          />
+        </template>
+        <template v-if="column.key === 'action'">
+          <TableAction
+            :actions="[
+              {
+                auth: 'archives:patrolWard:edit',
+                icon: 'icon-xt-details_edit_default|iconfont',
+                tooltip: '编辑',
+                onClick: handleEdit.bind(null, record),
+              },
+              {
+                auth: 'archives:patrolWard:remove',
+                icon: 'icon-xt-details_delete_default|iconfont',
+                tooltip: '删除',
+                popConfirm: {
+                  title: '是否取消删除',
+                  placement: 'left',
+                  confirm: handleDelete.bind(null, record, column),
+                },
+              },
+            ]"
+          />
+        </template>
+      </template>
+    </BasicTable>
+    <FormModal @register="registerModal" @success="callSuccess" />
+  </div>
+</template>
+<script lang="ts" setup>
+  import { onBeforeMount, onMounted, reactive, ref } from 'vue';
+  import { XTForm } from '/@/components/XTForm/index';
+  import { BasicTable, useTable, TableAction } from '/@/components/Table';
+  import { useMessage } from '/@/hooks/web/useMessage';
+  import FormModal from '../maintenanceFormModal.vue';
+
+  import Icon from '/@/components/Icon/index';
+  import { columns } from './data';
+  import { listDictModelBatch } from '/@/api/common';
+  import { maintenanceQueryPage, maintenanceRemove } from '/@/api/biz/engineer/maintenanceApi';
+  import { useModal } from '/@/components/Modal';
+  import { Image } from 'ant-design-vue';
+  import dayjs from 'dayjs';
+  import { formatDictValue } from '/@/utils';
+
+  const props = defineProps({
+    info: {
+      type: Object,
+      default: () => {},
+    },
+  });
+
+  const { createMessage } = useMessage();
+  const [registerModal, { openModal }] = useModal();
+  const tableSort = ref([
+    {
+      field: 'create_time',
+      direction: 'DESC',
+    },
+  ]) as any;
+
+  const dictsOption = ref([]) as any;
+  // formdata
+  const formData = [
+    {
+      label: '维修方',
+      name: 'maintenanceCompany',
+      componentType: 'Select',
+      dicts: dictsOption.value,
+      placeholder: '请选择维修方',
+      prefix: 'icon-xt-search',
+      width: 280,
+    },
+    {
+      name: 'patrolTime',
+      label: '维修时间',
+      componentType: 'RangePicker',
+      placeholder: '请选择维修时间',
+      format: 'YYYY-MM-DD',
+      valueFormat: 'YYYY-MM-DD',
+    },
+  ];
+  const formValue = reactive({
+    patrolTime: [],
+    maintenanceCompany: '',
+  });
+
+  const [registerTable, { reload }] = useTable({
+    api: maintenanceQueryPage,
+    rowKey: 'id',
+    columns,
+    showIndexColumn: false,
+    bordered: true,
+    striped: false,
+    actionColumn: {
+      width: 200,
+      title: '操作',
+      dataIndex: 'action',
+    },
+    beforeFetch: handleBeforeFetch,
+    sortFn: handleSortFn,
+  });
+  const bizDictData = ref([
+    { key: 'dmc', dictCode: 'dmc' },
+    { key: 'rp', dictCode: 'rp' },
+  ]);
+
+  const bizDictOptions = reactive<any>({});
+  onBeforeMount(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 types = bizDictOptions.dmc;
+    dictsOption.value.push({
+      label: '全部',
+      value: '',
+    });
+    types.forEach(item => {
+      dictsOption.value.push({
+        label: item.label,
+        value: item.value,
+      });
+    });
+  });
+  onMounted(async () => {
+    // callForm({
+    //   patrolTime: [
+    //     dayjs().subtract(3, 'month').format('YYYY-MM-DD'),
+    //     dayjs().add(1, 'day').format('YYYY-MM-DD'),
+    //   ],
+    // });
+  });
+  // 新增按钮事件
+  function handleCreate() {
+    openModal(true, {
+      isUpdate: false,
+      record: { id: props.info.id },
+    });
+  }
+
+  // 编辑按钮事件
+  function handleEdit(record: Recordable) {
+    openModal(true, {
+      record: { id: props.info.id },
+      maintenanceRecord: record,
+      isUpdate: true,
+    });
+  }
+
+  // 删除按钮事件
+  async function handleDelete(record: Recordable) {
+    await maintenanceRemove([record.id]);
+    createMessage.success('删除成功!');
+    await reload();
+  }
+  // 表格点击字段排序
+  function handleSortFn(sortInfo) {
+    if (sortInfo?.order && sortInfo?.columnKey) {
+      // 默认单列排序
+      tableSort.value = [
+        {
+          field: sortInfo.columnKey,
+          direction: sortInfo.order.replace(/(\w+)(end)/g, '$1').toUpperCase(),
+        },
+      ];
+    }
+  }
+
+  // 表格请求之前,对参数进行处理, 添加默认 排序
+  function handleBeforeFetch(params) {
+    return {
+      ...params,
+      orders: tableSort.value,
+      deviceId: props.info?.id,
+      maintenanceTime:
+        formValue.patrolTime && formValue.patrolTime.length > 0
+          ? [
+              formValue.patrolTime[0],
+              dayjs(formValue.patrolTime[1]).add(1, 'day').format('YYYY-MM-DD'),
+            ]
+          : undefined,
+      maintenanceCompany: formValue.maintenanceCompany,
+    };
+  }
+
+  // 弹窗回调事件
+  async function callSuccess() {
+    await reload();
+  }
+
+  // 查询回调
+  async function callForm(data) {
+    formValue.maintenanceCompany = data.maintenanceCompany || '';
+    formValue.patrolTime = data.patrolTime || [];
+    await reload();
+  }
+</script>
+<style lang="less" scoped>
+  ::v-deep(.ant-btn-link) {
+    color: rgb(61 65 85 / 100%);
+  }
+
+  ::v-deep(.ant-input) {
+    border-color: #c2ccd4;
+    border-radius: 4px;
+  }
+
+  ::v-deep(.ant-input-affix-wrapper) {
+    border-color: #c2ccd4;
+    border-radius: 4px;
+  }
+
+  ::v-deep(.ant-picker) {
+    border-color: #c2ccd4;
+    border-radius: 4px;
+  }
+
+  ::v-deep(.ant-table-tbody > tr > td) {
+    border-right: 0 !important;
+    border-left: 0 !important;
+    border-bottom: 1px solid #f0f0f0;
+  }
+
+  ::v-deep(.ant-table-wrapper table) {
+    border: 0;
+  }
+
+  ::v-deep(.ant-table-cell-fix-right-first::after, .ant-table-cell-fix-right-last::after) {
+    content: '';
+    top: auto;
+    width: 0;
+  }
+
+  .btn-add {
+    width: 120px;
+    border-radius: 4px;
+  }
+</style>

+ 577 - 0
src/views/biz/engineer/water/treatmentList/data.ts

@@ -0,0 +1,577 @@
+import { BasicColumn, FormSchema } from '/@/components/Table';
+import { sysUserList } from '/@/api/sys/sysUserApi';
+
+import { useUserStore } from '@/store/modules/user';
+const useUser = useUserStore();
+export const columns: BasicColumn[] = [
+  {
+    title: '检查日期',
+    dataIndex: 'examinerTime',
+    align: 'center',
+    width: 120,
+  },
+  {
+    title: '检查人',
+    dataIndex: 'examinerName',
+    align: 'center',
+    width: 80,
+  },
+  {
+    title: '进水压力',
+    dataIndex: 'intakePressure',
+    align: 'center',
+    width: 80,
+  },
+  {
+    title: '控制器状态',
+    dataIndex: 'controllerState',
+    align: 'center',
+    width: 90,
+  },
+
+  {
+    title: '砂滤罐压差',
+    dataIndex: 'sandLeachDifferentialPressure',
+    align: 'center',
+    width: 90,
+  },
+  {
+    title: '砂滤罐反冲洗',
+    dataIndex: 'sandLeachBackFlush',
+    align: 'center',
+    width: 110,
+  },
+  {
+    title: '硬度',
+    dataIndex: 'hardness',
+    align: 'center',
+    width: 80,
+  },
+  {
+    title: '树脂罐压差',
+    dataIndex: 'resinDifferentialPressure',
+    align: 'center',
+    width: 90,
+  },
+  {
+    title: '树脂再生情况',
+    dataIndex: 'resinRegeneration',
+    align: 'center',
+    width: 110,
+  },
+  {
+    title: '残余氯',
+    dataIndex: 'residualChlorine',
+    align: 'center',
+    width: 60,
+  },
+  {
+    title: '活性炭罐压差',
+    dataIndex: 'activeCarbonDifferentialPressure',
+    align: 'center',
+    width: 110,
+  },
+  {
+    title: '活性炭罐反冲洗',
+    dataIndex: 'activeCarbonBackFlush',
+    align: 'center',
+    width: 120,
+  },
+  {
+    title: '更换滤器',
+    dataIndex: 'filterReplacement',
+    align: 'center',
+    width: 80,
+  },
+  {
+    title: '水机进水压力',
+    dataIndex: 'waterInletPressure',
+    align: 'center',
+    width: 110,
+  },
+  {
+    title: '1级膜进水压力',
+    dataIndex: 'membraneInletPressure1',
+    align: 'center',
+    width: 110,
+  },
+  {
+    title: '1级膜排水压力',
+    dataIndex: 'membraneDrainagePressure1',
+    align: 'center',
+    width: 110,
+  },
+  {
+    title: '1级膜产水压力',
+    dataIndex: 'membraneContributingWaterPressure1',
+    align: 'center',
+    width: 110,
+  },
+  {
+    title: '2级膜进水压力',
+    dataIndex: 'membraneInletPressure2',
+    align: 'center',
+    width: 110,
+  },
+  {
+    title: '2级膜排水压力',
+    dataIndex: 'membraneDrainagePressure2',
+    align: 'center',
+    width: 110,
+  },
+  {
+    title: '2级膜产水压力',
+    dataIndex: 'membraneContributingWaterPressure2',
+    align: 'center',
+    width: 110,
+  },
+  {
+    title: '纯水回水压力',
+    dataIndex: 'pureWaterReturnPressure',
+    align: 'center',
+    width: 110,
+  },
+  {
+    title: '进水电导率',
+    dataIndex: 'inletConductivity',
+    align: 'center',
+    width: 90,
+  },
+  {
+    title: '产水电导率',
+    dataIndex: 'contributingWaterConductivity',
+    align: 'center',
+    width: 90,
+  },
+  {
+    title: '进水量',
+    dataIndex: 'waterInflow',
+    align: 'center',
+    width: 60,
+  },
+  {
+    title: '产水量',
+    dataIndex: 'waterYield',
+    align: 'center',
+    width: 60,
+  },
+  {
+    title: '废水量',
+    dataIndex: 'wastewaterQuantity',
+    align: 'center',
+    width: 60,
+  },
+  {
+    title: '回水量',
+    dataIndex: 'waterReclamation',
+    align: 'center',
+    width: 60,
+  },
+  {
+    title: '水温',
+    dataIndex: 'waterTemperature',
+    align: 'center',
+    width: 60,
+  },
+  {
+    title: '系统消毒',
+    dataIndex: 'systemDisinfection',
+    align: 'center',
+    width: 80,
+  },
+];
+
+export const dataFormSchema: FormSchema[] = [
+  {
+    label: '检查日期',
+    field: 'examinerTime',
+    required: true,
+    component: 'DatePicker',
+    componentProps: {
+      format: 'YYYY-MM-DD',
+      placeholder: '请选择检查日期',
+      getPopupContainer: () => document.body,
+      valueFormat: 'YYYY-MM-DD',
+    },
+  },
+  {
+    label: '检查人',
+    field: 'examinerId',
+    required: true,
+    component: 'ApiSelect',
+    componentProps: ({ formModel }) => {
+      return {
+        api: sysUserList,
+        params: {
+          name: formModel.patientBasicName,
+          pageSize: 999,
+        },
+        labelField: 'nickname',
+        valueField: 'id',
+        resultField: 'data',
+        placeholder: '请输入患者姓名',
+        getPopupContainer: () => document.body,
+        showSearch: true,
+        filterOption: false,
+        onSearch: e => {
+          formModel.patientBasicName = e;
+        },
+      };
+    },
+    defaultValue: useUser.getUserInfo.userId,
+  },
+  {
+    label: '进水压力',
+    field: 'intakePressure',
+    component: 'RadioGroup',
+    componentProps: {
+      options: [
+        { label: '合格', value: 1 },
+        { label: '不合格', value: 0 },
+      ],
+    },
+    defaultValue: 1,
+  },
+  {
+    label: '控制器状态',
+    field: 'controllerState',
+    component: 'RadioGroup',
+    componentProps: {
+      options: [
+        { label: '合格', value: 1 },
+        { label: '不合格', value: 0 },
+      ],
+    },
+    defaultValue: 1,
+  },
+  {
+    label: '砂滤罐压差',
+    field: 'sandLeachDifferentialPressure',
+    component: 'RadioGroup',
+    componentProps: {
+      options: [
+        { label: '合格', value: 1 },
+        { label: '不合格', value: 0 },
+      ],
+    },
+    defaultValue: 1,
+  },
+  {
+    label: '砂滤罐反向冲洗',
+    field: 'sandLeachBackFlush',
+    component: 'RadioGroup',
+    componentProps: {
+      options: [
+        { label: '合格', value: 1 },
+        { label: '不合格', value: 0 },
+      ],
+    },
+    defaultValue: 1,
+  },
+  {
+    label: '硬度',
+    field: 'hardness',
+    component: 'RadioGroup',
+    componentProps: {
+      options: [
+        { label: '合格', value: 1 },
+        { label: '不合格', value: 0 },
+      ],
+    },
+    defaultValue: 1,
+  },
+  {
+    label: '树脂罐压差',
+    field: 'resinDifferentialPressure',
+    component: 'RadioGroup',
+    componentProps: {
+      options: [
+        { label: '合格', value: 1 },
+        { label: '不合格', value: 0 },
+      ],
+    },
+    defaultValue: 1,
+  },
+  {
+    label: '树脂再生情况',
+    field: 'resinRegeneration',
+    component: 'RadioGroup',
+    componentProps: {
+      options: [
+        { label: '合格', value: 1 },
+        { label: '不合格', value: 0 },
+      ],
+    },
+    defaultValue: 1,
+  },
+  {
+    label: '残余氯',
+    field: 'residualChlorine',
+    component: 'RadioGroup',
+    componentProps: {
+      options: [
+        { label: '合格', value: 1 },
+        { label: '不合格', value: 0 },
+      ],
+    },
+    defaultValue: 1,
+  },
+  {
+    label: '活性炭罐压差',
+    field: 'activeCarbonDifferentialPressure',
+    component: 'RadioGroup',
+    componentProps: {
+      options: [
+        { label: '合格', value: 1 },
+        { label: '不合格', value: 0 },
+      ],
+    },
+    defaultValue: 1,
+  },
+  {
+    label: '活性炭罐反向冲洗',
+    field: 'activeCarbonBackFlush',
+    component: 'RadioGroup',
+    componentProps: {
+      options: [
+        { label: '合格', value: 1 },
+        { label: '不合格', value: 0 },
+      ],
+    },
+    defaultValue: 1,
+  },
+  {
+    label: '更换滤器',
+    field: 'filterReplacement',
+    component: 'RadioGroup',
+    componentProps: {
+      options: [
+        { label: '是', value: 1 },
+        { label: '否', value: 0 },
+      ],
+    },
+    defaultValue: 1,
+  },
+  {
+    label: '水机进水压力',
+    field: 'waterInletPressure',
+    component: 'RadioGroup',
+    componentProps: {
+      options: [
+        { label: '合格', value: 1 },
+        { label: '不合格', value: 0 },
+      ],
+    },
+    defaultValue: 1,
+  },
+  {
+    label: '1级膜进水压力',
+    field: 'membraneInletPressure1',
+    component: 'RadioGroup',
+    componentProps: {
+      options: [
+        { label: '合格', value: 1 },
+        { label: '不合格', value: 0 },
+      ],
+    },
+    defaultValue: 1,
+  },
+  {
+    label: '1级膜排水压力',
+    field: 'membraneDrainagePressure1',
+    component: 'RadioGroup',
+    componentProps: {
+      options: [
+        { label: '合格', value: 1 },
+        { label: '不合格', value: 0 },
+      ],
+    },
+    defaultValue: 1,
+  },
+  {
+    label: '1级膜产水压力',
+    field: 'membraneContributingWaterPressure1',
+    component: 'RadioGroup',
+    componentProps: {
+      options: [
+        { label: '合格', value: 1 },
+        { label: '不合格', value: 0 },
+      ],
+    },
+    defaultValue: 1,
+  },
+  {
+    label: '2级膜进水压力',
+    field: 'membraneInletPressure2',
+    component: 'RadioGroup',
+    componentProps: {
+      options: [
+        { label: '合格', value: 1 },
+        { label: '不合格', value: 0 },
+      ],
+    },
+    defaultValue: 1,
+  },
+  {
+    label: '2级膜排水压力',
+    field: 'membraneDrainagePressure2',
+    component: 'RadioGroup',
+    componentProps: {
+      options: [
+        { label: '合格', value: 1 },
+        { label: '不合格', value: 0 },
+      ],
+    },
+    defaultValue: 1,
+  },
+  {
+    label: '2级膜产水压力',
+    field: 'membraneContributingWaterPressure2',
+    component: 'RadioGroup',
+    componentProps: {
+      options: [
+        { label: '合格', value: 1 },
+        { label: '不合格', value: 0 },
+      ],
+    },
+    defaultValue: 1,
+  },
+  {
+    label: '纯水回水压力',
+    field: 'pureWaterReturnPressure',
+    component: 'RadioGroup',
+    componentProps: {
+      options: [
+        { label: '合格', value: 1 },
+        { label: '不合格', value: 0 },
+      ],
+    },
+    defaultValue: 1,
+  },
+  {
+    label: '进水电导率',
+    field: 'inletConductivity',
+    component: 'RadioGroup',
+    componentProps: {
+      options: [
+        { label: '合格', value: 1 },
+        { label: '不合格', value: 0 },
+      ],
+    },
+    defaultValue: 1,
+  },
+  {
+    label: '产水电导率',
+    field: 'contributingWaterConductivity',
+    component: 'RadioGroup',
+    componentProps: {
+      options: [
+        { label: '合格', value: 1 },
+        { label: '不合格', value: 0 },
+      ],
+    },
+    defaultValue: 1,
+  },
+  {
+    label: '进水量',
+    field: 'waterInflow',
+    component: 'RadioGroup',
+    componentProps: {
+      options: [
+        { label: '合格', value: 1 },
+        { label: '不合格', value: 0 },
+      ],
+    },
+    defaultValue: 1,
+  },
+  {
+    label: '产水量',
+    field: 'waterYield',
+    component: 'RadioGroup',
+    componentProps: {
+      options: [
+        { label: '合格', value: 1 },
+        { label: '不合格', value: 0 },
+      ],
+    },
+    defaultValue: 1,
+  },
+  {
+    label: '废水量',
+    field: 'wastewaterQuantity',
+    component: 'RadioGroup',
+    componentProps: {
+      options: [
+        { label: '合格', value: 1 },
+        { label: '不合格', value: 0 },
+      ],
+    },
+    defaultValue: 1,
+  },
+  {
+    label: '回水量',
+    field: 'waterReclamation',
+    component: 'RadioGroup',
+    componentProps: {
+      options: [
+        { label: '合格', value: 1 },
+        { label: '不合格', value: 0 },
+      ],
+    },
+    defaultValue: 1,
+  },
+  {
+    label: '水温(℃)',
+    field: 'waterTemperature',
+    component: 'RadioGroup',
+    componentProps: {
+      options: [
+        { label: '合格', value: 1 },
+        { label: '不合格', value: 0 },
+      ],
+    },
+    defaultValue: 1,
+  },
+  {
+    label: '系统消毒',
+    field: 'systemDisinfection',
+    component: 'RadioGroup',
+    componentProps: {
+      options: [
+        { label: '是', value: 1 },
+        { label: '否', value: 0 },
+      ],
+    },
+    defaultValue: 1,
+  },
+];
+
+export const dataFiled = [
+  'intakePressure',
+  'controllerState',
+  'sandLeachDifferentialPressure',
+  'sandLeachBackFlush',
+  'hardness',
+  'resinDifferentialPressure',
+  'resinRegeneration',
+  'residualChlorine',
+  'activeCarbonDifferentialPressure',
+  'activeCarbonBackFlush',
+  'filterReplacement',
+  'waterInletPressure',
+  'membraneInletPressure1',
+  'membraneDrainagePressure1',
+  'membraneContributingWaterPressure1',
+  'membraneInletPressure2',
+  'membraneDrainagePressure2',
+  'membraneContributingWaterPressure2',
+  'pureWaterReturnPressure',
+  'inletConductivity',
+  'contributingWaterConductivity',
+  'waterInflow',
+  'waterYield',
+  'wastewaterQuantity',
+  'waterReclamation',
+  'waterTemperature',
+  'systemDisinfection',
+];

+ 112 - 0
src/views/biz/engineer/water/treatmentList/formDrawer.vue

@@ -0,0 +1,112 @@
+<template>
+  <BasicDrawer
+    v-bind="$attrs"
+    destroyOnClose
+    @register="registerDrawer"
+    :title="getTitle"
+    :width="width"
+    @ok="handleSubmit"
+    :showFooter="true"
+  >
+    <BasicForm @register="registerForm" layout="vertical"
+  /></BasicDrawer>
+</template>
+<script lang="ts" setup>
+  import { ref, computed, unref } from 'vue';
+  import { BasicDrawer, useDrawerInner } from '/@/components/Drawer';
+  import { BasicForm, useForm } from '/@/components/Form';
+  import { useMessage } from '/@/hooks/web/useMessage';
+  import { dataFormSchema } from './data';
+
+  import { waterTreatmentAdd } from '/@/api/biz/engineer/waterTreatmentApi';
+  const emit = defineEmits(['success', 'register']);
+  const getTitle = computed(() => '新增检查记录');
+  const width = '35%';
+  const isUpdate = ref(false);
+  const rowId = ref();
+  const { createMessage } = useMessage();
+  const [registerForm, { validate }] = useForm({
+    labelWidth: 120,
+    schemas: dataFormSchema,
+    showActionButtonGroup: false,
+    actionColOptions: {
+      span: 23,
+    },
+    baseColProps: {
+      span: 12,
+    },
+    wrapperCol: {
+      span: 24,
+    },
+  });
+  const [registerDrawer, { setDrawerProps, closeDrawer }] = useDrawerInner(async data => {
+    console.log('data::::::::', data);
+    rowId.value = data.record?.id;
+  });
+
+  // 提交按钮事件
+  async function handleSubmit() {
+    try {
+      const values = await validate();
+      setDrawerProps({ confirmLoading: true });
+      const params = {
+        deviceId: rowId.value,
+        examinerId: values.examinerId,
+        examinerTime: values.examinerTime,
+        checkContent: {} as any,
+      };
+      delete values.examinerId;
+      delete values.examinerTime;
+      params.checkContent = values;
+      console.log('params::::::::::', params);
+
+      await waterTreatmentAdd({ ...params });
+      createMessage.success('新增成功!');
+      closeDrawer();
+      emit('success', {
+        isUpdate: unref(isUpdate),
+        values: { ...values, id: rowId.value },
+      });
+    } finally {
+      setDrawerProps({ confirmLoading: false });
+    }
+  }
+</script>
+<style lang="less" scoped>
+  .device-info {
+    width: 100%;
+    max-height: 520px;
+    margin-top: -30px;
+    overflow: auto;
+
+    .device-card {
+      display: inline-block;
+      width: 270px;
+      height: 40px;
+      margin: 10px 20px;
+      background: #f4f6f9;
+      border-radius: 4px;
+      font-size: 14px;
+      font-weight: 500;
+      color: #000a18;
+      text-align: center;
+      line-height: 40px;
+      cursor: pointer;
+    }
+
+    .card-select {
+      background: #fff;
+      border-radius: 4px;
+      border: 1px solid #006dff;
+      color: #006dff;
+    }
+
+    .card-disable {
+      background: #fff;
+      border-radius: 4px;
+      border: 1px solid #a5a5a5;
+      color: #a5a5a5;
+      cursor: default;
+    }
+  }
+</style>

+ 170 - 0
src/views/biz/engineer/water/treatmentList/index.vue

@@ -0,0 +1,170 @@
+<template>
+  <div>
+    <div class="flex justify-between mb-4">
+      <div>
+        <a-button type="primary" size="large" class="btn-add" @click="handleCreate"
+          ><template #icon> <Icon icon="icon-xt-add_default|iconfont" /> </template
+          >新增检查</a-button
+        >
+      </div>
+      <div>
+        <XTForm :form-data="formData" @change="callForm" />
+      </div>
+    </div>
+    <BasicTable @register="registerTable">
+      <template #bodyCell="{ column, record }">
+        <template v-if="column.key === 'examinerTime'">
+          {{ dayjs(record.examinerTime).format('YYYY-MM-DD') }}
+        </template>
+        <template v-for="item in dataFiled" :key="item">
+          <template v-if="item === 'systemDisinfection' || item === 'filterReplacement'">
+            <template v-if="column.key === item">
+              <span>{{ record.checkContent[item] === 1 ? '是' : '否' }}</span>
+            </template>
+          </template>
+          <template v-else>
+            <template v-if="column.key === item">
+              <span>{{ record.checkContent[item] === 1 ? '合格' : '不合格' }}</span>
+            </template>
+          </template>
+        </template>
+      </template>
+    </BasicTable>
+    <FormDrawer @register="registerDrawer" @success="callSuccess" />
+    <!--  @cancel="handleCancel"  -->
+  </div>
+</template>
+<script lang="ts" setup>
+  import { onBeforeMount, reactive, ref } from 'vue';
+  import { XTForm } from '/@/components/XTForm/index';
+  import { useDrawer } from '/@/components/Drawer';
+  import FormDrawer from './formDrawer.vue';
+  import { BasicTable, useTable } from '/@/components/Table';
+  import Icon from '/@/components/Icon/index';
+  import { columns, dataFiled } from './data';
+  import { waterTreatmentQueryPage } from '/@/api/biz/engineer/waterTreatmentApi';
+  import dayjs from 'dayjs';
+
+  const props = defineProps({
+    info: {
+      type: Object,
+      default: () => {},
+    },
+  });
+  const [registerDrawer, { openDrawer }] = useDrawer();
+  const tableSort = ref([
+    {
+      field: 'create_time',
+      direction: 'DESC',
+    },
+  ]) as any;
+
+  // formdata
+  const formData = [
+    {
+      name: 'patrolTime',
+      label: '检查时间',
+      componentType: 'RangePicker',
+      placeholder: '请选择检查时间',
+      format: 'YYYY-MM-DD',
+      valueFormat: 'YYYY-MM-DD',
+    },
+  ];
+  const formValue = reactive({
+    patrolTime: [],
+    maintainCompany: '',
+  });
+
+  const [registerTable, { reload }] = useTable({
+    api: waterTreatmentQueryPage,
+    rowKey: 'id',
+    columns,
+    showIndexColumn: false,
+    bordered: true,
+    striped: false,
+    // actionColumn: {
+    //   width: 100,
+    //   title: '操作',
+    //   dataIndex: 'action',
+    // },
+    beforeFetch: handleBeforeFetch,
+  });
+  onBeforeMount(async () => {});
+  // 新增按钮事件
+  function handleCreate() {
+    openDrawer(true, {
+      isUpdate: false,
+      record: { id: props.info.id },
+    });
+  }
+
+  // 表格请求之前,对参数进行处理, 添加默认 排序
+  function handleBeforeFetch(params) {
+    return {
+      ...params,
+      orders: tableSort.value,
+      deviceId: props.info?.id,
+      maintainTime:
+        formValue.patrolTime && formValue.patrolTime.length > 0
+          ? [
+              formValue.patrolTime[0],
+              dayjs(formValue.patrolTime[1]).add(1, 'day').format('YYYY-MM-DD'),
+            ]
+          : undefined,
+      maintainCompany: formValue.maintainCompany,
+    };
+  }
+
+  // 弹窗回调事件
+  async function callSuccess() {
+    await reload();
+  }
+
+  // 查询回调
+  async function callForm(data) {
+    formValue.maintainCompany = data.maintainCompany || '';
+    formValue.patrolTime = data.patrolTime || [];
+    await reload();
+  }
+</script>
+<style lang="less" scoped>
+  ::v-deep(.ant-btn-link) {
+    color: rgb(61 65 85 / 100%);
+  }
+
+  ::v-deep(.ant-input) {
+    border-color: #c2ccd4;
+    border-radius: 4px;
+  }
+
+  ::v-deep(.ant-input-affix-wrapper) {
+    border-color: #c2ccd4;
+    border-radius: 4px;
+  }
+
+  ::v-deep(.ant-picker) {
+    border-color: #c2ccd4;
+    border-radius: 4px;
+  }
+
+  ::v-deep(.ant-table-tbody > tr > td) {
+    border-right: 0 !important;
+    border-left: 0 !important;
+    border-bottom: 1px solid #f0f0f0;
+  }
+
+  ::v-deep(.ant-table-wrapper table) {
+    border: 0;
+  }
+
+  ::v-deep(.ant-table-cell-fix-right-first::after, .ant-table-cell-fix-right-last::after) {
+    content: '';
+    top: auto;
+    width: 0;
+  }
+
+  .btn-add {
+    width: 120px;
+    border-radius: 4px;
+  }
+</style>

+ 106 - 0
src/views/biz/engineer/water/upkeepFormModal.vue

@@ -0,0 +1,106 @@
+<template>
+  <div class="modals">
+    <BasicModal
+      v-bind="$attrs"
+      destroyOnClose
+      @register="registerModal"
+      :title="getTitle"
+      @ok="handleSubmit"
+      :width="600"
+      @cancel="handleCancel"
+    >
+      <div class="!pl-8 !pt-4">
+        <BasicForm @register="registerForm">
+          <template #uniqueCode>
+            <span>{{ deviceInfo?.uniqueCode }}</span>
+          </template>
+          <template #manufacturer>
+            <span>{{ deviceInfo?.manufacturer }}</span>
+          </template>
+          <template #model>
+            <span>{{ deviceInfo?.model }}</span>
+          </template>
+        </BasicForm>
+      </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 { upkeepDataFormSchema } from './data';
+  import { otherDetail } from '@/api/biz/engineer/otherApi';
+  import { upkeepAdd, upkeepDetail, upkeepEdit } from '/@/api/biz/engineer/otherUpkeepApi';
+  const emit = defineEmits(['success', 'cancel', 'register']);
+
+  const getTitle = computed(() => (!unref(isUpdate) ? '保养设备' : '保养设备'));
+  const isUpdate = ref(false);
+  const deviceInfo = ref();
+  const rowId = ref();
+
+  const { createMessage } = useMessage();
+  const [registerForm, { resetFields, validate, setFieldsValue }] = useForm({
+    layout: 'vertical',
+    showResetButton: true,
+    labelWidth: 100,
+    schemas: upkeepDataFormSchema,
+    showActionButtonGroup: false,
+    actionColOptions: {
+      span: 24,
+    },
+    baseColProps: {
+      span: 24,
+    },
+    wrapperCol: {
+      span: 22,
+    },
+  });
+  const [registerModal, { setModalProps, closeModal }] = useModalInner(async data => {
+    await resetFields();
+    setModalProps({ confirmLoading: false });
+    isUpdate.value = !!data?.isUpdate;
+    if (!data.record.name) {
+      deviceInfo.value = await otherDetail(data.record?.id);
+    } else {
+      deviceInfo.value = data.record;
+    }
+    if (unref(isUpdate)) {
+      rowId.value = data.upkeepRecord.id;
+      const resData = await upkeepDetail(data.upkeepRecord.id);
+      console.log('resData::::', resData);
+      await setFieldsValue({
+        ...resData,
+      });
+    }
+  });
+
+  // 提交按钮事件
+  async function handleSubmit() {
+    try {
+      const values = await validate();
+      setModalProps({ confirmLoading: true });
+      values.picture = values.files && values.files.map(ele => ele.id);
+      values.deviceId = deviceInfo.value.id;
+      !unref(isUpdate)
+        ? await upkeepAdd({ ...values })
+        : await upkeepEdit({ ...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>

+ 24 - 0
src/views/biz/engineer/water/upkeepList/data.ts

@@ -0,0 +1,24 @@
+import { BasicColumn } from '/@/components/Table';
+
+export const columns: BasicColumn[] = [
+  {
+    title: '保养时间',
+    dataIndex: 'maintainTime',
+    align: 'left',
+  },
+  {
+    title: '保养方',
+    dataIndex: 'maintainCompany',
+    align: 'left',
+  },
+  {
+    title: '保养内容',
+    dataIndex: 'content',
+    align: 'left',
+  },
+  {
+    title: '保养图片',
+    dataIndex: 'picture',
+    align: 'left',
+  },
+];

+ 265 - 0
src/views/biz/engineer/water/upkeepList/index.vue

@@ -0,0 +1,265 @@
+<template>
+  <div>
+    <div class="flex justify-between mb-4">
+      <div>
+        <a-button type="primary" size="large" class="btn-add" @click="handleCreate"
+          ><template #icon> <Icon icon="icon-xt-add_default|iconfont" /> </template
+          >新增保养</a-button
+        >
+      </div>
+      <div>
+        <XTForm :form-data="formData" @change="callForm" />
+      </div>
+    </div>
+    <BasicTable @register="registerTable">
+      <template #bodyCell="{ column, record }">
+        <template v-if="column.key === 'maintainCompany'">
+          <span>{{ formatDictValue(bizDictOptions.dmc, record.maintainCompany) }}</span>
+        </template>
+        <template v-if="column.key === 'schedule'">
+          <span>{{ formatDictValue(bizDictOptions.rp, record.schedule) }}</span>
+        </template>
+        <template v-if="column.key === 'picture'">
+          <Image
+            v-if="record.files && record.files.length > 0"
+            :src="record.files[0].absolutePath"
+            :width="60"
+          />
+        </template>
+        <template v-if="column.key === 'action'">
+          <TableAction
+            :actions="[
+              {
+                auth: 'archives:patrolWard:edit',
+                icon: 'icon-xt-details_edit_default|iconfont',
+                tooltip: '编辑',
+                onClick: handleEdit.bind(null, record),
+              },
+              {
+                auth: 'archives:patrolWard:remove',
+                icon: 'icon-xt-details_delete_default|iconfont',
+                tooltip: '删除',
+                popConfirm: {
+                  title: '是否取消删除',
+                  placement: 'left',
+                  confirm: handleDelete.bind(null, record, column),
+                },
+              },
+            ]"
+          />
+        </template>
+      </template>
+    </BasicTable>
+    <FormModal @register="registerModal" @success="callSuccess" />
+  </div>
+</template>
+<script lang="ts" setup>
+  import { onBeforeMount, onMounted, reactive, ref } from 'vue';
+  import { XTForm } from '/@/components/XTForm/index';
+  import { BasicTable, useTable, TableAction } from '/@/components/Table';
+  import { useMessage } from '/@/hooks/web/useMessage';
+  import FormModal from '../upkeepFormModal.vue';
+  import Icon from '/@/components/Icon/index';
+  import { columns } from './data';
+  import { listDictModelBatch } from '/@/api/common';
+  import { upkeepQueryPage, upkeepRemove } from '/@/api/biz/engineer/otherUpkeepApi';
+  import { useModal } from '/@/components/Modal';
+  import { Image } from 'ant-design-vue';
+  import dayjs from 'dayjs';
+  import { formatDictValue } from '/@/utils';
+
+  const props = defineProps({
+    info: {
+      type: Object,
+      default: () => {},
+    },
+  });
+
+  const { createMessage } = useMessage();
+  const [registerModal, { openModal }] = useModal();
+  const tableSort = ref([
+    {
+      field: 'create_time',
+      direction: 'DESC',
+    },
+  ]) as any;
+
+  const dictsOption = ref([]) as any;
+  // formdata
+  const formData = [
+    {
+      label: '保养厂商',
+      name: 'maintainCompany',
+      componentType: 'Select',
+      dicts: dictsOption.value,
+      placeholder: '请选择维修方',
+      prefix: 'icon-xt-search',
+      width: 280,
+    },
+    {
+      name: 'patrolTime',
+      label: '保养时间',
+      componentType: 'RangePicker',
+      placeholder: '请选择保养时间',
+      format: 'YYYY-MM-DD',
+      valueFormat: 'YYYY-MM-DD',
+    },
+  ];
+  const formValue = reactive({
+    patrolTime: [],
+    maintainCompany: '',
+  });
+
+  const [registerTable, { reload }] = useTable({
+    api: upkeepQueryPage,
+    rowKey: 'id',
+    columns,
+    showIndexColumn: false,
+    bordered: true,
+    striped: false,
+    actionColumn: {
+      width: 200,
+      title: '操作',
+      dataIndex: 'action',
+    },
+    beforeFetch: handleBeforeFetch,
+    sortFn: handleSortFn,
+  });
+  const bizDictData = ref([
+    { key: 'dmc', dictCode: 'dmc' },
+    { key: 'rp', dictCode: 'rp' },
+  ]);
+
+  const bizDictOptions = reactive<any>({});
+  onBeforeMount(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 types = bizDictOptions.dmc;
+    dictsOption.value.push({
+      label: '全部',
+      value: '',
+    });
+    types.forEach(item => {
+      dictsOption.value.push({
+        label: item.label,
+        value: item.value,
+      });
+    });
+  });
+  onMounted(async () => {
+    // callForm({
+    //   patrolTime: [
+    //     dayjs().subtract(3, 'month').format('YYYY-MM-DD'),
+    //     dayjs().add(1, 'day').format('YYYY-MM-DD'),
+    //   ],
+    // });
+  });
+  // 新增按钮事件
+  function handleCreate() {
+    openModal(true, {
+      isUpdate: false,
+      record: { id: props.info.id },
+    });
+  }
+
+  // 编辑按钮事件
+  function handleEdit(record: Recordable) {
+    openModal(true, {
+      record: { id: props.info.id },
+      upkeepRecord: record,
+      isUpdate: true,
+    });
+  }
+
+  // 删除按钮事件
+  async function handleDelete(record: Recordable) {
+    await upkeepRemove([record.id]);
+    createMessage.success('删除成功!');
+    await reload();
+  }
+  // 表格点击字段排序
+  function handleSortFn(sortInfo) {
+    if (sortInfo?.order && sortInfo?.columnKey) {
+      // 默认单列排序
+      tableSort.value = [
+        {
+          field: sortInfo.columnKey,
+          direction: sortInfo.order.replace(/(\w+)(end)/g, '$1').toUpperCase(),
+        },
+      ];
+    }
+  }
+
+  // 表格请求之前,对参数进行处理, 添加默认 排序
+  function handleBeforeFetch(params) {
+    return {
+      ...params,
+      orders: tableSort.value,
+      deviceId: props.info?.id,
+      maintainTime:
+        formValue.patrolTime && formValue.patrolTime.length > 0
+          ? [
+              formValue.patrolTime[0],
+              dayjs(formValue.patrolTime[1]).add(1, 'day').format('YYYY-MM-DD'),
+            ]
+          : undefined,
+      maintainCompany: formValue.maintainCompany,
+    };
+  }
+
+  // 弹窗回调事件
+  async function callSuccess() {
+    await reload();
+  }
+
+  // 查询回调
+  async function callForm(data) {
+    formValue.maintainCompany = data.maintainCompany || '';
+    formValue.patrolTime = data.patrolTime || [];
+    await reload();
+  }
+</script>
+<style lang="less" scoped>
+  ::v-deep(.ant-btn-link) {
+    color: rgb(61 65 85 / 100%);
+  }
+
+  ::v-deep(.ant-input) {
+    border-color: #c2ccd4;
+    border-radius: 4px;
+  }
+
+  ::v-deep(.ant-input-affix-wrapper) {
+    border-color: #c2ccd4;
+    border-radius: 4px;
+  }
+
+  ::v-deep(.ant-picker) {
+    border-color: #c2ccd4;
+    border-radius: 4px;
+  }
+
+  ::v-deep(.ant-table-tbody > tr > td) {
+    border-right: 0 !important;
+    border-left: 0 !important;
+    border-bottom: 1px solid #f0f0f0;
+  }
+
+  ::v-deep(.ant-table-wrapper table) {
+    border: 0;
+  }
+
+  ::v-deep(.ant-table-cell-fix-right-first::after, .ant-table-cell-fix-right-last::after) {
+    content: '';
+    top: auto;
+    width: 0;
+  }
+
+  .btn-add {
+    width: 120px;
+    border-radius: 4px;
+  }
+</style>

+ 19 - 7
src/views/biz/inventory/pharmaceuticals/index.vue

@@ -89,6 +89,7 @@
   import { XTTitle } from '/@/components/XTTitle/index';
   import { XTTab } from '/@/components/XTTab/index';
   import { XTForm } from '/@/components/XTForm/index';
+  import dayjs from 'dayjs';
 
   // 标题数据
   const titleData = [
@@ -198,22 +199,31 @@
   async function handleDelete(record: Recordable) {
     if (record) {
       await pharmaceuticalsDel(record.id);
-      createMessage.success('停用成功!');
+      let message = '停用成功!';
+      if (record.disable === 1) {
+        message = '启用成功!';
+      }
+      createMessage.success(message);
       clearSelectedRowKeys();
-      await reload();
       await getTab();
+      await reload();
     }
   }
 
   // 表格请求之前,对参数进行处理, 添加默认 排序
   async function handleBeforeFetch(params) {
-    console.log('searchNames:::', searchNames.value);
+    const shiftTimes = [];
+    if (shiftDate.value && shiftDate.value.length > 0) {
+      shiftTimes.push(shiftDate.value[0]);
+      shiftTimes.push(shiftDate.value[1]);
+      shiftTimes[1] = dayjs(shiftTimes[1]).add(1, 'day').format('YYYY-MM-DD');
+    }
     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,
+      time: shiftTimes.length <= 0 ? undefined : shiftTimes,
     };
   }
 
@@ -247,22 +257,24 @@
       hasBracket: true,
     });
     typeOptions.value = typeList;
-    tabSelected.value = typeOptions.value[0].key;
+    if (tabSelected.value === undefined) {
+      tabSelected.value = typeOptions.value[0].key;
+    }
   }
 
   //取消按钮事件
   async function handleCancel() {
     clearSelectedRowKeys();
-    await reload();
     await getTab();
+    await reload();
   }
 
   // 弹窗回调事件
   async function callSuccess({ isUpdate, values }) {
     console.log(isUpdate);
     console.log(values);
-    await reload();
     await getTab();
+    await reload();
   }
   // 选项卡组件回调
   async function callTab(data) {

+ 19 - 7
src/views/biz/inventory/supplies/index.vue

@@ -89,6 +89,7 @@
   import { XTTitle } from '/@/components/XTTitle/index';
   import { XTTab } from '/@/components/XTTab/index';
   import { XTForm } from '/@/components/XTForm/index';
+  import dayjs from 'dayjs';
 
   // 标题数据
   const titleData = [
@@ -199,10 +200,14 @@
   async function handleDelete(record: Recordable) {
     if (record) {
       await suppliesDel(record.id);
-      createMessage.success('停用成功!');
+      let message = '停用成功!';
+      if (record.disable === 1) {
+        message = '启用成功!';
+      }
+      createMessage.success(message);
       clearSelectedRowKeys();
-      await reload();
       await getTab();
+      await reload();
     }
   }
   // 表格点击字段排序
@@ -220,13 +225,18 @@
 
   // 表格请求之前,对参数进行处理, 添加默认 排序
   async function handleBeforeFetch(params) {
-    console.log('searchNames:::', searchNames.value);
+    const shiftTimes = [];
+    if (shiftDate.value && shiftDate.value.length > 0) {
+      shiftTimes.push(shiftDate.value[0]);
+      shiftTimes.push(shiftDate.value[1]);
+      shiftTimes[1] = dayjs(shiftTimes[1]).add(1, 'day').format('YYYY-MM-DD');
+    }
     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,
+      time: shiftTimes.length <= 0 ? undefined : shiftTimes,
     };
   }
 
@@ -260,22 +270,24 @@
       hasBracket: true,
     });
     typeOptions.value = typeList;
-    tabSelected.value = typeOptions.value[0].key;
+    if (tabSelected.value === undefined) {
+      tabSelected.value = typeOptions.value[0].key;
+    }
   }
 
   //取消按钮事件
   async function handleCancel() {
     clearSelectedRowKeys();
-    await reload();
     await getTab();
+    await reload();
   }
 
   // 弹窗回调事件
   async function callSuccess({ isUpdate, values }) {
     console.log(isUpdate);
     console.log(values);
-    await reload();
     await getTab();
+    await reload();
   }
   // 选项卡组件回调
   async function callTab(data) {

+ 1 - 6
src/views/biz/management/complication/index.vue

@@ -69,11 +69,6 @@
                 ></template>
                 <BasicTable @register="registerTable">
                   <template #bodyCell="{ column, record }">
-                    <!-- <template v-if="column.key === 'disable'">
-                <Tag :color="formatDictColor(disableOptions, record.disable)">
-                  {{ formatDictValue(disableOptions, record.disable) }}
-                </Tag>
-              </template> -->
                     <template v-if="column.key === 'action'">
                       <TableAction
                         :actions="[
@@ -272,7 +267,7 @@
         },
         {
           label: '排序',
-          value: complication.sort,
+          value: complication.sort + '',
         },
         {
           label: '并发症描述',

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

@@ -29,7 +29,7 @@
                 v-if="editableData[record.key]"
                 v-model:value="editableData[record.key][column.dataIndex]"
                 :min="0"
-                :max="365"
+                :max="360"
                 placeholder="请输入参数内容"
               />
               <template v-else> {{ record.contents }} </template>

+ 9 - 9
src/views/biz/mission/article/data.ts

@@ -1,10 +1,12 @@
-import { BasicColumn, FormSchema } from '/@/components/Table';
+import { FormSchema } from '/@/components/Table';
 import { listDictModel, uploadApi } from '/@/api/common';
+// import { DescItem } from '@/components/Description';
 
-export const columns: BasicColumn[] = [
+export const columns = [
   {
     title: '封面',
     dataIndex: 'cover',
+    // slots: { customRender: 'pic' },
   },
   {
     title: '标题',
@@ -32,6 +34,7 @@ export const dataFormSchema: FormSchema[] = [
   {
     label: '宣教标题',
     field: 'title',
+    required: true,
     component: 'Input',
     componentProps: {},
   },
@@ -51,24 +54,21 @@ export const dataFormSchema: FormSchema[] = [
 
   {
     label: '封面',
-    field: 'cover',
+    field: 'files',
     component: 'XTUpload',
     componentProps: ({ formModel, schema }) => {
       return {
         api: uploadApi,
         maxSize: 10,
-        maxNumber: 5,
-        helpText: '仅支持上传jpg/png/pdf文件,文件大小不超过2M',
-        accept: ['image/*', '.pdf', '.doc', 'docx', 'xls'],
+        maxNumber: 1,
+        helpText: '仅支持上传jpg/png/pdf文件,文件大小不超过10M',
+        accept: ['image/*', '.pdf', '.doc', 'docx', 'xls', 'jpg', 'png'],
         onChange: data => {
           console.log('🚀 ~ file: data.ts:57 ~ data:', data);
           formModel[schema.field] = data;
         },
       };
     },
-    colProps: {
-      span: 22,
-    },
   },
   {
     label: '宣教内容',

+ 5 - 1
src/views/biz/mission/article/formDrawer.vue

@@ -47,12 +47,15 @@
   const [registerDrawer, { setDrawerProps, closeDrawer }] = useDrawerInner(async data => {
     await resetFields();
     setDrawerProps({ confirmLoading: false });
-    isUpdate.value = !!data?.isUpdate;
+    isUpdate.value = data?.isUpdate;
 
     if (unref(isUpdate)) {
       rowId.value = data.record.id;
       const resData = await articleById(data.record.id);
       console.log('resData::::', resData);
+      const files = [];
+      files.push(resData?.file);
+      resData.files = files;
       await setFieldsValue({
         ...resData,
       });
@@ -64,6 +67,7 @@
     try {
       const values = await validate();
       setDrawerProps({ confirmLoading: true });
+      values.cover = values.files && values.files.map(ele => ele.id)[0];
       !unref(isUpdate)
         ? await articleAdd({ ...values })
         : await articleEdit({ ...values, id: rowId.value });

+ 32 - 21
src/views/biz/mission/article/index.vue

@@ -9,12 +9,22 @@
           :selected="tabSelected"
           :data="typeOptions"
           @item-click="callTab"
+          :columns="columns"
         />
         <XTForm :form-data="formData" @change="callForm" />
       </div>
     </div>
     <BasicTable @register="registerTable">
       <template #bodyCell="{ column, record }">
+        <template v-if="column.dataIndex === 'cover'">
+          <Image :width="80" v-if="record.file" :src="record.file?.absolutePath" />
+          <span v-else />
+          <!-- <Image :src="record.cover" :width="80" /> -->
+        </template>
+        <template v-if="column.dataIndex === 'updatorName'">
+          <Image :src="record.updateAvatar" style="width: 20; height: 20px" />
+          {{ record.updatorName }}
+        </template>
         <template v-if="column.key === 'disable'">
           <span
             :class="['table-dot']"
@@ -60,7 +70,11 @@
                 auth: 'biz:educationLibrary:remove',
                 icon: 'icon-xt-details_delete_default|iconfont',
                 tooltip: '删除',
-                onClick: handleDelete.bind(null, record),
+                popConfirm: {
+                  title: '是否确认删除',
+                  placement: 'left',
+                  confirm: handleDelete.bind(null, record),
+                },
               },
             ]"
           />
@@ -88,13 +102,13 @@
     articleDel,
   } from '/@/api/biz/mission/articleApi';
   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';
   import { useDrawer } from '@/components/Drawer';
   import { useModal } from '@/components/Modal';
-
+  import { Image } from 'ant-design-vue';
+  // import { Select } from 'ant-design-vue';
   // 标题数据
   const titleData = [
     {
@@ -113,22 +127,27 @@
       btnText: '新增宣教',
     },
   ];
+
   // formdata
   const formData = [
     {
-      name: 'tableSort',
+      name: 'shiftDate',
       componentType: 'Select',
       placeholder: '请选择',
       width: 120,
-      defaultValue: 'patrolTime',
+      defaultValue: '',
       dicts: [
-        { label: '全部', value: 'patrolTime' },
-        { label: '正常', value: 'disable ="0"' },
-        { label: '停用', value: 'disable ="1"' },
+        { label: '全部', value: '' },
+        { label: '治疗宣教', value: 'het_treatment_education' },
+        { label: '血管通路', value: 'het_vascular_access' },
+        { label: '检查指导', value: 'het_inspection_guidance' },
+        { label: '药物指导', value: 'het_drug_guidance' },
+        { label: '饮食指导', value: 'het_dietary_guidance' },
+        { label: '康复指导', value: 'het_rehabilitative_guidance' },
       ],
     },
     {
-      name: 'title',
+      name: 'searchNames',
       componentType: 'Input',
       prefix: 'icon-xt-search',
       placeholder: '请输入宣教标题',
@@ -153,22 +172,15 @@
   const { createMessage } = useMessage();
   const [registerDrawer, { openDrawer }] = useDrawer();
   const [registerImpModal, { openModal: openImpModal }] = useModal();
-
-  // const tableSort = ref([
-  //   {
-  //     field: 'create_time',
-  //     direction: 'DESC',
-  //   },
-  // ]) as any;
-
   const [registerTable, { reload, clearSelectedRowKeys }] = useTable({
     api: articleList,
-    batchDelApi: articleStatus,
+    batchDelApi: articleDel,
     // batchExportApi: pharmaceuticalsExport,
     delAuthList: ['biz:educationLibrary:remove'],
     rowKey: 'id',
     columns,
     showIndexColumn: true,
+    rowSelection: { type: 'checkbox' },
     bordered: true,
     actionColumn: {
       width: 200,
@@ -226,9 +238,9 @@
     return {
       ...params,
       // orders: tableSort.value,
-      name: searchNames.value == '' ? undefined : searchNames.value,
+      title: searchNames.value == '' ? undefined : searchNames.value,
       status: tabSelected.value == '' ? undefined : tabSelected.value,
-      time: shiftDate.value.length <= 0 ? undefined : shiftDate.value,
+      type: shiftDate.value.length <= 0 ? undefined : shiftDate.value,
     };
   }
 
@@ -319,4 +331,3 @@
     margin-right: 5px;
   }
 </style>
-../../../../api/biz/mission/articleApi

+ 50 - 31
src/views/biz/mission/record/index.vue

@@ -24,11 +24,6 @@
           />
           <span> {{ formatDictValue(responseTypeOptions, record.treatmentStage) }}</span>
         </template>
-        <!-- <template v-if="column.key === 'supplierCategory'">
-          <span>
-            {{ formatDictValue(responsesupplierCategoryOptions, record.supplierCategory) }}</span
-          >
-        </template> -->
         <template v-if="column.key === 'action'">
           <TableAction
             :actions="[
@@ -36,7 +31,11 @@
                 auth: 'biz:drug:edit',
                 icon: 'icon-xt-details_delete_default|iconfont',
                 tooltip: '删除',
-                onClick: handleDelete.bind(null, record),
+                popConfirm: {
+                  title: '是否确认删除',
+                  placement: 'left',
+                  confirm: handleDelete.bind(null, record),
+                },
               },
             ]"
           />
@@ -53,12 +52,12 @@
   import { formatDictValue, formatDictPreColor } from '/@/utils';
   import { columns } from './data';
 
-  import { getrecordList, recordNumber, recordDel } from '/@/api/biz/mission/recordApi';
+  import { recordList, recordNumber, recordDel } from '/@/api/biz/mission/recordApi';
   import { listDictModel } from '/@/api/common';
   import { XTTitle } from '/@/components/XTTitle/index';
   import { XTTab } from '/@/components/XTTab/index';
   import { XTForm } from '/@/components/XTForm/index';
-
+  import dayjs from 'dayjs';
   // formdata
   const formData = [
     {
@@ -67,7 +66,7 @@
       format: 'YYYY-MM-DD',
       valueFormat: 'YYYY-MM-DD',
       placeholder: '请选择日期',
-      width: 240,
+      width: 300,
     },
     {
       name: 'searchNames',
@@ -77,6 +76,7 @@
       width: 240,
     },
   ];
+
   // tab 切换选中
   const tabSelected = ref();
   // 操作名称
@@ -85,27 +85,18 @@
 
   const typeOptions = ref();
   const responseTypeOptions = ref();
-  const responsesupplierCategoryOptions = ref();
   onBeforeMount(async () => {
-    responseTypeOptions.value = await listDictModel({ dictCode: 'sys_disable_type' });
-    responsesupplierCategoryOptions.value = await listDictModel({ dictCode: 'd' });
+    responseTypeOptions.value = await listDictModel({ dictCode: 'd' });
     getTab();
   });
 
-  const { createMessage } = useMessage();
-
-  // const tableSort = ref([
-  //   {
-  //     field: 'create_time',
-  //     direction: 'DESC',
-  //   },
-  // ]) as any;
+  const { createConfirm, createMessage } = useMessage();
 
-  const [registerTable, { reload, clearSelectedRowKeys }] = useTable({
-    api: getrecordList,
+  const [registerTable, { reload, clearSelectedRowKeys, getSelectRowKeys }] = useTable({
+    api: recordList,
     batchDelApi: recordDel,
     // batchExportApi: pharmaceuticalsExport,
-    delAuthList: ['biz:consumable:remove'],
+    delAuthList: ['biz:educationRecord:remove'],
     rowKey: 'id',
     columns,
     showIndexColumn: true,
@@ -121,28 +112,44 @@
   // 删除按钮事件
   async function handleDelete(record: Recordable) {
     if (record) {
-      await recordDel(record.id);
+      await recordDel([record.id]);
       createMessage.success('删除成功!');
       clearSelectedRowKeys();
       await reload();
       await getTab();
+    } else {
+      createConfirm({
+        content: '你确定要删除?',
+        iconType: 'warning',
+        onOk: async () => {
+          const keys = getSelectRowKeys();
+          await recordDel(keys);
+          createMessage.success('删除成功!');
+          await reload();
+          await getTab();
+        },
+      });
     }
   }
-
   // 表格请求之前,对参数进行处理, 添加默认 排序
   async function handleBeforeFetch(params) {
+    const shiftTimes = [];
+    if (shiftDate.value && shiftDate.value.length > 0) {
+      shiftTimes.push(shiftDate.value[0]);
+      shiftTimes.push(shiftDate.value[1]);
+      shiftTimes[1] = dayjs(shiftTimes[1]).add(1, 'day').format('YYYY-MM-DD');
+    }
     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,
+      title: searchNames.value == '' ? undefined : searchNames.value,
+      treatmentStage: tabSelected.value == '' ? undefined : tabSelected.value,
+      time: shiftTimes.length <= 0 ? undefined : shiftTimes,
     };
   }
 
   async function getTab() {
-    typeOptions.value = await listDictModel({ dictCode: 'sys_disable_type' });
+    typeOptions.value = await listDictModel({ dictCode: 'd' });
     const typeNums = await recordNumber(); // 获取各类型数量
     let typeList = [];
     typeOptions.value.forEach(ele => {
@@ -182,8 +189,8 @@
 
   // 查询组件回调
   async function callForm(data) {
-    shiftDate.value = data.shiftDate ? data.shiftDate : '';
     searchNames.value = data.searchNames ? data.searchNames : '';
+    shiftDate.value = data.shiftDate ? data.shiftDate : '';
     console.log('callForm:::', searchNames.value);
     await reload();
   }
@@ -197,6 +204,18 @@
     border-radius: 50%;
   }
 
+  &--d_after {
+    background-color: #1bc1b3;
+  }
+
+  &--d_among {
+    background-color: #2d5aff;
+  }
+
+  &--d_before {
+    background-color: #854aff;
+  }
+
   ::v-deep(.ant-btn-link) {
     color: #3d4155;
   }

+ 133 - 0
src/views/biz/stats/chart/pie.vue

@@ -0,0 +1,133 @@
+<template>
+  <div class="pie">
+    <!-- <div class="pie-percentage no-print" @click="handlePercentage"
+      >{{ showPercentage ? '隐藏' : '显示' }}百分比</div> -->
+    <div ref="chartRef" :style="{ height, width }" />
+  </div>
+</template>
+<script lang="ts" setup>
+  import { onMounted, PropType, ref, Ref, watch } from 'vue';
+
+  import { useECharts } from '/@/hooks/web/useECharts';
+  const props = defineProps({
+    width: {
+      type: String as PropType<string>,
+      default: '100%',
+    },
+    height: {
+      type: String as PropType<string>,
+      default: '400px',
+    },
+    data: {
+      type: Object,
+      default: () => {},
+    },
+  });
+  // console.log('props', props);
+  const chartRef = ref<HTMLDivElement | null>(null);
+  const { setOptions } = useECharts(chartRef as Ref<HTMLDivElement>);
+  const showPercentage = ref(true);
+  onMounted(async () => {
+    setChart();
+  });
+  watch(
+    () => props.data,
+    _newVal => {
+      setChart();
+    },
+  );
+  // function handlePercentage() {
+  //   showPercentage.value = !showPercentage.value;
+  //   setChart();
+  // }
+  function setChart() {
+    const data = props.data?.content;
+    const sum = data?.reduce((pre, cur) => pre + cur.value, 0) || 0;
+    let legend = {} as any;
+    legend =
+      props.data.id != 'bubble' &&
+      props.data.id != 'jam' &&
+      props.data.id != 'noBox' &&
+      props.data.id != 'limit' &&
+      props.data.id != 'lowBattery' &&
+      props.data.id != 'outOfControl' &&
+      props.data.id != 'machine'
+        ? {
+            orient: 'vertical',
+            right: 20,
+            top: 40,
+            bottom: 20,
+          }
+        : {
+            orient: 'horizontal',
+            right: 20,
+            top: 40,
+            type: 'scroll',
+          };
+
+    const lableFormatter = showPercentage.value ? '{b} (数量: {c}, 占比: {d}%)' : '{b} ({c})';
+    setOptions({
+      title: {
+        show: false,
+        text: props.data?.description || '',
+        left: 'left',
+      },
+      tooltip: {
+        trigger: 'item',
+        valueFormatter: (value: number) =>
+          '数量: ' + value + ' 占比: ' + (Number(((value / sum) * 100).toFixed(2)) || 0) + '%',
+      },
+      toolbox: {
+        feature: {
+          saveAsImage: {
+            type: 'png',
+          },
+        },
+      },
+      legend,
+      series: [
+        {
+          type: 'pie',
+          radius: '50%',
+          data: props.data?.content || [],
+          minAngle: 5,
+          avoidLabelOverlap: true,
+          label: {
+            show: true,
+            formatter: lableFormatter,
+            overflow: 'break',
+          },
+          emphasis: {
+            itemStyle: {
+              shadowBlur: 10,
+              shadowOffsetX: 0,
+              shadowColor: 'rgba(0, 0, 0, 0.5)',
+            },
+          },
+        },
+      ],
+    });
+  }
+</script>
+<style lang="less" scoped>
+  .pie {
+    position: relative;
+
+    &-percentage {
+      position: absolute;
+      top: 4px;
+      right: 35px;
+      z-index: 99;
+      border: 1px solid #ccc;
+      border-radius: 4px;
+      padding: 2px 4px;
+      font-size: 12px;
+      cursor: pointer;
+      transition: all 0.3s ease-in-out;
+
+      &:hover {
+        box-shadow: 2px 4px 4px rgb(12 12 12 / 10%);
+      }
+    }
+  }
+</style>

+ 6 - 0
src/views/biz/stats/supplies/data.ts

@@ -0,0 +1,6 @@
+export const BasicTab = [
+  { key: 'suppliesStats', value: 0, title: '耗材用量' },
+  { key: 'pharmaceuticalsStats', value: 1, title: '药品用量' },
+];
+
+export const BasicTabActive = BasicTab[0].key;

+ 51 - 3
src/views/biz/stats/supplies/index.vue

@@ -1,7 +1,55 @@
 <template>
-  <div> 占位符 </div>
+  <div class="m-4">
+    <div class="mb-4">
+      <XTTitle :title="title" :go-back="false" />
+    </div>
+    <div class="py-2 bg-white">
+      <a-tabs v-model:activeKey="activeKey" :destroyInactiveTabPane="true">
+        <a-tab-pane
+          v-for="item in tabData"
+          :key="item.key"
+          :tab="item.title"
+          :class="'tab-' + item.key"
+        >
+          <!-- 耗材用量 0 -->
+          <div v-if="item.value == 0 && info.id">
+            <SuppliesStats />
+          </div>
+          <!-- 药品用量 1 -->
+          <div v-if="item.value == 1 && info.id">
+            <PharmaceuticalsStats />
+          </div>
+        </a-tab-pane>
+      </a-tabs>
+    </div>
+  </div>
 </template>
 
-<script setup lang="ts"></script>
+<script setup lang="ts">
+  import { ref } from 'vue';
+  import { BasicTab, BasicTabActive } from './data';
+  import PharmaceuticalsStats from './pharmaceuticalsStats/index.vue';
+  import SuppliesStats from './suppliesStats/index.vue';
+  import { useRoute } from 'vue-router';
+  import { XTTitle } from '/@/components/XTTitle/index';
 
-<style lang="less" scoped></style>
+  const route = useRoute();
+  console.log('🚀 ~ file: index.vue:25 ~ route:', route);
+  const info = ref({
+    id: String(route.query?.id),
+    name: route.query?.name,
+  });
+  const activeKey = ref(BasicTabActive);
+  const tabData = ref(BasicTab);
+  const title = '药品耗材';
+</script>
+
+<style lang="less" scoped>
+  .tab-medicalDocuments {
+    padding: 0;
+  }
+
+  ::v-deep(.ant-tabs-tab) {
+    padding: 12px;
+  }
+</style>

+ 37 - 0
src/views/biz/stats/supplies/pharmaceuticalsStats/data.ts

@@ -0,0 +1,37 @@
+import { BasicColumn } from '/@/components/Table';
+
+export const columns: BasicColumn[] = [
+  {
+    title: '药品类型',
+    dataIndex: 'type',
+    align: 'left',
+  },
+  {
+    title: '数量',
+    dataIndex: 'sum',
+    align: 'left',
+  },
+  {
+    title: '占比',
+    dataIndex: 'proportion',
+    align: 'left',
+  },
+];
+
+export const detailColumns: BasicColumn[] = [
+  {
+    title: '药品名称',
+    dataIndex: 'type',
+    align: 'left',
+  },
+  {
+    title: '数量',
+    dataIndex: 'sum',
+    align: 'left',
+  },
+  {
+    title: '占比',
+    dataIndex: 'proportion',
+    align: 'left',
+  },
+];

+ 192 - 0
src/views/biz/stats/supplies/pharmaceuticalsStats/index.vue

@@ -0,0 +1,192 @@
+<template>
+  <div class="flex items-center xt-form">
+    <XTForm :form-data="formData" @change="callForm" />
+    <Button type="default" @click="handleDownload"
+      ><Icon icon="icon-xt-download-download_default|iconfont" :size="14" />
+    </Button>
+  </div>
+  <div class="mb-4">
+    <XTTitle :title="title" :go-back="false" />
+  </div>
+  <Row style="max-height: 400px">
+    <Col :span="16">
+      <Pie :data="pieData" />
+    </Col>
+    <Col :span="8">
+      <BasicTable @register="registerTable">
+        <template #bodyCell="{ column, record }">
+          <template v-if="column.key === 'type'">
+            <span> {{ formatDictValue(durgTypeOptions, record.type) }}</span>
+          </template>
+          <template v-if="column.key === 'sum'">
+            <a @click="handleDetail(record)">{{ record.sum }}</a>
+          </template>
+        </template>
+      </BasicTable>
+    </Col>
+  </Row>
+  <Row style="max-height: 200px">
+    <Col :span="24">
+      <BasicTable @register="registerDetailTable">
+        <template #headerCell="{ column }">
+          <template v-if="column.key === 'type'"> {{ detailName }} </template>
+          <template v-if="column.key === 'sum'"> 数量 </template>
+          <template v-if="column.key === 'proportion'"> 占比 </template>
+        </template>
+      </BasicTable>
+    </Col>
+  </Row>
+</template>
+
+<script setup lang="ts">
+  import { onMounted, ref } from 'vue';
+  import { Row, Col, Button } from 'ant-design-vue';
+  import { XTForm } from '/@/components/XTForm/index';
+  import { XTTitle } from '/@/components/XTTitle/index';
+  import { getDrugStats } from '/@/api/biz/stats/suppliesStatsApi';
+  import { BasicTable, useTable } from '@/components/Table';
+  import { columns, detailColumns } from './data';
+  import { Icon } from '/@/components/Icon';
+  import { formatDictValue } from '/@/utils';
+  import { listDictModel } from '/@/api/common';
+  import Pie from '../../chart/pie.vue';
+  // formdata
+  const title = ref('药品用量统计');
+  const pieData = ref({} as any); // Echart图表组件赋值变量
+  const tableData = ref([]); // 药品类型统计数据
+  const detailTableData = ref([]);
+  const patrolTime = ref([]); // 时间查询参数
+  const detailName = ref('');
+  const formData = [
+    {
+      name: 'patrolTime',
+      componentType: 'RangePicker',
+      placeholder: '请选择维修时间',
+      format: 'YYYY-MM-DD',
+      valueFormat: 'YYYY-MM-DD',
+    },
+    {},
+  ];
+  // 药品类型统计表格配置
+  const [registerTable, { setTableData }] = useTable({
+    showIndexColumn: false,
+    bordered: true,
+    striped: false,
+    pagination: false,
+    maxHeight: 200,
+    showSummary: true,
+    summaryFunc: handleSummary,
+    dataSource: tableData.value,
+    columns: columns,
+  });
+
+  const [registerDetailTable, { setTableData: setDetailTableData }] = useTable({
+    showIndexColumn: false,
+    bordered: true,
+    striped: false,
+    pagination: false,
+    maxHeight: 200,
+    dataSource: detailTableData.value,
+    columns: detailColumns,
+  });
+
+  const durgTypeOptions = ref();
+  onMounted(async () => {
+    durgTypeOptions.value = await listDictModel({ dictCode: 'pht' }); // 查询药品类型字典数据
+    getDatas(); // 执行获取统计数据方法
+  });
+
+  // 查询组件回调
+  async function callForm(data) {
+    patrolTime.value = data.patrolTime || [];
+    getDatas();
+  }
+  // 获取统计数据
+  async function getDatas() {
+    const params = {
+      statsTime: patrolTime.value,
+    };
+    const res = await getDrugStats(params); // 请求药品统计接口
+    tableData.value = res.data;
+    await setTableData(res.data); // 右侧统计数据表格赋值
+    // 组合Echart图表数据格式
+    const dataList = {
+      content: [],
+      description: title.value,
+    };
+    res.data.forEach(item => {
+      dataList.content.push({
+        name: formatDictValue(durgTypeOptions.value, item.type),
+        value: item.sum,
+      });
+    });
+    pieData.value = dataList; // Echart图表赋值
+    handleDetail(tableData.value[0]);
+  }
+  // 统计下载事件
+  function handleDownload() {
+    console.log('下载按钮');
+  }
+  // 点击表格中数字
+  async function handleDetail(record) {
+    if (record) {
+      const params = {
+        statsTime: patrolTime.value,
+        drugType: record.type,
+      };
+      const res = await getDrugStats(params); // 请求药品统计接口
+      detailName.value = formatDictValue(durgTypeOptions.value, record.type);
+      await setDetailTableData(res.data);
+    } else {
+      await setDetailTableData([]);
+    }
+  }
+
+  // 合计行加载方法
+  function handleSummary(tableData: any[]) {
+    console.log(tableData);
+    let totalSum = 0;
+    let totalProportion = 0;
+    tableData.forEach(item => {
+      totalSum += item.sum;
+      totalProportion += parseFloat(parseFloat(item.proportion.split('%')[0]).toFixed(2));
+    });
+    return [
+      {
+        type: '合计',
+        sum: totalSum,
+        proportion: totalProportion + '%',
+      },
+    ];
+  }
+</script>
+
+<style lang="less" scoped>
+  ::v-deep(.xt-title) {
+    padding-left: 40px;
+    background-color: #f6f8fa;
+  }
+
+  .xt-form {
+    justify-content: flex-end;
+    margin-right: 20px;
+  }
+
+  ::v-deep(.ant-row) {
+    max-height: 460px;
+  }
+
+  ::v-deep(.ant-table-body) {
+    height: auto !important;
+  }
+
+  ::v-deep(.ant-table-footer) {
+    padding: 0 !important;
+
+    .ant-table-tbody {
+      background-color: #f6f8fa !important;
+      font-weight: 600 !important;
+      color: #000a18 !important;
+    }
+  }
+</style>

+ 55 - 0
src/views/biz/stats/supplies/suppliesStats/data.ts

@@ -0,0 +1,55 @@
+import { BasicColumn } from '/@/components/Table';
+
+export const typeColumns: BasicColumn[] = [
+  {
+    title: '耗材类型',
+    dataIndex: 'type',
+    align: 'left',
+  },
+  {
+    title: '数量',
+    dataIndex: 'sum',
+    align: 'left',
+  },
+  {
+    title: '占比',
+    dataIndex: 'proportion',
+    align: 'left',
+  },
+];
+
+export const marksColumns: BasicColumn[] = [
+  {
+    title: '耗材厂商',
+    dataIndex: 'type',
+    align: 'left',
+  },
+  {
+    title: '数量',
+    dataIndex: 'sum',
+    align: 'left',
+  },
+  {
+    title: '占比',
+    dataIndex: 'proportion',
+    align: 'left',
+  },
+];
+
+export const detailColumns: BasicColumn[] = [
+  {
+    title: '药品名称',
+    dataIndex: 'type',
+    align: 'left',
+  },
+  {
+    title: '数量',
+    dataIndex: 'sum',
+    align: 'left',
+  },
+  {
+    title: '占比',
+    dataIndex: 'proportion',
+    align: 'left',
+  },
+];

+ 252 - 0
src/views/biz/stats/supplies/suppliesStats/index.vue

@@ -0,0 +1,252 @@
+<template>
+  <div class="flex items-center xt-form">
+    <XTForm :form-data="formData" @change="callForm" />
+    <Button type="default" @click="handleDownload"
+      ><Icon icon="icon-xt-download-download_default|iconfont" :size="14" />
+    </Button>
+  </div>
+  <!-- 耗材类型统计 -->
+  <div class="mb-4">
+    <XTTitle :title="typeTitle" :go-back="false" />
+  </div>
+  <Row style="max-height: 400px">
+    <Col :span="16">
+      <Pie :data="typePieData" />
+    </Col>
+    <Col :span="8">
+      <BasicTable @register="registerTypeTable">
+        <template #bodyCell="{ column, record }">
+          <template v-if="column.key === 'type'">
+            <span> {{ formatDictValue(suppliesTypeOptions, record.type) }}</span>
+          </template>
+        </template>
+      </BasicTable>
+    </Col>
+  </Row>
+  <!-- 耗材厂商统计 -->
+  <div class="mb-4">
+    <XTTitle :title="makerTitle" :go-back="false" />
+  </div>
+  <Row style="max-height: 400px">
+    <Col :span="16">
+      <Pie :data="marksPieData" />
+    </Col>
+    <Col :span="8">
+      <BasicTable @register="registerMarksTable">
+        <template #bodyCell="{ column, record }">
+          <template v-if="column.key === 'sum'">
+            <a @click="handleDetail(record)">{{ record.sum }}</a>
+          </template>
+        </template>
+      </BasicTable>
+    </Col>
+  </Row>
+  <!-- 底部详情统计 -->
+  <Row style="max-height: 200px">
+    <Col :span="24">
+      <BasicTable @register="registerDetailTable">
+        <template #headerCell="{ column }">
+          <template v-if="column.key === 'type'"> {{ detailName }} </template>
+          <template v-if="column.key === 'sum'"> 数量 </template>
+          <template v-if="column.key === 'proportion'"> 占比 </template>
+        </template>
+        <template #bodyCell="{ column, record }">
+          <template v-if="column.key === 'type'">
+            <span> {{ formatDictValue(suppliesTypeOptions, record.type) }}</span>
+          </template>
+        </template>
+      </BasicTable>
+    </Col>
+  </Row>
+</template>
+
+<script setup lang="ts">
+  import { onMounted, ref } from 'vue';
+  import { Row, Col, Button } from 'ant-design-vue';
+  import { XTForm } from '/@/components/XTForm/index';
+  import { XTTitle } from '/@/components/XTTitle/index';
+  import { getSuppliesTypeStats, getSuppliesMarksStats } from '/@/api/biz/stats/suppliesStatsApi';
+  import { BasicTable, useTable } from '@/components/Table';
+  import { typeColumns, marksColumns, detailColumns } from './data';
+  import { Icon } from '/@/components/Icon';
+  import { formatDictValue } from '/@/utils';
+  import { listDictModel } from '/@/api/common';
+  import Pie from '../../chart/pie.vue';
+  // formdata
+  const typeTitle = ref('耗材类型统计'); // 耗材类型统计
+  const makerTitle = ref('耗材厂商统计'); // 耗材厂商统计
+  const typePieData = ref({} as any); // Echart图表组件赋值变量
+  const marksPieData = ref({} as any); // Echart图表组件赋值变量
+  const typeTableData = ref([]); // 药品类型统计数据
+  const marksTableData = ref([]); // 耗材厂商统计数据
+  const detailTableData = ref([]); // 详情统计类型数据
+  const patrolTime = ref([]); // 时间查询参数
+  const detailName = ref('');
+  const formData = [
+    {
+      name: 'patrolTime',
+      componentType: 'RangePicker',
+      placeholder: '请选择维修时间',
+      format: 'YYYY-MM-DD',
+      valueFormat: 'YYYY-MM-DD',
+    },
+    {},
+  ];
+  // 耗材类型统计表格配置
+  const [registerTypeTable, { setTableData: setTypeTable }] = useTable({
+    showIndexColumn: false,
+    bordered: true,
+    striped: false,
+    showSummary: true,
+    summaryFunc: handleSummary,
+    pagination: false,
+    maxHeight: 200,
+    dataSource: typeTableData.value,
+    columns: typeColumns,
+  });
+
+  // 耗材厂商统计表格配置
+  const [registerMarksTable, { setTableData: setMarksTable }] = useTable({
+    showIndexColumn: false,
+    bordered: true,
+    showSummary: true,
+    summaryFunc: handleSummary,
+    striped: false,
+    pagination: false,
+    maxHeight: 200,
+    dataSource: marksTableData.value,
+    columns: marksColumns,
+  });
+
+  // 药品类型统计表格配置
+  const [registerDetailTable, { setTableData: setDetailTableData }] = useTable({
+    showIndexColumn: false,
+    bordered: true,
+    striped: false,
+    pagination: false,
+    maxHeight: 200,
+    dataSource: detailTableData.value,
+    columns: detailColumns,
+  });
+
+  const suppliesTypeOptions = ref();
+  onMounted(async () => {
+    suppliesTypeOptions.value = await listDictModel({ dictCode: 'ct' }); // 查询药品类型字典数据
+    getAllDatas(); // 执行获取统计数据方法
+  });
+
+  // 查询组件回调
+  async function callForm(data) {
+    patrolTime.value = data.patrolTime || [];
+    getAllDatas();
+  }
+  // 获取统计数据
+  async function getAllDatas() {
+    const params = {
+      statsTime: patrolTime.value,
+    };
+
+    // 获取设置耗材类型数据
+    const typeRes = await getSuppliesTypeStats(params); // 请求药品统计接口
+    typeTableData.value = typeRes.data;
+    await setTypeTable(typeRes.data); // 右侧统计数据表格赋值
+    // 组合Echart图表数据格式
+    const typeDataList = {
+      content: [],
+      description: typeTitle.value,
+    };
+    typeRes.data.forEach(item => {
+      typeDataList.content.push({
+        name: formatDictValue(suppliesTypeOptions.value, item.type),
+        value: item.sum,
+      });
+    });
+    typePieData.value = typeDataList; // Echart图表赋值
+
+    // 获取设置耗材厂商数据
+    const marksRes = await getSuppliesMarksStats(params); // 请求药品统计接口
+    marksTableData.value = marksRes.data;
+    await setMarksTable(marksRes.data); // 右侧统计数据表格赋值
+    // 组合Echart图表数据格式
+    const marksDataList = {
+      content: [],
+      description: typeTitle.value,
+    };
+    marksRes.data.forEach(item => {
+      marksDataList.content.push({
+        name: item.type,
+        value: item.sum,
+      });
+    });
+    marksPieData.value = marksDataList; // Echart图表赋值
+
+    // 默认耗材类型第一个为详情数据父级
+    handleDetail(marksTableData.value[0]);
+  }
+  // 统计下载事件
+  function handleDownload() {
+    console.log('下载按钮');
+  }
+
+  // 点击表格中数字
+  async function handleDetail(record) {
+    if (record) {
+      const params = {
+        statsTime: patrolTime.value,
+        makers: record.type,
+      };
+      const res = await getSuppliesTypeStats(params); // 请求药品统计接口
+      detailName.value = record.type;
+      await setDetailTableData(res.data);
+    } else {
+      await setDetailTableData([]);
+    }
+  }
+  // 合计行加载方法
+  function handleSummary(tableData: any[]) {
+    console.log(tableData);
+    let totalSum = 0;
+    let totalProportion = 0;
+    tableData.forEach(item => {
+      totalSum += item.sum;
+      totalProportion += parseFloat(parseFloat(item.proportion.split('%')[0]).toFixed(2));
+    });
+    return [
+      {
+        type: '合计',
+        sum: totalSum,
+        proportion: totalProportion + '%',
+      },
+    ];
+  }
+</script>
+
+<style lang="less" scoped>
+  ::v-deep(.xt-title) {
+    padding-left: 40px;
+    background-color: #f6f8fa;
+  }
+
+  .xt-form {
+    justify-content: flex-end;
+    margin-right: 20px;
+  }
+
+  ::v-deep(.ant-row) {
+    max-height: 460px;
+  }
+
+  ::v-deep(.ant-table-body) {
+    height: auto !important;
+  }
+
+  ::v-deep(.ant-table-footer) {
+    padding: 0 !important;
+
+    .ant-table-tbody {
+      background-color: #f6f8fa !important;
+      font-weight: 600 !important;
+      color: #000a18 !important;
+    }
+  }
+</style>

+ 12 - 11
src/views/biz/visit/check/data.ts

@@ -12,34 +12,35 @@ export const BasicTab = [
   },
   {
     key: '1',
-    label: '透中患者',
-    value: 12,
+    label: '关注',
+    value: 18,
     hasValue: true,
-    prefixColor: '#1BC1B3',
+    prefixColor: '#33CCFF',
     hasBracket: true,
   },
   {
     key: '2',
-    label: '非透中患者',
+    label: '患者',
     value: 18,
     hasValue: true,
-    prefixColor: '#D3D8DD',
+    prefixColor: '#F7B500',
     hasBracket: true,
   },
   {
     key: '3',
-    label: '患者',
+    label: '透中患者',
     value: 18,
     hasValue: true,
-    prefixColor: '#F7B500',
+    prefixColor: '#D3D8DD',
     hasBracket: true,
   },
+
   {
     key: '4',
-    label: '关注',
-    value: 18,
+    label: '非透中患者',
+    value: 12,
     hasValue: true,
-    prefixColor: '#33CCFF',
+    prefixColor: '#1BC1B3',
     hasBracket: true,
   },
 ];
@@ -64,7 +65,7 @@ export const checkColumns: BasicColumn[] = [
   },
   {
     title: '记录人',
-    dataIndex: 'recorder',
+    dataIndex: 'updatorName',
   },
 ];
 export const columns: BasicColumn[] = [

+ 76 - 59
src/views/biz/visit/check/index.vue

@@ -12,11 +12,22 @@
         />
         <XTForm :form-data="formData" @change="callFormChange" @click="callFormClick" />
       </div>
+      <div class="flex mb-2" v-if="siftData.length">
+        <Sift :data="siftData" />
+      </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>
+            <span :title="record.name">{{ record.name }}</span>
+          </template>
+          <template v-if="column.dataIndex === 'updatorName'">
+            <Image
+              v-if="record.updateAvatar"
+              :src="record.updateAvatar"
+              style="width: 20; height: 20px"
+            />
+            {{ record.updatorName }}
           </template>
           <template v-if="column.key === 'gender'">
             <Icon
@@ -42,7 +53,6 @@
                   tooltip: '查房记录',
                   label: '查房记录',
                   onClick: handleDetail.bind(null, record),
-                  // ifShow: !record.master,
                 },
                 {
                   auth: 'archives:patrolWard:edit',
@@ -51,10 +61,10 @@
                   onClick: handleEdit.bind(null, record),
                 },
                 {
-                  auth: 'storage:config:remove',
+                  auth: 'archives:patrolWard:followOrNo',
                   tooltip: '关注',
                   label: '关注',
-                  color: 'error',
+                  ifShow: record.status > 1,
                   popConfirm: {
                     title: '是否确定要关注?',
                     placement: 'left',
@@ -62,21 +72,21 @@
                   },
                 },
                 {
-                  auth: 'storage:config:remove',
+                  auth: 'archives:patrolWard:followOrNo',
                   tooltip: '取消关注',
                   label: '取消关注',
-                  color: 'error',
+                  ifShow: record.status == 1,
                   popConfirm: {
                     title: '是否确定要取消关注?',
                     placement: 'left',
-                    confirm: handleCancel.bind(null, record),
+                    confirm: handleClick.bind(null, record),
                   },
                 },
               ]"
               :actions="[
                 {
                   auth: 'archives:patrolWard:query',
-                  icon: 'icon-eye|iconfont',
+                  icon: 'icon-xt-ward_default|iconfont',
                   tooltip: '查房',
                   onClick: handleView.bind(null, record),
                 },
@@ -88,7 +98,6 @@
     </div>
     <FormModal @register="registerModal" @success="handleSuccess" />
     <ViewDrawer @register="registerDrawerView" @success="handleSuccess" />
-    <!-- <DatailModal @register="registerModalView" /> -->
   </div>
 </template>
 
@@ -97,11 +106,11 @@
   import { XTTab } from '/@/components/XTTab/index';
   import { XTForm } from '/@/components/XTForm/index';
   import { Icon } from '/@/components/Icon';
+  import { Image } from 'ant-design-vue';
   import { BasicTable, useTable, TableAction } from '/@/components/TableCard';
-  import { BasicTab, BasicTabActive, checkColumns } from './data';
+  import { checkColumns, BasicTabActive, BasicTab } from './data';
   import { ref } from 'vue';
   import { useRouter } from 'vue-router';
-
   import {
     checkQueryCheckRoomRecord,
     checkQueryPersonNumber,
@@ -114,9 +123,9 @@
   import FormModal from './formModal.vue';
   import ViewDrawer from './viewDrawer.vue';
   import { useDrawer } from '@/components/Drawer';
-
   import { useMessage } from '/@/hooks/web/useMessage';
 
+  // const bizDictOptions = reactive<any>({});
   //路由跳转
 
   const router = useRouter();
@@ -124,31 +133,11 @@
   const tabData = ref(BasicTab);
   const [registerModal, { openModal }] = useModal();
   const [registerDrawerView, { openDrawer, openDrawer: openDrawerView }] = useDrawer();
-
   onMounted(async () => {
-    const stats = await checkQueryPersonNumber();
-    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.dialysisPatients;
-      }
-      if (ele.key == '2') {
-        ele.value = stats.noDialysisPatients;
-      }
-      if (ele.key == '3') {
-        ele.value = stats.newPatient;
-      }
-      if (ele.key == '4') {
-        ele.value = stats.attention;
-      }
-      return ele;
-    });
+    getTab();
     console.log('🚀 ~ file: index.vue:118 ~ onMounted ~ tabData.value:', tabData.value);
   });
-  const [registerTable, { reload }] = useTable({
+  const [registerTable, { reload, clearSelectedRowKeys }] = useTable({
     api: checkQueryCheckRoomRecord,
     // exportAuthList: ['sys:log:export'],
     rowKey: 'id',
@@ -174,10 +163,10 @@
       componentType: 'Select',
       placeholder: '请选择',
       width: 120,
-      defaultValue: 'patrolTime',
+      defaultValue: 'st_patrol_time',
       dicts: [
-        { label: '按查房时间', value: 'patrolTime' },
-        { label: '按姓氏', value: 'patientNamePinyin' },
+        { label: '按查房时间', value: 'st_patrol_time' },
+        { label: '按姓氏', value: 'st_family_name' },
       ],
     },
     {
@@ -191,25 +180,41 @@
 
   const formValue = reactive({
     name: '',
+    tableSort: 'st_patrol_time',
   }) as any;
 
+  async function getTab() {
+    const personNumber = await checkQueryPersonNumber();
+    console.log('🚀 ~ file: index.vue:104 ~ onMounted ~ stats:', personNumber);
+    tabData.value = tabData.value.map(ele => {
+      if (ele.key == '0') {
+        ele.value = personNumber.all;
+      }
+      if (ele.key == '1') {
+        ele.value = personNumber.attention;
+      }
+      if (ele.key == '2') {
+        ele.value = personNumber.newPatient;
+      }
+      if (ele.key == '3') {
+        ele.value = personNumber.dialysisPatients;
+      }
+      if (ele.key == '4') {
+        ele.value = personNumber.noDialysisPatients;
+      }
+      return ele;
+    });
+  }
   // 表格请求之前,对参数进行处理, 添加默认 排序
   function handleBeforeFetch(params) {
-    // return { ...params, orders: tableSort.value };
-    const sift = {};
-    siftData.value.forEach(ele => {
-      sift[ele.field] = ele.isDict ? ele.dict : ele.value;
-    });
     return {
       ...params,
-      queryType: activeKey.value == '0' ? '0' : activeKey.value,
+      patientType: activeKey.value == '0' ? '0' : activeKey.value,
       name: formValue.name,
-      orders: [{ field: 'patrolTime', direction: 'DESC' }],
-      sortType: 'st_patrol_time',
-      ...sift,
+      orders: [],
+      sortType: formValue.tableSort,
     };
   }
-
   function handleAfterFetch(data) {
     // data.forEach(item => {
     //   item.status = 2;
@@ -235,12 +240,11 @@
         name: record.name,
         gender: record.gender,
         age: record.age,
+        activeKeys: 5,
       },
     });
   }
 
-  const { createMessage } = useMessage();
-
   // 编辑按钮事件
   function handleEdit(record: Recordable) {
     openModal(true, {
@@ -250,23 +254,33 @@
   }
 
   // 取消按钮事件
-  async function handleCancel(record) {
-    // clearSelectedRowKeys();
-    await checkFollowOrNo(record.id);
-    createMessage.success('操作成功!');
+  const { createMessage } = useMessage();
+  async function handleCancel(record: Recordable) {
+    await checkFollowOrNo(record.patientBasicId);
+    createMessage.success('关注成功!');
+    clearSelectedRowKeys();
+    await getTab();
+    await reload();
+  }
+  async function handleClick(record: Recordable) {
+    await checkFollowOrNo(record.patientBasicId);
+    createMessage.success('取消关注成功!');
+    clearSelectedRowKeys();
+    await getTab();
     await reload();
-    // await getTab();
   }
 
   // 弹窗回调事件
   async function handleSuccess({ isUpdate, values }) {
     console.log(isUpdate);
     console.log(values);
+    await getTab();
     await reload();
   }
   // 回调
   async function callTab(data) {
     activeKey.value = data.value;
+    await getTab();
     await reload();
   }
 
@@ -281,6 +295,9 @@
 
   async function callFormChange(data) {
     formValue.name = data.name ? data.name : '';
+    //   formValue.patientName = data.patientName ? data.patientName : '';
+    formValue.tableSort = data.tableSort ? data.tableSort : '';
+    await getTab();
     await reload();
   }
 
@@ -312,20 +329,20 @@
     margin-right: 6px;
     border-radius: 50%;
 
-    &hb--1 {
-      background-color: #1bc1b3;
+    &--1 {
+      background-color: #3cf;
     }
 
     &--2 {
-      background-color: #d3d8dd;
+      background-color: #f7b500;
     }
 
     &--3 {
-      background-color: #f7b500;
+      background-color: #d3d8dd;
     }
 
     &--4 {
-      background-color: #3cf;
+      background-color: #1bc1b3;
     }
   }
 

Algunos archivos no se mostraron porque demasiados archivos cambiaron en este cambio