zhuangyunsheng před 1 měsícem
rodič
revize
038712683c
61 změnil soubory, kde provedl 833 přidání a 373 odebrání
  1. 1 3
      .env.development
  2. 1 0
      .env.production
  3. 3 1
      .gitignore
  4. 12 9
      src/api/model/auth.js
  5. 21 15
      src/api/model/basic.js
  6. 12 13
      src/api/model/common.js
  7. 13 10
      src/api/model/process.js
  8. 8 5
      src/api/model/production.js
  9. 16 11
      src/api/model/sales.js
  10. 45 24
      src/api/model/system.js
  11. 8 2
      src/components/scFormTable/index.vue
  12. 21 0
      src/components/scTable/helper.js
  13. 0 1
      src/components/scTable/index.vue
  14. 4 0
      src/components/scTable/renderer/form-select.vue
  15. 8 1
      src/components/scTableInput/index.vue
  16. 0 94
      src/layout/components/probar.vue
  17. 56 0
      src/layout/components/tenantbar.vue
  18. 21 17
      src/layout/components/userbar.vue
  19. 0 35
      src/store/modules/project.js
  20. 35 0
      src/store/modules/tenant.js
  21. 5 5
      src/style/app.scss
  22. 6 19
      src/utils/request.js
  23. 12 0
      src/views/basic/customer/detail.vue
  24. 8 2
      src/views/basic/customer/index.vue
  25. 12 0
      src/views/basic/material/detail.vue
  26. 8 2
      src/views/basic/material/index.vue
  27. 5 1
      src/views/basic/qualityPlan/desc.vue
  28. 14 2
      src/views/basic/qualityPlan/detail.vue
  29. 8 2
      src/views/basic/qualityPlan/index.vue
  30. 4 3
      src/views/home/index.vue
  31. 4 1
      src/views/process/line/desc.vue
  32. 14 12
      src/views/process/line/detail.vue
  33. 1 0
      src/views/process/line/history.vue
  34. 16 5
      src/views/process/line/index.vue
  35. 13 1
      src/views/process/stage/detail.vue
  36. 8 2
      src/views/process/stage/index.vue
  37. 4 1
      src/views/production/bom/desc.vue
  38. 18 3
      src/views/production/bom/detail.vue
  39. 8 2
      src/views/production/bom/index.vue
  40. 1 1
      src/views/production/bom/main.js
  41. 4 1
      src/views/sales/order/desc.vue
  42. 15 2
      src/views/sales/order/detail.vue
  43. 8 2
      src/views/sales/order/index.vue
  44. 2 0
      src/views/sales/performance/components/bar.vue
  45. 20 17
      src/views/sales/performance/components/line.vue
  46. 2 0
      src/views/sales/performance/components/pie.vue
  47. 2 0
      src/views/sales/performance/index.vue
  48. 12 6
      src/views/sales/performance/main.js
  49. 12 0
      src/views/sales/plan/detail.vue
  50. 8 2
      src/views/sales/plan/index.vue
  51. 27 9
      src/views/system/dept/detail.vue
  52. 14 6
      src/views/system/dept/index.vue
  53. 5 2
      src/views/system/role/bind.vue
  54. 10 0
      src/views/system/role/detail.vue
  55. 10 3
      src/views/system/role/index.vue
  56. 79 0
      src/views/system/tenant/detail.vue
  57. 113 0
      src/views/system/tenant/index.vue
  58. 22 11
      src/views/system/user/detail.vue
  59. 11 4
      src/views/system/user/index.vue
  60. 1 1
      src/views/userCenter/index.vue
  61. 2 2
      vue.config.js

+ 1 - 3
.env.development

@@ -4,9 +4,7 @@ NODE_ENV = development
 # 接口地址
 VUE_APP_ICONIFY_BASEURL = https://api.iconify.design
 VUE_APP_ZEROAPI_BASEURL = http://www.qdeasydo.com
-# VUE_APP_MES_BASEURL = http://www.qdeasydo.com/mes
-# VUE_APP_MES_BASEURL = http://192.168.101.93:8200
 VUE_APP_MES_BASEURL = http://192.168.101.93:8080
 
 # 本地端口
-VUE_APP_PORT = 1200
+VUE_APP_PORT = 4400

+ 1 - 0
.env.production

@@ -3,4 +3,5 @@ NODE_ENV = production
 
 # 接口地址
 VUE_APP_MES_BASEURL =
+VUE_APP_ICONIFY_BASEURL = 
 VUE_APP_ZEROAPI_BASEURL =

+ 3 - 1
.gitignore

@@ -56,4 +56,6 @@ logs
 
 .vscode/
 
-easydo/
+easydo/
+dist/
+dist.zip

+ 12 - 9
src/api/model/auth.js

@@ -1,3 +1,4 @@
+import store from "@/store"
 import config from "@/config"
 import tool from "@/utils/tool"
 import http from "@/utils/request"
@@ -11,12 +12,12 @@ export default {
             code: data.code,
             uuid: data.uuid
         }
-        return await http.post("/mes/auth/login", query);
+        return await http.post("/mes/auth/login", query)
 	},
 
     // 获取登录验证码
 	codeImg: async function () {
-        return await http.get("/mes/auth/code");
+        return await http.get("/mes/auth/code")
 	},
 
     user: {
@@ -24,27 +25,29 @@ export default {
         url: "/mes/sysUser",
         
         get: async function (data = {}) {
-            return await http.post(`${this.url}/getPage`, data);
+            if (store.state.tenant.tenantId !== "0") data.tenantId = store.state.tenant.tenantId
+            return await http.post(`${this.url}/getPage`, data)
         },
 
         all: async function (data = {}) {
-            return await http.post(`${this.url}/getList`, data);
+            if (store.state.tenant.tenantId !== "0") data.tenantId = store.state.tenant.tenantId
+            return await http.post(`${this.url}/getList`, data)
         },
 
         add: async function (data = {}) {
-            return await http.post(`${this.url}/save`, data);
+            return await http.post(`${this.url}/save`, data)
         },
 
         edit: async function (data = {}) {
-            return await http.post(`${this.url}/update`, data);
+            return await http.post(`${this.url}/update`, data)
         },
 
         del: async function (data = {}) {
-            return await http.post(`${this.url}/remove`, data);
+            return await http.post(`${this.url}/remove`, data)
         },
 
         resetPass: async function (data = {}) {
-			return await http.post(`${this.url}/resetPass`, data);
+			return await http.post(`${this.url}/resetPass`, data)
 		},
 
         updatePass: async function (data = {}) {
@@ -52,7 +55,7 @@ export default {
 				oldPass: tool.crypto.encrypt(data.userPassword),
 				newPass: tool.crypto.encrypt(data.newPassword)
 			}
-			return await http.post(`${this.url}/updatePass`, query);
+			return await http.post(`${this.url}/updatePass`, query)
 		}
     }
 }

+ 21 - 15
src/api/model/basic.js

@@ -1,25 +1,27 @@
+import store from "@/store"
 import config from "@/config"
 import http from "@/utils/request"
 
 export default {
     material: {
-        name: "工序管理",
+        name: "物料管理",
         url: "/mes/processMaterial",
         
         get: async function (data = {}) {
-            return await http.post(`${this.url}/getPage`, data);
+            if (store.state.tenant.tenantId !== "0") data.tenantId = store.state.tenant.tenantId
+            return await http.post(`${this.url}/getPage`, data)
         },
 
         add: async function (data = {}) {
-            return await http.post(`${this.url}/save`, data);
+            return await http.post(`${this.url}/save`, data)
         },
 
         edit: async function (data = {}) {
-            return await http.post(`${this.url}/update`, data);
+            return await http.post(`${this.url}/update`, data)
         },
 
         del: async function (data = {}) {
-            return await http.post(`${this.url}/remove`, data);
+            return await http.post(`${this.url}/remove`, data)
         }
     },
 
@@ -28,23 +30,25 @@ export default {
         url: "/mes/qualityInspectProgram",
         
         get: async function (data = {}) {
-            return await http.post(`${this.url}/getPage`, data);
+            if (store.state.tenant.tenantId !== "0") data.tenantId = store.state.tenant.tenantId
+            return await http.post(`${this.url}/getPage`, data)
         },
 
         all: async function (data = {}) {
-            return await http.post(`${this.url}/getList`, data);
+            if (store.state.tenant.tenantId !== "0") data.tenantId = store.state.tenant.tenantId
+            return await http.post(`${this.url}/getList`, data)
         },
 
         add: async function (data = {}) {
-            return await http.post(`${this.url}/save`, data);
+            return await http.post(`${this.url}/save`, data)
         },
 
         edit: async function (data = {}) {
-            return await http.post(`${this.url}/update`, data);
+            return await http.post(`${this.url}/update`, data)
         },
 
         del: async function (data = {}) {
-            return await http.post(`${this.url}/remove`, data);
+            return await http.post(`${this.url}/remove`, data)
         }
     },
 
@@ -53,23 +57,25 @@ export default {
         url: "/mes/customer",
         
         get: async function (data = {}) {
-            return await http.post(`${this.url}/getPage`, data);
+            if (store.state.tenant.tenantId !== "0") data.tenantId = store.state.tenant.tenantId
+            return await http.post(`${this.url}/getPage`, data)
         },
 
         all: async function (data = {}) {
-            return await http.post(`${this.url}/getList`, data);
+            if (store.state.tenant.tenantId !== "0") data.tenantId = store.state.tenant.tenantId
+            return await http.post(`${this.url}/getList`, data)
         },
 
         add: async function (data = {}) {
-            return await http.post(`${this.url}/save`, data);
+            return await http.post(`${this.url}/save`, data)
         },
 
         edit: async function (data = {}) {
-            return await http.post(`${this.url}/update`, data);
+            return await http.post(`${this.url}/update`, data)
         },
 
         del: async function (data = {}) {
-            return await http.post(`${this.url}/remove`, data);
+            return await http.post(`${this.url}/remove`, data)
         }
     }
 }

+ 12 - 13
src/api/model/common.js

@@ -1,4 +1,3 @@
-import axios from "axios";
 import config from "@/config"
 import http from "@/utils/request"
 
@@ -6,7 +5,7 @@ export default {
     iconify: {
         name: "图标查询",
 		get: async function (params = {}) {
-			return await http.get("/iconify-api/search", params, config);
+			return await http.get("/iconify-api/search", params, config)
 		}
     },
 
@@ -15,11 +14,11 @@ export default {
 		url: `/mes/file`,
 
 		up: async function (data, config = {}) {
-			return await http.post(`${this.url}/upload`, data, config);
+			return await http.post(`${this.url}/upload`, data, config)
 		},
 
 		rm: async function (data) {
-			return await http.post(`${this.url}/remove`, data);
+			return await http.post(`${this.url}/remove`, data)
 		},
 
         download: async function (entityID, isTxt = false) { // url: string, isTxt: txt解码
@@ -27,23 +26,23 @@ export default {
                 responseType: "blob",
                 transformResponse: isTxt && [
                     async function (data) {
-                        return await transformData(data);
+                        return await transformData(data)
                     }
                 ] || []
-            });
+            })
 		}
 	}
 }
 
 function transformData(data) {
     return new Promise(resolve => {
-        const reader = new FileReader();
-        reader.readAsText(data, "UTF-8");
+        const reader = new FileReader()
+        reader.readAsText(data, "UTF-8")
         reader.onload = () => {
             if (reader.result.includes("�")) {
-                reader.readAsText(data, "GBK");
-                reader.onload = () => resolve(reader.result);
-            } else resolve(reader.result);
-        };
-    });
+                reader.readAsText(data, "GBK")
+                reader.onload = () => resolve(reader.result)
+            } else resolve(reader.result)
+        }
+    })
 }

+ 13 - 10
src/api/model/process.js

@@ -1,3 +1,4 @@
+import store from "@/store"
 import config from "@/config"
 import http from "@/utils/request"
 
@@ -7,19 +8,20 @@ export default {
         url: "/mes/processStage",
         
         get: async function (data = {}) {
-			return await http.post(`${this.url}/getPage`, data);
+            if (store.state.tenant.tenantId !== "0") data.tenantId = store.state.tenant.tenantId
+			return await http.post(`${this.url}/getPage`, data)
 		},
 
         add: async function (data = {}) {
-            return await http.post(`${this.url}/save`, data);
+            return await http.post(`${this.url}/save`, data)
         },
 
         edit: async function (data = {}) {
-            return await http.post(`${this.url}/update`, data);
+            return await http.post(`${this.url}/update`, data)
         },
 
         del: async function (data = {}) {
-            return await http.post(`${this.url}/remove`, data);
+            return await http.post(`${this.url}/remove`, data)
         }
     },
 
@@ -28,27 +30,28 @@ export default {
         url: "/mes/processRoute",
         
         get: async function (data = {}) {
-			return await http.post(`${this.url}/getPage`, data);
+            if (store.state.tenant.tenantId !== "0") data.tenantId = store.state.tenant.tenantId
+			return await http.post(`${this.url}/getPage`, data)
 		},
 
         add: async function (data = {}) {
-            return await http.post(`${this.url}/save`, data);
+            return await http.post(`${this.url}/save`, data)
         },
 
         edit: async function (data = {}) {
-            return await http.post(`${this.url}/update`, data);
+            return await http.post(`${this.url}/update`, data)
         },
 
         del: async function (data = {}) {
-            return await http.post(`${this.url}/remove`, data);
+            return await http.post(`${this.url}/remove`, data)
         },
 
         upgrade: async function (data = {}) {
-            return await http.post(`${this.url}/upgrade`, data);
+            return await http.post(`${this.url}/upgrade`, data)
         },
 
         regrade: async function (data = {}) {
-            return await http.post(`${this.url}/regrade`, data);
+            return await http.post(`${this.url}/regrade`, data)
         }
     }
 }

+ 8 - 5
src/api/model/production.js

@@ -1,3 +1,4 @@
+import store from "@/store"
 import config from "@/config"
 import http from "@/utils/request"
 
@@ -7,23 +8,25 @@ export default {
         url: "/mes/productBom",
         
         get: async function (data = {}) {
-            return await http.post(`${this.url}/getPage`, data);
+            if (store.state.tenant.tenantId !== "0") data.tenantId = store.state.tenant.tenantId
+            return await http.post(`${this.url}/getPage`, data)
         },
 
         getChild: async function (data = {}) {
-            return await http.post(`${this.url}/getChildrenList`, data);
+            if (store.state.tenant.tenantId !== "0") data.tenantId = store.state.tenant.tenantId
+            return await http.post(`${this.url}/getChildrenList`, data)
         },
 
         add: async function (data = {}) {
-            return await http.post(`${this.url}/save`, data);
+            return await http.post(`${this.url}/save`, data)
         },
 
         edit: async function (data = {}) {
-            return await http.post(`${this.url}/update`, data);
+            return await http.post(`${this.url}/update`, data)
         },
 
         del: async function (data = {}) {
-            return await http.post(`${this.url}/remove`, data);
+            return await http.post(`${this.url}/remove`, data)
         }
     }
 }

+ 16 - 11
src/api/model/sales.js

@@ -1,3 +1,4 @@
+import store from "@/store"
 import config from "@/config"
 import http from "@/utils/request"
 
@@ -7,19 +8,20 @@ export default {
         url: "/mes/salePlan",
         
         get: async function (data = {}) {
-            return await http.post(`${this.url}/getList`, data);
+            if (store.state.tenant.tenantId !== "0") data.tenantId = store.state.tenant.tenantId
+            return await http.post(`${this.url}/getList`, data)
         },
 
         add: async function (data = {}) {
-            return await http.post(`${this.url}/save`, data);
+            return await http.post(`${this.url}/save`, data)
         },
 
         edit: async function (data = {}) {
-            return await http.post(`${this.url}/update`, data);
+            return await http.post(`${this.url}/update`, data)
         },
 
         del: async function (data = {}) {
-            return await http.post(`${this.url}/remove`, data);
+            return await http.post(`${this.url}/remove`, data)
         }
     },
 
@@ -28,19 +30,20 @@ export default {
         url: "/mes/saleOrder",
         
         get: async function (data = {}) {
-            return await http.post(`${this.url}/getPage`, data);
+            if (store.state.tenant.tenantId !== "0") data.tenantId = store.state.tenant.tenantId
+            return await http.post(`${this.url}/getPage`, data)
         },
 
         add: async function (data = {}) {
-            return await http.post(`${this.url}/save`, data);
+            return await http.post(`${this.url}/save`, data)
         },
 
         edit: async function (data = {}) {
-            return await http.post(`${this.url}/update`, data);
+            return await http.post(`${this.url}/update`, data)
         },
 
         del: async function (data = {}) {
-            return await http.post(`${this.url}/remove`, data);
+            return await http.post(`${this.url}/remove`, data)
         }
     },
 
