Files
zkxw/.sisyphus/plans/2025-05-24-add-create-for-qc-production.md
T
2026-05-24 16:58:51 +08:00

33 KiB
Raw Blame History

四页面新增功能实施计划

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 错误