@liujitcn/kratos-admin-system 0.0.53 → 0.0.54
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.
|
@@ -8,28 +8,42 @@
|
|
|
8
8
|
:key="metric.key"
|
|
9
9
|
:class="['metric-card', `metric-card--${metric.tone}`, 'admin-page-card']"
|
|
10
10
|
>
|
|
11
|
-
<div class="metric-
|
|
12
|
-
<span class="metric-label">{{ metric.label }}</span
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
<el-icon><component :is="metric.icon" /></el-icon>
|
|
11
|
+
<div class="metric-top">
|
|
12
|
+
<span class="metric-label">{{ metric.label }}</span
|
|
13
|
+
><span class="metric-icon"
|
|
14
|
+
><el-icon><component :is="metric.icon" /></el-icon
|
|
15
|
+
></span>
|
|
17
16
|
</div>
|
|
17
|
+
<strong class="metric-value">{{ metric.value }}</strong>
|
|
18
18
|
</el-card>
|
|
19
19
|
</section>
|
|
20
|
-
|
|
21
20
|
<section class="chart-grid">
|
|
22
21
|
<el-card class="chart-card chart-card-wide admin-page-card">
|
|
23
|
-
<template #header
|
|
24
|
-
|
|
22
|
+
<template #header
|
|
23
|
+
><div class="chart-heading">
|
|
24
|
+
<span>{{ t("system.dashboard.login_trend") }}</span
|
|
25
|
+
><small>近 7 天</small>
|
|
26
|
+
</div></template
|
|
27
|
+
>
|
|
28
|
+
<div class="chart-wrap"><ECharts :option="loginTrendOption" height="285" /></div>
|
|
25
29
|
</el-card>
|
|
26
30
|
<el-card class="chart-card admin-page-card">
|
|
27
|
-
<template #header
|
|
28
|
-
|
|
31
|
+
<template #header
|
|
32
|
+
><div class="chart-heading">
|
|
33
|
+
<span>{{ t("system.dashboard.login_distribution") }}</span
|
|
34
|
+
><small>总计 {{ loginDistributionTotal }}</small>
|
|
35
|
+
</div></template
|
|
36
|
+
>
|
|
37
|
+
<div class="chart-wrap"><ECharts :option="loginDistributionOption" height="265" /></div>
|
|
29
38
|
</el-card>
|
|
30
39
|
<el-card class="chart-card admin-page-card">
|
|
31
|
-
<template #header
|
|
32
|
-
|
|
40
|
+
<template #header
|
|
41
|
+
><div class="chart-heading">
|
|
42
|
+
<span>{{ t("system.dashboard.operation_distribution") }}</span
|
|
43
|
+
><small>总计 {{ operationDistributionTotal }}</small>
|
|
44
|
+
</div></template
|
|
45
|
+
>
|
|
46
|
+
<div class="chart-wrap"><ECharts :option="operationDistributionOption" height="265" /></div>
|
|
33
47
|
</el-card>
|
|
34
48
|
</section>
|
|
35
49
|
</template>
|
|
@@ -37,7 +51,7 @@
|
|
|
37
51
|
</template>
|
|
38
52
|
|
|
39
53
|
<script setup lang="ts">
|
|
40
|
-
import { computed, onMounted, ref } from "vue";
|
|
54
|
+
import { computed, onBeforeUnmount, onMounted, ref } from "vue";
|
|
41
55
|
import ECharts from "@liujitcn/kratos-admin-core/components/ECharts/index.vue";
|
|
42
56
|
import { defBaseDashboardService } from "@liujitcn/kratos-admin-system/api/system/admin/v1/base_dashboard";
|
|
43
57
|
import type {
|
|
@@ -52,25 +66,84 @@ const overview = ref<BaseDashboardOverview>();
|
|
|
52
66
|
const trend = ref<BaseDashboardTrendPoint[]>([]);
|
|
53
67
|
const loginDistribution = ref<BaseDashboardDistributionItem[]>([]);
|
|
54
68
|
const operationDistribution = ref<BaseDashboardDistributionItem[]>([]);
|
|
55
|
-
|
|
69
|
+
const chartTheme = ref({
|
|
70
|
+
primary: "#409eff",
|
|
71
|
+
success: "#67c23a",
|
|
72
|
+
warning: "#e6a23c",
|
|
73
|
+
info: "#909399",
|
|
74
|
+
danger: "#f56c6c",
|
|
75
|
+
text: "#606266",
|
|
76
|
+
divider: "#e4e7ed",
|
|
77
|
+
background: "#ffffff"
|
|
78
|
+
});
|
|
56
79
|
const metrics = computed(() => [
|
|
57
80
|
{ key: "users", label: t("system.dashboard.users"), value: overview.value?.user_count ?? 0, icon: User, tone: "primary" },
|
|
58
81
|
{ key: "roles", label: t("system.dashboard.roles"), value: overview.value?.role_count ?? 0, icon: UserFilled, tone: "success" },
|
|
59
|
-
{
|
|
60
|
-
|
|
82
|
+
{
|
|
83
|
+
key: "logins",
|
|
84
|
+
label: t("system.dashboard.today_logins"),
|
|
85
|
+
value: overview.value?.today_login_count ?? 0,
|
|
86
|
+
icon: Timer,
|
|
87
|
+
tone: "warning"
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
key: "operations",
|
|
91
|
+
label: t("system.dashboard.today_operations"),
|
|
92
|
+
value: overview.value?.today_operation_count ?? 0,
|
|
93
|
+
icon: Operation,
|
|
94
|
+
tone: "danger"
|
|
95
|
+
}
|
|
61
96
|
]);
|
|
97
|
+
const loginDistributionTotal = computed(() => loginDistribution.value.reduce((total, item) => total + item.count, 0));
|
|
98
|
+
const operationDistributionTotal = computed(() => operationDistribution.value.reduce((total, item) => total + item.count, 0));
|
|
62
99
|
|
|
100
|
+
function readThemeColor(name: string, fallback: string) {
|
|
101
|
+
return getComputedStyle(document.documentElement).getPropertyValue(name).trim() || fallback;
|
|
102
|
+
}
|
|
103
|
+
function refreshChartTheme() {
|
|
104
|
+
chartTheme.value = {
|
|
105
|
+
primary: readThemeColor("--el-color-primary", "#409eff"),
|
|
106
|
+
success: readThemeColor("--el-color-success", "#67c23a"),
|
|
107
|
+
warning: readThemeColor("--el-color-warning", "#e6a23c"),
|
|
108
|
+
info: readThemeColor("--el-color-info", "#909399"),
|
|
109
|
+
danger: readThemeColor("--el-color-danger", "#f56c6c"),
|
|
110
|
+
text: readThemeColor("--el-text-color-secondary", "#606266"),
|
|
111
|
+
divider: readThemeColor("--el-border-color-lighter", "#e4e7ed"),
|
|
112
|
+
background: readThemeColor("--el-bg-color", "#ffffff")
|
|
113
|
+
};
|
|
114
|
+
}
|
|
63
115
|
const loginTrendOption = computed(() => ({
|
|
116
|
+
color: [chartTheme.value.primary],
|
|
64
117
|
tooltip: { trigger: "axis" as const },
|
|
65
|
-
grid: { left:
|
|
66
|
-
xAxis: {
|
|
67
|
-
|
|
68
|
-
|
|
118
|
+
grid: { left: 42, right: 20, top: 20, bottom: 28 },
|
|
119
|
+
xAxis: {
|
|
120
|
+
type: "category" as const,
|
|
121
|
+
boundaryGap: false,
|
|
122
|
+
data: trend.value.map(item => item.date),
|
|
123
|
+
axisLine: { lineStyle: { color: chartTheme.value.divider } },
|
|
124
|
+
axisLabel: { color: chartTheme.value.text }
|
|
125
|
+
},
|
|
126
|
+
yAxis: {
|
|
127
|
+
type: "value" as const,
|
|
128
|
+
minInterval: 1,
|
|
129
|
+
splitLine: { lineStyle: { color: chartTheme.value.divider } },
|
|
130
|
+
axisLabel: { color: chartTheme.value.text }
|
|
131
|
+
},
|
|
132
|
+
series: [
|
|
133
|
+
{
|
|
134
|
+
type: "line" as const,
|
|
135
|
+
smooth: true,
|
|
136
|
+
data: trend.value.map(item => item.count),
|
|
137
|
+
symbol: "circle",
|
|
138
|
+
symbolSize: 7,
|
|
139
|
+
lineStyle: { width: 3, color: chartTheme.value.primary },
|
|
140
|
+
itemStyle: { color: chartTheme.value.primary, borderWidth: 2, borderColor: chartTheme.value.background },
|
|
141
|
+
areaStyle: { color: chartTheme.value.primary, opacity: 0.12 }
|
|
142
|
+
}
|
|
143
|
+
]
|
|
69
144
|
}));
|
|
70
|
-
|
|
71
145
|
const loginDistributionOption = computed(() => distributionOption(loginDistribution.value));
|
|
72
146
|
const operationDistributionOption = computed(() => distributionOption(operationDistribution.value));
|
|
73
|
-
|
|
74
147
|
async function loadDashboard() {
|
|
75
148
|
loading.value = true;
|
|
76
149
|
try {
|
|
@@ -85,12 +158,11 @@ async function loadDashboard() {
|
|
|
85
158
|
loginDistribution.value = loginResponse.items ?? [];
|
|
86
159
|
operationDistribution.value = operationResponse.items ?? [];
|
|
87
160
|
} catch {
|
|
88
|
-
|
|
161
|
+
/* 请求层已展示具体错误。 */
|
|
89
162
|
} finally {
|
|
90
163
|
loading.value = false;
|
|
91
164
|
}
|
|
92
165
|
}
|
|
93
|
-
|
|
94
166
|
/** 将审计技术枚举名转换为当前语言的展示文案。 */
|
|
95
167
|
function distributionItemLabel(label: string) {
|
|
96
168
|
const mappings = [
|
|
@@ -102,67 +174,109 @@ function distributionItemLabel(label: string) {
|
|
|
102
174
|
const value = label.slice(mapping[0].length).toLowerCase();
|
|
103
175
|
return value === "unspecified" ? t("common.message.unknown") : t(`${mapping[1]}${value}`);
|
|
104
176
|
}
|
|
105
|
-
|
|
106
177
|
/** 创建使用本地化图例的分布图配置。 */
|
|
107
178
|
function distributionOption(items: BaseDashboardDistributionItem[]) {
|
|
108
179
|
return {
|
|
180
|
+
color: [
|
|
181
|
+
chartTheme.value.primary,
|
|
182
|
+
chartTheme.value.success,
|
|
183
|
+
chartTheme.value.info,
|
|
184
|
+
chartTheme.value.warning,
|
|
185
|
+
chartTheme.value.danger
|
|
186
|
+
],
|
|
109
187
|
tooltip: { trigger: "item" as const },
|
|
110
|
-
legend: { bottom: 0, type: "scroll" as const },
|
|
111
|
-
series: [
|
|
188
|
+
legend: { bottom: 0, type: "scroll" as const, textStyle: { color: chartTheme.value.text } },
|
|
189
|
+
series: [
|
|
190
|
+
{
|
|
191
|
+
type: "pie" as const,
|
|
192
|
+
radius: ["55%", "76%"] as [string, string],
|
|
193
|
+
center: ["50%", "43%"] as [string, string],
|
|
194
|
+
label: { color: chartTheme.value.text },
|
|
195
|
+
itemStyle: { borderColor: chartTheme.value.background, borderWidth: 3 },
|
|
196
|
+
data: items.map(item => ({ name: distributionItemLabel(item.label), value: item.count }))
|
|
197
|
+
}
|
|
198
|
+
]
|
|
112
199
|
};
|
|
113
200
|
}
|
|
114
|
-
|
|
115
|
-
|
|
201
|
+
onMounted(() => {
|
|
202
|
+
refreshChartTheme();
|
|
203
|
+
loadDashboard();
|
|
204
|
+
window.addEventListener("theme-change", refreshChartTheme);
|
|
205
|
+
});
|
|
206
|
+
onBeforeUnmount(() => window.removeEventListener("theme-change", refreshChartTheme));
|
|
116
207
|
</script>
|
|
117
208
|
|
|
118
209
|
<style scoped>
|
|
119
210
|
.dashboard-page {
|
|
120
211
|
min-width: 0;
|
|
121
212
|
}
|
|
213
|
+
.dashboard-heading {
|
|
214
|
+
display: flex;
|
|
215
|
+
align-items: flex-end;
|
|
216
|
+
justify-content: space-between;
|
|
217
|
+
margin-bottom: 22px;
|
|
218
|
+
}
|
|
219
|
+
.dashboard-eyebrow {
|
|
220
|
+
display: block;
|
|
221
|
+
color: var(--admin-page-text-placeholder);
|
|
222
|
+
font-size: 11px;
|
|
223
|
+
letter-spacing: 0.12em;
|
|
224
|
+
}
|
|
225
|
+
.dashboard-heading h1 {
|
|
226
|
+
margin: 6px 0 0;
|
|
227
|
+
color: var(--admin-page-text-primary);
|
|
228
|
+
font-size: 26px;
|
|
229
|
+
line-height: 1.2;
|
|
230
|
+
}
|
|
231
|
+
.dashboard-updated {
|
|
232
|
+
color: var(--admin-page-text-placeholder);
|
|
233
|
+
font-size: 12px;
|
|
234
|
+
}
|
|
122
235
|
.metric-grid,
|
|
123
236
|
.chart-grid {
|
|
124
237
|
display: grid;
|
|
125
|
-
gap:
|
|
238
|
+
gap: 16px;
|
|
126
239
|
}
|
|
127
240
|
.metric-grid {
|
|
128
241
|
grid-template-columns: repeat(4, minmax(0, 1fr));
|
|
129
|
-
margin-bottom:
|
|
242
|
+
margin-bottom: 16px;
|
|
243
|
+
}
|
|
244
|
+
.metric-card {
|
|
245
|
+
position: relative;
|
|
246
|
+
overflow: hidden;
|
|
130
247
|
}
|
|
131
248
|
.metric-card :deep(.el-card__body) {
|
|
249
|
+
min-height: 124px;
|
|
250
|
+
padding: 18px 20px 16px;
|
|
251
|
+
}
|
|
252
|
+
.metric-top {
|
|
132
253
|
display: flex;
|
|
133
|
-
gap: 16px;
|
|
134
254
|
align-items: center;
|
|
135
255
|
justify-content: space-between;
|
|
136
|
-
min-height: 92px;
|
|
137
|
-
}
|
|
138
|
-
.metric-content {
|
|
139
|
-
display: flex;
|
|
140
|
-
flex-direction: column;
|
|
141
|
-
gap: 10px;
|
|
142
|
-
min-width: 0;
|
|
143
256
|
}
|
|
144
257
|
.metric-label {
|
|
258
|
+
color: var(--admin-page-text-secondary);
|
|
145
259
|
font-size: 13px;
|
|
146
|
-
color: var(--el-text-color-secondary);
|
|
147
260
|
}
|
|
148
261
|
.metric-value {
|
|
149
|
-
|
|
262
|
+
display: block;
|
|
263
|
+
margin-top: 18px;
|
|
264
|
+
color: var(--admin-page-text-primary);
|
|
265
|
+
font-size: 32px;
|
|
150
266
|
line-height: 1;
|
|
151
|
-
|
|
267
|
+
font-variant-numeric: tabular-nums;
|
|
152
268
|
}
|
|
153
269
|
.metric-icon {
|
|
154
|
-
display:
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
height: 48px;
|
|
270
|
+
display: grid;
|
|
271
|
+
width: 34px;
|
|
272
|
+
height: 34px;
|
|
273
|
+
place-items: center;
|
|
274
|
+
border-radius: 10px;
|
|
160
275
|
color: var(--metric-color);
|
|
161
276
|
background: var(--metric-bg);
|
|
162
|
-
border-radius: var(--admin-page-radius);
|
|
163
277
|
}
|
|
164
278
|
.metric-icon .el-icon {
|
|
165
|
-
font-size:
|
|
279
|
+
font-size: 17px;
|
|
166
280
|
}
|
|
167
281
|
.metric-card--primary {
|
|
168
282
|
--metric-color: var(--el-color-primary);
|
|
@@ -186,11 +300,45 @@ onMounted(loadDashboard);
|
|
|
186
300
|
.chart-card-wide {
|
|
187
301
|
grid-column: 1 / -1;
|
|
188
302
|
}
|
|
303
|
+
.chart-card :deep(.el-card__header) {
|
|
304
|
+
padding: 17px 22px 14px;
|
|
305
|
+
border-bottom-color: var(--admin-page-divider);
|
|
306
|
+
}
|
|
307
|
+
.chart-card :deep(.el-card__body) {
|
|
308
|
+
padding: 0 20px 16px;
|
|
309
|
+
}
|
|
310
|
+
.chart-heading {
|
|
311
|
+
display: flex;
|
|
312
|
+
align-items: center;
|
|
313
|
+
justify-content: space-between;
|
|
314
|
+
}
|
|
315
|
+
.chart-heading span {
|
|
316
|
+
color: var(--admin-page-text-primary);
|
|
317
|
+
font-size: 16px;
|
|
318
|
+
font-weight: 600;
|
|
319
|
+
}
|
|
320
|
+
.chart-heading small {
|
|
321
|
+
color: var(--admin-page-text-placeholder);
|
|
322
|
+
font-size: 12px;
|
|
323
|
+
font-weight: 400;
|
|
324
|
+
}
|
|
189
325
|
.chart-wrap {
|
|
190
|
-
height:
|
|
326
|
+
height: 285px;
|
|
327
|
+
}
|
|
328
|
+
.chart-card:not(.chart-card-wide) .chart-wrap {
|
|
329
|
+
height: 265px;
|
|
191
330
|
}
|
|
192
|
-
|
|
193
331
|
@media (width <= 960px) {
|
|
332
|
+
.metric-grid {
|
|
333
|
+
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
@media (width <= 640px) {
|
|
337
|
+
.dashboard-heading {
|
|
338
|
+
align-items: flex-start;
|
|
339
|
+
flex-direction: column;
|
|
340
|
+
gap: 8px;
|
|
341
|
+
}
|
|
194
342
|
.metric-grid,
|
|
195
343
|
.chart-grid {
|
|
196
344
|
grid-template-columns: 1fr;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@liujitcn/kratos-admin-system",
|
|
3
3
|
"private": false,
|
|
4
|
-
"version": "0.0.
|
|
4
|
+
"version": "0.0.54",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "kratos-admin system management module",
|
|
7
7
|
"license": "MIT",
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
"sortablejs": "^1.15.2",
|
|
17
17
|
"swagger-ui-dist": "^5.32.15",
|
|
18
18
|
"vue-element-plus-x": "^2.0.3",
|
|
19
|
-
"@liujitcn/kratos-admin-core": "0.0.
|
|
19
|
+
"@liujitcn/kratos-admin-core": "0.0.54"
|
|
20
20
|
},
|
|
21
21
|
"peerDependencies": {
|
|
22
22
|
"@element-plus/icons-vue": "^2.3.1",
|