周玉环 2 долоо хоног өмнө
parent
commit
b6487c431b

+ 214 - 2
src/layouts/Header/index.module.css

@@ -3,7 +3,7 @@
   justify-content: space-between;
   align-items: center;
   padding: 16px;
-  /* height: 48px; */
+  height: 60px;
   /* border-bottom: 1px solid #e5e8eb; */
   background-color: #f7f9fa;
 }
@@ -15,6 +15,29 @@
   flex: 1;
 }
 
+/* Mobile Menu Button */
+.mobileMenuButton {
+  display: none;
+  justify-content: center;
+  align-items: center;
+  gap: 8px;
+  padding: 8px 12px;
+  background: #fff;
+  border: 1px solid #d3dce0;
+  border-radius: 6px;
+  cursor: pointer;
+  font-size: 15px;
+  color: rgb(17, 27, 43);
+  transition: all 0.2s;
+}
+
+.mobileMenuButton:hover {
+  border-color: #b8c4ce;
+  /* box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08); */
+    background-color: #e8ebed;
+
+}
+
 .logo {
   display: flex;
   align-items: center;
@@ -88,7 +111,7 @@
   font-size: 16px;
   display: flex;
   align-items: center;
-  line-height: 1;
+  /* line-height: 1; */
 }
 
 .menuText {
@@ -159,6 +182,36 @@
   color: #0066ff;
 }
 
+/* Responsive Design */
+@media (max-width: 1024px) {
+  .menuCards {
+    display: none;
+  }
+
+  .mobileMenuButton {
+    display: flex;
+  }
+
+  .envSelector {
+    display: none;
+  }
+}
+
+@media (max-width: 768px) {
+  .header {
+    padding: 0 12px;
+  }
+
+  .actions {
+    gap: 2px;
+  }
+
+  .iconButton {
+    width: 28px;
+    height: 28px;
+  }
+}
+
 /* Right Side */
 .right {
   display: flex;
@@ -212,3 +265,162 @@
   justify-content: center;
   font-size: 12px;
 }
+
+
+/* Mobile Menu */
+.mobileMenuWrapper {
+  position: relative;
+  display: none;
+}
+
+.mobileMenuButton {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  padding: 6px 12px;
+  background: white;
+  border: 1px solid #d3dce0;
+  border-radius: 6px;
+  cursor: pointer;
+  font-size: 13px;
+  color: #536171;
+  transition: all 0.2s;
+}
+
+.mobileMenuButton:hover {
+  border-color: #b8c4ce;
+  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);
+}
+
+.menuIcon {
+  font-size: 16px;
+  line-height: 1;
+}
+
+/* Mobile Menu Dropdown */
+.mobileMenuDropdown {
+  position: absolute;
+  top: calc(100% + 2px);
+  left: 0;
+  min-width: 240px;
+  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;
+}
+
+/* 添加桥接区域,防止鼠标移动时下拉菜单消失 */
+.mobileMenuDropdown::before {
+  content: "";
+  position: absolute;
+  top: -6px;
+  left: 0;
+  right: 0;
+  height: 6px;
+  background: transparent;
+}
+
+.mobileMenuItem {
+  position: relative;
+}
+
+.mobileMenuItemButton {
+  display: flex;
+  align-items: center;
+  gap: 10px;
+  width: 100%;
+  padding: 10px 12px;
+  text-align: left;
+  background: transparent;
+  border: none;
+  border-radius: 4px;
+  cursor: pointer;
+  font-size: 14px;
+  color: #536171;
+  transition: all 0.15s;
+}
+
+.mobileMenuItemButton:hover {
+  background: #f7f9fa;
+}
+
+.mobileMenuItemActive {
+  background: #e8ebed;
+  color: #1a1a1a;
+  font-weight: 500;
+}
+
+.mobileMenuItemIcon {
+  font-size: 16px;
+  display: flex;
+  align-items: center;
+}
+
+.mobileMenuItemText {
+  flex: 1;
+}
+
+.mobileMenuItemArrow {
+  font-size: 18px;
+  color: #999;
+}
+
+/* Mobile Submenu */
+.mobileSubmenu {
+  position: absolute;
+  top: 0;
+  left: calc(100% + 2px);
+  min-width: 200px;
+  background: white;
+  border: 1px solid #d3dce0;
+  border-radius: 6px;
+  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
+  padding: 4px;
+  z-index: 1001;
+  animation: dropdownFadeIn 0.15s ease-out;
+}
+
+/* 添加左侧桥接区域 */
+.mobileSubmenu::before {
+  content: "";
+  position: absolute;
+  top: 0;
+  left: -6px;
+  bottom: 0;
+  width: 6px;
+  background: transparent;
+}
+
+.mobileSubmenuItem {
+  display: block;
+  width: 100%;
+  padding: 10px 12px;
+  text-align: left;
+  background: transparent;
+  border: none;
+  border-radius: 4px;
+  cursor: pointer;
+  font-size: 14px;
+  color: #536171;
+  transition: all 0.15s;
+  white-space: nowrap;
+}
+
+.mobileSubmenuItem:hover {
+  background: #f7f9fa;
+  color: #0066ff;
+}
+
+/* Responsive - Show mobile menu on smaller screens */
+@media (max-width: 1024px) {
+  .menuCards {
+    display: none;
+  }
+
+  .mobileMenuWrapper {
+    display: block;
+  }
+}

+ 95 - 42
src/layouts/Header/index.tsx

@@ -1,5 +1,5 @@
 // import { Button } from "@contentful/f36-components";
-import { GearSixIcon } from "@contentful/f36-icons";
+import { GearSixIcon, ListIcon } from "@contentful/f36-icons";
 import { useAuth } from "../../contexts/AuthContext";
 import { useMenu } from "../../contexts/MenuContext";
 import { useNavigate, useLocation } from "react-router-dom";
