提交
This commit is contained in:
1 parent
04d462ad43
commit
7e9155fef1
7 files changed
+1754
-68
No files matched your search
@@ -0,0 +1,611 @@
|
||||
# 质检第三方按公司聚合卡片 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 (`- [x]`) syntax for tracking.
|
||||
|
||||
**Goal:** 质检工单 / 我的质检任务批次列表中,自检仍一项目一卡;第三方按「批次内公司」聚合成公司卡,弹窗内分项填参数/状态/结论,备注与检测材料一份并按可写集合同步写入。
|
||||
|
||||
**Architecture:** 纯前端改造。新增 `qc-company-task-group` 工具做分组与共享字段选取;新增 `QcCompanyTaskResultDialog` 做公司弹窗;在 `QcWorkOrderTaskSubList` 将任务拆成自检卡 + 公司卡并接线。保存循环调用现有 `fetchCompleteTaskWithResult`,不改后端表结构。
|
||||
|
||||
**Tech Stack:** Vue 3 + TypeScript + Element Plus(web-admin);单测用 `node:test` + `pnpm exec tsx --test`。
|
||||
|
||||
**Spec:** `docs/superpowers/specs/2026-08-28-qc-third-party-company-card-design.md`
|
||||
**Demo:** `.superpowers/brainstorm/80309-1787910046/content/001-qc-company-card-demo.html`
|
||||
|
||||
---
|
||||
|
||||
## File map
|
||||
|
||||
| 文件 | 职责 |
|
||||
|------|------|
|
||||
| Create: `web-admin/src/utils/qc-company-task-group.ts` | 第三方按公司分组、进度汇总、共享备注/附件选取 |
|
||||
| Create: `web-admin/src/utils/qc-company-task-group.test.ts` | 上述纯函数单测 |
|
||||
| Create: `web-admin/src/views/quality-inspection/qc-work-order/QcCompanyTaskResultDialog.vue` | 公司维度填写/查看弹窗 |
|
||||
| Modify: `web-admin/src/views/quality-inspection/qc-work-order/QcWorkOrderTaskSubList.vue` | 列表渲染拆分;打开公司弹窗;自检逻辑不动 |
|
||||
| 不改 | 后端 API / DDL;配置检测弹窗;附件 ZIP 目录 |
|
||||
|
||||
---
|
||||
|
||||
### Task 1: 分组与共享字段工具(TDD)
|
||||
|
||||
**Files:**
|
||||
- Create: `web-admin/src/utils/qc-company-task-group.ts`
|
||||
- Create: `web-admin/src/utils/qc-company-task-group.test.ts`
|
||||
|
||||
- [x] **Step 1: 写失败单测**
|
||||
|
||||
```ts
|
||||
// web-admin/src/utils/qc-company-task-group.test.ts
|
||||
import { describe, it } from 'node:test'
|
||||
import assert from 'node:assert/strict'
|
||||
import type { QcTask } from '@/api/qc-task'
|
||||
import {
|
||||
buildTaskListDisplayItems,
|
||||
pickSharedRemarkAndAttachments,
|
||||
companyGroupProgress
|
||||
} from './qc-company-task-group'
|
||||
|
||||
function task(partial: Partial<QcTask> & Pick<QcTask, 'id' | 'taskType'>): QcTask {
|
||||
return {
|
||||
taskNo: 'T1',
|
||||
itemCode: 'I',
|
||||
itemName: 'Item',
|
||||
taskStatus: 'dzx',
|
||||
workOrderId: 'wo1',
|
||||
...partial
|
||||
} as QcTask
|
||||
}
|
||||
|
||||
describe('buildTaskListDisplayItems', () => {
|
||||
it('keeps lab tasks as single cards and groups third-party by companyId', () => {
|
||||
const tasks = [
|
||||
task({ id: 'l1', taskType: 'sysz', itemName: '无菌' }),
|
||||
task({
|
||||
id: 't1',
|
||||
taskType: 'dsfjc',
|
||||
companyId: 'c1',
|
||||
companyName: '金域',
|
||||
itemName: '乙肝'
|
||||
}),
|
||||
task({
|
||||
id: 't2',
|
||||
taskType: 'dsfjc',
|
||||
companyId: 'c1',
|
||||
companyName: '金域',
|
||||
itemName: '丙肝'
|
||||
}),
|
||||
task({
|
||||
id: 't3',
|
||||
taskType: 'dsfjc',
|
||||
companyId: 'c2',
|
||||
companyName: '迪安',
|
||||
itemName: 'HIV'
|
||||
})
|
||||
]
|
||||
const items = buildTaskListDisplayItems(tasks)
|
||||
assert.equal(items.length, 3)
|
||||
assert.equal(items[0].kind, 'lab')
|
||||
assert.equal(items[0].kind === 'lab' && items[0].task.id, 'l1')
|
||||
assert.equal(items[1].kind, 'company')
|
||||
if (items[1].kind === 'company') {
|
||||
assert.equal(items[1].companyId, 'c1')
|
||||
assert.equal(items[1].tasks.length, 2)
|
||||
}
|
||||
assert.equal(items[2].kind, 'company')
|
||||
if (items[2].kind === 'company') {
|
||||
assert.equal(items[2].companyId, 'c2')
|
||||
}
|
||||
})
|
||||
|
||||
it('falls back to single lab-like card when third-party has empty companyId', () => {
|
||||
const tasks = [
|
||||
task({ id: 't0', taskType: 'dsfjc', companyId: '', companyName: '', itemName: '孤儿' })
|
||||
]
|
||||
const items = buildTaskListDisplayItems(tasks)
|
||||
assert.equal(items.length, 1)
|
||||
assert.equal(items[0].kind, 'orphan-third')
|
||||
})
|
||||
})
|
||||
|
||||
describe('companyGroupProgress', () => {
|
||||
it('counts ywc vs total', () => {
|
||||
const p = companyGroupProgress([
|
||||
task({ id: 'a', taskType: 'dsfjc', taskStatus: 'ywc' }),
|
||||
task({ id: 'b', taskType: 'dsfjc', taskStatus: 'dzx' })
|
||||
])
|
||||
assert.deepEqual(p, { completed: 1, total: 2 })
|
||||
})
|
||||
})
|
||||
|
||||
describe('pickSharedRemarkAndAttachments', () => {
|
||||
it('picks latest non-empty remark and attachments by updateTime', () => {
|
||||
const picked = pickSharedRemarkAndAttachments([
|
||||
task({
|
||||
id: 'a',
|
||||
taskType: 'dsfjc',
|
||||
remark: '旧备注',
|
||||
thirdPartyAttachments: 'http://a.pdf',
|
||||
updateTime: '2026-08-01 10:00:00',
|
||||
attachmentFiles: [{ fileUrl: 'http://a.pdf', fileName: 'a.pdf' }]
|
||||
}),
|
||||
task({
|
||||
id: 'b',
|
||||
taskType: 'dsfjc',
|
||||
remark: '新备注',
|
||||
thirdPartyAttachments: '',
|
||||
updateTime: '2026-08-28 12:00:00',
|
||||
attachmentFiles: []
|
||||
})
|
||||
])
|
||||
assert.equal(picked.remark, '新备注')
|
||||
assert.equal(picked.thirdPartyAttachments, 'http://a.pdf')
|
||||
assert.equal(picked.attachmentFiles?.length, 1)
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
- [x] **Step 2: 跑测确认失败**
|
||||
|
||||
```bash
|
||||
cd web-admin && pnpm exec tsx --test src/utils/qc-company-task-group.test.ts
|
||||
```
|
||||
|
||||
Expected: FAIL(模块不存在)
|
||||
|
||||
- [x] **Step 3: 实现工具**
|
||||
|
||||
```ts
|
||||
// web-admin/src/utils/qc-company-task-group.ts
|
||||
import type { QcTask } from '@/api/qc-task'
|
||||
|
||||
export type QcTaskListLabItem = { kind: 'lab'; task: QcTask }
|
||||
export type QcTaskListOrphanThirdItem = { kind: 'orphan-third'; task: QcTask }
|
||||
export type QcTaskListCompanyItem = {
|
||||
kind: 'company'
|
||||
companyId: string
|
||||
companyName: string
|
||||
tasks: QcTask[]
|
||||
}
|
||||
export type QcTaskListDisplayItem =
|
||||
| QcTaskListLabItem
|
||||
| QcTaskListOrphanThirdItem
|
||||
| QcTaskListCompanyItem
|
||||
|
||||
function trimId(v?: string) {
|
||||
return (v || '').trim()
|
||||
}
|
||||
|
||||
/** 自检卡在前(原序);有 companyId 的第三方按首次出现公司顺序分组;无 companyId 降级为 orphan-third */
|
||||
export function buildTaskListDisplayItems(tasks: QcTask[]): QcTaskListDisplayItem[] {
|
||||
const lab: QcTaskListDisplayItem[] = []
|
||||
const orphans: QcTaskListDisplayItem[] = []
|
||||
const companyOrder: string[] = []
|
||||
const companyMap = new Map<string, QcTaskListCompanyItem>()
|
||||
|
||||
for (const t of tasks || []) {
|
||||
if ((t.taskType || '').trim() === 'sysz') {
|
||||
lab.push({ kind: 'lab', task: t })
|
||||
continue
|
||||
}
|
||||
if ((t.taskType || '').trim() !== 'dsfjc') {
|
||||
lab.push({ kind: 'lab', task: t })
|
||||
continue
|
||||
}
|
||||
const cid = trimId(t.companyId)
|
||||
if (!cid) {
|
||||
orphans.push({ kind: 'orphan-third', task: t })
|
||||
continue
|
||||
}
|
||||
let g = companyMap.get(cid)
|
||||
if (!g) {
|
||||
g = {
|
||||
kind: 'company',
|
||||
companyId: cid,
|
||||
companyName: (t.companyName || '').trim() || '第三方公司',
|
||||
tasks: []
|
||||
}
|
||||
companyMap.set(cid, g)
|
||||
companyOrder.push(cid)
|
||||
}
|
||||
g.tasks.push(t)
|
||||
}
|
||||
|
||||
return [
|
||||
...lab,
|
||||
...companyOrder.map((id) => companyMap.get(id)!),
|
||||
...orphans
|
||||
]
|
||||
}
|
||||
|
||||
export function companyGroupProgress(tasks: QcTask[]) {
|
||||
const list = tasks || []
|
||||
const completed = list.filter((t) => (t.taskStatus || '').trim() === 'ywc').length
|
||||
return { completed, total: list.length }
|
||||
}
|
||||
|
||||
function timeMs(v?: string) {
|
||||
if (!v) return 0
|
||||
const n = Date.parse(String(v).replace(/-/g, '/'))
|
||||
return Number.isFinite(n) ? n : 0
|
||||
}
|
||||
|
||||
export function pickSharedRemarkAndAttachments(tasks: QcTask[]): {
|
||||
remark: string
|
||||
thirdPartyAttachments: string
|
||||
attachmentFiles: NonNullable<QcTask['attachmentFiles']>
|
||||
} {
|
||||
const list = [...(tasks || [])].sort((a, b) => timeMs(b.updateTime) - timeMs(a.updateTime))
|
||||
let remark = ''
|
||||
let thirdPartyAttachments = ''
|
||||
let attachmentFiles: NonNullable<QcTask['attachmentFiles']> = []
|
||||
for (const t of list) {
|
||||
if (!remark && (t.remark || '').trim()) remark = (t.remark || '').trim()
|
||||
const urls = (t.thirdPartyAttachments || '').trim()
|
||||
const files = t.attachmentFiles || []
|
||||
if (!thirdPartyAttachments && (urls || files.length)) {
|
||||
thirdPartyAttachments = urls
|
||||
attachmentFiles = files.length ? [...files] : []
|
||||
}
|
||||
if (remark && (thirdPartyAttachments || attachmentFiles.length)) break
|
||||
}
|
||||
return { remark, thirdPartyAttachments, attachmentFiles }
|
||||
}
|
||||
|
||||
export function uniqueAssigneeNames(tasks: QcTask[]): string {
|
||||
const names: string[] = []
|
||||
const seen = new Set<string>()
|
||||
for (const t of tasks || []) {
|
||||
const raw = (t.assigneeUserNames || '').trim()
|
||||
if (!raw) continue
|
||||
for (const part of raw.split(/[,,、]/)) {
|
||||
const n = part.trim()
|
||||
if (!n || seen.has(n)) continue
|
||||
seen.add(n)
|
||||
names.push(n)
|
||||
}
|
||||
}
|
||||
return names.join('、') || '—'
|
||||
}
|
||||
```
|
||||
|
||||
- [x] **Step 4: 跑测确认通过**
|
||||
|
||||
```bash
|
||||
cd web-admin && pnpm exec tsx --test src/utils/qc-company-task-group.test.ts
|
||||
```
|
||||
|
||||
Expected: PASS
|
||||
|
||||
- [x] **Step 5: Commit**(仅当用户要求提交时执行)
|
||||
|
||||
```bash
|
||||
git add web-admin/src/utils/qc-company-task-group.ts web-admin/src/utils/qc-company-task-group.test.ts
|
||||
git commit -m "$(cat <<'EOF'
|
||||
feat(qc): add third-party task grouping helpers by company
|
||||
|
||||
EOF
|
||||
)"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 2: 公司结果弹窗组件
|
||||
|
||||
**Files:**
|
||||
- Create: `web-admin/src/views/quality-inspection/qc-work-order/QcCompanyTaskResultDialog.vue`
|
||||
|
||||
- [x] **Step 1: 实现弹窗骨架与 props**
|
||||
|
||||
对齐现网 `QcWorkOrderTaskSubList` 单任务填写弹窗:
|
||||
|
||||
- `dialog-form-layout` + `align-center` + `append-to-body` + `modal-class="protocol-dialog-mask"`
|
||||
- Props:
|
||||
- `visible`(v-model)
|
||||
- `companyName: string`
|
||||
- `tasks: QcTask[]`(可写/可见集合)
|
||||
- `readonly?: boolean`
|
||||
- `operateScope: QcTaskOperateScope`
|
||||
- Emits:`update:visible`、`saved`
|
||||
|
||||
打开时:
|
||||
|
||||
1. 对每个 `task` 并行 `fetchQcTaskDetail` + `fetchQcTaskResults`
|
||||
2. 本地状态:`itemStates: Array<{ taskId, itemName, params: QcTaskResultItem[], saveStatus, conclusion }>`
|
||||
3. 共享:`remark`、`attachments`(`apiFilesToFinanceItems` + `pickSharedRemarkAndAttachments`)
|
||||
|
||||
模板结构(要点):
|
||||
|
||||
```vue
|
||||
<el-dialog
|
||||
:title="(readonly ? '查看检测结果' : '填写检测结果') + ' · ' + companyName"
|
||||
class="dialog-form-layout qc-company-task-result-dialog"
|
||||
...
|
||||
>
|
||||
<DialogFormBody :loading="loading">
|
||||
<el-form label-position="top" class="biz-dialog-form">
|
||||
<div v-for="item in itemStates" :key="item.taskId" class="company-item-block">
|
||||
<div class="company-item-block__head">
|
||||
<span>{{ item.itemName }}</span>
|
||||
<el-tag size="small" :type="taskStatusTagType(item.saveStatus)">
|
||||
{{ taskStatusLabel(item.saveStatus) }}
|
||||
</el-tag>
|
||||
</div>
|
||||
<div class="section-title">检测参数</div>
|
||||
<div class="form-grid-3"><!-- 同现网 paramType 分支 --></div>
|
||||
<div class="section-title">检测结论</div>
|
||||
<div class="form-grid-2 result-conclusion-row">
|
||||
<el-form-item label="任务状态" required>
|
||||
<el-radio-group v-model="item.saveStatus" :disabled="readonly">
|
||||
<el-radio value="dzx">待执行</el-radio>
|
||||
<el-radio value="ywc">已完成</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="结论" :required="item.saveStatus === 'ywc'">
|
||||
<el-radio-group v-model="item.conclusion" :disabled="readonly">
|
||||
<el-radio value="" :disabled="item.saveStatus === 'ywc'">待结论</el-radio>
|
||||
<el-radio value="tg">通过</el-radio>
|
||||
<el-radio value="btg">不通过</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-section">
|
||||
<div class="section-title">备注与检测材料</div>
|
||||
<el-form-item label="备注" class="form-col-span-2">
|
||||
<el-input v-model="remark" type="textarea" :rows="3" :disabled="readonly" />
|
||||
</el-form-item>
|
||||
<ProtocolFinanceConfirmFields
|
||||
v-model:finance-remark="remark"
|
||||
v-model:attachments="attachments"
|
||||
:show-remark="false"
|
||||
attachment-label="检测材料"
|
||||
:attachment-required="anyCompleted"
|
||||
upload-folder="qc-third-party"
|
||||
:disabled="readonly"
|
||||
/>
|
||||
</div>
|
||||
</el-form>
|
||||
</DialogFormBody>
|
||||
<template #footer>
|
||||
<el-button class="btn-ghost" @click="close"><el-icon><Close /></el-icon>取消</el-button>
|
||||
<el-button v-if="!readonly" class="btn-main" type="primary" :loading="saving" @click="submit">
|
||||
<el-icon><Check /></el-icon>保存
|
||||
</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
```
|
||||
|
||||
注意:`ProtocolFinanceConfirmFields` 若无 `disabled` prop,只读时用 `v-if` 换成只读附件列表(`ProtocolAttachmentReadonly`),与现网查看态一致即可。
|
||||
|
||||
- [x] **Step 2: 实现 submit**
|
||||
|
||||
校验顺序:
|
||||
|
||||
1. 附件上传中 → warning
|
||||
2. `anyCompleted` 且无有效附件 →「请上传检测材料」
|
||||
3. 任一 `saveStatus === 'ywc'` 且结论非 tg/btg →「请选择检测结论」(可带项目名)
|
||||
|
||||
然后顺序(或 `for...of`)调用:
|
||||
|
||||
```ts
|
||||
await fetchCompleteTaskWithResult(
|
||||
item.taskId,
|
||||
{
|
||||
conclusion: item.conclusion,
|
||||
results: item.params,
|
||||
remark,
|
||||
thirdPartyAttachments: financeAttachmentsToUrlString(attachments),
|
||||
taskStatus: item.saveStatus
|
||||
},
|
||||
operateScope
|
||||
)
|
||||
```
|
||||
|
||||
收集失败项:`failures: string[]`;若有失败 `ElMessage.error` 列出项目名;若全部成功 `ElMessage.success`、emit `saved`、关弹窗。部分成功也 emit `saved` 以便列表刷新。
|
||||
|
||||
复用标签 helper:从 `@/utils/qc-labels` 或 TaskSubList 现有 `taskStatusLabel` / `taskStatusTagType` / `parseQcParamOptions` 导入,**不要复制粘贴一大段标签映射**;若现函数未导出,把纯函数抽到 `qc-labels` 或本 dialog 旁小 util。
|
||||
|
||||
- [x] **Step 3: 本地目视**
|
||||
|
||||
`pnpm dev`,临时在任一页挂载组件用假数据打开(或等 Task 3 接线后验)。本任务以组件可编译、逻辑自洽为准。
|
||||
|
||||
- [x] **Step 4: Commit**(仅当用户要求提交时)
|
||||
|
||||
```bash
|
||||
git add web-admin/src/views/quality-inspection/qc-work-order/QcCompanyTaskResultDialog.vue
|
||||
git commit -m "$(cat <<'EOF'
|
||||
feat(qc): add company-level QC result dialog for third-party tasks
|
||||
|
||||
EOF
|
||||
)"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 3: 列表接线(QcWorkOrderTaskSubList)
|
||||
|
||||
**Files:**
|
||||
- Modify: `web-admin/src/views/quality-inspection/qc-work-order/QcWorkOrderTaskSubList.vue`
|
||||
|
||||
- [x] **Step 1: 计算 displayItems**
|
||||
|
||||
```ts
|
||||
import {
|
||||
buildTaskListDisplayItems,
|
||||
companyGroupProgress,
|
||||
uniqueAssigneeNames,
|
||||
type QcTaskListCompanyItem
|
||||
} from '@/utils/qc-company-task-group'
|
||||
import QcCompanyTaskResultDialog from './QcCompanyTaskResultDialog.vue'
|
||||
|
||||
const displayItems = computed(() => buildTaskListDisplayItems(tableData.value))
|
||||
```
|
||||
|
||||
- [x] **Step 2: 改卡片网格模板**
|
||||
|
||||
将原 `v-for="row in tableData"` 改为 `v-for="item in displayItems"`:
|
||||
|
||||
- `item.kind === 'lab' | 'orphan-third'`:沿用现有 `qc-task-card` 模板,数据用 `item.task`(orphan 第三方仍走原单任务「填写结果」弹窗,避免无公司维度)
|
||||
- `item.kind === 'company'`:新公司卡 UI(对齐 Demo)
|
||||
|
||||
公司卡要点:
|
||||
|
||||
```vue
|
||||
<div
|
||||
class="qc-task-card qc-task-card--compact qc-task-card--company"
|
||||
@click="openCompanyResult(item)"
|
||||
@dblclick="openCompanyResult(item)"
|
||||
>
|
||||
<div class="qc-task-card__line qc-task-card__line--top">
|
||||
<span class="qc-task-card__name">{{ item.companyName }}</span>
|
||||
<div class="qc-task-card__sub">第三方 · {{ item.tasks.length }} 个检测项目</div>
|
||||
<div class="qc-task-card__tags">
|
||||
<el-tag :type="companyProgressTagType(item)" size="small">
|
||||
{{ companyProgressText(item) }}
|
||||
</el-tag>
|
||||
<el-tag type="primary" size="small">第三方检测</el-tag>
|
||||
</div>
|
||||
</div>
|
||||
<div class="qc-task-card__line qc-task-card__line--bottom">
|
||||
<span class="qc-task-card__assignees">{{ uniqueAssigneeNames(item.tasks) }}</span>
|
||||
<el-button
|
||||
v-if="canFillCompany(item)"
|
||||
type="primary"
|
||||
size="small"
|
||||
class="btn-op-item primary"
|
||||
@click.stop="openCompanyResult(item, false)"
|
||||
>
|
||||
<el-icon><EditPen /></el-icon>填写结果
|
||||
</el-button>
|
||||
<el-button
|
||||
v-else
|
||||
type="primary"
|
||||
size="small"
|
||||
class="btn-op-item primary"
|
||||
@click.stop="openCompanyResult(item, true)"
|
||||
>
|
||||
<el-icon><View /></el-icon>查看
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
```ts
|
||||
function companyProgressText(item: QcTaskListCompanyItem) {
|
||||
const { completed, total } = companyGroupProgress(item.tasks)
|
||||
if (completed === 0) return '待执行'
|
||||
if (completed === total) return `${completed}/${total} 已完成`
|
||||
return `${completed}/${total} 已完成`
|
||||
}
|
||||
|
||||
function companyProgressTagType(item: QcTaskListCompanyItem) {
|
||||
const { completed, total } = companyGroupProgress(item.tasks)
|
||||
if (total > 0 && completed === total) return 'success'
|
||||
if (completed > 0) return 'warning'
|
||||
return 'info'
|
||||
}
|
||||
|
||||
function canFillCompany(item: QcTaskListCompanyItem) {
|
||||
return item.tasks.some((t) => hasPrimaryTaskActions(t) && canFillResult(t))
|
||||
}
|
||||
|
||||
const companyDialogVisible = ref(false)
|
||||
const companyDialogReadonly = ref(false)
|
||||
const companyDialogTasks = ref<QcTask[]>([])
|
||||
const companyDialogName = ref('')
|
||||
|
||||
function openCompanyResult(item: QcTaskListCompanyItem, forceReadonly?: boolean) {
|
||||
// mine:tasks 已是当前用户可见子集(列表本身已过滤);若工单页全量则可写=全部
|
||||
// 若 mine 模式下列表含非本人任务(通常不会),再 filter isCurrentUserAssignee
|
||||
let tasks = item.tasks
|
||||
if (listMode.value === 'mine') {
|
||||
tasks = tasks.filter((t) => isCurrentUserAssignee(t))
|
||||
}
|
||||
if (!tasks.length) return
|
||||
companyDialogName.value = item.companyName
|
||||
companyDialogTasks.value = tasks
|
||||
companyDialogReadonly.value =
|
||||
forceReadonly === true || !tasks.some((t) => hasPrimaryTaskActions(t) && canFillResult(t))
|
||||
companyDialogVisible.value = true
|
||||
}
|
||||
```
|
||||
|
||||
挂载:
|
||||
|
||||
```vue
|
||||
<QcCompanyTaskResultDialog
|
||||
v-model:visible="companyDialogVisible"
|
||||
:company-name="companyDialogName"
|
||||
:tasks="companyDialogTasks"
|
||||
:readonly="companyDialogReadonly"
|
||||
:operate-scope="operateScope"
|
||||
@saved="loadData(); emit('changed')"
|
||||
/>
|
||||
```
|
||||
|
||||
自检卡的开始 / 填写结果 / 查看 / 删除 **保持原函数**,不要改到公司路径。
|
||||
|
||||
- [x] **Step 3: 样式**
|
||||
|
||||
公司卡增加浅绿边框/浅底(`--primary-bg`),与 Demo 一致;勿引入小程序色 `#0a9f6d`。
|
||||
|
||||
- [x] **Step 4: 手动验收(对照 spec §8)**
|
||||
|
||||
在 `pnpm dev` 下验证:
|
||||
|
||||
1. 质检工单 `inbound_cell_storage`:自检卡数量不变;第三方变公司卡
|
||||
2. 打开公司弹窗:多参数三列;状态/结论单选
|
||||
3. 保存后同公司各任务备注/附件一致(工单页)
|
||||
4. 我的质检任务:仅本人负责项进弹窗与保存
|
||||
5. 自检「开始」仍可用
|
||||
|
||||
- [x] **Step 5: Commit**(仅当用户要求提交时)
|
||||
|
||||
```bash
|
||||
git add web-admin/src/views/quality-inspection/qc-work-order/QcWorkOrderTaskSubList.vue \
|
||||
web-admin/src/views/quality-inspection/qc-work-order/QcCompanyTaskResultDialog.vue \
|
||||
web-admin/src/utils/qc-company-task-group.ts \
|
||||
web-admin/src/utils/qc-company-task-group.test.ts
|
||||
git commit -m "$(cat <<'EOF'
|
||||
feat(qc): group third-party QC tasks by company on work-order cards
|
||||
|
||||
EOF
|
||||
)"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 4: 图谱更新与收尾
|
||||
|
||||
**Files:**
|
||||
- 运行:`./scripts/update-graph.sh frontend`(或 web-admin 对应入口)
|
||||
|
||||
- [x] **Step 1: 更新 graphify**
|
||||
|
||||
```bash
|
||||
cd /Users/wuzilong/work/00code/zkxw && ./scripts/update-graph.sh frontend
|
||||
```
|
||||
|
||||
- [x] **Step 2: 将 spec 状态改为已实现(实现完成后)**
|
||||
|
||||
把 `docs/superpowers/specs/2026-08-28-qc-third-party-company-card-design.md` 文首「待用户审阅」改为「已实现」并注明完成日期。
|
||||
|
||||
---
|
||||
|
||||
## Spec coverage checklist
|
||||
|
||||
| Spec 要求 | Task |
|
||||
|-----------|------|
|
||||
| 自检不合并 | Task 3 |
|
||||
| 第三方按公司聚合 | Task 1 + 3 |
|
||||
| 空 companyId 降级 | Task 1 |
|
||||
| 分项参数/状态/结论(现网单选) | Task 2 |
|
||||
| 共享备注/材料 + 任一项完成则材料必填 | Task 2 |
|
||||
| 工单全量同步 / 我的任务子集 | Task 2 + 3 |
|
||||
| 方案 A 循环 complete-with-result | Task 2 |
|
||||
| 打开回显最近非空共享字段 | Task 1 + 2 |
|
||||
| 不做配置检测/DDL/整公司删除 | 明确不在 File map |
|
||||
|
||||
## 执行说明
|
||||
|
||||
- 提交步骤默认 **跳过**,除非用户明确要求 commit。
|
||||
- 两页共用 `QcWorkOrderTaskSubList`,改一处即可覆盖质检工单与我的质检任务。
|
||||
Reference in new issue
Block a user