Procházet zdrojové kódy

修改省市区不能选中

xsh_1997 před 2 týdny
rodič
revize
9292dc8461

+ 7 - 1
ruoyi-ui/src/api/agri/region.js

@@ -1,9 +1,15 @@
1 1
 import request from '@/utils/request'
2
+import { normalizeRegionTree } from '@/utils/region'
2 3
 
3
-// 查询省市区三级树(平台端)
4
+// 查询省市区三级树(平台端,原始数据
4 5
 export function getRegionTree() {
5 6
   return request({
6 7
     url: '/agri/region/tree',
7 8
     method: 'get'
8 9
   })
9 10
 }
11
+
12
+/** 查询供 el-cascader 使用的省市区树(已 normalize,仅区县可选) */
13
+export function getRegionCascaderTree() {
14
+  return getRegionTree().then(response => normalizeRegionTree(response.data || []))
15
+}

+ 90 - 0
ruoyi-ui/src/mixins/regionCascader.js

@@ -0,0 +1,90 @@
1
+import { getRegionCascaderTree } from '@/api/agri/region'
2
+import {
3
+  REGION_CASCADER_PROPS,
4
+  findRegionPath as findRegionPathInTree,
5
+  getRegionNamesByCodes as getRegionNamesByCodesInTree,
6
+  findRegionNodeByCodes as findRegionNodeByCodesInTree,
7
+  parseRegionSelection
8
+} from '@/utils/region'
9
+
10
+/**
11
+ * 省市区 el-cascader 混入:loadRegionTree、回显、落库。
12
+ * 页面需提供 this.form;调用 syncRegionCascaderFromForm 时传入字段映射。
13
+ */
14
+export default {
15
+  data() {
16
+    return {
17
+      regionTree: [],
18
+      regionTreeLoaded: false,
19
+      regionCascaderProps: REGION_CASCADER_PROPS
20
+    }
21
+  },
22
+  methods: {
23
+    /** 加载规范化后的省市区树(全局缓存一次) */
24
+    loadRegionTree() {
25
+      if (this.regionTreeLoaded) {
26
+        return Promise.resolve(this.regionTree)
27
+      }
28
+      return getRegionCascaderTree().then(tree => {
29
+        this.regionTree = tree || []
30
+        this.regionTreeLoaded = true
31
+        return this.regionTree
32
+      }).catch(() => {
33
+        this.regionTree = []
34
+        return this.regionTree
35
+      })
36
+    },
37
+    findRegionPath(nodes, targetCode, path) {
38
+      return findRegionPathInTree(nodes || this.regionTree, targetCode, path)
39
+    },
40
+    getRegionNamesByCodes(codes) {
41
+      return getRegionNamesByCodesInTree(this.regionTree, codes)
42
+    },
43
+    findRegionNodeByCodes(codes) {
44
+      return findRegionNodeByCodesInTree(this.regionTree, codes)
45
+    },
46
+    /**
47
+     * 编辑回显:把库里的区县 code 转成 cascader 路径
48
+     * @param {Array<{ codeKey: string, cascaderKey: string }>} fields
49
+     */
50
+    syncRegionCascaderFromForm(fields) {
51
+      if (!this.form || !this.regionTree.length) {
52
+        return
53
+      }
54
+      const list = fields || []
55
+      list.forEach(item => {
56
+        const code = this.form[item.codeKey]
57
+        if (code) {
58
+          this.form[item.cascaderKey] = this.findRegionPath(this.regionTree, code) || []
59
+        }
60
+      })
61
+    },
62
+    /**
63
+     * 级联变更落库:code=区县 code,name=省/市/区县用 / 连接
64
+     * @param {Array} codes cascader 选中路径
65
+     * @param {string} prefix 如 reg / biz → regRegionCode、regRegionName
66
+     */
67
+    applyRegionSelection(codes, prefix) {
68
+      const codeKey = prefix + 'RegionCode'
69
+      const nameKey = prefix + 'RegionName'
70
+      const cascaderKey = prefix + 'RegionCascader'
71
+      const result = parseRegionSelection(this.regionTree, codes)
72
+      if (!codes || !codes.length) {
73
+        this.form[codeKey] = undefined
74
+        this.form[nameKey] = undefined
75
+        return
76
+      }
77
+      if (!result.valid) {
78
+        this.$modal.msgWarning('请选择完整的省/市/区县')
79
+        this.$nextTick(() => {
80
+          this.form[cascaderKey] = []
81
+          this.form[codeKey] = undefined
82
+          this.form[nameKey] = undefined
83
+        })
84
+        return
85
+      }
86
+      this.form[codeKey] = result.code
87
+      this.form[nameKey] = result.name
88
+    }
89
+  }
90
+}

+ 108 - 0
ruoyi-ui/src/utils/region.js

