collabdocchat 1.2.13 → 2.0.1
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/README.md +219 -218
- package/index.html +2 -0
- package/install-and-start.bat +5 -0
- package/install-and-start.sh +5 -0
- package/package.json +9 -2
- package/scripts/generate-docs.js +448 -0
- package/scripts/pre-publish-check.js +213 -0
- package/scripts/start-app.js +15 -15
- package/server/index.js +38 -6
- package/server/middleware/cache.js +115 -0
- package/server/middleware/errorHandler.js +209 -0
- package/server/models/Document.js +66 -59
- package/server/models/File.js +49 -43
- package/server/models/Group.js +6 -0
- package/server/models/KnowledgeBase.js +254 -0
- package/server/models/Message.js +43 -0
- package/server/models/Task.js +87 -55
- package/server/models/User.js +67 -60
- package/server/models/Workflow.js +249 -0
- package/server/routes/ai.js +327 -0
- package/server/routes/audit.js +245 -210
- package/server/routes/backup.js +108 -0
- package/server/routes/chunked-upload.js +343 -0
- package/server/routes/export.js +440 -0
- package/server/routes/files.js +294 -218
- package/server/routes/groups.js +182 -0
- package/server/routes/knowledge.js +509 -0
- package/server/routes/tasks.js +257 -110
- package/server/routes/workflows.js +380 -0
- package/server/utils/backup.js +439 -0
- package/server/utils/cache.js +223 -0
- package/server/utils/workflow-engine.js +479 -0
- package/server/websocket/enhanced.js +509 -0
- package/server/websocket/index.js +233 -1
- package/src/components/knowledge-modal.js +485 -0
- package/src/components/optimized-poll-detail.js +724 -0
- package/src/main.js +5 -0
- package/src/pages/admin-dashboard.js +2248 -44
- package/src/pages/optimized-backup-view.js +616 -0
- package/src/pages/optimized-knowledge-view.js +803 -0
- package/src/pages/optimized-task-detail.js +843 -0
- package/src/pages/optimized-workflow-view.js +806 -0
- package/src/pages/simplified-workflows.js +651 -0
- package/src/pages/user-dashboard.js +677 -58
- package/src/services/api.js +64 -0
- package/src/services/auth.js +1 -1
- package/src/services/websocket.js +124 -16
- package/src/styles/collaboration-modern.js +708 -0
- package/src/styles/enhancements.css +392 -0
- package/src/styles/main.css +620 -1420
- package/src/styles/responsive.css +1000 -0
- package/src/styles/sidebar-fix.css +60 -0
- package/src/utils/ai-assistant.js +1398 -0
- package/src/utils/chat-enhancements.js +509 -0
- package/src/utils/collaboration-enhancer.js +1151 -0
- package/src/utils/feature-integrator.js +1724 -0
- package/src/utils/onboarding-guide.js +734 -0
- package/src/utils/performance.js +394 -0
- package/src/utils/permission-manager.js +890 -0
- package/src/utils/responsive-handler.js +491 -0
- package/src/utils/theme-manager.js +811 -0
- package/src/utils/ui-enhancements-loader.js +329 -0
- package/USAGE.md +0 -298
package/server/routes/tasks.js
CHANGED
|
@@ -1,110 +1,257 @@
|
|
|
1
|
-
import express from 'express';
|
|
2
|
-
import Task from '../models/Task.js';
|
|
3
|
-
import Group from '../models/Group.js';
|
|
4
|
-
import { authenticate, isAdmin } from '../middleware/auth.js';
|
|
5
|
-
|
|
6
|
-
const router = express.Router();
|
|
7
|
-
|
|
8
|
-
// 创建任务(仅管理员)
|
|
9
|
-
router.post('/', authenticate, isAdmin, async (req, res) => {
|
|
10
|
-
try {
|
|
11
|
-
const { title, description, groupId, assignedTo, relatedDocument, deadline } = req.body;
|
|
12
|
-
|
|
13
|
-
const
|
|
14
|
-
title,
|
|
15
|
-
description,
|
|
16
|
-
group: groupId,
|
|
17
|
-
creator: req.user.userId,
|
|
18
|
-
assignedTo,
|
|
19
|
-
relatedDocument,
|
|
20
|
-
deadline
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
1
|
+
import express from 'express';
|
|
2
|
+
import Task from '../models/Task.js';
|
|
3
|
+
import Group from '../models/Group.js';
|
|
4
|
+
import { authenticate, isAdmin } from '../middleware/auth.js';
|
|
5
|
+
|
|
6
|
+
const router = express.Router();
|
|
7
|
+
|
|
8
|
+
// 创建任务(仅管理员)
|
|
9
|
+
router.post('/', authenticate, isAdmin, async (req, res) => {
|
|
10
|
+
try {
|
|
11
|
+
const { title, description, groupId, assignedTo, relatedDocument, deadline, type, pollData } = req.body;
|
|
12
|
+
|
|
13
|
+
const taskData = {
|
|
14
|
+
title,
|
|
15
|
+
description,
|
|
16
|
+
group: groupId,
|
|
17
|
+
creator: req.user.userId,
|
|
18
|
+
assignedTo,
|
|
19
|
+
relatedDocument,
|
|
20
|
+
deadline
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
// 如果是投票任务,添加投票数据
|
|
24
|
+
if (type === 'poll' && pollData) {
|
|
25
|
+
taskData.type = 'poll';
|
|
26
|
+
taskData.pollData = pollData;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const task = new Task(taskData);
|
|
30
|
+
|
|
31
|
+
await task.save();
|
|
32
|
+
|
|
33
|
+
// 更新群组的任务列表
|
|
34
|
+
await Group.findByIdAndUpdate(groupId, { $push: { tasks: task._id } });
|
|
35
|
+
|
|
36
|
+
res.status(201).json({ message: '任务创建成功', task });
|
|
37
|
+
} catch (error) {
|
|
38
|
+
res.status(500).json({ message: '创建任务失败', error: error.message });
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
// 获取用户的任务(必须在 /:id 之前)
|
|
43
|
+
router.get('/my', authenticate, async (req, res) => {
|
|
44
|
+
try {
|
|
45
|
+
const tasks = await Task.find({ assignedTo: req.user.userId })
|
|
46
|
+
.populate('creator', 'username avatar')
|
|
47
|
+
.populate('group', 'name')
|
|
48
|
+
.populate('relatedDocument', 'title')
|
|
49
|
+
.sort({ createdAt: -1 });
|
|
50
|
+
|
|
51
|
+
res.json({ tasks });
|
|
52
|
+
} catch (error) {
|
|
53
|
+
res.status(500).json({ message: '获取任务失败', error: error.message });
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
// 获取群组的所有任务
|
|
58
|
+
router.get('/group/:groupId', authenticate, async (req, res) => {
|
|
59
|
+
try {
|
|
60
|
+
const tasks = await Task.find({ group: req.params.groupId })
|
|
61
|
+
.populate('creator', 'username avatar')
|
|
62
|
+
.populate('assignedTo', 'username avatar')
|
|
63
|
+
.populate('relatedDocument', 'title')
|
|
64
|
+
.sort({ createdAt: -1 });
|
|
65
|
+
|
|
66
|
+
res.json({ tasks });
|
|
67
|
+
} catch (error) {
|
|
68
|
+
res.status(500).json({ message: '获取任务失败', error: error.message });
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
// 获取单个任务详情
|
|
73
|
+
router.get('/:id', authenticate, async (req, res) => {
|
|
74
|
+
try {
|
|
75
|
+
const task = await Task.findById(req.params.id)
|
|
76
|
+
.populate('creator', 'username avatar')
|
|
77
|
+
.populate('assignedTo', 'username avatar')
|
|
78
|
+
.populate('completedBy.user', 'username avatar')
|
|
79
|
+
.populate({
|
|
80
|
+
path: 'group',
|
|
81
|
+
select: 'name description members',
|
|
82
|
+
populate: {
|
|
83
|
+
path: 'members',
|
|
84
|
+
select: 'username avatar'
|
|
85
|
+
}
|
|
86
|
+
})
|
|
87
|
+
.populate('relatedDocument', 'title');
|
|
88
|
+
|
|
89
|
+
if (!task) {
|
|
90
|
+
return res.status(404).json({ message: '任务不存在' });
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
res.json({ task });
|
|
94
|
+
} catch (error) {
|
|
95
|
+
console.error('获取任务详情失败:', error);
|
|
96
|
+
res.status(500).json({ message: '获取任务详情失败', error: error.message });
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
// 更新任务状态
|
|
101
|
+
router.patch('/:id/status', authenticate, async (req, res) => {
|
|
102
|
+
try {
|
|
103
|
+
const { status } = req.body;
|
|
104
|
+
const task = await Task.findById(req.params.id);
|
|
105
|
+
|
|
106
|
+
if (!task) {
|
|
107
|
+
return res.status(404).json({ message: '任务不存在' });
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
task.status = status;
|
|
111
|
+
|
|
112
|
+
if (status === 'completed') {
|
|
113
|
+
// 检查用户是否已经标记为完成
|
|
114
|
+
const alreadyCompleted = task.completedBy.some(
|
|
115
|
+
item => item.user.toString() === req.user.userId
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
if (!alreadyCompleted) {
|
|
119
|
+
task.completedBy.push({
|
|
120
|
+
user: req.user.userId,
|
|
121
|
+
completedAt: new Date()
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
await task.save();
|
|
127
|
+
|
|
128
|
+
// 重新查询以填充用户信息
|
|
129
|
+
const updatedTask = await Task.findById(task._id)
|
|
130
|
+
.populate('creator', 'username avatar')
|
|
131
|
+
.populate('assignedTo', 'username avatar')
|
|
132
|
+
.populate('completedBy.user', 'username avatar')
|
|
133
|
+
.populate('group', 'name');
|
|
134
|
+
|
|
135
|
+
res.json({ message: '任务状态更新成功', task: updatedTask });
|
|
136
|
+
} catch (error) {
|
|
137
|
+
res.status(500).json({ message: '更新任务状态失败', error: error.message });
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
// 删除任务(仅管理员)
|
|
142
|
+
router.delete('/:id', authenticate, isAdmin, async (req, res) => {
|
|
143
|
+
try {
|
|
144
|
+
const task = await Task.findByIdAndDelete(req.params.id);
|
|
145
|
+
|
|
146
|
+
if (!task) {
|
|
147
|
+
return res.status(404).json({ message: '任务不存在' });
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
await Group.findByIdAndUpdate(task.group, { $pull: { tasks: task._id } });
|
|
151
|
+
|
|
152
|
+
res.json({ message: '任务删除成功' });
|
|
153
|
+
} catch (error) {
|
|
154
|
+
res.status(500).json({ message: '删除任务失败', error: error.message });
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
// 提交投票
|
|
159
|
+
router.post('/:id/vote', authenticate, async (req, res) => {
|
|
160
|
+
try {
|
|
161
|
+
const { selectedOptions } = req.body; // 选中的选项索引数组
|
|
162
|
+
const task = await Task.findById(req.params.id);
|
|
163
|
+
|
|
164
|
+
console.log('投票请求:', {
|
|
165
|
+
taskId: req.params.id,
|
|
166
|
+
userId: req.user.userId,
|
|
167
|
+
selectedOptions
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
if (!task) {
|
|
171
|
+
return res.status(404).json({ message: '任务不存在' });
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if (task.type !== 'poll') {
|
|
175
|
+
return res.status(400).json({ message: '该任务不是投票任务' });
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if (!task.pollData || !task.pollData.options) {
|
|
179
|
+
return res.status(400).json({ message: '投票数据不完整' });
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// 检查用户是否已投票(转换为字符串比较)
|
|
183
|
+
const userIdStr = req.user.userId.toString();
|
|
184
|
+
const hasVoted = task.pollData.options.some(opt =>
|
|
185
|
+
opt.votes && opt.votes.some(voteId => voteId.toString() === userIdStr)
|
|
186
|
+
);
|
|
187
|
+
|
|
188
|
+
if (hasVoted) {
|
|
189
|
+
return res.status(400).json({ message: '您已经投过票了' });
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// 验证选项索引
|
|
193
|
+
if (!Array.isArray(selectedOptions) || selectedOptions.length === 0) {
|
|
194
|
+
return res.status(400).json({ message: '请选择至少一个选项' });
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// 添加投票
|
|
198
|
+
selectedOptions.forEach(optionIndex => {
|
|
199
|
+
if (task.pollData.options[optionIndex]) {
|
|
200
|
+
if (!task.pollData.options[optionIndex].votes) {
|
|
201
|
+
task.pollData.options[optionIndex].votes = [];
|
|
202
|
+
}
|
|
203
|
+
task.pollData.options[optionIndex].votes.push(req.user.userId);
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
// 更新总投票数
|
|
208
|
+
task.pollData.totalVotes = (task.pollData.totalVotes || 0) + 1;
|
|
209
|
+
|
|
210
|
+
// 自动标记任务为已完成
|
|
211
|
+
const alreadyCompleted = task.completedBy.some(
|
|
212
|
+
item => item.user.toString() === userIdStr
|
|
213
|
+
);
|
|
214
|
+
|
|
215
|
+
if (!alreadyCompleted) {
|
|
216
|
+
task.completedBy.push({
|
|
217
|
+
user: req.user.userId,
|
|
218
|
+
completedAt: new Date()
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
task.status = 'completed';
|
|
223
|
+
|
|
224
|
+
// 标记 pollData 为已修改
|
|
225
|
+
task.markModified('pollData');
|
|
226
|
+
await task.save();
|
|
227
|
+
|
|
228
|
+
console.log('投票成功:', {
|
|
229
|
+
taskId: task._id,
|
|
230
|
+
totalVotes: task.pollData.totalVotes
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
// 重新查询以填充用户信息
|
|
234
|
+
const updatedTask = await Task.findById(task._id)
|
|
235
|
+
.populate('creator', 'username avatar')
|
|
236
|
+
.populate('assignedTo', 'username avatar')
|
|
237
|
+
.populate('completedBy.user', 'username avatar')
|
|
238
|
+
.populate('group', 'name');
|
|
239
|
+
|
|
240
|
+
res.json({
|
|
241
|
+
success: true,
|
|
242
|
+
message: '投票成功',
|
|
243
|
+
task: updatedTask
|
|
244
|
+
});
|
|
245
|
+
} catch (error) {
|
|
246
|
+
console.error('投票失败:', error);
|
|
247
|
+
res.status(500).json({
|
|
248
|
+
success: false,
|
|
249
|
+
message: '投票失败',
|
|
250
|
+
error: error.message
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
export default router;
|
|
256
|
+
|
|
257
|
+
|