xt-element-ui 1.3.3 → 1.4.0

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.
Files changed (124) hide show
  1. package/CHANGELOG.md +72 -72
  2. package/LICENSE +21 -21
  3. package/README.md +240 -240
  4. package/docs/README.md +100 -100
  5. package/docs/components/base/xt-button.md +114 -114
  6. package/docs/components/base/xt-card-item.md +104 -104
  7. package/docs/components/base/xt-card.md +108 -108
  8. package/docs/components/base/xt-config-provider.md +199 -199
  9. package/docs/components/base/xt-flex-box.md +115 -115
  10. package/docs/components/base/xt-grid-box.md +302 -302
  11. package/docs/components/base/xt-input.md +150 -150
  12. package/docs/components/base/xt-map-provider.md +23 -23
  13. package/docs/components/base/xt-map.md +331 -331
  14. package/docs/components/base/xt-step-price.md +165 -165
  15. package/docs/components/base/xt-text.md +212 -212
  16. package/docs/components/base/xt-time.md +165 -165
  17. package/docs/components/extend/ex-bar.md +67 -67
  18. package/docs/components/extend/ex-button.md +62 -62
  19. package/docs/components/extend/ex-card.md +86 -86
  20. package/docs/components/extend/ex-chart.md +463 -463
  21. package/docs/components/extend/ex-icon.md +189 -189
  22. package/docs/components/extend/ex-line.md +70 -70
  23. package/docs/components/extend/ex-multi.md +155 -155
  24. package/docs/components/extend/ex-pie.md +69 -69
  25. package/docs/components/extend/ex-select-tree.md +210 -210
  26. package/docs/components/extend/ex-table.md +590 -590
  27. package/docs/components/extend/ex-upload.md +134 -134
  28. package/docs/components/utils/size.md +147 -147
  29. package/docs/components/utils/theme.md +182 -182
  30. package/lib/index.common.js +4575 -4186
  31. package/lib/index.css +1 -1
  32. package/lib/index.umd.js +4475 -4085
  33. package/lib/index.umd.min.js +5 -5
  34. package/package.json +80 -81
  35. package/src/components/ex-button/index.js +7 -7
  36. package/src/components/ex-button/index.vue +66 -66
  37. package/src/components/ex-button/style/index.scss +67 -67
  38. package/src/components/ex-card/index.js +7 -7
  39. package/src/components/ex-card/index.vue +68 -68
  40. package/src/components/ex-card/style/index.scss +73 -73
  41. package/src/components/ex-chart/index.js +7 -7
  42. package/src/components/ex-chart/index.vue +136 -136
  43. package/src/components/ex-chart/theme/dark.js +4 -1
  44. package/src/components/ex-date-picker/index.js +7 -7
  45. package/src/components/ex-icon/index.js +7 -7
  46. package/src/components/ex-icon/index.vue +168 -168
  47. package/src/components/ex-icon/style/index.scss +7 -7
  48. package/src/components/ex-page/index.js +7 -7
  49. package/src/components/ex-select-tree/index.js +7 -7
  50. package/src/components/ex-table/ExTableCell.vue +22 -22
  51. package/src/components/ex-table/index.js +7 -7
  52. package/src/components/ex-table/virtualScrollData.js +35 -35
  53. package/src/components/ex-upload/index.js +7 -7
  54. package/src/components/index.scss +49 -40
  55. package/src/components/xt-badge/index.js +7 -0
  56. package/src/components/xt-badge/index.vue +71 -0
  57. package/src/components/xt-badge/style/index.scss +88 -0
  58. package/src/components/xt-button/index.js +7 -7
  59. package/src/components/xt-button/index.vue +97 -97
  60. package/src/components/xt-card/index.js +7 -7
  61. package/src/components/xt-card/style/index.scss +48 -48
  62. package/src/components/xt-card-item/index.js +7 -7
  63. package/src/components/xt-card-item/index.vue +5 -5
  64. package/src/components/xt-card-item/style/index copy.scss +71 -71
  65. package/src/components/xt-card-item/style/index.scss +82 -82
  66. package/src/components/xt-config-provider/index.js +7 -7
  67. package/src/components/xt-config-provider/index.vue +362 -362
  68. package/src/components/xt-config-provider/style/index.scss +11 -11
  69. package/src/components/xt-flex-box/index.js +7 -7
  70. package/src/components/xt-flex-box/style/index.scss +81 -81
  71. package/src/components/xt-grid-box/index.js +7 -7
  72. package/src/components/xt-grid-box/style/index.scss +92 -92
  73. package/src/components/xt-grid-item/index.js +7 -7
  74. package/src/components/xt-grid-item/index.vue +106 -106
  75. package/src/components/xt-input/index.js +7 -7
  76. package/src/components/xt-input/index.vue +66 -66
  77. package/src/components/xt-input/style/index.scss +84 -84
  78. package/src/components/xt-map/adapters/amap.js +235 -235
  79. package/src/components/xt-map/adapters/baidu.js +254 -254
  80. package/src/components/xt-map/adapters/base.js +267 -267
  81. package/src/components/xt-map/adapters/index.js +29 -29
  82. package/src/components/xt-map/adapters/tianditu.js +242 -242
  83. package/src/components/xt-map/config/xt-map-config.js +197 -197
  84. package/src/components/xt-map/index.js +8 -8
  85. package/src/components/xt-map/index.vue +351 -351
  86. package/src/components/xt-map/loaders/script-loader.js +114 -114
  87. package/src/components/xt-map/provider.vue +200 -200
  88. package/src/components/xt-map/style/index.scss +77 -77
  89. package/src/components/xt-map-provider/index.js +8 -8
  90. package/src/components/xt-progress/index.js +7 -0
  91. package/src/components/xt-progress/index.vue +103 -0
  92. package/src/components/xt-progress/style/index.scss +119 -0
  93. package/src/components/xt-step-price/index.js +10 -10
  94. package/src/components/xt-step-price/index.vue +272 -272
  95. package/src/components/xt-step-price/style/index.scss +123 -123
  96. package/src/components/xt-step-price-item/index.js +7 -7
  97. package/src/components/xt-step-price-item/index.vue +187 -187
  98. package/src/components/xt-tabs/TabPane.vue +36 -0
  99. package/src/components/xt-tabs/index.js +10 -0
  100. package/src/components/xt-tabs/index.vue +113 -0
  101. package/src/components/xt-tabs/style/index.scss +197 -0
  102. package/src/components/xt-text/index.js +7 -7
  103. package/src/components/xt-text/index.vue +16 -2
  104. package/src/components/xt-text/style/index.scss +110 -92
  105. package/src/components/xt-time/index.js +7 -7
  106. package/src/components/xt-time/index.vue +313 -313
  107. package/src/components/xt-time/style/index.scss +23 -23
  108. package/src/config/element-registry.js +135 -135
  109. package/src/index.js +169 -178
  110. package/src/styles/css-variables.scss +257 -257
  111. package/src/styles/theme/background.scss +5 -5
  112. package/src/styles/theme/border-radius.scss +3 -3
  113. package/src/styles/theme/borders.scss +3 -3
  114. package/src/styles/theme/colors.scss +101 -101
  115. package/src/styles/theme/component-variables.scss +69 -69
  116. package/src/styles/theme/dark-variables.scss +28 -28
  117. package/src/styles/theme/font.scss +12 -12
  118. package/src/styles/theme/index.scss +11 -11
  119. package/src/styles/theme/shadows.scss +2 -2
  120. package/src/styles/theme/spacing.scss +4 -4
  121. package/src/styles/theme/transitions.scss +2 -2
  122. package/src/styles/theme/typography.scss +4 -4
  123. package/src/styles/variables-export.scss +85 -85
  124. package/src/utils/index.js +191 -202
