diff --git a/src/pages/business_environmental_activities/components/ActivityCalendar.js b/src/pages/business_environmental_activities/components/ActivityCalendar.js index f6101f0..05a2046 100644 --- a/src/pages/business_environmental_activities/components/ActivityCalendar.js +++ b/src/pages/business_environmental_activities/components/ActivityCalendar.js @@ -1,10 +1,200 @@ -import React from 'react'; +import React, { useMemo, useState } from 'react'; +import { Button, Segmented, Select } from 'antd'; +import { PlusOutlined, GlobalOutlined, BranchesOutlined } from '@ant-design/icons'; +import dayjs from 'dayjs'; import styles from './ActivityCalendar.less'; +const weekDays = ['周日', '周一', '周二', '周三', '周四', '周五', '周六']; + +const calendarEvents = [ + { + id: 'earth-day', + title: '世界环保日', + start: dayjs('2020-12-14'), + end: dayjs('2020-12-15'), + tone: 'sand', + icon: , + }, + { + id: 'tree-day', + title: '全国植树日', + start: dayjs('2020-12-24'), + end: dayjs('2020-12-31'), + tone: 'mint', + icon: , + }, +]; + +const buildGrid = (referenceDate, viewMode) => { + if (viewMode === 'week') { + const startOfWeek = referenceDate.startOf('week'); + const days = Array.from({ length: 7 }, (_, index) => startOfWeek.add(index, 'day')); + return { days, gridStart: startOfWeek, weeks: 1 }; + } + + const startOfMonth = referenceDate.startOf('month'); + const gridStart = startOfMonth.startOf('week'); + const days = Array.from({ length: 42 }, (_, index) => gridStart.add(index, 'day')); + + return { days, gridStart, weeks: 6 }; +}; + +const buildEventSegments = (events, gridStart, weeks) => { + const gridEnd = gridStart.add(weeks * 7 - 1, 'day').endOf('day'); + const segments = []; + + events.forEach((event) => { + const start = event.start; + const end = event.end; + + if (end.isBefore(gridStart) || start.isAfter(gridEnd)) { + return; + } + + for (let weekIndex = 0; weekIndex < weeks; weekIndex += 1) { + const weekStart = gridStart.add(weekIndex * 7, 'day'); + const weekEnd = weekStart.add(6, 'day').endOf('day'); + + const segmentStart = start.isAfter(weekStart) ? start : weekStart; + const segmentEnd = end.isBefore(weekEnd) ? end : weekEnd; + + if (segmentEnd.isBefore(weekStart) || segmentStart.isAfter(weekEnd)) { + continue; + } + + const columnStart = segmentStart.diff(weekStart, 'day') + 1; + const columnEnd = segmentEnd.diff(weekStart, 'day') + 2; + + segments.push({ + id: `${event.id}-${weekIndex}`, + title: event.title, + tone: event.tone, + icon: event.icon, + rowStart: weekIndex + 1, + rowEnd: weekIndex + 2, + columnStart, + columnEnd, + }); + } + }); + + return segments; +}; + const ActivityCalendar = () => { + const [currentDate, setCurrentDate] = useState(dayjs('2020-12-01')); + const [viewMode, setViewMode] = useState('month'); + + const { days, gridStart, weeks } = useMemo(() => buildGrid(currentDate, viewMode), [currentDate, viewMode]); + const segments = useMemo(() => buildEventSegments(calendarEvents, gridStart, weeks), [gridStart, weeks]); + + const yearOptions = useMemo(() => { + const baseYear = dayjs().year(); + const startYear = Math.min(2020, baseYear - 4); + return Array.from({ length: 10 }, (_, index) => startYear + index); + }, []); + + const handleYearChange = (year) => { + setCurrentDate((prev) => prev.year(year)); + }; + + const handleMonthChange = (month) => { + setCurrentDate((prev) => prev.month(month - 1)); + }; + + const handleViewChange = (mode) => { + setViewMode(mode); + }; + + const handleBackToToday = () => { + const today = dayjs(); + setCurrentDate(today); + setViewMode('month'); + }; + return ( -
- 活动日历 待开发 +
+
+ +
+ ({ + value: index + 1, + label: `${index + 1}月`, + }))} + /> + + +
+
+ +
+
+ {weekDays.map((day) => ( +
+ {day} +
+ ))} +
+ +
+ {days.map((day) => { + const isCurrentMonth = day.month() === currentDate.month(); + return ( +
+ {day.date()} +
+ ); + })} + + {segments.map((segment) => ( +
+
+ + {segment.title} +
+
{segment.icon}
+
+ ))} +
+
); }; diff --git a/src/pages/business_environmental_activities/components/ActivityCalendar.less b/src/pages/business_environmental_activities/components/ActivityCalendar.less index 95c7170..0299013 100644 --- a/src/pages/business_environmental_activities/components/ActivityCalendar.less +++ b/src/pages/business_environmental_activities/components/ActivityCalendar.less @@ -1,8 +1,215 @@ -.placeholder { +.container { background: #fff; - border-radius: 4px; - padding: 24px; - min-height: 72vh; - font-size: 16px; - color: #666; + border-radius: 10px; + padding: 16px 20px 20px; + display: flex; + flex-direction: column; + height: 100%; + box-shadow: 0 6px 20px rgba(0, 0, 0, 0.03); + box-sizing: border-box; +} + +.toolbar { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 14px; +} + +.createButton { + background: linear-gradient(98deg, #12d49c 0%, #0ec9c9 100%); + border: none; + border-radius: 8px; + padding: 6px 16px; + height: 34px; + box-shadow: 0 6px 14px rgba(18, 212, 156, 0.25); + + &:hover, + &:focus { + background: linear-gradient(98deg, #0fcf94 0%, #0cbcbc 100%); + } +} + +.toolbarRight { + display: flex; + align-items: center; + gap: 10px; +} + +.select { + width: 88px; + + :global(.ant-select-selector) { + border-radius: 6px; + border-color: #e8e8e8; + box-shadow: none !important; + height: 34px; + } +} + +.dropdown { + :global(.ant-select-item-option-selected) { + background: #e8fff2 !important; + } +} + +.segmented { + :global(.ant-segmented-group) { + gap: 6px; + } + + :global(.ant-segmented-item) { + padding: 4px 12px; + min-width: 38px; + height: 34px; + align-items: center; + justify-content: center; + } + + :global(.ant-segmented-item-selected) { + background: #0fcfaf; + color: #fff; + } +} + +.dayButton { + width: 38px; + height: 34px; + border-radius: 6px; + border: 1px solid #e8e8e8; + color: #999; + background: #fff; + + &:hover, + &:focus { + border-color: #0fcfaf; + color: #0fcfaf; + } +} + +.calendarWrapper { + flex: 1; + display: flex; + flex-direction: column; + background: #fff; + border-radius: 8px; + overflow: hidden; + border: 1px solid #f2f2f2; +} + +.weekHeader { + display: grid; + grid-template-columns: repeat(7, 1fr); + background: #f9fbfc; + color: #9fa6ad; + font-size: 12px; + border-bottom: 1px solid #f2f2f2; +} + +.weekCell { + text-align: center; + padding: 8px 0; + border-right: 1px solid #f2f2f2; + + &:last-child { + border-right: none; + } +} + +.calendarGrid { + flex: 1; + display: grid; + grid-template-columns: repeat(7, 1fr); + grid-auto-rows: minmax(110px, 1fr); + position: relative; + background: #fff; + border-left: 1px solid #f2f2f2; + border-bottom: 1px solid #f2f2f2; + overflow: hidden; +} + +.dayCell { + border-right: 1px solid #f2f2f2; + border-top: 1px solid #f2f2f2; + padding: 10px; + position: relative; + box-sizing: border-box; +} + +.dayNumber { + position: absolute; + top: 8px; + right: 10px; + color: #9fa6ad; + font-size: 12px; +} + +.dimmed { + opacity: 0.45; +} + +.eventBlock { + position: relative; + margin: 34px 6px 10px; + border-radius: 8px; + padding: 10px 12px; + display: flex; + align-items: center; + justify-content: space-between; + font-size: 13px; + box-shadow: 0 6px 14px rgba(0, 0, 0, 0.06); + pointer-events: none; + border: 1px solid transparent; + box-sizing: border-box; + z-index: 1; +} + +.eventInfo { + display: flex; + align-items: center; + gap: 10px; +} + +.eventDot { + width: 8px; + height: 8px; + border-radius: 50%; + background: #2bd1ab; + flex-shrink: 0; +} + +.eventTitle { + color: #6f6f6f; +} + +.eventIcon { + font-size: 22px; +} + +.sand { + background: #fff7e6; + border-color: #ffe7c2; + color: #d48806; + + .eventDot { + background: #f5a623; + } + + .eventIcon { + color: #f5a623; + } +} + +.mint { + background: #e8fff2; + border-color: #c5f4da; + color: #12a77c; + + .eventDot { + background: #12d49c; + } + + .eventIcon { + color: #12a77c; + } } diff --git a/src/pages/business_environmental_activities/components/ActivityManagement.js b/src/pages/business_environmental_activities/components/ActivityManagement.js index e81a9e3..cb8b8ac 100644 --- a/src/pages/business_environmental_activities/components/ActivityManagement.js +++ b/src/pages/business_environmental_activities/components/ActivityManagement.js @@ -1,10 +1,162 @@ -import React from 'react'; +import React, { useMemo, useState } from 'react'; +import { Button, Checkbox, Select } from 'antd'; +import { + PlusOutlined, + FilePdfOutlined, + FileWordOutlined, + PictureOutlined, +} from '@ant-design/icons'; import styles from './ActivityManagement.less'; +const statusMeta = { + ongoing: { label: '进行中', ribbon: '#20c997' }, + published: { label: '已发布', ribbon: '#20c997' }, + finished: { label: '已结束', ribbon: '#fa8c16' }, + archived: { label: '已归档', ribbon: '#999999' }, +}; + +const sampleFiles = [ + { type: 'pdf', name: '总结.pdf', icon: }, + { type: 'word', name: '方案.docx', icon: }, + { type: 'img', name: '活动图片', icon: }, + { type: 'img', name: '活动图片', icon: }, + { type: 'img', name: '活动图片', icon: }, +]; + +const activities = [ + { + id: 'a1', + title: '2024-01环境应急演练', + participants: 98, + duration: 'xxxxxxxx', + owner: 'xxxxxxxx', + attachments: sampleFiles, + attachmentCount: 15, + status: 'published', + creator: '张三', + }, + { + id: 'a2', + title: '2024-01环境应急演练', + participants: 98, + duration: 'xxxxxxxx', + owner: 'xxxxxxxx', + attachments: sampleFiles, + attachmentCount: 15, + status: 'finished', + creator: '李四', + }, + { + id: 'a3', + title: '2024-01环境应急演练', + participants: 98, + duration: 'xxxxxxxx', + owner: 'xxxxxxxx', + attachments: sampleFiles, + attachmentCount: 15, + status: 'archived', + creator: '张三', + }, + { + id: 'a4', + title: '2024-01环境应急演练', + participants: 98, + duration: 'xxxxxxxx', + owner: 'xxxxxxxx', + attachments: sampleFiles, + attachmentCount: 15, + status: 'published', + creator: '李四', + }, +]; + const ActivityManagement = () => { + const [selectedStatuses, setSelectedStatuses] = useState(Object.keys(statusMeta)); + const [creator, setCreator] = useState('all'); + + const creators = useMemo(() => ['全部', '张三', '李四'], []); + + const filteredActivities = useMemo( + () => activities.filter((item) => selectedStatuses.includes(item.status) + && (creator === 'all' || item.creator === creator)), + [selectedStatuses, creator] + ); + + const handleStatusChange = (checkedValues) => { + setSelectedStatuses(checkedValues); + }; + + const statusOptions = useMemo( + () => Object.keys(statusMeta).map((key) => ({ label: statusMeta[key].label, value: key })), + [] + ); + return ( -
- 活动管理 待开发 +
+
+ +
+
+ 活动状态 + +
+
+ 创建人 + ({ value: item, label: item }))} + /> +
+
+
+ +
+ {filteredEvents.map((item) => { + const meta = statusMeta[item.status]; + return ( +
+
+ {meta.label} +
+
+ {item.code} +
+
+
事件地点 {item.reporter} 报警时间 2025-02-05
+
报警人 {item.assignTo}
+
报警方式 {item.response}
+
事件描述 {item.description}
+
+
+ + +
+
+ ); + })} +
); }; diff --git a/src/pages/business_environmental_activities/components/EventReport.less b/src/pages/business_environmental_activities/components/EventReport.less index 95c7170..93ee3a8 100644 --- a/src/pages/business_environmental_activities/components/EventReport.less +++ b/src/pages/business_environmental_activities/components/EventReport.less @@ -1,8 +1,192 @@ -.placeholder { + +.container { + background: #f6f8fb; + border-radius: 10px; + padding: 14px 14px 18px; + box-sizing: border-box; + display: flex; + flex-direction: column; + gap: 12px; + min-height: 100%; +} + +.statsRow { + display: grid; + grid-template-columns: repeat(5, 1fr); + gap: 12px; +} + +.statCard { + background: #fff; + border-radius: 10px; + padding: 12px 16px; + box-shadow: 0 8px 20px rgba(0, 0, 0, 0.05); + display: flex; + flex-direction: column; + gap: 6px; +} + +.statTitle { + color: #6c7681; + font-size: 12px; +} + +.statValue { + font-size: 28px; + font-weight: 700; + color: #222; +} + +.statTime { + color: #9fa6ad; + font-size: 12px; +} + +.filterRow { background: #fff; - border-radius: 4px; - padding: 24px; - min-height: 72vh; - font-size: 16px; + border-radius: 10px; + padding: 10px 12px; + display: flex; + align-items: center; + gap: 14px; + box-shadow: 0 8px 18px rgba(0, 0, 0, 0.04); +} + +.reportButton { + background: linear-gradient(98deg, #12d49c 0%, #0ec9c9 100%); + border: none; + border-radius: 8px; + padding: 0 16px; + height: 34px; + box-shadow: 0 6px 14px rgba(18, 212, 156, 0.25); +} + +.filters { + flex: 1; + display: flex; + align-items: center; + flex-wrap: wrap; + gap: 10px 14px; +} + +.filterTitle { color: #666; + font-size: 13px; + margin-right: 6px; +} + +.statusGroup { + :global(.ant-checkbox-wrapper) { + margin-right: 12px; + font-size: 13px; + color: #555; + } + + :global(.ant-checkbox-inner) { + border-radius: 4px; + } +} + +.rightFilters { + margin-left: auto; + display: flex; + align-items: center; + gap: 8px; +} + +.filterLabel { + color: #888; + font-size: 13px; +} + +.select { + width: 120px; + + :global(.ant-select-selector) { + border-radius: 6px !important; + height: 34px; + box-shadow: none !important; + } +} + +.grid { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 12px; +} + +.card { + position: relative; + background: #fff; + border-radius: 10px; + border: 1px solid #eef1f6; + box-shadow: 0 8px 18px rgba(0, 0, 0, 0.04); + overflow: hidden; + min-height: 170px; + display: flex; + flex-direction: column; +} + +.ribbon { + position: absolute; + top: 0; + right: 0; + color: #fff; + font-size: 12px; + padding: 8px 14px; + transform: translate(26px, -6px) rotate(45deg); + width: 92px; + text-align: center; + box-shadow: 0 6px 12px rgba(0, 0, 0, 0.12); +} + +.cardHeader { + padding: 14px 16px 6px; +} + +.cardTitle { + font-size: 14px; + font-weight: 600; + color: #333; +} + +.cardBody { + padding: 0 16px 10px; + display: flex; + flex-direction: column; + gap: 6px; + color: #555; + font-size: 12px; +} + +.row { + display: flex; + flex-wrap: wrap; + gap: 6px; +} + +.emphasis { + color: #12c48b; + font-weight: 600; +} + +.muted { + color: #888; +} + +.description { + color: #555; +} + +.cardFooter { + margin-top: auto; + border-top: 1px solid #f0f2f5; + padding: 8px 12px; + display: flex; + justify-content: space-between; + align-items: center; +} + +.secondaryAction { + color: #3e7aff; } diff --git a/src/pages/business_environmental_activities/components/StatisticsAnalysis.js b/src/pages/business_environmental_activities/components/StatisticsAnalysis.js index 19a39bd..fd72fca 100644 --- a/src/pages/business_environmental_activities/components/StatisticsAnalysis.js +++ b/src/pages/business_environmental_activities/components/StatisticsAnalysis.js @@ -1,10 +1,175 @@ -import React from 'react'; +import React, { useMemo } from 'react'; +import { Button, Checkbox, Select } from 'antd'; +import ReactECharts from 'echarts-for-react'; import styles from './StatisticsAnalysis.less'; +const statusOptions = [ + { label: '环保活动', value: 'env' }, + { label: '环保事件', value: 'event' }, + { label: '环保事件', value: 'incident' }, +]; + const StatisticsAnalysis = () => { + const activityTypeOption = useMemo( + () => ({ + color: ['#6abdfc', '#a585ff', '#fa8c16', '#ff6b6b'], + tooltip: { trigger: 'item' }, + legend: { orient: 'vertical', left: '70%' }, + series: [ + { + type: 'pie', + radius: ['30%', '60%'], + center: ['35%', '50%'], + label: { formatter: '{b}: {d}%' }, + data: [ + { value: 30, name: '培训' }, + { value: 15, name: '演练' }, + { value: 10, name: '其他' }, + { value: 45, name: '宣传' }, + ], + }, + ], + }), + [] + ); + + const participationOption = useMemo( + () => ({ + color: ['#6ee7d8', '#8fd0ff'], + tooltip: { trigger: 'axis' }, + grid: { left: 80, right: 30, top: 30, bottom: 30 }, + xAxis: { + type: 'value', + boundaryGap: [0, 0.1], + axisLine: { lineStyle: { color: '#d8dfe6' } }, + }, + yAxis: { + type: 'category', + data: ['xxx部门', 'xxx部门', 'xxx部门', 'xxx部门', 'xxx部门', 'xxx部门'], + axisLine: { lineStyle: { color: '#d8dfe6' } }, + }, + series: [ + { + name: '参与率', + type: 'bar', + barWidth: 16, + data: [92, 88, 74, 56, 48, 30], + label: { show: false }, + }, + ], + }), + [] + ); + + const incidentTypeOption = useMemo( + () => ({ + color: ['#4b7bff', '#5fd6f1', '#2fd192', '#ff9f7f', '#e85d8b', '#5e6ce6'], + tooltip: { trigger: 'item' }, + legend: { orient: 'vertical', right: 10, top: 'middle' }, + series: [ + { + type: 'pie', + radius: ['40%', '65%'], + center: ['40%', '50%'], + label: { formatter: '{b} {d}%' }, + data: [ + { value: 45, name: 'xxx2' }, + { value: 10, name: 'xxx3' }, + { value: 9, name: 'xxx5' }, + { value: 13, name: 'xxx4' }, + { value: 15, name: 'xxx3' }, + { value: 8, name: 'xxx6' }, + ], + }, + ], + }), + [] + ); + + const trendOption = useMemo( + () => ({ + color: ['#6abdfc', '#f7a35c'], + tooltip: { trigger: 'axis' }, + grid: { left: 50, right: 50, top: 30, bottom: 40 }, + xAxis: [{ type: 'category', data: ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月'] }], + yAxis: [ + { type: 'value', name: '活动数', axisLine: { lineStyle: { color: '#d8dfe6' } } }, + { type: 'value', name: '参与率', axisLine: { lineStyle: { color: '#d8dfe6' } } }, + ], + series: [ + { + name: '活动数', + type: 'bar', + data: [8, 18, 12, 10, 13, 9, 11, 10, 12, 16, 9, 14], + barWidth: 14, + }, + { + name: '参与率', + type: 'line', + yAxisIndex: 1, + data: [55, 60, 58, 63, 60, 62, 65, 63, 64, 68, 66, 70], + smooth: true, + }, + ], + }), + [] + ); + + const departmentOption = useMemo( + () => ({ + color: ['#6abdfc', '#63e2b7'], + tooltip: { trigger: 'axis' }, + legend: { top: 0 }, + grid: { left: 50, right: 20, top: 40, bottom: 30 }, + xAxis: [{ type: 'category', data: ['生产部', '运营部', '安全部', '后勤部', '质检部', '供应部'] }], + yAxis: [{ type: 'value' }], + series: [ + { name: '事件总数', type: 'bar', barWidth: 12, data: [35, 22, 28, 18, 20, 25] }, + { name: '处置完成', type: 'bar', barWidth: 12, data: [28, 18, 20, 14, 16, 19] }, + ], + }), + [] + ); + return ( -
- 统计分析 待开发 +
+
+ +
+ 事件状态 + item.value)} /> + 统计周期 + +
+
+ +
+
+
活动类型分布
+ +
+
+
活动参与热度
+ +
+
+
事件类型分布
+ +
+
+ +
+
+
月度活动趋势
+ +
+
+
事件部门分布
+ +
+
); }; diff --git a/src/pages/business_environmental_activities/components/StatisticsAnalysis.less b/src/pages/business_environmental_activities/components/StatisticsAnalysis.less index 95c7170..9774599 100644 --- a/src/pages/business_environmental_activities/components/StatisticsAnalysis.less +++ b/src/pages/business_environmental_activities/components/StatisticsAnalysis.less @@ -1,8 +1,72 @@ -.placeholder { +.container { + background: #f6f8fb; + border-radius: 10px; + padding: 14px 14px 18px; + box-sizing: border-box; + display: flex; + flex-direction: column; + gap: 12px; + min-height: 100%; +} + +.toolbar { + display: flex; + align-items: center; + justify-content: space-between; background: #fff; - border-radius: 4px; - padding: 24px; - min-height: 72vh; - font-size: 16px; + border-radius: 10px; + padding: 10px 12px; + box-shadow: 0 8px 18px rgba(0, 0, 0, 0.04); +} + +.toolbarFilters { + display: flex; + align-items: center; + gap: 10px; + flex-wrap: wrap; + justify-content: flex-end; +} + +.filterLabel { color: #666; + font-size: 13px; +} + +.select { + width: 120px; + + :global(.ant-select-selector) { + border-radius: 6px !important; + height: 34px; + box-shadow: none !important; + } +} + +.rowThree { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 12px; +} + +.rowTwo { + display: grid; + grid-template-columns: 2fr 1fr; + gap: 12px; +} + +.card, +.cardWide { + background: #fff; + border-radius: 10px; + box-shadow: 0 8px 18px rgba(0, 0, 0, 0.04); + padding: 10px 12px 6px; + display: flex; + flex-direction: column; +} + +.cardTitle { + font-size: 14px; + font-weight: 600; + color: #333; + margin-bottom: 4px; }