薪酬管理页面开发

master
jiangxucong 1 day ago
parent 1f512f9f03
commit ea9f629397

@ -1,14 +1,349 @@
import React, {Fragment, PureComponent} from 'react';
import React, { PureComponent } from 'react';
import { Card, Input, Button, Select, Row, Col, Avatar, Rate, message } from 'antd';
import { SearchOutlined, ReloadOutlined, SaveOutlined, RollbackOutlined, UserOutlined } from '@ant-design/icons';
import { history } from 'umi';
import styles from './PerformanceAssess.less';
const { TextArea } = Input;
const { Option } = Select;
class PerformanceAssess extends PureComponent {
constructor(props) {
super(props);
this.state = {
// 筛选条件
searchForm: {
name: '',
phone: '',
idCard: '',
performancePlan: '2025年度'
},
// 员工信息
employeeInfo: {
gender: '男',
age: 32,
department: '技术研发中心',
position: '高级软件工程师',
planName: '2025年度技术岗绩效计划'
},
// 绩效数据
performanceData: {
totalScore: 92,
status: '已确认',
objective: {
codeLines: '15,672',
docWords: '28,500',
trainings: '8',
projectAmount: '¥1,250,000',
projectRatio: '35%',
workHours: '1,850'
},
subjective: {
execution: {
score: 4.0,
comment: '该员工能够高效执行分配的任务,在项目开发过程中表现出色,能够按时保质完成工作。在团队协作中主动承担责任,遇到问题能够积极寻求解决方案。'
},
loyalty: {
score: 4.5,
comment: '该员工高度认同公司文化和价值观,积极参与公司组织的各项活动。在日常工作中能够主动传播正能量,对新员工起到良好的示范作用。对公司发展战略有深刻理解并积极支持。'
}
}
}
};
}
// 处理搜索表单变化
handleSearchFormChange = (field, value) => {
this.setState(prevState => ({
searchForm: {
...prevState.searchForm,
[field]: value
}
}));
};
// 处理主观评价变化
handleSubjectiveChange = (type, field, value) => {
this.setState(prevState => ({
performanceData: {
...prevState.performanceData,
subjective: {
...prevState.performanceData.subjective,
[type]: {
...prevState.performanceData.subjective[type],
[field]: value
}
}
}
}));
};
// 查询按钮处理
handleSearch = () => {
message.success('查询成功');
};
// 重置按钮处理
handleReset = () => {
this.setState({
searchForm: {
name: '',
phone: '',
idCard: '',
performancePlan: '2025年度'
}
});
message.info('已重置查询条件');
};
// 保存评价
handleSave = () => {
message.success('评价已保存');
};
// 返回列表
handleBack = () => {
history.push('/topnavbar00/performancemanage/performancelist');
};
render() {
const { searchForm, employeeInfo, performanceData } = this.state;
return (
<>
<iframe title="绩效设定" className={styles.frameContent} src="/绩效设定.html"/>
</>
)
<div className={styles['performance-assess-page']}>
{/* 筛选区 */}
<Card className={styles['search-section']}>
<Row gutter={16}>
<Col xs={24} sm={12} md={6}>
<div className={styles['form-item']}>
<label className={styles['form-label']}>姓名</label>
<Input
placeholder="请输入姓名"
value={searchForm.name}
onChange={(e) => this.handleSearchFormChange('name', e.target.value)}
/>
</div>
</Col>
<Col xs={24} sm={12} md={6}>
<div className={styles['form-item']}>
<label className={styles['form-label']}>电话</label>
<Input
placeholder="请输入电话"
value={searchForm.phone}
onChange={(e) => this.handleSearchFormChange('phone', e.target.value)}
/>
</div>
</Col>
<Col xs={24} sm={12} md={6}>
<div className={styles['form-item']}>
<label className={styles['form-label']}>身份证</label>
<Input
placeholder="请输入身份证号"
value={searchForm.idCard}
onChange={(e) => this.handleSearchFormChange('idCard', e.target.value)}
/>
</div>
</Col>
<Col xs={24} sm={12} md={6}>
<div className={styles['form-item']}>
<label className={styles['form-label']}>绩效计划</label>
<Select
style={{ width: '100%' }}
value={searchForm.performancePlan}
onChange={(value) => this.handleSearchFormChange('performancePlan', value)}
>
<Option value="2025年度">2025年度</Option>
<Option value="2024年度">2024年度</Option>
<Option value="2023年度">2023年度</Option>
</Select>
</div>
</Col>
</Row>
<div className={styles['search-actions']}>
<Button type="primary" icon={<SearchOutlined />} onClick={this.handleSearch}>
查询
</Button>
<Button icon={<ReloadOutlined />} onClick={this.handleReset}>
重置
</Button>
</div>
</Card>
{/* 人员信息 */}
<Card className={styles['employee-section']}>
<div className={styles['employee-info']}>
<Avatar size={64} icon={<UserOutlined />} className={styles['employee-avatar']} />
<div className={styles['employee-details']}>
<Row gutter={32}>
<Col span={6}>
<div className={styles['info-item']}>
<span className={styles['info-label']}>性别</span>
<span className={styles['info-value']}>{employeeInfo.gender}</span>
</div>
</Col>
<Col span={6}>
<div className={styles['info-item']}>
<span className={styles['info-label']}>年龄</span>
<span className={styles['info-value']}>{employeeInfo.age}</span>
</div>
</Col>
<Col span={6}>
<div className={styles['info-item']}>
<span className={styles['info-label']}>部门</span>
<span className={styles['info-value']}>{employeeInfo.department}</span>
</div>
</Col>
<Col span={6}>
<div className={styles['info-item']}>
<span className={styles['info-label']}>岗位</span>
<span className={styles['info-value']}>{employeeInfo.position}</span>
</div>
</Col>
</Row>
</div>
<div className={styles['plan-info']}>
<div className={styles['info-item']}>
<span className={styles['info-label']}>绩效计划名称</span>
<span className={styles['info-value']}>{employeeInfo.planName}</span>
</div>
</div>
</div>
</Card>
{/* 绩效公式 */}
<Card className={styles['formula-section']}>
<h3 className={styles['section-title']}>绩效公式</h3>
<div className={styles['formula-content']}>
总分 = 代码量 × 0.1 + 文档字数 × 0.01 + 培训次数 × 3 + 项目金额 × 0.01 + 项目比重 × 0.1 + 工作时长 × 0.01 + 执行力 × 0.1 + 对公司认同度 × 0.1
</div>
</Card>
{/* 绩效总分 */}
<Card className={styles['score-section']}>
<Row justify="space-between" align="middle">
<Col>
<div className={styles['score-info']}>
<span className={styles['score-label']}>绩效总分</span>
<span className={styles['score-value']}>{performanceData.totalScore}</span>
</div>
</Col>
<Col>
<div className={styles['status-info']}>
<span className={styles['status-label']}>审批状态</span>
<span className={styles['status-value']}>{performanceData.status}</span>
</div>
</Col>
</Row>
</Card>
{/* 客观指标 */}
<Card className={styles['objective-section']}>
<h3 className={styles['section-title']}>客观指标</h3>
<Row gutter={[32, 16]}>
<Col xs={24} sm={12} md={8}>
<div className={styles['metric-item']}>
<span className={styles['metric-label']}>代码量</span>
<span className={styles['metric-value']}>{performanceData.objective.codeLines} </span>
</div>
</Col>
<Col xs={24} sm={12} md={8}>
<div className={styles['metric-item']}>
<span className={styles['metric-label']}>文档字数</span>
<span className={styles['metric-value']}>{performanceData.objective.docWords} </span>
</div>
</Col>
<Col xs={24} sm={12} md={8}>
<div className={styles['metric-item']}>
<span className={styles['metric-label']}>培训次数</span>
<span className={styles['metric-value']}>{performanceData.objective.trainings} </span>
</div>
</Col>
<Col xs={24} sm={12} md={8}>
<div className={styles['metric-item']}>
<span className={styles['metric-label']}>项目金额</span>
<span className={styles['metric-value']}>{performanceData.objective.projectAmount}</span>
</div>
</Col>
<Col xs={24} sm={12} md={8}>
<div className={styles['metric-item']}>
<span className={styles['metric-label']}>项目比重</span>
<span className={styles['metric-value']}>{performanceData.objective.projectRatio}</span>
</div>
</Col>
<Col xs={24} sm={12} md={8}>
<div className={styles['metric-item']}>
<span className={styles['metric-label']}>工作时长</span>
<span className={styles['metric-value']}>{performanceData.objective.workHours} 小时</span>
</div>
</Col>
</Row>
</Card>
{/* 主观指标 */}
<Row gutter={16} className={styles['subjective-section']}>
{/* 执行力 */}
<Col xs={24} md={12}>
<Card className={styles['subjective-card']}>
<h3 className={styles['section-title']}>执行力</h3>
<div className={styles['rating-section']}>
<span className={styles['rating-label']}>评分</span>
<Rate
allowHalf
value={performanceData.subjective.execution.score}
onChange={(value) => this.handleSubjectiveChange('execution', 'score', value)}
/>
<span className={styles['rating-value']}>{performanceData.subjective.execution.score}</span>
</div>
<div className={styles['comment-section']}>
<label className={styles['comment-label']}>评价</label>
<TextArea
rows={4}
placeholder="请输入评价内容..."
value={performanceData.subjective.execution.comment}
onChange={(e) => this.handleSubjectiveChange('execution', 'comment', e.target.value)}
/>
</div>
</Card>
</Col>
{/* 对公司认同度 */}
<Col xs={24} md={12}>
<Card className={styles['subjective-card']}>
<h3 className={styles['section-title']}>对公司认同度</h3>
<div className={styles['rating-section']}>
<span className={styles['rating-label']}>评分</span>
<Rate
allowHalf
value={performanceData.subjective.loyalty.score}
onChange={(value) => this.handleSubjectiveChange('loyalty', 'score', value)}
/>
<span className={styles['rating-value']}>{performanceData.subjective.loyalty.score}</span>
</div>
<div className={styles['comment-section']}>
<label className={styles['comment-label']}>评价</label>
<TextArea
rows={4}
placeholder="请输入评价内容..."
value={performanceData.subjective.loyalty.comment}
onChange={(e) => this.handleSubjectiveChange('loyalty', 'comment', e.target.value)}
/>
</div>
</Card>
</Col>
</Row>
{/* 操作按钮 */}
<div className={styles['action-section']}>
<Button type="primary" icon={<SaveOutlined />} onClick={this.handleSave}>
保存评价
</Button>
<Button icon={<RollbackOutlined />} onClick={this.handleBack}>
返回列表
</Button>
</div>
</div>
);
}
}
export default PerformanceAssess
export default PerformanceAssess;

