jobdone-shared-files 1.0.42 → 1.0.44

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.
Files changed (39) hide show
  1. package/ModuleInfo/LayoutNav.vue +251 -251
  2. package/ModuleInfo/logo-with-text.svg +22 -22
  3. package/ModuleInfo/navButton.vue +218 -218
  4. package/ProjectManagement/projectNavbar.vue +363 -363
  5. package/README.md +0 -1
  6. package/autocompleteSelect.vue +467 -465
  7. package/common/directives/collapse.js +12 -12
  8. package/common/directives/popovers.js +10 -10
  9. package/common/directives/selectPlaceholder.js +52 -52
  10. package/common/directives/textareaAutoHeight.js +10 -10
  11. package/common/directives/tooltip.js +10 -10
  12. package/common/format.js +26 -26
  13. package/index.js +14 -14
  14. package/lightboxWithOverview.vue +156 -156
  15. package/package.json +19 -19
  16. package/paginate.vue +141 -141
  17. package/style/css/vue-loading-overlay/index.css +40 -40
  18. package/style/scss/Common/Animation.scss +9 -9
  19. package/style/scss/Common/SelectableTable.scss +36 -36
  20. package/style/scss/Common/filepond.scss +31 -31
  21. package/style/scss/Common/thumbnail-group.scss +14 -14
  22. package/style/scss/Layout/LayoutBase.scss +1032 -1032
  23. package/style/scss/Layout/LayoutInnerColumn.scss +263 -263
  24. package/style/scss/Layout/LayoutProject.scss +126 -126
  25. package/style/scss/Layout/LayoutSinglePage.scss +17 -17
  26. package/style/scss/Layout/LayoutTwoColumn.scss +60 -60
  27. package/style/scss/Settings/_Mixins.scss +232 -232
  28. package/style/scss/Settings/_MobileVariables.scss +11 -11
  29. package/style/scss/Settings/_bs-variables-dark.scss +70 -70
  30. package/style/scss/Settings/_bs-variables.scss +1743 -1743
  31. package/style/scss/Settings/_color-mode.scss +122 -122
  32. package/style/scss/Settings/_custom-variables.scss +10 -10
  33. package/tagEditor.vue +249 -249
  34. package/tree.vue +71 -71
  35. package/treeItem.vue +358 -358
  36. package/vueLoadingOverlay.vue +74 -74
  37. package/style/scss/Common/Tree.scss +0 -282
  38. package/treeItemV2.vue +0 -79
  39. package/treeV2.vue +0 -71
