mirror of
https://github.com/Kuingsmile/PicList.git
synced 2026-05-06 20:42:57 +08:00
✨ Feature(custom): rewrite gallery page for better performance
This commit is contained in:
27
package.json
27
package.json
@@ -40,27 +40,27 @@
|
||||
"upload-dist": "node ./scripts/upload-dist-to-r2.cjs"
|
||||
},
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-s3": "^3.857.0",
|
||||
"@aws-sdk/lib-storage": "^3.857.0",
|
||||
"@aws-sdk/s3-request-presigner": "^3.857.0",
|
||||
"@aws-sdk/client-s3": "^3.862.0",
|
||||
"@aws-sdk/lib-storage": "^3.862.0",
|
||||
"@aws-sdk/s3-request-presigner": "^3.862.0",
|
||||
"@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",
|
||||
"@highlightjs/vue-plugin": "^2.1.2",
|
||||
"@nodelib/fs.walk": "^3.0.1",
|
||||
"@octokit/rest": "^22.0.0",
|
||||
"@piclist/i18n": "^2.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",
|
||||
"@vueuse/core": "^13.6.0",
|
||||
"ali-oss": "^6.23.0",
|
||||
"axios": "^1.11.0",
|
||||
"compare-versions": "^6.1.1",
|
||||
"core-js": "^3.44.0",
|
||||
"cos-nodejs-sdk-v5": "^2.15.1",
|
||||
"cos-nodejs-sdk-v5": "^2.15.4",
|
||||
"dexie": "^3.2.4",
|
||||
"electron-updater": "^6.6.2",
|
||||
"element-plus": "2.10.4",
|
||||
"element-plus": "2.10.5",
|
||||
"fast-xml-parser": "^5.2.5",
|
||||
"form-data": "^4.0.4",
|
||||
"fs-extra": "^11.3.0",
|
||||
@@ -69,7 +69,7 @@
|
||||
"hpagent": "^1.2.0",
|
||||
"js-yaml": "^4.1.0",
|
||||
"lodash-es": "^4.17.21",
|
||||
"lucide-vue-next": "^0.535.0",
|
||||
"lucide-vue-next": "^0.537.0",
|
||||
"marked": "^16.1.1",
|
||||
"mime-types": "^2.1.35",
|
||||
"mitt": "^3.0.1",
|
||||
@@ -93,7 +93,6 @@
|
||||
"vue-i18n": "^11.1.11",
|
||||
"vue-router": "^4.5.1",
|
||||
"vue3-lazyload": "^0.3.8",
|
||||
"vue3-photo-preview": "^0.3.0",
|
||||
"webdav": "^5.8.0",
|
||||
"write-file-atomic": "^6.0.0"
|
||||
},
|
||||
@@ -124,7 +123,7 @@
|
||||
"electron-devtools-installer": "^4.0.0",
|
||||
"electron-vite": "^4.0.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-unicorn": "^60.0.0",
|
||||
"eslint-plugin-vue": "^10.4.0",
|
||||
@@ -134,10 +133,10 @@
|
||||
"prettier": "^3.6.2",
|
||||
"stylus": "^0.64.0",
|
||||
"typescript": "^5.8.3",
|
||||
"typescript-eslint": "^8.38.0",
|
||||
"vite": "^7.0.6",
|
||||
"typescript-eslint": "^8.39.0",
|
||||
"vite": "^7.1.0",
|
||||
"vue-eslint-parser": "^10.2.0",
|
||||
"vue-tsc": "^3.0.4"
|
||||
"vue-tsc": "^3.0.5"
|
||||
},
|
||||
"config": {
|
||||
"commitizen": {
|
||||
|
||||
BIN
public/errorLoading.png
Normal file
BIN
public/errorLoading.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.5 KiB |
@@ -174,8 +174,6 @@ class LifeCycle {
|
||||
}).catch(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.SETTING_WINDOW)
|
||||
|
||||
223
src/renderer/components/VirtualScroller.vue
Normal file
223
src/renderer/components/VirtualScroller.vue
Normal 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>
|
||||
112
src/renderer/hooks/useVirtualGrid.ts
Normal file
112
src/renderer/hooks/useVirtualGrid.ts
Normal 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
|
||||
}
|
||||
}
|
||||
@@ -423,6 +423,59 @@
|
||||
"checkProblemWithProxy": "Check if the proxy settings are normal",
|
||||
"fixDoneNeedReload": "Repair completed, need to restart to take effect, restart or not",
|
||||
"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",
|
||||
@@ -440,26 +493,8 @@
|
||||
"COPY": "Copy",
|
||||
"DELETE": "Delete",
|
||||
"SELECT_ALL": "Select All",
|
||||
"CHANGE_IMAGE_URL": "Change Image URL",
|
||||
"CHANGE_IMAGE_URL_SUCCEED": "Change Image URL 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_OPEN_CONFIG_FILE": "Open Config File",
|
||||
"SETTINGS_SET_SHORTCUT": "Set Shortcut",
|
||||
"SETTINGS_SET_SERVER": "Set Server",
|
||||
"SETTINGS_OPEN": "Open",
|
||||
"SETTINGS_CLOSE": "Close",
|
||||
"SETTINGS_RESULT": "Result",
|
||||
@@ -468,12 +503,6 @@
|
||||
"WAIT_TO_UPLOAD": "Wait to Upload",
|
||||
"ALREADY_UPLOAD": "Already Uploaded",
|
||||
"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_RESET_SUCCEED": "Reset successfully",
|
||||
"MANAGE_SETTING_TITLE": "Manage Setting",
|
||||
|
||||
@@ -418,6 +418,59 @@
|
||||
"checkProblemWithProxy": "检查代理设置是否正常",
|
||||
"fixDoneNeedReload": "修复完成,需要重启生效,是否重启",
|
||||
"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": "打开主窗口",
|
||||
@@ -435,26 +488,8 @@
|
||||
"COPY": "复制",
|
||||
"DELETE": "删除",
|
||||
"SELECT_ALL": "全选",
|
||||
"CHANGE_IMAGE_URL": "修改图片URL",
|
||||
"CHANGE_IMAGE_URL_SUCCEED": "修改图片URL成功",
|
||||
"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_OPEN_CONFIG_FILE": "打开配置文件",
|
||||
"SETTINGS_SET_SHORTCUT": "设置快捷键",
|
||||
"SETTINGS_SET_SERVER": "设置Server",
|
||||
"SETTINGS_OPEN": "开",
|
||||
"SETTINGS_CLOSE": "关",
|
||||
"SETTINGS_RESULT": "设置结果",
|
||||
@@ -463,12 +498,6 @@
|
||||
"WAIT_TO_UPLOAD": "等待上传",
|
||||
"ALREADY_UPLOAD": "已上传",
|
||||
"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_RESET_SUCCEED": "重置成功",
|
||||
"MANAGE_SETTING_TITLE": "管理页面设置",
|
||||
|
||||
@@ -418,6 +418,59 @@
|
||||
"checkProblemWithProxy": "檢查代理設置是否正常",
|
||||
"fixDoneNeedReload": "修復完成,需要重啟生效,是否重啟",
|
||||
"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": "打開主視窗",
|
||||
@@ -435,26 +488,8 @@
|
||||
"COPY": "複製",
|
||||
"DELETE": "刪除",
|
||||
"SELECT_ALL": "全選",
|
||||
"CHANGE_IMAGE_URL": "修改圖片URL",
|
||||
"CHANGE_IMAGE_URL_SUCCEED": "修改圖片URL成功",
|
||||
"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_OPEN_CONFIG_FILE": "打開設定檔案",
|
||||
"SETTINGS_SET_SHORTCUT": "設定快捷鍵",
|
||||
"SETTINGS_SET_SERVER": "設定Server",
|
||||
"SETTINGS_OPEN": "開",
|
||||
"SETTINGS_CLOSE": "關",
|
||||
"SETTINGS_RESULT": "設定結果",
|
||||
@@ -463,12 +498,6 @@
|
||||
"WAIT_TO_UPLOAD": "等待上傳",
|
||||
"ALREADY_UPLOAD": "已上傳",
|
||||
"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_RESET_SUCCEED": "重置成功",
|
||||
"MANAGE_SETTING_TITLE": "管理設定",
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import 'element-plus/dist/index.css'
|
||||
import 'vue3-photo-preview/dist/index.css'
|
||||
import 'video.js/dist/video-js.css'
|
||||
import 'highlight.js/styles/stackoverflow-light.css'
|
||||
import 'highlight.js/lib/common'
|
||||
@@ -12,7 +11,6 @@ import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
|
||||
import { createApp } from 'vue'
|
||||
import { createI18n } from 'vue-i18n'
|
||||
import VueLazyLoad from 'vue3-lazyload'
|
||||
import vue3PhotoPreview from 'vue3-photo-preview'
|
||||
|
||||
import App from '@/App.vue'
|
||||
import en from '@/i18n/locales/en.json'
|
||||
@@ -54,7 +52,6 @@ app.use(i18n)
|
||||
app.use(ElementUI)
|
||||
app.use(router)
|
||||
app.use(store)
|
||||
app.use(vue3PhotoPreview)
|
||||
app.use(pinia)
|
||||
app.use(hljsVuePlugin)
|
||||
app.use(VueVideoPlayer)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -259,8 +259,8 @@ const handleFix = async () => {
|
||||
})
|
||||
|
||||
confirm({
|
||||
title: t('pages.toolbox.fixDoneNeedReload'),
|
||||
message: t('pages.toolbox.notice'),
|
||||
title: t('pages.toolbox.notice'),
|
||||
message: t('pages.toolbox.fixDoneNeedReload'),
|
||||
type: 'warning',
|
||||
confirmButtonText: t('common.confirm'),
|
||||
cancelButtonText: t('common.cancel'),
|
||||
|
||||
1515
src/renderer/pages/css/Gallery.css
Normal file
1515
src/renderer/pages/css/Gallery.css
Normal file
File diff suppressed because it is too large
Load Diff
@@ -729,14 +729,12 @@ small {
|
||||
padding: 0.5rem 1rem;
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.4;
|
||||
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
border-radius: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.placeholder-item:hover {
|
||||
background: rgba(var(--color-accent-rgb), 0.08);
|
||||
transform: translateX(2px);
|
||||
}
|
||||
|
||||
.placeholder-item code {
|
||||
|
||||
@@ -637,17 +637,6 @@ html, body {
|
||||
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 */
|
||||
@media (max-width: 768px) {
|
||||
.plugin-container {
|
||||
|
||||
@@ -356,28 +356,6 @@
|
||||
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 */
|
||||
@media (max-width: 768px) {
|
||||
.shortkey-container {
|
||||
|
||||
@@ -33,6 +33,7 @@ export interface IConfigStruct {
|
||||
shortKey: {
|
||||
[key: string]: IShortKeyConfig
|
||||
}
|
||||
isAlwaysForceReload: boolean
|
||||
logLevel: string[]
|
||||
logPath: string
|
||||
logFileSizeLimit: number
|
||||
@@ -120,6 +121,7 @@ export const configPaths = {
|
||||
_path: 'settings.shortKey',
|
||||
'picgo:upload': 'settings.shortKey[picgo:upload]'
|
||||
},
|
||||
isAlwaysForceReload: 'settings.isAlwaysForceReload',
|
||||
logLevel: 'settings.logLevel',
|
||||
logPath: 'settings.logPath',
|
||||
logFileSizeLimit: 'settings.logFileSizeLimit',
|
||||
|
||||
Reference in New Issue
Block a user