From d8c754d3dae877aa077c043eb71114531b4a78f7 Mon Sep 17 00:00:00 2001 From: yupeng Date: Fri, 19 Sep 2025 16:42:40 +0800 Subject: [PATCH 1/4] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E9=A1=B6=E9=83=A8?= =?UTF-8?q?=E9=9D=A2=E5=8C=85=E5=B1=91=E5=AF=BC=E8=88=AA=E7=BB=84=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/GlobalComponent/Breadcrumb.jsx | 233 ++++++++++++++++++ .../GlobalComponent/breadcrumb.less | 151 ++++++++++++ src/components/GlobalComponent/index.js | 11 +- .../nav_system_content/SystemContentList.js | 6 +- .../nav_system_content/SystemContentList.less | 10 +- 5 files changed, 406 insertions(+), 5 deletions(-) create mode 100644 src/components/GlobalComponent/Breadcrumb.jsx create mode 100644 src/components/GlobalComponent/breadcrumb.less diff --git a/src/components/GlobalComponent/Breadcrumb.jsx b/src/components/GlobalComponent/Breadcrumb.jsx new file mode 100644 index 0000000..229ab12 --- /dev/null +++ b/src/components/GlobalComponent/Breadcrumb.jsx @@ -0,0 +1,233 @@ +import React, { useState, useEffect } from 'react'; +import { Breadcrumb } from 'antd'; +import { CloseOutlined } from '@ant-design/icons'; +import { history, useLocation, matchRoutes } from '@umijs/max'; +import './breadcrumb.less'; +import routes from '../../../config/routes'; + +const CustomBreadcrumb = () => { + // 从sessionStorage初始化面包屑数据,与Vue实例保持一致 + const [breadcrumbList, setBreadcrumbList] = useState(() => { + return JSON.parse(sessionStorage.getItem('breadcrumb')) || []; + }); + + const [currentRouterName, setCurrentRouterName] = useState(''); + + const location = useLocation(); + + // 获取当前路由的真实信息 + const getCurrentRoute = () => { + const pathname = location.pathname || '/'; + const href = window.location.href; + + console.log('当前路径:', pathname); + + // 使用matchRoutes匹配当前路径与路由配置 + const matchedRoutes = matchRoutes(routes, pathname); + + console.log('匹配的路由:', matchedRoutes); + + // 如果有匹配的路由,获取最深层的匹配路由信息 + if (matchedRoutes && matchedRoutes.length > 0) { + const matchedRoute = matchedRoutes[matchedRoutes.length - 1]; // 获取最深层的匹配路由 + const routeConfig = matchedRoute.route; + + console.log('匹配的路由配置:', routeConfig); + + // 根据路由配置文件,直接使用routeConfig.name作为标题,这样可以确保名称正确显示 + const routeTitle = routeConfig.name || '未知页面'; + + return { + name: routeConfig.name || pathname.replace(/\//g, '_').slice(1) || 'home', + path: pathname, + href, + fullPath: href, + meta: { + title: routeTitle, + affix: pathname === '/' + } + }; + } + + // 手动处理特殊情况,确保即使matchRoutes不工作,也能显示正确的路由名称 + const pathSegments = pathname.split('/').filter(Boolean); + const lastSegment = pathSegments[pathSegments.length - 1] || 'home'; + + // 直接返回路径的最后一段作为名称 + return { + name: lastSegment, + path: pathname, + href, + fullPath: href, + meta: { + title: lastSegment === 'home' ? '首页' : lastSegment, + 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..acd336f --- /dev/null +++ b/src/components/GlobalComponent/breadcrumb.less @@ -0,0 +1,151 @@ +// 面包屑容器样式 +.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: 16px; + background-color: #fff; + 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: #ECF2FF; + border-color: #0056FF; + + .ant-breadcrumb-link { + 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-size: 14px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + flex: 1; +} + +// 关闭图标样式 +.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..79acc22 100644 --- a/src/pages/nav_system_content/SystemContentList.js +++ b/src/pages/nav_system_content/SystemContentList.js @@ -10,6 +10,7 @@ import { HomeOutlined, SettingOutlined, LogoutOutlined } from '@ant-design/icons import { getPageQuery } from '@/utils/utils' import menuTitle from '@/assets/img/memuTitle.png' import menuTitle1 from '@/assets/img/memuTitle1.png' +import { CustomBreadcrumb } from '@/components/GlobalComponent' const SystemContentList = (props) => { const dynamicRoute = window.dynamicRoute @@ -181,7 +182,10 @@ const SystemContentList = (props) => { setRouteActive(value)} /> */} - + + + + {userInfo?.user_name_cn ? userInfo.user_name_cn : (userInfo?.user_name || '')} diff --git a/src/pages/nav_system_content/SystemContentList.less b/src/pages/nav_system_content/SystemContentList.less index 3260f3d..5ee4c8a 100644 --- a/src/pages/nav_system_content/SystemContentList.less +++ b/src/pages/nav_system_content/SystemContentList.less @@ -34,6 +34,14 @@ } } + .tabBarLeft { + text-align: right; + display: flex; + // justify-content: flex-end;s + flex-wrap: nowrap; + align-items: center; + } + .tabBarRight { text-align: right; display: flex; @@ -111,4 +119,4 @@ // height: 100%; overflow: auto; } -} +} \ No newline at end of file From 648ead93b1712ceef7065ccc2c6570a4f7da4615 Mon Sep 17 00:00:00 2001 From: yupeng Date: Mon, 22 Sep 2025 10:52:10 +0800 Subject: [PATCH 2/4] =?UTF-8?q?=E9=A1=B6=E9=83=A8=E5=AF=BC=E8=88=AA?= =?UTF-8?q?=E6=A0=B7=E5=BC=8F=E8=B0=83=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/GlobalComponent/Breadcrumb.jsx | 83 +++++++++++++------ .../GlobalComponent/breadcrumb.less | 26 ++++-- 2 files changed, 78 insertions(+), 31 deletions(-) diff --git a/src/components/GlobalComponent/Breadcrumb.jsx b/src/components/GlobalComponent/Breadcrumb.jsx index 229ab12..711faac 100644 --- a/src/components/GlobalComponent/Breadcrumb.jsx +++ b/src/components/GlobalComponent/Breadcrumb.jsx @@ -1,9 +1,8 @@ import React, { useState, useEffect } from 'react'; import { Breadcrumb } from 'antd'; -import { CloseOutlined } from '@ant-design/icons'; -import { history, useLocation, matchRoutes } from '@umijs/max'; +import { CloseCircleOutlined } from '@ant-design/icons'; +import { history, useLocation } from '@umijs/max'; import './breadcrumb.less'; -import routes from '../../../config/routes'; const CustomBreadcrumb = () => { // 从sessionStorage初始化面包屑数据,与Vue实例保持一致 @@ -15,6 +14,24 @@ const CustomBreadcrumb = () => { 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 || '/'; @@ -22,23 +39,22 @@ const CustomBreadcrumb = () => { console.log('当前路径:', pathname); - // 使用matchRoutes匹配当前路径与路由配置 - const matchedRoutes = matchRoutes(routes, pathname); - - console.log('匹配的路由:', matchedRoutes); - - // 如果有匹配的路由,获取最深层的匹配路由信息 - if (matchedRoutes && matchedRoutes.length > 0) { - const matchedRoute = matchedRoutes[matchedRoutes.length - 1]; // 获取最深层的匹配路由 - const routeConfig = matchedRoute.route; + // 使用自定义函数在window.dynamicRoute中匹配当前路径 + let matchedRoute = null; + if (window.dynamicRoute && Array.isArray(window.dynamicRoute)) { + matchedRoute = findMatchedRoute(window.dynamicRoute, pathname); + } - console.log('匹配的路由配置:', routeConfig); + console.log('匹配的路由:', matchedRoute); - // 根据路由配置文件,直接使用routeConfig.name作为标题,这样可以确保名称正确显示 - const routeTitle = routeConfig.name || '未知页面'; + // 如果有匹配的路由,获取路由信息 + if (matchedRoute) { + // 使用route.label作为标题,route.key作为路径标识符 + const routeTitle = matchedRoute.label || '未知页面'; + const routeKey = matchedRoute.key || pathname; return { - name: routeConfig.name || pathname.replace(/\//g, '_').slice(1) || 'home', + name: routeKey.replace(/\//g, '_').slice(1) || 'home', path: pathname, href, fullPath: href, @@ -49,18 +65,37 @@ const CustomBreadcrumb = () => { }; } - // 手动处理特殊情况,确保即使matchRoutes不工作,也能显示正确的路由名称 - const pathSegments = pathname.split('/').filter(Boolean); - const lastSegment = pathSegments[pathSegments.length - 1] || 'home'; + // 如果没有从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: lastSegment, + name: lastPart.replace(/\//g, '_') || 'home', path: pathname, href, fullPath: href, meta: { - title: lastSegment === 'home' ? '首页' : lastSegment, + title: routeTitle, affix: pathname === '/' } }; @@ -214,8 +249,8 @@ const CustomBreadcrumb = () => { className={`breadcrumb-item ${item.name === currentRouterName ? 'breadcrumb-item-active' : ''}`} > - {item.title} - {item.title} + { e.stopPropagation(); diff --git a/src/components/GlobalComponent/breadcrumb.less b/src/components/GlobalComponent/breadcrumb.less index acd336f..8efef5b 100644 --- a/src/components/GlobalComponent/breadcrumb.less +++ b/src/components/GlobalComponent/breadcrumb.less @@ -47,9 +47,10 @@ .breadcrumb-item { height: 32px; padding: 0 12px; - border-radius: 16px; - background-color: #fff; - border: 1px solid #E8E8E8; + 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; @@ -91,8 +92,8 @@ // 面包屑项选中状态样式 .breadcrumb-item-active { - background-color: #ECF2FF; - border-color: #0056FF; + background-color: rgba(94, 121, 246, 0.13); + // border-color: #0056FF; .ant-breadcrumb-link { color: #0056FF !important; @@ -100,6 +101,12 @@ } } +// 面包屑文本选中状态样式 - 对应第252行代码中的条件样式 +.breadcrumb-item-text-active { + color: #0056FF !important; + font-weight: 500; +} + // 面包屑项内容容器 .breadcrumb-item-content { display: flex; @@ -112,17 +119,22 @@ // 面包屑项文本样式 .breadcrumb-item-text { - font-size: 14px; + 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; + // color: #7D9CD8; margin-left: 8px; display: flex; align-items: center; From 7bbfd82146b0ff6b630c139b1f046779d45123a4 Mon Sep 17 00:00:00 2001 From: yupeng Date: Mon, 22 Sep 2025 14:07:21 +0800 Subject: [PATCH 3/4] 1 --- src/assets/img/智能管控平台-1.svg | 40 +++++++++++++++++++ src/assets/img/智能管控平台.svg | 8 ++++ .../nav_system_content/SystemContentList.js | 13 +++--- src/utils/routeUtils.js | 6 ++- 4 files changed, 60 insertions(+), 7 deletions(-) create mode 100644 src/assets/img/智能管控平台-1.svg create mode 100644 src/assets/img/智能管控平台.svg 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/pages/nav_system_content/SystemContentList.js b/src/pages/nav_system_content/SystemContentList.js index 79acc22..110b17a 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,10 @@ 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 { CustomBreadcrumb } from '@/components/GlobalComponent' const SystemContentList = (props) => { @@ -24,6 +24,7 @@ const SystemContentList = (props) => { let defaultKey = '' useEffect(() => { + console.log('dynamicRoute structure:', dynamicRoute) if (!dynamicRoute || dynamicRoute?.length === 0) return let newList = [] let routes = [] @@ -104,8 +105,8 @@ const SystemContentList = (props) => {
- menuTitle - menuTitle1 + menuTitle + menuTitle1