TVscreen/QYJY/index.html
2026-09-18 15:38:25 +08:00

497 lines
19 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!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">&nbsp;</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">&nbsp;</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>