yj-kikimore 0.1.0

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.
@@ -0,0 +1,480 @@
1
+ <template>
2
+ <el-select
3
+ v-bind="ElSelectProps"
4
+ v-model="innerValue"
5
+ v-on="Listeners"
6
+ @visible-change="onVisibleChange"
7
+ >
8
+ <template v-if="isGrouped">
9
+ <component
10
+ v-if="isGlobalSlot(Slots['option-prepend'])"
11
+ :is="Slots['option-prepend']()"
12
+ />
13
+ <slot
14
+ v-else
15
+ name="option-prepend"
16
+ >
17
+ <el-checkbox
18
+ v-if="innerShowSelectAllCheckbox"
19
+ v-model="allSelected"
20
+ :indeterminate="indeterminate"
21
+ style="padding: 10px 20px;"
22
+ @change="selectAll"
23
+ >
24
+ {{ SelectAllCheckboxLabel }}
25
+ </el-checkbox>
26
+ </slot>
27
+ <el-option-group
28
+ v-for="(group, groupIndex) of innerOptions"
29
+ :key="optionGroupPropsList[groupIndex].key"
30
+ :label="optionGroupPropsList[groupIndex].label"
31
+ :disabled="optionGroupPropsList[groupIndex].disabled"
32
+ >
33
+ <component
34
+ v-if="isGlobalSlot(Slots['group-prepend'])"
35
+ :is="Slots['group-prepend']()"
36
+ />
37
+ <slot
38
+ v-else
39
+ name="group-prepend"
40
+ />
41
+ <el-option
42
+ v-for="(option, optionIndex) of optionGroupPropsList[groupIndex].options"
43
+ :key="optionGroupPropsList[groupIndex].optionPropsList[optionIndex].key"
44
+ :label="optionGroupPropsList[groupIndex].optionPropsList[optionIndex].label"
45
+ :value="optionGroupPropsList[groupIndex].optionPropsList[optionIndex].value"
46
+ :disabled="optionGroupPropsList[groupIndex].optionPropsList[optionIndex].disabled"
47
+ >
48
+ <component
49
+ v-if="isGlobalSlot(Slots['default'])"
50
+ :is="Slots['default']({ option, index: optionIndex })"
51
+ />
52
+ <slot
53
+ v-else
54
+ :option="option"
55
+ :index="optionIndex"
56
+ >
57
+ {{ optionGroupPropsList[groupIndex].optionPropsList[optionIndex].label }}
58
+ </slot>
59
+ </el-option>
60
+ <component
61
+ v-if="isGlobalSlot(Slots['group-append'])"
62
+ :is="Slots['group-append']()"
63
+ />
64
+ <slot
65
+ v-else
66
+ name="group-append"
67
+ />
68
+ </el-option-group>
69
+ <component
70
+ v-if="isGlobalSlot(Slots['option-append'])"
71
+ :is="Slots['option-append']()"
72
+ />
73
+ <slot
74
+ v-else
75
+ name="option-append"
76
+ />
77
+ </template>
78
+
79
+ <template v-else>
80
+ <component
81
+ v-if="isGlobalSlot(Slots['option-prepend'])"
82
+ :is="Slots['option-prepend']()"
83
+ />
84
+ <slot
85
+ v-else
86
+ name="option-prepend"
87
+ >
88
+ <el-checkbox
89
+ v-if="innerShowSelectAllCheckbox"
90
+ v-model="allSelected"
91
+ :indeterminate="indeterminate"
92
+ style="padding: 10px 20px;"
93
+ @change="selectAll"
94
+ >
95
+ {{ SelectAllCheckboxLabel }}
96
+ </el-checkbox>
97
+ </slot>
98
+ <el-option
99
+ v-for="(v, i) of innerOptions"
100
+ :key="optionPropsList[i].key"
101
+ :label="optionPropsList[i].label"
102
+ :value="optionPropsList[i].value"
103
+ :disabled="optionPropsList[i].disabled"
104
+ >
105
+ <component
106
+ v-if="isGlobalSlot(Slots['default'])"
107
+ :is="Slots['default']({ option: v, index: i })"
108
+ />
109
+ <slot
110
+ v-else
111
+ :option="v"
112
+ :index="i"
113
+ >
114
+ {{ optionPropsList[i].label }}
115
+ </slot>
116
+ </el-option>
117
+ <component
118
+ v-if="isGlobalSlot(Slots['option-append'])"
119
+ :is="Slots['option-append']()"
120
+ />
121
+ <slot
122
+ v-else
123
+ name="option-append"
124
+ />
125
+ </template>
126
+
127
+ <template #prefix>
128
+ <component
129
+ v-if="isGlobalSlot(Slots['prefix'])"
130
+ :is="Slots['prefix']()"
131
+ />
132
+ <slot
133
+ v-else
134
+ name="prefix"
135
+ />
136
+ </template>
137
+
138
+ <template #empty>
139
+ <component
140
+ v-if="isGlobalSlot(Slots['empty'])"
141
+ :is="Slots['empty']()"
142
+ />
143
+ <slot
144
+ v-else
145
+ name="empty"
146
+ />
147
+ </template>
148
+ </el-select>
149
+ </template>
150
+
151
+ <script>
152
+ import { isVue3 } from 'vue-demi'
153
+ import { conclude, resolveConfig } from 'vue-global-config'
154
+ import { cloneDeep, isPlainObject } from 'lodash-es'
155
+ import { getListeners, isEmpty, isObject, notEmpty, unwrap, isGlobalSlot } from '../utils'
156
+
157
+ const globalProps = {}
158
+ const globalAttrs = {}
159
+ const globalListeners = {}
160
+ const globalSlots = {}
161
+
162
+ const model = {
163
+ prop: isVue3 ? 'modelValue' : 'value',
164
+ event: isVue3 ? 'update:modelValue' : 'input',
165
+ }
166
+
167
+ const boolProps = [
168
+ 'searchImmediately',
169
+ 'showSelectAllCheckbox',
170
+ ]
171
+
172
+ export default {
173
+ name: 'KiSelect',
174
+ install(app, options = {}) {
175
+ const { props, attrs, listeners, slots } = resolveConfig(options, this.props)
176
+ Object.assign(globalProps, props)
177
+ Object.assign(globalAttrs, attrs)
178
+ Object.assign(globalListeners, listeners)
179
+ Object.assign(globalSlots, slots)
180
+ app.component(this.name, this)
181
+ },
182
+ props: {
183
+ [model.prop]: {},
184
+ label: [String, Array],
185
+ options: Array,
186
+ props: {},
187
+ search: {},
188
+ selectAllCheckboxLabel: {},
189
+ ...Object.fromEntries(Array.from(boolProps, boolProp => [boolProp, {
190
+ type: Boolean,
191
+ default: undefined,
192
+ }])),
193
+ },
194
+ emits: [model.event, 'update:options', 'update:label'],
195
+ data() {
196
+ return {
197
+ innerValue: this[model.prop],
198
+ initialValue: undefined,
199
+ loading: undefined,
200
+ // 在组件内部维护一份 innerOptions 的目的:search 时可以不绑定 options
201
+ innerOptions: [],
202
+ optionGroupPropsList: [],
203
+ optionPropsList: [],
204
+ allSelected: false,
205
+ indeterminate: false,
206
+ previousQuery: null,
207
+ }
208
+ },
209
+ computed: {
210
+ Listeners() {
211
+ return getListeners.call(this, globalListeners)
212
+ },
213
+ Slots() {
214
+ return conclude([isVue3 ? this.$slots : this.$scopedSlots, globalSlots])
215
+ },
216
+ ShowSelectAllCheckbox() {
217
+ return conclude([this.showSelectAllCheckbox, globalProps.showSelectAllCheckbox, true], {
218
+ type: Boolean,
219
+ })
220
+ },
221
+ innerShowSelectAllCheckbox() {
222
+ return this.ShowSelectAllCheckbox && this.isMultiple && this.innerOptions.length > 1
223
+ },
224
+ SelectAllCheckboxLabel() {
225
+ return conclude([this.selectAllCheckboxLabel, globalProps.selectAllCheckboxLabel, 'Select All'], {
226
+ type: String,
227
+ required: true,
228
+ })
229
+ },
230
+ ElSelectProps() {
231
+ const remote = Boolean(this.Search)
232
+
233
+ return conclude([this.$attrs, globalAttrs, {
234
+ ref: 'elSelectRef',
235
+ clearable: true,
236
+ filterable: true,
237
+ remote,
238
+ reserveKeyword: true,
239
+ remoteMethod: remote ? this.remoteMethod : undefined,
240
+ valueKey: (this.Props.value && typeof this.Props.value === 'string') ? this.Props.value : undefined,
241
+ loading: this.loading,
242
+ }], {
243
+ type: Object,
244
+ camelizeObjectKeys: true,
245
+ })
246
+ },
247
+ Props() {
248
+ return conclude([this.props, globalProps.props, {}], {
249
+ type: Object,
250
+ })
251
+ },
252
+ Search() {
253
+ return conclude([this.search, globalProps.search], {
254
+ type: Function,
255
+ })
256
+ },
257
+ SearchImmediately() {
258
+ return conclude([this.searchImmediately, globalProps.searchImmediately, true], {
259
+ type: Boolean,
260
+ })
261
+ },
262
+ isMultiple() {
263
+ return [true, ''].includes(this.ElSelectProps.multiple)
264
+ },
265
+ isGrouped() {
266
+ return notEmpty(this.Props.groupOptions)
267
+ },
268
+ },
269
+ watch: {
270
+ options: {
271
+ immediate: true,
272
+ handler(newOptions) {
273
+ this.setInnerOptions(newOptions)
274
+ },
275
+ },
276
+ [model.prop]: {
277
+ handler(newValue) {
278
+ this.innerValue = newValue
279
+ this.updateSelectAllStatus()
280
+ this.updateLabel()
281
+ },
282
+ },
283
+ innerValue: {
284
+ handler(newInnerValue) {
285
+ // 清空时
286
+ if (isEmpty(newInnerValue)) {
287
+ this.remoteMethod()
288
+ }
289
+ this.$emit(model.event, newInnerValue)
290
+ },
291
+ },
292
+ },
293
+ created() {
294
+ if (this.SearchImmediately) {
295
+ this.remoteMethod()
296
+ }
297
+ },
298
+ mounted() {
299
+ this.initialValue = cloneDeep(this[model.prop])
300
+ },
301
+ methods: {
302
+ isGlobalSlot,
303
+ // 不写在 watch 里的原因:innerOptions、optionPropsList、optionGroupPropsList 的长度必须保持同步
304
+ setInnerOptions(newOptions) {
305
+ // 校验类型
306
+ conclude([newOptions], { type: Array })
307
+
308
+ // 必须先于 optionPropsList、optionGroupPropsList 执行,否则会影响 getValue 等的判断
309
+ this.innerOptions = newOptions || []
310
+
311
+ if (this.isGrouped) {
312
+ this.optionGroupPropsList = Array.from(newOptions || [], (group, groupIndex) => {
313
+ const options = this.getGroupOptions(group, groupIndex)
314
+ return {
315
+ key: this.getKey(group),
316
+ label: this.getGroupLabel(group, groupIndex),
317
+ disabled: this.isGroupDisabled(group, groupIndex),
318
+ options,
319
+ optionPropsList: Array.from(options || [], v => ({
320
+ key: this.getKey(v),
321
+ value: this.getValue(v),
322
+ label: this.getLabel(v),
323
+ disabled: this.isDisabled(v),
324
+ })),
325
+ }
326
+ })
327
+ } else {
328
+ this.optionPropsList = Array.from(newOptions || [], v => ({
329
+ key: this.getKey(v),
330
+ value: this.getValue(v),
331
+ label: this.getLabel(v),
332
+ disabled: this.isDisabled(v),
333
+ }))
334
+ }
335
+
336
+ this.updateSelectAllStatus()
337
+ this.updateLabel()
338
+ this.$emit('update:options', newOptions)
339
+ },
340
+ remoteMethod(e) {
341
+ if (!this.Search) {
342
+ return
343
+ }
344
+ this.loading = true
345
+ this.previousQuery = e
346
+ const res = this.Search(e)
347
+ if (res instanceof Promise) {
348
+ res.then((res) => {
349
+ this.setInnerOptions(res)
350
+ }).finally(() => {
351
+ this.loading = false
352
+ })
353
+ } else {
354
+ this.setInnerOptions(res)
355
+ this.loading = false
356
+ }
357
+ },
358
+ // value 没匹配上选项时,el-select 默认显示 value,改为显示 label
359
+ updateLabel() {
360
+ this.$nextTick(() => {
361
+ if (this.isMultiple) {
362
+ const label = []
363
+ this.$refs[this.ElSelectProps.ref].selected.forEach((v) => {
364
+ if (!v.currentLabel) {
365
+ v.currentLabel = this.getLabel(v.value)
366
+ }
367
+ label.push(v.currentLabel)
368
+ })
369
+ this.$emit('update:label', label)
370
+ } else {
371
+ if (!this.$refs[this.ElSelectProps.ref].selectedLabel) {
372
+ this.$refs[this.ElSelectProps.ref].selectedLabel = this.getLabel(this.innerValue)
373
+ }
374
+ this.$emit('update:label', this.$refs[this.ElSelectProps.ref].selectedLabel)
375
+ }
376
+ })
377
+ },
378
+ selectAll(checked) {
379
+ const innerValue = cloneDeep(this.innerValue)
380
+ // 便于高效判断一个选项是否被选中
381
+ const valueKeyToIndex = Object.fromEntries(Array.from(innerValue, (item, i) =>
382
+ [isObject(item) ? item[this.ElSelectProps.valueKey] : item, i]))
383
+
384
+ const callback = (disabled, value, key) => {
385
+ const i = valueKeyToIndex[isObject(value) ? key : value]
386
+ // 全选时,选项处于启用状态且没有被选中,选中它
387
+ if (checked) {
388
+ if (!disabled && i === undefined) {
389
+ innerValue.push(value)
390
+ }
391
+ }
392
+ // 全不选时,选项被选中了,取消选中它
393
+ else if (i !== undefined) {
394
+ innerValue[i] = undefined
395
+ }
396
+ }
397
+
398
+ if (this.isGrouped) {
399
+ this.optionGroupPropsList.forEach(({ disabled, optionPropsList }) => {
400
+ if (!disabled) {
401
+ optionPropsList?.forEach(({ disabled, value, key }) => callback(disabled, value, key))
402
+ }
403
+ })
404
+ } else {
405
+ this.optionPropsList.forEach(({ disabled, value, key }) => callback(disabled, value, key))
406
+ }
407
+
408
+ this.innerValue = innerValue.filter(v => v !== undefined)
409
+ },
410
+ // 更新全选按钮的勾选状态
411
+ updateSelectAllStatus() {
412
+ if (this.innerShowSelectAllCheckbox) {
413
+ if (this.innerValue?.length) {
414
+ // 便于高效判断一个选项是否被选中
415
+ const valueToIndex = Object.fromEntries(Array.from(this.innerValue, (item, i) =>
416
+ [isObject(item) ? item[this.ElSelectProps.valueKey] : item, i]))
417
+ let matchCount = 0
418
+ let optionsCount = 0
419
+
420
+ const callback = (value, key) => {
421
+ if (valueToIndex[isObject(value) ? key : value] !== undefined) {
422
+ matchCount++
423
+ }
424
+ optionsCount++
425
+ }
426
+
427
+ if (this.isGrouped) {
428
+ this.optionGroupPropsList.forEach(({ optionPropsList }) => {
429
+ optionPropsList?.forEach(({ value, key }) => callback(value, key))
430
+ })
431
+ } else {
432
+ this.optionPropsList.forEach(({ value, key }) => callback(value, key))
433
+ }
434
+ this.indeterminate = matchCount > 0 && matchCount < optionsCount
435
+ this.allSelected = matchCount > 0 && matchCount === optionsCount
436
+ } else {
437
+ this.indeterminate = false
438
+ this.allSelected = false
439
+ }
440
+ }
441
+ },
442
+ // 下拉框隐藏时,如果没有选中,el-select 会清空搜索关键字,此时需要恢复 options
443
+ onVisibleChange(isVisible) {
444
+ if (!isVisible) {
445
+ this.updateLabel()
446
+ if (isEmpty(this.innerValue) && this.previousQuery) {
447
+ // 加延迟的原因:在下拉框隐藏动画结束后再恢复
448
+ setTimeout(() => {
449
+ this.remoteMethod()
450
+ }, 100)
451
+ }
452
+ }
453
+ },
454
+ getKey(v) {
455
+ if (isObject(v) && !this.Props.value && !this.ElSelectProps.valueKey) {
456
+ throw new Error('Either props.value or valueKey should be specified when option value is of type object.')
457
+ }
458
+ return unwrap(v, this.ElSelectProps.valueKey)
459
+ },
460
+ getValue(v) {
461
+ return unwrap(v, this.Props.value)
462
+ },
463
+ getLabel(v) {
464
+ return unwrap(v, this.Props.label)
465
+ },
466
+ isDisabled(v) {
467
+ return this.Props.disabled ? unwrap(v, this.Props.disabled) : undefined
468
+ },
469
+ getGroupLabel(v) {
470
+ return this.Props.groupLabel ? unwrap(v, this.Props.groupLabel) : undefined
471
+ },
472
+ getGroupOptions(v) {
473
+ return this.Props.groupOptions ? unwrap(v, this.Props.groupOptions) : undefined
474
+ },
475
+ isGroupDisabled(v) {
476
+ return this.Props.groupDisabled ? unwrap(v, this.Props.groupDisabled) : undefined
477
+ },
478
+ },
479
+ }
480
+ </script>
package/src/index.ts ADDED
@@ -0,0 +1,6 @@
1
+ import KiFormDialog from './FormDialog/index.vue'
2
+ import KiPopButton from './PopButton/index.vue'
3
+ import KiPopSwitch from './PopSwitch/index.vue'
4
+ import KiSelect from './Select/index.vue'
5
+
6
+ export { KiFormDialog, KiPopButton, KiPopSwitch, KiSelect }
@@ -0,0 +1,2 @@
1
+ declare const _default: (element: any) => boolean;
2
+ export default _default;
@@ -0,0 +1,2 @@
1
+ declare const _default: (selectors?: string | Element | NodeList, container?: Window & typeof globalThis) => void;
2
+ export default _default;
@@ -0,0 +1 @@
1
+ export declare function getCharCount(text: string): number;
@@ -0,0 +1,5 @@
1
+ import { default as KiFormDialog } from './FormDialog/index.vue';
2
+ import { default as KiPopButton } from './PopButton/index.vue';
3
+ import { default as KiPopSwitch } from './PopSwitch/index.vue';
4
+ import { default as KiSelect } from './Select/index.vue';
5
+ export { KiFormDialog, KiPopButton, KiPopSwitch, KiSelect };
@@ -0,0 +1,8 @@
1
+ import { ComponentPublicInstance } from 'vue-demi';
2
+ export declare function getListeners(this: ComponentPublicInstance, globalListeners: Record<string, any>): any;
3
+ export declare function isGlobalSlot(slot: any): any;
4
+ export declare function isEmpty(value: any): boolean;
5
+ export declare function notEmpty(value: any): boolean;
6
+ export declare function isObject(value: any): boolean;
7
+ export declare function unwrap<V = any>(value: V, path?: string | ((value: V) => any) | symbol): any;
8
+ export declare function wrap(value: any, url: string): any;
package/src/utils.ts ADDED
@@ -0,0 +1,80 @@
1
+ import { conclude, getLocalListeners } from 'vue-global-config'
2
+ import { at, isPlainObject } from 'lodash-es'
3
+ import { isVue3 } from 'vue-demi'
4
+ import type { ComponentPublicInstance } from 'vue-demi'
5
+
6
+ export function getListeners(this: ComponentPublicInstance, globalListeners: Record<string, any>) {
7
+ if (isVue3) {
8
+ return {}
9
+ }
10
+
11
+ for (const k in globalListeners) {
12
+ globalListeners[k] = globalListeners[k].bind(this)
13
+ }
14
+
15
+ return conclude([getLocalListeners(this.$listeners)], {
16
+ default: globalListeners,
17
+ mergeFunction:
18
+ (localEventListener: Function, globalEventListener: Function) => (...args: any) => {
19
+ localEventListener(...args)
20
+ globalEventListener(...args)
21
+ },
22
+ })
23
+ }
24
+
25
+ export function isGlobalSlot(slot: any) {
26
+ return typeof slot === 'function' && slot.name.startsWith('#')
27
+ }
28
+
29
+ export function isEmpty(value: any): boolean {
30
+ return {
31
+ object: () =>
32
+ value === null ||
33
+ (Array.isArray(value) && value.length === 0) ||
34
+ (isPlainObject(value) && Object.getOwnPropertyNames(value).length === 0),
35
+ number: () => Number.isNaN(value),
36
+ string: () => value === '',
37
+ undefined: () => true,
38
+ boolean: () => value === false,
39
+ symbol: () => false,
40
+ bigint: () => false,
41
+ function: () => true,
42
+ }[typeof value]()
43
+ }
44
+
45
+ export function notEmpty(value: any): boolean {
46
+ return !isEmpty(value)
47
+ }
48
+
49
+ export function isObject(value: any) {
50
+ return typeof value === 'object' && value !== null && !Array.isArray(value)
51
+ }
52
+
53
+ // value 是某个 option,path 是 Props.xxx
54
+ export function unwrap<V = any>(value: V, path?: string | ((value: V) => any) | symbol): any {
55
+ if (!(value && path)) {
56
+ return value
57
+ }
58
+ switch (typeof path) {
59
+ case 'string':
60
+ // paths 为 undefined 或 '' 时结果为 undefined
61
+ return at(value, path)[0]
62
+ case 'function':
63
+ return path(value)
64
+ case 'symbol':
65
+ if (isPlainObject(value)) {
66
+ return value[path as keyof typeof value]
67
+ }
68
+ }
69
+ }
70
+
71
+ // 将 value 包装为符合 files 要求的格式
72
+ export function wrap(value: any, url: string) {
73
+ if (url && typeof url === 'string') {
74
+ if (isObject(value)) {
75
+ value.url = url
76
+ return value
77
+ }
78
+ return { url }
79
+ }
80
+ }