泛微薪资核算iframe表格

custom-人事报表/V2-demo
黎永顺 2 years ago
parent 8ec05fab81
commit c31ec3957a

@ -23,11 +23,14 @@
"@ant-design/charts": "^1.4.2",
"@ant-design/pro-layout": "6.32.1",
"@formily/antd": "^2.0.6",
"@jiaminghi/data-view-react": "^1.2.5",
"@types/lodash": "^4.14.172",
"@ztree/ztree_v3": "^3.5.42",
"ahooks": "^3.1.3",
"antd": "^4.17.3",
"axios": "^0.22.0",
"echarts": "^4.9.0",
"echarts-gl": "^1.1.2",
"fbemitter": "^3.0.0",
"js-base64": "^3.6.1",
"js-cookie": "^2.2.1",
@ -58,7 +61,6 @@
"@types/react": "^17.0.2",
"@types/react-dom": "^17.0.2",
"@umijs/plugin-access": "2.4.2",
"umi-plugin-authorize": "^2.8.12",
"@umijs/plugin-dva": "^0.13.0",
"@umijs/plugin-initial-state": "^2.4.0",
"@umijs/plugin-locale": "^0.15.0",
@ -71,6 +73,7 @@
"prettier": "^2.2.0",
"typescript": "^4.3.5",
"umi": "^3.5.20",
"umi-plugin-authorize": "^2.8.12",
"yorkie": "^2.0.0"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 832 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 108 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

@ -15,6 +15,7 @@ import { layoutConfig } from "@/layouts/config";
import stores from "@/store";
import "moment/locale/zh-cn";
import "antd/dist/antd.variable.min.css";
import "../utils/flexible";
moment.locale("zh-cn");

@ -2,6 +2,58 @@ import { G2, measureTextWidth } from "@ant-design/plots";
const G = G2.getEngine("canvas");
export const structureCardList = [
{
title: "当期总人数",
active: 0,
number: 489,
unit: "人",
color: "#FFBB31",
tabList: ["本月", "上月"]
},
{
title: "本月入职",
active: 0,
number: 100,
unit: "人",
color: "#3DA5F6",
tabList: ["本月", "上月"]
},
{
title: "本月离职",
active: 0,
number: 23,
unit: "人",
color: "#5FD5C7",
tabList: ["本月", "上月"]
},
{
title: "年度累计入职",
active: 0,
number: 238,
unit: "人",
color: "#2B6DF6",
tabList: ["本年", "去年"]
},
{
title: "年度累计离职",
active: 0,
number: 198,
unit: "人",
color: "#B571EA",
tabList: ["本年", "去年"]
},
{
title: "平均年龄",
active: 0,
number: 28.0,
unit: "岁",
color: "#5FCC7B",
tabList: ["本年", "去年"]
}
];
//人员流动数据
export const flowingData = [
{
@ -44,14 +96,10 @@ export const flowingConfig = {
offset: "-2%",
content: "{name}",
style: {
fontSize: 10
fontSize: 15
}
},
interactions: [
{
type: "element-active"
}
],
interactions: [],
pieStyle: {
lineWidth: 0
}
@ -179,11 +227,7 @@ export const seniorityConfig = {
textAlign: "center"
}
},
interactions: [
{
type: "element-active"
}
]
interactions: []
};
//人员流动情况趋势图数据信息
@ -279,8 +323,8 @@ export const institutionConfig = {
data: institutionData,
xField: "stage",
yField: "number",
dynamicHeight: true,
legend: false
dynamicHeight: true
// legend: false
};
//人员异动分析数数据
@ -300,7 +344,7 @@ export const personChangeData = [
];
export const personChangeConfig = {
appendPadding: 10,
data:[],
data: [],
angleField: "value",
colorField: "type",
radius: 0.75,
@ -309,104 +353,46 @@ export const personChangeConfig = {
labelHeight: 28,
content: "{name}\n{percentage}"
},
interactions: [
{
type: "element-active"
}
]
interactions: []
};
//详细图表柱状图
export const multiplData = [
{
"name": "全员离职率",
"年份": "2022",
"年份流动信息": 18.9
},
{
"name": "全员离职率",
"年份": "2023",
"年份流动信息": 28.8
},
{
"name": "主动离职率",
"年份": "2022",
"年份流动信息": 12.4
},
{
"name": "主动离职率",
"年份": "2023",
"年份流动信息": 23.2
},
{
"name": "被动离职率",
"年份": "2022",
"年份流动信息": 12.4
},
{
"name": "被动离职率",
"年份": "2023",
"年份流动信息": 23.2
},
{
"name": "关键岗位离职率",
"年份": "2022",
"年份流动信息": 12.4
},
{
"name": "关键岗位离职率",
"年份": "2023",
"年份流动信息": 23.2
},
{
"name": "绩优员工离职率",
"年份": "2022",
"年份流动信息": 12.4
},
{
"name": "绩优员工离职率",
"年份": "2023",
"年份流动信息": 23.2
},
{
"name": "转正率",
"年份": "2022",
"年份流动信息": 12.4
},
{
"name": "转正率",
"年份": "2023",
"年份流动信息": 23.2
},
{ "city": "12", "type": "今年", "value": 18000 },
{ "city": "12", "type": "去年", "value": 11000 },
{ "city": "11", "type": "今年", "value": 18000 },
{ "city": "11", "type": "去年", "value": 11000 },
{ "city": "10", "type": "今年", "value": 18000 },
{ "city": "10", "type": "去年", "value": 11000 },
{ "city": "09", "type": "今年", "value": 18000 },
{ "city": "09", "type": "去年", "value": 11000 },
{ "city": "08", "type": "今年", "value": 18000 },
{ "city": "08", "type": "去年", "value": 11000 },
{ "city": "07", "type": "今年", "value": 17000 },
{ "city": "07", "type": "去年", "value": 6000 },
{ "city": "06", "type": "今年", "value": 9000 },
{ "city": "06", "type": "去年", "value": 8500 },
{ "city": "05", "type": "今年", "value": 14000 },
{ "city": "05", "type": "去年", "value": 9000 },
{ "city": "04", "type": "今年", "value": 14000 },
{ "city": "04", "type": "去年", "value": 9000 },
{ "city": "03", "type": "今年", "value": 16000 },
{ "city": "03", "type": "去年", "value": 5000 },
{ "city": "02", "type": "今年", "value": 9000 },
{ "city": "02", "type": "去年", "value": 8500 },
{ "city": "01", "type": "今年", "value": 14500 },
{ "city": "01", "type": "去年", "value": 8500 }
];
//人员流动情况趋势图配置信息
export const multipleConfig = {
data: [],
xField: "city",
yField: "value",
seriesField: "type",
isGroup: true,
xField: "年份",
yField: "年份流动信息",
seriesField: "name",
// 分组柱状图 组内柱子间的间距 (像素级别)
dodgePadding: 2,
// 分组柱状图 组间的间距 (像素级别)
intervalPadding: 50,
label: {
// 可手动配置 label 数据标签位置
position: "middle",
// 'top', 'middle', 'bottom'
// 可配置附加的布局方法
layout: [
// 柱形图数据标签位置自动调整
{
type: "interval-adjust-position"
}, // 数据标签防遮挡
{
type: "interval-hide-overlap"
}, // 数据标签文颜色自动调整
{
type: "adjust-color"
}
]
columnStyle: {
radius: [20, 20, 0, 0]
}
};
@ -423,104 +409,69 @@ export const trainingData = [
];
//培训排名
export const rankingData =[
{
name: '机构一',
month: 'Jan.',
grades: 18.9,
},
{
name: '机构一',
month: 'Feb.',
grades: 28.8,
},
{
name: '机构一',
month: 'Mar.',
grades: 39.3,
},
export const rankingData = [
{
name: '机构一',
month: 'Apr.',
grades: 81.4,
label: "财务部",
type: "前五",
value: 2800
},
{
name: '机构一',
month: 'May',
grades: 47,
label: "销售部",
type: "前五",
value: 1800
},
{
name: '机构一',
month: 'Jun.',
grades: 20.3,
label: "策划部",
type: "前五",
value: 950
},
{
name: '机构一',
month: 'Jul.',
grades: 24,
label: "运营部",
type: "前五",
value: 500
},
{
name: '机构一',
month: 'Aug.',
grades: 35.6,
label: "后勤部",
type: "前五",
value: 170
},
{
name: '机构二',
month: 'Jan.',
grades: 12.4,
label: "财务部1",
type: "后五",
value: 2260
},
{
name: '机构二',
month: 'Feb.',
grades: 23.2,
label: "销售部1",
type: "后五",
value: 1300
},
{
name: '机构二',
month: 'Mar.',
grades: 34.5,
label: "策划部1",
type: "后五",
value: 900
},
{
name: '机构二',
month: 'Apr.',
grades: 99.7,
label: "运营部1",
type: "后五",
value: 390
},
{
name: '机构二',
month: 'May',
grades: 52.6,
},
{
name: '机构二',
month: 'Jun.',
grades: 35.5,
},
{
name: '机构二',
month: 'Jul.',
grades: 37.4,
},
{
name: '机构二',
month: 'Aug.',
grades: 42.4,
},
]
label: "后勤部1",
type: "后五",
value: 100
}
];
export const rankingConfig = {
data: [],
isGroup: true,
xField: "month",
yField: "grades",
seriesField: "name",
// 分组柱状图 组内柱子间的间距 (像素级别)
dodgePadding: 2,
// 分组柱状图 组间的间距 (像素级别)
intervalPadding: 10,
columnStyle: {
radius: [20, 20, 0],
},
xField: "value",
yField: "label",
seriesField: "type",
dodgePadding: 4,
label: {
// 可手动配置 label 数据标签位置
position: "middle",
// 'top', 'middle', 'bottom'
// 'left', 'middle', 'right'
// 可配置附加的布局方法
layout: [
// 柱形图数据标签位置自动调整
@ -602,257 +553,249 @@ export const humanAnalysisconfig = {
//人效排名数据
export const humanEfficiencyRankingData = [
{
title: '员工1',
title: "员工1",
description: "公司1",
rank:1
rank: 1
},
{
title: '员工2',
title: "员工2",
description: "公司2",
rank:2
rank: 2
},
{
title: '员工3',
title: "员工3",
description: "公司3",
rank:3
rank: 3
},
{
title: '员工4',
title: "员工4",
description: "公司4",
rank:4
},
rank: 4
}
];
//销售新客户合同数量排名
export const saleEfficiencyRankingData = [
{
title: '员工5',
title: "员工5",
description: "公司1",
rank:1
rank: 1
},
{
title: '员工6',
title: "员工6",
description: "公司2",
rank:2
rank: 2
},
{
title: '员工7',
title: "员工7",
description: "公司3",
rank:3
rank: 3
},
{
title: '员工8',
title: "员工8",
description: "公司4",
rank:4
},
rank: 4
}
];
//新销售有效合同
export const newSaleEfficiencyRankingData = [
{
title: '员工9',
title: "员工9",
description: "公司1",
rank:1
rank: 1
},
{
title: '员工10',
title: "员工10",
description: "公司2",
rank:2
rank: 2
},
{
title: '员工11',
title: "员工11",
description: "公司3",
rank:3
rank: 3
},
{
title: '员工12',
title: "员工12",
description: "公司4",
rank:4
},
rank: 4
}
];
//人工总成本数据
export const totalLaborCostdata = [
{
time: 'Jan.',
time: "Jan.",
value: 350,
count: 800,
count: 800
},
{
time: 'Feb.',
time: "Feb.",
value: 900,
count: 600,
count: 600
},
{
time: 'Mar.',
time: "Mar.",
value: 300,
count: 400,
count: 400
},
{
time: 'Apr.',
time: "Apr.",
value: 450,
count: 380,
count: 380
},
{
time: 'May.',
time: "May.",
value: 470,
count: 220,
count: 220
},
{
time: 'Jun.',
time: "Jun.",
value: 470,
count: 220,
count: 220
},
{
time: 'Jul.',
time: "Jul.",
value: 470,
count: 220,
count: 220
},
{
time: 'Aug.',
time: "Aug.",
value: 470,
count: 220,
},
count: 220
}
];
export const totalLaborCostConfig = {
data: [totalLaborCostdata, totalLaborCostdata],
xField: 'time',
yField: ['value', 'count'],
xField: "time",
yField: ["value", "count"],
yAxis: {
// 格式化左坐标轴
value: {
min: 0,
label: {
formatter: (val) => `${val}W`,
},
formatter: (val) => `${val}W`
}
},
// 隐藏右坐标轴
count: false,
count: false
},
geometryOptions: [
{
geometry: 'column',
color: '#5B8FF9',
geometry: "column",
color: "#5B8FF9",
columnWidthRatio: 0.4,
label: {
position: 'middle',
},
position: "middle"
}
},
{
geometry: 'line',
geometry: "line",
smooth: true,
color: '#5AD8A6',
},
],
interactions: [
{
type: 'element-highlight',
},
{
type: 'active-region',
},
color: "#5AD8A6"
}
],
interactions: [],
annotations: {
value: [
{
type: 'text',
position: ['2019-06', 'max'],
content: '柱线混合图',
},
type: "text",
position: ["2019-06", "max"],
content: "柱线混合图"
}
],
count: [
{
type: 'dataMarker',
type: "dataMarker",
top: true,
position: ['2019-05', 400],
position: ["2019-05", 400],
line: {
length: 20,
length: 20
},
text: {
content: '2019-05, 发布新版本',
content: "",
style: {
textAlign: 'left',
},
},
},
],
},
textAlign: "left"
}
}
}
]
}
};
//人均收入配置数据 1、人均固定收入 2、人均浮动收入
export const fixedIncomePerData = [
{
type: '销售',
value: 27,
type: "销售",
value: 27
},
{
type: '项目',
value: 25,
type: "项目",
value: 25
},
{
type: '开发',
value: 18,
type: "开发",
value: 18
},
{
type: '客服',
value: 15,
type: "客服",
value: 15
},
{
type: '其他',
value: 10,
type: "其他",
value: 10
},
{
type: '其他',
value: 5,
},
type: "其他",
value: 5
}
];
export const floatingIncomePerData = [
{
type: '销售',
value: 18,
type: "销售",
value: 18
},
{
type: '项目',
value: 29,
type: "项目",
value: 29
},
{
type: '开发',
value: 23,
type: "开发",
value: 23
},
{
type: '客服',
value: 3,
type: "客服",
value: 3
},
{
type: '其他',
value: 22,
type: "其他",
value: 22
},
{
type: '其他',
value: 5,
},
type: "其他",
value: 5
}
];
//人均收入配置信息
export const perCapitaIncomeConfig = {
appendPadding: 10,
data:[],
angleField: 'value',
colorField: 'type',
data: [],
angleField: "value",
colorField: "type",
radius: 1,
innerRadius: 0.64,
meta: {
value: {
formatter: (v) => `${v} ¥`,
},
formatter: (v) => `${v} ¥`
}
},
label: {
type: 'inner',
offset: '-50%',
type: "inner",
offset: "-50%",
style: {
textAlign: 'center',
textAlign: "center"
},
autoRotate: false,
content: '{value}',
content: "{value}"
},
statistic: {
title: {
@ -861,119 +804,117 @@ export const perCapitaIncomeConfig = {
const { width, height } = container.getBoundingClientRect();
const d = Math.sqrt(Math.pow(width / 2, 2) + Math.pow(height / 2, 2));
//总计
const text = datum ? datum.type : '';
const text = datum ? datum.type : "";
return renderStatistic(d, text, {
fontSize: 28,
fontSize: 28
});
},
}
},
content: {
offsetY: 4,
style: {
fontSize: '32px',
fontSize: "32px"
},
customHtml: (container, view, datum, data) => {
const { width } = container.getBoundingClientRect();
const text = datum ? `¥ ${datum.value}` : `¥ ${data.reduce((r, d) => r + d.value, 0)}`;
return renderStatistic(width, "", {
fontSize: 32,
fontSize: 32
});
},
},
}
}
},
// 添加 中心统计文本 交互
interactions: [
],
interactions: []
};
//人均人工成本数据
export const laborCostsPerCapitaData = [
{
year: 'Jan.',
year: "Jan.",
value: 3,
count: 10,
count: 10
},
{
year: 'Feb.',
year: "Feb.",
value: 4,
count: 4,
count: 4
},
{
year: 'Mar.',
year: "Mar.",
value: 3.5,
count: 5,
count: 5
},
{
year: 'Apr.',
year: "Apr.",
value: 5,
count: 5,
count: 5
},
{
year: 'May.',
year: "May.",
value: 4.9,
count: 4.9,
count: 4.9
},
{
year: 'Jun.',
year: "Jun.",
value: 6,
count: 35,
count: 35
},
{
year: 'Jul.',
year: "Jul.",
value: 7,
count: 7,
count: 7
},
{
year: 'Aug.',
year: "Aug.",
value: 9,
count: 1,
},
count: 1
}
];
export const laborCostsPerCapitaConfig = {
data: [laborCostsPerCapitaData, laborCostsPerCapitaData],
xField: 'year',
yField: ['value', 'count'],
xField: "year",
yField: ["value", "count"],
geometryOptions: [
{
geometry: 'line',
geometry: "line",
smooth: true,
color: '#5B8FF9',
color: "#5B8FF9",
label: {
formatter: (datum) => {
return `${datum.value}w`;
},
}
},
lineStyle: {
lineWidth: 3,
lineDash: [5, 5],
},
lineDash: [5, 5]
}
},
{
geometry: 'line',
geometry: "line",
smooth: true,
color: '#5AD8A6',
color: "#5AD8A6",
lineStyle: {
lineWidth: 4,
opacity: 0.5,
opacity: 0.5
},
label: {
formatter: (datum) => {
return `${datum.count}w`;
},
}
},
point: {
shape: 'circle',
shape: "circle",
size: 4,
style: {
opacity: 0.5,
stroke: '#5AD8A6',
fill: '#fff',
},
},
},
],
stroke: "#5AD8A6",
fill: "#fff"
}
}
}
]
};
function renderStatistic(containerWidth, text, style) {
@ -987,7 +928,7 @@ function renderStatistic(containerWidth, text, style) {
}
const textStyleStr = `width:${containerWidth}px;`;
return `<div style="${textStyleStr};font-size:${scale}em;line-height:${scale < 1 ? 1 : 'inherit'};">${text}</div>`;
return `<div style="${textStyleStr};font-size:${scale}em;line-height:${scale < 1 ? 1 : "inherit"};">${text}</div>`;
}

