cbvirtua 1.0.25 → 1.0.27

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,603 @@
1
+ <template>
2
+ <div class="autocomplete">
3
+ <div class="autocomplete__box" :class="{'autocomplete__searching' : showResults}">
4
+
5
+ <img v-if="!isLoading" class="autocomplete__icon" src="../assets/search.svg">
6
+ <img v-else class="autocomplete__icon animate-spin" src="../assets/loading.svg">
7
+
8
+ <div class="autocomplete__inputs">
9
+ <input
10
+ v-model="display"
11
+ :placeholder="placeholder"
12
+ :disabled="disableInput"
13
+ :maxlength="maxlength"
14
+ :class="inputClass"
15
+ @click="search"
16
+ @input="search"
17
+ @keydown.enter="enter"
18
+ @keydown.tab="close"
19
+ @keydown.up="up"
20
+ @keydown.down="down"
21
+ @keydown.esc="close"
22
+ @focus="focus"
23
+ @blur="blur"
24
+ type="text"
25
+ autocomplete="off">
26
+ <input :name="name" type="hidden" :value="value">
27
+ </div>
28
+
29
+ <!-- clearButtonIcon -->
30
+ <span v-show="!disableInput && !isEmpty && !isLoading && !hasError" class="autocomplete__icon autocomplete--clear" @click="clear">
31
+ <span v-if="clearButtonIcon" :class="clearButtonIcon"></span>
32
+ <img v-else src="../assets/close.svg">
33
+ </span>
34
+ </div>
35
+
36
+ <ul v-show="showResults" class="autocomplete__results" :style="listStyle">
37
+ <slot name="results">
38
+ <!-- error -->
39
+ <li v-if="hasError" class="autocomplete__results__item autocomplete__results__item--error">{{ error }}</li>
40
+
41
+ <!-- results -->
42
+ <template v-if="!hasError">
43
+ <slot name="firstResult"></slot>
44
+ <li
45
+ v-for="(result, key) in results"
46
+ :key="key"
47
+ @click.prevent="select(result)"
48
+ class="autocomplete__results__item"
49
+ :class="{'autocomplete__selected' : isSelected(key) }"
50
+ v-html="formatDisplay(result)">
51
+ </li>
52
+ <slot name="lastResult"></slot>
53
+ </template>
54
+
55
+ <!-- no results -->
56
+ <li v-if="noResultMessage" class="autocomplete__results__item autocomplete__no-results">
57
+ <slot name="noResults">No Results.</slot>
58
+ </li>
59
+ </slot>
60
+ </ul>
61
+ </div>
62
+ </template>
63
+
64
+ <script type="text/babel">
65
+ import debounce from 'lodash/debounce'
66
+ export default {
67
+ props: {
68
+ /**
69
+ * Data source for the results
70
+ * `String` is a url, typed input will be appended
71
+ * `Function` received typed input, and must return a string; to be used as a url
72
+ * `Array` and `Object` (see `results-property`) are used directly
73
+ */
74
+ source: {
75
+ type: [String, Function, Array, Object],
76
+ required: true
77
+ },
78
+ /**
79
+ * http method
80
+ */
81
+ method: {
82
+ type: String,
83
+ default: 'get'
84
+ },
85
+ /**
86
+ * Input placeholder
87
+ */
88
+ placeholder: {
89
+ default: 'Search'
90
+ },
91
+ /**
92
+ * Preset starting value
93
+ */
94
+ initialValue: {
95
+ type: [String, Number]
96
+ },
97
+ /**
98
+ * Preset starting display value
99
+ */
100
+ initialDisplay: {
101
+ type: String
102
+ },
103
+ /**
104
+ * CSS class for the surrounding input div
105
+ */
106
+ inputClass: {
107
+ type: [String, Object]
108
+ },
109
+ /**
110
+ * To disable the input
111
+ */
112
+ disableInput: {
113
+ type: Boolean
114
+ },
115
+ /**
116
+ * name property of the input holding the selected value
117
+ */
118
+ name: {
119
+ type: String
120
+ },
121
+ /**
122
+ * api - property of results array
123
+ */
124
+ resultsProperty: {
125
+ type: String
126
+ },
127
+ /**
128
+ * Results property used as the value
129
+ */
130
+ resultsValue: {
131
+ type: String,
132
+ default: 'id'
133
+ },
134
+ /**
135
+ * Results property used as the display
136
+ */
137
+ resultsDisplay: {
138
+ type: [String, Function],
139
+ default: 'name'
140
+ },
141
+
142
+ /**
143
+ * Callback to format the server data
144
+ */
145
+ resultsFormatter: {
146
+ type: Function
147
+ },
148
+
149
+ /**
150
+ * Whether to show the no results message
151
+ */
152
+ showNoResults: {
153
+ type: Boolean,
154
+ default: true
155
+ },
156
+
157
+ /**
158
+ * Additional request headers
159
+ */
160
+ requestHeaders: {
161
+ type: Object
162
+ },
163
+
164
+ /**
165
+ * Credentials: same-origin, include, *omit
166
+ */
167
+ credentials: {
168
+ type: String
169
+ },
170
+
171
+ /**
172
+ * Optional clear button icon class
173
+ */
174
+ clearButtonIcon: {
175
+ type: String
176
+ },
177
+
178
+ /**
179
+ * Optional max input length
180
+ */
181
+ maxlength: {
182
+ type: Number
183
+ }
184
+ },
185
+ data () {
186
+ return {
187
+ value: null,
188
+ display: null,
189
+ results: null,
190
+ selectedIndex: null,
191
+ loading: false,
192
+ isFocussed: false,
193
+ error: null,
194
+ selectedId: null,
195
+ selectedDisplay: null,
196
+ eventListener: false
197
+ }
198
+ },
199
+ computed: {
200
+ showResults () {
201
+ return Array.isArray(this.results) || this.hasError
202
+ },
203
+ noResults () {
204
+ return Array.isArray(this.results) && this.results.length === 0
205
+ },
206
+ noResultMessage () {
207
+ return this.noResults &&
208
+ !this.isLoading &&
209
+ this.isFocussed &&
210
+ !this.hasError &&
211
+ this.showNoResults
212
+ },
213
+ isEmpty () {
214
+ return !this.display
215
+ },
216
+ isLoading () {
217
+ return this.loading === true
218
+ },
219
+ hasError () {
220
+ return this.error !== null
221
+ },
222
+ listStyle () {
223
+ if (this.isLoading) {
224
+ return {
225
+ color: '#ccc'
226
+ }
227
+ }
228
+ }
229
+ },
230
+ methods: {
231
+ /**
232
+ * Search wrapper method
233
+ */
234
+ search () {
235
+ this.selectedIndex = null
236
+ switch (true) {
237
+ case typeof this.source === 'string':
238
+ // No resource search with no input
239
+ if (!this.display || this.display.length < 1) {
240
+ return
241
+ }
242
+
243
+ return this.resourceSearch(this.source + this.display)
244
+ case typeof this.source === 'function':
245
+ // No resource search with no input
246
+ if (!this.display || this.display.length < 1) {
247
+ return
248
+ }
249
+ return this.resourceSearch(this.source(this.display))
250
+ case Array.isArray(this.source):
251
+ return this.arrayLikeSearch()
252
+ default:
253
+ throw new TypeError()
254
+ }
255
+ },
256
+
257
+ /**
258
+ * Debounce the typed search query before making http requests
259
+ * @param {String} url
260
+ */
261
+ resourceSearch: debounce(function (url) {
262
+ if (!this.display) {
263
+ this.results = []
264
+ return
265
+ }
266
+ this.loading = true
267
+ this.setEventListener()
268
+ this.request(url)
269
+ }, 200),
270
+
271
+ /**
272
+ * Make an http request for results
273
+ * @param {String} url
274
+ */
275
+ request (url) {
276
+ let promise = fetch(url, {
277
+ method: this.method,
278
+ credentials: this.getCredentials(),
279
+ headers: this.getHeaders()
280
+ })
281
+
282
+ return promise
283
+ .then(response => {
284
+ if (response.ok) {
285
+ this.error = null
286
+ return response.json()
287
+ }
288
+ throw new Error('Network response was not ok.')
289
+ })
290
+ .then(response => {
291
+ this.results = this.setResults(response)
292
+ this.emitRequestResultEvent()
293
+ this.loading = false
294
+ })
295
+ .catch(error => {
296
+ this.error = error.message
297
+ this.loading = false
298
+ })
299
+ },
300
+
301
+ /**
302
+ * Set some default headers and apply user supplied headers
303
+ */
304
+ getHeaders () {
305
+ const headers = {
306
+ 'Accept': 'application/json, text/plain, */*'
307
+ }
308
+ if (this.requestHeaders) {
309
+ for (var prop in this.requestHeaders) {
310
+ headers[prop] = this.requestHeaders[prop]
311
+ }
312
+ }
313
+ return new Headers(headers)
314
+ },
315
+
316
+ /**
317
+ * Set default credentials and apply user supplied value
318
+ */
319
+ getCredentials () {
320
+ let credentials = 'same-origin'
321
+ if (this.credentials) {
322
+ credentials = this.credentials
323
+ }
324
+ return credentials
325
+ },
326
+
327
+ /**
328
+ * Set results property from api response
329
+ * @param {Object|Array} response
330
+ * @return {Array}
331
+ */
332
+ setResults (response) {
333
+ if (this.resultsFormatter) {
334
+ return this.resultsFormatter(response)
335
+ }
336
+ if (this.resultsProperty && response[this.resultsProperty]) {
337
+ return response[this.resultsProperty]
338
+ }
339
+ if (Array.isArray(response)) {
340
+ return response
341
+ }
342
+ return []
343
+ },
344
+
345
+ /**
346
+ * Emit an event based on the request results
347
+ */
348
+ emitRequestResultEvent () {
349
+ if (this.results.length === 0) {
350
+ this.$emit('noResults', {query: this.display})
351
+ } else {
352
+ this.$emit('results', {results: this.results})
353
+ }
354
+ },
355
+
356
+ /**
357
+ * Search in results passed via an array
358
+ */
359
+ arrayLikeSearch () {
360
+ this.setEventListener()
361
+ if (!this.display) {
362
+ this.results = this.source
363
+ this.$emit('results', {results: this.results})
364
+ this.loading = false
365
+ return true
366
+ }
367
+ this.results = this.source.filter((item) => {
368
+ return this.formatDisplay(item).toLowerCase().includes(this.display.toLowerCase())
369
+ })
370
+ this.$emit('results', {results: this.results})
371
+ this.loading = false
372
+ },
373
+
374
+ /**
375
+ * Select a result
376
+ * @param {Object}
377
+ */
378
+ select (obj) {
379
+ if (!obj) {
380
+ return
381
+ }
382
+ this.value = (this.resultsValue && obj[this.resultsValue]) ? obj[this.resultsValue] : obj.id
383
+ this.display = this.formatDisplay(obj)
384
+ this.selectedDisplay = this.display
385
+ this.$emit('selected', {
386
+ value: this.value,
387
+ display: this.display,
388
+ selectedObject: obj
389
+ })
390
+ this.$emit('input', this.value)
391
+ this.close()
392
+ },
393
+
394
+ /**
395
+ * @param {Object} obj
396
+ * @return {String}
397
+ */
398
+ formatDisplay (obj) {
399
+ switch (typeof this.resultsDisplay) {
400
+ case 'function':
401
+ return this.resultsDisplay(obj)
402
+ case 'string':
403
+ if (!obj[this.resultsDisplay]) {
404
+ throw new Error(`"${this.resultsDisplay}" property expected on result but is not defined.`)
405
+ }
406
+ return obj[this.resultsDisplay]
407
+ default:
408
+ throw new TypeError()
409
+ }
410
+ },
411
+
412
+ /**
413
+ * Register the component as focussed
414
+ */
415
+ focus () {
416
+ this.isFocussed = true
417
+ },
418
+
419
+ /**
420
+ * Remove the focussed value
421
+ */
422
+ blur () {
423
+ this.isFocussed = false
424
+ },
425
+
426
+ /**
427
+ * Is this item selected?
428
+ * @param {Object}
429
+ * @return {Boolean}
430
+ */
431
+ isSelected (key) {
432
+ return key === this.selectedIndex
433
+ },
434
+
435
+ /**
436
+ * Focus on the previous results item
437
+ */
438
+ up () {
439
+ if (this.selectedIndex === null) {
440
+ this.selectedIndex = this.results.length - 1
441
+ return
442
+ }
443
+ this.selectedIndex = (this.selectedIndex === 0) ? this.results.length - 1 : this.selectedIndex - 1
444
+ },
445
+
446
+ /**
447
+ * Focus on the next results item
448
+ */
449
+ down () {
450
+ if (this.selectedIndex === null) {
451
+ this.selectedIndex = 0
452
+ return
453
+ }
454
+ this.selectedIndex = (this.selectedIndex === this.results.length - 1) ? 0 : this.selectedIndex + 1
455
+ },
456
+
457
+ /**
458
+ * Select an item via the keyboard
459
+ */
460
+ enter () {
461
+ if (this.selectedIndex === null) {
462
+ this.$emit('nothingSelected', this.display)
463
+ return
464
+ }
465
+ this.select(this.results[this.selectedIndex])
466
+ this.$emit('enter', this.display)
467
+ },
468
+
469
+ /**
470
+ * Clear all values, results and errors
471
+ */
472
+ clear () {
473
+ this.display = null
474
+ this.value = null
475
+ this.results = null
476
+ this.error = null
477
+ this.$emit('clear')
478
+ },
479
+
480
+ /**
481
+ * Close the results list. If nothing was selected clear the search
482
+ */
483
+ close () {
484
+ if (!this.value || !this.selectedDisplay) {
485
+ this.clear()
486
+ }
487
+ if (this.selectedDisplay !== this.display && this.value) {
488
+ this.display = this.selectedDisplay
489
+ }
490
+
491
+ this.results = null
492
+ this.error = null
493
+ this.removeEventListener()
494
+ this.$emit('close')
495
+ },
496
+
497
+ /**
498
+ * Add event listener for clicks outside the results
499
+ */
500
+ setEventListener () {
501
+ if (this.eventListener) {
502
+ return false
503
+ }
504
+ this.eventListener = true
505
+ document.addEventListener('click', this.clickOutsideListener, true)
506
+ return true
507
+ },
508
+
509
+ /**
510
+ * Remove the click event listener
511
+ */
512
+ removeEventListener () {
513
+ this.eventListener = false
514
+ document.removeEventListener('click', this.clickOutsideListener, true)
515
+ },
516
+
517
+ /**
518
+ * Method invoked by the event listener
519
+ */
520
+ clickOutsideListener (event) {
521
+ if (this.$el && !this.$el.contains(event.target)) {
522
+ this.close()
523
+ }
524
+ }
525
+ },
526
+ mounted () {
527
+ this.value = this.initialValue
528
+ this.display = this.initialDisplay
529
+ this.selectedDisplay = this.initialDisplay
530
+ }
531
+ }
532
+ </script>
533
+
534
+ <style lang="stylus">
535
+ .autocomplete
536
+ position relative
537
+ width 100%
538
+ *
539
+ box-sizing border-box
540
+
541
+ .autocomplete__box
542
+ display flex
543
+ align-items center
544
+ background #fff
545
+ border: 1px solid #ccc
546
+ border-radius 3px
547
+ padding 0 5px
548
+
549
+ .autocomplete__searching
550
+ border-radius 3px 3px 0 0
551
+
552
+ .autocomplete__inputs
553
+ flex-grow 1
554
+ padding 0 5px
555
+ input
556
+ width 100%
557
+ border 0
558
+ &:focus
559
+ outline none
560
+
561
+ .autocomplete--clear
562
+ cursor pointer
563
+
564
+ .autocomplete__results
565
+ margin 0
566
+ padding 0
567
+ list-style-type none
568
+ z-index 1000
569
+ position absolute
570
+ max-height 400px
571
+ overflow-y auto
572
+ background white
573
+ width 100%
574
+ border 1px solid #ccc
575
+ border-top 0
576
+ color black
577
+
578
+ .autocomplete__results__item--error
579
+ color red
580
+
581
+ .autocomplete__results__item
582
+ padding 7px 10px
583
+ cursor pointer
584
+ &:hover
585
+ background rgba(0, 180, 255, 0.075)
586
+ &.autocomplete__selected
587
+ background rgba(0, 180, 255, 0.15)
588
+
589
+ .autocomplete__icon
590
+ height 14px
591
+ width 14px
592
+
593
+ .animate-spin
594
+ animation spin 2s infinite linear
595
+
596
+ @keyframes spin
597
+ from
598
+ transform rotate(0deg)
599
+ to
600
+ transform rotate(360deg)
601
+
602
+
603
+ </style>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cbvirtua",
3
- "version": "1.0.25",
3
+ "version": "1.0.27",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -83,3 +83,81 @@ page{
83
83
  width: 120rpx;
84
84
  }
