xt-element-ui 2.1.4 → 2.1.6

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 (33) hide show
  1. package/docs/components/base/xt-date-picker.md +127 -37
  2. package/docs/components/base/xt-form-schema.md +358 -0
  3. package/docs/components/base/xt-grid-box.md +4 -4
  4. package/docs/components/base/xt-input.md +204 -42
  5. package/docs/components/base/xt-list.md +458 -458
  6. package/docs/components/base/xt-text.md +1 -0
  7. package/docs/components/base/xt-transfer-tree.md +272 -0
  8. package/docs/components/utils/config.md +285 -0
  9. package/docs/components/utils/format.md +445 -0
  10. package/lib/index.common.js +93128 -112769
  11. package/lib/index.css +1 -1
  12. package/lib/index.umd.js +93128 -112769
  13. package/lib/index.umd.min.js +1 -34
  14. package/package.json +4 -2
  15. package/src/components/xt-date-picker/component/Picker.vue +196 -0
  16. package/src/components/xt-date-picker/component/RangeDate.vue +136 -0
  17. package/src/components/xt-date-picker/index.vue +164 -144
  18. package/src/components/xt-flex-box/index.vue +1 -1
  19. package/src/components/xt-form-schema/index.js +8 -0
  20. package/src/components/xt-form-schema/index.vue +328 -0
  21. package/src/components/xt-grid-item/index.vue +2 -2
  22. package/src/components/xt-input/index.vue +224 -28
  23. package/src/components/xt-input/style/index.scss +10 -0
  24. package/src/components/xt-list/index.js +7 -7
  25. package/src/components/xt-list/index.vue +885 -885
  26. package/src/components/xt-text/index.vue +16 -4
  27. package/src/components/xt-transfer-tree/index.js +8 -0
  28. package/src/components/xt-transfer-tree/index.vue +494 -0
  29. package/src/index.js +8 -2
  30. package/src/utils/index.js +278 -1
  31. package/src/components/xt-date-picker/SearchDate.vue +0 -45
  32. package/src/components/xt-date-picker/quarter.vue +0 -154
  33. package/src/components/xt-table/index copy.vue +0 -663
