| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316 |
- <template>
- <div class="app-container supply-dashboard-page">
- <div class="page-header">
- <h2><i class="el-icon-s-order"></i> 供应商成本分析</h2>
- <p class="page-desc">分析各供应商针对特定产品的报价及成本竞争力</p>
- </div>
- <!-- 搜索框 -->
- <el-card class="box-card">
- <div slot="header" class="clearfix">
- <span><i class="el-icon-search"></i> 查询产品成本分析</span>
- </div>
- <el-form :inline="true" class="form-inline">
- <el-form-item label="产品编码">
- <el-input
- v-model="inputProductCode"
- placeholder="请输入产品编码(如:20220606J0100MR4)"
- style="width: 300px"
- @keyup.enter.native="fetchCostData(inputProductCode)"
- />
- </el-form-item>
- <el-form-item>
- <el-button type="primary" @click="fetchCostData(inputProductCode)"
- >分析成本</el-button
- >
- </el-form-item>
- </el-form>
- </el-card>
- <!-- 表格数据 -->
- <el-card v-loading="loading" class="box-card">
- <div slot="header" class="clearfix">
- <span><i class="el-icon-document"></i> 成本详情表格</span>
- <el-button
- v-if="costDetails.length > 0"
- style="float: right; padding: 3px 0"
- type="text"
- @click="exportData"
- >
- 导出数据
- </el-button>
- </div>
- <el-table
- v-if="costDetails.length > 0"
- :data="costDetails"
- highlight-current-row
- style="width: 100%"
- >
- <el-table-column prop="供应商名称" label="供应商名称" min-width="220" />
- <el-table-column prop="参考价" label="参考价" width="110" align="right">
- <template slot-scope="scope">
- ¥{{ formatNumber(scope.row.参考价, 2) }}
- </template>
- </el-table-column>
- <el-table-column prop="入库数量" label="入库数量" width="120" align="right">
- <template slot-scope="scope">
- {{ formatNumber(scope.row.入库数量, 0) }}
- </template>
- </el-table-column>
- <el-table-column prop="入库金额" label="入库金额" width="130" align="right">
- <template slot-scope="scope">
- ¥{{ formatNumber(scope.row.入库金额, 2) }}
- </template>
- </el-table-column>
- <el-table-column prop="采购占比" label="采购占比" width="110" align="right">
- <template slot-scope="scope">
- {{ formatPercent(scope.row.采购占比) }}
- </template>
- </el-table-column>
- <el-table-column prop="价格差异率" label="价格差异率" width="120" align="right">
- <template slot-scope="scope">
- {{ formatPercent(scope.row.价格差异率) }}
- </template>
- </el-table-column>
- <el-table-column prop="成本排名" label="成本排名" width="100" align="center">
- <template slot-scope="scope"> 第{{ scope.row.成本排名 }}名 </template>
- </el-table-column>
- <el-table-column prop="成本分数" label="成本分数" width="110" align="right">
- <template slot-scope="scope">
- {{ formatNumber(scope.row.成本分数, 2) }}
- </template>
- </el-table-column>
- <el-table-column prop="成本等级" label="成本等级" width="100" align="center">
- <template slot-scope="scope">
- <el-tag :type="costLevelType(scope.row.成本等级)" size="mini">
- {{ scope.row.成本等级 }}
- </el-tag>
- </template>
- </el-table-column>
- <el-table-column prop="入库行数" label="入库行数" width="100" align="right" />
- <el-table-column prop="最近验收日期" label="最近验收日期" width="130" />
- </el-table>
- <div v-else class="no-data">
- <p v-if="error" class="error">{{ error }}</p>
- <p v-else>暂无成本数据,请输入产品编码进行分析</p>
- </div>
- </el-card>
- </div>
- </template>
- <script>
- import { saveAs } from "file-saver";
- import { getProductDetails } from "@/api/supply";
- export default {
- name: "SupplyCostAnalysis",
- data() {
- return {
- inputProductCode: "",
- costDetails: [],
- loading: false,
- error: "",
- };
- },
- created() {
- // 如果有传入的 productCode,则自动触发分析
- if (this.$route.query.productCode) {
- this.inputProductCode = this.$route.query.productCode;
- this.fetchCostData(this.inputProductCode);
- }
- },
- methods: {
- // 获取成本数据
- async fetchCostData(code) {
- if (!code) {
- this.$modal.msgError("请输入产品编码");
- return;
- }
- this.loading = true;
- this.error = "";
- try {
- // 使用项目标准的request方式调用API
- const response = await getProductDetails(code);
- // Flask API通常直接返回数据,没有code字段
- if (response && response.cost_details !== undefined) {
- this.costDetails = this.enrichCostDetails(response.cost_details || []);
- this.$modal.msgSuccess("成本分析完成");
- } else {
- // 尝试其他可能的数据结构
- if (response && Array.isArray(response)) {
- this.costDetails = this.enrichCostDetails(response || []);
- this.$modal.msgSuccess("成本分析完成");
- } else {
- this.error = "获取成本数据失败,API响应格式不匹配";
- this.$modal.msgError(this.error);
- }
- }
- } catch (err) {
- console.error("获取成本数据失败:", err);
- this.error = err.message || "获取成本数据失败";
- this.$modal.msgError(this.error);
- } finally {
- this.loading = false;
- }
- },
- enrichCostDetails(rows) {
- const list = Array.isArray(rows) ? rows : [];
- const totalAmount = list.reduce(
- (sum, row) => sum + this.toNumber(row.入库金额),
- 0
- );
- const minPrice = list.reduce((min, row) => {
- const price = this.toNumber(row.参考价);
- return price > 0 ? Math.min(min, price) : min;
- }, Number.POSITIVE_INFINITY);
- return list.map((row) => {
- const amount = this.toNumber(row.入库金额);
- const price = this.toNumber(row.参考价);
- const diffRate = Number.isFinite(minPrice) && minPrice > 0
- ? (price - minPrice) / minPrice
- : 0;
- return {
- ...row,
- 采购占比: totalAmount > 0 ? amount / totalAmount : 0,
- 价格差异率: diffRate,
- 成本等级: this.costLevel(row.成本排名),
- };
- });
- },
- toNumber(value) {
- const number = Number(value);
- return Number.isFinite(number) ? number : 0;
- },
- formatNumber(value, digits = 2) {
- return this.toNumber(value).toLocaleString("zh-CN", {
- minimumFractionDigits: digits,
- maximumFractionDigits: digits,
- });
- },
- formatPercent(value) {
- return `${(this.toNumber(value) * 100).toFixed(1)}%`;
- },
- costLevel(rank) {
- const value = Number(rank);
- if (value === 1) return "低成本";
- if (value <= 3) return "中成本";
- return "高成本";
- },
- costLevelType(level) {
- if (level === "低成本") return "success";
- if (level === "中成本") return "warning";
- return "danger";
- },
- // 导出数据
- exportData() {
- if (!this.costDetails || this.costDetails.length === 0) {
- this.$modal.msgError("暂无数据可导出");
- return;
- }
- // 创建CSV内容
- const header = [
- "供应商名称",
- "参考价",
- "入库数量",
- "入库金额",
- "采购占比",
- "价格差异率",
- "成本排名",
- "成本分数",
- "成本等级",
- "入库行数",
- "最近验收日期",
- ].join(",");
- const csvContent = [
- header,
- ...this.costDetails.map((row) =>
- [
- `"${row.供应商名称}"`,
- row.参考价,
- row.入库数量,
- row.入库金额,
- this.formatPercent(row.采购占比),
- this.formatPercent(row.价格差异率),
- `第${row.成本排名}名`,
- row.成本分数,
- row.成本等级,
- row.入库行数,
- row.最近验收日期,
- ].join(",")
- ),
- ].join("\n");
- // 下载文件
- const blob = new Blob(["\ufeff" + csvContent], {
- type: "text/csv;charset=utf-8;",
- });
- saveAs(blob, `供应商成本分析_${new Date().getTime()}.csv`);
- },
- },
- };
- </script>
- <style lang="scss" scoped>
- .app-container {
- padding: 20px;
- }
- .page-header {
- margin-bottom: 20px;
- h2 {
- font-size: 24px;
- font-weight: 600;
- color: #303133;
- margin-bottom: 8px;
- i {
- margin-right: 8px;
- color: #409eff;
- }
- }
- .page-desc {
- color: #909399;
- font-size: 14px;
- margin: 0;
- }
- }
- .box-card {
- margin-bottom: 20px;
- }
- .form-inline {
- display: flex;
- align-items: center;
- }
- .no-data {
- text-align: center;
- padding: 40px 0;
- color: #909399;
- .error {
- color: #f56c6c;
- }
- }
- ::v-deep .el-card__header {
- font-weight: bold;
- }
- </style>
|