collabdocchat 2.0.5 → 2.0.6
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/src/components/knowledge-modal.js +14 -13
- package/src/components/optimized-poll-detail.js +19 -18
- package/src/main.js +3 -2
- package/src/pages/admin-dashboard.js +256 -255
- package/src/pages/login.js +9 -8
- package/src/pages/optimized-backup-view.js +2 -1
- package/src/pages/optimized-knowledge-view.js +2 -1
- package/src/pages/optimized-task-detail.js +38 -37
- package/src/pages/optimized-workflow-view.js +2 -1
- package/src/pages/simplified-workflows.js +62 -61
- package/src/pages/user-dashboard.js +82 -81
- package/src/services/api.js +5 -4
- package/src/services/auth.js +2 -1
- package/src/services/websocket.js +13 -12
- package/src/styles/collaboration-modern.js +10 -9
- package/src/utils/ai-assistant.js +76 -75
- package/src/utils/chat-enhancements.js +20 -19
- package/src/utils/collaboration-enhancer.js +86 -85
- package/src/utils/feature-integrator.js +80 -79
- package/src/utils/onboarding-guide.js +84 -83
- package/src/utils/performance.js +11 -10
- package/src/utils/permission-manager.js +41 -40
- package/src/utils/responsive-handler.js +24 -23
- package/src/utils/theme-manager.js +39 -38
- package/src/utils/ui-enhancements-loader.js +32 -31
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ApiService } from '../services/api.js';
|
|
1
|
+
import { ApiService } from '../services/api.js';
|
|
2
2
|
import { AuthService } from '../services/auth.js';
|
|
3
3
|
import { FeatureIntegrator } from '../utils/feature-integrator.js';
|
|
4
4
|
import { uiEnhancementsLoader } from '../utils/ui-enhancements-loader.js';
|
|
@@ -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">管理�?/span>
|
|
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">管理�?/div>
|
|
38
|
+
<div class="user-role">管理�?/div>
|
|
39
39
|
</div>
|
|
40
40
|
</div>
|
|
41
41
|
|
|
@@ -50,9 +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> 知识�? </button>
|
|
53
|
+
<span class="icon">📚</span> 知识�? </button>
|
|
54
54
|
<button class="nav-item" data-view="workflows">
|
|
55
|
-
<span class="icon">🔄</span> 工作�? </button>
|
|
55
|
+
<span class="icon">🔄</span> 工作�? </button>
|
|
56
56
|
<button class="nav-item" data-view="files">
|
|
57
57
|
<span class="icon">📎</span> 文件管理
|
|
58
58
|
</button>
|
|
@@ -78,11 +78,11 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
78
78
|
<span class="icon">⚙️</span> 设置
|
|
79
79
|
</button>
|
|
80
80
|
<button class="nav-item" data-view="help">
|
|
81
|
-
<span class="icon">�?/span> 帮助
|
|
81
|
+
<span class="icon">�?/span> 帮助
|
|
82
82
|
</button>
|
|
83
83
|
</div>
|
|
84
84
|
|
|
85
|
-
<button class="btn-logout" id="logoutBtn">退出登�?/button>
|
|
85
|
+
<button class="btn-logout" id="logoutBtn">退出登�?/button>
|
|
86
86
|
</aside>
|
|
87
87
|
|
|
88
88
|
<main class="main-content">
|
|
@@ -101,7 +101,7 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
101
101
|
});
|
|
102
102
|
});
|
|
103
103
|
|
|
104
|
-
// 退出登�? document.getElementById('logoutBtn').addEventListener('click', () => {
|
|
104
|
+
// 退出登�? document.getElementById('logoutBtn').addEventListener('click', () => {
|
|
105
105
|
authService.logout();
|
|
106
106
|
});
|
|
107
107
|
|
|
@@ -122,7 +122,7 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
122
122
|
await renderOptimizedKnowledgeView(contentArea, currentGroup, apiService);
|
|
123
123
|
break;
|
|
124
124
|
case 'workflows':
|
|
125
|
-
// 使用优化后的工作流视图(可视化创建,无需JSON代码�? if (typeof window.renderOptimizedWorkflowView === 'function') {
|
|
125
|
+
// 使用优化后的工作流视图(可视化创建,无需JSON代码�? if (typeof window.renderOptimizedWorkflowView === 'function') {
|
|
126
126
|
await window.renderOptimizedWorkflowView(contentArea, currentGroup, apiService);
|
|
127
127
|
} else {
|
|
128
128
|
await renderWorkflowsView(contentArea);
|
|
@@ -167,11 +167,11 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
167
167
|
<div class="groups-grid" id="groupsList"></div>
|
|
168
168
|
<div id="createGroupModal" class="modal hidden">
|
|
169
169
|
<div class="modal-content">
|
|
170
|
-
<h3>创建新群�?/h3>
|
|
170
|
+
<h3>创建新群�?/h3>
|
|
171
171
|
<form id="createGroupForm">
|
|
172
172
|
<div class="form-group">
|
|
173
173
|
<label>群组名称</label>
|
|
174
|
-
<input type="text" name="name" placeholder="请输入群组名�? required>
|
|
174
|
+
<input type="text" name="name" placeholder="请输入群组名�? required>
|
|
175
175
|
</div>
|
|
176
176
|
<div class="form-group">
|
|
177
177
|
<label>群组描述</label>
|
|
@@ -180,7 +180,7 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
180
180
|
<div class="form-group">
|
|
181
181
|
<label>添加成员(可选)</label>
|
|
182
182
|
<div id="usersList" style="max-height: 200px; overflow-y: auto; border: 1px solid var(--border); border-radius: 8px; padding: 10px;">
|
|
183
|
-
<p>加载�?..</p>
|
|
183
|
+
<p>加载�?..</p>
|
|
184
184
|
</div>
|
|
185
185
|
</div>
|
|
186
186
|
<div style="display: flex; gap: 10px;">
|
|
@@ -195,7 +195,7 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
195
195
|
<h3>管理成员</h3>
|
|
196
196
|
<div id="currentMembers"></div>
|
|
197
197
|
<div class="form-group">
|
|
198
|
-
<label>添加新成�?/label>
|
|
198
|
+
<label>添加新成�?/label>
|
|
199
199
|
<div id="availableUsers"></div>
|
|
200
200
|
</div>
|
|
201
201
|
<button type="button" class="btn-secondary" id="closeMembersModal">关闭</button>
|
|
@@ -226,7 +226,7 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
226
226
|
btn.addEventListener('click', () => {
|
|
227
227
|
currentGroup = groups.find(g => g._id === btn.dataset.id);
|
|
228
228
|
wsService.joinGroup(currentGroup._id);
|
|
229
|
-
alert(`已加入群�? ${currentGroup.name}`);
|
|
229
|
+
alert(`已加入群�? ${currentGroup.name}`);
|
|
230
230
|
});
|
|
231
231
|
});
|
|
232
232
|
|
|
@@ -261,7 +261,7 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
261
261
|
formData.get('description'),
|
|
262
262
|
selectedUsers
|
|
263
263
|
);
|
|
264
|
-
alert('群组创建成功�?);
|
|
264
|
+
alert('群组创建成功�?);
|
|
265
265
|
await renderGroupsView(container);
|
|
266
266
|
document.getElementById('createGroupModal').classList.add('hidden');
|
|
267
267
|
} catch (error) {
|
|
@@ -279,7 +279,7 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
279
279
|
<label style="display: flex; align-items: center; gap: 10px; padding: 8px; cursor: pointer;">
|
|
280
280
|
<input type="checkbox" value="${u._id}">
|
|
281
281
|
<div class="avatar" style="width: 30px; height: 30px; font-size: 14px;">${u.username[0].toUpperCase()}</div>
|
|
282
|
-
<span>${u.username} (${u.role === 'admin' ? '管理�? : '用户'})</span>
|
|
282
|
+
<span>${u.username} (${u.role === 'admin' ? '管理�? : '用户'})</span>
|
|
283
283
|
</label>
|
|
284
284
|
`).join('');
|
|
285
285
|
} catch (error) {
|
|
@@ -302,7 +302,7 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
302
302
|
<div style="display: flex; align-items: center; justify-content: space-between; padding: 8px; border-bottom: 1px solid var(--border);">
|
|
303
303
|
<div style="display: flex; align-items: center; gap: 10px;">
|
|
304
304
|
<div class="avatar" style="width: 30px; height: 30px; font-size: 14px;">${member.username[0].toUpperCase()}</div>
|
|
305
|
-
<span>${member.username} ${member._id.toString() === group.admin._id.toString() ? '(管理�?' : ''}</span>
|
|
305
|
+
<span>${member.username} ${member._id.toString() === group.admin._id.toString() ? '(管理�?' : ''}</span>
|
|
306
306
|
</div>
|
|
307
307
|
${member._id.toString() !== group.admin._id.toString() ?
|
|
308
308
|
`<button class="btn-secondary btn-sm" onclick="removeMember('${groupId}', '${member._id}')">移除</button>` :
|
|
@@ -317,7 +317,7 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
317
317
|
|
|
318
318
|
const availableUsersDiv = document.getElementById('availableUsers');
|
|
319
319
|
if (availableUsers.length === 0) {
|
|
320
|
-
availableUsersDiv.innerHTML = '<p>所有用户都已在群组�?/p>';
|
|
320
|
+
availableUsersDiv.innerHTML = '<p>所有用户都已在群组�?/p>';
|
|
321
321
|
} else {
|
|
322
322
|
availableUsersDiv.innerHTML = availableUsers.map(u => `
|
|
323
323
|
<div style="display: flex; align-items: center; justify-content: space-between; padding: 8px; border-bottom: 1px solid var(--border);">
|
|
@@ -337,10 +337,10 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
337
337
|
}
|
|
338
338
|
}
|
|
339
339
|
|
|
340
|
-
// 全局函数供按钮调�? window.addMember = async (groupId, userId) => {
|
|
340
|
+
// 全局函数供按钮调�? window.addMember = async (groupId, userId) => {
|
|
341
341
|
try {
|
|
342
342
|
await apiService.addMember(groupId, userId);
|
|
343
|
-
alert('成员添加成功�?);
|
|
343
|
+
alert('成员添加成功�?);
|
|
344
344
|
await showManageMembersModal(groupId);
|
|
345
345
|
} catch (error) {
|
|
346
346
|
alert('添加失败: ' + error.message);
|
|
@@ -351,7 +351,7 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
351
351
|
if (confirm('确定要移除该成员吗?')) {
|
|
352
352
|
try {
|
|
353
353
|
await apiService.removeMember(groupId, userId);
|
|
354
|
-
alert('成员移除成功�?);
|
|
354
|
+
alert('成员移除成功�?);
|
|
355
355
|
await showManageMembersModal(groupId);
|
|
356
356
|
} catch (error) {
|
|
357
357
|
alert('移除失败: ' + error.message);
|
|
@@ -361,7 +361,7 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
361
361
|
|
|
362
362
|
async function renderTasksView(container) {
|
|
363
363
|
if (!currentGroup) {
|
|
364
|
-
container.innerHTML = '<div class="empty-state">请先选择一个群�?/div>';
|
|
364
|
+
container.innerHTML = '<div class="empty-state">请先选择一个群�?/div>';
|
|
365
365
|
return;
|
|
366
366
|
}
|
|
367
367
|
|
|
@@ -375,15 +375,15 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
375
375
|
<div class="tasks-list" id="tasksList"></div>
|
|
376
376
|
<div id="createTaskModal" class="modal hidden">
|
|
377
377
|
<div class="modal-content">
|
|
378
|
-
<h3>创建新任�?/h3>
|
|
378
|
+
<h3>创建新任�?/h3>
|
|
379
379
|
<form id="createTaskForm">
|
|
380
380
|
<div class="form-group">
|
|
381
381
|
<label>任务标题</label>
|
|
382
|
-
<input type="text" name="title" placeholder="请输入任务标�? required>
|
|
382
|
+
<input type="text" name="title" placeholder="请输入任务标�? required>
|
|
383
383
|
</div>
|
|
384
384
|
<div class="form-group">
|
|
385
385
|
<label>任务描述</label>
|
|
386
|
-
<textarea name="description" placeholder="请输入任务描�?></textarea>
|
|
386
|
+
<textarea name="description" placeholder="请输入任务描�?></textarea>
|
|
387
387
|
</div>
|
|
388
388
|
<div class="form-group">
|
|
389
389
|
<label>截止日期</label>
|
|
@@ -403,7 +403,7 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
403
403
|
<button class="modal-close" id="closeTaskDetailModal">×</button>
|
|
404
404
|
</div>
|
|
405
405
|
<div class="modal-body" id="taskDetailContent">
|
|
406
|
-
<div class="loading">加载�?..</div>
|
|
406
|
+
<div class="loading">加载�?..</div>
|
|
407
407
|
</div>
|
|
408
408
|
</div>
|
|
409
409
|
</div>
|
|
@@ -426,16 +426,16 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
426
426
|
<div style="display: flex; justify-content: space-between; align-items: start;">
|
|
427
427
|
<div style="flex: 1;">
|
|
428
428
|
<h3>${task.title}</h3>
|
|
429
|
-
<p>${task.description || '无描�?}</p>
|
|
429
|
+
<p>${task.description || '无描�?}</p>
|
|
430
430
|
<div class="task-meta">
|
|
431
431
|
<span class="status-badge">${getStatusText(task.status)}</span>
|
|
432
|
-
<span>截止: ${task.deadline ? new Date(task.deadline).toLocaleDateString() : '�?}</span>
|
|
433
|
-
<span>完成�? ${completionRate}% (${completedMembers}/${totalMembers})</span>
|
|
432
|
+
<span>截止: ${task.deadline ? new Date(task.deadline).toLocaleDateString() : '�?}</span>
|
|
433
|
+
<span>完成�? ${completionRate}% (${completedMembers}/${totalMembers})</span>
|
|
434
434
|
</div>
|
|
435
435
|
</div>
|
|
436
436
|
<div style="display: flex; gap: 10px;">
|
|
437
437
|
<button class="btn-primary btn-sm" data-id="${task._id}" data-action="view-detail" title="查看详细">📊 详情</button>
|
|
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>
|
|
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>
|
|
439
439
|
</div>
|
|
440
440
|
</div>
|
|
441
441
|
`;
|
|
@@ -456,10 +456,10 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
456
456
|
btn.addEventListener('click', async (e) => {
|
|
457
457
|
e.stopPropagation();
|
|
458
458
|
const taskId = btn.dataset.id;
|
|
459
|
-
if (confirm('确定要删除这个任务吗?删除后无法恢复�?)) {
|
|
459
|
+
if (confirm('确定要删除这个任务吗?删除后无法恢复�?)) {
|
|
460
460
|
try {
|
|
461
461
|
await apiService.deleteTask(taskId);
|
|
462
|
-
alert('任务删除成功�?);
|
|
462
|
+
alert('任务删除成功�?);
|
|
463
463
|
await renderTasksView(container);
|
|
464
464
|
} catch (error) {
|
|
465
465
|
console.error('删除任务错误:', error);
|
|
@@ -482,16 +482,16 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
482
482
|
e.preventDefault();
|
|
483
483
|
const formData = new FormData(e.target);
|
|
484
484
|
try {
|
|
485
|
-
// 获取群组信息,自动分配给所有成�? const groupResult = await apiService.getGroup(currentGroup._id);
|
|
485
|
+
// 获取群组信息,自动分配给所有成�? const groupResult = await apiService.getGroup(currentGroup._id);
|
|
486
486
|
const memberIds = groupResult.group.members.map(m => m._id);
|
|
487
487
|
|
|
488
488
|
await apiService.createTask({
|
|
489
489
|
title: formData.get('title'),
|
|
490
490
|
description: formData.get('description'),
|
|
491
491
|
groupId: currentGroup._id,
|
|
492
|
-
assignedTo: memberIds, // 分配给所有成�? deadline: formData.get('deadline') || null
|
|
492
|
+
assignedTo: memberIds, // 分配给所有成�? deadline: formData.get('deadline') || null
|
|
493
493
|
});
|
|
494
|
-
alert('任务创建成功!已分配给所有群组成�?);
|
|
494
|
+
alert('任务创建成功!已分配给所有群组成�?);
|
|
495
495
|
await renderTasksView(container);
|
|
496
496
|
document.getElementById('createTaskModal').classList.add('hidden');
|
|
497
497
|
} catch (error) {
|
|
@@ -506,7 +506,7 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
506
506
|
const modal = document.getElementById('taskDetailModal');
|
|
507
507
|
const content = document.getElementById('taskDetailContent');
|
|
508
508
|
|
|
509
|
-
// 先设置关闭事件(只设置一次,避免重复�? const closeBtn = document.getElementById('closeTaskDetailModal');
|
|
509
|
+
// 先设置关闭事件(只设置一次,避免重复�? const closeBtn = document.getElementById('closeTaskDetailModal');
|
|
510
510
|
if (closeBtn && !closeBtn.dataset.listenerSet) {
|
|
511
511
|
closeBtn.addEventListener('click', () => {
|
|
512
512
|
modal.classList.add('hidden');
|
|
@@ -526,7 +526,7 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
526
526
|
|
|
527
527
|
try {
|
|
528
528
|
modal.classList.remove('hidden');
|
|
529
|
-
content.innerHTML = '<div class="loading">加载�?..</div>';
|
|
529
|
+
content.innerHTML = '<div class="loading">加载�?..</div>';
|
|
530
530
|
|
|
531
531
|
// 获取任务详情
|
|
532
532
|
const taskResult = await apiService.getTask(taskId);
|
|
@@ -534,12 +534,12 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
534
534
|
|
|
535
535
|
// 验证必要数据
|
|
536
536
|
if (!task) {
|
|
537
|
-
throw new Error('任务数据不存�?);
|
|
537
|
+
throw new Error('任务数据不存�?);
|
|
538
538
|
}
|
|
539
539
|
|
|
540
|
-
// 群组信息已经在task.group中(后端已populate�? const group = task.group;
|
|
540
|
+
// 群组信息已经在task.group中(后端已populate�? const group = task.group;
|
|
541
541
|
if (!group || !group.members) {
|
|
542
|
-
throw new Error('群组数据不完�?);
|
|
542
|
+
throw new Error('群组数据不完�?);
|
|
543
543
|
}
|
|
544
544
|
|
|
545
545
|
// 计算完成情况
|
|
@@ -574,11 +574,11 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
574
574
|
};
|
|
575
575
|
}).filter(m => m !== null);
|
|
576
576
|
|
|
577
|
-
// 准备任务数据给美化组�? const taskData = {
|
|
577
|
+
// 准备任务数据给美化组�? const taskData = {
|
|
578
578
|
...task,
|
|
579
579
|
group: group.name,
|
|
580
580
|
assignedTo: task.assignedTo && task.assignedTo.length > 0 && task.assignedTo[0].username ?
|
|
581
|
-
task.assignedTo[0].username : '未分�?,
|
|
581
|
+
task.assignedTo[0].username : '未分�?,
|
|
582
582
|
members: assignedMembers,
|
|
583
583
|
completedCount: completedMembers
|
|
584
584
|
};
|
|
@@ -590,7 +590,7 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
590
590
|
console.error('加载任务详情失败:', error);
|
|
591
591
|
content.innerHTML = `
|
|
592
592
|
<div class="error-state">
|
|
593
|
-
<h3>�?加载失败</h3>
|
|
593
|
+
<h3>�?加载失败</h3>
|
|
594
594
|
<p>${error.message || '未知错误'}</p>
|
|
595
595
|
<button class="btn-primary" onclick="document.getElementById('taskDetailModal').classList.add('hidden')">关闭</button>
|
|
596
596
|
</div>
|
|
@@ -599,7 +599,7 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
599
599
|
}
|
|
600
600
|
async function renderDocumentsView(container) {
|
|
601
601
|
if (!currentGroup) {
|
|
602
|
-
container.innerHTML = '<div class="empty-state">请先选择一个群�?/div>';
|
|
602
|
+
container.innerHTML = '<div class="empty-state">请先选择一个群�?/div>';
|
|
603
603
|
return;
|
|
604
604
|
}
|
|
605
605
|
|
|
@@ -613,20 +613,20 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
613
613
|
<div class="documents-list" id="docsList"></div>
|
|
614
614
|
<div id="createDocModal" class="modal hidden">
|
|
615
615
|
<div class="modal-content">
|
|
616
|
-
<h3>创建新文�?/h3>
|
|
616
|
+
<h3>创建新文�?/h3>
|
|
617
617
|
<form id="createDocForm">
|
|
618
618
|
<div class="form-group">
|
|
619
619
|
<label>文档标题</label>
|
|
620
|
-
<input type="text" name="title" placeholder="请输入文档标�? required>
|
|
620
|
+
<input type="text" name="title" placeholder="请输入文档标�? required>
|
|
621
621
|
</div>
|
|
622
622
|
<div class="form-group">
|
|
623
623
|
<label>文档内容</label>
|
|
624
|
-
<textarea name="content" placeholder="请输入文档内�? rows="6"></textarea>
|
|
624
|
+
<textarea name="content" placeholder="请输入文档内�? rows="6"></textarea>
|
|
625
625
|
</div>
|
|
626
626
|
<div class="form-group">
|
|
627
627
|
<label>权限设置</label>
|
|
628
628
|
<select name="permission">
|
|
629
|
-
<option value="editable">可编�?/option>
|
|
629
|
+
<option value="editable">可编�?/option>
|
|
630
630
|
<option value="readonly">只读</option>
|
|
631
631
|
</select>
|
|
632
632
|
</div>
|
|
@@ -651,13 +651,13 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
651
651
|
<div style="flex: 1;">
|
|
652
652
|
<h3>📄 ${doc.title}</h3>
|
|
653
653
|
<div class="doc-meta">
|
|
654
|
-
<span>创建�? ${doc.creator.username}</span>
|
|
655
|
-
<span>${doc.permission === 'readonly' ? '🔒 只读' : '✏️ 可编�?}</span>
|
|
654
|
+
<span>创建�? ${doc.creator.username}</span>
|
|
655
|
+
<span>${doc.permission === 'readonly' ? '🔒 只读' : '✏️ 可编�?}</span>
|
|
656
656
|
</div>
|
|
657
657
|
</div>
|
|
658
658
|
<div style="display: flex; gap: 10px; align-items: center;">
|
|
659
659
|
<button class="btn-edit" data-id="${doc._id}">编辑</button>
|
|
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>
|
|
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>
|
|
661
661
|
</div>
|
|
662
662
|
</div>
|
|
663
663
|
`;
|
|
@@ -676,10 +676,10 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
676
676
|
btn.addEventListener('click', async (e) => {
|
|
677
677
|
e.stopPropagation();
|
|
678
678
|
const docId = btn.dataset.id;
|
|
679
|
-
if (confirm('确定要删除这个文档吗?删除后无法恢复�?)) {
|
|
679
|
+
if (confirm('确定要删除这个文档吗?删除后无法恢复�?)) {
|
|
680
680
|
try {
|
|
681
681
|
await apiService.deleteDocument(docId);
|
|
682
|
-
alert('文档删除成功�?);
|
|
682
|
+
alert('文档删除成功�?);
|
|
683
683
|
await renderDocumentsView(container);
|
|
684
684
|
} catch (error) {
|
|
685
685
|
console.error('删除文档错误:', error);
|
|
@@ -708,7 +708,7 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
708
708
|
currentGroup._id,
|
|
709
709
|
formData.get('permission')
|
|
710
710
|
);
|
|
711
|
-
alert('文档创建成功�?);
|
|
711
|
+
alert('文档创建成功�?);
|
|
712
712
|
await renderDocumentsView(container);
|
|
713
713
|
document.getElementById('createDocModal').classList.add('hidden');
|
|
714
714
|
} catch (error) {
|
|
@@ -724,7 +724,7 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
724
724
|
|
|
725
725
|
container.innerHTML = `
|
|
726
726
|
<div class="view-header">
|
|
727
|
-
<button class="btn-back" id="backBtn">�?返回</button>
|
|
727
|
+
<button class="btn-back" id="backBtn">�?返回</button>
|
|
728
728
|
<h2>${doc.title}</h2>
|
|
729
729
|
<span class="doc-status">${doc.permission === 'readonly' ? '🔒 只读模式' : '✏️ 编辑模式'}</span>
|
|
730
730
|
</div>
|
|
@@ -737,12 +737,12 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
737
737
|
</div>
|
|
738
738
|
<div id="editor"></div>
|
|
739
739
|
<div class="editor-footer">
|
|
740
|
-
<span>最后编�? ${new Date(doc.updatedAt).toLocaleString()}</span>
|
|
740
|
+
<span>最后编�? ${new Date(doc.updatedAt).toLocaleString()}</span>
|
|
741
741
|
</div>
|
|
742
742
|
</div>
|
|
743
743
|
`;
|
|
744
744
|
|
|
745
|
-
// 初始�?Quill 编辑�? const quill = new Quill('#editor', {
|
|
745
|
+
// 初始�?Quill 编辑�? const quill = new Quill('#editor', {
|
|
746
746
|
theme: 'snow',
|
|
747
747
|
modules: {
|
|
748
748
|
toolbar: [
|
|
@@ -787,7 +787,7 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
787
787
|
try {
|
|
788
788
|
const content = quill.root.innerHTML;
|
|
789
789
|
await apiService.updateDocument(documentId, content);
|
|
790
|
-
alert('保存成功�?);
|
|
790
|
+
alert('保存成功�?);
|
|
791
791
|
} catch (error) {
|
|
792
792
|
alert('保存失败: ' + error.message);
|
|
793
793
|
}
|
|
@@ -804,7 +804,7 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
804
804
|
}
|
|
805
805
|
});
|
|
806
806
|
|
|
807
|
-
// 监听打字状�? wsService.on('typing', (data) => {
|
|
807
|
+
// 监听打字状�? wsService.on('typing', (data) => {
|
|
808
808
|
if (data.documentId === documentId && data.userId !== user.id) {
|
|
809
809
|
const onlineUsers = document.getElementById('onlineUsers');
|
|
810
810
|
if (data.isTyping) {
|
|
@@ -823,7 +823,7 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
823
823
|
|
|
824
824
|
async function renderFilesView(container) {
|
|
825
825
|
if (!currentGroup) {
|
|
826
|
-
container.innerHTML = '<div class="empty-state">请先选择一个群�?/div>';
|
|
826
|
+
container.innerHTML = '<div class="empty-state">请先选择一个群�?/div>';
|
|
827
827
|
return;
|
|
828
828
|
}
|
|
829
829
|
|
|
@@ -848,7 +848,7 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
848
848
|
<div class="form-group">
|
|
849
849
|
<label>选择文件</label>
|
|
850
850
|
<input type="file" id="fileInput" required>
|
|
851
|
-
<small>支持图片、PDF、Word、Excel等,最�?0MB</small>
|
|
851
|
+
<small>支持图片、PDF、Word、Excel等,最�?0MB</small>
|
|
852
852
|
</div>
|
|
853
853
|
<div class="form-group">
|
|
854
854
|
<label>描述(可选)</label>
|
|
@@ -880,7 +880,7 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
880
880
|
<div class="file-info">
|
|
881
881
|
<h4>${file.originalName}</h4>
|
|
882
882
|
<div class="file-meta">
|
|
883
|
-
<span>上传�? ${file.uploader.username}</span>
|
|
883
|
+
<span>上传�? ${file.uploader.username}</span>
|
|
884
884
|
<span>大小: ${fileSize}</span>
|
|
885
885
|
<span>时间: ${new Date(file.createdAt).toLocaleString()}</span>
|
|
886
886
|
</div>
|
|
@@ -937,10 +937,10 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
937
937
|
// 删除文件事件
|
|
938
938
|
document.querySelectorAll('[data-action="delete-file"]').forEach(btn => {
|
|
939
939
|
btn.addEventListener('click', async () => {
|
|
940
|
-
if (confirm('确定要删除这个文件吗�?)) {
|
|
940
|
+
if (confirm('确定要删除这个文件吗�?)) {
|
|
941
941
|
try {
|
|
942
942
|
await apiService.deleteFile(btn.dataset.id);
|
|
943
|
-
alert('文件删除成功�?);
|
|
943
|
+
alert('文件删除成功�?);
|
|
944
944
|
await renderFilesView(container);
|
|
945
945
|
} catch (error) {
|
|
946
946
|
alert('删除失败: ' + error.message);
|
|
@@ -977,7 +977,7 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
977
977
|
|
|
978
978
|
try {
|
|
979
979
|
await apiService.uploadFile(currentGroup._id, fileInput.files[0], description);
|
|
980
|
-
alert('文件上传成功�?);
|
|
980
|
+
alert('文件上传成功�?);
|
|
981
981
|
document.getElementById('uploadFileModal').classList.add('hidden');
|
|
982
982
|
document.getElementById('uploadFileForm').reset();
|
|
983
983
|
await renderFilesView(container);
|
|
@@ -997,7 +997,7 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
997
997
|
}
|
|
998
998
|
|
|
999
999
|
function getFileIcon(mimetype) {
|
|
1000
|
-
if (mimetype.startsWith('image/')) return '🖼�?;
|
|
1000
|
+
if (mimetype.startsWith('image/')) return '🖼�?;
|
|
1001
1001
|
if (mimetype === 'application/pdf') return '📕';
|
|
1002
1002
|
if (mimetype.includes('word') || mimetype.includes('document')) return '📘';
|
|
1003
1003
|
if (mimetype.includes('excel') || mimetype.includes('spreadsheet')) return '📗';
|
|
@@ -1017,7 +1017,7 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
1017
1017
|
|
|
1018
1018
|
async function renderChatView(container) {
|
|
1019
1019
|
if (!currentGroup) {
|
|
1020
|
-
container.innerHTML = '<div class="empty-state">请先选择一个群�?/div>';
|
|
1020
|
+
container.innerHTML = '<div class="empty-state">请先选择一个群�?/div>';
|
|
1021
1021
|
return;
|
|
1022
1022
|
}
|
|
1023
1023
|
|
|
@@ -1032,10 +1032,10 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
1032
1032
|
🤖 AI
|
|
1033
1033
|
</button>
|
|
1034
1034
|
<button class="btn-secondary" id="showCollabTools" title="协作工具">
|
|
1035
|
-
🛠�?工具
|
|
1035
|
+
🛠�?工具
|
|
1036
1036
|
</button>
|
|
1037
|
-
<button class="btn-secondary" id="markAllRead" title="全部标记为已�?>
|
|
1038
|
-
�?已读
|
|
1037
|
+
<button class="btn-secondary" id="markAllRead" title="全部标记为已�?>
|
|
1038
|
+
�?已读
|
|
1039
1039
|
</button>
|
|
1040
1040
|
<button class="btn-secondary" id="muteAllBtn">全体禁言</button>
|
|
1041
1041
|
<button class="btn-secondary" id="manageMuteBtn">个人禁言</button>
|
|
@@ -1047,7 +1047,7 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
1047
1047
|
<div class="chat-input">
|
|
1048
1048
|
<button class="btn-emoji" id="emojiBtn">😊</button>
|
|
1049
1049
|
<input type="text" id="messageInput" placeholder="输入消息... (使用 @ 提及用户)">
|
|
1050
|
-
<button class="btn-primary" id="sendBtn">发�?/button>
|
|
1050
|
+
<button class="btn-primary" id="sendBtn">发�?/button>
|
|
1051
1051
|
</div>
|
|
1052
1052
|
<emoji-picker id="emojiPicker" class="hidden"></emoji-picker>
|
|
1053
1053
|
</div>
|
|
@@ -1062,22 +1062,22 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
1062
1062
|
<div class="modal-content">
|
|
1063
1063
|
<h3>清除聊天记录</h3>
|
|
1064
1064
|
<div style="padding: 20px;">
|
|
1065
|
-
<p style="margin-bottom: 20px; color: var(--danger);">⚠️ 警告:此操作不可恢复�?/p>
|
|
1065
|
+
<p style="margin-bottom: 20px; color: var(--danger);">⚠️ 警告:此操作不可恢复�?/p>
|
|
1066
1066
|
<div class="form-group">
|
|
1067
1067
|
<label>
|
|
1068
1068
|
<input type="radio" name="clearType" value="all" checked>
|
|
1069
|
-
清除所有聊天记�? </label>
|
|
1069
|
+
清除所有聊天记�? </label>
|
|
1070
1070
|
</div>
|
|
1071
1071
|
<div class="form-group">
|
|
1072
1072
|
<label>
|
|
1073
1073
|
<input type="radio" name="clearType" value="before">
|
|
1074
|
-
清除指定日期之前的记�? </label>
|
|
1074
|
+
清除指定日期之前的记�? </label>
|
|
1075
1075
|
<input type="date" id="clearBeforeDate" style="margin-left: 10px; margin-top: 10px;" disabled>
|
|
1076
1076
|
</div>
|
|
1077
1077
|
<div class="form-group">
|
|
1078
1078
|
<label>
|
|
1079
1079
|
<input type="radio" name="clearType" value="user">
|
|
1080
|
-
清除指定用户的消�? </label>
|
|
1080
|
+
清除指定用户的消�? </label>
|
|
1081
1081
|
<select id="clearUserId" style="margin-left: 10px; margin-top: 10px;" disabled>
|
|
1082
1082
|
<option value="">选择用户</option>
|
|
1083
1083
|
</select>
|
|
@@ -1137,7 +1137,7 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
1137
1137
|
}
|
|
1138
1138
|
});
|
|
1139
1139
|
|
|
1140
|
-
// 表情包功�? emojiBtn.addEventListener('click', () => {
|
|
1140
|
+
// 表情包功�? emojiBtn.addEventListener('click', () => {
|
|
1141
1141
|
emojiPicker.classList.toggle('hidden');
|
|
1142
1142
|
});
|
|
1143
1143
|
|
|
@@ -1147,7 +1147,7 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
1147
1147
|
emojiPicker.classList.add('hidden');
|
|
1148
1148
|
});
|
|
1149
1149
|
|
|
1150
|
-
// 点击外部关闭表情选择�? document.addEventListener('click', (e) => {
|
|
1150
|
+
// 点击外部关闭表情选择�? document.addEventListener('click', (e) => {
|
|
1151
1151
|
if (!emojiBtn.contains(e.target) && !emojiPicker.contains(e.target)) {
|
|
1152
1152
|
emojiPicker.classList.add('hidden');
|
|
1153
1153
|
}
|
|
@@ -1175,7 +1175,7 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
1175
1175
|
const messagesResult = await apiService.getGroupMessages(currentGroup._id);
|
|
1176
1176
|
if (messagesResult.messages) {
|
|
1177
1177
|
messagesResult.messages.forEach(msg => {
|
|
1178
|
-
// 使用增强的消息渲�? features.chatEnhancements.renderMessage(msg, messagesDiv, group.members);
|
|
1178
|
+
// 使用增强的消息渲�? features.chatEnhancements.renderMessage(msg, messagesDiv, group.members);
|
|
1179
1179
|
});
|
|
1180
1180
|
messagesDiv.scrollTop = messagesDiv.scrollHeight;
|
|
1181
1181
|
}
|
|
@@ -1190,7 +1190,7 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
1190
1190
|
};
|
|
1191
1191
|
refreshMuteButtons();
|
|
1192
1192
|
|
|
1193
|
-
// 全体禁言(服务端生效�? document.getElementById('muteAllBtn').addEventListener('click', async () => {
|
|
1193
|
+
// 全体禁言(服务端生效�? document.getElementById('muteAllBtn').addEventListener('click', async () => {
|
|
1194
1194
|
try {
|
|
1195
1195
|
const next = !isMutedAll;
|
|
1196
1196
|
const res = await apiService.setMuteAll(currentGroup._id, next);
|
|
@@ -1199,7 +1199,7 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
1199
1199
|
|
|
1200
1200
|
const notification = document.createElement('div');
|
|
1201
1201
|
notification.className = 'notification';
|
|
1202
|
-
notification.textContent = isMutedAll ? '已开启全体禁言(成员无法发言�? : '已取消全体禁言';
|
|
1202
|
+
notification.textContent = isMutedAll ? '已开启全体禁言(成员无法发言�? : '已取消全体禁言';
|
|
1203
1203
|
messagesDiv.appendChild(notification);
|
|
1204
1204
|
messagesDiv.scrollTop = messagesDiv.scrollHeight;
|
|
1205
1205
|
} catch (e) {
|
|
@@ -1207,8 +1207,8 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
1207
1207
|
}
|
|
1208
1208
|
});
|
|
1209
1209
|
|
|
1210
|
-
// 个人禁言(服务端生效�? document.getElementById('manageMuteBtn').addEventListener('click', async () => {
|
|
1211
|
-
// 重新拉取最�?group(避免成员变动不同步�? const latest = await apiService.getGroup(currentGroup._id);
|
|
1210
|
+
// 个人禁言(服务端生效�? document.getElementById('manageMuteBtn').addEventListener('click', async () => {
|
|
1211
|
+
// 重新拉取最�?group(避免成员变动不同步�? const latest = await apiService.getGroup(currentGroup._id);
|
|
1212
1212
|
group = latest.group;
|
|
1213
1213
|
mutedUsers = new Set((group.mutedUsers || []).map(String));
|
|
1214
1214
|
|
|
@@ -1253,7 +1253,7 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
1253
1253
|
|
|
1254
1254
|
// 清除聊天记录功能
|
|
1255
1255
|
document.getElementById('clearChatBtn').addEventListener('click', async () => {
|
|
1256
|
-
// 加载群组成员到下拉列�? const clearUserId = document.getElementById('clearUserId');
|
|
1256
|
+
// 加载群组成员到下拉列�? const clearUserId = document.getElementById('clearUserId');
|
|
1257
1257
|
clearUserId.innerHTML = '<option value="">选择用户</option>';
|
|
1258
1258
|
group.members.forEach(member => {
|
|
1259
1259
|
clearUserId.innerHTML += `<option value="${member._id}">${member.username}</option>`;
|
|
@@ -1280,14 +1280,14 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
1280
1280
|
|
|
1281
1281
|
let confirmMsg = '';
|
|
1282
1282
|
if (clearType === 'all') {
|
|
1283
|
-
confirmMsg = '确定要清除所有聊天记录吗?此操作不可恢复�?;
|
|
1283
|
+
confirmMsg = '确定要清除所有聊天记录吗?此操作不可恢复�?;
|
|
1284
1284
|
} else if (clearType === 'before') {
|
|
1285
1285
|
const date = document.getElementById('clearBeforeDate').value;
|
|
1286
1286
|
if (!date) {
|
|
1287
1287
|
alert('请选择日期');
|
|
1288
1288
|
return;
|
|
1289
1289
|
}
|
|
1290
|
-
confirmMsg = `确定要清�?${date} 之前的所有聊天记录吗?此操作不可恢复!`;
|
|
1290
|
+
confirmMsg = `确定要清�?${date} 之前的所有聊天记录吗?此操作不可恢复!`;
|
|
1291
1291
|
} else if (clearType === 'user') {
|
|
1292
1292
|
const userId = document.getElementById('clearUserId').value;
|
|
1293
1293
|
if (!userId) {
|
|
@@ -1295,7 +1295,7 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
1295
1295
|
return;
|
|
1296
1296
|
}
|
|
1297
1297
|
const username = group.members.find(m => m._id === userId)?.username;
|
|
1298
|
-
confirmMsg = `确定要清除用�?${username} 的所有消息吗?此操作不可恢复!`;
|
|
1298
|
+
confirmMsg = `确定要清除用�?${username} 的所有消息吗?此操作不可恢复!`;
|
|
1299
1299
|
}
|
|
1300
1300
|
|
|
1301
1301
|
if (!confirm(confirmMsg)) {
|
|
@@ -1322,7 +1322,7 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
1322
1322
|
const messagesResult = await apiService.getGroupMessages(currentGroup._id);
|
|
1323
1323
|
if (messagesResult.messages) {
|
|
1324
1324
|
messagesResult.messages.forEach(msg => {
|
|
1325
|
-
// 使用增强的消息渲�? features.chatEnhancements.renderMessage(msg, messagesDiv, group.members);
|
|
1325
|
+
// 使用增强的消息渲�? features.chatEnhancements.renderMessage(msg, messagesDiv, group.members);
|
|
1326
1326
|
});
|
|
1327
1327
|
}
|
|
1328
1328
|
} catch (error) {
|
|
@@ -1333,13 +1333,13 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
1333
1333
|
// 监听消息
|
|
1334
1334
|
wsService.on('chat_message', (data) => {
|
|
1335
1335
|
if (data.groupId === currentGroup._id) {
|
|
1336
|
-
// 使用增强的消息渲�? features.chatEnhancements.renderMessage(data, messagesDiv, group.members);
|
|
1336
|
+
// 使用增强的消息渲�? features.chatEnhancements.renderMessage(data, messagesDiv, group.members);
|
|
1337
1337
|
messagesDiv.scrollTop = messagesDiv.scrollHeight;
|
|
1338
1338
|
|
|
1339
|
-
// 显示通知(如果不是自己发送的消息�? if (data.userId !== currentUserId) {
|
|
1339
|
+
// 显示通知(如果不是自己发送的消息�? if (data.userId !== currentUserId) {
|
|
1340
1340
|
features.notifications.showNewMessageNotification(data.username, data.content);
|
|
1341
1341
|
|
|
1342
|
-
// 检查是否@了当前用�? if (features.chatEnhancements.checkMentionsMe(data.content, group.members)) {
|
|
1342
|
+
// 检查是否@了当前用�? if (features.chatEnhancements.checkMentionsMe(data.content, group.members)) {
|
|
1343
1343
|
features.notifications.showMentionNotification(data.username, data.content);
|
|
1344
1344
|
}
|
|
1345
1345
|
}
|
|
@@ -1374,17 +1374,17 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
1374
1374
|
}
|
|
1375
1375
|
});
|
|
1376
1376
|
|
|
1377
|
-
// 发送被拦截提示(来自服务端�? wsService.on('chat_blocked', (data) => {
|
|
1377
|
+
// 发送被拦截提示(来自服务端�? wsService.on('chat_blocked', (data) => {
|
|
1378
1378
|
if (data.groupId === currentGroup._id) {
|
|
1379
1379
|
const notification = document.createElement('div');
|
|
1380
1380
|
notification.className = 'notification';
|
|
1381
|
-
notification.textContent = data.message || '消息发送失�?;
|
|
1381
|
+
notification.textContent = data.message || '消息发送失�?;
|
|
1382
1382
|
messagesDiv.appendChild(notification);
|
|
1383
1383
|
messagesDiv.scrollTop = messagesDiv.scrollHeight;
|
|
1384
1384
|
}
|
|
1385
1385
|
});
|
|
1386
1386
|
|
|
1387
|
-
// 发送消�? const sendMessage = () => {
|
|
1387
|
+
// 发送消�? const sendMessage = () => {
|
|
1388
1388
|
const content = messageInput.value.trim();
|
|
1389
1389
|
if (content) {
|
|
1390
1390
|
// 解析@提及
|
|
@@ -1396,7 +1396,7 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
1396
1396
|
// 显示AI智能回复建议(如果启用)
|
|
1397
1397
|
if (localStorage.getItem('ai_smartReplies') !== 'false') {
|
|
1398
1398
|
features.aiAssistant.getSmartReplies(content).then(replies => {
|
|
1399
|
-
// 可以在这里显示智能回复建�? console.log('智能回复建议:', replies);
|
|
1399
|
+
// 可以在这里显示智能回复建�? console.log('智能回复建议:', replies);
|
|
1400
1400
|
});
|
|
1401
1401
|
}
|
|
1402
1402
|
}
|
|
@@ -1410,7 +1410,7 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
1410
1410
|
|
|
1411
1411
|
async function renderCallView(container) {
|
|
1412
1412
|
if (!currentGroup) {
|
|
1413
|
-
container.innerHTML = '<div class="empty-state">请先选择一个群�?/div>';
|
|
1413
|
+
container.innerHTML = '<div class="empty-state">请先选择一个群�?/div>';
|
|
1414
1414
|
return;
|
|
1415
1415
|
}
|
|
1416
1416
|
|
|
@@ -1422,7 +1422,7 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
1422
1422
|
<div class="call-controls">
|
|
1423
1423
|
<label>点名人数:</label>
|
|
1424
1424
|
<input type="number" id="callCount" value="1" min="1" max="10">
|
|
1425
|
-
<button class="btn-primary btn-large" id="randomCallBtn">🎲 开始点�?/button>
|
|
1425
|
+
<button class="btn-primary btn-large" id="randomCallBtn">🎲 开始点�?/button>
|
|
1426
1426
|
</div>
|
|
1427
1427
|
<div id="callResult" class="call-result"></div>
|
|
1428
1428
|
</div>
|
|
@@ -1467,9 +1467,9 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
1467
1467
|
<option value="title_edit">标题编辑</option>
|
|
1468
1468
|
<option value="document_permission_change">权限修改</option>
|
|
1469
1469
|
</select>
|
|
1470
|
-
<input type="date" id="startDate" class="form-input" title="开始日�?>
|
|
1470
|
+
<input type="date" id="startDate" class="form-input" title="开始日�?>
|
|
1471
1471
|
<input type="date" id="endDate" class="form-input" title="结束日期">
|
|
1472
|
-
<button class="btn-primary" id="applyFilters">筛�?/button>
|
|
1472
|
+
<button class="btn-primary" id="applyFilters">筛�?/button>
|
|
1473
1473
|
<button class="btn-secondary" id="exportLogs">导出</button>
|
|
1474
1474
|
</div>
|
|
1475
1475
|
</div>
|
|
@@ -1490,13 +1490,13 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
1490
1490
|
</div>
|
|
1491
1491
|
|
|
1492
1492
|
<div class="audit-logs" id="auditLogs">
|
|
1493
|
-
<div class="loading">加载�?..</div>
|
|
1493
|
+
<div class="loading">加载�?..</div>
|
|
1494
1494
|
</div>
|
|
1495
1495
|
|
|
1496
1496
|
<div class="pagination" id="auditPagination" style="display: none;">
|
|
1497
|
-
<button class="btn-secondary" id="prevPage">上一�?/button>
|
|
1498
|
-
<span id="pageInfo">�?1 页,�?1 �?/span>
|
|
1499
|
-
<button class="btn-secondary" id="nextPage">下一�?/button>
|
|
1497
|
+
<button class="btn-secondary" id="prevPage">上一�?/button>
|
|
1498
|
+
<span id="pageInfo">�?1 页,�?1 �?/span>
|
|
1499
|
+
<button class="btn-secondary" id="nextPage">下一�?/button>
|
|
1500
1500
|
</div>
|
|
1501
1501
|
|
|
1502
1502
|
<div id="auditDetailModal" class="modal hidden">
|
|
@@ -1531,7 +1531,7 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
1531
1531
|
async function loadAuditLogs(page = 1, filters = {}) {
|
|
1532
1532
|
try {
|
|
1533
1533
|
const auditLogsDiv = document.getElementById('auditLogs');
|
|
1534
|
-
auditLogsDiv.innerHTML = '<div class="loading">加载�?..</div>';
|
|
1534
|
+
auditLogsDiv.innerHTML = '<div class="loading">加载�?..</div>';
|
|
1535
1535
|
|
|
1536
1536
|
const options = { page, limit: 20 };
|
|
1537
1537
|
const result = await apiService.getAuditLogs(filters, options);
|
|
@@ -1571,7 +1571,7 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
1571
1571
|
// 更新分页
|
|
1572
1572
|
const pagination = document.getElementById('auditPagination');
|
|
1573
1573
|
const pageInfo = document.getElementById('pageInfo');
|
|
1574
|
-
pageInfo.textContent = `�?${result.pagination.page} 页,�?${result.pagination.pages} 页`;
|
|
1574
|
+
pageInfo.textContent = `�?${result.pagination.page} 页,�?${result.pagination.pages} 页`;
|
|
1575
1575
|
|
|
1576
1576
|
document.getElementById('prevPage').disabled = result.pagination.page <= 1;
|
|
1577
1577
|
document.getElementById('nextPage').disabled = result.pagination.page >= result.pagination.pages;
|
|
@@ -1618,20 +1618,20 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
1618
1618
|
const content = document.getElementById('auditDetailContent');
|
|
1619
1619
|
|
|
1620
1620
|
modal.classList.remove('hidden');
|
|
1621
|
-
content.innerHTML = '<div class="loading">加载�?..</div>';
|
|
1621
|
+
content.innerHTML = '<div class="loading">加载�?..</div>';
|
|
1622
1622
|
|
|
1623
1623
|
// 获取审计日志详情
|
|
1624
1624
|
const result = await apiService.getAuditLogDetail(logId);
|
|
1625
1625
|
const log = result.log;
|
|
1626
1626
|
|
|
1627
|
-
// 格式化详细信�? let detailsHtml = '';
|
|
1627
|
+
// 格式化详细信�? let detailsHtml = '';
|
|
1628
1628
|
if (log.details) {
|
|
1629
1629
|
detailsHtml = Object.entries(log.details).map(([key, value]) => {
|
|
1630
1630
|
let displayValue = value;
|
|
1631
1631
|
|
|
1632
1632
|
// 特殊处理某些字段
|
|
1633
1633
|
if (key === 'oldContent' || key === 'newContent') {
|
|
1634
|
-
// 截断过长的内�? if (typeof value === 'string' && value.length > 200) {
|
|
1634
|
+
// 截断过长的内�? if (typeof value === 'string' && value.length > 200) {
|
|
1635
1635
|
displayValue = value.substring(0, 200) + '...';
|
|
1636
1636
|
}
|
|
1637
1637
|
} else if (typeof value === 'object') {
|
|
@@ -1640,7 +1640,7 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
1640
1640
|
|
|
1641
1641
|
return `
|
|
1642
1642
|
<div class="info-row">
|
|
1643
|
-
<span class="info-label">${formatFieldName(key)}�?/span>
|
|
1643
|
+
<span class="info-label">${formatFieldName(key)}�?/span>
|
|
1644
1644
|
<span class="info-value">${displayValue || '-'}</span>
|
|
1645
1645
|
</div>
|
|
1646
1646
|
`;
|
|
@@ -1651,19 +1651,19 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
1651
1651
|
<div class="audit-detail">
|
|
1652
1652
|
<div class="task-detail-info">
|
|
1653
1653
|
<div class="info-row">
|
|
1654
|
-
<span class="info-label">操作时间�?/span>
|
|
1654
|
+
<span class="info-label">操作时间�?/span>
|
|
1655
1655
|
<span class="info-value">${new Date(log.createdAt).toLocaleString()}</span>
|
|
1656
1656
|
</div>
|
|
1657
1657
|
<div class="info-row">
|
|
1658
|
-
<span class="info-label">操作用户�?/span>
|
|
1658
|
+
<span class="info-label">操作用户�?/span>
|
|
1659
1659
|
<span class="info-value">${log.user?.username || '未知用户'}</span>
|
|
1660
1660
|
</div>
|
|
1661
1661
|
<div class="info-row">
|
|
1662
|
-
<span class="info-label">用户角色�?/span>
|
|
1663
|
-
<span class="info-value">${log.user?.role === 'admin' ? '管理�? : '普通用�?}</span>
|
|
1662
|
+
<span class="info-label">用户角色�?/span>
|
|
1663
|
+
<span class="info-value">${log.user?.role === 'admin' ? '管理�? : '普通用�?}</span>
|
|
1664
1664
|
</div>
|
|
1665
1665
|
<div class="info-row">
|
|
1666
|
-
<span class="info-label">操作类型�?/span>
|
|
1666
|
+
<span class="info-label">操作类型�?/span>
|
|
1667
1667
|
<span class="info-value">
|
|
1668
1668
|
<span class="action-badge action-${log.action}">${getActionText(log.action)}</span>
|
|
1669
1669
|
</span>
|
|
@@ -1673,15 +1673,15 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
1673
1673
|
<span class="info-value">${log.metadata?.groupId?.name || '-'}</span>
|
|
1674
1674
|
</div>
|
|
1675
1675
|
<div class="info-row">
|
|
1676
|
-
<span class="info-label">资源类型�?/span>
|
|
1676
|
+
<span class="info-label">资源类型�?/span>
|
|
1677
1677
|
<span class="info-value">${log.resourceType || '-'}</span>
|
|
1678
1678
|
</div>
|
|
1679
1679
|
<div class="info-row">
|
|
1680
|
-
<span class="info-label">资源标题�?/span>
|
|
1680
|
+
<span class="info-label">资源标题�?/span>
|
|
1681
1681
|
<span class="info-value">${log.resourceTitle || log.resourceId || '-'}</span>
|
|
1682
1682
|
</div>
|
|
1683
1683
|
<div class="info-row">
|
|
1684
|
-
<span class="info-label">IP地址�?/span>
|
|
1684
|
+
<span class="info-label">IP地址�?/span>
|
|
1685
1685
|
<span class="info-value">${log.metadata?.ipAddress || '-'}</span>
|
|
1686
1686
|
</div>
|
|
1687
1687
|
</div>
|
|
@@ -1713,12 +1713,12 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
1713
1713
|
function formatFieldName(key) {
|
|
1714
1714
|
const fieldNames = {
|
|
1715
1715
|
'description': '描述',
|
|
1716
|
-
'oldTitle': '原标�?,
|
|
1717
|
-
'newTitle': '新标�?,
|
|
1718
|
-
'oldContent': '原内�?,
|
|
1719
|
-
'newContent': '新内�?,
|
|
1720
|
-
'oldPermission': '原权�?,
|
|
1721
|
-
'newPermission': '新权�?,
|
|
1716
|
+
'oldTitle': '原标�?,
|
|
1717
|
+
'newTitle': '新标�?,
|
|
1718
|
+
'oldContent': '原内�?,
|
|
1719
|
+
'newContent': '新内�?,
|
|
1720
|
+
'oldPermission': '原权�?,
|
|
1721
|
+
'newPermission': '新权�?,
|
|
1722
1722
|
'contentLength': '内容长度',
|
|
1723
1723
|
'changes': '变更内容',
|
|
1724
1724
|
'reason': '原因'
|
|
@@ -1735,7 +1735,7 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
1735
1735
|
endDate: document.getElementById('endDate').value
|
|
1736
1736
|
};
|
|
1737
1737
|
|
|
1738
|
-
// 移除空�? Object.keys(currentFilters).forEach(key => {
|
|
1738
|
+
// 移除空�? Object.keys(currentFilters).forEach(key => {
|
|
1739
1739
|
if (!currentFilters[key]) {
|
|
1740
1740
|
delete currentFilters[key];
|
|
1741
1741
|
}
|
|
@@ -1777,7 +1777,7 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
1777
1777
|
</div>
|
|
1778
1778
|
<div class="search-container">
|
|
1779
1779
|
<div class="search-box">
|
|
1780
|
-
<input type="text" id="searchInput" placeholder="搜索消息、文档、任�?..">
|
|
1780
|
+
<input type="text" id="searchInput" placeholder="搜索消息、文档、任�?..">
|
|
1781
1781
|
<button class="btn-primary" id="searchBtn">搜索</button>
|
|
1782
1782
|
</div>
|
|
1783
1783
|
<div class="search-filters">
|
|
@@ -1812,7 +1812,7 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
1812
1812
|
tasks: document.getElementById('filterTasks').checked
|
|
1813
1813
|
};
|
|
1814
1814
|
|
|
1815
|
-
searchResults.innerHTML = '<div class="loading">搜索�?..</div>';
|
|
1815
|
+
searchResults.innerHTML = '<div class="loading">搜索�?..</div>';
|
|
1816
1816
|
|
|
1817
1817
|
try {
|
|
1818
1818
|
const results = [];
|
|
@@ -1895,7 +1895,7 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
1895
1895
|
|
|
1896
1896
|
// 显示结果
|
|
1897
1897
|
if (results.length === 0) {
|
|
1898
|
-
searchResults.innerHTML = '<div class="empty-state">未找到相关结�?/div>';
|
|
1898
|
+
searchResults.innerHTML = '<div class="empty-state">未找到相关结�?/div>';
|
|
1899
1899
|
} else {
|
|
1900
1900
|
searchResults.innerHTML = results.map(result => {
|
|
1901
1901
|
const typeIcon = {
|
|
@@ -1912,7 +1912,7 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
1912
1912
|
<h4>${highlightText(result.title, query)}</h4>
|
|
1913
1913
|
<p>${highlightText(result.content, query)}</p>
|
|
1914
1914
|
${result.group ? `<span class="result-group">群组: ${result.group}</span>` : ''}
|
|
1915
|
-
${result.status ? `<span class="result-status">状�? ${getStatusText(result.status)}</span>` : ''}
|
|
1915
|
+
${result.status ? `<span class="result-status">状�? ${getStatusText(result.status)}</span>` : ''}
|
|
1916
1916
|
</div>
|
|
1917
1917
|
`;
|
|
1918
1918
|
}).join('');
|
|
@@ -1936,10 +1936,10 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
1936
1936
|
|
|
1937
1937
|
function getStatusText(status) {
|
|
1938
1938
|
const statusMap = {
|
|
1939
|
-
'pending': '待处�?,
|
|
1940
|
-
'in_progress': '进行�?,
|
|
1941
|
-
'completed': '已完�?,
|
|
1942
|
-
'terminated': '已终�?
|
|
1939
|
+
'pending': '待处�?,
|
|
1940
|
+
'in_progress': '进行�?,
|
|
1941
|
+
'completed': '已完�?,
|
|
1942
|
+
'terminated': '已终�?
|
|
1943
1943
|
};
|
|
1944
1944
|
return statusMap[status] || status;
|
|
1945
1945
|
}
|
|
@@ -1989,15 +1989,15 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
1989
1989
|
</div>
|
|
1990
1990
|
<div class="stat-card">
|
|
1991
1991
|
<h3>自动备份</h3>
|
|
1992
|
-
<div class="stat-number">${configResult.data?.config?.autoBackup ? '�?已启�? : '�?已禁�?}</div>
|
|
1992
|
+
<div class="stat-number">${configResult.data?.config?.autoBackup ? '�?已启�? : '�?已禁�?}</div>
|
|
1993
1993
|
</div>
|
|
1994
1994
|
<div class="stat-card">
|
|
1995
1995
|
<h3>备份频率</h3>
|
|
1996
|
-
<div class="stat-number">${configResult.data?.config?.schedule || '未设�?}</div>
|
|
1996
|
+
<div class="stat-number">${configResult.data?.config?.schedule || '未设�?}</div>
|
|
1997
1997
|
</div>
|
|
1998
1998
|
<div class="stat-card">
|
|
1999
1999
|
<h3>保留天数</h3>
|
|
2000
|
-
<div class="stat-number">${configResult.data?.config?.retention || 30} �?/div>
|
|
2000
|
+
<div class="stat-number">${configResult.data?.config?.retention || 30} �?/div>
|
|
2001
2001
|
</div>
|
|
2002
2002
|
</div>
|
|
2003
2003
|
|
|
@@ -2008,7 +2008,7 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
2008
2008
|
<div>备份时间</div>
|
|
2009
2009
|
<div>类型</div>
|
|
2010
2010
|
<div>大小</div>
|
|
2011
|
-
<div>状�?/div>
|
|
2011
|
+
<div>状�?/div>
|
|
2012
2012
|
<div>操作</div>
|
|
2013
2013
|
</div>
|
|
2014
2014
|
${backupsResult.data.backups.map(backup => `
|
|
@@ -2022,7 +2022,7 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
2022
2022
|
<div>${formatFileSize(backup.size)}</div>
|
|
2023
2023
|
<div>
|
|
2024
2024
|
<span class="status-badge status-${backup.status}">
|
|
2025
|
-
${backup.status === 'completed' ? '�?完成' : backup.status === 'failed' ? '�?失败' : '�?进行�?}
|
|
2025
|
+
${backup.status === 'completed' ? '�?完成' : backup.status === 'failed' ? '�?失败' : '�?进行�?}
|
|
2026
2026
|
</span>
|
|
2027
2027
|
</div>
|
|
2028
2028
|
<div class="backup-actions">
|
|
@@ -2035,7 +2035,7 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
2035
2035
|
</div>
|
|
2036
2036
|
`).join('')}
|
|
2037
2037
|
</div>
|
|
2038
|
-
` : '<div class="empty-state">暂无备份记录<br>点击"立即备份"创建第一个备�?/div>'}
|
|
2038
|
+
` : '<div class="empty-state">暂无备份记录<br>点击"立即备份"创建第一个备�?/div>'}
|
|
2039
2039
|
</div>
|
|
2040
2040
|
|
|
2041
2041
|
<!-- 创建备份模态框 -->
|
|
@@ -2050,7 +2050,7 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
2050
2050
|
<label>备份类型</label>
|
|
2051
2051
|
<select id="backupType" required>
|
|
2052
2052
|
<option value="full">完整备份(所有数据)</option>
|
|
2053
|
-
<option value="incremental">增量备份(仅变更�?/option>
|
|
2053
|
+
<option value="incremental">增量备份(仅变更�?/option>
|
|
2054
2054
|
</select>
|
|
2055
2055
|
</div>
|
|
2056
2056
|
<div class="form-group">
|
|
@@ -2059,7 +2059,7 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
2059
2059
|
</div>
|
|
2060
2060
|
<div class="form-actions">
|
|
2061
2061
|
<button type="button" class="btn-secondary" id="cancelCreateBackup">取消</button>
|
|
2062
|
-
<button type="submit" class="btn-primary">开始备�?/button>
|
|
2062
|
+
<button type="submit" class="btn-primary">开始备�?/button>
|
|
2063
2063
|
</div>
|
|
2064
2064
|
</form>
|
|
2065
2065
|
</div>
|
|
@@ -2081,16 +2081,16 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
2081
2081
|
</div>
|
|
2082
2082
|
<div class="form-group">
|
|
2083
2083
|
<label>备份频率(Cron表达式)</label>
|
|
2084
|
-
<input type="text" id="backupSchedule" value="${configResult.data?.config?.schedule || '0 2 * * *'}" placeholder="0 2 * * * (每天凌晨2�?">
|
|
2085
|
-
<small>示例�? 2 * * * (每天凌晨2�?�? */6 * * * (�?小时)</small>
|
|
2084
|
+
<input type="text" id="backupSchedule" value="${configResult.data?.config?.schedule || '0 2 * * *'}" placeholder="0 2 * * * (每天凌晨2�?">
|
|
2085
|
+
<small>示例�? 2 * * * (每天凌晨2�?�? */6 * * * (�?小时)</small>
|
|
2086
2086
|
</div>
|
|
2087
2087
|
<div class="form-group">
|
|
2088
2088
|
<label>保留天数</label>
|
|
2089
2089
|
<input type="number" id="backupRetention" value="${configResult.data?.config?.retention || 30}" min="1" max="365">
|
|
2090
|
-
<small>超过此天数的备份将自动删�?/small>
|
|
2090
|
+
<small>超过此天数的备份将自动删�?/small>
|
|
2091
2091
|
</div>
|
|
2092
2092
|
<div class="form-group">
|
|
2093
|
-
<label>最大备份数�?/label>
|
|
2093
|
+
<label>最大备份数�?/label>
|
|
2094
2094
|
<input type="number" id="maxBackups" value="${configResult.data?.config?.maxBackups || 10}" min="1" max="100">
|
|
2095
2095
|
</div>
|
|
2096
2096
|
<div class="form-actions">
|
|
@@ -2132,7 +2132,7 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
2132
2132
|
|
|
2133
2133
|
const result = await response.json();
|
|
2134
2134
|
if (result.success) {
|
|
2135
|
-
alert('备份创建成功�?);
|
|
2135
|
+
alert('备份创建成功�?);
|
|
2136
2136
|
document.getElementById('createBackupModal').classList.add('hidden');
|
|
2137
2137
|
renderBackupView(container);
|
|
2138
2138
|
} else {
|
|
@@ -2177,7 +2177,7 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
2177
2177
|
|
|
2178
2178
|
const result = await response.json();
|
|
2179
2179
|
if (result.success) {
|
|
2180
|
-
alert('设置保存成功�?);
|
|
2180
|
+
alert('设置保存成功�?);
|
|
2181
2181
|
document.getElementById('configBackupModal').classList.add('hidden');
|
|
2182
2182
|
renderBackupView(container);
|
|
2183
2183
|
} else {
|
|
@@ -2199,7 +2199,7 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
2199
2199
|
// 恢复备份
|
|
2200
2200
|
document.querySelectorAll('[data-action="restore-backup"]').forEach(btn => {
|
|
2201
2201
|
btn.addEventListener('click', async () => {
|
|
2202
|
-
if (!confirm('确定要恢复此备份吗?这将覆盖当前数据�?)) {
|
|
2202
|
+
if (!confirm('确定要恢复此备份吗?这将覆盖当前数据�?)) {
|
|
2203
2203
|
return;
|
|
2204
2204
|
}
|
|
2205
2205
|
|
|
@@ -2239,7 +2239,7 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
2239
2239
|
|
|
2240
2240
|
const result = await response.json();
|
|
2241
2241
|
if (result.success) {
|
|
2242
|
-
alert('备份删除成功�?);
|
|
2242
|
+
alert('备份删除成功�?);
|
|
2243
2243
|
renderBackupView(container);
|
|
2244
2244
|
} else {
|
|
2245
2245
|
alert('删除失败: ' + result.error.message);
|
|
@@ -2264,7 +2264,7 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
2264
2264
|
// 知识库视图(管理员)
|
|
2265
2265
|
async function renderKnowledgeView(container) {
|
|
2266
2266
|
if (!currentGroup) {
|
|
2267
|
-
container.innerHTML = '<div class="empty-state">请先选择一个群�?/div>';
|
|
2267
|
+
container.innerHTML = '<div class="empty-state">请先选择一个群�?/div>';
|
|
2268
2268
|
return;
|
|
2269
2269
|
}
|
|
2270
2270
|
|
|
@@ -2277,7 +2277,7 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
2277
2277
|
|
|
2278
2278
|
container.innerHTML = `
|
|
2279
2279
|
<div class="view-header">
|
|
2280
|
-
<h2>📚 知识库管�?- ${currentGroup.name}</h2>
|
|
2280
|
+
<h2>📚 知识库管�?- ${currentGroup.name}</h2>
|
|
2281
2281
|
<button class="btn-primary" id="createKnowledgeBtn">+ 新建文档</button>
|
|
2282
2282
|
</div>
|
|
2283
2283
|
<div class="knowledge-list" id="knowledgeList"></div>
|
|
@@ -2286,7 +2286,7 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
2286
2286
|
const knowledgeList = document.getElementById('knowledgeList');
|
|
2287
2287
|
|
|
2288
2288
|
if (!result.data || !result.data.knowledgeList || result.data.knowledgeList.length === 0) {
|
|
2289
|
-
knowledgeList.innerHTML = '<div class="empty-state">暂无知识库文�?/div>';
|
|
2289
|
+
knowledgeList.innerHTML = '<div class="empty-state">暂无知识库文�?/div>';
|
|
2290
2290
|
} else {
|
|
2291
2291
|
result.data.knowledgeList.forEach(kb => {
|
|
2292
2292
|
const kbCard = document.createElement('div');
|
|
@@ -2294,11 +2294,11 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
2294
2294
|
kbCard.innerHTML = `
|
|
2295
2295
|
<div class="kb-header">
|
|
2296
2296
|
<h3>${kb.title}</h3>
|
|
2297
|
-
<span class="kb-status ${kb.status}">${kb.status === 'published' ? '已发�? : kb.status === 'draft' ? '草稿' : '已归�?}</span>
|
|
2297
|
+
<span class="kb-status ${kb.status}">${kb.status === 'published' ? '已发�? : kb.status === 'draft' ? '草稿' : '已归�?}</span>
|
|
2298
2298
|
</div>
|
|
2299
2299
|
<div class="kb-meta">
|
|
2300
|
-
<span>📁 ${kb.category || '未分�?}</span>
|
|
2301
|
-
<span>👁�?${kb.views || 0} 浏览</span>
|
|
2300
|
+
<span>📁 ${kb.category || '未分�?}</span>
|
|
2301
|
+
<span>👁�?${kb.views || 0} 浏览</span>
|
|
2302
2302
|
<span>👍 ${kb.likes ? kb.likes.length : 0} 点赞</span>
|
|
2303
2303
|
</div>
|
|
2304
2304
|
<div class="kb-actions">
|
|
@@ -2310,7 +2310,7 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
2310
2310
|
knowledgeList.appendChild(kbCard);
|
|
2311
2311
|
});
|
|
2312
2312
|
|
|
2313
|
-
// 查看、编辑、删除事�? document.querySelectorAll('[data-action="view-kb"]').forEach(btn => {
|
|
2313
|
+
// 查看、编辑、删除事�? document.querySelectorAll('[data-action="view-kb"]').forEach(btn => {
|
|
2314
2314
|
btn.addEventListener('click', async () => {
|
|
2315
2315
|
await showKnowledgeDetail(btn.dataset.id);
|
|
2316
2316
|
});
|
|
@@ -2330,7 +2330,7 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
2330
2330
|
method: 'DELETE',
|
|
2331
2331
|
headers: { 'Authorization': `Bearer ${token}` }
|
|
2332
2332
|
});
|
|
2333
|
-
alert('删除成功�?);
|
|
2333
|
+
alert('删除成功�?);
|
|
2334
2334
|
renderKnowledgeView(container);
|
|
2335
2335
|
} catch (error) {
|
|
2336
2336
|
alert('删除失败: ' + error.message);
|
|
@@ -2345,10 +2345,10 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
2345
2345
|
});
|
|
2346
2346
|
|
|
2347
2347
|
} catch (error) {
|
|
2348
|
-
console.error('加载知识库失�?', error);
|
|
2348
|
+
console.error('加载知识库失�?', error);
|
|
2349
2349
|
container.innerHTML = `
|
|
2350
2350
|
<div class="view-header">
|
|
2351
|
-
<h2>📚 知识库管�?/h2>
|
|
2351
|
+
<h2>📚 知识库管�?/h2>
|
|
2352
2352
|
</div>
|
|
2353
2353
|
<div class="empty-state">加载失败: ${error.message}</div>
|
|
2354
2354
|
`;
|
|
@@ -2363,28 +2363,28 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
2363
2363
|
modal.innerHTML = `
|
|
2364
2364
|
<div class="modal-content" style="max-width: 800px;">
|
|
2365
2365
|
<div class="modal-header">
|
|
2366
|
-
<h3>📚 创建知识库文�?/h3>
|
|
2366
|
+
<h3>📚 创建知识库文�?/h3>
|
|
2367
2367
|
<button class="close-btn" id="closeCreateKnowledge">×</button>
|
|
2368
2368
|
</div>
|
|
2369
2369
|
<form id="createKnowledgeForm">
|
|
2370
2370
|
<div class="form-group">
|
|
2371
2371
|
<label>标题 *</label>
|
|
2372
|
-
<input type="text" id="kbTitle" required placeholder="请输入文档标�?>
|
|
2372
|
+
<input type="text" id="kbTitle" required placeholder="请输入文档标�?>
|
|
2373
2373
|
</div>
|
|
2374
2374
|
<div class="form-group">
|
|
2375
2375
|
<label>分类</label>
|
|
2376
2376
|
<input type="text" id="kbCategory" placeholder="例如:技术文档、产品说明等">
|
|
2377
2377
|
</div>
|
|
2378
2378
|
<div class="form-group">
|
|
2379
|
-
<label>标签(用逗号分隔�?/label>
|
|
2380
|
-
<input type="text" id="kbTags" placeholder="例如:前�?React,教程">
|
|
2379
|
+
<label>标签(用逗号分隔�?/label>
|
|
2380
|
+
<input type="text" id="kbTags" placeholder="例如:前�?React,教程">
|
|
2381
2381
|
</div>
|
|
2382
2382
|
<div class="form-group">
|
|
2383
2383
|
<label>内容 *</label>
|
|
2384
|
-
<textarea id="kbContent" rows="10" required placeholder="请输入文档内�?></textarea>
|
|
2384
|
+
<textarea id="kbContent" rows="10" required placeholder="请输入文档内�?></textarea>
|
|
2385
2385
|
</div>
|
|
2386
2386
|
<div class="form-group">
|
|
2387
|
-
<label>状�?/label>
|
|
2387
|
+
<label>状�?/label>
|
|
2388
2388
|
<select id="kbStatus">
|
|
2389
2389
|
<option value="draft">草稿</option>
|
|
2390
2390
|
<option value="published">发布</option>
|
|
@@ -2395,15 +2395,15 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
2395
2395
|
<select id="kbReadPermission">
|
|
2396
2396
|
<option value="group">群组成员可见</option>
|
|
2397
2397
|
<option value="public">公开</option>
|
|
2398
|
-
<option value="private">仅自己可�?/option>
|
|
2398
|
+
<option value="private">仅自己可�?/option>
|
|
2399
2399
|
</select>
|
|
2400
2400
|
</div>
|
|
2401
2401
|
<div class="form-group">
|
|
2402
2402
|
<label>编辑权限</label>
|
|
2403
2403
|
<select id="kbWritePermission">
|
|
2404
|
-
<option value="author">仅作�?/option>
|
|
2405
|
-
<option value="admin">管理�?/option>
|
|
2406
|
-
<option value="all">所有成�?/option>
|
|
2404
|
+
<option value="author">仅作�?/option>
|
|
2405
|
+
<option value="admin">管理�?/option>
|
|
2406
|
+
<option value="all">所有成�?/option>
|
|
2407
2407
|
</select>
|
|
2408
2408
|
</div>
|
|
2409
2409
|
<div style="display: flex; gap: 10px; margin-top: 20px;">
|
|
@@ -2462,7 +2462,7 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
2462
2462
|
body: JSON.stringify({
|
|
2463
2463
|
title,
|
|
2464
2464
|
content,
|
|
2465
|
-
category: category || '未分�?,
|
|
2465
|
+
category: category || '未分�?,
|
|
2466
2466
|
tags,
|
|
2467
2467
|
groupId: currentGroup._id,
|
|
2468
2468
|
status,
|
|
@@ -2478,19 +2478,19 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
2478
2478
|
if (result.success) {
|
|
2479
2479
|
alert('知识库文档创建成功!');
|
|
2480
2480
|
modal.remove();
|
|
2481
|
-
// 重新加载知识库列�? const contentArea = document.getElementById('contentArea');
|
|
2481
|
+
// 重新加载知识库列�? const contentArea = document.getElementById('contentArea');
|
|
2482
2482
|
renderKnowledgeView(contentArea);
|
|
2483
2483
|
} else {
|
|
2484
2484
|
alert('创建失败: ' + (result.error?.message || result.message || '未知错误'));
|
|
2485
2485
|
}
|
|
2486
2486
|
} catch (error) {
|
|
2487
|
-
console.error('创建知识库文档失�?', error);
|
|
2487
|
+
console.error('创建知识库文档失�?', error);
|
|
2488
2488
|
alert('创建失败: ' + error.message);
|
|
2489
2489
|
}
|
|
2490
2490
|
});
|
|
2491
2491
|
}
|
|
2492
2492
|
|
|
2493
|
-
// 显示知识库文档详�? async function showKnowledgeDetail(knowledgeId) {
|
|
2493
|
+
// 显示知识库文档详�? async function showKnowledgeDetail(knowledgeId) {
|
|
2494
2494
|
try {
|
|
2495
2495
|
const token = localStorage.getItem('token');
|
|
2496
2496
|
const response = await fetch(`http://localhost:3000/api/knowledge/${knowledgeId}`, {
|
|
@@ -2514,14 +2514,14 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
2514
2514
|
</div>
|
|
2515
2515
|
<div style="padding: 20px;">
|
|
2516
2516
|
<div style="display: flex; gap: 20px; margin-bottom: 20px; padding-bottom: 20px; border-bottom: 1px solid var(--border);">
|
|
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>
|
|
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>
|
|
2521
2521
|
</div>
|
|
2522
2522
|
${kb.tags && kb.tags.length > 0 ? `
|
|
2523
2523
|
<div style="margin-bottom: 20px;">
|
|
2524
|
-
<strong>标签�?/strong>
|
|
2524
|
+
<strong>标签�?/strong>
|
|
2525
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('')}
|
|
2526
2526
|
</div>
|
|
2527
2527
|
` : ''}
|
|
@@ -2530,8 +2530,8 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
2530
2530
|
</div>
|
|
2531
2531
|
<div style="margin-top: 20px; padding-top: 20px; border-top: 1px solid var(--border); color: var(--text-secondary); font-size: 13px;">
|
|
2532
2532
|
<div>作者:${kb.author?.username || '未知'}</div>
|
|
2533
|
-
<div>创建时间�?{new Date(kb.createdAt).toLocaleString()}</div>
|
|
2534
|
-
<div>更新时间�?{new Date(kb.updatedAt).toLocaleString()}</div>
|
|
2533
|
+
<div>创建时间�?{new Date(kb.createdAt).toLocaleString()}</div>
|
|
2534
|
+
<div>更新时间�?{new Date(kb.updatedAt).toLocaleString()}</div>
|
|
2535
2535
|
</div>
|
|
2536
2536
|
</div>
|
|
2537
2537
|
</div>
|
|
@@ -2549,7 +2549,7 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
2549
2549
|
}
|
|
2550
2550
|
});
|
|
2551
2551
|
} catch (error) {
|
|
2552
|
-
console.error('加载知识库详情失�?', error);
|
|
2552
|
+
console.error('加载知识库详情失�?', error);
|
|
2553
2553
|
alert('加载失败: ' + error.message);
|
|
2554
2554
|
}
|
|
2555
2555
|
}
|
|
@@ -2574,7 +2574,7 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
2574
2574
|
modal.innerHTML = `
|
|
2575
2575
|
<div class="modal-content" style="max-width: 800px;">
|
|
2576
2576
|
<div class="modal-header">
|
|
2577
|
-
<h3>✏️ 编辑知识库文�?/h3>
|
|
2577
|
+
<h3>✏️ 编辑知识库文�?/h3>
|
|
2578
2578
|
<button class="close-btn" id="closeEditKnowledge">×</button>
|
|
2579
2579
|
</div>
|
|
2580
2580
|
<form id="editKnowledgeForm">
|
|
@@ -2587,7 +2587,7 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
2587
2587
|
<input type="text" id="editKbCategory" value="${kb.category || ''}">
|
|
2588
2588
|
</div>
|
|
2589
2589
|
<div class="form-group">
|
|
2590
|
-
<label>标签(用逗号分隔�?/label>
|
|
2590
|
+
<label>标签(用逗号分隔�?/label>
|
|
2591
2591
|
<input type="text" id="editKbTags" value="${kb.tags ? kb.tags.join(', ') : ''}">
|
|
2592
2592
|
</div>
|
|
2593
2593
|
<div class="form-group">
|
|
@@ -2595,7 +2595,7 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
2595
2595
|
<textarea id="editKbContent" rows="10" required>${kb.content}</textarea>
|
|
2596
2596
|
</div>
|
|
2597
2597
|
<div class="form-group">
|
|
2598
|
-
<label>状�?/label>
|
|
2598
|
+
<label>状�?/label>
|
|
2599
2599
|
<select id="editKbStatus">
|
|
2600
2600
|
<option value="draft" ${kb.status === 'draft' ? 'selected' : ''}>草稿</option>
|
|
2601
2601
|
<option value="published" ${kb.status === 'published' ? 'selected' : ''}>发布</option>
|
|
@@ -2607,15 +2607,15 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
2607
2607
|
<select id="editKbReadPermission">
|
|
2608
2608
|
<option value="group" ${kb.permissions?.read === 'group' ? 'selected' : ''}>群组成员可见</option>
|
|
2609
2609
|
<option value="public" ${kb.permissions?.read === 'public' ? 'selected' : ''}>公开</option>
|
|
2610
|
-
<option value="private" ${kb.permissions?.read === 'private' ? 'selected' : ''}>仅自己可�?/option>
|
|
2610
|
+
<option value="private" ${kb.permissions?.read === 'private' ? 'selected' : ''}>仅自己可�?/option>
|
|
2611
2611
|
</select>
|
|
2612
2612
|
</div>
|
|
2613
2613
|
<div class="form-group">
|
|
2614
2614
|
<label>编辑权限</label>
|
|
2615
2615
|
<select id="editKbWritePermission">
|
|
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>
|
|
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>
|
|
2619
2619
|
</select>
|
|
2620
2620
|
</div>
|
|
2621
2621
|
<div style="display: flex; gap: 10px; margin-top: 20px;">
|
|
@@ -2671,7 +2671,7 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
2671
2671
|
body: JSON.stringify({
|
|
2672
2672
|
title,
|
|
2673
2673
|
content,
|
|
2674
|
-
category: category || '未分�?,
|
|
2674
|
+
category: category || '未分�?,
|
|
2675
2675
|
tags,
|
|
2676
2676
|
status,
|
|
2677
2677
|
permissions: {
|
|
@@ -2687,18 +2687,18 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
2687
2687
|
if (updateResult.success) {
|
|
2688
2688
|
alert('知识库文档更新成功!');
|
|
2689
2689
|
modal.remove();
|
|
2690
|
-
// 重新加载知识库列�? const contentArea = document.getElementById('contentArea');
|
|
2690
|
+
// 重新加载知识库列�? const contentArea = document.getElementById('contentArea');
|
|
2691
2691
|
renderKnowledgeView(contentArea);
|
|
2692
2692
|
} else {
|
|
2693
2693
|
alert('更新失败: ' + (updateResult.error?.message || updateResult.message || '未知错误'));
|
|
2694
2694
|
}
|
|
2695
2695
|
} catch (error) {
|
|
2696
|
-
console.error('更新知识库文档失�?', error);
|
|
2696
|
+
console.error('更新知识库文档失�?', error);
|
|
2697
2697
|
alert('更新失败: ' + error.message);
|
|
2698
2698
|
}
|
|
2699
2699
|
});
|
|
2700
2700
|
} catch (error) {
|
|
2701
|
-
console.error('加载知识库文档失�?', error);
|
|
2701
|
+
console.error('加载知识库文档失�?', error);
|
|
2702
2702
|
alert('加载失败: ' + error.message);
|
|
2703
2703
|
}
|
|
2704
2704
|
}
|
|
@@ -2706,7 +2706,7 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
2706
2706
|
// 工作流视图(管理员)
|
|
2707
2707
|
async function renderWorkflowsView(container) {
|
|
2708
2708
|
if (!currentGroup) {
|
|
2709
|
-
container.innerHTML = '<div class="empty-state">请先选择一个群�?/div>';
|
|
2709
|
+
container.innerHTML = '<div class="empty-state">请先选择一个群�?/div>';
|
|
2710
2710
|
return;
|
|
2711
2711
|
}
|
|
2712
2712
|
|
|
@@ -2720,33 +2720,33 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
2720
2720
|
|
|
2721
2721
|
container.innerHTML = `
|
|
2722
2722
|
<div class="view-header">
|
|
2723
|
-
<h2>🔄 工作流管�?- ${currentGroup.name}</h2>
|
|
2724
|
-
<button class="btn-primary" id="createWorkflowBtn">+ 创建工作�?/button>
|
|
2723
|
+
<h2>🔄 工作流管�?- ${currentGroup.name}</h2>
|
|
2724
|
+
<button class="btn-primary" id="createWorkflowBtn">+ 创建工作�?/button>
|
|
2725
2725
|
</div>
|
|
2726
2726
|
|
|
2727
2727
|
<div class="info-box" style="background: var(--bg-card); padding: 20px; border-radius: 12px; margin-bottom: 20px; border-left: 4px solid var(--primary);">
|
|
2728
2728
|
<h3 style="margin-bottom: 10px;">💡 什么是工作流?</h3>
|
|
2729
2729
|
<p style="color: var(--text-secondary); line-height: 1.6; margin-bottom: 15px;">
|
|
2730
|
-
工作流是一�?strong>自动化流程引�?/strong>,可以自动执行一系列预定义的操作,提高团队协作效率�? </p>
|
|
2730
|
+
工作流是一�?strong>自动化流程引�?/strong>,可以自动执行一系列预定义的操作,提高团队协作效率�? </p>
|
|
2731
2731
|
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 15px; margin-top: 15px;">
|
|
2732
2732
|
<div style="background: var(--bg-dark); padding: 15px; border-radius: 8px;">
|
|
2733
2733
|
<div style="font-size: 24px; margin-bottom: 8px;">📋</div>
|
|
2734
2734
|
<strong>文档审批流程</strong>
|
|
2735
2735
|
<p style="font-size: 13px; color: var(--text-secondary); margin-top: 5px;">
|
|
2736
|
-
自动通知审批�?�?等待审批 �?审批通过后发�? </p>
|
|
2736
|
+
自动通知审批�?�?等待审批 �?审批通过后发�? </p>
|
|
2737
2737
|
</div>
|
|
2738
2738
|
<div style="background: var(--bg-dark); padding: 15px; border-radius: 8px;">
|
|
2739
|
-
<div style="font-size: 24px; margin-bottom: 8px;">�?/div>
|
|
2739
|
+
<div style="font-size: 24px; margin-bottom: 8px;">�?/div>
|
|
2740
2740
|
<strong>任务自动分配</strong>
|
|
2741
2741
|
<p style="font-size: 13px; color: var(--text-secondary); margin-top: 5px;">
|
|
2742
|
-
新任务创�?�?自动分配成员 �?发送通知
|
|
2742
|
+
新任务创�?�?自动分配成员 �?发送通知
|
|
2743
2743
|
</p>
|
|
2744
2744
|
</div>
|
|
2745
2745
|
<div style="background: var(--bg-dark); padding: 15px; border-radius: 8px;">
|
|
2746
|
-
<div style="font-size: 24px; margin-bottom: 8px;">�?/div>
|
|
2746
|
+
<div style="font-size: 24px; margin-bottom: 8px;">�?/div>
|
|
2747
2747
|
<strong>定时报告生成</strong>
|
|
2748
2748
|
<p style="font-size: 13px; color: var(--text-secondary); margin-top: 5px;">
|
|
2749
|
-
每天凌晨2�?�?收集数据 �?生成报告 �?发送邮�? </p>
|
|
2749
|
+
每天凌晨2�?�?收集数据 �?生成报告 �?发送邮�? </p>
|
|
2750
2750
|
</div>
|
|
2751
2751
|
</div>
|
|
2752
2752
|
</div>
|
|
@@ -2757,17 +2757,17 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
2757
2757
|
<div class="modal hidden" id="workflowModal">
|
|
2758
2758
|
<div class="modal-content" style="max-width: 720px;">
|
|
2759
2759
|
<div class="modal-header">
|
|
2760
|
-
<h3 id="workflowModalTitle">创建工作�?/h3>
|
|
2760
|
+
<h3 id="workflowModalTitle">创建工作�?/h3>
|
|
2761
2761
|
<button class="modal-close" id="closeWorkflowModal">×</button>
|
|
2762
2762
|
</div>
|
|
2763
2763
|
<form id="workflowForm">
|
|
2764
2764
|
<div class="form-group">
|
|
2765
2765
|
<label>名称 *</label>
|
|
2766
|
-
<input type="text" id="wfName" required placeholder="例如:文档审批流�?>
|
|
2766
|
+
<input type="text" id="wfName" required placeholder="例如:文档审批流�?>
|
|
2767
2767
|
</div>
|
|
2768
2768
|
<div class="form-group">
|
|
2769
2769
|
<label>描述</label>
|
|
2770
|
-
<textarea id="wfDescription" rows="2" placeholder="说明此工作流的用�?></textarea>
|
|
2770
|
+
<textarea id="wfDescription" rows="2" placeholder="说明此工作流的用�?></textarea>
|
|
2771
2771
|
</div>
|
|
2772
2772
|
<div class="form-group">
|
|
2773
2773
|
<label>触发方式</label>
|
|
@@ -2778,17 +2778,17 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
2778
2778
|
</div>
|
|
2779
2779
|
<div class="form-group" id="wfScheduleGroup">
|
|
2780
2780
|
<label>定时表达式(可选)</label>
|
|
2781
|
-
<input type="text" id="wfSchedule" placeholder="例如�? 2 * * * 表示每天凌晨2�?>
|
|
2782
|
-
<small>使用 Cron 表达式配置定时触发时�?/small>
|
|
2781
|
+
<input type="text" id="wfSchedule" placeholder="例如�? 2 * * * 表示每天凌晨2�?>
|
|
2782
|
+
<small>使用 Cron 表达式配置定时触发时�?/small>
|
|
2783
2783
|
</div>
|
|
2784
2784
|
<div class="form-group">
|
|
2785
|
-
<label>步骤配置(JSON 数组�?/label>
|
|
2786
|
-
<textarea id="wfSteps" rows="6" placeholder='例如:[{"name":"通知审批�?,"type":"notification","config":{"message":"有新文档需要审�?}}]'></textarea>
|
|
2787
|
-
<small>高级用法:直接编�?JSON,字段与后端 Workflow.steps 一�?/small>
|
|
2785
|
+
<label>步骤配置(JSON 数组�?/label>
|
|
2786
|
+
<textarea id="wfSteps" rows="6" placeholder='例如:[{"name":"通知审批�?,"type":"notification","config":{"message":"有新文档需要审�?}}]'></textarea>
|
|
2787
|
+
<small>高级用法:直接编�?JSON,字段与后端 Workflow.steps 一�?/small>
|
|
2788
2788
|
</div>
|
|
2789
2789
|
<div class="form-actions">
|
|
2790
2790
|
<button type="button" class="btn-secondary" id="cancelWorkflow">取消</button>
|
|
2791
|
-
<button type="submit" class="btn-primary">保存工作�?/button>
|
|
2791
|
+
<button type="submit" class="btn-primary">保存工作�?/button>
|
|
2792
2792
|
</div>
|
|
2793
2793
|
</form>
|
|
2794
2794
|
</div>
|
|
@@ -2798,17 +2798,17 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
2798
2798
|
const workflowsList = document.getElementById('workflowsList');
|
|
2799
2799
|
|
|
2800
2800
|
if (workflows.length === 0) {
|
|
2801
|
-
workflowsList.innerHTML = '<div class="empty-state">暂无工作�?br><small style="color: var(--text-secondary);">点击"创建工作�?开始自动化您的工作流程</small></div>';
|
|
2801
|
+
workflowsList.innerHTML = '<div class="empty-state">暂无工作�?br><small style="color: var(--text-secondary);">点击"创建工作�?开始自动化您的工作流程</small></div>';
|
|
2802
2802
|
} else {
|
|
2803
2803
|
workflows.forEach(wf => {
|
|
2804
2804
|
const wfCard = document.createElement('div');
|
|
2805
2805
|
wfCard.className = 'workflow-card';
|
|
2806
2806
|
const triggerType = wf.trigger && wf.trigger.type ? wf.trigger.type : 'manual';
|
|
2807
2807
|
const triggerLabel = triggerType === 'manual'
|
|
2808
|
-
? '🖱�?手动'
|
|
2808
|
+
? '🖱�?手动'
|
|
2809
2809
|
: triggerType === 'scheduled'
|
|
2810
|
-
? '�?定时'
|
|
2811
|
-
: '�?事件';
|
|
2810
|
+
? '�?定时'
|
|
2811
|
+
: '�?事件';
|
|
2812
2812
|
const stepsCount = Array.isArray(wf.steps) ? wf.steps.length : 0;
|
|
2813
2813
|
const totalExec = wf.stats && typeof wf.stats.totalExecutions === 'number'
|
|
2814
2814
|
? wf.stats.totalExecutions
|
|
@@ -2817,13 +2817,13 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
2817
2817
|
wfCard.innerHTML = `
|
|
2818
2818
|
<div class="wf-header">
|
|
2819
2819
|
<h3>${wf.name}</h3>
|
|
2820
|
-
<span class="wf-status ${wf.status}">${wf.status === 'active' ? '�?已激�? : wf.status === 'inactive' ? '⏸️ 已停�? : '📝 草稿'}</span>
|
|
2820
|
+
<span class="wf-status ${wf.status}">${wf.status === 'active' ? '�?已激�? : wf.status === 'inactive' ? '⏸️ 已停�? : '📝 草稿'}</span>
|
|
2821
2821
|
</div>
|
|
2822
2822
|
<p>${wf.description || '暂无描述'}</p>
|
|
2823
2823
|
<div class="wf-meta">
|
|
2824
|
-
<span>触发�? ${triggerLabel}</span>
|
|
2824
|
+
<span>触发�? ${triggerLabel}</span>
|
|
2825
2825
|
<span>步骤: ${stepsCount}</span>
|
|
2826
|
-
<span>执行: ${totalExec} �?/span>
|
|
2826
|
+
<span>执行: ${totalExec} �?/span>
|
|
2827
2827
|
</div>
|
|
2828
2828
|
<div class="wf-actions">
|
|
2829
2829
|
<button class="btn-primary btn-sm" data-id="${wf._id}" data-action="view-wf">查看详情</button>
|
|
@@ -2832,7 +2832,7 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
2832
2832
|
<button class="btn-secondary btn-sm" data-id="${wf._id}" data-action="trigger-wf">手动触发</button>
|
|
2833
2833
|
<button class="btn-warning btn-sm" data-id="${wf._id}" data-action="deactivate-wf">停用</button>
|
|
2834
2834
|
` : `
|
|
2835
|
-
<button class="btn-success btn-sm" data-id="${wf._id}" data-action="activate-wf">激�?/button>
|
|
2835
|
+
<button class="btn-success btn-sm" data-id="${wf._id}" data-action="activate-wf">激�?/button>
|
|
2836
2836
|
`}
|
|
2837
2837
|
<button class="btn-danger btn-sm" data-id="${wf._id}" data-action="delete-wf">删除</button>
|
|
2838
2838
|
</div>
|
|
@@ -2841,17 +2841,17 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
2841
2841
|
});
|
|
2842
2842
|
}
|
|
2843
2843
|
|
|
2844
|
-
// 详情查看(暂时简单提示,可后续扩展为真正详情面板�? document.querySelectorAll('[data-action="view-wf"]').forEach(btn => {
|
|
2844
|
+
// 详情查看(暂时简单提示,可后续扩展为真正详情面板�? document.querySelectorAll('[data-action="view-wf"]').forEach(btn => {
|
|
2845
2845
|
const wf = workflows.find(w => w._id === btn.dataset.id);
|
|
2846
2846
|
btn.addEventListener('click', () => {
|
|
2847
|
-
alert(`工作流详情:\n\n名称�?{wf.name}\n状态:${wf.status}\n触发方式�?{wf.trigger?.type || 'manual'}\n步骤数:${(wf.steps || []).length}`);
|
|
2847
|
+
alert(`工作流详情:\n\n名称�?{wf.name}\n状态:${wf.status}\n触发方式�?{wf.trigger?.type || 'manual'}\n步骤数:${(wf.steps || []).length}`);
|
|
2848
2848
|
});
|
|
2849
2849
|
});
|
|
2850
2850
|
|
|
2851
2851
|
// 手动触发
|
|
2852
2852
|
document.querySelectorAll('[data-action="trigger-wf"]').forEach(btn => {
|
|
2853
2853
|
btn.addEventListener('click', async () => {
|
|
2854
|
-
if (confirm('确定要手动触发此工作流吗�?)) {
|
|
2854
|
+
if (confirm('确定要手动触发此工作流吗�?)) {
|
|
2855
2855
|
try {
|
|
2856
2856
|
await fetch(`http://localhost:3000/api/workflows/${btn.dataset.id}/trigger`, {
|
|
2857
2857
|
method: 'POST',
|
|
@@ -2861,7 +2861,7 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
2861
2861
|
},
|
|
2862
2862
|
body: JSON.stringify({ triggerData: {} })
|
|
2863
2863
|
});
|
|
2864
|
-
alert('工作流已触发�?);
|
|
2864
|
+
alert('工作流已触发�?);
|
|
2865
2865
|
} catch (error) {
|
|
2866
2866
|
alert('触发失败: ' + error.message);
|
|
2867
2867
|
}
|
|
@@ -2869,7 +2869,7 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
2869
2869
|
});
|
|
2870
2870
|
});
|
|
2871
2871
|
|
|
2872
|
-
// 激�?停用
|
|
2872
|
+
// 激�?停用
|
|
2873
2873
|
document.querySelectorAll('[data-action="activate-wf"], [data-action="deactivate-wf"]').forEach(btn => {
|
|
2874
2874
|
btn.addEventListener('click', async () => {
|
|
2875
2875
|
const action = btn.dataset.action === 'activate-wf' ? 'activate' : 'deactivate';
|
|
@@ -2886,7 +2886,7 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
2886
2886
|
});
|
|
2887
2887
|
});
|
|
2888
2888
|
|
|
2889
|
-
// 删除工作�? document.querySelectorAll('[data-action="delete-wf"]').forEach(btn => {
|
|
2889
|
+
// 删除工作�? document.querySelectorAll('[data-action="delete-wf"]').forEach(btn => {
|
|
2890
2890
|
btn.addEventListener('click', async () => {
|
|
2891
2891
|
if (confirm('确定要删除这个工作流吗?删除后无法恢复!')) {
|
|
2892
2892
|
try {
|
|
@@ -2923,7 +2923,7 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
2923
2923
|
|
|
2924
2924
|
function openWorkflowModal(workflow) {
|
|
2925
2925
|
editingWorkflow = workflow || null;
|
|
2926
|
-
workflowModalTitle.textContent = workflow ? '编辑工作�? : '创建工作�?;
|
|
2926
|
+
workflowModalTitle.textContent = workflow ? '编辑工作�? : '创建工作�?;
|
|
2927
2927
|
nameInput.value = workflow?.name || '';
|
|
2928
2928
|
descInput.value = workflow?.description || '';
|
|
2929
2929
|
const type = workflow?.trigger?.type || 'manual';
|
|
@@ -2955,7 +2955,7 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
2955
2955
|
btn.addEventListener('click', () => openWorkflowModal(wf));
|
|
2956
2956
|
});
|
|
2957
2957
|
|
|
2958
|
-
// 保存工作�? workflowForm.addEventListener('submit', async (e) => {
|
|
2958
|
+
// 保存工作�? workflowForm.addEventListener('submit', async (e) => {
|
|
2959
2959
|
e.preventDefault();
|
|
2960
2960
|
const name = nameInput.value.trim();
|
|
2961
2961
|
if (!name) {
|
|
@@ -2972,11 +2972,11 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
2972
2972
|
try {
|
|
2973
2973
|
const parsed = JSON.parse(stepsText);
|
|
2974
2974
|
if (!Array.isArray(parsed)) {
|
|
2975
|
-
throw new Error('步骤配置必须�?JSON 数组');
|
|
2975
|
+
throw new Error('步骤配置必须�?JSON 数组');
|
|
2976
2976
|
}
|
|
2977
2977
|
steps = parsed;
|
|
2978
2978
|
} catch (err) {
|
|
2979
|
-
alert('步骤配置必须是合法的 JSON 数组�? + err.message);
|
|
2979
|
+
alert('步骤配置必须是合法的 JSON 数组�? + err.message);
|
|
2980
2980
|
return;
|
|
2981
2981
|
}
|
|
2982
2982
|
}
|
|
@@ -3019,10 +3019,10 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
3019
3019
|
});
|
|
3020
3020
|
|
|
3021
3021
|
} catch (error) {
|
|
3022
|
-
console.error('加载工作流失�?', error);
|
|
3022
|
+
console.error('加载工作流失�?', error);
|
|
3023
3023
|
container.innerHTML = `
|
|
3024
3024
|
<div class="view-header">
|
|
3025
|
-
<h2>🔄 工作流管�?/h2>
|
|
3025
|
+
<h2>🔄 工作流管�?/h2>
|
|
3026
3026
|
</div>
|
|
3027
3027
|
<div class="empty-state">加载失败: ${error.message}</div>
|
|
3028
3028
|
`;
|
|
@@ -3031,10 +3031,10 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
3031
3031
|
|
|
3032
3032
|
function getStatusText(status) {
|
|
3033
3033
|
const statusMap = {
|
|
3034
|
-
'pending': '待处�?,
|
|
3035
|
-
'in_progress': '进行�?,
|
|
3036
|
-
'completed': '已完�?,
|
|
3037
|
-
'terminated': '已终�?
|
|
3034
|
+
'pending': '待处�?,
|
|
3035
|
+
'in_progress': '进行�?,
|
|
3036
|
+
'completed': '已完�?,
|
|
3037
|
+
'terminated': '已终�?
|
|
3038
3038
|
};
|
|
3039
3039
|
return statusMap[status] || status;
|
|
3040
3040
|
}
|
|
@@ -3065,7 +3065,7 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
3065
3065
|
<h2>💾 备份管理</h2>
|
|
3066
3066
|
<div style="display: flex; gap: 10px;">
|
|
3067
3067
|
<button class="btn-primary" id="createBackupBtn">
|
|
3068
|
-
<span style="font-size: 18px;">�?/span> 立即备份
|
|
3068
|
+
<span style="font-size: 18px;">�?/span> 立即备份
|
|
3069
3069
|
</button>
|
|
3070
3070
|
<button class="btn-secondary" id="configBackupBtn">
|
|
3071
3071
|
<span style="font-size: 18px;">⚙️</span> 备份设置
|
|
@@ -3087,11 +3087,11 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
3087
3087
|
|
|
3088
3088
|
<div class="stat-card-modern">
|
|
3089
3089
|
<div class="stat-icon" style="background: linear-gradient(135deg, #10b981 0%, #059669 100%);">
|
|
3090
|
-
${config.autoBackup ? '�? : '�?}
|
|
3090
|
+
${config.autoBackup ? '�? : '�?}
|
|
3091
3091
|
</div>
|
|
3092
3092
|
<div class="stat-content">
|
|
3093
3093
|
<div class="stat-label">自动备份</div>
|
|
3094
|
-
<div class="stat-value">${config.autoBackup ? '已启�? : '已禁�?}</div>
|
|
3094
|
+
<div class="stat-value">${config.autoBackup ? '已启�? : '已禁�?}</div>
|
|
3095
3095
|
</div>
|
|
3096
3096
|
</div>
|
|
3097
3097
|
|
|
@@ -3101,7 +3101,7 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
3101
3101
|
</div>
|
|
3102
3102
|
<div class="stat-content">
|
|
3103
3103
|
<div class="stat-label">备份频率</div>
|
|
3104
|
-
<div class="stat-value">${config.schedule || '未设�?}</div>
|
|
3104
|
+
<div class="stat-value">${config.schedule || '未设�?}</div>
|
|
3105
3105
|
</div>
|
|
3106
3106
|
</div>
|
|
3107
3107
|
|
|
@@ -3111,7 +3111,7 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
3111
3111
|
</div>
|
|
3112
3112
|
<div class="stat-content">
|
|
3113
3113
|
<div class="stat-label">保留天数</div>
|
|
3114
|
-
<div class="stat-value">${config.retention || 30} �?/div>
|
|
3114
|
+
<div class="stat-value">${config.retention || 30} �?/div>
|
|
3115
3115
|
</div>
|
|
3116
3116
|
</div>
|
|
3117
3117
|
</div>
|
|
@@ -3119,13 +3119,13 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
3119
3119
|
<!-- 备份列表 -->
|
|
3120
3120
|
<div class="backup-list-section">
|
|
3121
3121
|
<h3 style="margin: 30px 0 20px; color: var(--text-primary); font-size: 20px;">
|
|
3122
|
-
📦 最近备�? </h3>
|
|
3122
|
+
📦 最近备�? </h3>
|
|
3123
3123
|
<div class="backup-cards-grid" id="backupCards">
|
|
3124
3124
|
${backups.length === 0 ? `
|
|
3125
3125
|
<div class="empty-state-modern">
|
|
3126
3126
|
<div class="empty-icon">📭</div>
|
|
3127
3127
|
<h3>暂无备份记录</h3>
|
|
3128
|
-
<p>点击"立即备份"创建第一个备�?/p>
|
|
3128
|
+
<p>点击"立即备份"创建第一个备�?/p>
|
|
3129
3129
|
<button class="btn-primary" onclick="document.getElementById('createBackupBtn').click()">
|
|
3130
3130
|
立即备份
|
|
3131
3131
|
</button>
|
|
@@ -3158,11 +3158,11 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
3158
3158
|
const typeConfig = {
|
|
3159
3159
|
manual: { icon: '📦', label: '手动备份', color: '#6366f1' },
|
|
3160
3160
|
scheduled: { icon: '🔄', label: '定时备份', color: '#10b981' },
|
|
3161
|
-
auto: { icon: '�?, label: '自动备份', color: '#f59e0b' }
|
|
3161
|
+
auto: { icon: '�?, label: '自动备份', color: '#f59e0b' }
|
|
3162
3162
|
};
|
|
3163
3163
|
|
|
3164
3164
|
const config = typeConfig[backup.type] || typeConfig.manual;
|
|
3165
|
-
const statusIcon = backup.status === 'completed' ? '�? : backup.status === 'failed' ? '�? : '�?;
|
|
3165
|
+
const statusIcon = backup.status === 'completed' ? '�? : backup.status === 'failed' ? '�? : '�?;
|
|
3166
3166
|
|
|
3167
3167
|
return `
|
|
3168
3168
|
<div class="backup-card-modern">
|
|
@@ -3183,8 +3183,8 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
3183
3183
|
<span class="info-value">${formatFileSize(backup.size)}</span>
|
|
3184
3184
|
</div>
|
|
3185
3185
|
<div class="backup-info-row">
|
|
3186
|
-
<span class="info-label">状�?/span>
|
|
3187
|
-
<span class="info-value">${backup.status === 'completed' ? '完成' : backup.status === 'failed' ? '失败' : '进行�?}</span>
|
|
3186
|
+
<span class="info-label">状�?/span>
|
|
3187
|
+
<span class="info-value">${backup.status === 'completed' ? '完成' : backup.status === 'failed' ? '失败' : '进行�?}</span>
|
|
3188
3188
|
</div>
|
|
3189
3189
|
</div>
|
|
3190
3190
|
|
|
@@ -3197,13 +3197,13 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
3197
3197
|
<span>🔄</span> 恢复
|
|
3198
3198
|
</button>
|
|
3199
3199
|
<button class="btn-action btn-delete" data-id="${backup._id}" data-action="delete">
|
|
3200
|
-
<span>🗑�?/span> 删除
|
|
3200
|
+
<span>🗑�?/span> 删除
|
|
3201
3201
|
</button>
|
|
3202
3202
|
</div>
|
|
3203
3203
|
` : `
|
|
3204
3204
|
<div class="backup-card-actions">
|
|
3205
3205
|
<button class="btn-action btn-delete" data-id="${backup._id}" data-action="delete">
|
|
3206
|
-
<span>🗑�?/span> 删除
|
|
3206
|
+
<span>🗑�?/span> 删除
|
|
3207
3207
|
</button>
|
|
3208
3208
|
</div>
|
|
3209
3209
|
`}
|
|
@@ -3225,7 +3225,7 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
3225
3225
|
<label>备份类型</label>
|
|
3226
3226
|
<select id="backupType" required>
|
|
3227
3227
|
<option value="full">完整备份(所有数据)</option>
|
|
3228
|
-
<option value="incremental">增量备份(仅变更�?/option>
|
|
3228
|
+
<option value="incremental">增量备份(仅变更�?/option>
|
|
3229
3229
|
</select>
|
|
3230
3230
|
</div>
|
|
3231
3231
|
<div class="form-group">
|
|
@@ -3233,7 +3233,7 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
3233
3233
|
<textarea id="backupDescription" rows="3" placeholder="备份说明..."></textarea>
|
|
3234
3234
|
</div>
|
|
3235
3235
|
<div style="display: flex; gap: 10px; margin-top: 20px;">
|
|
3236
|
-
<button type="submit" class="btn-primary">开始备�?/button>
|
|
3236
|
+
<button type="submit" class="btn-primary">开始备�?/button>
|
|
3237
3237
|
<button type="button" class="btn-secondary" id="cancelCreateBackup">取消</button>
|
|
3238
3238
|
</div>
|
|
3239
3239
|
</form>
|
|
@@ -3256,16 +3256,16 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
3256
3256
|
</div>
|
|
3257
3257
|
<div class="form-group">
|
|
3258
3258
|
<label>备份频率(Cron表达式)</label>
|
|
3259
|
-
<input type="text" id="backupSchedule" value="${config.schedule || '0 2 * * *'}" placeholder="0 2 * * * (每天凌晨2�?">
|
|
3260
|
-
<small>示例�? 2 * * * (每天凌晨2�?�? */6 * * * (�?小时)</small>
|
|
3259
|
+
<input type="text" id="backupSchedule" value="${config.schedule || '0 2 * * *'}" placeholder="0 2 * * * (每天凌晨2�?">
|
|
3260
|
+
<small>示例�? 2 * * * (每天凌晨2�?�? */6 * * * (�?小时)</small>
|
|
3261
3261
|
</div>
|
|
3262
3262
|
<div class="form-group">
|
|
3263
3263
|
<label>保留天数</label>
|
|
3264
3264
|
<input type="number" id="backupRetention" value="${config.retention || 30}" min="1" max="365">
|
|
3265
|
-
<small>超过此天数的备份将自动删�?/small>
|
|
3265
|
+
<small>超过此天数的备份将自动删�?/small>
|
|
3266
3266
|
</div>
|
|
3267
3267
|
<div class="form-group">
|
|
3268
|
-
<label>最大备份数�?/label>
|
|
3268
|
+
<label>最大备份数�?/label>
|
|
3269
3269
|
<input type="number" id="maxBackups" value="${config.maxBackups || 10}" min="1" max="100">
|
|
3270
3270
|
</div>
|
|
3271
3271
|
<div style="display: flex; gap: 10px; margin-top: 20px;">
|
|
@@ -3535,7 +3535,7 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
3535
3535
|
|
|
3536
3536
|
const result = await response.json();
|
|
3537
3537
|
if (result.success) {
|
|
3538
|
-
alert('备份创建成功�?);
|
|
3538
|
+
alert('备份创建成功�?);
|
|
3539
3539
|
document.getElementById('createBackupModal').classList.add('hidden');
|
|
3540
3540
|
renderOptimizedBackupView(container);
|
|
3541
3541
|
} else {
|
|
@@ -3568,7 +3568,7 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
3568
3568
|
|
|
3569
3569
|
const result = await response.json();
|
|
3570
3570
|
if (result.success) {
|
|
3571
|
-
alert('设置保存成功�?);
|
|
3571
|
+
alert('设置保存成功�?);
|
|
3572
3572
|
document.getElementById('configBackupModal').classList.add('hidden');
|
|
3573
3573
|
renderOptimizedBackupView(container);
|
|
3574
3574
|
} else {
|
|
@@ -3589,7 +3589,7 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
3589
3589
|
|
|
3590
3590
|
document.querySelectorAll('[data-action="restore"]').forEach(btn => {
|
|
3591
3591
|
btn.addEventListener('click', async () => {
|
|
3592
|
-
if (!confirm('确定要恢复此备份吗?这将覆盖当前数据�?)) return;
|
|
3592
|
+
if (!confirm('确定要恢复此备份吗?这将覆盖当前数据�?)) return;
|
|
3593
3593
|
|
|
3594
3594
|
const backupId = btn.dataset.id;
|
|
3595
3595
|
try {
|
|
@@ -3624,7 +3624,7 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
3624
3624
|
|
|
3625
3625
|
const result = await response.json();
|
|
3626
3626
|
if (result.success) {
|
|
3627
|
-
alert('备份删除成功�?);
|
|
3627
|
+
alert('备份删除成功�?);
|
|
3628
3628
|
renderOptimizedBackupView(container);
|
|
3629
3629
|
} else {
|
|
3630
3630
|
alert('删除失败: ' + (result.error?.message || '未知错误'));
|
|
@@ -3652,3 +3652,4 @@ export function renderAdminDashboard(user, wsService) {
|
|
|
3652
3652
|
features.showOnboarding();
|
|
3653
3653
|
}
|
|
3654
3654
|
|
|
3655
|
+
|