feat(wms): 新增APS排产相关页面及产需单跳转排产功能
1. 新增/aps/requirement和/aps/schedule路由页面 2. 实现产需单列表跳转排产单功能 3. 为排产页面添加参数接收与自动查询逻辑 4. 新增弹窗组件间传递APS页面参数的能力 5. 优化产需单列表UI布局与绑定状态展示
This commit is contained in:
@@ -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',
|
path: '/system/dict-data',
|
||||||
component: Layout,
|
component: Layout,
|
||||||
|
|||||||
@@ -27,16 +27,23 @@
|
|||||||
<div class="list-container">
|
<div class="list-container">
|
||||||
<div v-for="item in reqList" :key="item.scheduleId" class="list-item"
|
<div v-for="item in reqList" :key="item.scheduleId" class="list-item"
|
||||||
:class="{ active: currentReq && currentReq.scheduleId === item.scheduleId }" @click="handleReqClick(item)">
|
:class="{ active: currentReq && currentReq.scheduleId === item.scheduleId }" @click="handleReqClick(item)">
|
||||||
<div class="item-title">
|
<div class="list-item-body">
|
||||||
{{ item.scheduleNo }}
|
<div class="list-item-left">
|
||||||
<span class="badge" :class="'badge-' + statusBadgeType(item.scheduleStatus)">{{
|
<div class="item-title">
|
||||||
statusMap[item.scheduleStatus] || '未知' }}</span>
|
{{ item.scheduleNo }}
|
||||||
</div>
|
<span class="badge" :class="'badge-' + statusBadgeType(item.scheduleStatus)">{{
|
||||||
<div class="item-sub">
|
statusMap[item.scheduleStatus] || '未知' }}</span>
|
||||||
生产日期:{{ item.prodDate || '-' }} 客户:{{ item.customerName || '-' }} 业务员:{{ item.businessUser || '-' }}
|
</div>
|
||||||
</div>
|
<div class="item-sub">
|
||||||
<div class="item-actions">
|
生产日期:{{ item.prodDate || '-' }} 客户:{{ item.customerName || '-' }} 业务员:{{ item.businessUser || '-' }}
|
||||||
<button class="link-btn" @click.stop="handleDelete(item)">删除</button>
|
</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>
|
</div>
|
||||||
<div v-if="reqList.length === 0 && !reqLoading" style="padding: 40px; text-align: center; color: #909399;">
|
<div v-if="reqList.length === 0 && !reqLoading" style="padding: 40px; text-align: center; color: #909399;">
|
||||||
@@ -362,7 +369,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 右侧:产品内容预览 -->
|
<!-- 右侧:产品内容预览 + 已绑定产需单信息 -->
|
||||||
<div style="width: 55%; display: flex; flex-direction: column;">
|
<div style="width: 55%; display: flex; flex-direction: column;">
|
||||||
<div
|
<div
|
||||||
style="font-size:13px; font-weight:600; color:#2c3e50; margin-bottom:8px; padding-left:8px; border-left:3px solid #ff4d4f;">
|
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>总数量:{{ bindPreviewTotalQty }} 吨</span>
|
||||||
<span>含税总额:{{ bindPreviewTotalAmount }}</span>
|
<span>含税总额:{{ bindPreviewTotalAmount }}</span>
|
||||||
</div>
|
</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>
|
||||||
<div v-else
|
<div v-else
|
||||||
style="flex:1; display:flex; align-items:center; justify-content:center; color:#bfbfbf; font-size:13px;">
|
style="flex:1; display:flex; align-items:center; justify-content:center; color:#bfbfbf; font-size:13px;">
|
||||||
@@ -471,6 +497,10 @@ export default {
|
|||||||
bindPreviewProductName: '',
|
bindPreviewProductName: '',
|
||||||
bindPreviewTotalQty: 0,
|
bindPreviewTotalQty: 0,
|
||||||
bindPreviewTotalAmount: 0,
|
bindPreviewTotalAmount: 0,
|
||||||
|
// 当前预览订单已绑定的产需单列表
|
||||||
|
bindPreviewExistingRels: [],
|
||||||
|
// 产需单状态映射(用于展示绑定状态)
|
||||||
|
bindPreviewRelStatusMap: { 0: '草稿', 1: '待审核', 2: '已下达', 3: '已退回' },
|
||||||
|
|
||||||
// 排产明细
|
// 排产明细
|
||||||
detailLoading: false,
|
detailLoading: false,
|
||||||
@@ -559,6 +589,18 @@ export default {
|
|||||||
this.getList()
|
this.getList()
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// 跳转到排产单页面(路由跳转)
|
||||||
|
handleJumpToSchedule(item) {
|
||||||
|
this.$router.push({
|
||||||
|
path: '/aps/schedule',
|
||||||
|
query: {
|
||||||
|
prodDate: item.prodDate || '',
|
||||||
|
scheduleNo: item.scheduleNo || '',
|
||||||
|
scheduleStatus: item.scheduleStatus
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
statusBadgeType(status) {
|
statusBadgeType(status) {
|
||||||
return this.statusBadgeMap[status] || 'gray'
|
return this.statusBadgeMap[status] || 'gray'
|
||||||
},
|
},
|
||||||
@@ -738,6 +780,7 @@ export default {
|
|||||||
this.bindPreviewProductName = ''
|
this.bindPreviewProductName = ''
|
||||||
this.bindPreviewTotalQty = 0
|
this.bindPreviewTotalQty = 0
|
||||||
this.bindPreviewTotalAmount = 0
|
this.bindPreviewTotalAmount = 0
|
||||||
|
this.bindPreviewExistingRels = []
|
||||||
this.searchBindOrders()
|
this.searchBindOrders()
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -780,6 +823,29 @@ export default {
|
|||||||
this.bindPreviewTotalQty = 0
|
this.bindPreviewTotalQty = 0
|
||||||
this.bindPreviewTotalAmount = 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() {
|
confirmBind() {
|
||||||
@@ -1038,6 +1104,31 @@ export default {
|
|||||||
transition: background 0.15s;
|
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 {
|
.list-item:hover {
|
||||||
background: $aps-silver-1;
|
background: $aps-silver-1;
|
||||||
}
|
}
|
||||||
@@ -1063,10 +1154,6 @@ export default {
|
|||||||
margin-bottom: 4px;
|
margin-bottom: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.list-item .item-actions {
|
|
||||||
display: flex;
|
|
||||||
gap: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ====== 徽标 ======
|
// ====== 徽标 ======
|
||||||
.badge {
|
.badge {
|
||||||
|
|||||||
@@ -653,6 +653,13 @@ import ProcessSelect from '@/components/KLPService/ProcessSelect/index.vue'
|
|||||||
export default {
|
export default {
|
||||||
name: 'ApsSchedule',
|
name: 'ApsSchedule',
|
||||||
components: { ProcessSelect },
|
components: { ProcessSelect },
|
||||||
|
props: {
|
||||||
|
// 由 flow.vue 通过 switchApsPage 传入的参数
|
||||||
|
apsParams: {
|
||||||
|
type: Object,
|
||||||
|
default: null
|
||||||
|
}
|
||||||
|
},
|
||||||
data() {
|
data() {
|
||||||
const today = new Date()
|
const today = new Date()
|
||||||
const y = today.getFullYear()
|
const y = today.getFullYear()
|
||||||
@@ -726,8 +733,49 @@ export default {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
watch: {},
|
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() {
|
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()
|
this.handleQuery()
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
|||||||
@@ -44,6 +44,7 @@
|
|||||||
v-if="dynamicComponent && !dialogLoading"
|
v-if="dynamicComponent && !dialogLoading"
|
||||||
:is="dynamicComponent"
|
:is="dynamicComponent"
|
||||||
:key="dialogKey"
|
:key="dialogKey"
|
||||||
|
:aps-params="apsDialogParams"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
@@ -533,6 +534,8 @@ export default {
|
|||||||
dialogLoading: false,
|
dialogLoading: false,
|
||||||
dynamicComponent: null,
|
dynamicComponent: null,
|
||||||
dialogKey: 0,
|
dialogKey: 0,
|
||||||
|
// APS 页面切换参数(传递给子组件)
|
||||||
|
apsDialogParams: null,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -701,6 +704,7 @@ export default {
|
|||||||
this.dialogWidth = dialogWidth
|
this.dialogWidth = dialogWidth
|
||||||
this.dialogLoading = true
|
this.dialogLoading = true
|
||||||
this.dynamicComponent = null
|
this.dynamicComponent = null
|
||||||
|
this.apsDialogParams = params || null
|
||||||
|
|
||||||
this.resolveImport(componentPath)
|
this.resolveImport(componentPath)
|
||||||
.then(module => {
|
.then(module => {
|
||||||
@@ -716,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) {
|
resolveImport(componentPath) {
|
||||||
// 与 src/store/modules/permission.js 的 loadView 同款模式
|
// 与 src/store/modules/permission.js 的 loadView 同款模式
|
||||||
// componentPath 相对于 @/views/,如 'crm/order/index'
|
// componentPath 相对于 @/views/,如 'crm/order/index'
|
||||||
@@ -730,6 +764,7 @@ export default {
|
|||||||
handleDialogClosed() {
|
handleDialogClosed() {
|
||||||
this.dynamicComponent = null
|
this.dynamicComponent = null
|
||||||
this.dialogKey++
|
this.dialogKey++
|
||||||
|
this.apsDialogParams = null
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user