Files
klp-oa/klp-ui/src/views/wms/post/aps/schedule.vue
朱昊天 f42798cf2c Merge remote-tracking branch 'origin/0.8.X' into 0.8.X
# Conflicts:
#	klp-ui/src/views/wms/post/aps/schedule.vue
2026-07-09 17:40:15 +08:00

1737 lines
60 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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