@ -1,64 +0,0 @@
.chartWrapper {
display: flex;
flex-direction: column;
width: 100%;
height: 100%;
background: #e5e5e5;
padding: 8px;
overflow: hidden;
.talentTableBox, .flowRateTableBox, .profitTableBox {
display: flex;
align-items: center;
& > div:not(:last-child) {
margin-right: 8px;
}
& > div {
flex: 1;
height: 100%;
position: relative;
background: #fff;
padding: 40px 10px 8px;
overflow: hidden;
: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;
& > div {
height: 100% !important;
}
}
}
}
}
}
.title {
position: absolute;
left: 2%;
top: 2%;
font-size: 16px;
font-weight: 700;
}
}
}
& > div {
margin-bottom: 8px;
flex: 1;
overflow: hidden;
}
}

@ -0,0 +1,6 @@
.peopleFlowWrapper {
width: 100%;
height: 100%;
background: #e5e5e5;
overflow: auto;
}

@ -0,0 +1,23 @@
/*
* Author:
* name:
* Description:
* Date: 2023/4/27
*/
import React, { FunctionComponent } from "react";
import styles from "./index.less";
interface OwnProps {
}
type Props = OwnProps;
const index: FunctionComponent<Props> = (props) => {
return (
<div className={styles.peopleFlowWrapper}>
</div>
);
};
export default index;

@ -0,0 +1,116 @@
const AgeStructureBar = (echarts: any) => {
// 实例化对象
const myChart = echarts.init(document.getElementById("ageStructure"));
// 配置项
const option = {
legend: {
icon: "circle",
top: "-1%",
right: "0%",
itemGap: 20,
textStyle:{
fontSize: 12,//字体大小
color: '#ffffff'//字体颜色
},
},
tooltip: {
// 坐标轴指示器,坐标轴触发有效
trigger: "axis",
axisPointer: {
// 默认为直线,可选为:'line' | 'shadow'
type: "shadow"
}
},
grid: {
left: "0%",
top: 20,
right: "0%",
bottom: "0%",
containLabel: true
},
xAxis: {
type: "value",
show: false,
axisLine: {
show: false
},
splitLine: {
show: false
},
axisTick: {
show: false // 不显示坐标轴刻度线
}
},
yAxis: {
type: "category",
data: ["30岁以下", "3040岁", "4050岁", "5060岁", "60岁以上"],
// 修改坐标值样式
axisLine: {
show: false
},
axisLabel: {
color: "rgba(255, 255, 255, 1)",
fontSize: 10,
show: true
},
axisTick: {
show: false // 不显示坐标轴刻度线
}
},
series: [
{
name: "男",
type: "bar",
stack: "total",
barWidth: "30%",
label: {
show: true,
color: "rgba(255, 255, 255, 1)",
fontSize: 10
},
emphasis: {
focus: "series"
},
data: [320, 302, 301, 334, 390],
// bar 样式修改
itemStyle: {
barBorderRadius: [10, 0 , 0 , 10],
color: new echarts.graphic.LinearGradient(0, 0, 1, 0, [
{ offset: 0, color: "#3EA1FF" },
{ offset: 0.5, color: "#72CFFF" },
{ offset: 1, color: "#72CFFF" }
]),
},
},
{
name: "女",
type: "bar",
stack: "total",
barWidth: "30%",
label: {
show: true,
color: "rgba(255, 255, 255, 1)",
fontSize: 10
},
emphasis: {
focus: "series"
},
data: [120, 132, 101, 134, 90],
// bar 样式修改
itemStyle: {
barBorderRadius: [0, 10 , 10 , 0],
color: new echarts.graphic.LinearGradient(0, 0, 1, 0, [
{ offset: 0, color: "#B2FDB2" },
{ offset: 0.5, color: "#00D5FF" },
{ offset: 1, color: "#00D5FF" }
]),
},
}
]
};
// 配置项给实例对象
myChart.setOption(option);
return myChart;
};
export default AgeStructureBar;

