2026-06-25-qc-work-order-protocol-group.md 24 KB

入库质检工单 · 协议单多样本 Tab 分组 Implementation Plan

For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (- [ ]) syntax for tracking.

Goal: 在细胞存储质检、科研捐赠质检两个 Tab 中,将同一协议单下多个样本质检工单合并为一张协议组卡片,组内用 Tab 切换样本,样本内保留触发来源 Tab。

Architecture: 前端新增 groupInboundQcWorkOrdersByProtocol() 与 QcWorkOrderProtocolGroup.vue 作为协议外层;复用 QcWorkOrderSampleGroup 作为样本内层(hideContextBar);QcWorkOrderCardList 在 inbound-grouped 布局时渲染协议组。后端 list_work_orders 关键词增加 protocol_no 匹配。两页(manage/mine)共用 CardList,mine 关键词补 protocolNo。

Tech Stack: Python 3.9 / FastAPI / SQLAlchemy(backend);Vue 3 / TypeScript / Element Plus(web-admin)

Spec: docs/superpowers/specs/2026-06-25-qc-work-order-protocol-group-design.md


File Map

文件 职责
web-admin/src/utils/qc-work-order-groups.ts 协议分组模型、groupInboundQcWorkOrdersByProtocol、gate 工单/进度统计 helper
web-admin/src/utils/qc-work-order-card-meta.ts 协议组头副标题、协议状态 Tag、组级 paymentSummary 选取
web-admin/src/utils/qc-work-order-tabs.ts 入库 Tab 关键词 placeholder 含协议号/捐赠单号
web-admin/src/utils/qc-mine-task-groups.ts mine 前端关键词增加 protocolNo
web-admin/src/views/.../QcWorkOrderProtocolGroup.vue 新建 协议组卡片(组头 + 样本 Tab + 内嵌 SampleGroup)
web-admin/src/views/.../QcWorkOrderSampleGroup.vue hideContextBar、hideProtocolFeeSummary prop
web-admin/src/views/.../QcWorkOrderSingleCard.vue 尊重 hideProtocolFeeSummary
web-admin/src/views/.../QcWorkOrderCardList.vue inbound 布局改用 QcWorkOrderProtocolGroup
backend/app/crud/qc_work_order.py 列表关键词 OR 增加 protocol_no.like

不修改: buildAllTabListItems(「所有」Tab)、出库/临时 Tab、buildMineCardListModel(mine 走 flat rows + CardList 分组)。


Task 1: 协议分组工具函数

Files:

  • Modify: web-admin/src/utils/qc-work-order-groups.ts

  • [ ] Step 1: 在文件末尾、buildAllTabListItems 之前追加类型与 helper

在 isGateTriggerSource 之后、buildAllTabListItems 之前插入:

export function resolveInboundProtocolGroupKey(row: QcWorkOrder): string {
  const id = (row.collectionProtocolId || '').trim()
  if (id) return id
  const no = (row.protocolNo || '').trim()
  if (no) return `protocol-no:${no}`
  return row.id
}

export function resolveInboundSampleGroupKey(row: QcWorkOrder): string {
  return (row.sampleCode || '').trim() || row.id
}

/** 样本组内用于组头/Tab 状态的 gate 工单(接收优先,否则首条) */
export function resolveGateWorkOrderForSampleGroup(
  workOrders: QcWorkOrder[]
): QcWorkOrder | undefined {
  if (!workOrders.length) return undefined
  return (
    workOrders.find((w) => isGateTriggerSource(w.triggerSource)) ||
    workOrders[0]
  )
}

export type QcWorkOrderProtocolGroupModel = {
  groupKey: string
  protocolId: string
  protocolNo: string
  receptionSource: 'cell_storage' | 'research_donation'
  sampleGroups: QcWorkOrderSampleGroupModel[]
  workOrders: QcWorkOrder[]
}

function protocolGroupSortTime(group: QcWorkOrderProtocolGroupModel): string {
  let max = ''
  for (const wo of group.workOrders) {
    const t = String(wo.createTime || '')
    if (t > max) max = t
  }
  return max
}

