feat(bid): 完成甲方报价模块全量功能开发

1.  新增甲方报价业务实体,继承基础实体类
2.  新增供应商报价明细查询接口,支持按供应商ID展开物料明细
3.  重构甲方报价关联逻辑,通过material_id精确关联物料表
4.  新增甲方报价历史统计、月度趋势、快速新建等服务功能
5.  完善菜单配置,修正甲方报价菜单结构,添加完整权限控制
6.  新增物料搜索自动补全功能,优化报价单详情页面
7.  在供应商详情页新增报价历史Tab页签,展示该供应商的所有报价物料明细
8.  在物料详情页新增甲方报价记录Tab页签,展示该物料的所有甲方报价历史
9.  新增数据库优化脚本,添加索引并修复历史数据关联
This commit is contained in:
2026-06-01 19:05:04 +08:00
parent 18a71526dc
commit a75589018f
23 changed files with 1758 additions and 144 deletions

View File

@@ -3,72 +3,100 @@
<!-- 操作栏 -->
<div class="tab-toolbar">
<span class="tab-title">甲方报价记录</span>
<el-button size="mini" icon="el-icon-download" @click="exportExcel">导出Excel</el-button>
<div class="tab-actions">
<el-button size="mini" icon="el-icon-plus" type="primary" @click="goToHistory">查看全部历史</el-button>
<el-button size="mini" icon="el-icon-download" @click="exportExcel">导出Excel</el-button>
</div>
</div>
<!-- 报价表格 -->
<el-table
:data="list"
v-loading="loading"
border
<el-table
:data="list"
v-loading="loading"
border
size="small"
class="quote-table"
:header-cell-style="headerStyle">
<el-table-column label="报价日期" width="120" align="center">
<template slot-scope="scope">
<span class="date-cell">{{ formatDate(scope.row.create_time) }}</span>
<span class="date-cell">{{ formatDate(scope.row.createTime) }}</span>
</template>
</el-table-column>
<el-table-column label="甲方信息" min-width="200">
<el-table-column label="甲方信息" min-width="180">
<template slot-scope="scope">
<div class="client-info">
<div class="client-name">{{ scope.row.client_name }}</div>
<div class="client-name">{{ scope.row.clientName }}</div>
<div class="quote-ref" v-if="scope.row.quoteNo">单号{{ scope.row.quoteNo }}</div>
</div>
</template>
</el-table-column>
<el-table-column label="成本价(元)" width="120" align="right">
<el-table-column label="规格/型号" min-width="160">
<template slot-scope="scope">
<span class="price-cell cost-price">¥{{ formatPrice(scope.row.cost_price) }}</span>
<div class="spec-info">
<span v-if="scope.row.spec">规格{{ scope.row.spec }}</span>
<span v-if="scope.row.modelNo"> / {{ scope.row.modelNo }}</span>
</div>
</template>
</el-table-column>
<el-table-column label="单价(元)" width="120" align="right">
<el-table-column label="数量" width="80" align="center">
<template slot-scope="scope">
<span class="price-cell">¥{{ formatPrice(scope.row.unit_price) }}</span>
{{ scope.row.quantity || '-' }}
</template>
</el-table-column>
<el-table-column label="成价(元)" width="120" align="right">
<el-table-column label="成价(元)" width="110" align="right">
<template slot-scope="scope">
<span class="price-cell total-price">¥{{ formatPrice(scope.row.total_price) }}</span>
<span class="price-cell cost-price">¥{{ formatPrice(scope.row.costPrice) }}</span>
</template>
</el-table-column>
<el-table-column label="报价单号" width="150" align="center">
<el-table-column label="单价(元)" width="110" align="right">
<template slot-scope="scope">
<span class="quote-no">{{ scope.row.quote_no }}</span>
<span class="price-cell">¥{{ formatPrice(scope.row.unitPrice) }}</span>
</template>
</el-table-column>
<el-table-column label="状态" width="100" align="center">
<el-table-column label="成交价(元)" width="110" align="right">
<template slot-scope="scope">
<el-tag
:type="getStatusType(scope.row.quote_status)"
<span class="price-cell total-price">¥{{ formatPrice(scope.row.totalPrice) }}</span>
</template>
</el-table-column>
<el-table-column label="状态" width="90" align="center">
<template slot-scope="scope">
<el-tag
:type="getStatusType(scope.row.quoteStatus)"
size="small"
effect="dark"
class="status-tag">
{{ getStatusText(scope.row.quote_status) }}
{{ getStatusText(scope.row.quoteStatus) }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" width="90" align="center">
<template slot-scope="scope">
<el-button type="text" size="mini" icon="el-icon-top-right" @click="viewQuote(scope.row)">
查看报价单
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 空状态 -->
<div v-if="!loading && list.length === 0" class="empty-state">
<i class="el-icon-document"></i>
<p>暂无甲方报价记录</p>
<el-button size="small" type="primary" icon="el-icon-plus" @click="goToHistory">去创建报价</el-button>
</div>
</div>
</template>
<script>
import { getClientQuotes } from "@/api/bid/material";
import { getClientQuoteByMaterial } from "@/api/bid/clientquote";
export default {
name: "ClientQuoteTab",
@@ -79,7 +107,7 @@ export default {
loadData() {
if (!this.materialId) return;
this.loading = true;
getClientQuotes(this.materialId).then(res => {
getClientQuoteByMaterial(this.materialId).then(res => {
this.list = res.data || [];
this.loading = false;
}).catch(() => { this.loading = false; });
@@ -94,21 +122,29 @@ export default {
return Number(price).toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 });
},
getStatusType(status) {
const map = { 'accepted': 'success', 'rejected': 'danger', 'pending': 'warning', 'draft': 'info' };
const map = { 'confirmed': 'success', 'rejected': 'danger', 'sent': 'primary', 'draft': 'info' };
return map[status] || 'info';
},
getStatusText(status) {
const map = { 'accepted': '已接受', 'rejected': '已拒绝', 'pending': '待处理', 'draft': '草稿' };
const map = { 'confirmed': '已确认', 'rejected': '已拒绝', 'sent': '已发送', 'draft': '草稿' };
return map[status] || status;
},
headerStyle() {
return {
background: '#f5f7fa',
color: '#606266',
return {
background: '#f5f7fa',
color: '#606266',
fontWeight: 600,
fontSize: '13px'
};
},
viewQuote(row) {
if (row.quoteId) {
this.$router.push({ path: "/quotemgr/clientquote/detail", query: { quoteId: row.quoteId } });
}
},
goToHistory() {
this.$router.push("/quotemgr/clientquote");
},
exportExcel() {
this.$message.info('导出功能开发中');
}
@@ -121,7 +157,6 @@ export default {
padding: 16px;
}
/* 工具栏 */
.tab-toolbar {
display: flex;
justify-content: space-between;
@@ -139,7 +174,11 @@ export default {
color: #303133;
}
/* 表格样式 */
.tab-actions {
display: flex;
gap: 8px;
}
.quote-table {
border-radius: 6px;
overflow: hidden;
@@ -150,14 +189,12 @@ export default {
box-shadow: 0 1px 2px rgba(0,0,0,0.05);
}
/* 日期单元格 */
.date-cell {
font-size: 13px;
color: #606266;
font-weight: 500;
}
/* 甲方信息 */
.client-info {
padding: 4px 0;
}
@@ -169,7 +206,17 @@ export default {
line-height: 1.4;
}
/* 价格单元格 */
.quote-ref {
font-size: 11px;
color: #909399;
margin-top: 2px;
}
.spec-info {
font-size: 12px;
color: #606266;
}
.price-cell {
font-size: 14px;
font-weight: 600;
@@ -185,22 +232,27 @@ export default {
font-weight: 700;
}
/* 报价单号 */
.quote-no {
font-size: 12px;
color: #909399;
font-family: 'Courier New', monospace;
background: #f5f7fa;
padding: 2px 6px;
border-radius: 3px;
}
/* 状态标签 */
.status-tag {
font-weight: 500;
}
/* 行悬停效果 */
.empty-state {
text-align: center;
padding: 40px 20px;
color: #c0c4cc;
}
.empty-state i {
font-size: 48px;
margin-bottom: 12px;
display: block;
}
.empty-state p {
font-size: 14px;
margin-bottom: 16px;
}
>>> .el-table__row:hover {
background-color: #f5f7fa !important;
}