mirror of
https://github.com/Kuingsmile/PicList.git
synced 2026-05-06 20:42:57 +08:00
✨ Feature(custom): support adjust grid size in gallery page
ISSUES CLOSED: #419
This commit is contained in:
@@ -50,6 +50,7 @@
|
|||||||
"@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.4.7",
|
"@smithy/node-http-handler": "^4.4.7",
|
||||||
|
"@vueuse/core": "^14.1.0",
|
||||||
"ali-oss": "^6.23.0",
|
"ali-oss": "^6.23.0",
|
||||||
"axios": "^1.13.2",
|
"axios": "^1.13.2",
|
||||||
"chalk": "^5.6.2",
|
"chalk": "^5.6.2",
|
||||||
|
|||||||
@@ -45,6 +45,7 @@
|
|||||||
"dateRange": "Date Range",
|
"dateRange": "Date Range",
|
||||||
"delete": "Delete",
|
"delete": "Delete",
|
||||||
"edit": "Edit",
|
"edit": "Edit",
|
||||||
|
"gridSize": "Grid Size",
|
||||||
"gridView": "Grid",
|
"gridView": "Grid",
|
||||||
"haveDuplicate": "There are naming duplicates among the selected images, do you want to continue?",
|
"haveDuplicate": "There are naming duplicates among the selected images, do you want to continue?",
|
||||||
"hideFilters": "Filters",
|
"hideFilters": "Filters",
|
||||||
|
|||||||
@@ -45,6 +45,7 @@
|
|||||||
"dateRange": "日期范围",
|
"dateRange": "日期范围",
|
||||||
"delete": "删除",
|
"delete": "删除",
|
||||||
"edit": "编辑",
|
"edit": "编辑",
|
||||||
|
"gridSize": "网格大小",
|
||||||
"gridView": "网格",
|
"gridView": "网格",
|
||||||
"haveDuplicate": "已选中的图片中有命名重复, 是否继续?",
|
"haveDuplicate": "已选中的图片中有命名重复, 是否继续?",
|
||||||
"hideFilters": "过滤器",
|
"hideFilters": "过滤器",
|
||||||
|
|||||||
@@ -45,6 +45,7 @@
|
|||||||
"dateRange": "日期範圍",
|
"dateRange": "日期範圍",
|
||||||
"delete": "刪除",
|
"delete": "刪除",
|
||||||
"edit": "編輯",
|
"edit": "編輯",
|
||||||
|
"gridSize": "網格大小",
|
||||||
"gridView": "網格",
|
"gridView": "網格",
|
||||||
"haveDuplicate": "已選中的圖片中有命名重複, 是否繼續?",
|
"haveDuplicate": "已選中的圖片中有命名重複, 是否繼續?",
|
||||||
"hideFilters": "過濾器",
|
"hideFilters": "過濾器",
|
||||||
|
|||||||
@@ -13,6 +13,18 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="header-actions">
|
<div class="header-actions">
|
||||||
|
<div class="grid-size-control">
|
||||||
|
<GridIcon :size="14" />
|
||||||
|
<input
|
||||||
|
v-model.number="userGridColumns"
|
||||||
|
type="range"
|
||||||
|
min="1"
|
||||||
|
max="15"
|
||||||
|
step="1"
|
||||||
|
class="grid-slider"
|
||||||
|
:title="t('pages.gallery.gridSize')"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
<div class="sync-delete-toggle">
|
<div class="sync-delete-toggle">
|
||||||
<span class="toggle-label">{{ t('pages.gallery.isAlwaysForceReload') }}</span>
|
<span class="toggle-label">{{ t('pages.gallery.isAlwaysForceReload') }}</span>
|
||||||
<label class="custom-switch">
|
<label class="custom-switch">
|
||||||
@@ -195,7 +207,7 @@
|
|||||||
:items="filterList"
|
:items="filterList"
|
||||||
:item-height="itemHeight"
|
:item-height="itemHeight"
|
||||||
:grid-items="4"
|
:grid-items="4"
|
||||||
:grid-breakpoints="gridBreakpoints"
|
:grid-breakpoints="effectiveGridBreakpoints"
|
||||||
key-field="key"
|
key-field="key"
|
||||||
:page-mode="true"
|
:page-mode="true"
|
||||||
:buffer-factor="0.5"
|
:buffer-factor="0.5"
|
||||||
@@ -468,6 +480,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
import { useStorage } from '@vueuse/core'
|
||||||
import {
|
import {
|
||||||
CheckSquareIcon,
|
CheckSquareIcon,
|
||||||
ChevronDownIcon,
|
ChevronDownIcon,
|
||||||
@@ -564,18 +577,15 @@ const dateRangeEnd = ref('')
|
|||||||
const picBedDropdownOpen = ref(false)
|
const picBedDropdownOpen = ref(false)
|
||||||
const sortDropdownOpen = ref(false)
|
const sortDropdownOpen = ref(false)
|
||||||
const showFormatInfo = ref(false)
|
const showFormatInfo = ref(false)
|
||||||
const viewMode = ref<'list' | 'grid'>('grid')
|
const viewMode = useStorage<'list' | 'grid'>('galleryViewMode', 'grid')
|
||||||
const componentKey = ref(0)
|
const componentKey = ref(0)
|
||||||
const currentSortField = ref<'name' | 'time' | 'ext' | 'check'>('name')
|
const currentSortField = ref<'name' | 'time' | 'ext' | 'check'>('name')
|
||||||
|
const userGridColumns = useStorage<number>('galleryGridColumns', 4)
|
||||||
const itemHeight = 300
|
const itemHeight = 300
|
||||||
const gridBreakpoints = [
|
|
||||||
{ min: 0, cols: 1 },
|
const effectiveGridBreakpoints = computed(() => {
|
||||||
{ min: 380, cols: 2 },
|
return [{ min: 0, cols: userGridColumns.value }]
|
||||||
{ min: 768, cols: 3 },
|
})
|
||||||
{ min: 1024, cols: 4 },
|
|
||||||
{ min: 1280, cols: 6 },
|
|
||||||
{ min: 1536, cols: 7 },
|
|
||||||
]
|
|
||||||
|
|
||||||
const imageLoadStates = reactive<Record<string, boolean>>({})
|
const imageLoadStates = reactive<Record<string, boolean>>({})
|
||||||
const imageErrorStates = reactive<Record<string, boolean>>({})
|
const imageErrorStates = reactive<Record<string, boolean>>({})
|
||||||
@@ -961,7 +971,6 @@ function handleImageTouchEnd(event: TouchEvent) {
|
|||||||
|
|
||||||
function toggleViewMode() {
|
function toggleViewMode() {
|
||||||
viewMode.value = viewMode.value === 'grid' ? 'list' : 'grid'
|
viewMode.value = viewMode.value === 'grid' ? 'list' : 'grid'
|
||||||
localStorage.setItem('galleryViewMode', viewMode.value)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function getViewModeIcon() {
|
function getViewModeIcon() {
|
||||||
@@ -982,7 +991,6 @@ onBeforeRouteUpdate((to, from) => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
async function initConf() {
|
async function initConf() {
|
||||||
viewMode.value = (localStorage.getItem('galleryViewMode') as 'list' | 'grid') || 'grid'
|
|
||||||
pasteStyle.value = (await getConfig(configPaths.settings.pasteStyle)) || IPasteStyle.MARKDOWN
|
pasteStyle.value = (await getConfig(configPaths.settings.pasteStyle)) || IPasteStyle.MARKDOWN
|
||||||
useShortUrl.value = (await getConfig(configPaths.settings.useShortUrl))
|
useShortUrl.value = (await getConfig(configPaths.settings.useShortUrl))
|
||||||
? t('pages.gallery.shortUrl')
|
? t('pages.gallery.shortUrl')
|
||||||
@@ -1104,6 +1112,14 @@ watch(filterList, () => {
|
|||||||
clearChoosedList()
|
clearChoosedList()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
watch(userGridColumns, _ => {
|
||||||
|
nextTick(() => {
|
||||||
|
if (virtualScrollerRef.value) {
|
||||||
|
virtualScrollerRef.value.refresh()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
let searchDebounceTimer: ReturnType<typeof setTimeout> | null = null
|
let searchDebounceTimer: ReturnType<typeof setTimeout> | null = null
|
||||||
let searchURLDebounceTimer: ReturnType<typeof setTimeout> | null = null
|
let searchURLDebounceTimer: ReturnType<typeof setTimeout> | null = null
|
||||||
|
|
||||||
|
|||||||
@@ -719,7 +719,6 @@
|
|||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<input v-model="customLink.value" type="text" class="form-input" :placeholder="''" />
|
<input v-model="customLink.value" type="text" class="form-input" :placeholder="''" />
|
||||||
</div>
|
</div>
|
||||||
<small> </small>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="dialog-footer">
|
<div class="dialog-footer">
|
||||||
<button class="btn btn-secondary" @click="cancelCustomLink">
|
<button class="btn btn-secondary" @click="cancelCustomLink">
|
||||||
|
|||||||
@@ -181,6 +181,78 @@ input:checked + .switch-slider::before {
|
|||||||
transform: translateX(20px);
|
transform: translateX(20px);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Grid Size Control */
|
||||||
|
.grid-size-control {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
border: 1px solid var(--color-border-secondary);
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
padding: 0.375rem 0.5rem;
|
||||||
|
background: var(--color-surface-elevated);
|
||||||
|
gap: 0.375rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.grid-size-control .control-label {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
font-weight: 500;
|
||||||
|
white-space: nowrap;
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.grid-slider {
|
||||||
|
border-radius: 2px;
|
||||||
|
width: 70px;
|
||||||
|
height: 4px;
|
||||||
|
background: var(--color-border);
|
||||||
|
outline: none;
|
||||||
|
appearance: none;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.grid-slider::-webkit-slider-thumb {
|
||||||
|
border-radius: 50%;
|
||||||
|
width: 14px;
|
||||||
|
height: 14px;
|
||||||
|
background: var(--color-blue-common);
|
||||||
|
transition: var(--transition-fast);
|
||||||
|
appearance: none;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.grid-slider::-webkit-slider-thumb:hover {
|
||||||
|
transform: scale(1.15);
|
||||||
|
box-shadow: 0 0 0 3px rgb(56 114 250 / 20%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.grid-slider::-moz-range-thumb {
|
||||||
|
border: none;
|
||||||
|
border-radius: 50%;
|
||||||
|
width: 14px;
|
||||||
|
height: 14px;
|
||||||
|
background: var(--color-blue-common);
|
||||||
|
transition: var(--transition-fast);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.grid-slider::-moz-range-thumb:hover {
|
||||||
|
transform: scale(1.15);
|
||||||
|
box-shadow: 0 0 0 3px rgb(56 114 250 / 20%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.grid-value {
|
||||||
|
display: inline-flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
border-radius: var(--radius-sm);
|
||||||
|
padding: 0.125rem 0.375rem;
|
||||||
|
min-width: 20px;
|
||||||
|
font-size: 0.6875rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: white;
|
||||||
|
background: var(--color-blue-common);
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
/* Filter Card */
|
/* Filter Card */
|
||||||
.filter-card {
|
.filter-card {
|
||||||
border-radius: var(--radius-lg);
|
border-radius: var(--radius-lg);
|
||||||
|
|||||||
24
yarn.lock
24
yarn.lock
@@ -4461,6 +4461,11 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@types/video.js/-/video.js-7.3.58.tgz#7e8cdafee25c75d6eb18f530b93ac52edff53c03"
|
resolved "https://registry.yarnpkg.com/@types/video.js/-/video.js-7.3.58.tgz#7e8cdafee25c75d6eb18f530b93ac52edff53c03"
|
||||||
integrity sha512-1CQjuSrgbv1/dhmcfQ83eVyYbvGyqhTvb2Opxr0QCV+iJ4J6/J+XWQ3Om59WiwCd1MN3rDUHasx5XRrpUtewYQ==
|
integrity sha512-1CQjuSrgbv1/dhmcfQ83eVyYbvGyqhTvb2Opxr0QCV+iJ4J6/J+XWQ3Om59WiwCd1MN3rDUHasx5XRrpUtewYQ==
|
||||||
|
|
||||||
|
"@types/web-bluetooth@^0.0.21":
|
||||||
|
version "0.0.21"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/web-bluetooth/-/web-bluetooth-0.0.21.tgz#525433c784aed9b457aaa0ee3d92aeb71f346b63"
|
||||||
|
integrity sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA==
|
||||||
|
|
||||||
"@types/write-file-atomic@^4.0.3":
|
"@types/write-file-atomic@^4.0.3":
|
||||||
version "4.0.3"
|
version "4.0.3"
|
||||||
resolved "https://registry.yarnpkg.com/@types/write-file-atomic/-/write-file-atomic-4.0.3.tgz#bda169b8369022e2c87028671fa4b742c08d98c9"
|
resolved "https://registry.yarnpkg.com/@types/write-file-atomic/-/write-file-atomic-4.0.3.tgz#bda169b8369022e2c87028671fa4b742c08d98c9"
|
||||||
@@ -4934,6 +4939,25 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.5.26.tgz#1e02ef2d64aced818cd31d81ce5175711dc90a9f"
|
resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.5.26.tgz#1e02ef2d64aced818cd31d81ce5175711dc90a9f"
|
||||||
integrity sha512-7Z6/y3uFI5PRoKeorTOSXKcDj0MSasfNNltcslbFrPpcw6aXRUALq4IfJlaTRspiWIUOEZbrpM+iQGmCOiWe4A==
|
integrity sha512-7Z6/y3uFI5PRoKeorTOSXKcDj0MSasfNNltcslbFrPpcw6aXRUALq4IfJlaTRspiWIUOEZbrpM+iQGmCOiWe4A==
|
||||||
|
|
||||||
|
"@vueuse/core@^14.1.0":
|
||||||
|
version "14.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@vueuse/core/-/core-14.1.0.tgz#274e98e591a505333b7dfb2bcaf7b4530a10b9c9"
|
||||||
|
integrity sha512-rgBinKs07hAYyPF834mDTigH7BtPqvZ3Pryuzt1SD/lg5wEcWqvwzXXYGEDb2/cP0Sj5zSvHl3WkmMELr5kfWw==
|
||||||
|
dependencies:
|
||||||
|
"@types/web-bluetooth" "^0.0.21"
|
||||||
|
"@vueuse/metadata" "14.1.0"
|
||||||
|
"@vueuse/shared" "14.1.0"
|
||||||
|
|
||||||
|
"@vueuse/metadata@14.1.0":
|
||||||
|
version "14.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@vueuse/metadata/-/metadata-14.1.0.tgz#70fc2e94775e4a07369f11f86f6f0a465b04a381"
|
||||||
|
integrity sha512-7hK4g015rWn2PhKcZ99NyT+ZD9sbwm7SGvp7k+k+rKGWnLjS/oQozoIZzWfCewSUeBmnJkIb+CNr7Zc/EyRnnA==
|
||||||
|
|
||||||
|
"@vueuse/shared@14.1.0":
|
||||||
|
version "14.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@vueuse/shared/-/shared-14.1.0.tgz#49b2face86a9c0c52e20eaf4c732a0223276c11f"
|
||||||
|
integrity sha512-EcKxtYvn6gx1F8z9J5/rsg3+lTQnvOruQd8fUecW99DCK04BkWD7z5KQ/wTAx+DazyoEE9dJt/zV8OIEQbM6kw==
|
||||||
|
|
||||||
"@xmldom/xmldom@^0.8.3":
|
"@xmldom/xmldom@^0.8.3":
|
||||||
version "0.8.6"
|
version "0.8.6"
|
||||||
resolved "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.6.tgz#8a1524eb5bd5e965c1e3735476f0262469f71440"
|
resolved "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.6.tgz#8a1524eb5bd5e965c1e3735476f0262469f71440"
|
||||||
|
|||||||
Reference in New Issue
Block a user