泛微薪资核算iframe表格
|
After Width: | Height: | Size: 950 B |
|
After Width: | Height: | Size: 3.0 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 3.5 KiB |
|
After Width: | Height: | Size: 101 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 3.2 KiB |
|
After Width: | Height: | Size: 3.1 KiB |
|
After Width: | Height: | Size: 104 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 3.3 KiB |
|
After Width: | Height: | Size: 3.1 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 254 B |
|
After Width: | Height: | Size: 1017 B |
|
After Width: | Height: | Size: 389 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 31 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 3.4 KiB |
|
|
@ -0,0 +1,172 @@
|
|||
@node-color: #1890ff;
|
||||
@line-width: 1px;
|
||||
@line-color: @node-color;
|
||||
@expand-icon-size: 16px;
|
||||
.text-overflow-ellipsis() {
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.org-chart {
|
||||
&-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-top: 26px;
|
||||
}
|
||||
|
||||
&-table {
|
||||
border-collapse: separate;
|
||||
border-spacing: 0;
|
||||
line-height: 1.5715;
|
||||
|
||||
.expand-icon {
|
||||
display: inline-block;
|
||||
width: @expand-icon-size;
|
||||
height: @expand-icon-size;
|
||||
border-radius: 50%;
|
||||
background-color: @line-color;
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
bottom: 0;
|
||||
margin-left: -@expand-icon-size / 2;
|
||||
margin-bottom: -@expand-icon-size / 2;
|
||||
z-index: 99;
|
||||
cursor: pointer;
|
||||
|
||||
&-expanded,
|
||||
&-collapsed {
|
||||
&::before {
|
||||
content: '';
|
||||
width: 8px;
|
||||
height: 2px;
|
||||
background-color: #fff;
|
||||
position: absolute;
|
||||
top: 7px;
|
||||
left: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
&-collapsed {
|
||||
&::after {
|
||||
content: '';
|
||||
width: 2px;
|
||||
height: 8px;
|
||||
background-color: #fff;
|
||||
position: absolute;
|
||||
top: 4px;
|
||||
left: 7px;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: darken(@line-color, 5%);
|
||||
}
|
||||
}
|
||||
|
||||
td {
|
||||
text-align: center;
|
||||
padding: 0;
|
||||
vertical-align: top;
|
||||
|
||||
&.hidden {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
&-node {
|
||||
display: inline-block;
|
||||
//border: 1px solid @node-color;
|
||||
//padding: 0.5rem;
|
||||
margin: 0 5px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
&-line {
|
||||
height: 20px;
|
||||
position: relative;
|
||||
|
||||
&:not(&-children) {
|
||||
&:before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
width: @line-width;
|
||||
height: 100%;
|
||||
background-color: @line-color;
|
||||
}
|
||||
}
|
||||
|
||||
&-children {
|
||||
&:nth-child(odd) {
|
||||
border-right: @line-width solid @line-color;
|
||||
}
|
||||
|
||||
&:not(:first-child, :last-child) {
|
||||
border-top: @line-width solid @line-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 水平
|
||||
&&-horizontal {
|
||||
.expand-icon {
|
||||
top: 50%;
|
||||
bottom: auto;
|
||||
left: auto;
|
||||
right: 0;
|
||||
margin: -@expand-icon-size / 2 -@expand-icon-size / 2 auto auto;
|
||||
}
|
||||
|
||||
td {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
tr {
|
||||
&:nth-child(odd) {
|
||||
> .org-chart-table-line-children {
|
||||
border-bottom: @line-width solid @line-color;
|
||||
}
|
||||
}
|
||||
|
||||
&:not(:first-child, :last-child) {
|
||||
> .org-chart-table-line-children {
|
||||
border-left: @line-width solid @line-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.org-chart-table {
|
||||
&-node {
|
||||
margin: 5px 0;
|
||||
width: 120px;
|
||||
|
||||
&-content {
|
||||
.text-overflow-ellipsis();
|
||||
}
|
||||
}
|
||||
|
||||
&-line {
|
||||
height: auto;
|
||||
width: 20px;
|
||||
|
||||
&:not(&-children) {
|
||||
&:before {
|
||||
top: auto;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: @line-width;
|
||||
}
|
||||
}
|
||||
|
||||
&-children {
|
||||
&:nth-child(odd),
|
||||
&:not(:first-child, :last-child) {
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
import classNames from "classnames";
|
||||
import React from "react";
|
||||
import { OrgChartProps } from "./interface";
|
||||
import OrgChartNode from "./components/OrgChartNode";
|
||||
import ChartWrapper from "./components/ChartWrapper";
|
||||
import styles from "./OrgChart.less";
|
||||
|
||||
const OrgChart = (props: OrgChartProps) => {
|
||||
const { data, className, style, pan, zoom, maxZoom,minZoom,zoomStep, ...otherProps } = props;
|
||||
return !!data ? (
|
||||
<ChartWrapper
|
||||
pan={pan}
|
||||
zoom={zoom}
|
||||
maxZoom={maxZoom}
|
||||
minZoom={minZoom}
|
||||
zoomStep={zoomStep}
|
||||
>
|
||||
<div className={classNames(styles["org-chart-container"], className)} style={style}>
|
||||
<OrgChartNode {...otherProps} data={data}/>
|
||||
</div>
|
||||
</ChartWrapper>
|
||||
) : null;
|
||||
};
|
||||
|
||||
OrgChart.defaultProps = {
|
||||
direction: "vertical",
|
||||
expandAll: true,
|
||||
expandable: false
|
||||
};
|
||||
|
||||
export default OrgChart;
|
||||
|
|
@ -0,0 +1,126 @@
|
|||
import React from "react";
|
||||
import styles from "../OrgChart.less";
|
||||
|
||||
|
||||
const clsPrefix = "cp-react-org-chart-chart-wrapper";
|
||||
|
||||
export default class ChartWrapper extends React.PureComponent {
|
||||
|
||||
static propTypes = {};
|
||||
|
||||
static defaultProps = {
|
||||
maxZoom: 2,
|
||||
minZoom: 0.5,
|
||||
zoomStep: 0.03
|
||||
};
|
||||
|
||||
state = {
|
||||
scale: 1,
|
||||
isMove: false,
|
||||
translateX: 0,
|
||||
translateY: 0,
|
||||
originX: 0,
|
||||
originY: 0
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
document.addEventListener("mousemove", this.onMouseMove);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
document.removeEventListener("mousemove", this.onMouseMove);
|
||||
}
|
||||
|
||||
onZoom = e => {
|
||||
const { zoom, maxZoom, minZoom, zoomStep } = this.props;
|
||||
|
||||
if (!zoom) return;
|
||||
const { deltaY, pageX, pageY } = e;
|
||||
|
||||
const { offsetWidth, offsetHeight } = this.wrapper;
|
||||
let { scale } = this.state;
|
||||
const { translateX, translateY } = this.state;
|
||||
if (deltaY < 0 && scale < maxZoom) {
|
||||
scale += zoomStep;
|
||||
}
|
||||
if (deltaY > 0 && scale > minZoom) {
|
||||
scale -= zoomStep;
|
||||
}
|
||||
this.setState({
|
||||
scale,
|
||||
originX: pageX,
|
||||
originY: pageY
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
toggleMove(isMove) {
|
||||
this.setState({ isMove });
|
||||
}
|
||||
|
||||
onMouseDown = e => {
|
||||
this.toggleMove(true);
|
||||
const { pageX, pageY } = e;
|
||||
const { translateX, translateY } = this.state;
|
||||
this.pageX = pageX;
|
||||
this.pageY = pageY;
|
||||
this.translateX = translateX;
|
||||
this.translateY = translateY;
|
||||
};
|
||||
|
||||
onMouseUp = e => {
|
||||
this.toggleMove(false);
|
||||
};
|
||||
|
||||
onMouseMove = e => {
|
||||
|
||||
const { isMove, scale } = this.state;
|
||||
if (!isMove) return;
|
||||
const { pageX, pageY } = e;
|
||||
const x = (pageX - this.pageX) / scale + this.translateX;
|
||||
const y = (pageY - this.pageY) / scale + this.translateY;
|
||||
this.setState({
|
||||
translateX: x,
|
||||
translateY: y
|
||||
});
|
||||
};
|
||||
|
||||
getProps() {
|
||||
const { zoom, pan } = this.props;
|
||||
const props = {};
|
||||
if (zoom) {
|
||||
props.onWheel = this.onZoom;
|
||||
}
|
||||
if (pan) {
|
||||
props.onMouseUp = this.onMouseUp;
|
||||
props.onMouseDown = this.onMouseDown;
|
||||
props.onMouseMove = this.onMouseMove;
|
||||
}
|
||||
return props;
|
||||
}
|
||||
|
||||
render() {
|
||||
const { children } = this.props;
|
||||
const { scale, translateX, translateY, isMove, originX, originY } = this.state;
|
||||
const cursor = isMove ? "move" : "default";
|
||||
const props = this.getProps();
|
||||
return (
|
||||
<div className={styles[clsPrefix]} style={{ overflow: "hidden", flex: 1 }}>
|
||||
<div
|
||||
ref={node => {
|
||||
this.wrapper = node;
|
||||
}}
|
||||
style={{
|
||||
cursor,
|
||||
transform: `scale(${scale}) translate(${translateX}px, ${translateY}px)`,
|
||||
transformOrigin: `${originX}px ${originY}px`
|
||||
}}
|
||||
className={styles["chart-wrapper-main"]}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,180 @@
|
|||
import classNames from "classnames";
|
||||
import React from "react";
|
||||
import { OrgChartNodeDataType, OrgChartNodeProps, RowProps } from "../interface";
|
||||
import OrgChartNodeRow from "./OrgChartNodeRow";
|
||||
import styles from "../OrgChart.less";
|
||||
|
||||
const OrgChartNode = (props: OrgChartNodeProps) => {
|
||||
const {
|
||||
data,
|
||||
expandAll,
|
||||
expandable,
|
||||
direction,
|
||||
renderNode,
|
||||
onExpand,
|
||||
onClick
|
||||
} = props;
|
||||
|
||||
const [expanded, setExpanded] = React.useState<boolean>(false);
|
||||
|
||||
const handleExpandChange = () => {
|
||||
const newExpanded = !expanded;
|
||||
setExpanded(newExpanded);
|
||||
onExpand?.(newExpanded, data);
|
||||
};
|
||||
|
||||
const getOrgNodeRow = (
|
||||
span: number,
|
||||
data: OrgChartNodeDataType
|
||||
): RowProps => {
|
||||
const contentNode: React.ReactNode = (
|
||||
<div className={styles["org-chart-table-node-content"]} title={data.label}>
|
||||
{data.label}
|
||||
</div>
|
||||
);
|
||||
|
||||
return [
|
||||
{
|
||||
span,
|
||||
content: (
|
||||
<td>
|
||||
<div
|
||||
className={classNames(styles["org-chart-table-node"], data.className)}
|
||||
style={data.style}
|
||||
onClick={() => onClick?.(data)}>
|
||||
{!!renderNode ? renderNode(data, contentNode) : contentNode}
|
||||
</div>
|
||||
</td>
|
||||
)
|
||||
}
|
||||
];
|
||||
};
|
||||
|
||||
const getLineRow = (span: number): RowProps => {
|
||||
return [
|
||||
{
|
||||
span,
|
||||
content: (
|
||||
<td className={styles["org-chart-table-line"]}>
|
||||
{expandable ? (
|
||||
<div
|
||||
className={classNames({
|
||||
[styles["expand-icon"]]: expandable,
|
||||
[styles["expand-icon-expanded"]]: expandable && expanded,
|
||||
[styles["expand-icon-collapsed"]]: expandable && !expanded
|
||||
})}
|
||||
onClick={() => handleExpandChange()}></div>
|
||||
) : null}
|
||||
</td>
|
||||
)
|
||||
}
|
||||
];
|
||||
};
|
||||
|
||||
const getChildrenLineRow = (span: number, itemSpan: number): RowProps => {
|
||||
const cells: RowProps = [];
|
||||
for (let index = 0; index < span; index = index + itemSpan) {
|
||||
cells.push({
|
||||
span: 1,
|
||||
content: (
|
||||
<td
|
||||
key={index}
|
||||
className={classNames(
|
||||
styles["org-chart-table-line"],
|
||||
styles["org-chart-table-line-children"],
|
||||
{
|
||||
[styles["hidden"]]: !expanded
|
||||
}
|
||||
)}>
|
||||
|
||||
</td>
|
||||
)
|
||||
});
|
||||
}
|
||||
return cells;
|
||||
};
|
||||
|
||||
const getChildrenNode = (
|
||||
datas: OrgChartNodeDataType[] = [],
|
||||
itemSpan: number
|
||||
): RowProps => {
|
||||
const cells: RowProps = [];
|
||||
datas.forEach((data, index) => {
|
||||
cells[itemSpan * index] = {
|
||||
span: itemSpan,
|
||||
content: (
|
||||
<td
|
||||
key={data.key}
|
||||
className={classNames(styles["org-chart-table-node-children"], {
|
||||
[styles["hidden"]]: !expanded
|
||||
})}>
|
||||
<OrgChartNode {...props} data={data}/>
|
||||
</td>
|
||||
)
|
||||
};
|
||||
});
|
||||
return cells;
|
||||
};
|
||||
|
||||
const getRows = (): RowProps[] => {
|
||||
if (data) {
|
||||
const rows: RowProps[] = [];
|
||||
const childrenLength = data?.children?.length || 0;
|
||||
const span = childrenLength * 2;
|
||||
|
||||
rows.push(getOrgNodeRow(span, data));
|
||||
// 判断是否有子节点
|
||||
if (data.children?.length ?? 0 > 0) {
|
||||
rows.push(getLineRow(span));
|
||||
rows.push(getChildrenLineRow(span, 1));
|
||||
rows.push(getChildrenNode(data.children, 2));
|
||||
}
|
||||
|
||||
if (direction === "horizontal") {
|
||||
const newRow: RowProps[] = [];
|
||||
rows.forEach((row, rowIndex) => {
|
||||
row.forEach((cell, cellIndex) => {
|
||||
newRow[cellIndex] = newRow[cellIndex] || [];
|
||||
newRow[cellIndex][rowIndex] = cell;
|
||||
});
|
||||
});
|
||||
return newRow;
|
||||
}
|
||||
return rows;
|
||||
}
|
||||
return [];
|
||||
};
|
||||
|
||||
const rows = React.useMemo(() => {
|
||||
return getRows();
|
||||
}, [data, expanded, direction]);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (expandable === true) {
|
||||
setExpanded(!!expandAll);
|
||||
} else {
|
||||
setExpanded(true);
|
||||
}
|
||||
}, [expandAll, expandable]);
|
||||
|
||||
return (
|
||||
<table
|
||||
className={classNames(styles["org-chart-table"], {
|
||||
[styles["org-chart-table-horizontal"]]: direction === "horizontal",
|
||||
[styles["org-chart-table-vertical"]]: direction === "vertical"
|
||||
})}>
|
||||
<tbody>
|
||||
{rows.map((row, index) => (
|
||||
<OrgChartNodeRow
|
||||
key={index}
|
||||
index={index}
|
||||
row={row}
|
||||
direction={direction}
|
||||
/>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
);
|
||||
};
|
||||
|
||||
export default OrgChartNode;
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
import { OrgChartNodeProps, RowProps } from '../interface';
|
||||
import React from 'react';
|
||||
|
||||
export interface OrgChartNodeRowProps
|
||||
extends Pick<OrgChartNodeProps, 'direction'> {
|
||||
className?: string;
|
||||
index: number;
|
||||
row: RowProps;
|
||||
}
|
||||
|
||||
const OrgChartNodeRow = (props: OrgChartNodeRowProps) => {
|
||||
const { className, index, row, direction } = props;
|
||||
|
||||
return (
|
||||
<tr key={index} className={className}>
|
||||
{row.map((cell, cellIndex) => {
|
||||
return React.cloneElement(cell.content, {
|
||||
key: cellIndex,
|
||||
...(direction === 'horizontal' ? { rowSpan: cell.span } : {}),
|
||||
...(direction === 'vertical' ? { colSpan: cell.span } : {}),
|
||||
});
|
||||
})}
|
||||
</tr>
|
||||
);
|
||||
};
|
||||
|
||||
export default OrgChartNodeRow;
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
import { OrgChartNodeDataType, OrgChartProps } from './interface';
|
||||
import OrgChart from './OrgChart';
|
||||
|
||||
export { OrgChartNodeDataType, OrgChartProps };
|
||||
export default OrgChart;
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
import React from "react";
|
||||
|
||||
export interface CellProps {
|
||||
span: number;
|
||||
content: React.ReactElement;
|
||||
}
|
||||
|
||||
export type RowProps = CellProps[];
|
||||
|
||||
export interface OrgChartNodeDataType {
|
||||
key: string | number;
|
||||
label: string;
|
||||
operatingIncome?: string;
|
||||
operatingIncomePercent?: string;
|
||||
operatingProfit?: string;
|
||||
operatingProfitPercent?: string;
|
||||
children?: OrgChartNodeDataType[];
|
||||
className?: string;
|
||||
style?: React.CSSProperties;
|
||||
}
|
||||
|
||||
export interface OrgChartNodeProps {
|
||||
data: OrgChartNodeDataType;
|
||||
expandAll?: boolean;
|
||||
expandable?: boolean;
|
||||
direction?: "horizontal" | "vertical";
|
||||
renderNode?: (
|
||||
node: OrgChartNodeDataType,
|
||||
originNode: React.ReactNode
|
||||
) => React.ReactNode;
|
||||
onExpand?: (expanded: boolean, node: OrgChartNodeDataType) => void;
|
||||
onClick?: (node: OrgChartNodeDataType) => void;
|
||||
}
|
||||
|
||||
export interface OrgChartProps extends Partial<OrgChartNodeProps> {
|
||||
className?: string;
|
||||
style?: React.CSSProperties;
|
||||
pan?: boolean;
|
||||
zoom?: boolean;
|
||||
maxZoom?: number;
|
||||
minZoom?: number;
|
||||
zoomStep?: number;
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
* Author: 黎永顺
|
||||
* name: 框架组件
|
||||
* Description:
|
||||
* Date: 2023/5/6
|
||||
*/
|
||||
import React, { FunctionComponent } from "react";
|
||||
import styles from "./index.less";
|
||||
|
||||
interface OwnProps {
|
||||
children: any;
|
||||
title: string;
|
||||
}
|
||||
|
||||
type Props = OwnProps;
|
||||
|
||||
const frameBox: FunctionComponent<Props> = (props) => {
|
||||
const { children, title } = props;
|
||||
|
||||
return (
|
||||
<div className={styles.frameBoxWrapper}>
|
||||
<div className={styles.header}><span><span>{title}</span></span></div>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default frameBox;
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
.frameBoxWrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
|
||||
.header {
|
||||
height: 54px;
|
||||
line-height: 54px;
|
||||
width: 100%;
|
||||
padding: 0 16px;
|
||||
|
||||
& > span {
|
||||
display: inline-block;
|
||||
font-size: 18px;
|
||||
color: #333333;
|
||||
letter-spacing: 0;
|
||||
font-weight: 700;
|
||||
width: 100%;
|
||||
height: inherit;
|
||||
border-bottom: 1px solid #e5e5e5;
|
||||
|
||||
span {
|
||||
border-left: 5px solid #0055F3;
|
||||
padding-left: 8px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
/*
|
||||
* Author: 黎永顺
|
||||
* name: 人员流量表卡片组件
|
||||
* Description:
|
||||
* Date: 2023/5/5
|
||||
*/
|
||||
import React, { FC } from "react";
|
||||
import { Progress } from "antd";
|
||||
import styles from "./index.less";
|
||||
import { TinyArea } from "@ant-design/plots";
|
||||
|
||||
interface OwnProps {
|
||||
title: string;
|
||||
number: string;
|
||||
unit: string;
|
||||
color: string;
|
||||
icon: any;
|
||||
isLine: boolean;
|
||||
yoyVal?: string;
|
||||
chainVal?: string;
|
||||
yoyIcon?: any;
|
||||
departureVal?: string,
|
||||
refusal?: string,
|
||||
chainIcon?: any;
|
||||
}
|
||||
|
||||
type Props = OwnProps;
|
||||
const config = {
|
||||
height: 60,
|
||||
autoFit: false,
|
||||
data: [264, 417, 438, 887, 309, 397, 550],
|
||||
smooth: true,
|
||||
areaStyle: {
|
||||
fill: "#d6e3fd"
|
||||
},
|
||||
tooltip: {
|
||||
showCrosshairs: false,
|
||||
showContent: false
|
||||
}
|
||||
};
|
||||
const FrameCard1: FC<Props> = (props) => {
|
||||
const { title, number, unit, color, icon, isLine, yoyIcon, chainIcon, yoyVal, chainVal, departureVal, refusal } = props;
|
||||
|
||||
return (
|
||||
<div className={styles.frameCard1Wrapper}>
|
||||
<div className={styles.top}>
|
||||
<div><img src={icon} alt=""/><span>{title}</span></div>
|
||||
<div><span>{number}</span><span>{unit}</span></div>
|
||||
</div>
|
||||
<div className={styles.center}>
|
||||
{
|
||||
isLine ?
|
||||
<TinyArea {..._.assign(config, {
|
||||
areaStyle: { fill: `l(270) 0:#fff 0.5:${color} 1:${color}` },
|
||||
line: { color }
|
||||
})} /> : <Progress percent={0.57} showInfo={false}/>
|
||||
}
|
||||
</div>
|
||||
<div className={styles.bottom}>
|
||||
{
|
||||
yoyIcon ? <>
|
||||
<span><span>同比:</span>{yoyIcon && <img src={yoyIcon} alt=""/>}<span>{yoyVal}</span></span>
|
||||
<span><span>环比:</span>{chainIcon && <img src={chainIcon} alt=""/>}<span>{chainVal}</span></span>
|
||||
</>:<>
|
||||
<span><span>主动离职:</span>{yoyIcon && <img src={yoyIcon} alt=""/>}<span>{departureVal}</span></span>
|
||||
<span><span>辞退:</span>{chainIcon && <img src={chainIcon} alt=""/>}<span>{refusal}</span></span>
|
||||
</>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default FrameCard1;
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
/*
|
||||
* Author: 黎永顺
|
||||
* name: 人员流量表卡片组件
|
||||
* Description:
|
||||
* Date: 2023/5/5
|
||||
*/
|
||||
import React, { FC } from "react";
|
||||
import styles from "./index.less";
|
||||
|
||||
interface OwnProps {
|
||||
title: string;
|
||||
icon: any;
|
||||
actualValue: any;
|
||||
contemValue: any;
|
||||
yoyVal?: string;
|
||||
chainVal?: string;
|
||||
yoyIcon?: any;
|
||||
chainIcon?: any;
|
||||
}
|
||||
|
||||
type Props = OwnProps;
|
||||
const FrameCard2: FC<Props> = (props) => {
|
||||
const { title, icon, actualValue, contemValue, yoyIcon, chainIcon, yoyVal, chainVal } = props;
|
||||
|
||||
return (
|
||||
<div className={styles.frameCard2Wrapper}>
|
||||
<div className={styles.top}>
|
||||
<div>{title}</div>
|
||||
<img src={icon} alt=""/>
|
||||
</div>
|
||||
<div className={styles.center}>
|
||||
<div><span>{actualValue?.title}</span>
|
||||
<div><span>{actualValue?.number}</span><span>{actualValue?.unit}</span></div>
|
||||
</div>
|
||||
<div><span>{contemValue?.title}</span>
|
||||
<div><span>{contemValue?.number}</span><span>{contemValue?.unit}</span></div>
|
||||
</div>
|
||||
</div>
|
||||
{
|
||||
yoyIcon &&
|
||||
<div className={styles.bottom}>
|
||||
<span><span>同比:</span>{yoyIcon && <img src={yoyIcon} alt=""/>}<span>{yoyVal}</span></span>
|
||||
<span><span>环比:</span>{chainIcon && <img src={chainIcon} alt=""/>}<span>{chainVal}</span></span>
|
||||
</div>
|
||||
}
|
||||
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default FrameCard2;
|
||||
|
|
@ -0,0 +1,193 @@
|
|||
.frameCard1Wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
.top {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 10px;
|
||||
|
||||
& > div:first-child {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
|
||||
img {
|
||||
width: 20px;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
span {
|
||||
font-size: 16px;
|
||||
color: #333333;
|
||||
letter-spacing: 0;
|
||||
font-weight: 700;
|
||||
}
|
||||
}
|
||||
|
||||
& > div:last-child {
|
||||
& > span:first-child {
|
||||
font-size: 24px;
|
||||
color: #14224F;
|
||||
letter-spacing: 0;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
& > span:last-child {
|
||||
font-size: 12px;
|
||||
color: #D4D4D4;
|
||||
font-weight: 400;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.center {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
min-height: 83px;
|
||||
|
||||
& > div {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
:global {
|
||||
.ant-progress {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.bottom {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
border-top: 0.5px solid #e5e5e5;
|
||||
padding-top: 10px;
|
||||
|
||||
& > span {
|
||||
img {
|
||||
width: 8px;
|
||||
margin-left: 8px;
|
||||
margin-right: 4px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
& > span:first-child {
|
||||
font-size: 12px;
|
||||
color: #D4D4D4;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
& > span:last-child {
|
||||
font-size: 12px;
|
||||
color: #333333;
|
||||
font-weight: 400;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.frameCard2Wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
.top {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding-bottom: 16px;
|
||||
background: url("../../../../assets/images/top_bottom.png") no-repeat;
|
||||
background-size: 100% auto;
|
||||
background-position-y: bottom;
|
||||
|
||||
& > div {
|
||||
font-size: 18px;
|
||||
color: #FFFFFF;
|
||||
letter-spacing: 0;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
img {
|
||||
width: 30px;
|
||||
}
|
||||
}
|
||||
|
||||
.center {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
||||
& > div {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
& > span:first-child {
|
||||
font-size: 16px;
|
||||
color: #FFFFFF;
|
||||
letter-spacing: 0;
|
||||
font-weight: 400;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
& > div:last-child {
|
||||
& > span:first-child {
|
||||
font-size: 24px;
|
||||
color: #FFF;
|
||||
letter-spacing: 0;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
& > span:last-child {
|
||||
font-size: 12px;
|
||||
color: #FFF;
|
||||
font-weight: 400;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.bottom {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
background: rgba(255, 255, 255, 0.3);
|
||||
border-radius: 2px;
|
||||
color: #FFF;
|
||||
padding: 0 8px;
|
||||
height: 33px;
|
||||
|
||||
& > span {
|
||||
img {
|
||||
width: 8px;
|
||||
margin-left: 8px;
|
||||
margin-right: 4px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
& > span:first-child {
|
||||
font-size: 12px;
|
||||
color: #FFF;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
& > span:last-child {
|
||||
font-size: 12px;
|
||||
color: #FFF;
|
||||
font-weight: 400;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,136 @@
|
|||
const PersonnelFlowBar = (echarts: any) => {
|
||||
// 实例化对象
|
||||
const myChart = echarts.init(document.getElementById("personnelFlowBar"));
|
||||
// 配置项
|
||||
const option = {
|
||||
tooltip: {
|
||||
// 坐标轴指示器,坐标轴触发有效
|
||||
trigger: "axis",
|
||||
axisPointer: {
|
||||
// 默认为直线,可选为:'line' | 'shadow'
|
||||
type: "shadow"
|
||||
},
|
||||
formatter: function (params: any) {
|
||||
let str = params[0].name + "<br>";
|
||||
for (let item of params) {
|
||||
str += item.seriesName + " : " + item.value + "%<br>";
|
||||
}
|
||||
return str;
|
||||
}
|
||||
},
|
||||
legend: {
|
||||
icon: "circle",
|
||||
top: "-1%",
|
||||
right: "2%",
|
||||
itemGap: 20,
|
||||
textStyle: {
|
||||
fontSize: 12,//字体大小
|
||||
color: "#B8B8B8"//字体颜色
|
||||
}
|
||||
},
|
||||
grid: {
|
||||
top: "15%",
|
||||
left: "2%",
|
||||
right: "2%",
|
||||
bottom: "3%",
|
||||
containLabel: true
|
||||
},
|
||||
xAxis: [
|
||||
{
|
||||
type: "category",
|
||||
data: ["一月", "二月", "三月", "四月", "五月", "六月", "七月", "八月", "九月", "十月", "十一月", "十二月"],
|
||||
axisTick: {
|
||||
alignWithLabel: true,
|
||||
show: false
|
||||
},
|
||||
// 修改坐标值样式
|
||||
axisLabel: {
|
||||
color: "#B8B8B8",
|
||||
fontSize: 16,
|
||||
show: true
|
||||
},
|
||||
axisLine: {
|
||||
show: false
|
||||
}
|
||||
}
|
||||
],
|
||||
yAxis: [
|
||||
{
|
||||
type: "value",
|
||||
name: "%",
|
||||
// 修改坐标值样式
|
||||
axisLabel: {
|
||||
color: "#787E95",
|
||||
fontSize: 14
|
||||
// formatter: '{value} 人'
|
||||
},
|
||||
nameTextStyle: {
|
||||
color: "#787E95",
|
||||
fontSize: 16
|
||||
},
|
||||
// 修改坐标轴线样式
|
||||
axisLine: {
|
||||
show: true,
|
||||
lineStyle: {
|
||||
color: "transparent"
|
||||
}
|
||||
},
|
||||
axisTick: {
|
||||
show: false // 不显示坐标轴刻度线
|
||||
},
|
||||
splitLine: {
|
||||
lineStyle: {
|
||||
type: "dashed",
|
||||
color: "rgba(93,126,158,1)"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
series: [
|
||||
{
|
||||
name: "今年",
|
||||
type: "bar",
|
||||
showBackground: false,
|
||||
barWidth: "15%",
|
||||
barGap: "80%",
|
||||
data: initData([12, 10, 23, 15, 3, 7, 11, 20, 17, 15, 8, 12]),
|
||||
// bar 样式修改
|
||||
itemStyle: {
|
||||
color: "#3EA1FF"
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "去年",
|
||||
type: "bar",
|
||||
showBackground: false,
|
||||
barWidth: "15%",
|
||||
data: initData([7, 12, 8, 12, 12, 5, 8, 15, 18, 12, 9, 7]),
|
||||
// bar 样式修改
|
||||
itemStyle: {
|
||||
color: "#0ED8F9"
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
// 配置项给实例对象
|
||||
myChart.setOption(option);
|
||||
return myChart;
|
||||
};
|
||||
|
||||
export default PersonnelFlowBar;
|
||||
|
||||
|
||||
const initData = (arr: Array<number>) => {
|
||||
const total = _.reduce(arr, (pre: number, cur: number) => pre + cur, 0);
|
||||
const serie = [];
|
||||
for (let i = 0; i < arr.length; i++) {
|
||||
const num = arr[i];
|
||||
const numcount = Percentage(num, total);
|
||||
serie.push(numcount);
|
||||
}
|
||||
return serie;
|
||||
};
|
||||
|
||||
const Percentage = (num: number, total: number) => {
|
||||
return (Math.round(num / total * 100));// 小数点后两位百分比
|
||||
};
|
||||
|
|
@ -0,0 +1,161 @@
|
|||
const TrendsInSeparationsCharts = (echarts: any) => {
|
||||
// 实例化对象
|
||||
const myChart = echarts.init(document.getElementById("trendsInSeparationsCharts"));
|
||||
// 配置项
|
||||
const option = {
|
||||
tooltip: {
|
||||
// 坐标轴指示器,坐标轴触发有效
|
||||
trigger: "axis",
|
||||
axisPointer: {
|
||||
// 默认为直线,可选为:'line' | 'shadow'
|
||||
type: "shadow"
|
||||
}
|
||||
},
|
||||
legend: {
|
||||
icon: "circle",
|
||||
top: "5%",
|
||||
right: "center",
|
||||
itemGap: 20,
|
||||
textStyle: {
|
||||
fontSize: 12,//字体大小
|
||||
color: "#787E95"//字体颜色
|
||||
}
|
||||
},
|
||||
grid: {
|
||||
top: "20%",
|
||||
left: "2%",
|
||||
right: "2%",
|
||||
bottom: "3%",
|
||||
containLabel: true
|
||||
},
|
||||
xAxis: [
|
||||
{
|
||||
type: "category",
|
||||
data: ["一月", "二月", "三月", "四月", "五月", "六月", "七月", "八月", "九月", "十月", "十一月", "十二月"],
|
||||
axisTick: {
|
||||
alignWithLabel: true,
|
||||
show: false
|
||||
},
|
||||
// 修改坐标值样式
|
||||
axisLabel: {
|
||||
color: "#B8B8B8",
|
||||
fontSize: 16,
|
||||
show: true
|
||||
},
|
||||
axisLine: {
|
||||
show: false
|
||||
}
|
||||
}
|
||||
],
|
||||
yAxis: [
|
||||
{
|
||||
type: "value",
|
||||
name: "人",
|
||||
// 修改坐标值样式
|
||||
axisLabel: {
|
||||
color: "#787E95",
|
||||
fontSize: 14
|
||||
// formatter: '{value} 人'
|
||||
},
|
||||
nameTextStyle: {
|
||||
color: "#787E95",
|
||||
fontSize: 16
|
||||
},
|
||||
// 修改坐标轴线样式
|
||||
axisLine: {
|
||||
show: true,
|
||||
lineStyle: {
|
||||
color: "transparent"
|
||||
}
|
||||
},
|
||||
axisTick: {
|
||||
show: false // 不显示坐标轴刻度线
|
||||
},
|
||||
splitLine: {
|
||||
lineStyle: {
|
||||
type: "dashed",
|
||||
color: "rgba(93,126,158,1)"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
type: "value",
|
||||
// 修改坐标值样式
|
||||
axisLabel: {
|
||||
color: "#787E95",
|
||||
fontSize: 14,
|
||||
formatter: '{value} %'
|
||||
},
|
||||
nameTextStyle: {
|
||||
color: "#787E95",
|
||||
fontSize: 16
|
||||
},
|
||||
// 修改坐标轴线样式
|
||||
axisLine: {
|
||||
show: true,
|
||||
lineStyle: {
|
||||
color: "transparent"
|
||||
}
|
||||
},
|
||||
axisTick: {
|
||||
show: false // 不显示坐标轴刻度线
|
||||
},
|
||||
splitLine: {
|
||||
show: false,
|
||||
lineStyle: {
|
||||
type: "dashed",
|
||||
color: "rgba(93,126,158,1)"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
series: [
|
||||
{
|
||||
name: "主动离职",
|
||||
type: "bar",
|
||||
stack:"Search Engine",
|
||||
showBackground: false,
|
||||
barWidth: "10%",
|
||||
barGap: "100%",
|
||||
data: [80, 30, 10, 38, 45, 70, 70, 70, 40, 50, 30, 10],
|
||||
// bar 样式修改
|
||||
itemStyle: {
|
||||
barBorderRadius: 2,
|
||||
color: "#00C48B"
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "辞退",
|
||||
type: "bar",
|
||||
stack:"Search Engine",
|
||||
showBackground: false,
|
||||
barWidth: "10%",
|
||||
data: [20, 10, 5, 10, 10, 1, 1, 12, 10, 15, 5, 2],
|
||||
// bar 样式修改
|
||||
itemStyle: {
|
||||
barBorderRadius: 2,
|
||||
color: "#FFA400"
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "离职率",
|
||||
type: "line",
|
||||
yAxisIndex: 1,
|
||||
data: [10, 4, 2, 5, 5, 7, 7, 10, 4, 6, 4, 2],
|
||||
itemStyle: {
|
||||
normal: {
|
||||
color: "#66EFE8",
|
||||
lineStyle: {
|
||||
color: "#66EFE8"
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
]
|
||||
};
|
||||
// 配置项给实例对象
|
||||
myChart.setOption(option);
|
||||
return myChart;
|
||||
};
|
||||
|
||||
export default TrendsInSeparationsCharts;
|
||||
|
|
@ -1,6 +1,201 @@
|
|||
@gap: 16px;
|
||||
@cardWidth: calc(~"20vw" - 4 * @gap / 5);
|
||||
.peopleFlowWrapper {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: #e5e5e5;
|
||||
overflow: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.headerCardWrapper {
|
||||
display: flex;
|
||||
margin-bottom: @gap;
|
||||
padding: 0;
|
||||
|
||||
& > li:not(:last-child) {
|
||||
margin-right: @gap;
|
||||
|
||||
}
|
||||
|
||||
& > li:not(:first-child) {
|
||||
background-size: 100% 100% !important;
|
||||
}
|
||||
|
||||
& > li:first-child {
|
||||
background: #FFF;
|
||||
}
|
||||
|
||||
& > li {
|
||||
width: @cardWidth;
|
||||
min-height: 220px;
|
||||
border-radius: 5px;
|
||||
padding: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.contentBox {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
|
||||
.contentBoxLeft {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.contenBoxTop {
|
||||
flex: 1;
|
||||
|
||||
.chartsBox {
|
||||
flex: 4 1;
|
||||
display: flex;
|
||||
|
||||
& > ul {
|
||||
width: @cardWidth;
|
||||
margin: 0 @gap 0 0;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
|
||||
& > li:first-child {
|
||||
margin-bottom: @gap;
|
||||
}
|
||||
|
||||
& > li {
|
||||
background: #FFF;
|
||||
min-height: 220px;
|
||||
border-radius: 5px;
|
||||
padding: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
& > div {
|
||||
flex: 3 1;
|
||||
background: #FFF;
|
||||
border-radius: 5px;
|
||||
min-height: 220px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
& > ul {
|
||||
margin: 20px 16px;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
|
||||
& > li:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
& > li.active {
|
||||
font-size: 16px;
|
||||
color: #0055F3;
|
||||
letter-spacing: 0;
|
||||
font-weight: 700;
|
||||
border-bottom: 1px solid #0055F3;
|
||||
}
|
||||
|
||||
& > li:not(:last-child) {
|
||||
margin-right: 26px;
|
||||
}
|
||||
|
||||
li {
|
||||
font-size: 14px;
|
||||
color: #999999;
|
||||
letter-spacing: 0;
|
||||
font-weight: 400;
|
||||
padding: 6px 0;
|
||||
}
|
||||
}
|
||||
|
||||
& > div {
|
||||
min-height: 377px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.contenBoxBottom {
|
||||
flex: 1;
|
||||
margin-top: @gap;
|
||||
background: #FFF;
|
||||
border-radius: 5px;
|
||||
|
||||
.trendsInSeparations {
|
||||
min-height: 220px;
|
||||
max-height: 220px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.rankingBox {
|
||||
width: @cardWidth;
|
||||
margin-left: @gap;
|
||||
background: #FFF;
|
||||
border-radius: 5px;
|
||||
|
||||
.trainingRanking {
|
||||
overflow-y: auto;
|
||||
|
||||
.rankDescBox {
|
||||
display: flex;
|
||||
font-size: 4px;
|
||||
align-items: center;
|
||||
|
||||
img {
|
||||
width: 30px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.rankNum {
|
||||
margin-right: 10px
|
||||
}
|
||||
|
||||
.rankNumber {
|
||||
width: 30px;
|
||||
margin-right: 10px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.rankDescPersBox {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
& > span:last-child {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-left: 8px;
|
||||
|
||||
& > span:first-child {
|
||||
font-size: 14px;
|
||||
color: #333333;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
& > span:last-child {
|
||||
font-size: 12px;
|
||||
color: #B8B8B8;
|
||||
font-weight: 400;
|
||||
min-width: 130px;
|
||||
max-width: 130px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.rankRow {
|
||||
background: #FEFAED;
|
||||
}
|
||||
|
||||
:global {
|
||||
.ant-list-item {
|
||||
padding: 16px 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,18 @@
|
|||
* Description:
|
||||
* Date: 2023/4/27
|
||||
*/
|
||||
import React, { FunctionComponent } from "react";
|
||||
import React, { FunctionComponent, useEffect, useState } from "react";
|
||||
// @ts-ignore
|
||||
import echarts from "echarts";
|
||||
import { Avatar, List } from "antd";
|
||||
import FrameCard1 from "./components/frameCard1";
|
||||
import FrameCard2 from "./components/frameCard2";
|
||||
import FrameBox from "../components/frameBox";
|
||||
import { frameCard1List, frameCard2List } from "@/pages/personnelReport/constants";
|
||||
import PersonnelFlowBar from "./components/personnelFlowCharts";
|
||||
import TrendsInSeparationsCharts from "./components/trendsInSeparationsCharts";
|
||||
import { personnelFlowTablist, trainingRankingList } from "../constants";
|
||||
import cs from "classnames";
|
||||
import styles from "./index.less";
|
||||
|
||||
interface OwnProps {
|
||||
|
|
@ -13,9 +24,116 @@ interface OwnProps {
|
|||
type Props = OwnProps;
|
||||
|
||||
const index: FunctionComponent<Props> = (props) => {
|
||||
|
||||
const [active, setActive] = useState<number>(0);
|
||||
useEffect(() => {
|
||||
const personnelFlowBar = PersonnelFlowBar(echarts);
|
||||
const trendsInSeparationsCharts = TrendsInSeparationsCharts(echarts);
|
||||
// 屏幕缩放对chart图表进行自适应处理,调用实例的resize方法
|
||||
window.onresize = () => {
|
||||
personnelFlowBar.resize();
|
||||
trendsInSeparationsCharts.resize();
|
||||
};
|
||||
return () => {
|
||||
};
|
||||
}, []);
|
||||
return (
|
||||
<div className={styles.peopleFlowWrapper}>
|
||||
<ul className={styles.headerCardWrapper}>
|
||||
<li>
|
||||
<FrameCard1
|
||||
title="在职人数"
|
||||
number="14338"
|
||||
unit="人"
|
||||
color="#78C0F9"
|
||||
isLine
|
||||
yoyIcon={require("../../../assets/images/down.png")}
|
||||
chainIcon={require("../../../assets/images/up.png")}
|
||||
yoyVal="4.75%"
|
||||
chainVal="4.75%"
|
||||
icon={require("../../../assets/images/zzrs.png")}
|
||||
/>
|
||||
</li>
|
||||
{_.map(frameCard2List, (item: any, index: number) => {
|
||||
return (
|
||||
<li key={index} style={{ background: `url(${item.bg}) no-repeat` }}>
|
||||
<FrameCard2 {...item} />
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
<div className={styles.contentBox}>
|
||||
<div className={styles.contentBoxLeft}>
|
||||
<div className={styles.contenBoxTop}>
|
||||
<div className={styles.chartsBox}>
|
||||
<ul>
|
||||
{_.map(frameCard1List, (item: any, index: number) => {
|
||||
return (
|
||||
<li key={index}>
|
||||
<FrameCard1 {...item} />
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
<div>
|
||||
<ul>
|
||||
{
|
||||
_.map(personnelFlowTablist, (item: any, index: number) => {
|
||||
const classes = cs({
|
||||
[styles["active"]]: index === active
|
||||
});
|
||||
return <li key={index} className={classes} onClick={() => setActive(index)}>{item}</li>;
|
||||
})
|
||||
}
|
||||
</ul>
|
||||
<div id="personnelFlowBar"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div className={styles.contenBoxBottom}>
|
||||
<FrameBox title="离职情况变化趋势">
|
||||
<div className={styles.trendsInSeparations} id="trendsInSeparationsCharts"/>
|
||||
</FrameBox>
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.rankingBox}>
|
||||
<FrameBox title="培训排名">
|
||||
<div className={styles.trainingRanking}>
|
||||
<List
|
||||
dataSource={trainingRankingList}
|
||||
renderItem={(item, index) => (
|
||||
<List.Item key={index}
|
||||
className={cs({ [styles["rankRow"]]: index === 0 || index === 1 || index === 2 })}>
|
||||
<List.Item.Meta
|
||||
title={
|
||||
<div className={styles.rankDescBox}>
|
||||
{
|
||||
(index === 0 || index === 1 || index === 2) ?
|
||||
<img src={index === 0 ?
|
||||
require("../../../assets/images/one.png") : index === 1 ?
|
||||
require("../../../assets/images/two.png") :
|
||||
require("../../../assets/images/three.png")} alt=""/> :
|
||||
<span className={styles.rankNumber}>{index + 1}</span>
|
||||
}
|
||||
<div className={styles.rankDescPersBox}>
|
||||
<Avatar src={item.avatar}/>
|
||||
<span><span>{item.name}</span><span>{item.dept}</span></span>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
<div style={{
|
||||
fontWeight: (index === 0 || index === 1 || index === 2) ? 700 : 400,
|
||||
fontSize: (index === 0 || index === 1 || index === 2) ? 16 : 14,
|
||||
color: index === 0 ? "#FFA000" : index === 1 ? "#6F9DAE" : index === 2 ? "#B0442B" : "#333333"
|
||||
}}>{item.score}</div>
|
||||
</List.Item>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</FrameBox>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
* Author: 黎永顺
|
||||
* name: 公司组件
|
||||
* Description:
|
||||
* Date: 2023/5/10
|
||||
*/
|
||||
import React, { FunctionComponent } from "react";
|
||||
import { OrgChartNodeDataType } from "@/components/reactChart/interface";
|
||||
import styles from "./index.less";
|
||||
import classNames from "classnames";
|
||||
|
||||
interface OwnProps {
|
||||
node: OrgChartNodeDataType;
|
||||
}
|
||||
|
||||
type Props = OwnProps;
|
||||
|
||||
const CompanyItem: FunctionComponent<Props> = (props) => {
|
||||
const { node } = props;
|
||||
const classes = classNames(styles["header"], {
|
||||
[styles["parent"]]: node.key === 0
|
||||
});
|
||||
return (
|
||||
<div className={styles.companyItemWrapper}>
|
||||
<div className={classes}>{node?.label}</div>
|
||||
<div className={styles.contBox}>
|
||||
<div>
|
||||
<div>
|
||||
<span>营业收入</span>
|
||||
<span>{node?.operatingIncome}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span>同比</span>
|
||||
<span>{node?.operatingIncomePercent}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div>
|
||||
<span>利润总额</span>
|
||||
<span>{node?.operatingProfit}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span>同比</span>
|
||||
<span>{node?.operatingProfitPercent}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default CompanyItem;
|
||||
|
|
@ -0,0 +1,94 @@
|
|||
const IncomeStatisticsRadar = (echarts: any) => {
|
||||
// 实例化对象
|
||||
const myChart = echarts.init(document.getElementById("incomeStatisticsRadar"));
|
||||
// 配置项
|
||||
const option = {
|
||||
tooltip: {
|
||||
show: true
|
||||
},
|
||||
legend: {
|
||||
show: true, //是否显示图例
|
||||
icon: "rect", //图例形状
|
||||
bottom: "2%", // 图例距离顶部边距
|
||||
left: "center", // 图例距离左侧边距
|
||||
itemWidth: 10, // 图例标记的图形宽度。[ default: 25 ]
|
||||
itemHeight: 10, // 图例标记的图形高度。[ default: 14 ]
|
||||
itemGap: 30, // 图例每项之间的间隔。[ default: 10 ]横向布局时为水平间隔,纵向布局时为纵向间隔。
|
||||
orient: "horizontal", // 图例列表的布局朝向,'horizontal'为横向,''为纵向.
|
||||
textStyle: { // 图例的公用文本样式。
|
||||
fontSize: 14,
|
||||
color: "#D4D4D4"
|
||||
}
|
||||
},
|
||||
radar: {
|
||||
// shape: 'circle',
|
||||
// , axisLabel: { show: true } 该属性加上会报错
|
||||
indicator: [
|
||||
{ name: "销售", max: 20000 },
|
||||
{ name: "客服", max: 20000 },
|
||||
{ name: "开发", max: 20000 },
|
||||
{ name: "其他", max: 20000 },
|
||||
{ name: "项目", max: 20000 }
|
||||
],
|
||||
splitArea: {
|
||||
show: false
|
||||
},
|
||||
axisLine: {
|
||||
show: false
|
||||
}
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: "人均浮动收入",
|
||||
type: "radar",
|
||||
data: [
|
||||
{
|
||||
value: [9000, 2000, 9000, 6000, 6000],
|
||||
name: "人均浮动收入"
|
||||
}
|
||||
],
|
||||
label: {
|
||||
show: false,
|
||||
formatter: function (params: any) {
|
||||
return params.value;
|
||||
}
|
||||
},
|
||||
itemStyle: { //此属性的颜色和下面areaStyle属性的颜色都设置成相同色即可实现
|
||||
color: "#64C3FF",
|
||||
borderColor: "#64C3FF"
|
||||
},
|
||||
areaStyle: {
|
||||
color: "rgba(100,195,255,0.26)"
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "人均固定收入",
|
||||
type: "radar",
|
||||
data: [
|
||||
{
|
||||
value: [5500, 8000, 5000, 8800, 8900],
|
||||
name: "人均固定收入"
|
||||
}
|
||||
],
|
||||
label: {
|
||||
show: false,
|
||||
formatter: function (params: any) {
|
||||
return params.value;
|
||||
}
|
||||
},
|
||||
itemStyle: { //此属性的颜色和下面areaStyle属性的颜色都设置成相同色即可实现
|
||||
color: "#66EFE8",
|
||||
borderColor: "#66EFE8"
|
||||
},
|
||||
areaStyle: {
|
||||
color: "rgba(102,239,232,0.26)"
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
// 配置项给实例对象
|
||||
myChart.setOption(option);
|
||||
return myChart;
|
||||
};
|
||||
|
||||
export default IncomeStatisticsRadar;
|
||||
|
|
@ -0,0 +1,100 @@
|
|||
.companyItemWrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.header {
|
||||
height: 38px;
|
||||
line-height: 38px;
|
||||
text-align: center;
|
||||
color: #FFF;
|
||||
font-size: 16px;
|
||||
background: #199DFF;
|
||||
}
|
||||
|
||||
.parent {
|
||||
background: #1E66F6 !important;
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.contBox {
|
||||
display: flex;
|
||||
padding: 10px;
|
||||
color: #B8B8B8;
|
||||
font-size: 12px;
|
||||
border: 1px solid rgba(25, 157, 255, 1);
|
||||
border-top: none;
|
||||
|
||||
& > div:first-child {
|
||||
border-right: 1px solid #e5e5e5;
|
||||
}
|
||||
|
||||
& > div {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
& > div:first-child {
|
||||
margin: 10px;
|
||||
}
|
||||
|
||||
& > div {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
|
||||
& > span:last-child {
|
||||
color: #787E95;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.regionItemWrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: #EBF5FF;
|
||||
min-width: 116px;
|
||||
|
||||
.header {
|
||||
height: 38px;
|
||||
line-height: 38px;
|
||||
text-align: center;
|
||||
color: #333333;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.contBox {
|
||||
display: flex;
|
||||
padding: 10px 0;
|
||||
color: #B8B8B8;
|
||||
font-size: 12px;
|
||||
|
||||
& > div:first-child {
|
||||
border-right: 1px solid #e5e5e5;
|
||||
}
|
||||
|
||||
& > div {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
& > div {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
|
||||
& > span:last-child {
|
||||
color: #787E95;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.regionItemWrapperChild {
|
||||
background: #EAFCFC;
|
||||
}
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
* Author: 黎永顺
|
||||
* name: 公司组件
|
||||
* Description:
|
||||
* Date: 2023/5/10
|
||||
*/
|
||||
import React, { FunctionComponent } from "react";
|
||||
import { OrgChartNodeDataType } from "@/components/reactChart/interface";
|
||||
import styles from "./index.less";
|
||||
import classNames from "classnames";
|
||||
|
||||
interface OwnProps {
|
||||
node: OrgChartNodeDataType;
|
||||
}
|
||||
|
||||
type Props = OwnProps;
|
||||
|
||||
const RegionItem: FunctionComponent<Props> = (props) => {
|
||||
const { node } = props;
|
||||
const classes = classNames(styles["regionItemWrapper"], {
|
||||
[styles["regionItemWrapperChild"]]: node.key === 20 || node.key === 21 || node.key === 22 || node.key === 23 || node.key === 24 || node.key === 25
|
||||
});
|
||||
return (
|
||||
<div className={classes}>
|
||||
<div className={styles.header}>{node?.label}</div>
|
||||
<div className={styles.contBox}>
|
||||
<div>
|
||||
<div>
|
||||
<span>营业收入</span>
|
||||
<span>{node?.operatingIncome}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span>同比</span>
|
||||
<span>{node?.operatingIncomePercent}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div>
|
||||
<span>利润总额</span>
|
||||
<span>{node?.operatingProfit}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span>同比</span>
|
||||
<span>{node?.operatingProfitPercent}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default RegionItem;
|
||||
|
|
@ -0,0 +1,133 @@
|
|||
const TotalCost = (echarts: any) => {
|
||||
// 实例化对象
|
||||
const myChart = echarts.init(document.getElementById("totalCost"));
|
||||
// 配置项
|
||||
const option = {
|
||||
tooltip: {
|
||||
// 坐标轴指示器,坐标轴触发有效
|
||||
trigger: "axis",
|
||||
axisPointer: {
|
||||
// 默认为直线,可选为:'line' | 'shadow'
|
||||
type: "shadow"
|
||||
}
|
||||
},
|
||||
legend: {
|
||||
icon: "circle",
|
||||
top: "5%",
|
||||
right: "2%",
|
||||
itemGap: 20,
|
||||
textStyle: {
|
||||
fontSize: 12,//字体大小
|
||||
color: "#787E95"//字体颜色
|
||||
}
|
||||
},
|
||||
grid: {
|
||||
top: "20%",
|
||||
left: "2%",
|
||||
right: "2%",
|
||||
bottom: "3%",
|
||||
containLabel: true
|
||||
},
|
||||
xAxis: [
|
||||
{
|
||||
type: "category",
|
||||
data: ["一月", "二月", "三月", "四月", "五月", "六月", "七月", "八月", "九月", "十月", "十一月", "十二月"],
|
||||
axisTick: {
|
||||
alignWithLabel: true,
|
||||
show: false
|
||||
},
|
||||
// 修改坐标值样式
|
||||
axisLabel: {
|
||||
color: "#B8B8B8",
|
||||
fontSize: 16,
|
||||
show: true
|
||||
},
|
||||
axisLine: {
|
||||
show: false
|
||||
}
|
||||
}
|
||||
],
|
||||
yAxis: [
|
||||
{
|
||||
type: "value",
|
||||
name: "万元",
|
||||
// 修改坐标值样式
|
||||
axisLabel: {
|
||||
color: "#787E95",
|
||||
fontSize: 14
|
||||
// formatter: '{value} 人'
|
||||
},
|
||||
nameTextStyle: {
|
||||
color: "#787E95",
|
||||
fontSize: 16
|
||||
},
|
||||
// 修改坐标轴线样式
|
||||
axisLine: {
|
||||
show: true,
|
||||
lineStyle: {
|
||||
color: "transparent"
|
||||
}
|
||||
},
|
||||
axisTick: {
|
||||
show: false // 不显示坐标轴刻度线
|
||||
},
|
||||
splitLine: {
|
||||
lineStyle: {
|
||||
type: "dashed",
|
||||
color: "rgba(93,126,158,1)"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
series: [
|
||||
{
|
||||
name: "人工成本",
|
||||
type: "bar",
|
||||
showBackground: false,
|
||||
barWidth: "20%",
|
||||
data: [200, 120, 230, 225, 165, 135, 165, 120, 210, 120, 85, 210],
|
||||
// bar 样式修改
|
||||
itemStyle: {
|
||||
color: "#4CAEFF"
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "人工成本",
|
||||
type: "line",
|
||||
yAxisIndex: 0,
|
||||
data: [190, 145, 250, 225, 165, 140, 170, 130, 215, 120, 85, 210],
|
||||
itemStyle: {
|
||||
normal: {
|
||||
color: "#64C3FF",
|
||||
lineStyle: {
|
||||
color: "#64C3FF"
|
||||
}
|
||||
}
|
||||
},
|
||||
areaStyle: {
|
||||
normal: {
|
||||
color: {//分隔区域的颜色
|
||||
x0: 0,
|
||||
y0: 0,
|
||||
x2: 0,
|
||||
y2: 1,
|
||||
colorStops: [{
|
||||
offset: 0,
|
||||
color: "#64C3FF" // 0% 处的颜色
|
||||
}, {
|
||||
offset: 1,
|
||||
color: "rgba(100,195,255,0)" // 100% 处的颜色;中间还可以设置50%、20%、70%时的颜色
|
||||
}],
|
||||
globalCoord: false // 缺省为 false,以确保上面的x,y,x2,y2表示的是百分比
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
// 配置项给实例对象
|
||||
myChart.setOption(option);
|
||||
return myChart;
|
||||
};
|
||||
|
||||
export default TotalCost;
|
||||
|
|
@ -0,0 +1,155 @@
|
|||
const TotalLaborCostPerCapita = (echarts: any) => {
|
||||
// 实例化对象
|
||||
const myChart = echarts.init(document.getElementById("totalLaborCostPerCapita"));
|
||||
// 配置项
|
||||
const option = {
|
||||
tooltip: {
|
||||
// 坐标轴指示器,坐标轴触发有效
|
||||
trigger: "axis",
|
||||
axisPointer: {
|
||||
// 默认为直线,可选为:'line' | 'shadow'
|
||||
type: "line"
|
||||
}
|
||||
},
|
||||
legend: {
|
||||
icon: "circle",
|
||||
top: "5%",
|
||||
right: "2%",
|
||||
itemGap: 20,
|
||||
textStyle: {
|
||||
fontSize: 12,//字体大小
|
||||
color: "#787E95"//字体颜色
|
||||
}
|
||||
},
|
||||
grid: {
|
||||
top: "20%",
|
||||
left: "2%",
|
||||
right: "2%",
|
||||
bottom: "3%",
|
||||
containLabel: true
|
||||
},
|
||||
xAxis: [
|
||||
{
|
||||
type: "category",
|
||||
boundaryGap: false,
|
||||
data: ["一月", "二月", "三月", "四月", "五月", "六月", "七月", "八月", "九月", "十月", "十一月", "十二月"],
|
||||
axisTick: {
|
||||
alignWithLabel: true,
|
||||
show: false
|
||||
},
|
||||
// 修改坐标值样式
|
||||
axisLabel: {
|
||||
color: "#B8B8B8",
|
||||
fontSize: 16,
|
||||
show: true
|
||||
},
|
||||
axisLine: {
|
||||
show: false
|
||||
}
|
||||
}
|
||||
],
|
||||
yAxis: [
|
||||
{
|
||||
type: "value",
|
||||
name: "万元",
|
||||
// 修改坐标值样式
|
||||
axisLabel: {
|
||||
color: "#787E95",
|
||||
fontSize: 14
|
||||
// formatter: '{value} 人'
|
||||
},
|
||||
nameTextStyle: {
|
||||
color: "#787E95",
|
||||
fontSize: 16
|
||||
},
|
||||
// 修改坐标轴线样式
|
||||
axisLine: {
|
||||
show: true,
|
||||
lineStyle: {
|
||||
color: "transparent"
|
||||
}
|
||||
},
|
||||
axisTick: {
|
||||
show: false // 不显示坐标轴刻度线
|
||||
},
|
||||
splitLine: {
|
||||
lineStyle: {
|
||||
type: "dashed",
|
||||
color: "rgba(93,126,158,1)"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
series: [
|
||||
{
|
||||
name: "人工福利",
|
||||
type: "line",
|
||||
yAxisIndex: 0,
|
||||
data: [190, 145, 250, 225, 165, 140, 170, 130, 215, 120, 85, 210],
|
||||
itemStyle: {
|
||||
normal: {
|
||||
color: "#64C3FF",
|
||||
lineStyle: {
|
||||
color: "#64C3FF"
|
||||
}
|
||||
}
|
||||
},
|
||||
areaStyle: {
|
||||
normal: {
|
||||
color: {//分隔区域的颜色
|
||||
x0: 0,
|
||||
y0: 0,
|
||||
x2: 0,
|
||||
y2: 1,
|
||||
colorStops: [{
|
||||
offset: 0,
|
||||
color: "#64C3FF" // 0% 处的颜色
|
||||
}, {
|
||||
offset: 1,
|
||||
color: "rgba(100,195,255,0)" // 100% 处的颜色;中间还可以设置50%、20%、70%时的颜色
|
||||
}],
|
||||
globalCoord: false // 缺省为 false,以确保上面的x,y,x2,y2表示的是百分比
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "人均人工成本",
|
||||
type: "line",
|
||||
yAxisIndex: 0,
|
||||
data: [149, 145, 220, 160, 130, 165, 145, 125, 175, 110, 120, 167],
|
||||
itemStyle: {
|
||||
normal: {
|
||||
color: "#66EFE8",
|
||||
lineStyle: {
|
||||
color: "#66EFE8"
|
||||
}
|
||||
}
|
||||
},
|
||||
areaStyle: {
|
||||
normal: {
|
||||
color: {//分隔区域的颜色
|
||||
x0: 0,
|
||||
y0: 0,
|
||||
x2: 0,
|
||||
y2: 1,
|
||||
colorStops: [{
|
||||
offset: 0,
|
||||
color: "#66EFE8" // 0% 处的颜色
|
||||
}, {
|
||||
offset: 1,
|
||||
color: "rgba(102,239,232,0)" // 100% 处的颜色;中间还可以设置50%、20%、70%时的颜色
|
||||
}],
|
||||
globalCoord: false // 缺省为 false,以确保上面的x,y,x2,y2表示的是百分比
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
// 配置项给实例对象
|
||||
myChart.setOption(option);
|
||||
return myChart;
|
||||
};
|
||||
|
||||
export default TotalLaborCostPerCapita;
|
||||
|
|
@ -1,114 +1,277 @@
|
|||
.profitWrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: #e5e5e5;
|
||||
padding: 8px;
|
||||
overflow: hidden;
|
||||
overflow: hidden auto;
|
||||
|
||||
.profitLeft {
|
||||
& > div:not(:last-child) {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.profitHeader {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 580px;
|
||||
overflow: hidden;
|
||||
|
||||
& > :not(:last-child) {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
& > div:first-child {
|
||||
& > div {
|
||||
height: 100% !important;
|
||||
}
|
||||
& > div:not(:last-child) {
|
||||
margin-right: 16px;
|
||||
}
|
||||
|
||||
& > div {
|
||||
flex: 1;
|
||||
min-height: 200px;
|
||||
background: #FFF;
|
||||
position: relative;
|
||||
padding: 40px 10px 8px;
|
||||
overflow: hidden;
|
||||
border-radius: 5px;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.title {
|
||||
display: inline-block;
|
||||
position: absolute;
|
||||
left: 1%;
|
||||
top: 2%;
|
||||
font-size: 16px;
|
||||
//数据看板
|
||||
.dataTableWrapper {
|
||||
table-layout: fixed;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
thead th {
|
||||
text-align: center;
|
||||
font-size: 14px;
|
||||
color: #333333;
|
||||
letter-spacing: 0;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
:global {
|
||||
.ant-tabs {
|
||||
height: 100%;
|
||||
line-height: normal;
|
||||
tbody {
|
||||
td {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.ant-tabs-content-holder {
|
||||
flex: 1;
|
||||
td.title {
|
||||
font-size: 14px;
|
||||
color: #9A9A9A;
|
||||
letter-spacing: 0;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.ant-tabs-content {
|
||||
height: 100%;
|
||||
td.col3 {
|
||||
img {
|
||||
width: 15px;
|
||||
margin-right: 8px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
|
||||
.ant-tabs-tabpane {
|
||||
overflow-y: auto;
|
||||
.dataVal {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
& > div {
|
||||
height: 100% !important;
|
||||
}
|
||||
}
|
||||
& > span:first-child {
|
||||
border-bottom: 1px solid #e5e5e5;
|
||||
line-height: 36px;
|
||||
|
||||
& > span:first-child {
|
||||
font-size: 22px;
|
||||
color: #0055F3;
|
||||
letter-spacing: 0;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
& > span:last-child {
|
||||
font-size: 12px;
|
||||
color: #D4D4D4;
|
||||
font-weight: 400;
|
||||
}
|
||||
}
|
||||
|
||||
& > span:last-child {
|
||||
margin-top: 8px;
|
||||
|
||||
& > span:first-child {
|
||||
font-size: 12px;
|
||||
color: #9A9A9A;
|
||||
letter-spacing: 0;
|
||||
font-weight: 400;
|
||||
margin-right: 18px;
|
||||
}
|
||||
|
||||
& > span:last-child {
|
||||
font-size: 12px;
|
||||
color: #FF4751;
|
||||
letter-spacing: 0;
|
||||
font-weight: 400;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
.incomeStatisticsRadar {
|
||||
min-height: 360px;
|
||||
max-height: 360px;
|
||||
}
|
||||
|
||||
// 团队分析排名
|
||||
.teamRankingWrapper, .personRankingWrapper {
|
||||
min-height: 360px;
|
||||
max-height: 360px;
|
||||
overflow-y: auto;
|
||||
|
||||
.rankDescBox {
|
||||
display: flex;
|
||||
font-size: 4px;
|
||||
align-items: center;
|
||||
|
||||
img {
|
||||
width: 30px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.rankNum {
|
||||
margin-right: 10px
|
||||
}
|
||||
|
||||
.rankNumber {
|
||||
width: 30px;
|
||||
margin-right: 10px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.rankDescPersBox {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
& > span:last-child {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-left: 8px;
|
||||
|
||||
& > span:first-child {
|
||||
font-size: 14px;
|
||||
color: #333333;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
& > span:last-child {
|
||||
font-size: 12px;
|
||||
color: #B8B8B8;
|
||||
font-weight: 400;
|
||||
min-width: 130px;
|
||||
max-width: 130px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.rankDescPersBox {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
& > span:last-child {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-left: 8px;
|
||||
|
||||
& > span:first-child {
|
||||
font-size: 14px;
|
||||
color: #333333;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
& > span:last-child {
|
||||
font-size: 12px;
|
||||
color: #B8B8B8;
|
||||
font-weight: 400;
|
||||
min-width: 130px;
|
||||
max-width: 130px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.rankRow {
|
||||
background: #FEFAED;
|
||||
}
|
||||
|
||||
:global {
|
||||
.ant-select {
|
||||
margin: 16px;
|
||||
}
|
||||
|
||||
.ant-list-item {
|
||||
padding: 6px 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.rankingTabWrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
line-height: 32px;
|
||||
padding: 16px;
|
||||
margin: 0;
|
||||
|
||||
& > li.active {
|
||||
color: #0055F3;
|
||||
font-weight: 700;
|
||||
border-bottom: 2px solid #0055F3;
|
||||
}
|
||||
|
||||
& > li:not(:last-child) {
|
||||
margin-right: 16px;
|
||||
}
|
||||
|
||||
& > li {
|
||||
font-size: 14px;
|
||||
color: #999999;
|
||||
letter-spacing: 0;
|
||||
font-weight: 400;
|
||||
cursor: pointer;
|
||||
border-bottom: 2px solid transparent;
|
||||
min-width: 95px;
|
||||
max-width: 95px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.profitRight {
|
||||
flex: 1;
|
||||
position: relative;
|
||||
margin-left: 16px;
|
||||
.profitCenter {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
|
||||
& > div:first-child {
|
||||
margin-bottom: 16px;
|
||||
margin-right: 16px;
|
||||
}
|
||||
|
||||
& > div {
|
||||
background: #fff;
|
||||
min-height: 200px;
|
||||
background: #FFF;
|
||||
border-radius: 5px;
|
||||
flex: 1;
|
||||
padding: 40px 10px 8px;
|
||||
position: relative;
|
||||
height: 100px;
|
||||
|
||||
:global {
|
||||
.ant-tabs {
|
||||
height: 100%;
|
||||
line-height: normal;
|
||||
|
||||
.ant-tabs-content-holder {
|
||||
flex: 1;
|
||||
|
||||
.ant-tabs-content {
|
||||
height: 100%;
|
||||
|
||||
.ant-tabs-tabpane {
|
||||
overflow-y: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.title {
|
||||
display: inline-block;
|
||||
position: absolute;
|
||||
left: 1%;
|
||||
top: 2%;
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
}
|
||||
}
|
||||
|
||||
.totalLaborCost {
|
||||
min-height: 326px;
|
||||
max-height: 326px;
|
||||
}
|
||||
}
|
||||
|
||||
.profitBottom {
|
||||
min-height: 800px;
|
||||
background: #FFF;
|
||||
border-radius: 5px;
|
||||
|
||||
//& > div {
|
||||
// height: 100%;
|
||||
//
|
||||
// & > div {
|
||||
// height: 100%;
|
||||
// }
|
||||
//}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,21 +4,28 @@
|
|||
* Description:
|
||||
* Date: 2023/4/27
|
||||
*/
|
||||
import React, { FC } from "react";
|
||||
import styles from "./index.less";
|
||||
import { Avatar, List, Tabs } from "antd";
|
||||
import React, { FC, useEffect, useState } from "react";
|
||||
// @ts-ignore
|
||||
import echarts from "echarts";
|
||||
import { Avatar, List, Select } from "antd";
|
||||
import FrameBox from "../components/frameBox";
|
||||
import IncomeStatisticsRadar from "./components/incomStaticsRadar";
|
||||
import TotalCostCharts from "./components/totalCostCharts";
|
||||
import TotalLaborCostPerCapita from "./components/totalLaborCostPerCapita";
|
||||
import OrgChart from "@/components/reactChart";
|
||||
import CompanyItem from "./components/companyItem";
|
||||
import RegionItem from "./components/regionItem";
|
||||
import {
|
||||
fixedIncomePerData,
|
||||
floatingIncomePerData,
|
||||
humanAnalysisconfig,
|
||||
humanEfficiencyRankingData,
|
||||
laborCostsPerCapitaConfig,
|
||||
newSaleEfficiencyRankingData,
|
||||
perCapitaIncomeConfig,
|
||||
saleEfficiencyRankingData,
|
||||
totalLaborCostConfig
|
||||
} from "@/pages/personnelReport/constants";
|
||||
import { Column, DualAxes, Pie } from "@ant-design/plots";
|
||||
humanRankingList,
|
||||
orgList,
|
||||
personRankingTablist,
|
||||
teamAnalysisRankingsOptions,
|
||||
teamRankingList,
|
||||
humanRankingList_1,
|
||||
humanRankingList_2
|
||||
} from "../constants";
|
||||
import cs from "classnames";
|
||||
import styles from "./index.less";
|
||||
|
||||
interface OwnProps {
|
||||
}
|
||||
|
|
@ -26,211 +33,212 @@ interface OwnProps {
|
|||
type Props = OwnProps;
|
||||
|
||||
const index: FC<Props> = (props) => {
|
||||
|
||||
const [active, setActive] = useState<number>(0);
|
||||
useEffect(() => {
|
||||
const incomeStatisticsRadar = IncomeStatisticsRadar(echarts);
|
||||
const totalCostCharts = TotalCostCharts(echarts);
|
||||
const totalLaborCostPerCapita = TotalLaborCostPerCapita(echarts);
|
||||
// 屏幕缩放对chart图表进行自适应处理,调用实例的resize方法
|
||||
window.onresize = () => {
|
||||
incomeStatisticsRadar.resize();
|
||||
totalCostCharts.resize();
|
||||
totalLaborCostPerCapita.resize();
|
||||
};
|
||||
return () => {
|
||||
};
|
||||
}, []);
|
||||
// @ts-ignore
|
||||
return (
|
||||
<div className={styles.profitWrapper}>
|
||||
<div className={styles.profitLeft}>
|
||||
<div className={styles.profitLeftItem1}>
|
||||
<span className={styles.title}>人效分析</span>
|
||||
{/*@ts-ignore*/}
|
||||
<Column {...humanAnalysisconfig} />
|
||||
<div className={styles.profitHeader}>
|
||||
<div>
|
||||
<FrameBox title="数据看板">
|
||||
<div style={{ padding: 16, minHeight: 360, maxHeight: 360 }}>
|
||||
<table className={styles.dataTableWrapper}>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>调查类型</th>
|
||||
<th>实际值</th>
|
||||
<th>较去年同期</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td className={styles.title}>销售收入投资回报</td>
|
||||
<td>
|
||||
<div className={styles.dataVal}>
|
||||
<span><span>3016</span><span>万元</span></span>
|
||||
<span><span>同期值</span><span>3457</span></span>
|
||||
</div>
|
||||
</td>
|
||||
<td className={styles.col3} style={{ color: "#FF4751" }}>
|
||||
<img src={require("../../../assets/images/up.png")} alt=""/>
|
||||
<span>4.75%</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className={styles.title}>人均净利润</td>
|
||||
<td>
|
||||
<div className={styles.dataVal}>
|
||||
<span><span>17456</span><span>元</span></span>
|
||||
<span><span>同期值</span><span>13567</span></span>
|
||||
</div>
|
||||
</td>
|
||||
<td className={styles.col3} style={{ color: "#FF4751" }}>
|
||||
<img src={require("../../../assets/images/up.png")} alt=""/>
|
||||
<span>4.75%</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className={styles.title}>人均销售额</td>
|
||||
<td>
|
||||
<div className={styles.dataVal}>
|
||||
<span><span>34.6</span><span>万元</span></span>
|
||||
<span><span>同期值</span><span>45.16</span></span>
|
||||
</div>
|
||||
</td>
|
||||
<td className={styles.col3} style={{ color: "#00BB7A" }}>
|
||||
<img src={require("../../../assets/images/down.png")} alt=""/>
|
||||
<span>4.75%</span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</FrameBox>
|
||||
</div>
|
||||
<div className={styles.profitLeftItem2}>
|
||||
<span className={styles.title}>人均收入</span>
|
||||
<Tabs defaultActiveKey="1">
|
||||
<Tabs.TabPane tab="人均固定收入" key="1">
|
||||
<Pie {...perCapitaIncomeConfig} data={fixedIncomePerData}/>
|
||||
</Tabs.TabPane>
|
||||
<Tabs.TabPane tab="人均浮动收入" key="2">
|
||||
<Pie {...perCapitaIncomeConfig} data={floatingIncomePerData}/>
|
||||
</Tabs.TabPane>
|
||||
</Tabs>
|
||||
<div>
|
||||
<FrameBox title="人员收入统计">
|
||||
<div className={styles.incomeStatisticsRadar} id="incomeStatisticsRadar"/>
|
||||
</FrameBox>
|
||||
</div>
|
||||
<div className={styles.profitLeftItem3}>
|
||||
<span className={styles.title}>人工成本</span>
|
||||
<Tabs defaultActiveKey="1">
|
||||
<Tabs.TabPane tab="人工总成本" key="1">
|
||||
{/*@ts-ignore*/}
|
||||
<DualAxes {...totalLaborCostConfig} />
|
||||
</Tabs.TabPane>
|
||||
<Tabs.TabPane tab="人均人工成本" key="2">
|
||||
<DualAxes {...laborCostsPerCapitaConfig} />
|
||||
</Tabs.TabPane>
|
||||
</Tabs>
|
||||
<div>
|
||||
<FrameBox title="团队分析排名">
|
||||
<div className={styles.teamRankingWrapper}>
|
||||
<Select defaultValue="1"
|
||||
options={teamAnalysisRankingsOptions}
|
||||
/>
|
||||
<List
|
||||
dataSource={teamRankingList}
|
||||
renderItem={(item, index) => (
|
||||
<List.Item key={index}
|
||||
className={cs({ [styles["rankRow"]]: index === 0 || index === 1 || index === 2 })}>
|
||||
<List.Item.Meta
|
||||
title={
|
||||
<div
|
||||
className={styles.rankDescBox}>
|
||||
{
|
||||
(index === 0 || index === 1 || index === 2) ?
|
||||
<img src={index === 0 ?
|
||||
require("../../../assets/images/one.png") : index === 1 ?
|
||||
require("../../../assets/images/two.png") :
|
||||
require("../../../assets/images/three.png")} alt=""/> :
|
||||
<span className={styles.rankNumber}>{index + 1}</span>
|
||||
}
|
||||
<div className={styles.rankDescPersBox}>
|
||||
{
|
||||
(index === 0 || index === 1 || index === 2) &&
|
||||
<Avatar src={item.avatar}/>
|
||||
}
|
||||
<span><span>{item.name}</span><span>{item.dept}</span></span>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
<div style={{
|
||||
fontWeight: (index === 0 || index === 1 || index === 2) ? 500 : 400,
|
||||
fontSize: (index === 0 || index === 1 || index === 2) ? 16 : 14,
|
||||
color: "#333333"
|
||||
}}>{item.score}</div>
|
||||
</List.Item>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</FrameBox>
|
||||
</div>
|
||||
<div>
|
||||
<FrameBox title="人效分析排名">
|
||||
<div className={styles.personRankingWrapper}>
|
||||
<ul className={styles.rankingTabWrapper}>
|
||||
{
|
||||
_.map(personRankingTablist, (item: any, index: number) => {
|
||||
const classes = cs({
|
||||
[styles["active"]]: index === active
|
||||
});
|
||||
return <li key={index} className={classes} onClick={() => setActive(index)}
|
||||
title={item}>{item}</li>;
|
||||
})
|
||||
}
|
||||
</ul>
|
||||
<List
|
||||
dataSource={active === 0 ? humanRankingList : active === 1 ? humanRankingList_1 : humanRankingList_2}
|
||||
renderItem={(item, index) => (
|
||||
<List.Item key={index}
|
||||
className={cs({ [styles["rankRow"]]: index === 0 || index === 1 || index === 2 })}>
|
||||
<List.Item.Meta
|
||||
title={
|
||||
<div className={styles.rankDescBox}>
|
||||
{
|
||||
(index === 0 || index === 1 || index === 2) ?
|
||||
<img src={index === 0 ?
|
||||
require("../../../assets/images/one.png") : index === 1 ?
|
||||
require("../../../assets/images/two.png") :
|
||||
require("../../../assets/images/three.png")} alt=""/> :
|
||||
<span className={styles.rankNumber}>{index + 1}</span>
|
||||
}
|
||||
<div className={styles.rankDescPersBox}>
|
||||
{
|
||||
(index === 0 || index === 1 || index === 2) &&
|
||||
<Avatar src={item.avatar}/>
|
||||
}
|
||||
<span><span>{item.name}</span><span>{item.dept}</span></span>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
<div style={{
|
||||
fontWeight: (index === 0 || index === 1 || index === 2) ? 500 : 400,
|
||||
fontSize: (index === 0 || index === 1 || index === 2) ? 16 : 14,
|
||||
color: "#333333"
|
||||
}}>{item.score}</div>
|
||||
</List.Item>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</FrameBox>
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.profitRight}>
|
||||
<div className={styles.profitCenter}>
|
||||
<div>
|
||||
<span className={styles.title}>人效分析排名</span>
|
||||
<Tabs defaultActiveKey="1">
|
||||
<Tabs.TabPane tab="有效金额排名" key="1">
|
||||
<List
|
||||
itemLayout="horizontal"
|
||||
dataSource={humanEfficiencyRankingData}
|
||||
renderItem={item => (
|
||||
<List.Item actions={[<a key="list-loadmore-more">more</a>]}>
|
||||
<List.Item.Meta
|
||||
avatar={<Avatar src="https://joeschmoe.io/api/v1/random"/>}
|
||||
title={<a href="https://ant.design">{item.title}</a>}
|
||||
description={item.description}
|
||||
/>
|
||||
<div>{item.rank}</div>
|
||||
</List.Item>
|
||||
)}
|
||||
/>
|
||||
</Tabs.TabPane>
|
||||
<Tabs.TabPane tab="合同数量排名" key="2">
|
||||
<List
|
||||
itemLayout="horizontal"
|
||||
dataSource={saleEfficiencyRankingData}
|
||||
renderItem={item => (
|
||||
<List.Item actions={[<a key="list-loadmore-more">more</a>]}>
|
||||
<List.Item.Meta
|
||||
avatar={<Avatar src="https://joeschmoe.io/api/v1/random"/>}
|
||||
title={<a href="https://ant.design">{item.title}</a>}
|
||||
description={item.description}
|
||||
/>
|
||||
<div>{item.rank}</div>
|
||||
</List.Item>
|
||||
)}
|
||||
/>
|
||||
</Tabs.TabPane>
|
||||
<Tabs.TabPane tab="有效合同排名" key="3">
|
||||
<List
|
||||
itemLayout="horizontal"
|
||||
dataSource={newSaleEfficiencyRankingData}
|
||||
renderItem={item => (
|
||||
<List.Item actions={[<a key="list-loadmore-more">more</a>]}>
|
||||
<List.Item.Meta
|
||||
avatar={<Avatar src="https://joeschmoe.io/api/v1/random"/>}
|
||||
title={<a href="https://ant.design">{item.title}</a>}
|
||||
description={item.description}
|
||||
/>
|
||||
<div>{item.rank}</div>
|
||||
</List.Item>
|
||||
)}
|
||||
/>
|
||||
</Tabs.TabPane>
|
||||
</Tabs>
|
||||
<FrameBox title="人工总成本">
|
||||
<div className={styles.totalLaborCost} id="totalCost"/>
|
||||
</FrameBox>
|
||||
</div>
|
||||
<div>
|
||||
<span className={styles.title}>团队效能排名</span>
|
||||
<Tabs defaultActiveKey="1">
|
||||
<Tabs.TabPane tab="指标<400万" key="1">
|
||||
<List
|
||||
itemLayout="horizontal"
|
||||
dataSource={humanEfficiencyRankingData}
|
||||
renderItem={item => (
|
||||
<List.Item actions={[<a key="list-loadmore-more">more</a>]}>
|
||||
<List.Item.Meta
|
||||
avatar={<Avatar src="https://joeschmoe.io/api/v1/random"/>}
|
||||
title={<a href="https://ant.design">{item.title}</a>}
|
||||
description={item.description}
|
||||
/>
|
||||
<div>{item.rank}</div>
|
||||
</List.Item>
|
||||
)}
|
||||
/>
|
||||
</Tabs.TabPane>
|
||||
<Tabs.TabPane tab="400万≤指标<600万" key="2">
|
||||
<List
|
||||
itemLayout="horizontal"
|
||||
dataSource={humanEfficiencyRankingData}
|
||||
renderItem={item => (
|
||||
<List.Item actions={[<a key="list-loadmore-more">more</a>]}>
|
||||
<List.Item.Meta
|
||||
avatar={<Avatar src="https://joeschmoe.io/api/v1/random"/>}
|
||||
title={<a href="https://ant.design">{item.title}</a>}
|
||||
description={item.description}
|
||||
/>
|
||||
<div>{item.rank}</div>
|
||||
</List.Item>
|
||||
)}
|
||||
/>
|
||||
</Tabs.TabPane>
|
||||
<Tabs.TabPane tab="600万≤指标<800万" key="3">
|
||||
<List
|
||||
itemLayout="horizontal"
|
||||
dataSource={humanEfficiencyRankingData}
|
||||
renderItem={item => (
|
||||
<List.Item actions={[<a key="list-loadmore-more">more</a>]}>
|
||||
<List.Item.Meta
|
||||
avatar={<Avatar src="https://joeschmoe.io/api/v1/random"/>}
|
||||
title={<a href="https://ant.design">{item.title}</a>}
|
||||
description={item.description}
|
||||
/>
|
||||
<div>{item.rank}</div>
|
||||
</List.Item>
|
||||
)}
|
||||
/>
|
||||
</Tabs.TabPane>
|
||||
<Tabs.TabPane tab="800万≤指标<1200万" key="4">
|
||||
<List
|
||||
itemLayout="horizontal"
|
||||
dataSource={humanEfficiencyRankingData}
|
||||
renderItem={item => (
|
||||
<List.Item actions={[<a key="list-loadmore-more">more</a>]}>
|
||||
<List.Item.Meta
|
||||
avatar={<Avatar src="https://joeschmoe.io/api/v1/random"/>}
|
||||
title={<a href="https://ant.design">{item.title}</a>}
|
||||
description={item.description}
|
||||
/>
|
||||
<div>{item.rank}</div>
|
||||
</List.Item>
|
||||
)}
|
||||
/>
|
||||
</Tabs.TabPane>
|
||||
<Tabs.TabPane tab="1200万≤指标<1600万" key="5">
|
||||
<List
|
||||
itemLayout="horizontal"
|
||||
dataSource={humanEfficiencyRankingData}
|
||||
renderItem={item => (
|
||||
<List.Item actions={[<a key="list-loadmore-more">more</a>]}>
|
||||
<List.Item.Meta
|
||||
avatar={<Avatar src="https://joeschmoe.io/api/v1/random"/>}
|
||||
title={<a href="https://ant.design">{item.title}</a>}
|
||||
description={item.description}
|
||||
/>
|
||||
<div>{item.rank}</div>
|
||||
</List.Item>
|
||||
)}
|
||||
/>
|
||||
</Tabs.TabPane>
|
||||
<Tabs.TabPane tab="1600万≤指标<3000万" key="6">
|
||||
<List
|
||||
itemLayout="horizontal"
|
||||
dataSource={humanEfficiencyRankingData}
|
||||
renderItem={item => (
|
||||
<List.Item actions={[<a key="list-loadmore-more">more</a>]}>
|
||||
<List.Item.Meta
|
||||
avatar={<Avatar src="https://joeschmoe.io/api/v1/random"/>}
|
||||
title={<a href="https://ant.design">{item.title}</a>}
|
||||
description={item.description}
|
||||
/>
|
||||
<div>{item.rank}</div>
|
||||
</List.Item>
|
||||
)}
|
||||
/>
|
||||
</Tabs.TabPane>
|
||||
<Tabs.TabPane tab="指标>=3000万" key="7">
|
||||
<List
|
||||
itemLayout="horizontal"
|
||||
dataSource={humanEfficiencyRankingData}
|
||||
renderItem={item => (
|
||||
<List.Item actions={[<a key="list-loadmore-more">more</a>]}>
|
||||
<List.Item.Meta
|
||||
avatar={<Avatar src="https://joeschmoe.io/api/v1/random"/>}
|
||||
title={<a href="https://ant.design">{item.title}</a>}
|
||||
description={item.description}
|
||||
/>
|
||||
<div>{item.rank}</div>
|
||||
</List.Item>
|
||||
)}
|
||||
/>
|
||||
</Tabs.TabPane>
|
||||
</Tabs>
|
||||
<FrameBox title="人均人工总成本">
|
||||
<div className={styles.totalLaborCost} id="totalLaborCostPerCapita"/>
|
||||
</FrameBox>
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.profitBottom}>
|
||||
<FrameBox title="人工利润架构图">
|
||||
{/*@ts-ignore*/}
|
||||
{/*<FlowAnalysisGraph {...laborProfitDataConfig}/>*/}
|
||||
<OrgChart
|
||||
pan
|
||||
// zoom
|
||||
// maxZoom={2}
|
||||
// minZoom={0.5}
|
||||
// zoomStep={0.02}
|
||||
data={orgList} expandable
|
||||
renderNode={(node) => {
|
||||
const { key } = node;
|
||||
return (key === 0 || key === 1 || key === 2 || key === 3 || key === 4 || key === 5) ?
|
||||
<CompanyItem node={node}/> : <RegionItem node={node}/>;
|
||||
}}
|
||||
/>
|
||||
</FrameBox>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -8,12 +8,13 @@ const EducationStaticsRedar = (echarts: any) => {
|
|||
},
|
||||
radar: {
|
||||
// shape: 'circle',
|
||||
// , axisLabel: { show: true }
|
||||
indicator: [
|
||||
{ name: "博士及以上", max: 800, axisLabel: { show: true } },
|
||||
{ name: "大专以下", max: 800 },
|
||||
{ name: "博士及以上", max: 800 },
|
||||
{ name: "硕士", max: 800 },
|
||||
{ name: "本科", max: 800 },
|
||||
{ name: "大专", max: 800 },
|
||||
{ name: "大专一下", max: 800 }
|
||||
{ name: "大专", max: 800 }
|
||||
],
|
||||
splitArea: {
|
||||
show: false
|
||||
|
|
@ -28,7 +29,7 @@ const EducationStaticsRedar = (echarts: any) => {
|
|||
type: "radar",
|
||||
data: [
|
||||
{
|
||||
value: [210, 400, 800, 230, 180],
|
||||
value: [100, 210, 400, 720, 230],
|
||||
name: "学历统计"
|
||||
}
|
||||
],
|
||||
|
|
|
|||
|
|
@ -3,6 +3,10 @@ const GenderStaticsPie = (echarts: any) => {
|
|||
const myChart = echarts.init(document.getElementById("genderStatics"));
|
||||
// 配置项
|
||||
const option = {
|
||||
tooltip: {
|
||||
show: true,
|
||||
formatter: "{a} <br/>{b}: {c} ({d}%)"
|
||||
},
|
||||
legend: {
|
||||
icon: "circle",
|
||||
bottom: "0%",
|
||||
|
|
@ -22,11 +26,19 @@ const GenderStaticsPie = (echarts: any) => {
|
|||
{
|
||||
name: "性别统计",
|
||||
type: "pie",
|
||||
startAngle:-180,
|
||||
startAngle: -180,
|
||||
radius: [40, 70],
|
||||
roseType: "radius",
|
||||
avoidLabelOverlap: false,
|
||||
animation: false,
|
||||
label: {
|
||||
normal: {
|
||||
formatter: "{d}%",
|
||||
padding: [0, -25],
|
||||
position: "outer" //文字显示在内部,如果在外边把这个去掉就好
|
||||
}
|
||||
},
|
||||
labelLine: {
|
||||
show: false
|
||||
},
|
||||
data: [
|
||||
|
|
|
|||
|
|
@ -60,7 +60,25 @@
|
|||
}
|
||||
|
||||
.charts {
|
||||
width: 100%;
|
||||
height: 62px;
|
||||
|
||||
:global {
|
||||
div {
|
||||
width: 100% !important;
|
||||
height: 100% !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.vCenter {
|
||||
position: relative;
|
||||
min-height: 62px;
|
||||
|
||||
& > div {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -72,11 +90,17 @@
|
|||
padding-top: 10px;
|
||||
|
||||
& > span {
|
||||
img {
|
||||
width: 8px;
|
||||
margin-left: 8px;
|
||||
margin-right: 4px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
& > span:first-child {
|
||||
font-size: 12px;
|
||||
color: #D4D4D4;
|
||||
font-weight: 400;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
& > span:last-child {
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ const InternStaticsBar = (echarts: any) => {
|
|||
xAxis: [
|
||||
{
|
||||
type: "value",
|
||||
name: "人",
|
||||
axisLabel: {
|
||||
formatter: "{value} "
|
||||
},
|
||||
|
|
@ -31,7 +32,7 @@ const InternStaticsBar = (echarts: any) => {
|
|||
grid: {
|
||||
top: "10%",
|
||||
left: "5%",
|
||||
right: "5%",
|
||||
right: "10%",
|
||||
bottom: "3%",
|
||||
containLabel: true
|
||||
},
|
||||
|
|
@ -39,7 +40,8 @@ const InternStaticsBar = (echarts: any) => {
|
|||
{
|
||||
type: "category",
|
||||
offset: 0,
|
||||
data: ["项目实习生", "销售实习生", "技术实习生", "大区实习生", "客服实习生"],
|
||||
inverse:true,
|
||||
data: ["项目实\n\n" + "习生", "销售实\n\n" + "习生", "技术实\n\n" + "习生", "支撑实\n\n" + "习生", "客服实\n\n" + "习生"],
|
||||
axisPointer: {
|
||||
type: "shadow"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -34,10 +34,10 @@ const LineStaticsPie = (echarts: any) => {
|
|||
},
|
||||
data: [
|
||||
{ value: 40, name: "项目" },
|
||||
{ value: 15, name: "市场" },
|
||||
{ value: 20, name: "销售" },
|
||||
{ value: 15, name: "支撑" },
|
||||
{ value: 10, name: "销售1" }
|
||||
{ value: 25, name: "技术" },
|
||||
{ value: 10, name: "支撑" },
|
||||
{ value: 5, name: "客服" }
|
||||
]
|
||||
}
|
||||
]
|
||||
|
|
|
|||
|
|
@ -76,7 +76,7 @@ const MonthlyTrendsBar = (echarts: any) => {
|
|||
showBackground: false,
|
||||
barWidth: "8%",
|
||||
barGap: "100%",
|
||||
data: [250, 250, 320, 410, 280, 320, 405, 405, 320, 250, 280, 405],
|
||||
data: [90, 70, 120, 60, 130, 80, 60, 125, 80, 60, 120, 40],
|
||||
// bar 样式修改
|
||||
itemStyle: {
|
||||
barBorderRadius: 5,
|
||||
|
|
@ -88,7 +88,7 @@ const MonthlyTrendsBar = (echarts: any) => {
|
|||
type: "bar",
|
||||
showBackground: false,
|
||||
barWidth: "8%",
|
||||
data: [370, 370, 260, 110, 390, 280, 110, 110, 370, 260, 390, 110],
|
||||
data: [30, 50, 55, 25, 48, 30, 30, 75, 35, 20, 28, 10],
|
||||
// bar 样式修改
|
||||
itemStyle: {
|
||||
barBorderRadius: 5,
|
||||
|
|
@ -97,21 +97,9 @@ const MonthlyTrendsBar = (echarts: any) => {
|
|||
},
|
||||
{
|
||||
name: "在职",
|
||||
type: "bar",
|
||||
showBackground: false,
|
||||
barWidth: "8%",
|
||||
data: [310, 310, 80, 280, 310, 70, 280, 280, 80, 280, 310, 280],
|
||||
// bar 样式修改
|
||||
itemStyle: {
|
||||
barBorderRadius: 5,
|
||||
color: "#FFD600"
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "离职",
|
||||
type: "line",
|
||||
yAxisIndex: 0,
|
||||
data: [370, 370, 260, 110, 390, 280, 110, 110, 370, 260, 390, 110],
|
||||
data: [90, 70, 120, 60, 130, 110, 60, 125, 80, 60, 120, 40],
|
||||
itemStyle: {
|
||||
normal: {
|
||||
color: "#FFD700",
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
* Date: 2023/5/5
|
||||
*/
|
||||
import React, { FC } from "react";
|
||||
import { TinyArea } from "@ant-design/plots";
|
||||
import { TinyArea, TinyColumn } from "@ant-design/plots";
|
||||
import styles from "./index.less";
|
||||
import cs from "classnames";
|
||||
|
||||
|
|
@ -15,7 +15,13 @@ interface OwnProps {
|
|||
number: string;
|
||||
unit: string;
|
||||
color: string;
|
||||
yoyVal?: string;
|
||||
chainVal?: string;
|
||||
yoyIcon?: any;
|
||||
chainIcon?: any;
|
||||
idx?: number;
|
||||
tabList: Array<any>;
|
||||
onTabChange?: any;
|
||||
}
|
||||
|
||||
type Props = OwnProps;
|
||||
|
|
@ -32,8 +38,22 @@ const config = {
|
|||
showContent: false
|
||||
}
|
||||
};
|
||||
const miniBarConfig = {
|
||||
height: 60,
|
||||
autoFit: false,
|
||||
data: [274, 337, 81, 497, 666, 219, 269],
|
||||
columnWidthRatio: 0.3,
|
||||
tooltip: {
|
||||
showCrosshairs: false,
|
||||
showContent: false
|
||||
}
|
||||
};
|
||||
|
||||
const StructureCard: FC<Props> = (props) => {
|
||||
const { active, tabList, title, number, unit, color } = props;
|
||||
const { active, tabList, title, number, unit, color, yoyIcon, chainIcon, yoyVal, chainVal, idx, onTabChange } = props;
|
||||
const classes = cs(styles["center"], {
|
||||
[styles["vCenter"]]: (idx === 3 || idx === 4 || idx === 5)
|
||||
});
|
||||
|
||||
return (
|
||||
<div className={styles.structureCardWrapper}>
|
||||
|
|
@ -41,25 +61,32 @@ const StructureCard: FC<Props> = (props) => {
|
|||
<span>{title}</span>
|
||||
<ul>
|
||||
{
|
||||
_.map(tabList, (it, idx) => {
|
||||
_.map(tabList, (it, index) => {
|
||||
const classes = cs({
|
||||
[styles["active"]]: idx === active
|
||||
[styles["active"]]: index === active
|
||||
});
|
||||
return <li key={it} className={classes}>{it}</li>;
|
||||
return <li key={it} className={classes} onClick={() => onTabChange(idx, index)}>{it}</li>;
|
||||
})
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
<div className={styles.center}>
|
||||
<div className={classes}>
|
||||
<div><span style={{ color }}>{number}</span><span>{unit}</span></div>
|
||||
<div className={styles.charts}><TinyArea {..._.assign(config, {
|
||||
areaStyle: { fill: `l(270) 0:#fff 0.5:${color} 1:${color}` },
|
||||
line: { color }
|
||||
})} /></div>
|
||||
<div className={styles.charts}>
|
||||
{
|
||||
idx === 5 ? <TinyColumn {..._.assign(miniBarConfig, {
|
||||
columnStyle: { fill: color }
|
||||
})} /> :
|
||||
<TinyArea {..._.assign(config, {
|
||||
areaStyle: { fill: `l(270) 0:#fff 0.5:${color} 1:${color}` },
|
||||
line: { color }
|
||||
})} />
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.bottom}>
|
||||
<span><span>同比:</span><span>4.75%</span></span>
|
||||
<span><span>环比:</span><span>1.75%</span></span>
|
||||
<span><span>同比:</span>{yoyIcon && <img src={yoyIcon} alt=""/>}<span>{yoyVal}</span></span>
|
||||
<span><span>环比:</span>{chainIcon && <img src={chainIcon} alt=""/>}<span>{chainVal}</span></span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@
|
|||
border-radius: 5px;
|
||||
background: #FFF;
|
||||
max-height: 160px;
|
||||
min-height: 100px;
|
||||
min-height: 160px;
|
||||
padding: 16px;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,8 +7,6 @@
|
|||
import React, { FC, useEffect, useState } from "react";
|
||||
// @ts-ignore
|
||||
import echarts from "echarts";
|
||||
// @ts-ignore
|
||||
import { CapsuleChart } from "@jiaminghi/data-view-react";
|
||||
import StructureCard from "./components/structureCard";
|
||||
import StructureFrame from "./components/structureFrame";
|
||||
import MonthlyTrendsBar from "./components/monthlyTrendsCharts";
|
||||
|
|
@ -50,12 +48,22 @@ const index: FC<Props> = (props) => {
|
|||
};
|
||||
}, []);
|
||||
|
||||
const handleChangeTab = (itemIdx: number, val: number) => {
|
||||
setCard(_.map(card, (item, idex: number) => {
|
||||
if (idex === itemIdx) {
|
||||
return { ...item, active: val };
|
||||
}
|
||||
return { ...item };
|
||||
}
|
||||
));
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={styles.structureWrapper}>
|
||||
<ul className={styles.structureColumn_1}>
|
||||
{
|
||||
_.map(card, (it, idx) => {
|
||||
return <li key={idx}><StructureCard {...it}/></li>;
|
||||
return <li key={idx}><StructureCard {...it} idx={idx} onTabChange={handleChangeTab}/></li>;
|
||||
})
|
||||
}
|
||||
</ul>
|
||||
|
|
|
|||