效率管理页面开发

master
jiangxucong 1 day ago
parent ea9f629397
commit 87325fc3ad

@ -128,7 +128,7 @@ export async function getInitialState (type) {
// history.replace({
// pathname: loginPath
// })
/topnavbar00/hrefficiency
// /topnavbar00/hrefficiency
}
}

@ -1,14 +1,686 @@
import React, {Fragment, PureComponent} from 'react';
import React, { PureComponent } from 'react';
import {
Card,
Tree,
Button,
Select,
Space,
Row,
Col,
Pagination,
Modal,
message
} from 'antd';
import { history } from 'umi';
import {
ExpandOutlined,
UserOutlined,
TeamOutlined,
ApartmentOutlined,
SyncOutlined,
DollarOutlined,
SettingOutlined,
DownloadOutlined,
UserAddOutlined,
EditOutlined,
DeleteOutlined
} from '@ant-design/icons';
import styles from './WorkReport.less';
import StandardTable from '@/components/StandardTable';
import WorkReportAdd from './form/WorkReportAdd';
import WorkReportRenderSimpleForm from "./form/WorkReportRenderSimpleForm" //表单
const { Option } = Select;
class WorkReport 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,
workType: '招聘面试',
workPerson: '张三',
department: '人事部',
startTime: '2025-09-03 09:00',
endTime: '2025-09-03 17:30',
duration: '8.5小时',
workQuantity: 3
},
{
key: '2',
id: 2,
workType: '培训组织',
workPerson: '李四',
department: '人事部',
startTime: '2025-09-03 08:30',
endTime: '2025-09-03 18:00',
duration: '9.5小时',
workQuantity: 2
},
{
key: '3',
id: 3,
workType: '绩效考核',
workPerson: '王五',
department: '人事部',
startTime: '2025-09-03 09:15',
endTime: '2025-09-03 17:45',
duration: '8.5小时',
workQuantity: 5
},
{
key: '4',
id: 4,
workType: '薪酬核算',
workPerson: '赵六',
department: '人事部',
startTime: '2025-09-03 08:45',
endTime: '2025-09-03 17:15',
duration: '8.5小时',
workQuantity: 4
},
{
key: '5',
id: 5,
workType: '招聘面试',
workPerson: '陈七',
department: '人事部',
startTime: '2025-09-03 09:30',
endTime: '2025-09-03 16:30',
duration: '7小时',
workQuantity: 1
},
],
pagination: {
current: 1,
pageSize: 5,
total: 356,
},
selectedRecord: null, // 选中的记录,用于编辑
};
// 默认列配置
this.defaultColumns = [
{
title: '序号',
dataIndex: 'id',
key: 'id',
width: 80,
align: 'center',
render: (text, record, index) => (
<span style={{ fontWeight: '500' }}>
{index + 1}
</span>
),
},
{
title: '作业类型',
dataIndex: 'workType',
key: 'workType',
width: 120,
align: 'center',
render: (text) => (
<span style={{
backgroundColor: '#f0f9ff',
border: '1px solid #91d5ff',
borderRadius: '6px',
padding: '2px 8px',
color: '#1890ff',
fontSize: '12px',
fontWeight: '500'
}}>
{text}
</span>
),
},
{
title: '作业人员',
dataIndex: 'workPerson',
key: 'workPerson',
width: 100,
align: 'center',
render: (text, record) => (
<Button
type="link"
style={{ padding: 0, fontWeight: 500, color: '#1890ff' }}
onClick={() => this.handlePersonClick(record)}
>
{text}
</Button>
),
},
{
title: '部门',
dataIndex: 'department',
key: 'department',
width: 120,
align: 'center',
},
{
title: '开始时间',
dataIndex: 'startTime',
key: 'startTime',
width: 150,
align: 'center',
render: (time) => (
<span style={{ color: '#52c41a', fontWeight: '500' }}>
{time}
</span>
),
},
{
title: '结束时间',
dataIndex: 'endTime',
key: 'endTime',
width: 150,
align: 'center',
render: (time) => (
<span style={{ color: '#ff4d4f', fontWeight: '500' }}>
{time}
</span>
),
},
{
title: '持续时长',
dataIndex: 'duration',
key: 'duration',
width: 100,
align: 'center',
render: (duration) => (
<span style={{
backgroundColor: '#f6ffed',
border: '1px solid #b7eb8f',
borderRadius: '6px',
padding: '2px 8px',
color: '#52c41a',
fontWeight: 'bold'
}}>
{duration}
</span>
),
sorter: (a, b) => {
const aHours = parseFloat(a.duration);
const bHours = parseFloat(b.duration);
return aHours - bHours;
},
},
{
title: '作业数量',
dataIndex: 'workQuantity',
key: 'workQuantity',
width: 100,
align: 'center',
render: (quantity) => (
<span style={{
fontWeight: 'bold',
color: quantity >= 4 ? '#52c41a' :
quantity >= 2 ? '#1890ff' : '#faad14'
}}>
{quantity}
</span>
),
sorter: (a, b) => a.workQuantity - b.workQuantity,
},
{
title: '操作',
key: 'action',
width: 120,
align: 'center',
render: (_, record) => (
<Space size="middle">
<Button
type="link"
size="small"
icon={<EditOutlined />}
title="编辑工作报告"
onClick={() => this.handleEdit(record)}
>
编辑
</Button>
<Button
type="link"
size="small"
danger
icon={<DeleteOutlined />}
title="删除记录"
onClick={() => this.handleDelete(record)}
>
删除
</Button>
</Space>
),
},
];
}
// 获取处理后的树形数据
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 (
<WorkReportRenderSimpleForm {...parentMethods} />
);
}
renderAdvancedForm() {
const { prooperlog: { params } } = this.props;
const parentMethods = {
handleSearch: this.handleSearch,
handleFormReset: this.handleFormReset,
toggleForm: this.toggleForm,
params
};
return (
<WorkReportRenderSimpleForm {...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,
selectedRecord: null // 清空选中记录
});
};
// 新增成功回调
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
}
});
};
// 处理作业人员点击事件
handlePersonClick = (record) => {
Modal.info({
title: `${record.workPerson} - 作业详情`,
width: 600,
content: (
<div style={{ marginTop: 16 }}>
<Row gutter={[16, 16]}>
<Col span={12}><strong>作业人员</strong>{record.workPerson}</Col>
<Col span={12}><strong>所属部门</strong>{record.department}</Col>
<Col span={12}><strong>作业类型</strong>{record.workType}</Col>
<Col span={12}><strong>作业数量</strong><span style={{ color: '#1890ff', fontWeight: 'bold' }}>{record.workQuantity}</span></Col>
<Col span={24} style={{ borderTop: '1px solid #f0f0f0', paddingTop: 16, marginTop: 16 }}>
<h4>时间统计</h4>
</Col>
<Col span={12}><strong>开始时间</strong><span style={{ color: '#52c41a' }}>{record.startTime}</span></Col>
<Col span={12}><strong>结束时间</strong><span style={{ color: '#ff4d4f' }}>{record.endTime}</span></Col>
<Col span={12}><strong>持续时长</strong><span style={{
backgroundColor: '#f6ffed',
border: '1px solid #b7eb8f',
borderRadius: '4px',
padding: '2px 6px',
color: '#52c41a',
fontWeight: 'bold'
}}>{record.duration}</span></Col>
<Col span={12}><strong>工作效率</strong><span style={{ color: '#faad14', fontWeight: 'bold' }}>
{(record.workQuantity / parseFloat(record.duration)).toFixed(2)} /小时
</span></Col>
</Row>
</div>
),
okText: '确定'
});
};
// 处理编辑
handleEdit = (record) => {
console.log('编辑工作报告:', record);
this.setState({
selectedRecord: record,
addModalVisible: true
});
};
// 处理删除
handleDelete = (record) => {
Modal.confirm({
title: '删除确认',
content: `确定要删除 "${record.workPerson}" 的工作报告记录吗?`,
okText: '确定',
cancelText: '取消',
onOk: () => {
// 实际项目中这里应该调用删除接口
console.log('删除工作报告记录:', record.id);
const newData = this.state.tableData.filter(item => item.id !== record.id);
this.setState({
tableData: newData,
pagination: {
...this.state.pagination,
total: newData.length
}
});
message.success('工作报告删除成功');
}
});
};
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="middle"
// className={styles.exportButton}
>
导出
</Button>
<Button
type="primary"
icon={<UserAddOutlined />}
size="middle"
// 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>
{/* 新增/编辑绩效弹窗 */}
<WorkReportAdd
visible={addModalVisible}
loading={addLoading}
editData={this.state.selectedRecord}
onCancel={this.hideAddModal}
onSuccess={this.handleAddSuccess}
/>
</div>
);
}
}
export default WorkReport
export default WorkReport;

