feat(admin): add initial admin dashboard structure
This commit introduces the foundational structure for the new admin dashboard. - Utilizes @nuxt/ui to build the dashboard layout, including a collapsible sidebar and navigation. - Adds initial pages for the dashboard, news, events, and hall of fame management. - Implements a composable `useDashboardSidebarLinks` for managing sidebar navigation. - Refactors the default layout by integrating the header and footer directly. - Swaps the primary and secondary theme colors across the application.
This commit is contained in:
@@ -1,6 +0,0 @@
|
||||
@import "tailwindcss";
|
||||
|
||||
@theme {
|
||||
--color-primary: #fcef91;
|
||||
--color-secondary: #fb9e3a;
|
||||
}
|
||||
@@ -1,2 +1,9 @@
|
||||
@import "./app.css";
|
||||
@import "./markdown.css";
|
||||
|
||||
@import "tailwindcss";
|
||||
@import "@nuxt/ui";
|
||||
|
||||
@theme {
|
||||
--color-primary: #fb9e3a;
|
||||
--color-secondary: #fcef91;
|
||||
}
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
<template>
|
||||
<!-- 页脚 -->
|
||||
<footer class="bg-gray-900 text-gray-300 py-6">
|
||||
<div class="max-w-6xl mx-auto px-4 flex flex-col md:flex-row justify-between items-center">
|
||||
<!-- 左侧版权信息 -->
|
||||
<div class="text-center md:text-left">
|
||||
<p>© 2025 永平中学校友会. 保留所有权利.</p>
|
||||
</div>
|
||||
|
||||
<div class="">
|
||||
<p class="mt-1">
|
||||
Powered by:
|
||||
<a href="https://tootaio.com" target="_blank" class="font-semibold hover:underline" style="color: #e24545;">
|
||||
Tootaio Studio
|
||||
</a>
|
||||
<span class="mt-1 text-sm text-gray-400">2018 级毕业学长(麦祖奕)</span>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- 右侧社交链接 -->
|
||||
<div class="flex space-x-4 mt-3 md:mt-0">
|
||||
<a href="#">
|
||||
<Icon name="mdi-facebook" />
|
||||
</a>
|
||||
<a href="#">
|
||||
<Icon name="mdi-instagram" />
|
||||
</a>
|
||||
<a href="#">
|
||||
<Icon name="mdi-gmail" />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
@@ -1,25 +0,0 @@
|
||||
<template>
|
||||
<!-- 导航栏 -->
|
||||
<header class="bg-white shadow-md sticky top-0 z-50">
|
||||
<div class="max-w-6xl mx-auto px-4 py-3 flex justify-between items-center">
|
||||
<div>
|
||||
<img class="inline w-16" src="/Logo.svg" alt="YPHS Alumni">
|
||||
<h1 class="inline text-xl font-bold text-gray-900">
|
||||
<a href="/" class="ml-4 hover:text-secondary">永平中学校友会</a>
|
||||
</h1>
|
||||
</div>
|
||||
<nav class="space-x-6 hidden md:flex items-center">
|
||||
<a href="#news" class="hover:text-secondary">新闻</a>
|
||||
<a href="#events" class="hover:text-secondary">活动</a>
|
||||
<a href="#donate" class="hover:text-secondary">捐赠(未开放)</a>
|
||||
<a href="#about" class="hover:text-secondary">关于</a>
|
||||
<a href="/join-us"
|
||||
class="inline-flex items-center gap-2 bg-secondary text-white px-4 py-2 rounded-xl shadow hover:opacity-90">
|
||||
加入
|
||||
<Icon name="mdi:account-plus" class="w-5 h-5" />
|
||||
</a>
|
||||
|
||||
</nav>
|
||||
</div>
|
||||
</header>
|
||||
</template>
|
||||
52
app/components/admin/contents/news/AddModal.vue
Normal file
52
app/components/admin/contents/news/AddModal.vue
Normal file
@@ -0,0 +1,52 @@
|
||||
<template>
|
||||
<UModal v-model:open="open" title="撰写新闻" description="在数据库中添加一篇新闻">
|
||||
<UButton label="撰写新闻" icon="mdi:newspaper-plus" />
|
||||
|
||||
<template #body>
|
||||
<UForm :schema="newsSchema" :state="newsState" @submit="onSubmit">
|
||||
<UFormField label="新闻标题" name="title">
|
||||
<UInput v-model="newsState.title" placeholder="请输入新闻标题" class="w-full" />
|
||||
</UFormField>
|
||||
<UFormField label="新闻内容" name="contents">
|
||||
<UTextarea v-model="newsState.content" class="w-full" />
|
||||
</UFormField>
|
||||
这里应该是要有一个 Markdown 编辑界面
|
||||
<div class="flex justify-end gap-2">
|
||||
<UButton label="Cancel" color="neutral" variant="subtle" @click="open = false" />
|
||||
<UButton label="Create" color="primary" variant="solid" type="submit" />
|
||||
</div>
|
||||
</UForm>
|
||||
</template>
|
||||
</UModal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type { FormSubmitEvent } from '@nuxt/ui';
|
||||
import * as z from 'zod'
|
||||
|
||||
const open = ref(false);
|
||||
|
||||
const newsSchema = z.object({
|
||||
title: z.string().min(2, '标题太短了容易产生歧义,请写长一些').max(15, '标题太长了影响阅读体验,请简短一些'),
|
||||
content: z.string()
|
||||
})
|
||||
|
||||
type NewsSchema = z.output<typeof newsSchema>
|
||||
|
||||
const newsState = reactive<Partial<NewsSchema>>({
|
||||
title: undefined,
|
||||
content: undefined
|
||||
})
|
||||
|
||||
const toast = useToast();
|
||||
const onSubmit = async (event: FormSubmitEvent<NewsSchema>) => {
|
||||
toast.add({
|
||||
title: "成功",
|
||||
description: `${event.data.title} 新闻稿已经撰写完毕`,
|
||||
color: 'success'
|
||||
})
|
||||
open.value = false
|
||||
}
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
@@ -1,10 +1,10 @@
|
||||
<template>
|
||||
<div>
|
||||
<!-- 捐赠模块 -->
|
||||
<section id="donate" class="py-16 text-center bg-[var(--color-primary)]">
|
||||
<section id="donate" class="py-16 text-center bg-secondary">
|
||||
<h3 class="text-2xl font-bold text-gray-900 mb-4">支持与捐赠(功能未开放)</h3>
|
||||
<p class="max-w-2xl mx-auto text-gray-700 mb-6">您的捐赠将用于奖学金、校园建设及校友活动发展。感谢您对母校的支持!</p>
|
||||
<a href="#" class="bg-[var(--color-secondary)] text-white px-8 py-3 rounded-xl shadow hover:opacity-90">立即捐赠</a>
|
||||
<a href="#" class="bg-primary text-white px-8 py-3 rounded-xl shadow hover:opacity-90">立即捐赠</a>
|
||||
</section>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
<h4 class="font-semibold text-lg mb-2">{{ event.title }}</h4>
|
||||
<p class="text-sm text-gray-600 mb-1">日期:{{ useChineseDateFormat(event.date) }}</p>
|
||||
<p class="text-sm text-gray-600 mb-4">地点:{{ event.location }}</p>
|
||||
<a :href="event.path" class="bg-secondary text-white px-5 py-2 rounded-lg hover:opacity-90">阅读详情</a>
|
||||
<a :href="event.path" class="bg-primary text-white px-5 py-2 rounded-lg hover:opacity-90">阅读详情</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<h3 class="text-2xl font-bold text-center text-gray-900 mb-6">名人堂</h3>
|
||||
<div class="grid md:grid-cols-4 gap-6">
|
||||
<div v-for="person in persons" :key="person.id" class="flex flex-col items-center cursor-pointer transition hover:scale-105 hover:drop-shadow-2xl hover:-translate-y-1" @click="jumpToPersonIntro(person.path)">
|
||||
<img :src="person.photo" :alt="person.name" class="w-40 rounded-full border-secondary border-4" />
|
||||
<img :src="person.photo" :alt="person.name" class="w-40 rounded-full border-primary border-4" />
|
||||
<h4 class="text-lg font-bold">{{ person.name }}</h4>
|
||||
<p class="text-sm text-gray-500">{{ person.title }}</p>
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div>
|
||||
<!-- Hero Banner -->
|
||||
<section class="relative bg-primary py-32 md:py-48 lg:py-64 text-center bg-cover bg-center"
|
||||
<section class="relative bg-secondary py-32 md:py-48 lg:py-64 text-center bg-cover bg-center"
|
||||
style="background-image: url('/hero-image.jpg');">
|
||||
<!-- 遮罩 -->
|
||||
<div class="absolute inset-0 bg-black/40"></div>
|
||||
@@ -15,7 +15,7 @@
|
||||
马来西亚柔佛永平中学校友会官方网站
|
||||
</p>
|
||||
<div class="mt-6 space-x-4">
|
||||
<a href="/join-us" class="bg-secondary text-white px-6 py-3 rounded-xl shadow hover:opacity-90">
|
||||
<a href="/join-us" class="bg-primary text-white px-6 py-3 rounded-xl shadow hover:opacity-90">
|
||||
立即加入我们
|
||||
</a>
|
||||
<!-- <a href="#donate"
|
||||
|
||||
@@ -5,11 +5,11 @@
|
||||
<h2 class="text-2xl font-bold text-gray-900 mb-6">最新新闻与公告</h2>
|
||||
<div class="grid md:grid-cols-3 gap-6">
|
||||
<article v-for="n in news" :key="n.id" @click="jumpToNewsDetail(n.stem)"
|
||||
class="bg-primary/10 rounded-xl shadow cursor-pointer transition transform hover:-translate-y-1 hover:scale-105 hover:shadow-xl duration-300 ease-in-out">
|
||||
class="bg-secondary/10 rounded-xl shadow cursor-pointer transition transform hover:-translate-y-1 hover:scale-105 hover:shadow-xl duration-300 ease-in-out">
|
||||
<img class="rounded-xl" :src="n.cover" :alt="n.title">
|
||||
<div class="p-5">
|
||||
<h3 class="font-semibold mb-2">{{ n.title }}</h3>
|
||||
<div class="px-1 w-max bg-primary/25 border-primary border-2 rounded-xl text-secondary text-sm mb-2">{{
|
||||
<div class="px-1 w-max bg-secondary/25 border-secondary border-2 rounded-xl text-primary text-sm mb-2">{{
|
||||
useChineseDateFormat(n.date) }}</div>
|
||||
<p class="text-sm text-gray-600">{{ n.description }}</p>
|
||||
</div>
|
||||
|
||||
56
app/composables/useDashboardSidebarLinks.ts
Normal file
56
app/composables/useDashboardSidebarLinks.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
import type { NavigationMenuItem } from "@nuxt/ui";
|
||||
|
||||
export const useDashboardSidebarLinks = () => {
|
||||
const sidebarLinks = [
|
||||
[
|
||||
{
|
||||
label: "回到主站",
|
||||
icon: "mdi:home",
|
||||
type: "link",
|
||||
to: "/",
|
||||
target: "_blank"
|
||||
},
|
||||
{
|
||||
label: "仪表盘",
|
||||
icon: "mdi:view-dashboard",
|
||||
to: "/admin/dashboard",
|
||||
},
|
||||
{
|
||||
label: "内容管理",
|
||||
icon: "mdi:bookshelf",
|
||||
defaultOpen: true,
|
||||
type: "trigger",
|
||||
children: [
|
||||
{
|
||||
label: "新闻",
|
||||
icon: "mdi:newspaper",
|
||||
to: "/admin/contents/news",
|
||||
},
|
||||
{
|
||||
label: "活动",
|
||||
icon: "mdi:event",
|
||||
to: "/admin/contents/events",
|
||||
},
|
||||
{
|
||||
label: "名人堂",
|
||||
icon: "mdi:trophy-award",
|
||||
to: "/admin/contents/hall-of-fames",
|
||||
},
|
||||
],
|
||||
},
|
||||
// {
|
||||
// label: "Settings",
|
||||
// to: "/settings",
|
||||
// icon: "mdi:cog",
|
||||
// defaultOpen: true,
|
||||
// type: "trigger",
|
||||
// children: [
|
||||
// { label: "General", icon: "mdi:tune", to: "/settings", exact: true },
|
||||
// { label: "Advanced", icon: "mdi:flask", to: "/settings/advanced" },
|
||||
// ],
|
||||
// },
|
||||
],
|
||||
] satisfies NavigationMenuItem[][];
|
||||
|
||||
return { sidebarLinks };
|
||||
};
|
||||
44
app/layouts/admin-dashboard.vue
Normal file
44
app/layouts/admin-dashboard.vue
Normal file
@@ -0,0 +1,44 @@
|
||||
<template>
|
||||
<UDashboardGroup>
|
||||
<UDashboardSidebar id="default" :open="sidebarOpen" collapsible resizable>
|
||||
<template #header="{ collapsed }">
|
||||
<div class="font-bold flex items-center">
|
||||
<img src="/Logo.svg" alt="YPHS Alumni" class="h-8">
|
||||
<span class="ml-2" v-if="!collapsed">永平中学校友会官网</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #default="{ collapsed }">
|
||||
<UDashboardSearchButton :collapsed="collapsed" icon="mdi:magnify" class="bg-transparent ring-default" />
|
||||
|
||||
<UNavigationMenu :collapsed="collapsed" :items="sidebarLinks" orientation="vertical" tooltip popover />
|
||||
</template>
|
||||
|
||||
<template #footer="{ collapsed }">
|
||||
</template>
|
||||
</UDashboardSidebar>
|
||||
|
||||
<UDashboardSearch :groups="groups" />
|
||||
|
||||
<slot />
|
||||
|
||||
</UDashboardGroup>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
const sidebarOpen = ref(false);
|
||||
|
||||
const groups = ref([]);
|
||||
|
||||
const { sidebarLinks } = useDashboardSidebarLinks();
|
||||
|
||||
const route = useRoute();
|
||||
|
||||
// 🪄 自动根据路由 name 或 meta 显示标题
|
||||
const pageTitle = computed(() => {
|
||||
// 如果路由定义了 meta.title 优先用它,否则用 name
|
||||
return route.meta.title || route.name || '首页'
|
||||
})
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
13
app/layouts/admin-login.vue
Normal file
13
app/layouts/admin-login.vue
Normal file
@@ -0,0 +1,13 @@
|
||||
<template>
|
||||
<div>
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
</style>
|
||||
@@ -1,8 +1,63 @@
|
||||
<template>
|
||||
<div>
|
||||
<AppHeader />
|
||||
<!-- 导航栏 -->
|
||||
<header class="bg-white shadow-md sticky top-0 z-50">
|
||||
<div class="max-w-6xl mx-auto px-4 py-3 flex justify-between items-center">
|
||||
<div>
|
||||
<img class="inline w-16" src="/Logo.svg" alt="YPHS Alumni">
|
||||
<h1 class="inline text-xl font-bold text-gray-900">
|
||||
<a href="/" class="ml-4 hover:text-primary">永平中学校友会</a>
|
||||
</h1>
|
||||
</div>
|
||||
<nav class="space-x-6 hidden md:flex items-center">
|
||||
<a href="#news" class="hover:text-primary">新闻</a>
|
||||
<a href="#events" class="hover:text-primary">活动</a>
|
||||
<a href="#donate" class="hover:text-primary">捐赠(未开放)</a>
|
||||
<a href="#about" class="hover:text-primary">关于</a>
|
||||
<a href="/join-us"
|
||||
class="inline-flex items-center gap-2 bg-primary text-white px-4 py-2 rounded-xl shadow hover:opacity-90">
|
||||
加入
|
||||
<Icon name="mdi:account-plus" class="w-5 h-5" />
|
||||
</a>
|
||||
|
||||
</nav>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<slot />
|
||||
<AppFooter />
|
||||
|
||||
<!-- 页脚 -->
|
||||
<footer class="bg-gray-900 text-gray-300 py-6">
|
||||
<div class="max-w-6xl mx-auto px-4 flex flex-col md:flex-row justify-between items-center">
|
||||
<!-- 左侧版权信息 -->
|
||||
<div class="text-center md:text-left">
|
||||
<p>© 2025 永平中学校友会. 保留所有权利.</p>
|
||||
</div>
|
||||
|
||||
<div class="">
|
||||
<p class="mt-1">
|
||||
Powered by:
|
||||
<a href="https://tootaio.com" target="_blank" class="font-semibold hover:underline" style="color: #e24545;">
|
||||
Tootaio Studio
|
||||
</a>
|
||||
<span class="mt-1 text-sm text-gray-400">2018 级毕业学长(麦祖奕)</span>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- 右侧社交链接 -->
|
||||
<div class="flex space-x-4 mt-3 md:mt-0">
|
||||
<a href="#">
|
||||
<Icon name="mdi-facebook" />
|
||||
</a>
|
||||
<a href="#">
|
||||
<Icon name="mdi-instagram" />
|
||||
</a>
|
||||
<a href="#">
|
||||
<Icon name="mdi-gmail" />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -10,6 +65,4 @@
|
||||
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
</style>
|
||||
<style></style>
|
||||
27
app/pages/admin/contents/events/index.vue
Normal file
27
app/pages/admin/contents/events/index.vue
Normal file
@@ -0,0 +1,27 @@
|
||||
<template>
|
||||
<UDashboardPanel>
|
||||
<template #header>
|
||||
<UDashboardNavbar :title="pageTitle" toggle>
|
||||
<template #leading>
|
||||
<UDashboardSidebarCollapse />
|
||||
</template>
|
||||
</UDashboardNavbar>
|
||||
</template>
|
||||
<template #body>
|
||||
This is events page
|
||||
</template>
|
||||
</UDashboardPanel>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
const pageTitle = "活动列表"
|
||||
definePageMeta({
|
||||
layout: "admin-dashboard",
|
||||
title: pageTitle
|
||||
})
|
||||
useHead({
|
||||
title: pageTitle
|
||||
})
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
27
app/pages/admin/contents/hall-of-fames/index.vue
Normal file
27
app/pages/admin/contents/hall-of-fames/index.vue
Normal file
@@ -0,0 +1,27 @@
|
||||
<template>
|
||||
<UDashboardPanel>
|
||||
<template #header>
|
||||
<UDashboardNavbar :title="pageTitle" toggle>
|
||||
<template #leading>
|
||||
<UDashboardSidebarCollapse />
|
||||
</template>
|
||||
</UDashboardNavbar>
|
||||
</template>
|
||||
<template #body>
|
||||
This is hall of fame page
|
||||
</template>
|
||||
</UDashboardPanel>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
const pageTitle = "名人堂列表"
|
||||
definePageMeta({
|
||||
layout: "admin-dashboard",
|
||||
title: pageTitle
|
||||
})
|
||||
useHead({
|
||||
title: pageTitle
|
||||
})
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
34
app/pages/admin/contents/news/index.vue
Normal file
34
app/pages/admin/contents/news/index.vue
Normal file
@@ -0,0 +1,34 @@
|
||||
<template>
|
||||
<UDashboardPanel>
|
||||
<template #header>
|
||||
<UDashboardNavbar :title="pageTitle" toggle>
|
||||
<template #leading>
|
||||
<UDashboardSidebarCollapse />
|
||||
</template>
|
||||
<template #trailing>
|
||||
<UBadge size="md" label="4" variant="outline" color="neutral" />
|
||||
</template>
|
||||
<template #right>
|
||||
<AdminContentsNewsAddModal />
|
||||
</template>
|
||||
</UDashboardNavbar>
|
||||
</template>
|
||||
|
||||
<template #body>
|
||||
This is news page
|
||||
</template>
|
||||
</UDashboardPanel>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
const pageTitle = "新闻列表"
|
||||
definePageMeta({
|
||||
layout: "admin-dashboard",
|
||||
title: pageTitle
|
||||
})
|
||||
useHead({
|
||||
title: pageTitle
|
||||
})
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
28
app/pages/admin/dashboard/index.vue
Normal file
28
app/pages/admin/dashboard/index.vue
Normal file
@@ -0,0 +1,28 @@
|
||||
<template>
|
||||
<UDashboardPanel>
|
||||
<template #header>
|
||||
<UDashboardNavbar :title="pageTitle" toggle>
|
||||
<template #leading>
|
||||
<UDashboardSidebarCollapse />
|
||||
</template>
|
||||
</UDashboardNavbar>
|
||||
</template>
|
||||
|
||||
<template #body>
|
||||
This is home page
|
||||
</template>
|
||||
</UDashboardPanel>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
const pageTitle = "仪表盘"
|
||||
definePageMeta({
|
||||
layout: "admin-dashboard",
|
||||
title: pageTitle,
|
||||
})
|
||||
useHead({
|
||||
title: pageTitle
|
||||
})
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
15
app/pages/admin/index.vue
Normal file
15
app/pages/admin/index.vue
Normal file
@@ -0,0 +1,15 @@
|
||||
<template>
|
||||
<div>
|
||||
Login Page
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
definePageMeta({
|
||||
layout: "admin-login"
|
||||
})
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
</style>
|
||||
Reference in New Issue
Block a user