xto-fronted 0.1.1 → 0.1.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 (67) hide show
  1. package/.env.development +3 -4
  2. package/.env.production +3 -4
  3. package/bin/cli.js +104 -0
  4. package/dist/{403-MQkNUulz.js → 403-DM5wfQkM.js} +6 -6
  5. package/dist/{404-BOFYLq4X.js → 404-BurAu5LC.js} +7 -7
  6. package/dist/api/auth.d.ts +9 -8
  7. package/dist/api/menu.d.ts +3 -0
  8. package/dist/api/user.d.ts +2 -12
  9. package/dist/composables/index.d.ts +8 -0
  10. package/dist/composables/useApp.d.ts +64 -0
  11. package/dist/composables/useAuth.d.ts +19 -4
  12. package/dist/composables/useMenu.d.ts +34 -0
  13. package/dist/config/index.d.ts +11 -0
  14. package/dist/index-BNiEld34.js +15 -0
  15. package/dist/index-Be9RiEfo.js +98 -0
  16. package/dist/index-BqRv1bdN.js +1185 -0
  17. package/dist/index-CQLVXvNJ.js +15 -0
  18. package/dist/index-CyiE8n2V.js +15 -0
  19. package/dist/index-xauR1bOL.js +15 -0
  20. package/dist/index.d.ts +7 -4
  21. package/dist/index.es.js +50 -66
  22. package/dist/index.umd.js +1 -1
  23. package/dist/stores/auth.d.ts +60 -23
  24. package/dist/stores/menu.d.ts +40 -29
  25. package/dist/stores/user.d.ts +63 -84
  26. package/dist/style.css +1 -1
  27. package/dist/utils/auth.d.ts +15 -7
  28. package/dist/utils/permission.d.ts +1 -6
  29. package/dist/views/system/menu/index.vue.d.ts +1 -3
  30. package/dist/views/system/role/index.vue.d.ts +1 -3
  31. package/dist/views/system/user/index.vue.d.ts +1 -3
  32. package/package.json +27 -19
  33. package/src/api/auth.ts +34 -25
  34. package/src/api/menu.ts +13 -0
  35. package/src/api/user.ts +11 -45
  36. package/src/components/Layout/Header.vue +334 -389
  37. package/src/components/Layout/Sidebar.vue +212 -296
  38. package/src/components/Layout/Tabs.vue +19 -133
  39. package/src/composables/index.ts +9 -0
  40. package/src/composables/useApp.ts +170 -0
  41. package/src/composables/useAuth.ts +69 -44
  42. package/src/composables/useMenu.ts +141 -0
  43. package/src/config/index.ts +19 -0
  44. package/src/directives/permission.ts +40 -37
  45. package/src/index.ts +9 -4
  46. package/src/router/index.ts +70 -80
  47. package/src/stores/auth.ts +44 -31
  48. package/src/stores/menu.ts +157 -79
  49. package/src/stores/user.ts +40 -72
  50. package/src/types/api.d.ts +102 -83
  51. package/src/types/xto.d.ts +148 -148
  52. package/src/utils/auth.ts +85 -61
  53. package/src/utils/permission.ts +29 -41
  54. package/src/utils/request.ts +125 -125
  55. package/src/utils/storage.ts +10 -1
  56. package/src/views/dashboard/index.vue +31 -283
  57. package/src/views/login/index.vue +140 -247
  58. package/src/views/system/menu/index.vue +31 -380
  59. package/src/views/system/role/index.vue +31 -303
  60. package/src/views/system/user/index.vue +31 -326
  61. package/vite.config.ts +3 -3
  62. package/dist/index-BJxYdNPy.js +0 -475
  63. package/dist/index-BvnIIBR1.js +0 -142
  64. package/dist/index-CEvAq6KE.js +0 -372
  65. package/dist/index-DPkqej__.js +0 -345
  66. package/dist/index-pq9Z5K62.js +0 -184
  67. package/dist/index-vVfjShJR.js +0 -1183
