Feature(custom): rewrite gallery page for better performance

This commit is contained in:
Kuingsmile
2025-08-08 16:00:21 +08:00
parent 6aebfcbb28
commit fd9dac735a
17 changed files with 4240 additions and 1298 deletions

View File

@@ -40,27 +40,27 @@
"upload-dist": "node ./scripts/upload-dist-to-r2.cjs" "upload-dist": "node ./scripts/upload-dist-to-r2.cjs"
}, },
"dependencies": { "dependencies": {
"@aws-sdk/client-s3": "^3.857.0", "@aws-sdk/client-s3": "^3.862.0",
"@aws-sdk/lib-storage": "^3.857.0", "@aws-sdk/lib-storage": "^3.862.0",
"@aws-sdk/s3-request-presigner": "^3.857.0", "@aws-sdk/s3-request-presigner": "^3.862.0",
"@electron-toolkit/preload": "^3.0.2", "@electron-toolkit/preload": "^3.0.2",
"@element-plus/icons-vue": "^2.3.1", "@element-plus/icons-vue": "^2.3.2",
"@headlessui/vue": "^1.7.23", "@headlessui/vue": "^1.7.23",
"@highlightjs/vue-plugin": "^2.1.2", "@highlightjs/vue-plugin": "^2.1.2",
"@nodelib/fs.walk": "^3.0.1", "@nodelib/fs.walk": "^3.0.1",
"@octokit/rest": "^22.0.0", "@octokit/rest": "^22.0.0",
"@piclist/i18n": "^2.0.0", "@piclist/i18n": "^2.0.0",
"@piclist/store": "^3.0.0", "@piclist/store": "^3.0.0",
"@smithy/node-http-handler": "^4.1.0", "@smithy/node-http-handler": "^4.1.1",
"@videojs-player/vue": "^1.0.0", "@videojs-player/vue": "^1.0.0",
"@vueuse/core": "^13.6.0",
"ali-oss": "^6.23.0", "ali-oss": "^6.23.0",
"axios": "^1.11.0", "axios": "^1.11.0",
"compare-versions": "^6.1.1", "compare-versions": "^6.1.1",
"core-js": "^3.44.0", "cos-nodejs-sdk-v5": "^2.15.4",
"cos-nodejs-sdk-v5": "^2.15.1",
"dexie": "^3.2.4", "dexie": "^3.2.4",
"electron-updater": "^6.6.2", "electron-updater": "^6.6.2",
"element-plus": "2.10.4", "element-plus": "2.10.5",
"fast-xml-parser": "^5.2.5", "fast-xml-parser": "^5.2.5",
"form-data": "^4.0.4", "form-data": "^4.0.4",
"fs-extra": "^11.3.0", "fs-extra": "^11.3.0",
@@ -69,7 +69,7 @@
"hpagent": "^1.2.0", "hpagent": "^1.2.0",
"js-yaml": "^4.1.0", "js-yaml": "^4.1.0",
"lodash-es": "^4.17.21", "lodash-es": "^4.17.21",
"lucide-vue-next": "^0.535.0", "lucide-vue-next": "^0.537.0",
"marked": "^16.1.1", "marked": "^16.1.1",
"mime-types": "^2.1.35", "mime-types": "^2.1.35",
"mitt": "^3.0.1", "mitt": "^3.0.1",
@@ -93,7 +93,6 @@
"vue-i18n": "^11.1.11", "vue-i18n": "^11.1.11",
"vue-router": "^4.5.1", "vue-router": "^4.5.1",
"vue3-lazyload": "^0.3.8", "vue3-lazyload": "^0.3.8",
"vue3-photo-preview": "^0.3.0",
"webdav": "^5.8.0", "webdav": "^5.8.0",
"write-file-atomic": "^6.0.0" "write-file-atomic": "^6.0.0"
}, },
@@ -124,7 +123,7 @@
"electron-devtools-installer": "^4.0.0", "electron-devtools-installer": "^4.0.0",
"electron-vite": "^4.0.0", "electron-vite": "^4.0.0",
"eslint": "^9.32.0", "eslint": "^9.32.0",
"eslint-plugin-prettier": "^5.5.3", "eslint-plugin-prettier": "^5.5.4",
"eslint-plugin-simple-import-sort": "^12.1.1", "eslint-plugin-simple-import-sort": "^12.1.1",
"eslint-plugin-unicorn": "^60.0.0", "eslint-plugin-unicorn": "^60.0.0",
"eslint-plugin-vue": "^10.4.0", "eslint-plugin-vue": "^10.4.0",
@@ -134,10 +133,10 @@
"prettier": "^3.6.2", "prettier": "^3.6.2",
"stylus": "^0.64.0", "stylus": "^0.64.0",
"typescript": "^5.8.3", "typescript": "^5.8.3",
"typescript-eslint": "^8.38.0", "typescript-eslint": "^8.39.0",
"vite": "^7.0.6", "vite": "^7.1.0",
"vue-eslint-parser": "^10.2.0", "vue-eslint-parser": "^10.2.0",
"vue-tsc": "^3.0.4" "vue-tsc": "^3.0.5"
}, },
"config": { "config": {
"commitizen": { "commitizen": {

BIN
public/errorLoading.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

View File

@@ -174,8 +174,6 @@ class LifeCycle {
}).catch(err => { }).catch(err => {
console.log('An error occurred: ', err) console.log('An error occurred: ', err)
}) })
const setwin = windowManager.get(IWindowList.SETTING_WINDOW)
setwin?.webContents?.openDevTools({ mode: 'detach' })
} }
windowManager.create(IWindowList.TRAY_WINDOW) windowManager.create(IWindowList.TRAY_WINDOW)
windowManager.create(IWindowList.SETTING_WINDOW) windowManager.create(IWindowList.SETTING_WINDOW)

