web-component-gallery 2.3.12 → 2.3.13

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.
@@ -45,7 +45,7 @@
45
45
  <li
46
46
  class="cascader-content-item cascader-content-item-check-all"
47
47
  @click="handleCascaderAllItemClick(isCheckAll)"
48
- v-if="levelIndex === 0"
48
+ v-if="levelIndex === 0 && showAll"
49
49
  >
50
50
  <Checkbox
51
51
  :indeterminate="isIndeterminateAll"
@@ -148,6 +148,10 @@
148
148
  type: String,
149
149
  default: "全部"
150
150
  },
151
+ showAll: {
152
+ type: Boolean,
153
+ default: true
154
+ },
151
155
  noDataText: {
152
156
  type: String,
153
157
  default: "暂无数据"
@@ -417,6 +421,20 @@
417
421
  handleFocus() {
418
422
  this.isOpen = true;
419
423
  this.isClickOutSide = false;
424
+ this.$nextTick(() => {
425
+ if (!this.treeDataList.length || !this.treeDataList[0].length) return;
426
+ const childrenTag = this.selectOptionsConfig.children;
427
+ const first = this.treeDataList[0][0];
428
+ if (!first || !first[childrenTag] || !first[childrenTag].length) return;
429
+ const clearActive = list => list.forEach(item => {
430
+ item.$active = false;
431
+ if (item[childrenTag] && item[childrenTag].length) clearActive(item[childrenTag]);
432
+ });
433
+ clearActive(this.treeDataList[0]);
434
+ first.$active = true;
435
+ this.addHideSelectedList(first[childrenTag]);
436
+ this.$set(this, 'treeDataList', [this.treeDataList[0], first[childrenTag]]);
437
+ });
420
438
  },
421
439
  updatePostData() {
422
440
  const childrenTag = this.selectOptionsConfig.children;
@@ -1,5 +1,11 @@
1
1
 
2
2
  .gb-ant-select-multiple-cascader {
3
+ cursor: pointer;
4
+
5
+ .ant-select-selection {
6
+ cursor: pointer;
7
+ }
8
+
3
9
  .ant-select-dropdown-menu-item {
4
10
  display: none;
5
11
  }
@@ -7,9 +13,9 @@
7
13
  .cascader-content-item {
8
14
  .checkbox-text {
9
15
  padding-left: @padding-xs;
10
- font-size: @font-size-sm;
16
+ font-size: @font-size-base;
11
17
  color: @text-color;
12
- cursor: default;
18
+ cursor: pointer;
13
19
  flex: 1;
14
20
  text-align: left;
15
21
  .ellipsis();
@@ -99,6 +105,7 @@
99
105
  margin-top: @padding-xs - 2;
100
106
  align-items: center;
101
107
  line-height: 1;
108
+ cursor: pointer;
102
109
  .flex-layout();
103
110
 
104
111
  &:hover {
@@ -4,78 +4,82 @@ import _ from 'lodash'
4
4
 
5
5
  export default {
6
6
  name: 'WModel',
7
+ // 定义组件的双向绑定模型,prop 为 value,事件为 Change:Model
7
8
  model: {
8
9
  prop: 'value',
9
10
  event: 'Change:Model'
10
- },
11
- data() {
12
- return {
13
- formRules: {}
14
- }
15
11
  },
12
+
16
13
  props: {
14
+ // 表单绑定的数据对象
17
15
  value: {
18
16
  type: Object,
19
17
  default: () => ({})
20
- },
21
- /* Form布局方式 */
22
- layout: {
18
+ },
19
+ // 表单布局模式:'vertical' 或其他(水平布局)
20
+ layout: {
23
21
  type: String,
24
22
  default: 'vertical'
25
23
  },
26
- /* 一行显示几个 PS:最多5个 */
24
+ // 布局尺寸配置,用于计算表单项宽度
27
25
  layoutSize: {
28
26
  type: Number,
29
27
  default: 4
30
28
  },
31
- /* 表单项配置 */
32
- formSetting: Array
29
+ // 表单配置项数组,定义每个表单项的属性
30
+ formSetting: {
31
+ type: Array,
32
+ default: () => []
33
+ }
34
+ },
35
+
36
+ data() {
37
+ return {
38
+ // 存储动态生成的表单验证规则
39
+ formRules: {}
40
+ }
33
41
  },
42
+
34
43
  computed: {
44
+ // 双向绑定的 form 数据,获取时触发字段验证,设置时发射更新事件
35
45
  form: {
36
46
  get() {
37
- /** 校验单独赋值延迟的字段 */
38
- for (const k in this.value) {
39
- Object.hasOwnProperty.call(this.formRules, k) && this.$refs.FormModel?.validateField(k)
40
- }
47
+ this.validateChangedFields()
41
48
  return this.value
42
49
  },
43
50
  set(value) {
44
- this.$emit('Change:Model', value)
51
+ this.$emit('Change:Model', value)
45
52
  }
46
53
  },
54
+
55
+ // 根据布局模式计算表单属性(labelCol, wrapperCol 等)
47
56
  formAttrs() {
48
57
  const baseAttrs = {
49
- // 是否禁用表单,默认否
50
58
  disabled: false,
51
59
  ...this.$attrs
52
60
  }
53
-
54
- // 如果不是垂直布局,则添加标签列和包装列的配置
55
- if (this.layout !== 'vertical') {
56
- return {
57
- labelCol: { span: 4 },
58
- wrapperCol: { span: 20 },
59
- ...baseAttrs
60
- }
61
- }
62
-
63
- return baseAttrs
61
+
62
+ return this.layout !== 'vertical'
63
+ ? { labelCol: { span: 4 }, wrapperCol: { span: 20 }, ...baseAttrs }
64
+ : baseAttrs
64
65
  },
66
+
67
+ // 动态生成当前可见表单项的验证规则
65
68
  dynamicFormRules() {
66
- const formRules = {}
67
- this.visibleFormItems
68
- .forEach(node => {
69
- // if (!node.rules || node.rules.length === 0) return
70
- formRules[node.model] = setFormItemRule.call(this, node)
71
- })
72
- return formRules
69
+ return this.visibleFormItems.reduce((rules, node) => {
70
+ rules[node.model] = setFormItemRule.call(this, node)
71
+ return rules
72
+ }, {})
73
73
  },
74
+
75
+ // 过滤出未隐藏的表单项配置
74
76
  visibleFormItems() {
75
- return this.formSetting.filter(settingItem => !settingItem.hidden)
77
+ return this.formSetting.filter(item => !item.hidden)
76
78
  }
77
79
  },
80
+
78
81
  watch: {
82
+ // 监听动态规则变化,同步到 formRules 以触发重新渲染
79
83
  dynamicFormRules: {
80
84
  handler(newRules) {
81
85
  this.formRules = newRules
@@ -83,81 +87,119 @@ export default {
83
87
  immediate: true
84
88
  }
85
89
  },
90
+
86
91
  mounted() {
92
+ // 将内部 FormModel 实例通过事件暴露给父组件
87
93
  this.$emit('update:refForm', this.$refs.FormModel)
88
94
  },
95
+
89
96
  methods: {
97
+ // 验证发生变化的字段
98
+ validateChangedFields() {
99
+ Object.keys(this.value).forEach(key => {
100
+ if (this.formRules[key] && this.$refs.FormModel) {
101
+ this.$refs.FormModel.validateField(key)
102
+ }
103
+ })
104
+ },
105
+
106
+ // 解析模型路径(支持嵌套如 'user.name'),返回父级、子级及是否嵌套标志
107
+ parseModelPath(modelPath) {
108
+ const parts = modelPath.split('.')
109
+ return {
110
+ parent: parts[0],
111
+ child: parts[1],
112
+ isNested: parts.length > 1
113
+ }
114
+ },
115
+
116
+ // 渲染单个表单项
90
117
  renderSingleFormItem(h, props) {
91
- return setFormItem.call(this, h, this.form, props)
92
- },
93
-
118
+ const { parent, child, isNested } = this.parseModelPath(props.model)
119
+ const targetModel = isNested ? this.form[parent] : this.form
120
+ const finalModel = isNested ? child : parent
121
+
122
+ return setFormItem.call(this, h, targetModel, { ...props, model: finalModel }, {
123
+ prop: props.model
124
+ })
125
+ },
126
+
127
+ // 渲染动态多行表单项(适用于数组型数据)
94
128
  renderDynamicFormItems(h, dynamicModel, props) {
95
- // multipleConfig为配置的动态项数据
96
- return dynamicModel.map((modelItem, key) => {
97
- // 为动态项时重新定义绑定key、prop等来进行检验
98
- return props.multipleConfig.map((configItem) => {
129
+ return dynamicModel.map((modelItem, index) =>
130
+ props.multipleConfig.map(configItem => {
99
131
  const childAttrs = {
100
- key,
101
- prop: `${props.model}.${key}.${configItem.model}`,
132
+ key: index,
133
+ // 后续有空添加另一种形式([0, 1, 2])数组中仅一位,则省略configItem.model
134
+ prop: `${props.model}.${index}.${configItem.model}`,
102
135
  style: getFormWidth.call(this, configItem, this.layoutSize ?? props.layoutSize),
103
136
  rules: setFormItemRule.call(this, configItem, props),
104
137
  parentModel: props.model
105
- }
138
+ }
106
139
  return setFormItem.call(this, h, modelItem, configItem, childAttrs)
107
140
  })
108
- })
109
- },
110
-
141
+ )
142
+ },
143
+
144
+ // 根据配置渲染表单项容器(支持单条或多条模式)
111
145
  setModelRender(h, props) {
112
- const dynamicModel = this.form[props.model]
146
+ const { parent, isNested } = this.parseModelPath(props.model)
147
+ const dynamicModel = this.form[parent]
148
+
149
+ const containerStyle = getFormWidth.call(
150
+ this,
151
+ props,
152
+ this.layoutSize,
153
+ props.multiple ? 0 : 24
154
+ )
155
+
113
156
  return (
114
- <div
115
- class={props.multiple && ['MultipleForm']}
116
- {...{ attrs: { style: getFormWidth.call(this, props, this.layoutSize, props.multiple ? 0 : 24) } }}
157
+ <div
158
+ class={props.multiple && ['MultipleForm']}
159
+ {...{ attrs: { style: containerStyle } }}
117
160
  >
118
- {/* 添加分层提示信息及其他隔层操作 */}
119
- {this.$scopedSlots[`${props.model}Tips`] && this.$scopedSlots[`${props.model}Tips`](props)}
120
- {
121
- // multiple为动态多项(使用场景:处理动态增减表单
122
- dynamicModel instanceof Array && props.multiple
123
- ? this.renderDynamicFormItems(h, dynamicModel, props)
124
- : this.renderSingleFormItem(h, props)
161
+ {this.$scopedSlots[`${props.model}Tips`]?.(props)}
162
+ {dynamicModel instanceof Array && props.multiple
163
+ ? this.renderDynamicFormItems(h, dynamicModel, props)
164
+ : this.renderSingleFormItem(h, props)
125
165
  }
126
- {/* 使用场景:如另起一行添加其他额外信息及操作 */}
127
- {this.$scopedSlots[`${props.model}Operate`] && this.$scopedSlots[`${props.model}Operate`](props)}
166
+ {this.$scopedSlots[`${props.model}Operate`]?.(props)}
128
167
  </div>
129
168
  )
130
169
  },
131
-
170
+
171
+ // 记忆化计算表单宽度,避免重复计算
132
172
  getFormWidth: _.memoize(function(props, layoutSize, multiple) {
133
173
  return getFormWidth.call(this, props, layoutSize, multiple ? 24 : 0)
134
- }),
135
-
136
- formSubmit() {
137
- return new Promise((resolve, reject) => {
138
- this.$refs.FormModel.validate()
139
- .then(resolve)
140
- .catch(err => {
141
- console.error('表单验证失败:', err)
142
- this.$nextTick(() => {
143
- const errorDiv = document.getElementsByClassName('has-error')
144
- errorDiv[0].scrollIntoView({
145
- behavior: "smooth",
146
- block: "center"
147
- })
148
- })
149
- reject()
150
- })
174
+ }),
175
+
176
+ // 提交表单并处理验证错误
177
+ async formSubmit() {
178
+ try {
179
+ await this.$refs.FormModel.validate()
180
+ } catch (err) {
181
+ console.error('表单验证失败:', err)
182
+ this.scrollToFirstError()
183
+ throw err
184
+ }
185
+ },
186
+
187
+ // 滚动到第一个报错的表单项
188
+ scrollToFirstError() {
189
+ this.$nextTick(() => {
190
+ const errorElement = document.querySelector('.has-error')
191
+ errorElement?.scrollIntoView({ behavior: 'smooth', block: 'center' })
151
192
  })
152
193
  }
153
194
  },
195
+
154
196
  render(h) {
155
- const {layout, formAttrs, setModelRender} = this
197
+ const { layout, formAttrs, setModelRender } = this
156
198
 
157
199
  return (
158
200
  <FormModel
159
201
  ref="FormModel"
160
- class="FormModel"
202
+ class="FormModel"
161
203
  props={{
162
204
  model: this.form,
163
205
  rules: this.formRules,
@@ -165,11 +207,8 @@ export default {
165
207
  ...formAttrs
166
208
  }}
167
209
  >
168
- {
169
- this.$slots.default ??
170
- this.visibleFormItems.map(props => setModelRender(h, props))
171
- }
210
+ {this.$slots.default || this.visibleFormItems.map(props => setModelRender(h, props))}
172
211
  </FormModel>
173
212
  )
174
213
  }
175
- }
214
+ }
@@ -8,90 +8,75 @@ const FormModelItem = FormModel.Item
8
8
  * @param {Object} child - 表单项配置
9
9
  * @param {number} layoutSize - 一行显示的表单项数量
10
10
  * @param {number} [gap=24] - 表单项间距
11
- * @returns {string} - 计算后的CSS样式
11
+ * @returns {string} CSS 样式字符串
12
12
  */
13
13
  export function getFormWidth(child, layoutSize, gap = 24) {
14
- const width = (100 / layoutSize) * (child.size ?? 1)
15
- if (this.layout === 'vertical') {
16
- return `width: calc(${width}% - ${gap}px); margin-right: ${gap}px;`
17
- }
18
- return `width: ${width}%;`
14
+ const widthPercent = (100 / layoutSize) * (child.size ?? 1)
15
+
16
+ return this.layout === 'vertical'
17
+ ? `width: calc(${widthPercent}% - ${gap}px); margin-right: ${gap}px;`
18
+ : `width: ${widthPercent}%;`
19
19
  }
20
20
 
21
21
  /**
22
22
  * 动态设置表单项验证规则
23
23
  * @param {Object} node - 当前表单项配置
24
24
  * @param {Object} [nodeParent={}] - 父表单项配置
25
- * @returns {Array} - 验证规则数组
25
+ * @returns {Array} 验证规则数组
26
26
  */
27
27
  export function setFormItemRule(node, nodeParent = {}) {
28
- // 确定是否必填
29
- const required = (node.required ?? nodeParent.required ?? this.formAttrs.required) ?? true
28
+ const required = node.required ?? nodeParent.required ?? this.formAttrs.required ?? true
30
29
 
31
- // 生成提示信息
32
30
  const message = node.attrs?.placeholder ||
33
- (/(select|picker|radio|upload)/.test((node.is ?? 'Input').toLowerCase()) ? '请选择' : '请输入') +
34
- (node.label || '')
35
-
36
- // 基础规则
37
- const baseRules = []
31
+ `${/(select|picker|radio|upload)/.test((node.is ?? 'Input').toLowerCase()) ? '请选择' : '请输入'}${node.label || ''}`
38
32
 
39
- if (required) {
40
- baseRules.push({
41
- required,
42
- message,
43
- trigger: ['change', 'blur']
44
- })
45
- }
33
+ const baseRules = required ? [{
34
+ required,
35
+ message,
36
+ trigger: ['change', 'blur']
37
+ }] : []
46
38
 
47
- // 合并自定义规则
48
- const customRules = (node.rules || []).map(rule => {
49
- if (rule.pattern && typeof rule.pattern === 'string') {
50
- return { ...rule, pattern: new RegExp(rule.pattern) }
51
- }
52
- return rule
53
- })
39
+ const customRules = (node.rules || []).map(rule =>
40
+ rule.pattern && typeof rule.pattern === 'string'
41
+ ? { ...rule, pattern: new RegExp(rule.pattern) }
42
+ : rule
43
+ )
54
44
 
55
45
  return [...baseRules, ...customRules]
56
- }
46
+ }
57
47
 
58
48
  /**
59
49
  * 动态渲染表单项
60
- * @param {Function} h - Vue的createElement函数
50
+ * @param {Function} h - Vue createElement 函数
61
51
  * @param {Object} vModel - 表单数据对象
62
52
  * @param {Object} child - 表单项配置
63
53
  * @param {Object} [childAttrs] - 表单项属性
64
- * @returns {VNode} - 渲染结果
54
+ * @returns {VNode} 渲染结果
65
55
  */
66
- export function setFormItem(h, vModel, child, childAttrs) {
56
+ export function setFormItem(h, vModel, child, childAttrs = {}) {
67
57
  const { layout, layoutSize, formAttrs, $scopedSlots } = this
58
+ const slotsName = `${childAttrs.parentModel || ''}${child.model}`
68
59
 
69
- // 设置表单项属性
70
60
  const formItemAttrs = {
71
61
  key: child.model,
72
62
  prop: child.model,
73
- colon: !(layout === 'inline'),
63
+ colon: layout !== 'inline',
74
64
  ...childAttrs
75
65
  }
76
66
 
77
- // 生成插槽名称
78
- const slotsName = (childAttrs?.parentModel ?? '') + child.model
67
+ const contentAttrs = {
68
+ disabled: childAttrs.disabled ?? formAttrs.disabled ?? false,
69
+ ...child.attrs
70
+ }
79
71
 
80
- // 渲染标签
81
- const renderLabel = customLabel =>
82
- customLabel ? customLabel(vModel) : <span>{child.label}</span>
72
+ const renderLabel = () =>
73
+ child.customLabel ? child.customLabel(vModel) : <span>{child.label}</span>
83
74
 
84
- // 渲染表单项内容
85
75
  const renderContent = () => {
86
76
  if ($scopedSlots[child.model]) {
87
77
  return $scopedSlots[child.model](child)
88
78
  }
89
79
 
90
- const contentAttrs = {
91
- disabled: childAttrs?.disabled ?? formAttrs.disabled ?? false,
92
- ...child.attrs
93
- }
94
-
95
80
  return (
96
81
  <RenderComp
97
82
  v-model={vModel[child.model]}
@@ -109,13 +94,9 @@ export function setFormItem(h, vModel, child, childAttrs) {
109
94
  }}
110
95
  {...{ attrs: formItemAttrs }}
111
96
  >
112
- <template slot="label">
113
- {renderLabel(child.customLabel)}
114
- </template>
115
- {/* 当前表单项可slot进行单独开发配置 (目前仅支持非动态增减情况) */}
97
+ <template slot="label">{renderLabel()}</template>
116
98
  {renderContent()}
117
- {/* 如若再某个model后需添加按钮等场景(例如地图的选址按钮 */}
118
99
  {$scopedSlots[`${slotsName}Handle`] && $scopedSlots[`${slotsName}Handle`](formItemAttrs)}
119
100
  </FormModelItem>
120
101
  )
121
- }
102
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "web-component-gallery",
3
- "version": "2.3.12",
3
+ "version": "2.3.13",
4
4
  "description": "基础vue、antdvue、less实现的私有组件库",
5
5
  "main": "dist/index.umd.js",
6
6
  "files": [
package/utils/Filter.js CHANGED
@@ -463,7 +463,8 @@ export function transferData(data, format, options = {}) {
463
463
  * @param {Array} format - 格式配置
464
464
  * - ['a', 'b', 'c'] - 默认转为 Number
465
465
  * - [['a', 'b'], 'Number'] - 指定类型
466
- * - [{ fields: ['a', 'b'], type: 'Number' }] - 对象配置
466
+ * - [{ fields: ['a', 'b'], type: 'Number' }] - 单个对象配置
467
+ * - [{ fields: ['a', 'b'], type: 'Number', defaultValue: 0 }, { fields: ['c'], type: 'String' }] - 多个对象配置
467
468
  * @param {Object} options - 配置选项
468
469
  * @returns {*} 转换后的数据
469
470
  */
@@ -472,11 +473,14 @@ function transferDataByFields(data, format, options) {
472
473
  return data ?? options.defaultValue
473
474
  }
474
475
 
476
+ // 解析数组配置
477
+ if (format.length === 0) return data
478
+
475
479
  let fields = []
476
480
  let type = 'Number' // 默认转为 Number
477
481
 
478
- // 解析数组配置
479
- if (format.length === 0) return data
482
+ // 对象处理:转换指定字段
483
+ const result = { ...data }
480
484
 
481
485
  // 形式 1: ['a', 'b', 'c'] - 默认转 Number
482
486
  if (typeof format[0] === 'string') {
@@ -489,18 +493,21 @@ function transferDataByFields(data, format, options) {
489
493
  }
490
494
  // 形式 3: [{ fields: ['a', 'b'], type: 'Number' }] - 对象配置
491
495
  else if (typeof format[0] === 'object') {
492
- const config = format[0]
493
- fields = config.fields || []
494
- type = config.type || 'Number'
496
+ format.forEach(config => {
497
+ // 转换指定字段
498
+ for (const field of config.fields) {
499
+ if (field in result) {
500
+ const fieldDefaultValue = config.defaultValue || options.defaultValue
501
+ result[field] = transferData(result[field], config.type, fieldDefaultValue)
502
+ }
503
+ }
504
+ })
495
505
  }
496
506
 
497
507
  // 如果是数组,递归处理每个元素
498
508
  if (Array.isArray(data)) {
499
509
  return data.map(item => transferDataByFields(item, format, options))
500
510
  }
501
-
502
- // 对象处理:转换指定字段
503
- const result = { ...data }
504
511
 
505
512
  for (const field of fields) {
506
513
  if (field in result) {