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

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.682",
5
5
  "scripts": {
6
6
  "dev": "vue-cli-service serve",
7
7
  "lint": "eslint --ext .js,.vue src",
@@ -135,6 +135,8 @@
135
135
 
136
136
  <script>
137
137
  const LEVEL_LABELS = ["省份", "城市", "区县", "乡镇"];
138
+ const AREA_LEVEL_KEYS = ["province", "city", "district", "town"];
139
+ const MAX_AREA_DEPTH = 4;
138
140
  const SEARCH_DEBOUNCE = 300;
139
141
 
140
142
  export default {
@@ -188,10 +190,10 @@ export default {
188
190
  type: Number,
189
191
  default: 630,
190
192
  },
191
- /** path: 逗号分隔各级 id,如 1,2,3,4leaf: 仅末级 id */
193
+ /** leaf: 绑定末级 id;path: 兼容旧版逗号分隔路径 */
192
194
  valueFormat: {
193
195
  type: String,
194
- default: "path",
196
+ default: "leaf",
195
197
  },
196
198
  },
197
199
  data() {
@@ -209,6 +211,9 @@ export default {
209
211
  searchTimer: null,
210
212
  syncToken: 0,
211
213
  skipValueSync: false,
214
+ committedBrowseSnapshot: null,
215
+ committedBoundValue: null,
216
+ lastSelectionPayload: null,
212
217
  };
213
218
  },
214
219
  computed: {
@@ -219,20 +224,20 @@ export default {
219
224
  return this.searchDropdownVisible && this.hasSearchKeyword;
220
225
  },
221
226
  displayText() {
227
+ if (this.labelValue) {
228
+ return this.labelValue;
229
+ }
222
230
  const pathLabel = this.selectedPath.length
223
231
  ? this.buildPathLabel(this.selectedPath)
224
232
  : "";
225
233
  if (this.popoverVisible && pathLabel) {
226
234
  return pathLabel;
227
235
  }
228
- if (this.labelValue) {
229
- return this.labelValue;
230
- }
231
236
  return pathLabel || "";
232
237
  },
233
238
  tabs() {
234
239
  const tabs = [];
235
- for (let i = 0; i < this.maxLevel; i++) {
240
+ for (let i = 0; i < this.browseLevelCount; i++) {
236
241
  tabs.push({
237
242
  level: i + 1,
238
243
  label: LEVEL_LABELS[i] || `第${i + 1}级`,
@@ -254,10 +259,13 @@ export default {
254
259
  },
255
260
  canNextLevel() {
256
261
  return (
257
- this.activeLevel < this.maxLevel &&
262
+ this.activeLevel < this.browseLevelCount &&
258
263
  this.isTabEnabled(this.activeLevel + 1)
259
264
  );
260
265
  },
266
+ browseLevelCount() {
267
+ return Math.max(this.maxLevel, this.selectedPath.length, 1);
268
+ },
261
269
  currentLevelLabel() {
262
270
  return LEVEL_LABELS[this.activeLevel - 1] || `第${this.activeLevel}级`;
263
271
  },
@@ -272,10 +280,6 @@ export default {
272
280
  labelValue(val) {
273
281
  if (!val && !this.value) {
274
282
  this.resetBrowseState(false);
275
- return;
276
- }
277
- if (this.value && !this.isValueSynced(this.parseValueIds(this.value))) {
278
- this.syncFromValue();
279
283
  }
280
284
  },
281
285
  },
@@ -283,47 +287,79 @@ export default {
283
287
  clearTimeout(this.searchTimer);
284
288
  },
285
289
  methods: {
290
+ getResolveDepthLimit(item) {
291
+ if (item && item.treePath) {
292
+ const ids = this.parseTreePathIds(item.treePath);
293
+ return Math.min(Math.max(ids.length, this.maxLevel, 1), MAX_AREA_DEPTH);
294
+ }
295
+ return MAX_AREA_DEPTH;
296
+ },
297
+ isPathResolvedForItem(path, item) {
298
+ if (!path || !path.length || !item || item.id == null) {
299
+ return false;
300
+ }
301
+ return this.getAreaId(path[path.length - 1]) == item.id;
302
+ },
286
303
  isSearchItemActive(item) {
287
304
  if (!item || this.value == null || this.value === "") {
288
305
  return false;
289
306
  }
290
- const ids = this.parseValueIds(this.value);
291
- if (!ids.length) {
307
+ const leafId = this.parseValueId(this.value);
308
+ if (leafId == null) {
292
309
  return false;
293
310
  }
294
- return ids[ids.length - 1] == this.getAreaId(item);
311
+ return leafId == this.getAreaId(item);
295
312
  },
296
- parseValueIds(val) {
313
+ parseValueId(val) {
297
314
  if (val == null || val === "") {
298
- return [];
315
+ return null;
299
316
  }
300
317
  const str = String(val).trim();
301
- if (this.valueFormat === "path" && str.includes(",")) {
302
- return str.split(",").map((part) => part.trim()).filter((part) => part !== "");
318
+ if (this.isLegacyPathValue(str)) {
319
+ const parts = str.split(",").map((part) => part.trim()).filter(Boolean);
320
+ return parts.length ? parts[parts.length - 1] : null;
321
+ }
322
+ return str;
323
+ },
324
+ parseLegacyPathIds(val) {
325
+ if (val == null || val === "") {
326
+ return [];
303
327
  }
304
- return [str];
328
+ return String(val)
329
+ .split(",")
330
+ .map((part) => part.trim())
331
+ .filter((part) => part !== "");
305
332
  },
306
- isPathValue(val) {
333
+ isLegacyPathValue(val) {
307
334
  return (
308
335
  this.valueFormat === "path" &&
309
336
  val != null &&
310
- String(val).includes(",")
337
+ String(val).includes(",") &&
338
+ !String(val).startsWith(",")
311
339
  );
312
340
  },
313
- formatPathValue(path) {
314
- if (!path || !path.length) {
315
- return null;
341
+ parseTreePathIds(treePath) {
342
+ if (treePath == null || treePath === "") {
343
+ return [];
316
344
  }
317
- const ids = path
318
- .map((item) => this.getAreaId(item))
319
- .filter((id) => id != null && id !== "");
320
- if (!ids.length) {
345
+ return String(treePath)
346
+ .split(",")
347
+ .map((part) => part.trim())
348
+ .filter((part) => part !== "");
349
+ },
350
+ formatBoundValue(path, item) {
351
+ const last = item || (path && path.length ? path[path.length - 1] : null);
352
+ const leafId = this.getAreaId(last);
353
+ if (leafId == null) {
321
354
  return null;
322
355
  }
323
- if (this.valueFormat === "leaf") {
324
- return ids[ids.length - 1];
356
+ if (this.valueFormat === "path") {
357
+ const ids = (path || [])
358
+ .map((node) => this.getAreaId(node))
359
+ .filter((id) => id != null && id !== "");
360
+ return ids.length ? ids.join(",") : String(leafId);
325
361
  }
326
- return ids.join(",");
362
+ return leafId;
327
363
  },
328
364
  getAreaId(item) {
329
365
  if (!item) {
@@ -340,11 +376,14 @@ export default {
340
376
  }
341
377
  return null;
342
378
  },
343
- isValueSynced(ids) {
344
- if (!ids.length || this.selectedPath.length !== ids.length) {
379
+ isValueSynced(leafId) {
380
+ if (leafId == null || leafId === "" || !this.selectedPath.length) {
345
381
  return false;
346
382
  }
347
- return ids.every((id, index) => this.getAreaId(this.selectedPath[index]) == id);
383
+ const currentLeaf = this.getAreaId(
384
+ this.selectedPath[this.selectedPath.length - 1]
385
+ );
386
+ return currentLeaf == leafId;
348
387
  },
349
388
  buildPathLabel(path) {
350
389
  if (!path || !path.length) {
@@ -359,6 +398,92 @@ export default {
359
398
  .filter((name) => !!name)
360
399
  .join("");
361
400
  },
401
+ createEmptyAreaPathResult(value) {
402
+ return {
403
+ value: value != null && value !== "" ? value : null,
404
+ label: null,
405
+ path: [],
406
+ province: null,
407
+ city: null,
408
+ district: null,
409
+ town: null,
410
+ };
411
+ },
412
+ formatAreaPathResult(value, path, label) {
413
+ const result = this.createEmptyAreaPathResult(value);
414
+ result.label =
415
+ label != null && label !== ""
416
+ ? label
417
+ : this.buildPathLabel(path) || this.labelValue || null;
418
+ result.path = path || [];
419
+ (path || []).forEach((node, index) => {
420
+ const key = AREA_LEVEL_KEYS[index];
421
+ if (key && Object.prototype.hasOwnProperty.call(result, key)) {
422
+ result[key] = node;
423
+ }
424
+ });
425
+ return result;
426
+ },
427
+ async ensurePathDetail(path) {
428
+ const result = [];
429
+ for (let i = 0; i < path.length; i++) {
430
+ const node = path[i];
431
+ const id = this.getAreaId(node);
432
+ if (id != null) {
433
+ const detail = await this.fetchAreaById(id);
434
+ result.push(detail || node);
435
+ } else {
436
+ result.push(node);
437
+ }
438
+ }
439
+ return result;
440
+ },
441
+ async getAreaPath(value) {
442
+ const val = value !== undefined ? value : this.value;
443
+ if (val == null || val === "") {
444
+ return this.createEmptyAreaPathResult(null);
445
+ }
446
+ const leafId = this.getLeafIdFromValue(val);
447
+ let path = null;
448
+ if (
449
+ leafId != null &&
450
+ this.isValueSynced(leafId) &&
451
+ !this.shouldResyncBrowsePath() &&
452
+ this.selectedPath.length
453
+ ) {
454
+ path = this.selectedPath.map((item) => ({ ...item }));
455
+ }
456
+ if (!path || !path.length) {
457
+ if (this.isLegacyPathValue(val)) {
458
+ path = await this.resolvePathByIds(this.parseLegacyPathIds(val));
459
+ } else if (leafId != null) {
460
+ const area = await this.fetchAreaById(leafId);
461
+ if (area) {
462
+ path = await this.resolveAreaPath(area);
463
+ } else {
464
+ path = [{ id: leafId, fullName: this.labelValue || undefined }];
465
+ }
466
+ }
467
+ }
468
+ if (!path || !path.length) {
469
+ return this.createEmptyAreaPathResult(val);
470
+ }
471
+ path = await this.ensurePathDetail(path);
472
+ return this.formatAreaPathResult(val, path);
473
+ },
474
+ hasDisplayLabel() {
475
+ return !!(this.labelValue && String(this.labelValue).trim());
476
+ },
477
+ getLeafIdFromValue(val) {
478
+ if (val == null || val === "") {
479
+ return null;
480
+ }
481
+ if (this.isLegacyPathValue(val)) {
482
+ const ids = this.parseLegacyPathIds(val);
483
+ return ids.length ? ids[ids.length - 1] : null;
484
+ }
485
+ return this.parseValueId(val);
486
+ },
362
487
  emitResolvedLabelIfNeeded() {
363
488
  if (this.popoverVisible || this.labelValue) {
364
489
  return;
@@ -385,11 +510,89 @@ export default {
385
510
  return level === 1 || this.selectedPath.length >= level - 1;
386
511
  },
387
512
  handlePopoverShow() {
513
+ this.saveCommittedBrowseSnapshot();
514
+ this.$emit("popover-open");
388
515
  this.$nextTick(() => {
389
516
  this.$refs.searchInputRef && this.$refs.searchInputRef.focus();
390
517
  });
391
518
  this.initBrowseStateFromValue();
392
519
  },
520
+ saveCommittedBrowseSnapshot() {
521
+ this.committedBoundValue = this.value;
522
+ this.committedBrowseSnapshot = {
523
+ boundValue: this.value,
524
+ leafId: this.getLeafIdFromValue(this.value),
525
+ selectedPath: this.selectedPath.map((item) => ({ ...item })),
526
+ activeLevel: this.activeLevel,
527
+ };
528
+ },
529
+ isSameBoundValue(a, b) {
530
+ const normalize = (val) => {
531
+ if (val == null || val === "") {
532
+ return null;
533
+ }
534
+ return String(val);
535
+ };
536
+ return normalize(a) === normalize(b);
537
+ },
538
+ emitChangeIfCommitted() {
539
+ const snapshot = this.committedBrowseSnapshot;
540
+ if (!snapshot) {
541
+ return;
542
+ }
543
+ const oldVal = snapshot.boundValue;
544
+ const newVal = this.value;
545
+ if (!this.isSameBoundValue(oldVal, newVal)) {
546
+ const payload = this.lastSelectionPayload || {
547
+ value: newVal,
548
+ row: null,
549
+ label: this.labelValue,
550
+ };
551
+ this.$emit("change", {
552
+ value: payload.value != null ? payload.value : newVal,
553
+ row: payload.row || null,
554
+ label: payload.label != null ? payload.label : this.labelValue,
555
+ path: payload.path || [],
556
+ });
557
+ }
558
+ this.committedBoundValue = null;
559
+ this.lastSelectionPayload = null;
560
+ },
561
+ restoreBrowseStateIfUncommitted() {
562
+ const snapshot = this.committedBrowseSnapshot;
563
+ if (!snapshot) {
564
+ return;
565
+ }
566
+ if (!this.isSameBoundValue(this.value, snapshot.boundValue)) {
567
+ this.levelItems = [];
568
+ this.committedBrowseSnapshot = null;
569
+ return;
570
+ }
571
+ const committedLeafId = snapshot.leafId;
572
+ if (this.isValueSynced(committedLeafId)) {
573
+ this.levelItems = [];
574
+ this.committedBrowseSnapshot = null;
575
+ return;
576
+ }
577
+ if (snapshot.selectedPath && snapshot.selectedPath.length) {
578
+ this.selectedPath = snapshot.selectedPath.map((item) => ({ ...item }));
579
+ this.activeLevel = snapshot.activeLevel;
580
+ } else if (committedLeafId) {
581
+ this.selectedPath = [
582
+ {
583
+ id: committedLeafId,
584
+ fullName: this.labelValue || undefined,
585
+ name: this.labelValue || undefined,
586
+ },
587
+ ];
588
+ this.activeLevel = 1;
589
+ } else {
590
+ this.selectedPath = [];
591
+ this.activeLevel = 1;
592
+ }
593
+ this.levelItems = [];
594
+ this.committedBrowseSnapshot = null;
595
+ },
393
596
  syncFromValue() {
394
597
  if (this.skipValueSync) {
395
598
  return;
@@ -399,34 +602,68 @@ export default {
399
602
  this.resetBrowseState(false);
400
603
  return;
401
604
  }
402
- const ids = this.parseValueIds(val);
403
- if (this.isValueSynced(ids)) {
605
+ if (!this.hasDisplayLabel()) {
606
+ this.resolveLabelFromValue();
607
+ }
608
+ },
609
+ async resolveLabelFromValue() {
610
+ const val = this.value;
611
+ if (!val || this.hasDisplayLabel()) {
404
612
  return;
405
613
  }
406
- if (this.isPathValue(val)) {
407
- this.syncBrowseStateFromPathIds(ids);
614
+ const leafId = this.getLeafIdFromValue(val);
615
+ if (leafId == null) {
408
616
  return;
409
617
  }
410
- this.syncBrowseStateFromArea({
411
- id: ids[0],
412
- fullName: this.labelValue || undefined,
413
- });
618
+ const token = ++this.syncToken;
619
+ const area = await this.fetchAreaById(leafId);
620
+ if (token !== this.syncToken || this.hasDisplayLabel()) {
621
+ return;
622
+ }
623
+ const label =
624
+ (area && area.fullName) ||
625
+ (area && area.name) ||
626
+ null;
627
+ if (label) {
628
+ this.$emit("update:labelValue", label);
629
+ }
414
630
  },
415
- initBrowseStateFromValue() {
416
- if (!this.value) {
631
+ shouldResyncBrowsePath() {
632
+ if (!this.labelValue || !this.selectedPath.length) {
633
+ return false;
634
+ }
635
+ const pathLabel = this.buildPathLabel(this.selectedPath);
636
+ return pathLabel !== String(this.labelValue).trim();
637
+ },
638
+ syncBrowseStateForPopover() {
639
+ const val = this.value;
640
+ if (!val) {
417
641
  if (!this.levelItems.length) {
418
642
  this.loadLevelItems(this.getParentId());
419
643
  }
420
644
  return;
421
645
  }
422
- const ids = this.parseValueIds(this.value);
423
- if (!this.isValueSynced(ids)) {
424
- this.syncFromValue();
646
+ const leafId = this.getLeafIdFromValue(val);
647
+ if (this.isValueSynced(leafId) && !this.shouldResyncBrowsePath()) {
648
+ const expectedActive = this.selectedPath.length || 1;
649
+ if (this.activeLevel !== expectedActive) {
650
+ this.activeLevel = expectedActive;
651
+ this.loadLevelItems(this.getParentId());
652
+ return;
653
+ }
654
+ if (!this.levelItems.length) {
655
+ this.loadLevelItems(this.getParentId());
656
+ }
425
657
  return;
426
658
  }
427
- if (!this.levelItems.length) {
428
- this.loadLevelItems(this.getParentId());
659
+ if (this.isLegacyPathValue(val)) {
660
+ this.syncBrowseStateFromPathIds(this.parseLegacyPathIds(val));
661
+ return;
429
662
  }
663
+ this.syncBrowseStateFromLeafId(leafId);
664
+ },
665
+ initBrowseStateFromValue() {
666
+ this.syncBrowseStateForPopover();
430
667
  },
431
668
  getAreaRequestData(extra = {}) {
432
669
  return {
@@ -485,22 +722,36 @@ export default {
485
722
  if (id == null || id === "") {
486
723
  return Promise.resolve(null);
487
724
  }
488
- return this.requestAreaList({ id }).then(
489
- (rows) => rows.find((row) => this.getAreaId(row) == id) || null
490
- );
725
+ return new Promise((resolve) => {
726
+ this.$http({
727
+ url: USER_PREFIX + "/area/get",
728
+ method: "post",
729
+ data: this.getAreaRequestData({ id }),
730
+ success: (res) => {
731
+ const objx = res && res.objx;
732
+ if (objx && typeof objx === "object" && !Array.isArray(objx)) {
733
+ resolve(objx);
734
+ return;
735
+ }
736
+ resolve(null);
737
+ },
738
+ error: () => resolve(null),
739
+ });
740
+ });
491
741
  },
492
742
  isAreaObject(node) {
493
743
  return node && typeof node === "object" && node.id != null;
494
744
  },
495
- buildAreaPath(item) {
745
+ buildAreaPath(item, depthLimit) {
746
+ const limit = depthLimit == null ? this.maxLevel : depthLimit;
496
747
  if (item.pathList && Array.isArray(item.pathList) && item.pathList.length > 1) {
497
748
  if (this.isAreaObject(item.pathList[0])) {
498
- return item.pathList.slice(0, this.maxLevel);
749
+ return item.pathList.slice(0, limit);
499
750
  }
500
751
  }
501
752
  if (item.areaPath && Array.isArray(item.areaPath) && item.areaPath.length > 1) {
502
753
  if (this.isAreaObject(item.areaPath[0])) {
503
- return item.areaPath.slice(0, this.maxLevel);
754
+ return item.areaPath.slice(0, limit);
504
755
  }
505
756
  }
506
757
  return null;
@@ -521,7 +772,7 @@ export default {
521
772
  }
522
773
  return null;
523
774
  },
524
- async resolvePathByFullName(item) {
775
+ async resolvePathByFullName(item, depthLimit) {
525
776
  const fullName = (item.fullName || "").trim();
526
777
  if (!fullName) {
527
778
  return null;
@@ -529,7 +780,7 @@ export default {
529
780
  const path = [];
530
781
  let parentId = 0;
531
782
  let prefix = "";
532
- for (let level = 0; level < this.maxLevel; level++) {
783
+ for (let level = 0; level < MAX_AREA_DEPTH; level++) {
533
784
  const children = await this.fetchChildren(parentId);
534
785
  if (!children.length) {
535
786
  break;
@@ -554,13 +805,13 @@ export default {
554
805
  path.push(item);
555
806
  }
556
807
  }
557
- return path.length ? path.slice(0, this.maxLevel) : null;
808
+ return path.length ? path : null;
558
809
  },
559
- async resolvePathByParentChain(item) {
810
+ async resolvePathByParentChain(item, depthLimit) {
560
811
  const chain = [];
561
812
  let current = item;
562
813
  let safety = 0;
563
- while (current && safety < this.maxLevel + 5) {
814
+ while (current && safety < MAX_AREA_DEPTH + 2) {
564
815
  safety += 1;
565
816
  chain.unshift(current);
566
817
  const parentId = this.getParentIdFromRow(current);
@@ -573,23 +824,93 @@ export default {
573
824
  }
574
825
  current = parent;
575
826
  }
576
- return chain.length ? chain.slice(0, this.maxLevel) : null;
827
+ return chain.length ? chain : null;
577
828
  },
578
- async resolveAreaPath(item) {
579
- const presetPath = this.buildAreaPath(item);
580
- if (presetPath && presetPath.length) {
829
+ async resolvePathFromTreePath(area, depthLimit) {
830
+ const ids = this.parseTreePathIds(area && area.treePath);
831
+ if (!ids.length) {
832
+ return area && area.id != null ? [area] : null;
833
+ }
834
+ const path = [];
835
+ for (let i = 0; i < ids.length; i++) {
836
+ const id = ids[i];
837
+ let node = null;
838
+ if (area && this.getAreaId(area) == id) {
839
+ node = area;
840
+ } else {
841
+ node = await this.fetchAreaById(id);
842
+ }
843
+ path.push(node || { id });
844
+ }
845
+ if (area && area.id != null) {
846
+ const exists = path.some((node) => this.getAreaId(node) == area.id);
847
+ if (!exists) {
848
+ path.push(area);
849
+ }
850
+ }
851
+ return path.length ? path : null;
852
+ },
853
+ async resolveAreaPath(item, options = {}) {
854
+ if (!item || item.id == null) {
855
+ return item ? [item] : [];
856
+ }
857
+ const parentPath = await this.resolvePathByParentChain(item);
858
+ if (this.isPathResolvedForItem(parentPath, item)) {
859
+ return parentPath;
860
+ }
861
+ const depthLimit =
862
+ options.depthLimit != null
863
+ ? options.depthLimit
864
+ : this.getResolveDepthLimit(item);
865
+ if (item.treePath) {
866
+ const treePath = await this.resolvePathFromTreePath(item, depthLimit);
867
+ if (this.isPathResolvedForItem(treePath, item)) {
868
+ return treePath;
869
+ }
870
+ }
871
+ const presetPath = this.buildAreaPath(item, depthLimit);
872
+ if (this.isPathResolvedForItem(presetPath, item)) {
581
873
  return presetPath;
582
874
  }
583
- const fullNamePath = await this.resolvePathByFullName(item);
584
- if (fullNamePath && fullNamePath.length) {
875
+ const fullNamePath = await this.resolvePathByFullName(item, depthLimit);
876
+ if (this.isPathResolvedForItem(fullNamePath, item)) {
585
877
  return fullNamePath;
586
878
  }
587
- const parentPath = await this.resolvePathByParentChain(item);
588
879
  if (parentPath && parentPath.length) {
589
880
  return parentPath;
590
881
  }
882
+ if (item.treePath) {
883
+ const treePath = await this.resolvePathFromTreePath(item, depthLimit);
884
+ if (treePath && treePath.length) {
885
+ return treePath;
886
+ }
887
+ }
888
+ if (presetPath && presetPath.length) {
889
+ return presetPath;
890
+ }
891
+ if (fullNamePath && fullNamePath.length) {
892
+ return fullNamePath;
893
+ }
591
894
  return [item];
592
895
  },
896
+ async syncBrowseStateFromLeafId(id) {
897
+ if (id == null || id === "") {
898
+ return;
899
+ }
900
+ const token = ++this.syncToken;
901
+ const area = await this.fetchAreaById(id);
902
+ if (token !== this.syncToken) {
903
+ return;
904
+ }
905
+ if (area) {
906
+ await this.syncBrowseStateFromArea(area);
907
+ return;
908
+ }
909
+ await this.syncBrowseStateFromArea({
910
+ id,
911
+ fullName: this.labelValue || undefined,
912
+ });
913
+ },
593
914
  async syncBrowseStateFromArea(item) {
594
915
  const token = ++this.syncToken;
595
916
  let seed = item || {};
@@ -628,21 +949,16 @@ export default {
628
949
  if (token !== this.syncToken) {
629
950
  return;
630
951
  }
631
- path = path.slice(0, this.maxLevel);
632
952
  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
- }
953
+ this.activeLevel = path.length || 1;
639
954
  this.loadLevelItems(this.getParentId());
640
955
  this.emitResolvedLabelIfNeeded();
641
956
  },
642
- async resolvePathByIds(ids) {
957
+ async resolvePathByIds(ids, depthLimit) {
958
+ const limit = depthLimit == null ? ids.length : depthLimit;
643
959
  const path = [];
644
960
  let parentId = 0;
645
- for (let i = 0; i < ids.length && i < this.maxLevel; i++) {
961
+ for (let i = 0; i < ids.length && i < limit; i++) {
646
962
  const targetId = ids[i];
647
963
  const children = await this.fetchChildren(parentId);
648
964
  let matched = children.find((row) => this.getAreaId(row) == targetId);
@@ -664,7 +980,8 @@ export default {
664
980
  },
665
981
  async syncBrowseStateFromPathIds(ids) {
666
982
  const token = ++this.syncToken;
667
- const path = await this.resolvePathByIds(ids);
983
+ const depthLimit = Math.min(Math.max(ids.length, this.maxLevel, 1), MAX_AREA_DEPTH);
984
+ const path = await this.resolvePathByIds(ids, depthLimit);
668
985
  if (token !== this.syncToken) {
669
986
  return;
670
987
  }
@@ -674,9 +991,8 @@ export default {
674
991
  }
675
992
  return;
676
993
  }
677
- this.selectedPath = path.slice(0, this.maxLevel);
678
- const pathLen = this.selectedPath.length;
679
- this.activeLevel = pathLen >= this.maxLevel ? this.maxLevel : pathLen;
994
+ this.selectedPath = path;
995
+ this.activeLevel = path.length;
680
996
  this.loadLevelItems(this.getParentId());
681
997
  this.emitResolvedLabelIfNeeded();
682
998
  },
@@ -684,6 +1000,9 @@ export default {
684
1000
  this.searchKeyword = "";
685
1001
  this.searchList = [];
686
1002
  this.searchDropdownVisible = false;
1003
+ this.cancelPendingSync();
1004
+ this.emitChangeIfCommitted();
1005
+ this.restoreBrowseStateIfUncommitted();
687
1006
  },
688
1007
  handleSearchInput() {
689
1008
  const keyword = (this.searchKeyword || "").trim();
@@ -723,7 +1042,7 @@ export default {
723
1042
  });
724
1043
  },
725
1044
  switchLevel(level) {
726
- if (level > this.maxLevel || !this.isTabEnabled(level)) {
1045
+ if (level > this.browseLevelCount || !this.isTabEnabled(level)) {
727
1046
  return;
728
1047
  }
729
1048
  this.cancelPendingSync();
@@ -748,7 +1067,9 @@ export default {
748
1067
  this.searchKeyword = "";
749
1068
  this.searchList = [];
750
1069
  this.searchDropdownVisible = false;
751
- this.applySelection(item);
1070
+ this.applySelection(item).then(() => {
1071
+ this.popoverVisible = false;
1072
+ });
752
1073
  });
753
1074
  },
754
1075
  selectItem(item) {
@@ -759,37 +1080,66 @@ export default {
759
1080
  const level = this.activeLevel;
760
1081
  this.selectedPath = this.selectedPath.slice(0, level - 1);
761
1082
  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));
1083
+ this.applySelection(item).then(() => {
1084
+ if (level >= this.maxLevel) {
1085
+ this.popoverVisible = false;
1086
+ return;
1087
+ }
1088
+ this.fetchChildren(this.getAreaId(item)).then((children) => {
1089
+ if (!children.length) {
1090
+ this.popoverVisible = false;
1091
+ return;
1092
+ }
1093
+ this.activeLevel = level + 1;
1094
+ this.levelItems = children;
1095
+ });
1096
+ });
769
1097
  },
770
- applySelection(item) {
1098
+ async applySelection(item) {
771
1099
  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);
1100
+ let last = path[path.length - 1] || item;
1101
+ const leafId = this.getAreaId(last);
1102
+ if (leafId != null && !last.fullName) {
1103
+ const detail = await this.fetchAreaById(leafId);
1104
+ if (detail) {
1105
+ last = detail;
1106
+ if (path.length) {
1107
+ this.$set(this.selectedPath, this.selectedPath.length - 1, detail);
1108
+ }
780
1109
  }
781
1110
  }
1111
+ const label = last.fullName || this.buildPathLabel(path);
1112
+ const formattedValue = this.formatBoundValue(path, last);
1113
+ this.lastSelectionPayload = {
1114
+ value: formattedValue,
1115
+ row: last,
1116
+ label,
1117
+ path: path.map((node) => ({ ...node })),
1118
+ };
782
1119
  this.skipValueSync = true;
783
1120
  this.$emit("input", formattedValue);
784
1121
  this.$emit("update:labelValue", label);
785
- this.$emit("change", { value: formattedValue, row: last, label });
786
1122
  this.$nextTick(() => {
787
1123
  this.skipValueSync = false;
788
1124
  });
789
1125
  },
790
1126
  handleClear() {
791
- this.resetBrowseState(true);
1127
+ const oldVal = this.value;
1128
+ const wasOpen = this.popoverVisible;
1129
+ this.lastSelectionPayload = { value: null, row: null, label: null };
1130
+ this.skipValueSync = true;
1131
+ this.$emit("input", null);
1132
+ this.$emit("update:labelValue", null);
1133
+ this.resetBrowseState(false);
792
1134
  this.popoverVisible = false;
1135
+ this.$nextTick(() => {
1136
+ this.skipValueSync = false;
1137
+ if (!wasOpen && !this.isSameBoundValue(oldVal, null)) {
1138
+ this.$emit("change", this.lastSelectionPayload);
1139
+ this.committedBoundValue = null;
1140
+ this.lastSelectionPayload = null;
1141
+ }
1142
+ });
793
1143
  },
794
1144
  resetBrowseState(emitEvent) {
795
1145
  this.searchKeyword = "";
@@ -797,10 +1147,10 @@ export default {
797
1147
  this.selectedPath = [];
798
1148
  this.activeLevel = 1;
799
1149
  this.levelItems = [];
1150
+ this.committedBrowseSnapshot = null;
800
1151
  if (emitEvent) {
801
1152
  this.$emit("input", null);
802
1153
  this.$emit("update:labelValue", null);
803
- this.$emit("change", { value: null, row: null });
804
1154
  }
805
1155
  },
806
1156
  },
@@ -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: "省/市/区/乡镇",