View File

@@ -0,0 +1,223 @@
<template>
<div
ref="containerRef"
class="virtual-scroller"
:style="{ height: `${containerHeight}px` }"
@scroll="handleScroll"
>
<div
class="virtual-scroller-content"
:style="contentStyles"
>
<div
class="virtual-scroller-viewport"
:class="{ 'is-grid': isGridMode, 'is-list': !isGridMode }"
:style="viewportStyle"
>
<div
v-for="realIndex in visibleIndexes"
:key="itemsRef[realIndex] && itemsRef[realIndex][props.keyField || 'id'] ? itemsRef[realIndex][props.keyField || 'id'] : realIndex"
class="virtual-scroller-item"
:style="itemStyle"
>
<slot
:item="itemsRef[realIndex]"
:index="realIndex"
/>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue'
import { useVirtualGrid } from '@/hooks/useVirtualGrid'
type Item = any
interface Breakpoint { min: number; cols: number }
const props = withDefaults(defineProps<{
items: Item[]
itemHeight: number
height?: number
gridItems?: number
gridBreakpoints?: Breakpoint[]
bufferFactor?: number
pageMode?: boolean
keyField?: string
itemPadding?: number
viewMode?: 'list' | 'grid'
}>(), {
height: 400,
gridItems: 1,
gridBreakpoints: () => [],
bufferFactor: 0.5,
pageMode: false,
keyField: 'id',
itemPadding: 0,
viewMode: 'grid'
})
const containerRef = ref<HTMLElement | null>(null)
const containerHeight = ref<number>(props.pageMode ? 0 : props.height)
const containerWidth = ref<number>(0)
const itemsRef = ref<Item[]>(props.items)
watch(() => props.items, v => { itemsRef.value = v })
const localViewMode = ref< 'list' | 'grid'>(props.viewMode)
watch(() => props.viewMode, v => { localViewMode.value = v })
const sortedBreakpoints = computed<Breakpoint[]>(() =>
[...props.gridBreakpoints].sort((a, b) => a.min - b.min)
)
const isForcedList = computed(() => localViewMode.value === 'list')
const effectiveCols = computed<number>(() => {
if (isForcedList.value) return 1
const base = Math.max(1, props.gridItems || 1)
const w = containerWidth.value || 0
let cols = base
for (const bp of sortedBreakpoints.value) {
if (w >= bp.min) cols = Math.max(1, bp.cols)
}
return cols
})
const isGridMode = computed(() => effectiveCols.value > 1)
const {
gridCalculations,
visibleIndexes,
viewportOffset,
updateScrollTop,
scrollToItem, scrollToTop, scrollToBottom
} = useVirtualGrid({
items: itemsRef,
itemHeight: props.itemHeight,
containerHeight,
gridItems: effectiveCols,
bufferFactor: props.bufferFactor
})
const contentStyles = computed(() => ({
height: `${gridCalculations.value.totalHeight}px`
}))
const viewportStyle = computed(() => {
const base: Record<string, string> = {
transform: `translateY(${viewportOffset.value}px)`
}
if (isGridMode.value) {
base['--items-per-row'] = String(effectiveCols.value)
base['--row-height'] = `${props.itemHeight}px`
base['--item-gap'] = `${props.itemPadding}px`
}
return base
})
const itemStyle = computed(() =>
isGridMode.value
? {}
: { height: `${props.itemHeight}px`, padding: `${props.itemPadding}px` }
)
function handleScroll () {
const c = containerRef.value
if (!c) return
updateScrollTop(c.scrollTop)
}
let ro: ResizeObserver | null = null
function updateContainerMetrics () {
const el = containerRef.value
if (!el) return
const rect = el.getBoundingClientRect()
containerWidth.value = rect.width
if (props.pageMode) {
containerHeight.value = Math.max(200, window.innerHeight - rect.top - 12)
} else {
containerHeight.value = props.height
}
}
onMounted(() => {
const el = containerRef.value
if (!el) return
ro = new ResizeObserver(updateContainerMetrics)
ro.observe(el)
if (props.pageMode) ro.observe(document.documentElement)
updateContainerMetrics()
if (props.pageMode) {
window.addEventListener('resize', updateContainerMetrics, { passive: true })
}
})
onBeforeUnmount(() => {
if (ro) ro.disconnect()
window.removeEventListener('resize', updateContainerMetrics)
})
function scrollTo (index: number) { scrollToItem(index) }
function setViewMode (mode: 'list' | 'grid') {
localViewMode.value = mode
}
function toggleViewMode () {
setViewMode(isGridMode.value ? 'list' : 'grid')
}
function refresh () {
updateContainerMetrics()
if (containerRef.value) {
updateScrollTop(containerRef.value.scrollTop)
}
}
defineExpose({ scrollTo, scrollToTop, scrollToBottom, setViewMode, toggleViewMode, refresh })
</script>
<style scoped>
.virtual-scroller {
position: relative;
overflow: auto;
contain: layout style paint;
will-change: transform;
-webkit-overflow-scrolling: touch;
}
.virtual-scroller-content {
position: relative;
width: 100%;
}
/* Base viewport (list mode) stacks children; offset applied via translateY */
.virtual-scroller-viewport {
position: absolute;
inset: 0 auto auto 0;
will-change: transform;
backface-visibility: hidden;
}
.virtual-scroller-viewport.is-grid {
display: grid;
grid-template-columns: repeat(var(--items-per-row, 1), minmax(0, 1fr));
grid-auto-rows: var(--row-height, 1px);
gap: var(--item-gap, 0px);
}
.virtual-scroller-viewport.is-list .virtual-scroller-item {
width: 100%;
}
.virtual-scroller-viewport.is-grid .virtual-scroller-item {
width: 100%;
}
</style>

