Feature(custom): rewrite setting page, WIP

This commit is contained in:
Kuingsmile
2025-08-06 11:19:19 +08:00
parent 32c3eaba12
commit 4b8bfded1d
182 changed files with 5536 additions and 3322 deletions

View File

@@ -110,7 +110,7 @@ import { reactive, ref, toRefs, watch } from 'vue'
import { useRoute } from 'vue-router'
import { getConfig } from '@/utils/dataSender'
import { IPicGoPluginConfig, IStringKeyMap } from '#/types/types'
import type { IPicGoPluginConfig, IStringKeyMap } from '#/types/types'
interface IProps {
config: any[]

View File

@@ -83,7 +83,7 @@ import { cloneDeep, union } from 'lodash-es'
import { reactive, ref, watch } from 'vue'
import { getConfig } from '@/utils/dataSender'
import { IPicGoPluginConfig, IStringKeyMap } from '#/types/types'
import type { IPicGoPluginConfig, IStringKeyMap } from '#/types/types'
interface IProps {
config: any[]

View File

@@ -24,7 +24,7 @@ import { Loading } from '@element-plus/icons-vue'
import { computed, onMounted, ref, watch } from 'vue'
import { getFileIconPath } from '@/manage/utils/common'
import { IRPCActionType } from '#/types/enum'
import { IRPCActionType } from '@/utils/enum'
const preSignedUrl = ref('')

View File

@@ -3,7 +3,7 @@ import { ElIcon, ElImage } from 'element-plus'
import { computed, defineComponent, onMounted, ref, watch } from 'vue'
import { getFileIconPath } from '@/manage/utils/common'
import { IRPCActionType } from '#/types/enum'
import { IRPCActionType } from '@/utils/enum'
export default defineComponent({
props: {

View File

@@ -511,8 +511,8 @@ import type { IBuildInCompressOptions, IBuildInWaterMarkOptions } from 'piclist'
import { computed, onBeforeMount, reactive, ref, toRaw } from 'vue'
import { useI18n } from 'vue-i18n'
import { configPaths } from '@/utils/configPaths'
import { getConfig, saveConfig } from '@/utils/dataSender'
import { configPaths } from '#/utils/configPaths'
const { t } = useI18n()
const imageProcessDialogVisible = defineModel<boolean>()

View File

@@ -25,7 +25,7 @@ import { computed, onMounted, ref, watch } from 'vue'
import { getFileIconPath } from '@/manage/utils/common'
import { getAuthHeader } from '@/manage/utils/digestAuth'
import { formatEndpoint } from '#/utils/common'
import { formatEndpoint } from '@/utils/common'
const base64Url = ref('')
const success = ref(false)

View File

@@ -4,7 +4,7 @@ import { computed, defineComponent, onMounted, ref, watch } from 'vue'
import { getFileIconPath } from '@/manage/utils/common'
import { getAuthHeader } from '@/manage/utils/digestAuth'
import { formatEndpoint } from '#/utils/common'
import { formatEndpoint } from '@/utils/common'
export default defineComponent({
props: {

View File

@@ -32,8 +32,8 @@ import type { IpcRendererEvent } from 'electron'
import { onBeforeMount, onBeforeUnmount, reactive, ref } from 'vue'
import $bus from '@/utils/bus'
import { SHOW_INPUT_BOX, SHOW_INPUT_BOX_RESPONSE } from '#/events/constants'
import { IShowInputBoxOption } from '#/types/types'
import { SHOW_INPUT_BOX, SHOW_INPUT_BOX_RESPONSE } from '@/utils/constant'
import type { IShowInputBoxOption } from '#/types/types'
const inputBoxValue = ref('')
const showInputBoxVisible = ref(false)

View File

@@ -218,14 +218,14 @@ import { pick } from 'lodash-es'
import { BadgeInfoIcon, CheckIcon, ChevronDownIcon, CopyIcon, DatabaseIcon, FolderIcon, PieChartIcon, PlugIcon, Settings, UploadIcon } from 'lucide-vue-next'
import QrcodeVue from 'qrcode.vue'
import pkg from 'root/package.json'
import { SHOW_MAIN_PAGE_QRCODE } from 'root/src/universal/events/constants'
import { computed, nextTick, onBeforeMount, reactive, Ref, ref, watch } from 'vue'
import { useI18n } from 'vue-i18n'
import * as config from '@/router/config'
import { SHOW_MAIN_PAGE_QRCODE } from '@/utils/constant'
import { getConfig } from '@/utils/dataSender'
import { IRPCActionType } from '@/utils/enum'
import { picBedGlobal, updatePicBedGlobal } from '@/utils/global'
import { IRPCActionType } from '#/types/enum'
import ThemeSwitcher from './ui/ThemeSwitcher.vue'
const version = ref(pkg.version)

View File

@@ -11,10 +11,9 @@
</template>
<script lang="ts" setup>
import { IToolboxItemCheckStatus } from '#/types/enum'
interface IProps {
status: IToolboxItemCheckStatus
status: string
value: any
handlerText: string
handler: (value: any) => void | Promise<void>

View File

@@ -19,10 +19,10 @@
import { CircleCloseFilled, Loading, SuccessFilled } from '@element-plus/icons-vue'
import { computed } from 'vue'
import { IToolboxItemCheckStatus } from '#/types/enum'
import { IToolboxItemCheckStatus } from '@/utils/enum'
interface IProps {
status: IToolboxItemCheckStatus
status: string
}
const props = defineProps<IProps>()

View File

@@ -0,0 +1,342 @@
<template>
<div
v-if="isOpen"
class="messagebox-overlay"
@click="onCancel"
>
<div
class="messagebox-container"
@click.stop
>
<div class="messagebox-header">
<h3 class="messagebox-title">
{{ title }}
</h3>
<button
v-if="showClose"
class="messagebox-close"
@click="onCancel"
>
×
</button>
</div>
<div class="messagebox-content">
<div
v-if="type"
class="messagebox-icon"
>
<component
:is="iconComponent"
:size="48"
/>
</div>
<div class="messagebox-message">
<p>{{ message }}</p>
</div>
</div>
<div
v-if="center"
class="messagebox-actions center"
>
<button
class="messagebox-btn cancel-btn"
@click="onCancel"
>
{{ cancelButtonText }}
</button>
<button
class="messagebox-btn confirm-btn"
:class="confirmButtonClass"
@click="onConfirm"
>
{{ confirmButtonText }}
</button>
</div>
<div
v-else
class="messagebox-actions"
>
<button
class="messagebox-btn confirm-btn"
:class="confirmButtonClass"
@click="onConfirm"
>
{{ confirmButtonText }}
</button>
<button
class="messagebox-btn cancel-btn"
@click="onCancel"
>
{{ cancelButtonText }}
</button>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { AlertTriangle, CheckCircle, Info, XCircle } from 'lucide-vue-next'
import { computed } from 'vue'
interface Props {
isOpen: boolean
title?: string
message: string
type?: 'info' | 'success' | 'warning' | 'error'
confirmButtonText?: string
cancelButtonText?: string
showClose?: boolean
center?: boolean
}
interface Emits {
(e: 'confirm'): void
(e: 'cancel'): void
}
const props = withDefaults(defineProps<Props>(), {
title: 'Confirm',
confirmButtonText: 'Confirm',
cancelButtonText: 'Cancel',
showClose: true,
center: false,
type: undefined
})
const emit = defineEmits<Emits>()
const iconComponent = computed(() => {
switch (props.type) {
case 'warning':
return AlertTriangle
case 'info':
return Info
case 'success':
return CheckCircle
case 'error':
return XCircle
default:
return Info
}
})
const confirmButtonClass = computed(() => {
switch (props.type) {
case 'warning':
case 'error':
return 'danger'
case 'success':
return 'success'
default:
return 'primary'
}
})
const onConfirm = () => {
emit('confirm')
}
const onCancel = () => {
emit('cancel')
}
</script>
<script lang="ts">
export default {
name: 'ConfirmMessageBox'
}
</script>
<style scoped>
.messagebox-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 2000;
}
.messagebox-container {
background: white;
border-radius: 0.75rem;
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
max-width: 32rem;
width: 90%;
max-height: 80vh;
overflow: hidden;
}
:root.dark .messagebox-container,
:root.auto.dark .messagebox-container {
background: rgb(31 41 55);
border: 1px solid rgb(55 65 81);
}
.messagebox-header {
padding: 1.5rem 1.5rem 0 1.5rem;
display: flex;
justify-content: space-between;
align-items: center;
}
.messagebox-title {
font-size: 1.25rem;
font-weight: 600;
color: rgb(17 24 39);
margin: 0;
}
:root.dark .messagebox-title,
:root.auto.dark .messagebox-title {
color: rgb(243 244 246);
}
.messagebox-close {
background: none;
border: none;
font-size: 1.5rem;
color: rgb(107 114 128);
cursor: pointer;
padding: 0;
width: 24px;
height: 24px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 0.25rem;
}
.messagebox-close:hover {
background: rgb(243 244 246);
color: rgb(17 24 39);
}
:root.dark .messagebox-close,
:root.auto.dark .messagebox-close {
color: rgb(156 163 175);
}
:root.dark .messagebox-close:hover,
:root.auto.dark .messagebox-close:hover {
background: rgb(55 65 81);
color: rgb(243 244 246);
}
.messagebox-content {
padding: 1rem 1.5rem;
display: flex;
gap: 1rem;
align-items: flex-start;
}
.messagebox-icon {
flex-shrink: 0;
color: rgb(107 114 128);
}
.messagebox-icon svg[data-lucide="alert-triangle"] {
color: rgb(245 158 11);
}
.messagebox-icon svg[data-lucide="info"] {
color: rgb(59 130 246);
}
.messagebox-icon svg[data-lucide="check-circle"] {
color: rgb(34 197 94);
}
.messagebox-icon svg[data-lucide="x-circle"] {
color: rgb(239 68 68);
}
.messagebox-message {
flex: 1;
}
.messagebox-message p {
color: rgb(107 114 128);
line-height: 1.6;
margin: 0;
}
:root.dark .messagebox-message p,
:root.auto.dark .messagebox-message p {
color: rgb(156 163 175);
}
.messagebox-actions {
display: flex;
gap: 0.75rem;
padding: 0 1.5rem 1.5rem 1.5rem;
justify-content: flex-end;
}
.messagebox-actions.center {
justify-content: center;
}
.messagebox-btn {
padding: 0.5rem 1rem;
border-radius: 0.375rem;
font-size: 0.875rem;
font-weight: 500;
border: none;
cursor: pointer;
min-width: 4rem;
}
.cancel-btn {
background: rgb(243 244 246);
color: rgb(75 85 99);
border: 1px solid rgb(209 213 219);
}
.cancel-btn:hover {
background: rgb(229 231 235);
}
:root.dark .cancel-btn,
:root.auto.dark .cancel-btn {
background: rgb(55 65 81);
color: rgb(209 213 219);
border-color: rgb(75 85 99);
}
:root.dark .cancel-btn:hover,
:root.auto.dark .cancel-btn:hover {
background: rgb(75 85 99);
}
.confirm-btn.primary {
background: rgb(59 130 246);
color: white;
}
.confirm-btn.primary:hover {
background: rgb(37 99 235);
}
.confirm-btn.danger {
background: rgb(239 68 68);
color: white;
}
.confirm-btn.danger:hover {
background: rgb(220 38 38);
}
.confirm-btn.success {
background: rgb(34 197 94);
color: white;
}
.confirm-btn.success:hover {
background: rgb(22 163 74);
}
</style>

View File

@@ -0,0 +1,260 @@
<template>
<Teleport to="body">
<div class="message-container">
<TransitionGroup
name="message"
tag="div"
>
<div
v-for="message in messages"
:key="message.id"
class="message-toast"
:class="getMessageClass(message.type)"
>
<div class="message-icon">
<component
:is="getIconComponent(message.type)"
:size="20"
/>
</div>
<div class="message-content">
{{ message.message }}
</div>
<button
v-if="message.showClose"
class="message-close"
@click="removeMessage(message.id)"
>
<X :size="16" />
</button>
</div>
</TransitionGroup>
</div>
</Teleport>
</template>
<script setup lang="ts">
import { AlertTriangle, CheckCircle, Info, X, XCircle } from 'lucide-vue-next'
import { reactive } from 'vue'
export interface MessageOptions {
message: string
type?: 'success' | 'warning' | 'info' | 'error'
duration?: number
showClose?: boolean
}
interface MessageItem extends MessageOptions {
id: string
timer?: ReturnType<typeof setTimeout>
}
const messages = reactive<MessageItem[]>([])
const getIconComponent = (type: MessageOptions['type']) => {
switch (type) {
case 'success':
return CheckCircle
case 'warning':
return AlertTriangle
case 'error':
return XCircle
default:
return Info
}
}
const getMessageClass = (type: MessageOptions['type']) => {
return `message-${type || 'info'}`
}
const removeMessage = (id: string) => {
const index = messages.findIndex(msg => msg.id === id)
if (index > -1) {
const message = messages[index]
if (message.timer) {
clearTimeout(message.timer)
}
messages.splice(index, 1)
}
}
const addMessage = (options: MessageOptions) => {
const id = `message-${Date.now()}-${Math.random()}`
const duration = options.duration ?? 3000
const showClose = options.showClose ?? true
const message: MessageItem = {
id,
...options,
showClose
}
if (duration > 0) {
message.timer = setTimeout(() => {
removeMessage(id)
}, duration)
}
messages.push(message)
return id
}
// Expose methods for external use
const success = (message: string, options?: Partial<MessageOptions>) => {
return addMessage({ message, type: 'success', ...options })
}
const error = (message: string, options?: Partial<MessageOptions>) => {
return addMessage({ message, type: 'error', ...options })
}
const warning = (message: string, options?: Partial<MessageOptions>) => {
return addMessage({ message, type: 'warning', ...options })
}
const info = (message: string, options?: Partial<MessageOptions>) => {
return addMessage({ message, type: 'info', ...options })
}
defineExpose({
success,
error,
warning,
info,
addMessage,
removeMessage
})
</script>
<script lang="ts">
export default {
name: 'MessageToast'
}
</script>
<style scoped>
.message-container {
position: fixed;
top: 34px;
right: 20px;
z-index: 3000;
pointer-events: none;
}
.message-toast {
display: flex;
align-items: center;
gap: 0.75rem;
padding: 0.75rem 1rem;
margin-bottom: 0.5rem;
border-radius: 0.5rem;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
max-width: 24rem;
pointer-events: all;
background: white;
border: 1px solid rgb(229 231 235);
}
:root.dark .message-toast,
:root.auto.dark .message-toast {
background: rgb(31 41 55);
border-color: rgb(55 65 81);
}
.message-info {
border-left: 4px solid rgb(59 130 246);
}
.message-info .message-icon {
color: rgb(59 130 246);
}
.message-success {
border-left: 4px solid rgb(34 197 94);
}
.message-success .message-icon {
color: rgb(34 197 94);
}
.message-warning {
border-left: 4px solid rgb(245 158 11);
}
.message-warning .message-icon {
color: rgb(245 158 11);
}
.message-error {
border-left: 4px solid rgb(239 68 68);
}
.message-error .message-icon {
color: rgb(239 68 68);
}
.message-icon {
flex-shrink: 0;
}
.message-content {
flex: 1;
color: rgb(75 85 99);
font-size: 0.875rem;
line-height: 1.25rem;
}
:root.dark .message-content,
:root.auto.dark .message-content {
color: rgb(209 213 219);
}
.message-close {
background: none;
border: none;
color: rgb(107 114 128);
cursor: pointer;
padding: 0.25rem;
border-radius: 0.25rem;
display: flex;
align-items: center;
justify-content: center;
}
.message-close:hover {
background: rgb(243 244 246);
color: rgb(75 85 99);
}
:root.dark .message-close,
:root.auto.dark .message-close {
color: rgb(156 163 175);
}
:root.dark .message-close:hover,
:root.auto.dark .message-close:hover {
background: rgb(55 65 81);
color: rgb(209 213 219);
}
/* Transition animations */
.message-enter-active,
.message-leave-active {
transition: all 0.3s ease;
}
.message-enter-from {
opacity: 0;
transform: translateX(100%);
}
.message-leave-to {
opacity: 0;
transform: translateX(100%);
}
.message-move {
transition: transform 0.3s ease;
}
</style>

View File

@@ -3,7 +3,7 @@ import { Monitor, Moon, Sun } from 'lucide-vue-next'
import { computed } from 'vue'
import { useI18n } from 'vue-i18n'
import { useAppStore } from '@/hooks/appStore'
import { useAppStore } from '@/hooks/useAppStore'
const { t } = useI18n()
const appStore = useAppStore()

View File

@@ -78,7 +78,7 @@ import type { IpcRendererEvent } from 'electron'
import { MinusIcon, PinIcon, ShrinkIcon, XIcon } from 'lucide-vue-next'
import { onBeforeMount, onBeforeUnmount, ref } from 'vue'
import { IRPCActionType } from '#/types/enum'
import { IRPCActionType } from '@/utils/enum'
const isShowprogress = ref(false)
const progress = ref(0)

View File

@@ -0,0 +1,101 @@
<template>
<div>
<!-- MessageToast component -->
<MessageToast ref="messageRef" />
<!-- ConfirmMessageBox component -->
<ConfirmMessageBox
:is-open="confirmVisible"
:title="confirmOptions.title"
:message="confirmOptions.message"
:type="confirmOptions.type"
:confirm-button-text="confirmOptions.confirmButtonText"
:cancel-button-text="confirmOptions.cancelButtonText"
:show-close="confirmOptions.showClose"
:center="confirmOptions.center"
@confirm="handleConfirm"
@cancel="handleCancel"
/>
</div>
</template>
<script setup lang="ts">
import { onMounted, reactive, ref } from 'vue'
import useConfirm, { type ConfirmOptions } from '@/hooks/useConfirm'
import useMessage from '@/hooks/useMessage'
import ConfirmMessageBox from './ConfirmMessageBox.vue'
import MessageToast from './MessageToast.vue'
const messageRef = ref<InstanceType<typeof MessageToast> | null>(null)
const confirmVisible = ref(false)
const confirmOptions = reactive<ConfirmOptions>({
message: '',
title: 'Confirm',
type: 'info',
confirmButtonText: 'Confirm',
cancelButtonText: 'Cancel',
showClose: true,
center: false
})
let confirmResolve: ((value: boolean) => void) | null = null
const handleConfirm = () => {
confirmVisible.value = false
if (confirmResolve) {
confirmResolve(true)
confirmResolve = null
}
}
const handleCancel = () => {
confirmVisible.value = false
if (confirmResolve) {
confirmResolve(false)
confirmResolve = null
}
}
const showConfirm = (options: ConfirmOptions): Promise<boolean> => {
return new Promise((resolve) => {
Object.assign(confirmOptions, {
title: 'Confirm',
type: 'info',
confirmButtonText: 'Confirm',
cancelButtonText: 'Cancel',
showClose: true,
center: false,
...options
})
confirmResolve = resolve
confirmVisible.value = true
})
}
onMounted(() => {
// Initialize message service
const { setMessageService } = useMessage()
if (messageRef.value) {
setMessageService({
success: messageRef.value.success,
error: messageRef.value.error,
warning: messageRef.value.warning,
info: messageRef.value.info
})
}
// Initialize confirm service
const { setConfirmService } = useConfirm()
setConfirmService({
confirm: showConfirm
})
})
</script>
<script lang="ts">
export default {
name: 'UIServiceProvider'
}
</script>