@@ -1,466 +1,468 @@
1
- <template>
2
- <div class="autocomplete-select-component-trigger-content" ref="componentContentInput">
3
- <!-- preview input -->
4
- <button type="button" class='form-control autocomplete-select-component-display-selecting' :disabled="disabled"
5
- :class="[triggerClass, previewInput]">{{
6
- selectedPreviewText
7
- }}</button>
8
- <!-- search input -->
9
- <input class='form-control autocomplete-select-component-keyword-filter-input' :class="[triggerClass, searchInput]"
10
- type="text" ref="keywordFilterInput" v-model="keyword"
11
- :placeholder="searchPlaceholder == '' ? selectedPreviewText : searchPlaceholder" maxlength="50"
12
- @keydown.enter="keyboardSelectConfirm($event)" @keydown.up="keyboardSwitch($event, -1)"
13
- @keydown.down="keyboardSwitch($event, 1)" @change="keyboardSwitchIndexReset()">
14
- <Teleport :to="listPut">
15
- <div class="autocomplete-select-component-selector-content" :class="{ 'active': active }"
16
- ref="componentContentList" :style="positionStyle">
17
- <ul class="autocomplete-select-component-opt-list" ref="opt_list">
18
- <li v-if="!listIsOnTop && clearBtn" class="border-bottom" :title="clearPlaceholder">
19
- <button class="autocomplete-select-component-opt-item is-clear" type="button"
20
- @click.stop="clearSelected()">
21
- {{ clearPlaceholder }}
22
- </button>
23
- </li>
24
- <li v-if="filterList?.length == 0">
25
- <div class="autocomplete-select-component-opt-item is-nothing">
26
- 沒有符合條件的選項
27
- </div>
28
- </li>
29
- <template v-if="active">
30
- <li v-for="(opt, idx) in filterList" :key="idx" @click.stop="selectConfirm(opt)" :title="opt.name"
31
- :id="`autocomplete-select-component-opt-item_${idx}`">
32
- <button class="autocomplete-select-component-opt-item" type="button"
33
- :class="`${idx == keyboardSwitchIndex ? 'active' : ''} ${optClass}`">
34
- <template v-if="htmlOption">
35
- <slot name="option" :optData="opt"></slot>
36
- </template>
37
- <template v-else>
38
- {{ previewAdjust(opt) }}
39
- </template>
40
- </button>
41
- </li>
42
- </template>
43
- <li v-if="listIsOnTop && clearBtn" class="border-top" :title="clearPlaceholder">
44
- <button class="autocomplete-select-component-opt-item is-clear" type="button"
45
- @click.stop="clearSelected()">
46
- {{ clearPlaceholder }}
47
- </button>
48
- </li>
49
- </ul>
50
- </div>
51
- </Teleport>
52
- </div>
53
- </template>
54
- <script setup>
55
- // enum Data & Functions
56
-
57
- // vue & bootstrap
58
- import { ref, onMounted, onUnmounted, computed, toRaw } from 'vue'
59
-
60
- // plugins
61
-
62
- // vue components
63
-
64
- const props = defineProps({
65
- selectedData: {
66
- required: true
67
- },
68
- opts: {
69
- type: Array,
70
- default: []
71
- },
72
- optClass: {
73
- type: String,
74
- default: '',
75
- },
76
- placeholder: {
77
- type: String,
78
- default: '請選擇'
79
- },
80
- searchPlaceholder: {
81
- type: String,
82
- default: ''
83
- },
84
- triggerClass: {
85
- type: String,
86
- default: ''
87
- },
88
- listPut: {
89
- type: String,
90
- default: 'body'
91
- },
92
-
93
- clearBtn: {
94
- type: Boolean,
95
- default: true
96
- },
97
- clearPlaceholder: {
98
- type: String,
99
- default: '清除選擇'
100
- },
101
- resetValue: {
102
- default: ''
103
- },
104
- disabled: {
105
- type: Boolean,
106
- default: false
107
- },
108
-
109
-
110
- previewKey: {
111
- type: String,
112
- default: '',
113
- },
114
- bindingKey: {
115
- type: String,
116
- default: '',
117
- },
118
- filterKeys: {
119
- type: Array,
120
- default: ['name'],
121
- },
122
- isUndefinedHint: {
123
- type: String,
124
- default: '⚠️ 未找到符合原選擇的選項,可能相關資料曾發生異動',
125
- },
126
- htmlOption: {
127
- type: Boolean,
128
- default: false
129
- },
130
-
131
-
132
- });
133
-
134
- const emit = defineEmits(['select'])
135
-
136
- // Just DOM
137
- const componentContentInput = ref(null)
138
- const componentContentList = ref(null)
139
- const keywordFilterInput = ref(null)
140
-
141
-
142
- // 是否開啟下拉選單
143
- const active = ref(false)
144
- const domPosition = ref({ bottom: 0, height: 0, left: 0, right: 0, top: 0, width: 0, x: 0, y: 0 })
145
- function activeSelector() {
146
- if (props.disabled) {
147
- return
148
- }
149
- active.value = true
150
- keywordFilterInput.value.focus()
151
- domPosition.value = componentContentInput.value.getBoundingClientRect()
152
- }
153
- const listIsOnTop = ref(false)
154
- const positionStyle = computed(() => {
155
- let screenHeight = window.innerHeight;
156
- let info = toRaw(domPosition.value)
157
- listIsOnTop.value = info.top >= (screenHeight - info.bottom)
158
- let str = ''
159
- if (listIsOnTop.value) {
160
- str = `bottom:${screenHeight - info.top}px;max-height:calc(${info.top}px - 10%);`
161
- } else {
162
- str = `top:${info.bottom}px;max-height:calc(${screenHeight - info.bottom}px - 10%);`
163
- }
164
- return `${str}left:${info.x}px;max-width:calc(90% - ${info.x}px);min-width:${Math.abs(info.x - info.right)}px;`
165
- })
166
-
167
- function leave() {
168
- listIsOnTop.value = false
169
- active.value = false
170
- keyword.value = ''
171
- // TODO SCROLL CLOSE
172
- keyboardSwitchIndexReset()
173
- keywordFilterInput.value.blur()
174
- }
175
-
176
- // 確認傳入選單列表內的選項Type,只檢查第一個,請內容統一,不要傳奇怪的東西進來
177
- const allowOptItemType = ['string', 'number', 'object']
178
- const optItemType = computed(() => {
179
- if (!Array.isArray(props.opts) || props.opts?.length < 1) {
180
- return ''
181
- }
182
- let sampling = props.opts[0]
183
- let type = typeof sampling
184
- if (!allowOptItemType.includes(type)) {
185
- return ''
186
- }
187
- if ((sampling instanceof Date) && (sampling instanceof RegExp) && Array.isArray(sampling)) {
188
- return ''
189
- }
190
- return type?.toLowerCase()
191
-
192
- })
193
-
194
- //
195
- // 篩選
196
- const keyword = ref("")
197
- const filterList = computed(() => {
198
- if (optItemType.value === '') {
199
- return []
200
- }
201
- if (keyword.value == '') {
202
- return props.opts
203
- }
204
- let kwReplace = keyword.value.replace(/[.*+?^${}()|[\]\\]/g, "");
205
- let regex = new RegExp(kwReplace, "i");
206
- if (optItemType.value === 'object') {
207
- let final = props.opts.filter(i => {
208
- for (var idx = 0; idx < props.filterKeys.length; idx++) {
209
- if (regex.test(i[props.filterKeys[idx].toString()])) {
210
- return i
211
- };
212
- }
213
- })
214
- return final
215
- }
216
- return props.opts.filter(i => {
217
- if (regex.test(i)) {
218
- return i
219
- }
220
- })
221
- })
222
-
223
- // 從選項列表中挖出整個選中的item,因選中的值(可能是某個key)
224
- const selectedItem = computed(() => {
225
- if (!props.selectedData) {
226
- return
227
- }
228
-
229
- // 如果選中的值是某個Key,需自行指定bindingKey
230
- if (props.bindingKey !== '') {
231
- return props.opts.find(i => i[props.bindingKey.toString()] == props.selectedData) ?? props.selectedData
232
- }
233
- return props.selectedData
234
- })
235
-
236
-
237
- // 整理 選項與選中值 的顯示文字
238
- function previewAdjust(itemObj) {
239
- if (props.previewKey !== '') {
240
- let strAry = props.previewKey.split('+')
241
- let isUndefined = false
242
-
243
- let finalStr = strAry.reduce((acc, cur) => {
244
- let v = cur.trim()
245
- if (v.match(/'[^']+'/g)) {
246
- return acc + v.substring(1, v.length - 1)
247
- } else {
248
- if (itemObj[v] === undefined) {
249
- isUndefined = true
250
- }
251
- return acc + itemObj[v]
252
- }
253
- }, '')
254
- if (isUndefined) {
255
- return props.isUndefinedHint
256
- }
257
- return finalStr ? finalStr : props.placeholder
258
- }
259
- return itemObj
260
- }
261
-
262
-
263
- // 選中的值的顯示
264
- const selectedPreviewText = computed(() => {
265
-
266
-
267
- if (!selectedItem.value || Object.keys(selectedItem.value).length === 0) {
268
- return props.placeholder
269
- }
270
- return previewAdjust(selectedItem.value)
271
- })
272
-
273
- // 清除選擇內容
274
- function clearSelected() {
275
- emit('select', props.resetValue)
276
- leave()
277
- }
278
-
279
- // 選擇送出
280
- function selectConfirm(v) {
281
- if (!v) {
282
- return
283
- }
284
- emit('select', JSON.parse(JSON.stringify(v)))
285
- leave()
286
- keyboardSwitchIndexReset()
287
- }
288
-
289
- // 鍵盤送出
290
- function keyboardSelectConfirm(event) {
291
- if (event.isComposing) {
292
- return
293
- }
294
- if (filterList.value?.length) {
295
- selectConfirm(filterList.value[keyboardSwitchIndex.value])
296
- }
297
- }
298
-
299
- // 鍵盤挑選
300
- const keyboardSwitchIndex = ref(0)
301
- function keyboardSwitch(event, v) {
302
- if (event.isComposing) {
303
- return
304
- }
305
- if (filterList.value?.length < 1) {
306
- keyboardSwitchIndexReset()
307
- return
308
- }
309
- let idx = Number(keyboardSwitchIndex.value) + v
310
-
311
- if (idx < 0) {
312
- keyboardSwitchIndex.value = filterList.value.length - 1
313
- document.getElementById(`autocomplete-select-component-opt-item_${keyboardSwitchIndex.value}`)?.scrollIntoView()
314
- return
315
- }
316
- if (idx > filterList.value.length - 1) {
317
- keyboardSwitchIndexReset()
318
- document.getElementById(`autocomplete-select-component-opt-item_${keyboardSwitchIndex.value}`)?.scrollIntoView()
319
- return
320
- }
321
- keyboardSwitchIndex.value = idx
322
- document.getElementById(`autocomplete-select-component-opt-item_${keyboardSwitchIndex.value}`)?.scrollIntoView()
323
- }
324
-
325
- function keyboardSwitchIndexReset() {
326
- keyboardSwitchIndex.value = 0
327
- }
328
-
329
- // 樣式
330
- const previewInput = computed(() => {
331
- if (selectedPreviewText.value == props.placeholder) {
332
- return 'autocomplete-select-component-value-null'
333
- }
334
- return ''
335
- })
336
- const searchInput = computed(() => {
337
- if (active.value) {
338
- return 'active'
339
- }
340
- return ''
341
- })
342
- function initTrigger(event) {
343
- if (componentContentInput.value.contains(event.target) || componentContentList.value.contains(event.target)) {
344
- activeSelector()
345
- } else {
346
- leave()
347
- }
348
- }
349
- onMounted(() => {
350
- window.addEventListener("click", initTrigger);
351
- })
352
-
353
- onUnmounted(() => {
354
- window.removeEventListener("click", initTrigger);
355
- })
356
-
357
- </script>
358
- <style lang="scss" scoped>
359
- * {
360
- word-break: break-word;
361
- overflow-wrap: break-word;
362
- }
363
-
364
- .autocomplete-select-component-trigger-content {
365
- position: relative;
366
- width: 100%;
367
-
368
- .form-control {
369
- background-image: url("data:image/svg+xml,%3csvg xmlns=%27http://www.w3.org/2000/svg%27 viewBox=%270 0 16 16%27%3e%3cpath fill=%27none%27 stroke=%27%232D4155%27 stroke-linecap=%27round%27 stroke-linejoin=%27round%27 stroke-width=%272%27 d=%27m2 5 6 6 6-6%27/%3e%3c/svg%3e");
370
- background-repeat: no-repeat;
371
- background-position: right 0.75rem center;
372
- background-size: 16px 12px;
373
- text-align: start;
374
- width: 100%;
375
- }
376
-
377
- .autocomplete-select-component-value-null {
378
- color: var(--bs-secondary-color);
379
- }
380
-
381
- .autocomplete-select-component-display-selecting {
382
- position: absolute;
383
- top: 0;
384
- left: 0;
385
- z-index: 1;
386
- white-space: nowrap;
387
- overflow: hidden;
388
- text-overflow: ellipsis;
389
- }
390
-
391
- .autocomplete-select-component-keyword-filter-input {
392
- opacity: 0;
393
- position: relative;
394
-
395
- &.active {
396
- opacity: 1;
397
- z-index: 2;
398
- }
399
- }
400
- }
401
-
402
- .autocomplete-select-component-selector-content {
403
- visibility: hidden;
404
- display: none;
405
- position: fixed;
406
- z-index: 2000;
407
- margin-top: 0.25rem;
408
- margin-bottom: 0.25rem;
409
- max-height: 76vh;
410
- overflow: auto;
411
-
412
- ul.autocomplete-select-component-opt-list {
413
- background: #fff;
414
- list-style: none;
415
- border: 1px solid var(--gray-400);
416
- border-radius: var(--bs-border-radius);
417
- margin: 0;
418
- padding: 0;
419
- overflow: auto;
420
- padding: .25rem 0;
421
-
422
- >li {
423
- width: 100%;
424
- overflow: auto;
425
- }
426
-
427
- .autocomplete-select-component-opt-item {
428
- background-color: transparent;
429
- border: none;
430
- padding: .25rem .5rem;
431
- word-break: break-word;
432
- width: 100%;
433
- overflow: hidden;
434
- text-align: start;
435
- text-overflow: ellipsis;
436
- color: var(--gray-600);
437
-
438
- &.is-nothing {
439
- color: var(--bs-danger);
440
- }
441
-
442
- &.is-clear {
443
- color: var(--gray-500);
444
- }
445
-
446
- &:hover,
447
- &.is-clear:hover {
448
- background: rgba(var(--bs-primary-rgb), 1) !important;
449
- color: #fff !important;
450
- }
451
-
452
- &.active {
453
- background: rgba(var(--bs-primary-rgb), .1);
454
- color: var(--bs-primary);
455
- }
456
- }
457
- }
458
-
459
- &.active {
460
- display: block;
461
- box-shadow: var(--bs-box-shadow);
462
- visibility: visible;
463
- z-index: 2000;
464
- }
465
- }
1
+ <template>
2
+ <div class="autocomplete-select-component-trigger-content" ref="componentContentInput">
3
+ <!-- preview input -->
4
+ <button type="button" class='form-control autocomplete-select-component-display-selecting' :disabled="disabled"
5
+ :class="[triggerClass, previewInput]">{{
6
+ selectedPreviewText
7
+ }}</button>
8
+ <!-- search input -->
9
+ <input class='form-control autocomplete-select-component-keyword-filter-input' :class="[triggerClass, searchInput]"
10
+ type="text" ref="keywordFilterInput" v-model="keyword"
11
+ :placeholder="searchPlaceholder == '' ? selectedPreviewText : searchPlaceholder" maxlength="50"
12
+ @keydown.enter="keyboardSelectConfirm($event)" @keydown.up="keyboardSwitch($event, -1)"
13
+ @keydown.down="keyboardSwitch($event, 1)" @change="keyboardSwitchIndexReset()">
14
+ <Teleport :to="listPut">
15
+ <div class="autocomplete-select-component-selector-content" :class="{ 'active': active }"
16
+ ref="componentContentList" :style="positionStyle">
17
+ <ul class="autocomplete-select-component-opt-list" ref="opt_list">
18
+ <li v-if="!listIsOnTop && clearBtn" class="border-bottom" :title="clearPlaceholder">
19
+ <button class="autocomplete-select-component-opt-item is-clear" type="button"
20
+ @click.stop="clearSelected()">
21
+ {{ clearPlaceholder }}
22
+ </button>
23
+ </li>
24
+ <li v-if="filterList?.length == 0">
25
+ <div class="autocomplete-select-component-opt-item is-nothing">
26
+ 沒有符合條件的選項
27
+ </div>
28
+ </li>
29
+ <template v-if="active">
30
+ <li v-for="(opt, idx) in filterList" :key="idx" @click.stop="selectConfirm(opt)" :title="opt.name"
31
+ :id="`autocomplete-select-component-opt-item_${idx}`">
32
+ <button class="autocomplete-select-component-opt-item" type="button"
33
+ :class="`${idx == keyboardSwitchIndex ? 'active' : ''} ${optClass}`">
34
+ <template v-if="htmlOption">
35
+ <slot name="option" :optData="opt"></slot>
36
+ </template>
37
+ <template v-else>
38
+ {{ previewAdjust(opt) }}
39
+ </template>
40
+ </button>
41
+ </li>
42
+ </template>
43
+ <li v-if="listIsOnTop && clearBtn" class="border-top" :title="clearPlaceholder">
44
+ <button class="autocomplete-select-component-opt-item is-clear" type="button"
45
+ @click.stop="clearSelected()">
46
+ {{ clearPlaceholder }}
47
+ </button>
48
+ </li>
49
+ </ul>
50
+ </div>
51
+ </Teleport>
52
+ </div>
53
+ </template>
54
+ <script setup>
55
+ // enum Data & Functions
56
+
57
+ // vue & bootstrap
58
+ import { ref, onMounted, onUnmounted, computed, toRaw } from 'vue'
59
+
60
+ // plugins
61
+
62
+ // vue components
63
+
64
+ const props = defineProps({
65
+ selectedData: {
66
+ required: true
67
+ },
68
+ opts: {
69
+ type: Array,
70
+ default: []
71
+ },
72
+ optClass: {
73
+ type: String,
74
+ default: '',
75
+ },
76
+ placeholder: {
77
+ type: String,
78
+ default: '請選擇'
79
+ },
80
+ searchPlaceholder: {
81
+ type: String,
82
+ default: ''
83
+ },
84
+ triggerClass: {
85
+ type: String,
86
+ default: ''
87
+ },
88
+ listPut: {
89
+ type: String,
90
+ default: 'body'
91
+ },
92
+
93
+ clearBtn: {
94
+ type: Boolean,
95
+ default: true
96
+ },
97
+ clearPlaceholder: {
98
+ type: String,
99
+ default: '清除選擇'
100
+ },
101
+ resetValue: {
102
+ default: ''
103
+ },
104
+ disabled: {
105
+ type: Boolean,
106
+ default: false
107
+ },
108
+
109
+
110
+ previewKey: {
111
+ type: String,
112
+ default: '',
113
+ },
114
+ bindingKey: {
115
+ type: String,
116
+ default: '',
117
+ },
118
+ filterKeys: {
119
+ type: Array,
120
+ default: ['name'],
121
+ },
122
+ isUndefinedHint: {
123
+ type: String,
124
+ default: '⚠️ 未找到符合原選擇的選項,可能相關資料曾發生異動',
125
+ },
126
+ htmlOption: {
127
+ type: Boolean,
128
+ default: false
129
+ },
130
+
131
+
132
+ });
133
+
134
+ const emit = defineEmits(['select'])
135
+
136
+ // Just DOM
137
+ const componentContentInput = ref(null)
138
+ const componentContentList = ref(null)
139
+ const keywordFilterInput = ref(null)
140
+
141
+
142
+ // 是否開啟下拉選單
143
+ const active = ref(false)
144
+ const domPosition = ref({ bottom: 0, height: 0, left: 0, right: 0, top: 0, width: 0, x: 0, y: 0 })
145
+ function activeSelector() {
146
+ if (props.disabled) {
147
+ return
148
+ }
149
+ active.value = true
150
+ keywordFilterInput.value.focus()
151
+ domPosition.value = componentContentInput.value.getBoundingClientRect()
152
+ }
153
+ const listIsOnTop = ref(false)
154
+ const positionStyle = computed(() => {
155
+ let screenHeight = window.innerHeight;
156
+ let info = toRaw(domPosition.value)
157
+ listIsOnTop.value = info.top >= (screenHeight - info.bottom)
158
+ let str = ''
159
+ if (listIsOnTop.value) {
160
+ str = `bottom:${screenHeight - info.top}px;max-height:calc(${info.top}px - 10%);`
161
+ } else {
162
+ str = `top:${info.bottom}px;max-height:calc(${screenHeight - info.bottom}px - 10%);`
163
+ }
164
+ return `${str}left:${info.x}px;max-width:calc(90% - ${info.x}px);min-width:${Math.abs(info.x - info.right)}px;`
165
+ })
166
+
167
+ function leave() {
168
+ listIsOnTop.value = false
169
+ active.value = false
170
+ keyword.value = ''
171
+ // TODO SCROLL CLOSE
172
+ keyboardSwitchIndexReset()
173
+ keywordFilterInput.value.blur()
174
+ }
175
+
176
+ // 確認傳入選單列表內的選項Type,只檢查第一個,請內容統一,不要傳奇怪的東西進來
177
+ const allowOptItemType = ['string', 'number', 'object']
178
+ const optItemType = computed(() => {
179
+ if (!Array.isArray(props.opts) || props.opts?.length < 1) {
180
+ return ''
181
+ }
182
+ let sampling = props.opts[0]
183
+ let type = typeof sampling
184
+ if (!allowOptItemType.includes(type)) {
185
+ return ''
186
+ }
187
+ if ((sampling instanceof Date) && (sampling instanceof RegExp) && Array.isArray(sampling)) {
188
+ return ''
189
+ }
190
+ return type?.toLowerCase()
191
+
192
+ })
193
+
194
+ //
195
+ // 篩選
196
+ const keyword = ref("")
197
+ const filterList = computed(() => {
198
+ if (optItemType.value === '') {
199
+ return []
200
+ }
201
+ if (keyword.value == '') {
202
+ return props.opts
203
+ }
204
+ let kwReplace = keyword.value.replace(/[.*+?^${}()|[\]\\]/g, "");
205
+ let regex = new RegExp(kwReplace, "i");
206
+ if (optItemType.value === 'object') {
207
+ let final = props.opts.filter(i => {
208
+ for (var idx = 0; idx < props.filterKeys.length; idx++) {
209
+ if (regex.test(i[props.filterKeys[idx].toString()])) {
210
+ return i
211
+ };
212
+ }
213
+ })
214
+ return final
215
+ }
216
+ return props.opts.filter(i => {
217
+ if (regex.test(i)) {
218
+ return i
219
+ }
220
+ })
221
+ })
222
+
223
+ // 從選項列表中挖出整個選中的item,因選中的值(可能是某個key)
224
+ const selectedItem = computed(() => {
225
+ if (!props.selectedData) {
226
+ return
227
+ }
228
+
229
+ // 如果選中的值是某個Key,需自行指定bindingKey
230
+ if (props.bindingKey !== '') {
231
+ return props.opts.find(i => i[props.bindingKey.toString()] == props.selectedData) ?? props.selectedData
232
+ }
233
+ return props.selectedData
234
+ })
235
+
236
+
237
+ // 整理 選項與選中值 的顯示文字
238
+ function previewAdjust(itemObj) {
239
+ if (props.previewKey !== '') {
240
+ let strAry = props.previewKey.split('+')
241
+ let isUndefined = false
242
+
243
+ let finalStr = strAry.reduce((acc, cur) => {
244
+ let v = cur.trim()
245
+ if (v.match(/'[^']+'/g)) {
246
+ return acc + v.substring(1, v.length - 1)
247
+ } else {
248
+ if (itemObj[v] === undefined) {
249
+ isUndefined = true
250
+ }
251
+ return acc + itemObj[v]
252
+ }
253
+ }, '')
254
+ if (isUndefined) {
255
+ return props.isUndefinedHint
256
+ }
257
+ return finalStr ? finalStr : props.placeholder
258
+ }
259
+ return itemObj
260
+ }
261
+
262
+
263
+ // 選中的值的顯示
264
+ const selectedPreviewText = computed(() => {
265
+
266
+
267
+ if (!selectedItem.value || Object.keys(selectedItem.value).length === 0) {
268
+ return props.placeholder
269
+ }
270
+ return previewAdjust(selectedItem.value)
271
+ })
272
+
273
+ // 清除選擇內容
274
+ function clearSelected() {
275
+ emit('select', props.resetValue)
276
+ leave()
277
+ }
278
+
279
+ // 選擇送出
280
+ function selectConfirm(v) {
281
+ if (!v) {
282
+ return
283
+ }
284
+ emit('select', JSON.parse(JSON.stringify(v)))
285
+ leave()
286
+ keyboardSwitchIndexReset()
287
+ }
288
+
289
+ // 鍵盤送出
290
+ function keyboardSelectConfirm(event) {
291
+ if (event.isComposing) {
292
+ return
293
+ }
294
+ if (filterList.value?.length) {
295
+ selectConfirm(filterList.value[keyboardSwitchIndex.value])
296
+ }
297
+ }
298
+
299
+ // 鍵盤挑選
300
+ const keyboardSwitchIndex = ref(0)
301
+ function keyboardSwitch(event, v) {
302
+ if (event.isComposing) {
303
+ return
304
+ }
305
+ if (filterList.value?.length < 1) {
306
+ keyboardSwitchIndexReset()
307
+ return
308
+ }
309
+ let idx = Number(keyboardSwitchIndex.value) + v
310
+
311
+ if (idx < 0) {
312
+ keyboardSwitchIndex.value = filterList.value.length - 1
313
+ document.getElementById(`autocomplete-select-component-opt-item_${keyboardSwitchIndex.value}`)?.scrollIntoView()
314
+ return
315
+ }
316
+ if (idx > filterList.value.length - 1) {
317
+ keyboardSwitchIndexReset()
318
+ document.getElementById(`autocomplete-select-component-opt-item_${keyboardSwitchIndex.value}`)?.scrollIntoView()
319
+ return
320
+ }
321
+ keyboardSwitchIndex.value = idx
322
+ document.getElementById(`autocomplete-select-component-opt-item_${keyboardSwitchIndex.value}`)?.scrollIntoView()
323
+ }
324
+
325
+ function keyboardSwitchIndexReset() {
326
+ keyboardSwitchIndex.value = 0
327
+ }
328
+
329
+ // 樣式
330
+ const previewInput = computed(() => {
331
+ if (selectedPreviewText.value == props.placeholder) {
332
+ return 'autocomplete-select-component-value-null'
333
+ }
334
+ return ''
335
+ })
336
+ const searchInput = computed(() => {
337
+ if (active.value) {
338
+ return 'active'
339
+ }
340
+ return ''
341
+ })
342
+ function initTrigger(event) {
343
+ if (componentContentInput.value.contains(event.target) || componentContentList.value.contains(event.target)) {
344
+ activeSelector()
345
+ } else {
346
+ leave()
347
+ }
348
+ }
349
+ onMounted(() => {
350
+ window.addEventListener("click", initTrigger);
351
+ })
352
+
353
+ onUnmounted(() => {
354
+ window.removeEventListener("click", initTrigger);
355
+ })
356
+
357
+ </script>
358
+ <style lang="scss" scoped>
359
+ * {
360
+ word-break: break-word;
361
+ overflow-wrap: break-word;
362
+ }
363
+
364
+ .autocomplete-select-component-trigger-content {
365
+ position: relative;
366
+ width: 100%;
367
+
368
+ .form-control {
369
+ background-image: url("data:image/svg+xml,%3csvg xmlns=%27http://www.w3.org/2000/svg%27 viewBox=%270 0 16 16%27%3e%3cpath fill=%27none%27 stroke=%27%232D4155%27 stroke-linecap=%27round%27 stroke-linejoin=%27round%27 stroke-width=%272%27 d=%27m2 5 6 6 6-6%27/%3e%3c/svg%3e");
370
+ background-repeat: no-repeat;
371
+ background-position: right 0.75rem center;
372
+ background-size: 16px 12px;
373
+ text-align: start;
374
+ width: 100%;
375
+ }
376
+
377
+ .autocomplete-select-component-value-null {
378
+ color: var(--bs-secondary-color);
379
+ }
380
+
381
+ .autocomplete-select-component-display-selecting {
382
+ position: absolute;
383
+ top: 0;
384
+ left: 0;
385
+ z-index: 1;
386
+ white-space: nowrap;
387
+ overflow: hidden;
388
+ text-overflow: ellipsis;
389
+ padding-right: 2rem;
390
+ }
391
+
392
+ .autocomplete-select-component-keyword-filter-input {
393
+ opacity: 0;
394
+ position: relative;
395
+ padding-right: 2rem;
396
+
397
+ &.active {
398
+ opacity: 1;
399
+ z-index: 2;
400
+ }
401
+ }
402
+ }
403
+
404
+ .autocomplete-select-component-selector-content {
405
+ visibility: hidden;
406
+ display: none;
407
+ position: fixed;
408
+ z-index: 2000;
409
+ margin-top: 0.25rem;
410
+ margin-bottom: 0.25rem;
411
+ max-height: 76vh;
412
+ overflow: auto;
413
+
414
+ ul.autocomplete-select-component-opt-list {
415
+ background: #fff;
416
+ list-style: none;
417
+ border: 1px solid var(--gray-400);
418
+ border-radius: var(--bs-border-radius);
419
+ margin: 0;
420
+ padding: 0;
421
+ overflow: auto;
422
+ padding: .25rem 0;
423
+
424
+ >li {
425
+ width: 100%;
426
+ overflow: auto;
427
+ }
428
+
429
+ .autocomplete-select-component-opt-item {
430
+ background-color: transparent;
431
+ border: none;
432
+ padding: .25rem .5rem;
433
+ word-break: break-word;
434
+ width: 100%;
435
+ overflow: hidden;
436
+ text-align: start;
437
+ text-overflow: ellipsis;
438
+ color: var(--gray-600);
439
+
440
+ &.is-nothing {
441
+ color: var(--bs-danger);
442
+ }
443
+
444
+ &.is-clear {
445
+ color: var(--gray-500);
446
+ }
447
+
448
+ &:hover,
449
+ &.is-clear:hover {
450
+ background: rgba(var(--bs-primary-rgb), 1) !important;
451
+ color: #fff !important;
452
+ }
453
+
454
+ &.active {
455
+ background: rgba(var(--bs-primary-rgb), .1);
456
+ color: var(--bs-primary);
457
+ }
458
+ }
459
+ }
460
+
461
+ &.active {
462
+ display: block;
463
+ box-shadow: var(--bs-box-shadow);
464
+ visibility: visible;
465
+ z-index: 2000;
466
+ }
467
+ }
466
468
  </style>