Feature(custom): support indicate current picbed config page

This commit is contained in:
Kuingsmile
2026-01-20 22:27:33 +08:00
parent 4cfa61c040
commit 79808e7d39
3 changed files with 96 additions and 498 deletions

View File

@@ -1,26 +1,38 @@
<template>
<nav class="navigation" :class="{ collapsed: isCollapsed }">
<div class="title-bar">
<div v-show="!isCollapsed" class="app-title">
<div class="app-text" @click="openGithubPage">
<nav
class="group no-scrollbar flex h-screen w-[150px] flex-col overflow-hidden border-r border-r-border-secondary/50 bg-bg-secondary transition-all duration-medium ease-apple max-md:w-[60px] [.collapsed]:w-[60px]"
:class="{ collapsed: isCollapsed }"
>
<div
class="relative flex items-center justify-center bg-bg-secondary px-4 py-5 group-[.collapsed]:px-2 group-[.collapsed]:py-4"
>
<div v-show="!isCollapsed" class="flex flex-col items-center gap-1 group-[.collapsed]:hidden max-md:hidden">
<div
class="text-[16px] font-bold tracking-tight text-main hover:cursor-pointer hover:text-accent"
@click="openGithubPage"
>
{{ t('app.title') }}
</div>
<div class="app-version">v{{ version }}</div>
<div
class="rounded-lg border border-border/50 bg-bg-secondary px-[8px] py-[3px] text-[10px] font-medium text-secondary"
>
v{{ version }}
</div>
</div>
<button
:title="isCollapsed ? t('navigation.expand') : t('navigation.collapse')"
class="collapse-button"
class="absolute top-1/2 right-[8px] flex -translate-y-1/2 cursor-pointer items-center justify-center rounded-sm border-none bg-transparent p-[4px] transition-all duration-200 ease-apple group-[.collapsed]:absolute group-[.collapsed]:top-[20px] group-[.collapsed]:right-[16px] group-[.collapsed]:transform-none hover:bg-surface-elevated hover:text-main"
@click="isCollapsed = !isCollapsed"
>
<component :is="isCollapsed ? ChevronRightIcon : ChevronLeftIcon" :size="16" />
</button>
</div>
<div class="theme-section">
<div class="flex items-center justify-center p-3">
<ThemeSwitcher :collapsed="isCollapsed" />
</div>
<div class="nav-menu">
<div class="no-scrollbar min-h-0 flex-1 overflow-y-auto py-4">
<div
v-for="item in navigationItems.slice(0, 3)"
:key="item.path"
@@ -32,22 +44,29 @@
<div class="nav-icon-container">
<component :is="item.icon" :size="18" />
</div>
<span v-show="!isCollapsed" class="nav-label">{{ item.name }}</span>
<span v-show="!isCollapsed" class="max-md:hidden" :class="isCollapsed ? 'hidden' : ''">{{ item.name }}</span>
</div>
<Disclosure v-show="!isCollapsed" v-slot="{ open }" as="div" class="nav-submenu">
<DisclosureButton class="nav-item submenu-trigger">
<Disclosure v-show="!isCollapsed" v-slot="{ open }" as="div" class="relative mt-[4px] justify-center">
<DisclosureButton
class="nav-item relative flex w-full cursor-pointer items-center justify-center gap-3 border-none bg-transparent px-4 py-3 text-sm font-medium text-secondary no-underline transition-all duration-200 ease-apple hover:bg-surface-elevated hover:text-main"
>
<div class="nav-icon-container">
<DatabaseIcon :size="18" />
</div>
<span class="nav-label">{{ t('navigation.picbed') }}</span>
<ChevronDownIcon :size="16" class="submenu-arrow" :class="{ 'rotate-180': open }" />
<span class="shrink-0 max-md:hidden" :class="isCollapsed ? 'hidden' : ''">{{ t('navigation.picbed') }}</span>
<ChevronDownIcon
:size="16"
class="absolute right-4 shrink-0 transition-all duration-200 ease-apple"
:class="{ 'rotate-180': open }"
/>
</DisclosureButton>
<DisclosurePanel class="submenu-panel">
<DisclosurePanel class="mt-[2px] flex flex-col gap-[4px] pl-11">
<div
v-for="item in visiblePicBeds"
:key="item.type"
class="submenu-item"
:class="{ 'router-link-active': isPicBedPathActive(item.type) }"
class="flex cursor-pointer items-center px-4 py-2 text-sm font-medium text-secondary no-underline transition-all duration-200 ease-apple hover:bg-surface-elevated hover:text-accent-hover [.router-link-active]:border-r-4 [.router-link-active]:border-accent [.router-link-active]:bg-surface [.router-link-active]:text-accent"
@click="navigateToUploaderConfig(item.type)"
>
<span>{{ item.name }}</span>
@@ -56,7 +75,7 @@
</Disclosure>
<div
v-show="isCollapsed"
class="nav-item collapsed-picbed"
class="nav-item cursor-default bg-surface-elevated hover:text-main"
:title="t('navigation.picbed')"
@click="isCollapsed = !isCollapsed"
>
@@ -76,11 +95,15 @@
<div class="nav-icon-container">
<component :is="item.icon" :size="18" />
</div>
<span v-show="!isCollapsed" class="nav-label">{{ item.name }}</span>
<span v-show="!isCollapsed" class="max-md:hidden" :class="isCollapsed ? 'hidden' : ''">{{ item.name }}</span>
</div>
</div>
<div class="sidebar-footer">
<button class="footer-button" :title="t('navigation.moreOptions')" @click="openMenu">
<div class="border-t border-t-border p-3">
<button
class="fixed bottom-[4px] left-[4px] cursor-pointer rounded-full border-none bg-transparent p-[8px] text-tertiary hover:bg-surface-elevated hover:text-main"
:title="t('navigation.moreOptions')"
@click="openMenu"
>
<Info :size="20" />
</button>
</div>
@@ -89,38 +112,51 @@
<FirstTimeGuide ref="guideRef" />
<TransitionRoot appear :show="qrcodeVisible" as="template">
<Dialog as="div" class="qr-dialog" @close="qrcodeVisible = false">
<div class="dialog-container">
<Dialog
as="div"
class="fixed inset-0 z-50 flex items-center justify-center overflow-y-auto"
@close="qrcodeVisible = false"
>
<div class="fixed inset-0 z-50 flex min-h-screen items-center justify-center overflow-y-auto p-[16px]">
<TransitionChild as="template">
<DialogPanel class="dialog-panel">
<DialogTitle class="dialog-title">
<DialogPanel
class="w-full max-w-[500px] overflow-visible rounded-xl border border-border bg-bg-tertiary shadow-md"
>
<DialogTitle class="m-0 px-[24px] pt-[20px] text-2xl font-semibold text-main">
{{ t('navigation.picBedQrCode') }}
</DialogTitle>
<div class="dialog-content">
<div class="form-group">
<label class="form-label">{{ t('navigation.choosePicBed') }}</label>
<div class="p-4">
<div class="mb-5">
<label class="mb-2 block text-base font-medium text-main">{{ t('navigation.choosePicBed') }}</label>
<Listbox v-model="choosedPicBedForQRCode" multiple>
<div class="listbox-container">
<ListboxButton class="listbox-button">
<span v-if="choosedPicBedForQRCode.length === 0" class="placeholder">
<div class="relative">
<ListboxButton
class="flex w-full cursor-pointer items-center justify-between rounded-2xl border border-border bg-surface px-4 py-3 text-base text-main hover:border-accent"
>
<span v-if="choosedPicBedForQRCode.length === 0" class="text-secondary">
{{ t('navigation.selectPicBeds') }}
</span>
<span v-else class="selected-count">
<span v-else class="text-main">
{{ choosedPicBedForQRCode.length }} {{ t('navigation.selected') }}
</span>
<ChevronDownIcon :size="16" class="listbox-arrow" />
<ChevronDownIcon :size="16" class="text-secondary" />
</ListboxButton>
<transition>
<ListboxOptions class="listbox-options">
<ListboxOptions
class="absolute top-full right-0 left-0 z-1000 mt-[4px] max-h-[300px] overflow-y-auto rounded-sm border border-border bg-bg-tertiary shadow-md"
>
<ListboxOption
v-for="picbed in picBedG"
:key="picbed.type"
v-slot="{ active, selected }"
:value="picbed.type"
>
<li class="listbox-option" :class="{ active, selected }">
<li
class="flex cursor-pointer items-center justify-between px-4 py-3 text-base text-main [.active]:bg-surface-elevated [.selected]:bg-accent [.selected]:text-white"
:class="{ active, selected }"
>
<span>{{ picbed.name }}</span>
<CheckIcon v-if="selected" :size="16" />
</li>
@@ -130,19 +166,26 @@
</div>
</Listbox>
<button v-if="choosedPicBedForQRCode.length > 0" class="copy-button" @click="handleCopyPicBedConfig">
<button
v-if="choosedPicBedForQRCode.length > 0"
class="mt-3 flex cursor-pointer items-center gap-2 rounded-sm border-none bg-accent px-4 py-2 text-base font-medium text-white hover:bg-accent-hover"
@click="handleCopyPicBedConfig"
>
<CopyIcon :size="16" />
{{ t('navigation.copyPicBedConfig') }}
</button>
</div>
<div v-if="choosedPicBedForQRCode.length > 0" class="qr-container">
<qrcode-vue :size="280" :value="picBedConfigString" class="qr-code" />
<div v-if="choosedPicBedForQRCode.length > 0" class="flex justify-center py-5">
<qrcode-vue :size="280" :value="picBedConfigString" class="overflow-hidden shadow-sm" />
</div>
</div>
<div class="dialog-actions">
<button class="cancel-button" @click="qrcodeVisible = false">
<div class="flex justify-end gap-3 px-4 pb-4">
<button
class="cursor-pointer rounded-sm border border-border bg-danger/50 px-4 py-2 text-base text-main hover:bg-danger/70"
@click="qrcodeVisible = false"
>
{{ $t('navigation.close') }}
</button>
</div>
@@ -268,6 +311,11 @@ function isPathActive(path: string): boolean {
return route.path === path
}
function isPicBedPathActive(type: string): boolean {
console.log('type:', type, 'route.params.type:', route.params.type)
return route.name === routerConfig.UPLOADER_CONFIG_PAGE && route.params.type === type
}
const navigationItems = computed(() => [
{ name: t('navigation.upload'), path: '/main-page/upload', icon: UploadIcon },
{ name: t('navigation.manage'), path: '/main-page/manage-login-page', icon: BriefcaseBusiness },

View File

@@ -1,464 +1,14 @@
.navigation {
display: flex;
overflow: hidden;
border-right: 1px solid var(--color-border-secondary);
width: 150px;
height: 100vh;
background: var(--color-background-secondary);
transition: width 0.3s ease;
flex-direction: column;
}
.navigation.collapsed {
width: 60px;
}
.title-bar {
position: relative;
display: flex;
justify-content: center;
align-items: center;
padding: 1.25rem 1rem;
background: var(--color-background-secondary);
}
.navigation.collapsed .title-bar {
padding: 1rem 0.5rem;
}
.collapse-button {
position: absolute;
top: 50%;
right: 8px;
display: flex;
justify-content: center;
align-items: center;
border: none;
border-radius: 4px;
padding: 4px;
color: var(--color-text-primary);
background: transparent;
transition: all 0.2s ease;
transform: translateY(-50%);
cursor: pointer;
}
.collapse-button:hover {
color: var(--color-text-primary);
background: var(--color-surface-elevated);
}
.navigation.collapsed .collapse-button {
position: static;
transform: none;
}
.app-title {
display: flex;
flex-direction: column;
align-items: center;
gap: 0.25rem;
}
.app-text {
font-size: 16px;
font-weight: 700;
color: var(--color-text-primary);
letter-spacing: -0.025em;
}
.app-text:hover {
cursor: pointer;
color: var(--color-accent);
}
.app-version {
border: 1px solid var(--color-border);
border-radius: var(--radius-lg);
padding: 3px 8px;
font-size: 10px;
font-weight: 500;
color: var(--color-text-secondary);
background: var(--color-background-secondary);
}
.theme-section {
display: flex;
justify-content: center;
align-items: center;
padding: 0.75rem;
}
.nav-menu {
overflow-y: auto;
padding: 1rem 0;
min-height: 0;
flex: 1;
}
@import "tailwindcss" reference;
@import "../../assets/css/theme.css" reference;
@import "../../assets/css/utilities.css" reference;
.nav-item {
display: flex;
justify-content: center;
align-items: center;
padding: 0.75rem 1rem;
font-size: 0.875rem;
font-weight: 500;
text-decoration: none;
color: var(--color-text-secondary);
transition: all 0.2s ease;
gap: 0.75rem;
cursor: pointer;
}
.navigation.collapsed .nav-item {
justify-content: center;
padding: 0.75rem 0.5rem;
gap: 0;
}
.navigation.collapsed .nav-label {
display: none;
}
.nav-item:hover {
color: var(--color-text-primary);
background: var(--color-surface);
}
.nav-item.router-link-active {
border-right: 3px solid var(--color-accent);
color: var(--color-accent);
background: var(--color-surface);
@apply flex items-center justify-center py-3 px-4 text-sm font-medium no-underline text-secondary gap-3 cursor-pointer transition-all duration-200 ease-apple;
@apply hover:text-main hover:bg-surface;
@apply group-[.collapsed]:justify-center group-[.collapsed]:py-3 group-[.collapsed]:px-2 group-[.collapsed]:gap-0 ;
@apply [.router-link-active]:border-accent [.router-link-active]:border-r-4 [.router-link-active]:text-accent [.router-link-active]:bg-surface;
}
.nav-icon-container {
position: relative;
display: flex;
justify-content: center;
align-items: center;
width: 20px;
height: 20px;
flex-shrink: 0;
}
.sidebar-footer {
border-top: 1px solid var(--color-border);
padding: 12px;
}
.footer-button {
position: fixed;
bottom: 4px;
left: 4px;
border: none;
border-radius: var(--radius-sm);
padding: 8px;
color: var(--color-text-tertiary);
background: transparent;
cursor: pointer;
}
.footer-button:hover {
color: var(--color-text-primary);
background: var(--color-surface-elevated);
}
.nav-submenu {
position: relative;
justify-content: center;
margin-top: 4px;
}
.submenu-trigger {
position: relative;
display: flex;
justify-content: center;
align-items: center;
border: none;
padding: 0.75rem 1rem;
width: 100%;
font-size: 0.875rem;
font-weight: 500;
text-decoration: none;
color: var(--color-text-secondary);
background: transparent;
transition: all 0.2s ease;
gap: 0.75rem;
cursor: pointer;
}
.submenu-trigger:hover {
color: var(--color-text-primary);
background: var(--color-surface-elevated);
}
.submenu-trigger .nav-icon-container {
flex-shrink: 0;
}
.submenu-trigger span {
flex-shrink: 0;
}
.submenu-arrow {
position: absolute;
right: 1rem;
transition: transform 0.2s ease;
flex-shrink: 0;
}
.rotate-180 {
transform: rotate(180deg);
}
.submenu-panel {
display: flex;
margin-top: 2px;
padding-left: 2.75rem;
flex-direction: column;
gap: 4px;
}
.submenu-item {
display: flex;
align-items: center;
border-radius: var(--radius-sm);
padding: 0.5rem 1rem;
font-size: 0.8125rem;
font-weight: 500;
text-decoration: none;
color: var(--color-text-secondary);
transition: all 0.2s ease;
cursor: pointer;
}
.submenu-item:hover {
color: var(--color-text-primary);
background: var(--color-surface-elevated);
}
.collapsed-picbed {
cursor: default;
}
.collapsed-picbed:hover {
color: var(--color-text-primary);
background: var(--color-surface-elevated);
}
.qr-dialog {
position: fixed;
z-index: 50;
display: flex;
justify-content: center;
align-items: center;
overflow-y: auto;
inset: 0;
}
.dialog-container {
position: fixed;
z-index: 50;
display: flex;
justify-content: center;
align-items: center;
overflow-y: auto;
padding: 16px;
min-height: 100vh;
inset: 0;
}
.dialog-panel {
overflow: visible;
border: 1px solid var(--color-border);
border-radius: var(--radius-xl);
width: 100%;
max-width: 500px;
background: var(--color-background-tertiary);
box-shadow: var(--shadow-md);
}
.dialog-title {
margin: 0;
padding: 20px 24px 0;
font-size: 18px;
font-weight: 600;
color: var(--color-text-primary);
}
.dialog-content {
padding: 20px 24px;
}
.form-group {
margin-bottom: 20px;
}
.form-label {
display: block;
margin-bottom: 8px;
font-size: 14px;
font-weight: 500;
color: var(--color-text-primary);
}
.listbox-container {
position: relative;
}
.listbox-button {
display: flex;
justify-content: space-between;
align-items: center;
border: 1px solid var(--color-border);
border-radius: var(--border-radius);
padding: 12px 16px;
width: 100%;
font-size: 14px;
color: var(--color-text-primary);
background: var(--color-surface);
transition: var(--transition);
cursor: pointer;
}
.listbox-button:hover {
border-color: var(--color-accent);
}
.placeholder {
color: var(--color-text-secondary);
}
.selected-count {
color: var(--color-text-primary);
}
.listbox-arrow {
color: var(--color-text-secondary);
}
.listbox-options {
position: absolute;
top: 100%;
right: 0;
left: 0;
z-index: 1000;
overflow-y: auto;
margin-top: 4px;
border: 1px solid var(--color-border);
border-radius: var(--border-radius);
max-height: 300px;
background: var(--color-background-tertiary);
box-shadow: var(--shadow-md);
}
.listbox-option {
display: flex;
justify-content: space-between;
align-items: center;
padding: 12px 16px;
font-size: 14px;
color: var(--color-text-primary);
transition: var(--transition);
cursor: pointer;
}
.listbox-option.active {
background: var(--color-surface-elevated);
}
.listbox-option.selected {
color: white;
background: var(--color-accent);
}
.copy-button {
display: flex;
align-items: center;
margin-top: 12px;
border: none;
border-radius: var(--border-radius);
padding: 10px 16px;
font-size: 14px;
font-weight: 500;
color: white;
background: var(--color-accent);
transition: var(--transition);
gap: 8px;
cursor: pointer;
}
.copy-button:hover {
background: var(--color-accent-hover);
}
.qr-container {
display: flex;
justify-content: center;
padding: 20px 0;
}
.qr-code {
overflow: hidden;
border-radius: var(--border-radius);
box-shadow: var(--shadow-sm);
}
.dialog-actions {
display: flex;
justify-content: flex-end;
padding: 0 24px 20px;
gap: 12px;
}
.cancel-button {
border: 1px solid var(--color-border);
border-radius: var(--border-radius);
padding: 10px 20px;
font-size: 14px;
color: var(--color-text-primary);
background: var(--color-surface-elevated);
transition: var(--transition);
cursor: pointer;
}
.cancel-button:hover {
background: var(--color-border);
}
/* Responsive Design */
@media (width <= 768px) {
.navigation {
width: 60px;
}
.nav-label {
display: none;
}
.app-title {
display: none;
}
.collapse-button {
display: none;
}
}
/* Scrollbar Styling */
::-webkit-scrollbar {
display: none;
}
::-webkit-scrollbar-track {
background: transparent;
}
::-webkit-scrollbar-thumb {
border-radius: 0;
background: var(--color-border);
}
::-webkit-scrollbar-thumb:hover {
background: var(--color-text-secondary);
@apply relative flex justify-center items-center w-[20px] h-[20px] shrink-0;
}

View File

@@ -34,10 +34,10 @@
<!-- Config Grid -->
<div
class="no-scrollbar flex w-full flex-1 items-center gap-4 overflow-auto rounded-2xl border border-border-secondary px-4 py-6 shadow-md"
class="no-scrollbar flex w-full flex-1 items-start gap-4 overflow-auto rounded-2xl border border-border-secondary px-4 py-6 shadow-md"
>
<div
class="no-scrollbar grid h-full w-full grid-cols-[repeat(auto-fill,minmax(300px,1fr))] gap-5 overflow-auto border-none p-1 max-md:grid-cols-1 max-md:gap-4 xl:grid-cols-[repeat(auto-fill,minmax(325px,1fr))]"
class="no-scrollbar grid h-auto w-full grid-cols-[repeat(auto-fill,minmax(300px,1fr))] gap-5 overflow-auto border-none p-1 max-md:grid-cols-1 max-md:gap-4 xl:grid-cols-[repeat(auto-fill,minmax(325px,1fr))]"
>
<!-- Config Items -->
<div