befly-admin-ui 1.10.1 → 1.10.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/components/detailPanel.vue +52 -54
  2. package/components/pageDialog.vue +131 -117
  3. package/components/pageTableDetail.vue +381 -402
  4. package/layouts/default.vue +212 -215
  5. package/package.json +2 -2
  6. package/views/config/dict/components/edit.vue +83 -60
  7. package/views/config/dict/index.vue +51 -55
  8. package/views/config/dictType/components/edit.vue +81 -57
  9. package/views/config/dictType/index.vue +37 -39
  10. package/views/config/system/components/edit.vue +97 -86
  11. package/views/config/system/index.vue +35 -37
  12. package/views/index/components/addonList.vue +14 -11
  13. package/views/index/components/environmentInfo.vue +25 -22
  14. package/views/index/components/operationLogs.vue +20 -16
  15. package/views/index/components/performanceMetrics.vue +24 -21
  16. package/views/index/components/serviceStatus.vue +26 -22
  17. package/views/index/components/systemNotifications.vue +24 -19
  18. package/views/index/components/systemOverview.vue +368 -385
  19. package/views/index/components/systemResources.vue +22 -19
  20. package/views/index/components/userInfo.vue +56 -56
  21. package/views/log/email/index.vue +96 -85
  22. package/views/log/error/index.vue +111 -117
  23. package/views/log/login/index.vue +15 -13
  24. package/views/log/operate/index.vue +31 -31
  25. package/views/login_1/index.vue +50 -32
  26. package/views/people/admin/components/edit.vue +1 -1
  27. package/views/people/admin/index.vue +27 -28
  28. package/views/permission/api/index.vue +44 -45
  29. package/views/permission/menu/index.vue +35 -35
  30. package/views/permission/role/components/api.vue +143 -144
  31. package/views/permission/role/components/edit.vue +74 -52
  32. package/views/permission/role/components/menu.vue +119 -121
  33. package/views/permission/role/index.vue +47 -49
  34. package/views/resource/gallery/index.vue +77 -78
  35. package/LICENSE +0 -201
@@ -9,35 +9,35 @@
9
9
  <div class="summary-grid">
10
10
  <div class="summary-card overview-card-small stat-primary">
11
11
  <div class="summary-content">
12
- <div class="summary-value">{{ permissionStats.menuCount }}</div>
12
+ <div class="summary-value">{{ $Data.permissionStats.menuCount }}</div>
13
13
  <div class="summary-label">菜单总数</div>
14
14
  </div>
15
15
  </div>
16
16
 
17
17
  <div class="summary-card overview-card-small stat-success">
18
18
  <div class="summary-content">
19
- <div class="summary-value">{{ permissionStats.apiCount }}</div>
19
+ <div class="summary-value">{{ $Data.permissionStats.apiCount }}</div>
20
20
  <div class="summary-label">接口总数</div>
21
21
  </div>
22
22
  </div>
23
23
 
24
24
  <div class="summary-card overview-card-small stat-warning">
25
25
  <div class="summary-content">
26
- <div class="summary-value">{{ permissionStats.roleCount }}</div>
26
+ <div class="summary-value">{{ $Data.permissionStats.roleCount }}</div>
27
27
  <div class="summary-label">角色总数</div>
28
28
  </div>
29
29
  </div>
30
30
 
31
31
  <div class="summary-card overview-card-small stat-danger">
32
32
  <div class="summary-content">
33
- <div class="summary-value">{{ realtimeStats.onlineCount }}</div>
33
+ <div class="summary-value">{{ $Data.realtimeStats.onlineCount }}</div>
34
34
  <div class="summary-label">当前在线</div>
35
35
  </div>
36
36
  </div>
37
37
 
38
38
  <div class="summary-card overview-card-small stat-critical">
39
39
  <div class="summary-content">
40
- <div class="summary-value">{{ errorTodayCount }}</div>
40
+ <div class="summary-value">{{ $Computed.errorTodayCount }}</div>
41
41
  <div class="summary-label">今日错误</div>
42
42
  </div>
43
43
  </div>
@@ -50,14 +50,14 @@
50
50
  </div>
51
51
 
52
52
  <div class="product-select-wrap">
53
- <TSelect :model-value="productState.selectedKey" class="product-select" placeholder="请选择产品" @change="selectProduct">
54
- <TOption v-for="item in productOptions" :key="item.key" :value="item.key" :label="item.productName" />
53
+ <TSelect :model-value="$Data.productState.selectedKey" class="product-select" placeholder="请选择产品" @change="$Method.selectProduct">
54
+ <TOption v-for="item in $Computed.productOptions" :key="item.key" :value="item.key" :label="item.productName" />
55
55
  </TSelect>
56
56
  </div>
57
57
  </div>
58
58
 
59
59
  <div class="product-metrics-grid">
60
- <div v-for="item in productPeriodCards" :key="item.key" class="metric-box metric-box-period">
60
+ <div v-for="item in $Computed.productPeriodCards" :key="item.key" class="metric-box metric-box-period">
61
61
  <div class="metric-period-label">{{ item.label }}</div>
62
62
  <div class="metric-pair-row">
63
63
  <span class="metric-pair-name">PV</span>
@@ -84,20 +84,20 @@
84
84
  </div>
85
85
  </div>
86
86
 
87
- <div v-if="selectedDaysHasData" class="trend-content">
87
+ <div v-if="$Computed.selectedDaysHasData" class="trend-content">
88
88
  <div class="trend-bars">
89
- <div v-for="item in selectedDaysBars" :key="item.reportDate" class="trend-bar-group">
89
+ <div v-for="item in $Computed.selectedDaysBars" :key="item.reportDate" class="trend-bar-group">
90
90
  <div class="trend-bar-stack">