@ -1,10 +1,456 @@
@import '~@/utils/utils.less';
.frameContent {
width: 100%;
height: 100vh;
border: none;
/* 绩效评估页面样式 */
.performance-assess-page {
padding: 24px;
background-color: #f5f6fa;
min-height: 90vh;
max-height: 90vh; /* 限制最大高度 */
overflow-y: auto; /* 添加垂直滚动条 */
overflow-x: hidden; /* 隐藏水平滚动条 */
/* 自定义滚动条样式 */
&::-webkit-scrollbar {
width: 8px;
}
&::-webkit-scrollbar-track {
background: #f1f1f1;
border-radius: 4px;
}
&::-webkit-scrollbar-thumb {
background: #c1c1c1;
border-radius: 4px;
&:hover {
background: #a8a8a8;
}
}
.ant-card {
border-radius: 8px;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
margin-bottom: 20px; /* 增加卡片间隔 */
transition: all 0.3s ease;
&:hover {
box-shadow: 0 8px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
transform: translateY(-2px);
}
/* 最后一个卡片不需要下边距 */
&:last-child {
margin-bottom: 0;
}
}
}
/* 筛选区样式 */
.search-section {
margin-bottom: 10px; /* 增加与后续内容的间隔 */
.ant-card-body {
padding: 20px; /* 调整内边距 */
}
.form-item {
margin-bottom: 16px;
.form-label {
display: block;
margin: 0;
padding: 0;
font-size: 12px;
color: #6b7280;
margin-bottom: 4px;
}
}
.search-actions {
display: flex;
justify-content: flex-end;
gap: 8px;
margin-top: 16px;
padding-top: 16px;
border-top: 1px solid #f3f4f6; /* 添加分隔线 */
.ant-btn {
font-size: 14px;
height: 32px;
padding: 0 16px;
border-radius: 4px;
}
}
}
/* 人员信息样式 */
.employee-section {
margin-bottom: 10px;
.ant-card-body {
padding: 20px;
}
.employee-info {
display: flex;
align-items: center;
gap: 20px; /* 增加元素间隔 */
.employee-avatar {
flex-shrink: 0;
background-color: #e5e7eb;
}
.employee-details {
flex: 1;
}
.plan-info {
flex-shrink: 0;
min-width: 200px; /* 确保有足够空间 */
}
.info-item {
display: flex;
flex-direction: column;
margin-bottom: 8px; /* 增加项目间隔 */
.info-label {
font-size: 12px;
color: #6b7280;
margin-bottom: 4px;
}
.info-value {
font-size: 14px;
color: #111827;
font-weight: 500;
}
}
}
}
/* 绩效公式样式 */
.formula-section {
margin-bottom: 10px;
.ant-card-body {
padding: 20px;
}
.section-title {
font-size: 14px;
font-weight: 500;
color: #111827;
margin-bottom: 12px;
margin-top: 0;
}
.formula-content {
background-color: #f3f4f6;
padding: 12px; /* 增加内边距 */
border-radius: 6px;
font-size: 12px;
color: #374151;
line-height: 1.5; /* 增加行高 */
border-left: 4px solid #2d5cf6; /* 添加左边框 */
}
}
/* 绩效总分样式 */
.score-section {
margin-bottom: 10px;
.ant-card-body {
padding: 20px;
}
.score-info {
display: flex;
flex-direction: column;
.score-label {
font-size: 12px;
color: #6b7280;
margin-bottom: 6px;
}
.score-value {
font-size: 36px; /* 增大字体 */
font-weight: bold;
color: #2d5cf6;
line-height: 1;
}
}
.status-info {
display: flex;
flex-direction: column;
align-items: flex-end;
.status-label {
font-size: 12px;
color: #6b7280;
margin-bottom: 6px;
}
.status-value {
font-size: 16px; /* 增大字体 */
color: #059669;
font-weight: 500;
padding: 4px 12px;
background-color: #d1fae5;
border-radius: 12px;
}
}
}
/* 客观指标样式 */
.objective-section {
margin-bottom: 10px;
.ant-card-body {
padding: 20px;
}
.section-title {
font-size: 14px;
font-weight: 500;
color: #111827;
margin-bottom: 16px;
margin-top: 0;
padding-bottom: 8px;
border-bottom: 2px solid #f3f4f6;
}
.metric-item {
display: flex;
flex-direction: column;
padding: 12px;
background-color: #f9fafb;
border-radius: 6px;
border: 1px solid #e5e7eb;
margin-bottom: 8px;
.metric-label {
font-size: 12px;
color: #6b7280;
margin-bottom: 4px;
}
.metric-value {
font-size: 16px;
color: #111827;
font-weight: 600;
}
}
}
/* 主观指标样式 */
.subjective-section {
// margin-bottom: 10px;
.subjective-card {
height: auto;
margin-bottom: 16px;
.ant-card-body {
padding: 20px;
}
.section-title {
font-size: 14px;
font-weight: 500;
color: #111827;
margin-bottom: 16px;
margin-top: 0;
padding-bottom: 8px;
border-bottom: 2px solid #f3f4f6;
}
.rating-section {
display: flex;
align-items: center;
gap: 12px;
margin-bottom: 16px;
padding: 12px;
background-color: #f9fafb;
border-radius: 6px;
.rating-label {
font-size: 14px;
color: #374151;
font-weight: 500;
}
.rating-value {
font-size: 14px;
color: #111827;
font-weight: 600;
background-color: #2d5cf6;
color: white;
padding: 2px 8px;
border-radius: 4px;
}
}
.comment-section {
.comment-label {
display: block;
font-size: 12px;
color: #6b7280;
margin-bottom: 8px;
font-weight: 500;
}
.ant-input {
font-size: 14px;
border-radius: 6px;
border: 2px solid #e5e7eb;
padding: 12px;
min-height: 120px; /* 增加最小高度 */
&:focus {
border-color: #2d5cf6;
box-shadow: 0 0 0 3px rgba(45, 92, 246, 0.1);
}
}
}
}
}
/* 操作按钮样式 */
.action-section {
display: flex;
justify-content: flex-end;
gap: 16px;
// margin-top: 32px;
// padding-top: 24px;
// border-top: 2px solid #f3f4f6;
.ant-btn {
padding: 12px 32px;
height: auto;
font-size: 14px;
border-radius: 6px;
font-weight: 500;
}
.ant-btn-primary {
background-color: #2d5cf6;
border-color: #2d5cf6;
&:hover {
background-color: #1d4ed8;
border-color: #1d4ed8;
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(45, 92, 246, 0.3);
}
}
.ant-btn:not(.ant-btn-primary) {
border: 2px solid #e5e7eb;
color: #374151;
&:hover {
border-color: #d1d5db;
transform: translateY(-1px);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
}
}
/* 响应式样式 */
@media (max-width: 768px) {
.performance-assess-page {
padding: 16px;
max-height: 90vh;
overflow-y: auto;
}
.ant-card {
margin-bottom: 16px;
.ant-card-body {
padding: 16px !important;
}
}
.employee-section {
.employee-info {
flex-direction: column;
align-items: flex-start;
gap: 16px;
.employee-details {
width: 100%;
}
.plan-info {
width: 100%;
min-width: auto;
}
}
}
.subjective-section {
.ant-col {
margin-bottom: 16px;
}
}
.action-section {
flex-direction: column;
gap: 12px;
.ant-btn {
width: 100%;
}
}
.objective-section {
.ant-row {
.ant-col {
margin-bottom: 12px;
}
}
}
}
/* 自定义Rate组件样式 */
.ant-rate {
font-size: 18px;
.ant-rate-star {
margin-right: 6px;
}
.ant-rate-star-full .ant-rate-star-first,
.ant-rate-star-full .ant-rate-star-second {
color: #faad14;
}
}
/* Firefox 滚动条样式 */
.performance-assess-page {
scrollbar-width: thin;
scrollbar-color: #c1c1c1 #f1f1f1;
}
/* 输入框聚焦效果增强 */
.ant-input:focus,
.ant-select-focused .ant-select-selector {
border-color: #2d5cf6;
box-shadow: 0 0 0 3px rgba(45, 92, 246, 0.1);
}
/* 卡片内容区域滚动优化 */
.ant-card-body {
overflow: hidden; /* 防止内容溢出 */
}
/* 长文本内容滚动 */
.comment-section .ant-input {
resize: vertical; /* 允许垂直调整大小 */
max-height: 200px; /* 限制最大高度 */
overflow-y: auto; /* 超出时显示滚动条 */
}

@ -1,14 +1,647 @@
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,
PlusOutlined
} from '@ant-design/icons';
import styles from './PerformanceList.less';
import StandardTable from '@/components/StandardTable';
import PerformanceAdd from './form/PerformanceAdd';
import PerformanceRenderSimpleForm from "./form/PerformanceRenderSimpleForm" //表单
const { Option } = Select;
class PerformanceList 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',
name: '张三',
gender: '男',
age: 28,
department: '技术部',
position: '高级工程师',
totalScore: 92,
performanceStatus: '优秀'
},
{
key: '2',
name: '李四',
gender: '男',
age: 30,
department: '产品部',
position: '产品经理',
totalScore: 88,
performanceStatus: '良好'
},
{
key: '3',
name: '王五',
gender: '女',
age: 25,
department: '运营部',
position: '运营专员',
totalScore: 85,
performanceStatus: '良好'
},
{
key: '4',
name: '赵六',
gender: '男',
age: 32,
department: '财务部',
position: '会计',
totalScore: 78,
performanceStatus: '合格'
},
{
key: '5',
name: '陈七',
gender: '女',
age: 26,
department: '人事部',
position: 'HR专员',
totalScore: 65,
performanceStatus: '不合格'
},
],
pagination: {
current: 1,
pageSize: 5,
total: 356,
},
selectedRecord: null, // 选中的记录,用于编辑
};
// 默认列配置
this.defaultColumns = [
{
title: '姓名',
dataIndex: 'name',
key: 'name',
width: 100,
align: 'center',
render: (text, record) => (
<Button
type="link"
style={{ padding: 0, fontWeight: 500, color: '#1890ff' }}
onClick={() => this.handleNameClick(record)}
>
{text}
</Button>
),
},
{
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: 'totalScore',
key: 'totalScore',
width: 100,
align: 'center',
render: (score) => (
<span style={{
fontWeight: 'bold',
color: score >= 90 ? '#52c41a' :
score >= 80 ? '#1890ff' :
score >= 70 ? '#faad14' : '#ff4d4f'
}}>
{score}
</span>
),
sorter: (a, b) => a.totalScore - b.totalScore,
},
{
title: '绩效状态',
dataIndex: 'performanceStatus',
key: 'performanceStatus',
width: 120,
align: 'center',
render: (status) => {
const statusConfig = {
'优秀': { color: '#52c41a', bgColor: '#f6ffed', borderColor: '#b7eb8f' },
'良好': { color: '#1890ff', bgColor: '#e6f7ff', borderColor: '#91d5ff' },
'合格': { color: '#faad14', bgColor: '#fffbe6', borderColor: '#ffd666' },
'不合格': { color: '#ff4d4f', bgColor: '#fff2f0', borderColor: '#ffadd2' },
'待评估': { color: '#666', bgColor: '#f5f5f5', borderColor: '#d9d9d9' }
};
const config = statusConfig[status] || statusConfig['待评估'];
return (
<span style={{
color: config.color,
backgroundColor: config.bgColor,
border: `1px solid ${config.borderColor}`,
borderRadius: '12px',
padding: '2px 8px',
fontSize: '12px',
fontWeight: '500'
}}>
{status}
</span>
);
}
},
{
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 (
<PerformanceRenderSimpleForm {...parentMethods} />
);
}
renderAdvancedForm() {
const { prooperlog: { params } } = this.props;
const parentMethods = {
handleSearch: this.handleSearch,
handleFormReset: this.handleFormReset,
toggleForm: this.toggleForm,
params
};
return (
<PerformanceRenderSimpleForm {...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
}
});
};
// 处理姓名点击事件
handleNameClick = (record) => {
console.log('查看员工绩效详情:', record);
// 可以跳转到绩效详情页面或显示详情模态框
Modal.info({
title: `${record.name} - 绩效详情`,
width: 600,
content: (
<div style={{ marginTop: 16 }}>
<p><strong>员工姓名</strong>{record.name}</p>
<p><strong>性别</strong>{record.gender}</p>
<p><strong>年龄</strong>{record.age}</p>
<p><strong>部门</strong>{record.department}</p>
<p><strong>职位</strong>{record.position}</p>
<p><strong>绩效总分</strong><span style={{
fontWeight: 'bold',
color: record.totalScore >= 90 ? '#52c41a' :
record.totalScore >= 80 ? '#1890ff' :
record.totalScore >= 70 ? '#faad14' : '#ff4d4f'
}}>{record.totalScore}</span></p>
<p><strong>绩效状态</strong>{record.performanceStatus}</p>
</div>
),
okText: '确定'
});
};
// 处理编辑
handleEdit = (record) => {
console.log('编辑员工绩效:', record);
this.setState({
selectedRecord: record,
showPerformanceAdd: true
});
};
// 处理删除
handleDelete = (record) => {
Modal.confirm({
title: '删除确认',
content: `确定要删除员工 "${record.name}" 的绩效记录吗?`,
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={<PlusOutlined />}
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>
{/* 新增/编辑绩效弹窗 */}
<PerformanceAdd
visible={addModalVisible}
loading={addLoading}
editData={this.state.selectedRecord}
onCancel={this.hideAddModal}
onSuccess={this.handleAddSuccess}
/>
</div>
);
}
}
export default PerformanceList
export default PerformanceList;

