提交基础采购

This commit is contained in:
2025-11-18 16:45:05 +08:00
parent cc9b1c0e92
commit 7c04e13198
77 changed files with 5733 additions and 0 deletions

View File

@@ -0,0 +1,289 @@
import request from '@/utils/request'
// 供应商
export function listSupplier(query) {
return request({
url: '/erp/supplier/list',
method: 'get',
params: query
})
}
export function addSupplier(data) {
return request({
url: '/erp/supplier',
method: 'post',
data
})
}
export function updateSupplier(data) {
return request({
url: '/erp/supplier',
method: 'put',
data
})
}
export function delSupplier(ids) {
return request({
url: `/erp/supplier/${ids}`,
method: 'delete'
})
}
// 供应商价格
export function listSupplierPrice(query) {
return request({
url: '/erp/supplierPrice/list',
method: 'get',
params: query
})
}
export function addSupplierPrice(data) {
return request({
url: '/erp/supplierPrice',
method: 'post',
data
})
}
export function updateSupplierPrice(data) {
return request({
url: '/erp/supplierPrice',
method: 'put',
data
})
}
export function delSupplierPrice(ids) {
return request({
url: `/erp/supplierPrice/${ids}`,
method: 'delete'
})
}
// 采购订单
export function listPurchaseOrder(query) {
return request({
url: '/erp/purchaseOrder/list',
method: 'get',
params: query
})
}
export function addPurchaseOrder(data) {
return request({
url: '/erp/purchaseOrder',
method: 'post',
data
})
}
export function updatePurchaseOrder(data) {
return request({
url: '/erp/purchaseOrder',
method: 'put',
data
})
}
export function delPurchaseOrder(ids) {
return request({
url: `/erp/purchaseOrder/${ids}`,
method: 'delete'
})
}
export function confirmPurchaseOrder(orderId) {
return request({
url: `/erp/purchaseOrder/${orderId}/confirm`,
method: 'put'
})
}
export function partialPurchaseOrder(orderId) {
return request({
url: `/erp/purchaseOrder/${orderId}/partial`,
method: 'put'
})
}
export function completePurchaseOrder(orderId) {
return request({
url: `/erp/purchaseOrder/${orderId}/complete`,
method: 'put'
})
}
export function cancelPurchaseOrder(orderId) {
return request({
url: `/erp/purchaseOrder/${orderId}/cancel`,
method: 'put'
})
}
// 采购订单明细
export function listPurchaseOrderItem(query) {
return request({
url: '/erp/purchaseOrderItem/list',
method: 'get',
params: query
})
}
export function addPurchaseOrderItem(data) {
return request({
url: '/erp/purchaseOrderItem',
method: 'post',
data
})
}
export function updatePurchaseOrderItem(data) {
return request({
url: '/erp/purchaseOrderItem',
method: 'put',
data
})
}
export function delPurchaseOrderItem(ids) {
return request({
url: `/erp/purchaseOrderItem/${ids}`,
method: 'delete'
})
}
// 采购需求分析
export function analyzePurchaseRequirement(data) {
return request({
url: '/erp/purchaseRequirement/analyze',
method: 'post',
data
})
}
// 收货记录
export function listPurchaseReceipt(query) {
return request({
url: '/erp/purchaseReceipt/list',
method: 'get',
params: query
})
}
export function addPurchaseReceipt(data) {
return request({
url: '/erp/purchaseReceipt',
method: 'post',
data
})
}
export function updatePurchaseReceipt(data) {
return request({
url: '/erp/purchaseReceipt',
method: 'put',
data
})
}
export function delPurchaseReceipt(receiptId) {
return request({
url: `/erp/purchaseReceipt/${receiptId}`,
method: 'delete'
})
}
// 退货主表
export function listPurchaseReturn(query) {
return request({
url: '/erp/purchaseReturn/list',
method: 'get',
params: query
})
}
export function addPurchaseReturn(data) {
return request({
url: '/erp/purchaseReturn',
method: 'post',
data
})
}
export function updatePurchaseReturn(data) {
return request({
url: '/erp/purchaseReturn',
method: 'put',
data
})
}
export function delPurchaseReturn(returnIds) {
return request({
url: `/erp/purchaseReturn/${returnIds}`,
method: 'delete'
})
}
// 退货明细
export function listPurchaseReturnItem(query) {
return request({
url: '/erp/purchaseReturnItem/list',
method: 'get',
params: query
})
}
export function addPurchaseReturnItem(data) {
return request({
url: '/erp/purchaseReturnItem',
method: 'post',
data
})
}
export function updatePurchaseReturnItem(data) {
return request({
url: '/erp/purchaseReturnItem',
method: 'put',
data
})
}
export function delPurchaseReturnItem(itemIds) {
return request({
url: `/erp/purchaseReturnItem/${itemIds}`,
method: 'delete'
})
}
// 报表
export function getPurchaseReportSummary(params) {
return request({
url: '/erp/purchaseReport/summary',
method: 'get',
params
})
}
export function getPurchasePriceTrend(params) {
return request({
url: '/erp/purchaseReport/trend',
method: 'get',
params
})
}
export function getSupplierQuality(params) {
return request({
url: '/erp/purchaseReport/supplierQuality',
method: 'get',
params
})
}

View File

