tangning пре 2 година
родитељ
комит
acad1be033
71 измењених фајлова са 8218 додато и 146 уклоњено
  1. 2 2
      .env.development
  2. 8 8
      src/api/account/index.ts
  3. 6 6
      src/api/customer/index.ts
  4. 1 1
      src/api/demo/error.ts
  5. 5 5
      src/api/device/index.ts
  6. 1 1
      src/api/equity/index.ts
  7. 211 0
      src/api/home/index.ts
  8. 36 0
      src/api/home/model.ts
  9. 35 35
      src/api/operate/index.ts
  10. 11 11
      src/api/order/index.ts
  11. 17 17
      src/api/product/index.ts
  12. 286 0
      src/api/spares/index.ts
  13. 202 0
      src/api/spares/model.ts
  14. 7 7
      src/api/staff/list.ts
  15. 13 13
      src/api/statistics/index.ts
  16. 1 1
      src/api/sys/menu.ts
  17. 13 13
      src/api/sys/system.ts
  18. 3 3
      src/api/sys/user.ts
  19. 0 1
      src/components/Form/src/components/FormItem.vue
  20. 1 1
      src/enums/cacheEnum.ts
  21. 1 1
      src/enums/httpEnum.ts
  22. 1 1
      src/enums/pageEnum.ts
  23. 2 2
      src/layouts/default/header/components/user-dropdown/index.vue
  24. 0 2
      src/layouts/default/menu/index.vue
  25. 22 0
      src/locales/lang/zh-CN/routes/spares.ts
  26. 18 0
      src/locales/lang/zh-CN/routes/staff.ts
  27. 29 0
      src/router/routes/modules/home.ts
  28. 29 0
      src/router/routes/modules/spares.ts
  29. 75 0
      src/router/routes/modules/work.ts
  30. 1 1
      src/settings/projectSetting.ts
  31. 2 0
      src/store/modules/permission.ts
  32. 17 14
      src/store/modules/user.ts
  33. 123 0
      src/views/home/condition.vue
  34. 229 0
      src/views/home/faultType.vue
  35. 72 0
      src/views/home/index.vue
  36. 148 0
      src/views/home/lineEcharts.vue
  37. 129 0
      src/views/home/orderEchart.vue
  38. 164 0
      src/views/home/pieEchart.vue
  39. 56 0
      src/views/home/props.ts
  40. 153 0
      src/views/invoice/EditModal.vue
  41. 199 0
      src/views/invoice/InvoiceModal.vue
  42. 233 0
      src/views/invoice/data.tsx
  43. 171 0
      src/views/invoice/index.vue
  44. 101 0
      src/views/spares/RoleDrawer.vue
  45. 254 0
      src/views/spares/detail.vue
  46. 246 0
      src/views/spares/index.vue
  47. 134 0
      src/views/spares/list.vue
  48. 206 0
      src/views/spares/orderEntryModal.vue
  49. 102 0
      src/views/spares/recoveryModal.vue
  50. 129 0
      src/views/spares/remarksModal.vue
  51. 199 0
      src/views/spares/role.data.ts
  52. 150 0
      src/views/spares/spareInModal.vue
  53. 163 0
      src/views/spares/spareModal.vue
  54. 241 0
      src/views/staff/adddetailsModal.vue
  55. 5 0
      src/views/staff/category.vue
  56. 78 0
      src/views/staff/clean.vue
  57. 124 0
      src/views/staff/delListModal.vue
  58. 180 0
      src/views/staff/detailsModal.vue
  59. 349 0
      src/views/staff/list.vue
  60. 125 0
      src/views/staff/setpaswordModal.vue
  61. 5 0
      src/views/staff/sorts.vue
  62. 297 0
      src/views/work/addAccessoryModel.vue
  63. 211 0
      src/views/work/aftermarket.vue
  64. 294 0
      src/views/work/checkModel.vue
  65. 295 0
      src/views/work/followedList.vue
  66. 253 0
      src/views/work/maintenance.vue
  67. 295 0
      src/views/work/outModal.vue
  68. 238 0
      src/views/work/query.vue
  69. 214 0
      src/views/work/quoteModel.vue
  70. 368 0
      src/views/work/recoveryModal.vue
  71. 229 0
      src/views/work/takingOrdersModel.vue

+ 2 - 2
.env.development

@@ -7,13 +7,13 @@ VITE_PUBLIC_PATH = ./
 # Cross-domain proxy, you can configure multiple
 # Please note that no line breaks
 # VITE_PROXY = [["/basic-api","http://localhost:3000"],["/upload","https://testeur.4dkankan.com/service/manage/common/upload/files"],["/service","https://testeur.4dkankan.com"]]
-VITE_PROXY = [["/basic-api","http://localhost:3000"],["/upload","https://v4-uat.4dkankan.com/service/manage/common/upload/files"],["/service","https://v4-uat.4dkankan.com"]]
+VITE_PROXY = [["/basic-api","http://localhost:3000"],["/upload","https://v4-uat.4dkankan.com/service/manage/common/upload/files"],["/service","http://192.168.0.38:8188/service"]]
 
 # Delete console
 VITE_DROP_CONSOLE = false
 
 # Basic interface address SPA
-VITE_GLOB_API_URL=/service
+VITE_GLOB_API_URL=
 
 # File upload address, optional
 VITE_GLOB_UPLOAD_URL=/upload

+ 8 - 8
src/api/account/index.ts

