From 2336483927dcdda8501eac93999e05a4b51727da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=90=8C=E8=90=8C=E5=93=92=E8=B5=AB=E8=90=9D?= <ma_shiqing@163.com> Date: Tue, 15 Aug 2023 00:00:27 -0700 Subject: [PATCH] :sparkles: Feature: optimize filename display --- src/main/manage/apis/local.ts | 40 +++++++------ src/renderer/components/ImageLocal.vue | 76 ------------------------ src/renderer/manage/pages/bucketPage.vue | 33 +++++----- src/renderer/manage/pages/logIn.vue | 7 ++- src/renderer/manage/utils/common.ts | 4 +- src/universal/utils/common.ts | 21 +++++++ 6 files changed, 65 insertions(+), 116 deletions(-) delete mode 100644 src/renderer/components/ImageLocal.vue diff --git a/src/main/manage/apis/local.ts b/src/main/manage/apis/local.ts index 6fe4de2..80d90c8 100644 --- a/src/main/manage/apis/local.ts +++ b/src/main/manage/apis/local.ts @@ -50,12 +50,12 @@ class LocalApi { transBack (filePath: string | undefined) { if (!filePath) return '' return this.isWindows - ? filePath.split(path.posix.sep).join(path.sep).replace(/^\\+/, '') - : `/${filePath.replace(/^\/+/, '')}` + ? filePath.split(path.posix.sep).join(path.sep).replace(/^\\+|\\+$/g, '') + : `/${filePath.replace(/^\/+|\/+$/g, '')}` } formatFolder (item: fs.Stats, urlPrefix: string, fileName: string, filePath: string) { - const key = this.transPathToUnix(filePath) + const key = `${this.transPathToUnix(filePath)}/`.replace(/\/+$/, '/') return { ...item, key, @@ -71,8 +71,8 @@ class LocalApi { } } - formatFile (item: fs.Stats, urlPrefix: string, fileName: string, filePath: string) { - const key = this.transPathToUnix(filePath) + formatFile (item: fs.Stats, urlPrefix: string, fileName: string, filePath: string, isDownload = false) { + const key = isDownload ? filePath : this.transPathToUnix(filePath) return { ...item, key, @@ -106,7 +106,7 @@ class LocalApi { finished: false } try { - res = fsWalk.walkSync(prefix, { + res = fsWalk.walkSync(this.transBack(prefix), { followSymbolicLinks: true, fs, stats: true, @@ -114,9 +114,9 @@ class LocalApi { }) if (res.length) { result.fullList.push( - ...res.data + ...res .filter((item: fsWalk.Entry) => item.stats?.isFile()) - .map((item: any) => this.formatFile(item, urlPrefix, item.name, item.path)) + .map((item: any) => this.formatFile(item, urlPrefix, item.name, item.path, true)) ) result.success = true } @@ -133,7 +133,7 @@ class LocalApi { const { customUrl = '', cancelToken, baseDir } = configMap let prefix = configMap.prefix prefix = this.transBack(prefix) - let urlPrefix = customUrl.replace(/\/+$/, '') + const urlPrefix = customUrl.replace(/\/+$/, '') let webPath = configMap.webPath || '' if (webPath && customUrl && webPath !== '/') { webPath = webPath.replace(/^\/+|\/+$/, '') @@ -156,20 +156,22 @@ class LocalApi { withFileTypes: true }) if (res.length) { + let urlPrefixF res.forEach((item: fs.Dirent) => { const pathOfFile = path.join(prefix, item.name) - const relativePath = path.relative(baseDir, pathOfFile) - const relative = webPath && urlPrefix + `/${path.join(webPath, relativePath)}`.replace(/\\/g, '/').replace(/\/+/g, '/') - if (webPath && customUrl) { - urlPrefix = relative + let relative + if (customUrl) { + const relativePath = path.relative(this.transBack(baseDir), pathOfFile) + relative = urlPrefix + `/${path.join(webPath, relativePath)}`.replace(/\\/g, '/').replace(/\/+/g, '/') + urlPrefixF = this.isWindows ? relative.replace(/\/[a-zA-Z]:\//, '/') : relative } else { - urlPrefix = pathOfFile + urlPrefixF = pathOfFile } const stats = fs.statSync(pathOfFile) if (item.isDirectory()) { - result.fullList.push(this.formatFolder(stats, urlPrefix, item.name, relativePath)) + result.fullList.push(this.formatFolder(stats, urlPrefixF, item.name, pathOfFile)) } else { - result.fullList.push(this.formatFile(stats, urlPrefix, item.name, relativePath)) + result.fullList.push(this.formatFile(stats, urlPrefixF, item.name, pathOfFile)) } }) result.success = true @@ -210,7 +212,7 @@ class LocalApi { const { key } = configMap let result = false try { - await fs.rmdir(this.transBack(key), { + await fs.rm(this.transBack(key), { recursive: true }) result = true @@ -241,7 +243,7 @@ class LocalApi { noProgress: true }) try { - fs.ensureFileSync(filePath) + fs.ensureFileSync(this.transBack(key)) await fs.copyFile(filePath, this.transBack(key)) instance.updateUploadTask({ id, @@ -281,7 +283,7 @@ class LocalApi { const instance = UpDownTaskQueue.getInstance() for (const item of fileArray) { const { alias, bucketName, key, fileName } = item - const savedFilePath = path.join(downloadPath, fileName) + const savedFilePath = path.join(downloadPath, fileName.replace(/[:*?"<>|]/g, '')) const id = `${alias}-${bucketName}-local-${key}` if (instance.getDownloadTask(id)) { continue diff --git a/src/renderer/components/ImageLocal.vue b/src/renderer/components/ImageLocal.vue deleted file mode 100644 index 9e0468f..0000000 --- a/src/renderer/components/ImageLocal.vue +++ /dev/null @@ -1,76 +0,0 @@ -<template> - <el-image - :src="isShowThumbnail && item.isImage ? - base64Image - : 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, watch } from 'vue' -import { getFileIconPath } from '@/manage/utils/common' -import { Loading } from '@element-plus/icons-vue' -import fs from 'fs-extra' -import mime from 'mime-types' -import path from 'path' - -const base64Image = ref('') -const props = defineProps( - { - isShowThumbnail: { - type: Boolean, - required: true - }, - item: { - type: Object, - required: true - }, - localPath: { - type: String, - required: true - } - } -) - -watch( - () => props.localPath, - async (newLocalPath, oldLocalPath) => { - if (newLocalPath !== oldLocalPath) { - try { - await createBase64Image() - } catch (e) { - console.log(e) - } - } - } -) - -const createBase64Image = async () => { - const filePath = path.normalize(props.localPath) - const base64 = await fs.readFile(filePath, 'base64') - base64Image.value = `data:${mime.lookup(filePath) || 'image/png'};base64,${base64}` -} - -onBeforeMount(async () => { - try { - await createBase64Image() - } catch (e) { - console.log(e) - } -}) -</script> diff --git a/src/renderer/manage/pages/bucketPage.vue b/src/renderer/manage/pages/bucketPage.vue index 00e75d7..c310052 100644 --- a/src/renderer/manage/pages/bucketPage.vue +++ b/src/renderer/manage/pages/bucketPage.vue @@ -59,7 +59,7 @@ class="icon" size="25px" > - <DocumentAdd /> + <Upload /> </el-icon> </el-tooltip> </el-button> @@ -80,7 +80,7 @@ size="25px" style="margin-left: 5px;" > - <Upload /> + <UploadFilled /> </el-icon> </el-tooltip> </el-button> @@ -497,7 +497,7 @@ https://www.baidu.com/img/bd_logo1.png" shadow="hover" > <el-image - v-if="!item.isDir && currentPicBedName !== 'webdavplist' && currentPicBedName !== 'local'" + v-if="!item.isDir && currentPicBedName !== 'webdavplist'" :src="isShowThumbnail && item.isImage ? item.url : require(`./assets/icons/${getFileIconPath(item.fileName ?? '')}`)" @@ -526,13 +526,6 @@ https://www.baidu.com/img/bd_logo1.png" :url="item.url" @click="handleClickFile(item)" /> - <ImageLocal - v-else-if="!item.isDir && currentPicBedName === 'local'" - :is-show-thumbnail="isShowThumbnail" - :item="item" - :local-path="item.key" - @click="handleClickFile(item)" - /> <el-image v-else :src="require('./assets/icons/folder.webp')" @@ -554,7 +547,7 @@ https://www.baidu.com/img/bd_logo1.png" :underline="false" :type="item.checked ? 'primary' : 'info'" > - {{ formatFileName(item.fileName ?? '', 8) }} + {{ formatFileName(item.fileName ?? '', 15) }} </el-link> </el-tooltip> </div> @@ -1422,7 +1415,7 @@ import { ref, reactive, watch, onBeforeMount, computed, onBeforeUnmount } from ' import { useRoute } from 'vue-router' // Element Plus 图标 -import { InfoFilled, 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 { InfoFilled, Grid, Fold, Close, Folder, FolderAdd, Upload, CircleClose, Loading, CopyDocument, Edit, UploadFilled, Link, Refresh, ArrowRight, HomeFilled, Document, Coin, Download, DeleteFilled, Sort, FolderOpened } from '@element-plus/icons-vue' // 状态管理相关 import { useManageStore } from '../store/manageStore' @@ -1492,7 +1485,6 @@ import { videoExt } from '../utils/videofile' // 组件 import ImageWebdav from '@/components/ImageWebdav.vue' -import ImageLocal from '@/components/ImageLocal.vue' // 国际化函数 import { T as $T } from '@/i18n' @@ -2113,6 +2105,16 @@ async function initCustomDomainList () { currentCustomDomain.value = endpoint } handleChangeCustomUrl() + } else if (currentPicBedName.value === 'local') { + const currentConfigs = await getConfig<any>('picBed') + const currentConfig = currentConfigs[configMap.alias] + const currentTransformedConfig = JSON.parse(currentConfig.transformedConfig ?? '{}') + if (currentTransformedConfig[configMap.bucketName] && currentTransformedConfig[configMap.bucketName]?.customUrl) { + currentCustomDomain.value = currentTransformedConfig[configMap.bucketName].customUrl ?? '' + } else { + currentCustomDomain.value = '' + } + handleChangeCustomUrl() } } @@ -2535,7 +2537,7 @@ function handleCreateFolder () { ElMessageBox.prompt($T('MANAGE_BUCKET_CREATE_FOLDER_BOX_TITLE'), $T('MANAGE_BUCKET_CREATE_FOLDER_BOX_TIP'), { confirmButtonText: $T('MANAGE_BUCKET_CREATE_FOLDER_BOX_CONFIRM'), cancelButtonText: $T('MANAGE_BUCKET_CREATE_FOLDER_BOX_CANCEL'), - inputPattern: /^[\u4e00-\u9fff_a-zA-Z0-9/]+$/, + inputPattern: /^[\p{Unified_Ideograph}_a-zA-Z0-9-]+$/u, inputErrorMessage: $T('MANAGE_BUCKET_CREATE_FOLDER_ERROR_MSG') }).then(async ({ value }) => { let formatedPath = value @@ -2801,7 +2803,6 @@ async function getBucketFileListBackStage () { param.baseDir = configMap.baseDir param.webPath = configMap.webPath } - console.log(param) ipcRenderer.send('getBucketListBackstage', configMap.alias, param) ipcRenderer.on('refreshFileTransferList', (evt: IpcRendererEvent, data) => { fileTransferStore.refreshFileTransferList(data) @@ -3445,7 +3446,7 @@ const columns: Column<any>[] = [ <div style="font-size: 14px;color: #303133;font-family: Arial, Helvetica, sans-serif;" > - {formatFileName(item.fileName ?? '')} + {formatFileName(item.fileName ?? '', 40)} </div> </ElTooltip> </div> diff --git a/src/renderer/manage/pages/logIn.vue b/src/renderer/manage/pages/logIn.vue index 873eab5..9423f1d 100644 --- a/src/renderer/manage/pages/logIn.vue +++ b/src/renderer/manage/pages/logIn.vue @@ -74,9 +74,9 @@ effect="light" :content="item.alias" placement="top" - :disabled="item.alias.length <= 15" + :disabled="isNeedToShorten(item.alias)" > - {{ item.alias.length > 15 ? item.alias.slice(0, 8) + '...' + item.alias.slice(-6) : item.alias }} + {{ isNeedToShorten(item.alias) ? safeSliceF(item.alias, 17) + '...' : item.alias }} </el-tooltip> </el-button> </template> @@ -290,6 +290,7 @@ import { getConfig as getPicBedsConfig } from '@/utils/dataSender' // 端点地址格式化 import { formatEndpoint } from '~/main/manage/utils/common' +import { isNeedToShorten, safeSliceF } from '#/utils/common' // 国际化函数 import { T as $T } from '@/i18n' @@ -373,7 +374,7 @@ async function handleConfigChange (name: string) { const aliasList = getAliasList() const allKeys = Object.keys(supportedPicBedList[name].configOptions) const resultMap:IStringKeyMap = {} - const reg = /^[\u4e00-\u9fff_a-zA-Z0-9-]+$/ + const reg = /^[\p{Unified_Ideograph}_a-zA-Z0-9-]+$/u for (const key of allKeys) { const resultKey = name + '.' + key if (supportedPicBedList[name].configOptions[key].required) { diff --git a/src/renderer/manage/utils/common.ts b/src/renderer/manage/utils/common.ts index 6ada585..6e7ae79 100644 --- a/src/renderer/manage/utils/common.ts +++ b/src/renderer/manage/utils/common.ts @@ -14,7 +14,7 @@ import { availableIconList } from './icon' import { getConfig } from './dataSender' // 工具函数 -import { handleUrlEncode } from '~/universal/utils/common' +import { handleUrlEncode, safeSliceF, isNeedToShorten } from '~/universal/utils/common' export function randomStringGenerator (length: number): string { const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789' @@ -103,7 +103,7 @@ export function formatFileName (fileName: string, length: number = 20) { let ext = path.extname(fileName) ext = ext.length > 5 ? ext.slice(ext.length - 5) : ext const name = path.basename(fileName, ext) - return name.length > length ? `${name.slice(0, length)}...${ext}` : fileName + return isNeedToShorten(fileName, length) ? `${safeSliceF(name, length - 3 - ext.length)}...${ext}` : fileName } export const getExtension = (fileName: string) => path.extname(fileName).slice(1) diff --git a/src/universal/utils/common.ts b/src/universal/utils/common.ts index 9875843..7fdbd23 100644 --- a/src/universal/utils/common.ts +++ b/src/universal/utils/common.ts @@ -44,3 +44,24 @@ export const isDev = process.env.NODE_ENV === 'development' export const trimValues = <T extends IStringKeyMap>(obj: T): {[K in keyof T]: T[K] extends string ? string : T[K]} => { return Object.fromEntries(Object.entries(obj).map(([key, value]) => [key, typeof value === 'string' ? value.trim() : value])) as {[K in keyof T]: T[K] extends string ? string : T[K]} } + +export function isNeedToShorten (alias: string, cutOff: number = 20) { + let totalLen = 0 + for (const s of alias) { + totalLen += s.charCodeAt(0) > 255 ? 2 : 1 + } + return totalLen > cutOff +} + +export function safeSliceF (str:string, total: number) { + let result = '' + let totalLen = 0 + for (const s of str) { + if (totalLen >= total) { + break + } + result += s + totalLen += s.charCodeAt(0) > 255 ? 2 : 1 + } + return result +}