@@ -1,304 +1,32 @@
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, Card, 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 Role {
11
- id: number
12
- name: string
13
- code: string
14
- description: string
15
- status: Status
16
- permissions: string[]
17
- createTime: string
18
- }
19
-
20
- // Mock 数据
21
- const mockRoles: Role[] = [
22
- { id: 1, name: '超级管理员', code: 'admin', description: '拥有所有权限', status: Status.ENABLED, permissions: ['*'], createTime: '2024-01-01 10:00:00' },
23
- { id: 2, name: '编辑', code: 'editor', description: '内容编辑权限', status: Status.ENABLED, permissions: ['user:read', 'user:write'], createTime: '2024-01-02 10:00:00' },
24
- { id: 3, name: '访客', code: 'viewer', description: '只读权限', status: Status.ENABLED, permissions: ['user:read'], createTime: '2024-01-03 10:00:00' }
25
- ]
26
-
27
- const loading = ref(false)
28
- const roleList = ref<Role[]>([])
29
- const total = ref(0)
30
- const currentPage = ref(1)
31
- const pageSize = ref(10)
32
-
33
- // 搜索条件
34
- const searchForm = reactive({
35
- keyword: '',
36
- status: undefined as Status | undefined
37
- })
38
-
39
- // 弹窗
40
- const modalVisible = ref(false)
41
- const modalTitle = computed(() => formData.id ? '编辑角色' : '新增角色')
42
- const formData = reactive({
43
- id: 0,
44
- name: '',
45
- code: '',
46
- description: '',
47
- status: Status.ENABLED,
48
- permissions: [] as string[]
49
- })
50
-
51
- const rules: Record<string, any[]> = {
52
- name: [
53
- { required: true, message: '请输入角色名称', trigger: 'blur' }
54
- ],
55
- code: [
56
- { required: true, message: '请输入角色编码', trigger: 'blur' }
57
- ]
58
- }
59
-
60
- const formRef = ref()
61
-
62
- // 获取角色列表
63
- const getRoleList = () => {
64
- loading.value = true
65
- setTimeout(() => {
66
- let list = [...mockRoles]
67
-
68
- if (searchForm.keyword) {
69
- list = list.filter(role =>
70
- role.name.includes(searchForm.keyword) ||
71
- role.code.includes(searchForm.keyword)
72
- )
73
- }
74
- if (searchForm.status !== undefined) {
75
- list = list.filter(role => role.status === searchForm.status)
76
- }
77
-
78
- total.value = list.length
79
- roleList.value = list.slice((currentPage.value - 1) * pageSize.value, currentPage.value * pageSize.value)
80
- loading.value = false
81
- }, 300)
82
- }
83
-
84
- // 搜索
85
- const handleSearch = () => {
86
- currentPage.value = 1
87
- getRoleList()
88
- }
89
-
90
- // 重置
91
- const handleReset = () => {
92
- searchForm.keyword = ''
93
- searchForm.status = undefined
94
- currentPage.value = 1
95
- getRoleList()
96
- }
97
-
98
- // 新增
99
- const handleAdd = () => {
100
- Object.assign(formData, {
101
- id: 0,
102
- name: '',
103
- code: '',
104
- description: '',
105
- status: Status.ENABLED,
106
- permissions: []
107
- })
108
- modalVisible.value = true
109
- }
110
-
111
- // 编辑
112
- const handleEdit = (row: Role) => {
113
- Object.assign(formData, row)
114
- modalVisible.value = true
115
- }
116
-
117
- // 删除
118
- const handleDelete = (_id: number) => {
119
- Message.success('删除成功')
120
- getRoleList()
121
- }
122
-
123
- // 提交
124
- const handleSubmit = async () => {
125
- try {
126
- await formRef.value?.validate()
127
- Message.success(formData.id ? '编辑成功' : '新增成功')
128
- modalVisible.value = false
129
- getRoleList()
130
- } catch (error) {
131
- console.error(error)
132
- }
133
- }
134
-
135
- onMounted(() => {
136
- getRoleList()
137
- })
138
- </script>
139
-
140
- <template>
141
- <div class="role-page">
142
- <!-- 搜索栏 -->
143
- <Card class="search-card">
144
- <Form :model="searchForm" inline>
145
- <FormItem label="关键词">
146
- <Input
147
- v-model="searchForm.keyword"
148
- placeholder="角色名称/编码"
149
- clearable
150
- @keyup.enter="handleSearch"
151
- />
152
- </FormItem>
153
- <FormItem label="状态">
154
- <Select
155
- v-model="searchForm.status"
156
- :options="StatusOptions"
157
- placeholder="请选择"
158
- clearable
159
- />
160
- </FormItem>
161
- <FormItem>
162
- <Space>
163
- <Button type="primary" @click="handleSearch">搜索</Button>
164
- <Button @click="handleReset">重置</Button>
165
- </Space>
166
- </FormItem>
167
- </Form>
168
- </Card>
169
-
170
- <!-- 表格 -->
171
- <Card class="table-card">
172
- <!-- 工具栏 -->
173
- <div class="toolbar">
174
- <Button type="primary" @click="handleAdd">新增角色</Button>
175
- </div>
176
-
177
- <!-- 表格 -->
178
- <table class="data-table">
179
- <thead>
180
- <tr>
181
- <th>ID</th>
182
- <th>角色名称</th>
183
- <th>角色编码</th>
184
- <th>描述</th>
185
- <th>状态</th>
186
- <th>创建时间</th>
187
- <th>操作</th>
188
- </tr>
189
- </thead>
190
- <tbody>
191
- <tr v-if="loading">
192
- <td colspan="7" class="loading-cell">加载中...</td>
193
- </tr>
194
- <tr v-else-if="roleList.length === 0">
195
- <td colspan="7" class="empty-cell">暂无数据</td>
196
- </tr>
197
- <tr v-else v-for="row in roleList" :key="row.id">
198
- <td>{{ row.id }}</td>
199
- <td>{{ row.name }}</td>
200
- <td>
201
- <Tag type="primary" size="small">{{ row.code }}</Tag>
202
- </td>
203
- <td>{{ row.description }}</td>
204
- <td>
205
- <Tag :type="row.status === Status.ENABLED ? 'success' : 'danger'" size="small">
206
- {{ row.status === Status.ENABLED ? '启用' : '禁用' }}
207
- </Tag>
208
- </td>
209
- <td>{{ row.createTime }}</td>
210
- <td>
211
- <Space>
212
- <Button type="primary" link size="small" @click="handleEdit(row)">编辑</Button>
213
- <Popconfirm title="确定删除该角色吗?" @confirm="handleDelete(row.id)">
214
- <Button type="danger" link size="small">删除</Button>
215
- </Popconfirm>
216
- </Space>
217
- </td>
218
- </tr>
219
- </tbody>
220
- </table>
221
-
222
- <!-- 分页 -->
223
- <div class="pagination-wrapper">
224
- <Pagination
225
- v-model:current-page="currentPage"
226
- v-model:page-size="pageSize"
227
- :total="total"
228
- :page-sizes="[10, 20, 50, 100]"
229
- layout="total, sizes, prev, pager, next"
230
- @current-change="getRoleList"
231
- @size-change="getRoleList"
232
- />
233
- </div>
234
- </Card>
235
-
236
- <!-- 编辑弹窗 -->
237
- <Modal v-model="modalVisible" :title="modalTitle" width="500px">
238
- <Form ref="formRef" :model="formData" :rules="rules" label-width="80px">
239
- <FormItem label="角色名称" prop="name">
240
- <Input v-model="formData.name" placeholder="请输入角色名称" />
241
- </FormItem>
242
- <FormItem label="角色编码" prop="code">
243
- <Input v-model="formData.code" placeholder="请输入角色编码" />
244
- </FormItem>
245
- <FormItem label="描述">
246
- <Input v-model="formData.description" placeholder="请输入描述" />
247
- </FormItem>
248
- <FormItem label="状态">
249
- <Switch v-model="formData.status" :active-value="Status.ENABLED" :inactive-value="Status.DISABLED" />
250
- </FormItem>
251
- </Form>
252
- <template #footer>
253
- <Space>
254
- <Button @click="modalVisible = false">取消</Button>
255
- <Button type="primary" @click="handleSubmit">确定</Button>
256
- </Space>
257
- </template>
258
- </Modal>
259
- </div>
260
- </template>
261
-
262
- <style lang="scss" scoped>
263
- .role-page {
264
- padding: 20px;
265
-
266
- .search-card {
267
- margin-bottom: 20px;
268
- }
269
-
270
- .toolbar {
271
- margin-bottom: 15px;
272
- }
273
- }
274
-
275
- .data-table {
276
- width: 100%;
277
- border-collapse: collapse;
278
-
279
- th, td {
280
- padding: 12px;
281
- text-align: left;
282
- border-bottom: 1px solid var(--color-border-lighter);
283
- }
284
-
285
- th {
286
- font-weight: 500;
287
- color: var(--color-text-regular);
288
- background-color: var(--color-fill-light);
289
- }
290
-
291
- .loading-cell,
292
- .empty-cell {
293
- text-align: center;
294
- color: var(--color-text-secondary);
295
- padding: 40px;
296
- }
297
- }
298
-
299
- .pagination-wrapper {
300
- display: flex;
301
- justify-content: flex-end;
302
- margin-top: 20px;
303
- }
1
+ <script setup lang="ts">
2
+ </script>
3
+
4
+ <template>
5
+ <div class="page">
6
+ <div class="page__placeholder">
7
+ <h2>角色管理</h2>
8
+ <p>功能开发中...</p>
9
+ </div>
10
+ </div>
11
+ </template>
12
+
13
+ <style lang="scss" scoped>
14
+ .page {
15
+ padding: 20px;
16
+ height: 100%;
17
+
18
+ &__placeholder {
19
+ display: flex;
20
+ flex-direction: column;
21
+ align-items: center;
22
+ justify-content: center;
23
+ height: 100%;
24
+ color: var(--color-text-secondary);
25
+
26
+ h2 {
27
+ margin-bottom: 10px;
28
+ color: var(--color-text-primary);
29
+ }
30
+ }
31
+ }
304
32
  </style>
