值班日志记录

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: '时间',
dataIndex: 'time',
key: 'time',
width: 180,
}, },
{ {
title: '天气', title: '地点',
dataIndex: 'weather', dataIndex: 'location',
key: 'weather', key: 'location',
width: 80, width: 200,
}, },
{ {
title: '温度', title: '事件类型',
dataIndex: 'temperature', dataIndex: 'eventType',
key: 'temperature', key: 'eventType',
width: 80, width: 120,
}, },
{ {
title: '记录人', title: '事件描述',
dataIndex: 'recorder', dataIndex: 'description',
key: 'recorder', key: 'description',
width: 100, 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,79 +204,223 @@ const DutyLog = () => {
setPagination(pagination); setPagination(pagination);
}; };
return ( // 渲染状态标签
<div className={styles.container}> const renderStatusTag = (status) => {
{/* 页面标题 */} const statusConfig = {
<div className={styles.header}> '预警': { color: '#FF7A45', bg: '#FFF7E6' },
<div className={styles.titleBar}></div> '紧急': { color: '#F5222D', bg: '#FFF1F0' },
<h2 className={styles.title}>值班日志记录</h2> '常规': { 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> </div>
);
};
{/* 搜索和操作区域 */} return (
<div className={styles.searchBar}> <div className={styles.container}>
<div className={styles.searchLeft}> {/* A块顶部统计卡片 */}
<RangePicker <div className={styles.blockA}>
placeholder={['开始日期', '结束日期']} <div className={styles.statCard}>
style={{width: 240, height: 30, borderRadius: 2}} <div className={styles.statIcon}>
/> <div className={styles.cubeIcon}></div>
<Select </div>
placeholder="值班类型" <div className={styles.statContent}>
value={searchValue} <div className={styles.statNumber}>24</div>
onChange={setSearchValue} <div className={styles.statLabel}>今日日志总数</div>
style={{width: 120, height: 30, borderRadius: 2}} </div>
allowClear
>
<Option value="白班">白班</Option>
<Option value="夜班">夜班</Option>
</Select>
<Button
type="primary"
icon={<SearchOutlined />}
onClick={handleSearch}
loading={loading}
className={styles.customButton}
>
查询
</Button>
</div> </div>
<div className={styles.searchRight}> <div className={styles.statCard}>
<Button <div className={styles.statIcon}>
type="primary" <div className={styles.warningIcon}></div>
icon={<PlusOutlined />} </div>
onClick={handleAdd} <div className={styles.statContent}>
className={styles.customButton} <div className={styles.statNumber}>5</div>
> <div className={styles.statLabel}>紧急事件</div>
新增 </div>
</Button> </div>
<Button <div className={styles.statCard}>
danger <div className={styles.statIcon}>
style={{width: 70, height: 30, borderRadius: 2, display: 'flex', alignItems: 'center', justifyContent: 'center'}} <div className={styles.successIcon}></div>
icon={<img src={iconsc} alt="delete" style={{width: 14, height: 14, marginTop: -2}}/>} </div>
onClick={handleBatchDelete} <div className={styles.statContent}>
> <div className={styles.statNumber}>16</div>
删除 <div className={styles.statLabel}>已处理事件</div>
</Button> </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>
</div> </div>
{/* 数据表格 */} {/* B块主要内容区域 */}
<div className={styles.tableContainer}> <div className={styles.blockB}>
<StandardTable {/* 左块 */}
columns={columns} <div className={styles.blockBLeft}>
data={{ {/* 上部分:事件类型分布 */}
list: dataSource, <div className={styles.leftTop}>
pagination: pagination <div className={styles.sectionTitle}>事件类型分布</div>
}} <div className={styles.chartWrapper}>
rowKey="key" {renderPieChart()}
selectedRows={selectedRowKeys} </div>
onSelectRow={setSelectedRowKeys} </div>
onChange={handleTableChange} {/* 下部分:最近值班日志 */}
loading={loading} <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 className={styles.blockBRight}>
<div className={styles.sectionTitle}>值班日志列表</div>
{/* 搜索和操作区域 */}
<div className={styles.searchBar}>
<Input
placeholder="请输入关键字..."
prefix={<SearchOutlined />}
className={styles.searchInput}
style={{ borderRadius: '2px' }}
/>
<Select
placeholder="所有类型"
value={eventType}
onChange={setEventType}
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>
</Select>
<Select
placeholder="所有状态"
value={eventStatus}
onChange={setEventStatus}
allowClear
className={styles.filterSelect}
style={{ borderRadius: '2px' }}
>
<Option value="已解决">已解决</Option>
<Option value="待处理">待处理</Option>
<Option value="已处理">已处理</Option>
</Select>
<Button
type="primary"
icon={<PlusOutlined />}
onClick={handleAdd}
className={styles.addButton}
style={{ borderRadius: '2px' }}
>
新增日志
</Button>
</div>
{/* 数据表格 */}
<div className={styles.tableContainer}>
<StandardTable
columns={columns}
data={{
list: dataSource,
pagination: pagination
}}
rowKey="key"
selectedRows={selectedRowKeys}
onSelectRow={setSelectedRowKeys}
onChange={handleTableChange}
loading={loading}
/>
</div>
</div>
</div> </div>
</div> </div>
); );
}; };
export default DutyLog; export default DutyLog;

