xto-fronted 0.4.93 → 0.4.95

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 (92) hide show
  1. package/dist/assets/404-C9Uh6Uu-.css +1 -0
  2. package/dist/assets/404-zjGLLssH.js +1 -0
  3. package/dist/assets/_plugin-vue_export-helper-DlAUqK2U.js +1 -0
  4. package/dist/assets/index-B5xc4gQB.css +1 -0
  5. package/dist/assets/index-BDgOY6Rp.js +1 -0
  6. package/dist/assets/index-BIoRANs0.js +1 -0
  7. package/dist/assets/index-BRR97dc6.js +1 -0
  8. package/dist/assets/index-Bz0BgZQ1.js +1 -0
  9. package/dist/assets/index-CAdztNsv.css +1 -0
  10. package/dist/assets/index-CCXrcISf.css +1 -0
  11. package/dist/assets/index-CfpZmcpk.css +1 -0
  12. package/dist/assets/index-CwJSA85U.js +1 -0
  13. package/dist/assets/index-CwRA10ac.js +1 -0
  14. package/dist/assets/index-D8NDxq9d.js +1 -0
  15. package/dist/assets/index-DEB6-Iv_.js +2 -0
  16. package/dist/assets/index-DM4Ezclc.css +1 -0
  17. package/dist/assets/index-DYv7nImj.css +1 -0
  18. package/dist/assets/index-t-2Y0KhA.css +1 -0
  19. package/dist/assets/vendor-CUVPinTg.js +13 -0
  20. package/dist/assets/vue-vendor-DeJXJVbN.js +29 -0
  21. package/dist/assets/xto-base-CL2NKZJJ.css +1 -0
  22. package/dist/assets/xto-base-PwLGsxxb.js +1 -0
  23. package/dist/assets/xto-business--V1F5Gwb.css +1 -0
  24. package/dist/assets/xto-core-CtL4zKiV.js +1 -0
  25. package/dist/assets/xto-data-MxZsiJgi.css +1 -0
  26. package/dist/assets/xto-data-bCXQa7fT.js +1 -0
  27. package/dist/assets/xto-feedback-Bxx38c3P.css +1 -0
  28. package/dist/assets/xto-feedback-CPydp0kn.js +1 -0
  29. package/dist/assets/xto-form-Cu6q3VLG.css +1 -0
  30. package/dist/assets/xto-form-bywohdAf.js +1 -0
  31. package/dist/assets/xto-layout-BDD6sSlM.css +1 -0
  32. package/dist/assets/xto-navigation-Bbdpine9.js +1 -0
  33. package/dist/assets/xto-navigation-XfpyMpEo.css +1 -0
  34. package/dist/components/Layout/MixTopMenu.vue.d.ts +6 -1
  35. package/dist/components/Layout/Sidebar.vue.d.ts +2 -0
  36. package/dist/components/Layout/TopMenu.vue.d.ts +6 -1
  37. package/dist/components/Layout/index.vue.d.ts +6 -1
  38. package/dist/{index-D59X6HmM.js → index-3ekBp4iW.js} +2 -2
  39. package/dist/{index-Cp8kqjtv.js → index-58aI1w0v.js} +2 -2
  40. package/dist/{index-me_Uu2lk.js → index-A_B_Ap_A.js} +1560 -1590
  41. package/dist/{index-lJrh5CFc.js → index-B5DLfOYb.js} +23 -23
  42. package/dist/index-BAmYUT0G.js +189 -0
  43. package/dist/{index-B4U8Dy2W.js → index-BK4Mut6H.js} +2 -2
  44. package/dist/index-BRvi9qW-.js +515 -0
  45. package/dist/index-BVGW4DDQ.js +189 -0
  46. package/dist/index-BXg94yA2.js +515 -0
  47. package/dist/index-Bmf0YbVq.js +189 -0
  48. package/dist/index-C2-a5KSQ.js +4233 -0
  49. package/dist/index-CAHSv7LK.js +4285 -0
  50. package/dist/index-CeZ0CSSs.js +641 -0
  51. package/dist/index-Cf8E7FM1.js +4270 -0
  52. package/dist/index-CgyQqbdx.js +189 -0
  53. package/dist/index-ChowNrlU.js +641 -0
  54. package/dist/index-D25KzR0I.js +479 -0
  55. package/dist/index-DCBIjLHy.js +515 -0
  56. package/dist/index-DEYOivza.js +641 -0
  57. package/dist/index-DReodgBw.js +4233 -0
  58. package/dist/index-DjERNRXX.js +515 -0
  59. package/dist/index-LSdsO2Ox.js +479 -0
  60. package/dist/index-UJixTdep.js +479 -0
  61. package/dist/index-gBlRG4kk.js +479 -0
  62. package/dist/index-xWU3J3OH.js +641 -0
  63. package/dist/index.es.js +1 -1
  64. package/dist/index.html +28 -0
  65. package/dist/index.umd.js +8 -8
  66. package/dist/style.css +1 -1
  67. package/package.json +91 -91
  68. package/src/App.vue +48 -48
  69. package/src/assets/styles/_dark.scss +639 -639
  70. package/src/assets/styles/_root.scss +183 -183
  71. package/src/assets/styles/_variables.scss +69 -69
  72. package/src/assets/styles/index.scss +460 -460
  73. package/src/components/Layout/MixTopMenu.vue +8 -1
  74. package/src/components/Layout/Sidebar.vue +200 -198
  75. package/src/components/Layout/SidebarMenuItem.vue +158 -158
  76. package/src/components/Layout/TopMenu.vue +1177 -1170
  77. package/src/components/Layout/index.vue +199 -192
  78. package/src/composables/useI18n.ts +43 -43
  79. package/src/index.ts +100 -100
  80. package/src/router/layoutRoute.ts +59 -59
  81. package/src/stores/index.ts +15 -15
  82. package/src/stores/locale.ts +66 -66
  83. package/src/types/json-bigint.d.ts +18 -18
  84. package/src/types/xto.d.ts +172 -172
  85. package/src/utils/request.ts +184 -184
  86. package/src/views/dashboard/index.vue +545 -545
  87. package/src/views/error/403.vue +251 -251
  88. package/src/views/error/404.vue +253 -253
  89. package/src/views/login/index.vue +586 -586
  90. package/src/views/system/menu/index.vue +690 -690
  91. package/src/views/system/role/index.vue +583 -583
  92. package/src/views/system/user/index.vue +655 -655