@@ -0,0 +1,328 @@
1
+ <template>
2
+ <div class="xt-form-schema">
3
+ <div v-if="showSearchTags" class="search-tags">
4
+ <span class="tags-title">已检索:</span>
5
+ <el-tag
6
+ v-for="(tag, key) in activeSearchTags"
7
+ :key="key"
8
+ closable
9
+ size="small"
10
+ @close="handleRemoveTag(key)"
11
+ >
12
+ {{ tag.label }}: {{ tag.value }}
13
+ </el-tag>
14
+ <span v-if="activeSearchTagsCount === 0" class="no-tags">暂无检索条件</span>
15
+ </div>
16
+
17
+ <el-dialog
18
+ v-if="mode === 'dialog'"
19
+ :title="title"
20
+ :visible.sync="visible"
21
+ :width="width"
22
+ :before-close="handleCancel"
23
+ class="xt-form-schema-dialog"
24
+ append-to-body
25
+ modal-append-to-body
26
+ >
27
+ <el-form :model="formData" :inline="false" label-width="120px">
28
+ <el-form-item :label="field.label" :prop="field.prop" :key="field.prop" v-for="field in schema">
29
+ <component v-if="!field.isSlot" :is="getFieldComponent(field)" v-bind="getFieldProps(field)" />
30
+ <slot v-else :name="field.prop" :field="field">
31
+ </slot>
32
+ </el-form-item>
33
+ </el-form>
34
+ <div slot="footer" class="dialog-footer">
35
+ <el-button @click="handleCancel">取消</el-button>
36
+ <el-button type="primary" @click="handleConfirm">确认</el-button>
37
+ </div>
38
+ </el-dialog>
39
+
40
+ <el-drawer
41
+ v-if="mode === 'drawer'"
42
+ :title="title"
43
+ :visible.sync="visible"
44
+ :direction="direction"
45
+ :size="width"
46
+ :before-close="handleCancel"
47
+ class="xt-form-schema-drawer"
48
+ append-to-body
49
+ modal-append-to-body
50
+ >
51
+ <el-form :model="formData" :inline="false" label-width="120px">
52
+ <el-form-item :label="field.label" :prop="field.prop" v-for="field in schema" :key="field.prop">
53
+ <component v-if="!field.isSlot" :is="getFieldComponent(field)" v-bind="getFieldProps(field)" />
54
+ <slot v-else :name="field.prop" :field="field">
55
+ </slot>
56
+ </el-form-item>
57
+ </el-form>
58
+ <div class="drawer-footer">
59
+ <el-button @click="handleCancel">取消</el-button>
60
+ <el-button type="primary" @click="handleConfirm">确认</el-button>
61
+ </div>
62
+ </el-drawer>
63
+ </div>
64
+ </template>
65
+
66
+ <script>
67
+ export default {
68
+ name: 'XtFormSchema',
69
+ props: {
70
+ visible: {
71
+ type: Boolean,
72
+ default: false
73
+ },
74
+ value: {
75
+ type: Object,
76
+ default: () => ({})
77
+ },
78
+ schema: {
79
+ type: Array,
80
+ default: () => []
81
+ },
82
+ mode: {
83
+ type: String,
84
+ default: 'dialog',
85
+ validator: (val) => ['dialog', 'drawer'].includes(val)
86
+ },
87
+ title: {
88
+ type: String,
89
+ default: '高级搜索'
90
+ },
91
+ width: {
92
+ type: [String, Number],
93
+ default: '500px'
94
+ },
95
+ direction: {
96
+ type: String,
97
+ default: 'rtl',
98
+ validator: (val) => ['ltr', 'rtl', 'ttb', 'btt'].includes(val)
99
+ },
100
+ showSearchTags: {
101
+ type: Boolean,
102
+ default: false
103
+ },
104
+ simpleFields: {
105
+ type: Array,
106
+ default: () => []
107
+ }
108
+ },
109
+ data() {
110
+ return {
111
+ formData: {}
112
+ }
113
+ },
114
+ computed: {
115
+ activeSearchTags() {
116
+ const tags = {}
117
+ this.schema.forEach(field => {
118
+ const value = this.formData[field.prop]
119
+ if (value !== null && value !== undefined && value !== '' && value !== []) {
120
+ let displayValue = value
121
+ if (field.type === 'select' && field.options) {
122
+ const option = field.options.find(opt => opt.value === value)
123
+ displayValue = option ? option.label : value
124
+ } else if (field.type === 'date' && Array.isArray(value)) {
125
+ displayValue = value.map(v => v ? this.formatDate(v) : '').join(' - ')
126
+ } else if (field.type === 'daterange' && Array.isArray(value)) {
127
+ displayValue = value.map(v => v ? this.formatDate(v) : '').join(' - ')
128
+ } else if (field.type === 'checkbox-group' && Array.isArray(value)) {
129
+ const labels = value.map(v => {
130
+ const option = (field.options || []).find(opt => opt.value === v)
131
+ return option ? option.label : v
132
+ })
133
+ displayValue = labels.join(', ')
134
+ }
135
+ tags[field.prop] = {
136
+ label: field.label,
137
+ value: displayValue
138
+ }
139
+ }
140
+ })
141
+ return tags
142
+ },
143
+ activeSearchTagsCount() {
144
+ return Object.keys(this.activeSearchTags).length
145
+ }
146
+ },
147
+ watch: {
148
+ visible: {
149
+ immediate: true,
150
+ handler(val) {
151
+ if (val) {
152
+ this.initFormData()
153
+ }
154
+ }
155
+ },
156
+ value: {
157
+ deep: true,
158
+ handler(val) {
159
+ if (val && this.visible) {
160
+ this.formData = { ...val }
161
+ }
162
+ }
163
+ }
164
+ },
165
+ methods: {
166
+ initFormData() {
167
+ const data = {}
168
+ this.schema.forEach(field => {
169
+ data[field.prop] = this.value[field.prop] !== undefined ? this.value[field.prop] : (field.defaultValue !== undefined ? field.defaultValue : '')
170
+ })
171
+ this.formData = data
172
+ },
173
+ getFieldComponent(field) {
174
+ const componentMap = {
175
+ input: 'el-input',
176
+ textarea: 'el-input',
177
+ select: 'el-select',
178
+ checkbox: 'el-checkbox',
179
+ 'checkbox-group': 'el-checkbox-group',
180
+ radio: 'el-radio',
181
+ 'radio-group': 'el-radio-group',
182
+ 'date': 'el-date-picker',
183
+ daterange: 'el-date-picker',
184
+ datetime: 'el-date-picker',
185
+ 'datetime-range': 'el-date-picker',
186
+ number: 'el-input-number',
187
+ switch: 'el-switch'
188
+ }
189
+ return componentMap[field.type] || 'el-input'
190
+ },
191
+ getFieldProps(field) {
192
+ const props = {
193
+ value: this.formData[field.prop],
194
+ placeholder: field.placeholder || `请输入${field.label}`
195
+ }
196
+
197
+ if (field.type === 'textarea') {
198
+ props.type = 'textarea'
199
+ props.rows = field.rows || 3
200
+ }
201
+
202
+ if (field.type === 'select') {
203
+ props.filterable = field.filterable || false
204
+ props.multiple = field.multiple || false
205
+ }
206
+
207
+ if (field.type === 'date') {
208
+ props.type = field.dateType || 'date'
209
+ props.format = field.format || 'yyyy-MM-dd'
210
+ props['value-format'] = field.valueFormat || 'yyyy-MM-dd'
211
+ }
212
+
213
+ if (field.type === 'daterange') {
214
+ props.type = 'daterange'
215
+ props.format = field.format || 'yyyy-MM-dd'
216
+ props['value-format'] = field.valueFormat || 'yyyy-MM-dd'
217
+ }
218
+
219
+ if (field.type === 'datetime') {
220
+ props.type = 'datetime'
221
+ props.format = field.format || 'yyyy-MM-dd HH:mm:ss'
222
+ props['value-format'] = field.valueFormat || 'yyyy-MM-dd HH:mm:ss'
223
+ }
224
+
225
+ if (field.type === 'datetime-range') {
226
+ props.type = 'datetimerange'
227
+ props.format = field.format || 'yyyy-MM-dd HH:mm:ss'
228
+ props['value-format'] = field.valueFormat || 'yyyy-MM-dd HH:mm:ss'
229
+ }
230
+
231
+ if (field.type === 'number') {
232
+ props.min = field.min
233
+ props.max = field.max
234
+ props.step = field.step || 1
235
+ }
236
+
237
+ if (field.type === 'checkbox-group' || field.type === 'radio-group') {
238
+ props.options = field.options || []
239
+ }
240
+
241
+ if (field.disabled !== undefined) {
242
+ props.disabled = field.disabled
243
+ }
244
+
245
+ if (field.size !== undefined) {
246
+ props.size = field.size
247
+ }
248
+
249
+ props['on:input'] = (val) => this.handleFieldChange(field.prop, val)
250
+ props['on:change'] = (val) => this.handleFieldChange(field.prop, val)
251
+
252
+ return props
253
+ },
254
+ handleFieldChange(prop, val) {
255
+ this.formData[prop] = val
256
+ this.$emit('input', { ...this.formData })
257
+ this.$emit('change', { prop, value: val })
258
+ },
259
+ handleRemoveTag(prop) {
260
+ this.formData[prop] = ''
261
+ this.$emit('input', { ...this.formData })
262
+ this.$emit('change', { prop, value: '' })
263
+ },
264
+ handleConfirm() {
265
+ this.$emit('confirm', { ...this.formData })
266
+ this.$emit('input', { ...this.formData })
267
+ this.$emit('update:visible', false)
268
+ },
269
+ handleCancel() {
270
+ this.$emit('cancel')
271
+ this.$emit('update:visible', false)
272
+ },
273
+ formatDate(date) {
274
+ if (!date) return ''
275
+ const d = new Date(date)
276
+ const year = d.getFullYear()
277
+ const month = String(d.getMonth() + 1).padStart(2, '0')
278
+ const day = String(d.getDate()).padStart(2, '0')
279
+ return `${year}-${month}-${day}`
280
+ },
281
+ resetFields() {
282
+ this.initFormData()
283
+ this.schema.forEach(field => {
284
+ this.formData[field.prop] = field.defaultValue !== undefined ? field.defaultValue : ''
285
+ })
286
+ this.$emit('input', { ...this.formData })
287
+ }
288
+ }
289
+ }
290
+ </script>
291
+
292
+ <style lang="scss" scoped>
293
+ .xt-form-schema {
294
+ .search-tags {
295
+ display: flex;
296
+ flex-wrap: wrap;
297
+ align-items: center;
298
+ padding: 8px 12px;
299
+ background: #f5f7fa;
300
+ border-radius: 4px;
301
+ margin-bottom: 12px;
302
+
303
+ .tags-title {
304
+ font-size: 14px;
305
+ color: #606266;
306
+ margin-right: 8px;
307
+ flex-shrink: 0;
308
+ }
309
+
310
+ .el-tag {
311
+ margin: 4px;
312
+ cursor: pointer;
313
+ }
314
+
315
+ .no-tags {
316
+ font-size: 14px;
317
+ color: #c0c4cc;
318
+ }
319
+ }
320
+
321
+ .dialog-footer,
322
+ .drawer-footer {
323
+ text-align: right;
324
+ padding-top: 16px;
325
+ border-top: 1px solid #ebeef5;
326
+ }
327
+ }
328
+ </style>
@@ -12,13 +12,13 @@ export default {
12
12
  span: {
13
13
  type: Number,
14
14
  default: 1,
15
- validator: (val) => val > 0
15
+ validator: (val) => val >= 1
16
16
  },
17
17
  // 跨行数
18
18
  rowSpan: {
19
19
  type: Number,
20
20
  default: 1,
21
- validator: (val) => val > 0
21
+ validator: (val) => val >= 1
22
22
  },
23
23
  // 起始列
24
24
  start: {
@@ -3,22 +3,35 @@
3
3
  class="xt-input"
4
4
  :class="[
5
5
  size ? 'xt-input--' + size : '',
6
- { 'is-disabled': disabled }
6
+ { 'is-disabled': disabled },
7
+ { 'is-error': hasError }
7
8
  ]"
8
9
  >
9
10
  <el-input
10
11
  :value="displayValue"
11
12
  :placeholder="placeholder"
12
- :type="isNumberType ? 'text' : type"
13
+ :type="inputType"
13
14
  :size="size"
14
15
  :disabled="disabled"
15
16
  :readonly="readonly"
16
17
  :style="inputStyle"
18
+ :maxlength="maxlength"
19
+ :show-word-limit="showWordLimit"
20
+ :prefix-icon="prefixIcon"
21
+ :suffix-icon="suffixIcon"
17
22
  @input="handleInput"
18
23
  @change="handleChange"
19
24
  @focus="handleFocus"
20
25
  @blur="handleBlur"
21
- />
26
+ @clear="handleClear"
27
+ >
28
+ <template v-if="$slots.prefix" #prefix>
29
+ <slot name="prefix"></slot>
30
+ </template>
31
+ <template v-if="$slots.suffix" #suffix>
32
+ <slot name="suffix"></slot>
33
+ </template>
34
+ </el-input>
22
35
  </div>
23
36
  </template>
24
37
 
@@ -33,7 +46,8 @@ export default {
33
46
  },
34
47
  type: {
35
48
  type: String,
36
- default: 'text'
49
+ default: 'text',
50
+ validator: (val) => ['text', 'number', 'integer', 'decimal', 'money', 'phone', 'email', 'idcard', 'password', 'textarea'].includes(val)
37
51
  },
38
52
  size: {
39
53
  type: String,
@@ -50,31 +64,87 @@ export default {
50
64
  color: {
51
65
  type: String,
52
66
  default: ''
67
+ },
68
+ precision: {
69
+ type: Number,
70
+ default: 2,
71
+ validator: (val) => val >= 0 && val <= 10
72
+ },
73
+ min: {
74
+ type: Number,
75
+ default: undefined
76
+ },
77
+ max: {
78
+ type: Number,
79
+ default: undefined
80
+ },
81
+ allowNegative: {
82
+ type: Boolean,
83
+ default: false
84
+ },
85
+ thousandSeparator: {
86
+ type: Boolean,
87
+ default: false
88
+ },
89
+ maxlength: {
90
+ type: Number,
91
+ default: undefined
92
+ },
93
+ showWordLimit: {
94
+ type: Boolean,
95
+ default: false
96
+ },
97
+ prefixIcon: {
98
+ type: String,
99
+ default: ''
100
+ },
101
+ suffixIcon: {
102
+ type: String,
103
+ default: ''
104
+ },
105
+ trim: {
106
+ type: Boolean,
107
+ default: false
53
108
  }
54
109
  },
55
110
  data() {
56
111
  return {
57
112
  currentStr: '',
58
- isFocused: false
113
+ isFocused: false,
114
+ hasError: false
59
115
  }
60
116
  },
61
117
  computed: {
118
+ inputType() {
119
+ if (this.type === 'textarea') {
120
+ return 'textarea'
121
+ }
122
+ if (this.type === 'password') {
123
+ return 'password'
124
+ }
125
+ if (this.isNumberType) {
126
+ return 'text'
127
+ }
128
+ return 'text'
129
+ },
62
130
  isNumberType() {
63
- return this.type === 'number'
131
+ return ['number', 'integer', 'decimal', 'money'].includes(this.type)
64
132
  },
65
133
  displayValue() {
66
134
  if (this.isNumberType) {
135
+ if (this.thousandSeparator && this.currentStr) {
136
+ return this.formatThousand(this.currentStr)
137
+ }
67
138
  return this.currentStr
68
139
  }
69
140
  return this.value
70
141
  },
71
142
  inputStyle() {
143
+ const style = {}
72
144
  if (this.color) {
73
- return {
74
- '--xt-input-focus-color': this.color
75
- }
145
+ style['--xt-input-focus-color'] = this.color
76
146
  }
77
- return {}
147
+ return style
78
148
  }
79
149
  },
80
150
  watch: {
@@ -82,34 +152,141 @@ export default {
82
152
  immediate: true,
83
153
  handler(val) {
84
154
  if (this.isNumberType && !this.isFocused) {
85
- this.currentStr = val === null || val === undefined || val === '' ? '' : String(val)
155
+ if (val === null || val === undefined || val === '') {
156
+ this.currentStr = ''
157
+ } else {
158
+ const strVal = String(val)
159
+ if (this.thousandSeparator) {
160
+ this.currentStr = this.parseThousand(strVal)
161
+ } else {
162
+ this.currentStr = strVal
163
+ }
164
+ }
86
165
  }
87
166
  }
88
167
  }
89
168
  },
90
169
  methods: {
91
170
  isValidNumber(str) {
92
- return /^[+-]?\d*\.?\d*$/.test(str)
171
+ if (!str) return true
172
+ const negativePattern = this.allowNegative ? '[-+]?' : ''
173
+ if (this.type === 'integer') {
174
+ return new RegExp(`^${negativePattern}\\d*$`).test(str)
175
+ }
176
+ if (this.type === 'decimal' || this.type === 'number') {
177
+ if (this.precision === 0) {
178
+ return new RegExp(`^${negativePattern}\\d*$`).test(str)
179
+ }
180
+ return new RegExp(`^${negativePattern}\\d*\\.?\\d{0,${this.precision}}$`).test(str)
181
+ }
182
+ if (this.type === 'money') {
183
+ return new RegExp(`^${negativePattern}\\d*\\.?\\d{0,${this.precision}}$`).test(str)
184
+ }
185
+ return true
186
+ },
187
+ isValidPhone(str) {
188
+ return /^1[3-9]\d{0,9}$/.test(str)
189
+ },
190
+ isValidEmail(str) {
191
+ if (!str) return true
192
+ return /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/.test(str)
193
+ },
194
+ isValidIdCard(str) {
195
+ return /^[1-9]\d{0,17}$/.test(str)
196
+ },
197
+ formatThousand(str) {
198
+ if (!str) return ''
199
+ const parts = str.split('.')
200
+ parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ',')
201
+ return parts.join('.')
202
+ },
203
+ parseThousand(str) {
204
+ return str.replace(/,/g, '')
205
+ },
206
+ parseToNumber(str) {
207
+ if (!str || str === '+' || str === '-' || str === '.' || str === '+.' || str === '-.' || str === '-.') {
208
+ return undefined
209
+ }
210
+ const num = parseFloat(str)
211
+ return isNaN(num) ? undefined : num
93
212
  },
94
213
  handleInput(val) {
214
+ let inputVal = val
215
+
216
+ if (this.thousandSeparator && this.isNumberType) {
217
+ inputVal = this.parseThousand(val)
218
+ }
219
+
220
+ if (this.type === 'phone') {
221
+ inputVal = val.replace(/[^\d]/g, '')
222
+ if (!this.isValidPhone(inputVal)) {
223
+ inputVal = inputVal.slice(0, -1)
224
+ }
225
+ this.$emit('input', inputVal)
226
+ return
227
+ }
228
+
229
+ if (this.type === 'idcard') {
230
+ inputVal = val.replace(/[^\dXx]/g, '')
231
+ if (!this.isValidIdCard(inputVal)) {
232
+ inputVal = inputVal.slice(0, -1)
233
+ }
234
+ this.$emit('input', inputVal.toUpperCase())
235
+ return
236
+ }
237
+
238
+ if (this.type === 'email') {
239
+ if (!this.isValidEmail(inputVal)) {
240
+ this.hasError = true
241
+ } else {
242
+ this.hasError = false
243
+ }
244
+ this.$emit('input', inputVal)
245
+ return
246
+ }
247
+
95
248
  if (this.isNumberType) {
96
- if (this.isValidNumber(val)) {
97
- this.currentStr = val
98
- const num = this.parseToNumber(val)
249
+ if (this.isValidNumber(inputVal)) {
250
+ this.currentStr = inputVal
251
+ const num = this.parseToNumber(inputVal)
99
252
  if (num !== undefined) {
100
- this.$emit('input', num)
253
+ let finalNum = num
254
+ if (this.min !== undefined && num < this.min) {
255
+ finalNum = this.min
256
+ this.currentStr = String(finalNum)
257
+ }
258
+ if (this.max !== undefined && num > this.max) {
259
+ finalNum = this.max
260
+ this.currentStr = String(finalNum)
261
+ }
262
+ this.$emit('input', finalNum)
263
+ this.hasError = false
264
+ } else {
265
+ this.$emit('input', undefined)
101
266
  }
102
267
  }
103
268
  } else {
104
- this.$emit('input', val)
269
+ if (this.trim) {
270
+ inputVal = inputVal.trim()
271
+ }
272
+ this.$emit('input', inputVal)
105
273
  }
106
274
  },
107
275
  handleChange(val) {
276
+ let changeVal = val
277
+
278
+ if (this.thousandSeparator && this.isNumberType) {
279
+ changeVal = this.parseThousand(val)
280
+ }
281
+
108
282
  if (this.isNumberType) {
109
- const num = this.parseToNumber(val)
283
+ const num = this.parseToNumber(changeVal)
110
284
  this.$emit('change', num)
111
285
  } else {
112
- this.$emit('change', val)
286
+ if (this.trim) {
287
+ changeVal = changeVal.trim()
288
+ }
289
+ this.$emit('change', changeVal)
113
290
  }
114
291
  },
115
292
  handleFocus(e) {
@@ -118,29 +295,48 @@ export default {
118
295
  },
119
296
  handleBlur(e) {
120
297
  this.isFocused = false
298
+
121
299
  if (this.isNumberType) {
122
300
  const num = this.parseToNumber(this.currentStr)
123
- this.$emit('blur', num, e)
124
301
  if (num !== undefined) {
125
- this.currentStr = String(num)
126
- this.$emit('input', num)
302
+ let finalNum = num
303
+ if (this.precision > 0) {
304
+ finalNum = Number(num.toFixed(this.precision))
305
+ }
306
+ this.currentStr = String(finalNum)
307
+ this.$emit('input', finalNum)
308
+ this.$emit('blur', finalNum, e)
309
+ this.hasError = false
127
310
  } else {
128
311
  if (this.currentStr !== '') {
129
312
  this.currentStr = ''
130
313
  this.$emit('input', undefined)
131
314
  }
315
+ this.$emit('blur', undefined, e)
316
+ this.hasError = false
132
317
  }
133
318
  } else {
319
+ if (this.type === 'email') {
320
+ if (!this.isValidEmail(this.value)) {
321
+ this.hasError = true
322
+ }
323
+ }
134
324
  this.$emit('blur', e)
135
325
  }
136
326
  },
137
- parseToNumber(str) {
138
- if (!str || str === '+' || str === '-' || str === '.' || str === '+.' || str === '-.' || str === '-.') {
139
- return undefined
327
+ handleClear() {
328
+ if (this.isNumberType) {
329
+ this.currentStr = ''
140
330
  }
141
- const num = parseFloat(str)
142
- return isNaN(num) ? undefined : num
331
+ this.hasError = false
332
+ this.$emit('input', this.isNumberType ? undefined : '')
333
+ this.$emit('clear')
334
+ },
335
+ reset() {
336
+ this.currentStr = ''
337
+ this.hasError = false
338
+ this.$emit('input', this.isNumberType ? undefined : '')
143
339
  }
144
340
  }
145
341
  }
146
- </script>
342
+ </script>
@@ -82,3 +82,13 @@
82
82
  .xt-input.is-disabled .el-input__inner::placeholder {
83
83
  color: var(--xt-input-disabled-text-color);
84
84
  }
85
+
86
+ // error 状态
87
+ .xt-input.is-error .el-input__inner {
88
+ border-color: var(--xt-color-danger, #f56c6c);
89
+ }
90
+
91
+ .xt-input.is-error .el-input__inner:focus {
92
+ border-color: var(--xt-color-danger, #f56c6c);
93
+ box-shadow: 0 0 0 2px color-mix(in srgb, var(--xt-color-danger, #f56c6c) 20%, transparent);
94
+ }