@befly-addon/admin 1.2.1 → 1.2.3

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 (36) hide show
  1. package/adminViews/403_1/index.vue +75 -0
  2. package/adminViews/config/dict/components/edit.vue +109 -0
  3. package/adminViews/config/dict/index.vue +266 -0
  4. package/adminViews/config/dictType/components/edit.vue +100 -0
  5. package/adminViews/config/dictType/index.vue +244 -0
  6. package/adminViews/config/index.vue +12 -0
  7. package/adminViews/config/system/components/edit.vue +171 -0
  8. package/adminViews/config/system/index.vue +286 -0
  9. package/adminViews/index/components/addonList.vue +132 -0
  10. package/adminViews/index/components/environmentInfo.vue +100 -0
  11. package/adminViews/index/components/operationLogs.vue +112 -0
  12. package/adminViews/index/components/performanceMetrics.vue +145 -0
  13. package/adminViews/index/components/quickActions.vue +30 -0
  14. package/adminViews/index/components/serviceStatus.vue +192 -0
  15. package/adminViews/index/components/systemNotifications.vue +137 -0
  16. package/adminViews/index/components/systemOverview.vue +190 -0
  17. package/adminViews/index/components/systemResources.vue +111 -0
  18. package/adminViews/index/components/userInfo.vue +204 -0
  19. package/adminViews/index/index.vue +74 -0
  20. package/adminViews/log/email/index.vue +292 -0
  21. package/adminViews/log/index.vue +12 -0
  22. package/adminViews/log/login/index.vue +187 -0
  23. package/adminViews/log/operate/index.vue +249 -0
  24. package/adminViews/login_1/index.vue +415 -0
  25. package/adminViews/people/admin/components/edit.vue +168 -0
  26. package/adminViews/people/admin/index.vue +240 -0
  27. package/adminViews/people/index.vue +12 -0
  28. package/adminViews/permission/api/index.vue +149 -0
  29. package/adminViews/permission/index.vue +12 -0
  30. package/adminViews/permission/menu/index.vue +130 -0
  31. package/adminViews/permission/role/components/api.vue +361 -0
  32. package/adminViews/permission/role/components/edit.vue +142 -0
  33. package/adminViews/permission/role/components/menu.vue +118 -0
  34. package/adminViews/permission/role/index.vue +263 -0
  35. package/package.json +12 -10
  36. package/tsconfig.json +15 -0