/** 入库 Tab:按协议单分组,组内再按样本分组 */
export function groupInboundQcWorkOrdersByProtocol(
  rows: QcWorkOrder[]
): QcWorkOrderProtocolGroupModel[] {
  const map = new Map<string, QcWorkOrder[]>()
  for (const row of rows) {
    const key = resolveInboundProtocolGroupKey(row)
    const list = map.get(key)
    if (list) list.push(row)
    else map.set(key, [row])
  }
  const groups = Array.from(map.entries()).map(([groupKey, workOrders]) => {
    const sampleGroups = groupInboundQcWorkOrders(workOrders)
    const head = workOrders[0]
    const source =
      (head?.receptionSource || 'cell_storage') === 'research_donation'
        ? 'research_donation'
        : 'cell_storage'
    return {
      groupKey,
      protocolId: (head?.collectionProtocolId || '').trim(),
      protocolNo: (head?.protocolNo || '').trim(),
      receptionSource: source as 'cell_storage' | 'research_donation',
      sampleGroups,
      workOrders
    }
  })
  return groups.sort(
    (a, b) => protocolGroupSortTime(b).localeCompare(protocolGroupSortTime(a))
  )
}

/** 协议组进度:已配置样本数 / 样本总数(按 gate 工单 workOrderStatus !== dpz) */
export function countConfiguredSamplesInProtocolGroup(
  group: QcWorkOrderProtocolGroupModel
): { configured: number; total: number } {
  const total = group.sampleGroups.length
  let configured = 0
  for (const sg of group.sampleGroups) {
    const gate = resolveGateWorkOrderForSampleGroup(sg.workOrders)
    if (gate && gate.workOrderStatus !== 'dpz') configured += 1
  }
  return { configured, total }
}
  • Step 2: 本地 smoke(Node REPL 或临时 script,可选)

在 web-admin 目录用两条 mock 工单(同 collectionProtocolId、不同 sampleCode)调用 groupInboundQcWorkOrdersByProtocol,期望返回 1 个 group、sampleGroups.length === 2。

  • Step 3: Commit
git add web-admin/src/utils/qc-work-order-groups.ts
git commit -m "feat(web-admin): add inbound QC work order protocol grouping utils"

Task 2: 协议组头 card-meta helper

Files:

  • Modify: web-admin/src/utils/qc-work-order-card-meta.ts

  • [ ] Step 1: 增加 import 与 helper

文件顶部 import 区增加:

import {
  countConfiguredSamplesInProtocolGroup,
  type QcWorkOrderProtocolGroupModel
} from '@/utils/qc-work-order-groups'
import type { QcProtocolPaymentSummary } from '@/api/qc-work-order'

在 buildDonationGroupContext 之后追加:

export function buildProtocolGroupProgressLabel(
  group: QcWorkOrderProtocolGroupModel
): string {
  const { configured, total } = countConfiguredSamplesInProtocolGroup(group)
  if (total <= 0) return ''
  if (configured <= 0) return ''
  return `${configured}/${total} 已配置`
}

export function resolveProtocolGroupStatus(row: QcWorkOrder): {
  label: string
  tagType: TagType
} {
  const status = String(row.protocolStatus || '')
  const source = row.receptionSource || 'cell_storage'
  return {
    label: protocolStatusLabelBySource(status, source),
    tagType: protocolStatusTagTypeBySource(status, source)
  }
}

/** 协议组级费用摘要:取组内首条有 paymentSummary 的 cell_storage 工单 */
export function resolveProtocolGroupPaymentSummary(
  group: QcWorkOrderProtocolGroupModel
): QcProtocolPaymentSummary | null {
  if (group.receptionSource !== 'cell_storage') return null
  for (const wo of group.workOrders) {
    if (wo.paymentSummary) return wo.paymentSummary
  }
  return null
}
  • Step 2: Commit
git add web-admin/src/utils/qc-work-order-card-meta.ts
git commit -m "feat(web-admin): add protocol group card meta helpers for inbound QC"

Task 3: SampleGroup / SingleCard 支持隐藏组头与卡片级协议费用

Files:

  • Modify: web-admin/src/views/quality-inspection/qc-work-order/QcWorkOrderSampleGroup.vue
  • Modify: web-admin/src/views/quality-inspection/qc-work-order/QcWorkOrderSingleCard.vue

  • [ ] Step 1: QcWorkOrderSampleGroup.vue — props + 条件渲染

defineProps 增加:

hideContextBar?: boolean
hideProtocolFeeSummary?: boolean

模板中 qc-sample-row__ctx 外层加 v-if="!hideContextBar"。

QcWorkOrderSingleCard 传参增加:

:hide-protocol-fee-summary="hideProtocolFeeSummary"
  • Step 2: QcWorkOrderSingleCard.vue — prop + computed

props 增加:

hideProtocolFeeSummary?: boolean

