feat(): 强化法治模块开发
parent
d246a90eab
commit
98f1781507
Binary file not shown.
|
After Width: | Height: | Size: 20 KiB |
|
|
@ -337,6 +337,13 @@ input:-webkit-autofill:active {
|
|||
color: #fff;
|
||||
}
|
||||
}
|
||||
.ant-picker-cell-disabled {
|
||||
pointer-events: none;
|
||||
.ant-picker-cell-inner {
|
||||
color: rgba(255, 255, 255, 0.25) !important;
|
||||
background-color: rgba(255, 255, 255, 0.1) !important;
|
||||
}
|
||||
}
|
||||
|
||||
// Footer/Ranges
|
||||
.ant-picker-footer {
|
||||
|
|
|
|||
|
|
@ -71,6 +71,16 @@ const apiurl = {
|
|||
manageInfo: service + '/screen/mechanisms/equipment',
|
||||
managePic: service + '/screen/manageHouseImg/get',
|
||||
wzPage:service + '/rescue/goods/page/query'
|
||||
},
|
||||
qhfz: {
|
||||
lawPage: service + '/SzRuleByLaw/page',
|
||||
systemPage: service + '/SzRegulatoryFramework/page',
|
||||
caseSta:service + '/szCase/statistics/1'
|
||||
},
|
||||
zrz: {
|
||||
respPerson: service + '/screen/responsibility/getPerson',
|
||||
floodPerson: service + '/screen/responsibility/getFxPerson',
|
||||
train:service + '/screen/responsibility/getTraining'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import centerOfMass from '@turf/center-of-mass';
|
|||
import bbox from '@turf/bbox';
|
||||
import turfLength from '@turf/length';
|
||||
import along from '@turf/along';
|
||||
|
||||
import { config } from '@/config';
|
||||
const class2type = {};
|
||||
const { toString } = class2type;
|
||||
'Boolean Number String Function Array Date RegExp Object Error'.split(' ').forEach((name) => {
|
||||
|
|
@ -597,3 +597,16 @@ export const myFiltrate = (data,params)=>{
|
|||
|
||||
return query.substring(iStart, iEnd);
|
||||
}
|
||||
|
||||
export const download = (url) => {
|
||||
let downloadLink = document.createElement("a");
|
||||
// downloadLink.href = `${process.env.REACT_APP_API_URL}/gunshiApp/ss/personnelPlan/file/download/${params}`;
|
||||
downloadLink.href = config.ip +url;
|
||||
// downloadLink.download = `${params.fileName}`;
|
||||
downloadLink.style.display = "none";
|
||||
// 将链接添加到页面中
|
||||
document.body.appendChild(downloadLink);
|
||||
|
||||
// 模拟点击事件,开始下载
|
||||
downloadLink.click();
|
||||
}
|
||||
|
|
@ -6,6 +6,7 @@ import { treeList, srcData, videoList, ysyToken } from './http'
|
|||
import VideoControler from "@/components/VideoCom/VideoControler"
|
||||
import { httppost } from "@/utils/request"
|
||||
import apiurl from "@/service/apiurl"
|
||||
import { Spin } from "antd"
|
||||
|
||||
|
||||
const VideoList = () => {
|
||||
|
|
@ -15,6 +16,7 @@ const VideoList = () => {
|
|||
const [size, setSize] = useState(1)
|
||||
const [treeListData, setTreeData] = useState([])
|
||||
const [selectList, setSelectList] = useState()
|
||||
const [loading, setLoading] = useState(true)
|
||||
|
||||
const selectedKeys = async (list, node) => {
|
||||
console.log(node, 'node');
|
||||
|
|
@ -74,6 +76,7 @@ const VideoList = () => {
|
|||
|
||||
}
|
||||
const getTreeData = async () => {
|
||||
try {
|
||||
const res = await treeList()
|
||||
const res1 = await videoList()
|
||||
const arr = res1.data.filter(item => item.menuId).map((item, index) => {
|
||||
|
|
@ -86,6 +89,11 @@ const VideoList = () => {
|
|||
const arr1 = [...arr, ...res.data]
|
||||
console.log("before", arr1);
|
||||
setTreeData(buildTree(arr1))
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
function buildTree(data, parentId = "0") {
|
||||
const tree = [];
|
||||
|
|
@ -137,6 +145,12 @@ const VideoList = () => {
|
|||
}, [])
|
||||
return (
|
||||
<div className='flex videoList'>
|
||||
{loading ? (
|
||||
<div style={{ width: '100%', height: '100%', display: 'flex', justifyContent: 'center', alignItems: 'center' }}>
|
||||
<Spin tip="加载中..." />
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<div className={['treeRight', (selectList && selectList.type == 1) ? 'ptz-visible' : ''].join(' ')}>
|
||||
<TreeData size={size} selectedKeys={selectedKeys} treeListData={treeListData} videoArr={videoArr} />
|
||||
{
|
||||
|
|
@ -151,6 +165,8 @@ const VideoList = () => {
|
|||
}
|
||||
</div>
|
||||
<div className='treeLeft'><SplitScreen count={count} videoArr={videoArr} clickIndex={clickIndex} getType={getType} /></div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,212 @@
|
|||
import React, { useState,useEffect } from 'react';
|
||||
import { Image } from 'antd';
|
||||
import arrowIcon from '@/assets/images/card/arrow.png';
|
||||
import selectedBg from '@/assets/images/modal/selected.png';
|
||||
import resperson from '@/assets/images/card/resperson.png';
|
||||
import smallCard from '@/assets/images/card/smallCard.png';
|
||||
import qys from '@/assets/images/business/qys.png';
|
||||
import PdfView from '@/views/Home/components/UI/PdfView';
|
||||
import apiurl from '@/service/apiurl';
|
||||
import { httpget } from '@/utils/request';
|
||||
import { download } from '@/utils/tools';
|
||||
import './index.less';
|
||||
|
||||
const ImplementResponsibility = () => {
|
||||
const types = {
|
||||
1:"行政责任人",
|
||||
2:"主管部门责任人",
|
||||
3:"管理单位责任人",
|
||||
4:"巡查责任人",
|
||||
5:"技术责任人",
|
||||
}
|
||||
const [activeTab, setActiveTab] = useState('dam'); // 'dam' or 'flood'
|
||||
const [pdfInfo, setPdfInfo] = useState({ visible: false, title: '', fileId: '' });
|
||||
const [imagePreview, setImagePreview] = useState({ visible: false, src: '' });
|
||||
const [respData, setRespData] = useState([])
|
||||
const [floodData, setFloodData] = useState([])
|
||||
const [trainData, setTrainData] = useState({})
|
||||
const [trainFileData, setTrainFileData] = useState({})
|
||||
|
||||
// 获取大坝安全责任人
|
||||
const getRespData = async () => {
|
||||
try {
|
||||
const { code, data } = await httpget(apiurl.sz.zrz.respPerson)
|
||||
if (code == 200 && data.length > 0) {
|
||||
const list = data.map(item => ({name:item.name,phone:item.contactInfo,role:types[item.type]}))
|
||||
setRespData(list)
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
// 获取防汛安全责任人
|
||||
const getFloodData = async () => {
|
||||
try {
|
||||
const { code, data } = await httpget(apiurl.sz.zrz.floodPerson)
|
||||
if (code == 200 && data.length > 0) {
|
||||
const list = data.map(item => ({name:item.name,phone:item.contactInfo,role:types[item.type]}))
|
||||
setFloodData(list)
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
|
||||
// 获取岗位培训
|
||||
const getTrainData = async () => {
|
||||
try {
|
||||
const { code, data } = await httpget(apiurl.sz.zrz.train)
|
||||
if (code == 200) {
|
||||
setTrainData(data)
|
||||
setTrainFileData(data?.latestPersonnelPlan?.files[0])
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
const {trainingCount,hasTraining,hasNoTraining,totalTraining,latestPersonnelPlan} = trainData||{}
|
||||
const trainingStats = [
|
||||
{ label: '培训计划', value: trainingCount??'-' },
|
||||
{ label: '已开展', value: hasTraining??'-' },
|
||||
{ label: '未开展', value: hasNoTraining??'-' },
|
||||
{ label: '参训总人次', value: totalTraining??'-' }
|
||||
];
|
||||
|
||||
const handleTrainingClick = () => {
|
||||
if (!trainFileData || !trainFileData.fileName) return;
|
||||
|
||||
const fileName = trainFileData.fileName;
|
||||
const fileId = trainFileData.fileId;
|
||||
const extension = fileName.split('.').pop().toLowerCase();
|
||||
const downloadUrl = `/gunshiApp/ss/personnelPlan/file/download/${fileId}`;
|
||||
|
||||
if (['jpg', 'jpeg', 'png', 'gif', 'bmp'].includes(extension)) {
|
||||
setImagePreview({
|
||||
visible: true,
|
||||
src: downloadUrl
|
||||
});
|
||||
} else if (extension === 'pdf') {
|
||||
setPdfInfo({
|
||||
visible: true,
|
||||
title: fileName,
|
||||
fileId: fileId
|
||||
});
|
||||
} else {
|
||||
download(downloadUrl)
|
||||
// window.location.href = downloadUrl;
|
||||
}
|
||||
};
|
||||
|
||||
const currentPersonList = activeTab === 'dam' ? respData : floodData;
|
||||
|
||||
useEffect(() => {
|
||||
if (activeTab === 'dam') {
|
||||
getRespData()
|
||||
} else {
|
||||
getFloodData()
|
||||
}
|
||||
}, [activeTab])
|
||||
|
||||
useEffect(() => {
|
||||
getTrainData()
|
||||
}, [])
|
||||
|
||||
|
||||
return (
|
||||
<div className="implement-responsibility">
|
||||
{/* Top Tabs */}
|
||||
<div className="tabs-container">
|
||||
<div
|
||||
className={`tab-item ${activeTab === 'dam' ? 'active' : ''}`}
|
||||
onClick={() => setActiveTab('dam')}
|
||||
style={activeTab === 'dam' ? { backgroundImage: `url(${selectedBg})` } : {}}
|
||||
>
|
||||
大坝安全责任人
|
||||
</div>
|
||||
<div
|
||||
className={`tab-item ${activeTab === 'flood' ? 'active' : ''}`}
|
||||
onClick={() => setActiveTab('flood')}
|
||||
style={activeTab === 'flood' ? { backgroundImage: `url(${selectedBg})` } : {}}
|
||||
>
|
||||
防汛 “三个责任人”
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Person List */}
|
||||
<div className="person-list">
|
||||
{currentPersonList.map((person, index) => (
|
||||
<div key={index} className="person-item">
|
||||
<img src={resperson} alt="avatar" className="avatar" />
|
||||
<span className="name">{person.name}</span>
|
||||
<span className="phone">{person.phone}</span>
|
||||
<div
|
||||
className="role-label"
|
||||
style={{ backgroundImage: `url(${smallCard})` }}
|
||||
title={person.role}
|
||||
>
|
||||
{person.role}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Job Training Section */}
|
||||
<div className="section-header">
|
||||
<div className="title-wrapper">
|
||||
<img src={arrowIcon} alt="arrow" className="arrow-icon" />
|
||||
<span>岗位培训</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Training Stats */}
|
||||
<div className="training-stats">
|
||||
{trainingStats.map((stat, index) => (
|
||||
<div key={index} className="stat-item">
|
||||
<span className="stat-value">{stat.value}</span>
|
||||
<img src={qys} alt="icon" className="stat-icon" />
|
||||
<span className="stat-label">{stat.label}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Latest Training Content */}
|
||||
<div className="latest-training" style={{ backgroundImage: `url(${smallCard})` }}>
|
||||
<span className="label">最新培训内容:</span>
|
||||
<div
|
||||
className="value-box"
|
||||
onClick={handleTrainingClick}
|
||||
title={trainFileData.fileName?.replace(/\.[^/.]+$/, '')}
|
||||
>
|
||||
{trainFileData.fileName?.replace(/\.[^/.]+$/, '')}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* PDF Viewer */}
|
||||
{pdfInfo.visible && (
|
||||
<PdfView
|
||||
visible={pdfInfo.visible}
|
||||
onClose={() => setPdfInfo({ ...pdfInfo, visible: false })}
|
||||
title={pdfInfo.title}
|
||||
fileId={pdfInfo.fileId}
|
||||
url="/gunshiApp/ss/personnelPlan/file/download/"
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Image Preview */}
|
||||
<div style={{ display: 'none' }}>
|
||||
<Image
|
||||
src={imagePreview.src}
|
||||
preview={{
|
||||
visible: imagePreview.visible,
|
||||
src: imagePreview.src,
|
||||
onVisibleChange: (value) => {
|
||||
setImagePreview({ ...imagePreview, visible: value });
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ImplementResponsibility;
|
||||
|
|
@ -0,0 +1,173 @@
|
|||
.implement-responsibility {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
box-sizing: border-box;
|
||||
|
||||
.tabs-container {
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
margin-bottom: 10px;
|
||||
|
||||
.tab-item {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
padding: 3px 0;
|
||||
font-size: 14px;
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
cursor: pointer;
|
||||
background-size: 100% 100%;
|
||||
background-repeat: no-repeat;
|
||||
transition: all 0.3s;
|
||||
|
||||
&.active {
|
||||
color: #fff;
|
||||
text-shadow: 0 0 10px #00a0e9;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.person-list {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 0 10px;
|
||||
margin-bottom: 10px;
|
||||
|
||||
.person-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
width: 30%;
|
||||
|
||||
.avatar {
|
||||
width: 40px;
|
||||
height: 45px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.name {
|
||||
font-size: 14px;
|
||||
color: #fff;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.phone {
|
||||
font-size: 14px;
|
||||
color: #fff;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.role-label {
|
||||
width: 100%;
|
||||
height: 24px;
|
||||
line-height: 24px;
|
||||
text-align: center;
|
||||
color: #fff;
|
||||
font-size: 14px;
|
||||
background-size: 100% 100%;
|
||||
background-repeat: no-repeat;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.section-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 10px;
|
||||
|
||||
.title-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.arrow-icon {
|
||||
width: 20px;
|
||||
height: 18px;
|
||||
margin-right: 8px;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
span {
|
||||
font-size: 14px;
|
||||
color: #fff;
|
||||
text-shadow: 0 0 5px rgba(0, 160, 233, 0.5);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.training-stats {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 0 10px;
|
||||
margin-bottom: 15px;
|
||||
|
||||
.stat-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
|
||||
.stat-value {
|
||||
font-size: 18px;
|
||||
color: #00eaff;
|
||||
font-weight: bold;
|
||||
margin-bottom: -5px;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.stat-icon {
|
||||
width: 100px;
|
||||
height: 60px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: 14px;
|
||||
color: rgba(255, 255, 255);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.latest-training {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 10px;
|
||||
margin-top: auto;
|
||||
margin-bottom: 10px;
|
||||
background-size: 100% 100%;
|
||||
background-repeat: no-repeat;
|
||||
.label {
|
||||
font-size: 14px;
|
||||
color: #fff;
|
||||
margin-right: 10px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.value-box {
|
||||
flex: 1;
|
||||
height: 30px;
|
||||
line-height: 30px;
|
||||
padding: 0 10px;
|
||||
color: #00eaff;
|
||||
font-size: 14px;
|
||||
|
||||
cursor: pointer;
|
||||
text-decoration: underline;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
|
||||
&:hover {
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,362 @@
|
|||
import React, { useState,useEffect } from 'react';
|
||||
import ReactEcharts from 'echarts-for-react';
|
||||
import arrowIcon from '@/assets/images/card/arrow.png';
|
||||
import selectedBg from '@/assets/images/modal/selected.png';
|
||||
import YearSelect from '@/views/Home/components/UI/YearSelect';
|
||||
import PdfView from '@/views/Home/components/UI/PdfView';
|
||||
import { FileTextOutlined } from '@ant-design/icons';
|
||||
import apiurl from '@/service/apiurl';
|
||||
import { httppost, httpget } from '@/utils/request';
|
||||
import { Empty } from 'antd';
|
||||
import './index.less';
|
||||
import moment from 'moment';
|
||||
|
||||
const StrengthenRuleOfLaw = () => {
|
||||
const params = {
|
||||
pageSo:{ pageSize: 9999, pageNumber: 1 }
|
||||
}
|
||||
const [activeTab, setActiveTab] = useState('laws');
|
||||
const [lawData, setLawData] = useState([]);
|
||||
const [systemData, setSystemData] = useState([]);
|
||||
const [year, setYear] = useState(moment().format('YYYY'));
|
||||
const [pdfInfo, setPdfInfo] = useState({ visible: false, title: '', fileId: '' });
|
||||
|
||||
// 获取法律法规文件
|
||||
const lawsFiles = async () => {
|
||||
try {
|
||||
const { code, data } = await httppost(apiurl.sz.qhfz.lawPage, params)
|
||||
if (code == 200) {
|
||||
const list = data?.records.map(item => {
|
||||
if (item?.files?.length === 0) {
|
||||
return []
|
||||
}
|
||||
return { name: item?.files[0]?.fileName?.replace(/\.pdf$/i, ''), fileId: item?.files[0]?.fileId }
|
||||
}).flat()
|
||||
setLawData(list)
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
|
||||
// 获取系统文件
|
||||
const systemFiles = async () => {
|
||||
try {
|
||||
const { code, data } = await httppost(apiurl.sz.qhfz.systemPage, params)
|
||||
if (code == 200) {
|
||||
const list = data?.records.map(item => {
|
||||
if (item?.files?.length === 0) {
|
||||
return []
|
||||
}
|
||||
return { name: item?.files[0]?.fileName?.replace(/\.pdf$/i, ''), fileId: item?.files[0]?.fileId }
|
||||
}).flat()
|
||||
|
||||
setSystemData(list)
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
|
||||
const [chartData, setChartData] = useState([]);
|
||||
const [selectedPieItem, setSelectedPieItem] = useState(null);
|
||||
const [startAngle, setStartAngle] = useState(90);
|
||||
|
||||
const typeMapping = {
|
||||
0: { name: '违建', color: '#1890ff' },
|
||||
1: { name: '毁林垦荒', color: '#00eaff' },
|
||||
2: { name: '筑坝拦汊', color: '#3155fb' },
|
||||
3: { name: '填占库容', color: '#bcebf7' },
|
||||
4: { name: '违法取水', color: '#00d085' },
|
||||
5: { name: '其他', color: '#1890ff' },
|
||||
};
|
||||
|
||||
// 获取水政执法数据 0:违建,1:毀林垦荒,2:筑坝拦汊,3:填占库容,4:违法取水,5:其他
|
||||
const caseList = async (params) => {
|
||||
try {
|
||||
const { code, data } = await httppost(apiurl.sz.qhfz.caseSta, params)
|
||||
if (code == 200) {
|
||||
if (!data || data.length === 0) {
|
||||
setChartData([]);
|
||||
return;
|
||||
}
|
||||
|
||||
const newChartData = data.map(item => {
|
||||
const typeInfo = typeMapping[item.type] || { name: '未知', color: '#ccc' };
|
||||
return {
|
||||
name: typeInfo.name,
|
||||
value: item.count,
|
||||
color: typeInfo.color
|
||||
};
|
||||
});
|
||||
setChartData(newChartData);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
|
||||
const total = chartData.reduce((acc, cur) => acc + cur.value, 0);
|
||||
|
||||
const getOption = () => {
|
||||
let centerName = '案件总数';
|
||||
let centerValue = total;
|
||||
let centerPercent = '100%';
|
||||
|
||||
if (selectedPieItem) {
|
||||
centerName = selectedPieItem.name;
|
||||
centerValue = selectedPieItem.value;
|
||||
centerPercent = selectedPieItem.percent;
|
||||
} else if (chartData.length === 1) {
|
||||
centerName = chartData[0].name;
|
||||
}
|
||||
|
||||
return {
|
||||
tooltip: {
|
||||
trigger: 'item',
|
||||
formatter: '{b}: {c} ({d}%)'
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '水政执法-外圈',
|
||||
type: 'pie',
|
||||
radius: ['48%', '82%'],
|
||||
center: ['34%', '50%'],
|
||||
startAngle: startAngle,
|
||||
avoidLabelOverlap: false,
|
||||
label: {
|
||||
show: true,
|
||||
position: 'inside',
|
||||
formatter: '{b}',
|
||||
color: '#fff',
|
||||
fontSize: 10
|
||||
},
|
||||
itemStyle: {
|
||||
opacity: 0.4
|
||||
},
|
||||
data: chartData.map(item => ({
|
||||
value: item.value,
|
||||
name: item.name,
|
||||
itemStyle: { color: item.color }
|
||||
}))
|
||||
},
|
||||
{
|
||||
name: '水政执法-内圈',
|
||||
type: 'pie',
|
||||
radius: ['50%', '56%'],
|
||||
center: ['34%', '50%'],
|
||||
startAngle: startAngle,
|
||||
avoidLabelOverlap: false,
|
||||
label: {
|
||||
show: true,
|
||||
position: 'center',
|
||||
formatter: () => `{name|${centerName}}\n{value|${centerValue}}\n{percent|${centerPercent}}`,
|
||||
rich: {
|
||||
name: {
|
||||
fontSize: 12,
|
||||
color: 'rgba(255,255,255,0.6)',
|
||||
lineHeight: 16
|
||||
},
|
||||
value: {
|
||||
fontSize: 14,
|
||||
color: '#fff',
|
||||
fontWeight: 'bold',
|
||||
lineHeight: 20
|
||||
},
|
||||
percent: {
|
||||
fontSize: 12,
|
||||
color: '#fff',
|
||||
lineHeight: 16
|
||||
}
|
||||
}
|
||||
},
|
||||
emphasis: {
|
||||
label: {
|
||||
show: true,
|
||||
fontSize: '14',
|
||||
fontWeight: 'bold'
|
||||
}
|
||||
},
|
||||
labelLine: {
|
||||
show: false
|
||||
},
|
||||
data: chartData.map(item => ({
|
||||
value: item.value,
|
||||
name: item.name,
|
||||
itemStyle: { color: item.color }
|
||||
}))
|
||||
}
|
||||
]
|
||||
}};
|
||||
|
||||
const onChartClick = (params) => {
|
||||
if (params.componentType !== 'series') return;
|
||||
|
||||
const { name, value, dataIndex } = params;
|
||||
|
||||
// Toggle selection
|
||||
if (selectedPieItem && selectedPieItem.name === name) {
|
||||
setSelectedPieItem(null);
|
||||
setStartAngle(90);
|
||||
return;
|
||||
}
|
||||
|
||||
let sumBefore = 0;
|
||||
for (let i = 0; i < dataIndex; i++) {
|
||||
sumBefore += chartData[i].value;
|
||||
}
|
||||
|
||||
const offset = (sumBefore + value / 2) / total * 360;
|
||||
const newStartAngle = 90 + offset;
|
||||
|
||||
setStartAngle(newStartAngle);
|
||||
setSelectedPieItem({
|
||||
name,
|
||||
value,
|
||||
percent: ((value / total) * 100).toFixed(0) + '%'
|
||||
});
|
||||
};
|
||||
|
||||
const handleItemClick = (item) => {
|
||||
setPdfInfo({
|
||||
visible: true,
|
||||
title: item.name,
|
||||
fileId: item.fileId
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (chartData && chartData.length > 0) {
|
||||
const totalVal = chartData.reduce((acc, cur) => acc + cur.value, 0);
|
||||
const firstItem = chartData[0];
|
||||
const value = firstItem.value;
|
||||
|
||||
const offset = (value / 2) / totalVal * 360;
|
||||
const newStartAngle = 90 + offset;
|
||||
|
||||
setStartAngle(newStartAngle);
|
||||
setSelectedPieItem({
|
||||
name: firstItem.name,
|
||||
value: value,
|
||||
percent: ((value / totalVal) * 100).toFixed(0) + '%'
|
||||
});
|
||||
} else {
|
||||
setSelectedPieItem(null);
|
||||
setStartAngle(90);
|
||||
}
|
||||
}, [chartData]);
|
||||
|
||||
useEffect(() => {
|
||||
if (activeTab == 'laws') {
|
||||
lawsFiles()
|
||||
} else {
|
||||
systemFiles()
|
||||
}
|
||||
}, [activeTab])
|
||||
useEffect(() => {
|
||||
if (year) {
|
||||
const params = {
|
||||
stm: moment(year).format('YYYY-01-01 00:00:00'),
|
||||
etm: moment(year).format('YYYY-12-31 23:59:59'),
|
||||
}
|
||||
caseList(params)
|
||||
}
|
||||
}, [year])
|
||||
return (
|
||||
<div className="strengthen-rule-of-law">
|
||||
{/* Top Tabs */}
|
||||
<div className="tabs-container">
|
||||
<div
|
||||
className={`tab-item ${activeTab === 'laws' ? 'active' : ''}`}
|
||||
onClick={() => setActiveTab('laws')}
|
||||
style={activeTab === 'laws' ? { backgroundImage: `url(${selectedBg})` } : {}}
|
||||
>
|
||||
法律法规
|
||||
</div>
|
||||
<div
|
||||
className={`tab-item ${activeTab === 'system' ? 'active' : ''}`}
|
||||
onClick={() => setActiveTab('system')}
|
||||
style={activeTab === 'system' ? { backgroundImage: `url(${selectedBg})` } : {}}
|
||||
>
|
||||
制度管理
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* List Content */}
|
||||
<div className="list-content">
|
||||
{(activeTab === 'laws' ? lawData : systemData).length > 0 ? (
|
||||
(activeTab === 'laws' ? lawData : systemData).map((item, index) => (
|
||||
<div key={index} className="list-item" onClick={() => handleItemClick(item)}>
|
||||
<div className="icon-wrapper">
|
||||
<FileTextOutlined style={{ color: '#1890ff', fontSize: '16px' }} />
|
||||
</div>
|
||||
<span className="text">{item.name}</span>
|
||||
</div>
|
||||
))
|
||||
) : (
|
||||
<div style={{ height: '100%', display: 'flex', justifyContent: 'center', alignItems: 'center' }}>
|
||||
<Empty image={Empty.PRESENTED_IMAGE_SIMPLE} description={<span style={{color:'#fff'}}>暂无数据</span>} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Water Administration Law Enforcement */}
|
||||
<div className="enforcement-section">
|
||||
<div className="section-header">
|
||||
<div className="title-wrapper">
|
||||
<img src={arrowIcon} alt="arrow" className="arrow-icon" />
|
||||
<span>水政执法</span>
|
||||
</div>
|
||||
<YearSelect
|
||||
value={year}
|
||||
onChange={setYear}
|
||||
style={{ width: 100 }}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="chart-legend-container">
|
||||
{chartData.length > 0 ? (
|
||||
<>
|
||||
<div className="chart-wrapper">
|
||||
<ReactEcharts
|
||||
option={getOption()}
|
||||
style={{ height: '100%', width: '100%' }}
|
||||
notMerge={true}
|
||||
onEvents={{
|
||||
'click': onChartClick
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className="legend-wrapper">
|
||||
{chartData.map((item, index) => (
|
||||
<div key={index} className="legend-item">
|
||||
<span className="color-dot" style={{ backgroundColor: item.color }}></span>
|
||||
<span className="name">{item.name}</span>
|
||||
<span className="value">{item.value}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<div style={{ width: '100%', height: '100%', display: 'flex', justifyContent: 'center', alignItems: 'center' }}>
|
||||
<Empty image={Empty.PRESENTED_IMAGE_SIMPLE} description={<span style={{color:'#fff'}}>暂无数据</span>} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* PDF Viewer */}
|
||||
{pdfInfo.visible && (
|
||||
<PdfView
|
||||
visible={pdfInfo.visible}
|
||||
onClose={() => setPdfInfo({ ...pdfInfo, visible: false })}
|
||||
title={pdfInfo.title}
|
||||
fileId={pdfInfo.fileId}
|
||||
url="/gunshiApp/ss/projectEvents/file/download/"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default StrengthenRuleOfLaw;
|
||||
|
|
@ -0,0 +1,148 @@
|
|||
.strengthen-rule-of-law {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
color: #fff;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.tabs-container {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
||||
|
||||
.tab-item {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
padding: 3px 0;
|
||||
font-size: 14px;
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
cursor: pointer;
|
||||
background-size: 100% 100%;
|
||||
background-repeat: no-repeat;
|
||||
transition: all 0.3s;
|
||||
|
||||
&.active {
|
||||
color: #fff;
|
||||
text-shadow: 0 0 10px #00a0e9;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.list-content {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
margin-bottom: 15px;
|
||||
max-height: 120px; /* Limit height to show scroll if needed, though items are few */
|
||||
|
||||
.list-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 8px 0;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.3s;
|
||||
|
||||
&:hover {
|
||||
background-color: rgba(255, 255, 255, 0.05);
|
||||
.text {
|
||||
color: #00a0e9;
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
.icon-wrapper {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-right: 10px;
|
||||
background: rgba(24, 144, 255, 0.1);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.text {
|
||||
font-size: 14px;
|
||||
color: rgba(255, 255, 255, 0.9);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.enforcement-section {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.section-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 10px;
|
||||
|
||||
.title-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.arrow-icon {
|
||||
width: 20px;
|
||||
height: 18px;
|
||||
margin-right: 8px;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
span {
|
||||
font-size: 14px;
|
||||
color: #fff;
|
||||
text-shadow: 0 0 5px rgba(0, 160, 233, 0.5);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.chart-legend-container {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.chart-wrapper {
|
||||
flex: 1;
|
||||
height: 100%;
|
||||
min-height: 140px;
|
||||
}
|
||||
|
||||
.legend-wrapper {
|
||||
width: 140px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
padding-left: 10px;
|
||||
|
||||
.legend-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 6px;
|
||||
font-size: 12px;
|
||||
|
||||
.color-dot {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
margin-right: 8px;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.name {
|
||||
flex: 1;
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
}
|
||||
|
||||
.value {
|
||||
color: #fff;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2,6 +2,8 @@ import React, { useState, useEffect } from 'react';
|
|||
import CommonCard from '../../UI/CommonCard';
|
||||
import PerfectSystem from './components/PerfectSystem';
|
||||
import SoundMechanism from './components/SoundMechanism';
|
||||
import StrengthenRuleOfLaw from './components/StrengthenRuleOfLaw';
|
||||
import ImplementResponsibility from './components/ImplementResponsibility';
|
||||
import { httppost } from '@/utils/request';
|
||||
import apiurl from '@/service/apiurl';
|
||||
import './index.less';
|
||||
|
|
@ -36,10 +38,10 @@ const SiZhi = () => {
|
|||
|
||||
<div className="side-panel right">
|
||||
<CommonCard title="强化法治" className="panel-card card-1">
|
||||
<div className="placeholder-content">内容填充区域</div>
|
||||
<StrengthenRuleOfLaw />
|
||||
</CommonCard>
|
||||
<CommonCard title="落实责任制" className="panel-card card-2">
|
||||
<div className="placeholder-content">内容填充区域</div>
|
||||
<ImplementResponsibility />
|
||||
</CommonCard>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ const PdfView = ({ visible, title, onClose, url, fileId }) => {
|
|||
return (
|
||||
<CommonModal
|
||||
visible={visible}
|
||||
title={title}
|
||||
title={title?.replace(/\.pdf$/i, '')}
|
||||
onClose={onClose}
|
||||
width="60%"
|
||||
bodyStyle={{ padding: 0, overflow: 'hidden' }}
|
||||
|
|
|
|||
|
|
@ -9,14 +9,18 @@ const YearSelect = ({ value, onChange, className, style, ...props }) => {
|
|||
onChange(dateString);
|
||||
}
|
||||
};
|
||||
const disabledDate = (current) => {
|
||||
return current && current > moment().endOf('day');
|
||||
};
|
||||
|
||||
return (
|
||||
<DatePicker
|
||||
picker="year"
|
||||
disabledDate={disabledDate}
|
||||
value={value ? moment(value, 'YYYY') : null}
|
||||
onChange={handleChange}
|
||||
className={`custom-year-select ${className || ''}`}
|
||||
dropdownClassName="custom-year-select-dropdown"
|
||||
// dropdownClassName="custom-year-select-dropdown"
|
||||
style={style}
|
||||
allowClear={false}
|
||||
{...props}
|
||||
|
|
|
|||
|
|
@ -65,6 +65,14 @@
|
|||
color: #fff;
|
||||
}
|
||||
|
||||
.ant-picker-cell-disabled {
|
||||
pointer-events: none;
|
||||
.ant-picker-cell-inner {
|
||||
color: rgba(255, 255, 255, 0.25) !important;
|
||||
background-color: rgba(255, 255, 255, 0.1) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.ant-picker-cell-selected .ant-picker-cell-inner {
|
||||
background-color: #00a0e9 !important;
|
||||
color: #fff;
|
||||
|
|
|
|||
Loading…
Reference in New Issue