feat: 任务附件上传(图片/文件随任务提交)
B-① 附件上传(⑧ ★ POST /api/tasks/:id/attachments):
后端:
- 加 @fastify/multipart;schema/db.ts tasks.attachments 列(幂等迁移)
- types.Attachment{name,type,path} + Task.attachments;mappers 映射
- store.addAttachments(追加元数据 + 广播 task.updated)
- POST /api/tasks/:id/attachments:multipart files[](单文件≤25MB/单次≤10),
落盘 <data>/tasks/<id>/attachments/,文件名消毒,返回全部附件
前端:
- api.uploadAttachments(FormData multipart)
- 新建任务表单加文件选择 + 已选列表;创建任务后自动上传附件
验证:typecheck 干净;206 测试通过;前端 build 通过。
(端到端需重启 daemon 加载新端点——与后续 SSE/takeover 一并重启。)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -31,6 +31,18 @@ export const api = {
|
||||
createTask: (pid, body) => http('POST', `/api/projects/${pid}/tasks`, body),
|
||||
patchProject: (pid, body) => http('PATCH', `/api/projects/${pid}`, body),
|
||||
createProject: (body) => http('POST', '/api/projects', body),
|
||||
// 附件上传走 multipart(不能用 JSON helper,content-type 由浏览器带 boundary)
|
||||
uploadAttachments: async (taskId, files) => {
|
||||
const fd = new FormData();
|
||||
for (const f of files) fd.append('files', f, f.name);
|
||||
const res = await fetch(`/api/tasks/${taskId}/attachments`, { method: 'POST', body: fd });
|
||||
if (!res.ok) {
|
||||
let msg = res.statusText;
|
||||
try { msg = (await res.json()).error || msg; } catch { /* 非 JSON */ }
|
||||
throw new Error(`上传附件失败 → ${res.status} ${msg}`);
|
||||
}
|
||||
return res.json();
|
||||
},
|
||||
};
|
||||
|
||||
// WS 单向订阅:连上后服务端推送 events 表记录。断线自动重连(指数退避,封顶 10s)。
|
||||
|
||||
+6
-1
@@ -208,8 +208,13 @@ export function App() {
|
||||
catch (e) { toast('warn', '同步失败:' + e.message); }
|
||||
};
|
||||
const createTask = async (payload) => {
|
||||
const { files, ...body } = payload;
|
||||
try {
|
||||
await api.createTask(currentId, payload);
|
||||
const task = await api.createTask(currentId, body);
|
||||
if (files && files.length) {
|
||||
try { await api.uploadAttachments(task.id, files); toast('ok', `附件已上传 · ${files.length} 个`); }
|
||||
catch (e) { toast('warn', e.message); }
|
||||
}
|
||||
toast('ok', t.toastCreated);
|
||||
loadProject(currentId); loadGlobal();
|
||||
} catch (e) { toast('warn', '创建失败:' + e.message); }
|
||||
|
||||
Reference in New Issue
Block a user