View File

@@ -0,0 +1,112 @@
import type { Ref } from 'vue'
import { computed, isRef, ref, watch } from 'vue'
export interface UseVirtualGridOptions {
items: Ref<any[]>
itemHeight: number
containerHeight: Ref<number>
gridItems?: number | Ref<number>
bufferFactor?: number
}
export function useVirtualGrid (options: UseVirtualGridOptions) {
const {
items,
itemHeight,
containerHeight,
gridItems = 1,
bufferFactor = 0.5
} = options
const gridItemsRef = isRef(gridItems) ? gridItems : ref(gridItems)
const scrollTop = ref(0)
const gridCalculations = computed(() => {
const itemsPerRow = Math.max(1, gridItemsRef.value || 1)
const totalRows = Math.ceil(items.value.length / itemsPerRow)
const rowHeight = itemHeight
const totalHeight = totalRows * rowHeight
return {
itemsPerRow,
totalRows,
rowHeight,
totalHeight
}
})
const visibleRange = computed(() => {
const { rowHeight, totalRows } = gridCalculations.value
const height = containerHeight.value
if (!height || !rowHeight || totalRows === 0) {
return { startRow: 0, endRow: 0, visibleRows: 0 }
}
const buffer = Math.ceil(height / rowHeight * bufferFactor)
const startRow = Math.max(0, Math.floor(scrollTop.value / rowHeight) - buffer)
const visibleRows = Math.ceil(height / rowHeight) + buffer * 2
const endRow = Math.min(totalRows, startRow + visibleRows)
return { startRow, endRow, visibleRows }
})
const visibleIndexes = computed(() => {
const { itemsPerRow } = gridCalculations.value
const { startRow, endRow } = visibleRange.value
const indexes: number[] = []
for (let rowIndex = startRow; rowIndex < endRow; rowIndex++) {
for (let col = 0; col < itemsPerRow; col++) {
const itemIndex = rowIndex * itemsPerRow + col
if (itemIndex < items.value.length) {
indexes.push(itemIndex)
}
}
}
return indexes
})
const viewportOffset = computed(() => {
const { rowHeight } = gridCalculations.value
const { startRow } = visibleRange.value
return startRow * rowHeight
})
function updateScrollTop (newScrollTop: number) {
scrollTop.value = newScrollTop
}
function scrollToItem (index: number) {
const { itemsPerRow, rowHeight } = gridCalculations.value
const rowIndex = Math.floor(index / itemsPerRow)
scrollTop.value = rowIndex * rowHeight
}
function scrollToTop () {
scrollTop.value = 0
}
function scrollToBottom () {
const { totalHeight } = gridCalculations.value
scrollTop.value = Math.max(0, totalHeight - containerHeight.value)
}
watch(containerHeight, () => {
const { totalHeight } = gridCalculations.value
if (scrollTop.value > totalHeight - containerHeight.value) {
scrollTop.value = Math.max(0, totalHeight - containerHeight.value)
}
})
return {
gridCalculations,
visibleIndexes,
viewportOffset,
updateScrollTop,
scrollToItem,
scrollToTop,
scrollToBottom
}
}

