xt-element-ui 2.0.0 → 2.0.2

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,6 +1,6 @@
1
1
  {
2
2
  "name": "xt-element-ui",
3
- "version": "2.0.0",
3
+ "version": "2.0.2",
4
4
  "description": "基于 Vue 2.7 + ElementUI 的企业级组件库,提供丰富的自定义组件和扩展组件",
5
5
  "main": "lib/index.common.js",
6
6
  "module": "lib/index.esm.js",
@@ -1,8 +1,8 @@
1
- import XtButton from './index.vue'
2
-
3
- XtButton.install = function (Vue) {
4
- Vue.component(XtButton.name, XtButton)
5
- }
6
-
7
- export default XtButton
1
+ import XtButton from './index.vue'
2
+
3
+ XtButton.install = function (Vue) {
4
+ Vue.component(XtButton.name, XtButton)
5
+ }
6
+
7
+ export default XtButton
8
8
  export { XtButton }
@@ -1,7 +1,7 @@
1
- import XtCard from './index.vue'
2
-
3
- XtCard.install = function (Vue) {
4
- Vue.component(XtCard.name, XtCard)
5
- }
6
-
1
+ import XtCard from './index.vue'
2
+
3
+ XtCard.install = function (Vue) {
4
+ Vue.component(XtCard.name, XtCard)
5
+ }
6
+
7
7
  export default XtCard
@@ -1,8 +1,8 @@
1
- import XtChart from './index.vue'
2
-
3
- XtChart.install = function (Vue) {
4
- Vue.component(XtChart.name, XtChart)
5
- }
6
-
7
- export default XtChart
1
+ import XtChart from './index.vue'
2
+
3
+ XtChart.install = function (Vue) {
4
+ Vue.component(XtChart.name, XtChart)
5
+ }
6
+
7
+ export default XtChart
8
8
  export { XtChart }
@@ -1,8 +1,8 @@
1
- import XtDatePicker from './index.vue'
2
-
3
- XtDatePicker.install = function (Vue) {
4
- Vue.component(XtDatePicker.name, XtDatePicker)
5
- }
6
-
7
- export default XtDatePicker
1
+ import XtDatePicker from './index.vue'
2
+
3
+ XtDatePicker.install = function (Vue) {
4
+ Vue.component(XtDatePicker.name, XtDatePicker)
5
+ }
6
+
7
+ export default XtDatePicker
8
8
  export { XtDatePicker }
@@ -1,8 +1,8 @@
1
- import XtIcon from './index.vue'
2
-
3
- XtIcon.install = function (Vue) {
4
- Vue.component(XtIcon.name, XtIcon)
5
- }
6
-
7
- export default XtIcon
1
+ import XtIcon from './index.vue'
2
+
3
+ XtIcon.install = function (Vue) {
4
+ Vue.component(XtIcon.name, XtIcon)
5
+ }
6
+
7
+ export default XtIcon
8
8
  export { XtIcon }
@@ -7,17 +7,17 @@
7
7
  ]"
8
8
  >
9
9
  <el-input
10
- :value="value"
10
+ :value="displayValue"
11
11
  :placeholder="placeholder"
12
- :type="type"
12
+ :type="isNumberType ? 'text' : type"
13
13
  :size="size"
14
14
  :disabled="disabled"
15
15
  :readonly="readonly"
16
16
  :style="inputStyle"
17
- @input="$emit('input', $event)"
18
- @change="$emit('change', $event)"
19
- @focus="$emit('focus', $event)"
20
- @blur="$emit('blur', $event)"
17
+ @input="handleInput"
18
+ @change="handleChange"
19
+ @focus="handleFocus"
20
+ @blur="handleBlur"
21
21
  />
22
22
  </div>
23
23
  </template>
@@ -52,7 +52,22 @@ export default {
52
52
  default: ''
53
53
  }
54
54
  },