@ -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,321 @@
import React, { PureComponent } from 'react';
import {
Modal,
Form,
Input,
Select,
InputNumber,
Row,
Col,
message,
DatePicker,
TreeSelect
} from 'antd';
import {
UserOutlined,
TeamOutlined,
ClockCircleOutlined,
NumberOutlined
} from '@ant-design/icons';
import moment from 'moment';
const { Option } = Select;
class WorkReportAdd extends PureComponent {
constructor(props) {
super(props);
this.formRef = React.createRef();
// 组织架构树数据
this.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' }
],
},
],
},
];
}
componentDidUpdate(prevProps) {
// 当编辑数据发生变化时,更新表单值
if (this.props.editData !== prevProps.editData && this.props.editData) {
const editData = this.props.editData;
this.formRef.current?.setFieldsValue({
workType: editData.workType,
workPerson: editData.workPerson,
department: editData.department,
workQuantity: editData.workQuantity,
startTime: editData.startTime ? moment(editData.startTime) : null,
endTime: editData.endTime ? moment(editData.endTime) : null,
workDescription: editData.workDescription || '',
});
}
// 当弹窗关闭时清空表单
if (!this.props.visible && prevProps.visible) {
this.formRef.current?.resetFields();
}
}
// 提交表单
handleSubmit = (values) => {
const { editData } = this.props;
const isEdit = !!editData;
console.log(isEdit ? '编辑工作报告:' : '新增工作报告:', values);
// 处理时间数据
const formattedValues = {
...values,
startTime: values.startTime ? values.startTime.format('YYYY-MM-DD HH:mm:ss') : null,
endTime: values.endTime ? values.endTime.format('YYYY-MM-DD HH:mm:ss') : null,
};
// 计算持续时长
if (values.startTime && values.endTime) {
const duration = moment(values.endTime).diff(moment(values.startTime), 'hours', true);
formattedValues.duration = `${duration.toFixed(1)}小时`;
}
// 如果是编辑保留原有的id
if (isEdit) {
formattedValues.id = editData.id;
formattedValues.key = editData.key;
}
// 这里可以调用API接口保存数据
// 模拟保存成功
message.success(isEdit ? '编辑工作报告成功!' : '新增工作报告成功!');
// 重置表单
this.formRef.current?.resetFields();
// 调用父组件的回调函数
if (this.props.onSuccess) {
this.props.onSuccess(formattedValues);
}
// 关闭弹窗
this.handleCancel();
};
// 取消操作
handleCancel = () => {
// 重置表单
this.formRef.current?.resetFields();
// 调用父组件的关闭回调
if (this.props.onCancel) {
this.props.onCancel();
}
};
render() {
const { visible, loading = false, editData } = this.props;
const isEdit = !!editData;
return (
<Modal
title={isEdit ? "编辑工作报告" : "新增工作报告"}
open={visible}
onOk={() => this.formRef.current?.submit()}
onCancel={this.handleCancel}
width={800}
confirmLoading={loading}
destroyOnClose={true}
maskClosable={false}
>
<Form
ref={this.formRef}
layout="vertical"
onFinish={this.handleSubmit}
initialValues={{
department: '人事部'
}}
>
<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="workPerson"
label="作业人员"
rules={[
{ required: true, message: '请输入作业人员姓名' },
{ min: 2, max: 10, message: '姓名长度为2-10个字符' }
]}
>
<Input
placeholder="请输入作业人员姓名"
prefix={<UserOutlined />}
/>
</Form.Item>
</Col>
</Row>
<Row gutter={16}>
<Col span={12}>
<Form.Item
name="department"
label="部门"
rules={[
{ required: true, message: '请选择部门' }
]}
>
<TreeSelect
placeholder="请选择部门"
treeData={this.treeData}
style={{ width: '100%' }}
dropdownStyle={{ maxHeight: 400, overflow: 'auto' }}
treeDefaultExpandAll
showSearch
treeNodeFilterProp="title"
allowClear
suffixIcon={<TeamOutlined />}
/>
</Form.Item>
</Col>
<Col span={12}>
<Form.Item
name="workQuantity"
label="作业数量"
rules={[
{ required: true, message: '请输入作业数量' }
]}
>
<InputNumber
placeholder="请输入作业数量"
min={1}
max={100}
style={{ width: '100%' }}
prefix={<NumberOutlined />}
/>
</Form.Item>
</Col>
</Row>
<Row gutter={16}>
<Col span={12}>
<Form.Item
name="startTime"
label="开始时间"
rules={[
{ required: true, message: '请选择开始时间' }
]}
>
<DatePicker
showTime
placeholder="请选择开始时间"
style={{ width: '100%' }}
format="YYYY-MM-DD HH:mm:ss"
suffixIcon={<ClockCircleOutlined />}
/>
</Form.Item>
</Col>
<Col span={12}>
<Form.Item
name="endTime"
label="结束时间"
rules={[
{ required: true, message: '请选择结束时间' }
]}
>
<DatePicker
showTime
placeholder="请选择结束时间"
style={{ width: '100%' }}
format="YYYY-MM-DD HH:mm:ss"
suffixIcon={<ClockCircleOutlined />}
/>
</Form.Item>
</Col>
</Row>
<Row gutter={16}>
<Col span={24}>
<Form.Item
name="workDescription"
label="工作描述"
>
<Input.TextArea
placeholder="请输入具体的工作内容描述(可选)"
rows={3}
maxLength={500}
showCount
/>
</Form.Item>
</Col>
</Row>
</Form>
</Modal>
);
}
}
export default WorkReportAdd;

@ -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;

