diff --git a/package-lock.json b/package-lock.json index 598a7b5..c68223d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -51,6 +51,7 @@ "react-loadable": "5.5.0", "react-orgchart": "^1.0.5", "react-resizable": "^1.10.1", + "react-slick": "^0.31.0", "react-to-print": "^2.0.0-alpha-2", "react-umeditor": "1.0.12", "react-websocket": "2.0.1", @@ -2637,6 +2638,22 @@ "resolved": "https://registry.npmjs.org/@ant-design/icons/-/icons-2.1.1.tgz", "integrity": "sha512-jCH+k2Vjlno4YWl6g535nHR09PwCEmTBKAG6VqF+rhkrSPRLfgpU2maagwbZPLjaHuU5Jd1DFQ2KJpQuI6uG8w==" }, + "node_modules/antd/node_modules/react-slick": { + "version": "0.25.2", + "resolved": "https://registry.npmmirror.com/react-slick/-/react-slick-0.25.2.tgz", + "integrity": "sha512-8MNH/NFX/R7zF6W/w+FS5VXNyDusF+XDW1OU0SzODEU7wqYB+ZTGAiNJ++zVNAVqCAHdyCybScaUB+FCZOmBBw==", + "dependencies": { + "classnames": "^2.2.5", + "enquire.js": "^2.1.6", + "json2mq": "^0.2.0", + "lodash.debounce": "^4.0.8", + "resize-observer-polyfill": "^1.5.0" + }, + "peerDependencies": { + "react": "^0.14.0 || ^15.0.1 || ^16.0.0", + "react-dom": "^0.14.0 || ^15.0.1 || ^16.0.0" + } + }, "node_modules/any-promise": { "version": "1.3.0", "resolved": "https://registry.npmmirror.com/any-promise/-/any-promise-1.3.0.tgz", @@ -21055,19 +21072,18 @@ "license": "BSD-3-Clause" }, "node_modules/react-slick": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/react-slick/-/react-slick-0.25.2.tgz", - "integrity": "sha512-8MNH/NFX/R7zF6W/w+FS5VXNyDusF+XDW1OU0SzODEU7wqYB+ZTGAiNJ++zVNAVqCAHdyCybScaUB+FCZOmBBw==", + "version": "0.31.0", + "resolved": "https://registry.npmmirror.com/react-slick/-/react-slick-0.31.0.tgz", + "integrity": "sha512-zo6VLT8wuSBJffg/TFPbzrw2dEnfZ/cUKmYsKByh3AgatRv29m2LoFbq5vRMa3R3A4wp4d8gwbJKO2fWZFaI3g==", "dependencies": { "classnames": "^2.2.5", - "enquire.js": "^2.1.6", "json2mq": "^0.2.0", "lodash.debounce": "^4.0.8", "resize-observer-polyfill": "^1.5.0" }, "peerDependencies": { - "react": "^0.14.0 || ^15.0.1 || ^16.0.0", - "react-dom": "^0.14.0 || ^15.0.1 || ^16.0.0" + "react": "^0.14.0 || ^15.0.1 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^0.14.0 || ^15.0.1 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "node_modules/react-test-renderer": { @@ -22508,7 +22524,6 @@ "version": "1.8.1", "resolved": "https://registry.npmmirror.com/slick-carousel/-/slick-carousel-1.8.1.tgz", "integrity": "sha512-XB9Ftrf2EEKfzoQXt3Nitrt/IPbT+f1fgqBdoxO3W/+JYvtEOW6EgxnWfr9GH6nmULv7Y2tPmEX3koxThVmebA==", - "license": "MIT", "peerDependencies": { "jquery": ">=1.8.0" } @@ -28673,6 +28688,18 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/@ant-design/icons/-/icons-2.1.1.tgz", "integrity": "sha512-jCH+k2Vjlno4YWl6g535nHR09PwCEmTBKAG6VqF+rhkrSPRLfgpU2maagwbZPLjaHuU5Jd1DFQ2KJpQuI6uG8w==" + }, + "react-slick": { + "version": "0.25.2", + "resolved": "https://registry.npmmirror.com/react-slick/-/react-slick-0.25.2.tgz", + "integrity": "sha512-8MNH/NFX/R7zF6W/w+FS5VXNyDusF+XDW1OU0SzODEU7wqYB+ZTGAiNJ++zVNAVqCAHdyCybScaUB+FCZOmBBw==", + "requires": { + "classnames": "^2.2.5", + "enquire.js": "^2.1.6", + "json2mq": "^0.2.0", + "lodash.debounce": "^4.0.8", + "resize-observer-polyfill": "^1.5.0" + } } } }, @@ -42912,12 +42939,11 @@ } }, "react-slick": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/react-slick/-/react-slick-0.25.2.tgz", - "integrity": "sha512-8MNH/NFX/R7zF6W/w+FS5VXNyDusF+XDW1OU0SzODEU7wqYB+ZTGAiNJ++zVNAVqCAHdyCybScaUB+FCZOmBBw==", + "version": "0.31.0", + "resolved": "https://registry.npmmirror.com/react-slick/-/react-slick-0.31.0.tgz", + "integrity": "sha512-zo6VLT8wuSBJffg/TFPbzrw2dEnfZ/cUKmYsKByh3AgatRv29m2LoFbq5vRMa3R3A4wp4d8gwbJKO2fWZFaI3g==", "requires": { "classnames": "^2.2.5", - "enquire.js": "^2.1.6", "json2mq": "^0.2.0", "lodash.debounce": "^4.0.8", "resize-observer-polyfill": "^1.5.0" diff --git a/package.json b/package.json index 00e0845..757a26f 100644 --- a/package.json +++ b/package.json @@ -52,6 +52,7 @@ "react-loadable": "5.5.0", "react-orgchart": "^1.0.5", "react-resizable": "^1.10.1", + "react-slick": "^0.31.0", "react-to-print": "^2.0.0-alpha-2", "react-umeditor": "1.0.12", "react-websocket": "2.0.1", diff --git a/src/layout/FullOther/ClassBuild.js b/src/layout/FullOther/ClassBuild.js new file mode 100644 index 0000000..293ffd6 --- /dev/null +++ b/src/layout/FullOther/ClassBuild.js @@ -0,0 +1,228 @@ +// ClassBuild.js - 班组建设页面组件 +import React from 'react'; +import { Table } from 'antd'; +import styles from './../fullinter.less'; +import echarts from 'echarts'; + +class ClassBuild extends React.Component { + constructor(props) { + super(props); + this.echartsInstances = { + typeBarChart: null, // 班组风险类别柱状图 + }; + this.chartResizeHandlers = {}; + this.isUnmounted = false; + } + + waitForElement = (elementId, maxRetries = 10) => { + return new Promise((resolve) => { + let retries = 0; + const checkInterval = setInterval(() => { + const element = document.getElementById(elementId); + if (element || retries >= maxRetries) { + clearInterval(checkInterval); + resolve(!!element); + } + retries++; + }, 50); + }); + }; + + // 班组风险类别柱状图 + renderTypeBarChart = async () => { + if (this.isUnmounted) return; + const elementExists = await this.waitForElement('typeBarChart'); + if (!elementExists || this.isUnmounted) return; + + if (this.echartsInstances.typeBarChart) { + this.echartsInstances.typeBarChart.dispose(); + this.echartsInstances.typeBarChart = null; + } + + const chartDom = document.getElementById('typeBarChart'); + if (!chartDom) return; + + this.echartsInstances.typeBarChart = echarts.init(chartDom); + + const { classSubData } = this.props; + const companyList = classSubData || []; + + if (companyList.length === 0) { + this.echartsInstances.typeBarChart.setOption({ + title: { + text: '班组建设统计情况', + x: 'center', + y: '25%', + textStyle: { fontSize: 16, color: '#999' }, + }, + graphic: { + type: 'text', + left: 'center', + top: 'middle', + style: { + text: '暂无数据', + fill: '#999', + fontSize: 14, + }, + }, + }); + return; + } + + // 注意:数据结构是 { companyName: '邦泰', rate: '52%' } + const companyNames = companyList.map((item) => item.companyName); + const rates = companyList.map((item) => { + // 处理 rate 可能是字符串带百分号的情况 + const rateValue = typeof item.rate === 'string' ? parseFloat(item.rate.replace('%', '')) : item.rate; + return isNaN(rateValue) ? 0 : rateValue; + }); + + const option = { + title: { + text: '本月班前会议及时完成率分析', + x: 'center', + y: '5%', + textStyle: { fontSize: 16, color: '#000', fontWeight: 'bold' }, + }, + tooltip: { + trigger: 'axis', + axisPointer: { type: 'shadow' }, + formatter: function (params) { + let result = `${params[0].axisValue}
`; + params.forEach((param) => { + result += `${param.marker}${param.seriesName}: ${param.value}%
`; + }); + return result; + }, + }, + grid: { + left: '8%', + right: '5%', + top: '18%', + bottom: '8%', + containLabel: true, + }, + xAxis: [ + { + type: 'category', + data: companyNames, + axisLine: { show: false }, + axisTick: { show: false }, + axisLabel: { + textStyle: { color: '#000' }, + interval: 0, + fontSize: 12, + }, + }, + ], + yAxis: [ + { + type: 'value', + show: true, + axisLine: { show: false }, + axisTick: { show: false }, + axisLabel: { + show: true, + textStyle: { color: '#000' }, + formatter: '{value}%', + }, + splitLine: { show: true, lineStyle: { color: '#e0e0e0', type: 'dashed' } }, + }, + ], + series: [ + { + name: '班组建设完成率', + type: 'bar', + data: rates, + itemStyle: { + normal: { + color: '#4285F4', + borderRadius: [4, 4, 0, 0], + }, + }, + label: { + show: true, + position: 'top', + textStyle: { color: '#4285F4', fontSize: 12 }, + formatter: (params) => `${params.value}%`, + }, + barWidth: '50%', + }, + ], + }; + + this.echartsInstances.typeBarChart.setOption(option); + this.setupResizeHandler('typeBarChart', this.renderTypeBarChart); + }; + + setupResizeHandler = (chartName, renderMethod) => { + const resizeHandler = () => { + if (this.echartsInstances[chartName] && !this.isUnmounted) { + this.echartsInstances[chartName].resize(); + } + }; + this.chartResizeHandlers[chartName] = resizeHandler; + window.addEventListener('resize', resizeHandler); + }; + + initAllCharts = () => { + if (this.isUnmounted) return; + setTimeout(() => { + if (this.isUnmounted) return; + this.renderTypeBarChart(); + }, 100); + }; + + disposeAllCharts = () => { + Object.keys(this.echartsInstances).forEach((key) => { + if (this.echartsInstances[key]) { + try { + this.echartsInstances[key].dispose(); + } catch (e) { + console.warn(`Dispose chart ${key} error:`, e); + } + this.echartsInstances[key] = null; + } + }); + + Object.keys(this.chartResizeHandlers).forEach((key) => { + if (this.chartResizeHandlers[key]) { + window.removeEventListener('resize', this.chartResizeHandlers[key]); + } + }); + this.chartResizeHandlers = {}; + }; + + componentDidMount() { + this.isUnmounted = false; + this.initAllCharts(); + } + + // 修复:监听 classSubData 而不是 riskSubData + componentDidUpdate(prevProps) { + if (prevProps.classSubData !== this.props.classSubData) { + this.renderTypeBarChart(); + } + } + + componentWillUnmount() { + this.isUnmounted = true; + this.disposeAllCharts(); + } + + render() { + return ( +
+
+
+
+
+
+
+
+
+ ); + } +} + +export default ClassBuild; diff --git a/src/layout/FullOther/DangerJob.js b/src/layout/FullOther/DangerJob.js new file mode 100644 index 0000000..e38e3c7 --- /dev/null +++ b/src/layout/FullOther/DangerJob.js @@ -0,0 +1,312 @@ +// DangerJob.js - 危险作业页面组件 +import React from 'react'; +import { Table, Select, Pagination } from 'antd'; +import styles from './../fullinter.less'; + +const { Option } = Select; + +class DangerJob extends React.Component { + constructor(props) { + super(props); + this.state = { + currentPage: 1, + pageSize: 10, + }; + this.isUnmounted = false; + } + + // 生成公司选项(与 TrainingContent 保持一致) + getCompanyOptions = () => { + const { companyData } = this.props; + if (!companyData || companyData.length === 0) { + return ; + } + return companyData.map((company, index) => ( + + )); + }; + + // 处理公司筛选变化 + handleCompanyChange = (value) => { + const { onCompanyChange } = this.props; + this.setState({ currentPage: 1 }, () => { + if (onCompanyChange) { + onCompanyChange(value); + } + }); + }; + + // 处理页码变化 + handlePageChange = (page, pageSize) => { + this.setState({ currentPage: page, pageSize }); + }; + + // 处理每页条数变化 + handleShowSizeChange = (current, size) => { + this.setState({ currentPage: 1, pageSize: size }); + }; + + // 获取过滤后的数据(添加类型检查和默认值) + getFilteredData = () => { + const { dangerSubData, selectedCompany, companyData } = this.props; + + // 确保 dangerSubData 是数组 + let dataArray = Array.isArray(dangerSubData) ? dangerSubData : []; + + if (dataArray.length === 0) { + return []; + } + + // 根据选中的公司进行过滤(使用 ID 进行匹配) + if (selectedCompany) { + // 查找选中的公司名称 + const selectedCompanyObj = companyData?.find((company) => company.ID === selectedCompany); + const selectedCompanyName = selectedCompanyObj?.NAME; + + if (selectedCompanyName) { + return dataArray.filter((item) => item.companyName === selectedCompanyName); + } + } + return dataArray; + }; + + // 获取当前页数据 + getCurrentPageData = () => { + const { currentPage, pageSize } = this.state; + const filteredData = this.getFilteredData(); + // 确保 filteredData 是数组 + const dataArray = Array.isArray(filteredData) ? filteredData : []; + const startIndex = (currentPage - 1) * pageSize; + const endIndex = startIndex + pageSize; + return { + data: dataArray.slice(startIndex, endIndex), + total: dataArray.length, + }; + }; + + // 渲染危险作业表格 + renderDangerTable = () => { + const { selectedCompany, companyData } = this.props; + const { currentPage, pageSize } = this.state; + const { data: tableData, total } = this.getCurrentPageData(); + + if (!tableData || tableData.length === 0) { + // 获取选中的公司名称用于显示 + let companyName = ''; + if (selectedCompany && companyData) { + const selectedCompanyObj = companyData.find((company) => company.ID === selectedCompany); + companyName = selectedCompanyObj?.NAME || ''; + } + return ( +
+ {selectedCompany ? `${companyName}暂无危险作业数据` : '暂无危险作业数据'} +
+ ); + } + + // 表格列配置 + const columns = [ + { + title: '公司', + dataIndex: 'companyName', + key: 'companyName', + align: 'center', + width: 120, + fixed: 'left', + render: (text) => {text}, + }, + { + title: '开始时间', + dataIndex: 'startDate', + key: 'startDate', + align: 'center', + width: 160, + render: (text) => {text || '-'}, + }, + { + title: '结束时间', + dataIndex: 'endDate', + key: 'endDate', + align: 'center', + width: 160, + render: (text) => {text || '-'}, + }, + { + title: '作业名称', + dataIndex: 'jobName', + key: 'jobName', + align: 'center', + minWidth: 120, + render: (text) => {text || '-'}, + }, + { + title: '作业区域', + dataIndex: 'areaName', + key: 'areaName', + align: 'center', + minWidth: 120, + render: (text) => {text || '-'}, + }, + { + title: '作业地点', + dataIndex: 'place', + key: 'place', + align: 'center', + minWidth: 150, + render: (text) => {text || '-'}, + }, + { + title: '作业人员', + dataIndex: 'users', + key: 'users', + align: 'center', + minWidth: 150, + render: (text) => {text || '-'}, + }, + { + title: '监护人', + dataIndex: 'monitor', + key: 'monitor', + align: 'center', + width: 120, + render: (text) => {text || '-'}, + }, + { + title: '审批领导', + dataIndex: 'approveUsers', + key: 'approveUsers', + align: 'center', + minWidth: 150, + render: (text) => {text || '-'}, + }, + ]; + + // 计算横向滚动宽度(如果列数过多) + const scrollX = columns.reduce((sum, col) => sum + (col.width || 120), 0); + const scrollConfig = scrollX > 1200 ? { x: scrollX } : {}; + + // 表格数据转换(添加唯一key) + const dataSource = tableData.map((item, index) => ({ + key: `${item.companyName}_${item.startDate}_${index}`, + companyName: item.companyName, + startDate: item.startDate, + endDate: item.endDate, + jobName: item.jobName, + areaName: item.areaName, + place: item.place, + users: item.users, + monitor: item.monitor, + approveUsers: item.approveUsers, + })); + + return ( +
+
+ 当日各公司危险作业清单 +
+
+ + + {/* 分页组件 */} +
+ `共 ${total} 条记录`} + onChange={this.handlePageChange} + onShowSizeChange={this.handleShowSizeChange} + pageSizeOptions={['10', '20', '50']} + /> +
+ + ); + }; + + componentDidMount() { + this.isUnmounted = false; + } + + componentDidUpdate(prevProps) { + // 当筛选条件变化时,重置到第一页 + if (prevProps.selectedCompany !== this.props.selectedCompany) { + this.setState({ currentPage: 1 }); + } + // 当数据变化时,如果当前页没有数据且不是第一页,重置到第一页 + if (prevProps.dangerSubData !== this.props.dangerSubData) { + const { currentPage } = this.state; + const filteredData = this.getFilteredData(); + const dataArray = Array.isArray(filteredData) ? filteredData : []; + const maxPage = Math.ceil(dataArray.length / this.state.pageSize) || 1; + if (currentPage > maxPage) { + this.setState({ currentPage: 1 }); + } + } + } + + componentWillUnmount() { + this.isUnmounted = true; + } + + render() { + const { companyData, selectedCompany } = this.props; + + return ( +
+
+
+
+ {/* 公司筛选器 */} +
+ 选择公司: + +
+ {this.renderDangerTable()} +
+
+
+
+ ); + } +} + +export default DangerJob; diff --git a/src/layout/FullOther/HiddenSolve.js b/src/layout/FullOther/HiddenSolve.js new file mode 100644 index 0000000..0fd37fb --- /dev/null +++ b/src/layout/FullOther/HiddenSolve.js @@ -0,0 +1,578 @@ +// HiddenSolve.js - 隐患解决页面组件 +import React from 'react'; +import { Select } from 'antd'; +import styles from './../fullinter.less'; +import echarts from 'echarts'; + +const { Option } = Select; + +class HiddenSolve extends React.Component { + constructor(props) { + super(props); + this.echartsInstances = { + hiddenBarChart: null, // 各公司隐患统计柱状图(使用 hiddenList) + hiddenRectifyChart: null, // 隐患整改情况柱状图(使用 hiddenRectifyList) + }; + this.chartResizeHandlers = {}; + this.isUnmounted = false; + } + + // 获取公司选项(使用 props 传入的 companyData,与 TrainingContent 一致) + getCompanyOptions = () => { + const { companyData } = this.props; + if (!companyData || companyData.length === 0) { + return []; + } + return companyData.map((company, index) => ( + + )); + }; + + // 根据选中的公司ID获取公司名称(用于筛选模拟数据) + getSelectedCompanyName = () => { + const { companyData, selectedCompany } = this.props; + if (!selectedCompany || !companyData || companyData.length === 0) { + return null; + } + const selectedCompanyObj = companyData.find((company) => company.ID === selectedCompany); + return selectedCompanyObj ? selectedCompanyObj.NAME : null; + }; + + waitForElement = (elementId, maxRetries = 10) => { + return new Promise((resolve) => { + let retries = 0; + const checkInterval = setInterval(() => { + const element = document.getElementById(elementId); + if (element || retries >= maxRetries) { + clearInterval(checkInterval); + resolve(!!element); + } + retries++; + }, 50); + }); + }; + + // 图表1: 各公司隐患统计柱状图(使用 hiddenList 数据) + renderHiddenBarChart = async () => { + if (this.isUnmounted) return; + const elementExists = await this.waitForElement('hiddenBarChart'); + if (!elementExists || this.isUnmounted) return; + + if (this.echartsInstances.hiddenBarChart) { + this.echartsInstances.hiddenBarChart.dispose(); + this.echartsInstances.hiddenBarChart = null; + } + + const chartDom = document.getElementById('hiddenBarChart'); + if (!chartDom) return; + + this.echartsInstances.hiddenBarChart = echarts.init(chartDom); + + const { hiddenSubData } = this.props; + let hiddenList = hiddenSubData?.hiddenList || []; + + if (hiddenList.length === 0) { + this.echartsInstances.hiddenBarChart.setOption({ + title: { + text: '各公司累计隐患统计数据', + x: 'center', + y: '25%', + textStyle: { fontSize: 16, color: '#999' }, + }, + graphic: { + type: 'text', + left: 'center', + top: 'middle', + style: { + text: '暂无数据', + fill: '#999', + fontSize: 14, + }, + }, + }); + return; + } + + const companyNames = hiddenList.map((item) => item.companyName); + const majorCounts = hiddenList.map((item) => item.majorCount); + const generalCounts = hiddenList.map((item) => item.generalCount); + + const option = { + title: { + text: '各公司累计隐患统计数据', + x: 'center', + y: '5%', + textStyle: { fontSize: 16, color: '#000', fontWeight: 'bold' }, + }, + tooltip: { + trigger: 'axis', + axisPointer: { type: 'shadow' }, + formatter: function (params) { + let result = `${params[0].axisValue}
`; + params.forEach((param) => { + result += `${param.marker}${param.seriesName}: ${param.value}
`; + }); + return result; + }, + }, + legend: { + data: ['重大隐患', '一般隐患'], + orient: 'vertical', + right: '3%', + top: '5%', + itemGap: 16, + itemWidth: 18, + itemHeight: 12, + textStyle: { color: '#000', fontSize: 14 }, + }, + grid: { + left: '8%', + right: '5%', + top: '18%', + bottom: '8%', + containLabel: true, + }, + xAxis: [ + { + type: 'category', + data: companyNames, + axisLine: { show: false }, + axisTick: { show: false }, + axisLabel: { + textStyle: { color: '#000' }, + rotate: companyNames.length > 4 ? 15 : 0, + interval: 0, + fontSize: 12, + }, + }, + ], + yAxis: [ + { + type: 'value', + show: true, + axisLine: { show: false }, + axisTick: { show: false }, + axisLabel: { + show: true, + textStyle: { color: '#000' }, + }, + splitLine: { show: false }, + name: '隐患数量', + nameTextStyle: { fontSize: 12 }, + }, + ], + series: [ + { + name: '重大隐患', + type: 'bar', + data: majorCounts, + itemStyle: { + normal: { + color: '#c92a2a', // 红色 + }, + }, + label: { + show: true, + position: 'top', + textStyle: { color: '#c92a2a', fontSize: 12 }, + formatter: (params) => `${params.value}`, + }, + barWidth: '35%', + }, + { + name: '一般隐患', + type: 'bar', + data: generalCounts, + itemStyle: { + normal: { + color: '#4285F4', // 蓝色 + }, + }, + label: { + show: true, + position: 'top', + textStyle: { color: '#4285F4', fontSize: 12 }, + formatter: (params) => `${params.value}`, + }, + barWidth: '35%', + }, + ], + }; + + this.echartsInstances.hiddenBarChart.setOption(option); + this.setupResizeHandler('hiddenBarChart', this.renderHiddenBarChart); + }; + + // 图表2: 隐患整改情况柱状图(使用 hiddenRectifyList 数据) + renderHiddenRectifyChart = async () => { + if (this.isUnmounted) return; + const elementExists = await this.waitForElement('hiddenRectifyChart'); + if (!elementExists || this.isUnmounted) return; + + if (this.echartsInstances.hiddenRectifyChart) { + this.echartsInstances.hiddenRectifyChart.dispose(); + this.echartsInstances.hiddenRectifyChart = null; + } + + const chartDom = document.getElementById('hiddenRectifyChart'); + if (!chartDom) return; + + this.echartsInstances.hiddenRectifyChart = echarts.init(chartDom); + + const { hiddenSubData } = this.props; + let hiddenRectifyList = hiddenSubData?.hiddenRectifyList || []; + + if (hiddenRectifyList.length === 0) { + this.echartsInstances.hiddenRectifyChart.setOption({ + title: { + text: '当月各公司隐患统计数据', + x: 'center', + y: '25%', + textStyle: { fontSize: 16, color: '#999' }, + }, + graphic: { + type: 'text', + left: 'center', + top: 'middle', + style: { + text: '暂无数据', + fill: '#999', + fontSize: 14, + }, + }, + }); + return; + } + + const companyNames = hiddenRectifyList.map((item) => item.companyName); + const majorTotal = hiddenRectifyList.map((item) => item.majorCount); + const majorRectified = hiddenRectifyList.map((item) => (item.majorCount || 0) - (item.majorCountNo || 0)); + const generalTotal = hiddenRectifyList.map((item) => item.generalCount); + const generalRectified = hiddenRectifyList.map((item) => (item.generalCount || 0) - (item.generalCountNo || 0)); + + const option = { + title: { + text: '当月各公司隐患统计数据', + x: 'center', + y: '5%', + textStyle: { fontSize: 16, color: '#000', fontWeight: 'bold' }, + }, + tooltip: { + trigger: 'axis', + axisPointer: { type: 'shadow' }, + formatter: function (params) { + let result = `${params[0].axisValue}
`; + params.forEach((param) => { + result += `${param.marker}${param.seriesName}: ${param.value}
`; + }); + return result; + }, + }, + legend: { + data: ['重大隐患量', '重大隐患未整改量', '一般隐患量', '一般隐患未整改量'], + orient: 'vertical', + right: '3%', + top: '5%', + itemGap: 12, + itemWidth: 18, + itemHeight: 12, + textStyle: { color: '#000', fontSize: 12 }, + }, + grid: { + left: '8%', + right: '5%', + top: '18%', + bottom: '8%', + containLabel: true, + }, + xAxis: [ + { + type: 'category', + data: companyNames, + axisLine: { show: false }, + axisTick: { show: false }, + axisLabel: { + textStyle: { color: '#000' }, + rotate: companyNames.length > 4 ? 15 : 0, + interval: 0, + fontSize: 12, + }, + }, + ], + yAxis: [ + { + type: 'value', + show: true, + axisLine: { show: false }, + axisTick: { show: false }, + axisLabel: { + show: true, + textStyle: { color: '#000' }, + }, + splitLine: { show: false }, + name: '隐患数量', + nameTextStyle: { fontSize: 12 }, + }, + ], + series: [ + { + name: '重大隐患量', + type: 'bar', + data: majorTotal, + itemStyle: { + normal: { + color: '#c92a2a', // 红色 + }, + }, + label: { + show: true, + position: 'top', + textStyle: { color: '#c92a2a', fontSize: 11 }, + formatter: (params) => `${params.value}`, + }, + barWidth: '20%', + }, + { + name: '重大隐患未整改量', + type: 'bar', + data: majorRectified, + itemStyle: { + normal: { + color: '#ffa94d', // 橙色 + }, + }, + label: { + show: true, + position: 'top', + textStyle: { color: '#ffa94d', fontSize: 11 }, + formatter: (params) => `${params.value}`, + }, + barWidth: '20%', + }, + { + name: '一般隐患量', + type: 'bar', + data: generalTotal, + itemStyle: { + normal: { + color: '#4285F4', // 蓝色 + }, + }, + label: { + show: true, + position: 'top', + textStyle: { color: '#4285F4', fontSize: 11 }, + formatter: (params) => `${params.value}`, + }, + barWidth: '20%', + }, + { + name: '一般隐患未整改量', + type: 'bar', + data: generalRectified, + itemStyle: { + normal: { + color: '#ffe066', // 黄色 + }, + }, + label: { + show: true, + position: 'top', + textStyle: { color: '#d4a000', fontSize: 11 }, + formatter: (params) => `${params.value}`, + }, + barWidth: '20%', + }, + ], + }; + + this.echartsInstances.hiddenRectifyChart.setOption(option); + this.setupResizeHandler('hiddenRectifyChart', this.renderHiddenRectifyChart); + }; + + // 排名列表组件(支持公司筛选) + renderRankingList = () => { + const { hiddenSubData, selectedCompany, onCompanyChange, companyData } = this.props; + let hiddenRanking = hiddenSubData?.hiddenRanking || []; + + // 根据选中的公司筛选数据 + const selectedCompanyName = this.getSelectedCompanyName(); + if (selectedCompanyName) { + hiddenRanking = hiddenRanking.filter((item) => item.companyName === selectedCompanyName); + } + + // if (hiddenRanking.length === 0) { + // return ( + //
+ // {selectedCompanyName ? `${selectedCompanyName} 暂无隐患排名数据` : '暂无隐患排名数据'} + //
+ // ); + // } + + return ( +
+
+ 隐患次数排名列表 +
+
+ 选择公司: + +
+ {/* 公司筛选器 - 与 TrainingContent 样式保持一致 */} + +
+
+ + + + + + + + + {hiddenRanking.length > 0 ? ( + hiddenRanking.map((item, index) => ( + + + + + + )) + ) : ( +
{'暂无隐患排名数据'}
+ )} + +
序号 + 隐患名称 + 数量
+ + {index + 1} + + {item.hiddenName} + {item.qty} +
+
+
+ ); + }; + + setupResizeHandler = (chartName, renderMethod) => { + const resizeHandler = () => { + if (this.echartsInstances[chartName] && !this.isUnmounted) { + this.echartsInstances[chartName].resize(); + } + }; + this.chartResizeHandlers[chartName] = resizeHandler; + window.addEventListener('resize', resizeHandler); + }; + + initAllCharts = () => { + if (this.isUnmounted) return; + setTimeout(() => { + if (this.isUnmounted) return; + this.renderHiddenBarChart(); + this.renderHiddenRectifyChart(); + }, 100); + }; + + disposeAllCharts = () => { + Object.keys(this.echartsInstances).forEach((key) => { + if (this.echartsInstances[key]) { + try { + this.echartsInstances[key].dispose(); + } catch (e) { + console.warn(`Dispose chart ${key} error:`, e); + } + this.echartsInstances[key] = null; + } + }); + + Object.keys(this.chartResizeHandlers).forEach((key) => { + if (this.chartResizeHandlers[key]) { + window.removeEventListener('resize', this.chartResizeHandlers[key]); + } + }); + this.chartResizeHandlers = {}; + }; + + componentDidMount() { + this.isUnmounted = false; + this.initAllCharts(); + } + + componentDidUpdate(prevProps) { + // 当 hiddenSubData 或 selectedCompany 变化时重新渲染图表 + if ( + prevProps.hiddenSubData !== this.props.hiddenSubData || + prevProps.selectedCompany !== this.props.selectedCompany + ) { + this.renderHiddenBarChart(); + this.renderHiddenRectifyChart(); + } + } + + componentWillUnmount() { + this.isUnmounted = true; + this.disposeAllCharts(); + } + + render() { + const { selectedCompany, onCompanyChange } = this.props; + + return ( +
+
+ {/* 第一行 - 柱状图(left)+ 排名列表(right) */} +
+
+ {/* 添加公司筛选器(左上角,与 TrainingContent 样式一致) */} +
+
+
+ {this.renderRankingList()} +
+
+ {/* 第二行 - 隐患整改情况柱状图 */} +
+
+
+
+
+
+
+ ); + } +} + +export default HiddenSolve; diff --git a/src/layout/FullOther/HomeContent.js b/src/layout/FullOther/HomeContent.js index 10bdee3..08a095f 100644 --- a/src/layout/FullOther/HomeContent.js +++ b/src/layout/FullOther/HomeContent.js @@ -1,13 +1,18 @@ // HomeContent.js import React, { useEffect, useRef } from 'react'; -import { Row, Col, Icon, Carousel } from 'antd'; +import { Row, Col, Icon, Modal, Button } from 'antd'; +import Slider from 'react-slick'; import styles from './../fullinter.less'; import echarts from 'echarts'; +import configc from '../../config'; +import 'slick-carousel/slick/slick.css'; +import 'slick-carousel/slick/slick-theme.css'; +import { extendRule } from '../../utils/common'; class HomeContent extends React.Component { constructor(props) { super(props); - this.carouselRef = React.createRef(); + this.sliderRef = React.createRef(); this.echartsInstances = { riskLevel: null, safeCheckChart: null, @@ -16,6 +21,7 @@ class HomeContent extends React.Component { }; this.chartResizeHandlers = {}; this.isUnmounted = false; + this.autoplayTimer = null; } waitForElement = (elementId, maxRetries = 10) => { @@ -111,46 +117,6 @@ class HomeContent extends React.Component { window.addEventListener('resize', resizeHandler); }; - transformDat = (originalData, barTopColor, num) => { - if (!originalData || !Array.isArray(originalData) || originalData.length === 0) { - return { companyNames: [], series: [], legendData: [] }; - } - - let allTypes = []; - if (num == 1) { - allTypes = [...new Set(originalData.flatMap((item) => item.details?.map((detail) => detail.jobName)))]; - } else { - allTypes = [...new Set(originalData.flatMap((item) => item.details?.map((detail) => detail.name)))]; - } - - allTypes = allTypes.filter(Boolean); - const companyNames = originalData?.map((item) => item.company); - const series = allTypes?.map((typeName, index) => ({ - name: typeName, - type: 'bar', - itemStyle: { - normal: { - color: function (params) { - if (num == 1) { - return barTopColor[index]; - } else { - return new echarts.graphic.LinearGradient(0, 0, 0, 1, [ - { offset: 0, color: barTopColor[index][0] }, - { offset: 1, color: barTopColor[index][1] }, - ]); - } - }, - }, - }, - data: originalData?.map((company) => { - const detail = company.details?.find((d) => (num == 1 ? d.jobName : d.name) === typeName); - return detail ? detail.qty : 0; - }), - })); - - return { companyNames, series, legendData: allTypes }; - }; - safeCheckChart = async () => { if (this.isUnmounted) return; const elementExists = await this.waitForElement('safeCheckChart'); @@ -164,48 +130,130 @@ class HomeContent extends React.Component { let safeCheckCharts = document.getElementById('safeCheckChart'); if (!safeCheckCharts) return; - const barTopColor = ['#02c3f1', '#53e568', '#a154e9']; - const jobData = this.props.jobTodayTop3 || []; + const hiddenSummary = this.props.hiddenSummary || {}; - if (jobData.length === 0) return; + // 检查是否有有效数据 + const hasData = + hiddenSummary.monthMajorQty !== undefined || + hiddenSummary.unfinishMonthMajorQty !== undefined || + hiddenSummary.monthGeneralQty !== undefined || + hiddenSummary.unfinishMonthGeneralQty !== undefined; + + if (!hasData) { + this.echartsInstances.safeCheckChart = echarts.init(safeCheckCharts); + this.echartsInstances.safeCheckChart.setOption({ + title: { + text: '当月隐患统计', + x: 'center', + y: '25%', + textStyle: { fontSize: 16, color: '#fff' }, + }, + graphic: { + type: 'text', + left: 'center', + top: 'middle', + style: { + text: '暂无数据', + fill: '#999', + fontSize: 14, + }, + }, + }); + return; + } + + // 定义图例和数据 + const legendData = ['重大隐患数', '重大隐患未整改数', '一般隐患数', '一般隐患未整改数']; + const seriesData = [ + { name: '重大隐患数', value: hiddenSummary.monthMajorQty || 0, color: '#c92a2a' }, + { name: '重大隐患未整改数', value: hiddenSummary.unfinishMonthMajorQty || 0, color: '#ffa94d' }, + { name: '一般隐患数', value: hiddenSummary.monthGeneralQty || 0, color: '#ffe066' }, + { name: '一般隐患未整改数', value: hiddenSummary.unfinishMonthGeneralQty || 0, color: '#4285F4' }, + ]; this.echartsInstances.safeCheckChart = echarts.init(safeCheckCharts); - let xdata = this.transformDat(jobData, barTopColor, 1); this.echartsInstances.safeCheckChart.setOption({ title: { - text: '当日工作票排名前三家的数据', - textStyle: { fontSize: 16, color: '#fff' }, + text: '当月隐患统计', + x: 'center', + y: '5%', + textStyle: { fontSize: 16, color: '#000' }, + }, + tooltip: { + trigger: 'axis', + axisPointer: { type: 'shadow' }, + formatter: function (params) { + if (!params || params.length === 0) return ''; + let result = `${params[0].axisValue}
`; + params.forEach((param) => { + result += `${param.marker}${param.seriesName}: ${param.value}
`; + }); + return result; + }, }, - tooltip: { trigger: 'axis', axisPointer: { type: 'shadow' } }, legend: { - data: xdata.legendData, - align: 'right', - right: 10, - itemGap: 16, - itemWidth: 18, + data: legendData, + right: 0, + top: '10%', + itemGap: 12, + itemWidth: 16, itemHeight: 10, - textStyle: { color: '#fff', fontSize: 14 }, + textStyle: { color: '#000', fontSize: 12 }, + }, + color: seriesData.map((item) => item.color), + grid: { + left: '5%', + right: '8%', + top: '28%', + bottom: '5%', + containLabel: true, }, - color: barTopColor, - grid: { left: '3%', right: '4%', bottom: '3%', containLabel: true }, xAxis: [ { type: 'category', - data: xdata.companyNames, - axisLine: { lineStyle: { color: '#3eb2e8' } }, - axisLabel: { textStyle: { color: '#fff' } }, + data: ['当月隐患'], + axisLine: { lineStyle: { color: '#c4c6c9' } }, + axisLabel: { textStyle: { color: '#000', fontSize: 14 } }, + axisTick: { show: false }, }, ], yAxis: [ { type: 'value', + name: '数量', + nameTextStyle: { color: '#000' }, axisLine: { show: false }, - axisLabel: { textStyle: { color: '#fff' } }, - splitLine: { lineStyle: { color: '#4784e8' } }, + axisTick: { show: false }, + splitLine: { show: false }, + axisLabel: { textStyle: { color: '#000' } }, }, ], - series: xdata.series, + series: seriesData.map((item) => ({ + name: item.name, + type: 'bar', + data: [item.value], + itemStyle: { + normal: { + color: item.color, + barBorderRadius: [8, 8, 0, 0], + }, + emphasis: { + shadowBlur: 10, + shadowOffsetX: 0, + shadowColor: 'rgba(0, 0, 0, 0.5)', + }, + }, + label: { + show: true, + position: 'top', + textStyle: { color: '#000', fontSize: 12 }, + formatter: '{c}', + }, + barWidth: '10%', + barGap: '30%', + barCategoryGap: '20%', + })), }); const resizeHandler = () => { @@ -230,9 +278,33 @@ class HomeContent extends React.Component { const dangerOperationCharts = document.getElementById('dangerOperationChart'); if (!dangerOperationCharts) return; - const linkData = this.props.linkSum || []; - if (linkData.length === 0) return; + const linkData = this.props.jobTodayQty || []; + // 如果没有数据,显示“暂无数据”提示 + if (linkData.length === 0) { + this.echartsInstances.dangerOperation = echarts.init(dangerOperationCharts); + this.echartsInstances.dangerOperation.setOption({ + title: { + text: '当日工作票的统计数量', + x: 'center', + y: '25%', + textStyle: { fontSize: 16, color: '#999' }, + }, + graphic: { + type: 'text', + left: 'center', + top: 'middle', + style: { + text: '暂无数据', + fill: '#999', + fontSize: 14, + }, + }, + }); + return; + } + + // 有数据时正常渲染图表 this.echartsInstances.dangerOperation = echarts.init(dangerOperationCharts); const xAxisData = linkData.map((item) => item.name); const seriesData = linkData.map((item) => item.qty); @@ -253,10 +325,10 @@ class HomeContent extends React.Component { formatter: function (params) { const color = params[0].color; return `
- - ${params[0].name}: - ${params[0].value} -
`; + + ${params[0].name}: + ${params[0].value} + `; }, backgroundColor: 'rgba(255, 255, 255, 0.5)', borderColor: '#FFFFFF', @@ -268,7 +340,7 @@ class HomeContent extends React.Component { { type: 'category', data: xAxisData, - axisLine: { lineStyle: { color: '#000' } }, + axisLine: { lineStyle: { color: '#c4c6c9' } }, axisTick: { show: false }, axisLabel: { textStyle: { color: '#000' }, rotate: 30, interval: 0 }, }, @@ -374,17 +446,17 @@ class HomeContent extends React.Component { }, legend: { data: ['培训人次', '培训场次'], - orient: 'vertical', // 垂直排列 - right: 0, // 水平居中 - top: 'middle', // 垂直居中 - itemGap: 16, // 图例项间隔 + orient: 'vertical', + right: 0, + top: 'middle', + itemGap: 16, itemWidth: 18, itemHeight: 12, textStyle: { color: '#000', fontSize: 14 }, }, grid: { left: '5%', - right: '5%', // 为右侧垂直图例留出空间 + right: '5%', top: '18%', bottom: '5%', containLabel: true, @@ -393,8 +465,8 @@ class HomeContent extends React.Component { { type: 'category', data: listNAME, - axisLine: { show: false }, // 隐藏x轴线 - axisTick: { show: false }, // 隐藏x轴刻度线 + axisLine: { lineStyle: { color: '#c4c6c9' } }, + axisTick: { show: false }, axisLabel: { textStyle: { color: '#000' }, rotate: listNAME.length > 4 ? 15 : 0, @@ -405,16 +477,16 @@ class HomeContent extends React.Component { yAxis: [ { type: 'value', - show: true, // 显示y轴 - axisLine: { show: false }, // 隐藏y轴线 - axisTick: { show: false }, // 隐藏y轴刻度线 + show: true, + axisLine: { show: false }, + axisTick: { show: false }, axisLabel: { - show: true, // 显示数值标签 + show: true, textStyle: { color: '#000' }, }, - splitLine: { show: false }, // 隐藏横向网格线 - name: '', // 不显示单位名称 - nameTextStyle: { show: false }, // 隐藏单位文字 + splitLine: { show: false }, + name: '', + nameTextStyle: { show: false }, }, ], series: [ @@ -424,8 +496,8 @@ class HomeContent extends React.Component { data: monthPersonCount, itemStyle: { normal: { - color: '#4285F4', // 蓝色 - barBorderRadius: 12, // 柱体圆角 + color: '#4285F4', + barBorderRadius: 12, }, }, label: { @@ -442,8 +514,8 @@ class HomeContent extends React.Component { data: monthRecordCount, itemStyle: { normal: { - color: '#ffe066', // 黄色 - barBorderRadius: 12, // 柱体圆角(上左、上右、下右、下左) + color: '#ffe066', + barBorderRadius: 12, }, }, label: { @@ -468,6 +540,28 @@ class HomeContent extends React.Component { window.addEventListener('resize', resizeHandler); }; + // 启动自动轮播 + startAutoplay = (intervalTime) => { + this.stopAutoplay(); + if (!intervalTime || intervalTime <= 0) return; + + this.autoplayTimer = setInterval(() => { + if (this.sliderRef.current && !this.isUnmounted) { + const nextIndex = (this.props.currentMediaIndex + 1) % (this.props.trainingData?.listVideoImg?.length || 1); + this.props.onCarouselChange?.(nextIndex); + this.sliderRef.current.slickGoTo(nextIndex); + } + }, intervalTime); + }; + + // 停止自动轮播 + stopAutoplay = () => { + if (this.autoplayTimer) { + clearInterval(this.autoplayTimer); + this.autoplayTimer = null; + } + }; + initAllCharts = () => { if (this.isUnmounted) return; setTimeout(() => { @@ -500,26 +594,52 @@ class HomeContent extends React.Component { }; handlePrev = () => { - if (this.carouselRef.current) { - this.carouselRef.current.prev(); + if (this.sliderRef.current) { + this.sliderRef.current.slickPrev(); + const nextIndex = this.props.currentMediaIndex - 1; + if (nextIndex >= 0) { + this.props.onCarouselChange?.(nextIndex); + } + // 鼠标点击时重启自动播放计时器 + this.restartAutoplay(); } }; handleNext = () => { - if (this.carouselRef.current) { - this.carouselRef.current.next(); + if (this.sliderRef.current) { + this.sliderRef.current.slickNext(); + const nextIndex = this.props.currentMediaIndex + 1; + const total = this.props.trainingData?.listVideoImg?.length || 0; + if (nextIndex < total) { + this.props.onCarouselChange?.(nextIndex); + } + // 鼠标点击时重启自动播放计时器 + this.restartAutoplay(); } }; - handleCarouselChange = (current) => { - this.props.onCarouselChange?.(current); + // 重启自动播放 + restartAutoplay = () => { + const playSet = this.props.trainingData?.playSet || {}; + const isAutoplay = playSet.IMG_ISRE !== undefined ? playSet.IMG_ISRE : true; + const autoplaySpeed = playSet.IMG_TIMESPAN ? playSet.IMG_TIMESPAN * 1000 : 5000; + + if (isAutoplay && autoplaySpeed > 0) { + this.startAutoplay(autoplaySpeed); + } }; handleDotClick = (index) => { this.props.onDotClick?.(index); - if (this.carouselRef.current) { - this.carouselRef.current.goTo(index); + if (this.sliderRef.current) { + this.sliderRef.current.slickGoTo(index); } + // 点击圆点时重启自动播放 + this.restartAutoplay(); + }; + + handleCarouselChange = (current) => { + this.props.onCarouselChange?.(current); }; componentDidMount() { @@ -528,12 +648,20 @@ class HomeContent extends React.Component { } componentDidUpdate(prevProps) { + // 当 trainingData 变化时,重新初始化自动播放 + if ( + prevProps.trainingData?.playSet?.IMG_ISRE !== this.props.trainingData?.playSet?.IMG_ISRE || + prevProps.trainingData?.playSet?.IMG_TIMESPAN !== this.props.trainingData?.playSet?.IMG_TIMESPAN + ) { + this.restartAutoplay(); + } + if ( prevProps.riskTypeRate !== this.props.riskTypeRate || - prevProps.jobTodayTop3 !== this.props.jobTodayTop3 || - prevProps.linkSum !== this.props.linkSum || + prevProps.hiddenSummary !== this.props.hiddenSummary || + prevProps.jobTodayQty !== this.props.jobTodayQty || prevProps.taskTop3 !== this.props.taskTop3 || - prevProps.trainingData !== this.props.trainingData // 新增 + prevProps.trainingData !== this.props.trainingData ) { this.disposeAllCharts(); this.initAllCharts(); @@ -542,18 +670,170 @@ class HomeContent extends React.Component { componentWillUnmount() { this.isUnmounted = true; + this.stopAutoplay(); this.disposeAllCharts(); } + // 新增:下载附件方法 + handleDownload = (file) => { + const fileUrl = configc.picServerHost + file.Nav_ImgFile.FILE_PATH; + const link = document.createElement('a'); + link.href = fileUrl; + link.download = file.Nav_ImgFile.FILE_NAME; + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + }; + // 新增:预览附件方法 + handlePreview = (file) => { + const fileUrl = configc.picServerHost + file.Nav_ImgFile.FILE_PATH; + window.open(fileUrl, '_blank'); + }; + + // 新增:渲染公告弹窗内容 + renderAnnouncementModal = () => { + const { currentAnnouncement, announcementDetailLoading } = this.props; + + if (announcementDetailLoading) { + return ( +
+ +
+ ); + } + + if (!currentAnnouncement) return null; + + const { TITLE, ABSTRACT, START, END, CONTENT, Nav_Files = [] } = currentAnnouncement; + + return ( +
+ {/* 标题 */} +
+ {TITLE} +
+ + {/* 摘要 */} +
+ {ABSTRACT} +
+ + {/* 日期 */} +
+ {START} 至 {END} +
+ + {/* 分割线 */} +
+ + {/* 正文内容 */} +
+ + {/* 附件列表 */} + {Nav_Files && Nav_Files.length > 0 && ( +
+
+ 附件 ({Nav_Files.length}个) +
+
+ {Nav_Files.map((file, index) => ( +
+ + + {file.Nav_ImgFile?.FILE_NAME || '未知文件'} + +
+ + +
+
+ ))} +
+
+ )} +
+ ); + }; render() { const { riskTypeRate = [], - riskTotal = [], - mediaList = [], - announcementList = [], currentMediaIndex = 0, + hiddenSummary = {}, + trainingData = {}, // 新增 props + onAnnouncementClick, + announcementModalVisible, + onAnnouncementModalClose, } = this.props; + // 获取播放配置,提供默认值 + const playSet = trainingData.playSet || {}; + const videoConfig = { + autoPlay: playSet.V_ISAUTO !== undefined ? playSet.V_ISAUTO : true, + loop: playSet.V_ISRE !== undefined ? playSet.V_ISRE : true, + muted: playSet.V_ISSILENT !== undefined ? playSet.V_ISSILENT : true, + controls: playSet.V_ISSHOWCONTROL !== undefined ? playSet.V_ISSHOWCONTROL : false, + }; + + const isAutoplay = playSet.IMG_ISRE !== undefined ? playSet.IMG_ISRE : true; + const autoplaySpeed = playSet.IMG_TIMESPAN ? playSet.IMG_TIMESPAN * 1000 : 5000; + const slickSettings = { + dots: false, + arrows: false, // 🔑 隐藏默认箭头 ← 添加这一行 + infinite: true, + speed: 500, + slidesToShow: 1, + slidesToScroll: 1, + autoplay: isAutoplay, + autoplaySpeed: autoplaySpeed, + fade: true, + pauseOnHover: true, + afterChange: this.handleCarouselChange, + }; + return (
@@ -581,12 +861,18 @@ class HomeContent extends React.Component {
年度隐患数据
- {riskTotal.map((item, index) => ( -
-
{item.name}
-
{item.value}
-
- ))} +
+
年度重大隐患数
+
{hiddenSummary.majorQty}
+
+
+
年度一般隐患数量
+
{hiddenSummary.generalQty}
+
+
+
未整改隐患数量
+
{hiddenSummary.unfinishQty}
+
@@ -598,47 +884,55 @@ class HomeContent extends React.Component { {/* 中间区域 */}
-
安全方针:以人为本、关注健康、依法治企、安全发展。
-
安全理念:一切风险皆可控,一切事故皆可防!
+
- - {mediaList.map((item, index) => ( -
- {item.type === 'video' ? ( -
- ))} -
-
- -
-
- -
-
- {mediaList.map((_, index) => ( - this.handleDotClick(index)} + {trainingData.listVideoImg && trainingData.listVideoImg[0] ? ( + trainingData.listVideoImg[0].TYPE == 10 ? ( +
+ ) : ( +
+ + {trainingData.listVideoImg?.map((item, index) => ( +
+ {`轮播图 +
+ ))} +
+
+ +
+
+ +
+
+ {trainingData.listVideoImg?.map((_, index) => ( + this.handleDotClick(index)} + /> + ))} +
+
+ ) + ) : ( +
+ )}
@@ -648,23 +942,21 @@ class HomeContent extends React.Component { 公司公告
- 共 {announcementList.length} 条公告 + 共 {trainingData.listAnnourcement?.length || 0} 条公告
- {announcementList.length > 0 ? ( + {trainingData.listAnnourcement?.length > 0 ? ( @@ -684,14 +976,13 @@ class HomeContent extends React.Component {
年度培训数据
- {this.props.trainingData?.listNAME?.map((name, index) => ( + {trainingData?.listNAME?.map((name, index) => (
{name}
-
{this.props.trainingData?.YearCount?.[index] || 0}
+
{trainingData?.YearCount?.[index] || 0}
))} - {/* 如果 listNAME 为空,显示默认提示 */} - {(!this.props.trainingData?.listNAME || this.props.trainingData.listNAME.length === 0) && ( + {(!trainingData?.listNAME || trainingData.listNAME.length === 0) && (
暂无培训数据
)} @@ -708,6 +999,21 @@ class HomeContent extends React.Component {
+ {/* 公告详情弹窗 */} + + 关闭 + , + ]} + width="80%" + bodyStyle={{ padding: '20px', maxHeight: '70vh', overflowY: 'auto' }} + > + {this.renderAnnouncementModal()} +
); } diff --git a/src/layout/FullOther/RiskControl.js b/src/layout/FullOther/RiskControl.js new file mode 100644 index 0000000..a8232aa --- /dev/null +++ b/src/layout/FullOther/RiskControl.js @@ -0,0 +1,515 @@ +// RiskControl.js - 风险管控页面组件 +import React from 'react'; +import { Table } from 'antd'; +import styles from './../fullinter.less'; +import echarts from 'echarts'; + +class RiskControl extends React.Component { + constructor(props) { + super(props); + this.echartsInstances = { + stackBarChart: null, // 堆叠柱状图 + typeBarChart: null, // 风险类别柱状图 + }; + this.chartResizeHandlers = {}; + this.isUnmounted = false; + } + + waitForElement = (elementId, maxRetries = 10) => { + return new Promise((resolve) => { + let retries = 0; + const checkInterval = setInterval(() => { + const element = document.getElementById(elementId); + if (element || retries >= maxRetries) { + clearInterval(checkInterval); + resolve(!!element); + } + retries++; + }, 50); + }); + }; + + // 堆叠柱状图: 各公司风险统计(与 trainingContent 样式一致) + renderStackBarChart = async () => { + if (this.isUnmounted) return; + const elementExists = await this.waitForElement('stackBarChart'); + if (!elementExists || this.isUnmounted) return; + + if (this.echartsInstances.stackBarChart) { + this.echartsInstances.stackBarChart.dispose(); + this.echartsInstances.stackBarChart = null; + } + + const chartDom = document.getElementById('stackBarChart'); + if (!chartDom) return; + + this.echartsInstances.stackBarChart = echarts.init(chartDom); + + const { riskSubData } = this.props; + const riskList = riskSubData?.riskList || []; + + if (riskList.length === 0) { + this.echartsInstances.stackBarChart.setOption({ + title: { + text: '各家公司的风险统计情况', + x: 'center', + y: '25%', + textStyle: { fontSize: 16, color: '#999' }, + }, + graphic: { + type: 'text', + left: 'center', + top: 'middle', + style: { + text: '暂无数据', + fill: '#999', + fontSize: 14, + }, + }, + }); + return; + } + + const companyNames = riskList.map((item) => item.companyName); + const majorCounts = riskList.map((item) => item.majorCount); + const largerCounts = riskList.map((item) => item.largerCount); + const generalCounts = riskList.map((item) => item.generalCount); + const lowCounts = riskList.map((item) => item.lowCount); + + const option = { + title: { + text: '各家公司的风险统计情况', + x: 'center', + y: '5%', + textStyle: { fontSize: 16, color: '#000', fontWeight: 'bold' }, + }, + tooltip: { + trigger: 'axis', + axisPointer: { type: 'shadow' }, + formatter: function (params) { + let result = `${params[0].axisValue}
`; + params.forEach((param) => { + result += `${param.marker}${param.seriesName}: ${param.value}
`; + }); + return result; + }, + }, + legend: { + data: ['重大风险', '较大风险', '一般风险', '低风险'], + orient: 'vertical', + right: '3%', + top: '5%', + itemGap: 16, + itemWidth: 18, + itemHeight: 12, + textStyle: { color: '#000', fontSize: 14 }, + }, + grid: { + left: '8%', + right: '5%', + top: '18%', + bottom: '8%', + containLabel: true, + }, + xAxis: [ + { + type: 'category', + data: companyNames, + axisLine: { show: false }, + axisTick: { show: false }, + axisLabel: { + textStyle: { color: '#000' }, + rotate: companyNames.length > 4 ? 15 : 0, + interval: 0, + fontSize: 12, + }, + }, + ], + yAxis: [ + { + type: 'value', + show: true, + axisLine: { show: false }, + axisTick: { show: false }, + axisLabel: { + show: true, + textStyle: { color: '#000' }, + }, + splitLine: { show: false }, + name: '风险数量', + nameTextStyle: { fontSize: 12 }, + }, + ], + series: [ + { + name: '重大风险', + type: 'bar', + stack: 'total', + data: majorCounts, + itemStyle: { normal: { color: '#c92a2a' } }, + label: { + show: true, + position: 'inside', + textStyle: { color: '#fff', fontSize: 11 }, + formatter: (params) => (params.value > 0 ? params.value : ''), + }, + barWidth: '35%', + }, + { + name: '较大风险', + type: 'bar', + stack: 'total', + data: largerCounts, + itemStyle: { normal: { color: '#ffa94d' } }, + label: { + show: true, + position: 'inside', + textStyle: { color: '#fff', fontSize: 11 }, + formatter: (params) => (params.value > 0 ? params.value : ''), + }, + barWidth: '35%', + }, + { + name: '一般风险', + type: 'bar', + stack: 'total', + data: generalCounts, + itemStyle: { normal: { color: '#ffe066' } }, + label: { + show: true, + position: 'inside', + textStyle: { color: '#fff', fontSize: 11 }, + formatter: (params) => (params.value > 0 ? params.value : ''), + }, + barWidth: '35%', + }, + { + name: '低风险', + type: 'bar', + stack: 'total', + data: lowCounts, + itemStyle: { normal: { color: '#4285F4' } }, + label: { + show: true, + position: 'inside', + textStyle: { color: '#fff', fontSize: 11 }, + formatter: (params) => (params.value > 0 ? params.value : ''), + }, + barWidth: '35%', + }, + ], + }; + + this.echartsInstances.stackBarChart.setOption(option); + this.setupResizeHandler('stackBarChart', this.renderStackBarChart); + }; + + // 风险类别柱状图 + renderTypeBarChart = async () => { + if (this.isUnmounted) return; + const elementExists = await this.waitForElement('typeBarChart'); + if (!elementExists || this.isUnmounted) return; + + if (this.echartsInstances.typeBarChart) { + this.echartsInstances.typeBarChart.dispose(); + this.echartsInstances.typeBarChart = null; + } + + const chartDom = document.getElementById('typeBarChart'); + if (!chartDom) return; + + this.echartsInstances.typeBarChart = echarts.init(chartDom); + + const { riskSubData } = this.props; + const riskTypeList = riskSubData?.riskTypeList || []; + + if (riskTypeList.length === 0) { + this.echartsInstances.typeBarChart.setOption({ + title: { + text: '风险类别统计情况', + x: 'center', + y: '25%', + textStyle: { fontSize: 16, color: '#999' }, + }, + graphic: { + type: 'text', + left: 'center', + top: 'middle', + style: { + text: '暂无数据', + fill: '#999', + fontSize: 14, + }, + }, + }); + return; + } + + const typeNames = riskTypeList.map((item) => item.typeName); + const quantities = riskTypeList.map((item) => item.qty); + + const option = { + title: { + text: '风险类别统计情况', + x: 'center', + y: '5%', + textStyle: { fontSize: 16, color: '#000', fontWeight: 'bold' }, + }, + tooltip: { + trigger: 'axis', + axisPointer: { type: 'shadow' }, + formatter: function (params) { + let result = `${params[0].axisValue}
`; + params.forEach((param) => { + result += `${param.marker}${param.seriesName}: ${param.value}
`; + }); + return result; + }, + }, + grid: { + left: '8%', + right: '5%', + top: '18%', + bottom: '8%', + containLabel: true, + }, + xAxis: [ + { + type: 'category', + data: typeNames, + axisLine: { show: false }, + axisTick: { show: false }, + axisLabel: { + textStyle: { color: '#000' }, + // rotate: typeNames.length > 4 ? 15 : 0, + interval: 0, + fontSize: 12, + }, + }, + ], + yAxis: [ + { + type: 'value', + show: true, + axisLine: { show: false }, + axisTick: { show: false }, + axisLabel: { + show: true, + textStyle: { color: '#000' }, + }, + splitLine: { show: false }, + }, + ], + series: [ + { + name: '风险数量', + type: 'bar', + data: quantities, + itemStyle: { + normal: { + color: '#4285F4', // 与 trainingContent 一致的蓝色 + }, + }, + label: { + show: true, + position: 'top', + textStyle: { color: '#4285F4', fontSize: 12 }, + formatter: (params) => `${params.value}`, + }, + barWidth: '50%', + }, + ], + }; + + this.echartsInstances.typeBarChart.setOption(option); + this.setupResizeHandler('typeBarChart', this.renderTypeBarChart); + }; + + // 表格: 各公司风险统计明细表 + renderRiskTable = () => { + const { riskSubData } = this.props; + const riskList = riskSubData?.riskList || []; + + if (riskList.length === 0) { + return
暂无风险数据
; + } + + // 表格列配置(与 trainingContent 样式保持一致) + const columns = [ + { + title: '公司名称', + dataIndex: 'companyName', + key: 'companyName', + align: 'center', + width: 120, + render: (text) => {text}, + }, + { + title: '重大风险', + dataIndex: 'majorCount', + key: 'majorCount', + align: 'center', + width: 100, + render: (text) => {text}, + }, + { + title: '较大风险', + dataIndex: 'largerCount', + key: 'largerCount', + align: 'center', + width: 100, + render: (text) => {text}, + }, + { + title: '一般风险', + dataIndex: 'generalCount', + key: 'generalCount', + align: 'center', + width: 100, + render: (text) => {text}, + }, + { + title: '低风险', + dataIndex: 'lowCount', + key: 'lowCount', + align: 'center', + width: 100, + render: (text) => {text}, + }, + { + title: '小计', + dataIndex: 'totalCount', + key: 'totalCount', + align: 'center', + width: 100, + render: (text) => {text}, + }, + ]; + + const tableData = riskList.map((item, index) => ({ + key: index, + companyName: item.companyName, + majorCount: item.majorCount, + largerCount: item.largerCount, + generalCount: item.generalCount, + lowCount: item.lowCount, + totalCount: item.totalCount, + })); + + // 合计行 + const summary = { + majorTotal: riskList.reduce((sum, item) => sum + (item.majorCount || 0), 0), + largerTotal: riskList.reduce((sum, item) => sum + (item.largerCount || 0), 0), + generalTotal: riskList.reduce((sum, item) => sum + (item.generalCount || 0), 0), + lowTotal: riskList.reduce((sum, item) => sum + (item.lowCount || 0), 0), + totalAll: riskList.reduce((sum, item) => sum + (item.totalCount || 0), 0), + }; + const scrollConfig = columns.length > 10 ? { x: columns.length * 100, y: 380 } : {}; + return ( +
+
+ 各家公司的风险统计情况 +
+
+ + + + ); + }; + + setupResizeHandler = (chartName, renderMethod) => { + const resizeHandler = () => { + if (this.echartsInstances[chartName] && !this.isUnmounted) { + this.echartsInstances[chartName].resize(); + } + }; + this.chartResizeHandlers[chartName] = resizeHandler; + window.addEventListener('resize', resizeHandler); + }; + + initAllCharts = () => { + if (this.isUnmounted) return; + setTimeout(() => { + if (this.isUnmounted) return; + this.renderStackBarChart(); + this.renderTypeBarChart(); + }, 100); + }; + + disposeAllCharts = () => { + Object.keys(this.echartsInstances).forEach((key) => { + if (this.echartsInstances[key]) { + try { + this.echartsInstances[key].dispose(); + } catch (e) { + console.warn(`Dispose chart ${key} error:`, e); + } + this.echartsInstances[key] = null; + } + }); + + Object.keys(this.chartResizeHandlers).forEach((key) => { + if (this.chartResizeHandlers[key]) { + window.removeEventListener('resize', this.chartResizeHandlers[key]); + } + }); + this.chartResizeHandlers = {}; + }; + + componentDidMount() { + this.isUnmounted = false; + this.initAllCharts(); + } + + componentDidUpdate(prevProps) { + if (prevProps.riskSubData !== this.props.riskSubData) { + this.renderStackBarChart(); + this.renderTypeBarChart(); + } + } + + componentWillUnmount() { + this.isUnmounted = true; + this.disposeAllCharts(); + } + + render() { + return ( +
+
+ {/* 第一行 - 表格 + 堆叠柱状图 */} +
+
{this.renderRiskTable()}
+
+
+
+
+ {/* 第二行 - 风险类别柱状图 */} +
+
+
+
+
+
+
+ ); + } +} + +export default RiskControl; diff --git a/src/layout/FullOther/TrainingContent.js b/src/layout/FullOther/TrainingContent.js index e115a23..f8e7401 100644 --- a/src/layout/FullOther/TrainingContent.js +++ b/src/layout/FullOther/TrainingContent.js @@ -226,7 +226,7 @@ class TrainingContent extends React.Component { if (companyNames.length === 0) { this.echartsInstances.chart2.setOption({ title: { - text: '本年度各公司培训分析', + text: '各公司月度培训分析', x: 'center', y: '25%', textStyle: { fontSize: 16, color: '#999' }, @@ -247,7 +247,7 @@ class TrainingContent extends React.Component { const option = { title: { - text: '本年度各公司培训分析', + text: '各公司月度培训分析', x: 'center', y: '5%', textStyle: { fontSize: 16, color: '#000', fontWeight: 'bold' }, @@ -693,7 +693,7 @@ class TrainingContent extends React.Component { color: '#000', }} > - 特种作业操作证统计 + 各公司证件统计
{this.renderCertificateTable()}
diff --git a/src/layout/FullScreenInter.js b/src/layout/FullScreenInter.js index a7964a5..ecb8356 100644 --- a/src/layout/FullScreenInter.js +++ b/src/layout/FullScreenInter.js @@ -2,14 +2,18 @@ import React from 'react'; import { connect } from 'dva'; import { withRouter } from 'dva/router'; -import { Icon, Row, Col } from 'antd'; +import { Icon, Row, Col, message } from 'antd'; import styles from './fullinter.less'; import logo from '../assets/layout/headerno-logo-new.png'; import debounce from 'lodash.debounce'; import storage from '../utils/storage'; -import { initFilter } from '../utils/common'; +import { initFilter, extendRule } from '../utils/common'; import HomeContent from './FullOther/HomeContent'; -import TrainingContent from './FullOther/TrainingContent'; // 新增导入 +import TrainingContent from './FullOther/TrainingContent'; +import RiskControl from './FullOther/RiskControl'; +import ClassBuild from './FullOther/ClassBuild'; +import DangerJob from './FullOther/DangerJob'; +import HiddenSolve from './FullOther/HiddenSolve'; const getScale = () => { const width = 1920, @@ -25,13 +29,9 @@ class FullScreen extends React.Component { this.state = { nowDate: '', riskTypeRate: [], - riskTotal: [ - { name: '年度重大隐患数', value: 1 }, - { name: '年度一般隐患数量', value: 89 }, - { name: '未整改隐患数量', value: 18 }, - ], - linkSum: [], - jobTodayTop3: [], + riskTotal: [], + jobTodayQty: [], + hiddenSummary: [], taskTop3: [], scale: getScale(), configBanner: ['首页', '风险管控', '隐患治理', '班组建设', '危险作业', '安全培训'], @@ -39,14 +39,17 @@ class FullScreen extends React.Component { currentMediaIndex: 0, announcementList: [], activeTab: '首页', - // 新增:培训数据 + // 安全培训相关数据 trainingData: { listNAME: [], YearCount: [], MonthRecordCount: [], MonthPersonCount: [], + listAnnourcement: [], + TITLE: '', + listVideoImg: [], + playSet: [], }, - // 新增:安全培训页面专用数据 trainingSubData: { listNAME: [], YearCount: [], @@ -61,12 +64,29 @@ class FullScreen extends React.Component { }, trainingSubIDCard: [], trainingSubBSType: {}, - companyData: [], - // 新增:当前选择的月份 - selectedMonth: new Date().getMonth() + 1, // 默认当前月份,1-12 - selectedCompany: '', + trainingCompanyData: [], // 安全培训专用公司数据 + selectedTrainingMonth: new Date().getMonth() + 1, + selectedTrainingCompany: '', + + // 隐患治理相关数据 + hiddenSubData: {}, // 隐患治理数据 + hiddenCompanyData: [], // 隐患治理专用公司数据 + selectedHiddenCompany: '', // 隐患治理选中的公司 + + // 其他页面数据 + riskSubData: {}, + classSubData: {}, + dangerSubData: {}, + dangerCompanyData: [], + selectedDangerCompany: '', + + announcementDetail: null, + announcementDetailLoading: false, + announcementModalVisible: false, + currentAnnouncement: null, }; this.isUnmounted = false; + this.dataTimer = null; // 新增:用于数据定时请求 } setScale = debounce(() => { @@ -81,15 +101,56 @@ class FullScreen extends React.Component { document.exitFullscreen?.(); } }; + startDataRefreshTimer = () => { + // 清除已有定时器 + if (this.dataTimer) { + clearInterval(this.dataTimer); + } + + // 设置新的定时器,每5分钟执行一次 + this.dataTimer = setInterval( + () => { + if (!this.isUnmounted) { + if (this.state.activeTab === '首页') { + this.getHomeDataArray(); + this.getYearPXData(); + } + // 安全培训 - 参考原有逻辑 + else if (this.state.activeTab === '安全培训') { + this.getTrainingCompanyData(); // 先获取公司列表 + this.getHomeSESubYearData(); + this.getHomeSESubYearMonthData(); + this.getHomeSESubIDCardData(); + } + // 隐患治理 - 参考安全培训的调用顺序 + else if (this.state.activeTab === '隐患治理') { + this.getHiddenCompanyData(); // 先获取公司列表(与安全培训独立) + } + // 风险管控 + else if (this.state.activeTab === '风险管控') { + this.getRiskSubData(); + } + // 班组建设 + else if (this.state.activeTab === '班组建设') { + this.getClassSubData(); + } + // 危险作业 + else if (this.state.activeTab === '危险作业') { + this.getDangerCompanyData(); + } + } + }, + 5 * 60 * 1000 + ); // 5分钟 = 300000毫秒 + }; componentDidMount() { this.isUnmounted = false; window.addEventListener('resize', this.setScale); this.getHomeDataArray(); this.getYearPXData(); - this.loadMediaFiles(); - this.getAnnouncementData(); - + // 启动定时刷新 + this.startDataRefreshTimer(); this.timer = setInterval(() => { if (!this.isUnmounted) this.setState({ nowDate: this.getDate() }); }, 1000); @@ -97,24 +158,56 @@ class FullScreen extends React.Component { document.addEventListener('fullscreenchange', this.handleFullscreenChange); } - getAnnouncementData = () => { - this.setState({ - announcementList: [ - { id: 1, title: '关于2024年安全生产月活动的通知', publishTime: '2024-06-01', url: '' }, - { id: 2, title: '公司第三季度安全培训安排', publishTime: '2024-05-28', url: '' }, - { id: 3, title: '关于开展安全隐患排查整治工作的通知', publishTime: '2024-05-20', url: '' }, - { id: 4, title: '安全生产标准化建设阶段性总结', publishTime: '2024-05-15', url: '' }, - { id: 5, title: '关于表彰2024年第一季度安全生产先进单位的决定', publishTime: '2024-05-10', url: '' }, - ], + getAnnouncementDetail = (announcement) => { + return new Promise((resolve, reject) => { + const orgId = storage('lacal').getItem('webOrgId')?.val; + const json = initFilter(orgId); + extendRule(json, 'ID', 1, announcement.ID); + json.Include = ['Nav_Orgs', 'Nav_Orgs.Nav_OrgSub', 'Nav_Files.Nav_ImgFile']; + + this.setState({ announcementDetailLoading: true, announcementDetail: null }); + + this.props.dispatch({ + type: 'app/getDataByPost', + payload: json, + url: 'PF/Annourcement/Get', + onComplete: (ret) => { + if (ret && !this.isUnmounted) { + this.setState({ + announcementDetail: ret, + announcementDetailLoading: false, + }); + resolve(ret); + } else { + message.error('暂无公告详情'); + this.setState({ announcementDetailLoading: false }); + reject(new Error('获取公告详情失败')); + } + }, + onError: (error) => { + this.setState({ announcementDetailLoading: false }); + reject(error); + }, + }); }); }; - loadMediaFiles = () => { + handleAnnouncementClick = async (announcement) => { + try { + const detail = await this.getAnnouncementDetail(announcement); + this.setState({ + currentAnnouncement: detail, + announcementModalVisible: true, + }); + } catch (error) { + console.error('获取公告详情失败:', error); + } + }; + + handleAnnouncementModalClose = () => { this.setState({ - mediaList: [ - { type: 'image', url: 'http://10.2.7.18:28028//WZ_Images/static/smyzw@2x.png' }, - { type: 'video', url: 'http://10.2.7.18:28028/WZ_Images/Img_JFSC/welcom/1.mp4' }, - ], + announcementModalVisible: false, + currentAnnouncement: null, }); }; @@ -129,17 +222,31 @@ class FullScreen extends React.Component { handleTabClick = (name) => { this.setState({ activeTab: name }); if (name === '首页') { + this.getHomeDataArray(); this.getYearPXData(); - this.loadMediaFiles(); - this.getAnnouncementData(); } - // 当点击安全培训tab时,获取对应数据 + // 安全培训 - 参考原有逻辑 if (name === '安全培训') { + this.getTrainingCompanyData(); // 先获取公司列表 this.getHomeSESubYearData(); this.getHomeSESubYearMonthData(); this.getHomeSESubIDCardData(); - // this.getBSTypeMonthData(); - this.getCompanyData(); + } + // 隐患治理 - 参考安全培训的调用顺序 + if (name === '隐患治理') { + this.getHiddenCompanyData(); // 先获取公司列表(与安全培训独立) + } + // 风险管控 + if (name === '风险管控') { + this.getRiskSubData(); + } + // 班组建设 + if (name === '班组建设') { + this.getClassSubData(); + } + // 危险作业 + if (name === '危险作业') { + this.getDangerCompanyData(); } }; @@ -151,6 +258,9 @@ class FullScreen extends React.Component { this.isUnmounted = true; window.removeEventListener('resize', this.setScale); if (this.timer) clearInterval(this.timer); + // 清除数据刷新定时器 + if (this.dataTimer) clearInterval(this.dataTimer); + document.removeEventListener('fullscreenchange', this.handleFullscreenChange); } @@ -179,14 +289,15 @@ class FullScreen extends React.Component { if (ret && !this.isUnmounted) { this.setState({ riskTypeRate: ret.riskTypeRate || [], - linkSum: ret.linkSum || [], - jobTodayTop3: ret.jobTodayTop3 || [], + jobTodayQty: ret.jobTodayQty || [], + hiddenSummary: ret.hiddenSummary || [], taskTop3: ret.taskTop3 || [], }); } }, }); }; + getYearPXData = () => { const orgId = storage('lacal').getItem('webOrgId')?.val; const json = initFilter(orgId); @@ -202,29 +313,63 @@ class FullScreen extends React.Component { YearCount: ret.YearCount || [], MonthRecordCount: ret.MonthRecordCount || [], MonthPersonCount: ret.MonthPersonCount || [], + listAnnourcement: ret.listAnnourcement || [], + TITLE: ret.TITLE || '', + listVideoImg: ret.listVideoImg || [], + playSet: ret.playSet || [], }, }); } }, }); }; - // 获取安全培训页面数据 + + // ==================== 安全培训相关方法 ==================== + getTrainingCompanyData = () => { + const orgId = storage('lacal').getItem('webOrgId')?.val; + const json = initFilter(orgId); + this.props.dispatch({ + type: 'app/getDataByPost', + payload: json, + url: 'FM/Organization/OrderPaged', + onComplete: (ret) => { + if (ret && !this.isUnmounted) { + // 默认选中邦泰公司 + const defaultCompany = ret.find((company) => company.ID === '00500000-0000-0000-0000-000000000000'); + this.setState({ + trainingCompanyData: ret, + }); + if (defaultCompany && !this.state.selectedTrainingCompany) { + this.setState( + { + selectedTrainingCompany: defaultCompany.ID, + }, + () => { + this.getBSTypeMonthData(); // 公司设置完成后获取培训类型数据 + } + ); + } else { + this.getBSTypeMonthData(); + } + } + }, + }); + }; + getHomeSESubYearData = () => { const orgId = storage('lacal').getItem('webOrgId')?.val; const json = initFilter(orgId); - const currentYear = new Date().getFullYear(); // 获取当前年份 - json.Parameter1 = currentYear.toString(); // 设置为当前年份 + const currentYear = new Date().getFullYear(); + json.Parameter1 = currentYear.toString(); this.props.dispatch({ type: 'app/getDataByPost', payload: json, url: 'BI/BIHeadSE/HomeSESubYear', onComplete: (ret) => { if (ret && !this.isUnmounted) { - // ret 是数组格式: [{ CName: "邦泰", PCount: 0, RCount: 13 }, ...] const listNAME = ret.map((item) => item.CName); - const MonthPersonCount = ret.map((item) => item.PCount); // 培训人次 - const MonthRecordCount = ret.map((item) => item.RCount); // 培训场次 - // YearCount 暂时用 PCount 的总和或保持为空数组 + const MonthPersonCount = ret.map((item) => item.PCount); + const MonthRecordCount = ret.map((item) => item.RCount); const YearCount = ret.map((item) => item.PCount); this.setState({ @@ -239,38 +384,34 @@ class FullScreen extends React.Component { }, }); }; - // 新增:处理月份变化 - handleMonthChange = (month) => { - this.setState({ selectedMonth: month }, () => { - // 重新获取月份数据 + + handleTrainingMonthChange = (month) => { + this.setState({ selectedTrainingMonth: month }, () => { this.getHomeSESubYearMonthData(); }); }; - handleCompanyChange = (company) => { - console.log(company, '11111'); - this.setState({ selectedCompany: company }, () => { - // 重新获取月份数据 + + handleTrainingCompanyChange = (company) => { + this.setState({ selectedTrainingCompany: company }, () => { this.getBSTypeMonthData(); }); }; - // 获取安全培训页面数据 + getHomeSESubYearMonthData = () => { const orgId = storage('lacal').getItem('webOrgId')?.val; const json = initFilter(orgId); - const currentYear = new Date().getFullYear(); // 获取当前年份 - json.Parameter1 = currentYear.toString(); // 设置为当前年份 - json.Parameter2 = this.state.selectedMonth.toString(); // 月份 + const currentYear = new Date().getFullYear(); + json.Parameter1 = currentYear.toString(); + json.Parameter2 = this.state.selectedTrainingMonth.toString(); this.props.dispatch({ type: 'app/getDataByPost', payload: json, url: 'BI/BIHeadSE/HomeSESubYear', onComplete: (ret) => { if (ret && !this.isUnmounted) { - // ret 是数组格式: [{ CName: "邦泰", PCount: 0, RCount: 13 }, ...] const listNAME = ret.map((item) => item.CName); - const MonthPersonCount = ret.map((item) => item.PCount); // 培训人次 - const MonthRecordCount = ret.map((item) => item.RCount); // 培训场次 - // YearCount 暂时用 PCount 的总和或保持为空数组 + const MonthPersonCount = ret.map((item) => item.PCount); + const MonthRecordCount = ret.map((item) => item.RCount); const YearCount = ret.map((item) => item.PCount); this.setState({ @@ -285,6 +426,7 @@ class FullScreen extends React.Component { }, }); }; + getHomeSESubIDCardData = () => { const orgId = storage('lacal').getItem('webOrgId')?.val; const json = initFilter(orgId); @@ -292,9 +434,9 @@ class FullScreen extends React.Component { const year = currentDate.getFullYear(); const month = String(currentDate.getMonth() + 1).padStart(2, '0'); const day = String(currentDate.getDate()).padStart(2, '0'); - const currentDateStr = `${year}-${month}-${day}`; // 例如:2024-06-15 + const currentDateStr = `${year}-${month}-${day}`; - json.Parameter1 = currentDateStr; // 设置为当前日期 + json.Parameter1 = currentDateStr; this.props.dispatch({ type: 'app/getDataByPost', payload: json, @@ -308,6 +450,7 @@ class FullScreen extends React.Component { }, }); }; + getBSTypeMonthData = () => { const orgId = storage('lacal').getItem('webOrgId')?.val; const json = initFilter(orgId); @@ -315,11 +458,11 @@ class FullScreen extends React.Component { const year = currentDate.getFullYear(); const month = String(currentDate.getMonth() + 1).padStart(2, '0'); const day = String(currentDate.getDate()).padStart(2, '0'); - const currentDateStr = `${year}-${month}-${day}`; // 例如:2024-06-15 + const currentDateStr = `${year}-${month}-${day}`; - json.Parameter1 = currentDateStr; // 设置为当前日期 - if (this.state.selectedCompany) { - json.Parameter2 = this.state.selectedCompany; + json.Parameter1 = currentDateStr; + if (this.state.selectedTrainingCompany) { + json.Parameter2 = this.state.selectedTrainingCompany; } this.props.dispatch({ type: 'app/getDataByPost', @@ -334,7 +477,10 @@ class FullScreen extends React.Component { }, }); }; - getCompanyData = () => { + + // ==================== 隐患治理相关方法 ==================== + // 获取隐患治理的公司列表(独立于安全培训) + getHiddenCompanyData = () => { const orgId = storage('lacal').getItem('webOrgId')?.val; const json = initFilter(orgId); this.props.dispatch({ @@ -343,18 +489,155 @@ class FullScreen extends React.Component { url: 'FM/Organization/OrderPaged', onComplete: (ret) => { if (ret && !this.isUnmounted) { - //加载页面默认为绑泰 + // 默认选中邦泰公司 const defaultCompany = ret.find((company) => company.ID === '00500000-0000-0000-0000-000000000000'); this.setState({ - companyData: ret, + hiddenCompanyData: ret, }); - // 如果找到了默认公司,设置 selectedCompany - if (defaultCompany && !this.state.selectedCompany) { + if (defaultCompany && !this.state.selectedHiddenCompany) { this.setState({ - selectedCompany: defaultCompany.ID, + selectedHiddenCompany: defaultCompany.ID, }); } - this.getBSTypeMonthData(); + this.getHiddenSubData(); + } + }, + }); + }; + + // 隐患治理公司筛选变化处理 + handleHiddenCompanyChange = (company) => { + this.setState({ selectedHiddenCompany: company }, () => { + this.getHiddenSubData(); + }); + }; + + getHiddenSubData = () => { + // 以下是真实的API调用代码,如需使用请取消注释 + const orgId = storage('lacal').getItem('webOrgId')?.val; + const json = initFilter(orgId); + this.props.dispatch({ + type: 'app/getDataByPost', + payload: json, + url: 'BI/BIKanBanController/HiddenManage', + onComplete: (ret) => { + if (ret && !this.isUnmounted) { + let filteredData = ret; + if (this.state.selectedHiddenCompany) { + const selectedCompanyObj = this.state.hiddenCompanyData?.find( + (company) => company.ID === this.state.selectedHiddenCompany + ); + const selectedCompanyName = selectedCompanyObj?.NAME; + + if (selectedCompanyName) { + // 筛选 hiddenRanking + filteredData.hiddenRanking = filteredData.hiddenRanking.filter( + (item) => item.companyName === selectedCompanyName + ); + } + } + this.setState({ + hiddenSubData: filteredData, + }); + } + }, + }); + }; + + // ==================== 其他页面方法 ==================== + getRiskSubData = () => { + const orgId = storage('lacal').getItem('webOrgId')?.val; + const json = initFilter(orgId); + this.props.dispatch({ + type: 'app/getDataByPost', + payload: json, + url: 'BI/BIKanBanController/RiskManage', + onComplete: (ret) => { + if (ret && !this.isUnmounted) { + this.setState({ + riskSubData: ret, + }); + } + }, + }); + }; + + getClassSubData = () => { + const orgId = storage('lacal').getItem('webOrgId')?.val; + const json = initFilter(orgId); + this.props.dispatch({ + type: 'app/getDataByPost', + payload: json, + url: 'BI/BIKanBanController/TeamManage', + onComplete: (ret) => { + if (ret && !this.isUnmounted) { + this.setState({ + classSubData: ret, + }); + } + }, + }); + }; + + getDangerCompanyData = () => { + const orgId = storage('lacal').getItem('webOrgId')?.val; + const json = initFilter(orgId); + this.props.dispatch({ + type: 'app/getDataByPost', + payload: json, + url: 'FM/Organization/OrderPaged', + onComplete: (ret) => { + if (ret && !this.isUnmounted) { + const defaultCompany = ret.find((company) => company.ID === '00500000-0000-0000-0000-000000000000'); + this.setState({ + dangerCompanyData: ret, + }); + if (defaultCompany && !this.state.selectedDangerCompany) { + this.setState( + { + selectedDangerCompany: defaultCompany.ID, + }, + () => { + this.getDangerSubData(); + } + ); + } else { + this.getDangerSubData(); + } + } + }, + }); + }; + + handleDangerCompanyChange = (company) => { + this.setState({ selectedDangerCompany: company }, () => { + this.getDangerSubData(); + }); + }; + + getDangerSubData = () => { + const orgId = storage('lacal').getItem('webOrgId')?.val; + const json = initFilter(orgId); + this.props.dispatch({ + type: 'app/getDataByPost', + payload: json, + url: 'BI/BIKanBanController/JobManage', + onComplete: (ret) => { + if (ret && !this.isUnmounted) { + let filteredData = ret; + if (this.state.selectedDangerCompany) { + const selectedCompanyObj = this.state.dangerCompanyData?.find( + (company) => company.ID === this.state.selectedDangerCompany + ); + const selectedCompanyName = selectedCompanyObj?.NAME; + if (selectedCompanyName) { + filteredData = filteredData.filter((item) => item.companyName === selectedCompanyName); + } + } + + this.setState({ + dangerSubData: filteredData, + }); } }, }); @@ -363,32 +646,61 @@ class FullScreen extends React.Component { renderOtherTabContent = () => { const { activeTab, - trainingData, trainingSubData, - trainingSubDataMonth, - selectedMonth, - selectedCompany, trainingSubBSType, - companyData, + trainingCompanyData, + trainingSubDataMonth, + selectedTrainingMonth, + selectedTrainingCompany, trainingSubIDCard, + riskSubData, + classSubData, + hiddenSubData, + hiddenCompanyData, + selectedHiddenCompany, } = this.state; - // 如果是安全培训,显示专门的培训页面 + if (activeTab === '安全培训') { return ( ); } + if (activeTab === '风险管控') { + return ; + } + if (activeTab === '隐患治理') { + return ( + + ); + } + if (activeTab === '班组建设') { + return ; + } + if (activeTab === '危险作业') { + return ( + + ); + } return (
@@ -408,13 +720,16 @@ class FullScreen extends React.Component { activeTab, riskTypeRate, riskTotal, - jobTodayTop3, - linkSum, + hiddenSummary, + jobTodayQty, taskTop3, mediaList, announcementList, currentMediaIndex, trainingData, + announcementModalVisible, + currentAnnouncement, + announcementDetailLoading, } = this.state; return ( @@ -470,15 +785,20 @@ class FullScreen extends React.Component { ) : ( this.renderOtherTabContent() diff --git a/src/layout/fullinter.less b/src/layout/fullinter.less index b9bccfe..54e5648 100644 --- a/src/layout/fullinter.less +++ b/src/layout/fullinter.less @@ -281,7 +281,7 @@ justify-content: center; font-weight: bold; color: #fff; - background-color: #1e2a3a; + background-color: #909399; margin: 10px 20px; padding: 5px 10px; border-radius: 4px; @@ -313,7 +313,7 @@ justify-content: center; font-weight: bold; color: #fff; - background-color: #1e2a3a; + background-color: #909399; margin: 10px 20px; padding: 5px 10px; border-radius: 4px; @@ -492,38 +492,59 @@ margin-bottom: 16px; } -// 轮播图控件 .customArrowLeft, .customArrowRight { position: absolute; top: 50%; transform: translateY(-50%); - width: 40px; - height: 60px; + width: 44px; + height: 64px; display: flex; align-items: center; justify-content: center; cursor: pointer; z-index: 10; transition: all 0.3s ease; - backdrop-filter: blur(4px); + border-radius: 8px; + + // 深色半透明背景,确保在任何背景下都可见 + background-color: rgba(0, 0, 0, 0.5); + + // 添加白色外发光 + box-shadow: 0 0 8px rgba(255, 255, 255, 0.3); + + // 图标颜色设为白色 + i, + .anticon { + color: #ffffff !important; + text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.5); + } &:hover { - background-color: rgba(0, 0, 0, 0.3); - transform: translateY(-50%) scale(1.1); + background-color: rgba(0, 0, 0, 0.7); + transform: translateY(-50%) scale(1.05); + box-shadow: 0 0 12px rgba(255, 255, 255, 0.5); + + i, + .anticon { + color: #ffffff !important; + transform: scale(1.1); + } } &:active { - transform: translateY(-50%) scale(0.95); + transform: translateY(-50%) scale(0.98); } } .customArrowLeft { - left: 20px; + left: 16px; + border-radius: 0 8px 8px 0; } .customArrowRight { - right: 20px; + right: 16px; + border-radius: 8px 0 0 8px; } .customDots { @@ -691,19 +712,89 @@ white-space: nowrap; } - // 奇数行背景色 - .ant-table-tbody > tr:nth-child(odd) > td { - background-color: #d4d5e3; - } - - // 偶数行背景色 - .ant-table-tbody > tr:nth-child(even) > td { - background-color: #d0cedc; - } - // hover 效果 .ant-table-tbody > tr:hover > td { background-color: #e6f7ff; } } } + +// fullinter.less - 在文件末尾添加 + +// 公告内容富文本样式 +.announcementContent { + font-size: 14px; + line-height: 1.6; + color: #333; + + // 富文本内容样式 + h1, + h2, + h3, + h4, + h5, + h6 { + margin: 10px 0; + font-weight: bold; + } + + p { + margin: 8px 0; + } + + img { + max-width: 100%; + height: auto; + } + + ul, + ol { + padding-left: 20px; + margin: 8px 0; + } + + li { + margin: 4px 0; + } + + table { + border-collapse: collapse; + width: 100%; + margin: 8px 0; + } + + th, + td { + border: 1px solid #ddd; + padding: 6px; + text-align: left; + } + + th { + background-color: #f5f5f5; + } + + strong, + b { + font-weight: bold; + } + + em, + i { + font-style: italic; + } + + u { + text-decoration: underline; + } + + sub { + vertical-align: sub; + font-size: smaller; + } + + sup { + vertical-align: super; + font-size: smaller; + } +} diff --git a/yarn.lock b/yarn.lock index 6ec1f1e..5842eb0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10651,7 +10651,7 @@ "recompose" "^0.27.1" "shallowequal" "^1.0.2" -"react-dom@*", "react-dom@^0.14.0 || ^15.0.0 || ^16.0.0", "react-dom@^0.14.0 || ^15.0.0 || ^16.0.0-beta || ^16.0.0", "react-dom@^0.14.0 || ^15.0.0-0 || ^16.0.0", "react-dom@^0.14.0 || ^15.0.0-rc || ^16.0.0-rc || ^16.0.0", "react-dom@^0.14.0 || ^15.0.1 || ^16.0.0", "react-dom@^15.0.0 || ^16.0.0", "react-dom@^15.0.2 || ^16.0.0-rc || ^16.0.0 || ^17.0.0 || ^18.0.0", "react-dom@^15.0.2|| ^16.0.0-rc || ^16.0.0", "react-dom@^16.0.0", "react-dom@^16.0.0-0", "react-dom@^16.3.0", "react-dom@^16.4.1", "react-dom@^16.6.3", "react-dom@^16.8.0", "react-dom@>=15.0.0", "react-dom@>=15.x", "react-dom@>=16.0.0", "react-dom@>=16.9.0", "react-dom@>0.14.5", "react-dom@0.14.x || 15.x || 16.x", "react-dom@15.x || ^16.0.0-0", "react-dom@16.8.2": +"react-dom@*", "react-dom@^0.14.0 || ^15.0.0 || ^16.0.0", "react-dom@^0.14.0 || ^15.0.0 || ^16.0.0-beta || ^16.0.0", "react-dom@^0.14.0 || ^15.0.0-0 || ^16.0.0", "react-dom@^0.14.0 || ^15.0.0-rc || ^16.0.0-rc || ^16.0.0", "react-dom@^0.14.0 || ^15.0.1 || ^16.0.0", "react-dom@^0.14.0 || ^15.0.1 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom@^15.0.0 || ^16.0.0", "react-dom@^15.0.2 || ^16.0.0-rc || ^16.0.0 || ^17.0.0 || ^18.0.0", "react-dom@^15.0.2|| ^16.0.0-rc || ^16.0.0", "react-dom@^16.0.0", "react-dom@^16.0.0-0", "react-dom@^16.3.0", "react-dom@^16.4.1", "react-dom@^16.6.3", "react-dom@^16.8.0", "react-dom@>=15.0.0", "react-dom@>=15.x", "react-dom@>=16.0.0", "react-dom@>=16.9.0", "react-dom@>0.14.5", "react-dom@0.14.x || 15.x || 16.x", "react-dom@15.x || ^16.0.0-0", "react-dom@16.8.2": "integrity" "sha512-cPGfgFfwi+VCZjk73buu14pYkYBR1b/SRMSYqkLDdhSEHnSwcuYTPu6/Bh6ZphJFIk80XLvbSe2azfcRzNF+Xg==" "resolved" "https://registry.npmmirror.com/react-dom/-/react-dom-16.8.2.tgz" "version" "16.8.2" @@ -10789,9 +10789,19 @@ "prop-types" "^15.6.1" "warning" "^4.0.1" +"react-slick@^0.31.0": + "integrity" "sha512-zo6VLT8wuSBJffg/TFPbzrw2dEnfZ/cUKmYsKByh3AgatRv29m2LoFbq5vRMa3R3A4wp4d8gwbJKO2fWZFaI3g==" + "resolved" "https://registry.npmmirror.com/react-slick/-/react-slick-0.31.0.tgz" + "version" "0.31.0" + dependencies: + "classnames" "^2.2.5" + "json2mq" "^0.2.0" + "lodash.debounce" "^4.0.8" + "resize-observer-polyfill" "^1.5.0" + "react-slick@~0.25.2": "integrity" "sha512-8MNH/NFX/R7zF6W/w+FS5VXNyDusF+XDW1OU0SzODEU7wqYB+ZTGAiNJ++zVNAVqCAHdyCybScaUB+FCZOmBBw==" - "resolved" "https://registry.npmjs.org/react-slick/-/react-slick-0.25.2.tgz" + "resolved" "https://registry.npmmirror.com/react-slick/-/react-slick-0.25.2.tgz" "version" "0.25.2" dependencies: "classnames" "^2.2.5" @@ -10834,7 +10844,7 @@ "resolved" "https://registry.npmmirror.com/react-websocket/-/react-websocket-2.0.1.tgz" "version" "2.0.1" -"react@*", "react@^0.14 || ^15.0.0 || ^16.0.0-alpha", "react@^0.14.0 || ^15.0.0 || ^16.0.0", "react@^0.14.0 || ^15.0.0 || ^16.0.0-0", "react@^0.14.0 || ^15.0.0 || ^16.0.0-beta || ^16.0.0", "react@^0.14.0 || ^15.0.0-0 || ^16.0.0", "react@^0.14.0 || ^15.0.0-0 || ^16.0.0-0", "react@^0.14.0 || ^15.0.0-rc || ^16.0.0-rc || ^16.0.0", "react@^0.14.0 || ^15.0.1 || ^16.0.0", "react@^15.0.0 || ^16.0.0", "react@^15.0.2 || ^16.0.0-rc || ^16.0.0 || ^17.0.0 || ^18.0.0", "react@^15.0.2|| ^16.0.0-rc || ^16.0.0", "react@^16.0.0", "react@^16.0.0-0", "react@^16.3.0", "react@^16.4.1", "react@^16.6.3", "react@^16.8.0", "react@^16.8.0 || ^17.0.0 || ^18.0.0", "react@>= 16.3", "react@>=0.13.2 || ^0.14 || ^15.0.0 || >=16.0.0", "react@>=15", "react@>=15.0.0", "react@>=15.x", "react@>=16.0.0", "react@>=16.9.0", "react@>0.14.5", "react@0.13.x || 0.14.x || ^15.0.0-0 || ^16.0.0-0", "react@0.14.x || 15.x || 16.x", "react@15.x || ^16.0.0-0", "react@16.8.2", "react@16.x": +"react@*", "react@^0.14 || ^15.0.0 || ^16.0.0-alpha", "react@^0.14.0 || ^15.0.0 || ^16.0.0", "react@^0.14.0 || ^15.0.0 || ^16.0.0-0", "react@^0.14.0 || ^15.0.0 || ^16.0.0-beta || ^16.0.0", "react@^0.14.0 || ^15.0.0-0 || ^16.0.0", "react@^0.14.0 || ^15.0.0-0 || ^16.0.0-0", "react@^0.14.0 || ^15.0.0-rc || ^16.0.0-rc || ^16.0.0", "react@^0.14.0 || ^15.0.1 || ^16.0.0", "react@^0.14.0 || ^15.0.1 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react@^15.0.0 || ^16.0.0", "react@^15.0.2 || ^16.0.0-rc || ^16.0.0 || ^17.0.0 || ^18.0.0", "react@^15.0.2|| ^16.0.0-rc || ^16.0.0", "react@^16.0.0", "react@^16.0.0-0", "react@^16.3.0", "react@^16.4.1", "react@^16.6.3", "react@^16.8.0", "react@^16.8.0 || ^17.0.0 || ^18.0.0", "react@>= 16.3", "react@>=0.13.2 || ^0.14 || ^15.0.0 || >=16.0.0", "react@>=15", "react@>=15.0.0", "react@>=15.x", "react@>=16.0.0", "react@>=16.9.0", "react@>0.14.5", "react@0.13.x || 0.14.x || ^15.0.0-0 || ^16.0.0-0", "react@0.14.x || 15.x || 16.x", "react@15.x || ^16.0.0-0", "react@16.8.2", "react@16.x": "integrity" "sha512-aB2ctx9uQ9vo09HVknqv3DGRpI7OIGJhCx3Bt0QqoRluEjHSaObJl+nG12GDdYH6sTgE7YiPJ6ZUyMx9kICdXw==" "resolved" "https://registry.npmmirror.com/react/-/react-16.8.2.tgz" "version" "16.8.2"