@ -1,10 +1,523 @@
@import '~@/utils/utils.less';
.frameContent {
width: 100%;
.staffInfoContainer {
min-height: 100vh;
height: 100vh;
// 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;
display: block;
margin: 0;
padding: 0;
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,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 PerformanceAdd 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 PerformanceAdd;

@ -0,0 +1,222 @@
import React, { useState } from 'react';
import { Button, Col, Form, Input, Row, message, Select, Space, InputNumber } from 'antd';
import { ClearOutlined, SearchOutlined, ExpandOutlined, CompressOutlined, UserOutlined, PhoneOutlined, IdcardOutlined, TeamOutlined } from '@ant-design/icons';
import styles from "../PerformanceList.less";
const FormItem = Form.Item;
const { Option } = Select;
const PerformanceRenderSimpleForm = (props) => {
const [form] = Form.useForm();
const [expandForm, setExpandForm] = useState(false);
const { submitButtons, handleSearch, handleFormReset, toggleForm, params } = props;
React.useEffect(() => {
form.setFieldsValue({
name: params?.name,
phone: params?.phone,
idCard: params?.idCard,
department: params?.department,
gender: params?.gender,
ageMin: params?.ageMin,
ageMax: params?.ageMax,
performanceStatus: params?.performanceStatus,
});
}, [params]);
const onFinish = values => {
const searchParams = {
...values,
// 年龄范围验证
ageMin: values.ageMin,
ageMax: values.ageMax,
};
// 验证年龄范围
if (values.ageMin && values.ageMax && values.ageMin > values.ageMax) {
message.error("最小年龄不能大于最大年龄!");
return;
}
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="name"
label="姓名"
>
<Input
placeholder="请输入姓名"
allowClear
prefix={<UserOutlined />}
/>
</Form.Item>
</Col>
<Col span={6}>
<Form.Item
name="phone"
label="电话"
>
<Input
placeholder="请输入电话"
allowClear
prefix={<PhoneOutlined />}
/>
</Form.Item>
</Col>
<Col span={6}>
<Form.Item
name="idCard"
label="身份证号"
>
<Input
placeholder="请输入身份证号"
allowClear
prefix={<IdcardOutlined />}
/>
</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="department"
label="部门"
>
<Select
placeholder="请选择部门"
allowClear
suffixIcon={<TeamOutlined />}
>
<Option value="技术部">技术部</Option>
<Option value="产品部">产品部</Option>
<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="gender"
label="性别"
>
<Select placeholder="请选择性别" allowClear>
<Option value="男"></Option>
<Option value="女"></Option>
</Select>
</Form.Item>
</Col>
<Col span={6}>
<Form.Item
label="年龄范围"
>
<Input.Group compact>
<Form.Item
name="ageMin"
noStyle
>
<InputNumber
placeholder="最小年龄"
min={18}
max={65}
style={{ width: '48%' }}
/>
</Form.Item>
<span style={{
display: 'inline-block',
width: '4%',
textAlign: 'center',
lineHeight: '32px'
}}>
-
</span>
<Form.Item
name="ageMax"
noStyle
>
<InputNumber
placeholder="最大年龄"
min={18}
max={65}
style={{ width: '48%' }}
/>
</Form.Item>
</Input.Group>
</Form.Item>
</Col>
<Col span={6}>
<Form.Item
name="performanceStatus"
label="绩效状态"
>
<Select placeholder="请选择绩效状态" allowClear>
<Option value="excellent">优秀</Option>
<Option value="good">良好</Option>
<Option value="qualified">合格</Option>
<Option value="unqualified">不合格</Option>
<Option value="pending">待评估</Option>
</Select>
</Form.Item>
</Col>
</Row>
)}
</Form>
</div>
);
};
export default PerformanceRenderSimpleForm;

