diff --git a/backend/routers/submissions.py b/backend/routers/submissions.py index e01907f..fb8b00f 100644 --- a/backend/routers/submissions.py +++ b/backend/routers/submissions.py @@ -66,7 +66,7 @@ def list_submissions( return [submission_to_out(s) for s in subs] -@router.post("", response_model=SubmissionOut) +@router.post("") def create_submission( req: SubmissionCreate, db: Session = Depends(get_db), @@ -88,12 +88,16 @@ def create_submission( if req.hours_spent is None or req.hours_spent <= 0: raise HTTPException(status_code=422, detail="请填写投入时长") - # 集数校验:项目级内容类型不需要集数,其他必填 + # 确定集数列表 from models import PROJECT_LEVEL_TYPES content_val = req.content_type.value if hasattr(req.content_type, 'value') else req.content_type if content_val in PROJECT_LEVEL_TYPES: - req.episode_number = None - elif not req.episode_number: + episode_list = [None] # 项目级,单条无集数 + elif req.episode_numbers and len(req.episode_numbers) > 0: + episode_list = req.episode_numbers # 批量多集 + elif req.episode_number: + episode_list = [req.episode_number] # 单集 + else: raise HTTPException(status_code=422, detail="请选择集数") # 校验项目存在 @@ -101,10 +105,10 @@ def create_submission( if not project: raise HTTPException(status_code=404, detail="项目不存在") - # 自动计算总秒数 + # 自动计算总秒数(每集) total_seconds = (req.duration_minutes or 0) * 60 + (req.duration_seconds or 0) - # 自动关联里程碑:根据 content_type 匹配同名里程碑 + # 自动关联里程碑 milestone_id = None content_val = req.content_type if content_val in CONTENT_PHASE_MAP: @@ -114,7 +118,6 @@ def create_submission( ).first() if ms: milestone_id = ms.id - # 超期校验:如果里程碑已超期,必须填写延期原因 if ms.estimated_days and ms.start_date: expected_end = ms.start_date + timedelta(days=ms.estimated_days) if req.submit_date > expected_end and not req.delay_reason: @@ -123,27 +126,38 @@ def create_submission( detail=f"里程碑「{ms.name}」已超期(预估{ms.estimated_days}天),请填写延期原因" ) - sub = Submission( - user_id=target_user_id, - project_id=req.project_id, - project_phase=PhaseGroup(req.project_phase), - work_type=WorkType(req.work_type), - content_type=ContentType(req.content_type), - duration_minutes=req.duration_minutes or 0, - duration_seconds=req.duration_seconds or 0, - total_seconds=total_seconds, - hours_spent=req.hours_spent, - submit_to=SubmitTo(req.submit_to), - description=req.description, - submit_date=req.submit_date, - milestone_id=milestone_id, - delay_reason=req.delay_reason, - episode_number=req.episode_number, - ) - db.add(sub) + # 投入时长平均分配到每集 + ep_count = len(episode_list) + hours_per_ep = round(req.hours_spent / ep_count, 2) + + results = [] + for ep in episode_list: + sub = Submission( + user_id=target_user_id, + project_id=req.project_id, + project_phase=PhaseGroup(req.project_phase), + work_type=WorkType(req.work_type), + content_type=ContentType(req.content_type), + duration_minutes=req.duration_minutes or 0, + duration_seconds=req.duration_seconds or 0, + total_seconds=total_seconds, + hours_spent=hours_per_ep, + submit_to=SubmitTo(req.submit_to), + description=req.description, + submit_date=req.submit_date, + milestone_id=milestone_id, + delay_reason=req.delay_reason, + episode_number=ep, + ) + db.add(sub) + results.append(sub) db.commit() - db.refresh(sub) - return submission_to_out(sub) + for s in results: + db.refresh(s) + + if len(results) == 1: + return submission_to_out(results[0]) + return [submission_to_out(s) for s in results] @router.put("/{submission_id}", response_model=SubmissionOut) diff --git a/backend/schemas.py b/backend/schemas.py index 7adfd50..7a20210 100644 --- a/backend/schemas.py +++ b/backend/schemas.py @@ -166,6 +166,7 @@ class SubmissionCreate(BaseModel): submit_date: date delay_reason: Optional[str] = None episode_number: Optional[int] = None + episode_numbers: Optional[List[int]] = None # 批量多集提交 user_id: Optional[int] = None # 代人提交时指定目标用户 diff --git a/frontend/.env.development b/frontend/.env.development index e3b0e79..b30d5fb 100644 --- a/frontend/.env.development +++ b/frontend/.env.development @@ -1 +1 @@ -VITE_API_BASE_URL=http://localhost:8000/api +VITE_API_BASE_URL=http://localhost:8001/api diff --git a/frontend/src/views/Submissions.vue b/frontend/src/views/Submissions.vue index caad223..f50de11 100644 --- a/frontend/src/views/Submissions.vue +++ b/frontend/src/views/Submissions.vue @@ -97,9 +97,12 @@ - + +
+ 已选 {{ form.episode_numbers.length }} 集,投入时长将平均分配到每集 +
@@ -283,7 +286,7 @@ const form = reactive({ project_id: null, project_phase: '中期', work_type: '制作', content_type: '动画制作', duration_minutes: 0, duration_seconds: 0, hours_spent: null, submit_to: '组长', description: '', submit_date: today, - delay_reason: '', episode_number: null, user_id: null, + delay_reason: '', episode_numbers: [], user_id: null, }) // ── 编辑逻辑 ── @@ -339,7 +342,7 @@ watch(() => form.content_type, (val) => { form.duration_seconds = 0 } if (!needsEpisode(val)) { - form.episode_number = null + form.episode_numbers = [] } }) @@ -413,7 +416,7 @@ async function load() { async function handleCreate() { if (!form.project_id) { ElMessage.warning('请选择项目'); return } - if (needsEpisode(form.content_type) && !form.episode_number) { ElMessage.warning('请选择集数'); return } + if (needsEpisode(form.content_type) && form.episode_numbers.length === 0) { ElMessage.warning('请选择集数'); return } if (!form.description?.trim()) { ElMessage.warning('请填写描述'); return } if (!form.hours_spent || form.hours_spent <= 0) { ElMessage.warning('请填写投入时长'); return } if (isMilestoneOverdue.value && !form.delay_reason?.trim()) { @@ -431,7 +434,7 @@ async function handleCreate() { form.hours_spent = null form.description = '' form.delay_reason = '' - form.episode_number = null + form.episode_numbers = [] form.user_id = null load() // 刷新工时进度 diff --git a/frontend/vite.config.js b/frontend/vite.config.js index d62dc96..1f8926f 100644 --- a/frontend/vite.config.js +++ b/frontend/vite.config.js @@ -7,7 +7,7 @@ export default defineConfig({ port: 3000, proxy: { '/api': { - target: 'http://localhost:8000', + target: 'http://localhost:8001', changeOrigin: true, } }