Files
klp-oa/klp-ui/src/views/wms/post/aps/order.vue
jhd 444aedf551 feat(wms): 添加订单页面显示已绑定产需单功能
- 在订单详情页面新增已绑定产需单卡片组件
- 实现产需单表格展示包括单号、生产日期、重量等信息
- 添加产需单状态标签显示功能
- 实现加载已绑定产需单的业务逻辑
- 集成产需单API接口获取相关数据
- 添加空状态提示和错误处理机制
2026-07-10 09:12:38 +08:00

540 lines
20 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="app-container" style="height: calc(100vh - 84px); display: flex;">
<!-- 左侧订单列表 -->
<div class="left-panel" v-loading="orderLoading"
style="width: 30%; border-right: 1px solid #e4e7ed; overflow-y: auto;">
<!-- 筛选区 -->
<div class="filter-section" style="padding: 10px; border-bottom: 1px solid #e4e7ed;">
<!-- 第一行搜索 + 按钮 + 记录数 -->
<div style="display: flex; align-items: center; justify-content: space-between; margin-bottom: 10px;">
<div style="display: flex; align-items: center; gap: 4px; flex-wrap: wrap;">
<el-input v-model="queryParams.keyword" placeholder="请输入关键字" clearable @keyup.enter.native="handleSearch"
style="width: 160px;" />
<el-button class="aps-btn-red" icon="el-icon-search" size="mini" @click="handleSearch">筛选</el-button>
<el-button icon="el-icon-sort" size="mini" @click="toggleMoreFilter"
:class="showMoreFilter ? 'aps-btn-red' : 'aps-btn-silver'"></el-button>
</div>
<div style="font-size: 12px; color: #909399;">
<span class="aps-total-count">{{ total }}</span> 条记录
</div>
</div>
<!-- 第二行日期范围 -->
<div style="display: flex; align-items: center; gap: 16px; flex-wrap: wrap; margin-bottom: 4px;">
<div style="display: flex; align-items: center; gap: 4px; font-size: 13px; color: #606266;">
<span style="white-space: nowrap;">签订日期</span>
<el-date-picker clearable v-model="queryParams.signDateStart" type="date" value-format="yyyy-MM-dd"
placeholder="签订开始" style="width: 150px;" @change="handleSearch" />
<span>~</span>
<el-date-picker clearable v-model="queryParams.signDateEnd" type="date" value-format="yyyy-MM-dd"
placeholder="签订结束" style="width: 150px;" @change="handleSearch" />
</div>
<div style="display: flex; align-items: center; gap: 4px; font-size: 13px; color: #606266;">
<span style="white-space: nowrap;">交货日期</span>
<el-date-picker clearable v-model="queryParams.deliveryDateStart" type="date" value-format="yyyy-MM-dd"
placeholder="交货开始" style="width: 150px;" @change="handleSearch" />
<span>~</span>
<el-date-picker clearable v-model="queryParams.deliveryDateEnd" type="date" value-format="yyyy-MM-dd"
placeholder="交货结束" style="width: 150px;" @change="handleSearch" />
</div>
</div>
<!-- 第三行更多筛选条件 -->
<div v-show="showMoreFilter" class="more-filter"
style="margin-top: 8px; padding-top: 10px; border-top: 1px dashed #e4e7ed;">
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" label-width="80px">
<el-form-item label="订单编号" prop="orderCode">
<el-input v-model="queryParams.orderCode" placeholder="请输入订单编号" clearable
@keyup.enter.native="handleSearch" />
</el-form-item>
<el-form-item label="销售员" prop="salesman">
<el-select v-model="queryParams.salesman" placeholder="请选择销售员" clearable style="width: 140px;">
<el-option v-for="item in dict.type.wip_pack_saleman" :key="item.value" :label="item.label"
:value="item.value" />
</el-select>
</el-form-item>
<el-form-item label="订单状态" prop="orderStatus">
<el-select v-model="queryParams.orderStatus" placeholder="请选择订单状态" clearable style="width: 140px;">
<el-option v-for="(value, key) in ORDER_STATUS" :key="value" :label="key" :value="value" />
</el-select>
</el-form-item>
<el-form-item>
<el-button class="aps-btn-silver" icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
</div>
</div>
<!-- 列表区域 -->
<div class="custom-list">
<div class="list-body">
<div v-for="item in orderList" :key="item.orderId" class="list-item"
:class="{ 'list-item-active': currentOrder && currentOrder.orderId === item.orderId }"
@click="handleOrderClick(item)">
<!-- 第一行订单编号 + 客户公司 -->
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px;">
<div style="display: flex; align-items: center; gap: 8px;">
<div style="font-weight: bold;">{{ item.contractName }}</div>
</div>
<div style="font-size: 12px; color: #606266;">{{ item.companyName }}</div>
</div>
<!-- 第二行销售员 + 合同号 -->
<div style="font-size: 12px; color: #909399; margin-bottom: 6px;">
<span>销售员: {{ item.salesman }}</span>
<span style="margin-left: 20px;">合同号: {{ item.contractCode }}</span>
</div>
<!-- 第三行签订时间 + 交货日期 -->
<div style="font-size: 12px; color: #909399; margin-bottom: 6px;">
<span>签订时间: {{ item.signTime }}</span>
<span style="margin-left: 20px;">交货日期: {{ item.deliveryDate }}</span>
</div>
<!-- 第四行签订地点 + 状态 -->
<div style="display: flex; justify-content: space-between; align-items: center;">
<div style="font-size: 12px; color: #909399;">
签订地点: {{ item.signLocation || '-' }}
</div>
<el-tag :type="statusTagType(item.orderStatus)" size="small">{{ statusLabel(item.orderStatus) }}</el-tag>
</div>
</div>
<div v-if="orderList.length === 0 && !orderLoading"
style="padding: 40px; text-align: center; color: #909399;">
暂无订单数据
</div>
</div>
</div>
<pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNum" :limit.sync="queryParams.pageSize"
@pagination="getList" style="padding: 10px; margin-bottom: 10px !important;" />
</div>
<!-- 右侧内容区域 -->
<div class="right-panel" v-if="currentOrder && currentOrder.orderId"
style="flex: 1; display: flex; flex-direction: column;">
<div class="detail-panel">
<!-- 订单基本信息含甲乙双方卡片 -->
<div class="detail-card">
<div class="detail-card-header">
<span>订单基本信息</span>
</div>
<div class="detail-card-body">
<div class="form-grid-2">
<div class="form-field"><label>销售员</label>
<div class="field-value">{{ currentOrder.salesman }}</div>
</div>
<div class="form-field"><label>合同号</label>
<div class="field-value">{{ currentOrder.contractCode }}</div>
</div>
<div class="form-field"><label>签订时间</label>
<div class="field-value">{{ currentOrder.signTime }}</div>
</div>
<div class="form-field"><label>交货日期</label>
<div class="field-value">{{ currentOrder.deliveryDate }}</div>
</div>
<div class="form-field"><label>订单总金额</label>
<div class="field-value">{{ currentOrder.orderAmount }}</div>
</div>
<div class="form-field"><label>签订地点</label>
<div class="field-value">{{ currentOrder.signLocation }}</div>
</div>
<div class="form-field" style="grid-column:1/3;"><label>备注</label>
<div class="field-value">{{ currentOrder.remark }}</div>
</div>
</div>
<div class="section-divider"></div>
<div class="form-grid-2">
<div class="form-field"><label>供方名称甲方</label>
<div class="field-value">{{ currentOrder.supplier }}</div>
</div>
<div class="form-field"><label>供方地址</label>
<div class="field-value">{{ currentOrder.supplierAddress }}</div>
</div>
<div class="form-field"><label>供方电话</label>
<div class="field-value">{{ currentOrder.supplierPhone }}</div>
</div>
<div class="form-field"><label>供方开户行</label>
<div class="field-value">{{ currentOrder.supplierBank }}</div>
</div>
<div class="form-field"><label>供方账号</label>
<div class="field-value">{{ currentOrder.supplierAccount }}</div>
</div>
<div class="form-field"><label>供方税号</label>
<div class="field-value">{{ currentOrder.supplierTaxNo }}</div>
</div>
<div class="form-field"
style="border-top:1px solid #e8e8e8;padding-top:8px;margin-top:4px;grid-column:1/3;"></div>
<div class="form-field"><label>需方名称乙方</label>
<div class="field-value">{{ currentOrder.customer }}</div>
</div>
<div class="form-field"><label>需方地址</label>
<div class="field-value">{{ currentOrder.customerAddress }}</div>
</div>
<div class="form-field"><label>需方电话</label>
<div class="field-value">{{ currentOrder.customerPhone }}</div>
</div>
<div class="form-field"><label>需方开户行</label>
<div class="field-value">{{ currentOrder.customerBank }}</div>
</div>
<div class="form-field"><label>需方账号</label>
<div class="field-value">{{ currentOrder.customerAccount }}</div>
</div>
<div class="form-field"><label>需方税号</label>
<div class="field-value">{{ currentOrder.customerTaxNo }}</div>
</div>
</div>
</div>
</div>
<!-- 已绑定的产需单卡片 -->
<div class="detail-card">
<div class="detail-card-header">
<span>已绑定的产需单{{ boundReqList.length }} </span>
</div>
<div class="detail-card-body" style="padding:0;">
<el-table v-if="boundReqList.length > 0" :data="boundReqList" border size="small" class="aps-product-table">
<el-table-column label="产需单号" prop="scheduleNo" min-width="140" />
<el-table-column label="生产日期" prop="prodDate" width="100" align="center" />
<el-table-column label="总重量(吨)" prop="totalPlanWeight" width="110" align="right" />
<el-table-column label="占用重量(吨)" prop="relWeight" width="110" align="right" />
<el-table-column label="状态" width="80" align="center">
<template slot-scope="{ row }">
<el-tag :type="reqStatusTag(row.scheduleStatus)" size="small">
{{ reqStatusLabel(row.scheduleStatus) }}
</el-tag>
</template>
</el-table-column>
</el-table>
<el-empty v-else description="暂未绑定产需单" :image-size="50" style="padding: 20px 0;" />
</div>
</div>
<!-- 订单明细卡片 -->
<div class="detail-card">
<div class="detail-card-header">
<span>订单明细{{ productList.length }} </span>
</div>
<div class="detail-card-body" style="padding:0;">
<div v-if="productList.length > 0" class="aps-product-table-wrap">
<div class="aps-product-summary">
<span>产品名称{{ productName }}</span>
<span>总数量{{ totalQuantity }} </span>
<span>含税总额{{ totalTaxTotal }}</span>
</div>
<el-table :data="productList" border size="small" class="aps-product-table">
<el-table-column label="规格" prop="spec" min-width="120" />
<el-table-column label="材质" prop="material" width="100" align="center" />
<el-table-column label="数量(吨)" prop="quantity" width="90" align="right" />
<el-table-column label="含税单价" prop="taxPrice" width="100" align="right" />
<el-table-column label="含税总额" prop="taxTotal" width="100" align="right" />
<el-table-column label="无税单价" prop="noTaxPrice" width="100" align="right" />
<el-table-column label="无税总额" prop="noTaxTotal" width="100" align="right" />
<el-table-column label="税额" prop="taxAmount" width="90" align="right" />
<el-table-column label="备注" prop="remark" min-width="120" />
</el-table>
</div>
<el-empty v-else description="暂无产品明细" />
</div>
</div>
</div>
</div>
<div v-else style="flex: 1; display: flex; flex-direction: column;">
<el-empty description="选择订单后查看内容" />
</div>
</div>
</template>
<script>
import { listCrmOrder } from '@/api/aps/order'
import { listRel, getRequirement } from '@/api/aps/requirement'
import { parseProductContent } from '@/utils/productContent'
import { ORDER_STATUS } from '@/views/crm/js/enum'
import FileList from '@/components/FileList'
export default {
name: 'ApsOrder',
components: { FileList },
dicts: ['wip_pack_saleman'],
data() {
return {
ORDER_STATUS,
showMoreFilter: false,
orderLoading: false,
orderList: [],
total: 0,
currentOrder: null,
boundReqList: [],
productList: [],
productName: '',
totalQuantity: 0,
totalTaxTotal: 0,
queryParams: {
keyword: '',
orderCode: '',
salesman: '',
orderStatus: undefined,
signDateStart: undefined,
signDateEnd: undefined,
deliveryDateStart: undefined,
deliveryDateEnd: undefined,
pageNum: 1,
pageSize: 10
}
}
},
created() {
this.getList()
},
methods: {
toggleMoreFilter() {
this.showMoreFilter = !this.showMoreFilter
},
handleSearch() {
this.queryParams.pageNum = 1
this.getList()
},
resetQuery() {
this.queryParams.orderCode = ''
this.queryParams.salesman = ''
this.queryParams.orderStatus = undefined
this.queryParams.signDateStart = undefined
this.queryParams.signDateEnd = undefined
this.queryParams.deliveryDateStart = undefined
this.queryParams.deliveryDateEnd = undefined
this.handleSearch()
},
getList() {
this.orderLoading = true
listCrmOrder(this.queryParams).then(res => {
this.orderList = res.rows || []
this.total = res.total || 0
}).catch(() => {
this.orderList = []
this.total = 0
}).finally(() => {
this.orderLoading = false
})
},
handleOrderClick(order) {
this.currentOrder = order
this.parseProductContent(order)
this.loadBoundRequirements(order.orderId)
},
parseProductContent(order) {
if (!order || !order.productContent) {
this.productList = []
this.productName = ''
this.totalQuantity = 0
this.totalTaxTotal = 0
return
}
const parsed = parseProductContent(order.productContent)
this.productList = parsed.products || []
this.productName = parsed.productName || ''
this.totalQuantity = parsed.totalQuantity || 0
this.totalTaxTotal = parsed.totalTaxTotal || 0
},
statusTagType(status) {
const map = { 0: 'info', 1: 'warning', 2: 'primary', 3: 'success', 4: 'success' }
return map[status] || 'info'
},
statusLabel(status) {
const labels = { 0: '待生产', 1: '生产中', 2: '部分发货', 3: '已发货', 4: '已签收' }
return labels[status] || '未知'
},
/** 加载已绑定的产需单 */
loadBoundRequirements(orderId) {
this.boundReqList = []
if (!orderId) return
listRel({ orderId }).then(relRes => {
const rels = (relRes.rows || []).filter(r => r.scheduleId)
if (rels.length === 0) return
const scheduleIds = [...new Set(rels.map(r => r.scheduleId))]
Promise.all(scheduleIds.map(id => getRequirement(id).catch(() => null))).then(reqs => {
const reqMap = {}
reqs.forEach(r => {
if (r && r.data) reqMap[r.data.scheduleId] = r.data
})
this.boundReqList = rels.reduce((arr, rel) => {
const req = reqMap[rel.scheduleId]
if (req) {
arr.push({
scheduleNo: req.scheduleNo || '-',
prodDate: req.prodDate || '-',
scheduleStatus: req.scheduleStatus,
totalPlanWeight: req.totalPlanWeight,
relWeight: rel.relWeight
})
}
return arr
}, [])
})
}).catch(() => {
this.boundReqList = []
})
},
reqStatusTag(status) {
const map = { 0: 'info', 1: 'warning', 2: 'success', 3: 'danger' }
return map[status] || 'info'
},
reqStatusLabel(status) {
const labels = { 0: '草稿', 1: '待审核', 2: '已下达', 3: '已退回' }
return labels[status] || '未知'
}
}
}
</script>
<style scoped lang="scss">
@import './scss/aps-theme.scss';
.app-container {
overflow: hidden;
padding: 0;
}
.left-panel {
height: calc(100vh - 84px);
box-sizing: border-box;
overflow-y: auto;
}
.right-panel {
height: calc(100vh - 84px);
overflow: hidden;
min-height: 0;
}
// ====== 左侧列表(参照 ContractList.vue ======
.custom-list {
border: 1px solid #e4e7ed;
border-radius: 4px;
overflow: hidden;
}
.list-item {
padding: 10px;
border-bottom: 2px solid #dddddd;
cursor: pointer;
}
.list-item:hover {
background-color: $aps-silver-1;
}
.list-item-active {
background-color: $aps-red-1;
border-left: 3px solid $aps-red-2;
}
.aps-total-count {
color: $aps-red-2;
font-weight: bold;
}
.aps-btn-red {
@include aps-btn-red;
}
.aps-btn-silver {
@include aps-btn-silver;
}
// ====== 右侧详情面板(参照 HTML 设计稿) ======
.detail-panel {
flex: 1;
overflow-y: scroll;
padding: 16px;
display: flex;
flex-direction: column;
gap: 16px;
background: $aps-bg;
min-height: 0;
}
.detail-card {
background: $aps-white;
border: 1px solid $aps-border;
border-radius: $aps-radius;
box-shadow: $aps-shadow-sm;
}
.detail-card-header {
background: linear-gradient(to right, $aps-red-2, $aps-red-3);
color: white;
padding: 8px 14px;
font-size: 13px;
font-weight: 600;
display: flex;
align-items: center;
justify-content: space-between;
}
.detail-card-body {
padding: 14px;
}
.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;
}
// ====== 产品明细 ======
.aps-detail-title {
font-size: 15px;
font-weight: 600;
color: $aps-silver-5;
border-left: 3px solid $aps-red-2;
padding-left: 8px;
}
.aps-product-table-wrap {
border: 1px solid $aps-silver-3;
border-radius: 4px;
overflow: hidden;
}
.aps-product-table {
width: 100%;
::v-deep th {
background: $aps-silver-1 !important;
color: $aps-silver-5 !important;
font-weight: 600 !important;
}
}
.aps-product-summary {
display: flex;
gap: 24px;
padding: 8px 16px;
background: $aps-silver-1;
border-top: 1px solid $aps-silver-3;
font-size: 13px;
color: $aps-silver-5;
font-weight: 500;
}
</style>