91
- <div class="trend-bar trend-bar-pv" :style="{ height: item.pvHeight }" :title="`${formatReportDate(item.reportDate)} PV ${item.pv}`"></div>
92
- <div class="trend-bar trend-bar-uv" :style="{ height: item.uvHeight }" :title="`${formatReportDate(item.reportDate)} UV ${item.uv}`"></div>
91
+ <div class="trend-bar trend-bar-pv" :style="{ height: item.pvHeight }" :title="`${$Method.formatReportDate(item.reportDate)} PV ${item.pv}`"></div>
92
+ <div class="trend-bar trend-bar-uv" :style="{ height: item.uvHeight }" :title="`${$Method.formatReportDate(item.reportDate)} UV ${item.uv}`"></div>
93
93
  </div>
94
- <div class="trend-bar-label">{{ formatReportDate(item.reportDate) }}</div>
94
+ <div class="trend-bar-label">{{ $Method.formatReportDate(item.reportDate) }}</div>
95
95
  </div>
96
96
  </div>
97
97
 
98
98
  <div class="trend-briefs trend-briefs-wide">
99
- <div v-for="item in selectedDaysTrendPreview" :key="item.reportDate" class="trend-brief-item">
100
- <div class="trend-date">{{ formatReportDate(item.reportDate) }}</div>
99
+ <div v-for="item in $Computed.selectedDaysTrendPreview" :key="item.reportDate" class="trend-brief-item">
100
+ <div class="trend-date">{{ $Method.formatReportDate(item.reportDate) }}</div>
101
101
  <div class="trend-values">
102
102
  <span class="trend-value pv">{{ item.pv }}</span>
103
103
  <span class="trend-value uv">{{ item.uv }}</span>
@@ -113,9 +113,9 @@
113
113
  <div class="ua-grid">
114
114
  <div class="ua-card overview-card-small">
115
115
  <div class="ua-card-title">设备类型</div>
116
- <div v-if="uaStats.deviceTypes.length > 0" class="ua-list">
117
- <div v-for="item in uaStats.deviceTypes.slice(0, 5)" :key="`device-type-${item.name}`" class="ua-list-item">
118
- <span class="ua-list-name">{{ formatDeviceType(item.name) }}</span>
116
+ <div v-if="$Data.uaStats.deviceTypes.length > 0" class="ua-list">
117
+ <div v-for="item in $Data.uaStats.deviceTypes.slice(0, 5)" :key="`device-type-${item.name}`" class="ua-list-item">
118
+ <span class="ua-list-name">{{ $Method.formatDeviceType(item.name) }}</span>
119
119
  <span class="ua-list-count">{{ item.count }}</span>
120
120
  </div>
121
121
  </div>
@@ -124,8 +124,8 @@
124
124
 
125
125
  <div class="ua-card overview-card-small">
126
126
  <div class="ua-card-title">浏览器分布</div>
127
- <div v-if="uaStats.browsers.length > 0" class="ua-list">
128
- <div v-for="item in uaStats.browsers.slice(0, 5)" :key="`browser-${item.name}`" class="ua-list-item">
127
+ <div v-if="$Data.uaStats.browsers.length > 0" class="ua-list">
128
+ <div v-for="item in $Data.uaStats.browsers.slice(0, 5)" :key="`browser-${item.name}`" class="ua-list-item">
129
129
  <span class="ua-list-name">{{ item.name }}</span>
130
130
  <span class="ua-list-count">{{ item.count }}</span>
131
131
  </div>
@@ -135,8 +135,8 @@
135
135
 
136
136
  <div class="ua-card overview-card-small">
137
137
  <div class="ua-card-title">操作系统分布</div>
138
- <div v-if="uaStats.osList.length > 0" class="ua-list">
139
- <div v-for="item in uaStats.osList.slice(0, 5)" :key="`os-${item.name}`" class="ua-list-item">
138
+ <div v-if="$Data.uaStats.osList.length > 0" class="ua-list">
139
+ <div v-for="item in $Data.uaStats.osList.slice(0, 5)" :key="`os-${item.name}`" class="ua-list-item">
140
140
  <span class="ua-list-name">{{ item.name }}</span>
141
141
  <span class="ua-list-count">{{ item.count }}</span>
142
142
  </div>
@@ -146,8 +146,8 @@
146
146
 
147
147
  <div class="ua-card overview-card-small">
148
148
  <div class="ua-card-title">设备厂商</div>
149
- <div v-if="uaStats.deviceVendors.length > 0" class="ua-list">
150
- <div v-for="item in uaStats.deviceVendors.slice(0, 5)" :key="`vendor-${item.name}`" class="ua-list-item">
149
+ <div v-if="$Data.uaStats.deviceVendors.length > 0" class="ua-list">
150
+ <div v-for="item in $Data.uaStats.deviceVendors.slice(0, 5)" :key="`vendor-${item.name}`" class="ua-list-item">
151
151
  <span class="ua-list-name">{{ item.name }}</span>
152
152
  <span class="ua-list-count">{{ item.count }}</span>
153
153
  </div>
@@ -161,24 +161,24 @@
161
161
  <div class="ua-meta-group">
162
162
  <div class="ua-meta-block">
163
163
  <div class="ua-meta-title">设备型号</div>
