2026-07-16-storage-release-review-tabs.md 12 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,服务端按 reviewPhase 过滤并正确分页,默认进入待审核。

Architecture: 列表 API 增加可选参数 reviewPhase=pending|reviewed;CRUD list_reviews 按 review_conclusion 是否为空过滤。前端对齐样本接收的 panel-pill-tabs,切 Tab 时重置页码与勾选、保留筛选,请求始终带对应 reviewPhase。审核/入库弹窗与业务规则不改。

Tech Stack: FastAPI + SQLAlchemy(backend)、Vue 3 + Element Plus(web-admin)

Spec: docs/superpowers/specs/2026-07-16-storage-release-review-tabs-design.md


File Map

文件 职责
backend/app/crud/storage_release_review.py normalize_review_phase + list_reviews 阶段过滤
backend/app/api/storage_release_review.py 列表接口增加 reviewPhase Query
backend/tests/test_storage_release_review_phase.py 新建 阶段参数规范化单测
web-admin/src/api/storage-release-review.ts 列表 params 增加 reviewPhase
web-admin/src/views/quality-inspection/storage-release-review/index.vue pill Tabs、切 Tab、请求带 phase、去掉无效「待审核」选项

Task 1: Backend — reviewPhase 规范化与列表过滤

Files:

  • Modify: backend/app/crud/storage_release_review.py
  • Create: backend/tests/test_storage_release_review_phase.py
  • Modify: backend/app/api/storage_release_review.py

  • [ ] Step 1: 写失败单测(规范化函数)

创建 backend/tests/test_storage_release_review_phase.py:

from app.crud.storage_release_review import normalize_review_phase


def test_normalize_review_phase_accepts_pending_and_reviewed():
    assert normalize_review_phase("pending") == "pending"
    assert normalize_review_phase("reviewed") == "reviewed"
    assert normalize_review_phase(" PENDING ") == "pending"
    assert normalize_review_phase("Reviewed") == "reviewed"


def test_normalize_review_phase_rejects_unknown():
    assert normalize_review_phase(None) is None
    assert normalize_review_phase("") is None
    assert normalize_review_phase("all") is None
    assert normalize_review_phase("tg") is None
  • Step 2: 跑单测确认失败
cd backend && source venv/bin/activate && pytest tests/test_storage_release_review_phase.py -v

Expected: FAIL(normalize_review_phase 未定义或导入失败)

  • Step 3: 实现 normalize_review_phase 并在 list_reviews 中应用过滤

在 backend/app/crud/storage_release_review.py 中,于 list_reviews 之前增加:

def normalize_review_phase(review_phase: Optional[str]) -> Optional[str]:
    """Return 'pending' | 'reviewed' or None when absent/invalid."""
    p = (review_phase or "").strip().lower()
    if p in ("pending", "reviewed"):
        return p
    return None

修改 list_reviews 签名,增加 review_phase: Optional[str] = None(放在 keyword 与 user_info 之间):

def list_reviews(
    db: Session,
    *,
    tenant_no: str,
    page: int = 1,
    page_size: int = 10,
    reception_source: Optional[str] = None,
    review_conclusion: Optional[str] = None,
    storage_status: Optional[str] = None,
    keyword: Optional[str] = None,
    review_phase: Optional[str] = None,
    user_info=None,
) -> Dict[str, Any]:

在现有 reception_source 过滤之后、review_conclusion 过滤之前插入:

    phase = normalize_review_phase(review_phase)
    if phase == "pending":
        q0 = q0.filter(
            or_(
                BizStorageReleaseReview.review_conclusion.is_(None),
                BizStorageReleaseReview.review_conclusion == "",
            )
        )
    elif phase == "reviewed":
        q0 = q0.filter(
            BizStorageReleaseReview.review_conclusion.isnot(None),
            BizStorageReleaseReview.review_conclusion != "",
        )

说明:or_ 已在文件顶部从 sqlalchemy 导入,无需新增 import。

  • Step 4: API 增加 Query 并传入 CRUD

修改 backend/app/api/storage_release_review.py 的 list_storage_release_reviews:

@router.get("/storage-release-reviews")
async def list_storage_release_reviews(
    page: int = Query(1, ge=1),
    pageSize: int = Query(10, ge=1, le=100),
    keyword: Optional[str] = None,
    receptionSource: Optional[str] = None,
    reviewConclusion: Optional[str] = None,
    storageStatus: Optional[str] = None,
    reviewPhase: Optional[str] = None,
    db: Session = Depends(get_db),
    user: dict = Depends(require_menu_code("biz:storageReleaseReview:list")),
):
    try:
        tenant_no = _get_tenant_no(db, user)
        result = srr_svc.list_reviews(
            db,
            tenant_no=tenant_no,
            page=page,
            page_size=pageSize,
            reception_source=receptionSource,
            review_conclusion=reviewConclusion,
            storage_status=storageStatus,
            keyword=keyword,
            review_phase=reviewPhase,
            user_info=user,
        )
        return success_response(result)
    except PermissionError as e:
        return error_response(403, str(e))
    except Exception as e:
        return error_response(400, str(e))
  • Step 5: 跑单测确认通过
cd backend && source venv/bin/activate && pytest tests/test_storage_release_review_phase.py -v

Expected: PASS(2 passed)

  • Step 6: Commit
git add backend/app/crud/storage_release_review.py \
  backend/app/api/storage_release_review.py \
  backend/tests/test_storage_release_review_phase.py
git commit -m "$(cat <<'EOF'
feat: add reviewPhase filter for storage release review list

EOF
)"

Task 2: Frontend API 类型