@@ -1,272 +1,272 @@
1
- <template>
2
- <div class="xt-step-price" :class="{ 'is-disabled': disabled }">
3
- <div v-if="title || $slots.header" class="xt-step-price__header">
4
- <xt-text v-if="title" bold size="medium">{{ title }}</xt-text>
5
- <slot name="header" />
6
- <xt-button
7
- v-if="!disabled && !isLimitReached"
8
- type="primary"
9
- size="small"
10
- icon="el-icon-plus"
11
- plain
12
- @click="onAdd"
13
- >新增档位</xt-button>
14
- <xt-text v-if="isLimitReached" size="small" type-color="info">已达上限({{ localItems.length }}/{{ limit }})</xt-text>
15
- </div>
16
-
17
- <div class="xt-step-price__list">
18
- <XtStepPriceItem
19
- v-for="(item, idx) in localItems"
20
- :key="idx"
21
- :value="item"
22
- :index="idx"
23
- :is-first="idx === 0"
24
- :is-last="idx === localItems.length - 1"
25
- :items-length="localItems.length"
26
- :removable="idx !== 0"
27
- :min-locked="idx !== 0 ? true : false"
28
- :unit="unit"
29
- :precision="precision"
30
- :step="step"
31
- :left-bracket="leftBracket"
32
- :right-bracket="rightBracket"
33
- :field-keys="fieldKeys"
34
- :disabled="disabled"
35
- @input="(val) => onItemInput(val, idx)"
36
- @max-change="onMaxChange"
37
- @min-change="onMinChange"
38
- @delete="onDelete"
39
- />
40
- </div>
41
-
42
- <div v-if="localItems.length === 0" class="xt-step-price__empty">
43
- <span>暂无数据,点击右上角「新增档位」开始配置</span>
44
- </div>
45
-
46
- <div v-if="tip || $slots.tip" class="xt-step-price__tip">
47
- <slot name="tip">
48
- <xt-text size="small" type-color="warning">{{ tip }}</xt-text>
49
- </slot>
50
- </div>
51
- </div>
52
- </template>
53
-
54
- <script>
55
- import XtStepPriceItem from '../xt-step-price-item/index.vue'
56
-
57
- export default {
58
- name: 'XtStepPrice',
59
-
60
- components: { XtStepPriceItem },
61
-
62
- computed: {
63
- keyMin() { return (this.fieldKeys && this.fieldKeys.min) || 'min' },
64
- keyMax() { return (this.fieldKeys && this.fieldKeys.max) || 'max' },
65
- keyPrice() { return (this.fieldKeys && this.fieldKeys.price) || 'price' },
66
- isLimitReached() {
67
- const lim = Number(this.limit)
68
- return lim > 0 && this.localItems.length >= lim
69
- }
70
- },
71
-
72
- props: {
73
- value: {
74
- type: Array,
75
- default: () => []
76
- },
77
- title: { type: String, default: '' },
78
- unit: { type: String, default: '元' },
79
- precision: { type: Number, default: 2 },
80
- // 左括号:默认 '[',传空字符串则不显示
81
- leftBracket: { type: String, default: '[' },
82
- // 右括号:默认 null,走内置逻辑(只有1条为 ']',多条为 ')');传具体值则强制使用
83
- rightBracket: { type: String, default: null },
84
- // 字段名映射:{ min, max, price },允许传入的 value 使用自定义字段名
85
- fieldKeys: {
86
- type: Object,
87
- default: () => ({ min: 'min', max: 'max', price: 'price' })
88
- },
89
- // 阶梯数量上限;<= 0 表示不限制
90
- limit: { type: Number, default: 0 },
91
- // 阶梯增量:新增/校正时,下一条阶梯的 min = 当前 max = 当前 min + step(默认 1)
92
- step: { type: Number, default: 10 },
93
- disabled: { type: Boolean, default: false },
94
- tip: {
95
- type: String,
96
- default: '区间左闭右闭 [min, max],最后一级为 [min, +∞),保证连续且不重叠。'
97
- },
98
- // 默认初始值:当传入空数组时,是否自动生成首条默认阶梯 [0, +∞)
99
- defaultFirst: { type: Boolean, default: true }
100
- },
101
-
102
- data() {
103
- return {
104
- localItems: this.normalize(this.value)
105
- }
106
- },
107
-
108
- watch: {
109
- value: {
110
- deep: true,
111
- immediate: true,
112
- handler(val) {
113
- this.localItems = this.normalize(val)
114
- }
115
- }
116
- },
117
-
118
- methods: {
119
- // 统一数字转换:空值/非法值一律转为 fallback(默认 0)
120
- safeNumber(v, fallback = 0) {
121
- if (v === null || v === undefined || v === '' || v === Infinity || v === -Infinity) return fallback
122
- const n = Number(v)
123
- return isNaN(n) ? fallback : n
124
- },
125
-
126
- cloneItems(items) {
127
- if (!Array.isArray(items)) return []
128
- return items.map((it) => ({
129
- [this.keyMin]: this.safeNumber(it && it[this.keyMin], 0),
130
- [this.keyMax]: (it && it[this.keyMax] == null) || (it && it[this.keyMax] === '') ? null : this.safeNumber(it[this.keyMax], null),
131
- [this.keyPrice]: this.safeNumber(it && it[this.keyPrice], 0)
132
- }))
133
- },
134
-
135
- normalize(items) {
136
- const list = this.cloneItems(items)
137
- if (list.length === 0 && this.defaultFirst) {
138
- list.push({ [this.keyMin]: 0, [this.keyMax]: null, [this.keyPrice]: 0 })
139
- }
140
- this.ensureContinuity(list)
141
- return list
142
- },
143
-
144
- // 保证 items 连续且不重叠:items[i].max === items[i+1].min,首条 min === 0,末条 max === null
145
- ensureContinuity(list) {
146
- if (!Array.isArray(list) || list.length === 0) return
147
- list[0][this.keyMin] = 0
148
- const stepVal = this.safeNumber(this.step, 1)
149
-
150
- for (let i = 0; i < list.length; i++) {
151
- const cur = list[i]
152
- const next = list[i + 1]
153
- const curMin = this.safeNumber(cur[this.keyMin], 0)
154
- if (next) {
155
- let curMax = this.safeNumber(cur[this.keyMax], curMin + stepVal)
156
- if (curMax <= curMin) curMax = curMin + stepVal
157
- cur[this.keyMax] = curMax
158
- next[this.keyMin] = curMax
159
- } else {
160
- cur[this.keyMax] = null
161
- }
162
- cur[this.keyPrice] = this.safeNumber(cur[this.keyPrice], 0)
163
- }
164
- },
165
-
166
- emit() {
167
- this.ensureContinuity(this.localItems)
168
- const cloned = this.cloneItems(this.localItems)
169
- this.$emit('input', cloned)
170
- this.$emit('change', cloned)
171
- },
172
-
173
- onItemInput(val, idx) {
174
- const cur = this.localItems[idx]
175
- if (!cur) return
176
- if (val && val[this.keyPrice] !== undefined) cur[this.keyPrice] = this.safeNumber(val[this.keyPrice], 0)
177
- },
178
-
179
- onMaxChange(val, idx) {
180
- const cur = this.localItems[idx]
181
- if (!cur) return
182
- const n = this.safeNumber(val, this.safeNumber(cur[this.keyMin], 0) + 1)
183
- cur[this.keyMax] = n
184
- const next = this.localItems[idx + 1]
185
- if (next) next[this.keyMin] = n
186
- this.emit()
187
- },
188
-
189
- onMinChange(val, idx) {
190
- const cur = this.localItems[idx]
191
- if (!cur) return
192
- if (idx === 0) {
193
- cur[this.keyMin] = 0
194
- } else {
195
- const n = this.safeNumber(val, this.safeNumber(cur[this.keyMin], 0))
196
- cur[this.keyMin] = n
197
- const prev = this.localItems[idx - 1]
198
- if (prev) prev[this.keyMax] = n
199
- }
200
- this.emit()
201
- },
202
-
203
- onAdd() {
204
- const list = this.localItems
205
- const lim = Number(this.limit)
206
- if (lim > 0 && list.length >= lim) return
207
- const stepVal = this.safeNumber(this.step, 1)
208
-
209
- // 场景 1:空数组 —— 直接 push 一条 [0, +∞)
210
- if (list.length === 0) {
211
- list.push({
212
- [this.keyMin]: 0,
213
- [this.keyMax]: null,
214
- [this.keyPrice]: 10
215
- })
216
- this.emit()
217
- return
218
- }
219
-
220
- // 场景 2:已有数据 —— 在末条前插入新条,新条的 max = 末条 min + step
221
- const last = list[list.length - 1]
222
- const newMin = this.safeNumber(last[this.keyMin], 0)
223
- const newMax = newMin + stepVal
224
-
225
- // 新条 price:优先继承「倒数第二条」的 price,其次用末条 price(>0 时),否则默认 10
226
- const prev = list[list.length - 2]
227
- let newPrice = 10
228
- if (prev) {
229
- newPrice = this.safeNumber(prev[this.keyPrice], 10)
230
- } else {
231
- const lastPrice = this.safeNumber(last[this.keyPrice], 0)
232
- newPrice = lastPrice > 0 ? lastPrice : 10
233
- }
234
-
235
- const newArr = [
236
- ...list.slice(0, -1),
237
- {
238
- [this.keyMin]: newMin,
239
- [this.keyMax]: newMax,
240
- [this.keyPrice]: newPrice
241
- },
242
- {
243
- [this.keyMin]: newMax,
244
- [this.keyMax]: null,
245
- [this.keyPrice]: newPrice
246
- }
247
- ]
248
-
249
- this.localItems = newArr
250
- this.emit()
251
- },
252
-
253
- onDelete(idx) {
254
- const list = this.localItems
255
- if (list.length <= 1) return // 至少保留一条
256
- if (idx === 0) return // 首条不允许删除,保证区间起点始终为 0
257
- const cur = list[idx]
258
- const prev = list[idx - 1]
259
- const next = list[idx + 1]
260
- if (prev && next) {
261
- prev[this.keyMax] = cur[this.keyMax] == null ? null : Number(cur[this.keyMax])
262
- } else if (prev && !next) {
263
- prev[this.keyMax] = null
264
- } else if (!prev && next) {
265
- next[this.keyMin] = 0
266
- }
267
- list.splice(idx, 1)
268
- this.emit()
269
- }
270
- }
271
- }
272
- </script>
1
+ <template>
2
+ <div class="xt-step-price" :class="{ 'is-disabled': disabled }">
3
+ <div v-if="title || $slots.header" class="xt-step-price__header">
4
+ <xt-text v-if="title" bold size="medium">{{ title }}</xt-text>
5
+ <slot name="header" />
6
+ <xt-button
7
+ v-if="!disabled && !isLimitReached"
8
+ type="primary"
9
+ size="small"
10
+ icon="el-icon-plus"
11
+ plain
12
+ @click="onAdd"
13
+ >新增档位</xt-button>
14
+ <xt-text v-if="isLimitReached" size="small" type-color="info">已达上限({{ localItems.length }}/{{ limit }})</xt-text>
15
+ </div>
16
+
17
+ <div class="xt-step-price__list">
18
+ <XtStepPriceItem
19
+ v-for="(item, idx) in localItems"
20
+ :key="idx"
21
+ :value="item"
22
+ :index="idx"
23
+ :is-first="idx === 0"
24
+ :is-last="idx === localItems.length - 1"
25
+ :items-length="localItems.length"
26
+ :removable="idx !== 0"
27
+ :min-locked="idx !== 0 ? true : false"
28
+ :unit="unit"
29
+ :precision="precision"
30
+ :step="step"
31
+ :left-bracket="leftBracket"
32
+ :right-bracket="rightBracket"
33
+ :field-keys="fieldKeys"
34
+ :disabled="disabled"
35
+ @input="(val) => onItemInput(val, idx)"
36
+ @max-change="onMaxChange"
37
+ @min-change="onMinChange"
38
+ @delete="onDelete"
39
+ />
40
+ </div>
41
+
42
+ <div v-if="localItems.length === 0" class="xt-step-price__empty">
43
+ <span>暂无数据,点击右上角「新增档位」开始配置</span>
44
+ </div>
45
+
46
+ <div v-if="tip || $slots.tip" class="xt-step-price__tip">
47
+ <slot name="tip">
48
+ <xt-text size="small" type-color="warning">{{ tip }}</xt-text>
49
+ </slot>
50
+ </div>
51
+ </div>
52
+ </template>
53
+
54
+ <script>
55
+ import XtStepPriceItem from '../xt-step-price-item/index.vue'
56
+
57
+ export default {
58
+ name: 'XtStepPrice',
59
+
60
+ components: { XtStepPriceItem },
61
+
62
+ computed: {
63
+ keyMin() { return (this.fieldKeys && this.fieldKeys.min) || 'min' },
64
+ keyMax() { return (this.fieldKeys && this.fieldKeys.max) || 'max' },
65
+ keyPrice() { return (this.fieldKeys && this.fieldKeys.price) || 'price' },
66
+ isLimitReached() {
67
+ const lim = Number(this.limit)
68
+ return lim > 0 && this.localItems.length >= lim
69
+ }
70
+ },
71
+
72
+ props: {
73
+ value: {
74
+ type: Array,
75
+ default: () => []
76
+ },
77
+ title: { type: String, default: '' },
78
+ unit: { type: String, default: '元' },
79
+ precision: { type: Number, default: 2 },
80
+ // 左括号:默认 '[',传空字符串则不显示
81
+ leftBracket: { type: String, default: '[' },
82
+ // 右括号:默认 null,走内置逻辑(只有1条为 ']',多条为 ')');传具体值则强制使用
83
+ rightBracket: { type: String, default: null },
84
+ // 字段名映射:{ min, max, price },允许传入的 value 使用自定义字段名
85
+ fieldKeys: {
86
+ type: Object,
87
+ default: () => ({ min: 'min', max: 'max', price: 'price' })
88
+ },
89
+ // 阶梯数量上限;<= 0 表示不限制
90
+ limit: { type: Number, default: 0 },
91
+ // 阶梯增量:新增/校正时,下一条阶梯的 min = 当前 max = 当前 min + step(默认 1)
92
+ step: { type: Number, default: 10 },
93
+ disabled: { type: Boolean, default: false },
94
+ tip: {
95
+ type: String,
96
+ default: '区间左闭右闭 [min, max],最后一级为 [min, +∞),保证连续且不重叠。'
97
+ },
98
+ // 默认初始值:当传入空数组时,是否自动生成首条默认阶梯 [0, +∞)
99
+ defaultFirst: { type: Boolean, default: true }
100
+ },
101
+
102
+ data() {
103
+ return {
104
+ localItems: this.normalize(this.value)
105
+ }
106
+ },
107
+
108
+ watch: {
109
+ value: {
110
+ deep: true,
111
+ immediate: true,
112
+ handler(val) {
113
+ this.localItems = this.normalize(val)
114
+ }
115
+ }
116
+ },
117
+
118
+ methods: {
119
+ // 统一数字转换:空值/非法值一律转为 fallback(默认 0)
120
+ safeNumber(v, fallback = 0) {
121
+ if (v === null || v === undefined || v === '' || v === Infinity || v === -Infinity) return fallback
122
+ const n = Number(v)
123
+ return isNaN(n) ? fallback : n
124
+ },
125
+
126
+ cloneItems(items) {
127
+ if (!Array.isArray(items)) return []
128
+ return items.map((it) => ({
129
+ [this.keyMin]: this.safeNumber(it && it[this.keyMin], 0),
130
+ [this.keyMax]: (it && it[this.keyMax] == null) || (it && it[this.keyMax] === '') ? null : this.safeNumber(it[this.keyMax], null),
131
+ [this.keyPrice]: this.safeNumber(it && it[this.keyPrice], 0)
132
+ }))
133
+ },
134
+
135
+ normalize(items) {
136
+ const list = this.cloneItems(items)
137
+ if (list.length === 0 && this.defaultFirst) {
138
+ list.push({ [this.keyMin]: 0, [this.keyMax]: null, [this.keyPrice]: 0 })
139
+ }
140
+ this.ensureContinuity(list)
141
+ return list
142
+ },
143
+
144
+ // 保证 items 连续且不重叠:items[i].max === items[i+1].min,首条 min === 0,末条 max === null
145
+ ensureContinuity(list) {
146
+ if (!Array.isArray(list) || list.length === 0) return
147
+ list[0][this.keyMin] = 0
148
+ const stepVal = this.safeNumber(this.step, 1)
149
+
150
+ for (let i = 0; i < list.length; i++) {
151
+ const cur = list[i]
152
+ const next = list[i + 1]
153
+ const curMin = this.safeNumber(cur[this.keyMin], 0)
154
+ if (next) {
155
+ let curMax = this.safeNumber(cur[this.keyMax], curMin + stepVal)
156
+ if (curMax <= curMin) curMax = curMin + stepVal
157
+ cur[this.keyMax] = curMax
158
+ next[this.keyMin] = curMax
159
+ } else {
160
+ cur[this.keyMax] = null
161
+ }
162
+ cur[this.keyPrice] = this.safeNumber(cur[this.keyPrice], 0)
163
+ }
164
+ },
165
+
166
+ emit() {
167
+ this.ensureContinuity(this.localItems)
168
+ const cloned = this.cloneItems(this.localItems)
169
+ this.$emit('input', cloned)
170
+ this.$emit('change', cloned)
171
+ },
172
+
173
+ onItemInput(val, idx) {
174
+ const cur = this.localItems[idx]
175
+ if (!cur) return
176
+ if (val && val[this.keyPrice] !== undefined) cur[this.keyPrice] = this.safeNumber(val[this.keyPrice], 0)
177
+ },
178
+
179
+ onMaxChange(val, idx) {
180
+ const cur = this.localItems[idx]
181
+ if (!cur) return
182
+ const n = this.safeNumber(val, this.safeNumber(cur[this.keyMin], 0) + 1)
183
+ cur[this.keyMax] = n
184
+ const next = this.localItems[idx + 1]
185
+ if (next) next[this.keyMin] = n
186
+ this.emit()
187
+ },
188
+
189
+ onMinChange(val, idx) {
190
+ const cur = this.localItems[idx]
191
+ if (!cur) return
192
+ if (idx === 0) {
193
+ cur[this.keyMin] = 0
194
+ } else {
195
+ const n = this.safeNumber(val, this.safeNumber(cur[this.keyMin], 0))
196
+ cur[this.keyMin] = n
197
+ const prev = this.localItems[idx - 1]
198
+ if (prev) prev[this.keyMax] = n
199
+ }
200
+ this.emit()
201
+ },
202
+
203
+ onAdd() {
204
+ const list = this.localItems
205
+ const lim = Number(this.limit)
206
+ if (lim > 0 && list.length >= lim) return
207
+ const stepVal = this.safeNumber(this.step, 1)
208
+
209
+ // 场景 1:空数组 —— 直接 push 一条 [0, +∞)
210
+ if (list.length === 0) {
211
+ list.push({
212
+ [this.keyMin]: 0,
213
+ [this.keyMax]: null,
214
+ [this.keyPrice]: 10
215
+ })
216
+ this.emit()
217
+ return
218
+ }
219
+
220
+ // 场景 2:已有数据 —— 在末条前插入新条,新条的 max = 末条 min + step
221
+ const last = list[list.length - 1]
222
+ const newMin = this.safeNumber(last[this.keyMin], 0)
223
+ const newMax = newMin + stepVal
224
+
225
+ // 新条 price:优先继承「倒数第二条」的 price,其次用末条 price(>0 时),否则默认 10
226
+ const prev = list[list.length - 2]
227
+ let newPrice = 10
228
+ if (prev) {
229
+ newPrice = this.safeNumber(prev[this.keyPrice], 10)
230
+ } else {
231
+ const lastPrice = this.safeNumber(last[this.keyPrice], 0)
232
+ newPrice = lastPrice > 0 ? lastPrice : 10
233
+ }
234
+
235
+ const newArr = [
236
+ ...list.slice(0, -1),
237
+ {
238
+ [this.keyMin]: newMin,
239
+ [this.keyMax]: newMax,
240
+ [this.keyPrice]: newPrice
241
+ },
242
+ {
243
+ [this.keyMin]: newMax,
244
+ [this.keyMax]: null,
245
+ [this.keyPrice]: newPrice
246
+ }
247
+ ]
248
+
249
+ this.localItems = newArr
250
+ this.emit()
251
+ },
252
+
253
+ onDelete(idx) {
254
+ const list = this.localItems
255
+ if (list.length <= 1) return // 至少保留一条
256
+ if (idx === 0) return // 首条不允许删除,保证区间起点始终为 0
257
+ const cur = list[idx]
258
+ const prev = list[idx - 1]
259
+ const next = list[idx + 1]
260
+ if (prev && next) {
261
+ prev[this.keyMax] = cur[this.keyMax] == null ? null : Number(cur[this.keyMax])
262
+ } else if (prev && !next) {
263
+ prev[this.keyMax] = null
264
+ } else if (!prev && next) {
265
+ next[this.keyMin] = 0
266
+ }
267
+ list.splice(idx, 1)
268
+ this.emit()
269
+ }
270
+ }
271
+ }
272
+ </script>