@ddj-v2/user-management 2.0.0 → 2.2.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.
package/index.ts CHANGED
@@ -1,342 +1,480 @@
1
- import {
2
- Context, Handler, param, PRIV, Types, UserModel, DomainModel,
3
- ValidationError, UserNotFoundError, PermissionError, Time, SystemModel, moment
4
- } from 'hydrooj';
5
-
6
- declare module 'hydrooj' {
7
- interface Collections {
8
- // 扩展用户集合类型
9
- }
10
- }
11
-
12
- // 用户管理处理器基类
13
- class UserManageHandler extends Handler {
14
- async prepare() {
15
- // 检查是否有系统管理权限
16
- this.checkPriv(PRIV.PRIV_EDIT_SYSTEM);
17
- }
18
- }
19
-
20
- // 用户管理主页面处理器
21
- class UserManageMainHandler extends UserManageHandler {
22
- @param('page', Types.PositiveInt, true)
23
- @param('search', Types.String, true)
24
- @param('sort', Types.String, true)
25
- async get(domainId: string, page = 1, search = '', sort = '_id') {
26
- const limit = 50;
27
- const query: any = {};
28
-
29
- // 搜索功能
30
- if (search) {
31
- const searchRegex = new RegExp(search, 'i');
32
- query.$or = [
33
- { uname: searchRegex },
34
- { mail: searchRegex },
35
- { _id: isNaN(+search) ? undefined : +search }
36
- ].filter(Boolean);
37
- }
38
-
39
- // 排序选项
40
- const sortOptions: Record<string, any> = {
41
- '_id': { _id: 1 },
42
- 'uname': { uname: 1 },
43
- 'regat': { regat: -1 },
44
- 'loginat': { loginat: -1 },
45
- 'priv': { priv: -1 }
46
- };
47
-
48
- const sortQuery = sortOptions[sort] || { _id: 1 };
49
-
50
- // 获取用户列表
51
- const [udocs, upcount] = await this.paginate(
52
- UserModel.getMulti(query).sort(sortQuery),
53
- page,
54
- limit
55
- );
56
-
57
- // 获取用户在当前域的信息
58
- const duids = udocs.map(udoc => udoc._id);
59
- const dudocs = await DomainModel.getMultiUserInDomain(domainId, { uid: { $in: duids } }).toArray();
60
- const dudocMap = Object.fromEntries(dudocs.map(dudoc => [dudoc.uid, dudoc]));
61
-
62
- this.response.template = 'user_manage_main.html';
63
- this.response.body = {
64
- udocs,
65
- dudocMap,
66
- page,
67
- upcount,
68
- search,
69
- sort,
70
- canEdit: true,
71
- moment
72
- };
73
- }
74
- }
75
-
76
- // 用户详情和编辑处理器
77
- class UserManageDetailHandler extends UserManageHandler {
78
- @param('uid', Types.Int)
79
- async get(domainId: string, uid: number) {
80
- const udoc = await UserModel.getById(domainId, uid);
81
- if (!udoc) throw new UserNotFoundError(uid);
82
-
83
- const dudoc = await DomainModel.getDomainUser(domainId, udoc);
84
-
85
- this.response.template = 'user_manage_detail.html';
86
- this.response.body = {
87
- udoc,
88
- dudoc,
89
- canEdit: true,
90
- moment
91
- };
92
- }
93
-
94
- @param('uid', Types.Int)
95
- @param('operation', Types.String)
96
- async post(domainId: string, uid: number, operation: string) {
97
- const udoc = await UserModel.getById(domainId, uid);
98
- if (!udoc) throw new UserNotFoundError(uid);
99
-
100
- if (operation === 'edit') {
101
- await this.postEdit(domainId, uid);
102
- } else if (operation === 'resetPassword') {
103
- await this.postResetPassword(domainId, uid);
104
- } else if (operation === 'setPriv') {
105
- await this.postSetPriv(domainId, uid);
106
- } else if (operation === 'ban') {
107
- await this.postBan(domainId, uid);
108
- } else if (operation === 'unban') {
109
- await this.postUnban(domainId, uid);
110
- }
111
-
112
- this.back();
113
- }
114
-
115
- @param('uid', Types.Int)
116
- @param('mail', Types.Email, true)
117
- @param('uname', Types.Username, true)
118
- @param('school', Types.String, true)
119
- @param('bio', Types.Content, true)
120
- async postEdit(domainId: string, uid: number, mail?: string, uname?: string, school?: string, bio?: string) {
121
- const udoc = await UserModel.getById(domainId, uid);
122
- if (!udoc) throw new UserNotFoundError(uid);
123
-
124
- if (mail && mail !== udoc.mail) {
125
- // 检查邮箱是否已被使用
126
- const existing = await UserModel.getByEmail(domainId, mail);
127
- if (existing && existing._id !== uid) {
128
- throw new ValidationError('mail', 'Email already in use');
129
- }
130
- await UserModel.setEmail(uid, mail);
131
- }
132
-
133
- if (uname && uname !== udoc.uname) {
134
- // 检查用户名是否已被使用
135
- const existing = await UserModel.getByUname(domainId, uname);
136
- if (existing && existing._id !== uid) {
137
- throw new ValidationError('uname', 'Username already in use');
138
- }
139
- await UserModel.setUname(uid, uname);
140
- }
141
-
142
- const updates: any = {};
143
- if (school !== undefined) updates.school = school;
144
- if (bio !== undefined) updates.bio = bio;
145
-
146
- if (Object.keys(updates).length > 0) {
147
- await UserModel.setById(uid, updates);
148
- }
149
- }
150
-
151
- @param('uid', Types.Int)
152
- @param('password', Types.Password)
153
- async postResetPassword(domainId: string, uid: number, password: string) {
154
- const udoc = await UserModel.getById(domainId, uid);
155
- if (!udoc) throw new UserNotFoundError(uid);
156
-
157
- // 不允许重置超级管理员密码(除非当前用户也是超级管理员)
158
- if (udoc.priv === PRIV.PRIV_ALL && this.user.priv !== PRIV.PRIV_ALL) {
159
- throw new PermissionError('Cannot reset super admin password');
160
- }
161
-
162
- await UserModel.setPassword(uid, password);
163
- }
164
-
165
- @param('uid', Types.Int)
166
- @param('priv', Types.Int)
167
- async postSetPriv(domainId: string, uid: number, priv: number) {
168
- const udoc = await UserModel.getById(domainId, uid);
169
- if (!udoc) throw new UserNotFoundError(uid);
170
-
171
- // 不允许修改超级管理员权限(除非当前用户也是超级管理员)
172
- if ((udoc.priv === PRIV.PRIV_ALL || priv === PRIV.PRIV_ALL) && this.user.priv !== PRIV.PRIV_ALL) {
173
- throw new PermissionError('Cannot modify super admin privileges');
174
- }
175
-
176
- await UserModel.setPriv(uid, priv);
177
- }
178
-
179
- @param('uid', Types.Int)
180
- async postBan(domainId: string, uid: number) {
181
- const udoc = await UserModel.getById(domainId, uid);
182
- if (!udoc) throw new UserNotFoundError(uid);
183
-
184
- // 不允许封禁超级管理员
185
- if (udoc.priv === PRIV.PRIV_ALL) {
186
- throw new PermissionError('Cannot ban super admin');
187
- }
188
-
189
- await UserModel.ban(uid, 'Banned by administrator');
190
- }
191
-
192
- @param('uid', Types.Int)
193
- async postUnban(domainId: string, uid: number) {
194
- const udoc = await UserModel.getById(domainId, uid);
195
- if (!udoc) throw new UserNotFoundError(uid);
196
-
197
- // 恢复为默认权限
198
- const defaultPriv = await SystemModel.get('default.priv');
199
- await UserModel.setPriv(uid, defaultPriv);
200
- }
201
- }
202
-
203
-
204
-
205
- export async function apply(ctx: Context) {
206
- // 注册路由
207
- ctx.Route('user_manage_main', '/manage/users', UserManageMainHandler, PRIV.PRIV_EDIT_SYSTEM);
208
- ctx.Route('user_manage_detail', '/manage/users/:uid', UserManageDetailHandler, PRIV.PRIV_EDIT_SYSTEM);
209
-
210
- // 在控制面板侧边栏添加用户管理菜单项
211
- ctx.injectUI('ControlPanel', 'user_manage_main', { icon: 'user' });
212
-
213
- // 添加国际化支持
214
- ctx.i18n.load('zh', {
215
- 'user_manage_main': '用户管理',
216
- 'user_manage_detail': '用户详情',
217
-
218
- 'User Management': '用户管理',
219
- 'User List': '用户列表',
220
- 'Search Users': '搜索用户',
221
- 'Search by': '搜索方式',
222
- 'Username': '用户名',
223
- 'Email': '邮箱',
224
- 'User ID': '用户ID',
225
- 'Keyword': '关键词',
226
- 'Sort by': '排序方式',
227
- 'Registration Time': '注册时间',
228
- 'Last Login': '最后登录',
229
- 'Privilege': '权限',
230
- 'Order': '顺序',
231
- 'Ascending': '升序',
232
- 'Descending': '降序',
233
- 'Search': '搜索',
234
- 'Clear': '清空',
235
- 'Refresh': '刷新',
236
-
237
- 'Normal User': '普通用户',
238
- 'Admin': '管理员',
239
- 'Banned': '已封禁',
240
- 'Super Admin': '超级管理员',
241
- 'Active': '活跃',
242
- 'Inactive': '不活跃',
243
- 'Actions': '操作',
244
- 'View': '查看',
245
- 'Edit': '编辑',
246
- 'Ban': '封禁',
247
- 'Unban': '解封',
248
- 'Set Privilege': '设置权限',
249
- 'Status': '状态',
250
- 'School': '学校',
251
- 'Bio': '个人简介',
252
- 'Never': '从未',
253
- 'Not set': '未设置',
254
- 'Previous': '上一页',
255
- 'Next': '下一页',
256
- 'Page': '页',
257
- 'of': '',
258
- 'users': '用户',
259
- 'Total': '总计',
260
- 'Showing': '显示',
261
- 'to': '到',
262
- 'User Details': '用户详情',
263
- 'Basic Information': '基本信息',
264
- 'User Statistics': '用户统计',
265
- 'Privilege Management': '权限管理',
266
- 'Password Management': '密码管理',
267
- 'User Status': '用户状态',
268
- 'Back to List': '返回列表',
269
- 'Save Changes': '保存更改',
270
- 'Cancel': '取消',
271
- 'Reset Password': '重置密码',
272
- 'Current Privilege': '当前权限',
273
- 'Ban User': '封禁用户',
274
- 'Unban User': '解封用户',
275
- 'Copy User ID': '复制用户ID'
276
- });
277
-
278
- ctx.i18n.load('en', {
279
- 'user_manage_main': 'User Management',
280
- 'user_manage_detail': 'User Detail',
281
- 'user_manage_batch': 'Batch Operations',
282
- 'User Management': 'User Management',
283
- 'User List': 'User List',
284
- 'Search Users': 'Search Users',
285
- 'Search by': 'Search by',
286
- 'Username': 'Username',
287
- 'Email': 'Email',
288
- 'User ID': 'User ID',
289
- 'Keyword': 'Keyword',
290
- 'Sort by': 'Sort by',
291
- 'Registration Time': 'Registration Time',
292
- 'Last Login': 'Last Login',
293
- 'Privilege': 'Privilege',
294
- 'Order': 'Order',
295
- 'Ascending': 'Ascending',
296
- 'Descending': 'Descending',
297
- 'Search': 'Search',
298
- 'Clear': 'Clear',
299
- 'Refresh': 'Refresh',
300
- 'Batch Operations': 'Batch Operations',
301
- 'Export Users': 'Export Users',
302
- 'Normal User': 'Normal User',
303
- 'Admin': 'Admin',
304
- 'Banned': 'Banned',
305
- 'Super Admin': 'Super Admin',
306
- 'Active': 'Active',
307
- 'Inactive': 'Inactive',
308
- 'Actions': 'Actions',
309
- 'View': 'View',
310
- 'Edit': 'Edit',
311
- 'Ban': 'Ban',
312
- 'Unban': 'Unban',
313
- 'Set Privilege': 'Set Privilege',
314
- 'Status': 'Status',
315
- 'School': 'School',
316
- 'Bio': 'Bio',
317
- 'Never': 'Never',
318
- 'Not set': 'Not set',
319
- 'Previous': 'Previous',
320
- 'Next': 'Next',
321
- 'Page': 'Page',
322
- 'of': 'of',
323
- 'users': 'users',
324
- 'Total': 'Total',
325
- 'Showing': 'Showing',
326
- 'to': 'to',
327
- 'User Details': 'User Details',
328
- 'Basic Information': 'Basic Information',
329
- 'User Statistics': 'User Statistics',
330
- 'Privilege Management': 'Privilege Management',
331
- 'Password Management': 'Password Management',
332
- 'User Status': 'User Status',
333
- 'Back to List': 'Back to List',
334
- 'Save Changes': 'Save Changes',
335
- 'Cancel': 'Cancel',
336
- 'Reset Password': 'Reset Password',
337
- 'Current Privilege': 'Current Privilege',
338
- 'Ban User': 'Ban User',
339
- 'Unban User': 'Unban User',
340
- 'Copy User ID': 'Copy User ID'
341
- });
1
+ import {
2
+ Context, Handler, param, PRIV, Types, UserModel, DomainModel,
3
+ ValidationError, UserNotFoundError, PermissionError, Time, SystemModel, moment,
4
+ PERM
5
+ } from 'hydrooj';
6
+ import domain from 'hydrooj/src/model/domain';
7
+
8
+ declare module 'hydrooj' {
9
+ interface Collections {
10
+ // 扩展用户集合类型
11
+ }
12
+ }
13
+
14
+ // 用户管理处理器基类
15
+ class UserManageHandler extends Handler {
16
+ async prepare() {
17
+ // 检查是否有系统管理权限
18
+ this.checkPriv(PRIV.PRIV_EDIT_SYSTEM);
19
+ }
20
+ }
21
+
22
+ // 用户管理主页面处理器
23
+ class UserManageMainHandler extends UserManageHandler {
24
+ @param('page', Types.PositiveInt, true)
25
+ @param('search', Types.String, true)
26
+ @param('sort', Types.String, true)
27
+ async get(domainId: string, page = 1, search = '', sort = '_id') {
28
+ const limit = 50;
29
+ const query: any = {};
30
+
31
+ // 搜索功能
32
+ if (search) {
33
+ const searchRegex = new RegExp(search, 'i');
34
+ query.$or = [
35
+ { uname: searchRegex },
36
+ { mail: searchRegex },
37
+ { _id: isNaN(+search) ? undefined : +search }
38
+ ].filter(Boolean);
39
+ }
40
+
41
+ // 排序选项
42
+ const sortOptions: Record<string, any> = {
43
+ '_id': { _id: 1 },
44
+ 'uname': { uname: 1 },
45
+ 'regat': { regat: -1 },
46
+ 'loginat': { loginat: -1 },
47
+ 'priv': { priv: -1 }
48
+ };
49
+
50
+ const sortQuery = sortOptions[sort] || { _id: 1 };
51
+
52
+ // 获取用户列表
53
+ const [udocs, upcount] = await this.paginate(
54
+ UserModel.getMulti(query).sort(sortQuery),
55
+ page,
56
+ limit
57
+ );
58
+
59
+ // 获取用户在当前域的信息
60
+ const duids = udocs.map(udoc => udoc._id);
61
+ const dudocs = await DomainModel.getMultiUserInDomain(domainId, { uid: { $in: duids } }).toArray();
62
+ const dudocMap = Object.fromEntries(dudocs.map(dudoc => [dudoc.uid, dudoc]));
63
+
64
+ this.response.template = 'user_manage_main.html';
65
+ this.response.body = {
66
+ udocs,
67
+ dudocMap,
68
+ page,
69
+ upcount,
70
+ search,
71
+ sort,
72
+ canEdit: true,
73
+ moment
74
+ };
75
+ }
76
+ }
77
+
78
+ // 用户详情和编辑处理器
79
+ class UserManageDetailHandler extends UserManageHandler {
80
+ @param('uid', Types.Int)
81
+ async get(domainId: string, uid: number) {
82
+ const udoc = await UserModel.getById(domainId, uid);
83
+ if (!udoc) throw new UserNotFoundError(uid);
84
+
85
+ const dudoc = await DomainModel.getDomainUser(domainId, udoc);
86
+
87
+ this.response.template = 'user_manage_detail.html';
88
+ this.response.body = {
89
+ udoc,
90
+ dudoc,
91
+ canEdit: true,
92
+ moment
93
+ };
94
+ }
95
+
96
+ @param('uid', Types.Int)
97
+ @param('operation', Types.String)
98
+ async post(domainId: string, uid: number, operation: string) {
99
+ const udoc = await UserModel.getById(domainId, uid);
100
+ if (!udoc) throw new UserNotFoundError(uid);
101
+
102
+ if (operation === 'edit') {
103
+ await this.postEdit(domainId, uid);
104
+ } else if (operation === 'resetPassword') {
105
+ await this.postResetPassword(domainId, uid);
106
+ } else if (operation === 'setPriv') {
107
+ await this.postSetPriv(domainId, uid);
108
+ } else if (operation === 'ban') {
109
+ await this.postBan(domainId, uid);
110
+ } else if (operation === 'unban') {
111
+ await this.postUnban(domainId, uid);
112
+ }
113
+
114
+ this.back();
115
+ }
116
+
117
+ @param('uid', Types.Int)
118
+ @param('mail', Types.Email, true)
119
+ @param('uname', Types.Username, true)
120
+ @param('school', Types.String, true)
121
+ @param('bio', Types.Content, true)
122
+ async postEdit(domainId: string, uid: number, mail?: string, uname?: string, school?: string, bio?: string) {
123
+ const udoc = await UserModel.getById(domainId, uid);
124
+ if (!udoc) throw new UserNotFoundError(uid);
125
+
126
+ if (mail && mail !== udoc.mail) {
127
+ // 检查邮箱是否已被使用
128
+ const existing = await UserModel.getByEmail(domainId, mail);
129
+ if (existing && existing._id !== uid) {
130
+ throw new ValidationError('mail', 'Email already in use');
131
+ }
132
+ await UserModel.setEmail(uid, mail);
133
+ }
134
+
135
+ if (uname && uname !== udoc.uname) {
136
+ // 检查用户名是否已被使用
137
+ const existing = await UserModel.getByUname(domainId, uname);
138
+ if (existing && existing._id !== uid) {
139
+ throw new ValidationError('uname', 'Username already in use');
140
+ }
141
+ await UserModel.setUname(uid, uname);
142
+ }
143
+
144
+ const updates: any = {};
145
+ if (school !== undefined) updates.school = school;
146
+ if (bio !== undefined) updates.bio = bio;
147
+
148
+ if (Object.keys(updates).length > 0) {
149
+ await UserModel.setById(uid, updates);
150
+ }
151
+ }
152
+
153
+ @param('uid', Types.Int)
154
+ @param('password', Types.Password)
155
+ async postResetPassword(domainId: string, uid: number, password: string) {
156
+ const udoc = await UserModel.getById(domainId, uid);
157
+ if (!udoc) throw new UserNotFoundError(uid);
158
+
159
+ // 不允许重置超级管理员密码(除非当前用户也是超级管理员)
160
+ if (udoc.priv === PRIV.PRIV_ALL && this.user.priv !== PRIV.PRIV_ALL) {
161
+ throw new PermissionError('Cannot reset super admin password');
162
+ }
163
+
164
+ await UserModel.setPassword(uid, password);
165
+ }
166
+
167
+ @param('uid', Types.Int)
168
+ @param('priv', Types.Int)
169
+ async postSetPriv(domainId: string, uid: number, priv: number) {
170
+ const udoc = await UserModel.getById(domainId, uid);
171
+ if (!udoc) throw new UserNotFoundError(uid);
172
+
173
+ // 不允许修改超级管理员权限(除非当前用户也是超级管理员)
174
+ if ((udoc.priv === PRIV.PRIV_ALL || priv === PRIV.PRIV_ALL) && this.user.priv !== PRIV.PRIV_ALL) {
175
+ throw new PermissionError('Cannot modify super admin privileges');
176
+ }
177
+
178
+ await UserModel.setPriv(uid, priv);
179
+ }
180
+
181
+ @param('uid', Types.Int)
182
+ async postBan(domainId: string, uid: number) {
183
+ const udoc = await UserModel.getById(domainId, uid);
184
+ if (!udoc) throw new UserNotFoundError(uid);
185
+
186
+ // 不允许封禁超级管理员
187
+ if (udoc.priv === PRIV.PRIV_ALL) {
188
+ throw new PermissionError('Cannot ban super admin');
189
+ }
190
+
191
+ await UserModel.ban(uid, 'Banned by administrator');
192
+ }
193
+
194
+ @param('uid', Types.Int)
195
+ async postUnban(domainId: string, uid: number) {
196
+ const udoc = await UserModel.getById(domainId, uid);
197
+ if (!udoc) throw new UserNotFoundError(uid);
198
+
199
+ // 恢复为默认权限
200
+ const defaultPriv = await SystemModel.get('default.priv');
201
+ await UserModel.setPriv(uid, defaultPriv);
202
+ }
203
+ }
204
+
205
+
206
+
207
+ export async function apply(ctx: Context) {
208
+ // 注册路由
209
+ ctx.Route('user_manage_main', '/manage/users', UserManageMainHandler, PRIV.PRIV_EDIT_SYSTEM);
210
+ ctx.Route('user_manage_detail', '/manage/users/:uid', UserManageDetailHandler, PRIV.PRIV_EDIT_SYSTEM);
211
+
212
+ // 在控制面板侧边栏添加用户管理菜单项
213
+ ctx.injectUI('ControlPanel', 'user_manage_main', { icon: 'user' });
214
+
215
+ ctx.withHandlerClass('DomainUser', (DomainUserHandler: { prototype: any }) => {
216
+ const originalGet = DomainUserHandler.prototype.get;
217
+
218
+ // 包裝原方法
219
+ DomainUserHandler.prototype.get = async function() {
220
+ const { domainId } = this.args;
221
+ const format = this.args.format || 'default';
222
+ console.log('DomainUserHandler get called with domainId:', domainId, 'format:', format);
223
+ const [dudocs, roles] = await Promise.all([
224
+ domain.collUser.aggregate([
225
+ {
226
+ $match: {
227
+ // TODO: add a page to display users who joined but with default role
228
+ role: {
229
+ $nin: ['guest'],
230
+ $ne: null,
231
+ },
232
+ domainId,
233
+ },
234
+ },
235
+ {
236
+ $lookup: {
237
+ from: 'user',
238
+ let: { uid: '$uid' },
239
+ pipeline: [
240
+ {
241
+ $match: {
242
+ $expr: { $eq: ['$_id', '$$uid'] },
243
+ priv: { $bitsAllSet: PRIV.PRIV_USER_PROFILE },
244
+ },
245
+ },
246
+ {
247
+ $project: {
248
+ _id: 1,
249
+ uname: 1,
250
+ avatar: 1,
251
+ },
252
+ },
253
+ ],
254
+ as: 'user',
255
+ },
256
+ },
257
+ { $unwind: '$user' },
258
+ {
259
+ $project: {
260
+ user: 1,
261
+ role: 1,
262
+ join: 1,
263
+ ...(this.user.hasPerm(PERM.PERM_VIEW_USER_PRIVATE_INFO) ? { displayName: 1 } : {}),
264
+ },
265
+ },
266
+ ]).toArray(),
267
+ domain.getRoles(domainId),
268
+ ]);
269
+ const users = dudocs.map((dudoc) => {
270
+ const u = {
271
+ ...dudoc,
272
+ ...dudoc.user,
273
+ };
274
+ delete u.user;
275
+ return u;
276
+ });
277
+ const rudocs: Record<string, any[]> = {};
278
+ for (const role of roles) rudocs[role._id] = users.filter((udoc) => udoc.role === role._id);
279
+ this.response.template = format === 'raw' ? 'domain_user_raw.html' : 'domain_user.html';
280
+ this.response.body = {
281
+ roles, rudocs, domain: this.domain,
282
+ };
283
+ };
284
+ return DomainUserHandler;
285
+ });
286
+ // 添加国际化支持
287
+ ctx.i18n.load('zh', {
288
+ 'user_manage_main': '用户管理',
289
+ 'user_manage_detail': '用户详情',
290
+
291
+ 'User Management': '用户管理',
292
+ 'User List': '用户列表',
293
+ 'Search Users': '搜索用户',
294
+ 'Search by': '搜索方式',
295
+ 'Username': '用户名',
296
+ 'Email': '邮箱',
297
+ 'User ID': '用户ID',
298
+ 'Keyword': '关键词',
299
+ 'Sort by': '排序方式',
300
+ 'Registration Time': '注册时间',
301
+ 'Last Login': '最后登录',
302
+ 'Privilege': '权限',
303
+ 'Order': '顺序',
304
+ 'Ascending': '升序',
305
+ 'Descending': '降序',
306
+ 'Search': '搜索',
307
+ 'Clear': '清空',
308
+ 'Refresh': '刷新',
309
+
310
+ 'Normal User': '普通用户',
311
+ 'Admin': '管理员',
312
+ 'Banned': '已封禁',
313
+ 'Super Admin': '超级管理员',
314
+ 'Active': '活跃',
315
+ 'Inactive': '不活跃',
316
+ 'Actions': '操作',
317
+ 'View': '查看',
318
+ 'Edit': '编辑',
319
+ 'Ban': '封禁',
320
+ 'Unban': '解封',
321
+ 'Set Privilege': '设置权限',
322
+ 'Status': '状态',
323
+ 'School': '学校',
324
+ 'Bio': '个人简介',
325
+ 'Never': '从未',
326
+ 'Not set': '未设置',
327
+ 'Previous': '上一页',
328
+ 'Next': '下一页',
329
+ 'Page': '',
330
+ 'of': '',
331
+ 'users': '用户',
332
+ 'Total': '总计',
333
+ 'Showing': '显示',
334
+ 'to': '',
335
+ 'User Details': '用户详情',
336
+ 'Basic Information': '基本信息',
337
+ 'User Statistics': '用户统计',
338
+ 'Privilege Management': '权限管理',
339
+ 'Password Management': '密码管理',
340
+ 'User Status': '用户状态',
341
+ 'Back to List': '返回列表',
342
+ 'Save Changes': '保存更改',
343
+ 'Cancel': '取消',
344
+ 'Reset Password': '重置密码',
345
+ 'Current Privilege': '当前权限',
346
+ 'Ban User': '封禁用户',
347
+ 'Unban User': '解封用户',
348
+ 'Copy User ID': '复制用户ID'
349
+ });
350
+
351
+ // 添加国际化支持
352
+ ctx.i18n.load('zh_TW', {
353
+ 'user_manage_main': '用戶管理',
354
+ 'user_manage_detail': '用戶詳情',
355
+
356
+ 'User Management': '用戶管理',
357
+ 'User List': '用戶列表',
358
+ 'Search Users': '搜尋用戶',
359
+ 'Search by': '搜尋方式',
360
+ 'Username': '用戶名',
361
+ 'Email': '電子郵件',
362
+ 'User ID': '用戶ID',
363
+ 'Keyword': '關鍵字',
364
+ 'Sort by': '排序方式',
365
+ 'Registration Time': '註冊時間',
366
+ 'Last Login': '最後登入',
367
+ 'Privilege': '權限',
368
+ 'Order': '順序',
369
+ 'Ascending': '升序',
370
+ 'Descending': '降序',
371
+ 'Search': '搜尋',
372
+ 'Clear': '清除',
373
+ 'Refresh': '刷新',
374
+
375
+ 'Normal User': '普通用戶',
376
+ 'Admin': '管理員',
377
+ 'Banned': '已封禁',
378
+ 'Super Admin': '超級管理員',
379
+ 'Active': '活躍',
380
+ 'Inactive': '不活躍',
381
+ 'Actions': '操作',
382
+ 'View': '查看',
383
+ 'Edit': '編輯',
384
+ 'Ban': '封禁',
385
+ 'Unban': '解封',
386
+ 'Set Privilege': '設置權限',
387
+ 'Status': '狀態',
388
+ 'School': '學校',
389
+ 'Bio': '個人簡介',
390
+ 'Never': '從未',
391
+ 'Not set': '未設置',
392
+ 'Previous': '上一頁',
393
+ 'Next': '下一頁',
394
+ 'Page': '頁',
395
+ 'of': '共',
396
+ 'users': '用戶',
397
+ 'Total': '總計',
398
+ 'Showing': '顯示',
399
+ 'to': '到',
400
+ 'User Details': '用戶詳情',
401
+ 'Basic Information': '基本資訊',
402
+ 'User Statistics': '用戶統計',
403
+ 'Privilege Management': '權限管理',
404
+ 'Password Management': '密碼管理',
405
+ 'User Status': '用戶狀態',
406
+ 'Back to List': '返回列表',
407
+ 'Save Changes': '保存更改',
408
+ 'Cancel': '取消',
409
+ 'Reset Password': '重設密碼',
410
+ 'Current Privilege': '當前權限',
411
+ 'Ban User': '封禁用戶',
412
+ 'Unban User': '解封用戶',
413
+ 'Copy User ID': '複製用戶ID'
414
+ });
415
+
416
+ ctx.i18n.load('en', {
417
+ 'user_manage_main': 'User Management',
418
+ 'user_manage_detail': 'User Detail',
419
+ 'user_manage_batch': 'Batch Operations',
420
+ 'User Management': 'User Management',
421
+ 'User List': 'User List',
422
+ 'Search Users': 'Search Users',
423
+ 'Search by': 'Search by',
424
+ 'Username': 'Username',
425
+ 'Email': 'Email',
426
+ 'User ID': 'User ID',
427
+ 'Keyword': 'Keyword',
428
+ 'Sort by': 'Sort by',
429
+ 'Registration Time': 'Registration Time',
430
+ 'Last Login': 'Last Login',
431
+ 'Privilege': 'Privilege',
432
+ 'Order': 'Order',
433
+ 'Ascending': 'Ascending',
434
+ 'Descending': 'Descending',
435
+ 'Search': 'Search',
436
+ 'Clear': 'Clear',
437
+ 'Refresh': 'Refresh',
438
+ 'Batch Operations': 'Batch Operations',
439
+ 'Export Users': 'Export Users',
440
+ 'Normal User': 'Normal User',
441
+ 'Admin': 'Admin',
442
+ 'Banned': 'Banned',
443
+ 'Super Admin': 'Super Admin',
444
+ 'Active': 'Active',
445
+ 'Inactive': 'Inactive',
446
+ 'Actions': 'Actions',
447
+ 'View': 'View',
448
+ 'Edit': 'Edit',
449
+ 'Ban': 'Ban',
450
+ 'Unban': 'Unban',
451
+ 'Set Privilege': 'Set Privilege',
452
+ 'Status': 'Status',
453
+ 'School': 'School',
454
+ 'Bio': 'Bio',
455
+ 'Never': 'Never',
456
+ 'Not set': 'Not set',
457
+ 'Previous': 'Previous',
458
+ 'Next': 'Next',
459
+ 'Page': 'Page',
460
+ 'of': 'of',
461
+ 'users': 'users',
462
+ 'Total': 'Total',
463
+ 'Showing': 'Showing',
464
+ 'to': 'to',
465
+ 'User Details': 'User Details',
466
+ 'Basic Information': 'Basic Information',
467
+ 'User Statistics': 'User Statistics',
468
+ 'Privilege Management': 'Privilege Management',
469
+ 'Password Management': 'Password Management',
470
+ 'User Status': 'User Status',
471
+ 'Back to List': 'Back to List',
472
+ 'Save Changes': 'Save Changes',
473
+ 'Cancel': 'Cancel',
474
+ 'Reset Password': 'Reset Password',
475
+ 'Current Privilege': 'Current Privilege',
476
+ 'Ban User': 'Ban User',
477
+ 'Unban User': 'Unban User',
478
+ 'Copy User ID': 'Copy User ID'
479
+ });
342
480
  }