View File

@@ -423,6 +423,59 @@
"checkProblemWithProxy": "Check if the proxy settings are normal", "checkProblemWithProxy": "Check if the proxy settings are normal",
"fixDoneNeedReload": "Repair completed, need to restart to take effect, restart or not", "fixDoneNeedReload": "Repair completed, need to restart to take effect, restart or not",
"notice": "Notice" "notice": "Notice"
},
"gallery": {
"title": "Gallery",
"images": "Images",
"syncDelete": "Delete from Cloud",
"hideFilters": "Filters",
"showFilters": "Filters",
"refresh": "Refresh",
"picBedType": "PicBed Type",
"chooseShowedPicBed": "Choose Showed PicBed",
"selected": "Selected",
"dateRange": "Date Range",
"pasteFormat": "Paste Format",
"urlType": "URL Type",
"sort": "Sort",
"sortBy": {
"name": "Name",
"time": "Date",
"ext": "Extension",
"check": "Checked"
},
"searchFilename": "Search by Filename",
"searchUrl": "Search by URL",
"copy": "Copy",
"edit": "Edit",
"delete": "Delete",
"selectAll": "Select All",
"cancel": "Cancel",
"noImagesFound": "No Images Found",
"tryAdjustingFilters": "Try Adjusting Filters or Uploading Some Images",
"changeImageUrl": "Change Image URL",
"batchEditUrl": "Batch Edit URL",
"regexPattern": "Regex Pattern Matched: {matched} Times",
"regexPatternPlaceholder": "For example: ^\\d{4}-\\d{2}-\\d{2}, without the slashes on both sides of the regex",
"replacedWith": "The string to be replaced, placeholders from the custom renaming rules can be used",
"longUrl": "Long URL",
"shortUrl": "Short URL",
"copyLinkSucceed": "Copy Link Succeed",
"notice": "Notice",
"confirmRemove": "Are you sure you want to delete the selected images?",
"cloudDeleteSucceed": "Cloud Delete Succeed",
"cloudDeleteFailed": "Cloud Delete Failed",
"operationSucceed": "Operation Succeed",
"operationFailed": "Operation Failed",
"haveDuplicate": "There are naming duplicates among the selected images, do you want to continue?",
"canceled": "Canceled",
"previewHelp": "Scroll to zoom • Drag to pan • Arrow keys to navigate • ESC to close",
"listView": "List",
"gridView": "Grid",
"isAlwaysForceReload": "No Cache",
"inputRegexTip": "Please enter the matching string",
"noMatch": "No matching items found",
"noItemsNeedRename": "No items need to be renamed"
} }
}, },
"OPEN_MAIN_WINDOW": "Open Main Window", "OPEN_MAIN_WINDOW": "Open Main Window",
@@ -440,26 +493,8 @@
"COPY": "Copy", "COPY": "Copy",
"DELETE": "Delete", "DELETE": "Delete",
"SELECT_ALL": "Select All", "SELECT_ALL": "Select All",
"CHANGE_IMAGE_URL": "Change Image URL",
"CHANGE_IMAGE_URL_SUCCEED": "Change Image URL Succeed",
"COPY_LINK_SUCCEED": "Copy Link Succeed", "COPY_LINK_SUCCEED": "Copy Link Succeed",
"BATCH_COPY_LINK_SUCCEED": "Batch Copy Link Succeed",
"FILE_RENAME": "File Rename",
"OPEN_FILE_PATH": "Open file path",
"GALLERY_SYNC_DELETE": "Cloud Sync Delete",
"GALLERY_SYNC_DELETE_NOTICE_TITLE": "Notice",
"GALLERY_SYNC_DELETE_NOTICE_SUCCEED": "Cloud Delete Succeed",
"GALLERY_SYNC_DELETE_NOTICE_FAILED": "Cloud Delete Failed",
"GALLERY_CHANGE_URL": "Rename",
"GALLERY_SEARCH_FILENAME": "Search by Filename",
"GALLERY_SEARCH_URL": "Search by URL",
"GALLERY_MATCHED": " Matched: ",
"UPLOAD_SHORT_URL": "Short",
"UPLOAD_NORMAL_URL": "Long",
"SETTINGS": "Settings", "SETTINGS": "Settings",
"SETTINGS_OPEN_CONFIG_FILE": "Open Config File",
"SETTINGS_SET_SHORTCUT": "Set Shortcut",
"SETTINGS_SET_SERVER": "Set Server",
"SETTINGS_OPEN": "Open", "SETTINGS_OPEN": "Open",
"SETTINGS_CLOSE": "Close", "SETTINGS_CLOSE": "Close",
"SETTINGS_RESULT": "Result", "SETTINGS_RESULT": "Result",
@@ -468,12 +503,6 @@
"WAIT_TO_UPLOAD": "Wait to Upload", "WAIT_TO_UPLOAD": "Wait to Upload",
"ALREADY_UPLOAD": "Already Uploaded", "ALREADY_UPLOAD": "Already Uploaded",
"TIPS_DRAG_VALID_PICTURE_OR_URL": "Drag valid picture or url to here", "TIPS_DRAG_VALID_PICTURE_OR_URL": "Drag valid picture or url to here",
"TIPS_REMOVE_LINK": "This operation will remove the picture from the album, continue?",
"TIPS_WILL_REMOVE_CHOOSED_IMAGES": "This operation will remove the picture from the album, continue?",
"TIPS_MUST_CONTAINS_URL": "Must contains $url or $fileName or $extName",
"TIPS_NETWORK_ERROR": "Network Error",
"TIPS_NEED_RELOAD": "Need Reload App",
"TIPS_PLEASE_CHOOSE_LOG_LEVEL": "Please choose log level",
"TIPS_SET_SUCCEED": "Set successfully", "TIPS_SET_SUCCEED": "Set successfully",
"TIPS_RESET_SUCCEED": "Reset successfully", "TIPS_RESET_SUCCEED": "Reset successfully",
"MANAGE_SETTING_TITLE": "Manage Setting", "MANAGE_SETTING_TITLE": "Manage Setting",