@@ -0,0 +1,145 @@
1
+ <template>
2
+ <div class="section-block">
3
+ <div class="section-header flex items-center gap-2">
4
+ <ILucideActivity />
5
+ <h2>性能指标</h2>
6
+ </div>
7
+ <div class="section-content">
8
+ <div class="performance-grid">
9
+ <div class="perf-metric">
10
+ <div class="perf-icon">
11
+ <ILucideClock />
12
+ </div>
13
+ <div class="perf-info">
14
+ <div class="perf-label">平均响应</div>
15
+ <div class="perf-value">{{ performanceMetrics.avgResponseTime }}ms</div>
16
+ </div>
17
+ </div>
18
+ <div class="perf-metric">
19
+ <div class="perf-icon">
20
+ <ILucideTrendingUp />
21
+ </div>
22
+ <div class="perf-info">
23
+ <div class="perf-label">QPS</div>
24
+ <div class="perf-value">{{ performanceMetrics.qps }}/s</div>
25
+ </div>
26
+ </div>
27
+ <div class="perf-metric">
28
+ <div class="perf-icon">
29
+ <ILucideAlertCircle />
30
+ </div>
31
+ <div class="perf-info">
32
+ <div class="perf-label">错误率</div>
33
+ <div class="perf-value">{{ performanceMetrics.errorRate }}%</div>
34
+ </div>
35
+ </div>
36
+ <div class="perf-metric">
37
+ <div class="perf-icon">
38
+ <ILucideActivity />
39
+ </div>
40
+ <div class="perf-info">
41
+ <div class="perf-label">活跃连接</div>
42
+ <div class="perf-value">{{ performanceMetrics.activeConnections }}</div>
43
+ </div>
44
+ </div>
45
+ </div>
46
+ <!-- 最慢接口提示 -->
47
+ <div v-if="performanceMetrics.slowestApi" class="perf-slowest">
48
+ <ILucideAlertTriangle />
49
+ <span>最慢接口: {{ performanceMetrics.slowestApi.path }} ({{ performanceMetrics.slowestApi.time }}ms)</span>
50
+ </div>
51
+ </div>
52
+ </div>
53
+ </template>
54
+
55
+ <script setup>
56
+ import ILucideActivity from "~icons/lucide/activity";
57
+ import ILucideClock from "~icons/lucide/clock";
58
+ import ILucideTrendingUp from "~icons/lucide/trending-up";
59
+ import ILucideAlertCircle from "~icons/lucide/alert-circle";
60
+ import ILucideAlertTriangle from "~icons/lucide/alert-triangle";
61
+ import { $Http } from "@/plugins/http";
62
+
63
+ // 组件内部数据
64
+ const performanceMetrics = $ref({
65
+ avgResponseTime: 0,
66
+ qps: 0,
67
+ errorRate: 0,
68
+ activeConnections: 0,
69
+ slowestApi: null
70
+ });
71
+
72
+ // 获取数据
73
+ const fetchData = async () => {
74
+ try {
75
+ const { data } = await $Http("/addon/admin/dashboard/performanceMetrics");
76
+ Object.assign(performanceMetrics, data);
77
+ } catch (error) {
78
+ // 静默失败:不阻断页面展示
79
+ }
80
+ };
81
+
82
+ fetchData();
83
+ </script>
84
+
85
+ <style scoped lang="scss">
86
+ .performance-grid {
87
+ display: grid;
88
+ grid-template-columns: repeat(4, 1fr);
89
+ gap: var(--spacing-sm);
90
+ margin-bottom: var(--spacing-sm);
91
+
92
+ .perf-metric {
93
+ display: flex;
94
+ align-items: center;
95
+ gap: var(--spacing-sm);
96
+ padding: var(--spacing-sm) var(--spacing-md);
97
+ background: rgba(var(--primary-color-rgb), 0.02);
98
+ border-radius: var(--border-radius);
99
+ border: 1px solid var(--border-color);
100
+ transition: all 0.2s;
101
+
102
+ &:hover {
103
+ background: rgba(var(--primary-color-rgb), 0.05);
104
+ border-color: var(--primary-color);
105
+ }
106
+
107
+ .perf-icon {
108
+ display: flex;
109
+ align-items: center;
110
+ justify-content: center;
111
+ width: 36px;
112
+ height: 36px;
113
+ border-radius: var(--border-radius-small);
114
+ background: linear-gradient(135deg, rgba(0, 168, 112, 0.1) 0%, rgba(0, 168, 112, 0.05) 100%);
115
+ color: var(--success-color);
116
+ flex-shrink: 0;
117
+ }
118
+
119
+ .perf-info {
120
+ flex: 1;
121
+
122
+ .perf-label {
123
+ font-size: 14px;
124
+ color: var(--text-secondary);
125
+ margin-bottom: 2px;
126
+ }
127
+
128
+ .perf-value {
129
+ font-size: 16px;
130
+ font-weight: 700;
131
+ color: var(--primary-color);
132
+ }
133
+ }
134
+ }
135
+ }
136
+
137
+ .warning-tip {
138
+ padding: var(--spacing-sm) var(--spacing-md);
139
+ background: rgba(var(--warning-color-rgb), 0.05);
140
+ border-radius: var(--border-radius-small);
141
+ border: 1px solid rgba(var(--warning-color-rgb), 0.2);
142
+ font-size: 13px;
143
+ color: var(--warning-color);
144
+ }
145
+ </style>
@@ -0,0 +1,30 @@
1
+ <template>
2
+ <div class="section-block">
3
+ <div class="section-content">
4
+ <TButton theme="primary" size="large" @click="handleClearCache">
5
+ <template #prefix>
6
+ <ILucideRotateCw />
7
+ </template>
8
+ 刷新缓存
9
+ </TButton>
10
+ </div>
11
+ </div>
12
+ </template>
13
+
14
+ <script setup>
15
+ import { Button as TButton } from "tdesign-vue-next";
16
+ import ILucideRotateCw from "~icons/lucide/rotate-cw";
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>
@@ -0,0 +1,192 @@
1
+ <template>
2
+ <div class="section-block">
3
+ <div class="section-header flex items-center gap-2">
4
+ <ILucideCheckCircle />
5
+ <h2>服务状态</h2>
6
+ </div>
7
+ <div class="section-content">
8
+ <div class="config-grid">
9
+ <div v-for="service in services" :key="service.name" class="config-card" :class="`config-${service.status}`">
10
+ <div class="config-icon">
11
+ <ILucideDatabase v-if="service.name === '数据库'" style="width: 20px; height: 20px" />
12
+ <ILucideZap v-else-if="service.name === 'Redis'" style="width: 20px; height: 20px" />
13
+ <ILucideHardDrive v-else-if="service.name === '文件系统'" style="width: 20px; height: 20px" />
14
+ <ILucideMail v-else-if="service.name === '邮件服务'" style="width: 20px; height: 20px" />
15
+ <ILucideCloud v-else-if="service.name === 'OSS存储'" style="width: 20px; height: 20px" />
16
+ <ILucideCircle v-else style="width: 20px; height: 20px" />
17
+ </div>
18
+ <div class="config-info">
19
+ <div class="config-name">{{ service.name }}</div>
20
+ <div class="config-status">
21
+ {{ getStatusText(service.status) }}
22
+ <span v-if="service.responseTime && service.responseTime !== '-'" class="latency">{{ service.responseTime }}</span>
23
+ </div>
24
+ </div>
25
+ <div class="config-badge">
26
+ <ILucideCheckCircle v-if="service.status === 'running'" style="width: 32px; height: 32px" />
27
+ <ILucideXCircle v-else-if="service.status === 'stopped'" style="width: 32px; height: 32px" />
28
+ <ILucideAlertCircle v-else-if="service.status === 'unconfigured'" style="width: 32px; height: 32px" />
29
+ <ILucideCircle v-else style="width: 32px; height: 32px" />
30
+ </div>
31
+ </div>
32
+ </div>
33
+ </div>
34
+ </div>
35
+ </template>
36
+
37
+ <script setup>
38
+ import ILucideCheckCircle from "~icons/lucide/check-circle";
39
+ import ILucideDatabase from "~icons/lucide/database";
40
+ import ILucideZap from "~icons/lucide/zap";
41
+ import ILucideHardDrive from "~icons/lucide/hard-drive";
42
+ import ILucideMail from "~icons/lucide/mail";
43
+ import ILucideCloud from "~icons/lucide/cloud";
44
+ import ILucideCircle from "~icons/lucide/circle";
45
+ import ILucideXCircle from "~icons/lucide/x-circle";
46
+ import ILucideAlertCircle from "~icons/lucide/alert-circle";
47
+ import { $Http } from "@/plugins/http";
48
+
49
+ // 组件内部数据
50
+ const services = $ref([]);
51
+
52
+ // 获取数据
53
+ const fetchData = async () => {
54
+ try {
55
+ const { data } = await $Http("/addon/admin/dashboard/serviceStatus");
56
+ services.splice(0, services.length, ...data.services);
57
+ } catch (error) {
58
+ // 静默失败:不阻断页面展示
59
+ }
60
+ };
61
+
62
+ fetchData();
63
+
64
+ // 工具函数
65
+ const getStatusColor = (status) => {
66
+ const colors = {
67
+ running: "success",
68
+ stopped: "error",
69
+ unconfigured: "warning"
70
+ };
71
+ return colors[status] || "default";
72
+ };
73
+
74
+ const getStatusText = (status) => {
75
+ const texts = {
76
+ running: "正常",
77
+ stopped: "停止",
78
+ unconfigured: "未配置"
79
+ };
80
+ return texts[status] || status;
81
+ };
82
+ </script>
83
+
84
+ <style scoped lang="scss">
85
+ .config-grid {
86
+ display: grid;
87
+ grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
88
+ gap: 10px;
89
+
90
+ .config-card {
91
+ background: rgba(var(--primary-color-rgb), 0.02);
92
+ border: 1px solid var(--border-color);
93
+ border-radius: 6px;
94
+ padding: 12px;
95
+ display: flex;
96
+ align-items: center;
97
+ gap: 10px;
98
+ position: relative;
99
+ overflow: hidden;
100
+ transition: all 0.3s;
101
+
102
+ &:hover {
103
+ background: rgba(var(--primary-color-rgb), 0.05);
104
+ border-color: var(--primary-color);
105
+ transform: translateY(-2px);
106
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
107
+ }
108
+
109
+ .config-icon {
110
+ width: 40px;
111
+ height: 40px;
112
+ display: flex;
113
+ align-items: center;
114
+ justify-content: center;
115
+ border-radius: 6px;
116
+ flex-shrink: 0;
117
+ }
118
+
119
+ .config-info {
120
+ flex: 1;
121
+ min-width: 0;
122
+
123
+ .config-name {
124
+ font-size: 14px;
125
+ font-weight: 600;
126
+ margin-bottom: 2px;
127
+ }
128
+
129
+ .config-status {
130
+ font-size: 14px;
131
+ display: flex;
132
+ align-items: center;
133
+ gap: 4px;
134
+
135
+ .latency {
136
+ margin-left: 4px;
137
+ color: var(--text-placeholder);
138
+ }
139
+ }
140
+ }
141
+
142
+ .config-badge {
143
+ position: absolute;
144
+ top: 6px;
145
+ right: 6px;
146
+ opacity: 0.2;
147
+ }
148
+
149
+ &.config-running {
150
+ border-color: var(--success-color);
151
+ background: linear-gradient(135deg, rgba(82, 196, 26, 0.05), white);
152
+
153
+ .config-icon {
154
+ background: rgba(82, 196, 26, 0.1);
155
+ color: var(--success-color);
156
+ }
157
+
158
+ .config-name {
159
+ color: var(--success-color);
160
+ }
161
+ }
162
+
163
+ &.config-unconfigured {
164
+ border-color: var(--warning-color);
165
+ background: linear-gradient(135deg, rgba(250, 173, 20, 0.05), white);
166
+
167
+ .config-icon {
168
+ background: rgba(250, 173, 20, 0.1);
169
+ color: var(--warning-color);
170
+ }
171
+
172
+ .config-name {
173
+ color: var(--warning-color);
174
+ }
175
+ }
176
+
177
+ &.config-stopped {
178
+ border-color: var(--error-color);
179
+ background: linear-gradient(135deg, rgba(255, 77, 79, 0.05), white);
180
+
181
+ .config-icon {
182
+ background: rgba(255, 77, 79, 0.1);
183
+ color: var(--error-color);
184
+ }
185
+
186
+ .config-name {
187
+ color: var(--error-color);
188
+ }
189
+ }
190
+ }
191
+ }
192
+ </style>
@@ -0,0 +1,137 @@
1
+ <template>
2
+ <div class="section-block">
3
+ <div class="section-header flex items-center gap-2">
4
+ <ILucideBell />
5
+ <h2>系统通知</h2>
6
+ </div>
7
+ <div class="section-content">
8
+ <div class="notification-compact-list">
9
+ <div v-for="notification in notifications" :key="notification.id" class="notification-compact-item">
10
+ <div class="notification-icon" :class="`type-${notification.type}`">
11
+ <ILucideInfo v-if="notification.type === 'info'" />
12
+ <ILucideCheckCircle v-else-if="notification.type === 'success'" />
13
+ <ILucideAlertTriangle v-else-if="notification.type === 'warning'" />
14
+ <ILucideXCircle v-else-if="notification.type === 'error'" />
15
+ <ILucideBell v-else />
16
+ </div>
17
+ <div class="notification-content">
18
+ <span class="notification-title">{{ notification.title }}</span>
19
+ <span class="notification-time">{{ 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 { Tag as TTag } from "tdesign-vue-next";
30
+ import ILucideBell from "~icons/lucide/bell";
31
+ import ILucideInfo from "~icons/lucide/info";
32
+ import ILucideCheckCircle from "~icons/lucide/check-circle";
33
+ import ILucideAlertTriangle from "~icons/lucide/alert-triangle";
34
+ import ILucideXCircle from "~icons/lucide/x-circle";
35
+
36
+ // 组件内部数据
37
+ const notifications = $ref([
38
+ { id: 1, type: "warning", title: "系统更新提醒 - v1.1.0 版本已发布", isRead: false, createdAt: Date.now() - 3600000 },
39
+ { id: 2, type: "info", title: "数据备份完成 - 今日凌晨自动备份成功", isRead: true, createdAt: Date.now() - 21600000 },
40
+ { id: 3, type: "error", title: "SSL证书即将过期 - 请及时更新证书", isRead: false, createdAt: Date.now() - 86400000 },
41
+ { id: 4, type: "success", title: "性能优化完成 - 响应速度提升30%", isRead: true, createdAt: Date.now() - 172800000 }
42
+ ]);
43
+
44
+ const formatTime = (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
+ } else if (diff < 86400000) {
52
+ return `${Math.floor(diff / 3600000)}小时前`;
53
+ } else {
54
+ const month = String(date.getMonth() + 1).padStart(2, "0");
55
+ const day = String(date.getDate()).padStart(2, "0");
56
+ return `${month}-${day}`;
57
+ }
58
+ };
59
+ </script>
60
+
61
+ <style scoped lang="scss">
62
+ .notification-compact-list {
63
+ display: flex;
64
+ flex-direction: column;
65
+ gap: var(--spacing-xs);
66
+
67
+ .notification-compact-item {
68
+ display: flex;
69
+ align-items: center;
70
+ gap: var(--spacing-sm);
71
+ padding: var(--spacing-sm) var(--spacing-md);
72
+ background: rgba(var(--primary-color-rgb), 0.02);
73
+ border-radius: var(--border-radius-small);
74
+ border: 1px solid var(--border-color);
75
+ transition: all 0.2s;
76
+
77
+ &:hover {
78
+ background: rgba(var(--primary-color-rgb), 0.05);
79
+ border-color: var(--primary-color);
80
+ }
81
+
82
+ .notification-icon {
83
+ display: flex;
84
+ align-items: center;
85
+ justify-content: center;
86
+ width: 32px;
87
+ height: 32px;
88
+ border-radius: var(--border-radius-small);
89
+ flex-shrink: 0;
90
+
91
+ &.type-info {
92
+ background: rgba(var(--primary-color-rgb), 0.1);
93
+ color: var(--primary-color);
94
+ }
95
+
96
+ &.type-success {
97
+ background: rgba(var(--success-color-rgb), 0.1);
98
+ color: var(--success-color);
99
+ }
100
+
101
+ &.type-warning {
102
+ background: rgba(var(--warning-color-rgb), 0.1);
103
+ color: var(--warning-color);
104
+ }
105
+
106
+ &.type-error {
107
+ background: rgba(var(--error-color-rgb), 0.1);
108
+ color: var(--error-color);
109
+ }
110
+ }
111
+
112
+ .notification-content {
113
+ display: flex;
114
+ align-items: center;
115
+ gap: var(--spacing-sm);
116
+ flex: 1;
117
+ min-width: 0;
118
+
119
+ .notification-title {
120
+ font-size: 14px;
121
+ color: var(--text-primary);
122
+ font-weight: 500;
123
+ overflow: hidden;
124
+ text-overflow: ellipsis;
125
+ white-space: nowrap;
126
+ flex: 1;
127
+ }
128
+
129
+ .notification-time {
130
+ font-size: 14px;
131
+ color: var(--text-placeholder);
132
+ flex-shrink: 0;
133
+ }
134
+ }
135
+ }
136
+ }
137
+ </style>
@@ -0,0 +1,190 @@
1
+ <template>
2
+ <div class="section-block">
3
+ <div class="section-header flex items-center gap-2">
4
+ <ILucideInfo />
5
+ <h2>系统概览</h2>
6
+ </div>
7
+ <div class="section-content">
8
+ <div class="info-block">
9
+ <div class="stats-grid">
10
+ <div class="stat-box stat-primary">
11
+ <ILucideMenu />
12
+ <div class="stat-content">
13
+ <div class="stat-value">{{ permissionStats.menuCount }}</div>
14
+ <div class="stat-label">菜单总数</div>
15
+ </div>
16
+ </div>
17
+ <div class="stat-box stat-success">
18
+ <ILucideWebhook />
19
+ <div class="stat-content">
20
+ <div class="stat-value">{{ permissionStats.apiCount }}</div>
21
+ <div class="stat-label">接口总数</div>
22
+ </div>
23
+ </div>
24
+ <div class="stat-box stat-warning">
25
+ <ILucideUsers />
26
+ <div class="stat-content">
27
+ <div class="stat-value">{{ permissionStats.roleCount }}</div>
28
+ <div class="stat-label">角色总数</div>
29
+ </div>
30
+ </div>
31
+ </div>
32
+ </div>
33
+ </div>
34
+ </div>
35
+ </template>
36
+
37
+ <script setup>
38
+ import ILucideInfo from "~icons/lucide/info";
39
+ import ILucideMenu from "~icons/lucide/menu";
40
+ import ILucideWebhook from "~icons/lucide/webhook";
41
+ import ILucideUsers from "~icons/lucide/users";
42
+ import { $Http } from "@/plugins/http";
43
+
44
+ // 组件内部数据
45
+ const permissionStats = $ref({
46
+ menuCount: 0,
47
+ apiCount: 0,
48
+ roleCount: 0
49
+ });
50
+
51
+ // 获取数据
52
+ const fetchData = async () => {
53
+ try {
54
+ const { data } = await $Http("/addon/admin/dashboard/systemOverview");
55
+ Object.assign(permissionStats, data);
56
+ } catch (error) {
57
+ // 静默失败:不阻断页面展示
58
+ }
59
+ };
60
+
61
+ fetchData();
62
+ </script>
63
+
64
+ <style scoped lang="scss">
65
+ .info-block {
66
+ background: transparent;
67
+ border: none;
68
+ padding: 0;
69
+ height: 100%;
70
+
71
+ .info-header {
72
+ display: flex;
73
+ align-items: center;
74
+ gap: 6px;
75
+ padding-bottom: 8px;
76
+ margin-bottom: 12px;
77
+ border-bottom: 2px solid var(--primary-color);
78
+
79
+ .info-title {
80
+ font-size: 14px;
81
+ font-weight: 600;
82
+ color: var(--text-primary);
83
+ }
84
+ }
85
+
86
+ .info-grid-compact {
87
+ display: grid;
88
+ grid-template-columns: repeat(3, 1fr);
89
+ gap: 10px;
90
+
91
+ .info-grid-item {
92
+ display: flex;
93
+ justify-content: space-between;
94
+ align-items: center;
95
+ padding: 10px 12px;
96
+ background: rgba(var(--primary-color-rgb), 0.02);
97
+ border-radius: var(--border-radius-small);
98
+ border: 1px solid var(--border-color);
99
+ transition: all 0.2s ease;
100
+
101
+ &:hover {
102
+ background: rgba(var(--primary-color-rgb), 0.05);
103
+ border-color: var(--primary-color);
104
+ }
105
+
106
+ .label {
107
+ font-size: 14px;
108
+ color: var(--text-secondary);
109
+ font-weight: 500;
110
+ }
111
+
112
+ .value {
113
+ font-size: 14px;
114
+ color: var(--text-primary);
115
+ font-weight: 600;
116
+
117
+ &.highlight {
118
+ color: var(--primary-color);
119
+ }
120
+ }
121
+ }
122
+ }
123
+ }
124
+
125
+ .stats-grid {
126
+ display: grid;
127
+ grid-template-columns: repeat(3, 1fr);
128
+ gap: 10px;
129
+
130
+ .stat-box {
131
+ background: rgba(var(--primary-color-rgb), 0.02);
132
+ border: 1px solid var(--border-color);
133
+ border-radius: 6px;
134
+ padding: 12px;
135
+ display: flex;
136
+ align-items: center;
137
+ gap: 10px;
138
+ transition: all 0.3s;
139
+
140
+ &:hover {
141
+ background: rgba(var(--primary-color-rgb), 0.05);
142
+ border-color: var(--primary-color);
143
+ transform: translateY(-2px);
144
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
145
+ }
146
+
147
+ .stat-content {
148
+ flex: 1;
149
+
150
+ .stat-value {
151
+ font-size: 20px;
152
+ font-weight: 700;
153
+ margin-bottom: 2px;
154
+ }
155
+
156
+ .stat-label {
157
+ font-size: 14px;
158
+ color: var(--text-secondary);
159
+ }
160
+ }
161
+
162
+ &.stat-primary {
163
+ border-color: var(--primary-color);
164
+ background: linear-gradient(135deg, rgba(var(--primary-color-rgb), 0.05), white);
165
+
166
+ .stat-value {
167
+ color: var(--primary-color);
168
+ }
169
+ }
170
+
171
+ &.stat-success {
172
+ border-color: var(--success-color);
173
+ background: linear-gradient(135deg, rgba(var(--success-color-rgb), 0.05), white);
174
+
175
+ .stat-value {
176
+ color: var(--success-color);
177
+ }
178
+ }
179
+
180
+ &.stat-warning {
181
+ border-color: var(--warning-color);
182
+ background: linear-gradient(135deg, rgba(var(--warning-color-rgb), 0.05), white);
183
+
184
+ .stat-value {
185
+ color: var(--warning-color);
186
+ }
187
+ }
188
+ }
189
+ }
190
+ </style>