@ -0,0 +1,119 @@
const PersonnelCategoryBar = (echarts: any) => {
// 实例化对象
const myChart = echarts.init(document.getElementById("changeStatistics"));
// 配置项
const option = {
tooltip: {
// 坐标轴指示器,坐标轴触发有效
trigger: "axis",
axisPointer: {
// 默认为直线,可选为:'line' | 'shadow'
type: "shadow"
}
},
grid: {
left: "0%",
top: 10,
right: "0%",
bottom: "4%",
// true: 数值离DOM盒子的距离 false: 坐标轴离DOM盒子的距离
containLabel: true
},
xAxis: [
{
type: "category",
data: ["公司本部", "公司1", "公司2", "公司3", "公司4", "公司5", "公司6", "公司7"],
axisTick: {
alignWithLabel: true
},
// 修改坐标值样式
axisLabel: {
color: "rgba(255, 255, 255, 1)",
fontSize: 10,
show: true
},
axisLine: {
show: false
}
}
],
yAxis: [
{
type: "value",
// 修改坐标值样式
axisLabel: {
color: "rgba(255, 255, 255, 1)",
fontSize: 12
},
// 修改坐标轴线样式
axisLine: {
show: false
},
axisTick: {
show: false // 不显示坐标轴刻度线
},
splitLine: {
lineStyle: {
type: "dashed",
color: "rgba(93,126,158,1)"
}
}
}
],
series: [
{
name: "入职",
type: "bar",
showBackground: false,
barWidth: "10%",
data: [250, 250, 320, 410, 280, 320, 405, 405],
// bar 样式修改
itemStyle: {
barBorderRadius: 10,
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: "#3EA1FF" },
{ offset: 0.5, color: "#72CFFF" },
{ offset: 1, color: "#72CFFF" }
])
}
},
{
name: "离职",
type: "bar",
showBackground: false,
barWidth: "10%",
data: [370, 370, 260, 110, 390, 280, 110, 110],
// bar 样式修改
itemStyle: {
barBorderRadius: 10,
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: "#00D5FF" },
{ offset: 0.5, color: "#B2FDB2" },
{ offset: 1, color: "#B2FDB2" }
])
}
},
{
name: "调职",
type: "bar",
showBackground: false,
barWidth: "10%",
data: [310, 310, 80, 280, 310, 70, 280, 280],
// bar 样式修改
itemStyle: {
barBorderRadius: 10,
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: "#1455FF" },
{ offset: 0.5, color: "#5F9CFF" },
{ offset: 1, color: "#5F9CFF" }
])
}
}
]
};
// 配置项给实例对象
myChart.setOption(option);
return myChart;
};
export default PersonnelCategoryBar;

@ -0,0 +1,103 @@
import { getPie3D } from "@/utils/chart";
const color = ["#FE772D", "#FEDB4B", "#2A71FF", "#00EDFE"];
const DegreeStructurePie = (echarts: any) => {
// 实例化对象
const myChart = echarts.init(document.getElementById("degreeStructure"));
// 配置项
const option = {
...getPie3D(setLabel([
{
name: "研究生(博士)",
value: 176
},
{
name: "研究生(硕士)",
value: 288
},
{
name: "本科",
value: 588
},
{
name: "大专及以下",
value: 78
}
], color), 0.6, 240, 26, 18, 1, true)
};
// 配置项给实例对象
myChart.setOption({
...option,
legend: {
orient: "vertical",
icon: "circle",
top: "25%",
left: "10%",
itemGap: 28,
textStyle: {
fontSize: 12,//字体大小
color: "#ffffff"//字体颜色
}
}
});
return myChart;
};
export default DegreeStructurePie;
export const setLabel = (optionData: any, color: any) => {
return optionData.map((item: any, index: number) => {
return {
...item,
itemStyle: {
color: color[index]
},
label: {
normal: {
show: false,
color: color[index],
position: "right",
// distance:-10,
offset: [0, 3],
formatter: [
"{d|{d}%}",
"————",
// '{c|{c}}{b|台}',
"{b|{b}}"
].join("\n"), // 用\n来换行
rich: {
b: {
// color: '#fff',
lineHeight: 25,
align: "left",
color: color[index]
},
c: {
fontSize: 22,
textShadowColor: "#1c90a6",
textShadowOffsetX: 0,
textShadowOffsetY: 2,
textShadowBlur: 5,
color: color[index]
},
d: {
color: color[index],
align: "left"
}
}
}
},
labelLine: {
normal: {
show: false,
length2: 30,
lineStyle: {
width: 1,
color: color[index]
}
}
}
};
});
};

@ -0,0 +1,47 @@
import { getPie3D } from "@/utils/chart";
import { setLabel } from "./degreeStructurePie";
const color = ["#BD42EF", "#6D45F3", "#3358F3", "#6ECDE5"];
const GradAnalysisPie = (echarts: any) => {
// 实例化对象
const myChart = echarts.init(document.getElementById("gradAnalysis"));
// 配置项
const option = {
...getPie3D(setLabel([
{
name: "正高级",
value: 500
},
{
name: "副高级",
value: 1120
},
{
name: "中级",
value: 4567
},
{
name: "初级",
value: 5278
}
], color), 0.6, 240, 26, 18, 1, false)
};
// 配置项给实例对象
myChart.setOption({
...option,
legend: {
orient: "vertical",
icon: "circle",
top: "25%",
left: "10%",
itemGap: 28,
textStyle: {
fontSize: 12,//字体大小
color: "#ffffff"//字体颜色
}
}
});
return myChart;
};
export default GradAnalysisPie;

@ -0,0 +1,96 @@
const PersonnelCategoryBar = (echarts: any) => {
// 实例化对象
const myChart = echarts.init(document.getElementById("personnelCategory"));
// 配置项
const option = {
tooltip: {
// 坐标轴指示器,坐标轴触发有效
trigger: "axis",
axisPointer: {
// 默认为直线,可选为:'line' | 'shadow'
type: "shadow"
}
},
grid: {
left: "0%",
top: 10,
right: "0%",
bottom: "4%",
// true: 数值离DOM盒子的距离 false: 坐标轴离DOM盒子的距离
containLabel: true
},
xAxis: [
{
type: "category",
data: ["领导班子", "海外中国籍", "正式在岗", "海外外籍", "退休返聘", "其他临时", "劳务派遣", "内退"],
axisTick: {
alignWithLabel: true
},
// 修改坐标值样式
axisLabel: {
color: "rgba(255, 255, 255, 1)",
fontSize: 10,
rotate: 60,
show: true
},
axisLine: {
show: false
}
}
],
yAxis: [
{
type: "value",
// 修改坐标值样式
axisLabel: {
color: "rgba(255, 255, 255, 1)",
fontSize: 12
},
// 修改坐标轴线样式
axisLine: {
show: false
},
axisTick: {
show: false // 不显示坐标轴刻度线
},
splitLine: {
lineStyle: {
type: "dashed",
color: "rgba(93,126,158,1)"
}
}
}
],
series: [
{
name: "直接访问",
type: "bar",
showBackground: true,
barWidth: "30%",
data: [10, 52, 200, 334, 390, 1030, 220, 210],
// bar 样式修改
itemStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: "#3EA1FF" },
{ offset: 0.5, color: "#72CFFF" },
{ offset: 1, color: "#72CFFF" }
]),
emphasis: {
itemStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: "#AFD8FF" },
{ offset: 0.7, color: "#AFD8FF" },
{ offset: 1, color: "#AFD8FF" }
])
}
}
}
}
]
};
// 配置项给实例对象
myChart.setOption(option);
return myChart;
};
export default PersonnelCategoryBar;

@ -0,0 +1,64 @@
/*
* Author:
* name:
* Description:
* Date: 2023/5/4
*/
import React, { FC, useState } from "react";
import cs from "classnames";
import styles from "./index.less";
interface OwnProps {
position: string;
tabList: Array<any>;
children: any;
titleImg: any;
type?: string;
}
type Props = OwnProps;
const FrameComp: FC<Props> = (props) => {
const { position, children, tabList, titleImg } = props;
const [active, setActive] = useState<number>(0);
return (
<div className={styles.frameWrapper}>
<div className={styles.headerTop}>
<div className={cs(styles.headerLeft, { [styles["halfHeaderLeft"]]: position === "right" })}>
<img src={titleImg} alt=""/>
{
position === "right" &&
<ul className={styles.tabWrapper}>
{
_.map(tabList, (item, index) => {
const classes = cs({
[styles["liActive"]]: index === active
});
return <li key={index} onClick={() => setActive(index)} className={classes}>{item}</li>;
})
}
</ul>
}
</div>
<span className={styles.moreWrapper}>More <img src={require("../../../../assets/images/more.png")}
alt=""/></span>
</div>
{
position === "bottom" &&
<ul className={styles.tabWrapper}>
{
_.map(tabList, (item, index) => {
const classes = cs({
[styles["liActive"]]: index === active
});
return <li key={index} onClick={() => setActive(index)} className={classes}>{item}</li>;
})
}
</ul>
}
{children}
</div>
);
};
export default FrameComp;

@ -0,0 +1,125 @@
.topPageWrapper {
height: 1.25rem;
position: relative;
background: url("../../../../assets/images/head_bg1.png") no-repeat;
background-size: 100% 100%;
.title {
text-align: center;
line-height: 0.875rem;
position: relative;
.updateTime {
color: #fff;
line-height: 0;
margin-top: 0.0625rem;
img {
width: 0.225rem;
margin-right: .2rem;
}
}
img {
width: 4.475rem;
}
}
.logoWrapper, .showTime {
position: absolute;
}
.showTime {
right: 2.375rem;
top: 0.4rem;
text-align: right;
color: #fff;
.dateDay {
padding: 0 .35rem;
}
}
.logoWrapper {
top: 0.1rem;
left: 2.375rem;
line-height: 0.875rem;
img {
width: 1.675rem;
}
}
}
.mapWrapper {
width: 10.625rem;
height: 8.125rem;
margin-top: 1rem;
}
.frameWrapper {
background: url("../../../../assets/images/frame_bg.png") no-repeat;
background-size: 100% 100%;
margin-bottom: 0.2rem;
.headerTop {
height: 0.475rem;
position: relative;
background: url("../../../../assets/images/frame_title_bg.png") no-repeat;
background-size: 100% 100%;
display: flex;
justify-content: flex-end;
.halfHeaderLeft {
left: 1.45rem !important;
}
.headerLeft {
position: absolute;
left: 0.7375rem;
top: 0.0625rem;
display: flex;
img {
height: 0.3rem;
}
}
.moreWrapper {
color: #fff;
font-size: 0.175rem;
position: absolute;
right: 0.3375rem;
top: 0.1rem;
cursor: pointer;
img {
width: 0.2375rem;
}
}
.tabWrapper {
margin-left: 0.775rem;
padding: 0;
}
}
.tabWrapper {
display: flex;
margin: 0;
padding: 0 0.3375rem;
li {
padding: 0.1rem 0.175rem;
font-size: 14px;
color: #ACACAC;
cursor: pointer;
}
li.liActive {
background: url("../../../../assets/images/tabSelected.png") no-repeat;
background-size: 100% 100%;
background-position-y: .125rem;
}
}
}

