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.
- package/dist/js.umd.js +1 -1
- package/lib/form-comp/ASelectCustom.vue +148 -76
- package/lib/model/Model.js +39 -32
- package/lib/model/utils/render.js +7 -9
- package/package.json +1 -1
|
@@ -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-
|
|
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="
|
|
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
|
|
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
|
-
|
|
81
|
-
//
|
|
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
|
-
|
|
98
|
-
|
|
99
|
-
|
|
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
|
-
|
|
129
|
+
searchValue: ''
|
|
107
130
|
}
|
|
108
131
|
},
|
|
132
|
+
|
|
109
133
|
computed: {
|
|
110
|
-
|
|
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.
|
|
150
|
+
return this.innerOptions.length < this.pagination.total
|
|
127
151
|
},
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
157
|
-
this.
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
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
|
-
|
|
236
|
+
|
|
237
|
+
// 安全的自定义label处理
|
|
172
238
|
customLabelHandler(item) {
|
|
239
|
+
if (!this.customLabel) return item[this.labelKey]
|
|
240
|
+
|
|
173
241
|
try {
|
|
174
|
-
//
|
|
175
|
-
const
|
|
176
|
-
|
|
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
|
-
|
|
184
|
-
|
|
185
|
-
const selectOptions =
|
|
186
|
-
|
|
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.
|
|
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
|
-
|
|
280
|
+
|
|
281
|
+
// 加载更多数据
|
|
282
|
+
async loadMoreData() {
|
|
198
283
|
if (this.isLoadingMore || !this.hasMore) return
|
|
199
284
|
|
|
200
|
-
this.
|
|
201
|
-
this.
|
|
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
|
}
|
package/lib/model/Model.js
CHANGED
|
@@ -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
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
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 ?
|
|
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
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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
|
)
|