導航選單

本頁內容

EmDash 選單是您透過管理介面管理的有序連結清單。選單支援下拉式選單的巢狀結構,可以連結到頁面、文章、分類術語或外部 URL。

查詢選單

使用 getMenu() 透過唯一名稱取得選單:

---
import { getMenu } from "emdash";

const primaryMenu = await getMenu("primary");
---

{primaryMenu && (
  <nav>
    <ul>
      {primaryMenu.items.map(item => (
        <li>
          <a href={item.url}>{item.label}</a>
        </li>
      ))}
    </ul>
  </nav>
)}

如果不存在該名稱的選單,函式將返回 null

選單結構

選單包含中繼資料和項目陣列:

interface Menu {
	id: string;
	name: string; // 唯一識別碼("primary"、"footer")
	label: string; // 顯示名稱("Primary Navigation")
	items: MenuItem[];
}

interface MenuItem {
	id: string;
	label: string;
	url: string; // 解析後的 URL
	target?: string; // 新視窗使用 "_blank"
	titleAttr?: string; // HTML title 屬性
	cssClasses?: string; // 自訂 CSS 類別
	children: MenuItem[]; // 用於下拉式選單的巢狀項目
}

URL 根據項目類型自動解析:

  • 頁面/文章項目解析為 /{collection}/{slug}
  • 分類項目解析為 /{taxonomy}/{slug}
  • 集合項目解析為 /{collection}/
  • 自訂連結按原樣使用 URL

渲染巢狀選單

選單項目可以具有用於下拉式導航的子項目。透過遞迴渲染 children 陣列來處理巢狀:

---
import { getMenu } from "emdash";
import type { MenuItem } from "emdash";

interface Props {
  name: string;
}

const menu = await getMenu(Astro.props.name);
---

{menu && (
  <nav class="nav">
    <ul class="nav-list">
      {menu.items.map(item => (
        <li class:list={["nav-item", item.cssClasses]}>
          <a
            href={item.url}
            target={item.target}
            title={item.titleAttr}
            aria-current={Astro.url.pathname === item.url ? "page" : undefined}
          >
            {item.label}
          </a>
          {item.children.length > 0 && (
            <ul class="submenu">
              {item.children.map(child => (
                <li>
                  <a href={child.url} target={child.target}>
                    {child.label}
                  </a>
                </li>
              ))}
            </ul>
          )}
        </li>
      ))}
    </ul>
  </nav>
)}

選單項目類型

管理面板支援五種類型的選單項目:

類型描述URL 解析
page連結到頁面/{collection}/{slug}
post連結到文章/{collection}/{slug}
taxonomy連結到分類或標籤/{taxonomy}/{slug}
collection連結到集合封存/{collection}/
custom外部或自訂 URL按原樣使用

列出所有選單

使用 getMenus() 檢索所有選單定義(不含項目):

import { getMenus } from "emdash";

const menus = await getMenus();
// 返回:[{ id, name, label, locale }, ...]

這主要用於管理介面或除錯。

建立選單

透過 /_emdash/admin/menus 的管理介面建立選單,或使用管理 API:

POST /_emdash/api/menus
Content-Type: application/json

{
  "name": "footer",
  "label": "Footer Navigation"
}

向選單新增項目:

POST /_emdash/api/menus/footer/items
Content-Type: application/json

{
  "type": "page",
  "referenceCollection": "pages",
  "referenceId": "page_privacy",
  "label": "Privacy Policy"
}

新增自訂外部連結:

POST /_emdash/api/menus/footer/items
Content-Type: application/json

{
  "type": "custom",
  "customUrl": "https://github.com/example",
  "label": "GitHub",
  "target": "_blank"
}

重新排序和巢狀

使用重新排序端點更新項目順序和父子關係:

POST /_emdash/api/menus/primary/reorder
Content-Type: application/json

{
  "items": [
    { "id": "item_1", "parentId": null, "sortOrder": 0 },
    { "id": "item_2", "parentId": null, "sortOrder": 1 },
    { "id": "item_3", "parentId": "item_2", "sortOrder": 0 }
  ]
}

這使 item_3 成為 item_2 的子項目,建立下拉式選單。

完整範例

以下範例顯示了帶有主導航的響應式頁眉:

---
import { getMenu, getSiteSettings } from "emdash";

const settings = await getSiteSettings();
const primaryMenu = await getMenu("primary");
---

<html lang="en">
  <head>
    <title>{settings.title}</title>
  </head>
  <body>
    <header class="header">
      <a href="/" class="logo">
        {settings.logo ? (
          <img src={settings.logo.url} alt={settings.logo.alt || settings.title} />
        ) : (
          settings.title
        )}
      </a>

      {primaryMenu && (
        <nav class="main-nav" aria-label="Main navigation">
          <ul>
            {primaryMenu.items.map(item => (
              <li class:list={[item.cssClasses, { "has-children": item.children.length > 0 }]}>
                <a
                  href={item.url}
                  target={item.target}
                  aria-current={Astro.url.pathname === item.url ? "page" : undefined}
                >
                  {item.label}
                </a>
                {item.children.length > 0 && (
                  <ul class="dropdown">
                    {item.children.map(child => (
                      <li>
                        <a href={child.url} target={child.target}>{child.label}</a>
                      </li>
                    ))}
                  </ul>
                )}
              </li>
            ))}
          </ul>
        </nav>
      )}
    </header>

    <main>
      <slot />
    </main>
  </body>
</html>

API 參考

getMenu(name)

透過名稱取得包含所有項目和已解析 URL 的選單。

參數:

  • name — 選單的唯一識別碼(字串)

返回: Promise<Menu | null>

getMenus()

列出所有選單定義(不含項目)。

返回: Promise<Array<{ id: string; name: string; label: string; locale: string }>>