@xmszm/core 0.0.2 → 0.0.4

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 (71) hide show
  1. package/README.md +10 -1
  2. package/dist/index.cjs +2 -2
  3. package/dist/index.mjs +1296 -1285
  4. package/dist/plugin/vite/initRouteMeta.cjs +1 -0
  5. package/dist/plugin/vite/initRouteMeta.mjs +13 -0
  6. package/dist/style.css +1 -1
  7. package/docs/.vitepress/config.mjs +10 -1
  8. package/docs/components/config-options.md +125 -0
  9. package/docs/components/dataform.md +175 -22
  10. package/docs/components/datatable.md +21 -39
  11. package/docs/components/dialog.md +155 -16
  12. package/docs/components/options.md +43 -14
  13. package/docs/components/query.md +20 -12
  14. package/docs/components/utils.md +118 -10
  15. package/docs/guide/changelog.md +81 -0
  16. package/docs/guide/config.md +241 -4
  17. package/docs/guide/quickstart.md +27 -2
  18. package/docs/index.md +1 -1
  19. package/docs/usage.md +16 -3
  20. package/examples/README.md +46 -0
  21. package/examples/index.html +14 -0
  22. package/examples/package.json +25 -0
  23. package/examples/pnpm-lock.yaml +1568 -0
  24. package/examples/pnpm-workspace.yaml +2 -0
  25. package/examples/src/AdminSystem.vue +870 -0
  26. package/examples/src/App.vue +330 -0
  27. package/examples/src/Introduction.vue +307 -0
  28. package/examples/src/main.js +22 -0
  29. package/examples/src/utils/permission.js +16 -0
  30. package/examples/src/utils/request.js +10 -0
  31. package/examples/vite.config.js +41 -0
  32. package/package.json +10 -4
  33. package/src/dialog/commonDialog.tsx +286 -0
  34. package/src/dialog/utils/{dialog.js → dialog.ts} +2 -0
  35. package/src/enum/sort.tsx +45 -0
  36. package/src/form/DataForm.vue +26 -52
  37. package/src/{index.js → index.ts} +7 -6
  38. package/src/list/{useList.jsx → useList.tsx} +49 -14
  39. package/src/options/{Options.jsx → Options.tsx} +37 -36
  40. package/src/options/defaultOptions.tsx +656 -0
  41. package/src/query/CommonQuery.vue +57 -89
  42. package/src/table/DataTable.vue +60 -94
  43. package/src/table/opr/{DataColumnCollet.jsx → DataColumnCollet.tsx} +18 -8
  44. package/src/table/opr/{useDataColumn.jsx → useDataColumn.tsx} +43 -48
  45. package/src/table/opr/{useDataColumnButton.jsx → useDataColumnButton.tsx} +13 -6
  46. package/src/table/opr/{useDataColumnPop.jsx → useDataColumnPop.tsx} +13 -5
  47. package/src/utils/{array.js → array.ts} +4 -6
  48. package/src/utils/{config.js → config.ts} +16 -2
  49. package/src/utils/{dialog.js → dialog.ts} +2 -2
  50. package/src/utils/{object.js → object.ts} +1 -0
  51. package/src/utils/{upload.js → upload.ts} +3 -3
  52. package/types/components.d.ts +402 -0
  53. package/types/index.d.ts +145 -7
  54. package/types/plugin/vite/initRouteMeta.d.ts +23 -0
  55. package/types/src.d.ts +55 -0
  56. package/types/vue-shim.d.ts +9 -0
  57. package/examples/demo.vue +0 -224
  58. package/src/dialog/commonDialog.jsx +0 -262
  59. package/src/enum/sort.jsx +0 -31
  60. package/src/options/defaultOptions.jsx +0 -580
  61. /package/src/dialog/{useCommonDialog.js → useCommonDialog.ts} +0 -0
  62. /package/src/directives/{auto-register.js → auto-register.ts} +0 -0
  63. /package/src/directives/{permission.js → permission.ts} +0 -0
  64. /package/src/enum/{options.js → options.ts} +0 -0
  65. /package/src/plugin/{index.js → index.ts} +0 -0
  66. /package/src/plugin/vite/{initRouteMeta.js → initRouteMeta.ts} +0 -0
  67. /package/src/store/utils/{index.js → index.ts} +0 -0
  68. /package/src/table/opr/{useQRCode.js → useQRCode.ts} +0 -0
  69. /package/src/table/utils/{ellipsis.js → ellipsis.ts} +0 -0
  70. /package/src/utils/{auth.js → auth.ts} +0 -0
  71. /package/src/utils/{time.js → time.ts} +0 -0
