1737 lines
60 KiB
Vue
1737 lines
60 KiB
Vue
<template>
|
||
<div class="aps-sch-page">
|
||
<!-- 顶部工具栏 -->
|
||
<div class="aps-sch-toolbar">
|
||
<span class="aps-sch-label">生产日期:</span>
|
||
<el-date-picker
|
||
v-model="queryDate"
|
||
type="date"
|
||
placeholder="选择生产日期"
|
||
value-format="yyyy-MM-dd"
|
||
size="small"
|
||
style="width:160px"
|
||
@change="handleDateChange"
|
||
/>
|
||
<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">
|
||
<span>{{ summaryText }}</span>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 待审核 Tab -->
|
||
<div v-loading="loading" v-show="activeTab === 'pending'" class="detail-card aps-sch-card">
|
||
<div class="detail-card-header">
|
||
<span>待审核产需单明细</span>
|
||
<span v-if="pendingScheduleList.length > 0" style="font-weight:normal;font-size:12px;opacity:0.8;">
|
||
共 {{ pendingScheduleList.length }} 个产需单
|
||
</span>
|
||
</div>
|
||
<div element-loading-text="正在加载待审核产需单..." class="detail-card-body" style="padding:0;">
|
||
<div v-if="pendingScheduleList.length > 0" class="aps-sch-list">
|
||
<div v-for="sch in pendingScheduleList" :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" :class="'status-' + (sch.scheduleStatus || 1)">{{ statusMap[sch.scheduleStatus] || '待审核' }}</span>
|
||
<span class="aps-sch-header-extra">
|
||
{{ sch.customerName }} · {{ sch.businessUser }} · {{ sch.deliveryCycle }}天
|
||
</span>
|
||
</div>
|
||
<div class="aps-sch-item-actions">
|
||
<el-button type="text" size="mini" style="color:#52c41a;" @click.stop="handleAccept(sch)">接收</el-button>
|
||
<el-button type="text" size="mini" style="color:#ff4d4f;" @click.stop="handleReject(sch)">驳回</el-button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 产需单完整信息表 -->
|
||
<table class="req-info-table">
|
||
<colgroup>
|
||
<col style="width: 88px;">
|
||
<col>
|
||
<col style="width: 88px;">
|
||
<col>
|
||
<col style="width: 88px;">
|
||
<col>
|
||
<col style="width: 88px;">
|
||
<col>
|
||
</colgroup>
|
||
<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>
|
||
<td class="req-td-label">排产状态</td>
|
||
<td class="req-td-value">
|
||
<span class="aps-status-tag" :class="'status-' + (sch.scheduleStatus || 1)">{{ statusMap[sch.scheduleStatus] || '未知' }}</span>
|
||
</td>
|
||
<td class="req-td-label">业务员</td>
|
||
<td class="req-td-value">{{ sch.businessUser }}</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.businessPhone }}</td>
|
||
<td class="req-td-label">交货期(天)</td>
|
||
<td class="req-td-value">{{ sch.deliveryCycle }}</td>
|
||
<td class="req-td-label">品名</td>
|
||
<td class="req-td-value">{{ sch.productType }}</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>
|
||
<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>
|
||
<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>
|
||
<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" colspan="7">{{ sch.remark || '-' }}</td>
|
||
</tr>
|
||
<tr v-if="sch.otherTechReq">
|
||
<td class="req-td-label">其他技术要求</td>
|
||
<td class="req-td-value" colspan="7">{{ sch.otherTechReq }}</td>
|
||
</tr>
|
||
<tr v-if="sch.paymentDesc">
|
||
<td class="req-td-label">付款情况说明</td>
|
||
<td class="req-td-value" colspan="7">{{ sch.paymentDesc }}</td>
|
||
</tr>
|
||
<tr v-if="sch.returnReason">
|
||
<td class="req-td-label" style="color:#e74c3c;">驳回原因</td>
|
||
<td class="req-td-value" colspan="7" style="color:#e74c3c;background:#fdecea;">{{ sch.returnReason }}</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="!loading" style="padding:24px;text-align:center;color:#909399;">
|
||
{{ hasQueried ? '该日期暂无待审核产需单' : '请选择日期查询' }}
|
||
</div>
|
||
</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">
|
||
<colgroup>
|
||
<col style="width: 88px;">
|
||
<col>
|
||
<col style="width: 88px;">
|
||
<col>
|
||
<col style="width: 88px;">
|
||
<col>
|
||
<col style="width: 88px;">
|
||
<col>
|
||
</colgroup>
|
||
<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>
|
||
<td class="req-td-label">排产状态</td>
|
||
<td class="req-td-value">
|
||
<span class="aps-status-tag status-2">已接收</span>
|
||
</td>
|
||
<td class="req-td-label">业务员</td>
|
||
<td class="req-td-value">{{ sch.businessUser }}</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.businessPhone }}</td>
|
||
<td class="req-td-label">交货期(天)</td>
|
||
<td class="req-td-value">{{ sch.deliveryCycle }}</td>
|
||
<td class="req-td-label">品名</td>
|
||
<td class="req-td-value">{{ sch.productType }}</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>
|
||
<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>
|
||
<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>
|
||
<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" colspan="7">{{ sch.remark || '-' }}</td>
|
||
</tr>
|
||
<tr v-if="sch.otherTechReq">
|
||
<td class="req-td-label">其他技术要求</td>
|
||
<td class="req-td-value" colspan="7">{{ sch.otherTechReq }}</td>
|
||
</tr>
|
||
<tr v-if="sch.paymentDesc">
|
||
<td class="req-td-label">付款情况说明</td>
|
||
<td class="req-td-value" colspan="7">{{ sch.paymentDesc }}</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:24px;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">
|
||
<span>已排产明细</span>
|
||
<div style="display:flex; align-items:center; gap:8px;">
|
||
<span v-if="scheduledItemList.length > 0" style="font-weight:normal;font-size:12px;opacity:0.8;">
|
||
共 {{ scheduledItemList.length }} 条
|
||
</span>
|
||
</div>
|
||
</div>
|
||
<div element-loading-text="正在加载已排产数据..." class="detail-card-body" style="padding:0;">
|
||
<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 class="aps-step-tabs-right">
|
||
<el-button size="mini" class="aps-btn-red" :disabled="!scheduledSortDirty" :loading="saveSortLoading" @click="handleSaveScheduledSort">保存排序</el-button>
|
||
</div>
|
||
</div>
|
||
<div class="aps-scheduled-cards">
|
||
<draggable v-model="scheduledStepItems" ghost-class="aps-drag-ghost" handle=".aps-drag-handle" @end="handleScheduledDragEnd">
|
||
<div v-for="row in scheduledStepItems" :key="row.scheduleId" class="aps-scheduled-card">
|
||
<div class="aps-scheduled-card-header">
|
||
<div class="aps-scheduled-card-title">
|
||
<i class="el-icon-rank aps-drag-handle"></i>
|
||
<span class="aps-scheduled-no">{{ row.scheduleNo }}</span>
|
||
<span class="aps-scheduled-step">{{ row.stepName }}</span>
|
||
</div>
|
||
<div class="aps-scheduled-card-actions">
|
||
<el-select
|
||
v-model="row.scheduleStatus"
|
||
size="mini"
|
||
:loading="row._statusLoading"
|
||
@change="val => handleInlineStatusChange(row, val)"
|
||
@click.stop.native
|
||
style="width: 110px;"
|
||
>
|
||
<el-option v-for="(label, val) in statusMap" :key="val" :label="label" :value="Number(val)" />
|
||
</el-select>
|
||
<el-button type="text" size="mini" style="color:#409EFF;padding:0 6px;" @click="handleEditScheduled(row)">编辑</el-button>
|
||
<el-button type="text" size="mini" style="color:#ff4d4f;padding:0 6px;" @click="handleDeleteScheduled(row)">删除</el-button>
|
||
</div>
|
||
</div>
|
||
<div class="aps-scheduled-card-body">
|
||
<div class="aps-scheduled-kv">
|
||
<div class="kv-item"><span class="kv-label">生产日期</span><span class="kv-value">{{ row.prodDate }}</span></div>
|
||
<div class="kv-item"><span class="kv-label">规格</span><span class="kv-value">{{ row.spec }}</span></div>
|
||
<div class="kv-item"><span class="kv-label">材质</span><span class="kv-value">{{ row.material }}</span></div>
|
||
<div class="kv-item"><span class="kv-label">排产吨数</span><span class="kv-value">{{ row.scheduleWeight }}</span></div>
|
||
<div class="kv-item"><span class="kv-label">品名项</span><span class="kv-value">{{ row.productItem }}</span></div>
|
||
<div class="kv-item"><span class="kv-label">品名</span><span class="kv-value">{{ row.productType }}</span></div>
|
||
<div class="kv-item"><span class="kv-label">订货单位</span><span class="kv-value">{{ row.customerName }}</span></div>
|
||
<div class="kv-item"><span class="kv-label">业务员</span><span class="kv-value">{{ row.businessUser }}</span></div>
|
||
<div class="kv-item"><span class="kv-label">联系电话</span><span class="kv-value">{{ row.businessPhone }}</span></div>
|
||
<div class="kv-item"><span class="kv-label">交货期(天)</span><span class="kv-value">{{ row.deliveryCycle }}</span></div>
|
||
<div class="kv-item"><span class="kv-label">厚度公差</span><span class="kv-value">{{ row.thicknessTolerance }}</span></div>
|
||
<div class="kv-item"><span class="kv-label">宽度公差</span><span class="kv-value">{{ row.widthTolerance }}</span></div>
|
||
<div class="kv-item"><span class="kv-label">表面质量</span><span class="kv-value">{{ row.surfaceQuality }}</span></div>
|
||
<div class="kv-item"><span class="kv-label">表面处理</span><span class="kv-value">{{ row.surfaceTreatment }}</span></div>
|
||
<div class="kv-item"><span class="kv-label">内径尺寸</span><span class="kv-value">{{ row.innerDiameter }}</span></div>
|
||
<div class="kv-item"><span class="kv-label">外径要求</span><span class="kv-value">{{ row.outerDiameter }}</span></div>
|
||
<div class="kv-item"><span class="kv-label">包装要求</span><span class="kv-value">{{ row.packReq }}</span></div>
|
||
<div class="kv-item"><span class="kv-label">切边要求</span><span class="kv-value">{{ row.cutEdgeReq }}</span></div>
|
||
<div class="kv-item"><span class="kv-label">单件重量</span><span class="kv-value">{{ row.singleCoilWeight }}</span></div>
|
||
<div class="kv-item"><span class="kv-label">交货重量偏差</span><span class="kv-value">{{ row.weightDeviation }}</span></div>
|
||
<div class="kv-item kv-span-4"><span class="kv-label">产品用途</span><span class="kv-value kv-value-wrap">{{ row.usePurpose }}</span></div>
|
||
<div class="kv-item kv-span-4" v-if="row.otherTechReq"><span class="kv-label">其他技术要求</span><span class="kv-value kv-value-wrap">{{ row.otherTechReq }}</span></div>
|
||
<div class="kv-item kv-span-4" v-if="row.paymentDesc"><span class="kv-label">付款情况说明</span><span class="kv-value kv-value-wrap">{{ row.paymentDesc }}</span></div>
|
||
<div class="kv-item kv-span-4" v-if="row.rowRemark"><span class="kv-label">单行排产备注</span><span class="kv-value kv-value-wrap">{{ row.rowRemark }}</span></div>
|
||
<div class="kv-item kv-span-4" v-if="row.remark"><span class="kv-label">备注</span><span class="kv-value kv-value-wrap">{{ row.remark }}</span></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</draggable>
|
||
</div>
|
||
</template>
|
||
<div v-else-if="!schLoading" style="padding:40px;text-align:center;color:#909399;">
|
||
{{ hasQueried ? '该日期暂无已排产数据' : '请选择日期查询' }}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 下钻弹窗(来源订单信息) -->
|
||
<el-dialog title="来源订单信息" :visible.sync="drillDialogVisible" width="600px" append-to-body>
|
||
<div v-if="drillOrder" class="detail-card" style="border:none;box-shadow:none;">
|
||
<div class="detail-card-body">
|
||
<div class="form-grid-2">
|
||
<div class="form-field"><label>订单编号</label><div class="field-value">{{ drillOrder.orderCode }}</div></div>
|
||
<div class="form-field"><label>销售员</label><div class="field-value">{{ drillOrder.salesman }}</div></div>
|
||
<div class="form-field"><label>客户公司</label><div class="field-value">{{ drillOrder.companyName }}</div></div>
|
||
<div class="form-field"><label>联系人</label><div class="field-value">{{ drillOrder.contactPerson }}</div></div>
|
||
<div class="form-field"><label>联系电话</label><div class="field-value">{{ drillOrder.contactWay }}</div></div>
|
||
<div class="form-field"><label>交货日期</label><div class="field-value">{{ drillOrder.deliveryDate }}</div></div>
|
||
<div class="form-field"><label>合同号</label><div class="field-value">{{ drillOrder.contractCode }}</div></div>
|
||
<div class="form-field" style="grid-column:1/3;"><label>备注</label><div class="field-value">{{ drillOrder.remark }}</div></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div v-else>
|
||
<el-empty description="未找到订单信息" />
|
||
</div>
|
||
</el-dialog>
|
||
|
||
<!-- 驳回理由对话框 -->
|
||
<el-dialog title="驳回产需单" :visible.sync="rejectDialogVisible" width="450px" append-to-body :close-on-click-modal="false">
|
||
<el-form ref="rejectForm" :model="rejectForm" label-width="90px" size="small">
|
||
<el-form-item label="排产单号">
|
||
<span style="color:#2c3e50;font-weight:500;">{{ rejectForm.scheduleNo }}</span>
|
||
</el-form-item>
|
||
<el-form-item label="驳回理由" prop="returnReason">
|
||
<el-input v-model="rejectForm.returnReason" type="textarea" :rows="4" placeholder="请填写驳回理由" />
|
||
</el-form-item>
|
||
</el-form>
|
||
<div slot="footer" class="dialog-footer">
|
||
<el-button :loading="rejectBtnLoading" type="danger" @click="confirmReject">确 定</el-button>
|
||
<el-button @click="rejectDialogVisible = false">取 消</el-button>
|
||
</div>
|
||
</el-dialog>
|
||
|
||
<!-- 已排产明细编辑对话框 -->
|
||
<el-dialog
|
||
:title="editDialogTitle"
|
||
:visible.sync="editDialogVisible"
|
||
width="800px"
|
||
append-to-body
|
||
:close-on-click-modal="false"
|
||
>
|
||
<el-form ref="editForm" :model="editForm" label-width="110px" size="small">
|
||
<el-row :gutter="16">
|
||
<el-col :span="12">
|
||
<el-form-item label="排产单号">
|
||
<span style="color:#2c3e50;font-weight:500;">{{ editForm.scheduleNo }}</span>
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="12">
|
||
<el-form-item label="排产状态">
|
||
<el-select v-model="editForm.scheduleStatus" placeholder="请选择排产状态" style="width:100%">
|
||
<el-option v-for="(label, val) in statusMap" :key="val" :label="label" :value="Number(val)" />
|
||
</el-select>
|
||
</el-form-item>
|
||
</el-col>
|
||
</el-row>
|
||
<el-row :gutter="16">
|
||
<el-col :span="12">
|
||
<el-form-item label="工序类型">
|
||
<el-select v-model="editForm.actionId" placeholder="请选择工序类型" clearable filterable disabled 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-col :span="12">
|
||
<el-form-item label="订货单位">
|
||
<el-input v-model="editForm.customerName" />
|
||
</el-form-item>
|
||
</el-col>
|
||
</el-row>
|
||
<el-row :gutter="16">
|
||
<el-col :span="12">
|
||
<el-form-item label="规格" prop="spec">
|
||
<el-input v-model="editForm.spec" />
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="12">
|
||
<el-form-item label="材质" prop="material">
|
||
<el-input v-model="editForm.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="editForm.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-number v-model="editForm.totalPlanWeight" :min="0" :precision="3" :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="editForm.productType" />
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="12">
|
||
<el-form-item label="品名项">
|
||
<el-input v-model="editForm.productItem" />
|
||
</el-form-item>
|
||
</el-col>
|
||
</el-row>
|
||
<el-row :gutter="16">
|
||
<el-col :span="12">
|
||
<el-form-item label="业务员">
|
||
<el-input v-model="editForm.businessUser" />
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="12">
|
||
<el-form-item label="联系电话">
|
||
<el-input v-model="editForm.businessPhone" />
|
||
</el-form-item>
|
||
</el-col>
|
||
</el-row>
|
||
<!-- <el-row :gutter="16">-->
|
||
<!-- <el-col :span="12">-->
|
||
<!-- <el-form-item label="关联销售合同号">-->
|
||
<!-- <el-input v-model="editForm.relContractNo" />-->
|
||
<!-- </el-form-item>-->
|
||
<!-- </el-col>-->
|
||
<!-- <el-col :span="12">-->
|
||
<!-- <el-form-item label="订单日期">-->
|
||
<!-- <el-date-picker v-model="editForm.orderDate" type="date" value-format="yyyy-MM-dd" style="width:100%" />-->
|
||
<!-- </el-form-item>-->
|
||
<!-- </el-col>-->
|
||
<!-- </el-row>-->
|
||
<el-row :gutter="16">
|
||
<el-col :span="12">
|
||
<el-form-item label="交货期(天)">
|
||
<el-input-number v-model="editForm.deliveryCycle" :min="0" :controls="false" style="width:100%" />
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="12">
|
||
<el-form-item label="产品用途">
|
||
<el-input v-model="editForm.usePurpose" />
|
||
</el-form-item>
|
||
</el-col>
|
||
</el-row>
|
||
<el-row :gutter="16">
|
||
<el-col :span="12">
|
||
<el-form-item label="厚度公差">
|
||
<el-input v-model="editForm.thicknessTolerance" />
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="12">
|
||
<el-form-item label="宽度公差">
|
||
<el-input v-model="editForm.widthTolerance" />
|
||
</el-form-item>
|
||
</el-col>
|
||
</el-row>
|
||
<el-row :gutter="16">
|
||
<el-col :span="12">
|
||
<el-form-item label="表面质量">
|
||
<el-input v-model="editForm.surfaceQuality" />
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="12">
|
||
<el-form-item label="表面处理">
|
||
<el-input v-model="editForm.surfaceTreatment" />
|
||
</el-form-item>
|
||
</el-col>
|
||
</el-row>
|
||
<el-row :gutter="16">
|
||
<el-col :span="12">
|
||
<el-form-item label="内径尺寸">
|
||
<el-input v-model="editForm.innerDiameter" />
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="12">
|
||
<el-form-item label="外径要求">
|
||
<el-input v-model="editForm.outerDiameter" />
|
||
</el-form-item>
|
||
</el-col>
|
||
</el-row>
|
||
<el-row :gutter="16">
|
||
<el-col :span="12">
|
||
<el-form-item label="包装要求">
|
||
<el-input v-model="editForm.packReq" />
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="12">
|
||
<el-form-item label="切边要求">
|
||
<el-input v-model="editForm.cutEdgeReq" />
|
||
</el-form-item>
|
||
</el-col>
|
||
</el-row>
|
||
<el-row :gutter="16">
|
||
<el-col :span="12">
|
||
<el-form-item label="单件重量">
|
||
<el-input v-model="editForm.singleCoilWeight" />
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="12">
|
||
<el-form-item label="交货重量偏差">
|
||
<el-input v-model="editForm.weightDeviation" />
|
||
</el-form-item>
|
||
</el-col>
|
||
</el-row>
|
||
<el-row :gutter="16">
|
||
<el-col :span="24">
|
||
<el-form-item label="其他技术要求">
|
||
<el-input v-model="editForm.otherTechReq" type="textarea" :rows="2" />
|
||
</el-form-item>
|
||
</el-col>
|
||
</el-row>
|
||
<el-row :gutter="16">
|
||
<el-col :span="24">
|
||
<el-form-item label="付款情况说明">
|
||
<el-input v-model="editForm.paymentDesc" type="textarea" :rows="2" />
|
||
</el-form-item>
|
||
</el-col>
|
||
</el-row>
|
||
<el-row :gutter="16">
|
||
<el-col :span="12">
|
||
<el-form-item label="单行排产备注">
|
||
<el-input v-model="editForm.rowRemark" type="textarea" :rows="2" />
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="12">
|
||
<el-form-item label="备注">
|
||
<el-input v-model="editForm.remark" type="textarea" :rows="2" />
|
||
</el-form-item>
|
||
</el-col>
|
||
</el-row>
|
||
</el-form>
|
||
<div slot="footer" class="dialog-footer">
|
||
<el-button :loading="editBtnLoading" type="danger" @click="submitEditForm">确 定</el-button>
|
||
<el-button @click="editDialogVisible = false">取 消</el-button>
|
||
</div>
|
||
</el-dialog>
|
||
|
||
<!-- 接收产需单弹窗 - 配置工序 -->
|
||
<el-dialog
|
||
title="接收产需单 - 配置工序"
|
||
:visible.sync="receiveDialogVisible"
|
||
width="900px"
|
||
append-to-body
|
||
:close-on-click-modal="false"
|
||
>
|
||
<div style="margin-bottom:12px;font-size:13px;color:#e67e22;">
|
||
请为每条明细配置工序,所有明细配置完成后才能接收。
|
||
</div>
|
||
|
||
<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">
|
||
<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>
|
||
|
||
<div slot="footer" class="dialog-footer">
|
||
<el-button :loading="receiveBtnLoading" type="primary" :disabled="!canConfirmReceive" @click="confirmReceive">确 定 接 收</el-button>
|
||
<el-button @click="receiveDialogVisible = false">取 消</el-button>
|
||
</div>
|
||
</el-dialog>
|
||
</div>
|
||
</template>
|
||
|
||
<script>
|
||
import {
|
||
listRequirement,
|
||
updateRequirement
|
||
} from '@/api/aps/requirement'
|
||
import {
|
||
getCrmOrderInfo,
|
||
listScheduleItem,
|
||
updateScheduleItem,
|
||
delScheduleItem,
|
||
receiveScheduleItem,
|
||
saveScheduleItemSort
|
||
} from '@/api/aps/schedule'
|
||
import { PROCESSES } from '@/utils/meta'
|
||
import ProcessSelect from '@/components/KLPService/ProcessSelect/index.vue'
|
||
import draggable from 'vuedraggable'
|
||
|
||
export default {
|
||
name: 'ApsSchedule',
|
||
components: { ProcessSelect, draggable },
|
||
components: { ProcessSelect },
|
||
props: {
|
||
// 由 flow.vue 通过 switchApsPage 传入的参数
|
||
apsParams: {
|
||
type: Object,
|
||
default: null
|
||
}
|
||
},
|
||
data() {
|
||
const today = new Date()
|
||
const y = today.getFullYear()
|
||
const m = String(today.getMonth() + 1).padStart(2, '0')
|
||
const d = String(today.getDate()).padStart(2, '0')
|
||
return {
|
||
queryDate: `${y}-${m}-${d}`,
|
||
activeTab: 'pending',
|
||
loading: false,
|
||
schLoading: false,
|
||
hasQueried: false,
|
||
|
||
// 待审核
|
||
pendingScheduleList: [],
|
||
summaryText: '',
|
||
statusMap: { 1: '待审核', 2: '已接收', 3: '已驳回', 4: '已排产' },
|
||
processOptions: PROCESSES,
|
||
|
||
// 已接收
|
||
acceptedScheduleList: [],
|
||
acceptedLoading: false,
|
||
|
||
// 已排产
|
||
scheduledItemList: [],
|
||
scheduledStepTab: '',
|
||
scheduledOrderMap: {},
|
||
scheduledStepItems: [],
|
||
scheduledSortDirtyMap: {},
|
||
saveSortLoading: false,
|
||
|
||
// 驳回
|
||
rejectDialogVisible: false,
|
||
rejectBtnLoading: false,
|
||
rejectForm: {
|
||
scheduleId: null,
|
||
scheduleNo: '',
|
||
returnReason: ''
|
||
},
|
||
|
||
// 已排产编辑
|
||
editDialogVisible: false,
|
||
editDialogTitle: '编辑排产明细',
|
||
editBtnLoading: false,
|
||
editForm: this.getEmptyEditForm(),
|
||
|
||
// 下钻
|
||
drillDialogVisible: false,
|
||
drillOrder: null,
|
||
|
||
// 接收产需单弹窗
|
||
receiveDialogVisible: false,
|
||
receiveBtnLoading: false,
|
||
receiveScheduleId: null,
|
||
receiveDetailList: []
|
||
}
|
||
},
|
||
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
|
||
)
|
||
},
|
||
scheduledSortDirty() {
|
||
const tab = this.scheduledStepTab
|
||
if (!tab) return false
|
||
return !!this.scheduledSortDirtyMap[tab]
|
||
}
|
||
},
|
||
watch: {
|
||
scheduledStepTab() {
|
||
this.refreshScheduledStepItems()
|
||
}
|
||
},
|
||
watch: {
|
||
// 接收 flow.vue 传入的参数:设置日期并自动查询
|
||
apsParams: {
|
||
handler(val) {
|
||
if (val && val.prodDate) {
|
||
this.queryDate = val.prodDate
|
||
}
|
||
if (val && val.scheduleStatus !== undefined) {
|
||
// 根据产需单状态自动切换 Tab
|
||
// 1=待审核 2=已接收 4=已排产
|
||
const statusToTab = { 1: 'pending', 2: 'accepted', 4: 'scheduled' }
|
||
const tab = statusToTab[val.scheduleStatus] || 'pending'
|
||
this.activeTab = tab
|
||
}
|
||
this.$nextTick(() => {
|
||
this.handleQuery()
|
||
})
|
||
},
|
||
immediate: false
|
||
}
|
||
},
|
||
created() {
|
||
// 优先从路由参数获取(产需单点击"跳转排产"通过 $router.push 传参)
|
||
const query = this.$route.query
|
||
// 其次从 apsParams prop 获取(弹窗内由 flow.vue 切换组件传入)
|
||
const params = this.apsParams
|
||
|
||
let hasExternal = false
|
||
if (query && query.prodDate) {
|
||
this.queryDate = query.prodDate
|
||
hasExternal = true
|
||
if (query.scheduleStatus !== undefined) {
|
||
const statusToTab = { 1: 'pending', 2: 'accepted', 4: 'scheduled' }
|
||
this.activeTab = statusToTab[Number(query.scheduleStatus)] || 'pending'
|
||
}
|
||
} else if (params && params.prodDate) {
|
||
this.queryDate = params.prodDate
|
||
hasExternal = true
|
||
if (params.scheduleStatus !== undefined) {
|
||
const statusToTab = { 1: 'pending', 2: 'accepted', 4: 'scheduled' }
|
||
this.activeTab = statusToTab[params.scheduleStatus] || 'pending'
|
||
}
|
||
}
|
||
this.handleQuery()
|
||
},
|
||
methods: {
|
||
getScheduledItemKey(row) {
|
||
return row && row.scheduleId != null ? String(row.scheduleId) : ''
|
||
},
|
||
refreshScheduledStepItems() {
|
||
const tab = this.scheduledStepTab
|
||
if (!tab) {
|
||
this.scheduledStepItems = []
|
||
return
|
||
}
|
||
const stepItems = this.scheduledItemList.filter(i => this.normalizeStepName(i.stepName) === tab)
|
||
const order = this.scheduledOrderMap[tab]
|
||
if (!Array.isArray(order) || order.length === 0) {
|
||
const keys = stepItems.map(i => this.getScheduledItemKey(i)).filter(Boolean)
|
||
this.$set(this.scheduledOrderMap, tab, keys)
|
||
this.scheduledStepItems = stepItems.slice()
|
||
return
|
||
}
|
||
const indexMap = new Map(order.map((k, idx) => [k, idx]))
|
||
this.scheduledStepItems = stepItems
|
||
.slice()
|
||
.sort((a, b) => {
|
||
const ka = this.getScheduledItemKey(a)
|
||
const kb = this.getScheduledItemKey(b)
|
||
const ia = indexMap.has(ka) ? indexMap.get(ka) : 999999
|
||
const ib = indexMap.has(kb) ? indexMap.get(kb) : 999999
|
||
return ia - ib
|
||
})
|
||
const existKeys = new Set(stepItems.map(i => this.getScheduledItemKey(i)))
|
||
const nextOrder = order.filter(k => existKeys.has(k))
|
||
const missing = stepItems.map(i => this.getScheduledItemKey(i)).filter(k => k && !nextOrder.includes(k))
|
||
if (missing.length) {
|
||
nextOrder.push(...missing)
|
||
}
|
||
if (nextOrder.join('|') !== order.join('|')) {
|
||
this.$set(this.scheduledOrderMap, tab, nextOrder)
|
||
}
|
||
},
|
||
handleScheduledDragEnd() {
|
||
const tab = this.scheduledStepTab
|
||
if (!tab) return
|
||
const keys = (this.scheduledStepItems || []).map(i => this.getScheduledItemKey(i)).filter(Boolean)
|
||
this.$set(this.scheduledOrderMap, tab, keys)
|
||
this.$set(this.scheduledSortDirtyMap, tab, true)
|
||
},
|
||
handleSaveScheduledSort() {
|
||
const tab = this.scheduledStepTab
|
||
if (!tab) return
|
||
const scheduleIds = (this.scheduledStepItems || []).map(i => i.scheduleId).filter(v => v != null)
|
||
if (scheduleIds.length === 0) return
|
||
this.saveSortLoading = true
|
||
saveScheduleItemSort({ scheduleIds }).then(() => {
|
||
this.$modal.msgSuccess('排序已保存')
|
||
this.$set(this.scheduledSortDirtyMap, tab, false)
|
||
this.queryScheduled()
|
||
}).catch(() => {
|
||
this.$modal.msgError('保存排序失败')
|
||
}).finally(() => {
|
||
this.saveSortLoading = false
|
||
})
|
||
},
|
||
getEmptyEditForm() {
|
||
return {
|
||
scheduleId: undefined,
|
||
scheduleNo: '',
|
||
actionId: '',
|
||
prodDate: '',
|
||
scheduleStatus: undefined,
|
||
totalPlanWeight: undefined,
|
||
relContractNo: '',
|
||
businessUser: '',
|
||
businessPhone: '',
|
||
orderDate: '',
|
||
customerName: '',
|
||
deliveryCycle: undefined,
|
||
usePurpose: '',
|
||
productType: '',
|
||
thicknessTolerance: '',
|
||
widthTolerance: '',
|
||
surfaceQuality: '',
|
||
surfaceTreatment: '',
|
||
innerDiameter: '',
|
||
outerDiameter: '',
|
||
packReq: '',
|
||
cutEdgeReq: '',
|
||
singleCoilWeight: '',
|
||
weightDeviation: '',
|
||
otherTechReq: '',
|
||
paymentDesc: '',
|
||
spec: '',
|
||
material: '',
|
||
scheduleWeight: 0,
|
||
productItem: '',
|
||
rowRemark: '',
|
||
remark: ''
|
||
}
|
||
},
|
||
|
||
handleDateChange() {
|
||
this.handleQuery()
|
||
},
|
||
|
||
handleQuery() {
|
||
if (!this.queryDate) {
|
||
this.$message.warning('请选择生产日期')
|
||
return
|
||
}
|
||
this.hasQueried = true
|
||
if (this.activeTab === 'pending') {
|
||
this.queryPending()
|
||
} else if (this.activeTab === 'accepted') {
|
||
this.queryAccepted()
|
||
} else {
|
||
this.queryScheduled()
|
||
}
|
||
},
|
||
|
||
// ====== 待审核 ======
|
||
queryPending() {
|
||
this.loading = true
|
||
this.pendingScheduleList = []
|
||
|
||
listRequirement({
|
||
prodDate: this.queryDate,
|
||
scheduleStatus: 1,
|
||
pageNum: 1,
|
||
pageSize: 999
|
||
}).then(res => {
|
||
const list = res.rows || []
|
||
this.pendingScheduleList = list
|
||
|
||
const totalWeight = list.reduce((sum, sch) => {
|
||
const details = sch.detailList || []
|
||
return sum + details.reduce((s, d) => s + (parseFloat(d.scheduleWeight) || 0), 0)
|
||
}, 0)
|
||
const totalCount = list.reduce((sum, sch) => sum + (sch.detailList || []).length, 0)
|
||
this.summaryText = `共 ${list.length} 个产需单,${totalCount} 条明细,排产总吨数 ${totalWeight.toFixed(3)} 吨`
|
||
}).catch(() => {
|
||
this.pendingScheduleList = []
|
||
this.summaryText = ''
|
||
}).finally(() => {
|
||
this.loading = false
|
||
})
|
||
},
|
||
|
||
// ====== 已接收 ======
|
||
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
|
||
}
|
||
// 初始化接收明细列表,为每条明细添加 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) {
|
||
this.rejectForm.scheduleId = sch.scheduleId
|
||
this.rejectForm.scheduleNo = sch.scheduleNo
|
||
this.rejectForm.returnReason = ''
|
||
this.rejectDialogVisible = true
|
||
this.$nextTick(() => {
|
||
this.$refs.rejectForm && this.$refs.rejectForm.clearValidate()
|
||
})
|
||
},
|
||
|
||
confirmReject() {
|
||
if (!this.rejectForm.returnReason || !this.rejectForm.returnReason.trim()) {
|
||
this.$message.warning('请填写驳回理由')
|
||
return
|
||
}
|
||
this.rejectBtnLoading = true
|
||
updateRequirement({
|
||
scheduleId: this.rejectForm.scheduleId,
|
||
scheduleStatus: 3,
|
||
returnReason: this.rejectForm.returnReason.trim()
|
||
}).then(() => {
|
||
this.$modal.msgSuccess('已驳回')
|
||
this.rejectDialogVisible = false
|
||
this.queryPending()
|
||
}).catch(() => {
|
||
this.$modal.msgError('驳回失败')
|
||
}).finally(() => {
|
||
this.rejectBtnLoading = false
|
||
})
|
||
},
|
||
|
||
// ====== 已排产 ======
|
||
queryScheduled() {
|
||
this.schLoading = true
|
||
this.scheduledItemList = []
|
||
this.scheduledStepTab = ''
|
||
this.scheduledOrderMap = {}
|
||
this.scheduledStepItems = []
|
||
this.scheduledSortDirtyMap = {}
|
||
|
||
listScheduleItem({ prodDate: this.queryDate, pageNum: 1, pageSize: 999 }).then(res => {
|
||
this.scheduledItemList = res.rows || []
|
||
// 默认选中第一个步骤tab
|
||
const tabs = this.scheduledStepTabs
|
||
if (tabs.length > 0) {
|
||
this.scheduledStepTab = tabs[0]
|
||
}
|
||
this.refreshScheduledStepItems()
|
||
const totalWeight = this.scheduledItemList.reduce((sum, d) => {
|
||
const w = parseFloat(d.scheduleWeight)
|
||
return sum + (isNaN(w) ? 0 : w)
|
||
}, 0)
|
||
this.summaryText = `共 ${this.scheduledItemList.length} 条明细,排产总吨数 ${totalWeight.toFixed(3)} 吨`
|
||
}).catch((e) => {
|
||
console.error('queryScheduled error:', e)
|
||
this.scheduledItemList = []
|
||
this.scheduledOrderMap = {}
|
||
this.scheduledStepItems = []
|
||
this.scheduledSortDirtyMap = {}
|
||
this.summaryText = ''
|
||
}).finally(() => {
|
||
this.schLoading = false
|
||
})
|
||
},
|
||
|
||
// 表格内快捷修改排产状态
|
||
handleInlineStatusChange(row, val) {
|
||
this.$set(row, '_statusLoading', true)
|
||
updateScheduleItem({ scheduleId: row.scheduleId, scheduleStatus: val }).then(() => {
|
||
this.$modal.msgSuccess('状态已更新')
|
||
}).catch(() => {
|
||
this.$modal.msgError('状态更新失败')
|
||
this.queryScheduled()
|
||
}).finally(() => {
|
||
this.$set(row, '_statusLoading', false)
|
||
})
|
||
},
|
||
|
||
handleEditScheduled(row) {
|
||
this.editForm = { ...this.getEmptyEditForm(), ...row }
|
||
// 确保 scheduleStatus 为数字类型以匹配下拉选项
|
||
if (this.editForm.scheduleStatus != null && typeof this.editForm.scheduleStatus !== 'number') {
|
||
this.editForm.scheduleStatus = Number(this.editForm.scheduleStatus)
|
||
}
|
||
this.editDialogTitle = '编辑排产明细'
|
||
this.editDialogVisible = true
|
||
this.$nextTick(() => {
|
||
this.$refs.editForm && this.$refs.editForm.clearValidate()
|
||
})
|
||
},
|
||
|
||
submitEditForm() {
|
||
this.editBtnLoading = true
|
||
updateScheduleItem(this.editForm).then(() => {
|
||
this.$modal.msgSuccess('修改成功')
|
||
this.editDialogVisible = false
|
||
this.queryScheduled()
|
||
}).catch(() => {
|
||
this.$modal.msgError('修改失败')
|
||
}).finally(() => {
|
||
this.editBtnLoading = false
|
||
})
|
||
},
|
||
|
||
handleDeleteScheduled(row) {
|
||
this.$confirm(`确认删除排产明细「${row.spec} / ${row.material}」吗?`, '提示', {
|
||
confirmButtonText: '确定',
|
||
cancelButtonText: '取消',
|
||
type: 'warning'
|
||
}).then(() => {
|
||
delScheduleItem(row.scheduleId).then(() => {
|
||
this.$modal.msgSuccess('删除成功')
|
||
this.queryScheduled()
|
||
}).catch(() => {
|
||
this.$modal.msgError('删除失败')
|
||
})
|
||
}).catch(() => {})
|
||
},
|
||
|
||
// ====== 下钻 & 辅助方法 ======
|
||
scheduleTotalWeight(sch) {
|
||
const details = sch.detailList || []
|
||
const total = details.reduce((sum, d) => sum + (parseFloat(d.scheduleWeight) || 0), 0)
|
||
return total.toFixed(3)
|
||
},
|
||
|
||
getActionIdName(actionId) {
|
||
const p = this.processOptions.find(item => String(item.actionId) === String(actionId))
|
||
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) {
|
||
this.$message.warning('未找到关联订单')
|
||
return
|
||
}
|
||
const order = sch.orderList[0]
|
||
getCrmOrderInfo(order.orderId).then(res => {
|
||
this.drillOrder = res.data
|
||
this.drillDialogVisible = true
|
||
}).catch(() => {
|
||
this.$message.warning('未找到来源订单')
|
||
})
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style scoped lang="scss">
|
||
@import './scss/aps-theme.scss';
|
||
|
||
.aps-sch-page {
|
||
height: 100%;
|
||
padding: 6px;
|
||
box-sizing: border-box;
|
||
background: $aps-bg;
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 10px;
|
||
}
|
||
|
||
// 工具栏
|
||
.aps-sch-toolbar {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
padding: 8px 12px;
|
||
background: $aps-white;
|
||
border: 1px solid $aps-border;
|
||
border-radius: $aps-radius;
|
||
box-shadow: $aps-shadow-sm;
|
||
flex-shrink: 0;
|
||
flex-wrap: wrap;
|
||
}
|
||
|
||
.aps-sch-label {
|
||
font-size: 13px;
|
||
font-weight: 600;
|
||
color: $aps-text;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.aps-sch-summary {
|
||
margin-left: auto;
|
||
font-size: 12px;
|
||
color: $aps-text-muted;
|
||
background: $aps-silver-1;
|
||
padding: 4px 12px;
|
||
border-radius: $aps-radius;
|
||
border: 1px solid $aps-border;
|
||
}
|
||
|
||
// 排产卡片
|
||
.aps-sch-card {
|
||
flex: 1;
|
||
display: flex;
|
||
flex-direction: column;
|
||
min-height: 0;
|
||
overflow: hidden;
|
||
|
||
.detail-card-body {
|
||
flex: 1;
|
||
overflow: auto;
|
||
min-height: 0;
|
||
overflow-x: auto;
|
||
}
|
||
}
|
||
|
||
// 表格
|
||
.aps-table {
|
||
width: 100%;
|
||
|
||
::v-deep th {
|
||
background: $aps-silver-1 !important;
|
||
color: $aps-text !important;
|
||
font-weight: 600 !important;
|
||
}
|
||
|
||
::v-deep .el-table__body tr:hover > td {
|
||
background-color: $aps-red-1 !important;
|
||
cursor: pointer;
|
||
}
|
||
|
||
::v-deep td {
|
||
padding: 6px 8px;
|
||
}
|
||
}
|
||
|
||
// 复用卡片/网格变量
|
||
.aps-btn-red {
|
||
@include aps-btn-red;
|
||
}
|
||
|
||
.aps-btn-red.is-disabled,
|
||
.aps-btn-red.is-disabled:hover,
|
||
.aps-btn-red.is-disabled:focus,
|
||
.aps-btn-red.is-disabled:active {
|
||
color: $aps-silver-4;
|
||
background: $aps-silver-2;
|
||
border-color: $aps-silver-3;
|
||
}
|
||
|
||
.detail-card {
|
||
background: $aps-white;
|
||
border: 1px solid $aps-border;
|
||
border-radius: $aps-radius;
|
||
box-shadow: $aps-shadow-sm;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.detail-card-header {
|
||
background: linear-gradient(to right, $aps-red-2, $aps-red-3);
|
||
color: white;
|
||
padding: 6px 12px;
|
||
font-size: 13px;
|
||
font-weight: 600;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.detail-card-body {
|
||
padding: 10px;
|
||
}
|
||
|
||
.form-grid-2 {
|
||
display: grid;
|
||
grid-template-columns: repeat(2, 1fr);
|
||
gap: 10px 16px;
|
||
}
|
||
|
||
.form-field {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 3px;
|
||
}
|
||
|
||
.form-field label {
|
||
font-size: 11px;
|
||
color: $aps-text-muted;
|
||
font-weight: 500;
|
||
}
|
||
|
||
.form-field .field-value {
|
||
font-size: 13px;
|
||
color: $aps-text;
|
||
padding: 4px 0;
|
||
border-bottom: 1px solid $aps-silver-mid;
|
||
}
|
||
|
||
// Tabs 覆盖
|
||
::v-deep .el-tabs__item {
|
||
font-size: 13px;
|
||
padding: 0 14px;
|
||
}
|
||
|
||
::v-deep .el-tabs__header {
|
||
margin: 0;
|
||
}
|
||
|
||
::v-deep .el-tabs__active-bar {
|
||
background-color: $aps-red-2 !important;
|
||
}
|
||
|
||
::v-deep .el-tabs__item.is-active {
|
||
color: $aps-red-2 !important;
|
||
}
|
||
|
||
// ====== 待审核自定义列表 ======
|
||
.aps-sch-list {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 8px;
|
||
padding: 6px;
|
||
}
|
||
|
||
.aps-sch-item {
|
||
background: $aps-white;
|
||
border: 1px solid $aps-border;
|
||
border-radius: $aps-radius;
|
||
box-shadow: $aps-shadow-sm;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.aps-sch-item-header {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
padding: 6px 12px;
|
||
background: linear-gradient(135deg, $aps-silver-1 0%, #f8f9fb 100%);
|
||
border-bottom: 1px solid $aps-border;
|
||
}
|
||
|
||
.aps-sch-item-header-left {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 10px;
|
||
flex: 1;
|
||
min-width: 0;
|
||
}
|
||
|
||
.aps-sch-header-extra {
|
||
font-size: 12px;
|
||
color: $aps-text-muted;
|
||
white-space: nowrap;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
}
|
||
|
||
// 状态标签
|
||
.aps-status-tag {
|
||
display: inline-block;
|
||
font-size: 11px;
|
||
padding: 2px 6px;
|
||
border-radius: 3px;
|
||
font-weight: 500;
|
||
white-space: nowrap;
|
||
|
||
&.status-1 { background: #fff3e0; color: #e67e22; }
|
||
&.status-2 { background: #e8f5e9; color: #388e3c; }
|
||
&.status-3 { background: #fdecea; color: #e74c3c; }
|
||
&.status-4 { background: #e3f2fd; color: #1976d2; }
|
||
}
|
||
|
||
// 产需单信息表
|
||
.req-info-table {
|
||
width: 100%;
|
||
border-collapse: collapse;
|
||
font-size: 12px;
|
||
table-layout: fixed;
|
||
|
||
td {
|
||
padding: 3px 6px;
|
||
border: 1px solid $aps-silver-mid;
|
||
vertical-align: middle;
|
||
word-break: break-word;
|
||
}
|
||
|
||
.req-td-label {
|
||
width: 80px;
|
||
background: $aps-silver-1;
|
||
color: $aps-text-muted;
|
||
font-weight: 500;
|
||
text-align: right;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.req-td-value {
|
||
color: $aps-text;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.req-td-value[colspan] {
|
||
overflow: visible;
|
||
text-overflow: clip;
|
||
white-space: normal;
|
||
}
|
||
}
|
||
|
||
.aps-sch-no {
|
||
color: $aps-red-2;
|
||
font-weight: 600;
|
||
font-size: 13px;
|
||
}
|
||
|
||
.aps-sch-item-actions {
|
||
display: flex;
|
||
gap: 4px;
|
||
flex-shrink: 0;
|
||
margin-left: 8px;
|
||
}
|
||
|
||
// 明细表
|
||
.aps-sch-item-details {
|
||
padding: 0;
|
||
}
|
||
|
||
.aps-sch-detail-header {
|
||
display: flex;
|
||
padding: 7px 14px;
|
||
background: $aps-silver-1;
|
||
border-bottom: 1px solid $aps-border;
|
||
font-size: 14px;
|
||
font-weight: 600;
|
||
color: $aps-text;
|
||
}
|
||
|
||
.aps-sch-detail-row {
|
||
display: flex;
|
||
padding: 7px 14px;
|
||
border-bottom: 1px solid $aps-silver-mid;
|
||
font-size: 14px;
|
||
color: $aps-text;
|
||
cursor: pointer;
|
||
transition: background 0.15s;
|
||
|
||
&:last-child {
|
||
border-bottom: none;
|
||
}
|
||
|
||
&:hover {
|
||
background: $aps-red-1;
|
||
}
|
||
}
|
||
|
||
.aps-sch-detail-col {
|
||
&.col-spec { flex: 1.2; min-width: 100px; }
|
||
&.col-material { flex: 0 0 80px; text-align: center; }
|
||
&.col-weight { flex: 0 0 90px; text-align: right; font-weight: 500; }
|
||
&.col-type { flex: 1; min-width: 80px; padding: 0 8px; }
|
||
&.col-remark { flex: 1; min-width: 80px; }
|
||
}
|
||
|
||
.aps-sch-item-empty {
|
||
padding: 14px;
|
||
text-align: center;
|
||
color: $aps-text-muted;
|
||
font-size: 12px;
|
||
}
|
||
|
||
// ====== 接收弹窗步骤展示 ======
|
||
.receive-steps-flow {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
align-items: center;
|
||
gap: 2px;
|
||
}
|
||
|
||
.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;
|
||
}
|
||
|
||
.receive-step-arrow {
|
||
color: $aps-red-2;
|
||
font-size: 14px;
|
||
padding: 0 2px;
|
||
}
|
||
|
||
// ====== 已排产步骤工序 Tab ======
|
||
.aps-step-tabs {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
padding: 8px 12px;
|
||
gap: 6px;
|
||
background: $aps-silver-1;
|
||
border-bottom: 1px solid $aps-border;
|
||
}
|
||
|
||
.aps-step-tabs-right {
|
||
margin-left: auto;
|
||
display: flex;
|
||
align-items: center;
|
||
}
|
||
|
||
.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);
|
||
}
|
||
}
|
||
|
||
.aps-scheduled-cards {
|
||
padding: 10px 10px 12px;
|
||
}
|
||
|
||
.aps-scheduled-card {
|
||
background: $aps-white;
|
||
border: 1px solid $aps-border;
|
||
border-radius: $aps-radius;
|
||
box-shadow: $aps-shadow-sm;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.aps-scheduled-card + .aps-scheduled-card {
|
||
margin-top: 10px;
|
||
}
|
||
|
||
.aps-scheduled-card-header {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
gap: 12px;
|
||
padding: 8px 12px;
|
||
background: linear-gradient(135deg, $aps-silver-1 0%, #f8f9fb 100%);
|
||
border-bottom: 1px solid $aps-border;
|
||
}
|
||
|
||
.aps-scheduled-card-title {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 10px;
|
||
min-width: 0;
|
||
flex: 1;
|
||
}
|
||
|
||
.aps-drag-handle {
|
||
cursor: grab;
|
||
color: $aps-text-muted;
|
||
font-size: 16px;
|
||
flex: 0 0 auto;
|
||
}
|
||
|
||
.aps-scheduled-no {
|
||
color: $aps-red-2;
|
||
font-weight: 600;
|
||
font-size: 13px;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.aps-scheduled-step {
|
||
color: $aps-text-muted;
|
||
font-size: 12px;
|
||
white-space: nowrap;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
}
|
||
|
||
.aps-scheduled-card-actions {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 4px;
|
||
flex: 0 0 auto;
|
||
}
|
||
|
||
.aps-scheduled-card-body {
|
||
padding: 10px 12px 12px;
|
||
}
|
||
|
||
.aps-scheduled-kv {
|
||
display: grid;
|
||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||
gap: 8px 12px;
|
||
}
|
||
|
||
.kv-item {
|
||
min-width: 0;
|
||
display: flex;
|
||
align-items: baseline;
|
||
gap: 8px;
|
||
}
|
||
|
||
.kv-label {
|
||
flex: 0 0 auto;
|
||
color: $aps-text-muted;
|
||
font-size: 12px;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.kv-value {
|
||
color: $aps-text;
|
||
font-size: 12px;
|
||
font-weight: 500;
|
||
min-width: 0;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.kv-value-wrap {
|
||
white-space: normal;
|
||
overflow: visible;
|
||
text-overflow: clip;
|
||
}
|
||
|
||
.kv-span-2 {
|
||
grid-column: span 2;
|
||
}
|
||
|
||
.kv-span-4 {
|
||
grid-column: 1 / -1;
|
||
}
|
||
|
||
.aps-drag-ghost {
|
||
opacity: 0.6;
|
||
}
|
||
|
||
@media (max-width: 1366px) {
|
||
.aps-scheduled-kv {
|
||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||
}
|
||
}
|
||
|
||
@media (max-width: 1024px) {
|
||
.aps-scheduled-kv {
|
||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||
}
|
||
|
||
.kv-span-2 {
|
||
grid-column: span 2;
|
||
}
|
||
}
|
||
</style>
|