Feature(custom): optimize manage main page

This commit is contained in:
Kuingsmile
2026-01-24 15:12:05 +08:00
parent f7430310d2
commit 6ac1b6413d
16 changed files with 682 additions and 1458 deletions

View File

@@ -31,6 +31,7 @@
### 🐛 问题修复 ### 🐛 问题修复
- 修复了管理页面中排序下拉框显示异常的问题 - 修复了管理页面中排序下拉框显示异常的问题
- 修复了管理页面图床列表没有正确为当前选中图床高亮的问题
- 修复了暗色模式下任务页面的显示问题 - 修复了暗色模式下任务页面的显示问题
- 修复了图床设置页面设置为默认图床按钮状态没有及时更新的问题 - 修复了图床设置页面设置为默认图床按钮状态没有及时更新的问题
- 修复了预处理设置页面,图床水印独立设置的按钮状态没有及时更新的问题 - 修复了预处理设置页面,图床水印独立设置的按钮状态没有及时更新的问题

View File

@@ -8,6 +8,8 @@
:title="t('pages.configForm.configName')" :title="t('pages.configForm.configName')"
:placeholder="t('pages.configForm.configNamePlaceholder')" :placeholder="t('pages.configForm.configNamePlaceholder')"
required required
:class="{ 'border-error!': validationErrors._configName }"
@blur="validateForm"
@input="clearFieldError('_configName')" @input="clearFieldError('_configName')"
/> />
<template v-if="validationErrors._configName" #extra> <template v-if="validationErrors._configName" #extra>
@@ -27,24 +29,10 @@
:class="{ 'border-error!': validationErrors[item.name] }" :class="{ 'border-error!': validationErrors[item.name] }"
:title="item.alias || item.name" :title="item.alias || item.name"
:required="item.required || false" :required="item.required || false"
:tips="item.tips"
@blur="validateForm"
@input="clearFieldError(item.name)" @input="clearFieldError(item.name)"
>
<template #title-extra>
<div v-if="showTooltips && item.tips" class="relative">
<div
class="flex h-[20px] w-[20px] cursor-pointer items-center justify-center rounded-full p-[2px] text-secondary hover:bg-bg-secondary hover:text-accent"
@click="toggleTooltip(item.name + index)"
>
<Info :size="15" />
</div>
<div
v-show="visibleTooltips[item.name + index]"
class="absolute top-full left-0 z-1000 max-w-[300px] min-w-[200px] rounded-md border border-border bg-bg-secondary p-3 text-xs leading-[1.4] text-main shadow-lg max-md:max-w-[250px] max-md:min-w-[150px]"
v-html="transformMarkdownToHTML(item.tips)"
/> />
</div>
</template>
</CustomInput>
<CustomSwitch <CustomSwitch
v-if="item.type === 'confirm'" v-if="item.type === 'confirm'"
v-model="ruleForm[item.name]" v-model="ruleForm[item.name]"
@@ -114,8 +102,6 @@
<script lang="ts" setup> <script lang="ts" setup>
import { cloneDeep, union } from 'lodash-es' import { cloneDeep, union } from 'lodash-es'
import { Info } from 'lucide-vue-next'
import { marked } from 'marked'
import { reactive, ref, watch } from 'vue' import { reactive, ref, watch } from 'vue'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import { useRoute } from 'vue-router' import { useRoute } from 'vue-router'
@@ -133,10 +119,9 @@ interface IProps {
type: 'uploader' | 'transformer' | 'plugin' type: 'uploader' | 'transformer' | 'plugin'
id: string id: string
mode?: 'picbed' | 'plugin' mode?: 'picbed' | 'plugin'
showTooltips?: boolean
} }
const { config: configProp, type, id, mode = 'picbed', showTooltips = true } = defineProps<IProps>() const { config: configProp, type, id, mode = 'picbed' } = defineProps<IProps>()
const $route = useRoute() const $route = useRoute()
const { t } = useI18n() const { t } = useI18n()
@@ -144,7 +129,6 @@ const { t } = useI18n()
const configList = ref<IPicGoPluginConfig[]>([]) const configList = ref<IPicGoPluginConfig[]>([])
const ruleForm = reactive<IStringKeyMap>({}) const ruleForm = reactive<IStringKeyMap>({})
const validationErrors = reactive<IStringKeyMap>({}) const validationErrors = reactive<IStringKeyMap>({})
const visibleTooltips = reactive<Record<string, boolean>>({})
// Watch for config changes // Watch for config changes
watch( watch(
@@ -197,16 +181,6 @@ function clearFieldError(fieldName: string) {
} }
} }
function toggleTooltip(key: string) {
visibleTooltips[key] = !visibleTooltips[key]
Object.keys(visibleTooltips).forEach(otherKey => {
if (otherKey !== key) {
visibleTooltips[otherKey] = false
}
})
}
async function validate(): Promise<IStringKeyMap | false> { async function validate(): Promise<IStringKeyMap | false> {
return new Promise(resolve => { return new Promise(resolve => {
const isValid = validateForm() const isValid = validateForm()
@@ -218,14 +192,6 @@ async function validate(): Promise<IStringKeyMap | false> {
}) })
} }
function transformMarkdownToHTML(markdown: string) {
try {
return marked.parse(markdown)
} catch (_e) {
return markdown
}
}
function getConfigType() { function getConfigType() {
switch (type) { switch (type) {
case 'plugin': { case 'plugin': {

View File

@@ -1,11 +1,24 @@
<template> <template>
<div class="flex flex-col"> <div class="flex flex-col">
<div class="flex items-center gap-2"> <div class="mb-1 flex items-center gap-2">
<label class="mb-2 text-sm font-semibold text-secondary" <label class="text-sm font-semibold text-secondary"
>{{ title }} >{{ title }}
<span v-if="required" class="ml-1 text-danger">*</span> <span v-if="required" class="ml-1 text-danger">*</span>
</label> </label>
<slot name="title-extra"></slot> <slot name="title-extra"></slot>
<div v-if="tips" class="relative">
<div
class="flex h-[20px] w-[20px] cursor-pointer items-center justify-center rounded-full p-[2px] text-secondary hover:bg-bg-secondary hover:text-accent"
@click="toggleTooltip()"
>
<Info :size="16" />
</div>
<div
v-show="visibleTooltips"
class="absolute top-full left-0 z-1000 max-w-[300px] min-w-[200px] rounded-md border border-border bg-bg-secondary p-3 text-xs leading-[1.4] text-main shadow-lg max-md:max-w-[250px] max-md:min-w-[150px]"
v-html="transformMarkdownToHTML(tips)"
/>
</div>
</div> </div>
<div class="relative w-full"> <div class="relative w-full">
<input <input
@@ -15,7 +28,6 @@
class="box-border w-full rounded-md border border-border bg-bg-tertiary p-3 pr-10 text-sm text-main transition-all duration-200 ease-apple focus:border-accent focus:outline-none disabled:cursor-not-allowed disabled:opacity-50" class="box-border w-full rounded-md border border-border bg-bg-tertiary p-3 pr-10 text-sm text-main transition-all duration-200 ease-apple focus:border-accent focus:outline-none disabled:cursor-not-allowed disabled:opacity-50"
:placeholder="placeholder" :placeholder="placeholder"
/> />
<button <button
v-if="isPassword" v-if="isPassword"
type="button" type="button"
@@ -31,7 +43,8 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { EyeClosedIcon, EyeIcon } from 'lucide-vue-next' import { EyeClosedIcon, EyeIcon, Info } from 'lucide-vue-next'
import { marked } from 'marked'
import { onMounted, ref } from 'vue' import { onMounted, ref } from 'vue'
const [modelValue, modifiers] = defineModel<any>({ const [modelValue, modifiers] = defineModel<any>({
@@ -49,12 +62,14 @@ const [modelValue, modifiers] = defineModel<any>({
}) })
const type = ref('text') const type = ref('text')
const visibleTooltips = ref(false)
const { const {
isPassword = false, isPassword = false,
title, title,
inputType = 'text', inputType = 'text',
placeholder, placeholder,
tips = '',
required = false, required = false,
} = defineProps<{ } = defineProps<{
isPassword?: boolean isPassword?: boolean
@@ -62,8 +77,21 @@ const {
inputType?: string inputType?: string
placeholder: string placeholder: string
required?: boolean required?: boolean
tips?: string
}>() }>()
function toggleTooltip() {
visibleTooltips.value = !visibleTooltips.value
}
function transformMarkdownToHTML(markdown: string) {
try {
return marked.parse(markdown)
} catch (_e) {
return markdown
}
}
defineOptions({ defineOptions({
inheritAttrs: false, inheritAttrs: false,
}) })

View File

@@ -4,7 +4,7 @@
class="flex cursor-pointer items-center gap-4 rounded-lg border border-border p-4 transition-all duration-200 ease-apple hover:border-accent" class="flex cursor-pointer items-center gap-4 rounded-lg border border-border p-4 transition-all duration-200 ease-apple hover:border-accent"
:class="noBorder ? 'border-none' : ''" :class="noBorder ? 'border-none' : ''"
> >
<input v-model="modelValue" type="checkbox" class="peer hidden" /> <input v-model="modelValue" type="checkbox" class="peer hidden" @change.stop="emit('change', modelValue)" />
<span <span
class="bg-linear-180-r relative shrink-0 rounded-full bg-gray-400/80 shadow-sm transition-all duration-medium ease-standard peer-checked:bg-accent peer-checked:shadow-[inset_0_1px_3px_rgba(0,0,0,0.1),0_2px_8px_color-mix(in_srgb,var(--color-accent),transparent_30%)] before:absolute before:rounded-full before:bg-white before:shadow-sm before:transition-all before:duration-200 before:ease-apple before:content-[''] peer-checked:before:translate-x-[24px]" class="bg-linear-180-r relative shrink-0 rounded-full bg-gray-400/80 shadow-sm transition-all duration-medium ease-standard peer-checked:bg-accent peer-checked:shadow-[inset_0_1px_3px_rgba(0,0,0,0.1),0_2px_8px_color-mix(in_srgb,var(--color-accent),transparent_30%)] before:absolute before:rounded-full before:bg-white before:shadow-sm before:transition-all before:duration-200 before:ease-apple before:content-[''] peer-checked:before:translate-x-[24px]"
:class=" :class="
@@ -26,7 +26,7 @@
</div> </div>
</label> </label>
<slot name="title-extra"></slot> <slot name="title-extra"></slot>
<div v-if="showTooltips && tips !== ''" class="relative"> <div v-if="tips" class="relative">
<div <div
class="flex h-[20px] w-[20px] cursor-pointer items-center justify-center rounded-full p-[2px] text-secondary hover:bg-bg-secondary hover:text-accent" class="flex h-[20px] w-[20px] cursor-pointer items-center justify-center rounded-full p-[2px] text-secondary hover:bg-bg-secondary hover:text-accent"
@click="toggleTooltip()" @click="toggleTooltip()"
@@ -47,6 +47,8 @@ import { Info } from 'lucide-vue-next'
import { marked } from 'marked' import { marked } from 'marked'
import { onMounted, ref } from 'vue' import { onMounted, ref } from 'vue'
const emit = defineEmits(['change'])
const visibleTooltips = ref(false) const visibleTooltips = ref(false)
const modelValue = defineModel<boolean>() const modelValue = defineModel<boolean>()
@@ -55,7 +57,6 @@ const {
description = '', description = '',
noBorder = false, noBorder = false,
small = false, small = false,
showTooltips = true,
tips = '', tips = '',
required = false, required = false,
} = defineProps<{ } = defineProps<{
@@ -63,7 +64,6 @@ const {
title?: string title?: string
description?: string description?: string
small?: boolean small?: boolean
showTooltips?: boolean
tips?: string tips?: string
required?: boolean required?: boolean
}>() }>()

View File

@@ -20,7 +20,7 @@
<div <div
v-show="dropDownOpen" v-show="dropDownOpen"
ref="optionsRef" ref="optionsRef"
class="sort-options fixed z-10 mt-[2px] min-w-[150px] overflow-hidden rounded-md border border-border-secondary bg-bg-tertiary shadow-lg" class="sort-options fixed z-10 mt-[2px] max-h-[200px] min-w-[150px] overflow-hidden overflow-y-auto rounded-md border border-border-secondary bg-bg-tertiary shadow-lg"
> >
<button <button
v-for="key in keyList" v-for="key in keyList"

View File

@@ -521,6 +521,7 @@
"noDataDesc": "Please create a bucket or upload images first" "noDataDesc": "Please create a bucket or upload images first"
}, },
"login": { "login": {
"aliasExistMsg": "Alias duplicate ",
"aliasMsg": "Alias may only contain Chinese characters, letters, numbers, underscores, and hyphens", "aliasMsg": "Alias may only contain Chinese characters, letters, numbers, underscores, and hyphens",
"configChangeMsg": "Configuration changed", "configChangeMsg": "Configuration changed",
"configSaveMsg": "Configuration saved", "configSaveMsg": "Configuration saved",

View File

@@ -521,6 +521,7 @@
"noDataDesc": "请先创建存储桶或上传图片" "noDataDesc": "请先创建存储桶或上传图片"
}, },
"login": { "login": {
"aliasExistMsg": "别名与已有配置重复",
"aliasMsg": "别名只能包含中文、英文、数字、下划线和中划线", "aliasMsg": "别名只能包含中文、英文、数字、下划线和中划线",
"configChangeMsg": "配置已更改", "configChangeMsg": "配置已更改",
"configSaveMsg": "配置已保存", "configSaveMsg": "配置已保存",

View File

@@ -521,6 +521,7 @@
"noDataDesc": "請先建立儲存桶或上傳圖片" "noDataDesc": "請先建立儲存桶或上傳圖片"
}, },
"login": { "login": {
"aliasExistMsg": "別名與已有配置重複",
"aliasMsg": "別名只能包含中文、英文、數字、底線和連字號", "aliasMsg": "別名只能包含中文、英文、數字、底線和連字號",
"configChangeMsg": "設定已變更", "configChangeMsg": "設定已變更",
"configSaveMsg": "設定已儲存", "configSaveMsg": "設定已儲存",

View File

@@ -1263,7 +1263,6 @@ import { marked } from 'marked'
import { v4 as uuidv4 } from 'uuid' import { v4 as uuidv4 } from 'uuid'
import { computed, nextTick, onBeforeMount, onBeforeUnmount, reactive, ref, useTemplateRef, watch } from 'vue' import { computed, nextTick, onBeforeMount, onBeforeUnmount, reactive, ref, useTemplateRef, watch } from 'vue'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import { useRoute } from 'vue-router'
import ImageLocal from '@/components/ImageLocal.vue' import ImageLocal from '@/components/ImageLocal.vue'
import ImagePreSign from '@/components/ImagePreSign.vue' import ImagePreSign from '@/components/ImagePreSign.vue'
@@ -1290,9 +1289,6 @@ import { trimPath } from '@/utils/common'
import { IRPCActionType } from '@/utils/enum' import { IRPCActionType } from '@/utils/enum'
import { cancelDownloadLoadingFileList, refreshDownloadFileTransferList } from '@/utils/static' import { cancelDownloadLoadingFileList, refreshDownloadFileTransferList } from '@/utils/static'
const { t } = useI18n()
const message = useMessage()
const confirm = useConfirm()
/* /*
configMap:{ configMap:{
prefix: string, -> baseDir prefix: string, -> baseDir
@@ -1304,37 +1300,28 @@ configMap:{
bucketConfig bucketConfig
} }
*/ */
const getExtension = (fileName: string) => window.node.path.extname(fileName).slice(1)
const linkFormatArray = [ const props = defineProps<{
{ key: 'Url', value: 'url' }, configMap: Record<string, any>
{ key: 'Markdown', value: 'markdown' }, }>()
{ key: 'Markdown-link', value: 'markdown-with-link' },
{ key: 'Html', value: 'html' },
{ key: 'BBCode', value: 'bbcode' },
{ key: 'Custom', value: 'custom' },
]
const linkFormatList = ['url', 'markdown', 'markdown-with-link', 'html', 'bbcode', 'custom']
type ISortTypeList = 'name' | 'size' | 'time' | 'ext' | 'check' | 'init' type ISortTypeList = 'name' | 'size' | 'time' | 'ext' | 'check' | 'init'
const sortTypeList = ['name', 'size', 'time', 'ext', 'check', 'init']
const currentSortType = ref<ISortTypeList>('name')
// 路由相关 let fileTransferInterval: NodeJS.Timeout | undefined
const route = useRoute() let downloadInterval: NodeJS.Timeout | undefined
let scrollTimeout: ReturnType<typeof setTimeout> | undefined
const { t } = useI18n()
const message = useMessage()
const confirm = useConfirm()
// 页面状态变量相关 // 页面状态变量相关
const manageStore = useManageStore() const manageStore = useManageStore()
const configMap = reactive(JSON.parse(route.query.configMap as string)) const configMap = ref<Record<string, any>>(JSON.parse(JSON.stringify(props.configMap)))
// 页面布局控制 // 页面布局控制
const isLoadingData = ref(false) const isLoadingData = ref(false)
const isShowLoadingPage = ref(false) const isShowLoadingPage = ref(false)
const isShowImagePreview = ref(false) const isShowImagePreview = ref(false)
const layoutStyle = ref<'list' | 'grid'>('grid')
// Refs for scroll handling
const virtualScrollerRef = useTemplateRef('virtualScrollerRef')
const bucketContainerRef = useTemplateRef('bucketContainerRef')
// 全屏控制变量
const isContentFullscreen = ref(false) const isContentFullscreen = ref(false)
// 新增的UI控制变量 const layoutStyle = ref<'list' | 'grid'>('grid')
const copyDropdownOpen = ref(false) const copyDropdownOpen = ref(false)
const sortDropdownOpen = ref(false) const sortDropdownOpen = ref(false)
const copyDropdownIndex = ref(-1) const copyDropdownIndex = ref(-1)
@@ -1376,57 +1363,26 @@ const uploadPanelFilesList = ref([] as any[])
const cancelToken = ref('') const cancelToken = ref('')
const isLoadingUploadPanelFiles = ref(false) const isLoadingUploadPanelFiles = ref(false)
const isUploadKeepDirStructure = ref(manageStore.config.settings.isUploadKeepDirStructure ?? true) const isUploadKeepDirStructure = ref(manageStore.config.settings.isUploadKeepDirStructure ?? true)
const uploadingTaskList = computed(() => const currentSortType = ref<ISortTypeList>('name')
uploadTaskList.value.filter(item => ['uploading', 'queuing', 'paused'].includes(item.status)),
)
const uploadedTaskList = computed(() =>
uploadTaskList.value.filter(item => ['uploaded', 'failed', 'canceled'].includes(item.status)),
)
// 下载页面相关 // 下载页面相关
const isShowDownloadPanel = ref(false) const isShowDownloadPanel = ref(false)
const isLoadingDownloadData = ref(false) const isLoadingDownloadData = ref(false)
const activeDownLoadTab = ref('downloading') const activeDownLoadTab = ref('downloading')
const currentDownloadFileList = reactive([] as any[]) const currentDownloadFileList = reactive([] as any[])
const downloadTaskList = ref([] as IDownloadTask[]) const downloadTaskList = ref([] as IDownloadTask[])
const refreshDownloadTaskId = ref<NodeJS.Timeout | undefined>(undefined)
const downloadCancelToken = ref('')
const downloadingTaskList = computed(() =>
downloadTaskList.value.filter(item => ['downloading', 'queuing', 'paused'].includes(item.status)),
)
const downloadedTaskList = computed(() =>
downloadTaskList.value.filter(item => ['downloaded', 'failed', 'canceled'].includes(item.status)),
)
// 上传文件相关 // 上传文件相关
const dialogVisible = ref(false) const dialogVisible = ref(false)
const urlToUpload = ref('') const urlToUpload = ref('')
// 图片预览相关 // 图片预览相关
const previewedImage = ref('') const previewedImage = ref('')
const filterList = computed(() => {
return getList()
})
const selectedItems = computed(() => filterList.value.filter(item => item.checked))
const ImagePreviewList = computed(() => filterList.value.filter(item => item.isImage).map(item => item.url))
const getCurrentPreviewIndex = computed(() => ImagePreviewList.value.indexOf(previewedImage.value))
// 快捷键相关 // 快捷键相关
const isShiftKeyPress = ref<boolean>(false) const isShiftKeyPress = ref<boolean>(false)
const lastChoosed = ref<number>(-1) const lastChoosed = ref<number>(-1)
// 自定义域名相关 // 自定义域名相关
const customDomainList = ref([] as any[]) const customDomainList = ref([] as any[])
const currentCustomDomain = ref('') const currentCustomDomain = ref('')
const isShowCustomDomainSelectList = computed(() => const refreshDownloadTaskId = ref<NodeJS.Timeout | undefined>(undefined)
['tcyun', 'aliyun', 'qiniu', 'github'].includes(currentPicBedName.value), const downloadCancelToken = ref('')
)
const isShowCustomDomainInput = computed(() =>
['aliyun', 'qiniu', 'tcyun', 's3plist', 'webdavplist', 'local', 'sftp'].includes(currentPicBedName.value),
)
const isAutoCustomDomain = computed(() =>
manageStore.config.picBed[configMap.alias].isAutoCustomUrl === undefined
? true
: manageStore.config.picBed[configMap.alias].isAutoCustomUrl,
)
// 文件预览相关 // 文件预览相关
const isShowMarkDownDialog = ref(false) const isShowMarkDownDialog = ref(false)
const markDownContent = ref('') const markDownContent = ref('')
@@ -1439,25 +1395,73 @@ const videoPlayerHeaders = ref({})
const isShowCreateFolderDialog = ref(false) const isShowCreateFolderDialog = ref(false)
const newFolderName = ref('') const newFolderName = ref('')
const folderNameInput = useTemplateRef('folderNameInput') const folderNameInput = useTemplateRef('folderNameInput')
// 重命名相关 // Refs for scroll handling
const isShowRenameFileIcon = computed(() => const virtualScrollerRef = useTemplateRef('virtualScrollerRef')
['tcyun', 'aliyun', 'qiniu', 'upyun', 's3plist', 'webdavplist', 'local', 'sftp'].includes(currentPicBedName.value), const bucketContainerRef = useTemplateRef('bucketContainerRef')
)
const isShowBatchRenameDialog = ref(false) const isShowBatchRenameDialog = ref(false)
const batchRenameMatch = ref('') const batchRenameMatch = ref('')
const batchRenameReplace = ref('') const batchRenameReplace = ref('')
const isRenameIncludeExt = ref(false) const isRenameIncludeExt = ref(false)
const isSingleRename = ref(false) const isSingleRename = ref(false)
const itemToBeRenamed = ref({} as any) const itemToBeRenamed = ref({} as any)
const previousPageNumber = ref(1)
let fileTransferInterval: NodeJS.Timeout | undefined const linkFormatArray = [
{ key: 'Url', value: 'url' },
{ key: 'Markdown', value: 'markdown' },
{ key: 'Markdown-link', value: 'markdown-with-link' },
{ key: 'Html', value: 'html' },
{ key: 'BBCode', value: 'bbcode' },
{ key: 'Custom', value: 'custom' },
]
const linkFormatList = ['url', 'markdown', 'markdown-with-link', 'html', 'bbcode', 'custom']
const sortTypeList = ['name', 'size', 'time', 'ext', 'check', 'init']
let downloadInterval: NodeJS.Timeout | undefined const uploadingTaskList = computed(() =>
uploadTaskList.value.filter(item => ['uploading', 'queuing', 'paused'].includes(item.status)),
)
const uploadedTaskList = computed(() =>
uploadTaskList.value.filter(item => ['uploaded', 'failed', 'canceled'].includes(item.status)),
)
const downloadingTaskList = computed(() =>
downloadTaskList.value.filter(item => ['downloading', 'queuing', 'paused'].includes(item.status)),
)
const downloadedTaskList = computed(() =>
downloadTaskList.value.filter(item => ['downloaded', 'failed', 'canceled'].includes(item.status)),
)
const filterList = computed(() => {
return getList()
})
const selectedItems = computed(() => filterList.value.filter(item => item.checked))
const ImagePreviewList = computed(() => filterList.value.filter(item => item.isImage).map(item => item.url))
const getCurrentPreviewIndex = computed(() => ImagePreviewList.value.indexOf(previewedImage.value))
const isShowCustomDomainSelectList = computed(() =>
['tcyun', 'aliyun', 'qiniu', 'github'].includes(currentPicBedName.value),
)
const isShowCustomDomainInput = computed(() =>
['aliyun', 'qiniu', 'tcyun', 's3plist', 'webdavplist', 'local', 'sftp'].includes(currentPicBedName.value),
)
const isAutoCustomDomain = computed(() =>
manageStore.config.picBed[configMap.value.alias].isAutoCustomUrl === undefined
? true
: manageStore.config.picBed[configMap.value.alias].isAutoCustomUrl,
)
// 重命名相关
const isShowRenameFileIcon = computed(() =>
['tcyun', 'aliyun', 'qiniu', 'upyun', 's3plist', 'webdavplist', 'local', 'sftp'].includes(currentPicBedName.value),
)
// 当前页面信息相关 // 当前页面信息相关
const currentPicBedName = computed<string>(() => manageStore.config.picBed[configMap.alias].picBedName) const currentPicBedName = computed<string>(() => manageStore.config.picBed[configMap.value.alias].picBedName)
const paging = computed(() => manageStore.config.picBed[configMap.alias].paging) const paging = computed(() => manageStore.config.picBed[configMap.value.alias].paging)
const itemsPerPage = computed(() => manageStore.config.picBed[configMap.alias].itemsPerPage) const itemsPerPage = computed(() => manageStore.config.picBed[configMap.value.alias].itemsPerPage)
const calculateAllFileSize = computed( const calculateAllFileSize = computed(
() => () =>
formatFileSize(currentPageFilesInfo.reduce((total: any, item: { fileSize: any }) => total + item.fileSize, 0)) || formatFileSize(currentPageFilesInfo.reduce((total: any, item: { fileSize: any }) => total + item.fileSize, 0)) ||
@@ -1479,6 +1483,56 @@ const isShowPresignedUrl = computed(() =>
['aliyun', 'github', 'qiniu', 's3plist', 'tcyun', 'webdavplist'].includes(currentPicBedName.value), ['aliyun', 'github', 'qiniu', 's3plist', 'tcyun', 'webdavplist'].includes(currentPicBedName.value),
) )
watch(
() => props.configMap,
async newValue => {
isShowLoadingPage.value = true
configMap.value = JSON.parse(JSON.stringify(newValue))
await initCustomDomainList()
await resetParam(true)
await manageStore.refreshConfig()
isShowLoadingPage.value = false
},
{ deep: true, immediate: true },
)
watch(currentPageNumber, (newVal, oldVal) => {
if (typeof newVal !== 'number') {
currentPageNumber.value = 1
}
// Update previousPageNumber when currentPageNumber changes programmatically
if (oldVal && typeof oldVal === 'number') {
previousPageNumber.value = oldVal
}
})
// Watch upload panel visibility to start/stop refresh task
watch(isShowUploadPanel, newValue => {
if (newValue) {
startRefreshUploadTask()
} else {
stopRefreshUploadTask()
}
})
// Watch download panel visibility to start/stop refresh task
watch(isShowDownloadPanel, newValue => {
if (newValue) {
startRefreshDownloadTask()
} else {
stopRefreshDownloadTask()
}
})
watch(
() => manageStore.config.settings.isUploadKeepDirStructure,
newValue => {
isUploadKeepDirStructure.value = newValue ?? true
},
)
const getExtension = (fileName: string) => window.node.path.extname(fileName).slice(1)
function getList() { function getList() {
if (!searchText.value) { if (!searchText.value) {
return currentPageFilesInfo return currentPageFilesInfo
@@ -1492,8 +1546,6 @@ function getList() {
}) })
} }
// 上传相关函数
function handleUploadKeepDirChange() { function handleUploadKeepDirChange() {
saveConfig('settings.isUploadKeepDirStructure', isUploadKeepDirStructure.value) saveConfig('settings.isUploadKeepDirStructure', isUploadKeepDirStructure.value)
manageStore.refreshConfig() manageStore.refreshConfig()
@@ -1516,7 +1568,7 @@ function stopRefreshUploadTask() {
} }
function handleGetWebdavConfig() { function handleGetWebdavConfig() {
return manageStore.config.picBed[configMap.alias] return manageStore.config.picBed[configMap.value.alias]
} }
// 下载相关函数 // 下载相关函数
@@ -1550,7 +1602,6 @@ function toggleContentFullscreen() {
isContentFullscreen.value = !isContentFullscreen.value isContentFullscreen.value = !isContentFullscreen.value
} }
let scrollTimeout: ReturnType<typeof setTimeout> | undefined
function handleBucketContainerScroll() { function handleBucketContainerScroll() {
if (scrollTimeout) { if (scrollTimeout) {
clearTimeout(scrollTimeout) clearTimeout(scrollTimeout)
@@ -1760,18 +1811,18 @@ function uploadFiles() {
} }
formateduploadPanelFilesList.forEach((item: any) => { formateduploadPanelFilesList.forEach((item: any) => {
param.fileArray.push({ param.fileArray.push({
alias: configMap.alias, alias: configMap.value.alias,
bucketName: configMap.bucketName, bucketName: configMap.value.bucketName,
region: configMap.bucketConfig.Location, region: configMap.value.bucketConfig.Location,
key: item.key, key: item.key,
filePath: item.path, filePath: item.path,
fileSize: item.size, fileSize: item.size,
fileName: item.rawName, fileName: item.rawName,
githubBranch: currentCustomDomain.value, githubBranch: currentCustomDomain.value,
aclForUpload: manageStore.config.picBed[configMap.alias].aclForUpload, aclForUpload: manageStore.config.picBed[configMap.value.alias].aclForUpload,
}) })
}) })
window.electron.sendRPC(IRPCActionType.MANAGE_UPLOAD_BUCKET_FILE, configMap.alias, param) window.electron.sendRPC(IRPCActionType.MANAGE_UPLOAD_BUCKET_FILE, configMap.value.alias, param)
} }
function handleCopyUploadingTaskInfo() { function handleCopyUploadingTaskInfo() {
@@ -1827,7 +1878,7 @@ async function handleBreadcrumbClick(index: number) {
isLoadingData.value = false isLoadingData.value = false
window.electron.sendToMain('cancelLoadingFileList', cancelToken.value) window.electron.sendToMain('cancelLoadingFileList', cancelToken.value)
} }
configMap.prefix = targetPrefix configMap.value.prefix = targetPrefix
isShowLoadingPage.value = true isShowLoadingPage.value = true
resetParam(false) resetParam(false)
isShowLoadingPage.value = false isShowLoadingPage.value = false
@@ -1837,7 +1888,7 @@ async function handleClickFile(item: any) {
const options = {} as any const options = {} as any
if (currentPicBedName.value === 'webdavplist') { if (currentPicBedName.value === 'webdavplist') {
options.headers = { options.headers = {
Authorization: `Basic ${window.node.buffer.from(`${manageStore.config.picBed[configMap.alias].username}:${manageStore.config.picBed[configMap.alias].password}`).toString('base64')}`, Authorization: `Basic ${window.node.buffer.from(`${manageStore.config.picBed[configMap.value.alias].username}:${manageStore.config.picBed[configMap.value.alias].password}`).toString('base64')}`,
} }
} }
if (item.isImage) { if (item.isImage) {
@@ -1848,7 +1899,7 @@ async function handleClickFile(item: any) {
isLoadingData.value = false isLoadingData.value = false
window.electron.sendToMain('cancelLoadingFileList', cancelToken.value) window.electron.sendToMain('cancelLoadingFileList', cancelToken.value)
} }
configMap.prefix = `/${item.key}` configMap.value.prefix = `/${item.key}`
isShowLoadingPage.value = true isShowLoadingPage.value = true
await resetParam(false) await resetParam(false)
isShowLoadingPage.value = false isShowLoadingPage.value = false
@@ -1892,17 +1943,17 @@ async function handleChangeCustomUrlInput() {
async function handleChangeCustomUrl() { async function handleChangeCustomUrl() {
if (['aliyun', 'tcyun', 'qiniu', 's3plist', 'webdavplist', 'local', 'sftp'].includes(currentPicBedName.value)) { if (['aliyun', 'tcyun', 'qiniu', 's3plist', 'webdavplist', 'local', 'sftp'].includes(currentPicBedName.value)) {
const currentConfigs = await getConfig<any>('picBed') const currentConfigs = await getConfig<any>('picBed')
const currentConfig = currentConfigs[configMap.alias] const currentConfig = currentConfigs[configMap.value.alias]
const currentTransformedConfig = JSON.parse(currentConfig.transformedConfig ?? '{}') const currentTransformedConfig = JSON.parse(currentConfig.transformedConfig ?? '{}')
if (currentTransformedConfig[configMap.bucketName]) { if (currentTransformedConfig[configMap.value.bucketName]) {
currentTransformedConfig[configMap.bucketName].customUrl = currentCustomDomain.value currentTransformedConfig[configMap.value.bucketName].customUrl = currentCustomDomain.value
} else { } else {
currentTransformedConfig[configMap.bucketName] = { currentTransformedConfig[configMap.value.bucketName] = {
customUrl: currentCustomDomain.value, customUrl: currentCustomDomain.value,
} }
} }
currentConfig.transformedConfig = JSON.stringify(currentTransformedConfig) currentConfig.transformedConfig = JSON.stringify(currentTransformedConfig)
saveConfig(`picBed.${configMap.alias}`, currentConfig) saveConfig(`picBed.${configMap.value.alias}`, currentConfig)
await manageStore.refreshConfig() await manageStore.refreshConfig()
} }
} }
@@ -1911,23 +1962,27 @@ async function handleChangeCustomUrl() {
async function initCustomDomainList() { async function initCustomDomainList() {
if ( if (
(['aliyun', 'tcyun', 'qiniu'].includes(currentPicBedName.value) && (['aliyun', 'tcyun', 'qiniu'].includes(currentPicBedName.value) &&
(manageStore.config.picBed[configMap.alias].isAutoCustomUrl === undefined || (manageStore.config.picBed[configMap.value.alias].isAutoCustomUrl === undefined ||
manageStore.config.picBed[configMap.alias].isAutoCustomUrl === true)) || manageStore.config.picBed[configMap.value.alias].isAutoCustomUrl === true)) ||
['github', 'smms', 'upyun', 'imgur'].includes(currentPicBedName.value) ['github', 'smms', 'upyun', 'imgur'].includes(currentPicBedName.value)
) { ) {
const param = { const param = {
bucketName: configMap.bucketName, bucketName: configMap.value.bucketName,
region: configMap.bucketConfig.Location, region: configMap.value.bucketConfig.Location,
} }
let defaultUrl = '' let defaultUrl = ''
if (currentPicBedName.value === 'tcyun') { if (currentPicBedName.value === 'tcyun') {
defaultUrl = `https://${configMap.bucketName}.cos.${configMap.bucketConfig.Location}.myqcloud.com` defaultUrl = `https://${configMap.value.bucketName}.cos.${configMap.value.bucketConfig.Location}.myqcloud.com`
} else if (currentPicBedName.value === 'aliyun') { } else if (currentPicBedName.value === 'aliyun') {
defaultUrl = `https://${configMap.bucketName}.${configMap.bucketConfig.Location}.aliyuncs.com` defaultUrl = `https://${configMap.value.bucketName}.${configMap.value.bucketConfig.Location}.aliyuncs.com`
} else if (currentPicBedName.value === 'github') { } else if (currentPicBedName.value === 'github') {
defaultUrl = 'main' defaultUrl = 'main'
} }
const res = await window.electron.triggerRPC<any>(IRPCActionType.MANAGE_GET_BUCKET_DOMAIN, configMap.alias, param) const res = await window.electron.triggerRPC<any>(
IRPCActionType.MANAGE_GET_BUCKET_DOMAIN,
configMap.value.alias,
param,
)
if (res.length > 0) { if (res.length > 0) {
customDomainList.value.length = 0 customDomainList.value.length = 0
res.forEach((item: any) => { res.forEach((item: any) => {
@@ -1958,49 +2013,52 @@ async function initCustomDomainList() {
} }
} else if (['aliyun', 'tcyun', 'qiniu'].includes(currentPicBedName.value)) { } else if (['aliyun', 'tcyun', 'qiniu'].includes(currentPicBedName.value)) {
const currentConfigs = await getConfig<any>('picBed') const currentConfigs = await getConfig<any>('picBed')
const currentConfig = currentConfigs[configMap.alias] const currentConfig = currentConfigs[configMap.value.alias]
const currentTransformedConfig = JSON.parse(currentConfig.transformedConfig ?? '{}') const currentTransformedConfig = JSON.parse(currentConfig.transformedConfig ?? '{}')
if (currentTransformedConfig[configMap.bucketName]) { if (currentTransformedConfig[configMap.value.bucketName]) {
currentCustomDomain.value = currentTransformedConfig[configMap.bucketName].customUrl ?? '' currentCustomDomain.value = currentTransformedConfig[configMap.value.bucketName].customUrl ?? ''
} else { } else {
currentCustomDomain.value = '' currentCustomDomain.value = ''
} }
} else if (currentPicBedName.value === 's3plist') { } else if (currentPicBedName.value === 's3plist') {
const currentConfigs = await getConfig<any>('picBed') const currentConfigs = await getConfig<any>('picBed')
const currentConfig = currentConfigs[configMap.alias] const currentConfig = currentConfigs[configMap.value.alias]
const currentTransformedConfig = JSON.parse(currentConfig.transformedConfig ?? '{}') const currentTransformedConfig = JSON.parse(currentConfig.transformedConfig ?? '{}')
if (currentTransformedConfig[configMap.bucketName]) { if (currentTransformedConfig[configMap.value.bucketName]) {
currentCustomDomain.value = currentTransformedConfig[configMap.bucketName].customUrl ?? '' currentCustomDomain.value = currentTransformedConfig[configMap.value.bucketName].customUrl ?? ''
} else { } else {
if (manageStore.config.picBed[configMap.alias].endpoint) { if (manageStore.config.picBed[configMap.value.alias].endpoint) {
const endpoint = manageStore.config.picBed[configMap.alias].endpoint const endpoint = manageStore.config.picBed[configMap.value.alias].endpoint
const s3ForcePathStyle = manageStore.config.picBed[configMap.alias].s3ForcePathStyle const s3ForcePathStyle = manageStore.config.picBed[configMap.value.alias].s3ForcePathStyle
let url let url
if (/^https?:\/\//.test(endpoint)) { if (/^https?:\/\//.test(endpoint)) {
url = new URL(endpoint) url = new URL(endpoint)
} else { } else {
url = new URL( url = new URL(
manageStore.config.picBed[configMap.alias].sslEnabled ? `https://${endpoint}` : `http://${endpoint}`, manageStore.config.picBed[configMap.value.alias].sslEnabled ? `https://${endpoint}` : `http://${endpoint}`,
) )
} }
if (s3ForcePathStyle) { if (s3ForcePathStyle) {
currentCustomDomain.value = `${url.protocol}//${url.hostname}${url.port ? ':' + url.port : ''}/${configMap.bucketName}` currentCustomDomain.value = `${url.protocol}//${url.hostname}${url.port ? ':' + url.port : ''}/${configMap.value.bucketName}`
} else { } else {
currentCustomDomain.value = `${url.protocol}//${configMap.bucketName}.${url.hostname}${url.port ? ':' + url.port : ''}` currentCustomDomain.value = `${url.protocol}//${configMap.value.bucketName}.${url.hostname}${url.port ? ':' + url.port : ''}`
} }
} else { } else {
currentCustomDomain.value = `https://${configMap.bucketName}.s3.amazonaws.com` currentCustomDomain.value = `https://${configMap.value.bucketName}.s3.amazonaws.com`
} }
} }
await handleChangeCustomUrl() await handleChangeCustomUrl()
} else if (currentPicBedName.value === 'webdavplist') { } else if (currentPicBedName.value === 'webdavplist') {
const currentConfigs = await getConfig<any>('picBed') const currentConfigs = await getConfig<any>('picBed')
const currentConfig = currentConfigs[configMap.alias] const currentConfig = currentConfigs[configMap.value.alias]
const currentTransformedConfig = JSON.parse(currentConfig.transformedConfig ?? '{}') const currentTransformedConfig = JSON.parse(currentConfig.transformedConfig ?? '{}')
if (currentTransformedConfig[configMap.bucketName] && currentTransformedConfig[configMap.bucketName]?.customUrl) { if (
currentCustomDomain.value = currentTransformedConfig[configMap.bucketName].customUrl currentTransformedConfig[configMap.value.bucketName] &&
currentTransformedConfig[configMap.value.bucketName]?.customUrl
) {
currentCustomDomain.value = currentTransformedConfig[configMap.value.bucketName].customUrl
} else { } else {
let endpoint = manageStore.config.picBed[configMap.alias].endpoint let endpoint = manageStore.config.picBed[configMap.value.alias].endpoint
if (!/^https?:\/\//.test(endpoint)) { if (!/^https?:\/\//.test(endpoint)) {
endpoint = 'http://' + endpoint endpoint = 'http://' + endpoint
} }
@@ -2009,10 +2067,13 @@ async function initCustomDomainList() {
await handleChangeCustomUrl() await handleChangeCustomUrl()
} else if (currentPicBedName.value === 'local' || currentPicBedName.value === 'sftp') { } else if (currentPicBedName.value === 'local' || currentPicBedName.value === 'sftp') {
const currentConfigs = await getConfig<any>('picBed') const currentConfigs = await getConfig<any>('picBed')
const currentConfig = currentConfigs[configMap.alias] const currentConfig = currentConfigs[configMap.value.alias]
const currentTransformedConfig = JSON.parse(currentConfig.transformedConfig ?? '{}') const currentTransformedConfig = JSON.parse(currentConfig.transformedConfig ?? '{}')
if (currentTransformedConfig[configMap.bucketName] && currentTransformedConfig[configMap.bucketName]?.customUrl) { if (
currentCustomDomain.value = currentTransformedConfig[configMap.bucketName].customUrl ?? '' currentTransformedConfig[configMap.value.bucketName] &&
currentTransformedConfig[configMap.value.bucketName]?.customUrl
) {
currentCustomDomain.value = currentTransformedConfig[configMap.value.bucketName].customUrl ?? ''
if (manageStore.config.settings.isForceCustomUrlHttps && currentCustomDomain.value.startsWith('http://')) { if (manageStore.config.settings.isForceCustomUrlHttps && currentCustomDomain.value.startsWith('http://')) {
currentCustomDomain.value = currentCustomDomain.value.replace('http://', 'https://') currentCustomDomain.value = currentCustomDomain.value.replace('http://', 'https://')
} }
@@ -2036,7 +2097,7 @@ async function resetParam(force: boolean = false) {
} }
cancelToken.value = '' cancelToken.value = ''
pagingMarker.value = '' pagingMarker.value = ''
currentPrefix.value = configMap.prefix currentPrefix.value = configMap.value.prefix
currentPageNumber.value = 1 currentPageNumber.value = 1
currentPageFilesInfo.length = 0 currentPageFilesInfo.length = 0
currentDownloadFileList.length = 0 currentDownloadFileList.length = 0
@@ -2085,18 +2146,6 @@ async function resetParam(force: boolean = false) {
} }
} }
watch(route, async newRoute => {
const queryConfigMap = newRoute.query.configMap as string
if (queryConfigMap) {
isShowLoadingPage.value = true
const parsedConfigMap = JSON.parse(queryConfigMap)
Object.assign(configMap, parsedConfigMap)
await initCustomDomainList()
await resetParam(true)
isShowLoadingPage.value = false
}
})
async function forceRefreshFileList() { async function forceRefreshFileList() {
if (isLoadingData.value) { if (isLoadingData.value) {
message.error(t('pages.manage.bucket.isLoadingMsg')) message.error(t('pages.manage.bucket.isLoadingMsg'))
@@ -2107,16 +2156,6 @@ async function forceRefreshFileList() {
isShowLoadingPage.value = false isShowLoadingPage.value = false
} }
watch(currentPageNumber, (newVal, oldVal) => {
if (typeof newVal !== 'number') {
currentPageNumber.value = 1
}
// Update previousPageNumber when currentPageNumber changes programmatically
if (oldVal && typeof oldVal === 'number') {
previousPageNumber.value = oldVal
}
})
const changePage = async (cur: number | undefined, prev: number | undefined) => { const changePage = async (cur: number | undefined, prev: number | undefined) => {
if (!cur || !prev) { if (!cur || !prev) {
currentPageNumber.value = 1 currentPageNumber.value = 1
@@ -2161,33 +2200,6 @@ const changePage = async (cur: number | undefined, prev: number | undefined) =>
} }
} }
// Watch upload panel visibility to start/stop refresh task
watch(isShowUploadPanel, newValue => {
if (newValue) {
startRefreshUploadTask()
} else {
stopRefreshUploadTask()
}
})
// Watch download panel visibility to start/stop refresh task
watch(isShowDownloadPanel, newValue => {
if (newValue) {
startRefreshDownloadTask()
} else {
stopRefreshDownloadTask()
}
})
watch(
() => manageStore.config.settings.isUploadKeepDirStructure,
newValue => {
isUploadKeepDirStructure.value = newValue ?? true
},
)
const previousPageNumber = ref(1)
const handlePageNumberInput = async (event: Event) => { const handlePageNumberInput = async (event: Event) => {
const target = event.target as HTMLInputElement const target = event.target as HTMLInputElement
const value = parseInt(target.value, 10) const value = parseInt(target.value, 10)
@@ -2294,9 +2306,9 @@ async function handleFolderBatchDownload(item: any) {
cancelToken.value = uuidv4() cancelToken.value = uuidv4()
const paramGet = { const paramGet = {
// tcyun // tcyun
bucketName: configMap.bucketName, bucketName: configMap.value.bucketName,
bucketConfig: { bucketConfig: {
Location: configMap.bucketConfig.Location, Location: configMap.value.bucketConfig.Location,
}, },
paging: paging.value, paging: paging.value,
prefix: `/${item.key.replace(/^\/+|\/+$/, '')}/`, prefix: `/${item.key.replace(/^\/+|\/+$/, '')}/`,
@@ -2305,12 +2317,12 @@ async function handleFolderBatchDownload(item: any) {
customUrl: currentCustomDomain.value, customUrl: currentCustomDomain.value,
currentPage: currentPageNumber.value, currentPage: currentPageNumber.value,
cancelToken: cancelToken.value, cancelToken: cancelToken.value,
cdnUrl: configMap.cdnUrl, cdnUrl: configMap.value.cdnUrl,
} }
isLoadingDownloadData.value = true isLoadingDownloadData.value = true
const downloadFileTransferStore = useDownloadFileTransferStore() const downloadFileTransferStore = useDownloadFileTransferStore()
downloadFileTransferStore.resetDownloadFileTransferList() downloadFileTransferStore.resetDownloadFileTransferList()
window.electron.sendRPC(IRPCActionType.MANAGE_GET_BUCKET_LIST_RECURSIVELY, configMap.alias, paramGet) window.electron.sendRPC(IRPCActionType.MANAGE_GET_BUCKET_LIST_RECURSIVELY, configMap.value.alias, paramGet)
window.electron.ipcRendererOn(refreshDownloadFileTransferList, data => { window.electron.ipcRendererOn(refreshDownloadFileTransferList, data => {
downloadFileTransferStore.refreshDownloadFileTransferList(data) downloadFileTransferStore.refreshDownloadFileTransferList(data)
}) })
@@ -2326,9 +2338,9 @@ async function handleFolderBatchDownload(item: any) {
if (currentDownloadFileList.length) { if (currentDownloadFileList.length) {
currentDownloadFileList.forEach((item: any) => { currentDownloadFileList.forEach((item: any) => {
param.fileArray.push({ param.fileArray.push({
alias: configMap.alias, alias: configMap.value.alias,
bucketName: configMap.bucketName, bucketName: configMap.value.bucketName,
region: configMap.bucketConfig.Location, region: configMap.value.bucketConfig.Location,
key: item.key, key: item.key,
fileName: [undefined, true].includes(manageStore.config.settings.isDownloadFolderKeepDirStructure) fileName: [undefined, true].includes(manageStore.config.settings.isDownloadFolderKeepDirStructure)
? `/${item.key.replace(/^\/+|\/+$/, '')}` ? `/${item.key.replace(/^\/+|\/+$/, '')}`
@@ -2336,11 +2348,11 @@ async function handleFolderBatchDownload(item: any) {
customUrl: currentCustomDomain.value, customUrl: currentCustomDomain.value,
downloadUrl: item.downloadUrl, downloadUrl: item.downloadUrl,
githubUrl: item.url, githubUrl: item.url,
githubPrivate: configMap.bucketConfig.private, githubPrivate: configMap.value.bucketConfig.private,
}) })
}) })
} }
window.electron.sendRPC(IRPCActionType.MANAGE_DOWNLOAD_BUCKET_FILE, configMap.alias, param) window.electron.sendRPC(IRPCActionType.MANAGE_DOWNLOAD_BUCKET_FILE, configMap.value.alias, param)
isShowDownloadPanel.value = true isShowDownloadPanel.value = true
} else { } else {
message.error(t('pages.manage.bucket.getDownloadListFailed')) message.error(t('pages.manage.bucket.getDownloadListFailed'))
@@ -2367,9 +2379,9 @@ async function handleBatchDownload() {
selectedItems.value.forEach((item: any) => { selectedItems.value.forEach((item: any) => {
if (!item.isDir) { if (!item.isDir) {
param.fileArray.push({ param.fileArray.push({
alias: configMap.alias, alias: configMap.value.alias,
bucketName: configMap.bucketName, bucketName: configMap.value.bucketName,
region: configMap.bucketConfig.Location, region: configMap.value.bucketConfig.Location,
key: item.key, key: item.key,
fileName: manageStore.config.settings.isDownloadFileKeepDirStructure fileName: manageStore.config.settings.isDownloadFileKeepDirStructure
? `/${item.key.replace(/^\/+|\/+$/, '')}` ? `/${item.key.replace(/^\/+|\/+$/, '')}`
@@ -2377,11 +2389,11 @@ async function handleBatchDownload() {
customUrl: currentCustomDomain.value, customUrl: currentCustomDomain.value,
downloadUrl: item.downloadUrl, downloadUrl: item.downloadUrl,
githubUrl: item.url, githubUrl: item.url,
githubPrivate: configMap.bucketConfig.private, githubPrivate: configMap.value.bucketConfig.private,
}) })
} }
}) })
window.electron.sendRPC(IRPCActionType.MANAGE_DOWNLOAD_BUCKET_FILE, configMap.alias, param) window.electron.sendRPC(IRPCActionType.MANAGE_DOWNLOAD_BUCKET_FILE, configMap.value.alias, param)
handleCancelCheck() handleCancelCheck()
isShowDownloadPanel.value = true isShowDownloadPanel.value = true
} }
@@ -2415,14 +2427,14 @@ async function confirmCreateFolder() {
formatedPath = trimPath(formatedPath) formatedPath = trimPath(formatedPath)
const param = { const param = {
// tcyun // tcyun
bucketName: configMap.bucketName, bucketName: configMap.value.bucketName,
region: configMap.bucketConfig.Location, region: configMap.value.bucketConfig.Location,
key: currentPrefix.value.slice(1) + formatedPath + '/', key: currentPrefix.value.slice(1) + formatedPath + '/',
githubBranch: currentCustomDomain.value, githubBranch: currentCustomDomain.value,
} }
const res = await window.electron.triggerRPC<any>( const res = await window.electron.triggerRPC<any>(
IRPCActionType.MANAGE_CREATE_BUCKET_FOLDER, IRPCActionType.MANAGE_CREATE_BUCKET_FOLDER,
configMap.alias, configMap.value.alias,
param, param,
) )
if (res) { if (res) {
@@ -2540,14 +2552,14 @@ async function BatchRename() {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const param = { const param = {
// tcyun // tcyun
bucketName: configMap.bucketName, bucketName: configMap.value.bucketName,
region: configMap.bucketConfig.Location, region: configMap.value.bucketConfig.Location,
oldKey: item.key, oldKey: item.key,
newKey: (item.key.slice(0, item.key.lastIndexOf('/') + 1) + item.newName).replaceAll('//', '/'), newKey: (item.key.slice(0, item.key.lastIndexOf('/') + 1) + item.newName).replaceAll('//', '/'),
customUrl: currentCustomDomain.value, customUrl: currentCustomDomain.value,
} }
window.electron window.electron
.triggerRPC<any>(IRPCActionType.MANAGE_RENAME_BUCKET_FILE, configMap.alias, param) .triggerRPC<any>(IRPCActionType.MANAGE_RENAME_BUCKET_FILE, configMap.value.alias, param)
.then((res: any) => { .then((res: any) => {
if (res) { if (res) {
successCount++ successCount++
@@ -2714,9 +2726,9 @@ async function getBucketFileListBackStage() {
cancelToken.value = uuidv4() cancelToken.value = uuidv4()
const param = { const param = {
// tcyun // tcyun
bucketName: configMap.bucketName, bucketName: configMap.value.bucketName,
bucketConfig: { bucketConfig: {
Location: configMap.bucketConfig.Location, Location: configMap.value.bucketConfig.Location,
}, },
paging: paging.value, paging: paging.value,
prefix: currentPrefix.value, prefix: currentPrefix.value,
@@ -2725,17 +2737,17 @@ async function getBucketFileListBackStage() {
customUrl: currentCustomDomain.value, customUrl: currentCustomDomain.value,
currentPage: currentPageNumber.value, currentPage: currentPageNumber.value,
cancelToken: cancelToken.value, cancelToken: cancelToken.value,
cdnUrl: configMap.cdnUrl, cdnUrl: configMap.value.cdnUrl,
} as IStringKeyMap } as IStringKeyMap
isLoadingData.value = true isLoadingData.value = true
const fileTransferStore = useFileTransferStore() const fileTransferStore = useFileTransferStore()
fileTransferStore.resetFileTransferList() fileTransferStore.resetFileTransferList()
const picBedNamesArr = ['webdavplist', 'local', 'sftp'] const picBedNamesArr = ['webdavplist', 'local', 'sftp']
if (picBedNamesArr.includes(currentPicBedName.value)) { if (picBedNamesArr.includes(currentPicBedName.value)) {
param.baseDir = configMap.baseDir param.baseDir = configMap.value.baseDir
param.webPath = configMap.webPath param.webPath = configMap.value.webPath
} }
window.electron.sendRPC(IRPCActionType.MANAGE_GET_BUCKET_LIST_BACKSTAGE, configMap.alias, param) window.electron.sendRPC(IRPCActionType.MANAGE_GET_BUCKET_LIST_BACKSTAGE, configMap.value.alias, param)
window.electron.ipcRendererOn('refreshFileTransferList', data => { window.electron.ipcRendererOn('refreshFileTransferList', data => {
fileTransferStore.refreshFileTransferList(data) fileTransferStore.refreshFileTransferList(data)
}) })
@@ -2769,9 +2781,9 @@ async function getBucketFileListBackStage() {
async function getBucketFileList() { async function getBucketFileList() {
const param = { const param = {
// tcyun // tcyun
bucketName: configMap.bucketName, bucketName: configMap.value.bucketName,
bucketConfig: { bucketConfig: {
Location: configMap.bucketConfig.Location, Location: configMap.value.bucketConfig.Location,
}, },
paging: paging.value, paging: paging.value,
prefix: currentPrefix.value, prefix: currentPrefix.value,
@@ -2780,7 +2792,7 @@ async function getBucketFileList() {
customUrl: currentCustomDomain.value, customUrl: currentCustomDomain.value,
currentPage: currentPageNumber.value, currentPage: currentPageNumber.value,
} }
return await window.electron.triggerRPC<any>(IRPCActionType.MANAGE_GET_BUCKET_FILE_LIST, configMap.alias, param) return await window.electron.triggerRPC<any>(IRPCActionType.MANAGE_GET_BUCKET_FILE_LIST, configMap.value.alias, param)
} }
async function handleBatchDeleteInfo() { async function handleBatchDeleteInfo() {
@@ -2800,15 +2812,19 @@ async function handleBatchDeleteInfo() {
for (const item of copiedSelectedItems) { for (const item of copiedSelectedItems) {
const param = { const param = {
bucketName: configMap.bucketName, bucketName: configMap.value.bucketName,
region: configMap.bucketConfig.Location, region: configMap.value.bucketConfig.Location,
key: item.key, key: item.key,
DeleteHash: item.sha, DeleteHash: item.sha,
githubBranch: currentCustomDomain.value, githubBranch: currentCustomDomain.value,
} }
const result = item.isDir const result = item.isDir
? await window.electron.triggerRPC<any>(IRPCActionType.MANAGE_DELETE_BUCKET_FOLDER, configMap.alias, param) ? await window.electron.triggerRPC<any>(
: await window.electron.triggerRPC<any>(IRPCActionType.MANAGE_DELETE_BUCKET_FILE, configMap.alias, param) IRPCActionType.MANAGE_DELETE_BUCKET_FOLDER,
configMap.value.alias,
param,
)
: await window.electron.triggerRPC<any>(IRPCActionType.MANAGE_DELETE_BUCKET_FILE, configMap.value.alias, param)
if (result) { if (result) {
successCount++ successCount++
currentPageFilesInfo.splice( currentPageFilesInfo.splice(
@@ -2856,17 +2872,25 @@ async function handleDeleteFile(item: any) {
if (!result) return if (!result) return
let res = false let res = false
const param = { const param = {
bucketName: configMap.bucketName, bucketName: configMap.value.bucketName,
region: configMap.bucketConfig.Location, region: configMap.value.bucketConfig.Location,
key: item.key, key: item.key,
DeleteHash: item.sha, DeleteHash: item.sha,
githubBranch: currentCustomDomain.value, githubBranch: currentCustomDomain.value,
} }
if (item.isDir) { if (item.isDir) {
message.info(t('pages.manage.bucket.deletingMsg')) message.info(t('pages.manage.bucket.deletingMsg'))
res = await window.electron.triggerRPC<any>(IRPCActionType.MANAGE_DELETE_BUCKET_FOLDER, configMap.alias, param) res = await window.electron.triggerRPC<any>(
IRPCActionType.MANAGE_DELETE_BUCKET_FOLDER,
configMap.value.alias,
param,
)
} else { } else {
res = await window.electron.triggerRPC<any>(IRPCActionType.MANAGE_DELETE_BUCKET_FILE, configMap.alias, param) res = await window.electron.triggerRPC<any>(
IRPCActionType.MANAGE_DELETE_BUCKET_FILE,
configMap.value.alias,
param,
)
} }
if (res) { if (res) {
message.success(t('pages.manage.bucket.deleteSuccess')) message.success(t('pages.manage.bucket.deleteSuccess'))
@@ -2927,13 +2951,15 @@ function singleRename() {
const item = currentPageFilesInfo[index] const item = currentPageFilesInfo[index]
const param = { const param = {
// tcyun // tcyun
bucketName: configMap.bucketName, bucketName: configMap.value.bucketName,
region: configMap.bucketConfig.Location, region: configMap.value.bucketConfig.Location,
oldKey: item.key, oldKey: item.key,
newKey: (item.key.slice(0, item.key.lastIndexOf('/') + 1) + itemToBeRenamed.value.newName).replaceAll('//', '/'), newKey: (item.key.slice(0, item.key.lastIndexOf('/') + 1) + itemToBeRenamed.value.newName).replaceAll('//', '/'),
customUrl: currentCustomDomain.value, customUrl: currentCustomDomain.value,
} }
window.electron.triggerRPC<any>(IRPCActionType.MANAGE_RENAME_BUCKET_FILE, configMap.alias, param).then((res: any) => { window.electron
.triggerRPC<any>(IRPCActionType.MANAGE_RENAME_BUCKET_FILE, configMap.value.alias, param)
.then((res: any) => {
if (res) { if (res) {
const oldKey = currentPrefix.value + item.fileName const oldKey = currentPrefix.value + item.fileName
if (pagingMarker.value === oldKey.slice(1)) { if (pagingMarker.value === oldKey.slice(1)) {
@@ -2989,12 +3015,12 @@ function singleRename() {
function handleGetS3Config(item: any) { function handleGetS3Config(item: any) {
return { return {
bucketName: configMap.bucketName, bucketName: configMap.value.bucketName,
region: configMap.bucketConfig.Location, region: configMap.value.bucketConfig.Location,
key: item.key, key: item.key,
customUrl: currentCustomDomain.value, customUrl: currentCustomDomain.value,
expires: manageStore.config.settings.PreSignedExpire, expires: manageStore.config.settings.PreSignedExpire,
githubPrivate: configMap.bucketConfig.private, githubPrivate: configMap.value.bucketConfig.private,
rawUrl: item.url, rawUrl: item.url,
} }
} }
@@ -3002,15 +3028,15 @@ function handleGetS3Config(item: any) {
async function getPreSignedUrl(item: any) { async function getPreSignedUrl(item: any) {
const param = { const param = {
// tcyun // tcyun
bucketName: configMap.bucketName, bucketName: configMap.value.bucketName,
region: configMap.bucketConfig.Location, region: configMap.value.bucketConfig.Location,
key: item.key, key: item.key,
customUrl: currentCustomDomain.value, customUrl: currentCustomDomain.value,
expires: manageStore.config.settings.PreSignedExpire, expires: manageStore.config.settings.PreSignedExpire,
githubPrivate: configMap.bucketConfig.private, githubPrivate: configMap.value.bucketConfig.private,
rawUrl: item.url, rawUrl: item.url,
} }
return await window.electron.triggerRPC<any>(IRPCActionType.MANAGE_GET_PRE_SIGNED_URL, configMap.alias, param) return await window.electron.triggerRPC<any>(IRPCActionType.MANAGE_GET_PRE_SIGNED_URL, configMap.value.alias, param)
} }
function copyToClipboard(text: string) { function copyToClipboard(text: string) {
@@ -3094,9 +3120,9 @@ function getTableKeyOfDb() {
let tableKey let tableKey
if (currentPicBedName.value === 'github') { if (currentPicBedName.value === 'github') {
// customUrl is branch // customUrl is branch
tableKey = `${configMap.alias}@${configMap.bucketConfig.githubUsername}@${configMap.bucketName}@${currentCustomDomain.value}@${currentPrefix.value}` tableKey = `${configMap.value.alias}@${configMap.value.bucketConfig.githubUsername}@${configMap.value.bucketName}@${currentCustomDomain.value}@${currentPrefix.value}`
} else { } else {
tableKey = `${configMap.alias}@${configMap.bucketName}@${currentPrefix.value}` tableKey = `${configMap.value.alias}@${configMap.value.bucketName}@${currentPrefix.value}`
} }
return tableKey return tableKey
} }
@@ -3123,11 +3149,6 @@ function handleDetectShiftKey(event: KeyboardEvent) {
} }
onBeforeMount(async () => { onBeforeMount(async () => {
await manageStore.refreshConfig()
isShowLoadingPage.value = true
await initCustomDomainList()
await resetParam(true)
isShowLoadingPage.value = false
document.addEventListener('keydown', handleDetectShiftKey) document.addEventListener('keydown', handleDetectShiftKey)
document.addEventListener('keyup', handleDetectShiftKey) document.addEventListener('keyup', handleDetectShiftKey)
document.addEventListener('click', handleClickOutside) document.addEventListener('click', handleClickOutside)

View File

@@ -8,7 +8,7 @@
<h3 class="mb-2 text-xl font-semibold text-main"> <h3 class="mb-2 text-xl font-semibold text-main">
{{ t('pages.manage.empty.noData') }} {{ t('pages.manage.empty.noData') }}
</h3> </h3>
<p class="text-sm leading-[1.5] text-secondary"> <p v-if="!noDesc" class="text-sm leading-[1.5] text-secondary">
{{ t('pages.manage.empty.noDataDesc') }} {{ t('pages.manage.empty.noDataDesc') }}
</p> </p>
</div> </div>
@@ -21,4 +21,8 @@ import { FolderOpenIcon } from 'lucide-vue-next'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
const { t } = useI18n() const { t } = useI18n()
const { noDesc = false } = defineProps<{
noDesc?: boolean
}>()
</script> </script>

View File

@@ -9,7 +9,7 @@
<div> <div>
<h1 class="m-0 text-2xl font-semibold tracking-tight text-main">{{ t('pages.manage.login.title') }}</h1> <h1 class="m-0 text-2xl font-semibold tracking-tight text-main">{{ t('pages.manage.login.title') }}</h1>
<p class="m-0 text-sm text-secondary"> <p class="m-0 text-sm text-secondary">
{{ sortedAllConfigAliasMap.length }} {{ t('pages.manage.login.savedConfigs') }} {{ sortedAllConfigAliasList.length }} {{ t('pages.manage.login.savedConfigs') }}
</p> </p>
</div> </div>
</div> </div>
@@ -40,7 +40,7 @@
v-for="item in tabItems" v-for="item in tabItems"
:key="item.key" :key="item.key"
class="transition-al flex min-w-fit flex-none cursor-pointer items-center gap-2 rounded-md border border-border-secondary bg-bg-secondary px-4 py-2 text-sm font-semibold whitespace-nowrap text-secondary no-underline duration-200 ease-apple hover:border-border hover:bg-accent/10 hover:text-main [.active]:border-accent [.active]:bg-accent [.active]:text-white" class="transition-al flex min-w-fit flex-none cursor-pointer items-center gap-2 rounded-md border border-border-secondary bg-bg-secondary px-4 py-2 text-sm font-semibold whitespace-nowrap text-secondary no-underline duration-200 ease-apple hover:border-border hover:bg-accent/10 hover:text-main [.active]:border-accent [.active]:bg-accent [.active]:text-white"
:class="{ active: activeName === item.key }" :class="{ active: activePlatform === item.key }"
@click="handleTabChange(item.key)" @click="handleTabChange(item.key)"
> >
<FolderIcon v-if="item.key === 'login'" :size="16" /> <FolderIcon v-if="item.key === 'login'" :size="16" />
@@ -62,9 +62,9 @@
> >
<div class="no-scrollbar h-full w-full flex-1 overflow-auto rounded-2xl border-none"> <div class="no-scrollbar h-full w-full flex-1 overflow-auto rounded-2xl border-none">
<!-- Main Config List Tab --> <!-- Main Config List Tab -->
<div v-if="activeName === 'login'" class="h-full w-full p-4"> <div v-if="activePlatform === 'login'" class="h-full w-full p-4">
<div <div
v-if="sortedAllConfigAliasMap.length === 0" v-if="sortedAllConfigAliasList.length === 0"
class="flex h-full w-full flex-col items-center justify-center p-4" class="flex h-full w-full flex-col items-center justify-center p-4"
> >
<div class="mb-2 text-accent/50"> <div class="mb-2 text-accent/50">
@@ -78,7 +78,7 @@
class="grid w-full grid-cols-[repeat(auto-fill,minmax(300px,1fr))] gap-5 border-none p-1 max-md:gap-4" class="grid w-full grid-cols-[repeat(auto-fill,minmax(300px,1fr))] gap-5 border-none p-1 max-md:gap-4"
> >
<div <div
v-for="item in sortedAllConfigAliasMap" v-for="item in sortedAllConfigAliasList"
:key="item.alias" :key="item.alias"
class="group relative flex cursor-pointer flex-row gap-6 overflow-visible rounded-xl border border-border-secondary p-4 shadow-md transition-all duration-fast ease-apple hover:border-2 hover:border-accent" class="group relative flex cursor-pointer flex-row gap-6 overflow-visible rounded-xl border border-border-secondary p-4 shadow-md transition-all duration-fast ease-apple hover:border-2 hover:border-accent"
> >
@@ -106,9 +106,9 @@
> >
<InfoIcon :size="14" /> <InfoIcon :size="14" />
{{ t('pages.manage.login.viewDetails') }} {{ t('pages.manage.login.viewDetails') }}
<ChevronDownIcon :size="14" :class="{ 'rotate-180': expandedConfigs.includes(item.alias) }" /> <ChevronDownIcon :size="14" :class="{ 'rotate-180': visibleConfigItems.includes(item.alias) }" />
</button> </button>
<Teleport v-if="expandedConfigs.includes(item.alias)" to="body"> <Teleport v-if="visibleConfigItems.includes(item.alias)" to="body">
<div <div
class="fixed top-1/3 left-1/2 z-1000 h-auto max-h-[400px] w-auto max-w-[900px] min-w-[200px] -translate-x-1/2 overflow-auto rounded-xl border border-slate-200 bg-white shadow-xl ring-1 ring-black/5" class="fixed top-1/3 left-1/2 z-1000 h-auto max-h-[400px] w-auto max-w-[900px] min-w-[200px] -translate-x-1/2 overflow-auto rounded-xl border border-slate-200 bg-white shadow-xl ring-1 ring-black/5"
> >
@@ -185,7 +185,7 @@
class="grid w-full grid-cols-[repeat(auto-fill,minmax(300px,1fr))] gap-5 border-none p-1 max-md:gap-4" class="grid w-full grid-cols-[repeat(auto-fill,minmax(300px,1fr))] gap-5 border-none p-1 max-md:gap-4"
> >
<div <div
v-for="(item, index) in existingConfiguration" v-for="(item, index) in platformConfigList"
:key="item.alias + index" :key="item.alias + index"
class="relative flex min-h-[180px] cursor-pointer flex-col gap-6 overflow-hidden rounded-xl border border-border p-5 shadow-md transition-all duration-fast ease-apple hover:border-2 hover:border-accent hover:shadow-md" class="relative flex min-h-[180px] cursor-pointer flex-col gap-6 overflow-hidden rounded-xl border border-border p-5 shadow-md transition-all duration-fast ease-apple hover:border-2 hover:border-accent hover:shadow-md"
> >
@@ -241,7 +241,12 @@
</div> </div>
</div> </div>
<template v-else-if="editMode"> <template v-else-if="editMode">
<ManageEditPage v-model:edit-mode="editMode" :alias-name="editingAlias" :active-name="activeName" /> <ManageEditPage
v-model:edit-mode="editMode"
:alias-name="editingAlias"
:platform-name="activePlatform"
@update:edit-mode="loadExistingSettings(activePlatform)"
/>
</template> </template>
</div> </div>
</div> </div>
@@ -266,7 +271,7 @@ import {
TrashIcon, TrashIcon,
XIcon, XIcon,
} from 'lucide-vue-next' } from 'lucide-vue-next'
import { computed, onMounted, reactive, ref } from 'vue' import { computed, onMounted, ref } from 'vue'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
@@ -280,7 +285,7 @@ import { supportedPicBedList } from '@/manage/utils/constants'
import { getConfig, removeConfig, saveConfig } from '@/manage/utils/dataSender' import { getConfig, removeConfig, saveConfig } from '@/manage/utils/dataSender'
import { formatEndpoint } from '@/utils/common' import { formatEndpoint } from '@/utils/common'
import { configPaths } from '@/utils/configPaths' import { configPaths } from '@/utils/configPaths'
import { getConfig as getPicBedsConfig } from '@/utils/dataSender' import { getConfig as getPicListConfig } from '@/utils/dataSender'
import { II18nLanguage, IRPCActionType } from '@/utils/enum' import { II18nLanguage, IRPCActionType } from '@/utils/enum'
const { t } = useI18n() const { t } = useI18n()
@@ -291,101 +296,71 @@ const { confirm } = useConfirm()
const editMode = ref(false) const editMode = ref(false)
const editingAlias = ref('') const editingAlias = ref('')
const activeName = ref('login') const activePlatform = ref('login')
const expandedConfigs = ref<string[]>([]) const visibleConfigItems = ref<string[]>([])
const configResult: IStringKeyMap = reactive({}) const platformConfigList = ref<IStringKeyMap[]>([])
const existingConfiguration = reactive({} as IStringKeyMap) const allConfigAliasList = ref<IStringKeyMap[]>([])
const dataForTable = reactive([] as any[]) const importedNewConfig: IStringKeyMap = {}
const allConfigAliasMap = reactive({} as IStringKeyMap)
const currentAliasList = reactive([] as string[])
const formErrors = reactive({} as IStringKeyMap)
const sortedAllConfigAliasMap = computed(() => { const PB_LIST = [
return Object.values(allConfigAliasMap).sort((a, b) => { 'aliyun',
'aws-s3',
'aws-s3-plist',
'github',
'imgur',
'local',
'qiniu',
'sftpplist',
'smms',
'tcyun',
'upyun',
'webdavplist',
] as const
const sortedAllConfigAliasList = computed(() => {
return allConfigAliasList.value.slice().sort((a, b) => {
return a.picBedName.localeCompare(b.picBedName) return a.picBedName.localeCompare(b.picBedName)
}) })
}) })
const tabItems = computed(() => { const tabItems = computed(() => {
const items = [ const staticItem = {
{
key: 'login', key: 'login',
name: t('pages.manage.login.savedConfigs'), name: t('pages.manage.login.savedConfigs'),
icon: null, icon: null,
iconComponent: FolderIcon, iconComponent: FolderIcon,
}, }
]
Object.values(supportedPicBedList).forEach((item: any) => { const dynamicItems = Object.values(supportedPicBedList).map((item: any) => ({
items.push({
key: item.icon, key: item.icon,
name: item.name, name: item.name,
icon: item.icon, icon: item.icon,
iconComponent: null as any, iconComponent: null,
}) }))
})
return items return [staticItem, ...dynamicItems]
}) })
const importedNewConfig: IStringKeyMap = {}
const notifyUser = (msg: string, type: 'success' | 'error' | 'warning' = 'success') => { const notifyUser = (msg: string, type: 'success' | 'error' | 'warning' = 'success') => {
message[type](`${msg}`) message[type](`${msg}`)
} }
const initializeDefaultValues = (picBedName: string) => { async function loadExistingSettings(name: string) {
if (!supportedPicBedList[picBedName]) return
const options = supportedPicBedList[picBedName].options || []
for (const option of options) {
const fieldKey = `${picBedName}.${option}`
const configOption = supportedPicBedList[picBedName].configOptions[option]
if (configResult[fieldKey] === undefined || configResult[fieldKey] === '') {
if (configOption.default !== undefined) {
configResult[fieldKey] = configOption.default
} else if (configOption.type === 'boolean') {
configResult[fieldKey] = false
} else if (configOption.type === 'number') {
configResult[fieldKey] = 0
} else {
configResult[fieldKey] = ''
}
}
}
}
function getDataForTable() {
for (const key in existingConfiguration) {
dataForTable.push({ ...(existingConfiguration[key] as IStringKeyMap) })
}
}
async function getExistingConfig(name: string) {
if (name === 'login') { if (name === 'login') {
getAllConfigAliasArray() getAllConfigAliasArray()
return return
} }
currentAliasList.length = 0
const result = await getConfig<any>('picBed')
for (const key in existingConfiguration) {
delete existingConfiguration[key]
}
if (!result || typeof result !== 'object' || Object.keys(result).length === 0) {
existingConfiguration[name] = { fail: '暂无配置' }
} else {
for (const key in result) {
if (result[key].picBedName === name) {
existingConfiguration[key] = result[key]
currentAliasList.push(result[key].alias)
}
}
}
dataForTable.length = 0 const result = await getConfig<any>('picBed')
getDataForTable() const newConfig: IStringKeyMap[] = []
handleConfigImport(currentAliasList[0]) if (result && typeof result === 'object' && Object.keys(result).length > 0) {
Object.values(result).forEach((value: any) => {
if (value.picBedName === name) {
newConfig.push(value)
}
})
}
platformConfigList.value = newConfig
} }
function openBucketPageSetting() { function openBucketPageSetting() {
@@ -408,7 +383,7 @@ const handleConfigRemove = async (name: string) => {
removeConfig('picBed', name) removeConfig('picBed', name)
notifyUser(t('pages.manage.login.deleteConfigSuccessMsg'), 'success') notifyUser(t('pages.manage.login.deleteConfigSuccessMsg'), 'success')
manageStore.refreshConfig() manageStore.refreshConfig()
getAllConfigAliasArray() loadExistingSettings(activePlatform.value)
} catch (_error) { } catch (_error) {
notifyUser(t('pages.manage.login.deleteConfigFailedMsg'), 'error') notifyUser(t('pages.manage.login.deleteConfigFailedMsg'), 'error')
} }
@@ -417,17 +392,16 @@ const handleConfigRemove = async (name: string) => {
const getAllConfigAliasArray = async () => { const getAllConfigAliasArray = async () => {
const result = await getConfig<any>('picBed') const result = await getConfig<any>('picBed')
for (const key in allConfigAliasMap) { const newConfig: IStringKeyMap[] = []
delete allConfigAliasMap[key]
}
if (!result) return if (!result) return
Object.entries(result).forEach(([, value]: [string, any], index) => { Object.values(result).forEach((value: any) => {
allConfigAliasMap[index] = { newConfig.push({
alias: value.alias, alias: value.alias,
config: value, config: value,
picBedName: value.picBedName, picBedName: value.picBedName,
}
}) })
})
allConfigAliasList.value = newConfig
} }
const copyToClipboard = (text: string) => { const copyToClipboard = (text: string) => {
@@ -456,37 +430,18 @@ function openEditPage(alias: string) {
editMode.value = true editMode.value = true
} }
function handleConfigImport(alias: string) {
const selectedConfig = existingConfiguration[alias]
if (!selectedConfig) return
supportedPicBedList[selectedConfig.picBedName].options.forEach((option: any) => {
if (selectedConfig[option] !== undefined) {
configResult[selectedConfig.picBedName + '.' + option] = selectedConfig[option]
}
})
}
const handleTabChange = (tabName: string) => { const handleTabChange = (tabName: string) => {
editMode.value = false editMode.value = false
activeName.value = tabName activePlatform.value = tabName
getExistingConfig(tabName) loadExistingSettings(tabName)
for (const key in formErrors) {
delete formErrors[key]
}
if (tabName !== 'login') {
initializeDefaultValues(tabName)
}
} }
const toggleConfigDetails = async (alias: string) => { const toggleConfigDetails = async (alias: string) => {
const index = expandedConfigs.value.indexOf(alias) const index = visibleConfigItems.value.indexOf(alias)
if (index > -1) { if (index > -1) {
expandedConfigs.value.splice(index, 1) visibleConfigItems.value.splice(index, 1)
} else { } else {
expandedConfigs.value.push(alias) visibleConfigItems.value.push(alias)
} }
} }
@@ -495,37 +450,19 @@ const refreshConfigs = () => {
notifyUser(t('pages.manage.login.configurationRefreshMsg'), 'success') notifyUser(t('pages.manage.login.configurationRefreshMsg'), 'success')
} }
onMounted(() => {
getCurrentConfigList()
})
async function getCurrentConfigList() { async function getCurrentConfigList() {
await manageStore.refreshConfig() await manageStore.refreshConfig()
const configList = (await getPicBedsConfig<any>('uploader')) ?? {} const configList = (await getPicListConfig<any>('uploader')) ?? {}
const pbList = [
'aliyun',
'aws-s3',
'aws-s3-plist',
'github',
'imgur',
'local',
'qiniu',
'sftpplist',
'smms',
'tcyun',
'upyun',
'webdavplist',
]
const filteredConfigList = pbList.flatMap(pb => { const filteredConfigList = PB_LIST.flatMap(pb => {
const config = configList[pb] const config = configList[pb]
return config?.configList?.length ? config.configList.map((item: any) => ({ ...item, type: pb })) : [] return config?.configList?.length ? config.configList.map((item: any) => ({ ...item, type: pb })) : []
}) })
const autoImport = (await getPicBedsConfig<boolean>('settings.autoImport')) || false const autoImport = (await getPicListConfig<boolean>('settings.autoImport')) || false
if (autoImport) { if (autoImport) {
const autoImportPicBed = initArray( const autoImportPicBed = initArray(
(await getPicBedsConfig<string | string[]>('settings.autoImportPicBed')) || '', (await getPicListConfig<string | string[]>('settings.autoImportPicBed')) || '',
[], [],
) )
await Promise.all(filteredConfigList.flatMap(config => transUpToManage(config, config.type, autoImportPicBed))) await Promise.all(filteredConfigList.flatMap(config => transUpToManage(config, config.type, autoImportPicBed)))
@@ -547,7 +484,7 @@ async function goConfigPage() {
} }
function isImported(alias: string) { function isImported(alias: string) {
return Object.values(allConfigAliasMap).some(item => item.alias === alias) return Object.values(allConfigAliasList.value).some(item => item.alias === alias)
} }
function initArray(arrayT: string | string[], defaultValue: string[]) { function initArray(arrayT: string | string[], defaultValue: string[]) {
@@ -557,16 +494,18 @@ function initArray(arrayT: string | string[], defaultValue: string[]) {
return arrayT return arrayT
} }
function getPicBedAlias(name: string) {
const mapping: Record<string, string> = {
webdavplist: 'webdav',
sftpplist: 'sftp',
'aws-s3': 's3plist',
'aws-s3-plist': 's3plist',
}
return mapping[name] || name
}
async function transUpToManage(config: IUploaderConfigListItem, picBedName: string, autoImportPicBed: string[]) { async function transUpToManage(config: IUploaderConfigListItem, picBedName: string, autoImportPicBed: string[]) {
const alias = `${ const alias = `${getPicBedAlias(picBedName)}-${config._configName ?? 'Default'}-imp`
picBedName === 'webdavplist'
? 'webdav'
: picBedName === 'sftpplist'
? 'sftp'
: picBedName === 'aws-s3' || picBedName === 'aws-s3-plist'
? 's3plist'
: picBedName
}-${config._configName ?? 'Default'}-imp`
if (!autoImportPicBed.includes(picBedName) || isImported(alias)) return if (!autoImportPicBed.includes(picBedName) || isImported(alias)) return
const commonConfig = { const commonConfig = {
alias, alias,

View File

@@ -6,7 +6,7 @@
> >
<InfoIcon :size="20" /> <InfoIcon :size="20" />
<p class="m-0 text-sm leading-[1.5] font-semibold text-secondary"> <p class="m-0 text-sm leading-[1.5] font-semibold text-secondary">
{{ supportedPicBedList[activeName].explain }} {{ supportedPicBedList[platformName].explain }}
</p> </p>
</div> </div>
<div <div
@@ -14,9 +14,9 @@
> >
<LinkIcon :size="20" /> <LinkIcon :size="20" />
<p class="m-0 text-sm leading-[1.5] font-semibold text-secondary"> <p class="m-0 text-sm leading-[1.5] font-semibold text-secondary">
{{ supportedPicBedList[activeName].referenceText }} {{ supportedPicBedList[platformName].referenceText }}
<button class="link-button" @click="handleReferenceClick(supportedPicBedList[activeName].refLink)"> <button class="link-button" @click="handleReferenceClick(supportedPicBedList[platformName].refLink)">
{{ supportedPicBedList[activeName].refLink }} {{ supportedPicBedList[platformName].refLink }}
</button> </button>
</p> </p>
</div> </div>
@@ -24,92 +24,94 @@
<div class="grid w-full grid-cols-1 gap-3"> <div class="grid w-full grid-cols-1 gap-3">
<SettingCard> <SettingCard>
<CustomInput <CustomInput
v-model.trim="configResult[activeName + '.alias']" v-model.trim="configResult.alias"
type="text" type="text"
:placeholder="supportedPicBedList[activeName].configOptions.alias.placeholder || ''" :placeholder="supportedPicBedList[platformName].configOptions.alias.placeholder || ''"
:title="supportedPicBedList[activeName].configOptions.alias.description" :title="supportedPicBedList[platformName].configOptions.alias.description"
:required="supportedPicBedList[activeName].configOptions.alias.required" :required="supportedPicBedList[platformName].configOptions.alias.required"
:class="{ 'border-danger': formErrors[activeName + '.' + 'alias'] }" :class="{ 'border-danger': formErrors.alias }"
@blur="validateField(activeName, 'alias')" @blur="validateField(platformName, 'alias')"
@input="clearFieldError(activeName + '.alias')" @input="clearFieldError('alias')"
/> />
<template v-if="formErrors[activeName + '.' + 'alias']" #extra> <template v-if="formErrors.alias" #extra>
<div class="mt-1 text-xs text-danger"> <div class="mt-1 text-xs text-danger">
{{ formErrors[activeName + '.' + 'alias'] }} {{ formErrors.alias }}
</div> </div>
</template> </template>
</SettingCard> </SettingCard>
<template v-for="option in supportedPicBedList[activeName].options" :key="option"> <template v-for="option in supportedPicBedList[platformName].options" :key="option">
<SettingCard <SettingCard
v-if="supportedPicBedList[activeName].configOptions[option].type === 'string' && option !== 'alias'" v-if="supportedPicBedList[platformName].configOptions[option].type === 'string' && option !== 'alias'"
> >
<CustomInput <CustomInput
v-model.trim="configResult[activeName + '.' + option]" v-model.trim="configResult[option]"
type="text" type="text"
:placeholder="supportedPicBedList[activeName].configOptions[option].placeholder || ''" :placeholder="supportedPicBedList[platformName].configOptions[option].placeholder || ''"
:class="{ 'border-danger': formErrors[activeName + '.' + option] }" :class="{ 'border-danger': formErrors[option] }"
:title="supportedPicBedList[activeName].configOptions[option].description" :title="supportedPicBedList[platformName].configOptions[option].description"
:required="supportedPicBedList[activeName].configOptions[option].required" :required="supportedPicBedList[platformName].configOptions[option].required"
:disabled="!!supportedPicBedList[activeName].configOptions[option].disabled" :disabled="!!supportedPicBedList[platformName].configOptions[option].disabled"
@blur="validateField(activeName, option)" :tips="supportedPicBedList[platformName].configOptions[option].tooltip || ''"
@input="clearFieldError(activeName + '.' + option)" @blur="validateField(platformName, option)"
@input="clearFieldError(option)"
/> />
<template v-if="formErrors[activeName + '.' + option]" #extra> <template v-if="formErrors[option]" #extra>
<div class="mt-1 text-xs text-danger"> <div class="mt-1 text-xs text-danger">
{{ formErrors[activeName + '.' + option] }} {{ formErrors[option] }}
</div> </div>
</template> </template>
</SettingCard> </SettingCard>
</template> </template>
<template v-for="option in supportedPicBedList[activeName].options" :key="option"> <template v-for="option in supportedPicBedList[platformName].options" :key="option">
<SettingCard v-if="supportedPicBedList[activeName].configOptions[option].type === 'number'"> <SettingCard v-if="supportedPicBedList[platformName].configOptions[option].type === 'number'">
<CustomInput <CustomInput
v-model.number="configResult[activeName + '.' + option]" v-model.number="configResult[option]"
type="number" type="number"
:placeholder="supportedPicBedList[activeName].configOptions[option].placeholder || ''" :placeholder="supportedPicBedList[platformName].configOptions[option].placeholder || ''"
:class="{ 'border-danger': formErrors[activeName + '.' + option] }" :class="{ 'border-danger': formErrors[option] }"
:title="supportedPicBedList[activeName].configOptions[option].description" :title="supportedPicBedList[platformName].configOptions[option].description"
:required="supportedPicBedList[activeName].configOptions[option].required" :required="supportedPicBedList[platformName].configOptions[option].required"
@blur="validateField(activeName, option)" :tips="supportedPicBedList[platformName].configOptions[option].tooltip || ''"
@input="clearFieldError(activeName + '.' + option)" @blur="validateField(platformName, option)"
@input="clearFieldError(option)"
/> />
<template v-if="formErrors[activeName + '.' + option]" #extra> <template v-if="formErrors[option]" #extra>
<div class="mt-1 text-xs text-danger"> <div class="mt-1 text-xs text-danger">
{{ formErrors[activeName + '.' + option] }} {{ formErrors[option] }}
</div> </div>
</template> </template>
</SettingCard> </SettingCard>
</template> </template>
<template v-for="option in supportedPicBedList[activeName].options" :key="option"> <template v-for="option in supportedPicBedList[platformName].options" :key="option">
<SettingCard v-if="supportedPicBedList[activeName].configOptions[option].type === 'boolean'" p1> <SettingCard v-if="supportedPicBedList[platformName].configOptions[option].type === 'boolean'" p1>
<CustomSwitch <CustomSwitch
v-model="configResult[activeName + '.' + option]" v-model="configResult[option]"
no-border no-border
small small
:required="supportedPicBedList[activeName].configOptions[option].required" :required="supportedPicBedList[platformName].configOptions[option].required"
:title="supportedPicBedList[activeName].configOptions[option].description" :title="supportedPicBedList[platformName].configOptions[option].description"
:tips="supportedPicBedList[activeName].configOptions[option].tooltip || ''" :tips="supportedPicBedList[platformName].configOptions[option].tooltip || ''"
@update:model-value="validateField(activeName, option)" @update:model-value="validateField(platformName, option)"
> >
</CustomSwitch> </CustomSwitch>
</SettingCard> </SettingCard>
</template> </template>
<template v-for="option in supportedPicBedList[activeName].options" :key="option"> <template v-for="option in supportedPicBedList[platformName].options" :key="option">
<SettingCard v-if="supportedPicBedList[activeName].configOptions[option].type === 'select'"> <SettingCard v-if="supportedPicBedList[platformName].configOptions[option].type === 'select'">
<CustomSelect <CustomSelect
v-model="configResult[activeName + '.' + option]" v-model="configResult[option]"
:title="supportedPicBedList[activeName].configOptions[option].description" :title="supportedPicBedList[platformName].configOptions[option].description"
:required="supportedPicBedList[activeName].configOptions[option].required" :required="supportedPicBedList[platformName].configOptions[option].required"
:select-list=" :select-list="
Object.entries(supportedPicBedList[activeName].configOptions[option].selectOptions || {}).map( Object.entries(supportedPicBedList[platformName].configOptions[option].selectOptions || {}).map(
([key, value]) => ({ ([key, value]) => ({
value: key, value: key,
label: value as string, label: value as string,
}), }),
) )
" "
:class="{ 'border-danger': formErrors[activeName + '.' + option] }" :class="{ 'border-danger': formErrors[option] }"
@change="validateField(activeName, option)" @change="validateField(platformName, option)"
> >
<template #pre-info> <template #pre-info>
<option value="" disabled> <option value="" disabled>
@@ -117,9 +119,9 @@
</option> </option>
</template> </template>
</CustomSelect> </CustomSelect>
<template v-if="formErrors[activeName + '.' + option]" #extra> <template v-if="formErrors[option]" #extra>
<div class="mt-1 text-xs text-danger"> <div class="mt-1 text-xs text-danger">
{{ formErrors[activeName + '.' + option] }} {{ formErrors[option] }}
</div> </div>
</template> </template>
</SettingCard> </SettingCard>
@@ -141,13 +143,13 @@
type="primary" type="primary"
:text="t('pages.manage.login.save')" :text="t('pages.manage.login.save')"
:icon="SaveIcon" :icon="SaveIcon"
@click="handleConfigChange(activeName)" @click="handleConfigChange()"
/> />
<CustomButton <CustomButton
class="bg-danger/70" class="bg-danger/70"
:text="t('pages.manage.login.reset')" :text="t('pages.manage.login.reset')"
:icon="RotateCcwIcon" :icon="RotateCcwIcon"
@click="handleConfigReset(activeName)" @click="handleConfigReset()"
/> />
<CustomButton class="bg-warning/70" :text="t('common.cancel')" :icon="XIcon" @click="cancelEditMode" /> <CustomButton class="bg-warning/70" :text="t('common.cancel')" :icon="XIcon" @click="cancelEditMode" />
</div> </div>
@@ -157,7 +159,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { DownloadIcon, InfoIcon, LinkIcon, RotateCcwIcon, SaveIcon, XIcon } from 'lucide-vue-next' import { DownloadIcon, InfoIcon, LinkIcon, RotateCcwIcon, SaveIcon, XIcon } from 'lucide-vue-next'
import { onMounted, reactive, ref, watch } from 'vue' import { onMounted, ref, watch } from 'vue'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import CustomButton from '@/components/common/CustomButton.vue' import CustomButton from '@/components/common/CustomButton.vue'
@@ -173,22 +175,23 @@ import { getConfig, saveConfig } from '@/manage/utils/dataSender'
import { formatEndpoint } from '@/utils/common' import { formatEndpoint } from '@/utils/common'
import { IRPCActionType } from '@/utils/enum' import { IRPCActionType } from '@/utils/enum'
const editMode = defineModel<boolean>('editMode')
const emit = defineEmits<(e: 'update:editMode', value: boolean) => void>()
const { aliasName, platformName } = defineProps<{
aliasName: string
platformName: string
}>()
const { t } = useI18n() const { t } = useI18n()
const manageStore = useManageStore() const manageStore = useManageStore()
const message = useMessage() const message = useMessage()
const formErrors = reactive({} as IStringKeyMap) const formErrors = ref<IStringKeyMap>({})
const configResult: IStringKeyMap = reactive({}) const configResult = ref<IStringKeyMap>({})
const existingConfiguration = reactive({} as IStringKeyMap) const existingConfiguration = ref<IStringKeyMap>({})
const currentAliasList = reactive([] as string[]) const currentAliasList = ref<string[]>([])
const dataForTable = reactive([] as any[])
const editMode = defineModel<boolean>('editMode')
const selectedAlias = ref('') const selectedAlias = ref('')
const { aliasName, activeName } = defineProps<{
aliasName: string
activeName: string
}>()
watch(selectedAlias, newAlias => { watch(selectedAlias, newAlias => {
if (newAlias) { if (newAlias) {
handleConfigImport(newAlias) handleConfigImport(newAlias)
@@ -198,18 +201,17 @@ watch(selectedAlias, newAlias => {
const handleReferenceClick = (url: string) => window.electron.sendRPC(IRPCActionType.OPEN_URL, url) const handleReferenceClick = (url: string) => window.electron.sendRPC(IRPCActionType.OPEN_URL, url)
const validateField = (picBedName: string, optionKey: string) => { const validateField = (picBedName: string, optionKey: string) => {
const fieldKey = `${picBedName}.${optionKey}`
const configOption = supportedPicBedList[picBedName]?.configOptions?.[optionKey] const configOption = supportedPicBedList[picBedName]?.configOptions?.[optionKey]
const value = configResult[fieldKey] const value = configResult.value[optionKey]
if (!configOption) return if (!configOption) return
delete formErrors[fieldKey] delete formErrors.value[optionKey]
if (configOption.required) { if (configOption.required) {
if (configOption.type === 'boolean') { if (configOption.type === 'boolean') {
} else if (!value || value === '') { } else if (!value || value === '') {
formErrors[fieldKey] = t('pages.manage.constant.pleaseInput', { name: configOption.description }) formErrors.value[optionKey] = t('pages.manage.constant.pleaseInput', { name: configOption.description })
return return
} }
} }
@@ -220,7 +222,7 @@ const validateField = (picBedName: string, optionKey: string) => {
try { try {
rule.validator(rule, value, (error: Error | null) => { rule.validator(rule, value, (error: Error | null) => {
if (error) { if (error) {
formErrors[fieldKey] = error.message formErrors.value[optionKey] = error.message
} }
}) })
} catch (e) { } catch (e) {
@@ -228,7 +230,7 @@ const validateField = (picBedName: string, optionKey: string) => {
} }
} else if (rule.type === 'number' && value !== undefined && value !== '') { } else if (rule.type === 'number' && value !== undefined && value !== '') {
if (isNaN(Number(value))) { if (isNaN(Number(value))) {
formErrors[fieldKey] = rule.message || t('pages.manage.constant.itemsPPBeNumber') formErrors.value[optionKey] = rule.message || t('pages.manage.constant.itemsPPBeNumber')
return return
} }
} }
@@ -238,53 +240,56 @@ const validateField = (picBedName: string, optionKey: string) => {
if (optionKey === 'alias' && value) { if (optionKey === 'alias' && value) {
const reg = /^[\p{Unified_Ideograph}_a-zA-Z0-9-]+$/u const reg = /^[\p{Unified_Ideograph}_a-zA-Z0-9-]+$/u
if (!reg.test(value)) { if (!reg.test(value)) {
formErrors[fieldKey] = t('pages.manage.login.aliasMsg') formErrors.value[optionKey] = t('pages.manage.login.aliasMsg')
} }
} }
if (optionKey === 'itemsPerPage' && value !== undefined && value !== '') { if (optionKey === 'itemsPerPage' && value !== undefined && value !== '') {
const numValue = Number(value) const numValue = Number(value)
if (numValue < 20 || numValue > 1000) { if (numValue < 20 || numValue > 1000) {
formErrors[fieldKey] = t('pages.manage.login.itemsPerPageMsg') formErrors.value[optionKey] = t('pages.manage.login.itemsPerPageMsg')
} }
} }
} }
const clearFieldError = (fieldKey: string) => { const clearFieldError = (fieldKey: string) => {
delete formErrors[fieldKey] delete formErrors.value[fieldKey]
} }
async function handleConfigChange(name: string) { async function handleConfigChange() {
if (!validateAllFields(name)) { if (!validateAllFields(platformName)) {
notifyUser(t('pages.manage.login.noRequiredMsg'), 'error') notifyUser(t('pages.manage.login.noRequiredMsg'), 'error')
return return
} }
const aliasList = getAliasList() const aliasList = Object.values(existingConfiguration.value).map(item => item.alias)
const allKeys = Object.keys(supportedPicBedList[name].configOptions) const allKeys = Object.keys(supportedPicBedList[platformName].configOptions)
const resultMap: IStringKeyMap = {} const resultMap: IStringKeyMap = {}
if (aliasList.includes(configResult.value.alias) && aliasName !== configResult.value.alias) {
notifyUser(t('pages.manage.login.aliasExistMsg'), 'error')
return
}
for (const key of allKeys) { for (const key of allKeys) {
const resultKey = name + '.' + key if (key === 'customUrl' && configResult.value[key] !== undefined && configResult.value[key] !== '') {
if (key === 'customUrl' && configResult[resultKey] !== undefined && configResult[resultKey] !== '') { if (platformName !== 'upyun') {
if (name !== 'upyun') { configResult.value[key] = formatEndpoint(configResult.value[key], false)
configResult[resultKey] = formatEndpoint(configResult[resultKey], false)
} }
} }
if (supportedPicBedList[name].configOptions[key].default !== undefined && configResult[resultKey] === '') { if (supportedPicBedList[platformName].configOptions[key].default !== undefined && configResult.value[key] === '') {
resultMap[key] = supportedPicBedList[name].configOptions[key].default resultMap[key] = supportedPicBedList[platformName].configOptions[key].default
} else if (configResult[resultKey] === undefined) { } else if (configResult.value[key] === undefined) {
if (supportedPicBedList[name].configOptions[key].default !== undefined) { if (supportedPicBedList[platformName].configOptions[key].default !== undefined) {
resultMap[key] = supportedPicBedList[name].configOptions[key].default resultMap[key] = supportedPicBedList[platformName].configOptions[key].default
} else { } else {
resultMap[key] = '' resultMap[key] = ''
} }
} else { } else {
resultMap[key] = configResult[resultKey] resultMap[key] = configResult.value[key]
} }
} }
resultMap.picBedName = name resultMap.picBedName = platformName
if (resultMap.bucketName !== undefined) { if (resultMap.bucketName !== undefined) {
resultMap.transformedConfig = {} resultMap.transformedConfig = {}
const bucketName = resultMap.bucketName.split(',') const bucketName = resultMap.bucketName.split(',')
@@ -310,56 +315,51 @@ async function handleConfigChange(name: string) {
} }
saveConfig(`picBed.${resultMap.alias}`, resultMap) saveConfig(`picBed.${resultMap.alias}`, resultMap)
await manageStore.refreshConfig() await manageStore.refreshConfig()
await getExistingConfig(activeName) await getExistingConfig(platformName)
dataForTable.length = 0
getDataForTable()
if (aliasList.includes(resultMap.alias)) {
notifyUser(`${t('pages.manage.login.configChangeMsg')}${resultMap.alias}`, 'warning')
} else {
notifyUser(`${t('pages.manage.login.configSaveMsg')}${resultMap.alias}`, 'success') notifyUser(`${t('pages.manage.login.configSaveMsg')}${resultMap.alias}`, 'success')
}
editMode.value = false editMode.value = false
emit('update:editMode', false)
} }
const notifyUser = (msg: string, type: 'success' | 'error' | 'warning' = 'success') => { const notifyUser = (msg: string, type: 'success' | 'error' | 'warning' = 'success') => {
message[type](`${msg}`) message[type](`${msg}`)
} }
function getDataForTable() {
for (const key in existingConfiguration) {
dataForTable.push({ ...(existingConfiguration[key] as IStringKeyMap) })
}
}
async function getExistingConfig(name: string) { async function getExistingConfig(name: string) {
currentAliasList.length = 0 const newList: string[] = []
const result = await getConfig<any>('picBed') const result = await getConfig<any>('picBed')
for (const key in existingConfiguration) { const newConfiguration: IStringKeyMap = {}
delete existingConfiguration[key]
}
if (!result || typeof result !== 'object' || Object.keys(result).length === 0) { if (!result || typeof result !== 'object' || Object.keys(result).length === 0) {
existingConfiguration[name] = { fail: '暂无配置' } newConfiguration[name] = { fail: '暂无配置' }
} else { } else {
for (const key in result) { for (const key in result) {
if (result[key].picBedName === name) { if (result[key].picBedName === name) {
existingConfiguration[key] = result[key] newConfiguration[key] = result[key]
currentAliasList.push(result[key].alias) newList.push(result[key].alias)
} }
} }
} }
existingConfiguration.value = newConfiguration
dataForTable.length = 0 currentAliasList.value = newList
getDataForTable()
handleConfigImport(aliasName) handleConfigImport(aliasName)
} }
function handleConfigImport(alias: string) { function handleConfigImport(alias: string) {
const selectedConfig = existingConfiguration[alias] if (alias === '') {
supportedPicBedList[platformName].options.forEach((option: any) => {
const defaultValue = supportedPicBedList[platformName].configOptions[option].default
if (defaultValue !== undefined && option !== 'alias') {
configResult.value[option] = defaultValue
}
})
return
}
const selectedConfig = existingConfiguration.value[alias]
if (!selectedConfig) return if (!selectedConfig) return
supportedPicBedList[selectedConfig.picBedName].options.forEach((option: any) => { supportedPicBedList[selectedConfig.picBedName].options.forEach((option: any) => {
if (selectedConfig[option] !== undefined) { if (selectedConfig[option] !== undefined) {
configResult[selectedConfig.picBedName + '.' + option] = selectedConfig[option] configResult.value[option] = selectedConfig[option]
} }
}) })
} }
@@ -370,7 +370,7 @@ const validateAllFields = (picBedName: string): boolean => {
for (const option of options) { for (const option of options) {
validateField(picBedName, option) validateField(picBedName, option)
if (formErrors[`${picBedName}.${option}`]) { if (formErrors.value[`${picBedName}.${option}`]) {
isValid = false isValid = false
} }
} }
@@ -378,41 +378,36 @@ const validateAllFields = (picBedName: string): boolean => {
return isValid return isValid
} }
function getAliasList() { const handleConfigReset = () => {
return Object.values(existingConfiguration).map(item => item.alias) const keys = Object.keys(formErrors.value).filter(key => key.startsWith(platformName))
}
const handleConfigReset = (name: string) => {
const keys = Object.keys(formErrors).filter(key => key.startsWith(name))
keys.forEach(key => { keys.forEach(key => {
delete formErrors[key] delete formErrors.value[key]
}) })
const configKeys = Object.keys(configResult).filter(key => key.startsWith(name)) const configKeys = Object.keys(configResult.value).filter(key => key.startsWith(platformName))
configKeys.forEach(key => { configKeys.forEach(key => {
delete configResult[key] delete configResult.value[key]
}) })
initializeDefaultValues(name) initializeDefaultValues()
} }
const initializeDefaultValues = (picBedName: string) => { const initializeDefaultValues = () => {
if (!supportedPicBedList[picBedName]) return if (!supportedPicBedList[platformName]) return
const options = supportedPicBedList[picBedName].options || [] const options = supportedPicBedList[platformName].options || []
for (const option of options) { for (const option of options) {
const fieldKey = `${picBedName}.${option}` const configOption = supportedPicBedList[platformName].configOptions[option]
const configOption = supportedPicBedList[picBedName].configOptions[option]
if (configResult[fieldKey] === undefined || configResult[fieldKey] === '') { if (configResult.value[option] === undefined || configResult.value[option] === '') {
if (configOption.default !== undefined) { if (configOption.default !== undefined) {
configResult[fieldKey] = configOption.default configResult.value[option] = configOption.default
} else if (configOption.type === 'boolean') { } else if (configOption.type === 'boolean') {
configResult[fieldKey] = false configResult.value[option] = false
} else if (configOption.type === 'number') { } else if (configOption.type === 'number') {
configResult[fieldKey] = 0 configResult.value[option] = 0
} else { } else {
configResult[fieldKey] = '' configResult.value[option] = ''
} }
} }
} }
@@ -423,7 +418,7 @@ const cancelEditMode = () => {
} }
onMounted(async () => { onMounted(async () => {
getExistingConfig(activeName) getExistingConfig(platformName)
await manageStore.refreshConfig() await manageStore.refreshConfig()
}) })
</script> </script>

View File

@@ -56,11 +56,10 @@
/> />
<span class="text-sm font-semibold text-secondary">{{ t('pages.manage.main.loading') }}</span> <span class="text-sm font-semibold text-secondary">{{ t('pages.manage.main.loading') }}</span>
</div> </div>
<div v-else class="menu-list"> <div v-else class="flex flex-col gap-1">
<template v-for="item in bucketNameList" :key="item">
<div <div
v-for="item in bucketNameList" class="flex cursor-pointer items-center gap-3 rounded-sm p-3 text-sm shadow-xs hover:bg-surface [.active]:bg-accent/20"
:key="item"
class="menu-item"
:class="{ active: item === currentSelectedBucket }" :class="{ active: item === currentSelectedBucket }"
@click="handleSelectMenu(item)" @click="handleSelectMenu(item)"
> >
@@ -76,7 +75,8 @@
</div> </div>
</div> </div>
</span> </span>
</div> </div></template
>
</div> </div>
</div> </div>
@@ -101,7 +101,7 @@
:text="t('pages.manage.main.settings')" :text="t('pages.manage.main.settings')"
:icon="SettingsIcon" :icon="SettingsIcon"
class="border-none" class="border-none"
@click="openBucketPageSetting" @click="openSettingPage"
/> />
</div> </div>
</div> </div>
@@ -113,8 +113,16 @@
@mousedown="startResize" @mousedown="startResize"
></div> ></div>
<div class="content-area"> <div class="m-0 box-border flex h-full w-full flex-1 flex-col overflow-hidden border-none">
<router-view /> <template v-if="currentPageInMain === 'bucket'">
<BucketPage :config-map="configMap" />
</template>
<template v-else-if="currentPageInMain === 'setting'">
<ManageSetting />
</template>
<template v-else>
<EmptyPage no-desc />
</template>
</div> </div>
</div> </div>
</div> </div>
@@ -173,110 +181,68 @@
</div> </div>
</CustomModal> </CustomModal>
</transition> </transition>
<transition
name="modal-bucket"
enter-active-class="transition-all duration-200 ease-apple"
leave-active-class="transition-all duration-200 ease-apple"
enter-from-class="opacity-0"
leave-to-class="opacity-0"
>
<!-- New Bucket Drawer --> <!-- New Bucket Drawer -->
<div v-if="nweBucketDrawerVisible" class="drawer-overlay" @click="nweBucketDrawerVisible = false"> <CustomModal
<div class="drawer-container" @click.stop> v-if="bucketDrawerVisible"
<div class="drawer-header"> v-model:visible="bucketDrawerVisible"
<h3 class="drawer-title"> :title="t('pages.manage.main.newBucket')"
{{ t('pages.manage.main.newBucket') }} width="600px"
</h3> height="auto"
<button class="drawer-close" @click="nweBucketDrawerVisible = false"> >
<XIcon class="close-icon" />
</button>
</div>
<div class="drawer-content"> <div class="drawer-content">
<form @submit.prevent="createNewBucket(currentPicBedName)"> <SettingSection :title="supportedPicBedList[currentPicBedName].name" :icon="Database" only-one-row>
<div class="form-header"> <template v-for="option in newBucketConfig[currentPicBedName].options" :key="option">
<div class="form-icon"> <SettingCard :p1="newBucketConfig[currentPicBedName].configOptions[option].component === 'switch'">
<img :src="`./assets/${currentPicBedName}.webp`" class="picbed-form-icon" /> <CustomInput
</div> v-if="newBucketConfig[currentPicBedName].configOptions[option].component === 'input'"
</div>
<div class="form-divider" />
<div v-for="option in newBucketConfig[currentPicBedName].options" :key="option" class="form-group">
<label class="form-label">
{{ newBucketConfig[currentPicBedName].configOptions[option].description }}
</label>
<!-- Input field -->
<input
v-if="
newBucketConfig[currentPicBedName].configOptions[option].component === 'input' &&
currentPicBedName !== 'tcyun'
"
v-model.trim="newBucketConfigResult[currentPicBedName + '.' + option]" v-model.trim="newBucketConfigResult[currentPicBedName + '.' + option]"
type="text" type="text"
class="form-input" :title="newBucketConfig[currentPicBedName].configOptions[option].description"
:placeholder="newBucketConfig[currentPicBedName].configOptions[option].placeholder" :placeholder="newBucketConfig[currentPicBedName].configOptions[option].placeholder"
/>
<!-- TCyun special input with append -->
<div
v-if="
currentPicBedName === 'tcyun' &&
newBucketConfig[currentPicBedName].configOptions[option].component === 'input'
"
class="input-group"
> >
<input <template v-if="currentPicBedName === 'tcyun'" #input-extra>
v-model.trim="newBucketConfigResult[currentPicBedName + '.' + option]" <span
type="text" class="absolute top-0.5 right-0 flex cursor-not-allowed items-center justify-center rounded-xl border border-border bg-gray-300 p-2.5 text-sm font-semibold text-secondary"
class="form-input group-input" >{{ '-' + currentPagePicBedConfig.appId }}</span
:placeholder="newBucketConfig[currentPicBedName].configOptions[option].placeholder"
/>
<span class="input-append">{{ '-' + currentPagePicBedConfig.appId }}</span>
</div>
<!-- Select field -->
<div
v-if="newBucketConfig[currentPicBedName].configOptions[option].component === 'select'"
class="select-wrapper"
> >
<select v-model="newBucketConfigResult[currentPicBedName + '.' + option]" class="form-select"> </template>
<option </CustomInput>
v-for="(label, value) in newBucketConfig[currentPicBedName].configOptions[option].options" <CustomSwitch
:key="value"
:value="value"
>
{{ label }}
</option>
</select>
<ChevronDownIcon class="select-arrow" />
</div>
<!-- Switch field -->
<label
v-if="newBucketConfig[currentPicBedName].configOptions[option].component === 'switch'" v-if="newBucketConfig[currentPicBedName].configOptions[option].component === 'switch'"
class="switch-label"
>
<input
v-model="newBucketConfigResult[currentPicBedName + '.' + option]" v-model="newBucketConfigResult[currentPicBedName + '.' + option]"
type="checkbox" :title="newBucketConfig[currentPicBedName].configOptions[option].description"
class="switch-input" small
:true-value="true" no-border
:false-value="false"
/> />
<span class="switch-slider"> <SingleSelect
<span class="switch-button" /> v-if="newBucketConfig[currentPicBedName].configOptions[option].component === 'select'"
</span> v-model="newBucketConfigResult[currentPicBedName + '.' + option]"
</label> :title="newBucketConfig[currentPicBedName].configOptions[option].description"
</div> :key-list="Object.keys(newBucketConfig[currentPicBedName].configOptions[option].options)"
:fronticon="false"
<div class="form-actions"> >
<button type="button" class="action-button secondary" @click="nweBucketDrawerVisible = false"> <template #item="{ item }">
{{ $t('common.cancel') }} {{ newBucketConfig[currentPicBedName].configOptions[option].options[item] }}
</button> </template>
<button type="submit" class="action-button primary"> </SingleSelect>
<CheckIcon class="button-icon" /> </SettingCard>
{{ t('common.submit') }} </template>
</button> </SettingSection>
</div> <div></div>
</form>
</div>
</div>
</div> </div>
<template #footer>
<CustomButton type="secondary" :text="$t('common.cancel')" @click="bucketDrawerVisible = false" />
<CustomButton type="primary" :text="$t('common.submit')" @click="createNewBucket(currentPicBedName)" />
</template>
</CustomModal>
</transition>
</div> </div>
</template> </template>
@@ -284,20 +250,27 @@
import { import {
ArrowLeftRightIcon, ArrowLeftRightIcon,
CheckIcon, CheckIcon,
ChevronDownIcon, Database,
ExternalLinkIcon, ExternalLinkIcon,
HomeIcon, HomeIcon,
PlusIcon, PlusIcon,
SettingsIcon, SettingsIcon,
XIcon,
} from 'lucide-vue-next' } from 'lucide-vue-next'
import { onBeforeMount, reactive, ref, watch } from 'vue' import { onBeforeMount, reactive, ref, watch } from 'vue'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import { useRoute, useRouter } from 'vue-router' import { useRoute, useRouter } from 'vue-router'
import CustomButton from '@/components/common/CustomButton.vue' import CustomButton from '@/components/common/CustomButton.vue'
import CustomInput from '@/components/common/CustomInput.vue'
import CustomModal from '@/components/common/CustomModal.vue' import CustomModal from '@/components/common/CustomModal.vue'
import CustomSwitch from '@/components/common/CustomSwitch.vue'
import SettingCard from '@/components/common/SettingCard.vue'
import SettingSection from '@/components/common/SettingSection.vue'
import SingleSelect from '@/components/common/SingleSelect.vue'
import useMessage from '@/hooks/useMessage' import useMessage from '@/hooks/useMessage'
import BucketPage from '@/manage/pages/BucketPage.vue'
import EmptyPage from '@/manage/pages/EmptyPage.vue'
import ManageSetting from '@/manage/pages/ManageSetting.vue'
import { useManageStore } from '@/manage/store/manageStore' import { useManageStore } from '@/manage/store/manageStore'
import { supportedPicBedList } from '@/manage/utils/constants' import { supportedPicBedList } from '@/manage/utils/constants'
import { newBucketConfig } from '@/manage/utils/newBucketConfig' import { newBucketConfig } from '@/manage/utils/newBucketConfig'
@@ -308,6 +281,8 @@ const manageStore = useManageStore() as any
const route = useRoute() const route = useRoute()
const router = useRouter() const router = useRouter()
const message = useMessage() const message = useMessage()
const currentPageInMain = ref<'bucket' | 'setting' | 'empty'>('empty')
const configMap = ref<any>(null)
const currentAlias = ref(route.query.alias as string) const currentAlias = ref(route.query.alias as string)
const currentPicBedName = ref(route.query.picBedName as string) const currentPicBedName = ref(route.query.picBedName as string)
@@ -324,7 +299,7 @@ const currentSelectedBucket = ref('')
const bucketNameList = ref([] as string[]) const bucketNameList = ref([] as string[])
const isLoadingBucketList = ref(false) const isLoadingBucketList = ref(false)
const nweBucketDrawerVisible = ref(false) const bucketDrawerVisible = ref(false)
const picBedSwitchDialogVisible = ref(false) const picBedSwitchDialogVisible = ref(false)
watch( watch(
@@ -380,7 +355,7 @@ const menuTitleMap: IStringKeyMap = {
const openPicBedUrl = () => window.electron.sendRPC(IRPCActionType.OPEN_URL, urlMap[currentPagePicBedConfig.picBedName]) const openPicBedUrl = () => window.electron.sendRPC(IRPCActionType.OPEN_URL, urlMap[currentPagePicBedConfig.picBedName])
function openNewBucketDrawer() { function openNewBucketDrawer() {
nweBucketDrawerVisible.value = true bucketDrawerVisible.value = true
} }
function createNewBucket(picBedName: string) { function createNewBucket(picBedName: string) {
@@ -403,7 +378,7 @@ function createNewBucket(picBedName: string) {
if (result) { if (result) {
// Show success notification // Show success notification
message.success(t('pages.manage.main.createSuccess')) message.success(t('pages.manage.main.createSuccess'))
nweBucketDrawerVisible.value = false bucketDrawerVisible.value = false
setTimeout(() => { setTimeout(() => {
getBucketList() getBucketList()
}, 2000) }, 2000)
@@ -452,7 +427,7 @@ function handleSelectMenu(bucketName: string) {
prefix = prefix.endsWith('/') ? prefix : `${prefix}/` prefix = prefix.endsWith('/') ? prefix : `${prefix}/`
} }
const configMap = { const configMapT = {
prefix, prefix,
bucketName, bucketName,
customUrl: transformedConfig[bucketName]?.customUrl ?? '', customUrl: transformedConfig[bucketName]?.customUrl ?? '',
@@ -464,16 +439,8 @@ function handleSelectMenu(bucketName: string) {
webPath: currentPicBedConfig.webPath || '', webPath: currentPicBedConfig.webPath || '',
} }
currentSelectedBucket.value = bucketName currentSelectedBucket.value = bucketName
router.push({ configMap.value = configMapT
path: '/main-page/manage-main-page/manage-bucket-page', currentPageInMain.value = 'bucket'
query: {
configMap: JSON.stringify(configMap),
alias: currentAlias.value,
picBedName: currentPicBedName.value,
config: JSON.stringify(currentPagePicBedConfig),
allPicBedConfigure: JSON.stringify(allPicBedConfigure),
},
})
} }
function switchPicBed(picBedAlias: string) { function switchPicBed(picBedAlias: string) {
@@ -511,16 +478,8 @@ function changePicBed() {
picBedSwitchDialogVisible.value = true picBedSwitchDialogVisible.value = true
} }
function openBucketPageSetting() { function openSettingPage() {
router.push({ currentPageInMain.value = 'setting'
path: '/main-page/manage-main-page/manage-setting-page',
query: {
alias: currentAlias.value,
picBedName: currentPicBedName.value,
config: JSON.stringify(currentPagePicBedConfig),
allPicBedConfigure: JSON.stringify(allPicBedConfigure),
},
})
} }
function startResize(event: MouseEvent) { function startResize(event: MouseEvent) {
@@ -554,5 +513,3 @@ onBeforeMount(() => {
getBucketList() getBucketList()
}) })
</script> </script>
<style src="./css/ManageMain.css" scoped></style>

View File

@@ -1,688 +0,0 @@
/* ManageMain Page Styles */
html, body {
overflow-x: hidden;
}
.manage-container {
display: flex;
height: calc(100vh - 32px);
flex-direction: column;
}
.manage-card {
border: 1px solid var(--color-border);
border-radius: var(--radius-lg);
background: var(--color-background-secondary);
box-shadow: var(--shadow-sm);
}
.header-card {
padding: 0.5rem;
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
gap: 1rem;
}
.header-content {
display: flex;
align-items: center;
gap: 1rem;
}
.header-icon {
display: flex;
justify-content: center;
align-items: center;
width: 68px;
height: 48px;
}
.header-icon-img {
width: 40px;
height: 40px;
object-fit: contain;
}
.header-text .header-title {
margin: 0 0 0.01rem;
font-size: 1.25rem;
font-weight: 600;
color: var(--color-text-primary);
}
.header-text .header-subtitle {
margin: 0;
font-size: 0.875rem;
color: var(--color-text-secondary);
}
.header-actions {
display: flex;
gap: 1rem;
}
.main-card {
flex: 1;
overflow: hidden;
min-height: 0; /* Fix for flex overflow */
}
.main-layout {
display: flex;
height:100%;
}
.sidebar {
display: flex;
border-right: 1px solid var(--color-border);
min-width: 120px;
max-width: 400px;
min-height: 0; /* Fix for flex overflow */
background: var(--color-surface-secondary);
transition: width 0.1s ease-out;
flex-direction: column;
}
.resize-handle {
position: relative;
display: flex;
justify-content: center;
align-items: center;
width: 4px;
background: transparent;
cursor: col-resize;
flex-shrink: 0;
}
.resize-handle:hover {
background: var(--color-border);
}
.resize-handle:hover .resize-line {
opacity: 1;
}
.resize-line {
border-radius: 1px;
width: 2px;
height: 40px;
background: var(--color-accent);
opacity: 0;
transition: opacity 0.2s ease;
}
.sidebar-header {
border-bottom: 1px solid var(--color-border);
padding: 1rem;
flex-shrink: 0;
}
.sidebar-title {
margin: 0;
font-size: 1rem;
font-weight: 600;
color: var(--color-text-primary);
}
.sidebar-content {
flex: 1;
overflow-y: auto;
padding: 0.5rem;
min-height: 0;
}
.loading-container {
display: flex;
justify-content: center;
align-items: center;
padding: 2rem;
flex-direction: column;
gap: 0.5rem;
}
.loading-spinner {
border: 2px solid var(--color-border);
border-top: 2px solid var(--color-accent);
border-radius: var(--radius-round);
width: 20px;
height: 20px;
animation: spin 1s linear infinite;
}
@keyframes spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
.loading-text {
font-size: 0.75rem;
color: var(--color-text-secondary);
}
.menu-list {
display: flex;
flex-direction: column;
gap: 0.25rem;
}
.menu-item {
display: flex;
align-items: center;
border-radius: var(--radius-md);
padding: 0.75rem;
font-size: 0.875rem;
transition: var(--transition-fast);
gap: 0.75rem;
cursor: pointer;
}
.menu-item:hover {
background: var(--color-surface);
}
.menu-item.active {
color: white;
background: var(--color-accent);
}
.menu-item.active .menu-icon {
color: white;
}
.menu-icon {
width: 16px;
height: 16px;
color: var(--color-text-secondary);
flex-shrink: 0;
}
.menu-icon.active {
color: var(--color-accent);
}
.menu-text {
overflow: hidden;
min-width: 0;
font-weight: 500;
line-height: 1.2;
overflow-wrap: break-word;
flex: 1;
}
.sidebar-footer {
border-top: 1px solid var(--color-border);
padding: 0.5rem;
flex-shrink: 0;
}
.footer-actions {
display: flex;
flex-direction: column;
gap: 0.25rem;
}
.footer-action-item {
display: flex;
align-items: center;
border: none;
border-radius: var(--radius-md);
padding: 0.75rem;
width: 100%;
font-size: 0.875rem;
text-align: left;
color: var(--color-text-primary);
background: none;
transition: var(--transition-fast);
gap: 0.75rem;
cursor: pointer;
}
.footer-action-item:hover {
background: var(--color-surface);
}
.action-icon {
width: 16px;
height: 16px;
color: var(--color-text-secondary);
flex-shrink: 0;
}
.action-text {
font-weight: 500;
}
.content-area {
display: flex;
overflow: hidden;
flex-direction: column;
margin: 0;
width: 100%;
height: 100%;
box-sizing: border-box;
}
.action-button {
display: flex;
align-items: center;
border: none;
border-radius: var(--radius-lg);
padding: 0.5rem 1rem;
font-size: 0.8rem;
font-family: inherit;
font-weight: 500;
transition: var(--transition-fast);
gap: 0.5rem;
cursor: pointer;
}
.action-button.primary {
color: white;
background: var(--color-accent);
}
.action-button.primary:hover {
background: var(--color-accent-hover);
transform: translateY(-1px);
box-shadow: var(--shadow-md);
}
.action-button.secondary {
border: 1px solid var(--color-border);
color: var(--color-text-primary);
background: var(--color-accent);
}
.action-button.secondary:hover {
border-color: var(--color-accent);
}
.button-icon {
width: 14px;
height: 14px;
}
/* Dialog styles */
.dialog-overlay {
position: fixed;
inset: 0;
z-index: 2000;
display: flex;
justify-content: center;
align-items: center;
background: rgb(0 0 0 / 50%);
}
.dialog-container {
overflow: auto;
border: 1px solid var(--color-border);
border-radius: var(--radius-2xl);
padding: 0.75rem;
width: 85vw;
max-width: 90vw;
max-height: 85vh;
background: var(--color-background-tertiary);
box-shadow: var(--shadow-xl);
scrollbar-width: none;
}
.dialog-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 1.5rem 1.5rem 0;
}
.dialog-title {
font-size: 1.25rem;
margin-bottom: 10px;
font-weight: 600;
color: var(--color-text-primary);
}
.dialog-close {
display: flex;
justify-content: center;
align-items: center;
border: 1px solid var(--color-border);
border-radius: var(--radius-sm);
width: 32px;
height: 32px;
font-size: 1.5rem;
color: var(--color-text-secondary);
background: none;
transition: all 0.2s ease;
cursor: pointer;
}
.dialog-close:hover {
color: var(--color-text-primary);
background: var(--color-surface);
}
.close-icon {
width: 16px;
height: 16px;
}
.dialog-content {
overflow-y: auto;
margin-bottom: 1.5rem;
padding: 0.2rem;
scrollbar-width: none;
-ms-overflow-style: none;
}
.choice-cos {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 1rem;
}
.picbed-card {
position: relative;
display: flex;
align-items: center;
border: 2px solid var(--color-border);
border-radius: var(--radius-lg);
padding: 1.5rem;
background: var(--color-background-secondary);
transition: var(--transition-fast);
flex-direction: column;
cursor: pointer;
}
.picbed-card:hover {
border-color: var(--color-accent);
transform: translateY(-2px);
box-shadow: var(--shadow-md);
}
.picbed-card.active {
border-color: var(--color-accent);
background-color: var(--color-surface);
}
.picbed-card.main-card {
border-color: var(--color-primary);
}
.picbed-card.main-card:hover {
border-color: var(--color-primary);
background-color: var(--color-surface);
}
.card-icon {
display: flex;
justify-content: center;
align-items: center;
margin-bottom: 0.75rem;
width: 40px;
height: 40px;
}
.picbed-icon {
width: 32px;
height: 32px;
object-fit: contain;
}
.main-icon {
width: 24px;
height: 24px;
color: var(--color-error);
}
.card-content {
text-align: center;
}
.card-title {
font-size: 0.9rem;
font-weight: 600;
color: var(--color-text-primary);
}
.main-title {
color: var(--color-error);
}
.check-icon {
position: absolute;
top: 0.5rem;
right: 0.5rem;
width: 20px;
height: 20px;
color: var(--color-accent);
}
/* Drawer styles */
.drawer-overlay {
position: fixed;
inset: 32px 0 0;
z-index: 2000;
display: flex;
justify-content: flex-end;
align-items: center;
background: rgb(0 0 0 / 50%);
}
.drawer-container {
overflow-y: auto;
border: 1px solid var(--color-border);
border-radius: var(--radius-2xl);
width: 400px;
max-width: 90vw;
height: calc(100vh - 32px);
background: var(--color-background-tertiary);
box-shadow: var(--shadow-xl);
}
.drawer-header {
display: flex;
justify-content: space-between;
align-items: center;
border-bottom: 1px solid var(--color-border);
padding: 1.5rem;
}
.drawer-title {
margin: 0;
font-size: 1.25rem;
font-weight: 600;
color: var(--color-text-primary);
}
.drawer-close {
display: flex;
justify-content: center;
align-items: center;
border: none;
border-radius: var(--radius-sm);
padding: 0.25rem;
width: 24px;
height: 24px;
color: var(--color-text-secondary);
background: none;
transition: var(--transition-fast);
cursor: pointer;
}
.drawer-close:hover {
color: var(--color-text-primary);
background: var(--color-surface-elevated);
}
.drawer-content {
padding: 1.5rem;
}
.form-header {
display: flex;
justify-content: center;
padding: 2rem 0;
}
.form-icon {
display: flex;
justify-content: center;
align-items: center;
width: 60px;
height: 60px;
}
.picbed-form-icon {
width: 48px;
height: 48px;
object-fit: contain;
}
.form-divider {
margin: 1.5rem 0;
height: 1px;
background: var(--color-border);
}
.form-group {
margin-bottom: 1.5rem;
}
.form-label {
display: block;
margin-bottom: 0.5rem;
font-size: 0.875rem;
font-weight: 500;
color: var(--color-text-primary);
}
.form-input {
border: 1px solid var(--color-border);
border-radius: var(--radius-md);
padding: 0.75rem;
width: 100%;
font-size: 0.875rem;
color: var(--color-text-secondary);
background: var(--color-surface);
transition: var(--transition-fast);
box-sizing: border-box;
}
.form-input:focus {
border-color: var(--color-accent);
background: white;
outline: none;
box-shadow: 0 0 0 2px rgb(0 122 255 / 20%);
}
.input-group {
display: flex;
}
.group-input {
border-top-right-radius: 0;
border-bottom-right-radius: 0;
border-right: none;
}
.input-append {
border: 1px solid var(--color-border);
border-left: none;
padding: 0.75rem;
font-size: 0.875rem;
color: var(--color-text-secondary);
background: var(--color-background-secondary);
border-top-right-radius: var(--radius-md);
border-bottom-right-radius: var(--radius-md);
}
.select-wrapper {
position: relative;
}
.form-select {
border: 1px solid var(--color-border);
border-radius: var(--radius-md);
padding: 0.75rem 2.5rem 0.75rem 0.75rem;
width: 100%;
font-size: 0.875rem;
color: var(--color-text-primary);
background: var(--color-background-tertiary);
transition: var(--transition-fast);
appearance: none;
cursor: pointer;
}
.form-select:focus {
border-color: var(--color-accent);
outline: none;
box-shadow: 0 0 0 2px rgb(0 122 255 / 20%);
}
.select-arrow {
position: absolute;
top: 50%;
right: 0.75rem;
width: 16px;
height: 16px;
color: var(--color-text-secondary);
transform: translateY(-50%);
pointer-events: none;
}
.switch-label {
display: flex;
align-items: center;
cursor: pointer;
}
.switch-input {
position: absolute;
opacity: 0;
cursor: pointer;
}
.switch-slider {
position: relative;
border-radius: 0.75rem;
width: 3rem;
height: 1.5rem;
background: var(--color-border);
transition: var(--transition-fast);
}
.switch-button {
position: absolute;
top: 2px;
left: 2px;
border-radius: var(--radius-round);
width: 1.25rem;
height: 1.25rem;
background: white;
box-shadow: var(--shadow-sm);
transition: var(--transition-fast);
}
.switch-input:checked + .switch-slider {
background: var(--color-accent);
}
.switch-input:checked + .switch-slider .switch-button {
transform: translateX(1.5rem);
}
.form-actions {
display: flex;
justify-content: flex-end;
margin-top: 2rem;
gap: 1rem;
}

View File

@@ -71,6 +71,7 @@
small small
:title="t('pages.settings.system.isDisableGPU')" :title="t('pages.settings.system.isDisableGPU')"
:description="t('pages.settings.system.isDisableGPUDesc')" :description="t('pages.settings.system.isDisableGPUDesc')"
@update:model-value="handleIsDisableGPUChange"
/> />
</SettingCard> </SettingCard>
@@ -189,13 +190,16 @@
<!-- Startup & Shortcuts Section --> <!-- Startup & Shortcuts Section -->
<SettingSection :icon="Keyboard" :title="t('pages.settings.system.startupAndShortcuts')"> <SettingSection :icon="Keyboard" :title="t('pages.settings.system.startupAndShortcuts')">
<!-- Auto Launch Toggle --> <!-- Auto Launch Toggle -->
<SettingCard p1>
<CustomSwitch <CustomSwitch
v-model="formOfSetting.autoStart" v-model="formOfSetting.autoStart"
small small
no-border
:title="t('pages.settings.system.autoLaunch')" :title="t('pages.settings.system.autoLaunch')"
:description="t('pages.settings.system.autoLaunchDesc')" :description="t('pages.settings.system.autoLaunchDesc')"
@change="handleAutoStartChange(formOfSetting.autoStart)" @change="handleAutoStartChange(formOfSetting.autoStart)"
/> />
</SettingCard>
<CustomNavCard <CustomNavCard
:title="t('pages.settings.system.setShortCuts')" :title="t('pages.settings.system.setShortCuts')"
:description="t('pages.settings.system.setShortCutsDesc')" :description="t('pages.settings.system.setShortCutsDesc')"
@@ -1570,11 +1574,6 @@ const addWatch = () => {
} }
}) })
watch(isDisableGPU, newVal => {
message.info(t('pages.settings.system.needRestart'))
saveConfig({ [configPaths.settings.isDisableGPU]: newVal })
})
watch( watch(
advancedRename, advancedRename,
newVal => { newVal => {
@@ -1744,6 +1743,12 @@ async function handleThemeChange(theme: string) {
} }
} }
function handleIsDisableGPUChange(value: boolean | undefined) {
if (value === undefined) return
message.info(t('pages.settings.system.needRestart'))
saveConfig({ [configPaths.settings.isDisableGPU]: value })
}
async function initData() { async function initData() {
const config = (await getConfig<IConfig>()) || ({} as IConfig) const config = (await getConfig<IConfig>()) || ({} as IConfig)
const settings = config.settings || {} const settings = config.settings || {}

View File

@@ -270,14 +270,7 @@
height="auto" height="auto"
> >
<div class="flex-1 overflow-y-auto p-4"> <div class="flex-1 overflow-y-auto p-4">
<config-form <config-form :id="configName" ref="$configForm" :config="config" :type="currentType" mode="plugin" />
:id="configName"
ref="$configForm"
:config="config"
:type="currentType"
mode="plugin"
:show-tooltips="false"
/>
</div> </div>
<template #footer> <template #footer>
<CustomButton type="secondary" :text="t('common.cancel')" @click="dialogVisible = false" /> <CustomButton type="secondary" :text="t('common.cancel')" @click="dialogVisible = false" />