@@ -1,327 +1,32 @@
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, Card, 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
- <Card class="search-card">
159
- <Form :model="searchForm" inline>
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">搜索</Button>
179
- <Button @click="handleReset">重置</Button>
180
- </Space>
181
- </FormItem>
182
- </Form>
183
- </Card>
184
-
185
- <!-- 表格 -->
186
- <Card class="table-card">
187
- <!-- 工具栏 -->
188
- <div class="toolbar">
189
- <Button type="primary" @click="handleAdd">新增用户</Button>
190
- </div>
191
-
192
- <!-- 表格 -->
193
- <table class="data-table">
194
- <thead>
195
- <tr>
196
- <th>ID</th>
197
- <th>用户名</th>
198
- <th>昵称</th>
199
- <th>邮箱</th>
200
- <th>手机号</th>
201
- <th>角色</th>
202
- <th>状态</th>
203
- <th>创建时间</th>
204
- <th>操作</th>
205
- </tr>
206
- </thead>
207
- <tbody>
208
- <tr v-if="loading">
209
- <td colspan="9" class="loading-cell">加载中...</td>
210
- </tr>
211
- <tr v-else-if="userList.length === 0">
212
- <td colspan="9" class="empty-cell">暂无数据</td>
213
- </tr>
214
- <tr v-else v-for="row in userList" :key="row.id">
215
- <td>{{ row.id }}</td>
216
- <td>{{ row.username }}</td>
217
- <td>{{ row.nickname }}</td>
218
- <td>{{ row.email }}</td>
219
- <td>{{ row.phone }}</td>
220
- <td>
221
- <Tag v-for="role in row.roles" :key="role" size="small">{{ role }}</Tag>
222
- </td>
223
- <td>
224
- <Switch
225
- :model-value="row.status === Status.ENABLED"
226
- @update:model-value="row.status = $event ? Status.ENABLED : Status.DISABLED; handleStatusChange(row)"
227
- />
228
- </td>
229
- <td>{{ row.createTime }}</td>
230
- <td>
231
- <Space>
232
- <Button type="primary" link size="small" @click="handleEdit(row)">编辑</Button>
233
- <Popconfirm title="确定删除该用户吗?" @confirm="handleDelete(row.id)">
234
- <Button type="danger" link size="small">删除</Button>
235
- </Popconfirm>
236
- </Space>
237
- </td>
238
- </tr>
239
- </tbody>
240
- </table>
241
-
242
- <!-- 分页 -->
243
- <div class="pagination-wrapper">
244
- <Pagination
245
- v-model:current-page="currentPage"
246
- v-model:page-size="pageSize"
247
- :total="total"
248
- :page-sizes="[10, 20, 50, 100]"
249
- layout="total, sizes, prev, pager, next"
250
- @current-change="getUserList"
251
- @size-change="getUserList"
252
- />
253
- </div>
254
- </Card>
255
-
256
- <!-- 编辑弹窗 -->
257
- <Modal v-model="modalVisible" :title="modalTitle" width="500px">
258
- <Form ref="formRef" :model="formData" :rules="rules" label-width="80px">
259
- <FormItem label="用户名" prop="username">
260
- <Input v-model="formData.username" placeholder="请输入用户名" />
261
- </FormItem>
262
- <FormItem label="昵称" prop="nickname">
263
- <Input v-model="formData.nickname" placeholder="请输入昵称" />
264
- </FormItem>
265
- <FormItem label="邮箱" prop="email">
266
- <Input v-model="formData.email" placeholder="请输入邮箱" />
267
- </FormItem>
268
- <FormItem label="手机号" prop="phone">
269
- <Input v-model="formData.phone" placeholder="请输入手机号" />
270
- </FormItem>
271
- <FormItem label="状态">
272
- <Switch v-model="formData.status" :active-value="Status.ENABLED" :inactive-value="Status.DISABLED" />
273
- </FormItem>
274
- </Form>
275
- <template #footer>
276
- <Space>
277
- <Button @click="modalVisible = false">取消</Button>
278
- <Button type="primary" @click="handleSubmit">确定</Button>
279
- </Space>
280
- </template>
281
- </Modal>
282
- </div>
283
- </template>
284
-
285
- <style lang="scss" scoped>
286
- .user-page {
287
- padding: 20px;
288
-
289
- .search-card {
290
- margin-bottom: 20px;
291
- }
292
-
293
- .toolbar {
294
- margin-bottom: 15px;
295
- }
296
- }
297
-
298
- .data-table {
299
- width: 100%;
300
- border-collapse: collapse;
301
-
302
- th, td {
303
- padding: 12px;
304
- text-align: left;
305
- border-bottom: 1px solid var(--color-border-lighter);
306
- }
307
-
308
- th {
309
- font-weight: 500;
310
- color: var(--color-text-regular);
311
- background-color: var(--color-fill-light);
312
- }
313
-
314
- .loading-cell,
315
- .empty-cell {
316
- text-align: center;
317
- color: var(--color-text-secondary);
318
- padding: 40px;
319
- }
320
- }
321
-
322
- .pagination-wrapper {
323
- display: flex;
324
- justify-content: flex-end;
325
- margin-top: 20px;
326
- }
1
+ <script setup lang="ts">
2
+ </script>
3
+
4
+ <template>
5
+ <div class="page">
6
+ <div class="page__placeholder">
7
+ <h2>用户管理</h2>
8
+ <p>功能开发中...</p>
9
+ </div>
10
+ </div>
11
+ </template>
12
+
13
+ <style lang="scss" scoped>
14
+ .page {
15
+ padding: 20px;
16
+ height: 100%;
17
+
18
+ &__placeholder {
19
+ display: flex;
20
+ flex-direction: column;
21
+ align-items: center;
22
+ justify-content: center;
23
+ height: 100%;
24
+ color: var(--color-text-secondary);
25
+
26
+ h2 {
27
+ margin-bottom: 10px;
28
+ color: var(--color-text-primary);
29
+ }
30
+ }
31
+ }
327
32
  </style>