@mixd-id/web-scaffold 0.1.250801007 → 0.1.250801009
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/package.json +1 -1
- package/src/components/Card.vue +21 -4
- package/src/components/List.vue +57 -52
- package/src/components/MultiDropdown.vue +15 -11
- package/src/components/Timepicker.vue +1 -1
- package/src/components/VirtualGrid.vue +3 -3
- package/src/components/VirtualTable.vue +2 -3
- package/src/directives/intersect.js +26 -0
- package/src/index.js +4 -0
- package/src/utils/list.mjs +112 -54
package/package.json
CHANGED
package/src/components/Card.vue
CHANGED
|
@@ -19,13 +19,13 @@
|
|
|
19
19
|
</div>
|
|
20
20
|
</slot>
|
|
21
21
|
|
|
22
|
-
<slot name="default"></slot>
|
|
22
|
+
<slot name="default" :context="context"></slot>
|
|
23
23
|
|
|
24
24
|
</div>
|
|
25
25
|
</Transition>
|
|
26
26
|
</Teleport>
|
|
27
27
|
<div v-else :class="computedClass">
|
|
28
|
-
<slot name="default"></slot>
|
|
28
|
+
<slot name="default" :context="context"></slot>
|
|
29
29
|
</div>
|
|
30
30
|
</template>
|
|
31
31
|
|
|
@@ -76,7 +76,8 @@ export default{
|
|
|
76
76
|
data(){
|
|
77
77
|
return {
|
|
78
78
|
mounted: false,
|
|
79
|
-
_state: false
|
|
79
|
+
_state: false,
|
|
80
|
+
context: {}
|
|
80
81
|
}
|
|
81
82
|
},
|
|
82
83
|
|
|
@@ -100,7 +101,9 @@ export default{
|
|
|
100
101
|
|
|
101
102
|
},
|
|
102
103
|
|
|
103
|
-
open(){
|
|
104
|
+
open(context = {}){
|
|
105
|
+
this.context = context
|
|
106
|
+
|
|
104
107
|
if(this.query){
|
|
105
108
|
|
|
106
109
|
}
|
|
@@ -129,6 +132,11 @@ export default{
|
|
|
129
132
|
|
|
130
133
|
class: String,
|
|
131
134
|
|
|
135
|
+
dismissable: {
|
|
136
|
+
type: Boolean,
|
|
137
|
+
default: false
|
|
138
|
+
},
|
|
139
|
+
|
|
132
140
|
float: Boolean,
|
|
133
141
|
|
|
134
142
|
height: {
|
|
@@ -218,4 +226,13 @@ html[data-theme='dark'] .overlay{
|
|
|
218
226
|
@apply bg-black/50;
|
|
219
227
|
}
|
|
220
228
|
|
|
229
|
+
@media screen and (max-width: 640px){
|
|
230
|
+
|
|
231
|
+
.modal {
|
|
232
|
+
width: 100% !important;
|
|
233
|
+
max-height: 90vh;
|
|
234
|
+
@apply self-end;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
}
|
|
221
238
|
</style>
|
package/src/components/List.vue
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div class="flex flex-col"
|
|
2
|
+
<div class="flex flex-col bg-base-400 md:bg-transparent"
|
|
3
|
+
v-intersect="onVisibleChange">
|
|
3
4
|
|
|
4
5
|
<div v-if="readyState >= 1"
|
|
5
6
|
class="flex-1 flex flex-row">
|
|
@@ -36,14 +37,14 @@
|
|
|
36
37
|
:preset="preset"
|
|
37
38
|
:presetSelector="$refs.presetSelector"
|
|
38
39
|
:compPrefix="compPrefix">
|
|
39
|
-
<div class="flex flex-col md:flex-row gap-3 md:items-end overflow-hidden p-3 md:p-0
|
|
40
|
+
<div class="flex flex-col md:flex-row gap-3 md:items-end overflow-hidden p-3 pb-1 md:p-0"
|
|
40
41
|
:class="headerClass">
|
|
41
|
-
|
|
42
|
-
|
|
42
|
+
<div class="flex md:flex-1 flex-row items-end md:gap-3 py-1 md:py-0">
|
|
43
|
+
<slot name="left"></slot>
|
|
43
44
|
|
|
44
45
|
<div class="flex flex-row gap-2 md:gap-3 items-center px-3" ref="title">
|
|
45
46
|
<div class="flex flex-col whitespace-nowrap text-ellipsis overflow-hidden">
|
|
46
|
-
<div class="cursor-pointer group leading-
|
|
47
|
+
<div class="cursor-pointer group leading-4" @click="$refs.contextMenu.open($refs.title)">
|
|
47
48
|
<small class="text-text-400 text-xs">{{ title ?? 'Untitled' }}</small>
|
|
48
49
|
<div class="flex flex-row items-baseline gap-1">
|
|
49
50
|
<h3 class="whitespace-nowrap relative text-ellipsis overflow-hidden group-hover:text-primary">
|
|
@@ -90,8 +91,9 @@
|
|
|
90
91
|
|
|
91
92
|
<slot name="toolbar">
|
|
92
93
|
</slot>
|
|
94
|
+
|
|
93
95
|
<slot name="right-toolbar">
|
|
94
|
-
<div class="flex flex-row gap-2" v-if="Boolean(toolbar)">
|
|
96
|
+
<div class="hidden md:flex flex-row gap-2" v-if="Boolean(toolbar)">
|
|
95
97
|
<Textbox v-if="canSearch"
|
|
96
98
|
class="flex-1 md:w-[240px]"
|
|
97
99
|
placeholder="Search..."
|
|
@@ -106,26 +108,6 @@
|
|
|
106
108
|
</div>
|
|
107
109
|
</template>
|
|
108
110
|
</Textbox>
|
|
109
|
-
|
|
110
|
-
<div v-if="false && $slots.gridItem"
|
|
111
|
-
class="hidden md:grid grid-cols-2 gap-[1px] border-[1px] border-border-50 rounded-lg">
|
|
112
|
-
<Radio v-model="preset.view"
|
|
113
|
-
name="view"
|
|
114
|
-
value="table"
|
|
115
|
-
:custom="true"
|
|
116
|
-
class="p-2 px-3 rounded-lg rounded-r-none"
|
|
117
|
-
:class="preset.view === 'table' ? 'bg-primary-500' : 'bg-base-500'">
|
|
118
|
-
<svg width="14" height="14" :class="preset.view === 'table' ? 'fill-white' : 'fill-text'" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--! Font Awesome Pro 6.0.0-alpha3 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) --><path d="M448 32H64.05C28.7 32 .0492 60.65 .0492 96v320c0 35.35 28.65 64 64 64h383.1c35.35 0 64-28.65 64-64V96C512 60.65 483.4 32 448 32zM224 416H64v-96h160V416zM224 256H64V160h160V256zM448 416h-160v-96h160V416zM448 256h-160V160h160V256z"/></svg>
|
|
119
|
-
</Radio>
|
|
120
|
-
<Radio v-model="preset.view"
|
|
121
|
-
name="view"
|
|
122
|
-
value="grid"
|
|
123
|
-
:custom="true"
|
|
124
|
-
class="p-2 px-3 bg-base-500 rounded-lg rounded-l-none"
|
|
125
|
-
:class="preset.view === 'grid' ? 'bg-primary-500' : 'bg-base-500'">
|
|
126
|
-
<svg width="14" height="14" :class="preset.view === 'grid' ? 'fill-white' : 'fill-text'" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--! Font Awesome Pro 6.0.0-alpha3 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) --><path d="M144 288h-96c-17.67 0-32 14.33-32 32v96c0 17.67 14.33 32 32 32h96c17.67 0 32-14.33 32-32v-96C176 302.3 161.7 288 144 288zM368 288h-96c-17.67 0-32 14.33-32 32v96c0 17.67 14.33 32 32 32h96c17.67 0 32-14.33 32-32v-96C400 302.3 385.7 288 368 288zM592 288h-96c-17.67 0-32 14.33-32 32v96c0 17.67 14.33 32 32 32h96c17.67 0 32-14.33 32-32v-96C624 302.3 609.7 288 592 288zM144 64h-96c-17.67 0-32 14.33-32 32v96c0 17.67 14.33 32 32 32h96c17.67 0 32-14.33 32-32V96C176 78.33 161.7 64 144 64zM368 64h-96c-17.67 0-32 14.33-32 32v96c0 17.67 14.33 32 32 32h96c17.67 0 32-14.33 32-32V96C400 78.33 385.7 64 368 64zM592 64h-96c-17.67 0-32 14.33-32 32v96c0 17.67 14.33 32 32 32h96c17.67 0 32-14.33 32-32V96C624 78.33 609.7 64 592 64z"/></svg>
|
|
127
|
-
</Radio>
|
|
128
|
-
</div>
|
|
129
111
|
</div>
|
|
130
112
|
</slot>
|
|
131
113
|
</div>
|
|
@@ -133,6 +115,27 @@
|
|
|
133
115
|
</div>
|
|
134
116
|
</slot>
|
|
135
117
|
|
|
118
|
+
<div class="md:hidden">
|
|
119
|
+
<slot name="mobile-toolbar">
|
|
120
|
+
<div class="p-3">
|
|
121
|
+
<Textbox v-if="canSearch"
|
|
122
|
+
placeholder="Search..."
|
|
123
|
+
class="bg-base-300"
|
|
124
|
+
clearable="1"
|
|
125
|
+
maxlength="100"
|
|
126
|
+
v-model="preset.search"
|
|
127
|
+
@clear="delete preset.search; load()"
|
|
128
|
+
@keyup.enter="load">
|
|
129
|
+
<template #start>
|
|
130
|
+
<div class="p-2 pr-0">
|
|
131
|
+
<svg width="14" height="14" class="fill-text-300" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--! Font Awesome Pro 6.0.0-alpha3 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) --><path d="M504.1 471l-134-134C399.1 301.5 415.1 256.8 415.1 208c0-114.9-93.13-208-208-208S-.0002 93.13-.0002 208S93.12 416 207.1 416c48.79 0 93.55-16.91 129-45.04l134 134C475.7 509.7 481.9 512 488 512s12.28-2.344 16.97-7.031C514.3 495.6 514.3 480.4 504.1 471zM48 208c0-88.22 71.78-160 160-160s160 71.78 160 160s-71.78 160-160 160S48 296.2 48 208z"/></svg>
|
|
132
|
+
</div>
|
|
133
|
+
</template>
|
|
134
|
+
</Textbox>
|
|
135
|
+
</div>
|
|
136
|
+
</slot>
|
|
137
|
+
</div>
|
|
138
|
+
|
|
136
139
|
<div class="flex-1 flex" ref="content">
|
|
137
140
|
<div v-if="readyState === 2" class="flex-1 flex items-center justify-center">
|
|
138
141
|
<svg class="animate-spin" width="36" height="36" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle><path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path></svg>
|
|
@@ -288,7 +291,7 @@ import VirtualGrid from "./VirtualGrid.vue";
|
|
|
288
291
|
import PresetSelector from "../widgets/PresetSelector.vue";
|
|
289
292
|
import {generatePivotColumns, generateTotalColumns, setupConfig, sortsFn} from "../utils/preset-selector.mjs";
|
|
290
293
|
import PresetBar from "../widgets/PresetBar.vue";
|
|
291
|
-
import {
|
|
294
|
+
import {invokeAfterIdle, queueForLater} from "../utils/helpers.mjs";
|
|
292
295
|
|
|
293
296
|
export default{
|
|
294
297
|
|
|
@@ -899,40 +902,42 @@ export default{
|
|
|
899
902
|
if(column){
|
|
900
903
|
delete column.freeze
|
|
901
904
|
}
|
|
902
|
-
}
|
|
905
|
+
},
|
|
903
906
|
|
|
904
|
-
|
|
907
|
+
onVisibleChange({ inViewport, entry }){
|
|
908
|
+
if(inViewport){
|
|
909
|
+
this.$addResizeListener(this.$el, () => this.compPrefix = this.$util.calculateMediaPrefix(this.$el.clientWidth))
|
|
910
|
+
|
|
911
|
+
this.loadPreset()
|
|
912
|
+
.finally(() => {
|
|
913
|
+
this.readyState = 1
|
|
914
|
+
this.$nextTick(() => {
|
|
915
|
+
this.calcItemsPerPage()
|
|
916
|
+
this.load()
|
|
917
|
+
this.loadExt()
|
|
918
|
+
})
|
|
919
|
+
})
|
|
905
920
|
|
|
906
|
-
|
|
921
|
+
if(this.subscribeKey){
|
|
922
|
+
this.useSocket().send('user.subscribe', { name:this.subscribeKey })
|
|
923
|
+
this.useSocket().on(this.subscribeKey, this.onSignal)
|
|
924
|
+
}
|
|
907
925
|
|
|
908
|
-
|
|
909
|
-
this.$addResizeListener(this.$el, () => this.compPrefix = this.$util.calculateMediaPrefix(this.$el.clientWidth))
|
|
926
|
+
window.addEventListener('keydown', this.onKeyDown)
|
|
910
927
|
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
this.
|
|
914
|
-
this.$nextTick(() => {
|
|
915
|
-
this.calcItemsPerPage()
|
|
916
|
-
this.load()
|
|
917
|
-
this.loadExt()
|
|
918
|
-
})
|
|
928
|
+
this.queue = queueForLater({
|
|
929
|
+
pop: this.loadQueued,
|
|
930
|
+
delay: this.updateInterval
|
|
919
931
|
})
|
|
920
932
|
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
}
|
|
925
|
-
|
|
926
|
-
window.addEventListener('keydown', this.onKeyDown)
|
|
927
|
-
|
|
928
|
-
this.queue = queueForLater({
|
|
929
|
-
pop: this.loadQueued,
|
|
930
|
-
delay: this.updateInterval
|
|
931
|
-
})
|
|
933
|
+
this.useSocket().on('connect', this.onConnect)
|
|
934
|
+
}
|
|
935
|
+
},
|
|
932
936
|
|
|
933
|
-
this.useSocket().on('connect', this.onConnect)
|
|
934
937
|
},
|
|
935
938
|
|
|
939
|
+
components: {PresetBar, PresetSelector, VirtualGrid, VirtualTable},
|
|
940
|
+
|
|
936
941
|
unmounted() {
|
|
937
942
|
this.$removeResizeListener(this.$el)
|
|
938
943
|
window.removeEventListener('keydown', this.onKeyDown)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div :class="$style.dropdown">
|
|
3
|
-
<div ref="btn" class="flex flex-row items-center w-[150px]"
|
|
3
|
+
<div ref="btn" class="flex-1 flex flex-row items-center w-[150px]"
|
|
4
4
|
:class="containerClass"
|
|
5
5
|
@click="open()">
|
|
6
6
|
<div class="flex-1 p-2 text-ellipsis-nowrap">
|
|
@@ -12,9 +12,9 @@
|
|
|
12
12
|
</button>
|
|
13
13
|
</div>
|
|
14
14
|
|
|
15
|
-
<ContextMenu ref="contextMenu"
|
|
15
|
+
<ContextMenu ref="contextMenu" class="mt-2">
|
|
16
16
|
<div class="flex flex-col min-w-[200px]">
|
|
17
|
-
<div class="p-2">
|
|
17
|
+
<div class="p-2" v-if="viewedItems.length > 10">
|
|
18
18
|
<Textbox v-model="search" :clearable="true" @clear="search = ''">
|
|
19
19
|
<template #start>
|
|
20
20
|
<div class="pl-2">
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
</template>
|
|
24
24
|
</Textbox>
|
|
25
25
|
</div>
|
|
26
|
-
<div v-for="item in viewedItems" class="p-2">
|
|
26
|
+
<div v-for="item in viewedItems" class="p-2" @click.stop>
|
|
27
27
|
<Checkbox :value="item[valueKey]" v-model="selectedItems">
|
|
28
28
|
{{ item[textKey] }}
|
|
29
29
|
</Checkbox>
|
|
@@ -40,7 +40,12 @@ export default{
|
|
|
40
40
|
props: {
|
|
41
41
|
containerClass: String,
|
|
42
42
|
|
|
43
|
-
modelValue:
|
|
43
|
+
modelValue: {
|
|
44
|
+
type: Array,
|
|
45
|
+
validator(value){
|
|
46
|
+
return Array.isArray(value)
|
|
47
|
+
}
|
|
48
|
+
},
|
|
44
49
|
|
|
45
50
|
items: Array,
|
|
46
51
|
|
|
@@ -58,15 +63,15 @@ export default{
|
|
|
58
63
|
computed: {
|
|
59
64
|
|
|
60
65
|
selectedText(){
|
|
61
|
-
if(this.modelValue.length > 0)
|
|
66
|
+
if((this.modelValue ?? []).length > 0)
|
|
62
67
|
return this.modelValue.map(_ => _[this.textKey] ?? _[this.valueKey]).join(', ')
|
|
63
68
|
return this.$t('(Not Selected)')
|
|
64
69
|
},
|
|
65
70
|
|
|
66
71
|
viewedItems(){
|
|
67
72
|
if(this.search !== '')
|
|
68
|
-
return this.items.filter(_ => `${_[this.textKey]}`.toLowerCase().indexOf(this.search.toLowerCase()) >= 0)
|
|
69
|
-
return this.items
|
|
73
|
+
return (this.items ?? []).filter(_ => `${_[this.textKey]}`.toLowerCase().indexOf(this.search.toLowerCase()) >= 0)
|
|
74
|
+
return this.items ?? []
|
|
70
75
|
}
|
|
71
76
|
|
|
72
77
|
},
|
|
@@ -81,7 +86,7 @@ export default{
|
|
|
81
86
|
methods: {
|
|
82
87
|
|
|
83
88
|
open(){
|
|
84
|
-
this.selectedItems = this.modelValue.map(_ => _[this.valueKey])
|
|
89
|
+
this.selectedItems = (this.modelValue ?? []).map(_ => _[this.valueKey])
|
|
85
90
|
this.$refs.contextMenu.open(this.$refs.btn)
|
|
86
91
|
}
|
|
87
92
|
|
|
@@ -92,8 +97,7 @@ export default{
|
|
|
92
97
|
selectedItems: {
|
|
93
98
|
deep: true,
|
|
94
99
|
handler(_items){
|
|
95
|
-
this.$emit('update:modelValue',
|
|
96
|
-
this.items.filter(_ => _items.includes(_[this.valueKey]))
|
|
100
|
+
this.$emit('update:modelValue', (this.items ?? []).filter(_ => _items.includes(_[this.valueKey]))
|
|
97
101
|
)
|
|
98
102
|
}
|
|
99
103
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div :class="$style.virtualGrid"
|
|
2
|
+
<div :class="$style.virtualGrid">
|
|
3
3
|
|
|
4
4
|
<div ref="scroller" :class="$style.scroller" :style="scrollerStyle">
|
|
5
5
|
<div :class="spacerClass" ref="spacer" :style="spacerStyle">
|
|
@@ -273,7 +273,7 @@ export default{
|
|
|
273
273
|
|
|
274
274
|
.virtualGrid{
|
|
275
275
|
@apply flex-1 overflow-y-auto;
|
|
276
|
-
@apply border-[1px] border-border-50 bg-base-400;
|
|
276
|
+
@apply md:border-[1px] border-border-50 md:bg-base-400;
|
|
277
277
|
}
|
|
278
278
|
|
|
279
279
|
.scroller{
|
|
@@ -286,7 +286,7 @@ export default{
|
|
|
286
286
|
.spacer{
|
|
287
287
|
will-change: auto;
|
|
288
288
|
position: relative;
|
|
289
|
-
@apply grid;
|
|
289
|
+
@apply grid divide-y divide-border-50 border-t-[1px] border-border-50;
|
|
290
290
|
}
|
|
291
291
|
|
|
292
292
|
.calc{
|
|
@@ -207,7 +207,7 @@ const _DEFAULT_COLUMN_WIDTH = '100px'
|
|
|
207
207
|
|
|
208
208
|
export default{
|
|
209
209
|
|
|
210
|
-
inject: [ 'emitRoot'
|
|
210
|
+
inject: [ 'emitRoot' ],
|
|
211
211
|
|
|
212
212
|
emits: [ 'dragover', 'freeze', 'unfreeze', 'scroll-end', 'item-click' ],
|
|
213
213
|
|
|
@@ -685,8 +685,7 @@ export default{
|
|
|
685
685
|
|
|
686
686
|
return [
|
|
687
687
|
this.$style.tdDiv,
|
|
688
|
-
this.$style['align-' + align]
|
|
689
|
-
this.listStyle ? this.listStyle[appearanceClass] : ''
|
|
688
|
+
this.$style['align-' + align]
|
|
690
689
|
]
|
|
691
690
|
.join(' ')
|
|
692
691
|
},
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// directives/intersect.js
|
|
2
|
+
export default {
|
|
3
|
+
mounted(el, binding) {
|
|
4
|
+
const callback = binding.value;
|
|
5
|
+
|
|
6
|
+
const observer = new IntersectionObserver(
|
|
7
|
+
(entries) => {
|
|
8
|
+
entries.forEach(entry => {
|
|
9
|
+
callback({
|
|
10
|
+
inViewport: entry.isIntersecting,
|
|
11
|
+
entry
|
|
12
|
+
});
|
|
13
|
+
});
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
threshold: 0.1, // adjust sensitivity
|
|
17
|
+
}
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
observer.observe(el);
|
|
21
|
+
el._observer = observer;
|
|
22
|
+
},
|
|
23
|
+
unmounted(el) {
|
|
24
|
+
if (el._observer) el._observer.disconnect();
|
|
25
|
+
}
|
|
26
|
+
};
|
package/src/index.js
CHANGED
|
@@ -2,6 +2,8 @@ import {defineAsyncComponent, ref} from "vue"
|
|
|
2
2
|
import {arrayPush, arrayRemove, arrayUnshift, downloadUrl, invokeAfterIdle, observeInit} from './utils/helpers.mjs'
|
|
3
3
|
import dayjs from "dayjs";
|
|
4
4
|
import {useDevice} from './hooks/device.js'
|
|
5
|
+
import IntersectDirective from './directives/intersect';
|
|
6
|
+
|
|
5
7
|
|
|
6
8
|
let _UNIQID = 0
|
|
7
9
|
const uniqid = (additionalKey = '') => {
|
|
@@ -425,6 +427,8 @@ export default{
|
|
|
425
427
|
app.config.globalProperties.$mounted.value = true
|
|
426
428
|
}
|
|
427
429
|
|
|
430
|
+
app.directive('intersect', IntersectDirective)
|
|
431
|
+
|
|
428
432
|
app.component('Ahref', defineAsyncComponent(() => import("./components/Ahref.vue")))
|
|
429
433
|
app.component('Paragraph', defineAsyncComponent(() => import("./components/Paragraph.vue")))
|
|
430
434
|
app.component('Alert', defineAsyncComponent(() => import("./components/Alert.vue")))
|
package/src/utils/list.mjs
CHANGED
|
@@ -366,34 +366,57 @@ const getValue = (filter, opt) => {
|
|
|
366
366
|
return whereObj
|
|
367
367
|
}
|
|
368
368
|
|
|
369
|
-
const
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
369
|
+
const getColumnTypeFromModelAttribute = (attribute, opt) => {
|
|
370
|
+
const { DataTypes } = opt?.Sequelize ?? Sequelize
|
|
371
|
+
|
|
372
|
+
switch(attribute.type.key){
|
|
373
|
+
case DataTypes.BOOLEAN.key:
|
|
374
|
+
return 'boolean'
|
|
375
|
+
|
|
376
|
+
case DataTypes.INTEGER.key:
|
|
377
|
+
case DataTypes.FLOAT.key:
|
|
378
|
+
case DataTypes.DOUBLE.key:
|
|
379
|
+
case DataTypes.DECIMAL.key:
|
|
380
|
+
return 'number'
|
|
381
|
+
|
|
382
|
+
case DataTypes.DATE.key:
|
|
383
|
+
case DataTypes.DATEONLY.key:
|
|
384
|
+
return 'date'
|
|
385
|
+
|
|
386
|
+
default:
|
|
387
|
+
return 'string'
|
|
375
388
|
}
|
|
389
|
+
}
|
|
376
390
|
|
|
391
|
+
const filtersToSequelizeWhere = async(filters, opt) => {
|
|
377
392
|
let whereArr = []
|
|
378
393
|
let replacements = []
|
|
379
394
|
const { literal, Op } = opt.Sequelize ?? Sequelize
|
|
395
|
+
const { columns = [] } = opt?.config ?? {}
|
|
396
|
+
const modelAttributes = opt.model.getAttributes()
|
|
397
|
+
const tableName = opt.model.getTableName()
|
|
398
|
+
|
|
399
|
+
if(columns.length < 1){
|
|
400
|
+
for(let key in modelAttributes){
|
|
401
|
+
columns.push({
|
|
402
|
+
key,
|
|
403
|
+
fieldName: modelAttributes[key]?.fieldName || key,
|
|
404
|
+
type: getColumnTypeFromModelAttribute(modelAttributes[key], opt)
|
|
405
|
+
})
|
|
406
|
+
}
|
|
407
|
+
}
|
|
380
408
|
|
|
381
|
-
const
|
|
409
|
+
const keyColumns = (columns ?? []).reduce((res, cur) => {
|
|
410
|
+
res[cur.key] = cur
|
|
411
|
+
cur.fieldName = modelAttributes[cur.key]?.fieldName || cur.key
|
|
412
|
+
return res
|
|
413
|
+
}, {})
|
|
382
414
|
|
|
383
|
-
|
|
384
|
-
let { key, operator, value, fn:filterFn } = filter
|
|
385
|
-
const
|
|
386
|
-
const type = ((keyColumns[key] ?? [])[0] ?? {}).type
|
|
415
|
+
const getValue = (filter) => {
|
|
416
|
+
let { key, operator, value, fn:filterFn, fieldName } = filter
|
|
417
|
+
const type = (keyColumns[key] ?? {}).type ?? 'string'
|
|
387
418
|
|
|
388
419
|
let whereObj = {}
|
|
389
|
-
let Model, field
|
|
390
|
-
if(opt.model){
|
|
391
|
-
const modelAttributes = opt.model.getAttributes()
|
|
392
|
-
if(modelAttributes[key]){
|
|
393
|
-
Model = opt.model
|
|
394
|
-
field = modelAttributes[key].field
|
|
395
|
-
}
|
|
396
|
-
}
|
|
397
420
|
|
|
398
421
|
switch(type){
|
|
399
422
|
|
|
@@ -539,7 +562,7 @@ const filtersToSequelizeWhere = async(filters, opt) => {
|
|
|
539
562
|
switch(filterFn) {
|
|
540
563
|
|
|
541
564
|
case 'format_hour':
|
|
542
|
-
whereObj = literal(`DATE_FORMAT(${
|
|
565
|
+
whereObj = literal(`DATE_FORMAT(${tableName}.${fieldName}, "%H") < "${filter.value}"`)
|
|
543
566
|
break
|
|
544
567
|
|
|
545
568
|
default:
|
|
@@ -557,7 +580,7 @@ const filtersToSequelizeWhere = async(filters, opt) => {
|
|
|
557
580
|
switch(filterFn) {
|
|
558
581
|
|
|
559
582
|
case 'format_hour':
|
|
560
|
-
whereObj = literal(`DATE_FORMAT(${
|
|
583
|
+
whereObj = literal(`DATE_FORMAT(${tableName}.${fieldName}, "%H") <= "${filter.value}"`)
|
|
561
584
|
break
|
|
562
585
|
|
|
563
586
|
default:
|
|
@@ -575,19 +598,19 @@ const filtersToSequelizeWhere = async(filters, opt) => {
|
|
|
575
598
|
switch(filterFn) {
|
|
576
599
|
|
|
577
600
|
case 'format_date':
|
|
578
|
-
whereObj = literal(`DATE_FORMAT(${
|
|
601
|
+
whereObj = literal(`DATE_FORMAT(${tableName}.${fieldName}, "%Y-%m-%d") = "${filter.value}"`)
|
|
579
602
|
break
|
|
580
603
|
|
|
581
604
|
case 'format_hour':
|
|
582
|
-
whereObj = literal(`DATE_FORMAT(${
|
|
605
|
+
whereObj = literal(`DATE_FORMAT(${tableName}.${fieldName}, "%H") = "${filter.value}"`)
|
|
583
606
|
break
|
|
584
607
|
|
|
585
608
|
case 'format_month':
|
|
586
|
-
whereObj = literal(`DATE_FORMAT(${
|
|
609
|
+
whereObj = literal(`DATE_FORMAT(${tableName}.${fieldName}, "%m") = "${filter.value.split('-')[1]}"`)
|
|
587
610
|
break
|
|
588
611
|
|
|
589
612
|
case 'format_year':
|
|
590
|
-
whereObj = literal(`DATE_FORMAT(${
|
|
613
|
+
whereObj = literal(`DATE_FORMAT(${tableName}.${fieldName}, "%Y") = "${filter.value}"`)
|
|
591
614
|
break
|
|
592
615
|
|
|
593
616
|
default:
|
|
@@ -606,7 +629,7 @@ const filtersToSequelizeWhere = async(filters, opt) => {
|
|
|
606
629
|
switch(filterFn) {
|
|
607
630
|
|
|
608
631
|
case 'format_hour':
|
|
609
|
-
whereObj = literal(`DATE_FORMAT(${
|
|
632
|
+
whereObj = literal(`DATE_FORMAT(${tableName}.${fieldName}, "%H") > "${filter.value}"`)
|
|
610
633
|
break
|
|
611
634
|
|
|
612
635
|
default:
|
|
@@ -624,7 +647,7 @@ const filtersToSequelizeWhere = async(filters, opt) => {
|
|
|
624
647
|
switch(filterFn) {
|
|
625
648
|
|
|
626
649
|
case 'format_hour':
|
|
627
|
-
whereObj = literal(`DATE_FORMAT(${
|
|
650
|
+
whereObj = literal(`DATE_FORMAT(${tableName}.${fieldName}, "%H") >= "${filter.value}"`)
|
|
628
651
|
break
|
|
629
652
|
|
|
630
653
|
default:
|
|
@@ -736,41 +759,74 @@ const filtersToSequelizeWhere = async(filters, opt) => {
|
|
|
736
759
|
return whereObj
|
|
737
760
|
}
|
|
738
761
|
|
|
739
|
-
|
|
740
|
-
|
|
762
|
+
if(Array.isArray(filters)){
|
|
763
|
+
for(let filter of filters) {
|
|
764
|
+
if (filter.enabled === false) continue
|
|
741
765
|
|
|
742
|
-
|
|
766
|
+
const { key, value, modifier = 'and' } = filter
|
|
743
767
|
|
|
744
|
-
|
|
768
|
+
if(!keyColumns[key]) continue
|
|
745
769
|
|
|
746
|
-
|
|
770
|
+
if(Array.isArray(value)){
|
|
747
771
|
|
|
748
|
-
|
|
772
|
+
const modifierValue = []
|
|
749
773
|
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
774
|
+
for(let _value of value){
|
|
775
|
+
modifierValue.push(getValue({
|
|
776
|
+
..._value,
|
|
777
|
+
key
|
|
778
|
+
}, opt))
|
|
779
|
+
}
|
|
756
780
|
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
781
|
+
switch(modifier){
|
|
782
|
+
case 'or':
|
|
783
|
+
whereArr.push({
|
|
784
|
+
[Op.or]: modifierValue
|
|
785
|
+
})
|
|
786
|
+
break
|
|
763
787
|
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
788
|
+
default:
|
|
789
|
+
whereArr.push({
|
|
790
|
+
[Op.and]: modifierValue
|
|
791
|
+
})
|
|
792
|
+
break
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
else{
|
|
796
|
+
whereArr.push(getValue(filter, opt))
|
|
769
797
|
}
|
|
770
798
|
}
|
|
771
|
-
|
|
772
|
-
|
|
799
|
+
}
|
|
800
|
+
else if(filters && typeof filters === 'object'){
|
|
801
|
+
|
|
802
|
+
for(let key in filters){
|
|
803
|
+
if(!keyColumns[key]) continue
|
|
804
|
+
|
|
805
|
+
const value = filters[key]
|
|
806
|
+
|
|
807
|
+
if(Array.isArray(value)){
|
|
808
|
+
const modifierValue = []
|
|
809
|
+
|
|
810
|
+
for(let _value of value){
|
|
811
|
+
modifierValue.push(getValue({
|
|
812
|
+
..._value,
|
|
813
|
+
key
|
|
814
|
+
}, opt))
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
whereArr.push({
|
|
818
|
+
[Op.and]: modifierValue
|
|
819
|
+
})
|
|
820
|
+
}
|
|
821
|
+
else{
|
|
822
|
+
whereArr.push(getValue({
|
|
823
|
+
key,
|
|
824
|
+
operator: '=',
|
|
825
|
+
value
|
|
826
|
+
}, opt))
|
|
827
|
+
}
|
|
773
828
|
}
|
|
829
|
+
|
|
774
830
|
}
|
|
775
831
|
|
|
776
832
|
return {
|
|
@@ -1305,7 +1361,8 @@ const presetToSequelizeList = async(preset, opt = {}) => {
|
|
|
1305
1361
|
replacements: initialReplacements,
|
|
1306
1362
|
include,
|
|
1307
1363
|
order:initialOrder,
|
|
1308
|
-
logging = false
|
|
1364
|
+
logging = false,
|
|
1365
|
+
debug = false
|
|
1309
1366
|
} = opt
|
|
1310
1367
|
|
|
1311
1368
|
if(!model) return {}
|
|
@@ -1349,7 +1406,8 @@ const presetToSequelizeList = async(preset, opt = {}) => {
|
|
|
1349
1406
|
const { where:filterWhere, replacements:filterReplacements } = await filtersToSequelizeWhere(filters, {
|
|
1350
1407
|
config,
|
|
1351
1408
|
model,
|
|
1352
|
-
Sequelize: opt.Sequelize
|
|
1409
|
+
Sequelize: opt.Sequelize,
|
|
1410
|
+
debug
|
|
1353
1411
|
})
|
|
1354
1412
|
|
|
1355
1413
|
let searchWhere = {}, searchReplacements = []
|