web-component-gallery 2.0.22 → 2.0.24

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.
@@ -11,33 +11,33 @@
11
11
  <Select
12
12
  show-search
13
13
  :style="{ width: width || '100%' }"
14
- :mode="mode"
14
+ :mode="mode"
15
15
  :open="isSelectOpen"
16
16
  :placeholder="placeholder"
17
- :filterOption="false"
17
+ :filterOption="listPageHandler ? false : filterOption"
18
18
  :getPopupContainer="tirggerNode => tirggerNode.parentNode"
19
19
  @select="handleSelect"
20
20
  @search="handleSearch"
21
21
  @popupScroll="handleScroll"
22
22
  v-bind="attrs"
23
- v-model="childSelectedValue"
23
+ v-on="$listeners"
24
+ v-model="innerValue"
24
25
  >
25
- <!-- v-on="$listeners" -->
26
26
  <template v-for="(index, name) in $slots" v-slot:[name]>
27
27
  <slot :name="name" />
28
28
  </template>
29
29
  <template v-for="(index, name) in $scopedSlots" v-slot:[name]="data">
30
30
  <slot :name="name" v-bind="data"></slot>
31
- </template>
31
+ </template>
32
32
  <div slot="dropdownRender" slot-scope="menu">
33
- <Checkbox v-if="mode" :checked="selectChecked" @change="handleSelectAll">全选</Checkbox>
33
+ <Checkbox v-if="mode" :checked="checkedAll" @change="handleSelectAll">全选</Checkbox>
34
34
  <v-nodes :vnodes="menu" />
35
35
  <div v-if="isLoadingMore" class="loading-more">
36
36
  <Icon type="loading" />
37
37
  加载中...
38
38
  </div>
39
39
  </div>
40
- <SelectOption v-for="(item, index) in options" :key="index" :value="item[valueKey]">
40
+ <SelectOption v-for="(item, index) in innerOptions" :key="index" :value="item[valueKey]">
41
41
  {{ customLabel ? customLabelHandler(item) : item[labelKey] }}
42
42
  </SelectOption>
43
43
  </Select>
@@ -61,53 +61,77 @@ export default {
61
61
  Checkbox
62
62
  },
63
63
  props: {
64
+ // 基础配置
64
65
  value: {
65
66
  type: [String, Number, Array, Boolean, Object],
66
67
  default: undefined
67
68
  },
68
- // 是否多选
69
69
  mode: String,
70
- // 是否支持输入保留字符
70
+ width: String,
71
71
  isInput: {
72
72
  type: Boolean,
73
73
  default: false
74
74
  },
75
- // 提示文字
76
75
  placeholder: {
77
76
  type: String,
78
77
  default: '请选择'
79
78
  },
80
- width: String,
81
- // 根据valueKey来进行自定义label
82
- customLabel: String,
79
+
80
+ // 数据映射
83
81
  valueKey: {
84
82
  type: String,
85
83
  default: 'value'
86
84
  },
87
85
  labelKey: {
88
86
  type: String,
89
- default: 'label'
87
+ default: 'label'
90
88
  },
89
+ customLabel: String,
90
+
91
91
  // 数据源
92
92
  options: {
93
93
  type: Array,
94
- default: () => ([])
94
+ default: () => []
95
+ },
96
+ initLoad: {
97
+ type: Boolean,
98
+ default: true
99
+ },
100
+ listPageParams: Object,
101
+ listPageHandler: Function,
102
+
103
+ // 分页配置
104
+ pSize: {
105
+ type: Number,
106
+ default: 50
95
107
  },
96
- // 分页页数
97
- pSize: Number,
98
- // 分页总数,如要实现完整分页功能要传递接口总数
99
- pTotal: Number
108
+ searchKey: {
109
+ type: String,
110
+ default: 'keyword'
111
+ }
100
112
  },