@ -1,14 +1,900 @@
import React, {Fragment, PureComponent} from 'react';
/**
* 数据字典管理页面
* 功能管理系统中的元数据域和属性配置
* 包含元数据域树形结构属性列表新增属性等功能
*/
import React, { PureComponent } from 'react';
import {
Card,
Tree,
Button,
Select,
Space,
Row,
Col,
Pagination,
Modal,
message,
Input
} from 'antd';
import { history } from 'umi';
import {
ExpandOutlined,
UserOutlined,
TeamOutlined,
ApartmentOutlined,
SyncOutlined,
SettingOutlined,
UserAddOutlined,
EditOutlined,
DeleteOutlined,
SearchOutlined,
FolderOutlined,
FileTextOutlined,
IdcardOutlined,
PhoneOutlined,
MailOutlined,
CalendarOutlined,
HomeOutlined,
PlusOutlined,
DownloadOutlined,
ExclamationCircleOutlined
} from '@ant-design/icons';
import styles from './PerformanceRuleset.less';
import StandardTable from '@/components/StandardTable';
import PerformanceRulesetAdd from './form/PerformanceRulesetAdd';
const { Option } = Select;
/**
* 数据字典管理组件
* 左侧显示元数据域树形结构右侧显示选中域的属性列表
*/
class PerformanceRuleset extends PureComponent {
render() {
constructor(props) {
super(props);
this.state = {
searchForm: {},
selectedKeys: ['performance_params'],
expandedKeys: ['basic_info', 'performance_params'],
searchValue: '', // 搜索关键词
filteredTreeData: [], // 过滤后的树数据
addModalVisible: false, // 新增弹窗显示状态
addLoading: false, // 新增loading状态
organizationData: [
{
title: '基础信息',
key: 'basic_info',
code: 'EMP_BASIC_INFO',
description: '员工基础信息数据域,包含员工的基本身份信息和个人资料',
children: [
{ title: '员工编号', key: 'employee_id', isLeaf: true },
{ title: '姓名', key: 'name', isLeaf: true },
{ title: '性别', key: 'gender', isLeaf: true }
],
},
{
title: '技术职务',
key: 'tech_position',
code: 'EMP_TECH_POSITION',
description: '员工技术职务信息数据域,包含技术等级、职务级别等信息',
children: [],
},
{
title: '绩效参数',
key: 'performance_params',
code: 'EMP_PERFORMANCE',
description: '用于记录员工各项绩效指标的参数集合',
children: [
{ title: '代码量', key: 'code_amount', isLeaf: true },
{ title: '文档字数', key: 'doc_words', isLeaf: true },
{ title: '出勤时长', key: 'attendance_hours', isLeaf: true },
{ title: '项目金额', key: 'project_amount', isLeaf: true },
{ title: '培训时长', key: 'training_hours', isLeaf: true },
{ title: '会议次数', key: 'meeting_count', isLeaf: true },
{ title: '会议时长', key: 'meeting_hours', isLeaf: true },
{ title: '千行 bug 数', key: 'bug_count', isLeaf: true },
{ title: '运维响应时间', key: 'response_time', isLeaf: true },
{ title: '员工满意度', key: 'satisfaction', isLeaf: true }
],
},
{
title: '联系方式',
key: 'contact_info',
code: 'EMP_CONTACT',
description: '员工联系方式信息数据域,包含电话、邮箱、地址等联系信息',
children: [],
},
{
title: '其他信息',
key: 'other_info',
code: 'EMP_OTHER',
description: '员工其他补充信息数据域,包含备注、特殊说明等信息',
children: [],
},
],
tableData: [
{
key: '1',
id: 1,
fieldName: 'code_amount',
displayName: '代码量',
dataType: '数字',
unit: '行',
required: true,
defaultValue: '-'
},
{
key: '2',
id: 2,
fieldName: 'doc_words',
displayName: '文档字数',
dataType: '数字',
unit: '字',
required: true,
defaultValue: '-'
},
{
key: '3',
id: 3,
fieldName: 'attendance_hours',
displayName: '出勤时长',
dataType: '数字',
unit: '小时',
required: true,
defaultValue: '-'
},
{
key: '4',
id: 4,
fieldName: 'project_amount',
displayName: '项目金额',
dataType: '数字',
unit: '元',
required: false,
defaultValue: '-'
},
{
key: '5',
id: 5,
fieldName: 'training_hours',
displayName: '培训时长',
dataType: '数字',
unit: '小时',
required: false,
defaultValue: '-'
},
{
key: '6',
id: 6,
fieldName: 'meeting_count',
displayName: '会议次数',
dataType: '数字',
unit: '次',
required: true,
defaultValue: '-'
},
{
key: '7',
id: 7,
fieldName: 'meeting_hours',
displayName: '会议时长',
dataType: '数字',
unit: '分钟',
required: false,
defaultValue: '-'
},
{
key: '8',
id: 8,
fieldName: 'bug_count',
displayName: '千行 bug 数',
dataType: '数字',
unit: '个/千行',
required: true,
defaultValue: '-'
},
{
key: '9',
id: 9,
fieldName: 'response_time',
displayName: '运维响应时间',
dataType: '数字',
unit: '分钟',
required: true,
defaultValue: '-'
},
{
key: '10',
id: 10,
fieldName: 'satisfaction',
displayName: '员工满意度',
dataType: '数字',
unit: '分',
required: false,
defaultValue: '-'
}
],
pagination: {
current: 1,
pageSize: 10,
total: 10,
},
selectedRecord: null, // 选中的记录,用于编辑
selectedMetadata: null, // 选中的元数据域信息
};
// 更新列配置 - 绩效参数管理
this.defaultColumns = [
{
title: '属性名称',
dataIndex: 'displayName',
key: 'displayName',
width: 120,
align: 'left',
},
{
title: '编码',
dataIndex: 'fieldName',
key: 'fieldName',
width: 150,
align: 'left',
render: (text) => (
<code style={{
backgroundColor: '#f0f0f0',
padding: '2px 6px',
borderRadius: '4px',
fontSize: '12px',
color: '#d4380d'
}}>
{text}
</code>
),
},
{
title: '数据类型',
dataIndex: 'dataType',
key: 'dataType',
width: 120,
align: 'center',
render: (type) => {
const typeConfig = {
'文本': { color: '#722ed1', bgColor: '#f9f0ff', borderColor: '#d3adf7' },
'数字': { color: '#52c41a', bgColor: '#f6ffed', borderColor: '#b7eb8f' },
'列表': { color: '#1890ff', bgColor: '#e6f7ff', borderColor: '#91d5ff' },
'日期': { color: '#722ed1', bgColor: '#f9f0ff', borderColor: '#d3adf7' },
'布尔': { color: '#eb2f96', bgColor: '#fff0f6', borderColor: '#ffadd2' }
};
const config = typeConfig[type] || typeConfig['文本'];
return (
<>
<iframe title="绩效参数设置" className={styles.frameContent} src="/绩效参数设置.html"/>
</>
<span style={{
color: config.color,
backgroundColor: config.bgColor,
border: `1px solid ${config.borderColor}`,
borderRadius: '12px',
padding: '2px 8px',
fontSize: '12px',
fontWeight: '500'
}}>
{type}
</span>
);
}
},
{
title: '单位',
dataIndex: 'unit',
key: 'unit',
width: 80,
align: 'center',
render: (unit) => unit || '-'
},
{
title: '必填',
dataIndex: 'required',
key: 'required',
width: 80,
align: 'center',
render: (required) => (
<span style={{
color: required ? '#52c41a' : '#666',
backgroundColor: required ? '#f6ffed' : '#f5f5f5',
border: `1px solid ${required ? '#b7eb8f' : '#d9d9d9'}`,
borderRadius: '12px',
padding: '2px 8px',
fontSize: '12px',
fontWeight: '500'
}}>
{required ? '是' : '否'}
</span>
)
},
{
title: '操作',
key: 'action',
width: 120,
align: 'center',
render: (_, record) => (
<Space size="small">
<Button
type="link"
size="small"
icon={<EditOutlined />}
onClick={() => this.handleEdit(record)}
>
{/* 编辑 */}
</Button>
<Button
type="link"
size="small"
danger
icon={<DeleteOutlined />}
onClick={() => this.handleDelete(record)}
>
{/* 删除 */}
</Button>
</Space>
),
},
];
}
// 获取处理后的树形数据
getTreeData = () => {
const { organizationData, filteredTreeData, searchValue } = this.state;
// 如果有搜索条件,使用过滤后的数据,否则使用原始数据
const dataSource = searchValue ? filteredTreeData : organizationData;
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>
{node.isLeaf ? null : <span className={styles['node-count']}>({node.children ? node.children.length : 0})</span>}
</span>
),
children: node.children ? node.children.map(processNode) : undefined
});
return dataSource.map(processNode);
};
// 获取节点图标
getNodeIcon = (title) => {
// 主分类图标 - 使用文件夹风格
if (title.includes('基础信息')) return <FolderOutlined style={{ color: '#52c41a', fontSize: '16px' }} />;
if (title.includes('技术职务')) return <FolderOutlined style={{ color: '#fa8c16', fontSize: '16px' }} />;
if (title.includes('绩效参数')) return <FolderOutlined style={{ color: '#2d5cf6', fontSize: '16px' }} />;
if (title.includes('联系方式')) return <FolderOutlined style={{ color: '#722ed1', fontSize: '16px' }} />;
if (title.includes('其他信息')) return <FolderOutlined style={{ color: '#13c2c2', fontSize: '16px' }} />;
// 绩效参数属性图标
if (title.includes('代码量')) return <FileTextOutlined style={{ color: '#1890ff', fontSize: '14px' }} />;
if (title.includes('文档字数')) return <FileTextOutlined style={{ color: '#52c41a', fontSize: '14px' }} />;
if (title.includes('出勤时长')) return <CalendarOutlined style={{ color: '#fa8c16', fontSize: '14px' }} />;
if (title.includes('项目金额')) return <SettingOutlined style={{ color: '#eb2f96', fontSize: '14px' }} />;
if (title.includes('培训时长')) return <CalendarOutlined style={{ color: '#722ed1', fontSize: '14px' }} />;
if (title.includes('会议次数')) return <TeamOutlined style={{ color: '#13c2c2', fontSize: '14px' }} />;
if (title.includes('会议时长')) return <CalendarOutlined style={{ color: '#13c2c2', fontSize: '14px' }} />;
if (title.includes('bug')) return <ExclamationCircleOutlined style={{ color: '#ff4d4f', fontSize: '14px' }} />;
if (title.includes('响应时间')) return <SettingOutlined style={{ color: '#fa8c16', fontSize: '14px' }} />;
if (title.includes('满意度')) return <UserOutlined style={{ color: '#52c41a', fontSize: '14px' }} />;
// 基础信息属性图标
if (title.includes('员工编号') || title.includes('编号')) return <IdcardOutlined style={{ color: '#1890ff', fontSize: '14px' }} />;
if (title.includes('姓名') || title.includes('名称')) return <UserOutlined style={{ color: '#52c41a', fontSize: '14px' }} />;
if (title.includes('性别')) return <TeamOutlined style={{ color: '#fa8c16', fontSize: '14px' }} />;
// 默认图标
return <FileTextOutlined style={{ color: '#666', fontSize: '14px' }} />;
};
// 搜索处理
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 });
// 如果选中的是父节点(元数据域),显示元数据域信息
if (selectedKeys.length > 0 && info.selectedNodes.length > 0) {
const selectedNode = info.selectedNodes[0];
const nodeKey = selectedKeys[0];
// 查找对应的元数据域信息
const metadataInfo = this.findMetadataByKey(nodeKey);
if (metadataInfo && !metadataInfo.isLeaf) {
// 只有父节点才显示元数据域信息
this.setState({
selectedMetadata: {
name: metadataInfo.title,
code: metadataInfo.code,
description: metadataInfo.description,
key: metadataInfo.key
}
});
} else {
// 选中子节点时清空元数据域信息
this.setState({ selectedMetadata: null });
}
} else {
this.setState({ selectedMetadata: null });
}
};
// 树节点展开
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) });
}
};
// 搜索树节点
onTreeSearch = (value) => {
const { organizationData } = this.state;
this.setState({ searchValue: value });
if (!value) {
// 搜索为空时,显示所有数据并恢复默认展开状态
this.setState({
filteredTreeData: [],
expandedKeys: ['basic_info', 'admin_position']
});
return;
}
// 过滤树数据
const filterTree = (data) => {
return data.map(item => {
const match = item.title.toLowerCase().includes(value.toLowerCase());
let children = [];
if (item.children) {
children = filterTree(item.children).filter(child => child);
}
if (match || children.length > 0) {
return {
...item,
children: children.length > 0 ? children : undefined
};
}
return null;
}).filter(item => item);
};
const filtered = filterTree(organizationData);
// 搜索时自动展开所有匹配的节点
const getAllKeys = (data) => {
let keys = [];
data.forEach(item => {
keys.push(item.key);
if (item.children) {
keys = keys.concat(getAllKeys(item.children));
}
});
return keys;
};
this.setState({
filteredTreeData: filtered,
expandedKeys: getAllKeys(filtered)
});
};
// 根据key查找元数据域信息
findMetadataByKey = (key) => {
const { organizationData } = this.state;
const findInData = (data) => {
for (let item of data) {
if (item.key === key) {
return item;
}
if (item.children) {
const found = findInData(item.children);
if (found) return found;
}
}
return null;
};
return findInData(organizationData);
};
// 渲染元数据域信息区域
renderMetadataInfo = () => {
const { selectedMetadata } = this.state;
if (!selectedMetadata) {
return null;
}
return (
<div className={styles.metadataInfoSection}>
<Row gutter={16}>
<Col span={12}>
<div className={styles.metadataField}>
<label className={styles.metadataLabel}>元数据域名称</label>
<Input
value={selectedMetadata.name}
className={styles.metadataInput}
readOnly
/>
</div>
</Col>
<Col span={12}>
<div className={styles.metadataField}>
<label className={styles.metadataLabel}>元数据域编码</label>
<Input
value={selectedMetadata.code}
className={styles.metadataInput}
readOnly
/>
</div>
</Col>
<Col span={24}>
<div className={styles.metadataField}>
<label className={styles.metadataLabel}>描述</label>
<Input.TextArea
value={selectedMetadata.description}
className={styles.metadataTextarea}
rows={2}
readOnly
/>
</div>
</Col>
</Row>
</div>
);
};
renderForm() {
// 数据字典页面暂不需要搜索表单
return null;
}
// 分页处理
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
}
});
};
// 处理姓名点击事件
handleNameClick = (record) => {
console.log('查看员工绩效详情:', record);
// 可以跳转到绩效详情页面或显示详情模态框
Modal.info({
title: `${record.name} - 绩效详情`,
width: 600,
content: (
<div style={{ marginTop: 16 }}>
<p><strong>员工姓名</strong>{record.name}</p>
<p><strong>性别</strong>{record.gender}</p>
<p><strong>年龄</strong>{record.age}</p>
<p><strong>部门</strong>{record.department}</p>
<p><strong>职位</strong>{record.position}</p>
<p><strong>绩效总分</strong><span style={{
fontWeight: 'bold',
color: record.totalScore >= 90 ? '#52c41a' :
record.totalScore >= 80 ? '#1890ff' :
record.totalScore >= 70 ? '#faad14' : '#ff4d4f'
}}>{record.totalScore}</span></p>
<p><strong>绩效状态</strong>{record.performanceStatus}</p>
</div>
),
okText: '确定'
});
};
// 处理编辑
handleEdit = (record) => {
console.log('编辑员工绩效:', record);
this.setState({
selectedRecord: record,
showPerformanceAdd: true
});
};
// 处理删除
handleDelete = (record) => {
Modal.confirm({
title: '删除确认',
content: `确定要删除员工 "${record.name}" 的绩效记录吗?`,
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('删除成功');
}
});
};
// 新增元数据域
handleAddMetadataDomain = () => {
console.log('新增元数据域');
// 这里可以打开新增元数据域的弹窗
message.info('新增元数据域功能开发中...');
};
// 导出配置
handleExportConfig = () => {
console.log('导出配置');
// 这里可以调用导出配置的接口
message.info('导出配置功能开发中...');
};
render() {
const { tableData, pagination, expandedKeys, addModalVisible, addLoading } = this.state;
return (
<div className={styles.dataDictContainer}>
{/* 页面标题区域 */}
<div style={{
display: 'flex',
justifyContent: 'flex-end',
alignItems: 'center',
padding: '12px 24px',
}}>
<Space size="middle">
<Button
type="primary"
icon={<PlusOutlined />}
onClick={this.handleAddMetadataDomain}
style={{
backgroundColor: '#2D5CF6',
borderColor: '#2D5CF6',
borderRadius: '4px'
}}
>
新增元数据域
</Button>
<Button
icon={<DownloadOutlined />}
onClick={this.handleExportConfig}
style={{
borderColor: '#d9d9d9',
borderRadius: '4px'
}}
>
导出配置
</Button>
</Space>
</div>
{/* 主体内容 */}
<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' }}>
<ApartmentOutlined 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['tree-search']}>
<Input
placeholder="搜索元数据域..."
prefix={<SearchOutlined />}
value={this.state.searchValue}
onChange={(e) => this.onTreeSearch(e.target.value)}
allowClear
size="middle"
/>
</div>
<div className={styles.treeContainer}>
{this.state.searchValue && this.getTreeData().length === 0 ? (
<div style={{
textAlign: 'center',
padding: '40px 20px',
color: '#999',
fontSize: '14px'
}}>
<SearchOutlined style={{ fontSize: '24px', marginBottom: '8px', display: 'block' }} />
未找到匹配的元数据域
<div style={{ fontSize: '12px', marginTop: '4px' }}>
尝试使用其他关键词搜索
</div>
</div>
) : (
<Tree
showIcon
expandedKeys={expandedKeys}
selectedKeys={this.state.selectedKeys}
onSelect={this.onTreeSelect}
onExpand={this.onTreeExpand}
treeData={this.getTreeData()}
className={styles.orgTree}
/>
)}
</div>
</Card>
</Col>
{/* 右侧属性信息区 */}
<Col span={19}>
{/* 属性表格 */}
<Card
className={styles.tableCard}
title={
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
<span>绩效参数属性配置</span>
<Button
type="primary"
size="middle"
icon={<PlusOutlined />}
onClick={this.showAddModal}
>
新增属性
</Button>
</div>
}
>
{/* 元数据域信息区域 */}
{this.renderMetadataInfo()}
<StandardTable
columns={this.defaultColumns}
dataSource={tableData}
pagination={false}
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>
{/* 新增/编辑绩效弹窗 */}
<PerformanceRulesetAdd
visible={addModalVisible}
loading={addLoading}
editData={this.state.selectedRecord}
onCancel={this.hideAddModal}
onSuccess={this.handleAddSuccess}
/>
</div>
);
}
}
export default PerformanceRuleset
export default PerformanceRuleset;