@ -27,7 +27,9 @@ const SystemContentList = (props) => {
defaultKey = getDefaultRoute(routes)
const mathRoute = matchRoutes(routes, pathName)
setRouteActive({ key: mathRoute?.length ? pathName : defaultKey }, routes)
// 如果当前路径匹配到菜单路由,或者是以当前模块开头的路径(如详情页),都不强制跳转
const shouldUseCurrentPath = mathRoute?.length > 0 || pathName.startsWith(tempRoute[0]?.key + '/')
setRouteActive({ key: shouldUseCurrentPath ? pathName : defaultKey }, routes)
}
setMenuItems(newList)
@ -35,19 +37,41 @@ const SystemContentList = (props) => {
const setRouteActive = (value, menu) => {
const curKey = value.key
console.log(curKey,'7777777777777')
const tempMenu = menu ?? menuItems ?? []
const mathRoute = matchRoutes(tempMenu, curKey)
let openKeys = []
let selectedKeys = []
mathRoute?.map((item, index) => {
mathRoute?.length !== index + 1 && (openKeys = [...openKeys, item.pathname])
mathRoute?.length === index + 1 && (selectedKeys = [...selectedKeys, item.pathname])
})
if (mathRoute?.length > 0) {
// 如果找到匹配的菜单路由,正常处理
mathRoute?.map((item, index) => {
mathRoute?.length !== index + 1 && (openKeys = [...openKeys, item.pathname])
mathRoute?.length === index + 1 && (selectedKeys = [...selectedKeys, item.pathname])
})
} else {
// 如果是不在菜单中的路由(如详情页),尝试激活父级菜单
const pathSegments = curKey.split('/').filter(Boolean)
for (let i = pathSegments.length - 1; i > 0; i--) {
const parentPath = '/' + pathSegments.slice(0, i).join('/')
const parentRoute = matchRoutes(tempMenu, parentPath)
if (parentRoute?.length > 0) {
parentRoute?.map((item, index) => {
parentRoute?.length !== index + 1 && (openKeys = [...openKeys, item.pathname])
parentRoute?.length === index + 1 && (selectedKeys = [...selectedKeys, item.pathname])
})
break
}
}
}
setOpenKey(openKeys)
setSelectedKey(selectedKeys)
history.replace(curKey)
// 只有当前路径与目标路径不同时才执行跳转
if (location.pathname !== curKey) {
history.replace(curKey)
}
}
return (

@ -0,0 +1,717 @@
import React, { PureComponent } from 'react';
import {
Card,
Tree,
Input,
Button,
DatePicker,
Select,
Row,
Col,
Space,
Radio,
Checkbox,
InputNumber,
Avatar,
message,
Timeline,
Tag,
Typography,
Divider,
Upload,
Form,
Table,
Pagination
} from 'antd';
import {
SaveOutlined,
CloseOutlined,
PlusCircleOutlined,
DeleteOutlined,
ExpandOutlined,
UserOutlined,
TeamOutlined,
ApartmentOutlined,
SyncOutlined,
DollarOutlined,
SettingOutlined,
CalendarOutlined,
UsergroupAddOutlined,
UserSwitchOutlined,
StopOutlined,
CheckCircleOutlined,
CloseCircleOutlined,
ContactsOutlined,
PrinterOutlined,
CheckOutlined,
ClockCircleOutlined,
LoadingOutlined,
EyeOutlined,
SearchOutlined,
PictureOutlined,
UploadOutlined,
EditOutlined,
ShareAltOutlined,
FilterOutlined,
DownloadOutlined,
PlusOutlined,
CaretDownOutlined,
SortAscendingOutlined,
SortDescendingOutlined
} from '@ant-design/icons';
import ReactECharts from 'echarts-for-react';
import dayjs from 'dayjs';
import styles from './Performance.less';
const { Option } = Select;
const { TextArea } = Input;
const { Title, Text, Paragraph } = Typography;
const { RangePicker } = DatePicker;
class Performance extends PureComponent {
constructor(props) {
super(props);
this.state = {
selectedOrgKeys: ['0-0-4'], // 默认选中人事部
expandedKeys: ['0-0', '0-0-0', '0-0-1', '0-0-2'],
// 绩效数据
performanceData: {
selectedPlan: '2023年Q3季度考核',
currentPage: 1,
pageSize: 10,
total: 128,
// 绩效评分分布数据
scoreDistribution: [
{ name: '0-60分', value: 12, color: '#FF6B6B' },
{ name: '60-80分', value: 45, color: '#4ECDC4' },
{ name: '80-100分', value: 71, color: '#45B7D1' }
],
// 部门绩效平均分数据
departmentScores: {
departments: ['技术研发', '市场营销', '产品运营', '人力资源', '财务中心', '客户服务'],
scores: [89.5, 87.2, 85.8, 84.3, 83.7, 82.1]
},
// 人员绩效明细数据
performanceList: [
{
key: '1',
name: '张明远',
department: '技术研发中心',
totalScore: 96.5,
codeLines: 12500,
documentWords: 8500,
projectAmount: '¥1,250,000',
workHours: 186,
values: 48.0
},
{
key: '2',
name: '李思颖',
department: '市场营销中心',
totalScore: 95.0,
codeLines: 11800,
documentWords: 9200,
projectAmount: '¥1,100,000',
workHours: 182,
values: 47.0
},
{
key: '3',
name: '王浩然',
department: '产品运营中心',
totalScore: 93.5,
codeLines: 11000,
documentWords: 7800,
projectAmount: '¥980,000',
workHours: 180,
values: 47.0
},
{
key: '4',
name: '陈雨桐',
department: '人力资源中心',
totalScore: 92.0,
codeLines: 10500,
documentWords: 9500,
projectAmount: '¥920,000',
workHours: 178,
values: 46.5
},
{
key: '5',
name: '刘子轩',
department: '技术研发中心',
totalScore: 91.5,
codeLines: 9800,
documentWords: 7200,
projectAmount: '¥890,000',
workHours: 175,
values: 46.0
},
{
key: '6',
name: '赵欣然',
department: '客户服务中心',
totalScore: 90.0,
codeLines: 9200,
documentWords: 8500,
projectAmount: '¥850,000',
workHours: 172,
values: 45.5
},
{
key: '7',
name: '孙伟杰',
department: '财务中心',
totalScore: 89.5,
codeLines: 8900,
documentWords: 6800,
projectAmount: '¥820,000',
workHours: 170,
values: 45.0
},
{
key: '8',
name: '周晓彤',
department: '市场营销中心',
totalScore: 88.0,
codeLines: 8500,
documentWords: 7900,
projectAmount: '¥780,000',
workHours: 168,
values: 44.5
},
{
key: '9',
name: '吴宇航',
department: '技术研发中心',
totalScore: 87.5,
codeLines: 8200,
documentWords: 6500,
projectAmount: '¥750,000',
workHours: 165,
values: 44.0
},
{
key: '10',
name: '郑雅文',
department: '产品运营中心',
totalScore: 86.0,
codeLines: 8000,
documentWords: 7800,
projectAmount: '¥720,000',
workHours: 162,
values: 43.5
}
]
},
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', fontSize: '14px' }} />;
if (title.includes('技术') || title.includes('开发') || title.includes('测试')) return <SettingOutlined style={{ color: '#52c41a', fontSize: '14px' }} />;
if (title.includes('产品') || title.includes('设计') || title.includes('体验')) return <TeamOutlined style={{ color: '#fa8c16', fontSize: '14px' }} />;
if (title.includes('运营') || title.includes('市场') || title.includes('客户') || title.includes('商务')) return <TeamOutlined style={{ color: '#eb2f96', fontSize: '14px' }} />;
if (title.includes('财务') || title.includes('会计') || title.includes('审计')) return <DollarOutlined style={{ color: '#722ed1', fontSize: '14px' }} />;
if (title.includes('人事') || title.includes('HR') || title.includes('招聘') || title.includes('培训') || title.includes('薪酬')) return <UserOutlined style={{ color: '#13c2c2', fontSize: '14px' }} />;
return <TeamOutlined style={{ color: '#666', fontSize: '14px' }} />;
};
// 刷新树数据
handleTreeRefresh = () => {
message.info('刷新组织架构数据');
};
// 展开/收缩所有节点
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) });
}
};
// 组织树选择
onTreeSelect = (selectedKeys, info) => {
console.log('选中节点:', selectedKeys, info);
this.setState({ selectedOrgKeys: selectedKeys });
};
// 树展开/收缩
onTreeExpand = (expandedKeys) => {
this.setState({ expandedKeys });
};
// 绩效计划切换
handlePlanChange = (value) => {
this.setState({
performanceData: {
...this.state.performanceData,
selectedPlan: value
}
});
message.success(`切换到: ${value}`);
};
// 部门层级切换
handleDepartmentLevelChange = (value) => {
message.success(`切换到: ${value}`);
};
// 分页变化
handlePageChange = (page, pageSize) => {
this.setState({
performanceData: {
...this.state.performanceData,
currentPage: page,
pageSize: pageSize
}
});
};
// 饼图配置
getPieOption = () => {
const { scoreDistribution } = this.state.performanceData;
return {
animation: false,
tooltip: {
trigger: 'item',
formatter: '{a} <br/>{b}: {c} ({d}%)'
},
legend: {
orient: 'vertical',
right: 10,
top: 'center',
data: scoreDistribution.map(item => item.name)
},
series: [
{
name: '绩效评分分布',
type: 'pie',
radius: ['50%', '70%'],
center: ['40%', '50%'],
avoidLabelOverlap: false,
itemStyle: {
borderRadius: 4,
borderColor: '#fff',
borderWidth: 2
},
label: {
show: false,
position: 'center'
},
emphasis: {
label: {
show: true,
fontSize: '14',
fontWeight: 'bold'
}
},
labelLine: {
show: false
},
data: scoreDistribution.map(item => ({
value: item.value,
name: item.name,
itemStyle: {
color: item.color
}
}))
}
]
};
};
// 柱状图配置
getBarOption = () => {
const { departmentScores } = this.state.performanceData;
const colorList = ['#45B7D1', '#4ECDC4', '#A5DEE4', '#7AC5CD', '#66C6C4', '#85C1E9'];
return {
animation: false,
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'shadow'
}
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
top: '10%',
containLabel: true
},
xAxis: {
type: 'value',
boundaryGap: [0, 0.01]
},
yAxis: {
type: 'category',
data: departmentScores.departments
},
series: [
{
name: '平均分',
type: 'bar',
data: departmentScores.scores.map((score, index) => ({
value: score,
itemStyle: {
color: colorList[index],
borderRadius: [0, 4, 4, 0]
}
}))
}
]
};
};
render() {
const { selectedOrgKeys, expandedKeys, performanceData } = this.state;
// 表格列配置
const columns = [
{
title: (
<div className={styles.tableHeader}>
<span>姓名</span>
<SortAscendingOutlined className={styles.sortIcon} />
</div>
),
dataIndex: 'name',
key: 'name',
sorter: true,
render: (text) => <Text strong>{text}</Text>
},
{
title: (
<div className={styles.tableHeader}>
<span>部门</span>
<SortAscendingOutlined className={styles.sortIcon} />
</div>
),
dataIndex: 'department',
key: 'department',
sorter: true
},
{
title: (
<div className={styles.tableHeader}>
<span>总分</span>
<SortDescendingOutlined className={styles.sortIcon} />
</div>
),
dataIndex: 'totalScore',
key: 'totalScore',
sorter: true,
render: (score) => <Text strong className={styles.scoreText}>{score}</Text>
},
{
title: (
<div className={styles.tableHeader}>
<span>代码行数</span>
<SortAscendingOutlined className={styles.sortIcon} />
</div>
),
dataIndex: 'codeLines',
key: 'codeLines',
sorter: true,
render: (value) => value.toLocaleString()
},
{
title: (
<div className={styles.tableHeader}>
<span>文档字数</span>
<SortAscendingOutlined className={styles.sortIcon} />
</div>
),
dataIndex: 'documentWords',
key: 'documentWords',
sorter: true,
render: (value) => value.toLocaleString()
},
{
title: (
<div className={styles.tableHeader}>
<span>项目金额数</span>
<SortAscendingOutlined className={styles.sortIcon} />
</div>
),
dataIndex: 'projectAmount',
key: 'projectAmount',
sorter: true
},
{
title: (
<div className={styles.tableHeader}>
<span>出勤时长</span>
<SortAscendingOutlined className={styles.sortIcon} />
</div>
),
dataIndex: 'workHours',
key: 'workHours',
sorter: true
},
{
title: (
<div className={styles.tableHeader}>
<span>价值观</span>
<SortAscendingOutlined className={styles.sortIcon} />
</div>
),
dataIndex: 'values',
key: 'values',
sorter: true
}
];
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.content}>
{/* 头部标题和选择器 */}
<div className={styles.dashboardHeader}>
<Title level={3} className={styles.dashboardTitle}>绩效仪表盘</Title>
<div className={styles.planSelector}>
<Text className={styles.selectorLabel}>绩效计划</Text>
<Select
value={performanceData.selectedPlan}
onChange={this.handlePlanChange}
className={styles.planSelect}
suffixIcon={<CaretDownOutlined />}
>
<Option value="2023年Q3季度考核">2023年Q3季度考核</Option>
<Option value="2023年Q2季度考核">2023年Q2季度考核</Option>
<Option value="2023年Q1季度考核">2023年Q1季度考核</Option>
<Option value="2022年年度考核">2022年年度考核</Option>
</Select>
</div>
</div>
{/* 图表区域 */}
<Row gutter={16} className={styles.chartsRow}>
<Col span={12}>
<Card className={styles.chartCard}>
<Title level={4} className={styles.chartTitle}>绩效评分分布</Title>
<div className={styles.chartContainer}>
<ReactECharts
option={this.getPieOption()}
style={{ height: '300px', width: '100%' }}
opts={{ renderer: 'canvas' }}
/>
</div>
</Card>
</Col>
<Col span={12}>
<Card className={styles.chartCard}>
<div className={styles.chartHeader}>
<Title level={4} className={styles.chartTitle}>部门绩效平均分</Title>
<Select
defaultValue="一级部门"
className={styles.departmentSelect}
onChange={this.handleDepartmentLevelChange}
suffixIcon={<CaretDownOutlined />}
>
<Option value="一级部门">一级部门</Option>
<Option value="二级部门">二级部门</Option>
<Option value="三级部门">三级部门</Option>
</Select>
</div>
<div className={styles.chartContainer}>
<ReactECharts
option={this.getBarOption()}
style={{ height: '300px', width: '100%' }}
opts={{ renderer: 'canvas' }}
/>
</div>
</Card>
</Col>
</Row>
{/* 人员绩效明细表 */}
<Card className={styles.tableCard}>
<Title level={4} className={styles.tableTitle}>人员绩效明细</Title>
<div className={styles.tableWrapper}>
<Table
columns={columns}
dataSource={performanceData.performanceList}
pagination={false}
className={styles.performanceTable}
scroll={{ y: 400 }}
onRow={(record) => ({
className: styles.tableRowHover
})}
/>
{/* 分页 */}
<div className={styles.paginationContainer}>
<div className={styles.paginationInfo}>
显示 {((performanceData.currentPage - 1) * performanceData.pageSize) + 1} {' '}
{Math.min(performanceData.currentPage * performanceData.pageSize, performanceData.total)}
{performanceData.total} 条记录
</div>
<Pagination
current={performanceData.currentPage}
pageSize={performanceData.pageSize}
total={performanceData.total}
onChange={this.handlePageChange}
showSizeChanger={false}
className={styles.pagination}
/>
</div>
</div>
</Card>
</div>
</Card>
</Col>
</Row>
</Card>
</div>
</div>
);
}
}
export default Performance;

