提交
This commit is contained in:
+629
@@ -0,0 +1,629 @@
|
||||
<!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>
|
||||
Reference in New Issue
Block a user