@@ -1,656 +1,656 @@
1
- <script setup lang="ts">
2
- import { ref, reactive, computed, onMounted } from 'vue'
3
- import { Form, FormItem, Input, Select, Switch } from '@xto/form'
4
- import { Tag, Pagination } from '@xto/data'
5
- import { Modal, Message, Popconfirm } from '@xto/feedback'
6
- import { Space, Button } from '@xto/base'
7
- import { Status, StatusOptions } from '@/enums'
8
-
9
- // 用户数据类型
10
- interface User {
11
- id: number
12
- username: string
13
- nickname: string
14
- email: string
15
- phone: string
16
- status: Status
17
- roles: string[]
18
- createTime: string
19
- }
20
-
21
- // Mock 数据
22
- const mockUsers: User[] = [
23
- { id: 1, username: 'admin', nickname: '管理员', email: 'admin@example.com', phone: '13800138001', status: Status.ENABLED, roles: ['admin'], createTime: '2024-01-01 10:00:00' },
24
- { id: 2, username: 'zhangsan', nickname: '张三', email: 'zhangsan@example.com', phone: '13800138002', status: Status.ENABLED, roles: ['editor'], createTime: '2024-01-02 10:00:00' },
25
- { id: 3, username: 'lisi', nickname: '李四', email: 'lisi@example.com', phone: '13800138003', status: Status.DISABLED, roles: ['viewer'], createTime: '2024-01-03 10:00:00' },
26
- { id: 4, username: 'wangwu', nickname: '王五', email: 'wangwu@example.com', phone: '13800138004', status: Status.ENABLED, roles: ['editor'], createTime: '2024-01-04 10:00:00' },
27
- { id: 5, username: 'zhaoliu', nickname: '赵六', email: 'zhaoliu@example.com', phone: '13800138005', status: Status.ENABLED, roles: ['viewer'], createTime: '2024-01-05 10:00:00' }
28
- ]
29
-
30
- const loading = ref(false)
31
- const userList = ref<User[]>([])
32
- const total = ref(0)
33
- const currentPage = ref(1)
34
- const pageSize = ref(10)
35
-
36
- // 搜索条件
37
- const searchForm = reactive({
38
- keyword: '',
39
- status: undefined as Status | undefined
40
- })
41
-
42
- // 弹窗
43
- const modalVisible = ref(false)
44
- const modalTitle = computed(() => formData.id ? '编辑用户' : '新增用户')
45
- const formData = reactive({
46
- id: 0,
47
- username: '',
48
- nickname: '',
49
- email: '',
50
- phone: '',
51
- status: Status.ENABLED,
52
- roles: [] as string[]
53
- })
54
-
55
- const rules: Record<string, any[]> = {
56
- username: [
57
- { required: true, message: '请输入用户名', trigger: 'blur' }
58
- ],
59
- nickname: [
60
- { required: true, message: '请输入昵称', trigger: 'blur' }
61
- ],
62
- email: [
63
- { required: true, message: '请输入邮箱', trigger: 'blur' },
64
- { type: 'email', message: '请输入正确的邮箱格式', trigger: 'blur' }
65
- ]
66
- }
67
-
68
- const formRef = ref()
69
-
70
- // 获取用户列表
71
- const getUserList = () => {
72
- loading.value = true
73
- setTimeout(() => {
74
- let list = [...mockUsers]
75
-
76
- // 搜索过滤
77
- if (searchForm.keyword) {
78
- list = list.filter(user =>
79
- user.username.includes(searchForm.keyword) ||
80
- user.nickname.includes(searchForm.keyword)
81
- )
82
- }
83
- if (searchForm.status !== undefined) {
84
- list = list.filter(user => user.status === searchForm.status)
85
- }
86
-
87
- total.value = list.length
88
- userList.value = list.slice((currentPage.value - 1) * pageSize.value, currentPage.value * pageSize.value)
89
- loading.value = false
90
- }, 300)
91
- }
92
-
93
- // 搜索
94
- const handleSearch = () => {
95
- currentPage.value = 1
96
- getUserList()
97
- }
98
-
99
- // 重置
100
- const handleReset = () => {
101
- searchForm.keyword = ''
102
- searchForm.status = undefined
103
- currentPage.value = 1
104
- getUserList()
105
- }
106
-
107
- // 新增
108
- const handleAdd = () => {
109
- Object.assign(formData, {
110
- id: 0,
111
- username: '',
112
- nickname: '',
113
- email: '',
114
- phone: '',
115
- status: Status.ENABLED,
116
- roles: []
117
- })
118
- modalVisible.value = true
119
- }
120
-
121
- // 编辑
122
- const handleEdit = (row: User) => {
123
- Object.assign(formData, row)
124
- modalVisible.value = true
125
- }
126
-
127
- // 删除
128
- const handleDelete = (_id: number) => {
129
- Message.success('删除成功')
130
- getUserList()
131
- }
132
-
133
- // 提交
134
- const handleSubmit = async () => {
135
- try {
136
- await formRef.value?.validate()
137
- Message.success(formData.id ? '编辑成功' : '新增成功')
138
- modalVisible.value = false
139
- getUserList()
140
- } catch (error) {
141
- console.error(error)
142
- }
143
- }
144
-
145
- // 状态切换
146
- const handleStatusChange = (row: User) => {
147
- Message.success(`已${row.status === Status.ENABLED ? '启用' : '禁用'}用户 ${row.username}`)
148
- }
149
-
150
- onMounted(() => {
151
- getUserList()
152
- })
153
- </script>
154
-
155
- <template>
156
- <div class="user-page">
157
- <!-- 搜索栏 -->
158
- <div class="search-section">
159
- <Form :model="searchForm" inline class="search-form">
160
- <FormItem label="关键词">
161
- <Input
162
- v-model="searchForm.keyword"
163
- placeholder="用户名/昵称"
164
- clearable
165
- @keyup.enter="handleSearch"
166
- />
167
- </FormItem>
168
- <FormItem label="状态">
169
- <Select
170
- v-model="searchForm.status"
171
- :options="StatusOptions"
172
- placeholder="请选择"
173
- clearable
174
- />
175
- </FormItem>
176
- <FormItem>
177
- <Space>
178
- <Button type="primary" @click="handleSearch">
179
- <template #icon>
180
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
181
- <circle cx="11" cy="11" r="8"/>
182
- <path d="M21 21l-4.35-4.35"/>
183
- </svg>
184
- </template>
185
- 搜索
186
- </Button>
187
- <Button @click="handleReset">
188
- <template #icon>
189
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
190
- <path d="M3 12a9 9 0 109-9 9.75 9.75 0 00-6.74 2.74L3 8"/>
191
- <path d="M3 3v5h5"/>
192
- </svg>
193
- </template>
194
- 重置
195
- </Button>
196
- </Space>
197
- </FormItem>
198
- </Form>
199
- </div>
200
-
201
- <!-- 表格 -->
202
- <div class="table-section">
203
- <!-- 工具栏 -->
204
- <div class="table-toolbar">
205
- <div class="toolbar-left">
206
- <Button type="primary" @click="handleAdd">
207
- <template #icon>
208
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
209
- <line x1="12" y1="5" x2="12" y2="19"/>
210
- <line x1="5" y1="12" x2="19" y2="12"/>
211
- </svg>
212
- </template>
213
- 新增用户
214
- </Button>
215
- </div>
216
- <div class="toolbar-right">
217
- <span class="table-count">共 {{ total }} 条数据</span>
218
- </div>
219
- </div>
220
-
221
- <!-- 表格 -->
222
- <div class="table-wrapper">
223
- <table class="data-table">
224
- <thead>
225
- <tr>
226
- <th class="col-id">ID</th>
227
- <th class="col-user">用户信息</th>
228
- <th class="col-contact">联系方式</th>
229
- <th class="col-roles">角色</th>
230
- <th class="col-status">状态</th>
231
- <th class="col-time">创建时间</th>
232
- <th class="col-actions">操作</th>
233
- </tr>
234
- </thead>
235
- <tbody>
236
- <tr v-if="loading">
237
- <td colspan="7" class="loading-cell">
238
- <div class="loading-content">
239
- <div class="loading-spinner"></div>
240
- <span>加载中...</span>
241
- </div>
242
- </td>
243
- </tr>
244
- <tr v-else-if="userList.length === 0">
245
- <td colspan="7" class="empty-cell">
246
- <div class="empty-content">
247
- <svg viewBox="0 0 64 41" fill="none">
248
- <g transform="translate(0 1)">
249
- <ellipse fill="#f5f5f5" cx="32" cy="33" rx="32" ry="7"/>
250
- <g stroke="var(--color-text-placeholder)" stroke-width="2">
251
- <path d="M55 12.76L44.854 1.258C44.367.474 43.656 0 42.907 0H21.093c-.749 0-1.46.474-1.947 1.257L9 12.761V22h46v-9.24z"/>
252
- <path d="M41.613 15.931c0-1.605.994-2.93 2.227-2.931H55v18.137C55 33.26 53.68 35 52.05 35H11.95C10.32 35 9 33.259 9 31.137V13h11.16c1.233 0 2.227 1.323 2.227 2.928v.022c0 1.605 1.005 2.901 2.237 2.901h14.752c1.232 0 2.237-1.308 2.237-2.913v-.007z" fill="var(--color-fill)"/>
253
- </g>
254
- </g>
255
- </svg>
256
- <span>暂无数据</span>
257
- </div>
258
- </td>
259
- </tr>
260
- <tr v-else v-for="row in userList" :key="row.id" class="data-row">
261
- <td class="col-id">
262
- <span class="id-badge">{{ row.id }}</span>
263
- </td>
264
- <td class="col-user">
265
- <div class="user-info">
266
- <div class="user-avatar">{{ row.nickname.charAt(0) }}</div>
267
- <div class="user-detail">
268
- <span class="user-nickname">{{ row.nickname }}</span>
269
- <span class="user-username">@{{ row.username }}</span>
270
- </div>
271
- </div>
272
- </td>
273
- <td class="col-contact">
274
- <div class="contact-info">
275
- <span class="contact-email">{{ row.email }}</span>
276
- <span class="contact-phone">{{ row.phone }}</span>
277
- </div>
278
- </td>
279
- <td class="col-roles">
280
- <div class="role-tags">
281
- <Tag v-for="role in row.roles" :key="role" size="small" :type="role === 'admin' ? 'primary' : 'info'">
282
- {{ role }}
283
- </Tag>
284
- </div>
285
- </td>
286
- <td class="col-status">
287
- <div class="status-switch">
288
- <Switch
289
- :model-value="row.status === Status.ENABLED"
290
- @update:model-value="row.status = $event ? Status.ENABLED : Status.DISABLED; handleStatusChange(row)"
291
- />
292
- <span class="status-text" :class="{ enabled: row.status === Status.ENABLED }">
293
- {{ row.status === Status.ENABLED ? '已启用' : '已禁用' }}
294
- </span>
295
- </div>
296
- </td>
297
- <td class="col-time">
298
- <span class="time-text">{{ row.createTime }}</span>
299
- </td>
300
- <td class="col-actions">
301
- <Space class="action-buttons">
302
- <Button type="primary" link size="small" @click="handleEdit(row)">
303
- <template #icon>
304
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
305
- <path d="M11 4H4a2 2 0 00-2 2v14a2 2 0 002 2h14a2 2 0 002-2v-7"/>
306
- <path d="M18.5 2.5a2.121 2.121 0 013 3L12 15l-4 1 1-4 9.5-9.5z"/>
307
- </svg>
308
- </template>
309
- 编辑
310
- </Button>
311
- <Popconfirm title="确定删除该用户吗?" @confirm="handleDelete(row.id)">
312
- <Button type="danger" link size="small">
313
- <template #icon>
314
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
315
- <polyline points="3,6 5,6 21,6"/>
316
- <path d="M19 6v14a2 2 0 01-2 2H7a2 2 0 01-2-2V6m3 0V4a2 2 0 012-2h4a2 2 0 012 2v2"/>
317
- <line x1="10" y1="11" x2="10" y2="17"/>
318
- <line x1="14" y1="11" x2="14" y2="17"/>
319
- </svg>
320
- </template>
321
- 删除
322
- </Button>
323
- </Popconfirm>
324
- </Space>
325
- </td>
326
- </tr>
327
- </tbody>
328
- </table>
329
- </div>
330
-
331
- <!-- 分页 -->
332
- <div class="pagination-wrapper">
333
- <Pagination
334
- v-model:current-page="currentPage"
335
- v-model:page-size="pageSize"
336
- :total="total"
337
- :page-sizes="[10, 20, 50, 100]"
338
- layout="total, sizes, prev, pager, next"
339
- @current-change="getUserList"
340
- @size-change="getUserList"
341
- />
342
- </div>
343
- </div>
344
-
345
- <!-- 编辑弹窗 -->
346
- <Modal v-model="modalVisible" :title="modalTitle" width="520px" class="user-modal">
347
- <Form ref="formRef" :model="formData" :rules="rules" label-width="80px" class="user-form">
348
- <FormItem label="用户名" prop="username">
349
- <Input v-model="formData.username" placeholder="请输入用户名" />
350
- </FormItem>
351
- <FormItem label="昵称" prop="nickname">
352
- <Input v-model="formData.nickname" placeholder="请输入昵称" />
353
- </FormItem>
354
- <FormItem label="邮箱" prop="email">
355
- <Input v-model="formData.email" placeholder="请输入邮箱" />
356
- </FormItem>
357
- <FormItem label="手机号" prop="phone">
358
- <Input v-model="formData.phone" placeholder="请输入手机号" />
359
- </FormItem>
360
- <FormItem label="状态">
361
- <div class="status-field">
362
- <Switch v-model="formData.status" :active-value="Status.ENABLED" :inactive-value="Status.DISABLED" />
363
- <span class="status-label">{{ formData.status === Status.ENABLED ? '启用' : '禁用' }}</span>
364
- </div>
365
- </FormItem>
366
- </Form>
367
- <template #footer>
368
- <Space>
369
- <Button @click="modalVisible = false">取消</Button>
370
- <Button type="primary" @click="handleSubmit">确定</Button>
371
- </Space>
372
- </template>
373
- </Modal>
374
- </div>
375
- </template>
376
-
377
- <style lang="scss" scoped>
378
- .user-page {
379
- padding: 24px;
380
- background: var(--bg-color-page);
381
- min-height: 100%;
382
- }
383
-
384
- // 搜索区域
385
- .search-section {
386
- background: var(--bg-color);
387
- border-radius: var(--border-radius-large);
388
- padding: 20px 24px;
389
- margin-bottom: 16px;
390
- box-shadow: var(--box-shadow-card);
391
-
392
- .search-form {
393
- display: flex;
394
- flex-wrap: wrap;
395
- gap: 16px;
396
-
397
- :deep(.x-form-item) {
398
- margin-bottom: 0;
399
- }
400
-
401
- :deep(.x-input),
402
- :deep(.x-select) {
403
- width: 200px;
404
- }
405
- }
406
- }
407
-
408
- // 表格区域
409
- .table-section {
410
- background: var(--bg-color);
411
- border-radius: var(--border-radius-large);
412
- box-shadow: var(--box-shadow-card);
413
- overflow: hidden;
414
- }
415
-
416
- .table-toolbar {
417
- display: flex;
418
- justify-content: space-between;
419
- align-items: center;
420
- padding: 16px 24px;
421
- border-bottom: 1px solid var(--color-border-lighter);
422
-
423
- .toolbar-left {
424
- display: flex;
425
- gap: 12px;
426
- }
427
-
428
- .table-count {
429
- font-size: 14px;
430
- color: var(--color-text-secondary);
431
- }
432
- }
433
-
434
- .table-wrapper {
435
- overflow-x: auto;
436
- }
437
-
438
- // 表格样式
439
- .data-table {
440
- width: 100%;
441
- border-collapse: collapse;
442
-
443
- th, td {
444
- padding: 14px 16px;
445
- text-align: left;
446
- border-bottom: 1px solid var(--color-border-lighter);
447
- }
448
-
449
- th {
450
- font-size: 14px;
451
- font-weight: 500;
452
- color: var(--color-text-secondary);
453
- background: var(--color-fill-light);
454
- white-space: nowrap;
455
- }
456
-
457
- .data-row {
458
- transition: background-color 0.2s;
459
-
460
- &:hover {
461
- background: var(--color-primary-light-6);
462
- }
463
- }
464
-
465
- td {
466
- vertical-align: middle;
467
- }
468
-
469
- .loading-cell,
470
- .empty-cell {
471
- padding: 60px 20px;
472
- }
473
-
474
- .loading-content,
475
- .empty-content {
476
- display: flex;
477
- flex-direction: column;
478
- align-items: center;
479
- gap: 16px;
480
- color: var(--color-text-placeholder);
481
-
482
- svg {
483
- width: 64px;
484
- height: 41px;
485
- }
486
- }
487
-
488
- .loading-spinner {
489
- width: 32px;
490
- height: 32px;
491
- border: 3px solid var(--color-border-lighter);
492
- border-top-color: var(--color-primary);
493
- border-radius: 50%;
494
- animation: spin 0.8s linear infinite;
495
- }
496
- }
497
-
498
- @keyframes spin {
499
- to { transform: rotate(360deg); }
500
- }
501
-
502
- // 列样式
503
- .col-id {
504
- width: 80px;
505
- }
506
-
507
- .id-badge {
508
- display: inline-block;
509
- min-width: 32px;
510
- padding: 4px 10px;
511
- background: var(--color-fill);
512
- border-radius: 12px;
513
- font-size: 12px;
514
- font-weight: 500;
515
- color: var(--color-text-secondary);
516
- text-align: center;
517
- }
518
-
519
- .col-user {
520
- min-width: 180px;
521
- }
522
-
523
- .user-info {
524
- display: flex;
525
- align-items: center;
526
- gap: 12px;
527
- }
528
-
529
- .user-avatar {
530
- width: 40px;
531
- height: 40px;
532
- display: flex;
533
- align-items: center;
534
- justify-content: center;
535
- background: linear-gradient(135deg, var(--color-primary) 0%, var(--color-primary-light-1) 100%);
536
- border-radius: 50%;
537
- font-size: 16px;
538
- font-weight: 500;
539
- color: #fff;
540
- flex-shrink: 0;
541
- }
542
-
543
- .user-detail {
544
- display: flex;
545
- flex-direction: column;
546
- gap: 2px;
547
- }
548
-
549
- .user-nickname {
550
- font-size: 14px;
551
- font-weight: 500;
552
- color: var(--color-text-primary);
553
- }
554
-
555
- .user-username {
556
- font-size: 12px;
557
- color: var(--color-text-placeholder);
558
- }
559
-
560
- .col-contact {
561
- min-width: 180px;
562
- }
563
-
564
- .contact-info {
565
- display: flex;
566
- flex-direction: column;
567
- gap: 4px;
568
- }
569
-
570
- .contact-email,
571
- .contact-phone {
572
- font-size: 13px;
573
- color: var(--color-text-regular);
574
- }
575
-
576
- .contact-phone {
577
- color: var(--color-text-secondary);
578
- }
579
-
580
- .col-roles {
581
- min-width: 100px;
582
- }
583
-
584
- .role-tags {
585
- display: flex;
586
- gap: 6px;
587
- }
588
-
589
- .col-status {
590
- min-width: 120px;
591
- }
592
-
593
- .status-switch {
594
- display: flex;
595
- align-items: center;
596
- gap: 8px;
597
- }
598
-
599
- .status-text {
600
- font-size: 13px;
601
- color: var(--color-text-secondary);
602
-
603
- &.enabled {
604
- color: var(--color-success);
605
- }
606
- }
607
-
608
- .col-time {
609
- min-width: 160px;
610
- }
611
-
612
- .time-text {
613
- font-size: 13px;
614
- color: var(--color-text-secondary);
615
- }
616
-
617
- .col-actions {
618
- min-width: 140px;
619
- }
620
-
621
- .action-buttons {
622
- :deep(.x-button) {
623
- padding: 4px 8px;
624
-
625
- svg {
626
- width: 14px;
627
- height: 14px;
628
- margin-right: 4px;
629
- }
630
- }
631
- }
632
-
633
- // 分页
634
- .pagination-wrapper {
635
- display: flex;
636
- justify-content: flex-end;
637
- padding: 16px 24px;
638
- border-top: 1px solid var(--color-border-lighter);
639
- }
640
-
641
- // 弹窗表单
642
- .user-form {
643
- padding: 16px 0;
644
-
645
- .status-field {
646
- display: flex;
647
- align-items: center;
648
- gap: 12px;
649
- }
650
-
651
- .status-label {
652
- font-size: 14px;
653
- color: var(--color-text-secondary);
654
- }
655
- }
1
+ <script setup lang="ts">
2
+ import { ref, reactive, computed, onMounted } from 'vue'
3
+ import { Form, FormItem, Input, Select, Switch } from '@xto/form'
4
+ import { Tag, Pagination } from '@xto/data'
5
+ import { Modal, Message, Popconfirm } from '@xto/feedback'
6
+ import { Space, Button } from '@xto/base'
7
+ import { Status, StatusOptions } from '@/enums'
8
+
9
+ // 用户数据类型
10
+ interface User {
11
+ id: number
12
+ username: string
13
+ nickname: string
14
+ email: string
15
+ phone: string
16
+ status: Status
17
+ roles: string[]
18
+ createTime: string
19
+ }
20
+
21
+ // Mock 数据
22
+ const mockUsers: User[] = [
23
+ { id: 1, username: 'admin', nickname: '管理员', email: 'admin@example.com', phone: '13800138001', status: Status.ENABLED, roles: ['admin'], createTime: '2024-01-01 10:00:00' },
24
+ { id: 2, username: 'zhangsan', nickname: '张三', email: 'zhangsan@example.com', phone: '13800138002', status: Status.ENABLED, roles: ['editor'], createTime: '2024-01-02 10:00:00' },
25
+ { id: 3, username: 'lisi', nickname: '李四', email: 'lisi@example.com', phone: '13800138003', status: Status.DISABLED, roles: ['viewer'], createTime: '2024-01-03 10:00:00' },
26
+ { id: 4, username: 'wangwu', nickname: '王五', email: 'wangwu@example.com', phone: '13800138004', status: Status.ENABLED, roles: ['editor'], createTime: '2024-01-04 10:00:00' },
27
+ { id: 5, username: 'zhaoliu', nickname: '赵六', email: 'zhaoliu@example.com', phone: '13800138005', status: Status.ENABLED, roles: ['viewer'], createTime: '2024-01-05 10:00:00' }
28
+ ]
29
+
30
+ const loading = ref(false)
31
+ const userList = ref<User[]>([])
32
+ const total = ref(0)
33
+ const currentPage = ref(1)
34
+ const pageSize = ref(10)
35
+
36
+ // 搜索条件
37
+ const searchForm = reactive({
38
+ keyword: '',
39
+ status: undefined as Status | undefined
40
+ })
41
+
42
+ // 弹窗
43
+ const modalVisible = ref(false)
44
+ const modalTitle = computed(() => formData.id ? '编辑用户' : '新增用户')
45
+ const formData = reactive({
46
+ id: 0,
47
+ username: '',
48
+ nickname: '',
49
+ email: '',
50
+ phone: '',
51
+ status: Status.ENABLED,
52
+ roles: [] as string[]
53
+ })
54
+
55
+ const rules: Record<string, any[]> = {
56
+ username: [
57
+ { required: true, message: '请输入用户名', trigger: 'blur' }
58
+ ],
59
+ nickname: [
60
+ { required: true, message: '请输入昵称', trigger: 'blur' }
61
+ ],
62
+ email: [
63
+ { required: true, message: '请输入邮箱', trigger: 'blur' },
64
+ { type: 'email', message: '请输入正确的邮箱格式', trigger: 'blur' }
65
+ ]
66
+ }
67
+
68
+ const formRef = ref()
69
+
70
+ // 获取用户列表
71
+ const getUserList = () => {
72
+ loading.value = true
73
+ setTimeout(() => {
74
+ let list = [...mockUsers]
75
+
76
+ // 搜索过滤
77
+ if (searchForm.keyword) {
78
+ list = list.filter(user =>
79
+ user.username.includes(searchForm.keyword) ||
80
+ user.nickname.includes(searchForm.keyword)
81
+ )
82
+ }
83
+ if (searchForm.status !== undefined) {
84
+ list = list.filter(user => user.status === searchForm.status)
85
+ }
86
+
87
+ total.value = list.length
88
+ userList.value = list.slice((currentPage.value - 1) * pageSize.value, currentPage.value * pageSize.value)
89
+ loading.value = false
90
+ }, 300)
91
+ }
92
+
93
+ // 搜索
94
+ const handleSearch = () => {
95
+ currentPage.value = 1
96
+ getUserList()
97
+ }
98
+
99
+ // 重置
100
+ const handleReset = () => {
101
+ searchForm.keyword = ''
102
+ searchForm.status = undefined
103
+ currentPage.value = 1
104
+ getUserList()
105
+ }
106
+
107
+ // 新增
108
+ const handleAdd = () => {
109
+ Object.assign(formData, {
110
+ id: 0,
111
+ username: '',
112
+ nickname: '',
113
+ email: '',
114
+ phone: '',
115
+ status: Status.ENABLED,
116
+ roles: []
117
+ })
118
+ modalVisible.value = true
119
+ }
120
+
121
+ // 编辑
122
+ const handleEdit = (row: User) => {
123
+ Object.assign(formData, row)
124
+ modalVisible.value = true
125
+ }
126
+
127
+ // 删除
128
+ const handleDelete = (_id: number) => {
129
+ Message.success('删除成功')
130
+ getUserList()
131
+ }
132
+
133
+ // 提交
134
+ const handleSubmit = async () => {
135
+ try {
136
+ await formRef.value?.validate()
137
+ Message.success(formData.id ? '编辑成功' : '新增成功')
138
+ modalVisible.value = false
139
+ getUserList()
140
+ } catch (error) {
141
+ console.error(error)
142
+ }
143
+ }
144
+
145
+ // 状态切换
146
+ const handleStatusChange = (row: User) => {
147
+ Message.success(`已${row.status === Status.ENABLED ? '启用' : '禁用'}用户 ${row.username}`)
148
+ }
149
+
150
+ onMounted(() => {
151
+ getUserList()
152
+ })
153
+ </script>
154
+
155
+ <template>
156
+ <div class="user-page">
157
+ <!-- 搜索栏 -->
158
+ <div class="search-section">
159
+ <Form :model="searchForm" inline class="search-form">
160
+ <FormItem label="关键词">
161
+ <Input
162
+ v-model="searchForm.keyword"
163
+ placeholder="用户名/昵称"
164
+ clearable
165
+ @keyup.enter="handleSearch"
166
+ />
167
+ </FormItem>
168
+ <FormItem label="状态">
169
+ <Select
170
+ v-model="searchForm.status"
171
+ :options="StatusOptions"
172
+ placeholder="请选择"
173
+ clearable
174
+ />
175
+ </FormItem>
176
+ <FormItem>
177
+ <Space>
178
+ <Button type="primary" @click="handleSearch">
179
+ <template #icon>
180
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
181
+ <circle cx="11" cy="11" r="8"/>
182
+ <path d="M21 21l-4.35-4.35"/>
183
+ </svg>
184
+ </template>
185
+ 搜索
186
+ </Button>
187
+ <Button @click="handleReset">
188
+ <template #icon>
189
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
190
+ <path d="M3 12a9 9 0 109-9 9.75 9.75 0 00-6.74 2.74L3 8"/>
191
+ <path d="M3 3v5h5"/>
192
+ </svg>
193
+ </template>
194
+ 重置
195
+ </Button>
196
+ </Space>
197
+ </FormItem>
198
+ </Form>
199
+ </div>
200
+
201
+ <!-- 表格 -->
202
+ <div class="table-section">
203
+ <!-- 工具栏 -->
204
+ <div class="table-toolbar">
205
+ <div class="toolbar-left">
206
+ <Button type="primary" @click="handleAdd">
207
+ <template #icon>
208
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
209
+ <line x1="12" y1="5" x2="12" y2="19"/>
210
+ <line x1="5" y1="12" x2="19" y2="12"/>
211
+ </svg>
212
+ </template>
213
+ 新增用户
214
+ </Button>
215
+ </div>
216
+ <div class="toolbar-right">
217
+ <span class="table-count">共 {{ total }} 条数据</span>
218
+ </div>
219
+ </div>
220
+
221
+ <!-- 表格 -->
222
+ <div class="table-wrapper">
223
+ <table class="data-table">
224
+ <thead>
225
+ <tr>
226
+ <th class="col-id">ID</th>
227
+ <th class="col-user">用户信息</th>
228
+ <th class="col-contact">联系方式</th>
229
+ <th class="col-roles">角色</th>
230
+ <th class="col-status">状态</th>
231
+ <th class="col-time">创建时间</th>
232
+ <th class="col-actions">操作</th>
233
+ </tr>
234
+ </thead>
235
+ <tbody>
236
+ <tr v-if="loading">
237
+ <td colspan="7" class="loading-cell">
238
+ <div class="loading-content">
239
+ <div class="loading-spinner"></div>
240
+ <span>加载中...</span>
241
+ </div>
242
+ </td>
243
+ </tr>
244
+ <tr v-else-if="userList.length === 0">
245
+ <td colspan="7" class="empty-cell">
246
+ <div class="empty-content">
247
+ <svg viewBox="0 0 64 41" fill="none">
248
+ <g transform="translate(0 1)">
249
+ <ellipse fill="#f5f5f5" cx="32" cy="33" rx="32" ry="7"/>
250
+ <g stroke="var(--color-text-placeholder)" stroke-width="2">
251
+ <path d="M55 12.76L44.854 1.258C44.367.474 43.656 0 42.907 0H21.093c-.749 0-1.46.474-1.947 1.257L9 12.761V22h46v-9.24z"/>
252
+ <path d="M41.613 15.931c0-1.605.994-2.93 2.227-2.931H55v18.137C55 33.26 53.68 35 52.05 35H11.95C10.32 35 9 33.259 9 31.137V13h11.16c1.233 0 2.227 1.323 2.227 2.928v.022c0 1.605 1.005 2.901 2.237 2.901h14.752c1.232 0 2.237-1.308 2.237-2.913v-.007z" fill="var(--color-fill)"/>
253
+ </g>
254
+ </g>
255
+ </svg>
256
+ <span>暂无数据</span>
257
+ </div>
258
+ </td>
259
+ </tr>
260
+ <tr v-else v-for="row in userList" :key="row.id" class="data-row">
261
+ <td class="col-id">
262
+ <span class="id-badge">{{ row.id }}</span>
263
+ </td>
264
+ <td class="col-user">
265
+ <div class="user-info">
266
+ <div class="user-avatar">{{ row.nickname.charAt(0) }}</div>
267
+ <div class="user-detail">
268
+ <span class="user-nickname">{{ row.nickname }}</span>
269
+ <span class="user-username">@{{ row.username }}</span>
270
+ </div>
271
+ </div>
272
+ </td>
273
+ <td class="col-contact">
274
+ <div class="contact-info">
275
+ <span class="contact-email">{{ row.email }}</span>
276
+ <span class="contact-phone">{{ row.phone }}</span>
277
+ </div>
278
+ </td>
279
+ <td class="col-roles">
280
+ <div class="role-tags">
281
+ <Tag v-for="role in row.roles" :key="role" size="small" :type="role === 'admin' ? 'primary' : 'info'">
282
+ {{ role }}
283
+ </Tag>
284
+ </div>
285
+ </td>
286
+ <td class="col-status">
287
+ <div class="status-switch">
288
+ <Switch
289
+ :model-value="row.status === Status.ENABLED"
290
+ @update:model-value="row.status = $event ? Status.ENABLED : Status.DISABLED; handleStatusChange(row)"
291
+ />
292
+ <span class="status-text" :class="{ enabled: row.status === Status.ENABLED }">
293
+ {{ row.status === Status.ENABLED ? '已启用' : '已禁用' }}
294
+ </span>
295
+ </div>
296
+ </td>
297
+ <td class="col-time">
298
+ <span class="time-text">{{ row.createTime }}</span>
299
+ </td>
300
+ <td class="col-actions">
301
+ <Space class="action-buttons">
302
+ <Button type="primary" link size="small" @click="handleEdit(row)">
303
+ <template #icon>
304
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
305
+ <path d="M11 4H4a2 2 0 00-2 2v14a2 2 0 002 2h14a2 2 0 002-2v-7"/>
306
+ <path d="M18.5 2.5a2.121 2.121 0 013 3L12 15l-4 1 1-4 9.5-9.5z"/>
307
+ </svg>
308
+ </template>
309
+ 编辑
310
+ </Button>
311
+ <Popconfirm title="确定删除该用户吗?" @confirm="handleDelete(row.id)">
312
+ <Button type="danger" link size="small">
313
+ <template #icon>
314
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
315
+ <polyline points="3,6 5,6 21,6"/>
316
+ <path d="M19 6v14a2 2 0 01-2 2H7a2 2 0 01-2-2V6m3 0V4a2 2 0 012-2h4a2 2 0 012 2v2"/>
317
+ <line x1="10" y1="11" x2="10" y2="17"/>
318
+ <line x1="14" y1="11" x2="14" y2="17"/>
319
+ </svg>
320
+ </template>
321
+ 删除
322
+ </Button>
323
+ </Popconfirm>
324
+ </Space>
325
+ </td>
326
+ </tr>
327
+ </tbody>
328
+ </table>
329
+ </div>
330
+
331
+ <!-- 分页 -->
332
+ <div class="pagination-wrapper">
333
+ <Pagination
334
+ v-model:current-page="currentPage"
335
+ v-model:page-size="pageSize"
336
+ :total="total"
337
+ :page-sizes="[10, 20, 50, 100]"
338
+ layout="total, sizes, prev, pager, next"
339
+ @current-change="getUserList"
340
+ @size-change="getUserList"
341
+ />
342
+ </div>
343
+ </div>
344
+
345
+ <!-- 编辑弹窗 -->
346
+ <Modal v-model="modalVisible" :title="modalTitle" width="520px" class="user-modal">
347
+ <Form ref="formRef" :model="formData" :rules="rules" label-width="80px" class="user-form">
348
+ <FormItem label="用户名" prop="username">
349
+ <Input v-model="formData.username" placeholder="请输入用户名" />
350
+ </FormItem>
351
+ <FormItem label="昵称" prop="nickname">
352
+ <Input v-model="formData.nickname" placeholder="请输入昵称" />
353
+ </FormItem>
354
+ <FormItem label="邮箱" prop="email">
355
+ <Input v-model="formData.email" placeholder="请输入邮箱" />
356
+ </FormItem>
357
+ <FormItem label="手机号" prop="phone">
358
+ <Input v-model="formData.phone" placeholder="请输入手机号" />
359
+ </FormItem>
360
+ <FormItem label="状态">
361
+ <div class="status-field">
362
+ <Switch v-model="formData.status" :active-value="Status.ENABLED" :inactive-value="Status.DISABLED" />
363
+ <span class="status-label">{{ formData.status === Status.ENABLED ? '启用' : '禁用' }}</span>
364
+ </div>
365
+ </FormItem>
366
+ </Form>
367
+ <template #footer>
368
+ <Space>
369
+ <Button @click="modalVisible = false">取消</Button>
370
+ <Button type="primary" @click="handleSubmit">确定</Button>
371
+ </Space>
372
+ </template>
373
+ </Modal>
374
+ </div>
375
+ </template>
376
+
377
+ <style lang="scss" scoped>
378
+ .user-page {
379
+ padding: 24px;
380
+ background: var(--bg-color-page);
381
+ min-height: 100%;
382
+ }
383
+
384
+ // 搜索区域
385
+ .search-section {
386
+ background: var(--bg-color);
387
+ border-radius: var(--border-radius-large);
388
+ padding: 20px 24px;
389
+ margin-bottom: 16px;
390
+ box-shadow: var(--box-shadow-card);
391
+
392
+ .search-form {
393
+ display: flex;
394
+ flex-wrap: wrap;
395
+ gap: 16px;
396
+
397
+ :deep(.x-form-item) {
398
+ margin-bottom: 0;
399
+ }
400
+
401
+ :deep(.x-input),
402
+ :deep(.x-select) {
403
+ width: 200px;
404
+ }
405
+ }
406
+ }
407
+
408
+ // 表格区域
409
+ .table-section {
410
+ background: var(--bg-color);
411
+ border-radius: var(--border-radius-large);
412
+ box-shadow: var(--box-shadow-card);
413
+ overflow: hidden;
414
+ }
415
+
416
+ .table-toolbar {
417
+ display: flex;
418
+ justify-content: space-between;
419
+ align-items: center;
420
+ padding: 16px 24px;
421
+ border-bottom: 1px solid var(--color-border-lighter);
422
+
423
+ .toolbar-left {
424
+ display: flex;
425
+ gap: 12px;
426
+ }
427
+
428
+ .table-count {
429
+ font-size: 14px;
430
+ color: var(--color-text-secondary);
431
+ }
432
+ }
433
+
434
+ .table-wrapper {
435
+ overflow-x: auto;
436
+ }
437
+
438
+ // 表格样式
439
+ .data-table {
440
+ width: 100%;
441
+ border-collapse: collapse;
442
+
443
+ th, td {
444
+ padding: 14px 16px;
445
+ text-align: left;
446
+ border-bottom: 1px solid var(--color-border-lighter);
447
+ }
448
+
449
+ th {
450
+ font-size: 14px;
451
+ font-weight: 500;
452
+ color: var(--color-text-secondary);
453
+ background: var(--color-fill-light);
454
+ white-space: nowrap;
455
+ }
456
+
457
+ .data-row {
458
+ transition: background-color 0.2s;
459
+
460
+ &:hover {
461
+ background: var(--color-primary-light-6);
462
+ }
463
+ }
464
+
465
+ td {
466
+ vertical-align: middle;
467
+ }
468
+
469
+ .loading-cell,
470
+ .empty-cell {
471
+ padding: 60px 20px;
472
+ }
473
+
474
+ .loading-content,
475
+ .empty-content {
476
+ display: flex;
477
+ flex-direction: column;
478
+ align-items: center;
479
+ gap: 16px;
480
+ color: var(--color-text-placeholder);
481
+
482
+ svg {
483
+ width: 64px;
484
+ height: 41px;
485
+ }
486
+ }
487
+
488
+ .loading-spinner {
489
+ width: 32px;
490
+ height: 32px;
491
+ border: 3px solid var(--color-border-lighter);
492
+ border-top-color: var(--color-primary);
493
+ border-radius: 50%;
494
+ animation: spin 0.8s linear infinite;
495
+ }
496
+ }
497
+
498
+ @keyframes spin {
499
+ to { transform: rotate(360deg); }
500
+ }
501
+
502
+ // 列样式
503
+ .col-id {
504
+ width: 80px;
505
+ }
506
+
507
+ .id-badge {
508
+ display: inline-block;
509
+ min-width: 32px;
510
+ padding: 4px 10px;
511
+ background: var(--color-fill);
512
+ border-radius: 12px;
513
+ font-size: 12px;
514
+ font-weight: 500;
515
+ color: var(--color-text-secondary);
516
+ text-align: center;
517
+ }
518
+
519
+ .col-user {
520
+ min-width: 180px;
521
+ }
522
+
523
+ .user-info {
524
+ display: flex;
525
+ align-items: center;
526
+ gap: 12px;
527
+ }
528
+
529
+ .user-avatar {
530
+ width: 40px;
531
+ height: 40px;
532
+ display: flex;
533
+ align-items: center;
534
+ justify-content: center;
535
+ background: linear-gradient(135deg, var(--color-primary) 0%, var(--color-primary-light-1) 100%);
536
+ border-radius: 50%;
537
+ font-size: 16px;
538
+ font-weight: 500;
539
+ color: #fff;
540
+ flex-shrink: 0;
541
+ }
542
+
543
+ .user-detail {
544
+ display: flex;
545
+ flex-direction: column;
546
+ gap: 2px;
547
+ }
548
+
549
+ .user-nickname {
550
+ font-size: 14px;
551
+ font-weight: 500;
552
+ color: var(--color-text-primary);
553
+ }
554
+
555
+ .user-username {
556
+ font-size: 12px;
557
+ color: var(--color-text-placeholder);
558
+ }
559
+
560
+ .col-contact {
561
+ min-width: 180px;
562
+ }
563
+
564
+ .contact-info {
565
+ display: flex;
566
+ flex-direction: column;
567
+ gap: 4px;
568
+ }
569
+
570
+ .contact-email,
571
+ .contact-phone {
572
+ font-size: 13px;
573
+ color: var(--color-text-regular);
574
+ }
575
+
576
+ .contact-phone {
577
+ color: var(--color-text-secondary);
578
+ }
579
+
580
+ .col-roles {
581
+ min-width: 100px;
582
+ }
583
+
584
+ .role-tags {
585
+ display: flex;
586
+ gap: 6px;
587
+ }
588
+
589
+ .col-status {
590
+ min-width: 120px;
591
+ }
592
+
593
+ .status-switch {
594
+ display: flex;
595
+ align-items: center;
596
+ gap: 8px;
597
+ }
598
+
599
+ .status-text {
600
+ font-size: 13px;
601
+ color: var(--color-text-secondary);
602
+
603
+ &.enabled {
604
+ color: var(--color-success);
605
+ }
606
+ }
607
+
608
+ .col-time {
609
+ min-width: 160px;
610
+ }
611
+
612
+ .time-text {
613
+ font-size: 13px;
614
+ color: var(--color-text-secondary);
615
+ }
616
+
617
+ .col-actions {
618
+ min-width: 140px;
619
+ }
620
+
621
+ .action-buttons {
622
+ :deep(.x-button) {
623
+ padding: 4px 8px;
624
+
625
+ svg {
626
+ width: 14px;
627
+ height: 14px;
628
+ margin-right: 4px;
629
+ }
630
+ }
631
+ }
632
+
633
+ // 分页
634
+ .pagination-wrapper {
635
+ display: flex;
636
+ justify-content: flex-end;
637
+ padding: 16px 24px;
638
+ border-top: 1px solid var(--color-border-lighter);
639
+ }
640
+
641
+ // 弹窗表单
642
+ .user-form {
643
+ padding: 16px 0;
644
+
645
+ .status-field {
646
+ display: flex;
647
+ align-items: center;
648
+ gap: 12px;
649
+ }
650
+
651
+ .status-label {
652
+ font-size: 14px;
653
+ color: var(--color-text-secondary);
654
+ }
655
+ }
656
656
  </style>