befly-admin-ui 1.10.17 → 1.10.19
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/README.md +2 -1
- package/components/detailPanel.vue +0 -1
- package/components/pageDialog.vue +0 -1
- package/components/pageTableDetail.vue +1 -1
- package/layouts/default.vue +1 -2
- package/package.json +2 -2
- package/styles/dashboardCard.scss +228 -0
- package/views/config/dict/components/edit.vue +1 -1
- package/views/config/dict/index.vue +1 -2
- package/views/config/dictType/components/edit.vue +1 -1
- package/views/config/dictType/index.vue +0 -2
- package/views/config/system/components/edit.vue +1 -1
- package/views/config/system/index.vue +0 -2
- package/views/index/components/environmentInfo.vue +66 -81
- package/views/index/components/projectStats.vue +392 -0
- package/views/index/components/serviceStatus.vue +28 -105
- package/views/index/components/systemOverview.vue +41 -1065
- package/views/index/components/systemResources.vue +35 -94
- package/views/index/index.vue +10 -7
- package/views/log/email/index.vue +1 -2
- package/views/log/error/index.vue +1 -2
- package/views/log/login/index.vue +0 -1
- package/views/log/operate/index.vue +0 -1
- package/views/login_1/index.vue +1 -2
- package/views/people/admin/components/edit.vue +0 -6
- package/views/people/admin/index.vue +0 -2
- package/views/permission/api/index.vue +1 -2
- package/views/permission/menu/index.vue +1 -1
- package/views/permission/role/components/api.vue +1 -2
- package/views/permission/role/components/edit.vue +0 -6
- package/views/permission/role/components/menu.vue +1 -2
- package/views/permission/role/index.vue +0 -2
- package/views/resource/gallery/index.vue +1 -2
- package/views/index/components/addonList.vue +0 -140
- package/views/index/components/operationLogs.vue +0 -120
- package/views/index/components/performanceMetrics.vue +0 -158
- package/views/index/components/quickActions.vue +0 -30
- package/views/index/components/systemNotifications.vue +0 -139
- package/views/index/components/userInfo.vue +0 -188
|
@@ -1,158 +0,0 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<div class="section-block">
|
|
3
|
-
<div class="section-header flex items-center gap-2">
|
|
4
|
-
<ChartIcon />
|
|
5
|
-
<h2>性能指标</h2>
|
|
6
|
-
</div>
|
|
7
|
-
<div class="section-content">
|
|
8
|
-
<div class="performance-grid">
|
|
9
|
-
<div class="perf-metric">
|
|
10
|
-
<div class="perf-info">
|
|
11
|
-
<div class="perf-label">平均响应</div>
|
|
12
|
-
<div class="perf-value">{{ $Data.performanceMetrics.avgResponseTime }}ms</div>
|
|
13
|
-
</div>
|
|
14
|
-
</div>
|
|
15
|
-
<div class="perf-metric">
|
|
16
|
-
<div class="perf-info">
|
|
17
|
-
<div class="perf-label">QPS</div>
|
|
18
|
-
<div class="perf-value">{{ $Data.performanceMetrics.qps }}/s</div>
|
|
19
|
-
</div>
|
|
20
|
-
</div>
|
|
21
|
-
<div class="perf-metric">
|
|
22
|
-
<div class="perf-info">
|
|
23
|
-
<div class="perf-label">错误率</div>
|
|
24
|
-
<div class="perf-value">{{ $Data.performanceMetrics.errorRate }}%</div>
|
|
25
|
-
</div>
|
|
26
|
-
</div>
|
|
27
|
-
<div class="perf-metric">
|
|
28
|
-
<div class="perf-info">
|
|
29
|
-
<div class="perf-label">活跃连接</div>
|
|
30
|
-
<div class="perf-value">{{ $Data.performanceMetrics.activeConnections }}</div>
|
|
31
|
-
</div>
|
|
32
|
-
</div>
|
|
33
|
-
</div>
|
|
34
|
-
<!-- 最慢接口提示 -->
|
|
35
|
-
<div v-if="$Data.performanceMetrics.slowestApi" class="perf-slowest">
|
|
36
|
-
<ErrorTriangleIcon />
|
|
37
|
-
<span>最慢接口: {{ $Data.performanceMetrics.slowestApi.path }} ({{ $Data.performanceMetrics.slowestApi.time }}ms)</span>
|
|
38
|
-
</div>
|
|
39
|
-
</div>
|
|
40
|
-
</div>
|
|
41
|
-
</template>
|
|
42
|
-
|
|
43
|
-
<script setup>
|
|
44
|
-
import { ChartIcon, ErrorTriangleIcon } from "tdesign-icons-vue-next";
|
|
45
|
-
import { reactive } from "vue";
|
|
46
|
-
|
|
47
|
-
import { $Http } from "@/plugins/http.js";
|
|
48
|
-
|
|
49
|
-
// 组件内部数据
|
|
50
|
-
const $Data = reactive({
|
|
51
|
-
performanceMetrics: {
|
|
52
|
-
avgResponseTime: 0,
|
|
53
|
-
qps: 0,
|
|
54
|
-
errorRate: 0,
|
|
55
|
-
activeConnections: 0,
|
|
56
|
-
slowestApi: null
|
|
57
|
-
}
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
const $Method = {
|
|
61
|
-
fetchData: async function () {
|
|
62
|
-
try {
|
|
63
|
-
const res = await $Http("/core/dashboard/performanceMetrics", {}, [""]);
|
|
64
|
-
Object.assign($Data.performanceMetrics, res.data);
|
|
65
|
-
} catch (_error) {
|
|
66
|
-
// 静默失败:不阻断页面展示
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
};
|
|
70
|
-
|
|
71
|
-
$Method.fetchData();
|
|
72
|
-
</script>
|
|
73
|
-
|
|
74
|
-
<style scoped lang="scss">
|
|
75
|
-
.performance-grid {
|
|
76
|
-
display: grid;
|
|
77
|
-
grid-template-columns: repeat(4, minmax(0, 1fr));
|
|
78
|
-
gap: 10px;
|
|
79
|
-
margin-bottom: 8px;
|
|
80
|
-
|
|
81
|
-
.perf-metric {
|
|
82
|
-
display: flex;
|
|
83
|
-
align-items: flex-start;
|
|
84
|
-
justify-content: center;
|
|
85
|
-
min-height: 92px;
|
|
86
|
-
padding: 14px;
|
|
87
|
-
background: linear-gradient(180deg, rgba(var(--primary-color-rgb), 0.018), rgba(255, 255, 255, 0.96));
|
|
88
|
-
border-radius: 8px;
|
|
89
|
-
border: 1px solid rgba(0, 0, 0, 0.06);
|
|
90
|
-
transition: all 0.2s;
|
|
91
|
-
|
|
92
|
-
&:hover {
|
|
93
|
-
background: rgba(var(--primary-color-rgb), 0.05);
|
|
94
|
-
border-color: var(--primary-color);
|
|
95
|
-
box-shadow: 0 8px 18px rgba(31, 35, 41, 0.08);
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
.perf-info {
|
|
99
|
-
display: flex;
|
|
100
|
-
flex-direction: column;
|
|
101
|
-
justify-content: center;
|
|
102
|
-
gap: 4px;
|
|
103
|
-
flex: 1;
|
|
104
|
-
width: 100%;
|
|
105
|
-
min-width: 0;
|
|
106
|
-
|
|
107
|
-
.perf-label {
|
|
108
|
-
font-size: 12px;
|
|
109
|
-
color: var(--text-secondary);
|
|
110
|
-
line-height: 1.2;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
.perf-value {
|
|
114
|
-
font-size: 22px;
|
|
115
|
-
font-weight: 700;
|
|
116
|
-
line-height: 1.1;
|
|
117
|
-
letter-spacing: -0.3px;
|
|
118
|
-
font-variant-numeric: tabular-nums;
|
|
119
|
-
color: var(--primary-color);
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
@media (max-width: 1200px) {
|
|
126
|
-
.performance-grid {
|
|
127
|
-
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
.perf-slowest {
|
|
132
|
-
display: flex;
|
|
133
|
-
align-items: flex-start;
|
|
134
|
-
gap: 6px;
|
|
135
|
-
padding: 8px 10px;
|
|
136
|
-
background: rgba(var(--warning-color-rgb), 0.05);
|
|
137
|
-
border-radius: 8px;
|
|
138
|
-
border: 1px solid rgba(var(--warning-color-rgb), 0.2);
|
|
139
|
-
font-size: 12px;
|
|
140
|
-
color: var(--warning-color);
|
|
141
|
-
word-break: break-word;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
.warning-tip {
|
|
145
|
-
padding: var(--spacing-sm) var(--spacing-md);
|
|
146
|
-
background: rgba(var(--warning-color-rgb), 0.05);
|
|
147
|
-
border-radius: var(--border-radius-small);
|
|
148
|
-
border: 1px solid rgba(var(--warning-color-rgb), 0.2);
|
|
149
|
-
font-size: 14px;
|
|
150
|
-
color: var(--warning-color);
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
@media (max-width: 640px) {
|
|
154
|
-
.performance-grid {
|
|
155
|
-
grid-template-columns: 1fr;
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
</style>
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<div class="section-block">
|
|
3
|
-
<div class="section-content">
|
|
4
|
-
<TButton theme="primary" size="large" @click="handleClearCache">
|
|
5
|
-
<template #prefix>
|
|
6
|
-
<RefreshIcon />
|
|
7
|
-
</template>
|
|
8
|
-
刷新缓存
|
|
9
|
-
</TButton>
|
|
10
|
-
</div>
|
|
11
|
-
</div>
|
|
12
|
-
</template>
|
|
13
|
-
|
|
14
|
-
<script setup>
|
|
15
|
-
import { RefreshIcon } from "tdesign-icons-vue-next";
|
|
16
|
-
import { Button as TButton } from "tdesign-vue-next";
|
|
17
|
-
|
|
18
|
-
const handleClearCache = () => {
|
|
19
|
-
// TODO: 接入刷新缓存接口
|
|
20
|
-
};
|
|
21
|
-
</script>
|
|
22
|
-
|
|
23
|
-
<style scoped lang="scss">
|
|
24
|
-
.section-block {
|
|
25
|
-
.section-content {
|
|
26
|
-
display: flex;
|
|
27
|
-
justify-content: center;
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
</style>
|
|
@@ -1,139 +0,0 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<div class="section-block">
|
|
3
|
-
<div class="section-header flex items-center gap-2">
|
|
4
|
-
<NotificationIcon />
|
|
5
|
-
<h2>系统通知</h2>
|
|
6
|
-
</div>
|
|
7
|
-
<div class="section-content">
|
|
8
|
-
<div class="notification-compact-list">
|
|
9
|
-
<div v-for="notification in $Const.notifications" :key="notification.id" class="notification-compact-item">
|
|
10
|
-
<div class="notification-icon" :class="`type-${notification.type}`">
|
|
11
|
-
<InfoCircleIcon v-if="notification.type === 'info'" />
|
|
12
|
-
<CheckCircleIcon v-else-if="notification.type === 'success'" />
|
|
13
|
-
<ErrorTriangleIcon v-else-if="notification.type === 'warning'" />
|
|
14
|
-
<CloseCircleIcon v-else-if="notification.type === 'error'" />
|
|
15
|
-
<NotificationIcon v-else />
|
|
16
|
-
</div>
|
|
17
|
-
<div class="notification-content">
|
|
18
|
-
<span class="notification-title">{{ notification.title }}</span>
|
|
19
|
-
<span class="notification-time">{{ $Method.formatTime(notification.createdAt) }}</span>
|
|
20
|
-
</div>
|
|
21
|
-
<TTag v-if="!notification.isRead" type="primary" size="small">新</TTag>
|
|
22
|
-
</div>
|
|
23
|
-
</div>
|
|
24
|
-
</div>
|
|
25
|
-
</div>
|
|
26
|
-
</template>
|
|
27
|
-
|
|
28
|
-
<script setup>
|
|
29
|
-
import { CheckCircleIcon, CloseCircleIcon, ErrorTriangleIcon, InfoCircleIcon, NotificationIcon } from "tdesign-icons-vue-next";
|
|
30
|
-
import { Tag as TTag } from "tdesign-vue-next";
|
|
31
|
-
import { reactive } from "vue";
|
|
32
|
-
|
|
33
|
-
// 组件内部数据
|
|
34
|
-
const $Const = {
|
|
35
|
-
notifications: [
|
|
36
|
-
{ id: 1, type: "warning", title: "系统更新提醒 - v1.1.0 版本已发布", isRead: false, createdAt: Date.now() - 3600000 },
|
|
37
|
-
{ id: 2, type: "info", title: "数据备份完成 - 今日凌晨自动备份成功", isRead: true, createdAt: Date.now() - 21600000 },
|
|
38
|
-
{ id: 3, type: "error", title: "SSL证书即将过期 - 请及时更新证书", isRead: false, createdAt: Date.now() - 86400000 },
|
|
39
|
-
{ id: 4, type: "success", title: "性能优化完成 - 响应速度提升30%", isRead: true, createdAt: Date.now() - 172800000 }
|
|
40
|
-
]
|
|
41
|
-
};
|
|
42
|
-
|
|
43
|
-
const $Method = {
|
|
44
|
-
formatTime: function (timestamp) {
|
|
45
|
-
const date = new Date(timestamp);
|
|
46
|
-
const now = Date.now();
|
|
47
|
-
const diff = now - timestamp;
|
|
48
|
-
|
|
49
|
-
if (diff < 3600000) {
|
|
50
|
-
return `${Math.floor(diff / 60000)}分钟前`;
|
|
51
|
-
}
|
|
52
|
-
if (diff < 86400000) {
|
|
53
|
-
return `${Math.floor(diff / 3600000)}小时前`;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
const month = String(date.getMonth() + 1).padStart(2, "0");
|
|
57
|
-
const day = String(date.getDate()).padStart(2, "0");
|
|
58
|
-
return `${month}-${day}`;
|
|
59
|
-
}
|
|
60
|
-
};
|
|
61
|
-
</script>
|
|
62
|
-
|
|
63
|
-
<style scoped lang="scss">
|
|
64
|
-
.notification-compact-list {
|
|
65
|
-
display: flex;
|
|
66
|
-
flex-direction: column;
|
|
67
|
-
gap: var(--spacing-xs);
|
|
68
|
-
|
|
69
|
-
.notification-compact-item {
|
|
70
|
-
display: flex;
|
|
71
|
-
align-items: center;
|
|
72
|
-
gap: var(--spacing-sm);
|
|
73
|
-
padding: var(--spacing-sm) var(--spacing-md);
|
|
74
|
-
background: rgba(var(--primary-color-rgb), 0.02);
|
|
75
|
-
border-radius: var(--border-radius-small);
|
|
76
|
-
border: 1px solid var(--border-color);
|
|
77
|
-
transition: all 0.2s;
|
|
78
|
-
|
|
79
|
-
&:hover {
|
|
80
|
-
background: rgba(var(--primary-color-rgb), 0.05);
|
|
81
|
-
border-color: var(--primary-color);
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
.notification-icon {
|
|
85
|
-
display: flex;
|
|
86
|
-
align-items: center;
|
|
87
|
-
justify-content: center;
|
|
88
|
-
width: 32px;
|
|
89
|
-
height: 32px;
|
|
90
|
-
border-radius: var(--border-radius-small);
|
|
91
|
-
flex-shrink: 0;
|
|
92
|
-
|
|
93
|
-
&.type-info {
|
|
94
|
-
background: rgba(var(--primary-color-rgb), 0.1);
|
|
95
|
-
color: var(--primary-color);
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
&.type-success {
|
|
99
|
-
background: rgba(var(--success-color-rgb), 0.1);
|
|
100
|
-
color: var(--success-color);
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
&.type-warning {
|
|
104
|
-
background: rgba(var(--warning-color-rgb), 0.1);
|
|
105
|
-
color: var(--warning-color);
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
&.type-error {
|
|
109
|
-
background: rgba(var(--error-color-rgb), 0.1);
|
|
110
|
-
color: var(--error-color);
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
.notification-content {
|
|
115
|
-
display: flex;
|
|
116
|
-
align-items: center;
|
|
117
|
-
gap: var(--spacing-sm);
|
|
118
|
-
flex: 1;
|
|
119
|
-
min-width: 0;
|
|
120
|
-
|
|
121
|
-
.notification-title {
|
|
122
|
-
font-size: 14px;
|
|
123
|
-
color: var(--text-primary);
|
|
124
|
-
font-weight: 500;
|
|
125
|
-
overflow: hidden;
|
|
126
|
-
text-overflow: ellipsis;
|
|
127
|
-
white-space: nowrap;
|
|
128
|
-
flex: 1;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
.notification-time {
|
|
132
|
-
font-size: 14px;
|
|
133
|
-
color: var(--text-placeholder);
|
|
134
|
-
flex-shrink: 0;
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
</style>
|
|
@@ -1,188 +0,0 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<div class="section-block user-info-card">
|
|
3
|
-
<div class="user-header">
|
|
4
|
-
<div class="user-avatar">
|
|
5
|
-
<UserIcon />
|
|
6
|
-
</div>
|
|
7
|
-
<div class="user-basic">
|
|
8
|
-
<div class="user-name">
|
|
9
|
-
{{ $Data.userInfo.nickname || $Data.userInfo.name || $Data.userInfo.username || "未设置" }}
|
|
10
|
-
</div>
|
|
11
|
-
<div class="user-role">{{ $Data.userInfo.role?.name || "普通用户" }}</div>
|
|
12
|
-
</div>
|
|
13
|
-
</div>
|
|
14
|
-
<div class="user-details">
|
|
15
|
-
<div class="detail-item">
|
|
16
|
-
<MailIcon />
|
|
17
|
-
<span>{{ $Data.userInfo.email || "未设置" }}</span>
|
|
18
|
-
</div>
|
|
19
|
-
<div v-if="$Data.userInfo.phone" class="detail-item">
|
|
20
|
-
<CallIcon />
|
|
21
|
-
<span>{{ $Data.userInfo.phone }}</span>
|
|
22
|
-
</div>
|
|
23
|
-
<div v-if="$Data.userInfo.lastLoginTime" class="detail-item">
|
|
24
|
-
<TimeIcon />
|
|
25
|
-
<span>{{ $Method.formatTime($Data.userInfo.lastLoginTime) }}</span>
|
|
26
|
-
</div>
|
|
27
|
-
</div>
|
|
28
|
-
|
|
29
|
-
<!-- 仅 dev 角色显示刷新缓存按钮 -->
|
|
30
|
-
<div v-if="$Data.userInfo.roleCode === 'dev'" class="user-actions">
|
|
31
|
-
<TButton theme="primary" size="mini" :loading="$Data.refreshing" @click="$Method.handleRefreshCache">
|
|
32
|
-
<template #icon>
|
|
33
|
-
<RefreshIcon />
|
|
34
|
-
</template>
|
|
35
|
-
刷新缓存
|
|
36
|
-
</TButton>
|
|
37
|
-
</div>
|
|
38
|
-
</div>
|
|
39
|
-
</template>
|
|
40
|
-
|
|
41
|
-
<script setup>
|
|
42
|
-
import { CallIcon, MailIcon, RefreshIcon, TimeIcon, UserIcon } from "tdesign-icons-vue-next";
|
|
43
|
-
import { Button as TButton, MessagePlugin } from "tdesign-vue-next";
|
|
44
|
-
import { reactive } from "vue";
|
|
45
|
-
|
|
46
|
-
import { $Http } from "@/plugins/http.js";
|
|
47
|
-
|
|
48
|
-
// 响应式数据
|
|
49
|
-
const $Data = reactive({
|
|
50
|
-
userInfo: {},
|
|
51
|
-
refreshing: false
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
const $Method = {
|
|
55
|
-
fetchData: async function () {
|
|
56
|
-
try {
|
|
57
|
-
const res = await $Http("/core/admin/detail", {}, [""]);
|
|
58
|
-
Object.assign($Data.userInfo, res.data);
|
|
59
|
-
} catch (error) {
|
|
60
|
-
MessagePlugin.error(error.msg || error.message || "获取用户信息失败");
|
|
61
|
-
}
|
|
62
|
-
},
|
|
63
|
-
handleRefreshCache: async function () {
|
|
64
|
-
try {
|
|
65
|
-
$Data.refreshing = true;
|
|
66
|
-
const result = await $Http("/core/admin/cacheRefresh");
|
|
67
|
-
|
|
68
|
-
const apis = result.data.apis;
|
|
69
|
-
const menus = result.data.menus;
|
|
70
|
-
const roles = result.data.roles;
|
|
71
|
-
const messages = [];
|
|
72
|
-
|
|
73
|
-
if (apis && apis.success) {
|
|
74
|
-
messages.push(`接口缓存: ${apis.count} 个`);
|
|
75
|
-
}
|
|
76
|
-
if (menus && menus.success) {
|
|
77
|
-
messages.push(`菜单缓存: ${menus.count} 个`);
|
|
78
|
-
}
|
|
79
|
-
if (roles && roles.success) {
|
|
80
|
-
messages.push(`角色缓存: ${roles.count} 个`);
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
MessagePlugin.success(`缓存刷新成功!${messages.join(",")}`);
|
|
84
|
-
} catch (error) {
|
|
85
|
-
MessagePlugin.error(error.msg || error.message || "刷新缓存失败,请稍后重试");
|
|
86
|
-
} finally {
|
|
87
|
-
$Data.refreshing = false;
|
|
88
|
-
}
|
|
89
|
-
},
|
|
90
|
-
formatTime: function (timestamp) {
|
|
91
|
-
if (!timestamp) return "";
|
|
92
|
-
const date = new Date(Number(timestamp));
|
|
93
|
-
const now = new Date();
|
|
94
|
-
const diff = now.getTime() - date.getTime();
|
|
95
|
-
|
|
96
|
-
if (diff < 60000) {
|
|
97
|
-
return "刚刚";
|
|
98
|
-
}
|
|
99
|
-
if (diff < 3600000) {
|
|
100
|
-
return `${Math.floor(diff / 60000)}分钟前`;
|
|
101
|
-
}
|
|
102
|
-
if (diff < 86400000) {
|
|
103
|
-
return `${Math.floor(diff / 3600000)}小时前`;
|
|
104
|
-
}
|
|
105
|
-
if (diff < 604800000) {
|
|
106
|
-
return `${Math.floor(diff / 86400000)}天前`;
|
|
107
|
-
}
|
|
108
|
-
return `${date.getMonth() + 1}月${date.getDate()}日`;
|
|
109
|
-
}
|
|
110
|
-
};
|
|
111
|
-
|
|
112
|
-
$Method.fetchData();
|
|
113
|
-
</script>
|
|
114
|
-
|
|
115
|
-
<style scoped lang="scss">
|
|
116
|
-
.user-info-card {
|
|
117
|
-
background-color: #fff;
|
|
118
|
-
padding: 15px;
|
|
119
|
-
.user-header {
|
|
120
|
-
display: flex;
|
|
121
|
-
align-items: center;
|
|
122
|
-
gap: 12px;
|
|
123
|
-
padding-bottom: 12px;
|
|
124
|
-
border-bottom: 1px solid var(--border-color);
|
|
125
|
-
|
|
126
|
-
.user-avatar {
|
|
127
|
-
width: 48px;
|
|
128
|
-
height: 48px;
|
|
129
|
-
background: linear-gradient(135deg, var(--primary-color), #764ba2);
|
|
130
|
-
border-radius: 50%;
|
|
131
|
-
display: flex;
|
|
132
|
-
align-items: center;
|
|
133
|
-
justify-content: center;
|
|
134
|
-
color: white;
|
|
135
|
-
flex-shrink: 0;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
.user-basic {
|
|
139
|
-
flex: 1;
|
|
140
|
-
min-width: 0;
|
|
141
|
-
|
|
142
|
-
.user-name {
|
|
143
|
-
font-size: 16px;
|
|
144
|
-
font-weight: 600;
|
|
145
|
-
color: var(--text-primary);
|
|
146
|
-
margin-bottom: 4px;
|
|
147
|
-
overflow: hidden;
|
|
148
|
-
text-overflow: ellipsis;
|
|
149
|
-
white-space: nowrap;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
.user-role {
|
|
153
|
-
font-size: 12px;
|
|
154
|
-
color: var(--text-secondary);
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
.user-details {
|
|
160
|
-
display: flex;
|
|
161
|
-
flex-direction: column;
|
|
162
|
-
gap: 8px;
|
|
163
|
-
margin-top: 12px;
|
|
164
|
-
|
|
165
|
-
.detail-item {
|
|
166
|
-
display: flex;
|
|
167
|
-
align-items: center;
|
|
168
|
-
gap: 8px;
|
|
169
|
-
font-size: 12px;
|
|
170
|
-
color: var(--text-secondary);
|
|
171
|
-
|
|
172
|
-
span {
|
|
173
|
-
overflow: hidden;
|
|
174
|
-
text-overflow: ellipsis;
|
|
175
|
-
white-space: nowrap;
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
.user-actions {
|
|
181
|
-
margin-top: 16px;
|
|
182
|
-
padding-top: 12px;
|
|
183
|
-
border-top: 1px solid var(--border-color);
|
|
184
|
-
display: flex;
|
|
185
|
-
justify-content: center;
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
</style>
|