@@ -0,0 +1,394 @@
<template>
<div class="erp-order-page">
<el-card shadow="never" class="panel-card">
<div slot="header" class="panel-header">
<span>采购订单列表</span>
<div>
<el-button type="primary" size="mini" @click="openOrderDialog()">新增订单</el-button>
<el-button size="mini" @click="openItemDialog()">维护明细</el-button>
</div>
</div>
<div class="toolbar">
<el-input v-model="orderQuery.orderCode" placeholder="订单编号" size="small" clearable class="toolbar-input" />
<el-input v-model="orderQuery.supplierId" placeholder="供应商ID" size="small" clearable class="toolbar-input" />
<el-select v-model="orderQuery.orderStatus" placeholder="状态" size="small" clearable class="toolbar-input">
<el-option label="草稿" :value="0" />
<el-option label="执行中" :value="1" />
<el-option label="部分到货" :value="2" />
<el-option label="已完成" :value="3" />
<el-option label="已取消" :value="4" />
</el-select>
<el-date-picker
v-model="orderRange"
type="daterange"
unlink-panels
value-format="yyyy-MM-dd"
start-placeholder="下单开始"
end-placeholder="下单结束"
size="small"
/>
<el-button size="small" type="primary" @click="loadOrders">查询</el-button>
<el-button size="small" @click="resetOrderQuery">重置</el-button>
</div>
<el-table :data="orderList" border stripe highlight-current-row size="small" v-loading="orderLoading">
<el-table-column prop="orderCode" label="订单编号" width="150" fixed="left" />
<el-table-column prop="supplierId" label="供应商ID" width="120" />
<el-table-column prop="orderDate" label="下单日期" width="120" />
<el-table-column prop="expectedArrival" label="期望到货" width="120" />
<el-table-column prop="orderType" label="类型" width="120" />
<el-table-column prop="totalAmount" label="金额" width="120" />
<el-table-column label="状态" width="120">
<template slot-scope="scope">
<el-tag :type="statusTag(scope.row.orderStatus)" size="mini">{{ statusText(scope.row.orderStatus) }}</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" width="360" fixed="right">
<template slot-scope="scope">
<el-button type="text" size="mini" @click="openOrderDialog(scope.row)">编辑</el-button>
<el-button type="text" size="mini" @click="handleDeleteOrder(scope.row)" style="color:#c0392b">删除</el-button>
<el-divider direction="vertical" />
<el-button type="text" size="mini" @click="confirmOrder(scope.row)">下达</el-button>
<el-button type="text" size="mini" @click="partialOrder(scope.row)">部分到货</el-button>
<el-button type="text" size="mini" @click="completeOrder(scope.row)">完成</el-button>
<el-button type="text" size="mini" @click="cancelOrder(scope.row)">取消</el-button>
</template>
</el-table-column>
</el-table>
<pagination
v-show="orderTotal > 0"
:total="orderTotal"
:page.sync="orderQuery.pageNum"
:limit.sync="orderQuery.pageSize"
@pagination="loadOrders"
/>
</el-card>
<!-- 订单弹窗 -->
<el-dialog :title="orderDialog.title" :visible.sync="orderDialog.visible" width="600px">
<el-form :model="orderDialog.form" :rules="orderRules" ref="orderForm" label-width="100px" size="small">
<el-row :gutter="12">
<el-col :span="12">
<el-form-item label="订单编号" prop="orderCode">
<el-input v-model="orderDialog.form.orderCode" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="供应商ID" prop="supplierId">
<el-input v-model="orderDialog.form.supplierId" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="下单日期" prop="orderDate">
<el-date-picker v-model="orderDialog.form.orderDate" type="date" value-format="yyyy-MM-dd" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="期望到货">
<el-date-picker v-model="orderDialog.form.expectedArrival" type="date" value-format="yyyy-MM-dd" />
</el-form-item>
</el-col>
</el-row>
<el-form-item label="订单类型">
<el-input v-model="orderDialog.form.orderType" />
</el-form-item>
<el-form-item label="金额">
<el-input-number v-model="orderDialog.form.totalAmount" :min="0" :precision="2" />
</el-form-item>
<el-form-item label="备注">
<el-input type="textarea" v-model="orderDialog.form.remark" />
</el-form-item>
</el-form>
<div slot="footer">
<el-button @click="orderDialog.visible = false"> </el-button>
<el-button type="primary" @click="submitOrder"> </el-button>
</div>
</el-dialog>
<!-- 明细弹窗 -->
<el-dialog :title="itemDialog.title" :visible.sync="itemDialog.visible" width="640px">
<div class="toolbar">
<el-input v-model="itemQuery.orderId" placeholder="订单ID" size="small" clearable class="toolbar-input" />
<el-button size="small" type="primary" @click="loadItems">查询</el-button>
<el-button size="small" @click="resetItemQuery">重置</el-button>
<div class="toolbar-spacer"></div>
<el-button type="primary" size="small" @click="openItemForm()">新增明细</el-button>
</div>
<el-table :data="itemList" border size="small" v-loading="itemLoading">
<el-table-column prop="orderId" label="订单ID" width="120" />
<el-table-column prop="materialTypeCode" label="物料类型" width="150" />
<el-table-column prop="specification" label="规格" min-width="150" />
<el-table-column prop="quantity" label="数量" width="100" />
<el-table-column prop="unitPrice" label="单价" width="100" />
<el-table-column label="操作" width="150">
<template slot-scope="scope">
<el-button type="text" size="mini" @click="openItemForm(scope.row)">编辑</el-button>
<el-button type="text" size="mini" @click="handleDeleteItem(scope.row)" style="color:#c0392b">删除</el-button>
</template>
</el-table-column>
</el-table>
<pagination
v-show="itemTotal > 0"
:total="itemTotal"
:page.sync="itemQuery.pageNum"
:limit.sync="itemQuery.pageSize"
@pagination="loadItems"
/>
</el-dialog>
<!-- 明细编辑 -->
<el-dialog :title="itemFormDialog.title" :visible.sync="itemFormDialog.visible" width="520px">
<el-form :model="itemFormDialog.form" :rules="itemRules" ref="itemForm" label-width="110px" size="small">
<el-form-item label="订单ID" prop="orderId">
<el-input v-model="itemFormDialog.form.orderId" />
</el-form-item>
<el-form-item label="物料类型" prop="materialTypeCode">
<el-input v-model="itemFormDialog.form.materialTypeCode" />
</el-form-item>
<el-form-item label="规格">
<el-input v-model="itemFormDialog.form.specification" />
</el-form-item>
<el-form-item label="采购数量" prop="quantity">
<el-input-number v-model="itemFormDialog.form.quantity" :min="0" :precision="3" />
</el-form-item>
<el-form-item label="含税单价">
<el-input-number v-model="itemFormDialog.form.unitPrice" :min="0" :precision="2" />
</el-form-item>
<el-form-item label="备注">
<el-input type="textarea" v-model="itemFormDialog.form.remark" />
</el-form-item>
</el-form>
<div slot="footer">
<el-button @click="itemFormDialog.visible = false"> </el-button>
<el-button type="primary" @click="submitItem"> </el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import {
listPurchaseOrder,
addPurchaseOrder,
updatePurchaseOrder,
delPurchaseOrder,
confirmPurchaseOrder,
partialPurchaseOrder,
completePurchaseOrder,
cancelPurchaseOrder,
listPurchaseOrderItem,
addPurchaseOrderItem,
updatePurchaseOrderItem,
delPurchaseOrderItem
} from '@/api/erp/purchase'
export default {
name: 'ErpPurchaseOrder',
data() {
return {
orderQuery: { pageNum: 1, pageSize: 10, orderCode: null, supplierId: null, orderStatus: null },
orderRange: [],
orderList: [],
orderTotal: 0,
orderLoading: false,
orderDialog: { visible: false, title: '', form: {} },
orderRules: {
orderCode: [{ required: true, message: '请输入订单编号', trigger: 'blur' }],
supplierId: [{ required: true, message: '请输入供应商ID', trigger: 'blur' }]
},
itemDialog: { visible: false, title: '订单明细' },
itemQuery: { pageNum: 1, pageSize: 10, orderId: null },
itemList: [],
itemTotal: 0,
itemLoading: false,
itemFormDialog: { visible: false, title: '', form: {} },
itemRules: {
orderId: [{ required: true, message: '请输入订单ID', trigger: 'blur' }],
materialTypeCode: [{ required: true, message: '请输入物料类型', trigger: 'blur' }],
quantity: [{ required: true, message: '请输入数量', trigger: 'blur' }]
}
}
},
created() {
this.loadOrders()
},
methods: {
loadOrders() {
const query = { ...this.orderQuery }
if (this.orderRange && this.orderRange.length === 2) {
query.beginTime = this.orderRange[0]
query.endTime = this.orderRange[1]
}
this.orderLoading = true
listPurchaseOrder(query)
.then(res => {
this.orderList = res.rows || []
this.orderTotal = res.total || 0
})
.finally(() => {
this.orderLoading = false
})
},
resetOrderQuery() {
this.orderQuery = { pageNum: 1, pageSize: 10, orderCode: null, supplierId: null, orderStatus: null }
this.orderRange = []
this.loadOrders()
},
openOrderDialog(row) {
if (row) {
this.orderDialog.form = { ...row }
this.orderDialog.title = '编辑采购订单'
} else {
this.orderDialog.form = { orderCode: '', supplierId: '', orderDate: '', orderStatus: 0 }
this.orderDialog.title = '新增采购订单'
}
this.orderDialog.visible = true
this.$nextTick(() => this.$refs.orderForm && this.$refs.orderForm.clearValidate())
},
submitOrder() {
this.$refs.orderForm.validate(valid => {
if (!valid) return
const api = this.orderDialog.form.orderId ? updatePurchaseOrder : addPurchaseOrder
api(this.orderDialog.form).then(() => {
this.$message.success('保存成功')
this.orderDialog.visible = false
this.loadOrders()
})
})
},
handleDeleteOrder(row) {
this.$confirm('确定删除该订单吗?', '提示').then(() => {
return delPurchaseOrder(row.orderId)
}).then(() => {
this.$message.success('删除成功')
this.loadOrders()
})
},
confirmOrder(row) {
confirmPurchaseOrder(row.orderId).then(() => {
this.$message.success('已下达')
this.loadOrders()
})
},
partialOrder(row) {
partialPurchaseOrder(row.orderId).then(() => {
this.$message.success('已标记部分到货')
this.loadOrders()
})
},
completeOrder(row) {
completePurchaseOrder(row.orderId).then(() => {
this.$message.success('订单已完成')
this.loadOrders()
})
},
cancelOrder(row) {
cancelPurchaseOrder(row.orderId).then(() => {
this.$message.success('订单已取消')
this.loadOrders()
})
},
statusText(status) {
switch (status) {
case 0: return '草稿'
case 1: return '执行中'
case 2: return '部分到货'
case 3: return '已完成'
case 4: return '已取消'
default: return '-'
}
},
statusTag(status) {
const map = {
0: 'info',
1: 'primary',
2: 'warning',
3: 'success',
4: 'danger'
}
return map[status] || 'info'
},
openItemDialog() {
this.itemDialog.visible = true
this.loadItems()
},
loadItems() {
this.itemLoading = true
listPurchaseOrderItem(this.itemQuery)
.then(res => {
this.itemList = res.rows || []
this.itemTotal = res.total || 0
})
.finally(() => {
this.itemLoading = false
})
},
resetItemQuery() {
this.itemQuery = { pageNum: 1, pageSize: 10, orderId: null }
this.loadItems()
},
openItemForm(row) {
if (row) {
this.itemFormDialog.form = { ...row }
this.itemFormDialog.title = '编辑明细'
} else {
this.itemFormDialog.form = { orderId: '', materialTypeCode: '', quantity: 0 }
this.itemFormDialog.title = '新增明细'
}
this.itemFormDialog.visible = true
this.$nextTick(() => this.$refs.itemForm && this.$refs.itemForm.clearValidate())
},
submitItem() {
this.$refs.itemForm.validate(valid => {
if (!valid) return
const api = this.itemFormDialog.form.itemId ? updatePurchaseOrderItem : addPurchaseOrderItem
api(this.itemFormDialog.form).then(() => {
this.$message.success('保存成功')
this.itemFormDialog.visible = false
this.loadItems()
})
})
},
handleDeleteItem(row) {
this.$confirm('确定删除该明细吗?', '提示').then(() => {
return delPurchaseOrderItem(row.itemId)
}).then(() => {
this.$message.success('删除成功')
this.loadItems()
})
}
}
}
</script>
<style lang="scss" scoped>
.erp-order-page {
padding: 16px;
background: #eef1f3;
min-height: 100%;
}
.panel-card {
border: 1px solid #d0d5d8;
}
.panel-header {
display: flex;
justify-content: space-between;
align-items: center;
font-weight: 600;
color: #1f2d3d;
}
.toolbar {
display: flex;
align-items: center;
margin-bottom: 12px;
flex-wrap: wrap;
.toolbar-input {
width: 150px;
margin-right: 8px;
margin-bottom: 6px;
}
}
.toolbar-spacer {
flex: 1;
}
</style>

View File

