Merge remote-tracking branch 'origin/0.8.X' into 0.8.X

# Conflicts:
#	klp-ui/src/views/wms/post/aps/schedule.vue
This commit is contained in:
朱昊天
2026-07-09 17:40:15 +08:00
22 changed files with 396 additions and 564 deletions

View File

@@ -183,6 +183,33 @@ export const dynamicRoutes = [
}
]
},
// APS 排产独立页面(用于产需单跳转排产单)
{
path: '/aps/requirement',
component: Layout,
hidden: true,
children: [
{
path: '',
component: () => import('@/views/wms/post/aps/requirement'),
name: 'ApsRequirement',
meta: { title: '产需单' }
}
]
},
{
path: '/aps/schedule',
component: Layout,
hidden: true,
children: [
{
path: '',
component: () => import('@/views/wms/post/aps/schedule'),
name: 'ApsSchedule',
meta: { title: '排产单' }
}
]
},
{
path: '/system/dict-data',
component: Layout,

View File

@@ -27,16 +27,23 @@
<div class="list-container">
<div v-for="item in reqList" :key="item.scheduleId" class="list-item"
:class="{ active: currentReq && currentReq.scheduleId === item.scheduleId }" @click="handleReqClick(item)">
<div class="item-title">
{{ item.scheduleNo }}
<span class="badge" :class="'badge-' + statusBadgeType(item.scheduleStatus)">{{
statusMap[item.scheduleStatus] || '未知' }}</span>
</div>
<div class="item-sub">
生产日期{{ item.prodDate || '-' }} 客户{{ item.customerName || '-' }} 业务员{{ item.businessUser || '-' }}
</div>
<div class="item-actions">
<button class="link-btn" @click.stop="handleDelete(item)">删除</button>
<div class="list-item-body">
<div class="list-item-left">
<div class="item-title">
{{ item.scheduleNo }}
<span class="badge" :class="'badge-' + statusBadgeType(item.scheduleStatus)">{{
statusMap[item.scheduleStatus] || '未知' }}</span>
</div>
<div class="item-sub">
生产日期{{ item.prodDate || '-' }} 客户{{ item.customerName || '-' }} 业务员{{ item.businessUser || '-' }}
</div>
<div class="item-actions">
<button class="link-btn" @click.stop="handleDelete(item)">删除</button>
</div>
</div>
<div class="list-item-right">
<el-button size="mini" plain @click.stop="handleJumpToSchedule(item)">跳转排产</el-button>
</div>
</div>
</div>
<div v-if="reqList.length === 0 && !reqLoading" style="padding: 40px; text-align: center; color: #909399;">
@@ -362,7 +369,7 @@
</div>
</div>
<!-- 右侧产品内容预览 -->
<!-- 右侧产品内容预览 + 已绑定产需单信息 -->
<div style="width: 55%; display: flex; flex-direction: column;">
<div
style="font-size:13px; font-weight:600; color:#2c3e50; margin-bottom:8px; padding-left:8px; border-left:3px solid #ff4d4f;">
@@ -382,6 +389,25 @@
<span>总数量{{ bindPreviewTotalQty }} </span>
<span>含税总额{{ bindPreviewTotalAmount }}</span>
</div>
<!-- 已绑定产需单信息 -->
<div v-if="bindPreviewExistingRels.length > 0" style="margin-top:12px; padding-top:10px; border-top:1px dashed #e4e7ed;">
<div style="font-size:12px; font-weight:600; color:#e74c3c; margin-bottom:6px;">
该订单已绑定以下产需单
</div>
<div style="display:flex; flex-wrap:wrap; gap:6px;">
<el-tag
v-for="rel in bindPreviewExistingRels"
:key="rel.scheduleId"
size="small"
:type="rel.scheduleStatus === 3 ? 'danger' : 'info'"
effect="plain"
:style="rel.scheduleStatus === 2 ? 'color:#555;border-color:#ccc;background:#f5f5f5;' : ''"
>
{{ rel.scheduleNo }}
<span style="margin-left:4px; opacity:0.7;">{{ bindPreviewRelStatusMap[rel.scheduleStatus] || '' }}</span>
</el-tag>
</div>
</div>
</div>
<div v-else
style="flex:1; display:flex; align-items:center; justify-content:center; color:#bfbfbf; font-size:13px;">
@@ -471,6 +497,10 @@ export default {
bindPreviewProductName: '',
bindPreviewTotalQty: 0,
bindPreviewTotalAmount: 0,
// 当前预览订单已绑定的产需单列表
bindPreviewExistingRels: [],
// 产需单状态映射(用于展示绑定状态)
bindPreviewRelStatusMap: { 0: '草稿', 1: '待审核', 2: '已下达', 3: '已退回' },
// 排产明细
detailLoading: false,
@@ -559,6 +589,18 @@ export default {
this.getList()
},
// 跳转到排产单页面(路由跳转)
handleJumpToSchedule(item) {
this.$router.push({
path: '/aps/schedule',
query: {
prodDate: item.prodDate || '',
scheduleNo: item.scheduleNo || '',
scheduleStatus: item.scheduleStatus
}
})
},
statusBadgeType(status) {
return this.statusBadgeMap[status] || 'gray'
},
@@ -738,6 +780,7 @@ export default {
this.bindPreviewProductName = ''
this.bindPreviewTotalQty = 0
this.bindPreviewTotalAmount = 0
this.bindPreviewExistingRels = []
this.searchBindOrders()
},
@@ -780,6 +823,29 @@ export default {
this.bindPreviewTotalQty = 0
this.bindPreviewTotalAmount = 0
}
// 查询该订单已关联了哪些产需单
this.bindPreviewExistingRels = []
listRel({ orderId: order.orderId }).then(res => {
const rows = res.rows || []
// 按 scheduleId 去重,并与当前已有的 reqList 匹配获取单号
const seen = new Set()
const reqMap = new Map()
;(this.reqList || []).forEach(r => reqMap.set(r.scheduleId, r))
const unique = []
rows.forEach(r => {
const key = r.scheduleId
if (!key || seen.has(key)) return
seen.add(key)
// 从已加载的 reqList 中找单号,找不到则不展示(已删除或不在当前页)
const match = reqMap.get(key)
if (match) {
unique.push({ ...r, scheduleNo: match.scheduleNo, scheduleStatus: match.scheduleStatus })
}
})
this.bindPreviewExistingRels = unique
}).catch(() => {
this.bindPreviewExistingRels = []
})
},
confirmBind() {
@@ -1038,6 +1104,31 @@ export default {
transition: background 0.15s;
}
.list-item-body {
display: flex;
align-items: stretch;
gap: 8px;
}
.list-item-left {
flex: 1;
min-width: 0;
}
.list-item-right {
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
padding-left: 4px;
}
.item-actions {
display: flex;
gap: 8px;
margin-top: 4px;
}
.list-item:hover {
background: $aps-silver-1;
}
@@ -1063,10 +1154,6 @@ export default {
margin-bottom: 4px;
}
.list-item .item-actions {
display: flex;
gap: 8px;
}
// ====== 徽标 ======
.badge {

View File

@@ -626,7 +626,7 @@
<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 />
@@ -634,8 +634,8 @@
<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"
<ProcessSelect
v-model="scope.row.processId"
placeholder="请选择工序"
@change="(val, data) => handleReceiveProcessChange(scope.row, val, data)"
/>
@@ -682,6 +682,14 @@ 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()
@@ -769,7 +777,49 @@ export default {
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: {

View File

@@ -44,6 +44,7 @@
v-if="dynamicComponent && !dialogLoading"
:is="dynamicComponent"
:key="dialogKey"
:aps-params="apsDialogParams"
/>
</div>
</el-dialog>
@@ -129,8 +130,7 @@ const NODE_EVENT_CONFIG = {
{ id: 'H', label: '选择工艺路线', handler: 'handleOpen', params: { componentPath: 'wms/post/aps/schedule' } },
{ id: 'I', label: '自动生成排产项', handler: 'handleOpen', params: { componentPath: 'wms/post/aps/schedule' } },
{ id: 'J', label: '已排产明细管理', handler: 'handleOpen', params: { componentPath: 'wms/post/aps/schedule' } },
{ id: 'K', label: '按工序绑定钢卷', handler: 'handleOpen', params: { componentPath: 'wms/post/aps/compare' } },
{ id: 'L', label: '执行生产', handler: 'handleOpen', params: { componentPath: 'wms/post/aps/schedule' } },
{ id: 'K', label: '执行生产', handler: 'handleOpen', params: { componentPath: 'wms/post/aps/schedule' } },
],
equipmentRepair: [
{ id: 'A', label: '创建维修计划', handler: 'handleOpen', params: { componentPath: 'wms/post/eqp/index' } },
@@ -164,11 +164,14 @@ const NODE_EVENT_CONFIG = {
inspection: [
{ id: 'A', label: '维护检验方案模板', handler: 'handleOpen', params: { componentPath: 'mes/qc/template/index' } },
{ id: 'B', label: '取样/检验数据录入', handler: 'handleOpen', params: { componentPath: 'mes/qc/inspectionSample/index' } },
{ id: 'D', label: '常规检验录入', handler: 'handleOpen', params: { componentPath: 'mes/qc/inspectionSample/index' } },
{ id: 'B', label: '检验数据录入', handler: 'handleOpen', params: { componentPath: 'mes/qc/inspectionSample/index' } },
{ id: 'D', label: '化学检验数据', handler: 'handleOpen', params: { componentPath: 'mes/qc/inspectionSample/index' } },
{ id: 'E', label: '拉伸检验(专项)', handler: 'handleOpen', params: { componentPath: 'mes/qc/inspectionTensile/index' } },
{ id: 'H', label: '生成质量证明书', handler: 'handleOpen', params: { componentPath: 'mes/qc/certificate/book' } },
{ id: 'I', label: '触发质量评审', handler: 'handleOpen', params: { componentPath: 'mes/qc/qualityReview/index' } },
{ id: 'F', label: '创建审批申请', handler: 'handleOpen', params: { componentPath: 'mes/qc/approval/index' } },
{ id: 'H', label: '审批中', handler: 'handleOpen', params: { componentPath: 'mes/qc/approval/index' } },
{ id: 'J', label: '填写检验数据', handler: 'handleOpen', params: { componentPath: 'mes/qc/approval/index' } },
{ id: 'M', label: '生成质量证明书', handler: 'handleOpen', params: { componentPath: 'mes/qc/certificate/book' } },
{ id: 'N', label: '触发质量评审', handler: 'handleOpen', params: { componentPath: 'mes/qc/qualityReview/index' } },
],
qualityReview: [
@@ -320,9 +323,8 @@ graph TD
H --> I["<b>自动生成排产项</b><br/>按工序步骤分组"]:::p7
I --> J["<b>已排产明细管理</b><br/>按步骤查看/编辑<br/>内联修改排产状态"]:::p7
J --> K["<b>按工序绑定钢卷</b><br/>将现存钢卷绑定到<br/>对应的排产明细"]:::p8
K --> L["<b>执行生产</b>"]:::p9
L --> M(["<b>排产完结</b>"]):::pend
J --> K["<b>执行生产</b>"]:::p8
K --> L(["<b>排产完结</b>"]):::pend
classDef p1 fill:#409eff,stroke:#337ecc,color:#fff,stroke-width:2px
classDef p2 fill:#e6fffa,stroke:#13c2c2,color:#303133,stroke-width:2px
@@ -332,7 +334,6 @@ graph TD
classDef p6 fill:#fffbe6,stroke:#fadb14,color:#606266,stroke-width:2px
classDef p7 fill:#fff0f6,stroke:#eb2f96,color:#303133,stroke-width:2px
classDef p8 fill:#f6ffed,stroke:#52c41a,color:#303133,stroke-width:2px
classDef p9 fill:#f0f5ff,stroke:#597ef7,color:#303133,stroke-width:2px
classDef dec fill:#f9f0ff,stroke:#722ed1,color:#303133,stroke-width:2px
classDef pend fill:#dcf7e8,stroke:#52c41a,color:#303133,stroke-width:2px,rx:10,ry:10
linkStyle default stroke:#bfbfbf,stroke-width:2px
@@ -427,25 +428,34 @@ graph TD
inspection: `
graph TD
A["<b>维护检验方案模板</b><br/>定义检验项/单位/标准"]:::i1
A --> B["<b>取样/检验数据录入</b><br/>选择方案模板<br/>动态生成检验项表格"]:::i2
B --> C{"检验类型"}:::dec
C -->|常规项| D["<b>常规检验录入</b><br/>直接在模板表格<br/>填写检测数据"]:::i3
C -->|拉伸试验| E["<b>拉伸检验(专项)</b><br/>独立试验记录<br/>管理明细数据"]:::i4
D --> F["<b>汇总检验结果</b>"]:::i5
A["<b>维护检验方案模板</b><br/>定义检验项/单位/标准"]:::i1
A --> B["<b>检验数据录入</b><br/>选择方案模板<br/>填写样品信息<br/>录入检验结果"]:::i2
B --> C{"检验类型"}:::dec
C -->|化学检验| D["<b>化学检验数据</b><br/>按模板动态生成<br/>检验项表格并填值"]:::i3
C -->|物理检验| E["<b>拉伸检验(专项)</b><br/>独立试验记录<br/>管理明细数据"]:::i4
D --> F["<b>创建审批申请</b><br/>关联检验记录<br/>填写申请信息"]:::i5
E --> F
F --> G{"判定"}:::dec
G -->|合格| H["<b>生成质量证明书</b><br/>汇总全部检验数据"]:::i6
G -->|不合格| I["<b>触发质量评审</b><br/>进入评审流程"]:::i7
H --> J(["检验归档"]):::iend
F --> G{"审批流转"}:::dec
G -->|送审| H["<b>审批中</b><br/>待审批"]:::i6
H --> I{"审批"}:::dec
I -->|通过| J["<b>填写检验数据</b><br/>录入详细检验结果<br/>含判定"]:::i7
I -->|驳回| K["<b>退回修改</b><br/>重新编辑后送审"]:::i8
K --> F
J --> L{"判定"}:::dec
L -->|合格| M["<b>生成质量证明书</b><br/>管理/编辑/打印<br/>支持化学/物理模板"]:::i9
L -->|不合格| N["<b>触发质量评审</b><br/>进入评审流程"]:::i10
M --> O(["检验归档"]):::iend
classDef i1 fill:#409eff,stroke:#337ecc,color:#fff,stroke-width:2px
classDef i2 fill:#e6fffa,stroke:#13c2c2,color:#303133,stroke-width:2px
classDef i3 fill:#fff7e6,stroke:#fa8c16,color:#303133,stroke-width:2px
classDef i4 fill:#f0f5ff,stroke:#597ef7,color:#303133,stroke-width:2px
classDef i5 fill:#fffbe6,stroke:#fadb14,color:#303133,stroke-width:2px
classDef i6 fill:#f6ffed,stroke:#52c41a,color:#303133,stroke-width:2px
classDef i7 fill:#fff1f0,stroke:#f5222d,color:#303133,stroke-width:2px
classDef i6 fill:#e6f7ff,stroke:#1890ff,color:#303133,stroke-width:2px
classDef i7 fill:#f6ffed,stroke:#52c41a,color:#303133,stroke-width:2px
classDef i8 fill:#fff1f0,stroke:#f5222d,color:#303133,stroke-width:2px
classDef i9 fill:#f0f5ff,stroke:#597ef7,color:#303133,stroke-width:2px
classDef i10 fill:#fff1f0,stroke:#f5222d,color:#303133,stroke-width:2px
classDef dec fill:#f9f0ff,stroke:#722ed1,color:#303133,stroke-width:2px
classDef iend fill:#dcf7e8,stroke:#52c41a,color:#303133,stroke-width:2px,rx:10,ry:10
linkStyle default stroke:#bfbfbf,stroke-width:2px
@@ -524,6 +534,8 @@ export default {
dialogLoading: false,
dynamicComponent: null,
dialogKey: 0,
// APS 页面切换参数(传递给子组件)
apsDialogParams: null,
}
},
@@ -692,6 +704,7 @@ export default {
this.dialogWidth = dialogWidth
this.dialogLoading = true
this.dynamicComponent = null
this.apsDialogParams = params || null
this.resolveImport(componentPath)
.then(module => {
@@ -707,6 +720,36 @@ export default {
})
},
/**
* APS 页面间切换(由子组件调用 this.$parent.switchApsPage
* @param {string} componentPath - 相对于 @/views/ 的路径,如 'wms/post/aps/schedule'
* @param {object} params - 传递给目标组件的参数
* @param {string} title - 弹窗标题
*/
switchApsPage(componentPath, params, title) {
if (!componentPath) {
this.$message.warning('组件路径不能为空')
return
}
this.dialogLoading = true
this.dynamicComponent = null
this.apsDialogParams = params || null
if (title) this.dialogTitle = title
this.resolveImport(componentPath)
.then(module => {
this.dynamicComponent = module.default || module
})
.catch(err => {
console.error('[FlowChart] switchApsPage error:', err)
this.$message.error('组件加载失败: ' + (err.message || '未知错误'))
})
.finally(() => {
this.dialogLoading = false
})
},
resolveImport(componentPath) {
// 与 src/store/modules/permission.js 的 loadView 同款模式
// componentPath 相对于 @/views/,如 'crm/order/index'
@@ -721,6 +764,7 @@ export default {
handleDialogClosed() {
this.dynamicComponent = null
this.dialogKey++
this.apsDialogParams = null
},
},
}