@ -0,0 +1,429 @@
@import '~@/utils/utils.less';
// 使用与OnBoarding相同的基础样式
.container {
min-height: 100vh;
height: 100vh;
overflow: hidden;
background-color: #f5f6fa;
}
.mainContent {
height: 100vh;
overflow: hidden;
.contentCard {
height: 100%;
border: none !important;
:global(.ant-card-body) {
padding: 12px 24px;
// height: calc(100vh - 24px);
// overflow: hidden;
}
}
}
// 组织架构树样式 - 与OnBoarding保持一致
.treeCard {
height: 600px;
border: 1px solid #e8e8e8;
border-radius: 8px;
.treeContainer {
height: 520px;
overflow-y: auto;
&::-webkit-scrollbar {
width: 6px;
}
&::-webkit-scrollbar-track {
background: rgba(0, 0, 0, 0.02);
}
&::-webkit-scrollbar-thumb {
background: rgba(0, 0, 0, 0.15);
border-radius: 3px;
&:hover {
background: rgba(0, 0, 0, 0.25);
}
}
}
.orgTree {
.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);
}
}
}
}
.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;
}
}
// 主卡片样式
.mainCard {
height: calc(100vh - 60px);
border: 1px solid #e8e8e8;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
overflow: hidden;
.ant-card-body {
padding: 0;
height: 100%;
overflow: hidden;
display: flex;
flex-direction: column;
}
}
// 内容区域
.content {
flex: 1;
padding: 20px 24px 28px 24px;
overflow-y: auto;
overflow-x: hidden;
min-height: 0; // 关键允许flex子元素收缩
height: calc(100vh - 110px); // 减去头部高度和其他空间,强制设置高度
max-height: calc(100vh - 110px); // 明确最大高度,确保滚动条出现
&::-webkit-scrollbar {
width: 8px;
}
&::-webkit-scrollbar-track {
background: rgba(0, 0, 0, 0.02);
border-radius: 4px;
}
&::-webkit-scrollbar-thumb {
background: rgba(0, 0, 0, 0.18);
border-radius: 4px;
&:hover {
background: rgba(0, 0, 0, 0.28);
}
}
scrollbar-width: thin;
scrollbar-color: rgba(0, 0, 0, 0.18) rgba(0, 0, 0, 0.02);
}
// 仪表盘头部
.dashboardHeader {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 24px;
.dashboardTitle {
margin: 0 !important;
font-size: 20px !important;
font-weight: 700 !important;
color: #333 !important;
}
.planSelector {
display: flex;
align-items: center;
.selectorLabel {
font-size: 14px;
color: #666;
margin-right: 8px;
}
.planSelect {
min-width: 180px;
.ant-select-selector {
border-radius: 4px;
background: #f5f6fa;
border: 1px solid #e8e8e8;
&:hover {
border-color: #2d5cf6;
}
}
}
}
}
// 图表区域
.chartsRow {
margin-bottom: 24px;
}
.chartCard {
border: 1px solid #e8e8e8;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
.ant-card-body {
padding: 20px;
}
.chartHeader {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16px;
.departmentSelect {
min-width: 120px;
.ant-select-selector {
border-radius: 4px;
background: #f5f6fa;
border: 1px solid #e8e8e8;
&:hover {
border-color: #2d5cf6;
}
}
}
}
.chartTitle {
margin: 0 0 16px 0 !important;
font-size: 16px !important;
font-weight: 600 !important;
color: #333 !important;
}
.chartContainer {
height: 300px;
// echarts-for-react容器样式
.echarts-for-react {
height: 100% !important;
width: 100% !important;
}
// 确保ECharts容器正确显示
> div {
height: 100% !important;
width: 100% !important;
}
}
}
// 表格卡片
.tableCard {
border: 1px solid #e8e8e8;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
.ant-card-body {
padding: 20px;
}
.tableTitle {
margin: 0 0 16px 0 !important;
font-size: 16px !important;
font-weight: 600 !important;
color: #333 !important;
}
}
// 表格样式
.tableWrapper {
.performanceTable {
.ant-table {
border: 1px solid #e8e8e8;
border-radius: 8px;
overflow: hidden;
.ant-table-thead > tr > th {
background-color: #fafafa;
border-bottom: 1px solid #e8e8e8;
padding: 12px 16px;
font-weight: 600;
color: #333;
font-size: 12px;
text-transform: uppercase;
letter-spacing: 0.5px;
&:hover {
background-color: #f5f6fa;
}
}
.ant-table-tbody > tr {
transition: all 0.3s ease;
&:hover {
background-color: #f8f9fa !important;
transform: translateY(-1px);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
> td {
border-bottom: 1px solid #f0f0f0;
padding: 12px 16px;
font-size: 14px;
color: #333;
}
}
}
}
}
// 表格头部样式
.tableHeader {
display: flex;
align-items: center;
cursor: pointer;
.sortIcon {
margin-left: 4px;
font-size: 10px;
color: #bfbfbf;
opacity: 0.6;
}
&:hover .sortIcon {
color: #2d5cf6;
opacity: 1;
}
}
// 总分文字样式
.scoreText {
color: #2d5cf6 !important;
font-weight: 600 !important;
}
// 分页容器
.paginationContainer {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 16px;
padding-top: 16px;
border-top: 1px solid #f0f0f0;
.paginationInfo {
color: #666;
font-size: 14px;
}
.pagination {
.ant-pagination-item {
border-radius: 4px;
&.ant-pagination-item-active {
background: #2d5cf6;
border-color: #2d5cf6;
}
}
.ant-pagination-prev,
.ant-pagination-next {
border-radius: 4px;
}
}
}
// 响应式设计
@media (max-width: 1200px) {
.treeCard {
height: 300px;
}
.mainCard {
height: auto;
max-height: 80vh;
min-height: 500px;
.content {
max-height: 65vh;
&::-webkit-scrollbar {
width: 4px;
}
}
}
.dashboardHeader {
flex-direction: column;
align-items: flex-start;
gap: 16px;
}
.chartsRow {
.chartCard {
margin-bottom: 16px;
.chartContainer {
height: 250px;
}
}
}
.chartHeader {
flex-direction: column;
align-items: flex-start;
gap: 12px;
}
}
// 页面特定样式
.container {
.ant-btn-primary {
border-radius: 6px;
font-weight: 500;
transition: all 0.3s ease;
}
.ant-btn:not(.ant-btn-primary) {
border-radius: 6px;
transition: all 0.3s ease;
&:hover,
&:focus {
border-color: #4c7bff;
color: #2d5cf6;
}
}
// 选择器样式优化
.ant-select-focused .ant-select-selector {
border-color: #2d5cf6 !important;
box-shadow: 0 0 0 2px rgba(45, 92, 246, 0.2) !important;
}
}

@ -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;

@ -0,0 +1,489 @@
.particularsContainer {
background: #f5f6fa;
min-width: 70vw;
min-height: 100vh;
max-height: 100vh;
overflow-y: auto;
overflow-x: hidden;
padding: 16px 24px 32px 24px;
}
// 最外层容器样式
:global(.salary-detail-main-container) {
// 设置固定高度以启用滚动考虑padding
height: 100vh;
overflow-x: hidden; // 防止横向滚动条
overflow-y: auto; // 允许纵向滚动
box-sizing: border-box; // 包含padding在高度计算内
// 滚动条样式
&::-webkit-scrollbar {
width: 8px;
}
&::-webkit-scrollbar-track {
background: rgba(0,0,0,0.05);
border-radius: 4px;
}
&::-webkit-scrollbar-thumb {
background: rgba(0,0,0,0.2);
border-radius: 4px;
&:hover {
background: rgba(0,0,0,0.3);
}
}
}
// 最外层卡片样式优化
:global(.salary-detail-card) {
// Card高度自适应让外层容器处理滚动
width: 100%;
margin-bottom: 24px; // 确保底部有足够间距,不被截断
// Card body 样式优化
.ant-card-body {
padding: 24px;
padding-bottom: 32px; // 增加底部padding确保内容不被截断
// 确保内容之间有适当的间距
> * {
margin-bottom: 24px;
&:last-child {
margin-bottom: 0;
}
}
}
// 表格在容器内的优化
.ant-table-wrapper {
// 确保表格可以水平滚动
overflow-x: auto;
margin-bottom: 16px; // 添加底部间距
// 表格滚动条样式
&::-webkit-scrollbar {
height: 6px;
}
&::-webkit-scrollbar-track {
background: #f1f1f1;
border-radius: 3px;
}
&::-webkit-scrollbar-thumb {
background: #c1c1c1;
border-radius: 3px;
&:hover {
background: #a8a8a8;
}
}
.ant-table-container {
overflow: visible;
}
.ant-table {
overflow: visible;
// 确保表格最小宽度
min-width: 800px;
}
}
// 考勤标签区域优化
.attendance-tags {
margin-top: 16px;
margin-bottom: 24px; // 确保底部有足够空间
.ant-tag {
margin-bottom: 8px; // 为换行的标签添加底部间距
}
}
}
// 薪资相关样式
:global(.salary-total-row) {
background: #1890ff !important;
td {
background: #1890ff !important;
color: #fff !important;
font-weight: bold !important;
}
&:hover td {
background: #40a9ff !important;
}
}
:global(.salary-subtotal-row) {
background: #f5f5f5 !important;
td {
background: #f5f5f5 !important;
font-weight: 500 !important;
}
&:hover td {
background: #e6f7ff !important;
}
}
:global(.salary-normal-row) {
&:hover td {
background: #fafafa !important;
}
}
.announcementBar {
background: #fff;
box-shadow: 0 2px 8px #f0f1f2;
padding: 8px 24px;
font-size: 14px;
color: #666;
display: flex;
align-items: center;
gap: 24px;
}
.announcementTitle {
color: #2d5cf6;
font-weight: 500;
margin-right: 8px;
}
.announcementItem {
margin-right: 16px;
}
.breadcrumb {
margin: 24px 0 8px 0;
font-size: 13px;
}
.backBtn {
margin-bottom: 12px;
}
.profileCard {
margin-bottom: 24px;
border-radius: 12px;
box-shadow: 0 2px 8px #f0f1f2;
}
.avatarCol {
display: flex;
align-items: center;
justify-content: center;
}
.profileInfoHeader {
padding: 8px 0;
}
.nameSection {
display: flex;
align-items: center;
gap: 12px;
margin-bottom: 16px;
}
.profileName {
font-size: 24px;
font-weight: bold;
color: #222;
}
.profileDept {
color: #888;
font-size: 15px;
margin-left: 4px;
}
.profileDetails {
margin-top: 8px;
}
.detailItem {
display: flex;
flex-direction: column;
gap: 4px;
}
.detailLabel {
font-size: 13px;
color: #888;
font-weight: 500;
}
.detailValue {
font-size: 15px;
color: #333;
font-weight: 600;
}
.statusTag {
margin: 0;
font-weight: 500;
}
.actionCol {
display: flex;
align-items: center;
justify-content: center;
}
.actionButtons {
display: flex;
flex-direction: column;
gap: 12px;
width: 100%;
max-width: 120px;
}
.editBtn {
width: 100%;
height: 40px;
border-radius: 8px;
font-weight: 500;
}
.printBtn {
width: 100%;
height: 40px;
border-radius: 8px;
font-weight: 500;
}
@media (max-width: 768px) {
.actionButtons {
flex-direction: row;
max-width: none;
}
.nameSection {
flex-wrap: wrap;
gap: 8px;
}
// 移动端优化
:global(.salary-detail-main-container) {
padding: 16px !important;
}
:global(.salary-detail-card) {
// 移动端Card样式
.ant-card-body {
padding: 16px;
padding-bottom: 24px; // 移动端也需要底部间距
}
// 移动端表格水平滚动
.ant-table-wrapper {
.ant-table {
min-width: 600px; // 移动端最小宽度
}
}
// 移动端考勤标签优化
.attendance-tags {
margin-bottom: 20px;
}
}
}
.infoCard {
margin-bottom: 24px;
border-radius: 12px;
box-shadow: 0 2px 8px #f0f1f2;
// 表格包装器样式
.tableWrapper {
margin-top: 16px;
border-radius: 8px;
overflow: hidden;
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.05);
// 自定义滚动条样式
&::-webkit-scrollbar {
width: 6px;
height: 6px;
}
&::-webkit-scrollbar-track {
background: #f5f5f5;
border-radius: 3px;
}
&::-webkit-scrollbar-thumb {
background: #d9d9d9;
border-radius: 3px;
&:hover {
background: #bfbfbf;
}
}
}
// 自定义表格样式
.customTable {
.ant-table-wrapper {
.ant-table-thead > tr > th {
background-color: #2c3e50 !important;
color: #ecf0f1 !important;
font-weight: 600;
border-bottom: 1px solid #34495e;
position: sticky;
top: 0;
z-index: 1;
text-align: center;
font-size: 14px;
padding: 16px 12px;
letter-spacing: 0.5px;
}
.ant-table-tbody > tr {
&:hover > td {
background-color: #f8f9ff !important;
}
&:nth-child(even) {
background-color: #fafbfc;
}
> td {
border-bottom: 1px solid #e8e8e8;
padding: 14px 12px;
font-size: 13px;
color: #333;
vertical-align: top;
line-height: 1.6;
}
}
.ant-table {
border-radius: 8px;
overflow: hidden;
border: 1px solid #e8e8e8;
}
.ant-table-container {
border-radius: 8px;
}
}
}
// 表格通用样式(为了向后兼容)
.ant-table-wrapper {
.ant-table-thead > tr > th {
background-color: #2c3e50 !important;
color: #ecf0f1 !important;
font-weight: 600;
border-bottom: 1px solid #34495e;
position: sticky;
top: 0;
z-index: 1;
text-align: center;
font-size: 13px;
}
.ant-table-tbody > tr {
&:hover > td {
background-color: #f8f9ff !important;
}
> td {
border-bottom: 1px solid #e8e8e8;
padding: 12px 8px;
font-size: 13px;
color: #333;
vertical-align: top;
}
}
.ant-table {
border-radius: 8px;
overflow: hidden;
}
}
}
.careerTimeline {
margin-top: 16px;
padding-left: 12px;
border-left: 2px solid #2d5cf6;
}
.timelineItem {
position: relative;
margin-bottom: 32px;
padding-left: 24px;
}
.timelineDot {
position: absolute;
left: -13px;
top: 8px;
width: 12px;
height: 12px;
background: #d9d9d9;
border-radius: 50%;
border: 2px solid #fff;
box-shadow: 0 0 0 2px #2d5cf6;
}
.timelineDotActive {
background: #2d5cf6;
box-shadow: 0 0 0 2px #2d5cf6;
}
.timelineContent {
margin-left: 8px;
}
.timelineHeader {
display: flex;
align-items: center;
gap: 8px;
font-weight: 500;
font-size: 16px;
}
.timelineCompany {
color: #222;
}
.timelinePosition {
color: #555;
font-size: 15px;
margin-top: 2px;
}
.timelineDuration {
color: #888;
font-size: 13px;
margin-top: 2px;
}
.timelineDescription {
color: #444;
font-size: 14px;
margin-top: 4px;
}
.orgChartWrapper {
display: flex;
align-items: center;
justify-content: center;
background: #f5f6fa;
border-radius: 8px;
padding: 16px;
min-height: 180px;
}
.orgChartImg {
max-width: 100%;
max-height: 220px;
object-fit: contain;
}
.footer {
margin-top: 32px;
text-align: center;
color: #888;
font-size: 13px;
}
.footerLinks {
margin-bottom: 8px;
display: flex;
flex-wrap: wrap;
gap: 16px;
justify-content: center;
}
.footerLinks a {
color: #888;
text-decoration: none;
transition: color 0.2s;
}
.footerLinks a:hover {
color: #2d5cf6;
}
.footerCopyright {
margin-top: 4px;
}

