考勤管理页面开发

master
jiangxucong 2 months ago
parent 88aeb60d11
commit 1f512f9f03

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

@ -1,14 +1,576 @@
import React, {Fragment, PureComponent} from 'react';
import styles from './attendancemanageAttendancedata.less';
class attendancemanageAttendancedata extends PureComponent {
import React, { PureComponent } from 'react';
import {
Card,
Tree,
Button,
Select,
Space,
Row,
Col,
Pagination
} from 'antd';
import { history } from 'umi';
import {
ExpandOutlined,
UserOutlined,
TeamOutlined,
ApartmentOutlined,
SyncOutlined,
DollarOutlined,
SettingOutlined,
DownloadOutlined,
UserAddOutlined,
EditOutlined,
DeleteOutlined
} from '@ant-design/icons';
import styles from './AttendancemanageAttendancedata.less';
import StandardTable from '@/components/StandardTable';
import AttendancedataAdd from './form/AttendancedataAdd';
import AttendancedataAddRenderSimpleForm from "./form/AttendancedataAddRenderSimpleForm" //表单
const { Option } = Select;
class AttendancemanageAttendancedata 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',
employeeId: 'EMP001',
employeeName: '张三',
date: '2024-01-15',
unit: '飞利信科技有限公司',
department: '技术部',
attendanceStatus: '正常',
clockInTime: '09:00:00',
clockOutTime: '18:00:00'
},
{
key: '2',
employeeId: 'EMP002',
employeeName: '李四',
date: '2024-01-15',
unit: '飞利信科技有限公司',
department: '产品部',
attendanceStatus: '迟到',
clockInTime: '09:15:00',
clockOutTime: '18:00:00'
},
{
key: '3',
employeeId: 'EMP003',
employeeName: '王五',
date: '2024-01-15',
unit: '飞利信科技有限公司',
department: '运营部',
attendanceStatus: '早退',
clockInTime: '09:00:00',
clockOutTime: '17:30:00'
},
{
key: '4',
employeeId: 'EMP004',
employeeName: '赵六',
date: '2024-01-15',
unit: '飞利信科技有限公司',
department: '财务部',
attendanceStatus: '正常',
clockInTime: '08:55:00',
clockOutTime: '18:05:00'
},
{
key: '5',
employeeId: 'EMP005',
employeeName: '陈七',
date: '2024-01-15',
unit: '飞利信科技有限公司',
department: '人事部',
attendanceStatus: '缺勤',
clockInTime: '--',
clockOutTime: '--'
},
],
pagination: {
current: 1,
pageSize: 5,
total: 356,
}
};
// 默认列配置
this.defaultColumns = [
{
title: '员工ID',
dataIndex: 'employeeId',
key: 'employeeId',
width: 100,
align: 'center',
},
{
title: '员工姓名',
dataIndex: 'employeeName',
key: 'employeeName',
width: 100,
align: 'center',
},
{
title: '日期',
dataIndex: 'date',
key: 'date',
width: 120,
align: 'center',
},
{
title: '单位',
dataIndex: 'unit',
key: 'unit',
width: 180,
ellipsis: true,
},
{
title: '部门',
dataIndex: 'department',
key: 'department',
width: 100,
align: 'center',
},
{
title: '考勤状态',
dataIndex: 'attendanceStatus',
key: 'attendanceStatus',
width: 100,
align: 'center',
render: (text) => {
const statusColors = {
'正常': '#52c41a',
'迟到': '#faad14',
'早退': '#ff7875',
'缺勤': '#f50',
'请假': '#1890ff'
};
return (
<span style={{ color: statusColors[text] || '#666' }}>
{text}
</span>
);
}
},
{
title: '上班时间',
dataIndex: 'clockInTime',
key: 'clockInTime',
width: 100,
align: 'center',
},
{
title: '下班时间',
dataIndex: 'clockOutTime',
key: 'clockOutTime',
width: 100,
align: 'center',
},
{
title: '操作',
key: 'action',
align: 'center',
width: 80,
render: (_, record) => (
<Space size="middle">
<Button
type="link"
size="small"
icon={<EditOutlined />}
title="编辑"
onClick={() => this.handleEdit(record)}
/>
<Button
type="link"
size="small"
danger
icon={<DeleteOutlined />}
title="删除"
onClick={() => this.handleDelete(record)}
/>
</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 (
<AttendancedataAddRenderSimpleForm {...parentMethods} />
);
}
renderAdvancedForm() {
const { prooperlog: { params } } = this.props;
const parentMethods = {
handleSearch: this.handleSearch,
handleFormReset: this.handleFormReset,
toggleForm: this.toggleForm,
params
};
return (
<AttendancedataAddRenderSimpleForm {...parentMethods} />
);
}
renderForm() {
const { expandForm } = this.state;
return expandForm ? this.renderAdvancedForm() : this.renderSimpleForm();
}
// 分页处理
onPaginationChange = (page, pageSize) => {
this.setState({
pagination: {
...this.state.pagination,
current: page,
pageSize,
}
});
};
// 显示新增弹窗
showAddModal = () => {
this.setState({ addModalVisible: true });
};
// 关闭新增弹窗
hideAddModal = () => {
this.setState({ addModalVisible: false });
};
// 新增成功回调
handleAddSuccess = (values) => {
console.log('新增考勤记录成功:', values);
// 这里可以刷新列表数据
// 模拟添加到表格数据中
const newAttendance = {
key: String(Date.now()),
employeeId: values.employeeId || `EMP${String(this.state.tableData.length + 1).padStart(3, '0')}`,
employeeName: values.employeeName,
date: values.date || new Date().toISOString().split('T')[0],
unit: values.unit || '飞利信科技有限公司',
department: values.department,
attendanceStatus: values.attendanceStatus || '正常',
clockInTime: values.clockInTime || '09:00:00',
clockOutTime: values.clockOutTime || '18:00:00'
};
this.setState({
tableData: [...this.state.tableData, newAttendance],
pagination: {
...this.state.pagination,
total: this.state.pagination.total + 1
}
});
};
// 处理姓名点击事件
handleNameClick = (record) => {
console.log('点击员工:', record);
// 跳转到人员详情页面传递员工ID参数
history.push(`/topnavbar00/staffmanage/particulars`);
};
// 处理编辑
handleEdit = (record) => {
console.log('编辑员工:', record);
// 这里可以打开编辑弹窗或跳转到编辑页面
};
// 处理删除
handleDelete = (record) => {
console.log('删除员工:', record);
// 这里可以弹出确认框并执行删除操作
};
render() {
const { tableData, pagination, expandedKeys, addModalVisible, addLoading } = this.state;
return (
<>
<iframe title="考勤查询" className={styles.frameContent} src="/考勤查询.html"/>
</>
)
<div className={styles.staffInfoContainer}>
{/* 主体内容 */}
<div className={styles.mainContent}>
<Card className={styles.contentCard}>
<Row gutter={24}>
{/* 左侧组织架构树 */}
<Col span={5}>
<Card
title={
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', width: '100%' }}>
<div style={{ display: 'flex', alignItems: 'center' }}>
<TeamOutlined style={{ marginRight: 8, color: '#1890ff' }} />
<span>组织架构</span>
</div>
<Space>
<Button
type="text"
icon={<SyncOutlined />}
size="small"
style={{ color: '#1890ff' }}
onClick={this.handleTreeRefresh}
title="刷新"
/>
<Button
type="text"
icon={<ExpandOutlined />}
size="small"
style={{ color: '#1890ff' }}
onClick={this.handleTreeToggle}
title={expandedKeys.length > 0 ? "收缩全部" : "展开全部"}
/>
</Space>
</div>
}
className={styles.treeCard}
>
<div className={styles.treeContainer}>
<Tree
showIcon
expandedKeys={expandedKeys}
onSelect={this.onTreeSelect}
onExpand={this.onTreeExpand}
treeData={this.getTreeData()}
className={styles.orgTree}
/>
</div>
</Card>
</Col>
{/* 右侧人员信息区 */}
<Col span={19}>
{/* 筛选条件 */}
<Card className={styles.searchCard}>
{this.renderForm()}
</Card>
{/* 操作按钮和统计 */}
<div className={styles.actionBar}>
<div className={styles.totalInfo}>
{/* 共 {pagination.total} 条记录 */}
</div>
<Space size="middle">
<Button
icon={<DownloadOutlined />}
size="middle"
// className={styles.exportButton}
>
导出
</Button>
{/* <Button
type="primary"
icon={<UserAddOutlined />}
size="large"
className={styles.addButton}
onClick={this.showAddModal}
>
新增
</Button> */}
</Space>
</div>
{/* 人员表格 */}
<Card className={styles.tableCard}>
<StandardTable
columns={this.defaultColumns}
dataSource={tableData}
pagination={false}
scroll={{ x: 1000,y: 600 }}
size="small"
selectedRows={[]}
/>
{/* 分页 */}
<div className={styles.paginationWrapper}>
<Pagination
current={pagination.current}
total={pagination.total}
pageSize={pagination.pageSize}
showSizeChanger
showQuickJumper
showTotal={(total, range) =>
`显示 ${range[0]}${range[1]} 条,共 ${total} 条记录`
}
onChange={this.onPaginationChange}
onShowSizeChange={this.onPaginationChange}
/>
</div>
</Card>
</Col>
</Row>
</Card>
</div>
{/* 新增人员弹窗 */}
<AttendancedataAdd
visible={addModalVisible}
loading={addLoading}
onCancel={this.hideAddModal}
onSuccess={this.handleAddSuccess}
/>
</div>
);
}
}
export default attendancemanageAttendancedata
export default AttendancemanageAttendancedata;