@ -1,10 +1,830 @@
@import '~@/utils/utils.less';
.frameContent {
.dataDictContainer,
.staffInfoContainer {
min-height: 90vh;
height: 90vh;
// 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;
/* 搜索框样式 */
.tree-search {
margin-bottom: 16px;
.ant-input-affix-wrapper {
border-radius: 8px;
border: 1px solid #d9d9d9;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.02);
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
&:hover {
border-color: #40a9ff;
box-shadow: 0 2px 8px rgba(24, 144, 255, 0.1);
}
&:focus-within {
border-color: #1890ff;
box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2);
}
.ant-input {
border: none;
box-shadow: none;
font-size: 13px;
&::placeholder {
color: #bfbfbf;
font-style: italic;
}
}
.ant-input-prefix {
color: #bfbfbf;
margin-right: 8px;
}
}
}
.treeHeader {
display: flex;
justify-content: space-between;
align-items: center;
font-size: 14px;
font-weight: 500;
}
.treeContainer {
height: 480px; /* 减少高度为搜索框留空间 */
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: 6px;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
padding: 6px 10px;
margin: 1px 0;
&:hover {
background: linear-gradient(135deg, #f0f9ff 0%, #e6f7ff 100%);
transform: translateX(3px);
box-shadow: 0 2px 8px rgba(24, 144, 255, 0.15);
}
&.ant-tree-node-selected {
background: linear-gradient(135deg, #e6f7ff 0%, #bae7ff 100%) !important;
box-shadow: 0 3px 12px rgba(24, 144, 255, 0.25);
border-left: 3px solid #1890ff;
}
}
.ant-tree-iconEle {
margin-right: 10px;
display: flex;
align-items: center;
justify-content: center;
width: 20px;
height: 20px;
}
.ant-tree-title {
font-size: 14px;
font-weight: 500;
}
/* 展开/收起图标样式 */
.ant-tree-switcher {
width: 20px;
height: 20px;
display: flex;
align-items: center;
justify-content: center;
.ant-tree-switcher-icon {
color: #8c8c8c;
font-size: 12px;
transition: all 0.3s ease;
&:hover {
color: #1890ff;
}
}
}
}
}
}
}
/* Tree节点标题样式 */
.tree-node-title {
display: flex;
align-items: center;
width: 100%;
height: 100vh;
transition: all 0.3s ease;
.anticon {
font-size: 16px;
margin-right: 8px;
display: flex;
align-items: center;
justify-content: center;
width: 24px;
height: 24px;
border-radius: 6px;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
background: rgba(24, 144, 255, 0.06);
&:hover {
background: rgba(24, 144, 255, 0.12);
transform: scale(1.08);
box-shadow: 0 2px 8px rgba(24, 144, 255, 0.15);
}
}
.node-title {
flex: 1;
font-size: 14px;
color: #333;
font-weight: 500;
transition: color 0.3s ease;
}
.node-count {
color: #999;
font-size: 11px;
margin-left: auto;
background: #f5f5f5;
padding: 2px 8px;
border-radius: 10px;
min-width: 20px;
text-align: center;
font-weight: 500;
transition: all 0.3s ease;
&:hover {
background: #e6f7ff;
color: #1890ff;
}
}
}
.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: 0 24px;
}
}
}
/* 元数据域信息区域样式 */
.metadataInfoSection {
background-color: #f9fafb;
border-radius: 8px;
padding: 16px;
margin: 12px 0;
border: 1px solid #e5e7eb;
.metadataField {
margin-bottom: 16px;
&:last-child {
margin-bottom: 0;
}
}
.metadataLabel {
display: block;
margin: 0;
padding: 0;
font-size: 14px;
font-weight: 500;
color: #374151;
margin-bottom: 4px;
}
.metadataInput,
.metadataTextarea {
border-radius: 4px;
border: 1px solid #d1d5db;
background-color: #ffffff;
&:focus {
border-color: #2d5cf6;
box-shadow: 0 0 0 1px #2d5cf6;
}
&[readonly] {
background-color: #f9fafb;
cursor: default;
}
}
.metadataInput {
font-size: 14px;
padding: 8px 12px;
}
.metadataTextarea {
font-size: 14px;
padding: 8px 12px;
resize: vertical;
min-height: 60px;
}
}
/* 表格卡片样式优化 */
.tableCard {
.ant-card-body {
padding: 24px;
}
.ant-card-head {
border-bottom: 1px solid #f0f0f0;
}
.ant-card-head-title {
font-size: 18px;
font-weight: 600;
color: #262626;
}
}
/* 树形卡片样式 */
.treeCard {
height: 100%;
.ant-card-body {
padding: 16px;
height: calc(100% - 57px);
overflow: hidden;
}
.ant-card-head {
border-bottom: 1px solid #f0f0f0;
}
.ant-card-head-title {
font-size: 16px;
font-weight: 600;
color: #262626;
}
}
/* 树容器样式 */
.treeContainer {
height: calc(100% - 50px);
overflow-y: auto;
padding-top: 8px;
/* 自定义滚动条 */
&::-webkit-scrollbar {
width: 6px;
}
&::-webkit-scrollbar-track {
background: #f1f5f9;
border-radius: 3px;
}
&::-webkit-scrollbar-thumb {
background: #cbd5e1;
border-radius: 3px;
&:hover {
background: #94a3b8;
}
}
}
/* 树搜索样式 */
.tree-search {
margin-bottom: 12px;
.ant-input {
border-radius: 4px;
border: 1px solid #d1d5db;
&:focus {
border-color: #2d5cf6;
box-shadow: 0 0 0 1px #2d5cf6;
}
}
.ant-input-prefix {
color: #9ca3af;
}
}
/* 树节点样式 */
.orgTree {
.ant-tree-node-content-wrapper {
border-radius: 4px;
transition: all 0.2s;
&:hover {
background-color: #f3f4f6;
}
&.ant-tree-node-selected {
background-color: #dbeafe !important;
color: #2d5cf6;
}
}
.ant-tree-switcher {
display: flex;
align-items: center;
justify-content: center;
}
.ant-tree-iconEle {
display: flex;
align-items: center;
justify-content: center;
}
}
.tree-node-title {
display: flex;
align-items: center;
.node-title {
margin-left: 8px;
font-size: 14px;
color: #374151;
}
.node-count {
margin-left: 4px;
font-size: 12px;
color: #9ca3af;
}
}
/* 响应式样式 */
@media (max-width: 768px) {
.metadataInfoSection {
padding: 12px;
margin-bottom: 16px;
.metadataField {
margin-bottom: 12px;
}
.metadataLabel {
font-size: 13px;
}
.metadataInput,
.metadataTextarea {
font-size: 13px;
padding: 6px 10px;
}
}
.tableCard .ant-card-body {
padding: 16px;
}
.treeCard .ant-card-body {
padding: 12px;
}
}
@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,222 @@
import React, { useState } from 'react';
import { Button, Col, Form, Input, Row, message, Select, Space, InputNumber } from 'antd';
import { ClearOutlined, SearchOutlined, ExpandOutlined, CompressOutlined, UserOutlined, PhoneOutlined, IdcardOutlined, TeamOutlined } from '@ant-design/icons';
import styles from "../PerformanceList.less";
const FormItem = Form.Item;
const { Option } = Select;
const PerformanceRenderSimpleForm = (props) => {
const [form] = Form.useForm();
const [expandForm, setExpandForm] = useState(false);
const { submitButtons, handleSearch, handleFormReset, toggleForm, params } = props;
React.useEffect(() => {
form.setFieldsValue({
name: params?.name,
phone: params?.phone,
idCard: params?.idCard,
department: params?.department,
gender: params?.gender,
ageMin: params?.ageMin,
ageMax: params?.ageMax,
performanceStatus: params?.performanceStatus,
});
}, [params]);
const onFinish = values => {
const searchParams = {
...values,
// 年龄范围验证
ageMin: values.ageMin,
ageMax: values.ageMax,
};
// 验证年龄范围
if (values.ageMin && values.ageMax && values.ageMin > values.ageMax) {
message.error("最小年龄不能大于最大年龄!");
return;
}
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="name"
label="姓名"
>
<Input
placeholder="请输入姓名"
allowClear
prefix={<UserOutlined />}
/>
</Form.Item>
</Col>
<Col span={6}>
<Form.Item
name="phone"
label="电话"
>
<Input
placeholder="请输入电话"
allowClear
prefix={<PhoneOutlined />}
/>
</Form.Item>
</Col>
<Col span={6}>
<Form.Item
name="idCard"
label="身份证号"
>
<Input
placeholder="请输入身份证号"
allowClear
prefix={<IdcardOutlined />}
/>
</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="department"
label="部门"
>
<Select
placeholder="请选择部门"
allowClear
suffixIcon={<TeamOutlined />}
>
<Option value="技术部">技术部</Option>
<Option value="产品部">产品部</Option>
<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="gender"
label="性别"
>
<Select placeholder="请选择性别" allowClear>
<Option value="男"></Option>
<Option value="女"></Option>
</Select>
</Form.Item>
</Col>
<Col span={6}>
<Form.Item
label="年龄范围"
>
<Input.Group compact>
<Form.Item
name="ageMin"
noStyle
>
<InputNumber
placeholder="最小年龄"
min={18}
max={65}
style={{ width: '48%' }}
/>
</Form.Item>
<span style={{
display: 'inline-block',
width: '4%',
textAlign: 'center',
lineHeight: '32px'
}}>
-
</span>
<Form.Item
name="ageMax"
noStyle
>
<InputNumber
placeholder="最大年龄"
min={18}
max={65}
style={{ width: '48%' }}
/>
</Form.Item>
</Input.Group>
</Form.Item>
</Col>
<Col span={6}>
<Form.Item
name="performanceStatus"
label="绩效状态"
>
<Select placeholder="请选择绩效状态" allowClear>
<Option value="excellent">优秀</Option>
<Option value="good">良好</Option>
<Option value="qualified">合格</Option>
<Option value="unqualified">不合格</Option>
<Option value="pending">待评估</Option>
</Select>
</Form.Item>
</Col>
</Row>
)}
</Form>
</div>
);
};
export default PerformanceRenderSimpleForm;

@ -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 PerformanceRuleset 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 PerformanceRuleset;
Loading…
Cancel
Save