497 lines
19 KiB
HTML
497 lines
19 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="zh-CN">
|
||
|
||
<head>
|
||
<meta charset="UTF-8" />
|
||
<script>
|
||
const cssVer = Date.now();
|
||
// 直接document.write输出带版本的link,写在head内,不会闪屏
|
||
document.write(`
|
||
<link rel="stylesheet" href="./css/icon.css">
|
||
<link rel="stylesheet" href="./css/index.css?v=${cssVer}">
|
||
<link rel="stylesheet" href="./css/public.css">
|
||
`);
|
||
</script>
|
||
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
|
||
<meta http-equiv="Pragma" content="no-cache" />
|
||
<meta http-equiv="Expires" content="0" />
|
||
<meta name="viewport"
|
||
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1, user-scalable=no" />
|
||
<title>区域检验数据中心</title>
|
||
<!-- css/js增加版本号防缓存 -->
|
||
<!-- <link rel="stylesheet" href="./css/icon.css" />
|
||
<link rel="stylesheet" href="./css/index.css" />
|
||
<link rel="stylesheet" href="./css/public.css" /> -->
|
||
</head>
|
||
|
||
<body>
|
||
<div class="bg">
|
||
<div class="title">区域检验数据中心</div>
|
||
<div class="container">
|
||
<div class="leftMain">
|
||
<div class="leftMain_top">
|
||
<div class="leftMain_topIn">
|
||
<div class="title_nums">当月累计数据</div>
|
||
<ul id="sourceMonthWrap"></ul>
|
||
<div class="title_nums">当年累计数据</div>
|
||
<ul id="sourceYearWrap"></ul>
|
||
</div>
|
||
</div>
|
||
<div class="leftMain_middle">
|
||
<div class="leftMain_middle_left">
|
||
<div class="leftMain_middle_leftIn">
|
||
<h3>委托机构当月送检标本数量</h3>
|
||
<div class="biaoge">
|
||
<div id="chartmain" style="width:100%;height:100%;"></div>
|
||
</div>
|
||
<span class="border_bg_leftTop"></span>
|
||
<span class="border_bg_rightTop"></span>
|
||
<span class="border_bg_leftBottom"></span>
|
||
<span class="border_bg_rightBottom"></span>
|
||
</div>
|
||
<div class="leftMain_middle_leftIn leftMain_middle_leftIn_zhe">
|
||
<h3>当年送检标本数量趋势图</h3>
|
||
<div class="biaoge">
|
||
<div id="chartmain_zhe" style="width:100%;height:100%;"></div>
|
||
</div>
|
||
<span class="border_bg_leftTop"></span>
|
||
<span class="border_bg_rightTop"></span>
|
||
<span class="border_bg_leftBottom"></span>
|
||
<span class="border_bg_rightBottom"></span>
|
||
</div>
|
||
</div>
|
||
<div class="leftMain_middle_right">
|
||
<div class="leftMain_middle_rightIn">
|
||
<h3>当月标本全流程监控情况</h3>
|
||
<div class="biaoge biaoge_pai">
|
||
<div class="biaoge_paiIn">
|
||
<ul id="dayCountsWrap"></ul>
|
||
</div>
|
||
</div>
|
||
<span class="border_bg_leftTop"></span>
|
||
<span class="border_bg_rightTop"></span>
|
||
<span class="border_bg_leftBottom"></span>
|
||
<span class="border_bg_rightBottom"></span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="rightMain">
|
||
<div class="rightMain_topIn">
|
||
<h3>委托机构当月送检标本占比</h3>
|
||
<div class="proportion">
|
||
<ul id="listDataWrap"></ul>
|
||
</div>
|
||
<span class="border_bg_leftTop"></span>
|
||
<span class="border_bg_rightTop"></span>
|
||
<span class="border_bg_leftBottom"></span>
|
||
<span class="border_bg_rightBottom"></span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<script src="./js/echarts.min.js"></script>
|
||
<script>
|
||
// 全局变量
|
||
let echarts1Instance = null;
|
||
let echarts3Instance = null;
|
||
// 基础数据模板
|
||
const sourceMonth = [
|
||
{ name: '区域机构', num: 0, unit: '家', pre: 'jgs' },
|
||
{ name: '检验人数', num: 0, unit: '人', pre: 'rs' },
|
||
{ name: '检验管数', num: 0, unit: '个', pre: 'gs' },
|
||
{ name: '检验报告数', num: 0, unit: '份', pre: 'bgs' },
|
||
];
|
||
const sourceYear = [
|
||
{ name: '区域机构', num: 0, unit: '家', pre: 'jgs' },
|
||
{ name: '检验人数', num: 0, unit: '人', pre: 'rs' },
|
||
{ name: '检验管数', num: 0, unit: '个', pre: 'gs' },
|
||
{ name: '检验报告数', num: 0, unit: '份', pre: 'bgs' },
|
||
];
|
||
let dayCounts = [
|
||
{ dict_name: '检验申请', sl: 0, percent: '0%', flag: false },
|
||
{ dict_name: '标本采集', sl: 0, percent: '0%', remain: 0, remainPer: '0%', flag: true },
|
||
{ dict_name: '标本送检', sl: 0, percent: '0%', remain: 0, remainPer: '0%', flag: true },
|
||
{ dict_name: '标本签收', sl: 0, percent: '0%', remain: 0, remainPer: '0%', flag: true },
|
||
{ dict_name: '上机检测', sl: 0, percent: '0%', remain: 0, remainPer: '0%', flag: true },
|
||
{ dict_name: '结果审核', sl: 0, percent: '0%', remain: 0, remainPer: '0%', flag: true },
|
||
{ dict_name: '申请作废', sl: 0, percent: '0%', flag: false },
|
||
{ dict_name: '标本退回', sl: 0, percent: '0%', flag: false },
|
||
|
||
];
|
||
let listData = [];
|
||
|
||
|
||
// 折线图轮播变量
|
||
let lineTipTimer = null;
|
||
let lineCurrentIndex = -1;
|
||
const LINE_TIP_INTERVAL = 2500;
|
||
function startLineTipLoop() {
|
||
if (lineTipTimer) clearInterval(lineTipTimer);
|
||
lineCurrentIndex = -1;
|
||
lineTipTimer = setInterval(() => {
|
||
if (!echarts3Instance) return;
|
||
const opt = echarts3Instance.getOption();
|
||
const dataLen = opt.xAxis[0].data.length;
|
||
if (dataLen === 0) return;
|
||
lineCurrentIndex = (lineCurrentIndex + 1) % dataLen;
|
||
opt.series.forEach((_, idx) => echarts3Instance.dispatchAction({ type: 'downplay', seriesIndex: idx }));
|
||
opt.series.forEach((_, idx) => echarts3Instance.dispatchAction({ type: 'highlight', seriesIndex: idx, dataIndex: lineCurrentIndex }));
|
||
echarts3Instance.dispatchAction({ type: 'showTip', seriesIndex: 0, dataIndex: lineCurrentIndex });
|
||
}, LINE_TIP_INTERVAL);
|
||
}
|
||
function stopLineTipLoop() {
|
||
if (lineTipTimer) clearInterval(lineTipTimer);
|
||
lineTipTimer = null;
|
||
}
|
||
|
||
// 渲染列表函数
|
||
function renderNumList(containerId, arr) {
|
||
const wrap = document.getElementById(containerId);
|
||
wrap.innerHTML = '';
|
||
arr.forEach(item => {
|
||
const li = document.createElement('li');
|
||
li.innerHTML = `
|
||
<div class="liIn">
|
||
<div class="nums_box">
|
||
<h3>${item.name}</h3>
|
||
<p class="shu"><span class="shu1">${item.num}</span><i>${item.unit}</i></p>
|
||
</div>
|
||
<span class="border_bg_leftTop"></span>
|
||
<span class="border_bg_rightTop"></span>
|
||
<span class="border_bg_leftBottom"></span>
|
||
<span class="border_bg_rightBottom"></span>
|
||
</div>
|
||
`;
|
||
wrap.appendChild(li);
|
||
})
|
||
}
|
||
function renderDayCounts() {
|
||
const wrap = document.getElementById('dayCountsWrap');
|
||
wrap.innerHTML = '';
|
||
dayCounts.forEach(item => {
|
||
const li = document.createElement('li');
|
||
li.innerHTML = `
|
||
<div class="liIn">
|
||
<div class="liIn_left"><span class="zi">${item.dict_name}</span></div>
|
||
<div class="liIn_line">
|
||
<div class="line_lineIn" style="width:${item.percent}">
|
||
<div class="per">${item.percent}</div>
|
||
</div>
|
||
</div>
|
||
<p class="num">${item.sl}</p>
|
||
<div class="clear"></div>
|
||
</div>
|
||
<div class="liIn" style="display:${item.flag ? 'flex' : 'none'}">
|
||
<div class="liIn_left"><span class="zi"> </span></div>
|
||
<div class="liIn_line">
|
||
<div class="line_lineIn_sy" style="width:${item.remainPer}">
|
||
<div class="per">剩余${item.remain}</div>
|
||
</div>
|
||
</div>
|
||
<p class="num"> </p>
|
||
<div class="clear"></div>
|
||
</div>
|
||
`;
|
||
wrap.appendChild(li);
|
||
})
|
||
}
|
||
function renderListData() {
|
||
const wrap = document.getElementById('listDataWrap');
|
||
wrap.innerHTML = '';
|
||
listData.forEach(item => {
|
||
const li = document.createElement('li');
|
||
li.innerHTML = `
|
||
<div class="liIn">
|
||
<div class="liIn_left"><span class="zi" title="${item.groupname}">${item.groupname}</span></div>
|
||
<div class="liIn_line">
|
||
<div class="line_lineIn" style="width:${item.percentage}">
|
||
<div class="per">${item.percentage}</div>
|
||
</div>
|
||
</div>
|
||
<p class="num">${item.groupsum}</p>
|
||
<div class="clear"></div>
|
||
</div>
|
||
`;
|
||
wrap.appendChild(li);
|
||
})
|
||
}
|
||
|
||
// ========= 只初始化一次柱状图,不再每次刷新销毁重建 =========
|
||
function initBarChart() {
|
||
const dom = document.getElementById('chartmain');
|
||
echarts1Instance = echarts.init(dom, null, { devicePixelRatio: window.devicePixelRatio });
|
||
const option = {
|
||
title: { text: '' },
|
||
grid: { x: 30, y: 30, x2: 10, y2: 20 },
|
||
tooltip: {
|
||
trigger: 'axis',
|
||
show: false,
|
||
backgroundColor: 'rgba(0,18,36,0.9)',
|
||
textStyle: { color: '#fff', fontSize: 14 },
|
||
padding: [12, 16],
|
||
borderWidth: 1,
|
||
borderColor: '#0efdff'
|
||
},
|
||
label: {
|
||
show: true,
|
||
color: '#fff',
|
||
fontSize: 16,
|
||
fontWeight: 700,
|
||
position: 'top',
|
||
//没有数据 0的情况不显示
|
||
formatter: function (params) {
|
||
if (params.value == 0) {
|
||
return '';
|
||
}
|
||
return params.value;
|
||
}
|
||
},
|
||
xAxis: {
|
||
data: [],
|
||
axisLabel: { interval: 0, color: '#fff', fontSize: 12 },
|
||
axisTick: { show: false },
|
||
axisLine: { show: true, symbol: ['none', 'arrow'], symbolOffset: 12, lineStyle: { color: '#fff' } },
|
||
z: 10
|
||
},
|
||
yAxis: {
|
||
type: 'value', name: '单位:个', minInterval: 1,
|
||
axisLine: { show: true, symbol: ['none', 'arrow'], symbolOffset: 12, lineStyle: { color: '#fff' } },
|
||
axisTick: { show: false },
|
||
axisLabel: { color: '#fff', fontSize: 12 }
|
||
},
|
||
series: [{
|
||
type: 'bar',
|
||
itemStyle: {
|
||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1,
|
||
[{ offset: 0, color: '#0efdff' }, { offset: 0.5, color: '#188df0' }, { offset: 1, color: '#188df0' }]
|
||
)
|
||
},
|
||
emphasis: {
|
||
itemStyle: {
|
||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1,
|
||
[{ offset: 0, color: '#2378f7' }, { offset: 0.7, color: '#2378f7' }, { offset: 1, color: '#0efdff' }]
|
||
)
|
||
}
|
||
},
|
||
data: []
|
||
}]
|
||
};
|
||
echarts1Instance.setOption(option);
|
||
}
|
||
// 仅更新柱状图数据,不销毁实例
|
||
function updateBarData(data) {
|
||
const dataAxis = data.map(item => item.daynum);
|
||
const dataVal = data.map(item => item.sl);
|
||
echarts1Instance.setOption({ xAxis: { data: dataAxis }, series: [{ data: dataVal }] });
|
||
}
|
||
// 基准:1920宽度下字号12px,最小字号8px防止过小
|
||
function calcLegendFont(container, baseFont = 12, baseWidth = 1920, minFont = 8) {
|
||
const width = container.clientWidth;
|
||
const scale = width / baseWidth;
|
||
return Math.max(minFont, Math.round(baseFont * scale));
|
||
}
|
||
// ========= 只初始化一次折线图,不再每次刷新销毁重建 =========
|
||
function initLineChart() {
|
||
const dom = document.getElementById('chartmain_zhe');
|
||
echarts3Instance = echarts.init(dom, null, { devicePixelRatio: window.devicePixelRatio });
|
||
const colors = ['#ff7f50', '#87cefa', '#da70d6', '#32cd32', '#6495ed', '#ff69b0', '#ba55d3', '#cd5c5c', '#ffa500', '#40e0d0', '#1e90ff', '#ff6347', '#7b68ee', '#d0648a', '#ffd700', '#6b8e23', '#4ea397', '#3cb371', '#b8860b'];
|
||
const legendFont = calcLegendFont(dom, 35, 1920);
|
||
// 图例方块也同步缩放,1920下itemWidth/itemHeight=15
|
||
const itemSize = calcLegendFont(dom, 35, 1920);
|
||
const option = {
|
||
title: { text: '' },
|
||
tooltip: {
|
||
trigger: 'axis',
|
||
confine: false, // 限制在图表内
|
||
backgroundColor: 'rgba(0,18,36,0.9)',
|
||
textStyle: { color: '#fff', fontSize: 14 },
|
||
padding: [12, 16],
|
||
borderWidth: 1,
|
||
borderColor: '#0efdff',
|
||
},
|
||
legend: {
|
||
// type: 'scroll', textStyle: { color: '#fff', fontSize: 12, lineHeight: 15, },
|
||
// // icon: 'circle',
|
||
// pageIcon: 'circle', pageIconColor: '#0efdff', pageIconSize: 12,
|
||
// pageIconInactiveColor: '#666', pageTextStyle: { color: '#0efdff' },
|
||
type: 'plain',
|
||
orient: 'horizontal', // ✅必须开启水平排列,超出width自动换行
|
||
left: 10,
|
||
right: 10,
|
||
top: 0,
|
||
width: 'auto', // 或者写固定像素,例如 width:450;left+right限制可用宽度
|
||
itemGap: 2, // 图例项水平间距
|
||
itemWidth: itemSize,
|
||
itemHeight: itemSize,
|
||
textStyle: { color: '#fff', fontSize: legendFont, fontWeight: 700 },
|
||
data: []
|
||
},
|
||
grid: { x: 30, y: 80, x2: 20, y2: 20 },
|
||
xAxis: {
|
||
type: 'category', boundaryGap: false, data: [],
|
||
axisLabel: { interval: 0, color: '#fff', fontSize: 12 },
|
||
axisTick: { show: false },
|
||
axisLine: { show: true, symbol: ['none', 'arrow'], symbolOffset: 12, lineStyle: { color: '#fff' } }
|
||
},
|
||
yAxis: {
|
||
type: 'value', name: '单位:个', minInterval: 1,
|
||
axisLine: { show: true, symbol: ['none', 'arrow'], symbolOffset: 12, lineStyle: { color: '#fff' } },
|
||
axisTick: { show: false },
|
||
axisLabel: { color: '#fff', fontSize: 12 }
|
||
},
|
||
series: []
|
||
}
|
||
echarts3Instance.setOption(option);
|
||
// dom.addEventListener('mouseenter', stopLineTipLoop);
|
||
// dom.addEventListener('mouseleave', startLineTipLoop);
|
||
// startLineTipLoop();
|
||
|
||
}
|
||
|
||
// 仅更新折线数据,不销毁实例
|
||
function updateLineData(list) {
|
||
const xData = list[0].dayline.map(item => item.month + '月');
|
||
const legendData = list.map(item => item.groupname);
|
||
const colors = ['#D62728', '#FF7F0E', '#FFBB78', '#2CA02C', '#17BECF', '#1F77B4', '#9467BD', '#8C564B', '#E377C2', '#BCBD22', '#393B79', '#637939',
|
||
'#8C6D31', '#843C39', '#5254A3', '#BD9E39', '#3182BD', '#E6550D', '#7F7F7F'];
|
||
const series = list.map((item, i) => ({
|
||
name: item.groupname,
|
||
type: 'line',
|
||
symbol: 'circle',
|
||
symbolSize: 7,
|
||
// 无数据转为 null,线条自动断开
|
||
data: item.dayline.map(v => {
|
||
if (!v.sl || v.sl <= 0) {
|
||
return null
|
||
}
|
||
return v.sl
|
||
}),
|
||
connectNulls: false, // 遇到null不连线
|
||
itemStyle: { color: colors[i] },
|
||
lineStyle: { color: colors[i], width: 2 }
|
||
}))
|
||
echarts3Instance.setOption({
|
||
xAxis: { data: xData },
|
||
legend: { data: legendData },
|
||
series: series
|
||
})
|
||
}
|
||
|
||
// 窗口自适应
|
||
function handleResize() {
|
||
if (echarts1Instance) echarts1Instance.resize();
|
||
if (echarts3Instance) echarts3Instance.resize();
|
||
}
|
||
|
||
// 原生Ajax请求封装
|
||
function httpGet(url, callback) {
|
||
const xhr = new XMLHttpRequest();
|
||
xhr.open('GET', url, true);
|
||
xhr.onload = function () {
|
||
if (xhr.status === 200) {
|
||
const res = JSON.parse(xhr.responseText);
|
||
callback(res);
|
||
}
|
||
}
|
||
xhr.onerror = function () {
|
||
console.log('接口请求失败:' + url);
|
||
}
|
||
xhr.send();
|
||
}
|
||
|
||
// 初始化数据
|
||
function init() {
|
||
renderNumList('sourceMonthWrap', sourceMonth);
|
||
renderNumList('sourceYearWrap', sourceYear);
|
||
// 一次性初始化两张图表,后续只更新数据
|
||
initBarChart();
|
||
initLineChart();
|
||
getDataInfo();
|
||
}
|
||
|
||
// 获取全部统计数据(30秒只更新数据,不重建图表)
|
||
//http://47.97.125.165:8905
|
||
function getDataInfo() {
|
||
const apiUrl = '/api/transitstat/getallcount';
|
||
httpGet(apiUrl, function (res) {
|
||
const data = res.data;
|
||
// 赋值当月/当年数字
|
||
sourceMonth.forEach(item => item.num = data.regStatAllMonth[item.pre]);
|
||
sourceYear.forEach(item => item.num = data.regStatAllYear[item.pre]);
|
||
listData = data.regStatHospMonth;
|
||
|
||
// 匹配流程数量&百分比
|
||
const baseNum = data.regStatStatusTotal['检验申请'] || 0
|
||
|
||
dayCounts.forEach(item => {
|
||
item.sl = res.data.regStatStatusTotal[item.dict_name] || 0
|
||
item.percent = (item.sl / baseNum * 100).toFixed(2) + '%'
|
||
if (item.percent.endsWith('.00%')) {
|
||
item.percent = item.percent.replace('.00%', '%')
|
||
}
|
||
})
|
||
const flowOrder = dayCounts.map(item => item.dict_name).filter(name => name !== '申请作废' && name !== '标本退回');
|
||
//初始化全部remain=0
|
||
dayCounts.forEach(d => d.remain = 0);
|
||
for (let i = 1; i < flowOrder.length; i++) {
|
||
const prevName = flowOrder[i - 1];
|
||
const currName = flowOrder[i];
|
||
const prevItem = dayCounts.find(x => x.dict_name === prevName);
|
||
const currItem = dayCounts.find(x => x.dict_name === currName);
|
||
if (prevItem && currItem) {
|
||
currItem.remain = prevItem.sl - currItem.sl;
|
||
if (currItem.remain < 0) currItem.remain = 0;
|
||
|
||
if (prevItem.sl > 0) {
|
||
currItem.remainPer = (currItem.remain / prevItem.sl * 100).toFixed(2) + '%';
|
||
} else {
|
||
currItem.remainPer = "0%";
|
||
}
|
||
}
|
||
}
|
||
// console.log('dayCounts==>', dayCounts);
|
||
// 刷新文字列表
|
||
renderNumList('sourceMonthWrap', sourceMonth);
|
||
renderNumList('sourceYearWrap', sourceYear);
|
||
renderDayCounts();
|
||
renderListData();
|
||
// 增量更新图表,不销毁画布(解决刷新卡顿)
|
||
updateBarData(data.regStatMonth);
|
||
updateLineData(data.regStatHospYear);
|
||
})
|
||
}
|
||
|
||
// setTimeout(function () {
|
||
// window.location.href = "index2.html";
|
||
// }, 60000);
|
||
let dataRefreshTimer = null;
|
||
// 页面初始化
|
||
window.onload = function () {
|
||
// 窗口/全屏监听
|
||
window.addEventListener('resize', handleResize);
|
||
document.addEventListener('fullscreenchange', handleResize);
|
||
document.addEventListener('webkitfullscreenchange', handleResize);
|
||
// 首次拉取数据
|
||
init();
|
||
// 定时刷新数据
|
||
dataRefreshTimer = setInterval(getDataInfo, 60000);
|
||
}
|
||
|
||
// 页面销毁释放资源
|
||
window.onbeforeunload = function () {
|
||
if (dataRefreshTimer) {
|
||
clearInterval(dataRefreshTimer);
|
||
dataRefreshTimer = null;
|
||
}
|
||
|
||
window.removeEventListener('resize', handleResize);
|
||
document.removeEventListener('fullscreenchange', handleResize);
|
||
document.removeEventListener('webkitfullscreenchange');
|
||
if (echarts1Instance) echarts1Instance.dispose();
|
||
if (echarts3Instance) echarts3Instance.dispose();
|
||
}
|
||
</script>
|
||
</body>
|
||
|
||
</html> |