@@ -0,0 +1,108 @@
1
+/** el-cascader 省市区配置(须配合 normalizeRegionTree 使用) */
2
+export const REGION_CASCADER_PROPS = {
3
+  value: 'code',
4
+  label: 'name',
5
+  children: 'children',
6
+  emitPath: true,
7
+  leaf: 'leaf',
8
+  disabled: 'disabled'
9
+}
10
+
11
+/**
12
+ * 规范化地区树供 el-cascader 使用。
13
+ * Element UI 默认把 children: [] 当作叶子并可点击选中;
14
+ * 后端 RegionTreeVO 对无下级节点也返回空数组,导致省/市被误选。
15
+ * 仅 type=3(区县)标记为 leaf;无下级的非区县节点 disabled。
16
+ */
17
+export function normalizeRegionTree(nodes) {
18
+  if (!nodes || !nodes.length) {
19
+    return []
20
+  }
21
+  return nodes.map(node => {
22
+    const hasChildren = node.children && node.children.length > 0
23
+    const children = hasChildren ? normalizeRegionTree(node.children) : undefined
24
+    const isDistrict = node.type === 3
25
+    const item = {
26
+      code: node.code,
27
+      name: node.name,
28
+      type: node.type,
29
+      leaf: isDistrict,
30
+      disabled: !isDistrict && !hasChildren
31
+    }
32
+    if (children && children.length) {
33
+      item.children = children
34
+    }
35
+    return item
36
+  })
37
+}
38
+
39
+/** 根据区县 code 回显级联选中路径 */
40
+export function findRegionPath(nodes, targetCode, path) {
41
+  if (!nodes || !nodes.length) {
42
+    return null
43
+  }
44
+  for (let i = 0; i < nodes.length; i++) {
45
+    const node = nodes[i]
46
+    const nextPath = (path || []).concat(node.code)
47
+    if (String(node.code) === String(targetCode)) {
48
+      return nextPath
49
+    }
50
+    if (node.children && node.children.length) {
51
+      const found = findRegionPath(node.children, targetCode, nextPath)
52
+      if (found) {
53
+        return found
54
+      }
55
+    }
56
+  }
57
+  return null
58
+}
59
+
60
+/** 根据级联 code 路径拼地区名称 */
61
+export function getRegionNamesByCodes(regionTree, codes) {
62
+  const names = []
63
+  let nodes = regionTree
64
+  for (let i = 0; i < codes.length; i++) {
65
+    const code = codes[i]
66
+    const node = (nodes || []).find(item => String(item.code) === String(code))
67
+    if (!node) {
68
+      break
69
+    }
70
+    names.push(node.name)
71
+    nodes = node.children || []
72
+  }
73
+  return names
74
+}
75
+
76
+/** 根据级联路径找到末级节点 */
77
+export function findRegionNodeByCodes(regionTree, codes) {
78
+  let nodes = regionTree
79
+  let node = null
80
+  for (let i = 0; i < codes.length; i++) {
81
+    node = (nodes || []).find(item => String(item.code) === String(codes[i]))
82
+    if (!node) {
83
+      return null
84
+    }
85
+    nodes = node.children || []
86
+  }
87
+  return node
88
+}
89
+
90
+/**
91
+ * 解析级联选择结果。
92
+ * @returns {{ valid: boolean, code?: string, name?: string }}
93
+ */
94
+export function parseRegionSelection(regionTree, codes) {
95
+  if (!codes || !codes.length) {
96
+    return { valid: true, code: undefined, name: undefined }
97
+  }
98
+  const lastNode = findRegionNodeByCodes(regionTree, codes)
99
+  if (!lastNode || lastNode.type !== 3) {
100
+    return { valid: false }
101
+  }
102
+  const names = getRegionNamesByCodes(regionTree, codes)
103
+  return {
104
+    valid: true,
105
+    code: String(codes[codes.length - 1]),
106
+    name: names.join('/')
107
+  }
108
+}

+ 8 - 79
ruoyi-ui/src/views/agri/org/merchant/index.vue

@@ -336,12 +336,18 @@
336 336
 
337 337
 <script>
338 338
 import { listMerchant, getMerchant, addMerchant, updateMerchant, delMerchant, deleteMerchantCheck, adminUserOptions, memberOptions } from "@/api/agri/merchant"
339
-import { getRegionTree } from "@/api/agri/region"
339
+import regionCascaderMixin from "@/mixins/regionCascader"
340 340
 import MerchantDetail from "./detail"
341 341
 
