mirror of
https://github.com/Kuingsmile/PicList.git
synced 2026-05-06 20:42:57 +08:00
✨ Feature(custom): support collapse navi bar
This commit is contained in:
@@ -1,7 +1,13 @@
|
|||||||
<template>
|
<template>
|
||||||
<nav class="navigation">
|
<nav
|
||||||
|
class="navigation"
|
||||||
|
:class="{ collapsed: isCollapsed }"
|
||||||
|
>
|
||||||
<div class="title-bar">
|
<div class="title-bar">
|
||||||
<div class="app-title">
|
<div
|
||||||
|
v-if="!isCollapsed"
|
||||||
|
class="app-title"
|
||||||
|
>
|
||||||
<div
|
<div
|
||||||
class="app-text"
|
class="app-text"
|
||||||
@click="openGithubPage"
|
@click="openGithubPage"
|
||||||
@@ -12,10 +18,24 @@
|
|||||||
v{{ version }}
|
v{{ version }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<button
|
||||||
|
:title="isCollapsed ? t('navigation.expand') : t('navigation.collapse')"
|
||||||
|
class="collapse-button"
|
||||||
|
@click="isCollapsed = !isCollapsed"
|
||||||
|
>
|
||||||
|
<ChevronLeftIcon
|
||||||
|
v-if="!isCollapsed"
|
||||||
|
:size="20"
|
||||||
|
/>
|
||||||
|
<ChevronRightIcon
|
||||||
|
v-else
|
||||||
|
:size="20"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="theme-section">
|
<div class="theme-section">
|
||||||
<ThemeSwitcher />
|
<ThemeSwitcher :collapsed="isCollapsed" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="nav-menu">
|
<div class="nav-menu">
|
||||||
@@ -32,9 +52,13 @@
|
|||||||
:size="18"
|
:size="18"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<span class="nav-label">{{ item.name }}</span>
|
<span
|
||||||
|
v-if="!isCollapsed"
|
||||||
|
class="nav-label"
|
||||||
|
>{{ item.name }}</span>
|
||||||
</router-link>
|
</router-link>
|
||||||
<Disclosure
|
<Disclosure
|
||||||
|
v-if="!isCollapsed"
|
||||||
v-slot="{ open }"
|
v-slot="{ open }"
|
||||||
as="div"
|
as="div"
|
||||||
class="nav-submenu"
|
class="nav-submenu"
|
||||||
@@ -61,6 +85,15 @@
|
|||||||
</router-link>
|
</router-link>
|
||||||
</DisclosurePanel>
|
</DisclosurePanel>
|
||||||
</Disclosure>
|
</Disclosure>
|
||||||
|
<div
|
||||||
|
v-else
|
||||||
|
class="nav-item collapsed-picbed"
|
||||||
|
:title="t('navigation.picbed')"
|
||||||
|
>
|
||||||
|
<div class="nav-icon-container">
|
||||||
|
<DatabaseIcon :size="18" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="sidebar-footer">
|
<div class="sidebar-footer">
|
||||||
<button
|
<button
|
||||||
@@ -205,7 +238,7 @@ import {
|
|||||||
TransitionRoot
|
TransitionRoot
|
||||||
} from '@headlessui/vue'
|
} from '@headlessui/vue'
|
||||||
import { pick } from 'lodash-es'
|
import { pick } from 'lodash-es'
|
||||||
import { CheckIcon, ChevronDownIcon, CopyIcon, DatabaseIcon, FolderIcon, Info, PieChartIcon, PlugIcon, Settings, UploadIcon } from 'lucide-vue-next'
|
import { CheckIcon, ChevronDownIcon, ChevronLeftIcon, ChevronRightIcon, CopyIcon, DatabaseIcon, FolderIcon, Info, PieChartIcon, PlugIcon, Settings, UploadIcon } from 'lucide-vue-next'
|
||||||
import QrcodeVue from 'qrcode.vue'
|
import QrcodeVue from 'qrcode.vue'
|
||||||
import pkg from 'root/package.json'
|
import pkg from 'root/package.json'
|
||||||
import { computed, nextTick, onBeforeMount, onBeforeUnmount, reactive, Ref, ref, watch } from 'vue'
|
import { computed, nextTick, onBeforeMount, onBeforeUnmount, reactive, Ref, ref, watch } from 'vue'
|
||||||
@@ -220,6 +253,7 @@ import { picBedGlobal, updatePicBedGlobal } from '@/utils/global'
|
|||||||
|
|
||||||
import ThemeSwitcher from './ui/ThemeSwitcher.vue'
|
import ThemeSwitcher from './ui/ThemeSwitcher.vue'
|
||||||
const version = ref(pkg.version)
|
const version = ref(pkg.version)
|
||||||
|
const isCollapsed = ref(false)
|
||||||
|
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
const message = useMessage()
|
const message = useMessage()
|
||||||
@@ -230,6 +264,11 @@ const picBedConfigString = ref('')
|
|||||||
|
|
||||||
let removeIpcListener: () => void = () => {}
|
let removeIpcListener: () => void = () => {}
|
||||||
|
|
||||||
|
// Save collapsed state to localStorage when it changes
|
||||||
|
watch(isCollapsed, (newValue) => {
|
||||||
|
localStorage.setItem('navigation-collapsed', JSON.stringify(newValue))
|
||||||
|
})
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => choosedPicBedForQRCode,
|
() => choosedPicBedForQRCode,
|
||||||
val => {
|
val => {
|
||||||
@@ -278,6 +317,12 @@ function openGithubPage () {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onBeforeMount(() => {
|
onBeforeMount(() => {
|
||||||
|
// Load collapsed state from localStorage
|
||||||
|
const savedState = localStorage.getItem('navigation-collapsed')
|
||||||
|
if (savedState !== null) {
|
||||||
|
isCollapsed.value = JSON.parse(savedState)
|
||||||
|
}
|
||||||
|
|
||||||
updatePicBedGlobal()
|
updatePicBedGlobal()
|
||||||
removeIpcListener = window.electron.ipcRendererOn(SHOW_MAIN_PAGE_QRCODE, qrCodeHandler)
|
removeIpcListener = window.electron.ipcRendererOn(SHOW_MAIN_PAGE_QRCODE, qrCodeHandler)
|
||||||
})
|
})
|
||||||
@@ -297,6 +342,11 @@ onBeforeUnmount(() => {
|
|||||||
background: var(--color-background-secondary);
|
background: var(--color-background-secondary);
|
||||||
border-right: 1px solid rgb(229 231 235);
|
border-right: 1px solid rgb(229 231 235);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
transition: width 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navigation.collapsed {
|
||||||
|
width: 60px;
|
||||||
}
|
}
|
||||||
|
|
||||||
:root.dark .navigation,
|
:root.dark .navigation,
|
||||||
@@ -312,6 +362,38 @@ onBeforeUnmount(() => {
|
|||||||
padding: 1.25rem 1rem;
|
padding: 1.25rem 1rem;
|
||||||
border-bottom: 1px solid var(--color-border);
|
border-bottom: 1px solid var(--color-border);
|
||||||
background: var(--color-background-secondary);
|
background: var(--color-background-secondary);
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navigation.collapsed .title-bar {
|
||||||
|
padding: 1rem 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.collapse-button {
|
||||||
|
position: absolute;
|
||||||
|
right: 8px;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
color: var(--color-text-primary);
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 4px;
|
||||||
|
border-radius: 4px;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.collapse-button:hover {
|
||||||
|
background: var(--color-surface-elevated);
|
||||||
|
color: var(--color-text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.navigation.collapsed .collapse-button {
|
||||||
|
position: static;
|
||||||
|
transform: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
:root.dark .title-bar,
|
:root.dark .title-bar,
|
||||||
@@ -382,6 +464,16 @@ onBeforeUnmount(() => {
|
|||||||
transition: all 0.2s ease;
|
transition: all 0.2s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.navigation.collapsed .nav-item {
|
||||||
|
padding: 0.75rem 0.5rem;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navigation.collapsed .nav-label {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
:root.dark .nav-item,
|
:root.dark .nav-item,
|
||||||
:root.auto.dark .nav-item {
|
:root.auto.dark .nav-item {
|
||||||
color: rgb(209 213 219);
|
color: rgb(209 213 219);
|
||||||
@@ -526,15 +618,19 @@ onBeforeUnmount(() => {
|
|||||||
color: var(--color-text-primary);
|
color: var(--color-text-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.submenu-item.router-link-active {
|
.collapsed-picbed {
|
||||||
background: rgb(239 246 255);
|
cursor: default;
|
||||||
color: rgb(99 102 241);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
:root.dark .submenu-item.router-link-active,
|
.collapsed-picbed:hover {
|
||||||
:root.auto.dark .submenu-item.router-link-active {
|
background: rgb(243 244 246);
|
||||||
background: rgb(30 58 138 / 0.2);
|
color: rgb(17 24 39);
|
||||||
color: rgb(129 140 248);
|
}
|
||||||
|
|
||||||
|
:root.dark .collapsed-picbed:hover,
|
||||||
|
:root.auto.dark .collapsed-picbed:hover {
|
||||||
|
background: rgb(55 65 81);
|
||||||
|
color: rgb(243 244 246);
|
||||||
}
|
}
|
||||||
|
|
||||||
.qr-dialog {
|
.qr-dialog {
|
||||||
@@ -718,7 +814,7 @@ onBeforeUnmount(() => {
|
|||||||
}
|
}
|
||||||
/* Responsive Design */
|
/* Responsive Design */
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
.sidebar {
|
.navigation {
|
||||||
width: 60px;
|
width: 60px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -726,6 +822,13 @@ onBeforeUnmount(() => {
|
|||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.app-title {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.collapse-button {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Scrollbar Styling */
|
/* Scrollbar Styling */
|
||||||
|
|||||||
@@ -5,6 +5,12 @@ import { useI18n } from 'vue-i18n'
|
|||||||
|
|
||||||
import { useAppStore } from '@/hooks/useAppStore'
|
import { useAppStore } from '@/hooks/useAppStore'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
collapsed?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
defineProps<Props>()
|
||||||
|
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
const appStore = useAppStore()
|
const appStore = useAppStore()
|
||||||
|
|
||||||
@@ -44,6 +50,7 @@ const toggleTheme = () => {
|
|||||||
<div class="theme-switcher">
|
<div class="theme-switcher">
|
||||||
<button
|
<button
|
||||||
class="theme-toggle-btn"
|
class="theme-toggle-btn"
|
||||||
|
:class="{ collapsed }"
|
||||||
:title="t('settings.theme.toggle')"
|
:title="t('settings.theme.toggle')"
|
||||||
@click="toggleTheme"
|
@click="toggleTheme"
|
||||||
>
|
>
|
||||||
@@ -51,7 +58,10 @@ const toggleTheme = () => {
|
|||||||
:is="currentThemeOption.icon"
|
:is="currentThemeOption.icon"
|
||||||
:size="18"
|
:size="18"
|
||||||
/>
|
/>
|
||||||
<span class="theme-label">{{ currentThemeOption.label }}</span>
|
<span
|
||||||
|
v-if="!collapsed"
|
||||||
|
class="theme-label"
|
||||||
|
>{{ currentThemeOption.label }}</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -74,6 +84,13 @@ const toggleTheme = () => {
|
|||||||
border-radius: var(--radius-md);
|
border-radius: var(--radius-md);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font-size: 0.875rem;
|
font-size: 0.875rem;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-toggle-btn.collapsed {
|
||||||
|
padding: 0.5rem;
|
||||||
|
gap: 0;
|
||||||
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.theme-toggle-btn:hover {
|
.theme-toggle-btn:hover {
|
||||||
@@ -90,5 +107,11 @@ const toggleTheme = () => {
|
|||||||
.theme-label {
|
.theme-label {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.theme-toggle-btn {
|
||||||
|
padding: 0.5rem;
|
||||||
|
gap: 0;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -23,7 +23,9 @@
|
|||||||
"picBedQrCode": "PicBed QR Code",
|
"picBedQrCode": "PicBed QR Code",
|
||||||
"choosePicBed": "Choose PicBed",
|
"choosePicBed": "Choose PicBed",
|
||||||
"selectPicBeds": "Select PicBeds",
|
"selectPicBeds": "Select PicBeds",
|
||||||
"copySuccess": "Copy Success"
|
"copySuccess": "Copy Success",
|
||||||
|
"collapse": "Collapse Sidebar",
|
||||||
|
"expand": "Expand Sidebar"
|
||||||
},
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
"theme": {
|
"theme": {
|
||||||
|
|||||||
@@ -23,7 +23,9 @@
|
|||||||
"picBedQrCode": "图床配置二维码",
|
"picBedQrCode": "图床配置二维码",
|
||||||
"choosePicBed": "选择图床",
|
"choosePicBed": "选择图床",
|
||||||
"selectPicBeds": "请选择图床",
|
"selectPicBeds": "请选择图床",
|
||||||
"copySuccess": "复制成功"
|
"copySuccess": "复制成功",
|
||||||
|
"collapse": "收起侧边栏",
|
||||||
|
"expand": "展开侧边栏"
|
||||||
},
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
"theme": {
|
"theme": {
|
||||||
|
|||||||
@@ -23,7 +23,9 @@
|
|||||||
"picBedQrCode": "圖床配置 QRCODE",
|
"picBedQrCode": "圖床配置 QRCODE",
|
||||||
"choosePicBed": "選擇圖床",
|
"choosePicBed": "選擇圖床",
|
||||||
"selectPicBeds": "請選擇圖床",
|
"selectPicBeds": "請選擇圖床",
|
||||||
"copySuccess": "複製成功"
|
"copySuccess": "複製成功",
|
||||||
|
"collapse": "收起側邊欄",
|
||||||
|
"expand": "展開側邊欄"
|
||||||
},
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
"theme": {
|
"theme": {
|
||||||
|
|||||||
@@ -42,7 +42,11 @@
|
|||||||
<!-- Main Content Card -->
|
<!-- Main Content Card -->
|
||||||
<div class="manage-card main-card">
|
<div class="manage-card main-card">
|
||||||
<div class="main-layout">
|
<div class="main-layout">
|
||||||
<div class="sidebar">
|
<div
|
||||||
|
ref="sidebar"
|
||||||
|
class="sidebar"
|
||||||
|
:style="{ width: sidebarWidth + 'px' }"
|
||||||
|
>
|
||||||
<div class="sidebar-header">
|
<div class="sidebar-header">
|
||||||
<h3 class="sidebar-title">
|
<h3 class="sidebar-title">
|
||||||
{{ menuTitleMap[currentPicBedName] }}
|
{{ menuTitleMap[currentPicBedName] }}
|
||||||
@@ -80,16 +84,11 @@
|
|||||||
v-else-if="currentPicBedName === 'github'"
|
v-else-if="currentPicBedName === 'github'"
|
||||||
class="menu-icon"
|
class="menu-icon"
|
||||||
/>
|
/>
|
||||||
<span class="menu-text">
|
<span
|
||||||
{{
|
class="menu-text"
|
||||||
currentPicBedName === 'tcyun'
|
:title="item"
|
||||||
? item.slice(0, item.length - 11)
|
>
|
||||||
: currentPicBedName === 'github'
|
{{ truncateText(item, currentPicBedName) }}
|
||||||
? item.length > 10
|
|
||||||
? `${item.slice(0, 5)}..${item.slice(-5)}`
|
|
||||||
: item
|
|
||||||
: item
|
|
||||||
}}
|
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -122,7 +121,18 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="content-area">
|
<!-- Resize Handle -->
|
||||||
|
<div
|
||||||
|
class="resize-handle"
|
||||||
|
@mousedown="startResize"
|
||||||
|
>
|
||||||
|
<div class="resize-line" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
ref="contentArea"
|
||||||
|
class="content-area"
|
||||||
|
>
|
||||||
<router-view />
|
<router-view />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -337,7 +347,7 @@ import {
|
|||||||
SettingsIcon,
|
SettingsIcon,
|
||||||
XIcon
|
XIcon
|
||||||
} from 'lucide-vue-next'
|
} from 'lucide-vue-next'
|
||||||
import { onBeforeMount, reactive, ref, watch } from 'vue'
|
import { computed, onBeforeMount, reactive, ref, watch } from 'vue'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
import { useRoute, useRouter } from 'vue-router'
|
import { useRoute, useRouter } from 'vue-router'
|
||||||
|
|
||||||
@@ -357,6 +367,10 @@ const message = useMessage()
|
|||||||
const currentAlias = ref(route.query.alias as string)
|
const currentAlias = ref(route.query.alias as string)
|
||||||
const currentPicBedName = ref(route.query.picBedName as string)
|
const currentPicBedName = ref(route.query.picBedName as string)
|
||||||
|
|
||||||
|
const contentArea = ref<HTMLElement>()
|
||||||
|
const sidebarWidth = ref(160)
|
||||||
|
const isResizing = ref(false)
|
||||||
|
|
||||||
let allPicBedConfigure = JSON.parse(route.query.allPicBedConfigure as string)
|
let allPicBedConfigure = JSON.parse(route.query.allPicBedConfigure as string)
|
||||||
let currentPagePicBedConfig = reactive(JSON.parse(route.query.config as string))
|
let currentPagePicBedConfig = reactive(JSON.parse(route.query.config as string))
|
||||||
|
|
||||||
@@ -369,6 +383,46 @@ const isLoadingBucketList = ref(false)
|
|||||||
const nweBucketDrawerVisible = ref(false)
|
const nweBucketDrawerVisible = ref(false)
|
||||||
const picBedSwitchDialogVisible = ref(false)
|
const picBedSwitchDialogVisible = ref(false)
|
||||||
|
|
||||||
|
const maxTextLength = computed(() => {
|
||||||
|
const fixedSpace = 16 + 12 + 24 + 8
|
||||||
|
const availableWidth = sidebarWidth.value - fixedSpace
|
||||||
|
const estimatedCharWidth = 14 * 0.6
|
||||||
|
const maxChars = Math.floor(availableWidth / estimatedCharWidth)
|
||||||
|
return Math.max(6, Math.min(maxChars, 60))
|
||||||
|
})
|
||||||
|
|
||||||
|
const truncateText = (text: string, picBedName: string): string => {
|
||||||
|
if (!text) return ''
|
||||||
|
|
||||||
|
if (picBedName === 'tcyun') {
|
||||||
|
const baseName = text.slice(0, text.length - 11)
|
||||||
|
if (baseName.length <= maxTextLength.value) {
|
||||||
|
return baseName
|
||||||
|
}
|
||||||
|
return `${baseName.slice(0, maxTextLength.value - 3)}...`
|
||||||
|
} else if (picBedName === 'github') {
|
||||||
|
if (text.length <= maxTextLength.value) {
|
||||||
|
return text
|
||||||
|
}
|
||||||
|
const minSideLength = 3
|
||||||
|
const totalEllipsis = 2 // '..'
|
||||||
|
const availableForContent = maxTextLength.value - totalEllipsis
|
||||||
|
|
||||||
|
if (availableForContent < minSideLength * 2) {
|
||||||
|
return `${text.slice(0, maxTextLength.value - 3)}...`
|
||||||
|
}
|
||||||
|
|
||||||
|
const prefixLength = Math.ceil(availableForContent / 2)
|
||||||
|
const suffixLength = availableForContent - prefixLength
|
||||||
|
return `${text.slice(0, prefixLength)}..${text.slice(-suffixLength)}`
|
||||||
|
} else {
|
||||||
|
if (text.length <= maxTextLength.value) {
|
||||||
|
return text
|
||||||
|
}
|
||||||
|
return `${text.slice(0, maxTextLength.value - 3)}...`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
watch(route, async (newRoute) => {
|
watch(route, async (newRoute) => {
|
||||||
if (newRoute.fullPath.split('?')[0] === '/main-page/manage-main-page') {
|
if (newRoute.fullPath.split('?')[0] === '/main-page/manage-main-page') {
|
||||||
currentAlias.value = newRoute.query.alias as string
|
currentAlias.value = newRoute.query.alias as string
|
||||||
@@ -379,6 +433,8 @@ watch(route, async (newRoute) => {
|
|||||||
}
|
}
|
||||||
}, { deep: true })
|
}, { deep: true })
|
||||||
|
|
||||||
|
watch(sidebarWidth, () => {}, { immediate: false })
|
||||||
|
|
||||||
const urlMap: IStringKeyMap = {
|
const urlMap: IStringKeyMap = {
|
||||||
aliyun: 'https://oss.console.aliyun.com',
|
aliyun: 'https://oss.console.aliyun.com',
|
||||||
github: 'https://github.com',
|
github: 'https://github.com',
|
||||||
@@ -563,6 +619,33 @@ function openBucketPageSetting () {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function startResize (event: MouseEvent) {
|
||||||
|
isResizing.value = true
|
||||||
|
const startX = event.clientX
|
||||||
|
const startWidth = sidebarWidth.value
|
||||||
|
|
||||||
|
const handleMouseMove = (e: MouseEvent) => {
|
||||||
|
if (!isResizing.value) return
|
||||||
|
|
||||||
|
const deltaX = e.clientX - startX
|
||||||
|
const newWidth = Math.max(120, Math.min(400, startWidth + deltaX))
|
||||||
|
sidebarWidth.value = newWidth
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleMouseUp = () => {
|
||||||
|
isResizing.value = false
|
||||||
|
document.removeEventListener('mousemove', handleMouseMove)
|
||||||
|
document.removeEventListener('mouseup', handleMouseUp)
|
||||||
|
document.body.style.cursor = ''
|
||||||
|
document.body.style.userSelect = ''
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener('mousemove', handleMouseMove)
|
||||||
|
document.addEventListener('mouseup', handleMouseUp)
|
||||||
|
document.body.style.cursor = 'col-resize'
|
||||||
|
document.body.style.userSelect = 'none'
|
||||||
|
}
|
||||||
|
|
||||||
onBeforeMount(() => {
|
onBeforeMount(() => {
|
||||||
getBucketList()
|
getBucketList()
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -75,12 +75,42 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.sidebar {
|
.sidebar {
|
||||||
width: 160px;
|
min-width: 120px;
|
||||||
|
max-width: 400px;
|
||||||
background: var(--color-surface-secondary);
|
background: var(--color-surface-secondary);
|
||||||
border-right: 1px solid var(--color-border);
|
border-right: 1px solid var(--color-border);
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
min-height: 0; /* Fix for flex overflow */
|
min-height: 0; /* Fix for flex overflow */
|
||||||
|
transition: width 0.1s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.resize-handle {
|
||||||
|
width: 4px;
|
||||||
|
background: transparent;
|
||||||
|
cursor: col-resize;
|
||||||
|
position: relative;
|
||||||
|
flex-shrink: 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.resize-handle:hover {
|
||||||
|
background: var(--color-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.resize-handle:hover .resize-line {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.resize-line {
|
||||||
|
width: 2px;
|
||||||
|
height: 40px;
|
||||||
|
background: var(--color-accent);
|
||||||
|
border-radius: 1px;
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0.2s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar-header {
|
.sidebar-header {
|
||||||
@@ -180,6 +210,9 @@
|
|||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
line-height: 1.2;
|
line-height: 1.2;
|
||||||
word-break: break-word;
|
word-break: break-word;
|
||||||
|
overflow: hidden;
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar-footer {
|
.sidebar-footer {
|
||||||
|
|||||||
Reference in New Issue
Block a user