@ -1,10 +1,521 @@
@import '~@/utils/utils.less';
.frameContent {
width: 100%;
height: 100vh;
border: none;
display: block;
margin: 0;
padding: 0;
.staffInfoContainer {
min-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;
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 {
.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,218 @@
import React, { PureComponent } from 'react';
import {
Modal,
Form,
Input,
Select,
InputNumber,
Row,
Col,
message,
TreeSelect
} from 'antd';
import {
ApartmentOutlined,
UserOutlined,
TeamOutlined,
FileTextOutlined,
NumberOutlined
} from '@ant-design/icons';
const { Option } = Select;
class DeptMaintainAdd 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={700}
confirmLoading={loading}
destroyOnClose={true}
maskClosable={false}
>
<Form
ref={this.formRef}
layout="vertical"
onFinish={this.handleSubmit}
initialValues={{
status: '1',
level: '2'
}}
>
<Row gutter={16}>
<Col span={12}>
<Form.Item
name="deptName"
label="部门名称"
rules={[
{ required: true, message: '请输入部门名称' },
{ min: 2, max: 20, message: '部门名称长度为2-20个字符' }
]}
>
<Input
placeholder="请输入部门名称"
prefix={<ApartmentOutlined />}
/>
</Form.Item>
</Col>
<Col span={12}>
<Form.Item
name="deptCode"
label="部门编码"
rules={[
{ required: true, message: '请输入部门编码' },
{ pattern: /^[A-Z0-9]{3,10}$/, message: '部门编码为3-10位大写字母和数字' }
]}
>
<Input
placeholder="请输入部门编码TECH001"
prefix={<NumberOutlined />}
style={{ textTransform: 'uppercase' }}
/>
</Form.Item>
</Col>
</Row>
<Row gutter={16}>
<Col span={12}>
<Form.Item
name="parentDept"
label="上级部门"
rules={[
{ required: false, message: '请选择上级部门' }
]}
>
<TreeSelect
placeholder="请选择上级部门"
allowClear
treeDefaultExpandAll
treeData={[
{
title: '飞利信科技有限公司',
value: '0-0',
children: [
{ title: '技术部', value: '0-0-0' },
{ title: '产品部', value: '0-0-1' },
{ title: '运营部', value: '0-0-2' },
{ title: '财务部', value: '0-0-3' },
{ title: '人事部', value: '0-0-4' },
]
}
]}
/>
</Form.Item>
</Col>
<Col span={12}>
<Form.Item
name="deptHead"
label="部门负责人"
rules={[
{ required: true, message: '请输入部门负责人' }
]}
>
<Input
placeholder="请输入部门负责人姓名"
prefix={<UserOutlined />}
/>
</Form.Item>
</Col>
</Row>
<Row gutter={16}>
<Col span={12}>
<Form.Item
name="level"
label="部门层级"
rules={[
{ required: true, message: '请选择部门层级' }
]}
>
<Select placeholder="请选择部门层级">
<Option value="1">一级部门</Option>
<Option value="2">二级部门</Option>
<Option value="3">三级部门</Option>
</Select>
</Form.Item>
</Col>
<Col span={12}>
<Form.Item
name="status"
label="状态"
rules={[
{ required: true, message: '请选择状态' }
]}
>
<Select placeholder="请选择状态">
<Option value="1">启用</Option>
<Option value="0">禁用</Option>
</Select>
</Form.Item>
</Col>
</Row>
<Row gutter={16}>
<Col span={24}>
<Form.Item
name="description"
label="部门描述"
rules={[
{ max: 200, message: '描述不能超过200个字符' }
]}
>
<Input.TextArea
placeholder="请输入部门职责描述"
rows={3}
showCount
maxLength={200}
/>
</Form.Item>
</Col>
</Row>
</Form>
</Modal>
);
}
}
export default DeptMaintainAdd;