342
+const REGION_CASCADER_FIELDS = [
343
+  { codeKey: "regRegionCode", cascaderKey: "regRegionCascader" },
344
+  { codeKey: "bizRegionCode", cascaderKey: "bizRegionCascader" }
345
+]
346
+
342 347
 export default {
343 348
   name: "AgriMerchant",
344 349
   components: { MerchantDetail },
350
+  mixins: [regionCascaderMixin],
345 351
   data() {
346 352
     return {
347 353
       loading: true,
@@ -356,14 +362,6 @@ export default {
356 362
       bindSearchLoading: false,
357 363
       adminUserList: [],
358 364
       memberList: [],
359
-      regionTree: [],
360
-      regionTreeLoaded: false,
361
-      regionCascaderProps: {
362
-        value: "code",
363
-        label: "name",
364
-        children: "children",
365
-        emitPath: true
366
-      },
367 365
       queryParams: {
368 366
         pageNum: 1,
369 367
         pageSize: 10,
@@ -509,7 +507,7 @@ export default {
509 507
         if (data.licenseValidType === "1" && data.licenseValidStart && data.licenseValidEnd) {
510 508
           this.form.licenseValidRange = [data.licenseValidStart, data.licenseValidEnd]
511 509
         }
512
-        this.syncRegionCascaderFromForm()
510
+        this.syncRegionCascaderFromForm(REGION_CASCADER_FIELDS)
513 511
         this.activeTab = "subject"
514 512
         this.open = true
515 513
         this.title = "编辑商户"
@@ -528,62 +526,6 @@ export default {
528 526
       this.adminUserList = []
529 527
       this.memberList = []
530 528
     },
531
-    /** 加载省市区树 */
532
-    loadRegionTree() {
533
-      if (this.regionTreeLoaded) {
534
-        return Promise.resolve()
535
-      }
536
-      return getRegionTree().then(response => {
537
-        this.regionTree = response.data || []
538
-        this.regionTreeLoaded = true
539
-      }).catch(() => {
540
-        this.regionTree = []
541
-      })
542
-    },
543
-    /** 根据区县 code 回显级联选中路径 */
544
-    findRegionPath(nodes, targetCode, path) {
545
-      if (!nodes || !nodes.length) {
546
-        return null
547
-      }
548
-      for (let i = 0; i < nodes.length; i++) {
549
-        const node = nodes[i]
550
-        const nextPath = (path || []).concat(node.code)
551
-        if (String(node.code) === String(targetCode)) {
552
-          return nextPath
553
-        }
554
-        if (node.children && node.children.length) {
555
-          const found = this.findRegionPath(node.children, targetCode, nextPath)
556
-          if (found) {
557
-            return found
558
-          }
559
-        }
560
-      }
561
-      return null
562
-    },
563
-    /** 根据级联 code 路径拼地区名称 */
564
-    getRegionNamesByCodes(codes) {
565
-      const names = []
566
-      let nodes = this.regionTree
567
-      for (let i = 0; i < codes.length; i++) {
568
-        const code = codes[i]
569
-        const node = (nodes || []).find(item => String(item.code) === String(code))
570
-        if (!node) {
571
-          break
572
-        }
573
-        names.push(node.name)
574
-        nodes = node.children || []
575
-      }
576
-      return names
577
-    },
578
-    /** 编辑回显:把库里的 code 转成 cascader 路径 */
579
-    syncRegionCascaderFromForm() {
580
-      if (this.form.regRegionCode && this.regionTree.length) {
581
-        this.form.regRegionCascader = this.findRegionPath(this.regionTree, this.form.regRegionCode) || []
582
-      }
583
-      if (this.form.bizRegionCode && this.regionTree.length) {
584
-        this.form.bizRegionCascader = this.findRegionPath(this.regionTree, this.form.bizRegionCode) || []
585
-      }
586
-    },
587 529
     /** 企业注册地区变更:写入 regRegionCode / regRegionName */
588 530
     handleRegRegionChange(codes) {
589 531
       this.applyRegionSelection(codes, "reg")
@@ -592,19 +534,6 @@ export default {
592 534
     handleBizRegionChange(codes) {
593 535
       this.applyRegionSelection(codes, "biz")
594 536
     },
595
-    /** 级联选择落库:code=区县 code,name=省/市/区县用 / 连接 */
596
-    applyRegionSelection(codes, prefix) {
597
-      const codeKey = prefix + "RegionCode"
598
-      const nameKey = prefix + "RegionName"
599
-      if (!codes || !codes.length) {
600
-        this.form[codeKey] = undefined
601
-        this.form[nameKey] = undefined
602
-        return
603
-      }
604
-      const names = this.getRegionNamesByCodes(codes)
605
-      this.form[codeKey] = String(codes[codes.length - 1])
606
-      this.form[nameKey] = names.join("/")
607
-    },
608 537
     formatAdminOption(item) {
609 538
       const parts = [item.nickName || item.userName, item.userName]
610 539
       if (item.phonenumber) {