Add headless CMS for official site content management

Integrate article and page management into the Laravel admin dashboard
to serve as a headless CMS for the Next.js frontend (usher-site).

Backend:
- 7 migrations: article_categories, article_tags, articles, pivots, attachments, pages
- 5 models with relationships: Article, ArticleCategory, ArticleTag, ArticleAttachment, Page
- 4 admin controllers: articles (with publish/archive/pin), categories, tags, pages
- Admin views with EasyMDE markdown editor, multi-select categories/tags
- Navigation section "官網管理" in admin sidebar

API (v1):
- GET /api/v1/articles (filtered by type, category, tag, search; paginated)
- GET /api/v1/articles/{slug} (with related articles)
- GET /api/v1/categories
- GET /api/v1/pages/{slug} (with children)
- GET /api/v1/homepage (aggregated homepage data)
- Attachment download endpoint
- CORS configured for usher.org.tw, vercel.app, localhost:3000

Content migration:
- ImportHugoContent command: imports Hugo markdown files as articles/pages
- Successfully imported 27 articles, 17 categories, 11 tags, 9 pages

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-07 11:58:22 +08:00
parent bfbec861d0
commit a30af8eaf7
45 changed files with 4816 additions and 31 deletions

View File

@@ -13,6 +13,8 @@
<!-- Scripts -->
@vite(['resources/css/app.css', 'resources/js/app.js'])
@stack('styles')
</head>
<body class="font-sans antialiased bg-gray-50 text-slate-900 transition-colors duration-300 dark:bg-slate-900 dark:text-slate-100">
<a href="#main-content" class="sr-only focus:not-sr-only focus:absolute focus:z-50 focus:p-4 focus:bg-white focus:text-black focus:outline-none focus:ring-2 focus:ring-indigo-500">
@@ -35,5 +37,7 @@
{{ $slot }}
</main>
</div>
@stack('scripts')
</body>
</html>

View File

@@ -24,7 +24,7 @@
文件
</x-nav-link>
@if(Auth::user() && (Auth::user()->hasRole(['admin', 'membership_manager', 'finance_accountant', 'finance_cashier', 'staff']) || Auth::user()->canAny(['view_finance_documents', 'view_incomes', 'view_accounting_transactions', 'manage_system_settings'])))
@if(Auth::user() && (Auth::user()->hasRole(['admin', 'membership_manager', 'finance_accountant', 'finance_cashier', 'staff']) || Auth::user()->canAny(['view_finance_documents', 'view_incomes', 'view_accounting_transactions', 'manage_system_settings', 'view_articles', 'view_pages'])))
<div class="hidden sm:flex sm:items-center">
<x-dropdown align="right" width="48">
<x-slot name="trigger">
@@ -97,6 +97,23 @@
文件管理
</x-dropdown-link>
@endhasrole
@can('view_articles')
<div class="border-t border-gray-200 dark:border-gray-600 my-1"></div>
<div class="px-4 py-1 text-xs font-semibold text-gray-400 dark:text-gray-500 uppercase tracking-wider">官網管理</div>
<x-dropdown-link :href="route('admin.articles.index')">
文章管理
</x-dropdown-link>
@endcan
@can('view_pages')
<x-dropdown-link :href="route('admin.pages.index')">
頁面管理
</x-dropdown-link>
@endcan
@can('view_articles')
<x-dropdown-link :href="route('admin.article-categories.index')">
文章分類
</x-dropdown-link>
@endcan
@can('manage_system_settings')
<x-dropdown-link :href="route('admin.settings.general')">
系統設定
@@ -170,7 +187,7 @@
文件
</x-responsive-nav-link>
@if(Auth::user() && (Auth::user()->hasRole(['admin', 'membership_manager', 'finance_accountant', 'finance_cashier', 'staff']) || Auth::user()->canAny(['view_finance_documents', 'view_incomes', 'view_accounting_transactions', 'manage_system_settings'])))
@if(Auth::user() && (Auth::user()->hasRole(['admin', 'membership_manager', 'finance_accountant', 'finance_cashier', 'staff']) || Auth::user()->canAny(['view_finance_documents', 'view_incomes', 'view_accounting_transactions', 'manage_system_settings', 'view_articles', 'view_pages'])))
<div class="pt-2 pb-1 border-t border-gray-200 dark:border-gray-700 mt-2">
<div class="px-4 text-xs font-semibold text-gray-500 dark:text-gray-400 uppercase tracking-wider">
管理
@@ -233,6 +250,26 @@
文件管理
</x-responsive-nav-link>
@endhasrole
@can('view_articles')
<div class="pt-2 pb-1 border-t border-gray-200 dark:border-gray-700 mt-2">
<div class="px-4 text-xs font-semibold text-gray-500 dark:text-gray-400 uppercase tracking-wider">
官網管理
</div>
</div>
<x-responsive-nav-link :href="route('admin.articles.index')" :active="request()->routeIs('admin.articles.*')">
文章管理
</x-responsive-nav-link>
@endcan
@can('view_pages')
<x-responsive-nav-link :href="route('admin.pages.index')" :active="request()->routeIs('admin.pages.*')">
頁面管理
</x-responsive-nav-link>
@endcan
@can('view_articles')
<x-responsive-nav-link :href="route('admin.article-categories.index')" :active="request()->routeIs('admin.article-categories.*')">
文章分類
</x-responsive-nav-link>
@endcan
@can('manage_system_settings')
<x-responsive-nav-link :href="route('admin.settings.general')" :active="request()->routeIs('admin.settings.*')">
系統設定