SGGL_JT/SUBQHSE/FineUIPro.Web/DataShow/kq.aspx

874 lines
28 KiB
Plaintext

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="kq.aspx.cs" Inherits="FineUIPro.Web.DataShow.kq" %>
<!DOCTYPE html>
<html lang="en">
<head runat="server">
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title></title>
</head>
<link rel="stylesheet" href="../res/css/kq.css?t=129873"/>
<script src="../res/lib/flex.js"></script>
<body>
<div id="app">
<header>
<div class="nav-box" style="padding-left: 0.3rem">
<div class="logo-img"></div>
<div class="navs" style="justify-content: flex-end">
</div>
</div>
<div class="logo-tit">QHSE系统实名制管理看板</div>
<div class="nav-box" style="padding-right: 0.3rem">
<div class="navs" style="justify-content: flex-start">
</div>
<div class="timer">
<p>
<span>{{timer.city}}</span><span>{{timer.time}}</span>
</p>
<p>
<span>{{timer.date}}</span><span>{{timer.week}}</span>
</p>
</div>
</div>
<div class="set">
<div class="iconfont icon-nav08" @click="onToggleFullscreen">全屏</div>
</div>
</header>
<div id="content">
<div>
<div id="tools" class="block" style="height: 100%">
<div class="block-tit">
<span>劳务人员工种结构 TOP10</span>
</div>
<div class="block-main flex-between">
<div class="top-row" v-for="(i,idx) in lw" ::key="idx">
<div class="top-row-num color-blue">{{idx|formatNumber}}</div>
<div class="top-row-r">
<div class="top-row-r-t">
<span class="color-blue">
{{i.name}}
</span
><span class="color-05937B">{{i.value}}个</span>
</div>
<div class="top-row-r-b">
<div
class="top-row-r-b-inner"
:style="{width: formatPercent(i.value)}">
</div>
</div>
</div>
</div>
</div>
</div>
<div id="tools" class="block" style="height: calc(100% - 0.25rem)">
<div class="block-tit">
<span>劳务人员</span>
</div>
<div class="block-main ry">
<div class="th">
<span>序号</span><span>头像</span>
<span>
姓名
</span
><span>工种</span><span>时间</span>
</div>
<div class="body">
<div class="tr" v-for="(item, index) in lwInOutData" :key="index">
<div>{{index + 1}}</div>
<div class="photo">
<img :src="getPhotoUrl(item.PhotoUrl)" :alt="item.PersonName"/>
</div>
<div>{{item.PersonName}}</div>
<div>{{item.WorkPostName}}</div>
<div>{{item.ChangeTime}}</div>
<div :class="item.IsIn === 'out' ? 'out' : 'in'">{{item.IsIn === 'out' ? '出' : '入'}}</div>
</div>
</div>
</div>
</div>
</div>
<div style="grid-template-rows: 65% 35%; position: relative;">
<div class="kq-box">
<div class="kx-item">
<div class="kx-item-tit">总在册</div>
<div class="kx-item-num" style="color: #00FFFF;">{{form.person.allNum}}</div>
</div>
<div class="kx-item">
<div class="kx-item-tit">当前在册</div>
<div class="kx-item-num" style="color: #51C6FF;">{{form.person.onDutyNum}}</div>
</div>
<div class="kx-item">
<div class="kx-item-tit">今日出勤</div>
<div class="kx-item-num" style="color: #12CDA2;">{{form.person.todayAttendanceNum}}</div>
</div>
<div class="kx-item">
<div class="kx-item-tit">今日出勤率</div>
<div class="kx-item-num" style="color: #FFB82B;">{{form.person.todayAttendanceRate}}</div>
</div>
</div>
<div id="chainmap"></div>
<div class="block4">
<div class="block4-tit">
<span>出勤动态曲线</span>
</div>
<div class="block4-main" id="ry"></div>
</div>
</div>
<div>
<div id="tools" class="block" style="height: 100%">
<div class="block-tit">
<span>考勤统计</span>
</div>
<div class="block-main kq">
<div class="th">
<span>序号</span><span>组织名称</span>
<span>
总在册
</span
><span>当前在册</span><span>今日出勤</span>
</div>
<div class="body">
<div class="tr" v-for="(item, index) in attendanceStats" :key="index">
<span>{{index + 1}}</span>
<span>{{item.projectName}}</span>
<span>{{item.allNum}}</span>
<span class="zc">{{item.onDutyNum}}</span>
<span>{{item.todayAttendanceNum}}</span>
</div>
</div>
</div>
</div>
<div id="tools" class="block" style="height: calc(100% - 0.25rem)">
<div class="block-tit">
<span> 劳务人员年龄性别结构</span>
</div>
<div class="block-main">
<div id="nlzb"></div>
<div class="bl">
<div id="man"></div>
<div class="bl-txt">
<span>性别</span>
<span>占比统计</span>
</div>
<div id="woman"></div>
</div>
</div>
</div>
</div>
</div>
</div>
</body>
</html>
<script src="../res/lib/china1.js"></script>
<script src="../res/lib/echarts.min.js"></script>
<script src="../res/lib/vue.min.js"></script>
<script src="../res/lib/jquery.js"></script>
<script>
new Vue({
el: "#app",
data: {
timer: {
date: "",
time: "",
city: "",
T: "15-26℃",
week: "",
},
chainaMap: null,
lw: [],
form:{
person:{
allNum: 0,//总在册
onDutyNum: 0,//总在场
todayAttendanceNum: 0,//今日出勤
todayAttendanceRate: 0,//今日出勤率
}
},
attendanceStats: [],
ageData: [],
genderData: [],
chinaMapData: [],
lwInOutData: [],
attendanceCurveData: {
managerData: [],
workerData: []
}
},
filters: {
formatNumber(value) {
return (value + 1).toFixed().padStart(2, "0")
},
},
mounted() {
this.$nextTick(() => {
this.startAutoScroll(".ry>.body", 6)
this.startAutoScroll(".kq>.body", 3)
this.initNlzb()
this.initMan("man")
this.initMan("woman", "女", 40)
// this.initChainaMap()
this.initRy([],[])
this.loadData()
})
setInterval(() => {
this.getLocation()
}, 1000)
},
methods: {
onToggleFullscreen() {
if (!document.fullscreenElement) {
document.documentElement.requestFullscreen().catch(() => {})
} else if (document.exitFullscreen) {
document.exitFullscreen()
}
},
getLocation() {
let date = new Date()
this.timer.time = `${date.getHours().toString().padStart(2, "0")}:${date.getMinutes().toString().padStart(2, "0")}:${date.getSeconds().toString().padStart(2, "0")}`
this.timer.date = `${date.getFullYear()}-${(date.getMonth() + 1).toString().padStart(2, "0")}-${date.getDate().toString().padStart(2, "0")}`
this.timer.city = "北京市"
this.timer.week = "星期" + "日一二三四五六".charAt(date.getDay())
},
getPhotoUrl(photoUrl) {
if (!photoUrl || photoUrl.trim() === '') {
return ''; // 默认无头像
}
return 'https://qhse.cncecci.com/SUBQHSE/' + photoUrl;
},
// 从后台加载数据
loadData() {
const that = this;
$.ajax({
type: "POST",
url: "kq.aspx/GetData", //调用后台WebMethod
contentType: "application/json; charset=utf-8",
dataType: "json",
success: function (response) {
console.log("请求结果:", response.d)
if (response.d.success) {
// 更新数据成功
that.form = { ...response.d.data }; // 更新表单数据
that.lw = response.d.data.workTypeStats || [];
that.attendanceStats = response.d.data.attendanceStats || [];
that.ageData = response.d.data.ageStats || [];
that.genderData = response.d.data.genderStats || [];
that.lwInOutData = response.d.data.lwInOutList || [];
that.chinaMapData = response.d.data.chinaMapData; // 更新地图数据
if (response.d.data.attendanceCurve) {
that.attendanceCurveData.managerData = response.d.data.attendanceCurve.managerData || [];
that.attendanceCurveData.workerData = response.d.data.attendanceCurve.workerData || [];
}
that.$nextTick(() => {
that.initNlzb(that.ageData);
that.initGenderCharts(that.genderData);
that.initChainaMap(that.chinaMapData); // 重新创建地图
that.initRy(
that.attendanceCurveData.managerData,
that.attendanceCurveData.workerData
)
});
} else {
// 更新数据失败
alert("加载数据失败:" + response.d.msg);
}
},
error: function (xhr, status, error) {
// AJAX请求错误处理
alert("请求失败:" + error);
}
});
},
initRy(data1, data2) {
let xLabel = []
for (let i = 0; i < 24; i++) {
xLabel.push(i + ":00");
}
var chartDom = document.getElementById("ry")
var myChart = echarts.init(chartDom)
var option
option = {
legend: {
textStyle:{
color: "#94DAFF"
}
},
tooltip:{
trigger: "axis",
axisPointer: {
type: "cross",
label: {
backgroundColor: "#6a7985",
},
},
},
grid: {
left: "2%",
right: "2%",
bottom: "10%",
containLabel: true,
},
dataZoom: [
{
type: "slider",
xAxisIndex: 0,
bottom: 4,
height: 10,
borderColor: "transparent",
backgroundColor: "rgba(255,255,255,0.08)",
fillerColor: "rgba(70,180,255,0.25)",
dataBackground: {
lineStyle: { color: "rgba(70,180,255,0.3)" },
areaStyle: { color: "rgba(70,180,255,0.1)" },
},
handleStyle: {
color: "#46B4FF",
borderColor: "#fff",
borderWidth: 1,
},
moveHandleSize: 8,
moveHandleColor: "rgba(70,180,255,0.5)",
textStyle: { color: "#9FBCEE" },
showDetail: false,
startValue: 0,
endValue: 9,
},
],
xAxis: {
type: "category",
data: xLabel,
axisLabel: {
interval: 0,
textStyle: {
color: "#9FBCEE",
},
},
axisLine: {
lineStyle: {
color: "#9FBCEE",
},
},
},
yAxis: {
name: "数量",
type: "value",
nameTextStyle: {
color: "#9FBCEE",
},
splitLine: {
lineStyle: {
color: "#9FBCEE",
},
},
axisLabel: {
textStyle: {
color: "#9FBCEE",
},
},
},
series: [
{
name: "管理人员",
data:data1,
type: "line",
smooth: true,
showSymbol: false,
lineStyle: { color: "#FFC72C" },
areaStyle: {
color: {
type: "linear",
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [
{ offset: 0, color: "#FFC72C" },
{ offset: 1, color: "rgba(70,180,255,0.02)" },
],
},
},
},
{
name: "劳务人员",
data:data2,
type: "line",
smooth: true,
showSymbol: false,
lineStyle: { color: "#50E5FF" },
areaStyle: {
color: {
type: "linear",
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [
{ offset: 0, color: "#50E5FF" },
{ offset: 1, color: "rgba(255,180,70,0.02)" },
],
},
},
},
],
}
option && myChart.setOption(option)
},
initChainaMap(list) {
let data = list || [
{ value: [116.407394, 39.904211], name: "北京" }, // 北京
{ value: [121.473668, 31.230389], name: "上海" }, // 上海
{ value: [113.264385, 23.129086], name: "广州" }, // 广州
{ value: [114.057868, 22.582846], name: "深圳" }, // 深圳
]
if (!this.chainaMap) {
this.chainaMap = echarts.init(document.getElementById("chainmap"))
}
// 中国地图 配置
echarts.registerMap("china", chinaJson)
let chinaOption = {
geo: [
{
// 地理坐标系组件
map: "china",
roam: false, // 可以缩放和平移
aspectScale: 0.85, // 比例
top: 200,
zlevel: 2,
zoom: 1.2,
label: {
show: false,
},
regions: [
{
name: "南海诸岛",
itemStyle: {
// 隐藏地图
normal: {
opacity: 0, // 为 0 时不绘制该图形
},
},
label: {
show: false, // 隐藏文字
},
},
],
itemStyle: {
// 图形上的地图区域
normal: {
areaColor: "#07121B",
borderColor: "#07121B",
},
},
emphasis: {
disabled: true,
},
},
{
// 地理坐标系组件
map: "china",
roam: false, // 可以缩放和平移
aspectScale: 0.85, // 比例
top: 190,
zlevel: 9,
zoom: 1.2,
regions: [
{
name: "南海诸岛",
itemStyle: {
// 隐藏地图
normal: {
opacity: 0, // 为 0 时不绘制该图形
},
},
label: {
show: false, // 隐藏文字
},
},
],
itemStyle: {
// 图形上的地图区域
normal: {
areaColor: "#076393",
borderColor: "#076393",
},
},
emphasis: {
disabled: true,
},
},
],
tooltip: {
trigger: "item",
backgroundColor: "rgba(0,0,0,0.7)",
borderColor: "#fff",
borderWidth: 1,
padding: 10,
formatter: function (params) {
// 自定义 tooltip 内容
if (params.seriesType === "effectScatter") {
return `<div style="color: white;">
<p><strong>${params.name}</strong></p>
<p>详细信息: ${params.data.address}</p>
</div>`
}
},
},
series: [
{
// 地理坐标系组件
type: "map",
map: "china",
roam: false, // 可以缩放和平移
aspectScale: 0.85, // 比例
top: 180,
zlevel: 12,
zoom: 1.2,
data: data,
regions: [
{
name: "南海诸岛",
label: {
show: false, // 隐藏文字
},
},
],
itemStyle: {
// 图形上的地图区域
normal: {
borderWidth: 1,
areaColor: "rgba(0, 221, 255, .8)",
borderColor: "RGBA(27, 225, 255, 1)",
},
},
emphasis: {
disabled: true,
},
},
{
name: "散点",
type: "effectScatter",
coordinateSystem: "geo",
data: data,
zlevel: 14,
symbolSize: 10,
showEffectOn: "render",
rippleEffect: {
brushType: "stroke",
},
hoverAnimation: true,
label: {
formatter: "{b}",
position: "right",
show: false,
},
itemStyle: {
color: "#FBE795",
},
},
],
}
this.chainaMap.setOption(chinaOption)
this.chainaMap.on("click", function (params) {
// 控制台打印点击的地区名称
if (params.seriesType == "effectScatter") {
// alert("点击了" + params.name)
var projectId = params.data && params.data.id ? params.data.id : '';
var jumpUrl = "../HSSE/KqShowScreen/KqShowScreen.aspx?projectId=" + encodeURIComponent(projectId);
window.open(jumpUrl)
}
// 你可以在这里添加你的逻辑代码
})
},
initGenderCharts(genderData) {
if (!genderData || genderData.length === 0) {
this.initMan("man", "男", 50);
this.initMan("woman", "女", 50);
return;
}
var total = genderData.reduce((sum, item) => sum + item.value, 0);
if (total === 0) {
this.initMan("man", "男", 50);
this.initMan("woman", "女", 50);
return;
}
var maleData = genderData.find(item => item.name === "男");
var femaleData = genderData.find(item => item.name === "女");
var maleCount = maleData ? maleData.value : 0;
var femaleCount = femaleData ? femaleData.value : 0;
var malePercent = Math.round((maleCount / total) * 100);
var femalePercent = Math.round((femaleCount / total) * 100);
this.initMan("man", "男", malePercent);
this.initMan("woman", "女", femalePercent);
},
initMan(ele, tit = "男", val = 20) {
var chartDom = document.getElementById(ele)
var myChart = echarts.init(chartDom)
var option = {
series: [
{
type: "gauge",
startAngle: 220,
endAngle: -40,
min: 0,
max: 100,
center: ["50%", "50%"],
radius: "70%",
pointer: {
show: false,
},
axisLine: {
lineStyle: {
width: 10,
color: [
[
val / 100,
new echarts.graphic.LinearGradient(0, 0, 1, 0, [
{ offset: 0, color: "#00B4DB" },
{ offset: 1, color: "#9D5CFF" },
]),
],
[1, "#516C82"],
],
},
},
axisTick: { show: false },
splitLine: { show: false },
axisLabel: { show: false },
title: { show: false },
detail: {
valueAnimation: true,
offsetCenter: [0, 0],
formatter: "{value}%",
fontSize: 16,
fontWeight: "bold",
color: "#fff",
},
data: [{ value: val, name: tit }],
},
{
type: "gauge",
startAngle: 220,
endAngle: -40,
min: 0,
max: 100,
radius: "55%",
pointer: { show: false },
axisLine: {
show: true,
lineStyle: { width: 10, color: [[1, "#20415D"]] },
},
axisTick: { show: false },
splitLine: { show: false },
axisLabel: { show: false },
title: {
show: true,
offsetCenter: [0, "50%"],
fontSize: 14,
color: "#fff",
formatter: tit,
},
detail: {
valueAnimation: true,
offsetCenter: [0, "50%"],
formatter: tit,
fontSize: 14,
fontWeight: "bold",
color: "#fff",
},
data: [{ value: 100 }],
},
],
}
myChart.setOption(option)
},
initNlzb(data1) {
let data = data1 || [
{ value: 1048, name: "46-55" },
{ value: 735, name: "36-45" },
{ value: 580, name: "26-35" },
{ value: 484, name: "16-25" },
]
var chartDom = document.getElementById("nlzb")
var myChart = echarts.init(chartDom)
var option
option = {
title: {
text: "年龄占比统计",
top: "0px",
right: "20%",
textStyle: {
color: "#8CEEFF",
fontSize: 14,
},
},
legend: {
orient: "vertical",
top: "25px",
right: "20%",
// top: "middle",
icon: "circle",
itemWidth: 10,
itemHeight: 10,
textStyle: {
color: "#fff",
fontSize: 10,
},
formatter: function (name) {
var dataItem = option.series[0].data.find(function (item) {
return item.name === name
})
var total = option.series[0].data.reduce(function (acc, cur) {
return acc + cur.value
}, 0)
var percent = ((dataItem.value / total) * 100).toFixed(2)
return name + " " + percent + "%"
},
},
series: [
{
name: "1",
type: "pie",
center: ["30%", "50%"],
radius: ["40%", "70%"],
avoidLabelOverlap: false,
gapAngle: 3,
itemStyle: {
borderColor: "#001B35",
borderWidth: 3,
},
color: [
"rgba(0, 180, 219, 0.8)",
"rgba(152, 221, 136, 0.8)",
"rgba(247, 232, 74, 0.8)",
"rgba(244, 122, 122, 0.8)",
],
label: {
show: false,
position: "center",
formatter: function (params) {
var total = option.series[0].data.reduce(function (acc, cur) {
return acc + cur.value
}, 0)
var percent = ((params.value / total) * 100).toFixed(1)
return "{a|" + percent + "%}\n{b|" + params.name + "}"
},
rich: {
a: {
fontSize: 14,
color: "#1FC6FF",
fontWeight: "bold",
},
b: {
fontSize: 12,
color: "#fff",
},
},
},
emphasis: {
label: {
show: true,
},
},
labelLine: {
show: false,
},
data,
},
{
name: "2",
type: "pie",
center: ["30%", "50%"],
radius: ["75%", "90%"],
avoidLabelOverlap: false,
gapAngle: 3,
itemStyle: {
borderColor: "#001B35",
borderWidth: 3,
},
color: ["#00B4DB", "#98DD88", "#F7E84A", "#F47A7A"],
label: {
show: false,
position: "center",
formatter: function (params) {
var total = option.series[0].data.reduce(function (acc, cur) {
return acc + cur.value
}, 0)
var percent = ((params.value / total) * 100).toFixed(1)
return "{a|" + percent + "%}\n{b|" + params.name + "}"
},
rich: {
a: {
fontSize: 14,
color: "#1FC6FF",
fontWeight: "bold",
},
b: {
fontSize: 12,
color: "#fff",
},
},
},
emphasis: {
label: {
show: true,
},
},
labelLine: {
show: false,
},
data,
},
],
}
option && myChart.setOption(option)
},
startAutoScroll(el, speed = 5) {
const container = document.querySelector(el)
if (!container) return
let scrollOffset = 0
const speed1 = speed / 100
const scroll = () => {
scrollOffset += speed1
container.scrollTop = scrollOffset
if (scrollOffset >= container.scrollHeight - container.clientHeight) {
scrollOffset = 0
}
requestAnimationFrame(scroll)
}
requestAnimationFrame(scroll)
},
formatPercent(value) {
let sum = this.lw.reduce((acc, cur) => acc + cur.value, 0)
return ((value / sum) * 100).toFixed(2) + "%"
},
},
})
</script>