collabdocchat 2.1.2 → 2.1.4

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 'emoji-picker-element';
4
4
 
@@ -16,42 +16,42 @@ export function renderUserDashboard(user, wsService) {
16
16
  <aside class="sidebar">
17
17
  <div class="sidebar-header">
18
18
  <h2>CollabDocChat</h2>
19
- <span class="badge-user">鐢ㄦ埛</span>
19
+ <span class="badge-user">用户</span>
20
20
  </div>
21
21
 
22
22
  <div class="user-info">
23
23
  <div class="avatar">${user.username[0].toUpperCase()}</div>
24
24
  <div>
25
25
  <div class="username">${user.username}</div>
26
- <div class="user-role">鏅€氱敤鎴?/div>
26
+ <div class="user-role">普通用户</div>
27
27
  </div>
28
28
  </div>
29
29
 
30
30
  <nav class="nav-menu">
31
31
  <button class="nav-item active" data-view="groups">
32
- <span class="icon">馃懃</span> 鎴戠殑缇ょ粍
32
+ <span class="icon">👥</span> 我的群组
33
33
  </button>
34
34
  <button class="nav-item" data-view="allgroups">
35
- <span class="icon">馃寪</span> 鎵€鏈夌兢缁?
35
+ <span class="icon">🌐</span> 所有群组
36
36
  </button>
37
37
  <button class="nav-item" data-view="tasks">
38
- <span class="icon">馃搵</span> 鎴戠殑浠诲姟
38
+ <span class="icon">📋</span> 我的任务
39
39
  </button>
40
40
  <button class="nav-item" data-view="documents">
41
- <span class="icon">馃搫</span> 鍏变韩鏂囨。
41
+ <span class="icon">📄</span> 共享文档
42
42
  </button>
43
43
  <button class="nav-item" data-view="files">
44
- <span class="icon">馃搸</span> 鏂囦欢鍏变韩
44
+ <span class="icon">📎</span> 文件共享
45
45
  </button>
46
46
  <button class="nav-item" data-view="chat">
47
- <span class="icon">馃挰</span> 缇よ亰
47
+ <span class="icon">💬</span> 群聊
48
48
  </button>
49
49
  <button class="nav-item" data-view="search">
50
- <span class="icon">馃攳</span> 鎼滅储
50
+ <span class="icon">🔍</span> 搜索
51
51
  </button>
52
52
  </nav>
53
53
 
54
- <button class="btn-logout" id="logoutBtn">閫€鍑虹櫥褰?/button>
54
+ <button class="btn-logout" id="logoutBtn">退出登录</button>
55
55
  </aside>
56
56
 
57
57
  <main class="main-content">
@@ -60,7 +60,7 @@ export function renderUserDashboard(user, wsService) {
60
60
  </div>
61
61
  `;
62
62
 
63
- // 瀵艰埅鍒囨崲
63
+ // 导航切换
64
64
  document.querySelectorAll('.nav-item').forEach(item => {
65
65
  item.addEventListener('click', () => {
66
66
  document.querySelectorAll('.nav-item').forEach(i => i.classList.remove('active'));
@@ -70,7 +70,7 @@ export function renderUserDashboard(user, wsService) {
70
70
  });
71
71
  });
72
72
 
73
- // 閫€鍑虹櫥褰?
73
+ // 退出登录
74
74
  document.getElementById('logoutBtn').addEventListener('click', () => {
75
75
  authService.logout();
76
76
  });
@@ -109,14 +109,14 @@ export function renderUserDashboard(user, wsService) {
109
109
 
110
110
  container.innerHTML = `
111
111
  <div class="view-header">
112
- <h2>鎴戠殑缇ょ粍</h2>
112
+ <h2>我的群组</h2>
113
113
  </div>
114
114
  <div class="groups-grid" id="groupsList"></div>
115
115
  `;
116
116
 
117
117
  const groupsList = document.getElementById('groupsList');
118
118
  if (groups.length === 0) {
119
- groupsList.innerHTML = '<div class="empty-state">鎮ㄨ繕娌℃湁鍔犲叆浠讳綍缇ょ粍<br>璇峰墠寰€"鎵€鏈夌兢缁?鏌ョ湅骞跺姞鍏?/div>';
119
+ groupsList.innerHTML = '<div class="empty-state">您还没有加入任何群组<br>请前往"所有群组"查看并加入</div>';
120
120
  return;
121
121
  }
122
122
 
@@ -125,15 +125,15 @@ export function renderUserDashboard(user, wsService) {
125
125
  groupCard.className = 'group-card';
126
126
  groupCard.innerHTML = `
127
127
  <h3>${group.name}</h3>
128
- <p>${group.description || '鏆傛棤鎻忚堪'}</p>
128
+ <p>${group.description || '暂无描述'}</p>
129
129
  <div class="group-stats">
130
- <span>馃懃 ${group.members.length} 鎴愬憳</span>
131
- <span>馃搫 ${group.documents.length} 鏂囨。</span>
132
- <span>馃搵 ${group.tasks.length} 浠诲姟</span>
130
+ <span>👥 ${group.members.length} 成员</span>
131
+ <span>📄 ${group.documents.length} 文档</span>
132
+ <span>📋 ${group.tasks.length} 任务</span>
133
133
  </div>
134
134
  <div style="display: flex; gap: 10px; margin-top: 10px;">
135
- <button class="btn-select" data-id="${group._id}">杩涘叆缇ょ粍</button>
136
- <button class="btn-secondary" data-id="${group._id}" data-action="leave">閫€鍑虹兢缁?/button>
135
+ <button class="btn-select" data-id="${group._id}">进入群组</button>
136
+ <button class="btn-secondary" data-id="${group._id}" data-action="leave">退出群组</button>
137
137
  </div>
138
138
  `;
139
139
  groupsList.appendChild(groupCard);
@@ -143,19 +143,19 @@ export function renderUserDashboard(user, wsService) {
143
143
  btn.addEventListener('click', () => {
144
144
  currentGroup = groups.find(g => g._id === btn.dataset.id);
145
145
  wsService.joinGroup(currentGroup._id);
146
- alert(`宸茶繘鍏ョ兢缁? ${currentGroup.name}`);
146
+ alert(`已进入群组: ${currentGroup.name}`);
147
147
  });
148
148
  });
149
149
 
150
150
  document.querySelectorAll('[data-action="leave"]').forEach(btn => {
151
151
  btn.addEventListener('click', async () => {
152
- if (confirm('纭畾瑕侀€€鍑鸿缇ょ粍鍚楋紵')) {
152
+ if (confirm('确定要退出该群组吗?')) {
153
153
  try {
154
154
  await apiService.leaveGroup(btn.dataset.id);
155
- alert('宸查€€鍑虹兢缁?);
155
+ alert('已退出群组');
156
156
  await renderGroupsView(container);
157
157
  } catch (error) {
158
- alert('閫€鍑哄け璐? ' + error.message);
158
+ alert('退出失败: ' + error.message);
159
159
  }
160
160
  }
161
161
  });
@@ -169,7 +169,7 @@ export function renderUserDashboard(user, wsService) {
169
169
 
170
170
  container.innerHTML = `
171
171
  <div class="view-header">
172
- <h2>鎵€鏈夌兢缁?/h2>
172
+ <h2>所有群组</h2>
173
173
  </div>
174
174
  <div class="groups-grid" id="allGroupsList"></div>
175
175
  `;
@@ -181,14 +181,14 @@ export function renderUserDashboard(user, wsService) {
181
181
  groupCard.className = 'group-card';
182
182
  groupCard.innerHTML = `
183
183
  <h3>${group.name}</h3>
184
- <p>${group.description || '鏆傛棤鎻忚堪'}</p>
184
+ <p>${group.description || '暂无描述'}</p>
185
185
  <div class="group-stats">
186
- <span>馃懃 ${group.members.length} 鎴愬憳</span>
187
- <span>馃搫 ${group.documents.length} 鏂囨。</span>
186
+ <span>👥 ${group.members.length} 成员</span>
187
+ <span>📄 ${group.documents.length} 文档</span>
188
188
  </div>
189
189
  ${isJoined ?
190
- '<div style="color: var(--success); margin-top: 10px;">鉁?宸插姞鍏?/div>' :
191
- `<button class="btn-primary" data-id="${group._id}" data-action="join">鍔犲叆缇ょ粍</button>`
190
+ '<div style="color: var(--success); margin-top: 10px;">✓ 已加入</div>' :
191
+ `<button class="btn-primary" data-id="${group._id}" data-action="join">加入群组</button>`
192
192
  }
193
193
  `;
194
194
  allGroupsList.appendChild(groupCard);
@@ -198,10 +198,10 @@ export function renderUserDashboard(user, wsService) {
198
198
  btn.addEventListener('click', async () => {
199
199
  try {
200
200
  await apiService.joinGroup(btn.dataset.id);
201
- alert('鍔犲叆鎴愬姛锛?);
201
+ alert('加入成功!');
202
202
  await renderAllGroupsView(container);
203
203
  } catch (error) {
204
- alert('鍔犲叆澶辫触: ' + error.message);
204
+ alert('加入失败: ' + error.message);
205
205
  }
206
206
  });
207
207
  });
@@ -213,7 +213,7 @@ export function renderUserDashboard(user, wsService) {
213
213
 
214
214
  container.innerHTML = `
215
215
  <div class="view-header">
216
- <h2>鎴戠殑浠诲姟</h2>
216
+ <h2>我的任务</h2>
217
217
  </div>
218
218
  <div class="tasks-list" id="tasksList"></div>
219
219
  `;
@@ -221,7 +221,7 @@ export function renderUserDashboard(user, wsService) {
221
221
  const tasksList = document.getElementById('tasksList');
222
222
 
223
223
  if (result.tasks.length === 0) {
224
- tasksList.innerHTML = '<div class="empty-state">鏆傛棤浠诲姟</div>';
224
+ tasksList.innerHTML = '<div class="empty-state">暂无任务</div>';
225
225
  return;
226
226
  }
227
227
 
@@ -233,19 +233,19 @@ export function renderUserDashboard(user, wsService) {
233
233
  <p>${task.description}</p>
234
234
  <div class="task-meta">
235
235
  <span class="status-badge">${getStatusText(task.status)}</span>
236
- <span>缇ょ粍: ${task.group.name}</span>
237
- ${task.deadline ? `<span>鎴: ${new Date(task.deadline).toLocaleDateString()}</span>` : ''}
236
+ <span>群组: ${task.group.name}</span>
237
+ ${task.deadline ? `<span>截止: ${new Date(task.deadline).toLocaleDateString()}</span>` : ''}
238
238
  </div>
239
- ${task.relatedDocument ? `<a href="#" class="doc-link" data-id="${task.relatedDocument._id}">馃搫 鏌ョ湅鐩稿叧鏂囨。</a>` : ''}
239
+ ${task.relatedDocument ? `<a href="#" class="doc-link" data-id="${task.relatedDocument._id}">📄 查看相关文档</a>` : ''}
240
240
  <div class="task-actions">
241
- ${task.status === 'pending' ? `<button class="btn-primary btn-sm" data-id="${task._id}" data-action="start">寮€濮嬩换鍔?/button>` : ''}
242
- ${task.status === 'in_progress' ? `<button class="btn-success btn-sm" data-id="${task._id}" data-action="complete">瀹屾垚浠诲姟</button>` : ''}
241
+ ${task.status === 'pending' ? `<button class="btn-primary btn-sm" data-id="${task._id}" data-action="start">开始任务</button>` : ''}
242
+ ${task.status === 'in_progress' ? `<button class="btn-success btn-sm" data-id="${task._id}" data-action="complete">完成任务</button>` : ''}
243
243
  </div>
244
244
  `;
245
245
  tasksList.appendChild(taskCard);
246
246
  });