@@ -48,12 +51,14 @@ export default {
         name: "销售业绩",
         url: "/mes/salePerformance",
         
-        census: async function () {
-            return await http.post(`${this.url}/getTotalPrice`);
+        census: async function (data = {}) {
+            if (store.state.tenant.tenantId !== "0") data.tenantId = store.state.tenant.tenantId
+            return await http.post(`${this.url}/getTotalPrice`)
         },
 
         echart: async function (data = {}) {
-            return await http.post(`${this.url}/getEcharts`, data);
+            if (store.state.tenant.tenantId !== "0") data.tenantId = store.state.tenant.tenantId
+            return await http.post(`${this.url}/getEcharts`, data)
         }
     }
 }

+ 45 - 24
src/api/model/system.js

@@ -1,37 +1,30 @@
+import store from "@/store"
 import config from "@/config"
 import http from "@/utils/request"
 
 export default {
-	projectUser: {
-		name: "获取我的项目",
-		url: "/api/projectUserRef/getUserProjectList",
-		get: async function () {
-			return await http.post(this.url);
-		}
-	},
-
     menu: {
         name: "菜单管理",
 		url: "/mes/sysMenu",
 
         build: async function () {
-			return await http.post(`${this.url}/build`);
+			return await http.post(`${this.url}/build`)
 		},
 
 		get: async function (data = {}) {
-			return await http.post(`${this.url}/getList`, data);
+			return await http.post(`${this.url}/getList`, data)
 		},
 
         add: async function (data = {}) {
-            return await http.post(`${this.url}/save`, data);
+            return await http.post(`${this.url}/save`, data)
         },
 
         edit: async function (data = {}) {
-            return await http.post(`${this.url}/update`, data);
+            return await http.post(`${this.url}/update`, data)
         },
 
         del: async function (data = {}) {
-            return await http.post(`${this.url}/remove`, data);
+            return await http.post(`${this.url}/remove`, data)
         }
     },
 
@@ -40,23 +33,25 @@ export default {
 		url: "/mes/sysRole",
 
 		get: async function (data = {}) {
-			return await http.post(`${this.url}/getPage`, data);
+            if (store.state.tenant.tenantId !== "0") data.tenantId = store.state.tenant.tenantId
+			return await http.post(`${this.url}/getPage`, data)
 		},
 
         all: async function (data = {}) {
-			return await http.post(`${this.url}/getList`, data);
+            if (store.state.tenant.tenantId !== "0") data.tenantId = store.state.tenant.tenantId
+			return await http.post(`${this.url}/getList`, data)
 		},
 
         add: async function (data = {}) {
-            return await http.post(`${this.url}/save`, data);
+            return await http.post(`${this.url}/save`, data)
         },
 
         edit: async function (data = {}) {
-            return await http.post(`${this.url}/update`, data);
+            return await http.post(`${this.url}/update`, data)
         },
 
         del: async function (data = {}) {
-            return await http.post(`${this.url}/remove`, data);
+            return await http.post(`${this.url}/remove`, data)
         }
     },
 
@@ -65,11 +60,11 @@ export default {
 		url: "/mes/sysRolesMenus",
         
         get: async function (data = {}) {
-			return await http.post(`${this.url}/getList`, data);
+			return await http.post(`${this.url}/getList`, data)
 		},
 
         edit: async function (data = {}) {
-            return await http.post(`${this.url}/batchSave`, data);
+            return await http.post(`${this.url}/batchSave`, data)
         }
     },
 
@@ -78,19 +73,45 @@ export default {
 		url: "/mes/sysDept",
 
         get: async function (data = {}) {
-			return await http.post(`${this.url}/getList`, data);
+            if (store.state.tenant.tenantId !== "0") data.tenantId = store.state.tenant.tenantId
+			return await http.post(`${this.url}/getList`, data)
+		},
+
+        add: async function (data = {}) {
+            return await http.post(`${this.url}/save`, data)
+        },
+
+        edit: async function (data = {}) {
+            return await http.post(`${this.url}/update`, data)
+        },
+
+        del: async function (data = {}) {
+            return await http.post(`${this.url}/remove`, data)
+        }
+    },
+
+    tenant: {
+        name: "租户管理",
+		url: "/mes/tenant",
+
+		get: async function (data = {}) {
+			return await http.post(`${this.url}/getPage`, data)
+		},
+
+        all: async function (data = {}) {
+			return await http.post(`${this.url}/getList`, data)
 		},
 
         add: async function (data = {}) {
-            return await http.post(`${this.url}/save`, data);
+            return await http.post(`${this.url}/save`, data)
         },
 
         edit: async function (data = {}) {
-            return await http.post(`${this.url}/update`, data);
+            return await http.post(`${this.url}/update`, data)
         },
 
         del: async function (data = {}) {
-            return await http.post(`${this.url}/remove`, data);
+            return await http.post(`${this.url}/remove`, data)
         }
     }
 }

+ 8 - 2
src/components/scFormTable/index.vue

@@ -47,6 +47,7 @@ import domZIndex from "dom-zindex";
 domZIndex.setCurrent(domZIndex.getMax() + 1);
 
 import XEUtils from "xe-utils";
+import store from "@/store";
 import scUploadFile from "@/components/scUpload/file";
 import selectTable from "@/components/scFormTable/detail";
 
