Files
erp-next/ruoyi-ui/src/views/bid/quotation/index.vue
王文昊 c97fdf4c6f feat: 页面功能完善
3.1 供货商管理页面

  - 移除了右侧面板的"供货清单"Tab
  - 报价历史板块新增搜索功能(物料名称/报价单号/状态/日期范围)
  - 后端 Mapper 改造支持动态 SQL 过滤

  3.2 报价请求与供应商报价关联

  - 新增"供应商报价汇总"弹窗,展示 RFQ 下所有供应商的报价对比
  - 报价单号改为可点击链接,跳转到供应商报价列表并按单号搜索

  3.3 智慧比价详情页

  - 修复了比价详情页路由(在 router/index.js 中补充)
  - 移除了评分维度展示(价格/交期/质量/服务评分条、综合分标签)
  - 精简为纯粹的供应商价格对比视图

  3.4 其他修复

  - 首页快捷操作路径修正(/bid/xxx → /xxx)
  - 停用 bid 目录后受影响的 router.push 路径全部修复
  - biz_tenant 表缺失修复(创建建表 SQL 并执行)
  - 比价详情页路由注册补充
  - goCompare 跳转路径修正
2026-06-06 15:20:46 +08:00

630 lines
29 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 quotation-page">
<!-- 顶部统计卡片 -->
<el-row :gutter="14" class="stat-row">
<el-col :span="6">
<div class="stat-card stat-all">
<div class="stat-num">{{ stats.total }}</div>
<div class="stat-lbl">全部报价</div>
<i class="el-icon-document stat-icon"></i>
</div>
</el-col>
<el-col :span="6">
<div class="stat-card stat-draft">
<div class="stat-num">{{ stats.draft }}</div>
<div class="stat-lbl">草稿</div>
<i class="el-icon-edit-outline stat-icon"></i>
</div>
</el-col>
<el-col :span="6">
<div class="stat-card stat-submitted">
<div class="stat-num">{{ stats.submitted }}</div>
<div class="stat-lbl">待处理</div>
<i class="el-icon-time stat-icon"></i>
</div>
</el-col>
<el-col :span="6">
<div class="stat-card stat-accepted">
<div class="stat-num">{{ stats.accepted }}</div>
<div class="stat-lbl">已采纳</div>
<i class="el-icon-circle-check stat-icon"></i>
</div>
</el-col>
</el-row>
<!-- 搜索栏 -->
<el-card shadow="never" class="search-card">
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true">
<el-form-item label="报价单号">
<el-input v-model="queryParams.quoteNo" placeholder="报价单号" clearable style="width:150px" @keyup.enter.native="handleQuery" />
</el-form-item>
<el-form-item label="RFQ单号">
<el-input v-model="queryParams.rfqNo" placeholder="询价单号" clearable style="width:150px" @keyup.enter.native="handleQuery" />
</el-form-item>
<el-form-item label="供应商">
<el-input v-model="queryParams.supplierName" placeholder="供应商名称" clearable style="width:150px" @keyup.enter.native="handleQuery" />
</el-form-item>
<el-form-item label="状态">
<el-select v-model="queryParams.status" placeholder="全部" clearable style="width:110px">
<el-option label="草稿" value="draft" />
<el-option label="已提交" value="submitted" />
<el-option label="已采纳" value="accepted" />
<el-option label="已拒绝" value="rejected" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
</el-card>
<!-- 工具栏 -->
<el-row :gutter="10" class="mb8" style="margin-top:12px">
<el-col :span="1.5">
<el-button type="primary" icon="el-icon-plus" size="mini" @click="handleAdd">新建报价</el-button>
</el-col>
</el-row>
<!-- 报价列表 -->
<el-table v-loading="loading" :data="list" border>
<el-table-column label="报价单号" prop="quoteNo" width="155" />
<el-table-column label="关联询价单" width="200">
<template slot-scope="s">
<div style="font-weight:600;color:#303133">{{ s.row.rfqNo }}</div>
<div style="font-size:12px;color:#909399;margin-top:2px" v-if="s.row.rfqTitle">{{ s.row.rfqTitle }}</div>
</template>
</el-table-column>
<el-table-column label="供应商" prop="supplierName" min-width="150">
<template slot-scope="s">
<div style="display:flex;align-items:center;gap:6px">
<el-avatar :size="28" style="background:#1171c4;flex-shrink:0">{{ (s.row.supplierName||'?').charAt(0) }}</el-avatar>
<span>{{ s.row.supplierName }}</span>
</div>
</template>
</el-table-column>
<el-table-column label="金额" width="120" align="right">
<template slot-scope="s">
<strong style="color:#409EFF;font-size:15px">¥{{ s.row.totalAmount | money }}</strong>
</template>
</el-table-column>
<el-table-column label="交期" prop="deliveryDays" width="80" align="center">
<template slot-scope="s">{{ s.row.deliveryDays || '-' }} </template>
</el-table-column>
<el-table-column label="状态" width="105" align="center">
<template slot-scope="s">
<div class="status-chip" :class="'status-' + s.row.status">
<i :class="statusIcon(s.row.status)"></i>
{{ statusLabel(s.row.status) }}
</div>
</template>
</el-table-column>
<el-table-column label="提交时间" prop="submitTime" width="155" align="center">
<template slot-scope="s">
<span v-if="s.row.submitTime" style="font-size:12px">{{ s.row.submitTime }}</span>
<span v-else style="color:#c0c4cc;font-size:12px">未提交</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" width="220" fixed="right">
<template slot-scope="s">
<el-button size="mini" type="text" icon="el-icon-view" @click="handleView(s.row)">查看</el-button>
<el-button size="mini" type="text" icon="el-icon-edit" @click="handleUpdate(s.row)"
v-if="s.row.status==='draft'">编辑</el-button>
<el-button size="mini" type="text" style="color:#67C23A" icon="el-icon-upload2"
@click="handleSubmit(s.row)" v-if="s.row.status==='draft'">提交</el-button>
<el-button size="mini" type="text" style="color:#67C23A" icon="el-icon-check"
@click="handleAccept(s.row)" v-if="s.row.status==='submitted' && !isSupplier">采纳</el-button>
<el-button size="mini" type="text" style="color:#F56C6C" icon="el-icon-close"
@click="handleReject(s.row)" v-if="s.row.status==='submitted' && !isSupplier">拒绝</el-button>
<el-button size="mini" type="text" icon="el-icon-delete" style="color:#f56c6c"
@click="handleDelete(s.row)" v-if="s.row.status==='draft'">删除</el-button>
</template>
</el-table-column>
</el-table>
<pagination v-show="total>0" :total="total" :page.sync="queryParams.pageNum"
:limit.sync="queryParams.pageSize" @pagination="getList" />
<!-- 创建/编辑对话框 -->
<el-dialog :title="dialogTitle" :visible.sync="dialogOpen" width="1000px" append-to-body :close-on-click-modal="false">
<el-form ref="form" :model="form" :rules="rules" label-width="90px" size="small">
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="关联RFQ" prop="rfqId">
<el-select v-model="form.rfqId" placeholder="选择询价单" filterable style="width:100%" @change="onRfqChange">
<el-option v-for="r in rfqOptions" :key="r.rfqId"
:label="r.rfqNo + ' · ' + r.rfqTitle" :value="r.rfqId">
<div style="display:flex;justify-content:space-between">
<span style="font-weight:600">{{ r.rfqNo }}</span>
<span style="color:#909399;font-size:12px;margin-left:8px">{{ r.rfqTitle }}</span>
</div>
</el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="供应商" prop="supplierId">
<el-select v-model="form.supplierId" placeholder="选择供应商" filterable style="width:100%">
<el-option v-for="s in supplierOptions" :key="s.supplierId"
:label="s.supplierName" :value="s.supplierId" />
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="8">
<el-form-item label="报价有效期">
<el-input-number v-model="form.validDays" :min="1" :max="365" style="width:100%" />
<span style="margin-left:4px;color:#909399;font-size:12px"></span>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="整体交期">
<el-input-number v-model="form.deliveryDays" :min="0" style="width:100%" />
<span style="margin-left:4px;color:#909399;font-size:12px"></span>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="币种">
<el-select v-model="form.currency" style="width:100%">
<el-option label="人民币 CNY" value="CNY" />
<el-option label="美元 USD" value="USD" />
<el-option label="欧元 EUR" value="EUR" />
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-divider content-position="left">
<span style="font-weight:700;color:#1a2c4e">报价明细</span>
<el-tooltip content="选择RFQ后可自动加载物料需求" placement="top">
<i class="el-icon-question" style="margin-left:6px;color:#909399;cursor:pointer"></i>
</el-tooltip>
</el-divider>
<div v-if="rfqItemsLoading" style="text-align:center;padding:20px;color:#909399">
<i class="el-icon-loading"></i> 正在加载RFQ物料
</div>
<el-table v-else :data="form.items" border size="small" class="items-table">
<el-table-column type="index" width="44" label="#" />
<el-table-column label="物料名称" min-width="140">
<template slot-scope="s">
<el-input v-model="s.row.materialName" size="mini" placeholder="物料名称" />
</template>
</el-table-column>
<el-table-column label="规格" width="120">
<template slot-scope="s">
<el-input v-model="s.row.spec" size="mini" placeholder="规格" />
</template>
</el-table-column>
<el-table-column label="型号" width="120">
<template slot-scope="s">
<el-input v-model="s.row.modelNo" size="mini" placeholder="型号" />
</template>
</el-table-column>
<el-table-column label="单位" width="72">
<template slot-scope="s">
<el-input v-model="s.row.unit" size="mini" />
</template>
</el-table-column>
<el-table-column label="需求数量" width="90" align="right">
<template slot-scope="s">
<span style="color:#909399;font-size:13px">{{ s.row.quantity }}</span>
</template>
</el-table-column>
<el-table-column label="报价(元)" width="120">
<template slot-scope="s">
<el-input-number v-model="s.row.unitPrice" :min="0" :precision="4"
size="mini" controls-position="right" style="width:100%" @change="calcItem(s.row)" />
</template>
</el-table-column>
<el-table-column label="金额(元)" width="110" align="right">
<template slot-scope="s">
<strong style="color:#409EFF">¥{{ itemTotal(s.row) }}</strong>
</template>
</el-table-column>
<el-table-column label="交期(天)" width="85">
<template slot-scope="s">
<el-input-number v-model="s.row.deliveryDays" :min="0"
size="mini" controls-position="right" style="width:100%" />
</template>
</el-table-column>
<el-table-column label="备注" min-width="100">
<template slot-scope="s">
<el-input v-model="s.row.remark" size="mini" placeholder="备注" />
</template>
</el-table-column>
<el-table-column width="46" align="center">
<template slot="header">
<el-button type="text" icon="el-icon-plus" @click="addItem" />
</template>
<template slot-scope="s">
<el-button type="text" icon="el-icon-delete" style="color:#f56c6c"
@click="form.items.splice(s.$index, 1)" />
</template>
</el-table-column>
</el-table>
<div class="form-total-bar">
合计报价<strong>¥{{ formTotal }}</strong>
<span style="margin-left:16px;color:#909399;font-size:12px">{{ form.items.length }} 项物料</span>
</div>
<el-form-item label="备注" style="margin-top:12px">
<el-input v-model="form.note" type="textarea" :rows="2" placeholder="整体备注说明" />
</el-form-item>
</el-form>
<div slot="footer">
<el-button @click="dialogOpen = false">取消</el-button>
<el-button type="success" @click="submitForm('draft')" :loading="saving">保存草稿</el-button>
<el-button type="primary" @click="submitForm('submit')" :loading="submitting">保存并提交</el-button>
</div>
</el-dialog>
<!-- 详情对话框 -->
<el-dialog title="报价单详情" :visible.sync="detailOpen" width="860px" append-to-body>
<div v-if="detailData">
<!-- 状态流程条 -->
<div class="detail-steps">
<div class="step-item" :class="{ active: ['draft','submitted','accepted','rejected'].includes(detailData.status) }">
<i class="el-icon-edit-outline"></i><span>草稿</span>
</div>
<div class="step-line" :class="{ active: ['submitted','accepted','rejected'].includes(detailData.status) }"></div>
<div class="step-item" :class="{ active: ['submitted','accepted','rejected'].includes(detailData.status) }">
<i class="el-icon-upload2"></i><span>已提交</span>
</div>
<div class="step-line" :class="{ active: ['accepted','rejected'].includes(detailData.status) }"></div>
<div class="step-item" :class="{ active: detailData.status === 'accepted', rejected: detailData.status === 'rejected' }">
<i :class="detailData.status === 'rejected' ? 'el-icon-circle-close' : 'el-icon-circle-check'"></i>
<span>{{ detailData.status === 'rejected' ? '已拒绝' : '已采纳' }}</span>
</div>
</div>
<!-- PDF 内容区 -->
<div id="quote-pdf-area" class="pdf-area">
<div class="pdf-header">
<img :src="logoSrc" class="pdf-logo" />
<div class="pdf-header-text">
<div class="pdf-company">福安德综合报价系统</div>
<div class="pdf-doc-type">供应商报价单</div>
</div>
<div class="pdf-header-no">{{ detailData.quoteNo }}</div>
</div>
<div class="pdf-divider"></div>
<table class="pdf-meta-table">
<tr>
<td class="meta-label">报价单号</td><td class="meta-val">{{ detailData.quoteNo }}</td>
<td class="meta-label">供应商</td><td class="meta-val"><strong>{{ detailData.supplierName }}</strong></td>
</tr>
<tr>
<td class="meta-label">关联询价</td><td class="meta-val">{{ detailData.rfqNo }}</td>
<td class="meta-label">状态</td>
<td class="meta-val">
<el-tag :type="statusType(detailData.status)" size="small">{{ statusLabel(detailData.status) }}</el-tag>
</td>
</tr>
<tr>
<td class="meta-label">报价总额</td>
<td class="meta-val amount">¥{{ detailData.totalAmount | money }}</td>
<td class="meta-label">整体交期</td>
<td class="meta-val">{{ detailData.deliveryDays || '-' }} </td>
</tr>
<tr>
<td class="meta-label">有效期</td><td class="meta-val">{{ detailData.validDays }} </td>
<td class="meta-label">币种</td><td class="meta-val">{{ detailData.currency }}</td>
</tr>
<tr v-if="detailData.note">
<td class="meta-label">备注</td><td class="meta-val" colspan="3">{{ detailData.note }}</td>
</tr>
</table>
<div class="pdf-section-title">报价明细</div>
<table class="pdf-items-table">
<thead>
<tr>
<th>#</th><th>物料名称</th><th>规格</th><th>型号</th>
<th>单位</th><th>数量</th><th>单价()</th><th>金额()</th><th>交期()</th>
</tr>
</thead>
<tbody>
<tr v-for="(item, i) in detailData.items" :key="i">
<td>{{ i + 1 }}</td>
<td style="text-align:left;font-weight:500">{{ item.materialName }}</td>
<td>{{ item.spec || '-' }}</td>
<td>{{ item.modelNo || item.spec || '-' }}</td>
<td>{{ item.unit }}</td>
<td>{{ item.quantity }}</td>
<td>{{ item.unitPrice }}</td>
<td class="amount-cell">{{ item.totalPrice || itemTotal(item) }}</td>
<td>{{ item.deliveryDays || '-' }}</td>
</tr>
</tbody>
<tfoot>
<tr>
<td colspan="7" style="text-align:right;font-weight:700;padding:10px">合计金额</td>
<td class="amount-cell total-cell" colspan="2">¥{{ detailData.totalAmount | money }}</td>
</tr>
</tfoot>
</table>
<div class="pdf-footer">生成时间{{ new Date().toLocaleString('zh-CN') }}</div>
</div>
</div>
<div slot="footer">
<el-button @click="detailOpen = false">关闭</el-button>
<el-button type="primary" icon="el-icon-download" :loading="pdfLoading" @click="exportPdf">导出 PDF</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import { listQuotation, getQuotation, addQuotation, updateQuotation,
submitQuotation, acceptQuotation, rejectQuotation, delQuotation } from "@/api/bid/quotation";
import { listRfq, getRfqItems } from "@/api/bid/rfq";
import { listSupplier } from "@/api/bid/supplier";
import logoImg from "@/assets/logo/logo.png";
import html2canvas from "html2canvas";
import jsPDF from "jspdf";
export default {
name: "Quotation",
filters: { money: v => v ? Number(v).toFixed(2) : "0.00" },
data() {
return {
loading: false, saving: false, submitting: false, pdfLoading: false,
total: 0, list: [],
stats: { total: 0, draft: 0, submitted: 0, accepted: 0 },
dialogOpen: false, dialogTitle: "",
detailOpen: false, detailData: null,
rfqItemsLoading: false,
rfqOptions: [], supplierOptions: [],
logoSrc: logoImg,
queryParams: { pageNum: 1, pageSize: 10, status: null, rfqNo: null, quoteNo: null, supplierName: null },
form: { items: [], currency: "CNY", validDays: 30, deliveryDays: null },
rules: {
rfqId: [{ required: true, message: "请选择询价单", trigger: "change" }],
supplierId: [{ required: true, message: "请选择供应商", trigger: "change" }],
}
};
},
computed: {
/** 是否为供应商用户 */
isSupplier() {
return this.$store.getters.roles && this.$store.getters.roles.includes('supplier');
},
formTotal() {
return (this.form.items || []).reduce((s, i) => s + (parseFloat(i.quantity || 0) * parseFloat(i.unitPrice || 0)), 0).toFixed(2);
}
},
created() {
// 读取 URL 查询参数,实现跨页面跳转自动搜索
const query = this.$route.query;
if (query.rfqNo) this.queryParams.rfqNo = query.rfqNo;
if (query.supplierName) this.queryParams.supplierName = query.supplierName;
if (query.quoteNo) this.queryParams.quoteNo = query.quoteNo;
this.getList();
// 供应商只需加载自己可见的RFQ无需加载全部供应商列表
listRfq({ pageSize: 200 }).then(r => { this.rfqOptions = r.rows || []; });
if (!this.isSupplier) {
listSupplier({ pageSize: 200 }).then(r => { this.supplierOptions = r.rows || []; });
}
},
methods: {
getList() {
this.loading = true;
listQuotation(this.queryParams).then(r => {
this.list = r.rows || [];
this.total = r.total || 0;
// Compute stats
this.stats.total = this.total;
this.stats.draft = (r.rows || []).filter(x => x.status === "draft").length;
this.stats.submitted = (r.rows || []).filter(x => x.status === "submitted").length;
this.stats.accepted = (r.rows || []).filter(x => x.status === "accepted").length;
this.loading = false;
}).catch(() => { this.loading = false; });
},
handleQuery() { this.queryParams.pageNum = 1; this.getList(); },
resetQuery() {
this.resetForm("queryForm");
this.queryParams.quoteNo = null;
this.handleQuery();
},
handleAdd() {
this.form = { items: [], currency: "CNY", validDays: 30, deliveryDays: null, status: "draft" };
this.dialogTitle = "新建报价单";
this.dialogOpen = true;
},
handleUpdate(row) {
getQuotation(row.quotationId).then(r => {
this.form = { ...r.data, items: r.data.items || [] };
this.dialogTitle = "编辑报价单";
this.dialogOpen = true;
});
},
handleView(row) {
getQuotation(row.quotationId).then(r => {
this.detailData = r.data;
this.detailOpen = true;
});
},
async onRfqChange(rfqId) {
if (!rfqId) return;
this.rfqItemsLoading = true;
try {
const res = await getRfqItems(rfqId);
const items = res.data || [];
this.form.items = items.map(i => ({
materialName: i.materialName || "",
spec: i.spec || "",
modelNo: i.modelNo || "",
unit: i.unit || "件",
quantity: i.quantity || 1,
unitPrice: 0,
deliveryDays: null,
remark: ""
}));
if (this.form.items.length > 0) {
this.$message.success(`已加载 ${this.form.items.length} 项物料需求`);
}
} catch(e) {
// If items API fails, just leave empty
}
this.rfqItemsLoading = false;
},
addItem() {
this.form.items.push({ materialName: "", spec: "", modelNo: "", unit: "件", quantity: 1, unitPrice: 0, deliveryDays: null, remark: "" });
},
calcItem(row) {
// trigger reactivity
this.$set(row, "unitPrice", row.unitPrice);
},
itemTotal(row) {
return ((parseFloat(row.quantity) || 0) * (parseFloat(row.unitPrice) || 0)).toFixed(2);
},
handleSubmit(row) {
this.$modal.confirm("确认提交报价?提交后不可修改").then(() => submitQuotation(row.quotationId))
.then(() => { this.$modal.msgSuccess("提交成功"); this.getList(); });
},
handleAccept(row) {
this.$modal.confirm("确认采纳此报价?").then(() => acceptQuotation(row.quotationId))
.then(() => { this.$modal.msgSuccess("已采纳"); this.getList(); });
},
handleReject(row) {
this.$modal.confirm("确认拒绝此报价?").then(() => rejectQuotation(row.quotationId))
.then(() => { this.$modal.msgSuccess("已拒绝"); this.getList(); });
},
handleDelete(row) {
this.$modal.confirm("确认删除?").then(() => delQuotation(row.quotationId))
.then(() => { this.$modal.msgSuccess("删除成功"); this.getList(); });
},
submitForm(mode) {
this.$refs.form.validate(valid => {
if (!valid) return;
if (mode === "submit") this.submitting = true;
else this.saving = true;
const action = this.form.quotationId ? updateQuotation : addQuotation;
action(this.form).then(res => {
const id = (res.data && res.data.quotationId) || this.form.quotationId;
if (mode === "submit" && id) {
return submitQuotation(id).then(() => {
this.$modal.msgSuccess("提交成功");
this.dialogOpen = false;
this.getList();
});
}
this.$modal.msgSuccess("保存成功");
this.dialogOpen = false;
this.getList();
}).finally(() => { this.saving = false; this.submitting = false; });
});
},
async exportPdf() {
this.pdfLoading = true;
await this.$nextTick();
try {
const el = document.getElementById("quote-pdf-area");
const canvas = await html2canvas(el, { scale: 2, useCORS: true, backgroundColor: "#ffffff" });
const imgData = canvas.toDataURL("image/png");
const pdf = new jsPDF({ orientation: "p", unit: "mm", format: "a4" });
const pageW = pdf.internal.pageSize.getWidth();
const pageH = pdf.internal.pageSize.getHeight();
const imgH = (canvas.height * pageW) / canvas.width;
let y = 0, rem = imgH, first = true;
while (rem > 0) {
if (!first) pdf.addPage();
pdf.addImage(imgData, "PNG", 0, y, pageW, imgH);
y -= pageH; rem -= pageH; first = false;
}
pdf.save("报价单_" + this.detailData.quoteNo + ".pdf");
this.$message.success("PDF已导出");
} catch(e) { this.$message.error("导出失败:" + e.message); }
finally { this.pdfLoading = false; }
},
statusType(s) { return { draft: "info", submitted: "warning", accepted: "success", rejected: "danger" }[s] || ""; },
statusLabel(s) { return { draft: "草稿", submitted: "已提交", accepted: "已采纳", rejected: "已拒绝" }[s] || s; },
statusIcon(s) { return { draft: "el-icon-edit-outline", submitted: "el-icon-time", accepted: "el-icon-circle-check", rejected: "el-icon-circle-close" }[s] || "el-icon-document"; }
}
};
</script>
<style lang="scss" scoped>
.quotation-page { padding-bottom: 30px; }
/* ── 顶部统计卡片 ── */
.stat-row { margin-bottom: 16px; }
.stat-card {
border-radius: 10px; padding: 18px 20px; position: relative;
overflow: hidden; color: #fff; cursor: default;
}
.stat-num { font-size: 32px; font-weight: 700; line-height: 1; }
.stat-lbl { font-size: 13px; margin-top: 6px; opacity: 0.9; }
.stat-icon { position: absolute; right: 16px; top: 50%; transform: translateY(-50%); font-size: 48px; opacity: 0.2; }
.stat-all { background: linear-gradient(135deg, #1171c4, #22a4ff); }
.stat-draft { background: linear-gradient(135deg, #909399, #b0b3b8); }
.stat-submitted{ background: linear-gradient(135deg, #e6a23c, #f0c040); }
.stat-accepted { background: linear-gradient(135deg, #67c23a, #85ce61); }
/* ── 搜索 ── */
.search-card { ::v-deep .el-card__body { padding: 16px 20px 8px; } }
/* ── 状态芯片 ── */
.status-chip {
display: inline-flex; align-items: center; gap: 4px; padding: 3px 10px;
border-radius: 12px; font-size: 12px; font-weight: 600;
i { font-size: 12px; }
}
.status-draft { background: #f4f4f5; color: #909399; }
.status-submitted { background: #fdf6ec; color: #e6a23c; border: 1px solid #faecd8; }
.status-accepted { background: #f0f9eb; color: #67c23a; border: 1px solid #c2e7b0; }
.status-rejected { background: #fef0f0; color: #f56c6c; border: 1px solid #fbc4c4; }
/* ── 表单合计 ── */
.items-table { margin-bottom: 0; }
.form-total-bar {
text-align: right; padding: 10px 16px;
background: linear-gradient(90deg, #f9fbff, #f0f7ff);
border: 1px solid #e4e7ed; border-top: none; border-radius: 0 0 4px 4px;
font-size: 14px; color: #606266;
strong { font-size: 20px; color: #409EFF; margin-left: 6px; }
}
/* ── 详情 - 状态流程 ── */
.detail-steps {
display: flex; align-items: center; justify-content: center;
padding: 16px 0 20px; gap: 0;
}
.step-item {
display: flex; flex-direction: column; align-items: center; gap: 4px;
color: #c0c4cc; font-size: 12px;
i { font-size: 22px; }
&.active { color: #1171c4; }
&.rejected { color: #f56c6c; }
}
.step-line {
flex: 1; max-width: 80px; height: 2px; background: #e4e7ed; margin: 0 8px; margin-top: -12px;
&.active { background: #1171c4; }
}
/* ── PDF ── */
.pdf-area { padding: 24px; background: #fff; font-family: "Microsoft YaHei", "Noto Sans SC", Arial, sans-serif; font-size: 13px; color: #222; }
.pdf-header { display: flex; align-items: center; padding-bottom: 14px; }
.pdf-logo { width: 48px; height: 48px; object-fit: contain; margin-right: 16px; }
.pdf-header-text { flex: 1; }
.pdf-company { font-size: 20px; font-weight: 700; color: #1171c4; letter-spacing: 1px; }
.pdf-doc-type { font-size: 13px; color: #666; margin-top: 2px; }
.pdf-header-no { font-size: 13px; color: #888; }
.pdf-divider { border-top: 2px solid #1171c4; margin-bottom: 16px; }
.pdf-meta-table { width: 100%; border-collapse: collapse; margin-bottom: 20px; td { padding: 7px 10px; border: 1px solid #e4e7ed; } }
.meta-label { background: #f5f7fa; color: #606266; font-weight: 600; width: 90px; }
.meta-val { color: #303133; }
.amount { color: #409EFF; font-weight: 700; font-size: 15px; }
.pdf-section-title { font-size: 14px; font-weight: 700; color: #1a2c4e; margin: 0 0 10px; padding-left: 8px; border-left: 4px solid #1171c4; }
.pdf-items-table { width: 100%; border-collapse: collapse; margin-bottom: 20px;
th { background: #1171c4; color: #fff; padding: 8px; text-align: center; font-weight: 600; font-size: 12px; }
td { border: 1px solid #e4e7ed; padding: 7px 8px; text-align: center; font-size: 12px; }
tbody tr:nth-child(even) td { background: #f9fbff; }
}
.amount-cell { color: #409EFF; font-weight: 600; }
.total-cell { font-size: 15px; background: #f0f7ff !important; font-weight: 700; }
.pdf-footer { text-align: right; font-size: 11px; color: #aaa; margin-top: 10px; border-top: 1px solid #f0f2f5; padding-top: 8px; }
</style>