修改 showProtocolFeeSummary computed:

const showProtocolFeeSummary = computed(
  () =>
    !props.hideProtocolFeeSummary &&
    effectiveCardMeta.value.showProtocolFeeSummary &&
    !!props.row.paymentSummary
)
  • Step 3: 浏览器快速确认

打开细胞存储 Tab,在改动 CardList 前行为应不变(默认 prop false)。

  • Step 4: Commit
git add web-admin/src/views/quality-inspection/qc-work-order/QcWorkOrderSampleGroup.vue \
  web-admin/src/views/quality-inspection/qc-work-order/QcWorkOrderSingleCard.vue
git commit -m "feat(web-admin): allow hiding sample context bar and card-level protocol fee"

Task 4: 新建 QcWorkOrderProtocolGroup.vue

Files:

  • Create: web-admin/src/views/quality-inspection/qc-work-order/QcWorkOrderProtocolGroup.vue

  • [ ] Step 1: 创建组件(完整骨架)

<!-- 质检工单 · 同协议多样本分组(协议组头 + 样本 Tab + SampleGroup) -->
<template>
  <article class="qc-protocol-row">
    <div class="qc-protocol-row__ctx">
      <span class="qc-protocol-row__idx">{{ groupIndex }}</span>
      <span class="qc-protocol-row__code">{{ group.protocolNo || '—' }}</span>
      <el-tag v-if="protocolStatus.label" :type="protocolStatus.tagType" size="small">
        {{ protocolStatus.label }}
      </el-tag>
      <span class="qc-protocol-row__sub">
        {{ sampleCountLabel }}
        <template v-if="progressLabel"> · {{ progressLabel }}</template>
      </span>
      <span v-if="group.workOrders.length > 1" class="qc-protocol-row__badge">
        {{ group.workOrders.length }} 条质检
      </span>
    </div>

    <QcWorkOrderProtocolPaymentPanel
      v-if="protocolPaymentSummary"
      :summary="protocolPaymentSummary"
      compact
      class="qc-protocol-row__fee"
    />

    <div
      v-if="group.sampleGroups.length > 1"
      class="qc-protocol-sample-tabs"
    >
      <div class="qc-protocol-sample-tabs__row" role="tablist" aria-label="同协议样本">
        <button
          v-for="(sampleGroup, tabIndex) in group.sampleGroups"
          :key="sampleTabKey(sampleGroup)"
          type="button"
          role="tab"
          class="qc-wo-indep-tabs__btn"
          :class="{ 'is-active': sampleTabKey(sampleGroup) === activeSampleKey }"
          :aria-selected="sampleTabKey(sampleGroup) === activeSampleKey"
          @click="activeSampleKey = sampleTabKey(sampleGroup)"
        >
          <span class="qc-wo-tab__index">{{ tabIndex + 1 }}</span>
          <span class="qc-wo-tab__no">{{ sampleGroup.sampleCode || '—' }}</span>
          <span v-if="sampleTypeLabel(sampleGroup)" class="qc-wo-tab__source qc-wo-tab__source--reception">
            {{ sampleTypeLabel(sampleGroup) }}
          </span>
          <el-tag
            v-if="gateWorkOrder(sampleGroup)"
            :type="workOrderStatusTagType(gateWorkOrder(sampleGroup)!.workOrderStatus)"
            size="small"
            class="qc-wo-tab__status"
          >
            {{ workOrderStatusLabel(gateWorkOrder(sampleGroup)!.workOrderStatus) }}
          </el-tag>
        </button>
      </div>
    </div>

    <QcWorkOrderSampleGroup
      v-if="activeSampleGroup"
      :group="activeSampleGroup"
      :group-index="groupIndex"
      :card-meta-config="cardMetaConfig"
      :task-refresh-token="taskRefreshToken"
      :show-work-order-ops="showWorkOrderOps"
      :show-work-order-view-only="showWorkOrderViewOnly"
      :task-list-mode="taskListMode"
      :preset-tasks-by-work-order-id="presetTasksByWorkOrderId"
      hide-context-bar
      :hide-protocol-fee-summary="!!protocolPaymentSummary"
      @configure="(row) => emit('configure', row)"
      @view="(row) => emit('view', row)"
      @command="(cmd, row) => emit('command', cmd, row)"
      @tasks-changed="emit('tasks-changed')"
    />
  </article>
</template>