@@ -0,0 +1,734 @@
<template>
<div class="erp-purchase-page">
<el-row :gutter="16" class="summary-row">
<el-col :span="6">
<div class="summary-card">
<p class="label">采购总金额</p>
<p class="value">{{ summary.totalAmount | formatAmount }}</p>
</div>
</el-col>
<el-col :span="6">
<div class="summary-card">
<p class="label">计划采购量</p>
<p class="value">{{ summary.suggestionTotal }} </p>
</div>
</el-col>
<el-col :span="6">
<div class="summary-card">
<p class="label">待处理订单</p>
<p class="value">{{ summary.pendingOrder }}</p>
</div>
</el-col>
<el-col :span="6">
<div class="summary-card">
<p class="label">供应商数量</p>
<p class="value">{{ summary.supplierBrief.length }}</p>
</div>
</el-col>
</el-row>
<el-card class="panel-card" shadow="never">
<div slot="header" class="panel-header">
<span>采购需求分析</span>
<div class="panel-actions">
<el-switch v-model="persistResult" active-text="写入建议表" inactive-text="仅计算" />
<el-button type="primary" size="mini" @click="handleAnalyze" :loading="analysisLoading">执行分析</el-button>
</div>
</div>
<el-table :data="mappingRows" border size="small" class="mapping-table">
<el-table-column label="产品ID" width="160">
<template slot-scope="scope">
<el-input v-model="scope.row.productId" placeholder="产品ID" />
</template>
</el-table-column>
<el-table-column label="原料ID" width="160">
<template slot-scope="scope">
<el-input v-model="scope.row.rawMaterialId" placeholder="原料ID" />
</template>
</el-table-column>
<el-table-column label="转换率" width="140">
<template slot-scope="scope">
<el-input-number v-model="scope.row.conversionRate" :min="0" :max="1" :step="0.01" />
</template>
</el-table-column>
<el-table-column label="操作" width="120">
<template slot-scope="scope">
<el-button type="text" @click="removeMappingRow(scope.$index)" :disabled="mappingRows.length === 1">移除</el-button>
</template>
</el-table-column>
</el-table>
<div class="mapping-toolbar">
<el-button icon="el-icon-plus" size="mini" @click="addMappingRow">新增映射</el-button>
</div>
<el-table
:data="analysisData"
v-loading="analysisLoading"
size="small"
border
class="analysis-table"
show-summary
:summary-method="analysisSummary"
>
<el-table-column label="产品" prop="productName" min-width="180">
<template slot-scope="scope">
<div class="cell-title">{{ scope.row.productName || '-' }}</div>
<div class="cell-sub">ID: {{ scope.row.productId || '-' }}</div>
</template>
</el-table-column>
<el-table-column label="规格" prop="specification" min-width="140" />
<el-table-column label="销售需求(吨)" prop="salesDemand" min-width="120" />
<el-table-column label="成品库存/卷" min-width="140">
<template slot-scope="scope">
<div>{{ scope.row.productStockWeight }}</div>
<div class="cell-sub">{{ scope.row.productStockCoilCount }} </div>
</template>
</el-table-column>
<el-table-column label="原料折算(吨)" prop="rawStockConverted" min-width="120" />
<el-table-column label="在途折算(吨)" prop="inTransitConverted" min-width="120" />
<el-table-column label="待下达折算(吨)" prop="pendingConverted" min-width="120" />
<el-table-column label="建议采购(吨)" prop="suggestedPurchase" min-width="120" />
</el-table>
</el-card>
<el-card class="panel-card" shadow="never">
<div slot="header" class="panel-header">
<span>执行控制</span>
</div>
<el-tabs v-model="activeTab" type="border-card">
<el-tab-pane label="收货记录" name="receipt">
<div class="toolbar">
<el-input v-model="receiptQuery.orderId" placeholder="订单ID" size="small" clearable class="toolbar-input" />
<el-input v-model="receiptQuery.itemId" placeholder="明细ID" size="small" clearable class="toolbar-input" />
<el-button size="small" type="primary" @click="loadReceipts">查询</el-button>
<el-button size="small" @click="resetReceiptQuery">重置</el-button>
<div class="toolbar-spacer"></div>
<el-button type="primary" size="small" @click="openReceiptDialog()">新增收货</el-button>
</div>
<el-table :data="receiptList" v-loading="receiptLoading" border size="small">
<el-table-column prop="receiptId" label="ID" width="80" />
<el-table-column prop="orderId" label="订单ID" width="100" />
<el-table-column prop="itemId" label="明细ID" width="100" />
<el-table-column prop="receivedQty" label="收货数量" width="100" />
<el-table-column prop="qualityResult" label="质检结果" width="120">
<template slot-scope="scope">
<el-tag :type="scope.row.qualityResult === 'NG' ? 'danger' : 'success'" size="mini">
{{ scope.row.qualityResult || '合格' }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="receiptTime" label="收货时间" width="160" />
<el-table-column label="操作" width="160">
<template slot-scope="scope">
<el-button type="text" size="mini" @click="openReceiptDialog(scope.row)">编辑</el-button>
<el-button type="text" size="mini" @click="handleDeleteReceipt(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<pagination
v-show="receiptTotal > 0"
:total="receiptTotal"
:page.sync="receiptQuery.pageNum"
:limit.sync="receiptQuery.pageSize"
@pagination="loadReceipts"
/>
</el-tab-pane>
<el-tab-pane label="退货管理" name="return">
<div class="toolbar">
<el-input v-model="returnQuery.orderId" placeholder="订单ID" size="small" clearable class="toolbar-input" />
<el-select v-model="returnQuery.status" placeholder="状态" size="small" clearable class="toolbar-input">
<el-option label="草稿" :value="0" />
<el-option label="完成" :value="1" />
</el-select>
<el-button size="small" type="primary" @click="loadReturns">查询</el-button>
<el-button size="small" @click="resetReturnQuery">重置</el-button>
<div class="toolbar-spacer"></div>
<el-button type="primary" size="small" @click="openReturnDialog()">新增退货</el-button>
<el-button size="small" @click="openReturnItemDialog()">退货明细</el-button>
</div>
<el-table :data="returnList" v-loading="returnLoading" border size="small">
<el-table-column prop="returnId" label="退货单ID" width="120" />
<el-table-column prop="orderId" label="订单ID" width="120" />
<el-table-column prop="returnType" label="类型" width="120" />
<el-table-column prop="reason" label="原因" min-width="150" />
<el-table-column prop="status" label="状态" width="120">
<template slot-scope="scope">
<el-tag :type="scope.row.status === 1 ? 'success' : 'info'" size="mini">
{{ scope.row.status === 1 ? '完成' : '草稿' }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" width="160">
<template slot-scope="scope">
<el-button type="text" size="mini" @click="openReturnDialog(scope.row)">编辑</el-button>
<el-button type="text" size="mini" @click="handleDeleteReturn(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<pagination
v-show="returnTotal > 0"
:total="returnTotal"
:page.sync="returnQuery.pageNum"
:limit.sync="returnQuery.pageSize"
@pagination="loadReturns"
/>
</el-tab-pane>
</el-tabs>
</el-card>
<el-card class="panel-card" shadow="never">
<div slot="header" class="panel-header">
<span>采购报表</span>
<div class="panel-actions">
<el-date-picker
v-model="reportRange"
type="monthrange"
unlink-panels
value-format="yyyy-MM"
range-separator=""
start-placeholder="开始月份"
end-placeholder="结束月份"
@change="loadReports"
size="small"
/>
</div>
</div>
<el-row :gutter="16">
<el-col :span="8">
<div class="report-block">
<h4>供应商采购额TOP</h4>
<el-table :data="summary.supplierBrief" size="mini" height="220" border>
<el-table-column prop="supplierId" label="供应商ID" width="120" />
<el-table-column prop="totalAmount" label="金额" />
<el-table-column prop="orderCount" label="订单" width="80" />
</el-table>
</div>
</el-col>
<el-col :span="8">
<div class="report-block">
<h4>价格趋势</h4>
<el-table :data="priceTrend" size="mini" height="220" border>
<el-table-column prop="period" label="月份" width="120" />
<el-table-column prop="materialCode" label="物料编码" width="150" />
<el-table-column prop="avgPrice" label="平均含税单价" />
</el-table>
</div>
</el-col>
<el-col :span="8">
<div class="report-block">
<h4>供应商退货率</h4>
<el-table :data="supplierQuality" size="mini" height="220" border>
<el-table-column prop="supplierId" label="供应商ID" width="120" />
<el-table-column prop="receivedQty" label="收货量" />
<el-table-column prop="returnQty" label="退货量" />
<el-table-column label="退货率">
<template slot-scope="scope">
{{ formatPercent(scope.row.returnRate) }}
</template>
</el-table-column>
</el-table>
</div>
</el-col>
</el-row>
</el-card>
<!-- 收货弹窗 -->
<el-dialog :title="receiptDialog.title" :visible.sync="receiptDialog.visible" width="480px">
<el-form :model="receiptDialog.form" :rules="receiptRules" ref="receiptForm" label-width="100px" size="small">
<el-form-item label="订单ID" prop="orderId">
<el-input v-model="receiptDialog.form.orderId" />
</el-form-item>
<el-form-item label="明细ID" prop="itemId">
<el-input v-model="receiptDialog.form.itemId" />
</el-form-item>
<el-form-item label="收货数量" prop="receivedQty">
<el-input-number v-model="receiptDialog.form.receivedQty" :min="0" :precision="3" />
</el-form-item>
<el-form-item label="质检结果" prop="qualityResult">
<el-select v-model="receiptDialog.form.qualityResult" clearable placeholder="默认合格">
<el-option label="合格" value="OK" />
<el-option label="不合格" value="NG" />
</el-select>
</el-form-item>
<el-form-item label="收货时间">
<el-date-picker
v-model="receiptDialog.form.receiptTime"
type="datetime"
value-format="yyyy-MM-dd HH:mm:ss"
placeholder="自动取当前"
/>
</el-form-item>
<el-form-item label="备注">
<el-input type="textarea" v-model="receiptDialog.form.remark" />
</el-form-item>
</el-form>
<div slot="footer">
<el-button @click="receiptDialog.visible = false"> </el-button>
<el-button type="primary" @click="submitReceipt"> </el-button>
</div>
</el-dialog>
<!-- 退货弹窗 -->
<el-dialog :title="returnDialog.title" :visible.sync="returnDialog.visible" width="500px">
<el-form :model="returnDialog.form" :rules="returnRules" ref="returnForm" label-width="100px" size="small">
<el-form-item label="订单ID" prop="orderId">
<el-input v-model="returnDialog.form.orderId" />
</el-form-item>
<el-form-item label="退货类型">
<el-select v-model="returnDialog.form.returnType" clearable>
<el-option label="质量问题" value="QUALITY" />
<el-option label="数量错误" value="QTY" />
<el-option label="规格不符" value="SPEC" />
<el-option label="其他" value="OTHER" />
</el-select>
</el-form-item>
<el-form-item label="退货原因">
<el-input type="textarea" v-model="returnDialog.form.reason" />
</el-form-item>
<el-form-item label="状态">
<el-select v-model="returnDialog.form.status">
<el-option label="草稿" :value="0" />
<el-option label="完成" :value="1" />
</el-select>
</el-form-item>
<el-form-item label="备注">
<el-input type="textarea" v-model="returnDialog.form.remark" />
</el-form-item>
</el-form>
<div slot="footer">
<el-button @click="returnDialog.visible = false"> </el-button>
<el-button type="primary" @click="submitReturn"> </el-button>
</div>
</el-dialog>
<!-- 退货明细弹窗 -->
<el-dialog :title="returnItemDialog.title" :visible.sync="returnItemDialog.visible" width="520px">
<div class="toolbar">
<el-input v-model="returnItemQuery.returnId" placeholder="退货单ID" size="small" clearable class="toolbar-input" />
<el-input v-model="returnItemQuery.itemId" placeholder="订单明细ID" size="small" clearable class="toolbar-input" />
<el-button size="small" type="primary" @click="loadReturnItems">查询</el-button>
<el-button size="small" @click="resetReturnItemQuery">重置</el-button>
<div class="toolbar-spacer"></div>
<el-button type="primary" size="small" @click="openReturnItemForm()">新增明细</el-button>
</div>
<el-table :data="returnItemList" v-loading="returnItemLoading" border size="small">
<el-table-column prop="returnItemId" label="ID" width="80" />
<el-table-column prop="returnId" label="退货单ID" width="100" />
<el-table-column prop="itemId" label="订单明细ID" width="120" />
<el-table-column prop="returnQty" label="退货数量" width="120" />
<el-table-column label="操作" width="140">
<template slot-scope="scope">
<el-button type="text" size="mini" @click="openReturnItemForm(scope.row)">编辑</el-button>
<el-button type="text" size="mini" @click="handleDeleteReturnItem(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<pagination
v-show="returnItemTotal > 0"
:total="returnItemTotal"
:page.sync="returnItemQuery.pageNum"
:limit.sync="returnItemQuery.pageSize"
@pagination="loadReturnItems"
/>
</el-dialog>
<!-- 退货明细编辑 -->
<el-dialog :title="returnItemFormDialog.title" :visible.sync="returnItemFormDialog.visible" width="420px">
<el-form ref="returnItemForm" :model="returnItemFormDialog.form" :rules="returnItemRules" label-width="110px" size="small">
<el-form-item label="退货单ID" prop="returnId">
<el-input v-model="returnItemFormDialog.form.returnId" />
</el-form-item>
<el-form-item label="订单明细ID" prop="itemId">
<el-input v-model="returnItemFormDialog.form.itemId" />
</el-form-item>
<el-form-item label="退货数量" prop="returnQty">
<el-input-number v-model="returnItemFormDialog.form.returnQty" :min="0" :precision="3" />
</el-form-item>
<el-form-item label="照片">
<el-input v-model="returnItemFormDialog.form.photos" placeholder="图片URL用逗号分隔" />
</el-form-item>
<el-form-item label="备注">
<el-input type="textarea" v-model="returnItemFormDialog.form.remark" />
</el-form-item>
</el-form>
<div slot="footer">
<el-button @click="returnItemFormDialog.visible = false"> </el-button>
<el-button type="primary" @click="submitReturnItem"> </el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import {
analyzePurchaseRequirement,
listPurchaseReceipt,
addPurchaseReceipt,
updatePurchaseReceipt,
delPurchaseReceipt,
listPurchaseReturn,
addPurchaseReturn,
updatePurchaseReturn,
delPurchaseReturn,
listPurchaseReturnItem,
addPurchaseReturnItem,
updatePurchaseReturnItem,
delPurchaseReturnItem,
getPurchaseReportSummary,
getPurchasePriceTrend,
getSupplierQuality
} from '@/api/erp/purchase'
export default {
name: 'ErpPurchaseWorkbench',
data() {
return {
mappingRows: [{ productId: '', rawMaterialId: '', conversionRate: 1 }],
persistResult: false,
analysisLoading: false,
analysisData: [],
summary: { totalAmount: 0, suggestionTotal: 0, pendingOrder: 0, supplierBrief: [] },
reportRange: [],
priceTrend: [],
supplierQuality: [],
activeTab: 'receipt',
receiptList: [],
receiptTotal: 0,
receiptLoading: false,
receiptQuery: { pageNum: 1, pageSize: 10, orderId: null, itemId: null },
receiptDialog: { visible: false, title: '', form: {} },
receiptRules: {
orderId: [{ required: true, message: '请输入订单ID', trigger: 'blur' }],
itemId: [{ required: true, message: '请输入明细ID', trigger: 'blur' }],
receivedQty: [{ required: true, message: '请输入收货数量', trigger: 'blur' }]
},
returnList: [],
returnTotal: 0,
returnLoading: false,
returnQuery: { pageNum: 1, pageSize: 10, orderId: null, status: null },
returnDialog: { visible: false, title: '', form: {} },
returnRules: {
orderId: [{ required: true, message: '请输入订单ID', trigger: 'blur' }]
},
returnItemDialog: { visible: false, title: '退货明细列表' },
returnItemQuery: { pageNum: 1, pageSize: 10, returnId: null, itemId: null },
returnItemList: [],
returnItemTotal: 0,
returnItemLoading: false,
returnItemFormDialog: { visible: false, title: '', form: {} },
returnItemRules: {
returnId: [{ required: true, message: '请输入退货单ID', trigger: 'blur' }],
itemId: [{ required: true, message: '请输入订单明细ID', trigger: 'blur' }],
returnQty: [{ required: true, message: '请输入退货数量', trigger: 'blur' }]
}
}
},
filters: {
formatAmount(val) {
if (!val) return '0.00'
return Number(val).toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })
}
},
created() {
this.loadReports()
this.loadReceipts()
this.loadReturns()
},
methods: {
addMappingRow() {
this.mappingRows.push({ productId: '', rawMaterialId: '', conversionRate: 1 })
},
removeMappingRow(index) {
if (this.mappingRows.length === 1) return
this.mappingRows.splice(index, 1)
},
handleAnalyze() {
if (!this.mappingRows.every(row => row.productId && row.rawMaterialId)) {
this.$message.warning('请补全产品与原料映射')
return
}
this.analysisLoading = true
const payload = {
mappings: this.mappingRows.map(row => ({
productId: Number(row.productId),
rawMaterialId: Number(row.rawMaterialId),
conversionRate: Number(row.conversionRate || 0)
})),
persistResult: this.persistResult
}
analyzePurchaseRequirement(payload)
.then(res => {
this.analysisData = res || []
this.summary.suggestionTotal = this.analysisData.reduce((sum, item) => sum + Number(item.suggestedPurchase || 0), 0)
this.$message.success('分析完成')
})
.finally(() => {
this.analysisLoading = false
})
},
analysisSummary({ data }) {
const sums = []
const columns = ['salesDemand', 'productStockWeight', 'rawStockConverted', 'inTransitConverted', 'pendingConverted', 'suggestedPurchase']
sums[0] = '总计'
data.forEach(row => {
columns.forEach((col, idx) => {
const value = Number(row[col] || 0)
sums[idx + 2] = (Number(sums[idx + 2]) || 0) + value
})
})
return sums
},
loadReports() {
const params = {}
if (this.reportRange && this.reportRange.length === 2) {
params.beginTime = `${this.reportRange[0]}-01`
params.endTime = `${this.reportRange[1]}-31`
}
getPurchaseReportSummary(params).then(res => {
this.summary.totalAmount = res.totalAmount || 0
this.summary.supplierBrief = res.bySupplier || []
this.summary.pendingOrder = this.summary.supplierBrief.reduce((sum, item) => sum + (item.orderCount || 0), 0)
})
getPurchasePriceTrend(params).then(res => {
this.priceTrend = res || []
})
getSupplierQuality(params).then(res => {
this.supplierQuality = res || []
})
},
formatPercent(value) {
if (!value || !isFinite(value)) return '0%'
return `${(Number(value) * 100).toFixed(2)}%`
},
// 收货
loadReceipts() {
this.receiptLoading = true
listPurchaseReceipt(this.receiptQuery)
.then(res => {
this.receiptList = res.rows || []
this.receiptTotal = res.total || 0
})
.finally(() => {
this.receiptLoading = false
})
},
resetReceiptQuery() {
this.receiptQuery = { pageNum: 1, pageSize: 10, orderId: null, itemId: null }
this.loadReceipts()
},
openReceiptDialog(row) {
if (row) {
this.receiptDialog.form = { ...row }
this.receiptDialog.title = '编辑收货'
} else {
this.receiptDialog.form = { orderId: '', itemId: '', receivedQty: 0, qualityResult: 'OK' }
this.receiptDialog.title = '新增收货'
}
this.receiptDialog.visible = true
this.$nextTick(() => this.$refs.receiptForm && this.$refs.receiptForm.clearValidate())
},
submitReceipt() {
this.$refs.receiptForm.validate(valid => {
if (!valid) return
const api = this.receiptDialog.form.receiptId ? updatePurchaseReceipt : addPurchaseReceipt
api(this.receiptDialog.form).then(() => {
this.$message.success('保存成功')
this.receiptDialog.visible = false
this.loadReceipts()
})
})
},
handleDeleteReceipt(row) {
this.$confirm('确定删除该收货记录吗?', '提示').then(() => {
return delPurchaseReceipt(row.receiptId)
}).then(() => {
this.$message.success('删除成功')
this.loadReceipts()
})
},
// 退货
loadReturns() {
this.returnLoading = true
listPurchaseReturn(this.returnQuery)
.then(res => {
this.returnList = res.rows || []
this.returnTotal = res.total || 0
})
.finally(() => {
this.returnLoading = false
})
},
resetReturnQuery() {
this.returnQuery = { pageNum: 1, pageSize: 10, orderId: null, status: null }
this.loadReturns()
},
openReturnDialog(row) {
if (row) {
this.returnDialog.form = { ...row }
this.returnDialog.title = '编辑退货单'
} else {
this.returnDialog.form = { orderId: '', returnType: 'QUALITY', status: 0 }
this.returnDialog.title = '新增退货单'
}
this.returnDialog.visible = true
this.$nextTick(() => this.$refs.returnForm && this.$refs.returnForm.clearValidate())
},
submitReturn() {
this.$refs.returnForm.validate(valid => {
if (!valid) return
const api = this.returnDialog.form.returnId ? updatePurchaseReturn : addPurchaseReturn
api(this.returnDialog.form).then(() => {
this.$message.success('保存成功')
this.returnDialog.visible = false
this.loadReturns()
})
})
},
handleDeleteReturn(row) {
this.$confirm('确定删除该退货单吗?', '提示').then(() => {
return delPurchaseReturn(row.returnId)
}).then(() => {
this.$message.success('删除成功')
this.loadReturns()
})
},
// 退货明细
openReturnItemDialog() {
this.returnItemDialog.visible = true
this.loadReturnItems()
},
loadReturnItems() {
this.returnItemLoading = true
listPurchaseReturnItem(this.returnItemQuery)
.then(res => {
this.returnItemList = res.rows || []
this.returnItemTotal = res.total || 0
})
.finally(() => {
this.returnItemLoading = false
})
},
resetReturnItemQuery() {
this.returnItemQuery = { pageNum: 1, pageSize: 10, returnId: null, itemId: null }
this.loadReturnItems()
},
openReturnItemForm(row) {
if (row) {
this.returnItemFormDialog.form = { ...row }
this.returnItemFormDialog.title = '编辑退货明细'
} else {
this.returnItemFormDialog.form = { returnId: '', itemId: '', returnQty: 0 }
this.returnItemFormDialog.title = '新增退货明细'
}
this.returnItemFormDialog.visible = true
this.$nextTick(() => this.$refs.returnItemForm && this.$refs.returnItemForm.clearValidate())
},
submitReturnItem() {
this.$refs.returnItemForm.validate(valid => {
if (!valid) return
const api = this.returnItemFormDialog.form.returnItemId ? updatePurchaseReturnItem : addPurchaseReturnItem
api(this.returnItemFormDialog.form).then(() => {
this.$message.success('保存成功')
this.returnItemFormDialog.visible = false
this.loadReturnItems()
})
})
},
handleDeleteReturnItem(row) {
this.$confirm('确定删除该退货明细吗?', '提示').then(() => {
return delPurchaseReturnItem(row.returnItemId)
}).then(() => {
this.$message.success('删除成功')
this.loadReturnItems()
})
}
}
}
</script>
<style lang="scss" scoped>
.erp-purchase-page {
padding: 16px;
background: #eef1f3;
min-height: 100%;
.summary-row {
margin-bottom: 16px;
}
}
.summary-card {
background: #fdfdfd;
border: 1px solid #d9dee4;
padding: 16px;
height: 90px;
display: flex;
flex-direction: column;
justify-content: center;
.label {
color: #5c6b77;
font-size: 14px;
margin-bottom: 6px;
}
.value {
color: #1f2d3d;
font-size: 20px;
font-weight: 600;
}
}
.panel-card {
margin-bottom: 18px;
border: 1px solid #d0d5d8;
background: #fff;
}
.panel-header {
display: flex;
justify-content: space-between;
align-items: center;
font-weight: 600;
color: #1f2d3d;
}
.panel-actions {
display: flex;
align-items: center;
gap: 12px;
}
.mapping-table {
margin-bottom: 8px;
}
.mapping-toolbar {
margin-bottom: 12px;
}
.analysis-table {
margin-top: 8px;
}
.cell-title {
font-weight: 600;
color: #1f2d3d;
}
.cell-sub {
color: #7c8792;
font-size: 12px;
}
.toolbar {
display: flex;
align-items: center;
margin-bottom: 12px;
}
.toolbar-input {
width: 160px;
margin-right: 8px;
}
.toolbar-spacer {
flex: 1;
}
.report-block {
border: 1px solid #d9dee4;
padding: 12px;
background: #fafbfc;
h4 {
font-weight: 600;
margin: 0 0 8px;
color: #2f3c4c;
}
}
</style>

