collabdocchat 1.2.13 → 2.0.0

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.
Files changed (62) hide show
  1. package/README.md +219 -218
  2. package/index.html +2 -0
  3. package/install-and-start.bat +5 -0
  4. package/install-and-start.sh +5 -0
  5. package/package.json +9 -2
  6. package/scripts/pre-publish-check.js +213 -0
  7. package/scripts/start-app.js +15 -15
  8. package/server/index.js +38 -6
  9. package/server/middleware/cache.js +115 -0
  10. package/server/middleware/errorHandler.js +209 -0
  11. package/server/models/Document.js +66 -59
  12. package/server/models/File.js +49 -43
  13. package/server/models/Group.js +6 -0
  14. package/server/models/KnowledgeBase.js +254 -0
  15. package/server/models/Message.js +43 -0
  16. package/server/models/Task.js +87 -55
  17. package/server/models/User.js +67 -60
  18. package/server/models/Workflow.js +249 -0
  19. package/server/routes/ai.js +327 -0
  20. package/server/routes/audit.js +245 -210
  21. package/server/routes/backup.js +108 -0
  22. package/server/routes/chunked-upload.js +343 -0
  23. package/server/routes/export.js +440 -0
  24. package/server/routes/files.js +294 -218
  25. package/server/routes/groups.js +182 -0
  26. package/server/routes/knowledge.js +509 -0
  27. package/server/routes/tasks.js +257 -110
  28. package/server/routes/workflows.js +380 -0
  29. package/server/utils/backup.js +439 -0
  30. package/server/utils/cache.js +223 -0
  31. package/server/utils/workflow-engine.js +479 -0
  32. package/server/websocket/enhanced.js +509 -0
  33. package/server/websocket/index.js +233 -1
  34. package/src/components/knowledge-modal.js +485 -0
  35. package/src/components/optimized-poll-detail.js +724 -0
  36. package/src/main.js +5 -0
  37. package/src/pages/admin-dashboard.js +2248 -44
  38. package/src/pages/optimized-backup-view.js +616 -0
  39. package/src/pages/optimized-knowledge-view.js +803 -0
  40. package/src/pages/optimized-task-detail.js +843 -0
  41. package/src/pages/optimized-workflow-view.js +806 -0
  42. package/src/pages/simplified-workflows.js +651 -0
  43. package/src/pages/user-dashboard.js +677 -58
  44. package/src/services/api.js +65 -1
  45. package/src/services/auth.js +1 -1
  46. package/src/services/websocket.js +124 -16
  47. package/src/styles/collaboration-modern.js +708 -0
  48. package/src/styles/enhancements.css +392 -0
  49. package/src/styles/main.css +620 -1420
  50. package/src/styles/responsive.css +1000 -0
  51. package/src/styles/sidebar-fix.css +60 -0
  52. package/src/utils/ai-assistant.js +1398 -0
  53. package/src/utils/chat-enhancements.js +509 -0
  54. package/src/utils/collaboration-enhancer.js +1151 -0
  55. package/src/utils/feature-integrator.js +1724 -0
  56. package/src/utils/onboarding-guide.js +734 -0
  57. package/src/utils/performance.js +394 -0
  58. package/src/utils/permission-manager.js +890 -0
  59. package/src/utils/responsive-handler.js +491 -0
  60. package/src/utils/theme-manager.js +811 -0
  61. package/src/utils/ui-enhancements-loader.js +329 -0
  62. package/USAGE.md +0 -298