Files:

  • Modify: web-admin/src/api/storage-release-review.ts

  • [ ] Step 1: 列表 params 增加 reviewPhase

将 fetchStorageReleaseReviewList 的 params 类型改为:

export function fetchStorageReleaseReviewList(params?: {
  page?: number
  pageSize?: number
  receptionSource?: string
  reviewConclusion?: string
  storageStatus?: string
  keyword?: string
  reviewPhase?: 'pending' | 'reviewed'
}) {
  return request.get<PageList<StorageReleaseReview>>({
    url: '/web/admin/storage-release-reviews',
    params
  })
}
  • Step 2: Commit
git add web-admin/src/api/storage-release-review.ts
git commit -m "$(cat <<'EOF'
feat: add reviewPhase param to storage release review list API

EOF
)"

Task 3: 列表页双 Tab UI 与请求

Files:

  • Modify: web-admin/src/views/quality-inspection/storage-release-review/index.vue
  • Reference: web-admin/src/views/quality-inspection/sample-reception/index.vue(pill tabs 结构)

  • [ ] Step 1: 在 panel-top 之后插入 pill Tabs

在 </div>(panel-top 结束)与 <div class="filter-block"> 之间插入:

      <div class="panel-pill-tabs panel-pill-tabs--lg">
        <div class="panel-pill-tabs__track" role="tablist">
          <button
            v-for="tab in STATUS_TABS"
            :key="tab.name"
            type="button"
            role="tab"
            class="panel-pill-tabs__btn"
            :class="{ 'is-active': activeTab === tab.name }"
            :aria-selected="activeTab === tab.name"
            @click="handleTabChange(tab.name)"
          >
            {{ tab.label }}
          </button>
        </div>
      </div>
  • Step 2: 去掉「审核结论」下拉中的无效「待审核」选项

删除:

                <el-option label="待审核" value="" />

保留 clearable + placeholder「全部」+ reviewConclusionOptions(通过/不通过)。

  • Step 3: 增加 Tab 状态与切换逻辑

在 <script setup> 中、searchForm 之前增加:

  type ReviewTab = 'pending' | 'reviewed'

  const STATUS_TABS = [
    { name: 'pending' as const, label: '待审核' },
    { name: 'reviewed' as const, label: '已审核' }
  ]

  const activeTab = ref<ReviewTab>('pending')

  function handleTabChange(name: ReviewTab) {
    if (activeTab.value === name) return
    activeTab.value = name
    pagination.page = 1
    selectedRows.value = []
    loadData()
  }

注意:handleTabChange 使用了 pagination / selectedRows / loadData,须放在这些声明之后;或先声明 activeTab/STATUS_TABS,把 handleTabChange 放在 loadData 之后。推荐顺序:STATUS_TABS + activeTab → searchForm / pagination / selectedRows → loadData → handleTabChange。

  • Step 4: loadData 请求带上 reviewPhase
  async function loadData() {
    loading.value = true
    try {
      const res = await fetchStorageReleaseReviewList({
        page: pagination.page,
        pageSize: pagination.pageSize,
        receptionSource: searchForm.receptionSource || undefined,
        reviewConclusion: searchForm.reviewConclusion || undefined,
        storageStatus: searchForm.storageStatus || undefined,
        keyword: searchForm.keyword || undefined,
        reviewPhase: activeTab.value
      })
      tableData.value = res.list || []
      pagination.total = res.total ?? 0
    } finally {
      loading.value = false
    }
  }

handleReset 不要重置 activeTab(仅清空筛选);重置后仍在当前 Tab 刷新。

  • Step 5: 本地手测清单

启动后端与 web-admin(pnpm dev),打开 /#/market/cell-storage/storage-release-review:

  1. 默认「待审核」高亮;列表行审核结论均为「待审核」
  2. 切到「已审核」:仅通过/不通过;用「审核结论」再筛有效
  3. 切 Tab:页码回 1、勾选清空、筛选保留
  4. 待审核提交审核后该行消失;已审核可再改结论;入库按钮行为不变
  • Step 6: 更新图谱(前端 + 后端)
./scripts/update-graph.sh all
  • Step 7: 将 design spec 状态改为已确认

编辑 docs/superpowers/specs/2026-07-16-storage-release-review-tabs-design.md 头部:

> 状态:已确认
  • Step 8: Commit
git add web-admin/src/views/quality-inspection/storage-release-review/index.vue \
  docs/superpowers/specs/2026-07-16-storage-release-review-tabs-design.md \
  backend/graphify-out web-admin/graphify-out
git commit -m "$(cat <<'EOF'
feat: split storage release review list into pending and reviewed tabs

EOF
)"

(若图谱无变更可从 git add 中去掉 graphify-out 路径。)


Spec Coverage Checklist

Spec 要求 Task
双 Tab 待审核/已审核 Task 3
默认待审核 Task 3 activeTab = 'pending'
已审核含 tg+btg,可再筛结论 Task 1 phase + Task 3 筛选
已审核仍可改结论 不改弹窗;操作列保持「审核」
两 Tab 筛选项相同 Task 3(仅去掉无效待审核 option)
reviewPhase API + 缺省兼容 Task 1
非法 phase 忽略 Task 1 normalize_review_phase
切 Tab 重置页码/勾选、保留筛选 Task 3 handleTabChange
审核后刷新当前 Tab 现有 onReviewSubmitted → loadData
不做角标/不改弹窗/不改菜单 范围外,无任务

完成定义

  • pytest tests/test_storage_release_review_phase.py 通过
  • 页面双 Tab 行为符合 spec §6 验收要点
  • 相关 commit 已完成(若执行时用户要求提交)