View File

@@ -418,6 +418,59 @@
"checkProblemWithProxy": "检查代理设置是否正常", "checkProblemWithProxy": "检查代理设置是否正常",
"fixDoneNeedReload": "修复完成,需要重启生效,是否重启", "fixDoneNeedReload": "修复完成,需要重启生效,是否重启",
"notice": "通知" "notice": "通知"
},
"gallery": {
"title": "相册",
"images": "图片",
"syncDelete": "删除云端",
"hideFilters": "过滤器",
"showFilters": "过滤器",
"refresh": "刷新",
"picBedType": "图床类型",
"chooseShowedPicBed": "请选择显示的图床",
"selected": "已选择",
"dateRange": "日期范围",
"pasteFormat": "粘贴格式",
"urlType": "链接类型",
"sort": "排序",
"sortBy": {
"name": "名称",
"time": "日期",
"ext": "后缀",
"check": "已选中"
},
"searchFilename": "搜索文件名",
"searchUrl": "搜索URL",
"copy": "复制",
"edit": "编辑",
"delete": "删除",
"selectAll": "全选",
"cancel": "取消",
"noImagesFound": "未找到图片",
"tryAdjustingFilters": "尝试调整过滤器或上传一些图片",
"changeImageUrl": "修改图片URL",
"batchEditUrl": "批量修改URL",
"regexPattern": "进行替换时匹配的字符串或js正则表达式 匹配到: {matched} 个",
"regexPatternPlaceholder": "例如:^\\d{4}-\\d{2}-\\d{2}, 不包含正则两边的斜杠",
"replacedWith": "需要替换的字符串,可使用自定义重命名规则中的占位符",
"longUrl": "长链接",
"shortUrl": "短链接",
"copyLinkSucceed": "复制链接成功",
"notice": "通知",
"confirmRemove": "确认删除选中的图片吗?",
"cloudDeleteSucceed": "云端删除成功",
"cloudDeleteFailed": "云端删除失败",
"operationSucceed": "操作成功",
"operationFailed": "操作失败",
"haveDuplicate": "已选中的图片中有命名重复, 是否继续?",
"canceled": "已取消",
"previewHelp": "滚轮缩放 • 拖拽平移 • 方向键导航 • ESC关闭",
"listView": "列表",
"gridView": "网格",
"isAlwaysForceReload": "禁用缓存",
"inputRegexTip": "请输入匹配字符串",
"noMatch": "未找到匹配项",
"noItemsNeedRename": "没有需要重命名的项目"
} }
}, },
"OPEN_MAIN_WINDOW": "打开主窗口", "OPEN_MAIN_WINDOW": "打开主窗口",
@@ -435,26 +488,8 @@
"COPY": "复制", "COPY": "复制",
"DELETE": "删除", "DELETE": "删除",
"SELECT_ALL": "全选", "SELECT_ALL": "全选",
"CHANGE_IMAGE_URL": "修改图片URL",
"CHANGE_IMAGE_URL_SUCCEED": "修改图片URL成功",
"COPY_LINK_SUCCEED": "复制链接成功", "COPY_LINK_SUCCEED": "复制链接成功",
"BATCH_COPY_LINK_SUCCEED": "批量复制链接成功",
"FILE_RENAME": "文件改名",
"OPEN_FILE_PATH": "打开文件路径",
"GALLERY_SYNC_DELETE": "删除云端",
"GALLERY_SYNC_DELETE_NOTICE_TITLE": "通知",
"GALLERY_SYNC_DELETE_NOTICE_SUCCEED": "云端删除成功",
"GALLERY_SYNC_DELETE_NOTICE_FAILED": "云端删除失败",
"GALLERY_CHANGE_URL": "修改",
"GALLERY_SEARCH_FILENAME": "搜索文件名",
"GALLERY_SEARCH_URL": "搜索URL",
"GALLERY_MATCHED": " 匹配到: ",
"UPLOAD_SHORT_URL": "短链接",
"UPLOAD_NORMAL_URL": "长链接",
"SETTINGS": "设置", "SETTINGS": "设置",
"SETTINGS_OPEN_CONFIG_FILE": "打开配置文件",
"SETTINGS_SET_SHORTCUT": "设置快捷键",
"SETTINGS_SET_SERVER": "设置Server",
"SETTINGS_OPEN": "开", "SETTINGS_OPEN": "开",
"SETTINGS_CLOSE": "关", "SETTINGS_CLOSE": "关",
"SETTINGS_RESULT": "设置结果", "SETTINGS_RESULT": "设置结果",
@@ -463,12 +498,6 @@
"WAIT_TO_UPLOAD": "等待上传", "WAIT_TO_UPLOAD": "等待上传",
"ALREADY_UPLOAD": "已上传", "ALREADY_UPLOAD": "已上传",
"TIPS_DRAG_VALID_PICTURE_OR_URL": "请拖入合法的图片文件或者图片URL地址", "TIPS_DRAG_VALID_PICTURE_OR_URL": "请拖入合法的图片文件或者图片URL地址",
"TIPS_REMOVE_LINK": "此操作将把该图片移出相册, 是否继续?",
"TIPS_WILL_REMOVE_CHOOSED_IMAGES": "将在相册中移除刚才选中的 ${m} 张图片,是否继续?",
"TIPS_MUST_CONTAINS_URL": "必须含有$url 或 $fileName 或 $extName",
"TIPS_NETWORK_ERROR": "网络错误暂时无法获取",
"TIPS_NEED_RELOAD": "需要重启生效",
"TIPS_PLEASE_CHOOSE_LOG_LEVEL": "请选择日志记录等级",
"TIPS_SET_SUCCEED": "设置成功", "TIPS_SET_SUCCEED": "设置成功",
"TIPS_RESET_SUCCEED": "重置成功", "TIPS_RESET_SUCCEED": "重置成功",
"MANAGE_SETTING_TITLE": "管理页面设置", "MANAGE_SETTING_TITLE": "管理页面设置",

