diff --git a/src/assets/img/book.svg b/src/assets/img/book.svg new file mode 100644 index 0000000..d1aef3b --- /dev/null +++ b/src/assets/img/book.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/img/danger.svg b/src/assets/img/danger.svg new file mode 100644 index 0000000..e87d4a6 --- /dev/null +++ b/src/assets/img/danger.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/assets/img/danger1.svg b/src/assets/img/danger1.svg new file mode 100644 index 0000000..9be66a6 --- /dev/null +++ b/src/assets/img/danger1.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/assets/img/fireHydrant.svg b/src/assets/img/fireHydrant.svg new file mode 100644 index 0000000..a15c04a --- /dev/null +++ b/src/assets/img/fireHydrant.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/assets/img/fireHydrant1.svg b/src/assets/img/fireHydrant1.svg new file mode 100644 index 0000000..0c7b497 --- /dev/null +++ b/src/assets/img/fireHydrant1.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/assets/img/license.svg b/src/assets/img/license.svg new file mode 100644 index 0000000..a6996e5 --- /dev/null +++ b/src/assets/img/license.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/img/people.svg b/src/assets/img/people.svg new file mode 100644 index 0000000..7e68013 --- /dev/null +++ b/src/assets/img/people.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/img/risk.svg b/src/assets/img/risk.svg new file mode 100644 index 0000000..d51b483 --- /dev/null +++ b/src/assets/img/risk.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/img/trouble.svg b/src/assets/img/trouble.svg new file mode 100644 index 0000000..94d7763 --- /dev/null +++ b/src/assets/img/trouble.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/img/智能管控平台-1.svg b/src/assets/img/智能管控平台-1.svg new file mode 100644 index 0000000..06f318c --- /dev/null +++ b/src/assets/img/智能管控平台-1.svg @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/assets/img/智能管控平台.svg b/src/assets/img/智能管控平台.svg new file mode 100644 index 0000000..e46fcc8 --- /dev/null +++ b/src/assets/img/智能管控平台.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/components/GlobalComponent/Breadcrumb.jsx b/src/components/GlobalComponent/Breadcrumb.jsx new file mode 100644 index 0000000..711faac --- /dev/null +++ b/src/components/GlobalComponent/Breadcrumb.jsx @@ -0,0 +1,268 @@ +import React, { useState, useEffect } from 'react'; +import { Breadcrumb } from 'antd'; +import { CloseCircleOutlined } from '@ant-design/icons'; +import { history, useLocation } from '@umijs/max'; +import './breadcrumb.less'; + +const CustomBreadcrumb = () => { + // 从sessionStorage初始化面包屑数据,与Vue实例保持一致 + const [breadcrumbList, setBreadcrumbList] = useState(() => { + return JSON.parse(sessionStorage.getItem('breadcrumb')) || []; + }); + + const [currentRouterName, setCurrentRouterName] = useState(''); + + const location = useLocation(); + + // 递归搜索window.dynamicRoute中匹配当前路径的路由项 + const findMatchedRoute = (routes, pathname) => { + for (const route of routes) { + // 检查当前路由项是否匹配 + if (route.key === pathname) { + return route; + } + // 如果有子路由,递归搜索 + if (route.children && route.children.length > 0) { + const matchedChild = findMatchedRoute(route.children, pathname); + if (matchedChild) { + return matchedChild; + } + } + } + return null; + }; + + // 获取当前路由的真实信息 + const getCurrentRoute = () => { + const pathname = location.pathname || '/'; + const href = window.location.href; + + console.log('当前路径:', pathname); + + // 使用自定义函数在window.dynamicRoute中匹配当前路径 + let matchedRoute = null; + if (window.dynamicRoute && Array.isArray(window.dynamicRoute)) { + matchedRoute = findMatchedRoute(window.dynamicRoute, pathname); + } + + console.log('匹配的路由:', matchedRoute); + + // 如果有匹配的路由,获取路由信息 + if (matchedRoute) { + // 使用route.label作为标题,route.key作为路径标识符 + const routeTitle = matchedRoute.label || '未知页面'; + const routeKey = matchedRoute.key || pathname; + + return { + name: routeKey.replace(/\//g, '_').slice(1) || 'home', + path: pathname, + href, + fullPath: href, + meta: { + title: routeTitle, + affix: pathname === '/' + } + }; + } + + // 如果没有从window.dynamicRoute中匹配到路由,使用原来的逻辑处理 + // 将路径按照/分割,获取最后一段作为名称 + const parts = pathname.split('/').filter(Boolean); + + // 根路径处理 + if (parts.length === 0) { + return { + name: 'home', + path: pathname, + href, + fullPath: href, + meta: { + title: '首页', + affix: true + } + }; + } + + // 获取最后一段作为页面名称 + const lastPart = parts[parts.length - 1]; + + // 使用最后一段作为标题 + const routeTitle = lastPart || '未知页面'; + + return { + name: lastPart.replace(/\//g, '_') || 'home', + path: pathname, + href, + fullPath: href, + meta: { + title: routeTitle, + affix: pathname === '/' + } + }; + }; + + // 生成面包屑数据 - 修复了path指向错误的问题 + const generateBreadcrumb = () => { + // 从当前路由获取信息 + const currentRoute = getCurrentRoute(); + const { meta: { title }, name, path } = currentRoute; + + // 如果title存在,将路由信息添加到breadcrumbList数组 + if (title) { + const newBreadcrumbList = [...breadcrumbList]; + newBreadcrumbList.push({ + name, + path: path, // 正确使用当前路由的path属性 + title + }); + + // 使用reduce方法对breadcrumbList进行去重处理,与Vue组件完全一致 + const uniqueList = newBreadcrumbList.reduce((accumulator, current) => { + const x = accumulator.find(item => item.name === current.name); + + if (!x) { + return accumulator.concat(current); + } else { + accumulator.forEach((val, i) => { + if (val.name == current.name) { + val.title = current.title; + val.path = current.path; + } + }); + return accumulator; + } + }, []); + + // 将处理后的面包屑数据存储到sessionStorage中 + setBreadcrumbList(uniqueList); + sessionStorage.setItem('breadcrumb', JSON.stringify(uniqueList)); + } + }; + + // 处理导航逻辑 + const toPath = (item) => { + const currentRoute = getCurrentRoute(); + // 如果路径相同,点击导航则不跳转 + if (item.name === currentRoute.name) { + return ''; + } + // 如果是固定路由,则不跳转 + if (item.meta?.affix) { + return ''; + } else { + // 跳转路由 + return item.path; + } + }; + + // 关闭面包屑项,先跳转再删除面包屑 + const cloneCurrentPage = (item, index) => { + // 如果没有面包屑页面或只剩一个页面,跳转到根目录 + if (!breadcrumbList || breadcrumbList.length <= 1) { + // 使用window.location.href进行有感刷新 + window.location.href = '/'; + return; + } + + const updatedList = [...breadcrumbList]; + updatedList.splice(index, 1); + + let length = updatedList.length; + // 先进行页面跳转 + if (currentRouterName === item.name && length > 0) { + // 使用history进行无刷新跳转 + history.push(updatedList[length - 1].path); + } + + // 然后删除面包屑项 + setBreadcrumbList(updatedList); + sessionStorage.setItem('breadcrumb', JSON.stringify(updatedList)); + }; + + // 处理面包屑项点击事件 + const handleItemClick = (item) => { + const path = toPath(item); + if (path) { + console.log(currentRouterName, 'yp') + // 使用history进行无刷新跳转 + history.push(path); + } + }; + + // 组件挂载时初始化 + useEffect(() => { + const currentRoute = getCurrentRoute(); + setCurrentRouterName(currentRoute.name); + generateBreadcrumb(); + }, []); + + // 监听路由变化,当路由变化时触发generateBreadcrumb方法 + useEffect(() => { + const handleRouteChange = () => { + const currentRoute = getCurrentRoute(); + setCurrentRouterName(currentRoute.name); + generateBreadcrumb(); + }; + + // 监听浏览器前进后退事件 + window.addEventListener('popstate', handleRouteChange); + + // 重写pushState和replaceState方法以捕获更多路由变化 + const originalPushState = window.history.pushState; + const originalReplaceState = window.history.replaceState; + + window.history.pushState = function (...args) { + originalPushState.apply(this, args); + handleRouteChange(); + }; + + window.history.replaceState = function (...args) { + originalReplaceState.apply(this, args); + handleRouteChange(); + }; + + // 监听hash变化(针对hash路由) + window.addEventListener('hashchange', handleRouteChange); + + return () => { + window.removeEventListener('popstate', handleRouteChange); + window.removeEventListener('hashchange', handleRouteChange); + // 还原原始方法 + window.history.pushState = originalPushState; + window.history.replaceState = originalReplaceState; + }; + }, [breadcrumbList, location]); + + // 当location.pathname变化时,自动更新currentRouterName + useEffect(() => { + const currentRoute = getCurrentRoute(); + setCurrentRouterName(currentRoute.name); + }, [location.pathname]); + + return ( +
+ + {breadcrumbList.map((item, index) => ( + handleItemClick(item)} + className={`breadcrumb-item ${item.name === currentRouterName ? 'breadcrumb-item-active' : ''}`} + > + + {item.title} + { + e.stopPropagation(); + cloneCurrentPage(item, index); + }} + /> + + + ))} + +
+ ); +}; + +export default CustomBreadcrumb; \ No newline at end of file diff --git a/src/components/GlobalComponent/breadcrumb.less b/src/components/GlobalComponent/breadcrumb.less new file mode 100644 index 0000000..8efef5b --- /dev/null +++ b/src/components/GlobalComponent/breadcrumb.less @@ -0,0 +1,163 @@ +// 面包屑容器样式 +.bread-crumb { + // display: flex; + align-items: center; + overflow-x: auto; + overflow-y: hidden; + height: auto; + max-width: 100%; + margin: 8px 0; + padding: 0 12px; + background-color: transparent; + flex-wrap: nowrap; + justify-content: flex-start; + box-sizing: border-box; +} + +// 面包屑容器滚动条样式 +.bread-crumb::-webkit-scrollbar { + height: 4px; +} + +.bread-crumb::-webkit-scrollbar-track { + background-color: #f1f1f1; + border-radius: 2px; +} + +.bread-crumb::-webkit-scrollbar-thumb { + background-color: #c1c1c1; + border-radius: 2px; +} + +.bread-crumb::-webkit-scrollbar-thumb:hover { + background-color: #a1a1a1; +} + +// 面包屑导航容器 +.ant-breadcrumb { + display: flex !important; + flex-wrap: nowrap !important; + align-items: center; + gap: 8px; + margin: 0; + padding: 0; +} + +// 面包屑项基础样式 +.breadcrumb-item { + height: 32px; + padding: 0 12px; + border-radius: 8px; + // 使用rgba格式设置带透明度的背景色,最后一个参数0.13表示13%的透明度 + background-color: rgba(186, 186, 186, 0.13); + // border: 1px solid #E8E8E8; + margin-right: 8px; + display: flex; + align-items: center; + transition: all 0.3s ease; + cursor: pointer; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + max-width: 180px; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.04); + box-sizing: border-box; + + // 移除默认的分隔符 + .ant-breadcrumb-separator { + display: none !important; + } + + // 链接样式 + .ant-breadcrumb-link { + display: flex; + align-items: center; + height: 100%; + color: #808080 !important; + background: transparent !important; + padding: 0; + margin: 0; + overflow: hidden; + text-overflow: ellipsis; + width: 100%; + } + + // 悬停效果 + &:hover { + border-color: #0056FF; + // transform: translateY(-1px); + box-shadow: 0 4px 8px rgba(0, 86, 255, 0.1); + } +} + +// 面包屑项选中状态样式 +.breadcrumb-item-active { + background-color: rgba(94, 121, 246, 0.13); + // border-color: #0056FF; + + .ant-breadcrumb-link { + color: #0056FF !important; + font-weight: 500; + } +} + +// 面包屑文本选中状态样式 - 对应第252行代码中的条件样式 +.breadcrumb-item-text-active { + color: #0056FF !important; + font-weight: 500; +} + +// 面包屑项内容容器 +.breadcrumb-item-content { + display: flex; + align-items: center; + height: 100%; + overflow: hidden; + text-overflow: ellipsis; + width: 100%; +} + +// 面包屑项文本样式 +.breadcrumb-item-text { + font-weight: 500; + font-size: 16px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + flex: 1; + + .breadcrumb-item-text-active { + color: #0056FF !important; + } +} + +// 关闭图标样式 +.breadcrumb-close-icon { + font-size: 14px; + // color: #7D9CD8; + margin-left: 8px; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + transition: color 0.3s ease; + flex-shrink: 0; + + &:hover { + color: #0056FF; + } +} + +// 确保所有面包屑链接样式一致 +.ant-breadcrumb-link { + color: #808080 !important; + transition: color 0.3s ease; +} + +.ant-breadcrumb-link:hover { + color: #0056FF !important; +} + +.ant-breadcrumb-item-active .ant-breadcrumb-link { + color: #0056FF !important; +} \ No newline at end of file diff --git a/src/components/GlobalComponent/index.js b/src/components/GlobalComponent/index.js index 3a637ab..ac2716b 100644 --- a/src/components/GlobalComponent/index.js +++ b/src/components/GlobalComponent/index.js @@ -1,5 +1,6 @@ -import { PureComponent } from 'react' -import * as plugins from 'antd' +import React, { PureComponent } from 'react'; +import * as plugins from 'antd'; +import CustomBreadcrumb from './Breadcrumb'; class GlobalComponent extends PureComponent { constructor(props) { @@ -69,7 +70,11 @@ class GlobalComponent extends PureComponent { this.Typography = plugins.Typography this.Upload = plugins.Upload this.message = plugins.message + // 自定义组件 + this.CustomBreadcrumb = CustomBreadcrumb; } } -export default GlobalComponent +export default GlobalComponent; +// 同时导出自定义面包屑组件,方便直接使用 +export { CustomBreadcrumb }; diff --git a/src/pages/nav_system_content/SystemContentList.js b/src/pages/nav_system_content/SystemContentList.js index db805a0..6a6e162 100644 --- a/src/pages/nav_system_content/SystemContentList.js +++ b/src/pages/nav_system_content/SystemContentList.js @@ -1,4 +1,4 @@ -import React, { useState, useEffect } from 'react'; +import React, { useState, useEffect } from 'react' import { history, Outlet, useLocation, matchRoutes, useModel } from '@umijs/max' import { Menu, Tabs, Select } from 'antd' import './SystemContentList.less' @@ -6,10 +6,53 @@ import { formatRoute, getDefaultRoute } from '@/utils/routeUtils' import styles from './TopNavBar.less' import { Row, Col, Avatar, Dropdown, Button } from 'antd' import { userInfo } from '@/utils/globalCommon' -import { HomeOutlined, SettingOutlined, LogoutOutlined } from '@ant-design/icons' +import { HomeOutlined, LogoutOutlined, AppstoreOutlined, UserOutlined, SettingOutlined, DatabaseOutlined, FileTextOutlined, LockOutlined, AreaChartOutlined } from '@ant-design/icons' import { getPageQuery } from '@/utils/utils' -import menuTitle from '@/assets/img/memuTitle.png' -import menuTitle1 from '@/assets/img/memuTitle1.png' +import menuTitle from '@/assets/img/智能管控平台.svg' +import menuTitle1 from '@/assets/img/智能管控平台-1.svg' +import fireHydrant from '@/assets/img/fireHydrant.svg' +import fireHydrant1 from '@/assets/img/fireHydrant1.svg' +import trouble from '@/assets/img/trouble.svg' +import book from '@/assets/img/book.svg' +import danger from '@/assets/img/danger.svg' +import danger1 from '@/assets/img/danger1.svg' +import license from '@/assets/img/license.svg' +import people from '@/assets/img/people.svg' +import risk from '@/assets/img/risk.svg' +import { CustomBreadcrumb } from '@/components/GlobalComponent' + +// 自定义菜单项渲染组件,支持根据激活状态显示不同图片 +const CustomMenuItem = ({ item, selectedKeys }) => { + const isActive = selectedKeys.includes(item.key); + + // 为每个菜单项定义激活状态的图片 + // 如果没有专门的激活状态图片,则使用默认图片 + const getActiveIcon = (baseIcon) => { + // 这里可以根据需要为每个图标定义对应的激活状态图片 + // 目前暂时使用相同的图片,用户可以根据实际情况替换 + return baseIcon; + }; + + const renderIcon = () => { + if (item.icon && typeof item.icon === 'object' && item.icon.props && item.icon.props.src) { + const iconSrc = isActive ? getActiveIcon(item.icon.props.src) : item.icon.props.src; + return {item.icon.props.alt + } + return item.icon; + }; + + const renderMenuItem = (menuItem) => ({ + ...menuItem, + icon: renderIcon(), + children: menuItem.children?.map(child => renderMenuItem(child)) + }); + + return renderMenuItem(item); +}; const SystemContentList = (props) => { const dynamicRoute = window.dynamicRoute @@ -23,6 +66,7 @@ const SystemContentList = (props) => { let defaultKey = '' useEffect(() => { + console.log('dynamicRoute structure:', dynamicRoute) if (!dynamicRoute || dynamicRoute?.length === 0) return let newList = [] let routes = [] @@ -39,6 +83,73 @@ const SystemContentList = (props) => { } setMenuItems(newList) + + const fixedMenuItems = [ + { + "path": "/topnavbar00/hrefficiency/timesheet", + icon: 工时仪表盘, + "key": "/topnavbar00/hrefficiency/timesheet", + "label": " 工时仪表盘" + }, + { + "path": "/topnavbar00/hrefficiency/staffsheet", + icon: 工时仪表盘, + "key": "/topnavbar00/hrefficiency/staffsheet", + "label": "员工仪表盘" + }, + { + "path": "/topnavbar00/hrefficiency/staffuph", + icon: 工时仪表盘, + "key": "/topnavbar00/hrefficiency/staffuph", + "label": "员工效率监控" + }, + { + "path": "/topnavbar00/hrefficiency/deptuph", + icon: 工时仪表盘, + "key": "/topnavbar00/hrefficiency/deptuph", + "label": "部门效率监控" + }, + { + "path": "/topnavbar00/hrefficiency/allstaffuph", + icon: 工时仪表盘, + "key": "/topnavbar00/hrefficiency/allstaffuph", + "label": "全员效率监控" + }, + { + "path": "/topnavbar00/hrefficiency/hiddentrouble", + icon: 工时仪表盘, + "key": "/topnavbar00/hrefficiency/hiddentrouble", + "label": "隐患排查" + }, + { + "path": "/topnavbar00/hrefficiency/system", + icon: 工时仪表盘, + "key": "/topnavbar00/hrefficiency/system", + "label": "系统管理", + "children": [ + { + "path": "/topnavbar00/hrefficiency/system/systemOrganization", + "key": "/topnavbar00/hrefficiency/system/systemOrganization", + "label": "组织管理" + }, + { + "path": "/topnavbar00/hrefficiency/system/systemRole", + "key": "/topnavbar00/hrefficiency/system/systemRole", + "label": "角色配置" + }, + { + "path": "/topnavbar00/hrefficiency/system/systemMenu", + "key": "/topnavbar00/hrefficiency/system/systemMenu", + "label": "菜单配置" + } + ] + } + ] + setMenuItems(fixedMenuItems) + // 初始化默认路由 + const defaultRoute = fixedMenuItems[0]?.key || '' + const mathRoute = matchRoutes(fixedMenuItems, pathName) + setRouteActive({ key: mathRoute?.length ? pathName : defaultRoute }, fixedMenuItems) }, [pathName]) const setRouteActive = (value, menu) => { @@ -103,17 +214,19 @@ const SystemContentList = (props) => {
- menuTitle - menuTitle1 + menuTitle + menuTitle1