View File

@@ -0,0 +1,214 @@
<template>
<div class="erp-receipt-page">
<el-card shadow="never" class="panel-card">
<div slot="header" class="panel-header">
<span>收货记录</span>
<el-button type="primary" size="mini" @click="openDialog()">新增收货</el-button>
</div>
<div class="toolbar">
<el-input v-model="query.orderId" placeholder="订单ID" size="small" clearable class="toolbar-input" />
<el-input v-model="query.itemId" placeholder="订单明细ID" size="small" clearable class="toolbar-input" />
<el-select v-model="query.quality" placeholder="质检结果" size="small" clearable class="toolbar-input">
<el-option label="合格" value="OK" />
<el-option label="不合格" value="NG" />
</el-select>
<el-button size="small" type="primary" @click="loadData">查询</el-button>
<el-button size="small" @click="resetQuery">重置</el-button>
</div>
<el-table :data="list" border size="small" v-loading="loading">
<el-table-column prop="receiptId" label="ID" width="80" />
<el-table-column prop="orderId" label="订单ID" width="120" />
<el-table-column prop="itemId" label="明细ID" width="120" />
<el-table-column prop="receivedQty" label="收货数量" width="120" />
<el-table-column prop="qualityResult" label="质检结果" width="120">
<template slot-scope="scope">
<el-tag :type="scope.row.qualityResult === 'NG' ? 'danger' : 'success'" size="mini">
{{ scope.row.qualityResult || 'OK' }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="receiptTime" label="收货时间" width="160" />
<el-table-column label="备注" min-width="160" prop="remark" />
<el-table-column label="操作" width="140">
<template slot-scope="scope">
<el-button type="text" size="mini" @click="openDialog(scope.row)">编辑</el-button>
<el-button type="text" size="mini" style="color:#c0392b" @click="handleDelete(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<pagination
v-show="total > 0"
:total="total"
:page.sync="query.pageNum"
:limit.sync="query.pageSize"
@pagination="loadData"
/>
</el-card>
<el-card shadow="never" class="panel-card">
<div slot="header" class="panel-header">
<span>到货趋势</span>
</div>
<el-table :data="chartData" border size="small" height="300">
<el-table-column prop="date" label="日期" width="140" />
<el-table-column prop="totalQty" label="累计收货(吨)" />
<el-table-column prop="qualifiedQty" label="合格数量(吨)" />
<el-table-column prop="ngQty" label="不合格(吨)" />
</el-table>
</el-card>
<el-dialog :title="dialog.title" :visible.sync="dialog.visible" width="480px">
<el-form :model="dialog.form" :rules="rules" ref="form" label-width="100px" size="small">
<el-form-item label="订单ID" prop="orderId">
<el-input v-model="dialog.form.orderId" />
</el-form-item>
<el-form-item label="明细ID" prop="itemId">
<el-input v-model="dialog.form.itemId" />
</el-form-item>
<el-form-item label="收货数量" prop="receivedQty">
<el-input-number v-model="dialog.form.receivedQty" :min="0" :precision="3" />
</el-form-item>
<el-form-item label="质检结果">
<el-select v-model="dialog.form.qualityResult" clearable>
<el-option label="合格" value="OK" />
<el-option label="不合格" value="NG" />
</el-select>
</el-form-item>
<el-form-item label="收货时间">
<el-date-picker v-model="dialog.form.receiptTime" type="datetime" value-format="yyyy-MM-dd HH:mm:ss" />
</el-form-item>
<el-form-item label="备注">
<el-input type="textarea" v-model="dialog.form.remark" />
</el-form-item>
</el-form>
<div slot="footer">
<el-button @click="dialog.visible = false"> </el-button>
<el-button type="primary" @click="submit"> </el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import {
listPurchaseReceipt,
addPurchaseReceipt,
updatePurchaseReceipt,
delPurchaseReceipt
} from '@/api/erp/purchase'
export default {
name: 'ErpPurchaseReceipt',
data() {
return {
query: { pageNum: 1, pageSize: 10, orderId: null, itemId: null },
list: [],
total: 0,
loading: false,
chartData: [],
dialog: { visible: false, title: '', form: {} },
rules: {
orderId: [{ required: true, message: '请输入订单ID', trigger: 'blur' }],
itemId: [{ required: true, message: '请输入明细ID', trigger: 'blur' }],
receivedQty: [{ required: true, message: '请输入收货数量', trigger: 'blur' }]
}
}
},
created() {
this.loadData()
},
methods: {
loadData() {
this.loading = true
listPurchaseReceipt(this.query)
.then(res => {
this.list = res.rows || []
this.total = res.total || 0
this.buildChart()
})
.finally(() => {
this.loading = false
})
},
resetQuery() {
this.query = { pageNum: 1, pageSize: 10, orderId: null, itemId: null }
this.loadData()
},
openDialog(row) {
if (row) {
this.dialog.form = { ...row }
this.dialog.title = '编辑收货'
} else {
this.dialog.form = { orderId: '', itemId: '', receivedQty: 0, qualityResult: 'OK' }
this.dialog.title = '新增收货'
}
this.dialog.visible = true
this.$nextTick(() => this.$refs.form && this.$refs.form.clearValidate())
},
submit() {
this.$refs.form.validate(valid => {
if (!valid) return
const api = this.dialog.form.receiptId ? updatePurchaseReceipt : addPurchaseReceipt
api(this.dialog.form).then(() => {
this.$message.success('保存成功')
this.dialog.visible = false
this.loadData()
})
})
},
handleDelete(row) {
this.$confirm('确定删除该收货记录吗?', '提示').then(() => {
return delPurchaseReceipt(row.receiptId)
}).then(() => {
this.$message.success('删除成功')
this.loadData()
})
},
buildChart() {
const map = {}
this.list.forEach(item => {
const date = (item.receiptTime || '').slice(0, 10)
if (!map[date]) {
map[date] = { date, totalQty: 0, qualifiedQty: 0, ngQty: 0 }
}
map[date].totalQty += Number(item.receivedQty || 0)
if (item.qualityResult === 'NG') {
map[date].ngQty += Number(item.receivedQty || 0)
} else {
map[date].qualifiedQty += Number(item.receivedQty || 0)
}
})
this.chartData = Object.values(map).sort((a, b) => a.date.localeCompare(b.date))
}
}
}
</script>
<style lang="scss" scoped>
.erp-receipt-page {
padding: 16px;
background: #eef1f3;
min-height: 100%;
}
.panel-card {
margin-bottom: 18px;
border: 1px solid #d0d5d8;
}
.panel-header {
display: flex;
justify-content: space-between;
align-items: center;
font-weight: 600;
color: #1f2d3d;
}
.toolbar {
display: flex;
align-items: center;
margin-bottom: 12px;
}
.toolbar-input {
width: 140px;
margin-right: 8px;
}
</style>

View File

@@ -0,0 +1,186 @@
<template>
<div class="erp-report-page">
<el-card shadow="never" class="panel-card">
<div slot="header" class="panel-header">
<span>采购汇总</span>
<el-date-picker
v-model="range"
type="datetimerange"
value-format="yyyy-MM-dd HH:mm:ss"
size="small"
unlink-panels
start-placeholder="开始时间"
end-placeholder="结束时间"
@change="loadSummary"
/>
</div>
<el-row :gutter="16">
<el-col :span="6">
<div class="metric-card">
<p class="label">采购总金额</p>
<p class="value">{{ summary.totalAmount | formatMoney }}</p>
</div>
</el-col>
<el-col :span="6">
<div class="metric-card">
<p class="label">供应商数量</p>
<p class="value">{{ supplierBrief.length }}</p>
</div>
</el-col>
<el-col :span="6">
<div class="metric-card">
<p class="label">最大供应商金额</p>
<p class="value">{{ topSupplierAmount | formatMoney }}</p>
</div>
</el-col>
<el-col :span="6">
<div class="metric-card">
<p class="label">平均订单金额</p>
<p class="value">{{ avgSupplierAmount | formatMoney }}</p>
</div>
</el-col>
</el-row>
</el-card>
<el-row :gutter="16">
<el-col :span="8">
<el-card shadow="never" class="panel-card">
<div slot="header" class="panel-header">
<span>供应商汇总</span>
</div>
<el-table :data="supplierBrief" border size="small" height="320">
<el-table-column prop="supplierId" label="供应商ID" width="140" />
<el-table-column prop="orderCount" label="订单数" width="100" />
<el-table-column prop="totalAmount" label="总金额" />
</el-table>
</el-card>
</el-col>
<el-col :span="8">
<el-card shadow="never" class="panel-card">
<div slot="header" class="panel-header">
<span>价格趋势</span>
</div>
<el-table :data="priceTrend" border size="small" height="320">
<el-table-column prop="period" label="月份" width="120" />
<el-table-column prop="materialCode" label="物料编码" width="140" />
<el-table-column prop="avgPrice" label="平均含税价" />
</el-table>
</el-card>
</el-col>
<el-col :span="8">
<el-card shadow="never" class="panel-card">
<div slot="header" class="panel-header">
<span>供应商退货率</span>
</div>
<el-table :data="supplierQuality" border size="small" height="320">
<el-table-column prop="supplierId" label="供应商ID" width="140" />
<el-table-column prop="receivedQty" label="收货量" />
<el-table-column prop="returnQty" label="退货量" />
<el-table-column label="退货率">
<template slot-scope="scope">
{{ formatPercent(scope.row.returnRate) }}
</template>
</el-table-column>
</el-table>
</el-card>
</el-col>
</el-row>
</div>
</template>
<script>
import { getPurchaseReportSummary, getPurchasePriceTrend, getSupplierQuality } from '@/api/erp/purchase'
export default {
name: 'ErpPurchaseReport',
data() {
return {
range: [],
summary: { totalAmount: 0 },
supplierBrief: [],
priceTrend: [],
supplierQuality: []
}
},
filters: {
formatMoney(value) {
return Number(value || 0).toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })
}
},
computed: {
topSupplierAmount() {
if (!this.supplierBrief.length) return 0
return Math.max(...this.supplierBrief.map(item => Number(item.totalAmount || 0)))
},
avgSupplierAmount() {
if (!this.supplierBrief.length) return 0
const sum = this.supplierBrief.reduce((acc, cur) => acc + Number(cur.totalAmount || 0), 0)
return sum / this.supplierBrief.length
}
},
created() {
this.loadSummary()
},
methods: {
buildParams() {
const params = {}
if (this.range && this.range.length === 2) {
params.beginTime = this.range[0]
params.endTime = this.range[1]
}
return params
},
loadSummary() {
const params = this.buildParams()
getPurchaseReportSummary(params).then(res => {
this.summary = { totalAmount: res.totalAmount || 0 }
this.supplierBrief = res.bySupplier || []
})
getPurchasePriceTrend(params).then(res => {
this.priceTrend = res || []
})
getSupplierQuality(params).then(res => {
this.supplierQuality = res || []
})
},
formatPercent(value) {
if (!value || !isFinite(value)) return '0%'
return `${(Number(value) * 100).toFixed(2)}%`
}
}
}
</script>
<style lang="scss" scoped>
.erp-report-page {
padding: 16px;
background: #eef1f3;
min-height: 100%;
}
.panel-card {
margin-bottom: 18px;
border: 1px solid #d0d5d8;
}
.panel-header {
display: flex;
justify-content: space-between;
align-items: center;
font-weight: 600;
color: #1f2d3d;
}
.metric-card {
border: 1px solid #d9dee4;
padding: 16px;
background: #fff;
.label {
color: #5b6875;
margin-bottom: 6px;
}
.value {
font-size: 20px;
font-weight: 600;
color: #1f2d3d;
}
}
</style>