@ -0,0 +1,204 @@
import React, { useState } from 'react';
import { Button, Col, Form, Input, Row, message, Select, Space, DatePicker, TreeSelect } from 'antd';
import { ClearOutlined, SearchOutlined, ExpandOutlined, CompressOutlined, UserOutlined, ApartmentOutlined, TeamOutlined } from '@ant-design/icons';
import styles from "../AttendancemanageAttendancedata.less";
const FormItem = Form.Item;
const { Option } = Select;
const { RangePicker } = DatePicker;
const AttendancedataAddRenderSimpleForm = (props) => {
const [form] = Form.useForm();
const [expandForm, setExpandForm] = useState(false);
const { submitButtons, handleSearch, handleFormReset, toggleForm, params } = props;
React.useEffect(() => {
form.setFieldsValue({
employeeId: params?.employeeId,
employeeName: params?.employeeName,
unit: params?.unit,
department: params?.department,
dateRange: params?.dateRange,
attendanceStatus: params?.attendanceStatus,
});
}, [params]);
const onFinish = values => {
const searchParams = {
...values,
dateRange: values.dateRange ? [
values.dateRange[0].format('YYYY-MM-DD'),
values.dateRange[1].format('YYYY-MM-DD')
] : undefined,
};
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="employeeId"
label="员工ID"
>
<Input
placeholder="请输入员工ID"
allowClear
prefix={<UserOutlined />}
/>
</Form.Item>
</Col>
<Col span={6}>
<Form.Item
name="employeeName"
label="员工姓名"
>
<Input
placeholder="请输入员工姓名"
allowClear
prefix={<UserOutlined />}
/>
</Form.Item>
</Col>
<Col span={6}>
<Form.Item
name="unit"
label="单位"
>
<Select
placeholder="请选择单位"
allowClear
suffixIcon={<ApartmentOutlined />}
>
<Option value="company1">飞利信科技有限公司</Option>
<Option value="company2">飞利信软件分公司</Option>
<Option value="company3">飞利信研发中心</Option>
</Select>
</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="部门"
>
<TreeSelect
placeholder="请选择部门"
allowClear
treeDefaultExpandAll
suffixIcon={<TeamOutlined />}
treeData={[
{
title: '技术部',
value: 'tech',
children: [
{ title: '前端组', value: 'tech-frontend' },
{ title: '后端组', value: 'tech-backend' },
{ title: '测试组', value: 'tech-test' }
]
},
{
title: '产品部',
value: 'product',
children: [
{ title: '产品设计组', value: 'product-design' },
{ title: '用户体验组', value: 'product-ux' }
]
},
{
title: '运营部',
value: 'operation',
children: [
{ title: '市场营销组', value: 'operation-marketing' },
{ title: '客户服务组', value: 'operation-service' }
]
},
{ title: '财务部', value: 'finance' },
{ title: '人事部', value: 'hr' }
]}
/>
</Form.Item>
</Col>
<Col span={6}>
<Form.Item
name="dateRange"
label="日期范围"
>
<RangePicker
placeholder={['开始日期', '结束日期']}
style={{ width: '100%' }}
format="YYYY-MM-DD"
/>
</Form.Item>
</Col>
<Col span={6}>
<Form.Item
name="attendanceStatus"
label="考勤状态"
>
<Select placeholder="请选择考勤状态" allowClear>
<Option value="normal">正常</Option>
<Option value="late">迟到</Option>
<Option value="early">早退</Option>
<Option value="absence">缺勤</Option>
<Option value="leave">请假</Option>
</Select>
</Form.Item>
</Col>
</Row>
)}
</Form>
</div>
);
};
export default AttendancedataAddRenderSimpleForm;

