657 lines
20 KiB
JavaScript
657 lines
20 KiB
JavaScript
|
|
// 常量定义
|
|||
|
|
const CHART_INIT_DELAY = 500; // 图表初始化延迟(毫秒)
|
|||
|
|
const PROGRESS_ANIM_DELAY = 800; // 进度条动画延迟(毫秒)
|
|||
|
|
const RESIZE_DEBOUNCE_TIME = 300; // 防抖时间(毫秒)
|
|||
|
|
const PROGRESS_STEP = 0.4; // 进度条每帧增量
|
|||
|
|
const PROGRESS_INTERVAL = 20; // 进度条更新间隔(毫秒)
|
|||
|
|
const MOBILE_BREAKPOINT = 768; // 移动端断点(px)
|
|||
|
|
const DESIGN_WIDTH = 1920; // 设计稿宽度(px)
|
|||
|
|
|
|||
|
|
// 年度销售数据(可通过API动态获取)
|
|||
|
|
let yearData = {
|
|||
|
|
target: 300000000, // 年度目标(万元)
|
|||
|
|
actual: 231154000 // 年度实际销售额(万元)
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// PC端等比例缩放函数
|
|||
|
|
function setScale() {
|
|||
|
|
const screenWidth = window.innerWidth;
|
|||
|
|
|
|||
|
|
// 移动端不缩放,保持响应式布局
|
|||
|
|
if (screenWidth <= MOBILE_BREAKPOINT) {
|
|||
|
|
document.body.style.transform = 'none';
|
|||
|
|
document.body.style.transformOrigin = 'left top';
|
|||
|
|
document.body.style.marginLeft = '0px';
|
|||
|
|
document.body.style.width = '100%';
|
|||
|
|
document.body.style.minWidth = '320px';
|
|||
|
|
document.body.style.height = 'auto';
|
|||
|
|
document.body.style.overflowX = 'hidden';
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// PC端等比例缩放
|
|||
|
|
const scale = screenWidth / DESIGN_WIDTH;
|
|||
|
|
|
|||
|
|
// 只限制最小缩放比例,允许放大
|
|||
|
|
// 最小缩放到60%,最大可以放大填满屏幕
|
|||
|
|
const finalScale = Math.max(scale, 0.6);
|
|||
|
|
|
|||
|
|
// 重置样式以获取真实内容高度
|
|||
|
|
document.body.style.transform = 'none';
|
|||
|
|
document.body.style.height = 'auto';
|
|||
|
|
|
|||
|
|
// 获取真实内容高度
|
|||
|
|
const contentHeight = document.body.scrollHeight;
|
|||
|
|
|
|||
|
|
// 计算缩放后的高度
|
|||
|
|
const scaledHeight = Math.ceil(contentHeight * finalScale);
|
|||
|
|
|
|||
|
|
// 应用缩放
|
|||
|
|
document.body.style.transform = `scale(${finalScale})`;
|
|||
|
|
document.body.style.transformOrigin = 'left top';
|
|||
|
|
document.body.style.width = `${DESIGN_WIDTH}px`;
|
|||
|
|
document.body.style.minWidth = `${DESIGN_WIDTH}px`;
|
|||
|
|
document.body.style.height = `${scaledHeight}px`;
|
|||
|
|
document.body.style.marginLeft = '0px';
|
|||
|
|
document.body.style.overflowX = 'hidden';
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 模拟数据 - 小组排名
|
|||
|
|
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 },
|
|||
|
|
{ name: '杨明', target: 3200000000, actual: 2800000000 },
|
|||
|
|
{ name: '赵丽', target: 3000000000, actual: 2500000000 }
|
|||
|
|
];
|
|||
|
|
|
|||
|
|
// 格式化数字(万元)
|
|||
|
|
function formatNumber(num) {
|
|||
|
|
return (num / 10000).toFixed(0) + '万';
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 格式化大数字(万元)
|
|||
|
|
function formatLargeNumber(num) {
|
|||
|
|
return num.toLocaleString('zh-CN') + ' 万元';
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 获取年度销售数据(可替换为API调用)
|
|||
|
|
async function fetchYearData() {
|
|||
|
|
// TODO: 替换为实际的API调用
|
|||
|
|
// 示例:
|
|||
|
|
// try {
|
|||
|
|
// const response = await fetch('/api/year-data');
|
|||
|
|
// const data = await response.json();
|
|||
|
|
// return data;
|
|||
|
|
// } catch (error) {
|
|||
|
|
// console.error('获取数据失败:', error);
|
|||
|
|
// return null;
|
|||
|
|
// }
|
|||
|
|
|
|||
|
|
// 当前使用模拟数据
|
|||
|
|
console.log('正在获取年度数据...');
|
|||
|
|
return {
|
|||
|
|
target: 3500, // 年度目标(万元)- 可修改此值测试
|
|||
|
|
actual: 2750 // 年度实际销售额(万元)- 可修改此值测试
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 更新顶部数据区域
|
|||
|
|
function updateTopData() {
|
|||
|
|
const targetEl = document.querySelector('.target-amount');
|
|||
|
|
const actualEl = document.querySelector('.total-amount');
|
|||
|
|
const progressTextEl = document.querySelector('.progress-text');
|
|||
|
|
|
|||
|
|
console.log('正在更新顶部数据:', yearData);
|
|||
|
|
|
|||
|
|
if (targetEl) {
|
|||
|
|
targetEl.textContent = formatLargeNumber(yearData.target);
|
|||
|
|
console.log('年度目标已更新:', formatLargeNumber(yearData.target));
|
|||
|
|
} else {
|
|||
|
|
console.error('找不到 .target-amount 元素');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (actualEl) {
|
|||
|
|
actualEl.textContent = formatLargeNumber(yearData.actual);
|
|||
|
|
console.log('年度总销售额已更新:', formatLargeNumber(yearData.actual));
|
|||
|
|
} else {
|
|||
|
|
console.error('找不到 .total-amount 元素');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 计算完成率
|
|||
|
|
const progressRate = ((yearData.actual / yearData.target) * 100).toFixed(2);
|
|||
|
|
if (progressTextEl) {
|
|||
|
|
progressTextEl.textContent = progressRate + '%';
|
|||
|
|
console.log('完成率已更新:', progressRate + '%');
|
|||
|
|
} else {
|
|||
|
|
console.error('找不到 .progress-text 元素');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return parseFloat(progressRate);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 启动进度条动画
|
|||
|
|
function startProgressAnimation(targetRate) {
|
|||
|
|
const progressBar = document.querySelector('.progress-bar');
|
|||
|
|
if (!progressBar) return;
|
|||
|
|
|
|||
|
|
let value = 0;
|
|||
|
|
let lastTime = 0;
|
|||
|
|
|
|||
|
|
function animateProgress(currentTime) {
|
|||
|
|
if (!lastTime) lastTime = currentTime;
|
|||
|
|
const deltaTime = currentTime - lastTime;
|
|||
|
|
|
|||
|
|
if (deltaTime >= PROGRESS_INTERVAL) {
|
|||
|
|
value += PROGRESS_STEP;
|
|||
|
|
if (value >= targetRate) {
|
|||
|
|
value = targetRate;
|
|||
|
|
progressBar.style.width = value + '%';
|
|||
|
|
return; // 动画完成
|
|||
|
|
}
|
|||
|
|
progressBar.style.width = value + '%';
|
|||
|
|
lastTime = currentTime;
|
|||
|
|
}
|
|||
|
|
requestAnimationFrame(animateProgress);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
requestAnimationFrame(animateProgress);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 渲染小组排名列表
|
|||
|
|
function renderGroupRankList() {
|
|||
|
|
const container = document.getElementById('groupRankList');
|
|||
|
|
container.innerHTML = groupData.map((item, index) => {
|
|||
|
|
const rate = ((item.actual / item.target) * 100).toFixed(1);
|
|||
|
|
const rankClass = index < 3 ? `top-${index + 1}` : 'other';
|
|||
|
|
return `
|
|||
|
|
<div class="ranking-item ${rankClass}">
|
|||
|
|
<div class="rank-number">${index + 1}</div>
|
|||
|
|
<div class="rank-name">${item.name}</div>
|
|||
|
|
<div class="rank-sales">销售额: ${formatNumber(item.actual)} | 目标: ${formatNumber(item.target)}</div>
|
|||
|
|
<div class="rank-rate">完成率: ${rate}%</div>
|
|||
|
|
</div>
|
|||
|
|
`;
|
|||
|
|
}).join('');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 渲染个人排名列表
|
|||
|
|
function renderPersonRankList() {
|
|||
|
|
const container = document.getElementById('personRankList');
|
|||
|
|
container.innerHTML = personData.map((item, index) => {
|
|||
|
|
const rate = ((item.actual / item.target) * 100).toFixed(1);
|
|||
|
|
const rankClass = index < 3 ? `top-${index + 1}` : 'other';
|
|||
|
|
return `
|
|||
|
|
<div class="ranking-item ${rankClass}">
|
|||
|
|
<div class="rank-number">${index + 1}</div>
|
|||
|
|
<div class="rank-name">${item.name}</div>
|
|||
|
|
<div class="rank-sales">销售额: ${formatNumber(item.actual)} | 目标: ${formatNumber(item.target)}</div>
|
|||
|
|
<div class="rank-rate">完成率: ${rate}%</div>
|
|||
|
|
</div>
|
|||
|
|
`;
|
|||
|
|
}).join('');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 渲染小组图表
|
|||
|
|
function renderGroupChart() {
|
|||
|
|
const chartDom = document.getElementById('groupChart');
|
|||
|
|
const chart = echarts.init(chartDom);
|
|||
|
|
const names = groupData.map(item => item.name);
|
|||
|
|
const actuals = groupData.map(item => item.actual / 100000000);
|
|||
|
|
const targets = groupData.map(item => item.target / 100000000);
|
|||
|
|
|
|||
|
|
// 判断是否为移动端
|
|||
|
|
const isMobile = window.innerWidth <= MOBILE_BREAKPOINT;
|
|||
|
|
|
|||
|
|
const option = {
|
|||
|
|
backgroundColor: 'transparent',
|
|||
|
|
tooltip: {
|
|||
|
|
trigger: 'axis',
|
|||
|
|
axisPointer: {
|
|||
|
|
type: 'shadow'
|
|||
|
|
},
|
|||
|
|
formatter: function(params) {
|
|||
|
|
let result = params[0].name + '<br/>';
|
|||
|
|
params.forEach(param => {
|
|||
|
|
result += param.marker + param.seriesName + ': ' + param.value + '万<br/>';
|
|||
|
|
});
|
|||
|
|
return result;
|
|||
|
|
},
|
|||
|
|
backgroundColor: 'rgba(0, 0, 0, 0.9)',
|
|||
|
|
borderColor: '#ff6600',
|
|||
|
|
borderWidth: 1,
|
|||
|
|
textStyle: {
|
|||
|
|
color: '#fff',
|
|||
|
|
fontSize: 12
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
grid: isMobile ? {
|
|||
|
|
left: '5%',
|
|||
|
|
right: '5%',
|
|||
|
|
bottom: '15%',
|
|||
|
|
top: '20%',
|
|||
|
|
containLabel: true
|
|||
|
|
} : {
|
|||
|
|
left: '15%',
|
|||
|
|
right: '10%',
|
|||
|
|
bottom: '10%',
|
|||
|
|
top: '20%',
|
|||
|
|
containLabel: true
|
|||
|
|
},
|
|||
|
|
legend: {
|
|||
|
|
data: ['目标销售额', '实际销售额'],
|
|||
|
|
textStyle: {
|
|||
|
|
color: '#fff',
|
|||
|
|
fontSize: 12
|
|||
|
|
},
|
|||
|
|
top: '2%'
|
|||
|
|
},
|
|||
|
|
xAxis: isMobile ? {
|
|||
|
|
type: 'category',
|
|||
|
|
data: names,
|
|||
|
|
name: '小组',
|
|||
|
|
nameTextStyle: {
|
|||
|
|
color: '#fff'
|
|||
|
|
},
|
|||
|
|
axisLabel: {
|
|||
|
|
color: '#fff',
|
|||
|
|
fontSize: 10,
|
|||
|
|
interval: 0,
|
|||
|
|
rotate: 30
|
|||
|
|
},
|
|||
|
|
axisLine: {
|
|||
|
|
lineStyle: {
|
|||
|
|
color: 'rgba(255, 102, 0, 0.5)'
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
} : {
|
|||
|
|
type: 'value',
|
|||
|
|
name: '万元',
|
|||
|
|
nameTextStyle: {
|
|||
|
|
color: '#fff'
|
|||
|
|
},
|
|||
|
|
axisLabel: {
|
|||
|
|
color: '#fff',
|
|||
|
|
fontSize: 11,
|
|||
|
|
formatter: function(value) {
|
|||
|
|
return value * 100;
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
axisLine: {
|
|||
|
|
lineStyle: {
|
|||
|
|
color: 'rgba(255, 102, 0, 0.5)'
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
splitLine: {
|
|||
|
|
lineStyle: {
|
|||
|
|
color: 'rgba(255, 255, 255, 0.1)'
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
yAxis: isMobile ? {
|
|||
|
|
type: 'value',
|
|||
|
|
name: '万',
|
|||
|
|
nameTextStyle: {
|
|||
|
|
color: '#fff'
|
|||
|
|
},
|
|||
|
|
axisLabel: {
|
|||
|
|
color: '#fff',
|
|||
|
|
fontSize: 11
|
|||
|
|
},
|
|||
|
|
axisLine: {
|
|||
|
|
lineStyle: {
|
|||
|
|
color: 'rgba(255, 102, 0, 0.5)'
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
splitLine: {
|
|||
|
|
lineStyle: {
|
|||
|
|
color: 'rgba(255, 255, 255, 0.1)'
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
} : {
|
|||
|
|
type: 'category',
|
|||
|
|
data: names,
|
|||
|
|
axisLabel: {
|
|||
|
|
color: '#fff',
|
|||
|
|
fontSize: 11
|
|||
|
|
},
|
|||
|
|
axisLine: {
|
|||
|
|
lineStyle: {
|
|||
|
|
color: 'rgba(255, 102, 0, 0.5)'
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
series: [
|
|||
|
|
{
|
|||
|
|
name: '目标销售额',
|
|||
|
|
type: 'bar',
|
|||
|
|
data: targets,
|
|||
|
|
itemStyle: {
|
|||
|
|
color: new echarts.graphic.LinearGradient(isMobile ? 0 : 0, isMobile ? 1 : 0, isMobile ? 0 : 1, isMobile ? 0 : 0, [
|
|||
|
|
{ offset: 0, color: '#F77436' },
|
|||
|
|
{ offset: 1, color: '#F77436' }
|
|||
|
|
]),
|
|||
|
|
borderRadius: isMobile ? [4, 4, 0, 0] : [0, 4, 4, 0]
|
|||
|
|
},
|
|||
|
|
barMaxWidth: 15,
|
|||
|
|
emphasis: {
|
|||
|
|
itemStyle: {
|
|||
|
|
shadowBlur: 10,
|
|||
|
|
shadowOffsetX: 0,
|
|||
|
|
shadowColor: 'rgba(102, 102, 102, 0.8)'
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
name: '实际销售额',
|
|||
|
|
type: 'bar',
|
|||
|
|
data: actuals,
|
|||
|
|
itemStyle: {
|
|||
|
|
color: new echarts.graphic.LinearGradient(isMobile ? 0 : 0, isMobile ? 1 : 0, isMobile ? 0 : 1, isMobile ? 0 : 0, [
|
|||
|
|
{ offset: 0, color: '#65491D' },
|
|||
|
|
{ offset: 1, color: '#65491D' }
|
|||
|
|
]),
|
|||
|
|
borderRadius: isMobile ? [4, 4, 0, 0] : [0, 4, 4, 0]
|
|||
|
|
},
|
|||
|
|
barMaxWidth: 15,
|
|||
|
|
emphasis: {
|
|||
|
|
itemStyle: {
|
|||
|
|
shadowBlur: 10,
|
|||
|
|
shadowOffsetX: 0,
|
|||
|
|
shadowColor: 'rgba(255, 102, 0, 0.8)'
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
]
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
chart.setOption(option);
|
|||
|
|
return chart;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 渲染个人图表
|
|||
|
|
function renderPersonChart() {
|
|||
|
|
const chartDom = document.getElementById('personChart');
|
|||
|
|
const chart = echarts.init(chartDom);
|
|||
|
|
const names = personData.map(item => item.name);
|
|||
|
|
const actuals = personData.map(item => item.actual / 100000000);
|
|||
|
|
const targets = personData.map(item => item.target / 100000000);
|
|||
|
|
|
|||
|
|
// 判断是否为移动端
|
|||
|
|
const isMobile = window.innerWidth <= MOBILE_BREAKPOINT;
|
|||
|
|
|
|||
|
|
const option = {
|
|||
|
|
backgroundColor: 'transparent',
|
|||
|
|
tooltip: {
|
|||
|
|
trigger: 'axis',
|
|||
|
|
axisPointer: {
|
|||
|
|
type: 'shadow'
|
|||
|
|
},
|
|||
|
|
formatter: function(params) {
|
|||
|
|
let result = params[0].name + '<br/>';
|
|||
|
|
params.forEach(param => {
|
|||
|
|
result += param.marker + param.seriesName + ': ' + param.value + '万<br/>';
|
|||
|
|
});
|
|||
|
|
return result;
|
|||
|
|
},
|
|||
|
|
backgroundColor: 'rgba(0, 0, 0, 0.9)',
|
|||
|
|
borderColor: '#ff6600',
|
|||
|
|
borderWidth: 1,
|
|||
|
|
textStyle: {
|
|||
|
|
color: '#fff',
|
|||
|
|
fontSize: 12
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
grid: isMobile ? {
|
|||
|
|
left: '10%',
|
|||
|
|
right: '5%',
|
|||
|
|
bottom: '15%',
|
|||
|
|
top: '20%',
|
|||
|
|
containLabel: true
|
|||
|
|
} : {
|
|||
|
|
left: '15%',
|
|||
|
|
right: '10%',
|
|||
|
|
bottom: '10%',
|
|||
|
|
top: '20%',
|
|||
|
|
containLabel: true
|
|||
|
|
},
|
|||
|
|
legend: {
|
|||
|
|
data: ['目标销售额', '实际销售额'],
|
|||
|
|
textStyle: {
|
|||
|
|
color: '#fff',
|
|||
|
|
fontSize: 12
|
|||
|
|
},
|
|||
|
|
top: '2%'
|
|||
|
|
},
|
|||
|
|
xAxis: isMobile ? {
|
|||
|
|
type: 'value',
|
|||
|
|
name: '万',
|
|||
|
|
nameLocation: 'end',
|
|||
|
|
nameGap: 5,
|
|||
|
|
nameTextStyle: {
|
|||
|
|
color: '#fff',
|
|||
|
|
fontSize: 10
|
|||
|
|
},
|
|||
|
|
axisLabel: {
|
|||
|
|
color: '#fff',
|
|||
|
|
fontSize: 10
|
|||
|
|
},
|
|||
|
|
axisLine: {
|
|||
|
|
lineStyle: {
|
|||
|
|
color: 'rgba(255, 102, 0, 0.5)'
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
splitLine: {
|
|||
|
|
lineStyle: {
|
|||
|
|
color: 'rgba(255, 255, 255, 0.1)'
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
} : {
|
|||
|
|
type: 'value',
|
|||
|
|
name: '万',
|
|||
|
|
nameTextStyle: {
|
|||
|
|
color: '#fff'
|
|||
|
|
},
|
|||
|
|
axisLabel: {
|
|||
|
|
color: '#fff',
|
|||
|
|
fontSize: 11,
|
|||
|
|
formatter: function(value) {
|
|||
|
|
return value * 100;
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
axisLine: {
|
|||
|
|
lineStyle: {
|
|||
|
|
color: 'rgba(255, 102, 0, 0.5)'
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
splitLine: {
|
|||
|
|
lineStyle: {
|
|||
|
|
color: 'rgba(255, 255, 255, 0.1)'
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
yAxis: isMobile ? {
|
|||
|
|
type: 'category',
|
|||
|
|
data: names,
|
|||
|
|
axisLabel: {
|
|||
|
|
color: '#fff',
|
|||
|
|
fontSize: 10
|
|||
|
|
},
|
|||
|
|
axisLine: {
|
|||
|
|
lineStyle: {
|
|||
|
|
color: 'rgba(255, 102, 0, 0.5)'
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
} : {
|
|||
|
|
type: 'category',
|
|||
|
|
data: names,
|
|||
|
|
axisLabel: {
|
|||
|
|
color: '#fff',
|
|||
|
|
fontSize: 11
|
|||
|
|
},
|
|||
|
|
axisLine: {
|
|||
|
|
lineStyle: {
|
|||
|
|
color: 'rgba(255, 102, 0, 0.5)'
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
series: [
|
|||
|
|
{
|
|||
|
|
name: '目标销售额',
|
|||
|
|
type: 'bar',
|
|||
|
|
data: targets,
|
|||
|
|
itemStyle: {
|
|||
|
|
color: new echarts.graphic.LinearGradient(0, 0, 1, 0, [
|
|||
|
|
{ offset: 0, color: '#F77436' },
|
|||
|
|
{ offset: 1, color: '#F77436' }
|
|||
|
|
]),
|
|||
|
|
borderRadius: [0, 4, 4, 0]
|
|||
|
|
},
|
|||
|
|
barMaxWidth: 15,
|
|||
|
|
emphasis: {
|
|||
|
|
itemStyle: {
|
|||
|
|
shadowBlur: 10,
|
|||
|
|
shadowOffsetX: 0,
|
|||
|
|
shadowColor: 'rgba(102, 102, 102, 0.8)'
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
name: '实际销售额',
|
|||
|
|
type: 'bar',
|
|||
|
|
data: actuals,
|
|||
|
|
itemStyle: {
|
|||
|
|
color: new echarts.graphic.LinearGradient(0, 0, 1, 0, [
|
|||
|
|
{ offset: 0, color: '#65491D' },
|
|||
|
|
{ offset: 1, color: '#65491D' }
|
|||
|
|
]),
|
|||
|
|
borderRadius: [0, 4, 4, 0]
|
|||
|
|
},
|
|||
|
|
barMaxWidth: 15,
|
|||
|
|
emphasis: {
|
|||
|
|
itemStyle: {
|
|||
|
|
shadowBlur: 10,
|
|||
|
|
shadowOffsetX: 0,
|
|||
|
|
shadowColor: 'rgba(255, 102, 0, 0.8)'
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
]
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
chart.setOption(option);
|
|||
|
|
return chart;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 初始化
|
|||
|
|
document.addEventListener('DOMContentLoaded', async () => {
|
|||
|
|
// PC端等比例缩放
|
|||
|
|
setScale();
|
|||
|
|
|
|||
|
|
// 获取年度数据并更新顶部显示
|
|||
|
|
yearData = await fetchYearData();
|
|||
|
|
const progressRate = updateTopData();
|
|||
|
|
|
|||
|
|
// 渲染排名列表
|
|||
|
|
renderGroupRankList();
|
|||
|
|
renderPersonRankList();
|
|||
|
|
|
|||
|
|
// 初始化图表
|
|||
|
|
let groupChart = null;
|
|||
|
|
let personChart = null;
|
|||
|
|
let resizeTimer = null;
|
|||
|
|
|
|||
|
|
function initCharts() {
|
|||
|
|
// 销毁旧图表
|
|||
|
|
if (groupChart) {
|
|||
|
|
groupChart.dispose();
|
|||
|
|
}
|
|||
|
|
if (personChart) {
|
|||
|
|
personChart.dispose();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 重新渲染图表
|
|||
|
|
groupChart = renderGroupChart();
|
|||
|
|
personChart = renderPersonChart();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
setTimeout(() => {
|
|||
|
|
initCharts();
|
|||
|
|
|
|||
|
|
// 响应式调整 - 使用防抖优化性能
|
|||
|
|
window.addEventListener('resize', () => {
|
|||
|
|
// 立即更新缩放(不防抖,保证视觉效果流畅)
|
|||
|
|
setScale();
|
|||
|
|
|
|||
|
|
// 图表重新渲染使用防抖
|
|||
|
|
clearTimeout(resizeTimer);
|
|||
|
|
resizeTimer = setTimeout(() => {
|
|||
|
|
initCharts();
|
|||
|
|
}, RESIZE_DEBOUNCE_TIME);
|
|||
|
|
});
|
|||
|
|
}, CHART_INIT_DELAY);
|
|||
|
|
|
|||
|
|
// 初始化季度和周数显示
|
|||
|
|
function initQuarters() {
|
|||
|
|
// 获取当前周数(1-52)
|
|||
|
|
const now = new Date();
|
|||
|
|
const start = new Date(now.getFullYear(), 0, 1);
|
|||
|
|
const diff = now - start;
|
|||
|
|
const currentWeek = Math.floor(diff / (7 * 24 * 60 * 60 * 1000)) + 1;
|
|||
|
|
|
|||
|
|
// 定义每个季度的周数范围
|
|||
|
|
const quarters = [
|
|||
|
|
{ id: 'q1Weeks', start: 1, end: 13 },
|
|||
|
|
{ id: 'q2Weeks', start: 14, end: 26 },
|
|||
|
|
{ id: 'q3Weeks', start: 27, end: 39 },
|
|||
|
|
{ id: 'q4Weeks', start: 40, end: 52 }
|
|||
|
|
];
|
|||
|
|
|
|||
|
|
// 为每个季度生成周数圆圈
|
|||
|
|
quarters.forEach(quarter => {
|
|||
|
|
const container = document.getElementById(quarter.id);
|
|||
|
|
for (let i = quarter.start; i <= quarter.end; i++) {
|
|||
|
|
const weekCircle = document.createElement('div');
|
|||
|
|
weekCircle.className = 'week-circle';
|
|||
|
|
weekCircle.textContent = i;
|
|||
|
|
|
|||
|
|
// 添加样式类
|
|||
|
|
if (i < currentWeek) {
|
|||
|
|
weekCircle.classList.add('passed');
|
|||
|
|
} else if (i === currentWeek) {
|
|||
|
|
weekCircle.classList.add('current');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 添加悬停提示
|
|||
|
|
weekCircle.title = `第${i}周`;
|
|||
|
|
|
|||
|
|
container.appendChild(weekCircle);
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
initQuarters();
|
|||
|
|
|
|||
|
|
// 延迟启动进度条动画,使用动态计算的完成率
|
|||
|
|
setTimeout(() => {
|
|||
|
|
startProgressAnimation(progressRate);
|
|||
|
|
}, PROGRESS_ANIM_DELAY);
|
|||
|
|
|
|||
|
|
// 可选:定时刷新数据(每30秒)
|
|||
|
|
// setInterval(async () => {
|
|||
|
|
// yearData = await fetchYearData();
|
|||
|
|
// updateTopData();
|
|||
|
|
// }, 30000);
|
|||
|
|
});
|