mirror of
https://github.com/Kuingsmile/PicList.git
synced 2026-05-06 20:42:57 +08:00
🚧 WIP(custom): re-design manage main page
This commit is contained in:
@@ -20,6 +20,7 @@
|
|||||||
- 重新设计了管理功能的全部页面
|
- 重新设计了管理功能的全部页面
|
||||||
- 重构了几乎全部页面,优化了数十项UI细节问题,整体风格更加统一
|
- 重构了几乎全部页面,优化了数十项UI细节问题,整体风格更加统一
|
||||||
- 相册页面多项优化,支持显示已选择图片数量,匹配的url列表和记忆过滤器打开状态
|
- 相册页面多项优化,支持显示已选择图片数量,匹配的url列表和记忆过滤器打开状态
|
||||||
|
- 优化了管理文件浏览页面侧边栏名字的显示,现在在超出宽度时会滚动显示完整名称
|
||||||
- 插件页面现在可以浏览所有插件列表,查看详情和安装
|
- 插件页面现在可以浏览所有插件列表,查看详情和安装
|
||||||
- 新增教学引导页面,首次运行时会自动弹出
|
- 新增教学引导页面,首次运行时会自动弹出
|
||||||
|
|
||||||
|
|||||||
@@ -1,194 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="switch-container">
|
|
||||||
<div class="switch-label-wrapper">
|
|
||||||
<span class="switch-label-text">
|
|
||||||
<span v-for="(segment, index) in segments" :key="index" :style="segment.style">
|
|
||||||
{{ segment.text }}
|
|
||||||
</span>
|
|
||||||
<div v-if="tooltip" class="tooltip-wrapper">
|
|
||||||
<div class="info-icon" @click="toggleTooltip">
|
|
||||||
<svg viewBox="0 0 20 20" fill="currentColor" class="info-svg">
|
|
||||||
<path
|
|
||||||
fill-rule="evenodd"
|
|
||||||
d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z"
|
|
||||||
clip-rule="evenodd"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
<div v-show="showTooltip" class="tooltip-content">
|
|
||||||
{{ tooltip }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div class="switch-control">
|
|
||||||
<label class="switch">
|
|
||||||
<input v-model="value" type="checkbox" class="switch-input" />
|
|
||||||
<span class="switch-slider">
|
|
||||||
<span class="switch-button" />
|
|
||||||
</span>
|
|
||||||
</label>
|
|
||||||
<div v-if="activeText || inactiveText" class="switch-text">
|
|
||||||
{{ value ? activeText : inactiveText }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { ref } from 'vue'
|
|
||||||
|
|
||||||
defineProps<{
|
|
||||||
tooltip?: string
|
|
||||||
activeText?: string
|
|
||||||
inactiveText?: string
|
|
||||||
segments?: { text: string; style: string }[]
|
|
||||||
}>()
|
|
||||||
|
|
||||||
const value = defineModel<boolean>()
|
|
||||||
const showTooltip = ref(false)
|
|
||||||
|
|
||||||
const toggleTooltip = () => {
|
|
||||||
showTooltip.value = !showTooltip.value
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.switch-container {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
margin-bottom: 1.5rem;
|
|
||||||
border: 1px solid var(--color-border);
|
|
||||||
border-radius: var(--radius-md);
|
|
||||||
padding: 1rem;
|
|
||||||
background: var(--color-background-secondary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.switch-label-wrapper {
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.switch-label-text {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.5rem;
|
|
||||||
font-size: 0.875rem;
|
|
||||||
font-weight: 500;
|
|
||||||
color: var(--color-text-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.tooltip-wrapper {
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.info-icon {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
border-radius: var(--radius-round);
|
|
||||||
padding: 2px;
|
|
||||||
width: 20px;
|
|
||||||
height: 20px;
|
|
||||||
color: var(--color-text-secondary);
|
|
||||||
transition: var(--transition-fast);
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.info-icon:hover {
|
|
||||||
color: var(--color-accent);
|
|
||||||
background: var(--color-background-secondary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.info-svg {
|
|
||||||
width: 16px;
|
|
||||||
height: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tooltip-content {
|
|
||||||
position: absolute;
|
|
||||||
top: 100%;
|
|
||||||
left: 0;
|
|
||||||
z-index: 1000;
|
|
||||||
border: 1px solid var(--color-border);
|
|
||||||
border-radius: var(--radius-md);
|
|
||||||
padding: 0.75rem;
|
|
||||||
min-width: 200px;
|
|
||||||
max-width: 300px;
|
|
||||||
font-size: 0.75rem;
|
|
||||||
color: var(--color-text-primary);
|
|
||||||
background: var(--color-surface-elevated);
|
|
||||||
box-shadow: var(--shadow-lg);
|
|
||||||
line-height: 1.4;
|
|
||||||
}
|
|
||||||
|
|
||||||
.switch-control {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.75rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.switch {
|
|
||||||
position: relative;
|
|
||||||
display: inline-block;
|
|
||||||
width: 44px;
|
|
||||||
height: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.switch-input {
|
|
||||||
width: 0;
|
|
||||||
height: 0;
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.switch-slider {
|
|
||||||
position: absolute;
|
|
||||||
inset: 0;
|
|
||||||
border-radius: 24px;
|
|
||||||
background: linear-gradient(180deg, #d0d3d9 0%, #c0c4cc 100%);
|
|
||||||
box-shadow: inset 0 1px 3px rgb(0 0 0 / 15%);
|
|
||||||
transition: all var(--transition-medium);
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.switch-button {
|
|
||||||
position: absolute;
|
|
||||||
bottom: 2px;
|
|
||||||
left: 2px;
|
|
||||||
border-radius: var(--radius-round);
|
|
||||||
width: 17px;
|
|
||||||
height: 17px;
|
|
||||||
background: linear-gradient(180deg, #ffffff 0%, #f5f5f5 100%);
|
|
||||||
box-shadow:
|
|
||||||
0 2px 6px rgb(0 0 0 / 20%),
|
|
||||||
0 1px 2px rgb(0 0 0 / 10%);
|
|
||||||
transition: all var(--transition-medium);
|
|
||||||
content: '';
|
|
||||||
}
|
|
||||||
|
|
||||||
.switch-input:checked + .switch-slider {
|
|
||||||
background: var(--color-accent);
|
|
||||||
box-shadow:
|
|
||||||
inset 0 1px 3px rgb(0 0 0 / 10%),
|
|
||||||
0 2px 8px color-mix(in srgb, var(--color-accent), transparent 30%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.switch-input:checked + .switch-slider .switch-button {
|
|
||||||
transform: translateX(23px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.switch-input:focus + .switch-slider {
|
|
||||||
box-shadow: 0 0 0 2px rgb(0 122 255 / 20%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.switch-text {
|
|
||||||
min-width: 50px;
|
|
||||||
font-size: 0.875rem;
|
|
||||||
font-weight: 500;
|
|
||||||
color: var(--color-text-secondary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.switch-input:checked ~ .switch-text {
|
|
||||||
color: var(--color-accent);
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,14 +1,14 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="empty-page">
|
<div class="flex h-full items-center justify-center p-8">
|
||||||
<div class="empty-container">
|
<div class="flex max-w-[400px] flex-col items-center text-center">
|
||||||
<div class="empty-icon">
|
<div class="mb-6">
|
||||||
<FolderOpenIcon class="icon" />
|
<FolderOpenIcon class="h-[64px] w-[64px] text-secondary" />
|
||||||
</div>
|
</div>
|
||||||
<div class="empty-content">
|
<div class="flex flex-col gap-2">
|
||||||
<h3 class="empty-title">
|
<h3 class="mb-2 text-xl font-semibold text-main">
|
||||||
{{ t('pages.manage.empty.noData') }}
|
{{ t('pages.manage.empty.noData') }}
|
||||||
</h3>
|
</h3>
|
||||||
<p class="empty-description">
|
<p class="text-sm leading-[1.5] text-secondary">
|
||||||
{{ t('pages.manage.empty.noDataDesc') }}
|
{{ t('pages.manage.empty.noDataDesc') }}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -22,40 +22,3 @@ import { useI18n } from 'vue-i18n'
|
|||||||
|
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="stylus" scoped>
|
|
||||||
.empty-page
|
|
||||||
height 100%
|
|
||||||
display flex
|
|
||||||
align-items center
|
|
||||||
justify-content center
|
|
||||||
padding 2rem
|
|
||||||
|
|
||||||
.empty-container
|
|
||||||
display flex
|
|
||||||
flex-direction column
|
|
||||||
align-items center
|
|
||||||
text-align center
|
|
||||||
max-width 400px
|
|
||||||
|
|
||||||
.empty-icon
|
|
||||||
margin-bottom 1.5rem
|
|
||||||
|
|
||||||
.icon
|
|
||||||
width 64px
|
|
||||||
height 64px
|
|
||||||
color var(--color-text-secondary)
|
|
||||||
|
|
||||||
.empty-content
|
|
||||||
.empty-title
|
|
||||||
font-size 1.25rem
|
|
||||||
font-weight 600
|
|
||||||
color var(--color-text-primary)
|
|
||||||
margin 0 0 0.5rem 0
|
|
||||||
|
|
||||||
.empty-description
|
|
||||||
font-size 0.875rem
|
|
||||||
color var(--color-text-secondary)
|
|
||||||
margin 0
|
|
||||||
line-height 1.5
|
|
||||||
</style>
|
|
||||||
|
|||||||
@@ -1,52 +1,60 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="manage-container">
|
<div
|
||||||
|
class="relative z-1 flex h-full w-full flex-col items-center justify-start gap-2 rounded-xl border-none p-2 shadow-sm"
|
||||||
|
>
|
||||||
<!-- Header Card -->
|
<!-- Header Card -->
|
||||||
<div class="manage-card header-card">
|
<div class="flex w-full items-center justify-between gap-4 rounded-xl border border-border-secondary p-0 shadow-sm">
|
||||||
<div class="card-header">
|
<div class="flex flex-wrap items-center gap-1 p-1 max-md:justify-center max-md:text-center">
|
||||||
<div class="header-content">
|
<div class="flex h-[34px] w-[34px] shrink-0 items-center justify-center rounded-md bg-bg-secondary">
|
||||||
<div class="header-icon">
|
<img :src="`./assets/${currentPagePicBedConfig.picBedName}.webp`" class="h-[24px] w-[24px] object-contain" />
|
||||||
<img :src="`./assets/${currentPagePicBedConfig.picBedName}.webp`" class="header-icon-img" />
|
|
||||||
</div>
|
</div>
|
||||||
<div class="header-text">
|
<div class="flex flex-row items-center justify-center gap-2 max-md:text-center">
|
||||||
<h2 class="header-title">
|
<h2 class="m-0 text-xl font-bold tracking-tight text-main">
|
||||||
{{ supportedPicBedList[currentPagePicBedConfig.picBedName].name }}
|
{{ supportedPicBedList[currentPagePicBedConfig.picBedName].name }}
|
||||||
</h2>
|
</h2>
|
||||||
<p class="header-subtitle">
|
<p class="m-0 text-sm font-semibold text-secondary">
|
||||||
{{ menuTitleMap[currentPicBedName] }}
|
{{ menuTitleMap[currentPicBedName] }}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="header-actions">
|
<div class="mr-2 flex items-center justify-center gap-3">
|
||||||
<button class="action-button secondary" @click="openPicBedUrl">
|
<CustomButton
|
||||||
<ExternalLinkIcon class="button-icon" />
|
type="secondary"
|
||||||
{{ t('pages.manage.main.openPicBedUrl') }}
|
:text="t('pages.manage.main.openPicBedUrl')"
|
||||||
</button>
|
:icon="ExternalLinkIcon"
|
||||||
<button
|
@click="openPicBedUrl"
|
||||||
|
/>
|
||||||
|
<CustomButton
|
||||||
v-if="showNewIconList.includes(currentPicBedName)"
|
v-if="showNewIconList.includes(currentPicBedName)"
|
||||||
class="action-button primary"
|
type="secondary"
|
||||||
|
:text="t('pages.manage.main.newBucket')"
|
||||||
|
:icon="PlusIcon"
|
||||||
@click="openNewBucketDrawer"
|
@click="openNewBucketDrawer"
|
||||||
>
|
/>
|
||||||
<PlusIcon class="button-icon" />
|
|
||||||
{{ t('pages.manage.main.newBucket') }}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Main Content Card -->
|
<!-- Main Content Card -->
|
||||||
<div class="manage-card main-card">
|
<div
|
||||||
<div class="main-layout">
|
class="flex w-full flex-1 items-center gap-4 overflow-hidden rounded-xl border border-border-secondary p-2 shadow-md"
|
||||||
<div class="sidebar" :style="{ width: sidebarWidth + 'px' }">
|
>
|
||||||
<div class="sidebar-header">
|
<div class="flex h-full w-full">
|
||||||
<h3 class="sidebar-title">
|
<div
|
||||||
|
class="flex min-h-0 max-w-[400px] min-w-[120px] flex-col border-r-2 border-r-border transition-all duration-100 ease-out"
|
||||||
|
:style="{ width: sidebarWidth + 'px' }"
|
||||||
|
>
|
||||||
|
<div class="shrink-0 border-b-2 border-b-border-secondary p-2">
|
||||||
|
<h3 class="m-0 text-center text-sm font-semibold text-secondary">
|
||||||
{{ menuTitleMap[currentPicBedName] }}
|
{{ menuTitleMap[currentPicBedName] }}
|
||||||
</h3>
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="sidebar-content">
|
<div class="min-h-0 flex-1 overflow-y-auto p-2">
|
||||||
<div v-if="isLoadingBucketList" class="loading-container">
|
<div v-if="isLoadingBucketList" class="flex flex-col items-center justify-center gap-2 p-8">
|
||||||
<div class="loading-spinner" />
|
<div
|
||||||
<span class="loading-text">{{ t('pages.manage.main.loading') }}</span>
|
class="h-[25px] w-[25px] animate-spin rounded-full border-3 border-t-2 border-border border-t-accent"
|
||||||
|
/>
|
||||||
|
<span class="text-sm font-semibold text-secondary">{{ t('pages.manage.main.loading') }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="menu-list">
|
<div v-else class="menu-list">
|
||||||
<div
|
<div
|
||||||
@@ -56,41 +64,54 @@
|
|||||||
:class="{ active: item === currentSelectedBucket }"
|
:class="{ active: item === currentSelectedBucket }"
|
||||||
@click="handleSelectMenu(item)"
|
@click="handleSelectMenu(item)"
|
||||||
>
|
>
|
||||||
<FolderIcon
|
<span
|
||||||
v-if="currentSelectedBucket === item && currentPicBedName !== 'github'"
|
class="group/badge overflow-hidden text-sm font-medium text-ellipsis whitespace-nowrap text-secondary"
|
||||||
class="menu-icon active"
|
>
|
||||||
/>
|
<div class="min-w-0 flex-1 overflow-hidden">
|
||||||
<FolderIcon v-else-if="currentPicBedName !== 'github'" class="menu-icon" />
|
<div
|
||||||
<GitBranchIcon v-else-if="currentPicBedName === 'github'" class="menu-icon" />
|
class="flex overflow-hidden text-ellipsis whitespace-nowrap group-hover/badge:w-fit group-hover/badge:animate-[badge-scroll_5s_linear_infinite] group-hover/badge:text-clip"
|
||||||
<span class="menu-text" :title="item">
|
>
|
||||||
{{ truncateText(item, currentPicBedName) }}
|
<span class="leading-none whitespace-nowrap group-hover/badge:pr-[20px]">{{ item }}</span>
|
||||||
|
<span class="hidden leading-none whitespace-nowrap group-hover/badge:block">{{ item }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="sidebar-footer">
|
<div class="border-t border-t-border-secondary p-2">
|
||||||
<div class="footer-actions">
|
<div class="flex flex-col gap-1">
|
||||||
<button class="footer-action-item" @click="switchPicBed('main')">
|
<CustomButton
|
||||||
<HomeIcon class="action-icon" />
|
type="secondary"
|
||||||
<span class="action-text">{{ t('pages.manage.main.backToHome') }}</span>
|
:text="t('pages.manage.main.backToHome')"
|
||||||
</button>
|
:icon="HomeIcon"
|
||||||
<button class="footer-action-item" @click="changePicBed">
|
class="border-none"
|
||||||
<ArrowLeftRightIcon class="action-icon" />
|
@click="switchPicBed('main')"
|
||||||
<span class="action-text">{{ t('pages.manage.main.switchPicBed') }}</span>
|
/>
|
||||||
</button>
|
<CustomButton
|
||||||
<button class="footer-action-item" @click="openBucketPageSetting">
|
type="secondary"
|
||||||
<SettingsIcon class="action-icon" />
|
:text="t('pages.manage.main.switchPicBed')"
|
||||||
<span class="action-text">{{ t('pages.manage.main.settings') }}</span>
|
:icon="ArrowLeftRightIcon"
|
||||||
</button>
|
class="border-none"
|
||||||
|
@click="changePicBed"
|
||||||
|
/>
|
||||||
|
<CustomButton
|
||||||
|
type="secondary"
|
||||||
|
:text="t('pages.manage.main.settings')"
|
||||||
|
:icon="SettingsIcon"
|
||||||
|
class="border-none"
|
||||||
|
@click="openBucketPageSetting"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Resize Handle -->
|
<!-- Resize Handle -->
|
||||||
<div class="resize-handle" @mousedown="startResize">
|
<div
|
||||||
<div class="resize-line" />
|
class="group/resize relative flex w-[4px] shrink-0 cursor-col-resize items-center justify-center bg-transparent hover:bg-accent/70"
|
||||||
</div>
|
@mousedown="startResize"
|
||||||
|
></div>
|
||||||
|
|
||||||
<div class="content-area">
|
<div class="content-area">
|
||||||
<router-view />
|
<router-view />
|
||||||
@@ -106,25 +127,23 @@
|
|||||||
enter-from-class="opacity-0"
|
enter-from-class="opacity-0"
|
||||||
leave-to-class="opacity-0"
|
leave-to-class="opacity-0"
|
||||||
>
|
>
|
||||||
<div v-if="picBedSwitchDialogVisible" class="dialog-overlay" @click="picBedSwitchDialogVisible = false">
|
<CustomModal
|
||||||
<div class="dialog-container" @click.stop>
|
v-if="picBedSwitchDialogVisible"
|
||||||
<div class="dialog-header">
|
v-model:visible="picBedSwitchDialogVisible"
|
||||||
<h3 class="dialog-title">
|
:title="t('pages.manage.main.switchPicBed')"
|
||||||
{{ t('pages.manage.main.switchPicBed') }}
|
>
|
||||||
</h3>
|
<div class="no-scrollbar h-full w-full overflow-auto p-8">
|
||||||
<button class="dialog-close" @click="picBedSwitchDialogVisible = false">
|
<div class="grid grid-cols-[repeat(auto-fill,minmax(200px,1fr))] gap-4">
|
||||||
<XIcon class="close-icon" />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div class="dialog-content">
|
|
||||||
<div class="choice-cos">
|
|
||||||
<!-- Back to main card -->
|
<!-- Back to main card -->
|
||||||
<div class="picbed-card main-card" @click="switchPicBed('main')">
|
<div
|
||||||
<div class="card-icon">
|
class="relative flex cursor-pointer flex-col items-center rounded-lg border-2 border-success/80 bg-bg-secondary p-6 transition-all duration-fast ease-apple"
|
||||||
<HomeIcon class="main-icon" />
|
@click="switchPicBed('main')"
|
||||||
|
>
|
||||||
|
<div class="mb-3 flex h-[40px] w-[40px] items-center justify-center">
|
||||||
|
<HomeIcon class="h-[24px] w-[24px] text-main" />
|
||||||
</div>
|
</div>
|
||||||
<div class="card-content">
|
<div class="text-center">
|
||||||
<div class="card-title main-title">
|
<div class="text-sm font-semibold text-main">
|
||||||
{{ $t('pages.manage.main.backToHome') }}
|
{{ $t('pages.manage.main.backToHome') }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -134,26 +153,25 @@
|
|||||||
<div
|
<div
|
||||||
v-for="(config, alias) in allPicBedConfigure"
|
v-for="(config, alias) in allPicBedConfigure"
|
||||||
:key="String(alias)"
|
:key="String(alias)"
|
||||||
class="picbed-card"
|
class="relative flex cursor-pointer flex-col items-center rounded-lg border-2 border-border/80 bg-bg-secondary p-6 transition-all duration-fast ease-apple [.active]:border-accent"
|
||||||
:class="{ active: String(alias) === currentAlias }"
|
:class="{ active: String(alias) === currentAlias }"
|
||||||
@click="switchPicBed(String(alias))"
|
@click="switchPicBed(String(alias))"
|
||||||
>
|
>
|
||||||
<div class="card-icon">
|
<div class="mb-3 flex h-[40px] w-[40px] items-center justify-center">
|
||||||
<img :src="`./assets/${config.picBedName}.webp`" class="picbed-icon" />
|
<img :src="`./assets/${config.picBedName}.webp`" class="h-[32px] w-[32px] object-contain" />
|
||||||
</div>
|
</div>
|
||||||
<div class="card-content">
|
<div class="text-center">
|
||||||
<div class="card-title">
|
<div class="text-sm font-semibold text-main">
|
||||||
{{ config.alias }}
|
{{ config.alias }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="String(alias) === currentAlias" class="check-icon">
|
<div v-if="String(alias) === currentAlias" class="absolute top-2 right-2 h-[20px] w-[20px] text-accent">
|
||||||
<CheckIcon />
|
<CheckIcon />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</CustomModal>
|
||||||
</div>
|
|
||||||
</transition>
|
</transition>
|
||||||
|
|
||||||
<!-- New Bucket Drawer -->
|
<!-- New Bucket Drawer -->
|
||||||
@@ -268,17 +286,17 @@ import {
|
|||||||
CheckIcon,
|
CheckIcon,
|
||||||
ChevronDownIcon,
|
ChevronDownIcon,
|
||||||
ExternalLinkIcon,
|
ExternalLinkIcon,
|
||||||
FolderIcon,
|
|
||||||
GitBranchIcon,
|
|
||||||
HomeIcon,
|
HomeIcon,
|
||||||
PlusIcon,
|
PlusIcon,
|
||||||
SettingsIcon,
|
SettingsIcon,
|
||||||
XIcon,
|
XIcon,
|
||||||
} from 'lucide-vue-next'
|
} from 'lucide-vue-next'
|
||||||
import { computed, onBeforeMount, reactive, ref, watch } from 'vue'
|
import { 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'
|
||||||
|
|
||||||
|
import CustomButton from '@/components/common/CustomButton.vue'
|
||||||
|
import CustomModal from '@/components/common/CustomModal.vue'
|
||||||
import useMessage from '@/hooks/useMessage'
|
import useMessage from '@/hooks/useMessage'
|
||||||
import { useManageStore } from '@/manage/store/manageStore'
|
import { useManageStore } from '@/manage/store/manageStore'
|
||||||
import { supportedPicBedList } from '@/manage/utils/constants'
|
import { supportedPicBedList } from '@/manage/utils/constants'
|
||||||
@@ -309,46 +327,6 @@ 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(
|
watch(
|
||||||
route,
|
route,
|
||||||
async newRoute => {
|
async newRoute => {
|
||||||
|
|||||||
@@ -39,6 +39,10 @@
|
|||||||
tag="div"
|
tag="div"
|
||||||
class="flex max-w-[calc(100%-300px)] flex-wrap items-center gap-[0.2rem] [.has-many]:max-w-[300px]"
|
class="flex max-w-[calc(100%-300px)] flex-wrap items-center gap-[0.2rem] [.has-many]:max-w-[300px]"
|
||||||
:class="{ 'has-many': favoritePicbeds.length >= 4 }"
|
:class="{ 'has-many': favoritePicbeds.length >= 4 }"
|
||||||
|
enter-active-class="transition-all duration-200 ease-apple"
|
||||||
|
leave-active-class="transition-all duration-200 ease-apple"
|
||||||
|
enter-from-class="opacity-0"
|
||||||
|
leave-to-class="opacity-0"
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
v-for="picbedType in favoritePicbeds"
|
v-for="picbedType in favoritePicbeds"
|
||||||
|
|||||||
Reference in New Issue
Block a user