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