@@ -0,0 +1,724 @@
1
+ /**
2
+ * 优化后的投票详情界面
3
+ * 美化投票结果展示,提升用户体验
4
+ */
5
+
6
+ export function renderOptimizedPollDetail(task, assignedMembers) {
7
+ const totalMembers = assignedMembers.length;
8
+ const isPollTask = task.type === 'poll' && task.pollData;
9
+
10
+ if (!isPollTask) {
11
+ return '';
12
+ }
13
+
14
+ // 计算投票统计
15
+ const pollOptions = task.pollData.options || [];
16
+ const totalVotes = pollOptions.reduce((sum, option) => {
17
+ const voteCount = Array.isArray(option.votes) ? option.votes.length : Number(option.votes || 0);
18
+ return sum + voteCount;
19
+ }, 0);
20
+
21
+ const votedMembers = new Set();
22
+ pollOptions.forEach(option => {
23
+ if (Array.isArray(option.votes)) {
24
+ option.votes.forEach(vote => {
25
+ const userId = vote.user?._id || vote.user || vote;
26
+ votedMembers.add(String(userId));
27
+ });
28
+ }
29
+ });
30
+
31
+ const votedCount = votedMembers.size;
32
+ const notVotedCount = totalMembers - votedCount;
33
+ const voteRate = totalMembers > 0 ? Math.round((votedCount / totalMembers) * 100) : 0;
34
+
35
+ return `
36
+ <div class="poll-detail-modern">
37
+ <!-- 投票统计卡片 -->
38
+ <div class="poll-stats-header">
39
+ <h3>📊 投票统计</h3>
40
+ <div class="poll-stats-grid">
41
+ <div class="poll-stat-card">
42
+ <div class="stat-icon" style="background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%);">
43
+ 👥
44
+ </div>
45
+ <div class="stat-info">
46
+ <div class="stat-label">总人数</div>
47
+ <div class="stat-value">${totalMembers}</div>
48
+ </div>
49
+ </div>
50
+
51
+ <div class="poll-stat-card">
52
+ <div class="stat-icon" style="background: linear-gradient(135deg, #10b981 0%, #059669 100%);">
53
+
54
+ </div>
55
+ <div class="stat-info">
56
+ <div class="stat-label">已投票</div>
57
+ <div class="stat-value">${votedCount}</div>
58
+ </div>
59
+ </div>
60
+
61
+ <div class="poll-stat-card">
62
+ <div class="stat-icon" style="background: linear-gradient(135deg, #f59e0b 0%, #d97706 100%);">
63
+
64
+ </div>
65
+ <div class="stat-info">
66
+ <div class="stat-label">未投票</div>
67
+ <div class="stat-value">${notVotedCount}</div>
68
+ </div>
69
+ </div>
70
+
71
+ <div class="poll-stat-card">
72
+ <div class="stat-icon" style="background: linear-gradient(135deg, #3b82f6 0%, #2563eb 100%);">
73
+ 📈
74
+ </div>
75
+ <div class="stat-info">
76
+ <div class="stat-label">投票率</div>
77
+ <div class="stat-value">${voteRate}%</div>
78
+ </div>
79
+ </div>
80
+ </div>
81
+
82
+ <!-- 投票率进度条 -->
83
+ <div class="vote-rate-progress">
84
+ <div class="progress-label">
85
+ <span>投票进度</span>
86
+ <span class="progress-percentage">${voteRate}%</span>
87
+ </div>
88
+ <div class="progress-bar-modern">
89
+ <div class="progress-fill-modern" style="width: ${voteRate}%;">
90
+ <div class="progress-shine"></div>
91
+ </div>
92
+ </div>
93
+ </div>
94
+ </div>
95
+
96
+ <!-- 投票选项结果 -->
97
+ <div class="poll-options-section">
98
+ <h3>🗳️ 投票结果</h3>
99
+ <div class="poll-options-list">
100
+ ${pollOptions.map((option, index) => {
101
+ const voteCount = Array.isArray(option.votes) ? option.votes.length : Number(option.votes || 0);
102
+ const percentage = totalVotes > 0 ? Math.round((voteCount / totalVotes) * 100) : 0;
103
+ const isWinning = voteCount > 0 && voteCount === Math.max(...pollOptions.map(o =>
104
+ Array.isArray(o.votes) ? o.votes.length : Number(o.votes || 0)
105
+ ));
106
+
107
+ return `
108
+ <div class="poll-option-card ${isWinning ? 'winning' : ''}">
109
+ <div class="option-header">
110
+ <div class="option-rank">#${index + 1}</div>
111
+ <div class="option-text">${option.text}</div>
112
+ ${isWinning ? '<div class="winning-badge">🏆 领先</div>' : ''}
113
+ </div>
114
+
115
+ <div class="option-stats">
116
+ <div class="option-votes">
117
+ <span class="votes-count">${voteCount}</span>
118
+ <span class="votes-label">票</span>
119
+ </div>
120
+ <div class="option-percentage">${percentage}%</div>
121
+ </div>
122
+
123
+ <div class="option-progress">
124
+ <div class="progress-bar-option">
125
+ <div class="progress-fill-option" style="width: ${percentage}%; background: ${isWinning ? 'linear-gradient(90deg, #10b981 0%, #059669 100%)' : 'linear-gradient(90deg, #6366f1 0%, #8b5cf6 100%)'};">
126
+ <div class="progress-shine"></div>
127
+ </div>
128
+ </div>
129
+ </div>
130
+
131
+ <!-- 投票成员列表 -->
132
+ ${voteCount > 0 ? `
133
+ <div class="voters-list">
134
+ <div class="voters-header">
135
+ <span>投票成员:</span>
136
+ <button class="btn-toggle-voters" onclick="toggleVoters(${index})">
137
+ <span id="voters-toggle-${index}">展开 ▼</span>
138
+ </button>
139
+ </div>
140
+ <div class="voters-content hidden" id="voters-${index}">
141
+ ${Array.isArray(option.votes) ? option.votes.map(vote => {
142
+ const userId = vote.user?._id || vote.user || vote;
143
+ const member = assignedMembers.find(m => String(m.id) === String(userId));
144
+ const username = member?.username || vote.user?.username || '未知用户';
145
+ const votedAt = vote.votedAt ? new Date(vote.votedAt).toLocaleString() : '';
146
+
147
+ return `
148
+ <div class="voter-item">
149
+ <div class="voter-avatar">${username[0].toUpperCase()}</div>
150
+ <div class="voter-info">
151
+ <div class="voter-name">${username}</div>
152
+ ${votedAt ? `<div class="voter-time">${votedAt}</div>` : ''}
153
+ </div>
154
+ </div>
155
+ `;
156
+ }).join('') : ''}
157
+ </div>
158
+ </div>
159
+ ` : ''}
160
+ </div>
161
+ `;
162
+ }).join('')}
163
+ </div>
164
+ </div>
165
+
166
+ <!-- 未投票成员 -->
167
+ ${notVotedCount > 0 ? `
168
+ <div class="not-voted-section">
169
+ <h3>⏳ 未投票成员 (${notVotedCount})</h3>
170
+ <div class="not-voted-list">
171
+ ${assignedMembers.filter(member => !votedMembers.has(String(member.id))).map(member => `
172
+ <div class="not-voted-item">
173
+ <div class="member-avatar">${member.username[0].toUpperCase()}</div>
174
+ <div class="member-name">${member.username}</div>
175
+ <div class="not-voted-badge">未投票</div>
176
+ </div>
177
+ `).join('')}
178
+ </div>
179
+ </div>
180
+ ` : ''}
181
+ </div>
182
+ `;
183
+ }
184
+
185
+ // 添加投票详情样式
186
+ export function addPollDetailStyles() {
187
+ if (document.getElementById('poll-detail-modern-styles')) return;
188
+
189
+ const style = document.createElement('style');
190
+ style.id = 'poll-detail-modern-styles';
191
+ style.textContent = `
192
+ .poll-detail-modern {
193
+ margin-top: 30px;
194
+ }
195
+
196
+ .poll-stats-header {
197
+ background: linear-gradient(135deg, var(--bg-card) 0%, rgba(99,102,241,0.05) 100%);
198
+ border: 2px solid var(--border);
199
+ border-radius: 16px;
200
+ padding: 24px;
201
+ margin-bottom: 24px;
202
+ }
203
+
204
+ .poll-stats-header h3 {
205
+ margin: 0 0 20px;
206
+ font-size: 20px;
207
+ color: var(--text-primary);
208
+ display: flex;
209
+ align-items: center;
210
+ gap: 8px;
211
+ }
212
+
213
+ .poll-stats-grid {
214
+ display: grid;
215
+ grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
216
+ gap: 16px;
217
+ margin-bottom: 24px;
218
+ }
219
+
220
+ .poll-stat-card {
221
+ background: var(--bg-dark);
222
+ border: 1px solid var(--border);
223
+ border-radius: 12px;
224
+ padding: 16px;
225
+ display: flex;
226
+ align-items: center;
227
+ gap: 12px;
228
+ transition: all 0.3s ease;
229
+ }
230
+
231
+ .poll-stat-card:hover {
232
+ transform: translateY(-3px);
233
+ box-shadow: 0 8px 24px rgba(99,102,241,0.15);
234
+ border-color: var(--primary);
235
+ }
236
+
237
+ .poll-stat-card .stat-icon {
238
+ width: 50px;
239
+ height: 50px;
240
+ border-radius: 10px;
241
+ display: flex;
242
+ align-items: center;
243
+ justify-content: center;
244
+ font-size: 24px;
245
+ flex-shrink: 0;
246
+ }
247
+
248
+ .poll-stat-card .stat-info {
249
+ flex: 1;
250
+ }
251
+
252
+ .poll-stat-card .stat-label {
253
+ font-size: 12px;
254
+ color: var(--text-secondary);
255
+ margin-bottom: 4px;
256
+ font-weight: 600;
257
+ text-transform: uppercase;
258
+ }
259
+
260
+ .poll-stat-card .stat-value {
261
+ font-size: 28px;
262
+ font-weight: 800;
263
+ color: var(--text-primary);
264
+ }
265
+
266
+ .vote-rate-progress {
267
+ margin-top: 20px;
268
+ }
269
+
270
+ .progress-label {
271
+ display: flex;
272
+ justify-content: space-between;
273
+ margin-bottom: 8px;
274
+ font-size: 14px;
275
+ font-weight: 600;
276
+ color: var(--text-primary);
277
+ }
278
+
279
+ .progress-percentage {
280
+ color: var(--primary);
281
+ }
282
+
283
+ .progress-bar-modern {
284
+ height: 24px;
285
+ background: var(--bg-dark);
286
+ border-radius: 12px;
287
+ overflow: hidden;
288
+ position: relative;
289
+ border: 1px solid var(--border);
290
+ }
291
+
292
+ .progress-fill-modern {
293
+ height: 100%;
294
+ background: linear-gradient(90deg, #6366f1 0%, #8b5cf6 100%);
295
+ border-radius: 12px;
296
+ transition: width 0.6s ease;
297
+ position: relative;
298
+ overflow: hidden;
299
+ display: flex;
300
+ align-items: center;
301
+ justify-content: flex-end;
302
+ padding-right: 8px;
303
+ color: white;
304
+ font-weight: 700;
305
+ font-size: 12px;
306
+ }
307
+
308
+ .progress-shine {
309
+ position: absolute;
310
+ top: 0;
311
+ left: -100%;
312
+ width: 100%;
313
+ height: 100%;
314
+ background: linear-gradient(90deg, transparent, rgba(255,255,255,0.3), transparent);
315
+ animation: shine 2s infinite;
316
+ }
317
+
318
+ @keyframes shine {
319
+ to {
320
+ left: 100%;
321
+ }
322
+ }
323
+
324
+ .poll-options-section {
325
+ margin-bottom: 24px;
326
+ }
327
+
328
+ .poll-options-section h3 {
329
+ margin: 0 0 16px;
330
+ font-size: 20px;
331
+ color: var(--text-primary);
332
+ display: flex;
333
+ align-items: center;
334
+ gap: 8px;
335
+ }
336
+
337
+ .poll-options-list {
338
+ display: flex;
339
+ flex-direction: column;
340
+ gap: 16px;
341
+ }
342
+
343
+ .poll-option-card {
344
+ background: var(--bg-card);
345
+ border: 2px solid var(--border);
346
+ border-radius: 16px;
347
+ padding: 20px;
348
+ transition: all 0.3s ease;
349
+ }
350
+
351
+ .poll-option-card:hover {
352
+ border-color: var(--primary);
353
+ box-shadow: 0 8px 24px rgba(99,102,241,0.15);
354
+ }
355
+
356
+ .poll-option-card.winning {
357
+ border-color: var(--success);
358
+ background: linear-gradient(135deg, var(--bg-card) 0%, rgba(16,185,129,0.05) 100%);
359
+ }
360
+
361
+ .option-header {
362
+ display: flex;
363
+ align-items: center;
364
+ gap: 12px;
365
+ margin-bottom: 16px;
366
+ }
367
+
368
+ .option-rank {
369
+ width: 36px;
370
+ height: 36px;
371
+ border-radius: 8px;
372
+ background: linear-gradient(135deg, var(--primary) 0%, var(--secondary) 100%);
373
+ color: white;
374
+ display: flex;
375
+ align-items: center;
376
+ justify-content: center;
377
+ font-weight: 700;
378
+ font-size: 14px;
379
+ flex-shrink: 0;
380
+ }
381
+
382
+ .poll-option-card.winning .option-rank {
383
+ background: linear-gradient(135deg, #10b981 0%, #059669 100%);
384
+ }
385
+
386
+ .option-text {
387
+ flex: 1;
388
+ font-size: 16px;
389
+ font-weight: 600;
390
+ color: var(--text-primary);
391
+ }
392
+
393
+ .winning-badge {
394
+ padding: 6px 12px;
395
+ background: linear-gradient(135deg, #10b981 0%, #059669 100%);
396
+ color: white;
397
+ border-radius: 8px;
398
+ font-size: 12px;
399
+ font-weight: 700;
400
+ display: flex;
401
+ align-items: center;
402
+ gap: 4px;
403
+ }
404
+
405
+ .option-stats {
406
+ display: flex;
407
+ justify-content: space-between;
408
+ align-items: center;
409
+ margin-bottom: 12px;
410
+ }
411
+
412
+ .option-votes {
413
+ display: flex;
414
+ align-items: baseline;
415
+ gap: 4px;
416
+ }
417
+
418
+ .votes-count {
419
+ font-size: 32px;
420
+ font-weight: 800;
421
+ color: var(--text-primary);
422
+ }
423
+
424
+ .votes-label {
425
+ font-size: 14px;
426
+ color: var(--text-secondary);
427
+ }
428
+
429
+ .option-percentage {
430
+ font-size: 24px;
431
+ font-weight: 700;
432
+ color: var(--primary);
433
+ }
434
+
435
+ .poll-option-card.winning .option-percentage {
436
+ color: var(--success);
437
+ }
438
+
439
+ .option-progress {
440
+ margin-bottom: 16px;
441
+ }
442
+
443
+ .progress-bar-option {
444
+ height: 12px;
445
+ background: var(--bg-dark);
446
+ border-radius: 6px;
447
+ overflow: hidden;
448
+ position: relative;
449
+ }
450
+
451
+ .progress-fill-option {
452
+ height: 100%;
453
+ border-radius: 6px;
454
+ transition: width 0.6s ease;
455
+ position: relative;
456
+ overflow: hidden;
457
+ }
458
+
459
+ .voters-list {
460
+ border-top: 1px solid var(--border);
461
+ padding-top: 16px;
462
+ }
463
+
464
+ .voters-header {
465
+ display: flex;
466
+ justify-content: space-between;
467
+ align-items: center;
468
+ margin-bottom: 12px;
469
+ font-size: 14px;
470
+ font-weight: 600;
471
+ color: var(--text-secondary);
472
+ }
473
+
474
+ .btn-toggle-voters {
475
+ background: transparent;
476
+ border: none;
477
+ color: var(--primary);
478
+ cursor: pointer;
479
+ font-size: 13px;
480
+ font-weight: 600;
481
+ padding: 4px 8px;
482
+ border-radius: 4px;
483
+ transition: all 0.3s ease;
484
+ }
485
+
486
+ .btn-toggle-voters:hover {
487
+ background: rgba(99,102,241,0.1);
488
+ }
489
+
490
+ .voters-content {
491
+ display: grid;
492
+ grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
493
+ gap: 12px;
494
+ }
495
+
496
+ .voters-content.hidden {
497
+ display: none;
498
+ }
499
+
500
+ .voter-item {
501
+ display: flex;
502
+ align-items: center;
503
+ gap: 10px;
504
+ padding: 10px;
505
+ background: var(--bg-dark);
506
+ border: 1px solid var(--border);
507
+ border-radius: 8px;
508
+ transition: all 0.3s ease;
509
+ }
510
+
511
+ .voter-item:hover {
512
+ border-color: var(--primary);
513
+ transform: translateX(3px);
514
+ }
515
+
516
+ .voter-avatar {
517
+ width: 36px;
518
+ height: 36px;
519
+ border-radius: 8px;
520
+ background: linear-gradient(135deg, var(--primary) 0%, var(--secondary) 100%);
521
+ color: white;
522
+ display: flex;
523
+ align-items: center;
524
+ justify-content: center;
525
+ font-weight: 700;
526
+ font-size: 14px;
527
+ flex-shrink: 0;
528
+ }
529
+
530
+ .voter-info {
531
+ flex: 1;
532
+ min-width: 0;
533
+ }
534
+
535
+ .voter-name {
536
+ font-size: 14px;
537
+ font-weight: 600;
538
+ color: var(--text-primary);
539
+ white-space: nowrap;
540
+ overflow: hidden;
541
+ text-overflow: ellipsis;
542
+ }
543
+
544
+ .voter-time {
545
+ font-size: 11px;
546
+ color: var(--text-secondary);
547
+ }
548
+
549
+ .not-voted-section {
550
+ background: linear-gradient(135deg, var(--bg-card) 0%, rgba(245,158,11,0.05) 100%);
551
+ border: 2px solid rgba(245,158,11,0.3);
552
+ border-radius: 16px;
553
+ padding: 24px;
554
+ }
555
+
556
+ .not-voted-section h3 {
557
+ margin: 0 0 16px;
558
+ font-size: 18px;
559
+ color: var(--text-primary);
560
+ display: flex;
561
+ align-items: center;
562
+ gap: 8px;
563
+ }
564
+
565
+ .not-voted-list {
566
+ display: grid;
567
+ grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
568
+ gap: 12px;
569
+ }
570
+
571
+ .not-voted-item {
572
+ display: flex;
573
+ align-items: center;
574
+ gap: 10px;
575
+ padding: 12px;
576
+ background: var(--bg-dark);
577
+ border: 1px solid var(--border);
578
+ border-radius: 8px;
579
+ transition: all 0.3s ease;
580
+ }
581
+
582
+ .not-voted-item:hover {
583
+ border-color: #f59e0b;
584
+ transform: translateY(-2px);
585
+ }
586
+
587
+ .member-avatar {
588
+ width: 36px;
589
+ height: 36px;
590
+ border-radius: 8px;
591
+ background: linear-gradient(135deg, #f59e0b 0%, #d97706 100%);
592
+ color: white;
593
+ display: flex;
594
+ align-items: center;
595
+ justify-content: center;
596
+ font-weight: 700;
597
+ font-size: 14px;
598
+ flex-shrink: 0;
599
+ }
600
+
601
+ .member-name {
602
+ flex: 1;
603
+ font-size: 14px;
604
+ font-weight: 600;
605
+ color: var(--text-primary);
606
+ white-space: nowrap;
607
+ overflow: hidden;
608
+ text-overflow: ellipsis;
609
+ }
610
+
611
+ .not-voted-badge {
612
+ padding: 4px 8px;
613
+ background: rgba(245,158,11,0.2);
614
+ color: #f59e0b;
615
+ border-radius: 6px;
616
+ font-size: 11px;
617
+ font-weight: 600;
618
+ }
619
+
620
+ /* 简单投票表单样式 */
621
+ .poll-form-simple {
622
+ background: var(--bg-dark);
623
+ border: 2px solid var(--border);
624
+ border-radius: 12px;
625
+ padding: 20px;
626
+ margin: 20px 0;
627
+ }
628
+
629
+ .poll-form-simple .poll-question {
630
+ margin-bottom: 16px;
631
+ font-size: 16px;
632
+ color: var(--text-primary);
633
+ }
634
+
635
+ .poll-option-simple {
636
+ padding: 12px;
637
+ background: var(--bg-card);
638
+ border: 2px solid var(--border);
639
+ border-radius: 8px;
640
+ margin-bottom: 10px;
641
+ transition: all 0.3s ease;
642
+ }
643
+
644
+ .poll-option-simple:hover {
645
+ border-color: var(--primary);
646
+ transform: translateX(3px);
647
+ }
648
+
649
+ .poll-option-simple label {
650
+ display: flex;
651
+ align-items: center;
652
+ gap: 10px;
653
+ cursor: pointer;
654
+ width: 100%;
655
+ }
656
+
657
+ .poll-option-simple input[type="radio"],
658
+ .poll-option-simple input[type="checkbox"] {
659
+ width: 20px;
660
+ height: 20px;
661
+ cursor: pointer;
662
+ }
663
+
664
+ .poll-option-simple .option-text {
665
+ flex: 1;
666
+ font-size: 15px;
667
+ font-weight: 500;
668
+ color: var(--text-primary);
669
+ }
670
+
671
+ .voted-message {
672
+ text-align: center;
673
+ padding: 20px;
674
+ background: linear-gradient(135deg, var(--bg-card) 0%, rgba(16,185,129,0.05) 100%);
675
+ border: 2px solid var(--success);
676
+ border-radius: 12px;
677
+ margin: 20px 0;
678
+ }
679
+
680
+ .voted-badge {
681
+ display: inline-flex;
682
+ align-items: center;
683
+ gap: 6px;
684
+ padding: 10px 20px;
685
+ background: linear-gradient(135deg, #10b981 0%, #059669 100%);
686
+ color: white;
687
+ border-radius: 8px;
688
+ font-size: 14px;
689
+ font-weight: 700;
690
+ }
691
+
692
+ @media (max-width: 768px) {
693
+ .poll-stats-grid {
694
+ grid-template-columns: repeat(2, 1fr);
695
+ }
696
+
697
+ .voters-content {
698
+ grid-template-columns: 1fr;
699
+ }
700
+
701
+ .not-voted-list {
702
+ grid-template-columns: 1fr;
703
+ }
704
+ }
705
+ `;
706
+ document.head.appendChild(style);
707
+ }
708
+
709
+ // 切换投票成员列表显示
710
+ window.toggleVoters = function(index) {
711
+ const content = document.getElementById(`voters-${index}`);
712
+ const toggle = document.getElementById(`voters-toggle-${index}`);
713
+
714
+ if (content && toggle) {
715
+ if (content.classList.contains('hidden')) {
716
+ content.classList.remove('hidden');
717
+ toggle.textContent = '收起 ▲';
718
+ } else {
719
+ content.classList.add('hidden');
720
+ toggle.textContent = '展开 ▼';
721
+ }
722
+ }
723
+ };
724
+