<script setup lang="ts">
  import { ref, computed, watch } from 'vue'
  import type { QcWorkOrder } from '@/api/qc-work-order'
  import type { QcTask } from '@/api/qc-task'
  import { workOrderStatusLabel } from '@/utils/qc-labels'
  import type { TagType } from '@/utils/status-tag-registry'
  import {
    type QcWorkOrderProtocolGroupModel,
    type QcWorkOrderSampleGroupModel,
    resolveGateWorkOrderForSampleGroup,
    resolveInboundSampleGroupKey
  } from '@/utils/qc-work-order-groups'
  import type { QcWorkOrderCardMetaConfig } from '@/utils/qc-work-order-card-meta'
  import {
    buildProtocolGroupProgressLabel,
    resolveProtocolGroupPaymentSummary,
    resolveProtocolGroupStatus
  } from '@/utils/qc-work-order-card-meta'
  import QcWorkOrderSampleGroup from './QcWorkOrderSampleGroup.vue'
  import QcWorkOrderProtocolPaymentPanel from './QcWorkOrderProtocolPaymentPanel.vue'

  defineOptions({ name: 'QcWorkOrderProtocolGroup' })

  const props = withDefaults(
    defineProps<{
      group: QcWorkOrderProtocolGroupModel
      groupIndex: number
      cardMetaConfig: QcWorkOrderCardMetaConfig
      taskRefreshToken: number
      showWorkOrderOps?: boolean
      showWorkOrderViewOnly?: boolean
      taskListMode?: 'workOrder' | 'mine'
      presetTasksByWorkOrderId?: Record<string, QcTask[]>
    }>(),
    {
      showWorkOrderOps: true,
      showWorkOrderViewOnly: false,
      taskListMode: 'workOrder'
    }
  )

  const emit = defineEmits<{
    configure: [row: QcWorkOrder]
    view: [row: QcWorkOrder]
    command: [cmd: string, row: QcWorkOrder]
    'tasks-changed': []
  }>()

  function sampleTabKey(sampleGroup: QcWorkOrderSampleGroupModel) {
    return sampleGroup.groupKey || resolveInboundSampleGroupKey(sampleGroup.workOrders[0])
  }

  const activeSampleKey = ref(
    props.group.sampleGroups[0] ? sampleTabKey(props.group.sampleGroups[0]) : ''
  )

  watch(
    () => props.group.sampleGroups.map((g) => sampleTabKey(g)).join(','),
    () => {
      const keys = props.group.sampleGroups.map((g) => sampleTabKey(g))
      if (!keys.includes(activeSampleKey.value)) {
        activeSampleKey.value = keys[0] || ''
      }
    }
  )

  const activeSampleGroup = computed(() =>
    props.group.sampleGroups.find((g) => sampleTabKey(g) === activeSampleKey.value)
  )

  const gateRow = computed(() =>
    resolveGateWorkOrderForSampleGroup(
      props.group.sampleGroups[0]?.workOrders || props.group.workOrders
    )
  )

  const protocolStatus = computed(() =>
    gateRow.value
      ? resolveProtocolGroupStatus(gateRow.value)
      : { label: '', tagType: 'info' as TagType }
  )

  const sampleCountLabel = computed(() => `${props.group.sampleGroups.length} 个样本`)

  const progressLabel = computed(() => buildProtocolGroupProgressLabel(props.group))

  const protocolPaymentSummary = computed(() =>
    resolveProtocolGroupPaymentSummary(props.group)
  )

  function sampleTypeLabel(sampleGroup: QcWorkOrderSampleGroupModel) {
    const gate = resolveGateWorkOrderForSampleGroup(sampleGroup.workOrders)
    return (gate?.sampleTypeName || sampleGroup.workOrders[0]?.sampleTypeName || '').trim()
  }

  function gateWorkOrder(sampleGroup: QcWorkOrderSampleGroupModel) {
    return resolveGateWorkOrderForSampleGroup(sampleGroup.workOrders)
  }

  function workOrderStatusTagType(status: string): TagType {
    if (status === 'ywc') return 'success'
    if (status === 'ypz') return 'warning'
    if (status === 'yzf') return 'danger'
    return 'info'
  }
</script>
  • Step 2: 追加 scoped SCSS

在 </script> 后追加样式(复用 QcWorkOrderSampleGroup 的 pill Tab 类名,协议组头对齐 qc-sample-row__ctx):