164
- <div v-if="uaStats.deviceModels.length > 0" class="ua-chip-list">
165
- <span v-for="item in uaStats.deviceModels.slice(0, 6)" :key="`model-${item.name}`" class="ua-chip">{{ item.name }} · {{ item.count }}</span>
164
+ <div v-if="$Data.uaStats.deviceModels.length > 0" class="ua-chip-list">
165
+ <span v-for="item in $Data.uaStats.deviceModels.slice(0, 6)" :key="`model-${item.name}`" class="ua-chip">{{ item.name }} · {{ item.count }}</span>
166
166
  </div>
167
167
  <div v-else class="ua-empty ua-empty-inline">暂无设备型号数据</div>
168
168
  </div>
169
169
 
170
170
  <div class="ua-meta-block">
171
171
  <div class="ua-meta-title">渲染引擎</div>
172
- <div v-if="uaStats.engines.length > 0" class="ua-chip-list">
173
- <span v-for="item in uaStats.engines.slice(0, 6)" :key="`engine-${item.name}`" class="ua-chip">{{ item.name }} · {{ item.count }}</span>
172
+ <div v-if="$Data.uaStats.engines.length > 0" class="ua-chip-list">
173
+ <span v-for="item in $Data.uaStats.engines.slice(0, 6)" :key="`engine-${item.name}`" class="ua-chip">{{ item.name }} · {{ item.count }}</span>
174
174
  </div>
175
175
  <div v-else class="ua-empty ua-empty-inline">暂无渲染引擎数据</div>
176
176
  </div>
177
177
 
178
178
  <div class="ua-meta-block">
179
179
  <div class="ua-meta-title">CPU 架构</div>
180
- <div v-if="uaStats.cpuArchitectures.length > 0" class="ua-chip-list">
181
- <span v-for="item in uaStats.cpuArchitectures.slice(0, 6)" :key="`cpu-${item.name}`" class="ua-chip">{{ item.name }} · {{ item.count }}</span>
180
+ <div v-if="$Data.uaStats.cpuArchitectures.length > 0" class="ua-chip-list">
181
+ <span v-for="item in $Data.uaStats.cpuArchitectures.slice(0, 6)" :key="`cpu-${item.name}`" class="ua-chip">{{ item.name }} · {{ item.count }}</span>
182
182
  </div>
183
183
  <div v-else class="ua-empty ua-empty-inline">暂无 CPU 架构数据</div>
184
184
  </div>
@@ -194,409 +194,392 @@ import { computed, reactive } from "vue";
194
194
  import { Option as TOption, Select as TSelect } from "tdesign-vue-next";
195
195
  import { InfoCircleIcon, TrendingUpIcon } from "tdesign-icons-vue-next";
196
196
 
