mirror of
https://github.com/Kuingsmile/PicList.git
synced 2026-05-06 20:42:57 +08:00
✨ Feature(custom): optimize UI of setting page
This commit is contained in:
@@ -28,12 +28,12 @@ const {
|
||||
text,
|
||||
disabled,
|
||||
active = false,
|
||||
icon,
|
||||
icon = null,
|
||||
iconSize = 16,
|
||||
type = 'primary',
|
||||
} = defineProps<{
|
||||
text: string
|
||||
icon: any
|
||||
icon?: any
|
||||
active?: boolean
|
||||
iconSize?: number
|
||||
disabled?: boolean
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
<input
|
||||
v-model="modelValue"
|
||||
:type="type"
|
||||
v-bind="$attrs"
|
||||
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"
|
||||
:placeholder="placeholder"
|
||||
/>
|
||||
@@ -26,20 +27,25 @@
|
||||
<script setup lang="ts">
|
||||
import { EyeClosedIcon, EyeIcon } from 'lucide-vue-next'
|
||||
import { onMounted, ref } from 'vue'
|
||||
const modelValue = defineModel<string>()
|
||||
const modelValue = defineModel<any>()
|
||||
const type = ref('text')
|
||||
const {
|
||||
isPassword = false,
|
||||
title,
|
||||
inputType = 'text',
|
||||
placeholder,
|
||||
} = defineProps<{
|
||||
isPassword?: boolean
|
||||
title: string
|
||||
inputType?: string
|
||||
placeholder: string
|
||||
}>()
|
||||
|
||||
onMounted(() => {
|
||||
if (isPassword) {
|
||||
type.value = 'password'
|
||||
} else {
|
||||
type.value = inputType
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
78
src/renderer/components/common/customModal.vue
Normal file
78
src/renderer/components/common/customModal.vue
Normal file
@@ -0,0 +1,78 @@
|
||||
<template>
|
||||
<div
|
||||
class="fixed inset-0 z-1000 flex items-center justify-center overflow-y-auto bg-black/30"
|
||||
:class="{ 'advanced-animation': enableAdvancedAnimation }"
|
||||
@click.stop
|
||||
>
|
||||
<div
|
||||
class="m-auto flex flex-col overflow-hidden rounded-lg border border-border-secondary bg-bg-tertiary shadow-xl"
|
||||
:style="{
|
||||
height: height || '85vh',
|
||||
maxHeight: maxHeight || '85vh',
|
||||
width: width || '90vw',
|
||||
maxWidth: maxWidth || '90vw',
|
||||
}"
|
||||
@click.stop
|
||||
>
|
||||
<div class="flex items-center justify-between border border-border-secondary bg-bg-tertiary px-5 py-4 max-md:p-2">
|
||||
<slot name="titleBar"></slot>
|
||||
<h3 v-if="title !== ''" class="m-0 text-xl font-semibold text-main">
|
||||
{{ title }}
|
||||
</h3>
|
||||
<span v-if="description !== ''" class="mt-1 text-xl font-semibold text-secondary">
|
||||
{{ description }}
|
||||
</span>
|
||||
<button
|
||||
class="flex h-8 w-8 cursor-pointer items-center justify-center rounded-full border border-border bg-surface-elevated text-secondary transition-all duration-fast ease-apple hover:scale-105 hover:border-danger hover:bg-danger hover:text-white focus-visible:focus-ring"
|
||||
@click="visible = false"
|
||||
>
|
||||
<XIcon :size="20" />
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
class="no-scrollbar h-[calc(90vh-90px)] flex-1 overflow-y-auto max-md:p-4"
|
||||
:style="{ height: height ? 'calc(' + height + ' - 90px)' : 'calc(85vh - 90px)' }"
|
||||
>
|
||||
<slot></slot>
|
||||
</div>
|
||||
<div v-if="$slots.footer" class="flex justify-end gap-3 border-t border-border-secondary p-3">
|
||||
<slot name="footer"></slot>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { XIcon } from 'lucide-vue-next'
|
||||
import { onBeforeMount, ref } from 'vue'
|
||||
|
||||
import { getConfig } from '@/utils/dataSender'
|
||||
|
||||
const visible = defineModel<boolean>('visible')
|
||||
|
||||
const enableAdvancedAnimation = ref(false)
|
||||
|
||||
const {
|
||||
title = '',
|
||||
description = '',
|
||||
height = '',
|
||||
maxHeight = '',
|
||||
width = '',
|
||||
maxWidth = '',
|
||||
} = defineProps<{
|
||||
title?: string
|
||||
description?: string
|
||||
height?: string
|
||||
width?: string
|
||||
maxHeight?: string
|
||||
maxWidth?: string
|
||||
}>()
|
||||
|
||||
async function initConf() {
|
||||
enableAdvancedAnimation.value = (await getConfig('settings.enableAdvancedAnimation')) || false
|
||||
}
|
||||
|
||||
onBeforeMount(() => {
|
||||
initConf()
|
||||
})
|
||||
</script>
|
||||
@@ -1,9 +1,12 @@
|
||||
<template>
|
||||
<div class="w-full rounded-lg border border-border bg-bg-secondary p-6 shadow-sm">
|
||||
<div class="mb-2 flex items-start gap-3">
|
||||
<div class="mb-2 flex h-[30px] w-[30px] shrink-0 items-center justify-center rounded-lg bg-accent text-white">
|
||||
<div
|
||||
v-if="icon"
|
||||
class="mb-2 flex h-[30px] w-[30px] shrink-0 items-center justify-center rounded-lg bg-accent text-white"
|
||||
>
|
||||
<slot name="icon">
|
||||
<component :is="icon" v-if="icon" :size="iconSize" />
|
||||
<component :is="icon" :size="iconSize" />
|
||||
</slot>
|
||||
</div>
|
||||
<div>
|
||||
@@ -27,15 +30,15 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
const {
|
||||
title,
|
||||
title = '',
|
||||
description = '',
|
||||
icon,
|
||||
icon = null,
|
||||
iconSize = 20,
|
||||
onlyOneRow = false,
|
||||
} = defineProps<{
|
||||
title: string
|
||||
title?: string
|
||||
description?: string
|
||||
icon: any
|
||||
icon?: any
|
||||
iconSize?: number
|
||||
onlyOneRow?: boolean
|
||||
}>()
|
||||
|
||||
@@ -384,48 +384,32 @@
|
||||
|
||||
<!-- Edit URL Modal -->
|
||||
<transition name="modal">
|
||||
<div
|
||||
<CustomModal
|
||||
v-if="dialogVisible"
|
||||
class="modal-overlay"
|
||||
:class="{ 'advanced-animation': enableAdvancedAnimation }"
|
||||
@click="dialogVisible = false"
|
||||
v-model:visible="dialogVisible"
|
||||
:height="'auto'"
|
||||
:width="'40%'"
|
||||
:title="t('pages.gallery.changeImageUrl')"
|
||||
>
|
||||
<div class="modal-container" @click.stop>
|
||||
<div class="modal-header">
|
||||
<h3 class="m-0 text-xl font-semibold text-main">{{ t('pages.gallery.changeImageUrl') }}</h3>
|
||||
<button class="modal-close-btn" @click="dialogVisible = false">
|
||||
<XIcon :size="20" />
|
||||
</button>
|
||||
</div>
|
||||
<div class="p-6">
|
||||
<div class="p-2">
|
||||
<input v-model="imgInfo.imgUrl" type="text" class="form-input" placeholder="Enter new URL" />
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn-secondary" @click="dialogVisible = false">
|
||||
{{ t('common.cancel') }}
|
||||
</button>
|
||||
<button class="btn-primary" @click="confirmModify">
|
||||
{{ t('common.confirm') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<template #footer>
|
||||
<CustomButton :type="'secondary'" :text="t('common.cancel')" @click="dialogVisible = false" />
|
||||
<CustomButton :type="'primary'" :text="t('common.confirm')" @click="confirmModify" />
|
||||
</template>
|
||||
</CustomModal>
|
||||
</transition>
|
||||
|
||||
<!-- Batch Rename Modal -->
|
||||
<transition name="modal">
|
||||
<div
|
||||
<CustomModal
|
||||
v-if="isShowBatchRenameDialog"
|
||||
class="modal-overlay"
|
||||
:class="{ 'advanced-animation': enableAdvancedAnimation }"
|
||||
v-model:visible="isShowBatchRenameDialog"
|
||||
:height="'auto'"
|
||||
:width="'700px'"
|
||||
:title="t('pages.gallery.batchEditUrl')"
|
||||
>
|
||||
<div class="modal-container" @click.stop>
|
||||
<div class="modal-header">
|
||||
<h3 class="m-0 text-xl font-semibold text-main">{{ t('pages.gallery.batchEditUrl') }}</h3>
|
||||
<button class="modal-close-btn" @click="isShowBatchRenameDialog = false">
|
||||
<XIcon :size="20" />
|
||||
</button>
|
||||
</div>
|
||||
<div class="p-6">
|
||||
<div class="mb-6 last:mb-0">
|
||||
<label class="form-label">
|
||||
@@ -443,9 +427,7 @@
|
||||
v-if="showMatchedUrls && matchedUrls.length > 0"
|
||||
class="absolute z-1000 mt-2 max-h-[300px] max-w-[650px] overflow-hidden rounded-md border border-border-secondary bg-bg-tertiary p-0 shadow-md"
|
||||
>
|
||||
<div
|
||||
class="border-b border-b-border-secondary bg-bg-secondary px-4 py-3 text-sm font-semibold text-main"
|
||||
>
|
||||
<div class="border-b border-b-border-secondary bg-bg-secondary px-4 py-3 text-sm font-semibold text-main">
|
||||
Matched URLs ({{ matchedUrls.length }}):
|
||||
</div>
|
||||
<div class="max-h-[240px] overflow-auto p-2">
|
||||
@@ -532,16 +514,11 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn-secondary" @click="isShowBatchRenameDialog = false">
|
||||
{{ t('common.cancel') }}
|
||||
</button>
|
||||
<button class="btn-primary" @click="handleBatchRename()">
|
||||
{{ t('common.confirm') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<template #footer>
|
||||
<CustomButton :type="'secondary'" :text="t('common.cancel')" @click="isShowBatchRenameDialog = false" />
|
||||
<CustomButton :type="'primary'" :text="t('common.confirm')" @click="handleBatchRename" />
|
||||
</template>
|
||||
</CustomModal>
|
||||
</transition>
|
||||
</div>
|
||||
</template>
|
||||
@@ -582,6 +559,8 @@ import { useI18n } from 'vue-i18n'
|
||||
import { onBeforeRouteUpdate } from 'vue-router'
|
||||
|
||||
import ALLApi from '@/apis/allApi'
|
||||
import CustomButton from '@/components/common/customButton.vue'
|
||||
import CustomModal from '@/components/common/customModal.vue'
|
||||
import MultiSelect from '@/components/common/multiSelect.vue'
|
||||
import SingleSelect from '@/components/common/singleSelect.vue'
|
||||
import VirtualScroller from '@/components/VirtualScroller.vue'
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -245,53 +245,22 @@
|
||||
</div>
|
||||
<!-- Image Process Dialog -->
|
||||
<transition name="modal">
|
||||
<div
|
||||
<CustomModal
|
||||
v-if="imageProcessDialogVisible"
|
||||
class="fixed inset-0 z-1000 flex items-center justify-center overflow-y-auto bg-black/30"
|
||||
:class="{ 'advanced-animation': enableAdvancedAnimation }"
|
||||
@click.stop
|
||||
>
|
||||
<div
|
||||
class="m-auto flex h-[85vh] w-[90vw] flex-col overflow-hidden rounded-2xl border border-border-secondary bg-bg-tertiary shadow-xl"
|
||||
@click.stop
|
||||
>
|
||||
<div
|
||||
class="flex items-center justify-between border border-border-secondary bg-bg-tertiary px-5 py-4 max-md:p-2"
|
||||
>
|
||||
<h3 class="m-0 text-xl font-semibold text-main">
|
||||
{{ t('pages.imageProcess.title') }}
|
||||
</h3>
|
||||
<span class="mt-1 text-xl font-semibold text-secondary">
|
||||
{{
|
||||
v-model:visible="imageProcessDialogVisible"
|
||||
:title="t('pages.imageProcess.title')"
|
||||
:description="
|
||||
PicBedId === '' ? t('pages.imageProcess.subtitle-Global') : t('pages.imageProcess.subtitle-PerPicbed')
|
||||
}}
|
||||
</span>
|
||||
<button
|
||||
class="flex h-8 w-8 cursor-pointer items-center justify-center rounded-full border border-border bg-surface-elevated text-secondary transition-all duration-fast ease-apple hover:scale-105 hover:border-danger hover:bg-danger hover:text-white focus-visible:focus-ring"
|
||||
@click="imageProcessDialogVisible = false"
|
||||
"
|
||||
>
|
||||
<XIcon :size="20" />
|
||||
</button>
|
||||
</div>
|
||||
<div class="no-scrollbar h-[calc(90vh-90px)] flex-1 overflow-y-auto max-md:p-4">
|
||||
<ImageProcessSetting :config-id="PicBedId" :current-picbed-name="defaultPicBedG" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</CustomModal>
|
||||
</transition>
|
||||
|
||||
<!-- Task Queue Manager Modal -->
|
||||
<transition name="modal">
|
||||
<div
|
||||
v-if="taskDialogVisible"
|
||||
class="fixed inset-0 z-1000 flex items-center justify-center overflow-y-auto bg-black/50 p-4 max-md:p-4"
|
||||
:class="{ 'advanced-animation': enableAdvancedAnimation }"
|
||||
>
|
||||
<div
|
||||
class="m-auto flex h-[85vh] max-h-[85vh] w-[90vw] max-w-[90vw] flex-col overflow-hidden rounded-2xl border border-border-secondary bg-bg-tertiary shadow-xl max-md:max-h-[90vh] max-md:w-[95vw]"
|
||||
@click.stop
|
||||
>
|
||||
<div class="flex items-center justify-between border border-border-secondary bg-bg-tertiary px-5 py-4">
|
||||
<CustomModal v-if="taskDialogVisible" v-model:visible="taskDialogVisible">
|
||||
<template #titleBar>
|
||||
<div class="flex flex-row items-center gap-4">
|
||||
<h3 class="flex items-center gap-2.5 bg-clip-text text-xl font-bold tracking-tight text-main">
|
||||
{{ t('pages.upload.taskQueue.title') }}
|
||||
@@ -305,13 +274,7 @@
|
||||
}}
|
||||
</span>
|
||||
</div>
|
||||
<button
|
||||
class="flex h-8 w-8 cursor-pointer items-center justify-center rounded-full border border-border bg-surface-elevated text-secondary transition-all duration-fast ease-apple hover:scale-105 hover:border-danger hover:bg-danger hover:text-white focus-visible:focus-ring"
|
||||
@click="taskDialogVisible = false"
|
||||
>
|
||||
<XIcon :size="20" />
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div class="no-scrollbar max-h-[calc(90vh-90px)] overflow-y-auto">
|
||||
<!-- Action Bar -->
|
||||
@@ -514,11 +477,7 @@
|
||||
<button class="filter-tab" :class="{ active: taskFilter === 'all' }" @click="taskFilter = 'all'">
|
||||
{{ t('pages.upload.taskQueue.filterAll') }}
|
||||
</button>
|
||||
<button
|
||||
class="filter-tab"
|
||||
:class="{ active: taskFilter === 'pending' }"
|
||||
@click="taskFilter = 'pending'"
|
||||
>
|
||||
<button class="filter-tab" :class="{ active: taskFilter === 'pending' }" @click="taskFilter = 'pending'">
|
||||
{{ t('pages.upload.taskQueue.filterPending') }}
|
||||
</button>
|
||||
<button
|
||||
@@ -679,8 +638,7 @@
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</CustomModal>
|
||||
</transition>
|
||||
</div>
|
||||
</template>
|
||||
@@ -718,6 +676,7 @@ import { computed, onBeforeMount, onBeforeUnmount, reactive, ref, useTemplateRef
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
import CustomModal from '@/components/common/customModal.vue'
|
||||
import ImageProcessSetting from '@/components/ImageProcessSetting.vue'
|
||||
import { usePicBed } from '@/hooks/useGlobal'
|
||||
import useMessage from '@/hooks/useMessage'
|
||||
@@ -787,7 +746,6 @@ const progress = ref(0)
|
||||
const showProgress = ref(false)
|
||||
const showError = ref(false)
|
||||
const pasteStyle = ref(IPasteStyle.MARKDOWN)
|
||||
const enableAdvancedAnimation = ref(false)
|
||||
const PicBedId = ref('')
|
||||
const fileInput = useTemplateRef('fileInput')
|
||||
const uploadInterval = ref(1000)
|
||||
@@ -987,7 +945,6 @@ function ipcSendFiles(files: FileList) {
|
||||
|
||||
async function initConf() {
|
||||
const settingConfig = await getConfig<any>('settings')
|
||||
enableAdvancedAnimation.value = settingConfig?.enableAdvancedAnimation || false
|
||||
pasteStyle.value = settingConfig?.pasteStyle || IPasteStyle.MARKDOWN
|
||||
pasteFormatList.value.Custom = settingConfig?.customLink || ''
|
||||
useShortUrl.value = settingConfig?.useShortUrl || false
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div
|
||||
class="relative z-1 no-scrollbar flex h-full w-full flex-col items-center justify-start gap-4 overflow-auto rounded-xl border-none px-4 py-6 shadow-sm"
|
||||
class="relative z-1 flex h-full w-full flex-col items-center justify-start gap-4 rounded-xl border-none px-4 py-6 shadow-sm"
|
||||
>
|
||||
<div
|
||||
class="flex w-full items-center justify-between gap-4 rounded-2xl border border-border-secondary px-6 py-2 shadow-md"
|
||||
@@ -34,11 +34,10 @@
|
||||
|
||||
<!-- Config Grid -->
|
||||
<div
|
||||
class="no-scrollbar flex w-full flex-1 items-start gap-4 overflow-auto rounded-2xl border border-border-secondary px-4 py-6 shadow-md"
|
||||
>
|
||||
<div
|
||||
class="no-scrollbar grid h-auto w-full grid-cols-[repeat(auto-fill,minmax(300px,1fr))] gap-5 overflow-auto border-none p-1 max-md:grid-cols-1 max-md:gap-4 xl:grid-cols-[repeat(auto-fill,minmax(325px,1fr))]"
|
||||
class="flex w-full flex-1 items-center gap-4 overflow-hidden rounded-2xl border border-border-secondary px-4 py-6 shadow-md"
|
||||
>
|
||||
<div class="no-scrollbar h-full w-full overflow-auto rounded-sm">
|
||||
<div class="grid w-full grid-cols-[repeat(auto-fill,minmax(300px,1fr))] gap-5 border-none p-1 max-md:gap-4">
|
||||
<!-- Config Items -->
|
||||
<div
|
||||
v-for="(item, index) in curConfigList"
|
||||
@@ -139,6 +138,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user