1. 礼物特效
This commit is contained in:
@@ -1,61 +1,95 @@
|
||||
<template>
|
||||
<div class="min-h-screen bg-[#f6f4ef] text-[#2c2c2c]">
|
||||
<!-- 顶部栏 -->
|
||||
<header class="sticky top-0 z-30 bg-white/90 backdrop-blur border-b border-[#a88c6b]/15 px-4 sm:px-6 py-3 flex items-center justify-between gap-3">
|
||||
<div class="flex items-center gap-3 min-w-0">
|
||||
<span class="text-xl text-[#a88c6b] font-serif shrink-0">❀</span>
|
||||
<div class="min-w-0">
|
||||
<div class="text-sm tracking-[0.18em] font-light truncate">婚礼邀请函 · 内容编辑后台</div>
|
||||
<div class="text-[10px] text-[#8A8680] tracking-[0.12em] mt-0.5">
|
||||
制作者 · <span class="text-[#a88c6b]">年糕崽崽</span>
|
||||
<header class="admin-header sticky top-0 z-30 bg-white/90 backdrop-blur border-b border-[#a88c6b]/15">
|
||||
<div class="admin-header-row">
|
||||
<div class="flex items-center gap-3 min-w-0 shrink-0">
|
||||
<span class="text-xl text-[#a88c6b] font-serif shrink-0">❀</span>
|
||||
<div class="min-w-0">
|
||||
<div class="text-sm tracking-[0.18em] font-light truncate">婚礼邀请函 · 内容编辑后台</div>
|
||||
<div class="text-[10px] text-[#8A8680] tracking-[0.12em] mt-0.5">
|
||||
制作者 · <span class="text-[#a88c6b]">年糕崽崽</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center gap-2 flex-wrap justify-end">
|
||||
<a-segmented
|
||||
v-model:value="menuLayout"
|
||||
size="small"
|
||||
class="admin-layout-seg"
|
||||
:options="menuLayoutOptions"
|
||||
/>
|
||||
<a-button size="small" @click="openPreview">
|
||||
<i class="fa-solid fa-eye mr-1 text-[#a88c6b]"></i>预览请柬
|
||||
</a-button>
|
||||
<a-dropdown>
|
||||
<a-button size="small">
|
||||
<i class="fa-solid fa-scroll mr-1 text-[#a88c6b]"></i>预览海报 <i class="fa-solid fa-chevron-down ml-1 text-[10px]"></i>
|
||||
|
||||
<!-- 顶部菜单:一级分类 + 下拉 -->
|
||||
<nav v-if="menuLayout === 'top'" class="admin-top-menu" aria-label="后台菜单">
|
||||
<a-dropdown
|
||||
v-for="group in adminMenuGroups"
|
||||
:key="group.key"
|
||||
:trigger="['hover', 'click']"
|
||||
placement="bottomLeft"
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
class="admin-top-menu-trigger"
|
||||
:class="{ active: isMenuGroupActive(group) }"
|
||||
>
|
||||
<span>{{ group.label }}</span>
|
||||
<i class="fa-solid fa-chevron-down admin-top-menu-caret" />
|
||||
</button>
|
||||
<template #overlay>
|
||||
<a-menu
|
||||
class="admin-top-dropdown"
|
||||
:selected-keys="[activeTab]"
|
||||
@click="({ key }) => { activeTab = String(key) }"
|
||||
>
|
||||
<a-menu-item v-for="item in group.items" :key="item.key">
|
||||
<i :class="item.icon" class="mr-2 text-[12px] opacity-80" />
|
||||
{{ item.label }}
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
</nav>
|
||||
|
||||
<div class="admin-header-actions">
|
||||
<a-segmented
|
||||
v-model:value="menuLayout"
|
||||
size="small"
|
||||
class="admin-layout-seg"
|
||||
:options="menuLayoutOptions"
|
||||
/>
|
||||
<a-button size="small" @click="openPreview">
|
||||
<i class="fa-solid fa-eye mr-1 text-[#a88c6b]"></i>预览请柬
|
||||
</a-button>
|
||||
<template #overlay>
|
||||
<a-menu>
|
||||
<a-menu-item v-for="s in POSTER_SIDES" :key="s.key" @click="openPosterPreview(s.key)">
|
||||
{{ s.label }}({{ s.key }}) {{ s.route }}
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
<a-button
|
||||
v-if="isConfigTab"
|
||||
type="primary"
|
||||
size="small"
|
||||
:loading="saving"
|
||||
@click="onSave"
|
||||
class="!bg-[#a88c6b] hover:!bg-[#967b5c] !border-[#a88c6b]"
|
||||
>
|
||||
<i class="fa-solid fa-floppy-disk mr-1"></i>保存配置
|
||||
</a-button>
|
||||
<a-dropdown>
|
||||
<a-button size="small">{{ adminStore.user?.account || '管理员' }}<i class="fa-solid fa-chevron-down ml-1 text-[10px]"></i></a-button>
|
||||
<template #overlay>
|
||||
<a-menu>
|
||||
<a-menu-item key="logout" @click="onLogout"><i class="fa-solid fa-right-from-bracket mr-2"></i>退出登录</a-menu-item>
|
||||
</a-menu>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
<a-dropdown>
|
||||
<a-button size="small">
|
||||
<i class="fa-solid fa-scroll mr-1 text-[#a88c6b]"></i>预览海报 <i class="fa-solid fa-chevron-down ml-1 text-[10px]"></i>
|
||||
</a-button>
|
||||
<template #overlay>
|
||||
<a-menu>
|
||||
<a-menu-item v-for="s in POSTER_SIDES" :key="s.key" @click="openPosterPreview(s.key)">
|
||||
{{ s.label }}({{ s.key }}) {{ s.route }}
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
<a-button
|
||||
v-if="isConfigTab"
|
||||
type="primary"
|
||||
size="small"
|
||||
:loading="saving"
|
||||
@click="onSave"
|
||||
class="!bg-[#a88c6b] hover:!bg-[#967b5c] !border-[#a88c6b]"
|
||||
>
|
||||
<i class="fa-solid fa-floppy-disk mr-1"></i>保存配置
|
||||
</a-button>
|
||||
<a-dropdown>
|
||||
<a-button size="small">{{ adminStore.user?.account || '管理员' }}<i class="fa-solid fa-chevron-down ml-1 text-[10px]"></i></a-button>
|
||||
<template #overlay>
|
||||
<a-menu>
|
||||
<a-menu-item key="logout" @click="onLogout"><i class="fa-solid fa-right-from-bracket mr-2"></i>退出登录</a-menu-item>
|
||||
</a-menu>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="admin-shell" :class="menuLayout === 'side' ? 'admin-shell--side' : 'admin-shell--top'">
|
||||
<nav class="admin-nav" aria-label="后台菜单">
|
||||
<nav v-if="menuLayout === 'side'" class="admin-nav" aria-label="后台菜单">
|
||||
<div
|
||||
v-for="group in adminMenuGroups"
|
||||
:key="group.key"
|
||||
@@ -1205,6 +1239,11 @@ const menuLayout = ref(readStoredMenuLayout())
|
||||
watch(menuLayout, (v) => {
|
||||
try { localStorage.setItem(MENU_LAYOUT_KEY, v) } catch { /* ignore */ }
|
||||
})
|
||||
|
||||
function isMenuGroupActive(group) {
|
||||
return Array.isArray(group?.items) && group.items.some((item) => item.key === activeTab.value)
|
||||
}
|
||||
|
||||
const activePhotoTab = ref('0')
|
||||
const saving = ref(false)
|
||||
const uploading = ref(false)
|
||||
@@ -2248,6 +2287,66 @@ onUnmounted(() => {
|
||||
.admin-layout-seg :deep(.ant-segmented-item-selected) {
|
||||
color: #a88c6b;
|
||||
}
|
||||
.admin-header {
|
||||
padding: 10px 16px 10px;
|
||||
}
|
||||
.admin-header-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px 16px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.admin-header-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
flex-wrap: wrap;
|
||||
justify-content: flex-end;
|
||||
margin-left: auto;
|
||||
}
|
||||
.admin-top-menu {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 2px;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
overflow-x: auto;
|
||||
scrollbar-width: none;
|
||||
padding: 2px 0;
|
||||
}
|
||||
.admin-top-menu::-webkit-scrollbar { display: none; }
|
||||
.admin-top-menu-trigger {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
border: none;
|
||||
background: transparent;
|
||||
color: #5c5348;
|
||||
font-size: 13px;
|
||||
letter-spacing: 0.04em;
|
||||
padding: 8px 12px;
|
||||
border-radius: 10px;
|
||||
cursor: pointer;
|
||||
white-space: nowrap;
|
||||
transition: background 0.15s, color 0.15s;
|
||||
}
|
||||
.admin-top-menu-trigger:hover {
|
||||
background: rgba(168, 140, 107, 0.08);
|
||||
color: #967b5c;
|
||||
}
|
||||
.admin-top-menu-trigger.active {
|
||||
color: #a88c6b;
|
||||
background: rgba(168, 140, 107, 0.12);
|
||||
font-weight: 500;
|
||||
}
|
||||
.admin-top-menu-caret {
|
||||
font-size: 9px;
|
||||
opacity: 0.65;
|
||||
}
|
||||
.admin-top-dropdown :deep(.ant-dropdown-menu-item-selected) {
|
||||
color: #a88c6b;
|
||||
background: rgba(168, 140, 107, 0.12);
|
||||
}
|
||||
.admin-shell {
|
||||
margin: 0 auto;
|
||||
padding: 20px 16px 40px;
|
||||
@@ -2267,15 +2366,6 @@ onUnmounted(() => {
|
||||
border: 1px solid rgba(168, 140, 107, 0.14);
|
||||
border-radius: 16px;
|
||||
box-shadow: 0 8px 28px rgba(88, 68, 42, 0.05);
|
||||
}
|
||||
.admin-shell--top .admin-nav {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 10px 18px;
|
||||
padding: 14px 16px 12px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.admin-shell--side .admin-nav {
|
||||
position: sticky;
|
||||
top: 68px;
|
||||
width: 212px;
|
||||
@@ -2291,27 +2381,21 @@ onUnmounted(() => {
|
||||
margin-bottom: 6px;
|
||||
padding: 0 6px;
|
||||
}
|
||||
.admin-shell--top .admin-nav-group {
|
||||
min-width: 0;
|
||||
}
|
||||
.admin-shell--side .admin-nav-group + .admin-nav-group {
|
||||
.admin-nav-group + .admin-nav-group {
|
||||
margin-top: 14px;
|
||||
padding-top: 12px;
|
||||
border-top: 1px dashed rgba(168, 140, 107, 0.22);
|
||||
}
|
||||
.admin-nav-items {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 6px;
|
||||
}
|
||||
.admin-shell--side .admin-nav-items {
|
||||
flex-direction: column;
|
||||
flex-wrap: nowrap;
|
||||
gap: 6px;
|
||||
}
|
||||
.admin-nav-item {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
width: 100%;
|
||||
border: 1px solid transparent;
|
||||
background: transparent;
|
||||
color: #5c5348;
|
||||
@@ -2323,9 +2407,6 @@ onUnmounted(() => {
|
||||
transition: background 0.15s, color 0.15s, border-color 0.15s;
|
||||
text-align: left;
|
||||
}
|
||||
.admin-shell--side .admin-nav-item {
|
||||
width: 100%;
|
||||
}
|
||||
.admin-nav-item:hover {
|
||||
background: rgba(168, 140, 107, 0.08);
|
||||
color: #967b5c;
|
||||
@@ -2351,29 +2432,33 @@ onUnmounted(() => {
|
||||
color: #a88c6b;
|
||||
text-align: center;
|
||||
}
|
||||
.admin-shell--top .admin-nav-credit {
|
||||
width: 100%;
|
||||
margin-top: 4px;
|
||||
padding-top: 10px;
|
||||
}
|
||||
.admin-main {
|
||||
min-width: 0;
|
||||
flex: 1;
|
||||
}
|
||||
@media (max-width: 960px) {
|
||||
.admin-top-menu {
|
||||
order: 3;
|
||||
flex: 1 1 100%;
|
||||
border-top: 1px solid rgba(168, 140, 107, 0.12);
|
||||
margin-top: 2px;
|
||||
padding-top: 8px;
|
||||
}
|
||||
}
|
||||
@media (max-width: 860px) {
|
||||
.admin-shell--side {
|
||||
flex-direction: column;
|
||||
}
|
||||
.admin-shell--side .admin-nav {
|
||||
.admin-nav {
|
||||
position: static;
|
||||
width: 100%;
|
||||
max-height: none;
|
||||
}
|
||||
.admin-shell--side .admin-nav-items {
|
||||
.admin-nav-items {
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.admin-shell--side .admin-nav-item {
|
||||
.admin-nav-item {
|
||||
width: auto;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user