fix: 胶囊进度按子任务加权 + 调研任务清单必填+预设选项

1. 胶囊进度计算改为子任务加权:
   - 分子 = 所有plan中已完成的子任务数
   - 分母 = 所有plan的子任务总数
   - completed的plan视为其子任务全部完成
   - 没有子任务的plan按1项计算(不会导致分母为0)
   - 例:4个completed(共8子任务) + 1个pending(2子任务)
         = 8/(8+2) = 80%

2. 调研任务清单必须至少1项:
   - handleSubmit 加校验:tasks.length === 0 时 return
   - 红色提示:"请至少添加一项任务"

3. 预设可选任务选项(空列表时显示):
   - 竞品分析、用户访谈、数据调研
   - 技术可行性分析、市场调研、需求分析
   - 点击即添加,也可自定义输入

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Script Generator
2026-06-15 15:39:38 +08:00
parent 5bbcf02962
commit 2d3f56b00c
2 changed files with 45 additions and 17 deletions

View File

@@ -303,27 +303,33 @@ export default function VersionDetailPage() {
const calcGroupProgress = (group: typeof vPlans, type: 'research' | 'product' | 'ui') => {
if (group.length === 0) return 0;
// 每个 plan 按权重贡献进度completed=1, in_progress=子任务完成率, pending=0
let totalWeight = 0;
// 分子=已完成子任务数,分母=所有plan的子任务总数
// completed 的 plan其子任务全算已完成
let totalItems = 0;
let doneItems = 0;
for (const p of group) {
if (p.status === 'completed') {
totalWeight += 1;
} else if (p.status === 'in_progress') {
if (type === 'research') {
const tasks = p.tasks || [];
if (tasks.length > 0) {
totalWeight += tasks.filter((t) => t.status === 'completed').length / tasks.length;
}
if (type === 'research') {
const tasks = p.tasks || [];
const count = Math.max(tasks.length, 1);
totalItems += count;
if (p.status === 'completed') {
doneItems += count;
} else {
doneItems += tasks.filter((t) => t.status === 'completed').length;
}
} else {
const linked = p.linkedRequirementIds || [];
const count = Math.max(linked.length, 1);
totalItems += count;
if (p.status === 'completed') {
doneItems += count;
} else {
const linked = p.linkedRequirementIds || [];
const completed = p.completedRequirementIds || [];
if (linked.length > 0) {
totalWeight += completed.filter((id) => linked.includes(id)).length / linked.length;
}
doneItems += completed.filter((id) => linked.includes(id)).length;
}
}
}
return Math.round((totalWeight / group.length) * 100);
return totalItems > 0 ? Math.round((doneItems / totalItems) * 100) : 0;
};
const getPlanStatus = (group: typeof vPlans): 'idle' | 'active' | 'done' => {