feat: 实现产需单按工序步骤生成排产明细功能

1. 重构接收产需单接口,支持按配置工序步骤生成明细
2. 新增工艺、工艺步骤CRUD接口与管理页面
3. 新增工序选择组件
4. 优化产需单页面,增加历史记录功能
5. 为排产明细添加工序步骤名称展示
This commit is contained in:
2026-07-04 15:43:05 +08:00
parent ce09ac9da3
commit 2fde9ec993
12 changed files with 1529 additions and 427 deletions

View File

@@ -15,6 +15,7 @@
<el-button size="small" class="aps-btn-red" icon="el-icon-search" @click="handleQuery">查询</el-button>
<el-tabs v-model="activeTab" size="small" style="margin:0 0 0 16px;" @tab-click="handleQuery">
<el-tab-pane label="待审核" name="pending" />
<el-tab-pane label="已接收" name="accepted" />
<el-tab-pane label="已排产" name="scheduled" />
</el-tabs>
<div v-if="summaryText" class="aps-sch-summary">
@@ -161,6 +162,104 @@
</div>
</div>
<!-- 已接收 Tab -->
<div v-loading="acceptedLoading" v-show="activeTab === 'accepted'" class="detail-card aps-sch-card">
<div class="detail-card-header">
<span>已接收产需单</span>
<span v-if="acceptedScheduleList.length > 0" style="font-weight:normal;font-size:12px;opacity:0.8;">
{{ acceptedScheduleList.length }} 个产需单
</span>
</div>
<div class="detail-card-body" style="padding:0;">
<div v-if="acceptedScheduleList.length > 0" class="aps-sch-list">
<div v-for="sch in acceptedScheduleList" :key="sch.scheduleId" class="aps-sch-item">
<div class="aps-sch-item-header">
<div class="aps-sch-item-header-left">
<span class="aps-sch-no">{{ sch.scheduleNo }}</span>
<span class="aps-status-tag status-2">已接收</span>
<span class="aps-sch-header-extra">
{{ sch.customerName }} · {{ sch.businessUser }} · {{ sch.deliveryCycle }}
</span>
</div>
</div>
<table class="req-info-table">
<tbody>
<tr>
<td class="req-td-label">排产单号</td><td class="req-td-value">{{ sch.scheduleNo }}</td>
<td class="req-td-label">生产日期</td><td class="req-td-value">{{ sch.prodDate }}</td>
</tr>
<tr>
<td class="req-td-label">业务员</td><td class="req-td-value">{{ sch.businessUser }}</td>
<td class="req-td-label">联系电话</td><td class="req-td-value">{{ sch.businessPhone }}</td>
</tr>
<tr>
<td class="req-td-label">订货单位</td><td class="req-td-value">{{ sch.customerName }}</td>
<td class="req-td-label">交货期()</td><td class="req-td-value">{{ sch.deliveryCycle }}</td>
</tr>
<tr>
<td class="req-td-label">品名</td><td class="req-td-value" colspan="3">{{ sch.productType }}</td>
</tr>
<tr>
<td class="req-td-label">产品用途</td><td class="req-td-value" colspan="3">{{ sch.usePurpose }}</td>
</tr>
<tr>
<td class="req-td-label">厚度公差</td><td class="req-td-value">{{ sch.thicknessTolerance }}</td>
<td class="req-td-label">宽度公差</td><td class="req-td-value">{{ sch.widthTolerance }}</td>
</tr>
<tr>
<td class="req-td-label">表面质量</td><td class="req-td-value">{{ sch.surfaceQuality }}</td>
<td class="req-td-label">表面处理</td><td class="req-td-value">{{ sch.surfaceTreatment }}</td>
</tr>
<tr>
<td class="req-td-label">内径尺寸</td><td class="req-td-value">{{ sch.innerDiameter }}</td>
<td class="req-td-label">外径要求</td><td class="req-td-value">{{ sch.outerDiameter }}</td>
</tr>
<tr>
<td class="req-td-label">包装要求</td><td class="req-td-value">{{ sch.packReq }}</td>
<td class="req-td-label">切边要求</td><td class="req-td-value">{{ sch.cutEdgeReq }}</td>
</tr>
<tr>
<td class="req-td-label">单件重量</td><td class="req-td-value">{{ sch.singleCoilWeight }}</td>
<td class="req-td-label">交货重量偏差</td><td class="req-td-value">{{ sch.weightDeviation }}</td>
</tr>
<tr v-if="sch.otherTechReq">
<td class="req-td-label">其他技术要求</td><td class="req-td-value" colspan="3">{{ sch.otherTechReq }}</td>
</tr>
<tr v-if="sch.paymentDesc">
<td class="req-td-label">付款情况说明</td><td class="req-td-value" colspan="3">{{ sch.paymentDesc }}</td>
</tr>
<tr v-if="sch.remark">
<td class="req-td-label">备注</td><td class="req-td-value" colspan="3">{{ sch.remark }}</td>
</tr>
</tbody>
</table>
<div v-if="(sch.detailList || []).length > 0" class="aps-sch-item-details">
<div class="aps-sch-detail-header">
<span class="aps-sch-detail-col col-spec">规格</span>
<span class="aps-sch-detail-col col-material">材质</span>
<span class="aps-sch-detail-col col-weight">排产吨数</span>
<span class="aps-sch-detail-col col-type">品名</span>
<span class="aps-sch-detail-col col-remark">备注</span>
</div>
<div v-for="(d, di) in sch.detailList" :key="di" class="aps-sch-detail-row" @click="handleDetailClick(sch, d)">
<span class="aps-sch-detail-col col-spec">{{ d.spec }}</span>
<span class="aps-sch-detail-col col-material">{{ d.material }}</span>
<span class="aps-sch-detail-col col-weight">{{ d.scheduleWeight }}</span>
<span class="aps-sch-detail-col col-type">{{ sch.productType || d.productType || '' }}</span>
<span class="aps-sch-detail-col col-remark">{{ d.remark }}</span>
</div>
</div>
<div v-else class="aps-sch-item-empty">暂无明细</div>
</div>
</div>
<div v-else-if="!acceptedLoading" style="padding:40px;text-align:center;color:#909399;">
{{ hasQueried ? '该日期暂无已接收产需单' : '请选择日期查询' }}
</div>
</div>
</div>
<!-- 已排产 Tab -->
<div v-loading="schLoading" v-show="activeTab === 'scheduled'" class="detail-card aps-sch-card">
<div class="detail-card-header">
@@ -169,85 +268,73 @@
<span v-if="scheduledItemList.length > 0" style="font-weight:normal;font-size:12px;opacity:0.8;">
{{ scheduledItemList.length }}
</span>
<span v-if="selectedItems.length >= 2" style="margin-left: auto;">
<el-button
size="mini"
type="warning"
icon="el-icon-link"
@click.stop="handleMergePrepare"
>
合并选中项 ({{ selectedItems.length }})
</el-button>
</span>
<span v-else style="margin-left: auto;">
<el-button
size="mini"
type="warning"
icon="el-icon-link"
disabled
>
合并选中项
</el-button>
</span>
</div>
</div>
<div element-loading-text="正在加载已排产数据..." class="detail-card-body" style="padding:0;">
<el-table
v-if="scheduledItemList.length > 0"
ref="scheduledTable"
:data="scheduledItemList"
border
size="small"
class="aps-table"
:row-class-name="getItemRowClassName"
@selection-change="handleSelectionChange"
>
<el-table-column type="selection" width="45" align="center" />
<el-table-column label="排产单号" prop="scheduleNo" min-width="140" show-overflow-tooltip />
<el-table-column label="生产日期" prop="prodDate" width="110" align="center" show-overflow-tooltip />
<el-table-column label="工序类型" prop="actionId" width="100" align="center" show-overflow-tooltip>
<template slot-scope="scope">
{{ getActionIdName(scope.row.actionId) }}
</template>
</el-table-column>
<el-table-column label="排产状态" prop="scheduleStatus" width="90" align="center">
<template slot-scope="scope">
<span class="aps-status-tag" :class="'status-' + (scope.row.scheduleStatus || 1)">{{ statusMap[scope.row.scheduleStatus] || '未知' }}</span>
</template>
</el-table-column>
<el-table-column label="规格" prop="spec" min-width="120" show-overflow-tooltip />
<el-table-column label="材质" prop="material" width="90" align="center" show-overflow-tooltip />
<el-table-column label="排产吨数" prop="scheduleWeight" width="100" align="right" show-overflow-tooltip />
<el-table-column label="品名项" prop="productItem" min-width="100" show-overflow-tooltip />
<el-table-column label="品名" prop="productType" min-width="90" align="center" show-overflow-tooltip />
<el-table-column label="订货单位" prop="customerName" min-width="140" show-overflow-tooltip />
<el-table-column label="业务员" prop="businessUser" width="90" align="center" show-overflow-tooltip />
<el-table-column label="联系电话" prop="businessPhone" width="110" align="center" show-overflow-tooltip />
<el-table-column label="交货期(天)" prop="deliveryCycle" width="100" align="center" show-overflow-tooltip />
<el-table-column label="产品用途" prop="usePurpose" min-width="120" show-overflow-tooltip />
<el-table-column label="厚度公差" prop="thicknessTolerance" min-width="100" show-overflow-tooltip />
<el-table-column label="宽度公差" prop="widthTolerance" min-width="100" show-overflow-tooltip />
<el-table-column label="表面质量" prop="surfaceQuality" min-width="100" show-overflow-tooltip />
<el-table-column label="表面处理" prop="surfaceTreatment" min-width="100" show-overflow-tooltip />
<el-table-column label="内径尺寸" prop="innerDiameter" width="90" align="center" show-overflow-tooltip />
<el-table-column label="外径要求" prop="outerDiameter" width="90" align="center" show-overflow-tooltip />
<el-table-column label="包装要求" prop="packReq" min-width="100" show-overflow-tooltip />
<el-table-column label="切边要求" prop="cutEdgeReq" min-width="100" show-overflow-tooltip />
<el-table-column label="单件重量" prop="singleCoilWeight" width="90" align="center" show-overflow-tooltip />
<el-table-column label="交货重量偏差" prop="weightDeviation" min-width="110" show-overflow-tooltip />
<el-table-column label="其他技术要求" prop="otherTechReq" min-width="130" show-overflow-tooltip />
<el-table-column label="付款情况说明" prop="paymentDesc" min-width="130" show-overflow-tooltip />
<el-table-column label="单行排产备注" prop="rowRemark" min-width="100" show-overflow-tooltip />
<el-table-column label="备注" prop="remark" min-width="100" show-overflow-tooltip />
<el-table-column label="操作" width="150" align="center" fixed="right">
<template slot-scope="scope">
<div style="display:flex; justify-content:center; gap:0;">
<el-button type="text" size="small" style="color:#409EFF;padding:0 6px;" @click="handleEditScheduled(scope.row)">编辑</el-button>
<el-button type="text" size="small" style="color:#ff4d4f;padding:0 6px;" @click="handleDeleteScheduled(scope.row)">删除</el-button>
</div>
</template>
</el-table-column>
</el-table>
<template v-if="scheduledItemList.length > 0">
<!-- 步骤工序 Tab -->
<div class="aps-step-tabs">
<span
v-for="tab in scheduledStepTabs"
:key="tab"
class="aps-step-tab"
:class="{ 'aps-step-tab-active': scheduledStepTab === tab }"
@click="scheduledStepTab = tab"
>
{{ tab }}
<span class="aps-step-tab-count">{{ scheduledItemList.filter(i => normalizeStepName(i.stepName) === tab).length }}</span>
</span>
</div>
<!-- 表格 -->
<el-table
ref="scheduledTable"
:data="currentStepItems"
border
size="small"
class="aps-table"
>
<el-table-column label="排产单号" prop="scheduleNo" min-width="140" show-overflow-tooltip />
<el-table-column label="生产日期" prop="prodDate" width="110" align="center" show-overflow-tooltip />
<el-table-column label="工序步骤" prop="stepName" width="120" align="center" show-overflow-tooltip />
<el-table-column label="排产状态" prop="scheduleStatus" width="90" align="center">
<template slot-scope="scope">
<span class="aps-status-tag" :class="'status-' + (scope.row.scheduleStatus || 1)">{{ statusMap[scope.row.scheduleStatus] || '未知' }}</span>
</template>
</el-table-column>
<el-table-column label="规格" prop="spec" min-width="120" show-overflow-tooltip />
<el-table-column label="材质" prop="material" width="90" align="center" show-overflow-tooltip />
<el-table-column label="排产吨数" prop="scheduleWeight" width="100" align="right" show-overflow-tooltip />
<el-table-column label="品名项" prop="productItem" min-width="100" show-overflow-tooltip />
<el-table-column label="品名" prop="productType" min-width="90" align="center" show-overflow-tooltip />
<el-table-column label="订货单位" prop="customerName" min-width="140" show-overflow-tooltip />
<el-table-column label="业务员" prop="businessUser" width="90" align="center" show-overflow-tooltip />
<el-table-column label="联系电话" prop="businessPhone" width="110" align="center" show-overflow-tooltip />
<el-table-column label="交货期(天)" prop="deliveryCycle" width="100" align="center" show-overflow-tooltip />
<el-table-column label="产品用途" prop="usePurpose" min-width="120" show-overflow-tooltip />
<el-table-column label="厚度公差" prop="thicknessTolerance" min-width="100" show-overflow-tooltip />
<el-table-column label="宽度公差" prop="widthTolerance" min-width="100" show-overflow-tooltip />
<el-table-column label="表面质量" prop="surfaceQuality" min-width="100" show-overflow-tooltip />
<el-table-column label="表面处理" prop="surfaceTreatment" min-width="100" show-overflow-tooltip />
<el-table-column label="内径尺寸" prop="innerDiameter" width="90" align="center" show-overflow-tooltip />
<el-table-column label="外径要求" prop="outerDiameter" width="90" align="center" show-overflow-tooltip />
<el-table-column label="包装要求" prop="packReq" min-width="100" show-overflow-tooltip />
<el-table-column label="切边要求" prop="cutEdgeReq" min-width="100" show-overflow-tooltip />
<el-table-column label="单件重量" prop="singleCoilWeight" width="90" align="center" show-overflow-tooltip />
<el-table-column label="交货重量偏差" prop="weightDeviation" min-width="110" show-overflow-tooltip />
<el-table-column label="其他技术要求" prop="otherTechReq" min-width="130" show-overflow-tooltip />
<el-table-column label="付款情况说明" prop="paymentDesc" min-width="130" show-overflow-tooltip />
<el-table-column label="单行排产备注" prop="rowRemark" min-width="100" show-overflow-tooltip />
<el-table-column label="备注" prop="remark" min-width="100" show-overflow-tooltip />
<el-table-column label="操作" width="150" align="center" fixed="right">
<template slot-scope="scope">
<div style="display:flex; justify-content:center; gap:0;">
<el-button type="text" size="small" style="color:#409EFF;padding:0 6px;" @click="handleEditScheduled(scope.row)">编辑</el-button>
<el-button type="text" size="small" style="color:#ff4d4f;padding:0 6px;" @click="handleDeleteScheduled(scope.row)">删除</el-button>
</div>
</template>
</el-table-column>
</el-table>
</template>
<div v-else-if="!schLoading" style="padding:40px;text-align:center;color:#909399;">
{{ hasQueried ? '该日期暂无已排产数据' : '请选择日期查询' }}
</div>
@@ -493,149 +580,48 @@
</div>
</el-dialog>
<!-- 合并排产明细对话框 -->
<!-- 接收产需单弹窗 - 配置工序 -->
<el-dialog
title="合并排产明细"
:visible.sync="mergeDialogVisible"
width="850px"
title="接收产需单 - 配置工序"
:visible.sync="receiveDialogVisible"
width="900px"
append-to-body
:close-on-click-modal="false"
>
<div style="margin-bottom:12px;font-size:13px;color:#e67e22;">
选中 {{ mergeForm.itemCount }} 条明细请选择一个作为合并模板下方字段将自动填充您可修改后确认合并
请为每条明细配置工序所有明细配置完成后才能接收
</div>
<!-- 选择模板 -->
<el-table :data="mergeSourceRows" border size="small" style="margin-bottom:12px;" @row-click="pickMergeTemplate">
<el-table-column width="50" align="center">
<el-table :data="receiveDetailList" border size="small" class="aps-table" max-height="400">
<el-table-column label="规格" prop="spec" min-width="120" show-overflow-tooltip />
<el-table-column label="材质" prop="material" width="90" align="center" show-overflow-tooltip />
<el-table-column label="排产吨数" prop="scheduleWeight" width="100" align="right" />
<el-table-column label="品名" prop="productType" min-width="90" align="center" show-overflow-tooltip />
<el-table-column label="配置工序" min-width="200">
<template slot-scope="scope">
<el-radio v-model="mergeTemplateIndex" :label="scope.$index" @click.native.stop />
<ProcessSelect
v-model="scope.row.processId"
placeholder="请选择工序"
@change="(val, data) => handleReceiveProcessChange(scope.row, val, data)"
/>
</template>
</el-table-column>
<el-table-column label="工序步骤" min-width="300">
<template slot-scope="scope">
<div v-if="scope.row.stepList && scope.row.stepList.length > 0" class="receive-steps-flow">
<template v-for="(step, index) in scope.row.stepList">
<span :key="step.stepId" class="receive-step-tag">{{ step.stepName }}</span>
<span v-if="index < scope.row.stepList.length - 1" :key="'arrow-' + index" class="receive-step-arrow"></span>
</template>
</div>
<span v-else style="color:#909399;font-size:12px;"></span>
</template>
</el-table-column>
<el-table-column label="排产单号" prop="scheduleNo" min-width="130" show-overflow-tooltip />
<el-table-column label="规格" prop="spec" min-width="100" show-overflow-tooltip />
<el-table-column label="材质" prop="material" width="80" show-overflow-tooltip />
<el-table-column label="排产吨数" prop="scheduleWeight" width="90" align="right" />
<el-table-column label="品名" prop="productType" min-width="80" show-overflow-tooltip />
<el-table-column label="订货单位" prop="customerName" min-width="120" show-overflow-tooltip />
</el-table>
<!-- 合并表单 -->
<el-form ref="mergeFormRef" :model="mergeForm" label-width="110px" size="small">
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="排产单号"><el-input v-model="mergeForm.scheduleNo" /></el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="工序类型">
<el-select v-model="mergeForm.actionId" placeholder="请选择工序类型" clearable filterable style="width:100%">
<el-option v-for="p in processOptions" :key="p.actionId" :label="p.name" :value="p.actionId" />
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="订货单位"><el-input v-model="mergeForm.customerName" /></el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="规格"><el-input v-model="mergeForm.spec" /></el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="材质"><el-input v-model="mergeForm.material" /></el-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="排产吨数(累加)">
<el-input-number v-model="mergeForm.scheduleWeight" :min="0" :precision="3" :controls="false" style="width:100%" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="品名"><el-input v-model="mergeForm.productType" /></el-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="品名项"><el-input v-model="mergeForm.productItem" /></el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="业务员"><el-input v-model="mergeForm.businessUser" /></el-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="联系电话"><el-input v-model="mergeForm.businessPhone" /></el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="交货期(天)">
<el-input-number v-model="mergeForm.deliveryCycle" :min="0" :controls="false" style="width:100%" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="产品用途"><el-input v-model="mergeForm.usePurpose" /></el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="厚度公差"><el-input v-model="mergeForm.thicknessTolerance" /></el-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="宽度公差"><el-input v-model="mergeForm.widthTolerance" /></el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="表面质量"><el-input v-model="mergeForm.surfaceQuality" /></el-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="表面处理"><el-input v-model="mergeForm.surfaceTreatment" /></el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="内径尺寸"><el-input v-model="mergeForm.innerDiameter" /></el-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="外径要求"><el-input v-model="mergeForm.outerDiameter" /></el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="包装要求"><el-input v-model="mergeForm.packReq" /></el-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="切边要求"><el-input v-model="mergeForm.cutEdgeReq" /></el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="单件重量"><el-input v-model="mergeForm.singleCoilWeight" /></el-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="交货重量偏差"><el-input v-model="mergeForm.weightDeviation" /></el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="其他技术要求"><el-input v-model="mergeForm.otherTechReq" /></el-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="付款情况说明"><el-input v-model="mergeForm.paymentDesc" /></el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="备注"><el-input v-model="mergeForm.remark" /></el-form-item>
</el-col>
</el-row>
</el-form>
<div style="margin-top:8px;font-size:12px;color:#909399;">
合并后 schedule_detail_ids{{ mergeForm.scheduleDetailIds }}
</div>
<div slot="footer" class="dialog-footer">
<el-button :loading="mergeBtnLoading" type="warning" @click="confirmMerge"> </el-button>
<el-button @click="mergeDialogVisible = false"> </el-button>
<el-button :loading="receiveBtnLoading" type="primary" :disabled="!canConfirmReceive" @click="confirmReceive"> </el-button>
<el-button @click="receiveDialogVisible = false"> </el-button>
</div>
</el-dialog>
</div>
@@ -651,13 +637,14 @@ import {
listScheduleItem,
updateScheduleItem,
delScheduleItem,
receiveScheduleItem,
mergeScheduleItem
receiveScheduleItem
} from '@/api/aps/schedule'
import { PROCESSES } from '@/utils/meta'
import ProcessSelect from '@/components/KLPService/ProcessSelect/index.vue'
export default {
name: 'ApsSchedule',
components: { ProcessSelect },
data() {
const today = new Date()
const y = today.getFullYear()
@@ -676,26 +663,13 @@ export default {
statusMap: { 1: '待审核', 2: '已接收', 3: '已驳回' },
processOptions: PROCESSES,
// 已接收
acceptedScheduleList: [],
acceptedLoading: false,
// 已排产
scheduledItemList: [],
selectedItems: [],
sourceColorMap: {},
// 合并对话框
mergeDialogVisible: false,
mergeBtnLoading: false,
mergeTemplateIndex: 0,
mergeSourceRows: [],
mergeForm: {
itemCount: 0, scheduleNo: '', actionId: '', customerName: '', spec: '', material: '',
scheduleWeight: 0, productType: '', productItem: '', businessUser: '',
businessPhone: '', deliveryCycle: undefined, usePurpose: '',
thicknessTolerance: '', widthTolerance: '', surfaceQuality: '',
surfaceTreatment: '', innerDiameter: '', outerDiameter: '',
packReq: '', cutEdgeReq: '', singleCoilWeight: '',
weightDeviation: '', otherTechReq: '', paymentDesc: '',
remark: '', scheduleDetailIds: ''
},
scheduledStepTab: '',
// 驳回
rejectDialogVisible: false,
@@ -714,14 +688,37 @@ export default {
// 下钻
drillDialogVisible: false,
drillOrder: null
drillOrder: null,
// 接收产需单弹窗
receiveDialogVisible: false,
receiveBtnLoading: false,
receiveScheduleId: null,
receiveDetailList: []
}
},
watch: {
activeTab() {
this.selectedItems = []
computed: {
canConfirmReceive() {
if (!this.receiveDetailList || this.receiveDetailList.length === 0) return false
return this.receiveDetailList.every(d => d.processId)
},
// 已排产按步骤分组:去掉括号及括号内文字
scheduledStepTabs() {
const names = this.scheduledItemList
.map(item => this.normalizeStepName(item.stepName))
.filter(Boolean)
return [...new Set(names)]
},
// 当前选中的步骤tab对应的数据
currentStepItems() {
if (!this.scheduledStepTab) return this.scheduledItemList
const tab = this.scheduledStepTab
return this.scheduledItemList.filter(
item => this.normalizeStepName(item.stepName) === tab
)
}
},
watch: {},
created() {
this.handleQuery()
},
@@ -775,6 +772,8 @@ export default {
this.hasQueried = true
if (this.activeTab === 'pending') {
this.queryPending()
} else if (this.activeTab === 'accepted') {
this.queryAccepted()
} else {
this.queryScheduled()
}
@@ -808,25 +807,82 @@ export default {
})
},
// ====== 已接收 ======
queryAccepted() {
this.acceptedLoading = true
this.acceptedScheduleList = []
listRequirement({
prodDate: this.queryDate,
scheduleStatus: 2,
pageNum: 1,
pageSize: 999
}).then(res => {
this.acceptedScheduleList = res.rows || []
const totalCount = this.acceptedScheduleList.reduce((sum, sch) => sum + (sch.detailList || []).length, 0)
this.summaryText = `${this.acceptedScheduleList.length} 个已接收产需单,${totalCount} 条明细`
}).catch(() => {
this.acceptedScheduleList = []
this.summaryText = ''
}).finally(() => {
this.acceptedLoading = false
})
},
handleAccept(sch) {
const details = sch.detailList || []
if (details.length === 0) {
this.$message.warning('该产需单无可排产的明细')
return
}
this.$confirm(
`确认接收产需单「${sch.scheduleNo}」的全部排产明细吗?共 ${details.length} 条明细`,
'提示',
{ confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' }
).then(() => {
this.$message.info('正在处理接收...')
receiveScheduleItem(sch.scheduleId).then(() => {
this.$modal.msgSuccess('接收成功,排产明细已写入')
this.queryPending()
}).catch(() => {
this.$modal.msgError('接收失败')
})
}).catch(() => {})
// 初始化接收明细列表,为每条明细添加 processId 和 stepList 字段
this.receiveScheduleId = sch.scheduleId
this.receiveDetailList = details.map(d => ({
...d,
processId: undefined,
stepList: []
}))
this.receiveDialogVisible = true
},
handleReceiveProcessChange(row, processId, processData) {
if (processData && processData.stepList) {
row.stepList = processData.stepList
} else {
row.stepList = []
}
},
confirmReceive() {
// 验证所有明细都配置了工序
const unconfigured = this.receiveDetailList.filter(d => !d.processId)
if (unconfigured.length > 0) {
this.$message.warning(`还有 ${unconfigured.length} 条明细未配置工序,请为所有明细配置工序`)
return
}
// 构建接收数据,传递每条明细的 processId 和 stepList
const receiveData = {
scheduleId: this.receiveScheduleId,
detailProcessList: this.receiveDetailList.map(d => ({
scheduleDetailId: d.scheduleDetailId || d.scheduleDetailIds,
processId: d.processId,
stepList: (d.stepList || []).map(s => ({
stepId: s.stepId,
stepOrder: s.stepOrder,
stepName: s.stepName
}))
}))
}
this.receiveBtnLoading = true
receiveScheduleItem(receiveData).then(() => {
this.$modal.msgSuccess('接收成功,排产明细已写入')
this.receiveDialogVisible = false
this.queryPending()
}).catch(() => {
this.$modal.msgError('接收失败')
}).finally(() => {
this.receiveBtnLoading = false
})
},
handleReject(sch) {
@@ -864,16 +920,14 @@ export default {
queryScheduled() {
this.schLoading = true
this.scheduledItemList = []
this.sourceColorMap = {}
this.selectedItems = []
this.scheduledStepTab = ''
listScheduleItem({ prodDate: this.queryDate, pageNum: 1, pageSize: 999 }).then(res => {
this.scheduledItemList = (res.rows || []).sort((a, b) => (a.scheduleNo || '').localeCompare(b.scheduleNo || ''))
try {
this.sourceColorMap = this.buildGroupColorMap(this.scheduledItemList)
} catch (e) {
console.error('buildGroupColorMap error:', e)
this.sourceColorMap = {}
// 默认选中第一个步骤tab
const tabs = this.scheduledStepTabs
if (tabs.length > 0) {
this.scheduledStepTab = tabs[0]
}
const totalWeight = this.scheduledItemList.reduce((sum, d) => {
const w = parseFloat(d.scheduleWeight)
@@ -934,98 +988,6 @@ export default {
}).catch(() => {})
},
// ====== 合并 ======
handleSelectionChange(rows) {
this.selectedItems = rows
},
getItemRowClassName({ row }) {
const key = row.scheduleNo
if (!key) return ''
const colorIndex = this.sourceColorMap[key]
return colorIndex !== undefined ? `merge-source-${colorIndex}` : ''
},
buildGroupColorMap(list) {
const map = {}
let colorIdx = 0
list.forEach(item => {
const key = item.scheduleNo
if (!key) return
if (!(key in map)) {
map[key] = colorIdx % 5
colorIdx++
}
})
return map
},
handleMergePrepare() {
if (this.selectedItems.length < 2) {
this.$message.warning('请至少选择2条排产明细进行合并')
return
}
this.mergeSourceRows = [...this.selectedItems]
this.mergeTemplateIndex = 0
this.pickMergeTemplate(this.mergeSourceRows[0])
this.mergeDialogVisible = true
},
pickMergeTemplate(row) {
const idx = this.mergeSourceRows.indexOf(row)
if (idx >= 0) this.mergeTemplateIndex = idx
this.mergeForm = {
itemCount: this.mergeSourceRows.length,
scheduleNo: row.scheduleNo || '',
actionId: row.actionId != null ? Number(row.actionId) : '',
customerName: row.customerName || '',
spec: row.spec || '',
material: row.material || '',
scheduleWeight: this.mergeSourceRows.reduce((sum, r) => sum + (parseFloat(r.scheduleWeight) || 0), 0),
productType: row.productType || '',
productItem: row.productItem || '',
businessUser: row.businessUser || '',
businessPhone: row.businessPhone || '',
deliveryCycle: row.deliveryCycle,
usePurpose: row.usePurpose || '',
thicknessTolerance: row.thicknessTolerance || '',
widthTolerance: row.widthTolerance || '',
surfaceQuality: row.surfaceQuality || '',
surfaceTreatment: row.surfaceTreatment || '',
innerDiameter: row.innerDiameter || '',
outerDiameter: row.outerDiameter || '',
packReq: row.packReq || '',
cutEdgeReq: row.cutEdgeReq || '',
singleCoilWeight: row.singleCoilWeight || '',
weightDeviation: row.weightDeviation || '',
otherTechReq: row.otherTechReq || '',
paymentDesc: row.paymentDesc || '',
remark: row.remark || '',
scheduleDetailIds: this.mergeSourceRows.map(r => r.scheduleDetailIds || '').filter(Boolean).join(',')
}
},
confirmMerge() {
this.mergeBtnLoading = true
const ids = this.mergeSourceRows.map(r => r.scheduleId)
const mergedBo = {
...this.mergeForm,
prodDate: this.queryDate,
scheduleStatus: 2
}
delete mergedBo.itemCount
mergeScheduleItem({ ids, mergedBo }).then(() => {
this.$modal.msgSuccess(`合并成功:${ids.length} 条明细合并为 1 条`)
this.mergeDialogVisible = false
this.selectedItems = []
this.queryScheduled()
}).catch(() => {
this.$modal.msgError('合并失败')
}).finally(() => {
this.mergeBtnLoading = false
})
},
// ====== 下钻 & 辅助方法 ======
scheduleTotalWeight(sch) {
const details = sch.detailList || []
@@ -1038,6 +1000,12 @@ export default {
return p ? p.name : (actionId || '')
},
// 去掉步骤名称中的括号及括号内文字:镀锌(毛化)=> 镀锌
normalizeStepName(name) {
if (!name) return ''
return name.replace(/[(][^)]*[)]/g, '').trim()
},
handleDetailClick(sch, detail) {
// 点击明细行查看来源订单
if (!sch || !sch.orderList || sch.orderList.length === 0) {
@@ -1350,20 +1318,85 @@ export default {
font-size: 12px;
}
// ====== 合并候选行颜色高亮(同一源排产单 → 同色) ======
::v-deep .el-table__body tr.merge-source-0 > td {
background-color: #f0f5ff !important;
// ====== 接收弹窗步骤展示 ======
.receive-steps-flow {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 2px;
}
::v-deep .el-table__body tr.merge-source-1 > td {
background-color: #f6ffed !important;
.receive-step-tag {
display: inline-block;
padding: 2px 8px;
background: $aps-silver-1;
border: 1px solid $aps-silver-mid;
border-radius: 3px;
font-size: 12px;
color: $aps-text;
}
::v-deep .el-table__body tr.merge-source-2 > td {
background-color: #fff7e6 !important;
.receive-step-arrow {
color: $aps-red-2;
font-size: 14px;
padding: 0 2px;
}
::v-deep .el-table__body tr.merge-source-3 > td {
background-color: #f9f0ff !important;
// ====== 已排产步骤工序 Tab ======
.aps-step-tabs {
display: flex;
flex-wrap: wrap;
padding: 8px 12px;
gap: 6px;
background: $aps-silver-1;
border-bottom: 1px solid $aps-border;
}
::v-deep .el-table__body tr.merge-source-4 > td {
background-color: #fff0f6 !important;
.aps-step-tab {
display: inline-flex;
align-items: center;
gap: 4px;
padding: 4px 14px;
font-size: 12px;
font-weight: 500;
color: $aps-text-muted;
background: $aps-white;
border: 1px solid $aps-silver-mid;
border-radius: $aps-radius;
cursor: pointer;
transition: all 0.2s;
user-select: none;
&:hover {
color: $aps-red-2;
border-color: $aps-red-2;
}
}
.aps-step-tab-active {
color: #fff;
background: $aps-red-2;
border-color: $aps-red-2;
&:hover {
color: #fff;
}
}
.aps-step-tab-count {
display: inline-flex;
align-items: center;
justify-content: center;
min-width: 18px;
height: 16px;
padding: 0 5px;
font-size: 10px;
font-weight: 600;
border-radius: 8px;
background: rgba(0, 0, 0, 0.1);
.aps-step-tab-active & {
background: rgba(255, 255, 255, 0.25);
}
}
</style>