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
| 文件 | 职责 |
|---|---|
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、去掉无效「待审核」选项 |
reviewPhase 规范化与列表过滤Files:
backend/app/crud/storage_release_review.pybackend/tests/test_storage_release_review_phase.pyModify: 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
cd backend && source venv/bin/activate && pytest tests/test_storage_release_review_phase.py -v
Expected: FAIL(normalize_review_phase 未定义或导入失败)
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。
修改 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))
cd backend && source venv/bin/activate && pytest tests/test_storage_release_review_phase.py -v
Expected: PASS(2 passed)
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
)"
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
})
}
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
)"
Files:
web-admin/src/views/quality-inspection/storage-release-review/index.vueReference: 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>
删除:
<el-option label="待审核" value="" />
保留 clearable + placeholder「全部」+ reviewConclusionOptions(通过/不通过)。
在 <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。
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 刷新。
启动后端与 web-admin(pnpm dev),打开 /#/market/cell-storage/storage-release-review:
./scripts/update-graph.sh all
编辑 docs/superpowers/specs/2026-07-16-storage-release-review-tabs-design.md 头部:
> 状态:已确认
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 要求 | 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 通过