@@ -56,6 +57,7 @@ const props = defineProps({
     disabled: { type: Boolean, default: false },
     tableKey: { type: String, default: "" },
     addTemplate: { type: Object, default: () => ({}) },
+    tenantId: { type: String, default: store.state.tenant.tenantId },
     
     layouts: { type: Array, default: () => [["Top", "Form"], ["Toolbar", "Table", "Bottom", "Pager"]] },
     rowKey: { type: String, default: "id" },
@@ -131,7 +133,10 @@ const gridOptions = reactive({
 const selectTableOptions = reactive({
     tableKey: props.tableKey,
     multiple: true,
-    paramsColums: XEUtils.get(props.selectOptions, "paramsColums", []),
+    paramsColums: [
+        ...XEUtils.get(props.selectOptions, "paramsColums", []),
+        { column: "tenantId", defaultValue: computed(() => props.tenantId) }
+    ],
     options: {
         rowKey: props.rowKey,
         ...XEUtils.omit(props.selectOptions, "paramsColums")
@@ -173,7 +178,8 @@ const validateFormTable = async () => {
 }
 
 defineExpose({
-    validateFormTable
+    validateFormTable,
+    clearTable: () => gridOptions.data = []
 });
 </script>
 

+ 21 - 0
src/components/scTable/helper.js

@@ -1,4 +1,5 @@
 import XEUtils from "xe-utils";
+import store from "@/store";
 
 /**
  * 输入框配置
@@ -77,4 +78,24 @@ export const mapFormItemDatePicker = (field, title, config = {}) => ({
         ...XEUtils.omit(config, "props")
     },
     ...XEUtils.omit(config, "props")
+})
+
+/**
+ * 选择框租户
+ * @param field 字段
+ * @param title 标题
+ * @param config 其他配置
+ */
+export const mapFormItemTenant = (config = {}) => ({
+    visible: computed(() => store.state.tenant.tenantId === "0").value,
+    field: "tenantId",
+    title: "所属租户",
+    titlePrefix: { content: "所属租户", icon: "vxe-icon-question-circle-fill" },
+    itemRender: {
+        name: "$form-select",
+        props: { popperClass: "vxe-table-slot--popper", type: "select", filterable: true, clearable: true, placeholder: "请选择所属租户" },
+        options: store.state.tenant.tenants,
+        optionProps: { label: "name", value: "id" },
+        ...config
+    }
 })

+ 0 - 1
src/components/scTable/index.vue

@@ -30,7 +30,6 @@ import XEUtils from "xe-utils";
 import store from "@/store";
 import config from "@/config/table";
 import pagerBatchDel from "./renderer/pager-batch-del";
-import { computed, nextTick, watch, watchEffect } from "vue";
 
 const props = defineProps({
     apiObj: { type: Object, default: () => ({}) },

+ 4 - 0
src/components/scTable/renderer/form-select.vue

@@ -21,6 +21,10 @@ const modelValue = ref(XEUtils.get(props.params.data, props.params.field, null))
 const options = ref(props.renderOpts.options || []);
 const optionProps = reactive(props.renderOpts.optionProps || config.props);
 
+watch(() => props.params.data, value => {
+    if (modelValue.value != XEUtils.get(value, props.params.field, null)) modelValue.value = XEUtils.get(value, props.params.field, null);
+}, { deep: true });
+
 const getRemoteData = async () => {
     if (props.renderOpts.api) {
         loading.value = true;

+ 8 - 1
src/components/scTableInput/index.vue

@@ -16,16 +16,18 @@
         </el-input>
     </div>
 
-    <select-table v-if="dialog" ref="selectTableRef" :tableKey="tableKey" :paramsColums="paramsColums" :options="options" @success="success" @closed="dialog = false"></select-table>
+    <select-table v-if="dialog" ref="selectTableRef" :tableKey="tableKey" :paramsColums="tenantParamsColums" :options="options" @success="success" @closed="dialog = false"></select-table>
 </template>
 
 <script setup>
 import XEUtils from "xe-utils";
+import store from "@/store";
 import selectTable from "@/components/scFormTable/detail";
 
 const $emit = defineEmits(["update:modelValue"]);
 const props = defineProps({
     modelValue: { type: Object, default: () => ({}) },
+    tenantId: { type: String, default: store.state.tenant.tenantId },
     valueKey: { type: String, default: () => "id" },
     tableKey: { type: String, default: () => "" },
     hideShow: { type: Boolean, default: () => false },
@@ -35,6 +37,11 @@ const props = defineProps({
 
 const dialog = ref(false);
 const defaultValue = ref(XEUtils.get(props.modelValue, props.valueKey, null));
+const tenantParamsColums = reactive([...props.paramsColums, { column: "tenantId", defaultValue: computed(() => props.tenantId) }]);
+
+watch(() => props.modelValue, value => {
+    if (defaultValue.value != XEUtils.get(value, props.valueKey, null)) defaultValue.value = XEUtils.get(value, props.valueKey, null);
+}, { deep: true });
 
 const selectTableRef = ref();
 const show = () => {

+ 0 - 94
src/layout/components/probar.vue

@@ -1,94 +0,0 @@
-<template>
-    <el-drawer class="pro-drawer" v-model="visible" :size="800" append-to-body @closed="$emit('closed')">
-        <template #header="{ titleId, titleClass }">
-            <span :id="titleId" :class="titleClass">当前企业:<span>{{ deptData?.name }}</span></span>
-        </template>
-
-        <el-container class="dept-content">
-            <el-aside width="300px">
-                <el-header>企业列表</el-header>
-                <el-main class="nopadding">
-                    <el-scrollbar>
-                        <el-tree node-key="id" :current-node-key="filter.deptId" :data="deptTree" :default-expanded-keys="expandedKeys" highlight-current expand-on-click-node accordion @node-click="data => filter.deptId = data.id">
-                            <template #default="{ data }">
-                                <vxe-text-ellipsis :id="data.id" :title="data.name" class="custom-tree-node" :content="data.name"></vxe-text-ellipsis>
-                            </template>
-                        </el-tree>
-                    </el-scrollbar>
-                </el-main>
-            </el-aside>
-            <el-container>
-                <el-header>项目列表
-                    <el-input v-model="filter.text" placeholder="输入项目名称" clearable></el-input>
-                </el-header>
-                <el-main class="nopadding">
-                    <el-scrollbar>
-                        <el-tree node-key="fpiId" :current-node-key="$store.state.project.projectId" :data="projects" highlight-current @node-click="data => nodeClick(data)">
-                            <template #default="{ data }">
-                                <vxe-text-ellipsis :id="data.fpiId" :title="data.projectName" class="custom-tree-node" :content="data.projectName"></vxe-text-ellipsis>
-                            </template>
-                        </el-tree>
-                    </el-scrollbar>
-                </el-main>
-            </el-container>
-        </el-container>
-	</el-drawer>
-</template>
-
-<script>
-import XEUtils from "xe-utils";
-
-export default {
-    data() {
-        return {
-            visible: false,
-
-            filter: {
-                deptId: XEUtils.toNumber(XEUtils.get(XEUtils.find(this.$TOOL.data.get("PROJECTS"), item => item.fpiId == this.$store.state.project.projectId), "belongDeptId")),
-                text: ""
-            }
-        }
-    },
-
-    computed: {
-        expandedKeys() {
-            return XEUtils.map(XEUtils.toTreeArray(XEUtils.searchTree(this.deptTree, item => item.id == this.filter.deptId)), item => item.id);
-        },
-
-        deptTree() {
-            return XEUtils.first(XEUtils.toArrayTree(this.$TOOL.data.get("DEPTS"), { parentKey: "pid" })).children;
-        },
-
-        projects() {
-            if (this.filter.text) return XEUtils.filter(this.$TOOL.data.get("PROJECTS"), item => item.projectName.includes(this.filter.text));
-            return XEUtils.filter(this.$TOOL.data.get("PROJECTS"), item => item.belongDeptId == this.deptData.id);
-        },
-
-        deptData() {
-            return XEUtils.find(this.$TOOL.data.get("DEPTS"), item => item.id == this.filter.deptId);
-        }
-    },
-
-    methods: {
-        open() {
-            this.visible = true;
-            nextTick(() => XEUtils.arrayEach(document.querySelectorAll(".el-tree-node.is-current"), el => el.scrollIntoView({ block: "center" })));
-            return this;
-        },
-
-        nodeClick(data) {
-            this.$store.commit("SET_projectId", data.fpiId);
-            this.visible = false;
-        },
-    }
-}
-</script>
-
-<style lang="scss" scoped>
-.dept-content {border-top: 1px solid var(--el-border-color-light);}
-.dept-content :deep(.el-header) {height: fit-content;padding: 10px;padding-left: 0;border: none;font-size: 14px;font-weight: bold;color: rgba(0, 0, 0, 0.85);}
-.dept-content .el-aside {display: flex;flex-direction: column;}
-.dept-content .el-aside + .el-container {padding-left: 15px;}
-.dept-content .el-aside + .el-container .el-header {flex-direction: column;align-items: flex-start;}
-.dept-content .el-aside + .el-container .el-header .el-input {width: 200px;margin-top: 10px;}
-</style>

+ 56 - 0
src/layout/components/tenantbar.vue

@@ -0,0 +1,56 @@
+<template>
+    <el-drawer class="tenant-drawer" v-model="visible" title="租户列表" :size="400" append-to-body @closed="$emit('closed')">
+        <el-container>
+            <el-header>
+                <el-input v-model="filterText" placeholder="输入租户名称" clearable></el-input>
+            </el-header>
+            <el-main class="nopadding">
+                <el-scrollbar>
+                    <el-tree node-key="id" :current-node-key="$store.state.tenant.tenantId" :data="tenants" highlight-current @node-click="data => nodeClick(data)">
+                        <template #default="{ data }">
+                            <vxe-text-ellipsis :id="data.id" :title="data.name" class="custom-tree-node" :content="data.name"></vxe-text-ellipsis>
+                        </template>
+                    </el-tree>
+                </el-scrollbar>
+            </el-main>
+        </el-container>
+	</el-drawer>
+</template>
+
+<script>
+import XEUtils from "xe-utils";
+
+export default {
+    data() {
+        return {
+            visible: false,
+            filterText: ""
+        }
+    },
+
+    computed: {
+        tenants() {
+            if (this.filterText) return XEUtils.filter(this.$store.state.tenant.tenants, item => item.name.includes(this.filterText));
+            return this.$store.state.tenant.tenants || [];
+        }
+    },
+
+    methods: {
+        open() {
+            this.visible = true;
+            nextTick(() => XEUtils.arrayEach(document.querySelectorAll(".el-tree-node.is-current"), el => el.scrollIntoView({ block: "center" })));
+            return this;
+        },
+
+        nodeClick(data) {
+            this.$store.commit("SET_tenantId", data.id);
+            this.visible = false;
+        }
+    }
+}
+</script>
+
+<style lang="scss" scoped>
+.el-container .el-header {height: fit-content;padding: 10px;border: none;}
+.el-container .el-header .el-input {width: 200px;}
+</style>

+ 21 - 17
src/layout/components/userbar.vue

@@ -1,10 +1,10 @@
 <template>
 	<div class="user-bar">
-        <!-- <div class="panel-item" @click="showProDrawer">
-            <vxe-text-ellipsis v-if="projectName" :title="projectName" class="project-name" :content="projectName"></vxe-text-ellipsis>
-            <sc-iconify title="切换项目" icon="ep:sort" size="20"></sc-iconify>
+        <div :class="['panel-item', !multipleTenants && 'hide-style']" @click="showTenantDrawer">
+            <vxe-text-ellipsis v-if="tenantName" :title="tenantName" class="tenant-name" :content="tenantName"></vxe-text-ellipsis>
+            <sc-iconify v-if="multipleTenants" title="切换租户" icon="ep:sort" size="20"></sc-iconify>
 		</div>
-        <div class="panel-item hidden-sm-and-down" @click="toScreen">
+        <!-- <div class="panel-item hidden-sm-and-down" @click="toScreen">
             <sc-iconify title="大屏展示" icon="tabler:heart-rate-monitor" size="20"></sc-iconify>
 		</div> -->
         <div class="panel-item hidden-sm-and-down" @click="dialog.search = true">
@@ -25,7 +25,7 @@
 		</el-dropdown>
 	</div>
         
-    <dept-pro-drawer v-if="dialog.project" ref="deptProDrawer" @closed="dialog.project = false"></dept-pro-drawer>
+    <tenant-drawer v-if="dialog.tenant" ref="tenantDrawer" @closed="dialog.tenant = false"></tenant-drawer>
 
 	<el-dialog v-model="dialog.search" title="搜索" :width="700" center destroy-on-close>
 		<search @success="dialog.search = false"></search>
@@ -40,17 +40,17 @@
 
 <script>
 import XEUtils from "xe-utils";
-import deptProDrawer from "./probar";
+import tenantDrawer from "./tenantbar";
 import search from "./search";
 import setting from "./setting";
 import passwordDetail from "./password";
 
 export default {
-    components: { deptProDrawer, search, setting, passwordDetail },
+    components: { tenantDrawer, search, setting, passwordDetail },
     data() {
         return {
             dialog: {
-                project: false,
+                tenant: false,
                 search: false,
                 setting: false,
                 password: false
@@ -59,25 +59,29 @@ export default {
     },
 
     computed: {
-        projectName() {
-            return this.$store.state.project.projectName;
+        tenantName() {
+            return this.$store.state.tenant.tenantName;
+        },
+
+        multipleTenants() {
+            return this.$store.state.tenant.tenants.length > 1
         }
     },
 
     created() {
-        // this.$store.dispatch("fetchProject");
+        this.$store.dispatch("fetchTenant");
     },
 
     methods: {
-        showProDrawer() {
-            if (this.projectName) {
-                this.dialog.project = true;
-                nextTick(() => this.$refs.deptProDrawer.open());
+        showTenantDrawer() {
+            if (this.multipleTenants && this.tenantName) {
+                this.dialog.tenant = true;
+                nextTick(() => this.$refs.tenantDrawer.open());
             }
         },
 
         toScreen() {
-            // if (this.$TOOL.data.get("PROJECT")) window.open(`/easydo/media/#/school?projectId=${this.$TOOL.data.get("PROJECT_ID")}`)
+            // window.open(`/easydo/media/#/`)
         },
         
         handleUser(command) {
@@ -105,6 +109,6 @@ export default {
 
 <style scoped>
 .user-bar {display: flex;align-items: center;height: 100%;}
-.user-bar .panel-item .project-name {max-width: 346px;font-size: 16px;font-weight: bold;}
+.user-bar .panel-item .tenant-name {max-width: 346px;font-size: 16px;font-weight: bold;}
 .user-bar .user-avatar .sc-iconify-icon{top: -1px;}
 </style>

+ 0 - 35
src/store/modules/project.js

@@ -1,35 +0,0 @@
-import XEUtils from "xe-utils";
-import API from "@/api";
-import TOOL from "@/utils/tool";
-
-export default {
-	state: {
-		projectId: TOOL.data.get("PROJECT_ID") || "",
-		projectName: ""
-	},
-
-	mutations: {
-        SET_projectId(state, data) {
-            state.projectId = data
-            state.projectName = XEUtils.get(XEUtils.find(TOOL.data.get("PROJECTS"), item => item.fpiId == data), "projectName")
-            TOOL.data.set("PROJECT_ID", data)
-        }
-	},
-
-    actions: {
-        fetchProject({ state, commit }) {
-            API.system.projectUser.get().then(res => {
-                const projects = XEUtils.filter(res.projectList, item => !!item.belongDeptId)
-                TOOL.data.set("PROJECTS", projects)
-                TOOL.data.set("DEPTS", res.content)
-
-				commit("SET_projectId", state.projectId || XEUtils.get(XEUtils.first(projects), "fpiId"))
-                commit("setRouteShow", true)
-            }).catch(() => {
-                commit("setRouteShow", false)
-                TOOL.data.remove("PROJECTS")
-                TOOL.data.remove("DEPTS")
-            })
-        }
-    }
-}

+ 35 - 0
src/store/modules/tenant.js

@@ -0,0 +1,35 @@
+import XEUtils from "xe-utils";
+import API from "@/api";
+import TOOL from "@/utils/tool";
+
+export default {
+	state: {
+		tenants: [],
+		tenantId: TOOL.data.get("TENANT_ID") || "",
+		tenantName: "",
+	},
+
+	mutations: {
+        SET_tenantId(state, list) {
+            state.tenants = list
+            const tenantData = XEUtils.find(list, item => item.id == state.tenantId)
+            if (!tenantData) state.tenantId = TOOL.data.get("USER_INFO").tenantId
+            state.tenantName = XEUtils.get(tenantData, "name", TOOL.data.get("USER_INFO").tenant.name)
+            TOOL.data.set("TENANT_ID", state.tenantId)
+        }
+	},
+
+    actions: {
+        fetchTenant({ state, commit }) {
+            const query = { orderBy: "id_asc" }
+            XEUtils.get(TOOL.data.get("USER_INFO"), "tenantId", "0") !== "0" && XEUtils.set(query, "id", TOOL.data.get("USER_INFO").tenantId)
+
+            API.system.tenant.all(query).then(res => {
+				commit("SET_tenantId", res)
+                commit("setRouteShow", true)
+            }).catch(() => {
+                commit("setRouteShow", false)
+            })
+        }
+    }
+}

+ 5 - 5
src/style/app.scss

@@ -18,6 +18,8 @@ a,button,input,textarea{-webkit-tap-highlight-color:rgba(0,0,0,0);box-sizing: bo
 .aminui-wrapper {display: flex;flex:1;overflow: auto;}
 .aminui .panel-item {cursor: pointer;display: flex;align-items: center;height: 100%;padding: 0 10px;}
 .aminui .panel-item:hover {background: rgba(255, 255, 255, 0.1);}
+.aminui .panel-item.hide-style {cursor: unset;}
+.aminui .panel-item.hide-style:hover {background: unset;}
 
 /* 全局滚动条样式 */
 .scrollable {-webkit-overflow-scrolling: touch;}
@@ -82,11 +84,9 @@ a,button,input,textarea{-webkit-tap-highlight-color:rgba(0,0,0,0);box-sizing: bo
 .aminui-topbar .right-panel {display: flex;align-items: center;}
 .aminui-topbar .user-bar .panel-item:hover {background: rgba(0, 0, 0, 0.1);}
 
-.el-drawer.rtl.pro-drawer {height: calc(100% - 50px);top: 50px;}
-.el-drawer.rtl.pro-drawer .el-drawer__header {margin-bottom: 0;padding: 15px 15px 0;}
-.el-drawer.rtl.pro-drawer .el-drawer__header .el-drawer__title {font-size: 15px;color: var(--el-text-color-secondary);}
-.el-drawer.rtl.pro-drawer .el-drawer__header .el-drawer__title span {color: var(--el-color-primary);}
-.el-drawer.rtl.pro-drawer .el-drawer__body {padding: 15px;}
+.el-drawer.rtl.tenant-drawer {height: calc(100% - 58px);top: 58px;}
+.el-drawer.rtl.tenant-drawer .el-drawer__header {margin-bottom: 0;padding: 15px 15px 0;}
+.el-drawer.rtl.tenant-drawer .el-drawer__body {padding: 15px;}
 
 .aminui-tags {height:35px;background: #fff;border-bottom: 1px solid #e6e6e6;}
 .aminui-tags ul {display: flex;overflow: hidden;}

+ 6 - 19
src/utils/request.js

@@ -5,7 +5,6 @@ import tool from "@/utils/tool"
 import router from "@/router"
 
 axios.defaults.baseURL = ""
-
 axios.defaults.timeout = sysConfig.TIMEOUT
 
 // HTTP request 拦截器
@@ -23,9 +22,7 @@ axios.interceptors.request.use(
 		Object.assign(config.headers, sysConfig.HEADERS)
 		return config
 	},
-	(error) => {
-		return Promise.reject(error)
-	}
+	(error) => Promise.reject(error)
 )
 
 //FIX 多个API同时401时疯狂弹窗BUG
@@ -93,16 +90,9 @@ var http = {
 	 */
 	get: function (url, params = {}, config = {}) {
 		return new Promise((resolve, reject) => {
-			axios({
-				url,
-				method: "get",
-				params,
-				...config
-			}).then(response => {
-				resolve(response.data)
-			}).catch(error => {
-				reject(error)
-			})
+			axios({ url, method: "get", params, ...config })
+                .then(response => resolve(response.data))
+                .catch(error => reject(error))
 		})
 	},
 
@@ -128,11 +118,8 @@ var http = {
 				data: emptyField.length ? XEUtils.omit(data, emptyField) : data,
 				...config,
 				params
-			}).then(response => {
-				resolve(response.data)
-			}).catch(error => {
-				reject(error)
-			})
+			}).then(response => resolve(response.data))
+              .catch(error => reject(error))
 		})
 	}
 }

+ 12 - 0
src/views/basic/customer/detail.vue

@@ -4,6 +4,14 @@
             <el-collapse v-model="activeNames">
                 <el-collapse-item title="基本信息" name="basic">
                     <el-row>
+                        <el-col v-if="$store.state.tenant.tenantId === '0'" :md="8" :xs="24">
+                            <el-form-item label="所属租户" prop="tenantId">
+                                <el-select v-if="!form.id" v-model="form.tenantId" filterable placeholder="请选择所属租户">
+                                    <el-option v-for="item in $store.state.tenant.tenants" :key="item.id" :label="item.name" :value="item.id"></el-option>
+                                </el-select>
+                                <el-input v-else v-model="tenantName" readonly></el-input>
+                            </el-form-item>
+                        </el-col>
                         <el-col :md="8" :xs="24">
                             <el-form-item label="客户名称" prop="name">
                                 <el-input v-model="form.name" placeholder="请输入客户名称"></el-input>
@@ -86,6 +94,7 @@
 import XEUtils from "xe-utils";
 
 import API from "@/api";
+import store from "@/store";
 import { customerDic } from "@/utils/basicDic";
 import { verifyIdCard } from "@/utils/verificate";
 
@@ -101,8 +110,10 @@ const titleMap = reactive({
     edit: "修改客户"
 });
 
+const tenantName = computed(() => XEUtils.get(XEUtils.find(store.state.tenant.tenants, item => item.id == form.value.tenantId), "name"));
 const form = ref({
     id: null,
+    tenantId: store.state.tenant.tenantId,
     name: null,
     code: null,
     customerType: "customer",
@@ -117,6 +128,7 @@ const form = ref({
     address: null
 });
 const rules = reactive({
+    tenantId: [{ required: true, message: "请选择所属租户" }],
     name: [{ required: true, message: "请输入客户名称" }],
     type: [{ required: true, message: "请选择客户分类" }],
     managerName: [{ required: true, message: "请输入联系人姓名" }],

+ 8 - 2
src/views/basic/customer/index.vue

@@ -29,9 +29,12 @@ import XEUtils from "xe-utils";
 import API from "@/api";
 import TOOL from "@/utils/tool";
 import { statusDic, customerDic } from "@/utils/basicDic";
-import { mapFormItemInput, mapFormItemSelect } from "@/components/scTable/helper";
+import { mapFormItemInput, mapFormItemSelect, mapFormItemTenant } from "@/components/scTable/helper";
 import customerDetail from "./detail";
 
+import store from "@/store";
+watch(() => store.state.tenant.tenantId, () => refreshTable());
+
 const props = defineProps({
     selectable: { type: Boolean, default: false },
     multiple: { type: Boolean, default: false },
@@ -53,6 +56,7 @@ const formConfig = reactive({
         mapFormItemInput("codeLike", "客户编号"),
         mapFormItemSelect("type", "客户分类", { ...selectConfig, options: customerDic.type })
     ] : [
+        mapFormItemTenant({ events: { change: data => XEUtils.merge(formConfig.data, data) } }),
         mapFormItemInput("nameLike", "客户名称"),
         mapFormItemInput("codeLike", "客户编号"),
         mapFormItemSelect("status", "客户状态", selectConfig),
@@ -63,6 +67,7 @@ const formConfig = reactive({
 const paramsColums = reactive([
     { column: "orderBy", defaultValue: "code_asc" },
     { column: "customerType", defaultValue: "customer" },
+    { column: "tenantId" },
     { column: "nameLike" },
     { column: "codeLike" },
     { column: "status" },
@@ -81,6 +86,7 @@ const columns = computed(() => props.selectable ? [
     { type: "html", field: "managerPhone", title: "联系方式", minWidth: 120, sortable: true }
 ] : [
     { type: "seq", fixed: "left", width: 60 },
+    { visible: store.state.tenant.tenantId === "0", type: "html", field: "tenantName", title: "所属租户", fixed: "left", minWidth: 200, sortable: true, formatter: ({ cellValue, row }) => cellValue || XEUtils.get(XEUtils.find(store.state.tenant.tenants, item => item.id == row.tenantId), "name") },
     { type: "html", field: "name", title: "客户名称", fixed: "left", minWidth: 150, sortable: true },
     { type: "html", field: "code", title: "客户编号", fixed: "left", minWidth: 150, sortable: true },
     { field: "status", title: "客户状态", minWidth: 100, editRender: { name: "$cell-tag", options: statusDic } },
@@ -97,7 +103,7 @@ const columns = computed(() => props.selectable ? [
 
 // 显示隐藏 筛选表单
 const xGridTable = ref();
-const refreshTable = (mode = "add") => xGridTable.value.searchData(mode);
+const refreshTable = (mode = "add") => (xGridTable.value.searchData(mode), xGridTable.value.reloadColumn(columns.value));
 
 const customerRef = ref();
 const dialog = ref(false);

+ 12 - 0
src/views/basic/material/detail.vue

@@ -4,6 +4,14 @@
             <el-collapse v-model="activeNames">
                 <el-collapse-item title="基本信息" name="basic">
                     <el-row>
+                        <el-col v-if="$store.state.tenant.tenantId === '0'" :md="8" :xs="24">
+                            <el-form-item label="所属租户" prop="tenantId">
+                                <el-select v-if="!form.id" v-model="form.tenantId" filterable placeholder="请选择所属租户">
+                                    <el-option v-for="item in $store.state.tenant.tenants" :key="item.id" :label="item.name" :value="item.id"></el-option>
+                                </el-select>
+                                <el-input v-else v-model="tenantName" readonly></el-input>
+                            </el-form-item>
+                        </el-col>
                         <el-col :md="8" :xs="24">
                             <el-form-item label="物料名称" prop="name">
                                 <el-input v-model="form.name" placeholder="请输入物料名称"></el-input>
@@ -76,6 +84,7 @@
 import XEUtils from "xe-utils";
 
 import API from "@/api";
+import store from "@/store";
 import { statusDic, materialDic } from "@/utils/basicDic";
 
 const $emit = defineEmits(["success", "closed"]);
@@ -90,8 +99,10 @@ const titleMap = reactive({
     edit: "修改物料"
 });
 
+const tenantName = computed(() => XEUtils.get(XEUtils.find(store.state.tenant.tenants, item => item.id == form.value.tenantId), "name"));
 const form = ref({
     id: null,
+    tenantId: store.state.tenant.tenantId,
     name: null,
     code: null,
     materialType: null,
@@ -102,6 +113,7 @@ const form = ref({
     remark: null
 });
 const rules = reactive({
+    tenantId: [{ required: true, message: "请选择所属租户" }],
     name: [{ required: true, message: "请输入物料名称" }],
     code: [{ required: true, message: "请输入物料编码" }],
     materialType: [{ required: true, message: "请选择物料类型" }],

+ 8 - 2
src/views/basic/material/index.vue

@@ -30,9 +30,12 @@ import XEUtils from "xe-utils";
 import API from "@/api";
 import TOOL from "@/utils/tool";
 import { statusDic, materialDic } from "@/utils/basicDic";
-import { mapFormItemInput, mapFormItemSelect } from "@/components/scTable/helper";
+import { mapFormItemInput, mapFormItemSelect, mapFormItemTenant } from "@/components/scTable/helper";
 import materialDetail from "./detail";
 
+import store from "@/store";
+watch(() => store.state.tenant.tenantId, () => refreshTable());
+
 const props = defineProps({
     selectable: { type: Boolean, default: false },
     multiple: { type: Boolean, default: false },
@@ -53,6 +56,7 @@ const formConfig = reactive({
         mapFormItemInput("nameLike", "物料名称"),
         mapFormItemInput("codeLike", "物料编码")
     ]: [
+        mapFormItemTenant({ events: { change: data => XEUtils.merge(formConfig.data, data) } }),
         mapFormItemInput("nameLike", "物料名称"),
         mapFormItemInput("codeLike", "物料编码"),
         mapFormItemSelect("materialType", "物料类型", selectConfig),
@@ -63,6 +67,7 @@ const formConfig = reactive({
 
 const paramsColums = reactive([
     { column: "orderBy", defaultValue: "code_asc" },
+    { column: "tenantId" },
     { column: "nameLike" },
     { column: "codeLike" },
     { column: "materialType" },
@@ -83,6 +88,7 @@ const columns = computed(() => props.selectable ? [
     { visible: false, type: "html", field: "price", title: "标准售价", minWidth: 100, sortable: true }
 ]: [
     { type: "seq", fixed: "left", width: 60 },
+    { visible: store.state.tenant.tenantId === "0", type: "html", field: "tenantName", title: "所属租户", fixed: "left", minWidth: 200, sortable: true, formatter: ({ cellValue, row }) => cellValue || XEUtils.get(XEUtils.find(store.state.tenant.tenants, item => item.id == row.tenantId), "name") },
     { type: "html", field: "name", title: "物料名称", fixed: "left", minWidth: 150, sortable: true },
     { type: "html", field: "code", title: "物料编码", fixed: "left", minWidth: 150, sortable: true },
     { field: "status", title: "物料状态", minWidth: 100, editRender: { name: "$cell-tag", options: statusDic } },
@@ -98,7 +104,7 @@ const columns = computed(() => props.selectable ? [
 
 // 显示隐藏 筛选表单
 const xGridTable = ref();
-const refreshTable = (mode = "add") => xGridTable.value.searchData(mode);
+const refreshTable = (mode = "add") => (xGridTable.value.searchData(mode), xGridTable.value.reloadColumn(columns.value));
 
 const materialRef = ref();
 const dialog = ref(false);

+ 5 - 1
src/views/basic/qualityPlan/desc.vue

@@ -4,6 +4,7 @@
             <el-collapse v-model="activeNames">
                 <el-collapse-item title="基本信息" name="basic">
                     <el-descriptions :column="3" label-width="140" border>
+                        <el-descriptions-item v-if="$store.state.tenant.tenantId === '0'" label="所属租户" :span="ismobile ? 3 : 1" label-align="right" min-width="120">{{ tenantName }}</el-descriptions-item>
                         <el-descriptions-item label="方案名称" :span="ismobile ? 3 : 1" label-align="right" min-width="120">{{ descData.name }}</el-descriptions-item>
                         <el-descriptions-item label="方案编号" :span="ismobile ? 3 : 1" label-align="right" min-width="120">{{ descData.code }}</el-descriptions-item>
                         <el-descriptions-item label="添加时间" :span="ismobile ? 3 : 1" label-align="right" min-width="120">{{ descData.createTime }}</el-descriptions-item>
@@ -64,8 +65,9 @@ const $emit = defineEmits(["closed"]);
 const visible = ref(false);
 const isSaving = ref(false);
 
-const store = useStore();
+import store from "@/store";
 const ismobile = computed(() => store.state.global.ismobile);
+const tenantName = computed(() => XEUtils.get(XEUtils.find(store.state.tenant.tenants, item => item.id == descData.value.tenantId), "name"));
 
 const activeNames = ref(["basic", "approval"]);
 const mode = ref("detail");
@@ -76,6 +78,7 @@ const titleMap = reactive({
 
 const descData = ref({
     id: null,
+    tenantId: store.state.tenant.tenantId,
     name: null,
     code: null,
     inspectUserName: null,
@@ -92,6 +95,7 @@ const descData = ref({
 });
 
 const rules = reactive({
+    tenantId: [{ required: true, message: "请选择所属租户" }],
     reviewStatus: [{ required: true, validator: (rule, value, callback) => {
         if (value === "pending") return callback(new Error("请选择审批结果"));
         callback();

+ 14 - 2
src/views/basic/qualityPlan/detail.vue

@@ -4,6 +4,14 @@
             <el-collapse v-model="activeNames">
                 <el-collapse-item title="基本信息" name="basic">
                     <el-row>
+                        <el-col v-if="$store.state.tenant.tenantId === '0'" :md="8" :xs="24">
+                            <el-form-item label="所属租户" prop="tenantId">
+                                <el-select v-if="!form.id" v-model="form.tenantId" filterable placeholder="请选择所属租户">
+                                    <el-option v-for="item in $store.state.tenant.tenants" :key="item.id" :label="item.name" :value="item.id"></el-option>
+                                </el-select>
+                                <el-input v-else v-model="tenantName" readonly></el-input>
+                            </el-form-item>
+                        </el-col>
                         <el-col :md="8" :xs="24">
                             <el-form-item label="方案名称" prop="name">
                                 <el-input v-model="form.name" placeholder="请输入方案名称"></el-input>
@@ -17,7 +25,7 @@
                         <el-col :md="8" :xs="24">
                             <el-form-item label="质检人员" prop="inspectUserId">
                                 <el-select v-model="form.inspectUserId" placeholder="请选择质检人员">
-                                    <el-option v-for="item in users" :key="item.id" :label="item.nickName" :value="item.id" />
+                                    <el-option v-for="item in users.filter(r => r.tenantId == form.tenantId)" :key="item.id" :label="item.nickName" :value="item.id" />
                                 </el-select>
                             </el-form-item>
                         </el-col>
@@ -47,7 +55,7 @@
                         <el-col :md="8" :xs="24">
                             <el-form-item label="审批人员">
                                 <el-select v-model="form.reviewUserId" clearable placeholder="请选择审批人员">
-                                    <el-option v-for="item in users" :key="item.id" :label="item.nickName" :value="item.id" />
+                                    <el-option v-for="item in users.filter(r => r.tenantId == form.tenantId)" :key="item.id" :label="item.nickName" :value="item.id" />
                                 </el-select>
                             </el-form-item>
                         </el-col>
@@ -84,6 +92,7 @@
 import XEUtils from "xe-utils";
 
 import API from "@/api";
+import store from "@/store";
 import { qualityPlanTypeDic } from "@/utils/basicDic";
 import scUploadFile from "@/components/scUpload/file";
 
@@ -100,8 +109,10 @@ const titleMap = reactive({
 });
 
 const users = ref([]);
+const tenantName = computed(() => XEUtils.get(XEUtils.find(store.state.tenant.tenants, item => item.id == form.value.tenantId), "name"));
 const form = ref({
     id: null,
+    tenantId: store.state.tenant.tenantId,
     name: null,
     code: null,
     inspectUserId: null,
@@ -114,6 +125,7 @@ const form = ref({
     fileList: []
 });
 const rules = reactive({
+    tenantId: [{ required: true, message: "请选择所属租户" }],
     name: [{ required: true, message: "请输入方案名称" }],
     inspectUserId: [{ required: true, message: "请选择质检人员" }],
     sampleRate: [{ required: true, message: "请输入抽检比例" }],

+ 8 - 2
src/views/basic/qualityPlan/index.vue

@@ -34,10 +34,13 @@ import XEUtils from "xe-utils";
 import API from "@/api";
 import TOOL from "@/utils/tool";
 import { reviewStatusDic, qualityPlanTypeDic } from "@/utils/basicDic";
-import { mapFormItemInput, mapFormItemSelect, mapFormItemDatePicker } from "@/components/scTable/helper";
+import { mapFormItemInput, mapFormItemSelect, mapFormItemDatePicker, mapFormItemTenant } from "@/components/scTable/helper";
 import planDetail from "./detail";
 import planDesc from "./desc";
 
+import store from "@/store";
+watch(() => store.state.tenant.tenantId, () => refreshTable());
+
 const selectConfig = reactive({
     options: reviewStatusDic,
     events: {
@@ -58,6 +61,7 @@ const daterangeConfig = reactive({
 const formConfig = reactive({
     data: {},
     items: [
+        mapFormItemTenant({ events: { change: data => XEUtils.merge(formConfig.data, data) } }),
         mapFormItemInput("nameLike", "方案名称"),
         mapFormItemInput("codeLike", "方案编号"),
         mapFormItemSelect("reviewStatus", "审批状态", selectConfig),
@@ -68,6 +72,7 @@ const formConfig = reactive({
 
 const paramsColums = reactive([
     { column: "orderBy", defaultValue: "code_desc" },
+    { column: "tenantId" },
     { column: "nameLike" },
     { column: "codeLike" },
     { column: "reviewStatus" },
@@ -78,6 +83,7 @@ const paramsColums = reactive([
 
 const columns = reactive([
     { type: "seq", fixed: "left", width: 60 },
+    { visible: computed(() => store.state.tenant.tenantId === "0"), type: "html", field: "tenantName", title: "所属租户", minWidth: 200, sortable: true, formatter: ({ cellValue, row }) => cellValue || XEUtils.get(XEUtils.find(store.state.tenant.tenants, item => item.id == row.tenantId), "name") },
     { type: "html", field: "name", title: "方案名称", fixed: "left", minWidth: 150, sortable: true },
     { field: "code", title: "方案编号", fixed: "left", minWidth: 150, sortable: true, className: "vxe-table-link-cell", slots: { default: "code_link" } },
     { visible: false, type: "html", field: "inspectUserName", title: "质检人员", minWidth: 120, sortable: true },
@@ -93,7 +99,7 @@ const columns = reactive([
 
 // 显示隐藏 筛选表单
 const xGridTable = ref();
-const refreshTable = (mode = "add") => xGridTable.value.searchData(mode);
+const refreshTable = (mode = "add") => (xGridTable.value.searchData(mode), xGridTable.value.reloadColumn(columns));
 
 const planRef = ref();
 const planDescRef = ref();

+ 4 - 3
src/views/home/index.vue

@@ -1,11 +1,12 @@
 <template>
-    <el-main class="home-container">首页{{projectId}}
-    </el-main>
+    <el-main class="home-container"></el-main>
 </template>
 
 <script setup>
 import XEUtils from "xe-utils";
-const projectId = computed(() => useStore().state.project.projectId);
+import store from "@/store";
+
+const tenantId = computed(() => store.state.tenant.tenantId);
 </script>
 
 <style lang="scss" scoped>

+ 4 - 1
src/views/process/line/desc.vue

@@ -4,6 +4,7 @@
             <el-collapse v-model="activeNames">
                 <el-collapse-item title="基本信息" name="basic">
                     <el-descriptions :column="3" label-width="140" border>
+                        <el-descriptions-item v-if="$store.state.tenant.tenantId === '0'" label="所属租户" :span="ismobile ? 3 : 1" label-align="right" min-width="120">{{ tenantName }}</el-descriptions-item>
                         <el-descriptions-item label="工艺路线名称" :span="ismobile ? 3 : 1" label-align="right" min-width="120">{{ descData.name }}</el-descriptions-item>
                         <el-descriptions-item label="工艺路线编号" :span="ismobile ? 3 : 1" label-align="right" min-width="120">{{ descData.code }}</el-descriptions-item>
                         <el-descriptions-item label="添加时间" :span="ismobile ? 3 : 1" label-align="right" min-width="120">{{ descData.createTime }}</el-descriptions-item>
@@ -38,8 +39,9 @@ import scUploadFile from "@/components/scUpload/file";
 const $emit = defineEmits(["closed"]);
 const visible = ref(false);
 
-const store = useStore();
+import store from "@/store";
 const ismobile = computed(() => store.state.global.ismobile);
+const tenantName = computed(() => XEUtils.get(XEUtils.find(store.state.tenant.tenants, item => item.id == descData.value.tenantId), "name"));
 
 const options = reactive({
     disabled: true,
@@ -51,6 +53,7 @@ const options = reactive({
 const activeNames = ref(["basic", "line", "plan"]);
 const descData = ref({
     id: null,
+    tenantId: store.state.tenant.tenantId,
     parentId: null,
     name: null,
     code: null,

+ 14 - 12
src/views/process/line/detail.vue

@@ -4,6 +4,14 @@
             <el-collapse v-model="activeNames">
                 <el-collapse-item title="基本信息" name="basic">
                     <el-row>
+                        <el-col v-if="$store.state.tenant.tenantId === '0'" :md="8" :xs="24">
+                            <el-form-item label="所属租户" prop="tenantId">
+                                <el-select v-if="!form.id" v-model="form.tenantId" filterable placeholder="请选择所属租户" @change="tenantChange">
+                                    <el-option v-for="item in $store.state.tenant.tenants" :key="item.id" :label="item.name" :value="item.id"></el-option>
+                                </el-select>
+                                <el-input v-else v-model="tenantName" readonly></el-input>
+                            </el-form-item>
+                        </el-col>
                         <el-col :md="8" :xs="24">
                             <el-form-item label="工艺路线名称" prop="name">
                                 <el-input v-model="form.name" placeholder="请输入工艺路线名称"></el-input>
@@ -26,28 +34,17 @@
                                 <el-input v-model="form.version" :readonly="mode == 'edit'" placeholder="v1.0.0"></el-input>
                             </el-form-item>
                         </el-col>
-                        <!-- <el-col :md="8" :xs="24">
-                            <el-form-item label="适用产品">
-                                <el-radio-group v-model="form.processType">
-                                    <el-radio label="所有产品" value="all"></el-radio>
-                                    <el-radio label="指定产品" value="specify"></el-radio>
-                                </el-radio-group>
-                            </el-form-item>
-                        </el-col> -->
                     </el-row>
                 </el-collapse-item>
 
                 <el-collapse-item title="加工路线" name="line">
-                    <sc-form-table ref="formTableRef" v-model="form.detailList" v-bind="tableOptions"></sc-form-table>
+                    <sc-form-table ref="formTableRef" v-model="form.detailList" v-bind="tableOptions" :tenantId="form.tenantId"></sc-form-table>
                 </el-collapse-item>
 
                 <!-- <el-collapse-item title="质检方案" name="plan">
                     inspectProgramId
                 </el-collapse-item> -->
 
-                <!-- <el-collapse-item title="适用产品" name="product">
-                </el-collapse-item> -->
-
                 <el-collapse-item title="其他说明" name="other">
                     <el-row>
                         <el-col :xs="24">
@@ -85,6 +82,7 @@
 import XEUtils from "xe-utils";
 
 import API from "@/api";
+import store from "@/store";
 import { processDic } from "@/utils/basicDic";
 import { tableOptions } from "./main";
 import scUploadFile from "@/components/scUpload/file";
@@ -102,6 +100,7 @@ const titleMap = reactive({
     upgrade: "发布新版本"
 });
 
+const tenantName = computed(() => XEUtils.get(XEUtils.find(store.state.tenant.tenants, item => item.id == form.value.tenantId), "name"));
 const oldData = reactive({
     code: null,
     version: null,
@@ -110,6 +109,7 @@ const oldData = reactive({
 const form = ref({
     id: null,
     parentId: null,
+    tenantId: store.state.tenant.tenantId,
     name: null,
     code: null,
     timeUnit: "minute",
@@ -119,6 +119,7 @@ const form = ref({
     fileList: []
 });
 const rules = reactive({
+    tenantId: [{ required: true, message: "请选择所属租户" }],
     code: [{ required: true, validator: (rule, value, callback) => {
         if (mode.value === "upgrade" && value === oldData.code) return callback(new Error("请清空或更新工艺路线编号"));
         callback();
@@ -153,6 +154,7 @@ const setData = (data, modeKey = "edit") => {
 
 const formRef = ref();
 const formTableRef = ref();
+const tenantChange = () => formTableRef.value.clearTable();
 const submit = () => {
     formRef.value.validate(async valid => {
         if (valid) {

+ 1 - 0
src/views/process/line/history.vue

@@ -18,6 +18,7 @@ const visible = ref(false);
 const setData = data => {
     visible.value = true;
     paramsColums.push({ column: "parentId", defaultValue: data.id });
+    paramsColums.push({ column: "tenantId", defaultValue: data.tenantId });
 }
 
 defineExpose({

+ 16 - 5
src/views/process/line/index.vue

@@ -47,11 +47,14 @@ import XEUtils from "xe-utils";
 import API from "@/api";
 import TOOL from "@/utils/tool";
 import { statusDic, processDic } from "@/utils/basicDic";
-import { mapFormItemInput, mapFormItemSelect, mapFormItemDatePicker } from "@/components/scTable/helper";
+import { mapFormItemInput, mapFormItemSelect, mapFormItemDatePicker, mapFormItemTenant } from "@/components/scTable/helper";
 import lineDetail from "./detail";
 import lineDesc from "./desc";
 import versionHistory from "./history";
 
+import store from "@/store";
+watch(() => store.state.tenant.tenantId, () => refreshTable());
+
 const props = defineProps({
     hidePageHeader: { type: Boolean, default: false },
     hideHandler: { type: Boolean, default: false },
@@ -79,17 +82,24 @@ const daterangeConfig = reactive({
 
 const formConfig = reactive({
     data: {},
-    items: [
+    items: computed(() => props.hideHandler ? [
+        mapFormItemInput("nameLike", "工艺路线名称"),
+        mapFormItemInput("codeLike", "工艺路线编号"),
+        mapFormItemSelect("status", "工艺路线状态", selectConfig),
+        mapFormItemDatePicker("createTime", "创建日期", daterangeConfig)
+    ] : [
+        mapFormItemTenant({ events: { change: data => XEUtils.merge(formConfig.data, data) } }),
         mapFormItemInput("nameLike", "工艺路线名称"),
         mapFormItemInput("codeLike", "工艺路线编号"),
         mapFormItemSelect("status", "工艺路线状态", selectConfig),
         mapFormItemDatePicker("createTime", "创建日期", daterangeConfig)
-    ]
+    ])
 });
 
 const paramsColums = reactive([
     { column: "orderBy", defaultValue: "code_asc" },
     { column: "parentId", defaultValue: "0" },
+    { column: "tenantId" },
     { column: "nameLike" },
     { column: "codeLike" },
     { column: "status" },
@@ -100,6 +110,7 @@ const paramsColums = reactive([
 
 const columns = reactive([
     { type: "seq", fixed: "left", width: 60 },
+    { visible: computed(() => !props.hideHandler && store.state.tenant.tenantId === "0"), type: "html", field: "tenantName", title: "所属租户", fixed: "left", minWidth: 200, sortable: true, formatter: ({ cellValue, row }) => cellValue || XEUtils.get(XEUtils.find(store.state.tenant.tenants, item => item.id == row.tenantId), "name") },
     { type: "html", field: "name", title: "工艺路线名称", fixed: "left", minWidth: 150, sortable: true },
     { field: "code", title: "工艺路线编号", fixed: "left", minWidth: 150, sortable: true, className: "vxe-table-link-cell", slots: { default: "code_link" } },
     { visible: !props.hideHandler, field: "status", title: "工艺路线状态", minWidth: 120, editRender: { name: "$cell-tag", options: statusDic } },
@@ -113,7 +124,7 @@ const columns = reactive([
 
 // 显示隐藏 筛选表单
 const xGridTable = ref();
-const refreshTable = (mode = "add") => xGridTable.value.searchData(mode);
+const refreshTable = (mode = "add") => (xGridTable.value.searchData(mode), xGridTable.value.reloadColumn(columns));
 
 const lineRef = ref();
 const lineDescRef = ref();
@@ -179,7 +190,7 @@ const table_change = row => {
         confirmButtonText: "确定",
         cancelButtonText: "取消"
     }).then(() => {
-        API.process.line.edit({ id: row.id, status }).then(() => {
+        API.process.line.edit({ ...XEUtils.pick(row, "id", "detailList"), status }).then(() => {
             ElMessage.success("操作成功");
             refreshTable();
         });

+ 13 - 1
src/views/process/stage/detail.vue

@@ -4,6 +4,14 @@
             <el-collapse v-model="activeNames">
                 <el-collapse-item title="基本信息" name="basic">
                     <el-row>
+                        <el-col v-if="$store.state.tenant.tenantId === '0'" :md="8" :xs="24">
+                            <el-form-item label="所属租户" prop="tenantId">
+                                <el-select v-if="!form.id" v-model="form.tenantId" filterable placeholder="请选择所属租户">
+                                    <el-option v-for="item in $store.state.tenant.tenants" :key="item.id" :label="item.name" :value="item.id"></el-option>
+                                </el-select>
+                                <el-input v-else v-model="tenantName" readonly></el-input>
+                            </el-form-item>
+                        </el-col>
                         <el-col :md="8" :xs="24">
                             <el-form-item label="工序名称" prop="name">
                                 <el-input v-model="form.name" placeholder="请输入工序名称"></el-input>
@@ -38,7 +46,7 @@
                                 </el-radio-group>
                             </el-form-item>
                         </el-col>
-                        <el-col :xs="24">
+                        <el-col :md="16" :xs="24">
                             <el-form-item label="工资默认计算方式" label-width="150" required>
                                 <el-radio-group v-model="form.calculateMethod">
                                     <el-radio v-for="(label, key) in processDic.calcMethod" :key="key" :label="label" :value="key"></el-radio>
@@ -78,6 +86,7 @@
 import XEUtils from "xe-utils";
 
 import API from "@/api";
+import store from "@/store";
 import { processDic } from "@/utils/basicDic";
 import scUploadFile from "@/components/scUpload/file";
 
@@ -93,8 +102,10 @@ const titleMap = reactive({
     edit: "修改工序"
 });
 
+const tenantName = computed(() => XEUtils.get(XEUtils.find(store.state.tenant.tenants, item => item.id == form.value.tenantId), "name"));
 const form = ref({
     id: null,
+    tenantId: store.state.tenant.tenantId,
     name: null,
     code: null,
     category: null,
@@ -106,6 +117,7 @@ const form = ref({
     fileList: []
 });
 const rules = reactive({
+    tenantId: [{ required: true, message: "请选择所属租户" }],
     name: [{ required: true, message: "请输入工序名称" }],
     directorPhone: [{ pattern: /^\d{11}$/, message: "请输入11位手机号码" }]
 });

+ 8 - 2
src/views/process/stage/index.vue

@@ -30,9 +30,12 @@ import XEUtils from "xe-utils";
 import API from "@/api";
 import TOOL from "@/utils/tool";
 import { statusDic, processDic } from "@/utils/basicDic";
-import { mapFormItemInput, mapFormItemSelect, mapFormItemDatePicker } from "@/components/scTable/helper";
+import { mapFormItemInput, mapFormItemSelect, mapFormItemDatePicker, mapFormItemTenant } from "@/components/scTable/helper";
 import stageDetail from "./detail";
 
+import store from "@/store";
+watch(() => store.state.tenant.tenantId, () => refreshTable());
+
 const props = defineProps({
     selectable: { type: Boolean, default: false },
     multiple: { type: Boolean, default: false },
@@ -63,6 +66,7 @@ const formConfig = reactive({
         mapFormItemInput("nameLike", "工序名称"),
         mapFormItemInput("codeLike", "工序编号")
     ]: [
+        mapFormItemTenant({ events: { change: data => XEUtils.merge(formConfig.data, data) } }),
         mapFormItemInput("nameLike", "工序名称"),
         mapFormItemInput("codeLike", "工序编号"),
         mapFormItemSelect("status", "工序状态", selectConfig),
@@ -73,6 +77,7 @@ const formConfig = reactive({
 
 const paramsColums = reactive([
     { column: "orderBy", defaultValue: "code_asc" },
+    { column: "tenantId" },
     { column: "nameLike" },
     { column: "codeLike" },
     { column: "status" },
@@ -91,6 +96,7 @@ const columns = computed(() => props.selectable ? [
     { type: "html", field: "calculateMethod", title: "工资默认计算方式", minWidth: 140, sortable: true, formatter: ({ cellValue }) => XEUtils.get(processDic.calcMethod, cellValue, cellValue) }
 ] : [
     { type: "seq", fixed: "left", width: 60 },
+    { visible: store.state.tenant.tenantId === "0", type: "html", field: "tenantName", title: "所属租户", fixed: "left", minWidth: 200, sortable: true, formatter: ({ cellValue, row }) => cellValue || XEUtils.get(XEUtils.find(store.state.tenant.tenants, item => item.id == row.tenantId), "name") },
     { type: "html", field: "name", title: "工序名称", fixed: "left", minWidth: 150, sortable: true },
     { type: "html", field: "code", title: "工序编号", fixed: "left", minWidth: 150, sortable: true },
     { field: "status", title: "工序状态", minWidth: 100, editRender: { name: "$cell-tag", options: statusDic } },
@@ -106,7 +112,7 @@ const columns = computed(() => props.selectable ? [
 
 // 显示隐藏 筛选表单
 const xGridTable = ref();
-const refreshTable = (mode = "add") => xGridTable.value.searchData(mode);
+const refreshTable = (mode = "add") => (xGridTable.value.searchData(mode), xGridTable.value.reloadColumn(columns.value));
 
 const stageRef = ref();
 const dialog = ref(false);

+ 4 - 1
src/views/production/bom/desc.vue

@@ -4,6 +4,7 @@
             <el-collapse v-model="activeNames">
                 <el-collapse-item title="基本信息" name="basic">
                     <el-descriptions :column="3" label-width="140" border>
+                        <el-descriptions-item v-if="$store.state.tenant.tenantId === '0'" label="所属租户" :span="ismobile ? 3 : 1" label-align="right" min-width="120">{{ tenantName }}</el-descriptions-item>
                         <el-descriptions-item label="BOM单编号" :span="ismobile ? 3 : 1" label-align="right" min-width="120">{{ descData.bomCode }}</el-descriptions-item>
                         <el-descriptions-item label="BOM单状态" :span="ismobile ? 3 : 1" label-align="right" min-width="120">{{ XEUtils.get(statusDic, descData.status, descData.status) }}</el-descriptions-item>
                         <el-descriptions-item label="添加时间" :span="ismobile ? 3 : 1" label-align="right" min-width="120">{{ descData.createTime }}</el-descriptions-item>
@@ -38,12 +39,14 @@ import { tableOptions } from "./main";
 const $emit = defineEmits(["closed"]);
 const visible = ref(false);
 
-const store = useStore();
+import store from "@/store";
 const ismobile = computed(() => store.state.global.ismobile);
+const tenantName = computed(() => XEUtils.get(XEUtils.find(store.state.tenant.tenants, item => item.id == descData.value.tenantId), "name"));
 
 const activeNames = ref(["basic", "material", "children"]);
 const descData = ref({
     id: null,
+    tenantId: store.state.tenant.tenantId,
     bomCode: null,
     material: {
         code: null,

+ 18 - 3
src/views/production/bom/detail.vue

@@ -3,6 +3,16 @@
         <el-form ref="formRef" :model="form" :rules="rules" label-width="100">
             <el-collapse v-model="activeNames">
                 <el-collapse-item v-if="form.parentId == 0" title="基本信息" name="basic">
+                    <el-row>
+                        <el-col v-if="$store.state.tenant.tenantId === '0'" :md="8" :xs="24">
+                            <el-form-item label="所属租户" prop="tenantId">
+                                <el-select v-if="!form.id" v-model="form.tenantId" filterable placeholder="请选择所属租户" @change="tenantChange">
+                                    <el-option v-for="item in $store.state.tenant.tenants" :key="item.id" :label="item.name" :value="item.id"></el-option>
+                                </el-select>
+                                <el-input v-else v-model="tenantName" readonly></el-input>
+                            </el-form-item>
+                        </el-col>
+                    </el-row>
                     <el-row>
                         <el-col :md="8" :xs="24">
                             <el-form-item label="BOM单编号" required>
@@ -21,7 +31,7 @@
                     <el-row>
                         <el-col :md="8" :xs="24">
                             <el-form-item label="产品编号" prop="material.code">
-                                <sc-table-input v-model="form.material" :hideShow="!!form.id" placeholder="选择产品" v-bind="selectOptions"></sc-table-input>
+                                <sc-table-input v-model="form.material" :hideShow="!!form.id" placeholder="选择产品" v-bind="selectOptions" :tenantId="form.tenantId"></sc-table-input>
                             </el-form-item>
                         </el-col>
                         <el-col :md="8" :xs="24">
@@ -48,7 +58,7 @@
                 </el-collapse-item>
 
                 <el-collapse-item title="子件信息" name="children">
-                    <sc-form-table ref="formTableRef" v-model="form.childrenList" v-bind="tableOptions"></sc-form-table>
+                    <sc-form-table ref="formTableRef" v-model="form.childrenList" v-bind="tableOptions" :tenantId="form.tenantId"></sc-form-table>
                 </el-collapse-item>
             </el-collapse>
         </el-form>
@@ -64,6 +74,7 @@
 import XEUtils from "xe-utils";
 
 import API from "@/api";
+import store from "@/store";
 import { mapFormItemInput } from "@/components/scTable/helper";
 import { materialDic } from "@/utils/basicDic";
 import { tableOptions, selectOptions } from "./main";
@@ -79,9 +90,11 @@ const titleMap = reactive({
     edit: "修改BOM单"
 });
 
+const tenantName = computed(() => XEUtils.get(XEUtils.find(store.state.tenant.tenants, item => item.id == form.value.tenantId), "name"));
 const form = ref({
     id: null,
     parentId: "0",
+    tenantId: store.state.tenant.tenantId,
     bomCode: null,
     material: {
         code: null,
@@ -93,6 +106,7 @@ const form = ref({
     remark: null
 });
 const rules = reactive({
+    tenantId: [{ required: true, message: "请选择所属租户" }],
     "material.code": [{ required: true, message: "请选择产品" }]
 });
 
@@ -111,6 +125,7 @@ const setData = async (data = {}, model = "add") => {
 
 const formRef = ref();
 const formTableRef = ref();
+const tenantChange = () => (formTableRef.value.clearTable(), XEUtils.clear(form.value.material, null));
 const submit = () => {
     formRef.value.validate(async valid => {
         if (valid) {
@@ -118,7 +133,7 @@ const submit = () => {
 
             if (await formTableRef.value.validateFormTable()) {
                 const data = XEUtils.omit(form.value, "material", "childrenList");
-                const childrenList = XEUtils.map(form.value.childrenList, item => ({ id: XEUtils.get(item, "childrenId", null), emptyField: item.remark ? []: ["remark"], materialCode: item.code, materialName: item.name, ...XEUtils.pick(item, "quantity", "remark") }));
+                const childrenList = XEUtils.map(form.value.childrenList, item => ({ id: XEUtils.get(item, "childrenId", null), emptyField: item.remark ? []: ["remark"], materialCode: item.code, materialName: item.name, ...XEUtils.pick(item, "tenantId", "quantity", "remark") }));
                 XEUtils.set(data, "materialName", form.value.material.name);
                 XEUtils.set(data, "materialCode", form.value.material.code);
                 XEUtils.set(data, "childrenList", childrenList);

+ 8 - 2
src/views/production/bom/index.vue

@@ -42,10 +42,13 @@ import XEUtils from "xe-utils";
 import API from "@/api";
 import TOOL from "@/utils/tool";
 import { statusDic } from "@/utils/basicDic";
-import { mapFormItemInput, mapFormItemSelect, mapFormItemDatePicker } from "@/components/scTable/helper";
+import { mapFormItemInput, mapFormItemSelect, mapFormItemDatePicker, mapFormItemTenant } from "@/components/scTable/helper";
 import bomDetail from "./detail";
 import bomDesc from "./desc";
 
+import store from "@/store";
+watch(() => store.state.tenant.tenantId, () => refreshTable());
+
 const treeConfig = reactive({
     lazy: true,
     hasChildField: "isHaveChildren",
@@ -72,6 +75,7 @@ const daterangeConfig = reactive({
 const formConfig = reactive({
     data: {},
     items: [
+        mapFormItemTenant({ events: { change: data => XEUtils.merge(formConfig.data, data) } }),
         mapFormItemInput("bomCodeLike", "BOM单编号"),
         mapFormItemInput("materialCodeLike", "产品编码"),
         mapFormItemInput("materialNameLike", "产品名称"),
@@ -83,6 +87,7 @@ const formConfig = reactive({
 const paramsColums = reactive([
     { column: "orderBy", defaultValue: "bomCode_asc" },
     { column: "parentId", defaultValue: "0" },
+    { column: "tenantId" },
     { column: "bomCodeLike" },
     { column: "materialCodeLike" },
     { column: "materialNameLike" },
@@ -93,6 +98,7 @@ const paramsColums = reactive([
 
 const columns = reactive([
     { type: "seq", fixed: "left", width: 80 },
+    { visible: computed(() => store.state.tenant.tenantId === "0"), type: "html", field: "tenantName", title: "所属租户", fixed: "left", minWidth: 200, sortable: true, formatter: ({ cellValue, row }) => cellValue || XEUtils.get(XEUtils.find(store.state.tenant.tenants, item => item.id == row.tenantId), "name") },
     { type: "html", field: "materialCode", title: "产品编码", fixed: "left", minWidth: 150, treeNode: true, headerAlign: "center", align: "left", sortable: true },
     { type: "html", field: "materialName", title: "产品名称", fixed: "left", minWidth: 150, sortable: true },
     { field: "bomCode", title: "BOM单编号", minWidth: 150, sortable: true, className: "vxe-table-link-cell", slots: { default: "code_link" } },
@@ -104,7 +110,7 @@ const columns = reactive([
 
 // 显示隐藏 筛选表单
 const xGridTable = ref();
-const refreshTable = (mode = "add") => xGridTable.value.searchData(mode);
+const refreshTable = (mode = "add") => (xGridTable.value.searchData(mode), xGridTable.value.reloadColumn(columns));
 
 const bomRef = ref();
 const bomDescRef = ref();

+ 1 - 1
src/views/production/bom/main.js

@@ -27,7 +27,7 @@ export const tableOptions = reactive({
         ]
     },
 
-    add_success: (oldValue, newValue) => XEUtils.map(newValue, (item, index) => XEUtils.pick(item, "id", "code", "name", "specification", "unit"))
+    add_success: (oldValue, newValue) => XEUtils.map(newValue, (item, index) => XEUtils.pick(item, "id", "tenantId", "code", "name", "specification", "unit"))
 })
 
 export const selectOptions = reactive({

+ 4 - 1
src/views/sales/order/desc.vue

@@ -4,6 +4,7 @@
             <el-collapse v-model="activeNames">
                 <el-collapse-item title="基本信息" name="basic">
                     <el-descriptions :column="3" label-width="140" border>
+                        <el-descriptions-item v-if="$store.state.tenant.tenantId === '0'" label="所属租户" :span="ismobile ? 3 : 1" label-align="right" min-width="120">{{ tenantName }}</el-descriptions-item>
                         <el-descriptions-item label="单据编号" :span="ismobile ? 3 : 1" label-align="right" min-width="120">{{ descData.code }}</el-descriptions-item>
                         <el-descriptions-item label="合同编号" :span="ismobile ? 3 : 1" label-align="right">{{ descData.contractNo }}</el-descriptions-item>
                         <el-descriptions-item label="单据日期" :span="ismobile ? 3 : 1" label-align="right" min-width="120">{{ descData.orderDate }}</el-descriptions-item>
@@ -46,12 +47,14 @@ import scUploadFile from "@/components/scUpload/file";
 const $emit = defineEmits(["closed"]);
 const visible = ref(false);
 
-const store = useStore();
+import store from "@/store";
 const ismobile = computed(() => store.state.global.ismobile);
+const tenantName = computed(() => XEUtils.get(XEUtils.find(store.state.tenant.tenants, item => item.id == descData.value.tenantId), "name"));
 
 const activeNames = ref(["basic", "material", "amount"]);
 const descData = ref({
     id: null,
+    tenantId: store.state.tenant.tenantId,
     code: null,
     orderDate: null,
     customerName: null,

+ 15 - 2
src/views/sales/order/detail.vue

@@ -4,6 +4,14 @@
             <el-collapse v-model="activeNames">
                 <el-collapse-item title="基本信息" name="basic">
                     <el-row>
+                        <el-col v-if="$store.state.tenant.tenantId === '0'" :md="8" :xs="24">
+                            <el-form-item label="所属租户" prop="tenantId">
+                                <el-select v-if="!form.id" v-model="form.tenantId" filterable placeholder="请选择所属租户" @change="tenantChange">
+                                    <el-option v-for="item in $store.state.tenant.tenants" :key="item.id" :label="item.name" :value="item.id"></el-option>
+                                </el-select>
+                                <el-input v-else v-model="tenantName" readonly></el-input>
+                            </el-form-item>
+                        </el-col>
                         <el-col :md="8" :xs="24">
                             <el-form-item label="单据编号" required>
                                 <el-input v-model="form.code" :readonly="!!form.id" maxlength="50" show-word-limit clearable placeholder="不填将自动生成"></el-input>
@@ -16,7 +24,7 @@
                         </el-col>
                         <el-col :md="8" :xs="24">
                             <el-form-item label="客户" prop="customer.id">
-                                <sc-table-input v-model="form.customer" placeholder="选择客户" v-bind="selectOptions"></sc-table-input>
+                                <sc-table-input v-model="form.customer" placeholder="选择客户" v-bind="selectOptions" :tenantId="form.tenantId"></sc-table-input>
                             </el-form-item>
                         </el-col>
                         <el-col :md="8" :xs="24">
@@ -45,7 +53,7 @@
                 </el-collapse-item>
 
                 <el-collapse-item title="产品信息" name="material">
-                    <sc-form-table ref="formTableRef" v-model="form.childrenList" v-bind="tableOptions"></sc-form-table>
+                    <sc-form-table ref="formTableRef" v-model="form.childrenList" v-bind="tableOptions" :tenantId="form.tenantId"></sc-form-table>
                 </el-collapse-item>
 
                 <el-collapse-item title="金额信息" name="amount">
@@ -98,6 +106,7 @@ import moment from "moment";
 import XEUtils from "xe-utils";
 
 import API from "@/api";
+import store from "@/store";
 import { tableOptions, selectOptions } from "./main";
 import scUploadFile from "@/components/scUpload/file";
 
@@ -114,8 +123,10 @@ const titleMap = reactive({
 });
 
 const users = ref([]);
+const tenantName = computed(() => XEUtils.get(XEUtils.find(store.state.tenant.tenants, item => item.id == form.value.tenantId), "name"));
 const form = ref({
     id: null,
+    tenantId: store.state.tenant.tenantId,
     code: null,
     orderDate: moment().format("YYYY-MM-DD"),
     customer: { id: null, name: null },
@@ -131,6 +142,7 @@ const form = ref({
     fileList: []
 });
 const rules = reactive({
+    tenantId: [{ required: true, message: "请选择所属租户" }],
     orderDate: [{ required: true, message: "请选择单据日期" }],
     "customer.id": [{ required: true, message: "请选择客户" }],
     planReceiveDate: [{ required: true, message: "请选择预计交期" }],
@@ -159,6 +171,7 @@ const setData = data => {
 
 const formRef = ref();
 const formTableRef = ref();
+const tenantChange = () => (formTableRef.value.clearTable(), XEUtils.clear(form.value.material, null));
 const submit = () => {
     formRef.value.validate(async valid => {
         if (valid) {

+ 8 - 2
src/views/sales/order/index.vue

@@ -29,10 +29,13 @@ import XEUtils from "xe-utils";
 import API from "@/api";
 import TOOL from "@/utils/tool";
 import { salesDic } from "@/utils/basicDic";
-import { mapFormItemInput, mapFormItemSelect, mapFormItemDatePicker } from "@/components/scTable/helper";
+import { mapFormItemInput, mapFormItemSelect, mapFormItemDatePicker, mapFormItemTenant } from "@/components/scTable/helper";
 import orderDetail from "./detail";
 import orderDesc from "./desc";
 
+import store from "@/store";
+watch(() => store.state.tenant.tenantId, () => refreshTable());
+
 const selectConfig = reactive({
     options: salesDic.orderStatus,
     events: {
@@ -61,6 +64,7 @@ const daterangeConfig = reactive({
 const formConfig = reactive({
     data: {},
     items: [
+        mapFormItemTenant({ events: { change: data => XEUtils.merge(formConfig.data, data) } }),
         mapFormItemInput("codeLike", "单据编号"),
         mapFormItemSelect("status", "单据状态", selectConfig),
         mapFormItemSelect("customerId", "客户", customerConfig),
@@ -71,6 +75,7 @@ const formConfig = reactive({
 
 const paramsColums = reactive([
     { column: "orderBy", defaultValue: "orderDate_asc" },
+    { column: "tenantId" },
     { column: "codeLike" },
     { column: "status" },
     { column: "customerId" },
@@ -81,6 +86,7 @@ const paramsColums = reactive([
 
 const columns = reactive([
     { type: "seq", fixed: "left", width: 60 },
+    { visible: computed(() => store.state.tenant.tenantId === "0"), type: "html", field: "tenantName", title: "所属租户", fixed: "left", minWidth: 200, sortable: true, formatter: ({ cellValue, row }) => cellValue || XEUtils.get(XEUtils.find(store.state.tenant.tenants, item => item.id == row.tenantId), "name") },
     { field: "code", title: "单据编号", fixed: "left", minWidth: 150, sortable: true, className: "vxe-table-link-cell", slots: { default: "code_link" } },
     { type: "html", field: "orderDate", title: "单据日期", fixed: "left", minWidth: 120, sortable: true },
     { field: "status", title: "单据状态", minWidth: 120, editRender: { name: "$cell-tag", options: salesDic.orderStatus } },
@@ -100,7 +106,7 @@ const columns = reactive([
 
 // 显示隐藏 筛选表单
 const xGridTable = ref();
-const refreshTable = (mode = "add") => xGridTable.value.searchData(mode);
+const refreshTable = (mode = "add") => (xGridTable.value.searchData(mode), xGridTable.value.reloadColumn(columns));
 
 const orderRef = ref();
 const orderDescRef = ref();

+ 2 - 0
src/views/sales/performance/components/bar.vue

@@ -6,6 +6,8 @@
 import moment from "moment";
 import XEUtils from "xe-utils";
 import API from "@/api";
+import store from "@/store";
+watch(() => store.state.tenant.tenantId, () => getEcharts());
 
 const option = reactive({
     title: { text: "近7日实际销售额", textStyle: { fontSize: 16 } },

+ 20 - 17
src/views/sales/performance/components/line.vue

@@ -1,12 +1,13 @@
 <template>
     <el-header>
-        <el-radio-group v-model="query.type" @change="radioChange">
+        <el-radio-group v-model="query.type" @change="getEcharts">
             <el-radio-button v-for="(item, key) in radioDic" :key="key" :label="key">{{ item.label }}</el-radio-button>
         </el-radio-group>
         
-        <el-date-picker v-if="query.type == 'year'" v-model="query.year" type="yearrange" :clearable="false" range-separator="至" @change="getEcharts" />
-        <el-date-picker v-if="query.type == 'month'" v-model="query.month" type="monthrange" :clearable="false" range-separator="至" @change="getEcharts" />
-        <el-date-picker v-if="query.type == 'day'" v-model="query.day" type="daterange" :clearable="false" range-separator="至" @change="getEcharts" />
+        <vxe-date-range-picker v-if="query.type == 'year'" v-model="query.year" type="year" transfer @change="pickerChange"></vxe-date-range-picker>
+        <vxe-date-range-picker v-if="query.type == 'quarter'" v-model="query.quarter" type="quarter" label-format="yyyy-Qq" transfer @change="pickerChange"></vxe-date-range-picker>
+        <vxe-date-range-picker v-if="query.type == 'month'" v-model="query.month" type="month" transfer @change="pickerChange"></vxe-date-range-picker>
+        <vxe-date-range-picker v-if="query.type == 'day'" v-model="query.day" transfer @change="pickerChange"></vxe-date-range-picker>
     </el-header>
 
     <div class="echart-panel">
@@ -19,19 +20,19 @@ import moment from "moment";
 import XEUtils from "xe-utils";
 
 import API from "@/api";
+import store from "@/store";
 import { radioDic, lineSeriesDefault } from "../main";
+watch(() => store.state.tenant.tenantId, () => getEcharts());
 
 const query = reactive({
     type: "year",
-    year: [moment().subtract(5, "year"), moment()],
+    year: [moment().subtract(5, "year").startOf("year").format("YYYY-MM-DD"), moment().endOf("year").format("YYYY-MM-DD")],
+    quarter: [moment().startOf("year").format("YYYY-MM-DD"), moment().endOf("year").format("YYYY-MM-DD")],
     month: [moment().startOf("year").format("YYYY-MM-DD"), moment().endOf("year").format("YYYY-MM-DD")],
-    day: [moment().startOf("month").format("YYYY-MM-DD"), moment().endOf("month").format("YYYY-MM-DD")],
+    day: [moment().startOf("month").format("YYYY-MM-DD"), moment().endOf("month").format("YYYY-MM-DD")]
 });
 
-const radioChange = (e) => {
-    option.series = []
-    getEcharts()
-}
+const pickerChange = ({ value }) => XEUtils.every(value, item => item) && getEcharts();
 
 const option = reactive({
     color: computed(() => query.type == "day" ? ["#36CE9E"] : []),
@@ -61,22 +62,22 @@ const option = reactive({
 });
 
 const getEcharts = () => {
-    const [beginDate, endDate] = radioDic[query.type].valueFormat(query[query.type]);
+    const beginDate = XEUtils.first(query[query.type]), endDate = moment(XEUtils.last(query[query.type])).endOf(query.type).format("YYYY-MM-DD");
     option.xAxis.data = radioDic[query.type].generateXAxis(beginDate, endDate);
 
     API.sales.performance.echart({ type: query.type, beginDate, endDate }).then(res => {
         if (query.type == "day") {
             option.series = [{
                 ...lineSeriesDefault[1],
-                data: XEUtils.map(option.xAxis.data, date => XEUtils.get(XEUtils.find(res.actualList, item => item.date == date), "price", 0)),
+                data: XEUtils.map(option.xAxis.data, date => XEUtils.get(XEUtils.find(res.actualList, item => item.date == date), "price", 0))
             }];
         } else {
             option.series = [{
                 ...lineSeriesDefault[0],
-                data: XEUtils.map(option.xAxis.data, date => XEUtils.get(XEUtils.find(res.planList, item => item.date2 == date), "price", 0)),
+                data: XEUtils.map(option.xAxis.data, date => XEUtils.get(XEUtils.find(res.planList, item => item.date2 == date), "price", 0))
             }, {
                 ...lineSeriesDefault[1],
-                data: XEUtils.map(option.xAxis.data, date => XEUtils.get(XEUtils.find(res.actualList, item => item.date2 == date), "price", 0)),
+                data: XEUtils.map(option.xAxis.data, date => XEUtils.get(XEUtils.find(res.actualList, item => item.date2 == date), "price", 0))
             }];
         }
     }).catch(() => {
@@ -88,10 +89,10 @@ const getEcharts = () => {
         } else {
             option.series = [{
                 ...lineSeriesDefault[0],
-                data: new Array(option.xAxis.data.length).fill(0),
+                data: new Array(option.xAxis.data.length).fill(0)
             }, {
                 ...lineSeriesDefault[1],
-                data: new Array(option.xAxis.data.length).fill(0),
+                data: new Array(option.xAxis.data.length).fill(0)
             }];
         }
     });
@@ -103,6 +104,8 @@ getEcharts();
 <style scoped>
 .el-header {height: fit-content;padding: 0;border: none;}
 .el-header :deep(.el-radio-group) {flex-wrap: nowrap;}
-.el-header :deep(.el-date-editor.el-range-editor) {flex-grow: 0;width: 260px;}
+.el-header :deep(.vxe-date-range-picker) {width: 240px;}
+.el-header :deep(.vxe-date-range-picker) .vxe-date-range-picker--inner {text-align: center;}
+
 .echart-panel {flex: 1;}
 </style>

+ 2 - 0
src/views/sales/performance/components/pie.vue

@@ -6,6 +6,8 @@
 import moment from "moment";
 import XEUtils from "xe-utils";
 import API from "@/api";
+import store from "@/store";
+watch(() => store.state.tenant.tenantId, () => getEcharts());
 
 const option = reactive({
     color: ["#409EFF", "#36CE9E", "#edb00d","#fc8452"],

+ 2 - 0
src/views/sales/performance/index.vue

@@ -40,7 +40,9 @@
 <script setup>
 import XEUtils from "xe-utils";
 import API from "@/api";
+import store from "@/store";
 import allComps from "./components";
+watch(() => store.state.tenant.tenantId, () => getCensus());
 
 const census = reactive({
     planSalePriceByYear: 0,

+ 12 - 6
src/views/sales/performance/main.js

@@ -3,7 +3,6 @@ import moment from "moment";
 export const radioDic = {
     year: {
         label: "年度",
-        valueFormat: value => [moment(value[0]).format("YYYY-01-01"), moment(value[1]).format("YYYY-12-31")],
         generateXAxis: (begin, end) => {
             const list = [];
             for (let year = moment(begin).year(); year <= moment(end).year(); year++) {
@@ -12,12 +11,20 @@ export const radioDic = {
             return list;
         }
     },
-    // quarter: {
-    //     label: "季度",
-    // },
+    quarter: {
+        label: "季度",
+        generateXAxis: (begin, end) => {
+            const list = [];
+            let current = moment(begin);
+            while (current.isSameOrBefore(end)) {
+                list.push(`${current.year()}-Q${current.quarter()}`);
+                current.add(1, "quarter");
+            }
+            return list;
+        }
+    },
     month: {
         label: "月度",
-        valueFormat: value => [moment(value[0]).format("YYYY-MM-01"), moment(value[1]).endOf("month").format("YYYY-MM-DD")],
         generateXAxis: (begin, end) => {
             const list = [];
             let current = moment(begin);
@@ -30,7 +37,6 @@ export const radioDic = {
     },
     day: {
         label: "天数",
-        valueFormat: value => [moment(value[0]).format("YYYY-MM-DD"), moment(value[1]).format("YYYY-MM-DD")],
         generateXAxis: (begin, end) => {
             const list = [];
             let current = moment(begin);

+ 12 - 0
src/views/sales/plan/detail.vue

@@ -4,6 +4,14 @@
             <el-collapse v-model="activeNames">
                 <el-collapse-item title="基本信息" name="basic">
                     <el-row>
+                        <el-col v-if="$store.state.tenant.tenantId === '0'" :md="8" :xs="24">
+                            <el-form-item label="所属租户" prop="tenantId">
+                                <el-select v-if="!form.id" v-model="form.tenantId" filterable placeholder="请选择所属租户">
+                                    <el-option v-for="item in $store.state.tenant.tenants" :key="item.id" :label="item.name" :value="item.id"></el-option>
+                                </el-select>
+                                <el-input v-else v-model="tenantName" readonly></el-input>
+                            </el-form-item>
+                        </el-col>
                         <el-col :md="8" :xs="24">
                             <el-form-item label="计划名称" prop="name">
                                 <el-input v-model="form.name" placeholder="请输入计划名称"></el-input>
@@ -74,6 +82,7 @@ import moment from "moment";
 import XEUtils from "xe-utils";
 
 import API from "@/api";
+import store from "@/store";
 import { salesDic } from "@/utils/basicDic";
 
 const $emit = defineEmits(["success", "closed"]);
@@ -89,8 +98,10 @@ const titleMap = reactive({
 
 const parentType = ref(null);
 
+const tenantName = computed(() => XEUtils.get(XEUtils.find(store.state.tenant.tenants, item => item.id == form.value.tenantId), "name"));
 const form = ref({
     id: null,
+    tenantId: store.state.tenant.tenantId,
     parentId: "0",
     name: null,
     code: null,
@@ -103,6 +114,7 @@ const form = ref({
     children: []
 });
 const rules = reactive({
+    tenantId: [{ required: true, message: "请选择所属租户" }],
     name: [{ required: true, message: "请输入计划名称" }],
     type: [{ required: true, message: "请选择计划类型" }],
     year: [{ required: true, message: "请选择计划年份" }],

+ 8 - 2
src/views/sales/plan/index.vue

@@ -27,9 +27,12 @@ import XEUtils from "xe-utils";
 import API from "@/api";
 import TOOL from "@/utils/tool";
 import { salesDic } from "@/utils/basicDic";
-import { mapFormItemInput, mapFormItemSelect, mapFormItemDatePicker } from "@/components/scTable/helper";
+import { mapFormItemInput, mapFormItemSelect, mapFormItemDatePicker, mapFormItemTenant } from "@/components/scTable/helper";
 import planDetail from "./detail";
 
+import store from "@/store";
+watch(() => store.state.tenant.tenantId, () => refreshTable());
+
 const formatStatus = row => {
     if (moment().isBefore(row.beginDate)) return "pending";
     if (moment().isAfter(row.endDate)) return "finished";
@@ -58,6 +61,7 @@ const formConfig = reactive({
         beginDate: [moment().startOf("year").format("YYYY-MM-DD"), moment().format("YYYY-MM-DD")]
     },
     items: [
+        mapFormItemTenant({ events: { change: data => XEUtils.merge(formConfig.data, data) } }),
         mapFormItemInput("nameLike", "计划名称"),
         mapFormItemInput("codeLike", "计划编号"),
         mapFormItemSelect("type", "计划类型", selectConfig),
@@ -68,6 +72,7 @@ const formConfig = reactive({
 const options = reactive({
     paramsColums: [
         { column: "orderBy", defaultValue: "beginDate_asc" },
+        { column: "tenantId" },
         { column: "nameLike" },
         { column: "codeLike" },
         { column: "type" },
@@ -78,6 +83,7 @@ const options = reactive({
     pagerConfig : { enabled: false },
     columns: [
         { type: "seq", fixed: "left", width: 80 },
+        { visible: computed(() => store.state.tenant.tenantId === "0"), type: "html", field: "tenantName", title: "所属租户", fixed: "left", minWidth: 200, sortable: true, formatter: ({ cellValue, row }) => cellValue || XEUtils.get(XEUtils.find(store.state.tenant.tenants, item => item.id == row.tenantId), "name") },
         { type: "html", field: "name", title: "计划名称", fixed: "left", minWidth: 160, treeNode: true, headerAlign: "center", align: "left", sortable: true },
         { type: "html", field: "code", title: "计划编号", fixed: "left", minWidth: 150, sortable: true },
         { type: "html", field: "type", title: "计划类型", minWidth: 120, sortable: true, formatter: ({ cellValue }) => XEUtils.get(salesDic.planType, cellValue, cellValue) },
@@ -94,7 +100,7 @@ const options = reactive({
 
 // 显示隐藏 筛选表单
 const xGridTable = ref();
-const refreshTable = () => xGridTable.value.searchData();
+const refreshTable = () => (xGridTable.value.searchData(), xGridTable.value.reloadColumn(options.columns));
 
 const planRef = ref();
 const dialog = ref(false);

+ 27 - 9
src/views/system/dept/detail.vue

@@ -1,8 +1,14 @@
 <template>
     <el-dialog v-model="visible" :title="titleMap[mode]" width="480" :close-on-click-modal="false" @closed="$emit('closed')">
         <el-form ref="formRef" :model="form" :rules="rules" label-width="120">
+            <el-form-item v-if="$store.state.tenant.tenantId === '0'" label="所属租户" prop="tenantId">
+                <el-select v-if="!form.id" v-model="form.tenantId" filterable placeholder="请选择所属租户">
+                    <el-option v-for="item in $store.state.tenant.tenants" :key="item.id" :label="item.name" :value="item.id"></el-option>
+                </el-select>
+                <el-input v-else v-model="tenantName" readonly></el-input>
+			</el-form-item>
             <el-form-item label="上级部门" prop="pid">
-				<el-tree-select v-model="form.pid" :node-key="treeProps.value" :data="deptTree" :props="treeProps" :default-expanded-keys="expandedKeys" check-strictly placeholder="上级部门"></el-tree-select>
+				<el-tree-select v-model="form.pid" :node-key="treeProps.value" :data="treeData" :props="treeProps" :default-expanded-keys="expandedKeys" check-strictly placeholder="上级部门"></el-tree-select>
 			</el-form-item>
 			<el-form-item label="部门名称" prop="name">
 				<el-input v-model="form.name" placeholder="请输入部门名称"></el-input>
@@ -32,6 +38,7 @@
 import XEUtils from "xe-utils";
 
 import API from "@/api";
+import store from "@/store";
 const $emit = defineEmits(["success", "closed"]);
 const visible = ref(false);
 const isSaving = ref(false);
@@ -42,20 +49,30 @@ const titleMap = reactive({
     edit: "修改部门"
 });
 
-const deptTree = ref([]);
-const expandedKeys = computed(() => {
-    if (mode.value == "add") return [];
-    return XEUtils.map(XEUtils.get(XEUtils.findTree(deptTree.value, item => item.id == form.value.pid), "nodes", []), item => item.id).slice(0, -1);
-});
+const depts = ref([]);
 const treeProps = reactive({
     label: "name",
     value: "id",
-    disabled: data => data.id == form.value.id
+    disabled: data => {
+        if (data.id == form.value.id) XEUtils.mapTree(data.children, item => item.disabled = true);
+        return data.disabled || data.id == form.value.id;
+    }
+});
+const treeData = computed(() => {
+    const filterTree = XEUtils.filter(depts.value, item => item.tenantId == form.value.tenantId);
+    if (mode.value == "add") form.value.pid = XEUtils.first(XEUtils.toArrayTree(filterTree, { parentKey: "pid" }))?.id || 1;
+    return XEUtils.toArrayTree(filterTree, { parentKey: "pid" });
+});
+const expandedKeys = computed(() => {
+    if (mode.value == "add") return [];
+    return XEUtils.map(XEUtils.get(XEUtils.findTree(treeData.value, item => item.id == form.value.pid), "nodes", []), item => item.id).slice(0, -1);
 });
 
+const tenantName = computed(() => XEUtils.get(XEUtils.find(store.state.tenant.tenants, item => item.id == form.value.tenantId), "name"));
 const form = ref({
     id: null,
     pid: 1,
+    tenantId: store.state.tenant.tenantId,
     name: null,
     firmFunctionary: null,
     firmFunctionaryPhone: null,
@@ -63,6 +80,7 @@ const form = ref({
     remark: null
 });
 const rules = reactive({
+    tenantId: [{ required: true, message: "请选择所属租户" }],
     pid: [{ required: true, message: "请选择上级部门" }],
     name: [{ required: true, message: "请输入部门名称" }],
     firmFunctionaryPhone: [{ pattern: /^\d{11}$/, message: "请输入11位手机号码" }],
@@ -85,7 +103,7 @@ const submit = () => {
                 isSaving.value = false;
                 ElMessage.success("操作成功");
                 visible.value = false;
-                $emit("success", mode.value);
+                $emit("success");
             }).catch(() => isSaving.value = false);
         } else {
             return false;
@@ -93,7 +111,7 @@ const submit = () => {
     });
 }
 
-const fetchDept = () => API.system.dept.get({ orderBy: "deptSort_asc" }).then(res => deptTree.value = XEUtils.toArrayTree(res, { parentKey: "pid" })).catch(() => deptTree.value = []);
+const fetchDept = () => API.system.dept.get({ orderBy: "deptSort_asc" }).then(res => depts.value = res).catch(() => depts.value = []);
 fetchDept();
 
 defineExpose({

+ 14 - 6
src/views/system/dept/index.vue

@@ -4,7 +4,7 @@
 
         <scTable ref="xGridTable" :apiObj="$API.system.dept" v-bind="options">
             <template #action="{ row }">
-                <template v-if="row.pid != 0">
+                <template v-if="!(row.pid == 0 || (row.pid == 1 && row.tenantId != 0))">
                     <el-button type="primary" link @click="table_edit(row)">
                         <template #icon><sc-iconify icon="ant-design:edit-outlined"></sc-iconify></template>修改
                     </el-button>
@@ -23,33 +23,41 @@
 import XEUtils from "xe-utils";
 
 import API from "@/api";
-import { mapFormItemInput } from "@/components/scTable/helper";
+import { mapFormItemInput, mapFormItemTenant } from "@/components/scTable/helper";
 import deptDetail from "./detail";
 
+import store from "@/store";
+watch(() => store.state.tenant.tenantId, () => refreshTable());
+
 const options = reactive({
     formConfig: {
         data: {},
-        items: [mapFormItemInput("nameLike", "部门名称")]
+        items: [
+            mapFormItemTenant({ events: { change: data => XEUtils.merge(options.formConfig.data, data) } }),
+            mapFormItemInput("nameLike", "部门名称")
+        ]
     },
     paramsColums: [
         { column: "orderBy", defaultValue: "deptSort_asc" },
+        { column: "tenantId" },
         { column: "nameLike" }
     ],
     treeConfig: { transform: true, parentField: "pid" },
     columns: [
         { type: "seq", width: 60 },
-        { type: "html", field: "name", title: "部门名称", minWidth: 200, treeNode: true, sortable: true },
+        { type: "html", field: "name", title: "部门名称", minWidth: 200, treeNode: true, sortable: true, headerAlign: "center", align: "left" },
+        { visible: computed(() => store.state.tenant.tenantId === "0"), type: "html", field: "tenantName", title: "所属租户", minWidth: 200, sortable: true, formatter: ({ cellValue, row }) => cellValue || XEUtils.get(XEUtils.find(store.state.tenant.tenants, item => item.id == row.tenantId), "name") },
         { type: "html", field: "firmFunctionary", title: "负责人", minWidth: 160, sortable: true },
         { type: "html", field: "firmFunctionaryPhone", title: "负责人电话", minWidth: 160, sortable: true },
         { type: "html", field: "remark", title: "备注", minWidth: 300, sortable: true },
-        { title: "操作", fixed: "right", width: 140, align: "center", slots: { default: "action" } }
+        { title: "操作", fixed: "right", width: 140, slots: { default: "action" } }
     ],
     pagerConfig: { enabled: false }
 });
 
 // 显示隐藏 筛选表单
 const xGridTable = ref();
-const refreshTable = (mode = "add") => xGridTable.value.searchData(mode);
+const refreshTable = () => (xGridTable.value.searchData(), xGridTable.value.reloadColumn(options.columns));
 
 const deptRef = ref();
 const dialog = ref(false);

+ 5 - 2
src/views/system/role/bind.vue

@@ -21,6 +21,7 @@
 import XEUtils from "xe-utils";
 
 import API from "@/api";
+import TOOL from "@/utils/tool";
 const $emit = defineEmits(["success", "closed"]);
 const visible = ref(false);
 const loading = ref(false);
@@ -40,13 +41,15 @@ const setData = data => {
 }
 
 const fetchBindMenu = () => {
+    const mode = TOOL.data.get("USER_INFO").tenantId === "0" ? "get" : "build";
+
     loading.value = true;
     Promise.all([
-        API.system.menu.get({ orderBy: "menuSort_asc" }),
+        API.system.menu[mode](),
         API.system.roleMenu.get({ roleId: form.value.id })
     ]).then(res => {
         loading.value = false;
-        menuTree.value = XEUtils.toArrayTree(XEUtils.first(res), { parentKey: "pid" });
+        menuTree.value = XEUtils.toArrayTree(XEUtils.first(res), { parentKey: "pid", sortKey: "menuSort" });
         form.value.menuIdList = XEUtils.map(XEUtils.last(res), item => item.menuId);
     }).catch(() => {
         loading.value = false;

+ 10 - 0
src/views/system/role/detail.vue

@@ -1,6 +1,12 @@
 <template>
     <el-dialog v-model="visible" :title="titleMap[mode]" width="480" :close-on-click-modal="false" @closed="$emit('closed')">
         <el-form ref="formRef" :model="form" :rules="rules" label-width="120">
+            <el-form-item v-if="$store.state.tenant.tenantId === '0'" label="所属租户" prop="tenantId">
+                <el-select v-if="!form.id" v-model="form.tenantId" filterable placeholder="请选择所属租户">
+                    <el-option v-for="item in $store.state.tenant.tenants" :key="item.id" :label="item.name" :value="item.id"></el-option>
+                </el-select>
+                <el-input v-else v-model="tenantName" readonly></el-input>
+			</el-form-item>
             <el-form-item label="角色名称" prop="name">
                 <el-input v-model="form.name" placeholder="请输入角色名称"></el-input>
             </el-form-item>
@@ -23,6 +29,7 @@
 import XEUtils from "xe-utils";
 
 import API from "@/api";
+import store from "@/store";
 const $emit = defineEmits(["success", "closed"]);
 const visible = ref(false);
 const isSaving = ref(false);
@@ -33,13 +40,16 @@ const titleMap = reactive({
     edit: "修改角色"
 });
 
+const tenantName = computed(() => XEUtils.get(XEUtils.find(store.state.tenant.tenants, item => item.id == form.value.tenantId), "name"));
 const form = ref({
     id: null,
+    tenantId: store.state.tenant.tenantId,
     name: null,
     level: 1,
     description: null
 });
 const rules = reactive({
+    tenantId: [{ required: true, message: "请选择所属租户" }],
     name: [{ required: true, message: "请输入角色名称" }],
     level: [{ required: true, message: "请输入角色级别" }]
 });

+ 10 - 3
src/views/system/role/index.vue

@@ -25,33 +25,40 @@
 import XEUtils from "xe-utils";
 
 import API from "@/api";
-import { mapFormItemInput } from "@/components/scTable/helper";
+import TOOL from "@/utils/tool";
+import { mapFormItemInput, mapFormItemTenant } from "@/components/scTable/helper";
 import roleDetail from "./detail";
 import bindDetail from "./bind";
 
+import store from "@/store";
+watch(() => store.state.tenant.tenantId, () => refreshTable());
+
 const formConfig = reactive({
     data: {},
     items: [
+        mapFormItemTenant({ events: { change: data => XEUtils.merge(formConfig.data, data) } }),
         mapFormItemInput("nameLike", "角色名称")
     ]
 });
 
 const paramsColums = reactive([
     { column: "orderBy", defaultValue: "id_desc" },
+    { column: "tenantId" },
     { column: "nameLike" }
 ]);
 
 const columns = reactive([
     { type: "seq", width: 60 },
+    { visible: computed(() => store.state.tenant.tenantId === "0"), type: "html", field: "tenantName", title: "所属租户", minWidth: 200, sortable: true, formatter: ({ cellValue, row }) => cellValue || XEUtils.get(XEUtils.find(store.state.tenant.tenants, item => item.id == row.tenantId), "name") },
     { type: "html", field: "name", title: "角色名称", minWidth: 160, sortable: true },
     { type: "html", field: "level", title: "角色级别", minWidth: 160, sortable: true },
     { type: "html", field: "description", title: "描述信息", minWidth: 300, sortable: true },
-    { title: "操作", fixed: "right", width: 220, align: "center", slots: { default: "action" } }
+    { title: "操作", fixed: "right", width: 220, slots: { default: "action" } }
 ]);
 
 // 显示隐藏 筛选表单
 const xGridTable = ref();
-const refreshTable = (mode = "add") => xGridTable.value.searchData(mode);
+const refreshTable = (mode = "add") => (xGridTable.value.searchData(mode), xGridTable.value.reloadColumn(columns));
 
 const roleRef = ref();
 const dialog = reactive({

+ 79 - 0
src/views/system/tenant/detail.vue

@@ -0,0 +1,79 @@
+<template>
+    <el-dialog v-model="visible" :title="titleMap[mode]" width="480" :close-on-click-modal="false" @closed="$emit('closed')">
+        <el-form ref="formRef" :model="form" :rules="rules" label-width="120">
+            <el-form-item label="租户名称" prop="name">
+                <el-input v-model="form.name" placeholder="请输入租户名称"></el-input>
+            </el-form-item>
+            <el-form-item label="联系人姓名">
+                <el-input v-model="form.managerName" clearable placeholder="请输入联系人姓名"></el-input>
+            </el-form-item>
+            <el-form-item label="联系方式" prop="managerPhone">
+                <el-input v-model="form.managerPhone" clearable placeholder="请输入联系方式"></el-input>
+            </el-form-item>
+        </el-form>
+
+        <template #footer>
+            <el-button auto-insert-space @click="visible = false">取消</el-button>
+            <el-button :loading="isSaving" type="primary" auto-insert-space @click="submit">保存</el-button>
+        </template>
+    </el-dialog>
+</template>
+
+<script setup>
+import XEUtils from "xe-utils";
+
+import API from "@/api";
+const $emit = defineEmits(["success", "closed"]);
+const visible = ref(false);
+const isSaving = ref(false);
+
+const mode = ref("add");
+const titleMap = reactive({
+    add: "新增租户",
+    edit: "修改租户"
+});
+
+const form = ref({
+    id: null,
+    name: null,
+    managerName: null,
+    managerPhone: null
+});
+const rules = reactive({
+    name: [{ required: true, message: "请输入租户名称" }],
+    managerPhone: [{ pattern: /^\d{11}$/, message: "请输入11位手机号码" }]
+});
+
+const open = () => visible.value = true;
+const setData = data => {
+    open();
+    mode.value = "edit";
+    XEUtils.objectEach(form.value, (_, key) => XEUtils.set(form.value, key, XEUtils.get(data, key)));
+}
+
+const formRef = ref();
+const submit = () => {
+    formRef.value.validate(valid => {
+        if (valid) {
+            isSaving.value = true;
+            API.system.tenant[mode.value](form.value).then(res => {
+                isSaving.value = false;
+                ElMessage.success("操作成功");
+                visible.value = false;
+                $emit("success", mode.value);
+            }).catch(() => isSaving.value = false);
+        } else {
+            return false;
+        }
+    });
+}
+
+defineExpose({
+    open,
+    setData
+});
+</script>
+
+<style scoped>
+.el-form {padding-right: 34px;}
+</style>

+ 113 - 0
src/views/system/tenant/index.vue

@@ -0,0 +1,113 @@
+<template>
+	<el-container class="is-vertical">
+        <sc-page-header @add="table_add"></sc-page-header>
+
+        <scTable ref="xGridTable" :apiObj="$API.system.tenant" :formConfig="formConfig" :paramsColums="paramsColums" :columns="columns">
+            <template #action="{ row }">
+                <el-button type="primary" link @click="table_edit(row)">
+                    <template #icon><sc-iconify icon="ant-design:edit-outlined"></sc-iconify></template>修改
+                </el-button>
+                <el-button v-if="row.status == 'enable'" type="primary" link @click="table_change(row)">
+                    <template #icon><sc-iconify icon="material-symbols:lock-outline"></sc-iconify></template>停用
+                </el-button>
+                <el-button v-else type="primary" link @click="table_change(row)">
+                    <template #icon><sc-iconify icon="material-symbols:lock-open-outline"></sc-iconify></template>启用
+                </el-button>
+                <el-button type="primary" link @click="table_del(row)">
+                    <template #icon><sc-iconify icon="ant-design:delete-outlined"></sc-iconify></template>删除
+                </el-button>
+            </template>
+        </scTable>
+	</el-container>
+
+    <tenant-detail v-if="dialog" ref="tenantRef" @success="refreshTable" @closed="dialog = false"></tenant-detail>
+</template>
+
+<script setup>
+import XEUtils from "xe-utils";
+
+import API from "@/api";
+import store from "@/store";
+import { statusDic } from "@/utils/basicDic";
+import { mapFormItemInput, mapFormItemSelect } from "@/components/scTable/helper";
+import tenantDetail from "./detail";
+
+const selectConfig = reactive({
+    options: statusDic,
+    events: {
+        change: data => XEUtils.merge(formConfig.data, data)
+    }
+});
+
+const formConfig = reactive({
+    data: {},
+    items: [
+        mapFormItemInput("nameLike", "租户名称"),
+        mapFormItemSelect("status", "租户状态", selectConfig)
+    ]
+});
+
+const paramsColums = reactive([
+    { column: "orderBy", defaultValue: "id_asc" },
+    { column: "nameLike" },
+    { column: "status" }
+]);
+
+const columns = reactive([
+    { type: "seq", fixed: "left", width: 60 },
+    { type: "html", field: "name", title: "租户名称", fixed: "left", minWidth: 150, sortable: true },
+    { field: "status", title: "租户状态", minWidth: 100, editRender: { name: "$cell-tag", options: statusDic } },
+    { type: "html", field: "managerName", title: "联系人", minWidth: 120, sortable: true },
+    { type: "html", field: "managerPhone", title: "联系方式", minWidth: 120, sortable: true },
+    { title: "操作", fixed: "right", width: 220, slots: { default: "action" } }
+]);
+
+// 显示隐藏 筛选表单
+const xGridTable = ref();
+const refreshTable = (mode = "add") => {
+    xGridTable.value.searchData(mode);
+    store.dispatch("fetchTenant");
+}
+
+const tenantRef = ref();
+const dialog = ref(false);
+
+const table_add = () => {
+    dialog.value = true;
+    nextTick(() => tenantRef.value?.open());
+}
+
+const table_edit = row => {
+    dialog.value = true;
+    nextTick(() => tenantRef.value?.setData(row));
+}
+
+const table_del = ({ id }) => {
+    ElMessageBox.confirm("是否确认删除该租户?", "删除警告", {
+        type: "warning",
+        confirmButtonText: "确定",
+        cancelButtonText: "取消"
+    }).then(() => {
+        API.system.tenant.del({ id }).then(() => {
+            ElMessage.success("操作成功");
+            refreshTable();
+        });
+    }).catch(() => {});
+}
+
+const table_change = row => {
+    const status = row.status == "enable" && "disable" || "enable";
+    const msg = row.status == "enable" && "停用" || "启用";
+
+    ElMessageBox.confirm(`是否确认${msg}该租户?`, `${msg}警告`, {
+        type: "warning",
+        confirmButtonText: "确定",
+        cancelButtonText: "取消"
+    }).then(() => {
+        API.system.tenant.edit({ id: row.id, status }).then(() => {
+            ElMessage.success("操作成功");
+            refreshTable();
+        });
+    }).catch(() => {});
+}
+</script>

+ 22 - 11
src/views/system/user/detail.vue

@@ -1,9 +1,18 @@
 <template>
     <el-dialog v-model="visible" :title="titleMap[mode]" width="480" :close-on-click-modal="false" @closed="$emit('closed')">
         <el-form ref="formRef" :model="form" :rules="rules" label-width="120">
+            <el-form-item v-if="$store.state.tenant.tenantId === '0'" label="所属租户" prop="tenantId">
+                <el-select v-if="!form.id" v-model="form.tenantId" filterable placeholder="请选择所属租户" @change="form.deptId = null, form.roleList = []">
+                    <el-option v-for="item in $store.state.tenant.tenants" :key="item.id" :label="item.name" :value="item.id"></el-option>
+                </el-select>
+                <el-input v-else v-model="tenantName" readonly></el-input>
+			</el-form-item>
+            <el-form-item label="所属部门" prop="deptId">
+				<el-tree-select v-model="form.deptId" :node-key="treeProps.value" :data="treeData" :props="treeProps" :default-expanded-keys="expandedKeys" check-strictly placeholder="请选择所属部门"></el-tree-select>
+			</el-form-item>
             <el-form-item label="角色" prop="roleList">
                 <el-select v-model="form.roleList" filterable multiple collapse-tags collapse-tags-tooltip placeholder="请选择用户角色">
-                    <el-option v-for="item in roles" :key="item.id" :label="item.name" :value="item.id"></el-option>
+                    <el-option v-for="item in roles.filter(r => r.tenantId == form.tenantId)" :key="item.id" :label="item.name" :value="item.id"></el-option>
                 </el-select>
             </el-form-item>
             <el-form-item label="用户名" prop="username">
@@ -27,9 +36,6 @@
                     <el-radio label="女"></el-radio>
                 </el-radio-group>
             </el-form-item>
-            <el-form-item label="所属部门" prop="deptId">
-				<el-tree-select v-model="form.deptId" :node-key="treeProps.value" :data="deptTree" :props="treeProps" :default-expanded-keys="expandedKeys" check-strictly placeholder="请选择所属部门"></el-tree-select>
-			</el-form-item>
         </el-form>
 
         <template #footer>
@@ -43,12 +49,10 @@
 import XEUtils from "xe-utils";
 
 import API from "@/api";
+import store from "@/store";
 import TOOL from "@/utils/tool";
 import { verifyIdCard } from "@/utils/verificate";
 const $emit = defineEmits(["success", "closed"]);
-const props = defineProps({
-    deptTree: { type: Array, default: () => [] }
-});
 const visible = ref(false);
 const isSaving = ref(false);
 
@@ -59,15 +63,19 @@ const titleMap = reactive({
 });
 
 const roles = ref([]);
+const depts = ref([]);
 const treeProps = reactive({ label: "name", value: "id" });
+const treeData = computed(() => XEUtils.toArrayTree(XEUtils.filter(depts.value, item => item.tenantId == form.value.tenantId), { parentKey: "pid" }));
 const expandedKeys = computed(() => {
     if (mode.value == "add") return [];
-    return XEUtils.map(XEUtils.get(XEUtils.findTree(props.deptTree, item => item.id == form.value.pid), "nodes", []), item => item.id).slice(0, -1);
+    return XEUtils.map(XEUtils.get(XEUtils.findTree(treeData.value, item => item.id == form.value.deptId), "nodes", []), item => item.id).slice(0, -1);
 });
 
+const tenantName = computed(() => XEUtils.get(XEUtils.find(store.state.tenant.tenants, item => item.id == form.value.tenantId), "name"));
 const form = ref({
     id: null,
     deptId: null,
+    tenantId: store.state.tenant.tenantId,
     roleList: [],
     username: null,
     nickName: null,
@@ -79,12 +87,13 @@ const form = ref({
     }
 });
 const rules = reactive({
+    tenantId: [{ required: true, message: "请选择所属租户" }],
+    deptId: [{ required: true, message: "请选择所属部门" }],
     roleList: [{ required: true, message: "请选择用户角色" }],
     username: [{ required: true, message: "请输入用户名" }],
     nickName: [{ required: true, message: "请输入用户昵称" }],
     "features.idcard": [{ validator: verifyIdCard }],
-    phone: [{ pattern: /^\d{11}$/, message: "请输入11位手机号码" }],
-    deptId: [{ required: true, message: "请选择所属部门" }]
+    phone: [{ pattern: /^\d{11}$/, message: "请输入11位手机号码" }]
 });
 
 const open = () => visible.value = true;
@@ -118,7 +127,7 @@ const submit = () => {
 
                 if (data.id == TOOL.data.get("USER_INFO").id) {
                     const userInfo = XEUtils.omit(form.value, "roleList", "features");
-                    XEUtils.set(userInfo, "dept", XEUtils.omit(XEUtils.get(XEUtils.findTree(props.deptTree, item => item.id == data.deptId), "item"), "children"));
+                    XEUtils.set(userInfo, "dept", XEUtils.find(depts.value, item => item.id == data.deptId));
                     XEUtils.set(userInfo, "roleList", XEUtils.map(form.value.roleList, id => XEUtils.find(roles.value, item => item.id == id)));
                     XEUtils.set(userInfo, "features", XEUtils.toJSONString(features));
                     TOOL.data.set("USER_INFO", userInfo);
@@ -131,7 +140,9 @@ const submit = () => {
 }
 
 const fetchRole = () => API.system.role.all({ orderBy: "id_asc" }).then(res => roles.value = res).catch(() => roles.value = []);
+const fetchDept = () => API.system.dept.get({ orderBy: "deptSort_asc" }).then(res => depts.value = res).catch(() => depts.value = []);
 fetchRole();
+fetchDept();
 
 defineExpose({
     open,

+ 11 - 4
src/views/system/user/index.vue

@@ -35,16 +35,20 @@
         </el-container>
 	</el-container>
 
-    <user-detail v-if="dialog" ref="userRef" :deptTree="deptTree" @success="refreshTable" @closed="dialog = false"></user-detail>
+    <user-detail v-if="dialog" ref="userRef" @success="refreshTable" @closed="dialog = false"></user-detail>
 </template>
 
 <script setup>
 import XEUtils from "xe-utils";
 
 import API from "@/api";
-import { mapFormItemInput } from "@/components/scTable/helper";
+import TOOL from "@/utils/tool";
+import { mapFormItemInput, mapFormItemTenant } from "@/components/scTable/helper";
 import userDetail from "./detail";
 
+import store from "@/store";
+watch(() => store.state.tenant.tenantId, () => (fetchDept(), refreshTable()));
+
 const filterText = ref("");
 const deptTree = ref([]);
 const treeRef = ref();
@@ -67,6 +71,7 @@ const nodeClick = data => {
 const formConfig = reactive({
     data: {},
     items: [
+        mapFormItemTenant({ events: { change: data => XEUtils.merge(formConfig.data, data) } }),
         mapFormItemInput("usernameLike", "用户名"),
         mapFormItemInput("nickNameLike", "用户昵称"),
         mapFormItemInput("phoneLike", "手机号")
@@ -75,6 +80,7 @@ const formConfig = reactive({
 
 const paramsColums = reactive([
     { column: "orderBy", defaultValue: "id_desc" },
+    { column: "tenantId" },
     { column: "deptId" },
     { column: "usernameLike" },
     { column: "nickNameLike" },
@@ -83,6 +89,7 @@ const paramsColums = reactive([
 
 const columns = reactive([
     { type: "seq", width: 60 },
+    { visible: computed(() => store.state.tenant.tenantId === "0"), type: "html", field: "tenant.name", title: "所属租户", minWidth: 200, sortable: true },
     { type: "html", field: "username", title: "用户名", minWidth: 160, sortable: true },
     { type: "html", field: "nickName", title: "用户昵称", minWidth: 160, sortable: true },
     { type: "html", field: "roleNames", title: "角色", minWidth: 160, sortable: true, formatter: ({ cellValue, row }) => cellValue || XEUtils.map(row.roleList, item => item.name).join() },
@@ -91,12 +98,12 @@ const columns = reactive([
     { type: "html", field: "phone", title: "手机号", minWidth: 120, sortable: true },
     { type: "html", field: "email", title: "邮箱", minWidth: 160, sortable: true },
     { visible: false, type: "html", field: "idcard", title: "身份证号", minWidth: 160, sortable: true, formatter: ({ cellValue, row }) => cellValue || XEUtils.get(XEUtils.toStringJSON(row.features), "idcard") },
-    { title: "操作", fixed: "right", width: 220, align: "center", slots: { default: "action" } }
+    { title: "操作", fixed: "right", width: 220, slots: { default: "action" } }
 ]);
 
 // 显示隐藏 筛选表单
 const xGridTable = ref();
-const refreshTable = (mode = "add") => xGridTable.value.searchData(mode);
+const refreshTable = (mode = "add") => (xGridTable.value.searchData(mode), xGridTable.value.reloadColumn(columns));
 
 const userRef = ref();
 const dialog = ref(false);

+ 1 - 1
src/views/userCenter/index.vue

@@ -65,8 +65,8 @@
 import XEUtils from "xe-utils";
 
 import API from "@/api";
+import store from "@/store";
 import TOOL from "@/utils/tool";
-const store = useStore();
 
 const isSaving = ref(false);
 const form = ref(XEUtils.clone(TOOL.data.get("USER_INFO"), true));

+ 2 - 2
vue.config.js

@@ -2,8 +2,8 @@ const { defineConfig } = require("@vue/cli-service")
 
 module.exports = defineConfig({
 	//设置为空打包后不分更目录还是多级目录
-	publicPath: "/easydo/mesWeb",
-    outputDir: "easydo/mesWeb",
+	publicPath: "/",
+    outputDir: "dist",
 	//build编译后存放静态文件的目录
 	// assetsDir: "static",