周玉环 2 hete
szülő
commit
54bd22c450

+ 1 - 1
src/http/index.ts

@@ -150,7 +150,7 @@ const transform: AxiosTransform = {
     config.headers["X-Tenant-ID"] = String(tenantId);
 
     // 如果需要 token,这里可以添加
-    const token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6IuWGheWuueS4reW_g-euoeeQhuWRmCIsImV4cCI6MTc2NDk2MzkwN30.m9qVPagPzMpSIdeaYdw_yWmno64A2ntLnFDi7GUB3Lg';
+    const token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6IuWGheWuueS4reW_g-euoeeQhuWRmCIsImV4cCI6MTc2NDk2NzgwNn0.mF7iqqQ5sGsHbb5aPSAKyMcVmidPkbWGQoXLYEbdnSI';
     if (token && options.requestOptions?.withToken !== false) {
       config.headers.Authorization = token;
       config.headers['X-Access-Token'] = token;

+ 11 - 57
src/index.css

@@ -1,69 +1,23 @@
-:root {
-  font-family: system-ui, Avenir, Helvetica, Arial, sans-serif;
-  line-height: 1.5;
-  font-weight: 400;
-
-  color-scheme: light dark;
-  color: rgba(255, 255, 255, 0.87);
-  background-color: #242424;
-
-  font-synthesis: none;
-  text-rendering: optimizeLegibility;
-  -webkit-font-smoothing: antialiased;
-  -moz-osx-font-smoothing: grayscale;
+* {
+  box-sizing: border-box;
 }
 
-a {
-  font-weight: 500;
-  color: #646cff;
-  text-decoration: inherit;
-}
-a:hover {
-  color: #535bf2;
+html {
+  /* 始终显示滚动条,防止页面抖动 */
+  overflow-y: scroll;
 }
 
 body {
   margin: 0;
   min-width: 320px;
   min-height: 100vh;
-  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
-    'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
+  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen",
+    "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
     sans-serif;
+  -webkit-font-smoothing: antialiased;
+  -moz-osx-font-smoothing: grayscale;
 }
 
-h1 {
-  font-size: 3.2em;
-  line-height: 1.1;
-}
-
-button {
-  border-radius: 8px;
-  border: 1px solid transparent;
-  padding: 0.6em 1.2em;
-  font-size: 1em;
-  font-weight: 500;
-  font-family: inherit;
-  background-color: #1a1a1a;
-  cursor: pointer;
-  transition: border-color 0.25s;
-}
-button:hover {
-  border-color: #646cff;
-}
-button:focus,
-button:focus-visible {
-  outline: 4px auto -webkit-focus-ring-color;
-}
-
-@media (prefers-color-scheme: light) {
-  :root {
-    color: #213547;
-    background-color: #ffffff;
-  }
-  a:hover {
-    color: #747bff;
-  }
-  button {
-    background-color: #f9f9f9;
-  }
+#root {
+  min-height: 100vh;
 }

+ 87 - 16
src/layouts/Header/index.module.css

@@ -2,9 +2,9 @@
   display: flex;
   justify-content: space-between;
   align-items: center;
-  padding: 0 16px;
-  height: 48px;
-  border-bottom: 1px solid #e5e8eb;
+  padding: 16px;
+  /* height: 48px; */
+  /* border-bottom: 1px solid #e5e8eb; */
   background-color: #f7f9fa;
 }
 
@@ -43,37 +43,45 @@
   height: 100%;
 }
 
+.menuCardWrapper {
+  position: relative;
+  height: 100%;
+  display: flex;
+  align-items: center;
+}
+
 .menuCard {
   display: flex;
   align-items: center;
-  gap: 8px;
+  gap: 6px;
   padding: 6px 14px;
-  height: 36px;
-  background: white;
-  border: 1px solid #d3dce0;
+  height: 30px;
+  background-color: rgb(247, 249, 250);
+  border: 1px solid transparent;
   border-radius: 6px;
   cursor: pointer;
   font-size: 13px;
-  color: #536171;
+  color: rgb(27, 39, 58);
   transition: all 0.2s;
   font-weight: 400;
 }
 
 .menuCard:hover {
-  border-color: #b8c4ce;
-  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);
+  /* border-color: #b8c4ce; */
+  /* box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08); */
+  background-color: #e8ebed;
 }
 
 .menuCardActive {
-  background: #0066ff;
-  border-color: #0066ff;
-  color: white;
-  box-shadow: 0 2px 4px rgba(0, 102, 255, 0.2);
+  background: none rgb(232, 245, 255);
+  border: 1px solid rgb(64, 160, 255);
+  color: rgb(0, 89, 200);
+  font-weight: 500;
 }
 
 .menuCardActive:hover {
-  background: #0052cc;
   border-color: #0052cc;
+  color: #0052cc;
 }
 
 .menuIcon {
@@ -88,6 +96,69 @@
   white-space: nowrap;
 }
 
+.menuArrow {
+  font-size: 10px;
+  opacity: 0.7;
+  margin-left: 2px;
+}
+
+/* Dropdown Menu */
+.dropdown {
+  position: absolute;
+  top: calc(100% + 2px);
+  left: 0;
+  min-width: 180px;
+  background: white;
+  border: 1px solid #d3dce0;
+  border-radius: 6px;
+  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
+  padding: 4px;
+  z-index: 1000;
+  animation: dropdownFadeIn 0.15s ease-out;
+}
+
+/* 添加一个不可见的桥接区域,防止鼠标移动时下拉菜单消失 */
+.dropdown::before {
+  content: "";
+  position: absolute;
+  top: -6px;
+  left: 0;
+  right: 0;
+  height: 6px;
+  background: transparent;
+}
+
+@keyframes dropdownFadeIn {
+  from {
+    opacity: 0;
+    transform: translateY(-8px);
+  }
+  to {
+    opacity: 1;
+    transform: translateY(0);
+  }
+}
+
+.dropdownItem {
+  display: block;
+  width: 100%;
+  padding: 8px 12px;
+  text-align: left;
+  background: transparent;
+  border: none;
+  border-radius: 4px;
+  cursor: pointer;
+  font-size: 13px;
+  color: #536171;
+  transition: all 0.15s;
+  white-space: nowrap;
+}
+
+.dropdownItem:hover {
+  background: #f7f9fa;
+  color: #0066ff;
+}
+
 /* Right Side */
 .right {
   display: flex;
@@ -110,7 +181,7 @@
   align-items: center;
   gap: 4px;
   padding-left: 12px;
-  border-left: 1px solid #e5e8eb;
+  /* border-left: 1px solid #e5e8eb; */
 }
 
 .iconButton {

+ 90 - 22
src/layouts/Header/index.tsx

@@ -1,15 +1,19 @@
-import { Button } from "@contentful/f36-components";
+// import { Button } from "@contentful/f36-components";
+import { GearSixIcon } from "@contentful/f36-icons";
 import { useAuth } from "../../contexts/AuthContext";
 import { useMenu } from "../../contexts/MenuContext";
 import { useNavigate, useLocation } from "react-router-dom";
-import { useEffect } from "react";
+import { useEffect, useState } from "react";
+import type { JeecgMenu } from "../../types/menu";
 import styles from "./index.module.css";
 
 export default function Header() {
   const { menus, loading } = useAuth();
-  const { activeFirstMenu, setActiveFirstMenu, setActiveSecondMenu } = useMenu();
+  const { activeFirstMenu, setActiveFirstMenu, setActiveSecondMenu } =
+    useMenu();
   const navigate = useNavigate();
   const location = useLocation();
+  const [hoveredMenu, setHoveredMenu] = useState<JeecgMenu | null>(null);
 
   // 获取顶级菜单(一级菜单)
   const topMenus = menus.filter((menu) => !menu.hidden && !menu.meta?.hideMenu);
@@ -19,7 +23,7 @@ export default function Header() {
     const path = location.pathname;
     const segments = path.split("/").filter(Boolean);
     const firstPath = segments[0];
-    
+
     // 尝试匹配一级菜单
     const matchedMenu = topMenus.find((menu) => {
       const menuPath = menu.path || "";
@@ -31,16 +35,24 @@ export default function Header() {
       setActiveFirstMenu(matchedMenu);
       setActiveSecondMenu(null);
     }
-  }, [location.pathname, topMenus, activeFirstMenu, setActiveFirstMenu, setActiveSecondMenu]);
+  }, [
+    location.pathname,
+    topMenus,
+    activeFirstMenu,
+    setActiveFirstMenu,
+    setActiveSecondMenu,
+  ]);
 
-  const handleMenuClick = (menu: typeof topMenus[0]) => {
+  const handleMenuClick = (menu: (typeof topMenus)[0]) => {
     console.log("Header: Clicked menu", menu.name, menu.path);
     setActiveFirstMenu(menu);
     setActiveSecondMenu(null);
 
     // 如果有子菜单,导航到第一个可见子菜单
     if (menu.children && menu.children.length > 0) {
-      const firstChild = menu.children.find((child) => !child.hidden && !child.meta?.hideMenu);
+      const firstChild = menu.children.find(
+        (child) => !child.hidden && !child.meta?.hideMenu
+      );
       if (firstChild) {
         const childPath = firstChild.path || "";
         console.log("Header: Navigating to first child", childPath);
@@ -60,11 +72,17 @@ export default function Header() {
         {/* Logo */}
         <div className={styles.logo}>
           <svg width="24" height="24" viewBox="0 0 24 24" fill="none">
-            <circle cx="12" cy="12" r="10" fill="#0066ff"/>
-            <path d="M8 12L11 15L16 9" stroke="white" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
+            <circle cx="12" cy="12" r="10" fill="#0066ff" />
+            <path
+              d="M8 12L11 15L16 9"
+              stroke="white"
+              strokeWidth="2"
+              strokeLinecap="round"
+              strokeLinejoin="round"
+            />
           </svg>
         </div>
-        
+
         {/* Card Navigation */}
         {loading ? (
           <div className={styles.loading}>加载中...</div>
@@ -72,15 +90,61 @@ export default function Header() {
           <nav className={styles.menuCards}>
             {topMenus.map((menu) => {
               const isActive = activeFirstMenu?.id === menu.id;
-              
+              const hasChildren = menu.children && menu.children.length > 0;
+
               return (
-                <button
+                <div
                   key={menu.id}
-                  className={`${styles.menuCard} ${isActive ? styles.menuCardActive : ""}`}
-                  onClick={() => handleMenuClick(menu)}
+                  className={styles.menuCardWrapper}
+                  onMouseEnter={() => hasChildren && setHoveredMenu(menu)}
+                  onMouseLeave={() => setHoveredMenu(null)}
                 >
-                  <span className={styles.menuText}>{menu.meta?.title || menu.name}</span>
-                </button>
+                  <button
+                    className={`${styles.menuCard} ${
+                      isActive ? styles.menuCardActive : ""
+                    }`}
+                    onClick={() => handleMenuClick(menu)}
+                  >
+                    <span className={styles.menuText}>
+                      {menu.meta?.title || menu.name}
+                    </span>
+                    {hasChildren && <span className={styles.menuArrow}>▼</span>}
+                  </button>
+
+                  {/* 下拉菜单 */}
+                  {hasChildren && hoveredMenu?.id === menu.id && (
+                    <div className={styles.dropdown}>
+                      {menu.children
+                        ?.filter((child) => !child.hidden && !child.meta?.hideMenu)
+                        .map((child) => {
+                          // 构建完整路径
+                          const childPath = child.path || "";
+                          const parentPath = menu.path || "";
+                          
+                          // 如果子路径是绝对路径,直接使用
+                          // 否则拼接父路径
+                          const fullPath = childPath.startsWith("/") 
+                            ? childPath 
+                            : `${parentPath}/${childPath}`;
+                          
+                          return (
+                            <button
+                              key={child.id}
+                              className={styles.dropdownItem}
+                              onClick={() => {
+                                setActiveFirstMenu(menu);
+                                setActiveSecondMenu(child);
+                                setHoveredMenu(null);
+                                navigate(fullPath);
+                              }}
+                            >
+                              {child.meta?.title || child.name}
+                            </button>
+                          );
+                        })}
+                    </div>
+                  )}
+                </div>
               );
             })}
           </nav>
@@ -89,18 +153,22 @@ export default function Header() {
 
       <div className={styles.right}>
         {/* Environment Selector */}
-        <div className={styles.envSelector}>
+        {/* <div className={styles.envSelector}>
           <Button variant="positive" size="small">
             Blank
           </Button>
-          <Button variant="secondary" size="small" className={styles.branchButton}>
+          <Button
+            variant="secondary"
+            size="small"
+            className={styles.branchButton}
+          >
             master 🔽
           </Button>
-        </div>
+        </div> */}
 
         {/* Action Icons */}
         <div className={styles.actions}>
-          <button className={styles.iconButton} title="Search">
+          {/* <button className={styles.iconButton} title="Search">
             🔍
           </button>
           <button className={styles.iconButton} title="Help">
@@ -108,9 +176,9 @@ export default function Header() {
           </button>
           <button className={styles.iconButton} title="Notifications">
             🔔
-          </button>
+          </button> */}
           <button className={styles.iconButton} title="Settings">
-            ⚙️
+            <GearSixIcon size="small" />
           </button>
           <button className={styles.iconButton} title="User">
             <div className={styles.avatar}>👤</div>

+ 5 - 1
src/layouts/MainLayout/index.module.css

@@ -3,16 +3,20 @@
   flex-direction: column;
   height: 100vh;
   overflow: hidden;
+  background-color: rgb(247, 249, 250);
 }
 
 .content {
   display: flex;
   flex: 1;
   overflow: hidden;
+  margin: 0 20px;
+  border-radius: 10px 10px 0px 0px;
 }
 
 .main {
   flex: 1;
   overflow: auto;
-  background-color: #f7f9fa;
+  background-color: #fff;
+  padding: 20px;
 }

+ 9 - 7
src/layouts/Sidebar/index.module.css

@@ -14,24 +14,26 @@
   display: flex;
   align-items: center;
   justify-content: space-between;
-  padding: 12px;
+  padding: 12px 16px;
   cursor: pointer;
   background-color: transparent;
-  color: #333;
-  border-radius: 4px;
+  color: #536171;
+  border-radius: 6px;
   font-size: 14px;
   transition: all 0.2s;
+  font-weight: 400;
 }
 
 .menuItem:hover {
-  background-color: #f0f0f0;
+  background-color: #f7f9fa;
 }
 
 .menuItemActive {
-  background-color: #0066ff;
-  color: white;
+  background-color: #e8ebed;
+  color: #1a1a1a;
+  font-weight: 500;
 }
 
 .menuItemActive:hover {
-  background-color: #0052cc;
+  background-color: #dfe3e6;
 }

+ 1 - 0
src/main.tsx

@@ -1,6 +1,7 @@
 import React from "react";
 import ReactDOM from "react-dom/client";
 import App from "./App";
+import "./index.css";
 import "@contentful/f36-tokens/dist/css/index.css";
 
 ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(

+ 0 - 0
src/pages/installed-apps/index.module.tsx


+ 8 - 0
src/pages/installed-apps/index.tsx

@@ -0,0 +1,8 @@
+export default function InstalledApps() {
+  return (
+    <div style={{ padding: "24px" }}>
+      <h1>已安装应用</h1>
+      <p>已安装应用列表页面</p>
+    </div>
+  );
+}

+ 0 - 0
src/pages/market-place/index.module.css


+ 8 - 0
src/pages/market-place/index.tsx

@@ -0,0 +1,8 @@
+export default function MarketPlace() {
+  return (
+    <div style={{ padding: "24px" }}>
+      <h1>市场</h1>
+      <p>应用市场页面</p>
+    </div>
+  );
+}

+ 1 - 0
src/router/index.tsx

@@ -92,6 +92,7 @@ function menuToRoute(menu: JeecgMenu, isChild = false): RouteObject | null {
   if (isChild && routePath.startsWith("/")) {
     const segments = routePath.split("/").filter(Boolean);
     routePath = segments[segments.length - 1] || "";
+    console.log(`[Router] Converting child path: ${menu.path} → ${routePath}`);
   }
 
   const route: RouteObject = {