View File

@@ -418,6 +418,59 @@
"checkProblemWithProxy": "檢查代理設置是否正常", "checkProblemWithProxy": "檢查代理設置是否正常",
"fixDoneNeedReload": "修復完成,需要重啟生效,是否重啟", "fixDoneNeedReload": "修復完成,需要重啟生效,是否重啟",
"notice": "通知" "notice": "通知"
},
"gallery": {
"title": "相簿",
"images": "圖片",
"syncDelete": "刪除雲端",
"hideFilters": "過濾器",
"showFilters": "過濾器",
"refresh": "刷新",
"picBedType": "圖床類型",
"chooseShowedPicBed": "請選擇顯示的圖床",
"selected": "已選擇",
"dateRange": "日期範圍",
"pasteFormat": "粘貼格式",
"urlType": "連結類型",
"sort": "排序",
"sortBy": {
"name": "名稱",
"time": "日期",
"ext": "後綴",
"check": "已選中"
},
"searchFilename": "搜索文件名",
"searchUrl": "搜索URL",
"copy": "複製",
"edit": "編輯",
"delete": "刪除",
"selectAll": "全選",
"cancel": "取消",
"noImagesFound": "未找到圖片",
"tryAdjustingFilters": "嘗試調整過濾器或上傳一些圖片",
"changeImageUrl": "修改圖片URL",
"batchEditUrl": "批量修改URL",
"regexPattern": "進行替換時匹配的字符串或js正則表達式 匹配到: {matched} 個",
"regexPatternPlaceholder": "例如:^\\d{4}-\\d{2}-\\d{2}, 不包含正則兩邊的斜杠",
"replacedWith": "需要替換的字符串,可使用自定義重命名規則中的佔位符",
"longUrl": "長連結",
"shortUrl": "短連結",
"copyLinkSucceed": "複製連結成功",
"notice": "通知",
"confirmRemove": "確認刪除選中的圖片嗎?",
"cloudDeleteSucceed": "雲端刪除成功",
"cloudDeleteFailed": "雲端刪除失敗",
"operationSucceed": "操作成功",
"operationFailed": "操作失敗",
"haveDuplicate": "已選中的圖片中有命名重複, 是否繼續?",
"canceled": "已取消",
"previewHelp": "滾輪縮放 • 拖拽平移 • 方向鍵導航 • ESC關閉",
"listView": "列表",
"gridView": "網格",
"isAlwaysForceReload": "無快取",
"inputRegexTip": "请输入匹配字符串",
"noMatch": "未找到匹配项",
"noItemsNeedRename": "没有需要重命名的项目"
} }
}, },
"OPEN_MAIN_WINDOW": "打開主視窗", "OPEN_MAIN_WINDOW": "打開主視窗",
@@ -435,26 +488,8 @@
"COPY": "複製", "COPY": "複製",
"DELETE": "刪除", "DELETE": "刪除",
"SELECT_ALL": "全選", "SELECT_ALL": "全選",
"CHANGE_IMAGE_URL": "修改圖片URL",
"CHANGE_IMAGE_URL_SUCCEED": "修改圖片URL成功",
"COPY_LINK_SUCCEED": "複製連結成功", "COPY_LINK_SUCCEED": "複製連結成功",
"BATCH_COPY_LINK_SUCCEED": "批量複製連結成功",
"FILE_RENAME": "檔案改名",
"OPEN_FILE_PATH": "打開檔案路徑",
"GALLERY_SYNC_DELETE": "刪除雲端",
"GALLERY_SYNC_DELETE_NOTICE_TITLE": "通知",
"GALLERY_SYNC_DELETE_NOTICE_SUCCEED": "雲端刪除成功",
"GALLERY_SYNC_DELETE_NOTICE_FAILED": "雲端刪除失敗",
"GALLERY_CHANGE_URL": "修改",
"GALLERY_SEARCH_FILENAME": "搜尋文件名",
"GALLERY_SEARCH_URL": "搜尋URL",
"GALLERY_MATCHED": " 匹配到: ",
"UPLOAD_SHORT_URL": "短連結",
"UPLOAD_NORMAL_URL": "长連結",
"SETTINGS": "設定", "SETTINGS": "設定",
"SETTINGS_OPEN_CONFIG_FILE": "打開設定檔案",
"SETTINGS_SET_SHORTCUT": "設定快捷鍵",
"SETTINGS_SET_SERVER": "設定Server",
"SETTINGS_OPEN": "開", "SETTINGS_OPEN": "開",
"SETTINGS_CLOSE": "關", "SETTINGS_CLOSE": "關",
"SETTINGS_RESULT": "設定結果", "SETTINGS_RESULT": "設定結果",
@@ -463,12 +498,6 @@
"WAIT_TO_UPLOAD": "等待上傳", "WAIT_TO_UPLOAD": "等待上傳",
"ALREADY_UPLOAD": "已上傳", "ALREADY_UPLOAD": "已上傳",
"TIPS_DRAG_VALID_PICTURE_OR_URL": "請拖入合法的圖片檔案或者圖片URL地址", "TIPS_DRAG_VALID_PICTURE_OR_URL": "請拖入合法的圖片檔案或者圖片URL地址",
"TIPS_REMOVE_LINK": "此操作將在相簿中移除該圖片,是否繼續?",
"TIPS_WILL_REMOVE_CHOOSED_IMAGES": "將在相簿中移除剛才選中的 ${m} 張圖片,是否繼續?",
"TIPS_MUST_CONTAINS_URL": "必須含有$url 或 $fileName 或 $extName",
"TIPS_NETWORK_ERROR": "網路錯誤,暫時無法取得",
"TIPS_NEED_RELOAD": "需要重新啟動生效",
"TIPS_PLEASE_CHOOSE_LOG_LEVEL": "請選擇記錄等級",
"TIPS_SET_SUCCEED": "設定成功", "TIPS_SET_SUCCEED": "設定成功",
"TIPS_RESET_SUCCEED": "重置成功", "TIPS_RESET_SUCCEED": "重置成功",
"MANAGE_SETTING_TITLE": "管理設定", "MANAGE_SETTING_TITLE": "管理設定",