@ -0,0 +1,466 @@
import React, { PureComponent } from 'react';
import { Card, Input, Button, Table, DatePicker, Tag, Avatar, Row, Col, Statistic, Progress } from 'antd';
import { SearchOutlined, SyncOutlined, ExclamationCircleOutlined, EditOutlined, ExportOutlined, LineChartOutlined, CalendarOutlined, CalculatorOutlined, DownloadOutlined, PieChartOutlined } from '@ant-design/icons';
import * as echarts from 'echarts';
import styles from './Attendancedetails.less';
const { RangePicker } = DatePicker;
class Attendancedetails extends PureComponent {
constructor(props) {
super(props);
this.state = {
employeeId: '',
employeeName: '',
dateRange: null,
attendanceData: [],
loading: false,
};
}
componentDidMount() {
this.initAttendanceChart();
}
// 初始化考勤分析图表
initAttendanceChart = () => {
const chartDom = document.getElementById('attendanceChart');
if (chartDom) {
const myChart = echarts.init(chartDom);
const option = {
tooltip: {
trigger: 'axis'
},
legend: {
data: ['正常出勤', '迟到', '早退', '缺勤']
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true
},
xAxis: {
type: 'category',
boundaryGap: false,
data: ['1日', '2日', '3日', '4日', '5日', '6日', '7日', '8日', '9日', '10日']
},
yAxis: {
type: 'value'
},
series: [
{
name: '正常出勤',
type: 'line',
stack: 'Total',
data: [9, 9, 8.5, 9, 9.5, 9, 8.8, 9.2, 9, 9.3]
},
{
name: '迟到',
type: 'line',
stack: 'Total',
data: [0, 0, 0.5, 0, 0, 0, 0.2, 0, 0, 0]
},
{
name: '早退',
type: 'line',
stack: 'Total',
data: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
},
{
name: '缺勤',
type: 'line',
stack: 'Total',
data: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
}
]
};
myChart.setOption(option);
}
};
// 查询考勤数据
handleSearch = () => {
this.setState({ loading: true });
// 模拟API调用
setTimeout(() => {
this.setState({ loading: false });
}, 1000);
};
// 表格列定义
getTableColumns = () => [
{
title: '日期',
dataIndex: 'date',
key: 'date',
width: 120,
},
{
title: '考勤地点',
dataIndex: 'location',
key: 'location',
width: 150,
},
{
title: '考勤状态',
dataIndex: 'status',
key: 'status',
width: 100,
render: (status) => {
const statusConfig = {
normal: { color: 'success', text: '正常' },
late: { color: 'error', text: '迟到' },
early: { color: 'warning', text: '早退' },
remote: { color: 'processing', text: '远程' },
};
const config = statusConfig[status] || statusConfig.normal;
return <Tag color={config.color}>{config.text}</Tag>;
},
},
{
title: '上班时间',
dataIndex: 'checkInTime',
key: 'checkInTime',
width: 120,
},
{
title: '下班时间',
dataIndex: 'checkOutTime',
key: 'checkOutTime',
width: 120,
},
{
title: '工作时长',
dataIndex: 'workHours',
key: 'workHours',
width: 120,
},
{
title: '操作',
key: 'action',
width: 80,
render: (_, record) => (
<Button type="link" size="small">
详情
</Button>
),
},
];
// 模拟表格数据
getTableData = () => [
{
key: '1',
date: '2023-11-01',
location: '总部大楼-3F',
status: 'normal',
checkInTime: '08:56',
checkOutTime: '18:30',
workHours: '9小时34分钟',
},
{
key: '2',
date: '2023-10-31',
location: '总部大楼-3F',
status: 'normal',
checkInTime: '09:02',
checkOutTime: '18:45',
workHours: '9小时43分钟',
},
{
key: '3',
date: '2023-10-30',
location: '远程办公',
status: 'remote',
checkInTime: '09:15',
checkOutTime: '18:50',
workHours: '9小时35分钟',
},
{
key: '4',
date: '2023-10-27',
location: '总部大楼-3F',
status: 'late',
checkInTime: '09:35',
checkOutTime: '18:20',
workHours: '8小时45分钟',
},
{
key: '5',
date: '2023-10-26',
location: '总部大楼-3F',
status: 'normal',
checkInTime: '08:50',
checkOutTime: '18:10',
workHours: '9小时20分钟',
},
];
render() {
const { employeeId, employeeName, dateRange, loading } = this.state;
return (
<div className={styles['attendance-details-page']}>
{/* 查询条件 */}
<Card className={styles['search-section']}>
<Row gutter={16} align="bottom">
<Col xs={24} sm={12} md={6}>
<div className={styles['form-item']}>
<label className={styles['form-label']}>员工 ID</label>
<Input
placeholder="请输入员工ID"
value={employeeId}
onChange={(e) => this.setState({ employeeId: 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={employeeName}
onChange={(e) => this.setState({ employeeName: e.target.value })}
/>
</div>
</Col>
<Col xs={24} sm={12} md={6}>
<div className={styles['form-item']}>
<label className={styles['form-label']}>日期范围</label>
<RangePicker
style={{ width: '100%' }}
placeholder={['开始日期', '结束日期']}
value={dateRange}
onChange={(dates) => this.setState({ dateRange: dates })}
/>
</div>
</Col>
<Col xs={24} sm={12} md={6}>
<div className={styles['form-item']}>
<label className={styles['form-label']} style={{ visibility: 'hidden' }}>占位</label>
<Button
type="primary"
icon={<SearchOutlined />}
onClick={this.handleSearch}
loading={loading}
block
>
查询
</Button>
</div>
</Col>
</Row>
</Card>
{/* 第一行卡片组 */}
<Row gutter={[16, 16]} className={styles['cards-row']}>
{/* 员工基本信息卡片 */}
<Col xs={24} md={8}>
<Card className={styles['info-card']} hoverable>
<div className={styles['employee-info']}>
<Avatar size={64} src="https://mastergo.com/ai/api/search-image?query=professional20asian20business20woman20portrait20with20white20background20modern20office20attire20high20quality20detailed20facial20features20corporate20identity20isolated20on20solid20color20background&width=80&height=80&orientation=squarish&flag=be98d141ba2245f194713abc5bba69dc" />
<div className={styles['employee-details']}>
<h3>张雨晴</h3>
<p>ID: E10086</p>
</div>
</div>
<div className={styles['employee-meta']}>
<div className={styles['meta-item']}>
<span className={styles['meta-label']}>部门:</span>
<span>产品研发中心</span>
</div>
<div className={styles['meta-item']}>
<span className={styles['meta-label']}>岗位:</span>
<span>高级产品经理</span>
</div>
<div className={styles['meta-item']}>
<span className={styles['meta-label']}>职级:</span>
<span>P7</span>
</div>
<div className={styles['meta-item']}>
<span className={styles['meta-label']}>入职日期:</span>
<span>2019-05-15</span>
</div>
</div>
</Card>
</Col>
{/* 考勤状态卡片 */}
<Col xs={24} md={8}>
<Card title="今日考勤状态" className={styles['status-card']} hoverable>
<div className={styles['status-info']}>
<div className={styles['status-badge']}>
<div className={styles['status-dot']}></div>
<span>正常出勤</span>
</div>
<div className={styles['status-details']}>
<p>上班打卡: 08:58 (准时)</p>
<p>下班打卡: 18:32 (准时)</p>
<p>工作时长: 9小时34分钟</p>
<p>本月累计出勤: 18</p>
</div>
</div>
</Card>
</Col>
{/* 操作按钮卡片 */}
<Col xs={24} md={8}>
<Card title="快捷操作" className={styles['action-card']} hoverable>
<div className={styles['action-buttons']}>
<Row gutter={[8, 20]}>
<Col span={12}>
<Button block icon={<SyncOutlined />}>重新同步</Button>
</Col>
<Col span={12}>
<Button block icon={<ExclamationCircleOutlined />}>异常提报</Button>
</Col>
<Col span={12}>
<Button block icon={<EditOutlined />}>修改记录</Button>
</Col>
<Col span={12}>
<Button block icon={<ExportOutlined />}>导出数据</Button>
</Col>
<Col span={24}>
<Button type="primary" block icon={<LineChartOutlined />}>
查看考勤分析
</Button>
</Col>
</Row>
</div>
</Card>
</Col>
</Row>
{/* 考勤数据表格 */}
<Card className={styles['table-section']}>
<Table
columns={this.getTableColumns()}
dataSource={this.getTableData()}
pagination={{
showSizeChanger: true,
showQuickJumper: true,
showTotal: (total, range) => `显示 ${range[0]}-${range[1]} 条,共 ${total} 条记录`,
}}
scroll={{ x: 800 }}
/>
</Card>
{/* 第二行卡片组 */}
<Row gutter={[16, 16]} className={styles['cards-row']}>
{/* 排班数据卡片 */}
<Col xs={24} md={8}>
<Card title="当前排班" className={styles['schedule-card']} hoverable>
<div className={styles['schedule-info']}>
<div className={styles['schedule-item']}>
<span>排班名称:</span>
<span>标准工作日</span>
</div>
<div className={styles['schedule-item']}>
<span>上班时间:</span>
<span>09:00</span>
</div>
<div className={styles['schedule-item']}>
<span>下班时间:</span>
<span>18:00</span>
</div>
<div className={styles['schedule-item']}>
<span>午休时间:</span>
<span>12:00-13:30</span>
</div>
<div className={styles['schedule-item']}>
<span>适用日期:</span>
<span>周一至周五</span>
</div>
<Button block icon={<CalendarOutlined />} className={styles['schedule-button']}>
查看排班历史
</Button>
</div>
</Card>
</Col>
{/* 考勤明细卡片 */}
<Col xs={24} md={8}>
<Card title="本月考勤统计" className={styles['statistics-card']} hoverable>
<div className={styles['statistics-info']}>
<div className={styles['stat-item']}>
<span>应出勤天数:</span>
<span>22</span>
</div>
<div className={styles['stat-item']}>
<span>实际出勤天数:</span>
<span>18</span>
</div>
<div className={styles['stat-item']}>
<span>迟到次数:</span>
<span>1</span>
</div>
<div className={styles['stat-item']}>
<span>早退次数:</span>
<span>0</span>
</div>
<div className={styles['stat-item']}>
<span>缺卡次数:</span>
<span>0</span>
</div>
<Button block icon={<PieChartOutlined />} className={styles['stat-button']}>
查看详细统计
</Button>
</div>
</Card>
</Col>
{/* 同步信息卡片 */}
<Col xs={24} md={8}>
<Card title="数据同步信息" className={styles['sync-card']} hoverable>
<div className={styles['sync-info']}>
<div className={styles['sync-item']}>
<span>最后同步时间:</span>
<span>2023-11-01 23:45:12</span>
</div>
<div className={styles['sync-item']}>
<span>同步状态:</span>
<span className={styles['success-text']}>成功</span>
</div>
<div className={styles['sync-item']}>
<span>最后计算时间:</span>
<span>2023-11-02 00:15:30</span>
</div>
<div className={styles['sync-item']}>
<span>计算状态:</span>
<span className={styles['success-text']}>成功</span>
</div>
<div className={styles['sync-buttons']}>
<Row gutter={8}>
<Col span={12}>
<Button block icon={<SyncOutlined />}>立即同步</Button>
</Col>
<Col span={12}>
<Button type="primary" block icon={<CalculatorOutlined />}>
重新计算
</Button>
</Col>
</Row>
</div>
</div>
</Card>
</Col>
</Row>
{/* 考勤分析图表 */}
<Card className={styles['chart-section']}>
<div className={styles['chart-header']}>
<h3>月度考勤分析</h3>
<div className={styles['chart-actions']}>
<Button icon={<CalendarOutlined />}>选择月份</Button>
<Button type="primary" icon={<DownloadOutlined />}>导出图表</Button>
</div>
</div>
<div id="attendanceChart" className={styles['chart-container']}></div>
</Card>
</div>
);
}
}
export default Attendancedetails;

@ -0,0 +1,438 @@
@import '~@/utils/utils.less';
/* 考勤详情页面样式 */
.attendance-details-page {
padding: 24px;
background-color: #f5f5f5;
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 2px 8px rgba(0, 0, 0, 0.1);
transition: all 0.3s ease;
&:hover {
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
transform: translateY(-2px);
}
}
}
/* 查询条件样式 */
.search-section {
margin-bottom: 16px;
flex-shrink: 0; /* 防止被压缩 */
.form-item {
margin-bottom: 16px;
.form-label {
display: block;
font-size: 14px;
font-weight: 500;
color: #262626;
margin-bottom: 8px;
}
}
}
/* 卡片行样式 */
.cards-row {
margin-bottom: 16px;
flex-shrink: 0; /* 防止被压缩 */
}
/* 员工信息卡片 */
.info-card {
height: 255px; /* 设置固定高度 */
.ant-card-body {
height: calc(100% - 57px);
display: flex;
flex-direction: column;
overflow: hidden; /* 防止内容溢出 */
}
.employee-info {
display: flex;
align-items: center;
margin-bottom: 16px;
flex-shrink: 0;
.employee-details {
margin-left: 16px;
h3 {
font-size: 18px;
font-weight: 600;
color: #262626;
margin: 0 0 4px 0;
}
p {
font-size: 14px;
color: #8c8c8c;
margin: 0;
}
}
}
.employee-meta {
flex: 1;
overflow-y: auto; /* 如果内容过多,添加滚动 */
.meta-item {
display: flex;
margin-bottom: 12px;
.meta-label {
font-size: 14px;
color: #8c8c8c;
width: 80px;
flex-shrink: 0;
}
span:last-child {
font-size: 14px;
color: #262626;
}
}
}
}
/* 考勤状态卡片 */
.status-card {
height: 255px; /* 设置固定高度 */
.ant-card-body {
height: calc(100% - 57px);
display: flex;
flex-direction: column;
overflow: hidden;
}
.status-info {
flex: 1;
display: flex;
flex-direction: column;
.status-badge {
display: flex;
align-items: center;
margin-bottom: 16px;
flex-shrink: 0;
.status-dot {
width: 12px;
height: 12px;
border-radius: 50%;
background-color: #52c41a;
margin-right: 8px;
}
span {
font-size: 14px;
font-weight: 500;
color: #262626;
}
}
.status-details {
flex: 1;
overflow-y: auto;
p {
font-size: 14px;
color: #595959;
margin: 0 0 12px 0;
line-height: 1.4;
}
}
}
}
/* 操作按钮卡片 */
.action-card {
height: 255px; /* 设置固定高度 */
.ant-card-body {
height: calc(100% - 57px);
display: flex;
flex-direction: column;
overflow: hidden;
}
.action-buttons {
flex: 1;
display: flex;
flex-direction: column;
justify-content: space-between;
.ant-row {
flex: 1;
display: flex;
flex-direction: column;
gap: 8px;
}
.ant-col:last-child {
margin-top: auto;
}
.ant-btn {
font-size: 12px;
height: 36px;
display: flex;
align-items: center;
justify-content: center;
}
}
}
/* 表格样式 */
.table-section {
margin-bottom: 16px;
flex-shrink: 0;
.ant-table-thead > tr > th {
background-color: #fafafa;
font-weight: 500;
}
.ant-table-tbody > tr:hover > td {
background-color: #f5f5f5;
}
/* 表格内部滚动 */
.ant-table-body {
max-height: 400px;
overflow-y: auto;
}
}
/* 排班数据卡片 */
.schedule-card {
height: 280px; /* 设置固定高度 */
.ant-card-body {
height: calc(100% - 57px);
display: flex;
flex-direction: column;
overflow: hidden;
}
.schedule-info {
flex: 1;
display: flex;
flex-direction: column;
.schedule-item {
display: flex;
justify-content: space-between;
margin-bottom: 9px;
font-size: 14px;
span:first-child {
color: #8c8c8c;
}
span:last-child {
font-weight: 500;
color: #262626;
}
}
.schedule-button {
margin-top: auto;
// padding-top: 16px;
// border-top: 1px solid #f0f0f0;
flex-shrink: 0;
}
}
}
/* 考勤统计卡片 */
.statistics-card {
height: 280px; /* 设置固定高度 */
.ant-card-body {
height: calc(100% - 57px);
display: flex;
flex-direction: column;
overflow: hidden;
}
.statistics-info {
flex: 1;
display: flex;
flex-direction: column;
.stat-item {
display: flex;
justify-content: space-between;
margin-bottom: 9px;
font-size: 14px;
span:first-child {
color: #8c8c8c;
}
span:last-child {
font-weight: 500;
color: #262626;
}
}
.stat-button {
margin-top: auto;
// padding-top: 16px;
// border-top: 1px solid #f0f0f0;
flex-shrink: 0;
}
}
}
/* 同步信息卡片 */
.sync-card {
height: 280px; /* 设置固定高度 */
.ant-card-body {
height: calc(100% - 57px);
display: flex;
flex-direction: column;
overflow: hidden;
}
.sync-info {
flex: 1;
display: flex;
flex-direction: column;
.sync-item {
display: flex;
justify-content: space-between;
margin-bottom: 12px;
font-size: 14px;
span:first-child {
color: #8c8c8c;
}
span:last-child {
font-weight: 500;
color: #262626;
}
}
.success-text {
color: #52c41a !important;
}
.sync-buttons {
margin-top: auto;
padding-top: 16px;
border-top: 1px solid #f0f0f0;
flex-shrink: 0;
}
}
}
/* 图表区域样式 */
.chart-section {
flex-shrink: 0;
.chart-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16px;
h3 {
font-size: 18px;
font-weight: 600;
color: #262626;
margin: 0;
}
.chart-actions {
display: flex;
gap: 8px;
}
}
.chart-container {
height: 320px;
width: 100%;
}
}
/* 响应式样式 */
@media (max-width: 768px) {
.attendance-details-page {
padding: 16px;
max-height: 90vh;
overflow-y: auto;
}
.info-card,
.status-card,
.action-card,
.schedule-card,
.statistics-card,
.sync-card {
height: auto; /* 移动端自适应高度 */
min-height: 200px;
}
.chart-header {
flex-direction: column;
align-items: stretch !important;
.chart-actions {
margin-top: 12px;
justify-content: center;
}
}
.employee-info {
flex-direction: column;
text-align: center;
.employee-details {
margin-left: 0 !important;
margin-top: 12px;
}
}
.chart-container {
height: 250px; /* 移动端降低图表高度 */
}
}
/* Firefox 滚动条样式 */
.attendance-details-page {
scrollbar-width: thin;
scrollbar-color: #c1c1c1 #f1f1f1;
}
Loading…
Cancel
Save