@ -0,0 +1,25 @@
/*
* Author:
* name:
* Description:
* Date: 2023/5/4
*/
import React, { FC } from "react";
import Chart from "@/utils/chart";
import { mapOptions } from "./options";
import styles from "./index.less";
interface OwnProps {
}
type Props = OwnProps;
const Map: FC<Props> = (props) => {
return (
<div className={styles.mapWrapper}>
<Chart renderer={"canvas"} option={mapOptions({})}/>
</div>
);
};
export default Map;

@ -0,0 +1,243 @@
import "echarts/map/js/china";
// 地图数据
const mapData = {
citys: [
{
name: "浙江",
value: [120.15, 29.28, -2],
symbolSize: 2,
itemStyle: {
normal: {
color: "#FFF"
}
}
},
{
name: "四川",
value: [103.36, 30.65, -5],
symbolSize: 2,
itemStyle: {
normal: {
color: "#FFF"
}
}
},
{
name: "内蒙古",
value: [112.17, 42.81, -23],
symbolSize: 2,
itemStyle: {
normal: {
color: "#FFF"
}
}
},
{
name: "江苏",
value: [120.26, 32.54, -1],
symbolSize: 2,
itemStyle: {
normal: {
color: "#FFF"
}
}
},
{
name: "西藏",
value: [89.13, 30.66, -1],
symbolSize: 2,
itemStyle: {
normal: {
color: "#FFF"
}
}
}
]
};
export const salaryData = {
header: ["业务板块", "二级机构", "人员类型", "起始时间", "截止时间", "发薪人数", "固定薪酬", "浮动薪酬", "薪酬合计", "薪酬固浮化"],
data: [
["国内工程", "建设集团", "领导班子", "2023.1", "2023.1", "23", "20,000", "20,000", "20,000", "20,000"],
["国内工程", "建设集团", "领导班子", "2023.1", "2023.1", "23", "20,000", "20,000", "20,000", "20,000"],
["国内工程", "建设集团", "领导班子", "2023.1", "2023.1", "23", "20,000", "20,000", "20,000", "20,000"],
["国内工程", "建设集团", "领导班子", "2023.1", "2023.1", "23", "20,000", "20,000", "20,000", "20,000"],
["国内工程", "建设集团", "领导班子", "2023.1", "2023.1", "23", "20,000", "20,000", "20,000", "20,000"],
["国内工程", "建设集团", "领导班子", "2023.1", "2023.1", "23", "20,000", "20,000", "20,000", "20,000"],
["国内工程", "建设集团", "领导班子", "2023.1", "2023.1", "23", "20,000", "20,000", "20,000", "20,000"],
["国内工程", "建设集团", "领导班子", "2023.1", "2023.1", "23", "20,000", "20,000", "20,000", "20,000"]
]
};
export const mapOptions = (params) => ({
title: {
show: false,
text: "全国物流输送图",
left: "center",
textStyle: {
color: "#fff"
}
},
legend: {
show: false
},
geo: [
{
nameMap: {
China: "中国"
},
map: "china",
zlevel: 5,
label: {
normal: {
show: true,
formatter: function (params) {
//圆环显示文字
return params.name === "江苏" || params.name === "浙江" || params.name === "四川" || params.name === "内蒙古" || params.name === "西藏" ? params.name : "";
},
fontSize: 12,
color: "#FFF"
},
emphasis: {
show: true,
color: "#FFF"
}
},
zoom: 1.2,
itemStyle: {
normal: {
borderColor: "rgba(148,224,250, .5)", //区域边框颜色
areaColor: "rgba(82,164,238,.1)", //区域颜色
borderWidth: 1, //区域边框宽度
shadowBlur: 5,
shadowColor: "rgba(3,113,173,.7)"
},
emphasis: {
borderColor: "rgba(148,224,250, .5)",
areaColor: "rgba(6,89,176,.3)",
borderWidth: 2.5,
shadowBlur: 5,
shadowColor: "rgba(3,113,173,.5)"
}
}
},
{
nameMap: {
China: "中国"
},
map: "china",
zlevel: 4,
top: "11%",
label: {
emphasis: {
show: false
}
},
zoom: 1.2,
itemStyle: {
normal: {
// borderColor: 'rgba(255,209,163, .5)', //区域边框颜色
areaColor: "rgba(73,86,166,.1)", //区域颜色
borderWidth: 0.5, //区域边框宽度
shadowBlur: 5,
shadowColor: "rgba(107,91,237,.7)"
},
emphasis: {
borderColor: "transparent",
areaColor: "transparent",
borderWidth: 1,
shadowBlur: 5,
shadowColor: "transparent"
}
}
},
{
nameMap: {
China: "中国"
},
map: "china",
zlevel: 3,
top: "12%",
label: {
emphasis: {
show: false
}
},
zoom: 1.2,
itemStyle: {
normal: {
areaColor: "rgba(73,86,166,.1)", //区域颜色
borderWidth: 0.5, //区域边框宽度
shadowBlur: 5,
shadowColor: "rgba(107,91,237,.7)"
},
emphasis: {
borderColor: "transparent",
areaColor: "transparent",
borderWidth: 1,
shadowBlur: 5,
shadowColor: "transparent"
}
}
},
{
nameMap: {
China: "中国"
},
map: "china",
zlevel: 2,
top: "13%",
label: {
emphasis: {
show: false
}
},
zoom: 1.2,
itemStyle: {
normal: {
// borderColor: 'rgba(255,209,163, .5)', //区域边框颜色
areaColor: "rgba(73,86,166,.1)", //区域颜色
borderWidth: 0.5, //区域边框宽度
shadowBlur: 5,
shadowColor: "rgba(107,91,237,.7)"
},
emphasis: {
borderColor: "transparent",
areaColor: "transparent",
borderWidth: 1,
shadowBlur: 5,
shadowColor: "transparent"
}
}
}
],
series: [
{
name: "地点",
type: "custom",
coordinateSystem: "geo",
zlevel: 12,
renderItem(params, api) {
//具体实现自定义图标的方法
return {
type: "image",
style: {
image: require("../../../../assets/images/zs.png"),
x: api.coord([
mapData.citys[params.dataIndex].value[0],
mapData.citys[params.dataIndex].value[1]
])[0],
y: api.coord([
mapData.citys[params.dataIndex].value[0],
mapData.citys[params.dataIndex].value[1]
])[1]
}
};
},
data: mapData.citys
}
]
});
export const userOptions = (params = {}) => ({
header: params.header,
data: params.data
});

@ -0,0 +1,53 @@
/*
* Author:
* name:
* Description:
* Date: 2023/5/4
*/
import React, { FC, useEffect, useRef, useState } from "react";
import { formatTime } from "@/utils/common";
import styles from "./index.less";
interface OwnProps {
}
type Props = OwnProps;
const weekday = ["星期天", "星期一", "星期二", "星期三", "星期四", "星期五", "星期六"];
const TopPage: FC<Props> = (props) => {
const [timeStr, setTimeStr] = useState<any>({});
const timing = useRef<any>(null);
useEffect(() => {
setTimingFn();
return () => {
timing.current = null;
};
}, []);
const setTimingFn = () => {
timing.current = setInterval(() => {
let dateYear = formatTime(new Date(), "yyyy-MM-dd");
let dateDay = formatTime(new Date(), "HH:mm:ss");
let dateWeek = weekday[new Date().getDay()];
setTimeStr({ dateYear, dateDay, dateWeek });
}, 1000);
};
return (
<div className={styles.topPageWrapper}>
<span className={styles.logoWrapper}><img src={require("../../../../assets/images/weaverlogo.png")}
alt=""/></span>
<div className={styles.title}>
<img src={require("../../../../assets/images/head_title.png")} alt=""/>
<div className={styles.updateTime}>
<img src={require("../../../../assets/images/refresh.png")} alt=""/>
16:02:09
</div>
</div>
<span
className={styles.showTime}><span>{timeStr?.dateYear}</span><span
className={styles.dateDay}>{timeStr?.dateDay}</span><span>{timeStr?.dateWeek}</span></span>
</div>
);
};
export default TopPage;

@ -0,0 +1,115 @@
.screenWrapperRoot {
width: 100%;
height: 100%;
overflow-y: auto;
.screenWrapper {
line-height: 1.15;
min-height: 100vh;
background: url("../../../assets/images/pageBg.png") top center no-repeat;
background-size: 100% 100%;
.screenMain {
display: flex;
min-width: 1024px;
max-width: 1920px;
margin: 0 auto;
padding: 0 0.6875rem;
.screenColCenter {
flex: 5 1;
margin: 0 0.125rem;
position: relative;
.cardWrapper {
display: flex;
width: 100%;
position: absolute;
top: 0;
left: 0;
z-index: 99;
padding: 0.25rem 0.125rem 0;
& > li:not(:last-child) {
margin-right: 0.275rem;
}
& > li {
flex: 1;
min-height: 1.1625rem;
background: url("../../../assets/images/card_bg.png") no-repeat;
background-size: 100% 100%;
display: flex;
align-items: center;
justify-content: center;
img {
width: 0.725rem;
margin-right: 0.2rem;
}
.cardInner {
display: flex;
flex-direction: column;
color: #FFF;
min-width: 1.125rem;
text-align: center;
span:first-child {
font-size: 0.275rem;
margin-bottom: 0.1rem;
}
span:last-child {
font-size: 0.2rem;
}
}
}
}
}
}
.screenCol {
flex: 3 1;
.personnelCate, .ageStruct, .degreeStructure, .gradAnalysis {
height: 3.875rem;
padding: 0 0.3375rem;
}
}
.screenFooter {
display: flex;
min-width: 1024px;
max-width: 1920px;
margin: 0 auto;
padding: 0 0.6875rem;
& > div:first-child {
margin-right: 0.125rem;
}
.screenFooterCol {
flex: 1;
.payrollStatistics {
padding-bottom: 0.2375rem !important;
:global {
.dv-scroll-board {
.rows .row-item, .header {
font-size: 0.15rem !important;
}
}
}
}
.payrollStatistics, .personnelChangeStatistics {
height: 3.5rem;
padding: 0 0.3375rem;
}
}
}
}
}

