@mixd-id/web-scaffold 0.1.2301231352 → 0.1.2301231353
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 +3 -2
- package/src/components/ContextMenu.vue +17 -6
- package/src/components/ListPage1.vue +172 -14
- package/src/utils/listpage1.js +14 -5
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mixd-id/web-scaffold",
|
|
3
3
|
"private": false,
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.2301231353",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"dev": "vite serve",
|
|
7
7
|
"build": "vite build",
|
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
"@vueuse/core": "^9.0.2",
|
|
25
25
|
"adm-zip": "^0.5.10",
|
|
26
26
|
"axios": "^1.3.4",
|
|
27
|
+
"chart.js": "^4.2.1",
|
|
27
28
|
"compression": "^1.7.4",
|
|
28
29
|
"cookie-parser": "^1.4.6",
|
|
29
30
|
"cors": "^2.8.5",
|
|
@@ -41,13 +42,13 @@
|
|
|
41
42
|
"serve-static": "^1.15.0",
|
|
42
43
|
"tailwindcss": "^3.2.4",
|
|
43
44
|
"vue": "^3.2.25",
|
|
45
|
+
"vue-chartjs": "^5.2.0",
|
|
44
46
|
"vue-router": "^4.0.14"
|
|
45
47
|
},
|
|
46
48
|
"devDependencies": {
|
|
47
49
|
"@vitejs/plugin-vue": "^2.2.0",
|
|
48
50
|
"autoprefixer": "^10.4.4",
|
|
49
51
|
"postcss": "^8.4.12",
|
|
50
|
-
"sequelize": "^6.29.0",
|
|
51
52
|
"vite": "^2.8.0"
|
|
52
53
|
},
|
|
53
54
|
"description": "This scaffold based on express vitejs vuejs",
|
|
@@ -56,7 +56,8 @@ export default {
|
|
|
56
56
|
return {
|
|
57
57
|
computedStyle: null,
|
|
58
58
|
isOpen: false,
|
|
59
|
-
context: null
|
|
59
|
+
context: null,
|
|
60
|
+
closing: false
|
|
60
61
|
}
|
|
61
62
|
},
|
|
62
63
|
|
|
@@ -80,16 +81,17 @@ export default {
|
|
|
80
81
|
if(typeof window === 'undefined') return
|
|
81
82
|
|
|
82
83
|
const transitionEnd = () => {
|
|
83
|
-
this.$emit('open')
|
|
84
84
|
window.addEventListener('click', this.onDismiss)
|
|
85
85
|
window.addEventListener('scroll', this.onDismiss)
|
|
86
86
|
this.$refs.contextMenu.removeEventListener('transitionend', transitionEnd)
|
|
87
|
+
this.$emit('open')
|
|
87
88
|
}
|
|
88
89
|
|
|
90
|
+
this.$refs.contextMenu.removeEventListener('transitionend', transitionEnd)
|
|
89
91
|
this.$refs.contextMenu.addEventListener('transitionend', transitionEnd)
|
|
90
|
-
|
|
92
|
+
this.$nextTick(() => {
|
|
91
93
|
this.$refs.contextMenu.classList.add(this.$style.active)
|
|
92
|
-
}
|
|
94
|
+
})
|
|
93
95
|
},
|
|
94
96
|
|
|
95
97
|
onDismiss(e){
|
|
@@ -104,6 +106,14 @@ export default {
|
|
|
104
106
|
|
|
105
107
|
open(caller, context){
|
|
106
108
|
|
|
109
|
+
if(this.isOpen){
|
|
110
|
+
return this.close(() => {
|
|
111
|
+
window.setTimeout(() => {
|
|
112
|
+
this.open(caller, context)
|
|
113
|
+
}, 30)
|
|
114
|
+
})
|
|
115
|
+
}
|
|
116
|
+
|
|
107
117
|
if(caller){
|
|
108
118
|
this.isOpen = true
|
|
109
119
|
}
|
|
@@ -181,10 +191,9 @@ export default {
|
|
|
181
191
|
else{
|
|
182
192
|
console.warn('Invalid context menu caller', this.caller)
|
|
183
193
|
}
|
|
184
|
-
|
|
185
194
|
},
|
|
186
195
|
|
|
187
|
-
close(){
|
|
196
|
+
close(fn){
|
|
188
197
|
const transitionEnd = () => {
|
|
189
198
|
window.removeEventListener('click', this.onDismiss)
|
|
190
199
|
window.removeEventListener('scroll', this.onDismiss)
|
|
@@ -194,7 +203,9 @@ export default {
|
|
|
194
203
|
this.computedStyle = null
|
|
195
204
|
this.isOpen = false
|
|
196
205
|
this.$emit('dismiss')
|
|
206
|
+
if(fn) fn()
|
|
197
207
|
}
|
|
208
|
+
this.$refs.contextMenu.removeEventListener('transitionend', transitionEnd)
|
|
198
209
|
this.$refs.contextMenu.addEventListener('transitionend', transitionEnd)
|
|
199
210
|
this.$refs.contextMenu.classList.remove(this.$style.active)
|
|
200
211
|
}
|
|
@@ -43,16 +43,14 @@
|
|
|
43
43
|
</div>
|
|
44
44
|
</div>
|
|
45
45
|
|
|
46
|
-
<
|
|
47
|
-
<
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
<div class="h-[2rem] bg-base-300 rounded-lg my-2"></div>
|
|
51
|
-
</div>
|
|
52
|
-
</template>
|
|
53
|
-
</VirtualScroll>
|
|
46
|
+
<div class="flex-1 mb-4 flex flex-col bg-base-500 border-text-50 border-[1px]" v-if="summary">
|
|
47
|
+
<VirtualTable v-if="preset.summary.mode === 'table'" :items="summary.items" :columns="summary.columns" class="flex-1"></VirtualTable>
|
|
48
|
+
|
|
49
|
+
<Bar v-else-if="preset.summary.mode === 'bar'" :data="chartData" :options="chartOptions" class="flex-1 w-full p-2"/>
|
|
54
50
|
|
|
55
|
-
|
|
51
|
+
</div>
|
|
52
|
+
|
|
53
|
+
<VirtualTable v-if="!hideDetails" class="flex-1" :columns="presetColumns" :items="items"
|
|
56
54
|
:appearances="appearances" @scroll-end="loadNext" :pinned="pinned">
|
|
57
55
|
<template v-for="column in presetColumns" #[colOf(column.key)]="{}">
|
|
58
56
|
<div :class="getHeader(column)" @click="openColumnOptions(column.key, $event.target.closest('.' + $style.header))">
|
|
@@ -163,7 +161,7 @@
|
|
|
163
161
|
</div>
|
|
164
162
|
|
|
165
163
|
<div class="flex justify-center">
|
|
166
|
-
<Tabs v-model="layout.presetTab" :items="
|
|
164
|
+
<Tabs v-model="layout.presetTab" :items="tabItems" />
|
|
167
165
|
</div>
|
|
168
166
|
</div>
|
|
169
167
|
|
|
@@ -187,7 +185,7 @@
|
|
|
187
185
|
</ListItem>
|
|
188
186
|
</div>
|
|
189
187
|
|
|
190
|
-
<div class="flex flex-col" v-if="layout.presetTab === 'filter'">
|
|
188
|
+
<div class="flex flex-col" v-else-if="layout.presetTab === 'filter'">
|
|
191
189
|
<div v-if="filterableColumns.length > 0">
|
|
192
190
|
<ListPage1Filter v-if="preset.filters" v-for="filter in preset.filters"
|
|
193
191
|
:filter="filter" :column="columns[filter.key]"
|
|
@@ -206,7 +204,7 @@
|
|
|
206
204
|
</div>
|
|
207
205
|
</div>
|
|
208
206
|
|
|
209
|
-
<div class="flex flex-col" v-if="layout.presetTab === 'sort'">
|
|
207
|
+
<div class="flex flex-col" v-else-if="layout.presetTab === 'sort'">
|
|
210
208
|
<div v-if="sortableColumns.length > 0" class="my-8">
|
|
211
209
|
<div v-for="sort in preset.sorts" class="py-4">
|
|
212
210
|
<div class="flex flex-row items-center gap-2">
|
|
@@ -244,6 +242,72 @@
|
|
|
244
242
|
</div>
|
|
245
243
|
</div>
|
|
246
244
|
|
|
245
|
+
<div class="flex flex-col gap-3 py-8" v-else-if="layout.presetTab === 'summary'">
|
|
246
|
+
|
|
247
|
+
<div class="flex flex-row items-center p-3">
|
|
248
|
+
<label class="text-text-400 flex-1">Enabled</label>
|
|
249
|
+
<Checkbox v-model="preset.summary.enabled" @change="load"/>
|
|
250
|
+
</div>
|
|
251
|
+
|
|
252
|
+
<div class="flex flex-row items-center p-3">
|
|
253
|
+
<label class="text-text-400 flex-1">Hide Details</label>
|
|
254
|
+
<Checkbox v-model="preset.summary.hideDetails" @change="load"/>
|
|
255
|
+
</div>
|
|
256
|
+
|
|
257
|
+
<div class="p-3">
|
|
258
|
+
<label class="text-text-400 flex-1">Row</label>
|
|
259
|
+
<div class="flex flex-row mt-2 gap-2">
|
|
260
|
+
<Dropdown v-model="preset.summary.rows[0].key" class="flex-1" @change="load">
|
|
261
|
+
<option value="" disabled selected>{{ $t('Add Column') }}</option>
|
|
262
|
+
<option v-for="column in filterableColumns" :value="column.key">{{ column.label }}</option>
|
|
263
|
+
</Dropdown>
|
|
264
|
+
<Dropdown v-model="preset.summary.rows[0].format" class="w-[100px]" @change="load">
|
|
265
|
+
<option value="">{{ $t('Default') }}</option>
|
|
266
|
+
<option value="date">{{ $t('Date') }}</option>
|
|
267
|
+
</Dropdown>
|
|
268
|
+
</div>
|
|
269
|
+
</div>
|
|
270
|
+
|
|
271
|
+
<div class="p-3">
|
|
272
|
+
<label class="text-text-400 flex-1">Column</label>
|
|
273
|
+
<div class="flex flex-row mt-2 gap-2">
|
|
274
|
+
<Dropdown v-model="preset.summary.columns[0].key" class="flex-1" @change="load">
|
|
275
|
+
<option value="" disabled selected>{{ $t('Add Column') }}</option>
|
|
276
|
+
<option v-for="column in filterableColumns" :value="column.key">{{ column.label }}</option>
|
|
277
|
+
</Dropdown>
|
|
278
|
+
<Dropdown v-model="preset.summary.columns[0].format" class="w-[100px]" @change="load">
|
|
279
|
+
<option value="">{{ $t('Default') }}</option>
|
|
280
|
+
<option value="date">{{ $t('Date') }}</option>
|
|
281
|
+
</Dropdown>
|
|
282
|
+
</div>
|
|
283
|
+
</div>
|
|
284
|
+
|
|
285
|
+
<div class="p-3">
|
|
286
|
+
<label class="text-text-400 flex-1">Values</label>
|
|
287
|
+
<div class="flex flex-row mt-2 gap-2">
|
|
288
|
+
<Dropdown v-model="preset.summary.values[0].key" class="flex-1" @change="load">
|
|
289
|
+
<option value="" disabled selected>{{ $t('Add Column') }}</option>
|
|
290
|
+
<option v-for="column in filterableColumns" :value="column.key">{{ column.label }}</option>
|
|
291
|
+
</Dropdown>
|
|
292
|
+
<Dropdown v-model="preset.summary.values[0].aggregrate" class="w-[100px]" @change="load">
|
|
293
|
+
<option value="">{{ $t('Default') }}</option>
|
|
294
|
+
<option value="count">{{ $t('Count') }}</option>
|
|
295
|
+
</Dropdown>
|
|
296
|
+
</div>
|
|
297
|
+
</div>
|
|
298
|
+
|
|
299
|
+
<div class="p-3">
|
|
300
|
+
<label class="text-text-400 flex-1">Type</label>
|
|
301
|
+
<div class="mt-2">
|
|
302
|
+
<Dropdown v-model="preset.summary.mode" class="flex-1" @change="load">
|
|
303
|
+
<option value="table">{{ $t('Table') }}</option>
|
|
304
|
+
<option value="bar">{{ $t('Bar Chart') }}</option>
|
|
305
|
+
</Dropdown>
|
|
306
|
+
</div>
|
|
307
|
+
</div>
|
|
308
|
+
|
|
309
|
+
</div>
|
|
310
|
+
|
|
247
311
|
</div>
|
|
248
312
|
|
|
249
313
|
<div class="px-6 py-3 flex flex-row gap-2"></div>
|
|
@@ -261,9 +325,15 @@
|
|
|
261
325
|
|
|
262
326
|
import throttle from "lodash/throttle";
|
|
263
327
|
import {urlQuery} from "../utils/helpers.mjs";
|
|
328
|
+
import { Bar } from 'vue-chartjs'
|
|
329
|
+
import { Chart as ChartJS, Title, Tooltip, Legend, BarElement, CategoryScale, LinearScale } from 'chart.js'
|
|
330
|
+
|
|
331
|
+
ChartJS.register(Title, Tooltip, Legend, BarElement, CategoryScale, LinearScale)
|
|
264
332
|
|
|
265
333
|
export default{
|
|
266
334
|
|
|
335
|
+
components: { Bar },
|
|
336
|
+
|
|
267
337
|
inject: [ 'socket', 'confirm', 'alert', 'socketEmit', 'toast' ],
|
|
268
338
|
|
|
269
339
|
props:{
|
|
@@ -345,12 +415,90 @@ export default{
|
|
|
345
415
|
currentFilters(){
|
|
346
416
|
if(!this.preset.filters) return []
|
|
347
417
|
return this.preset.filters.filter((_) => _.key === this.selectedColumn)
|
|
418
|
+
},
|
|
419
|
+
|
|
420
|
+
chartOptions(){
|
|
421
|
+
|
|
422
|
+
var style = getComputedStyle(document.body)
|
|
423
|
+
var gridColor = style.getPropertyValue('--text-50')
|
|
424
|
+
|
|
425
|
+
return {
|
|
426
|
+
responsive: true,
|
|
427
|
+
maintainAspectRatio: false,
|
|
428
|
+
plugins: {
|
|
429
|
+
legend: true
|
|
430
|
+
},
|
|
431
|
+
scales: {
|
|
432
|
+
x: {
|
|
433
|
+
grid: {
|
|
434
|
+
color: `rgb(${gridColor})`
|
|
435
|
+
}
|
|
436
|
+
},
|
|
437
|
+
y: {
|
|
438
|
+
grid: {
|
|
439
|
+
color: `rgb(${gridColor})`
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
},
|
|
445
|
+
|
|
446
|
+
chartData(){
|
|
447
|
+
|
|
448
|
+
//console.log(this.summary)
|
|
449
|
+
|
|
450
|
+
const labels = []
|
|
451
|
+
this.summary.items.forEach((item) => {
|
|
452
|
+
labels.push(item.dfDate)
|
|
453
|
+
})
|
|
454
|
+
|
|
455
|
+
const datasets = []
|
|
456
|
+
this.summary.columns.forEach((column) => {
|
|
457
|
+
if(column.key === 'dfDate') return
|
|
458
|
+
|
|
459
|
+
const data = []
|
|
460
|
+
this.summary.items.forEach((item) => {
|
|
461
|
+
data.push(parseInt(item[column.key]))
|
|
462
|
+
})
|
|
463
|
+
|
|
464
|
+
const dataset = {
|
|
465
|
+
label: column.key,
|
|
466
|
+
data,
|
|
467
|
+
backgroundColor: this.chartOpt.backgroundColors[datasets.length % 9],
|
|
468
|
+
borderColor: this.chartOpt.borderColors[datasets.length % 9]
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
datasets.push(dataset)
|
|
472
|
+
})
|
|
473
|
+
|
|
474
|
+
//console.log(labels)
|
|
475
|
+
//console.log(datasets)
|
|
476
|
+
|
|
477
|
+
return {
|
|
478
|
+
labels,
|
|
479
|
+
datasets
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
/*return {
|
|
483
|
+
labels: [ 'January', 'February', 'March' ],
|
|
484
|
+
datasets: [ { data: [40, 20, 12] } ]
|
|
485
|
+
}*/
|
|
486
|
+
},
|
|
487
|
+
|
|
488
|
+
hideDetails(){
|
|
489
|
+
if(this.preset && this.preset.summary && this.preset.summary.hideDetails)
|
|
490
|
+
return this.preset.summary.hideDetails
|
|
491
|
+
return false;
|
|
348
492
|
}
|
|
349
493
|
|
|
350
494
|
},
|
|
351
495
|
|
|
352
496
|
data(){
|
|
353
497
|
return {
|
|
498
|
+
chartOpt: {
|
|
499
|
+
backgroundColors: [ '#5D9CEC', '#4FC1E9', '#48CFAD', '#A0D468', '#FFCE54', '#FC6E51', '#ED5565', '#AC92EC', '#EC87C0' ],
|
|
500
|
+
borderColors: [ '#4A89DC', '#3BAFDA', '#37BC9B', '#8CC152', '#F6BB42', '#E9573F', '#DA4453', '#967ADC', '#D770AD' ],
|
|
501
|
+
},
|
|
354
502
|
layout: {
|
|
355
503
|
presetOpen: false,
|
|
356
504
|
presetTab: 'column',
|
|
@@ -358,12 +506,19 @@ export default{
|
|
|
358
506
|
presetFilterSelector: null,
|
|
359
507
|
presetSortSelector: null,
|
|
360
508
|
},
|
|
509
|
+
tabItems: [
|
|
510
|
+
{ text:'Columns', value:'column' },
|
|
511
|
+
{ text:'Filters', value:'filter' },
|
|
512
|
+
{ text:'Sorts', value:'sort' },
|
|
513
|
+
{ text:'Summary', value:'summary' },
|
|
514
|
+
],
|
|
361
515
|
name: null,
|
|
362
516
|
columns: [],
|
|
363
517
|
selectedColumn: null,
|
|
364
518
|
presets: [],
|
|
365
519
|
presetIdx: -1,
|
|
366
520
|
items: [],
|
|
521
|
+
summary: null,
|
|
367
522
|
count: null,
|
|
368
523
|
hasNext: false,
|
|
369
524
|
modal: null
|
|
@@ -399,6 +554,7 @@ export default{
|
|
|
399
554
|
this.patchPresets()
|
|
400
555
|
this.$nextTick(() => {
|
|
401
556
|
this.items = data.items
|
|
557
|
+
this.summary = data.summary
|
|
402
558
|
this.hasNext = data.hasNext
|
|
403
559
|
this.count = data.count
|
|
404
560
|
})
|
|
@@ -452,8 +608,10 @@ export default{
|
|
|
452
608
|
preset: this.preset
|
|
453
609
|
}, (res) => {
|
|
454
610
|
const data = res.data ? res.data : res
|
|
455
|
-
|
|
456
|
-
|
|
611
|
+
this.items = data.items
|
|
612
|
+
this.summary = data.summary
|
|
613
|
+
this.hasNext = data.hasNext
|
|
614
|
+
this.count = data.count
|
|
457
615
|
})
|
|
458
616
|
},
|
|
459
617
|
|
package/src/utils/listpage1.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
const { Op } = require('sequelize')
|
|
2
|
-
const Sequelize = require('sequelize')
|
|
1
|
+
const { Op } = module.parent.require('sequelize')
|
|
2
|
+
const Sequelize = module.parent.require('sequelize')
|
|
3
3
|
const dayjs = require("dayjs");
|
|
4
4
|
const util = require("util");
|
|
5
5
|
|
|
@@ -124,7 +124,7 @@ let ListPage1 = {
|
|
|
124
124
|
|
|
125
125
|
let attributeIncludes
|
|
126
126
|
if(this.getAttributeIncludes){
|
|
127
|
-
attributeIncludes = this.getAttributeIncludes()
|
|
127
|
+
attributeIncludes = this.getAttributeIncludes(preset)
|
|
128
128
|
}
|
|
129
129
|
|
|
130
130
|
const { rows:items, count } = await this.model.findAndCountAll({
|
|
@@ -136,7 +136,7 @@ let ListPage1 = {
|
|
|
136
136
|
order,
|
|
137
137
|
limit: itemsPerPage + 1,
|
|
138
138
|
replacements,
|
|
139
|
-
include: this.getModelIncludes ? this.getModelIncludes() : undefined
|
|
139
|
+
include: this.getModelIncludes ? this.getModelIncludes(preset) : undefined
|
|
140
140
|
})
|
|
141
141
|
|
|
142
142
|
const hasNext = items.length > itemsPerPage
|
|
@@ -144,6 +144,14 @@ let ListPage1 = {
|
|
|
144
144
|
items.splice(items.length - 1, 1)
|
|
145
145
|
}
|
|
146
146
|
|
|
147
|
+
let summary
|
|
148
|
+
if(!afterItem && preset.summary && preset.summary.enabled){
|
|
149
|
+
summary = await this.loadSummary(preset, {
|
|
150
|
+
where,
|
|
151
|
+
replacements
|
|
152
|
+
})
|
|
153
|
+
}
|
|
154
|
+
|
|
147
155
|
if(this.socket){
|
|
148
156
|
this.socket.joinWithArgs(this.channel, { preset })
|
|
149
157
|
}
|
|
@@ -151,7 +159,8 @@ let ListPage1 = {
|
|
|
151
159
|
return {
|
|
152
160
|
items,
|
|
153
161
|
hasNext,
|
|
154
|
-
count
|
|
162
|
+
count,
|
|
163
|
+
summary
|
|
155
164
|
}
|
|
156
165
|
},
|
|
157
166
|
|