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:
@@ -205,10 +205,11 @@ function NewTaskPanel({ tasks, onCancel, onCreate, t }) {
|
||||
const [complexity, setComplexity] = React.useState('auto');
|
||||
const [priority, setPriority] = React.useState('1');
|
||||
const [parentId, setParentId] = React.useState('');
|
||||
const [files, setFiles] = React.useState([]);
|
||||
const submit = (e) => {
|
||||
e.preventDefault();
|
||||
if (!title.trim()) return;
|
||||
onCreate({ title: title.trim(), complexity, priority: Number(priority), parentId: parentId || null });
|
||||
onCreate({ title: title.trim(), complexity, priority: Number(priority), parentId: parentId || null, files });
|
||||
};
|
||||
// 扁平化任务树供父任务下拉(含子任务)
|
||||
const flat = [];
|
||||
@@ -232,6 +233,18 @@ function NewTaskPanel({ tasks, onCancel, onCreate, t }) {
|
||||
<Select label={t.parentTask} style={{ width: '100%' }} onChange={setParentId} options={[{ value: '', label: t.topLevel }, ...flat]} />
|
||||
</span>
|
||||
</div>
|
||||
<div style={{ marginTop: 12 }}>
|
||||
<label style={{ display: 'flex', flexDirection: 'column', gap: 4, fontSize: 11, color: 'var(--muted)', letterSpacing: '.08em' }}>
|
||||
<span>{t.attachLabel || '附件 · 图片/文件(可选)'}</span>
|
||||
<input type="file" multiple onChange={(e) => setFiles([...e.target.files])}
|
||||
style={{ fontFamily: 'var(--mono)', fontSize: 12, color: 'var(--ink)' }} />
|
||||
</label>
|
||||
{files.length ? (
|
||||
<div style={{ marginTop: 6, fontSize: 11, color: 'var(--faint)' }}>
|
||||
{files.map((f) => f.name).join(' · ')}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
<div style={{ display: 'flex', gap: 10, justifyContent: 'flex-end', marginTop: 14, paddingTop: 12, borderTop: '1px solid var(--line-soft)' }}>
|
||||
<Button onClick={onCancel}>{t.cancel}</Button>
|
||||
<Button variant="solid" type="submit">{t.create}</Button>
|
||||
|
||||
Reference in New Issue
Block a user