befly-admin 3.13.7 → 3.13.8

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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "befly-admin",
3
- "version": "3.13.7",
4
- "gitHead": "aed5c2a383bfcc1d8489ee880bca3f36bc2cbd28",
3
+ "version": "3.13.8",
4
+ "gitHead": "3fab09ac346b6d336a2f60b123d4856d077f8c7d",
5
5
  "private": false,
6
6
  "description": "Befly Admin - 基于 Vue3 + TDesign Vue Next 的后台管理系统",
7
7
  "files": [
@@ -29,7 +29,7 @@
29
29
  },
30
30
  "dependencies": {
31
31
  "axios": "^1.13.5",
32
- "befly-admin-ui": "1.8.17",
32
+ "befly-admin-ui": "1.8.18",
33
33
  "befly-vite": "^1.5.3",
34
34
  "pinia": "^3.0.4",
35
35
  "tdesign-icons-vue-next": "^0.4.0",
package/src/layouts/2.vue CHANGED
@@ -1,8 +1,6 @@
1
1
  <template>
2
- <router-view />
2
+ <div class="layout-2">
3
+ 222
4
+ <RouterView />
5
+ </div>
3
6
  </template>
4
-
5
- <script setup>
6
- // 布局 2 - 空布局
7
- // 用于特殊页面,不包含任何框架结构
8
- </script>
@@ -2,13 +2,27 @@ import { Layouts } from "befly-vite";
2
2
  import { createRouter, createWebHashHistory } from "vue-router";
3
3
  import { routes } from "vue-router/auto-routes";
4
4
 
5
+ const builtinLayouts = {
6
+ default: () => import("befly-admin-ui/layouts/default.vue"),
7
+ 1: () => import("befly-admin-ui/layouts/1.vue")
8
+ };
9
+
10
+ const localLayouts = import.meta.glob("../layouts/*.vue");
11
+
5
12
  // 应用自定义布局系统(同时可选注入根路径重定向)
6
13
  const finalRoutes = Layouts(routes, $Config.homePath, (layoutName) => {
7
- if (layoutName === "default") {
8
- return () => import("../layouts/default.vue");
14
+ const key = String(layoutName || "default");
15
+
16
+ if (Object.hasOwn(builtinLayouts, key)) {
17
+ return builtinLayouts[key];
18
+ }
19
+
20
+ const localKey = `../layouts/${key}.vue`;
21
+ if (Object.hasOwn(localLayouts, localKey)) {
22
+ return localLayouts[localKey];
9
23
  }
10
24
 
11
- return () => import(`../layouts/${layoutName}.vue`);
25
+ return builtinLayouts["1"];
12
26
  });
13
27
 
14
28
  /**
@@ -0,0 +1,306 @@
1
+ <template>
2
+ <div class="dashboard">
3
+ <h1>后台数据面板</h1>
4
+ </div>
5
+ </template>
6
+
7
+ <script setup></script>
8
+
9
+ <style lang="scss" scoped>
10
+ .dashboard {
11
+ padding: 20px;
12
+ min-height: 100vh;
13
+ background-color: #f5f7fa;
14
+ font-family: "Arial", sans-serif;
15
+ }
16
+
17
+ h1 {
18
+ color: #2c3e50;
19
+ font-size: 24px;
20
+ margin-bottom: 20px;
21
+ font-weight: 600;
22
+ }
23
+
24
+ .stats-grid {
25
+ display: grid;
26
+ grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
27
+ gap: 20px;
28
+ margin-bottom: 20px;
29
+ }
30
+
31
+ .stat-card {
32
+ background-color: #fff;
33
+ border-radius: 12px;
34
+ padding: 20px;
35
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
36
+ display: flex;
37
+ align-items: center;
38
+ transition:
39
+ transform 0.2s,
40
+ box-shadow 0.2s;
41
+
42
+ &:hover {
43
+ transform: translateY(-5px);
44
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
45
+ }
46
+ }
47
+
48
+ .stat-icon {
49
+ width: 60px;
50
+ height: 60px;
51
+ border-radius: 12px;
52
+ display: flex;
53
+ align-items: center;
54
+ justify-content: center;
55
+ font-size: 24px;
56
+ margin-right: 20px;
57
+ color: #fff;
58
+
59
+ &.blue {
60
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
61
+ }
62
+
63
+ &.green {
64
+ background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
65
+ }
66
+
67
+ &.purple {
68
+ background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
69
+ }
70
+
71
+ &.orange {
72
+ background: linear-gradient(135deg, #43e97b 0%, #38f9d7 100%);
73
+ }
74
+ }
75
+
76
+ .stat-content {
77
+ flex: 1;
78
+ }
79
+
80
+ .stat-number {
81
+ font-size: 24px;
82
+ font-weight: 700;
83
+ color: #2c3e50;
84
+ margin-bottom: 4px;
85
+ }
86
+
87
+ .stat-label {
88
+ font-size: 14px;
89
+ color: #90a4ae;
90
+ margin-bottom: 4px;
91
+ }
92
+
93
+ .stat-change {
94
+ font-size: 12px;
95
+ font-weight: 600;
96
+
97
+ &.positive {
98
+ color: #4caf50;
99
+ }
100
+
101
+ &.negative {
102
+ color: #f44336;
103
+ }
104
+ }
105
+
106
+ .charts-section {
107
+ display: grid;
108
+ grid-template-columns: repeat(auto-fit, minmax(500px, 1fr));
109
+ gap: 20px;
110
+ margin-bottom: 20px;
111
+
112
+ @media (max-width: 768px) {
113
+ grid-template-columns: 1fr;
114
+ }
115
+ }
116
+
117
+ .chart-card {
118
+ background-color: #fff;
119
+ border-radius: 12px;
120
+ padding: 20px;
121
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
122
+ }
123
+
124
+ .chart-card h3 {
125
+ color: #2c3e50;
126
+ font-size: 18px;
127
+ margin-bottom: 20px;
128
+ font-weight: 600;
129
+ }
130
+
131
+ .chart-container {
132
+ height: 250px;
133
+ background-color: #fafafa;
134
+ border-radius: 8px;
135
+ padding: 20px;
136
+ display: flex;
137
+ align-items: center;
138
+ justify-content: center;
139
+ position: relative;
140
+ overflow: hidden;
141
+ }
142
+
143
+ .line-chart {
144
+ width: 100%;
145
+ height: 100%;
146
+ position: relative;
147
+
148
+ .chart-grid {
149
+ position: absolute;
150
+ top: 0;
151
+ left: 0;
152
+ right: 0;
153
+ bottom: 0;
154
+ background-image: linear-gradient(to bottom, rgba(0, 0, 0, 0.05) 1px, transparent 1px), linear-gradient(to right, rgba(0, 0, 0, 0.05) 1px, transparent 1px);
155
+ background-size: 40px 40px;
156
+ }
157
+
158
+ .chart-line {
159
+ position: absolute;
160
+ top: 50%;
161
+ left: 0;
162
+ right: 0;
163
+ height: 2px;
164
+ background: linear-gradient(to right, #3498db, #9b59b6);
165
+ animation: lineMove 3s ease-in-out infinite;
166
+ }
167
+
168
+ .chart-labels {
169
+ position: absolute;
170
+ bottom: 0;
171
+ left: 0;
172
+ right: 0;
173
+ display: flex;
174
+ justify-content: space-between;
175
+ font-size: 11px;
176
+ color: #90a4ae;
177
+ padding: 0 10px;
178
+ }
179
+ }
180
+
181
+ @keyframes lineMove {
182
+ 0%,
183
+ 100% {
184
+ transform: translateY(0);
185
+ }
186
+ 50% {
187
+ transform: translateY(-50px);
188
+ }
189
+ }
190
+
191
+ .bar-chart {
192
+ width: 100%;
193
+ height: 100%;
194
+ display: flex;
195
+ align-items: flex-end;
196
+ justify-content: center;
197
+ gap: 40px;
198
+ }
199
+
200
+ .bar-group {
201
+ display: flex;
202
+ align-items: flex-end;
203
+ gap: 20px;
204
+ }
205
+
206
+ .bar {
207
+ width: 80px;
208
+ border-radius: 4px 4px 0 0;
209
+ display: flex;
210
+ flex-direction: column;
211
+ align-items: center;
212
+ justify-content: flex-end;
213
+ padding: 10px 0;
214
+ position: relative;
215
+ transition: transform 0.3s ease;
216
+
217
+ &:hover {
218
+ transform: scaleY(1.05);
219
+ }
220
+
221
+ .bar-label {
222
+ font-size: 12px;
223
+ color: #2c3e50;
224
+ margin-bottom: 4px;
225
+ font-weight: 600;
226
+ }
227
+
228
+ .bar-value {
229
+ font-size: 14px;
230
+ color: #fff;
231
+ font-weight: 700;
232
+ margin-bottom: 4px;
233
+ }
234
+ }
235
+
236
+ .orders-table-card {
237
+ background-color: #fff;
238
+ border-radius: 12px;
239
+ padding: 20px;
240
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
241
+ }
242
+
243
+ .orders-table-card h3 {
244
+ color: #2c3e50;
245
+ font-size: 18px;
246
+ margin-bottom: 20px;
247
+ font-weight: 600;
248
+ }
249
+
250
+ .table-container {
251
+ overflow-x: auto;
252
+ }
253
+
254
+ .orders-table {
255
+ width: 100%;
256
+ border-collapse: collapse;
257
+ }
258
+
259
+ .orders-table th {
260
+ background-color: #f5f7fa;
261
+ color: #2c3e50;
262
+ text-align: left;
263
+ padding: 12px;
264
+ font-weight: 600;
265
+ font-size: 14px;
266
+ border-bottom: 2px solid #e0e0e0;
267
+ }
268
+
269
+ .orders-table td {
270
+ padding: 12px;
271
+ font-size: 14px;
272
+ color: #455a64;
273
+ border-bottom: 1px solid #e0e0e0;
274
+ }
275
+
276
+ .orders-table tbody tr:hover {
277
+ background-color: #fafafa;
278
+ }
279
+
280
+ .status {
281
+ padding: 4px 12px;
282
+ border-radius: 12px;
283
+ font-size: 12px;
284
+ font-weight: 600;
285
+
286
+ &.completed {
287
+ background-color: #e8f5e9;
288
+ color: #4caf50;
289
+ }
290
+
291
+ &.pending {
292
+ background-color: #fff3e0;
293
+ color: #ff9800;
294
+ }
295
+
296
+ &.shipping {
297
+ background-color: #e3f2fd;
298
+ color: #2196f3;
299
+ }
300
+
301
+ &.cancelled {
302
+ background-color: #ffebee;
303
+ color: #f44336;
304
+ }
305
+ }
306
+ </style>
@@ -1,192 +0,0 @@
1
- <template>
2
- <div class="detail-panel">
3
- <div class="detail-content">
4
- <div v-if="data">
5
- <div v-for="field in normalizedFields" :key="field.colKey" class="detail-item">
6
- <div class="detail-label">{{ field.title }}</div>
7
- <div class="detail-value">
8
- <!-- 状态字段特殊处理 -->
9
- <template v-if="field.colKey === 'state'">
10
- <TTag v-if="data.state === 1" shape="round" theme="success" variant="light-outline">正常</TTag>
11
- <TTag v-else-if="data.state === 2" shape="round" theme="warning" variant="light-outline">禁用</TTag>
12
- <TTag v-else-if="data.state === 0" shape="round" theme="danger" variant="light-outline">已删除</TTag>
13
- </template>
14
- <!-- 自定义插槽 -->
15
- <template v-else-if="$slots[field.colKey]">
16
- <slot :name="field.colKey" :value="data[field.colKey]" :row="data"></slot>
17
- </template>
18
- <!-- 默认显示 -->
19
- <template v-else>
20
- {{ formatValue(data[field.colKey], field) }}
21
- </template>
22
- </div>
23
- </div>
24
- </div>
25
- <div v-else class="detail-empty">
26
- <div class="empty-icon">📋</div>
27
- <div class="empty-text">{{ emptyText }}</div>
28
- </div>
29
- </div>
30
- </div>
31
- </template>
32
-
33
- <script setup>
34
- const props = defineProps({
35
- /**
36
- * 当前行数据
37
- */
38
- data: {
39
- type: Object,
40
- default: null
41
- },
42
- /**
43
- * 字段配置(columns 格式):[{ colKey: 'id', title: 'ID' }]
44
- * 自动过滤 row-select、operation 等非数据列
45
- */
46
- fields: {
47
- type: Array,
48
- required: true
49
- },
50
- /**
51
- * 需要过滤的列 key
52
- */
53
- excludeKeys: {
54
- type: Array,
55
- default: () => ["row-select", "operation", "index"]
56
- },
57
- /**
58
- * 空数据时的提示文字
59
- */
60
- emptyText: {
61
- type: String,
62
- default: "暂无数据"
63
- }
64
- });
65
-
66
- /**
67
- * 标准化字段配置(仅 columns:colKey/title)
68
- * 注意:模板对“嵌套 ref(对象属性里的 computed/ref)”不会自动解包,
69
- * 所以这里必须暴露为顶层 computed,避免 v-for 误迭代 computed 对象本身。
70
- */
71
- const normalizedFields = computed(() => {
72
- const row = props.data && typeof props.data === "object" ? props.data : null;
73
- const dataId = row && Object.hasOwn(row, "id") ? row.id : undefined;
74
-
75
- const rawFields = props.fields;
76
- const inputFields = Array.isArray(rawFields) ? rawFields : [];
77
- const excludeKeys = Array.isArray(props.excludeKeys) ? props.excludeKeys : [];
78
-
79
- const fields = inputFields
80
- .filter((item) => {
81
- return Boolean(item) && typeof item === "object";
82
- })
83
- .map((item) => {
84
- const colKey = item.colKey;
85
- if (item.colKey.length === 0) {
86
- return null;
87
- }
88
- if (excludeKeys.includes(item.colKey)) {
89
- return null;
90
- }
91
-
92
- return {
93
- colKey: item.colKey,
94
- title: item.title || item.colKey,
95
- default: item.default,
96
- formatter: item.formatter
97
- };
98
- })
99
- .filter((item) => {
100
- return Boolean(item);
101
- });
102
-
103
- // 约定:页面表格不展示 id,但右侧详情始终展示 id(如果 data.id 存在)
104
- if (typeof dataId !== "undefined" && !fields.some((f) => f.colKey === "id")) {
105
- fields.unshift({
106
- colKey: "id",
107
- title: "ID",
108
- default: "-",
109
- formatter: undefined
110
- });
111
- }
112
-
113
- const safeFields = fields.filter((item) => {
114
- if (!item || typeof item !== "object") {
115
- return false;
116
- }
117
- return item.colKey?.length > 0;
118
- });
119
-
120
- return safeFields;
121
- });
122
-
123
- function formatValue(value, field) {
124
- if (value === null || value === undefined || value === "") {
125
- return field.default || "-";
126
- }
127
- if (field.formatter) {
128
- const result = field.formatter(value);
129
- return result;
130
- }
131
- return value;
132
- }
133
- </script>
134
-
135
- <style scoped lang="scss">
136
- .detail-panel {
137
- height: 100%;
138
- overflow: auto;
139
- background: var(--bg-color-container);
140
- }
141
-
142
- .detail-content {
143
- padding: var(--spacing-md);
144
- }
145
-
146
- .detail-item {
147
- margin-bottom: var(--spacing-sm);
148
- padding: var(--spacing-sm) 0;
149
- border-bottom: 1px solid var(--border-color-light);
150
-
151
- &:first-child {
152
- padding-top: 0;
153
- }
154
-
155
- &:last-child {
156
- margin-bottom: 0;
157
- padding-bottom: 0;
158
- border-bottom: none;
159
- }
160
- }
161
-
162
- .detail-label {
163
- color: var(--text-secondary);
164
- margin-bottom: 6px;
165
- font-size: var(--font-size-xs);
166
- font-weight: var(--font-weight-medium);
167
- }
168
-
169
- .detail-value {
170
- color: var(--text-primary);
171
- font-size: var(--font-size-sm);
172
- font-weight: var(--font-weight-medium);
173
- word-break: break-all;
174
- line-height: 1.5;
175
- }
176
-
177
- .detail-empty {
178
- text-align: center;
179
- padding: var(--spacing-xl) 0;
180
- color: var(--text-placeholder);
181
- }
182
-
183
- .empty-icon {
184
- font-size: 40px;
185
- margin-bottom: var(--spacing-sm);
186
- opacity: 0.5;
187
- }
188
-
189
- .empty-text {
190
- font-size: var(--font-size-sm);
191
- }
192
- </style>