View File

@@ -0,0 +1,201 @@
<template>
<div class="erp-requirement-page">
<el-card shadow="never" class="panel-card">
<div slot="header" class="panel-header">
<span>采购需求分析</span>
<div class="panel-actions">
<el-switch v-model="persistResult" active-text="写入建议表" inactive-text="仅计算" />
<el-button type="primary" size="mini" @click="handleAnalyze" :loading="loading">执行分析</el-button>
</div>
</div>
<el-table :data="mappingRows" border size="small" class="mapping-table">
<el-table-column label="产品ID" width="180">
<template slot-scope="scope">
<el-input v-model="scope.row.productId" placeholder="产品ID" />
</template>
</el-table-column>
<el-table-column label="原料ID" width="180">
<template slot-scope="scope">
<el-input v-model="scope.row.rawMaterialId" placeholder="原料ID" />
</template>
</el-table-column>
<el-table-column label="转换率" width="160">
<template slot-scope="scope">
<el-input-number v-model="scope.row.conversionRate" :min="0" :max="1" :step="0.01" />
</template>
</el-table-column>
<el-table-column label="操作" width="120">
<template slot-scope="scope">
<el-button type="text" @click="removeMapping(scope.$index)" :disabled="mappingRows.length === 1">移除</el-button>
</template>
</el-table-column>
</el-table>
<el-button icon="el-icon-plus" size="mini" class="add-btn" @click="addMapping">新增映射</el-button>
<el-table
:data="resultList"
border
size="small"
v-loading="loading"
show-summary
:summary-method="summaryMethod"
>
<el-table-column label="产品" width="220">
<template slot-scope="scope">
<div class="title">{{ scope.row.productName || '-' }}</div>
<div class="cell-sub">ID: {{ scope.row.productId || '-' }}</div>
</template>
</el-table-column>
<el-table-column prop="specification" label="规格" width="160" />
<el-table-column prop="salesDemand" label="销售需求(吨)" width="140" />
<el-table-column prop="productStockWeight" label="成品库存(吨)" width="140" />
<el-table-column prop="rawStockConverted" label="原料折算(吨)" width="140" />
<el-table-column prop="inTransitConverted" label="在途折算(吨)" width="140" />
<el-table-column prop="pendingConverted" label="待下达折算(吨)" width="140" />
<el-table-column prop="suggestedPurchase" label="建议采购(吨)" width="160">
<template slot-scope="scope">
<span :class="scope.row.suggestedPurchase > 0 ? 'highlight' : ''">
{{ scope.row.suggestedPurchase }}
</span>
</template>
</el-table-column>
</el-table>
</el-card>
<el-card shadow="never" class="panel-card">
<div slot="header" class="panel-header">
<span>原料折算详情</span>
</div>
<el-table :data="rawDetailData" size="small" border height="320">
<el-table-column prop="productName" label="产品" width="200" />
<el-table-column prop="rawMaterialName" label="原料" width="200" />
<el-table-column prop="conversionRate" label="转换率" width="120" />
<el-table-column prop="stockWeight" label="库存重量" width="120" />
<el-table-column prop="stockCoilCount" label="库存卷数" width="120" />
<el-table-column prop="convertedStock" label="折算库存" width="120" />
<el-table-column prop="convertedInTransit" label="折算在途" width="120" />
<el-table-column prop="convertedPending" label="折算待下达" width="120" />
</el-table>
</el-card>
</div>
</template>
<script>
import { analyzePurchaseRequirement } from '@/api/erp/purchase'
export default {
name: 'ErpPurchaseRequirement',
data() {
return {
mappingRows: [{ productId: '', rawMaterialId: '', conversionRate: 1 }],
persistResult: false,
loading: false,
resultList: [],
rawDetailData: []
}
},
methods: {
addMapping() {
this.mappingRows.push({ productId: '', rawMaterialId: '', conversionRate: 1 })
},
removeMapping(idx) {
if (this.mappingRows.length === 1) return
this.mappingRows.splice(idx, 1)
},
handleAnalyze() {
if (!this.mappingRows.every(row => row.productId && row.rawMaterialId)) {
this.$message.warning('请完善产品与原料映射')
return
}
this.loading = true
analyzePurchaseRequirement({
persistResult: this.persistResult,
mappings: this.mappingRows.map(row => ({
productId: Number(row.productId),
rawMaterialId: Number(row.rawMaterialId),
conversionRate: Number(row.conversionRate || 0)
}))
})
.then(res => {
this.resultList = res || []
this.buildRawDetail(res || [])
this.$message.success('分析完成')
})
.finally(() => {
this.loading = false
})
},
buildRawDetail(list) {
const rows = []
list.forEach(item => {
(item.rawDetails || []).forEach(detail => {
rows.push({
productName: item.productName,
rawMaterialName: detail.rawMaterialName,
conversionRate: detail.conversionRate,
stockWeight: detail.stockWeight,
stockCoilCount: detail.stockCoilCount,
convertedStock: detail.convertedStock,
convertedInTransit: detail.convertedInTransit,
convertedPending: detail.convertedPending
})
})
})
this.rawDetailData = rows
},
summaryMethod({ data }) {
const sums = []
const fields = ['salesDemand', 'productStockWeight', 'rawStockConverted', 'inTransitConverted', 'pendingConverted', 'suggestedPurchase']
sums[0] = '总计'
data.forEach(row => {
fields.forEach((field, idx) => {
sums[idx + 2] = (Number(sums[idx + 2]) || 0) + Number(row[field] || 0)
})
})
return sums
}
}
}
</script>
<style lang="scss" scoped>
.erp-requirement-page {
padding: 16px;
background: #eef1f3;
min-height: 100%;
}
.panel-card {
margin-bottom: 18px;
border: 1px solid #d0d5d8;
}
.panel-header {
display: flex;
justify-content: space-between;
align-items: center;
font-weight: 600;
color: #1f2d3d;
}
.panel-actions {
display: flex;
align-items: center;
gap: 12px;
}
.mapping-table {
margin-bottom: 8px;
}
.add-btn {
margin-bottom: 12px;
}
.title {
font-weight: 600;
}
.cell-sub {
color: #7c8792;
font-size: 12px;
}
.highlight {
color: #c0392b;
font-weight: 600;
}
</style>