55
+ data() {
56
+ return {
57
+ currentStr: '',
58
+ isFocused: false
59
+ }
60
+ },
55
61
  computed: {
62
+ isNumberType() {
63
+ return this.type === 'number'
64
+ },
65
+ displayValue() {
66
+ if (this.isNumberType) {
67
+ return this.currentStr
68
+ }
69
+ return this.value
70
+ },
56
71
  inputStyle() {
57
72
  if (this.color) {
58
73
  return {
@@ -61,6 +76,71 @@ export default {
61
76
  }
62
77
  return {}
63
78
  }
79
+ },
80
+ watch: {
81
+ value: {
82
+ immediate: true,
83
+ handler(val) {
84
+ if (this.isNumberType && !this.isFocused) {
85
+ this.currentStr = val === null || val === undefined || val === '' ? '' : String(val)
86
+ }
87
+ }
88
+ }
89
+ },
90
+ methods: {
91
+ isValidNumber(str) {
92
+ return /^[+-]?\d*\.?\d*$/.test(str)
93
+ },
94
+ handleInput(val) {
95
+ if (this.isNumberType) {
96
+ if (this.isValidNumber(val)) {
97
+ this.currentStr = val
98
+ const num = this.parseToNumber(val)
99
+ if (num !== undefined) {
100
+ this.$emit('input', num)
101
+ }
102
+ }
103
+ } else {
104
+ this.$emit('input', val)
105
+ }
106
+ },
107
+ handleChange(val) {
108
+ if (this.isNumberType) {
109
+ const num = this.parseToNumber(val)
110
+ this.$emit('change', num)
111
+ } else {
112
+ this.$emit('change', val)
113
+ }
114
+ },
115
+ handleFocus(e) {
116
+ this.isFocused = true
117
+ this.$emit('focus', e)
118
+ },
119
+ handleBlur(e) {
120
+ this.isFocused = false
121
+ if (this.isNumberType) {
122
+ const num = this.parseToNumber(this.currentStr)
123
+ this.$emit('blur', num, e)
124
+ if (num !== undefined) {
125
+ this.currentStr = String(num)
126
+ this.$emit('input', num)
127
+ } else {
128
+ if (this.currentStr !== '') {
129
+ this.currentStr = ''
130
+ this.$emit('input', undefined)
131
+ }
132
+ }
133
+ } else {
134
+ this.$emit('blur', e)
135
+ }
136
+ },
137
+ parseToNumber(str) {
138
+ if (!str || str === '+' || str === '-' || str === '.' || str === '+.' || str === '-.' || str === '-.') {
139
+ return undefined
140
+ }
141
+ const num = parseFloat(str)
142
+ return isNaN(num) ? undefined : num
143
+ }
64
144
  }
65
145
  }
66
146
  </script>
@@ -1,8 +1,8 @@
1
- import XtPage from './index.vue'
2
-
3
- XtPage.install = function (Vue) {
4
- Vue.component(XtPage.name, XtPage)
5
- }
6
-
7
- export default XtPage
1
+ import XtPage from './index.vue'
2
+
3
+ XtPage.install = function (Vue) {
4
+ Vue.component(XtPage.name, XtPage)
5
+ }
6
+
7
+ export default XtPage
8
8
  export { XtPage }
@@ -1,8 +1,8 @@
1
- import XtSelectTree from './index.vue'
2
-
3
- XtSelectTree.install = function (Vue) {
4
- Vue.component(XtSelectTree.name, XtSelectTree)
5
- }
6
-
7
- export default XtSelectTree
1
+ import XtSelectTree from './index.vue'
2
+
3
+ XtSelectTree.install = function (Vue) {
4
+ Vue.component(XtSelectTree.name, XtSelectTree)
5
+ }
6
+
7
+ export default XtSelectTree
8
8
  export { XtSelectTree }
@@ -10,7 +10,7 @@
10
10
  icon="el-icon-plus"
11
11
  plain
12
12
  @click="onAdd"
13
- >新增档位</xt-button>
13
+ >新增{{stepName}}</xt-button>
14
14
  <xt-text v-if="isLimitReached" size="small" type-color="info">已达上限({{ localItems.length }}/{{ limit }})</xt-text>
15
15
  </div>
16
16
 
@@ -27,6 +27,7 @@
27
27
  :min-locked="idx !== 0 ? true : false"
28
28
  :unit="unit"
29
29
  :precision="precision"
30
+ :step-name="stepName"
30
31
  :step="step"
31
32
  :left-bracket="leftBracket"
32
33
  :right-bracket="rightBracket"
@@ -36,11 +37,12 @@
36
37
  @max-change="onMaxChange"
37
38
  @min-change="onMinChange"
38
39
  @delete="onDelete"
40
+ @blur="onFieldBlur"
39
41
  />
40
42
  </div>
41
43
 
42
44
  <div v-if="localItems.length === 0" class="xt-step-price__empty">
43
- <span>暂无数据,点击右上角「新增档位」开始配置</span>
45
+ <span>暂无数据,点击右上角「新增{{stepName}}」开始配置</span>
44
46
  </div>
45
47
 
46
48
  <div v-if="tip || $slots.tip" class="xt-step-price__tip">
@@ -59,6 +61,12 @@ export default {
59
61
 
60
62
  components: { XtStepPriceItem },
61
63
 
64
+ inject: {
65
+ elFormItem: {
66
+ default: ''
67
+ }
68
+ },
69
+
62
70
  computed: {
63
71
  keyMin() { return (this.fieldKeys && this.fieldKeys.min) || 'min' },
64
72
  keyMax() { return (this.fieldKeys && this.fieldKeys.max) || 'max' },
@@ -76,6 +84,7 @@ export default {
76
84
  },
77
85
  title: { type: String, default: '' },
78
86
  unit: { type: String, default: '元' },
87
+ stepName: { type: String, default: '阶梯' },
79
88
  precision: { type: Number, default: 2 },
80
89
  // 左括号:默认 '[',传空字符串则不显示
81
90
  leftBracket: { type: String, default: '[' },
@@ -125,11 +134,14 @@ export default {
125
134
 
126
135
  cloneItems(items) {
127
136
  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
- }))
137
+ return items.map((it) => {
138
+ const price = (it && it[this.keyPrice])
139
+ return {
140
+ [this.keyMin]: this.safeNumber(it && it[this.keyMin], 0),
141
+ [this.keyMax]: (it && it[this.keyMax] == null) || (it && it[this.keyMax] === '') ? null : this.safeNumber(it[this.keyMax], null),
142
+ [this.keyPrice]: (price === null || price === undefined || price === '') ? '' : this.safeNumber(price, 0)
143
+ }
144
+ })
133
145
  },
134
146
 
135
147
  normalize(items) {
@@ -154,12 +166,14 @@ export default {
154
166
  if (next) {
155
167
  let curMax = this.safeNumber(cur[this.keyMax], curMin + stepVal)
156
168
  if (curMax <= curMin) curMax = curMin + stepVal
169
+ curMax = Number(curMax.toFixed(this.precision))
157
170
  cur[this.keyMax] = curMax
158
171
  next[this.keyMin] = curMax
159
172
  } else {
160
173
  cur[this.keyMax] = null
161
174
  }
162
- cur[this.keyPrice] = this.safeNumber(cur[this.keyPrice], 0)
175
+ const price = cur[this.keyPrice]
176
+ cur[this.keyPrice] = (price === null || price === undefined || price === '') ? '' : this.safeNumber(price, 0)
163
177
  }
164
178
  },
165
179
 
@@ -168,18 +182,127 @@ export default {
168
182
  const cloned = this.cloneItems(this.localItems)
169
183
  this.$emit('input', cloned)
170
184
  this.$emit('change', cloned)
185
+ this.dispatchFormEvent('el.form.change', cloned)
186
+ },
187
+
188
+ dispatchFormEvent(eventName, value) {
189
+ if (this.elFormItem) {
190
+ this.elFormItem.$emit(eventName, value)
191
+ }
192
+ },
193
+
194
+ onFieldBlur() {
195
+ this.dispatchFormEvent('el.form.blur', this.localItems)
196
+ },
197
+
198
+ validate(callback) {
199
+ const list = this.localItems
200
+ if (!Array.isArray(list) || list.length === 0) {
201
+ callback && callback(false, [{ field: 'stepPrice', message: `请配置${this.stepName}`, type: 'error' }])
202
+ return false
203
+ }
204
+
205
+ const errors = []
206
+ for (let i = 0; i < list.length; i++) {
207
+ const item = list[i]
208
+ const rawPrice = item[this.keyPrice]
209
+ const rawMin = item[this.keyMin]
210
+ const rawMax = item[this.keyMax]
211
+ const price = this.safeNumber(rawPrice, 0)
212
+ const min = this.safeNumber(rawMin, 0)
213
+
214
+ if (rawPrice === null || rawPrice === undefined || rawPrice === '') {
215
+ errors.push({
216
+ field: `stepPrice[${i}].price`,
217
+ message: `第${i + 1}${this.stepName}价格不能为空`,
218
+ type: 'error'
219
+ })
220
+ } else if (price < 0) {
221
+ errors.push({
222
+ field: `stepPrice[${i}].price`,
223
+ message: `第${i + 1}${this.stepName}价格不能为负数`,
224
+ type: 'error'
225
+ })
226
+ }
227
+
228
+ if (rawMin === null || rawMin === undefined || rawMin === '') {
229
+ errors.push({
230
+ field: `stepPrice[${i}].min`,
231
+ message: `第${i + 1}${this.stepName}下限不能为空`,
232
+ type: 'error'
233
+ })
234
+ } else if (min < 0) {
235
+ errors.push({
236
+ field: `stepPrice[${i}].min`,
237
+ message: `第${i + 1}${this.stepName}下限不能为负数`,
238
+ type: 'error'
239
+ })
240
+ }
241
+
242
+ if (!this.isLast(i)) {
243
+ if (rawMax === null || rawMax === undefined || rawMax === '') {
244
+ errors.push({
245
+ field: `stepPrice[${i}].max`,
246
+ message: `第${i + 1}${this.stepName}上限不能为空`,
247
+ type: 'error'
248
+ })
249
+ } else if (this.safeNumber(rawMax, 0) <= min) {
250
+ errors.push({
251
+ field: `stepPrice[${i}].max`,
252
+ message: `第${i + 1}${this.stepName}上限必须大于下限`,
253
+ type: 'error'
254
+ })
255
+ }
256
+ }
257
+
258
+ if (i > 0) {
259
+ const prev = list[i - 1]
260
+ const prevMax = prev[this.keyMax]
261
+ if (prevMax !== null && this.safeNumber(prevMax, 0) !== min) {
262
+ errors.push({
263
+ field: `stepPrice[${i}].min`,
264
+ message: `第${i + 1}${this.stepName}下限必须等于上一${this.stepName}上限`,
265
+ type: 'error'
266
+ })
267
+ }
268
+ }
269
+ }
270
+
271
+ if (errors.length > 0) {
272
+ callback && callback(false, errors)
273
+ return false
274
+ }
275
+
276
+ callback && callback(true)
277
+ return true
278
+ },
279
+
280
+ isLast(idx) {
281
+ return idx === this.localItems.length - 1
171
282
  },
172
283
 
173
284
  onItemInput(val, idx) {
174
285
  const cur = this.localItems[idx]
175
286
  if (!cur) return
176
- if (val && val[this.keyPrice] !== undefined) cur[this.keyPrice] = this.safeNumber(val[this.keyPrice], 0)
287
+ if (val && val[this.keyPrice] !== undefined) {
288
+ const p = val[this.keyPrice]
289
+ if (p === null || p === undefined || p === '') {
290
+ cur[this.keyPrice] = ''
291
+ } else {
292
+ let num = this.safeNumber(p, 0)
293
+ num = Number(num.toFixed(this.precision))
294
+ cur[this.keyPrice] = num
295
+ }
296
+ this.emit()
297
+ }
177
298
  },
178
299
 
179
300
  onMaxChange(val, idx) {
180
301
  const cur = this.localItems[idx]
181
302
  if (!cur) return
182
- const n = this.safeNumber(val, this.safeNumber(cur[this.keyMin], 0) + 1)
303
+ const stepVal = this.safeNumber(this.step, 1)
304
+ let n = this.safeNumber(val, this.safeNumber(cur[this.keyMin], 0) + stepVal)
305
+ n = Number(n.toFixed(this.precision))
183
306
  cur[this.keyMax] = n
184
307
  const next = this.localItems[idx + 1]
185
308
  if (next) next[this.keyMin] = n
@@ -192,7 +315,10 @@ export default {
192
315
  if (idx === 0) {
193
316
  cur[this.keyMin] = 0
194
317
  } else {
195
- const n = this.safeNumber(val, this.safeNumber(cur[this.keyMin], 0))
318
+ let n = this.safeNumber(val, this.safeNumber(cur[this.keyMin], 0))
319
+ if (n !== 0) {
320
+ n = Number(n.toFixed(this.precision))
321
+ }
196
322
  cur[this.keyMin] = n
197
323
  const prev = this.localItems[idx - 1]
198
324
  if (prev) prev[this.keyMax] = n
@@ -222,15 +348,8 @@ export default {
222
348
  const newMin = this.safeNumber(last[this.keyMin], 0)
223
349
  const newMax = newMin + stepVal
224
350
 
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
- }
351
+ // 新条 price:使用默认值,由用户自行填写
352
+ let newPrice = 0
234
353
 
235
354
  const newArr = [
236
355
  ...list.slice(0, -1),
@@ -2,7 +2,7 @@
2
2
  <div class="xt-step-price-item">
3
3
  <div class="xt-step-price-item__range">
4
4
  <span class="xt-step-price-item__bracket">{{ finalLeftBracket }}</span>
5
- <span v-if="itemsLength > 1" class="xt-step-price-item__name">第{{ index + 1 }}档</span>
5
+ <span v-if="itemsLength > 1" class="xt-step-price-item__name">第{{ index + 1 }}{{stepName}}</span>
6
6
  <span class="xt-step-price-item__bracket">{{ finalRightBracket }}</span>
7
7
  <xt-input
8
8
  v-model.number="minInput"
@@ -10,17 +10,18 @@
10
10
  size="small"
11
11
  placeholder="下限"
12
12
  class="xt-step-price-item__input"
13
- @blur="onMinBlur"
13
+ @blur="(e) => { onMinBlur(); onBlur(e) }"
14
14
  />
15
15
  <span class="xt-step-price-item__comma">-</span>
16
16
  <xt-input
17
17
  v-if="!isLast"
18
18
  v-model.number="maxInput"
19
+ type="number"
19
20
  :disabled="disabled"
20
21
  size="small"
21
22
  placeholder="上限"
22
23
  class="xt-step-price-item__input"
23
- @blur="onMaxBlur"
24
+ @blur="(e) => { onMaxBlur(); onBlur(e) }"
24
25
  />
25
26
  <span v-else class="xt-step-price-item__infinity">+∞</span>
26
27
  </div>
@@ -29,11 +30,12 @@
29
30
  <div class="xt-step-price-item__price">
30
31
  <xt-input
31
32
  v-model.number="priceInput"
33
+ type="number"
32
34
  :disabled="disabled"
33
35
  size="small"
34
36
  placeholder="价格"
35
37
  class="xt-step-price-item__input xt-step-price-item__input--price"
36
- @blur="onPriceBlur"
38
+ @blur="(e) => { onPriceBlur(); onBlur(e) }"
37
39
  />
38
40
  <span class="xt-step-price-item__unit">{{ unit }}</span>
39
41
  </div>
@@ -58,6 +60,7 @@ export default {
58
60
  required: true,
59
61
  default: () => ({ min: 0, max: null, price: 0 })
60
62
  },
63
+ stepName: { type: String, default: '阶梯' },
61
64
  index: { type: Number, default: 0 },
62
65
  isFirst: { type: Boolean, default: false },
63
66
  isLast: { type: Boolean, default: false },
@@ -155,7 +158,10 @@ export default {
155
158
  },
156
159
 
157
160
  onMinBlur() {
158
- const v = this.safeNumber(this.minInput, 0)
161
+ let v = this.safeNumber(this.minInput, 0)
162
+ if (v !== 0) {
163
+ v = Number(v.toFixed(this.precision))
164
+ }
159
165
  this.minInput = v
160
166
  this.$emit('min-change', v, this.index)
161
167
  this.emitChange({ [this.keyMin]: v })
@@ -164,21 +170,33 @@ export default {
164
170
  onMaxBlur() {
165
171
  if (this.isLast) return
166
172
  const minVal = this.safeNumber(this.minInput, 0)
167
- let v = this.safeNumber(this.maxInput, minVal + 1)
168
- if (v <= minVal) v = minVal + 1
173
+ const stepVal = this.safeNumber(this.step, 1)
174
+ let v = this.safeNumber(this.maxInput, minVal + stepVal)
175
+ if (v <= minVal) v = minVal + stepVal
176
+ v = Number(v.toFixed(this.precision))
169
177
  this.maxInput = v
170
178
  this.$emit('max-change', v, this.index)
171
179
  this.emitChange({ [this.keyMax]: v })
172
180
  },
173
181
 
174
182
  onPriceBlur() {
175
- let v = this.safeNumber(this.priceInput, 0)
183
+ const rawVal = this.priceInput
184
+ if (rawVal === null || rawVal === undefined || rawVal === '' || isNaN(rawVal)) {
185
+ this.priceInput = ''
186
+ this.emitChange({ [this.keyPrice]: '' })
187
+ return
188
+ }
189
+ let v = this.safeNumber(rawVal, 0)
176
190
  if (v < 0) v = 0
177
191
  v = Number(v.toFixed(this.precision))
178
192
  this.priceInput = v
179
193
  this.emitChange({ [this.keyPrice]: v })
180
194
  },
181
195
 
196
+ onBlur(e) {
197
+ this.$emit('blur', e)
198
+ },
199
+
182
200
  onDelete() {
183
201
  this.$emit('delete', this.index)
184
202
  }
@@ -1,8 +1,8 @@
1
- import XtTable from './index.vue'
2
-
3
- XtTable.install = function (Vue) {
4
- Vue.component(XtTable.name, XtTable)
5
- }
6
-
7
- export default XtTable
1
+ import XtTable from './index.vue'
2
+
3
+ XtTable.install = function (Vue) {
4
+ Vue.component(XtTable.name, XtTable)
5
+ }
6
+
7
+ export default XtTable
8
8
  export { XtTable }