w-web-perm 1.0.47 → 1.0.49
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/.github/workflows/ci-test.yml +2 -2
- package/_gh-pages-deploy/.nojekyll +0 -0
- package/_gh-pages-deploy/WWebPerm.html +938 -0
- package/_gh-pages-deploy/WWebPerm.mjs.html +1228 -0
- package/_gh-pages-deploy/fonts/Montserrat/Montserrat-Bold.eot +0 -0
- package/_gh-pages-deploy/fonts/Montserrat/Montserrat-Bold.ttf +0 -0
- package/_gh-pages-deploy/fonts/Montserrat/Montserrat-Bold.woff +0 -0
- package/_gh-pages-deploy/fonts/Montserrat/Montserrat-Bold.woff2 +0 -0
- package/_gh-pages-deploy/fonts/Montserrat/Montserrat-Regular.eot +0 -0
- package/_gh-pages-deploy/fonts/Montserrat/Montserrat-Regular.ttf +0 -0
- package/_gh-pages-deploy/fonts/Montserrat/Montserrat-Regular.woff +0 -0
- package/_gh-pages-deploy/fonts/Montserrat/Montserrat-Regular.woff2 +0 -0
- package/_gh-pages-deploy/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.eot +0 -0
- package/_gh-pages-deploy/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.svg +978 -0
- package/_gh-pages-deploy/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.ttf +0 -0
- package/_gh-pages-deploy/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.woff +0 -0
- package/_gh-pages-deploy/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.woff2 +0 -0
- package/_gh-pages-deploy/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.eot +0 -0
- package/_gh-pages-deploy/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.svg +1049 -0
- package/_gh-pages-deploy/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.ttf +0 -0
- package/_gh-pages-deploy/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.woff +0 -0
- package/_gh-pages-deploy/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.woff2 +0 -0
- package/_gh-pages-deploy/index.html +84 -0
- package/_gh-pages-deploy/scripts/collapse.js +39 -0
- package/_gh-pages-deploy/scripts/commonNav.js +28 -0
- package/_gh-pages-deploy/scripts/linenumber.js +25 -0
- package/_gh-pages-deploy/scripts/nav.js +12 -0
- package/_gh-pages-deploy/scripts/polyfill.js +4 -0
- package/_gh-pages-deploy/scripts/prettify/Apache-License-2.0.txt +202 -0
- package/_gh-pages-deploy/scripts/prettify/lang-css.js +2 -0
- package/_gh-pages-deploy/scripts/prettify/prettify.js +28 -0
- package/_gh-pages-deploy/scripts/search.js +99 -0
- package/_gh-pages-deploy/styles/jsdoc.css +776 -0
- package/_gh-pages-deploy/styles/prettify.css +80 -0
- package/dist/css/app.d8b16216.css +1 -0
- package/dist/css/{chunk-vendors.f149e124.css → chunk-vendors.2d3ef2ba.css} +1 -1
- package/dist/index.tmp +2 -2
- package/dist/js/app-legacy.fb48310a.js +1 -0
- package/dist/js/app.fb48310a.js +1 -0
- package/dist/js/chunk-vendors-legacy.8fdd4435.js +21 -0
- package/dist/js/chunk-vendors.8fdd4435.js +21 -0
- package/dist/w-web-perm.umd.js +2 -2
- package/dist/w-web-perm.umd.js.map +1 -1
- package/docs/WWebPerm.html +2 -2
- package/docs/WWebPerm.mjs.html +138 -199
- package/docs/index.html +1 -1
- package/package.json +15 -11
- package/public/index.html +2 -2
- package/server/WWebPerm.mjs +137 -198
- package/server/procCore.mjs +169 -0
- package/server/procLang.mjs +197 -0
- package/server/procStaInfor.mjs +118 -0
- package/server/provideTabs.mjs +1 -1
- package/server/srLog.mjs +30 -0
- package/server/staLogs/staEvent.callWorker.mjs +46 -0
- package/server/staLogs/staEvent.mjs +116 -0
- package/server/staLogs/staEvent.shellWorker.mjs +19 -0
- package/server/staLogs/staEventTable.mjs +100 -0
- package/server/staLogs/test_staEvent.mjs +22 -0
- package/settings.json +40 -0
- package/src/components/Common/CheckYes.vue +18 -1
- package/src/components/Layout.vue +1 -1
- package/src/components/LayoutContent.vue +13 -1
- package/src/components/LayoutContentGrups.vue +48 -24
- package/src/components/LayoutContentPemis.vue +48 -24
- package/src/components/LayoutContentStaInfor.vue +536 -0
- package/src/components/LayoutContentTargets.vue +48 -24
- package/src/components/LayoutContentUsers.vue +48 -24
- package/src/components/LayoutState.vue +5 -0
- package/src/components/VeCgrups.vue +62 -20
- package/src/components/VeCpemis.vue +62 -20
- package/src/components/VeCrules.vue +58 -16
- package/src/components/VeGrupBlngUsers.vue +77 -32
- package/src/components/VePemiBlngGrups.vue +77 -32
- package/src/getPerm.mjs +7 -17
- package/src/getPermUserInfor.mjs +8 -18
- package/src/main.js +1 -0
- package/src/plugins/mShare.mjs +8 -13
- package/src/plugins/mUI.mjs +15 -7
- package/src/store/mutations.mjs +1 -1
- package/srv.mjs +76 -101
- package/tableTags.json +1 -1
- package/db/wperm/grups/data.mdb +0 -0
- package/db/wperm/grups/lock.mdb +0 -0
- package/db/wperm/pemis/data.mdb +0 -0
- package/db/wperm/pemis/lock.mdb +0 -0
- package/db/wperm/targets/data.mdb +0 -0
- package/db/wperm/targets/lock.mdb +0 -0
- package/db/wperm/users/data.mdb +0 -0
- package/db/wperm/users/lock.mdb +0 -0
- package/dist/css/app.30e81bfe.css +0 -1
- package/dist/js/app-legacy.f1277a07.js +0 -1
- package/dist/js/app.f1277a07.js +0 -1
- package/dist/js/chunk-vendors-legacy.e8703728.js +0 -21
- package/dist/js/chunk-vendors.e8703728.js +0 -21
- package/test/getUserRules.test.mjs +0 -899
|
@@ -0,0 +1,536 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div
|
|
3
|
+
style="height:100%;"
|
|
4
|
+
v-domresize
|
|
5
|
+
@domresize="resizePanel"
|
|
6
|
+
:changeLang="changeLang"
|
|
7
|
+
>
|
|
8
|
+
|
|
9
|
+
<div
|
|
10
|
+
style="height:100%; background:#f9fafb; overflow-y:auto;"
|
|
11
|
+
v-if="!firstLoading && !errMsg"
|
|
12
|
+
>
|
|
13
|
+
|
|
14
|
+
<div style="padding:20px 30px;">
|
|
15
|
+
|
|
16
|
+
<!-- 頁面標題 -->
|
|
17
|
+
<div class="mb-8">
|
|
18
|
+
<div style="font-size:1.5rem; font-weight:600;">{{$t('mmStaInfor')}}</div>
|
|
19
|
+
</div>
|
|
20
|
+
|
|
21
|
+
<div class="space-y-8">
|
|
22
|
+
|
|
23
|
+
<!-- 事件發生頻率區塊 -->
|
|
24
|
+
<div>
|
|
25
|
+
<div class="bg-white p-5 rounded-lg shadow-sm border border-gray-200">
|
|
26
|
+
<div class="pb-2 flex items-center font-semibold text-gray-700">
|
|
27
|
+
<WIcon :icon="mdiChartBoxOutline" :size="24" :color="'currentColor'" :colorHover="'currentColor'" class="text-purple-600 mr-2"></WIcon>
|
|
28
|
+
<span class="text-lg">{{$t('staEventTitle')}}</span>
|
|
29
|
+
</div>
|
|
30
|
+
<!-- 控制區: 事件多選 checklist + 全選/清除 + 全部加總 + 時間分組 -->
|
|
31
|
+
<div class="staCtrl">
|
|
32
|
+
|
|
33
|
+
<!-- 事件多選 checklist -->
|
|
34
|
+
<div class="staEventSel" v-if="allEvents.length > 0">
|
|
35
|
+
<span class="text-sm font-medium text-gray-700 mr-2">{{$t('staSelectEvents')}}</span>
|
|
36
|
+
<div class="staEventList">
|
|
37
|
+
<label
|
|
38
|
+
v-for="ev in allEvents"
|
|
39
|
+
:key="ev"
|
|
40
|
+
class="staEventItem"
|
|
41
|
+
>
|
|
42
|
+
<input type="checkbox" :value="ev" v-model="selectedEvents" @change="updateChartsDebounce" class="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded focus:ring-blue-500">
|
|
43
|
+
<span class="ml-1 text-sm text-gray-900">{{ev}}</span>
|
|
44
|
+
</label>
|
|
45
|
+
</div>
|
|
46
|
+
<div class="staEventBtns">
|
|
47
|
+
<button type="button" @click="selectAllEvents" class="staBtn">{{$t('staSelectAll')}}</button>
|
|
48
|
+
<button type="button" @click="clearEvents" class="staBtn">{{$t('staClear')}}</button>
|
|
49
|
+
</div>
|
|
50
|
+
</div>
|
|
51
|
+
|
|
52
|
+
<!-- 全部加總 + 時間分組 -->
|
|
53
|
+
<div class="staCtrlRight">
|
|
54
|
+
<div class="flex items-center">
|
|
55
|
+
<input id="staShowTotal" type="checkbox" v-model="showTotal" @change="updateChartsDebounce" class="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded focus:ring-blue-500">
|
|
56
|
+
<label for="staShowTotal" class="ml-2 text-sm font-medium text-gray-900">{{$t('staTotal')}}</label>
|
|
57
|
+
</div>
|
|
58
|
+
<select v-model="timeInterval" @change="changeTimeInterval" class="border rounded px-2 py-1 text-sm">
|
|
59
|
+
<option value="hr">{{$t('staTimeHr')}}</option>
|
|
60
|
+
<option value="day">{{$t('staTimeDay')}}</option>
|
|
61
|
+
</select>
|
|
62
|
+
</div>
|
|
63
|
+
|
|
64
|
+
</div>
|
|
65
|
+
|
|
66
|
+
<WEchartsVueDyn
|
|
67
|
+
style="width:100%; height:300px;"
|
|
68
|
+
:options="optEvent"
|
|
69
|
+
v-if="optEvent"
|
|
70
|
+
></WEchartsVueDyn>
|
|
71
|
+
|
|
72
|
+
<div
|
|
73
|
+
style="padding:0px; font-size:0.8rem; color:#777;"
|
|
74
|
+
v-else
|
|
75
|
+
>
|
|
76
|
+
{{$t('staNoData')}}
|
|
77
|
+
</div>
|
|
78
|
+
|
|
79
|
+
</div>
|
|
80
|
+
</div>
|
|
81
|
+
|
|
82
|
+
<!-- 事件統計表區塊 -->
|
|
83
|
+
<div>
|
|
84
|
+
<div class="bg-white p-5 rounded-lg shadow-sm border border-gray-200">
|
|
85
|
+
<div class="pb-2 flex items-center font-semibold text-gray-700">
|
|
86
|
+
<WIcon :icon="mdiTableLarge" :size="24" :color="'currentColor'" :colorHover="'currentColor'" class="text-purple-600 mr-2"></WIcon>
|
|
87
|
+
<span class="text-lg">{{$t('staTableTitle')}}</span>
|
|
88
|
+
</div>
|
|
89
|
+
|
|
90
|
+
<div v-if="eventTable.length > 0" class="overflow-x-auto relative shadow-md mt-2">
|
|
91
|
+
<table class="w-full text-sm text-left text-gray-500 border-collapse">
|
|
92
|
+
<thead class="text-xs text-gray-700 bg-gray-50">
|
|
93
|
+
<tr>
|
|
94
|
+
<th scope="col" class="py-3 px-6 border border-solid border-gray-200">{{$t('staColEvent')}}</th>
|
|
95
|
+
<th scope="col" class="py-3 px-6 border border-solid border-gray-200">{{$t('staColLast1Day')}}</th>
|
|
96
|
+
<th scope="col" class="py-3 px-6 border border-solid border-gray-200">{{$t('staColLast8Hour')}}</th>
|
|
97
|
+
<th scope="col" class="py-3 px-6 border border-solid border-gray-200">{{$t('staColLast4Hour')}}</th>
|
|
98
|
+
<th scope="col" class="py-3 px-6 border border-solid border-gray-200">{{$t('staColLast1Hour')}}</th>
|
|
99
|
+
</tr>
|
|
100
|
+
</thead>
|
|
101
|
+
<tbody>
|
|
102
|
+
<tr v-for="(row, index) in displayEventTable" :key="index" class="hover:bg-gray-50 transition-colors">
|
|
103
|
+
<td class="py-4 px-6 border border-solid border-gray-200">{{ row.event }}</td>
|
|
104
|
+
<td class="py-4 px-6 border border-solid border-gray-200">{{ formatNumber(row.last1Day) }}</td>
|
|
105
|
+
<td class="py-4 px-6 border border-solid border-gray-200">{{ formatNumber(row.last8Hour) }}</td>
|
|
106
|
+
<td class="py-4 px-6 border border-solid border-gray-200">{{ formatNumber(row.last4Hour) }}</td>
|
|
107
|
+
<td class="py-4 px-6 border border-solid border-gray-200">{{ formatNumber(row.last1Hour) }}</td>
|
|
108
|
+
</tr>
|
|
109
|
+
</tbody>
|
|
110
|
+
</table>
|
|
111
|
+
</div>
|
|
112
|
+
|
|
113
|
+
<div
|
|
114
|
+
style="padding:0px; font-size:0.8rem; color:#777;"
|
|
115
|
+
v-else
|
|
116
|
+
>
|
|
117
|
+
{{$t('staNoData')}}
|
|
118
|
+
</div>
|
|
119
|
+
|
|
120
|
+
</div>
|
|
121
|
+
</div>
|
|
122
|
+
|
|
123
|
+
</div>
|
|
124
|
+
|
|
125
|
+
</div>
|
|
126
|
+
|
|
127
|
+
</div>
|
|
128
|
+
<template v-else>
|
|
129
|
+
<div
|
|
130
|
+
style="padding:10px 15px; font-size:0.8rem;"
|
|
131
|
+
v-if="firstLoading"
|
|
132
|
+
>
|
|
133
|
+
{{$t('waitingData')}}
|
|
134
|
+
</div>
|
|
135
|
+
<div
|
|
136
|
+
style="padding:10px 15px; font-size:0.8rem;"
|
|
137
|
+
v-if="errMsg"
|
|
138
|
+
>
|
|
139
|
+
{{errMsg}}
|
|
140
|
+
</div>
|
|
141
|
+
</template>
|
|
142
|
+
|
|
143
|
+
</div>
|
|
144
|
+
</template>
|
|
145
|
+
|
|
146
|
+
<script>
|
|
147
|
+
import ot from 'dayjs'
|
|
148
|
+
import get from 'lodash-es/get.js'
|
|
149
|
+
import map from 'lodash-es/map.js'
|
|
150
|
+
import size from 'lodash-es/size.js'
|
|
151
|
+
import keys from 'lodash-es/keys.js'
|
|
152
|
+
import union from 'lodash-es/union.js'
|
|
153
|
+
import omit from 'lodash-es/omit.js'
|
|
154
|
+
import sortBy from 'lodash-es/sortBy.js'
|
|
155
|
+
import reverse from 'lodash-es/reverse.js'
|
|
156
|
+
import debounce from 'wsemi/src/debounce.mjs'
|
|
157
|
+
import isearr from 'wsemi/src/isearr.mjs'
|
|
158
|
+
import WEchartsVueDyn from 'w-component-vue/src/components/WEchartsVueDyn.vue'
|
|
159
|
+
import { mdiChartBoxOutline, mdiTableLarge } from '@mdi/js/mdi.js'
|
|
160
|
+
import WIcon from 'w-component-vue/src/components/WIcon.vue'
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
export default {
|
|
164
|
+
components: {
|
|
165
|
+
WEchartsVueDyn,
|
|
166
|
+
WIcon,
|
|
167
|
+
},
|
|
168
|
+
props: {
|
|
169
|
+
drawer: {
|
|
170
|
+
type: Boolean,
|
|
171
|
+
default: false,
|
|
172
|
+
},
|
|
173
|
+
},
|
|
174
|
+
data: function() {
|
|
175
|
+
return {
|
|
176
|
+
|
|
177
|
+
mdiChartBoxOutline,
|
|
178
|
+
mdiTableLarge,
|
|
179
|
+
|
|
180
|
+
dbc: debounce(300),
|
|
181
|
+
|
|
182
|
+
panelWidth: 100,
|
|
183
|
+
panelHeight: 100,
|
|
184
|
+
|
|
185
|
+
firstLoading: true,
|
|
186
|
+
errMsg: '',
|
|
187
|
+
|
|
188
|
+
timeLength: 7,
|
|
189
|
+
timeInterval: 'hr',
|
|
190
|
+
|
|
191
|
+
selectedEvents: [],
|
|
192
|
+
showTotal: false,
|
|
193
|
+
|
|
194
|
+
freqEvent: [],
|
|
195
|
+
|
|
196
|
+
optEvent: null,
|
|
197
|
+
|
|
198
|
+
eventTable: [],
|
|
199
|
+
|
|
200
|
+
}
|
|
201
|
+
},
|
|
202
|
+
mounted: function() {
|
|
203
|
+
// console.log('mounted')
|
|
204
|
+
|
|
205
|
+
let vo = this
|
|
206
|
+
|
|
207
|
+
//firstLoading, errMsg
|
|
208
|
+
vo.firstLoading = true
|
|
209
|
+
vo.errMsg = ''
|
|
210
|
+
|
|
211
|
+
//getAndRelaData
|
|
212
|
+
vo.getAndRelaData()
|
|
213
|
+
.catch((err) => {
|
|
214
|
+
console.log(err)
|
|
215
|
+
vo.errMsg = vo.$t('getDataError')
|
|
216
|
+
})
|
|
217
|
+
.finally(() => {
|
|
218
|
+
vo.firstLoading = false
|
|
219
|
+
})
|
|
220
|
+
|
|
221
|
+
},
|
|
222
|
+
computed: {
|
|
223
|
+
|
|
224
|
+
lang: function() {
|
|
225
|
+
let vo = this
|
|
226
|
+
return get(vo, `$store.state.lang`, '')
|
|
227
|
+
},
|
|
228
|
+
|
|
229
|
+
changeLang: function() {
|
|
230
|
+
let vo = this
|
|
231
|
+
let lang = vo.lang
|
|
232
|
+
vo.updateChartsDebounce(lang)
|
|
233
|
+
return ''
|
|
234
|
+
},
|
|
235
|
+
|
|
236
|
+
//allEvents: 從 freqEvent 各桶 data 取 event 名 union (排除 count), 排序
|
|
237
|
+
allEvents: function() {
|
|
238
|
+
let vo = this
|
|
239
|
+
let evs = []
|
|
240
|
+
map(vo.freqEvent, (entry) => {
|
|
241
|
+
let evNames = keys(omit(get(entry, 'data', {}), 'count'))
|
|
242
|
+
evs = union(evs, evNames)
|
|
243
|
+
})
|
|
244
|
+
return evs.sort()
|
|
245
|
+
},
|
|
246
|
+
|
|
247
|
+
//displayEventTable: 後端已依 last1Day 降序, 前端保險再 sortBy last1Day desc
|
|
248
|
+
displayEventTable: function() {
|
|
249
|
+
let vo = this
|
|
250
|
+
return reverse(sortBy(vo.eventTable.slice(), 'last1Day'))
|
|
251
|
+
},
|
|
252
|
+
|
|
253
|
+
},
|
|
254
|
+
methods: {
|
|
255
|
+
|
|
256
|
+
resizePanel: function(msg) {
|
|
257
|
+
// console.log('methods resizePanel', msg)
|
|
258
|
+
|
|
259
|
+
let vo = this
|
|
260
|
+
|
|
261
|
+
//panelWidth, panelHeight
|
|
262
|
+
vo.panelWidth = msg.snew.offsetWidth
|
|
263
|
+
vo.panelHeight = msg.snew.offsetHeight
|
|
264
|
+
|
|
265
|
+
},
|
|
266
|
+
|
|
267
|
+
genOpt: function(title, arr, keysPick) {
|
|
268
|
+
let vo = this
|
|
269
|
+
|
|
270
|
+
//check
|
|
271
|
+
if (size(arr) === 0) {
|
|
272
|
+
return null
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
//keysPick to array
|
|
276
|
+
if (!isearr(keysPick)) {
|
|
277
|
+
keysPick = [keysPick]
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
//colors
|
|
281
|
+
let colors = [
|
|
282
|
+
'#3b82f6', // blue-500
|
|
283
|
+
'#ef4444', // red-500
|
|
284
|
+
'#10b981', // emerald-500
|
|
285
|
+
'#f97316', // orange-500
|
|
286
|
+
'#8b5cf6', // violet-500
|
|
287
|
+
'#6366f1', // indigo-500
|
|
288
|
+
'#eab308', // yellow-500
|
|
289
|
+
'#ec4899', // pink-500
|
|
290
|
+
'#14b8a6', // teal-500
|
|
291
|
+
'#a855f7', // purple-500
|
|
292
|
+
]
|
|
293
|
+
function getColor(k) {
|
|
294
|
+
return colors[k % colors.length]
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
//getName: count 系列顯示為「全部」, 其餘 event 名照原字串
|
|
298
|
+
let getName = (valueKey) => {
|
|
299
|
+
if (valueKey === 'count') {
|
|
300
|
+
return vo.$t('staTotal')
|
|
301
|
+
}
|
|
302
|
+
return valueKey
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
//series: 折線圖, 每個 keysPick 一條系列, 缺值補 0
|
|
306
|
+
let series = map(keysPick, (valueKey, k) => {
|
|
307
|
+
let name = getName(valueKey)
|
|
308
|
+
let color = getColor(k)
|
|
309
|
+
return {
|
|
310
|
+
name,
|
|
311
|
+
type: 'line',
|
|
312
|
+
smooth: true,
|
|
313
|
+
data: map(arr, item => get(item, `data.${valueKey}`, 0)),
|
|
314
|
+
itemStyle: {
|
|
315
|
+
color,
|
|
316
|
+
},
|
|
317
|
+
emphasis: {
|
|
318
|
+
focus: 'series'
|
|
319
|
+
},
|
|
320
|
+
}
|
|
321
|
+
})
|
|
322
|
+
|
|
323
|
+
//formatX: hr 顯示 MM/DD HH:mm, day 顯示 MM/DD
|
|
324
|
+
let formatX = 'MM/DD'
|
|
325
|
+
if (vo.timeInterval === 'hr') {
|
|
326
|
+
formatX = 'MM/DD\nHH:mm'
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
//opt
|
|
330
|
+
let opt = {
|
|
331
|
+
title: {
|
|
332
|
+
text: title,
|
|
333
|
+
show: false, // 不顯示標題,由組件的HTML提供
|
|
334
|
+
},
|
|
335
|
+
tooltip: {
|
|
336
|
+
trigger: 'axis',
|
|
337
|
+
},
|
|
338
|
+
grid: {
|
|
339
|
+
left: '3%',
|
|
340
|
+
right: '4%',
|
|
341
|
+
bottom: '3%',
|
|
342
|
+
containLabel: true
|
|
343
|
+
},
|
|
344
|
+
legend: {
|
|
345
|
+
show: true,
|
|
346
|
+
},
|
|
347
|
+
xAxis: [
|
|
348
|
+
{
|
|
349
|
+
type: 'category',
|
|
350
|
+
boundaryGap: false,
|
|
351
|
+
data: map(arr, item => ot(item.time).format(formatX)),
|
|
352
|
+
axisTick: {
|
|
353
|
+
alignWithLabel: true
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
],
|
|
357
|
+
yAxis: [
|
|
358
|
+
{
|
|
359
|
+
type: 'value'
|
|
360
|
+
}
|
|
361
|
+
],
|
|
362
|
+
series,
|
|
363
|
+
}
|
|
364
|
+
return opt
|
|
365
|
+
},
|
|
366
|
+
|
|
367
|
+
grStaEvent: async function() {
|
|
368
|
+
let vo = this
|
|
369
|
+
|
|
370
|
+
//getStaEvent
|
|
371
|
+
await vo.$fapi.getStaEvent(vo.timeLength, vo.timeInterval)
|
|
372
|
+
.then((res) => {
|
|
373
|
+
// console.log('getStaEvent(freqEvent)', res)
|
|
374
|
+
vo.freqEvent = res
|
|
375
|
+
//首次資料載入後 selectedEvents 為空 → 預設全選
|
|
376
|
+
if (size(vo.selectedEvents) === 0) {
|
|
377
|
+
vo.selectedEvents = vo.allEvents.slice()
|
|
378
|
+
}
|
|
379
|
+
vo.updateCharts()
|
|
380
|
+
})
|
|
381
|
+
.catch((err) => {
|
|
382
|
+
console.log('getStaEvent catch', err)
|
|
383
|
+
throw err
|
|
384
|
+
})
|
|
385
|
+
|
|
386
|
+
},
|
|
387
|
+
|
|
388
|
+
grStaEventTable: async function() {
|
|
389
|
+
let vo = this
|
|
390
|
+
|
|
391
|
+
//getStaEventTable
|
|
392
|
+
await vo.$fapi.getStaEventTable()
|
|
393
|
+
.then((res) => {
|
|
394
|
+
// console.log('getStaEventTable(eventTable)', res)
|
|
395
|
+
vo.eventTable = res
|
|
396
|
+
})
|
|
397
|
+
.catch((err) => {
|
|
398
|
+
console.log('getStaEventTable catch', err)
|
|
399
|
+
throw err
|
|
400
|
+
})
|
|
401
|
+
|
|
402
|
+
},
|
|
403
|
+
|
|
404
|
+
getAndRelaData: async function() {
|
|
405
|
+
let vo = this
|
|
406
|
+
|
|
407
|
+
//grStaEvent
|
|
408
|
+
await vo.grStaEvent()
|
|
409
|
+
|
|
410
|
+
//grStaEventTable
|
|
411
|
+
await vo.grStaEventTable()
|
|
412
|
+
|
|
413
|
+
},
|
|
414
|
+
|
|
415
|
+
updateCharts: function() {
|
|
416
|
+
let vo = this
|
|
417
|
+
|
|
418
|
+
//ksEvent: showTotal 時前置 count 加總線, 其後接所選各 event (各一條系列)
|
|
419
|
+
let ksEvent = [
|
|
420
|
+
...(vo.showTotal ? ['count'] : []),
|
|
421
|
+
...vo.selectedEvents,
|
|
422
|
+
]
|
|
423
|
+
|
|
424
|
+
//optEvent
|
|
425
|
+
vo.optEvent = vo.genOpt(vo.$t('staEventTitle'), vo.freqEvent, ksEvent)
|
|
426
|
+
|
|
427
|
+
},
|
|
428
|
+
|
|
429
|
+
updateChartsDebounce: function() {
|
|
430
|
+
let vo = this
|
|
431
|
+
vo.dbc(() => {
|
|
432
|
+
vo.updateCharts()
|
|
433
|
+
})
|
|
434
|
+
},
|
|
435
|
+
|
|
436
|
+
selectAllEvents: function() {
|
|
437
|
+
let vo = this
|
|
438
|
+
vo.selectedEvents = vo.allEvents.slice()
|
|
439
|
+
vo.updateChartsDebounce()
|
|
440
|
+
},
|
|
441
|
+
|
|
442
|
+
clearEvents: function() {
|
|
443
|
+
let vo = this
|
|
444
|
+
vo.selectedEvents = []
|
|
445
|
+
vo.updateChartsDebounce()
|
|
446
|
+
},
|
|
447
|
+
|
|
448
|
+
formatNumber: function(num) {
|
|
449
|
+
if (num === null || num === undefined) {
|
|
450
|
+
return 0
|
|
451
|
+
}
|
|
452
|
+
return num.toLocaleString()
|
|
453
|
+
},
|
|
454
|
+
|
|
455
|
+
changeTimeInterval: function() {
|
|
456
|
+
let vo = this
|
|
457
|
+
|
|
458
|
+
//切換時間分組須重新向後端取數據 (hr/day 對應不同 time 粒度)
|
|
459
|
+
vo.firstLoading = true
|
|
460
|
+
vo.errMsg = ''
|
|
461
|
+
vo.getAndRelaData()
|
|
462
|
+
.catch((err) => {
|
|
463
|
+
console.log(err)
|
|
464
|
+
vo.errMsg = vo.$t('getDataError')
|
|
465
|
+
})
|
|
466
|
+
.finally(() => {
|
|
467
|
+
vo.firstLoading = false
|
|
468
|
+
})
|
|
469
|
+
|
|
470
|
+
},
|
|
471
|
+
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
</script>
|
|
475
|
+
|
|
476
|
+
<style scoped>
|
|
477
|
+
/* === Tailwind utility (非響應式) 同名定義: LayoutContentStaInfor; 由實際 Tailwind v3 產生規則抽出, 與 CDN 逐字一致 === */
|
|
478
|
+
.bg-gray-100 { --tw-bg-opacity: 1; background-color: rgb(243 244 246 / var(--tw-bg-opacity, 1)); }
|
|
479
|
+
.bg-gray-50 { --tw-bg-opacity: 1; background-color: rgb(249 250 251 / var(--tw-bg-opacity, 1)); }
|
|
480
|
+
.bg-white { --tw-bg-opacity: 1; background-color: rgb(255 255 255 / var(--tw-bg-opacity, 1)); }
|
|
481
|
+
.border { border-width: 1px; }
|
|
482
|
+
.border-collapse { border-collapse: collapse; }
|
|
483
|
+
.border-solid { border-style: solid; }
|
|
484
|
+
.border-gray-200 { --tw-border-opacity: 1; border-color: rgb(229 231 235 / var(--tw-border-opacity, 1)); }
|
|
485
|
+
.border-gray-300 { --tw-border-opacity: 1; border-color: rgb(209 213 219 / var(--tw-border-opacity, 1)); }
|
|
486
|
+
.flex { display: flex; }
|
|
487
|
+
.focus\:ring-blue-500:focus { --tw-ring-opacity: 1; --tw-ring-color: rgb(59 130 246 / var(--tw-ring-opacity, 1)); }
|
|
488
|
+
.font-medium { font-weight: 500; }
|
|
489
|
+
.font-semibold { font-weight: 600; }
|
|
490
|
+
.h-4 { height: 1rem; }
|
|
491
|
+
.hover\:bg-gray-50:hover { --tw-bg-opacity: 1; background-color: rgb(249 250 251 / var(--tw-bg-opacity, 1)); }
|
|
492
|
+
.items-center { align-items: center; }
|
|
493
|
+
.justify-end { justify-content: flex-end; }
|
|
494
|
+
.mb-8 { margin-bottom: 2rem; }
|
|
495
|
+
.ml-2 { margin-left: 0.5rem; }
|
|
496
|
+
.mr-2 { margin-right: 0.5rem; }
|
|
497
|
+
.mt-2 { margin-top: 0.5rem; }
|
|
498
|
+
.overflow-x-auto { overflow-x: auto; }
|
|
499
|
+
.p-5 { padding: 1.25rem; }
|
|
500
|
+
.pb-2 { padding-bottom: 0.5rem; }
|
|
501
|
+
.px-2 { padding-left: 0.5rem; padding-right: 0.5rem; }
|
|
502
|
+
.px-6 { padding-left: 1.5rem; padding-right: 1.5rem; }
|
|
503
|
+
.py-1 { padding-top: 0.25rem; padding-bottom: 0.25rem; }
|
|
504
|
+
.py-3 { padding-top: 0.75rem; padding-bottom: 0.75rem; }
|
|
505
|
+
.py-4 { padding-top: 1rem; padding-bottom: 1rem; }
|
|
506
|
+
.relative { position: relative; }
|
|
507
|
+
.rounded { border-radius: 0.25rem; }
|
|
508
|
+
.rounded-lg { border-radius: 0.5rem; }
|
|
509
|
+
.shadow-md { --tw-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1); --tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color), 0 2px 4px -2px var(--tw-shadow-color); box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); }
|
|
510
|
+
.space-x-4 > :not([hidden]) ~ :not([hidden]) { --tw-space-x-reverse: 0; margin-right: calc(1rem * var(--tw-space-x-reverse)); margin-left: calc(1rem * calc(1 - var(--tw-space-x-reverse))); }
|
|
511
|
+
.space-y-8 > :not([hidden]) ~ :not([hidden]) { --tw-space-y-reverse: 0; margin-top: calc(2rem * calc(1 - var(--tw-space-y-reverse))); margin-bottom: calc(2rem * var(--tw-space-y-reverse)); }
|
|
512
|
+
.text-blue-600 { --tw-text-opacity: 1; color: rgb(37 99 235 / var(--tw-text-opacity, 1)); }
|
|
513
|
+
.text-gray-500 { --tw-text-opacity: 1; color: rgb(107 114 128 / var(--tw-text-opacity, 1)); }
|
|
514
|
+
.text-gray-700 { --tw-text-opacity: 1; color: rgb(55 65 81 / var(--tw-text-opacity, 1)); }
|
|
515
|
+
.text-gray-900 { --tw-text-opacity: 1; color: rgb(17 24 39 / var(--tw-text-opacity, 1)); }
|
|
516
|
+
.text-left { text-align: left; }
|
|
517
|
+
.text-lg { font-size: 1.125rem; line-height: 1.75rem; }
|
|
518
|
+
.text-purple-600 { --tw-text-opacity: 1; color: rgb(147 51 234 / var(--tw-text-opacity, 1)); }
|
|
519
|
+
.text-sm { font-size: 0.875rem; line-height: 1.25rem; }
|
|
520
|
+
.text-xs { font-size: 0.75rem; line-height: 1rem; }
|
|
521
|
+
.transition-colors { transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, -webkit-text-decoration-color; transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); transition-duration: 150ms; }
|
|
522
|
+
.w-4 { width: 1rem; }
|
|
523
|
+
.w-full { width: 100%; }
|
|
524
|
+
|
|
525
|
+
/* === 控制區 (事件多選 checklist + 全部加總 + 時間分組), RWD flex-wrap === */
|
|
526
|
+
.staCtrl { display: flex; flex-wrap: wrap; justify-content: space-between; align-items: flex-start; gap: 12px; padding: 8px 0; }
|
|
527
|
+
.staEventSel { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; flex: 1 1 auto; min-width: 0; }
|
|
528
|
+
.staEventList { display: flex; flex-wrap: wrap; align-items: center; gap: 4px 14px; }
|
|
529
|
+
.staEventItem { display: inline-flex; align-items: center; cursor: pointer; }
|
|
530
|
+
.staEventBtns { display: inline-flex; align-items: center; gap: 6px; }
|
|
531
|
+
.staBtn { font-size: 0.8rem; color: rgb(37 99 235); border: 1px solid rgb(209 213 219); border-radius: 0.25rem; padding: 2px 8px; background: #fff; cursor: pointer; }
|
|
532
|
+
.staBtn:hover { background: rgb(243 244 246); }
|
|
533
|
+
.staCtrlRight { display: flex; flex-wrap: wrap; align-items: center; gap: 16px; }
|
|
534
|
+
.mr-2 { margin-right: 0.5rem; }
|
|
535
|
+
.ml-1 { margin-left: 0.25rem; }
|
|
536
|
+
</style>
|
|
@@ -39,6 +39,7 @@
|
|
|
39
39
|
|
|
40
40
|
<!-- 功能區 -->
|
|
41
41
|
<div
|
|
42
|
+
data-fmid="targets-toolbar"
|
|
42
43
|
style="padding:5px; border-top:1px solid #ddd; display:flex; align-items:center;"
|
|
43
44
|
_v-if="showIsEditable || isEditable"
|
|
44
45
|
>
|
|
@@ -185,6 +186,7 @@
|
|
|
185
186
|
:iconColor="'#eee'"
|
|
186
187
|
:iconColorHover="'#fff'"
|
|
187
188
|
:shadow="false"
|
|
189
|
+
:promiseUnlock="true"
|
|
188
190
|
@click="saveTargets"
|
|
189
191
|
></WButtonCircle>
|
|
190
192
|
|
|
@@ -346,12 +348,22 @@ export default {
|
|
|
346
348
|
vo.widthTargetId = get(vo, 'webInfor.widthTargetId', '')
|
|
347
349
|
vo.widthTargetDescription = get(vo, 'webInfor.widthTargetDescription', '')
|
|
348
350
|
|
|
349
|
-
|
|
351
|
+
//會觸發數據變更再導致opt變更導致觸發rowsChange等事件, 故得要延遲, 供組件偵測初始設定數據初始化之用
|
|
352
|
+
setTimeout(() => {
|
|
353
|
+
vo.firstSetting = false
|
|
354
|
+
// console.log('firstSetting', vo.firstSetting)
|
|
355
|
+
}, 1)
|
|
356
|
+
|
|
350
357
|
}
|
|
351
358
|
|
|
352
359
|
},
|
|
353
360
|
computed: {
|
|
354
361
|
|
|
362
|
+
syncState: function() {
|
|
363
|
+
let vo = this
|
|
364
|
+
return get(vo, '$store.state.syncState')
|
|
365
|
+
},
|
|
366
|
+
|
|
355
367
|
webInfor: function() {
|
|
356
368
|
let wi = get(this, `$store.state.webInfor`)
|
|
357
369
|
return wi
|
|
@@ -618,6 +630,11 @@ export default {
|
|
|
618
630
|
// console.log('rowsChange', rs)
|
|
619
631
|
// console.log('rowsChange cloneDeep(vo.opt.rows)', cloneDeep(vo.opt.rows))
|
|
620
632
|
|
|
633
|
+
//check
|
|
634
|
+
if (!vo.syncState || vo.firstLoading || vo.firstSetting) {
|
|
635
|
+
return
|
|
636
|
+
}
|
|
637
|
+
|
|
621
638
|
//isModified
|
|
622
639
|
vo.isModified = true
|
|
623
640
|
|
|
@@ -807,20 +824,28 @@ export default {
|
|
|
807
824
|
|
|
808
825
|
},
|
|
809
826
|
|
|
810
|
-
saveTargets: function() {
|
|
811
|
-
// console.log('method saveTargets')
|
|
827
|
+
saveTargets: function(msg) {
|
|
812
828
|
|
|
813
829
|
let vo = this
|
|
814
830
|
|
|
815
|
-
|
|
816
|
-
|
|
831
|
+
//第一行立刻釋放按鈕視覺鎖
|
|
832
|
+
msg.pm.resolve()
|
|
817
833
|
|
|
818
|
-
|
|
819
|
-
|
|
834
|
+
//fire-and-forget, 不 await
|
|
835
|
+
vo.doSaveTargets()
|
|
820
836
|
|
|
821
|
-
|
|
837
|
+
},
|
|
838
|
+
|
|
839
|
+
doSaveTargets: function() {
|
|
840
|
+
// console.log('method doSaveTargets')
|
|
841
|
+
|
|
842
|
+
let vo = this
|
|
843
|
+
|
|
844
|
+
async function core() {
|
|
845
|
+
|
|
846
|
+
//1) 同步檢測 (在開 loading 之前)
|
|
822
847
|
if (isestr(vo.isError)) {
|
|
823
|
-
vo.$
|
|
848
|
+
await vo.$dg.showCheckYes(`${vo.isError}`)
|
|
824
849
|
return
|
|
825
850
|
}
|
|
826
851
|
|
|
@@ -829,35 +854,34 @@ export default {
|
|
|
829
854
|
|
|
830
855
|
//check
|
|
831
856
|
if (size(rows) === 0) {
|
|
832
|
-
vo.$
|
|
857
|
+
await vo.$dg.showCheckYes(`${vo.$t('targetAddEmpty')}`)
|
|
833
858
|
return
|
|
834
859
|
}
|
|
835
860
|
|
|
836
|
-
//
|
|
861
|
+
//2) 確定打 API 才開 loading
|
|
862
|
+
vo.$ui.updateLoading(true)
|
|
863
|
+
|
|
864
|
+
//3) updateTargets, 各自 catch + 旗標短路
|
|
865
|
+
let okSave = false
|
|
837
866
|
await vo.$fapi.updateTargets(rows)
|
|
838
|
-
.
|
|
839
|
-
|
|
867
|
+
.then(() => { okSave = true })
|
|
868
|
+
.catch(async (err) => {
|
|
869
|
+
vo.$ui.updateLoading(false) //showCheckYes 前關 loading(modal 阻斷期間避免 loading 疊在底下)
|
|
870
|
+
await vo.$dg.showCheckYes(`${vo.$t('targetSaveTargetsFail')}: ${vo.$tErr(err)}`)
|
|
840
871
|
})
|
|
841
|
-
|
|
842
|
-
//check
|
|
843
|
-
if (errTemp !== null) {
|
|
844
|
-
vo.$alert(`${vo.$t('targetSaveTargetsFail')}: ${errTemp}`, { type: 'error' })
|
|
845
|
-
return
|
|
846
|
-
}
|
|
872
|
+
if (!okSave) return
|
|
847
873
|
|
|
848
874
|
//isModified
|
|
849
875
|
vo.isModified = false
|
|
850
876
|
|
|
851
|
-
//alert
|
|
852
|
-
vo.$
|
|
877
|
+
//alert(showCheckYes 前關 loading,避免 modal 等待期間 loading 疊在底下)
|
|
878
|
+
vo.$ui.updateLoading(false)
|
|
879
|
+
await vo.$dg.showCheckYes(vo.$t('targetSaveTargetsSuccess'), { type: 'success' })
|
|
853
880
|
|
|
854
881
|
}
|
|
855
882
|
|
|
856
883
|
//core
|
|
857
884
|
core()
|
|
858
|
-
// .then((res) => {
|
|
859
|
-
// console.log('then', res)
|
|
860
|
-
// })
|
|
861
885
|
.catch((err) => {
|
|
862
886
|
console.log('catch', err)
|
|
863
887
|
vo.$alert(vo.$t('anUnexpectedErrorOccurred'), { type: 'error' })
|