View File

@@ -0,0 +1,295 @@
<template>
<div class="erp-return-page">
<el-row :gutter="16">
<el-col :span="13">
<el-card shadow="never" class="panel-card">
<div slot="header" class="panel-header">
<span>退货单</span>
<el-button type="primary" size="mini" @click="openReturnDialog()">新增退货单</el-button>
</div>
<div class="toolbar">
<el-input v-model="returnQuery.orderId" placeholder="订单ID" size="small" clearable class="toolbar-input" />
<el-select v-model="returnQuery.status" placeholder="状态" size="small" clearable class="toolbar-input">
<el-option label="草稿" :value="0" />
<el-option label="完成" :value="1" />
</el-select>
<el-button size="small" type="primary" @click="loadReturns">查询</el-button>
<el-button size="small" @click="resetReturnQuery">重置</el-button>
</div>
<el-table :data="returnList" border size="small" v-loading="returnLoading" height="420">
<el-table-column prop="returnId" label="退货单ID" width="120" />
<el-table-column prop="orderId" label="订单ID" width="120" />
<el-table-column prop="returnType" label="类型" width="120" />
<el-table-column prop="reason" label="原因" min-width="160" />
<el-table-column prop="status" label="状态" width="100">
<template slot-scope="scope">
<el-tag :type="scope.row.status === 1 ? 'success' : 'info'" size="mini">
{{ scope.row.status === 1 ? '完成' : '草稿' }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" width="160">
<template slot-scope="scope">
<el-button type="text" size="mini" @click="openReturnDialog(scope.row)">编辑</el-button>
<el-button type="text" size="mini" @click="openReturnItems(scope.row)">明细</el-button>
<el-button type="text" size="mini" style="color:#c0392b" @click="handleDeleteReturn(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
</el-card>
</el-col>
<el-col :span="11">
<el-card shadow="never" class="panel-card">
<div slot="header" class="panel-header">
<span>退货明细</span>
<el-button type="primary" size="mini" @click="openReturnItemDialog()">新增明细</el-button>
</div>
<div class="toolbar">
<el-input v-model="returnItemQuery.returnId" placeholder="退货单ID" size="small" clearable class="toolbar-input" />
<el-input v-model="returnItemQuery.itemId" placeholder="订单明细ID" size="small" clearable class="toolbar-input" />
<el-button size="small" type="primary" @click="loadReturnItems">查询</el-button>
<el-button size="small" @click="resetReturnItemQuery">重置</el-button>
</div>
<el-table :data="returnItemList" border size="small" height="420" v-loading="returnItemLoading">
<el-table-column prop="returnItemId" label="ID" width="80" />
<el-table-column prop="returnId" label="退货单ID" width="120" />
<el-table-column prop="itemId" label="订单明细ID" width="150" />
<el-table-column prop="returnQty" label="数量" width="100" />
<el-table-column label="问题照片" min-width="140">
<template slot-scope="scope">
<el-tooltip v-if="scope.row.photos" :content="scope.row.photos" placement="top">
<span class="link">查看</span>
</el-tooltip>
<span v-else>-</span>
</template>
</el-table-column>
<el-table-column label="操作" width="140">
<template slot-scope="scope">
<el-button type="text" size="mini" @click="openReturnItemDialog(scope.row)">编辑</el-button>
<el-button type="text" size="mini" style="color:#c0392b" @click="handleDeleteReturnItem(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
</el-card>
</el-col>
</el-row>
<el-dialog :title="returnDialog.title" :visible.sync="returnDialog.visible" width="520px">
<el-form :model="returnDialog.form" :rules="returnRules" ref="returnForm" label-width="100px" size="small">
<el-form-item label="订单ID" prop="orderId">
<el-input v-model="returnDialog.form.orderId" />
</el-form-item>
<el-form-item label="退货类型">
<el-select v-model="returnDialog.form.returnType">
<el-option label="质量问题" value="QUALITY" />
<el-option label="数量错误" value="QTY" />
<el-option label="规格不符" value="SPEC" />
<el-option label="协商退货" value="OTHER" />
</el-select>
</el-form-item>
<el-form-item label="退货原因">
<el-input type="textarea" v-model="returnDialog.form.reason" />
</el-form-item>
<el-form-item label="状态">
<el-select v-model="returnDialog.form.status">
<el-option label="草稿" :value="0" />
<el-option label="完成" :value="1" />
</el-select>
</el-form-item>
<el-form-item label="备注">
<el-input type="textarea" v-model="returnDialog.form.remark" />
</el-form-item>
</el-form>
<div slot="footer">
<el-button @click="returnDialog.visible = false"> </el-button>
<el-button type="primary" @click="submitReturn"> </el按钮>
</div>
</el-dialog>
<el-dialog :title="returnItemDialog.title" :visible.sync="returnItemDialog.visible" width="500px">
<el-form :model="returnItemDialog.form" :rules="returnItemRules" ref="returnItemForm" label-width="120px" size="small">
<el-form-item label="退货单ID" prop="returnId">
<el-input v-model="returnItemDialog.form.returnId" />
</el-form-item>
<el-form-item label="订单明细ID" prop="itemId">
<el-input v-model="returnItemDialog.form.itemId" />
</el-form-item>
<el-form-item label="退货数量" prop="returnQty">
<el-input-number v-model="returnItemDialog.form.returnQty" :min="0" :precision="3" />
</el-form-item>
<el-form-item label="问题照片">
<el-input v-model="returnItemDialog.form.photos" placeholder="多张以逗号分隔" />
</el-form-item>
<el-form-item label="备注">
<el-input type="textarea" v-model="returnItemDialog.form.remark" />
</el-form-item>
</el-form>
<div slot="footer">
<el-button @click="returnItemDialog.visible = false"> </el-button>
<el-button type="primary" @click="submitReturnItem"> </el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import {
listPurchaseReturn,
addPurchaseReturn,
updatePurchaseReturn,
delPurchaseReturn,
listPurchaseReturnItem,
addPurchaseReturnItem,
updatePurchaseReturnItem,
delPurchaseReturnItem
} from '@/api/erp/purchase'
export default {
name: 'ErpPurchaseReturn',
data() {
return {
returnQuery: { pageNum: 1, pageSize: 10, orderId: null, status: null },
returnList: [],
returnLoading: false,
returnDialog: { visible: false, title: '', form: {} },
returnRules: { orderId: [{ required: true, message: '请输入订单ID', trigger: 'blur' }] },
returnItemQuery: { pageNum: 1, pageSize: 10, returnId: null, itemId: null },
returnItemList: [],
returnItemLoading: false,
returnItemDialog: { visible: false, title: '', form: {} },
returnItemRules: {
returnId: [{ required: true, message: '请输入退货单ID', trigger: 'blur' }],
itemId: [{ required: true, message: '请输入订单明细ID', trigger: 'blur' }],
returnQty: [{ required: true, message: '请输入退货数量', trigger: 'blur' }]
}
}
},
created() {
this.loadReturns()
this.loadReturnItems()
},
methods: {
loadReturns() {
this.returnLoading = true
listPurchaseReturn(this.returnQuery)
.then(res => {
this.returnList = res.rows || []
})
.finally(() => {
this.returnLoading = false
})
},
resetReturnQuery() {
this.returnQuery = { pageNum: 1, pageSize: 10, orderId: null, status: null }
this.loadReturns()
},
openReturnDialog(row) {
if (row) {
this.returnDialog.form = { ...row }
this.returnDialog.title = '编辑退货单'
} else {
this.returnDialog.form = { orderId: '', returnType: 'QUALITY', status: 0 }
this.returnDialog.title = '新增退货单'
}
this.returnDialog.visible = true
this.$nextTick(() => this.$refs.returnForm && this.$refs.returnForm.clearValidate())
},
submitReturn() {
this.$refs.returnForm.validate(valid => {
if (!valid) return
const api = this.returnDialog.form.returnId ? updatePurchaseReturn : addPurchaseReturn
api(this.returnDialog.form).then(() => {
this.$message.success('保存成功')
this.returnDialog.visible = false
this.loadReturns()
})
})
},
openReturnItems(row) {
this.returnItemQuery.returnId = row.returnId
this.loadReturnItems()
},
handleDeleteReturn(row) {
this.$confirm('确定删除该退货单吗?', '提示').then(() => delPurchaseReturn(row.returnId)).then(() => {
this.$message.success('删除成功')
this.loadReturns()
})
},
loadReturnItems() {
this.returnItemLoading = true
listPurchaseReturnItem(this.returnItemQuery)
.then(res => {
this.returnItemList = res.rows || []
})
.finally(() => {
this.returnItemLoading = false
})
},
resetReturnItemQuery() {
this.returnItemQuery = { pageNum: 1, pageSize: 10, returnId: null, itemId: null }
this.loadReturnItems()
},
openReturnItemDialog(row) {
if (row) {
this.returnItemDialog.form = { ...row }
this.returnItemDialog.title = '编辑退货明细'
} else {
this.returnItemDialog.form = { returnId: this.returnItemQuery.returnId || '', itemId: '', returnQty: 0 }
this.returnItemDialog.title = '新增退货明细'
}
this.returnItemDialog.visible = true
this.$nextTick(() => this.$refs.returnItemForm && this.$refs.returnItemForm.clearValidate())
},
submitReturnItem() {
this.$refs.returnItemForm.validate(valid => {
if (!valid) return
const api = this.returnItemDialog.form.returnItemId ? updatePurchaseReturnItem : addPurchaseReturnItem
api(this.returnItemDialog.form).then(() => {
this.$message.success('保存成功')
this.returnItemDialog.visible = false
this.loadReturnItems()
})
})
},
handleDeleteReturnItem(row) {
this.$confirm('确定删除该明细吗?', '提示').then(() => delPurchaseReturnItem(row.returnItemId)).then(() => {
this.$message.success('删除成功')
this.loadReturnItems()
})
}
}
}
</script>
<style lang="scss" scoped>
.erp-return-page {
padding: 16px;
background: #eef1f3;
min-height: 100%;
}
.panel-card {
border: 1px solid #d0d5d8;
margin-bottom: 16px;
}
.panel-header {
display: flex;
justify-content: space-between;
align-items: center;
font-weight: 600;
}
.toolbar {
display: flex;
align-items: center;
margin-bottom: 12px;
}
.toolbar-input {
width: 140px;
margin-right: 8px;
}
.link {
color: #2f86d7;
cursor: pointer;
}
</style>

View File

@@ -0,0 +1,340 @@
<template>
<div class="erp-supplier-page">
<el-row :gutter="16">
<el-col :span="10">
<el-card shadow="never" class="panel-card">
<div slot="header" class="panel-header">
<span>供应商档案</span>
<el-button type="primary" size="mini" @click="openSupplierDialog()">新增</el-button>
</div>
<div class="toolbar">
<el-input v-model="supplierQuery.name" placeholder="供应商名称" size="small" clearable class="toolbar-input" />
<el-select v-model="supplierQuery.creditRating" placeholder="信用等级" size="small" clearable class="toolbar-input">
<el-option label="A" value="A" />
<el-option label="B" value="B" />
<el-option label="C" value="C" />
<el-option label="D" value="D" />
</el-select>
<el-button size="small" type="primary" @click="loadSuppliers">查询</el-button>
<el-button size="small" @click="resetSupplierQuery">重置</el-button>
</div>
<el-table :data="supplierList" height="420" size="small" border v-loading="supplierLoading">
<el-table-column prop="supplierCode" label="编码" width="120" />
<el-table-column prop="name" label="名称" min-width="160" />
<el-table-column prop="creditRating" label="信用" width="80" />
<el-table-column label="联系人" width="140">
<template slot-scope="scope">
<div>{{ scope.row.contactPerson || '-' }}</div>
<div class="cell-sub">{{ scope.row.contactPhone || '' }}</div>
</template>
</el-table-column>
<el-table-column label="操作" width="160">
<template slot-scope="scope">
<el-button type="text" size="mini" @click="openSupplierDialog(scope.row)">编辑</el-button>
<el-button type="text" size="mini" @click="handleDeleteSupplier(scope.row)" style="color:#c0392b">删除</el-button>
</template>
</el-table-column>
</el-table>
</el-card>
</el-col>
<el-col :span="14">
<el-card shadow="never" class="panel-card">
<div slot="header" class="panel-header">
<span>供应商价格表</span>
<el-button type="primary" size="mini" @click="openPriceDialog()">新增价格</el-button>
</div>
<div class="toolbar">
<el-input v-model="priceQuery.materialTypeCode" placeholder="物料编码" size="small" clearable class="toolbar-input" />
<el-select v-model="priceQuery.supplierId" placeholder="供应商" size="small" clearable class="toolbar-input">
<el-option v-for="sp in supplierOptions" :key="sp.supplierId" :label="sp.name" :value="sp.supplierId" />
</el-select>
<el-date-picker v-model="priceRange" type="daterange" value-format="yyyy-MM-dd" size="small" range-separator="至"
start-placeholder="生效日期" end-placeholder="失效日期" />
<el-button size="small" type="primary" @click="loadPrices">查询</el-button>
</div>
<el-table :data="priceList" size="small" border v-loading="priceLoading">
<el-table-column prop="supplierId" label="供应商" width="150">
<template slot-scope="scope">
{{ matchSupplier(scope.row.supplierId) }}
</template>
</el-table-column>
<el-table-column prop="materialTypeCode" label="物料类型" width="160" />
<el-table-column prop="specification" label="规格" min-width="140" />
<el-table-column prop="price" label="价格(含税)" width="110" />
<el-table-column label="有效期" width="200">
<template slot-scope="scope">
<div>{{ scope.row.validFrom || '-' }}</div>
<div class="cell-sub">{{ scope.row.validTo || '-' }}</div>
</template>
</el-table-column>
<el-table-column label="操作" width="160">
<template slot-scope="scope">
<el-button type="text" size="mini" @click="openPriceDialog(scope.row)">编辑</el-button>
<el-button type="text" size="mini" @click="handleDeletePrice(scope.row)" style="color:#c0392b">删除</el-button>
</template>
</el-table-column>
</el-table>
</el-card>
</el-col>
</el-row>
<!-- 供应商弹窗 -->
<el-dialog :title="supplierDialog.title" :visible.sync="supplierDialog.visible" width="520px">
<el-form :model="supplierDialog.form" :rules="supplierRules" ref="supplierForm" label-width="90px" size="small">
<el-form-item label="编码" prop="supplierCode">
<el-input v-model="supplierDialog.form.supplierCode" />
</el-form-item>
<el-form-item label="名称" prop="name">
<el-input v-model="supplierDialog.form.name" />
</el-form-item>
<el-form-item label="类型">
<el-input v-model="supplierDialog.form.type" />
</el-form-item>
<el-form-item label="信用等级">
<el-select v-model="supplierDialog.form.creditRating" clearable>
<el-option label="A" value="A" />
<el-option label="B" value="B" />
<el-option label="C" value="C" />
<el-option label="D" value="D" />
</el-select>
</el-form-item>
<el-form-item label="联系人">
<el-input v-model="supplierDialog.form.contactPerson" />
</el-form-item>
<el-form-item label="联系电话">
<el-input v-model="supplierDialog.form.contactPhone" />
</el-form-item>
<el-form-item label="地址">
<el-input v-model="supplierDialog.form.address" />
</el-form-item>
<el-form-item label="备注">
<el-input type="textarea" v-model="supplierDialog.form.remark" />
</el-form-item>
</el-form>
<div slot="footer">
<el-button @click="supplierDialog.visible = false"> </el-button>
<el-button type="primary" @click="submitSupplier"> </el-button>
</div>
</el-dialog>
<!-- 价格弹窗 -->
<el-dialog :title="priceDialog.title" :visible.sync="priceDialog.visible" width="520px">
<el-form :model="priceDialog.form" :rules="priceRules" ref="priceForm" label-width="100px" size="small">
<el-form-item label="供应商" prop="supplierId">
<el-select v-model="priceDialog.form.supplierId" placeholder="选择供应商">
<el-option v-for="sp in supplierOptions" :key="sp.supplierId" :label="sp.name" :value="sp.supplierId" />
</el-select>
</el-form-item>
<el-form-item label="物料类型" prop="materialTypeCode">
<el-input v-model="priceDialog.form.materialTypeCode" />
</el-form-item>
<el-form-item label="规格">
<el-input v-model="priceDialog.form.specification" />
</el-form-item>
<el-form-item label="含税价格" prop="price">
<el-input-number v-model="priceDialog.form.price" :min="0" :precision="2" />
</el-form-item>
<el-form-item label="有效期">
<el-date-picker
v-model="priceValidRange"
type="daterange"
value-format="yyyy-MM-dd"
start-placeholder="开始"
end-placeholder="结束"
/>
</el-form-item>
<el-form-item label="备注">
<el-input type="textarea" v-model="priceDialog.form.remark" />
</el-form-item>
</el-form>
<div slot="footer">
<el-button @click="priceDialog.visible = false"> </el-button>
<el-button type="primary" @click="submitPrice"> </el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import {
listSupplier,
addSupplier,
updateSupplier,
delSupplier,
listSupplierPrice,
addSupplierPrice,
updateSupplierPrice,
delSupplierPrice
} from '@/api/erp/purchase'
export default {
name: 'ErpSupplierManage',
data() {
return {
supplierQuery: { pageNum: 1, pageSize: 50, name: null, creditRating: null },
supplierList: [],
supplierLoading: false,
supplierDialog: { visible: false, title: '', form: {} },
supplierRules: {
supplierCode: [{ required: true, message: '请输入编码', trigger: 'blur' }],
name: [{ required: true, message: '请输入名称', trigger: 'blur' }]
},
priceQuery: { pageNum: 1, pageSize: 50, supplierId: null, materialTypeCode: null },
priceList: [],
priceLoading: false,
priceDialog: { visible: false, title: '', form: {} },
priceRules: {
supplierId: [{ required: true, message: '请选择供应商', trigger: 'change' }],
materialTypeCode: [{ required: true, message: '请输入物料类型', trigger: 'blur' }],
price: [{ required: true, message: '请输入价格', trigger: 'change' }]
},
priceRange: [],
priceValidRange: [],
supplierOptions: []
}
},
created() {
this.loadSuppliers()
this.loadPrices()
},
methods: {
loadSuppliers() {
this.supplierLoading = true
listSupplier(this.supplierQuery)
.then(res => {
this.supplierList = res.rows || []
this.supplierOptions = this.supplierList
})
.finally(() => {
this.supplierLoading = false
})
},
resetSupplierQuery() {
this.supplierQuery = { pageNum: 1, pageSize: 50, name: null, creditRating: null }
this.loadSuppliers()
},
openSupplierDialog(row) {
if (row) {
this.supplierDialog.form = { ...row }
this.supplierDialog.title = '编辑供应商'
} else {
this.supplierDialog.form = { supplierCode: '', name: '', creditRating: 'A' }
this.supplierDialog.title = '新增供应商'
}
this.supplierDialog.visible = true
this.$nextTick(() => this.$refs.supplierForm && this.$refs.supplierForm.clearValidate())
},
submitSupplier() {
this.$refs.supplierForm.validate(valid => {
if (!valid) return
const api = this.supplierDialog.form.supplierId ? updateSupplier : addSupplier
api(this.supplierDialog.form).then(() => {
this.$message.success('保存成功')
this.supplierDialog.visible = false
this.loadSuppliers()
})
})
},
handleDeleteSupplier(row) {
this.$confirm('确认删除该供应商吗?', '提示').then(() => {
return delSupplier(row.supplierId)
}).then(() => {
this.$message.success('删除成功')
this.loadSuppliers()
})
},
// price
loadPrices() {
const query = { ...this.priceQuery }
if (this.priceRange && this.priceRange.length === 2) {
query.beginTime = this.priceRange[0]
query.endTime = this.priceRange[1]
}
this.priceLoading = true
listSupplierPrice(query)
.then(res => {
this.priceList = res.rows || []
})
.finally(() => {
this.priceLoading = false
})
},
matchSupplier(id) {
const target = this.supplierOptions.find(sp => sp.supplierId === id)
return target ? target.name : id
},
openPriceDialog(row) {
if (!this.supplierOptions.length) {
this.$message.warning('请先维护供应商')
return
}
if (row) {
this.priceDialog.form = { ...row }
this.priceValidRange = [row.validFrom, row.validTo]
this.priceDialog.title = '编辑价格'
} else {
this.priceDialog.form = { supplierId: null, materialTypeCode: '', price: 0 }
this.priceValidRange = []
this.priceDialog.title = '新增价格'
}
this.priceDialog.visible = true
this.$nextTick(() => this.$refs.priceForm && this.$refs.priceForm.clearValidate())
},
submitPrice() {
this.$refs.priceForm.validate(valid => {
if (!valid) return
if (this.priceValidRange && this.priceValidRange.length === 2) {
this.priceDialog.form.validFrom = this.priceValidRange[0]
this.priceDialog.form.validTo = this.priceValidRange[1]
}
const api = this.priceDialog.form.priceId ? updateSupplierPrice : addSupplierPrice
api(this.priceDialog.form).then(() => {
this.$message.success('保存成功')
this.priceDialog.visible = false
this.loadPrices()
})
})
},
handleDeletePrice(row) {
this.$confirm('确认删除该价格吗?', '提示').then(() => {
return delSupplierPrice(row.priceId)
}).then(() => {
this.$message.success('删除成功')
this.loadPrices()
})
}
}
}
</script>
<style lang="scss" scoped>
.erp-supplier-page {
background: #eef1f3;
padding: 16px;
min-height: 100%;
}
.panel-card {
border: 1px solid #d0d5d8;
}
.panel-header {
display: flex;
justify-content: space-between;
align-items: center;
font-weight: 600;
color: #1f2d3d;
}
.toolbar {
display: flex;
align-items: center;
margin-bottom: 12px;
}
.toolbar-input {
width: 150px;
margin-right: 8px;
}
.cell-sub {
font-size: 12px;
color: #7c8792;
}
</style>