w-web-perm 1.0.48 → 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 +32 -23
- package/src/components/LayoutContentPemis.vue +32 -23
- package/src/components/LayoutContentStaInfor.vue +536 -0
- package/src/components/LayoutContentTargets.vue +32 -23
- package/src/components/LayoutContentUsers.vue +32 -23
- package/src/components/LayoutState.vue +5 -0
- package/src/components/VeCgrups.vue +12 -3
- package/src/components/VeCpemis.vue +12 -3
- package/src/components/VeCrules.vue +12 -3
- package/src/components/VeGrupBlngUsers.vue +18 -6
- package/src/components/VePemiBlngGrups.vue +18 -6
- 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 +11 -0
- 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.3324392e.js +0 -1
- package/dist/js/app.3324392e.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
|
|
|
@@ -822,20 +824,28 @@ export default {
|
|
|
822
824
|
|
|
823
825
|
},
|
|
824
826
|
|
|
825
|
-
saveTargets: function() {
|
|
826
|
-
// console.log('method saveTargets')
|
|
827
|
+
saveTargets: function(msg) {
|
|
827
828
|
|
|
828
829
|
let vo = this
|
|
829
830
|
|
|
830
|
-
|
|
831
|
-
|
|
831
|
+
//第一行立刻釋放按鈕視覺鎖
|
|
832
|
+
msg.pm.resolve()
|
|
832
833
|
|
|
833
|
-
|
|
834
|
-
|
|
834
|
+
//fire-and-forget, 不 await
|
|
835
|
+
vo.doSaveTargets()
|
|
835
836
|
|
|
836
|
-
|
|
837
|
+
},
|
|
838
|
+
|
|
839
|
+
doSaveTargets: function() {
|
|
840
|
+
// console.log('method doSaveTargets')
|
|
841
|
+
|
|
842
|
+
let vo = this
|
|
843
|
+
|
|
844
|
+
async function core() {
|
|
845
|
+
|
|
846
|
+
//1) 同步檢測 (在開 loading 之前)
|
|
837
847
|
if (isestr(vo.isError)) {
|
|
838
|
-
vo.$
|
|
848
|
+
await vo.$dg.showCheckYes(`${vo.isError}`)
|
|
839
849
|
return
|
|
840
850
|
}
|
|
841
851
|
|
|
@@ -844,35 +854,34 @@ export default {
|
|
|
844
854
|
|
|
845
855
|
//check
|
|
846
856
|
if (size(rows) === 0) {
|
|
847
|
-
vo.$
|
|
857
|
+
await vo.$dg.showCheckYes(`${vo.$t('targetAddEmpty')}`)
|
|
848
858
|
return
|
|
849
859
|
}
|
|
850
860
|
|
|
851
|
-
//
|
|
861
|
+
//2) 確定打 API 才開 loading
|
|
862
|
+
vo.$ui.updateLoading(true)
|
|
863
|
+
|
|
864
|
+
//3) updateTargets, 各自 catch + 旗標短路
|
|
865
|
+
let okSave = false
|
|
852
866
|
await vo.$fapi.updateTargets(rows)
|
|
853
|
-
.
|
|
854
|
-
|
|
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)}`)
|
|
855
871
|
})
|
|
856
|
-
|
|
857
|
-
//check
|
|
858
|
-
if (errTemp !== null) {
|
|
859
|
-
vo.$alert(`${vo.$t('targetSaveTargetsFail')}: ${errTemp}`, { type: 'error' })
|
|
860
|
-
return
|
|
861
|
-
}
|
|
872
|
+
if (!okSave) return
|
|
862
873
|
|
|
863
874
|
//isModified
|
|
864
875
|
vo.isModified = false
|
|
865
876
|
|
|
866
|
-
//alert
|
|
867
|
-
vo.$
|
|
877
|
+
//alert(showCheckYes 前關 loading,避免 modal 等待期間 loading 疊在底下)
|
|
878
|
+
vo.$ui.updateLoading(false)
|
|
879
|
+
await vo.$dg.showCheckYes(vo.$t('targetSaveTargetsSuccess'), { type: 'success' })
|
|
868
880
|
|
|
869
881
|
}
|
|
870
882
|
|
|
871
883
|
//core
|
|
872
884
|
core()
|
|
873
|
-
// .then((res) => {
|
|
874
|
-
// console.log('then', res)
|
|
875
|
-
// })
|
|
876
885
|
.catch((err) => {
|
|
877
886
|
console.log('catch', err)
|
|
878
887
|
vo.$alert(vo.$t('anUnexpectedErrorOccurred'), { type: 'error' })
|