|
@@ -0,0 +1,804 @@
|
|
|
|
|
+<template>
|
|
|
|
|
+ <div class="home">
|
|
|
|
|
+ <div class="page-header">
|
|
|
|
|
+ <div class="page-title">供应商能力综合评分面板</div>
|
|
|
|
|
+ <div class="page-subtitle">
|
|
|
|
|
+ 实时监控供应商表现,智能分析综合能力,辅助决策优化供应链
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- Stats Grid -->
|
|
|
|
|
+ <div class="stats-grid">
|
|
|
|
|
+ <div class="stat-card">
|
|
|
|
|
+ <div class="stat-header">
|
|
|
|
|
+ <div class="stat-label">供应商总数</div>
|
|
|
|
|
+ <div class="stat-badge badge-time">实时</div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="stat-value">156</div>
|
|
|
|
|
+ <div class="stat-trend trend-up">
|
|
|
|
|
+ <span>↑ 6.2%</span>
|
|
|
|
|
+ <span style="color: #999">较上月</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <div class="stat-card">
|
|
|
|
|
+ <div class="stat-header">
|
|
|
|
|
+ <div class="stat-label">平均综合得分</div>
|
|
|
|
|
+ <div class="stat-badge badge-time">实时</div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="stat-value">82.5</div>
|
|
|
|
|
+ <div class="stat-trend trend-up">
|
|
|
|
|
+ <span>↑ 3.8%</span>
|
|
|
|
|
+ <span style="color: #999">较上月</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <div class="stat-card">
|
|
|
|
|
+ <div class="stat-header">
|
|
|
|
|
+ <div class="stat-label">预警供应商</div>
|
|
|
|
|
+ <div class="stat-badge badge-warning">预警</div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="stat-value">8</div>
|
|
|
|
|
+ <div class="stat-trend trend-down">
|
|
|
|
|
+ <span>↓ 2</span>
|
|
|
|
|
+ <span style="color: #999">低于60分</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <div class="stat-card">
|
|
|
|
|
+ <div class="stat-header">
|
|
|
|
|
+ <div class="stat-label">优秀供应商</div>
|
|
|
|
|
+ <div class="stat-badge badge-time">实时</div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="stat-value">45</div>
|
|
|
|
|
+ <div class="stat-trend trend-up">
|
|
|
|
|
+ <span>↑ 5</span>
|
|
|
|
|
+ <span style="color: #999">≥85分</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- Search and Chart Section -->
|
|
|
|
|
+ <div class="content-card">
|
|
|
|
|
+ <div class="card-title">🔍 单个产品查询供应商评分</div>
|
|
|
|
|
+ <div class="search-box">
|
|
|
|
|
+ <div class="input-wrapper">
|
|
|
|
|
+ <input
|
|
|
|
|
+ v-model="inputProductCode"
|
|
|
|
|
+ type="text"
|
|
|
|
|
+ class="search-input"
|
|
|
|
|
+ placeholder="输入产品编码(如:20220606J0100MR4)"
|
|
|
|
|
+ />
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <button @click="triggerEvaluation" class="search-btn">预测分析</button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <div class="chart-container">
|
|
|
|
|
+ <div style="font-size: 14px; color: #666; margin-bottom: 15px">
|
|
|
|
|
+ 产品供应商三维度评分趋势对比
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="chart-placeholder">
|
|
|
|
|
+ <svg
|
|
|
|
|
+ class="chart-lines"
|
|
|
|
|
+ viewBox="0 0 800 300"
|
|
|
|
|
+ preserveAspectRatio="none"
|
|
|
|
|
+ >
|
|
|
|
|
+ <polyline
|
|
|
|
|
+ points="50,250 150,180 250,120 350,80 450,90 550,110 650,140 750,200"
|
|
|
|
|
+ fill="none"
|
|
|
|
|
+ stroke="rgba(255,255,255,0.6)"
|
|
|
|
|
+ stroke-width="3"
|
|
|
|
|
+ />
|
|
|
|
|
+ <polyline
|
|
|
|
|
+ points="50,270 150,220 250,160 350,100 450,95 550,120 650,160 750,220"
|
|
|
|
|
+ fill="none"
|
|
|
|
|
+ stroke="rgba(255,255,255,0.4)"
|
|
|
|
|
+ stroke-width="2"
|
|
|
|
|
+ stroke-dasharray="5,5"
|
|
|
|
|
+ />
|
|
|
|
|
+ <polyline
|
|
|
|
|
+ points="50,280 150,240 250,200 350,140 450,130 550,150 650,180 750,240"
|
|
|
|
|
+ fill="none"
|
|
|
|
|
+ stroke="rgba(255,255,255,0.4)"
|
|
|
|
|
+ stroke-width="2"
|
|
|
|
|
+ stroke-dasharray="5,5"
|
|
|
|
|
+ />
|
|
|
|
|
+ </svg>
|
|
|
|
|
+ <div style="position: relative; z-index: 1">
|
|
|
|
|
+ <div style="font-size: 14px; opacity: 0.8; margin-bottom: 5px">
|
|
|
|
|
+ 选择产品查看供应商评分趋势
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div style="font-size: 12px; opacity: 0.6">
|
|
|
|
|
+ 蓝线:成本得分 | 红线:交付得分 | 绿线:账期得分
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <div class="info-grid">
|
|
|
|
|
+ <div class="info-box">
|
|
|
|
|
+ <div class="info-label">评估结果</div>
|
|
|
|
|
+ <div class="info-value" style="color: #52c41a">已加载</div>
|
|
|
|
|
+ <div class="info-detail">
|
|
|
|
|
+ 供应商数量: 5家<br />
|
|
|
|
|
+ 评估完成时间: 2025-10-29 14:30<br />
|
|
|
|
|
+ <span style="color: #1890ff">最佳供应商: 华信电子</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <div class="info-box">
|
|
|
|
|
+ <div class="info-label">数据周期</div>
|
|
|
|
|
+ <div class="info-value">180天</div>
|
|
|
|
|
+ <div class="info-detail">
|
|
|
|
|
+ 评估订单总数: 238笔<br />
|
|
|
|
|
+ 时间范围: 2025-05-01 至 2025-10-28
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <div class="info-box">
|
|
|
|
|
+ <div class="info-label">维度评分范围</div>
|
|
|
|
|
+ <div class="info-value">80-95分</div>
|
|
|
|
|
+ <div class="info-detail">
|
|
|
|
|
+ 成本: 80-95分<br />
|
|
|
|
|
+ 交付: 85-94分<br />
|
|
|
|
|
+ 账期: 80-90分
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <div class="info-box">
|
|
|
|
|
+ <div class="info-label">综合评级分布</div>
|
|
|
|
|
+ <div class="info-value">优秀: 5家</div>
|
|
|
|
|
+ <div class="info-detail">
|
|
|
|
|
+ A级(≥85分): 3家<br />
|
|
|
|
|
+ B级(70-85分): 2家<br />
|
|
|
|
|
+ C级(<70分): 0家
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- Weights Configuration Summary -->
|
|
|
|
|
+ <div class="content-card">
|
|
|
|
|
+ <div class="card-title">📊 评估维度权重配置</div>
|
|
|
|
|
+ <div style="margin-bottom: 20px; color: #666; font-size: 14px">
|
|
|
|
|
+ 当前配置:成本 40% | 交付 40% | 账期 20%
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div
|
|
|
|
|
+ style="display: grid; grid-template-columns: repeat(3, 1fr); gap: 20px"
|
|
|
|
|
+ >
|
|
|
|
|
+ <div class="info-box">
|
|
|
|
|
+ <div class="info-label">💰 成本维度</div>
|
|
|
|
|
+ <div class="info-value" style="color: #5b6cff">40%</div>
|
|
|
|
|
+ <div class="info-detail">
|
|
|
|
|
+ 评分规则:价格排名<br />
|
|
|
|
|
+ 第1名: 100分<br />
|
|
|
|
|
+ 第2名: 90分<br />
|
|
|
|
|
+ 第3名: 80分
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="info-box">
|
|
|
|
|
+ <div class="info-label">🚚 交付维度</div>
|
|
|
|
|
+ <div class="info-value" style="color: #52c41a">40%</div>
|
|
|
|
|
+ <div class="info-detail">
|
|
|
|
|
+ D1-准时率: 50%<br />
|
|
|
|
|
+ D2-平均偏差: 30%<br />
|
|
|
|
|
+ D3-最长延迟: 10%<br />
|
|
|
|
|
+ D4-数量满足率: 10%
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="info-box">
|
|
|
|
|
+ <div class="info-label">💳 账期维度</div>
|
|
|
|
|
+ <div class="info-value" style="color: #ff9800">20%</div>
|
|
|
|
|
+ <div class="info-detail">
|
|
|
|
|
+ ≥90天: 100分<br />
|
|
|
|
|
+ ≥60天: 90分<br />
|
|
|
|
|
+ ≥45天: 80分<br />
|
|
|
|
|
+ ≥30天: 60分
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- Data Statistics -->
|
|
|
|
|
+ <div class="content-card">
|
|
|
|
|
+ <div class="card-title">📋 评估数据统计</div>
|
|
|
|
|
+ <table class="supplier-table">
|
|
|
|
|
+ <thead>
|
|
|
|
|
+ <tr>
|
|
|
|
|
+ <th>维度</th>
|
|
|
|
|
+ <th>数据来源</th>
|
|
|
|
|
+ <th>评估指标</th>
|
|
|
|
|
+ <th>数据记录数</th>
|
|
|
|
|
+ <th>数据完整度</th>
|
|
|
|
|
+ <th>状态</th>
|
|
|
|
|
+ </tr>
|
|
|
|
|
+ </thead>
|
|
|
|
|
+ <tbody>
|
|
|
|
|
+ <tr>
|
|
|
|
|
+ <td><strong>💰 成本维度</strong></td>
|
|
|
|
|
+ <td>采购订单统计.xlsx</td>
|
|
|
|
|
+ <td>供应商报价排名</td>
|
|
|
|
|
+ <td>2,845条</td>
|
|
|
|
|
+ <td>
|
|
|
|
|
+ <div class="progress-bar">
|
|
|
|
|
+ <div class="progress-fill" style="width: 98.5%"></div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <span style="font-size: 12px; color: #666">98.5%</span>
|
|
|
|
|
+ </td>
|
|
|
|
|
+ <td><span style="color: #52c41a">● 正常</span></td>
|
|
|
|
|
+ </tr>
|
|
|
|
|
+ <tr>
|
|
|
|
|
+ <td><strong>🚚 交付维度</strong></td>
|
|
|
|
|
+ <td>采购数据_双键合并结果.xlsx</td>
|
|
|
|
|
+ <td>准时率、偏差、延迟、满足率</td>
|
|
|
|
|
+ <td>5,126条</td>
|
|
|
|
|
+ <td>
|
|
|
|
|
+ <div class="progress-bar">
|
|
|
|
|
+ <div class="progress-fill" style="width: 99.2%"></div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <span style="font-size: 12px; color: #666">99.2%</span>
|
|
|
|
|
+ </td>
|
|
|
|
|
+ <td><span style="color: #52c41a">● 正常</span></td>
|
|
|
|
|
+ </tr>
|
|
|
|
|
+ <tr>
|
|
|
|
|
+ <td><strong>💳 账期维度</strong></td>
|
|
|
|
|
+ <td>供应商账期.xlsx</td>
|
|
|
|
|
+ <td>结算期限天数</td>
|
|
|
|
|
+ <td>156条</td>
|
|
|
|
|
+ <td>
|
|
|
|
|
+ <div class="progress-bar">
|
|
|
|
|
+ <div class="progress-fill" style="width: 100%"></div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <span style="font-size: 12px; color: #666">100%</span>
|
|
|
|
|
+ </td>
|
|
|
|
|
+ <td><span style="color: #52c41a">● 正常</span></td>
|
|
|
|
|
+ </tr>
|
|
|
|
|
+ </tbody>
|
|
|
|
|
+ </table>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- Top 5 Suppliers -->
|
|
|
|
|
+ <div class="content-card">
|
|
|
|
|
+ <div class="card-title">
|
|
|
|
|
+ 🏆 Top 5 供应商综合排名
|
|
|
|
|
+ <span
|
|
|
|
|
+ style="
|
|
|
|
|
+ font-size: 12px;
|
|
|
|
|
+ color: #999;
|
|
|
|
|
+ font-weight: normal;
|
|
|
|
|
+ margin-left: 10px;
|
|
|
|
|
+ "
|
|
|
|
|
+ >
|
|
|
|
|
+ 基于产品代码: {{ displayedProductCode || "20220606J0100MR4" }}
|
|
|
|
|
+ </span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <div v-if="loading" class="loading">加载中...</div>
|
|
|
|
|
+ <div v-else-if="error" class="error">{{ error }}</div>
|
|
|
|
|
+ <div v-else>
|
|
|
|
|
+ <table class="supplier-table">
|
|
|
|
|
+ <thead>
|
|
|
|
|
+ <tr>
|
|
|
|
|
+ <th>综合排名</th>
|
|
|
|
|
+ <th>供应商名称</th>
|
|
|
|
|
+ <th>供应商代码</th>
|
|
|
|
|
+ <th>综合得分</th>
|
|
|
|
|
+ <th>维度得分</th>
|
|
|
|
|
+ <th>参考价</th>
|
|
|
|
|
+ <th>成本排名</th>
|
|
|
|
|
+ </tr>
|
|
|
|
|
+ </thead>
|
|
|
|
|
+ <tbody>
|
|
|
|
|
+ <tr
|
|
|
|
|
+ v-for="(supplier, index) in evaluationData"
|
|
|
|
|
+ :key="supplier.供应商代码"
|
|
|
|
|
+ >
|
|
|
|
|
+ <td>
|
|
|
|
|
+ <span :class="['rank-badge', getRankClass(index + 1)]">{{
|
|
|
|
|
+ index + 1
|
|
|
|
|
+ }}</span>
|
|
|
|
|
+ </td>
|
|
|
|
|
+ <td>
|
|
|
|
|
+ <strong>{{ supplier.供应商名称 }}</strong>
|
|
|
|
|
+ </td>
|
|
|
|
|
+ <td>{{ supplier.供应商代码 || "N/A" }}</td>
|
|
|
|
|
+ <td>
|
|
|
|
|
+ <strong style="font-size: 18px; color: #52c41a">{{
|
|
|
|
|
+ parseFloat(supplier.综合得分).toFixed(2)
|
|
|
|
|
+ }}</strong>
|
|
|
|
|
+ <div class="progress-bar">
|
|
|
|
|
+ <div
|
|
|
|
|
+ class="progress-fill"
|
|
|
|
|
+ :style="{ width: `${supplier.综合得分}%` }"
|
|
|
|
|
+ ></div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </td>
|
|
|
|
|
+ <td>
|
|
|
|
|
+ <div class="dimension-scores">
|
|
|
|
|
+ <span class="dim-score"
|
|
|
|
|
+ >成本:{{ parseFloat(supplier.成本分数).toFixed(0) }}</span
|
|
|
|
|
+ >
|
|
|
|
|
+ <span class="dim-score"
|
|
|
|
|
+ >交付:{{ parseFloat(supplier.交付分数).toFixed(0) }}</span
|
|
|
|
|
+ >
|
|
|
|
|
+ <span class="dim-score"
|
|
|
|
|
+ >账期:{{ parseFloat(supplier.账期分数).toFixed(0) }}</span
|
|
|
|
|
+ >
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </td>
|
|
|
|
|
+ <td>¥{{ parseFloat(supplier.参考价).toFixed(2) }}</td>
|
|
|
|
|
+ <td>第{{ supplier.成本排名 }}名</td>
|
|
|
|
|
+ </tr>
|
|
|
|
|
+ </tbody>
|
|
|
|
|
+ </table>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+</template>
|
|
|
|
|
+
|
|
|
|
|
+<script>
|
|
|
|
|
+import { getToken } from "@/utils/auth";
|
|
|
|
|
+import request from "@/utils/request";
|
|
|
|
|
+
|
|
|
|
|
+export default {
|
|
|
|
|
+ name: "SupplyOverallEvaluation",
|
|
|
|
|
+ data() {
|
|
|
|
|
+ return {
|
|
|
|
|
+ inputProductCode: "",
|
|
|
|
|
+ displayedProductCode: "",
|
|
|
|
|
+ evaluationData: [],
|
|
|
|
|
+ loading: false,
|
|
|
|
|
+ error: "",
|
|
|
|
|
+ };
|
|
|
|
|
+ },
|
|
|
|
|
+ created() {
|
|
|
|
|
+ // 如果有传入的 productCode,则自动触发评估
|
|
|
|
|
+ if (this.$route.query.productCode) {
|
|
|
|
|
+ this.inputProductCode = this.$route.query.productCode;
|
|
|
|
|
+ this.fetchEvaluationData(this.inputProductCode);
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ methods: {
|
|
|
|
|
+ // 监听路由传入的 productCode
|
|
|
|
|
+ triggerEvaluation() {
|
|
|
|
|
+ if (this.inputProductCode) {
|
|
|
|
|
+ this.fetchEvaluationData(this.inputProductCode);
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+
|
|
|
|
|
+ async fetchEvaluationData(code) {
|
|
|
|
|
+ this.loading = true;
|
|
|
|
|
+ this.error = "";
|
|
|
|
|
+ this.displayedProductCode = code;
|
|
|
|
|
+ try {
|
|
|
|
|
+ const response = await request({
|
|
|
|
|
+ url: "http://localhost:5000/api/evaluate",
|
|
|
|
|
+ method: "post",
|
|
|
|
|
+ data: {
|
|
|
|
|
+ product_code: code,
|
|
|
|
|
+ },
|
|
|
|
|
+ });
|
|
|
|
|
+ // Flask API可能直接返回数据,而不是包装在特定字段中
|
|
|
|
|
+ if (Array.isArray(response)) {
|
|
|
|
|
+ this.evaluationData = response;
|
|
|
|
|
+ } else if (response && response.data) {
|
|
|
|
|
+ this.evaluationData = response.data;
|
|
|
|
|
+ } else {
|
|
|
|
|
+ this.evaluationData = response || [];
|
|
|
|
|
+ }
|
|
|
|
|
+ this.$modal.msgSuccess("评估完成");
|
|
|
|
|
+ } catch (err) {
|
|
|
|
|
+ console.error("获取评估数据失败:", err);
|
|
|
|
|
+ this.error = err.message || "获取评估数据失败";
|
|
|
|
|
+ this.$modal.msgError(this.error);
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ this.loading = false;
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+
|
|
|
|
|
+ // 获取排名样式类
|
|
|
|
|
+ getRankClass(rank) {
|
|
|
|
|
+ if (rank === 1) return "rank-1";
|
|
|
|
|
+ if (rank === 2) return "rank-2";
|
|
|
|
|
+ if (rank === 3) return "rank-3";
|
|
|
|
|
+ return "rank-other";
|
|
|
|
|
+ },
|
|
|
|
|
+ },
|
|
|
|
|
+};
|
|
|
|
|
+</script>
|
|
|
|
|
+
|
|
|
|
|
+<style scoped>
|
|
|
|
|
+.page-header {
|
|
|
|
|
+ margin-bottom: 40px;
|
|
|
|
|
+ padding-bottom: 20px;
|
|
|
|
|
+ border-bottom: 2px solid rgba(102, 126, 234, 0.1);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.page-title {
|
|
|
|
|
+ font-size: 32px;
|
|
|
|
|
+ font-weight: 700;
|
|
|
|
|
+ margin-bottom: 12px;
|
|
|
|
|
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
|
|
|
+ -webkit-background-clip: text;
|
|
|
|
|
+ -webkit-text-fill-color: transparent;
|
|
|
|
|
+ background-clip: text;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.page-subtitle {
|
|
|
|
|
+ color: #666;
|
|
|
|
|
+ font-size: 15px;
|
|
|
|
|
+ font-weight: 400;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.stats-grid {
|
|
|
|
|
+ display: grid;
|
|
|
|
|
+ grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
|
|
|
|
|
+ gap: 24px;
|
|
|
|
|
+ margin-bottom: 40px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.stat-card {
|
|
|
|
|
+ background: white;
|
|
|
|
|
+ padding: 28px;
|
|
|
|
|
+ border-radius: 16px;
|
|
|
|
|
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
|
|
|
|
|
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
|
|
|
+ border: 1px solid rgba(102, 126, 234, 0.1);
|
|
|
|
|
+ position: relative;
|
|
|
|
|
+ overflow: hidden;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.stat-card::before {
|
|
|
|
|
+ content: "";
|
|
|
|
|
+ position: absolute;
|
|
|
|
|
+ top: 0;
|
|
|
|
|
+ left: 0;
|
|
|
|
|
+ right: 0;
|
|
|
|
|
+ height: 4px;
|
|
|
|
|
+ background: linear-gradient(90deg, #667eea 0%, #764ba2 100%);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.stat-card:hover {
|
|
|
|
|
+ transform: translateY(-4px);
|
|
|
|
|
+ box-shadow: 0 8px 30px rgba(102, 126, 234, 0.15);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.stat-header {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ justify-content: space-between;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ margin-bottom: 20px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.stat-label {
|
|
|
|
|
+ color: #666;
|
|
|
|
|
+ font-size: 14px;
|
|
|
|
|
+ font-weight: 500;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.stat-badge {
|
|
|
|
|
+ padding: 4px 10px;
|
|
|
|
|
+ border-radius: 12px;
|
|
|
|
|
+ font-size: 11px;
|
|
|
|
|
+ font-weight: 600;
|
|
|
|
|
+ text-transform: uppercase;
|
|
|
|
|
+ letter-spacing: 0.5px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.badge-time {
|
|
|
|
|
+ background: linear-gradient(135deg, #e6f0ff 0%, #d6e5ff 100%);
|
|
|
|
|
+ color: #667eea;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.badge-warning {
|
|
|
|
|
+ background: linear-gradient(135deg, #fff7e6 0%, #ffe6cc 100%);
|
|
|
|
|
+ color: #ff9800;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.stat-value {
|
|
|
|
|
+ font-size: 36px;
|
|
|
|
|
+ font-weight: 700;
|
|
|
|
|
+ margin-bottom: 12px;
|
|
|
|
|
+ color: #2c3e50;
|
|
|
|
|
+ line-height: 1.2;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.stat-trend {
|
|
|
|
|
+ font-size: 13px;
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ gap: 6px;
|
|
|
|
|
+ font-weight: 500;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.trend-up {
|
|
|
|
|
+ color: #52c41a;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.trend-down {
|
|
|
|
|
+ color: #ff4d4f;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.content-card {
|
|
|
|
|
+ background: white;
|
|
|
|
|
+ padding: 32px;
|
|
|
|
|
+ border-radius: 16px;
|
|
|
|
|
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
|
|
|
|
|
+ margin-bottom: 24px;
|
|
|
|
|
+ border: 1px solid rgba(102, 126, 234, 0.1);
|
|
|
|
|
+ transition: all 0.3s;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.content-card:hover {
|
|
|
|
|
+ box-shadow: 0 8px 30px rgba(0, 0, 0, 0.12);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.card-title {
|
|
|
|
|
+ font-size: 18px;
|
|
|
|
|
+ font-weight: 700;
|
|
|
|
|
+ margin-bottom: 24px;
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ gap: 10px;
|
|
|
|
|
+ color: #2c3e50;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.search-box {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ gap: 16px;
|
|
|
|
|
+ margin-bottom: 32px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.input-wrapper {
|
|
|
|
|
+ flex: 1;
|
|
|
|
|
+ position: relative;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.search-input {
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+ padding: 16px 20px;
|
|
|
|
|
+ border: 2px solid #e8ecf1;
|
|
|
|
|
+ border-radius: 12px;
|
|
|
|
|
+ font-size: 15px;
|
|
|
|
|
+ outline: none;
|
|
|
|
|
+ transition: all 0.3s;
|
|
|
|
|
+ background: #fafbfc;
|
|
|
|
|
+ font-weight: 500;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.search-input:focus {
|
|
|
|
|
+ border-color: #667eea;
|
|
|
|
|
+ background: white;
|
|
|
|
|
+ box-shadow: 0 0 0 4px rgba(102, 126, 234, 0.1);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.search-btn {
|
|
|
|
|
+ padding: 16px 40px;
|
|
|
|
|
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
|
|
|
+ color: white;
|
|
|
|
|
+ border: none;
|
|
|
|
|
+ border-radius: 12px;
|
|
|
|
|
+ font-size: 15px;
|
|
|
|
|
+ font-weight: 600;
|
|
|
|
|
+ cursor: pointer;
|
|
|
|
|
+ transition: all 0.3s;
|
|
|
|
|
+ box-shadow: 0 4px 15px rgba(102, 126, 234, 0.4);
|
|
|
|
|
+ white-space: nowrap;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.search-btn:hover {
|
|
|
|
|
+ transform: translateY(-2px);
|
|
|
|
|
+ box-shadow: 0 6px 20px rgba(102, 126, 234, 0.5);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.search-btn:active {
|
|
|
|
|
+ transform: translateY(0);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.chart-container {
|
|
|
|
|
+ margin-bottom: 32px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.chart-placeholder {
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+ height: 320px;
|
|
|
|
|
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
|
|
|
+ border-radius: 16px;
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ justify-content: center;
|
|
|
|
|
+ color: white;
|
|
|
|
|
+ font-size: 16px;
|
|
|
|
|
+ position: relative;
|
|
|
|
|
+ overflow: hidden;
|
|
|
|
|
+ box-shadow: 0 8px 30px rgba(102, 126, 234, 0.3);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.chart-lines {
|
|
|
|
|
+ position: absolute;
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+ height: 100%;
|
|
|
|
|
+ opacity: 0.2;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.info-grid {
|
|
|
|
|
+ display: grid;
|
|
|
|
|
+ grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
|
|
|
|
+ gap: 20px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.info-box {
|
|
|
|
|
+ background: linear-gradient(135deg, #f8f9fa 0%, #f0f2f5 100%);
|
|
|
|
|
+ padding: 24px;
|
|
|
|
|
+ border-radius: 12px;
|
|
|
|
|
+ border: 1px solid #e8ecf1;
|
|
|
|
|
+ transition: all 0.3s;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.info-box:hover {
|
|
|
|
|
+ transform: translateY(-2px);
|
|
|
|
|
+ box-shadow: 0 4px 15px rgba(0, 0, 0, 0.08);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.info-label {
|
|
|
|
|
+ font-size: 12px;
|
|
|
|
|
+ color: #999;
|
|
|
|
|
+ margin-bottom: 10px;
|
|
|
|
|
+ font-weight: 600;
|
|
|
|
|
+ text-transform: uppercase;
|
|
|
|
|
+ letter-spacing: 0.5px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.info-value {
|
|
|
|
|
+ font-size: 28px;
|
|
|
|
|
+ font-weight: 700;
|
|
|
|
|
+ margin-bottom: 12px;
|
|
|
|
|
+ color: #2c3e50;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.info-detail {
|
|
|
|
|
+ font-size: 13px;
|
|
|
|
|
+ color: #666;
|
|
|
|
|
+ line-height: 1.8;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.supplier-table {
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+ border-collapse: separate;
|
|
|
|
|
+ border-spacing: 0;
|
|
|
|
|
+ margin-top: 24px;
|
|
|
|
|
+ border-radius: 12px;
|
|
|
|
|
+ overflow: hidden;
|
|
|
|
|
+ box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.supplier-table th {
|
|
|
|
|
+ background: linear-gradient(135deg, #f8f9fa 0%, #f0f2f5 100%);
|
|
|
|
|
+ padding: 16px;
|
|
|
|
|
+ text-align: left;
|
|
|
|
|
+ font-size: 13px;
|
|
|
|
|
+ color: #666;
|
|
|
|
|
+ font-weight: 700;
|
|
|
|
|
+ text-transform: uppercase;
|
|
|
|
|
+ letter-spacing: 0.5px;
|
|
|
|
|
+ border-bottom: 2px solid #e8ecf1;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.supplier-table td {
|
|
|
|
|
+ padding: 18px 16px;
|
|
|
|
|
+ border-bottom: 1px solid #f0f0f0;
|
|
|
|
|
+ font-size: 14px;
|
|
|
|
|
+ background: white;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.supplier-table tr:last-child td {
|
|
|
|
|
+ border-bottom: none;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.supplier-table tr:hover td {
|
|
|
|
|
+ background: #f8f9ff;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.rank-badge {
|
|
|
|
|
+ display: inline-flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ justify-content: center;
|
|
|
|
|
+ width: 32px;
|
|
|
|
|
+ height: 32px;
|
|
|
|
|
+ border-radius: 50%;
|
|
|
|
|
+ font-weight: 700;
|
|
|
|
|
+ font-size: 14px;
|
|
|
|
|
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.rank-1 {
|
|
|
|
|
+ background: linear-gradient(135deg, #ffd700 0%, #ffed4e 100%);
|
|
|
|
|
+ color: #8b6914;
|
|
|
|
|
+}
|
|
|
|
|
+.rank-2 {
|
|
|
|
|
+ background: linear-gradient(135deg, #c0c0c0 0%, #e8e8e8 100%);
|
|
|
|
|
+ color: #666;
|
|
|
|
|
+}
|
|
|
|
|
+.rank-3 {
|
|
|
|
|
+ background: linear-gradient(135deg, #cd7f32 0%, #e6a052 100%);
|
|
|
|
|
+ color: white;
|
|
|
|
|
+}
|
|
|
|
|
+.rank-other {
|
|
|
|
|
+ background: #f0f0f0;
|
|
|
|
|
+ color: #666;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.progress-bar {
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+ height: 8px;
|
|
|
|
|
+ background: #f0f0f0;
|
|
|
|
|
+ border-radius: 10px;
|
|
|
|
|
+ overflow: hidden;
|
|
|
|
|
+ margin-top: 8px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.progress-fill {
|
|
|
|
|
+ height: 100%;
|
|
|
|
|
+ background: linear-gradient(90deg, #52c41a 0%, #95de64 100%);
|
|
|
|
|
+ border-radius: 10px;
|
|
|
|
|
+ transition: width 0.5s cubic-bezier(0.4, 0, 0.2, 1);
|
|
|
|
|
+ box-shadow: 0 2px 8px rgba(82, 196, 26, 0.3);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.dimension-scores {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ gap: 8px;
|
|
|
|
|
+ font-size: 12px;
|
|
|
|
|
+ flex-wrap: wrap;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.dim-score {
|
|
|
|
|
+ padding: 6px 12px;
|
|
|
|
|
+ background: linear-gradient(135deg, #f0f2ff 0%, #e8ecff 100%);
|
|
|
|
|
+ border-radius: 8px;
|
|
|
|
|
+ font-weight: 600;
|
|
|
|
|
+ color: #667eea;
|
|
|
|
|
+ border: 1px solid rgba(102, 126, 234, 0.2);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.loading,
|
|
|
|
|
+.error {
|
|
|
|
|
+ font-size: 16px;
|
|
|
|
|
+ margin: 3rem 0;
|
|
|
|
|
+ text-align: center;
|
|
|
|
|
+ padding: 40px;
|
|
|
|
|
+ background: white;
|
|
|
|
|
+ border-radius: 12px;
|
|
|
|
|
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.loading {
|
|
|
|
|
+ color: #667eea;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.error {
|
|
|
|
|
+ color: #ff4d4f;
|
|
|
|
|
+ background: #fff5f5;
|
|
|
|
|
+ border: 1px solid #ffccc7;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+@media (max-width: 1200px) {
|
|
|
|
|
+ .stats-grid {
|
|
|
|
|
+ grid-template-columns: repeat(2, 1fr);
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+@media (max-width: 768px) {
|
|
|
|
|
+ .stats-grid {
|
|
|
|
|
+ grid-template-columns: 1fr;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .search-box {
|
|
|
|
|
+ flex-direction: column;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .search-btn {
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+</style>
|