🚧 WIP(custom): optimzie setting page and add several custom components

This commit is contained in:
Kuingsmile
2026-01-22 11:52:32 +08:00
parent 8357d3acb5
commit d997348c3a
24 changed files with 1310 additions and 1566 deletions

View File

@@ -8,7 +8,8 @@
- windows下新增便携模式无需安装即可运行数据存储在程序目录下的`data`文件夹中支持自动更新Linux下新增`rpm`安装包 - windows下新增便携模式无需安装即可运行数据存储在程序目录下的`data`文件夹中支持自动更新Linux下新增`rpm`安装包
- 新增自定义主题功能,主题仓库[PicList ThemeHub](https://github.com/Kuingsmile/PicList-ThemeHub) - 新增自定义主题功能,主题仓库[PicList ThemeHub](https://github.com/Kuingsmile/PicList-ThemeHub)
- 12个内置主题供选择如bilibili、二次元、极夜紫、gemini等风格 - 12个内置主题供选择如bilibili、二次元、极夜紫等风格
- 重构了几乎全部页面优化了数十项UI细节问题
- 相册页面多项优化支持显示已选择图片数量匹配的url列表和记忆过滤器打开状态 - 相册页面多项优化支持显示已选择图片数量匹配的url列表和记忆过滤器打开状态
- 插件页面现在可以浏览所有插件列表,查看详情和安装 - 插件页面现在可以浏览所有插件列表,查看详情和安装
- 新增教学引导页面,首次运行时会自动弹出 - 新增教学引导页面,首次运行时会自动弹出

View File

@@ -8,7 +8,8 @@
- Added portable mode on Windows, allowing the program to run without installation. Data is stored in the `data` folder within the program directory, and automatic updates are supported. Added `rpm` installation package for Linux - Added portable mode on Windows, allowing the program to run without installation. Data is stored in the `data` folder within the program directory, and automatic updates are supported. Added `rpm` installation package for Linux
- Added custom theme functionality, with a theme repository available at [PicList ThemeHub](https://github.com/Kuingsmile/PicList-ThemeHub) - Added custom theme functionality, with a theme repository available at [PicList ThemeHub](https://github.com/Kuingsmile/PicList-ThemeHub)
- 12 built-in themes available, such as bilibili, ACG, Night Purple, gemini styles - 12 built-in themes available, such as bilibili, ACG, Night Purple styles
- Refactored almost all pages, optimizing dozens of UI detail issues
- Multiple optimizations on the album page, supporting display of the number of selected images, matching URL list, and remembering filter open state - Multiple optimizations on the album page, supporting display of the number of selected images, matching URL list, and remembering filter open state
- Plugin page now allows browsing of all plugin lists, viewing details, and installation - Plugin page now allows browsing of all plugin lists, viewing details, and installation
- Added tutorial guide page, which automatically pops up on first run - Added tutorial guide page, which automatically pops up on first run

View File

@@ -0,0 +1,69 @@
<template>
<button
:disabled="disabled"
class="flex min-w-fit cursor-pointer items-center justify-center gap-2 rounded-md px-4 py-2 text-sm font-medium transition-all duration-fast ease-apple not-disabled:hover:shadow-sm disabled:cursor-not-allowed disabled:opacity-50"
:class="classVar"
:data-active="active"
@click="emit('click')"
>
<slot name="icon">
<component :is="icon" v-if="icon" :size="iconSize" />
</slot>
<slot>
<span
:class="textClassVar"
:data-active="active"
class="[.primary] text-sm leading-[1.4] font-semibold text-secondary"
>{{ text }}</span
>
</slot>
<slot name="extra"> </slot>
</button>
</template>
<script setup lang="ts">
import { computed } from 'vue'
const {
text,
disabled,
active = false,
icon,
iconSize = 16,
type = 'primary',
} = defineProps<{
text: string
icon: any
active?: boolean
iconSize?: number
disabled?: boolean
type?: 'primary' | 'secondary' | 'tab'
}>()
const textClassVar = computed(() => {
switch (type) {
case 'primary':
return 'text-white'
case 'secondary':
return 'text-main'
case 'tab':
return active ? 'text-white' : 'text-secondary'
default:
return ''
}
})
const classVar = computed(() => {
switch (type) {
case 'primary':
return 'bg-accent text-white not-disabled:hover:bg-accent-hover! not-disabled:hover:-translate-y-px'
case 'secondary':
return 'border border-border! bg-bg-secondary! text-main! not-disabled:hover:bg-surface-elevated! not-disabled:hover:-translate-y-px'
case 'tab':
return 'flex-1 text-secondary not-disabled:data-[active=false]:hover:bg-accent/30! data-[active=true]:text-white data-[active=true]:bg-accent!'
default:
return ''
}
})
const emit = defineEmits<(e: 'click') => void>()
</script>

View File

@@ -0,0 +1,45 @@
<template>
<div class="flex flex-col">
<label class="mb-2 text-sm font-semibold text-secondary">{{ title }}</label>
<div class="relative w-full">
<input
v-model="modelValue"
:type="type"
class="box-border w-full rounded-md border border-border bg-bg-tertiary p-3 pr-10 text-sm text-main transition-all duration-200 ease-apple focus:border-accent focus:outline-none"
:placeholder="placeholder"
/>
<button
v-if="isPassword"
type="button"
class="absolute top-1/2 right-3 flex -translate-y-1/2 items-center justify-center text-main"
@click="type = type === 'password' ? 'text' : 'password'"
>
<EyeIcon v-if="type === 'password'" class="text-accent" :size="16" />
<EyeClosedIcon v-else class="text-accent" :size="16" />
</button>
</div>
</div>
</template>
<script setup lang="ts">
import { EyeClosedIcon, EyeIcon } from 'lucide-vue-next'
import { onMounted, ref } from 'vue'
const modelValue = defineModel<string>()
const type = ref('text')
const {
isPassword = false,
title,
placeholder,
} = defineProps<{
isPassword?: boolean
title: string
placeholder: string
}>()
onMounted(() => {
if (isPassword) {
type.value = 'password'
}
})
</script>

View File

@@ -0,0 +1,36 @@
<template>
<div
class="flex cursor-pointer items-center gap-4 rounded-lg border border-border bg-bg-secondary p-4 transition-all duration-200 ease-apple hover:-translate-y-px hover:border-accent hover:shadow-md"
@click="emit('click')"
>
<div class="flex h-[25px] w-[25px] shrink-0 items-center justify-center rounded-lg bg-accent text-white">
<component :is="icon" :size="15" />
</div>
<div class="flex-1">
<h4 class="text-[0.925rem] leading-[1.4] font-semibold text-secondary">{{ title }}</h4>
<slot name="description">
<p v-if="description" class="mt-1 text-xs font-medium text-secondary">{{ description }}</p>
</slot>
</div>
<div v-if="!noarrow" class="text-main transition-all duration-200 ease-apple hover:translate-x-1 hover:text-accent">
<ChevronRightIcon :size="16" />
</div>
<slot name="extra"></slot>
</div>
</template>
<script setup lang="ts">
import { ChevronRightIcon } from 'lucide-vue-next'
const {
title,
icon,
description = '',
noarrow = false,
} = defineProps<{
icon: any
title: string
description?: string
noarrow?: boolean
}>()
const emit = defineEmits<(e: 'click') => void>()
</script>

View File

@@ -0,0 +1,35 @@
<template>
<div class="mb-3 flex items-center gap-2 text-sm font-medium text-main">
<slot name="icon">
<component :is="icon" v-if="icon" :size="iconSize" class="text-accent" />
</slot>
<span class="text-[0.925rem] leading-[1.4] font-semibold text-secondary">{{ title }}</span>
</div>
<select
v-model="modelValue"
class="border-box w-full rounded-md border border-border bg-bg-tertiary p-3 text-sm text-main transition-all duration-200 ease-apple focus:border-accent focus:outline-none"
>
<template v-if="selectList.length > 0">
<option v-for="item in selectList" :key="item.value" :value="item.value">
{{ item.label }}
</option>
</template>
<slot name="extra"></slot>
</select>
</template>
<script setup lang="ts">
const modelValue = defineModel<string>()
const {
title,
icon,
iconSize = 18,
selectList = [],
} = defineProps<{
title: string
icon: any
selectList?: { value: string; label: string }[]
iconSize?: number
}>()
</script>

View File

@@ -1,22 +1,35 @@
<template> <template>
<label <label
class="flex cursor-pointer items-center gap-4 rounded-lg border border-border bg-bg p-4 transition-all duration-200 ease-apple hover:border-accent hover:bg-surface hover:shadow-sm" class="flex cursor-pointer items-center gap-4 rounded-lg border border-border p-4 transition-all duration-200 ease-apple hover:border-accent hover:bg-surface hover:shadow-sm"
:class="noBorder ? 'border-none' : ''"
> >
<input v-model="modelValue" type="checkbox" class="peer hidden" /> <input v-model="modelValue" type="checkbox" class="peer hidden" />
<span <span
class="bg-linear-180-r relative h-[28px] w-[52px] shrink-0 rounded-full bg-gray-400/80 shadow-sm transition-all duration-medium ease-standard peer-checked:bg-accent before:absolute before:top-[3px] before:left-[3px] before:h-[22px] before:w-[22px] before:rounded-full before:bg-white before:shadow-sm before:transition-all before:duration-200 before:ease-apple before:content-[''] peer-checked:before:translate-x-[24px]" class="bg-linear-180-r relative shrink-0 rounded-full bg-gray-400/80 shadow-sm transition-all duration-medium ease-standard peer-checked:bg-accent before:absolute before:rounded-full before:bg-white before:shadow-sm before:transition-all before:duration-200 before:ease-apple before:content-[''] peer-checked:before:translate-x-[24px]"
:class="
small
? 'h-[21px] w-[44px] before:top-[2px] before:left-[2px] before:h-[17px] before:w-[17px]'
: 'h-[28px] w-[52px] before:top-[3px] before:left-[3px] before:h-[22px] before:w-[22px]'
"
/> />
<div class="flex flex-1 flex-col gap-1"> <div class="flex flex-1 flex-col gap-1">
<span class="text-[0.925rem] leading-[1.4] font-semibold text-secondary">{{ props.title }}</span> <span class="text-[0.925rem] leading-[1.4] font-semibold text-secondary">{{ title }}</span>
<span class="text-xs text-secondary/90">{{ props.description }}</span> <span class="text-xs text-secondary/90">{{ description }}</span>
</div> </div>
</label> </label>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
const modelValue = defineModel<boolean>() const modelValue = defineModel<boolean>()
const props = defineProps<{ const {
title = '',
description = '',
noBorder = false,
small = false,
} = defineProps<{
noBorder?: boolean
title?: string title?: string
description?: string description?: string
small?: boolean
}>() }>()
</script> </script>

View File

@@ -0,0 +1,84 @@
<template>
<div :class="tight ? 'mb-0' : 'mb-3'" class="flex items-center gap-2 text-sm font-medium text-main">
<slot name="icon">
<component :is="icon" v-if="icon" :size="iconSize" class="text-accent" />
</slot>
<span class="text-[0.925rem] leading-[1.4] font-semibold text-secondary">{{ title }}</span>
</div>
<div ref="dropdownRef" class="custom-multiselect relative">
<button
class="flex h-[28px] w-full cursor-pointer items-center justify-between rounded-md border border-border-secondary px-2 py-1.5 text-sm leading-[1.4] text-main transition-all duration-fast ease-apple hover:border-accent-hover focus:[.active]:border-accent-hover focus:[.active]:shadow-sm"
:class="{ active: dropDownOpen }"
@click="toggleDropdown($event)"
>
<span v-if="choosed?.length === 0" class="text-center text-xs font-semibold text-secondary">{{
zeroPlaceholder
}}</span>
<span v-else class="text-center text-xs font-semibold text-secondary"
>{{ choosed?.length }} {{ t('pages.gallery.selected') }}</span
>
<ChevronDownIcon :size="16" />
</button>
<div
v-show="dropDownOpen"
class="multiselect-dropdown shadow-lg; fixed z-1000 mt-[2px] no-scrollbar max-h-[280px] min-w-[185px] overflow-y-auto rounded-md border border-border-secondary bg-bg-tertiary px-2 py-1.5 text-main"
>
<label
v-for="item in allList"
:key="item.type"
class="flex min-h-[unset] cursor-pointer items-center justify-between px-2 py-1 text-sm leading-[1.4] transition-all duration-fast ease-apple hover:bg-accent/50"
>
<input v-model="choosed" type="checkbox" :value="item.type" class="m-0" />
{{ item.name }}
</label>
</div>
</div>
</template>
<script setup lang="ts">
import { onClickOutside } from '@vueuse/core'
import { ChevronDownIcon } from 'lucide-vue-next'
import { nextTick, ref } from 'vue'
import { useI18n } from 'vue-i18n'
const choosed = defineModel<string[]>('choosed')
const { t } = useI18n()
const dropdownRef = ref(null)
const dropDownOpen = ref(false)
function toggleDropdown(event?: Event) {
dropDownOpen.value = !dropDownOpen.value
if (dropDownOpen.value && event) {
nextTick(() => {
const trigger = event.target as HTMLElement
const dropdown = trigger.parentElement?.querySelector('.multiselect-dropdown') as HTMLElement
if (dropdown && trigger) {
const rect = trigger.getBoundingClientRect()
dropdown.style.top = `${rect.bottom + 2}px`
dropdown.style.left = `${rect.left}px`
dropdown.style.width = `${Math.max(rect.width, 200)}px`
}
})
}
}
onClickOutside(dropdownRef, () => {
dropDownOpen.value = false
})
const {
tight = true,
title,
icon = null,
iconSize = 18,
zeroPlaceholder,
allList,
} = defineProps<{
tight?: boolean
title: string
icon?: any
iconSize?: number
zeroPlaceholder: string
allList: any
}>()
</script>

View File

@@ -5,7 +5,7 @@
<div <div
class="bg-linear-150-r m-0 border-b border-border bg-accent/10 px-4 pt-3.5 pb-2 text-sm font-semibold tracking-wide text-secondary" class="bg-linear-150-r m-0 border-b border-border bg-accent/10 px-4 pt-3.5 pb-2 text-sm font-semibold tracking-wide text-secondary"
> >
{{ $t(titleList[key]) }} {{ titleList[key] }}
</div> </div>
<div class="grid grid-cols-[repeat(auto-fill,minmax(240px,1fr))] gap-0 py-2"> <div class="grid grid-cols-[repeat(auto-fill,minmax(240px,1fr))] gap-0 py-2">
<div <div

View File

@@ -0,0 +1,11 @@
<template>
<div class="relative rounded-lg border border-border bg-bg-secondary shadow-sm" :class="p1 ? 'p-1' : 'p-4'">
<slot></slot>
</div>
</template>
<script setup lang="ts">
const { p1 = false } = defineProps<{
p1?: boolean
}>()
</script>

View File

@@ -0,0 +1,42 @@
<template>
<div class="w-full rounded-lg border border-border bg-bg-secondary p-6 shadow-sm">
<div class="mb-2 flex items-start gap-3">
<div class="mb-2 flex h-[30px] w-[30px] shrink-0 items-center justify-center rounded-lg bg-accent text-white">
<slot name="icon">
<component :is="icon" v-if="icon" :size="iconSize" />
</slot>
</div>
<div>
<slot name="title"
><h2 class="mb-2 text-lg font-semibold text-main">{{ title }}</h2></slot
>
<slot name="description">
<p v-if="description !== ''" class="mb-6 text-sm text-secondary">
{{ description }}
</p>
</slot>
</div>
</div>
<div :class="onlyOneRow ? 'grid grid-cols-1 gap-4' : 'grid grid-cols-2 gap-4 max-md:grid-cols-1'">
<slot />
</div>
<slot name="extra"></slot>
</div>
</template>
<script setup lang="ts">
const {
title,
description = '',
icon,
iconSize = 20,
onlyOneRow = false,
} = defineProps<{
title: string
description?: string
icon: any
iconSize?: number
onlyOneRow?: boolean
}>()
</script>

View File

@@ -0,0 +1,85 @@
<template>
<div :class="tight ? 'mb-0' : 'mb-3'" class="flex items-center gap-2 text-sm font-medium text-main">
<slot name="icon">
<component :is="icon" v-if="icon" :size="iconSize" class="text-accent" />
</slot>
<span class="text-[0.925rem] leading-[1.4] font-semibold text-secondary">{{ title }}</span>
</div>
<div ref="dropdownRef" class="sort-dropdown relative">
<button
class="flex h-[28px] w-full cursor-pointer items-center justify-between gap-1 rounded-md border border-border-secondary px-2 py-1.5 text-sm leading-[1.4] text-main transition-all duration-fast ease-apple hover:border-accent-hover focus:[.active]:border-accent-hover focus:[.active]:shadow-md"
:class="{ active: dropDownOpen }"
@click="toggleDropdown($event)"
>
<SortAscIcon v-if="fronticon" :size="14" />
<span class="text-center text-xs font-semibold text-secondary">{{ placeholder || modelValue }}</span>
<ChevronDownIcon :size="14" />
</button>
<div
v-show="dropDownOpen"
class="sort-options fixed z-10 mt-[2px] min-w-[150px] overflow-hidden rounded-md border border-border-secondary bg-bg-tertiary shadow-lg"
>
<button
v-for="key in keyList"
:key="key"
class="block min-h-[unset] w-full cursor-pointer border-none bg-bg-tertiary px-2 py-1 text-center text-sm leading-[1.4] text-main transition-all duration-fast ease-apple hover:bg-accent/50"
@click="selectItem(key)"
>
<slot name="item" :item="key"> {{ key }} </slot>
</button>
</div>
</div>
</template>
<script setup lang="ts">
import { onClickOutside } from '@vueuse/core'
import { ChevronDownIcon, SortAscIcon } from 'lucide-vue-next'
import { nextTick, ref } from 'vue'
const dropdownRef = ref(null)
const modelValue = defineModel<string>()
function selectItem(key: string) {
modelValue.value = key
dropDownOpen.value = false
}
const dropDownOpen = ref(false)
function toggleDropdown(event?: Event) {
dropDownOpen.value = !dropDownOpen.value
if (dropDownOpen.value && event) {
nextTick(() => {
const trigger = event.target as HTMLElement
const dropdown = trigger.parentElement?.querySelector('.sort-options') as HTMLElement
if (dropdown && trigger) {
const rect = trigger.getBoundingClientRect()
dropdown.style.top = `${rect.bottom + 2}px`
dropdown.style.left = `${rect.left}px`
dropdown.style.width = `${Math.max(rect.width, 160)}px`
}
})
}
}
onClickOutside(dropdownRef, () => {
dropDownOpen.value = false
})
const {
title,
placeholder = '',
fronticon = true,
keyList,
icon = null,
tight = true,
iconSize = 18,
} = defineProps<{
title: string
icon?: any
iconSize?: number
tight?: boolean
placeholder?: string
fronticon?: boolean
keyList: string[]
}>()
</script>

View File

@@ -914,6 +914,7 @@
"chooseSecondPicBedMode": "Choose Second Image Bed Mode", "chooseSecondPicBedMode": "Choose Second Image Bed Mode",
"chooseShowedPicBed": "Please select the image bed to display in the menu", "chooseShowedPicBed": "Please select the image bed to display in the menu",
"clipboardAndNotification": "Clipboard and Notification", "clipboardAndNotification": "Clipboard and Notification",
"controlShow": "Control Showed PicBeds",
"copySuccess": "Copy Successful: {content}", "copySuccess": "Copy Successful: {content}",
"customLinkFormat": "Custom Link Format", "customLinkFormat": "Custom Link Format",
"customLinkFormatDesc": "Set custom link output format", "customLinkFormatDesc": "Set custom link output format",
@@ -936,7 +937,7 @@
"imageProcessing": "Image Processing Settings", "imageProcessing": "Image Processing Settings",
"imageProcessingDesc": "Configure image preprocessing options before upload", "imageProcessingDesc": "Configure image preprocessing options before upload",
"isAutoListenClipboard": "Automatically listen for clipboard uploads when the software starts.", "isAutoListenClipboard": "Automatically listen for clipboard uploads when the software starts.",
"manualRname": "Manual Rename", "manualRename": "Manual Rename",
"placeholder": { "placeholder": {
"categoryFile": "File Related", "categoryFile": "File Related",
"categoryHash": "Hash Related", "categoryHash": "Hash Related",
@@ -968,7 +969,7 @@
"shortUrlServer": "Short URL Service", "shortUrlServer": "Short URL Service",
"sinkDomain": "Sink Domain", "sinkDomain": "Sink Domain",
"sinkToken": "Sink Token", "sinkToken": "Sink Token",
"timestampRname": "Timestamp Rename", "timestampRename": "Timestamp Rename",
"title": "Upload", "title": "Upload",
"uploadBehavior": "Upload Behavior", "uploadBehavior": "Upload Behavior",
"uploadProcessing": "Upload Processing", "uploadProcessing": "Upload Processing",

View File

@@ -914,6 +914,7 @@
"chooseSecondPicBedMode": "选择第二图床模式", "chooseSecondPicBedMode": "选择第二图床模式",
"chooseShowedPicBed": "请选择显示在菜单的图床", "chooseShowedPicBed": "请选择显示在菜单的图床",
"clipboardAndNotification": "剪贴板和通知", "clipboardAndNotification": "剪贴板和通知",
"controlShow": "图床显示控制",
"copySuccess": "复制成功: {content}", "copySuccess": "复制成功: {content}",
"customLinkFormat": "自定义链接格式", "customLinkFormat": "自定义链接格式",
"customLinkFormatDesc": "设置自定义的链接输出格式", "customLinkFormatDesc": "设置自定义的链接输出格式",
@@ -936,7 +937,7 @@
"imageProcessing": "图片预处理设置", "imageProcessing": "图片预处理设置",
"imageProcessingDesc": "配置上传前的图片预处理选项", "imageProcessingDesc": "配置上传前的图片预处理选项",
"isAutoListenClipboard": "软件启动时自动监听剪贴板上传", "isAutoListenClipboard": "软件启动时自动监听剪贴板上传",
"manualRname": "手动重命名", "manualRename": "手动重命名",
"placeholder": { "placeholder": {
"categoryFile": "文件相关", "categoryFile": "文件相关",
"categoryHash": "哈希相关", "categoryHash": "哈希相关",
@@ -968,7 +969,7 @@
"shortUrlServer": "短链接服务", "shortUrlServer": "短链接服务",
"sinkDomain": "Sink 域名", "sinkDomain": "Sink 域名",
"sinkToken": "Sink Token", "sinkToken": "Sink Token",
"timestampRname": "时间戳重命名", "timestampRename": "时间戳重命名",
"title": "上传", "title": "上传",
"uploadBehavior": "上传行为", "uploadBehavior": "上传行为",
"uploadProcessing": "上传处理", "uploadProcessing": "上传处理",

View File

@@ -914,6 +914,7 @@
"chooseSecondPicBedMode": "選擇第二圖床模式", "chooseSecondPicBedMode": "選擇第二圖床模式",
"chooseShowedPicBed": "請選擇顯示在菜單的圖床", "chooseShowedPicBed": "請選擇顯示在菜單的圖床",
"clipboardAndNotification": "剪貼板和通知", "clipboardAndNotification": "剪貼板和通知",
"controlShow": "控制顯示的圖床",
"copySuccess": "複製成功: {content}", "copySuccess": "複製成功: {content}",
"customLinkFormat": "自定義鏈接格式", "customLinkFormat": "自定義鏈接格式",
"customLinkFormatDesc": "設置自定義的鏈接輸出格式", "customLinkFormatDesc": "設置自定義的鏈接輸出格式",
@@ -936,7 +937,7 @@
"imageProcessing": "圖片預處理設置", "imageProcessing": "圖片預處理設置",
"imageProcessingDesc": "配置上傳前的圖片預處理選項", "imageProcessingDesc": "配置上傳前的圖片預處理選項",
"isAutoListenClipboard": "軟件啟動時自動監聽剪貼板上傳", "isAutoListenClipboard": "軟件啟動時自動監聽剪貼板上傳",
"manualRname": "手動重命名", "manualRename": "手動重命名",
"placeholder": { "placeholder": {
"categoryFile": "文件相關", "categoryFile": "文件相關",
"categoryHash": "哈希相關", "categoryHash": "哈希相關",
@@ -968,7 +969,7 @@
"shortUrlServer": "短鏈接服務", "shortUrlServer": "短鏈接服務",
"sinkDomain": "Sink 域名", "sinkDomain": "Sink 域名",
"sinkToken": "Sink Token", "sinkToken": "Sink Token",
"timestampRname": "時間戳重命名", "timestampRename": "時間戳重命名",
"title": "上傳", "title": "上傳",
"uploadBehavior": "上傳行為", "uploadBehavior": "上傳行為",
"uploadProcessing": "上傳處理", "uploadProcessing": "上傳處理",

View File

@@ -83,31 +83,12 @@
> >
<div class="mb-1 flex w-full flex-wrap items-start gap-3"> <div class="mb-1 flex w-full flex-wrap items-start gap-3">
<div class="filter-group"> <div class="filter-group">
<label class="filter-label">{{ t('pages.gallery.picBedType') }}</label> <MultiSelect
<div class="custom-multiselect relative"> v-model:choosed="choosedPicBed"
<button :title="t('pages.gallery.picBedType')"
class="flex h-[28px] w-full cursor-pointer items-center justify-between rounded-md border border-border-secondary px-2 py-1.5 text-sm leading-[1.4] text-main transition-all duration-fast ease-apple hover:border-accent-hover focus:[.active]:border-accent-hover focus:[.active]:shadow-sm" :zero-placeholder="t('pages.gallery.chooseShowedPicBed')"
:class="{ active: picBedDropdownOpen }" :all-list="filteredPicBedG"
@click="togglePicBedDropdown($event)" />
>
<span v-if="choosedPicBed.length === 0">{{ t('pages.gallery.chooseShowedPicBed') }}</span>
<span v-else>{{ choosedPicBed.length }} {{ t('pages.gallery.selected') }}</span>
<ChevronDownIcon :size="16" />
</button>
<div
v-show="picBedDropdownOpen"
class="multiselect-dropdown shadow-lg; fixed z-1000 mt-[2px] no-scrollbar max-h-[280px] min-w-[185px] overflow-y-auto rounded-md border border-border-secondary bg-bg-tertiary px-2 py-1.5 text-main"
>
<label
v-for="item in filteredPicBedG"
:key="item.type"
class="flex min-h-[unset] cursor-pointer items-center justify-between px-2 py-1 text-sm leading-[1.4] transition-all duration-fast ease-apple hover:bg-accent-hover"
>
<input v-model="choosedPicBed" type="checkbox" :value="item.type" class="m-0" />
{{ item.name }}
</label>
</div>
</div>
</div> </div>
<div class="filter-group"> <div class="filter-group">
@@ -120,55 +101,42 @@
</div> </div>
<div class="filter-group"> <div class="filter-group">
<label class="filter-label">{{ t('pages.gallery.pasteFormat') }}</label> <SingleSelect
<select v-model="pasteStyle" class="custom-select" @change="handlePasteStyleChange"> v-model="pasteStyle"
<option :title="t('pages.gallery.pasteFormat')"
v-for="(value, key) in pasteStyleMap" :fronticon="false"
:key="key" :key-list="pasteStyleList"
:value="value" >
class="bg-bg-tertiary text-sm text-main" <template #item="{ item }">
> {{ item }}
{{ key }} </template>
</option> </SingleSelect>
</select>
</div> </div>
<div class="filter-group"> <div class="filter-group">
<label class="filter-label">{{ t('pages.gallery.urlType') }}</label> <SingleSelect
<select v-model="useShortUrl" class="custom-select" @change="handleUseShortUrlChange"> v-model="useShortUrl"
<option :title="t('pages.gallery.urlType')"
v-for="(value, key) in shortURLMap" :fronticon="false"
:key="key" :key-list="shortURLList"
:value="value" >
class="bg-bg-tertiary text-sm text-main" <template #item="{ item }">
> {{ item }}
{{ key }} </template>
</option> </SingleSelect>
</select>
</div> </div>
<div class="filter-group"> <div class="filter-group">
<label class="filter-label">{{ t('pages.gallery.sort') }}</label> <SingleSelect
<div class="sort-dropdown relative"> v-model="currentSortField"
<button class="sort-button" :class="{ active: sortDropdownOpen }" @click="toggleSortDropdown($event)"> :placeholder="t(`pages.gallery.sortBy.${currentSortField}`)"
<SortAscIcon :size="14" /> :title="t('pages.gallery.sort')"
{{ t(`pages.gallery.sortBy.${currentSortField}`) }} :key-list="['name', 'ext', 'time', 'check']"
<ChevronDownIcon :size="14" /> >
</button> <template #item="{ item }">
<div {{ t(`pages.gallery.sortBy.${item}`) }}
v-show="sortDropdownOpen" </template>
class="sort-options fixed z-10 mt-[2px] min-w-[150px] overflow-hidden rounded-md border border-border-secondary bg-bg-tertiary shadow-lg" </SingleSelect>
>
<button
v-for="key in ['name', 'ext', 'time', 'check']"
:key="key"
class="block min-h-[unset] w-full cursor-pointer border-none bg-bg-tertiary px-2 py-1 text-center text-sm leading-[1.4] text-main transition-all duration-fast ease-apple hover:bg-accent-hover"
@click="sortFile(key as any)"
>
{{ t(`pages.gallery.sortBy.${key}`) }}
</button>
</div>
</div>
</div> </div>
</div> </div>
@@ -240,7 +208,7 @@
:key="componentKey" :key="componentKey"
ref="virtualScrollerRef" ref="virtualScrollerRef"
:view-mode="viewMode" :view-mode="viewMode"
class="virtual-gallery-scroller min-h-0 w-full flex-1 p-1" class="virtual-gallery-scroller min-h-0 w-full flex-1 p-3"
:items="filterList" :items="filterList"
:item-height="300" :item-height="300"
:grid-breakpoints="effectiveGridBreakpoints" :grid-breakpoints="effectiveGridBreakpoints"
@@ -596,7 +564,6 @@ import {
ListIcon, ListIcon,
RefreshCwIcon, RefreshCwIcon,
SearchIcon, SearchIcon,
SortAscIcon,
TrashIcon, TrashIcon,
XIcon, XIcon,
} from 'lucide-vue-next' } from 'lucide-vue-next'
@@ -615,6 +582,8 @@ import { useI18n } from 'vue-i18n'
import { onBeforeRouteUpdate } from 'vue-router' import { onBeforeRouteUpdate } from 'vue-router'
import ALLApi from '@/apis/allApi' import ALLApi from '@/apis/allApi'
import MultiSelect from '@/components/common/multiSelect.vue'
import SingleSelect from '@/components/common/singleSelect.vue'
import VirtualScroller from '@/components/VirtualScroller.vue' import VirtualScroller from '@/components/VirtualScroller.vue'
import useConfirm from '@/hooks/useConfirm' import useConfirm from '@/hooks/useConfirm'
import { usePicBed } from '@/hooks/useGlobal' import { usePicBed } from '@/hooks/useGlobal'
@@ -663,18 +632,10 @@ const debouncedSearchText = ref<string>('')
const debouncedSearchTextURL = ref<string>('') const debouncedSearchTextURL = ref<string>('')
const handleBarActive = useStorage<boolean>('galleryHandleBarActive', true) const handleBarActive = useStorage<boolean>('galleryHandleBarActive', true)
const pasteStyle = ref<string>('') const pasteStyle = ref<string>('')
const pasteStyleMap = { const pasteStyleList = ['markdown', 'HTML', 'URL', 'UBB', 'Custom']
Markdown: 'markdown',
HTML: 'HTML',
URL: 'URL',
UBB: 'UBB',
Custom: 'Custom',
}
const useShortUrl = ref<string>('') const useShortUrl = ref<string>('')
const shortURLMap = { const shortURLList = [t('pages.gallery.shortUrl'), t('pages.gallery.longUrl')]
[t('pages.gallery.shortUrl')]: t('pages.gallery.shortUrl'),
[t('pages.gallery.longUrl')]: t('pages.gallery.longUrl'),
}
const fileSortNameReverse = ref(false) const fileSortNameReverse = ref(false)
const fileSortTimeReverse = ref(false) const fileSortTimeReverse = ref(false)
const fileSortExtReverse = ref(false) const fileSortExtReverse = ref(false)
@@ -850,41 +811,6 @@ function onPreviewImageLoad() {
}) })
} }
function togglePicBedDropdown(event?: Event) {
picBedDropdownOpen.value = !picBedDropdownOpen.value
if (sortDropdownOpen.value) sortDropdownOpen.value = false
if (picBedDropdownOpen.value && event) {
nextTick(() => {
const trigger = event.target as HTMLElement
const dropdown = trigger.parentElement?.querySelector('.multiselect-dropdown') as HTMLElement
if (dropdown && trigger) {
const rect = trigger.getBoundingClientRect()
dropdown.style.top = `${rect.bottom + 2}px`
dropdown.style.left = `${rect.left}px`
dropdown.style.width = `${Math.max(rect.width, 200)}px`
}
})
}
}
function toggleSortDropdown(event?: Event) {
sortDropdownOpen.value = !sortDropdownOpen.value
if (picBedDropdownOpen.value) picBedDropdownOpen.value = false
if (sortDropdownOpen.value && event) {
nextTick(() => {
const trigger = event.target as HTMLElement
const dropdown = trigger.parentElement?.querySelector('.sort-options') as HTMLElement
if (dropdown && trigger) {
const rect = trigger.getBoundingClientRect()
dropdown.style.top = `${rect.bottom + 2}px`
dropdown.style.left = `${rect.left}px`
dropdown.style.width = `${Math.max(rect.width, 160)}px`
}
})
}
}
function navigateImage(direction: number) { function navigateImage(direction: number) {
const newIndex = gallerySliderControl.index + direction const newIndex = gallerySliderControl.index + direction
if (newIndex >= 0 && newIndex < filterList.value.length) { if (newIndex >= 0 && newIndex < filterList.value.length) {
@@ -1522,23 +1448,19 @@ function toggleHandleBar() {
handleBarActive.value = !handleBarActive.value handleBarActive.value = !handleBarActive.value
} }
async function handlePasteStyleChange(event: Event) { watch(pasteStyle, newVal => {
const target = event.target as HTMLSelectElement saveConfig(configPaths.settings.pasteStyle, newVal)
const val = target.value })
saveConfig(configPaths.settings.pasteStyle, val)
pasteStyle.value = val
}
function handleUseShortUrlChange(event: Event) { watch(useShortUrl, newVal => {
const target = event.target as HTMLSelectElement saveConfig(configPaths.settings.useShortUrl, newVal === t('pages.gallery.shortUrl'))
const value = target.value })
saveConfig(configPaths.settings.useShortUrl, value === t('pages.gallery.shortUrl'))
useShortUrl.value = value watch(currentSortField, () => {
} sortFile(currentSortField.value)
})
function sortFile(type: 'name' | 'time' | 'ext' | 'check') { function sortFile(type: 'name' | 'time' | 'ext' | 'check') {
sortDropdownOpen.value = false
currentSortField.value = type
switch (type) { switch (type) {
case 'name': case 'name':
fileSortNameReverse.value = !fileSortNameReverse.value fileSortNameReverse.value = !fileSortNameReverse.value

View File

@@ -22,8 +22,8 @@
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import InputBoxDialog from '@/components/InputBoxDialog.vue' import InputBoxDialog from '@/components/InputBoxDialog.vue'
import Navigation from '@/components/NavigationPage.vue' import Navigation from '@/pages/NavigationPage.vue'
import TitleBar from '@/components/ui/TitleBar.vue' import TitleBar from '@/pages/TitleBar.vue'
const $router = useRouter() const $router = useRouter()
const keepAlivePages = $router const keepAlivePages = $router

View File

@@ -233,6 +233,8 @@ import { computed, nextTick, onBeforeMount, onBeforeUnmount, reactive, Ref, ref,
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import { useRoute, useRouter } from 'vue-router' import { useRoute, useRouter } from 'vue-router'
import FirstTimeGuide from '@/components/FirstTimeGuide.vue'
import ThemeSwitcher from '@/components/ui/ThemeSwitcher.vue'
import { usePicBed } from '@/hooks/useGlobal' import { usePicBed } from '@/hooks/useGlobal'
import useMessage from '@/hooks/useMessage' import useMessage from '@/hooks/useMessage'
import * as config from '@/router/config' import * as config from '@/router/config'
@@ -240,9 +242,6 @@ import { SHOW_FIRST_TIME_GUIDE, SHOW_MAIN_PAGE_QRCODE } from '@/utils/constant'
import { getConfig } from '@/utils/dataSender' import { getConfig } from '@/utils/dataSender'
import { IRPCActionType } from '@/utils/enum' import { IRPCActionType } from '@/utils/enum'
import FirstTimeGuide from './FirstTimeGuide.vue'
import ThemeSwitcher from './ui/ThemeSwitcher.vue'
const version = ref(pkg.version) const version = ref(pkg.version)
const isCollapsed = useStorage('navigation-collapsed', false) const isCollapsed = useStorage('navigation-collapsed', false)

File diff suppressed because it is too large Load Diff

View File

@@ -85,8 +85,8 @@ onBeforeUnmount(() => {
<style scoped> <style scoped>
@import 'tailwindcss' reference; @import 'tailwindcss' reference;
@import '../../assets/css/theme.css' reference; @import '../assets/css/theme.css' reference;
@import '../../assets/css/utilities.css' reference; @import '../assets/css/utilities.css' reference;
.control-button { .control-button {
@apply flex h-[20px] w-[28px] cursor-pointer items-center justify-center rounded-sm border-0 bg-transparent text-secondary transition-all duration-fast ease-standard hover:bg-surface-elevated hover:text-main [.close:hover]:bg-danger [.close:hover]:text-white [.mini:hover]:bg-success/85 [.mini:hover]:text-white [.minimize:hover]:bg-accent/85 [.minimize:hover]:text-white; @apply flex h-[20px] w-[28px] cursor-pointer items-center justify-center rounded-sm border-0 bg-transparent text-secondary transition-all duration-fast ease-standard hover:bg-surface-elevated hover:text-main [.close:hover]:bg-danger [.close:hover]:text-white [.mini:hover]:bg-success/85 [.mini:hover]:text-white [.minimize:hover]:bg-accent/85 [.minimize:hover]:text-white;

View File

@@ -20,22 +20,11 @@
@apply mb-0 text-sm font-semibold text-secondary leading-[1.4]; @apply mb-0 text-sm font-semibold text-secondary leading-[1.4];
} }
.custom-select {
@apply border border-border-secondary rounded-md px-2 py-1.5 w-full min-w-0 h-[28px] text-sm text-main transition-all duration-fast ease-apple cursor-pointer text-center leading-[1.4];
@apply focus:border-accent focus:outline-none focus:shadow-md;
}
.date-input { .date-input {
@apply border border-border-secondary rounded-md px-2 py-1.5 min-w-[20px] h-[28px] text-xs text-main transition-all duration-fast ease-apple flex-1 leading-[1.2]; @apply border border-border-secondary rounded-md px-2 py-1.5 min-w-[20px] h-[28px] text-xs text-main transition-all duration-fast ease-apple flex-1 leading-[1.2];
@apply focus:border-accent-hover focus:outline-none focus:shadow-md; @apply focus:border-accent-hover focus:outline-none focus:shadow-md;
} }
.sort-button {
@apply flex justify-between items-center border border-border-secondary rounded-md px-2 py-1.5 w-full h-[28px] text-sm text-main transition-all duration-fast ease-apple cursor-pointer gap-1 leading-[1.4];
@apply hover:border-accent-hover;
@apply focus:[.active]:border-accent-hover focus:[.active]:shadow-md;
}
.search-input { .search-input {
@apply border border-border-secondary rounded-md pt-2 pr-3 pb-2 pl-9 w-full text-sm text-main transition-all duration-fast ease-apple; @apply border border-border-secondary rounded-md pt-2 pr-3 pb-2 pl-9 w-full text-sm text-main transition-all duration-fast ease-apple;
@apply focus:border-accent-hover focus:outline-none focus:shadow-md; @apply focus:border-accent-hover focus:outline-none focus:shadow-md;

View File

@@ -1,125 +1,8 @@
@import url('./common/advancedAnimation.css');
/* stylelint-disable selector-pseudo-class-no-unknown */ /* stylelint-disable selector-pseudo-class-no-unknown */
.piclist-settings { @import url('./common/advancedAnimation.css');
overflow-y: auto; @import 'tailwindcss' reference;
padding: 1.5rem; @import '../../assets/css/theme.css' reference;
min-height: 100vh; @import '../../assets/css/utilities.css' reference;
color: var(--color-text-primary);
background: var(--color-background-secondary);
scrollbar-width: none;
-ms-overflow-style: none;
}
.piclist-settings::-webkit-scrollbar {
display: none;
}
/* Header */
.settings-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 1rem;
border: 1px solid var(--color-border);
border-radius: var(--radius-lg);
padding: 1rem;
background: var(--color-background-secondary);
box-shadow: 0 2px 8px rgb(0 0 0 / 10%);
}
.header-content {
display: flex;
align-items: center;
gap: 1rem;
}
.header-icon {
color: var(--color-accent);
}
.settings-header h1 {
margin: 0;
font-size: 1.5rem;
font-weight: 600;
color: var(--color-text-primary);
}
.header-actions {
display: flex;
gap: 0.75rem;
}
/* Tab Navigation */
.tab-navigation {
display: flex;
margin-bottom: 1rem;
border: 1px solid var(--color-border);
border-radius: var(--radius-lg);
padding: 0.5rem;
background: var(--color-background-secondary);
box-shadow: 0 2px 8px rgb(0 0 0 / 10%);
}
.tab-button {
display: flex;
justify-content: center;
align-items: center;
border: none;
border-radius: var(--radius-md);
padding: 0.75rem 1rem;
font-size: 0.875rem;
font-weight: 500;
color: var(--color-text-secondary);
background: transparent;
transition: all 0.2s ease;
gap: 0.5rem;
cursor: pointer;
flex: 1;
}
.tab-button:hover {
color: var(--color-text-primary);
background: var(--color-background-primary);
}
.tab-button.active {
color: white;
background: var(--color-accent);
box-shadow: 0 2px 4px rgb(64 158 255 / 30%);
}
/* Settings Content */
.settings-content {
display: flex;
flex-direction: column;
}
.tab-content {
display: flex;
flex-direction: column;
gap: 1.5rem;
}
.settings-section {
border: 1px solid var(--color-border);
border-radius: var(--radius-lg);
padding: 1.5rem;
background: var(--color-background-secondary);
box-shadow: 0 2px 8px var(--color-border);
}
.settings-section h2 {
margin: 0 0 0.5rem;
font-size: 1.125rem;
font-weight: 600;
color: var(--color-text-primary);
}
.settings-section p {
margin: 0 0 1.5rem;
font-size: 0.875rem;
color: var(--color-text-secondary);
}
/* Form Elements */ /* Form Elements */
.form-group { .form-group {
@@ -672,57 +555,6 @@ small {
background: var(--color-accent-hover); background: var(--color-accent-hover);
} }
.release-notes-loading,
.release-notes-error,
.release-notes-empty {
display: flex;
justify-content: center;
align-items: center;
padding: 2rem;
font-size: 0.925rem;
text-align: center;
color: var(--color-text-secondary);
gap: 0.5rem;
}
.release-notes-error {
display: flex;
justify-content: center;
align-items: center;
padding: 3rem;
color: var(--color-error);
flex-direction: column;
gap: 0.75rem;
}
.release-notes-pre {
margin: 0;
border: none;
padding: 1.5rem;
font-size: 0.875rem;
font-family: 'SF Mono', Monaco, Inconsolata, 'Roboto Mono', Consolas, monospace;
white-space: pre-wrap;
color: var(--color-text-primary);
background: transparent;
line-height: 1.6;
overflow-wrap: break-word;
}
.release-notes-footer {
border-top: 1px solid var(--color-border);
padding: 0.75rem 1.5rem;
text-align: center;
background: var(--color-background-secondary);
}
.release-notes-footer small {
display: flex;
align-items: center;
font-size: 0.75rem;
color: var(--color-text-secondary);
gap: 0.375rem;
}
/* Rotation animation for loading icons */ /* Rotation animation for loading icons */
@keyframes rotate { @keyframes rotate {
from { from {
@@ -1472,127 +1304,6 @@ small {
flex-shrink: 0; flex-shrink: 0;
} }
/* Update Preferences */
.update-preferences-section .update-preference-card {
border: 1px solid var(--color-border);
border-radius: var(--radius-lg);
padding: 0.25rem;
background: var(--color-background-secondary);
}
.update-preferences-section .switch-label {
margin: 0;
border: none;
background: transparent;
}
.release-notes-card.enhanced {
overflow: hidden;
border: 1px solid var(--color-border);
border-radius: var(--radius-xl);
background: var(--color-background-secondary);
box-shadow: 0 4px 16px rgb(0 0 0 / 8%);
}
.release-notes-card.enhanced .release-notes-header {
display: flex;
justify-content: space-between;
align-items: center;
border-bottom: 1px solid var(--color-border);
padding: 1rem 1.5rem;
background: var(--color-background-secondary);
}
.release-notes-title {
display: flex;
align-items: center;
gap: 0.5rem;
}
.release-notes-title svg {
color: var(--color-accent);
}
.release-notes-title h3 {
margin: 0;
font-size: 1rem;
font-weight: 600;
color: var(--color-text-primary);
}
.refresh-btn {
display: flex;
align-items: center;
gap: 0.375rem;
}
.release-notes-card.enhanced .release-notes-content {
overflow-y: auto;
max-height: 400px;
background: var(--color-background-secondary);
}
.release-notes-loading {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
padding: 3rem;
gap: 1rem;
}
.loading-spinner {
display: flex;
justify-content: center;
align-items: center;
border-radius: var(--radius-round);
width: 48px;
height: 48px;
color: var(--color-accent);
background: rgb(64 158 255 / 12%);
}
.release-notes-loading span {
font-size: 0.875rem;
color: var(--color-text-secondary);
}
.error-icon {
font-size: 2rem;
}
.release-notes-error span {
font-size: 0.875rem;
color: var(--color-text-secondary);
}
.release-notes-empty {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
padding: 3rem;
gap: 0.75rem;
}
.empty-icon {
font-size: 2rem;
}
.release-notes-empty span {
font-size: 0.875rem;
color: var(--color-text-secondary);
}
.release-notes-card.enhanced .release-notes-footer {
display: flex;
justify-content: center;
align-items: center;
border-top: 1px solid var(--color-border);
padding: 0.75rem 1.5rem;
background: var(--color-background-secondary);
}
/* Responsive for Advanced & Update */ /* Responsive for Advanced & Update */
@media (width <= 768px) { @media (width <= 768px) {
.advanced-action-grid { .advanced-action-grid {
@@ -1746,11 +1457,7 @@ small {
} }
/* Theme Actions Grid */ /* Theme Actions Grid */
.theme-actions-grid {
display: flex;
gap: 0.5rem;
margin-top: 0.75rem;
}
.theme-action-btn { .theme-action-btn {
display: flex; display: flex;
@@ -2617,61 +2324,42 @@ small {
} }
.notes-body { .notes-body {
overflow-y: auto; @apply overflow-y-auto rounded-lg p-5 max-h-[200px] text-base leading-[1.5] bg-bg-tertiary text-secondary;
padding: 1.25rem;
max-height: 195px;
font-size: 0.9375rem;
line-height: 1.5;
background: var(--color-background-secondary);
color: var(--color-text-secondary);
} }
.notes-body :deep(h1), .notes-body :deep(h1),
.notes-body :deep(h2), .notes-body :deep(h2),
.notes-body :deep(h3) { .notes-body :deep(h3) {
margin-top: 1.25rem; @apply font-bold text-main mt-5 mb-2;
margin-bottom: 0.5rem;
font-weight: 700;
color: var(--color-text-primary);
} }
.notes-body :deep(h1:first-child), .notes-body :deep(h1:first-child),
.notes-body :deep(h2:first-child), .notes-body :deep(h2:first-child),
.notes-body :deep(h3:first-child) { .notes-body :deep(h3:first-child) {
margin-top: 0; @apply mt-0;
} }
.notes-body :deep(p) { .notes-body :deep(p) {
margin-bottom: 0.875rem; @apply mb-2;
} }
.notes-body :deep(ul), .notes-body :deep(ul),
.notes-body :deep(ol) { .notes-body :deep(ol) {
padding-left: 1.5rem; @apply list-inside my-3.5 pl-6;
margin: 0.875rem 0;
} }
.notes-body :deep(li) { .notes-body :deep(li) {
margin-bottom: 0.375rem; @apply mb-1.5;
} }
.notes-body :deep(code) { .notes-body :deep(a) {
border-radius: 4px; @apply text-accent underline;
padding: 0.125rem 0.375rem;
font-size: 0.875em;
background: var(--color-surface);
color: var(--color-text-primary);
} }
.notes-body :deep(pre) { .notes-body :deep(a:hover) {
overflow-x: auto; @apply text-accent-hover;
border-radius: var(--radius-md);
padding: 1rem;
margin: 0.875rem 0;
background: var(--color-surface);
} }
.notes-body :deep(pre code) { .notes-body :deep(img) {
padding: 0; @apply max-w-full rounded-md;
background: transparent;
} }

View File

@@ -1,12 +1,12 @@
import { createRouter, createWebHashHistory } from 'vue-router' import { createRouter, createWebHashHistory } from 'vue-router'
import MainPage from '@/layouts/Main.vue'
import ManageBucketPage from '@/manage/pages/BucketPage.vue' import ManageBucketPage from '@/manage/pages/BucketPage.vue'
import ManageEmptyPage from '@/manage/pages/EmptyPage.vue' import ManageEmptyPage from '@/manage/pages/EmptyPage.vue'
import ManageLoginPage from '@/manage/pages/LogInPage.vue' import ManageLoginPage from '@/manage/pages/LogInPage.vue'
import ManageMainPage from '@/manage/pages/ManageMain.vue' import ManageMainPage from '@/manage/pages/ManageMain.vue'
import ManageSettingPage from '@/manage/pages/ManageSetting.vue' import ManageSettingPage from '@/manage/pages/ManageSetting.vue'
import GalleryPage from '@/pages/Gallery.vue' import GalleryPage from '@/pages/Gallery.vue'
import MainPage from '@/pages/Main.vue'
import MiniPage from '@/pages/MiniPage.vue' import MiniPage from '@/pages/MiniPage.vue'
import PicBedsPage from '@/pages/picbeds/index.vue' import PicBedsPage from '@/pages/picbeds/index.vue'
import SettingPage from '@/pages/PicGoSetting.vue' import SettingPage from '@/pages/PicGoSetting.vue'