cloud-web-corejs 1.0.54-dev.680 → 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.680",
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",
@@ -88,7 +88,9 @@
88
88
  <div class="base-area-level-nav">
89
89
  <el-button
90
90
  size="mini"
91
+ class="base-area-nav-btn"
91
92
  :disabled="!canPrevLevel"
93
+ @mousedown.prevent
92
94
  @click="prevLevel"
93
95
  >
94
96
  <i class="el-icon-arrow-left"></i>
@@ -96,8 +98,9 @@
96
98
  <span class="base-area-level-label">{{ currentLevelLabel }}</span>
97
99
  <el-button
98
100
  size="mini"
99
- type="primary"
101
+ class="base-area-nav-btn"
100
102
  :disabled="!canNextLevel"
103
+ @mousedown.prevent
101
104
  @click="nextLevel"
102
105
  >
103
106
  <i class="el-icon-arrow-right"></i>
@@ -132,6 +135,8 @@
132
135
 
133
136
  <script>
134
137
  const LEVEL_LABELS = ["省份", "城市", "区县", "乡镇"];
138
+ const AREA_LEVEL_KEYS = ["province", "city", "district", "town"];
139
+ const MAX_AREA_DEPTH = 4;
135
140
  const SEARCH_DEBOUNCE = 300;
136
141
 
137
142
  export default {
@@ -185,10 +190,10 @@ export default {
185
190
  type: Number,
186
191
  default: 630,
187
192
  },
188
- /** path: 逗号分隔各级 id,如 1,2,3,4leaf: 仅末级 id */
193
+ /** leaf: 绑定末级 id;path: 兼容旧版逗号分隔路径 */
189
194
  valueFormat: {
190
195
  type: String,
191
- default: "path",
196
+ default: "leaf",
192
197
  },
193
198
  },
194
199
  data() {
@@ -206,6 +211,9 @@ export default {
206
211
  searchTimer: null,
207
212
  syncToken: 0,
208
213
  skipValueSync: false,
214
+ committedBrowseSnapshot: null,
215
+ committedBoundValue: null,
216
+ lastSelectionPayload: null,
209
217
  };
210
218
  },