@ -1,127 +1,412 @@
.container { .container {
padding: 20px; padding: 20px;
background: #fff; background: #F5F5F5;
height: 100vh; height: 100vh;
}
.header {
display: flex; display: flex;
align-items: center; flex-direction: column;
margin-bottom: 15px; overflow: hidden;
.titleBar {
width: 3px;
height: 16px;
background: #2E4CD4;
margin-right: 12px;
}
.title {
margin: 0;
font-size: 14px;
font-weight: 500;
color: #333;
}
}
.searchBar { // A块顶部统计卡片区域
display: flex; .blockA {
justify-content: space-between; width: 100%;
align-items: center; height: 20%;
margin-bottom: 10px;
padding: 5px;
.searchLeft {
display: flex; display: flex;
align-items: center; gap: 16px;
gap: 12px; margin-bottom: 16px;
} background: linear-gradient(135deg, #E6F4FF 0%, #BAE0FF 100%);
border-radius: 2px;
.searchRight { padding: 16px;
display: flex;
align-items: center;
gap: 12px;
}
}
// 自定义按钮样式 .statCard {
.customButton { flex: 1;
background-color: #2E4CD4 !important; height: 100%;
border-color: #2E4CD4 !important; background: #FFFFFF;
border-radius: 2px !important; border-radius: 2px;
height: 30px !important; padding: 20px;
width: 75px; display: flex;
display: flex !important; align-items: center;
align-items: center !important; gap: 16px;
justify-content: center !important;
&:hover {
background-color: #1e3bb8 !important;
border-color: #1e3bb8 !important;
}
&:focus {
background-color: #2E4CD4 !important;
border-color: #2E4CD4 !important;
}
}
.tableContainer { .statIcon {
background: #fff; width: 48px;
border-radius: 0px; height: 48px;
overflow: hidden; display: flex;
align-items: center;
.actionButtons { justify-content: center;
display: flex;
gap: 8px; .cubeIcon {
font-size: 10px; width: 32px;
justify-content: center; height: 32px;
background: #1890FF;
.ant-btn-link { border-radius: 4px;
padding: 0; }
height: auto;
font-size: 10px; .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;
}
}
}
.statContent {
flex: 1;
display: flex;
flex-direction: column;
justify-content: center;
.statNumber {
font-size: 32px;
font-weight: 600;
color: #333333;
line-height: 1;
margin-bottom: 8px;
}
.statLabel {
font-size: 14px;
color: #666666;
}
}
} }
} }
}
// 表格样式优化 // B块主要内容区域
.tableContainer { .blockB {
:global { width: 100%;
.ant-table-thead > tr > th { flex: 1;
background: #F5F5FA; display: flex;
font-weight: 500; gap: 16px;
color: #333333; overflow: hidden;
font-size: 14px;
text-align: center; // 左块
} .blockBLeft {
width: 400px;
.ant-table-tbody > tr > td { display: flex;
color: #666666; flex-direction: column;
font-size: 13px; gap: 16px;
text-align: center;
} // 上部分:事件类型分布
.leftTop {
.ant-table-tbody > tr:hover > td { flex: 1;
background: #f5f5f5; background: #FFFFFF;
} border-radius: 2px;
padding: 16px;
.ant-pagination { display: flex;
margin-top: 10px; flex-direction: column;
text-align: right; overflow: hidden;
}
.sectionTitle {
.ant-btn.ant-btn-sm { font-size: 16px;
font-size: 13px !important; font-weight: 600;
height: 20px !important; color: #333333;
padding: 0px 4px !important; margin-bottom: 16px;
}
.chartWrapper {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
}
.pieChartContainer {
display: flex;
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;
.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;
}
}
}
}
}
// 下部分:最近值班日志
.leftBottom {
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;
}
.recentLogsList {
flex: 1;
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;
}
}
.logDescription {
font-size: 13px;
color: #666666;
line-height: 1.5;
margin-bottom: 8px;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
.logFooter {
display: flex;
justify-content: flex-end;
.statusTag {
padding: 2px 8px;
border-radius: 12px;
font-size: 12px;
border: 1px solid;
}
}
}
}
}
} }
.ant-btn-link.ant-btn-sm { // 右块:值班日志列表
font-size: 13px !important; .blockBRight {
height: auto !important; flex: 1;
padding: 0 !important; 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;
&:hover {
background: #40A9FF;
border-color: #40A9FF;
}
}
}
.tableContainer {
flex: 1;
overflow: hidden;
display: flex;
flex-direction: column;
: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 {
background: #F5F5FA;
font-weight: 500;
color: #333333;
font-size: 14px;
text-align: center;
border-radius: 0;
}
.ant-table-tbody > tr > td {
color: #666666;
font-size: 13px;
text-align: center;
}
.ant-table-tbody > tr:hover > td {
background: #F5F5F5;
}
.ant-pagination {
margin-top: 16px;
display: flex;
justify-content: flex-end;
align-items: center;
padding: 0;
}
}
.actionButtons {
display: flex;
gap: 8px;
justify-content: center;
align-items: center;
.ant-btn-link {
padding: 0;
height: auto;
font-size: 13px;
}
}
}
} }
} }
} }

Loading…
Cancel
Save