@ -0,0 +1,147 @@
/*
* Author:
* name:
* Description:
* Date: 2023/5/4
*/
import React, { FC, useEffect } from "react";
// @ts-ignore
import echarts from "echarts";
import "echarts-gl";
import TopPage from "./components/topPage";
import Map from "./components/map";
import FrameComp from "./components/frameComp";
import PersonnelCategoryBar from "./components/echarts/personnelCategoryBar";
import AgeStructureBar from "./components/echarts/ageStructureBar";
import DegreeStructurePie from "./components/echarts/degreeStructurePie";
import GradAnalysisPie from "./components/echarts/gradAnalysisPie";
import ChangeStatisticsBar from "./components/echarts/changeStatisticsBar";
import { salaryData, userOptions } from "./components/options";
// @ts-ignore
import { ScrollBoard } from "@jiaminghi/data-view-react";
import styles from "./index.less";
interface OwnProps {
}
type Props = OwnProps;
const Index: FC<Props> = (props) => {
useEffect(() => {
const personnelCategoryBar = PersonnelCategoryBar(echarts);
const ageStructureBar = AgeStructureBar(echarts);
const degreeStructurePie = DegreeStructurePie(echarts);
const gradAnalysisPie = GradAnalysisPie(echarts);
const changeStatisticsBar = ChangeStatisticsBar(echarts);
// 屏幕缩放对chart图表进行自适应处理调用实例的resize方法
window.onresize = () => {
personnelCategoryBar.resize();
ageStructureBar.resize();
degreeStructurePie.resize();
gradAnalysisPie.resize();
changeStatisticsBar.resize();
};
return () => {
};
}, []);
return (
<div className={styles.screenWrapperRoot}>
<div className={styles.screenWrapper}>
<TopPage/>
<div className={styles.screenMain}>
<div className={styles.screenCol}>
<FrameComp position="bottom" tabList={["全部", "总部", "苏皖大区", "山东大区"]}
titleImg={require("../../../assets/images/rylbylb.png")}>
<div className={styles.personnelCate} id="personnelCategory"/>
</FrameComp>
<FrameComp position="bottom" tabList={["全部", "总部", "苏皖大区"]}
titleImg={require("../../../assets/images/hxzgzgnljg.png")}>
<div className={styles.ageStruct} id="ageStructure"/>
</FrameComp>
</div>
<div className={styles.screenColCenter}>
<ul className={styles.cardWrapper}>
<li>
<img src={require("../../../assets/images/xsje.png")} alt=""/>
<div className={styles.cardInner}>
<span>8,29,000</span>
<span></span>
</div>
</li>
<li>
<img src={require("../../../assets/images/xmys.png")} alt=""/>
<div className={styles.cardInner}>
<span>3,29,000</span>
<span></span>
</div>
</li>
<li>
<img src={require("../../../assets/images/yjgz.png")} alt=""/>
<div className={styles.cardInner}>
<span>12,000</span>
<span></span>
</div>
</li>
<li>
<img src={require("../../../assets/images/rwdcl.png")} alt=""/>
<div className={styles.cardInner}>
<span>98%</span>
<span></span>
</div>
</li>
</ul>
<Map/>
</div>
<div className={styles.screenCol}>
<FrameComp position="bottom" tabList={["全部", "总部", "苏皖大区"]}
titleImg={require("../../../assets/images/hxzgzgxljg.png")}>
<div className={styles.degreeStructure} id="degreeStructure"></div>
</FrameComp>
<FrameComp position="bottom" tabList={["全部", "总部", "苏皖大区"]}
titleImg={require("../../../assets/images/hxzgzgdjfx.png")}>
<div className={styles.gradAnalysis} id="gradAnalysis"></div>
</FrameComp>
</div>
</div>
<div className={styles.screenFooter}>
<div className={styles.screenFooterCol}>
<FrameComp position="right" tabList={["全部", "总部", "苏皖大区"]}
titleImg={require("../../../assets/images/zgxcfftj.png")}>
<div className={styles.payrollStatistics}>
<ScrollBoard
config={{
// 表头背景色
headerBGC: "#012B56",
// 奇数行背景色
oddRowBGC: "#042043",
// 偶数行背景色
evenRowBGC: "#00376D",
// 行号
index: false,
// 行号表头
// indexHeader: "序号",
// 宽度
// columnWidth: [],
// 对其方式
align: ["center"],
// 表行数
rowNum: 4,
...userOptions(salaryData)
}}
></ScrollBoard>
</div>
</FrameComp>
</div>
<div className={styles.screenFooterCol}>
<FrameComp position="right" tabList={["全部", "总部", "苏皖大区"]}
titleImg={require("../../../assets/images/hxzgzgrybdtj.png")}>
<div className={styles.personnelChangeStatistics} id="changeStatistics"/>
</FrameComp>
</div>
</div>
</div>
</div>
);
};
export default Index;

@ -0,0 +1,114 @@
.profitWrapper {
display: flex;
width: 100%;
height: 100%;
background: #e5e5e5;
padding: 8px;
overflow: hidden;
.profitLeft {
display: flex;
flex-direction: column;
width: 580px;
overflow: hidden;
& > :not(:last-child) {
margin-bottom: 16px;
}
& > div:first-child {
& > div {
height: 100% !important;
}
}
& > div {
flex: 1;
background: #FFF;
position: relative;
padding: 40px 10px 8px;
overflow: hidden;
.title {
display: inline-block;
position: absolute;
left: 1%;
top: 2%;
font-size: 16px;
font-weight: 700;
}
: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;
& > div {
height: 100% !important;
}
}
}
}
}
}
}
}
.profitRight {
flex: 1;
position: relative;
margin-left: 16px;
display: flex;
flex-direction: column;
overflow: hidden;
& > div:first-child {
margin-bottom: 16px;
}
& > div {
background: #fff;
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;
}
}
}
}

