cloud-web-corejs 1.0.54-dev.681 → 1.0.54-dev.683

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "cloud-web-corejs",
3
3
  "private": false,
4
- "version": "1.0.54-dev.681",
4
+ "version": "1.0.54-dev.683",
5
5
  "scripts": {
6
6
  "dev": "vue-cli-service serve",
7
7
  "lint": "eslint --ext .js,.vue src",
@@ -55,7 +55,8 @@
55
55
  v-if="index > 0"
56
56
  :key="'sep-' + index"
57
57
  class="base-area-path-sep"
58
- >/</span>
58
+ >/</span
59
+ >
59
60
  <span
60
61
  :key="'tab-' + index"
61
62
  class="base-area-tab"
@@ -65,7 +66,8 @@
65
66
  disabled: !isTabEnabled(index + 1),
66
67
  }"
67
68
  @click="switchLevel(index + 1)"
68
- >{{ getTabLabel(tab) }}</span>
69
+ >{{ getTabLabel(tab) }}</span
70
+ >
69
71
  </template>
70
72
  </div>
71
73
  </div>
@@ -80,7 +82,10 @@
80
82
  >
81
83
  <span>{{ item.name }}</span>
82
84
  </div>
83
- <div v-if="!browseLoading && !levelItems.length" class="base-area-empty">
85
+ <div
86
+ v-if="!browseLoading && !levelItems.length"
87
+ class="base-area-empty"
88
+ >
84
89
  {{ emptyText }}
85
90
  </div>
86
91
  </div>
@@ -135,6 +140,8 @@
135
140
 
136
141
  <script>
137
142
  const LEVEL_LABELS = ["省份", "城市", "区县", "乡镇"];
143
+ const AREA_LEVEL_KEYS = ["province", "city", "district", "town"];
144
+ const MAX_AREA_DEPTH = 4;
138
145
  const SEARCH_DEBOUNCE = 300;
139
146
 
140
147
  export default {
@@ -188,10 +195,10 @@ export default {
188
195
  type: Number,
189
196
  default: 630,
190
197
  },
191
- /** path: 逗号分隔各级 id,如 1,2,3,4leaf: 仅末级 id */
198
+ /** leaf: 绑定末级 id;path: 兼容旧版逗号分隔路径 */
192
199
  valueFormat: {
193
200
  type: String,
194
- default: "path",
201
+ default: "leaf",
195
202
  },
196
203
  },
197
204
  data() {
@@ -209,6 +216,9 @@ export default {
209
216
  searchTimer: null,
210
217
  syncToken: 0,
211
218
  skipValueSync: false,
219
+ committedBrowseSnapshot: null,
220
+ committedBoundValue: null,
221
+ lastSelectionPayload: null,
212
222
  };
213
223
  },
