collabdocchat 2.0.5 → 2.0.7

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.
@@ -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">&times;</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">&times;</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">&times;</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">&times;</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
+