@@ -6,14 +6,14 @@ import {
 import { Result } from '/#/axios';
 
 enum Api {
-  pageList = '/service/manage/user/list',
-  getIncrementListByUserId = '/service/manage/user/getIncrementListByUserId',
-  IncrementDelay = '/service/manage/user/incrementDelay',
-  addUserIncrement = '/service/manage/user/addUserIncrement',
-  addDownNum = '/service/manage/user/addDownNum',
-  getCameraDetail = '/service/manage/user/getCameraDetail',
-  unbindCamera = '/service/manage/user/unbindCamera',
-  allList = '/service/manage/incrementType/allList',
+  pageList = '/service/sale/user/list',
+  getIncrementListByUserId = '/service/sale/user/getIncrementListByUserId',
+  IncrementDelay = '/service/sale/user/incrementDelay',
+  addUserIncrement = '/service/sale/user/addUserIncrement',
+  addDownNum = '/service/sale/user/addDownNum',
+  getCameraDetail = '/service/sale/user/getCameraDetail',
+  unbindCamera = '/service/sale/user/unbindCamera',
+  allList = '/service/sale/incrementType/allList',
 }
 
 /**

+ 6 - 6
src/api/customer/index.ts

@@ -3,13 +3,13 @@ import { PageParams, deleteParams, addParams, cameraListParams, companyExcelPara
 import { Result, FileStream, UploadFileParams } from '/#/axios';
 
 enum Api {
-  companyList = '/service/manage/company/list',
-  companyAdd = '/service/manage/company/add',
-  companyUpdate = '/service/manage/company/update',
-  companyDelete = '/service/manage/company/delete',
+  companyList = '/service/sale/company/list',
+  companyAdd = '/service/sale/company/add',
+  companyUpdate = '/service/sale/company/update',
+  companyDelete = '/service/sale/company/delete',
   cameraList = '/service/agent/camera/list',
-  companyUploadExcel = '/service/manage/excel/uploadExcel',
-  downTemplate = '/service/manage/excel/downTemplate',
+  companyUploadExcel = '/service/sale/excel/uploadExcel',
+  downTemplate = '/service/sale/excel/downTemplate',
 }
 
 export const companyList = (params: PageParams) =>

+ 1 - 1
src/api/demo/error.ts

@@ -2,7 +2,7 @@ import { defHttp } from '/@/utils/http/axios';
 
 enum Api {
   // The address does not exist
-  Error = '/service/manage/operLog/pageOperLog',
+  Error = '/service/sale/operLog/pageOperLog',
 }
 
 /**

+ 5 - 5
src/api/device/index.ts

@@ -3,11 +3,11 @@ import { PageParams} from './model';
 import { Result } from '/#/axios';
 
 enum Api {
-  getParam = '/service/manage/camera/getParam',
-  getUpdate = '/service/manage/camera/update',
-  getOut = '/service/manage/camera/out',
-  getDelete = '/service/manage/camera/delete',
-  getIn = '/service/manage/camera/in',
+  getParam = '/service/sale/camera/getParam',
+  getUpdate = '/service/sale/camera/update',
+  getOut = '/service/sale/camera/out',
+  getDelete = '/service/sale/camera/delete',
+  getIn = '/service/sale/camera/in',
 }
 
 export const cameraParam = (params: PageParams) =>

+ 1 - 1
src/api/equity/index.ts

@@ -10,7 +10,7 @@ enum Api {
   allList = '/service/agent/incrementType/allList',
   addDowm = '/service/agent/down/add',
   dowmList = '/service/agent/down/list',
-  cameraIncrementLog = '/service/agent/cameraIncrementLog/list'
+  cameraIncrementLog = '/service/sale/operLog/pageOperLog'
 }
 
 export const listApi = (params:listParams) =>

+ 211 - 0
src/api/home/index.ts

@@ -0,0 +1,211 @@
+import { defHttp } from '/@/utils/http/axios';
+import { PageParams, ListGetResultModel, DelParams, roleParams, companyExcelParams } from './model';
+import { Result, FileStream } from '/#/axios';
+let qjurl = window.location.host == 'v4-uat.4dkankan.com'?'https://test.4dkankan.com':''
+enum Api {
+  buryPointList = '/service/sale/buryPoint/list',
+  buryPointAdd = '/service/sale/buryPoint/add',
+  buryPointDlt = '/service/sale/buryPoint/delete',
+  userTotal = '/service/sale/data/userTotal',
+  orderTotal = '/service/sale/data/orderTotal',
+  userTrend = '/service/sale/data/userTrend',
+  orderTrend = '/service/sale/data/orderTrend',
+  cameraTrend = '/service/sale/data/cameraTrend',
+  sceneTrend = '/service/sale/data/sceneTrend',
+  sceneTotal = '/service/sale/data/sceneTotal',
+  cameraExport = '/service/sale/order/camera/export',
+  downExport = '/service/sale/order/down/export',
+  incrementExport = '/service/sale/order/increment/export',
+  workType = '/qjkankan/api/age/report/work',
+  workTrend = '/qjkankan/api/age/report/workTrend',
+  qjuserTrend = '/qjkankan/api/age/report/userTrend',
+  volumeTrend = '/qjkankan/api/age/report/volumeTrend',
+}
+
+/**
+ * @description: Get sample list value
+ */
+
+export const buryPointList = (params: PageParams) =>
+  defHttp.post<Result>({
+    url: Api.buryPointList,
+    params,
+    data: params,
+    headers: {
+      // @ts-ignore
+      ignoreCancelToken: true,
+    },
+  });
+
+  export const buryPointAdd = (params: PageParams) =>
+  defHttp.post<Result>({
+    url: Api.buryPointAdd,
+    params,
+    data: params,
+    headers: {
+      // @ts-ignore
+      ignoreCancelToken: true,
+    },
+  });
+  export const buryPointDlt = (params: PageParams) =>
+  defHttp.post<Result>({
+    url: Api.buryPointDlt,
+    params,
+    data: params,
+    headers: {
+      // @ts-ignore
+      ignoreCancelToken: true,
+    },
+  });
+  export const userTotal = () =>
+  defHttp.get<Result>({
+    url: Api.userTotal,
+    headers: {
+      // @ts-ignore
+      ignoreCancelToken: true,
+    },
+  });
+  export const userTrend = (params) =>
+  defHttp.get<Result>({
+    url: Api.userTrend,
+    params,
+    data: params,
+    headers: {
+      // @ts-ignore
+      ignoreCancelToken: true,
+    },
+  });
+  export const orderTotal = () =>
+  defHttp.get<Result>({
+    url: Api.orderTotal,
+    headers: {
+      // @ts-ignore
+      ignoreCancelToken: true,
+    },
+  });
+  
+  export const orderTrend = (params) =>
+  defHttp.get<Result>({
+    url: Api.orderTrend,
+    params,
+    data: params,
+    headers: {
+      // @ts-ignore
+      ignoreCancelToken: true,
+    },
+  });
+  
+  export const cameraTrend = (params) =>
+  defHttp.get<Result>({
+    url: Api.cameraTrend,
+    params,
+    data: params,
+    headers: {
+      // @ts-ignore
+      ignoreCancelToken: true,
+    },
+  });
+  
+  export const sceneTotal = () =>
+  defHttp.get<Result>({
+    url: Api.sceneTotal,
+    headers: {
+      // @ts-ignore
+      ignoreCancelToken: true,
+    },
+  });
+  
+  export const sceneTrend = (params) =>
+  defHttp.get<Result>({
+    url: Api.sceneTrend,
+    params,
+    data: params,
+    headers: {
+      // @ts-ignore
+      ignoreCancelToken: true,
+    },
+  });
+  
+  export const workType = (params) =>
+  defHttp.get<Result>({
+    url: qjurl + Api.workType,
+    params,
+    data: params,
+    headers: {
+      // @ts-ignore
+      appId: 'BDA385EC848C1A425F746869011C8D23',
+      ignoreCancelToken: true,
+    },
+  });
+    
+  export const workTrend = (params) =>
+  defHttp.post<Result>({
+    url: qjurl + Api.workTrend,
+    params,
+    data: params,
+    headers: {
+      // @ts-ignore
+      appId: 'BDA385EC848C1A425F746869011C8D23',
+      ignoreCancelToken: true,
+    },
+  });    
+  export const qjuserTrend = (params) =>
+  defHttp.post<Result>({
+    url: qjurl + Api.qjuserTrend,
+    params,
+    data: params,
+    headers: {
+      // @ts-ignore
+      appId: 'BDA385EC848C1A425F746869011C8D23',
+      ignoreCancelToken: true,
+    },
+  });    
+  export const volumeTrend = (params) =>
+  defHttp.post<Result>({
+    url: qjurl + Api.volumeTrend,
+    params,
+    data: params,
+    headers: {
+      // @ts-ignore
+      appId: 'BDA385EC848C1A425F746869011C8D23',
+      ignoreCancelToken: true,
+    },
+  });
+  export const cameraExport = (params) =>
+  defHttp.downloadFile<FileStream>({
+    url: Api.cameraExport,
+    params: params,
+    // data: params,
+    fileName:'相机订单.xlsx',
+    headers: {
+      // @ts-ignore
+      ignoreCancelToken: true,
+    },
+    responseType: 'blob'
+  });
+
+  export const downExport = (params) =>
+  defHttp.downloadFile<FileStream>({
+    url: Api.downExport,
+    params: params,
+    // data: params,
+    fileName:'下载订单.xlsx',
+    headers: {
+      // @ts-ignore
+      ignoreCancelToken: true,
+    },
+    responseType: 'blob'
+  });
+
+  export const incrementExport = (params) =>
+  defHttp.downloadFile<FileStream>({
+    url: Api.incrementExport,
+    params: params,
+    fileName:'权益订单.xlsx',
+    // data: params,
+    headers: {
+      // @ts-ignore
+      ignoreCancelToken: true,
+    },
+    responseType: 'blob'
+  });

+ 36 - 0
src/api/home/model.ts

@@ -0,0 +1,36 @@
+import { BasicPageParams, BasicFetchResult } from '/@/api/model/baseModel';
+/**
+ * @description: Request list interface parameters
+ */
+export type PageParams = BasicPageParams;
+
+export interface DelParams {
+  userId: number;
+  toUserId: number;
+  toUserPhone: string;
+}
+
+export interface roleParams {
+  type: number;
+  roleId: number;
+}
+export interface StaffListItem {
+  id: number;
+
+  name: string;
+  company: string;
+  phone: string;
+  status: string;
+  role: string;
+  createTime: string;
+}
+
+export interface companyExcelParams {
+  file?: File | Blob;
+  type?: Number;
+}
+
+/**
+ * @description: Request list return value
+ */
+export type ListGetResultModel = BasicFetchResult<StaffListItem>;

+ 35 - 35
src/api/operate/index.ts

@@ -15,47 +15,47 @@ import {
 import { Result } from '/#/axios';
 
 enum Api {
-  pageList = '/service/manage/news/pageNews',
-  newDisplay = '/service/manage/news/display',
-  newPutTop = '/service/manage/news/putTop',
-  newUpdateNews = '/service/manage/news/updateNews',
-  newAddNews = '/service/manage/news/addNews',
-  newDelete = '/service/manage/news/delete',
-  publicNews = '/service/manage/news/publicNews',
-  newGetNewsDetail = '/service/manage/news/getNewsDetail',
+  pageList = '/service/sale/news/pageNews',
+  newDisplay = '/service/sale/news/display',
+  newPutTop = '/service/sale/news/putTop',
+  newUpdateNews = '/service/sale/news/updateNews',
+  newAddNews = '/service/sale/news/addNews',
+  newDelete = '/service/sale/news/delete',
+  publicNews = '/service/sale/news/publicNews',
+  newGetNewsDetail = '/service/sale/news/getNewsDetail',
   //招聘
-  employNoteList = '/service/manage/employNote/list',
-  addOrUpdate = '/service/manage/employNote/addOrUpdate',
-  employNoteDelete = '/service/manage/employNote/delete',
-  employNoteIsPush = '/service/manage/employNote/isPush',
-  employNoteIsTop = '/service/manage/employNote/isTop',
+  employNoteList = '/service/sale/employNote/list',
+  addOrUpdate = '/service/sale/employNote/addOrUpdate',
+  employNoteDelete = '/service/sale/employNote/delete',
+  employNoteIsPush = '/service/sale/employNote/isPush',
+  employNoteIsTop = '/service/sale/employNote/isTop',
   //留言
-  intercomMessageList = '/service/manage/intercomMessage/list',
-  intercomMessageHandle = '/service/manage/intercomMessage/handle',
+  intercomMessageList = '/service/sale/intercomMessage/list',
+  intercomMessageHandle = '/service/sale/intercomMessage/handle',
 
   operateSceneList = '/service/agent/scene/list',
   sceneMove = '/service/agent/scene/move',
-  sceneDelete = '/service/manage/scene/delete',
-  qjsceneDelete = '/service/manage/overall/delete',
-  sceneDownload = '/service/manage/scene/downScene',
+  sceneDelete = '/service/sale/scene/delete',
+  qjsceneDelete = '/service/sale/overall/delete',
+  sceneDownload = '/service/sale/scene/downScene',
   sceneCopy = '/service/agent/scene/copy',
-  checkDownLoad = '/service/manage/scene/checkDownLoad',
-  downloadProcess = '/service/manage/scene/downloadProcess',
-  rebuildScene = '/service/manage/scene/rebuildScene',
-  overallList = '/service/manage/overall/list',
-  overallDelete = '/service/manage/overall/delete',
-  agentAuditList = '/service/manage/agentAudit/list',
-  agentAuditHandle = '/service/manage/agentAudit/handle',
-  sceneApplyList = '/service/manage/sceneApply/list',
-  sendEmail = '/service/manage/sceneApply/sendEmail',
-  sceneApplyHandle = '/service/manage/sceneApply/handle',
-  getInfoMail = '/service/manage/getInfo',
-  updateMail = '/service/manage/update',
-  caseaddOrUpdate='/service/manage/case/addOrUpdate',
-  caseList = '/service/manage/case/list',
-  caseDel='/service/manage/case/del',
-  caseRelease='/service/manage/case/isShow',
-  caseWithdraw='/service/manage/case/release',
+  checkDownLoad = '/service/sale/scene/checkDownLoad',
+  downloadProcess = '/service/sale/scene/downloadProcess',
+  rebuildScene = '/service/sale/scene/rebuildScene',
+  overallList = '/service/sale/overall/list',
+  overallDelete = '/service/sale/overall/delete',
+  agentAuditList = '/service/sale/agentAudit/list',
+  agentAuditHandle = '/service/sale/agentAudit/handle',
+  sceneApplyList = '/service/sale/sceneApply/list',
+  sendEmail = '/service/sale/sceneApply/sendEmail',
+  sceneApplyHandle = '/service/sale/sceneApply/handle',
+  getInfoMail = '/service/sale/getInfo',
+  updateMail = '/service/sale/update',
+  caseaddOrUpdate='/service/sale/case/addOrUpdate',
+  caseList = '/service/sale/case/list',
+  caseDel='/service/sale/case/del',
+  caseRelease='/service/sale/case/isShow',
+  caseWithdraw='/service/sale/case/release',
 }
 
 /**

+ 11 - 11
src/api/order/index.ts

@@ -10,17 +10,17 @@ import {
 import { Result, UploadFileParams, FileStream } from '/#/axios';
 
 enum Api {
-  cameraList = '/service/manage/order/camera/list',
-  cameraExport = '/service/manage/order/camera/export',
-  cameraItem = '/service/manage/order/camera/item',
-  incrementList = '/service/manage/order/increment/list',
-  incrementExport = '/service/manage/order/increment/export',
-  downList = '/service/manage/order/down/list',
-  downExport = '/service/manage/order/down/export',
-  pageInvoice = '/service/agent/increment/list',
-  exportInvoice = '/service/manage/invoice/export',
-  invoiceRegister = '/service/manage/invoice/invoiceRegister',
-  getInvoiceRegisterDetail = '/service/manage/invoice/getInvoiceRegisterDetail',
+  cameraList = '/service/sale/order/camera/list',
+  cameraExport = '/service/sale/order/camera/export',
+  cameraItem = '/service/sale/order/camera/item',
+  incrementList = '/service/sale/order/increment/list',
+  incrementExport = '/service/sale/order/increment/export',
+  downList = '/service/sale/order/down/list',
+  downExport = '/service/sale/order/down/export',
+  pageInvoice = '/service/sale/increment/list',
+  exportInvoice = '/service/sale/invoice/export',
+  invoiceRegister = '/service/sale/invoice/invoiceRegister',
+  getInvoiceRegisterDetail = '/service/sale/invoice/getInvoiceRegisterDetail',
 }
 
 /**

+ 17 - 17
src/api/product/index.ts

@@ -4,23 +4,23 @@ import { Result,UploadFileParams } from '/#/axios';
 import { ContentTypeEnum } from '/@/enums/httpEnum';
 
 enum Api {
-  cameraVersionList = '/service/manage/cameraVersion/list',
-  upload = '/service/manage/common/upload/files',
-  spaceSdkList = '/service/manage/spaceSdk/list',
-  spaceSdkUpdate = '/service/manage/spaceSdk/update',
-  spaceSdkOnline = '/service/manage/spaceSdk/online',
-  spaceSdkTop = '/service/manage/spaceSdk/top',
-  spaceSdkDel = '/service/manage/spaceSdk/delete',
-  addAndUpload = '/service/manage/cameraVersion/addAndUpload',
-  editAndUpload = '/service/manage/cameraVersion/update',
-  delAndUpload = '/service/manage/cameraVersion/delete',
-  appFileAgentList = '/service/manage/appFile/agentList',
-  spaceSdkUpload = '/service/manage/spaceSdk/upload',
-  appFileAdd = '/service/manage/appFile/add',
-  appFileUpload = '/service/manage/appFile/upload',
-  spaceSdkDelete = '/service/manage/spaceSdk/delete',
-  appFileList = '/service/manage/appFile/list',
-  appFileDelete = '/service/manage/appFile/delete'
+  cameraVersionList = '/service/sale/cameraVersion/list',
+  upload = '/service/sale/upload/file',
+  spaceSdkList = '/service/sale/spaceSdk/list',
+  spaceSdkUpdate = '/service/sale/spaceSdk/update',
+  spaceSdkOnline = '/service/sale/spaceSdk/online',
+  spaceSdkTop = '/service/sale/spaceSdk/top',
+  spaceSdkDel = '/service/sale/spaceSdk/delete',
+  addAndUpload = '/service/sale/cameraVersion/addAndUpload',
+  editAndUpload = '/service/sale/cameraVersion/update',
+  delAndUpload = '/service/sale/cameraVersion/delete',
+  appFileAgentList = '/service/sale/appFile/agentList',
+  spaceSdkUpload = '/service/sale/spaceSdk/upload',
+  appFileAdd = '/service/sale/appFile/add',
+  appFileUpload = '/service/sale/appFile/upload',
+  spaceSdkDelete = '/service/sale/spaceSdk/delete',
+  appFileList = '/service/sale/appFile/list',
+  appFileDelete = '/service/sale/appFile/delete'
 }
 
 /**

+ 286 - 0
src/api/spares/index.ts

@@ -0,0 +1,286 @@
+import { defHttp } from '/@/utils/http/axios';
+import { Result, FileStream } from '/#/axios';
+import { useI18n } from '/@/hooks/web/useI18n';
+import { detailResult } from './model';
+const { t } = useI18n();
+
+enum Api {
+  saleOrderList = '/service/sale/salePersonnel/saleOrderList',//工单列表
+  orderReceiving = '/service/sale/salePersonnel/orderReceiving',//工单接单
+  getByRoleType = '/service/sale/sysUser/getByRoleType',//根据角色类型获取用户列表
+  updateRemark = '/service/sale/salePersonnel/updateRemark',//单修改备注
+  recording = '/service/sale/salePersonnel/recording',//系统录单
+  allList = '/service/sale/laborCost/allList',//人工费用全部列表
+  getPriceList = '/service/sale/salePersonnel/getPriceList',//根据工单号获取报价单
+  addOrUpdatePriceList = '/service/sale/salePersonnel/addOrUpdatePriceList',//添加或修改报价单
+  payRegister = '/service/sale/salePersonnel/payRegister',//付款登记
+  sendRegister = '/service/sale/salePersonnel/sendRegister',//发货登记
+  supplyOrderList = '/service/sale/supplyPersonnel/supplyOrderList',//维修备件管理 供应链 列表
+  partRecovery = '/service/sale/supplyPersonnel/partRecovery',//备件回收
+  repairOrderList = '/service/sale/repairPersonnel/repairOrderList',//工单列表
+  detail = '/service/sale/repairInfo/details',//工单详情
+  partList = '/service/sale/part/list',//备件列表
+  faultAllList = '/service/sale/fault/allList',//故障列表
+  checkRegisterInfo = ' /service/sale/repairPersonnel/checkRegisterInfo',//维修登记,维修完成回显详情
+  checkRegister = '/service/sale/repairPersonnel/checkRegister',//维修登记
+  partAllList = '/service/sale/part/allList',//全部备件列表没分页
+  partAddOrUpdate = '/service/sale/part/addOrUpdate',//新增设备,修改设备,修改状态
+  partInStock = '/service/sale/part/inStock',//添加库存,入库
+  repairOver = '/service/sale/repairPersonnel/repairOver',//维修完成
+  partInStockLog = '/service/sale/part/inStockLog',//添加库存,入库 日志列表
+}
+
+/**
+ * @description: Get sample list value
+ */
+
+ interface logDataResule {
+  lastDownNum: number;
+  lastHighNum: number;
+  lastMajorNum: number;
+}
+ export const saleOrderList = (params) =>
+ defHttp.post<logDataResule>({
+   url: Api.saleOrderList,
+   params: params,
+   headers: {
+     // @ts-ignore
+     ignoreCancelToken: true,
+   },
+ });
+
+ export const detail = (params) =>
+ defHttp.get<detailResult>({
+   url: Api.detail,
+   params: params,
+   headers: {
+     // @ts-ignore
+     ignoreCancelToken: true,
+   },
+ });
+
+
+ export const orderReceiving = (params) =>
+ defHttp.post<Result>({
+   url: Api.orderReceiving,
+   params: params,
+   // data: params,
+   headers: {
+     // @ts-ignore
+     ignoreCancelToken: true,
+   },
+ });
+
+ export const getByRoleType = (params) =>
+ defHttp.get<Result>({
+   url: Api.getByRoleType,
+   params: params,
+   // data: params,
+   headers: {
+     // @ts-ignore
+     ignoreCancelToken: true,
+   },
+ });
+ 
+ export const recording = (params) =>
+ defHttp.post<Result>({
+   url: Api.recording,
+   params: params,
+   // data: params,
+   headers: {
+     // @ts-ignore
+     ignoreCancelToken: true,
+   },
+ });
+
+ export const updateRemark = (params) =>
+ defHttp.post<Result>({
+   url: Api.updateRemark,
+   params: params,
+   // data: params,
+   headers: {
+     // @ts-ignore
+     ignoreCancelToken: true,
+   },
+ });
+
+ export const allList = (params) =>
+ defHttp.post<Result>({
+   url: Api.allList,
+   params: params,
+   // data: params,
+   headers: {
+     // @ts-ignore
+     ignoreCancelToken: true,
+   },
+ });
+
+
+ export const getPriceList = (params) =>
+ defHttp.get<Result>({
+   url: Api.getPriceList,
+   params: params,
+   // data: params,
+   headers: {
+     // @ts-ignore
+     ignoreCancelToken: true,
+   },
+ });
+
+
+ export const addOrUpdatePriceList = (params) =>
+ defHttp.post<Result>({
+   url: Api.addOrUpdatePriceList,
+   params: params,
+   // data: params,
+   headers: {
+     // @ts-ignore
+     ignoreCancelToken: true,
+   },
+ });
+
+
+ export const payRegister = (params) =>
+ defHttp.post<Result>({
+   url: Api.payRegister,
+   params: params,
+   // data: params,
+   headers: {
+     // @ts-ignore
+     ignoreCancelToken: true,
+   },
+ });
+
+
+ export const sendRegister = (params) =>
+ defHttp.post<Result>({
+   url: Api.sendRegister,
+   params: params,
+   // data: params,
+   headers: {
+     // @ts-ignore
+     ignoreCancelToken: true,
+   },
+ });
+ export const supplyOrderList = (params) =>
+ defHttp.post<Result>({
+   url: Api.supplyOrderList,
+   params: params,
+   // data: params,
+   headers: {
+     // @ts-ignore
+     ignoreCancelToken: true,
+   },
+ });
+ 
+ export const repairOrderList = (params) =>
+ defHttp.post<logDataResule>({
+   url: Api.repairOrderList,
+   params: params,
+   headers: {
+     // @ts-ignore
+     ignoreCancelToken: true,
+   },
+ });
+
+ export const partList = (params) =>
+ defHttp.post<detailResult>({
+   url: Api.partList,
+   params: params,
+   headers: {
+     // @ts-ignore
+     ignoreCancelToken: true,
+   },
+ });
+ 
+ export const partAllList = (params) =>
+ defHttp.post<detailResult>({
+   url: Api.partAllList,
+   params: params,
+   headers: {
+     // @ts-ignore
+     ignoreCancelToken: true,
+   },
+ });
+
+ export const checkRegister = (params) =>
+ defHttp.post<detailResult>({
+   url: Api.checkRegister,
+   params: params,
+   headers: {
+     // @ts-ignore
+     ignoreCancelToken: true,
+   },
+ });
+
+ export const checkRegisterInfo = (params) =>
+ defHttp.get<detailResult>({
+   url: Api.checkRegisterInfo,
+   params: params,
+   headers: {
+     // @ts-ignore
+     ignoreCancelToken: true,
+   },
+ });
+
+ export const faultAllList = (params) =>
+ defHttp.get<detailResult>({
+   url: Api.faultAllList,
+   params: params,
+   headers: {
+     // @ts-ignore
+     ignoreCancelToken: true,
+   },
+ });
+
+ export const partAddOrUpdate = (params) =>
+ defHttp.post<detailResult>({
+   url: Api.partAddOrUpdate,
+   params: params,
+   headers: {
+     // @ts-ignore
+     ignoreCancelToken: true,
+   },
+ }); 
+
+ export const partInStock = (params) =>
+ defHttp.post<detailResult>({
+   url: Api.partInStock,
+   params: params,
+   headers: {
+     // @ts-ignore
+     ignoreCancelToken: true,
+   },
+ });
+
+ export const repairOver = (params) =>
+ defHttp.post<detailResult>({
+   url: Api.repairOver,
+   params: params,
+   headers: {
+     // @ts-ignore
+     ignoreCancelToken: true,
+   },
+ });
+
+ export const partInStockLog = (params) =>
+ defHttp.post<detailResult>({
+   url: Api.partInStockLog,
+   params: params,
+   headers: {
+     // @ts-ignore
+     ignoreCancelToken: true,
+   },
+ });
+ export const DownExport = (params) =>
+  defHttp.downloadFile<FileStream>({
+    url: Api.export + `?lang=${params.lang}`,
+    params: params,
+    fileName:t('routes.equity.listTitle')+'.xlsx',
+    // data: params,
+    headers: {
+      // @ts-ignore
+      ignoreCancelToken: true,
+    },
+    responseType: 'blob'
+  });

+ 202 - 0
src/api/spares/model.ts

@@ -0,0 +1,202 @@
+import { BasicPageParams, BasicFetchResult } from '/@/api/model/baseModel';
+/**
+ * @description: Request list interface parameters
+ */
+export type PageParams = BasicPageParams;
+
+export interface addCameraParams {
+  address: string;
+  balance: string;
+  cameraType: string;
+  childName: string;
+  companyId?: string;
+  orderSn?: string;
+  own: string;
+  snCode?: string;
+  wifiName: string;
+}
+export interface updateParams {
+  id: number;
+  canShow: number;
+  liveRoomCapacities: number;
+}
+export interface getItemParams {
+  id: number;
+}
+export interface DeviceListItem {
+  id: number;
+  activatedTime: string;
+  address: string;
+  agentFrameworkId: string;
+  agentFrameworkName: string;
+  agentName: string;
+  balance: string;
+  cameraType: number;
+  childName: string;
+  companyId: string;
+  companyName: string;
+  cooperationUser: string;
+  cooperationUserName: string;
+  country: number;
+  createTime: string;
+  goodsId: number;
+  goodsName: string;
+  imageUrl: string;
+  inTime: string;
+  isExpire: string;
+  lastTime: string;
+  nickName: string;
+  orderSn: string;
+  outTime: string;
+  own: number;
+  pic: string;
+  recStatus: string;
+  responseUserIncrement: string;
+  sceneNum: string;
+  snCode: string;
+  space: string;
+  spaceContent: string;
+  spaceEndStr: string;
+  spaceEndTime: string;
+  spaceId: string;
+  spaceStr: string;
+  surplusDate: string;
+  totalSpace: number;
+  totalSpaceStr: string;
+  type: string;
+  usedSpace: number;
+  usedSpaceStr: string;
+  userId: string;
+  userIncrementId: string;
+  userName: string;
+  wifiName: string;
+}
+
+export interface InvoiceList {
+  orderSn: string;
+  payTimeStart: string;
+  payTimeEnd: string;
+  invoiceTimeStart: string;
+  invoiceTimeEnd: string;
+  orderBy: string;
+  sortBy: string;
+  pageSize: number;
+  pageNum: number;
+}
+
+export interface customer {
+  customerId:	number;
+  repairId:	string;
+  wxOpenId:	null;
+  companyName:	string;
+  customerName:	string;
+  phone:	string;
+  recStatus:	string;
+  createTime:	string;
+  updateTime:	string;
+}
+export interface customerAddress {
+  addrId:	number;
+  customerId:	number;
+  repairId:	string;
+  getType:	string;
+  getAddress:	string;
+  getAddrName:	string;
+  getAddrPhone:	string;
+  getTrackingNum:	string;
+  sendType:	string;
+  sendTrackingNum:	string;
+  recStatus:	string;
+  createTime:	string;
+  updateTime:	string;
+}
+export interface repairerVo {
+  repairId:	string;	
+  cameraSnCode:	string;	
+  cameraType:	number;	
+  faultMsg:	string;	
+  faultImg:	string;	
+  status:	number;	
+  remark:	null;	
+  receiverType:	number;	
+  recStatus:	string;	
+  createTime:	string;	
+  updateTime:	string;	
+  sysUserId:	number;	
+}
+export interface repairerVo {
+  repairId:	string;	
+  cameraSnCode:	string;	
+  cameraType:	number;	
+  faultMsg:	string;	
+  faultImg:	string;	
+  status:	number;	
+  remark:	null;	
+  receiverType:	number;	
+  recStatus:	string;	
+  createTime:	string;	
+  updateTime:	string;	
+  sysUserId:	number;	
+}
+export interface orderReceivingVo {
+  orderId:	number;	
+  repairId:	string;	
+  orderFaultMsg:	string;	
+  orderFaultImg:	string;	
+  warrantyType:	number;	
+  warrantyExpirationDate:	string;	
+  sysUserName:	string;	
+  saleId:	number;	
+  repairerId:	number;	
+  supplyAdminId:	number;	
+  testerId:	number;	
+  recStatus:	string;	
+  createTime:	string;	
+  updateTime:	string;	
+
+}
+export interface repairRegisterVo {
+  checkResult:	string;	
+  checkImg:	string;	
+  remark:	string;	
+  status:	string;	
+  sysUserName:	string;	
+  overTime:	string;	
+}
+export interface RepairTestVo {
+  resultStatus:	string;	
+  resultInfo:	string;	
+  resultImg:	string;	
+  sysUserName:	string;	
+  passTime:	string;	
+}
+export interface repairPay {
+  payId:	number;	
+  repairId:	string;	
+  payType:	number;	
+  payImg:	string;	
+  recStatus:	string;	
+  createTime:	string;	
+  updateTime:	string;	
+  sysUserId:	number;	
+}
+export interface priceListItem {
+  name:	string;	
+  price:	string;	
+  count:	string;	
+}
+export interface detailResult {
+  customer:any;
+  customerAddress:any;
+  repairerVo:any;
+  RepairTestVo:any;
+  repairRegisterVo:any;
+  orderReceivingVo:any;
+  repairPay:any;
+  priceList:any[];
+}
+export type InvoiceListResul = BasicPageParams<InvoiceList>;
+/**
+ * @description: Request list return value
+ */
+export type RentListGetResultModel = BasicFetchResult<DeviceListItem>;

+ 7 - 7
src/api/staff/list.ts

@@ -4,14 +4,14 @@ import { Result } from '/#/axios';
 import { encodeStr } from '/@/utils/encodeUtil';
 
 enum Api {
-  pageList = '/service/manage/sysUser/list',
-  sysUserAdd = '/service/manage/sysUser/add',
-  updatePassword = '/service/manage/sysUser/rePassword',
-  preDel = '/service/manage/sysUser/delete',
+  pageList = '/service/sale/sysUser/list',
+  sysUserAdd = '/service/sale/sysUser/add',
+  updatePassword = '/service/sale/sysUser/rePassword',
+  preDel = '/service/sale/sysUser/delete',
   roleList = '/zfb-api/zfb/shop/sys/user/roleList',
-  getRoleListByParam = '/service/manage/sysRole/allList',
-  staffSave = '/service/manage/sysUser/add',
-  update = '/service/manage/sysUser/update',
+  getRoleListByParam = '/service/sale/sysRole/allList',
+  staffSave = '/service/sale/sysUser/add',
+  update = '/service/sale/sysUser/update',
   checkUser = '/zfb-api/zfb/user/checkUserExists',
   deleteStaff = '/zfb-api/zfb/shop/sys/user/deleteStaff',
   getNumByStaff = '/zfb-api/zfb/shop/sys/user/getNumByStaff',

+ 13 - 13
src/api/statistics/index.ts

@@ -3,19 +3,19 @@ import { PageParams, ListGetResultModel, DelParams, roleParams, companyExcelPara
 import { Result, FileStream } from '/#/axios';
 
 enum Api {
-  buryPointList = '/service/manage/buryPoint/list',
-  buryPointAdd = '/service/manage/buryPoint/add',
-  buryPointDlt = '/service/manage/buryPoint/delete',
-  userTotal = '/service/manage/data/userTotal',
-  orderTotal = '/service/manage/data/orderTotal',
-  userTrend = '/service/manage/data/userTrend',
-  orderTrend = '/service/manage/data/orderTrend',
-  cameraTrend = '/service/manage/data/cameraTrend',
-  sceneTrend = '/service/manage/data/sceneTrend',
-  sceneTotal = '/service/manage/data/sceneTotal',
-  cameraExport = '/service/manage/order/camera/export',
-  downExport = '/service/manage/order/down/export',
-  incrementExport = '/service/manage/order/increment/export',
+  buryPointList = '/service/sale/buryPoint/list',
+  buryPointAdd = '/service/sale/buryPoint/add',
+  buryPointDlt = '/service/sale/buryPoint/delete',
+  userTotal = '/service/sale/data/userTotal',
+  orderTotal = '/service/sale/data/orderTotal',
+  userTrend = '/service/sale/data/userTrend',
+  orderTrend = '/service/sale/data/orderTrend',
+  cameraTrend = '/service/sale/data/cameraTrend',
+  sceneTrend = '/service/sale/data/sceneTrend',
+  sceneTotal = '/service/sale/data/sceneTotal',
+  cameraExport = '/service/sale/order/camera/export',
+  downExport = '/service/sale/order/down/export',
+  incrementExport = '/service/sale/order/increment/export',
 }
 
 /**

+ 1 - 1
src/api/sys/menu.ts

@@ -2,7 +2,7 @@ import { defHttp } from '/@/utils/http/axios';
 import { getMenuListResultModel } from './model/menuModel';
 
 enum Api {
-  GetMenuList = '/service/manage/sysMenu/getByUserId',
+  GetMenuList = '/service/sale/sysMenu/getByUserId',
 }
 
 /**

+ 13 - 13
src/api/sys/system.ts

@@ -24,14 +24,14 @@ import { TreeMenuNode } from '/@/utils/treeUtils';
 // sys/menu/queryAll
 // sys/menu/update
 enum Api {
-  // MenuList = '/service/manage/sysMenu/getByRoleId',
-  getMenuIdsByRoleId = '/service/manage/sysMenu/getMenuIdsByRoleId',
-  MenuList = '/service/manage/sysMenu/list',
-  saveMenu = '/service/manage/sysMenu/add',
-  updateMenu = '/service/manage/sysMenu/update',
-  deleteMenu = '/service/manage/sysMenu/delete',
+  // MenuList = '/service/sale/sysMenu/getByRoleId',
+  getMenuIdsByRoleId = '/service/sale/sysMenu/getMenuIdsByRoleId',
+  MenuList = '/service/sale/sysMenu/list',
+  saveMenu = '/service/sale/sysMenu/add',
+  updateMenu = '/service/sale/sysMenu/update',
+  deleteMenu = '/service/sale/sysMenu/delete',
   MenuUser = '/zfb-api/zfb/shop/sys/menu/delete',
-  getByRoleId = '/service/manage/sysMenu/getByRoleId',
+  getByRoleId = '/service/sale/sysMenu/getByRoleId',
 
   AccountList = '/zfb-api/zfb/shop/sys/user/list',
   saveAccount = '/zfb-api/zfb/shop/sys/user/save',
@@ -39,14 +39,14 @@ enum Api {
   IsAccountExist = '/basic-api/system/accountExist',
   deleteAccountUser = '/zfb-api/zfb/shop/sys/user/preDeleteStaff',
 
-  RolePageList = '/service/manage/sysRole/list',
+  RolePageList = '/service/sale/sysRole/list',
   setRoleStatus = '/basic-api/system/setRoleStatus',
-  saveRole = '/service/manage/sysRole/add',
-  deleteRole = '/service/manage/sysRole/delete',
-  updateRole = '/service/manage/sysRole/update',
-  GetAllRoleList = '/service/manage/sysRole/allList',
+  saveRole = '/service/sale/sysRole/add',
+  deleteRole = '/service/sale/sysRole/delete',
+  updateRole = '/service/sale/sysRole/update',
+  GetAllRoleList = '/service/sale/sysRole/allList',
   roleSelectList = '/zfb-api/zfb/shop/sys/role/select',
-  giveMenu = '/service/manage/sysRole/giveMenu',
+  giveMenu = '/service/sale/sysRole/giveMenu',
 
   DeptList = '/zfb-api/zfb/shop/sys/dept/list',
 

+ 3 - 3
src/api/sys/user.ts

@@ -9,8 +9,8 @@ enum Api {
   // Login = '/basic-api/login',
   Login = '/service/agent/fdLogin',
   Logout = '/service/agent/fdLogout',
-  GetUserInfo = '/service/agent/getUserInfo',
-  GetPermCode = '/service/manage/sysMenu/getButtonByUserId',
+  GetUserInfo = '/service/sale/sysUser/getInfo',
+  GetPermCode = '/service/sale/sysMenu/getButtonByUserId',
 }
 
 /**
@@ -36,7 +36,7 @@ export function loginApi(params: LoginParams, mode: ErrorMessageMode = 'modal')
  * @description: getUserInfo
  */
 export function getUserInfo() {
-  return defHttp.post<UserInfo>({ url: Api.GetUserInfo }, { errorMessageMode: 'none' });
+  return defHttp.get<UserInfo>({ url: Api.GetUserInfo }, { errorMessageMode: 'none' });
 }
 
 export function getPermCode() {

+ 0 - 1
src/components/Form/src/components/FormItem.vue

@@ -235,7 +235,6 @@
         const isCheck = component && ['Switch', 'Checkbox'].includes(component);
 
         const eventKey = `on${upperFirst(changeEvent)}`;
-        console.log('props',props)
         const on = {
           [eventKey]: (...args: Nullable<Recordable>[]) => {
             const [e] = args;

+ 1 - 1
src/enums/cacheEnum.ts

@@ -1,5 +1,5 @@
 // token key
-export const TOKEN_KEY = 'TOKEN__';
+export const TOKEN_KEY = 'TOKEN__MP__';
 
 export const LOCALE_KEY = 'LOCALE__';
 

+ 1 - 1
src/enums/httpEnum.ts

@@ -2,7 +2,7 @@
  * @description: Request result set
  */
 export enum ResultEnum {
-  SUCCESS = 0,
+  SUCCESS = 200,
   ERROR = 1,
   TIMEOUT = 4008,
   TYPE = 'success',

+ 1 - 1
src/enums/pageEnum.ts

@@ -2,7 +2,7 @@ export enum PageEnum {
   // basic login path
   BASE_LOGIN = '/login',
   // basic home path
-  BASE_HOME = '/device/index',
+  BASE_HOME = '/home/index',
   // error page path
   ERROR_PAGE = '/exception',
   // error log page path

+ 2 - 2
src/layouts/default/header/components/user-dropdown/index.vue

@@ -78,8 +78,8 @@
       const userStore = useUserStore();
       const { createConfirm } = useMessage();
       const getUserInfo = computed(() => {
-        const { head, desc ,agent } = userStore.getUserInfo || {};
-        let realName = agent.name + agent.userName
+        const { head, desc ,nickName } = userStore.getUserInfo || {};
+        let realName = nickName
         return { realName:realName, avatar: head || headerImg, desc };
       });
 

+ 0 - 2
src/layouts/default/menu/index.vue

@@ -140,8 +140,6 @@
 
       function renderMenu() {
         const { menus, ...menuProps } = unref(getCommonProps);
-        console.log('getCommonProps',getCommonProps)
-        // console.log(menus);
         if (!menus || !menus.length) return null;
         return !props.isHorizontal ? (
           <SimpleMenu {...menuProps} isSplitMenu={unref(getSplit)} items={menus} />

+ 22 - 0
src/locales/lang/zh-CN/routes/spares.ts

@@ -0,0 +1,22 @@
+export default {
+  tableType:{
+    '-1':'全部',
+    0:'待接单',
+    1:'待检测',
+    2:'待报价',
+    3:'待确认',
+    4:'已取消',
+    5:'待备料',
+    6:'维修中',
+    7:'待回收',
+    8:'待测试',
+    9:'待支付',
+    10:'待发货',
+    11:'已发货',
+    12:'已评价',
+    13:'待评价',
+    21:'待跟进',
+    22:'维修完成',
+    23:'已完结',
+  },
+}

+ 18 - 0
src/locales/lang/zh-CN/routes/staff.ts

@@ -0,0 +1,18 @@
+export default {
+  deptName: '所属公司',
+  userName: '姓名',
+  nickName: '员工昵称',
+  setpaswd: '修改密码',
+  mobile: '手机',
+  permList: '权益',
+  userId: '账号',
+  createTime: '创建时间',
+  updateTime: '修改时间',
+  staffList: '账号列表',
+  updateBtn: '修改密码',
+  password: '修改密码',
+  roleType: {
+    0: '公司管理员',
+    1: '公司员工',
+  },
+};

+ 29 - 0
src/router/routes/modules/home.ts

@@ -0,0 +1,29 @@
+import type { AppRouteRecordRaw } from '/@/router/types';
+import { t } from '/@/hooks/web/useI18n';
+import { LAYOUT } from '/@/router/constant';
+
+export const Home: AppRouteRecordRaw = {
+  path: '/home',
+  name: 'Home',
+  redirect: '/home/index',
+  component: LAYOUT,
+  meta: {
+    title: '售后工单统计',
+    icon: 'codicon:home-camera',
+    orderNo: 4,
+    hideChildrenInMenu: true,
+  },
+  children: [
+    {
+      path: 'index',
+      name: 'HomeIndex',
+      component: () => import('/@/views/home/index.vue'),
+      meta: {
+        title: '售后工单统计',
+        hideBreadcrumb: true,
+        // icon: 'codicon:home-camera',
+      },
+    },
+  ],
+};
+export default Home;

+ 29 - 0
src/router/routes/modules/spares.ts

@@ -0,0 +1,29 @@
+import type { AppRouteRecordRaw } from '/@/router/types';
+import { t } from '/@/hooks/web/useI18n';
+import { LAYOUT } from '/@/router/constant';
+
+export const Spares: AppRouteRecordRaw = {
+  path: '/spares',
+  name: 'Spares',
+  redirect: '/spares/index',
+  component: LAYOUT,
+  meta: {
+    title: '备件管理',
+    icon: 'codicon:device-camera',
+    orderNo: 4,
+    hideChildrenInMenu: true,
+  },
+  children: [
+    {
+      path: 'index',
+      name: 'SparesIndex',
+      component: () => import('/@/views/spares/index.vue'),
+      meta: {
+        title: '备件管理',
+        hideBreadcrumb: true,
+        // icon: 'codicon:device-camera',
+      },
+    },
+  ],
+};
+export default Spares;

+ 75 - 0
src/router/routes/modules/work.ts

@@ -0,0 +1,75 @@
+import type { AppRouteRecordRaw } from '/@/router/types';
+import { t } from '/@/hooks/web/useI18n';
+import { LAYOUT } from '/@/router/constant';
+
+export const Spares: AppRouteRecordRaw = {
+  path: '/work',
+  name: 'Work',
+  redirect: '/work/index',
+  component: LAYOUT,
+  meta: {
+    title: '工单管理',
+    icon: 'codicon:device-camera',
+    orderNo: 4,
+    // hideChildrenInMenu: true,
+  },
+  children: [
+    {
+      path: 'index',
+      name: 'WorkIndex',
+      component: () => import('/@/views/spares/index.vue'),
+      meta: {
+        title: '工单跟进',
+        hideBreadcrumb: true,
+        // icon: 'codicon:device-camera',
+      },
+    },{
+      path: 'repairspares',
+      name: 'RepairSpares',
+      component: () => import('/@/views/spares/index.vue'),
+      meta: {
+        title: '维修备件管理',
+        hideBreadcrumb: true,
+        // icon: 'codicon:device-camera',
+      },
+    },{
+      path: 'repair',
+      name: 'RepairList',
+      component: () => import('/@/views/spares/index.vue'),
+      meta: {
+        title: '维修测试',
+        hideBreadcrumb: true,
+        // icon: 'codicon:device-camera',
+      },
+    },{
+      path: 'detail/:id',
+      name: 'DetailIndex',
+      component: () => import('/@/views/spares/detail.vue'),
+      meta: {
+        title: '工单详情',
+        hideBreadcrumb: true,
+        hideMenu: true,
+        // icon: 'codicon:device-camera',
+      },
+    },{
+      path: 'query',
+      name: 'WorkQuery',
+      component: () => import('/@/views/work/query.vue'),
+      meta: {
+        title: '工单查询',
+        hideBreadcrumb: true,
+        // icon: 'codicon:device-camera',
+      },
+    },{
+      path: 'aftermarket',
+      name: 'WorkAftermarket',
+      component: () => import('/@/views/work/aftermarket.vue'),
+      meta: {
+        title: '售后工单',
+        hideBreadcrumb: true,
+        // icon: 'codicon:device-camera',
+      },
+    },
+  ],
+};
+export default Spares;

+ 1 - 1
src/settings/projectSetting.ts

@@ -24,7 +24,7 @@ const setting: ProjectConfig = {
   settingButtonPosition: SettingButtonPositionEnum.AUTO,
 
   // Permission mode
-  permissionMode: PermissionModeEnum.ROUTE_MAPPING,
+  permissionMode: PermissionModeEnum.BACK,
 
   // Permission-related cache is stored in sessionStorage or localStorage
   permissionCacheType: CacheTypeEnum.LOCAL,

+ 2 - 0
src/store/modules/permission.ts

@@ -108,6 +108,7 @@ export const usePermissionStore = defineStore({
       this.setPermCodeList(permsList);
     },
     async buildRoutesAction(): Promise<AppRouteRecordRaw[]> {
+      console.log('buildRoutesAction')
       const { t } = useI18n();
       const userStore = useUserStore();
       const appStore = useAppStoreWithOut();
@@ -158,6 +159,7 @@ export const usePermissionStore = defineStore({
         }
         return;
       };
+      console.log('buildRoutesAction',permissionMode)
       switch (permissionMode) {
         case PermissionModeEnum.ROLE:
           routes = filter(asyncRoutes, routeFilter);

+ 17 - 14
src/store/modules/user.ts

@@ -32,7 +32,7 @@ export const useUserStore = defineStore({
     // user info
     userInfo: null,
     // token
-    token: undefined,
+    token: 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJsb2dpblR5cGUiOiJsb2dpbiIsImxvZ2luSWQiOjEsImRldmljZSI6ImRlZmF1bHQtZGV2aWNlIiwiZWZmIjotMSwicm5TdHIiOiJnOVE0dXNjRnBYc0JoWUtsTlpoS3lJZWo1SHFGNzZmRCIsInVzZXJJZCI6MSwiaXNBZG1pbiI6MSwidXNlck5hbWUiOiJzdXBlci1hZG1pbiIsIm5pY2tOYW1lIjoi6LaF57qn566h55CG5ZGYIn0.2zKzeog-rzCV6-t_JaEg0zFfZM-pKJNnrqSxnVp2fgo',
     // roleList
     roleList: [],
     // Whether the login expired
@@ -101,25 +101,27 @@ export const useUserStore = defineStore({
     ): Promise<GetUserInfoModel | null> {
       try {
         const { goHome = true, mode, ...loginParams } = params;
-        const data = await loginApi(loginParams, mode);
-        const { token, user, agent } = data;
-        this.setAgent(agent)
-        this.setUserInfo({
-          ...user,
-          userId:user.id,
-          realName: user.nickName,
-          username:user.userName,
-          avatar:user.head||'https://q1.qlogo.cn/g?b=qq&nk=339449197&s=640',
-          roles:[data.roleId],
-        });
-        // save token
-        this.setToken(token);
+        console.log('setSessionTimeout',goHome)
+      // const data = await loginApi(loginParams, mode);
+        // const { token, user, agent } = data;
+        // this.setAgent(agent)
+        // this.setUserInfo({
+        //   ...user,
+        //   userId:user.id,
+        //   realName: user.nickName,
+        //   username:user.userName,
+        //   avatar:user.head||'https://q1.qlogo.cn/g?b=qq&nk=339449197&s=640',
+        //   roles:[data.roleId],
+        // });
+        // // save token
+        this.setToken('eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJsb2dpblR5cGUiOiJsb2dpbiIsImxvZ2luSWQiOjEsImRldmljZSI6ImRlZmF1bHQtZGV2aWNlIiwiZWZmIjotMSwicm5TdHIiOiJnOVE0dXNjRnBYc0JoWUtsTlpoS3lJZWo1SHFGNzZmRCIsInVzZXJJZCI6MSwiaXNBZG1pbiI6MSwidXNlck5hbWUiOiJzdXBlci1hZG1pbiIsIm5pY2tOYW1lIjoi6LaF57qn566h55CG5ZGYIn0.2zKzeog-rzCV6-t_JaEg0zFfZM-pKJNnrqSxnVp2fgo');
         return this.afterLoginAction(goHome);
       } catch (error) {
         return Promise.reject(error);
       }
     },
     async afterLoginAction(goHome?: boolean): Promise<GetUserInfoModel | null> {
+      this.setToken(this.token);
       if (!this.getToken) return null;
       // get user info
       const userInfo = await this.getUserInfoAction();
@@ -143,6 +145,7 @@ export const useUserStore = defineStore({
     async getUserInfoAction(): Promise<UserInfo | null> {
       if (!this.getToken) return null;
       const userInfo = await getUserInfo();
+      console.log('home/userInfo',userInfo)
       const { roleId, agent = null } = userInfo;
       this.setAgent(agent)
       if (isArray(roleId)) { 

+ 123 - 0
src/views/home/condition.vue

@@ -0,0 +1,123 @@
+<template>
+  <div class="condition">
+    <div class="selct" style="display: inline-block" v-if="!typeShow">
+      <!-- <span style="margin-right:15px"></span> -->
+      <Select
+        v-model:value="type"
+        style="width: 100px; margin-right: 30px"
+        placeholder="请选择类型"
+        :options="typeOptions"
+        @change="handleType"
+      ></Select>
+    </div>
+
+    <div class="selct" style="display: inline-block; margin-right: 15px">
+      <RangePicker v-model:value="selectTime" @calendarChange="calendarPriceRangeChange" valueFormat="YYYY-MM-DD" :disabled-date="disabledDate" format="YYYY-MM-DD" @change="handleTime"/>
+    </div>
+
+    <div class="selct" style="display: inline-block">
+      <!-- <span style="margin-right:15px">颗粒度</span> -->
+      <Select
+        v-model:value="value"
+        style="width: 100px; margin-right: 30px"
+        placeholder="请选择颗粒度"
+        :options="options"
+        @change="handleData"
+      ></Select>
+    </div>
+    <a-button type="primary" @click="but">导出</a-button>
+  </div>
+</template>
+<script lang="ts" setup>
+import { ref, Ref, defineEmits } from 'vue';
+const emit = defineEmits(["alertSome"])
+import { Select, DatePicker } from 'ant-design-vue';
+const { RangePicker } = DatePicker;
+// import type { dataItemType } from './props';
+import type { Dayjs } from 'dayjs';
+import dayjs from 'dayjs';
+const props = defineProps({
+  loading: Boolean,
+  typeShow: Boolean,
+  type: String,
+  timeType: String,
+  name:Object,
+  selectTimeType:Number,
+});
+
+type RangeValue = [Dayjs, Dayjs];
+const picker = ref('date');
+const type = ref(props.timeType ||'0');
+const value = ref(props.type ||'0');
+const isUserDate =ref(false);
+const selectPriceDate = ref(dayjs().subtract(6,'month').format('YYYY-MM-DD'))
+const selectTime = ref<RangeValue>([dayjs().subtract(props.selectTimeType == 1 ? 1:6,'month').format('YYYY-MM-DD'), dayjs().format('YYYY-MM-DD')]);
+const options = ref<SelectProps['options']>([
+  {
+    value: '0',
+    label: '日',
+  },
+  {
+    value: '1',
+    label: '周',
+  },
+  {
+    value: '2',
+    label: '月',
+  },
+]);
+const disabledDate = (current: Dayjs) => {
+    if(selectPriceDate.value){
+        return current >dayjs(selectPriceDate.value).add(2,'year')  || current < dayjs(selectPriceDate.value).subtract(2,'year')
+    }else{
+        return false
+    }
+};
+const typeOptions = ref<SelectProps['options']>([
+  {
+    value: '0',
+    label: props.name && props.name[0] || '新增',
+  },
+  {
+    value: '1',
+    label: props.name && props.name[1] || '累计',
+  },
+]);
+function calendarPriceRangeChange(date){
+  selectPriceDate.value = date[0]
+}
+function handleData(val) {
+  value.value = val
+  if(!isUserDate.value){
+    selectTime.value = [dayjs().subtract(value.value == '0' ? 1:6,'month').format('YYYY-MM-DD'), dayjs().format('YYYY-MM-DD')];
+  }
+  output()
+}
+
+function handleType(val) {
+  type.value = val
+  output()
+}
+
+function handleTime(val) {
+  selectTime.value = val
+  isUserDate.value = true
+  output()
+}
+function but(){
+  if(!props.typeShow){
+    emit('expor',type)
+  }else{
+    emit('expor')
+  }
+}
+function output(){
+  let data = {
+    startTime:selectTime.value[0],
+    endTime:selectTime.value[1],
+    dataType:value.value,
+    type:type.value,
+  }
+  emit('change',data)
+}
+</script>

+ 229 - 0
src/views/home/faultType.vue

@@ -0,0 +1,229 @@
+<template>
+  <Card title="作品类型统计">
+    <template #extra>
+      <condition type="2" :typeShow="false"  @change="Search" @expor="expor" />
+    </template>
+    <div class="piechart" ref="chartPieRef" :style="{ height: '280px', width: '100%' }"></div>
+  </Card>
+</template>
+<script lang="ts" setup>
+import { Card } from 'ant-design-vue';
+import { ref, Ref, onMounted } from 'vue';
+import { workType } from '/@/api/home/index';
+import { useECharts } from '/@/hooks/web/useECharts';
+const chartPieRef = ref<HTMLDivElement | null>(null);
+const { setOptions } = useECharts(chartPieRef as Ref<HTMLDivElement>);
+const colorList = ['#38a0ff', '#4cca73', '#FDD56A', '#d58b55', '#c8ffff'];
+let pieData = ref([]);
+var rich = {
+  name: {
+    color: '#666666',
+    fontSize: 14,
+    padding: [8, 30, 0, 30],
+    fontWeight: '400',
+    align: 'left',
+  },
+  value: {
+    color: '#333',
+    fontSize: 15,
+    padding: [0, 30, 8, 30],
+    fontWeight: '500',
+    align: 'left',
+  },
+  percent: {
+    color: '#FFF',
+    align: 'right',
+    fontSize: 15,
+    fontWeight: '500',
+    //padding: [0, 5]
+  },
+  hr: {
+    borderColor: '#DFDFDF',
+    width: '100%',
+    borderWidth: 1,
+    height: 0,
+  },
+  cir: {
+    fontSize: 26,
+  },
+};
+var ydata = [
+  {
+    name: '财务管理决策实训',
+    value: 18,
+  },
+  {
+    name: '商品流通业实训',
+    value: 16,
+  },
+  {
+    name: '暖心陪伴(津乐园20cm定制蛋糕)',
+    value: 15,
+  },
+  {
+    name: '嘉果荟萃(津乐园20cm定制蛋糕)',
+    value: 14,
+  },
+  {
+    name: '优雅圆舞曲(津乐园20cm)',
+    value: 10,
+  },
+  {
+    name: '巧克力之夏(津乐园20cm定制蛋糕)',
+    value: 7.9,
+  },
+  {
+    name: '财税宝4G',
+    value: 6.7,
+  },
+  {
+    name: '成本会计',
+    value: 6,
+  },
+  {
+    name: '纳税会计与筹划',
+    value: 4.5,
+  },
+  {
+    name: '金融担保业实训',
+    value: 3,
+  },
+  {
+    name: '成本会计aaa',
+    value: 6,
+  },
+  {
+    name: '纳税会计与筹划aaa',
+    value: 4.5,
+  },
+  {
+    name: '金融担保业实训aaa',
+    value: 3,
+  },
+];
+var color = [
+  '#8d7fec',
+  '#5085f2',
+  '#e75fc3',
+  '#f87be2',
+  '#f2719a',
+  '#fca4bb',
+  '#f59a8f',
+  '#fdb301',
+  '#57e7ec',
+  '#cf9ef1',
+];
+var xdata = [
+  '财务管理决策实训',
+  '商品流通业实训',
+  '暖心陪伴(津乐园20cm定制蛋糕)',
+  '嘉果荟萃(津乐园20cm定制蛋糕)',
+  '优雅圆舞曲(津乐园20cm)',
+  '巧克力之夏(津乐园20cm定制蛋糕)',
+  '财税宝4G',
+  '成本会计',
+  '纳税会计与筹划',
+  '金融担保业实训',
+  '成本会计aaa',
+  '纳税会计与筹划aaa',
+  '金融担保业实训aaa',
+];
+function handlesetOptions() {
+  setOptions({
+    tooltip: {
+      trigger: 'item',
+      formatter: '{b}<br/>数量 : {c} ({d}%)',
+    },
+    legend: {
+      // orient: 'vartical',
+      // x: 'left',
+      orient: 'vertical',
+      type: 'scroll',
+      top: 'center',
+      left: '60%',
+      bottom: '0%',
+      data: xdata,
+      itemWidth: 8,
+      itemHeight: 8,
+      itemGap: 16,
+      formatter: function (name) {
+        return '' + name;
+      },
+    },
+    series: [
+      {
+        type: 'pie',
+        clockwise: false, //饼图的扇区是否是顺时针排布
+        minAngle: 2, //最小的扇区角度(0 ~ 360)
+        center: ['30%', '50%'],
+        avoidLabelOverlap: false,
+        itemStyle: {
+          //图形样式
+          normal: {
+            borderColor: '#ffffff',
+            borderWidth: 6,
+          },
+        },
+        label: {
+          normal: {
+            show: false,
+            position: 'center',
+            formatter: '{text|{b}}\n{c} ({d}%)',
+            rich: {
+              text: {
+                color: '#666',
+                fontSize: 14,
+                align: 'center',
+                verticalAlign: 'middle',
+                padding: 8,
+              },
+              value: {
+                color: '#8693F3',
+                fontSize: 24,
+                align: 'center',
+                verticalAlign: 'middle',
+              },
+            },
+          },
+          emphasis: {
+            show: true,
+            textStyle: {
+              fontSize: 24,
+            },
+          },
+        },
+        data: ydata,
+      },
+    ],
+    color: colorList,
+    backgroundColor: '#fff',
+  });
+}
+async function getList() {
+  const res = await workType({});
+  let zhStr = {
+    '4dkk': '三维场景', //四维看看作品
+    mix: '综合作品', //   混合作品
+    pano: '全景图', //全景看看作品
+  };
+  pieData.value = res.map((ele) => {
+    return {
+      ...ele,
+      value: ele.count,
+      name: zhStr[ele.groupKey],
+    };
+  });
+  handlesetOptions();
+}
+
+onMounted(() => {
+  handlesetOptions();
+  // getList();
+});
+</script>
+<style lang="less" scoped>
+.piechart {
+  width: 100%;
+  height: 100%;
+}
+</style>

+ 72 - 0
src/views/home/index.vue

@@ -0,0 +1,72 @@
+<template>
+  <div class="p-4">
+    <div class="md:flex !my-4 enter-y">
+      <faultType class="md:w-1/2 w-full !md:mt-0 !mt-4 !md:mr-4"></faultType>
+      <pieEchart class="md:w-1/2 w-full !md:mt-0 !mt-4 !md:mr-4"></pieEchart>
+      <!-- <lineEcharts title="管理中心用户活跃度统计" class="md:w-1/2 mx-4 w-full" /> -->
+    </div>
+    <orderEchart title="作品趋势分析" class="!my-4 enter-y" @change="Search" :echartData="worksData" :loading="loading" />
+    <lineEcharts title="云容量趋势分析" class="!my-4 enter-y" />
+    test
+  </div>
+</template>
+<script lang="ts" setup>
+import { ref, onMounted, reactive } from 'vue';
+// import { workTrend, sceneTrend } from '/@/api/home/index';
+import orderEchart from './orderEchart.vue';
+import pieEchart from './pieEchart.vue'
+import lineEcharts from './lineEcharts.vue';
+import faultType from './faultType.vue'
+const loading = ref(true);
+// const growCardList = ref<GrowCardItem[]>([]);
+const worksData = reactive({
+  xdata:[],
+  downOrder:[],
+  incrementOrder:[],
+  partOrder:[],
+  echartTypr:'bar',
+});
+const SearchData = reactive({
+  startTime: '',
+  endTime: '',
+  timeType: 'month',
+  infoType: 'add',
+});
+onMounted(() => {
+  // getData();
+  // getList();
+});
+// async function getList() {
+//   let downlist = [], xdata = [], downOrder = [], partOrder = [], incrementOrder = [];
+//   let res =  await workTrend(SearchData);
+//   res = res.reverse()
+//   res.map((ele) => {
+//     xdata.push(ele.groupKey);
+//     // downlist.push(ele.count);
+//     downOrder.push(ele.pano);
+//     partOrder.push(ele.mix);
+//     incrementOrder.push(ele.age);
+//   });
+//   worksData.xdata = xdata
+//   worksData.downOrder = downOrder
+//   worksData.incrementOrder = incrementOrder
+//   worksData.partOrder = partOrder
+// }
+// function Search(val) {
+//   console.log('infoType',val)
+//   const { startTime, endTime, dataType, type } = val;
+//   let timeStr = {
+//       0:'day',
+//       1:'week',
+//       2:'month',
+//     }
+//   SearchData.startTime = startTime;
+//   SearchData.endTime = endTime;
+//   SearchData.infoType = type == '0'?'add':'all';
+//   SearchData.timeType = timeStr[dataType] || 'month'
+//   getList();
+// }
+// async function getData() {
+// }
+</script>
+

+ 148 - 0
src/views/home/lineEcharts.vue

@@ -0,0 +1,148 @@
+<template>
+  <Card :title="title" :loading="loading">
+    <template #extra>
+      <condition :selectTimeType="title=='管理中心用户活跃度统计'?1:0"  :type="title=='管理中心用户活跃度统计'?'0':'2'"  :typeShow="title=='管理中心用户活跃度统计'" @change="Search"  @expor="handleExport" />
+    </template>
+      <div ref="chartRef" :style="{ height, width }"></div>
+  </Card>
+</template>
+<script lang="ts">
+  import { basicProps } from './props';
+  import { Card, Select } from 'ant-design-vue';
+  import type { SelectProps } from 'ant-design-vue';
+</script>
+<script lang="ts" setup>
+  import { ref, Ref, watch, onMounted, reactive } from 'vue';
+  import { volumeTrend, qjuserTrend } from '/@/api/home/index';
+  import condition from './condition.vue';
+  import dayjs from 'dayjs';
+  import { useECharts } from '/@/hooks/web/useECharts';
+  import { exportElsxFile, } from '/@/utils/file/download';
+  const props = defineProps({
+  loading: Boolean,
+    ...basicProps,
+  });
+  const value = ref(1);
+  const SearchData = reactive({
+    startTime: dayjs().subtract(props.title == '云容量趋势分析'?6:1,'month').format('YYYY-MM-DD'),
+    endTime: dayjs().format('YYYY-MM-DD'),
+    timeType: props.title == '云容量趋势分析'?'month':'day',
+    infoType: props.title == '云容量趋势分析'?'add':'all',
+  });
+  const options = ref<SelectProps['options']>([
+        {
+          value: 1,
+          label: '日',
+        },
+        {
+          value: 2,
+          label: '周',
+        },
+        {
+          value: 3,
+          label: '月',
+        },
+      ]);
+  const viewStaticsData = ref<number[]>([]);
+  const shareStaticsData = ref<number[]>([]);
+  const yixStringData = ref<string[]>([]);
+  const maxSize = ref(0);
+  const chartRef = ref<HTMLDivElement | null>(null);
+  const { setOptions } = useECharts(chartRef as Ref<HTMLDivElement>);
+  onMounted(() => {
+    getAddUser();
+  });
+  function handleChange(val){
+    console.log('handleChange',val)
+    SearchData.value = val
+    getAddUser()
+  }
+  function Search(val) {
+    console.log('Search',val)
+    let timeStr = {
+      0:'day',
+      1:'week',
+      2:'month',
+    }
+    const { startTime, endTime, dataType, type } = val;
+    SearchData.timeType = timeStr[dataType] || 'month'
+    SearchData.startTime = startTime;
+    SearchData.endTime = endTime;
+    SearchData.infoType = props.title == '管理中心用户活跃度统计'?'all':type == '0'?'add':'all';
+    getAddUser();
+  }
+  function handleExport(){
+  console.log('props',props.title)
+  let fields  = {
+    'time':'日期',
+    'num':'数量',
+  }
+    let data = yixStringData.value.map((ele,index) => {
+      return {
+        'time':ele,
+        'num':viewStaticsData.value && viewStaticsData.value[index] || 0,
+      }
+    })
+    exportElsxFile(data, fields,  props.title)
+}
+  async function getAddUser() {
+    let xdata = [], yData=[]
+    let apiSrc = props.title=='云容量趋势分析'?volumeTrend:qjuserTrend
+    let res = await apiSrc(SearchData);
+    const data = res.reverse()
+    data.map(ele => {
+      xdata.push(ele.groupKey)
+      yData.push(ele.count)
+    })
+    yixStringData.value = xdata
+    viewStaticsData.value = yData
+    handlesetOptions()
+  }
+  function handlesetOptions() {
+    setOptions({
+      tooltip: {
+        trigger: 'axis',
+        axisPointer: {
+          lineStyle: {
+            width: 1,
+            color: '#019680',
+          },
+        },
+      },
+      legend: {
+        orient: 'horizontal',
+        bottom: 0,
+      },
+      xAxis: {
+        type: 'category',
+        data: yixStringData.value,
+      },
+      yAxis: {
+        name:props.title=='云容量趋势分析'?'容量/G':'',
+        type: 'value',
+        splitNumber: 4,
+      },
+      series: [
+        {
+          data: viewStaticsData.value,
+          type: 'line',
+          itemStyle: { color: '#38a0ff' },
+          name: props.title=='云容量趋势分析'?'容量数':'用户活跃度',
+        },
+      ],
+    });
+  }
+  watch(
+    () => props.loading,
+    () => {
+      const maxNumber = Math.max(...viewStaticsData.value.concat(shareStaticsData.value));
+      const pow = Math.pow(10, maxNumber.toString().length - 1);
+      maxSize.value = maxNumber > 10 ? Math.floor(maxNumber / 10) * 10 + pow * 2 : 10;
+      handlesetOptions();
+    },
+    {
+      immediate: true,
+      deep: true,
+    },
+  );
+</script>

+ 129 - 0
src/views/home/orderEchart.vue

@@ -0,0 +1,129 @@
+ <template>
+  <Card :title="title||'订单数据统计'">
+    <template #extra>
+      <condition type="2" :typeShow="title=='相机出库数量统计'" :name="title=='订单数据统计'?{1:'金额',0:'数量'}:{}" @change="Search" @expor="expor" />
+    </template>
+    <div ref="chartRef" :style="{ height, width }"></div>
+  </Card>
+</template>
+<script lang="ts" setup>
+  import { basicProps } from './props';
+  import condition from './condition.vue';
+  import { Card, DatePicker } from 'ant-design-vue';
+  import { ref, Ref, watch, defineEmits } from 'vue';
+  import { useECharts } from '/@/hooks/web/useECharts';
+  import { exportElsxFile, } from '/@/utils/file/download';
+  const props = defineProps({
+  loading: Boolean,
+    ...basicProps,
+  });
+  const emit = defineEmits(["alertSome"])
+  const downOrderData = ref<number[]>([]);
+  const incrementOrderData = ref<number[]>([]);
+  const partsOrderData = ref<number[]>([]);
+  const yixStringData = ref<string[]>([]);
+  const echartTypr = ref('line')
+  const nameList = ref<string[]>(['全景图','三维场景','综合作品']);
+  const maxSize = ref(0);
+  const chartRef = ref<HTMLDivElement | null>(null);
+  const { setOptions } = useECharts(chartRef as Ref<HTMLDivElement>)
+
+  function Search(val){
+    emit('change',val)
+  }
+  function expor(val){
+    // emit('expor',val)
+    let hader = ['时间', ...nameList.value]
+    console.log('数量',hader,val)
+    let fields  = {
+      'time':'日期',
+      '1':hader[1],
+      '2':hader[2],
+      '3':hader[3],
+    }
+    if(props.title=='订单数据统计' && val?.value){
+      fields.time = `${val.value == 0?'数量/':'金额/'}` + fields.time
+    }
+    let data = yixStringData.value.map((ele,index) => {
+      return {
+        'time':ele,
+        '1':downOrderData.value && downOrderData.value[index] || 0,
+        '2':incrementOrderData.value && incrementOrderData.value[index] || 0,
+        '3':partsOrderData.value && partsOrderData.value[index] || 0,
+      }
+    })
+    exportElsxFile(data,fields,props.title)
+  }
+  function handlesetOptions() {
+    setOptions({
+      tooltip: {
+        trigger: 'axis',
+        axisPointer: {
+          lineStyle: {
+            width: 1,
+            color: '#019680',
+          },
+        },
+      },
+      legend: {
+        orient: 'horizontal',
+        bottom: 0,
+      },
+      // grid: { left: '2%', right: '2%', top: '10%', bottom: '10%', containLabel: true },
+      xAxis: {
+        type: 'category',
+        // data: [...new Array(30)].map((_item, index) => `${index + 1}日`),
+        data: yixStringData.value,
+      },
+      yAxis: {
+        type: 'value',
+        // max: maxSize.value,
+        splitNumber: 4,
+      },
+      series: [
+        {
+          data: downOrderData.value,
+          type: 'bar',
+          itemStyle: { color: '#38a0ff' },
+          barMaxWidth: 40,
+          name: nameList.value[0],
+          "stack": "总量",
+        },
+        {
+          data: incrementOrderData.value,
+          type: 'bar',
+          itemStyle: { color: '#4cca73' },
+          barMaxWidth: 40,
+          name: nameList.value[1],
+          "stack": "总量",
+        },
+        {
+          data: partsOrderData.value,
+          type: 'bar',
+          itemStyle: { color: '#FDD56A' },
+          barMaxWidth: 40,
+          name: nameList.value[2],
+          "stack": "总量",
+        },
+      ],
+    });
+  }
+  watch(
+    () => props.echartData,
+    (echartData) => {
+      console.log('workTrendechartData', echartData);
+      downOrderData.value = echartData.downOrder ||[]
+      incrementOrderData.value = echartData.incrementOrder ||[]
+      partsOrderData.value = echartData.partOrder ||[]
+      yixStringData.value = echartData.xdata ||[]
+      if(echartData.echartTypr){
+        echartTypr.value = echartData.echartTypr
+      }
+      handlesetOptions();
+    },
+    {
+      immediate: true,
+      deep: true,
+    },
+  );
+</script>

+ 164 - 0
src/views/home/pieEchart.vue

@@ -0,0 +1,164 @@
+<template>
+  <Card title="作品类型统计">
+    <template #extra>
+      <condition :selectTimeType="0"  :typeShow="false" @change="Search"  @expor="handleExport" />
+    </template>
+    <div class="piechart" ref="chartPieRef" :style="{ height: '280px', width: '100%' }"></div>
+  </Card>
+</template>
+<script lang="ts" setup>
+import condition from './condition.vue';
+import { Card } from 'ant-design-vue';
+import { ref, Ref, onMounted } from 'vue';
+import { workType } from '/@/api/home/index';
+import { useECharts } from '/@/hooks/web/useECharts';
+const chartPieRef = ref<HTMLDivElement | null>(null);
+const { setOptions } = useECharts(chartPieRef as Ref<HTMLDivElement>);
+const colorList = ['#38a0ff', '#4cca73', '#FDD56A', '#d58b55', '#c8ffff'];
+let pieData = ref([]);
+var rich = {
+  name: {
+    color: '#666666',
+    fontSize: 14,
+    padding: [8, 30, 0, 30],
+    fontWeight: '400',
+    align: 'left',
+  },
+  value: {
+    color: '#333',
+    fontSize: 15,
+    padding: [0, 30, 8, 30],
+    fontWeight: '500',
+    align: 'left',
+  },
+  percent: {
+    color: '#FFF',
+    align: 'right',
+    fontSize: 15,
+    fontWeight: '500',
+    //padding: [0, 5]
+  },
+  hr: {
+    borderColor: '#DFDFDF',
+    width: '100%',
+    borderWidth: 1,
+    height: 0,
+  },
+  cir: {
+    fontSize: 26,
+  },
+};
+function handlesetOptions() {
+  setOptions({
+    tooltip: {
+      trigger: 'item',
+      formatter: '{b}<br/>数量 : {c} ({d}%)',
+    },
+    legend: {
+      orient: 'horizontal',
+      bottom: 0,
+      data: ['全景图', '三维场景', '综合作品'],
+    },
+    series: [
+      {
+        name: '库存情况',
+        type: 'pie',
+        radius: '68%',
+        center: ['50%', '50%'],
+        clockwise: false,
+        data: pieData.value,
+        label: {
+          normal: {
+            position: 'inner',
+            formatter: (params) => {
+              return '{percent|' + params.percent.toFixed(0) + '%}';
+            },
+            rich: rich,
+          },
+        },
+        labelLine: {
+          normal: {
+            position: 'inner',
+            formatter: (params) => {
+              return '{percent|' + params.percent.toFixed(0) + '%}';
+            },
+            rich: rich,
+          },
+        },
+        itemStyle: {
+          borderWidth: 5,
+          borderColor: '#fff',
+        },
+      },
+    ],
+    color: colorList,
+    backgroundColor: '#fff',
+  });
+}
+async function getList() {
+  // const res = await workType({});
+  let zhStr = {
+    '4dkk': '三维场景', //四维看看作品
+    mix: '综合作品', //   混合作品
+    pano: '全景图', //全景看看作品
+  };
+  pieData.value = [
+    {
+      name: '三维场景',
+      value: 26,
+    },
+    {
+      name: '综合作品',
+      value: 26,
+    },
+    {
+      name: '全景图',
+      value: 26,
+    },
+  ];
+  // res.map(ele => {
+  //   return {
+  //     ...ele,
+  //     value: ele.count,
+  //     name: zhStr[ele.groupKey],
+  //   }
+  // })
+  handlesetOptions();
+}
+function Search(val) {
+    // console.log('Search',val)
+    // let timeStr = {
+    //   0:'day',
+    //   1:'week',
+    //   2:'month',
+    // }
+    // const { startTime, endTime, dataType, type } = val;
+    // SearchData.timeType = timeStr[dataType] || 'month'
+    // SearchData.startTime = startTime;
+    // SearchData.endTime = endTime;
+    // SearchData.infoType = props.title == '管理中心用户活跃度统计'?'all':type == '0'?'add':'all';
+    // getAddUser();
+  }
+  function handleExport(){
+  // let fields  = {
+  //   'time':'日期',
+  //   'num':'数量',
+  // }
+  //   let data = yixStringData.value.map((ele,index) => {
+  //     return {
+  //       'time':ele,
+  //       'num':viewStaticsData.value && viewStaticsData.value[index] || 0,
+  //     }
+  //   })
+  //   exportElsxFile(data, fields,  props.title)
+}
+onMounted(() => {
+  getList();
+});
+</script>
+<style lang="less" scoped>
+.piechart {
+  width: 100%;
+  height: 100%;
+}
+</style>

+ 56 - 0
src/views/home/props.ts

@@ -0,0 +1,56 @@
+import { PropType } from 'vue';
+
+export interface BasicProps {
+  width: string;
+  height: string;
+}
+export type dataItemType = {
+  date: string;
+  amount: string | number;
+};
+export type echartData = {
+  xdata: string[];
+  downOrder: number[];
+  incrementOrder: number[];
+  partOrder: number[];
+  nameList?:string[];
+  kjList?:number[];
+  kkList?:number[];
+  ssList?:number[];
+  ssobjList?:number[];
+  echartTypr?:string;
+};
+export const basicProps = {
+  width: {
+    type: String as PropType<string>,
+    default: '100%',
+  },
+  height: {
+    type: String as PropType<string>,
+    default: '280px',
+  },
+  viewStatics: {
+    type: Array as PropType<Array<dataItemType>>,
+    default: [],
+  },
+  shareStatics: {
+    type: Array as PropType<Array<dataItemType>>,
+    default: [],
+  },
+  bulletChatAmounts: {
+    type: Array as PropType<Array<dataItemType>>,
+    default: [],
+  },
+  userAmount: {
+    type: Array as PropType<Array<dataItemType>>,
+    default: [],
+  },
+  echartData: {
+    type: Object as PropType<echartData>,
+    default: {},
+  },
+  title: {
+    type: String,
+    default: '',
+  },
+};

+ 153 - 0
src/views/invoice/EditModal.vue

@@ -0,0 +1,153 @@
+<template>
+  <BasicModal
+    v-bind="$attrs"
+    @register="register"
+    title="开票详情"
+    height="500"
+    :showOkBtn="false"
+    :showCancelBtn="false"
+    @visible-change="handleVisibleChange"
+    @cancel="resetFields"
+    @ok="handleSubmit"
+  >
+    <div class="pt-2px pr-3px">
+      <BasicForm @register="registerForm">
+        <template #text="{ model, field }">
+          {{ model[field]  }}
+        </template>
+        <template #img="{ model }">
+          <TableImg v-if="model.invoiceUrl" :size="200" :adaptations="true" :simpleShow="true" :imgList="[model.invoiceUrl]" />
+        </template>
+      </BasicForm>
+    </div>
+  </BasicModal>
+</template>
+<script lang="ts">
+  import { defineComponent, nextTick, onMounted, reactive } from 'vue';
+  import { BasicModal, useModalInner } from '/@/components/Modal';
+  import { BasicForm, FormSchema, useForm } from '/@/components/Form/index';
+  import { TableImg } from '/@/components/Table';
+  import { useMessage } from '/@/hooks/web/useMessage';
+  import { InvoiceRegister, InvoiceDetail } from '/@/api/order';
+  import { useI18n } from '/@/hooks/web/useI18n';
+  import { uploadApi } from '/@/api/product/index';
+  import { ResultEnum } from '/@/enums/httpEnum';
+
+  const { t } = useI18n();
+  export default defineComponent({
+    components: { BasicModal, BasicForm, TableImg },
+    props: {
+      userData: { type: Object },
+    },
+    emits: ['update', 'register'],
+    setup(props, { emit }) {
+      const fileFlow = reactive({
+        file:null,
+        type:2,//2-普通发票,3-专用发票
+      })
+      const { createMessage } = useMessage();
+      const schemas: FormSchema[] = [
+          {
+            field: 'id',
+            component: 'Input',
+            show:false,
+            label: '发票编号',
+            required: true,
+          },
+          {
+            field: 'email',
+            component: 'Input',
+            label: '邮箱',
+            slot: 'text',
+            ifShow:fileFlow.type == 2,
+            colProps: {
+              span: 24,
+            },
+          },{
+            field: 'invoiceNum',
+            component: 'Input',
+            label: '发票编号',
+            slot: 'text',
+            required: true,
+            colProps: {
+              span: 24,
+            },
+          },{
+            field: 'invoiceUrl',
+            component: 'Upload',
+            label: '电子发票',
+            ifShow:fileFlow.type == 2,
+            required: true,
+            slot: 'img',
+            colProps: {
+              span: 22,
+            },
+          },{
+            field: 'invoiceNum',
+            component: 'Input',
+            label: '发票编号',
+            required: true,
+            slot: 'text',
+            colProps: {
+              span: 24,
+            },
+          },{
+            field: 'shipNum',
+            component: 'Input',
+            ifShow:fileFlow.type == 3,
+            slot: 'text',
+            label: '快递单号',
+            required: true,
+            colProps: {
+              span: 24,
+            },
+          },
+      ];
+
+      const [registerForm, { validate, resetFields, setFieldsValue, updateSchema }] = useForm({
+        labelWidth: 120,
+        schemas:schemas,
+        showActionButtonGroup: false,
+        actionColOptions: {
+          span: 24,
+        },
+      });
+      onMounted(() => {});
+      let addListFunc = () => {};
+      const [register, { closeModal }] = useModalInner((data) => {
+        data && onDataReceive(data);
+      });
+      async function onDataReceive(data) {
+        resetFields();
+        let detail = await InvoiceDetail({id:data.id})
+        fileFlow.type = data.type
+        setFieldsValue({
+          ...data,
+          ...detail,
+        });
+        updateSchema([
+          {field: 'shipNum',ifShow:fileFlow.type == 3,},
+          {field: 'email',ifShow:fileFlow.type == 2,},
+          {field: 'invoiceUrl',ifShow:fileFlow.type == 2,},
+        ])
+      }
+      const handleSubmit = async () => {
+          closeModal();
+          emit('update');
+      };
+      function handleVisibleChange(v) {
+        v && props.userData && nextTick(() => onDataReceive(props.userData));
+      }
+      return {
+        register,
+        registerForm,
+        fileFlow,
+        handleVisibleChange,
+        handleSubmit,
+        addListFunc,
+        resetFields,
+        t,
+      };
+    },
+  });
+</script>

+ 199 - 0
src/views/invoice/InvoiceModal.vue

@@ -0,0 +1,199 @@
+<template>
+  <BasicModal
+    v-bind="$attrs"
+    @register="register"
+    title="开票登记"
+    :okText="okText"
+    @visible-change="handleVisibleChange"
+    @cancel="resetFields"
+    :confirmLoading="loading"
+    @ok="handleSubmit"
+  >
+    <div class="pt-2px pr-3px">
+      <BasicForm @register="registerForm">
+        <template #text="{ model, field }">
+          {{ model[field]  }}
+        </template>
+      </BasicForm>
+    </div>
+  </BasicModal>
+</template>
+<script lang="ts">
+  import { defineComponent, nextTick, onMounted, reactive, ref } from 'vue';
+  import { BasicModal, useModalInner } from '/@/components/Modal';
+  import { BasicForm, FormSchema, useForm } from '/@/components/Form/index';
+  import { useMessage } from '/@/hooks/web/useMessage';
+  import { InvoiceRegister } from '/@/api/order';
+  import { useI18n } from '/@/hooks/web/useI18n';
+  import { uploadApi } from '/@/api/product/index';
+  import { ResultEnum } from '/@/enums/httpEnum';
+
+  const { t } = useI18n();
+  export default defineComponent({
+    components: { BasicModal, BasicForm },
+    props: {
+      userData: { type: Object },
+    },
+    emits: ['update', 'register'],
+    setup(props, { emit }) {
+      const fileFlow = reactive({
+        file:null,
+        type:2,//2-普通发票,3-专用发票
+      })
+      const okText = ref('发送')
+      const loading = ref(false)
+      const { createMessage } = useMessage();
+      const schemas: FormSchema[] = [
+          {
+            field: 'id',
+            component: 'Input',
+            show:false,
+            label: '发票编号',
+            required: true,
+          },
+          {
+            field: 'email',
+            component: 'Input',
+            label: '邮箱',
+            slot: 'text',
+            ifShow:fileFlow.type == 2,
+            componentProps: {
+              maxLength: 50,
+            },
+            colProps: {
+              span: 24,
+            },
+          },{
+            field: 'invoiceNum',
+            component: 'Input',
+            label: '发票编号',
+            required: true,
+            componentProps: {
+              maxLength: 50,
+            },
+            colProps: {
+              span: 24,
+            },
+          },{
+            field: 'file',
+            component: 'Upload',
+            label: '电子发票',
+            ifShow:fileFlow.type == 2,
+            required: true,
+            rules: [{ required: true, message: t('common.uploadMessge') }],
+            itemProps: {
+              validateTrigger: 'blur',
+            },
+            componentProps: {
+              api: uploadApi,
+              fileFlow:true,
+              maxNumber: 1,
+              maxSize: 1000,
+              accept: ['jpeg','jpg','png'],
+              afterFetch: function (data) {
+                console.log('url',data)
+                // Reflect.set(data, 'url', data.file);
+                fileFlow.file = data.file
+                return data;
+              },
+            },
+
+            colProps: {
+              span: 22,
+            },
+          },{
+            field: 'invoiceNum',
+            component: 'Input',
+            label: '发票编号',
+            required: true,
+            componentProps: {
+              maxLength: 50,
+            },
+            colProps: {
+              span: 24,
+            },
+          },{
+            field: 'shipNum',
+            component: 'Input',
+            ifShow:fileFlow.type == 3,
+            label: '快递单号',
+            componentProps: {
+              maxLength: 50,
+            },
+            required: true,
+            colProps: {
+              span: 24,
+            },
+          },
+      ];
+
+      const [registerForm, { validate, resetFields, setFieldsValue, updateSchema }] = useForm({
+        labelWidth: 120,
+        schemas:schemas,
+        showActionButtonGroup: false,
+        actionColOptions: {
+          span: 24,
+        },
+      });
+      onMounted(() => {});
+      let addListFunc = () => {};
+      const [register, { closeModal }] = useModalInner((data) => {
+        data && onDataReceive(data);
+      });
+      function onDataReceive(data) {
+        resetFields();
+        fileFlow.type = data.type
+        setFieldsValue(data);
+        okText.value = fileFlow.type == 2?'发送':'确认'
+        updateSchema([
+          {field: 'shipNum',ifShow:fileFlow.type == 3,},
+          {field: 'email',ifShow:fileFlow.type == 2,},
+          {field: 'file',ifShow:fileFlow.type == 2,},
+        ])
+      }
+      const handleSubmit = async () => {
+        loading.value = true
+        try {
+          const params = await validate();
+          const apiData = {
+            data:fileFlow.type == 3?{
+              id:params.id,
+              invoiceNum:params.invoiceNum,
+              shipNum:params.shipNum,
+            }:{
+              id:params.id,
+              invoiceNum:params.invoiceNum,
+              file:fileFlow.file,
+            // file:params.file[0],
+            }
+          }
+          console.log('res', apiData,params);
+          await InvoiceRegister(apiData);
+          closeModal();
+          resetFields();
+          createMessage.success(t('common.optSuccess'));
+          emit('update');
+          loading.value = false
+        } catch (error) {
+          loading.value = false
+          console.log('not passing', error);
+        }
+      };
+      function handleVisibleChange(v) {
+        v && props.userData && nextTick(() => onDataReceive(props.userData));
+      }
+      return {
+        register,
+        registerForm,
+        fileFlow,
+        handleVisibleChange,
+        handleSubmit,
+        addListFunc,
+        resetFields,
+        okText,
+        loading,
+        t,
+      };
+    },
+  });
+</script>

+ 233 - 0
src/views/invoice/data.tsx

@@ -0,0 +1,233 @@
+import { Time } from '/@/components/Time';
+import { FormProps, BasicColumn } from '/@/components/Table';
+import { h } from 'vue';
+import { FormSchema } from '/@/components/Form/index';
+import { useI18n } from '/@/hooks/web/useI18n';
+const { t } = useI18n();
+
+export const columns: BasicColumn[] = [
+        
+  {
+    title: '订单号',
+    dataIndex: 'orderSn',
+    ellipsis: false,
+    width: 180,
+  },
+  {
+    title: '支付时间',
+    dataIndex: 'payTime',
+    width: 150,
+    customRender: ({ record }) => {
+      return (
+        record.payTime &&
+        h(Time, {
+          value: record.payTime,
+          mode: 'datetime',
+        })
+      );
+    },
+  },
+  {
+    title: '开票申请时间',
+    dataIndex: 'invoiceTime',
+    width: 150,
+    customRender: ({ record }) => {
+      return (
+        record.invoiceTime &&
+        h(Time, {
+          value: record.invoiceTime,
+          mode: 'datetime',
+        })
+      );
+    },
+  },
+  {
+    title: '开票金额',
+    dataIndex: 'money',
+    width: 80,
+  },
+  {
+    title: '开票类型',
+    dataIndex: 'type',
+    width: 80,
+    customRender:({record})=>{
+      if(record.type == 2){
+        return '普通发票'
+      }else{
+        return '专用发票'
+      }
+    }
+  },
+  {
+    title: '发票抬头',
+    dataIndex: 'title',
+    // slots: { customRender: 'orderType' },
+    width: 150,
+  },
+  {
+    title: '纳税人识别号',
+    dataIndex: 'code',
+    // slots: { customRender: 'orderStatus' },
+    width: 180,
+  },
+  {
+    title: '注册地址',
+    dataIndex: 'organizedAddress',
+    // slots: { customRender: 'orderStatus' },
+    width: 180,
+  },
+  {
+    title: '注册电话',
+    dataIndex: 'registerPhone',
+    // slots: { customRender: 'orderStatus' },
+    width: 120,
+  },
+  {
+    title: '开户银行',
+    dataIndex: 'bankName',
+    // slots: { customRender: 'orderStatus' },
+    width: 150,
+  },
+  {
+    title: '银行账号',
+    dataIndex: 'bankAccount',
+    // slots: { customRender: 'orderStatus' },
+    width: 130,
+  },
+  {
+    title: '收件人姓名',
+    dataIndex: 'shipName',
+    // slots: { customRender: 'orderStatus' },
+    width: 120,
+  },
+  {
+    title: '收件人电话',
+    dataIndex: 'shipMobile',
+    // slots: { customRender: 'orderStatus' },
+    width: 110,
+  },
+  {
+    title: '收货地址',
+    dataIndex: 'shipAddress',
+    // slots: { customRender: 'orderStatus' },
+    width: 130,
+  },
+  {
+    title: '邮箱',
+    dataIndex: 'email',
+    // slots: { customRender: 'orderStatus' },
+    width: 150,
+  },
+  {
+    title: '状态',
+    dataIndex: 'invoiced',
+    // slots: { customRender: 'orderStatus' },
+    width: 80,
+    customRender:({record})=>{
+      if(record.invoiced == 0){
+        return '未开票'
+      }else{
+        return '已开票'
+      }
+    }
+  },
+];
+export const searchForm: Partial<FormProps> = {
+  labelWidth: 100,
+  schemas: [
+    {
+      field: 'invoiceTime',
+      label: '开票申请时间',
+      component: 'RangePicker',
+      componentProps: {
+        maxLength: 100,
+        valueFormat:'YYYY-MM-DD',
+        format: 'YYYY-MM-DD',
+      },
+      colProps: {
+        xl: 7,
+        xxl: 7,
+      },
+    },
+    {
+      field: 'payTime',
+      label: '支付时间',
+      component: 'RangePicker',
+      componentProps: {
+        maxLength: 100,
+        valueFormat:'YYYY-MM-DD',
+        format: 'YYYY-MM-DD',
+      },
+      colProps: {
+        xl: 7,
+        xxl: 7,
+      },
+    },{
+      field: 'orderSn',
+      label: '订单号',
+      component: 'Input',
+      componentProps: {
+        maxLength: 100,
+      },
+      colProps: {
+        xl: 6,
+        xxl: 6,
+      },
+    }
+  ],
+};
+export const InvoiceSchemas: FormSchema[] = [
+  {
+    field: 'type',
+    component: 'Input',
+    label: t('routes.product.types'),
+    slot: 'text',
+    colProps: {
+      span: 24,
+    },
+  },
+  {
+    field: 'version',
+    component: 'Input',
+    label: t('routes.product.version'),
+    required: true,
+    colProps: {
+      span: 24,
+    },
+    rules: [
+      {
+        required: true,
+        // @ts-ignore
+        validator: async (rule, value) => {
+          if (!value) {
+            return Promise.reject(t('common.inputText')+t('routes.product.version'));
+          }
+          if(/.*[\u4e00-\u9fa5]+.*$/.test(value)){
+            /* eslint-disable-next-line */
+            return Promise.reject('不支持中文字符');
+          }
+          return Promise.resolve();
+        },
+        trigger: 'change',
+      },
+    ],
+    componentProps: {
+      maxLength: 15,
+      onChange: (data) => {
+        console.log('data', data);
+      },
+    },
+  },
+  {
+    field: 'description',
+    component: 'InputTextArea',
+    required: true,
+    label: t('routes.product.description'),
+    componentProps: {
+      rows:4,
+    },
+    colProps: {
+      span: 24,
+    },
+  },
+]

+ 171 - 0
src/views/invoice/index.vue

@@ -0,0 +1,171 @@
+<template>
+  <div>
+    <BasicTable @register="registerTable">
+      <template #toolbar>
+        <a-button type="primary" @click="exportExcel" v-if="getCheckPerm('invoice-export')"> 导出</a-button>
+      </template>
+      <template #action="{ record }">
+          <TableAction
+            stopButtonPropagation
+            :actions="[
+              {
+                label: '开票登记',
+                //icon: 'la:file-invoice-dollar',
+                ifShow:getCheckPerm('invoice-checkin') && record.invoiced==0,
+                onClick: handleInvoice.bind(null, record),
+              },
+              {
+                label: '电子发票',
+                ifShow:record.type == 2 && record.invoiced==1,
+                //icon: 'simple-icons:invoiceninja',
+                onClick: headleDetails.bind(null, record),
+              },
+              {
+                label: '快递单号',
+                ifShow:record.type != 2&& record.invoiced==1,
+                //icon: 'icon-park-outline:express-delivery',
+                onClick: headleDetails.bind(null, record),
+              },
+            ]"
+          />
+      </template>
+    </BasicTable>
+    <AddModal @update="reload" @register="registerAddModal" />
+    <EditModal @register="registerEditModal" @update="reload" />
+  </div>
+</template>
+<script lang="ts">
+  import { defineComponent, h, reactive } from 'vue';
+  import { BasicTable, useTable, TableAction, TableImg } from '/@/components/Table';
+  import { PageWrapper } from '/@/components/Page';
+  import { useModal } from '/@/components/Modal';
+  import { Descriptions } from 'ant-design-vue';
+  import { useI18n } from '/@/hooks/web/useI18n';
+  import { useMessage } from '/@/hooks/web/useMessage';
+  import { InvoiceList,InvoiceExport } from '/@/api/order'
+  import { downloadByData } from '/@/utils/file/download'
+  import { searchForm, columns } from './data'
+  import AddModal from './InvoiceModal.vue';
+  import EditModal from './EditModal.vue';
+  import { usePermissionStore } from '/@/store/modules/permission';
+  interface apiDataParam {
+    orderSn?: string;
+    payTimeStart?: string;
+    payTimeEnd?: string;
+    invoiceTimeStart?: string;
+    invoiceTimeEnd?: string;
+    orderBy?: string;
+    sortBy?: string;
+  }
+  export default defineComponent({
+    components: { 
+      AddModal,
+      EditModal,
+      BasicTable, 
+      TableAction, 
+      PageWrapper,
+      TableImg,
+      [Descriptions.name]: Descriptions,
+      [Descriptions.Item.name]: Descriptions.Item,
+    },
+    setup() {
+      const { t } = useI18n();
+      const permissionStore = usePermissionStore();
+      const { getCheckPerm } = permissionStore;
+      const apiData = reactive<apiDataParam>({
+        orderSn: '',
+        payTimeStart: '',
+        payTimeEnd: '',
+        invoiceTimeStart: '',
+        invoiceTimeEnd: '',
+        orderBy: '',
+        sortBy: '',
+      });
+      const { createMessage,createConfirm } = useMessage();
+      const [registerAddModal, { openModal: openAddModal }] = useModal();
+      const [registerEditModal, { openModal: openEditModal }] = useModal();
+      const [registerTable, { reload }] = useTable({
+        api: InvoiceList,
+        title: '发票列表',
+        // titleHelpMessage: ['已启用expandRowByClick', '已启用stopButtonPropagation'],
+        columns: columns,
+        useSearchForm: true,
+        formConfig: searchForm,
+        showTableSetting: true,
+        showIndexColumn:false,
+        rowKey: 'id',
+        beforeFetch:(params) =>{
+          let searchData = {
+              orderSn: params.orderSn,
+              payTimeStart: params.payTime && params.payTime[0],
+              payTimeEnd: params.payTime && params.payTime[1],
+              invoiceTimeStart: params.invoiceTime && params.invoiceTime[0],
+              invoiceTimeEnd: params.invoiceTime && params.invoiceTime[1],
+          }
+          apiData.orderSn = searchData.orderSn
+          apiData.payTimeStart = searchData.payTimeStart
+          apiData.payTimeEnd = searchData.payTimeEnd
+          apiData.invoiceTimeStart = searchData.invoiceTimeStart
+          apiData.invoiceTimeEnd = searchData.invoiceTimeEnd
+          return {
+            ...params,
+            ...searchData,
+          }
+        },
+        fetchSetting: {
+          pageField: 'pageNum',
+          sizeField: 'pageSize',
+          listField: 'list',
+          totalField: 'total',
+        },
+        actionColumn: {
+          width: 100,
+          title: '操作',
+          fixed: 'right',
+          dataIndex: 'action',
+          slots: { customRender: 'action' },
+        },
+        canResize: false,
+      });
+      function handleDelete(record: Recordable) {
+        console.log('点击了删除', record);
+      }
+      function handleInvoice(record: Recordable) {
+        openAddModal(true, {
+          ...record,
+        });
+      }
+    function handleEdit(record: Recordable) {
+      console.log('record', record);
+    }
+    function headleDetails(record: Recordable) {
+      console.log('record', record);
+      openEditModal(true, {
+        ...record,
+      });
+    }
+    function exportExcel() {
+        createConfirm({
+          iconType: 'warning',
+          title: () => h('span', '温馨提示'),
+          content: () => h('span', '确定导出当前发票所有记录?'),
+          onOk: async () => {
+           await InvoiceExport(apiData);
+          },
+        });
+      }
+      return {
+        registerTable,
+        handleDelete,
+        registerAddModal,
+        registerEditModal,
+        handleInvoice,
+        exportExcel,
+        handleEdit,
+        headleDetails,
+        reload,
+        getCheckPerm,
+      };
+    },
+  });
+</script>

+ 101 - 0
src/views/spares/RoleDrawer.vue

@@ -0,0 +1,101 @@
+<template>
+  <BasicModal
+    v-bind="$attrs"
+    @register="register"
+    title="备件入库"
+    width="700px"
+    @cancel="resetFields"
+    :confirmLoading="loading"
+    @ok="handleSubmit"
+  >
+    <div class="pt-2px pr-3px">
+      <BasicTable @register="registerTable"></BasicTable>
+    </div>
+  </BasicModal>
+</template>
+<script lang="ts">
+import { defineComponent, reactive, ref } from 'vue';
+import { BasicModal, useModalInner } from '/@/components/Modal';
+import { BasicForm } from '/@/components/Form/index';
+import { useMessage } from '/@/hooks/web/useMessage';
+import { partInStockLog, partInStock } from '/@/api/spares';
+import { useI18n } from '/@/hooks/web/useI18n';
+import { useTable, BasicTable, BasicColumn } from '/@/components/Table';
+
+const { t } = useI18n();
+export default defineComponent({
+  components: { BasicModal, BasicTable },
+  props: {
+    userData: { type: Object },
+  },
+  emits: ['update', 'register'],
+  setup(props, { emit }) {
+    const fileFlow = reactive({
+      file: null,
+      type: 2, //2-普通发票,3-专用发票
+    });
+    const loading = ref(false);
+    const { createMessage } = useMessage();
+    const columns: BasicColumn[] = [
+      {
+        title: '备件编号',
+        dataIndex: 'partId',
+        width: 80,
+      },
+      {
+        title: '备件名称',
+        dataIndex: 'partName',
+        width: 180,
+      },
+      {
+        title: '入库数量',
+        dataIndex: 'count',
+        width: 80,
+      },
+      {
+        title: '操作人',
+        dataIndex: 'sysUserName',
+        width: 100,
+      },
+      {
+        title: '操作时间',
+        dataIndex: 'createTime',
+        width: 150,
+      },
+    ];
+
+    const [registerTable, { reload }] = useTable({
+      api: partInStockLog,
+      columns,
+      useSearchForm: false,
+      showTableSetting: true,
+      bordered: true,
+      showIndexColumn: false,
+      canResize: true,
+      fetchSetting: {
+        pageField: 'pageNum',
+        sizeField: 'pageSize',
+        listField: 'list',
+        totalField: 'total',
+      },
+    });
+    const [register, { closeModal }] = useModalInner((data) => {
+        data && onDataReceive(data);
+      });
+    function onDataReceive(data) {
+      reload();
+    }
+    const handleSubmit = async () => {
+      closeModal()
+    };
+    return {
+      register,
+      registerTable,
+      fileFlow,
+      handleSubmit,
+      loading,
+      t,
+    };
+  },
+});
+</script>

+ 254 - 0
src/views/spares/detail.vue

@@ -0,0 +1,254 @@
+<template>
+  <div class="detailPage">
+    <div class="topButton">
+      <a-button type="primary" @click="goBack">
+        {{ t('common.back') }}
+      </a-button>
+    </div>
+    <div class="content">
+      <div class="content_left">
+        <div class="content_left_info">
+          <Descriptions title="客户信息" :column="3" v-if="detailData.customer">
+            <DescriptionsItem label="客户名称"> {{ detailData.customer.companyName }}</DescriptionsItem>
+            <DescriptionsItem label="联系人"> {{ detailData.customer.customerName }} </DescriptionsItem>
+            <DescriptionsItem label="联系电话"> {{ detailData.customer.phone }} </DescriptionsItem>
+          </Descriptions>
+          <Descriptions title="产品及故障信息" :column="3" v-if="detailData.orderReceivingVo">
+            <DescriptionsItem label="产品名称" v-if="detailData.repairerVo"> {{ t(`routes.device.type.${detailData.repairerVo.cameraType || 1}`)  }} </DescriptionsItem>
+            <DescriptionsItem label="产品SN码" v-if="detailData.repairerVo"> {{ detailData.repairerVo.cameraSnCode }} </DescriptionsItem>
+            <DescriptionsItem label="保修届满日期"> {{ detailData.orderReceivingVo.warrantyExpirationDate }} </DescriptionsItem>
+            <DescriptionsItem label="保修日期"> {{ detailData.orderReceivingVo.warrantyExpirationDate }} </DescriptionsItem>
+            <DescriptionsItem label="送修方式" v-if="detailData.customerAddress"> {{ detailData.customerAddress.sendType?'前台送修':'快递寄送' }} </DescriptionsItem>
+            <DescriptionsItem label="保修类型"> {{ detailData.orderReceivingVo.warrantyType }} </DescriptionsItem>
+            <DescriptionsItem label="维修单号"> {{ detailData.orderReceivingVo.repairId }} </DescriptionsItem>
+            <DescriptionsItem label="上次维修单号" :span="2"> {{ detailData.orderReceivingVo.repairerId }} </DescriptionsItem>
+            <DescriptionsItem label="故障描述" :span="3">
+              <div>
+                <p>{{ detailData.orderReceivingVo.orderFaultMsg }}</p>
+                <ImagePreviewGroup>
+                  <Image :width="80" v-for="item in detailData.orderReceivingVo.orderFaultImg" :key="item" :src="item"></Image>
+                </ImagePreviewGroup>
+              </div> 
+            </DescriptionsItem>
+            <DescriptionsItem label="机器外观" v-if="detailData.orderReceivingVo">
+              <div>
+                <p>{{ detailData.orderReceivingVo.paymentStatus }}</p>
+                <ImagePreviewGroup>
+                  <Image :width="80" v-for="item in [demopng,logo,demopng]" :key="item" :src="item"></Image>
+                </ImagePreviewGroup>
+              </div> 
+            </DescriptionsItem>
+            <DescriptionsItem label="售后工程师" v-if="detailData.orderReceivingVo"> {{ detailData.orderReceivingVo.sysUserName }} </DescriptionsItem>
+            <DescriptionsItem label="接单日期" v-if="detailData.orderReceivingVo"> {{ detailData.orderReceivingVo.createTime }} </DescriptionsItem>
+            <DescriptionsItem label="检测结果" v-if="detailData.repairRegisterVo">
+              <div>
+                <p>{{detailData.repairRegisterVo.checkResult}}</p>
+                <ImagePreviewGroup>
+                  <Image :width="80" v-for="item in detailData.repairRegisterVo.checkImg" :key="item" :src="item"></Image>
+                </ImagePreviewGroup>
+              </div> 
+            </DescriptionsItem>
+            <DescriptionsItem label="检测日期" v-if="detailData.orderReceivingVo"> {{ detailData.repairRegisterVo.createTime }} </DescriptionsItem>
+            <DescriptionsItem label="所需备件" v-if="detailData.orderReceivingVo"> {{ detailData.orderReceivingVo.shipMobile }} </DescriptionsItem>
+          </Descriptions>
+          <Descriptions title="维修清单" :column="3" layout="vertical">
+            <DescriptionsItem label="备件信息" :span="3">
+              <BasicTable @register="registerTable"></BasicTable>
+            </DescriptionsItem>
+          </Descriptions>
+          <Descriptions  :column="3" v-if="detailData.repairRegisterVo">
+            <DescriptionsItem label="维修工程师"> {{ detailData.repairRegisterVo.sysUserName }}</DescriptionsItem>
+            <DescriptionsItem label="维修完成日期"> {{ detailData.repairRegisterVo.overTime }} </DescriptionsItem>
+            <DescriptionsItem label="维修记录"> {{ detailData.repairRegisterVo.remark }} </DescriptionsItem>
+            <DescriptionsItem label="测试工程师" v-if="detailData.RepairTestVo"> {{ detailData.RepairTestVo.sysUserName }} </DescriptionsItem>
+            <DescriptionsItem label="测试通过时间" :span="2"> {{ detailData.repairRegisterVo.passTime }} </DescriptionsItem>
+            <DescriptionsItem label="支付方式" :span="3"> 
+              <div v-if="detailData.repairPay">
+                <p>{{ detailData.repairPay.payType==1?'微信':detailData.repairPay.payType==2?'支付宝':'银行' }}</p>
+                <ImagePreviewGroup>
+                  <Image :width="80" v-for="item in [detailData.repairPay.payImg]" :key="item" :src="item"></Image>
+                </ImagePreviewGroup>
+              </div> 
+            </DescriptionsItem>
+            <DescriptionsItem label="取回方式" v-if="detailData.customerAddress"> {{detailData.customerAddress.getType==0?'前台取回':`快递寄回 ${detailData.customerAddress.getTrackingNum}`}}</DescriptionsItem>
+            <DescriptionsItem label="收件信息" :span="2" v-if="detailData.customerAddress"> {{detailData.customerAddress.getAddrName}} {{ detailData.customerAddress.getAddrName }}{{ detailData.customerAddress.getAddress }} </DescriptionsItem>
+          </Descriptions>
+          <Descriptions title="单据下载" :column="3">
+            <DescriptionsItem label="维修记录" :span="3"> 
+              <div class="link">
+                <a v-for="(item,index) in ['www.baidusss.com','www.baidudd.com']" :key="index" :href="item" target="_blank">
+                  <span v-if="index !== 0">、</span>
+                  {{ item }}
+                </a>
+              </div>  
+            </DescriptionsItem>
+            <DescriptionsItem label="维修工单" :span="3"> 
+              <div class="link">
+                <a :href="'www.baidudd.com'" target="_blank">2022101200001维修工单.pdf </a>
+              </div>  
+            </DescriptionsItem>
+          </Descriptions>
+          <Descriptions title="客户评价" :column="3" v-if="detailData.orderReceivingVo">
+            <DescriptionsItem label="评价内容" > {{ detailData.orderReceivingVo.userName }} </DescriptionsItem>
+            <DescriptionsItem label="评分"> {{ detailData.orderReceivingVo.userName }} </DescriptionsItem>
+          </Descriptions>
+          <Descriptions title="备注" :column="3" v-if="detailData.repairerVo">
+            <DescriptionsItem label="备注内容"> {{ detailData.repairerVo.remark }} </DescriptionsItem>
+          </Descriptions>
+        </div>
+      </div>
+      <div class="content_right">
+        <Timeline>
+          <TimelineItem v-for="(item,indexs) in detailData.priceList" :color="indexs == 0 ?'red':'green'" :key="indexs">
+            <div class="timeItem">
+              <div class="name">
+                <span>维修完毕</span>
+                <a-button style="margin-left:50px" @click="handleSubmit"> 接单 </a-button>
+              </div>
+              <div class="status">罗*升 完成工单 <span>10-15 12:30</span></div>
+              <div class="itemText">前台取回 / 快递寄回  SF151315352892</div>
+              <div class="itemText">检测结论: 外壳有轻微划痕</div>
+              <div class="itemText">所需备件: 镜头x2、电池x1</div>
+              <div class="itemText">机器外观: 外壳有轻微划痕</div>
+              <div class="iamgeList">
+                <ImagePreviewGroup>
+                  <Image :width="80" v-for="item in [demopng,logo,demopng]" :key="item" :src="item"></Image>
+                </ImagePreviewGroup>
+              </div>
+            </div>
+          </TimelineItem>
+        </Timeline>
+      </div>
+    </div>
+    <div class="bottom_but">
+      <a-button type="primary" @click="goBack">
+        接单
+      </a-button>
+      <a-button type="primary" @click="goBack">
+        {{ t('common.back') }}
+      </a-button>
+    </div>
+  </div>
+</template>
+<script lang="ts" setup>
+import { ref, onMounted, reactive } from 'vue';
+import { useI18n } from '/@/hooks/web/useI18n';
+import { useRouter } from 'vue-router';
+import { useMessage } from '/@/hooks/web/useMessage';
+import { detail } from '/@/api/spares';
+import { detailResult } from '/@/api/spares/model';
+import logo from '/@/assets/images/grey-logo.png';
+import demopng from '/@/assets/images/demo.png';
+import { BasicTable, useTable, BasicColumn, TableImg, } from '/@/components/Table';
+import { Timeline, TimelineItem, Descriptions, DescriptionsItem, Image, ImagePreviewGroup } from 'ant-design-vue';
+const router = useRouter();
+const { createMessage } = useMessage();
+const { t } = useI18n();
+const repairId = ref<string|string[]>(router.currentRoute.value.params.id || '0')
+const detailData = ref<detailResult>({
+  customer:null,
+  customerAddress:null,
+  repairerVo:null,
+  RepairTestVo:null,
+  repairRegisterVo:null,
+  orderReceivingVo:null,
+  repairPay:null,
+  priceList:[],
+});
+onMounted(() => {
+  getData();
+});
+const dataSource = reactive([
+  {
+    id:1,
+    price:265,
+    name:'00',
+    count:2,
+    total:152
+  },{
+    id:2,
+    price:265,
+    name:'00',
+    count:2,
+    total:152
+  },{
+    id:3,
+    price:null,
+    name:'',
+    count:'合计(元)',
+    total:304
+  }
+])
+const columns: BasicColumn[] = [
+  {
+        title: '备件名称',
+        dataIndex: 'name',
+        width: 180,
+  },{
+        title: '单价(元)',
+        dataIndex: 'price',
+        width: 180,
+  },{
+        title: '数量',
+        dataIndex: 'count',
+        width: 180,
+  },{
+        title: '小计(元)',
+        dataIndex: 'total',
+        width: 180,
+  },
+]
+async function getData() {
+  detailData.value = await detail({repairId:repairId.value})
+  console.log('repairId',detailData.value)
+}
+function goBack() {
+  router.go(-1);
+}
+function handleSubmit() {
+  createMessage.success(t('common.optSuccess'));
+}
+const [registerTable] = useTable({
+  dataSource,
+  columns,
+  showIndexColumn:false,
+  rowKey:'id', 
+  bordered: true,
+  canResize: false,
+});
+// import { unref } from 'vue';
+
+// const { currentRoute, replace } = useRouter();
+
+// const { params, query } = unref(currentRoute);
+// const { path, _redirect_type = 'path' } = params;
+</script>
+<style lang="less" scoped>
+.detailPage {
+  margin: 20px;
+  padding: 20px;
+  background-color: #fff;
+  .topButton {
+    text-align: right;
+  }
+  .content {
+    width: 100%;
+    display: flex;
+    justify-content: space-between;
+
+    &_right {
+      width: 300px;
+      padding: 40px 20px;
+    }
+    &_left {
+      width: calc(100% - 300px);
+    }
+  }
+  .bottom_but{
+    text-align: center;
+    button{
+      margin: 20px;
+    }
+  }
+}
+</style>

+ 246 - 0
src/views/spares/index.vue

@@ -0,0 +1,246 @@
+<template>
+  <PageWrapper contentBackground>
+    <template #footer>
+      <a-tabs v-model:activeKey="tableType" @change="changeTable">
+        <a-tab-pane :key="0" :tab="t('routes.spares.tableType.0')" />
+        <a-tab-pane :key="1" :tab="t('routes.spares.tableType.21')" />
+        <a-tab-pane :key="2" :tab="t('routes.spares.tableType.23')" />
+      </a-tabs></template
+    >
+    <div class="desc-wrap-BasicTable">
+    <BasicTable @register="registerTable">
+      <template #toolbar>
+        <a-button type="primary" @click="handleOrder" v-if="getCheckPerm('invoice-export')"> 录单</a-button>
+      </template>
+      <template #action="{ record }">
+        <TableAction
+          stopButtonPropagation
+          :actions="[
+            {
+              label: '详情',
+              color: 'error',
+              onClick: handleDetail.bind(null, record),
+            },
+            {
+              label: '备件回收',
+              onClick: handleRecover.bind(null, record),
+            },
+          ]"
+        />
+      </template>
+    </BasicTable>
+    <recoveryModal @update="reload" @register="registerRecovery" />
+    <!-- ifShow: getCheckPerm('device-out') && !Boolean(record.outType), -->
+  </div>
+</PageWrapper>
+</template>
+<script lang="ts">
+import { defineComponent, onMounted, ref } from 'vue';
+import { PageWrapper } from '/@/components/Page';
+import {
+  BasicTable,
+  useTable,
+  TableAction,
+  BasicColumn,
+  TableImg,
+  FormProps,
+} from '/@/components/Table';
+import { Tabs } from 'ant-design-vue';
+import { operateSceneList } from '/@/api/operate';
+import { useI18n } from '/@/hooks/web/useI18n';
+import { usePermissionStore } from '/@/store/modules/permission';
+import recoveryModal from './recoveryModal.vue';
+import { useModal } from '/@/components/Modal';
+import { useRouter } from 'vue-router'
+import { saleOrderList } from '/@/api/spares';
+export default defineComponent({
+  components: {
+    BasicTable,
+    TableAction,
+    TableImg,
+    recoveryModal,
+    PageWrapper,
+    [Tabs.name]: Tabs,
+    [Tabs.TabPane.name]: Tabs.TabPane,
+  },
+  setup() {
+    const { t } = useI18n();
+    const permissionStore = usePermissionStore();
+    const router = useRouter()
+    const { getCheckPerm } = permissionStore;
+    const tableType = ref<Recordable>(0); //0看看 、1看见、2深时
+    onMounted(() => {
+      // console.log(router.currentRoute.value.params.id);
+    });
+    const columns: BasicColumn[] = [
+      {
+        title: '报修日期',
+        dataIndex: 'createTime',
+        width: 180,
+      },
+      {
+        title: '客户名称',
+        dataIndex: 'operationType',
+        width: 80,
+        customRender: ({ record }) => {
+          return t(`routes.equity.operation.${record.operationType || 0}`);
+        },
+      },
+      {
+        title: '产品类型',
+        dataIndex: 'cameraType',
+        width: 80,
+        customRender: ({ record }) => {
+          return t(`routes.equity.operation.${record.cameraType || 0}`);
+        },
+      },
+      {
+        title: '产品SN码',
+        dataIndex: 'cameraSnCode',
+        width: 100,
+      },
+      {
+        title: '故障描述',
+        dataIndex: 'faultMsg',
+        width: 100,
+      },
+      {
+        title: '送修方式',
+        dataIndex: 'sendType',
+        width: 100,
+        customRender: ({ record }) => {
+          return record.sendType == 0 ? '前台送修' : '快递寄送';
+        },
+      },
+      {
+        title: '快递单号',
+        dataIndex: 'sendTrackingNum',
+        width: 100,
+      },
+      {
+        title: '状态',
+        dataIndex: 'status',
+        width: 100,
+      },
+      {
+        title: '工单号',
+        dataIndex: 'repairId',
+        width: 100,
+      },
+      {
+        title: t('common.operating'),
+        dataIndex: 'action',
+        slots: { customRender: 'action' },
+        ifShow: true,
+        fixed: 'right',
+        flag: 'ACTION',
+        width: 120,
+      },
+    ];
+    const searchForm: Partial<FormProps> = {
+      labelWidth: 120,
+      autoAdvancedLine: 1,
+      actionColOptions: {
+        span: 24,
+      },
+      schemas: [
+        {
+          field: 'customerName',
+          component: 'Input',
+          label: '客户名称',
+          colProps: {
+            xl: 7,
+            xxl: 7,
+          },
+        },
+        {
+          field: 'cameraSnCode',
+          component: 'Input',
+          label: t('routes.device.snCode'),
+          colProps: {
+            xl: 7,
+            xxl: 7,
+          },
+        },
+        {
+          field: 'trackingNum',
+          component: 'Input',
+          label: '快递单号',
+          colProps: {
+            xl: 7,
+            xxl: 7,
+          },
+        },
+        {
+          field: 'timeList',
+          label: '报修日期',
+          component: 'RangePicker',
+          componentProps: {
+            maxLength: 100,
+            format: 'YYYY-MM-DD',
+            valueFormat: 'YYYY-MM-DD',
+            showTime: true,
+          },
+          colProps: {
+            xl: 7,
+            xxl: 7,
+          },
+        },
+      ],
+    };
+    const [registerRecovery, { openModal }] = useModal();
+    const [registerTable, { reload }] = useTable({
+      api: saleOrderList,
+      columns: columns,
+      useSearchForm: true,
+      searchInfo: { type: tableType },
+      formConfig: searchForm,
+      showTableSetting: true,
+      showIndexColumn: false,
+      fetchSetting: {
+        pageField: 'pageNum',
+        sizeField: 'pageSize',
+        listField: 'list',
+        totalField: 'total',
+      },
+      canResize: false,
+    });
+    async function handleDetail(record: Recordable) {
+      console.log('record', record);
+      router.push({path:`detail/${record.id||'20230215174919387'}`})
+    }
+    async function handleRecover(record: Recordable) {
+      openModal(true, {
+        ...record,
+      });
+    }
+    function handleOrder() {
+      openModal(true);
+    }
+    function changeTable(val: string) {
+      tableType.value = val;
+      reload();
+    }
+    return {
+      registerTable,
+      reload,
+      t,
+      tableType,
+      changeTable,
+      handleOrder,
+      getCheckPerm,
+      handleDetail,
+      handleRecover,
+      registerRecovery,
+    };
+  },
+});
+</script>
+<style lang="less" scoped>
+.desc-wrap-BasicTable {
+  background-color: #f0f2f5;
+  .vben-basic-table-form-container {
+    padding: 0;
+  }
+}
+</style>

+ 134 - 0
src/views/spares/list.vue

@@ -0,0 +1,134 @@
+<template>
+  <div>
+    <BasicTable @register="registerTable">
+      <template #toolbar>
+        <a-button type="primary" @click="handleRole" v-if="getCheckPerm('role-add')"> 新增备件类型 </a-button>
+        <a-button type="primary" @click="handleIn" v-if="getCheckPerm('role-add')"> 备件入库 </a-button>
+        <a-button type="primary" @click="handleCreate" v-if="getCheckPerm('role-add')"> 入库记录 </a-button>
+      </template>
+      <template #action="{ record }">
+        <TableAction  v-if="record.id != 1"
+          :actions="[
+            {
+              label: t('common.edit'),
+              ifShow:getCheckPerm('role-update'),
+              onClick: handleRole.bind(null, record),
+            },
+          ]"
+        />
+      </template>
+    </BasicTable>
+    <RoleDrawer @register="registerTableModal" @success="handleSuccess" />
+    <SpareModal @register="register" @update="reload" />
+    <SpareInModal @register="registerIn" @update="reload" />
+  </div>
+</template>
+<script lang="ts">
+  import { defineComponent, nextTick } from 'vue';
+  import { useI18n } from '/@/hooks/web/useI18n';
+  import { BasicTable, useTable, TableAction } from '/@/components/Table';
+  import { partList } from '/@/api/spares';//deleteRoleApi
+  import RoleDrawer from './RoleDrawer.vue';
+  import SpareModal from './spareModal.vue';
+  import SpareInModal from './spareInModal.vue';
+  import { useModal } from '/@/components/Modal';
+  import { usePermissionStore } from '/@/store/modules/permission';
+  // import { useUserStore } from '/@/store/modules/user';
+
+  import { columns, searchFormSchema } from './role.data';
+
+  export default defineComponent({
+    name: 'RoleManagement',
+    components: { BasicTable, TableAction,  RoleDrawer, SpareModal, SpareInModal },// 
+    setup() {
+      const { t } = useI18n();
+      const [registerTableModal, { openModal:openTableModal }] = useModal();
+      const [register, { openModal }] = useModal();
+      const [registerIn, { openModal:openInModal }] = useModal();
+      const permissionStore = usePermissionStore();
+      const { getCheckPerm } = permissionStore;
+      // const userStore = useUserStore();
+      // const { getCheckRole } = userStore;
+      const [registerTable, { reload }] = useTable({
+        title: t('routes.system.roleTitle'),
+        api: partList,
+        columns,
+        formConfig: {
+          labelWidth: 120,
+          schemas: searchFormSchema,
+        },
+        useSearchForm: true,
+        showTableSetting: true,
+        bordered: true,
+        showIndexColumn: false,
+        canResize: true,
+        fetchSetting: {
+          pageField: 'pageNum',
+          sizeField: 'pageSize',
+          listField: 'list',
+          totalField: 'total',
+        },
+        actionColumn: {
+          // ifShow: getCheckRole('super'),
+          width: 110,
+          title: t('common.operating'),
+          dataIndex: 'action',
+          slots: { customRender: 'action' },
+          fixed: undefined,
+        },
+      });
+
+      function handleCreate() {
+        openTableModal(true, {
+          isUpdate: false,
+        });
+      }
+
+      function handleRole(record: Recordable) {
+        openModal(true, record);
+      }
+
+      function handleIn(record: Recordable) {
+        openInModal(true, record);
+      }
+
+      function handleEdit(record: Recordable) {
+        openDrawer(true, {
+          record,
+          isUpdate: true,
+        });
+      }
+
+      async function handleDelete(record: Recordable) {
+        try {
+          console.log('roleId', [record.roleId]);
+          // const result = await deleteRoleApi({id:record.id});
+          // console.log('result', result);
+          nextTick(reload);
+        } catch (error) {
+          console.log('error', error);
+        }
+      }
+
+      function handleSuccess() {
+        reload();
+      }
+
+      return {
+        registerTable,
+        registerTableModal,
+        handleCreate,
+        handleEdit,
+        handleDelete,
+        handleSuccess,
+        handleRole,
+        handleIn,
+        register,
+        registerIn,
+        reload,
+        getCheckPerm,
+        t,
+      };
+    },
+  });
+</script>

+ 206 - 0
src/views/spares/orderEntryModal.vue

@@ -0,0 +1,206 @@
+<template>
+  <BasicModal
+    v-bind="$attrs"
+    @register="register"
+    title="备注"
+    :okText="okText"
+    @cancel="resetFields"
+    :confirmLoading="loading"
+    @ok="handleSubmit"
+  >
+    <div class="pt-2px pr-3px">
+      <BasicForm @register="registerForm">
+        <template #text="{ model, field }">
+          {{ model[field]  }}
+        </template>
+      </BasicForm>
+    </div>
+  </BasicModal>
+</template>
+<script lang="ts">
+  import { defineComponent, nextTick, onMounted, reactive, ref } from 'vue';
+  import { BasicModal, useModalInner } from '/@/components/Modal';
+  import { BasicForm, FormSchema, useForm } from '/@/components/Form/index';
+  import { useMessage } from '/@/hooks/web/useMessage';
+  import { InvoiceRegister } from '/@/api/order';
+  import { useI18n } from '/@/hooks/web/useI18n';
+  import { uploadApi } from '/@/api/product/index';
+
+  const { t } = useI18n();
+  export default defineComponent({
+    components: { BasicModal, BasicForm },
+    props: {
+      userData: { type: Object },
+    },
+    emits: ['update', 'register'],
+    setup(props, { emit }) {
+      const fileFlow = reactive({
+        file:null,
+        type:2,//2-普通发票,3-专用发票
+      })
+      const okText = ref('发送')
+      const loading = ref(false)
+      const { createMessage } = useMessage();
+      const schemas: FormSchema[] = [
+          {
+            field: 'id',
+            component: 'Input',
+            show:false,
+            label: '发票编号',
+            required: true,
+          },
+          {
+            field: 'email',
+            component: 'Input',
+            label: '邮箱',
+            slot: 'text',
+            ifShow:fileFlow.type == 2,
+            componentProps: {
+              maxLength: 50,
+            },
+            colProps: {
+              span: 24,
+            },
+          },{
+            field: 'invoiceNum',
+            component: 'Input',
+            label: '发票编号',
+            required: true,
+            componentProps: {
+              maxLength: 50,
+            },
+            colProps: {
+              span: 24,
+            },
+          },{
+            field: 'type',
+            component: 'CheckboxGroup',
+            label: '故障类型',
+            required: true,
+            componentProps: {
+              options:['Apple', 'Pear', 'Orange'],
+              name:'checkboxgroup',
+              maxLength: 50,
+            },
+            colProps: {
+              span: 24,
+            },
+          },{
+            field: 'file',
+            component: 'Upload',
+            label: '电子发票',
+            ifShow:fileFlow.type == 2,
+            required: true,
+            rules: [{ required: true, message: t('common.uploadMessge') }],
+            itemProps: {
+              validateTrigger: 'blur',
+            },
+            componentProps: {
+              api: uploadApi,
+              fileFlow:true,
+              maxNumber: 1,
+              maxSize: 1000,
+              accept: ['jpeg','jpg','png'],
+              afterFetch: function (data) {
+                console.log('url',data)
+                // Reflect.set(data, 'url', data.file);
+                fileFlow.file = data.file
+                return data;
+              },
+            },
+
+            colProps: {
+              span: 22,
+            },
+          },{
+            field: 'invoiceNum',
+            component: 'Input',
+            label: '发票编号',
+            required: true,
+            componentProps: {
+              maxLength: 50,
+            },
+            colProps: {
+              span: 24,
+            },
+          },{
+            field: 'shipNum',
+            component: 'Input',
+            ifShow:fileFlow.type == 3,
+            label: '快递单号',
+            componentProps: {
+              maxLength: 50,
+            },
+            required: true,
+            colProps: {
+              span: 24,
+            },
+          },
+      ];
+
+      const [registerForm, { validate, resetFields, setFieldsValue, updateSchema }] = useForm({
+        labelWidth: 120,
+        schemas:schemas,
+        showActionButtonGroup: false,
+        actionColOptions: {
+          span: 24,
+        },
+      });
+      onMounted(() => {});
+      let addListFunc = () => {};
+      const [register, { closeModal }] = useModalInner((data) => {
+        data && onDataReceive(data);
+      });
+      function onDataReceive(data) {
+        resetFields();
+        fileFlow.type = data.type
+        setFieldsValue(data);
+        okText.value = fileFlow.type == 2?'发送':'确认'
+        updateSchema([
+          {field: 'shipNum',ifShow:fileFlow.type == 3,},
+          {field: 'email',ifShow:fileFlow.type == 2,},
+          {field: 'file',ifShow:fileFlow.type == 2,},
+        ])
+      }
+      const handleSubmit = async () => {
+        loading.value = true
+        try {
+          const params = await validate();
+          const apiData = {
+            data:fileFlow.type == 3?{
+              id:params.id,
+              invoiceNum:params.invoiceNum,
+              shipNum:params.shipNum,
+            }:{
+              id:params.id,
+              invoiceNum:params.invoiceNum,
+              file:fileFlow.file,
+            // file:params.file[0],
+            }
+          }
+          console.log('res', apiData,params);
+          await InvoiceRegister(apiData);
+          closeModal();
+          resetFields();
+          createMessage.success(t('common.optSuccess'));
+          emit('update');
+          loading.value = false
+        } catch (error) {
+          loading.value = false
+          console.log('not passing', error);
+        }
+      };
+      return {
+        register,
+        registerForm,
+        fileFlow,
+        handleSubmit,
+        addListFunc,
+        resetFields,
+        okText,
+        loading,
+        t,
+      };
+    },
+  });
+</script>

+ 102 - 0
src/views/spares/recoveryModal.vue

@@ -0,0 +1,102 @@
+<template>
+  <BasicModal
+    v-bind="$attrs"
+    @register="register"
+    title="设备入库"
+    @visible-change="handleVisibleChange"
+    @cancel="resetFields"
+    @ok="handleSubmit"
+    :min-height="0"
+  >
+    <div class="pt-2px pr-3px recoverPage">
+      <div class="form_item"><div class="item_lable">维修单号:</div>2022101100001</div>
+      <div class="form_item"><div class="item_lable">维修工程师:</div>林繁茂</div>
+      <BasicTable @register="registerTable" />
+    </div>
+  </BasicModal>
+</template>
+<script lang="ts">
+import { defineComponent, ref, onMounted, reactive } from 'vue';
+import { BasicModal, useModalInner } from '/@/components/Modal';
+import { BasicTable, useTable, BasicColumn } from '/@/components/Table';
+import { useMessage } from '/@/hooks/web/useMessage';
+import { supplyOrderList } from '/@/api/spares';
+import { useI18n } from '/@/hooks/web/useI18n';
+const { t } = useI18n();
+export default defineComponent({
+  components: { BasicModal, BasicTable },
+  props: {
+    userData: { type: Object },
+  },
+  emits: ['reload'],
+  setup(_, { emit }) {
+    const modelRef = ref({});
+    const columns: BasicColumn[] = [
+      {
+        title: '备件编号',
+        dataIndex: 'repairId',
+        width: 100,
+      },{
+        title: '备件名称',
+        dataIndex: 'customerName',
+        width: 100,
+      },{
+        title: '数量',
+        dataIndex: 'sendTrackingNum',
+        width: 100,
+      },
+    ]
+    const [registerTable, { setLoading }] = useTable({
+      // api: supplyOrderList,
+      columns,
+    });
+    const { createMessage, createConfirm } = useMessage();
+    onMounted(() => {});
+    let addListFunc = () => {};
+    const [register, { closeModal }] = useModalInner((data) => {
+      // console.log(data);
+      data && onDataReceive(data);
+    });
+
+    function onDataReceive(data) {
+      modelRef.value = data;
+    }
+    function handleVisibleChange() {}
+
+    const handleSubmit = async () => {
+      createConfirm({
+          iconType: 'warning',
+          title: () => h('span', '温馨提示'),
+          content: '删除设备后需要重新入库<br/>确定删除吗?',
+          onOk: async () => {
+            await cameraIn({id:modelRef.id})
+            createMessage.success(t('common.optSuccess'));
+            closeModal();
+            emit('reload');
+          },
+        });
+    };
+    return {
+      register,
+      model: modelRef,
+      registerTable,
+      handleVisibleChange,
+      handleSubmit,
+      addListFunc,
+      t,
+    };
+  },
+});
+</script>
+<style lang="less" scoped>
+.recoverPage{
+  .form_item{
+    line-height: 30px;
+    .item_lable{
+      display: inline-block;
+      width: 100px;
+      text-align: right;
+    }
+  }
+}
+</style>

+ 129 - 0
src/views/spares/remarksModal.vue

@@ -0,0 +1,129 @@
+<template>
+  <BasicModal
+    v-bind="$attrs"
+    @register="register"
+    title="备注"
+    @cancel="resetFields"
+    :confirmLoading="loading"
+    @ok="handleSubmit"
+  >
+    <div class="pt-2px pr-3px">
+      <BasicForm @register="registerForm">
+        <template #text="{ model, field }">
+          {{ model[field]  }}
+        </template>
+      </BasicForm>
+    </div>
+  </BasicModal>
+</template>
+<script lang="ts">
+  import { defineComponent, nextTick, onMounted, reactive, ref } from 'vue';
+  import { BasicModal, useModalInner } from '/@/components/Modal';
+  import { BasicForm, FormSchema, useForm } from '/@/components/Form/index';
+  import { useMessage } from '/@/hooks/web/useMessage';
+  import { updateRemark } from '/@/api/spares';
+  import { useI18n } from '/@/hooks/web/useI18n';
+  import { uploadApi } from '/@/api/product/index';
+
+  const { t } = useI18n();
+  export default defineComponent({
+    components: { BasicModal, BasicForm },
+    props: {
+      userData: { type: Object },
+    },
+    emits: ['update', 'register'],
+    setup(props, { emit }) {
+      const fileFlow = reactive({
+        file:null,
+        type:2,//2-普通发票,3-专用发票
+      })
+      const loading = ref(false)
+      const { createMessage } = useMessage();
+      const schemas: FormSchema[] = [
+          {
+            field: 'id',
+            component: 'Input',
+            show:false,
+            label: '发票编号',
+          },
+          {
+            field: 'repairId',
+            component: 'Input',
+            label: '维修单号',
+            slot: 'text',
+            colProps: {
+              span: 24,
+            },
+          },{
+            field: 'deviceType',
+            component: 'Input',
+            label: '设备信息',
+            slot: 'text',
+            colProps: {
+              span: 24,
+            },
+          },{
+            field: 'remark',
+            component: 'InputTextArea',
+            required: true,
+            label: '备注',
+            componentProps: {
+              maxLength: 500,
+              rows:4,
+              placeholder: '请填写备注',
+            },
+            colProps: {
+              span: 22,
+            },
+        },
+      ];
+
+      const [registerForm, { validate, resetFields, setFieldsValue, updateSchema }] = useForm({
+        labelWidth: 120,
+        schemas:schemas,
+        showActionButtonGroup: false,
+        actionColOptions: {
+          span: 24,
+        },
+      });
+      onMounted(() => {});
+      let addListFunc = () => {};
+      const [register, { closeModal }] = useModalInner((data) => {
+        data && onDataReceive(data);
+      });
+      function onDataReceive(data) {
+        resetFields();
+        fileFlow.type = data.type
+        setFieldsValue({
+          ...data,
+          deviceType:t(`routes.scene.tableType.${data.cameraType || 0}`) +' '+ data.cameraSnCode
+        });
+      }
+      const handleSubmit = async () => {
+        loading.value = true
+        try {
+          const params = await validate();
+          await updateRemark(params);
+          closeModal();
+          resetFields();
+          createMessage.success(t('common.optSuccess'));
+          emit('update');
+          loading.value = false
+        } catch (error) {
+          loading.value = false
+          console.log('not passing', error);
+        }
+      };
+      return {
+        register,
+        registerForm,
+        fileFlow,
+        handleSubmit,
+        addListFunc,
+        resetFields,
+        loading,
+        t,
+      };
+    },
+  });
+</script>

+ 199 - 0
src/views/spares/role.data.ts

@@ -0,0 +1,199 @@
+import { BasicColumn } from '/@/components/Table';
+import { FormSchema } from '/@/components/Table';
+import { h } from 'vue';
+import { Switch } from 'ant-design-vue';
+import { Time } from '/@/components/Time';
+import { partAddOrUpdate } from '/@/api/spares'
+import { saveRoleApi } from '/@/api/sys/system';
+import { usePermissionStore } from '/@/store/modules/permission';
+  // import { ListAllCompanyApi } from '/@/api/corporation/list';
+import { useMessage } from '/@/hooks/web/useMessage';
+import { useI18n } from '/@/hooks/web/useI18n';
+  const { t } = useI18n();
+  const { createMessage } = useMessage();
+  const permissionStore = usePermissionStore();
+  const { getCheckPerm } = permissionStore;
+
+export const columns: BasicColumn[] = [
+  // {
+  //   title: 'ID',
+  //   dataIndex: 'id',
+  //   width: 80,
+  // },
+  {
+    title: '备件编号',
+    dataIndex: 'partId',
+    width: 200,
+  },
+  {
+    title: '名称',
+    dataIndex: 'partName',
+    width: 180,
+  },
+  {
+    title: '单位',
+    dataIndex: 'partUnit',
+    width: 100,
+  },
+  {
+    title: '单价(元)',
+    dataIndex: 'partPrice',
+    width: 100,
+  },
+  {
+    title: '库存(件)',
+    dataIndex: 'partStock',
+    width: 180,
+  },
+  {
+    title: '状态',
+    dataIndex: 'status',
+    width: 80,
+    ifShow:getCheckPerm('role-enable'),
+    customRender: ({ record }) => {
+      if (!Reflect.has(record, 'pendingStatus')) {
+        record.pendingStatus = false;
+      }
+      return h(Switch, {
+        checked: record.status === 0,
+        checkedChildren: '启用',
+        unCheckedChildren: '禁用',
+        loading: false,
+        onChange: async (checked: boolean) => {
+          record.pendingStatus = true;
+          const newStatus = checked ? 0 : 1;
+          Reflect.set(record, 'status', newStatus);
+          await partAddOrUpdate({ ...record, isShow: newStatus });
+          createMessage.success(t('common.optSuccess'));
+          // reload()
+        },
+      });
+    },
+  },
+  // {
+  //   title: t('routes.system.remarks'),
+  //   dataIndex: 'remark',
+  // },
+];
+
+export const searchFormSchema: FormSchema[] = [
+  {
+    field: 'type',
+    label: '设备类型',
+    component: 'Select',
+    componentProps: {
+      options: [
+        {
+          label: t('routes.scene.tableType.0'),
+          value: '0',
+        },{
+          label: t('routes.scene.tableType.1'),
+          value: '1',
+        },{
+          label: t('routes.scene.tableType.2'),
+          value: '2',
+        },
+      ],
+    },
+    colProps: {
+      xl: 6,
+      xxl: 6,
+    },
+  },{
+    field: 'roleName',
+    label: '备件名称',
+    component: 'Input',
+    componentProps: {
+      maxLength: 100,
+    },
+    colProps: { span: 8 },
+  },
+];
+
+export const formSchema: FormSchema[] = [
+  {
+    label: '',
+    field: 'id',
+    component: 'Input',
+    show:false,
+  },
+  {
+    field: 'roleName',
+    label: t('common.roleNameText'),
+    required: true,
+    component: 'Input',
+    componentProps:{
+      maxLength: 15,
+    }
+  },
+  // {
+  //   field: 'companyId',
+  //   label: t('routes.staff.companyId'),
+  //   component: 'ApiSelect',
+  //   // required: true,
+  //   itemProps: {
+  //     validateTrigger: 'blur',
+  //   },
+  //   componentProps: {
+  //     api: getAllRoleList,
+  //     resultField: 'data',
+  //     labelField: 'roleName',
+  //     valueField: 'id',
+  //     immediate: true,
+  //     onChange: function () {
+  //       // Reflect.set(modalRecord, 'shippingName', opt.label);
+  //     },
+  //     params: {
+  //       page: 1,
+  //       limit: 1000,
+  //     },
+  //     required: true,
+  //   },
+  // },
+  // {
+  //   field: 'canShow',
+  //   label: '状态',
+  //   component: 'RadioButtonGroup',
+  //   defaultValue: 0,
+  //   componentProps: {
+  //     options: [
+  //       { label: '启用', value: 0 },
+  //       { label: '停用', value: 1 },
+  //     ],
+  //   },
+  // },
+  // {
+  //   field: 'status',
+  //   label: t('routes.system.isPlatformRole'),
+  //   component: 'RadioButtonGroup',
+  //   defaultValue: 1,
+  //   componentProps: {
+  //     options: [
+  //       { label: '启用', value: 1 },
+  //       { label: '禁用', value: 0 },
+  //     ],
+  //   },
+  // },
+  {
+    label: t('routes.system.description'),
+    field: 'description',
+    component: 'InputTextArea',
+    required: true,
+    componentProps:{
+      rows:4,
+      maxLength: 200,
+    }
+  },
+  // {
+  //   label: ' ',
+  //   field: 'menuIdList',
+  //   slot: 'menu',
+  //   component: 'Input',
+  // },
+  // {
+  //   label: ' ',
+  //   field: 'deptIdList',
+  //   slot: 'dept',
+  //   component: 'Input',
+  // },
+];

+ 150 - 0
src/views/spares/spareInModal.vue

@@ -0,0 +1,150 @@
+<template>
+  <BasicModal
+    v-bind="$attrs"
+    @register="register"
+    title="备件入库"
+    @cancel="resetFields"
+    :confirmLoading="loading"
+    @ok="handleSubmit"
+  >
+    <div class="pt-2px pr-3px">
+      <BasicForm @register="registerForm">
+        <template #text="{ model, field }">
+          {{ model[field]  }}
+        </template>
+      </BasicForm>
+    </div>
+  </BasicModal>
+</template>
+<script lang="ts">
+  import { defineComponent, nextTick, onMounted, reactive, ref } from 'vue';
+  import { BasicModal, useModalInner } from '/@/components/Modal';
+  import { BasicForm, FormSchema, useForm } from '/@/components/Form/index';
+  import { useMessage } from '/@/hooks/web/useMessage';
+  import { partAllList, partInStock } from '/@/api/spares';
+  import { useI18n } from '/@/hooks/web/useI18n';
+  import { uploadApi } from '/@/api/product/index';
+
+  const { t } = useI18n();
+  export default defineComponent({
+    components: { BasicModal, BasicForm },
+    props: {
+      userData: { type: Object },
+    },
+    emits: ['update', 'register'],
+    setup(props, { emit }) {
+      const fileFlow = reactive({
+        file:null,
+        type:2,//2-普通发票,3-专用发票
+      })
+      const loading = ref(false)
+      const { createMessage } = useMessage();
+      const schemas: FormSchema[] = [
+        {
+            field: 'cameraType',
+            label: '设备类型',
+            component: 'Select',
+            required:true,
+            componentProps: {
+              options: [
+                {
+                  label: t('routes.scene.tableType.0'),
+                  value: '0',
+                },{
+                  label: t('routes.scene.tableType.1'),
+                  value: '1',
+                },{
+                  label: t('routes.scene.tableType.2'),
+                  value: '2',
+                },
+              ],
+              onChange:(value)=>{
+                updateSchema([{
+                    field: 'partId',
+                    componentProps:{
+                      disabled:false,
+                      params: {
+                        cameraType: value,
+                      },
+                    },
+                  }])
+            }
+            },
+            colProps: {
+              span: 18,
+            },
+          },{
+            field: 'partId',
+            label: '备件类型',
+            component: 'ApiSelect',
+            required:true,
+            componentProps: {
+              api:partAllList,
+              numberToString: true,
+              labelField: 'partName',
+              valueField: 'partId',
+              immediate: false,
+              disabled:true,
+            },
+            colProps: {
+              span: 18,
+            },
+          },{
+            field: 'partStock',
+            component: 'Input',
+            label: '入库数量',
+            required:true,
+            componentProps:{
+              maxLength: 50,
+            },
+            colProps: {
+              span: 18,
+            },
+          },
+      ];
+
+      const [registerForm, { validate, resetFields, setFieldsValue, updateSchema }] = useForm({
+        labelWidth: 120,
+        schemas:schemas,
+        showActionButtonGroup: false,
+        actionColOptions: {
+          span: 24,
+        },
+      });
+      onMounted(() => {});
+      let addListFunc = () => {};
+      const [register, { closeModal }] = useModalInner((data) => {
+        data && onDataReceive(data);
+      });
+      function onDataReceive(data) {
+        resetFields();
+        fileFlow.type = data.type
+      }
+      const handleSubmit = async () => {
+        loading.value = true
+        try {
+          const params = await validate();
+          await partInStock(params);
+          closeModal();
+          resetFields();
+          createMessage.success(t('common.optSuccess'));
+          emit('update');
+          loading.value = false
+        } catch (error) {
+          loading.value = false
+          console.log('not passing', error);
+        }
+      };
+      return {
+        register,
+        registerForm,
+        fileFlow,
+        handleSubmit,
+        addListFunc,
+        resetFields,
+        loading,
+        t,
+      };
+    },
+  });
+</script>

+ 163 - 0
src/views/spares/spareModal.vue

@@ -0,0 +1,163 @@
+<template>
+  <BasicModal
+    v-bind="$attrs"
+    @register="register"
+    :title="fileFlow.title"
+    @cancel="resetFields"
+    :confirmLoading="loading"
+    @ok="handleSubmit"
+  >
+    <div class="pt-2px pr-3px">
+      <BasicForm @register="registerForm">
+        <template #text="{ model, field }">
+          {{ model[field]  }}
+        </template>
+      </BasicForm>
+    </div>
+  </BasicModal>
+</template>
+<script lang="ts">
+  import { defineComponent, nextTick, onMounted, reactive, ref } from 'vue';
+  import { BasicModal, useModalInner } from '/@/components/Modal';
+  import { BasicForm, FormSchema, useForm } from '/@/components/Form/index';
+  import { useMessage } from '/@/hooks/web/useMessage';
+  import { InvoiceRegister } from '/@/api/order';
+  import { useI18n } from '/@/hooks/web/useI18n';
+  import { partAddOrUpdate } from '/@/api/spares';
+
+  const { t } = useI18n();
+  export default defineComponent({
+    components: { BasicModal, BasicForm },
+    props: {
+      userData: { type: Object },
+    },
+    emits: ['update', 'register'],
+    setup(props, { emit }) {
+      const fileFlow = reactive({
+        file:null,
+        title:'新增备件类型',
+        type:2,//2-普通发票,3-专用发票
+      })
+      const loading = ref(false)
+      const { createMessage } = useMessage();
+      const schemas: FormSchema[] = [
+        {
+            field: 'cameraType',
+            label: '设备类型',
+            component: 'Select',
+            required:true,
+            componentProps: {
+              options: [
+                {
+                  label: t('routes.scene.tableType.0'),
+                  value: 0,
+                },{
+                  label: t('routes.scene.tableType.1'),
+                  value: 1,
+                },{
+                  label: t('routes.scene.tableType.2'),
+                  value: 2,
+                },
+              ],
+            },
+            colProps: {
+              span: 18,
+            },
+          },{
+            field: 'partNum',
+            component: 'Input',
+            label: '备件编号',
+            required:true,
+            componentProps:{
+              maxLength: 50,
+            },
+            colProps: {
+              span: 18,
+            },
+          },{
+            field: 'partName',
+            component: 'Input',
+            label: '备件名称',
+            required:true,
+            componentProps:{
+              maxLength: 50,
+            },
+            colProps: {
+              span: 18,
+            },
+          },{
+            field: 'partUnit',
+            component: 'Input',
+            label: '单位',
+            required:true,
+            componentProps:{
+              maxLength: 50,
+            },
+            colProps: {
+              span: 18,
+            },
+          },{
+            field: 'partPrice',
+            component: 'Input',
+            label: '单价(元)',
+            required:true,
+            componentProps:{
+              maxLength: 50,
+            },
+            colProps: {
+              span: 18,
+            },
+          }
+      ];
+
+      const [registerForm, { validate, resetFields, setFieldsValue, updateSchema }] = useForm({
+        labelWidth: 120,
+        schemas:schemas,
+        showActionButtonGroup: false,
+        actionColOptions: {
+          span: 24,
+        },
+      });
+      onMounted(() => {});
+      let addListFunc = () => {};
+      const [register, { closeModal }] = useModalInner((data) => {
+        resetFields();
+        data && onDataReceive(data);
+      });
+      function onDataReceive(data) {
+        console.log('data',data)
+        fileFlow.title ='新增备件类型'
+        if(data.partId){
+          setFieldsValue(data)
+          fileFlow.title ='编辑备件类型'
+        }
+        fileFlow.type = data.type
+      }
+      const handleSubmit = async () => {
+        loading.value = true
+        try {
+          const params = await validate();
+          await partAddOrUpdate(params);
+          closeModal();
+          resetFields();
+          createMessage.success(t('common.optSuccess'));
+          emit('update');
+          loading.value = false
+        } catch (error) {
+          loading.value = false
+          console.log('not passing', error);
+        }
+      };
+      return {
+        register,
+        registerForm,
+        fileFlow,
+        handleSubmit,
+        addListFunc,
+        resetFields,
+        loading,
+        t,
+      };
+    },
+  });
+</script>

+ 241 - 0
src/views/staff/adddetailsModal.vue

@@ -0,0 +1,241 @@
+<template>
+  <BasicModal
+    v-bind="$attrs"
+    @cancel="resetFields"
+    @register="register"
+    :title="title"
+    :min-height="350"
+    height="500"
+    @ok="handleOk"
+  >
+    <BasicForm @register="registerForm" />
+  </BasicModal>
+</template>
+<script lang="ts">
+  import { defineComponent, ref, computed } from 'vue';
+  import { checkUserApi, saveApi, updateApi, getRoleListByParam } from '/@/api/staff/list'; //roleLIstApi
+  import { BasicModal, useModalInner } from '/@/components/Modal';
+  import { BasicForm, FormSchema, useForm } from '/@/components/Form/index';
+  import { useI18n } from '/@/hooks/web/useI18n';
+  import { useMessage } from '/@/hooks/web/useMessage';
+  import { useUserStore } from '/@/store/modules/user';
+  const { t } = useI18n();
+  export default defineComponent({
+    components: { BasicModal, BasicForm },
+    props: {
+      userData: { type: Object },
+    },
+    emits: ['ok'],
+    setup(_, context) {
+      const modelRef = ref({});
+      const userStore = useUserStore();
+      const userinfo = computed(() => userStore.getUserInfo);
+      const preventAutoFill = ref(true);
+      const { companyId } = userinfo.value;
+      const schemas: FormSchema[] = [
+        {
+          field: 'nickName',
+          component: 'Input',
+          label: '姓名',
+          colProps: {
+            span: 20,
+          },
+          componentProps: {
+            maxLength: 15,
+          },
+          rules: [
+            {
+              required: true,
+              // @ts-ignore
+              validator: async (rule, value) => {
+                var reg_tel = /^[a-zA-Z\u4e00-\u9fa5]+$/;
+                // var reg = /\S+@\S+\.\S+/;
+                if (!value) {
+                  return Promise.reject('请输入姓名');
+                }
+                if (!reg_tel.test(value)) {
+                  /* eslint-disable-next-line */
+                  return Promise.reject('请输入正确的姓名');
+                }
+                return Promise.resolve();
+              },
+              trigger: 'change',
+            },
+          ],
+        },
+        {
+          field: 'roleId',
+          component: 'ApiSelect',
+          label: '角色',
+          required: true,
+          itemProps: {
+            validateTrigger: 'blur',
+          },
+          colProps: {
+            span: 20,
+          },
+          defaultValue: '',
+          componentProps: {
+            api: getRoleListByParam,
+            labelField: 'roleName',
+            valueField: 'id',
+            params: {
+              type: 1,
+            },
+          },
+        },
+        {
+          field: 'userName',
+          component: 'Input',
+          label: '账号',
+          required: true,
+          colProps: {
+            span: 20,
+          },
+          componentProps:{
+            placeholder:"请输入字母或数字组合",
+            autoComplete:'off',
+            maxLength: 15,
+            readonly:preventAutoFill.value
+          },
+          rules: [
+            {
+              required: true,
+              // @ts-ignore
+              validator: async (rule, value) => {
+                console.log('value',value)
+                const regPos = /^[\da-z]+$/i; // 非中文
+                if (!value) {
+                  return Promise.reject('请输入字母或数字组合');
+                }
+                if (!regPos.test(value)) {
+                  /* eslint-disable-next-line */
+                  return Promise.reject('请输入字母或数字组合');
+                }
+                return Promise.resolve();
+              },
+              trigger: 'change',
+            },
+          ],
+        },
+        {
+          field: 'password',
+          component: 'StrengthMeter',
+          label: '密码',
+          required: true,
+          colProps: {
+            span: 20,
+          },
+          rules: [
+            {
+              required: true,
+              // @ts-ignore
+              validator: async (rule, value) => {
+                console.log('value',value)
+                const regPos = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[a-zA-Z\d]{8,}$/; // 非中文
+                if (!value) {
+                  return Promise.reject('请输入8-16位数字、字母大小写组合');
+                }
+                if (!regPos.test(value)) {
+                  /* eslint-disable-next-line */
+                  return Promise.reject('请输入8-16位数字、字母大小写组合');
+                }
+                return Promise.resolve();
+              },
+              trigger: 'change',
+            },
+          ],
+          componentProps:{
+            placeholder:"请输入8-16位数字、字母大小写组合",
+            maxLength: 16,
+            readonly:preventAutoFill.value,
+            minLength: 8,
+            autoComplete:'off',
+          }
+        },
+        {
+          field: 'id',
+          component: 'Input',
+          label: 'id',
+          show: false,
+        },
+      ];
+      const title = ref('新增账号');
+      const { createMessage } = useMessage();
+      const [registerForm, { setFieldsValue, validate, updateSchema, resetFields }] = useForm({
+        labelWidth: 120,
+        schemas,
+        showActionButtonGroup: false,
+        actionColOptions: {
+          span: 24,
+        },
+      });
+
+      const [register, { closeModal }] = useModalInner((data) => {
+        data && onDataReceive(data);
+      });
+      function onDataReceive(data) {
+        console.log('onDataReceive',data)
+        // 方式1;
+        setTimeout(()=>{
+          preventAutoFill.value = false
+          updateSchema([{
+            field: 'userName',
+            componentProps:{
+              readonly:false,
+            }
+          },{
+            field: 'password',
+            componentProps:{
+              readonly:false,
+            }
+          }])
+          // updateSchema(schemas);
+        },500)
+      }
+      function companyIdChange(companyId) {
+        // resetFields(['permList'])
+        updateSchema([
+          {
+            field: 'permList',
+            componentProps: {
+              params: {
+                companyId: companyId,
+              },
+            },
+          },
+        ]);
+        setFieldsValue({
+          permList: [],
+        });
+      }
+      async function handleOk() {
+        let data = await validate();
+        const requestApi = data.id ? updateApi : saveApi;
+        let res = await requestApi({
+          ...data,
+          // userName: data.phone,
+          phone: data.phone,
+          nickName: data.nickName,
+          roleId: data.roleId,
+          id: data.id,
+        });
+        context && context.emit('ok', res);
+        createMessage.success(t('common.optSuccess'));
+        closeModal();
+        resetFields();
+      }
+
+      return {
+        register,
+        title,
+        preventAutoFill,
+        schemas,
+        registerForm,
+        modelRef,
+        handleOk,
+        resetFields,
+      };
+    },
+  });
+</script>

+ 5 - 0
src/views/staff/category.vue

@@ -0,0 +1,5 @@
+<template>
+  <div> 设备管理 </div>
+</template>
+
+<script lang="ts" setup></script>

+ 78 - 0
src/views/staff/clean.vue

@@ -0,0 +1,78 @@
+<template>
+  <div class="p-4">
+    <BasicForm @register="register" @submit="handleSubmit" />
+  </div>
+</template>
+<script lang="ts">
+  import { defineComponent } from 'vue';
+  import { BasicForm, FormSchema, useForm } from '/@/components/Form/index';
+  import { useMessage } from '/@/hooks/web/useMessage';
+  import { useI18n } from '/@/hooks/web/useI18n';
+  import { clean } from '/@/api/staff/list';
+  // import { useCopyToClipboard } from '/@/hooks/web/useCopyToClipboard';
+  export default defineComponent({
+    components: {
+      BasicForm,
+    },
+    setup() {
+      const { t } = useI18n();
+      const { createMessage } = useMessage();
+      const schemas: FormSchema[] = [
+        {
+          field: 'userName',
+          component: 'Input',
+          label: '手机号',
+          colProps: {
+            span: 8,
+          },
+          componentProps: {
+            // type:"number"
+          },
+          rules: [
+            {
+              required: true,
+              // @ts-ignore
+              validator: async (rule, value) => {
+                var reg_tel =
+                  /^(13[0-9]|14[01456879]|15[0-35-9]|16[2567]|17[0-8]|18[0-9]|19[0-35-9])\d{8}$/;
+                // var reg = /\S+@\S+\.\S+/;
+                if (!value) {
+                  /* eslint-disable-next-line */
+
+                  return Promise.reject(t('common.phone'));
+                }
+                if (!reg_tel.test(value)) {
+                  /* eslint-disable-next-line */
+                  return Promise.reject(t('common.phoneError'));
+                }
+                return Promise.resolve();
+              },
+              trigger: 'change',
+            },
+          ],
+        },
+      ];
+      const [register, { setProps, validate, resetFields }] = useForm({
+        labelWidth: 120,
+        schemas,
+        actionColOptions: {
+          span: 4,
+        },
+        submitButtonOptions: {
+          text: '清除状态',
+        },
+      });
+      async function handleSubmit() {
+        let data = await validate();
+        await clean({ userName: data.userName }); //
+        createMessage.success(t('common.optSuccess'));
+        setTimeout(() => resetFields(), 500);
+      }
+      return {
+        handleSubmit,
+        setProps,
+        register,
+      };
+    },
+  });
+</script>

+ 124 - 0
src/views/staff/delListModal.vue

@@ -0,0 +1,124 @@
+<template>
+  <BasicModal
+    v-bind="$attrs"
+    @cancel="resetFields"
+    @register="register"
+    title="删除员工"
+    @ok="handleOk"
+  >
+    <div class="pt-3px pr-3px">
+      <BasicForm @register="registerForm">
+        <template #tips>
+          <div>请将员工的数据迁移后,再删除员工(删除员工后,权益将进行解绑)</div>
+        </template>
+      </BasicForm>
+    </div>
+  </BasicModal>
+</template>
+<script lang="ts">
+  import { defineComponent, ref, unref } from 'vue';
+  import { delApi } from '/@/api/staff/list';
+  import { BasicModal, useModalInner } from '/@/components/Modal';
+  import { BasicForm, FormSchema, useForm } from '/@/components/Form/index';
+  import { useI18n } from '/@/hooks/web/useI18n';
+  import { useMessage } from '/@/hooks/web/useMessage';
+  const { t } = useI18n();
+
+  export default defineComponent({
+    components: { BasicModal, BasicForm },
+    props: {
+      userData: { type: Object },
+    },
+    emits: ['reload'],
+    setup(_, context) {
+      const modelRef = ref({
+        toUserId: 0,
+        userId: 0,
+      });
+      const options = ref([]);
+      const { createMessage } = useMessage();
+      const schemas: FormSchema[] = [
+        {
+          field: 'toUserPhone',
+          component: 'Select',
+          label: '选择迁移的员工',
+          required: true,
+          itemProps: {
+            validateTrigger: 'blur',
+          },
+          colProps: {
+            span: 22,
+          },
+          componentProps: {
+            options: options.value,
+          },
+        },
+        {
+          field: 'tips',
+          component: 'Select',
+          slot: 'tips',
+          label: ' ',
+        },
+      ];
+      const [registerForm, { validate, resetFields, updateSchema }] = useForm({
+        labelWidth: 120,
+        schemas,
+        showActionButtonGroup: false,
+        actionColOptions: {
+          span: 24,
+        },
+      });
+
+      const [register, { closeModal }] = useModalInner((data) => {
+        data && onDataReceive(data);
+      });
+      function onDataReceive(data) {
+        data = unref(data);
+        modelRef.value.userId = data.id;
+        console.log('onDataReceive', data);
+        // 方式1;
+        if (data.option) {
+          options.value = data.option.map((ele) => {
+            ele.label = ele.nickName;
+            ele.value = ele.phone;
+            return ele;
+          });
+          updateSchema({
+            field: 'toUserPhone',
+            component: 'Select',
+            label: '选择迁移的员工',
+            required: true,
+            itemProps: {
+              validateTrigger: 'blur',
+            },
+            colProps: {
+              span: 22,
+            },
+            componentProps: {
+              options: data.option,
+              onChange: function (value, item) {
+                modelRef.value.toUserId = item.id;
+                console.log('onChange', value, item);
+              },
+            },
+          });
+        }
+      }
+
+      async function handleOk() {
+        let data = await validate();
+        let res = await delApi({
+          toUserPhone: data.toUserPhone,
+          userId: modelRef.value.userId,
+          toUserId: modelRef.value.toUserId,
+        });
+        context && context.emit('reload', res);
+        createMessage.success(t('common.optSuccess'));
+        closeModal();
+        resetFields();
+      }
+
+      return { options, register, registerForm, model: modelRef, handleOk, resetFields };
+    },
+  });
+</script>

+ 180 - 0
src/views/staff/detailsModal.vue

@@ -0,0 +1,180 @@
+<template>
+  <BasicModal
+    v-bind="$attrs"
+    @cancel="resetFields"
+    @register="register"
+    :title="title"
+    :min-height="200"
+    height="500"
+    @ok="handleOk"
+  >
+    <BasicForm @register="registerForm" />
+  </BasicModal>
+</template>
+<script lang="ts">
+  import { defineComponent, ref, computed } from 'vue';
+  import { checkUserApi, saveApi, updateApi, getRoleListByParam } from '/@/api/staff/list'; //roleLIstApi
+  import { BasicModal, useModalInner } from '/@/components/Modal';
+  import { BasicForm, FormSchema, useForm } from '/@/components/Form/index';
+  import { useI18n } from '/@/hooks/web/useI18n';
+  import { useMessage } from '/@/hooks/web/useMessage';
+  import { useUserStore } from '/@/store/modules/user';
+  const { t } = useI18n();
+  export default defineComponent({
+    components: { BasicModal, BasicForm },
+    props: {
+      userData: { type: Object },
+    },
+    emits: ['ok'],
+    setup(_, context) {
+      const modelRef = ref({});
+      const userStore = useUserStore();
+      const userinfo = computed(() => userStore.getUserInfo);
+      const preventAutoFill = ref(true);
+      const { companyId } = userinfo.value;
+      console.log('companyId', companyId);
+      const permListOptions = computed(() => {
+        return [
+          {
+            label: '带看',
+            value: '1',
+          },
+          {
+            label: '拍摄',
+            value: '2',
+          },
+        ];
+      });
+      const schemas: FormSchema[] = [
+        {
+          field: 'nickName',
+          component: 'Input',
+          label: '姓名',
+          colProps: {
+            span: 20,
+          },
+          componentProps: {
+            maxLength: 15,
+          },
+          rules: [
+            {
+              required: true,
+              // @ts-ignore
+              validator: async (rule, value) => {
+                var reg_tel = /^[a-zA-Z\u4e00-\u9fa5]+$/;
+                // var reg = /\S+@\S+\.\S+/;
+                if (!value) {
+                  return Promise.reject('请输入姓名');
+                }
+                if (!reg_tel.test(value)) {
+                  /* eslint-disable-next-line */
+                  return Promise.reject('请输入正确的姓名');
+                }
+                return Promise.resolve();
+              },
+              trigger: 'change',
+            },
+          ],
+        },
+        {
+          field: 'roleId',
+          component: 'ApiSelect',
+          label: '角色',
+          required: true,
+          itemProps: {
+            validateTrigger: 'blur',
+          },
+          colProps: {
+            span: 20,
+          },
+          componentProps: {
+            api: getRoleListByParam,
+              labelField: 'roleName',
+              valueField: 'id',
+              params: {
+                type: 1,
+              },
+            }
+        },
+        {
+          field: 'id',
+          component: 'Input',
+          label: 'id',
+          show: false,
+        },
+      ];
+      const title = ref('编辑账号');
+      const { createMessage } = useMessage();
+      const [registerForm, { setFieldsValue, validate, updateSchema, resetFields }] = useForm({
+        labelWidth: 120,
+        schemas,
+        showActionButtonGroup: false,
+        actionColOptions: {
+          span: 24,
+        },
+      });
+
+      const [register, { closeModal }] = useModalInner((data) => {
+        data && onDataReceive(data);
+      });
+      function onDataReceive(data) {
+        // 方式1;
+        console.log('useModalInner', data);
+        // preventAutoFill.value = false
+        setTimeout(()=>{
+            console.log('useModalInner', data);
+            setFieldsValue({
+            ...data,
+            userName:'',
+            password:'',
+            roleId: data.roleId != 2 ? data.roleId : '',
+          });
+        },200)
+      }
+      function companyIdChange(companyId) {
+        // resetFields(['permList'])
+        updateSchema([
+          {
+            field: 'permList',
+            componentProps: {
+              params: {
+                companyId: companyId,
+              },
+            },
+          },
+        ]);
+        setFieldsValue({
+          permList: [],
+        });
+      }
+      async function handleOk() {
+        let data = await validate();
+        const requestApi = data.id ? updateApi : saveApi;
+        let res = await requestApi({
+          ...data,
+          // userName: data.phone,
+          phone: data.phone,
+          nickName: data.nickName,
+          roleId: data.roleId,
+          id: data.id,
+        });
+        context && context.emit('ok', res);
+        createMessage.success(t('common.optSuccess'));
+        closeModal();
+        resetFields();
+      }
+
+      return {
+        register,
+        title,
+        preventAutoFill,
+        schemas,
+        registerForm,
+        modelRef,
+        handleOk,
+        resetFields,
+        permListOptions,
+      };
+    },
+  });
+</script>

+ 349 - 0
src/views/staff/list.vue

@@ -0,0 +1,349 @@
+<template>
+  <div class="p-4">
+    <BasicTable @register="registerTable">
+      <template #toolbar>
+        <a-button
+          type="primary"
+          @click="handleCreate"
+           v-if="getCheckPerm('sysuser-add')"
+          >新增账号</a-button
+        >
+      </template>
+      <template #role="{ record }">
+        {{ renderRoleType(record.role) }}
+      </template>
+      <template #status="{ record }">
+        {{ renderStatus(record.status) }}
+      </template>
+      <template #createTime="{ record }">
+        <Time :value="record.createTime" mode="datetime" />
+      </template>
+      <!-- , -->
+      <template #action="{ record }">
+        <TableAction
+          :actions="[
+            {
+              label: '编辑',
+              ifShow:getCheckPerm('sysuser-update'),
+              onClick: handleEdit.bind(null, record),
+            },
+            {
+              color: 'warning',
+              label: t('routes.staff.setpaswd'),
+              ifShow:getCheckPerm('sysuser-repassword'),
+              onClick: handleOpenModal.bind(null, record),
+            },
+            {
+              label: '删除',
+              color: 'error',
+              ifShow:getCheckPerm('sysuser-delete'),
+              popConfirm: {
+                title: '是否确认删除',
+                confirm: handleDelete.bind(null, record),
+              },
+            },
+          ]"
+        />
+      </template>
+    </BasicTable>
+    <DetailsModal @register="registerDetail" @ok="reload" />
+    <SetpaswordModal @register="register" @reload="reload" />
+    <addDetailsModal @register="registerAddDetail" @ok="reload" />
+  </div>
+</template>
+<script lang="ts">
+  import { defineComponent, computed, onMounted, ref, h } from 'vue';
+  import { BasicTable, useTable, BasicColumn, FormProps, TableAction } from '/@/components/Table';
+  import { useMessage } from '/@/hooks/web/useMessage';
+  import { useModal } from '/@/components/Modal';
+  import { uploadApi } from '/@/api/sys/upload';
+  import SetpaswordModal from './setpaswordModal.vue';
+  import DetailsModal from './detailsModal.vue';
+  import addDetailsModal from './adddetailsModal.vue';
+  import { Switch } from 'ant-design-vue';
+  // import DelListModal from './delListModal.vue';
+  import { Alert } from 'ant-design-vue';
+  // import { h } from 'vue';
+  import { ListApi, delApi, preDelApi, updateApi, getNumByStaff } from '/@/api/staff/list';
+  import { useI18n } from '/@/hooks/web/useI18n';
+  // import { useCopyToClipboard } from '/@/hooks/web/useCopyToClipboard';
+  import { RoleEnum } from '/@/enums/roleEnum';
+  import { useGo } from '/@/hooks/web/usePage';
+  import { Time } from '/@/components/Time';
+  import { useUserStore } from '/@/store/modules/user';
+  import { usePermissionStore } from '/@/store/modules/permission';
+  import { getRoleListByParam } from '/@/api/staff/list'; //roleLIstApi
+  export default defineComponent({
+    components: {
+      BasicTable,
+      TableAction,
+      Time,
+      SetpaswordModal,
+      DetailsModal,
+      addDetailsModal,
+      Alert,
+    },
+    setup() {
+      const [register, { openModal }] = useModal();
+      const surplusSubNum = ref({
+        lookNum: 0,
+        shotNum: 0,
+      });
+      const [registerDetail, { openModal: openDetaileModal }] = useModal();
+      const [registerAddDetail, { openModal: openAddDetaileModal }] = useModal();
+      const [registerDelList, { openModal: openDelListeModal }] = useModal();
+      const { createConfirm, createMessage } = useMessage();
+      const userStore = useUserStore();
+      const permissionStore = usePermissionStore();
+      const { getCheckPerm } = permissionStore;
+      const roleList = computed(() => userStore.getRoleList);
+      console.log('getRoleList', roleList);
+      const go = useGo();
+      const { t } = useI18n();
+      onMounted(() => {
+        // getNumByStaffData();
+      });
+
+      const columns: BasicColumn[] = [
+        // {
+        //   title: 'ID',
+        //   dataIndex: 'id',
+        //   fixed: 'left',
+        //   width: 60,
+        // },
+        // {
+        //   title: t('routes.staff.deptName'),
+        //   dataIndex: 'companyName',
+        //   width: 160,
+        // },
+        // {
+        //   title: t('routes.staff.userName'),
+        //   dataIndex: 'userName',
+        //   width: 80,
+        // },
+        {
+          title: t('routes.staff.userName'),
+          dataIndex: 'nickName',
+          width: 120,
+        },
+        {
+          title: t('routes.staff.userId'),
+          dataIndex: 'userName',
+          width: 150,
+        },
+        // {
+        //   title: '手机',
+        //   dataIndex: 'phone',
+        //   width: 160,
+        // },
+        {
+          title: t('common.roleName'),
+          dataIndex: 'roleName',
+          width: 100,
+        },
+
+        {
+          title: '创建人',
+          dataIndex: 'createUserName',
+          width: 80,
+        },
+
+        {
+          title: t('routes.staff.createTime'),
+          dataIndex: 'createTime',
+          slots: { customRender: 'createTime' },
+          width: 130,
+        },{
+        title: '状态',
+        dataIndex: 'status',
+        ifShow:getCheckPerm('sysuser-enable'),
+        width: 80,
+        customRender: ({ record }) => {
+          if (!Reflect.has(record, 'status')) {
+            record.pendingStatus = false;
+          }
+          return h(Switch, {
+            checked: record.status == 1 ? true : false,
+            checkedChildren: '启用',
+            unCheckedChildren: '禁用',
+            loading: false,
+            onChange: async (checked: boolean) => {
+              record.pendingStatus = true;
+              const newStatus = checked?1:0;
+              await updateApi({...record,status:newStatus});
+              Reflect.set(record, 'status', checked);
+              createMessage.success(t('common.optSuccess'));
+              reload()
+            },
+          });
+        },
+      },
+        {
+          title: '操作',
+          dataIndex: '',
+          // ifShow: !getCheckRole('tourist'),
+          slots: { customRender: 'action' },
+          fixed: 'right',
+          width: 140,
+        },
+      ];
+
+      const searchForm: Partial<FormProps> = {
+        labelWidth: 100,
+        schemas: [
+          {
+            field: 'nickName',
+            label: t('routes.staff.userName'),
+            component: 'Input',
+            componentProps: {
+              maxLength: 15,
+            },
+            colProps: {
+              xl: 6,
+              xxl: 6,
+            },
+          },
+          {
+            field: 'userName',
+            label: '账号',
+            component: 'Input',
+            componentProps: {
+              maxLength: 15,
+            },
+            colProps: {
+              xl: 6,
+              xxl: 6,
+            },
+          },{
+            field: 'roleId',
+            label: '角色',
+            component: 'ApiSelect',
+            componentProps: {
+              api: getRoleListByParam,
+              labelField: 'roleName',
+              valueField: 'id',
+            },
+            colProps: {
+              xl: 6,
+              xxl: 6,
+            },
+          },
+        ],
+      };
+
+      const [registerTable, { reload }] = useTable({
+        title: t('routes.staff.staffList'),
+        api: ListApi,
+        columns: columns,
+        useSearchForm: true,
+        formConfig: searchForm,
+        showTableSetting: true,
+        tableSetting: { fullScreen: true },
+        showIndexColumn: false,
+        canResize: true,
+        rowKey: 'id',
+        fetchSetting: {
+          pageField: 'pageNum',
+          sizeField: 'pageSize',
+          listField: 'list',
+          totalField: 'total',
+        },
+        pagination: { pageSize: 20 },
+        afterFetch: (T) => {
+          return T;
+        },
+        bordered: true,
+        sortFn: (sortInfo) => {
+          let order = sortInfo.order && sortInfo.order.replace('end', '');
+          return { ...sortInfo, sidx: sortInfo.field, order: order };
+        },
+      });
+
+      function renderRoleType(type: number): string {
+        switch (type) {
+          case 0:
+            return t('routes.staff.roleType.0');
+          case 1:
+            return t('routes.staff.roleType.1');
+          default:
+            return '';
+        }
+      }
+      function renderStatus(type: number): string {
+        switch (type) {
+          case 1:
+            return t('common.normal');
+          case 0:
+            return t('common.unNormal');
+          default:
+            return '';
+        }
+      }
+      function handleOpenModal(record: Recordable) {
+        openModal(true, record);
+      }
+      function handleCreate() {
+        openAddDetaileModal(true,{});
+      }
+      function handleEdit(record: Recordable) {
+        openDetaileModal(true, {
+          ...record,
+          phone:record.userName,
+        });
+      }
+      function getNumByStaffData() {
+        getNumByStaff({}).then((res) => {
+          surplusSubNum.value.lookNum = res.lookNum;
+          surplusSubNum.value.shotNum = res.shotNum;
+        });
+      }
+      async function handleDelete(record) {
+        let check = await preDelApi(record.id); //
+        if (Array.isArray(check)) {
+          return openDelListeModal(true, {
+            ...record,
+            option: check,
+          });
+        }
+        createMessage.success(t('common.optSuccess'));
+        reload();
+        // handDelconfirm(record);
+      }
+      function handDelconfirm(record) {
+        createConfirm({
+          iconType: 'warning',
+          title: '警告',
+          content: `此操作将对${record.userName}进行删除, 是否继续?`,
+          onOk: async () => {
+            await delApi({ userId: record.id });
+            reload();
+          },
+        });
+      }
+      return {
+        registerTable,
+        registerDetail,
+        registerDelList,
+        registerAddDetail,
+        openDelListeModal,
+        createMessage,
+        handDelconfirm,
+        t,
+        reload,
+        go,
+        renderRoleType,
+        renderStatus,
+        handleCreate,
+        handleOpenModal,
+        register,
+        handleEdit,
+        handleDelete,
+        uploadApi: uploadApi as any,
+        RoleEnum,
+        surplusSubNum,
+        getCheckPerm,
+        getNumByStaffData,
+      };
+    },
+  });
+</script>

+ 125 - 0
src/views/staff/setpaswordModal.vue

@@ -0,0 +1,125 @@
+<template>
+  <BasicModal
+    v-bind="$attrs"
+    @register="register"
+    @ok="handSubmit"
+    :title="t('routes.staff.updateBtn')"
+    :min-height="100"
+    @visible-change="handleVisibleChange"
+    @cancel="resetFields"
+  >
+    <div class="pt-3px pr-3px">
+      <BasicForm @register="registerForm" />
+      <span style="margin-left:100px;color:#9f9c9c">请设置新密码,设置后,管理员可使用新密码登录。</span>
+    </div>
+  </BasicModal>
+</template>
+<script lang="ts">
+  import { useI18n } from '/@/hooks/web/useI18n';
+  import { encodeStr } from '/@/utils/encodeUtil';
+  import { defineComponent, nextTick, ref } from 'vue';
+  import { BasicModal, useModalInner } from '/@/components/Modal';
+  import { BasicForm, FormSchema, useForm } from '/@/components/Form/index';
+  import { updatePasswordApi } from '/@/api/staff/list';
+  import { useMessage } from '/@/hooks/web/useMessage';
+
+  const { createMessage } = useMessage();
+
+  export default defineComponent({
+    components: { BasicModal, BasicForm },
+    props: {
+      userData: { type: Object },
+    },
+    setup(props) {
+      const { t } = useI18n();
+      const preventAutoFill = ref(true);
+      const schemas: FormSchema[] = [
+        {
+          field: 'id',
+          component: 'Input',
+          label: 'id',
+          show: false,
+        },
+        {
+          field: 'password',
+          component: 'StrengthMeter',
+          label: t('routes.staff.password'),
+          labelWidth: 100,
+          required: true,
+          rules: [
+            {
+              required: true,
+              // @ts-ignore
+              validator: async (rule, value) => {
+                console.log('value',value)
+                const regPos = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[a-zA-Z\d]{8,}$/; // 非中文
+                if (!value) {
+                  return Promise.reject('请输入8-16位数字、字母大小写组合');
+                }
+                if (!regPos.test(value)) {
+                  /* eslint-disable-next-line */
+                  return Promise.reject('请输入8-16位数字、字母大小写组合');
+                }
+                return Promise.resolve();
+              },
+              trigger: 'change',
+            },
+          ],
+          componentProps:{
+            placeholder:"请输入8-16位数字、字母大小写组合",
+            maxLength: 16,
+            minLength: 8,
+          },
+          colProps: { span: 18 },
+        },
+      ];
+
+      const [
+        registerForm,
+        {
+          setFieldsValue,
+          validate,
+          resetFields,
+          // setProps
+        },
+      ] = useForm({
+        labelWidth: 120,
+        schemas,
+        showActionButtonGroup: false,
+        actionColOptions: {
+          span: 24,
+        },
+      });
+
+      const [register, { closeModal }] = useModalInner((data) => {
+        data && onDataReceive(data);
+      });
+
+      function onDataReceive(data) {
+        setFieldsValue({
+          id: data.id,
+        });
+      }
+      async function handSubmit() {
+        const { id, password } = await validate();
+        await updatePasswordApi({ id, newPassword: encodeStr(window.btoa(password)) });
+        createMessage.success(t('common.optSuccess'));
+        resetFields();
+        closeModal();
+      }
+      function handleVisibleChange(v) {
+        v && props.userData && nextTick(() => onDataReceive(props.userData));
+      }
+
+      return {
+        handSubmit,
+        register,
+        t,
+        schemas,
+        registerForm,
+        handleVisibleChange,
+        resetFields,
+      };
+    },
+  });
+</script>

+ 5 - 0
src/views/staff/sorts.vue

@@ -0,0 +1,5 @@
+<template>
+  <div> 设备管理 </div>
+</template>
+
+<script lang="ts" setup></script>

+ 297 - 0
src/views/work/addAccessoryModel.vue

@@ -0,0 +1,297 @@
+<template>
+  <BasicModal
+    v-bind="$attrs"
+    @register="register"
+    :title="fileFlow.title"
+    width="700px"
+    @cancel="resetFields"
+    :confirmLoading="loading"
+    @ok="handleSubmit"
+  >
+    <div class="pt-2px pr-3px">
+      <BasicForm @register="registerForm">
+        <template #text="{ model, field }">
+          {{ model[field] }}
+        </template>
+        <template #add>
+          <Button @click="add">添加配件</Button>
+        </template>
+        <template #del="{ field }">
+          <Button @click="del(field)">删除</Button>
+        </template>
+      </BasicForm>
+    </div>
+  </BasicModal>
+</template>
+<script lang="ts">
+import { defineComponent, h, onMounted, reactive, ref } from 'vue';
+import { BasicModal, useModalInner } from '/@/components/Modal';
+import { BasicForm, FormSchema, useForm } from '/@/components/Form/index';
+import { useMessage } from '/@/hooks/web/useMessage';
+import { partAllList, faultAllList, checkRegister } from '/@/api/spares';
+import { useI18n } from '/@/hooks/web/useI18n';
+import { uploadApi } from '/@/api/product/index';
+
+const { t } = useI18n();
+export default defineComponent({
+  components: { BasicModal, BasicForm },
+  props: {
+    userData: { type: Object },
+  },
+  emits: ['update', 'register'],
+  setup(props, { emit }) {
+    const n = ref(1);
+    const fileFlow = reactive({
+      file: null,
+      title: '添加备件',
+      cameraType: 0,
+      type: 2, //2-普通发票,3-专用发票
+      faultList: [],
+    });
+    const loading = ref(false);
+    const { createMessage, createConfirm } = useMessage();
+    let schemas: FormSchema[] = [
+      {
+        field: 'repairId',
+        component: 'Input',
+        label: '维修单号',
+        slot: 'text',
+        colProps: {
+          span: 24,
+        },
+      },
+      {
+        field: 'deviceInfo',
+        component: 'Input',
+        label: '设备信息',
+        slot: 'text',
+        colProps: {
+          span: 18,
+        },
+      },
+      // {
+      //   field: 'faultIds',
+      //   component: 'CheckboxGroup',
+      //   label: '维修登记',
+      //   required: true,
+      //   componentProps: {
+      //     options: fileFlow.faultList,
+      //     maxLength: 50,
+      //   },
+      //   colProps: {
+      //     span: 18,
+      //   },
+      // },
+      {
+        field: 'checkResult',
+        component: 'InputTextArea',
+        label: '检测结果',
+        required: true,
+        componentProps: {
+          maxLength: 500,
+          rows: 3,
+          placeholder: '请描述检测后的具体故障情况。',
+        },
+        colProps: {
+          span: 18,
+        },
+      },
+      {
+        field: 'checkImg',
+        component: 'Upload',
+        label: '相关图片',
+        rules: [{ required: false, message: t('common.uploadMessge') }],
+        itemProps: {
+          validateTrigger: 'blur',
+        },
+        componentProps: {
+          api: uploadApi,
+          // fileFlow:true,
+          maxNumber: 5,
+          maxSize: 1000,
+          accept: ['jpeg', 'jpg', 'png'],
+          // afterFetch: function (data) {
+          //   console.log('url',data)
+          //   // Reflect.set(data, 'url', data.file);
+          //   fileFlow.file = data.file
+          //   return data;
+          // },
+        },
+
+        colProps: {
+          span: 12,
+        },
+      },
+      {
+        field: '0',
+        component: 'Input',
+        label: '',
+        labelWidth: 0,
+        colProps: {
+          span: 4,
+        },
+        slot: 'add',
+      },
+    ];
+
+    const [
+      registerForm,
+      {
+        validate,
+        resetFields,
+        setFieldsValue,
+        removeSchemaByFiled,
+        appendSchemaByField,
+        updateSchema,
+      },
+    ] = useForm({
+      labelWidth: 100,
+      schemas: schemas,
+      showActionButtonGroup: false,
+      actionColOptions: {
+        span: 24,
+      },
+    });
+    onMounted(() => {});
+    // getFaultList();
+    let addListFunc = () => {};
+    const [register, { closeModal }] = useModalInner((data) => {
+      data && onDataReceive(data);
+    });
+    async function getFaultList() {
+      const res = await faultAllList({});
+      fileFlow.faultList = res.map((item) => {
+        return {
+          ...item,
+          label: item.faultMsg,
+          value: item.faultId,
+        };
+      });
+    }
+
+    function onDataReceive(data) {
+      resetFields();
+      // updateSchema({
+      //   field: 'faultIds',
+      //   componentProps: {
+      //     options: fileFlow.faultList,
+      //   },
+      // });
+      fileFlow.type = data.type;
+      fileFlow.cameraType = data.cameraType;
+      setFieldsValue({
+        ...data,
+        deviceInfo: t(`routes.scene.tableType.${data.cameraType}`) + data.cameraSnCode,
+      });
+    }
+    function del(field) {
+      removeSchemaByFiled([`deviceType${field}`, `device_${field}`, `${field}`]);
+      n.value--;
+    }
+    function add() {
+      let list = addSchemas(n.value);
+      list.map((ele) => {
+        appendSchemaByField(ele, '');
+      });
+      n.value++;
+    }
+    function addSchemas(number) {
+      let parentList: FormSchema[] = [
+        {
+          field: 'part_id' + number,
+          label: '备件' + number,
+          component: 'ApiSelect',
+          colProps: {
+            span: 12,
+          },
+          required: true,
+          componentProps: {
+            maxLength: 15,
+            api: partAllList,
+            labelField: 'partName',
+            valueField: 'partId',
+            params: {
+              cameraType: fileFlow.cameraType,
+            },
+            showSearch: true,
+            optionFilterProp: 'label',
+          },
+        },
+        {
+          field: 'partCount' + number,
+          label: '数量',
+          component: 'InputNumber',
+          required: true,
+          defaultValue: 1,
+          labelWidth: 50,
+          componentProps: {
+            max: 999,
+            min: 1,
+          },
+          colProps: {
+            span: 6,
+          },
+        },
+        {
+          field: number.toString(),
+          component: 'Input',
+          label: '',
+          labelWidth: 0,
+          colProps: {
+            span: 6,
+          },
+          slot: 'del',
+        },
+      ];
+      return parentList;
+    }
+    const handleSubmit = async () => {
+      loading.value = true;
+      try {
+        createConfirm({
+          iconType: 'warning',
+          title: () => h('span', '温馨提示'),
+          content: '确定要添加备件吗?',
+          onOk: async () => {
+            const params = await validate();
+            let partList = [];
+            for (let s = 1; s <= n.value; s++) {
+              console.log('params', params, partList);
+              if (params[`part_id${s}`]) {
+                partList.push({
+                  part_id: params[`part_id${s}`],
+                  partCount: params[`partCount${s}`],
+                });
+              }
+            }
+            console.log('params', params, partList);
+            await checkRegister({
+              ...params,
+              partList,
+            });
+            loading.value = false;
+            createMessage.success(t('common.optSuccess'));
+            closeModal();
+            emit('update');
+          },
+        });
+      } catch (error) {
+        loading.value = false;
+        console.log('not passing', error);
+      }
+    };
+    return {
+      register,
+      registerForm,
+      fileFlow,
+      handleSubmit,
+      addListFunc,
+      resetFields,
+      loading,
+      t,
+      del,
+      add,
+    };
+  },
+});
+</script>

+ 211 - 0
src/views/work/aftermarket.vue

@@ -0,0 +1,211 @@
+<template>
+  <PageWrapper contentBackground>
+    <template #footer>
+      <a-tabs v-model:activeKey="tableType" @change="changeTable">
+        <a-tab-pane :key="0" :tab="t('routes.scene.tableType.0')" />
+        <a-tab-pane :key="1" :tab="t('routes.scene.tableType.1')" />
+      </a-tabs></template
+    >
+    <div class="desc-wrap-BasicTable">
+    <BasicTable @register="registerTable">
+    </BasicTable>
+  </div>
+</PageWrapper>
+</template>
+<script lang="ts">
+import { defineComponent, onMounted, ref } from 'vue';
+import { PageWrapper } from '/@/components/Page';
+import {
+  BasicTable,
+  useTable,
+  TableAction,
+  BasicColumn,
+  TableImg,
+  FormProps,
+} from '/@/components/Table';
+import { Tabs } from 'ant-design-vue';
+import { operateSceneList } from '/@/api/operate';
+import { useI18n } from '/@/hooks/web/useI18n';
+import { usePermissionStore } from '/@/store/modules/permission';
+import recoveryModal from './recoveryModal.vue';
+import { useModal } from '/@/components/Modal';
+import { useRouter } from 'vue-router'
+import { saleOrderList } from '/@/api/spares';
+export default defineComponent({
+  components: {
+    BasicTable,
+    TableAction,
+    TableImg,
+    recoveryModal,
+    PageWrapper,
+    [Tabs.name]: Tabs,
+    [Tabs.TabPane.name]: Tabs.TabPane,
+  },
+  setup() {
+    const { t } = useI18n();
+    const permissionStore = usePermissionStore();
+    const router = useRouter()
+    const { getCheckPerm } = permissionStore;
+    const tableType = ref<Recordable>(0); //0看看 、1看见、2深时
+    onMounted(() => {
+      // console.log(router.currentRoute.value.params.id);
+    });
+    const columns: BasicColumn[] = [
+      {
+        title: '报修日期',
+        dataIndex: 'createTime',
+        width: 180,
+      },
+      {
+        title: '客户名称',
+        dataIndex: 'operationType',
+        width: 80,
+        customRender: ({ record }) => {
+          return t(`routes.equity.operation.${record.operationType || 0}`);
+        },
+      },
+      {
+        title: '产品类型',
+        dataIndex: 'cameraType',
+        width: 80,
+        customRender: ({ record }) => {
+          return t(`routes.equity.operation.${record.cameraType || 0}`);
+        },
+      },
+      {
+        title: '产品SN码',
+        dataIndex: 'cameraSnCode',
+        width: 100,
+      },
+      {
+        title: '故障描述',
+        dataIndex: 'faultMsg',
+        width: 100,
+      },
+      {
+        title: '送修方式',
+        dataIndex: 'sendType',
+        width: 100,
+        customRender: ({ record }) => {
+          return record.sendType == 0 ? '前台送修' : '快递寄送';
+        },
+      },
+      {
+        title: '快递单号',
+        dataIndex: 'sendTrackingNum',
+        width: 100,
+      },
+      {
+        title: '状态',
+        dataIndex: 'status',
+        width: 100,
+      },
+      {
+        title: '工单号',
+        dataIndex: 'repairId',
+        width: 100,
+      },
+    ];
+    const searchForm: Partial<FormProps> = {
+      labelWidth: 120,
+      autoAdvancedLine: 1,
+      actionColOptions: {
+        span: 24,
+      },
+      schemas: [
+        {
+          field: 'customerName',
+          component: 'Input',
+          label: '客户名称',
+          colProps: {
+            xl: 7,
+            xxl: 7,
+          },
+        },
+        {
+          field: 'cameraSnCode',
+          component: 'Input',
+          label: t('routes.device.snCode'),
+          colProps: {
+            xl: 7,
+            xxl: 7,
+          },
+        },
+        {
+          field: 'trackingNum',
+          component: 'Input',
+          label: '快递单号',
+          colProps: {
+            xl: 7,
+            xxl: 7,
+          },
+        },
+        {
+          field: 'timeList',
+          label: '报修日期',
+          component: 'RangePicker',
+          componentProps: {
+            maxLength: 100,
+            format: 'YYYY-MM-DD',
+            valueFormat: 'YYYY-MM-DD',
+            showTime: true,
+          },
+          colProps: {
+            xl: 7,
+            xxl: 7,
+          },
+        },
+      ],
+    };
+    const [registerRecovery, { openModal }] = useModal();
+    const [registerTable, { reload }] = useTable({
+      api: saleOrderList,
+      columns: columns,
+      useSearchForm: true,
+      searchInfo: { type: tableType },
+      formConfig: searchForm,
+      showTableSetting: true,
+      showIndexColumn: false,
+      fetchSetting: {
+        pageField: 'pageNum',
+        sizeField: 'pageSize',
+        listField: 'list',
+        totalField: 'total',
+      },
+      canResize: false,
+    });
+    async function handleDetail(record: Recordable) {
+      console.log('record', record);
+      router.push({path:`detail/${record.id||'20230215174919387'}`})
+    }
+    async function handleRecover(record: Recordable) {
+      openModal(true, {
+        ...record,
+      });
+    }
+    function changeTable(val: string) {
+      tableType.value = val;
+      reload();
+    }
+    return {
+      registerTable,
+      reload,
+      t,
+      tableType,
+      changeTable,
+      getCheckPerm,
+      handleDetail,
+      handleRecover,
+      registerRecovery,
+    };
+  },
+});
+</script>
+<style lang="less" scoped>
+.desc-wrap-BasicTable {
+  background-color: #f0f2f5;
+  .vben-basic-table-form-container {
+    padding: 0;
+  }
+}
+</style>

+ 294 - 0
src/views/work/checkModel.vue

@@ -0,0 +1,294 @@
+<template>
+  <BasicModal
+    v-bind="$attrs"
+    @register="register"
+    title="检测登记"
+    width="700px"
+    @cancel="resetFields"
+    :confirmLoading="loading"
+    @ok="handleSubmit"
+  >
+    <div class="pt-2px pr-3px">
+      <BasicForm @register="registerForm">
+        <template #text="{ model, field }">
+          {{ model[field] }}
+        </template>
+        <template #add>
+          <Button @click="add">添加配件</Button>
+        </template>
+        <template #del="{ field }">
+          <Button @click="del(field)">删除</Button>
+        </template>
+      </BasicForm>
+    </div>
+  </BasicModal>
+</template>
+<script lang="ts">
+import { defineComponent, h, onMounted, reactive, ref } from 'vue';
+import { BasicModal, useModalInner } from '/@/components/Modal';
+import { BasicForm, FormSchema, useForm } from '/@/components/Form/index';
+import { useMessage } from '/@/hooks/web/useMessage';
+import { partAllList, faultAllList, checkRegister } from '/@/api/spares';
+import { useI18n } from '/@/hooks/web/useI18n';
+import { uploadApi } from '/@/api/product/index';
+
+const { t } = useI18n();
+export default defineComponent({
+  components: { BasicModal, BasicForm },
+  props: {
+    userData: { type: Object },
+  },
+  emits: ['update', 'register'],
+  setup(props, { emit }) {
+    const n = ref(1);
+    const fileFlow = reactive({
+      file: null,
+      cameraType: 0,
+      type: 2, //2-普通发票,3-专用发票
+      faultList: [],
+    });
+    const loading = ref(false);
+    const { createMessage, createConfirm } = useMessage();
+    let schemas: FormSchema[] = [
+      {
+        field: 'repairId',
+        component: 'Input',
+        label: '维修单号',
+        slot: 'text',
+        colProps: {
+          span: 24,
+        },
+      },
+      {
+        field: 'deviceInfo',
+        component: 'Input',
+        label: '设备信息',
+        slot: 'text',
+        colProps: {
+          span: 18,
+        },
+      },
+      {
+        field: 'faultIds',
+        component: 'CheckboxGroup',
+        label: '故障类型',
+        required: true,
+        componentProps: {
+          options: fileFlow.faultList,
+          maxLength: 50,
+        },
+        colProps: {
+          span: 18,
+        },
+      },
+      {
+        field: 'checkResult',
+        component: 'InputTextArea',
+        label: '检测结果',
+        required: true,
+        componentProps: {
+          maxLength: 500,
+          rows: 3,
+          placeholder: '请描述检测后的具体故障情况。',
+        },
+        colProps: {
+          span: 18,
+        },
+      },
+      {
+        field: 'checkImg',
+        component: 'Upload',
+        label: '相关图片',
+        rules: [{ required: false, message: t('common.uploadMessge') }],
+        itemProps: {
+          validateTrigger: 'blur',
+        },
+        componentProps: {
+          api: uploadApi,
+          // fileFlow:true,
+          maxNumber: 5,
+          maxSize: 1000,
+          accept: ['jpeg', 'jpg', 'png'],
+          // afterFetch: function (data) {
+          //   console.log('url',data)
+          //   // Reflect.set(data, 'url', data.file);
+          //   fileFlow.file = data.file
+          //   return data;
+          // },
+        },
+
+        colProps: {
+          span: 12,
+        },
+      },
+      {
+        field: '0',
+        component: 'Input',
+        label: '',
+        labelWidth: 0,
+        colProps: {
+          span: 4,
+        },
+        slot: 'add',
+      },
+    ];
+
+    const [
+      registerForm,
+      {
+        validate,
+        resetFields,
+        setFieldsValue,
+        removeSchemaByFiled,
+        appendSchemaByField,
+        updateSchema,
+      },
+    ] = useForm({
+      labelWidth: 100,
+      schemas: schemas,
+      showActionButtonGroup: false,
+      actionColOptions: {
+        span: 24,
+      },
+    });
+    onMounted(() => {});
+    getFaultList();
+    let addListFunc = () => {};
+    const [register, { closeModal }] = useModalInner((data) => {
+      data && onDataReceive(data);
+    });
+    async function getFaultList() {
+      const res = await faultAllList({});
+      fileFlow.faultList = res.map((item) => {
+        return {
+          ...item,
+          label: item.faultMsg,
+          value: item.faultId,
+        };
+      });
+    }
+
+    function onDataReceive(data) {
+      resetFields();
+      updateSchema({
+        field: 'faultIds',
+        componentProps: {
+          options: fileFlow.faultList,
+        },
+      });
+      fileFlow.type = data.type;
+      fileFlow.cameraType = data.cameraType;
+      setFieldsValue({
+        ...data,
+        deviceInfo: t(`routes.scene.tableType.${data.cameraType}`) + data.cameraSnCode,
+      });
+    }
+    function del(field) {
+      removeSchemaByFiled([`deviceType${field}`, `device_${field}`, `${field}`]);
+      n.value--;
+    }
+    function add() {
+      let list = addSchemas(n.value);
+      list.map((ele) => {
+        appendSchemaByField(ele, '');
+      });
+      n.value++;
+    }
+    function addSchemas(number) {
+      let parentList: FormSchema[] = [
+        {
+          field: 'part_id' + number,
+          label: '备件' + number,
+          component: 'ApiSelect',
+          colProps: {
+            span: 12,
+          },
+          required: true,
+          componentProps: {
+            maxLength: 15,
+            api: partAllList,
+            labelField: 'partName',
+            valueField: 'partId',
+            params: {
+              cameraType: fileFlow.cameraType,
+            },
+            showSearch: true,
+            optionFilterProp: 'label',
+          },
+        },
+        {
+          field: 'partCount' + number,
+          label: '数量',
+          component: 'InputNumber',
+          required: true,
+          defaultValue: 1,
+          labelWidth: 50,
+          componentProps: {
+            max: 999,
+            min: 1,
+          },
+          colProps: {
+            span: 6,
+          },
+        },
+        {
+          field: number.toString(),
+          component: 'Input',
+          label: '',
+          labelWidth: 0,
+          colProps: {
+            span: 6,
+          },
+          slot: 'del',
+        },
+      ];
+      return parentList;
+    }
+    const handleSubmit = async () => {
+      loading.value = true;
+      try {
+        // createConfirm({
+        //   iconType: 'warning',
+        //   title: () => h('span', '温馨提示'),
+        //   content: '确定要提交检测报告吗?',
+        //   onOk: async () => {
+        const params = await validate();
+        let partList = [];
+        for (let s = 1; s <= n.value; s++) {
+          console.log('params', params, partList);
+          if (params[`part_id${s}`]) {
+            partList.push({
+              part_id: params[`part_id${s}`],
+              partCount: params[`partCount${s}`],
+            });
+          }
+        }
+        console.log('params', params, partList);
+        await checkRegister({
+          ...params,
+          partList,
+        })
+        loading.value = false;
+        createMessage.success(t('common.optSuccess'));
+        closeModal();
+        emit('update');
+      } catch (error) {
+        loading.value = false;
+        console.log('not passing', error);
+      }
+    };
+    return {
+      register,
+      registerForm,
+      fileFlow,
+      handleSubmit,
+      addListFunc,
+      resetFields,
+      loading,
+      t,
+      del,
+      add,
+    };
+  },
+});
+</script>

+ 295 - 0
src/views/work/followedList.vue

@@ -0,0 +1,295 @@
+<template>
+  <PageWrapper contentBackground>
+    <template #footer>
+      <a-tabs v-model:activeKey="tableType" @change="changeTable">
+        <a-tab-pane :key="0" :tab="t('routes.spares.tableType.0')" />
+        <a-tab-pane :key="1" :tab="t('routes.spares.tableType.21')" />
+        <a-tab-pane :key="2" :tab="t('routes.spares.tableType.23')" />
+      </a-tabs></template
+    >
+    <div class="desc-wrap-BasicTable">
+    <BasicTable @register="registerTable">
+      <template #toolbar>
+        <a-button type="primary" @click="handleOrder" v-if="getCheckPerm('invoice-export')"> 录单</a-button>
+      </template>
+      <template #action="{ record }">
+        <TableAction
+          stopButtonPropagation
+          :actions="[  
+            {
+              label: '详情',
+              onClick: handleDetail.bind(null, record),
+            },
+            {
+              label: '接单',
+              ifShow:record.status == 0,
+              onClick: handleRecover.bind(null, record),
+            },
+            {
+              label: '报价',
+              //ifShow:record.status == 2,
+              onClick: handleQuote.bind(null, record),
+            },
+            {
+              label: '修改报价',
+              ifShow:record.status == 3,
+              onClick: handleRecover.bind(null, record),
+            },
+            {
+              label: '付款登记',
+              ifShow:record.status == 9,
+              onClick: handleRecover.bind(null, record),
+            },
+            {
+              label: '发货登记',
+              ifShow:record.status == 10,
+              onClick: handleRecover.bind(null, record),
+            },
+            {
+              label: '备注',
+              onClick: handleRemarks.bind(null, record),
+            },
+          ]"
+        />
+      </template>
+    </BasicTable>
+    <recoveryModal @update="reload" @register="registerRecovery" />
+    <remarksModal @update="reload" @register="registerRemarks" />
+    <takingOrdersModel @update="reload" @register="registerTakingOrders" />
+    <quoteModel @update="reload" @register="registerQuote" />
+    <!-- ifShow: getCheckPerm('device-out') && !Boolean(record.outType), -->
+  </div>
+</PageWrapper>
+</template>
+<script lang="ts">
+import { defineComponent, onMounted, ref } from 'vue';
+import { PageWrapper } from '/@/components/Page';
+import {
+  BasicTable,
+  useTable,
+  TableAction,
+  BasicColumn,
+  TableImg,
+  FormProps,
+} from '/@/components/Table';
+import { Tabs } from 'ant-design-vue';
+import { operateSceneList } from '/@/api/operate';
+import { useI18n } from '/@/hooks/web/useI18n';
+import { usePermissionStore } from '/@/store/modules/permission';
+import recoveryModal from './recoveryModal.vue';
+import quoteModel from './quoteModel.vue';
+import takingOrdersModel from './takingOrdersModel.vue';
+import remarksModal from '../spares/remarksModal.vue';
+
+import { useModal } from '/@/components/Modal';
+import { useRouter } from 'vue-router'
+import { saleOrderList } from '/@/api/spares';
+export default defineComponent({
+  components: {
+    BasicTable,
+    TableAction,
+    TableImg,
+    recoveryModal,
+    takingOrdersModel,
+    remarksModal,
+    quoteModel,
+    PageWrapper,
+    [Tabs.name]: Tabs,
+    [Tabs.TabPane.name]: Tabs.TabPane,
+  },
+  setup() {
+    const { t } = useI18n();
+    const permissionStore = usePermissionStore();
+    const router = useRouter()
+    const { getCheckPerm } = permissionStore;
+    const tableType = ref<Recordable>(0); //0看看 、1看见、2深时
+    onMounted(() => {
+      // console.log(router.currentRoute.value.params.id);
+    });
+    const columns: BasicColumn[] = [
+      {
+        title: '报修日期',
+        dataIndex: 'createTime',
+        width: 180,
+      },
+      {
+        title: '客户名称',
+        dataIndex: 'operationType',
+        width: 80,
+      },
+      {
+        title: '产品类型',
+        dataIndex: 'cameraType',
+        width: 80,
+        customRender: ({ record }) => {
+          return t(`routes.scene.tableType.${record.cameraType || 0}`);
+        },
+      },
+      {
+        title: '产品SN码',
+        dataIndex: 'cameraSnCode',
+        width: 100,
+      },
+      {
+        title: '故障描述',
+        dataIndex: 'faultMsg',
+        width: 100,
+      },
+      {
+        title: '送修方式',
+        dataIndex: 'sendType',
+        width: 100,
+        customRender: ({ record }) => {
+          return record.sendType == 0 ? '前台送修' : '快递寄送';
+        },
+      },
+      {
+        title: '快递单号',
+        dataIndex: 'sendTrackingNum',
+        width: 150,
+      },
+      {
+        title: '状态',
+        dataIndex: 'status',
+        width: 100,
+        customRender: ({ record }) => {
+          return t(`routes.spares.tableType.${record.status || 0}`);
+        },
+      },
+      {
+        title: '工单号',
+        dataIndex: 'repairId',
+        width: 150,
+      },
+      {
+        title: t('common.operating'),
+        dataIndex: 'action',
+        slots: { customRender: 'action' },
+        ifShow: true,
+        fixed: 'right',
+        flag: 'ACTION',
+        width: 160,
+      },
+    ];
+    const searchForm: Partial<FormProps> = {
+      labelWidth: 120,
+      autoAdvancedLine: 1,
+      actionColOptions: {
+        span: 24,
+      },
+      schemas: [
+        {
+          field: 'customerName',
+          component: 'Input',
+          label: '客户名称',
+          colProps: {
+            xl: 7,
+            xxl: 7,
+          },
+        },
+        {
+          field: 'cameraSnCode',
+          component: 'Input',
+          label: t('routes.device.snCode'),
+          colProps: {
+            xl: 7,
+            xxl: 7,
+          },
+        },
+        {
+          field: 'trackingNum',
+          component: 'Input',
+          label: '快递单号',
+          colProps: {
+            xl: 7,
+            xxl: 7,
+          },
+        },
+        {
+          field: 'timeList',
+          label: '报修日期',
+          component: 'RangePicker',
+          componentProps: {
+            maxLength: 100,
+            format: 'YYYY-MM-DD',
+            valueFormat: 'YYYY-MM-DD',
+            showTime: true,
+          },
+          colProps: {
+            xl: 7,
+            xxl: 7,
+          },
+        },
+      ],
+    };
+    const [registerRecovery, { openModal }] = useModal();
+    const [registerQuote, { openModal:openQuoteModal }] = useModal();
+    const [registerTakingOrders, { openModal:openTakingOrders }] = useModal();
+    const [registerRemarks, { openModal:openRemarksModal }] = useModal();
+    const [registerTable, { reload }] = useTable({
+      api: saleOrderList,
+      columns: columns,
+      useSearchForm: true,
+      searchInfo: { statusParam: tableType },
+      formConfig: searchForm,
+      showTableSetting: true,
+      showIndexColumn: false,
+      fetchSetting: {
+        pageField: 'pageNum',
+        sizeField: 'pageSize',
+        listField: 'list',
+        totalField: 'total',
+      },
+      canResize: false,
+    });
+    async function handleDetail(record: Recordable) {
+      console.log('record', record);
+      router.push({path:`detail/${record.id||'20230215174919387'}`})
+    }
+    async function handleRecover(record: Recordable) {
+      openTakingOrders(true, {
+        ...record,
+      });
+    }
+    function handleRemarks(record: Recordable) {
+      openRemarksModal(true,record);
+    }
+    
+    function handleOrder() {
+      openModal(true);
+    }
+    function handleQuote(record: Recordable) {
+      openQuoteModal(true,record)
+    }
+    function changeTable(val: string) {
+      tableType.value = val;
+      reload();
+    }
+    return {
+      registerTable,
+      reload,
+      t,
+      tableType,
+      registerRemarks,
+      changeTable,
+      handleOrder,
+      getCheckPerm,
+      handleDetail,
+      handleQuote,
+      registerTakingOrders,
+      handleRecover,
+      handleRemarks,
+      registerQuote,
+      registerRecovery,
+    };
+  },
+});
+</script>
+<style lang="less" scoped>
+.desc-wrap-BasicTable {
+  background-color: #f0f2f5;
+  .vben-basic-table-form-container {
+    padding: 0;
+  }
+}
+</style>

+ 253 - 0
src/views/work/maintenance.vue

@@ -0,0 +1,253 @@
+<template>
+  <PageWrapper contentBackground>
+    <template #footer>
+      <a-tabs v-model:activeKey="tableType" @change="changeTable">
+        <a-tab-pane :key="0" :tab="t('routes.spares.tableType.0')" />
+        <a-tab-pane :key="1" :tab="t('routes.spares.tableType.21')" />
+        <a-tab-pane :key="2" :tab="t('routes.spares.tableType.22')" />
+      </a-tabs></template
+    >
+    <div class="desc-wrap-BasicTable">
+    <BasicTable @register="registerTable">
+      <template #action="{ record }">
+        <TableAction
+          stopButtonPropagation
+          :actions="[
+            {
+              label: '详情',
+              color: 'error',
+              onClick: handleDetail.bind(null, record),
+            },
+            {
+              label: '添加备件',
+              onClick: handleAdd.bind(null, record),
+            },
+            {
+              label: '完成维修',
+              onClick: handleOut.bind(null, record),
+            },
+            {
+              label: '检测登记',
+              onClick: handleRecover.bind(null, record),
+            },
+          ]"
+        />
+      </template>
+    </BasicTable>
+    <checkModel @update="reload" @register="registerRecovery" />
+    <addAccessoryModel @update="reload" @register="registerAdd" />
+    <outModal @update="reload" @register="registerOut" />
+    
+    <!-- ifShow: getCheckPerm('device-out') && !Boolean(record.outType), -->
+  </div>
+</PageWrapper>
+</template>
+<script lang="ts">
+import { defineComponent, onMounted, ref } from 'vue';
+import { PageWrapper } from '/@/components/Page';
+import {
+  BasicTable,
+  useTable,
+  TableAction,
+  BasicColumn,
+  TableImg,
+  FormProps,
+} from '/@/components/Table';
+import { Tabs } from 'ant-design-vue';
+import { operateSceneList } from '/@/api/operate';
+import { useI18n } from '/@/hooks/web/useI18n';
+import { usePermissionStore } from '/@/store/modules/permission';
+import checkModel from './checkModel.vue';
+import outModal from './outModal.vue';
+import addAccessoryModel from './addAccessoryModel.vue';
+import { useModal } from '/@/components/Modal';
+import { useRouter } from 'vue-router'
+import { saleOrderList, repairOrderList } from '/@/api/spares';
+export default defineComponent({
+  components: {
+    BasicTable,
+    TableAction,
+    TableImg,
+    checkModel,
+    outModal,
+    PageWrapper,
+    addAccessoryModel,
+    [Tabs.name]: Tabs,
+    [Tabs.TabPane.name]: Tabs.TabPane,
+  },
+  setup() {
+    const { t } = useI18n();
+    const permissionStore = usePermissionStore();
+    const router = useRouter()
+    const { getCheckPerm } = permissionStore;
+    const tableType = ref<number>(0); //0 待接单,1待跟进,2已完结
+    onMounted(() => {
+      // console.log(router.currentRoute.value.params.id);
+    });
+    const columns: BasicColumn[] = [
+      {
+        title: '报修日期',
+        dataIndex: 'createTime',
+        width: 180,
+      },
+      {
+        title: '客户名称',
+        dataIndex: 'addrId',
+        width: 80,
+      },
+      {
+        title: '产品类型',
+        dataIndex: 'cameraType',
+        width: 80,
+        customRender: ({ record }) => {
+          return t(`routes.device.type.${record.operationType || 1}`);
+        },
+      },
+      {
+        title: '产品SN码',
+        dataIndex: 'cameraSnCode',
+        width: 100,
+      },
+      {
+        title: '故障描述',
+        dataIndex: 'faultMsg',
+        width: 100,
+      },
+      {
+        title: '售后工程师',
+        dataIndex: 'saleName',
+        width: 100,
+      },
+      {
+        title: '接单日期',
+        dataIndex: 'orderReceivingTime',
+        width: 100,
+      },
+      {
+        title: '状态',
+        dataIndex: 'status',
+        width: 100,
+        customRender: ({ record }) => {
+          return t(`routes.spares.tableType.${record.status || 0}`);
+        },
+      },
+      {
+        title: '工单号',
+        dataIndex: 'repairId',
+        width: 100,
+      },
+      {
+        title: t('common.operating'),
+        dataIndex: 'action',
+        slots: { customRender: 'action' },
+        ifShow: true,
+        fixed: 'right',
+        flag: 'ACTION',
+        width: 200,
+      },
+    ];
+    const searchForm: Partial<FormProps> = {
+      labelWidth: 120,
+      schemas: [
+        {
+            field: 'type',
+            label: '设备类型',
+            component: 'Select',
+            componentProps: {
+              options: [
+                {
+                  label: t('routes.scene.tableType.0'),
+                  value: '0',
+                },{
+                  label: t('routes.scene.tableType.1'),
+                  value: '1',
+                },{
+                  label: t('routes.scene.tableType.2'),
+                  value: '2',
+                },
+              ],
+            },
+            colProps: {
+              xl: 6,
+              xxl: 6,
+            },
+          },
+        {
+          field: 'cameraSnCode',
+          component: 'Input',
+          label: t('routes.device.snCode'),
+          colProps: {
+            xl: 7,
+            xxl: 7,
+          },
+        },
+      ],
+    };
+    
+    const [registerRecovery, { openModal }] = useModal();
+    const [registerOut, { openModal:openOutModal }] = useModal();
+    const [registerAdd, { openModal:openAddModal }] = useModal();
+    const [registerTable, { reload }] = useTable({
+      api: repairOrderList,
+      columns: columns,
+      useSearchForm: true,
+      searchInfo: { statusParam: tableType },
+      formConfig: searchForm,
+      showTableSetting: true,
+      showIndexColumn: false,
+      fetchSetting: {
+        pageField: 'pageNum',
+        sizeField: 'pageSize',
+        listField: 'list',
+        totalField: 'total',
+      },
+      canResize: false,
+    });
+    async function handleDetail(record: Recordable) {
+      console.log('record', record);
+      router.push({path:`detail/${record.id||'20230215174919387'}`})
+    }
+    async function handleRecover(record: Recordable) {
+      openModal(true, record);
+    }
+    function handleAdd(record: Recordable) {
+      openAddModal(true,record);
+    }
+    function handleOut(record: Recordable) {
+      openOutModal(true,record);
+    }
+    function handleOrder() {
+      openModal(true);
+    }
+    function changeTable(val: string) {
+      tableType.value = val;
+      reload();
+    }
+    return {
+      registerTable,
+      reload,
+      t,
+      tableType,
+      openAddModal,
+      registerOut,
+      registerAdd,
+      changeTable,
+      handleOrder,
+      getCheckPerm,
+      handleDetail,
+      handleAdd,
+      handleOut,
+      handleRecover,
+      registerRecovery,
+    };
+  },
+});
+</script>
+<style lang="less" scoped>
+.desc-wrap-BasicTable {
+  background-color: #f0f2f5;
+  .vben-basic-table-form-container {
+    padding: 0;
+  }
+}
+</style>

+ 295 - 0
src/views/work/outModal.vue

@@ -0,0 +1,295 @@
+<template>
+  <BasicModal
+    v-bind="$attrs"
+    @register="register"
+    :title="fileFlow.title"
+    width="700px"
+    @cancel="resetFields"
+    :confirmLoading="loading"
+    @ok="handleSubmit"
+  >
+    <div class="pt-2px pr-3px">
+      <BasicForm @register="registerForm">
+        <template #text="{ model, field }">
+          {{ model[field] }}
+        </template>
+        <template #add>
+          <Button @click="add">添加配件</Button>
+        </template>
+        <template #del="{ field }">
+          <Button @click="del(field)">删除</Button>
+        </template>
+      </BasicForm>
+      <BasicTable @register="registerTable"></BasicTable>
+    </div>
+  </BasicModal>
+</template>
+<script lang="ts">
+import { defineComponent, h, onMounted, reactive, ref } from 'vue';
+import { BasicModal, useModalInner } from '/@/components/Modal';
+import { BasicForm, FormSchema, useForm } from '/@/components/Form/index';
+import { useMessage } from '/@/hooks/web/useMessage';
+import {
+  partAllList,
+  faultAllList,
+  checkRegister,
+  checkRegisterInfo,
+  repairOver,
+} from '/@/api/spares';
+import { useI18n } from '/@/hooks/web/useI18n';
+import { uploadApi } from '/@/api/product/index';
+import { BasicTable, useTable } from '/@/components/Table';
+const { t } = useI18n();
+export default defineComponent({
+  components: { BasicModal, BasicForm, BasicTable },
+  props: {
+    userData: { type: Object },
+  },
+  emits: ['update', 'register'],
+  setup(props, { emit }) {
+    const n = ref(1);
+    const repairId = ref(1);
+    const fileFlow = reactive({
+      file: null,
+      title: '添加备件',
+      repairId: null,
+      cameraType: 0,
+      type: 2, //2-普通发票,3-专用发票
+      faultList: [],
+    });
+    const loading = ref(false);
+    const { createMessage, createConfirm } = useMessage();
+    let schemas: FormSchema[] = [
+      {
+        field: 'repairId',
+        component: 'Input',
+        label: '维修单号',
+        slot: 'text',
+        colProps: {
+          span: 24,
+        },
+      },
+      {
+        field: 'deviceInfo',
+        component: 'Input',
+        label: '设备信息',
+        slot: 'text',
+        colProps: {
+          span: 18,
+        },
+      },
+      // {
+      //   field: 'faultIds',
+      //   component: 'CheckboxGroup',
+      //   label: '维修登记',
+      //   required: true,
+      //   componentProps: {
+      //     options: fileFlow.faultList,
+      //     maxLength: 50,
+      //   },
+      //   colProps: {
+      //     span: 18,
+      //   },
+      // },
+      {
+        field: 'remark',
+        component: 'InputTextArea',
+        label: '维修记录',
+        required: true,
+        componentProps: {
+          maxLength: 500,
+          rows: 3,
+          placeholder: '请填写维修记录',
+        },
+        colProps: {
+          span: 18,
+        },
+      },
+    ];
+    const columns = [
+      {
+        title: '备件编号',
+        dataIndex: 'partId',
+        width: 100,
+      },
+      {
+        title: '备件名称',
+        dataIndex: 'partName',
+        width: 150,
+      },
+      {
+        title: '数量',
+        dataIndex: 'partCount',
+        width: 80,
+      },
+    ];
+    const [registerTable, { reload }] = useTable({
+      api: checkRegisterInfo,
+      columns: columns,
+      searchInfo: { repairId: repairId },
+      showTableSetting: true,
+      showIndexColumn: false,
+      afterFetch: (T) => {
+        console.log(T);
+        return T;
+      },
+      immediate: false,
+      pagination: false,
+      fetchSetting: {
+        listField: 'partList',
+      },
+      canResize: false,
+    });
+    const [
+      registerForm,
+      {
+        validate,
+        resetFields,
+        setFieldsValue,
+        removeSchemaByFiled,
+        appendSchemaByField,
+        updateSchema,
+      },
+    ] = useForm({
+      labelWidth: 100,
+      schemas: schemas,
+      showActionButtonGroup: false,
+      actionColOptions: {
+        span: 24,
+      },
+    });
+    onMounted(() => {});
+    // getFaultList();
+    let addListFunc = () => {};
+    const [register, { closeModal }] = useModalInner((data) => {
+      repairId.value = data?.repairId;
+      data && onDataReceive(data);
+    });
+    async function getFaultList() {
+      const res = await faultAllList({});
+      fileFlow.faultList = res.map((item) => {
+        return {
+          ...item,
+          label: item.faultMsg,
+          value: item.faultId,
+        };
+      });
+    }
+
+    function onDataReceive(data) {
+      console.log('openOutModal', repairId.value);
+      resetFields();
+      if (repairId.value) {
+        reload();
+      }
+      // updateSchema({
+      //   field: 'faultIds',
+      //   componentProps: {
+      //     options: fileFlow.faultList,
+      //   },
+      // });
+      fileFlow.type = data.type;
+      fileFlow.cameraType = data.cameraType;
+      setFieldsValue({
+        ...data,
+        deviceInfo: t(`routes.scene.tableType.${data.cameraType}`) + data.cameraSnCode,
+      });
+    }
+    function del(field) {
+      removeSchemaByFiled([`deviceType${field}`, `device_${field}`, `${field}`]);
+      n.value--;
+    }
+    function add() {
+      let list = addSchemas(n.value);
+      list.map((ele) => {
+        appendSchemaByField(ele, '');
+      });
+      n.value++;
+    }
+    function addSchemas(number) {
+      let parentList: FormSchema[] = [
+        {
+          field: 'part_id' + number,
+          label: '备件' + number,
+          component: 'ApiSelect',
+          colProps: {
+            span: 12,
+          },
+          required: true,
+          componentProps: {
+            maxLength: 15,
+            api: partAllList,
+            labelField: 'partName',
+            valueField: 'partId',
+            params: {
+              cameraType: fileFlow.cameraType,
+            },
+            showSearch: true,
+            optionFilterProp: 'label',
+          },
+        },
+        {
+          field: 'partCount' + number,
+          label: '数量',
+          component: 'InputNumber',
+          required: true,
+          defaultValue: 1,
+          labelWidth: 50,
+          componentProps: {
+            max: 999,
+            min: 1,
+          },
+          colProps: {
+            span: 6,
+          },
+        },
+        {
+          field: number.toString(),
+          component: 'Input',
+          label: '',
+          labelWidth: 0,
+          colProps: {
+            span: 6,
+          },
+          slot: 'del',
+        },
+      ];
+      return parentList;
+    }
+    const handleSubmit = async () => {
+      loading.value = true;
+      try {
+        createConfirm({
+          iconType: 'warning',
+          title: () => h('span', '温馨提示'),
+          content: '确定要完成维修吗?',
+          onOk: async () => {
+            const params = await validate();
+            await repairOver(params);
+            loading.value = false;
+            createMessage.success(t('common.optSuccess'));
+            closeModal();
+            emit('update');
+          },
+        });
+      } catch (error) {
+        loading.value = false;
+        console.log('not passing', error);
+      }
+    };
+    return {
+      register,
+      registerForm,
+      fileFlow,
+      handleSubmit,
+      addListFunc,
+      resetFields,
+      registerTable,
+      loading,
+      t,
+      del,
+      add,
+    };
+  },
+});
+</script>

+ 238 - 0
src/views/work/query.vue

@@ -0,0 +1,238 @@
+<template>
+  <PageWrapper contentBackground>
+    <template #footer>
+      <a-tabs v-model:activeKey="tableType" @change="changeTable">
+        <a-tab-pane :key="0" :tab="t('routes.scene.tableType.0')" />
+        <a-tab-pane :key="1" :tab="t('routes.scene.tableType.1')" />
+      </a-tabs></template
+    >
+    <div class="desc-wrap-BasicTable">
+    <BasicTable @register="registerTable">
+      <template #action="{ record }">
+        <TableAction
+          stopButtonPropagation
+          :actions="[
+            {
+              label: '详情',
+              color: 'error',
+              onClick: handleDetail.bind(null, record),
+            },
+            {
+              label: '备件回收',
+              onClick: handleRecover.bind(null, record),
+            },
+          ]"
+        />
+      </template>
+    </BasicTable>
+    <recoveryModal @update="reload" @register="registerRecovery" />
+    <!-- ifShow: getCheckPerm('device-out') && !Boolean(record.outType), -->
+  </div>
+</PageWrapper>
+</template>
+<script lang="ts">
+import { defineComponent, onMounted, ref } from 'vue';
+import { PageWrapper } from '/@/components/Page';
+import {
+  BasicTable,
+  useTable,
+  TableAction,
+  BasicColumn,
+  TableImg,
+  FormProps,
+} from '/@/components/Table';
+import { Tabs } from 'ant-design-vue';
+import { operateSceneList } from '/@/api/operate';
+import { useI18n } from '/@/hooks/web/useI18n';
+import { usePermissionStore } from '/@/store/modules/permission';
+import recoveryModal from './recoveryModal.vue';
+import { useModal } from '/@/components/Modal';
+import { useRouter } from 'vue-router'
+import { saleOrderList } from '/@/api/spares';
+export default defineComponent({
+  components: {
+    BasicTable,
+    TableAction,
+    TableImg,
+    recoveryModal,
+    PageWrapper,
+    [Tabs.name]: Tabs,
+    [Tabs.TabPane.name]: Tabs.TabPane,
+  },
+  setup() {
+    const { t } = useI18n();
+    const permissionStore = usePermissionStore();
+    const router = useRouter()
+    const { getCheckPerm } = permissionStore;
+    const tableType = ref<Recordable>(0); //0看看 、1看见、2深时
+    onMounted(() => {
+      // console.log(router.currentRoute.value.params.id);
+    });
+    const columns: BasicColumn[] = [
+      {
+        title: '报修日期',
+        dataIndex: 'createTime',
+        width: 180,
+      },
+      {
+        title: '客户名称',
+        dataIndex: 'operationType',
+        width: 80,
+        customRender: ({ record }) => {
+          return t(`routes.equity.operation.${record.operationType || 0}`);
+        },
+      },
+      {
+        title: '产品类型',
+        dataIndex: 'cameraType',
+        width: 80,
+        customRender: ({ record }) => {
+          return t(`routes.equity.operation.${record.cameraType || 0}`);
+        },
+      },
+      {
+        title: '产品SN码',
+        dataIndex: 'cameraSnCode',
+        width: 100,
+      },
+      {
+        title: '故障描述',
+        dataIndex: 'faultMsg',
+        width: 100,
+      },
+      {
+        title: '送修方式',
+        dataIndex: 'sendType',
+        width: 100,
+        customRender: ({ record }) => {
+          return record.sendType == 0 ? '前台送修' : '快递寄送';
+        },
+      },
+      {
+        title: '快递单号',
+        dataIndex: 'sendTrackingNum',
+        width: 100,
+      },
+      {
+        title: '状态',
+        dataIndex: 'status',
+        width: 100,
+      },
+      {
+        title: '工单号',
+        dataIndex: 'repairId',
+        width: 100,
+      },
+      {
+        title: t('common.operating'),
+        dataIndex: 'action',
+        slots: { customRender: 'action' },
+        ifShow: true,
+        fixed: 'right',
+        flag: 'ACTION',
+        width: 120,
+      },
+    ];
+    const searchForm: Partial<FormProps> = {
+      labelWidth: 120,
+      autoAdvancedLine: 1,
+      actionColOptions: {
+        span: 24,
+      },
+      schemas: [
+        {
+          field: 'customerName',
+          component: 'Input',
+          label: '客户名称',
+          colProps: {
+            xl: 7,
+            xxl: 7,
+          },
+        },
+        {
+          field: 'cameraSnCode',
+          component: 'Input',
+          label: t('routes.device.snCode'),
+          colProps: {
+            xl: 7,
+            xxl: 7,
+          },
+        },
+        {
+          field: 'trackingNum',
+          component: 'Input',
+          label: '快递单号',
+          colProps: {
+            xl: 7,
+            xxl: 7,
+          },
+        },
+        {
+          field: 'timeList',
+          label: '报修日期',
+          component: 'RangePicker',
+          componentProps: {
+            maxLength: 100,
+            format: 'YYYY-MM-DD',
+            valueFormat: 'YYYY-MM-DD',
+            showTime: true,
+          },
+          colProps: {
+            xl: 7,
+            xxl: 7,
+          },
+        },
+      ],
+    };
+    const [registerRecovery, { openModal }] = useModal();
+    const [registerTable, { reload }] = useTable({
+      api: saleOrderList,
+      columns: columns,
+      useSearchForm: true,
+      searchInfo: { type: tableType },
+      formConfig: searchForm,
+      showTableSetting: true,
+      showIndexColumn: false,
+      fetchSetting: {
+        pageField: 'pageNum',
+        sizeField: 'pageSize',
+        listField: 'list',
+        totalField: 'total',
+      },
+      canResize: false,
+    });
+    async function handleDetail(record: Recordable) {
+      console.log('record', record);
+      router.push({path:`detail/${record.id||'20230215174919387'}`})
+    }
+    async function handleRecover(record: Recordable) {
+      openModal(true, {
+        ...record,
+      });
+    }
+    function changeTable(val: string) {
+      tableType.value = val;
+      reload();
+    }
+    return {
+      registerTable,
+      reload,
+      t,
+      tableType,
+      changeTable,
+      getCheckPerm,
+      handleDetail,
+      handleRecover,
+      registerRecovery,
+    };
+  },
+});
+</script>
+<style lang="less" scoped>
+.desc-wrap-BasicTable {
+  background-color: #f0f2f5;
+  .vben-basic-table-form-container {
+    padding: 0;
+  }
+}
+</style>

+ 214 - 0
src/views/work/quoteModel.vue

@@ -0,0 +1,214 @@
+<template>
+  <BasicModal
+    v-bind="$attrs"
+    @register="register"
+    title="维修报价"
+    width="700px"
+    @cancel="resetFields"
+    :confirmLoading="loading"
+    @ok="handleSubmit"
+  >
+    <div class="pt-2px pr-3px">
+      <BasicForm @register="registerForm">
+        <template #text="{ model, field }">
+          {{ model[field] }}
+        </template>
+        <template #add>
+          <Button  @click="add">更新总价</Button>
+          <Button  @click="add" style="margin-left:20px">添加人工</Button>
+        </template> 
+        <template #del="{ field }">
+          <Button @click="del(field)">删除</Button>
+        </template>
+      </BasicForm>
+    </div>
+  </BasicModal>
+</template>
+<script lang="ts">
+  import { defineComponent, h, onMounted, reactive, ref } from 'vue';
+  import { BasicModal, useModalInner } from '/@/components/Modal';
+  import { BasicForm, FormSchema, useForm } from '/@/components/Form/index';
+  import { useMessage } from '/@/hooks/web/useMessage';
+  import { InvoiceRegister } from '/@/api/order';
+  import { getPriceList } from '/@/api/spares'
+  import { useI18n } from '/@/hooks/web/useI18n';
+  import { uploadApi } from '/@/api/product/index';
+
+  const { t } = useI18n();
+  export default defineComponent({
+    components: { BasicModal, BasicForm },
+    props: {
+      userData: { type: Object },
+    },
+    emits: ['update', 'register'],
+    setup(props, { emit }) {
+      const n = ref(1);
+      const fileFlow = reactive({
+        file:null,
+        type:2,//2-普通发票,3-专用发票
+      })
+      const loading = ref(false)
+      const { createMessage } = useMessage();
+      let schemas: FormSchema[] = [
+          {
+            field: 'id',
+            component: 'Input',
+            show:false,
+            label: '发票编号',
+            required: true,
+          },
+          {
+            field: 'repairId',
+            component: 'Input',
+            label: '维修单号',
+            slot: 'text',
+            colProps: {
+              span: 24,
+            },
+          },{
+            field: 'deviceInfo',
+            component: 'Input',
+            label: '设备信息',
+            slot: 'text',
+            colProps: {
+              span: 18,
+            },
+          },{
+            field: 'noteContent',
+            component: 'Input',
+            label: '检测结果',
+            slot: 'text',
+            colProps: {
+              span: 18,
+            },
+          },{
+            field: '0',
+            component: 'Input',
+            label: '第一次报价',
+            labelWidth:0,
+            colProps: {
+              span: 24,
+            },
+            slot: 'add',
+          },
+      ];
+
+      const [registerForm, { validate, resetFields, setFieldsValue, removeSchemaByFiled, appendSchemaByField }] = useForm({
+        labelWidth: 100,
+        schemas:schemas,
+        showActionButtonGroup: false,
+        actionColOptions: {
+          span: 24,
+        },
+      });
+      onMounted(() => {});
+      let addListFunc = () => {};
+      const [register, { closeModal }] = useModalInner((data) => {
+        data && onDataReceive(data);
+      });
+      async function onDataReceive(data) {
+        const { priceLists, count } = await getPriceList({repairId:data.repairId})
+        console.log('repairId',priceLists, count)
+        resetFields();
+        fileFlow.type = data.type
+        setFieldsValue({
+          ...data,
+          deviceInfo:t(`routes.scene.tableType.${data.cameraType}`)+data.cameraSnCode
+        });
+      }
+      function del(field) {
+        removeSchemaByFiled([`deviceType${field}`, `device_${field}`, `${field}`]);
+        n.value--;
+      }
+      function add() {
+        let list = addSchemas(n.value);
+        list.map(ele => {
+          appendSchemaByField(ele,'')
+        })
+        n.value++
+      }
+      function addSchemas(number){
+        let parentList: FormSchema[] = [
+          {
+            field: 'deviceType' + number,
+            label: '人工费'+ number,
+            component: 'ApiSelect',
+            colProps: {
+              span: 12,
+            },
+            required: true,
+            componentProps: {
+              maxLength: 15,
+              api: InvoiceRegister,
+              fieldNames: {
+                label: 'name',
+                key: 'id',
+                value: 'id',
+              },
+              showSearch: true,
+              optionFilterProp: 'label',
+            },
+          },
+          {
+            field: 'device_' + number,
+            label: '数量',
+            component: 'InputNumber',
+            required: true,
+            defaultValue: 1,
+            labelWidth:50,
+            componentProps: {
+              max: 999,
+              min:1,
+            },
+            colProps: {
+              span: 6,
+            },
+          },{
+            field: number.toString(),
+            component: 'Input',
+            label: '',
+            labelWidth:0,
+            colProps: {
+              span: 6,
+            },
+            slot: 'del',
+          }
+        ];
+        return parentList
+      }
+      const handleSubmit = async () => {
+        loading.value = true
+        try {
+          createConfirm({
+          iconType: 'warning',
+          title: () => h('span', '温馨提示'),
+          content: '确定要提交检测报告吗?',
+          onOk: async () => {
+            const params = await validate();
+            await cameraIn(params)
+            createMessage.success(t('common.optSuccess'));
+            closeModal();
+            emit('update');
+            loading.value = false
+          },
+        });
+        } catch (error) {
+          loading.value = false
+          console.log('not passing', error);
+        }
+      };
+      return {
+        register,
+        registerForm,
+        fileFlow,
+        handleSubmit,
+        addListFunc,
+        resetFields,
+        loading,
+        t,
+        del,
+        add,
+      };
+    },
+  });
+</script>

+ 368 - 0
src/views/work/recoveryModal.vue

@@ -0,0 +1,368 @@
+<template>
+  <BasicModal
+    v-bind="$attrs"
+    @register="register"
+    title="录单"
+    @cancel="resetFields"
+    @ok="handleSubmit"
+    :min-height="0"
+  >
+    <BasicForm @register="registerForm" />
+  </BasicModal>
+</template>
+<script lang="ts">
+import { defineComponent, ref, onMounted, h } from 'vue';
+import { BasicModal, useModalInner } from '/@/components/Modal';
+import { useMessage } from '/@/hooks/web/useMessage';
+import { BasicForm, FormSchema, useForm } from '/@/components/Form/index';
+import { supplyOrderList, getByRoleType, recording } from '/@/api/spares';
+import { uploadApi } from '/@/api/product/index';
+import { useI18n } from '/@/hooks/web/useI18n';
+const { t } = useI18n();
+export default defineComponent({
+  components: { BasicModal, BasicForm },
+  props: {
+    userData: { type: Object },
+  },
+  emits: ['reload'],
+  setup(_, { emit }) {
+    const modelRef = ref({});
+    const schemas: FormSchema[] = [
+      {
+        field: 'connection-info',
+        component: 'Divider',
+        label: '客户信息',
+      },
+      {
+        field: 'companyName',
+        component: 'Input',
+        required: true,
+        label: '公司名称',
+        componentProps: {
+          maxLength: 50,
+        },        colProps: {
+          span: 18,
+        },  
+      },
+      {
+        field: 'customerName',
+        component: 'Input',
+        required: true,
+        label: '联系人',
+        componentProps: {
+          placeholder: '请填写姓名',
+          maxLength: 50,
+        },        colProps: {
+          span: 18,
+        },  
+      },
+      {
+        field: 'phone',
+        component: 'Input',
+        required: true,
+        label: '联系电话',
+        rules: [
+          {
+            required: true,
+            // @ts-ignore
+            validator: async (rule, value) => {
+              var reg_tel =
+                /^(13[0-9]|14[01456879]|15[0-35-9]|16[2567]|17[0-8]|18[0-9]|19[0-35-9])\d{8}$/;
+              // var reg = /\S+@\S+\.\S+/;
+              if (!value) {
+                /* eslint-disable-next-line */
+
+                return Promise.reject(t('common.phone'));
+              }
+              if (!reg_tel.test(value)) {
+                /* eslint-disable-next-line */
+                return Promise.reject(t('common.phoneError'));
+              }
+              return Promise.resolve();
+            },
+            trigger: 'change',
+          },
+        ],        colProps: {
+          span: 18,
+        },  
+        componentProps: {
+          placeholder: '请填写手机号',
+          maxLength: 50,
+        },
+      },
+      {
+        field: 'hitch-info',
+        component: 'Divider',
+        label: '故障信息',
+      },
+      {
+        field: 'cameraSnCode',
+        component: 'Input',
+        label: '产品SN码',        
+        required: true,
+        colProps: {
+          span: 18,
+        },  
+        componentProps: {
+          placeholder: '请填写下划线后的数字母组合',
+          maxLength: 50,
+        },
+      },
+      {
+        field: 'faultMsg',
+        component: 'InputTextArea',
+        label: '故障描述',
+        required: true,
+        componentProps: {
+          maxLength: 500,
+          rows: 3,
+          placeholder: '请具体描述故障现象,相关操作等。',
+        },
+        colProps: {
+          span: 18,
+        },
+      },
+      {
+        field: 'faultImg',
+        component: 'Upload',
+        label: '相关图片',
+        required: true,
+        rules: [{ required: true, message: t('common.uploadMessge') }],
+        itemProps: {
+          validateTrigger: 'blur',
+        },
+        componentProps: {
+          api: uploadApi,
+          // fileFlow:true,
+          maxNumber: 5,
+          maxSize: 5,
+          accept: ['jpeg', 'jpg', 'png'],
+        },
+        colProps: {
+          span: 12,
+        },
+      },
+      {
+        field: 'sendType',
+        component: 'RadioGroup',
+        required: true,
+        label: '送修方式',
+        defaultValue:1,
+        componentProps: {
+          placeholder: '请填写姓名',
+          options:[
+            { label: '前台送修', value: 0 },
+            { label: '快递寄送', value: 1 },
+          ],
+          onChange:(e)=>{
+            updateSchema([{
+              field: 'sendTrackingNum',
+              ifShow:e.target.value == 1
+            }])
+          }
+        },
+      },
+      {
+        field: 'sendTrackingNum',
+        component: 'Input',
+        label: '快递单号',
+        required: true,
+        colProps: {
+          span: 18,
+        },
+      },{
+        field: 'getType',
+        component: 'RadioGroup',
+        required: true,
+        label: '送修方式',
+        defaultValue:1,
+        componentProps: {
+          placeholder: '请填写姓名',
+          options:[
+            { label: '前台取回', value: 0 },
+            { label: '快递寄回', value: 1 },
+          ],
+          onChange:(e)=>{
+            updateSchema([{
+              field: 'getAddress',
+              ifShow:e.target.value == 1
+            },{
+              field: 'getAddrName',
+              ifShow:e.target.value == 1
+            },{
+              field: 'getAddrPhone',
+              ifShow:e.target.value == 1
+            }])
+          }
+        },
+      },
+      {
+        field: 'getAddress',
+        component: 'Input',
+        required: true,
+        label: '收件地址',
+        colProps: {
+          span: 18,
+        },   
+        componentProps: {
+          maxLength: 50,
+        },  
+      },
+      {
+        field: 'getAddrName',
+        component: 'Input',
+        required: true,
+        label: '收件人',
+        colProps: {
+          span: 18,
+        },   
+        componentProps: {
+          maxLength: 50,
+        },  
+      },
+      {
+        field: 'getAddrPhone',
+        component: 'Input',
+        label: '收件人电话',
+        colProps: {
+          span: 18,
+        },      
+        componentProps: {
+          maxLength: 50,
+          placeholder: '请填写手机号',
+        },  
+        rules: [
+          {
+            required: true,
+            // @ts-ignore
+            validator: async (rule, value) => {
+              var reg_tel =
+                /^(13[0-9]|14[01456879]|15[0-35-9]|16[2567]|17[0-8]|18[0-9]|19[0-35-9])\d{8}$/;
+              // var reg = /\S+@\S+\.\S+/;
+              if (!value) {
+                /* eslint-disable-next-line */
+
+                return Promise.reject(t('common.phone'));
+              }
+              if (!reg_tel.test(value)) {
+                /* eslint-disable-next-line */
+                return Promise.reject(t('common.phoneError'));
+              }
+              return Promise.resolve();
+            },
+            trigger: 'change',
+          },
+        ],
+      },
+      {
+        field: 'work-info',
+        component: 'Divider',
+        label: '工单信息',
+      },{
+        field: 'warrantyType',
+        component: 'RadioGroup',
+        required: true,
+        label: '保修类型',
+        defaultValue:1,
+        componentProps: {
+          placeholder: '请填写姓名',
+          options:[
+            { label: '保修期内', value: 0 },
+            { label: '保修期外', value: 1 },
+            { label: '非保修项目', value: 2 },
+          ],
+        },
+      },
+      {
+        field: 'warrantyExpirationDate',
+        component: 'Input',
+        label: '保修界面日期',
+        required: true,
+        colProps: {
+          span: 18,
+        },   
+        componentProps: {
+          maxLength: 100,
+        },  
+      },{
+          field: 'testerId',
+          label: '测试工程师',
+          component: 'ApiSelect',
+          required: true,
+          componentProps: {
+            api: getByRoleType,
+            numberToString: true,
+            labelField: 'nickName',
+            valueField: 'id',
+            immediate: true,
+            params: {
+              roleType: 5,
+            },
+          },
+          colProps: {
+            span: 18,
+          },
+        },
+    ];
+    const [registerForm, { validate, resetFields, setFieldsValue, updateSchema }] = useForm({
+      labelWidth: 100,
+      schemas: schemas,
+      showActionButtonGroup: false,
+      actionColOptions: {
+        span: 24,
+      },
+    });
+    const { createMessage, createConfirm } = useMessage();
+    onMounted(() => {});
+    let addListFunc = () => {};
+    const [register, { closeModal }] = useModalInner((data) => {
+      // console.log(data);
+      data && onDataReceive(data);
+    });
+
+    function onDataReceive(data) {
+      modelRef.value = data;
+    }
+
+    const handleSubmit = async () => {
+      // createConfirm({
+      //   iconType: 'warning',
+      //   title: () => h('span', '温馨提示'),
+      //   content: '删除设备后需要重新入库<br/>确定删除吗?',
+      //   onOk: async () => {
+          const params = await validate()
+          const res = await recording({
+            ...params,
+            faultImg:params.faultImg?params.faultImg.toString():[]
+          });
+          console.log('validate',params,res)
+          createMessage.success(t('common.optSuccess'));
+          closeModal();
+          emit('reload');
+      //   },
+      // });
+    };
+    return {
+      register,
+      model: modelRef,
+      registerForm,
+      handleSubmit,
+      addListFunc,
+      resetFields,
+      t,
+    };
+  },
+});
+</script>
+<style lang="less" scoped>
+.recoverPage {
+  .form_item {
+    line-height: 30px;
+    .item_lable {
+      display: inline-block;
+      width: 100px;
+      text-align: right;
+    }
+  }
+}
+</style>

+ 229 - 0
src/views/work/takingOrdersModel.vue

@@ -0,0 +1,229 @@
+<template>
+  <BasicModal
+    v-bind="$attrs"
+    @register="register"
+    title="接单"
+    @cancel="resetFields"
+    @ok="handleSubmit"
+    :min-height="0"
+  >
+    <BasicForm @register="registerForm" >
+        <template #text="{ model, field }">
+          {{ model[field]  }}
+        </template>
+    </BasicForm>
+  </BasicModal>
+</template>
+<script lang="ts">
+import { defineComponent, ref, onMounted, h } from 'vue';
+import { BasicModal, useModalInner } from '/@/components/Modal';
+import { useMessage } from '/@/hooks/web/useMessage';
+import { BasicForm, FormSchema, useForm } from '/@/components/Form/index';
+import { supplyOrderList, getByRoleType, orderReceiving } from '/@/api/spares';
+import { uploadApi } from '/@/api/product/index';
+import { useI18n } from '/@/hooks/web/useI18n';
+const { t } = useI18n();
+export default defineComponent({
+  components: { BasicModal, BasicForm },
+  props: {
+    userData: { type: Object },
+  },
+  emits: ['reload'],
+  setup(_, { emit }) {
+    const modelRef = ref({});
+    const schemas: FormSchema[] = [
+      {
+        field: 'repairId',
+        component: 'Input',
+        slot: 'text',
+        label: '维修单号',
+        componentProps: {
+          maxLength: 50,
+        },        colProps: {
+          span: 18,
+        },  
+      },
+      {
+        field: 'deviceType',
+        component: 'Input',
+        slot: 'text',
+        label: '设备信息',
+      },
+      {
+        field: 'cameraFaultInfo',
+        component: 'InputTextArea',
+        label: '机器外观',
+        required: true,
+        componentProps: {
+          maxLength: 500,
+          rows: 3,
+          placeholder: '请描述检测后的具体故障情况。',
+        },
+        colProps: {
+          span: 18,
+        },
+      },
+      {
+        field: 'imageUrl',
+        component: 'Upload',
+        label: '相关图片',
+        rules: [{ required: true, message: t('common.uploadMessge') }],
+        itemProps: {
+          validateTrigger: 'blur',
+        },
+        componentProps: {
+          api: uploadApi,
+          // fileFlow:true,
+          maxNumber: 5,
+          maxSize: 5,
+          accept: ['jpeg', 'jpg', 'png'],
+        },
+        colProps: {
+          span: 12,
+        },
+      },{
+        field: 'warrantyType',
+        component: 'RadioGroup',
+        required: true,
+        label: '保修类型',
+        defaultValue:1,
+        componentProps: {
+          placeholder: '请填写姓名',
+          options:[
+            { label: '保修期内', value: 0 },
+            { label: '保修期外', value: 1 },
+            { label: '非保修项目', value: 2 },
+          ],
+        },
+      },
+      {
+        field: 'warrantyExpirationDate',
+        component: 'Input',
+        label: '保修界面日期',
+        required: true,
+        colProps: {
+          span: 18,
+        },   
+        componentProps: {
+          maxLength: 100,
+        },  
+      },{
+          field: 'repairManId',
+          label: '维修工程师',
+          component: 'ApiSelect',
+          required: true,
+          componentProps: {
+            api: getByRoleType,
+            numberToString: true,
+            labelField: 'nickName',
+            valueField: 'id',
+            immediate: true,
+            params: {
+              roleType: 3,
+            },
+          },
+          colProps: {
+            span: 18,
+          },
+        },{
+          field: 'supplyAdminId',
+          label: '供应链管理员',
+          component: 'ApiSelect',
+          required: true,
+          componentProps: {
+            api: getByRoleType,
+            numberToString: true,
+            labelField: 'nickName',
+            valueField: 'id',
+            immediate: true,
+            params: {
+              roleType: 4,
+            },
+          },
+          colProps: {
+            span: 18,
+          },
+        },{
+          field: 'testerId',
+          label: '测试工程师',
+          component: 'ApiSelect',
+          required: true,
+          componentProps: {
+            api: getByRoleType,
+            numberToString: true,
+            labelField: 'nickName',
+            valueField: 'id',
+            immediate: true,
+            params: {
+              roleType: 5,
+            },
+          },
+          colProps: {
+            span: 18,
+          },
+        },
+    ];
+    const [registerForm, { validate, resetFields, setFieldsValue, updateSchema }] = useForm({
+      labelWidth: 100,
+      schemas: schemas,
+      showActionButtonGroup: false,
+      actionColOptions: {
+        span: 24,
+      },
+    });
+    const { createMessage, createConfirm } = useMessage();
+    onMounted(() => {});
+    let addListFunc = () => {};
+    const [register, { closeModal }] = useModalInner((data) => {
+      // console.log(data);
+      data && onDataReceive(data);
+    });
+
+    function onDataReceive(data) {
+      modelRef.value = data;
+      resetFields();
+      setFieldsValue({
+        ...data,
+        deviceType:t(`routes.scene.tableType.${data.cameraType || 0}`) +' '+ data.cameraSnCode
+      });
+    }
+
+    const handleSubmit = async () => {
+      // createConfirm({
+      //   iconType: 'warning',
+      //   title: () => h('span', '温馨提示'),
+      //   content: '删除设备后需要重新入库<br/>确定删除吗?',
+      //   onOk: async () => {
+          const params = await validate()
+          const res = await orderReceiving(params);
+          console.log('validate',params,res)
+          createMessage.success(t('common.optSuccess'));
+          closeModal();
+          emit('reload');
+      //   },
+      // });
+    };
+    return {
+      register,
+      model: modelRef,
+      registerForm,
+      handleSubmit,
+      addListFunc,
+      resetFields,
+      t,
+    };
+  },
+});
+</script>
+<style lang="less" scoped>
+.recoverPage {
+  .form_item {
+    line-height: 30px;
+    .item_lable {
+      display: inline-block;
+      width: 100px;
+      text-align: right;
+    }
+  }
+}
+</style>