View File

@@ -1,5 +1,4 @@
import 'element-plus/dist/index.css' import 'element-plus/dist/index.css'
import 'vue3-photo-preview/dist/index.css'
import 'video.js/dist/video-js.css' import 'video.js/dist/video-js.css'
import 'highlight.js/styles/stackoverflow-light.css' import 'highlight.js/styles/stackoverflow-light.css'
import 'highlight.js/lib/common' import 'highlight.js/lib/common'
@@ -12,7 +11,6 @@ import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
import { createApp } from 'vue' import { createApp } from 'vue'
import { createI18n } from 'vue-i18n' import { createI18n } from 'vue-i18n'
import VueLazyLoad from 'vue3-lazyload' import VueLazyLoad from 'vue3-lazyload'
import vue3PhotoPreview from 'vue3-photo-preview'
import App from '@/App.vue' import App from '@/App.vue'
import en from '@/i18n/locales/en.json' import en from '@/i18n/locales/en.json'
@@ -54,7 +52,6 @@ app.use(i18n)
app.use(ElementUI) app.use(ElementUI)
app.use(router) app.use(router)
app.use(store) app.use(store)
app.use(vue3PhotoPreview)
app.use(pinia) app.use(pinia)
app.use(hljsVuePlugin) app.use(hljsVuePlugin)
app.use(VueVideoPlayer) app.use(VueVideoPlayer)

