先上成品示意图:
几个关键步骤:
一 定义数据结构:
export const data = [
{
key: 'row1',
beforeUpdateLevel: '等级1',
level_1: '保',
level_2: '升',
level_3: '升',
level_4: '未设置',
level: 1, // 表示当前行变更前为等级1
},
{
key: 'row2',
beforeUpdateLevel: '等级2',
level_1: '降',
level_2: '保',
level_3: '升',
level_4: '升',
level: 2,
},
{
key: 'row3',
beforeUpdateLevel: '等级3',
level_1: '降',
level_2: '未设置',
level_3: '未设置',
level_4: '升',
level: 3,
},
{
key: 'row4',
beforeUpdateLevel: '等级4',
level_1: '降',
level_2: '未设置',
level_3: '未设置',
level_4: '未设置',
level: 4,
},
];
二 实现表头分组:
const columns = [
// 利用title可以返回ReactNode属性 和 css 实现分组
{
title: (
<div className={styler.headerCell}>
<div className={styler.afer}>变更后等级</div>
<div className={styler.before}>变更前等级</div>
</div>
),
dataIndex: 'beforeUpdateLevel',
key: 'beforeUpdateLevel',
// 固定分组这一栏的宽度
width: 220,
align: 'center',
},
{
title: '等级1',
dataIndex: 'level_1',
align: 'center',
key: 'level_1',
},
{
title: '等级2',
dataIndex: 'level_2',
align: 'center',
key: 'level_2',
},
{
title: '等级3',
dataIndex: 'level_3',
align: 'center',
key: 'level_3',
},
{
title: '等级4',
dataIndex: 'level_4',
align: 'center',
key: 'level_4',
},
];
css 实现画对角线:
.headerCell {
// 画三角形
border-top: 43px rgb(250 250 250) solid;
/*上边框宽度等于表格第一行行高*/
width: 0px;
/*让容器宽度为0*/
height: 0px;
/*让容器高度为0*/
border-left: 237px #f8fbff solid;
/*左边框宽度等于表格第一行第一格宽度*/
position: relative;
.afer {
position: absolute;
top: -36px;
left: -70px;
width: 60px;
color: #666666;
}
.before {
position: absolute;
top: -24px;
left: -226px;
width: 60px;
color: #666666;
}
// 伪元素画分割线
&::after {
content: '';
position: absolute;
width: 1px;
height: 237px;
top: -140px;
left: -120px;
background-color: rgb(239 239 239);
display: block;
transform: rotate(-80deg);
}
}
注意:
固定分组表头的宽度,否则响应式可能会引起表头斜线错位等问题
三:添加单元格点击事件 设置选中状态
利用onCell属性为单元格添加点击事件
// 设置第一个单元格为默认选中
const [active, setActive] = useState('row1_level_1');
const newColums = columns.map((col, index) => {
return {
...col,
onCell: (record) => ({
record,
dataIndex: col.dataIndex,
title: col.title,
onClick: () => cellHandleClick(record, col),
}),
};
});
const cellHandleClick = (record, col) => {
const { key } = record;
const { dataIndex } = col;
// 利用record和col的组合key
setActive(`${key}_${dataIndex}`);
};
四:完整代码
import React, { useState, useEffect } from 'react';
import { Popover, Table, Tag } from 'antd';
import { data } from './data';
import classNames from 'classnames';
import DownPopover from './DownPopover';
import StayPopover from './StayPopover';
import UpPopover from './UpPopover';
import styler from './index.less';
import { history } from 'umi';
export default function ViewFlow({ onCellClick }) {
const [active, setActive] = useState('row1_level_1');
const SettingCell = ({
title,
dataIndex,
children,
record,
...restProps
}) => {
let childNode = children;
const [show, setShow] = useState(false);
useEffect(() => {}, [show]);
const handleOver = () => {
setShow(true);
};
const handleOut = () => {
setShow(false);
};
const SettingBtn = (
<div
className={styler.settingBtn}
onClick={() => handleGoSetting(record, dataIndex)}
>
去设置
</div>
);
const renderText = (record, dataIndex) => {
const { level } = record;
const [_, currentLevel] = dataIndex.split('_');
const text = record?.[dataIndex];
switch (text) {
case '未设置':
return show ? SettingBtn : <div className={styler.base}>{text}</div>;
case '保':
return (
<Item
text={text}
level={level}
currentLevel={currentLevel}
color="#3880FF"
background="rgba(56, 128, 255, 0.05)"
/>
);
case '降':
return (
<Item
text={text}
level={level}
currentLevel={currentLevel}
color="#FF3D00"
background="rgba(255, 61, 0, 0.05)"
/>
);
case '升':
return (
<Item
text={text}
level={level}
currentLevel={currentLevel}
color="#24B488"
background="rgba(36, 180, 136, 0.05)"
/>
);
default:
return text;
}
};
childNode = renderText(record, dataIndex);
const key = `${record?.key}_${dataIndex}`;
return (
<td
onMouseOut={handleOut}
onMouseOver={handleOver}
{...restProps}
className={classNames(
{ active: key === active },
{ firstColumnClass: dataIndex === 'beforeUpdateLevel' },
)}
>
{childNode}
</td>
);
};
const ItemMap = {
升: UpPopover,
降: DownPopover,
保: StayPopover,
};
const Item = ({ color, background, text, level, currentLevel }) => {
const Content = ItemMap[text];
return (
<Popover
content={<Content level={level} currentLevel={currentLevel} />}
placement="bottomRight"
>
<div className={styler.text}>
<div style={{ color, background }}>{text}</div>
</div>
</Popover>
);
};
const handleGoSetting = (record, dataIndex) => {
const { level } = record;
const [, levelNum] = dataIndex.split('_');
};
const cellHandleClick = (record, col) => {
const { key } = record;
const { dataIndex } = col;
setActive(`${key}_${dataIndex}`);
};
const columns = [
{
title: (
<div className={styler.headerCell}>
<div className={styler.afer}>变更后等级</div>
<div className={styler.before}>变更前等级</div>
</div>
),
dataIndex: 'beforeUpdateLevel',
key: 'beforeUpdateLevel',
width: 220,
align: 'center',
},
{
title: '等级1',
dataIndex: 'level_1',
align: 'center',
key: 'level_1',
},
{
title: '等级2',
dataIndex: 'level_2',
align: 'center',
key: 'level_2',
},
{
title: '等级3',
dataIndex: 'level_3',
align: 'center',
key: 'level_3',
},
{
title: '等级4',
dataIndex: 'level_4',
align: 'center',
key: 'level_4',
},
];
const newColums = columns.map((col, index) => {
return {
...col,
onCell: (record) => ({
record,
dataIndex: col.dataIndex,
title: col.title,
onClick: () => cellHandleClick(record, col),
}),
};
});
const components = {
body: {
cell: SettingCell,
},
};
return (
<div className={styler.content}>
<Table
components={components}
columns={newColums}
bordered
dataSource={data}
pagination={false}
/>
</div>
);
}
data.js
export const data = [
{
key: 'row1',
beforeUpdateLevel: '等级1',
level_1: '保',
level_2: '升',
level_3: '升',
level_4: '未设置',
level: 1, // 表示当前行变更前为等级1
},
{
key: 'row2',
beforeUpdateLevel: '等级2',
level_1: '降',
level_2: '保',
level_3: '升',
level_4: '升',
level: 2,
},
{
key: 'row3',
beforeUpdateLevel: '等级3',
level_1: '降',
level_2: '未设置',
level_3: '未设置',
level_4: '升',
level: 3,
},
{
key: 'row4',
beforeUpdateLevel: '等级4',
level_1: '降',
level_2: '未设置',
level_3: '未设置',
level_4: '未设置',
level: 4,
},
];
index.less
.content {
.base {
padding: 5px 0px;
color: #c8c8c8;
width: 60px;
display: inline-block;
}
.settingBtn {
box-sizing: border-box;
display: inline-block;
background: rgba(56, 128, 255, 0.1);
border: 1px solid rgba(56, 128, 255, 0.5);
padding: 4px;
width: 60px;
border-radius: 2px;
color: #3880ff;
}
.text {
box-sizing: border-box;
width: 60px;
padding: 4px 0;
display: inline-flex;
justify-content: center;
align-items: center;
div {
width: 20px;
height: 20px;
border-radius: 50%;
background: rgba(56, 128, 255, 0.05);
}
}
.headerCell {
// 画三角形
border-top: 43px rgb(250 250 250) solid;
/*上边框宽度等于表格第一行行高*/
width: 0px;
/*让容器宽度为0*/
height: 0px;
/*让容器高度为0*/
border-left: 237px #f8fbff solid;
/*左边框宽度等于表格第一行第一格宽度*/
position: relative;
.afer {
position: absolute;
top: -36px;
left: -70px;
width: 60px;
color: #666666;
}
.before {
position: absolute;
top: -24px;
left: -226px;
width: 60px;
color: #666666;
}
// 伪元素画分割线
&::after {
content: '';
position: absolute;
width: 1px;
height: 237px;
top: -140px;
left: -120px;
background-color: rgb(239 239 239);
display: block;
transform: rotate(-80deg);
}
}
:global {
.firstColumnClass {
background: #f8fbff;
}
.active {
background: linear-gradient(
0deg,
rgba(56, 128, 255, 0.05),
rgba(56, 128, 255, 0.05)
);
// border: 1px solid #3880ff;
// border-right: 1px solid #3880ff !important ;
// box-sizing: border-box;
box-shadow: 0px 0px 2px #3880ff inset;
}
.ant-table-thead {
height: 30px;
}
.ant-table-tbody > tr > td {
padding: 8px 0px;
}
.ant-table.ant-table-bordered
> .ant-table-container
> .ant-table-content
> table
> thead
> tr
> th {
padding: 0;
}
// .ant-table-tbody > tr > td :first-child {
// background: #f8fbff;
// &:hover {
// background: #f8fbff;
// }
// }background: #f8fbff;
// .ant-table-tbody > tr > td:nth-child(2) {
// background: #bae7ff;
// }
.ant-table-tbody > tr > td:hover {
cursor: pointer;
background: linear-gradient(
0deg,
rgba(56, 128, 255, 0.05),
rgba(56, 128, 255, 0.05)
);
}
}
}
DownPopver.jsx
import React from 'react';
import { Popover, Space, Tag, Typography } from 'antd';
export default function DownPopover({ level, currentLevel }) {
return (
<Space size={10} direction="vertical">
<Tag color="#FF3D00">降级</Tag>
<>
<Typography.Text type="secondary">
变更前等级:<Typography.Text>等级{`${level}`}</Typography.Text>
</Typography.Text>
<Typography.Text type="secondary">
变更后等级:
<Typography.Text>等级{`${currentLevel}`}</Typography.Text>
</Typography.Text>
</>
</Space>
);
}
StayPopover.jsx
import React from 'react';
import { Popover, Space, Tag, Typography } from 'antd';
export default function StayPopover({ level, currentLevel }) {
return (
<Space size={10} direction="vertical">
<Tag color="#599CFF">保级</Tag>
<>
<Typography.Text type="secondary">
保级等级:<Typography.Text>等级{`${currentLevel}`}</Typography.Text>
</Typography.Text>
</>
</Space>
);
}
UpPopover.jsx
import React from 'react';
import { Popover, Space, Tag, Typography } from 'antd';
export default function UpPopover({ level, currentLevel }) {
return (
<Space size={10} direction="vertical">
<Tag color="#24B488">升级</Tag>
<>
<Typography.Text type="secondary">
变更前等级:<Typography.Text>等级{`${level}`}</Typography.Text>
</Typography.Text>
<Typography.Text type="secondary">
变更后等级:
<Typography.Text>等级{`${currentLevel}`}</Typography.Text>
</Typography.Text>
<Typography.Text type="secondary">
状态:<Typography.Text>启用</Typography.Text>
</Typography.Text>
</>
</Space>
);
}