值班日志记录

main
wangyunfei 2 months ago
parent 62c999b13c
commit d3d4a393c6

@ -1,122 +1,144 @@
import React, { useState } from 'react'; import React, { useState } from 'react';
import { Input, Button, Select, message, Modal, DatePicker } from 'antd'; import { Input, Button, Select, message, Modal, Tag } from 'antd';
import { SearchOutlined, PlusOutlined, DeleteOutlined } from '@ant-design/icons'; import { SearchOutlined, PlusOutlined } from '@ant-design/icons';
import StandardTable from '@/components/StandardTable'; import StandardTable from '@/components/StandardTable';
import styles from './DutyLog.less'; import styles from './DutyLog.less';
import iconsc from '@/assets/yjzygl/iconsc.svg';
const { Option } = Select; const { Option } = Select;
const { RangePicker } = DatePicker;
const DutyLog = () => { const DutyLog = () => {
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [selectedRowKeys, setSelectedRowKeys] = useState([]); const [selectedRowKeys, setSelectedRowKeys] = useState([]);
const [searchValue, setSearchValue] = useState(''); const [searchValue, setSearchValue] = useState('');
const [eventType, setEventType] = useState('');
const [eventStatus, setEventStatus] = useState('');
const [pagination, setPagination] = useState({ const [pagination, setPagination] = useState({
current: 1, current: 3,
pageSize: 10, pageSize: 5,
total: 15, total: 48,
showSizeChanger: true, showSizeChanger: false,
showQuickJumper: true, showQuickJumper: true,
showTotal: (total, range) => `${total}`, showTotal: (total) => `${total}`,
}); });
// 模拟数据 // 模拟数据
const [dataSource, setDataSource] = useState([ const [dataSource, setDataSource] = useState([
{ {
key: '1', key: '1',
number: '01', time: '2025-10-19 15:35:27',
logDate: '2025-10-28 08:00', location: '义宾南区16号楼',
dutyPerson: '张三', eventType: '交通事故',
dutyType: '白班', description: '主干道发生交通事故,造成交通拥堵',
logContent: '正常巡检,未发现异常情况', status: '已解决',
weather: '晴',
temperature: '18°C',
recorder: '张三',
status: '已提交',
}, },
{ {
key: '2', key: '2',
number: '02', time: '2025-10-15 13:44:41',
logDate: '2025-10-28 20:00', location: '中山南大街甲1号楼-1-5号附近',
dutyPerson: '李四', eventType: '自然灾害',
dutyType: '夜班', description: '强风导致广告牌松动,存在安全隐患',
logContent: '夜间巡检,设备运行正常', status: '待处理',
weather: '晴',
temperature: '12°C',
recorder: '李四',
status: '已提交',
}, },
{ {
key: '3', key: '3',
number: '03', time: '2025-10-15 00:29:03',
logDate: '2025-10-27 08:00', location: '西辛南区66号(卧龙环岛东南角)',
dutyPerson: '王五', eventType: '社会安全',
dutyType: '白班', description: '商场内发生顾客纠纷,可能引发冲突',
logContent: '发现A区域照明设备故障已报修', status: '已解决',
weather: '多云',
temperature: '16°C',
recorder: '王五',
status: '已处理',
}, },
]);
// 表格列配置
const columns = [
{ {
title: '编号', key: '4',
dataIndex: 'number', time: '2025-10-14 17:21:38',
key: 'number', location: '怡馨家园5号楼',
width: 80, eventType: '自然灾害',
description: '强风导致广告牌松动,存在安全隐患',
status: '已解决',
}, },
{ {
title: '记录时间', key: '5',
dataIndex: 'logDate', time: '2025-10-15 02:30:41',
key: 'logDate', location: '站前南街',
width: 150, eventType: '社会安全',
description: '商场内发生顾客纠纷,可能引发冲突',
status: '已解决',
}, },
]);
// 最近值班日志数据
const recentLogs = [
{ {
title: '值班人员', title: '设备故障报告',
dataIndex: 'dutyPerson', time: '2023-04-17 14:45',
key: 'dutyPerson', location: '3号机房',
width: 120, description: '服务器温度异常升高,已启动备用设备..',
status: '预警',
}, },
{ {
title: '值班类型', title: '网络中断处理',
dataIndex: 'dutyType', time: '2023-04-17 14:45',
key: 'dutyType', location: '3号机房',
width: 100, description: '主办公楼网络中断,已联系维修人员处理..',
status: '紧急',
}, },
{ {
title: '日志内容', title: '日常巡检记录',
dataIndex: 'logContent', time: '2023-04-17 14:45',
key: 'logContent', location: '83号机房',
width: 250, description: '完成日常安全巡检,所有设备运行正常...',
ellipsis: true, status: '常规',
}, },
];
// 事件类型分布数据
const eventTypeData = [
{ name: '交通事故', value: 10, color: '#52C41A' },
{ name: '自然灾害', value: 15, color: '#FAAD14' },
{ name: '社会安全', value: 8, color: '#FF7A45' },
{ name: '电力故障', value: 5, color: '#F5222D' },
{ name: '火灾隐患', value: 3, color: '#CF1322' },
{ name: '设施故障', value: 7, color: '#1890FF' },
];
// 表格列配置
const columns = [
{ {
title: '天气', title: '时间',
dataIndex: 'weather', dataIndex: 'time',
key: 'weather', key: 'time',
width: 80, width: 180,
}, },
{ {
title: '温度', title: '地点',
dataIndex: 'temperature', dataIndex: 'location',
key: 'temperature', key: 'location',
width: 80, width: 200,
}, },
{ {
title: '记录人', title: '事件类型',
dataIndex: 'recorder', dataIndex: 'eventType',
key: 'recorder', key: 'eventType',
width: 100, width: 120,
},
{
title: '事件描述',
dataIndex: 'description',
key: 'description',
ellipsis: true,
}, },
{ {
title: '状态', title: '状态',
dataIndex: 'status', dataIndex: 'status',
key: 'status', key: 'status',
width: 100, width: 100,
render: (status) => {
const colorMap = {
'已解决': 'success',
'待处理': 'warning',
'已处理': 'processing',
};
return <Tag color={colorMap[status] || 'default'}>{status}</Tag>;
},
}, },
{ {
title: '操作', title: '操作',
@ -128,21 +150,16 @@ const DutyLog = () => {
type="link" type="link"
size="small" size="small"
onClick={() => handleView(record)} onClick={() => handleView(record)}
style={{ color: '#1890FF', padding: 0 }}
> >
查看 查看
</Button> </Button>
<Button
type="link"
size="small"
onClick={() => handleEdit(record)}
>
修改
</Button>
<Button <Button
type="link" type="link"
size="small" size="small"
danger danger
onClick={() => handleDelete(record)} onClick={() => handleDelete(record)}
style={{ padding: 0 }}
> >
删除 删除
</Button> </Button>
@ -167,29 +184,7 @@ const DutyLog = () => {
// 查看处理 // 查看处理
const handleView = (record) => { const handleView = (record) => {
message.info(`查看日志详情:${record.logContent}`); message.info(`查看日志详情:${record.description}`);
};
// 批量删除处理
const handleBatchDelete = () => {
if (selectedRowKeys.length === 0) {
message.warning('请选择要删除的数据');
return;
}
Modal.confirm({
title: '确认删除',
content: `确定要删除选中的 ${selectedRowKeys.length} 条数据吗?`,
onOk() {
setDataSource(dataSource.filter(item => !selectedRowKeys.includes(item.key)));
setSelectedRowKeys([]);
message.success('删除成功');
},
});
};
// 编辑处理
const handleEdit = (record) => {
message.info(`编辑日志记录`);
}; };
// 删除处理 // 删除处理
@ -209,61 +204,204 @@ const DutyLog = () => {
setPagination(pagination); setPagination(pagination);
}; };
// 渲染状态标签
const renderStatusTag = (status) => {
const statusConfig = {
'预警': { color: '#FF7A45', bg: '#FFF7E6' },
'紧急': { color: '#F5222D', bg: '#FFF1F0' },
'常规': { color: '#1890FF', bg: '#E6F7FF' },
};
const config = statusConfig[status] || { color: '#666', bg: '#F5F5F5' };
return (
<span
className={styles.statusTag}
style={{
color: config.color,
backgroundColor: config.bg,
borderColor: config.color
}}
>
{status}
</span>
);
};
// 渲染饼图(简化版)
const renderPieChart = () => {
const total = eventTypeData.reduce((sum, item) => sum + item.value, 0);
let currentAngle = 0;
return (
<div className={styles.pieChartContainer}>
<svg viewBox="0 0 200 200" className={styles.pieSvg}>
{eventTypeData.map((item, index) => {
const percentage = (item.value / total) * 100;
const angle = (percentage / 100) * 360;
const largeArc = percentage > 50 ? 1 : 0;
const x1 = 100 + 60 * Math.cos((currentAngle * Math.PI) / 180);
const y1 = 100 + 60 * Math.sin((currentAngle * Math.PI) / 180);
const x2 = 100 + 60 * Math.cos(((currentAngle + angle) * Math.PI) / 180);
const y2 = 100 + 60 * Math.sin(((currentAngle + angle) * Math.PI) / 180);
const path = [
`M 100 100`,
`L ${x1} ${y1}`,
`A 60 60 0 ${largeArc} 1 ${x2} ${y2}`,
`Z`,
].join(' ');
currentAngle += angle;
return (
<path
key={index}
d={path}
fill={item.color}
stroke="#fff"
strokeWidth="2"
/>
);
})}
<circle cx="100" cy="100" r="30" fill="#fff" />
</svg>
<div className={styles.pieLegend}>
{eventTypeData.map((item, index) => (
<div key={index} className={styles.legendItem}>
<span
className={styles.legendColor}
style={{ backgroundColor: item.color }}
/>
<span className={styles.legendText}>{item.name}</span>
</div>
))}
</div>
</div>
);
};
return ( return (
<div className={styles.container}> <div className={styles.container}>
{/* 页面标题 */} {/* A块顶部统计卡片 */}
<div className={styles.header}> <div className={styles.blockA}>
<div className={styles.titleBar}></div> <div className={styles.statCard}>
<h2 className={styles.title}>值班日志记录</h2> <div className={styles.statIcon}>
<div className={styles.cubeIcon}></div>
</div>
<div className={styles.statContent}>
<div className={styles.statNumber}>24</div>
<div className={styles.statLabel}>今日日志总数</div>
</div>
</div>
<div className={styles.statCard}>
<div className={styles.statIcon}>
<div className={styles.warningIcon}></div>
</div>
<div className={styles.statContent}>
<div className={styles.statNumber}>5</div>
<div className={styles.statLabel}>紧急事件</div>
</div>
</div>
<div className={styles.statCard}>
<div className={styles.statIcon}>
<div className={styles.successIcon}></div>
</div>
<div className={styles.statContent}>
<div className={styles.statNumber}>16</div>
<div className={styles.statLabel}>已处理事件</div>
</div>
</div>
<div className={styles.statCard}>
<div className={styles.statIcon}>
<div className={styles.clockIcon}></div>
</div>
<div className={styles.statContent}>
<div className={styles.statNumber}>5</div>
<div className={styles.statLabel}>待处理事件</div>
</div>
</div>
</div>
{/* B块主要内容区域 */}
<div className={styles.blockB}>
{/* 左块 */}
<div className={styles.blockBLeft}>
{/* 上部分:事件类型分布 */}
<div className={styles.leftTop}>
<div className={styles.sectionTitle}>事件类型分布</div>
<div className={styles.chartWrapper}>
{renderPieChart()}
</div>
</div>
{/* 下部分:最近值班日志 */}
<div className={styles.leftBottom}>
<div className={styles.sectionTitle}>最近值班日志</div>
<div className={styles.recentLogsList}>
{recentLogs.map((log, index) => (
<div key={index} className={styles.logCard}>
<div className={styles.logTitle}>{log.title}</div>
<div className={styles.logInfo}>
<span>{log.time}</span>
<span className={styles.logLocation}>{log.location}</span>
</div>
<div className={styles.logDescription}>{log.description}</div>
<div className={styles.logFooter}>
{renderStatusTag(log.status)}
</div>
</div>
))}
</div>
</div>
</div> </div>
{/* 右块:值班日志列表 */}
<div className={styles.blockBRight}>
<div className={styles.sectionTitle}>值班日志列表</div>
{/* 搜索和操作区域 */} {/* 搜索和操作区域 */}
<div className={styles.searchBar}> <div className={styles.searchBar}>
<div className={styles.searchLeft}> <Input
<RangePicker placeholder="请输入关键字..."
placeholder={['开始日期', '结束日期']} prefix={<SearchOutlined />}
style={{width: 240, height: 30, borderRadius: 2}} className={styles.searchInput}
style={{ borderRadius: '2px' }}
/> />
<Select <Select
placeholder="值班类型" placeholder="所有类型"
value={searchValue} value={eventType}
onChange={setSearchValue} onChange={setEventType}
style={{width: 120, height: 30, borderRadius: 2}}
allowClear allowClear
className={styles.filterSelect}
style={{ borderRadius: '2px' }}
> >
<Option value="白班">白班</Option> <Option value="交通事故">交通事故</Option>
<Option value="夜班">夜班</Option> <Option value="自然灾害">自然灾害</Option>
<Option value="社会安全">社会安全</Option>
<Option value="电力故障">电力故障</Option>
<Option value="火灾隐患">火灾隐患</Option>
<Option value="设施故障">设施故障</Option>
</Select> </Select>
<Button <Select
type="primary" placeholder="所有状态"
icon={<SearchOutlined />} value={eventStatus}
onClick={handleSearch} onChange={setEventStatus}
loading={loading} allowClear
className={styles.customButton} className={styles.filterSelect}
style={{ borderRadius: '2px' }}
> >
查询 <Option value="已解决">已解决</Option>
</Button> <Option value="待处理">待处理</Option>
</div> <Option value="已处理">已处理</Option>
<div className={styles.searchRight}> </Select>
<Button <Button
type="primary" type="primary"
icon={<PlusOutlined />} icon={<PlusOutlined />}
onClick={handleAdd} onClick={handleAdd}
className={styles.customButton} className={styles.addButton}
> style={{ borderRadius: '2px' }}
新增
</Button>
<Button
danger
style={{width: 70, height: 30, borderRadius: 2, display: 'flex', alignItems: 'center', justifyContent: 'center'}}
icon={<img src={iconsc} alt="delete" style={{width: 14, height: 14, marginTop: -2}}/>}
onClick={handleBatchDelete}
> >
删除 新增日志
</Button> </Button>
</div> </div>
</div>
{/* 数据表格 */} {/* 数据表格 */}
<div className={styles.tableContainer}> <div className={styles.tableContainer}>
<StandardTable <StandardTable
@ -280,8 +418,9 @@ const DutyLog = () => {
/> />
</div> </div>
</div> </div>
</div>
</div>
); );
}; };
export default DutyLog; export default DutyLog;

@ -1,99 +1,378 @@
.container { .container {
padding: 20px; padding: 20px;
background: #fff; background: #F5F5F5;
height: 100vh; height: 100vh;
} display: flex;
flex-direction: column;
overflow: hidden;
// A块顶部统计卡片区域
.blockA {
width: 100%;
height: 20%;
display: flex;
gap: 16px;
margin-bottom: 16px;
background: linear-gradient(135deg, #E6F4FF 0%, #BAE0FF 100%);
border-radius: 2px;
padding: 16px;
.header { .statCard {
flex: 1;
height: 100%;
background: #FFFFFF;
border-radius: 2px;
padding: 20px;
display: flex; display: flex;
align-items: center; align-items: center;
margin-bottom: 15px; gap: 16px;
.statIcon {
width: 48px;
height: 48px;
display: flex;
align-items: center;
justify-content: center;
.cubeIcon {
width: 32px;
height: 32px;
background: #1890FF;
border-radius: 4px;
}
.warningIcon {
width: 0;
height: 0;
border-left: 16px solid transparent;
border-right: 16px solid transparent;
border-bottom: 28px solid #1890FF;
position: relative;
&::after {
content: '!';
position: absolute;
top: 8px;
left: -6px;
color: #fff;
font-weight: bold;
font-size: 16px;
}
}
.successIcon {
width: 32px;
height: 32px;
background: #1890FF;
border-radius: 2px;
position: relative;
&::after {
content: '✓';
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
color: #fff;
font-weight: bold;
font-size: 18px;
}
}
.clockIcon {
width: 32px;
height: 32px;
background: #1890FF;
border-radius: 2px;
position: relative;
&::after {
content: 'L';
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
color: #fff;
font-weight: bold;
font-size: 18px;
}
}
}
.titleBar { .statContent {
width: 3px; flex: 1;
height: 16px; display: flex;
background: #2E4CD4; flex-direction: column;
margin-right: 12px; justify-content: center;
.statNumber {
font-size: 32px;
font-weight: 600;
color: #333333;
line-height: 1;
margin-bottom: 8px;
} }
.title { .statLabel {
margin: 0;
font-size: 14px; font-size: 14px;
font-weight: 500; color: #666666;
color: #333; }
}
} }
} }
.searchBar { // B块主要内容区域
.blockB {
width: 100%;
flex: 1;
display: flex; display: flex;
justify-content: space-between; gap: 16px;
align-items: center; overflow: hidden;
margin-bottom: 10px;
padding: 5px; // 左块
.blockBLeft {
width: 400px;
display: flex;
flex-direction: column;
gap: 16px;
.searchLeft { // 上部分:事件类型分布
.leftTop {
flex: 1;
background: #FFFFFF;
border-radius: 2px;
padding: 16px;
display: flex;
flex-direction: column;
overflow: hidden;
.sectionTitle {
font-size: 16px;
font-weight: 600;
color: #333333;
margin-bottom: 16px;
}
.chartWrapper {
flex: 1;
display: flex; display: flex;
align-items: center; align-items: center;
gap: 12px; justify-content: center;
} }
.searchRight { .pieChartContainer {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 24px;
width: 100%;
.pieSvg {
width: 160px;
height: 160px;
flex-shrink: 0;
}
.pieLegend {
flex: 1;
display: flex;
flex-direction: column;
gap: 12px; gap: 12px;
.legendItem {
display: flex;
align-items: center;
gap: 8px;
.legendColor {
width: 12px;
height: 12px;
border-radius: 2px;
flex-shrink: 0;
}
.legendText {
font-size: 14px;
color: #666666;
}
}
}
} }
} }
// 自定义按钮样式 // 下部分:最近值班日志
.customButton { .leftBottom {
background-color: #2E4CD4 !important; flex: 1;
border-color: #2E4CD4 !important; background: #FFFFFF;
border-radius: 2px !important; border-radius: 2px;
height: 30px !important; padding: 16px;
width: 75px; display: flex;
display: flex !important; flex-direction: column;
align-items: center !important; overflow: hidden;
justify-content: center !important;
&:hover { .sectionTitle {
background-color: #1e3bb8 !important; font-size: 16px;
border-color: #1e3bb8 !important; font-weight: 600;
color: #333333;
margin-bottom: 16px;
} }
&:focus { .recentLogsList {
background-color: #2E4CD4 !important; flex: 1;
border-color: #2E4CD4 !important; display: flex;
flex-direction: column;
gap: 12px;
overflow-y: auto;
.logCard {
background: #F5F5F5;
border-radius: 2px;
padding: 12px;
.logTitle {
font-size: 14px;
font-weight: 500;
color: #333333;
margin-bottom: 8px;
}
.logInfo {
font-size: 12px;
color: #999999;
margin-bottom: 8px;
display: flex;
gap: 8px;
.logLocation {
margin-left: 8px;
} }
} }
.tableContainer { .logDescription {
background: #fff; font-size: 13px;
border-radius: 0px; color: #666666;
line-height: 1.5;
margin-bottom: 8px;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden; overflow: hidden;
}
.actionButtons { .logFooter {
display: flex; display: flex;
gap: 8px; justify-content: flex-end;
font-size: 10px;
.statusTag {
padding: 2px 8px;
border-radius: 12px;
font-size: 12px;
border: 1px solid;
}
}
}
}
}
}
// 右块:值班日志列表
.blockBRight {
flex: 1;
background: #FFFFFF;
border-radius: 2px;
padding: 16px;
display: flex;
flex-direction: column;
overflow: hidden;
.sectionTitle {
font-size: 16px;
font-weight: 600;
color: #333333;
margin-bottom: 16px;
}
.searchBar {
display: flex;
gap: 12px;
margin-bottom: 16px;
align-items: center;
.searchInput {
flex: 1;
max-width: 300px;
}
.filterSelect {
width: 120px;
}
.addButton {
background: #1890FF;
border-color: #1890FF;
color: #FFFFFF;
border-radius: 2px;
height: 32px;
padding: 0 16px;
display: flex;
align-items: center;
justify-content: center; justify-content: center;
.ant-btn-link { &:hover {
padding: 0; background: #40A9FF;
height: auto; border-color: #40A9FF;
font-size: 10px;
} }
} }
} }
// 表格样式优化
.tableContainer { .tableContainer {
flex: 1;
overflow: hidden;
display: flex;
flex-direction: column;
:global { :global {
.ant-table-wrapper {
flex: 1;
display: flex;
flex-direction: column;
.ant-spin-nested-loading {
flex: 1;
display: flex;
flex-direction: column;
.ant-spin-container {
flex: 1;
display: flex;
flex-direction: column;
.ant-table {
flex: 1;
display: flex;
flex-direction: column;
.ant-table-container {
flex: 1;
display: flex;
flex-direction: column;
.ant-table-body {
flex: 1;
overflow-y: auto;
}
}
}
}
}
}
.ant-table-thead > tr > th { .ant-table-thead > tr > th {
background: #F5F5FA; background: #F5F5FA;
font-weight: 500; font-weight: 500;
color: #333333; color: #333333;
font-size: 14px; font-size: 14px;
text-align: center; text-align: center;
border-radius: 0;
} }
.ant-table-tbody > tr > td { .ant-table-tbody > tr > td {
@ -103,25 +382,31 @@
} }
.ant-table-tbody > tr:hover > td { .ant-table-tbody > tr:hover > td {
background: #f5f5f5; background: #F5F5F5;
} }
.ant-pagination { .ant-pagination {
margin-top: 10px; margin-top: 16px;
text-align: right; display: flex;
justify-content: flex-end;
align-items: center;
padding: 0;
} }
.ant-btn.ant-btn-sm {
font-size: 13px !important;
height: 20px !important;
padding: 0px 4px !important;
} }
.ant-btn-link.ant-btn-sm { .actionButtons {
font-size: 13px !important; display: flex;
height: auto !important; gap: 8px;
padding: 0 !important; justify-content: center;
align-items: center;
.ant-btn-link {
padding: 0;
height: auto;
font-size: 13px;
}
}
}
} }
} }
} }

Loading…
Cancel
Save