File diff suppressed because it is too large Load Diff

View File

@@ -259,8 +259,8 @@ const handleFix = async () => {
}) })
confirm({ confirm({
title: t('pages.toolbox.fixDoneNeedReload'), title: t('pages.toolbox.notice'),
message: t('pages.toolbox.notice'), message: t('pages.toolbox.fixDoneNeedReload'),
type: 'warning', type: 'warning',
confirmButtonText: t('common.confirm'), confirmButtonText: t('common.confirm'),
cancelButtonText: t('common.cancel'), cancelButtonText: t('common.cancel'),

File diff suppressed because it is too large Load Diff

View File

@@ -729,14 +729,12 @@ small {
padding: 0.5rem 1rem; padding: 0.5rem 1rem;
font-size: 0.875rem; font-size: 0.875rem;
line-height: 1.4; line-height: 1.4;
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
border-radius: 0; border-radius: 0;
margin: 0; margin: 0;
} }
.placeholder-item:hover { .placeholder-item:hover {
background: rgba(var(--color-accent-rgb), 0.08); background: rgba(var(--color-accent-rgb), 0.08);
transform: translateX(2px);
} }
.placeholder-item code { .placeholder-item code {

View File

@@ -637,17 +637,6 @@ html, body {
transform: translateY(-1rem); transform: translateY(-1rem);
} }
.modal-enter-active,
.modal-leave-active {
transition: all var(--transition-medium);
}
.modal-enter-from,
.modal-leave-to {
opacity: 0;
transform: scale(0.95);
}
/* Responsive Design */ /* Responsive Design */
@media (max-width: 768px) { @media (max-width: 768px) {
.plugin-container { .plugin-container {

View File

@@ -356,28 +356,6 @@
text-align: center; text-align: center;
} }
/* Transitions */
.modal-enter-active,
.modal-leave-active {
transition: all 0.3s ease;
}
.modal-enter-from,
.modal-leave-to {
opacity: 0;
transform: scale(0.9);
}
.modal-enter-active .modal-content,
.modal-leave-active .modal-content {
transition: all 0.3s ease;
}
.modal-enter-from .modal-content,
.modal-leave-to .modal-content {
transform: translateY(-20px);
}
/* Responsive Design */ /* Responsive Design */
@media (max-width: 768px) { @media (max-width: 768px) {
.shortkey-container { .shortkey-container {

View File

@@ -33,6 +33,7 @@ export interface IConfigStruct {
shortKey: { shortKey: {
[key: string]: IShortKeyConfig [key: string]: IShortKeyConfig
} }
isAlwaysForceReload: boolean
logLevel: string[] logLevel: string[]
logPath: string logPath: string
logFileSizeLimit: number logFileSizeLimit: number
@@ -120,6 +121,7 @@ export const configPaths = {
_path: 'settings.shortKey', _path: 'settings.shortKey',
'picgo:upload': 'settings.shortKey[picgo:upload]' 'picgo:upload': 'settings.shortKey[picgo:upload]'
}, },
isAlwaysForceReload: 'settings.isAlwaysForceReload',
logLevel: 'settings.logLevel', logLevel: 'settings.logLevel',
logPath: 'settings.logPath', logPath: 'settings.logPath',
logFileSizeLimit: 'settings.logFileSizeLimit', logFileSizeLimit: 'settings.logFileSizeLimit',

1366
yarn.lock

File diff suppressed because it is too large Load Diff