zant-admin 1.0.0

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 (82) hide show
  1. package/README.en.md +36 -0
  2. package/README.md +248 -0
  3. package/SCAFFOLD_README.md +215 -0
  4. package/bin/cli.js +99 -0
  5. package/bin/generator.js +503 -0
  6. package/bin/prompts.js +159 -0
  7. package/bin/utils.js +134 -0
  8. package/package.json +74 -0
  9. package/public/logo.png +0 -0
  10. package/src/App.vue +16 -0
  11. package/src/api/methods/logError.js +8 -0
  12. package/src/api/methods/logOperation.js +8 -0
  13. package/src/api/methods/login.js +6 -0
  14. package/src/api/methods/quartz.js +36 -0
  15. package/src/api/methods/region.js +16 -0
  16. package/src/api/methods/sysAccount.js +30 -0
  17. package/src/api/methods/sysDict.js +29 -0
  18. package/src/api/methods/sysDictItem.js +26 -0
  19. package/src/api/methods/sysMenu.js +42 -0
  20. package/src/api/methods/sysRole.js +35 -0
  21. package/src/api/methods/sysUser.js +25 -0
  22. package/src/api/methods/system.js +16 -0
  23. package/src/api/request.js +225 -0
  24. package/src/assets/css/style.css +70 -0
  25. package/src/assets/css/zcui.css +340 -0
  26. package/src/assets/imgs/loginbackground.svg +69 -0
  27. package/src/assets/imgs/logo.png +0 -0
  28. package/src/assets/imgs/md/1.png +0 -0
  29. package/src/assets/imgs/md/10.png +0 -0
  30. package/src/assets/imgs/md/11.png +0 -0
  31. package/src/assets/imgs/md/2.png +0 -0
  32. package/src/assets/imgs/md/3.png +0 -0
  33. package/src/assets/imgs/md/4.png +0 -0
  34. package/src/assets/imgs/md/5.png +0 -0
  35. package/src/assets/imgs/md/6.png +0 -0
  36. package/src/assets/imgs/md/7.png +0 -0
  37. package/src/assets/imgs/md/8.png +0 -0
  38. package/src/assets/imgs/md/9.png +0 -0
  39. package/src/components/FormTable.vue +875 -0
  40. package/src/components/IconPicker.vue +344 -0
  41. package/src/components/MainPage.vue +957 -0
  42. package/src/components/details/logErrorDetails.vue +58 -0
  43. package/src/components/details/logOperationDetails.vue +76 -0
  44. package/src/components/edit/QuartzEdit.vue +221 -0
  45. package/src/components/edit/SysAccountEdit.vue +178 -0
  46. package/src/components/edit/SysDictEdit.vue +114 -0
  47. package/src/components/edit/SysDictItemEdit.vue +134 -0
  48. package/src/components/edit/SysRoleEdit.vue +109 -0
  49. package/src/components/edit/sysMenuEdit.vue +305 -0
  50. package/src/config/index.js +74 -0
  51. package/src/directives/permission.js +45 -0
  52. package/src/main.js +38 -0
  53. package/src/router/index.js +270 -0
  54. package/src/stores/config.js +37 -0
  55. package/src/stores/dict.js +33 -0
  56. package/src/stores/menu.js +57 -0
  57. package/src/stores/user.js +21 -0
  58. package/src/utils/baseEcharts.js +661 -0
  59. package/src/utils/dictTemplate.js +26 -0
  60. package/src/utils/regionUtils.js +169 -0
  61. package/src/utils/useFormCRUD.js +60 -0
  62. package/src/views/baiscstatis/center.vue +463 -0
  63. package/src/views/baiscstatis/iframePage.vue +31 -0
  64. package/src/views/baiscstatis/notFound.vue +192 -0
  65. package/src/views/console.vue +771 -0
  66. package/src/views/demo/importexport.vue +123 -0
  67. package/src/views/demo/region.vue +240 -0
  68. package/src/views/demo/statistics.vue +195 -0
  69. package/src/views/home.vue +7 -0
  70. package/src/views/login.vue +272 -0
  71. package/src/views/operations/log/logError.vue +78 -0
  72. package/src/views/operations/log/logLogin.vue +66 -0
  73. package/src/views/operations/log/logOperation.vue +103 -0
  74. package/src/views/operations/log/logQuartz.vue +57 -0
  75. package/src/views/operations/quartz.vue +181 -0
  76. package/src/views/operations/serviceMonitoring.vue +134 -0
  77. package/src/views/system/sysAccount.vue +123 -0
  78. package/src/views/system/sysDict.vue +156 -0
  79. package/src/views/system/sysDictItem.vue +118 -0
  80. package/src/views/system/sysMenu.vue +223 -0
  81. package/src/views/system/sysRole.vue +184 -0
  82. package/templates/env.production +2 -0