197
- import { $Config } from "@/plugins/config";
198
- import { $Http } from "@/plugins/http";
199
-
200
- const permissionStats = reactive({
201
- menuCount: 0,
202
- apiCount: 0,
203
- roleCount: 0
204
- });
205
-
206
- const realtimeStats = reactive({
207
- onlineCount: 0,
208
- todayPv: 0,
209
- todayUv: 0,
210
- days: []
211
- });
212
-
213
- const errorStats = reactive({
214
- trend: []
215
- });
216
-
217
- const productInfo = reactive({
218
- productName: String($Config.productName || "-"),
219
- productCode: String($Config.productCode || "-"),
220
- productVersion: String($Config.productVersion || "-")
221
- });
222
-
223
- const productState = reactive({
224
- selectedKey: "",
225
- options: []
226
- });
227
-
228
- const uaStats = reactive({
229
- deviceTypes: [],
230
- browsers: [],
231
- browserVersions: [],
232
- osList: [],
233
- osVersions: [],
234
- deviceVendors: [],
235
- deviceModels: [],
236
- engines: [],
237
- cpuArchitectures: []
238
- });
239
-
240
- const errorTodayTrend = computed(() => sortTrendList(errorStats.trend, "bucketTime"));
241
-
242
- const errorTodayCount = computed(() => {
243
- let total = 0;
244
-
245
- for (const item of errorTodayTrend.value) {
246
- total += Number(item.count || 0);
197
+ import { $Config } from "@/plugins/config.js";
198
+ import { $Http } from "@/plugins/http.js";
199
+
200
+ const $Data = reactive({
201
+ permissionStats: {
202
+ menuCount: 0,
203
+ apiCount: 0,
204
+ roleCount: 0
205
+ },
206
+ realtimeStats: {
207
+ onlineCount: 0,
208
+ todayPv: 0,
209
+ todayUv: 0,
210
+ days: []
211
+ },
212
+ errorStats: {
213
+ trend: []
214
+ },
215
+ productInfo: {
216
+ productName: String($Config.productName || "-"),
217
+ productCode: String($Config.productCode || "-"),
218
+ productVersion: String($Config.productVersion || "-")
219
+ },
220
+ productState: {
221
+ selectedKey: "",
222
+ options: []
223
+ },
224
+ uaStats: {
225
+ deviceTypes: [],
226
+ browsers: [],
227
+ browserVersions: [],
228
+ osList: [],
229
+ osVersions: [],
230
+ deviceVendors: [],
231
+ deviceModels: [],
232
+ engines: [],
233
+ cpuArchitectures: []
247
234
  }
248
-
249
- return total;
250
235
  });
251
236
 
252
- const productOptions = computed(() => {
253
- const list = Array.isArray(productState.options) ? productState.options.slice() : [];
254
- const fallbackProduct = buildFallbackProduct();
255
-
256
- if (list.length === 0) {
257
- return [fallbackProduct];
258
- }
237
+ const $Method = {
238
+ buildFallbackProduct() {
239
+ const days = $Method.sortTrendList($Data.realtimeStats.days, "reportDate");
240
+
241
+ return {
242
+ key: "all-products",
243
+ productName: "全部产品",
244
+ productCode: $Data.productInfo.productCode,
245
+ productVersion: $Data.productInfo.productVersion,
246
+ today: {
247
+ pv: Number($Data.realtimeStats.todayPv || 0),
248
+ uv: Number($Data.realtimeStats.todayUv || 0)
249
+ },
250
+ days: days,
251
+ totalPv: $Method.sumTrendField(days, "pv"),
252
+ totalUv: $Method.sumTrendField(days, "uv")
253
+ };
254
+ },
255
+ getReportDateNumber(timestamp) {
256
+ return Number(
257
+ new Intl.DateTimeFormat("en-CA", {
258
+ year: "numeric",
259
+ month: "2-digit",
260
+ day: "2-digit"
261
+ })
262
+ .format(timestamp)
263
+ .replace(/[^0-9]/g, "")
264
+ );
265
+ },
266
+ buildRecentReportDateList(limit) {
267
+ const list = [];
259
268
 
260
- list.sort((a, b) => {
261
- if (a.productName === productInfo.productName && b.productName !== productInfo.productName) {
262
- return -1;
269
+ for (let i = limit - 1; i >= 0; i--) {
270
+ list.push($Method.getReportDateNumber(Date.now() - i * 24 * 60 * 60 * 1000));
263
271
  }
264
272
 
265
- if (b.productName === productInfo.productName && a.productName !== productInfo.productName) {
266
- return 1;
273
+ return list;
274
+ },
275
+ buildFilledTrendDays(list, limit) {
276
+ const dateList = $Method.buildRecentReportDateList(limit);
277
+ const dataMap = new Map();
278
+
279
+ for (const item of list) {
280
+ dataMap.set(Number(item?.reportDate || 0), {
281
+ reportDate: Number(item?.reportDate || 0),
282
+ pv: Number(item?.pv || 0),
283
+ uv: Number(item?.uv || 0)
284
+ });
267
285
  }
268
286
 
269
- if (Number(b.totalPv || 0) !== Number(a.totalPv || 0)) {
270
- return Number(b.totalPv || 0) - Number(a.totalPv || 0);
287
+ return dateList.map((reportDate) => {
288
+ return (
289
+ dataMap.get(reportDate) || {
290
+ reportDate: reportDate,
291
+ pv: 0,
292
+ uv: 0
293
+ }
294
+ );
295
+ });
296
+ },
297
+ sumTrendRange(list) {
298
+ let pv = 0;
299
+ let uv = 0;
300
+
301
+ for (const item of list) {
302
+ pv += Number(item?.pv || 0);
303
+ uv += Number(item?.uv || 0);
271
304
  }
272
305
 
273
- return String(a.productName || "").localeCompare(String(b.productName || ""), "zh-CN");
274
- });
275
-
276
- return [fallbackProduct, ...list];
277
- });
278
-
279
- const selectedProduct = computed(() => {
280
- const list = productOptions.value;
281
-
282
- if (list.length === 0) {
283
- return null;
284
- }
285
-
286
- const matched = list.find((item) => item.key === productState.selectedKey);
287
- return matched || list[0];
288
- });
306
+ return {
307
+ pv: pv,
308
+ uv: uv
309
+ };
310
+ },
311
+ ensureSelectedProduct() {
312
+ const list = $Computed.productOptions;
313
+
314
+ if (list.length === 0) {
315
+ $Data.productState.selectedKey = "";
316
+ return;
317
+ }
289
318
 
290
- const selectedDaysTrend = computed(() => sortTrendList(selectedProduct.value?.days || [], "reportDate"));
291
- const selectedDaysFilled = computed(() => buildFilledTrendDays(selectedProduct.value?.days || [], 30));
292
- const selectedDaysHasData = computed(() => selectedDaysTrend.value.length > 0);
293
- const selectedDaysTrendPreview = computed(() => buildTrendPreview(selectedDaysFilled.value, 10));
294
- const selectedDaysBars = computed(() => buildTrendBars(selectedDaysFilled.value, getTrendMax(selectedDaysFilled.value)));
295
- const productPeriodCards = computed(() => {
296
- const list = selectedDaysFilled.value;
297
- const today = list[list.length - 1] || { pv: 0, uv: 0 };
298
- const yesterday = list[list.length - 2] || { pv: 0, uv: 0 };
299
- const dayBeforeYesterday = list[list.length - 3] || { pv: 0, uv: 0 };
300
- const recent7 = sumTrendRange(list.slice(-7));
301
- const recent30 = sumTrendRange(list);
302
-
303
- return [
304
- { key: "today", label: "今日", pv: today.pv, uv: today.uv },
305
- { key: "yesterday", label: "昨日", pv: yesterday.pv, uv: yesterday.uv },
306
- { key: "dayBeforeYesterday", label: "前日", pv: dayBeforeYesterday.pv, uv: dayBeforeYesterday.uv },
307
- { key: "recent7", label: "最近7天", pv: recent7.pv, uv: recent7.uv },
308
- { key: "recent30", label: "最近30天", pv: recent30.pv, uv: recent30.uv }
309
- ];
310
- });
319
+ if (list.some((item) => item.key === $Data.productState.selectedKey)) {
320
+ return;
321
+ }
311
322
 
312
- function buildFallbackProduct() {
313
- const days = sortTrendList(realtimeStats.days, "reportDate");
314
-
315
- return {
316
- key: "all-products",
317
- productName: "全部产品",
318
- productCode: productInfo.productCode,
319
- productVersion: productInfo.productVersion,
320
- today: {
321
- pv: Number(realtimeStats.todayPv || 0),
322
- uv: Number(realtimeStats.todayUv || 0)
323
- },
324
- days: days,
325
- totalPv: sumTrendField(days, "pv"),
326
- totalUv: sumTrendField(days, "uv")
327
- };
328
- }
329
-
330
- function getReportDateNumber(timestamp) {
331
- return Number(
332
- new Intl.DateTimeFormat("en-CA", {
333
- year: "numeric",
334
- month: "2-digit",
335
- day: "2-digit"
336
- })
337
- .format(timestamp)
338
- .replace(/[^0-9]/g, "")
339
- );
340
- }
341
-
342
- function buildRecentReportDateList(limit) {
343
- const list = [];
344
-
345
- for (let i = limit - 1; i >= 0; i--) {
346
- list.push(getReportDateNumber(Date.now() - i * 24 * 60 * 60 * 1000));
347
- }
323
+ const currentProduct = list.find((item) => item.productName === $Data.productInfo.productName);
324
+ $Data.productState.selectedKey = currentProduct?.key || "all-products";
325
+ },
326
+ selectProduct(productKey) {
327
+ $Data.productState.selectedKey = String(productKey || "");
328
+ },
329
+ sumTrendField(list, field) {
330
+ let total = 0;
331
+
332
+ for (const item of list) {
333
+ total += Number(item?.[field] || 0);
334
+ }
348
335
 
349
- return list;
350
- }
336
+ return total;
337
+ },
338
+ sortTrendList(list, keyField) {
339
+ const result = Array.isArray(list) ? list.slice() : [];
340
+
341
+ result.sort((a, b) => Number(a?.[keyField] || 0) - Number(b?.[keyField] || 0));
342
+ return result;
343
+ },
344
+ buildTrendPreview(list, maxCount) {
345
+ if (list.length <= maxCount) {
346
+ return list;
347
+ }
351
348
 
352
- function buildFilledTrendDays(list, limit) {
353
- const dateList = buildRecentReportDateList(limit);
354
- const dataMap = new Map();
349
+ const preview = [];
350
+ const usedIndex = new Set();
355
351
 
356
- for (const item of list) {
357
- dataMap.set(Number(item?.reportDate || 0), {
358
- reportDate: Number(item?.reportDate || 0),
359
- pv: Number(item?.pv || 0),
360
- uv: Number(item?.uv || 0)
361
- });
362
- }
352
+ for (let i = 0; i < maxCount; i++) {
353
+ const index = Math.round((i * (list.length - 1)) / (maxCount - 1));
363
354
 
364
- return dateList.map((reportDate) => {
365
- return (
366
- dataMap.get(reportDate) || {
367
- reportDate: reportDate,
368
- pv: 0,
369
- uv: 0
355
+ if (usedIndex.has(index)) {
356
+ continue;
370
357
  }
371
- );
372
- });
373
- }
374
-
375
- function sumTrendRange(list) {
376
- let pv = 0;
377
- let uv = 0;
378
-
379
- for (const item of list) {
380
- pv += Number(item?.pv || 0);
381
- uv += Number(item?.uv || 0);
382
- }
383
-
384
- return {
385
- pv: pv,
386
- uv: uv
387
- };
388
- }
389
358
 
390
- function ensureSelectedProduct() {
391
- const list = productOptions.value;
392
-
393
- if (list.length === 0) {
394
- productState.selectedKey = "";
395
- return;
396
- }
397
-
398
- if (list.some((item) => item.key === productState.selectedKey)) {
399
- return;
400
- }
401
-
402
- const currentProduct = list.find((item) => item.productName === productInfo.productName);
403
- productState.selectedKey = currentProduct?.key || "all-products";
404
- }
405
-
406
- function selectProduct(productKey) {
407
- productState.selectedKey = String(productKey || "");
408
- }
409
-
410
- function sumTrendField(list, field) {
411
- let total = 0;
412
-
413
- for (const item of list) {
414
- total += Number(item?.[field] || 0);
415
- }
416
-
417
- return total;
418
- }
419
-
420
- function sortTrendList(list, keyField) {
421
- const result = Array.isArray(list) ? list.slice() : [];
359
+ usedIndex.add(index);
360
+ preview.push(list[index]);
361
+ }
422
362
 
423
- result.sort((a, b) => Number(a?.[keyField] || 0) - Number(b?.[keyField] || 0));
424
- return result;
425
- }
363
+ return preview;
364
+ },
365
+ buildTrendBars(list, maxValue) {
366
+ const result = [];
367
+
368
+ for (const item of list) {
369
+ const pv = Number(item?.pv || 0);
370
+ const uv = Number(item?.uv || 0);
371
+
372
+ result.push({
373
+ reportDate: Number(item?.reportDate || 0),
374
+ pv: pv,
375
+ uv: uv,
376
+ pvHeight: pv > 0 ? `${Math.max((pv / maxValue) * 100, 6)}%` : "0%",
377
+ uvHeight: uv > 0 ? `${Math.max((uv / maxValue) * 100, 6)}%` : "0%"
378
+ });
379
+ }
426
380
 
427
- function buildTrendPreview(list, maxCount) {
428
- if (list.length <= maxCount) {
429
- return list;
430
- }
381
+ return result;
382
+ },
383
+ getTrendMax(list) {
384
+ let max = 0;
431
385
 
432
- const preview = [];
433
- const usedIndex = new Set();
386
+ for (const item of list) {
387
+ const pv = Number(item?.pv || 0);
388
+ const uv = Number(item?.uv || 0);
434
389
 
435
- for (let i = 0; i < maxCount; i++) {
436
- const index = Math.round((i * (list.length - 1)) / (maxCount - 1));
390
+ if (pv > max) {
391
+ max = pv;
392
+ }
437
393
 
438
- if (usedIndex.has(index)) {
439
- continue;
394
+ if (uv > max) {
395
+ max = uv;
396
+ }
440
397
  }
441
398
 
442
- usedIndex.add(index);
443
- preview.push(list[index]);
444
- }
445
-
446
- return preview;
447
- }
399
+ return max > 0 ? max : 1;
400
+ },
401
+ formatDeviceType(deviceType) {
402
+ if (deviceType === "desktop") {
403
+ return "桌面端";
404
+ }
448
405
 
449
- function buildTrendBars(list, maxValue) {
450
- const result = [];
406
+ if (deviceType === "mobile") {
407
+ return "移动端";
408
+ }
451
409
 
452
- for (const item of list) {
453
- const pv = Number(item?.pv || 0);
454
- const uv = Number(item?.uv || 0);
410
+ if (deviceType === "tablet") {
411
+ return "平板";
412
+ }
455
413
 
456
- result.push({
457
- reportDate: Number(item?.reportDate || 0),
458
- pv: pv,
459
- uv: uv,
460
- pvHeight: pv > 0 ? `${Math.max((pv / maxValue) * 100, 6)}%` : "0%",
461
- uvHeight: uv > 0 ? `${Math.max((uv / maxValue) * 100, 6)}%` : "0%"
462
- });
463
- }
414
+ if (deviceType === "smarttv") {
415
+ return "电视";
416
+ }
464
417
 
465
- return result;
466
- }
418
+ if (deviceType === "wearable") {
419
+ return "穿戴设备";
420
+ }
467
421
 
468
- function getTrendMax(list) {
469
- let max = 0;
422
+ if (deviceType === "embedded") {
423
+ return "嵌入式设备";
424
+ }
470
425
 
471
- for (const item of list) {
472
- const pv = Number(item?.pv || 0);
473
- const uv = Number(item?.uv || 0);
426
+ return String(deviceType || "Unknown");
427
+ },
428
+ formatReportDate(reportDate) {
429
+ const text = String(reportDate || "");
474
430
 
475
- if (pv > max) {
476
- max = pv;
431
+ if (text.length !== 8) {
432
+ return text;
477
433
  }
478
434
 
479
- if (uv > max) {
480
- max = uv;
435
+ return `${text.slice(4, 6)}-${text.slice(6, 8)}`;
436
+ },
437
+ async fetchData() {
438
+ try {
439
+ const [overviewRes, onlineStatsRes, infoStatsRes, errorStatsRes] = await Promise.all([$Http("/core/dashboard/systemOverview", {}, [""]), $Http("/core/tongJi/onlineStats", {}, [""]), $Http("/core/tongJi/infoStats", {}, [""]), $Http("/core/tongJi/errorStats", {}, [""])]);
440
+
441
+ Object.assign($Data.permissionStats, overviewRes.data);
442
+ $Data.realtimeStats.onlineCount = Number(onlineStatsRes.data?.onlineCount || 0);
443
+ $Data.realtimeStats.todayPv = Number(onlineStatsRes.data?.today?.pv || 0);
444
+ $Data.realtimeStats.todayUv = Number(onlineStatsRes.data?.today?.uv || 0);
445
+ $Data.realtimeStats.days = Array.isArray(onlineStatsRes.data?.days) ? onlineStatsRes.data.days : [];
446
+ $Data.productInfo.productName = String($Config.productName || "-");
447
+ $Data.productInfo.productCode = String($Config.productCode || "-");
448
+ $Data.productInfo.productVersion = String($Config.productVersion || "-");
449
+ $Data.productState.options = Array.isArray(onlineStatsRes.data?.products)
450
+ ? onlineStatsRes.data.products.map((item) => {
451
+ return {
452
+ key: String(item?.key || item?.productName || ""),
453
+ productName: String(item?.productName || "-"),
454
+ today: {
455
+ pv: Number(item?.today?.pv || 0),
456
+ uv: Number(item?.today?.uv || 0)
457
+ },
458
+ week: {
459
+ pv: Number(item?.week?.pv || 0),
460
+ uv: Number(item?.week?.uv || 0)
461
+ },
462
+ month: {
463
+ pv: Number(item?.month?.pv || 0),
464
+ uv: Number(item?.month?.uv || 0)
465
+ },
466
+ days: Array.isArray(item?.days) ? item.days : [],
467
+ totalPv: Number(item?.totalPv || 0),
468
+ totalUv: Number(item?.totalUv || 0)
469
+ };
470
+ })
471
+ : [];
472
+ if (Array.isArray(infoStatsRes.data?.month?.productNames) && infoStatsRes.data.month.productNames.length === 1 && $Data.productInfo.productName !== "-") {
473
+ $Data.productState.options = [
474
+ {
475
+ key: $Data.productInfo.productName,
476
+ productName: $Data.productInfo.productName,
477
+ today: {
478
+ pv: $Data.realtimeStats.todayPv,
479
+ uv: $Data.realtimeStats.todayUv
480
+ },
481
+ week: {
482
+ pv: Number(onlineStatsRes.data?.week?.pv || 0),
483
+ uv: Number(onlineStatsRes.data?.week?.uv || 0)
484
+ },
485
+ month: {
486
+ pv: Number(onlineStatsRes.data?.month?.pv || 0),
487
+ uv: Number(onlineStatsRes.data?.month?.uv || 0)
488
+ },
489
+ days: $Data.realtimeStats.days,
490
+ totalPv: $Method.sumTrendField($Data.realtimeStats.days, "pv"),
491
+ totalUv: $Method.sumTrendField($Data.realtimeStats.days, "uv")
492
+ }
493
+ ];
494
+ }
495
+ $Data.uaStats.deviceTypes = Array.isArray(infoStatsRes.data?.today?.deviceTypes) ? infoStatsRes.data.today.deviceTypes : [];
496
+ $Data.uaStats.browsers = Array.isArray(infoStatsRes.data?.today?.browsers) ? infoStatsRes.data.today.browsers : [];
497
+ $Data.uaStats.browserVersions = Array.isArray(infoStatsRes.data?.today?.browserVersions) ? infoStatsRes.data.today.browserVersions : [];
498
+ $Data.uaStats.osList = Array.isArray(infoStatsRes.data?.today?.osList) ? infoStatsRes.data.today.osList : [];
499
+ $Data.uaStats.osVersions = Array.isArray(infoStatsRes.data?.today?.osVersions) ? infoStatsRes.data.today.osVersions : [];
500
+ $Data.uaStats.deviceVendors = Array.isArray(infoStatsRes.data?.today?.deviceVendors) ? infoStatsRes.data.today.deviceVendors : [];
501
+ $Data.uaStats.deviceModels = Array.isArray(infoStatsRes.data?.today?.deviceModels) ? infoStatsRes.data.today.deviceModels : [];
502
+ $Data.uaStats.engines = Array.isArray(infoStatsRes.data?.today?.engines) ? infoStatsRes.data.today.engines : [];
503
+ $Data.uaStats.cpuArchitectures = Array.isArray(infoStatsRes.data?.today?.cpuArchitectures) ? infoStatsRes.data.today.cpuArchitectures : [];
504
+ $Data.errorStats.trend = Array.isArray(errorStatsRes.data?.trend) ? errorStatsRes.data.trend : [];
505
+ $Method.ensureSelectedProduct();
506
+ } catch (_error) {
507
+ // 静默失败:不阻断页面展示
481
508
  }
482
509
  }
510
+ };
483
511
 
484
- return max > 0 ? max : 1;
485
- }
486
-
487
- function formatDeviceType(deviceType) {
488
- if (deviceType === "desktop") {
489
- return "桌面端";
490
- }
512
+ const $Computed = reactive({
513
+ errorTodayTrend: computed(() => $Method.sortTrendList($Data.errorStats.trend, "bucketTime")),
514
+ errorTodayCount: computed(() => {
515
+ let total = 0;
491
516
 
492
- if (deviceType === "mobile") {
493
- return "移动端";
494
- }
517
+ for (const item of $Computed.errorTodayTrend) {
518
+ total += Number(item.count || 0);
519
+ }
495
520
 
496
- if (deviceType === "tablet") {
497
- return "平板";
498
- }
521
+ return total;
522
+ }),
523
+ productOptions: computed(() => {
524
+ const list = Array.isArray($Data.productState.options) ? $Data.productState.options.slice() : [];
525
+ const fallbackProduct = $Method.buildFallbackProduct();
499
526
 
500
- if (deviceType === "smarttv") {
501
- return "电视";
502
- }
527
+ if (list.length === 0) {
528
+ return [fallbackProduct];
529
+ }
503
530
 
504
- if (deviceType === "wearable") {
505
- return "穿戴设备";
506
- }
531
+ list.sort((a, b) => {
532
+ if (a.productName === $Data.productInfo.productName && b.productName !== $Data.productInfo.productName) {
533
+ return -1;
534
+ }
507
535
 
508
- if (deviceType === "embedded") {
509
- return "嵌入式设备";
510
- }
536
+ if (b.productName === $Data.productInfo.productName && a.productName !== $Data.productInfo.productName) {
537
+ return 1;
538
+ }
511
539
 
512
- return String(deviceType || "Unknown");
513
- }
540
+ if (Number(b.totalPv || 0) !== Number(a.totalPv || 0)) {
541
+ return Number(b.totalPv || 0) - Number(a.totalPv || 0);
542
+ }
514
543
 
515
- function formatReportDate(reportDate) {
516
- const text = String(reportDate || "");
544
+ return String(a.productName || "").localeCompare(String(b.productName || ""), "zh-CN");
545
+ });
517
546
 
518
- if (text.length !== 8) {
519
- return text;
520
- }
547
+ return [fallbackProduct, ...list];
548
+ }),
549
+ selectedProduct: computed(() => {
550
+ const list = $Computed.productOptions;
521
551
 
522
- return `${text.slice(4, 6)}-${text.slice(6, 8)}`;
523
- }
524
-
525
- const fetchData = async () => {
526
- try {
527
- const [overviewRes, onlineStatsRes, infoStatsRes, errorStatsRes] = await Promise.all([$Http("/core/dashboard/systemOverview", {}, [""]), $Http("/core/tongJi/onlineStats", {}, [""]), $Http("/core/tongJi/infoStats", {}, [""]), $Http("/core/tongJi/errorStats", {}, [""])]);
528
-
529
- Object.assign(permissionStats, overviewRes.data);
530
- realtimeStats.onlineCount = Number(onlineStatsRes.data?.onlineCount || 0);
531
- realtimeStats.todayPv = Number(onlineStatsRes.data?.today?.pv || 0);
532
- realtimeStats.todayUv = Number(onlineStatsRes.data?.today?.uv || 0);
533
- realtimeStats.days = Array.isArray(onlineStatsRes.data?.days) ? onlineStatsRes.data.days : [];
534
- productInfo.productName = String($Config.productName || "-");
535
- productInfo.productCode = String($Config.productCode || "-");
536
- productInfo.productVersion = String($Config.productVersion || "-");
537
- productState.options = Array.isArray(onlineStatsRes.data?.products)
538
- ? onlineStatsRes.data.products.map((item) => {
539
- return {
540
- key: String(item?.key || item?.productName || ""),
541
- productName: String(item?.productName || "-"),
542
- today: {
543
- pv: Number(item?.today?.pv || 0),
544
- uv: Number(item?.today?.uv || 0)
545
- },
546
- week: {
547
- pv: Number(item?.week?.pv || 0),
548
- uv: Number(item?.week?.uv || 0)
549
- },
550
- month: {
551
- pv: Number(item?.month?.pv || 0),
552
- uv: Number(item?.month?.uv || 0)
553
- },
554
- days: Array.isArray(item?.days) ? item.days : [],
555
- totalPv: Number(item?.totalPv || 0),
556
- totalUv: Number(item?.totalUv || 0)
557
- };
558
- })
559
- : [];
560
- if (Array.isArray(infoStatsRes.data?.month?.productNames) && infoStatsRes.data.month.productNames.length === 1 && productInfo.productName !== "-") {
561
- productState.options = [
562
- {
563
- key: productInfo.productName,
564
- productName: productInfo.productName,
565
- today: {
566
- pv: realtimeStats.todayPv,
567
- uv: realtimeStats.todayUv
568
- },
569
- week: {
570
- pv: Number(onlineStatsRes.data?.week?.pv || 0),
571
- uv: Number(onlineStatsRes.data?.week?.uv || 0)
572
- },
573
- month: {
574
- pv: Number(onlineStatsRes.data?.month?.pv || 0),
575
- uv: Number(onlineStatsRes.data?.month?.uv || 0)
576
- },
577
- days: realtimeStats.days,
578
- totalPv: sumTrendField(realtimeStats.days, "pv"),
579
- totalUv: sumTrendField(realtimeStats.days, "uv")
580
- }
581
- ];
552
+ if (list.length === 0) {
553
+ return null;
582
554
  }
583
- uaStats.deviceTypes = Array.isArray(infoStatsRes.data?.today?.deviceTypes) ? infoStatsRes.data.today.deviceTypes : [];
584
- uaStats.browsers = Array.isArray(infoStatsRes.data?.today?.browsers) ? infoStatsRes.data.today.browsers : [];
585
- uaStats.browserVersions = Array.isArray(infoStatsRes.data?.today?.browserVersions) ? infoStatsRes.data.today.browserVersions : [];
586
- uaStats.osList = Array.isArray(infoStatsRes.data?.today?.osList) ? infoStatsRes.data.today.osList : [];
587
- uaStats.osVersions = Array.isArray(infoStatsRes.data?.today?.osVersions) ? infoStatsRes.data.today.osVersions : [];
588
- uaStats.deviceVendors = Array.isArray(infoStatsRes.data?.today?.deviceVendors) ? infoStatsRes.data.today.deviceVendors : [];
589
- uaStats.deviceModels = Array.isArray(infoStatsRes.data?.today?.deviceModels) ? infoStatsRes.data.today.deviceModels : [];
590
- uaStats.engines = Array.isArray(infoStatsRes.data?.today?.engines) ? infoStatsRes.data.today.engines : [];
591
- uaStats.cpuArchitectures = Array.isArray(infoStatsRes.data?.today?.cpuArchitectures) ? infoStatsRes.data.today.cpuArchitectures : [];
592
- errorStats.trend = Array.isArray(errorStatsRes.data?.trend) ? errorStatsRes.data.trend : [];
593
- ensureSelectedProduct();
594
- } catch (_error) {
595
- // 静默失败:不阻断页面展示
596
- }
597
- };
598
555
 
599
- fetchData();
556
+ const matched = list.find((item) => item.key === $Data.productState.selectedKey);
557
+ return matched || list[0];
558
+ }),
559
+ selectedDaysTrend: computed(() => $Method.sortTrendList($Computed.selectedProduct?.days || [], "reportDate")),
560
+ selectedDaysFilled: computed(() => $Method.buildFilledTrendDays($Computed.selectedProduct?.days || [], 30)),
561
+ selectedDaysHasData: computed(() => $Computed.selectedDaysTrend.length > 0),
562
+ selectedDaysTrendPreview: computed(() => $Method.buildTrendPreview($Computed.selectedDaysFilled, 10)),
563
+ selectedDaysBars: computed(() => $Method.buildTrendBars($Computed.selectedDaysFilled, $Method.getTrendMax($Computed.selectedDaysFilled))),
564
+ productPeriodCards: computed(() => {
565
+ const list = $Computed.selectedDaysFilled;
566
+ const today = list[list.length - 1] || { pv: 0, uv: 0 };
567
+ const yesterday = list[list.length - 2] || { pv: 0, uv: 0 };
568
+ const dayBeforeYesterday = list[list.length - 3] || { pv: 0, uv: 0 };
569
+ const recent7 = $Method.sumTrendRange(list.slice(-7));
570
+ const recent30 = $Method.sumTrendRange(list);
571
+
572
+ return [
573
+ { key: "today", label: "今日", pv: today.pv, uv: today.uv },
574
+ { key: "yesterday", label: "昨日", pv: yesterday.pv, uv: yesterday.uv },
575
+ { key: "dayBeforeYesterday", label: "前日", pv: dayBeforeYesterday.pv, uv: dayBeforeYesterday.uv },
576
+ { key: "recent7", label: "最近7天", pv: recent7.pv, uv: recent7.uv },
577
+ { key: "recent30", label: "最近30天", pv: recent30.pv, uv: recent30.uv }
578
+ ];
579
+ })
580
+ });
581
+
582
+ $Method.fetchData();
600
583
  </script>
601
584
 
602
585
  <style scoped lang="scss">