@@ -14,6 +14,7 @@ export default function Header() {
   const navigate = useNavigate();
   const location = useLocation();
   const [hoveredMenu, setHoveredMenu] = useState<JeecgMenu | null>(null);
+  const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
 
   // 获取顶级菜单(一级菜单)
   const topMenus = menus.filter((menu) => !menu.hidden && !menu.meta?.hideMenu);
@@ -45,21 +46,18 @@ export default function Header() {
 
   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
-      );
-      if (firstChild) {
-        const childPath = firstChild.path || "";
-        console.log("Header: Navigating to first child", childPath);
-        navigate(childPath);
-      }
+
+    const hasChildren = menu.children && menu.children.length > 0;
+
+    // 如果有子菜单,只设置激活状态,不跳转
+    if (hasChildren) {
+      setActiveFirstMenu(menu);
+      setActiveSecondMenu(null);
+      // 不导航,让用户点击下拉菜单中的选项
     } else {
       // 没有子菜单,直接导航到该菜单
+      setActiveFirstMenu(menu);
+      setActiveSecondMenu(null);
       const menuPath = menu.path || "";
       console.log("Header: Navigating to menu", menuPath);
       navigate(menuPath);
@@ -83,6 +81,82 @@ export default function Header() {
           </svg>
         </div>
 
+        {/* Mobile Menu Button */}
+        <div
+          className={styles.mobileMenuWrapper}
+          onMouseEnter={() => setMobileMenuOpen(true)}
+          onMouseLeave={() => setMobileMenuOpen(false)}
+        >
+          <button className={styles.mobileMenuButton}>
+            <span className={styles.menuIcon}><ListIcon /></span>
+            <span>Menu</span>
+          </button>
+
+          {/* Mobile Menu Dropdown */}
+          {mobileMenuOpen && (
+            <div className={styles.mobileMenuDropdown}>
+              {topMenus.map((menu) => {
+                const hasChildren = menu.children && menu.children.length > 0;
+                const isActive = activeFirstMenu?.id === menu.id;
+
+                return (
+                  <div
+                    key={menu.id}
+                    className={styles.mobileMenuItem}
+                    onMouseEnter={() => hasChildren && setHoveredMenu(menu)}
+                    onMouseLeave={() => setHoveredMenu(null)}
+                  >
+                    <button
+                      className={`${styles.mobileMenuItemButton} ${
+                        isActive ? styles.mobileMenuItemActive : ""
+                      }`}
+                      onClick={() => !hasChildren && handleMenuClick(menu)}
+                    >
+                      <span className={styles.mobileMenuItemText}>
+                        {menu.meta?.title || menu.name}
+                      </span>
+                      {hasChildren && (
+                        <span className={styles.mobileMenuItemArrow}>›</span>
+                      )}
+                    </button>
+
+                    {/* Submenu */}
+                    {hasChildren && hoveredMenu?.id === menu.id && (
+                      <div className={styles.mobileSubmenu}>
+                        {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.mobileSubmenuItem}
+                                onClick={() => {
+                                  setActiveFirstMenu(menu);
+                                  setActiveSecondMenu(child);
+                                  setMobileMenuOpen(false);
+                                  setHoveredMenu(null);
+                                  navigate(fullPath);
+                                }}
+                              >
+                                {child.meta?.title || child.name}
+                              </button>
+                            );
+                          })}
+                      </div>
+                    )}
+                  </div>
+                );
+              })}
+            </div>
+          )}
+        </div>
+
         {/* Card Navigation */}
         {loading ? (
           <div className={styles.loading}>加载中...</div>
@@ -115,18 +189,20 @@ export default function Header() {
                   {hasChildren && hoveredMenu?.id === menu.id && (
                     <div className={styles.dropdown}>
                       {menu.children
-                        ?.filter((child) => !child.hidden && !child.meta?.hideMenu)
+                        ?.filter(
+                          (child) => !child.hidden && !child.meta?.hideMenu
+                        )
                         .map((child) => {
                           // 构建完整路径
                           const childPath = child.path || "";
                           const parentPath = menu.path || "";
-                          
+
                           // 如果子路径是绝对路径,直接使用
                           // 否则拼接父路径
-                          const fullPath = childPath.startsWith("/") 
-                            ? childPath 
+                          const fullPath = childPath.startsWith("/")
+                            ? childPath
                             : `${parentPath}/${childPath}`;
-                          
+
                           return (
                             <button
                               key={child.id}
@@ -152,31 +228,8 @@ export default function Header() {
       </div>
 
       <div className={styles.right}>
-        {/* Environment Selector */}
-        {/* <div className={styles.envSelector}>
-          <Button variant="positive" size="small">
-            Blank
-          </Button>
-          <Button
-            variant="secondary"
-            size="small"
-            className={styles.branchButton}
-          >
-            master 🔽
-          </Button>
-        </div> */}
-
         {/* Action Icons */}
         <div className={styles.actions}>
-          {/* <button className={styles.iconButton} title="Search">
-            🔍
-          </button>
-          <button className={styles.iconButton} title="Help">
-            ❓
-          </button>
-          <button className={styles.iconButton} title="Notifications">
-            🔔
-          </button> */}
           <button className={styles.iconButton} title="Settings">
             <GearSixIcon size="small" />
           </button>

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

@@ -18,5 +18,5 @@
   flex: 1;
   overflow: auto;
   background-color: #fff;
-  padding: 20px;
+  padding: 10px;
 }

+ 1 - 1
src/pages/installed-apps/index.tsx

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

+ 1 - 1
src/pages/market-place/index.tsx

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