值班日志记录

main
wangyunfei 2 months ago
parent 62c999b13c
commit d3d4a393c6

@ -1,122 +1,144 @@
import React, { useState } from 'react';
import { Input, Button, Select, message, Modal, DatePicker } from 'antd';
import { SearchOutlined, PlusOutlined, DeleteOutlined } from '@ant-design/icons';
import { Input, Button, Select, message, Modal, Tag } from 'antd';
import { SearchOutlined, PlusOutlined } from '@ant-design/icons';
import StandardTable from '@/components/StandardTable';
import styles from './DutyLog.less';
import iconsc from '@/assets/yjzygl/iconsc.svg';
const { Option } = Select;
const { RangePicker } = DatePicker;
const DutyLog = () => {
const [loading, setLoading] = useState(false);
const [selectedRowKeys, setSelectedRowKeys] = useState([]);
const [searchValue, setSearchValue] = useState('');
const [eventType, setEventType] = useState('');
const [eventStatus, setEventStatus] = useState('');
const [pagination, setPagination] = useState({
current: 1,
pageSize: 10,
total: 15,
showSizeChanger: true,
current: 3,
pageSize: 5,
total: 48,
showSizeChanger: false,
showQuickJumper: true,
showTotal: (total, range) => `${total}`,
showTotal: (total) => `${total}`,
});
// 模拟数据
const [dataSource, setDataSource] = useState([
{
key: '1',
number: '01',
logDate: '2025-10-28 08:00',
dutyPerson: '张三',
dutyType: '白班',
logContent: '正常巡检,未发现异常情况',
weather: '晴',
temperature: '18°C',
recorder: '张三',
status: '已提交',
time: '2025-10-19 15:35:27',
location: '义宾南区16号楼',
eventType: '交通事故',
description: '主干道发生交通事故,造成交通拥堵',
status: '已解决',
},
{
key: '2',
number: '02',
logDate: '2025-10-28 20:00',
dutyPerson: '李四',
dutyType: '夜班',
logContent: '夜间巡检,设备运行正常',
weather: '晴',
temperature: '12°C',
recorder: '李四',
status: '已提交',
time: '2025-10-15 13:44:41',
location: '中山南大街甲1号楼-1-5号附近',
eventType: '自然灾害',
description: '强风导致广告牌松动,存在安全隐患',
status: '待处理',
},
{
key: '3',
number: '03',
logDate: '2025-10-27 08:00',
dutyPerson: '王五',
dutyType: '白班',
logContent: '发现A区域照明设备故障已报修',
weather: '多云',
temperature: '16°C',
recorder: '王五',
status: '已处理',
time: '2025-10-15 00:29:03',
location: '西辛南区66号(卧龙环岛东南角)',
eventType: '社会安全',
description: '商场内发生顾客纠纷,可能引发冲突',
status: '已解决',
},
]);
// 表格列配置
const columns = [
{
title: '编号',
dataIndex: 'number',
key: 'number',
width: 80,
key: '4',
time: '2025-10-14 17:21:38',
location: '怡馨家园5号楼',
eventType: '自然灾害',
description: '强风导致广告牌松动,存在安全隐患',
status: '已解决',
},
{
title: '记录时间',
dataIndex: 'logDate',
key: 'logDate',
width: 150,
key: '5',
time: '2025-10-15 02:30:41',
location: '站前南街',
eventType: '社会安全',
description: '商场内发生顾客纠纷,可能引发冲突',
status: '已解决',
},
]);
// 最近值班日志数据
const recentLogs = [
{
title: '值班人员',
dataIndex: 'dutyPerson',
key: 'dutyPerson',
width: 120,
title: '设备故障报告',
time: '2023-04-17 14:45',
location: '3号机房',
description: '服务器温度异常升高,已启动备用设备..',
status: '预警',
},
{
title: '值班类型',
dataIndex: 'dutyType',
key: 'dutyType',
width: 100,
title: '网络中断处理',
time: '2023-04-17 14:45',
location: '3号机房',
description: '主办公楼网络中断,已联系维修人员处理..',
status: '紧急',
},
{
title: '日志内容',
dataIndex: 'logContent',
key: 'logContent',
width: 250,
ellipsis: true,
title: '日常巡检记录',
time: '2023-04-17 14:45',
location: '83号机房',
description: '完成日常安全巡检,所有设备运行正常...',
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: 'weather',
key: 'weather',
width: 80,
title: '时间',
dataIndex: 'time',
key: 'time',
width: 180,
},
{
title: '温度',
dataIndex: 'temperature',
key: 'temperature',
width: 80,
title: '地点',
dataIndex: 'location',
key: 'location',
width: 200,
},
{
title: '记录人',
dataIndex: 'recorder',
key: 'recorder',
width: 100,
title: '事件类型',
dataIndex: 'eventType',
key: 'eventType',
width: 120,
},
{
title: '事件描述',
dataIndex: 'description',
key: 'description',
ellipsis: true,
},
{
title: '状态',
dataIndex: 'status',
key: 'status',
width: 100,
render: (status) => {
const colorMap = {
'已解决': 'success',
'待处理': 'warning',
'已处理': 'processing',
};
return <Tag color={colorMap[status] || 'default'}>{status}</Tag>;
},
},
{
title: '操作',
@ -128,21 +150,16 @@ const DutyLog = () => {
type="link"
size="small"
onClick={() => handleView(record)}
style={{ color: '#1890FF', padding: 0 }}
>
查看
</Button>
<Button
type="link"
size="small"
onClick={() => handleEdit(record)}
>
修改
</Button>
<Button
type="link"
size="small"
danger
onClick={() => handleDelete(record)}
style={{ padding: 0 }}
>
删除
</Button>
@ -167,29 +184,7 @@ const DutyLog = () => {
// 查看处理
const handleView = (record) => {
message.info(`查看日志详情:${record.logContent}`);
};
// 批量删除处理
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(`编辑日志记录`);
message.info(`查看日志详情:${record.description}`);
};
// 删除处理
@ -209,61 +204,204 @@ const DutyLog = () => {
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 (
<div className={styles.container}>
{/* 页面标题 */}
<div className={styles.header}>
<div className={styles.titleBar}></div>
<h2 className={styles.title}>值班日志记录</h2>
{/* A块顶部统计卡片 */}
<div className={styles.blockA}>
<div className={styles.statCard}>
<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 className={styles.blockBRight}>
<div className={styles.sectionTitle}>值班日志列表</div>
{/* 搜索和操作区域 */}
<div className={styles.searchBar}>
<div className={styles.searchLeft}>
<RangePicker
placeholder={['开始日期', '结束日期']}
style={{width: 240, height: 30, borderRadius: 2}}
<Input
placeholder="请输入关键字..."
prefix={<SearchOutlined />}
className={styles.searchInput}
style={{ borderRadius: '2px' }}
/>
<Select
placeholder="值班类型"
value={searchValue}
onChange={setSearchValue}
style={{width: 120, height: 30, borderRadius: 2}}
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>
<Option value="火灾隐患">火灾隐患</Option>
<Option value="设施故障">设施故障</Option>
</Select>
<Button
type="primary"
icon={<SearchOutlined />}
onClick={handleSearch}
loading={loading}
className={styles.customButton}
<Select
placeholder="所有状态"
value={eventStatus}
onChange={setEventStatus}
allowClear
className={styles.filterSelect}
style={{ borderRadius: '2px' }}
>
查询
</Button>
</div>
<div className={styles.searchRight}>
<Option value="已解决">已解决</Option>
<Option value="待处理">待处理</Option>
<Option value="已处理">已处理</Option>
</Select>
<Button
type="primary"
icon={<PlusOutlined />}
onClick={handleAdd}
className={styles.customButton}
>
新增
</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}
className={styles.addButton}
style={{ borderRadius: '2px' }}
>
删除
新增日志
</Button>
</div>
</div>
{/* 数据表格 */}
<div className={styles.tableContainer}>
<StandardTable
@ -280,8 +418,9 @@ const DutyLog = () => {
/>
</div>
</div>
</div>
</div>
);
};
export default DutyLog;

@ -1,99 +1,378 @@
.container {
padding: 20px;
background: #fff;
background: #F5F5F5;
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;
align-items: center;
margin-bottom: 15px;
gap: 16px;
.statIcon {
width: 48px;
height: 48px;
display: flex;
align-items: center;
justify-content: center;
.titleBar {
width: 3px;
height: 16px;
background: #2E4CD4;
margin-right: 12px;
.cubeIcon {
width: 32px;
height: 32px;
background: #1890FF;
border-radius: 4px;
}
.title {
margin: 0;
.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;
font-weight: 500;
color: #333;
color: #666666;
}
}
}
}
}
.searchBar {
// B块主要内容区域
.blockB {
width: 100%;
flex: 1;
display: flex;
gap: 16px;
overflow: hidden;
// 左块
.blockBLeft {
width: 400px;
display: flex;
flex-direction: column;
gap: 16px;
// 上部分:事件类型分布
.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;
justify-content: space-between;
align-items: center;
margin-bottom: 10px;
padding: 5px;
justify-content: center;
}
.searchLeft {
.pieChartContainer {
display: flex;
align-items: center;
gap: 12px;
gap: 24px;
width: 100%;
.pieSvg {
width: 160px;
height: 160px;
flex-shrink: 0;
}
.searchRight {
.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;
}
}
// 自定义按钮样式
.customButton {
background-color: #2E4CD4 !important;
border-color: #2E4CD4 !important;
border-radius: 2px !important;
height: 30px !important;
width: 75px;
display: flex !important;
align-items: center !important;
justify-content: center !important;
.logInfo {
font-size: 12px;
color: #999999;
margin-bottom: 8px;
display: flex;
gap: 8px;
&:hover {
background-color: #1e3bb8 !important;
border-color: #1e3bb8 !important;
.logLocation {
margin-left: 8px;
}
}
&:focus {
background-color: #2E4CD4 !important;
border-color: #2E4CD4 !important;
.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;
}
}
}
}
}
}
}
.tableContainer {
background: #fff;
border-radius: 0px;
// 右块:值班日志列表
.blockBRight {
flex: 1;
background: #FFFFFF;
border-radius: 2px;
padding: 16px;
display: flex;
flex-direction: column;
overflow: hidden;
.actionButtons {
.sectionTitle {
font-size: 16px;
font-weight: 600;
color: #333333;
margin-bottom: 16px;
}
.searchBar {
display: flex;
gap: 8px;
font-size: 10px;
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;
.ant-btn-link {
padding: 0;
height: auto;
font-size: 10px;
&:hover {
background: #40A9FF;
border-color: #40A9FF;
}
}
}
}
// 表格样式优化
.tableContainer {
.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 {
@ -103,25 +382,31 @@
}
.ant-table-tbody > tr:hover > td {
background: #f5f5f5;
background: #F5F5F5;
}
.ant-pagination {
margin-top: 10px;
text-align: right;
margin-top: 16px;
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 {
font-size: 13px !important;
height: auto !important;
padding: 0 !important;
.actionButtons {
display: flex;
gap: 8px;
justify-content: center;
align-items: center;
.ant-btn-link {
padding: 0;
height: auto;
font-size: 13px;
}
}
}
}
}
}

Loading…
Cancel
Save