211
219
  computed: {
@@ -216,20 +224,20 @@ export default {
216
224
  return this.searchDropdownVisible && this.hasSearchKeyword;
217
225
  },
218
226
  displayText() {
227
+ if (this.labelValue) {
228
+ return this.labelValue;
229
+ }
219
230
  const pathLabel = this.selectedPath.length
220
231
  ? this.buildPathLabel(this.selectedPath)
221
232
  : "";
222
233
  if (this.popoverVisible && pathLabel) {
223
234
  return pathLabel;
224
235
  }
225
- if (this.labelValue) {
226
- return this.labelValue;
227
- }
228
236
  return pathLabel || "";
229
237
  },
230
238
  tabs() {
231
239
  const tabs = [];
232
- for (let i = 0; i < this.maxLevel; i++) {
240
+ for (let i = 0; i < this.browseLevelCount; i++) {
233
241
  tabs.push({
234
242
  level: i + 1,
235
243
  label: LEVEL_LABELS[i] || `第${i + 1}级`,
@@ -251,10 +259,13 @@ export default {
251
259
  },
252
260
  canNextLevel() {
253
261
  return (
254
- this.activeLevel < this.maxLevel &&
262
+ this.activeLevel < this.browseLevelCount &&
255
263
  this.isTabEnabled(this.activeLevel + 1)
256
264
  );
257
265
  },
266
+ browseLevelCount() {
267
+ return Math.max(this.maxLevel, this.selectedPath.length, 1);
268
+ },
258
269
  currentLevelLabel() {
259
270
  return LEVEL_LABELS[this.activeLevel - 1] || `第${this.activeLevel}级`;
260
271
  },
@@ -269,10 +280,6 @@ export default {
269
280
  labelValue(val) {
270
281
  if (!val && !this.value) {
271
282
  this.resetBrowseState(false);
272
- return;
273
- }
274
- if (this.value && !this.isValueSynced(this.parseValueIds(this.value))) {
275
- this.syncFromValue();
276
283
  }
277
284
  },
278
285
  },
@@ -280,47 +287,79 @@ export default {
280
287
  clearTimeout(this.searchTimer);
281
288
  },
282
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
+ },
283
303
  isSearchItemActive(item) {
284
304
  if (!item || this.value == null || this.value === "") {
285
305
  return false;
286
306
  }
287
- const ids = this.parseValueIds(this.value);
288
- if (!ids.length) {
307
+ const leafId = this.parseValueId(this.value);
308
+ if (leafId == null) {
289
309
  return false;
290
310
  }
291
- return ids[ids.length - 1] == this.getAreaId(item);
311
+ return leafId == this.getAreaId(item);
292
312
  },
293
- parseValueIds(val) {
313
+ parseValueId(val) {
294
314
  if (val == null || val === "") {
295
- return [];
315
+ return null;
296
316
  }
297
317
  const str = String(val).trim();
298
- if (this.valueFormat === "path" && str.includes(",")) {
299
- 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 [];
300
327
  }
301
- return [str];
328
+ return String(val)
329
+ .split(",")
330
+ .map((part) => part.trim())
331
+ .filter((part) => part !== "");
302
332
  },
303
- isPathValue(val) {
333
+ isLegacyPathValue(val) {
304
334
  return (
305
335
  this.valueFormat === "path" &&
306
336
  val != null &&
307
- String(val).includes(",")
337
+ String(val).includes(",") &&
338
+ !String(val).startsWith(",")
308
339
  );
309
340
  },
310
- formatPathValue(path) {
311
- if (!path || !path.length) {
312
- return null;
341
+ parseTreePathIds(treePath) {
342
+ if (treePath == null || treePath === "") {
343
+ return [];
313
344
  }
314
- const ids = path
315
- .map((item) => this.getAreaId(item))
316
- .filter((id) => id != null && id !== "");
317
- 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) {
318
354
  return null;
319
355
  }
320
- if (this.valueFormat === "leaf") {
321
- 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);
322
361
  }
323
- return ids.join(",");
362
+ return leafId;
324
363
  },
325
364
  getAreaId(item) {
326
365
  if (!item) {
@@ -337,11 +376,14 @@ export default {
337
376
  }
338
377
  return null;
339
378
  },
340
- isValueSynced(ids) {
341
- if (!ids.length || this.selectedPath.length !== ids.length) {
379
+ isValueSynced(leafId) {
380
+ if (leafId == null || leafId === "" || !this.selectedPath.length) {
342
381
  return false;
343
382
  }
344
- 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;
345
387
  },
346
388
  buildPathLabel(path) {
347
389
  if (!path || !path.length) {
@@ -356,6 +398,92 @@ export default {
356
398
  .filter((name) => !!name)
357
399
  .join("");
358
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
+ },
359
487
  emitResolvedLabelIfNeeded() {
360
488
  if (this.popoverVisible || this.labelValue) {
361
489
  return;
@@ -382,11 +510,89 @@ export default {
382
510
  return level === 1 || this.selectedPath.length >= level - 1;
383
511
  },
384
512
  handlePopoverShow() {
513
+ this.saveCommittedBrowseSnapshot();
514
+ this.$emit("popover-open");
385
515
  this.$nextTick(() => {
386
516
  this.$refs.searchInputRef && this.$refs.searchInputRef.focus();
387
517
  });
388
518
  this.initBrowseStateFromValue();
389
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
+ },
390
596
  syncFromValue() {
391
597
  if (this.skipValueSync) {
392
598
  return;
@@ -396,34 +602,68 @@ export default {
396
602
  this.resetBrowseState(false);
397
603
  return;
398
604
  }
399
- const ids = this.parseValueIds(val);
400
- 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()) {
401
612
  return;
402
613
  }
403
- if (this.isPathValue(val)) {
404
- this.syncBrowseStateFromPathIds(ids);
614
+ const leafId = this.getLeafIdFromValue(val);
615
+ if (leafId == null) {
405
616
  return;
406
617
  }
407
- this.syncBrowseStateFromArea({
408
- id: ids[0],
409
- fullName: this.labelValue || undefined,
410
- });
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
+ }
411
630
  },
412
- initBrowseStateFromValue() {
413
- 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) {
414
641
  if (!this.levelItems.length) {
415
642
  this.loadLevelItems(this.getParentId());
416
643
  }
417
644
  return;
418
645
  }
419
- const ids = this.parseValueIds(this.value);
420
- if (!this.isValueSynced(ids)) {
421
- 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
+ }
422
657
  return;
423
658
  }
424
- if (!this.levelItems.length) {
425
- this.loadLevelItems(this.getParentId());
659
+ if (this.isLegacyPathValue(val)) {
660
+ this.syncBrowseStateFromPathIds(this.parseLegacyPathIds(val));
661
+ return;
426
662
  }
663
+ this.syncBrowseStateFromLeafId(leafId);
664
+ },
665
+ initBrowseStateFromValue() {
666
+ this.syncBrowseStateForPopover();
427
667
  },
428
668
  getAreaRequestData(extra = {}) {
429
669
  return {
@@ -482,22 +722,36 @@ export default {
482
722
  if (id == null || id === "") {
483
723
  return Promise.resolve(null);
484
724
  }
485
- return this.requestAreaList({ id }).then(
486
- (rows) => rows.find((row) => this.getAreaId(row) == id) || null
487
- );
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
+ });
488
741
  },
489
742
  isAreaObject(node) {
490
743
  return node && typeof node === "object" && node.id != null;
491
744
  },
492
- buildAreaPath(item) {
745
+ buildAreaPath(item, depthLimit) {
746
+ const limit = depthLimit == null ? this.maxLevel : depthLimit;
493
747
  if (item.pathList && Array.isArray(item.pathList) && item.pathList.length > 1) {
494
748
  if (this.isAreaObject(item.pathList[0])) {
495
- return item.pathList.slice(0, this.maxLevel);
749
+ return item.pathList.slice(0, limit);
496
750
  }
497
751
  }
498
752
  if (item.areaPath && Array.isArray(item.areaPath) && item.areaPath.length > 1) {
499
753
  if (this.isAreaObject(item.areaPath[0])) {
500
- return item.areaPath.slice(0, this.maxLevel);
754
+ return item.areaPath.slice(0, limit);
501
755
  }
502
756
  }
503
757
  return null;
@@ -518,7 +772,7 @@ export default {
518
772
  }
519
773
  return null;
520
774
  },
521
- async resolvePathByFullName(item) {
775
+ async resolvePathByFullName(item, depthLimit) {
522
776
  const fullName = (item.fullName || "").trim();
523
777
  if (!fullName) {
524
778
  return null;
@@ -526,7 +780,7 @@ export default {
526
780
  const path = [];
527
781
  let parentId = 0;
528
782
  let prefix = "";
529
- for (let level = 0; level < this.maxLevel; level++) {
783
+ for (let level = 0; level < MAX_AREA_DEPTH; level++) {
530
784
  const children = await this.fetchChildren(parentId);
531
785
  if (!children.length) {
532
786
  break;
@@ -551,13 +805,13 @@ export default {
551
805
  path.push(item);
552
806
  }
553
807
  }
554
- return path.length ? path.slice(0, this.maxLevel) : null;
808
+ return path.length ? path : null;
555
809
  },
556
- async resolvePathByParentChain(item) {
810
+ async resolvePathByParentChain(item, depthLimit) {
557
811
  const chain = [];
558
812
  let current = item;
559
813
  let safety = 0;
560
- while (current && safety < this.maxLevel + 5) {
814
+ while (current && safety < MAX_AREA_DEPTH + 2) {
561
815
  safety += 1;
562
816
  chain.unshift(current);
563
817
  const parentId = this.getParentIdFromRow(current);
@@ -570,23 +824,93 @@ export default {
570
824
  }
571
825
  current = parent;
572
826
  }
573
- return chain.length ? chain.slice(0, this.maxLevel) : null;
827
+ return chain.length ? chain : null;
574
828
  },
575
- async resolveAreaPath(item) {
576
- const presetPath = this.buildAreaPath(item);
577
- 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)) {
578
873
  return presetPath;
579
874
  }
580
- const fullNamePath = await this.resolvePathByFullName(item);
581
- if (fullNamePath && fullNamePath.length) {
875
+ const fullNamePath = await this.resolvePathByFullName(item, depthLimit);
876
+ if (this.isPathResolvedForItem(fullNamePath, item)) {
582
877
  return fullNamePath;
583
878
  }
584
- const parentPath = await this.resolvePathByParentChain(item);
585
879
  if (parentPath && parentPath.length) {
586
880
  return parentPath;
587
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
+ }
588
894
  return [item];
589
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
+ },
590
914
  async syncBrowseStateFromArea(item) {
591
915
  const token = ++this.syncToken;
592
916
  let seed = item || {};
@@ -625,21 +949,16 @@ export default {
625
949
  if (token !== this.syncToken) {
626
950
  return;
627
951
  }
628
- path = path.slice(0, this.maxLevel);
629
952
  this.selectedPath = path;
630
- const pathLen = path.length;
631
- if (pathLen >= this.maxLevel) {
632
- this.activeLevel = this.maxLevel;
633
- } else {
634
- this.activeLevel = pathLen;
635
- }
953
+ this.activeLevel = path.length || 1;
636
954
  this.loadLevelItems(this.getParentId());
637
955
  this.emitResolvedLabelIfNeeded();
638
956
  },
639
- async resolvePathByIds(ids) {
957
+ async resolvePathByIds(ids, depthLimit) {
958
+ const limit = depthLimit == null ? ids.length : depthLimit;
640
959
  const path = [];
641
960
  let parentId = 0;
642
- for (let i = 0; i < ids.length && i < this.maxLevel; i++) {
961
+ for (let i = 0; i < ids.length && i < limit; i++) {
643
962
  const targetId = ids[i];
644
963
  const children = await this.fetchChildren(parentId);
645
964
  let matched = children.find((row) => this.getAreaId(row) == targetId);
@@ -661,7 +980,8 @@ export default {
661
980
  },
662
981
  async syncBrowseStateFromPathIds(ids) {
663
982
  const token = ++this.syncToken;
664
- 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);
665
985
  if (token !== this.syncToken) {
666
986
  return;
667
987
  }
@@ -671,9 +991,8 @@ export default {
671
991
  }
672
992
  return;
673
993
  }
674
- this.selectedPath = path.slice(0, this.maxLevel);
675
- const pathLen = this.selectedPath.length;
676
- this.activeLevel = pathLen >= this.maxLevel ? this.maxLevel : pathLen;
994
+ this.selectedPath = path;
995
+ this.activeLevel = path.length;
677
996
  this.loadLevelItems(this.getParentId());
678
997
  this.emitResolvedLabelIfNeeded();
679
998
  },
@@ -681,6 +1000,9 @@ export default {
681
1000
  this.searchKeyword = "";
682
1001
  this.searchList = [];
683
1002
  this.searchDropdownVisible = false;
1003
+ this.cancelPendingSync();
1004
+ this.emitChangeIfCommitted();
1005
+ this.restoreBrowseStateIfUncommitted();
684
1006
  },
685
1007
  handleSearchInput() {
686
1008
  const keyword = (this.searchKeyword || "").trim();
@@ -720,7 +1042,7 @@ export default {
720
1042
  });
721
1043
  },
722
1044
  switchLevel(level) {
723
- if (level > this.maxLevel || !this.isTabEnabled(level)) {
1045
+ if (level > this.browseLevelCount || !this.isTabEnabled(level)) {
724
1046
  return;
725
1047
  }
726
1048
  this.cancelPendingSync();
@@ -745,7 +1067,9 @@ export default {
745
1067
  this.searchKeyword = "";
746
1068
  this.searchList = [];
747
1069
  this.searchDropdownVisible = false;
748
- this.applySelection(item);
1070
+ this.applySelection(item).then(() => {
1071
+ this.popoverVisible = false;
1072
+ });
749
1073
  });
750
1074
  },
751
1075
  selectItem(item) {
@@ -756,37 +1080,66 @@ export default {
756
1080
  const level = this.activeLevel;
757
1081
  this.selectedPath = this.selectedPath.slice(0, level - 1);
758
1082
  this.selectedPath.push(item);
759
- if (level >= this.maxLevel) {
760
- this.applySelection(item);
761
- this.popoverVisible = false;
762
- return;
763
- }
764
- this.activeLevel = level + 1;
765
- 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
+ });
766
1097
  },
767
- applySelection(item) {
1098
+ async applySelection(item) {
768
1099
  const path = this.selectedPath.length ? this.selectedPath : [item];
769
- const last = path[path.length - 1] || item;
770
- const label = this.buildPathLabel(path);
771
- let formattedValue = this.formatPathValue(path);
772
- if (formattedValue == null) {
773
- const leafId = this.getAreaId(last);
774
- if (leafId != null) {
775
- formattedValue =
776
- 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
+ }
777
1109
  }
778
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
+ };
779
1119
  this.skipValueSync = true;
780
1120
  this.$emit("input", formattedValue);
781
1121
  this.$emit("update:labelValue", label);
782
- this.$emit("change", { value: formattedValue, row: last, label });
783
1122
  this.$nextTick(() => {
784
1123
  this.skipValueSync = false;
785
1124
  });
786
1125
  },
787
1126
  handleClear() {
788
- 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);
789
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
+ });
790
1143
  },
791
1144
  resetBrowseState(emitEvent) {
792
1145
  this.searchKeyword = "";
@@ -794,10 +1147,10 @@ export default {
794
1147
  this.selectedPath = [];
795
1148
  this.activeLevel = 1;
796
1149
  this.levelItems = [];
1150
+ this.committedBrowseSnapshot = null;
797
1151
  if (emitEvent) {
798
1152
  this.$emit("input", null);
799
1153
  this.$emit("update:labelValue", null);
800
- this.$emit("change", { value: null, row: null });
801
1154
  }
802
1155
  },
803
1156
  },
@@ -1016,7 +1369,42 @@ export default {
1016
1369
  display: flex;
1017
1370
  align-items: center;
1018
1371
  justify-content: space-between;
1019
- margin-top: 4px;
1372
+ margin-top: 8px;
1373
+
1374
+ .base-area-nav-btn {
1375
+ min-width: 32px;
1376
+ padding: 7px 10px;
1377
+ border-radius: 8px;
1378
+ color: #fff;
1379
+ border-color: $baseColor;
1380
+ background-color: $baseColor;
1381
+
1382
+ i {
1383
+ font-weight: bold;
1384
+ }
1385
+
1386
+ &.el-button:focus,
1387
+ &.el-button:active {
1388
+ color: #fff;
1389
+ border-color: $baseColor;
1390
+ background-color: $baseColor;
1391
+ }
1392
+
1393
+ &.el-button:hover:not(.is-disabled) {
1394
+ color: #fff;
1395
+ border-color: $baseColor;
1396
+ background-color: $baseColor;
1397
+ opacity: 0.92;
1398
+ }
1399
+
1400
+ &.is-disabled,
1401
+ &.el-button.is-disabled {
1402
+ color: #c0c4cc;
1403
+ border-color: #eee;
1404
+ background-color: #f5f7fa;
1405
+ opacity: 1;
1406
+ }
1407
+ }
1020
1408
 
1021
1409
  .base-area-level-label {
1022
1410
  flex: 1;
@@ -1090,6 +1478,26 @@ export default {
1090
1478
  box-sizing: border-box;
1091
1479
  }
1092
1480
 
1481
+ .base-area-nav-btn.el-button {
1482
+ border-radius: 8px;
1483
+ color: #fff;
1484
+ border-color: $baseColor;
1485
+ background-color: $baseColor;
1486
+ }
1487
+
1488
+ .base-area-nav-btn.el-button:focus,
1489
+ .base-area-nav-btn.el-button:active {
1490
+ color: #fff;
1491
+ border-color: $baseColor;
1492
+ background-color: $baseColor;
1493
+ }
1494
+
1495
+ .base-area-nav-btn.el-button.is-disabled {
1496
+ color: #c0c4cc;
1497
+ border-color: #eee;
1498
+ background-color: #f5f7fa;
1499
+ }
1500
+
1093
1501
  .base-area-tab.disabled {
1094
1502
  color: #c0c4cc;
1095
1503
  cursor: not-allowed;
@@ -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: "省/市/区/乡镇",