113
+
101
114
  data() {
102
115
  return {
116
+ // 分页状态
117
+ pagination: {
118
+ current: 1,
119
+ size: 10,
120
+ total: 0
121
+ },
122
+
123
+ // 当前数据
124
+ innerOptions: [],
125
+
126
+ // UI状态
103
127
  isSelectOpen: false,
104
- currentPage: 1,
105
128
  isLoadingMore: false,
106
- searchKeyword: ''
129
+ searchValue: ''
107
130
  }
108
131
  },
132
+
109
133
  computed: {
110
- childSelectedValue: {
134
+ innerValue: {
111
135
  get() {
112
136
  return this.value
113
137
  },
@@ -115,7 +139,7 @@ export default {
115
139
  this.$emit('input', val)
116
140
  }
117
141
  },
118
- attrs() {
142
+ attrs() {
119
143
  return {
120
144
  allowClear: true,
121
145
  showSearch: true,
@@ -123,95 +147,143 @@ export default {
123
147
  }
124
148
  },
125
149
  hasMore() {
126
- return this.options.length < this.pTotal
150
+ return this.innerOptions.length < this.pagination.total
127
151
  },
128
- selectChecked: {
129
- get() {
130
- return this.childSelectedValue?.length === this.options?.length
152
+ checkedAll() {
153
+ return this.innerValue?.length === this.innerOptions?.length
154
+ }
155
+ },
156
+
157
+ watch: {
158
+ options: {
159
+ handler(newVal) {
160
+ this.innerOptions = [...newVal]
131
161
  },
132
- set(val) {
133
- this.$emit('input', val)
134
- }
162
+ immediate: true
135
163
  }
136
164
  },
165
+
137
166
  created() {
138
- this.debouncedSearch = debounce(this.handleSearch, 800)
139
- this.debouncedLoadMore = debounce(this.loadMoreData, 300)
167
+ this.debouncedSearch = debounce(this.handleSearch, 800, { leading: true, trailing: false })
168
+ this.debouncedLoadMore = debounce(this.loadMoreData, 300, { leading: false, trailing: true })
140
169
  },
170
+
141
171
  mounted() {
142
- document.addEventListener('click', this.bodyCloseMenus)
172
+ this.pagination.size = this.pSize
173
+ this.initLoad && this.fetchData()
174
+ document.addEventListener('click', this.handleDocumentClick)
143
175
  },
176
+
144
177
  beforeDestroy() {
145
- document.removeEventListener('click', this.bodyCloseMenus)
178
+ document.removeEventListener('click', this.handleDocumentClick)
179
+ // 清理debounce定时器
180
+ this.debouncedSearch.cancel()
181
+ this.debouncedLoadMore.cancel()
146
182
  },
183
+
147
184
  methods: {
185
+ async fetchData() {
186
+ if (!this.listPageHandler) return
187
+
188
+ try {
189
+ this.isLoadingMore = true
190
+ const { total, records } = await this.listPageHandler({
191
+ ...this.pagination,
192
+ ...this.listPageParams,
193
+ [this.searchKey]: this.searchValue
194
+ })
195
+
196
+ this.pagination.current === 1 ?
197
+ this.innerOptions = [...records] :
198
+ this.innerOptions = [...this.innerOptions, ...records]
199
+
200
+ this.pagination.total = total
201
+ } catch (error) {
202
+ this.pagination.current--
203
+ console.error('分页加载失败:', error)
204
+ } finally {
205
+ this.isLoadingMore = false
206
+ }
207
+ },
208
+
148
209
  // 下拉滚动加载更多
149
210
  handleScroll(e) {
150
211
  const { scrollTop, scrollHeight, clientHeight } = e.target
151
212
  const reachBottom = scrollTop + clientHeight >= scrollHeight - 10
152
213
  if (reachBottom && !this.isLoadingMore && this.hasMore) this.debouncedLoadMore()
153
214
  },
154
- // 搜索事件
215
+
216
+ // 搜索事件处理
155
217
  handleSearch(value) {
156
- this.isInput && (this.childSelectedValue = value)
157
- this.searchKeyword = value
158
- this.currentPage = 1
159
-
160
- // 根据是否有分页决定emit参数格式
161
- this.$emit('search', this.pTotal ? {
162
- keyword: value,
163
- current: this.currentPage,
164
- size: this.pSize
165
- } : value)
166
- },
167
- // 点击选择器外内容下拉回收
168
- bodyCloseMenus(e) {
218
+ this.searchValue = value
219
+ this.pagination.current = 1
220
+
221
+ this.fetchData()
222
+ },
223
+
224
+ // 本地搜索
225
+ filterOption(input, option) {
226
+ this.isInput && (this.innerValue = input)
227
+ return option.componentOptions.children[0].text
228
+ .toLowerCase()
229
+ .indexOf(input.toLowerCase()) >= 0
230
+ },
231
+
232
+ // 点击选择器外内容关闭下拉
233
+ handleDocumentClick(e) {
169
234
  if (this.$refs.main && !this.$refs.main.contains(e.target)) this.isSelectOpen = false
170
235
  },
171
- // 自定义label显示
236
+
237
+ // 安全的自定义label处理
172
238
  customLabelHandler(item) {
239
+ if (!this.customLabel) return item[this.labelKey]
240
+
173
241
  try {
174
- // 使用函数式替代eval
175
- const func = new Function('item', `return ${this.customLabel}`)
176
- return func(item)
242
+ // 使用安全的模板字符串解析
243
+ const template = this.customLabel
244
+ .replace(/{{([^{}]+)}}/g, (match, key) => {
245
+ const path = key.trim().split('.')
246
+ let value = item
247
+ for (const p of path) {
248
+ if (value == null) return ''
249
+ value = value[p]
250
+ }
251
+ return value == null ? '' : value
252
+ })
253
+ return template
177
254
  } catch (e) {
178
255
  console.error('自定义label解析错误:', e)
179
256
  return item[this.labelKey]
180
257
  }
181
258
  },
182
- // 选中全部
183
- handleSelectAll(event) {
184
- const isChecked = event.target.checked
185
- const selectOptions = isChecked ? this.options.map(item => item[this.valueKey]) : []
186
- this.$emit('input', selectOptions)
259
+
260
+ // 选中全部选项
261
+ handleSelectAll({target}) {
262
+ const selectOptions = target.checked
263
+ ? this.innerOptions.map(item => item[this.valueKey])
264
+ : []
265
+
187
266
  this.isSelectOpen = false
267
+ this.$emit('input', selectOptions)
188
268
  },
189
- // 选中事件
269
+
270
+ // 选中单个选项
190
271
  handleSelect(value, option) {
191
272
  if (!value) return
273
+
192
274
  this.isSelectOpen = false
193
- const selectedOption = this.options.find(item => item[this.valueKey] === value)
275
+ const selectedOption = this.innerOptions.find(
276
+ item => item[this.valueKey] === value
277
+ )
194
278
  selectedOption && this.$emit('select', value, selectedOption)
195
279
  },
196
- // 加载更多
197
- loadMoreData() {
280
+
281
+ // 加载更多数据
282
+ async loadMoreData() {
198
283
  if (this.isLoadingMore || !this.hasMore) return
199
284
 
200
- this.isLoadingMore = true
201
- this.currentPage++
202
-
203
- try {
204
- this.$emit('load-more', {
205
- keyword: this.searchKeyword,
206
- current: this.currentPage,
207
- size: this.pSize
208
- })
209
- } catch (error) {
210
- this.currentPage--
211
- console.error('加载更多失败:', error)
212
- } finally {
213
- this.isLoadingMore = false
214
- }
285
+ this.pagination.current++
286
+ await this.fetchData()
215
287
  }
216
288
  }
217
289
  }
@@ -7,7 +7,7 @@ export default {
7
7
  model: {
8
8
  prop: 'value',
9
9
  event: 'Change:Model'
10
- },
10
+ },
11
11
  data() {
12
12
  return {
13
13
  formRules: {}
@@ -17,7 +17,7 @@ export default {
17
17
  value: {
18
18
  type: Object,
19
19
  default: () => ({})
20
- },
20
+ },
21
21
  /* Form布局方式 */
22
22
  layout: {
23
23
  type: String,
@@ -32,17 +32,7 @@ export default {
32
32
  }
33
33
  },
34
34
  /* 表单项配置 */
35
- formSetting: {
36
- type: Array,
37
- default: () => [],
38
- validator: value => {
39
- return value.every(item => {
40
- return typeof item === 'object' &&
41
- 'model' in item &&
42
- 'type' in item
43
- })
44
- }
45
- }
35
+ formSetting: Array
46
36
  },
47
37
  computed: {
48
38
  form: {
@@ -57,6 +47,16 @@ export default {
57
47
  this.$emit('Change:Model', value)
58
48
  }
59
49
  },
50
+ formAttrs() {
51
+ const attr =
52
+ this.layout === 'vertical' ?
53
+ this.$attrs :
54
+ {
55
+ ...getDefaultFormAttrs(),
56
+ ...this.$attrs
57
+ }
58
+ return attr
59
+ },
60
60
  filterSetting() {
61
61
  return this.formSetting.filter(settingItem => !settingItem.hidden)
62
62
  }
@@ -82,36 +82,43 @@ export default {
82
82
  }
83
83
  },
84
84
 
85
- renderSingleFormItem(props) {
85
+ renderSingleFormItem(h, props) {
86
86
  return setFormItem.call(this, h, this.form, props)
87
- },
87
+ },
88
88
 
89
- renderDynamicFormItems(props) {
90
- return props.multipleConfig.map((configItem, key) => {
91
- const childAttrs = {
92
- key,
93
- prop: `${props.model}.${key}.${configItem.model}`,
94
- style: getFormWidth.call(this, configItem, this.layoutSize ?? props.layoutSize),
95
- rules: setFormItemRule.call(this, configItem, props),
96
- parentModel: props.model
97
- }
98
- return setFormItem.call(this, h, this.form[props.model][key], configItem, childAttrs)
89
+ renderDynamicFormItems(h, dynamicModel, props) {
90
+ // multipleConfig为配置的动态项数据
91
+ return dynamicModel.map((modelItem, key) => {
92
+ // 为动态项时重新定义绑定key、prop等来进行检验
93
+ props.multipleConfig.map((configItem) => {
94
+ const childAttrs = {
95
+ key,
96
+ prop: `${props.model}.${key}.${configItem.model}`,
97
+ style: getFormWidth.call(this, configItem, this.layoutSize ?? props.layoutSize),
98
+ rules: setFormItemRule.call(this, configItem, props),
99
+ parentModel: props.model
100
+ }
101
+ return setFormItem.call(this, h, modelItem, configItem, childAttrs)
102
+ })
99
103
  })
100
104
  },
101
105
 
102
- setModelRender(props) {
106
+ setModelRender(h, props) {
103
107
  const dynamicModel = this.form[props.model]
104
108
  return (
105
109
  <div
106
110
  class={props.multiple && ['MultipleForm']}
107
- {...{ attrs: { style: getFormWidth.call(this, props, this.layoutSize, props.multiple ? 24 : 0) } }}
111
+ {...{ attrs: { style: getFormWidth.call(this, props, this.layoutSize, props.multiple ? 0 : 24) } }}
108
112
  >
113
+ {/* 添加分层提示信息及其他隔层操作 */}
109
114
  {this.$scopedSlots[`${props.model}Tips`] && this.$scopedSlots[`${props.model}Tips`](props)}
110
115
  {
116
+ // multiple为动态多项(使用场景:处理动态增减表单
111
117
  dynamicModel instanceof Array && props.multiple
112
- ? this.renderDynamicFormItems(props)
113
- : this.renderSingleFormItem(props)
118
+ ? this.renderDynamicFormItems(h, dynamicModel, props)
119
+ : this.renderSingleFormItem(h, props)
114
120
  }
121
+ {/* 使用场景:如另起一行添加其他额外信息及操作 */}
115
122
  {this.$scopedSlots[`${props.model}Operate`] && this.$scopedSlots[`${props.model}Operate`](props)}
116
123
  </div>
117
124
  )
@@ -119,7 +126,7 @@ export default {
119
126
 
120
127
  getFormWidth: _.memoize(function(props, layoutSize, multiple) {
121
128
  return getFormWidth.call(this, props, layoutSize, multiple ? 24 : 0)
122
- }),
129
+ }),
123
130
 
124
131
  formSubmit() {
125
132
  return new Promise((resolve, reject) => {
@@ -138,7 +145,7 @@ export default {
138
145
  })
139
146
  }
140
147
  },
141
- render() {
148
+ render(h) {
142
149
  const {layout, formAttrs, setModelRender} = this
143
150
 
144
151
  return (
@@ -154,7 +161,7 @@ export default {
154
161
  >
155
162
  {
156
163
  this.$slots.default ??
157
- this.filterSetting.map(props => setModelRender(props))
164
+ this.filterSetting.map(props => setModelRender(h, props))
158
165
  }
159
166
  </FormModel>
160
167
  )
@@ -9,13 +9,11 @@ const FormModelItem = FormModel.Item
9
9
  * @param {number} layoutSize - 一行显示的表单项数量
10
10
  * @param {number} [gap=24] - 表单项间距
11
11
  * @returns {string} - 计算后的CSS样式
12
- */
12
+ */
13
13
  export function getFormWidth(child, layoutSize, gap = 24) {
14
- if (this.layout === 'vertical') {
15
- const width = (100 / layoutSize) * (child.size ?? 1)
16
- return `flex: 0 1 calc(${width}% - ${gap}px); margin-right: ${gap}px;`
17
- }
18
- return `flex: 0 1 ${(100 / layoutSize) * (child.size ?? 1)}%;`
14
+ const width = (100 / layoutSize) * (child.size ?? 1)
15
+ if (this.layout === 'vertical') return `flex: 0 1 calc(${width}% - ${gap}px); margin-right: ${gap}px;`
16
+ return `flex: 0 1 ${width}%;`
19
17
  }
20
18
 
21
19
  /**
@@ -80,9 +78,7 @@ export function setFormItem(h, vModel, child, childAttrs) {
80
78
 
81
79
  // 渲染表单项内容
82
80
  const renderContent = () => {
83
- if ($scopedSlots[child.model]) {
84
- return $scopedSlots[child.model](child)
85
- }
81
+ if ($scopedSlots[child.model]) return $scopedSlots[child.model](child)
86
82
  return (
87
83
  <RenderComp
88
84
  v-model={vModel[child.model]}
@@ -103,7 +99,9 @@ export function setFormItem(h, vModel, child, childAttrs) {
103
99
  <template slot="label">
104
100
  {renderLabel(child.customLabel)}
105
101
  </template>
102
+ {/* 当前表单项可slot进行单独开发配置 (目前仅支持非动态增减情况) */}
106
103
  {renderContent()}
104
+ {/* 如若再某个model后需添加按钮等场景(例如地图的选址按钮 */}
107
105
  {$scopedSlots[`${slotsName}Handle`] && $scopedSlots[`${slotsName}Handle`](formItemAttrs)}
108
106
  </FormModelItem>
109
107
  )
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "web-component-gallery",
3
- "version": "2.0.22",
3
+ "version": "2.0.24",
4
4
  "description": "基础vue、antdvue、less实现的私有组件库",
5
5
  "main": "dist/index.umd.js",
6
6
  "files": [