feat(PerformanceComparison): 优化性能对比表格样式和功能
- 重构表格样式,改进视觉层次和交互效果 - 调整变化值显示格式,将百分比和差值分开显示 - 增加表格行的悬停效果和斑马纹 - 改进数值格式化函数,添加单位显示 - 增强选择器交互效果和样式 - 添加NaN检查防止计算错误
This commit is contained in:
@@ -25,26 +25,27 @@
|
|||||||
<table class="comparison-table">
|
<table class="comparison-table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>核心指标</th>
|
<th class="metric-col">核心指标</th>
|
||||||
<th>本期数据</th>
|
<th>本期数据</th>
|
||||||
<th>{{ selectedPeriodLabel }}数据</th>
|
<th>{{ selectedPeriodLabel }}数据</th>
|
||||||
<th>变化情况</th>
|
<th class="change-col">变化情况</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr v-for="metric in comparedMetrics" :key="metric.key">
|
<tr v-for="metric in comparedMetrics" :key="metric.key">
|
||||||
<td>{{ metric.label }}</td>
|
<td class="metric-col">
|
||||||
|
<span class="metric-label">{{ metric.label }}</span>
|
||||||
|
</td>
|
||||||
<td>{{ formatValue(metric.current, metric.unit) }}</td>
|
<td>{{ formatValue(metric.current, metric.unit) }}</td>
|
||||||
<td>{{ formatValue(metric.previous, metric.unit) }}</td>
|
<td>{{ formatValue(metric.previous, metric.unit) }}</td>
|
||||||
<td>
|
<td class="change-col">
|
||||||
<div class="change-cell" :class="getChangeClass(metric.change.trend)">
|
<div class="change-cell">
|
||||||
<span class="change-value">
|
<span class="change-value" :class="getChangeClass(metric.change.trend)">
|
||||||
{{ formatChange(metric.change.diff, metric.unit) }}
|
<span v-if="metric.change.trend === 'up'" class="trend-icon">↑</span>
|
||||||
({{ metric.change.percentage }})
|
<span v-if="metric.change.trend === 'down'" class="trend-icon">↓</span>
|
||||||
|
{{ metric.change.percentage }}
|
||||||
</span>
|
</span>
|
||||||
<span v-if="metric.change.trend === 'up'" class="trend-icon">↑</span>
|
<span class="change-diff">{{ formatChange(metric.change.diff, metric.unit) }}</span>
|
||||||
<span v-if="metric.change.trend === 'down'" class="trend-icon">↓</span>
|
|
||||||
<span v-if="metric.change.trend === 'neutral'" class="trend-icon">-</span>
|
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -174,7 +175,9 @@ const comparedMetrics = computed(() => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const calculateChange = (current, previous) => {
|
const calculateChange = (current, previous) => {
|
||||||
if (previous === null || previous === undefined) return { diff: 'N/A', percentage: 'N/A', trend: 'neutral' };
|
if (previous === null || previous === undefined || isNaN(current) || isNaN(previous)) {
|
||||||
|
return { diff: 'N/A', percentage: 'N/A', trend: 'neutral' };
|
||||||
|
}
|
||||||
|
|
||||||
const diff = current - previous;
|
const diff = current - previous;
|
||||||
let percentage;
|
let percentage;
|
||||||
@@ -195,7 +198,7 @@ const calculateChange = (current, previous) => {
|
|||||||
const formatValue = (value, unit) => {
|
const formatValue = (value, unit) => {
|
||||||
if (value === null || value === undefined) return '-';
|
if (value === null || value === undefined) return '-';
|
||||||
if (unit === '分钟') {
|
if (unit === '分钟') {
|
||||||
return Math.round(value / 60); // 将秒转换为分钟
|
return `${Math.round(value / 60)} 分钟`;
|
||||||
}
|
}
|
||||||
if (unit === '%') {
|
if (unit === '%') {
|
||||||
return `${value.toFixed(1)}%`;
|
return `${value.toFixed(1)}%`;
|
||||||
@@ -205,57 +208,79 @@ const formatValue = (value, unit) => {
|
|||||||
|
|
||||||
const formatChange = (diff, unit) => {
|
const formatChange = (diff, unit) => {
|
||||||
if (typeof diff !== 'number') return '';
|
if (typeof diff !== 'number') return '';
|
||||||
const formattedDiff = formatValue(Math.abs(diff), unit);
|
const prefix = diff > 0 ? '+' : '-';
|
||||||
return `${diff > 0 ? '+' : '-'}${formattedDiff}`;
|
const absDiff = Math.abs(diff);
|
||||||
|
|
||||||
|
if (unit === '分钟') {
|
||||||
|
return `${prefix}${Math.round(absDiff / 60)} 分钟`;
|
||||||
|
}
|
||||||
|
if (unit === '%') {
|
||||||
|
return `${prefix}${absDiff.toFixed(1)}%`;
|
||||||
|
}
|
||||||
|
return `${prefix}${absDiff.toLocaleString()}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
const getChangeClass = (trend) => {
|
const getChangeClass = (trend) => {
|
||||||
if (trend === 'up') return 'text-positive';
|
if (trend === 'up') return 'positive';
|
||||||
if (trend === 'down') return 'text-negative';
|
if (trend === 'down') return 'negative';
|
||||||
return 'text-neutral';
|
return 'neutral';
|
||||||
};
|
};
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.performance-comparison {
|
.performance-comparison {
|
||||||
background: white;
|
background: #ffffff;
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
padding: 1.5rem;
|
padding: 2rem;
|
||||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
|
||||||
margin-top: 1rem;
|
margin-top: 1.5rem;
|
||||||
|
border: 1px solid #e2e8f0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.comparison-header {
|
.comparison-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
margin-bottom: 1.5rem;
|
margin-bottom: 2rem;
|
||||||
|
padding-bottom: 1rem;
|
||||||
|
border-bottom: 1px solid #f1f5f9;
|
||||||
|
|
||||||
h2 {
|
h2 {
|
||||||
font-size: 1.4rem;
|
font-size: 1.5rem;
|
||||||
font-weight: 600;
|
font-weight: 700;
|
||||||
color: #1e293b;
|
color: #1a202c;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.period-selector-wrapper {
|
.period-selector-wrapper {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 0.5rem;
|
gap: 0.75rem;
|
||||||
|
|
||||||
label {
|
label {
|
||||||
font-size: 0.9rem;
|
font-size: 0.9rem;
|
||||||
color: #64748b;
|
color: #718096;
|
||||||
}
|
}
|
||||||
|
|
||||||
.period-select {
|
.period-select {
|
||||||
padding: 0.5rem;
|
padding: 0.6rem 1rem;
|
||||||
border-radius: 6px;
|
border-radius: 8px;
|
||||||
border: 1px solid #e2e8f0;
|
border: 1px solid #cbd5e0;
|
||||||
background-color: #f8fafc;
|
background-color: #ffffff;
|
||||||
font-size: 0.9rem;
|
font-size: 0.9rem;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
transition: border-color 0.2s, box-shadow 0.2s;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
border-color: #a0aec0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: #4299e1;
|
||||||
|
box-shadow: 0 0 0 3px rgba(66, 153, 225, 0.5);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -269,67 +294,108 @@ const getChangeClass = (trend) => {
|
|||||||
border-collapse: collapse;
|
border-collapse: collapse;
|
||||||
|
|
||||||
th, td {
|
th, td {
|
||||||
padding: 0.85rem 1rem;
|
padding: 1rem 1.25rem;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
border-bottom: 1px solid #f1f5f9;
|
vertical-align: middle;
|
||||||
}
|
}
|
||||||
|
|
||||||
thead th {
|
thead th {
|
||||||
font-size: 0.8rem;
|
font-size: 0.8rem;
|
||||||
color: #64748b;
|
color: #718096;
|
||||||
font-weight: 500;
|
font-weight: 600;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
background-color: #f8fafc;
|
background-color: #f7fafc;
|
||||||
|
border-bottom: 2px solid #e2e8f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
tbody tr {
|
||||||
|
transition: background-color 0.15s ease-in-out;
|
||||||
|
&:nth-child(odd) {
|
||||||
|
background-color: #fdfdff;
|
||||||
|
}
|
||||||
|
&:not(:last-child) {
|
||||||
|
border-bottom: 1px solid #f1f5f9;
|
||||||
|
}
|
||||||
|
&:hover {
|
||||||
|
background-color: #f0f5ff;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
tbody td {
|
tbody td {
|
||||||
font-size: 0.95rem;
|
font-size: 0.95rem;
|
||||||
color: #334155;
|
color: #2d3748;
|
||||||
|
}
|
||||||
|
|
||||||
|
.metric-col {
|
||||||
|
min-width: 150px;
|
||||||
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
tbody tr:last-child td {
|
.change-col {
|
||||||
border-bottom: none;
|
width: 180px;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.change-cell {
|
.change-cell {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 0.25rem;
|
||||||
|
|
||||||
|
.change-value {
|
||||||
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 0.5rem;
|
gap: 0.35rem;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
|
padding: 0.25rem 0.6rem;
|
||||||
|
border-radius: 9999px;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
|
||||||
|
&.positive {
|
||||||
|
background-color: #ecfdf5;
|
||||||
|
color: #065f46;
|
||||||
|
}
|
||||||
|
&.negative {
|
||||||
|
background-color: #fff1f2;
|
||||||
|
color: #9f1239;
|
||||||
|
}
|
||||||
|
&.neutral {
|
||||||
|
background-color: #f8fafc;
|
||||||
|
color: #475569;
|
||||||
|
}
|
||||||
|
|
||||||
.trend-icon {
|
.trend-icon {
|
||||||
font-size: 1.1rem;
|
font-size: 1rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.change-diff {
|
||||||
|
font-size: 0.8rem;
|
||||||
|
color: #718096;
|
||||||
|
margin-left: 0.25rem;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.text-positive {
|
|
||||||
color: #10b981; /* 绿色 */
|
|
||||||
}
|
|
||||||
.text-negative {
|
|
||||||
color: #ef4444; /* 红色 */
|
|
||||||
}
|
|
||||||
.text-neutral {
|
|
||||||
color: #64748b; /* 灰色 */
|
|
||||||
}
|
|
||||||
|
|
||||||
.loading-state, .empty-state {
|
.loading-state, .empty-state {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
padding: 3rem;
|
padding: 4rem;
|
||||||
color: #64748b;
|
color: #a0aec0;
|
||||||
font-size: 0.9rem;
|
font-size: 1rem;
|
||||||
|
background-color: #f7fafc;
|
||||||
|
border-radius: 8px;
|
||||||
|
|
||||||
.loading-spinner {
|
.loading-spinner {
|
||||||
width: 32px;
|
width: 40px;
|
||||||
height: 32px;
|
height: 40px;
|
||||||
border: 3px solid #e2e8f0;
|
border: 4px solid #e2e8f0;
|
||||||
border-top: 3px solid #3b82f6;
|
border-top: 4px solid #4299e1;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
animation: spin 1s linear infinite;
|
animation: spin 1s linear infinite;
|
||||||
margin-bottom: 1rem;
|
margin-bottom: 1.5rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user