mirror of
https://github.com/Kuingsmile/PicList.git
synced 2025-01-23 06:38:13 -05:00
✨ Feature: add grid view for file explorer
This commit is contained in:
parent
d533f6f5f3
commit
69e1b48ecf
63
src/renderer/components/ImageWebdav.vue
Normal file
63
src/renderer/components/ImageWebdav.vue
Normal file
@ -0,0 +1,63 @@
|
||||
<template>
|
||||
<el-image
|
||||
:src="isShowThumbnail && item.isImage ?
|
||||
base64Url
|
||||
: require(`../manage/pages/assets/icons/${getFileIconPath(item.fileName ?? '')}`)"
|
||||
fit="contain"
|
||||
style="height: 100px;width: 100%;margin: 0 auto;"
|
||||
>
|
||||
<template #placeholder>
|
||||
<el-icon>
|
||||
<Loading />
|
||||
</el-icon>
|
||||
</template>
|
||||
<template #error>
|
||||
<el-image
|
||||
:src="require(`../manage/pages/assets/icons/${getFileIconPath(item.fileName ?? '')}`)"
|
||||
fit="contain"
|
||||
style="height: 100px;width: 100%;margin: 0 auto;"
|
||||
/>
|
||||
</template>
|
||||
</el-image>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, onBeforeMount } from 'vue'
|
||||
import { getFileIconPath } from '@/manage/utils/common'
|
||||
import { Loading } from '@element-plus/icons-vue'
|
||||
|
||||
const base64Url = ref('')
|
||||
const props = defineProps(
|
||||
{
|
||||
isShowThumbnail: {
|
||||
type: Boolean,
|
||||
required: true
|
||||
},
|
||||
item: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
url: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
headers: {
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
const urlCreateObjectURL = async () => {
|
||||
await fetch(props.url, {
|
||||
method: 'GET',
|
||||
headers: props.headers
|
||||
}).then(res => res.blob()).then(blob => {
|
||||
base64Url.value = URL.createObjectURL(blob)
|
||||
})
|
||||
}
|
||||
|
||||
onBeforeMount(async () => {
|
||||
await urlCreateObjectURL()
|
||||
})
|
||||
</script>
|
@ -369,6 +369,22 @@ ea/*
|
||||
</el-dropdown-item>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
<el-button-group
|
||||
size="small"
|
||||
style="margin-left: 10px;width: 80px;flex-shrink: 0;"
|
||||
type="primary"
|
||||
>
|
||||
<el-button
|
||||
:icon="Grid"
|
||||
:type="showFileStyle === 'grid' ? 'primary' : 'info'"
|
||||
@click="handleViewChange('grid')"
|
||||
/>
|
||||
<el-button
|
||||
:icon="Fold"
|
||||
:type="showFileStyle === 'list' ? 'primary' : 'info'"
|
||||
@click="handleViewChange('list')"
|
||||
/>
|
||||
</el-button-group>
|
||||
<el-input-number
|
||||
v-if="paging"
|
||||
v-model="currentPage"
|
||||
@ -409,14 +425,17 @@ https://www.baidu.com/img/bd_logo1.png"
|
||||
</template>
|
||||
</el-dialog>
|
||||
<div
|
||||
v-show="showFileStyle === 'list'"
|
||||
class="layout-table"
|
||||
style="margin: 0 15px 15px 15px;overflow-y: auto;overflow-x: hidden;height: 80vh;"
|
||||
>
|
||||
<el-auto-resizer>
|
||||
<template #default="{ height, width }">
|
||||
<template
|
||||
#default="{ height, width }"
|
||||
>
|
||||
<el-table-v2
|
||||
ref="elTable"
|
||||
:columns="columns"
|
||||
:columns="columns "
|
||||
:data="currentPageFilesInfo"
|
||||
:row-class="rowClass"
|
||||
:width="width"
|
||||
@ -425,6 +444,184 @@ https://www.baidu.com/img/bd_logo1.png"
|
||||
</template>
|
||||
</el-auto-resizer>
|
||||
</div>
|
||||
<div
|
||||
v-show="showFileStyle === 'grid'"
|
||||
class="layout-grid"
|
||||
style="margin: 0 15px 15px 15px;overflow-y: auto;overflow-x: hidden;height: 80vh;"
|
||||
>
|
||||
<el-col
|
||||
:span="24"
|
||||
>
|
||||
<el-row
|
||||
:gutter="16"
|
||||
>
|
||||
<el-col
|
||||
v-for="(item,index) in currentPageFilesInfo"
|
||||
:key="index"
|
||||
:xs="24"
|
||||
:sm="12"
|
||||
:md="8"
|
||||
:lg="3"
|
||||
:xl="2"
|
||||
>
|
||||
<el-card
|
||||
v-if="item.match || !searchText"
|
||||
:body-style="{ padding: '0px', height: '150px', width: '100%', background: item.checked ? '#f2f2f2' : '#fff' }"
|
||||
style="margin-bottom: 10px;"
|
||||
shadow="hover"
|
||||
>
|
||||
<el-image
|
||||
v-if="!item.isDir && currentPicBedName !== 'webdavplist'"
|
||||
:src="isShowThumbnail && item.isImage ?
|
||||
item.url
|
||||
: require(`./assets/icons/${getFileIconPath(item.fileName ?? '')}`)"
|
||||
fit="contain"
|
||||
style="height: 100px;width: 100%;margin: 0 auto;"
|
||||
@click="handleClickFile(item)"
|
||||
>
|
||||
<template #placeholder>
|
||||
<el-icon>
|
||||
<Loading />
|
||||
</el-icon>
|
||||
</template>
|
||||
<template #error>
|
||||
<el-image
|
||||
:src="require(`./assets/icons/${getFileIconPath(item.fileName ?? '')}`)"
|
||||
fit="contain"
|
||||
style="height: 100px;width: 100%;margin: 0 auto;"
|
||||
/>
|
||||
</template>
|
||||
</el-image>
|
||||
<ImageWebdav
|
||||
v-else-if="!item.isDir && currentPicBedName === 'webdavplist'"
|
||||
:is-show-thumbnail="isShowThumbnail"
|
||||
:item="item"
|
||||
:headers="getBase64ofWebdav()"
|
||||
:url="item.url"
|
||||
@click="handleClickFile(item)"
|
||||
/>
|
||||
<el-image
|
||||
v-else
|
||||
:src="require('./assets/icons/folder.png')"
|
||||
fit="contain"
|
||||
style="height: 100px;width: 100%;margin: 0 auto;"
|
||||
@click="handleClickFile(item)"
|
||||
/>
|
||||
<div
|
||||
style="align-items: center;display: flex;justify-content: center;"
|
||||
@click="copyToClipboard(item.fileName ?? '')"
|
||||
>
|
||||
<el-tooltip
|
||||
placement="top"
|
||||
effect="light"
|
||||
:content="item.fileName"
|
||||
>
|
||||
<el-link
|
||||
style="font-size: 12px;font-family: Arial, Helvetica, sans-serif;"
|
||||
:underline="false"
|
||||
:type="item.checked ? 'primary' : 'info'"
|
||||
>
|
||||
{{ formatFileName(item.fileName ?? '', 10) }}
|
||||
</el-link>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
<el-row
|
||||
style="display: flex;"
|
||||
justify="space-between"
|
||||
align="middle"
|
||||
>
|
||||
<el-row>
|
||||
<el-icon
|
||||
v-if="!item.isDir || !showRenameFileIcon"
|
||||
size="20"
|
||||
style="cursor: pointer;"
|
||||
color="#409EFF"
|
||||
@click="handleRenameFile(item)"
|
||||
>
|
||||
<Edit />
|
||||
</el-icon>
|
||||
<el-dropdown>
|
||||
<template #default>
|
||||
<el-icon
|
||||
size="20"
|
||||
style="cursor: pointer;"
|
||||
color="#409EFF"
|
||||
@click="copyToClipboard(formatLink(item.url, item.fileName, manageStore.config.settings.pasteForma ?? '$markdown', manageStore.config.settings.customPasteFormat ?? '$url'))"
|
||||
>
|
||||
<CopyDocument />
|
||||
</el-icon>
|
||||
</template>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item
|
||||
@click="copyToClipboard(formatLink(item.url, item.fileName, 'url'))"
|
||||
>
|
||||
Url
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item
|
||||
@click="copyToClipboard(formatLink(item.url, item.fileName, 'markdown'))"
|
||||
>
|
||||
Markdown
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item
|
||||
@click="copyToClipboard(formatLink(item.url, item.fileName, 'markdown-with-link'))"
|
||||
>
|
||||
Markdown-link
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item
|
||||
@click="copyToClipboard(formatLink(item.url, item.fileName, 'html'))"
|
||||
>
|
||||
Html
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item
|
||||
@click="copyToClipboard(formatLink(item.url, item.fileName, 'bbcode'))"
|
||||
>
|
||||
BBCode
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item
|
||||
@click="copyToClipboard(formatLink(item.url, item.fileName, 'custom', manageStore.config.settings.customPasteFormat))"
|
||||
>
|
||||
自定义
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item
|
||||
v-if="showPresignedUrl"
|
||||
@click="async () => {
|
||||
copyToClipboard(await getPreSignedUrl(item))
|
||||
}"
|
||||
>
|
||||
预签名链接
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
<el-icon
|
||||
size="20"
|
||||
style="cursor: pointer;"
|
||||
color="#409EFF"
|
||||
@click="handleShowFileInfo(item)"
|
||||
>
|
||||
<Document />
|
||||
</el-icon>
|
||||
<el-icon
|
||||
size="20"
|
||||
style="cursor: pointer;"
|
||||
color="#FFB6C1"
|
||||
@click="handleDeleteFile(item)"
|
||||
>
|
||||
<DeleteFilled />
|
||||
</el-icon>
|
||||
</el-row>
|
||||
<el-checkbox
|
||||
v-model="item.checked"
|
||||
size="large"
|
||||
@change="handleCheckChange(item)"
|
||||
/>
|
||||
</el-row>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-col>
|
||||
</div>
|
||||
<el-image-viewer
|
||||
v-if="showImagePreview"
|
||||
:url-list="ImagePreviewList"
|
||||
@ -997,9 +1194,9 @@ https://www.baidu.com/img/bd_logo1.png"
|
||||
<script lang="tsx" setup>
|
||||
import { ref, reactive, watch, onBeforeMount, computed, onBeforeUnmount } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { Close, Folder, FolderAdd, Upload, CircleClose, Loading, CopyDocument, Edit, DocumentAdd, Link, Refresh, ArrowRight, HomeFilled, Document, Coin, Download, DeleteFilled, Sort, FolderOpened } from '@element-plus/icons-vue'
|
||||
import { Grid, Fold, Close, Folder, FolderAdd, Upload, CircleClose, Loading, CopyDocument, Edit, DocumentAdd, Link, Refresh, ArrowRight, HomeFilled, Document, Coin, Download, DeleteFilled, Sort, FolderOpened } from '@element-plus/icons-vue'
|
||||
import { useManageStore } from '../store/manageStore'
|
||||
import { renameFile, formatLink, formatFileName, getFileIconPath, formatFileSize, getExtension, isValidUrl } from '../utils/common'
|
||||
import { renameFile, formatLink, formatFileName, getFileIconPath, formatFileSize, getExtension, isValidUrl, svg } from '../utils/common'
|
||||
import { ipcRenderer, clipboard, IpcRendererEvent } from 'electron'
|
||||
import { fileCacheDbInstance } from '../store/bucketFileDb'
|
||||
import { trimPath } from '~/main/manage/utils/common'
|
||||
@ -1017,7 +1214,8 @@ import {
|
||||
ElDropdownMenu,
|
||||
ElProgress,
|
||||
ElLink,
|
||||
ElTag
|
||||
ElTag,
|
||||
ElCard
|
||||
} from 'element-plus'
|
||||
import type { Column, RowClassNameGetter } from 'element-plus'
|
||||
import { useFileTransferStore } from '@/manage/store/manageStore'
|
||||
@ -1029,6 +1227,8 @@ import { getConfig, saveConfig } from '../utils/dataSender'
|
||||
import { marked } from 'marked'
|
||||
import { textFileExt } from '../utils/textfile'
|
||||
import { videoExt } from '../utils/videofile'
|
||||
import ImageWebdav from '@/components/ImageWebdav.vue'
|
||||
|
||||
/*
|
||||
configMap:{
|
||||
prefix: string, -> baseDir
|
||||
@ -1040,16 +1240,6 @@ configMap:{
|
||||
bucketConfig
|
||||
}
|
||||
*/
|
||||
const svg = `
|
||||
<path class="path" d="
|
||||
M 30 15
|
||||
L 28 17
|
||||
M 25.61 25.61
|
||||
A 15 15, 0, 0, 1, 15 30
|
||||
A 15 15, 0, 1, 1, 27.99 7.5
|
||||
L 15 15
|
||||
" style="stroke-width: 4px; fill: rgba(0, 0, 0, 0)"/>
|
||||
`
|
||||
|
||||
const linkArray = [
|
||||
{ key: 'Url', value: 'url' },
|
||||
@ -1108,6 +1298,7 @@ const textfileContent = ref('')
|
||||
const isShowVideoFileDialog = ref(false)
|
||||
const videoFileUrl = ref('')
|
||||
const videoPlayerHeaders = ref({})
|
||||
const showFileStyle = ref<'list' | 'grid'>('grid')
|
||||
|
||||
const showCustomUrlSelectList = computed(() => ['tcyun', 'aliyun', 'qiniu', 'github'].includes(currentPicBedName.value))
|
||||
|
||||
@ -1129,6 +1320,17 @@ const downloadedTaskList = computed(() => downloadTaskList.value.filter(item =>
|
||||
|
||||
const isAutoCustomUrl = computed(() => manageStore.config.picBed[configMap.alias].isAutoCustomUrl === undefined ? true : manageStore.config.picBed[configMap.alias].isAutoCustomUrl)
|
||||
|
||||
function handleViewChange (val: 'list' | 'grid') {
|
||||
showFileStyle.value = val
|
||||
}
|
||||
|
||||
function getBase64ofWebdav () {
|
||||
const headers = {
|
||||
Authorization: 'Basic ' + Buffer.from(`${manageStore.config.picBed[configMap.alias].username}:${manageStore.config.picBed[configMap.alias].password}`).toString('base64')
|
||||
}
|
||||
return headers
|
||||
}
|
||||
|
||||
function startRefreshUploadTask () {
|
||||
refreshUploadTaskId.value = setInterval(() => {
|
||||
ipcRenderer.invoke('getUploadTaskList').then((res: any) => {
|
||||
@ -1613,6 +1815,7 @@ async function resetParam (force: boolean = false) {
|
||||
previewedImage.value = ''
|
||||
isShowFileInfo.value = false
|
||||
lastChoosed.value = -1
|
||||
showFileStyle.value = manageStore.config.picBed[configMap.alias].isShowList ? 'list' : 'grid'
|
||||
if (!isAutoRefresh.value && !force && !paging.value) {
|
||||
const cachedData = await searchExistFileList()
|
||||
if (cachedData.length > 0) {
|
||||
@ -2769,21 +2972,14 @@ const columns: Column<any>[] = [
|
||||
item.match || !searchText.value
|
||||
? item.isDir || !showRenameFileIcon.value
|
||||
? <span></span>
|
||||
: <ElTooltip
|
||||
placement="top"
|
||||
content="重命名"
|
||||
effect='light'
|
||||
hide-after={150}
|
||||
: <ElIcon
|
||||
size="20"
|
||||
style="cursor: pointer;"
|
||||
color="#409EFF"
|
||||
onClick={() => handleRenameFile(item)}
|
||||
>
|
||||
<ElIcon
|
||||
size="20"
|
||||
style="cursor: pointer;"
|
||||
color="#409EFF"
|
||||
onClick={() => handleRenameFile(item)}
|
||||
>
|
||||
<Edit />
|
||||
</ElIcon>
|
||||
</ElTooltip>
|
||||
<Edit />
|
||||
</ElIcon>
|
||||
: <template></template>
|
||||
)
|
||||
},
|
||||
@ -2933,20 +3129,13 @@ const columns: Column<any>[] = [
|
||||
width: 30,
|
||||
cellRenderer: ({ rowData: item }) => (
|
||||
item.match || !searchText.value
|
||||
? <ElTooltip
|
||||
placement="top"
|
||||
content="删除"
|
||||
effect='light'
|
||||
hide-after={150}
|
||||
? <ElIcon
|
||||
style="cursor: pointer;"
|
||||
color="red"
|
||||
onClick={() => handleDeleteFile(item)}
|
||||
>
|
||||
<ElIcon
|
||||
style="cursor: pointer;"
|
||||
color="red"
|
||||
onClick={() => handleDeleteFile(item)}
|
||||
>
|
||||
<DeleteFilled />
|
||||
</ElIcon>
|
||||
</ElTooltip>
|
||||
<DeleteFilled />
|
||||
</ElIcon>
|
||||
: <template></template>
|
||||
)
|
||||
}
|
||||
|
@ -19,6 +19,15 @@
|
||||
show-icon
|
||||
center
|
||||
/>
|
||||
<div
|
||||
v-if="isLoading"
|
||||
v-loading="isLoading"
|
||||
element-loading-text="加载中..."
|
||||
:element-loading-spinner="svg"
|
||||
element-loading-svg-view-box="0, 0, 50, 50"
|
||||
element-loading-background="rgba(122, 122, 122, 0.5)"
|
||||
style="width: 100%;height: 100%;"
|
||||
/>
|
||||
<el-row>
|
||||
<el-col
|
||||
v-for="item in sortedAllConfigAliasMap"
|
||||
@ -255,7 +264,7 @@ import { getConfig, saveConfig, removeConfig } from '../utils/dataSender'
|
||||
import { shell } from 'electron'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useManageStore } from '../store/manageStore'
|
||||
import { formObjToTableData } from '../utils/common'
|
||||
import { formObjToTableData, svg } from '../utils/common'
|
||||
import { getConfig as getPicBedsConfig } from '@/utils/dataSender'
|
||||
import { formatEndpoint } from '~/main/manage/utils/common'
|
||||
|
||||
@ -266,6 +275,7 @@ const dataForTable = reactive([] as any[])
|
||||
const allConfigAliasMap = reactive({} as IStringKeyMap)
|
||||
const router = useRouter()
|
||||
const manageStore = useManageStore()
|
||||
const isLoading = ref(false)
|
||||
|
||||
const sortedAllConfigAliasMap = computed(() => {
|
||||
const sorted = Object.values(allConfigAliasMap).sort((a, b) => {
|
||||
@ -517,6 +527,7 @@ const handleConfigClick = async (item: any) => {
|
||||
}
|
||||
|
||||
function handleConfigImport (alias: string) {
|
||||
isLoading.value = true
|
||||
const selectedConfig = existingConfiguration[alias]
|
||||
if (selectedConfig) {
|
||||
supportedPicBedList[selectedConfig.picBedName].options.forEach((option: any) => {
|
||||
@ -528,6 +539,7 @@ function handleConfigImport (alias: string) {
|
||||
}
|
||||
})
|
||||
}
|
||||
isLoading.value = false
|
||||
}
|
||||
|
||||
async function getCurrentConfigList () {
|
||||
|
@ -105,6 +105,23 @@
|
||||
@change="handelIsShowThumbnailChange"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<template #label>
|
||||
<span
|
||||
style="position:absolute;left: 0;"
|
||||
>文件列表默认显示方式
|
||||
</span>
|
||||
</template>
|
||||
<el-switch
|
||||
v-model="form.isShowList"
|
||||
style="position:absolute;right: 0;"
|
||||
active-text="列表"
|
||||
inactive-text="卡片"
|
||||
active-color="#13ce66"
|
||||
inactive-color="orange"
|
||||
@change="handelIsShowListChange"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<template #label>
|
||||
<span
|
||||
@ -431,7 +448,8 @@ const form = reactive<IStringKeyMap>({
|
||||
customRename: false,
|
||||
isAutoRefresh: false,
|
||||
isIgnoreCase: false,
|
||||
isForceCustomUrlHttps: false
|
||||
isForceCustomUrlHttps: false,
|
||||
isShowList: false
|
||||
})
|
||||
|
||||
const downloadDir = ref('')
|
||||
@ -517,6 +535,12 @@ function handelIsShowThumbnailChange (val:ICheckBoxValueType) {
|
||||
})
|
||||
}
|
||||
|
||||
function handelIsShowListChange (val:ICheckBoxValueType) {
|
||||
saveConfig({
|
||||
'settings.isShowList': val
|
||||
})
|
||||
}
|
||||
|
||||
function handelisIgnoreCaseChange (val:ICheckBoxValueType) {
|
||||
saveConfig({
|
||||
'settings.isIgnoreCase': val
|
||||
|
@ -83,10 +83,10 @@ export function formatFileSize (size: number) {
|
||||
return `${(size / Math.pow(2, index * 10)).toFixed(2)} ${units[index]}`
|
||||
}
|
||||
|
||||
export function formatFileName (fileName: string) {
|
||||
export function formatFileName (fileName: string, length: number = 20) {
|
||||
const ext = path.extname(fileName)
|
||||
const name = path.basename(fileName, ext)
|
||||
return name.length > 20 ? `${name.slice(0, 20)}...${ext}` : fileName
|
||||
return name.length > length ? `${name.slice(0, length)}...${ext}` : fileName
|
||||
}
|
||||
|
||||
export const getExtension = (fileName: string) => path.extname(fileName).slice(1)
|
||||
@ -143,3 +143,14 @@ export const formatHttpProxy = (proxy: string | undefined, type: 'object' | 'str
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const svg = `
|
||||
<path class="path" d="
|
||||
M 30 15
|
||||
L 28 17
|
||||
M 25.61 25.61
|
||||
A 15 15, 0, 0, 1, 15 30
|
||||
A 15 15, 0, 1, 1, 27.99 7.5
|
||||
L 15 15
|
||||
" style="stroke-width: 4px; fill: rgba(0, 0, 0, 0)"/>
|
||||
`
|
||||
|
@ -138,10 +138,10 @@
|
||||
v-for="(item, index) in filterList"
|
||||
:key="item.id"
|
||||
:xs="24"
|
||||
:sm="8"
|
||||
:md="6"
|
||||
:lg="4"
|
||||
:xl="3"
|
||||
:sm="12"
|
||||
:md="8"
|
||||
:lg="3"
|
||||
:xl="2"
|
||||
class="gallery-list__img"
|
||||
>
|
||||
<div
|
||||
|
Loading…
Reference in New Issue
Block a user