33 KiB
四页面新增功能实施计划
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: 为4个页面(入库质检工单、质检任务、入库生产工单、生产任务)新增"手动创建"功能,支持用户独立新增实体记录。
Architecture: 后端新增3个手动创建接口(生产任务已有),前端4个页面各自增加新增按钮和表单弹窗。工单/任务不再强依赖上游实体(样本接收记录/工单),通过空字符串默认值兼容现有 DB schema。
Tech Stack: FastAPI + Pydantic v2 (后端), Vue 3 + Element Plus (前端)
初始状态约定:
- 质检工单 →
dpz(待配置) - 生产工单 →
dpz(待配置) - 质检任务 →
dzx(待执行) - 生产任务 →
dpz(待配置)
File Structure
Backend (新建)
| 文件 | 职责 |
|---|---|
backend/app/schemas/qc_work_order.py |
质检工单 CreateBody schema |
backend/app/schemas/qc_task.py |
质检任务 CreateBody schema |
backend/app/schemas/production_work_order.py |
修改 追加 CreateBody schema |
Backend (修改)
| 文件 | 职责 |
|---|---|
backend/app/crud/qc_work_order.py |
新增 create_qc_work_order_manual() |
backend/app/crud/qc_task.py |
新增 create_qc_task_manual() |
backend/app/crud/production_work_order.py |
新增 create_production_work_order_manual() |
backend/app/api/qc.py |
新增 POST /qc-work-orders 和 POST /qc-tasks 路由 |
backend/app/api/production.py |
新增 POST /production-work-orders 路由 |
Frontend (修改)
| 文件 | 职责 |
|---|---|
web-admin/src/api/qc-work-order.ts |
新增 fetchCreateQcWorkOrder() |
web-admin/src/api/qc-task.ts |
新增 fetchCreateQcTask() |
web-admin/src/api/production-work-order.ts |
新增 fetchCreateProductionWorkOrder() |
web-admin/src/views/quality-inspection/qc-work-order/index.vue |
新增按钮 + 创建弹窗 |
web-admin/src/views/quality-inspection/qc-task/index.vue |
新增按钮 + 创建弹窗 |
web-admin/src/views/production/production-work-order/index.vue |
新增按钮 + 创建弹窗 |
web-admin/src/views/production/production-task/index.vue |
新增按钮 + 工单选择 + 复用 DispatchTaskDialog |
Task 1: 质检工单 — 后端手动创建接口
Files:
- Create:
backend/app/schemas/qc_work_order.py - Modify:
backend/app/crud/qc_work_order.py - Modify:
backend/app/api/qc.py
Step 1: 创建 Pydantic CreateBody schema
创建 backend/app/schemas/qc_work_order.py:
from pydantic import BaseModel, ConfigDict, Field
from typing import Optional
class QcWorkOrderCreateBody(BaseModel):
"""手动创建质检工单"""
model_config = ConfigDict(populate_by_name=True)
protocol_no: Optional[str] = Field(None, alias="protocolNo", description="协议号")
sample_code: Optional[str] = Field(None, alias="sampleCode", description="样本编码")
reception_source: Optional[str] = Field("cell_storage", alias="receptionSource", description="来源类型")
qc_biz_type: Optional[str] = Field("inbound", alias="qcBizType", description="质检业务类型")
remark: Optional[str] = Field(None, description="备注")
- 创建 schema 文件
Step 2: CRUD 层新增手动创建方法
在 backend/app/crud/qc_work_order.py 末尾新增:
def create_qc_work_order_manual(
db: Session,
*,
tenant_no: str,
user_id: Optional[str] = None,
create_dept_id: str = "",
protocol_no: str = "",
sample_code: str = "",
reception_source: str = "cell_storage",
qc_biz_type: str = "inbound",
remark: str = "",
) -> BizQcWorkOrder:
"""手动创建质检工单(不关联接收记录)"""
wo = BizQcWorkOrder(
id=generate_uuid(),
work_order_no=_gen_daily_no(db, tenant_no, "ZJ"),
reception_id="",
protocol_sample_id="",
collection_protocol_id="",
protocol_no=protocol_no,
sample_code=sample_code,
reception_source=reception_source,
qc_biz_type=qc_biz_type,
work_order_status="dpz",
has_lab="0",
has_third_party="0",
tenant_no=tenant_no,
remark=remark,
create_by=user_id,
create_dept_id=create_dept_id,
)
db.add(wo)
db.flush()
return wo
- 在 qc_work_order.py 末尾新增
create_qc_work_order_manual
Step 3: API 路由新增 POST 端点
在 backend/app/api/qc.py 的 @router.get("/qc-work-orders") 之前(约第549行前),新增:
@router.post("/qc-work-orders")
async def create_qc_work_order(
body: QcWorkOrderCreateBody,
db: Session = Depends(get_db),
user: dict = Depends(require_menu_code("biz:qcWorkOrder:list")),
):
try:
tenant_no = _get_tenant_no(db, user)
wo = wo_svc.create_qc_work_order_manual(
db,
tenant_no=tenant_no,
user_id=user.get("user_id"),
create_dept_id=user.get("dept_id", ""),
protocol_no=body.protocol_no or "",
sample_code=body.sample_code or "",
reception_source=body.reception_source or "cell_storage",
qc_biz_type=body.qc_biz_type or "inbound",
remark=body.remark or "",
)
db.commit()
return success_response(wo_svc._wo_to_dict(wo), message="创建成功")
except PermissionError as e:
return error_response(403, str(e))
except Exception as e:
db.rollback()
return error_response(400, str(e))
同时在文件顶部 import:
from app.schemas.qc_work_order import QcWorkOrderCreateBody
- 新增 POST 路由和 import
Step 4: 验证
- 启动后端,访问
/docs,确认POST /api/web/admin/qc-work-orders出现
Task 2: 质检任务 — 后端手动创建接口
Files:
- Create:
backend/app/schemas/qc_task.py - Modify:
backend/app/crud/qc_task.py - Modify:
backend/app/api/qc.py
Step 1: 创建 Pydantic CreateBody schema
创建 backend/app/schemas/qc_task.py:
from pydantic import BaseModel, ConfigDict, Field
from typing import Optional
class QcTaskCreateBody(BaseModel):
"""手动创建质检任务"""
model_config = ConfigDict(populate_by_name=True)
task_type: str = Field(..., alias="taskType", description="检测类型 sysz/dsfjc")
item_code: str = Field(..., alias="itemCode", description="项目编码")
item_name: str = Field(..., alias="itemName", description="项目名称")
company_id: Optional[str] = Field(None, alias="companyId", description="第三方公司ID")
company_name: Optional[str] = Field(None, alias="companyName", description="第三方公司名")
fee_amount: Optional[float] = Field(None, alias="feeAmount", description="费用")
sample_code: Optional[str] = Field(None, alias="sampleCode", description="样本编码")
qc_biz_type: Optional[str] = Field("inbound", alias="qcBizType", description="质检业务类型")
work_order_id: Optional[str] = Field(None, alias="workOrderId", description="关联工单ID(可选)")
remark: Optional[str] = Field(None, description="备注")
- 创建 schema 文件
Step 2: CRUD 层新增手动创建方法
在 backend/app/crud/qc_task.py 末尾新增:
def create_qc_task_manual(
db: Session,
*,
tenant_no: str,
user_id: Optional[str] = None,
create_dept_id: str = "",
task_type: str = "sysz",
item_code: str = "",
item_name: str = "",
company_id: Optional[str] = None,
company_name: Optional[str] = None,
fee_amount: Optional[float] = None,
sample_code: str = "",
work_order_id: str = "",
remark: str = "",
) -> BizQcTask:
"""手动创建质检任务(可独立于工单)"""
# 生成任务号:ZJRW + 日期 + 序号
day = datetime.now().strftime("%Y%m%d")
like = f"ZJRW{day}%"
cnt = db.query(BizQcTask).filter(
BizQcTask.tenant_no == tenant_no,
BizQcTask.task_no.like(like),
).count()
task_no = f"ZJRW{day}{cnt + 1:04d}"
task = BizQcTask(
id=generate_uuid(),
work_order_id=work_order_id or "",
task_no=task_no,
task_type=task_type,
source_line_id="",
sample_code=sample_code,
item_code=item_code,
item_name=item_name,
company_id=company_id,
company_name=company_name,
fee_amount=fee_amount,
task_status="dzx",
tenant_no=tenant_no,
remark=remark,
create_by=user_id,
create_dept_id=create_dept_id,
)
db.add(task)
db.flush()
return task
注意:需要在文件顶部确认已 import datetime, generate_uuid, BizQcTask。
- 在 qc_task.py 末尾新增
create_qc_task_manual
Step 3: API 路由新增 POST 端点
在 backend/app/api/qc.py 的 @router.get("/qc-tasks") 之前,新增:
@router.post("/qc-tasks")
async def create_qc_task(
body: QcTaskCreateBody,
db: Session = Depends(get_db),
user: dict = Depends(require_menu_code("biz:qcTask:list")),
):
try:
tenant_no = _get_tenant_no(db, user)
task = task_svc.create_qc_task_manual(
db,
tenant_no=tenant_no,
user_id=user.get("user_id"),
create_dept_id=user.get("dept_id", ""),
task_type=body.task_type,
item_code=body.item_code,
item_name=body.item_name,
company_id=body.company_id,
company_name=body.company_name,
fee_amount=body.fee_amount,
sample_code=body.sample_code or "",
work_order_id=body.work_order_id or "",
remark=body.remark or "",
)
db.commit()
return success_response(task_svc._task_to_dict(task), message="创建成功")
except PermissionError as e:
return error_response(403, str(e))
except Exception as e:
db.rollback()
return error_response(400, str(e))
同时在文件顶部 import:
from app.schemas.qc_task import QcTaskCreateBody
- 新增 POST 路由和 import
Step 4: 验证
- 启动后端,访问
/docs,确认POST /api/web/admin/qc-tasks出现
Task 3: 生产工单 — 后端手动创建接口
Files:
- Modify:
backend/app/schemas/production_work_order.py - Modify:
backend/app/crud/production_work_order.py - Modify:
backend/app/api/production.py
Step 1: 新增 CreateBody schema
在 backend/app/schemas/production_work_order.py 中追加:
class ProductionWorkOrderCreate(BaseModel):
"""手动创建生产工单"""
model_config = ConfigDict(populate_by_name=True)
protocol_no: Optional[str] = Field(None, alias="protocolNo", description="协议号")
sample_code: Optional[str] = Field(None, alias="sampleCode", description="样本编码")
reception_source: Optional[str] = Field("cell_storage", alias="receptionSource", description="来源类型")
remark: Optional[str] = Field(None, description="备注")
- 追加 schema
Step 2: CRUD 层新增手动创建方法
在 backend/app/crud/production_work_order.py 末尾新增:
def create_production_work_order_manual(
db: Session,
*,
tenant_no: str,
user_id: Optional[str] = None,
create_dept_id: str = "",
protocol_no: str = "",
sample_code: str = "",
reception_source: str = "cell_storage",
remark: str = "",
) -> BizProductionWorkOrder:
"""手动创建生产工单(不关联接收记录)"""
wo = BizProductionWorkOrder(
id=generate_uuid(),
work_order_no=_generate_work_order_no(db, tenant_no),
reception_id="",
protocol_sample_id="",
collection_protocol_id="",
protocol_no=protocol_no,
sample_code=sample_code,
reception_source=reception_source,
work_order_status="dpz",
tenant_no=tenant_no,
remark=remark,
create_by=user_id,
create_dept_id=create_dept_id,
)
db.add(wo)
db.flush()
return wo
- 新增
create_production_work_order_manual
Step 3: API 路由新增 POST 端点
在 backend/app/api/production.py 的 @router.get("/production-work-orders") 之前,新增:
@router.post("/production-work-orders")
async def create_production_work_order(
body: ProductionWorkOrderCreate,
user: dict = Depends(require_admin_or_super_admin),
db: Session = Depends(get_db),
):
try:
tenant_no = _get_tenant_no(db, user)
wo = production_work_order_svc.create_production_work_order_manual(
db,
tenant_no=tenant_no,
user_id=user.get("user_id"),
create_dept_id=user.get("dept_id", ""),
protocol_no=body.protocol_no or "",
sample_code=body.sample_code or "",
reception_source=body.reception_source or "cell_storage",
remark=body.remark or "",
)
db.commit()
return success_response(production_work_order_svc._wo_to_dict(wo), message="创建成功")
except ValueError as e:
return error_response(400, str(e))
except Exception as e:
db.rollback()
return error_response(400, str(e))
同时在文件顶部 import:
from app.schemas.production_work_order import ProductionWorkOrderCreate
注意:检查 production_work_order_svc 是否已 import,以及是否需要 import require_admin_or_super_admin(应已有)。
- 新增 POST 路由和 import
Step 4: 验证
- 启动后端,访问
/docs,确认POST /api/web/admin/production-work-orders出现
Task 4: 前端 API 层 — 新增创建函数
Files:
- Modify:
web-admin/src/api/qc-work-order.ts - Modify:
web-admin/src/api/qc-task.ts - Modify:
web-admin/src/api/production-work-order.ts
注意: production-task.ts 已有 fetchCreateProductionTask(),无需修改。
Step 1: 质检工单 API
在 web-admin/src/api/qc-work-order.ts 末尾追加:
export function fetchCreateQcWorkOrder(data: {
protocolNo?: string
sampleCode?: string
receptionSource?: string
qcBizType?: string
remark?: string
}) {
return request.post<QcWorkOrder>({
url: '/web/admin/qc-work-orders',
params: data
})
}
- 追加
fetchCreateQcWorkOrder
Step 2: 质检任务 API
在 web-admin/src/api/qc-task.ts 末尾追加:
export function fetchCreateQcTask(data: {
taskType: string
itemCode: string
itemName: string
companyId?: string
companyName?: string
feeAmount?: number
sampleCode?: string
qcBizType?: string
workOrderId?: string
remark?: string
}) {
return request.post<QcTask>({
url: '/web/admin/qc-tasks',
params: data
})
}
- 追加
fetchCreateQcTask
Step 3: 生产工单 API
在 web-admin/src/api/production-work-order.ts 末尾追加:
export function fetchCreateProductionWorkOrder(data: {
protocolNo?: string
sampleCode?: string
receptionSource?: string
remark?: string
}) {
return request.post<ProductionWorkOrderItem>({
url: '/web/admin/production-work-orders',
params: data
})
}
- 追加
fetchCreateProductionWorkOrder
Task 5: 质检工单页面 — 新增弹窗
Files:
- Modify:
web-admin/src/views/quality-inspection/qc-work-order/index.vue
参考模板页: src/views/system/unit/index.vue 的 handleAdd / dialog 模式
Step 1: 在 <script setup> 中添加新增相关状态和方法
在 defineOptions 之后,const searchForm 之前,添加:
import { Plus } from '@element-plus/icons-vue'
import { fetchCreateQcWorkOrder } from '@/api/qc-work-order'
const addVisible = ref(false)
const addLoading = ref(false)
const addFormRef = ref()
const addForm = reactive({
protocolNo: '',
sampleCode: '',
receptionSource: 'cell_storage',
qcBizType: 'inbound',
remark: ''
})
const addRules = {
receptionSource: [{ required: true, message: '请选择来源类型', trigger: 'change' }],
qcBizType: [{ required: true, message: '请选择业务类型', trigger: 'change' }]
}
function handleAdd() {
addForm.protocolNo = ''
addForm.sampleCode = ''
addForm.receptionSource = 'cell_storage'
addForm.qcBizType = 'inbound'
addForm.remark = ''
addVisible.value = true
}
async function submitAdd() {
await addFormRef.value?.validate()
addLoading.value = true
try {
await fetchCreateQcWorkOrder({ ...addForm })
ElMessage.success('创建成功')
addVisible.value = false
loadData()
} finally {
addLoading.value = false
}
}
- 添加新增逻辑
Step 2: 在 panel-top 中添加新增按钮
将模板中:
<div class="panel-top">
<h2>工单列表</h2>
</div>
改为:
<div class="panel-top">
<h2>工单列表</h2>
<el-button class="btn-main" type="primary" @click="handleAdd">
<el-icon><Plus /></el-icon>新增
</el-button>
</div>
- 添加新增按钮
Step 3: 在 </section> 之前添加新增弹窗
在 SystemViewDialog 的 </template> 之后、</section> 之前添加:
<!-- 新增质检工单弹窗 -->
<el-dialog
v-model="addVisible"
title="新增入库质检工单"
width="560px"
class="dialog-form-layout"
align-center
destroy-on-close
:close-on-click-modal="false"
>
<el-form ref="addFormRef" :model="addForm" :rules="addRules" label-position="top">
<div class="form-grid-2">
<el-form-item label="协议号">
<el-input v-model="addForm.protocolNo" placeholder="请输入协议号(可选)" clearable />
</el-form-item>
<el-form-item label="样本编码">
<el-input v-model="addForm.sampleCode" placeholder="请输入样本编码(可选)" clearable />
</el-form-item>
<el-form-item label="来源类型" prop="receptionSource">
<el-select v-model="addForm.receptionSource" style="width: 100%">
<el-option label="细胞存储" value="cell_storage" />
<el-option label="科研捐赠" value="research_donation" />
</el-select>
</el-form-item>
<el-form-item label="业务类型" prop="qcBizType">
<el-select v-model="addForm.qcBizType" style="width: 100%">
<el-option label="入库质检" value="inbound" />
<el-option label="出库质检" value="outbound" />
</el-select>
</el-form-item>
<el-form-item label="备注" class="form-col-span-2">
<el-input v-model="addForm.remark" type="textarea" :rows="3" placeholder="选填" maxlength="512" />
</el-form-item>
</div>
</el-form>
<template #footer>
<el-button class="btn-ghost" @click="addVisible = false">
<el-icon><Close /></el-icon>取消
</el-button>
<el-button class="btn-main" type="primary" :loading="addLoading" @click="submitAdd">
<el-icon><Check /></el-icon>确认
</el-button>
</template>
</el-dialog>
- 添加新增弹窗
Task 6: 质检任务页面 — 新增弹窗
Files:
- Modify:
web-admin/src/views/quality-inspection/qc-task/index.vue
Step 1: 在 <script setup> 中添加新增相关状态和方法
在 defineOptions 之后添加:
import { fetchCreateQcTask } from '@/api/qc-task'
const addVisible = ref(false)
const addLoading = ref(false)
const addFormRef = ref()
const addForm = reactive({
taskType: 'sysz',
itemCode: '',
itemName: '',
companyId: '',
companyName: '',
feeAmount: undefined as number | undefined,
sampleCode: '',
qcBizType: 'inbound',
remark: ''
})
const addRules = {
taskType: [{ required: true, message: '请选择检测类型', trigger: 'change' }],
itemCode: [{ required: true, message: '请输入项目编码', trigger: 'blur' }],
itemName: [{ required: true, message: '请输入项目名称', trigger: 'blur' }]
}
function handleAdd() {
addForm.taskType = 'sysz'
addForm.itemCode = ''
addForm.itemName = ''
addForm.companyId = ''
addForm.companyName = ''
addForm.feeAmount = undefined
addForm.sampleCode = ''
addForm.qcBizType = 'inbound'
addForm.remark = ''
addVisible.value = true
}
async function submitAdd() {
await addFormRef.value?.validate()
addLoading.value = true
try {
const payload: Record<string, any> = {
taskType: addForm.taskType,
itemCode: addForm.itemCode,
itemName: addForm.itemName,
sampleCode: addForm.sampleCode || undefined,
qcBizType: addForm.qcBizType,
remark: addForm.remark || undefined
}
if (addForm.taskType === 'dsfjc') {
payload.companyId = addForm.companyId || undefined
payload.companyName = addForm.companyName || undefined
payload.feeAmount = addForm.feeAmount
}
await fetchCreateQcTask(payload)
ElMessage.success('创建成功')
addVisible.value = false
loadData()
} finally {
addLoading.value = false
}
}
- 添加新增逻辑
Step 2: 在 panel-top 中添加新增按钮
将:
<div class="panel-top">
<h2>任务列表</h2>
</div>
改为:
<div class="panel-top">
<h2>任务列表</h2>
<el-button class="btn-main" type="primary" @click="handleAdd">
<el-icon><Plus /></el-icon>新增
</el-button>
</div>
- 添加新增按钮
Step 3: 在 </section> 之前添加新增弹窗
在 SystemViewDialog 结束之后、</section> 之前添加:
<!-- 新增质检任务弹窗 -->
<el-dialog
v-model="addVisible"
title="新增质检任务"
width="640px"
class="dialog-form-layout"
align-center
destroy-on-close
:close-on-click-modal="false"
>
<el-form ref="addFormRef" :model="addForm" :rules="addRules" label-position="top">
<div class="form-grid-2">
<el-form-item label="检测类型" prop="taskType">
<el-select v-model="addForm.taskType" style="width: 100%">
<el-option label="实验室自检" value="sysz" />
<el-option label="第三方检测" value="dsfjc" />
</el-select>
</el-form-item>
<el-form-item label="业务类型">
<el-select v-model="addForm.qcBizType" style="width: 100%">
<el-option label="入库质检" value="inbound" />
<el-option label="出库质检" value="outbound" />
</el-select>
</el-form-item>
<el-form-item label="项目编码" prop="itemCode">
<el-input v-model="addForm.itemCode" placeholder="请输入项目编码" clearable />
</el-form-item>
<el-form-item label="项目名称" prop="itemName">
<el-input v-model="addForm.itemName" placeholder="请输入项目名称" clearable />
</el-form-item>
<template v-if="addForm.taskType === 'dsfjc'">
<el-form-item label="第三方公司ID">
<el-input v-model="addForm.companyId" placeholder="请输入公司ID(可选)" clearable />
</el-form-item>
<el-form-item label="第三方公司名称">
<el-input v-model="addForm.companyName" placeholder="请输入公司名称(可选)" clearable />
</el-form-item>
<el-form-item label="费用(元)">
<el-input-number v-model="addForm.feeAmount" :precision="2" :min="0" style="width: 100%" placeholder="选填" />
</el-form-item>
</template>
<el-form-item label="样本编码">
<el-input v-model="addForm.sampleCode" placeholder="请输入样本编码(可选)" clearable />
</el-form-item>
<el-form-item label="备注" class="form-col-span-2">
<el-input v-model="addForm.remark" type="textarea" :rows="3" placeholder="选填" maxlength="512" />
</el-form-item>
</div>
</el-form>
<template #footer>
<el-button class="btn-ghost" @click="addVisible = false">
<el-icon><Close /></el-icon>取消
</el-button>
<el-button class="btn-main" type="primary" :loading="addLoading" @click="submitAdd">
<el-icon><Check /></el-icon>确认
</el-button>
</template>
</el-dialog>
- 添加新增弹窗
Task 7: 生产工单页面 — 新增弹窗
Files:
- Modify:
web-admin/src/views/production/production-work-order/index.vue
Step 1: 在 <script setup> 中添加新增相关状态和方法
在 defineOptions 之后添加:
import { fetchCreateProductionWorkOrder } from '@/api/production-work-order'
const addVisible = ref(false)
const addLoading = ref(false)
const addFormRef = ref()
const addForm = reactive({
protocolNo: '',
sampleCode: '',
receptionSource: 'cell_storage',
remark: ''
})
const addRules = {
receptionSource: [{ required: true, message: '请选择来源类型', trigger: 'change' }]
}
function handleAdd() {
addForm.protocolNo = ''
addForm.sampleCode = ''
addForm.receptionSource = 'cell_storage'
addForm.remark = ''
addVisible.value = true
}
async function submitAdd() {
await addFormRef.value?.validate()
addLoading.value = true
try {
await fetchCreateProductionWorkOrder({ ...addForm })
ElMessage.success('创建成功')
addVisible.value = false
loadData()
} finally {
addLoading.value = false
}
}
- 添加新增逻辑
Step 2: 在 panel-top 中添加新增按钮
将:
<div class="panel-top">
<h2>工单列表</h2>
</div>
改为:
<div class="panel-top">
<h2>工单列表</h2>
<el-button class="btn-main" type="primary" @click="handleAdd">
<el-icon><Plus /></el-icon>新增
</el-button>
</div>
- 添加新增按钮
Step 3: 在 </section> 之前添加新增弹窗
在最后一个 el-dialog(任务列表弹窗)结束后、</section> 之前添加:
<!-- 新增生产工单弹窗 -->
<el-dialog
v-model="addVisible"
title="新增入库生产工单"
width="560px"
class="dialog-form-layout"
align-center
destroy-on-close
:close-on-click-modal="false"
>
<el-form ref="addFormRef" :model="addForm" :rules="addRules" label-position="top">
<div class="form-grid-2">
<el-form-item label="协议号">
<el-input v-model="addForm.protocolNo" placeholder="请输入协议号(可选)" clearable />
</el-form-item>
<el-form-item label="样本编码">
<el-input v-model="addForm.sampleCode" placeholder="请输入样本编码(可选)" clearable />
</el-form-item>
<el-form-item label="来源类型" prop="receptionSource">
<el-select v-model="addForm.receptionSource" style="width: 100%">
<el-option label="细胞存储" value="cell_storage" />
<el-option label="科研捐赠" value="research_donation" />
</el-select>
</el-form-item>
<el-form-item label="备注" class="form-col-span-2">
<el-input v-model="addForm.remark" type="textarea" :rows="3" placeholder="选填" maxlength="512" />
</el-form-item>
</div>
</el-form>
<template #footer>
<el-button class="btn-ghost" @click="addVisible = false">
<el-icon><Close /></el-icon>取消
</el-button>
<el-button class="btn-main" type="primary" :loading="addLoading" @click="submitAdd">
<el-icon><Check /></el-icon>确认
</el-button>
</template>
</el-dialog>
- 添加新增弹窗
Task 8: 生产任务页面 — 复用 DispatchTaskDialog
Files:
- Modify:
web-admin/src/views/production/production-task/index.vue
设计思路: 生产任务页面新增时,先弹出一个工单选择弹窗,用户选择工单后复用 DispatchTaskDialog 创建任务。
Step 1: 在 <script setup> 中添加新增相关状态和方法
在 defineOptions 之后添加:
import { Plus } from '@element-plus/icons-vue'
import { Close, Check } from '@element-plus/icons-vue'
import DispatchTaskDialog from '../production-work-order/DispatchTaskDialog.vue'
import { fetchProductionWorkOrderList } from '@/api/production-work-order'
// ── 新增任务(工单选择 → 复用 DispatchTaskDialog) ──
const addSelectWoVisible = ref(false)
const addSelectWoList = ref<any[]>([])
const addSelectWoLoading = ref(false)
const addSelectWoKeyword = ref('')
const addDispatchVisible = ref(false)
const addDispatchRow = ref<any>(null)
async function handleAdd() {
addSelectWoKeyword.value = ''
addSelectWoVisible.value = true
addSelectWoLoading.value = true
try {
const res = await fetchProductionWorkOrderList({ pageSize: 100 })
addSelectWoList.value = (res.list || []).filter(
(wo: any) => wo.workOrderStatus === 'dpz' || wo.workOrderStatus === 'jxz'
)
} catch {
addSelectWoList.value = []
} finally {
addSelectWoLoading.value = false
}
}
async function searchWorkOrders() {
addSelectWoLoading.value = true
try {
const res = await fetchProductionWorkOrderList({
keyword: addSelectWoKeyword.value || undefined,
pageSize: 100
})
addSelectWoList.value = (res.list || []).filter(
(wo: any) => wo.workOrderStatus === 'dpz' || wo.workOrderStatus === 'jxz'
)
} catch {
addSelectWoList.value = []
} finally {
addSelectWoLoading.value = false
}
}
function selectWorkOrder(row: any) {
addDispatchRow.value = row
addSelectWoVisible.value = false
addDispatchVisible.value = true
}
- 添加新增逻辑
Step 2: 在 panel-top 中添加新增按钮
将:
<div class="panel-top">
<h2>任务列表</h2>
</div>
改为:
<div class="panel-top">
<h2>任务列表</h2>
<el-button class="btn-main" type="primary" @click="handleAdd">
<el-icon><Plus /></el-icon>新增
</el-button>
</div>
- 添加新增按钮
Step 3: 在 </section> 之前添加工单选择弹窗和 DispatchTaskDialog
在 TaskExecutionDialog 之后、</section> 之前添加:
<!-- 选择工单弹窗 -->
<el-dialog
v-model="addSelectWoVisible"
title="选择生产工单"
width="800px"
align-center
destroy-on-close
>
<div style="margin-bottom: 12px">
<el-input
v-model="addSelectWoKeyword"
placeholder="搜索工单号/协议号/样本编码"
clearable
@keyup.enter="searchWorkOrders"
>
<template #append>
<el-button @click="searchWorkOrders"><el-icon><Search /></el-icon></el-button>
</template>
</el-input>
</div>
<el-table
:data="addSelectWoList"
v-loading="addSelectWoLoading"
stripe
style="width: 100%"
max-height="400"
@row-click="selectWorkOrder"
highlight-current-row
cursor-pointer
>
<el-table-column prop="workOrderNo" label="工单号" min-width="150" align="center" />
<el-table-column prop="protocolNo" label="协议号" min-width="140" align="center">
<template #default="{ row }">{{ row.protocolNo || '—' }}</template>
</el-table-column>
<el-table-column prop="sampleCode" label="样本编码" min-width="120" align="center">
<template #default="{ row }">{{ row.sampleCode || '—' }}</template>
</el-table-column>
<el-table-column prop="receptionSource" label="来源" min-width="100" align="center">
<template #default="{ row }">
<el-tag :type="row.receptionSource === 'research_donation' ? 'warning' : 'success'" size="small">
{{ row.receptionSourceLabel || (row.receptionSource === 'research_donation' ? '科研捐赠' : '细胞存储') }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="workOrderStatus" label="状态" min-width="100" align="center">
<template #default="{ row }">
<el-tag :type="row.workOrderStatus === 'jxz' ? 'warning' : 'info'" size="small">
{{ row.workOrderStatus === 'jxz' ? '进行中' : '待配置' }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" min-width="100" align="center" fixed="right">
<template #default="{ row }">
<el-button link type="primary" size="small" @click.stop="selectWorkOrder(row)">选择</el-button>
</template>
</el-table-column>
</el-table>
<el-empty v-if="!addSelectWoLoading && !addSelectWoList.length" description="暂无可选工单" :image-size="48" />
</el-dialog>
<!-- 复用 DispatchTaskDialog 新增任务 -->
<DispatchTaskDialog
v-model:visible="addDispatchVisible"
:work-order-row="addDispatchRow"
@success="loadData"
/>
- 添加工单选择弹窗和复用 DispatchTaskDialog
自检清单
实施完成后,逐项验证:
- 后端 3 个新 POST 接口在
/docs可见且可调通 - 前端 4 个页面各有"新增"按钮
- 质检工单新增弹窗:协议号、样本编码、来源类型、业务类型、备注
- 质检任务新增弹窗:检测类型、业务类型、项目编码/名称、第三方字段(条件显示)、样本编码、备注
- 生产工单新增弹窗:协议号、样本编码、来源类型、备注
- 生产任务新增弹窗:工单选择列表 → 选择后打开 DispatchTaskDialog
- 新增成功后列表自动刷新
pnpm build无错误- 后端启动无 import 错误