<style scoped lang="scss">
  .qc-protocol-row {
    display: flex;
    flex-direction: column;
    min-width: 0;
    border: 1px solid #e2e8f0;
    border-radius: 10px;
    background: #fff;
    overflow: hidden;
  }

  .qc-protocol-row__ctx {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 8px 12px;
    padding: 12px 16px;
    background: linear-gradient(180deg, var(--primary-bg, #ecfdf5) 0%, #fff 100%);
    border-bottom: 1px solid #d1fae5;
  }

  .qc-protocol-row__idx {
    flex-shrink: 0;
    font-size: 12px;
    color: var(--mint-text-muted, #64748b);
    min-width: 20px;
  }

  .qc-protocol-row__code {
    font-size: 14px;
    font-weight: 600;
    color: #1e293b;
    font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
  }

  .qc-protocol-row__sub {
    flex: 1;
    min-width: 0;
    font-size: 12px;
    color: var(--mint-text-muted, #64748b);
  }

  .qc-protocol-row__badge {
    flex-shrink: 0;
    font-size: 11px;
    font-weight: 600;
    padding: 2px 10px;
    border-radius: 999px;
    background: var(--primary-bg, #ecfdf5);
    color: #047857;
    border: 1px solid #d1fae5;
  }

  .qc-protocol-row__fee {
    border-bottom: 1px solid #e2e8f0;
  }

  .qc-protocol-sample-tabs {
    padding: 12px 16px;
    border-bottom: 1px solid #e2e8f0;
    background: #f8fafc;
  }

  .qc-protocol-sample-tabs__row {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 24px;
    max-width: 100%;
  }

  /* 将 QcWorkOrderSampleGroup.vue 中 .qc-wo-indep-tabs__btn 及相关 pill Tab 样式复制到此处 */
</style>
  • Step 3: Commit
git add web-admin/src/views/quality-inspection/qc-work-order/QcWorkOrderProtocolGroup.vue
git commit -m "feat(web-admin): add protocol group card with sample tabs for inbound QC"

Task 5: QcWorkOrderCardList 接入协议分组

Files:

  • Modify: web-admin/src/views/quality-inspection/qc-work-order/QcWorkOrderCardList.vue

  • [ ] Step 1: import 与 groupedRows 分支

script 区:

import {
  groupInboundQcWorkOrders,
  groupInboundQcWorkOrdersByProtocol,
  groupOutboundQcWorkOrders,
  buildAllTabListItems,
  type QcWorkOrderAllTabListItem
} from '@/utils/qc-work-order-groups'
import QcWorkOrderProtocolGroup from './QcWorkOrderProtocolGroup.vue'

新增 computed:

const protocolGroupedRows = computed(() => {
  if (!cardMetaUsesInboundGroups(props.cardMetaConfig.tabKey)) return []
  return groupInboundQcWorkOrdersByProtocol(props.rows)
})
  • Step 2: 模板 — inbound 分支改用 ProtocolGroup

将 useGroupedList 分支中:

<QcWorkOrderSampleGroup
  v-for="(group, idx) in groupedRows"

替换为:

<QcWorkOrderProtocolGroup
  v-if="cardMetaUsesInboundGroups(cardMetaConfig.tabKey)"
  v-for="(group, idx) in protocolGroupedRows"
  :key="group.groupKey"
  :group="group"
  :group-index="cardIndex(idx)"
  ...
/>
<QcWorkOrderSampleGroup
  v-else
  v-for="(group, idx) in groupedRows"
  ...
/>

或更简洁:groupedRows computed 内 inbound 返回空数组,单独 protocolGroupedRows 仅 inbound 使用;outbound 仍用 groupedRows。

推荐改 groupedRows:

const groupedRows = computed(() => {
  if (cardMetaUsesOutboundGroups(props.cardMetaConfig.tabKey)) {
    return groupOutboundQcWorkOrders(props.rows)
  }
  return []
})

inbound 专用 protocolGroupedRows 见 Step 1。

  • Step 3: 浏览器验证 manage 页

URL: http://localhost:3006/#/quality-group/quality-manage/qc-work-order?tab=inbound_cell_storage

期望:同协议多样本 → 1 张协议卡 + 样本 Tab;单样本 → 无样本 Tab 行。

  • Step 4: Commit
git add web-admin/src/views/quality-inspection/qc-work-order/QcWorkOrderCardList.vue
git commit -m "feat(web-admin): render inbound QC lists as protocol groups with sample tabs"

Task 6: 关键词 placeholder + mine 前端过滤

Files:

  • Modify: web-admin/src/utils/qc-work-order-tabs.ts
  • Modify: web-admin/src/utils/qc-mine-task-groups.ts

  • [ ] Step 1: keywordPlaceholderForTab

将 inbound 默认 return 改为:

if (tab.key === 'inbound_research_donation') {
  return '工单号/捐赠单号/样本编码/入库编码'
}
if (tab.group === 'inbound') {
  return '工单号/协议号/样本编码/入库编码'
}

(放在 tab.group === 'outbound' 判断之前,且 inbound_research_donation 先于 general inbound。)

  • Step 2: keywordMatchesWorkOrder inbound 字段

将 inbound 组 fields 数组改为:

: [row.workOrderNo, row.protocolNo, row.sampleCode, row.labStorageCode]
  • Step 3: Commit
git add web-admin/src/utils/qc-work-order-tabs.ts web-admin/src/utils/qc-mine-task-groups.ts
git commit -m "feat(web-admin): support protocol number in inbound QC keyword filters"

Task 7: 后端列表关键词 — protocol_no

Files:

  • Modify: backend/app/crud/qc_work_order.py

  • [ ] Step 1: 在 list_work_orders 关键词 OR 中追加

定位 _filtered() 内(约 L1272):

keyword_conditions = [
    BizQcWorkOrder.work_order_no.like(like),
    BizQcWorkOrder.sample_code.like(like),
    BizQcWorkOrder.protocol_no.like(like),
]
  • Step 2: 手动 API 验证
cd backend && source venv/bin/activate
# 假设本地服务已起;替换 TOKEN 与协议号
curl -s "http://127.0.0.1:8000/api/web/admin/qc-work-orders?page=1&pageSize=10&workOrderType=inbound&receptionSource=cell_storage&keyword=XY2026" \
  -H "Authorization: Bearer <token>" | python -m json.tool | head -40

期望:返回列表中 protocolNo 含关键词的工单。

  • Step 3: Commit
git add backend/app/crud/qc_work_order.py
git commit -m "feat(backend): include protocol number in QC work order list keyword search"

Task 8: 联调回归与文档收尾

Files:

  • Modify: docs/superpowers/specs/2026-06-25-qc-work-order-protocol-group-design.md(状态 → 已实现)

  • [ ] Step 1: 前端 lint

cd web-admin && pnpm lint

期望:无新增 error。

  • Step 2: 手工回归清单(两页)
# 场景 页面 期望
1 同协议 2+ 样本 质检工单 · 细胞存储 1 协议卡,样本 Tab 可切换
2 同协议 1 样本 同上 无样本 Tab,直接工单区
3 同样本 接收+生产 同上 内层触发来源 Tab 正常
4 协议费用 细胞存储 组头一次,卡片内不重复
5 科研捐赠 科研捐赠 Tab 无费用条,捐赠单号组头
6 关键词 两页 协议号/捐赠单号可搜
7 所有 Tab 两页 仍平铺,无协议分组
8 出库/临时 Tab 质检工单 布局不变
9 我的质检任务 mine · 细胞存储 与 manage 分组一致,仅查看
  • Step 3: 更新知识图谱
./scripts/update-graph.sh frontend
./scripts/update-graph.sh backend
  • Step 4: 更新 spec 状态

docs/superpowers/specs/2026-06-25-qc-work-order-protocol-group-design.md 头部 状态:已实现。

  • Step 5: Commit
git add docs/superpowers/specs/2026-06-25-qc-work-order-protocol-group-design.md
git commit -m "docs: mark inbound QC protocol group spec as implemented"

Spec Coverage Checklist

Spec § Task
§2 三层结构 / 分组键 Task 1
§3.1 协议组头 Task 2, 4
§3.2 样本 Tab Task 4
§3.3 hideContextBar + 内层 SampleGroup Task 3, 4
§3 协议费用组级一次 Task 2, 3, 4
§4.4 所有 Tab 不变 不修改 buildAllTabListItems
§4.5 mine 共用 + protocolNo 关键词 Task 5, 6
§5.1 后端 protocol_no Task 7
§5.2 placeholder Task 6
§7 验收 1–9 Task 8

Plan complete

Saved to docs/superpowers/plans/2026-06-25-qc-work-order-protocol-group.md.

Two execution options:

  1. Subagent-Driven (recommended) — 按 Task 1→8 派发子 agent,每 Task 完成后 review
  2. Inline Execution — 本会话内用 executing-plans 连续实现,Task 5 完成后浏览器 checkpoint

需要哪种方式开始写代码?