|
1 | 1 | <template> |
2 | | - <nav class="navigation" :class="{ collapsed: isCollapsed }"> |
3 | | - <div class="title-bar"> |
4 | | - <div v-show="!isCollapsed" class="app-title"> |
5 | | - <div class="app-text" @click="openGithubPage"> |
| 2 | + <nav |
| 3 | + 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]" |
| 4 | + :class="{ collapsed: isCollapsed }" |
| 5 | + > |
| 6 | + <div |
| 7 | + class="relative flex items-center justify-center bg-bg-secondary px-4 py-5 group-[.collapsed]:px-2 group-[.collapsed]:py-4" |
| 8 | + > |
| 9 | + <div v-show="!isCollapsed" class="flex flex-col items-center gap-1 group-[.collapsed]:hidden max-md:hidden"> |
| 10 | + <div |
| 11 | + class="text-[16px] font-bold tracking-tight text-main hover:cursor-pointer hover:text-accent" |
| 12 | + @click="openGithubPage" |
| 13 | + > |
6 | 14 | {{ t('app.title') }} |
7 | 15 | </div> |
8 | | - <div class="app-version">v{{ version }}</div> |
| 16 | + <div |
| 17 | + class="rounded-lg border border-border/50 bg-bg-secondary px-[8px] py-[3px] text-[10px] font-medium text-secondary" |
| 18 | + > |
| 19 | + v{{ version }} |
| 20 | + </div> |
9 | 21 | </div> |
10 | 22 | <button |
11 | 23 | :title="isCollapsed ? t('navigation.expand') : t('navigation.collapse')" |
12 | | - class="collapse-button" |
| 24 | + 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" |
13 | 25 | @click="isCollapsed = !isCollapsed" |
14 | 26 | > |
15 | 27 | <component :is="isCollapsed ? ChevronRightIcon : ChevronLeftIcon" :size="16" /> |
16 | 28 | </button> |
17 | 29 | </div> |
18 | 30 |
|
19 | | - <div class="theme-section"> |
| 31 | + <div class="flex items-center justify-center p-3"> |
20 | 32 | <ThemeSwitcher :collapsed="isCollapsed" /> |
21 | 33 | </div> |
22 | 34 |
|
23 | | - <div class="nav-menu"> |
| 35 | + <div class="no-scrollbar min-h-0 flex-1 overflow-y-auto py-4"> |
24 | 36 | <div |
25 | 37 | v-for="item in navigationItems.slice(0, 3)" |
26 | 38 | :key="item.path" |
|
32 | 44 | <div class="nav-icon-container"> |
33 | 45 | <component :is="item.icon" :size="18" /> |
34 | 46 | </div> |
35 | | - <span v-show="!isCollapsed" class="nav-label">{{ item.name }}</span> |
| 47 | + <span v-show="!isCollapsed" class="max-md:hidden" :class="isCollapsed ? 'hidden' : ''">{{ item.name }}</span> |
36 | 48 | </div> |
37 | 49 |
|
38 | | - <Disclosure v-show="!isCollapsed" v-slot="{ open }" as="div" class="nav-submenu"> |
39 | | - <DisclosureButton class="nav-item submenu-trigger"> |
| 50 | + <Disclosure v-show="!isCollapsed" v-slot="{ open }" as="div" class="relative mt-[4px] justify-center"> |
| 51 | + <DisclosureButton |
| 52 | + 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" |
| 53 | + > |
40 | 54 | <div class="nav-icon-container"> |
41 | 55 | <DatabaseIcon :size="18" /> |
42 | 56 | </div> |
43 | | - <span class="nav-label">{{ t('navigation.picbed') }}</span> |
44 | | - <ChevronDownIcon :size="16" class="submenu-arrow" :class="{ 'rotate-180': open }" /> |
| 57 | + <span class="shrink-0 max-md:hidden" :class="isCollapsed ? 'hidden' : ''">{{ t('navigation.picbed') }}</span> |
| 58 | + <ChevronDownIcon |
| 59 | + :size="16" |
| 60 | + class="absolute right-4 shrink-0 transition-all duration-200 ease-apple" |
| 61 | + :class="{ 'rotate-180': open }" |
| 62 | + /> |
45 | 63 | </DisclosureButton> |
46 | | - <DisclosurePanel class="submenu-panel"> |
| 64 | + <DisclosurePanel class="mt-[2px] flex flex-col gap-[4px] pl-11"> |
47 | 65 | <div |
48 | 66 | v-for="item in visiblePicBeds" |
49 | 67 | :key="item.type" |
50 | | - class="submenu-item" |
| 68 | + :class="{ 'router-link-active': isPicBedPathActive(item.type) }" |
| 69 | + 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" |
51 | 70 | @click="navigateToUploaderConfig(item.type)" |
52 | 71 | > |
53 | 72 | <span>{{ item.name }}</span> |
|
56 | 75 | </Disclosure> |
57 | 76 | <div |
58 | 77 | v-show="isCollapsed" |
59 | | - class="nav-item collapsed-picbed" |
| 78 | + class="nav-item cursor-default bg-surface-elevated hover:text-main" |
60 | 79 | :title="t('navigation.picbed')" |
61 | 80 | @click="isCollapsed = !isCollapsed" |
62 | 81 | > |
|
76 | 95 | <div class="nav-icon-container"> |
77 | 96 | <component :is="item.icon" :size="18" /> |
78 | 97 | </div> |
79 | | - <span v-show="!isCollapsed" class="nav-label">{{ item.name }}</span> |
| 98 | + <span v-show="!isCollapsed" class="max-md:hidden" :class="isCollapsed ? 'hidden' : ''">{{ item.name }}</span> |
80 | 99 | </div> |
81 | 100 | </div> |
82 | | - <div class="sidebar-footer"> |
83 | | - <button class="footer-button" :title="t('navigation.moreOptions')" @click="openMenu"> |
| 101 | + <div class="border-t border-t-border p-3"> |
| 102 | + <button |
| 103 | + 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" |
| 104 | + :title="t('navigation.moreOptions')" |
| 105 | + @click="openMenu" |
| 106 | + > |
84 | 107 | <Info :size="20" /> |
85 | 108 | </button> |
86 | 109 | </div> |
|
89 | 112 | <FirstTimeGuide ref="guideRef" /> |
90 | 113 |
|
91 | 114 | <TransitionRoot appear :show="qrcodeVisible" as="template"> |
92 | | - <Dialog as="div" class="qr-dialog" @close="qrcodeVisible = false"> |
93 | | - <div class="dialog-container"> |
| 115 | + <Dialog |
| 116 | + as="div" |
| 117 | + class="fixed inset-0 z-50 flex items-center justify-center overflow-y-auto" |
| 118 | + @close="qrcodeVisible = false" |
| 119 | + > |
| 120 | + <div class="fixed inset-0 z-50 flex min-h-screen items-center justify-center overflow-y-auto p-[16px]"> |
94 | 121 | <TransitionChild as="template"> |
95 | | - <DialogPanel class="dialog-panel"> |
96 | | - <DialogTitle class="dialog-title"> |
| 122 | + <DialogPanel |
| 123 | + class="w-full max-w-[500px] overflow-visible rounded-xl border border-border bg-bg-tertiary shadow-md" |
| 124 | + > |
| 125 | + <DialogTitle class="m-0 px-[24px] pt-[20px] text-2xl font-semibold text-main"> |
97 | 126 | {{ t('navigation.picBedQrCode') }} |
98 | 127 | </DialogTitle> |
99 | 128 |
|
100 | | - <div class="dialog-content"> |
101 | | - <div class="form-group"> |
102 | | - <label class="form-label">{{ t('navigation.choosePicBed') }}</label> |
| 129 | + <div class="p-4"> |
| 130 | + <div class="mb-5"> |
| 131 | + <label class="mb-2 block text-base font-medium text-main">{{ t('navigation.choosePicBed') }}</label> |
103 | 132 | <Listbox v-model="choosedPicBedForQRCode" multiple> |
104 | | - <div class="listbox-container"> |
105 | | - <ListboxButton class="listbox-button"> |
106 | | - <span v-if="choosedPicBedForQRCode.length === 0" class="placeholder"> |
| 133 | + <div class="relative"> |
| 134 | + <ListboxButton |
| 135 | + 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" |
| 136 | + > |
| 137 | + <span v-if="choosedPicBedForQRCode.length === 0" class="text-secondary"> |
107 | 138 | {{ t('navigation.selectPicBeds') }} |
108 | 139 | </span> |
109 | | - <span v-else class="selected-count"> |
| 140 | + <span v-else class="text-main"> |
110 | 141 | {{ choosedPicBedForQRCode.length }} {{ t('navigation.selected') }} |
111 | 142 | </span> |
112 | | - <ChevronDownIcon :size="16" class="listbox-arrow" /> |
| 143 | + <ChevronDownIcon :size="16" class="text-secondary" /> |
113 | 144 | </ListboxButton> |
114 | 145 |
|
115 | 146 | <transition> |
116 | | - <ListboxOptions class="listbox-options"> |
| 147 | + <ListboxOptions |
| 148 | + 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" |
| 149 | + > |
117 | 150 | <ListboxOption |
118 | 151 | v-for="picbed in picBedG" |
119 | 152 | :key="picbed.type" |
120 | 153 | v-slot="{ active, selected }" |
121 | 154 | :value="picbed.type" |
122 | 155 | > |
123 | | - <li class="listbox-option" :class="{ active, selected }"> |
| 156 | + <li |
| 157 | + 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" |
| 158 | + :class="{ active, selected }" |
| 159 | + > |
124 | 160 | <span>{{ picbed.name }}</span> |
125 | 161 | <CheckIcon v-if="selected" :size="16" /> |
126 | 162 | </li> |
|
130 | 166 | </div> |
131 | 167 | </Listbox> |
132 | 168 |
|
133 | | - <button v-if="choosedPicBedForQRCode.length > 0" class="copy-button" @click="handleCopyPicBedConfig"> |
| 169 | + <button |
| 170 | + v-if="choosedPicBedForQRCode.length > 0" |
| 171 | + 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" |
| 172 | + @click="handleCopyPicBedConfig" |
| 173 | + > |
134 | 174 | <CopyIcon :size="16" /> |
135 | 175 | {{ t('navigation.copyPicBedConfig') }} |
136 | 176 | </button> |
137 | 177 | </div> |
138 | 178 |
|
139 | | - <div v-if="choosedPicBedForQRCode.length > 0" class="qr-container"> |
140 | | - <qrcode-vue :size="280" :value="picBedConfigString" class="qr-code" /> |
| 179 | + <div v-if="choosedPicBedForQRCode.length > 0" class="flex justify-center py-5"> |
| 180 | + <qrcode-vue :size="280" :value="picBedConfigString" class="overflow-hidden shadow-sm" /> |
141 | 181 | </div> |
142 | 182 | </div> |
143 | 183 |
|
144 | | - <div class="dialog-actions"> |
145 | | - <button class="cancel-button" @click="qrcodeVisible = false"> |
| 184 | + <div class="flex justify-end gap-3 px-4 pb-4"> |
| 185 | + <button |
| 186 | + class="cursor-pointer rounded-sm border border-border bg-danger/50 px-4 py-2 text-base text-main hover:bg-danger/70" |
| 187 | + @click="qrcodeVisible = false" |
| 188 | + > |
146 | 189 | {{ $t('navigation.close') }} |
147 | 190 | </button> |
148 | 191 | </div> |
@@ -268,6 +311,11 @@ function isPathActive(path: string): boolean { |
268 | 311 | return route.path === path |
269 | 312 | } |
270 | 313 |
|
| 314 | +function isPicBedPathActive(type: string): boolean { |
| 315 | + console.log('type:', type, 'route.params.type:', route.params.type) |
| 316 | + return route.name === routerConfig.UPLOADER_CONFIG_PAGE && route.params.type === type |
| 317 | +} |
| 318 | +
|
271 | 319 | const navigationItems = computed(() => [ |
272 | 320 | { name: t('navigation.upload'), path: '/main-page/upload', icon: UploadIcon }, |
273 | 321 | { name: t('navigation.manage'), path: '/main-page/manage-login-page', icon: BriefcaseBusiness }, |
|
0 commit comments