@ -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;

@ -1,10 +1,547 @@
@import '~@/utils/utils.less';
.frameContent {
width: 100%;
// 容器样式 - 参考SalaryData的staffInfoContainer
.container {
min-height: 100vh;
height: 100vh;
overflow: hidden;
// background-color: #f5f6fa;
}
// 主内容区域 - 参考SalaryData的mainContent布局
.mainContent {
height: 100vh;
border: none;
display: block;
margin: 0;
padding: 0;
overflow: hidden;
:global {
.ant-card-body {
padding: 12px 24px;
}
}
.contentCard {
height: 100%;
border: none !important;
.ant-card-body {
padding: 12px 24px;
height: calc(100vh - 24px);
overflow: hidden;
}
.ant-card-head {
.ant-card-head-title {
font-size: 18px;
font-weight: 600;
color: #333;
}
}
}
}
// 组织架构树卡片样式 - 对应左侧Col span={5}完全参考SalaryData样式
.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: rgba(0, 0, 0, 0.02);
}
&::-webkit-scrollbar-thumb {
background: rgba(0, 0, 0, 0.15);
border-radius: 3px;
&:hover {
background: rgba(0, 0, 0, 0.25);
}
}
.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;
}
}
// 主要内容卡片样式 - 对应右侧Col span={19},优化滚动显示
.mainCard {
height: calc(100vh - 60px); // 减少预留空间,最大化可用高度
min-height: 700px; // 增加最小高度确保内容充分显示
border: 1px solid #e8e8e8;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
overflow: hidden; // 确保卡片本身不产生滚动条
.ant-card-body {
padding: 0;
height: 100%;
overflow: hidden;
display: flex;
flex-direction: column;
}
}
// 头部区域
.header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 16px 24px;
border-bottom: 1px solid #f0f0f0;
flex-shrink: 0;
height: 64px; // 固定头部高度
h2 {
margin: 0;
font-size: 18px;
font-weight: 600;
color: #333;
}
}
// 内容区域 - 完全展示所有内容,超出时显示滚动条
.content {
flex: 1;
padding: 20px 24px 28px 24px; // 增加上下内边距,提升整体视觉效果
overflow-y: auto;
overflow-x: hidden;
min-height: 0; // 关键允许flex子元素收缩
height: calc(100vh - 160px); // 减去头部高度和其他空间,强制设置高度
max-height: calc(100vh - 160px); // 明确最大高度,确保滚动条出现
/* 使用细滚动条,浅色调 */
&::-webkit-scrollbar {
width: 8px;
height: 8px;
}
&::-webkit-scrollbar-track {
background: rgba(0, 0, 0, 0.02);
border-radius: 4px;
}
&::-webkit-scrollbar-thumb {
background: rgba(0, 0, 0, 0.18);
border-radius: 4px;
&:hover {
background: rgba(0, 0, 0, 0.28);
}
&:active {
background: rgba(0, 0, 0, 0.35);
}
}
/* 滚动条角落 */
&::-webkit-scrollbar-corner {
background: rgba(0, 0, 0, 0.02);
}
// Firefox滚动条设置为细和浅色
scrollbar-width: thin;
scrollbar-color: rgba(0, 0, 0, 0.18) rgba(0, 0, 0, 0.02);
// 第一个卡片顶部间距
> :first-child {
margin-top: 0;
}
// 最后一个卡片底部间距
> :last-child {
margin-bottom: 0;
}
}
// 表单卡片 - 确保内容完全显示
.formCard {
margin-bottom: 20px; // 增加卡片间距,提升视觉层次感
border-radius: 8px;
border: 1px solid #e8e8e8;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
width: 100%;
.ant-card-head {
border-bottom: 1px solid #f0f0f0;
padding: 12px 20px; // 增加头部内边距
min-height: 44px; // 增加头部高度
flex-shrink: 0; // 防止头部被压缩
.ant-card-head-title {
font-size: 15px;
font-weight: 600;
color: #333;
line-height: 20px;
}
}
.ant-card-body {
padding: 20px; // 增加内边距提升视觉效果
// 确保表格和其他内容正确显示
.ant-table-wrapper {
margin: 0;
}
.ant-row {
margin-bottom: 0;
.ant-col {
margin-bottom: 16px; // 增加列间距
}
}
}
&:last-child {
margin-bottom: 24px; // 最后一个卡片底部留一些空间
}
}
// 表单项
.formItem {
margin-bottom: 16px; // 增加表单项间距
&:last-child {
margin-bottom: 0;
}
label {
display: block;
font-weight: 500;
color: #333;
font-size: 14px;
margin-bottom: 6px; // 增加标签和输入框间距
}
}
// 薪酬项表格 - 支持滚动显示
.salaryTable {
width: 100%;
overflow-x: auto; // 水平滚动支持
/* 使用细滚动条,浅色调 */
&::-webkit-scrollbar {
width: 6px;
height: 6px;
}
&::-webkit-scrollbar-track {
background: rgba(0, 0, 0, 0.02);
border-radius: 3px;
}
&::-webkit-scrollbar-thumb {
background: rgba(0, 0, 0, 0.15);
border-radius: 3px;
&:hover {
background: rgba(0, 0, 0, 0.25);
}
}
.ant-table {
min-width: 600px; // 确保表格最小宽度
}
.ant-table-thead > tr > th {
background-color: #fafafa;
font-weight: 600;
color: #333;
border-bottom: 1px solid #e8e8e8;
white-space: nowrap; // 防止表头文字换行
}
.ant-table-tbody > tr {
&:hover > td {
background-color: #f5f5f5 !important;
}
> td {
border-bottom: 1px solid #f0f0f0;
white-space: nowrap; // 防止单元格内容换行影响布局
}
}
}
// 社保公积金设置
.socialRow {
margin-bottom: 20px; // 增加社保行间距
}
// 保险表格 - 支持滚动显示
.insuranceTable {
margin-top: 20px; // 增加表格顶部间距
margin-bottom: 8px; // 增加表格底部间距
width: 100%;
overflow-x: auto; // 水平滚动支持
/* 使用细滚动条,浅色调 */
&::-webkit-scrollbar {
width: 6px;
height: 6px;
}
&::-webkit-scrollbar-track {
background: rgba(0, 0, 0, 0.02);
border-radius: 3px;
}
&::-webkit-scrollbar-thumb {
background: rgba(0, 0, 0, 0.15);
border-radius: 3px;
&:hover {
background: rgba(0, 0, 0, 0.25);
}
}
h4 {
margin-bottom: 12px; // 增加标题下间距
font-size: 14px;
font-weight: 600;
color: #333;
}
.ant-table {
min-width: 500px; // 确保表格最小宽度
}
.ant-table-thead > tr > th {
background-color: #fafafa;
font-weight: 600;
white-space: nowrap; // 防止表头文字换行
color: #333;
border-bottom: 1px solid #e8e8e8;
}
.ant-table-tbody > tr {
> td {
border-bottom: 1px solid #f0f0f0;
}
}
}
// 滚动动画
@keyframes scroll {
0% {
transform: translateX(0);
}
100% {
transform: translateX(-50%);
}
}
// 响应式设计
@media (max-width: 1200px) {
.mainContent {
flex-direction: column;
height: auto;
min-height: calc(100vh - 32px);
// padding: 8px;
gap: 8px;
}
// 响应式样式:小屏幕下组织架构树卡片高度调整
.treeCard {
height: 300px;
}
// 响应式样式:小屏幕下主要内容卡片高度和滚动优化
.mainCard {
height: auto;
max-height: 80vh; // 使用视口高度的80%,提供更多滚动空间
min-height: 500px;
.content {
max-height: 65vh; // 确保内容区域不会过高
// 小屏幕下使用更细更浅的滚动条
&::-webkit-scrollbar {
width: 4px;
}
&::-webkit-scrollbar-track {
background: rgba(0, 0, 0, 0.01);
}
&::-webkit-scrollbar-thumb {
background: rgba(0, 0, 0, 0.12);
border-radius: 2px;
&:hover {
background: rgba(0, 0, 0, 0.20);
}
}
}
}
// 响应式表格滚动条
.salaryTable,
.insuranceTable {
&::-webkit-scrollbar {
height: 4px;
}
&::-webkit-scrollbar-track {
background: rgba(0, 0, 0, 0.01);
}
&::-webkit-scrollbar-thumb {
background: rgba(0, 0, 0, 0.12);
border-radius: 2px;
&:hover {
background: rgba(0, 0, 0, 0.20);
}
}
}
}
// 页面特定样式覆盖 - 限制在container内部避免影响其他页面
.container {
// 自定义主题色 - 只影响当前页面
.ant-btn-primary {
background: linear-gradient(135deg, #2d5cf6 0%, #4c7bff 100%);
border: none;
border-radius: 6px;
font-weight: 500;
font-size: 14px;
box-shadow: 0 2px 8px rgba(45, 92, 246, 0.3);
transition: all 0.3s ease;
&:hover,
&:focus {
background: linear-gradient(135deg, #4c7bff 0%, #6b8fff 100%);
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(45, 92, 246, 0.4);
}
&:active {
transform: translateY(0);
}
}
.ant-btn:not(.ant-btn-primary) {
background: #fff;
border: 1px solid #d9d9d9;
color: #666;
border-radius: 6px;
font-weight: 500;
font-size: 14px;
transition: all 0.3s ease;
&:hover,
&:focus {
border-color: #4c7bff;
color: #2d5cf6;
}
}
.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;
}
.ant-tree .ant-tree-node-selected {
background-color: #e6f7ff !important;
}
// 链接按钮样式,限制在当前容器内
.ant-btn-link {
padding: 0 4px;
font-size: 12px;
color: #ff4d4f;
&:hover,
&:focus {
color: #ff7875;
}
}
}

@ -18,6 +18,11 @@ const menuItem = [
key: '/topnavbar00/multidstatistics/staffstatistics',
// icon: <SettingOutlined />,
},
{
label: '绩效仪表盘',
key: '/topnavbar00/multidstatistics/performance',
// icon: <SettingOutlined />,
},
]
},
{
@ -31,26 +36,55 @@ const menuItem = [
// icon: <SettingOutlined />,
},
{
label: '人员履历',
key: '/topnavbar00/staffmanage/staffresume',
label: '入职管理',
key: '/topnavbar00/staffmanage/onboarding',
// icon: <SettingOutlined />,
},
{
label: '入职登记',
key: '/topnavbar00/staffmanage/onboardingregister',
// icon: <SettingOutlined />,
},
{
label: '入职审批',
key: '/topnavbar00/staffmanage/onboardingapproval',
// icon: <SettingOutlined />,
},
{
label: '人员调整',
key: '/topnavbar00/staffmanage/staffadjust',
label: '异动审批',
key: '/topnavbar00/staffmanage/resignation',
// icon: <SettingOutlined />,
},
{
label: '工作流',
key: '/topnavbar00/staffmanage/workflow',
label: '培训管理',
key: '/topnavbar00/staffmanage/training',
// icon: <SettingOutlined />,
},
{
label: '',
// label: '详情页',
key: '/topnavbar00/staffmanage/particulars',
label: '培训计划',
key: '/topnavbar00/staffmanage/trainingplan',
// icon: <SettingOutlined />,
},
// {
// label: '人员履历',
// key: '/topnavbar00/staffmanage/staffresume',
// // icon: <SettingOutlined />,
// },
// {
// label: '人员调整',
// key: '/topnavbar00/staffmanage/staffadjust',
// // icon: <SettingOutlined />,
// },
// {
// label: '工作流',
// key: '/topnavbar00/staffmanage/workflow',
// // icon: <SettingOutlined />,
// },
// {
// label: '详情页',
// key: '/topnavbar00/staffmanage/particulars',
// // icon: <SettingOutlined />,
// },
]
},
{
@ -63,6 +97,11 @@ const menuItem = [
key: '/topnavbar00/organmanage/organchart',
// icon: <SettingOutlined />,
},
{
label: '汇报关系',
key: '/topnavbar00/organmanage/reportrelation',
// icon: <SettingOutlined />,
},
{
label: '部门维护',
key: '/topnavbar00/organmanage/deptmaintain',
@ -79,6 +118,11 @@ const menuItem = [
label: '考勤数据',
key: '/topnavbar00/attendancemanage/attendancedata',
// icon: <SettingOutlined />,
},
{
label: '考勤详情及排查',
key: '/topnavbar00/attendancemanage/attendancedetails',
// icon: <SettingOutlined />,
}
]
},
@ -93,12 +137,12 @@ const menuItem = [
// icon: <SettingOutlined />,
},
{
label: '绩效规则设置',
label: '绩效参数设置',
key: '/topnavbar00/performancemanage/performanceruleset',
// icon: <SettingOutlined />,
},
{
label: '绩效定',
label: '绩效定',
key: '/topnavbar00/performancemanage/performanceassess',
// icon: <SettingOutlined />,
}
@ -159,15 +203,15 @@ const menuItem = [
]
},
{
label: '知识库',
label: '知识库管理',
key: '/topnavbar00/knowledgebase',
// icon: <SettingOutlined />,
children: [
{
label: '问答知识库',
key: '/topnavbar00/knowledgebase/qaknowledgebase',
// icon: <SettingOutlined />,
},
// {
// label: '问答知识库',
// key: '/topnavbar00/knowledgebase/qaknowledgebase',
// // icon: <SettingOutlined />,
// },
{
label: '文档知识库',
key: '/topnavbar00/knowledgebase/docknowledgebase',
@ -181,7 +225,7 @@ const menuItem = [
// icon: <SettingOutlined />,
children: [
{
label: '数据字',
label: '数据字',
key: '/topnavbar00/backendmanage/datadict',
// icon: <SettingOutlined />,
},
@ -237,6 +281,7 @@ const TopNavBar = (props) => {
const setRouteActive = value => {
const curKey = value.key
console.log(curKey, '6666666666666')
const activeKeys = menuItems?.filter(item => curKey.indexOf(item.key) !== -1)[0]?.key ?? ''
setActiveKey(activeKeys)

Loading…
Cancel
Save