@ -1,29 +1,24 @@
/*
* Author:
* name:
* Description:
* Date: 2023/4/27
*/
import React, { FC } from "react";
import styles from "./index.less";
import { Avatar, List, Tabs } from "antd";
import { Column, Funnel, Pie, DualAxes } from "@ant-design/plots";
import {
degreeData,
flowingConfig,
flowingTrendconfig,
genderData,
fixedIncomePerData,
floatingIncomePerData,
humanAnalysisconfig,
humanEfficiencyRankingData,
institutionConfig,
internData,
linesData,
multiplData,
multipleConfig, newSaleEfficiencyRankingData,
personChangeConfig,
personChangeData,
rankingConfig,
rankingData, saleEfficiencyRankingData,
seniorityConfig,
seniorityData,
trainingData,totalLaborCostConfig,
laborCostsPerCapitaConfig,
perCapitaIncomeConfig, fixedIncomePerData, floatingIncomePerData
} from "./constants";
import styles from "./index.less";
newSaleEfficiencyRankingData,
perCapitaIncomeConfig,
saleEfficiencyRankingData,
totalLaborCostConfig
} from "@/pages/personnelReport/constants";
import { Column, DualAxes, Pie } from "@ant-design/plots";
interface OwnProps {
}
@ -31,51 +26,42 @@ interface OwnProps {
type Props = OwnProps;
const index: FC<Props> = (props) => {
// @ts-ignore
return (
<div className={styles.chartWrapper}>
{/*人才结构表*/}
<div className={styles.talentTableBox}>
<div>
<div className={styles.title}></div>
<Pie {...flowingConfig} />
<div className={styles.profitWrapper}>
<div className={styles.profitLeft}>
<div className={styles.profitLeftItem1}>
<span className={styles.title}></span>
{/*@ts-ignore*/}
<Column {...humanAnalysisconfig} />
</div>
<div style={{ paddingTop: 0 }}>
<div className={styles.profitLeftItem2}>
<span className={styles.title}></span>
<Tabs defaultActiveKey="1">
<Tabs.TabPane tab="司龄占比" key="1">
<Pie {...seniorityConfig} data={seniorityData}/>
</Tabs.TabPane>
<Tabs.TabPane tab="性别占比" key="2">
<Pie {...seniorityConfig} data={genderData}/>
<Tabs.TabPane tab="人均固定收入" key="1">
<Pie {...perCapitaIncomeConfig} data={fixedIncomePerData}/>
</Tabs.TabPane>
<Tabs.TabPane tab="学历分布" key="3">
<Pie {...seniorityConfig} data={degreeData}/>
<Tabs.TabPane tab="人均浮动收入" key="2">
<Pie {...perCapitaIncomeConfig} data={floatingIncomePerData}/>
</Tabs.TabPane>
<Tabs.TabPane tab="条线占比" key="4">
<Pie {...seniorityConfig} data={linesData}/>
</Tabs>
</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="5">
<Pie {...seniorityConfig} data={internData}/>
<Tabs.TabPane tab="人均人工成本" key="2">
<DualAxes {...laborCostsPerCapitaConfig} />
</Tabs.TabPane>
</Tabs>
</div>
<div>
<div className={styles.title}></div>
<Column {...flowingTrendconfig} />
</div>
<div>
<div className={styles.title}></div>
<Funnel {...institutionConfig} />
</div>
</div>
{/*人才利润报表*/}
<div className={styles.profitTableBox}>
<div className={styles.profitRight}>
<div>
<div className={styles.title}></div>
<Column {...humanAnalysisconfig} />
</div>
<div>
<div className={styles.title}></div>
<span className={styles.title}></span>
<Tabs defaultActiveKey="1">
<Tabs.TabPane tab="有效金额排名" key="1">
<List
@ -128,7 +114,7 @@ const index: FC<Props> = (props) => {
</Tabs>
</div>
<div>
<div className={styles.title}></div>
<span className={styles.title}></span>
<Tabs defaultActiveKey="1">
<Tabs.TabPane tab="指标400万" key="1">
<List
@ -244,47 +230,6 @@ const index: FC<Props> = (props) => {
</Tabs.TabPane>
</Tabs>
</div>
<div>
<div className={styles.title}></div>
<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>
<div>
<div className={styles.title}></div>
<Tabs defaultActiveKey="1">
<Tabs.TabPane tab="人工总成本" key="1">
<DualAxes {...totalLaborCostConfig} />
</Tabs.TabPane>
<Tabs.TabPane tab="人均人工成本" key="2">
<DualAxes {...laborCostsPerCapitaConfig} />
</Tabs.TabPane>
</Tabs>
</div>
</div>
{/*人员流量表*/}
<div className={styles.flowRateTableBox}>
<div>
<div className={styles.title}></div>
<Pie {...personChangeConfig} data={personChangeData}/>
</div>
<div>
<div className={styles.title}></div>
<Column {...multipleConfig} data={multiplData}/>
</div>
<div>
<div className={styles.title}></div>
<Pie {...seniorityConfig} data={trainingData}/>
</div>
<div>
<div className={styles.title}></div>
<Column {...rankingConfig} data={rankingData}/>
</div>
</div>
</div>
);

@ -0,0 +1,48 @@
const AgeStaticsPie = (echarts: any) => {
// 实例化对象
const myChart = echarts.init(document.getElementById("ageStatics"));
// 配置项
const option = {
legend: {
icon: "circle",
bottom: "0%",
left: "center",
itemGap: 20,
textStyle: {
fontSize: 12,//字体大小
color: "#787E95"//字体颜色
}
},
color: ["#5FCD7B", "#F5CB49", "#7599F5", "#64BFDB"],
series: [
{
name: "司龄统计",
type: "pie",
radius: ["45%", "60%"],
avoidLabelOverlap: false,
animation: false,
label: {
normal: {
formatter: "{d}%",
padding: [0, -25],
position: "outer" //文字显示在内部,如果在外边把这个去掉就好
}
},
labelLine: {
show: false
},
data: [
{ value: 40, name: "1-3年" },
{ value: 35, name: "3-5年" },
{ value: 20, name: "5-8年" },
{ value: 5, name: "8年以上" }
]
}
]
};
// 配置项给实例对象
myChart.setOption(option);
return myChart;
};
export default AgeStaticsPie;

@ -0,0 +1,56 @@
const EducationStaticsRedar = (echarts: any) => {
// 实例化对象
const myChart = echarts.init(document.getElementById("educationStatics"));
// 配置项
const option = {
tooltip: {
show: true
},
radar: {
// shape: 'circle',
indicator: [
{ name: "博士及以上", max: 800, axisLabel: { show: true } },
{ name: "硕士", max: 800 },
{ name: "本科", max: 800 },
{ name: "大专", max: 800 },
{ name: "大专一下", max: 800 }
],
splitArea: {
show: false
},
axisLine: {
show: false
}
},
series: [
{
name: "学历统计",
type: "radar",
data: [
{
value: [210, 400, 800, 230, 180],
name: "学历统计"
}
],
label: {
show: false,
formatter: function (params: any) {
return params.value;
}
},
itemStyle: { //此属性的颜色和下面areaStyle属性的颜色都设置成相同色即可实现
color: "#4D95E9",
borderColor: "#4D95E9"
},
areaStyle: {
color: "rgba(77,149,233,0.26)"
}
}
]
};
// 配置项给实例对象
myChart.setOption(option);
return myChart;
};
export default EducationStaticsRedar;

@ -0,0 +1,44 @@
const GenderStaticsPie = (echarts: any) => {
// 实例化对象
const myChart = echarts.init(document.getElementById("genderStatics"));
// 配置项
const option = {
legend: {
icon: "circle",
bottom: "0%",
left: "center",
itemGap: 20,
textStyle: {
fontSize: 12,//字体大小
color: "#787E95"//字体颜色
}
},
color: ["#A6BEF3", "#205CE1"],
grid: {
bottom: "10%",
containLabel: true
},
series: [
{
name: "性别统计",
type: "pie",
startAngle:-180,
radius: [40, 70],
roseType: "radius",
animation: false,
label: {
show: false
},
data: [
{ value: 70, name: "男" },
{ value: 30, name: "女" }
]
}
]
};
// 配置项给实例对象
myChart.setOption(option);
return myChart;
};
export default GenderStaticsPie;

@ -0,0 +1,141 @@
.structureCardWrapper {
width: 100%;
height: 100%;
overflow: hidden;
display: flex;
flex-direction: column;
.top {
display: flex;
& > span {
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-size: 14px;
color: #333333;
font-weight: 700;
}
ul {
display: flex;
li:last-child {
margin-left: 16px;
}
li.active {
color: #333333;
}
li {
font-size: 12px;
color: #D4D4D4;
cursor: pointer;
}
}
}
.center {
display: flex;
align-items: flex-end;
& > div:first-child {
& > span:first-child {
font-size: 24px;
letter-spacing: 0;
font-weight: 700;
}
& > span:last-child {
font-size: 14px;
color: #D4D4D4;
font-weight: 400;
}
}
& > div {
flex: 1;
}
.charts {
height: 62px;
}
}
.bottom {
display: flex;
justify-content: space-between;
align-items: center;
border-top: 0.5px solid #e5e5e5;
padding-top: 10px;
& > span {
& > span:first-child {
font-size: 12px;
color: #D4D4D4;
font-weight: 400;
margin-right: 8px;
}
& > span:last-child {
font-size: 12px;
color: #333333;
font-weight: 400;
}
}
}
}
.structureFrameWrapper {
width: 100%;
min-height: 100px;
display: flex;
flex-direction: column;
background: #fff;
padding: 16px;
border-radius: 5px;
.header {
display: flex;
align-items: center;
border-bottom: .5px solid #e5e5e5;
padding-bottom: 14px;
img {
width: 28px;
}
span {
display: inline-block;
font-size: 18px;
color: #333333;
letter-spacing: 0;
font-weight: 700;
margin-left: 8px;
}
}
.tabBox {
margin: 0;
display: flex;
justify-content: flex-end;
padding: 20px 32px 0 0;
li:last-child {
margin-left: 16px;
}
li.active {
color: #1E66F6;
}
li {
font-size: 14px;
color: #999999;
cursor: pointer;
font-weight: 550;
}
}
}

@ -0,0 +1,85 @@
const InternStaticsBar = (echarts: any) => {
// 实例化对象
const myChart = echarts.init(document.getElementById("internStatics"));
// 配置项
const option = {
xAxis: [
{
type: "value",
axisLabel: {
formatter: "{value} "
},
splitLine: {
show: true,
lineStyle: {
type: "dashed",
color: "#1D3039"
}
},
axisTick: {
show: false
},
axisLine: {
show: false,
lineStyle: {
fontSize: 12,
color: "#D4D4D4"
}
}
}
],
grid: {
top: "10%",
left: "5%",
right: "5%",
bottom: "3%",
containLabel: true
},
yAxis: [
{
type: "category",
offset: 0,
data: ["项目实习生", "销售实习生", "技术实习生", "大区实习生", "客服实习生"],
axisPointer: {
type: "shadow"
},
axisLine: {
//这是x轴文字颜色
show: false
},
axisTick: {
show: false
},
axisLabel: {
color: "#787E95",
fontSize: 10
}
}
],
series: [
{
data: [34, 19, 15, 32, 11],
type: "bar",
barWidth: "25%",
realtimeSort: true,
yAxisIndex: 0,
itemStyle: {
color: "#4CAEFF" //设定单个柱子颜色
},
label: {
normal: {
show: true,
position: "right",
fontWeight: "bold",
fontSize: 12
}
}
}
]
};
// 配置项给实例对象
myChart.setOption(option);
return myChart;
};
export default InternStaticsBar;

@ -0,0 +1,50 @@
const LineStaticsPie = (echarts: any) => {
// 实例化对象
const myChart = echarts.init(document.getElementById("lineStatics"));
// 配置项
const option = {
legend: {
icon: "circle",
bottom: "0%",
left: "center",
itemGap: 20,
textStyle: {
fontSize: 12,//字体大小
color: "#787E95"//字体颜色
}
},
color: ["#61D485", "#F4CB48", "#7499F5", "#65BFDC", "#ED9C5F"],
series: [
{
name: "司龄统计",
type: "pie",
radius: ["45%", "60%"],
center: ['50%', '41%'],
avoidLabelOverlap: false,
animation: false,
label: {
normal: {
formatter: "{d}%",
padding: [0, -25],
position: "outer" //文字显示在内部,如果在外边把这个去掉就好
}
},
labelLine: {
show: false
},
data: [
{ value: 40, name: "项目" },
{ value: 15, name: "市场" },
{ value: 20, name: "销售" },
{ value: 15, name: "支撑" },
{ value: 10, name: "销售1" }
]
}
]
};
// 配置项给实例对象
myChart.setOption(option);
return myChart;
};
export default LineStaticsPie;

@ -0,0 +1,149 @@
const MonthlyTrendsBar = (echarts: any) => {
// 实例化对象
const myChart = echarts.init(document.getElementById("monthlyTrendsCharts"));
// 配置项
const option = {
tooltip: {
// 坐标轴指示器,坐标轴触发有效
trigger: "axis",
axisPointer: {
// 默认为直线,可选为:'line' | 'shadow'
type: "shadow"
}
},
grid: {
top: "10%",
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: "8%",
barGap: "100%",
data: [250, 250, 320, 410, 280, 320, 405, 405, 320, 250, 280, 405],
// bar 样式修改
itemStyle: {
barBorderRadius: 5,
color: "#0FC482"
}
},
{
name: "离职",
type: "bar",
showBackground: false,
barWidth: "8%",
data: [370, 370, 260, 110, 390, 280, 110, 110, 370, 260, 390, 110],
// bar 样式修改
itemStyle: {
barBorderRadius: 5,
color: "#FFA500"
}
},
{
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],
itemStyle: {
normal: {
color: "#FFD700",
lineStyle: {
color: "#FFD700"
}
}
},
areaStyle: {
normal: {
color: {//分隔区域的颜色
x0: 0,
y0: 0,
x2: 0,
y2: 1,
colorStops: [{
offset: 0,
color: "#FFD700" // 0% 处的颜色
}, {
offset: 1,
color: "rgba(255,215,0,0)" // 100% 处的颜色中间还可以设置50%、20%、70%时的颜色
}],
globalCoord: false // 缺省为 false以确保上面的x,y,x2,y2表示的是百分比
}
}
}
}
]
};
// 配置项给实例对象
myChart.setOption(option);
return myChart;
};
export default MonthlyTrendsBar;

@ -0,0 +1,182 @@
const RankingBar = (echarts: any) => {
// 实例化对象
const colorList = ["#1E66F6", "#70CDFF", "#00C48B", "#FFD700", "#1E66F6", "#70CDFF", "#00C48B", "#FFD700"];
const myChart = echarts.init(document.getElementById("rankingCharts"));
// 配置项
const option = {
xAxis: [
{
type: "value",
axisLabel: {
formatter: "{value} "
},
splitLine: {
show: true,
lineStyle: {
type: "dashed",
color: "#1D3039"
}
},
axisTick: {
show: false
},
axisLine: {
show: false,
lineStyle: {
fontSize: 12,
color: "#D4D4D4"
}
}
}
],
grid: {
top: "10%",
left: "5%",
right: "5%",
bottom: "3%",
containLabel: true
},
yAxis: [
{
type: "category",
offset: 0,
inverse: true, //升序
// axisLabel: {
// color: "#787E95",
// fontSize: 16
// },
data: ["契约锁", "泛微北京", "泛微上海", "产品研发中心", "泛微华南一部", "泛微广州一部", "泛微杭州", "EBU事业一部"],
axisPointer: {
type: "shadow"
},
axisLine: {
//这是x轴文字颜色
show: false
},
axisTick: {
show: false
},
axisLabel: {
color: "#787E95",
fontSize: 14,
formatter: function (value: any, index: number) {
if (index == 0) {
return "{one|" + "} {a| " + value + "}";
} else if (index == 1) {
return "{two|" + "} {a|" + value + "}";
} else if (index == 2) {
return "{three|" + "} {a|" + value + "}";
}
return value;
},
rich: {
a: {
color: "#787E95",
fontSize: 14
},
// 第一名
one: {
width: 40,
height: 20,
margin: 10,
align: "center",
backgroundColor: {
image: require("../../../../assets/images/one.png")
}
},
two: {
width: 40,
height: 20,
margin: 10,
align: "center",
backgroundColor: {
image: require("../../../../assets/images/two.png")
}
},
three: {
width: 40,
height: 20,
marginRight: 20,
align: "center",
backgroundColor: {
image: require("../../../../assets/images/three.png")
}
}
}
}
},
{
type: "category",
data: [4096, 1412, 1337, 788, 788, 347, 332, 291],
inverse: true,//数组翻转显示
axisTick: {
alignWithLabel: true,
show: false
},
axisLine: {
show: false//不显示y轴的线
},
axisLabel: {
textStyle: {
fontSize: 16,
color: function (params: any, index: any) {
return colorList[index];
}
}
}
}
],
series: [
{
data: _.map([4096, 1412, 1337, 788, 788, 347, 332, 291], (it, idx) => ({
value: it,
itemStyle: {
color: colorList[idx], //设定单个柱子颜色
borderColor: colorList[idx], //设定单个柱子边框颜色
barBorderRadius: [10, 10, 10, 10]
}
})),
type: "bar",
barWidth: "20%",
realtimeSort: true,
yAxisIndex: 0,
label: {
normal: {
show: false,
position: "right",
fontWeight: "bold",
fontSize: 14
}
},
showBackground: true,
backgroundStyle: {
color: "rgba(220, 220, 220, 0.8)",
barBorderRadius: [10, 10, 10, 10]
}
},
{
name: "",
type: "bar",
yAxisIndex: 1,//使两个柱状图重合的效果
barWidth: "25%",
data: _.map([5000, 5000, 5000, 5000, 5000, 5000, 5000, 5000], (it, idx) => ({
value: it,
itemStyle: {
color: "none",
borderColor: colorList[idx],
borderWidth: 2,
barBorderRadius: [10, 10, 10, 10]
}
})),
label: {
show: false
}
}
]
};
// 配置项给实例对象
myChart.setOption(option);
return myChart;
};
export default RankingBar;

@ -0,0 +1,68 @@
/*
* Author:
* name:
* Description:
* Date: 2023/5/5
*/
import React, { FC } from "react";
import { TinyArea } from "@ant-design/plots";
import styles from "./index.less";
import cs from "classnames";
interface OwnProps {
title: string;
active: number;
number: string;
unit: string;
color: string;
tabList: Array<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 StructureCard: FC<Props> = (props) => {
const { active, tabList, title, number, unit, color } = props;
return (
<div className={styles.structureCardWrapper}>
<div className={styles.top}>
<span>{title}</span>
<ul>
{
_.map(tabList, (it, idx) => {
const classes = cs({
[styles["active"]]: idx === active
});
return <li key={it} className={classes}>{it}</li>;
})
}
</ul>
</div>
<div className={styles.center}>
<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>
<div className={styles.bottom}>
<span><span></span><span>4.75%</span></span>
<span><span></span><span>1.75%</span></span>
</div>
</div>
);
};
export default StructureCard;

@ -0,0 +1,48 @@
/*
* Author:
* name:
* Description:
* Date: 2023/5/5
*/
import React, { FC, useState } from "react";
import styles from "./index.less";
import cs from "classnames";
interface OwnProps {
title: string;
children: any;
tabList?: Array<any>;
}
type Props = OwnProps;
const StructureFrame: FC<Props> = (props) => {
const { children, tabList, title } = props;
const [active, setActive] = useState<number>(0);
return (
<div className={styles.structureFrameWrapper}>
<div className={styles.header}>
<img src={require("../../../../assets/images/frame_icon.png")} alt=""/>
<span>{title}</span>
</div>
{
!_.isEmpty(tabList) &&
<ul className={styles.tabBox}>
{
_.map(tabList, (it, idx) => {
const classes = cs({
[styles["active"]]: idx === active
});
return <li key={it} className={classes} onClick={() => setActive(idx)}>{it}</li>;
})
}
</ul>
}
{children}
</div>
);
};
export default StructureFrame
;

@ -0,0 +1,111 @@
.structureWrapper {
display: flex;
flex-direction: column;
width: 100%;
height: 100%;
background: #e5e5e5;
overflow: auto;
.structureColumn_1 {
display: flex;
margin: 0 0 10px;
padding: 0;
& > li:not(:last-child) {
margin-right: 16px;
}
& > li {
flex: 1;
border-radius: 5px;
background: #FFF;
max-height: 160px;
min-height: 100px;
padding: 16px;
}
}
.structureColumn_2 {
margin-bottom: 10px;
.multipleCharts {
height: 330px;
display: flex;
& > div:not(:last-child) {
div {
border-right: .5px solid #e5e5e5;
}
}
& > div {
flex: 1;
display: flex;
flex-direction: column;
span {
display: inline-block;
width: 100%;
text-align: center;
font-size: 16px;
color: #333333;
font-weight: 700;
margin-top: 20px;
}
div {
flex: 1;
}
}
}
}
.structureColumn_3 {
display: flex;
& > div:first-child {
flex: 4 1;
margin-right: 16px;
}
& > div:last-child {
flex: 2 1;
}
.monthlyTrendsChartsBox {
width: 100%;
min-height: 438px;
max-height: 438px;
}
.rankingChartsBox {
width: 100%;
min-height: 480px;
max-height: 480px;
height: 480px;
:global {
.dv-capsule-chart {
width: 100%;
height: 100%;
.capsule-container {
.unit-label {
font-size: 12px;
color: #D4D4D4;
text-align: right;
font-weight: 400;
}
}
.label-column {
font-size: 16px;
color: #787E95;
text-align: right;
font-weight: 400;
}
}
}
}
}
}

@ -0,0 +1,99 @@
/*
* Author:
* name:
* Description:
* Date: 2023/4/27
*/
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";
import RankingBar from "./components/rankingCharts";
import AgeStaticsPie from "./components/ageStaticsCharts";
import GenderStaticsPie from "./components/genderStaticsCharts";
import LineStaticsPie from "./components/lineStaticsCharts";
import InternStaticsBar from "./components/internStaticsCharts";
import EducationStaticsRedar from "./components/educationStaticsCharts";
import { structureCardList } from "../constants";
import styles from "./index.less";
interface OwnProps {
}
type Props = OwnProps;
const index: FC<Props> = (props) => {
const [card, setCard] = useState<any>(structureCardList);
useEffect(() => {
const monthlyTrendsBar = MonthlyTrendsBar(echarts);
const rankingBar = RankingBar(echarts);
const ageStaticsPie = AgeStaticsPie(echarts);
const genderStaticsPie = GenderStaticsPie(echarts);
const lineStaticsPie = LineStaticsPie(echarts);
const internStaticsBar = InternStaticsBar(echarts);
const educationStaticsRedar = EducationStaticsRedar(echarts);
// 屏幕缩放对chart图表进行自适应处理调用实例的resize方法
window.onresize = () => {
monthlyTrendsBar.resize();
rankingBar.resize();
ageStaticsPie.resize();
genderStaticsPie.resize();
lineStaticsPie.resize();
internStaticsBar.resize();
educationStaticsRedar.resize();
};
return () => {
};
}, []);
return (
<div className={styles.structureWrapper}>
<ul className={styles.structureColumn_1}>
{
_.map(card, (it, idx) => {
return <li key={idx}><StructureCard {...it}/></li>;
})
}
</ul>
<div className={styles.structureColumn_2}>
<StructureFrame title="人员结构统计">
<div className={styles.multipleCharts}>
<div><span></span>
<div id="ageStatics"/>
</div>
<div><span></span>
<div id="genderStatics"/>
</div>
<div><span></span>
<div id="educationStatics"/>
</div>
<div><span>线</span>
<div id="lineStatics"/>
</div>
<div><span></span>
<div id="internStatics"/>
</div>
</div>
</StructureFrame>
</div>
<div className={styles.structureColumn_3}>
<div>
<StructureFrame title="人员流动情况月度趋势" tabList={["今年", "去年"]}>
<div className={styles.monthlyTrendsChartsBox} id="monthlyTrendsCharts"/>
</StructureFrame>
</div>
<div>
<StructureFrame title="机构人员排名数">
<div className={styles.rankingChartsBox} id="rankingCharts"/>
</StructureFrame>
</div>
</div>
</div>
);
};
export default index;

@ -0,0 +1,381 @@
/*
* ECharts 组件基础部分
* 传入 option 和渲染方式 renderer
* */
import React, { PureComponent } from "react";
import * as echarts from "echarts";
import "zrender/lib/svg/svg";
import { debounce } from "./common"; // 一个节流函数
export default class Chart extends PureComponent {
constructor(props) {
super(props);
this.state = {
width: "100%",
height: "100%"
};
this.chart = null;
}
async componentDidMount() {
// 初始化图表
await this.initChart(this.el);
// 将传入的配置(包含数据)注入
this.setOption(this.props.option);
// 监听屏幕缩放,重新绘制 echart 图表
window.addEventListener("resize", debounce(this.resize, 100));
}
componentDidUpdate() {
// 每次更新组件都重置
this.setOption(this.props.option);
}
componentWillUnmount() {
// 组件卸载前卸载图表
this.dispose();
}
render() {
const { width, height } = this.state;
return (
<div
className="default-chart"
ref={el => (this.el = el)}
style={{ width, height }}
/>
);
}
initChart = el => {
// renderer 用于配置渲染方式 可以是 svg 或者 canvas
const renderer = this.props.renderer || "canvas";
return new Promise(resolve => {
setTimeout(() => {
this.chart = echarts.init(el, null, {
renderer,
width: "auto",
height: "auto"
});
resolve();
}, 0);
});
};
setOption = option => {
if (!this.chart) {
return;
}
const notMerge = this.props.notMerge;
const lazyUpdate = this.props.lazyUpdate;
this.chart.setOption(option, notMerge, lazyUpdate);
};
dispose = () => {
if (!this.chart) {
return;
}
this.chart.dispose();
this.chart = null;
};
resize = () => {
this.chart && this.chart.resize();
};
getInstance = () => {
return this.chart;
};
}
/**
* 绘制3d图
* @param pieData 总数据
* @param internalDiameterRatio:透明的空心占比
* @param distance 视角到主体的距离
* @param alpha 旋转角度
* @param pieHeight 立体的高度
* @param opacity 饼或者环的透明度
* @param isSameHeight 饼或者环的透明度
*/
const getPie3D = (
pieData,
internalDiameterRatio,
distance,
alpha,
pieHeight,
opacity = 1,
isSameHeight
) => {
const series = [];
let sumValue = 0;
let startValue = 0;
let endValue = 0;
let legendData = [];
let legendBfb = [];
const k = 1 - internalDiameterRatio;
pieData.sort((a, b) => {
return b.value - a.value;
});
// 为每一个饼图数据,生成一个 series-surface 配置
for (let i = 0; i < pieData.length; i++) {
sumValue += pieData[i].value;
const seriesItem = {
name:
typeof pieData[i].name === "undefined" ? `series${i}` : pieData[i].name,
type: "surface",
parametric: true,
wireframe: {
show: false
},
pieData: pieData[i],
pieStatus: {
selected: false,
hovered: false,
k: k
},
center: ["10%", "50%"]
};
if (typeof pieData[i].itemStyle !== "undefined") {
const itemStyle = {};
itemStyle.color =
typeof pieData[i].itemStyle.color !== "undefined"
? pieData[i].itemStyle.color
: opacity;
itemStyle.opacity =
typeof pieData[i].itemStyle.opacity !== "undefined"
? pieData[i].itemStyle.opacity
: opacity;
seriesItem.itemStyle = itemStyle;
}
series.push(seriesItem);
}
// 使用上一次遍历时,计算出的数据和 sumValue调用 getParametricEquation 函数,
// 向每个 series-surface 传入不同的参数方程 series-surface.parametricEquation也就是实现每一个扇形。
legendData = [];
legendBfb = [];
for (let i = 0; i < series.length; i++) {
endValue = startValue + series[i].pieData.value;
series[i].pieData.startRatio = startValue / sumValue;
series[i].pieData.endRatio = endValue / sumValue;
series[i].parametricEquation = getParametricEquation(
series[i].pieData.startRatio,
series[i].pieData.endRatio,
false,
false,
k,
!isSameHeight && series[i].pieData.value
);
startValue = endValue;
const bfb = fomatFloat(series[i].pieData.value / sumValue, 4);
legendData.push({
name: series[i].name,
value: bfb
});
legendBfb.push({
name: series[i].name,
value: bfb
});
}
const boxHeight = getHeight3D(series, pieHeight); // 通过pieHeight设定3d饼/环的高度单位是px
// 准备待返回的配置项,把准备好的 legendData、series 传入。
const option = {
legend: {
show: false,
data: legendData,
orient: "vertical",
left: 10,
top: 10,
itemGap: 10,
textStyle: {
color: "#A1E2FF"
},
icon: "circle",
formatter: function (param) {
const item = legendBfb.filter((item) => item.name === param)[0];
const bfs = fomatFloat(item.value * 100, 2) + "%";
return `${item.name} ${bfs}`;
}
},
labelLine: {
show: true,
lineStyle: {
color: "#fff"
}
},
label: {
show: true,
position: "outside",
formatter: "{b} \n{c} {d}%"
},
tooltip: {
backgroundColor: "#033b77",
borderColor: "#21f2c4",
textStyle: {
color: "#fff",
fontSize: 13
},
formatter: (params) => {
if (
params.seriesName !== "mouseoutSeries" &&
params.seriesName !== "信用评价"
) {
// console.log(option.series, params.seriesName, "option.series[params.seriesIndex].pieData");
const bfb = (
(option.series[params.seriesIndex].pieData.endRatio -
option.series[params.seriesIndex].pieData.startRatio) *
100
).toFixed(2);
return (
// ${params.seriesName}
`<span style="display:inline-block; width: 100%;text-align: center; color: #fff">数量</span><br/>` +
`<span style="display:inline-block;margin-right:5px;border-radius:10px;width:10px;height:10px;background-color:${params.color};"></span>` +
`<span style="color: #3EA1FF">${option.series[params.seriesIndex].pieData.value}${bfb}%</span>`
);
}
}
},
xAxis3D: {
min: -1,
max: 1
},
yAxis3D: {
min: -1,
max: 1
},
zAxis3D: {
min: -1,
max: 1
},
grid3D: {
show: false,
left: "18%",
boxHeight: !isSameHeight ? boxHeight : 15, // 圆环的高度
viewControl: {
// 3d效果可以放大、旋转等请自己去查看官方配置
alpha, // 角度
distance, // 调整视角到主体的距离类似调整zoom
rotateSensitivity: 0, // 设置为0无法旋转
zoomSensitivity: 0, // 设置为0无法缩放
panSensitivity: 0, // 设置为0无法平移
autoRotate: false // 自动旋转
}
},
series: series
};
return option;
};
/**
* 生成扇形的曲面参数方程用于 series-surface.parametricEquation
*/
const getParametricEquation = (
startRatio,
endRatio,
isSelected,
isHovered,
k,
h = 1
) => {
// 计算
const midRatio = (startRatio + endRatio) / 2;
const startRadian = startRatio * Math.PI * 2;
const endRadian = endRatio * Math.PI * 2;
const midRadian = midRatio * Math.PI * 2;
// 如果只有一个扇形,则不实现选中效果。
if (startRatio === 0 && endRatio === 1) {
isSelected = false;
}
// 通过扇形内径/外径的值,换算出辅助参数 k默认值 1/3
k = typeof k !== "undefined" ? k : 1 / 3;
// 计算选中效果分别在 x 轴、y 轴方向上的位移(未选中,则位移均为 0
const offsetX = isSelected ? Math.cos(midRadian) * 0.1 : 0;
const offsetY = isSelected ? Math.sin(midRadian) * 0.1 : 0;
// 计算高亮效果的放大比例(未高亮,则比例为 1
const hoverRate = isHovered ? 1.05 : 1;
// 返回曲面参数方程
return {
u: {
min: -Math.PI,
max: Math.PI * 3,
step: Math.PI / 32
},
v: {
min: 0,
max: Math.PI * 2,
step: Math.PI / 20
},
x: function (u, v) {
if (u < startRadian) {
return (
offsetX + Math.cos(startRadian) * (1 + Math.cos(v) * k) * hoverRate
);
}
if (u > endRadian) {
return (
offsetX + Math.cos(endRadian) * (1 + Math.cos(v) * k) * hoverRate
);
}
return offsetX + Math.cos(u) * (1 + Math.cos(v) * k) * hoverRate;
},
y: function (u, v) {
if (u < startRadian) {
return (
offsetY + Math.sin(startRadian) * (1 + Math.cos(v) * k) * hoverRate
);
}
if (u > endRadian) {
return (
offsetY + Math.sin(endRadian) * (1 + Math.cos(v) * k) * hoverRate
);
}
return offsetY + Math.sin(u) * (1 + Math.cos(v) * k) * hoverRate;
},
z: function (u, v) {
if (u < -Math.PI * 0.5) {
return Math.sin(u);
}
if (u > Math.PI * 2.5) {
return Math.sin(u) * h * 0.1;
}
return Math.sin(v) > 0 ? 1 * h * 0.1 : -1;
}
};
};
/**
* 获取3d丙图的最高扇区的高度
*/
const getHeight3D = (series, height) => {
series.sort((a, b) => {
return b.pieData.value - a.pieData.value;
});
return (height * 25) / series[0].pieData.value;
};
/**
* 格式化浮点数
*/
const fomatFloat = (num, n) => {
let f = parseFloat(num);
if (isNaN(f)) {
return false;
}
f = Math.round(num * Math.pow(10, n)) / Math.pow(10, n); // n 幂
let s = f.toString();
let rs = s.indexOf(".");
// 判定如果是整数增加小数点再补0
if (rs < 0) {
rs = s.length;
s += ".";
}
while (s.length <= rs + n) {
s += "0";
}
return s;
};
export { getPie3D, getParametricEquation };

@ -60,6 +60,75 @@ export const exceptStr = (str) => {
return {};
}
};
/**
* @param {date} time 需要转换的时间
* @param {String} fmt 需要转换的格式 yyyy-MM-ddyyyy-MM-dd HH:mm:ss
*/
export const formatTime = (time, fmt) => {
if (!time) return "";
else {
const date = new Date(time);
const o = {
"M+": date.getMonth() + 1,
"d+": date.getDate(),
"H+": date.getHours(),
"m+": date.getMinutes(),
"s+": date.getSeconds(),
"q+": Math.floor((date.getMonth() + 3) / 3),
S: date.getMilliseconds()
};
if (/(y+)/.test(fmt))
fmt = fmt.replace(
RegExp.$1,
(date.getFullYear() + "").substr(4 - RegExp.$1.length)
);
for (const k in o) {
if (new RegExp("(" + k + ")").test(fmt)) {
fmt = fmt.replace(
RegExp.$1,
RegExp.$1.length === 1
? o[k]
: ("00" + o[k]).substr(("" + o[k]).length)
);
}
}
return fmt;
}
};
export const debounce = (func, wait, immediate) => {
let timeout, args, context, timestamp, result;
const later = function () {
// 据上一次触发时间间隔
const last = +new Date() - timestamp;
// 上次被包装函数被调用时间间隔 last 小于设定时间间隔 wait
if (last < wait && last > 0) {
timeout = setTimeout(later, wait - last);
} else {
timeout = null;
// 如果设定为immediate===true因为开始边界已经调用过了此处无需调用
if (!immediate) {
result = func.apply(context, args);
if (!timeout) context = args = null;
}
}
};
return function (...args) {
context = this;
timestamp = +new Date();
const callNow = immediate && !timeout;
// 如果延时不存在,重新设定延时
if (!timeout) timeout = setTimeout(later, wait);
if (callNow) {
result = func.apply(context, args);
context = args = null;
}
return result;
};
};
export const paginationFun = (tableListPageObj, sizeChange, onChange) => {
return {
@ -76,8 +145,8 @@ export const paginationFun = (tableListPageObj, sizeChange, onChange) => {
onChange({
pageNum: page,
size,
total,
total
});
},
}
};
};

@ -0,0 +1,147 @@
(function(win, lib) {
const doc = win.document;
const docEl = doc.documentElement;
let metaEl = doc.querySelector("meta[name=\"viewport\"]");
const flexibleEl = doc.querySelector("meta[name=\"flexible\"]");
let dpr = 0;
let scale = 0;
let tid;
const flexible = lib.flexible || (lib.flexible = {});
if (metaEl) {
console.warn("将根据已有的meta标签来设置缩放比例");
const match = metaEl
.getAttribute("content")
// eslint-disable-next-line no-useless-escape
.match(/initial\-scale=([\d\.]+)/);
if (match) {
scale = parseFloat(match[1]);
dpr = parseInt(1 / scale);
}
} else if (flexibleEl) {
const content = flexibleEl.getAttribute("content");
if (content) {
// eslint-disable-next-line no-useless-escape
const initialDpr = content.match(/initial\-dpr=([\d\.]+)/);
// eslint-disable-next-line no-useless-escape
const maximumDpr = content.match(/maximum\-dpr=([\d\.]+)/);
if (initialDpr) {
dpr = parseFloat(initialDpr[1]);
scale = parseFloat((1 / dpr).toFixed(2));
}
if (maximumDpr) {
dpr = parseFloat(maximumDpr[1]);
scale = parseFloat((1 / dpr).toFixed(2));
}
}
}
if (!dpr && !scale) {
// eslint-disable-next-line no-unused-vars
const isAndroid = win.navigator.appVersion.match(/android/gi);
const isIPhone = win.navigator.appVersion.match(/iphone/gi);
const devicePixelRatio = win.devicePixelRatio;
if (isIPhone) {
// iOS下对于2和3的屏用2倍的方案其余的用1倍方案
if (devicePixelRatio >= 3 && (!dpr || dpr >= 3)) {
dpr = 3;
} else if (devicePixelRatio >= 2 && (!dpr || dpr >= 2)) {
dpr = 2;
} else {
dpr = 1;
}
} else {
// 其他设备下仍旧使用1倍的方案
dpr = 1;
}
scale = 1 / dpr;
}
docEl.setAttribute("data-dpr", dpr);
if (!metaEl) {
metaEl = doc.createElement("meta");
metaEl.setAttribute("name", "viewport");
metaEl.setAttribute(
"content",
"initial-scale=" +
scale +
", maximum-scale=" +
scale +
", minimum-scale=" +
scale +
", user-scalable=no"
);
if (docEl.firstElementChild) {
docEl.firstElementChild.appendChild(metaEl);
} else {
const wrap = doc.createElement("div");
wrap.appendChild(metaEl);
doc.write(wrap.innerHTML);
}
}
function refreshRem() {
let width = docEl.getBoundingClientRect().width;
// 最小1366px最大适配2560px
if (width / dpr < 1366) {
width = 1366 * dpr;
} else if (width / dpr > 2560) {
width = 2560 * dpr;
}
// 设置成24等份设计稿时1920px的这样1rem就是80px
const rem = width / 24;
docEl.style.fontSize = rem + "px";
flexible.rem = win.rem = rem;
}
win.addEventListener(
"resize",
function() {
clearTimeout(tid);
tid = setTimeout(refreshRem, 300);
},
false
);
win.addEventListener(
"pageshow",
function(e) {
if (e.persisted) {
clearTimeout(tid);
tid = setTimeout(refreshRem, 300);
}
},
false
);
if (doc.readyState === "complete") {
doc.body.style.fontSize = 12 * dpr + "px";
} else {
doc.addEventListener(
"DOMContentLoaded",
// eslint-disable-next-line no-unused-vars
function(e) {
doc.body.style.fontSize = 12 * dpr + "px";
},
false
);
}
refreshRem();
flexible.dpr = win.dpr = dpr;
flexible.refreshRem = refreshRem;
flexible.rem2px = function(d) {
let val = parseFloat(d) * this.rem;
if (typeof d === "string" && d.match(/rem$/)) {
val += "px";
}
return val;
};
flexible.px2rem = function(d) {
let val = parseFloat(d) / this.rem;
if (typeof d === "string" && d.match(/px$/)) {
val += "rem";
}
return val;
};
})(window, window["lib"] || (window["lib"] = {}));
Loading…
Cancel
Save