collabdocchat 2.0.4 → 2.0.5
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/package.json +1 -1
- package/scripts/generate-docs.js +1 -0
- package/scripts/pre-publish-check.js +1 -0
- package/src/components/knowledge-modal.js +15 -17
- package/src/components/optimized-poll-detail.js +17 -20
- package/src/main.js +1 -1
- package/src/pages/admin-dashboard.js +281 -324
- package/src/pages/login.js +7 -7
- package/src/pages/optimized-backup-view.js +32 -35
- package/src/pages/optimized-knowledge-view.js +34 -41
- package/src/pages/optimized-task-detail.js +36 -42
- package/src/pages/optimized-workflow-view.js +64 -81
- package/src/pages/simplified-workflows.js +66 -66
- package/src/pages/user-dashboard.js +95 -111
- package/src/services/api.js +3 -6
- package/src/services/websocket.js +11 -14
- package/src/styles/collaboration-modern.js +8 -10
- package/src/utils/ai-assistant.js +74 -89
- package/src/utils/chat-enhancements.js +18 -18
- package/src/utils/collaboration-enhancer.js +84 -126
- package/src/utils/feature-integrator.js +78 -93
- package/src/utils/onboarding-guide.js +82 -93
- package/src/utils/performance.js +9 -9
- package/src/utils/permission-manager.js +39 -58
- package/src/utils/responsive-handler.js +22 -22
- package/src/utils/theme-manager.js +37 -49
- package/src/utils/ui-enhancements-loader.js +30 -40
|
@@ -28,14 +28,14 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
28
28
|
<aside class="sidebar">
|
|
29
29
|
<div class="sidebar-header">
|
|
30
30
|
<h2>CollabDocChat</h2>
|
|
31
|
-
<span class="badge-admin"
|
|
31
|
+
<span class="badge-admin">管理�?/span>
|
|
32
32
|
</div>
|
|
33
33
|
|
|
34
34
|
<div class="user-info">
|
|
35
35
|
<div class="avatar">${user.username[0].toUpperCase()}</div>
|
|
36
36
|
<div>
|
|
37
37
|
<div class="username">${user.username}</div>
|
|
38
|
-
<div class="user-role"
|
|
38
|
+
<div class="user-role">管理�?/div>
|
|
39
39
|
</div>
|
|
40
40
|
</div>
|
|
41
41
|
|
|
@@ -50,11 +50,9 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
50
50
|
<span class="icon">📄</span> 文档管理
|
|
51
51
|
</button>
|
|
52
52
|
<button class="nav-item" data-view="knowledge">
|
|
53
|
-
<span class="icon">📚</span>
|
|
54
|
-
</button>
|
|
53
|
+
<span class="icon">📚</span> 知识�? </button>
|
|
55
54
|
<button class="nav-item" data-view="workflows">
|
|
56
|
-
<span class="icon">🔄</span>
|
|
57
|
-
</button>
|
|
55
|
+
<span class="icon">🔄</span> 工作�? </button>
|
|
58
56
|
<button class="nav-item" data-view="files">
|
|
59
57
|
<span class="icon">📎</span> 文件管理
|
|
60
58
|
</button>
|
|
@@ -80,11 +78,11 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
80
78
|
<span class="icon">⚙️</span> 设置
|
|
81
79
|
</button>
|
|
82
80
|
<button class="nav-item" data-view="help">
|
|
83
|
-
<span class="icon"
|
|
81
|
+
<span class="icon">�?/span> 帮助
|
|
84
82
|
</button>
|
|
85
83
|
</div>
|
|
86
84
|
|
|
87
|
-
<button class="btn-logout" id="logoutBtn"
|
|
85
|
+
<button class="btn-logout" id="logoutBtn">退出登�?/button>
|
|
88
86
|
</aside>
|
|
89
87
|
|
|
90
88
|
<main class="main-content">
|
|
@@ -103,8 +101,7 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
103
101
|
});
|
|
104
102
|
});
|
|
105
103
|
|
|
106
|
-
//
|
|
107
|
-
document.getElementById('logoutBtn').addEventListener('click', () => {
|
|
104
|
+
// 退出登�? document.getElementById('logoutBtn').addEventListener('click', () => {
|
|
108
105
|
authService.logout();
|
|
109
106
|
});
|
|
110
107
|
|
|
@@ -125,8 +122,7 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
125
122
|
await renderOptimizedKnowledgeView(contentArea, currentGroup, apiService);
|
|
126
123
|
break;
|
|
127
124
|
case 'workflows':
|
|
128
|
-
// 使用优化后的工作流视图(可视化创建,无需JSON
|
|
129
|
-
if (typeof window.renderOptimizedWorkflowView === 'function') {
|
|
125
|
+
// 使用优化后的工作流视图(可视化创建,无需JSON代码�? if (typeof window.renderOptimizedWorkflowView === 'function') {
|
|
130
126
|
await window.renderOptimizedWorkflowView(contentArea, currentGroup, apiService);
|
|
131
127
|
} else {
|
|
132
128
|
await renderWorkflowsView(contentArea);
|
|
@@ -171,11 +167,11 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
171
167
|
<div class="groups-grid" id="groupsList"></div>
|
|
172
168
|
<div id="createGroupModal" class="modal hidden">
|
|
173
169
|
<div class="modal-content">
|
|
174
|
-
<h3
|
|
170
|
+
<h3>创建新群�?/h3>
|
|
175
171
|
<form id="createGroupForm">
|
|
176
172
|
<div class="form-group">
|
|
177
173
|
<label>群组名称</label>
|
|
178
|
-
<input type="text" name="name" placeholder="
|
|
174
|
+
<input type="text" name="name" placeholder="请输入群组名�? required>
|
|
179
175
|
</div>
|
|
180
176
|
<div class="form-group">
|
|
181
177
|
<label>群组描述</label>
|
|
@@ -184,7 +180,7 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
184
180
|
<div class="form-group">
|
|
185
181
|
<label>添加成员(可选)</label>
|
|
186
182
|
<div id="usersList" style="max-height: 200px; overflow-y: auto; border: 1px solid var(--border); border-radius: 8px; padding: 10px;">
|
|
187
|
-
<p
|
|
183
|
+
<p>加载�?..</p>
|
|
188
184
|
</div>
|
|
189
185
|
</div>
|
|
190
186
|
<div style="display: flex; gap: 10px;">
|
|
@@ -199,7 +195,7 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
199
195
|
<h3>管理成员</h3>
|
|
200
196
|
<div id="currentMembers"></div>
|
|
201
197
|
<div class="form-group">
|
|
202
|
-
<label
|
|
198
|
+
<label>添加新成�?/label>
|
|
203
199
|
<div id="availableUsers"></div>
|
|
204
200
|
</div>
|
|
205
201
|
<button type="button" class="btn-secondary" id="closeMembersModal">关闭</button>
|
|
@@ -230,7 +226,7 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
230
226
|
btn.addEventListener('click', () => {
|
|
231
227
|
currentGroup = groups.find(g => g._id === btn.dataset.id);
|
|
232
228
|
wsService.joinGroup(currentGroup._id);
|
|
233
|
-
alert(
|
|
229
|
+
alert(`已加入群�? ${currentGroup.name}`);
|
|
234
230
|
});
|
|
235
231
|
});
|
|
236
232
|
|
|
@@ -265,7 +261,7 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
265
261
|
formData.get('description'),
|
|
266
262
|
selectedUsers
|
|
267
263
|
);
|
|
268
|
-
alert('
|
|
264
|
+
alert('群组创建成功�?);
|
|
269
265
|
await renderGroupsView(container);
|
|
270
266
|
document.getElementById('createGroupModal').classList.add('hidden');
|
|
271
267
|
} catch (error) {
|
|
@@ -283,7 +279,7 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
283
279
|
<label style="display: flex; align-items: center; gap: 10px; padding: 8px; cursor: pointer;">
|
|
284
280
|
<input type="checkbox" value="${u._id}">
|
|
285
281
|
<div class="avatar" style="width: 30px; height: 30px; font-size: 14px;">${u.username[0].toUpperCase()}</div>
|
|
286
|
-
<span>${u.username} (${u.role === 'admin' ? '
|
|
282
|
+
<span>${u.username} (${u.role === 'admin' ? '管理�? : '用户'})</span>
|
|
287
283
|
</label>
|
|
288
284
|
`).join('');
|
|
289
285
|
} catch (error) {
|
|
@@ -306,7 +302,7 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
306
302
|
<div style="display: flex; align-items: center; justify-content: space-between; padding: 8px; border-bottom: 1px solid var(--border);">
|
|
307
303
|
<div style="display: flex; align-items: center; gap: 10px;">
|
|
308
304
|
<div class="avatar" style="width: 30px; height: 30px; font-size: 14px;">${member.username[0].toUpperCase()}</div>
|
|
309
|
-
<span>${member.username} ${member._id.toString() === group.admin._id.toString() ? '(
|
|
305
|
+
<span>${member.username} ${member._id.toString() === group.admin._id.toString() ? '(管理�?' : ''}</span>
|
|
310
306
|
</div>
|
|
311
307
|
${member._id.toString() !== group.admin._id.toString() ?
|
|
312
308
|
`<button class="btn-secondary btn-sm" onclick="removeMember('${groupId}', '${member._id}')">移除</button>` :
|
|
@@ -321,7 +317,7 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
321
317
|
|
|
322
318
|
const availableUsersDiv = document.getElementById('availableUsers');
|
|
323
319
|
if (availableUsers.length === 0) {
|
|
324
|
-
availableUsersDiv.innerHTML = '<p
|
|
320
|
+
availableUsersDiv.innerHTML = '<p>所有用户都已在群组�?/p>';
|
|
325
321
|
} else {
|
|
326
322
|
availableUsersDiv.innerHTML = availableUsers.map(u => `
|
|
327
323
|
<div style="display: flex; align-items: center; justify-content: space-between; padding: 8px; border-bottom: 1px solid var(--border);">
|
|
@@ -341,11 +337,10 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
341
337
|
}
|
|
342
338
|
}
|
|
343
339
|
|
|
344
|
-
//
|
|
345
|
-
window.addMember = async (groupId, userId) => {
|
|
340
|
+
// 全局函数供按钮调�? window.addMember = async (groupId, userId) => {
|
|
346
341
|
try {
|
|
347
342
|
await apiService.addMember(groupId, userId);
|
|
348
|
-
alert('
|
|
343
|
+
alert('成员添加成功�?);
|
|
349
344
|
await showManageMembersModal(groupId);
|
|
350
345
|
} catch (error) {
|
|
351
346
|
alert('添加失败: ' + error.message);
|
|
@@ -356,7 +351,7 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
356
351
|
if (confirm('确定要移除该成员吗?')) {
|
|
357
352
|
try {
|
|
358
353
|
await apiService.removeMember(groupId, userId);
|
|
359
|
-
alert('
|
|
354
|
+
alert('成员移除成功�?);
|
|
360
355
|
await showManageMembersModal(groupId);
|
|
361
356
|
} catch (error) {
|
|
362
357
|
alert('移除失败: ' + error.message);
|
|
@@ -366,7 +361,7 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
366
361
|
|
|
367
362
|
async function renderTasksView(container) {
|
|
368
363
|
if (!currentGroup) {
|
|
369
|
-
container.innerHTML = '<div class="empty-state"
|
|
364
|
+
container.innerHTML = '<div class="empty-state">请先选择一个群�?/div>';
|
|
370
365
|
return;
|
|
371
366
|
}
|
|
372
367
|
|
|
@@ -380,15 +375,15 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
380
375
|
<div class="tasks-list" id="tasksList"></div>
|
|
381
376
|
<div id="createTaskModal" class="modal hidden">
|
|
382
377
|
<div class="modal-content">
|
|
383
|
-
<h3
|
|
378
|
+
<h3>创建新任�?/h3>
|
|
384
379
|
<form id="createTaskForm">
|
|
385
380
|
<div class="form-group">
|
|
386
381
|
<label>任务标题</label>
|
|
387
|
-
<input type="text" name="title" placeholder="
|
|
382
|
+
<input type="text" name="title" placeholder="请输入任务标�? required>
|
|
388
383
|
</div>
|
|
389
384
|
<div class="form-group">
|
|
390
385
|
<label>任务描述</label>
|
|
391
|
-
<textarea name="description" placeholder="
|
|
386
|
+
<textarea name="description" placeholder="请输入任务描�?></textarea>
|
|
392
387
|
</div>
|
|
393
388
|
<div class="form-group">
|
|
394
389
|
<label>截止日期</label>
|
|
@@ -408,7 +403,7 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
408
403
|
<button class="modal-close" id="closeTaskDetailModal">×</button>
|
|
409
404
|
</div>
|
|
410
405
|
<div class="modal-body" id="taskDetailContent">
|
|
411
|
-
<div class="loading"
|
|
406
|
+
<div class="loading">加载�?..</div>
|
|
412
407
|
</div>
|
|
413
408
|
</div>
|
|
414
409
|
</div>
|
|
@@ -431,16 +426,16 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
431
426
|
<div style="display: flex; justify-content: space-between; align-items: start;">
|
|
432
427
|
<div style="flex: 1;">
|
|
433
428
|
<h3>${task.title}</h3>
|
|
434
|
-
<p>${task.description || '
|
|
429
|
+
<p>${task.description || '无描�?}</p>
|
|
435
430
|
<div class="task-meta">
|
|
436
431
|
<span class="status-badge">${getStatusText(task.status)}</span>
|
|
437
|
-
<span>截止: ${task.deadline ? new Date(task.deadline).toLocaleDateString() : '
|
|
438
|
-
<span
|
|
432
|
+
<span>截止: ${task.deadline ? new Date(task.deadline).toLocaleDateString() : '�?}</span>
|
|
433
|
+
<span>完成�? ${completionRate}% (${completedMembers}/${totalMembers})</span>
|
|
439
434
|
</div>
|
|
440
435
|
</div>
|
|
441
436
|
<div style="display: flex; gap: 10px;">
|
|
442
437
|
<button class="btn-primary btn-sm" data-id="${task._id}" data-action="view-detail" title="查看详细">📊 详情</button>
|
|
443
|
-
<button class="btn-danger btn-sm" data-id="${task._id}" data-action="delete-task" title="删除任务" style="min-width: 40px; height: 40px; display: flex; align-items: center; justify-content: center;"
|
|
438
|
+
<button class="btn-danger btn-sm" data-id="${task._id}" data-action="delete-task" title="删除任务" style="min-width: 40px; height: 40px; display: flex; align-items: center; justify-content: center;">🗑�?删除</button>
|
|
444
439
|
</div>
|
|
445
440
|
</div>
|
|
446
441
|
`;
|
|
@@ -461,10 +456,10 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
461
456
|
btn.addEventListener('click', async (e) => {
|
|
462
457
|
e.stopPropagation();
|
|
463
458
|
const taskId = btn.dataset.id;
|
|
464
|
-
if (confirm('
|
|
459
|
+
if (confirm('确定要删除这个任务吗?删除后无法恢复�?)) {
|
|
465
460
|
try {
|
|
466
461
|
await apiService.deleteTask(taskId);
|
|
467
|
-
alert('
|
|
462
|
+
alert('任务删除成功�?);
|
|
468
463
|
await renderTasksView(container);
|
|
469
464
|
} catch (error) {
|
|
470
465
|
console.error('删除任务错误:', error);
|
|
@@ -487,18 +482,16 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
487
482
|
e.preventDefault();
|
|
488
483
|
const formData = new FormData(e.target);
|
|
489
484
|
try {
|
|
490
|
-
//
|
|
491
|
-
const groupResult = await apiService.getGroup(currentGroup._id);
|
|
485
|
+
// 获取群组信息,自动分配给所有成�? const groupResult = await apiService.getGroup(currentGroup._id);
|
|
492
486
|
const memberIds = groupResult.group.members.map(m => m._id);
|
|
493
487
|
|
|
494
488
|
await apiService.createTask({
|
|
495
489
|
title: formData.get('title'),
|
|
496
490
|
description: formData.get('description'),
|
|
497
491
|
groupId: currentGroup._id,
|
|
498
|
-
assignedTo: memberIds, //
|
|
499
|
-
deadline: formData.get('deadline') || null
|
|
492
|
+
assignedTo: memberIds, // 分配给所有成�? deadline: formData.get('deadline') || null
|
|
500
493
|
});
|
|
501
|
-
alert('
|
|
494
|
+
alert('任务创建成功!已分配给所有群组成�?);
|
|
502
495
|
await renderTasksView(container);
|
|
503
496
|
document.getElementById('createTaskModal').classList.add('hidden');
|
|
504
497
|
} catch (error) {
|
|
@@ -513,8 +506,7 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
513
506
|
const modal = document.getElementById('taskDetailModal');
|
|
514
507
|
const content = document.getElementById('taskDetailContent');
|
|
515
508
|
|
|
516
|
-
//
|
|
517
|
-
const closeBtn = document.getElementById('closeTaskDetailModal');
|
|
509
|
+
// 先设置关闭事件(只设置一次,避免重复�? const closeBtn = document.getElementById('closeTaskDetailModal');
|
|
518
510
|
if (closeBtn && !closeBtn.dataset.listenerSet) {
|
|
519
511
|
closeBtn.addEventListener('click', () => {
|
|
520
512
|
modal.classList.add('hidden');
|
|
@@ -534,7 +526,7 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
534
526
|
|
|
535
527
|
try {
|
|
536
528
|
modal.classList.remove('hidden');
|
|
537
|
-
content.innerHTML = '<div class="loading"
|
|
529
|
+
content.innerHTML = '<div class="loading">加载�?..</div>';
|
|
538
530
|
|
|
539
531
|
// 获取任务详情
|
|
540
532
|
const taskResult = await apiService.getTask(taskId);
|
|
@@ -542,13 +534,12 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
542
534
|
|
|
543
535
|
// 验证必要数据
|
|
544
536
|
if (!task) {
|
|
545
|
-
throw new Error('
|
|
537
|
+
throw new Error('任务数据不存�?);
|
|
546
538
|
}
|
|
547
539
|
|
|
548
|
-
// 群组信息已经在task.group中(后端已populate
|
|
549
|
-
const group = task.group;
|
|
540
|
+
// 群组信息已经在task.group中(后端已populate�? const group = task.group;
|
|
550
541
|
if (!group || !group.members) {
|
|
551
|
-
throw new Error('
|
|
542
|
+
throw new Error('群组数据不完�?);
|
|
552
543
|
}
|
|
553
544
|
|
|
554
545
|
// 计算完成情况
|
|
@@ -583,12 +574,11 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
583
574
|
};
|
|
584
575
|
}).filter(m => m !== null);
|
|
585
576
|
|
|
586
|
-
//
|
|
587
|
-
const taskData = {
|
|
577
|
+
// 准备任务数据给美化组�? const taskData = {
|
|
588
578
|
...task,
|
|
589
579
|
group: group.name,
|
|
590
580
|
assignedTo: task.assignedTo && task.assignedTo.length > 0 && task.assignedTo[0].username ?
|
|
591
|
-
task.assignedTo[0].username : '
|
|
581
|
+
task.assignedTo[0].username : '未分�?,
|
|
592
582
|
members: assignedMembers,
|
|
593
583
|
completedCount: completedMembers
|
|
594
584
|
};
|
|
@@ -600,7 +590,7 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
600
590
|
console.error('加载任务详情失败:', error);
|
|
601
591
|
content.innerHTML = `
|
|
602
592
|
<div class="error-state">
|
|
603
|
-
<h3
|
|
593
|
+
<h3>�?加载失败</h3>
|
|
604
594
|
<p>${error.message || '未知错误'}</p>
|
|
605
595
|
<button class="btn-primary" onclick="document.getElementById('taskDetailModal').classList.add('hidden')">关闭</button>
|
|
606
596
|
</div>
|
|
@@ -609,7 +599,7 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
609
599
|
}
|
|
610
600
|
async function renderDocumentsView(container) {
|
|
611
601
|
if (!currentGroup) {
|
|
612
|
-
container.innerHTML = '<div class="empty-state"
|
|
602
|
+
container.innerHTML = '<div class="empty-state">请先选择一个群�?/div>';
|
|
613
603
|
return;
|
|
614
604
|
}
|
|
615
605
|
|
|
@@ -623,20 +613,20 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
623
613
|
<div class="documents-list" id="docsList"></div>
|
|
624
614
|
<div id="createDocModal" class="modal hidden">
|
|
625
615
|
<div class="modal-content">
|
|
626
|
-
<h3
|
|
616
|
+
<h3>创建新文�?/h3>
|
|
627
617
|
<form id="createDocForm">
|
|
628
618
|
<div class="form-group">
|
|
629
619
|
<label>文档标题</label>
|
|
630
|
-
<input type="text" name="title" placeholder="
|
|
620
|
+
<input type="text" name="title" placeholder="请输入文档标�? required>
|
|
631
621
|
</div>
|
|
632
622
|
<div class="form-group">
|
|
633
623
|
<label>文档内容</label>
|
|
634
|
-
<textarea name="content" placeholder="
|
|
624
|
+
<textarea name="content" placeholder="请输入文档内�? rows="6"></textarea>
|
|
635
625
|
</div>
|
|
636
626
|
<div class="form-group">
|
|
637
627
|
<label>权限设置</label>
|
|
638
628
|
<select name="permission">
|
|
639
|
-
<option value="editable"
|
|
629
|
+
<option value="editable">可编�?/option>
|
|
640
630
|
<option value="readonly">只读</option>
|
|
641
631
|
</select>
|
|
642
632
|
</div>
|
|
@@ -661,13 +651,13 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
661
651
|
<div style="flex: 1;">
|
|
662
652
|
<h3>📄 ${doc.title}</h3>
|
|
663
653
|
<div class="doc-meta">
|
|
664
|
-
<span
|
|
665
|
-
<span>${doc.permission === 'readonly' ? '🔒 只读' : '✏️
|
|
654
|
+
<span>创建�? ${doc.creator.username}</span>
|
|
655
|
+
<span>${doc.permission === 'readonly' ? '🔒 只读' : '✏️ 可编�?}</span>
|
|
666
656
|
</div>
|
|
667
657
|
</div>
|
|
668
658
|
<div style="display: flex; gap: 10px; align-items: center;">
|
|
669
659
|
<button class="btn-edit" data-id="${doc._id}">编辑</button>
|
|
670
|
-
<button class="btn-danger btn-sm" data-id="${doc._id}" data-action="delete-doc" title="删除文档" style="min-width: 40px; height: 40px; display: flex; align-items: center; justify-content: center;"
|
|
660
|
+
<button class="btn-danger btn-sm" data-id="${doc._id}" data-action="delete-doc" title="删除文档" style="min-width: 40px; height: 40px; display: flex; align-items: center; justify-content: center;">🗑�?删除</button>
|
|
671
661
|
</div>
|
|
672
662
|
</div>
|
|
673
663
|
`;
|
|
@@ -686,10 +676,10 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
686
676
|
btn.addEventListener('click', async (e) => {
|
|
687
677
|
e.stopPropagation();
|
|
688
678
|
const docId = btn.dataset.id;
|
|
689
|
-
if (confirm('
|
|
679
|
+
if (confirm('确定要删除这个文档吗?删除后无法恢复�?)) {
|
|
690
680
|
try {
|
|
691
681
|
await apiService.deleteDocument(docId);
|
|
692
|
-
alert('
|
|
682
|
+
alert('文档删除成功�?);
|
|
693
683
|
await renderDocumentsView(container);
|
|
694
684
|
} catch (error) {
|
|
695
685
|
console.error('删除文档错误:', error);
|
|
@@ -718,7 +708,7 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
718
708
|
currentGroup._id,
|
|
719
709
|
formData.get('permission')
|
|
720
710
|
);
|
|
721
|
-
alert('
|
|
711
|
+
alert('文档创建成功�?);
|
|
722
712
|
await renderDocumentsView(container);
|
|
723
713
|
document.getElementById('createDocModal').classList.add('hidden');
|
|
724
714
|
} catch (error) {
|
|
@@ -734,7 +724,7 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
734
724
|
|
|
735
725
|
container.innerHTML = `
|
|
736
726
|
<div class="view-header">
|
|
737
|
-
<button class="btn-back" id="backBtn"
|
|
727
|
+
<button class="btn-back" id="backBtn">�?返回</button>
|
|
738
728
|
<h2>${doc.title}</h2>
|
|
739
729
|
<span class="doc-status">${doc.permission === 'readonly' ? '🔒 只读模式' : '✏️ 编辑模式'}</span>
|
|
740
730
|
</div>
|
|
@@ -747,13 +737,12 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
747
737
|
</div>
|
|
748
738
|
<div id="editor"></div>
|
|
749
739
|
<div class="editor-footer">
|
|
750
|
-
<span
|
|
740
|
+
<span>最后编�? ${new Date(doc.updatedAt).toLocaleString()}</span>
|
|
751
741
|
</div>
|
|
752
742
|
</div>
|
|
753
743
|
`;
|
|
754
744
|
|
|
755
|
-
//
|
|
756
|
-
const quill = new Quill('#editor', {
|
|
745
|
+
// 初始�?Quill 编辑�? const quill = new Quill('#editor', {
|
|
757
746
|
theme: 'snow',
|
|
758
747
|
modules: {
|
|
759
748
|
toolbar: [
|
|
@@ -798,7 +787,7 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
798
787
|
try {
|
|
799
788
|
const content = quill.root.innerHTML;
|
|
800
789
|
await apiService.updateDocument(documentId, content);
|
|
801
|
-
alert('
|
|
790
|
+
alert('保存成功�?);
|
|
802
791
|
} catch (error) {
|
|
803
792
|
alert('保存失败: ' + error.message);
|
|
804
793
|
}
|
|
@@ -815,8 +804,7 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
815
804
|
}
|
|
816
805
|
});
|
|
817
806
|
|
|
818
|
-
//
|
|
819
|
-
wsService.on('typing', (data) => {
|
|
807
|
+
// 监听打字状�? wsService.on('typing', (data) => {
|
|
820
808
|
if (data.documentId === documentId && data.userId !== user.id) {
|
|
821
809
|
const onlineUsers = document.getElementById('onlineUsers');
|
|
822
810
|
if (data.isTyping) {
|
|
@@ -835,7 +823,7 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
835
823
|
|
|
836
824
|
async function renderFilesView(container) {
|
|
837
825
|
if (!currentGroup) {
|
|
838
|
-
container.innerHTML = '<div class="empty-state"
|
|
826
|
+
container.innerHTML = '<div class="empty-state">请先选择一个群�?/div>';
|
|
839
827
|
return;
|
|
840
828
|
}
|
|
841
829
|
|
|
@@ -860,7 +848,7 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
860
848
|
<div class="form-group">
|
|
861
849
|
<label>选择文件</label>
|
|
862
850
|
<input type="file" id="fileInput" required>
|
|
863
|
-
<small>支持图片、PDF、Word、Excel
|
|
851
|
+
<small>支持图片、PDF、Word、Excel等,最�?0MB</small>
|
|
864
852
|
</div>
|
|
865
853
|
<div class="form-group">
|
|
866
854
|
<label>描述(可选)</label>
|
|
@@ -892,7 +880,7 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
892
880
|
<div class="file-info">
|
|
893
881
|
<h4>${file.originalName}</h4>
|
|
894
882
|
<div class="file-meta">
|
|
895
|
-
<span
|
|
883
|
+
<span>上传�? ${file.uploader.username}</span>
|
|
896
884
|
<span>大小: ${fileSize}</span>
|
|
897
885
|
<span>时间: ${new Date(file.createdAt).toLocaleString()}</span>
|
|
898
886
|
</div>
|
|
@@ -915,7 +903,7 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
915
903
|
const token = localStorage.getItem('token');
|
|
916
904
|
|
|
917
905
|
// 使用 fetch 下载文件
|
|
918
|
-
const response = await fetch(`http://localhost:
|
|
906
|
+
const response = await fetch(`http://localhost:3000/api/files/${fileId}/download`, {
|
|
919
907
|
method: 'GET',
|
|
920
908
|
headers: {
|
|
921
909
|
'Authorization': `Bearer ${token}`
|
|
@@ -949,10 +937,10 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
949
937
|
// 删除文件事件
|
|
950
938
|
document.querySelectorAll('[data-action="delete-file"]').forEach(btn => {
|
|
951
939
|
btn.addEventListener('click', async () => {
|
|
952
|
-
if (confirm('
|
|
940
|
+
if (confirm('确定要删除这个文件吗�?)) {
|
|
953
941
|
try {
|
|
954
942
|
await apiService.deleteFile(btn.dataset.id);
|
|
955
|
-
alert('
|
|
943
|
+
alert('文件删除成功�?);
|
|
956
944
|
await renderFilesView(container);
|
|
957
945
|
} catch (error) {
|
|
958
946
|
alert('删除失败: ' + error.message);
|
|
@@ -989,7 +977,7 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
989
977
|
|
|
990
978
|
try {
|
|
991
979
|
await apiService.uploadFile(currentGroup._id, fileInput.files[0], description);
|
|
992
|
-
alert('
|
|
980
|
+
alert('文件上传成功�?);
|
|
993
981
|
document.getElementById('uploadFileModal').classList.add('hidden');
|
|
994
982
|
document.getElementById('uploadFileForm').reset();
|
|
995
983
|
await renderFilesView(container);
|
|
@@ -1009,7 +997,7 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
1009
997
|
}
|
|
1010
998
|
|
|
1011
999
|
function getFileIcon(mimetype) {
|
|
1012
|
-
if (mimetype.startsWith('image/')) return '
|
|
1000
|
+
if (mimetype.startsWith('image/')) return '🖼�?;
|
|
1013
1001
|
if (mimetype === 'application/pdf') return '📕';
|
|
1014
1002
|
if (mimetype.includes('word') || mimetype.includes('document')) return '📘';
|
|
1015
1003
|
if (mimetype.includes('excel') || mimetype.includes('spreadsheet')) return '📗';
|
|
@@ -1029,7 +1017,7 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
1029
1017
|
|
|
1030
1018
|
async function renderChatView(container) {
|
|
1031
1019
|
if (!currentGroup) {
|
|
1032
|
-
container.innerHTML = '<div class="empty-state"
|
|
1020
|
+
container.innerHTML = '<div class="empty-state">请先选择一个群�?/div>';
|
|
1033
1021
|
return;
|
|
1034
1022
|
}
|
|
1035
1023
|
|
|
@@ -1044,10 +1032,10 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
1044
1032
|
🤖 AI
|
|
1045
1033
|
</button>
|
|
1046
1034
|
<button class="btn-secondary" id="showCollabTools" title="协作工具">
|
|
1047
|
-
|
|
1035
|
+
🛠�?工具
|
|
1048
1036
|
</button>
|
|
1049
|
-
<button class="btn-secondary" id="markAllRead" title="
|
|
1050
|
-
|
|
1037
|
+
<button class="btn-secondary" id="markAllRead" title="全部标记为已�?>
|
|
1038
|
+
�?已读
|
|
1051
1039
|
</button>
|
|
1052
1040
|
<button class="btn-secondary" id="muteAllBtn">全体禁言</button>
|
|
1053
1041
|
<button class="btn-secondary" id="manageMuteBtn">个人禁言</button>
|
|
@@ -1059,7 +1047,7 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
1059
1047
|
<div class="chat-input">
|
|
1060
1048
|
<button class="btn-emoji" id="emojiBtn">😊</button>
|
|
1061
1049
|
<input type="text" id="messageInput" placeholder="输入消息... (使用 @ 提及用户)">
|
|
1062
|
-
<button class="btn-primary" id="sendBtn"
|
|
1050
|
+
<button class="btn-primary" id="sendBtn">发�?/button>
|
|
1063
1051
|
</div>
|
|
1064
1052
|
<emoji-picker id="emojiPicker" class="hidden"></emoji-picker>
|
|
1065
1053
|
</div>
|
|
@@ -1074,25 +1062,22 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
1074
1062
|
<div class="modal-content">
|
|
1075
1063
|
<h3>清除聊天记录</h3>
|
|
1076
1064
|
<div style="padding: 20px;">
|
|
1077
|
-
<p style="margin-bottom: 20px; color: var(--danger);">⚠️
|
|
1065
|
+
<p style="margin-bottom: 20px; color: var(--danger);">⚠️ 警告:此操作不可恢复�?/p>
|
|
1078
1066
|
<div class="form-group">
|
|
1079
1067
|
<label>
|
|
1080
1068
|
<input type="radio" name="clearType" value="all" checked>
|
|
1081
|
-
|
|
1082
|
-
</label>
|
|
1069
|
+
清除所有聊天记�? </label>
|
|
1083
1070
|
</div>
|
|
1084
1071
|
<div class="form-group">
|
|
1085
1072
|
<label>
|
|
1086
1073
|
<input type="radio" name="clearType" value="before">
|
|
1087
|
-
|
|
1088
|
-
</label>
|
|
1074
|
+
清除指定日期之前的记�? </label>
|
|
1089
1075
|
<input type="date" id="clearBeforeDate" style="margin-left: 10px; margin-top: 10px;" disabled>
|
|
1090
1076
|
</div>
|
|
1091
1077
|
<div class="form-group">
|
|
1092
1078
|
<label>
|
|
1093
1079
|
<input type="radio" name="clearType" value="user">
|
|
1094
|
-
|
|
1095
|
-
</label>
|
|
1080
|
+
清除指定用户的消�? </label>
|
|
1096
1081
|
<select id="clearUserId" style="margin-left: 10px; margin-top: 10px;" disabled>
|
|
1097
1082
|
<option value="">选择用户</option>
|
|
1098
1083
|
</select>
|
|
@@ -1152,8 +1137,7 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
1152
1137
|
}
|
|
1153
1138
|
});
|
|
1154
1139
|
|
|
1155
|
-
//
|
|
1156
|
-
emojiBtn.addEventListener('click', () => {
|
|
1140
|
+
// 表情包功�? emojiBtn.addEventListener('click', () => {
|
|
1157
1141
|
emojiPicker.classList.toggle('hidden');
|
|
1158
1142
|
});
|
|
1159
1143
|
|
|
@@ -1163,8 +1147,7 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
1163
1147
|
emojiPicker.classList.add('hidden');
|
|
1164
1148
|
});
|
|
1165
1149
|
|
|
1166
|
-
//
|
|
1167
|
-
document.addEventListener('click', (e) => {
|
|
1150
|
+
// 点击外部关闭表情选择�? document.addEventListener('click', (e) => {
|
|
1168
1151
|
if (!emojiBtn.contains(e.target) && !emojiPicker.contains(e.target)) {
|
|
1169
1152
|
emojiPicker.classList.add('hidden');
|
|
1170
1153
|
}
|
|
@@ -1192,8 +1175,7 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
1192
1175
|
const messagesResult = await apiService.getGroupMessages(currentGroup._id);
|
|
1193
1176
|
if (messagesResult.messages) {
|
|
1194
1177
|
messagesResult.messages.forEach(msg => {
|
|
1195
|
-
//
|
|
1196
|
-
features.chatEnhancements.renderMessage(msg, messagesDiv, group.members);
|
|
1178
|
+
// 使用增强的消息渲�? features.chatEnhancements.renderMessage(msg, messagesDiv, group.members);
|
|
1197
1179
|
});
|
|
1198
1180
|
messagesDiv.scrollTop = messagesDiv.scrollHeight;
|
|
1199
1181
|
}
|
|
@@ -1208,8 +1190,7 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
1208
1190
|
};
|
|
1209
1191
|
refreshMuteButtons();
|
|
1210
1192
|
|
|
1211
|
-
//
|
|
1212
|
-
document.getElementById('muteAllBtn').addEventListener('click', async () => {
|
|
1193
|
+
// 全体禁言(服务端生效�? document.getElementById('muteAllBtn').addEventListener('click', async () => {
|
|
1213
1194
|
try {
|
|
1214
1195
|
const next = !isMutedAll;
|
|
1215
1196
|
const res = await apiService.setMuteAll(currentGroup._id, next);
|
|
@@ -1218,7 +1199,7 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
1218
1199
|
|
|
1219
1200
|
const notification = document.createElement('div');
|
|
1220
1201
|
notification.className = 'notification';
|
|
1221
|
-
notification.textContent = isMutedAll ? '
|
|
1202
|
+
notification.textContent = isMutedAll ? '已开启全体禁言(成员无法发言�? : '已取消全体禁言';
|
|
1222
1203
|
messagesDiv.appendChild(notification);
|
|
1223
1204
|
messagesDiv.scrollTop = messagesDiv.scrollHeight;
|
|
1224
1205
|
} catch (e) {
|
|
@@ -1226,10 +1207,8 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
1226
1207
|
}
|
|
1227
1208
|
});
|
|
1228
1209
|
|
|
1229
|
-
//
|
|
1230
|
-
|
|
1231
|
-
// 重新拉取最新 group(避免成员变动不同步)
|
|
1232
|
-
const latest = await apiService.getGroup(currentGroup._id);
|
|
1210
|
+
// 个人禁言(服务端生效�? document.getElementById('manageMuteBtn').addEventListener('click', async () => {
|
|
1211
|
+
// 重新拉取最�?group(避免成员变动不同步�? const latest = await apiService.getGroup(currentGroup._id);
|
|
1233
1212
|
group = latest.group;
|
|
1234
1213
|
mutedUsers = new Set((group.mutedUsers || []).map(String));
|
|
1235
1214
|
|
|
@@ -1274,8 +1253,7 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
1274
1253
|
|
|
1275
1254
|
// 清除聊天记录功能
|
|
1276
1255
|
document.getElementById('clearChatBtn').addEventListener('click', async () => {
|
|
1277
|
-
//
|
|
1278
|
-
const clearUserId = document.getElementById('clearUserId');
|
|
1256
|
+
// 加载群组成员到下拉列�? const clearUserId = document.getElementById('clearUserId');
|
|
1279
1257
|
clearUserId.innerHTML = '<option value="">选择用户</option>';
|
|
1280
1258
|
group.members.forEach(member => {
|
|
1281
1259
|
clearUserId.innerHTML += `<option value="${member._id}">${member.username}</option>`;
|
|
@@ -1302,14 +1280,14 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
1302
1280
|
|
|
1303
1281
|
let confirmMsg = '';
|
|
1304
1282
|
if (clearType === 'all') {
|
|
1305
|
-
confirmMsg = '
|
|
1283
|
+
confirmMsg = '确定要清除所有聊天记录吗?此操作不可恢复�?;
|
|
1306
1284
|
} else if (clearType === 'before') {
|
|
1307
1285
|
const date = document.getElementById('clearBeforeDate').value;
|
|
1308
1286
|
if (!date) {
|
|
1309
1287
|
alert('请选择日期');
|
|
1310
1288
|
return;
|
|
1311
1289
|
}
|
|
1312
|
-
confirmMsg =
|
|
1290
|
+
confirmMsg = `确定要清�?${date} 之前的所有聊天记录吗?此操作不可恢复!`;
|
|
1313
1291
|
} else if (clearType === 'user') {
|
|
1314
1292
|
const userId = document.getElementById('clearUserId').value;
|
|
1315
1293
|
if (!userId) {
|
|
@@ -1317,7 +1295,7 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
1317
1295
|
return;
|
|
1318
1296
|
}
|
|
1319
1297
|
const username = group.members.find(m => m._id === userId)?.username;
|
|
1320
|
-
confirmMsg =
|
|
1298
|
+
confirmMsg = `确定要清除用�?${username} 的所有消息吗?此操作不可恢复!`;
|
|
1321
1299
|
}
|
|
1322
1300
|
|
|
1323
1301
|
if (!confirm(confirmMsg)) {
|
|
@@ -1344,8 +1322,7 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
1344
1322
|
const messagesResult = await apiService.getGroupMessages(currentGroup._id);
|
|
1345
1323
|
if (messagesResult.messages) {
|
|
1346
1324
|
messagesResult.messages.forEach(msg => {
|
|
1347
|
-
//
|
|
1348
|
-
features.chatEnhancements.renderMessage(msg, messagesDiv, group.members);
|
|
1325
|
+
// 使用增强的消息渲�? features.chatEnhancements.renderMessage(msg, messagesDiv, group.members);
|
|
1349
1326
|
});
|
|
1350
1327
|
}
|
|
1351
1328
|
} catch (error) {
|
|
@@ -1356,16 +1333,13 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
1356
1333
|
// 监听消息
|
|
1357
1334
|
wsService.on('chat_message', (data) => {
|
|
1358
1335
|
if (data.groupId === currentGroup._id) {
|
|
1359
|
-
//
|
|
1360
|
-
features.chatEnhancements.renderMessage(data, messagesDiv, group.members);
|
|
1336
|
+
// 使用增强的消息渲�? features.chatEnhancements.renderMessage(data, messagesDiv, group.members);
|
|
1361
1337
|
messagesDiv.scrollTop = messagesDiv.scrollHeight;
|
|
1362
1338
|
|
|
1363
|
-
//
|
|
1364
|
-
if (data.userId !== currentUserId) {
|
|
1339
|
+
// 显示通知(如果不是自己发送的消息�? if (data.userId !== currentUserId) {
|
|
1365
1340
|
features.notifications.showNewMessageNotification(data.username, data.content);
|
|
1366
1341
|
|
|
1367
|
-
//
|
|
1368
|
-
if (features.chatEnhancements.checkMentionsMe(data.content, group.members)) {
|
|
1342
|
+
// 检查是否@了当前用�? if (features.chatEnhancements.checkMentionsMe(data.content, group.members)) {
|
|
1369
1343
|
features.notifications.showMentionNotification(data.username, data.content);
|
|
1370
1344
|
}
|
|
1371
1345
|
}
|
|
@@ -1400,19 +1374,17 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
1400
1374
|
}
|
|
1401
1375
|
});
|
|
1402
1376
|
|
|
1403
|
-
//
|
|
1404
|
-
wsService.on('chat_blocked', (data) => {
|
|
1377
|
+
// 发送被拦截提示(来自服务端�? wsService.on('chat_blocked', (data) => {
|
|
1405
1378
|
if (data.groupId === currentGroup._id) {
|
|
1406
1379
|
const notification = document.createElement('div');
|
|
1407
1380
|
notification.className = 'notification';
|
|
1408
|
-
notification.textContent = data.message || '
|
|
1381
|
+
notification.textContent = data.message || '消息发送失�?;
|
|
1409
1382
|
messagesDiv.appendChild(notification);
|
|
1410
1383
|
messagesDiv.scrollTop = messagesDiv.scrollHeight;
|
|
1411
1384
|
}
|
|
1412
1385
|
});
|
|
1413
1386
|
|
|
1414
|
-
//
|
|
1415
|
-
const sendMessage = () => {
|
|
1387
|
+
// 发送消�? const sendMessage = () => {
|
|
1416
1388
|
const content = messageInput.value.trim();
|
|
1417
1389
|
if (content) {
|
|
1418
1390
|
// 解析@提及
|
|
@@ -1424,8 +1396,7 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
1424
1396
|
// 显示AI智能回复建议(如果启用)
|
|
1425
1397
|
if (localStorage.getItem('ai_smartReplies') !== 'false') {
|
|
1426
1398
|
features.aiAssistant.getSmartReplies(content).then(replies => {
|
|
1427
|
-
//
|
|
1428
|
-
console.log('智能回复建议:', replies);
|
|
1399
|
+
// 可以在这里显示智能回复建�? console.log('智能回复建议:', replies);
|
|
1429
1400
|
});
|
|
1430
1401
|
}
|
|
1431
1402
|
}
|
|
@@ -1439,7 +1410,7 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
1439
1410
|
|
|
1440
1411
|
async function renderCallView(container) {
|
|
1441
1412
|
if (!currentGroup) {
|
|
1442
|
-
container.innerHTML = '<div class="empty-state"
|
|
1413
|
+
container.innerHTML = '<div class="empty-state">请先选择一个群�?/div>';
|
|
1443
1414
|
return;
|
|
1444
1415
|
}
|
|
1445
1416
|
|
|
@@ -1451,7 +1422,7 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
1451
1422
|
<div class="call-controls">
|
|
1452
1423
|
<label>点名人数:</label>
|
|
1453
1424
|
<input type="number" id="callCount" value="1" min="1" max="10">
|
|
1454
|
-
<button class="btn-primary btn-large" id="randomCallBtn">🎲
|
|
1425
|
+
<button class="btn-primary btn-large" id="randomCallBtn">🎲 开始点�?/button>
|
|
1455
1426
|
</div>
|
|
1456
1427
|
<div id="callResult" class="call-result"></div>
|
|
1457
1428
|
</div>
|
|
@@ -1496,9 +1467,9 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
1496
1467
|
<option value="title_edit">标题编辑</option>
|
|
1497
1468
|
<option value="document_permission_change">权限修改</option>
|
|
1498
1469
|
</select>
|
|
1499
|
-
<input type="date" id="startDate" class="form-input" title="
|
|
1470
|
+
<input type="date" id="startDate" class="form-input" title="开始日�?>
|
|
1500
1471
|
<input type="date" id="endDate" class="form-input" title="结束日期">
|
|
1501
|
-
<button class="btn-primary" id="applyFilters"
|
|
1472
|
+
<button class="btn-primary" id="applyFilters">筛�?/button>
|
|
1502
1473
|
<button class="btn-secondary" id="exportLogs">导出</button>
|
|
1503
1474
|
</div>
|
|
1504
1475
|
</div>
|
|
@@ -1519,13 +1490,13 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
1519
1490
|
</div>
|
|
1520
1491
|
|
|
1521
1492
|
<div class="audit-logs" id="auditLogs">
|
|
1522
|
-
<div class="loading"
|
|
1493
|
+
<div class="loading">加载�?..</div>
|
|
1523
1494
|
</div>
|
|
1524
1495
|
|
|
1525
1496
|
<div class="pagination" id="auditPagination" style="display: none;">
|
|
1526
|
-
<button class="btn-secondary" id="prevPage"
|
|
1527
|
-
<span id="pageInfo"
|
|
1528
|
-
<button class="btn-secondary" id="nextPage"
|
|
1497
|
+
<button class="btn-secondary" id="prevPage">上一�?/button>
|
|
1498
|
+
<span id="pageInfo">�?1 页,�?1 �?/span>
|
|
1499
|
+
<button class="btn-secondary" id="nextPage">下一�?/button>
|
|
1529
1500
|
</div>
|
|
1530
1501
|
|
|
1531
1502
|
<div id="auditDetailModal" class="modal hidden">
|
|
@@ -1560,7 +1531,7 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
1560
1531
|
async function loadAuditLogs(page = 1, filters = {}) {
|
|
1561
1532
|
try {
|
|
1562
1533
|
const auditLogsDiv = document.getElementById('auditLogs');
|
|
1563
|
-
auditLogsDiv.innerHTML = '<div class="loading"
|
|
1534
|
+
auditLogsDiv.innerHTML = '<div class="loading">加载�?..</div>';
|
|
1564
1535
|
|
|
1565
1536
|
const options = { page, limit: 20 };
|
|
1566
1537
|
const result = await apiService.getAuditLogs(filters, options);
|
|
@@ -1600,7 +1571,7 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
1600
1571
|
// 更新分页
|
|
1601
1572
|
const pagination = document.getElementById('auditPagination');
|
|
1602
1573
|
const pageInfo = document.getElementById('pageInfo');
|
|
1603
|
-
pageInfo.textContent =
|
|
1574
|
+
pageInfo.textContent = `�?${result.pagination.page} 页,�?${result.pagination.pages} 页`;
|
|
1604
1575
|
|
|
1605
1576
|
document.getElementById('prevPage').disabled = result.pagination.page <= 1;
|
|
1606
1577
|
document.getElementById('nextPage').disabled = result.pagination.page >= result.pagination.pages;
|
|
@@ -1647,22 +1618,20 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
1647
1618
|
const content = document.getElementById('auditDetailContent');
|
|
1648
1619
|
|
|
1649
1620
|
modal.classList.remove('hidden');
|
|
1650
|
-
content.innerHTML = '<div class="loading"
|
|
1621
|
+
content.innerHTML = '<div class="loading">加载�?..</div>';
|
|
1651
1622
|
|
|
1652
1623
|
// 获取审计日志详情
|
|
1653
1624
|
const result = await apiService.getAuditLogDetail(logId);
|
|
1654
1625
|
const log = result.log;
|
|
1655
1626
|
|
|
1656
|
-
//
|
|
1657
|
-
let detailsHtml = '';
|
|
1627
|
+
// 格式化详细信�? let detailsHtml = '';
|
|
1658
1628
|
if (log.details) {
|
|
1659
1629
|
detailsHtml = Object.entries(log.details).map(([key, value]) => {
|
|
1660
1630
|
let displayValue = value;
|
|
1661
1631
|
|
|
1662
1632
|
// 特殊处理某些字段
|
|
1663
1633
|
if (key === 'oldContent' || key === 'newContent') {
|
|
1664
|
-
//
|
|
1665
|
-
if (typeof value === 'string' && value.length > 200) {
|
|
1634
|
+
// 截断过长的内�? if (typeof value === 'string' && value.length > 200) {
|
|
1666
1635
|
displayValue = value.substring(0, 200) + '...';
|
|
1667
1636
|
}
|
|
1668
1637
|
} else if (typeof value === 'object') {
|
|
@@ -1671,7 +1640,7 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
1671
1640
|
|
|
1672
1641
|
return `
|
|
1673
1642
|
<div class="info-row">
|
|
1674
|
-
<span class="info-label">${formatFieldName(key)}
|
|
1643
|
+
<span class="info-label">${formatFieldName(key)}�?/span>
|
|
1675
1644
|
<span class="info-value">${displayValue || '-'}</span>
|
|
1676
1645
|
</div>
|
|
1677
1646
|
`;
|
|
@@ -1682,19 +1651,19 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
1682
1651
|
<div class="audit-detail">
|
|
1683
1652
|
<div class="task-detail-info">
|
|
1684
1653
|
<div class="info-row">
|
|
1685
|
-
<span class="info-label"
|
|
1654
|
+
<span class="info-label">操作时间�?/span>
|
|
1686
1655
|
<span class="info-value">${new Date(log.createdAt).toLocaleString()}</span>
|
|
1687
1656
|
</div>
|
|
1688
1657
|
<div class="info-row">
|
|
1689
|
-
<span class="info-label"
|
|
1658
|
+
<span class="info-label">操作用户�?/span>
|
|
1690
1659
|
<span class="info-value">${log.user?.username || '未知用户'}</span>
|
|
1691
1660
|
</div>
|
|
1692
1661
|
<div class="info-row">
|
|
1693
|
-
<span class="info-label"
|
|
1694
|
-
<span class="info-value">${log.user?.role === 'admin' ? '
|
|
1662
|
+
<span class="info-label">用户角色�?/span>
|
|
1663
|
+
<span class="info-value">${log.user?.role === 'admin' ? '管理�? : '普通用�?}</span>
|
|
1695
1664
|
</div>
|
|
1696
1665
|
<div class="info-row">
|
|
1697
|
-
<span class="info-label"
|
|
1666
|
+
<span class="info-label">操作类型�?/span>
|
|
1698
1667
|
<span class="info-value">
|
|
1699
1668
|
<span class="action-badge action-${log.action}">${getActionText(log.action)}</span>
|
|
1700
1669
|
</span>
|
|
@@ -1704,15 +1673,15 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
1704
1673
|
<span class="info-value">${log.metadata?.groupId?.name || '-'}</span>
|
|
1705
1674
|
</div>
|
|
1706
1675
|
<div class="info-row">
|
|
1707
|
-
<span class="info-label"
|
|
1676
|
+
<span class="info-label">资源类型�?/span>
|
|
1708
1677
|
<span class="info-value">${log.resourceType || '-'}</span>
|
|
1709
1678
|
</div>
|
|
1710
1679
|
<div class="info-row">
|
|
1711
|
-
<span class="info-label"
|
|
1680
|
+
<span class="info-label">资源标题�?/span>
|
|
1712
1681
|
<span class="info-value">${log.resourceTitle || log.resourceId || '-'}</span>
|
|
1713
1682
|
</div>
|
|
1714
1683
|
<div class="info-row">
|
|
1715
|
-
<span class="info-label">IP
|
|
1684
|
+
<span class="info-label">IP地址�?/span>
|
|
1716
1685
|
<span class="info-value">${log.metadata?.ipAddress || '-'}</span>
|
|
1717
1686
|
</div>
|
|
1718
1687
|
</div>
|
|
@@ -1744,12 +1713,12 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
1744
1713
|
function formatFieldName(key) {
|
|
1745
1714
|
const fieldNames = {
|
|
1746
1715
|
'description': '描述',
|
|
1747
|
-
'oldTitle': '
|
|
1748
|
-
'newTitle': '
|
|
1749
|
-
'oldContent': '
|
|
1750
|
-
'newContent': '
|
|
1751
|
-
'oldPermission': '
|
|
1752
|
-
'newPermission': '
|
|
1716
|
+
'oldTitle': '原标�?,
|
|
1717
|
+
'newTitle': '新标�?,
|
|
1718
|
+
'oldContent': '原内�?,
|
|
1719
|
+
'newContent': '新内�?,
|
|
1720
|
+
'oldPermission': '原权�?,
|
|
1721
|
+
'newPermission': '新权�?,
|
|
1753
1722
|
'contentLength': '内容长度',
|
|
1754
1723
|
'changes': '变更内容',
|
|
1755
1724
|
'reason': '原因'
|
|
@@ -1766,8 +1735,7 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
1766
1735
|
endDate: document.getElementById('endDate').value
|
|
1767
1736
|
};
|
|
1768
1737
|
|
|
1769
|
-
//
|
|
1770
|
-
Object.keys(currentFilters).forEach(key => {
|
|
1738
|
+
// 移除空�? Object.keys(currentFilters).forEach(key => {
|
|
1771
1739
|
if (!currentFilters[key]) {
|
|
1772
1740
|
delete currentFilters[key];
|
|
1773
1741
|
}
|
|
@@ -1809,7 +1777,7 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
1809
1777
|
</div>
|
|
1810
1778
|
<div class="search-container">
|
|
1811
1779
|
<div class="search-box">
|
|
1812
|
-
<input type="text" id="searchInput" placeholder="
|
|
1780
|
+
<input type="text" id="searchInput" placeholder="搜索消息、文档、任�?..">
|
|
1813
1781
|
<button class="btn-primary" id="searchBtn">搜索</button>
|
|
1814
1782
|
</div>
|
|
1815
1783
|
<div class="search-filters">
|
|
@@ -1844,7 +1812,7 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
1844
1812
|
tasks: document.getElementById('filterTasks').checked
|
|
1845
1813
|
};
|
|
1846
1814
|
|
|
1847
|
-
searchResults.innerHTML = '<div class="loading"
|
|
1815
|
+
searchResults.innerHTML = '<div class="loading">搜索�?..</div>';
|
|
1848
1816
|
|
|
1849
1817
|
try {
|
|
1850
1818
|
const results = [];
|
|
@@ -1927,7 +1895,7 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
1927
1895
|
|
|
1928
1896
|
// 显示结果
|
|
1929
1897
|
if (results.length === 0) {
|
|
1930
|
-
searchResults.innerHTML = '<div class="empty-state"
|
|
1898
|
+
searchResults.innerHTML = '<div class="empty-state">未找到相关结�?/div>';
|
|
1931
1899
|
} else {
|
|
1932
1900
|
searchResults.innerHTML = results.map(result => {
|
|
1933
1901
|
const typeIcon = {
|
|
@@ -1944,7 +1912,7 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
1944
1912
|
<h4>${highlightText(result.title, query)}</h4>
|
|
1945
1913
|
<p>${highlightText(result.content, query)}</p>
|
|
1946
1914
|
${result.group ? `<span class="result-group">群组: ${result.group}</span>` : ''}
|
|
1947
|
-
${result.status ? `<span class="result-status"
|
|
1915
|
+
${result.status ? `<span class="result-status">状�? ${getStatusText(result.status)}</span>` : ''}
|
|
1948
1916
|
</div>
|
|
1949
1917
|
`;
|
|
1950
1918
|
}).join('');
|
|
@@ -1968,10 +1936,10 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
1968
1936
|
|
|
1969
1937
|
function getStatusText(status) {
|
|
1970
1938
|
const statusMap = {
|
|
1971
|
-
'pending': '
|
|
1972
|
-
'in_progress': '
|
|
1973
|
-
'completed': '
|
|
1974
|
-
'terminated': '
|
|
1939
|
+
'pending': '待处�?,
|
|
1940
|
+
'in_progress': '进行�?,
|
|
1941
|
+
'completed': '已完�?,
|
|
1942
|
+
'terminated': '已终�?
|
|
1975
1943
|
};
|
|
1976
1944
|
return statusMap[status] || status;
|
|
1977
1945
|
}
|
|
@@ -1994,13 +1962,13 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
1994
1962
|
const token = localStorage.getItem('token');
|
|
1995
1963
|
|
|
1996
1964
|
// 获取备份列表
|
|
1997
|
-
const backupsResponse = await fetch('http://localhost:
|
|
1965
|
+
const backupsResponse = await fetch('http://localhost:3000/api/backup/list', {
|
|
1998
1966
|
headers: { 'Authorization': `Bearer ${token}` }
|
|
1999
1967
|
});
|
|
2000
1968
|
const backupsResult = await backupsResponse.json();
|
|
2001
1969
|
|
|
2002
1970
|
// 获取备份配置
|
|
2003
|
-
const configResponse = await fetch('http://localhost:
|
|
1971
|
+
const configResponse = await fetch('http://localhost:3000/api/backup/config', {
|
|
2004
1972
|
headers: { 'Authorization': `Bearer ${token}` }
|
|
2005
1973
|
});
|
|
2006
1974
|
const configResult = await configResponse.json();
|
|
@@ -2021,15 +1989,15 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
2021
1989
|
</div>
|
|
2022
1990
|
<div class="stat-card">
|
|
2023
1991
|
<h3>自动备份</h3>
|
|
2024
|
-
<div class="stat-number">${configResult.data?.config?.autoBackup ? '
|
|
1992
|
+
<div class="stat-number">${configResult.data?.config?.autoBackup ? '�?已启�? : '�?已禁�?}</div>
|
|
2025
1993
|
</div>
|
|
2026
1994
|
<div class="stat-card">
|
|
2027
1995
|
<h3>备份频率</h3>
|
|
2028
|
-
<div class="stat-number">${configResult.data?.config?.schedule || '
|
|
1996
|
+
<div class="stat-number">${configResult.data?.config?.schedule || '未设�?}</div>
|
|
2029
1997
|
</div>
|
|
2030
1998
|
<div class="stat-card">
|
|
2031
1999
|
<h3>保留天数</h3>
|
|
2032
|
-
<div class="stat-number">${configResult.data?.config?.retention || 30}
|
|
2000
|
+
<div class="stat-number">${configResult.data?.config?.retention || 30} �?/div>
|
|
2033
2001
|
</div>
|
|
2034
2002
|
</div>
|
|
2035
2003
|
|
|
@@ -2040,7 +2008,7 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
2040
2008
|
<div>备份时间</div>
|
|
2041
2009
|
<div>类型</div>
|
|
2042
2010
|
<div>大小</div>
|
|
2043
|
-
<div
|
|
2011
|
+
<div>状�?/div>
|
|
2044
2012
|
<div>操作</div>
|
|
2045
2013
|
</div>
|
|
2046
2014
|
${backupsResult.data.backups.map(backup => `
|
|
@@ -2054,7 +2022,7 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
2054
2022
|
<div>${formatFileSize(backup.size)}</div>
|
|
2055
2023
|
<div>
|
|
2056
2024
|
<span class="status-badge status-${backup.status}">
|
|
2057
|
-
${backup.status === 'completed' ? '
|
|
2025
|
+
${backup.status === 'completed' ? '�?完成' : backup.status === 'failed' ? '�?失败' : '�?进行�?}
|
|
2058
2026
|
</span>
|
|
2059
2027
|
</div>
|
|
2060
2028
|
<div class="backup-actions">
|
|
@@ -2067,7 +2035,7 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
2067
2035
|
</div>
|
|
2068
2036
|
`).join('')}
|
|
2069
2037
|
</div>
|
|
2070
|
-
` : '<div class="empty-state">暂无备份记录<br>点击"立即备份"
|
|
2038
|
+
` : '<div class="empty-state">暂无备份记录<br>点击"立即备份"创建第一个备�?/div>'}
|
|
2071
2039
|
</div>
|
|
2072
2040
|
|
|
2073
2041
|
<!-- 创建备份模态框 -->
|
|
@@ -2082,7 +2050,7 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
2082
2050
|
<label>备份类型</label>
|
|
2083
2051
|
<select id="backupType" required>
|
|
2084
2052
|
<option value="full">完整备份(所有数据)</option>
|
|
2085
|
-
<option value="incremental"
|
|
2053
|
+
<option value="incremental">增量备份(仅变更�?/option>
|
|
2086
2054
|
</select>
|
|
2087
2055
|
</div>
|
|
2088
2056
|
<div class="form-group">
|
|
@@ -2091,7 +2059,7 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
2091
2059
|
</div>
|
|
2092
2060
|
<div class="form-actions">
|
|
2093
2061
|
<button type="button" class="btn-secondary" id="cancelCreateBackup">取消</button>
|
|
2094
|
-
<button type="submit" class="btn-primary"
|
|
2062
|
+
<button type="submit" class="btn-primary">开始备�?/button>
|
|
2095
2063
|
</div>
|
|
2096
2064
|
</form>
|
|
2097
2065
|
</div>
|
|
@@ -2113,16 +2081,16 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
2113
2081
|
</div>
|
|
2114
2082
|
<div class="form-group">
|
|
2115
2083
|
<label>备份频率(Cron表达式)</label>
|
|
2116
|
-
<input type="text" id="backupSchedule" value="${configResult.data?.config?.schedule || '0 2 * * *'}" placeholder="0 2 * * * (每天凌晨2
|
|
2117
|
-
<small
|
|
2084
|
+
<input type="text" id="backupSchedule" value="${configResult.data?.config?.schedule || '0 2 * * *'}" placeholder="0 2 * * * (每天凌晨2�?">
|
|
2085
|
+
<small>示例�? 2 * * * (每天凌晨2�?�? */6 * * * (�?小时)</small>
|
|
2118
2086
|
</div>
|
|
2119
2087
|
<div class="form-group">
|
|
2120
2088
|
<label>保留天数</label>
|
|
2121
2089
|
<input type="number" id="backupRetention" value="${configResult.data?.config?.retention || 30}" min="1" max="365">
|
|
2122
|
-
<small
|
|
2090
|
+
<small>超过此天数的备份将自动删�?/small>
|
|
2123
2091
|
</div>
|
|
2124
2092
|
<div class="form-group">
|
|
2125
|
-
<label
|
|
2093
|
+
<label>最大备份数�?/label>
|
|
2126
2094
|
<input type="number" id="maxBackups" value="${configResult.data?.config?.maxBackups || 10}" min="1" max="100">
|
|
2127
2095
|
</div>
|
|
2128
2096
|
<div class="form-actions">
|
|
@@ -2153,7 +2121,7 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
2153
2121
|
const description = document.getElementById('backupDescription').value;
|
|
2154
2122
|
|
|
2155
2123
|
try {
|
|
2156
|
-
const response = await fetch('http://localhost:
|
|
2124
|
+
const response = await fetch('http://localhost:3000/api/backup/create', {
|
|
2157
2125
|
method: 'POST',
|
|
2158
2126
|
headers: {
|
|
2159
2127
|
'Content-Type': 'application/json',
|
|
@@ -2164,7 +2132,7 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
2164
2132
|
|
|
2165
2133
|
const result = await response.json();
|
|
2166
2134
|
if (result.success) {
|
|
2167
|
-
alert('
|
|
2135
|
+
alert('备份创建成功�?);
|
|
2168
2136
|
document.getElementById('createBackupModal').classList.add('hidden');
|
|
2169
2137
|
renderBackupView(container);
|
|
2170
2138
|
} else {
|
|
@@ -2198,7 +2166,7 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
2198
2166
|
};
|
|
2199
2167
|
|
|
2200
2168
|
try {
|
|
2201
|
-
const response = await fetch('http://localhost:
|
|
2169
|
+
const response = await fetch('http://localhost:3000/api/backup/config', {
|
|
2202
2170
|
method: 'PUT',
|
|
2203
2171
|
headers: {
|
|
2204
2172
|
'Content-Type': 'application/json',
|
|
@@ -2209,7 +2177,7 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
2209
2177
|
|
|
2210
2178
|
const result = await response.json();
|
|
2211
2179
|
if (result.success) {
|
|
2212
|
-
alert('
|
|
2180
|
+
alert('设置保存成功�?);
|
|
2213
2181
|
document.getElementById('configBackupModal').classList.add('hidden');
|
|
2214
2182
|
renderBackupView(container);
|
|
2215
2183
|
} else {
|
|
@@ -2224,20 +2192,20 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
2224
2192
|
document.querySelectorAll('[data-action="download-backup"]').forEach(btn => {
|
|
2225
2193
|
btn.addEventListener('click', async () => {
|
|
2226
2194
|
const backupId = btn.dataset.id;
|
|
2227
|
-
window.location.href = `http://localhost:
|
|
2195
|
+
window.location.href = `http://localhost:3000/api/backup/${backupId}/download?token=${token}`;
|
|
2228
2196
|
});
|
|
2229
2197
|
});
|
|
2230
2198
|
|
|
2231
2199
|
// 恢复备份
|
|
2232
2200
|
document.querySelectorAll('[data-action="restore-backup"]').forEach(btn => {
|
|
2233
2201
|
btn.addEventListener('click', async () => {
|
|
2234
|
-
if (!confirm('
|
|
2202
|
+
if (!confirm('确定要恢复此备份吗?这将覆盖当前数据�?)) {
|
|
2235
2203
|
return;
|
|
2236
2204
|
}
|
|
2237
2205
|
|
|
2238
2206
|
const backupId = btn.dataset.id;
|
|
2239
2207
|
try {
|
|
2240
|
-
const response = await fetch(`http://localhost:
|
|
2208
|
+
const response = await fetch(`http://localhost:3000/api/backup/${backupId}/restore`, {
|
|
2241
2209
|
method: 'POST',
|
|
2242
2210
|
headers: { 'Authorization': `Bearer ${token}` }
|
|
2243
2211
|
});
|
|
@@ -2264,14 +2232,14 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
2264
2232
|
|
|
2265
2233
|
const backupId = btn.dataset.id;
|
|
2266
2234
|
try {
|
|
2267
|
-
const response = await fetch(`http://localhost:
|
|
2235
|
+
const response = await fetch(`http://localhost:3000/api/backup/${backupId}`, {
|
|
2268
2236
|
method: 'DELETE',
|
|
2269
2237
|
headers: { 'Authorization': `Bearer ${token}` }
|
|
2270
2238
|
});
|
|
2271
2239
|
|
|
2272
2240
|
const result = await response.json();
|
|
2273
2241
|
if (result.success) {
|
|
2274
|
-
alert('
|
|
2242
|
+
alert('备份删除成功�?);
|
|
2275
2243
|
renderBackupView(container);
|
|
2276
2244
|
} else {
|
|
2277
2245
|
alert('删除失败: ' + result.error.message);
|
|
@@ -2296,20 +2264,20 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
2296
2264
|
// 知识库视图(管理员)
|
|
2297
2265
|
async function renderKnowledgeView(container) {
|
|
2298
2266
|
if (!currentGroup) {
|
|
2299
|
-
container.innerHTML = '<div class="empty-state"
|
|
2267
|
+
container.innerHTML = '<div class="empty-state">请先选择一个群�?/div>';
|
|
2300
2268
|
return;
|
|
2301
2269
|
}
|
|
2302
2270
|
|
|
2303
2271
|
try {
|
|
2304
2272
|
const token = localStorage.getItem('token');
|
|
2305
|
-
const response = await fetch(`http://localhost:
|
|
2273
|
+
const response = await fetch(`http://localhost:3000/api/knowledge/group/${currentGroup._id}`, {
|
|
2306
2274
|
headers: { 'Authorization': `Bearer ${token}` }
|
|
2307
2275
|
});
|
|
2308
2276
|
const result = await response.json();
|
|
2309
2277
|
|
|
2310
2278
|
container.innerHTML = `
|
|
2311
2279
|
<div class="view-header">
|
|
2312
|
-
<h2>📚
|
|
2280
|
+
<h2>📚 知识库管�?- ${currentGroup.name}</h2>
|
|
2313
2281
|
<button class="btn-primary" id="createKnowledgeBtn">+ 新建文档</button>
|
|
2314
2282
|
</div>
|
|
2315
2283
|
<div class="knowledge-list" id="knowledgeList"></div>
|
|
@@ -2318,7 +2286,7 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
2318
2286
|
const knowledgeList = document.getElementById('knowledgeList');
|
|
2319
2287
|
|
|
2320
2288
|
if (!result.data || !result.data.knowledgeList || result.data.knowledgeList.length === 0) {
|
|
2321
|
-
knowledgeList.innerHTML = '<div class="empty-state"
|
|
2289
|
+
knowledgeList.innerHTML = '<div class="empty-state">暂无知识库文�?/div>';
|
|
2322
2290
|
} else {
|
|
2323
2291
|
result.data.knowledgeList.forEach(kb => {
|
|
2324
2292
|
const kbCard = document.createElement('div');
|
|
@@ -2326,11 +2294,11 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
2326
2294
|
kbCard.innerHTML = `
|
|
2327
2295
|
<div class="kb-header">
|
|
2328
2296
|
<h3>${kb.title}</h3>
|
|
2329
|
-
<span class="kb-status ${kb.status}">${kb.status === 'published' ? '
|
|
2297
|
+
<span class="kb-status ${kb.status}">${kb.status === 'published' ? '已发�? : kb.status === 'draft' ? '草稿' : '已归�?}</span>
|
|
2330
2298
|
</div>
|
|
2331
2299
|
<div class="kb-meta">
|
|
2332
|
-
<span>📁 ${kb.category || '
|
|
2333
|
-
<span
|
|
2300
|
+
<span>📁 ${kb.category || '未分�?}</span>
|
|
2301
|
+
<span>👁�?${kb.views || 0} 浏览</span>
|
|
2334
2302
|
<span>👍 ${kb.likes ? kb.likes.length : 0} 点赞</span>
|
|
2335
2303
|
</div>
|
|
2336
2304
|
<div class="kb-actions">
|
|
@@ -2342,8 +2310,7 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
2342
2310
|
knowledgeList.appendChild(kbCard);
|
|
2343
2311
|
});
|
|
2344
2312
|
|
|
2345
|
-
//
|
|
2346
|
-
document.querySelectorAll('[data-action="view-kb"]').forEach(btn => {
|
|
2313
|
+
// 查看、编辑、删除事�? document.querySelectorAll('[data-action="view-kb"]').forEach(btn => {
|
|
2347
2314
|
btn.addEventListener('click', async () => {
|
|
2348
2315
|
await showKnowledgeDetail(btn.dataset.id);
|
|
2349
2316
|
});
|
|
@@ -2359,11 +2326,11 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
2359
2326
|
btn.addEventListener('click', async () => {
|
|
2360
2327
|
if (confirm('确定要删除这个知识库文档吗?')) {
|
|
2361
2328
|
try {
|
|
2362
|
-
await fetch(`http://localhost:
|
|
2329
|
+
await fetch(`http://localhost:3000/api/knowledge/${btn.dataset.id}`, {
|
|
2363
2330
|
method: 'DELETE',
|
|
2364
2331
|
headers: { 'Authorization': `Bearer ${token}` }
|
|
2365
2332
|
});
|
|
2366
|
-
alert('
|
|
2333
|
+
alert('删除成功�?);
|
|
2367
2334
|
renderKnowledgeView(container);
|
|
2368
2335
|
} catch (error) {
|
|
2369
2336
|
alert('删除失败: ' + error.message);
|
|
@@ -2378,10 +2345,10 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
2378
2345
|
});
|
|
2379
2346
|
|
|
2380
2347
|
} catch (error) {
|
|
2381
|
-
console.error('
|
|
2348
|
+
console.error('加载知识库失�?', error);
|
|
2382
2349
|
container.innerHTML = `
|
|
2383
2350
|
<div class="view-header">
|
|
2384
|
-
<h2>📚
|
|
2351
|
+
<h2>📚 知识库管�?/h2>
|
|
2385
2352
|
</div>
|
|
2386
2353
|
<div class="empty-state">加载失败: ${error.message}</div>
|
|
2387
2354
|
`;
|
|
@@ -2396,28 +2363,28 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
2396
2363
|
modal.innerHTML = `
|
|
2397
2364
|
<div class="modal-content" style="max-width: 800px;">
|
|
2398
2365
|
<div class="modal-header">
|
|
2399
|
-
<h3>📚
|
|
2366
|
+
<h3>📚 创建知识库文�?/h3>
|
|
2400
2367
|
<button class="close-btn" id="closeCreateKnowledge">×</button>
|
|
2401
2368
|
</div>
|
|
2402
2369
|
<form id="createKnowledgeForm">
|
|
2403
2370
|
<div class="form-group">
|
|
2404
2371
|
<label>标题 *</label>
|
|
2405
|
-
<input type="text" id="kbTitle" required placeholder="
|
|
2372
|
+
<input type="text" id="kbTitle" required placeholder="请输入文档标�?>
|
|
2406
2373
|
</div>
|
|
2407
2374
|
<div class="form-group">
|
|
2408
2375
|
<label>分类</label>
|
|
2409
2376
|
<input type="text" id="kbCategory" placeholder="例如:技术文档、产品说明等">
|
|
2410
2377
|
</div>
|
|
2411
2378
|
<div class="form-group">
|
|
2412
|
-
<label
|
|
2413
|
-
<input type="text" id="kbTags" placeholder="
|
|
2379
|
+
<label>标签(用逗号分隔�?/label>
|
|
2380
|
+
<input type="text" id="kbTags" placeholder="例如:前�?React,教程">
|
|
2414
2381
|
</div>
|
|
2415
2382
|
<div class="form-group">
|
|
2416
2383
|
<label>内容 *</label>
|
|
2417
|
-
<textarea id="kbContent" rows="10" required placeholder="
|
|
2384
|
+
<textarea id="kbContent" rows="10" required placeholder="请输入文档内�?></textarea>
|
|
2418
2385
|
</div>
|
|
2419
2386
|
<div class="form-group">
|
|
2420
|
-
<label
|
|
2387
|
+
<label>状�?/label>
|
|
2421
2388
|
<select id="kbStatus">
|
|
2422
2389
|
<option value="draft">草稿</option>
|
|
2423
2390
|
<option value="published">发布</option>
|
|
@@ -2428,15 +2395,15 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
2428
2395
|
<select id="kbReadPermission">
|
|
2429
2396
|
<option value="group">群组成员可见</option>
|
|
2430
2397
|
<option value="public">公开</option>
|
|
2431
|
-
<option value="private"
|
|
2398
|
+
<option value="private">仅自己可�?/option>
|
|
2432
2399
|
</select>
|
|
2433
2400
|
</div>
|
|
2434
2401
|
<div class="form-group">
|
|
2435
2402
|
<label>编辑权限</label>
|
|
2436
2403
|
<select id="kbWritePermission">
|
|
2437
|
-
<option value="author"
|
|
2438
|
-
<option value="admin"
|
|
2439
|
-
<option value="all"
|
|
2404
|
+
<option value="author">仅作�?/option>
|
|
2405
|
+
<option value="admin">管理�?/option>
|
|
2406
|
+
<option value="all">所有成�?/option>
|
|
2440
2407
|
</select>
|
|
2441
2408
|
</div>
|
|
2442
2409
|
<div style="display: flex; gap: 10px; margin-top: 20px;">
|
|
@@ -2486,7 +2453,7 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
2486
2453
|
|
|
2487
2454
|
try {
|
|
2488
2455
|
const token = localStorage.getItem('token');
|
|
2489
|
-
const response = await fetch('http://localhost:
|
|
2456
|
+
const response = await fetch('http://localhost:3000/api/knowledge', {
|
|
2490
2457
|
method: 'POST',
|
|
2491
2458
|
headers: {
|
|
2492
2459
|
'Content-Type': 'application/json',
|
|
@@ -2495,7 +2462,7 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
2495
2462
|
body: JSON.stringify({
|
|
2496
2463
|
title,
|
|
2497
2464
|
content,
|
|
2498
|
-
category: category || '
|
|
2465
|
+
category: category || '未分�?,
|
|
2499
2466
|
tags,
|
|
2500
2467
|
groupId: currentGroup._id,
|
|
2501
2468
|
status,
|
|
@@ -2511,24 +2478,22 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
2511
2478
|
if (result.success) {
|
|
2512
2479
|
alert('知识库文档创建成功!');
|
|
2513
2480
|
modal.remove();
|
|
2514
|
-
//
|
|
2515
|
-
const contentArea = document.getElementById('contentArea');
|
|
2481
|
+
// 重新加载知识库列�? const contentArea = document.getElementById('contentArea');
|
|
2516
2482
|
renderKnowledgeView(contentArea);
|
|
2517
2483
|
} else {
|
|
2518
2484
|
alert('创建失败: ' + (result.error?.message || result.message || '未知错误'));
|
|
2519
2485
|
}
|
|
2520
2486
|
} catch (error) {
|
|
2521
|
-
console.error('
|
|
2487
|
+
console.error('创建知识库文档失�?', error);
|
|
2522
2488
|
alert('创建失败: ' + error.message);
|
|
2523
2489
|
}
|
|
2524
2490
|
});
|
|
2525
2491
|
}
|
|
2526
2492
|
|
|
2527
|
-
//
|
|
2528
|
-
async function showKnowledgeDetail(knowledgeId) {
|
|
2493
|
+
// 显示知识库文档详�? async function showKnowledgeDetail(knowledgeId) {
|
|
2529
2494
|
try {
|
|
2530
2495
|
const token = localStorage.getItem('token');
|
|
2531
|
-
const response = await fetch(`http://localhost:
|
|
2496
|
+
const response = await fetch(`http://localhost:3000/api/knowledge/${knowledgeId}`, {
|
|
2532
2497
|
headers: { 'Authorization': `Bearer ${token}` }
|
|
2533
2498
|
});
|
|
2534
2499
|
const result = await response.json();
|
|
@@ -2549,14 +2514,14 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
2549
2514
|
</div>
|
|
2550
2515
|
<div style="padding: 20px;">
|
|
2551
2516
|
<div style="display: flex; gap: 20px; margin-bottom: 20px; padding-bottom: 20px; border-bottom: 1px solid var(--border);">
|
|
2552
|
-
<div><strong
|
|
2553
|
-
<div><strong>状态:</strong><span class="status-badge status-${kb.status}">${kb.status === 'published' ? '
|
|
2554
|
-
<div><strong
|
|
2555
|
-
<div><strong
|
|
2517
|
+
<div><strong>分类�?/strong>${kb.category}</div>
|
|
2518
|
+
<div><strong>状态:</strong><span class="status-badge status-${kb.status}">${kb.status === 'published' ? '已发�? : kb.status === 'draft' ? '草稿' : '已归�?}</span></div>
|
|
2519
|
+
<div><strong>浏览�?/strong>${kb.views || 0}</div>
|
|
2520
|
+
<div><strong>点赞�?/strong>${kb.likes?.length || 0}</div>
|
|
2556
2521
|
</div>
|
|
2557
2522
|
${kb.tags && kb.tags.length > 0 ? `
|
|
2558
2523
|
<div style="margin-bottom: 20px;">
|
|
2559
|
-
<strong
|
|
2524
|
+
<strong>标签�?/strong>
|
|
2560
2525
|
${kb.tags.map(tag => `<span class="kb-tag" style="background: var(--bg-hover); padding: 4px 12px; border-radius: 12px; margin-right: 8px; font-size: 12px;">${tag}</span>`).join('')}
|
|
2561
2526
|
</div>
|
|
2562
2527
|
` : ''}
|
|
@@ -2565,8 +2530,8 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
2565
2530
|
</div>
|
|
2566
2531
|
<div style="margin-top: 20px; padding-top: 20px; border-top: 1px solid var(--border); color: var(--text-secondary); font-size: 13px;">
|
|
2567
2532
|
<div>作者:${kb.author?.username || '未知'}</div>
|
|
2568
|
-
<div
|
|
2569
|
-
<div
|
|
2533
|
+
<div>创建时间�?{new Date(kb.createdAt).toLocaleString()}</div>
|
|
2534
|
+
<div>更新时间�?{new Date(kb.updatedAt).toLocaleString()}</div>
|
|
2570
2535
|
</div>
|
|
2571
2536
|
</div>
|
|
2572
2537
|
</div>
|
|
@@ -2584,7 +2549,7 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
2584
2549
|
}
|
|
2585
2550
|
});
|
|
2586
2551
|
} catch (error) {
|
|
2587
|
-
console.error('
|
|
2552
|
+
console.error('加载知识库详情失�?', error);
|
|
2588
2553
|
alert('加载失败: ' + error.message);
|
|
2589
2554
|
}
|
|
2590
2555
|
}
|
|
@@ -2593,7 +2558,7 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
2593
2558
|
async function showEditKnowledgeModal(knowledgeId) {
|
|
2594
2559
|
try {
|
|
2595
2560
|
const token = localStorage.getItem('token');
|
|
2596
|
-
const response = await fetch(`http://localhost:
|
|
2561
|
+
const response = await fetch(`http://localhost:3000/api/knowledge/${knowledgeId}`, {
|
|
2597
2562
|
headers: { 'Authorization': `Bearer ${token}` }
|
|
2598
2563
|
});
|
|
2599
2564
|
const result = await response.json();
|
|
@@ -2609,7 +2574,7 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
2609
2574
|
modal.innerHTML = `
|
|
2610
2575
|
<div class="modal-content" style="max-width: 800px;">
|
|
2611
2576
|
<div class="modal-header">
|
|
2612
|
-
<h3>✏️
|
|
2577
|
+
<h3>✏️ 编辑知识库文�?/h3>
|
|
2613
2578
|
<button class="close-btn" id="closeEditKnowledge">×</button>
|
|
2614
2579
|
</div>
|
|
2615
2580
|
<form id="editKnowledgeForm">
|
|
@@ -2622,7 +2587,7 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
2622
2587
|
<input type="text" id="editKbCategory" value="${kb.category || ''}">
|
|
2623
2588
|
</div>
|
|
2624
2589
|
<div class="form-group">
|
|
2625
|
-
<label
|
|
2590
|
+
<label>标签(用逗号分隔�?/label>
|
|
2626
2591
|
<input type="text" id="editKbTags" value="${kb.tags ? kb.tags.join(', ') : ''}">
|
|
2627
2592
|
</div>
|
|
2628
2593
|
<div class="form-group">
|
|
@@ -2630,7 +2595,7 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
2630
2595
|
<textarea id="editKbContent" rows="10" required>${kb.content}</textarea>
|
|
2631
2596
|
</div>
|
|
2632
2597
|
<div class="form-group">
|
|
2633
|
-
<label
|
|
2598
|
+
<label>状�?/label>
|
|
2634
2599
|
<select id="editKbStatus">
|
|
2635
2600
|
<option value="draft" ${kb.status === 'draft' ? 'selected' : ''}>草稿</option>
|
|
2636
2601
|
<option value="published" ${kb.status === 'published' ? 'selected' : ''}>发布</option>
|
|
@@ -2642,15 +2607,15 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
2642
2607
|
<select id="editKbReadPermission">
|
|
2643
2608
|
<option value="group" ${kb.permissions?.read === 'group' ? 'selected' : ''}>群组成员可见</option>
|
|
2644
2609
|
<option value="public" ${kb.permissions?.read === 'public' ? 'selected' : ''}>公开</option>
|
|
2645
|
-
<option value="private" ${kb.permissions?.read === 'private' ? 'selected' : ''}
|
|
2610
|
+
<option value="private" ${kb.permissions?.read === 'private' ? 'selected' : ''}>仅自己可�?/option>
|
|
2646
2611
|
</select>
|
|
2647
2612
|
</div>
|
|
2648
2613
|
<div class="form-group">
|
|
2649
2614
|
<label>编辑权限</label>
|
|
2650
2615
|
<select id="editKbWritePermission">
|
|
2651
|
-
<option value="author" ${kb.permissions?.write === 'author' ? 'selected' : ''}
|
|
2652
|
-
<option value="admin" ${kb.permissions?.write === 'admin' ? 'selected' : ''}
|
|
2653
|
-
<option value="all" ${kb.permissions?.write === 'all' ? 'selected' : ''}
|
|
2616
|
+
<option value="author" ${kb.permissions?.write === 'author' ? 'selected' : ''}>仅作�?/option>
|
|
2617
|
+
<option value="admin" ${kb.permissions?.write === 'admin' ? 'selected' : ''}>管理�?/option>
|
|
2618
|
+
<option value="all" ${kb.permissions?.write === 'all' ? 'selected' : ''}>所有成�?/option>
|
|
2654
2619
|
</select>
|
|
2655
2620
|
</div>
|
|
2656
2621
|
<div style="display: flex; gap: 10px; margin-top: 20px;">
|
|
@@ -2697,7 +2662,7 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
2697
2662
|
const tags = tagsInput ? tagsInput.split(',').map(tag => tag.trim()).filter(tag => tag) : [];
|
|
2698
2663
|
|
|
2699
2664
|
try {
|
|
2700
|
-
const updateResponse = await fetch(`http://localhost:
|
|
2665
|
+
const updateResponse = await fetch(`http://localhost:3000/api/knowledge/${knowledgeId}`, {
|
|
2701
2666
|
method: 'PUT',
|
|
2702
2667
|
headers: {
|
|
2703
2668
|
'Content-Type': 'application/json',
|
|
@@ -2706,7 +2671,7 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
2706
2671
|
body: JSON.stringify({
|
|
2707
2672
|
title,
|
|
2708
2673
|
content,
|
|
2709
|
-
category: category || '
|
|
2674
|
+
category: category || '未分�?,
|
|
2710
2675
|
tags,
|
|
2711
2676
|
status,
|
|
2712
2677
|
permissions: {
|
|
@@ -2722,19 +2687,18 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
2722
2687
|
if (updateResult.success) {
|
|
2723
2688
|
alert('知识库文档更新成功!');
|
|
2724
2689
|
modal.remove();
|
|
2725
|
-
//
|
|
2726
|
-
const contentArea = document.getElementById('contentArea');
|
|
2690
|
+
// 重新加载知识库列�? const contentArea = document.getElementById('contentArea');
|
|
2727
2691
|
renderKnowledgeView(contentArea);
|
|
2728
2692
|
} else {
|
|
2729
2693
|
alert('更新失败: ' + (updateResult.error?.message || updateResult.message || '未知错误'));
|
|
2730
2694
|
}
|
|
2731
2695
|
} catch (error) {
|
|
2732
|
-
console.error('
|
|
2696
|
+
console.error('更新知识库文档失�?', error);
|
|
2733
2697
|
alert('更新失败: ' + error.message);
|
|
2734
2698
|
}
|
|
2735
2699
|
});
|
|
2736
2700
|
} catch (error) {
|
|
2737
|
-
console.error('
|
|
2701
|
+
console.error('加载知识库文档失�?', error);
|
|
2738
2702
|
alert('加载失败: ' + error.message);
|
|
2739
2703
|
}
|
|
2740
2704
|
}
|
|
@@ -2742,13 +2706,13 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
2742
2706
|
// 工作流视图(管理员)
|
|
2743
2707
|
async function renderWorkflowsView(container) {
|
|
2744
2708
|
if (!currentGroup) {
|
|
2745
|
-
container.innerHTML = '<div class="empty-state"
|
|
2709
|
+
container.innerHTML = '<div class="empty-state">请先选择一个群�?/div>';
|
|
2746
2710
|
return;
|
|
2747
2711
|
}
|
|
2748
2712
|
|
|
2749
2713
|
try {
|
|
2750
2714
|
const token = localStorage.getItem('token');
|
|
2751
|
-
const response = await fetch(`http://localhost:
|
|
2715
|
+
const response = await fetch(`http://localhost:3000/api/workflows/group/${currentGroup._id}`, {
|
|
2752
2716
|
headers: { 'Authorization': `Bearer ${token}` }
|
|
2753
2717
|
});
|
|
2754
2718
|
const result = await response.json();
|
|
@@ -2756,36 +2720,33 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
2756
2720
|
|
|
2757
2721
|
container.innerHTML = `
|
|
2758
2722
|
<div class="view-header">
|
|
2759
|
-
<h2>🔄
|
|
2760
|
-
<button class="btn-primary" id="createWorkflowBtn">+
|
|
2723
|
+
<h2>🔄 工作流管�?- ${currentGroup.name}</h2>
|
|
2724
|
+
<button class="btn-primary" id="createWorkflowBtn">+ 创建工作�?/button>
|
|
2761
2725
|
</div>
|
|
2762
2726
|
|
|
2763
2727
|
<div class="info-box" style="background: var(--bg-card); padding: 20px; border-radius: 12px; margin-bottom: 20px; border-left: 4px solid var(--primary);">
|
|
2764
2728
|
<h3 style="margin-bottom: 10px;">💡 什么是工作流?</h3>
|
|
2765
2729
|
<p style="color: var(--text-secondary); line-height: 1.6; margin-bottom: 15px;">
|
|
2766
|
-
|
|
2767
|
-
</p>
|
|
2730
|
+
工作流是一�?strong>自动化流程引�?/strong>,可以自动执行一系列预定义的操作,提高团队协作效率�? </p>
|
|
2768
2731
|
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 15px; margin-top: 15px;">
|
|
2769
2732
|
<div style="background: var(--bg-dark); padding: 15px; border-radius: 8px;">
|
|
2770
2733
|
<div style="font-size: 24px; margin-bottom: 8px;">📋</div>
|
|
2771
2734
|
<strong>文档审批流程</strong>
|
|
2772
2735
|
<p style="font-size: 13px; color: var(--text-secondary); margin-top: 5px;">
|
|
2773
|
-
|
|
2774
|
-
</p>
|
|
2736
|
+
自动通知审批�?�?等待审批 �?审批通过后发�? </p>
|
|
2775
2737
|
</div>
|
|
2776
2738
|
<div style="background: var(--bg-dark); padding: 15px; border-radius: 8px;">
|
|
2777
|
-
<div style="font-size: 24px; margin-bottom: 8px;"
|
|
2739
|
+
<div style="font-size: 24px; margin-bottom: 8px;">�?/div>
|
|
2778
2740
|
<strong>任务自动分配</strong>
|
|
2779
2741
|
<p style="font-size: 13px; color: var(--text-secondary); margin-top: 5px;">
|
|
2780
|
-
|
|
2742
|
+
新任务创�?�?自动分配成员 �?发送通知
|
|
2781
2743
|
</p>
|
|
2782
2744
|
</div>
|
|
2783
2745
|
<div style="background: var(--bg-dark); padding: 15px; border-radius: 8px;">
|
|
2784
|
-
<div style="font-size: 24px; margin-bottom: 8px;"
|
|
2746
|
+
<div style="font-size: 24px; margin-bottom: 8px;">�?/div>
|
|
2785
2747
|
<strong>定时报告生成</strong>
|
|
2786
2748
|
<p style="font-size: 13px; color: var(--text-secondary); margin-top: 5px;">
|
|
2787
|
-
每天凌晨2
|
|
2788
|
-
</p>
|
|
2749
|
+
每天凌晨2�?�?收集数据 �?生成报告 �?发送邮�? </p>
|
|
2789
2750
|
</div>
|
|
2790
2751
|
</div>
|
|
2791
2752
|
</div>
|
|
@@ -2796,17 +2757,17 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
2796
2757
|
<div class="modal hidden" id="workflowModal">
|
|
2797
2758
|
<div class="modal-content" style="max-width: 720px;">
|
|
2798
2759
|
<div class="modal-header">
|
|
2799
|
-
<h3 id="workflowModalTitle"
|
|
2760
|
+
<h3 id="workflowModalTitle">创建工作�?/h3>
|
|
2800
2761
|
<button class="modal-close" id="closeWorkflowModal">×</button>
|
|
2801
2762
|
</div>
|
|
2802
2763
|
<form id="workflowForm">
|
|
2803
2764
|
<div class="form-group">
|
|
2804
2765
|
<label>名称 *</label>
|
|
2805
|
-
<input type="text" id="wfName" required placeholder="
|
|
2766
|
+
<input type="text" id="wfName" required placeholder="例如:文档审批流�?>
|
|
2806
2767
|
</div>
|
|
2807
2768
|
<div class="form-group">
|
|
2808
2769
|
<label>描述</label>
|
|
2809
|
-
<textarea id="wfDescription" rows="2" placeholder="
|
|
2770
|
+
<textarea id="wfDescription" rows="2" placeholder="说明此工作流的用�?></textarea>
|
|
2810
2771
|
</div>
|
|
2811
2772
|
<div class="form-group">
|
|
2812
2773
|
<label>触发方式</label>
|
|
@@ -2817,17 +2778,17 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
2817
2778
|
</div>
|
|
2818
2779
|
<div class="form-group" id="wfScheduleGroup">
|
|
2819
2780
|
<label>定时表达式(可选)</label>
|
|
2820
|
-
<input type="text" id="wfSchedule" placeholder="
|
|
2821
|
-
<small>使用 Cron
|
|
2781
|
+
<input type="text" id="wfSchedule" placeholder="例如�? 2 * * * 表示每天凌晨2�?>
|
|
2782
|
+
<small>使用 Cron 表达式配置定时触发时�?/small>
|
|
2822
2783
|
</div>
|
|
2823
2784
|
<div class="form-group">
|
|
2824
|
-
<label>步骤配置(JSON
|
|
2825
|
-
<textarea id="wfSteps" rows="6" placeholder='例如:[{"name":"
|
|
2826
|
-
<small
|
|
2785
|
+
<label>步骤配置(JSON 数组�?/label>
|
|
2786
|
+
<textarea id="wfSteps" rows="6" placeholder='例如:[{"name":"通知审批�?,"type":"notification","config":{"message":"有新文档需要审�?}}]'></textarea>
|
|
2787
|
+
<small>高级用法:直接编�?JSON,字段与后端 Workflow.steps 一�?/small>
|
|
2827
2788
|
</div>
|
|
2828
2789
|
<div class="form-actions">
|
|
2829
2790
|
<button type="button" class="btn-secondary" id="cancelWorkflow">取消</button>
|
|
2830
|
-
<button type="submit" class="btn-primary"
|
|
2791
|
+
<button type="submit" class="btn-primary">保存工作�?/button>
|
|
2831
2792
|
</div>
|
|
2832
2793
|
</form>
|
|
2833
2794
|
</div>
|
|
@@ -2837,17 +2798,17 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
2837
2798
|
const workflowsList = document.getElementById('workflowsList');
|
|
2838
2799
|
|
|
2839
2800
|
if (workflows.length === 0) {
|
|
2840
|
-
workflowsList.innerHTML = '<div class="empty-state"
|
|
2801
|
+
workflowsList.innerHTML = '<div class="empty-state">暂无工作�?br><small style="color: var(--text-secondary);">点击"创建工作�?开始自动化您的工作流程</small></div>';
|
|
2841
2802
|
} else {
|
|
2842
2803
|
workflows.forEach(wf => {
|
|
2843
2804
|
const wfCard = document.createElement('div');
|
|
2844
2805
|
wfCard.className = 'workflow-card';
|
|
2845
2806
|
const triggerType = wf.trigger && wf.trigger.type ? wf.trigger.type : 'manual';
|
|
2846
2807
|
const triggerLabel = triggerType === 'manual'
|
|
2847
|
-
? '
|
|
2808
|
+
? '🖱�?手动'
|
|
2848
2809
|
: triggerType === 'scheduled'
|
|
2849
|
-
? '
|
|
2850
|
-
: '
|
|
2810
|
+
? '�?定时'
|
|
2811
|
+
: '�?事件';
|
|
2851
2812
|
const stepsCount = Array.isArray(wf.steps) ? wf.steps.length : 0;
|
|
2852
2813
|
const totalExec = wf.stats && typeof wf.stats.totalExecutions === 'number'
|
|
2853
2814
|
? wf.stats.totalExecutions
|
|
@@ -2856,13 +2817,13 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
2856
2817
|
wfCard.innerHTML = `
|
|
2857
2818
|
<div class="wf-header">
|
|
2858
2819
|
<h3>${wf.name}</h3>
|
|
2859
|
-
<span class="wf-status ${wf.status}">${wf.status === 'active' ? '
|
|
2820
|
+
<span class="wf-status ${wf.status}">${wf.status === 'active' ? '�?已激�? : wf.status === 'inactive' ? '⏸️ 已停�? : '📝 草稿'}</span>
|
|
2860
2821
|
</div>
|
|
2861
2822
|
<p>${wf.description || '暂无描述'}</p>
|
|
2862
2823
|
<div class="wf-meta">
|
|
2863
|
-
<span
|
|
2824
|
+
<span>触发�? ${triggerLabel}</span>
|
|
2864
2825
|
<span>步骤: ${stepsCount}</span>
|
|
2865
|
-
<span>执行: ${totalExec}
|
|
2826
|
+
<span>执行: ${totalExec} �?/span>
|
|
2866
2827
|
</div>
|
|
2867
2828
|
<div class="wf-actions">
|
|
2868
2829
|
<button class="btn-primary btn-sm" data-id="${wf._id}" data-action="view-wf">查看详情</button>
|
|
@@ -2871,7 +2832,7 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
2871
2832
|
<button class="btn-secondary btn-sm" data-id="${wf._id}" data-action="trigger-wf">手动触发</button>
|
|
2872
2833
|
<button class="btn-warning btn-sm" data-id="${wf._id}" data-action="deactivate-wf">停用</button>
|
|
2873
2834
|
` : `
|
|
2874
|
-
<button class="btn-success btn-sm" data-id="${wf._id}" data-action="activate-wf"
|
|
2835
|
+
<button class="btn-success btn-sm" data-id="${wf._id}" data-action="activate-wf">激�?/button>
|
|
2875
2836
|
`}
|
|
2876
2837
|
<button class="btn-danger btn-sm" data-id="${wf._id}" data-action="delete-wf">删除</button>
|
|
2877
2838
|
</div>
|
|
@@ -2880,20 +2841,19 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
2880
2841
|
});
|
|
2881
2842
|
}
|
|
2882
2843
|
|
|
2883
|
-
//
|
|
2884
|
-
document.querySelectorAll('[data-action="view-wf"]').forEach(btn => {
|
|
2844
|
+
// 详情查看(暂时简单提示,可后续扩展为真正详情面板�? document.querySelectorAll('[data-action="view-wf"]').forEach(btn => {
|
|
2885
2845
|
const wf = workflows.find(w => w._id === btn.dataset.id);
|
|
2886
2846
|
btn.addEventListener('click', () => {
|
|
2887
|
-
alert(`工作流详情:\n\n
|
|
2847
|
+
alert(`工作流详情:\n\n名称�?{wf.name}\n状态:${wf.status}\n触发方式�?{wf.trigger?.type || 'manual'}\n步骤数:${(wf.steps || []).length}`);
|
|
2888
2848
|
});
|
|
2889
2849
|
});
|
|
2890
2850
|
|
|
2891
2851
|
// 手动触发
|
|
2892
2852
|
document.querySelectorAll('[data-action="trigger-wf"]').forEach(btn => {
|
|
2893
2853
|
btn.addEventListener('click', async () => {
|
|
2894
|
-
if (confirm('
|
|
2854
|
+
if (confirm('确定要手动触发此工作流吗�?)) {
|
|
2895
2855
|
try {
|
|
2896
|
-
await fetch(`http://localhost:
|
|
2856
|
+
await fetch(`http://localhost:3000/api/workflows/${btn.dataset.id}/trigger`, {
|
|
2897
2857
|
method: 'POST',
|
|
2898
2858
|
headers: {
|
|
2899
2859
|
'Content-Type': 'application/json',
|
|
@@ -2901,7 +2861,7 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
2901
2861
|
},
|
|
2902
2862
|
body: JSON.stringify({ triggerData: {} })
|
|
2903
2863
|
});
|
|
2904
|
-
alert('
|
|
2864
|
+
alert('工作流已触发�?);
|
|
2905
2865
|
} catch (error) {
|
|
2906
2866
|
alert('触发失败: ' + error.message);
|
|
2907
2867
|
}
|
|
@@ -2909,12 +2869,12 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
2909
2869
|
});
|
|
2910
2870
|
});
|
|
2911
2871
|
|
|
2912
|
-
//
|
|
2872
|
+
// 激�?停用
|
|
2913
2873
|
document.querySelectorAll('[data-action="activate-wf"], [data-action="deactivate-wf"]').forEach(btn => {
|
|
2914
2874
|
btn.addEventListener('click', async () => {
|
|
2915
2875
|
const action = btn.dataset.action === 'activate-wf' ? 'activate' : 'deactivate';
|
|
2916
2876
|
try {
|
|
2917
|
-
await fetch(`http://localhost:
|
|
2877
|
+
await fetch(`http://localhost:3000/api/workflows/${btn.dataset.id}/${action}`, {
|
|
2918
2878
|
method: 'POST',
|
|
2919
2879
|
headers: { 'Authorization': `Bearer ${token}` }
|
|
2920
2880
|
});
|
|
@@ -2926,12 +2886,11 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
2926
2886
|
});
|
|
2927
2887
|
});
|
|
2928
2888
|
|
|
2929
|
-
//
|
|
2930
|
-
document.querySelectorAll('[data-action="delete-wf"]').forEach(btn => {
|
|
2889
|
+
// 删除工作�? document.querySelectorAll('[data-action="delete-wf"]').forEach(btn => {
|
|
2931
2890
|
btn.addEventListener('click', async () => {
|
|
2932
2891
|
if (confirm('确定要删除这个工作流吗?删除后无法恢复!')) {
|
|
2933
2892
|
try {
|
|
2934
|
-
await fetch(`http://localhost:
|
|
2893
|
+
await fetch(`http://localhost:3000/api/workflows/${btn.dataset.id}`, {
|
|
2935
2894
|
method: 'DELETE',
|
|
2936
2895
|
headers: { 'Authorization': `Bearer ${token}` }
|
|
2937
2896
|
});
|
|
@@ -2964,7 +2923,7 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
2964
2923
|
|
|
2965
2924
|
function openWorkflowModal(workflow) {
|
|
2966
2925
|
editingWorkflow = workflow || null;
|
|
2967
|
-
workflowModalTitle.textContent = workflow ? '
|
|
2926
|
+
workflowModalTitle.textContent = workflow ? '编辑工作�? : '创建工作�?;
|
|
2968
2927
|
nameInput.value = workflow?.name || '';
|
|
2969
2928
|
descInput.value = workflow?.description || '';
|
|
2970
2929
|
const type = workflow?.trigger?.type || 'manual';
|
|
@@ -2996,8 +2955,7 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
2996
2955
|
btn.addEventListener('click', () => openWorkflowModal(wf));
|
|
2997
2956
|
});
|
|
2998
2957
|
|
|
2999
|
-
//
|
|
3000
|
-
workflowForm.addEventListener('submit', async (e) => {
|
|
2958
|
+
// 保存工作�? workflowForm.addEventListener('submit', async (e) => {
|
|
3001
2959
|
e.preventDefault();
|
|
3002
2960
|
const name = nameInput.value.trim();
|
|
3003
2961
|
if (!name) {
|
|
@@ -3014,11 +2972,11 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
3014
2972
|
try {
|
|
3015
2973
|
const parsed = JSON.parse(stepsText);
|
|
3016
2974
|
if (!Array.isArray(parsed)) {
|
|
3017
|
-
throw new Error('
|
|
2975
|
+
throw new Error('步骤配置必须�?JSON 数组');
|
|
3018
2976
|
}
|
|
3019
2977
|
steps = parsed;
|
|
3020
2978
|
} catch (err) {
|
|
3021
|
-
alert('步骤配置必须是合法的 JSON
|
|
2979
|
+
alert('步骤配置必须是合法的 JSON 数组�? + err.message);
|
|
3022
2980
|
return;
|
|
3023
2981
|
}
|
|
3024
2982
|
}
|
|
@@ -3035,8 +2993,8 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
3035
2993
|
}
|
|
3036
2994
|
|
|
3037
2995
|
const url = editingWorkflow
|
|
3038
|
-
? `http://localhost:
|
|
3039
|
-
: 'http://localhost:
|
|
2996
|
+
? `http://localhost:3000/api/workflows/${editingWorkflow._id}`
|
|
2997
|
+
: 'http://localhost:3000/api/workflows';
|
|
3040
2998
|
const method = editingWorkflow ? 'PUT' : 'POST';
|
|
3041
2999
|
|
|
3042
3000
|
try {
|
|
@@ -3061,10 +3019,10 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
3061
3019
|
});
|
|
3062
3020
|
|
|
3063
3021
|
} catch (error) {
|
|
3064
|
-
console.error('
|
|
3022
|
+
console.error('加载工作流失�?', error);
|
|
3065
3023
|
container.innerHTML = `
|
|
3066
3024
|
<div class="view-header">
|
|
3067
|
-
<h2>🔄
|
|
3025
|
+
<h2>🔄 工作流管�?/h2>
|
|
3068
3026
|
</div>
|
|
3069
3027
|
<div class="empty-state">加载失败: ${error.message}</div>
|
|
3070
3028
|
`;
|
|
@@ -3073,10 +3031,10 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
3073
3031
|
|
|
3074
3032
|
function getStatusText(status) {
|
|
3075
3033
|
const statusMap = {
|
|
3076
|
-
'pending': '
|
|
3077
|
-
'in_progress': '
|
|
3078
|
-
'completed': '
|
|
3079
|
-
'terminated': '
|
|
3034
|
+
'pending': '待处�?,
|
|
3035
|
+
'in_progress': '进行�?,
|
|
3036
|
+
'completed': '已完�?,
|
|
3037
|
+
'terminated': '已终�?
|
|
3080
3038
|
};
|
|
3081
3039
|
return statusMap[status] || status;
|
|
3082
3040
|
}
|
|
@@ -3088,13 +3046,13 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
3088
3046
|
const token = localStorage.getItem('token');
|
|
3089
3047
|
|
|
3090
3048
|
// 获取备份列表
|
|
3091
|
-
const backupsResponse = await fetch('http://localhost:
|
|
3049
|
+
const backupsResponse = await fetch('http://localhost:3000/api/backup/list', {
|
|
3092
3050
|
headers: { 'Authorization': `Bearer ${token}` }
|
|
3093
3051
|
});
|
|
3094
3052
|
const backupsResult = await backupsResponse.json();
|
|
3095
3053
|
|
|
3096
3054
|
// 获取备份配置
|
|
3097
|
-
const configResponse = await fetch('http://localhost:
|
|
3055
|
+
const configResponse = await fetch('http://localhost:3000/api/backup/config', {
|
|
3098
3056
|
headers: { 'Authorization': `Bearer ${token}` }
|
|
3099
3057
|
});
|
|
3100
3058
|
const configResult = await configResponse.json();
|
|
@@ -3107,7 +3065,7 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
3107
3065
|
<h2>💾 备份管理</h2>
|
|
3108
3066
|
<div style="display: flex; gap: 10px;">
|
|
3109
3067
|
<button class="btn-primary" id="createBackupBtn">
|
|
3110
|
-
<span style="font-size: 18px;"
|
|
3068
|
+
<span style="font-size: 18px;">�?/span> 立即备份
|
|
3111
3069
|
</button>
|
|
3112
3070
|
<button class="btn-secondary" id="configBackupBtn">
|
|
3113
3071
|
<span style="font-size: 18px;">⚙️</span> 备份设置
|
|
@@ -3129,11 +3087,11 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
3129
3087
|
|
|
3130
3088
|
<div class="stat-card-modern">
|
|
3131
3089
|
<div class="stat-icon" style="background: linear-gradient(135deg, #10b981 0%, #059669 100%);">
|
|
3132
|
-
${config.autoBackup ? '
|
|
3090
|
+
${config.autoBackup ? '�? : '�?}
|
|
3133
3091
|
</div>
|
|
3134
3092
|
<div class="stat-content">
|
|
3135
3093
|
<div class="stat-label">自动备份</div>
|
|
3136
|
-
<div class="stat-value">${config.autoBackup ? '
|
|
3094
|
+
<div class="stat-value">${config.autoBackup ? '已启�? : '已禁�?}</div>
|
|
3137
3095
|
</div>
|
|
3138
3096
|
</div>
|
|
3139
3097
|
|
|
@@ -3143,7 +3101,7 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
3143
3101
|
</div>
|
|
3144
3102
|
<div class="stat-content">
|
|
3145
3103
|
<div class="stat-label">备份频率</div>
|
|
3146
|
-
<div class="stat-value">${config.schedule || '
|
|
3104
|
+
<div class="stat-value">${config.schedule || '未设�?}</div>
|
|
3147
3105
|
</div>
|
|
3148
3106
|
</div>
|
|
3149
3107
|
|
|
@@ -3153,7 +3111,7 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
3153
3111
|
</div>
|
|
3154
3112
|
<div class="stat-content">
|
|
3155
3113
|
<div class="stat-label">保留天数</div>
|
|
3156
|
-
<div class="stat-value">${config.retention || 30}
|
|
3114
|
+
<div class="stat-value">${config.retention || 30} �?/div>
|
|
3157
3115
|
</div>
|
|
3158
3116
|
</div>
|
|
3159
3117
|
</div>
|
|
@@ -3161,14 +3119,13 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
3161
3119
|
<!-- 备份列表 -->
|
|
3162
3120
|
<div class="backup-list-section">
|
|
3163
3121
|
<h3 style="margin: 30px 0 20px; color: var(--text-primary); font-size: 20px;">
|
|
3164
|
-
📦
|
|
3165
|
-
</h3>
|
|
3122
|
+
📦 最近备�? </h3>
|
|
3166
3123
|
<div class="backup-cards-grid" id="backupCards">
|
|
3167
3124
|
${backups.length === 0 ? `
|
|
3168
3125
|
<div class="empty-state-modern">
|
|
3169
3126
|
<div class="empty-icon">📭</div>
|
|
3170
3127
|
<h3>暂无备份记录</h3>
|
|
3171
|
-
<p>点击"立即备份"
|
|
3128
|
+
<p>点击"立即备份"创建第一个备�?/p>
|
|
3172
3129
|
<button class="btn-primary" onclick="document.getElementById('createBackupBtn').click()">
|
|
3173
3130
|
立即备份
|
|
3174
3131
|
</button>
|
|
@@ -3201,11 +3158,11 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
3201
3158
|
const typeConfig = {
|
|
3202
3159
|
manual: { icon: '📦', label: '手动备份', color: '#6366f1' },
|
|
3203
3160
|
scheduled: { icon: '🔄', label: '定时备份', color: '#10b981' },
|
|
3204
|
-
auto: { icon: '
|
|
3161
|
+
auto: { icon: '�?, label: '自动备份', color: '#f59e0b' }
|
|
3205
3162
|
};
|
|
3206
3163
|
|
|
3207
3164
|
const config = typeConfig[backup.type] || typeConfig.manual;
|
|
3208
|
-
const statusIcon = backup.status === 'completed' ? '
|
|
3165
|
+
const statusIcon = backup.status === 'completed' ? '�? : backup.status === 'failed' ? '�? : '�?;
|
|
3209
3166
|
|
|
3210
3167
|
return `
|
|
3211
3168
|
<div class="backup-card-modern">
|
|
@@ -3226,8 +3183,8 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
3226
3183
|
<span class="info-value">${formatFileSize(backup.size)}</span>
|
|
3227
3184
|
</div>
|
|
3228
3185
|
<div class="backup-info-row">
|
|
3229
|
-
<span class="info-label"
|
|
3230
|
-
<span class="info-value">${backup.status === 'completed' ? '完成' : backup.status === 'failed' ? '失败' : '
|
|
3186
|
+
<span class="info-label">状�?/span>
|
|
3187
|
+
<span class="info-value">${backup.status === 'completed' ? '完成' : backup.status === 'failed' ? '失败' : '进行�?}</span>
|
|
3231
3188
|
</div>
|
|
3232
3189
|
</div>
|
|
3233
3190
|
|
|
@@ -3240,13 +3197,13 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
3240
3197
|
<span>🔄</span> 恢复
|
|
3241
3198
|
</button>
|
|
3242
3199
|
<button class="btn-action btn-delete" data-id="${backup._id}" data-action="delete">
|
|
3243
|
-
<span
|
|
3200
|
+
<span>🗑�?/span> 删除
|
|
3244
3201
|
</button>
|
|
3245
3202
|
</div>
|
|
3246
3203
|
` : `
|
|
3247
3204
|
<div class="backup-card-actions">
|
|
3248
3205
|
<button class="btn-action btn-delete" data-id="${backup._id}" data-action="delete">
|
|
3249
|
-
<span
|
|
3206
|
+
<span>🗑�?/span> 删除
|
|
3250
3207
|
</button>
|
|
3251
3208
|
</div>
|
|
3252
3209
|
`}
|
|
@@ -3268,7 +3225,7 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
3268
3225
|
<label>备份类型</label>
|
|
3269
3226
|
<select id="backupType" required>
|
|
3270
3227
|
<option value="full">完整备份(所有数据)</option>
|
|
3271
|
-
<option value="incremental"
|
|
3228
|
+
<option value="incremental">增量备份(仅变更�?/option>
|
|
3272
3229
|
</select>
|
|
3273
3230
|
</div>
|
|
3274
3231
|
<div class="form-group">
|
|
@@ -3276,7 +3233,7 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
3276
3233
|
<textarea id="backupDescription" rows="3" placeholder="备份说明..."></textarea>
|
|
3277
3234
|
</div>
|
|
3278
3235
|
<div style="display: flex; gap: 10px; margin-top: 20px;">
|
|
3279
|
-
<button type="submit" class="btn-primary"
|
|
3236
|
+
<button type="submit" class="btn-primary">开始备�?/button>
|
|
3280
3237
|
<button type="button" class="btn-secondary" id="cancelCreateBackup">取消</button>
|
|
3281
3238
|
</div>
|
|
3282
3239
|
</form>
|
|
@@ -3299,16 +3256,16 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
3299
3256
|
</div>
|
|
3300
3257
|
<div class="form-group">
|
|
3301
3258
|
<label>备份频率(Cron表达式)</label>
|
|
3302
|
-
<input type="text" id="backupSchedule" value="${config.schedule || '0 2 * * *'}" placeholder="0 2 * * * (每天凌晨2
|
|
3303
|
-
<small
|
|
3259
|
+
<input type="text" id="backupSchedule" value="${config.schedule || '0 2 * * *'}" placeholder="0 2 * * * (每天凌晨2�?">
|
|
3260
|
+
<small>示例�? 2 * * * (每天凌晨2�?�? */6 * * * (�?小时)</small>
|
|
3304
3261
|
</div>
|
|
3305
3262
|
<div class="form-group">
|
|
3306
3263
|
<label>保留天数</label>
|
|
3307
3264
|
<input type="number" id="backupRetention" value="${config.retention || 30}" min="1" max="365">
|
|
3308
|
-
<small
|
|
3265
|
+
<small>超过此天数的备份将自动删�?/small>
|
|
3309
3266
|
</div>
|
|
3310
3267
|
<div class="form-group">
|
|
3311
|
-
<label
|
|
3268
|
+
<label>最大备份数�?/label>
|
|
3312
3269
|
<input type="number" id="maxBackups" value="${config.maxBackups || 10}" min="1" max="100">
|
|
3313
3270
|
</div>
|
|
3314
3271
|
<div style="display: flex; gap: 10px; margin-top: 20px;">
|
|
@@ -3567,7 +3524,7 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
3567
3524
|
const description = document.getElementById('backupDescription').value;
|
|
3568
3525
|
|
|
3569
3526
|
try {
|
|
3570
|
-
const response = await fetch('http://localhost:
|
|
3527
|
+
const response = await fetch('http://localhost:3000/api/backup/create', {
|
|
3571
3528
|
method: 'POST',
|
|
3572
3529
|
headers: {
|
|
3573
3530
|
'Content-Type': 'application/json',
|
|
@@ -3578,7 +3535,7 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
3578
3535
|
|
|
3579
3536
|
const result = await response.json();
|
|
3580
3537
|
if (result.success) {
|
|
3581
|
-
alert('
|
|
3538
|
+
alert('备份创建成功�?);
|
|
3582
3539
|
document.getElementById('createBackupModal').classList.add('hidden');
|
|
3583
3540
|
renderOptimizedBackupView(container);
|
|
3584
3541
|
} else {
|
|
@@ -3600,7 +3557,7 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
3600
3557
|
};
|
|
3601
3558
|
|
|
3602
3559
|
try {
|
|
3603
|
-
const response = await fetch('http://localhost:
|
|
3560
|
+
const response = await fetch('http://localhost:3000/api/backup/config', {
|
|
3604
3561
|
method: 'PUT',
|
|
3605
3562
|
headers: {
|
|
3606
3563
|
'Content-Type': 'application/json',
|
|
@@ -3611,7 +3568,7 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
3611
3568
|
|
|
3612
3569
|
const result = await response.json();
|
|
3613
3570
|
if (result.success) {
|
|
3614
|
-
alert('
|
|
3571
|
+
alert('设置保存成功�?);
|
|
3615
3572
|
document.getElementById('configBackupModal').classList.add('hidden');
|
|
3616
3573
|
renderOptimizedBackupView(container);
|
|
3617
3574
|
} else {
|
|
@@ -3626,17 +3583,17 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
3626
3583
|
document.querySelectorAll('[data-action="download"]').forEach(btn => {
|
|
3627
3584
|
btn.addEventListener('click', () => {
|
|
3628
3585
|
const backupId = btn.dataset.id;
|
|
3629
|
-
window.location.href = `http://localhost:
|
|
3586
|
+
window.location.href = `http://localhost:3000/api/backup/${backupId}/download?token=${token}`;
|
|
3630
3587
|
});
|
|
3631
3588
|
});
|
|
3632
3589
|
|
|
3633
3590
|
document.querySelectorAll('[data-action="restore"]').forEach(btn => {
|
|
3634
3591
|
btn.addEventListener('click', async () => {
|
|
3635
|
-
if (!confirm('
|
|
3592
|
+
if (!confirm('确定要恢复此备份吗?这将覆盖当前数据�?)) return;
|
|
3636
3593
|
|
|
3637
3594
|
const backupId = btn.dataset.id;
|
|
3638
3595
|
try {
|
|
3639
|
-
const response = await fetch(`http://localhost:
|
|
3596
|
+
const response = await fetch(`http://localhost:3000/api/backup/${backupId}/restore`, {
|
|
3640
3597
|
method: 'POST',
|
|
3641
3598
|
headers: { 'Authorization': `Bearer ${token}` }
|
|
3642
3599
|
});
|
|
@@ -3660,14 +3617,14 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
3660
3617
|
|
|
3661
3618
|
const backupId = btn.dataset.id;
|
|
3662
3619
|
try {
|
|
3663
|
-
const response = await fetch(`http://localhost:
|
|
3620
|
+
const response = await fetch(`http://localhost:3000/api/backup/${backupId}`, {
|
|
3664
3621
|
method: 'DELETE',
|
|
3665
3622
|
headers: { 'Authorization': `Bearer ${token}` }
|
|
3666
3623
|
});
|
|
3667
3624
|
|
|
3668
3625
|
const result = await response.json();
|
|
3669
3626
|
if (result.success) {
|
|
3670
|
-
alert('
|
|
3627
|
+
alert('备份删除成功�?);
|
|
3671
3628
|
renderOptimizedBackupView(container);
|
|
3672
3629
|
} else {
|
|
3673
3630
|
alert('删除失败: ' + (result.error?.message || '未知错误'));
|