collabdocchat 2.1.1 → 2.1.2

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