Ver Fonte

优化帮助文档菜单交互和样式,重构面包屑导航按钮逻辑

刘博博 há 4 dias atrás
pai
commit
94efce653f

+ 31 - 4
src/help/components/HelpLayout.tsx

@@ -24,9 +24,18 @@ const HelpLayout: React.FC = () => {
 
   const [md, setMd] = useState('');
   const [toc, setToc] = useState<TocItem[]>([]);
+  const [menuOpenKeys, setMenuOpenKeys] = useState<string[]>([]);
 
   const current = useMemo(() => findMenuByPath(pathname), [pathname]);
 
+  // 计算当前选中的菜单项 key
+  const selectedKey = useMemo(() => {
+    const fullPath = pathname + (location.hash || '');
+    // 解码 URL 以匹配菜单项的路径
+    const decodedPath = decodeURIComponent(fullPath);
+    return decodedPath;
+  }, [pathname, location.hash]);
+
   useEffect(() => {
     // 根据菜单项的 doc 加载 md
     const doc = current?.doc;
@@ -53,7 +62,7 @@ const HelpLayout: React.FC = () => {
   // build antd menu items
   const menuItems = useMemo(() => {
     const toAntd = (items: HelpMenuItem[]): any[] => items.map((it) => ({
-      key: it.path,
+      key: it.path, // 使用完整路径作为 key
       label: it.title,
       children: it.children ? toAntd(it.children) : undefined,
     }));
@@ -74,14 +83,31 @@ const HelpLayout: React.FC = () => {
     return keys;
   }, []);
 
+  // 初始化菜单展开状态
+  useEffect(() => {
+    const currentPathBase = pathname.split('#')[0];
+    const parentMenu = helpMenu.find(item => 
+      item.children && item.children.some(child => 
+        child.path.split('#')[0] === currentPathBase
+      )
+    );
+    
+    if (parentMenu) {
+      setMenuOpenKeys([parentMenu.path, ...defaultOpenKeys]);
+    } else {
+      setMenuOpenKeys(defaultOpenKeys);
+    }
+  }, [pathname, defaultOpenKeys]);
+
   return (
-    <Layout style={{ height: '100%', background: 'transparent' }}>
+    <Layout style={{ height: '100%', background: 'transparent' }} className="help-layout">
       <Sider width={240} style={{ background: '#fff', borderRight: '1px solid #f0f0f0' }}>
         <div style={{ padding: 12, fontWeight: 600 }}>帮助文档</div>
         <Menu
           mode="inline"
-          selectedKeys={[pathname + (location.hash || '')]}
-          defaultOpenKeys={defaultOpenKeys}
+          selectedKeys={[selectedKey]}
+          openKeys={menuOpenKeys}
+          onOpenChange={setMenuOpenKeys}
           items={menuItems}
           onClick={(e) => navigate(e.key)}
         />
@@ -110,3 +136,4 @@ const HelpLayout: React.FC = () => {
 };
 
 export default HelpLayout;
+

+ 17 - 14
src/pages/deepseek/questionAnswer/list/index.tsx

@@ -539,12 +539,15 @@ const QuestionAnswerList: React.FC = () => {
                                 </div>
                             ))
                         } */ }
-          <FormItem name='projectId'>
-            <Cascader
-              options={appProjectList}
-              placeholder="请选择项目"
-              showSearch
-            />
+       {appTypeList.find(item => item.value === selectedType)?.label === '项目级应用' && (
+            <FormItem name='projectId'>
+              <Cascader
+                options={appProjectList}
+                placeholder="请选择项目"
+                showSearch
+              />
+            </FormItem>
+          )}
             {/*<Select*/}
             {/*  placeholder='请选择项目'*/}
             {/*  allowClear*/}
@@ -564,19 +567,12 @@ const QuestionAnswerList: React.FC = () => {
             {/*    })*/}
             {/*  }*/}
             {/*</Select>*/}
-          </FormItem>
+          {/* </FormItem> */}
           {/*<FormItem name='keyword'>*/}
           {/*  <Input placeholder='请输入关键字' allowClear />*/}
           {/*</FormItem>*/}
           <FormItem>
             <Space size={12}>
-              <Tooltip title="重置">
-                <Button
-                  shape="circle"
-                  icon={<ReloadOutlined />}
-                  onClick={handleClickReset}
-                />
-              </Tooltip>
               <Tooltip title="查询">
                 <Button
                   type='primary'
@@ -585,6 +581,13 @@ const QuestionAnswerList: React.FC = () => {
                   icon={<SearchOutlined />}
                 />
               </Tooltip>
+              <Tooltip title="重置">
+                <Button
+                  shape="circle"
+                  icon={<ReloadOutlined />}
+                  onClick={handleClickReset}
+                />
+              </Tooltip>
             </Space>
           </FormItem>
 

+ 130 - 36
src/pages/layout/components/Breadcrumb.tsx

@@ -1,7 +1,7 @@
 import * as React from 'react';
 import { Link, useLocation } from 'react-router-dom';
-import { Breadcrumb as AntdBreadcrumb, Button } from 'antd';
-import { PlusOutlined } from '@ant-design/icons';
+import { Breadcrumb as AntdBreadcrumb, Button, Tooltip } from 'antd';
+import { PlusOutlined, TagOutlined, ReadOutlined } from '@ant-design/icons';
 import { State } from '../types';
 import router from '@/router';
 
@@ -10,50 +10,144 @@ interface Props {
     onKnowledgeLibCreate?: () => void; // 知识库创建回调
 };
 
+// 按钮配置类型
+interface ButtonConfig {
+    text: string;
+    icon: React.ReactNode;
+    tooltip: string;
+    onClick: () => void;
+    type?: 'default' | 'primary';
+    className?: string;
+}
+
+// 页面按钮配置
+interface PageButtonConfig {
+    pattern: string | RegExp;
+    matcher?: (path: string) => boolean;
+    buttons: ButtonConfig[];
+}
+
 const Breadcrumb: React.FC<Props> = (props: Props) => {
     const { routerMatchList, onKnowledgeLibCreate } = props;
     const location = useLocation();
 
+    // 创建帮助按钮的工厂函数
+    const createHelpButton = (helpPath: string, tooltipText: string): ButtonConfig => ({
+        text: '使用说明',
+        icon: <ReadOutlined />,
+        tooltip: tooltipText,
+        onClick: () => window.open(helpPath, '_blank'),
+        className: 'help-button'
+    });
+
+    // 创建主要操作按钮的工厂函数
+    const createPrimaryButton = (text: string, onClick: () => void): ButtonConfig => ({
+        text,
+        icon: <PlusOutlined />,
+        tooltip: '',
+        onClick,
+        type: 'primary'
+    });
+
+    // 页面按钮配置
+    const pageConfigs: PageButtonConfig[] = [
+        {
+            pattern: '/deepseek/questionAnswer',
+            matcher: (path) => path === '/deepseek/questionAnswer' || path === '/zhipu/questionAnswer',
+            buttons: [
+                createHelpButton('/help/questionAnswer/intro', '查看问答应用使用说明文档'),
+                createPrimaryButton('创建问答应用', () => {
+                    const basePath = location.pathname.startsWith('/deepseek') ? '/deepseek' : '/zhipu';
+                    router.navigate(`${basePath}/questionAnswer/create`);
+                })
+            ]
+        },
+        {
+            pattern: '/deepseek/knowledgeLib',
+            matcher: (path) => path === '/deepseek/knowledgeLib' || path === '/zhipu/knowledgeLib',
+            buttons: [
+                createHelpButton('/help/knowledge/intro', '查看知识库使用说明文档'),
+                createPrimaryButton('创建知识库', () => {
+                    const event = new CustomEvent('knowledgeLibCreate', {
+                        detail: { platform: location.pathname.startsWith('/deepseek') ? 'deepseek' : 'zhipu' }
+                    });
+                    window.dispatchEvent(event);
+                })
+            ]
+        },
+        {
+            pattern: /^\/(deepseek|zhipu)\/knowledgeLib\/[^/]+/,
+            matcher: (path) => !!path.match(/^\/(deepseek|zhipu)\/knowledgeLib\/[^/]+/) && !path.includes('/slice/'),
+            buttons: [
+                createHelpButton('/help/knowledge/intro#上传文档', '查看知识库上传文档使用说明')
+            ]
+        },
+        {
+            pattern: /^\/(deepseek|zhipu)\/knowledgeLib\/[^/]+\/[^/]+\/slice\/[^/]+\/[^/]+$/,
+            matcher: (path) => !!path.match(/^\/(deepseek|zhipu)\/knowledgeLib\/[^/]+\/[^/]+\/slice\/[^/]+\/[^/]+$/) && path.includes('/slice/'),
+            buttons: [
+                createHelpButton('/help/knowledge/intro#切片精修', '查看切片精修使用说明')
+            ]
+        },
+        {
+            pattern: /^\/(deepseek|zhipu)\/knowledgeLib\/[^/]+\/[^/]+\/slice\/[^/]+\/[^/]+\/[^/]+$/,
+            matcher: (path) => !!path.match(/^\/(deepseek|zhipu)\/knowledgeLib\/[^/]+\/[^/]+\/slice\/[^/]+\/[^/]+\/[^/]+$/),
+            buttons: [
+                createHelpButton('/help/knowledge/intro#切片精修-图片处理方法', '查看切片精修-图片处理方法使用说明')
+            ]
+        },
+        {
+            pattern: '/questionAnswer/create',
+            matcher: (path) => path === '/deepseek/questionAnswer/create' || path === '/zhipu/questionAnswer/create',
+            buttons: [
+                createHelpButton('/help/questionAnswer/intro#创建应用', '查看创建应用使用说明')
+            ]
+        },
+        {
+            pattern: '/questionAnswer/modify',
+            matcher: (path) => path === '/deepseek/questionAnswer/modify' || path === '/zhipu/questionAnswer/modify',
+            buttons: [
+                createHelpButton('/help/questionAnswer/intro#编辑修改应用', '查看编辑修改应用使用说明')
+            ]
+        }
+    ];
+
+    // 渲染按钮组件
+    const renderButton = (config: ButtonConfig, index: number) => {
+        const button = (
+            <Button
+                key={index}
+                type={config.type}
+                icon={config.icon}
+                className={config.className}
+                onClick={config.onClick}
+            >
+                {config.text}
+            </Button>
+        );
+
+        return config.tooltip ? (
+            <Tooltip key={index} title={config.tooltip}>
+                {button}
+            </Tooltip>
+        ) : button;
+    };
+
     // 根据当前路由生成动态按钮
     const getDynamicButton = () => {
         const path = location.pathname;
         
-        // 问答应用列表页面
-        if (path === '/deepseek/questionAnswer' || path === '/zhipu/questionAnswer') {
-            return (
-                <Button 
-                    type="primary" 
-                    icon={<PlusOutlined />}
-                    onClick={() => {
-                        const basePath = path.startsWith('/deepseek') ? '/deepseek' : '/zhipu';
-                        router.navigate(`${basePath}/questionAnswer/create`);
-                    }}
-                >
-                    创建问答应用
-                </Button>
-            );
-        }
-        
-        // 知识库列表页面
-        if (path === '/deepseek/knowledgeLib' || path === '/zhipu/knowledgeLib') {
-            return (
-                <Button 
-                    type="primary" 
-                    icon={<PlusOutlined />}
-                    onClick={() => {
-                        // 触发自定义事件,让页面组件监听并处理
-                        const event = new CustomEvent('knowledgeLibCreate', {
-                            detail: { platform: path.startsWith('/deepseek') ? 'deepseek' : 'zhipu' }
-                        });
-                        window.dispatchEvent(event);
-                    }}
-                >
-                    创建知识库
-                </Button>
-            );
+        for (const config of pageConfigs) {
+            if (config.matcher && config.matcher(path)) {
+                const buttons = config.buttons.map(renderButton);
+                return buttons.length > 1 ? (
+                    <div style={{ display: 'flex', gap: '8px' }}>
+                        {buttons}
+                    </div>
+                ) : buttons[0];
+            }
         }
         
-        // 其他页面不显示按钮
         return null;
     };
 

+ 192 - 12
src/pages/layout/style.less

@@ -242,43 +242,88 @@
         overflow-x: hidden;
         overflow-y: auto;
         border-right: none !important;
+        width: 100% !important; // 确保菜单占满容器宽度
 
         &-item {
             transition: all 0.2s ease;
-            margin: 4px 8px !important;
+            margin: 4px 6px !important; // 减少左右边距
             border-radius: 6px !important;
             height: 40px !important;
             line-height: 40px !important;
-
-            &:hover {
-                background-color: rgba(24, 144, 255, 0.06) !important;
+            position: relative;
+            // 默认状态:透明边框,保持布局稳定
+            border-left: 3px solid transparent !important;
+            padding-left: 13px !important;
+            padding-right: 8px !important; // 添加右内边距
+            // 确保菜单项不会超出容器宽度
+            box-sizing: border-box !important;
+            width: calc(100% - 12px) !important; // 减去左右边距的宽度
+            max-width: calc(100% - 12px) !important;
+
+            // 悬停状态 - 浅色背景,保持透明边框
+            &:hover:not(&-selected) {
+                background-color: rgba(24, 144, 255, 0.04) !important;
                 color: @primary-color !important;
+                // 移除位移效果,保持菜单宽度稳定
+                border-left: 3px solid transparent !important; // 保持透明边框,避免闪烁
+                padding-left: 13px !important; // 保持一致的内边距
+                padding-right: 8px !important; // 保持右内边距
             }
 
+            // 选中状态 - 深色背景,左侧蓝色边框,字体加粗
             &-selected {
                 font-weight: 600 !important;
                 color: @primary-color !important;
-                background-color: rgba(24, 144, 255, 0.1) !important;
+                background: linear-gradient(90deg, rgba(24, 144, 255, 0.12) 0%, rgba(24, 144, 255, 0.06) 100%) !important;
+                border-left: 3px solid #1890ff !important; // 使用您指定的颜色
+                padding-left: 13px !important; // 减少左内边距以补偿边框
+                padding-right: 8px !important; // 保持右内边距
+                box-shadow: 0 1px 3px rgba(24, 144, 255, 0.1);
                 
                 &::after {
                     display: none !important;
                 }
+
+                // 选中状态下的悬停效果 - 保持蓝条
+                &:hover {
+                    background: linear-gradient(90deg, rgba(24, 144, 255, 0.15) 0%, rgba(24, 144, 255, 0.08) 100%) !important;
+                    transform: none; // 选中状态不需要位移效果
+                    box-shadow: 0 2px 6px rgba(24, 144, 255, 0.15);
+                    border-left: 3px solid #1890ff !important; // 悬停时保持蓝条
+                    padding-left: 13px !important; // 保持左内边距
+                    padding-right: 8px !important; // 保持右内边距
+                }
             }
 
             &-active {
-                font-weight: 600 !important;
+                font-weight: 500 !important;
                 color: @primary-color !important;
                 background-color: rgba(24, 144, 255, 0.06) !important;
             }
         }
 
-        // 确保选中状态的图标也有正确的颜色
-        &-item-selected .anticon {
-            color: @primary-color !important;
-        }
+        // 图标颜色控制
+        &-item {
+            .anticon {
+                transition: all 0.2s ease;
+                font-size: 16px;
+            }
 
-        &-item-active .anticon {
-            color: @primary-color !important;
+            // 悬停时图标颜色
+            &:hover:not(&-selected) .anticon {
+                color: @primary-color !important;
+                transform: scale(1.05);
+            }
+
+            // 选中状态图标颜色和效果
+            &-selected .anticon {
+                color: @primary-color !important;
+                font-weight: bold;
+            }
+
+            &-active .anticon {
+                color: @primary-color !important;
+            }
         }
     }
 
@@ -337,6 +382,30 @@
         display: flex;
         align-items: center;
         gap: 8px;
+        
+        .help-button {
+            color: #606266;
+            background-color: #f5f7fa;
+            border: none;
+            border-radius: 6px;
+            font-weight: 500;
+            box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
+            transition: all 0.3s ease;
+            
+            &:hover {
+                background-color: #e9ecef;
+                color: #409eff;
+                box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
+            }
+            
+            &:active {
+                background-color: #d6d9dc;
+            }
+            
+            .anticon {
+                margin-right: 4px;
+            }
+        }
     }
 
     /* 内容区域滚动 */
@@ -423,4 +492,115 @@
     }
 }
 
+// 帮助文档菜单样式 - 与主菜单保持一致
+.help-layout {
+    .ant-layout-sider .ant-menu {
+        background-color: transparent !important;
+        border-right: none !important;
+        
+
+        // 子菜单样式
+        .ant-menu-submenu {
+            .ant-menu-submenu-title {
+                transition: all 0.2s ease !important;
+                margin: 4px 6px !important;
+                border-radius: 6px !important;
+                height: 40px !important;
+                line-height: 40px !important;
+                position: relative;
+                border-left: 3px solid transparent !important;
+                padding-left: 13px !important;
+                padding-right: 8px !important;
+                box-sizing: border-box !important;
+                width: calc(100% - 12px) !important;
+
+                &:hover {
+                    background-color: rgba(24, 144, 255, 0.04) !important;
+                    color: @primary-color !important;
+                    border-left: 3px solid transparent !important; // 悬停时无蓝条
+                }
+            }
+
+            // 子菜单内容区域
+            .ant-menu-sub {
+                background-color: transparent !important;
+            }
+        }
+
+        // 统一处理所有菜单项(包括子菜单项)
+        .ant-menu-item {
+            transition: all 0.2s ease !important;
+            margin: 4px 6px !important;
+            border-radius: 6px !important;
+            height: 40px !important;
+            line-height: 40px !important;
+            position: relative;
+            border-left: 3px solid transparent !important;
+            padding-left: 13px !important;
+            padding-right: 8px !important;
+            box-sizing: border-box !important;
+            width: calc(100% - 12px) !important;
+            max-width: calc(100% - 12px) !important;
+
+            // 子菜单项特殊样式
+            .ant-menu-sub & {
+                margin: 2px 6px 2px 20px !important; // 增加左边距以显示层级
+                height: 36px !important; // 稍微小一点
+                line-height: 36px !important;
+                font-size: 13px !important; // 稍微小一点的字体
+                width: calc(100% - 26px) !important; // 减去增加的左边距
+            }
+
+            // 悬停状态 - 无蓝条
+            &:hover:not(.ant-menu-item-selected) {
+                background-color: rgba(24, 144, 255, 0.04) !important;
+                color: @primary-color !important;
+                border-left: 3px solid transparent !important;
+                padding-left: 13px !important;
+                padding-right: 8px !important;
+            }
+
+            // 选中状态 - 有蓝条和选中效果
+            &.ant-menu-item-selected {
+                font-weight: 600 !important;
+                color: #1890ff !important;
+                background: linear-gradient(90deg, rgba(24, 144, 255, 0.12) 0%, rgba(24, 144, 255, 0.06) 100%) !important;
+                border-left: 3px solid #1890ff !important;
+                padding-left: 13px !important;
+                padding-right: 8px !important;
+                box-shadow: 0 1px 3px rgba(24, 144, 255, 0.1) !important;
+                
+                &::after {
+                    display: none !important;
+                }
+
+                &:hover {
+                    background: linear-gradient(90deg, rgba(24, 144, 255, 0.15) 0%, rgba(24, 144, 255, 0.08) 100%) !important;
+                    box-shadow: 0 2px 6px rgba(24, 144, 255, 0.15) !important;
+                    border-left: 3px solid #1890ff !important;
+                    padding-left: 13px !important;
+                    padding-right: 8px !important;
+                }
+            }
+        }
+
+        // 图标颜色控制
+        .ant-menu-item, .ant-menu-submenu-title {
+            .anticon {
+                transition: all 0.2s ease;
+                font-size: 16px;
+            }
+
+            &:hover:not(.ant-menu-item-selected) .anticon {
+                color: @primary-color !important;
+            }
+
+            &.ant-menu-item-selected .anticon {
+                color: @primary-color !important;
+                font-weight: bold;
+            }
+        }
+    }
+}
+