人员管理页面开发
parent
95a20db11c
commit
88aeb60d11
Binary file not shown.
|
After Width: | Height: | Size: 11 KiB |
@ -0,0 +1,838 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
import {
|
||||
Card,
|
||||
Tree,
|
||||
Input,
|
||||
Button,
|
||||
DatePicker,
|
||||
Select,
|
||||
Row,
|
||||
Col,
|
||||
Space,
|
||||
Radio,
|
||||
Checkbox,
|
||||
InputNumber,
|
||||
Avatar,
|
||||
message,
|
||||
Typography
|
||||
} from 'antd';
|
||||
import {
|
||||
SaveOutlined,
|
||||
CloseOutlined,
|
||||
PlusCircleOutlined,
|
||||
DeleteOutlined,
|
||||
ExpandOutlined,
|
||||
UserOutlined,
|
||||
TeamOutlined,
|
||||
ApartmentOutlined,
|
||||
SyncOutlined,
|
||||
DollarOutlined,
|
||||
SettingOutlined,
|
||||
CalendarOutlined,
|
||||
UsergroupAddOutlined,
|
||||
UserSwitchOutlined,
|
||||
StopOutlined,
|
||||
CheckCircleOutlined,
|
||||
CloseCircleOutlined,
|
||||
ContactsOutlined,
|
||||
} from '@ant-design/icons';
|
||||
import dayjs from 'dayjs';
|
||||
import styles from './OnBoarding.less';
|
||||
|
||||
const { Option } = Select;
|
||||
const { TextArea } = Input;
|
||||
const { Title } = Typography;
|
||||
class OnBoarding extends PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
selectedOrgKeys: ['0-0-4'], // 默认选中人事部
|
||||
expandedKeys: ['0-0', '0-0-0', '0-0-1', '0-0-2'],
|
||||
// 入职计划表单数据
|
||||
onboardingForm: {
|
||||
// 入职部门信息
|
||||
departmentName: '人力资源部',
|
||||
departmentManager: '张明远',
|
||||
currentStaff: '24/30',
|
||||
|
||||
// 计划时段设置
|
||||
planStartDate: dayjs('2023-09-01'),
|
||||
planEndDate: dayjs('2023-09-30'),
|
||||
|
||||
// 可入职岗位设置
|
||||
availablePositions: [
|
||||
{ id: 1, name: '人力资源专员', count: 3, checked: true },
|
||||
{ id: 2, name: '薪酬福利专员', count: 2, checked: true },
|
||||
{ id: 3, name: '培训发展专员', count: 1, checked: false },
|
||||
{ id: 4, name: '招聘专员', count: 2, checked: false }
|
||||
],
|
||||
|
||||
// 计划入职人数
|
||||
totalPlan: 5,
|
||||
confirmedCount: 2,
|
||||
remainingCount: 3,
|
||||
|
||||
// 人员要求
|
||||
ageRange: '22-30岁',
|
||||
gender: '不限',
|
||||
education: '本科及以上',
|
||||
workYears: '1-3年',
|
||||
otherRequirements: '',
|
||||
|
||||
// 黑名单
|
||||
blacklist: [
|
||||
{ id: 1, name: '林晓梅', idCard: '310***19880512****', avatar: null },
|
||||
{ id: 2, name: '王建国', idCard: '420***19900215****', avatar: null }
|
||||
],
|
||||
|
||||
// 白名单
|
||||
whitelist: [
|
||||
{ id: 1, name: '陈思思', recommender: '张明远', avatar: null }
|
||||
]
|
||||
},
|
||||
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 });
|
||||
|
||||
if (selectedKeys.length > 0) {
|
||||
const selectedKey = selectedKeys[0];
|
||||
const findNodeByKey = (nodes, key) => {
|
||||
for (let node of nodes) {
|
||||
if (node.key === key) {
|
||||
return node;
|
||||
}
|
||||
if (node.children) {
|
||||
const found = findNodeByKey(node.children, key);
|
||||
if (found) return found;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
const selectedNode = findNodeByKey(this.state.organizationData, selectedKey);
|
||||
if (selectedNode) {
|
||||
this.handleOnboardingFormChange('departmentName', selectedNode.title);
|
||||
const departmentInfo = this.getDepartmentInfo(selectedNode.title);
|
||||
this.setState(prevState => ({
|
||||
onboardingForm: {
|
||||
...prevState.onboardingForm,
|
||||
...departmentInfo
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 获取部门信息
|
||||
getDepartmentInfo = (departmentName) => {
|
||||
const departmentMap = {
|
||||
'技术部': {
|
||||
departmentManager: '李技术',
|
||||
currentStaff: '115/120',
|
||||
availablePositions: [
|
||||
{ id: 1, name: '前端工程师', count: 2, checked: true },
|
||||
{ id: 2, name: '后端工程师', count: 3, checked: true },
|
||||
{ id: 3, name: '测试工程师', count: 1, checked: false }
|
||||
]
|
||||
},
|
||||
'产品部': {
|
||||
departmentManager: '王产品',
|
||||
currentStaff: '65/68',
|
||||
availablePositions: [
|
||||
{ id: 1, name: '产品经理', count: 1, checked: true },
|
||||
{ id: 2, name: '产品设计师', count: 2, checked: true }
|
||||
]
|
||||
},
|
||||
'人事部': {
|
||||
departmentManager: '张明远',
|
||||
currentStaff: '24/30',
|
||||
availablePositions: [
|
||||
{ id: 1, name: '人力资源专员', count: 3, checked: true },
|
||||
{ id: 2, name: '薪酬福利专员', count: 2, checked: true },
|
||||
{ id: 3, name: '培训发展专员', count: 1, checked: false },
|
||||
{ id: 4, name: '招聘专员', count: 2, checked: false }
|
||||
]
|
||||
},
|
||||
'财务部': {
|
||||
departmentManager: '赵财务',
|
||||
currentStaff: '30/32',
|
||||
availablePositions: [
|
||||
{ id: 1, name: '会计', count: 1, checked: true },
|
||||
{ id: 2, name: '出纳', count: 1, checked: false }
|
||||
]
|
||||
},
|
||||
'运营部': {
|
||||
departmentManager: '钱运营',
|
||||
currentStaff: '48/52',
|
||||
availablePositions: [
|
||||
{ id: 1, name: '市场专员', count: 2, checked: true },
|
||||
{ id: 2, name: '客服专员', count: 1, checked: true }
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
return departmentMap[departmentName] || {
|
||||
departmentManager: '未知',
|
||||
currentStaff: '0/0',
|
||||
availablePositions: []
|
||||
};
|
||||
};
|
||||
|
||||
// 树展开/收缩
|
||||
onTreeExpand = (expandedKeys) => {
|
||||
this.setState({ expandedKeys });
|
||||
};
|
||||
|
||||
// 入职表单字段变化
|
||||
handleOnboardingFormChange = (field, value) => {
|
||||
this.setState(prevState => ({
|
||||
onboardingForm: {
|
||||
...prevState.onboardingForm,
|
||||
[field]: value
|
||||
}
|
||||
}));
|
||||
};
|
||||
|
||||
// 岗位选择变化
|
||||
handlePositionChange = (id, checked) => {
|
||||
const { onboardingForm } = this.state;
|
||||
const newPositions = onboardingForm.availablePositions.map(pos =>
|
||||
pos.id === id ? { ...pos, checked } : pos
|
||||
);
|
||||
this.setState({
|
||||
onboardingForm: {
|
||||
...onboardingForm,
|
||||
availablePositions: newPositions
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// 添加新岗位
|
||||
addNewPosition = () => {
|
||||
const { onboardingForm } = this.state;
|
||||
const newPosition = {
|
||||
id: Date.now(),
|
||||
name: '新岗位',
|
||||
count: 1,
|
||||
checked: false
|
||||
};
|
||||
this.setState({
|
||||
onboardingForm: {
|
||||
...onboardingForm,
|
||||
availablePositions: [...onboardingForm.availablePositions, newPosition]
|
||||
}
|
||||
});
|
||||
message.success('新岗位添加成功');
|
||||
};
|
||||
|
||||
// 移除黑名单人员
|
||||
removeFromBlacklist = (id) => {
|
||||
const { onboardingForm } = this.state;
|
||||
const newBlacklist = onboardingForm.blacklist.filter(person => person.id !== id);
|
||||
this.setState({
|
||||
onboardingForm: {
|
||||
...onboardingForm,
|
||||
blacklist: newBlacklist
|
||||
}
|
||||
});
|
||||
message.success('已移除黑名单人员');
|
||||
};
|
||||
|
||||
// 移除白名单人员
|
||||
removeFromWhitelist = (id) => {
|
||||
const { onboardingForm } = this.state;
|
||||
const newWhitelist = onboardingForm.whitelist.filter(person => person.id !== id);
|
||||
this.setState({
|
||||
onboardingForm: {
|
||||
...onboardingForm,
|
||||
whitelist: newWhitelist
|
||||
}
|
||||
});
|
||||
message.success('已移除白名单人员');
|
||||
};
|
||||
|
||||
// 添加黑名单人员
|
||||
addToBlacklist = () => {
|
||||
message.info('功能开发中,敬请期待');
|
||||
};
|
||||
|
||||
// 添加白名单人员
|
||||
addToWhitelist = () => {
|
||||
message.info('功能开发中,敬请期待');
|
||||
};
|
||||
|
||||
// 保存设置
|
||||
handleSave = () => {
|
||||
const { onboardingForm } = this.state;
|
||||
|
||||
if (!onboardingForm.departmentName) {
|
||||
message.error('请选择部门');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!onboardingForm.planStartDate || !onboardingForm.planEndDate) {
|
||||
message.error('请选择计划时段');
|
||||
return;
|
||||
}
|
||||
|
||||
if (onboardingForm.planStartDate.isAfter(onboardingForm.planEndDate)) {
|
||||
message.error('开始日期不能晚于结束日期');
|
||||
return;
|
||||
}
|
||||
|
||||
if (onboardingForm.totalPlan <= 0) {
|
||||
message.error('计划总人数必须大于0');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('保存入职计划设置:', onboardingForm);
|
||||
message.success('入职计划设置保存成功');
|
||||
};
|
||||
|
||||
// 取消操作
|
||||
handleCancel = () => {
|
||||
message.info('已取消操作');
|
||||
};
|
||||
|
||||
render() {
|
||||
const { selectedOrgKeys, expandedKeys, onboardingForm } = 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>
|
||||
|
||||
{/* 右侧入职计划设置 - 替换为HTML内容 */}
|
||||
<Col span={19}>
|
||||
<Card className={styles.mainCard}>
|
||||
{/* 头部标题 */}
|
||||
<div className={styles.header}>
|
||||
<Title level={3} style={{ margin: 0, color: '#333', fontSize: '18px' }}>
|
||||
入职计划设置
|
||||
</Title>
|
||||
<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={
|
||||
<span>
|
||||
<ApartmentOutlined style={{ marginRight: 8, color: '#2D5CF6' }} />
|
||||
入职部门信息
|
||||
</span>
|
||||
}
|
||||
size="small"
|
||||
className={styles.formCard}
|
||||
>
|
||||
<Row gutter={16}>
|
||||
<Col span={8}>
|
||||
<div className={styles.formItem}>
|
||||
<label>部门名称</label>
|
||||
<div className={styles.readOnlyField}>
|
||||
{onboardingForm.departmentName}
|
||||
</div>
|
||||
</div>
|
||||
</Col>
|
||||
<Col span={8}>
|
||||
<div className={styles.formItem}>
|
||||
<label>部门负责人</label>
|
||||
<div className={styles.readOnlyField}>
|
||||
{onboardingForm.departmentManager}
|
||||
</div>
|
||||
</div>
|
||||
</Col>
|
||||
<Col span={8}>
|
||||
<div className={styles.formItem}>
|
||||
<label>当前在岗人数</label>
|
||||
<div className={styles.readOnlyField}>
|
||||
{onboardingForm.currentStaff}
|
||||
</div>
|
||||
</div>
|
||||
</Col>
|
||||
</Row>
|
||||
</Card>
|
||||
|
||||
{/* 计划时段设置 */}
|
||||
<Card
|
||||
title={
|
||||
<span>
|
||||
<CalendarOutlined style={{ marginRight: 8, color: '#2D5CF6' }} />
|
||||
计划时段设置
|
||||
</span>
|
||||
}
|
||||
size="small"
|
||||
className={styles.formCard}
|
||||
>
|
||||
<Row gutter={16}>
|
||||
<Col span={12}>
|
||||
<div className={styles.formItem}>
|
||||
<label>计划开始日期</label>
|
||||
<DatePicker
|
||||
value={onboardingForm.planStartDate}
|
||||
style={{ width: '100%' }}
|
||||
onChange={(date) => this.handleOnboardingFormChange('planStartDate', date)}
|
||||
placeholder="选择开始日期"
|
||||
/>
|
||||
</div>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<div className={styles.formItem}>
|
||||
<label>计划结束日期</label>
|
||||
<DatePicker
|
||||
value={onboardingForm.planEndDate}
|
||||
style={{ width: '100%' }}
|
||||
onChange={(date) => this.handleOnboardingFormChange('planEndDate', date)}
|
||||
placeholder="选择结束日期"
|
||||
/>
|
||||
</div>
|
||||
</Col>
|
||||
</Row>
|
||||
</Card>
|
||||
|
||||
{/* 可入职岗位设置 */}
|
||||
<Card
|
||||
title={
|
||||
<span>
|
||||
<ContactsOutlined style={{ marginRight: 8, color: '#2D5CF6' }} />
|
||||
可入职岗位设置
|
||||
</span>
|
||||
}
|
||||
size="small"
|
||||
className={styles.formCard}
|
||||
>
|
||||
<div className={styles.positionList}>
|
||||
{onboardingForm.availablePositions.map(position => (
|
||||
<div key={position.id} className={styles.positionItem}>
|
||||
<Checkbox
|
||||
checked={position.checked}
|
||||
onChange={(e) => this.handlePositionChange(position.id, e.target.checked)}
|
||||
>
|
||||
{position.name} ({position.count}人)
|
||||
</Checkbox>
|
||||
</div>
|
||||
))}
|
||||
<Button
|
||||
type="link"
|
||||
icon={<PlusCircleOutlined />}
|
||||
onClick={this.addNewPosition}
|
||||
className={styles.addButton}
|
||||
>
|
||||
添加其他岗位
|
||||
</Button>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
{/* 计划入职人数 */}
|
||||
<Card
|
||||
title={
|
||||
<span>
|
||||
<UsergroupAddOutlined style={{ marginRight: 8, color: '#2D5CF6' }} />
|
||||
计划入职人数
|
||||
</span>
|
||||
}
|
||||
size="small"
|
||||
className={styles.formCard}
|
||||
>
|
||||
<Row gutter={16}>
|
||||
<Col span={8}>
|
||||
<div className={styles.formItem}>
|
||||
<label>计划总人数</label>
|
||||
<InputNumber
|
||||
value={onboardingForm.totalPlan}
|
||||
min={0}
|
||||
style={{ width: '100%' }}
|
||||
onChange={(value) => this.handleOnboardingFormChange('totalPlan', value)}
|
||||
placeholder="请输入计划总人数"
|
||||
/>
|
||||
</div>
|
||||
</Col>
|
||||
<Col span={8}>
|
||||
<div className={styles.formItem}>
|
||||
<label>已确认人数</label>
|
||||
<InputNumber
|
||||
value={onboardingForm.confirmedCount}
|
||||
disabled
|
||||
style={{ width: '100%' }}
|
||||
/>
|
||||
</div>
|
||||
</Col>
|
||||
<Col span={8}>
|
||||
<div className={styles.formItem}>
|
||||
<label>剩余名额</label>
|
||||
<InputNumber
|
||||
value={onboardingForm.remainingCount}
|
||||
disabled
|
||||
style={{ width: '100%' }}
|
||||
/>
|
||||
</div>
|
||||
</Col>
|
||||
</Row>
|
||||
</Card>
|
||||
|
||||
{/* 人员要求 */}
|
||||
<Card
|
||||
title={
|
||||
<span>
|
||||
<UserSwitchOutlined style={{ marginRight: 8, color: '#2D5CF6' }} />
|
||||
人员要求
|
||||
</span>
|
||||
}
|
||||
size="small"
|
||||
className={styles.formCard}
|
||||
>
|
||||
<div className={styles.requirementSection}>
|
||||
<div className={styles.formItem}>
|
||||
<label style={{display: 'block',marginRight: '10px'}}>年龄段</label>
|
||||
<Radio.Group
|
||||
value={onboardingForm.ageRange}
|
||||
onChange={(e) => this.handleOnboardingFormChange('ageRange', e.target.value)}
|
||||
>
|
||||
<Radio value="22-30岁">22-30岁</Radio>
|
||||
<Radio value="30-35岁">30-35岁</Radio>
|
||||
<Radio value="不限">不限</Radio>
|
||||
</Radio.Group>
|
||||
</div>
|
||||
|
||||
<div className={styles.formItem}>
|
||||
<label style={{display: 'block', marginRight: '10px'}}>性别</label>
|
||||
<Radio.Group
|
||||
value={onboardingForm.gender}
|
||||
onChange={(e) => this.handleOnboardingFormChange('gender', e.target.value)}
|
||||
>
|
||||
<Radio value="男">男</Radio>
|
||||
<Radio value="女">女</Radio>
|
||||
<Radio value="不限">不限</Radio>
|
||||
</Radio.Group>
|
||||
</div>
|
||||
|
||||
<Row gutter={16}>
|
||||
<Col span={12}>
|
||||
<div className={styles.formItem}>
|
||||
<label>学历要求</label>
|
||||
<Select
|
||||
value={onboardingForm.education}
|
||||
style={{ width: '100%' }}
|
||||
onChange={(value) => this.handleOnboardingFormChange('education', value)}
|
||||
placeholder="请选择学历要求"
|
||||
>
|
||||
<Option value="本科及以上">本科及以上</Option>
|
||||
<Option value="硕士及以上">硕士及以上</Option>
|
||||
<Option value="大专及以上">大专及以上</Option>
|
||||
<Option value="不限">不限</Option>
|
||||
</Select>
|
||||
</div>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<div className={styles.formItem}>
|
||||
<label>工作年限</label>
|
||||
<Select
|
||||
value={onboardingForm.workYears}
|
||||
style={{ width: '100%' }}
|
||||
onChange={(value) => this.handleOnboardingFormChange('workYears', value)}
|
||||
placeholder="请选择工作年限"
|
||||
>
|
||||
<Option value="1-3年">1-3年</Option>
|
||||
<Option value="3-5年">3-5年</Option>
|
||||
<Option value="5年以上">5年以上</Option>
|
||||
<Option value="不限">不限</Option>
|
||||
</Select>
|
||||
</div>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
<div className={styles.formItem}>
|
||||
<label>其他要求</label>
|
||||
<TextArea
|
||||
value={onboardingForm.otherRequirements}
|
||||
rows={3}
|
||||
placeholder="请输入其他特殊要求..."
|
||||
onChange={(e) => this.handleOnboardingFormChange('otherRequirements', e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
{/* 黑名单设置 */}
|
||||
<Card
|
||||
title={
|
||||
<span>
|
||||
<StopOutlined style={{ marginRight: 8, color: '#2D5CF6' }} />
|
||||
黑名单设置
|
||||
</span>
|
||||
}
|
||||
size="small"
|
||||
className={styles.formCard}
|
||||
>
|
||||
<div className={styles.personList}>
|
||||
{onboardingForm.blacklist.map(person => (
|
||||
<div key={person.id} className={styles.personItem}>
|
||||
<div className={styles.personInfo}>
|
||||
<Avatar
|
||||
src={person.avatar}
|
||||
size={32}
|
||||
icon={!person.avatar ? <UserOutlined /> : undefined}
|
||||
/>
|
||||
<div className={styles.personDetails}>
|
||||
<div className={styles.personName}>{person.name}</div>
|
||||
<div className={styles.personMeta}>身份证: {person.idCard}</div>
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
type="link"
|
||||
danger
|
||||
size="small"
|
||||
icon={<CloseCircleOutlined />}
|
||||
onClick={() => this.removeFromBlacklist(person.id)}
|
||||
>
|
||||
移除
|
||||
</Button>
|
||||
</div>
|
||||
))}
|
||||
<Button
|
||||
type="link"
|
||||
icon={<PlusCircleOutlined />}
|
||||
onClick={this.addToBlacklist}
|
||||
className={styles.addButton}
|
||||
>
|
||||
添加黑名单人员
|
||||
</Button>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
{/* 白名单设置 */}
|
||||
<Card
|
||||
title={
|
||||
<span>
|
||||
<CheckCircleOutlined style={{ marginRight: 8, color: '#2D5CF6' }} />
|
||||
白名单设置
|
||||
</span>
|
||||
}
|
||||
size="small"
|
||||
className={styles.formCard}
|
||||
>
|
||||
<div className={styles.personList}>
|
||||
{onboardingForm.whitelist.map(person => (
|
||||
<div key={person.id} className={styles.personItem}>
|
||||
<div className={styles.personInfo}>
|
||||
<Avatar
|
||||
src={person.avatar}
|
||||
size={32}
|
||||
icon={!person.avatar ? <UserOutlined /> : undefined}
|
||||
/>
|
||||
<div className={styles.personDetails}>
|
||||
<div className={styles.personName}>{person.name}</div>
|
||||
<div className={styles.personMeta}>推荐人: {person.recommender}</div>
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
type="link"
|
||||
danger
|
||||
size="small"
|
||||
icon={<CloseCircleOutlined />}
|
||||
onClick={() => this.removeFromWhitelist(person.id)}
|
||||
>
|
||||
移除
|
||||
</Button>
|
||||
</div>
|
||||
))}
|
||||
<Button
|
||||
type="link"
|
||||
icon={<PlusCircleOutlined />}
|
||||
onClick={this.addToWhitelist}
|
||||
className={styles.addButton}
|
||||
>
|
||||
添加白名单人员
|
||||
</Button>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
</Card>
|
||||
</Col>
|
||||
</Row>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default OnBoarding;
|
||||
@ -0,0 +1,345 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
import {
|
||||
Card,
|
||||
Form,
|
||||
Input,
|
||||
Button,
|
||||
Upload,
|
||||
message,
|
||||
Avatar,
|
||||
Row,
|
||||
Col,
|
||||
Space,
|
||||
Typography
|
||||
} from 'antd';
|
||||
import {
|
||||
CameraOutlined,
|
||||
UserOutlined,
|
||||
IdcardOutlined,
|
||||
PlusOutlined,
|
||||
CheckCircleOutlined
|
||||
} from '@ant-design/icons';
|
||||
import styles from './OnBoardingRegister.less';
|
||||
|
||||
const { TextArea } = Input;
|
||||
const { Title, Text } = Typography;
|
||||
|
||||
class OnBoardingRegister extends PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
faceVerified: false,
|
||||
idCardFront: null,
|
||||
idCardBack: null,
|
||||
formData: {
|
||||
department: '技术研发部',
|
||||
position: '前端开发工程师',
|
||||
joinDate: '2025-07-02',
|
||||
name: '',
|
||||
age: '',
|
||||
phone: '',
|
||||
idNumber: '',
|
||||
remark: ''
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// 刷脸认证
|
||||
handleFaceVerification = () => {
|
||||
// 模拟刷脸认证
|
||||
setTimeout(() => {
|
||||
this.setState({ faceVerified: true });
|
||||
message.success('刷脸认证成功');
|
||||
}, 2000);
|
||||
message.loading('正在进行刷脸认证...', 2);
|
||||
};
|
||||
|
||||
// 处理表单字段变化
|
||||
handleFormChange = (field, value) => {
|
||||
this.setState(prevState => ({
|
||||
formData: {
|
||||
...prevState.formData,
|
||||
[field]: value
|
||||
}
|
||||
}));
|
||||
};
|
||||
|
||||
// 处理身份证上传
|
||||
handleIdCardUpload = (type, info) => {
|
||||
if (info.file.status === 'uploading') {
|
||||
return;
|
||||
}
|
||||
if (info.file.status === 'done') {
|
||||
// 获取上传结果
|
||||
const imageUrl = URL.createObjectURL(info.file.originFileObj);
|
||||
this.setState({
|
||||
[type]: imageUrl
|
||||
});
|
||||
message.success(`身份证${type === 'idCardFront' ? '正面' : '反面'}上传成功`);
|
||||
}
|
||||
};
|
||||
|
||||
// 自定义上传
|
||||
customUpload = ({ file, onSuccess }) => {
|
||||
setTimeout(() => {
|
||||
onSuccess("ok");
|
||||
}, 0);
|
||||
};
|
||||
|
||||
// 提交表单
|
||||
handleSubmit = () => {
|
||||
const { formData, faceVerified, idCardFront, idCardBack } = this.state;
|
||||
|
||||
if (!faceVerified) {
|
||||
message.error('请先完成刷脸认证');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!formData.name) {
|
||||
message.error('请输入姓名');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!formData.age) {
|
||||
message.error('请输入年龄');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!formData.phone) {
|
||||
message.error('请输入手机号');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!formData.idNumber) {
|
||||
message.error('请输入身份证号');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!idCardFront || !idCardBack) {
|
||||
message.error('请上传身份证正反面照片');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('提交入职申请数据:', {
|
||||
...formData,
|
||||
faceVerified,
|
||||
idCardFront,
|
||||
idCardBack
|
||||
});
|
||||
|
||||
message.success('入职申请提交成功,请等待审核');
|
||||
};
|
||||
|
||||
render() {
|
||||
const { faceVerified, idCardFront, idCardBack, formData } = this.state;
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<div className={styles.content}>
|
||||
<Title level={2} className={styles.title}>
|
||||
人员入职登记
|
||||
</Title>
|
||||
|
||||
<Card className={styles.formCard}>
|
||||
{/* 刷脸认证区域 */}
|
||||
<div className={styles.faceSection}>
|
||||
<div className={styles.faceContainer}>
|
||||
{faceVerified ? (
|
||||
<Avatar size={120} icon={<UserOutlined />} className={styles.faceAvatar} />
|
||||
) : (
|
||||
<div className={styles.faceUpload}>
|
||||
<CameraOutlined className={styles.cameraIcon} />
|
||||
</div>
|
||||
)}
|
||||
{faceVerified && (
|
||||
<CheckCircleOutlined className={styles.verifiedIcon} />
|
||||
)}
|
||||
</div>
|
||||
<Button
|
||||
type="primary"
|
||||
onClick={this.handleFaceVerification}
|
||||
disabled={faceVerified}
|
||||
className={styles.faceButton}
|
||||
>
|
||||
{faceVerified ? '刷脸认证成功' : '点击刷脸认证'}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* 表单区域 */}
|
||||
<Form layout="vertical" className={styles.form}>
|
||||
{/* 入职信息 */}
|
||||
<Row gutter={16}>
|
||||
<Col span={8}>
|
||||
<Form.Item label="入职部门">
|
||||
<Input
|
||||
value={formData.department}
|
||||
disabled
|
||||
className={styles.readOnlyInput}
|
||||
/>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col span={8}>
|
||||
<Form.Item label="岗位">
|
||||
<Input
|
||||
value={formData.position}
|
||||
disabled
|
||||
className={styles.readOnlyInput}
|
||||
/>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col span={8}>
|
||||
<Form.Item label="入职时间">
|
||||
<Input
|
||||
value={formData.joinDate}
|
||||
disabled
|
||||
className={styles.readOnlyInput}
|
||||
/>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
{/* 个人信息 */}
|
||||
<Row gutter={16}>
|
||||
<Col span={12}>
|
||||
<Form.Item
|
||||
label="姓名"
|
||||
required
|
||||
className={styles.requiredField}
|
||||
>
|
||||
<Input
|
||||
value={formData.name}
|
||||
onChange={(e) => this.handleFormChange('name', e.target.value)}
|
||||
placeholder="请输入真实姓名"
|
||||
/>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<Form.Item
|
||||
label="年龄"
|
||||
required
|
||||
className={styles.requiredField}
|
||||
>
|
||||
<Input
|
||||
type="number"
|
||||
value={formData.age}
|
||||
onChange={(e) => this.handleFormChange('age', e.target.value)}
|
||||
placeholder="请输入年龄"
|
||||
/>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
<Row gutter={16}>
|
||||
<Col span={12}>
|
||||
<Form.Item
|
||||
label="手机号"
|
||||
required
|
||||
className={styles.requiredField}
|
||||
>
|
||||
<Input
|
||||
value={formData.phone}
|
||||
onChange={(e) => this.handleFormChange('phone', e.target.value)}
|
||||
placeholder="请输入手机号码"
|
||||
/>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<Form.Item
|
||||
label="身份证号"
|
||||
required
|
||||
className={styles.requiredField}
|
||||
>
|
||||
<Input
|
||||
value={formData.idNumber}
|
||||
onChange={(e) => this.handleFormChange('idNumber', e.target.value)}
|
||||
placeholder="请输入身份证号码"
|
||||
/>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
{/* 身份证上传 */}
|
||||
<Form.Item
|
||||
label="身份证正反面"
|
||||
required
|
||||
className={styles.requiredField}
|
||||
>
|
||||
<Row gutter={16}>
|
||||
<Col span={12}>
|
||||
<Upload
|
||||
name="idCardFront"
|
||||
listType="picture-card"
|
||||
className={styles.idCardUpload}
|
||||
showUploadList={false}
|
||||
customRequest={this.customUpload}
|
||||
onChange={(info) => this.handleIdCardUpload('idCardFront', info)}
|
||||
>
|
||||
{idCardFront ? (
|
||||
<img src={idCardFront} alt="身份证正面" className={styles.idCardImage} />
|
||||
) : (
|
||||
<div className={styles.uploadContent}>
|
||||
<IdcardOutlined className={styles.uploadIcon} />
|
||||
<div className={styles.uploadText}>身份证正面</div>
|
||||
<Button size="small" className={styles.uploadButton}>
|
||||
上传照片
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</Upload>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<Upload
|
||||
name="idCardBack"
|
||||
listType="picture-card"
|
||||
className={styles.idCardUpload}
|
||||
showUploadList={false}
|
||||
customRequest={this.customUpload}
|
||||
onChange={(info) => this.handleIdCardUpload('idCardBack', info)}
|
||||
>
|
||||
{idCardBack ? (
|
||||
<img src={idCardBack} alt="身份证反面" className={styles.idCardImage} />
|
||||
) : (
|
||||
<div className={styles.uploadContent}>
|
||||
<IdcardOutlined className={styles.uploadIcon} />
|
||||
<div className={styles.uploadText}>身份证反面</div>
|
||||
<Button size="small" className={styles.uploadButton}>
|
||||
上传照片
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</Upload>
|
||||
</Col>
|
||||
</Row>
|
||||
</Form.Item>
|
||||
|
||||
{/* 备注 */}
|
||||
<Form.Item label="备注">
|
||||
<TextArea
|
||||
value={formData.remark}
|
||||
onChange={(e) => this.handleFormChange('remark', e.target.value)}
|
||||
rows={3}
|
||||
placeholder="可填写特殊情况说明"
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
{/* 提交按钮 */}
|
||||
<Form.Item className={styles.submitSection}>
|
||||
<Button
|
||||
type="primary"
|
||||
size="large"
|
||||
block
|
||||
onClick={this.handleSubmit}
|
||||
className={styles.submitButton}
|
||||
>
|
||||
提交入职申请
|
||||
</Button>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default OnBoardingRegister;
|
||||
@ -0,0 +1,411 @@
|
||||
@import '~@/utils/utils.less';
|
||||
|
||||
.container {
|
||||
min-height: 90vh;
|
||||
height: 90vh; // 固定高度
|
||||
background-color: #ffffff;
|
||||
padding: 24px;
|
||||
overflow: hidden; // 容器不滚动
|
||||
}
|
||||
|
||||
.content {
|
||||
max-width: 960px;
|
||||
margin: 0 auto;
|
||||
height: 100%; // 占满容器高度
|
||||
overflow-y: auto; // 添加垂直滚动
|
||||
overflow-x: hidden;
|
||||
padding-right: 8px; // 为滚动条留出空间
|
||||
|
||||
/* 自定义滚动条样式 */
|
||||
&::-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);
|
||||
}
|
||||
|
||||
&:active {
|
||||
background: rgba(0, 0, 0, 0.35);
|
||||
}
|
||||
}
|
||||
|
||||
/* Firefox滚动条 */
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: rgba(0, 0, 0, 0.18) rgba(0, 0, 0, 0.02);
|
||||
}
|
||||
|
||||
.title {
|
||||
text-align: center;
|
||||
margin-bottom: 32px !important;
|
||||
color: #333;
|
||||
font-weight: 600;
|
||||
flex-shrink: 0; // 标题不收缩
|
||||
}
|
||||
|
||||
.formCard {
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
// 增强阴影效果,添加多层阴影
|
||||
box-shadow:
|
||||
0 2px 8px rgba(0, 0, 0, 0.06),
|
||||
0 4px 16px rgba(0, 0, 0, 0.08),
|
||||
0 8px 32px rgba(0, 0, 0, 0.04);
|
||||
border: 1px solid rgba(0, 0, 0, 0.06); // 添加边框增强立体感
|
||||
margin-bottom: 24px; // 底部留出空间
|
||||
transition: all 0.3s ease; // 添加过渡效果
|
||||
|
||||
// 悬停时增强阴影
|
||||
// &:hover {
|
||||
// box-shadow:
|
||||
// 0 4px 12px rgba(0, 0, 0, 0.08),
|
||||
// 0 8px 24px rgba(0, 0, 0, 0.12),
|
||||
// 0 16px 48px rgba(0, 0, 0, 0.06);
|
||||
// transform: translateY(-2px); // 轻微上浮效果
|
||||
// }
|
||||
|
||||
.ant-card-body {
|
||||
padding: 40px;
|
||||
}
|
||||
}
|
||||
|
||||
/* 刷脸认证区域 */
|
||||
.faceSection {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
|
||||
.faceContainer {
|
||||
position: relative;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.faceUpload {
|
||||
width: 120px;
|
||||
height: 120px;
|
||||
border: 2px dashed #d9d9d9;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: #fafafa;
|
||||
transition: all 0.3s ease;
|
||||
// 为刷脸上传区域添加轻微阴影
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.04);
|
||||
|
||||
&:hover {
|
||||
border-color: #2d5cf6;
|
||||
background-color: #f0f9ff;
|
||||
box-shadow: 0 4px 8px rgba(45, 92, 246, 0.1); // 悬停时显示主题色阴影
|
||||
}
|
||||
}
|
||||
|
||||
.cameraIcon {
|
||||
font-size: 36px;
|
||||
color: #bfbfbf;
|
||||
}
|
||||
|
||||
.faceAvatar {
|
||||
background-color: #2d5cf6 !important;
|
||||
border: 3px solid #52c41a;
|
||||
// 为头像添加阴影
|
||||
box-shadow: 0 4px 12px rgba(45, 92, 246, 0.2);
|
||||
}
|
||||
|
||||
.verifiedIcon {
|
||||
position: absolute;
|
||||
bottom: 8px;
|
||||
right: 8px;
|
||||
font-size: 24px;
|
||||
color: #52c41a;
|
||||
background: white;
|
||||
border-radius: 50%;
|
||||
padding: 2px;
|
||||
// 为认证图标添加阴影
|
||||
box-shadow: 0 2px 6px rgba(82, 196, 26, 0.3);
|
||||
}
|
||||
|
||||
.faceButton {
|
||||
background: linear-gradient(135deg, #2d5cf6 0%, #4c7bff 100%);
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
font-weight: 500;
|
||||
height: 36px;
|
||||
padding: 0 20px;
|
||||
// 增强按钮阴影
|
||||
box-shadow: 0 3px 8px rgba(45, 92, 246, 0.3);
|
||||
transition: all 0.3s ease;
|
||||
|
||||
&:hover {
|
||||
background: linear-gradient(135deg, #4c7bff 0%, #6b8fff 100%);
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 6px 16px rgba(45, 92, 246, 0.4);
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
background: #52c41a;
|
||||
transform: none;
|
||||
box-shadow: 0 2px 6px rgba(82, 196, 26, 0.2);
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
|
||||
/* 表单样式 */
|
||||
.form {
|
||||
.ant-form-item-label > label {
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
}
|
||||
}
|
||||
|
||||
.readOnlyInput {
|
||||
background-color: #f5f5f5 !important;
|
||||
color: #666 !important;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.requiredField {
|
||||
.ant-form-item-label > label::after {
|
||||
content: "*";
|
||||
color: #ff4d4f;
|
||||
margin-left: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
/* 身份证上传 */
|
||||
.idCardUpload {
|
||||
.ant-upload {
|
||||
width: 100% !important;
|
||||
height: 120px !important;
|
||||
border: 2px dashed #d9d9d9;
|
||||
border-radius: 8px;
|
||||
background-color: #fafafa;
|
||||
transition: all 0.3s ease;
|
||||
// 为上传区域添加阴影
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.04);
|
||||
|
||||
&:hover {
|
||||
border-color: #2d5cf6;
|
||||
background-color: #f0f9ff;
|
||||
box-shadow: 0 4px 8px rgba(45, 92, 246, 0.1);
|
||||
}
|
||||
}
|
||||
|
||||
.ant-upload-select {
|
||||
width: 100% !important;
|
||||
height: 100% !important;
|
||||
}
|
||||
}
|
||||
|
||||
.uploadContent {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.uploadIcon {
|
||||
font-size: 32px;
|
||||
color: #bfbfbf;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.uploadText {
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.uploadButton {
|
||||
background-color: #f5f6fa;
|
||||
border: 1px solid #d9d9d9;
|
||||
color: #666;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
// 为小按钮添加轻微阴影
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);
|
||||
transition: all 0.3s ease;
|
||||
|
||||
&:hover {
|
||||
border-color: #2d5cf6;
|
||||
color: #2d5cf6;
|
||||
box-shadow: 0 2px 6px rgba(45, 92, 246, 0.15);
|
||||
}
|
||||
}
|
||||
|
||||
.idCardImage {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
border-radius: 6px;
|
||||
// 为上传的图片添加阴影
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
/* 提交区域 */
|
||||
.submitSection {
|
||||
margin-top: 32px;
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
|
||||
.submitButton {
|
||||
background: linear-gradient(135deg, #2d5cf6 0%, #4c7bff 100%);
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
font-weight: 600;
|
||||
font-size: 16px;
|
||||
height: 48px;
|
||||
// 增强提交按钮阴影
|
||||
box-shadow:
|
||||
0 4px 12px rgba(45, 92, 246, 0.3),
|
||||
0 2px 6px rgba(45, 92, 246, 0.2);
|
||||
transition: all 0.3s ease;
|
||||
|
||||
&:hover {
|
||||
background: linear-gradient(135deg, #4c7bff 0%, #6b8fff 100%);
|
||||
transform: translateY(-2px);
|
||||
box-shadow:
|
||||
0 8px 24px rgba(45, 92, 246, 0.4),
|
||||
0 4px 12px rgba(45, 92, 246, 0.3);
|
||||
}
|
||||
|
||||
&:active {
|
||||
transform: translateY(0);
|
||||
box-shadow:
|
||||
0 2px 8px rgba(45, 92, 246, 0.3),
|
||||
0 1px 4px rgba(45, 92, 246, 0.2);
|
||||
}
|
||||
}
|
||||
|
||||
/* 输入框样式 */
|
||||
.container {
|
||||
.ant-input,
|
||||
.ant-input-number,
|
||||
.ant-select-selector {
|
||||
border-radius: 6px;
|
||||
border: 1px solid #d9d9d9;
|
||||
transition: all 0.3s ease;
|
||||
// 为输入框添加轻微阴影
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.04);
|
||||
|
||||
&:hover {
|
||||
border-color: #4c7bff;
|
||||
box-shadow: 0 2px 6px rgba(76, 123, 255, 0.1);
|
||||
}
|
||||
|
||||
&:focus,
|
||||
&.ant-input-focused,
|
||||
&.ant-select-focused .ant-select-selector {
|
||||
border-color: #2d5cf6;
|
||||
box-shadow:
|
||||
0 0 0 2px rgba(45, 92, 246, 0.2),
|
||||
0 2px 8px rgba(45, 92, 246, 0.15);
|
||||
}
|
||||
}
|
||||
|
||||
.ant-input::placeholder {
|
||||
color: #bfbfbf;
|
||||
}
|
||||
}
|
||||
|
||||
/* 响应式设计 */
|
||||
@media (max-width: 768px) {
|
||||
.container {
|
||||
padding: 16px;
|
||||
height: 90vh; // 保持固定高度
|
||||
}
|
||||
|
||||
.content {
|
||||
/* 移动端使用更细的滚动条 */
|
||||
&::-webkit-scrollbar {
|
||||
width: 4px;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background: rgba(0, 0, 0, 0.12);
|
||||
border-radius: 2px;
|
||||
|
||||
&:hover {
|
||||
background: rgba(0, 0, 0, 0.20);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.formCard {
|
||||
// 移动端减少阴影强度
|
||||
box-shadow:
|
||||
0 1px 4px rgba(0, 0, 0, 0.06),
|
||||
0 2px 8px rgba(0, 0, 0, 0.08);
|
||||
|
||||
&:hover {
|
||||
transform: none; // 移动端取消悬停上浮效果
|
||||
box-shadow:
|
||||
0 1px 4px rgba(0, 0, 0, 0.06),
|
||||
0 2px 8px rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.ant-card-body {
|
||||
padding: 24px 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.faceUpload {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
}
|
||||
|
||||
.cameraIcon {
|
||||
font-size: 28px;
|
||||
}
|
||||
|
||||
.uploadContent {
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.uploadIcon {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.idCardUpload .ant-upload {
|
||||
height: 100px !important;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 576px) {
|
||||
.container {
|
||||
padding: 12px; // 进一步减少内边距
|
||||
}
|
||||
|
||||
.content {
|
||||
padding-right: 4px; // 滚动条空间更小
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 20px !important;
|
||||
margin-bottom: 24px !important; // 减少标题下边距
|
||||
}
|
||||
|
||||
.formCard {
|
||||
.ant-card-body {
|
||||
padding: 20px 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.submitButton {
|
||||
font-size: 14px;
|
||||
height: 44px;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,631 @@
|
||||
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
|
||||
} 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
|
||||
} from '@ant-design/icons';
|
||||
import dayjs from 'dayjs';
|
||||
import styles from './Training.less';
|
||||
|
||||
const { Option } = Select;
|
||||
const { TextArea } = Input;
|
||||
const { Title, Text, Paragraph } = Typography;
|
||||
const { RangePicker } = DatePicker;
|
||||
|
||||
class Training extends PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
selectedOrgKeys: ['0-0-4'], // 默认选中人事部
|
||||
expandedKeys: ['0-0', '0-0-0', '0-0-1', '0-0-2'],
|
||||
searchKeyword: '',
|
||||
// 培训数据
|
||||
trainingData: {
|
||||
basicInfo: {
|
||||
name: '',
|
||||
cover: null,
|
||||
description: ''
|
||||
},
|
||||
details: {
|
||||
startDate: null,
|
||||
endDate: null,
|
||||
cost: ''
|
||||
}
|
||||
},
|
||||
// 可选课程
|
||||
availableCourses: [
|
||||
{
|
||||
id: 1,
|
||||
name: '高效沟通技巧',
|
||||
instructor: '张明远',
|
||||
duration: '2小时',
|
||||
selected: false
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: '团队协作与领导力',
|
||||
instructor: '李思雨',
|
||||
duration: '3小时',
|
||||
selected: false
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: '项目管理实战',
|
||||
instructor: '王建国',
|
||||
duration: '4小时',
|
||||
selected: false
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
name: 'Excel高级应用',
|
||||
instructor: '陈晓芳',
|
||||
duration: '2.5小时',
|
||||
selected: false
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
name: '商务礼仪与职业形象',
|
||||
instructor: '赵雅芝',
|
||||
duration: '2小时',
|
||||
selected: false
|
||||
}
|
||||
],
|
||||
// 已选课程
|
||||
selectedCourses: [
|
||||
{
|
||||
id: 1,
|
||||
name: '高效沟通技巧',
|
||||
instructor: '张明远',
|
||||
duration: '2小时',
|
||||
scheduleDate: null,
|
||||
scheduleTime: null,
|
||||
showSchedule: false
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: '团队协作与领导力',
|
||||
instructor: '李思雨',
|
||||
duration: '3小时',
|
||||
scheduleDate: null,
|
||||
scheduleTime: null,
|
||||
showSchedule: false
|
||||
}
|
||||
],
|
||||
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 });
|
||||
};
|
||||
|
||||
// 新建培训
|
||||
handleCreateTraining = () => {
|
||||
message.success('新建培训');
|
||||
};
|
||||
|
||||
// 选择课程
|
||||
handleSelectCourse = (course) => {
|
||||
const { selectedCourses, availableCourses } = this.state;
|
||||
|
||||
// 检查是否已经选择
|
||||
const isSelected = selectedCourses.find(item => item.id === course.id);
|
||||
if (isSelected) {
|
||||
message.warning('该课程已经选择');
|
||||
return;
|
||||
}
|
||||
|
||||
const newSelectedCourse = {
|
||||
...course,
|
||||
scheduleDate: null,
|
||||
scheduleTime: null,
|
||||
showSchedule: false
|
||||
};
|
||||
|
||||
this.setState({
|
||||
selectedCourses: [...selectedCourses, newSelectedCourse],
|
||||
availableCourses: availableCourses.map(item =>
|
||||
item.id === course.id ? { ...item, selected: true } : item
|
||||
)
|
||||
});
|
||||
message.success('课程添加成功');
|
||||
};
|
||||
|
||||
// 移除课程
|
||||
handleRemoveCourse = (courseId) => {
|
||||
const { selectedCourses, availableCourses } = this.state;
|
||||
|
||||
this.setState({
|
||||
selectedCourses: selectedCourses.filter(item => item.id !== courseId),
|
||||
availableCourses: availableCourses.map(item =>
|
||||
item.id === courseId ? { ...item, selected: false } : item
|
||||
)
|
||||
});
|
||||
message.success('课程移除成功');
|
||||
};
|
||||
|
||||
// 设置课程时间
|
||||
handleSetSchedule = (courseId) => {
|
||||
const { selectedCourses } = this.state;
|
||||
this.setState({
|
||||
selectedCourses: selectedCourses.map(item =>
|
||||
item.id === courseId ? { ...item, showSchedule: !item.showSchedule } : item
|
||||
)
|
||||
});
|
||||
};
|
||||
|
||||
// 确认时间设置
|
||||
handleConfirmSchedule = (courseId, date, time) => {
|
||||
const { selectedCourses } = this.state;
|
||||
this.setState({
|
||||
selectedCourses: selectedCourses.map(item =>
|
||||
item.id === courseId ? {
|
||||
...item,
|
||||
scheduleDate: date,
|
||||
scheduleTime: time,
|
||||
showSchedule: false
|
||||
} : item
|
||||
)
|
||||
});
|
||||
message.success('时间设置成功');
|
||||
};
|
||||
|
||||
// 搜索课程
|
||||
handleSearchCourse = (value) => {
|
||||
this.setState({ searchKeyword: value });
|
||||
};
|
||||
|
||||
// 保存培训
|
||||
handleSave = () => {
|
||||
message.success('培训保存成功');
|
||||
};
|
||||
|
||||
// 取消
|
||||
handleCancel = () => {
|
||||
message.info('取消操作');
|
||||
};
|
||||
|
||||
// 封面上传
|
||||
handleCoverUpload = (info) => {
|
||||
if (info.file.status === 'done') {
|
||||
message.success('封面上传成功');
|
||||
}
|
||||
};
|
||||
|
||||
// 过滤课程
|
||||
getFilteredCourses = () => {
|
||||
const { availableCourses, searchKeyword } = this.state;
|
||||
if (!searchKeyword) return availableCourses;
|
||||
|
||||
return availableCourses.filter(course =>
|
||||
course.name.toLowerCase().includes(searchKeyword.toLowerCase()) ||
|
||||
course.instructor.toLowerCase().includes(searchKeyword.toLowerCase())
|
||||
);
|
||||
};
|
||||
|
||||
render() {
|
||||
const { selectedOrgKeys, expandedKeys, selectedCourses, searchKeyword } = this.state;
|
||||
const filteredCourses = this.getFilteredCourses();
|
||||
|
||||
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}>
|
||||
<Title level={3} style={{ margin: 0, color: '#333', fontSize: '20px' }}>
|
||||
培训管理
|
||||
</Title>
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<PlusCircleOutlined />}
|
||||
onClick={this.handleCreateTraining}
|
||||
className={styles.createBtn}
|
||||
>
|
||||
新建培训
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* 滚动内容区 */}
|
||||
<div className={styles.content}>
|
||||
{/* 培训基本信息 */}
|
||||
<Card className={styles.sectionCard} title="基本信息">
|
||||
<Row gutter={16}>
|
||||
<Col span={12}>
|
||||
<div className={styles.formItem}>
|
||||
<Text className={styles.formLabel}>培训名称</Text>
|
||||
<Input
|
||||
placeholder="请输入培训名称"
|
||||
className={styles.formInput}
|
||||
/>
|
||||
</div>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<div className={styles.formItem}>
|
||||
<Text className={styles.formLabel}>培训封面</Text>
|
||||
<div className={styles.uploadContainer}>
|
||||
<Upload
|
||||
showUploadList={false}
|
||||
onChange={this.handleCoverUpload}
|
||||
className={styles.coverUpload}
|
||||
>
|
||||
<div className={styles.uploadArea}>
|
||||
<PictureOutlined className={styles.uploadIcon} />
|
||||
</div>
|
||||
</Upload>
|
||||
<Text className={styles.uploadText}>点击上传封面</Text>
|
||||
</div>
|
||||
</div>
|
||||
</Col>
|
||||
</Row>
|
||||
<div className={styles.formItem}>
|
||||
<Text className={styles.formLabel}>主题设计</Text>
|
||||
<TextArea
|
||||
rows={4}
|
||||
placeholder="在此输入培训主题和描述..."
|
||||
className={styles.formTextArea}
|
||||
/>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
{/* 培训详情 */}
|
||||
<Card className={styles.sectionCard} title="培训详情">
|
||||
<Row gutter={16}>
|
||||
<Col span={16}>
|
||||
<div className={styles.formItem}>
|
||||
<Text className={styles.formLabel}>培训时间</Text>
|
||||
<RangePicker
|
||||
style={{ width: '100%' }}
|
||||
className={styles.formInput}
|
||||
/>
|
||||
</div>
|
||||
</Col>
|
||||
<Col span={8}>
|
||||
<div className={styles.formItem}>
|
||||
<Text className={styles.formLabel}>培训费用(元)</Text>
|
||||
<InputNumber
|
||||
placeholder="请输入费用"
|
||||
style={{ width: '100%' }}
|
||||
className={styles.formInput}
|
||||
/>
|
||||
</div>
|
||||
</Col>
|
||||
</Row>
|
||||
</Card>
|
||||
|
||||
{/* 课程管理 */}
|
||||
<Card className={styles.sectionCard} title="课程管理">
|
||||
<Row gutter={16}>
|
||||
{/* 可选课程 */}
|
||||
<Col span={12}>
|
||||
<div className={styles.courseSection}>
|
||||
<div className={styles.courseSectionHeader}>
|
||||
<Text className={styles.courseSectionTitle}>可选课程</Text>
|
||||
<Input
|
||||
placeholder="搜索课程"
|
||||
prefix={<SearchOutlined />}
|
||||
value={searchKeyword}
|
||||
onChange={(e) => this.handleSearchCourse(e.target.value)}
|
||||
className={styles.searchInput}
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.courseList}>
|
||||
{filteredCourses.map(course => (
|
||||
<div
|
||||
key={course.id}
|
||||
className={`${styles.courseItem} ${course.selected ? styles.courseItemSelected : ''}`}
|
||||
onClick={() => this.handleSelectCourse(course)}
|
||||
>
|
||||
<div className={styles.courseInfo}>
|
||||
<Text className={styles.courseName}>{course.name}</Text>
|
||||
<Text className={styles.courseInstructor}>讲师: {course.instructor}</Text>
|
||||
</div>
|
||||
<Tag className={styles.courseDuration}>{course.duration}</Tag>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</Col>
|
||||
|
||||
{/* 已选课程 */}
|
||||
<Col span={12}>
|
||||
<div className={styles.courseSection}>
|
||||
<Text className={styles.courseSectionTitle}>已选课程</Text>
|
||||
<div className={styles.courseList}>
|
||||
{selectedCourses.map(course => (
|
||||
<div key={course.id} className={styles.selectedCourseItem}>
|
||||
<div className={styles.courseItemHeader}>
|
||||
<div className={styles.courseInfo}>
|
||||
<Text className={styles.courseName}>{course.name}</Text>
|
||||
<Text className={styles.courseInstructor}>讲师: {course.instructor}</Text>
|
||||
</div>
|
||||
<div className={styles.courseActions}>
|
||||
<Tag className={styles.courseDuration}>{course.duration}</Tag>
|
||||
<Button
|
||||
type="link"
|
||||
size="small"
|
||||
onClick={() => this.handleSetSchedule(course.id)}
|
||||
className={styles.scheduleBtn}
|
||||
>
|
||||
设置时间
|
||||
</Button>
|
||||
<Button
|
||||
type="text"
|
||||
size="small"
|
||||
icon={<DeleteOutlined />}
|
||||
onClick={() => this.handleRemoveCourse(course.id)}
|
||||
className={styles.removeBtn}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{course.showSchedule && (
|
||||
<div className={styles.scheduleSettings}>
|
||||
<div className={styles.scheduleRow}>
|
||||
<Text className={styles.scheduleLabel}>上课时间:</Text>
|
||||
<DatePicker
|
||||
placeholder="选择日期"
|
||||
className={styles.dateInput}
|
||||
/>
|
||||
<Input
|
||||
type="time"
|
||||
className={styles.timeInput}
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.scheduleActions}>
|
||||
<Button
|
||||
type="primary"
|
||||
size="small"
|
||||
onClick={() => this.handleConfirmSchedule(course.id)}
|
||||
className={styles.confirmBtn}
|
||||
>
|
||||
确认
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</Col>
|
||||
</Row>
|
||||
</Card>
|
||||
|
||||
{/* 底部操作按钮 */}
|
||||
<div className={styles.actionButtons}>
|
||||
<Button onClick={this.handleCancel}>取消</Button>
|
||||
<Button
|
||||
type="primary"
|
||||
onClick={this.handleSave}
|
||||
className={styles.saveBtn}
|
||||
>
|
||||
保存
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</Col>
|
||||
</Row>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default Training;
|
||||
Loading…
Reference in New Issue