85
85
  ————————————————
86
+
87
+ import { getDicts as getDicDataList } from '@/api/system/dict/data'
88
+
89
+ const state = {
90
+ dictDataList: {},
91
+ requestDictList: new Set()
92
+ }
93
+
94
+ const getters = {
95
+ getDictLabel: (state) => (dicType, dictValue) => {
96
+ const dictList = state.dictDataList[dicType] || []
97
+ if(dictValue && dictValue.includes(",")){
98
+ const arr = val.split(",");
99
+ return arr.map(item => dictList.find(o => String(o.dicItemCode) === String(item))).join(",");
100
+ }
101
+ const dictItem = dictList.find(item => String(item.dicItemCode) === String(dictValue)) || {}
102
+ return dictItem.dicItemName || dictValue
103
+ }
104
+ }
105
+
106
+ const mutations = {
107
+ setDictDataList (state, dictDataList) {
108
+ state.dictDataList = { ...state.dictDataList, ...dictDataList }
109
+ },
110
+ pushRequestDictList (state, dictType) {
111
+ state.requestDictList.add(dictType)
112
+ },
113
+ spliceRequestDictList (state, dictType) {
114
+ state.requestDictList.delete(dictType)
115
+ }
116
+ }
117
+
118
+ const actions = {
119
+ async getDictData ({ commit, state }, dictType) {
120
+ if (Array.isArray(dictType)) {
121
+ // 多个数据字典请求
122
+ const dicTypesRequest = dictType.filter(dictType => !state.dictDataList[dictType])
123
+ if (dicTypesRequest.length === 0) {
124
+ return state.dictDataList
125
+ }
126
+ const { data } = await getDicDataList(dicTypesRequest)
127
+ commit('setDictDataList', data)
128
+ return state.dictDataList
129
+ } else if (typeof dictType === 'string') {
130
+ if (!state.dictDataList[dictType]) {
131
+ if (!state.requestDictList.has(dictType)) {
132
+ commit('pushRequestDictList', dictType)
133
+ const { data } = await getDicDataList(dictType)
134
+ commit('setDictDataList', { [dictType]: data || [] })
135
+ commit('spliceRequestDictList', dictType)
136
+ return data
137
+ }
138
+ } else {
139
+ return state.dictDataList[dictType]
140
+ }
141
+ }
142
+ },
143
+ updateDictData({ commit, state }, dictType) {
144
+ if (!state.requestDictList.has(dictType)) {
145
+ commit('pushRequestDictList', dictType)
146
+ return getDicDataList(dictType).then(({ data }) => {
147
+ commit('setDictDataList', { [dictType]: data || [] })
148
+ return data
149
+ }).finally(() => {
150
+ commit('spliceRequestDictList', dictType)
151
+ })
152
+ }
153
+ }
154
+ }
155
+
156
+ export default {
157
+ namespaced: true,
158
+ state,
159
+ getters,
160
+ mutations,
161
+ actions
162
+ }
163
+