效果图

Main
import { useState, Fragment } from 'react';
import Table from './Components/Table'
import Modal from './Components/Modal'
const index = () =>
{
const [isModal, setIsModal] = useState(false),
[modalData, setModalData] = useState({ name: '', img: null, type: 3 })
const ModalFun = (flag) =>
{
setIsModal(flag)
}
const modalDataFun = (obj) =>
{
setModalData(obj)
}
return (
<Fragment >
<Table modalData={modalData} ModalFun={ModalFun} modalDataFun={modalDataFun} />
{isModal && <Modal data={modalData} ModalFun={ModalFun} />}
</Fragment>
);
};
export default index
Table Js
import { useState, useRef, useEffect, Fragment } from 'react';
import styles from './style.less'
import Table from './Table'
const contentStyle = {
overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap'
};
const dataSource = [
{
name: '用工',
grade: '优',
time: '2022/6/27',
describe: '农场主反映的儿子康复专业找工作问题,想找对口专业或社工工作。',
status: '已解决',
solution: '个人协调解决',
orderName: '企业问题',
type: '个人协调解决',
dispositionDetails: '与局事管科对接了解医疗卫生系统或社工招聘人员情况。目前正在招投标过程中,7月中下旬会有护理(康复)专业的招聘,请他儿子及查看区政府网站并报名参加应聘。针对要求政府给其儿子强制安排工作事宜,已向其解释现行招聘相关政策规定。',
key: 1
},
{
name: '用工',
grade: '优',
time: '2022/6/22',
describe: '人员流动性大',
status: '已解决',
solution: '个人协调解决',
key: 1
},
{
name: '用工',
grade: '优',
time: '2022/7/15',
describe: '周边配套谁是能尽快完善,职能流动性大,招工难',
status: '已解决',
solution: '单位协调解决',
key: 2
},
{
name: '用工',
grade: '差',
time: '2022/3/21',
describe: '公司目前人员招聘较困难,亟需在1个月之内招聘10位操作工人、水电工人。企业已经通过社区、张贴招聘简章、前程无忧网站等渠道发布信息。',
status: '推进中',
solution: '转联勤工单',
key: 3
},
{
name: '用工',
grade: '良',
time: '2022/3/21',
describe: '公司目前人员招聘较困难,亟需在1个月之内招聘10位操作工人、水电工人。企业已经通过社区、张贴招聘简章、前程无忧网站等渠道发布信息。',
status: '推进中',
solution: '转联勤工单',
key: 4
},
{
name: '资金',
grade: '差',
time: '2022/7/26',
describe: '货物税收减免政策审核流程较长,后期税收减免审批尽可能的缩短时间。',
status: '未解决',
solution: '转联勤工单',
key: 5
},
{
name: '市场',
grade: '差',
time: '2022/7/25',
describe: '走访企业在本月走访中依然反馈城建集团的工程款未及时支付到位,此工程款非城建集团在前两个月的反馈中提交的尾款部分,而是虎桥河园林绿化增项部分,据走访企业描述,因当时增项未新增合同,导致了现在追要工程款的难题。走访企业不仅通过大走访渠道反映,还多次尝试直接联系城建集团等途径来解决,但至本月走访为止,仍未取得实质性推进。本月走访中,走访企业负责人情绪较为激动,表达了即将信访的诉求。作为走访人,已经极力安抚了当事人的情绪。建议城建集团重视该企业诉求,尽快核查相关情况,如未支付的,尽快支付;如不符合支付条件的,与企业方做好解释说明与情绪疏导,避免矛盾隐患爆发。',
status: '未解决',
solution: '转联勤工单',
key: 6
},
{
name: '用工',
grade: '查',
time: '2022/7/15',
describe: '周边配套谁是能尽快完善,职能流动性大,招工难',
status: '已解决',
solution: '单位协调解决',
key: 7
},
{
name: '用工',
grade: '良',
time: '2022/7/15',
describe: '周边配套谁是能尽快完善,职能流动性大,招工难',
status: '已解决',
solution: '单位协调解决',
key: 8
},
{
name: '用工',
grade: '高',
time: '2022/7/15',
describe: '周边配套谁是能尽快完善,职能流动性大,招工难',
status: '已解决',
solution: '单位协调解决',
key: 9
},
];
const Index = ({ modalData, ModalFun, modalDataFun }) =>
{
const [tableData, setTableData] = useState([]),
[scroll, setScroll] = useState(false)
useEffect(() =>
{
getData()
}, [])
const getData = () =>
{
let REQUEST_url = ` http://32.24.202.71/hubble/common/v1/search?catalog=168f507ccc654a0892303018bfd9e6f2&features=1ebf6f025a9e4dcb980af2fb619999bd&max_feature=10000&search_type=1`
fetch(REQUEST_url)
.then((response) => response.json())
.then((responseJson) =>
{
setTableData(responseJson?.data)
if (responseJson.data?.reverse())
{
setScroll(true)
}
}, error =>
{
console.warn(error);
})
}
const columns = [
{
title: '走访时间',
dataIndex: 'f0000',
key: 'f0000',
},
{
title: '所属领域',
dataIndex: 'f0001',
key: 'f0001',
render: text => <div title={text} style={contentStyle}>{text}</div>
},
{
title: '问题分类',
dataIndex: 'f0002',
key: 'f0002',
render: text => <div title={text} style={contentStyle}>{text}</div>
},
{
title: '问题诉求',
dataIndex: 'f0003',
key: 'f0003',
render: text => <div title={text} style={contentStyle}>{text}</div>
},
{
title: '办理方式',
dataIndex: 'f0006',
key: 'f0006',
render: text => <div title={text} style={contentStyle}>{text}</div>
},
{
title: '处理状态',
dataIndex: 'f0007',
key: 'f0007',
render: text => <div title={text} className={styles.tableStatus} style={contentStyle}><div style={text === '已解决' ? { background: '#6de922' } : text === '良' ? { background: 'yellow' } : { background: '#F01E2A' }} className={styles.status} ></div ><span className={styles.statusSpan}>{text}</span></div>,
},
{
title: '操作',
width: 80,
render: (_, record) => (
<a className={styles.operation} onClick={() => { examine(_, record) }}>查看详情</a>
),
},
];
const examine = (_, record) =>
{
ModalFun(true)
const newData = { ...modalData, record, type: 3 }
modalDataFun(newData)
}
return (
<div className={styles.container}>
<div className={styles.core}>
<div className={styles.tableWarp}>
{scroll && <Table dataSource={tableData} columns={columns} isScroll rowClassName={{ oddStyle: { color: '#fff', background: '#103066', fontSize: '19px', height: '44px' }, evenStyle: { color: '#FFF2D6', background: '#0D2954', fontSize: '19px', height: '44px' } }} />}
</div>
</div>
</div >
);
};
export default Index;
Table css
.container {
pointer-events: auto;
width: 1000px;
z-index: 1;
.tableWarp {
width: 100%;
height: 410px;
// 名字
.tableName {
margin: 0 auto;
width: 50px;
height: 30px;
border-radius: 2px;
text-align: center;
line-height: 30px;
font-weight: bold;
background-color: rgba(63, 63, 204, .3);
font-size: 18px;
overflow: hidden;
}
// 等级
.tableStatus {
display: flex;
align-items: center;
justify-content: center;
.status {
width: 6px;
height: 6px;
background: #6DE922;
border-radius: 50%;
margin-right: 5px;
.statusSpan {
font-size: 14px;
font-weight: 400;
color: #FFFFFF;
}
}
}
.tableDetail {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.operation {
color: rgba(141, 237, 229, 1);
transition: all .3;
&:hover {
color: #ff4d4f;
}
}
}
}
Modal Js
import { Fragment } from 'react';
import styles from './style.less'
import { Carousel } from 'antd'
const index = ({ data, ModalFun }) =>
{
const ModalClick = (e) =>
{
ModalFun(false)
e.stopPropagation();
}
const analysisDom = () =>
{
const { child } = data
return (
<Carousel autoplay autoplaySpeed={5000}>
{child?.map((elm, index) => (
<div key={index} className={styles.center}><img src={elm.img} alt="" />
<div className={styles.footerSolid}></div>
</div>
))}
</Carousel>
)
}
const meetingDom = () =>
{
return (
<Fragment>
<div className={styles.head}>
<div className={styles.title}>{data?.title}</div>
</div>
<p className={styles.description}>
{data?.name}
</p>
<img src={data?.img} alt="" />
</Fragment>
)
}
const tableDom = () =>
{
const { record } = data
return (
<div className={styles.cardWarp} style={{ width: '100%', height: '100%' }}>
<div className={styles.cardHead}>
<div className={styles.cardHeadWrapper}>
{}
<div className={styles.cardHeadTitle}><p>问题详情</p></div>
</div>
</div>
<div className={styles.cardCore}>
<div className={styles.itemList}>
<div className={styles.designation}><p>走访时间</p></div>
<div className={styles.value}><p>{record?.f0000}</p></div>
</div>
<div className={styles.itemList}>
<div className={styles.designation}><p>走访对象名称</p></div>
<div className={styles.value}><p>{record?.f0008}</p></div>
<div className={styles.designation}><p>走访对象类型</p></div>
<div className={styles.value}><p>{record?.f0009}</p></div>
</div>
<div className={styles.itemList}>
<div className={styles.designation}><p>所属领域</p></div>
<div className={styles.value}>{record?.f0001}</div>
<div className={styles.designation}><p>问题分类</p></div>
<div className={styles.value}>{record?.f0002}</div>
</div>
<div className={styles.itemList}>
<div className={styles.designation}><p>问题诉求描述</p></div>
<div className={styles.value}>{record?.f0004}</div>
</div>
<div className={styles.itemList}>
<div className={styles.designation}><p>解决措施</p></div>
<div className={styles.value}>{record?.f0005}</div>
</div>
<div className={styles.itemList}>
<div className={styles.designation}><p>办理方式</p></div>
<div className={styles.value}>{record?.f0006}</div>
<div className={styles.designation}><p>处理状态</p></div>
<div className={styles.value}>{record?.f0007}</div>
</div>
</div>
</div>
)
}
const { type } = data
return (
<div onClick={ModalClick} className={styles.container}>
<div onClick={e => { e.stopPropagation() }} className={styles.core}>
{type === 1 && analysisDom()}
{type === 2 && meetingDom()}
{type === 3 && tableDom()}
</div>
</div>
);
};
export default index
Modal css
.container {
pointer-events: auto;
display: none;
position: fixed;
top: 0;
left: 0;
height: 100%;
width: 100%;
z-index: 9;
background: rgba(12, 13, 20, .6);
overflow: auto;
display: -webkit-box;
-webkit-box-align: center;
-webkit-box-pack: center;
display: -moz-box;
-moz-box-align: center;
-moz-box-pack: center;
display: -o-box;
-o-box-align: center;
-o-box-pack: center;
display: -ms-box;
-ms-box-align: center;
-ms-box-pack: center;
display: box;
box-align: center;
box-pack: center;
-webkit-transition: all 1.0s ease-in-out;
-moz-transition: all 1.0s ease-in-out;
-o-transition: all 1.0s ease-in-out;
-ms-transition: all 1.0s ease-in-out;
transition: all 1.0s ease-in-out;
opacity: .8;
&:hover {
opacity: 1;
}
.core {
position: relative;
width: 1200px;
height: 956px;
cursor: zoom-out;
border-style: none;
border: 0;
outline: none;
background: url('https://www.dataojocloud.com/dataeye/v1/data/image/get?imageid=631856f922a0c640685e8aeb')no-repeat;
background-size: cover;
padding: 0 63px;
text-align: center;
z-index: 9999999999;
user-select: none;
}
.cardWarp {
margin-top: 35px;
width: 100%;
height: calc(~'100% - 70px') !important;
// border: 5px solid rgba(38, 124, 219, .7);
// color: rgba(0, 0, 0, 0.85);
color: #fff;
font-size: 14px;
font-variant: tabular-nums;
line-height: 1.5715;
list-style: none;
font-feature-settings: 'tnum', "tnum";
position: relative;
// background: #fff;
border-radius: 2px;
// background: rgba(0, 0, 0, 0.45);
overflow: auto;
.cardHead {
min-height: 48px;
margin-bottom: -1px;
padding: 0 24px;
color: rgba(0, 0, 0, 0.85);
color: #fff;
font-weight: 500;
font-size: 16px;
background: transparent;
border-bottom: 1px solid #f0f0f0;
border-radius: 2px 2px 0 0;
.cardHeadWrapper {
display: flex;
align-items: center;
.cardHeadTitle {
display: flex;
justify-content: center;
align-items: center;
text-align: center;
// display: inline-block;
flex: 1 1;
padding: 8px 0;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
text-align: center;
p {
position: relative;
text-align: center;
font-size: 28px;
// width: 341px;
// height: 36px;
// font-size: 24px;
// background: linear-gradient(90deg, rgba(42, 77, 131, 0) 0%, #2A5697 51%, rgba(39, 75, 126, 0) 100%);
&::before {
position: absolute;
content: '';
// width: 100%;
width: 200%;
left: -50%;
height: 50px;
background: linear-gradient(90deg, rgba(42, 77, 131, 0) 0%, #2A5697 51%, rgba(39, 75, 126, 0) 100%);
z-index: -1;
}
}
}
.cardExtra {
float: right;
margin-left: auto;
padding: 16px 0;
color: rgba(0, 0, 0, 0.85);
font-weight: normal;
font-size: 14px;
a {
color: #1890ff;
text-decoration: none;
background-color: transparent;
outline: none;
cursor: pointer;
transition: color 0.3s;
&:hover {
color: #40a9ff;
}
}
}
}
}
// 内容
.cardCore {
padding: 24px;
color: #fff;
font-size: 24px;
// flex
.itemList {
display: flex;
// align-items: center;
margin-bottom: 20px;
// 分一份
.designation {
flex: 1;
p {
text-align: center;
width: 150px;
height: 33px;
// line-height: 33px;
background: linear-gradient(264deg, rgba(17, 58, 160, 0.0627) 0%, rgba(41, 102, 255, 0.6000) 100%);
}
}
// 剩下都是你的
.value {
text-indent: 20px;
text-align: left;
flex: 9;
}
}
}
}
}
utils Js
import { useState, useEffect, useRef, Fragment } from 'react';
import styles from './style.less'
const index = ({ columns, dataSource, rowClassName = {}, isScroll = false }) =>
{
const [timer, setTimer] = useState(null),
[active, setActive] = useState(null),
[tableWarpHeight, setTableWarpHeight] = useState(0),
[tableHeadHeight, setTableHeadHeight] = useState(0),
[tbodyHeight, setTbodyHeight] = useState(0)
const bodyRef = useRef(null),
scrollContainerRef = useRef(null),
tableWarpRef = useRef(null),
tableHeadRef = useRef(null)
useEffect(() =>
{
if (timer) clearInterval(timer);
InitialScroll(isScroll)
let tabHeight = tableWarpRef?.current.clientHeight
let tbodyHeight = scrollContainerRef?.current.clientHeight
let tableHead = tableHeadRef?.current.clientHeight
setTableWarpHeight(tabHeight)
setTableHeadHeight(tableHead)
setTbodyHeight(tbodyHeight)
return () =>
{
clearInterval(timer)
}
}, [dataSource])
const InitialScroll = (isScroll) =>
{
if (isScroll)
{
let startingTime = 30
let scrollContainer = scrollContainerRef.current
let initScroll = bodyRef.current
let time = setInterval(() =>
{
if (isScroll && initScroll.scrollTop >= scrollContainer?.scrollHeight)
{
initScroll.scrollTop = 0
} else
{
initScroll.scrollTop++
}
}, startingTime)
setTimer(time)
}
}
const handleMouse = (flag) =>
{
return () =>
{
setActive(flag)
}
}
const widthAndHigh = (data) =>
{
const { title, width } = data
let newWidth = width?.toString() + 'px'
let style = { color: '#37B6FF' }
let padding = null;
let color = '#ff4d4f'
title === '操作' ? style = { padding, color, width: newWidth } : null
return style;
};
const theadDom = () =>
{
return (
<tr >
{columns.map((data, index) => (
<th key={index} style={widthAndHigh(data)} className={styles.table_cell}>
{data.title}
</th>
))}
</tr>
)
}
const tableWarp = () =>
{
return <table style={{ tableLayout: 'fixed' }}>
<colgroup>
{columns.map((data, index) => (
<col style={{ width: data.width }} key={index} >
</col>
))}
</colgroup>
<tbody ref={scrollContainerRef} className={styles.table_tbody}>
{tbodyDom()}
</tbody>
</table>
}
const tableScrollWarp = () =>
{
return (
['臧', '小', '川'].map((_, index) => (
<table key={index} style={{ tableLayout: 'fixed' }}>
<colgroup>
{columns.map((data, index) => (
<col style={{ width: data.width }} key={index} >
</col>
))}
</colgroup>
<tbody ref={scrollContainerRef} className={styles.table_tbody}>
{tbodyDom()}
</tbody>
</table>
))
)
}
const tbodyDom = () =>
{
{ }
return dataSource.map((data, index) =>
{
let property = []
for (const key in columns)
{
if (Object.hasOwnProperty.call(dataSource, key))
{
const element = columns[key].dataIndex
property.push(element)
}
}
return (
<tr style={index % 2 === 0 ? rowClassName?.oddStyle : rowClassName?.evenStyle} key={index} onMouseEnter={handleMouse(index)}
onMouseLeave={handleMouse(null)} className={`${styles.tbody_row} ${styles.tbody_rowLevel_0}`}>
{}
{
columns.map((element, i) =>
{
return (
<Fragment key={i}>
{}
<td key={i} className={`${styles.table_cell} ${index === active && styles.tbody_rowHover}`}>
{}
{element.render ? element.render(data[property[i]], data) : data[property[i]]}
</td>
</Fragment>
)
})
}
</tr >
)
})
}
return (
<div ref={tableWarpRef}
style={{ height: '100%' }} className={styles.wrapper}>
<div className={styles.spin_nested_loading}>
<div className={styles.container}>
<div className={styles.table} >
<div className={styles.table_container}>
<div ref={tableHeadRef} className={styles.table_header}>
<table style={{ tableLayout: 'fixed' }}>
<colgroup>
{columns.map((data, index) => (
<col style={{ width: data.width }} key={index} >
</col>
))}
</colgroup>
<thead className={styles.table_thead}>
{theadDom()}
</thead>
</table>
</div>
<div onMouseEnter={() =>
{
if (timer) clearInterval(timer);
clearInterval(timer)
}} onMouseLeave={() =>
{
if (timer) clearInterval(timer);
InitialScroll(isScroll)
}} ref={bodyRef} id='cyclicScroll' style={{ overflow: 'auto', height: tableWarpHeight - tableHeadHeight + 'px' }} className={styles.table_body}>
{tbodyHeight >= (tableWarpHeight - tableHeadHeight) ? tableScrollWarp() : tableWarp()}
</div>
</div>
</div>
</div>
</div>
</div >
);
};
export default index
utils css
.wrapper {
clear: both;
max-width: 100%;
&::after {
display: table;
clear: both;
content: '';
}
&::before {
display: table;
content: '';
}
.spin_nested_loading {
position: relative;
}
:focus-visible {
outline: -webkit-focus-ring-color auto 1px;
}
}
.container {
position: relative;
transition: opacity 0.3s;
.table {
box-sizing: border-box;
margin: 0;
padding: 0;
color: rgba(0, 0, 0, 0.85);
font-variant: tabular-nums;
line-height: 1.5715;
list-style: none;
font-feature-settings: 'tnum', "tnum";
position: relative;
font-size: 14px;
border-radius: 2px;
background-color: rgba(255, 255, 255, 0);
table {
width: 100%;
text-align: left;
border-radius: 2px 2px 0 0;
border-collapse: separate;
border-spacing: 0;
// .table_thead tr th,
// .table_tbody tr td {
// position: relative;
// padding: 16px 16px;
// overflow-wrap: break-word;
// }
.table_thead {
width: 100%;
tr:first-child th:first-child {
border-top-left-radius: 2px;
}
tr:last-child th:last-child {
border-top-right-radius: 2px;
}
}
tr th {
position: relative;
color: rgba(0, 0, 0, 0.85);
font-weight: 500;
text-align: left;
background: #fafafa;
border-bottom: 1px solid #f0f0f0;
transition: background 0.3s ease;
background-color: rgba(13, 40, 84, .8);
height: 44px;
font-size: 21px;
font-weight: 700;
color: #37B6FF;
text-align: center;
border-bottom: none;
// 全选并且排出最后一个
&:not(:last-child)::before {
position: absolute;
top: 50%;
right: 0;
width: 1px;
height: 1.6em;
background-color: rgba(0, 0, 0, 0.06);
transform: translateY(-50%);
transition: background-color 0.3s;
content: '';
}
}
.table_tbody {
// 表格高亮
// tr {
// transition: background .3s;
// &:hover {
// background: rgba(12, 13, 20, 0.7) !important;
// z-index: 1;
// }
// }
tr td {
// border-bottom: 1px solid #f0f0f0;
transition: background .3s;
text-align: center;
.tag {
box-sizing: border-box;
margin: 0;
padding: 0;
color: rgba(0, 0, 0, 0.85);
font-size: 14px;
font-variant: tabular-nums;
line-height: 1.5715;
list-style: none;
font-feature-settings: 'tnum', "tnum";
display: inline-block;
height: auto;
margin-right: 8px;
padding: 0 7px;
font-size: 12px;
line-height: 20px;
white-space: nowrap;
background: #fafafa;
border: 1px solid #d9d9d9;
border-radius: 2px;
opacity: 1;
transition: all 0.3s;
}
.tag_green {
color: #389e0d;
background: #f6ffed;
border-color: #b7eb8f;
}
.tag_volcano {
color: #d4380d;
background: #fff2e8;
border-color: #ffbb96;
}
.space {
display: inline-flex;
}
.center {
align-items: center;
}
}
.tbody_rowHover {
// background: #fafafa;
background-color: rgba(12, 13, 20, .7);
}
}
}
}
&::after {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
z-index: 10;
display: none;
width: 100%;
height: 100%;
// background: #fff;
opacity: 0;
transition: all 0.3s;
content: '';
pointer-events: none;
}
.table_container {
border-top-left-radius: 2px;
border-top-right-radius: 2px;
&::after,
&::before {
position: absolute;
top: 0;
bottom: 0;
z-index: 1;
width: 30px;
transition: box-shadow 0.3s;
content: '';
pointer-events: none;
}
}
::-webkit-scrollbar {
// 改变滚动条宽度
height: 5px;
width: 7px;
overflow-y: auto;
}
::-webkit-scrollbar {
// 隐藏进度条
display: none;
}
}