630 lines
19 KiB
HTML
630 lines
19 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="zh-CN">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>年度销售数据看板</title>
|
|
<script src="https://cdn.jsdelivr.net/npm/echarts@5.4.3/dist/echarts.min.js"></script>
|
|
<style>
|
|
* {
|
|
margin: 0;
|
|
padding: 0;
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
body {
|
|
font-family: 'Microsoft YaHei', Arial, sans-serif;
|
|
background: linear-gradient(135deg, #0a0e27 0%, #1a1f3a 100%);
|
|
min-height: 100vh;
|
|
color: #fff;
|
|
overflow-x: hidden;
|
|
}
|
|
|
|
.container {
|
|
max-width: 1920px;
|
|
margin: 0 auto;
|
|
padding: 30px;
|
|
}
|
|
|
|
/* 顶部年度标题 */
|
|
.year-title {
|
|
text-align: center;
|
|
font-size: 36px;
|
|
color: #fff;
|
|
margin-bottom: 30px;
|
|
font-weight: bold;
|
|
letter-spacing: 4px;
|
|
}
|
|
|
|
/* 顶部销售额区域 */
|
|
.sales-header {
|
|
margin-bottom: 40px;
|
|
padding: 40px;
|
|
background: rgba(255, 255, 255, 0.03);
|
|
border-radius: 20px;
|
|
border: 1px solid rgba(240, 138, 49, 0.2);
|
|
}
|
|
|
|
.sales-section {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: stretch;
|
|
margin-bottom: 40px;
|
|
width: 100%;
|
|
}
|
|
|
|
.sales-progress-wrapper {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: flex-start;
|
|
gap: 15px;
|
|
margin-bottom: 15px;
|
|
width: 100%;
|
|
}
|
|
|
|
.sales-section .sales-info {
|
|
text-align: center;
|
|
display: flex;
|
|
justify-content: center;
|
|
}
|
|
|
|
.progress-label {
|
|
font-size: 18px;
|
|
color: #f08a31;
|
|
font-weight: bold;
|
|
min-width: 70px;
|
|
}
|
|
|
|
.goal-section {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
width: 100%;
|
|
}
|
|
|
|
.goal-section .sales-info {
|
|
margin-bottom: 15px;
|
|
}
|
|
|
|
.full-width {
|
|
width: 100% !important;
|
|
}
|
|
|
|
.sales-info {
|
|
display: flex;
|
|
align-items: baseline;
|
|
justify-content: center;
|
|
gap: 15px;
|
|
}
|
|
|
|
.sales-label {
|
|
font-size: 28px;
|
|
color: #f08a31;
|
|
font-weight: 500;
|
|
}
|
|
|
|
.sales-value {
|
|
font-size: 56px;
|
|
font-weight: bold;
|
|
color: #f08a31;
|
|
text-shadow: 0 0 20px rgba(240, 138, 49, 0.5);
|
|
letter-spacing: 2px;
|
|
}
|
|
|
|
.sales-unit {
|
|
font-size: 24px;
|
|
color: #f08a31;
|
|
}
|
|
|
|
.target-info {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 20px;
|
|
}
|
|
|
|
.target-row {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 20px;
|
|
}
|
|
|
|
.target-label {
|
|
font-size: 18px;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.target-label.completed {
|
|
color: #f08a31;
|
|
}
|
|
|
|
.target-label.goal {
|
|
color: #ff4d4f;
|
|
}
|
|
|
|
.progress-bar-container {
|
|
position: relative;
|
|
width: 800px;
|
|
height: 20px;
|
|
background: rgba(255, 255, 255, 0.1);
|
|
border-radius: 15px;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.progress-bar {
|
|
height: 100%;
|
|
background: linear-gradient(90deg, #f08a31, #ffb347);
|
|
border-radius: 15px;
|
|
transition: width 1s ease;
|
|
position: relative;
|
|
}
|
|
|
|
.target-line {
|
|
position: absolute;
|
|
top: 0;
|
|
bottom: 0;
|
|
width: 3px;
|
|
background: #ff4d4f;
|
|
box-shadow: 0 0 10px rgba(255, 77, 79, 0.8);
|
|
}
|
|
|
|
.target-line::before,
|
|
.target-line::after {
|
|
content: '';
|
|
position: absolute;
|
|
left: -6px;
|
|
width: 0;
|
|
height: 0;
|
|
border-left: 8px solid #ff4d4f;
|
|
border-top: 6px solid transparent;
|
|
border-bottom: 6px solid transparent;
|
|
}
|
|
|
|
.target-line::before {
|
|
top: -6px;
|
|
}
|
|
|
|
.target-line::after {
|
|
bottom: -6px;
|
|
transform: rotate(180deg);
|
|
}
|
|
|
|
.progress-value {
|
|
font-size: 18px;
|
|
color: #f08a31;
|
|
min-width: 60px;
|
|
}
|
|
|
|
.goal-text {
|
|
font-size: 18px;
|
|
color: #ff4d4f;
|
|
}
|
|
|
|
.sales-label.goal {
|
|
color: #ff4d4f;
|
|
font-size: 20px;
|
|
}
|
|
|
|
.sales-value.goal {
|
|
color: #ff4d4f;
|
|
text-shadow: 0 0 20px rgba(255, 77, 79, 0.5);
|
|
font-size: 20px;
|
|
}
|
|
|
|
.sales-unit.goal {
|
|
color: #ff4d4f;
|
|
|
|
}
|
|
|
|
.goal-bar {
|
|
background: linear-gradient(90deg, #ff4d4f, #ff7875);
|
|
}
|
|
|
|
/* 内容区域 */
|
|
.content-section {
|
|
margin-bottom: 30px;
|
|
}
|
|
|
|
.section-title {
|
|
font-size: 22px;
|
|
color: #f08a31;
|
|
margin-bottom: 20px;
|
|
padding-left: 20px;
|
|
border-left: 4px solid #f08a31;
|
|
}
|
|
|
|
.rank-container {
|
|
display: flex;
|
|
gap: 20px;
|
|
background: rgba(255, 255, 255, 0.03);
|
|
border-radius: 15px;
|
|
padding: 25px;
|
|
border: 1px solid rgba(240, 138, 49, 0.15);
|
|
align-items: stretch;
|
|
}
|
|
|
|
/* 排名列表 */
|
|
.rank-list {
|
|
width: 33.33%;
|
|
min-width: 300px;
|
|
display: flex;
|
|
flex-direction: column;
|
|
height: 100%;
|
|
}
|
|
|
|
.rank-item {
|
|
display: flex;
|
|
align-items: center;
|
|
padding: 15px 20px;
|
|
margin-bottom: 12px;
|
|
background: rgba(255, 255, 255, 0.05);
|
|
border-radius: 10px;
|
|
border: 1px solid rgba(255, 255, 255, 0.1);
|
|
transition: all 0.3s ease;
|
|
}
|
|
|
|
.rank-item:hover {
|
|
background: rgba(240, 138, 49, 0.15);
|
|
border-color: rgba(240, 138, 49, 0.4);
|
|
transform: translateX(5px);
|
|
}
|
|
|
|
.rank-item.top-1 {
|
|
background: linear-gradient(135deg, rgba(255, 215, 0, 0.2), rgba(255, 215, 0, 0.05));
|
|
border-color: rgba(255, 215, 0, 0.5);
|
|
}
|
|
|
|
.rank-item.top-2 {
|
|
background: linear-gradient(135deg, rgba(192, 192, 192, 0.2), rgba(192, 192, 192, 0.05));
|
|
border-color: rgba(192, 192, 192, 0.5);
|
|
}
|
|
|
|
.rank-item.top-3 {
|
|
background: linear-gradient(135deg, rgba(205, 127, 50, 0.2), rgba(205, 127, 50, 0.05));
|
|
border-color: rgba(205, 127, 50, 0.5);
|
|
}
|
|
|
|
.rank-number {
|
|
width: 40px;
|
|
height: 40px;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
font-size: 18px;
|
|
font-weight: bold;
|
|
border-radius: 50%;
|
|
margin-right: 15px;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.rank-item.top-1 .rank-number {
|
|
background: linear-gradient(135deg, #ffd700, #ffed4e);
|
|
color: #000;
|
|
}
|
|
|
|
.rank-item.top-2 .rank-number {
|
|
background: linear-gradient(135deg, #c0c0c0, #e8e8e8);
|
|
color: #000;
|
|
}
|
|
|
|
.rank-item.top-3 .rank-number {
|
|
background: linear-gradient(135deg, #cd7f32, #daa520);
|
|
color: #fff;
|
|
}
|
|
|
|
.rank-item:not(.top-1):not(.top-2):not(.top-3) .rank-number {
|
|
background: rgba(255, 255, 255, 0.1);
|
|
color: #fff;
|
|
}
|
|
|
|
.rank-info {
|
|
flex: 1;
|
|
min-width: 0;
|
|
}
|
|
|
|
.rank-name {
|
|
font-size: 18px;
|
|
color: #fff;
|
|
margin-bottom: 5px;
|
|
white-space: nowrap;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
}
|
|
|
|
.rank-sales {
|
|
font-size: 14px;
|
|
color: #f08a31;
|
|
}
|
|
|
|
.rank-rate {
|
|
font-size: 14px;
|
|
color: #4ade80;
|
|
margin-left: 10px;
|
|
}
|
|
|
|
/* 图表区域 */
|
|
.chart-container {
|
|
flex: 1;
|
|
min-height: 300px;
|
|
height: 100%;
|
|
}
|
|
|
|
/* 响应式 */
|
|
@media (max-width: 1400px) {
|
|
.sales-value {
|
|
font-size: 42px;
|
|
}
|
|
.progress-bar-container {
|
|
width: 500px;
|
|
}
|
|
}
|
|
|
|
@media (max-width: 1024px) {
|
|
.rank-container {
|
|
flex-direction: column;
|
|
}
|
|
.rank-list,
|
|
.chart-container {
|
|
width: 100%;
|
|
}
|
|
.sales-section,
|
|
.goal-section {
|
|
align-items: center;
|
|
}
|
|
.sales-info {
|
|
flex-direction: column;
|
|
align-items: center;
|
|
}
|
|
.progress-bar-container {
|
|
width: 100%;
|
|
}
|
|
.sales-progress-wrapper {
|
|
justify-content: center;
|
|
}
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="container">
|
|
<!-- 年度标题 -->
|
|
<div class="year-title">数据看板</div>
|
|
|
|
<!-- 顶部销售额区域 -->
|
|
<div class="sales-header">
|
|
<!-- 总销售额区域:进度条在上,金额在下,居左 -->
|
|
<div class="sales-section">
|
|
<div class="sales-progress-wrapper">
|
|
<div class="progress-bar-container">
|
|
<div class="progress-bar"></div>
|
|
</div>
|
|
<div class="progress-label">77.05%</div>
|
|
</div>
|
|
<div class="sales-info">
|
|
<span class="sales-label">总销售额</span>
|
|
<span class="sales-value">231,154,000,000</span>
|
|
<span class="sales-unit">元</span>
|
|
</div>
|
|
</div>
|
|
<!-- 年度目标区域:金额居中,进度条在下 -->
|
|
<div class="goal-section">
|
|
<div class="sales-info">
|
|
<span class="sales-label goal">年度目标</span>
|
|
<span class="sales-value goal">300,000,000,000</span>
|
|
<span class="sales-unit goal">元</span>
|
|
</div>
|
|
<div class="progress-bar-container full-width">
|
|
<div class="progress-bar goal-bar" style="width: 100%"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 小组排名区域 -->
|
|
<div class="content-section">
|
|
<div class="section-title">小组排名</div>
|
|
<div class="rank-container">
|
|
<div class="rank-list" id="groupRankList"></div>
|
|
<div class="chart-container" id="groupChart"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 个人排名区域 -->
|
|
<div class="content-section">
|
|
<div class="section-title">个人排名</div>
|
|
<div class="rank-container">
|
|
<div class="rank-list" id="personRankList"></div>
|
|
<div class="chart-container" id="personChart"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
// 模拟数据 - 小组排名
|
|
const groupData = [
|
|
{ name: '华东一组', target: 50000000000, actual: 62000000000 },
|
|
{ name: '华南二组', target: 45000000000, actual: 51000000000 },
|
|
{ name: '华北三组', target: 40000000000, actual: 43500000000 },
|
|
{ name: '西南四组', target: 35000000000, actual: 28000000000 },
|
|
{ name: '东北五组', target: 30000000000, actual: 26500000000 }
|
|
];
|
|
|
|
// 模拟数据 - 个人排名
|
|
const personData = [
|
|
{ name: '张伟', target: 5000000000, actual: 7200000000 },
|
|
{ name: '李娜', target: 4500000000, actual: 5800000000 },
|
|
{ name: '王强', target: 4000000000, actual: 4900000000 },
|
|
{ name: '刘洋', target: 3800000000, actual: 4100000000 },
|
|
{ name: '陈静', target: 3500000000, actual: 3600000000 }
|
|
];
|
|
|
|
// 格式化数字
|
|
function formatNumber(num) {
|
|
return (num / 100000000).toFixed(1) + '亿';
|
|
}
|
|
|
|
// 渲染排名列表
|
|
function renderRankList(containerId, data, type) {
|
|
const container = document.getElementById(containerId);
|
|
container.innerHTML = data.map((item, index) => {
|
|
const rate = ((item.actual / item.target) * 100).toFixed(1);
|
|
const rankClass = index < 3 ? `top-${index + 1}` : '';
|
|
return `
|
|
<div class="rank-item ${rankClass}">
|
|
<div class="rank-number">${index + 1}</div>
|
|
<div class="rank-info">
|
|
<div class="rank-name">${item.name}</div>
|
|
<div class="rank-sales">销售额: ${formatNumber(item.actual)} | 目标: ${formatNumber(item.target)}
|
|
<span class="rank-rate">完成率: ${rate}%</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
}).join('');
|
|
}
|
|
|
|
// 渲染图表
|
|
function renderChart(containerId, data, type) {
|
|
const chartDom = document.getElementById(containerId);
|
|
const chart = echarts.init(chartDom);
|
|
const names = data.map(item => item.name);
|
|
const targets = data.map(item => item.target / 100000000);
|
|
const actuals = data.map(item => item.actual / 100000000);
|
|
|
|
const option = {
|
|
backgroundColor: 'transparent',
|
|
tooltip: {
|
|
trigger: 'axis',
|
|
axisPointer: {
|
|
type: 'shadow'
|
|
},
|
|
formatter: function(params) {
|
|
let result = params[0].name + '<br/>';
|
|
params.forEach(item => {
|
|
result += `${item.marker}${item.seriesName}: ${item.value}亿<br/>`;
|
|
});
|
|
return result;
|
|
}
|
|
},
|
|
legend: {
|
|
data: ['目标销售额', '实际销售额'],
|
|
textStyle: {
|
|
color: '#fff'
|
|
},
|
|
top: 10
|
|
},
|
|
grid: {
|
|
left: '10%',
|
|
right: '5%',
|
|
bottom: '3%',
|
|
top: '15%',
|
|
containLabel: true
|
|
},
|
|
xAxis: {
|
|
type: 'value',
|
|
name: '销售额(亿)',
|
|
nameTextStyle: {
|
|
color: '#fff'
|
|
},
|
|
axisLabel: {
|
|
color: '#fff',
|
|
fontSize: 12
|
|
},
|
|
axisLine: {
|
|
lineStyle: {
|
|
color: 'rgba(255, 255, 255, 0.2)'
|
|
}
|
|
},
|
|
splitLine: {
|
|
lineStyle: {
|
|
color: 'rgba(255, 255, 255, 0.1)'
|
|
}
|
|
}
|
|
},
|
|
yAxis: {
|
|
type: 'category',
|
|
data: names,
|
|
axisLabel: {
|
|
color: '#fff',
|
|
fontSize: 14
|
|
},
|
|
axisLine: {
|
|
lineStyle: {
|
|
color: 'rgba(255, 255, 255, 0.2)'
|
|
}
|
|
}
|
|
},
|
|
series: [
|
|
{
|
|
name: '目标销售额',
|
|
type: 'bar',
|
|
data: targets,
|
|
itemStyle: {
|
|
color: 'rgba(255, 77, 79, 0.6)',
|
|
borderRadius: [0, 0, 0, 0]
|
|
},
|
|
barMaxWidth: 30
|
|
},
|
|
{
|
|
name: '实际销售额',
|
|
type: 'bar',
|
|
data: actuals,
|
|
itemStyle: {
|
|
color: new echarts.graphic.LinearGradient(0, 0, 1, 0, [
|
|
{ offset: 0, color: '#f08a31' },
|
|
{ offset: 1, color: '#ffb347' }
|
|
]),
|
|
borderRadius: [0, 0, 0, 0]
|
|
},
|
|
barMaxWidth: 30
|
|
}
|
|
]
|
|
};
|
|
|
|
chart.setOption(option);
|
|
chart.resize();
|
|
|
|
// 响应式
|
|
window.addEventListener('resize', () => {
|
|
chart.resize();
|
|
});
|
|
}
|
|
|
|
// 初始化
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
renderRankList('groupRankList', groupData, 'group');
|
|
renderRankList('personRankList', personData, 'person');
|
|
|
|
// 确保DOM完全渲染后再初始化图表
|
|
setTimeout(() => {
|
|
// 确保图表容器高度与排名列表一致
|
|
function syncChartHeight() {
|
|
const groupRankList = document.getElementById('groupRankList');
|
|
const groupChart = document.getElementById('groupChart');
|
|
const personRankList = document.getElementById('personRankList');
|
|
const personChart = document.getElementById('personChart');
|
|
|
|
if (groupRankList && groupChart) {
|
|
groupChart.style.height = groupRankList.offsetHeight + 'px';
|
|
}
|
|
if (personRankList && personChart) {
|
|
personChart.style.height = personRankList.offsetHeight + 'px';
|
|
}
|
|
}
|
|
|
|
syncChartHeight();
|
|
renderChart('groupChart', groupData, 'group');
|
|
renderChart('personChart', personData, 'person');
|
|
|
|
// 响应式调整
|
|
window.addEventListener('resize', () => {
|
|
syncChartHeight();
|
|
// 重新调整图表大小
|
|
const groupChart = echarts.getInstanceByDom(document.getElementById('groupChart'));
|
|
const personChart = echarts.getInstanceByDom(document.getElementById('personChart'));
|
|
if (groupChart) groupChart.resize();
|
|
if (personChart) personChart.resize();
|
|
});
|
|
}, 200);
|
|
});
|
|
</script>
|
|
</body>
|
|
</html>
|