|
|
@@ -1,441 +1,882 @@
|
|
|
<template>
|
|
|
+
|
|
|
<div class="order-value-view">
|
|
|
+
|
|
|
<!-- 头部区域 -->
|
|
|
+
|
|
|
<header class="page-header">
|
|
|
+
|
|
|
<h1 class="page-title">订单价值</h1>
|
|
|
+
|
|
|
<div class="header-controls">
|
|
|
+
|
|
|
<span class="control-label">时光回溯控制器</span>
|
|
|
+
|
|
|
<div class="control-item-group">
|
|
|
+
|
|
|
<button :class="{'time-tab': true, 'active': activeTab === '7d'}" @click="selectDateRange('7d')">最近7天</button>
|
|
|
+
|
|
|
<button :class="{'time-tab': true, 'active': activeTab === 'tm'}" @click="selectDateRange('tm')">本月</button>
|
|
|
+
|
|
|
<button :class="{'time-tab': true, 'active': activeTab === 'lm'}" @click="selectDateRange('lm')">上月</button>
|
|
|
+
|
|
|
<button :class="{'time-tab': true, 'active': activeTab === 'all'}" @click="selectDateRange('all')">全量数据</button>
|
|
|
+
|
|
|
</div>
|
|
|
+
|
|
|
</div>
|
|
|
+
|
|
|
</header>
|
|
|
|
|
|
+
|
|
|
+
|
|
|
<!-- ✨【修改点1:KPI卡片的值现在是动态的】✨ -->
|
|
|
+
|
|
|
<section class="kpi-cards-grid">
|
|
|
+
|
|
|
<KpiCard title="总交易额 (GMV)" :value="kpiData.gmv" :trend="kpiData.gmvTrend" :trend-color="getTrendColor(kpiData.gmvTrend)" icon="📈" />
|
|
|
+
|
|
|
<KpiCard title="P80 订单贡献比" :value="kpiData.p80Contribution" :trend="kpiData.p80Trend" :trend-color="getTrendColor(kpiData.p80Trend)" icon="🏆" />
|
|
|
+
|
|
|
<KpiCard title="Top 5 商品贡献比" :value="kpiData.top5Contribution" :trend="kpiData.top5Trend" :trend-color="getTrendColor(kpiData.top5Trend)" icon="🔥" />
|
|
|
+
|
|
|
<KpiCard title="平均支付响应" :value="kpiData.averagePaymentTime" :trend="kpiData.avgTimeTrend" :trend-color="getTrendColor(kpiData.avgTimeTrend)" icon="⏱" />
|
|
|
+
|
|
|
</section>
|
|
|
+
|
|
|
|
|
|
+
|
|
|
<!-- 图表区域 -->
|
|
|
+
|
|
|
<section class="charts-area">
|
|
|
+
|
|
|
<!-- 支付决策漏斗图 -->
|
|
|
+
|
|
|
<div class="chart-wrapper funnel-chart-wrapper">
|
|
|
+
|
|
|
<FunnelChart :date-range="currentDateRange" />
|
|
|
+
|
|
|
</div>
|
|
|
+
|
|
|
|
|
|
+
|
|
|
<!-- 明星商品价值环图 (Top 5) -->
|
|
|
+
|
|
|
<div class="chart-wrapper">
|
|
|
+
|
|
|
<Top5PieChart :date-range="currentDateRange" />
|
|
|
+
|
|
|
</div>
|
|
|
+
|
|
|
</section>
|
|
|
+
|
|
|
|
|
|
+
|
|
|
<!-- 订单价值漏损分析 (退款) - 铺满整排 -->
|
|
|
+
|
|
|
<section class="leakage-section">
|
|
|
+
|
|
|
<div class="chart-wrapper leakage-card-wrapper full-width">
|
|
|
+
|
|
|
<LeakageCard :date-range="currentDateRange" />
|
|
|
+
|
|
|
</div>
|
|
|
+
|
|
|
</section>
|
|
|
+
|
|
|
</div>
|
|
|
+
|
|
|
</template>
|
|
|
|
|
|
+
|
|
|
+
|
|
|
<script>
|
|
|
+
|
|
|
import axios from 'axios';
|
|
|
+
|
|
|
import KpiCard from './KpiCard/index.vue';
|
|
|
+
|
|
|
import FunnelChart from './FunnelChart/index.vue';
|
|
|
+
|
|
|
import Top5PieChart from './Top5PieChart/index.vue';
|
|
|
+
|
|
|
import LeakageCard from './LeakageCard/index.vue';
|
|
|
|
|
|
+
|
|
|
+
|
|
|
export default {
|
|
|
+
|
|
|
name: 'OrderValue',
|
|
|
+
|
|
|
components: {
|
|
|
+
|
|
|
KpiCard,
|
|
|
+
|
|
|
FunnelChart,
|
|
|
+
|
|
|
Top5PieChart,
|
|
|
+
|
|
|
LeakageCard
|
|
|
+
|
|
|
},
|
|
|
+
|
|
|
data() {
|
|
|
+
|
|
|
return {
|
|
|
+
|
|
|
selectedDate: new Date().toISOString().split('T')[0],
|
|
|
+
|
|
|
activeTab: '7d',
|
|
|
+
|
|
|
maxDate: '',
|
|
|
+
|
|
|
currentDateRange: { start: '', end: '' },
|
|
|
+
|
|
|
kpiData: {
|
|
|
+
|
|
|
gmv: '?0',
|
|
|
+
|
|
|
gmvTrend: '+0%',
|
|
|
+
|
|
|
p80Contribution: '0%',
|
|
|
+
|
|
|
p80Trend: '+0%',
|
|
|
+
|
|
|
top5Contribution: '0%',
|
|
|
+
|
|
|
top5Trend: '+0%',
|
|
|
- averagePaymentTime: '00:00',
|
|
|
+
|
|
|
+ averagePaymentTime: '00:00 秒',
|
|
|
+
|
|
|
avgTimeTrend: '+0%'
|
|
|
+
|
|
|
}
|
|
|
+
|
|
|
};
|
|
|
+
|
|
|
},
|
|
|
+
|
|
|
watch: {
|
|
|
+
|
|
|
selectedDate(newVal) {
|
|
|
+
|
|
|
if (this.activeTab === '7d') {
|
|
|
+
|
|
|
const startDate = this.formatYmd(this.addDays(newVal, -6));
|
|
|
+
|
|
|
const endDate = this.formatYmd(newVal);
|
|
|
+
|
|
|
this.currentDateRange = { start: startDate, end: endDate };
|
|
|
+
|
|
|
this.fetchAllApiData({ start: startDate, end: endDate });
|
|
|
+
|
|
|
} else {
|
|
|
+
|
|
|
this.selectDateRange(this.activeTab);
|
|
|
+
|
|
|
}
|
|
|
+
|
|
|
}
|
|
|
+
|
|
|
},
|
|
|
+
|
|
|
mounted() {
|
|
|
+
|
|
|
this.initDashboard();
|
|
|
+
|
|
|
},
|
|
|
+
|
|
|
methods: {
|
|
|
+
|
|
|
pad2(value) {
|
|
|
+
|
|
|
return String(value).padStart(2, '0');
|
|
|
+
|
|
|
},
|
|
|
+
|
|
|
toDate(value) {
|
|
|
+
|
|
|
if (value instanceof Date) return new Date(value.getTime());
|
|
|
+
|
|
|
if (!value) return new Date();
|
|
|
+
|
|
|
return new Date(`${value}T00:00:00`);
|
|
|
+
|
|
|
},
|
|
|
+
|
|
|
formatYmd(value) {
|
|
|
+
|
|
|
const date = this.toDate(value);
|
|
|
+
|
|
|
return `${date.getFullYear()}-${this.pad2(date.getMonth() + 1)}-${this.pad2(date.getDate())}`;
|
|
|
+
|
|
|
},
|
|
|
+
|
|
|
addDays(value, days) {
|
|
|
+
|
|
|
const date = this.toDate(value);
|
|
|
+
|
|
|
date.setDate(date.getDate() + days);
|
|
|
+
|
|
|
return date;
|
|
|
+
|
|
|
},
|
|
|
+
|
|
|
addMonths(value, months) {
|
|
|
+
|
|
|
const date = this.toDate(value);
|
|
|
+
|
|
|
date.setMonth(date.getMonth() + months);
|
|
|
+
|
|
|
return date;
|
|
|
+
|
|
|
},
|
|
|
+
|
|
|
startOfMonth(value) {
|
|
|
+
|
|
|
const date = this.toDate(value);
|
|
|
+
|
|
|
return new Date(date.getFullYear(), date.getMonth(), 1);
|
|
|
+
|
|
|
},
|
|
|
+
|
|
|
endOfMonth(value) {
|
|
|
+
|
|
|
const date = this.toDate(value);
|
|
|
+
|
|
|
return new Date(date.getFullYear(), date.getMonth() + 1, 0);
|
|
|
+
|
|
|
},
|
|
|
+
|
|
|
async fetchAllApiData(customRange = null) {
|
|
|
+
|
|
|
try {
|
|
|
+
|
|
|
let startDate;
|
|
|
+
|
|
|
let endDate;
|
|
|
+
|
|
|
if (customRange && customRange.start && customRange.end) {
|
|
|
+
|
|
|
startDate = customRange.start;
|
|
|
+
|
|
|
endDate = customRange.end;
|
|
|
+
|
|
|
} else {
|
|
|
+
|
|
|
endDate = this.selectedDate;
|
|
|
+
|
|
|
startDate = this.formatYmd(this.addDays(endDate, -6));
|
|
|
+
|
|
|
}
|
|
|
|
|
|
+
|
|
|
+
|
|
|
const [gmvRes, p80Res, top5Res, avgTimeRes] = await Promise.all([
|
|
|
+
|
|
|
axios.get(`/api/analysis/gmv?startDate=${startDate}&endDate=${endDate}`),
|
|
|
+
|
|
|
axios.get(`/api/analysis/r-big?startDate=${startDate}&endDate=${endDate}`),
|
|
|
+
|
|
|
axios.get(`/api/analysis/top5-percentage?startDate=${startDate}&endDate=${endDate}`),
|
|
|
+
|
|
|
axios.get(`/api/analysis/average-payment-time?startDate=${startDate}&endDate=${endDate}`)
|
|
|
+
|
|
|
]);
|
|
|
|
|
|
+
|
|
|
+
|
|
|
const gmvValue = gmvRes.data || 0;
|
|
|
+
|
|
|
this.kpiData.gmv = new Intl.NumberFormat('zh-CN', { style: 'currency', currency: 'CNY' }).format(gmvValue);
|
|
|
|
|
|
+
|
|
|
+
|
|
|
const p80Value = p80Res.data?.rBigRatio || 0;
|
|
|
+
|
|
|
this.kpiData.p80Contribution = `${Math.round(p80Value)}%`;
|
|
|
|
|
|
+
|
|
|
+
|
|
|
const top5Value = top5Res.data?.data?.top5Percentage || 0;
|
|
|
+
|
|
|
this.kpiData.top5Contribution = `${Math.round(top5Value)}%`;
|
|
|
|
|
|
+
|
|
|
+
|
|
|
const avgSeconds = avgTimeRes.data?.averagePaymentSeconds || 0;
|
|
|
+
|
|
|
const minutes = Math.floor(avgSeconds / 60);
|
|
|
+
|
|
|
const seconds = Math.round(avgSeconds % 60);
|
|
|
- this.kpiData.averagePaymentTime = `${this.pad2(minutes)}:${this.pad2(seconds)} ??`;
|
|
|
+
|
|
|
+ this.kpiData.averagePaymentTime = `${this.pad2(minutes)}:${this.pad2(seconds)} 秒`;
|
|
|
+
|
|
|
+
|
|
|
|
|
|
if (this.activeTab !== 'tm') {
|
|
|
+
|
|
|
this.kpiData.gmvTrend = '+0%';
|
|
|
+
|
|
|
this.kpiData.p80Trend = '+0%';
|
|
|
+
|
|
|
this.kpiData.top5Trend = '+0%';
|
|
|
+
|
|
|
this.kpiData.avgTimeTrend = '+0%';
|
|
|
+
|
|
|
}
|
|
|
+
|
|
|
} catch (error) {
|
|
|
- console.error('??????:', error);
|
|
|
+
|
|
|
+ console.error('获取订单价值数据失败:', error);
|
|
|
+
|
|
|
}
|
|
|
+
|
|
|
},
|
|
|
+
|
|
|
calculateTrend(currentValue, previousValue) {
|
|
|
+
|
|
|
if (previousValue === 0) return '+0%';
|
|
|
+
|
|
|
const change = ((currentValue - previousValue) / previousValue) * 100;
|
|
|
+
|
|
|
const sign = change >= 0 ? '+' : '';
|
|
|
+
|
|
|
return `${sign}${change.toFixed(1)}%`;
|
|
|
+
|
|
|
},
|
|
|
+
|
|
|
parseTimeString(timeString) {
|
|
|
+
|
|
|
if (!timeString) return 0;
|
|
|
+
|
|
|
const timeMatch = timeString.match(/(\d{2}):(\d{2})/);
|
|
|
+
|
|
|
if (timeMatch) {
|
|
|
+
|
|
|
const minutes = parseInt(timeMatch[1], 10);
|
|
|
+
|
|
|
const seconds = parseInt(timeMatch[2], 10);
|
|
|
+
|
|
|
return minutes * 60 + seconds;
|
|
|
+
|
|
|
}
|
|
|
+
|
|
|
return 0;
|
|
|
+
|
|
|
},
|
|
|
+
|
|
|
getTrendColor(trend) {
|
|
|
+
|
|
|
if (!trend) return 'green';
|
|
|
+
|
|
|
const value = parseFloat(trend);
|
|
|
+
|
|
|
return value >= 0 ? 'green' : 'red';
|
|
|
+
|
|
|
},
|
|
|
+
|
|
|
async fetchPreviousMonthData(currentStart) {
|
|
|
+
|
|
|
try {
|
|
|
+
|
|
|
const currentStartDate = this.toDate(currentStart);
|
|
|
+
|
|
|
const previousMonthBase = this.addMonths(currentStartDate, -1);
|
|
|
+
|
|
|
const previousMonthStart = this.formatYmd(this.startOfMonth(previousMonthBase));
|
|
|
+
|
|
|
const previousMonthEnd = this.formatYmd(this.endOfMonth(previousMonthBase));
|
|
|
|
|
|
+
|
|
|
+
|
|
|
const [gmvRes, p80Res, top5Res, avgTimeRes] = await Promise.all([
|
|
|
+
|
|
|
axios.get(`/api/analysis/gmv?startDate=${previousMonthStart}&endDate=${previousMonthEnd}`),
|
|
|
+
|
|
|
axios.get(`/api/analysis/r-big?startDate=${previousMonthStart}&endDate=${previousMonthEnd}`),
|
|
|
+
|
|
|
axios.get(`/api/analysis/top5-percentage?startDate=${previousMonthStart}&endDate=${previousMonthEnd}`),
|
|
|
+
|
|
|
axios.get(`/api/analysis/average-payment-time?startDate=${previousMonthStart}&endDate=${previousMonthEnd}`)
|
|
|
+
|
|
|
]);
|
|
|
|
|
|
+
|
|
|
+
|
|
|
return {
|
|
|
+
|
|
|
gmv: gmvRes.data || 0,
|
|
|
+
|
|
|
p80: p80Res.data?.rBigRatio || 0,
|
|
|
+
|
|
|
top5: top5Res.data?.data?.top5Percentage || 0,
|
|
|
+
|
|
|
avgTime: avgTimeRes.data?.averagePaymentSeconds || 0
|
|
|
+
|
|
|
};
|
|
|
+
|
|
|
} catch (error) {
|
|
|
- console.error('????????:', error);
|
|
|
+
|
|
|
+ console.error('获取订单价值数据失败:', error);
|
|
|
+
|
|
|
return null;
|
|
|
+
|
|
|
}
|
|
|
+
|
|
|
},
|
|
|
+
|
|
|
async initDashboard() {
|
|
|
+
|
|
|
try {
|
|
|
+
|
|
|
const res = await axios.get('/api/analysis/max-date');
|
|
|
+
|
|
|
this.maxDate = res.data;
|
|
|
+
|
|
|
this.selectedDate = res.data;
|
|
|
|
|
|
+
|
|
|
+
|
|
|
const startDate = this.formatYmd(this.addDays(res.data, -6));
|
|
|
+
|
|
|
this.currentDateRange = { start: startDate, end: res.data };
|
|
|
+
|
|
|
await this.fetchAllApiData();
|
|
|
+
|
|
|
this.kpiData.gmvTrend = '+0%';
|
|
|
+
|
|
|
this.kpiData.p80Trend = '+0%';
|
|
|
+
|
|
|
this.kpiData.top5Trend = '+0%';
|
|
|
+
|
|
|
this.kpiData.avgTimeTrend = '+0%';
|
|
|
+
|
|
|
} catch (error) {
|
|
|
- console.error('???????????????');
|
|
|
+
|
|
|
+ console.error('获取最大日期失败');
|
|
|
+
|
|
|
const today = this.formatYmd(new Date());
|
|
|
+
|
|
|
this.maxDate = today;
|
|
|
+
|
|
|
this.selectedDate = today;
|
|
|
+
|
|
|
const startDate = this.formatYmd(this.addDays(today, -6));
|
|
|
+
|
|
|
this.currentDateRange = { start: startDate, end: today };
|
|
|
+
|
|
|
await this.fetchAllApiData();
|
|
|
+
|
|
|
this.kpiData.gmvTrend = '+0%';
|
|
|
+
|
|
|
this.kpiData.p80Trend = '+0%';
|
|
|
+
|
|
|
this.kpiData.top5Trend = '+0%';
|
|
|
+
|
|
|
this.kpiData.avgTimeTrend = '+0%';
|
|
|
+
|
|
|
}
|
|
|
+
|
|
|
},
|
|
|
+
|
|
|
async selectDateRange(type) {
|
|
|
+
|
|
|
this.activeTab = type;
|
|
|
+
|
|
|
const baseDate = this.toDate(this.selectedDate);
|
|
|
+
|
|
|
let start;
|
|
|
+
|
|
|
let end;
|
|
|
|
|
|
+
|
|
|
+
|
|
|
if (type === '7d') {
|
|
|
+
|
|
|
end = this.formatYmd(baseDate);
|
|
|
+
|
|
|
start = this.formatYmd(this.addDays(baseDate, -6));
|
|
|
+
|
|
|
} else if (type === 'tm') {
|
|
|
+
|
|
|
start = this.formatYmd(this.startOfMonth(baseDate));
|
|
|
+
|
|
|
end = this.formatYmd(this.endOfMonth(baseDate));
|
|
|
+
|
|
|
} else if (type === 'lm') {
|
|
|
+
|
|
|
const lastMonth = this.addMonths(baseDate, -1);
|
|
|
+
|
|
|
start = this.formatYmd(this.startOfMonth(lastMonth));
|
|
|
+
|
|
|
end = this.formatYmd(this.endOfMonth(lastMonth));
|
|
|
+
|
|
|
} else if (type === 'all') {
|
|
|
+
|
|
|
start = '2022-01-01';
|
|
|
+
|
|
|
end = this.maxDate || this.formatYmd(baseDate);
|
|
|
+
|
|
|
}
|
|
|
|
|
|
+
|
|
|
+
|
|
|
this.currentDateRange = { start, end };
|
|
|
|
|
|
+
|
|
|
+
|
|
|
if (type === 'tm') {
|
|
|
+
|
|
|
await this.fetchAllApiData({ start, end });
|
|
|
+
|
|
|
const previousMonthData = await this.fetchPreviousMonthData(start);
|
|
|
+
|
|
|
if (previousMonthData) {
|
|
|
+
|
|
|
const currentGmv = parseFloat(this.kpiData.gmv.replace(/[?,]/g, '')) || 0;
|
|
|
+
|
|
|
const currentP80 = parseFloat(this.kpiData.p80Contribution.replace('%', '')) || 0;
|
|
|
+
|
|
|
const currentTop5 = parseFloat(this.kpiData.top5Contribution.replace('%', '')) || 0;
|
|
|
+
|
|
|
const currentAvgTime = this.parseTimeString(this.kpiData.averagePaymentTime) || 0;
|
|
|
|
|
|
+
|
|
|
+
|
|
|
this.kpiData.gmvTrend = this.calculateTrend(currentGmv, previousMonthData.gmv);
|
|
|
+
|
|
|
this.kpiData.p80Trend = this.calculateTrend(currentP80, previousMonthData.p80);
|
|
|
+
|
|
|
this.kpiData.top5Trend = this.calculateTrend(currentTop5, previousMonthData.top5);
|
|
|
+
|
|
|
this.kpiData.avgTimeTrend = this.calculateTrend(currentAvgTime, previousMonthData.avgTime);
|
|
|
+
|
|
|
}
|
|
|
+
|
|
|
} else {
|
|
|
+
|
|
|
this.fetchAllApiData({ start, end });
|
|
|
+
|
|
|
}
|
|
|
+
|
|
|
},
|
|
|
+
|
|
|
async fetchAllKpiData() {
|
|
|
+
|
|
|
try {
|
|
|
+
|
|
|
const [gmvRes, p80Res, top5Res, avgTimeRes] = await Promise.all([
|
|
|
+
|
|
|
axios.get('/api/analysis/gmv'),
|
|
|
+
|
|
|
axios.get('/api/analysis/r-big'),
|
|
|
+
|
|
|
axios.get('/api/analysis/top5-percentage'),
|
|
|
+
|
|
|
axios.get('/api/analysis/average-payment-time')
|
|
|
+
|
|
|
]);
|
|
|
|
|
|
+
|
|
|
+
|
|
|
const gmvValue = gmvRes.data || 0;
|
|
|
+
|
|
|
this.kpiData.gmv = new Intl.NumberFormat('zh-CN', { style: 'currency', currency: 'CNY' }).format(gmvValue);
|
|
|
+
|
|
|
const p80Value = p80Res.data?.rBigRatio || 0;
|
|
|
+
|
|
|
this.kpiData.p80Contribution = `${Math.round(p80Value)}%`;
|
|
|
+
|
|
|
const top5Value = top5Res.data?.data?.top5Percentage || 0;
|
|
|
+
|
|
|
this.kpiData.top5Contribution = `${Math.round(top5Value)}%`;
|
|
|
+
|
|
|
const avgSeconds = avgTimeRes.data?.averagePaymentSeconds || 0;
|
|
|
+
|
|
|
const minutes = Math.floor(avgSeconds / 60);
|
|
|
+
|
|
|
const seconds = Math.round(avgSeconds % 60);
|
|
|
- this.kpiData.averagePaymentTime = `${this.pad2(minutes)}:${this.pad2(seconds)} ??`;
|
|
|
+
|
|
|
+ this.kpiData.averagePaymentTime = `${this.pad2(minutes)}:${this.pad2(seconds)} 秒`;
|
|
|
+
|
|
|
} catch (error) {
|
|
|
- console.error('????KPI????:', error);
|
|
|
- this.kpiData.gmv = '????';
|
|
|
- this.kpiData.p80Contribution = '????';
|
|
|
- this.kpiData.top5Contribution = '????';
|
|
|
- this.kpiData.averagePaymentTime = '????';
|
|
|
+
|
|
|
+ console.error('获取KPI数据失败:', error);
|
|
|
+
|
|
|
+ this.kpiData.gmv = '?0';
|
|
|
+
|
|
|
+ this.kpiData.p80Contribution = '0%';
|
|
|
+
|
|
|
+ this.kpiData.top5Contribution = '0%';
|
|
|
+
|
|
|
+ this.kpiData.averagePaymentTime = '00:00 秒';
|
|
|
+
|
|
|
}
|
|
|
+
|
|
|
}
|
|
|
+
|
|
|
}
|
|
|
+
|
|
|
};
|
|
|
+
|
|
|
</script>
|
|
|
|
|
|
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
<style scoped>
|
|
|
+
|
|
|
.header-controls {
|
|
|
+
|
|
|
display: flex;
|
|
|
+
|
|
|
align-items: center;
|
|
|
+
|
|
|
gap: 15px;
|
|
|
+
|
|
|
margin-top: 10px;
|
|
|
+
|
|
|
}
|
|
|
+
|
|
|
.control-label {
|
|
|
+
|
|
|
font-size: 14px;
|
|
|
+
|
|
|
color: #666;
|
|
|
+
|
|
|
}
|
|
|
+
|
|
|
.control-item-group {
|
|
|
+
|
|
|
display: flex;
|
|
|
+
|
|
|
align-items: center;
|
|
|
+
|
|
|
gap: 10px;
|
|
|
+
|
|
|
}
|
|
|
+
|
|
|
.date-input {
|
|
|
+
|
|
|
padding: 4px 8px;
|
|
|
+
|
|
|
border: 1px solid #ddd;
|
|
|
+
|
|
|
border-radius: 4px;
|
|
|
+
|
|
|
font-size: 14px;
|
|
|
+
|
|
|
}
|
|
|
+
|
|
|
.time-tab {
|
|
|
+
|
|
|
padding: 5px 15px;
|
|
|
+
|
|
|
border: 1px solid #ddd;
|
|
|
+
|
|
|
background-color: #fff;
|
|
|
+
|
|
|
border-radius: 4px;
|
|
|
+
|
|
|
cursor: pointer;
|
|
|
+
|
|
|
font-size: 14px;
|
|
|
+
|
|
|
transition: all 0.2s;
|
|
|
+
|
|
|
}
|
|
|
+
|
|
|
.time-tab:hover {
|
|
|
+
|
|
|
border-color: #188df0;
|
|
|
+
|
|
|
color: #188df0;
|
|
|
+
|
|
|
}
|
|
|
+
|
|
|
.time-tab.active {
|
|
|
+
|
|
|
background-color: #188df0;
|
|
|
+
|
|
|
color: #fff;
|
|
|
+
|
|
|
border-color: #188df0;
|
|
|
+
|
|
|
}
|
|
|
+
|
|
|
/* 样式部分和之前一样,不用修改 */
|
|
|
+
|
|
|
.order-value-view {
|
|
|
+
|
|
|
display: flex;
|
|
|
+
|
|
|
flex-direction: column;
|
|
|
+
|
|
|
gap: 20px;
|
|
|
+
|
|
|
}
|
|
|
+
|
|
|
.page-header {
|
|
|
+
|
|
|
display: flex;
|
|
|
+
|
|
|
justify-content: space-between;
|
|
|
+
|
|
|
align-items: center;
|
|
|
+
|
|
|
background-color: white;
|
|
|
+
|
|
|
padding: 15px 20px;
|
|
|
+
|
|
|
border-radius: 8px;
|
|
|
+
|
|
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
|
|
|
+
|
|
|
}
|
|
|
+
|
|
|
.page-title {
|
|
|
+
|
|
|
font-size: 18px;
|
|
|
+
|
|
|
font-weight: 600;
|
|
|
+
|
|
|
color: #333;
|
|
|
+
|
|
|
margin: 0;
|
|
|
+
|
|
|
}
|
|
|
+
|
|
|
.header-controls {
|
|
|
+
|
|
|
display: flex;
|
|
|
+
|
|
|
align-items: center;
|
|
|
+
|
|
|
font-size: 14px;
|
|
|
+
|
|
|
}
|
|
|
+
|
|
|
.control-item {
|
|
|
+
|
|
|
margin-left: 20px;
|
|
|
+
|
|
|
color: #666;
|
|
|
+
|
|
|
}
|
|
|
+
|
|
|
.time-range-selector {
|
|
|
+
|
|
|
display: flex;
|
|
|
+
|
|
|
align-items: center;
|
|
|
+
|
|
|
}
|
|
|
+
|
|
|
.date-input {
|
|
|
+
|
|
|
padding: 6px 10px;
|
|
|
+
|
|
|
border: 1px solid #ddd;
|
|
|
+
|
|
|
border-radius: 4px;
|
|
|
+
|
|
|
margin-right: 10px;
|
|
|
+
|
|
|
}
|
|
|
+
|
|
|
.time-tab {
|
|
|
+
|
|
|
background: white;
|
|
|
+
|
|
|
border: 1px solid #ddd;
|
|
|
+
|
|
|
padding: 8px 12px;
|
|
|
+
|
|
|
margin-left: -1px;
|
|
|
+
|
|
|
cursor: pointer;
|
|
|
+
|
|
|
font-size: 12px;
|
|
|
+
|
|
|
color: #555;
|
|
|
+
|
|
|
transition: all 0.2s;
|
|
|
+
|
|
|
}
|
|
|
+
|
|
|
.time-tab:first-of-type {
|
|
|
+
|
|
|
border-top-left-radius: 4px;
|
|
|
+
|
|
|
border-bottom-left-radius: 4px;
|
|
|
+
|
|
|
}
|
|
|
+
|
|
|
.time-tab:last-of-type {
|
|
|
+
|
|
|
border-top-right-radius: 4px;
|
|
|
+
|
|
|
border-bottom-right-radius: 4px;
|
|
|
+
|
|
|
}
|
|
|
+
|
|
|
.time-tab.active {
|
|
|
+
|
|
|
background-color: #3366CC;
|
|
|
+
|
|
|
color: white;
|
|
|
+
|
|
|
border-color: #3366CC;
|
|
|
+
|
|
|
z-index: 1;
|
|
|
+
|
|
|
}
|
|
|
+
|
|
|
.kpi-cards-grid {
|
|
|
+
|
|
|
display: grid;
|
|
|
+
|
|
|
grid-template-columns: repeat(4, 1fr);
|
|
|
+
|
|
|
gap: 20px;
|
|
|
+
|
|
|
}
|
|
|
+
|
|
|
.charts-area {
|
|
|
+
|
|
|
display: flex;
|
|
|
+
|
|
|
flex-direction: column;
|
|
|
+
|
|
|
gap: 20px;
|
|
|
+
|
|
|
}
|
|
|
+
|
|
|
.leakage-section {
|
|
|
+
|
|
|
display: flex;
|
|
|
+
|
|
|
}
|
|
|
+
|
|
|
.full-width {
|
|
|
+
|
|
|
width: 100%;
|
|
|
+
|
|
|
}
|
|
|
+
|
|
|
.chart-wrapper {
|
|
|
+
|
|
|
background-color: white;
|
|
|
+
|
|
|
padding: 15px;
|
|
|
+
|
|
|
border-radius: 8px;
|
|
|
+
|
|
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
|
|
|
+
|
|
|
}
|
|
|
+
|
|
|
</style>
|
|
|
+
|