@@ -0,0 +1,169 @@
1
+ import { ref } from 'vue'
2
+ import region from '@/api/methods/region'
3
+
4
+ // 存储原始省市区数据,用于前端查询
5
+ const provinceData = ref([])
6
+ const cityData = ref([])
7
+ const areaData = ref([])
8
+ const allCity = ref([])
9
+ const allAreas = ref([])
10
+
11
+ /**
12
+ * 一次性获取所有省市区数据
13
+ * @returns {Promise} 返回获取数据的Promise
14
+ */
15
+ const fetchAllAddressData = async () => {
16
+ try {
17
+ // 并行获取所有省市区数据
18
+ const [provinceRes, cityRes, areaRes] = await Promise.all([
19
+ region.getAllProvince(),
20
+ region.getAllCity(),
21
+ region.getAllAreas()
22
+ ])
23
+
24
+ // 存储原始数据
25
+ provinceData.value = provinceRes.data
26
+ cityData.value = cityRes.data
27
+ areaData.value = areaRes.data
28
+
29
+ // 设置所有城市数据
30
+ allCity.value = cityData.value.map(item => ({
31
+ label: item.name,
32
+ value: item.adcode,
33
+ }))
34
+
35
+ // 设置所有区域数据
36
+ allAreas.value = areaData.value.map(item => ({
37
+ label: item.name,
38
+ value: item.adcode,
39
+ }))
40
+
41
+ return {
42
+ provinces: provinceData.value.map(item => ({
43
+ label: item.name,
44
+ value: item.adcode,
45
+ })),
46
+ cities: allCity.value,
47
+ areas: allAreas.value
48
+ }
49
+ } catch (error) {
50
+ console.error('获取省市区数据失败:', error)
51
+ throw error
52
+ }
53
+ }
54
+
55
+ /**
56
+ * 根据省份代码查询城市数据
57
+ * @param {string} provinceCode - 省份代码(adcode)
58
+ * @returns {Array} 城市数据数组
59
+ */
60
+ const getCitiesByProvinceCode = (provinceCode) => {
61
+ if (!provinceCode || !cityData.value.length) return []
62
+ return cityData.value.filter(city => city.parentAdcode === provinceCode).map(city => ({
63
+ label: city.name,
64
+ value: city.adcode,
65
+ }))
66
+ }
67
+
68
+ /**
69
+ * 根据城市代码查询区域数据
70
+ * @param {string} cityCode - 城市代码(adcode)
71
+ * @returns {Array} 区域数据数组
72
+ */
73
+ const getAreasByCityCode = (cityCode) => {
74
+ if (!cityCode || !areaData.value.length) return []
75
+ return areaData.value.filter(area => area.parentAdcode === cityCode).map(area => ({
76
+ label: area.name,
77
+ value: area.adcode,
78
+ }))
79
+ }
80
+
81
+ /**
82
+ * 获取所有省份数据
83
+ * @returns {Array} 省份数据数组
84
+ */
85
+ const getAllProvinces = () => {
86
+ return provinceData.value.map(item => ({
87
+ label: item.name,
88
+ value: item.adcode,
89
+ }))
90
+ }
91
+
92
+ /**
93
+ * 获取所有城市数据
94
+ * @returns {Array} 城市数据数组
95
+ */
96
+ const getAllCities = () => {
97
+ return allCity.value
98
+ }
99
+
100
+ /**
101
+ * 获取所有区域数据
102
+ * @returns {Array} 区域数据数组
103
+ */
104
+ const getAllAreas = () => {
105
+ return allAreas.value
106
+ }
107
+
108
+ /**
109
+ * 根据adcode获取省份信息
110
+ * @param {string} adcode - 省份adcode
111
+ * @returns {Object} 省份信息对象
112
+ */
113
+ const getProvinceByAdcode = (adcode) => {
114
+ if (!adcode || !provinceData.value.length) return null
115
+ return provinceData.value.find(province => province.adcode === adcode)
116
+ }
117
+
118
+ /**
119
+ * 根据adcode获取城市信息
120
+ * @param {string} adcode - 城市adcode
121
+ * @returns {Object} 城市信息对象
122
+ */
123
+ const getCityByAdcode = (adcode) => {
124
+ if (!adcode || !cityData.value.length) return null
125
+ return cityData.value.find(city => city.adcode === adcode)
126
+ }
127
+
128
+ /**
129
+ * 根据adcode获取区域信息
130
+ * @param {string} adcode - 区域adcode
131
+ * @returns {Object} 区域信息对象
132
+ */
133
+ const getAreaByAdcode = (adcode) => {
134
+ if (!adcode || !areaData.value.length) return null
135
+ return areaData.value.find(area => area.adcode === adcode)
136
+ }
137
+
138
+ /**
139
+ * 获取完整的省市区名称
140
+ * @param {string} provinceAdcode - 省份adcode
141
+ * @param {string} cityAdcode - 城市adcode
142
+ * @param {string} areaAdcode - 区域adcode
143
+ * @returns {string} 完整的省市区名称
144
+ */
145
+ const getFullAddressName = (provinceAdcode, cityAdcode, areaAdcode) => {
146
+ const province = getProvinceByAdcode(provinceAdcode)
147
+ const city = getCityByAdcode(cityAdcode)
148
+ const area = getAreaByAdcode(areaAdcode)
149
+
150
+ const parts = []
151
+ if (province) parts.push(province.name)
152
+ if (city) parts.push(city.name)
153
+ if (area) parts.push(area.name)
154
+
155
+ return parts.join('')
156
+ }
157
+
158
+ export default {
159
+ fetchAllAddressData,
160
+ getCitiesByProvinceCode,
161
+ getAreasByCityCode,
162
+ getAllProvinces,
163
+ getAllCities,
164
+ getAllAreas,
165
+ getProvinceByAdcode,
166
+ getCityByAdcode,
167
+ getAreaByAdcode,
168
+ getFullAddressName
169
+ }
@@ -0,0 +1,60 @@
1
+ import { ref } from 'vue'
2
+ import { message } from 'ant-design-vue'
3
+
4
+ /**
5
+ * 通用表单 CRUD Hook
6
+ * @param {Object} api - API对象,例如 sysRole
7
+ * @param {Object} [options] - 配置
8
+ * @param {string} [options.addMethod='add'] - 新增方法名
9
+ * @param {string} [options.updateMethod='update'] - 修改方法名
10
+ * @param {boolean} [options.autoMessage=true] - 是否自动提示
11
+ */
12
+ export default function useFormCRUD(api, {
13
+ addMethod = 'add',
14
+ updateMethod = 'update',
15
+ autoMessage = true
16
+ } = {}) {
17
+ const loading = ref(false)
18
+
19
+ /**
20
+ * 保存表单
21
+ * @param {Object} formRef - 表单 ref
22
+ * @param {Object} formData - 表单数据
23
+ * @param {Function} [onSuccess] - 成功回调
24
+ * @param {Function} [onError] - 错误回调
25
+ */
26
+ const save = async (formRef, formData, { onSuccess, onError } = {}) => {
27
+ try {
28
+ // 校验表单
29
+ await formRef?.validate()
30
+ loading.value = true
31
+
32
+ const method = (formData.id) ? updateMethod : addMethod
33
+ const result = await api[method](formData)
34
+ // 先显示提示,再执行回调
35
+ if (autoMessage) {
36
+ message.success(formData.id ? '修改成功' : '新增成功', 1, () => {
37
+ onSuccess?.(result)
38
+ })
39
+ } else {
40
+ onSuccess?.(result)
41
+ }
42
+
43
+ return result
44
+ } catch (err) {
45
+ if (err?.errorFields) {
46
+ message.warning('请完善表单信息')
47
+ } else {
48
+ onError?.(err)
49
+ if (autoMessage && onError) {
50
+ message.error('操作失败,请重试')
51
+ }
52
+ }
53
+ throw err
54
+ } finally {
55
+ loading.value = false
56
+ }
57
+ }
58
+
59
+ return { loading, save }
60
+ }
@@ -0,0 +1,463 @@
1
+ <template>
2
+ <div class="user-center">
3
+ <a-card title="个人中心" :bordered="false" class="user-center-card">
4
+ <a-row :gutter="32">
5
+ <!-- 左侧用户信息 -->
6
+ <a-col :span="6">
7
+ <div class="user-info">
8
+ <div class="avatar-section">
9
+ <a-avatar :size="80" :src="userInfo.avatar" class="user-avatar">
10
+ {{ userInfo.username?.charAt(0) || 'U' }}
11
+ </a-avatar>
12
+ <div class="user-basic-info">
13
+ <h3>{{ userInfo.username || '用户' }}</h3>
14
+ <p class="user-role">{{ userInfo.roleName || '普通用户' }}</p>
15
+ </div>
16
+ </div>
17
+ </div>
18
+ </a-col>
19
+
20
+ <!-- 右侧功能区域 -->
21
+ <a-col :span="18">
22
+ <a-tabs v-model:activeKey="activeTab" type="card">
23
+ <!-- 基本信息 -->
24
+ <a-tab-pane key="basic" tab="基本信息">
25
+ <div class="basic-info-container">
26
+ <a-descriptions title="用户信息" bordered :column="2">
27
+ <a-descriptions-item label="账号名称">
28
+ {{ userInfo.username || '未设置' }}
29
+ </a-descriptions-item>
30
+ <a-descriptions-item label="手机号">
31
+ {{ userInfo.phone || '未设置' }}
32
+ </a-descriptions-item>
33
+ <a-descriptions-item label="邮箱">
34
+ {{ userInfo.email || '未设置' }}
35
+ </a-descriptions-item>
36
+ <a-descriptions-item label="角色">
37
+ {{ userInfo.roleName || '普通用户' }}
38
+ </a-descriptions-item>
39
+ <a-descriptions-item label="最后登录时间" :span="2">
40
+ {{ userInfo.lastLoginTime ? formatDate(userInfo.lastLoginTime) : '从未登录' }}
41
+ </a-descriptions-item>
42
+ <a-descriptions-item label="注册时间" :span="2">
43
+ {{ userInfo.createTime ? formatDate(userInfo.createTime) : '未知' }}
44
+ </a-descriptions-item>
45
+ </a-descriptions>
46
+ </div>
47
+ </a-tab-pane>
48
+
49
+ <!-- 修改密码 -->
50
+ <a-tab-pane key="password" tab="修改密码">
51
+ <div class="password-form-container">
52
+ <a-form
53
+ :model="passwordForm"
54
+ :label-col="{ span: 8 }"
55
+ :wrapper-col="{ span: 12 }"
56
+ layout="horizontal"
57
+ :rules="passwordRules"
58
+ ref="formRef"
59
+ >
60
+ <a-form-item label="原密码" name="oldPassword">
61
+ <a-input-password v-model:value="passwordForm.oldPassword" placeholder="请输入原密码" />
62
+ </a-form-item>
63
+ <a-form-item label="新密码" name="newPassword">
64
+ <a-input-password v-model:value="passwordForm.newPassword" placeholder="请输入新密码" />
65
+ </a-form-item>
66
+ <a-form-item label="确认密码" name="confirmPassword">
67
+ <a-input-password v-model:value="passwordForm.confirmPassword" placeholder="请再次输入新密码" />
68
+ </a-form-item>
69
+ <a-form-item :wrapper-col="{ offset: 8, span: 12 }">
70
+ <a-button type="primary" @click="changePassword">修改密码</a-button>
71
+ <a-button style="margin-left: 10px" @click="resetPasswordForm">重置</a-button>
72
+ </a-form-item>
73
+ </a-form>
74
+ </div>
75
+ </a-tab-pane>
76
+
77
+ <!-- 安全设置 -->
78
+ <!-- <a-tab-pane key="security" tab="安全设置">
79
+ <a-list item-layout="horizontal">
80
+ <a-list-item>
81
+ <a-list-item-meta
82
+ title="登录提醒"
83
+ description="开启后,当有新设备登录时会发送提醒"
84
+ />
85
+ <template #actions>
86
+ <a-switch v-model:checked="securitySettings.loginAlert" />
87
+ </template>
88
+ </a-list-item>
89
+ <a-list-item>
90
+ <a-list-item-meta
91
+ title="双重验证"
92
+ description="开启双重验证,提高账户安全性"
93
+ />
94
+ <template #actions>
95
+ <a-switch v-model:checked="securitySettings.twoFactorAuth" />
96
+ </template>
97
+ </a-list-item>
98
+ <a-list-item>
99
+ <a-list-item-meta
100
+ title="会话管理"
101
+ description="查看和管理当前登录的设备"
102
+ />
103
+ <template #actions>
104
+ <a-button type="link" @click="showSessionManager">管理</a-button>
105
+ </template>
106
+ </a-list-item>
107
+ </a-list>
108
+ <div style="margin-top: 20px; text-align: center;">
109
+ <a-button type="primary" @click="saveSecuritySettings">保存设置</a-button>
110
+ </div>
111
+ </a-tab-pane> -->
112
+ </a-tabs>
113
+ </a-col>
114
+ </a-row>
115
+ </a-card>
116
+
117
+ <!-- 会话管理模态框 -->
118
+ <a-modal
119
+ v-model:visible="sessionModalVisible"
120
+ title="会话管理"
121
+ width="600px"
122
+ :footer="null"
123
+ >
124
+ <a-list
125
+ item-layout="horizontal"
126
+ :data-source="sessions"
127
+ >
128
+ <template #renderItem="{ item }">
129
+ <a-list-item>
130
+ <a-list-item-meta
131
+ :title="item.device"
132
+ :description="`最后活动: ${formatDate(item.lastActivity)}`"
133
+ />
134
+ <template #actions>
135
+ <a-button type="link" danger @click="logoutSession(item.id)">强制下线</a-button>
136
+ </template>
137
+ </a-list-item>
138
+ </template>
139
+ </a-list>
140
+ </a-modal>
141
+ </div>
142
+ </template>
143
+
144
+ <script setup>
145
+ import { ref, reactive, onMounted } from 'vue'
146
+ import { message } from 'ant-design-vue'
147
+ import { useUserStore } from '@/stores/user'
148
+ import sysAccount from '@/api/methods/sysAccount'
149
+
150
+ // 用户存储
151
+ const userStore = useUserStore()
152
+
153
+ // 响应式数据
154
+ const activeTab = ref('basic')
155
+ const sessionModalVisible = ref(false)
156
+ const formRef = ref(null)
157
+ // 用户信息
158
+ const userInfo = reactive({
159
+ username: '',
160
+ email: '',
161
+ phone: '',
162
+ position: '',
163
+ roleName: '',
164
+ avatar: '',
165
+ lastLoginTime: null,
166
+ createTime: null
167
+ })
168
+
169
+ // 密码表单
170
+ const passwordForm = reactive({
171
+ oldPassword: '',
172
+ newPassword: '',
173
+ confirmPassword: ''
174
+ })
175
+
176
+ // 安全设置
177
+ const securitySettings = reactive({
178
+ loginAlert: false,
179
+ twoFactorAuth: false
180
+ })
181
+
182
+ // 会话列表
183
+ const sessions = ref([])
184
+
185
+ // 密码验证规则
186
+ const passwordRules = {
187
+ oldPassword: [
188
+ { required: true, message: '请输入原密码', trigger: 'blur' }
189
+ ],
190
+ newPassword: [
191
+ { required: true, message: '请输入新密码', trigger: 'blur' },
192
+ // { min: 6, message: '密码长度不能少于6位', trigger: 'blur' }
193
+ ],
194
+ confirmPassword: [
195
+ { required: true, message: '请确认新密码', trigger: 'blur' },
196
+ {
197
+ validator: (rule, value) => {
198
+ if (value !== passwordForm.newPassword) {
199
+ return Promise.reject('两次输入的密码不一致')
200
+ }
201
+ return Promise.resolve()
202
+ },
203
+ trigger: 'blur'
204
+ }
205
+ ]
206
+ }
207
+
208
+ // 生命周期
209
+ onMounted(() => {
210
+ loadUserInfo()
211
+ })
212
+
213
+ // 加载用户信息
214
+ const loadUserInfo = async () => {
215
+ try {
216
+ // 从用户存储获取基本信息
217
+ const currentUser = userStore.userInfo
218
+ if (currentUser) {
219
+ Object.assign(userInfo, {
220
+ username: currentUser.name || '',
221
+ email: currentUser.email || '',
222
+ phone: currentUser.mobile || '',
223
+ position: currentUser.position || '',
224
+ roleName: currentUser.roleId === 1 ? '管理员' : '普通用户',
225
+ avatar: currentUser.profilePicture || '',
226
+ lastLoginTime: currentUser.lastLoginTime || null,
227
+ createTime: currentUser.createTime || null
228
+ })
229
+ }
230
+
231
+ // 这里可以添加API调用获取更详细的用户信息
232
+ // const response = await getUserDetail()
233
+ // if (response.success) {
234
+ // Object.assign(userInfo, response.data)
235
+ // }
236
+ } catch (error) {
237
+ console.error('加载用户信息失败:', error)
238
+ message.error('加载用户信息失败')
239
+ }
240
+ }
241
+
242
+
243
+
244
+ // 修改密码
245
+ const changePassword = async () => {
246
+ try {
247
+ // 验证表单
248
+ await formRef.value.validate()
249
+
250
+ if (passwordForm.newPassword !== passwordForm.confirmPassword) {
251
+ message.error('两次输入的密码不一致')
252
+ return
253
+ }
254
+
255
+ // 调用修改密码接口
256
+ const res = await sysAccount.updatePwd({
257
+ id: userStore.userInfo.id,
258
+ oldPwd: passwordForm.oldPassword,
259
+ newPwd: passwordForm.newPassword
260
+ })
261
+
262
+ message.success('密码修改成功')
263
+ resetPasswordForm()
264
+
265
+
266
+ } catch (error) {
267
+ console.error('修改密码失败:', error)
268
+ // message.error('修改密码失败')
269
+ }
270
+ }
271
+
272
+ // 重置密码表单
273
+ const resetPasswordForm = () => {
274
+ Object.assign(passwordForm, {
275
+ oldPassword: '',
276
+ newPassword: '',
277
+ confirmPassword: ''
278
+ })
279
+ }
280
+
281
+ // 保存安全设置
282
+ const saveSecuritySettings = async () => {
283
+ try {
284
+ // 这里添加保存安全设置的API调用
285
+ // await updateSecuritySettings(securitySettings)
286
+ message.success('安全设置保存成功')
287
+ } catch (error) {
288
+ console.error('保存安全设置失败:', error)
289
+ message.error('保存安全设置失败')
290
+ }
291
+ }
292
+
293
+ // 显示会话管理
294
+ const showSessionManager = () => {
295
+ sessionModalVisible.value = true
296
+ loadSessions()
297
+ }
298
+
299
+ // 加载会话列表
300
+ const loadSessions = async () => {
301
+ try {
302
+ // 这里添加获取会话列表的API调用
303
+ // const response = await getUserSessions()
304
+ // sessions.value = response.data || []
305
+
306
+ // 模拟数据
307
+ sessions.value = [
308
+ {
309
+ id: 1,
310
+ device: 'Windows Chrome',
311
+ lastActivity: new Date(),
312
+ location: '中国 北京'
313
+ },
314
+ {
315
+ id: 2,
316
+ device: 'Mac Safari',
317
+ lastActivity: new Date(Date.now() - 2 * 60 * 60 * 1000), // 2小时前
318
+ location: '中国 上海'
319
+ }
320
+ ]
321
+ } catch (error) {
322
+ console.error('加载会话列表失败:', error)
323
+ }
324
+ }
325
+
326
+ // 强制下线会话
327
+ const logoutSession = async (sessionId) => {
328
+ try {
329
+ // 这里添加强制下线会话的API调用
330
+ // await forceLogoutSession(sessionId)
331
+ message.success('会话已强制下线')
332
+ loadSessions()
333
+ } catch (error) {
334
+ console.error('强制下线失败:', error)
335
+ message.error('强制下线失败')
336
+ }
337
+ }
338
+
339
+ // 格式化日期
340
+ const formatDate = (date) => {
341
+ if (!date) return '未知'
342
+ const d = new Date(date)
343
+ return d.toLocaleString('zh-CN', {
344
+ year: 'numeric',
345
+ month: '2-digit',
346
+ day: '2-digit',
347
+ hour: '2-digit',
348
+ minute: '2-digit'
349
+ })
350
+ }
351
+ </script>
352
+
353
+ <style scoped>
354
+ .user-center {
355
+ padding: 10px;
356
+ }
357
+
358
+ .user-center-card {
359
+ border-radius: 8px;
360
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
361
+ }
362
+
363
+ .user-info {
364
+ text-align: center;
365
+ display: flex;
366
+ flex-direction: column;
367
+ justify-content: center;
368
+ height: 100%;
369
+ min-height: 400px;
370
+ }
371
+
372
+ .avatar-section {
373
+ margin-bottom: 24px;
374
+ }
375
+
376
+ .user-avatar {
377
+ margin-bottom: 16px;
378
+ border: 3px solid #fff;
379
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
380
+ background-color: #1677ff !important;
381
+ font-size: 38px !important;
382
+ font-weight: 600 !important;
383
+
384
+ }
385
+ .user-basic-info h3 {
386
+ margin: 0 0 8px 0;
387
+ font-size: 20px;
388
+ color: #262626;
389
+ }
390
+
391
+ .user-role {
392
+ margin: 0 0 4px 0;
393
+ color: #8c8c8c;
394
+ font-size: 14px;
395
+ }
396
+
397
+
398
+
399
+ .user-stats {
400
+ margin-top: 24px;
401
+ padding: 16px;
402
+ background: #fafafa;
403
+ border-radius: 6px;
404
+ }
405
+
406
+ .stat-item {
407
+ text-align: center;
408
+ }
409
+
410
+ .stat-number {
411
+ font-size: 18px;
412
+ font-weight: 600;
413
+ color: #1890ff;
414
+ margin-bottom: 4px;
415
+ }
416
+
417
+ .stat-label {
418
+ font-size: 12px;
419
+ color: #8c8c8c;
420
+ }
421
+
422
+ :deep(.ant-tabs-card > .ant-tabs-nav .ant-tabs-tab) {
423
+ border-radius: 6px 6px 0 0;
424
+ }
425
+
426
+ :deep(.ant-tabs-card > .ant-tabs-nav .ant-tabs-tab-active) {
427
+ background: #fff;
428
+ }
429
+
430
+ .basic-info-container {
431
+ padding: 20px;
432
+ }
433
+
434
+ :deep(.ant-descriptions) {
435
+ border-radius: 8px;
436
+ overflow: hidden;
437
+ }
438
+
439
+ :deep(.ant-descriptions-title) {
440
+ font-size: 18px;
441
+ font-weight: 600;
442
+ color: #262626;
443
+ margin-bottom: 16px;
444
+ }
445
+
446
+ :deep(.ant-descriptions-item-label) {
447
+ font-weight: 500;
448
+ background: #f8f9fa;
449
+ }
450
+
451
+ .password-form-container {
452
+ display: flex;
453
+ justify-content: center;
454
+ align-items: center;
455
+ min-height: 300px;
456
+ padding: 20px;
457
+ }
458
+
459
+ :deep(.password-form-container .ant-form) {
460
+ width: 100%;
461
+ max-width: 400px;
462
+ }
463
+ </style>