214
224
  computed: {
@@ -219,20 +229,20 @@ export default {
219
229
  return this.searchDropdownVisible && this.hasSearchKeyword;
220
230
  },
221
231
  displayText() {
232
+ if (this.labelValue) {
233
+ return this.labelValue;
234
+ }
222
235
  const pathLabel = this.selectedPath.length
223
236
  ? this.buildPathLabel(this.selectedPath)
224
237
  : "";
225
238
  if (this.popoverVisible && pathLabel) {
226
239
  return pathLabel;
227
240
  }
228
- if (this.labelValue) {
229
- return this.labelValue;
230
- }
231
241
  return pathLabel || "";
232
242
  },
233
243
  tabs() {
234
244
  const tabs = [];
235
- for (let i = 0; i < this.maxLevel; i++) {
245
+ for (let i = 0; i < this.browseLevelCount; i++) {
236
246
  tabs.push({
237
247
  level: i + 1,
238
248
  label: LEVEL_LABELS[i] || `第${i + 1}级`,
@@ -243,10 +253,7 @@ export default {
243
253
  },
244
254
  showClearIcon() {
245
255
  return (
246
- this.clearable &&
247
- !this.readonly &&
248
- !this.disabled &&
249
- !!this.displayText
256
+ this.clearable && !this.readonly && !this.disabled && !!this.displayText
250
257
  );
251
258
  },
252
259
  canPrevLevel() {
@@ -254,10 +261,13 @@ export default {
254
261
  },
255
262
  canNextLevel() {
256
263
  return (
257
- this.activeLevel < this.maxLevel &&
258
- this.isTabEnabled(this.activeLevel + 1)
264
+ this.activeLevel < this.browseLevelCount
265
+ && this.isTabEnabled(this.activeLevel + 1)
259
266
  );
260
267
  },
268
+ browseLevelCount() {
269
+ return Math.max(this.maxLevel, this.selectedPath.length, 1);
270
+ },
261
271
  currentLevelLabel() {
262
272
  return LEVEL_LABELS[this.activeLevel - 1] || `第${this.activeLevel}级`;
263
273
  },
@@ -272,10 +282,6 @@ export default {
272
282
  labelValue(val) {
273
283
  if (!val && !this.value) {
274
284
  this.resetBrowseState(false);
275
- return;
276
- }
277
- if (this.value && !this.isValueSynced(this.parseValueIds(this.value))) {
278
- this.syncFromValue();
279
285
  }
280
286
  },
281
287
  },
@@ -283,47 +289,82 @@ export default {
283
289
  clearTimeout(this.searchTimer);
284
290
  },
285
291
  methods: {
292
+ getResolveDepthLimit(item) {
293
+ if (item && item.treePath) {
294
+ const ids = this.parseTreePathIds(item.treePath);
295
+ return Math.min(Math.max(ids.length, this.maxLevel, 1), MAX_AREA_DEPTH);
296
+ }
297
+ return MAX_AREA_DEPTH;
298
+ },
299
+ isPathResolvedForItem(path, item) {
300
+ if (!path || !path.length || !item || item.id == null) {
301
+ return false;
302
+ }
303
+ return this.getAreaId(path[path.length - 1]) == item.id;
304
+ },
286
305
  isSearchItemActive(item) {
287
306
  if (!item || this.value == null || this.value === "") {
288
307
  return false;
289
308
  }
290
- const ids = this.parseValueIds(this.value);
291
- if (!ids.length) {
309
+ const leafId = this.parseValueId(this.value);
310
+ if (leafId == null) {
292
311
  return false;
293
312
  }
294
- return ids[ids.length - 1] == this.getAreaId(item);
313
+ return leafId == this.getAreaId(item);
295
314
  },
296
- parseValueIds(val) {
315
+ parseValueId(val) {
297
316
  if (val == null || val === "") {
298
- return [];
317
+ return null;
299
318
  }
300
319
  const str = String(val).trim();
301
- if (this.valueFormat === "path" && str.includes(",")) {
302
- return str.split(",").map((part) => part.trim()).filter((part) => part !== "");
320
+ if (this.isLegacyPathValue(str)) {
321
+ const parts = str
322
+ .split(",")
323
+ .map((part) => part.trim())
324
+ .filter(Boolean);
325
+ return parts.length ? parts[parts.length - 1] : null;
303
326
  }
304
- return [str];
327
+ return str;
305
328
  },
306
- isPathValue(val) {
329
+ parseLegacyPathIds(val) {
330
+ if (val == null || val === "") {
331
+ return [];
332
+ }
333
+ return String(val)
334
+ .split(",")
335
+ .map((part) => part.trim())
336
+ .filter((part) => part !== "");
337
+ },
338
+ isLegacyPathValue(val) {
307
339
  return (
308
- this.valueFormat === "path" &&
309
- val != null &&
310
- String(val).includes(",")
340
+ this.valueFormat === "path"
341
+ && val != null
342
+ && String(val).includes(",")
343
+ && !String(val).startsWith(",")
311
344
  );
312
345
  },
313
- formatPathValue(path) {
314
- if (!path || !path.length) {
315
- return null;
346
+ parseTreePathIds(treePath) {
347
+ if (treePath == null || treePath === "") {
348
+ return [];
316
349
  }
317
- const ids = path
318
- .map((item) => this.getAreaId(item))
319
- .filter((id) => id != null && id !== "");
320
- if (!ids.length) {
350
+ return String(treePath)
351
+ .split(",")
352
+ .map((part) => part.trim())
353
+ .filter((part) => part !== "");
354
+ },
355
+ formatBoundValue(path, item) {
356
+ const last = item || (path && path.length ? path[path.length - 1] : null);
357
+ const leafId = this.getAreaId(last);
358
+ if (leafId == null) {
321
359
  return null;
322
360
  }
323
- if (this.valueFormat === "leaf") {
324
- return ids[ids.length - 1];
361
+ if (this.valueFormat === "path") {
362
+ const ids = (path || [])
363
+ .map((node) => this.getAreaId(node))
364
+ .filter((id) => id != null && id !== "");
365
+ return ids.length ? ids.join(",") : String(leafId);
325
366
  }
326
- return ids.join(",");
367
+ return leafId;
327
368
  },
328
369
  getAreaId(item) {
329
370
  if (!item) {
@@ -340,11 +381,14 @@ export default {
340
381
  }
341
382
  return null;
342
383
  },
343
- isValueSynced(ids) {
344
- if (!ids.length || this.selectedPath.length !== ids.length) {
384
+ isValueSynced(leafId) {
385
+ if (leafId == null || leafId === "" || !this.selectedPath.length) {
345
386
  return false;
346
387
  }
347
- return ids.every((id, index) => this.getAreaId(this.selectedPath[index]) == id);
388
+ const currentLeaf = this.getAreaId(
389
+ this.selectedPath[this.selectedPath.length - 1]
390
+ );
391
+ return currentLeaf == leafId;
348
392
  },
349
393
  buildPathLabel(path) {
350
394
  if (!path || !path.length) {
@@ -359,6 +403,92 @@ export default {
359
403
  .filter((name) => !!name)
360
404
  .join("");
361
405
  },
406
+ createEmptyAreaPathResult(value) {
407
+ return {
408
+ value: value != null && value !== "" ? value : null,
409
+ label: null,
410
+ path: [],
411
+ province: null,
412
+ city: null,
413
+ district: null,
414
+ town: null,
415
+ };
416
+ },
417
+ formatAreaPathResult(value, path, label) {
418
+ const result = this.createEmptyAreaPathResult(value);
419
+ result.label
420
+ = label != null && label !== ""
421
+ ? label
422
+ : this.buildPathLabel(path) || this.labelValue || null;
423
+ result.path = path || [];
424
+ (path || []).forEach((node, index) => {
425
+ const key = AREA_LEVEL_KEYS[index];
426
+ if (key && Object.prototype.hasOwnProperty.call(result, key)) {
427
+ result[key] = node;
428
+ }
429
+ });
430
+ return result;
431
+ },
432
+ async ensurePathDetail(path) {
433
+ const result = [];
434
+ for (let i = 0; i < path.length; i++) {
435
+ const node = path[i];
436
+ const id = this.getAreaId(node);
437
+ if (id != null) {
438
+ const detail = await this.fetchAreaById(id);
439
+ result.push(detail || node);
440
+ } else {
441
+ result.push(node);
442
+ }
443
+ }
444
+ return result;
445
+ },
446
+ async getAreaPath(value) {
447
+ const val = value !== undefined ? value : this.value;
448
+ if (val == null || val === "") {
449
+ return this.createEmptyAreaPathResult(null);
450
+ }
451
+ const leafId = this.getLeafIdFromValue(val);
452
+ let path = null;
453
+ if (
454
+ leafId != null
455
+ && this.isValueSynced(leafId)
456
+ && !this.shouldResyncBrowsePath()
457
+ && this.selectedPath.length
458
+ ) {
459
+ path = this.selectedPath.map((item) => ({ ...item }));
460
+ }
461
+ if (!path || !path.length) {
462
+ if (this.isLegacyPathValue(val)) {
463
+ path = await this.resolvePathByIds(this.parseLegacyPathIds(val));
464
+ } else if (leafId != null) {
465
+ const area = await this.fetchAreaById(leafId);
466
+ if (area) {
467
+ path = await this.resolveAreaPath(area);
468
+ } else {
469
+ path = [{ id: leafId, fullName: this.labelValue || undefined }];
470
+ }
471
+ }
472
+ }
473
+ if (!path || !path.length) {
474
+ return this.createEmptyAreaPathResult(val);
475
+ }
476
+ path = await this.ensurePathDetail(path);
477
+ return this.formatAreaPathResult(val, path);
478
+ },
479
+ hasDisplayLabel() {
480
+ return !!(this.labelValue && String(this.labelValue).trim());
481
+ },
482
+ getLeafIdFromValue(val) {
483
+ if (val == null || val === "") {
484
+ return null;
485
+ }
486
+ if (this.isLegacyPathValue(val)) {
487
+ const ids = this.parseLegacyPathIds(val);
488
+ return ids.length ? ids[ids.length - 1] : null;
489
+ }
490
+ return this.parseValueId(val);
491
+ },
362
492
  emitResolvedLabelIfNeeded() {
363
493
  if (this.popoverVisible || this.labelValue) {
364
494
  return;
@@ -385,11 +515,89 @@ export default {
385
515
  return level === 1 || this.selectedPath.length >= level - 1;
386
516
  },
387
517
  handlePopoverShow() {
518
+ this.saveCommittedBrowseSnapshot();
519
+ this.$emit("popover-open");
388
520
  this.$nextTick(() => {
389
521
  this.$refs.searchInputRef && this.$refs.searchInputRef.focus();
390
522
  });
391
523
  this.initBrowseStateFromValue();
392
524
  },
525
+ saveCommittedBrowseSnapshot() {
526
+ this.committedBoundValue = this.value;
527
+ this.committedBrowseSnapshot = {
528
+ boundValue: this.value,
529
+ leafId: this.getLeafIdFromValue(this.value),
530
+ selectedPath: this.selectedPath.map((item) => ({ ...item })),
531
+ activeLevel: this.activeLevel,
532
+ };
533
+ },
534
+ isSameBoundValue(a, b) {
535
+ const normalize = (val) => {
536
+ if (val == null || val === "") {
537
+ return null;
538
+ }
539
+ return String(val);
540
+ };
541
+ return normalize(a) === normalize(b);
542
+ },
543
+ emitChangeIfCommitted() {
544
+ const snapshot = this.committedBrowseSnapshot;
545
+ if (!snapshot) {
546
+ return;
547
+ }
548
+ const oldVal = snapshot.boundValue;
549
+ const newVal = this.value;
550
+ if (!this.isSameBoundValue(oldVal, newVal)) {
551
+ const payload = this.lastSelectionPayload || {
552
+ value: newVal,
553
+ row: null,
554
+ label: this.labelValue,
555
+ };
556
+ this.$emit("change", {
557
+ value: payload.value != null ? payload.value : newVal,
558
+ row: payload.row || null,
559
+ label: payload.label != null ? payload.label : this.labelValue,
560
+ path: payload.path || [],
561
+ });
562
+ }
563
+ this.committedBoundValue = null;
564
+ this.lastSelectionPayload = null;
565
+ },
566
+ restoreBrowseStateIfUncommitted() {
567
+ const snapshot = this.committedBrowseSnapshot;
568
+ if (!snapshot) {
569
+ return;
570
+ }
571
+ if (!this.isSameBoundValue(this.value, snapshot.boundValue)) {
572
+ this.levelItems = [];
573
+ this.committedBrowseSnapshot = null;
574
+ return;
575
+ }
576
+ const committedLeafId = snapshot.leafId;
577
+ if (this.isValueSynced(committedLeafId)) {
578
+ this.levelItems = [];
579
+ this.committedBrowseSnapshot = null;
580
+ return;
581
+ }
582
+ if (snapshot.selectedPath && snapshot.selectedPath.length) {
583
+ this.selectedPath = snapshot.selectedPath.map((item) => ({ ...item }));
584
+ this.activeLevel = snapshot.activeLevel;
585
+ } else if (committedLeafId) {
586
+ this.selectedPath = [
587
+ {
588
+ id: committedLeafId,
589
+ fullName: this.labelValue || undefined,
590
+ name: this.labelValue || undefined,
591
+ },
592
+ ];
593
+ this.activeLevel = 1;
594
+ } else {
595
+ this.selectedPath = [];
596
+ this.activeLevel = 1;
597
+ }
598
+ this.levelItems = [];
599
+ this.committedBrowseSnapshot = null;
600
+ },
393
601
  syncFromValue() {
394
602
  if (this.skipValueSync) {
395
603
  return;
@@ -399,34 +607,65 @@ export default {
399
607
  this.resetBrowseState(false);
400
608
  return;
401
609
  }
402
- const ids = this.parseValueIds(val);
403
- if (this.isValueSynced(ids)) {
610
+ if (!this.hasDisplayLabel()) {
611
+ this.resolveLabelFromValue();
612
+ }
613
+ },
614
+ async resolveLabelFromValue() {
615
+ const val = this.value;
616
+ if (!val || this.hasDisplayLabel()) {
617
+ return;
618
+ }
619
+ const leafId = this.getLeafIdFromValue(val);
620
+ if (leafId == null) {
404
621
  return;
405
622
  }
406
- if (this.isPathValue(val)) {
407
- this.syncBrowseStateFromPathIds(ids);
623
+ const token = ++this.syncToken;
624
+ const area = await this.fetchAreaById(leafId);
625
+ if (token !== this.syncToken || this.hasDisplayLabel()) {
408
626
  return;
409
627
  }
410
- this.syncBrowseStateFromArea({
411
- id: ids[0],
412
- fullName: this.labelValue || undefined,
413
- });
628
+ const label = (area && area.fullName) || (area && area.name) || null;
629
+ if (label) {
630
+ this.$emit("update:labelValue", label);
631
+ }
414
632
  },
415
- initBrowseStateFromValue() {
416
- if (!this.value) {
633
+ shouldResyncBrowsePath() {
634
+ if (!this.labelValue || !this.selectedPath.length) {
635
+ return false;
636
+ }
637
+ const pathLabel = this.buildPathLabel(this.selectedPath);
638
+ return pathLabel !== String(this.labelValue).trim();
639
+ },
640
+ syncBrowseStateForPopover() {
641
+ const val = this.value;
642
+ if (!val) {
417
643
  if (!this.levelItems.length) {
418
644
  this.loadLevelItems(this.getParentId());
419
645
  }
420
646
  return;
421
647
  }
422
- const ids = this.parseValueIds(this.value);
423
- if (!this.isValueSynced(ids)) {
424
- this.syncFromValue();
648
+ const leafId = this.getLeafIdFromValue(val);
649
+ if (this.isValueSynced(leafId) && !this.shouldResyncBrowsePath()) {
650
+ const expectedActive = this.selectedPath.length || 1;
651
+ if (this.activeLevel !== expectedActive) {
652
+ this.activeLevel = expectedActive;
653
+ this.loadLevelItems(this.getParentId());
654
+ return;
655
+ }
656
+ if (!this.levelItems.length) {
657
+ this.loadLevelItems(this.getParentId());
658
+ }
425
659
  return;
426
660
  }
427
- if (!this.levelItems.length) {
428
- this.loadLevelItems(this.getParentId());
661
+ if (this.isLegacyPathValue(val)) {
662
+ this.syncBrowseStateFromPathIds(this.parseLegacyPathIds(val));
663
+ return;
429
664
  }
665
+ this.syncBrowseStateFromLeafId(leafId);
666
+ },
667
+ initBrowseStateFromValue() {
668
+ this.syncBrowseStateForPopover();
430
669
  },
431
670
  getAreaRequestData(extra = {}) {
432
671
  return {
@@ -448,7 +687,7 @@ export default {
448
687
  requestAreaList(extra = {}) {
449
688
  return new Promise((resolve) => {
450
689
  this.$http({
451
- url: USER_PREFIX + "/area/list",
690
+ url: USER_PREFIX + "/area/listPage",
452
691
  method: "post",
453
692
  data: this.getAreaRequestData(extra),
454
693
  success: (res) => resolve(this.normalizeAreaRows(res)),
@@ -485,22 +724,44 @@ export default {
485
724
  if (id == null || id === "") {
486
725
  return Promise.resolve(null);
487
726
  }
488
- return this.requestAreaList({ id }).then(
489
- (rows) => rows.find((row) => this.getAreaId(row) == id) || null
490
- );
727
+ return new Promise((resolve) => {
728
+ this.$http({
729
+ url: USER_PREFIX + "/area/get",
730
+ method: "post",
731
+ data: this.getAreaRequestData({ id }),
732
+ success: (res) => {
733
+ const objx = res && res.objx;
734
+ if (objx && typeof objx === "object" && !Array.isArray(objx)) {
735
+ resolve(objx);
736
+ return;
737
+ }
738
+ resolve(null);
739
+ },
740
+ error: () => resolve(null),
741
+ });
742
+ });
491
743
  },
492
744
  isAreaObject(node) {
493
745
  return node && typeof node === "object" && node.id != null;
494
746
  },
495
- buildAreaPath(item) {
496
- if (item.pathList && Array.isArray(item.pathList) && item.pathList.length > 1) {
747
+ buildAreaPath(item, depthLimit) {
748
+ const limit = depthLimit == null ? this.maxLevel : depthLimit;
749
+ if (
750
+ item.pathList
751
+ && Array.isArray(item.pathList)
752
+ && item.pathList.length > 1
753
+ ) {
497
754
  if (this.isAreaObject(item.pathList[0])) {
498
- return item.pathList.slice(0, this.maxLevel);
755
+ return item.pathList.slice(0, limit);
499
756
  }
500
757
  }
501
- if (item.areaPath && Array.isArray(item.areaPath) && item.areaPath.length > 1) {
758
+ if (
759
+ item.areaPath
760
+ && Array.isArray(item.areaPath)
761
+ && item.areaPath.length > 1
762
+ ) {
502
763
  if (this.isAreaObject(item.areaPath[0])) {
503
- return item.areaPath.slice(0, this.maxLevel);
764
+ return item.areaPath.slice(0, limit);
504
765
  }
505
766
  }
506
767
  return null;
@@ -521,7 +782,7 @@ export default {
521
782
  }
522
783
  return null;
523
784
  },
524
- async resolvePathByFullName(item) {
785
+ async resolvePathByFullName(item, depthLimit) {
525
786
  const fullName = (item.fullName || "").trim();
526
787
  if (!fullName) {
527
788
  return null;
@@ -529,7 +790,7 @@ export default {
529
790
  const path = [];
530
791
  let parentId = 0;
531
792
  let prefix = "";
532
- for (let level = 0; level < this.maxLevel; level++) {
793
+ for (let level = 0; level < MAX_AREA_DEPTH; level++) {
533
794
  const children = await this.fetchChildren(parentId);
534
795
  if (!children.length) {
535
796
  break;
@@ -554,13 +815,13 @@ export default {
554
815
  path.push(item);
555
816
  }
556
817
  }
557
- return path.length ? path.slice(0, this.maxLevel) : null;
818
+ return path.length ? path : null;
558
819
  },
559
- async resolvePathByParentChain(item) {
820
+ async resolvePathByParentChain(item, depthLimit) {
560
821
  const chain = [];
561
822
  let current = item;
562
823
  let safety = 0;
563
- while (current && safety < this.maxLevel + 5) {
824
+ while (current && safety < MAX_AREA_DEPTH + 2) {
564
825
  safety += 1;
565
826
  chain.unshift(current);
566
827
  const parentId = this.getParentIdFromRow(current);
@@ -573,31 +834,101 @@ export default {
573
834
  }
574
835
  current = parent;
575
836
  }
576
- return chain.length ? chain.slice(0, this.maxLevel) : null;
837
+ return chain.length ? chain : null;
577
838
  },
578
- async resolveAreaPath(item) {
579
- const presetPath = this.buildAreaPath(item);
580
- if (presetPath && presetPath.length) {
839
+ async resolvePathFromTreePath(area, depthLimit) {
840
+ const ids = this.parseTreePathIds(area && area.treePath);
841
+ if (!ids.length) {
842
+ return area && area.id != null ? [area] : null;
843
+ }
844
+ const path = [];
845
+ for (let i = 0; i < ids.length; i++) {
846
+ const id = ids[i];
847
+ let node = null;
848
+ if (area && this.getAreaId(area) == id) {
849
+ node = area;
850
+ } else {
851
+ node = await this.fetchAreaById(id);
852
+ }
853
+ path.push(node || { id });
854
+ }
855
+ if (area && area.id != null) {
856
+ const exists = path.some((node) => this.getAreaId(node) == area.id);
857
+ if (!exists) {
858
+ path.push(area);
859
+ }
860
+ }
861
+ return path.length ? path : null;
862
+ },
863
+ async resolveAreaPath(item, options = {}) {
864
+ if (!item || item.id == null) {
865
+ return item ? [item] : [];
866
+ }
867
+ const parentPath = await this.resolvePathByParentChain(item);
868
+ if (this.isPathResolvedForItem(parentPath, item)) {
869
+ return parentPath;
870
+ }
871
+ const depthLimit
872
+ = options.depthLimit != null
873
+ ? options.depthLimit
874
+ : this.getResolveDepthLimit(item);
875
+ if (item.treePath) {
876
+ const treePath = await this.resolvePathFromTreePath(item, depthLimit);
877
+ if (this.isPathResolvedForItem(treePath, item)) {
878
+ return treePath;
879
+ }
880
+ }
881
+ const presetPath = this.buildAreaPath(item, depthLimit);
882
+ if (this.isPathResolvedForItem(presetPath, item)) {
581
883
  return presetPath;
582
884
  }
583
- const fullNamePath = await this.resolvePathByFullName(item);
584
- if (fullNamePath && fullNamePath.length) {
885
+ const fullNamePath = await this.resolvePathByFullName(item, depthLimit);
886
+ if (this.isPathResolvedForItem(fullNamePath, item)) {
585
887
  return fullNamePath;
586
888
  }
587
- const parentPath = await this.resolvePathByParentChain(item);
588
889
  if (parentPath && parentPath.length) {
589
890
  return parentPath;
590
891
  }
892
+ if (item.treePath) {
893
+ const treePath = await this.resolvePathFromTreePath(item, depthLimit);
894
+ if (treePath && treePath.length) {
895
+ return treePath;
896
+ }
897
+ }
898
+ if (presetPath && presetPath.length) {
899
+ return presetPath;
900
+ }
901
+ if (fullNamePath && fullNamePath.length) {
902
+ return fullNamePath;
903
+ }
591
904
  return [item];
592
905
  },
906
+ async syncBrowseStateFromLeafId(id) {
907
+ if (id == null || id === "") {
908
+ return;
909
+ }
910
+ const token = ++this.syncToken;
911
+ const area = await this.fetchAreaById(id);
912
+ if (token !== this.syncToken) {
913
+ return;
914
+ }
915
+ if (area) {
916
+ await this.syncBrowseStateFromArea(area);
917
+ return;
918
+ }
919
+ await this.syncBrowseStateFromArea({
920
+ id,
921
+ fullName: this.labelValue || undefined,
922
+ });
923
+ },
593
924
  async syncBrowseStateFromArea(item) {
594
925
  const token = ++this.syncToken;
595
926
  let seed = item || {};
596
927
  if (
597
- seed.id != null &&
598
- !seed.fullName &&
599
- !seed.name &&
600
- !this.getParentIdFromRow(seed)
928
+ seed.id != null
929
+ && !seed.fullName
930
+ && !seed.name
931
+ && !this.getParentIdFromRow(seed)
601
932
  ) {
602
933
  const detail = await this.fetchAreaById(seed.id);
603
934
  if (detail) {
@@ -628,21 +959,16 @@ export default {
628
959
  if (token !== this.syncToken) {
629
960
  return;
630
961
  }
631
- path = path.slice(0, this.maxLevel);
632
962
  this.selectedPath = path;
633
- const pathLen = path.length;
634
- if (pathLen >= this.maxLevel) {
635
- this.activeLevel = this.maxLevel;
636
- } else {
637
- this.activeLevel = pathLen;
638
- }
963
+ this.activeLevel = path.length || 1;
639
964
  this.loadLevelItems(this.getParentId());
640
965
  this.emitResolvedLabelIfNeeded();
641
966
  },
642
- async resolvePathByIds(ids) {
967
+ async resolvePathByIds(ids, depthLimit) {
968
+ const limit = depthLimit == null ? ids.length : depthLimit;
643
969
  const path = [];
644
970
  let parentId = 0;
645
- for (let i = 0; i < ids.length && i < this.maxLevel; i++) {
971
+ for (let i = 0; i < ids.length && i < limit; i++) {
646
972
  const targetId = ids[i];
647
973
  const children = await this.fetchChildren(parentId);
648
974
  let matched = children.find((row) => this.getAreaId(row) == targetId);
@@ -664,7 +990,11 @@ export default {
664
990
  },
665
991
  async syncBrowseStateFromPathIds(ids) {
666
992
  const token = ++this.syncToken;
667
- const path = await this.resolvePathByIds(ids);
993
+ const depthLimit = Math.min(
994
+ Math.max(ids.length, this.maxLevel, 1),
995
+ MAX_AREA_DEPTH
996
+ );
997
+ const path = await this.resolvePathByIds(ids, depthLimit);
668
998
  if (token !== this.syncToken) {
669
999
  return;
670
1000
  }
@@ -674,9 +1004,8 @@ export default {
674
1004
  }
675
1005
  return;
676
1006
  }
677
- this.selectedPath = path.slice(0, this.maxLevel);
678
- const pathLen = this.selectedPath.length;
679
- this.activeLevel = pathLen >= this.maxLevel ? this.maxLevel : pathLen;
1007
+ this.selectedPath = path;
1008
+ this.activeLevel = path.length;
680
1009
  this.loadLevelItems(this.getParentId());
681
1010
  this.emitResolvedLabelIfNeeded();
682
1011
  },
@@ -684,6 +1013,9 @@ export default {
684
1013
  this.searchKeyword = "";
685
1014
  this.searchList = [];
686
1015
  this.searchDropdownVisible = false;
1016
+ this.cancelPendingSync();
1017
+ this.emitChangeIfCommitted();
1018
+ this.restoreBrowseStateIfUncommitted();
687
1019
  },
688
1020
  handleSearchInput() {
689
1021
  const keyword = (this.searchKeyword || "").trim();
@@ -703,7 +1035,7 @@ export default {
703
1035
  },
704
1036
  doSearch(keyword) {
705
1037
  this.searchLoading = true;
706
- this.requestAreaList({ name: keyword }).then((rows) => {
1038
+ this.requestAreaList({ name: keyword, size: 20 }).then((rows) => {
707
1039
  this.searchList = rows;
708
1040
  this.searchLoading = false;
709
1041
  });
@@ -723,7 +1055,7 @@ export default {
723
1055
  });
724
1056
  },
725
1057
  switchLevel(level) {
726
- if (level > this.maxLevel || !this.isTabEnabled(level)) {
1058
+ if (level > this.browseLevelCount || !this.isTabEnabled(level)) {
727
1059
  return;
728
1060
  }
729
1061
  this.cancelPendingSync();
@@ -748,7 +1080,9 @@ export default {
748
1080
  this.searchKeyword = "";
749
1081
  this.searchList = [];
750
1082
  this.searchDropdownVisible = false;
751
- this.applySelection(item);
1083
+ this.applySelection(item).then(() => {
1084
+ this.popoverVisible = false;
1085
+ });
752
1086
  });
753
1087
  },
754
1088
  selectItem(item) {
@@ -759,37 +1093,66 @@ export default {
759
1093
  const level = this.activeLevel;
760
1094
  this.selectedPath = this.selectedPath.slice(0, level - 1);
761
1095
  this.selectedPath.push(item);
762
- if (level >= this.maxLevel) {
763
- this.applySelection(item);
764
- this.popoverVisible = false;
765
- return;
766
- }
767
- this.activeLevel = level + 1;
768
- this.loadLevelItems(this.getAreaId(item));
1096
+ this.applySelection(item).then(() => {
1097
+ if (level >= this.maxLevel) {
1098
+ this.popoverVisible = false;
1099
+ return;
1100
+ }
1101
+ this.fetchChildren(this.getAreaId(item)).then((children) => {
1102
+ if (!children.length) {
1103
+ this.popoverVisible = false;
1104
+ return;
1105
+ }
1106
+ this.activeLevel = level + 1;
1107
+ this.levelItems = children;
1108
+ });
1109
+ });
769
1110
  },
770
- applySelection(item) {
1111
+ async applySelection(item) {
771
1112
  const path = this.selectedPath.length ? this.selectedPath : [item];
772
- const last = path[path.length - 1] || item;
773
- const label = this.buildPathLabel(path);
774
- let formattedValue = this.formatPathValue(path);
775
- if (formattedValue == null) {
776
- const leafId = this.getAreaId(last);
777
- if (leafId != null) {
778
- formattedValue =
779
- this.valueFormat === "leaf" ? leafId : String(leafId);
1113
+ let last = path[path.length - 1] || item;
1114
+ const leafId = this.getAreaId(last);
1115
+ if (leafId != null && !last.fullName) {
1116
+ const detail = await this.fetchAreaById(leafId);
1117
+ if (detail) {
1118
+ last = detail;
1119
+ if (path.length) {
1120
+ this.$set(this.selectedPath, this.selectedPath.length - 1, detail);
1121
+ }
780
1122
  }
781
1123
  }
1124
+ const label = last.fullName || this.buildPathLabel(path);
1125
+ const formattedValue = this.formatBoundValue(path, last);
1126
+ this.lastSelectionPayload = {
1127
+ value: formattedValue,
1128
+ row: last,
1129
+ label,
1130
+ path: path.map((node) => ({ ...node })),
1131
+ };
782
1132
  this.skipValueSync = true;
783
1133
  this.$emit("input", formattedValue);
784
1134
  this.$emit("update:labelValue", label);
785
- this.$emit("change", { value: formattedValue, row: last, label });
786
1135
  this.$nextTick(() => {
787
1136
  this.skipValueSync = false;
788
1137
  });
789
1138
  },
790
1139
  handleClear() {
791
- this.resetBrowseState(true);
1140
+ const oldVal = this.value;
1141
+ const wasOpen = this.popoverVisible;
1142
+ this.lastSelectionPayload = { value: null, row: null, label: null };
1143
+ this.skipValueSync = true;
1144
+ this.$emit("input", null);
1145
+ this.$emit("update:labelValue", null);
1146
+ this.resetBrowseState(false);
792
1147
  this.popoverVisible = false;
1148
+ this.$nextTick(() => {
1149
+ this.skipValueSync = false;
1150
+ if (!wasOpen && !this.isSameBoundValue(oldVal, null)) {
1151
+ this.$emit("change", this.lastSelectionPayload);
1152
+ this.committedBoundValue = null;
1153
+ this.lastSelectionPayload = null;
1154
+ }
1155
+ });
793
1156
  },
794
1157
  resetBrowseState(emitEvent) {
795
1158
  this.searchKeyword = "";
@@ -797,10 +1160,10 @@ export default {
797
1160
  this.selectedPath = [];
798
1161
  this.activeLevel = 1;
799
1162
  this.levelItems = [];
1163
+ this.committedBrowseSnapshot = null;
800
1164
  if (emitEvent) {
801
1165
  this.$emit("input", null);
802
1166
  this.$emit("update:labelValue", null);
803
- this.$emit("change", { value: null, row: null });
804
1167
  }
805
1168
  },
806
1169
  },
@@ -22,6 +22,7 @@
22
22
  />
23
23
  <baseArea
24
24
  v-else
25
+ ref="baseAreaRef"
25
26
  v-show="!isReadMode"
26
27
  class="full-width-input"
27
28
  :style="widgetStyle"
@@ -33,11 +34,11 @@
33
34
  :disabled="field.options.disabled"
34
35
  :readonly="field.options.readonly"
35
36
  :clearable="field.options.clearable"
36
- value-format="path"
37
37
  :placeholder="getI18nLabel(field.options.placeholder || '请选择地区')"
38
38
  :size="field.options.size || 'small'"
39
39
  @input="handleAreaInput"
40
40
  @change="handleAreaChange"
41
+ @popover-open="handleAreaPopoverOpen"
41
42
  />
42
43
  <template v-if="isReadMode">
43
44
  <span class="readonly-mode-field">{{ readModeText }}</span>
@@ -102,9 +103,12 @@ export default {
102
103
  return this.field.options.areaName || null;
103
104
  },
104
105
  maxLevel() {
105
- const areaDataType = this.field.options.areaDataType * 1 || 3;
106
- if (areaDataType === 1) return 2;
107
- if (areaDataType === 2) return 3;
106
+ const areaDataType = this.field.options.areaDataType;
107
+ const type =
108
+ areaDataType == null || areaDataType === "" ? 3 : areaDataType * 1;
109
+ if (type >= 0 && type <= 3) {
110
+ return type + 1;
111
+ }
108
112
  return 4;
109
113
  },
110
114
  readModeText() {
@@ -177,27 +181,45 @@ export default {
177
181
  }
178
182
  this.commitAreaValue(val);
179
183
  },
184
+ handleAreaPopoverOpen() {
185
+ if (this.designState) {
186
+ return;
187
+ }
188
+ this.oldFieldValue = deepClone(this.fieldModel);
189
+ },
180
190
  handleAreaChange(payload) {
181
191
  if (this.designState) {
182
192
  return;
183
193
  }
184
- const row =
185
- payload && Object.prototype.hasOwnProperty.call(payload, "row")
186
- ? payload.row
187
- : payload;
188
194
  const value =
189
195
  payload && Object.prototype.hasOwnProperty.call(payload, "value")
190
196
  ? payload.value
191
197
  : this.fieldModel;
192
- const label =
193
- payload && payload.label != null
194
- ? payload.label
195
- : row
196
- ? row.fullName || row.name || null
197
- : null;
198
- this.commitAreaValue(value, label);
199
198
  this.handleChangeEvent(value);
200
199
  },
200
+ createEmptyAreaPathResult(value) {
201
+ return {
202
+ value: value != null && value !== "" ? value : null,
203
+ label: null,
204
+ path: [],
205
+ province: null,
206
+ city: null,
207
+ district: null,
208
+ town: null,
209
+ };
210
+ },
211
+ async getAreaPath(value) {
212
+ if (this.designState) {
213
+ return this.createEmptyAreaPathResult(value);
214
+ }
215
+ const areaRef = this.$refs.baseAreaRef;
216
+ if (areaRef && areaRef.getAreaPath) {
217
+ const targetValue =
218
+ value !== undefined && value !== null ? value : this.getValue();
219
+ return areaRef.getAreaPath(targetValue);
220
+ }
221
+ return this.createEmptyAreaPathResult(value);
222
+ },
201
223
  getValue() {
202
224
  const fieldKey = this.fieldKeyName;
203
225
  if (this.fieldModel != null && this.fieldModel !== "") {
@@ -1,6 +1,7 @@
1
1
  <template>
2
2
  <el-form-item :label="i18nt('designer.setting.areaDataType')">
3
3
  <el-select v-model="optionModel.areaDataType" style="width: 100%">
4
+ <el-option :label="i18nt('designer.setting.areaDataType0')" :value="0" />
4
5
  <el-option :label="i18nt('designer.setting.areaDataType1')" :value="1" />
5
6
  <el-option :label="i18nt('designer.setting.areaDataType2')" :value="2" />
6
7
  <el-option :label="i18nt('designer.setting.areaDataType3')" :value="3" />
@@ -192,6 +192,7 @@ export default {
192
192
  htmlContent: "HTML",
193
193
  clearable: "Clearable",
194
194
  areaDataType: "Area Level",
195
+ areaDataType0: "Province",
195
196
  areaDataType1: "Province/City",
196
197
  areaDataType2: "Province/City/District",
197
198
  areaDataType3: "Province/City/District/Town",
@@ -355,6 +355,7 @@ export default {
355
355
  htmlContent: "HTML",
356
356
  clearable: "可清除",
357
357
  areaDataType: "地区层级",
358
+ areaDataType0: "省",
358
359
  areaDataType1: "省/市",
359
360
  areaDataType2: "省/市/区",
360
361
  areaDataType3: "省/市/区/乡镇",