feat(bid): 完成甲方报价模块全量功能开发
1. 新增甲方报价业务实体,继承基础实体类 2. 新增供应商报价明细查询接口,支持按供应商ID展开物料明细 3. 重构甲方报价关联逻辑,通过material_id精确关联物料表 4. 新增甲方报价历史统计、月度趋势、快速新建等服务功能 5. 完善菜单配置,修正甲方报价菜单结构,添加完整权限控制 6. 新增物料搜索自动补全功能,优化报价单详情页面 7. 在供应商详情页新增报价历史Tab页签,展示该供应商的所有报价物料明细 8. 在物料详情页新增甲方报价记录Tab页签,展示该物料的所有甲方报价历史 9. 新增数据库优化脚本,添加索引并修复历史数据关联
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user