@ddj-v2/user-management 2.2.0 → 2.3.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/README.md +0 -8
- package/index.ts +4 -6
- package/package.json +1 -1
- package/templates/user_manage_detail.html +0 -7
- package/asset/fig1.png +0 -0
- package/asset/fig2.png +0 -0
- package/pages/user_manage_detail.page.js +0 -268
- package/pages/user_manage_main.page.js +0 -200
package/README.md
CHANGED
|
@@ -67,14 +67,6 @@ pm2 restart hydrooj
|
|
|
67
67
|
|
|
68
68
|
MIT License
|
|
69
69
|
|
|
70
|
-
## 支援
|
|
71
|
-
|
|
72
|
-
若您在使用過程中遇到問題,請:
|
|
73
|
-
|
|
74
|
-
1. 查看 [Issues](https://github.com/SummerofOrange/hydrooj-user-management/issues) 頁面
|
|
75
|
-
2. 提交新的 Issue 描述您的問題
|
|
76
|
-
3. 聯絡作者取得技術支援
|
|
77
|
-
|
|
78
70
|
---
|
|
79
71
|
|
|
80
72
|
**注意**:此插件需 HydroOJ v5.0.0-beta.6 或更高版本。使用前請確保您有足夠的系統管理權限。
|
package/index.ts
CHANGED
|
@@ -100,11 +100,11 @@ class UserManageDetailHandler extends UserManageHandler {
|
|
|
100
100
|
if (!udoc) throw new UserNotFoundError(uid);
|
|
101
101
|
|
|
102
102
|
if (operation === 'edit') {
|
|
103
|
-
await this.postEdit(domainId, uid);
|
|
103
|
+
await this.postEdit(domainId, uid, this.args.mail, this.args.uname, this.args.bio);
|
|
104
104
|
} else if (operation === 'resetPassword') {
|
|
105
|
-
await this.postResetPassword(domainId, uid);
|
|
105
|
+
await this.postResetPassword(domainId, uid, this.args.password);
|
|
106
106
|
} else if (operation === 'setPriv') {
|
|
107
|
-
await this.postSetPriv(domainId, uid);
|
|
107
|
+
await this.postSetPriv(domainId, uid, this.args.priv);
|
|
108
108
|
} else if (operation === 'ban') {
|
|
109
109
|
await this.postBan(domainId, uid);
|
|
110
110
|
} else if (operation === 'unban') {
|
|
@@ -117,9 +117,8 @@ class UserManageDetailHandler extends UserManageHandler {
|
|
|
117
117
|
@param('uid', Types.Int)
|
|
118
118
|
@param('mail', Types.Email, true)
|
|
119
119
|
@param('uname', Types.Username, true)
|
|
120
|
-
@param('school', Types.String, true)
|
|
121
120
|
@param('bio', Types.Content, true)
|
|
122
|
-
async postEdit(domainId: string, uid: number, mail?: string, uname?: string,
|
|
121
|
+
async postEdit(domainId: string, uid: number, mail?: string, uname?: string, bio?: string) {
|
|
123
122
|
const udoc = await UserModel.getById(domainId, uid);
|
|
124
123
|
if (!udoc) throw new UserNotFoundError(uid);
|
|
125
124
|
|
|
@@ -142,7 +141,6 @@ class UserManageDetailHandler extends UserManageHandler {
|
|
|
142
141
|
}
|
|
143
142
|
|
|
144
143
|
const updates: any = {};
|
|
145
|
-
if (school !== undefined) updates.school = school;
|
|
146
144
|
if (bio !== undefined) updates.bio = bio;
|
|
147
145
|
|
|
148
146
|
if (Object.keys(updates).length > 0) {
|
package/package.json
CHANGED
|
@@ -50,13 +50,6 @@
|
|
|
50
50
|
</div>
|
|
51
51
|
</div>
|
|
52
52
|
<div class="row">
|
|
53
|
-
<div class="medium-6 columns">
|
|
54
|
-
{{ form.form_text({
|
|
55
|
-
label: _('School'),
|
|
56
|
-
name: 'school',
|
|
57
|
-
value: udoc.school or ''
|
|
58
|
-
}) }}
|
|
59
|
-
</div>
|
|
60
53
|
<div class="medium-6 columns">
|
|
61
54
|
{{ form.form_text({
|
|
62
55
|
label: _('Registration Time'),
|
package/asset/fig1.png
DELETED
|
Binary file
|
package/asset/fig2.png
DELETED
|
Binary file
|
|
@@ -1,268 +0,0 @@
|
|
|
1
|
-
import $ from 'jquery';
|
|
2
|
-
import { NamedPage } from 'vj/misc/Page';
|
|
3
|
-
import Notification from 'vj/components/notification';
|
|
4
|
-
import { request } from 'vj/utils';
|
|
5
|
-
import i18n from 'vj/utils/i18n';
|
|
6
|
-
import ActionDialog from 'vj/components/dialog/ActionDialog';
|
|
7
|
-
|
|
8
|
-
const page = new NamedPage('user_manage_detail', () => {
|
|
9
|
-
const uid = window.location.pathname.split('/').pop();
|
|
10
|
-
|
|
11
|
-
// 编辑用户信息
|
|
12
|
-
$('#edit-user-form').on('submit', async function(e) {
|
|
13
|
-
e.preventDefault();
|
|
14
|
-
|
|
15
|
-
const formData = {
|
|
16
|
-
operation: 'updateInfo',
|
|
17
|
-
uname: $('#uname').val().trim(),
|
|
18
|
-
email: $('#email').val().trim(),
|
|
19
|
-
school: $('#school').val().trim(),
|
|
20
|
-
bio: $('#bio').val().trim()
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
// 验证用户名
|
|
24
|
-
if (!formData.uname) {
|
|
25
|
-
Notification.error(i18n('Username cannot be empty'));
|
|
26
|
-
return;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
// 验证邮箱
|
|
30
|
-
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
31
|
-
if (!emailRegex.test(formData.email)) {
|
|
32
|
-
Notification.error(i18n('Invalid email format'));
|
|
33
|
-
return;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
try {
|
|
37
|
-
const response = await request.post(`/manage/users/${uid}`, formData);
|
|
38
|
-
|
|
39
|
-
if (response.success) {
|
|
40
|
-
Notification.success(i18n('User information updated successfully'));
|
|
41
|
-
// 更新页面显示
|
|
42
|
-
$('#display-uname').text(formData.uname);
|
|
43
|
-
$('#display-email').text(formData.email);
|
|
44
|
-
$('#display-school').text(formData.school || i18n('Not set'));
|
|
45
|
-
$('#display-bio').text(formData.bio || i18n('Not set'));
|
|
46
|
-
} else {
|
|
47
|
-
Notification.error(response.message || i18n('Update failed'));
|
|
48
|
-
}
|
|
49
|
-
} catch (error) {
|
|
50
|
-
console.error('Error:', error);
|
|
51
|
-
Notification.error(i18n('Update failed'));
|
|
52
|
-
}
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
// 重置密码
|
|
56
|
-
$('#reset-password').on('click', async function() {
|
|
57
|
-
const username = $('#display-uname').text();
|
|
58
|
-
|
|
59
|
-
if (!confirm(i18n('Are you sure to reset password for user {0}? A new random password will be generated.', username))) {
|
|
60
|
-
return;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
try {
|
|
64
|
-
const response = await request.post(`/manage/users/${uid}`, {
|
|
65
|
-
operation: 'resetPassword'
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
if (response.success) {
|
|
69
|
-
// 显示新密码
|
|
70
|
-
const dialog = new ActionDialog({
|
|
71
|
-
$body: $(`
|
|
72
|
-
<div class="typo">
|
|
73
|
-
<h3>${i18n('Password Reset Successfully')}</h3>
|
|
74
|
-
<p>${i18n('New password for user {0}:', username)}</p>
|
|
75
|
-
<div class="password-display">
|
|
76
|
-
<input type="text" value="${response.newPassword}" readonly style="width: 100%; font-family: monospace; font-size: 14px; padding: 8px; border: 1px solid #ccc; border-radius: 4px;">
|
|
77
|
-
</div>
|
|
78
|
-
<p class="text-red">${i18n('Please save this password and inform the user. This password will not be shown again.')}</p>
|
|
79
|
-
<button class="button primary" onclick="navigator.clipboard.writeText('${response.newPassword}'); $(this).text('${i18n('Copied!')}')">${i18n('Copy Password')}</button>
|
|
80
|
-
</div>
|
|
81
|
-
`),
|
|
82
|
-
canCancel: false
|
|
83
|
-
});
|
|
84
|
-
dialog.open();
|
|
85
|
-
} else {
|
|
86
|
-
Notification.error(response.message || i18n('Password reset failed'));
|
|
87
|
-
}
|
|
88
|
-
} catch (error) {
|
|
89
|
-
console.error('Error:', error);
|
|
90
|
-
Notification.error(i18n('Password reset failed'));
|
|
91
|
-
}
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
// 设置权限
|
|
95
|
-
$('#set-privilege').on('click', async function() {
|
|
96
|
-
const username = $('#display-uname').text();
|
|
97
|
-
const currentPriv = $(this).data('current-priv');
|
|
98
|
-
|
|
99
|
-
const newPriv = prompt(i18n('Enter new privilege value for user {0}:', username), currentPriv);
|
|
100
|
-
if (newPriv === null || newPriv === '') {
|
|
101
|
-
return;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
const privValue = parseInt(newPriv, 10);
|
|
105
|
-
if (isNaN(privValue)) {
|
|
106
|
-
Notification.error(i18n('Invalid privilege value'));
|
|
107
|
-
return;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
if (!confirm(i18n('Are you sure to set privilege of user {0} to {1}?', username, privValue))) {
|
|
111
|
-
return;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
try {
|
|
115
|
-
const response = await request.post(`/manage/users/${uid}`, {
|
|
116
|
-
operation: 'setPriv',
|
|
117
|
-
priv: privValue
|
|
118
|
-
});
|
|
119
|
-
|
|
120
|
-
if (response.success) {
|
|
121
|
-
Notification.success(i18n('Privilege updated successfully'));
|
|
122
|
-
// 更新页面显示
|
|
123
|
-
$('#current-privilege').text(privValue);
|
|
124
|
-
$(this).data('current-priv', privValue);
|
|
125
|
-
|
|
126
|
-
// 更新权限徽章
|
|
127
|
-
const $badge = $('#privilege-badge');
|
|
128
|
-
$badge.removeClass('success warning alert');
|
|
129
|
-
if (privValue === 0) {
|
|
130
|
-
$badge.addClass('alert').text(i18n('Banned'));
|
|
131
|
-
} else if (privValue === 1) {
|
|
132
|
-
$badge.addClass('success').text(i18n('Normal User'));
|
|
133
|
-
} else {
|
|
134
|
-
$badge.addClass('warning').text(i18n('Admin'));
|
|
135
|
-
}
|
|
136
|
-
} else {
|
|
137
|
-
Notification.error(response.message || i18n('Privilege update failed'));
|
|
138
|
-
}
|
|
139
|
-
} catch (error) {
|
|
140
|
-
console.error('Error:', error);
|
|
141
|
-
Notification.error(i18n('Privilege update failed'));
|
|
142
|
-
}
|
|
143
|
-
});
|
|
144
|
-
|
|
145
|
-
// 封禁/解封用户
|
|
146
|
-
$('#ban-user, #unban-user').on('click', async function() {
|
|
147
|
-
const username = $('#display-uname').text();
|
|
148
|
-
const action = $(this).attr('id') === 'ban-user' ? 'ban' : 'unban';
|
|
149
|
-
|
|
150
|
-
const confirmMessage = action === 'ban'
|
|
151
|
-
? i18n('Are you sure to ban user {0}? This will set their privilege to 0.', username)
|
|
152
|
-
: i18n('Are you sure to unban user {0}? This will restore their privilege to 1.', username);
|
|
153
|
-
|
|
154
|
-
if (!confirm(confirmMessage)) {
|
|
155
|
-
return;
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
try {
|
|
159
|
-
const response = await request.post(`/manage/users/${uid}`, {
|
|
160
|
-
operation: action
|
|
161
|
-
});
|
|
162
|
-
|
|
163
|
-
if (response.success) {
|
|
164
|
-
Notification.success(action === 'ban' ? i18n('User banned successfully') : i18n('User unbanned successfully'));
|
|
165
|
-
|
|
166
|
-
// 更新页面显示
|
|
167
|
-
const newPriv = action === 'ban' ? 0 : 1;
|
|
168
|
-
$('#current-privilege').text(newPriv);
|
|
169
|
-
$('#set-privilege').data('current-priv', newPriv);
|
|
170
|
-
|
|
171
|
-
// 更新权限徽章
|
|
172
|
-
const $badge = $('#privilege-badge');
|
|
173
|
-
$badge.removeClass('success warning alert');
|
|
174
|
-
if (action === 'ban') {
|
|
175
|
-
$badge.addClass('alert').text(i18n('Banned'));
|
|
176
|
-
$('#ban-user').hide();
|
|
177
|
-
$('#unban-user').show();
|
|
178
|
-
} else {
|
|
179
|
-
$badge.addClass('success').text(i18n('Normal User'));
|
|
180
|
-
$('#ban-user').show();
|
|
181
|
-
$('#unban-user').hide();
|
|
182
|
-
}
|
|
183
|
-
} else {
|
|
184
|
-
Notification.error(response.message || i18n('Operation failed'));
|
|
185
|
-
}
|
|
186
|
-
} catch (error) {
|
|
187
|
-
console.error('Error:', error);
|
|
188
|
-
Notification.error(i18n('Operation failed'));
|
|
189
|
-
}
|
|
190
|
-
});
|
|
191
|
-
|
|
192
|
-
// 返回用户列表
|
|
193
|
-
$('#back-to-list').on('click', function() {
|
|
194
|
-
window.location.href = '/manage/users';
|
|
195
|
-
});
|
|
196
|
-
|
|
197
|
-
// 刷新用户信息
|
|
198
|
-
$('#refresh-info').on('click', function() {
|
|
199
|
-
window.location.reload();
|
|
200
|
-
});
|
|
201
|
-
|
|
202
|
-
// 切换编辑模式
|
|
203
|
-
$('#toggle-edit').on('click', function() {
|
|
204
|
-
const $form = $('#edit-user-form');
|
|
205
|
-
const $display = $('#user-info-display');
|
|
206
|
-
|
|
207
|
-
if ($form.is(':visible')) {
|
|
208
|
-
$form.hide();
|
|
209
|
-
$display.show();
|
|
210
|
-
$(this).text(i18n('Edit'));
|
|
211
|
-
} else {
|
|
212
|
-
$form.show();
|
|
213
|
-
$display.hide();
|
|
214
|
-
$(this).text(i18n('Cancel'));
|
|
215
|
-
}
|
|
216
|
-
});
|
|
217
|
-
|
|
218
|
-
// 复制用户ID
|
|
219
|
-
$('#copy-uid').on('click', function() {
|
|
220
|
-
const uid = $(this).data('uid');
|
|
221
|
-
navigator.clipboard.writeText(uid).then(() => {
|
|
222
|
-
Notification.success(i18n('User ID copied to clipboard'));
|
|
223
|
-
}).catch(() => {
|
|
224
|
-
Notification.error(i18n('Failed to copy user ID'));
|
|
225
|
-
});
|
|
226
|
-
});
|
|
227
|
-
|
|
228
|
-
// 键盘快捷键
|
|
229
|
-
$(document).on('keydown', function(e) {
|
|
230
|
-
// Esc 键取消编辑
|
|
231
|
-
if (e.key === 'Escape' && $('#edit-user-form').is(':visible')) {
|
|
232
|
-
$('#toggle-edit').click();
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
// Ctrl+S 保存编辑
|
|
236
|
-
if (e.ctrlKey && e.key === 's' && $('#edit-user-form').is(':visible')) {
|
|
237
|
-
e.preventDefault();
|
|
238
|
-
$('#edit-user-form').submit();
|
|
239
|
-
}
|
|
240
|
-
});
|
|
241
|
-
|
|
242
|
-
// 初始化提示
|
|
243
|
-
$('[data-tooltip]').each(function() {
|
|
244
|
-
$(this).attr('title', $(this).data('tooltip'));
|
|
245
|
-
});
|
|
246
|
-
|
|
247
|
-
// 表单验证
|
|
248
|
-
$('#uname').on('input', function() {
|
|
249
|
-
const value = $(this).val().trim();
|
|
250
|
-
if (value.length < 3) {
|
|
251
|
-
$(this).addClass('error');
|
|
252
|
-
} else {
|
|
253
|
-
$(this).removeClass('error');
|
|
254
|
-
}
|
|
255
|
-
});
|
|
256
|
-
|
|
257
|
-
$('#email').on('input', function() {
|
|
258
|
-
const value = $(this).val().trim();
|
|
259
|
-
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
260
|
-
if (!emailRegex.test(value)) {
|
|
261
|
-
$(this).addClass('error');
|
|
262
|
-
} else {
|
|
263
|
-
$(this).removeClass('error');
|
|
264
|
-
}
|
|
265
|
-
});
|
|
266
|
-
});
|
|
267
|
-
|
|
268
|
-
export default page;
|
|
@@ -1,200 +0,0 @@
|
|
|
1
|
-
import $ from 'jquery';
|
|
2
|
-
import { NamedPage } from 'vj/misc/Page';
|
|
3
|
-
import Notification from 'vj/components/notification';
|
|
4
|
-
import { request } from 'vj/utils';
|
|
5
|
-
import i18n from 'vj/utils/i18n';
|
|
6
|
-
import pjax from 'vj/utils/pjax';
|
|
7
|
-
|
|
8
|
-
const page = new NamedPage('user_manage_main', () => {
|
|
9
|
-
// 搜索功能
|
|
10
|
-
$('#search-form').on('submit', function(e) {
|
|
11
|
-
e.preventDefault();
|
|
12
|
-
const keyword = $('#search-keyword').val().trim();
|
|
13
|
-
const type = $('#search-type').val();
|
|
14
|
-
const sort = $('#sort-by').val();
|
|
15
|
-
const order = $('#sort-order').val();
|
|
16
|
-
|
|
17
|
-
let url = window.location.pathname + '?';
|
|
18
|
-
const params = [];
|
|
19
|
-
|
|
20
|
-
if (keyword) {
|
|
21
|
-
params.push(`${type}=${encodeURIComponent(keyword)}`);
|
|
22
|
-
}
|
|
23
|
-
if (sort) {
|
|
24
|
-
params.push(`sort=${sort}`);
|
|
25
|
-
}
|
|
26
|
-
if (order) {
|
|
27
|
-
params.push(`order=${order}`);
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
url += params.join('&');
|
|
31
|
-
pjax.request({ url });
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
// 清空搜索
|
|
35
|
-
$('#clear-search').on('click', function() {
|
|
36
|
-
$('#search-keyword').val('');
|
|
37
|
-
$('#search-type').val('uname');
|
|
38
|
-
$('#sort-by').val('_id');
|
|
39
|
-
$('#sort-order').val('desc');
|
|
40
|
-
pjax.request({ url: window.location.pathname });
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
// 快速封禁/解封用户
|
|
44
|
-
$('.ban-user').on('click', async function(e) {
|
|
45
|
-
e.preventDefault();
|
|
46
|
-
const uid = $(this).data('uid');
|
|
47
|
-
const username = $(this).data('username');
|
|
48
|
-
const action = $(this).data('action');
|
|
49
|
-
|
|
50
|
-
const confirmMessage = action === 'ban'
|
|
51
|
-
? i18n('Are you sure to ban user {0}?', username)
|
|
52
|
-
: i18n('Are you sure to unban user {0}?', username);
|
|
53
|
-
|
|
54
|
-
if (!confirm(confirmMessage)) {
|
|
55
|
-
return;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
try {
|
|
59
|
-
const response = await request.post(`/manage/users/${uid}`, {
|
|
60
|
-
operation: action === 'ban' ? 'ban' : 'unban'
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
if (response.success) {
|
|
64
|
-
Notification.success(action === 'ban' ? i18n('User banned successfully') : i18n('User unbanned successfully'));
|
|
65
|
-
// 刷新页面
|
|
66
|
-
window.location.reload();
|
|
67
|
-
} else {
|
|
68
|
-
Notification.error(response.message || i18n('Operation failed'));
|
|
69
|
-
}
|
|
70
|
-
} catch (error) {
|
|
71
|
-
console.error('Error:', error);
|
|
72
|
-
Notification.error(i18n('Operation failed'));
|
|
73
|
-
}
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
// 快速设置权限
|
|
77
|
-
$('.set-priv').on('click', async function(e) {
|
|
78
|
-
e.preventDefault();
|
|
79
|
-
const uid = $(this).data('uid');
|
|
80
|
-
const username = $(this).data('username');
|
|
81
|
-
const currentPriv = $(this).data('priv');
|
|
82
|
-
|
|
83
|
-
const newPriv = prompt(i18n('Enter new privilege value for user {0}:', username), currentPriv);
|
|
84
|
-
if (newPriv === null || newPriv === '') {
|
|
85
|
-
return;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
const privValue = parseInt(newPriv, 10);
|
|
89
|
-
if (isNaN(privValue)) {
|
|
90
|
-
Notification.error(i18n('Invalid privilege value'));
|
|
91
|
-
return;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
if (!confirm(i18n('Are you sure to set privilege of user {0} to {1}?', username, privValue))) {
|
|
95
|
-
return;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
try {
|
|
99
|
-
const response = await request.post(`/manage/users/${uid}`, {
|
|
100
|
-
operation: 'setPriv',
|
|
101
|
-
priv: privValue
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
if (response.success) {
|
|
105
|
-
Notification.success(i18n('Privilege updated successfully'));
|
|
106
|
-
// 刷新页面
|
|
107
|
-
window.location.reload();
|
|
108
|
-
} else {
|
|
109
|
-
Notification.error(response.message || i18n('Operation failed'));
|
|
110
|
-
}
|
|
111
|
-
} catch (error) {
|
|
112
|
-
console.error('Error:', error);
|
|
113
|
-
Notification.error(i18n('Operation failed'));
|
|
114
|
-
}
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
// 分页链接处理
|
|
118
|
-
$('.pagination a').on('click', function(e) {
|
|
119
|
-
e.preventDefault();
|
|
120
|
-
const url = $(this).attr('href');
|
|
121
|
-
if (url && url !== '#') {
|
|
122
|
-
pjax.request({ url });
|
|
123
|
-
}
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
// 排序链接处理
|
|
127
|
-
$('.sortable').on('click', function(e) {
|
|
128
|
-
e.preventDefault();
|
|
129
|
-
const sort = $(this).data('sort');
|
|
130
|
-
const currentSort = new URLSearchParams(window.location.search).get('sort');
|
|
131
|
-
const currentOrder = new URLSearchParams(window.location.search).get('order') || 'desc';
|
|
132
|
-
|
|
133
|
-
let newOrder = 'desc';
|
|
134
|
-
if (currentSort === sort && currentOrder === 'desc') {
|
|
135
|
-
newOrder = 'asc';
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
const url = new URL(window.location);
|
|
139
|
-
url.searchParams.set('sort', sort);
|
|
140
|
-
url.searchParams.set('order', newOrder);
|
|
141
|
-
|
|
142
|
-
pjax.request({ url: url.toString() });
|
|
143
|
-
});
|
|
144
|
-
|
|
145
|
-
// 批量操作按钮
|
|
146
|
-
$('#batch-operations').on('click', function() {
|
|
147
|
-
window.location.href = '/manage/users/batch';
|
|
148
|
-
});
|
|
149
|
-
|
|
150
|
-
// 导出用户数据
|
|
151
|
-
$('#export-users').on('click', async function() {
|
|
152
|
-
try {
|
|
153
|
-
const response = await request.get('/manage/users', {
|
|
154
|
-
export: 'csv'
|
|
155
|
-
});
|
|
156
|
-
|
|
157
|
-
// 创建下载链接
|
|
158
|
-
const blob = new Blob([response], { type: 'text/csv' });
|
|
159
|
-
const url = window.URL.createObjectURL(blob);
|
|
160
|
-
const a = document.createElement('a');
|
|
161
|
-
a.href = url;
|
|
162
|
-
a.download = `users_${new Date().toISOString().split('T')[0]}.csv`;
|
|
163
|
-
document.body.appendChild(a);
|
|
164
|
-
a.click();
|
|
165
|
-
document.body.removeChild(a);
|
|
166
|
-
window.URL.revokeObjectURL(url);
|
|
167
|
-
|
|
168
|
-
Notification.success(i18n('Export completed'));
|
|
169
|
-
} catch (error) {
|
|
170
|
-
console.error('Export error:', error);
|
|
171
|
-
Notification.error(i18n('Export failed'));
|
|
172
|
-
}
|
|
173
|
-
});
|
|
174
|
-
|
|
175
|
-
// 刷新按钮
|
|
176
|
-
$('#refresh-list').on('click', function() {
|
|
177
|
-
window.location.reload();
|
|
178
|
-
});
|
|
179
|
-
|
|
180
|
-
// 键盘快捷键
|
|
181
|
-
$(document).on('keydown', function(e) {
|
|
182
|
-
// Ctrl+F 聚焦搜索框
|
|
183
|
-
if (e.ctrlKey && e.key === 'f') {
|
|
184
|
-
e.preventDefault();
|
|
185
|
-
$('#search-keyword').focus();
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
// Enter 键提交搜索
|
|
189
|
-
if (e.key === 'Enter' && $('#search-keyword').is(':focus')) {
|
|
190
|
-
$('#search-form').submit();
|
|
191
|
-
}
|
|
192
|
-
});
|
|
193
|
-
|
|
194
|
-
// 初始化提示
|
|
195
|
-
$('[data-tooltip]').each(function() {
|
|
196
|
-
$(this).attr('title', $(this).data('tooltip'));
|
|
197
|
-
});
|
|
198
|
-
});
|
|
199
|
-
|
|
200
|
-
export default page;
|