2026-05-22 10:07:56 +08:00
|
|
|
<template>
|
|
|
|
|
<div class="dashboard">
|
|
|
|
|
<el-row :gutter="20" class="stat-row">
|
2026-06-06 15:20:46 +08:00
|
|
|
<el-col :xs="12" :sm="6" v-for="item in (isSupplier ? statCards.filter(c => c.key==='rfqs') : statCards)" :key="item.key">
|
2026-05-22 10:07:56 +08:00
|
|
|
<div class="stat-card" :style="{ borderTopColor: item.color }">
|
|
|
|
|
<div class="stat-icon" :style="{ background: item.color + '18', color: item.color }">
|
|
|
|
|
<i :class="item.icon" />
|
|
|
|
|
</div>
|
|
|
|
|
<div class="stat-body">
|
|
|
|
|
<div class="stat-num">{{ stats[item.key] }}</div>
|
|
|
|
|
<div class="stat-label">{{ item.label }}</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</el-col>
|
|
|
|
|
</el-row>
|
|
|
|
|
|
|
|
|
|
<el-row :gutter="20">
|
|
|
|
|
<el-col :xs="24" :lg="14">
|
|
|
|
|
<el-card class="panel-card">
|
|
|
|
|
<div slot="header" class="panel-header">
|
|
|
|
|
<span><i class="el-icon-document" /> 最近询价单</span>
|
2026-06-06 15:20:46 +08:00
|
|
|
<router-link to="/rfq" class="panel-more">查看全部</router-link>
|
2026-05-22 10:07:56 +08:00
|
|
|
</div>
|
|
|
|
|
<el-table :data="recentRfqs" size="small" style="width:100%">
|
|
|
|
|
<el-table-column prop="rfqNo" label="单号" width="160" />
|
|
|
|
|
<el-table-column prop="title" label="标题" show-overflow-tooltip />
|
|
|
|
|
<el-table-column prop="deadline" label="截止日期" width="105" />
|
|
|
|
|
<el-table-column label="状态" width="80">
|
|
|
|
|
<template slot-scope="scope">
|
|
|
|
|
<el-tag size="mini" :type="rfqStatusType(scope.row.status)">{{ rfqStatusText(scope.row.status) }}</el-tag>
|
|
|
|
|
</template>
|
|
|
|
|
</el-table-column>
|
|
|
|
|
</el-table>
|
|
|
|
|
</el-card>
|
|
|
|
|
</el-col>
|
2026-06-06 15:20:46 +08:00
|
|
|
<el-col :xs="24" :lg="10" v-if="!isSupplier">
|
2026-05-22 10:07:56 +08:00
|
|
|
<el-card class="panel-card">
|
|
|
|
|
<div slot="header" class="panel-header">
|
|
|
|
|
<span><i class="el-icon-shopping-cart-full" /> 最近采购单</span>
|
2026-06-06 15:20:46 +08:00
|
|
|
<router-link to="/purchaseorder" class="panel-more">查看全部</router-link>
|
2026-05-22 10:07:56 +08:00
|
|
|
</div>
|
|
|
|
|
<el-table :data="recentPos" size="small" style="width:100%">
|
|
|
|
|
<el-table-column prop="poNo" label="单号" width="140" />
|
|
|
|
|
<el-table-column prop="supplierName" label="供应商" show-overflow-tooltip />
|
|
|
|
|
<el-table-column label="状态" width="80">
|
|
|
|
|
<template slot-scope="scope">
|
|
|
|
|
<el-tag size="mini" :type="poStatusType(scope.row.status)">{{ poStatusText(scope.row.status) }}</el-tag>
|
|
|
|
|
</template>
|
|
|
|
|
</el-table-column>
|
|
|
|
|
</el-table>
|
|
|
|
|
</el-card>
|
|
|
|
|
</el-col>
|
|
|
|
|
</el-row>
|
|
|
|
|
|
|
|
|
|
<el-row :gutter="20" style="margin-top:20px">
|
2026-06-06 15:20:46 +08:00
|
|
|
<el-col :xs="24" :lg="10" v-if="!isSupplier">
|
2026-05-22 10:07:56 +08:00
|
|
|
<el-card class="panel-card">
|
|
|
|
|
<div slot="header" class="panel-header">
|
|
|
|
|
<span><i class="el-icon-s-custom" /> 供应商列表</span>
|
2026-06-06 15:20:46 +08:00
|
|
|
<router-link to="/supplier" class="panel-more">查看全部</router-link>
|
2026-05-22 10:07:56 +08:00
|
|
|
</div>
|
|
|
|
|
<el-table :data="suppliers" size="small" style="width:100%">
|
|
|
|
|
<el-table-column prop="supplierName" label="供应商名称" show-overflow-tooltip />
|
|
|
|
|
<el-table-column prop="contactName" label="联系人" width="90" />
|
|
|
|
|
<el-table-column label="等级" width="70">
|
|
|
|
|
<template slot-scope="scope">
|
|
|
|
|
<el-tag size="mini" :type="gradeType(scope.row.grade)">{{ scope.row.grade || 'B' }}</el-tag>
|
|
|
|
|
</template>
|
|
|
|
|
</el-table-column>
|
|
|
|
|
</el-table>
|
|
|
|
|
</el-card>
|
|
|
|
|
</el-col>
|
|
|
|
|
<el-col :xs="24" :lg="14">
|
|
|
|
|
<el-card class="panel-card">
|
|
|
|
|
<div slot="header" class="panel-header">
|
|
|
|
|
<span><i class="el-icon-magic-stick" /> 快捷操作</span>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="quick-actions">
|
2026-06-06 15:20:46 +08:00
|
|
|
<router-link v-for="action in (isSupplier ? supplierQuickActions : quickActions)" :key="action.path" :to="action.path" class="quick-btn">
|
2026-05-22 10:07:56 +08:00
|
|
|
<div class="quick-icon" :style="{ background: action.color + '18', color: action.color }">
|
|
|
|
|
<i :class="action.icon" />
|
|
|
|
|
</div>
|
|
|
|
|
<span>{{ action.label }}</span>
|
|
|
|
|
</router-link>
|
|
|
|
|
</div>
|
|
|
|
|
</el-card>
|
|
|
|
|
</el-col>
|
|
|
|
|
</el-row>
|
|
|
|
|
</div>
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<script>
|
|
|
|
|
import { listSupplier } from "@/api/bid/supplier"
|
|
|
|
|
import { listMaterial } from "@/api/bid/material"
|
|
|
|
|
import { listRfq } from "@/api/bid/rfq"
|
|
|
|
|
import { listPurchaseorder } from "@/api/bid/purchaseorder"
|
|
|
|
|
|
|
|
|
|
export default {
|
|
|
|
|
name: "Dashboard",
|
2026-06-06 15:20:46 +08:00
|
|
|
computed: {
|
|
|
|
|
isSupplier() {
|
|
|
|
|
return this.$store.getters.roles && this.$store.getters.roles.includes('supplier');
|
|
|
|
|
},
|
|
|
|
|
supplierQuickActions() {
|
|
|
|
|
return [
|
|
|
|
|
{ label: "报价请求", icon: "el-icon-document", color: "#1171c4", path: "/rfq" },
|
|
|
|
|
{ label: "我的报价", icon: "el-icon-money", color: "#67c23a", path: "/quotation" }
|
|
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
},
|
2026-05-22 10:07:56 +08:00
|
|
|
data() {
|
|
|
|
|
return {
|
|
|
|
|
stats: { suppliers: 0, materials: 0, rfqs: 0, pos: 0 },
|
|
|
|
|
recentRfqs: [],
|
|
|
|
|
recentPos: [],
|
|
|
|
|
suppliers: [],
|
|
|
|
|
statCards: [
|
|
|
|
|
{ key: "suppliers", label: "供应商", icon: "el-icon-s-custom", color: "#1171c4" },
|
|
|
|
|
{ key: "materials", label: "物料数", icon: "el-icon-goods", color: "#67c23a" },
|
|
|
|
|
{ key: "rfqs", label: "询价单", icon: "el-icon-document", color: "#e6a23c" },
|
|
|
|
|
{ key: "pos", label: "采购单", icon: "el-icon-shopping-cart-full", color: "#f56c6c" }
|
|
|
|
|
],
|
|
|
|
|
quickActions: [
|
2026-06-06 15:20:46 +08:00
|
|
|
{ label: "新建询价单", icon: "el-icon-edit", color: "#1171c4", path: "/rfq" },
|
|
|
|
|
{ label: "物料管理", icon: "el-icon-goods", color: "#67c23a", path: "/material" },
|
|
|
|
|
{ label: "智慧比价", icon: "el-icon-data-analysis", color: "#e6a23c", path: "/comparison" },
|
|
|
|
|
{ label: "采购单", icon: "el-icon-shopping-cart-full", color: "#f56c6c", path: "/purchaseorder" },
|
|
|
|
|
{ label: "供应商", icon: "el-icon-s-cooperation", color: "#9b59b6", path: "/supplier" },
|
|
|
|
|
{ label: "供应商评价", icon: "el-icon-star-off", color: "#e67e22", path: "/evaluation" }
|
2026-05-22 10:07:56 +08:00
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
created() {
|
|
|
|
|
this.loadDashboard()
|
|
|
|
|
},
|
|
|
|
|
methods: {
|
|
|
|
|
loadDashboard() {
|
2026-06-06 15:20:46 +08:00
|
|
|
// 供应商只加载自己有权限的RFQ数据
|
|
|
|
|
if (this.isSupplier) {
|
|
|
|
|
listRfq({ pageNum: 1, pageSize: 5 }).then(r => {
|
|
|
|
|
this.recentRfqs = r.rows || []
|
|
|
|
|
this.stats.rfqs = r.total || 0
|
|
|
|
|
}).catch(() => {})
|
|
|
|
|
return
|
|
|
|
|
}
|
2026-05-22 10:07:56 +08:00
|
|
|
listSupplier({ pageNum: 1, pageSize: 5 }).then(r => {
|
|
|
|
|
this.suppliers = r.rows || []
|
|
|
|
|
this.stats.suppliers = r.total || 0
|
|
|
|
|
}).catch(() => {})
|
|
|
|
|
listMaterial({ pageNum: 1, pageSize: 1 }).then(r => {
|
|
|
|
|
this.stats.materials = r.total || 0
|
|
|
|
|
}).catch(() => {})
|
|
|
|
|
listRfq({ pageNum: 1, pageSize: 5 }).then(r => {
|
|
|
|
|
this.recentRfqs = r.rows || []
|
|
|
|
|
this.stats.rfqs = r.total || 0
|
|
|
|
|
}).catch(() => {})
|
|
|
|
|
listPurchaseorder({ pageNum: 1, pageSize: 5 }).then(r => {
|
|
|
|
|
this.recentPos = r.rows || []
|
|
|
|
|
this.stats.pos = r.total || 0
|
|
|
|
|
}).catch(() => {})
|
|
|
|
|
},
|
|
|
|
|
rfqStatusType(s) { return s === "open" ? "primary" : s === "closed" ? "info" : s === "awarded" ? "success" : "warning" },
|
|
|
|
|
rfqStatusText(s) { return s === "open" ? "询价中" : s === "closed" ? "已关闭" : s === "awarded" ? "已定标" : (s || "-") },
|
|
|
|
|
poStatusType(s) { return s === "approved" ? "success" : s === "pending" ? "warning" : s === "rejected" ? "danger" : "info" },
|
|
|
|
|
poStatusText(s) { return s === "approved" ? "已审批" : s === "pending" ? "待审批" : s === "rejected" ? "已拒绝" : (s || "-") },
|
|
|
|
|
gradeType(g) { return g === "A" ? "success" : g === "B" ? "primary" : g === "C" ? "warning" : "info" }
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
<style lang="scss" scoped>
|
|
|
|
|
.dashboard { padding: 20px; background: #f5f7fa; min-height: calc(100vh - 84px); }
|
|
|
|
|
.stat-row { margin-bottom: 20px !important; }
|
|
|
|
|
.stat-card {
|
|
|
|
|
background: #fff; border-radius: 8px; border-top: 3px solid #1171c4;
|
|
|
|
|
padding: 20px; display: flex; align-items: center; gap: 16px;
|
|
|
|
|
box-shadow: 0 2px 8px rgba(0,0,0,0.05); margin-bottom: 0;
|
|
|
|
|
}
|
|
|
|
|
.stat-icon { width: 52px; height: 52px; border-radius: 12px; display: flex; align-items: center; justify-content: center; font-size: 24px; flex-shrink: 0; }
|
|
|
|
|
.stat-num { font-size: 28px; font-weight: 700; color: #1a2c4e; line-height: 1; }
|
|
|
|
|
.stat-label { font-size: 13px; color: #8c97a8; margin-top: 4px; }
|
|
|
|
|
.panel-card {
|
|
|
|
|
border-radius: 8px; box-shadow: 0 2px 8px rgba(0,0,0,0.05);
|
|
|
|
|
::v-deep .el-card__header { padding: 14px 20px; border-bottom: 1px solid #f0f2f5; }
|
|
|
|
|
}
|
|
|
|
|
.panel-header {
|
|
|
|
|
display: flex; align-items: center; justify-content: space-between;
|
|
|
|
|
font-size: 14px; font-weight: 600; color: #1a2c4e;
|
|
|
|
|
i { margin-right: 6px; color: #1171c4; }
|
|
|
|
|
}
|
|
|
|
|
.panel-more { font-size: 12px; color: #1171c4; text-decoration: none; }
|
|
|
|
|
.panel-more:hover { text-decoration: underline; }
|
|
|
|
|
.quick-actions { display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px; padding: 4px 0; }
|
|
|
|
|
.quick-btn { display: flex; flex-direction: column; align-items: center; gap: 8px; text-decoration: none; padding: 16px 8px; border-radius: 8px; transition: background 0.2s; }
|
|
|
|
|
.quick-btn:hover { background: #f5f7fa; }
|
|
|
|
|
.quick-btn span { font-size: 13px; color: #4a5568; }
|
|
|
|
|
.quick-icon { width: 44px; height: 44px; border-radius: 10px; display: flex; align-items: center; justify-content: center; font-size: 20px; }
|
|
|
|
|
</style>
|