@@ -0,0 +1,870 @@
1
+ <script setup lang="jsx">
2
+ import { computed, onMounted, reactive, ref } from 'vue'
3
+ import {
4
+ NButton,
5
+ NCard,
6
+ NSpace,
7
+ NLayout,
8
+ NLayoutHeader,
9
+ NLayoutSider,
10
+ NLayoutContent,
11
+ NMenu,
12
+ NIcon,
13
+ NAvatar,
14
+ NDropdown,
15
+ NBadge,
16
+ NDivider,
17
+ NPagination,
18
+ } from 'naive-ui'
19
+ import {
20
+ PersonOutline,
21
+ SettingsOutline,
22
+ LogOutOutline,
23
+ NotificationsOutline,
24
+ MenuOutline,
25
+ PeopleOutline,
26
+ ShieldCheckmarkOutline,
27
+ DocumentTextOutline,
28
+ BarChartOutline,
29
+ HomeOutline,
30
+ ArrowBackOutline,
31
+ } from '@vicons/ionicons5'
32
+ import {
33
+ commonDialogMethod,
34
+ CommonQuery,
35
+ createActionColumnJsx,
36
+ DataTable,
37
+ } from '@xmszm/core'
38
+ import '@xmszm/core/dist/style.css'
39
+
40
+ // 当前选中的菜单项
41
+ const activeMenuKey = ref('users')
42
+ const collapsed = ref(false)
43
+
44
+ // 菜单配置
45
+ const menuOptions = [
46
+ {
47
+ label: '首页',
48
+ key: 'home',
49
+ icon: HomeOutline,
50
+ },
51
+ {
52
+ label: '用户管理',
53
+ key: 'users',
54
+ icon: PeopleOutline,
55
+ },
56
+ {
57
+ label: '角色管理',
58
+ key: 'roles',
59
+ icon: ShieldCheckmarkOutline,
60
+ },
61
+ {
62
+ label: '权限管理',
63
+ key: 'permissions',
64
+ icon: SettingsOutline,
65
+ },
66
+ {
67
+ label: '数据统计',
68
+ key: 'statistics',
69
+ icon: BarChartOutline,
70
+ },
71
+ {
72
+ label: '系统日志',
73
+ key: 'logs',
74
+ icon: DocumentTextOutline,
75
+ },
76
+ ]
77
+
78
+ // 用户信息
79
+ const userInfo = reactive({
80
+ name: '管理员',
81
+ avatar: '',
82
+ role: '超级管理员',
83
+ })
84
+
85
+ // 查询与分页状态
86
+ const listQuery = reactive({
87
+ page: 1,
88
+ pageSize: 10,
89
+ desc: true,
90
+ likeQuery: {
91
+ name: '',
92
+ email: '',
93
+ status: '',
94
+ },
95
+ })
96
+
97
+ const pageState = reactive({
98
+ data: [],
99
+ itemCount: 0,
100
+ loading: false,
101
+ page: computed(() => listQuery.page),
102
+ pageSize: computed(() => listQuery.pageSize),
103
+ showSizePicker: true,
104
+ pageSizes: [10, 20, 50, 100],
105
+ onUpdatePage: (p) => {
106
+ listQuery.page = p
107
+ pageState.fetchData()
108
+ },
109
+ onUpdatePageSize: (ps) => {
110
+ listQuery.pageSize = ps
111
+ listQuery.page = 1
112
+ pageState.fetchData()
113
+ },
114
+ fetchData: async () => {
115
+ pageState.loading = true
116
+ try {
117
+ // 根据当前菜单项模拟不同的数据
118
+ const mockData = generateMockData(activeMenuKey.value)
119
+ pageState.itemCount = mockData.length
120
+ const start = (listQuery.page - 1) * listQuery.pageSize
121
+ const end = start + listQuery.pageSize
122
+ pageState.data = mockData.slice(start, end)
123
+ } finally {
124
+ pageState.loading = false
125
+ }
126
+ },
127
+ search: () => {
128
+ listQuery.page = 1
129
+ pageState.fetchData()
130
+ },
131
+ reset: () => {
132
+ listQuery.likeQuery = { name: '', email: '', status: '' }
133
+ listQuery.page = 1
134
+ pageState.fetchData()
135
+ },
136
+ })
137
+
138
+ // 根据菜单项生成模拟数据
139
+ function generateMockData(key) {
140
+ const count = 35
141
+ switch (key) {
142
+ case 'users':
143
+ return Array.from({ length: count }).map((_, i) => ({
144
+ id: i + 1,
145
+ name: `用户${i + 1}`,
146
+ email: `user${i + 1}@example.com`,
147
+ role: i % 3 === 0 ? '管理员' : i % 3 === 1 ? '编辑' : '普通用户',
148
+ status: i % 4 === 0 ? '禁用' : '启用',
149
+ createTime: `2024-01-${String(i % 28 + 1).padStart(2, '0')} 10:00:00`,
150
+ }))
151
+ case 'roles':
152
+ return Array.from({ length: count }).map((_, i) => ({
153
+ id: i + 1,
154
+ name: `角色${i + 1}`,
155
+ code: `ROLE_${String(i + 1).padStart(3, '0')}`,
156
+ description: `这是角色${i + 1}的描述信息`,
157
+ userCount: Math.floor(Math.random() * 100),
158
+ createTime: `2024-01-${String(i % 28 + 1).padStart(2, '0')} 10:00:00`,
159
+ }))
160
+ case 'permissions':
161
+ return Array.from({ length: count }).map((_, i) => ({
162
+ id: i + 1,
163
+ name: `权限${i + 1}`,
164
+ code: `PERM_${String(i + 1).padStart(3, '0')}`,
165
+ type: i % 3 === 0 ? '菜单' : i % 3 === 1 ? '按钮' : '接口',
166
+ path: `/system/permission${i + 1}`,
167
+ createTime: `2024-01-${String(i % 28 + 1).padStart(2, '0')} 10:00:00`,
168
+ }))
169
+ case 'statistics':
170
+ return Array.from({ length: count }).map((_, i) => ({
171
+ id: i + 1,
172
+ date: `2024-01-${String(i % 28 + 1).padStart(2, '0')}`,
173
+ pv: Math.floor(Math.random() * 10000),
174
+ uv: Math.floor(Math.random() * 5000),
175
+ orderCount: Math.floor(Math.random() * 500),
176
+ revenue: (Math.random() * 100000).toFixed(2),
177
+ }))
178
+ case 'logs':
179
+ return Array.from({ length: count }).map((_, i) => ({
180
+ id: i + 1,
181
+ operator: `操作员${i + 1}`,
182
+ action: i % 4 === 0 ? '新增' : i % 4 === 1 ? '编辑' : i % 4 === 2 ? '删除' : '查询',
183
+ module: i % 3 === 0 ? '用户管理' : i % 3 === 1 ? '角色管理' : '权限管理',
184
+ ip: `192.168.1.${i % 255}`,
185
+ time: `2024-01-${String(i % 28 + 1).padStart(2, '0')} ${String(10 + (i % 12)).padStart(2, '0')}:${String(i % 60).padStart(2, '0')}:00`,
186
+ }))
187
+ default:
188
+ return []
189
+ }
190
+ }
191
+
192
+ // 根据菜单项获取查询配置
193
+ function getQueryOptions() {
194
+ switch (activeMenuKey.value) {
195
+ case 'users':
196
+ return [
197
+ { label: '用户名', key: 'name', queryType: 'likeQuery' },
198
+ { label: '邮箱', key: 'email', queryType: 'likeQuery' },
199
+ {
200
+ label: '状态',
201
+ key: 'status',
202
+ queryType: 'likeQuery',
203
+ way: 'select',
204
+ options: [
205
+ { name: '启用', id: '启用' },
206
+ { name: '禁用', id: '禁用' },
207
+ ],
208
+ },
209
+ ]
210
+ case 'roles':
211
+ return [
212
+ { label: '角色名称', key: 'name', queryType: 'likeQuery' },
213
+ { label: '角色编码', key: 'code', queryType: 'likeQuery' },
214
+ ]
215
+ case 'permissions':
216
+ return [
217
+ { label: '权限名称', key: 'name', queryType: 'likeQuery' },
218
+ { label: '权限编码', key: 'code', queryType: 'likeQuery' },
219
+ {
220
+ label: '类型',
221
+ key: 'type',
222
+ queryType: 'likeQuery',
223
+ way: 'select',
224
+ options: [
225
+ { name: '菜单', id: '菜单' },
226
+ { name: '按钮', id: '按钮' },
227
+ { name: '接口', id: '接口' },
228
+ ],
229
+ },
230
+ ]
231
+ case 'statistics':
232
+ return [
233
+ { label: '日期', key: 'date', queryType: 'likeQuery', way: 'date-picker' },
234
+ ]
235
+ case 'logs':
236
+ return [
237
+ { label: '操作员', key: 'operator', queryType: 'likeQuery' },
238
+ { label: '操作模块', key: 'module', queryType: 'likeQuery' },
239
+ ]
240
+ default:
241
+ return []
242
+ }
243
+ }
244
+
245
+ // 根据菜单项获取表格列配置
246
+ function getTableColumns() {
247
+ switch (activeMenuKey.value) {
248
+ case 'users':
249
+ return [
250
+ { title: 'ID', key: 'id', width: 80 },
251
+ { title: '用户名', key: 'name', width: 120 },
252
+ { title: '邮箱', key: 'email', width: 200 },
253
+ { title: '角色', key: 'role', width: 120 },
254
+ { title: '状态', key: 'status', width: 100 },
255
+ { title: '创建时间', key: 'createTime', width: 180 },
256
+ ]
257
+ case 'roles':
258
+ return [
259
+ { title: 'ID', key: 'id', width: 80 },
260
+ { title: '角色名称', key: 'name', width: 150 },
261
+ { title: '角色编码', key: 'code', width: 150 },
262
+ { title: '描述', key: 'description', width: 200 },
263
+ { title: '用户数', key: 'userCount', width: 100 },
264
+ { title: '创建时间', key: 'createTime', width: 180 },
265
+ ]
266
+ case 'permissions':
267
+ return [
268
+ { title: 'ID', key: 'id', width: 80 },
269
+ { title: '权限名称', key: 'name', width: 150 },
270
+ { title: '权限编码', key: 'code', width: 150 },
271
+ { title: '类型', key: 'type', width: 100 },
272
+ { title: '路径', key: 'path', width: 200 },
273
+ { title: '创建时间', key: 'createTime', width: 180 },
274
+ ]
275
+ case 'statistics':
276
+ return [
277
+ { title: 'ID', key: 'id', width: 80 },
278
+ { title: '日期', key: 'date', width: 120 },
279
+ { title: 'PV', key: 'pv', width: 120 },
280
+ { title: 'UV', key: 'uv', width: 120 },
281
+ { title: '订单数', key: 'orderCount', width: 120 },
282
+ { title: '收入(元)', key: 'revenue', width: 120 },
283
+ ]
284
+ case 'logs':
285
+ return [
286
+ { title: 'ID', key: 'id', width: 80 },
287
+ { title: '操作员', key: 'operator', width: 120 },
288
+ { title: '操作', key: 'action', width: 100 },
289
+ { title: '模块', key: 'module', width: 120 },
290
+ { title: 'IP地址', key: 'ip', width: 150 },
291
+ { title: '操作时间', key: 'time', width: 180 },
292
+ ]
293
+ default:
294
+ return []
295
+ }
296
+ }
297
+
298
+ // 根据菜单项获取表单配置
299
+ function getFormOptions() {
300
+ switch (activeMenuKey.value) {
301
+ case 'users':
302
+ return [
303
+ { key: 'name', label: '用户名', way: 'input', required: true },
304
+ { key: 'email', label: '邮箱', way: 'input', required: true },
305
+ {
306
+ key: 'role',
307
+ label: '角色',
308
+ way: 'select',
309
+ required: true,
310
+ options: [
311
+ { name: '管理员', id: '管理员' },
312
+ { name: '编辑', id: '编辑' },
313
+ { name: '普通用户', id: '普通用户' },
314
+ ],
315
+ },
316
+ {
317
+ key: 'status',
318
+ label: '状态',
319
+ way: 'select',
320
+ required: true,
321
+ options: [
322
+ { name: '启用', id: '启用' },
323
+ { name: '禁用', id: '禁用' },
324
+ ],
325
+ },
326
+ ]
327
+ case 'roles':
328
+ return [
329
+ { key: 'name', label: '角色名称', way: 'input', required: true },
330
+ { key: 'code', label: '角色编码', way: 'input', required: true },
331
+ { key: 'description', label: '描述', way: 'textarea' },
332
+ ]
333
+ case 'permissions':
334
+ return [
335
+ { key: 'name', label: '权限名称', way: 'input', required: true },
336
+ { key: 'code', label: '权限编码', way: 'input', required: true },
337
+ {
338
+ key: 'type',
339
+ label: '类型',
340
+ way: 'select',
341
+ required: true,
342
+ options: [
343
+ { name: '菜单', id: '菜单' },
344
+ { name: '按钮', id: '按钮' },
345
+ { name: '接口', id: '接口' },
346
+ ],
347
+ },
348
+ { key: 'path', label: '路径', way: 'input' },
349
+ ]
350
+ default:
351
+ return []
352
+ }
353
+ }
354
+
355
+ // 表格列与操作列
356
+ const columns = computed(() => getTableColumns())
357
+ const defaultColumns = []
358
+ const selectColumns = { type: 'selection', width: '40px' }
359
+ const opr = createActionColumnJsx([
360
+ {
361
+ label: '编辑',
362
+ type: 'primary',
363
+ onClick: (row) => onAdd(row, 'edit'),
364
+ },
365
+ {
366
+ label: '删除',
367
+ type: 'error',
368
+ mode: 'pop',
369
+ onClick: (row) => {
370
+ console.log('删除', row)
371
+ pageState.fetchData()
372
+ },
373
+ },
374
+ ])
375
+
376
+ // 弹窗新增/编辑
377
+ function onAdd(row = null, mode = 'add') {
378
+ if (activeMenuKey.value === 'statistics' || activeMenuKey.value === 'logs') {
379
+ return
380
+ }
381
+ const formOptions = getFormOptions()
382
+ if (formOptions.length === 0) return
383
+
384
+ commonDialogMethod({
385
+ title: mode === 'add' ? '新增' : '编辑',
386
+ mode,
387
+ options: formOptions,
388
+ valueData: { ...row },
389
+ interfaceFn: async (data, { close }) => {
390
+ console.log('提交数据', data)
391
+ pageState.fetchData()
392
+ close()
393
+ },
394
+ })
395
+ }
396
+
397
+ // 导出示例
398
+ const exportLoading = reactive({ value: false })
399
+ function onExport() {
400
+ exportLoading.value = true
401
+ setTimeout(() => (exportLoading.value = false), 800)
402
+ }
403
+
404
+ // 菜单切换
405
+ function handleMenuSelect(key) {
406
+ activeMenuKey.value = key
407
+ listQuery.page = 1
408
+ listQuery.likeQuery = { name: '', email: '', status: '' }
409
+ pageState.fetchData()
410
+ }
411
+
412
+ // 用户下拉菜单
413
+ const userMenuOptions = [
414
+ {
415
+ label: '个人设置',
416
+ key: 'settings',
417
+ icon: SettingsOutline,
418
+ },
419
+ {
420
+ label: '退出登录',
421
+ key: 'logout',
422
+ icon: LogOutOutline,
423
+ },
424
+ ]
425
+
426
+ function handleUserMenuSelect(key) {
427
+ if (key === 'logout') {
428
+ console.log('退出登录')
429
+ } else if (key === 'settings') {
430
+ console.log('个人设置')
431
+ }
432
+ }
433
+
434
+ // 返回按钮处理
435
+ function handleBack() {
436
+ // 如果在 GitHub Pages 环境,跳转到文档
437
+ if (window.location.pathname.includes('/examples/')) {
438
+ window.location.href = '/core/'
439
+ } else {
440
+ // 本地开发环境,切换视图
441
+ localStorage.setItem('core-app-view', 'introduction')
442
+ window.dispatchEvent(new CustomEvent('view-change', {
443
+ detail: { view: 'introduction' }
444
+ }))
445
+ }
446
+ }
447
+
448
+ onMounted(() => {
449
+ pageState.fetchData()
450
+ })
451
+ </script>
452
+
453
+ <template>
454
+ <n-layout class="admin-layout" has-sider>
455
+ <!-- 侧边栏 -->
456
+ <n-layout-sider
457
+ :collapsed="collapsed"
458
+ :collapsed-width="64"
459
+ :width="240"
460
+ show-trigger
461
+ collapse-mode="width"
462
+ bordered
463
+ class="admin-sider"
464
+ >
465
+ <div class="sider-header">
466
+ <div class="logo">
467
+ <n-icon :component="MenuOutline" :size="24" />
468
+ <span v-if="!collapsed" class="logo-text">管理系统</span>
469
+ </div>
470
+ </div>
471
+ <div class="sider-menu-wrapper">
472
+ <n-menu
473
+ :value="activeMenuKey"
474
+ :options="menuOptions"
475
+ :collapsed="collapsed"
476
+ :collapsed-width="64"
477
+ :collapsed-icon-size="22"
478
+ @update:value="handleMenuSelect"
479
+ />
480
+ </div>
481
+ </n-layout-sider>
482
+
483
+ <!-- 主布局 -->
484
+ <n-layout class="main-layout">
485
+ <!-- 顶部导航栏 -->
486
+ <n-layout-header bordered class="admin-header">
487
+ <div class="header-left">
488
+ <n-button
489
+ quaternary
490
+ circle
491
+ @click="collapsed = !collapsed"
492
+ >
493
+ <template #icon>
494
+ <n-icon :component="MenuOutline" />
495
+ </template>
496
+ </n-button>
497
+ <n-divider vertical style="margin: 0 8px" />
498
+ <n-button
499
+ quaternary
500
+ @click="handleBack"
501
+ >
502
+ <template #icon>
503
+ <n-icon :component="ArrowBackOutline" />
504
+ </template>
505
+ 返回
506
+ </n-button>
507
+ </div>
508
+ <div class="header-right">
509
+ <n-space :size="16">
510
+ <!-- 通知 -->
511
+ <n-badge :value="5" :max="99">
512
+ <n-button quaternary circle>
513
+ <template #icon>
514
+ <n-icon :component="NotificationsOutline" :size="20" />
515
+ </template>
516
+ </n-button>
517
+ </n-badge>
518
+
519
+ <!-- 用户信息 -->
520
+ <n-dropdown
521
+ :options="userMenuOptions"
522
+ @select="handleUserMenuSelect"
523
+ >
524
+ <n-space :size="12" style="cursor: pointer; padding: 4px 8px">
525
+ <n-avatar round :size="32">
526
+ {{ userInfo.name.charAt(0) }}
527
+ </n-avatar>
528
+ <div v-if="!collapsed" class="user-info">
529
+ <div class="user-name">{{ userInfo.name }}</div>
530
+ <div class="user-role">{{ userInfo.role }}</div>
531
+ </div>
532
+ </n-space>
533
+ </n-dropdown>
534
+ </n-space>
535
+ </div>
536
+ </n-layout-header>
537
+
538
+ <!-- 主内容区 -->
539
+ <n-layout-content class="admin-content">
540
+ <!-- 内容区域(可滚动) -->
541
+ <div class="content-scroll-area">
542
+ <!-- 页面标题 -->
543
+ <div class="page-header">
544
+ <h2 class="page-title">
545
+ {{ menuOptions.find(m => m.key === activeMenuKey)?.label || '管理后台' }}
546
+ </h2>
547
+ </div>
548
+
549
+ <!-- 查询区域 -->
550
+ <n-card class="query-card" v-if="activeMenuKey !== 'home'">
551
+ <CommonQuery
552
+ :query="listQuery"
553
+ :options="getQueryOptions()"
554
+ @submit="pageState.search()"
555
+ @reset="pageState.reset()"
556
+ />
557
+ </n-card>
558
+
559
+ <!-- 操作区域 -->
560
+ <div class="action-bar" v-if="activeMenuKey !== 'home' && activeMenuKey !== 'statistics' && activeMenuKey !== 'logs'">
561
+ <n-space>
562
+ <n-button type="primary" @click="onAdd()">新增</n-button>
563
+ <n-button type="primary" :loading="exportLoading.value" @click="onExport">
564
+ 导出
565
+ </n-button>
566
+ </n-space>
567
+ </div>
568
+
569
+ <!-- 首页内容 -->
570
+ <div v-if="activeMenuKey === 'home'" class="home-content">
571
+ <n-space vertical :size="24">
572
+ <n-card title="系统概览">
573
+ <n-space :size="24">
574
+ <n-card size="small" class="stat-card">
575
+ <div class="stat-value">1,234</div>
576
+ <div class="stat-label">总用户数</div>
577
+ </n-card>
578
+ <n-card size="small" class="stat-card">
579
+ <div class="stat-value">56</div>
580
+ <div class="stat-label">角色数量</div>
581
+ </n-card>
582
+ <n-card size="small" class="stat-card">
583
+ <div class="stat-value">128</div>
584
+ <div class="stat-label">权限数量</div>
585
+ </n-card>
586
+ <n-card size="small" class="stat-card">
587
+ <div class="stat-value">5,678</div>
588
+ <div class="stat-label">今日访问</div>
589
+ </n-card>
590
+ </n-space>
591
+ </n-card>
592
+ </n-space>
593
+ </div>
594
+
595
+ <!-- 表格区域 -->
596
+ <n-card v-else class="table-card">
597
+ <DataTable
598
+ :data="pageState.data"
599
+ :pagination="false"
600
+ :columns="columns"
601
+ :opr-columns="opr"
602
+ :default-columns="defaultColumns"
603
+ :row-key="(row) => row?.id"
604
+ :select-columns="selectColumns"
605
+ :loading="pageState.loading"
606
+ :flex-height="false"
607
+ />
608
+ </n-card>
609
+ </div>
610
+
611
+ <!-- 底部区域(固定) -->
612
+ <div class="content-footer" v-if="activeMenuKey !== 'home'">
613
+ <n-card class="footer-card">
614
+ <div class="footer-content">
615
+ <div class="pagination-wrapper">
616
+ <n-pagination
617
+ v-model:page="listQuery.page"
618
+ v-model:page-size="listQuery.pageSize"
619
+ :page-count="Math.ceil(pageState.itemCount / listQuery.pageSize)"
620
+ :page-sizes="pageState.pageSizes"
621
+ :show-size-picker="pageState.showSizePicker"
622
+ @update:page="pageState.onUpdatePage"
623
+ @update:page-size="pageState.onUpdatePageSize"
624
+ />
625
+ </div>
626
+ </div>
627
+ </n-card>
628
+ </div>
629
+ </n-layout-content>
630
+ </n-layout>
631
+ </n-layout>
632
+ </template>
633
+
634
+ <style scoped lang="less">
635
+ .admin-layout {
636
+ height: 100vh;
637
+ display: flex;
638
+ flex-direction: row;
639
+ overflow: hidden;
640
+
641
+ :deep(.n-layout) {
642
+ display: flex;
643
+ flex-direction: column;
644
+ height: 100vh;
645
+ overflow: hidden;
646
+ }
647
+ }
648
+
649
+ .main-layout {
650
+ flex: 1;
651
+ display: flex;
652
+ flex-direction: column;
653
+ overflow: hidden;
654
+ }
655
+
656
+ .admin-sider {
657
+ height: 100vh;
658
+ overflow: hidden;
659
+ display: flex;
660
+ flex-direction: column;
661
+
662
+ :deep(.n-layout-sider-scroll-container) {
663
+ display: flex;
664
+ flex-direction: column;
665
+ height: 100%;
666
+ overflow: hidden;
667
+ }
668
+
669
+ .sider-header {
670
+ height: 64px;
671
+ flex-shrink: 0;
672
+ display: flex;
673
+ align-items: center;
674
+ justify-content: center;
675
+ border-bottom: 1px solid var(--n-border-color);
676
+
677
+ .logo {
678
+ display: flex;
679
+ align-items: center;
680
+ gap: 12px;
681
+ font-size: 18px;
682
+ font-weight: 600;
683
+ color: #18a058;
684
+
685
+ .logo-text {
686
+ white-space: nowrap;
687
+ }
688
+ }
689
+ }
690
+
691
+ .sider-menu-wrapper {
692
+ flex: 1;
693
+ overflow-y: auto;
694
+ overflow-x: hidden;
695
+ }
696
+ }
697
+
698
+ .admin-header {
699
+ height: 64px;
700
+ flex-shrink: 0;
701
+ padding: 0 24px;
702
+ display: flex;
703
+ align-items: center;
704
+ justify-content: space-between;
705
+ background: #fff;
706
+ position: sticky;
707
+ top: 0;
708
+ z-index: 100;
709
+
710
+ .header-left {
711
+ display: flex;
712
+ align-items: center;
713
+ }
714
+
715
+ .header-right {
716
+ .user-info {
717
+ display: flex;
718
+ flex-direction: column;
719
+ line-height: 1.4;
720
+
721
+ .user-name {
722
+ font-size: 14px;
723
+ font-weight: 500;
724
+ color: #333;
725
+ }
726
+
727
+ .user-role {
728
+ font-size: 12px;
729
+ color: #999;
730
+ }
731
+ }
732
+ }
733
+ }
734
+
735
+ .admin-content {
736
+ flex: 1;
737
+ display: flex;
738
+ flex-direction: column;
739
+ overflow: hidden;
740
+ background: #f5f5f5;
741
+ min-height: 0;
742
+
743
+ // 内容滚动区域
744
+ .content-scroll-area {
745
+ flex: 1;
746
+ display: flex;
747
+ flex-direction: column;
748
+ overflow-y: auto;
749
+ overflow-x: hidden;
750
+ padding: 24px;
751
+ min-height: 0;
752
+ }
753
+
754
+ .page-header {
755
+ flex-shrink: 0;
756
+ margin-bottom: 24px;
757
+
758
+ .page-title {
759
+ margin: 0;
760
+ font-size: 20px;
761
+ font-weight: 600;
762
+ color: #333;
763
+ }
764
+ }
765
+
766
+ .query-card {
767
+ flex-shrink: 0;
768
+ margin-bottom: 16px;
769
+ border-radius: 8px;
770
+ }
771
+
772
+ .action-bar {
773
+ flex-shrink: 0;
774
+ margin-bottom: 16px;
775
+ }
776
+
777
+ .table-card {
778
+ flex: 1;
779
+ display: flex;
780
+ flex-direction: column;
781
+ min-height: 0;
782
+ border-radius: 8px;
783
+ overflow: hidden;
784
+
785
+ :deep(.n-card__content) {
786
+ flex: 1;
787
+ display: flex;
788
+ flex-direction: column;
789
+ min-height: 0;
790
+ overflow: hidden;
791
+ }
792
+
793
+ :deep(.n-data-table) {
794
+ flex: 1;
795
+ display: flex;
796
+ flex-direction: column;
797
+ min-height: 0;
798
+
799
+ .n-data-table-wrapper {
800
+ flex: 1;
801
+ overflow: auto;
802
+ }
803
+
804
+ .n-data-table-th {
805
+ background-color: #f7f8fa;
806
+ border-right: 1px solid var(--n-merged-border-color);
807
+ white-space: break-spaces;
808
+ }
809
+
810
+ .n-data-table-th--last {
811
+ border-right: 1px solid transparent;
812
+ }
813
+
814
+ .n-data-table-td--last-row {
815
+ border-bottom: 1px solid var(--n-merged-border-color);
816
+ }
817
+ }
818
+ }
819
+
820
+ .home-content {
821
+ flex: 1;
822
+ display: flex;
823
+ flex-direction: column;
824
+ min-height: 0;
825
+
826
+ .stat-card {
827
+ min-width: 200px;
828
+ text-align: center;
829
+
830
+ .stat-value {
831
+ font-size: 32px;
832
+ font-weight: 600;
833
+ color: #18a058;
834
+ margin-bottom: 8px;
835
+ }
836
+
837
+ .stat-label {
838
+ font-size: 14px;
839
+ color: #666;
840
+ }
841
+ }
842
+ }
843
+
844
+ // 底部固定区域
845
+ .content-footer {
846
+ flex-shrink: 0;
847
+ border-top: 1px solid var(--n-border-color);
848
+ background: #fff;
849
+
850
+ .footer-card {
851
+ border-radius: 0;
852
+ border: none;
853
+ margin: 0;
854
+
855
+ .footer-content {
856
+ display: flex;
857
+ justify-content: flex-end;
858
+ align-items: center;
859
+ padding: 12px 24px;
860
+
861
+ .pagination-wrapper {
862
+ display: flex;
863
+ align-items: center;
864
+ }
865
+ }
866
+ }
867
+ }
868
+ }
869
+ </style>
870
+