效率管理页面开发
parent
ea9f629397
commit
87325fc3ad
@ -1,10 +1,523 @@
|
||||
@import '~@/utils/utils.less';
|
||||
|
||||
.frameContent {
|
||||
width: 100%;
|
||||
.staffInfoContainer {
|
||||
min-height: 100vh;
|
||||
height: 100vh;
|
||||
border: none;
|
||||
display: block;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
// background-color: #f5f6fa;
|
||||
|
||||
.announcementBar {
|
||||
background: #e6f7ff;
|
||||
border: 1px solid #91d5ff;
|
||||
margin-bottom: 16px;
|
||||
border-radius: 6px;
|
||||
overflow: hidden;
|
||||
|
||||
.announcement {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.announcementLabel {
|
||||
background-color: #fef3c7;
|
||||
color: #92400e;
|
||||
border-radius: 4px;
|
||||
padding: 4px 8px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
margin-right: 12px;
|
||||
white-space: nowrap;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.anticon {
|
||||
margin-right: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
.scrollContainer {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.scrollContent {
|
||||
display: inline-block;
|
||||
animation: scroll 30s linear infinite;
|
||||
color: #666;
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.mainContent {
|
||||
// padding: 12px;
|
||||
|
||||
.contentCard {
|
||||
.ant-card-head {
|
||||
.ant-card-head-title {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.treeCard {
|
||||
height: 600px;
|
||||
border: 1px solid #e8e8e8;
|
||||
border-radius: 8px;
|
||||
|
||||
.treeHeader {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.treeContainer {
|
||||
height: 520px;
|
||||
overflow-y: auto;
|
||||
|
||||
/* 自定义滚动条样式 */
|
||||
&::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background: #d9d9d9;
|
||||
border-radius: 3px;
|
||||
|
||||
&:hover {
|
||||
background: #bfbfbf;
|
||||
}
|
||||
}
|
||||
|
||||
.orgTree {
|
||||
.ant-tree-treenode {
|
||||
padding: 2px 0;
|
||||
|
||||
.ant-tree-node-content-wrapper {
|
||||
border-radius: 4px;
|
||||
transition: all 0.3s ease;
|
||||
padding: 4px 8px;
|
||||
|
||||
&:hover {
|
||||
background-color: #f0f9ff;
|
||||
transform: translateX(2px);
|
||||
}
|
||||
|
||||
&.ant-tree-node-selected {
|
||||
background-color: #e6f7ff !important;
|
||||
box-shadow: 0 2px 8px rgba(24, 144, 255, 0.2);
|
||||
}
|
||||
}
|
||||
|
||||
.ant-tree-iconEle {
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.ant-tree-title {
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Tree节点标题样式 */
|
||||
.tree-node-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
|
||||
.node-title {
|
||||
flex: 1;
|
||||
font-size: 14px;
|
||||
margin-left: 8px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.node-count {
|
||||
color: #999;
|
||||
font-size: 12px;
|
||||
margin-left: auto;
|
||||
background: #f0f0f0;
|
||||
padding: 1px 6px;
|
||||
border-radius: 10px;
|
||||
min-width: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
.searchCard {
|
||||
margin-bottom: 16px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #e8e8e8;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
||||
|
||||
.ant-card-body {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
/* 搜索表单容器样式 */
|
||||
.searchFormContainer {
|
||||
.searchForm {
|
||||
.ant-form-item {
|
||||
margin-bottom: 16px;
|
||||
|
||||
.ant-form-item-label {
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
|
||||
label {
|
||||
color: #333 !important;
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
.ant-input,
|
||||
.ant-select-selector,
|
||||
.ant-picker {
|
||||
border-radius: 6px;
|
||||
border: 1px solid #d9d9d9;
|
||||
transition: all 0.3s ease;
|
||||
|
||||
&:hover {
|
||||
border-color: #4c7bff;
|
||||
}
|
||||
|
||||
&:focus,
|
||||
&.ant-select-focused .ant-select-selector,
|
||||
&.ant-picker-focused {
|
||||
border-color: #2d5cf6;
|
||||
box-shadow: 0 0 0 2px rgba(45, 92, 246, 0.2);
|
||||
}
|
||||
}
|
||||
|
||||
.ant-select-selection-placeholder,
|
||||
.ant-input::placeholder,
|
||||
.ant-picker-input input::placeholder {
|
||||
color: #bfbfbf;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 搜索按钮样式 */
|
||||
.searchButton {
|
||||
background: linear-gradient(135deg, #2d5cf6 0%, #4c7bff 100%);
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
color: white;
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
box-shadow: 0 2px 8px rgba(45, 92, 246, 0.3);
|
||||
transition: all 0.3s ease;
|
||||
height: 32px;
|
||||
padding: 0 16px;
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
background: linear-gradient(135deg, #4c7bff 0%, #6b8fff 100%);
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 4px 12px rgba(45, 92, 246, 0.4);
|
||||
color: white;
|
||||
}
|
||||
|
||||
&:active {
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
/* 重置按钮样式 */
|
||||
.resetButton {
|
||||
background: #fff;
|
||||
border: 1px solid #d9d9d9;
|
||||
color: #666;
|
||||
border-radius: 6px;
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
transition: all 0.3s ease;
|
||||
height: 32px;
|
||||
padding: 0 16px;
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
border-color: #4c7bff;
|
||||
color: #2d5cf6;
|
||||
}
|
||||
|
||||
.anticon {
|
||||
color: #ff7875;
|
||||
}
|
||||
}
|
||||
|
||||
/* 展开按钮样式 */
|
||||
.expandButton {
|
||||
color: #2d5cf6;
|
||||
font-size: 14px;
|
||||
padding: 0 8px;
|
||||
height: 32px;
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
color: #4c7bff;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.actionBar {
|
||||
|
||||
|
||||
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 16px;
|
||||
|
||||
.totalInfo {
|
||||
color: #666;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.exportButton {
|
||||
background: linear-gradient(135deg, #52c41a 0%, #73d13d 100%);
|
||||
border: none;
|
||||
color: white;
|
||||
border-radius: 8px;
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
box-shadow: 0 4px 12px rgba(82, 196, 26, 0.3);
|
||||
transition: all 0.3s ease;
|
||||
// margin-top: -8px;
|
||||
height: 35px;
|
||||
padding: 0 16px;
|
||||
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
background: linear-gradient(135deg, #73d13d 0%, #95de64 100%);
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 6px 20px rgba(82, 196, 26, 0.4);
|
||||
color: white;
|
||||
}
|
||||
|
||||
&:active {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.anticon {
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.addButton {
|
||||
background: linear-gradient(135deg, #fa8c16 0%, #ffa940 100%);
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
box-shadow: 0 4px 12px rgba(250, 140, 22, 0.3);
|
||||
transition: all 0.3s ease;
|
||||
// margin-top: -8px;
|
||||
height: 35px;
|
||||
padding: 0 16px;
|
||||
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
background: linear-gradient(135deg, #ffa940 0%, #ffc069 100%);
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 6px 20px rgba(250, 140, 22, 0.4);
|
||||
}
|
||||
|
||||
&:active {
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.tableCard {
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
||||
padding-bottom: 10px;
|
||||
|
||||
.ant-table-wrapper {
|
||||
max-height: 600px;
|
||||
overflow-y: auto;
|
||||
|
||||
/* 自定义滚动条样式 */
|
||||
&::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-track {
|
||||
background: #f5f5f5;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background: #d9d9d9;
|
||||
border-radius: 4px;
|
||||
|
||||
&:hover {
|
||||
background: #bfbfbf;
|
||||
}
|
||||
}
|
||||
|
||||
.ant-table-thead>tr>th {
|
||||
background-color: #fafafa;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
border-bottom: 1px solid #e8e8e8;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.ant-table-tbody>tr {
|
||||
&:hover>td {
|
||||
background-color: #f5f5f5 !important;
|
||||
}
|
||||
|
||||
>td {
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.paginationWrapper {
|
||||
margin-top: 20px;
|
||||
text-align: right;
|
||||
|
||||
.ant-pagination {
|
||||
.ant-pagination-total-text {
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.ant-pagination-item {
|
||||
border-radius: 4px;
|
||||
|
||||
&.ant-pagination-item-active {
|
||||
background-color: #2d5cf6;
|
||||
border-color: #2d5cf6;
|
||||
}
|
||||
}
|
||||
|
||||
.ant-pagination-prev,
|
||||
.ant-pagination-next {
|
||||
border-radius: 4px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.mainContent {
|
||||
// padding: 12px;
|
||||
|
||||
.contentCard {
|
||||
border: none !important;
|
||||
.ant-card-head {
|
||||
.ant-card-head-title {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.actionBar {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 16px;
|
||||
|
||||
.totalInfo {
|
||||
color: #666;
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
:global {
|
||||
.ant-card-body {
|
||||
padding: 12px 24px 0px 24px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@keyframes scroll {
|
||||
0% {
|
||||
transform: translateX(0);
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
}
|
||||
|
||||
// 响应式设计
|
||||
@media (max-width: 1200px) {
|
||||
.staffInfoContainer {
|
||||
.mainContent {
|
||||
.searchCard {
|
||||
.ant-row {
|
||||
.ant-col {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 自定义主题色
|
||||
.ant-btn-primary {
|
||||
background-color: #2d5cf6;
|
||||
border-color: #2d5cf6;
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
background-color: #4c7bff;
|
||||
border-color: #4c7bff;
|
||||
}
|
||||
}
|
||||
|
||||
.ant-tree .ant-tree-node-selected {
|
||||
background-color: #e6f7ff !important;
|
||||
}
|
||||
|
||||
.ant-select-focused .ant-select-selector,
|
||||
.ant-input-affix-wrapper-focused,
|
||||
.ant-input:focus,
|
||||
.ant-input-focused {
|
||||
border-color: #2d5cf6 !important;
|
||||
box-shadow: 0 0 0 2px rgba(45, 92, 246, 0.2) !important;
|
||||
}
|
||||
|
||||
// 标签样式优化
|
||||
.ant-tag {
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
// 按钮组间距调整
|
||||
.ant-space-item {
|
||||
.ant-btn+.ant-btn {
|
||||
margin-left: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
// 表格链接按钮样式
|
||||
.ant-btn-link {
|
||||
padding: 0 4px;
|
||||
font-size: 12px;
|
||||
}
|
@ -0,0 +1,309 @@
|
||||
import React, { useState } from 'react';
|
||||
import { Button, Col, Form, Input, Row, Select, Space, DatePicker, InputNumber, TreeSelect } from 'antd';
|
||||
import { ClearOutlined, SearchOutlined, ExpandOutlined, CompressOutlined, UserOutlined, TeamOutlined, ClockCircleOutlined, NumberOutlined } from '@ant-design/icons';
|
||||
import styles from "../WorkReport.less";
|
||||
|
||||
const { Option } = Select;
|
||||
const { RangePicker } = DatePicker;
|
||||
|
||||
// 组织架构树数据
|
||||
const treeData = [
|
||||
{
|
||||
title: '飞利信科技有限公司',
|
||||
value: '0-0',
|
||||
key: '0-0',
|
||||
children: [
|
||||
{
|
||||
title: '技术部',
|
||||
value: '技术部',
|
||||
key: '0-0-0',
|
||||
children: [
|
||||
{ title: '前端组', value: '前端组', key: '0-0-0-0' },
|
||||
{ title: '后端组', value: '后端组', key: '0-0-0-1' },
|
||||
{ title: '测试组', value: '测试组', key: '0-0-0-2' }
|
||||
],
|
||||
},
|
||||
{
|
||||
title: '产品部',
|
||||
value: '产品部',
|
||||
key: '0-0-1',
|
||||
children: [
|
||||
{ title: '产品设计组', value: '产品设计组', key: '0-0-1-0' },
|
||||
{ title: '用户体验组', value: '用户体验组', key: '0-0-1-1' },
|
||||
{ title: '产品运营组', value: '产品运营组', key: '0-0-1-2' }
|
||||
],
|
||||
},
|
||||
{
|
||||
title: '运营部',
|
||||
value: '运营部',
|
||||
key: '0-0-2',
|
||||
children: [
|
||||
{ title: '市场营销组', value: '市场营销组', key: '0-0-2-0' },
|
||||
{ title: '客户服务组', value: '客户服务组', key: '0-0-2-1' },
|
||||
{ title: '商务合作组', value: '商务合作组', key: '0-0-2-2' }
|
||||
],
|
||||
},
|
||||
{
|
||||
title: '财务部',
|
||||
value: '财务部',
|
||||
key: '0-0-3',
|
||||
children: [
|
||||
{ title: '会计组', value: '会计组', key: '0-0-3-0' },
|
||||
{ title: '审计组', value: '审计组', key: '0-0-3-1' }
|
||||
],
|
||||
},
|
||||
{
|
||||
title: '人事部',
|
||||
value: '人事部',
|
||||
key: '0-0-4',
|
||||
children: [
|
||||
{ title: 'HR专员组', value: 'HR专员组', key: '0-0-4-0' },
|
||||
{ title: '招聘组', value: '招聘组', key: '0-0-4-1' },
|
||||
{ title: '培训组', value: '培训组', key: '0-0-4-2' },
|
||||
{ title: '薪酬组', value: '薪酬组', key: '0-0-4-3' }
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const WorkReportRenderSimpleForm = (props) => {
|
||||
const [form] = Form.useForm();
|
||||
const [expandForm, setExpandForm] = useState(false);
|
||||
const { handleSearch, handleFormReset, params } = props;
|
||||
|
||||
React.useEffect(() => {
|
||||
form.setFieldsValue({
|
||||
workType: params?.workType,
|
||||
workPerson: params?.workPerson,
|
||||
department: params?.department,
|
||||
workDateRange: params?.workDateRange,
|
||||
durationMin: params?.durationMin,
|
||||
durationMax: params?.durationMax,
|
||||
quantityMin: params?.quantityMin,
|
||||
quantityMax: params?.quantityMax,
|
||||
});
|
||||
}, [params]);
|
||||
|
||||
const onFinish = values => {
|
||||
const searchParams = {
|
||||
...values,
|
||||
startDate: values.workDateRange ? values.workDateRange[0]?.format('YYYY-MM-DD') : undefined,
|
||||
endDate: values.workDateRange ? values.workDateRange[1]?.format('YYYY-MM-DD') : undefined,
|
||||
};
|
||||
delete searchParams.workDateRange;
|
||||
|
||||
handleSearch && handleSearch(searchParams);
|
||||
};
|
||||
|
||||
const myhandleFormReset = () => {
|
||||
form.resetFields();
|
||||
handleFormReset && handleFormReset();
|
||||
};
|
||||
|
||||
const toggleExpandForm = () => {
|
||||
setExpandForm(!expandForm);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={styles.searchFormContainer}>
|
||||
<Form
|
||||
form={form}
|
||||
onFinish={onFinish}
|
||||
layout="vertical"
|
||||
className={styles.searchForm}
|
||||
>
|
||||
<Row gutter={16}>
|
||||
<Col span={6}>
|
||||
<Form.Item
|
||||
name="workType"
|
||||
label="作业类型"
|
||||
>
|
||||
<Select
|
||||
placeholder="请选择作业类型"
|
||||
allowClear
|
||||
>
|
||||
<Option value="">全部</Option>
|
||||
<Option value="招聘面试">招聘面试</Option>
|
||||
<Option value="培训组织">培训组织</Option>
|
||||
<Option value="绩效考核">绩效考核</Option>
|
||||
<Option value="薪酬核算">薪酬核算</Option>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col span={6}>
|
||||
<Form.Item
|
||||
name="workPerson"
|
||||
label="作业人员"
|
||||
>
|
||||
<Input
|
||||
placeholder="请输入作业人员姓名"
|
||||
allowClear
|
||||
prefix={<UserOutlined />}
|
||||
/>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col span={6}>
|
||||
<Form.Item
|
||||
name="department"
|
||||
label="部门"
|
||||
>
|
||||
<TreeSelect
|
||||
placeholder="请选择部门"
|
||||
treeData={treeData}
|
||||
style={{ width: '100%' }}
|
||||
dropdownStyle={{ maxHeight: 400, overflow: 'auto' }}
|
||||
treeDefaultExpandAll
|
||||
showSearch
|
||||
treeNodeFilterProp="title"
|
||||
allowClear
|
||||
suffixIcon={<TeamOutlined />}
|
||||
/>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col span={6}>
|
||||
<Form.Item label=" " colon={false}>
|
||||
<Space size="middle">
|
||||
<Button
|
||||
type="primary"
|
||||
htmlType="submit"
|
||||
icon={<SearchOutlined />}
|
||||
className={styles.searchButton}
|
||||
>
|
||||
查询
|
||||
</Button>
|
||||
<Button
|
||||
onClick={myhandleFormReset}
|
||||
icon={<ClearOutlined />}
|
||||
className={styles.resetButton}
|
||||
>
|
||||
重置
|
||||
</Button>
|
||||
<Button
|
||||
type="link"
|
||||
onClick={toggleExpandForm}
|
||||
icon={expandForm ? <CompressOutlined /> : <ExpandOutlined />}
|
||||
className={styles.expandButton}
|
||||
>
|
||||
{expandForm ? '收起' : '展开'}
|
||||
</Button>
|
||||
</Space>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
{/* 展开的表单项 */}
|
||||
{expandForm && (
|
||||
<Row gutter={16}>
|
||||
<Col span={6}>
|
||||
<Form.Item
|
||||
name="workDateRange"
|
||||
label="作业时间"
|
||||
>
|
||||
<RangePicker
|
||||
placeholder={['开始日期', '结束日期']}
|
||||
style={{ width: '100%' }}
|
||||
allowClear
|
||||
/>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col span={6}>
|
||||
<Form.Item
|
||||
label="持续时长(小时)"
|
||||
>
|
||||
<Input.Group compact>
|
||||
<Form.Item
|
||||
name="durationMin"
|
||||
noStyle
|
||||
>
|
||||
<InputNumber
|
||||
placeholder="最少时长"
|
||||
min={0}
|
||||
max={24}
|
||||
step={0.5}
|
||||
style={{ width: '48%' }}
|
||||
/>
|
||||
</Form.Item>
|
||||
<span style={{
|
||||
display: 'inline-block',
|
||||
width: '4%',
|
||||
textAlign: 'center',
|
||||
lineHeight: '32px'
|
||||
}}>
|
||||
-
|
||||
</span>
|
||||
<Form.Item
|
||||
name="durationMax"
|
||||
noStyle
|
||||
>
|
||||
<InputNumber
|
||||
placeholder="最多时长"
|
||||
min={0}
|
||||
max={24}
|
||||
step={0.5}
|
||||
style={{ width: '48%' }}
|
||||
/>
|
||||
</Form.Item>
|
||||
</Input.Group>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col span={6}>
|
||||
<Form.Item
|
||||
label="作业数量"
|
||||
>
|
||||
<Input.Group compact>
|
||||
<Form.Item
|
||||
name="quantityMin"
|
||||
noStyle
|
||||
>
|
||||
<InputNumber
|
||||
placeholder="最少数量"
|
||||
min={0}
|
||||
max={100}
|
||||
style={{ width: '48%' }}
|
||||
/>
|
||||
</Form.Item>
|
||||
<span style={{
|
||||
display: 'inline-block',
|
||||
width: '4%',
|
||||
textAlign: 'center',
|
||||
lineHeight: '32px'
|
||||
}}>
|
||||
-
|
||||
</span>
|
||||
<Form.Item
|
||||
name="quantityMax"
|
||||
noStyle
|
||||
>
|
||||
<InputNumber
|
||||
placeholder="最多数量"
|
||||
min={0}
|
||||
max={100}
|
||||
style={{ width: '48%' }}
|
||||
/>
|
||||
</Form.Item>
|
||||
</Input.Group>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col span={6}>
|
||||
<Form.Item
|
||||
name="timeRange"
|
||||
label="时间段筛选"
|
||||
>
|
||||
<Select placeholder="请选择时间段" allowClear>
|
||||
<Option value="morning">上午(8:00-12:00)</Option>
|
||||
<Option value="afternoon">下午(12:00-18:00)</Option>
|
||||
<Option value="evening">晚上(18:00-22:00)</Option>
|
||||
<Option value="fullday">全天</Option>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
</Row>
|
||||
)}
|
||||
</Form>
|
||||
</div>
|
||||
);
|
||||
|
||||
};
|
||||
|
||||
export default WorkReportRenderSimpleForm;
|
@ -1,14 +1,607 @@
|
||||
import React, {Fragment, PureComponent} from 'react';
|
||||
import React, { PureComponent } from 'react';
|
||||
import {
|
||||
Card,
|
||||
Tree,
|
||||
Button,
|
||||
Select,
|
||||
Space,
|
||||
Row,
|
||||
Col,
|
||||
Pagination,
|
||||
Modal
|
||||
} from 'antd';
|
||||
import { history } from 'umi';
|
||||
import {
|
||||
ExpandOutlined,
|
||||
UserOutlined,
|
||||
TeamOutlined,
|
||||
ApartmentOutlined,
|
||||
SyncOutlined,
|
||||
DollarOutlined,
|
||||
SettingOutlined,
|
||||
DownloadOutlined,
|
||||
UserAddOutlined,
|
||||
EditOutlined,
|
||||
DeleteOutlined,
|
||||
EyeOutlined
|
||||
} from '@ant-design/icons';
|
||||
import styles from './SalaryData.less';
|
||||
import StandardTable from '@/components/StandardTable';
|
||||
import SalaryDataAdd from './form/SalaryDataAdd';
|
||||
import SalaryDataRenderSimpleForm from "./form/SalaryDataRenderSimpleForm" //表单
|
||||
|
||||
const { Option } = Select;
|
||||
|
||||
class SalaryData extends PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
searchForm: {},
|
||||
selectedKeys: [],
|
||||
expandedKeys: ['0-0', '0-0-0', '0-0-1', '0-0-2'],
|
||||
addModalVisible: false, // 新增弹窗显示状态
|
||||
addLoading: false, // 新增loading状态
|
||||
organizationData: [
|
||||
{
|
||||
title: '飞利信科技有限公司',
|
||||
key: '0-0',
|
||||
count: 356,
|
||||
children: [
|
||||
{
|
||||
title: '技术部',
|
||||
key: '0-0-0',
|
||||
count: 120,
|
||||
children: [
|
||||
{ title: '前端组', key: '0-0-0-0', count: 45 },
|
||||
{ title: '后端组', key: '0-0-0-1', count: 52 },
|
||||
{ title: '测试组', key: '0-0-0-2', count: 23 }
|
||||
],
|
||||
},
|
||||
{
|
||||
title: '产品部',
|
||||
key: '0-0-1',
|
||||
count: 68,
|
||||
children: [
|
||||
{ title: '产品设计组', key: '0-0-1-0', count: 28 },
|
||||
{ title: '用户体验组', key: '0-0-1-1', count: 25 },
|
||||
{ title: '产品运营组', key: '0-0-1-2', count: 15 }
|
||||
],
|
||||
},
|
||||
{
|
||||
title: '运营部',
|
||||
key: '0-0-2',
|
||||
count: 52,
|
||||
children: [
|
||||
{ title: '市场营销组', key: '0-0-2-0', count: 22 },
|
||||
{ title: '客户服务组', key: '0-0-2-1', count: 18 },
|
||||
{ title: '商务合作组', key: '0-0-2-2', count: 12 }
|
||||
],
|
||||
},
|
||||
{
|
||||
title: '财务部',
|
||||
key: '0-0-3',
|
||||
count: 32,
|
||||
children: [
|
||||
{ title: '会计组', key: '0-0-3-0', count: 18 },
|
||||
{ title: '审计组', key: '0-0-3-1', count: 14 }
|
||||
],
|
||||
},
|
||||
{
|
||||
title: '人事部',
|
||||
key: '0-0-4',
|
||||
count: 84,
|
||||
children: [
|
||||
{ title: 'HR专员组', key: '0-0-4-0', count: 25 },
|
||||
{ title: '招聘组', key: '0-0-4-1', count: 22 },
|
||||
{ title: '培训组', key: '0-0-4-2', count: 18 },
|
||||
{ title: '薪酬组', key: '0-0-4-3', count: 19 }
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
tableData: [
|
||||
{
|
||||
key: '1',
|
||||
id: 1,
|
||||
name: '张三',
|
||||
gender: '男',
|
||||
age: 28,
|
||||
department: '技术部',
|
||||
position: '高级工程师',
|
||||
month: '2025-08',
|
||||
preTaxSalary: 15000,
|
||||
afterTaxSalary: 12850,
|
||||
baseSalary: 10000,
|
||||
floatingSalary: 5000
|
||||
},
|
||||
{
|
||||
key: '2',
|
||||
id: 2,
|
||||
name: '李四',
|
||||
gender: '男',
|
||||
age: 30,
|
||||
department: '产品部',
|
||||
position: '产品经理',
|
||||
month: '2025-08',
|
||||
preTaxSalary: 18000,
|
||||
afterTaxSalary: 15200,
|
||||
baseSalary: 12000,
|
||||
floatingSalary: 6000
|
||||
},
|
||||
{
|
||||
key: '3',
|
||||
id: 3,
|
||||
name: '王五',
|
||||
gender: '女',
|
||||
age: 25,
|
||||
department: '运营部',
|
||||
position: '运营专员',
|
||||
month: '2025-08',
|
||||
preTaxSalary: 8000,
|
||||
afterTaxSalary: 7200,
|
||||
baseSalary: 6000,
|
||||
floatingSalary: 2000
|
||||
},
|
||||
{
|
||||
key: '4',
|
||||
id: 4,
|
||||
name: '赵六',
|
||||
gender: '男',
|
||||
age: 32,
|
||||
department: '财务部',
|
||||
position: '会计',
|
||||
month: '2025-08',
|
||||
preTaxSalary: 12000,
|
||||
afterTaxSalary: 10400,
|
||||
baseSalary: 8000,
|
||||
floatingSalary: 4000
|
||||
},
|
||||
{
|
||||
key: '5',
|
||||
id: 5,
|
||||
name: '陈七',
|
||||
gender: '女',
|
||||
age: 26,
|
||||
department: '人事部',
|
||||
position: 'HR专员',
|
||||
month: '2025-08',
|
||||
preTaxSalary: 10000,
|
||||
afterTaxSalary: 8800,
|
||||
baseSalary: 7000,
|
||||
floatingSalary: 3000
|
||||
},
|
||||
],
|
||||
pagination: {
|
||||
current: 1,
|
||||
pageSize: 5,
|
||||
total: 356,
|
||||
}
|
||||
};
|
||||
|
||||
// 默认列配置
|
||||
this.defaultColumns = [
|
||||
{
|
||||
title: '姓名',
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
width: 100,
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '性别',
|
||||
dataIndex: 'gender',
|
||||
key: 'gender',
|
||||
width: 80,
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '年龄',
|
||||
dataIndex: 'age',
|
||||
key: 'age',
|
||||
width: 80,
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '部门',
|
||||
dataIndex: 'department',
|
||||
key: 'department',
|
||||
width: 120,
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '岗位',
|
||||
dataIndex: 'position',
|
||||
key: 'position',
|
||||
width: 140,
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '月份',
|
||||
dataIndex: 'month',
|
||||
key: 'month',
|
||||
width: 100,
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '税前工资',
|
||||
dataIndex: 'preTaxSalary',
|
||||
key: 'preTaxSalary',
|
||||
width: 120,
|
||||
align: 'center',
|
||||
render: (salary) => (
|
||||
<span style={{ fontWeight: 'bold', color: '#1890ff' }}>
|
||||
¥{salary?.toLocaleString()}
|
||||
</span>
|
||||
),
|
||||
sorter: (a, b) => a.preTaxSalary - b.preTaxSalary,
|
||||
},
|
||||
{
|
||||
title: '税后工资',
|
||||
dataIndex: 'afterTaxSalary',
|
||||
key: 'afterTaxSalary',
|
||||
width: 120,
|
||||
align: 'center',
|
||||
render: (salary) => (
|
||||
<span style={{ fontWeight: 'bold', color: '#52c41a' }}>
|
||||
¥{salary?.toLocaleString()}
|
||||
</span>
|
||||
),
|
||||
sorter: (a, b) => a.afterTaxSalary - b.afterTaxSalary,
|
||||
},
|
||||
{
|
||||
title: '基本工资',
|
||||
dataIndex: 'baseSalary',
|
||||
key: 'baseSalary',
|
||||
width: 120,
|
||||
align: 'center',
|
||||
render: (salary) => `¥${salary?.toLocaleString()}`,
|
||||
sorter: (a, b) => a.baseSalary - b.baseSalary,
|
||||
},
|
||||
{
|
||||
title: '浮动工资',
|
||||
dataIndex: 'floatingSalary',
|
||||
key: 'floatingSalary',
|
||||
width: 120,
|
||||
align: 'center',
|
||||
render: (salary) => `¥${salary?.toLocaleString()}`,
|
||||
sorter: (a, b) => a.floatingSalary - b.floatingSalary,
|
||||
},
|
||||
{
|
||||
title: '详情',
|
||||
key: 'detail',
|
||||
width: 80,
|
||||
align: 'center',
|
||||
render: (_, record) => (
|
||||
<Button
|
||||
type="link"
|
||||
size="small"
|
||||
icon={<EyeOutlined />}
|
||||
onClick={() => this.handleViewDetail(record)}
|
||||
title="查看详情"
|
||||
style={{ color: '#1890ff' }}
|
||||
/>
|
||||
),
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
// 获取处理后的树形数据
|
||||
getTreeData = () => {
|
||||
const { organizationData } = this.state;
|
||||
|
||||
const processNode = (node) => ({
|
||||
key: node.key,
|
||||
title: (
|
||||
<span className={styles['tree-node-title']}>
|
||||
{this.getNodeIcon(node.title)}
|
||||
<span className={styles['node-title']}>{node.title}</span>
|
||||
<span className={styles['node-count']}>({node.count})</span>
|
||||
</span>
|
||||
),
|
||||
children: node.children ? node.children.map(processNode) : undefined
|
||||
});
|
||||
|
||||
return organizationData.map(processNode);
|
||||
};
|
||||
|
||||
// 获取节点图标
|
||||
getNodeIcon = (title) => {
|
||||
if (title.includes('公司') || title.includes('集团')) return <ApartmentOutlined style={{ color: '#1890ff' }} />;
|
||||
if (title.includes('技术') || title.includes('开发') || title.includes('测试')) return <SettingOutlined style={{ color: '#52c41a' }} />;
|
||||
if (title.includes('产品') || title.includes('设计') || title.includes('体验')) return <TeamOutlined style={{ color: '#fa8c16' }} />;
|
||||
if (title.includes('运营') || title.includes('市场') || title.includes('客户') || title.includes('商务')) return <TeamOutlined style={{ color: '#eb2f96' }} />;
|
||||
if (title.includes('财务') || title.includes('会计') || title.includes('审计')) return <DollarOutlined style={{ color: '#722ed1' }} />;
|
||||
if (title.includes('人事') || title.includes('HR') || title.includes('招聘') || title.includes('培训') || title.includes('薪酬')) return <UserOutlined style={{ color: '#13c2c2' }} />;
|
||||
return <TeamOutlined style={{ color: '#666' }} />;
|
||||
};
|
||||
|
||||
// 搜索处理
|
||||
handleSearch = (values) => {
|
||||
console.log('搜索参数:', values);
|
||||
this.setState({ searchForm: values });
|
||||
};
|
||||
|
||||
// 重置搜索
|
||||
handleReset = () => {
|
||||
this.formRef?.resetFields();
|
||||
this.setState({ searchForm: {} });
|
||||
};
|
||||
|
||||
// 树节点选择
|
||||
onTreeSelect = (selectedKeys, info) => {
|
||||
console.log('选中节点:', selectedKeys, info);
|
||||
this.setState({ selectedKeys });
|
||||
};
|
||||
|
||||
// 树节点展开
|
||||
onTreeExpand = (expandedKeys) => {
|
||||
this.setState({ expandedKeys });
|
||||
};
|
||||
|
||||
// 刷新树数据
|
||||
handleTreeRefresh = () => {
|
||||
// console.log('刷新组织架构');
|
||||
// 这里可以添加刷新树数据的逻辑
|
||||
};
|
||||
|
||||
// 展开/收缩所有节点
|
||||
handleTreeToggle = () => {
|
||||
const { expandedKeys, organizationData } = this.state;
|
||||
if (expandedKeys.length > 0) {
|
||||
// 收缩所有节点
|
||||
this.setState({ expandedKeys: [] });
|
||||
} else {
|
||||
// 展开所有节点
|
||||
const getAllKeys = (nodes) => {
|
||||
let keys = [];
|
||||
nodes.forEach(node => {
|
||||
keys.push(node.key);
|
||||
if (node.children) {
|
||||
keys = keys.concat(getAllKeys(node.children));
|
||||
}
|
||||
});
|
||||
return keys;
|
||||
};
|
||||
this.setState({ expandedKeys: getAllKeys(organizationData) });
|
||||
}
|
||||
};
|
||||
|
||||
renderSimpleForm() {
|
||||
const { prooperlog = {} } = this.props;
|
||||
const { params = {} } = prooperlog;
|
||||
const parentMethods = {
|
||||
handleSearch: this.handleSearch,
|
||||
handleFormReset: this.handleFormReset,
|
||||
toggleForm: this.toggleForm,
|
||||
submitButtons: styles.submitButtons,
|
||||
params
|
||||
};
|
||||
return (
|
||||
<SalaryDataRenderSimpleForm {...parentMethods} />
|
||||
);
|
||||
}
|
||||
|
||||
renderAdvancedForm() {
|
||||
const { prooperlog: { params } } = this.props;
|
||||
const parentMethods = {
|
||||
handleSearch: this.handleSearch,
|
||||
handleFormReset: this.handleFormReset,
|
||||
toggleForm: this.toggleForm,
|
||||
params
|
||||
};
|
||||
|
||||
return (
|
||||
<SalaryDataRenderSimpleForm {...parentMethods} />
|
||||
);
|
||||
}
|
||||
|
||||
renderForm() {
|
||||
const { expandForm } = this.state;
|
||||
return expandForm ? this.renderAdvancedForm() : this.renderSimpleForm();
|
||||
}
|
||||
|
||||
// 分页处理
|
||||
onPaginationChange = (page, pageSize) => {
|
||||
this.setState({
|
||||
pagination: {
|
||||
...this.state.pagination,
|
||||
current: page,
|
||||
pageSize,
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// 显示新增弹窗
|
||||
showAddModal = () => {
|
||||
this.setState({ addModalVisible: true });
|
||||
};
|
||||
|
||||
// 关闭新增弹窗
|
||||
hideAddModal = () => {
|
||||
this.setState({ addModalVisible: false });
|
||||
};
|
||||
|
||||
// 新增成功回调
|
||||
handleAddSuccess = (values) => {
|
||||
console.log('新增成功:', values);
|
||||
|
||||
// 这里可以刷新列表数据
|
||||
// 模拟添加到表格数据中
|
||||
const newStaff = {
|
||||
key: String(Date.now()),
|
||||
id: this.state.tableData.length + 1,
|
||||
name: values.name,
|
||||
phone: values.age, // 年龄
|
||||
idCard: values.department, // 部门
|
||||
department: values.position, // 岗位
|
||||
position: values.workType, // 工作性质
|
||||
status: values.phone, // 电话
|
||||
entryDate: new Date().toISOString().split('T')[0]
|
||||
};
|
||||
|
||||
this.setState({
|
||||
tableData: [...this.state.tableData, newStaff],
|
||||
pagination: {
|
||||
...this.state.pagination,
|
||||
total: this.state.pagination.total + 1
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// 处理姓名点击事件
|
||||
handleNameClick = (record) => {
|
||||
console.log('查看员工薪酬信息:', record);
|
||||
// 可以跳转到员工薪酬详情页面或显示详情模态框
|
||||
};
|
||||
|
||||
// 处理查看详情
|
||||
handleViewDetail = (record) => {
|
||||
// 跳转到薪酬详情页面
|
||||
history.push('/topnavbar00/salarymanage/salarydetail');
|
||||
};
|
||||
|
||||
// 处理编辑
|
||||
handleEdit = (record) => {
|
||||
console.log('编辑薪酬:', record);
|
||||
this.setState({
|
||||
addModalVisible: true
|
||||
});
|
||||
};
|
||||
|
||||
// 处理删除
|
||||
handleDelete = (record) => {
|
||||
console.log('删除薪酬记录:', record);
|
||||
// 这里可以弹出确认框并执行删除操作
|
||||
};
|
||||
|
||||
render() {
|
||||
const { tableData, pagination, expandedKeys, addModalVisible, addLoading } = this.state;
|
||||
|
||||
return (
|
||||
<>
|
||||
<iframe title="薪酬报表" className={styles.frameContent} src="/薪酬报表.html"/>
|
||||
</>
|
||||
)
|
||||
<div className={styles.staffInfoContainer}>
|
||||
{/* 主体内容 */}
|
||||
<div className={styles.mainContent}>
|
||||
<Card className={styles.contentCard}>
|
||||
<Row gutter={24}>
|
||||
{/* 左侧组织架构树 */}
|
||||
<Col span={5}>
|
||||
<Card
|
||||
title={
|
||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', width: '100%' }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center' }}>
|
||||
<TeamOutlined style={{ marginRight: 8, color: '#1890ff' }} />
|
||||
<span>组织架构</span>
|
||||
</div>
|
||||
<Space>
|
||||
<Button
|
||||
type="text"
|
||||
icon={<SyncOutlined />}
|
||||
size="small"
|
||||
style={{ color: '#1890ff' }}
|
||||
onClick={this.handleTreeRefresh}
|
||||
title="刷新"
|
||||
/>
|
||||
<Button
|
||||
type="text"
|
||||
icon={<ExpandOutlined />}
|
||||
size="small"
|
||||
style={{ color: '#1890ff' }}
|
||||
onClick={this.handleTreeToggle}
|
||||
title={expandedKeys.length > 0 ? "收缩全部" : "展开全部"}
|
||||
/>
|
||||
</Space>
|
||||
</div>
|
||||
}
|
||||
className={styles.treeCard}
|
||||
>
|
||||
<div className={styles.treeContainer}>
|
||||
<Tree
|
||||
showIcon
|
||||
expandedKeys={expandedKeys}
|
||||
onSelect={this.onTreeSelect}
|
||||
onExpand={this.onTreeExpand}
|
||||
treeData={this.getTreeData()}
|
||||
className={styles.orgTree}
|
||||
/>
|
||||
</div>
|
||||
</Card>
|
||||
</Col>
|
||||
|
||||
{/* 右侧人员信息区 */}
|
||||
<Col span={19}>
|
||||
{/* 筛选条件 */}
|
||||
<Card className={styles.searchCard}>
|
||||
{this.renderForm()}
|
||||
</Card>
|
||||
|
||||
{/* 操作按钮和统计 */}
|
||||
{/* <div className={styles.actionBar}>
|
||||
<div className={styles.totalInfo}>
|
||||
共 {pagination.total} 条记录
|
||||
</div>
|
||||
<Space size="middle">
|
||||
<Button
|
||||
icon={<DownloadOutlined />}
|
||||
size="large"
|
||||
className={styles.exportButton}
|
||||
>
|
||||
导出
|
||||
</Button>
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<UserAddOutlined />}
|
||||
size="large"
|
||||
className={styles.addButton}
|
||||
onClick={this.showAddModal}
|
||||
>
|
||||
新增
|
||||
</Button>
|
||||
</Space>
|
||||
</div> */}
|
||||
|
||||
{/* 人员表格 */}
|
||||
<Card className={styles.tableCard}>
|
||||
<StandardTable
|
||||
columns={this.defaultColumns}
|
||||
dataSource={tableData}
|
||||
pagination={false}
|
||||
// scroll={{ x: 1000,y: 600 }}
|
||||
size="small"
|
||||
selectedRows={[]}
|
||||
/>
|
||||
|
||||
{/* 分页 */}
|
||||
<div className={styles.paginationWrapper}>
|
||||
<Pagination
|
||||
current={pagination.current}
|
||||
total={pagination.total}
|
||||
pageSize={pagination.pageSize}
|
||||
showSizeChanger
|
||||
showQuickJumper
|
||||
showTotal={(total, range) =>
|
||||
`显示 ${range[0]} 到 ${range[1]} 条,共 ${total} 条记录`
|
||||
}
|
||||
onChange={this.onPaginationChange}
|
||||
onShowSizeChange={this.onPaginationChange}
|
||||
/>
|
||||
</div>
|
||||
</Card>
|
||||
</Col>
|
||||
</Row>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
{/* 新增人员弹窗 */}
|
||||
<SalaryDataAdd
|
||||
visible={addModalVisible}
|
||||
loading={addLoading}
|
||||
onCancel={this.hideAddModal}
|
||||
onSuccess={this.handleAddSuccess}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default SalaryData
|
||||
export default SalaryData;
|
@ -1,10 +1,523 @@
|
||||
@import '~@/utils/utils.less';
|
||||
|
||||
.frameContent {
|
||||
width: 100%;
|
||||
.staffInfoContainer {
|
||||
min-height: 100vh;
|
||||
height: 100vh;
|
||||
border: none;
|
||||
display: block;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
// background-color: #f5f6fa;
|
||||
|
||||
.announcementBar {
|
||||
background: #e6f7ff;
|
||||
border: 1px solid #91d5ff;
|
||||
margin-bottom: 16px;
|
||||
border-radius: 6px;
|
||||
overflow: hidden;
|
||||
|
||||
.announcement {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.announcementLabel {
|
||||
background-color: #fef3c7;
|
||||
color: #92400e;
|
||||
border-radius: 4px;
|
||||
padding: 4px 8px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
margin-right: 12px;
|
||||
white-space: nowrap;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.anticon {
|
||||
margin-right: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
.scrollContainer {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.scrollContent {
|
||||
display: inline-block;
|
||||
animation: scroll 30s linear infinite;
|
||||
color: #666;
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.mainContent {
|
||||
// padding: 12px;
|
||||
|
||||
.contentCard {
|
||||
.ant-card-head {
|
||||
.ant-card-head-title {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.treeCard {
|
||||
height: 600px;
|
||||
border: 1px solid #e8e8e8;
|
||||
border-radius: 8px;
|
||||
|
||||
.treeHeader {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.treeContainer {
|
||||
height: 520px;
|
||||
overflow-y: auto;
|
||||
|
||||
/* 自定义滚动条样式 */
|
||||
&::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background: #d9d9d9;
|
||||
border-radius: 3px;
|
||||
|
||||
&:hover {
|
||||
background: #bfbfbf;
|
||||
}
|
||||
}
|
||||
|
||||
.orgTree {
|
||||
.ant-tree-treenode {
|
||||
padding: 2px 0;
|
||||
|
||||
.ant-tree-node-content-wrapper {
|
||||
border-radius: 4px;
|
||||
transition: all 0.3s ease;
|
||||
padding: 4px 8px;
|
||||
|
||||
&:hover {
|
||||
background-color: #f0f9ff;
|
||||
transform: translateX(2px);
|
||||
}
|
||||
|
||||
&.ant-tree-node-selected {
|
||||
background-color: #e6f7ff !important;
|
||||
box-shadow: 0 2px 8px rgba(24, 144, 255, 0.2);
|
||||
}
|
||||
}
|
||||
|
||||
.ant-tree-iconEle {
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.ant-tree-title {
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Tree节点标题样式 */
|
||||
.tree-node-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
|
||||
.node-title {
|
||||
flex: 1;
|
||||
font-size: 14px;
|
||||
margin-left: 8px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.node-count {
|
||||
color: #999;
|
||||
font-size: 12px;
|
||||
margin-left: auto;
|
||||
background: #f0f0f0;
|
||||
padding: 1px 6px;
|
||||
border-radius: 10px;
|
||||
min-width: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
.searchCard {
|
||||
margin-bottom: 16px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #e8e8e8;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
||||
|
||||
.ant-card-body {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
/* 搜索表单容器样式 */
|
||||
.searchFormContainer {
|
||||
.searchForm {
|
||||
.ant-form-item {
|
||||
margin-bottom: 16px;
|
||||
|
||||
.ant-form-item-label {
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
|
||||
label {
|
||||
color: #333 !important;
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
.ant-input,
|
||||
.ant-select-selector,
|
||||
.ant-picker {
|
||||
border-radius: 6px;
|
||||
border: 1px solid #d9d9d9;
|
||||
transition: all 0.3s ease;
|
||||
|
||||
&:hover {
|
||||
border-color: #4c7bff;
|
||||
}
|
||||
|
||||
&:focus,
|
||||
&.ant-select-focused .ant-select-selector,
|
||||
&.ant-picker-focused {
|
||||
border-color: #2d5cf6;
|
||||
box-shadow: 0 0 0 2px rgba(45, 92, 246, 0.2);
|
||||
}
|
||||
}
|
||||
|
||||
.ant-select-selection-placeholder,
|
||||
.ant-input::placeholder,
|
||||
.ant-picker-input input::placeholder {
|
||||
color: #bfbfbf;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 搜索按钮样式 */
|
||||
.searchButton {
|
||||
background: linear-gradient(135deg, #2d5cf6 0%, #4c7bff 100%);
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
color: white;
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
box-shadow: 0 2px 8px rgba(45, 92, 246, 0.3);
|
||||
transition: all 0.3s ease;
|
||||
height: 32px;
|
||||
padding: 0 16px;
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
background: linear-gradient(135deg, #4c7bff 0%, #6b8fff 100%);
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 4px 12px rgba(45, 92, 246, 0.4);
|
||||
color: white;
|
||||
}
|
||||
|
||||
&:active {
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
/* 重置按钮样式 */
|
||||
.resetButton {
|
||||
background: #fff;
|
||||
border: 1px solid #d9d9d9;
|
||||
color: #666;
|
||||
border-radius: 6px;
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
transition: all 0.3s ease;
|
||||
height: 32px;
|
||||
padding: 0 16px;
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
border-color: #4c7bff;
|
||||
color: #2d5cf6;
|
||||
}
|
||||
|
||||
.anticon {
|
||||
color: #ff7875;
|
||||
}
|
||||
}
|
||||
|
||||
/* 展开按钮样式 */
|
||||
.expandButton {
|
||||
color: #2d5cf6;
|
||||
font-size: 14px;
|
||||
padding: 0 8px;
|
||||
height: 32px;
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
color: #4c7bff;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.actionBar {
|
||||
|
||||
|
||||
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 16px;
|
||||
|
||||
.totalInfo {
|
||||
color: #666;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.exportButton {
|
||||
background: linear-gradient(135deg, #52c41a 0%, #73d13d 100%);
|
||||
border: none;
|
||||
color: white;
|
||||
border-radius: 8px;
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
box-shadow: 0 4px 12px rgba(82, 196, 26, 0.3);
|
||||
transition: all 0.3s ease;
|
||||
// margin-top: -8px;
|
||||
height: 35px;
|
||||
padding: 0 16px;
|
||||
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
background: linear-gradient(135deg, #73d13d 0%, #95de64 100%);
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 6px 20px rgba(82, 196, 26, 0.4);
|
||||
color: white;
|
||||
}
|
||||
|
||||
&:active {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.anticon {
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.addButton {
|
||||
background: linear-gradient(135deg, #fa8c16 0%, #ffa940 100%);
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
box-shadow: 0 4px 12px rgba(250, 140, 22, 0.3);
|
||||
transition: all 0.3s ease;
|
||||
// margin-top: -8px;
|
||||
height: 35px;
|
||||
padding: 0 16px;
|
||||
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
background: linear-gradient(135deg, #ffa940 0%, #ffc069 100%);
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 6px 20px rgba(250, 140, 22, 0.4);
|
||||
}
|
||||
|
||||
&:active {
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.tableCard {
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
||||
padding-bottom: 10px;
|
||||
|
||||
.ant-table-wrapper {
|
||||
max-height: 600px;
|
||||
overflow-y: auto;
|
||||
|
||||
/* 自定义滚动条样式 */
|
||||
&::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-track {
|
||||
background: #f5f5f5;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background: #d9d9d9;
|
||||
border-radius: 4px;
|
||||
|
||||
&:hover {
|
||||
background: #bfbfbf;
|
||||
}
|
||||
}
|
||||
|
||||
.ant-table-thead>tr>th {
|
||||
background-color: #fafafa;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
border-bottom: 1px solid #e8e8e8;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.ant-table-tbody>tr {
|
||||
&:hover>td {
|
||||
background-color: #f5f5f5 !important;
|
||||
}
|
||||
|
||||
>td {
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.paginationWrapper {
|
||||
margin-top: 20px;
|
||||
text-align: right;
|
||||
|
||||
.ant-pagination {
|
||||
.ant-pagination-total-text {
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.ant-pagination-item {
|
||||
border-radius: 4px;
|
||||
|
||||
&.ant-pagination-item-active {
|
||||
background-color: #2d5cf6;
|
||||
border-color: #2d5cf6;
|
||||
}
|
||||
}
|
||||
|
||||
.ant-pagination-prev,
|
||||
.ant-pagination-next {
|
||||
border-radius: 4px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.mainContent {
|
||||
// padding: 12px;
|
||||
|
||||
.contentCard {
|
||||
border: none !important;
|
||||
.ant-card-head {
|
||||
.ant-card-head-title {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.actionBar {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 16px;
|
||||
|
||||
.totalInfo {
|
||||
color: #666;
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
:global {
|
||||
.ant-card-body {
|
||||
padding: 12px 24px 0px 24px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@keyframes scroll {
|
||||
0% {
|
||||
transform: translateX(0);
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
}
|
||||
|
||||
// 响应式设计
|
||||
@media (max-width: 1200px) {
|
||||
.staffInfoContainer {
|
||||
.mainContent {
|
||||
.searchCard {
|
||||
.ant-row {
|
||||
.ant-col {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 自定义主题色
|
||||
.ant-btn-primary {
|
||||
background-color: #2d5cf6;
|
||||
border-color: #2d5cf6;
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
background-color: #4c7bff;
|
||||
border-color: #4c7bff;
|
||||
}
|
||||
}
|
||||
|
||||
.ant-tree .ant-tree-node-selected {
|
||||
background-color: #e6f7ff !important;
|
||||
}
|
||||
|
||||
.ant-select-focused .ant-select-selector,
|
||||
.ant-input-affix-wrapper-focused,
|
||||
.ant-input:focus,
|
||||
.ant-input-focused {
|
||||
border-color: #2d5cf6 !important;
|
||||
box-shadow: 0 0 0 2px rgba(45, 92, 246, 0.2) !important;
|
||||
}
|
||||
|
||||
// 标签样式优化
|
||||
.ant-tag {
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
// 按钮组间距调整
|
||||
.ant-space-item {
|
||||
.ant-btn+.ant-btn {
|
||||
margin-left: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
// 表格链接按钮样式 - 限制在当前容器内,避免影响其他页面
|
||||
.staffInfoContainer .ant-btn-link {
|
||||
padding: 0 4px;
|
||||
font-size: 12px;
|
||||
}
|
@ -0,0 +1,197 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
import {
|
||||
Modal,
|
||||
Form,
|
||||
Input,
|
||||
Select,
|
||||
InputNumber,
|
||||
Row,
|
||||
Col,
|
||||
message
|
||||
} from 'antd';
|
||||
import {
|
||||
UserOutlined,
|
||||
PhoneOutlined,
|
||||
TeamOutlined,
|
||||
ApartmentOutlined
|
||||
} from '@ant-design/icons';
|
||||
|
||||
const { Option } = Select;
|
||||
|
||||
class SalaryDataAdd extends PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.formRef = React.createRef();
|
||||
}
|
||||
|
||||
// 提交表单
|
||||
handleSubmit = (values) => {
|
||||
console.log('新增人员信息:', values);
|
||||
|
||||
// 这里可以调用API接口保存数据
|
||||
// 模拟保存成功
|
||||
message.success('新增人员成功!');
|
||||
|
||||
// 重置表单
|
||||
this.formRef.current?.resetFields();
|
||||
|
||||
// 调用父组件的回调函数
|
||||
if (this.props.onSuccess) {
|
||||
this.props.onSuccess(values);
|
||||
}
|
||||
|
||||
// 关闭弹窗
|
||||
this.handleCancel();
|
||||
};
|
||||
|
||||
// 取消操作
|
||||
handleCancel = () => {
|
||||
// 重置表单
|
||||
this.formRef.current?.resetFields();
|
||||
|
||||
// 调用父组件的关闭回调
|
||||
if (this.props.onCancel) {
|
||||
this.props.onCancel();
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const { visible, loading = false } = this.props;
|
||||
|
||||
return (
|
||||
<Modal
|
||||
title="新增人员"
|
||||
open={visible}
|
||||
onOk={() => this.formRef.current?.submit()}
|
||||
onCancel={this.handleCancel}
|
||||
width={600}
|
||||
confirmLoading={loading}
|
||||
destroyOnClose={true}
|
||||
maskClosable={false}
|
||||
>
|
||||
<Form
|
||||
ref={this.formRef}
|
||||
layout="vertical"
|
||||
onFinish={this.handleSubmit}
|
||||
initialValues={{
|
||||
workType: '全职'
|
||||
}}
|
||||
>
|
||||
<Row gutter={16}>
|
||||
<Col span={12}>
|
||||
<Form.Item
|
||||
name="name"
|
||||
label="姓名"
|
||||
rules={[
|
||||
{ required: true, message: '请输入姓名' },
|
||||
{ min: 2, max: 10, message: '姓名长度为2-10个字符' }
|
||||
]}
|
||||
>
|
||||
<Input
|
||||
placeholder="请输入姓名"
|
||||
prefix={<UserOutlined />}
|
||||
/>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<Form.Item
|
||||
name="age"
|
||||
label="年龄"
|
||||
rules={[
|
||||
{ required: true, message: '请输入年龄' }
|
||||
]}
|
||||
>
|
||||
<InputNumber
|
||||
placeholder="请输入年龄"
|
||||
min={18}
|
||||
max={65}
|
||||
style={{ width: '100%' }}
|
||||
/>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
<Row gutter={16}>
|
||||
<Col span={12}>
|
||||
<Form.Item
|
||||
name="department"
|
||||
label="部门"
|
||||
rules={[
|
||||
{ required: true, message: '请选择部门' }
|
||||
]}
|
||||
>
|
||||
<Select
|
||||
placeholder="请选择部门"
|
||||
suffixIcon={<ApartmentOutlined />}
|
||||
>
|
||||
<Option value="技术部">技术部</Option>
|
||||
<Option value="产品部">产品部</Option>
|
||||
<Option value="运营部">运营部</Option>
|
||||
<Option value="财务部">财务部</Option>
|
||||
<Option value="人事部">人事部</Option>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<Form.Item
|
||||
name="position"
|
||||
label="岗位"
|
||||
rules={[
|
||||
{ required: true, message: '请选择岗位' }
|
||||
]}
|
||||
>
|
||||
<Select
|
||||
placeholder="请选择岗位"
|
||||
suffixIcon={<TeamOutlined />}
|
||||
>
|
||||
<Option value="高级工程师">高级工程师</Option>
|
||||
<Option value="产品经理">产品经理</Option>
|
||||
<Option value="运营专员">运营专员</Option>
|
||||
<Option value="会计">会计</Option>
|
||||
<Option value="HR专员">HR专员</Option>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
<Row gutter={16}>
|
||||
<Col span={12}>
|
||||
<Form.Item
|
||||
name="workType"
|
||||
label="工作性质"
|
||||
rules={[
|
||||
{ required: true, message: '请选择工作性质' }
|
||||
]}
|
||||
>
|
||||
<Select placeholder="请选择工作性质">
|
||||
<Option value="全职">全职</Option>
|
||||
{/* <Option value="兼职">兼职</Option> */}
|
||||
<Option value="实习">实习</Option>
|
||||
<Option value="外包">外包</Option>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<Form.Item
|
||||
name="phone"
|
||||
label="电话"
|
||||
rules={[
|
||||
{ required: true, message: '请输入电话号码' },
|
||||
{ pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号码' }
|
||||
]}
|
||||
>
|
||||
<Input
|
||||
placeholder="请输入电话号码"
|
||||
prefix={<PhoneOutlined />}
|
||||
maxLength={11}
|
||||
/>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
</Row>
|
||||
</Form>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default SalaryDataAdd;
|
@ -0,0 +1,104 @@
|
||||
import React from 'react';
|
||||
import { Button, Col, Form, Input, Row, Select, Space, DatePicker } from 'antd';
|
||||
import { ClearOutlined, SearchOutlined } from '@ant-design/icons';
|
||||
import styles from "../SalaryData.less";
|
||||
|
||||
const { Option } = Select;
|
||||
const SalaryDataRenderSimpleForm = (props) => {
|
||||
const [form] = Form.useForm();
|
||||
const { handleSearch, handleFormReset, params } = props;
|
||||
|
||||
React.useEffect(() => {
|
||||
form.setFieldsValue({
|
||||
name: params?.name,
|
||||
phone: params?.phone,
|
||||
month: params?.month,
|
||||
});
|
||||
}, [params]);
|
||||
|
||||
const onFinish = values => {
|
||||
const searchParams = {
|
||||
...values,
|
||||
month: values.month ? values.month.format('YYYY-MM') : undefined,
|
||||
};
|
||||
handleSearch && handleSearch(searchParams);
|
||||
};
|
||||
|
||||
const myhandleFormReset = () => {
|
||||
form.resetFields();
|
||||
handleFormReset && handleFormReset();
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={styles.searchFormContainer}>
|
||||
<Form
|
||||
form={form}
|
||||
onFinish={onFinish}
|
||||
layout="vertical"
|
||||
className={styles.searchForm}
|
||||
>
|
||||
<Row gutter={16}>
|
||||
<Col span={6}>
|
||||
<Form.Item
|
||||
name="name"
|
||||
label="姓名"
|
||||
>
|
||||
<Input
|
||||
placeholder="请输入姓名"
|
||||
allowClear
|
||||
/>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col span={6}>
|
||||
<Form.Item
|
||||
name="phone"
|
||||
label="电话"
|
||||
>
|
||||
<Input
|
||||
placeholder="请输入电话"
|
||||
allowClear
|
||||
/>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col span={6}>
|
||||
<Form.Item
|
||||
name="month"
|
||||
label="月份"
|
||||
>
|
||||
<DatePicker
|
||||
placeholder="请选择月份"
|
||||
picker="month"
|
||||
style={{ width: '100%' }}
|
||||
allowClear
|
||||
/>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col span={6}>
|
||||
<Form.Item label=" " colon={false}>
|
||||
<Space size="middle">
|
||||
<Button
|
||||
type="primary"
|
||||
htmlType="submit"
|
||||
icon={<SearchOutlined />}
|
||||
className={styles.searchButton}
|
||||
>
|
||||
查询
|
||||
</Button>
|
||||
<Button
|
||||
onClick={myhandleFormReset}
|
||||
icon={<ClearOutlined />}
|
||||
className={styles.resetButton}
|
||||
>
|
||||
重置
|
||||
</Button>
|
||||
</Space>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
</Row>
|
||||
</Form>
|
||||
</div>
|
||||
);
|
||||
|
||||
};
|
||||
|
||||
export default SalaryDataRenderSimpleForm;
|
@ -0,0 +1,395 @@
|
||||
|
||||
import React, { PureComponent } from 'react';
|
||||
import { Card, Avatar, Breadcrumb, Tag, Descriptions, Table, Row, Col, Button, Divider, Progress, Statistic } from 'antd';
|
||||
import { UserOutlined, DollarCircleOutlined, FileTextOutlined, CalendarOutlined, BarChartOutlined, ArrowLeftOutlined, EditOutlined, PrinterOutlined, FileExcelOutlined, RiseOutlined, FallOutlined } from '@ant-design/icons';
|
||||
import { history } from 'umi';
|
||||
import styles from './SalaryDetail.less';
|
||||
import girlAvatar from './girl.png';
|
||||
|
||||
// 薪资明细表数据
|
||||
const salaryDetailData = [
|
||||
{ key: '1', item: '基本工资', standard: '¥10,000', should: '¥10,000', deduct: '-', actual: '¥10,000', remark: '-' },
|
||||
{ key: '2', item: '绩效工资', standard: '¥6,000', should: '¥5,500', deduct: '¥500', actual: '¥5,500', remark: 'KPI未达标' },
|
||||
{ key: '3', item: '岗位津贴', standard: '¥1,200', should: '¥1,200', deduct: '-', actual: '¥1,200', remark: '主管级津贴' },
|
||||
{ key: '4', item: '餐补', standard: '¥600', should: '¥600', deduct: '-', actual: '¥600', remark: '22个工作日' },
|
||||
{ key: '5', item: '交通补贴', standard: '¥800', should: '¥800', deduct: '-', actual: '¥800', remark: '-' },
|
||||
{ key: '6', item: '通讯补贴', standard: '¥300', should: '¥300', deduct: '-', actual: '¥300', remark: '-' },
|
||||
{ key: '7', item: '加班费', standard: '-', should: '¥1,100', deduct: '-', actual: '¥1,100', remark: '国庆加班3天' },
|
||||
{ key: '8', item: '小计', standard: '-', should: '¥19,500', deduct: '¥500', actual: '¥19,000', remark: '-', isSubtotal: true },
|
||||
{ key: '9', item: '养老保险', standard: '8%', should: '-', deduct: '¥800', actual: '-¥800', remark: '个人缴纳' },
|
||||
{ key: '10', item: '医疗保险', standard: '2%', should: '-', deduct: '¥200', actual: '-¥200', remark: '个人缴纳' },
|
||||
{ key: '11', item: '失业保险', standard: '0.5%', should: '-', deduct: '¥50', actual: '-¥50', remark: '个人缴纳' },
|
||||
{ key: '12', item: '住房公积金', standard: '12%', should: '-', deduct: '¥1,200', actual: '-¥1,200', remark: '个人缴纳' },
|
||||
{ key: '13', item: '个人所得税', standard: '-', should: '-', deduct: '¥1,430', actual: '-¥1,430', remark: '累计预扣法' },
|
||||
{ key: '14', item: '合计扣减', standard: '-', should: '-', deduct: '¥3,680', actual: '-¥3,680', remark: '-', isSubtotal: true },
|
||||
{ key: '15', item: '实发工资', standard: '-', should: '-', deduct: '-', actual: '¥15,320', remark: '-', isTotal: true },
|
||||
];
|
||||
|
||||
// 考勤数据
|
||||
const attendanceData = [
|
||||
{ type: '出勤', count: 21, color: '#52c41a' },
|
||||
{ type: '迟到/早退', count: 1, color: '#faad14' },
|
||||
{ type: '缺勤', count: 0.5, color: '#ff4d4f' },
|
||||
{ type: '加班', count: 3, color: '#1890ff' },
|
||||
];
|
||||
class SalaryDetail extends PureComponent {
|
||||
handleGoBack = () => {
|
||||
// 跳转到薪酬管理页面
|
||||
history.push('/topnavbar00/salarymanage/salarydata');
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div
|
||||
className="salary-detail-main-container"
|
||||
style={{
|
||||
background: '#f5f5f5',
|
||||
padding: '24px'
|
||||
}}
|
||||
>
|
||||
<Card
|
||||
className="salary-detail-card"
|
||||
style={{
|
||||
borderRadius: '8px',
|
||||
boxShadow: '0 2px 8px rgba(0,0,0,0.1)'
|
||||
}}
|
||||
>
|
||||
{/* 面包屑导航 */}
|
||||
<Breadcrumb
|
||||
style={{ marginBottom: '16px' }}
|
||||
items={[
|
||||
{ title: '薪酬管理' },
|
||||
{ title: '薪酬详情' }
|
||||
]}
|
||||
/>
|
||||
|
||||
{/* 返回按钮 */}
|
||||
<Button
|
||||
type="link"
|
||||
icon={<ArrowLeftOutlined />}
|
||||
onClick={this.handleGoBack}
|
||||
style={{ marginBottom: '24px', padding: 0 }}
|
||||
>
|
||||
返回
|
||||
</Button>
|
||||
|
||||
{/* 个人信息卡片 */}
|
||||
<Card className={styles.profileCard} style={{ marginBottom: '24px' }}>
|
||||
<Row gutter={24} align="middle">
|
||||
<Col xs={24} sm={4}>
|
||||
<Avatar size={80} src={girlAvatar} />
|
||||
</Col>
|
||||
<Col xs={24} sm={14}>
|
||||
<div>
|
||||
<div style={{ marginBottom: '16px' }}>
|
||||
<span style={{ fontSize: '24px', fontWeight: 'bold', marginRight: '12px' }}>林雅婷</span>
|
||||
<span style={{ fontSize: '14px', color: '#666' }}>员工ID: HR20230085</span>
|
||||
</div>
|
||||
<Row gutter={[24, 8]}>
|
||||
<Col span={6}>
|
||||
<div>
|
||||
<div style={{ color: '#999', fontSize: '12px' }}>年龄</div>
|
||||
<div style={{ fontWeight: 500 }}>28岁</div>
|
||||
</div>
|
||||
</Col>
|
||||
<Col span={6}>
|
||||
<div>
|
||||
<div style={{ color: '#999', fontSize: '12px' }}>性别</div>
|
||||
<div style={{ fontWeight: 500 }}>女</div>
|
||||
</div>
|
||||
</Col>
|
||||
<Col span={6}>
|
||||
<div>
|
||||
<div style={{ color: '#999', fontSize: '12px' }}>部门</div>
|
||||
<div style={{ fontWeight: 500 }}>市场部</div>
|
||||
</div>
|
||||
</Col>
|
||||
<Col span={6}>
|
||||
<div>
|
||||
<div style={{ color: '#999', fontSize: '12px' }}>岗位</div>
|
||||
<div style={{ fontWeight: 500 }}>市场策划主管</div>
|
||||
</div>
|
||||
</Col>
|
||||
</Row>
|
||||
<Divider style={{ margin: '16px 0' }} />
|
||||
<Row gutter={[24, 8]}>
|
||||
<Col span={6}>
|
||||
<div>
|
||||
<div style={{ color: '#999', fontSize: '12px' }}>入职日期</div>
|
||||
<div style={{ fontWeight: 500 }}>2020-05-15</div>
|
||||
</div>
|
||||
</Col>
|
||||
<Col span={6}>
|
||||
<div>
|
||||
<div style={{ color: '#999', fontSize: '12px' }}>工龄</div>
|
||||
<div style={{ fontWeight: 500 }}>3年6个月</div>
|
||||
</div>
|
||||
</Col>
|
||||
<Col span={6}>
|
||||
<div>
|
||||
<div style={{ color: '#999', fontSize: '12px' }}>职级</div>
|
||||
<div style={{ fontWeight: 500 }}>P2-3</div>
|
||||
</div>
|
||||
</Col>
|
||||
<Col span={6}>
|
||||
<div>
|
||||
<div style={{ color: '#999', fontSize: '12px' }}>直属上级</div>
|
||||
<div style={{ fontWeight: 500 }}>张伟 (市场部经理)</div>
|
||||
</div>
|
||||
</Col>
|
||||
</Row>
|
||||
</div>
|
||||
</Col>
|
||||
<Col xs={24} sm={6}>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>
|
||||
<Button type="primary" icon={<EditOutlined />}>
|
||||
编辑信息
|
||||
</Button>
|
||||
<Button icon={<FileExcelOutlined />}>
|
||||
导出资料
|
||||
</Button>
|
||||
<Button icon={<PrinterOutlined />}>
|
||||
打印档案
|
||||
</Button>
|
||||
</div>
|
||||
</Col>
|
||||
</Row>
|
||||
</Card>
|
||||
|
||||
{/* 薪资概览 */}
|
||||
<Row gutter={24} style={{ marginBottom: '24px' }}>
|
||||
<Col xs={24} md={8}>
|
||||
<Card>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '16px' }}>
|
||||
<h3 style={{ margin: 0, fontSize: '16px', fontWeight: 600, color: '#333' }}>税前工资</h3>
|
||||
<Tag color="green" style={{ fontSize: '12px' }}>+5% 上月</Tag>
|
||||
</div>
|
||||
<div style={{ display: 'flex', alignItems: 'end', marginBottom: '16px' }}>
|
||||
<span style={{ fontSize: '32px', fontWeight: 'bold', color: '#1890ff' }}>¥18,500</span>
|
||||
<span style={{ color: '#999', marginLeft: '8px', marginBottom: '4px' }}>/月</span>
|
||||
</div>
|
||||
<div style={{ fontSize: '12px', color: '#666' }}>
|
||||
<div style={{ marginBottom: '4px' }}>年度累计: ¥203,500</div>
|
||||
<div>上月工资: ¥17,600</div>
|
||||
</div>
|
||||
</Card>
|
||||
</Col>
|
||||
<Col xs={24} md={8}>
|
||||
<Card>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '16px' }}>
|
||||
<h3 style={{ margin: 0, fontSize: '16px', fontWeight: 600, color: '#333' }}>税后工资</h3>
|
||||
<Tag color="green" style={{ fontSize: '12px' }}>+4.8% 上月</Tag>
|
||||
</div>
|
||||
<div style={{ display: 'flex', alignItems: 'end', marginBottom: '16px' }}>
|
||||
<span style={{ fontSize: '32px', fontWeight: 'bold', color: '#1890ff' }}>¥15,320</span>
|
||||
<span style={{ color: '#999', marginLeft: '8px', marginBottom: '4px' }}>/月</span>
|
||||
</div>
|
||||
<div style={{ fontSize: '12px', color: '#666' }}>
|
||||
<div style={{ marginBottom: '4px' }}>年度累计: ¥168,520</div>
|
||||
<div>上月工资: ¥14,620</div>
|
||||
</div>
|
||||
</Card>
|
||||
</Col>
|
||||
<Col xs={24} md={8}>
|
||||
<Card>
|
||||
<h3 style={{ margin: 0, fontSize: '16px', fontWeight: 600, color: '#333', marginBottom: '16px' }}>薪资构成</h3>
|
||||
<div style={{ marginBottom: '12px' }}>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', fontSize: '12px', marginBottom: '4px' }}>
|
||||
<span style={{ color: '#666' }}>基本工资</span>
|
||||
<span style={{ fontWeight: 500 }}>¥10,000</span>
|
||||
</div>
|
||||
<Progress percent={54} strokeColor="#1890ff" showInfo={false} size="small" />
|
||||
</div>
|
||||
<div style={{ marginBottom: '12px' }}>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', fontSize: '12px', marginBottom: '4px' }}>
|
||||
<span style={{ color: '#666' }}>绩效工资</span>
|
||||
<span style={{ fontWeight: 500 }}>¥5,500</span>
|
||||
</div>
|
||||
<Progress percent={30} strokeColor="#52c41a" showInfo={false} size="small" />
|
||||
</div>
|
||||
<div>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', fontSize: '12px', marginBottom: '4px' }}>
|
||||
<span style={{ color: '#666' }}>各类补贴</span>
|
||||
<span style={{ fontWeight: 500 }}>¥3,000</span>
|
||||
</div>
|
||||
<Progress percent={16} strokeColor="#faad14" showInfo={false} size="small" />
|
||||
</div>
|
||||
</Card>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
{/* 工资明细表 */}
|
||||
<Card style={{ marginBottom: '24px' }}>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '16px' }}>
|
||||
<h3 style={{ margin: 0, fontSize: '16px', fontWeight: 600, color: '#333' }}>2023年10月工资明细</h3>
|
||||
</div>
|
||||
<Table
|
||||
columns={[
|
||||
{
|
||||
title: '项目',
|
||||
dataIndex: 'item',
|
||||
key: 'item',
|
||||
render: (text, record) => (
|
||||
<span style={{
|
||||
fontWeight: record.isSubtotal || record.isTotal ? 'bold' : 'normal',
|
||||
color: record.isTotal ? '#fff' : '#333'
|
||||
}}>
|
||||
{text}
|
||||
</span>
|
||||
)
|
||||
},
|
||||
{
|
||||
title: '标准',
|
||||
dataIndex: 'standard',
|
||||
key: 'standard',
|
||||
render: (text, record) => (
|
||||
<span style={{
|
||||
fontWeight: record.isSubtotal || record.isTotal ? 'bold' : 'normal',
|
||||
color: record.isTotal ? '#fff' : '#666'
|
||||
}}>
|
||||
{text}
|
||||
</span>
|
||||
)
|
||||
},
|
||||
{
|
||||
title: '应发',
|
||||
dataIndex: 'should',
|
||||
key: 'should',
|
||||
render: (text, record) => (
|
||||
<span style={{
|
||||
fontWeight: record.isSubtotal || record.isTotal ? 'bold' : 'normal',
|
||||
color: record.isTotal ? '#fff' : '#666'
|
||||
}}>
|
||||
{text}
|
||||
</span>
|
||||
)
|
||||
},
|
||||
{
|
||||
title: '扣减',
|
||||
dataIndex: 'deduct',
|
||||
key: 'deduct',
|
||||
render: (text, record) => (
|
||||
<span style={{
|
||||
fontWeight: record.isSubtotal || record.isTotal ? 'bold' : 'normal',
|
||||
color: record.isTotal ? '#fff' : '#666'
|
||||
}}>
|
||||
{text}
|
||||
</span>
|
||||
)
|
||||
},
|
||||
{
|
||||
title: '实发',
|
||||
dataIndex: 'actual',
|
||||
key: 'actual',
|
||||
render: (text, record) => (
|
||||
<span style={{
|
||||
fontWeight: record.isSubtotal || record.isTotal ? 'bold' : 'normal',
|
||||
color: record.isTotal ? '#fff' : '#333'
|
||||
}}>
|
||||
{text}
|
||||
</span>
|
||||
)
|
||||
},
|
||||
{
|
||||
title: '备注',
|
||||
dataIndex: 'remark',
|
||||
key: 'remark',
|
||||
render: (text, record) => (
|
||||
<span style={{
|
||||
fontWeight: record.isSubtotal || record.isTotal ? 'bold' : 'normal',
|
||||
color: record.isTotal ? '#fff' : '#666'
|
||||
}}>
|
||||
{text}
|
||||
</span>
|
||||
)
|
||||
},
|
||||
]}
|
||||
dataSource={salaryDetailData}
|
||||
pagination={false}
|
||||
size="small"
|
||||
rowClassName={(record) => {
|
||||
if (record.isTotal) return 'salary-total-row';
|
||||
if (record.isSubtotal) return 'salary-subtotal-row';
|
||||
return 'salary-normal-row';
|
||||
}}
|
||||
/>
|
||||
</Card>
|
||||
|
||||
{/* 考勤记录 */}
|
||||
<Card>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '16px' }}>
|
||||
<h3 style={{ margin: 0, fontSize: '16px', fontWeight: 600, color: '#333' }}>2023年10月考勤记录</h3>
|
||||
<div>
|
||||
<Button size="small" style={{ marginRight: '8px' }}>月度</Button>
|
||||
<Button type="primary" size="small">年度</Button>
|
||||
</div>
|
||||
</div>
|
||||
<Row gutter={16} style={{ marginBottom: '24px' }}>
|
||||
<Col xs={12} md={6}>
|
||||
<div style={{ background: '#f6ffed', padding: '16px', borderRadius: '8px', textAlign: 'center' }}>
|
||||
<div style={{ fontSize: '12px', color: '#666', marginBottom: '8px' }}>应出勤天数</div>
|
||||
<div style={{ fontSize: '24px', fontWeight: 'bold', color: '#52c41a' }}>22</div>
|
||||
</div>
|
||||
</Col>
|
||||
<Col xs={12} md={6}>
|
||||
<div style={{ background: '#e6f7ff', padding: '16px', borderRadius: '8px', textAlign: 'center' }}>
|
||||
<div style={{ fontSize: '12px', color: '#666', marginBottom: '8px' }}>实际出勤</div>
|
||||
<div style={{ fontSize: '24px', fontWeight: 'bold', color: '#1890ff' }}>21</div>
|
||||
</div>
|
||||
</Col>
|
||||
<Col xs={12} md={6}>
|
||||
<div style={{ background: '#fffbe6', padding: '16px', borderRadius: '8px', textAlign: 'center' }}>
|
||||
<div style={{ fontSize: '12px', color: '#666', marginBottom: '8px' }}>迟到/早退</div>
|
||||
<div style={{ fontSize: '24px', fontWeight: 'bold', color: '#faad14' }}>1</div>
|
||||
</div>
|
||||
</Col>
|
||||
<Col xs={12} md={6}>
|
||||
<div style={{ background: '#fff2f0', padding: '16px', borderRadius: '8px', textAlign: 'center' }}>
|
||||
<div style={{ fontSize: '12px', color: '#666', marginBottom: '8px' }}>缺勤</div>
|
||||
<div style={{ fontSize: '24px', fontWeight: 'bold', color: '#ff4d4f' }}>0.5</div>
|
||||
</div>
|
||||
</Col>
|
||||
</Row>
|
||||
<div className="attendance-tags">
|
||||
<div style={{ display: 'flex', flexWrap: 'wrap', gap: '8px' }}>
|
||||
<Tag color="green">10-01 出勤</Tag>
|
||||
<Tag color="green">10-02 出勤</Tag>
|
||||
<Tag color="green">10-03 出勤</Tag>
|
||||
<Tag color="orange">10-04 迟到</Tag>
|
||||
<Tag color="green">10-05 出勤</Tag>
|
||||
<Tag color="green">10-06 出勤</Tag>
|
||||
<Tag color="green">10-07 出勤</Tag>
|
||||
<Tag color="green">10-08 出勤</Tag>
|
||||
<Tag color="green">10-09 出勤</Tag>
|
||||
<Tag color="green">10-10 出勤</Tag>
|
||||
<Tag color="green">10-11 出勤</Tag>
|
||||
<Tag color="green">10-12 出勤</Tag>
|
||||
<Tag color="green">10-13 出勤</Tag>
|
||||
<Tag color="green">10-14 出勤</Tag>
|
||||
<Tag color="green">10-15 出勤</Tag>
|
||||
<Tag color="green">10-16 出勤</Tag>
|
||||
<Tag color="green">10-17 出勤</Tag>
|
||||
<Tag color="green">10-18 出勤</Tag>
|
||||
<Tag color="green">10-19 出勤</Tag>
|
||||
<Tag color="green">10-20 出勤</Tag>
|
||||
<Tag color="red">10-21 缺勤</Tag>
|
||||
<Tag color="green">10-22 出勤</Tag>
|
||||
<Tag color="green">10-23 出勤</Tag>
|
||||
<Tag color="green">10-24 出勤</Tag>
|
||||
<Tag color="green">10-25 出勤</Tag>
|
||||
<Tag color="green">10-26 出勤</Tag>
|
||||
<Tag color="green">10-27 出勤</Tag>
|
||||
<Tag color="green">10-28 出勤</Tag>
|
||||
<Tag color="green">10-29 出勤</Tag>
|
||||
<Tag color="green">10-30 出勤</Tag>
|
||||
<Tag color="green">10-31 出勤</Tag>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default SalaryDetail;
|
@ -1,14 +1,636 @@
|
||||
import React, {Fragment, PureComponent} from 'react';
|
||||
import React, { PureComponent } from 'react';
|
||||
import {
|
||||
Card,
|
||||
Tree,
|
||||
Input,
|
||||
Button,
|
||||
DatePicker,
|
||||
Select,
|
||||
Table,
|
||||
Form,
|
||||
Row,
|
||||
Col,
|
||||
Space,
|
||||
Divider,
|
||||
message
|
||||
} from 'antd';
|
||||
import {
|
||||
SaveOutlined,
|
||||
CloseOutlined,
|
||||
PlusOutlined,
|
||||
DeleteOutlined,
|
||||
ExpandOutlined,
|
||||
FilterOutlined,
|
||||
DownOutlined,
|
||||
UserOutlined,
|
||||
TeamOutlined,
|
||||
ApartmentOutlined,
|
||||
SyncOutlined,
|
||||
DollarOutlined,
|
||||
SettingOutlined
|
||||
} from '@ant-design/icons';
|
||||
import dayjs from 'dayjs';
|
||||
import styles from './SalaryRuleset.less';
|
||||
|
||||
const { Option } = Select;
|
||||
const { TextArea } = Input;
|
||||
|
||||
class SalaryRuleset extends PureComponent {
|
||||
render() {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
selectedOrgKeys: [],
|
||||
expandedKeys: ['0-0', '0-0-0', '0-0-1', '0-0-2'],
|
||||
form: {
|
||||
ruleName: '',
|
||||
applicableOrg: '集团公司/北京分公司',
|
||||
effectiveDate: dayjs('2023-07-01'),
|
||||
socialBase: '员工基本工资',
|
||||
fundBase: '员工基本工资',
|
||||
taxThreshold: '5000',
|
||||
specialDeduction: '自动计算'
|
||||
},
|
||||
salaryItems: [
|
||||
{
|
||||
key: '1',
|
||||
name: '基本工资',
|
||||
type: '固定工资',
|
||||
taxType: '税前',
|
||||
formula: '岗位工资基数 * 职级系数'
|
||||
},
|
||||
{
|
||||
key: '2',
|
||||
name: '绩效奖金',
|
||||
type: '浮动工资',
|
||||
taxType: '税前',
|
||||
formula: '基本工资 * 绩效系数'
|
||||
},
|
||||
{
|
||||
key: '3',
|
||||
name: '交通补贴',
|
||||
type: '补贴',
|
||||
taxType: '税后',
|
||||
formula: '固定金额 500'
|
||||
}
|
||||
],
|
||||
insuranceRates: [
|
||||
{ key: '1', name: '养老保险', companyRate: '16%', personalRate: '8%' },
|
||||
{ key: '2', name: '医疗保险', companyRate: '9.5%', personalRate: '2%' },
|
||||
{ key: '3', name: '失业保险', companyRate: '0.5%', personalRate: '0.5%' },
|
||||
{ key: '4', name: '公积金', companyRate: '12%', personalRate: '12%' }
|
||||
],
|
||||
organizationData: [
|
||||
{
|
||||
title: '飞利信科技有限公司',
|
||||
key: '0-0',
|
||||
count: 356,
|
||||
children: [
|
||||
{
|
||||
title: '技术部',
|
||||
key: '0-0-0',
|
||||
count: 120,
|
||||
children: [
|
||||
{ title: '前端组', key: '0-0-0-0', count: 45 },
|
||||
{ title: '后端组', key: '0-0-0-1', count: 52 },
|
||||
{ title: '测试组', key: '0-0-0-2', count: 23 }
|
||||
],
|
||||
},
|
||||
{
|
||||
title: '产品部',
|
||||
key: '0-0-1',
|
||||
count: 68,
|
||||
children: [
|
||||
{ title: '产品设计组', key: '0-0-1-0', count: 28 },
|
||||
{ title: '用户体验组', key: '0-0-1-1', count: 25 },
|
||||
{ title: '产品运营组', key: '0-0-1-2', count: 15 }
|
||||
],
|
||||
},
|
||||
{
|
||||
title: '运营部',
|
||||
key: '0-0-2',
|
||||
count: 52,
|
||||
children: [
|
||||
{ title: '市场营销组', key: '0-0-2-0', count: 22 },
|
||||
{ title: '客户服务组', key: '0-0-2-1', count: 18 },
|
||||
{ title: '商务合作组', key: '0-0-2-2', count: 12 }
|
||||
],
|
||||
},
|
||||
{
|
||||
title: '财务部',
|
||||
key: '0-0-3',
|
||||
count: 32,
|
||||
children: [
|
||||
{ title: '会计组', key: '0-0-3-0', count: 18 },
|
||||
{ title: '审计组', key: '0-0-3-1', count: 14 }
|
||||
],
|
||||
},
|
||||
{
|
||||
title: '人事部',
|
||||
key: '0-0-4',
|
||||
count: 84,
|
||||
children: [
|
||||
{ title: 'HR专员组', key: '0-0-4-0', count: 25 },
|
||||
{ title: '招聘组', key: '0-0-4-1', count: 22 },
|
||||
{ title: '培训组', key: '0-0-4-2', count: 18 },
|
||||
{ title: '薪酬组', key: '0-0-4-3', count: 19 }
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
// 获取处理后的树形数据
|
||||
getTreeData = () => {
|
||||
const { organizationData } = this.state;
|
||||
|
||||
const processNode = (node) => ({
|
||||
key: node.key,
|
||||
title: (
|
||||
<span className={styles['tree-node-title']}>
|
||||
{this.getNodeIcon(node.title)}
|
||||
<span className={styles['node-title']}>{node.title}</span>
|
||||
<span className={styles['node-count']}>({node.count})</span>
|
||||
</span>
|
||||
),
|
||||
children: node.children ? node.children.map(processNode) : undefined
|
||||
});
|
||||
|
||||
return organizationData.map(processNode);
|
||||
};
|
||||
|
||||
// 获取节点图标
|
||||
getNodeIcon = (title) => {
|
||||
if (title.includes('公司') || title.includes('集团')) return <ApartmentOutlined style={{ color: '#1890ff' }} />;
|
||||
if (title.includes('技术') || title.includes('开发') || title.includes('测试')) return <SettingOutlined style={{ color: '#52c41a' }} />;
|
||||
if (title.includes('产品') || title.includes('设计') || title.includes('体验')) return <TeamOutlined style={{ color: '#fa8c16' }} />;
|
||||
if (title.includes('运营') || title.includes('市场') || title.includes('客户') || title.includes('商务')) return <TeamOutlined style={{ color: '#eb2f96' }} />;
|
||||
if (title.includes('财务') || title.includes('会计') || title.includes('审计')) return <DollarOutlined style={{ color: '#722ed1' }} />;
|
||||
if (title.includes('人事') || title.includes('HR') || title.includes('招聘') || title.includes('培训') || title.includes('薪酬')) return <UserOutlined style={{ color: '#13c2c2' }} />;
|
||||
return <TeamOutlined style={{ color: '#666' }} />;
|
||||
};
|
||||
|
||||
// 刷新树数据
|
||||
handleTreeRefresh = () => {
|
||||
// 这里可以添加刷新树数据的逻辑
|
||||
};
|
||||
|
||||
// 展开/收缩所有节点
|
||||
handleTreeToggle = () => {
|
||||
const { expandedKeys, organizationData } = this.state;
|
||||
if (expandedKeys.length > 0) {
|
||||
// 收缩所有节点
|
||||
this.setState({ expandedKeys: [] });
|
||||
} else {
|
||||
// 展开所有节点
|
||||
const getAllKeys = (nodes) => {
|
||||
let keys = [];
|
||||
nodes.forEach(node => {
|
||||
keys.push(node.key);
|
||||
if (node.children) {
|
||||
keys = keys.concat(getAllKeys(node.children));
|
||||
}
|
||||
});
|
||||
return keys;
|
||||
};
|
||||
this.setState({ expandedKeys: getAllKeys(organizationData) });
|
||||
}
|
||||
};
|
||||
|
||||
// 薪酬项表格列配置
|
||||
salaryColumns = [
|
||||
{
|
||||
title: '薪酬项名称',
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
width: 150,
|
||||
render: (text, record, index) => (
|
||||
<Input
|
||||
size="small"
|
||||
value={text}
|
||||
onChange={(e) => this.handleSalaryItemChange(index, 'name', e.target.value)}
|
||||
/>
|
||||
)
|
||||
},
|
||||
{
|
||||
title: '类型',
|
||||
dataIndex: 'type',
|
||||
key: 'type',
|
||||
width: 120,
|
||||
render: (text, record, index) => (
|
||||
<Select
|
||||
size="small"
|
||||
value={text}
|
||||
style={{ width: '100%' }}
|
||||
onChange={(value) => this.handleSalaryItemChange(index, 'type', value)}
|
||||
>
|
||||
<Option value="固定工资">固定工资</Option>
|
||||
<Option value="浮动工资">浮动工资</Option>
|
||||
<Option value="补贴">补贴</Option>
|
||||
</Select>
|
||||
)
|
||||
},
|
||||
{
|
||||
title: '计税方式',
|
||||
dataIndex: 'taxType',
|
||||
key: 'taxType',
|
||||
width: 120,
|
||||
render: (text, record, index) => (
|
||||
<Select
|
||||
size="small"
|
||||
value={text}
|
||||
style={{ width: '100%' }}
|
||||
onChange={(value) => this.handleSalaryItemChange(index, 'taxType', value)}
|
||||
>
|
||||
<Option value="税前">税前</Option>
|
||||
<Option value="税后">税后</Option>
|
||||
</Select>
|
||||
)
|
||||
},
|
||||
{
|
||||
title: '计算公式',
|
||||
dataIndex: 'formula',
|
||||
key: 'formula',
|
||||
width: 250,
|
||||
render: (text, record, index) => (
|
||||
<Input
|
||||
size="small"
|
||||
value={text}
|
||||
onChange={(e) => this.handleSalaryItemChange(index, 'formula', e.target.value)}
|
||||
/>
|
||||
)
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
width: 80,
|
||||
render: (_, record, index) => (
|
||||
<Button
|
||||
type="link"
|
||||
danger
|
||||
size="small"
|
||||
icon={<DeleteOutlined />}
|
||||
onClick={() => this.deleteSalaryItem(index)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
];
|
||||
|
||||
// 保险缴费比例表格列配置
|
||||
insuranceColumns = [
|
||||
{
|
||||
title: '项目',
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
width: 120
|
||||
},
|
||||
{
|
||||
title: '单位比例',
|
||||
dataIndex: 'companyRate',
|
||||
key: 'companyRate',
|
||||
width: 120,
|
||||
render: (text, record, index) => (
|
||||
<Input
|
||||
size="small"
|
||||
value={text}
|
||||
style={{ width: '80px' }}
|
||||
onChange={(e) => this.handleInsuranceRateChange(index, 'companyRate', e.target.value)}
|
||||
/>
|
||||
)
|
||||
},
|
||||
{
|
||||
title: '个人比例',
|
||||
dataIndex: 'personalRate',
|
||||
key: 'personalRate',
|
||||
width: 120,
|
||||
render: (text, record, index) => (
|
||||
<Input
|
||||
size="small"
|
||||
value={text}
|
||||
style={{ width: '80px' }}
|
||||
onChange={(e) => this.handleInsuranceRateChange(index, 'personalRate', e.target.value)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
];
|
||||
|
||||
// 组织树选择
|
||||
onTreeSelect = (selectedKeys, info) => {
|
||||
console.log('选中节点:', selectedKeys, info);
|
||||
this.setState({ selectedOrgKeys: selectedKeys });
|
||||
if (selectedKeys.length > 0) {
|
||||
const selectedNode = info.node;
|
||||
// 从节点的title中提取组织名称
|
||||
const orgName = selectedNode.title.props ?
|
||||
selectedNode.title.props.children[1].props.children :
|
||||
selectedNode.title;
|
||||
this.handleFormChange('applicableOrg', orgName);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<iframe title="薪酬规则设置" className={styles.frameContent} src="/薪酬规则设置.html"/>
|
||||
</>
|
||||
)
|
||||
// 树展开/收缩
|
||||
onTreeExpand = (expandedKeys) => {
|
||||
this.setState({ expandedKeys });
|
||||
};
|
||||
|
||||
// 表单字段变化
|
||||
handleFormChange = (field, value) => {
|
||||
this.setState(prevState => ({
|
||||
form: {
|
||||
...prevState.form,
|
||||
[field]: value
|
||||
}
|
||||
}));
|
||||
};
|
||||
|
||||
// 薪酬项变化
|
||||
handleSalaryItemChange = (index, field, value) => {
|
||||
const { salaryItems } = this.state;
|
||||
const newItems = [...salaryItems];
|
||||
newItems[index][field] = value;
|
||||
this.setState({ salaryItems: newItems });
|
||||
};
|
||||
|
||||
// 删除薪酬项
|
||||
deleteSalaryItem = (index) => {
|
||||
const { salaryItems } = this.state;
|
||||
const newItems = salaryItems.filter((_, i) => i !== index);
|
||||
this.setState({ salaryItems: newItems });
|
||||
message.success('已删除薪酬项');
|
||||
};
|
||||
|
||||
// 添加薪酬项
|
||||
addSalaryItem = () => {
|
||||
const { salaryItems } = this.state;
|
||||
const newItem = {
|
||||
key: Date.now().toString(),
|
||||
name: '',
|
||||
type: '固定工资',
|
||||
taxType: '税前',
|
||||
formula: ''
|
||||
};
|
||||
this.setState({ salaryItems: [...salaryItems, newItem] });
|
||||
};
|
||||
|
||||
// 保险费率变化
|
||||
handleInsuranceRateChange = (index, field, value) => {
|
||||
const { insuranceRates } = this.state;
|
||||
const newRates = [...insuranceRates];
|
||||
newRates[index][field] = value;
|
||||
this.setState({ insuranceRates: newRates });
|
||||
};
|
||||
|
||||
// 保存规则
|
||||
handleSave = () => {
|
||||
const { form, salaryItems, insuranceRates } = this.state;
|
||||
|
||||
if (!form.ruleName.trim()) {
|
||||
message.error('请输入规则名称');
|
||||
return;
|
||||
}
|
||||
|
||||
// 这里应该调用API保存数据
|
||||
console.log('保存数据:', {
|
||||
form,
|
||||
salaryItems,
|
||||
insuranceRates
|
||||
});
|
||||
|
||||
message.success('薪酬规则保存成功');
|
||||
};
|
||||
|
||||
// 取消操作
|
||||
handleCancel = () => {
|
||||
// 重置表单或返回上一页
|
||||
message.info('已取消操作');
|
||||
};
|
||||
|
||||
render() {
|
||||
const { selectedOrgKeys, expandedKeys, form, salaryItems, insuranceRates } = this.state;
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<div className={styles.mainContent}>
|
||||
<Card className={styles.contentCard}>
|
||||
<Row gutter={24}>
|
||||
{/* 左侧组织架构树 */}
|
||||
<Col span={5}>
|
||||
<Card
|
||||
title={
|
||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', width: '100%' }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center' }}>
|
||||
<TeamOutlined style={{ marginRight: 8, color: '#1890ff' }} />
|
||||
<span>组织架构</span>
|
||||
</div>
|
||||
<Space>
|
||||
<Button
|
||||
type="text"
|
||||
icon={<SyncOutlined />}
|
||||
size="small"
|
||||
style={{ color: '#1890ff' }}
|
||||
onClick={this.handleTreeRefresh}
|
||||
title="刷新"
|
||||
/>
|
||||
<Button
|
||||
type="text"
|
||||
icon={<ExpandOutlined />}
|
||||
size="small"
|
||||
style={{ color: '#1890ff' }}
|
||||
onClick={this.handleTreeToggle}
|
||||
title={expandedKeys.length > 0 ? "收缩全部" : "展开全部"}
|
||||
/>
|
||||
</Space>
|
||||
</div>
|
||||
}
|
||||
className={styles.treeCard}
|
||||
>
|
||||
<div className={styles.treeContainer}>
|
||||
<Tree
|
||||
showIcon
|
||||
selectedKeys={selectedOrgKeys}
|
||||
expandedKeys={expandedKeys}
|
||||
treeData={this.getTreeData()}
|
||||
onSelect={this.onTreeSelect}
|
||||
onExpand={this.onTreeExpand}
|
||||
className={styles.orgTree}
|
||||
/>
|
||||
</div>
|
||||
</Card>
|
||||
</Col>
|
||||
|
||||
{/* 右侧主要内容 */}
|
||||
<Col span={19}>
|
||||
<Card className={styles.mainCard}>
|
||||
{/* 头部操作区 */}
|
||||
<div className={styles.header}>
|
||||
<h2>薪酬计算规则设置</h2>
|
||||
<Space>
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<SaveOutlined />}
|
||||
onClick={this.handleSave}
|
||||
>
|
||||
保存规则
|
||||
</Button>
|
||||
<Button
|
||||
icon={<CloseOutlined />}
|
||||
onClick={this.handleCancel}
|
||||
>
|
||||
取消
|
||||
</Button>
|
||||
</Space>
|
||||
</div>
|
||||
|
||||
{/* 表单内容区 */}
|
||||
<div className={styles.content}>
|
||||
{/* 基本信息 */}
|
||||
<Card title="基本信息" size="small" className={styles.formCard}>
|
||||
<Row gutter={16}>
|
||||
<Col span={8}>
|
||||
<div className={styles.formItem}>
|
||||
<label>规则名称</label>
|
||||
<Input
|
||||
value={form.ruleName}
|
||||
placeholder="请输入规则名称"
|
||||
onChange={(e) => this.handleFormChange('ruleName', e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
</Col>
|
||||
<Col span={8}>
|
||||
<div className={styles.formItem}>
|
||||
<label>适用组织</label>
|
||||
<Select
|
||||
value={form.applicableOrg}
|
||||
style={{ width: '100%' }}
|
||||
onChange={(value) => this.handleFormChange('applicableOrg', value)}
|
||||
suffixIcon={<DownOutlined />}
|
||||
>
|
||||
<Option value="集团公司">集团公司</Option>
|
||||
<Option value="集团公司/北京分公司">集团公司/北京分公司</Option>
|
||||
<Option value="集团公司/上海分公司">集团公司/上海分公司</Option>
|
||||
<Option value="集团公司/深圳分公司">集团公司/深圳分公司</Option>
|
||||
</Select>
|
||||
</div>
|
||||
</Col>
|
||||
<Col span={8}>
|
||||
<div className={styles.formItem}>
|
||||
<label>生效日期</label>
|
||||
<DatePicker
|
||||
value={form.effectiveDate}
|
||||
style={{ width: '100%' }}
|
||||
onChange={(date) => this.handleFormChange('effectiveDate', date)}
|
||||
/>
|
||||
</div>
|
||||
</Col>
|
||||
</Row>
|
||||
</Card>
|
||||
|
||||
{/* 薪酬项设置 */}
|
||||
<Card
|
||||
title="薪酬项设置"
|
||||
size="small"
|
||||
className={styles.formCard}
|
||||
extra={
|
||||
<Button
|
||||
type="link"
|
||||
icon={<PlusOutlined />}
|
||||
onClick={this.addSalaryItem}
|
||||
>
|
||||
添加薪酬项
|
||||
</Button>
|
||||
}
|
||||
>
|
||||
<Table
|
||||
columns={this.salaryColumns}
|
||||
dataSource={salaryItems}
|
||||
pagination={false}
|
||||
size="small"
|
||||
className={styles.salaryTable}
|
||||
/>
|
||||
</Card>
|
||||
|
||||
{/* 社保公积金设置 */}
|
||||
<Card title="社保公积金设置" size="small" className={styles.formCard}>
|
||||
<Row gutter={16} className={styles.socialRow}>
|
||||
<Col span={12}>
|
||||
<div className={styles.formItem}>
|
||||
<label>社保缴纳基数</label>
|
||||
<Select
|
||||
value={form.socialBase}
|
||||
style={{ width: '100%' }}
|
||||
onChange={(value) => this.handleFormChange('socialBase', value)}
|
||||
>
|
||||
<Option value="员工基本工资">员工基本工资</Option>
|
||||
<Option value="员工总工资">员工总工资</Option>
|
||||
<Option value="固定基数">固定基数</Option>
|
||||
</Select>
|
||||
</div>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<div className={styles.formItem}>
|
||||
<label>公积金缴纳基数</label>
|
||||
<Select
|
||||
value={form.fundBase}
|
||||
style={{ width: '100%' }}
|
||||
onChange={(value) => this.handleFormChange('fundBase', value)}
|
||||
>
|
||||
<Option value="员工基本工资">员工基本工资</Option>
|
||||
<Option value="员工总工资">员工总工资</Option>
|
||||
<Option value="固定基数">固定基数</Option>
|
||||
</Select>
|
||||
</div>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
<div className={styles.insuranceTable}>
|
||||
<h4>缴纳比例</h4>
|
||||
<Table
|
||||
columns={this.insuranceColumns}
|
||||
dataSource={insuranceRates}
|
||||
pagination={false}
|
||||
size="small"
|
||||
/>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
{/* 个税设置 */}
|
||||
<Card title="个人所得税设置" size="small" className={styles.formCard}>
|
||||
<Row gutter={16}>
|
||||
<Col span={12}>
|
||||
<div className={styles.formItem}>
|
||||
<label>起征点</label>
|
||||
<Input
|
||||
value={form.taxThreshold}
|
||||
onChange={(e) => this.handleFormChange('taxThreshold', e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<div className={styles.formItem}>
|
||||
<label>专项附加扣除</label>
|
||||
<Select
|
||||
value={form.specialDeduction}
|
||||
style={{ width: '100%' }}
|
||||
onChange={(value) => this.handleFormChange('specialDeduction', value)}
|
||||
>
|
||||
<Option value="自动计算">自动计算</Option>
|
||||
<Option value="手动输入">手动输入</Option>
|
||||
<Option value="不扣除">不扣除</Option>
|
||||
</Select>
|
||||
</div>
|
||||
</Col>
|
||||
</Row>
|
||||
</Card>
|
||||
</div>
|
||||
</Card>
|
||||
</Col>
|
||||
</Row>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default SalaryRuleset
|
||||
export default SalaryRuleset;
|
Loading…
Reference in New Issue