247
247
 
248
- // 浠诲姟鎿嶄綔
248
+ // 任务操作
249
249
  document.querySelectorAll('[data-action]').forEach(btn => {
250
250
  btn.addEventListener('click', async () => {
251
251
  const taskId = btn.dataset.id;
@@ -256,24 +256,24 @@ export function renderUserDashboard(user, wsService) {
256
256
  await apiService.updateTaskStatus(taskId, status);
257
257
  await renderTasksView(container);
258
258
  } catch (error) {
259
- alert('鎿嶄綔澶辫触: ' + error.message);
259
+ alert('操作失败: ' + error.message);
260
260
  }
261
261
  });
262
262
  });
263
263
  } catch (error) {
264
- console.error('鑾峰彇浠诲姟澶辫触:', error);
264
+ console.error('获取任务失败:', error);
265
265
  container.innerHTML = `
266
266
  <div class="view-header">
267
- <h2>鎴戠殑浠诲姟</h2>
267
+ <h2>我的任务</h2>
268
268
  </div>
269
- <div class="empty-state">鍔犺浇浠诲姟澶辫触: ${error.message}</div>
269
+ <div class="empty-state">加载任务失败: ${error.message}</div>
270
270
  `;
271
271
  }
272
272
  }
273
273
 
274
274
  async function renderDocumentsView(container) {
275
275
  if (!currentGroup) {
276
- container.innerHTML = '<div class="empty-state">璇峰厛閫夋嫨涓€涓兢缁?/div>';
276
+ container.innerHTML = '<div class="empty-state">请先选择一个群组</div>';
277
277
  return;
278
278
  }
279
279
 
@@ -281,7 +281,7 @@ export function renderUserDashboard(user, wsService) {
281
281
 
282
282
  container.innerHTML = `
283
283
  <div class="view-header">
284
- <h2>鍏变韩鏂囨。 - ${currentGroup.name}</h2>
284
+ <h2>共享文档 - ${currentGroup.name}</h2>
285
285
  </div>
286
286
  <div class="documents-list" id="docsList"></div>
287
287
  `;
@@ -289,7 +289,7 @@ export function renderUserDashboard(user, wsService) {
289
289
  const docsList = document.getElementById('docsList');
290
290
 
291
291
  if (result.documents.length === 0) {
292
- docsList.innerHTML = '<div class="empty-state">鏆傛棤鏂囨。</div>';
292
+ docsList.innerHTML = '<div class="empty-state">暂无文档</div>';
293
293
  return;
294
294
  }
295
295
 
@@ -297,14 +297,14 @@ export function renderUserDashboard(user, wsService) {
297
297
  const docCard = document.createElement('div');
298
298
  docCard.className = 'document-card';
299
299
  docCard.innerHTML = `
300
- <h3>馃搫 ${doc.title}</h3>
300
+ <h3>📄 ${doc.title}</h3>
301
301
  <div class="doc-meta">
302
- <span>鍒涘缓鑰? ${doc.creator.username}</span>
303
- <span>${doc.permission === 'readonly' ? '馃敀 鍙' : '鉁忥笍 鍙紪杈?}</span>
304
- <span>鏇存柊: ${new Date(doc.updatedAt).toLocaleString()}</span>
302
+ <span>创建者: ${doc.creator.username}</span>
303
+ <span>${doc.permission === 'readonly' ? '🔒 只读' : '✏️ 可编辑'}</span>
304
+ <span>更新: ${new Date(doc.updatedAt).toLocaleString()}</span>
305
305
  </div>
306
306
  <button class="btn-edit" data-id="${doc._id}">
307
- ${doc.permission === 'readonly' ? '鏌ョ湅' : '缂栬緫'}
307
+ ${doc.permission === 'readonly' ? '查看' : '编辑'}
308
308
  </button>
309
309
  `;
310
310
  docsList.appendChild(docCard);
@@ -323,25 +323,25 @@ export function renderUserDashboard(user, wsService) {
323
323
 
324
324
  container.innerHTML = `
325
325
  <div class="view-header">
326
- <button class="btn-back" id="backBtn">鈫?杩斿洖</button>
326
+ <button class="btn-back" id="backBtn">← 返回</button>
327
327
  <h2>${doc.title}</h2>
328
- <span class="doc-status">${doc.permission === 'readonly' ? '馃敀 鍙妯″紡' : '鉁忥笍 缂栬緫妯″紡'}</span>
328
+ <span class="doc-status">${doc.permission === 'readonly' ? '🔒 只读模式' : '✏️ 编辑模式'}</span>
329
329
  </div>
330
330
  <div class="editor-container">
331
331
  <div class="editor-toolbar">
332
332
  <div class="online-users" id="onlineUsers">
333
- <span class="user-badge">馃懁 ${user.username}</span>
333
+ <span class="user-badge">👤 ${user.username}</span>
334
334
  </div>
335
- ${doc.permission === 'editable' ? '<button class="btn-primary" id="saveBtn">淇濆瓨</button>' : ''}
335
+ ${doc.permission === 'editable' ? '<button class="btn-primary" id="saveBtn">保存</button>' : ''}
336
336
  </div>
337
337
  <div id="editor" ${doc.permission === 'readonly' ? 'class="readonly"' : ''}></div>
338
338
  <div class="editor-footer">
339
- <span>鏈€鍚庣紪杈? ${new Date(doc.updatedAt).toLocaleString()}</span>
339
+ <span>最后编辑: ${new Date(doc.updatedAt).toLocaleString()}</span>
340
340
  </div>
341
341
  </div>
342
342
  `;
343
343
 
344
- // 鍒濆鍖?Quill 缂栬緫鍣?
344
+ // 初始化 Quill 编辑器
345
345
  const quill = new Quill('#editor', {
346
346
  theme: 'snow',
347
347
  modules: {
@@ -357,15 +357,15 @@ export function renderUserDashboard(user, wsService) {
357
357
  readOnly: doc.permission === 'readonly'
358
358
  });
359
359
 
360
- // 璁剧疆鍒濆鍐呭
361
- editor.value = doc.content || '';
360
+ // 设置初始内容
361
+ quill.root.innerHTML = doc.content || '';
362
362
 
363
- // 瀹炴椂鍚屾
363
+ // 实时同步
364
364
  if (doc.permission === 'editable') {
365
365
  let typingTimeout;
366
366
  let saveTimeout;
367
367
 
368
- editor.addEventListener('input', () => {
368
+ quill.on('text-change', () => {
369
369
  clearTimeout(typingTimeout);
370
370
  clearTimeout(saveTimeout);
371
371
  wsService.sendTyping(documentId, user.username, true);
@@ -374,45 +374,45 @@ export function renderUserDashboard(user, wsService) {
374
374
  wsService.sendTyping(documentId, user.username, false);
375
375
  }, 1000);
376
376
 
377
- // 鑷姩淇濆瓨
377
+ // 自动保存
378
378
  saveTimeout = setTimeout(async () => {
379
- const content = editor.value;
379
+ const content = quill.root.innerHTML;
380
380
  try {
381
381
  await apiService.updateDocument(documentId, content);
382
382
  } catch (error) {
383
- console.error('鑷姩淇濆瓨澶辫触:', error);
383
+ console.error('自动保存失败:', error);
384
384
  }
385
385
  }, 2000);
386
386
  });
387
387
 
388
388
  document.getElementById('saveBtn').addEventListener('click', async () => {
389
389
  try {
390
- const content = editor.value;
390
+ const content = quill.root.innerHTML;
391
391
  await apiService.updateDocument(documentId, content);
392
- alert('淇濆瓨鎴愬姛锛?);
392
+ alert('保存成功!');
393
393
  } catch (error) {
394
- alert('淇濆瓨澶辫触: ' + error.message);
394
+ alert('保存失败: ' + error.message);
395
395
  }
396
396
  });
397
397
  }
398
398
 
399
- // 鐩戝惉鏂囨。鏇存柊
399
+ // 监听文档更新
400
400
  wsService.on('document_update', (data) => {
401
401
  if (data.documentId === documentId && data.userId !== user.id) {
402
402
  const selection = quill.getSelection();
403
- editor.value = data.content;
403
+ quill.root.innerHTML = data.content;
404
404
  if (selection) {
405
405
  quill.setSelection(selection);
406
406
  }
407
407
  }
408
408
  });
409
409
 
410
- // 鐩戝惉鎵撳瓧鐘舵€?
410
+ // 监听打字状态
411
411
  wsService.on('typing', (data) => {
412
412
  if (data.documentId === documentId && data.userId !== user.id) {
413
413
  const onlineUsers = document.getElementById('onlineUsers');
414
414
  if (data.isTyping) {
415
- onlineUsers.innerHTML += `<span class="user-badge typing" data-user="${data.userId}">鉁忥笍 ${data.username}</span>`;
415
+ onlineUsers.innerHTML += `<span class="user-badge typing" data-user="${data.userId}">✏️ ${data.username}</span>`;
416
416
  } else {
417
417
  const badge = onlineUsers.querySelector(`[data-user="${data.userId}"]`);
418
418
  if (badge) badge.remove();
@@ -427,7 +427,7 @@ export function renderUserDashboard(user, wsService) {
427
427
 
428
428
  async function renderFilesView(container) {
429
429
  if (!currentGroup) {
430
- container.innerHTML = '<div class="empty-state">璇峰厛閫夋嫨涓€涓兢缁?/div>';
430
+ container.innerHTML = '<div class="empty-state">请先选择一个群组</div>';
431
431
  return;
432
432
  }
433
433
 
@@ -436,31 +436,31 @@ export function renderUserDashboard(user, wsService) {
436
436
 
437
437
  container.innerHTML = `
438
438
  <div class="view-header">
439
- <h2>鏂囦欢鍏变韩 - ${currentGroup.name}</h2>
440
- <button class="btn-primary" id="uploadFileBtn">馃摛 涓婁紶鏂囦欢</button>
439
+ <h2>文件共享 - ${currentGroup.name}</h2>
440
+ <button class="btn-primary" id="uploadFileBtn">📤 上传文件</button>
441
441
  </div>
442
442
  <div class="files-list" id="filesList"></div>
443
443
 
444
- <!-- 鏂囦欢涓婁紶妯℃€佹 -->
444
+ <!-- 文件上传模态框 -->
445
445
  <div class="modal hidden" id="uploadFileModal">
446
446
  <div class="modal-content">
447
447
  <div class="modal-header">
448
- <h3>涓婁紶鏂囦欢</h3>
448
+ <h3>上传文件</h3>
449
449
  <button class="modal-close" id="closeUploadModal">&times;</button>
450
450
  </div>
451
451
  <form id="uploadFileForm">
452
452
  <div class="form-group">
453
- <label>閫夋嫨鏂囦欢</label>
453
+ <label>选择文件</label>
454
454
  <input type="file" id="fileInput" required>
455
- <small>鏀寔鍥剧墖銆丳DF銆乄ord銆丒xcel绛夛紝鏈€澶?0MB</small>
455
+ <small>支持图片、PDF、Word、Excel等,最大10MB</small>
456
456
  </div>
457
457
  <div class="form-group">
458
- <label>鎻忚堪锛堝彲閫夛級</label>
459
- <textarea id="fileDescription" rows="3" placeholder="鏂囦欢鎻忚堪..."></textarea>
458
+ <label>描述(可选)</label>
459
+ <textarea id="fileDescription" rows="3" placeholder="文件描述..."></textarea>
460
460
  </div>
461
461
  <div class="form-actions">
462
- <button type="button" class="btn-secondary" id="cancelUpload">鍙栨秷</button>
463
- <button type="submit" class="btn-primary">涓婁紶</button>
462
+ <button type="button" class="btn-secondary" id="cancelUpload">取消</button>
463
+ <button type="submit" class="btn-primary">上传</button>
464
464
  </div>
465
465
  </form>
466
466
  </div>
@@ -470,7 +470,7 @@ export function renderUserDashboard(user, wsService) {
470
470
  const filesList = document.getElementById('filesList');
471
471
 
472
472
  if (!result.files || result.files.length === 0) {
473
- filesList.innerHTML = '<div class="empty-state">鏆傛棤鏂囦欢</div>';
473
+ filesList.innerHTML = '<div class="empty-state">暂无文件</div>';
474
474
  } else {
475
475
  result.files.forEach(file => {
476
476
  const fileCard = document.createElement('div');
@@ -484,37 +484,37 @@ export function renderUserDashboard(user, wsService) {
484
484
  <div class="file-info">
485
485
  <h4>${file.originalName}</h4>
486
486
  <div class="file-meta">
487
- <span>涓婁紶鑰? ${file.uploader.username}</span>
488
- <span>澶у皬: ${fileSize}</span>
489
- <span>鏃堕棿: ${new Date(file.createdAt).toLocaleString()}</span>
487
+ <span>上传者: ${file.uploader.username}</span>
488
+ <span>大小: ${fileSize}</span>
489
+ <span>时间: ${new Date(file.createdAt).toLocaleString()}</span>
490
490
  </div>
491
491
  ${file.description ? `<p class="file-description">${file.description}</p>` : ''}
492
492
  </div>
493
493
  <div class="file-actions">
494
- <a href="${apiService.getFileDownloadUrl(file._id)}" class="btn-primary" download>涓嬭浇</a>
495
- ${file.uploader._id === currentUserId ? `<button class="btn-danger" data-id="${file._id}" data-action="delete-file">鍒犻櫎</button>` : ''}
494
+ <a href="${apiService.getFileDownloadUrl(file._id)}" class="btn-primary" download>下载</a>
495
+ ${file.uploader._id === currentUserId ? `<button class="btn-danger" data-id="${file._id}" data-action="delete-file">删除</button>` : ''}
496
496
  </div>
497
497
  `;
498
498
  filesList.appendChild(fileCard);
499
499
  });
500
500
 
501
- // 鍒犻櫎鏂囦欢浜嬩欢
501
+ // 删除文件事件
502
502
  document.querySelectorAll('[data-action="delete-file"]').forEach(btn => {
503
503
  btn.addEventListener('click', async () => {
504
- if (confirm('纭畾瑕佸垹闄よ繖涓枃浠跺悧锛?)) {
504
+ if (confirm('确定要删除这个文件吗?')) {
505
505
  try {
506
506
  await apiService.deleteFile(btn.dataset.id);
507
- alert('鏂囦欢鍒犻櫎鎴愬姛锛?);
507
+ alert('文件删除成功!');
508
508
  await renderFilesView(container);
509
509
  } catch (error) {
510
- alert('鍒犻櫎澶辫触: ' + error.message);
510
+ alert('删除失败: ' + error.message);
511
511
  }
512
512
  }
513
513
  });
514
514
  });
515
515
  }
516
516
 
517
- // 鏂囦欢涓婁紶鍔熻兘
517
+ // 文件上传功能
518
518
  document.getElementById('uploadFileBtn').addEventListener('click', () => {
519
519
  document.getElementById('uploadFileModal').classList.remove('hidden');
520
520
  });
@@ -535,38 +535,38 @@ export function renderUserDashboard(user, wsService) {
535
535
  const description = document.getElementById('fileDescription').value;
536
536
 
537
537
  if (!fileInput.files[0]) {
538
- alert('璇烽€夋嫨鏂囦欢');
538
+ alert('请选择文件');
539
539
  return;
540
540
  }
541
541
 
542
542
  try {
543
543
  await apiService.uploadFile(currentGroup._id, fileInput.files[0], description);
544
- alert('鏂囦欢涓婁紶鎴愬姛锛?);
544
+ alert('文件上传成功!');
545
545
  document.getElementById('uploadFileModal').classList.add('hidden');
546
546
  document.getElementById('uploadFileForm').reset();
547
547
  await renderFilesView(container);
548
548
  } catch (error) {
549
- alert('涓婁紶澶辫触: ' + error.message);
549
+ alert('上传失败: ' + error.message);
550
550
  }
551
551
  });
552
552
  } catch (error) {
553
- console.error('鑾峰彇鏂囦欢鍒楄〃澶辫触:', error);
553
+ console.error('获取文件列表失败:', error);
554
554
  container.innerHTML = `
555
555
  <div class="view-header">
556
- <h2>鏂囦欢鍏变韩</h2>
556
+ <h2>文件共享</h2>
557
557
  </div>
558
- <div class="empty-state">鍔犺浇鏂囦欢澶辫触: ${error.message}</div>
558
+ <div class="empty-state">加载文件失败: ${error.message}</div>
559
559
  `;
560
560
  }
561
561
  }
562
562
 
563
563
  function getFileIcon(mimetype) {
564
- if (mimetype.startsWith('image/')) return '馃柤锔?;
565
- if (mimetype === 'application/pdf') return '馃摃';
566
- if (mimetype.includes('word') || mimetype.includes('document')) return '馃摌';
567
- if (mimetype.includes('excel') || mimetype.includes('spreadsheet')) return '馃摋';
568
- if (mimetype.includes('zip') || mimetype.includes('compressed')) return '馃摝';
569
- return '馃搫';
564
+ if (mimetype.startsWith('image/')) return '🖼️';
565
+ if (mimetype === 'application/pdf') return '📕';
566
+ if (mimetype.includes('word') || mimetype.includes('document')) return '📘';
567
+ if (mimetype.includes('excel') || mimetype.includes('spreadsheet')) return '📗';
568
+ if (mimetype.includes('zip') || mimetype.includes('compressed')) return '📦';
569
+ return '📄';
570
570
  }
571
571
 
572
572
  function formatFileSize(bytes) {
@@ -579,7 +579,7 @@ export function renderUserDashboard(user, wsService) {
579
579
 
580
580
  async function renderChatView(container) {
581
581
  if (!currentGroup) {
582
- container.innerHTML = '<div class="empty-state">璇峰厛閫夋嫨涓€涓兢缁?/div>';
582
+ container.innerHTML = '<div class="empty-state">请先选择一个群组</div>';
583
583
  return;
584
584
  }
585
585
 
@@ -591,14 +591,14 @@ export function renderUserDashboard(user, wsService) {
591
591
 
592
592
  container.innerHTML = `
593
593
  <div class="view-header">
594
- <h2>缇よ亰 - ${currentGroup.name}</h2>
594
+ <h2>群聊 - ${currentGroup.name}</h2>
595
595
  </div>
596
596
  <div class="chat-container">
597
597
  <div class="messages" id="messages"></div>
598
598
  <div class="chat-input">
599
- <button class="btn-emoji" id="emojiBtn" ${canSpeak ? '' : 'disabled'}>馃槉</button>
600
- <input type="text" id="messageInput" placeholder="${canSpeak ? '杈撳叆娑堟伅...' : (isMutedAll ? '鍏ㄤ綋绂佽█涓紝鏃犳硶鍙戣█' : '浣犲凡琚瑷€')}" ${canSpeak ? '' : 'disabled'}>
601
- <button class="btn-primary" id="sendBtn" ${canSpeak ? '' : 'disabled'}>鍙戦€?/button>
599
+ <button class="btn-emoji" id="emojiBtn" ${canSpeak ? '' : 'disabled'}>😊</button>
600
+ <input type="text" id="messageInput" placeholder="${canSpeak ? '输入消息...' : (isMutedAll ? '全体禁言中,无法发言' : '你已被禁言')}" ${canSpeak ? '' : 'disabled'}>
601
+ <button class="btn-primary" id="sendBtn" ${canSpeak ? '' : 'disabled'}>发送</button>
602
602
  </div>
603
603
  <emoji-picker id="emojiPicker" class="hidden"></emoji-picker>
604
604
  </div>
@@ -608,7 +608,7 @@ export function renderUserDashboard(user, wsService) {
608
608
  const messageInput = document.getElementById('messageInput');
609
609
  const sendBtn = document.getElementById('sendBtn');
610
610
 
611
- // 鍔犺浇鍘嗗彶娑堟伅
611
+ // 加载历史消息
612
612
  try {
613
613
  const messagesResult = await apiService.getGroupMessages(currentGroup._id);
614
614
  if (messagesResult.messages) {
@@ -627,10 +627,10 @@ export function renderUserDashboard(user, wsService) {
627
627
  messagesDiv.scrollTop = messagesDiv.scrollHeight;
628
628
  }
629
629
  } catch (err) {
630
- console.error('鍔犺浇鍘嗗彶娑堟伅澶辫触:', err);
630
+ console.error('加载历史消息失败:', err);
631
631
  }
632
632
 
633
- // 琛ㄦ儏鍖呭姛鑳?
633
+ // 表情包功能
634
634
  const emojiBtn = document.getElementById('emojiBtn');
635
635
  const emojiPicker = document.getElementById('emojiPicker');
636
636
 
@@ -644,15 +644,15 @@ export function renderUserDashboard(user, wsService) {
644
644
  emojiPicker.classList.add('hidden');
645
645
  });
646
646
 
647
- // 鐐瑰嚮澶栭儴鍏抽棴琛ㄦ儏閫夋嫨鍣?
647
+ // 点击外部关闭表情选择器
648
648
  document.addEventListener('click', (e) => {
649
649
  if (!emojiBtn.contains(e.target) && !emojiPicker.contains(e.target)) {
650
650
  emojiPicker.classList.add('hidden');
651
651
  }
652
652
  });
653
653
 
654
- // 娑堟伅閫氱煡绯荤粺
655
- function showNotification(title, body, icon = '馃挰') {
654
+ // 消息通知系统
655
+ function showNotification(title, body, icon = '💬') {
656
656
  if ('Notification' in window && Notification.permission === 'granted') {
657
657
  new Notification(title, {
658
658
  body: body,
@@ -663,12 +663,12 @@ export function renderUserDashboard(user, wsService) {
663
663
  }
664
664
  }
665
665
 
666
- // 璇锋眰閫氱煡鏉冮檺
666
+ // 请求通知权限
667
667
  if ('Notification' in window && Notification.permission === 'default') {
668
668
  Notification.requestPermission();
669
669
  }
670
670
 
671
- // 鐩戝惉娑堟伅
671
+ // 监听消息
672
672
  wsService.on('chat_message', (data) => {
673
673
  if (data.groupId === currentGroup._id) {
674
674
  const messageEl = document.createElement('div');
@@ -683,35 +683,35 @@ export function renderUserDashboard(user, wsService) {
683
683
  messagesDiv.appendChild(messageEl);
684
684
  messagesDiv.scrollTop = messagesDiv.scrollHeight;
685
685
 
686
- // 鏄剧ず閫氱煡锛堝鏋滀笉鏄嚜宸卞彂閫佺殑娑堟伅锛?
686
+ // 显示通知(如果不是自己发送的消息)
687
687
  if (data.userId !== currentUserId) {
688
- showNotification(`${data.username} 鍦?${currentGroup.name}`, data.content);
688
+ showNotification(`${data.username} 在 ${currentGroup.name}`, data.content);
689
689
  }
690
690
  }
691
691
  });
692
692
 
693
- // 鏈嶅姟绔嫤鎴彁绀猴紙姣斿琚瑷€/鏈姞鍏ョ兢缁勶級
693
+ // 服务端拦截提示(比如被禁言/未加入群组)
694
694
  wsService.on('chat_blocked', (data) => {
695
695
  if (data.groupId === currentGroup._id) {
696
696
  const notificationEl = document.createElement('div');
697
697
  notificationEl.className = 'notification';
698
- notificationEl.textContent = data.message || '娑堟伅鍙戦€佸け璐?;
698
+ notificationEl.textContent = data.message || '消息发送失败';
699
699
  messagesDiv.appendChild(notificationEl);
700
700
  messagesDiv.scrollTop = messagesDiv.scrollHeight;
701
701
  }
702
702
  });
703
703
 
704
- // 鐩戝惉鐐瑰悕閫氱煡
704
+ // 监听点名通知
705
705
  wsService.on('call_response', (data) => {
706
706
  if (data.groupId === currentGroup._id) {
707
707
  const notificationEl = document.createElement('div');
708
708
  notificationEl.className = 'notification';
709
- notificationEl.textContent = `${data.username} 宸插搷搴旂偣鍚峘;
709
+ notificationEl.textContent = `${data.username} 已响应点名`;
710
710
  messagesDiv.appendChild(notificationEl);
711
711
  }
712
712
  });
713
713
 
714
- // 鍙戦€佹秷鎭?
714
+ // 发送消息
715
715
  const sendMessage = () => {
716
716
  const content = messageInput.value.trim();
717
717
  if (content) {
@@ -729,22 +729,22 @@ export function renderUserDashboard(user, wsService) {
729
729
  async function renderSearchView(container) {
730
730
  container.innerHTML = `
731
731
  <div class="view-header">
732
- <h2>馃攳 鎼滅储</h2>
732
+ <h2>🔍 搜索</h2>
733
733
  </div>
734
734
  <div class="search-container">
735
735
  <div class="search-box">
736
- <input type="text" id="searchInput" placeholder="鎼滅储娑堟伅銆佹枃妗c€佷换鍔?..">
737
- <button class="btn-primary" id="searchBtn">鎼滅储</button>
736
+ <input type="text" id="searchInput" placeholder="搜索消息、文档、任务...">
737
+ <button class="btn-primary" id="searchBtn">搜索</button>
738
738
  </div>
739
739
  <div class="search-filters">
740
740
  <label>
741
- <input type="checkbox" id="filterMessages" checked> 娑堟伅
741
+ <input type="checkbox" id="filterMessages" checked> 消息
742
742
  </label>
743
743
  <label>
744
- <input type="checkbox" id="filterDocuments" checked> 鏂囨。
744
+ <input type="checkbox" id="filterDocuments" checked> 文档
745
745
  </label>
746
746
  <label>
747
- <input type="checkbox" id="filterTasks" checked> 浠诲姟
747
+ <input type="checkbox" id="filterTasks" checked> 任务
748
748
  </label>
749
749
  </div>
750
750
  <div class="search-results" id="searchResults"></div>
@@ -758,7 +758,7 @@ export function renderUserDashboard(user, wsService) {
758
758
  const performSearch = async () => {
759
759
  const query = searchInput.value.trim();
760
760
  if (!query) {
761
- searchResults.innerHTML = '<div class="empty-state">璇疯緭鍏ユ悳绱㈠叧閿瘝</div>';
761
+ searchResults.innerHTML = '<div class="empty-state">请输入搜索关键词</div>';
762
762
  return;
763
763
  }
764
764
 
@@ -768,12 +768,12 @@ export function renderUserDashboard(user, wsService) {
768
768
  tasks: document.getElementById('filterTasks').checked
769
769
  };
770
770
 
771
- searchResults.innerHTML = '<div class="loading">鎼滅储涓?..</div>';
771
+ searchResults.innerHTML = '<div class="loading">搜索中...</div>';
772
772
 
773
773
  try {
774
774
  const results = [];
775
775
 
776
- // 鎼滅储娑堟伅
776
+ // 搜索消息
777
777
  if (filters.messages && currentGroup) {
778
778
  try {
779
779
  const messagesResult = await apiService.getGroupMessages(currentGroup._id);
@@ -784,7 +784,7 @@ export function renderUserDashboard(user, wsService) {
784
784
  matchedMessages.forEach(msg => {
785
785
  results.push({
786
786
  type: 'message',
787
- title: `娑堟伅 - ${msg.username}`,
787
+ title: `消息 - ${msg.username}`,
788
788
  content: msg.content,
789
789
  time: msg.timestamp,
790
790
  group: currentGroup.name
@@ -792,11 +792,11 @@ export function renderUserDashboard(user, wsService) {
792
792
  });
793
793
  }
794
794
  } catch (err) {
795
- console.error('鎼滅储娑堟伅澶辫触:', err);
795
+ console.error('搜索消息失败:', err);
796
796
  }
797
797
  }
798
798
 
799
- // 鎼滅储鏂囨。
799
+ // 搜索文档
800
800
  if (filters.documents) {
801
801
  try {
802
802
  if (currentGroup) {
@@ -819,11 +819,11 @@ export function renderUserDashboard(user, wsService) {
819
819
  }
820
820
  }
821
821
  } catch (err) {
822
- console.error('鎼滅储鏂囨。澶辫触:', err);
822
+ console.error('搜索文档失败:', err);
823
823
  }
824
824
  }
825
825
 
826
- // 鎼滅储浠诲姟
826
+ // 搜索任务
827
827
  if (filters.tasks) {
828
828
  try {
829
829
  const tasksResult = await apiService.getMyTasks();
@@ -844,36 +844,36 @@ export function renderUserDashboard(user, wsService) {
844
844
  });
845
845
  }
846
846
  } catch (err) {
847
- console.error('鎼滅储浠诲姟澶辫触:', err);
847
+ console.error('搜索任务失败:', err);
848
848
  }
849
849
  }
850
850
 
851
- // 鏄剧ず缁撴灉
851
+ // 显示结果
852
852
  if (results.length === 0) {
853
- searchResults.innerHTML = '<div class="empty-state">鏈壘鍒扮浉鍏崇粨鏋?/div>';
853
+ searchResults.innerHTML = '<div class="empty-state">未找到相关结果</div>';
854
854
  } else {
855
855
  searchResults.innerHTML = results.map(result => {
856
856
  const typeIcon = {
857
- message: '馃挰',
858
- document: '馃搫',
859
- task: '馃搵'
857
+ message: '💬',
858
+ document: '📄',
859
+ task: '📋'
860
860
  };
861
861
  return `
862
862
  <div class="search-result-item">
863
863
  <div class="result-header">
864
- <span class="result-type">${typeIcon[result.type]} ${result.type === 'message' ? '娑堟伅' : result.type === 'document' ? '鏂囨。' : '浠诲姟'}</span>
864
+ <span class="result-type">${typeIcon[result.type]} ${result.type === 'message' ? '消息' : result.type === 'document' ? '文档' : '任务'}</span>
865
865
  <span class="result-time">${new Date(result.time).toLocaleString()}</span>
866
866
  </div>
867
867
  <h4>${highlightText(result.title, query)}</h4>
868
868
  <p>${highlightText(result.content, query)}</p>
869
- ${result.group ? `<span class="result-group">缇ょ粍: ${result.group}</span>` : ''}
870
- ${result.status ? `<span class="result-status">鐘舵€? ${getStatusText(result.status)}</span>` : ''}
869
+ ${result.group ? `<span class="result-group">群组: ${result.group}</span>` : ''}
870
+ ${result.status ? `<span class="result-status">状态: ${getStatusText(result.status)}</span>` : ''}
871
871
  </div>
872
872
  `;
873
873
  }).join('');
874
874
  }
875
875
  } catch (error) {
876
- searchResults.innerHTML = `<div class="empty-state">鎼滅储澶辫触: ${error.message}</div>`;
876
+ searchResults.innerHTML = `<div class="empty-state">搜索失败: ${error.message}</div>`;
877
877
  }
878
878
  };
879
879
 
@@ -891,10 +891,10 @@ export function renderUserDashboard(user, wsService) {
891
891
 
892
892
  function getStatusText(status) {
893
893
  const statusMap = {
894
- 'pending': '寰呭鐞?,
895
- 'in_progress': '杩涜涓?,
896
- 'completed': '宸插畬鎴?,
897
- 'terminated': '宸茬粓姝?
894
+ 'pending': '待处理',
895
+ 'in_progress': '进行中',
896
+ 'completed': '已完成',
897
+ 'terminated': '已终止'
898
898
  };
899
899
  return statusMap[status] || status;
900
900
  }