🔨 Refactor(custom): refactor mini toolbox shorkey page

This commit is contained in:
Kuingsmile
2026-01-23 10:55:29 +08:00
parent 7a7fb20fb6
commit 60a7e2a846
13 changed files with 295 additions and 1485 deletions

View File

@@ -1,57 +0,0 @@
<template>
<div class="toolbox-handler">
<button class="handler-button" @click="() => props.handler(value)">
{{ props.handlerText }}
</button>
</div>
</template>
<script lang="ts" setup>
interface IProps {
status: string
value: any
handlerText: string
handler: (value: any) => void | Promise<void>
}
const props = defineProps<IProps>()
</script>
<script lang="ts">
export default {
name: 'ToolboxHandler',
}
</script>
<style lang="stylus">
.toolbox-handler {
margin-top: 0.75rem;
}
.handler-button {
display: inline-flex;
align-items: center;
gap: 0.5rem;
padding: 0.5rem 1rem;
background: var(--color-accent);
color: white;
border: none;
border-radius: var(--radius-md);
font-size: 0.75rem;
font-weight: 500;
cursor: pointer;
transition: var(--transition-fast);
font-family: inherit;
text-decoration: none;
}
.handler-button:hover {
background: var(--color-accent-hover);
transform: translateY(-1px);
box-shadow: var(--shadow-sm);
}
.handler-button:active {
transform: translateY(0);
}
</style>

View File

@@ -0,0 +1,23 @@
<template>
<div class="mt-0">
<CustomButton type="primary" :text="props.handlerText" @click="() => props.handler(props.value)" />
</div>
</template>
<script lang="ts" setup>
import CustomButton from '@/components/common/CustomButton.vue'
interface IProps {
status: string
value: any
handlerText: string
handler: (value: any) => void | Promise<void>
}
const props = defineProps<IProps>()
</script>
<script lang="ts">
export default {
name: 'ToolboxHandler',
}
</script>

View File

@@ -1,5 +1,5 @@
<template>
<component :is="icon" class="toolbox-status-icon" :style="{ color }" />
<component :is="icon" class="flex h-[20px] w-[20px] items-center justify-center rounded-full" :style="{ color }" />
</template>
<script lang="ts" setup>
@@ -43,14 +43,3 @@ export default {
name: 'ToolboxStatusIcon',
}
</script>
<style lang="stylus">
.toolbox-status-icon {
display: flex;
align-items: center;
justify-content: center;
width: 20px;
height: 20px;
border-radius: var(--radius-full);
transition: var(--transition-fast);
}
</style>

View File

@@ -4,15 +4,15 @@
@layer base {
body {
@apply m-0 h-full w-full overflow-hidden bg-bg p-0 font-[inherit] text-main;
@apply m-0 h-full w-full overflow-hidden bg-transparent p-0 font-[inherit] text-main;
}
html {
@apply m-0 h-full p-0 w-full;
@apply m-0 h-full p-0 w-full bg-transparent;
}
#app {
@apply h-full w-full;
@apply h-full w-full bg-transparent;
}
:focus {

View File

@@ -1,11 +1,17 @@
<template>
<div id="mini-page">
<div
id="mini-page"
class="relative box-border h-screen w-screen cursor-pointer overflow-hidden border-4 border-white bg-accent bg-center bg-no-repeat text-center text-[40px] leading-[100vh]"
:class="[osGlobal === 'linux' ? 'rounded-none bg-size-[100vh_100vw]' : 'rounded-full bg-size-[90vh_90vw]']"
>
<div
id="upload-area"
class="h-full w-full transition-all duration-200 ease-in-out"
:class="{
'is-dragover': dragover,
uploading: isShowingProgress,
linux: osGlobal === 'linux',
'bg-[rgba(0,0,0,0.3)]': dragover,
'bg-[linear-gradient(to_top,#409EFF_50%,#fff_51%)] bg-size-[200%]': isShowingProgress,
'rounded-none': osGlobal === 'linux',
'rounded-full': osGlobal !== 'linux',
}"
:style="{ backgroundPosition: '0 ' + progress + '%' }"
@drop.prevent="onDrop"
@@ -15,12 +21,13 @@
<img
v-if="!dragover && !isShowingProgress"
:src="logoPath ? logoPath : './squareLogo.png'"
style="border-radius: var(--radius-round); width: 100%; height: 100%"
class="block h-full w-full [image-rendering:-webkit-optimize-contrast]"
:class="osGlobal === 'linux' ? 'rounded-none' : 'rounded-full'"
draggable="false"
@dragstart.prevent
/>
<div id="upload-dragger" @dblclick="openUploadWindow">
<input id="file-uploader" type="file" multiple @change="onChange" />
<div id="upload-dragger" class="h-full" @dblclick="openUploadWindow">
<input id="file-uploader" type="file" class="hidden" multiple @change="onChange" />
</div>
</div>
</div>
@@ -200,51 +207,3 @@ export default {
name: 'MiniPage',
}
</script>
<style lang="stylus">
html, body, #app
background: transparent
#mini-page
background: #409EFF;
overflow: hidden;
color #FFF
height 100vh
width 100vw
border-radius 50%
text-align center
line-height 100vh
font-size 40px
background-size 90vh 90vw
background-position center center
background-repeat no-repeat
position relative
border 4px solid #fff
box-sizing border-box
cursor pointer
&.linux
border-radius 0
background-size 100vh 100vw
#upload-area
height 100%
width 100%
border-radius 50%
transition all .2s ease-in-out
&.linux
border-radius 0
&.uploading
background: linear-gradient(to top, #409EFF 50%, #fff 51%)
background-size 200%
#upload-dragger
height 100%
&.is-dragover
background rgba(0,0,0,0.3)
#file-uploader
display none
#mini-page img
width 100%
height 100%
border-radius 50%
display block
image-rendering: -webkit-optimize-contrast
</style>

View File

@@ -1,39 +1,46 @@
<template>
<div class="rename-container">
<div class="rename-card">
<form @submit.prevent="confirmName">
<div class="form-content">
<div class="form-group">
<div class="input-wrapper">
<div class="flex h-full w-full items-center justify-center bg-bg-secondary p-2">
<div
class="flex h-full w-full flex-1 items-center overflow-hidden rounded-md border border-border bg-bg-tertiary shadow-md"
>
<form class="flex-1 p-4" @submit.prevent="confirmName">
<div class="p-4">
<div class="relative flex items-center">
<input
ref="fileNameInput"
v-model="form.fileName"
type="text"
class="form-input"
class="box-border w-full rounded-md border border-border px-4 py-3 text-sm text-main focus:border-accent focus:outline-none [.input-error]:border-danger"
:class="{ 'input-error': validationError }"
:placeholder="t('pages.rename.placeholder')"
autofocus
@keyup.enter="confirmName"
@input="clearValidationError"
/>
<button v-if="form.fileName" type="button" class="input-clear" @click="clearFileName">
<button
v-if="form.fileName"
type="button"
class="absolute top-1/2 -right-7 flex h-[24px] w-[24px] -translate-y-1/2 cursor-pointer items-center justify-center rounded-full border-none bg-danger/10 text-secondary hover:bg-danger/20 hover:text-white"
@click="clearFileName"
>
<XIcon :size="16" />
</button>
</div>
<div v-if="validationError" class="validation-error">
<div v-if="validationError" class="mt-2 flex justify-end gap-2 text-sm font-medium text-danger">
{{ validationError }}
</div>
</div>
</div>
<!-- Actions -->
<div class="form-actions">
<button type="button" class="btn btn-secondary" @click="cancel">
{{ $t('common.cancel') }}
</button>
<button type="submit" class="btn btn-primary" :disabled="!form.fileName.trim()">
{{ $t('common.confirm') }}
</button>
<div class="flex flex-col items-center justify-center gap-3">
<CustomButton class="w-[80%]" type="secondary" :text="t('common.cancel')" @click="cancel" />
<CustomButton
class="w-[80%]"
type="primary"
:text="t('common.confirm')"
:disabled="!form.fileName.trim()"
@click="confirmName"
/>
</div>
</form>
</div>
@@ -45,6 +52,7 @@ import { XIcon } from 'lucide-vue-next'
import { nextTick, onBeforeMount, onBeforeUnmount, reactive, ref, useTemplateRef } from 'vue'
import { useI18n } from 'vue-i18n'
import CustomButton from '@/components/common/CustomButton.vue'
import { GET_RENAME_FILE_NAME, RENAME_FILE_NAME } from '@/utils/constant'
const { t } = useI18n()
@@ -112,231 +120,9 @@ onBeforeUnmount(() => {
window.electron.ipcRendererRemoveAllListeners(RENAME_FILE_NAME)
})
</script>
<script lang="ts">
export default {
name: 'RenamePage',
}
</script>
<style scoped>
.rename-container {
display: flex;
justify-content: center;
align-items: center;
padding: 2rem;
min-height: 100vh;
background: var(--color-background-secondary);
}
.rename-card {
overflow: hidden;
border: 1px solid var(--color-border);
border-radius: var(--radius-lg);
width: 100%;
max-width: 500px;
background: var(--color-background-primary);
box-shadow:
0 20px 25px -5px rgb(0 0 0 / 10%),
0 10px 10px -5px rgb(0 0 0 / 4%);
}
/* Form */
.form-content {
padding: 2rem;
}
.form-group {
margin-bottom: 1.5rem;
}
.form-group:last-child {
margin-bottom: 0;
}
.form-label {
display: block;
margin-bottom: 0.75rem;
font-size: 0.875rem;
font-weight: 500;
color: var(--color-text-primary);
}
.input-wrapper {
position: relative;
display: flex;
align-items: center;
}
.form-input {
border: 1px solid var(--color-border);
border-radius: var(--radius-md);
padding: 0.875rem 1rem;
padding-right: 2.5rem;
width: 100%;
font-size: 0.875rem;
color: var(--color-text-primary);
background: var(--color-background-primary);
transition: all 0.2s ease;
box-sizing: border-box;
}
.form-input:focus {
border-color: var(--color-accent);
outline: none;
box-shadow: 0 0 0 2px color-mix(in srgb, var(--color-accent-hover), transparent 60%);
}
.form-input.input-error {
border-color: #f56c6c;
box-shadow: 0 0 0 2px rgb(245 108 108 / 20%);
}
.input-clear {
position: absolute;
top: 50%;
right: 0.75rem;
display: flex;
justify-content: center;
align-items: center;
border: none;
border-radius: 4px;
width: 24px;
height: 24px;
color: var(--color-text-secondary);
background: var(--color-background-tertiary);
transition: all 0.2s ease;
transform: translateY(-50%);
cursor: pointer;
}
.input-clear:hover {
color: var(--color-text-primary);
background: var(--color-background-secondary);
}
.validation-error {
display: flex;
align-items: center;
margin-top: 0.5rem;
font-size: 0.75rem;
color: #f56c6c;
gap: 0.25rem;
}
/* Actions */
.form-actions {
display: flex;
justify-content: flex-end;
gap: 0.75rem;
padding: 1rem 2rem 2rem;
background: var(--color-background-tertiary);
}
/* Buttons */
.btn {
display: inline-flex;
justify-content: center;
align-items: center;
border: none;
border-radius: var(--radius-md);
padding: 0.75rem 1.5rem;
min-width: fit-content;
font-size: 0.875rem;
font-weight: 500;
transition: all 0.2s ease;
gap: 0.5rem;
cursor: pointer;
}
.btn:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.btn:hover:not(:disabled) {
transform: translateY(-1px);
box-shadow: 0 4px 8px rgb(0 0 0 / 15%);
}
.btn-primary {
color: white;
background: #409eff;
}
.btn-primary:hover:not(:disabled) {
background: #66b1ff;
}
.btn-secondary {
border: 1px solid var(--color-border);
color: var(--color-text-primary);
background: var(--color-background-primary);
}
.btn-secondary:hover:not(:disabled) {
border-color: var(--color-accent);
background: var(--color-background-secondary);
}
/* Responsive Design */
@media (width <= 768px) {
.rename-container {
padding: 1rem;
}
.rename-card {
max-width: none;
}
.form-actions {
padding: 1rem 1.5rem 1.5rem;
flex-direction: column-reverse;
}
.btn {
justify-content: center;
width: 100%;
}
}
@media (width <= 480px) {
.rename-container {
padding: 0.75rem;
}
.form-actions {
padding: 1rem;
}
}
/* Focus styles for accessibility */
.btn:focus-visible,
.input-clear:focus-visible {
outline: 2px solid var(--color-accent);
outline-offset: 2px;
}
.form-input:focus-visible {
outline: 2px solid var(--color-accent);
outline-offset: 2px;
}
/* Animation for error state */
@keyframes shake {
0%,
100% {
transform: translateX(0);
}
25% {
transform: translateX(-4px);
}
75% {
transform: translateX(4px);
}
}
.input-error {
animation: shake 0.3s ease-in-out;
}
</style>

View File

@@ -1,61 +1,82 @@
<template>
<div class="shortkey-container">
<div class="relative flex h-full w-full items-center justify-center">
<!-- Header -->
<div class="shortkey-header">
<div class="header-content">
<KeyboardIcon :size="24" class="header-icon" />
<div class="relative z-1 flex h-full w-full flex-col items-center justify-start gap-4 rounded-xl border-none p-4">
<div
class="flex w-full items-center justify-between gap-4 overflow-visible rounded-2xl border border-border-secondary p-4 shadow-md max-md:items-stretch"
>
<div class="flex flex-1 flex-wrap items-center gap-4 p-1">
<KeyboardIcon :size="24" class="text-accent" />
<div>
<h1>{{ t('pages.shortKey.title') }}</h1>
<p>{{ ' ' }}</p>
<h1 class="m-0 text-2xl font-semibold tracking-tight text-main">{{ t('pages.shortKey.title') }}</h1>
</div>
</div>
</div>
<!-- Shortcuts Table Card -->
<div class="shortkey-card">
<div class="table-container">
<table class="shortkey-table">
<thead>
<div
class="relative flex h-full w-full flex-1 items-center justify-center overflow-hidden rounded-2xl border border-border-secondary p-1 shadow-md"
>
<div class="h-full w-full overflow-hidden rounded-xl shadow-sm">
<table class="w-full table-auto bg-white text-left text-sm text-main">
<thead class="bg-bg-secondary text-sm text-main uppercase">
<tr>
<th>{{ t('pages.shortKey.name') }}</th>
<th>{{ t('pages.shortKey.bind') }}</th>
<th>{{ t('pages.shortKey.status') }}</th>
<th>{{ t('pages.shortKey.source') }}</th>
<th>{{ t('pages.shortKey.handle') }}</th>
<th class="px-6 py-4 font-semibold">{{ t('pages.shortKey.name') }}</th>
<th class="px-6 py-4 font-semibold">{{ t('pages.shortKey.bind') }}</th>
<th class="px-6 py-4 font-semibold">{{ t('pages.shortKey.status') }}</th>
<th class="px-6 py-4 font-semibold">{{ t('pages.shortKey.source') }}</th>
<th class="px-6 py-4 font-semibold">{{ t('pages.shortKey.handle') }}</th>
</tr>
</thead>
<tbody>
<tr v-for="(item, index) in list" :key="item.name" class="table-row">
<td class="name-cell">
<div class="shortcut-name">
<tbody class="divide-y divide-gray-100">
<tr
v-for="(item, index) in list"
:key="item.name"
class="rounded-md transition-colors odd:bg-white even:bg-gray-50/30 hover:bg-accent/10"
>
<td class="px-6 py-4">
<div class="text-sm font-semibold text-secondary">
{{ item.label ? item.label : item.name }}
</div>
</td>
<td class="key-cell">
<div class="key-binding">
<kbd v-if="item.key" class="key-display">{{ item.key }}</kbd>
<span v-else class="no-binding">{{ t('pages.shortKey.noBinding') }}</span>
<td class="px-6 py-4">
<div class="flex gap-1">
<kbd
v-if="item.key"
class="rounded-md border border-b-4 border-gray-300 bg-gray-100 px-2 py-1 text-xs font-semibold text-main shadow-sm"
>{{ item.key }}</kbd
>
<span v-else class="text-xs text-secondary italic">{{ t('pages.shortKey.noBinding') }}</span>
</div>
</td>
<td class="status-cell">
<span class="status-badge" :class="{ 'status-enabled': item.enable, 'status-disabled': !item.enable }">
<td class="px-6 py-4">
<span
:class="[
'inline-flex items-center rounded-md p-2 text-sm font-semibold text-secondary',
item.enable ? 'bg-success/10' : 'bg-danger/10',
]"
>
{{ item.enable ? t('pages.shortKey.enabled') : t('pages.shortKey.disabled') }}
</span>
</td>
<td class="source-cell">
<span class="source-name">{{ calcOriginShowName(item.from || '') }}</span>
<td class="px-6 py-4 text-secondary">
<span class="rounded-md bg-accent/10 p-2 font-bold text-main">{{
calcOriginShowName(item.from || '')
}}</span>
</td>
<td class="actions-cell">
<div class="action-buttons">
<td class="px-6 py-4 text-center">
<div class="flex items-center justify-start gap-3">
<button
class="btn btn-sm"
:class="item.enable ? 'btn-danger' : 'btn-success'"
:class="item.enable ? 'text-danger' : 'text-success'"
class="w-[80px] rounded-md border border-border p-2 text-center text-sm font-semibold transition-colors hover:bg-accent/10"
@click="toggleEnable(item)"
>
{{ item.enable ? t('pages.shortKey.disable') : t('pages.shortKey.enable') }}
</button>
<button class="btn btn-sm btn-secondary" @click="openKeyBindingDialog(item, index)">
<Edit :size="14" />
<button
class="w-[80px] rounded-md border border-border p-2 text-center text-sm font-semibold text-secondary transition-colors hover:bg-accent/10"
@click="openKeyBindingDialog(item, index)"
>
{{ t('pages.shortKey.edit') }}
</button>
</div>
@@ -65,53 +86,45 @@
</table>
</div>
</div>
</div>
<!-- Key Binding Modal -->
<transition name="modal">
<div v-if="keyBindingVisible" class="modal-overlay" @click.self="cancelKeyBinding">
<div class="modal-content">
<div class="modal-header">
<h3 class="modal-title">
{{ t('pages.shortKey.changeUpload') }}
</h3>
<button class="modal-close" @click="cancelKeyBinding">
<XIcon :size="20" />
</button>
</div>
<div class="modal-body">
<div class="form-group">
<label>{{ t('pages.shortKey.keyBinding') }}</label>
<CustomModal
v-if="keyBindingVisible"
v-model:visible="keyBindingVisible"
:title="t('pages.shortKey.changeUpload')"
width="600px"
height="auto"
>
<div class="p-4">
<label class="mb-4 block text-sm font-semibold text-secondary">{{ t('pages.shortKey.keyBinding') }}</label>
<input
v-model="shortKey"
class="form-input key-input"
class="box-border w-full rounded-md border border-border bg-bg-secondary p-3 text-center font-mono text-sm tracking-wider text-main focus:border-accent focus:outline-none"
:placeholder="t('pages.shortKey.pressKeys')"
readonly
@keydown.prevent="keyDetect($event as KeyboardEvent)"
/>
<div class="input-hint">
<div class="mt-2 text-center text-sm text-secondary">
{{ t('pages.shortKey.pressHint') }}
</div>
</div>
</div>
<div class="modal-footer">
<button class="btn btn-secondary" @click="cancelKeyBinding">
{{ $t('common.cancel') }}
</button>
<button class="btn btn-primary" @click="confirmKeyBinding">
{{ $t('common.confirm') }}
</button>
</div>
</div>
</div>
<template #footer>
<CustomButton type="secondary" :text="t('common.cancel')" @click="cancelKeyBinding" />
<CustomButton type="primary" :text="t('common.confirm')" @click="confirmKeyBinding" />
</template>
</CustomModal>
</transition>
</div>
</template>
<script lang="ts" setup>
import { Edit, KeyboardIcon, XIcon } from 'lucide-vue-next'
import { KeyboardIcon } from 'lucide-vue-next'
import { onBeforeMount, onBeforeUnmount, ref, watch } from 'vue'
import { useI18n } from 'vue-i18n'
import CustomButton from '@/components/common/CustomButton.vue'
import CustomModal from '@/components/common/CustomModal.vue'
import { getRawData } from '@/utils/common'
import { configPaths } from '@/utils/configPaths'
import { getConfig } from '@/utils/dataSender'
@@ -191,5 +204,3 @@ export default {
name: 'ShortkeyPage',
}
</script>
<style scoped src="./css/ShortKey.css"></style>

View File

@@ -1,42 +1,46 @@
<template>
<div class="toolbox-container">
<div class="relative no-scrollbar flex h-full w-full items-center justify-center bg-bg-tertiary">
<div
class="relative z-1 no-scrollbar flex h-full w-full flex-col items-center justify-start gap-6 overflow-auto rounded-xl border-none p-8 shadow-sm"
>
<!-- Header Card -->
<div class="toolbox-card header-card">
<div class="card-header">
<div class="header-content">
<img class="header-logo" :src="defaultLogo" alt="Toolbox Logo" />
<div class="header-text">
<h1 class="header-title">
<div
class="flex w-full items-center justify-between gap-4 overflow-visible rounded-2xl border border-border-secondary px-6 py-2 shadow-md max-md:items-stretch max-md:p-5"
>
<div class="flex flex-1 flex-wrap items-center gap-4 p-1">
<div class="flex flex-1 items-center gap-4">
<img class="h-[48px] w-[48px] rounded-full object-cover" :src="defaultLogo" alt="Toolbox Logo" />
<div class="flex flex-col gap-1">
<h1 class="m-0 text-2xl font-semibold text-main">
{{ t('pages.toolbox.title') }}
</h1>
<p class="header-subtitle">
<p class="m-0 text-sm text-secondary">
{{ t('pages.toolbox.description') }}
</p>
</div>
</div>
<div class="header-actions">
<div class="flex flex-wrap items-center gap-3">
<template v-if="progress !== 100">
<button class="action-button" :class="{ disabled: isLoading }" :disabled="isLoading" @click="handleCheck">
<span>{{ t('pages.toolbox.startScan') }}</span>
</button>
<CustomButton
type="primary"
:text="t('pages.toolbox.startScan')"
:disabled="isLoading"
@click="handleCheck"
/>
</template>
<template v-else-if="isAllSuccess">
<div class="success-tips">
<div class="border border-success/50 bg-bg-secondary px-5 py-3 text-sm font-semibold text-secondary">
{{ t('pages.toolbox.success') }}
</div>
</template>
<template v-else-if="!isAllSuccess">
<template v-if="canFixLength !== 0">
<button class="action-button" @click="handleFix">
<span>{{ t('pages.toolbox.startFix') }}</span>
</button>
<CustomButton type="secondary" :text="t('pages.toolbox.startFix')" @click="handleFix" />
</template>
<template v-else>
<div class="cant-fix-container">
<span class="cant-fix-text">{{ $t('pages.toolbox.autoFixFail') }}</span>
<button class="action-button secondary small" @click="handleCheck">
<span>{{ t('pages.toolbox.reScan') }}</span>
</button>
<div class="flex flex-wrap items-center gap-3">
<span class="text-sm text-secondary">{{ $t('pages.toolbox.autoFixFail') }}</span>
<CustomButton type="secondary" :text="t('pages.toolbox.reScan')" @click="handleCheck" />
</div>
</template>
</template>
@@ -45,47 +49,48 @@
</div>
<!-- Progress Card -->
<div class="toolbox-card progress-card">
<div class="progress-container">
<div class="progress-bar">
<div class="progress-fill" :style="{ width: `${progress}%` }" />
<div class="w-full rounded-md border border-border-secondary shadow-sm">
<div class="flex items-center p-2">
<div class="relative mr-3 h-2 flex-1 overflow-hidden rounded-full bg-surface-elevated">
<div class="absolute top-0 left-0 h-2 rounded-full bg-success" :style="{ width: `${progress}%` }" />
</div>
<span class="progress-text">{{ Math.round(progress) }}%</span>
<span class="text-sm text-secondary">{{ Math.round(progress) }}%</span>
</div>
</div>
<!-- Items Card -->
<div class="toolbox-card items-card">
<div class="items-list">
<div class="w-full flex-1 overflow-hidden rounded-md border border-border-secondary shadow-sm">
<div class="h-full w-full overflow-auto p-4">
<div class="border border-border-secondary shadow-sm">
<div
v-for="(item, key) in fixList"
:key="key"
class="item"
class="border-b border-border-secondary last:border-0 hover:bg-surface-elevated"
:class="{
'item-active': activeTypes.includes(key),
'item-error': item.status === IToolboxItemCheckStatus.ERROR,
'item-success': item.status === IToolboxItemCheckStatus.SUCCESS,
'item-loading': item.status === IToolboxItemCheckStatus.LOADING,
'bg-surface-elevated': activeTypes.includes(key),
'border-l-3 border-danger': item.status === IToolboxItemCheckStatus.ERROR,
'border-l-3 border-success': item.status === IToolboxItemCheckStatus.SUCCESS,
'border-l-3 border-accent': item.status === IToolboxItemCheckStatus.LOADING,
}"
>
<div class="item-header" @click="toggleItem(key)">
<div class="item-title">
<div class="flex cursor-pointer items-center justify-between px-6 py-4" @click="toggleItem(key)">
<div class="flex flex-1 items-center gap-3 text-sm font-semibold text-secondary">
<span>{{ item.title }}</span>
<toolbox-status-icon :status="item.status" />
</div>
<div class="item-chevron">
<div class="flex items-center text-secondary">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<polyline points="6,9 12,15 18,9" />
</svg>
</div>
</div>
<transition name="item-content">
<div v-if="activeTypes.includes(key)" class="item-content">
<div class="item-message">
<div v-if="activeTypes.includes(key)" class="border-t border-border-secondary bg-surface p-2">
<div class="mb-3 text-sm leading-[1.5] text-secondary">
{{ item.msg || '' }}
</div>
<template v-if="item.handler && item.handlerText && item.value">
<div class="item-actions">
<div class="flex justify-start">
<toolbox-handler
:value="item.value"
:status="item.status"
@@ -100,14 +105,17 @@
</div>
</div>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import { computed, onUnmounted, reactive, ref } from 'vue'
import { useI18n } from 'vue-i18n'
import ToolboxHandler from '@/components/ToolboxHandler.vue'
import ToolboxStatusIcon from '@/components/ToolboxStatusIcon.vue'
import CustomButton from '@/components/common/CustomButton.vue'
import ToolboxHandler from '@/components/toolbox/ToolboxHandler.vue'
import ToolboxStatusIcon from '@/components/toolbox/ToolboxStatusIcon.vue'
import useConfirm from '@/hooks/useConfirm'
import { IRPCActionType, IToolboxItemCheckStatus, IToolboxItemType } from '@/utils/enum'
@@ -247,5 +255,3 @@ export default {
name: 'ToolBoxPage',
}
</script>
<style scoped src="./css/ToolboxPage.css"></style>

View File

@@ -142,10 +142,10 @@
<div
v-if="showProgress"
class="flex flex-wrap items-center justify-between gap-4 rounded-2xl border border-border-secondary p-0 shadow-md"
class="flex w-full flex-wrap items-center justify-between gap-4 rounded-2xl border border-border-secondary p-0 shadow-md"
>
<div class="mx-2 my-2 w-full rounded-lg border border-border bg-surface p-4">
<div class="mb-2 h-3 overflow-hidden rounded-lg bg-bg-secondary">
<div class="flex w-full items-center gap-2 rounded-lg border border-border bg-surface p-2">
<div class="h-3 flex-1 overflow-hidden rounded-lg bg-bg-secondary">
<div
class="h-full rounded-lg bg-[linear-gradient(90deg,var(--color-accent)_0%,var(--color-primary)_50%)] duration-fast ease-standard data-[error=true]:bg-danger data-[error=true]:bg-none"
:data-error="showError"

View File

@@ -1,427 +0,0 @@
.shortkey-container {
overflow-y: auto;
padding: 1.5rem;
min-height: 100vh;
color: var(--color-text-primary);
background: var(--color-background-secondary);
scrollbar-width: none;
-ms-overflow-style: none;
}
.shortkey-container::-webkit-scrollbar {
display: none;
}
/* Header */
.shortkey-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 1.5rem;
border: 1px solid var(--color-border);
border-radius: var(--radius-lg);
padding: 1.5rem;
background: var(--color-surface);
box-shadow: 0 2px 8px rgb(0 0 0 / 10%);
}
.header-content {
display: flex;
align-items: center;
gap: 1rem;
}
.header-icon {
color: var(--color-accent);
}
.shortkey-header h1 {
margin: 0;
font-size: 1.5rem;
font-weight: 600;
color: var(--color-text-primary);
}
.shortkey-header p {
margin: 0;
font-size: 0.875rem;
color: var(--color-text-secondary);
}
/* Card */
.shortkey-card {
overflow: hidden;
border: 1px solid var(--color-border);
border-radius: var(--radius-lg);
background: var(--color-background-primary);
box-shadow: 0 2px 8px var(--color-border);
}
/* Table */
.table-container {
overflow-x: auto;
}
.shortkey-table {
width: 100%;
border-collapse: collapse;
background: transparent;
}
.shortkey-table th {
border-bottom: 1px solid var(--color-border);
padding: 1rem;
font-size: 0.875rem;
font-weight: 600;
text-align: left;
color: var(--color-text-primary);
background: var(--color-background-tertiary);
}
.shortkey-table td {
border-bottom: 1px solid var(--color-border-secondary);
padding: 1rem;
vertical-align: middle;
}
.table-row:hover {
background: var(--color-background-tertiary);
}
.table-row:last-child td {
border-bottom: none;
}
/* Table Cells */
.name-cell {
width: 25%;
font-weight: 500;
color: var(--color-text-primary);
}
.key-cell {
width: 20%;
}
.key-binding {
display: flex;
align-items: center;
}
.key-display {
border: 1px solid var(--color-border);
border-radius: var(--radius-sm);
padding: 0.25rem 0.5rem;
font-size: 0.75rem;
font-family: monospace;
color: var(--color-text-primary);
background: var(--color-background-tertiary);
box-shadow: 0 1px 2px rgb(0 0 0 / 10%);
}
.no-binding {
font-size: 0.875rem;
color: var(--color-text-secondary);
font-style: italic;
}
.status-cell {
width: 15%;
}
.status-badge {
display: inline-block;
border-radius: var(--radius-lg);
padding: 0.25rem 0.75rem;
font-size: 0.75rem;
font-weight: 500;
}
.status-enabled {
border: 1px solid rgb(103 194 58 / 20%);
color: var(--color-success);
background: rgb(103 194 58 / 10%);
}
.status-disabled {
border: 1px solid rgb(245 108 108 / 20%);
color: var(--color-danger);
background: rgb(245 108 108 / 10%);
}
.source-cell {
width: 15%;
}
.source-name {
font-size: 0.875rem;
color: var(--color-text-secondary);
}
.actions-cell {
width: 25%;
}
.action-buttons {
display: flex;
gap: 0.5rem;
align-items: center;
}
/* Buttons */
.btn {
display: inline-flex;
justify-content: center;
align-items: center;
border: none;
border-radius: var(--radius-sm);
padding: 0.5rem 0.875rem;
min-width: fit-content;
font-size: 0.75rem;
font-weight: 500;
text-decoration: none;
transition: all 0.2s ease;
gap: 0.375rem;
cursor: pointer;
}
.btn:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.btn:hover:not(:disabled) {
transform: translateY(-1px);
box-shadow: 0 4px 8px rgb(0 0 0 / 15%);
}
.btn-sm {
padding: 0.375rem 0.75rem;
font-size: 0.75rem;
}
.btn-primary {
color: white;
background: var(--color-accent);
}
.btn-primary:hover:not(:disabled) {
background: var(--color-accent-hover);
}
.btn-secondary {
border: 1px solid var(--color-border);
color: var(--color-text-primary);
background: var(--color-background-tertiary);
}
.btn-secondary:hover:not(:disabled) {
border-color: var(--color-accent);
background: var(--color-background-secondary);
}
.btn-success {
color: white;
background: var(--color-success);
}
.btn-success:hover:not(:disabled) {
background: var(--color-success);
}
.btn-danger {
color: white;
background: var(--color-danger);
}
.btn-danger:hover:not(:disabled) {
background: var(--color-danger);
}
/* Modal */
.modal-overlay {
position: fixed;
inset: 0;
z-index: 1000;
display: flex;
justify-content: center;
align-items: center;
padding: 1rem;
background: rgb(0 0 0 / 50%);
}
.modal-content {
overflow: hidden;
border: 1px solid var(--color-border);
border-radius: var(--radius-lg);
width: 100%;
max-width: 500px;
max-height: 90vh;
background: var(--color-background-primary);
box-shadow: 0 20px 25px -5px rgb(0 0 0 / 10%), 0 10px 10px -5px rgb(0 0 0 / 4%);
}
.modal-header {
display: flex;
justify-content: space-between;
align-items: center;
border-bottom: 1px solid var(--color-border-secondary);
padding: 1.5rem;
}
.modal-title {
margin: 0;
font-size: 1.125rem;
font-weight: 600;
color: var(--color-text-primary);
}
.modal-close {
display: flex;
justify-content: center;
align-items: center;
border: none;
border-radius: var(--radius-sm);
width: 32px;
height: 32px;
color: var(--color-text-secondary);
background: var(--color-background-tertiary);
transition: all 0.2s ease;
cursor: pointer;
}
.modal-close:hover {
color: var(--color-text-primary);
background: var(--color-background-secondary);
}
.modal-body {
padding: 1.5rem;
}
.modal-footer {
display: flex;
justify-content: flex-end;
border-top: 1px solid var(--color-border-secondary);
padding: 1rem 1.5rem;
background: var(--color-background-tertiary);
gap: 0.75rem;
}
/* Form Elements */
.form-group {
margin-bottom: 1rem;
}
.form-group 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-primary);
background: var(--color-background-primary);
transition: all 0.2s ease;
box-sizing: border-box;
}
.form-input:focus {
border-color: var(--color-accent);
outline: none;
box-shadow: 0 0 0 2px rgb(64 158 255 / 20%);
}
.key-input {
font-family: monospace;
font-weight: 600;
text-align: center;
letter-spacing: 0.5px;
}
.input-hint {
margin-top: 0.5rem;
font-size: 0.75rem;
text-align: center;
color: var(--color-text-secondary);
}
/* Responsive Design */
@media (width <= 768px) {
.shortkey-container {
padding: 1rem;
}
.shortkey-header {
flex-direction: column;
align-items: flex-start;
gap: 1rem;
}
.table-container {
overflow-x: auto;
}
.shortkey-table th,
.shortkey-table td {
padding: 0.75rem 0.5rem;
}
.action-buttons {
flex-direction: column;
gap: 0.25rem;
}
.btn-sm {
padding: 0.25rem 0.5rem;
font-size: 0.7rem;
}
.modal-content {
margin: 1rem;
}
.modal-header,
.modal-body,
.modal-footer {
padding: 1rem;
}
}
@media (width <= 480px) {
.shortkey-container {
padding: 0.75rem;
}
.shortkey-header h1 {
font-size: 1.25rem;
}
.shortkey-table {
font-size: 0.875rem;
}
.shortkey-table th,
.shortkey-table td {
padding: 0.5rem 0.375rem;
}
}
/* Focus styles for accessibility */
.btn:focus-visible,
.modal-close:focus-visible {
outline: 2px solid var(--color-accent);
outline-offset: 2px;
}
.form-input:focus-visible {
outline: 2px solid var(--color-accent);
outline-offset: 2px;
}

View File

@@ -1,363 +0,0 @@
/* Global scrolling behavior */
html, body {
overflow-x: hidden;
}
/* Container */
.toolbox-container {
display: flex;
overflow-y: auto;
margin: 0;
padding: 1rem;
width: 100%;
min-height: 100vh;
flex-direction: column;
gap: 1.25rem;
box-sizing: border-box;
background: var(--color-background-tertiary);
}
/* Card Base */
.toolbox-card {
overflow: auto;
border: 1px solid var(--color-border-secondary);
border-radius: var(--radius-xl);
background: var(--color-background-tertiary);
box-shadow: var(--shadow-sm);
transition: var(--transition-medium);
}
.toolbox-card:hover {
border-color: var(--color-border);
box-shadow: var(--shadow-md);
}
/* Header Card */
.header-card .card-header {
display: flex;
justify-content: space-between;
align-items: center;
border-bottom: 1px solid var(--color-border-secondary);
padding: 1rem 1.5rem;
flex-wrap: wrap;
gap: 1rem;
}
.header-content {
display: flex;
align-items: center;
gap: 1rem;
flex: 1;
}
.header-logo {
border-radius: var(--radius-lg);
width: 48px;
height: 48px;
object-fit: cover;
}
.header-text {
display: flex;
flex-direction: column;
gap: 0.25rem;
}
.header-title {
margin: 0;
font-size: 1.5rem;
font-weight: 600;
color: var(--color-text-primary);
letter-spacing: -0.025em;
}
.header-subtitle {
margin: 0;
font-size: 0.875rem;
color: var(--color-text-secondary);
}
.header-actions {
display: flex;
align-items: center;
gap: 0.75rem;
flex-wrap: wrap;
}
.action-button {
display: flex;
justify-content: center;
align-items: center;
border: none;
border-radius: var(--radius-lg);
padding: 0.75rem 1.25rem;
min-width: 120px;
font-size: 0.875rem;
font-family: inherit;
font-weight: 500;
color: white;
background: var(--color-accent);
transition: var(--transition-fast);
gap: 0.5rem;
cursor: pointer;
}
.action-button:hover:not(.disabled) {
background: var(--color-accent-hover);
transform: translateY(-1px);
box-shadow: var(--shadow-md);
}
.action-button.disabled {
opacity: 0.6;
cursor: not-allowed;
transform: none;
}
.action-button.secondary {
border: 1px solid var(--color-border);
color: var(--color-text-primary);
background: var(--color-surface-elevated);
}
.action-button.secondary:hover {
border-color: var(--color-accent);
color: var(--color-accent);
background: var(--color-surface);
}
.action-button.small {
padding: 0.5rem 0.75rem;
min-width: auto;
font-size: 0.75rem;
}
.success-tips {
border: 1px solid rgb(103 194 58 / 30%);
border-radius: var(--radius-lg);
padding: 0.75rem 1.25rem;
font-size: 0.875rem;
font-weight: 500;
color: var(--color-success);
background: var(--color-background-secondary);
}
.cant-fix-container {
display: flex;
align-items: center;
gap: 0.75rem;
flex-wrap: wrap;
}
.cant-fix-text {
font-size: 0.875rem;
color: var(--color-text-secondary);
}
/* Progress Card */
.progress-card {
border-radius: var(--radius-lg);
}
.progress-container {
display: flex;
align-items: center;
padding: 1rem 1.5rem;
gap: 1rem;
}
.progress-bar {
position: relative;
overflow: hidden;
border-radius: var(--radius-full);
height: 8px;
background: var(--color-surface-elevated);
flex: 1;
}
.progress-fill {
position: relative;
border-radius: var(--radius-full);
height: 100%;
background: linear-gradient(90deg, var(--color-accent) 0%, var(--color-accent-hover) 100%);
transition: width 0.3s ease-in-out;
}
.progress-fill::after {
position: absolute;
inset: 0;
background: linear-gradient(90deg, transparent 0%, rgb(255 255 255 / 20%) 50%, transparent 100%);
content: '';
animation: shimmer 2s infinite;
}
@keyframes shimmer {
0% { transform: translateX(-100%); }
100% { transform: translateX(100%); }
}
.progress-text {
min-width: 40px;
font-size: 0.875rem;
font-weight: 600;
text-align: right;
color: var(--color-text-primary);
}
/* Items Card */
.items-card {
border-radius: var(--radius-lg);
height: 230px;
}
.card-title {
margin: 0;
font-size: 1.125rem;
font-weight: 600;
color: var(--color-text-primary);
}
.items-list {
padding: 0;
}
.item {
border-bottom: 1px solid var(--color-border-secondary);
transition: var(--transition-fast);
}
.item:last-child {
border-bottom: none;
}
.item:hover {
background: var(--color-surface-elevated);
}
.item-active {
background: var(--color-surface-elevated);
}
.item-error {
border-left: 3px solid var(--color-danger);
}
.item-success {
border-left: 3px solid var(--color-success);
}
.item-loading {
border-left: 3px solid var(--color-accent);
}
.item-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 1rem 1.5rem;
transition: var(--transition-fast);
cursor: pointer;
}
.item-header:hover {
background: rgb(0 0 0 / 2%);
}
.item-title {
display: flex;
align-items: center;
gap: 0.75rem;
flex: 1;
font-size: 0.875rem;
font-weight: 500;
color: var(--color-text-primary);
}
.item-chevron {
display: flex;
align-items: center;
color: var(--color-text-secondary);
transition: var(--transition-fast);
}
.item-active .item-chevron {
transform: rotate(180deg);
color: var(--color-accent);
}
.item-content-enter-active,
.item-content-leave-active {
overflow: hidden;
transition: all 0.3s ease;
}
.item-content-enter-from,
.item-content-leave-to {
max-height: 0;
opacity: 0;
}
.item-content-enter-to,
.item-content-leave-from {
max-height: 200px;
opacity: 1;
}
.item-content {
border-top: 1px solid var(--color-border-secondary);
padding: 0 1.5rem 1rem;
background: var(--color-surface);
}
.item-message {
margin-bottom: 0.75rem;
font-size: 0.875rem;
color: var(--color-text-secondary);
line-height: 1.5;
}
.item-actions {
display: flex;
justify-content: flex-start;
}
/* Responsive Design */
@media (width <= 768px) {
.toolbox-container {
padding: 0.75rem;
gap: 1rem;
}
.header-card .card-header {
flex-direction: column;
align-items: stretch;
gap: 1rem;
}
.header-content {
justify-content: center;
text-align: center;
}
.header-actions {
justify-content: center;
}
.cant-fix-container {
flex-direction: column;
align-items: center;
text-align: center;
}
.progress-container {
padding: 0.75rem 1rem;
}
.item-header {
padding: 0.75rem 1rem;
}
.item-content {
padding: 0 1rem 0.75rem;
}
}

View File

@@ -1,6 +0,0 @@
.advancedAnimation {
backdrop-filter: blur(5px) saturate(180%);
background: rgb(255 255 255 / 20%);
border: 1px solid rgb(255 255 255 / 30%);
box-shadow: 0 8px 32px rgb(0 0 0 / 10%);
}

View File

@@ -1,111 +0,0 @@
/* Modal - Base Styles (Used by ImageProcess Dialog) */
.modal-overlay {
position: fixed;
z-index: 1000;
display: flex;
justify-content: center;
align-items: center;
overflow-y: auto;
padding: 2rem;
background: rgb(0 0 0 / 50%);
inset: 0;
}
.modal-container {
overflow: hidden;
margin: auto;
border: 1px solid var(--color-border-secondary);
border-radius: var(--radius-2xl);
width: 90vw;
max-width: 90vw;
height: 85vh;
max-height: 85vh;
background: var(--color-background-tertiary);
box-shadow: var(--shadow-xl);
}
.modal-header {
display: flex;
justify-content: space-between;
align-items: center;
border-bottom: 1px solid var(--color-border-secondary);
padding: 1rem 1.25rem;
background: var(--color-background-tertiary);
}
.modal-title {
margin: 0;
font-size: 1.25rem;
font-weight: 600;
color: var(--color-text-primary);
}
.modal-subtitle {
margin: 0.25rem 0 0;
font-size: 1.25rem;
font-weight: 600;
color: var(--color-text-secondary);
}
.modal-close {
display: flex;
justify-content: center;
align-items: center;
border: 1px solid var(--color-border);
border-radius: var(--radius-round);
width: 32px;
height: 32px;
color: var(--color-text-secondary);
background: var(--color-surface-elevated);
cursor: pointer;
transition: all var(--transition-fast);
}
.modal-close:hover {
border-color: var(--color-danger);
color: white;
background: var(--color-danger);
transform: scale(1.05);
}
.modal-content {
overflow-y: auto;
max-height: calc(90vh - 90px);
scrollbar-width: none;
-ms-overflow-style: none;
}
.modal-content::-webkit-scrollbar {
width: 0.5rem;
}
.modal-content::-webkit-scrollbar-track {
background: var(--color-surface);
}
.modal-content::-webkit-scrollbar-thumb {
background: var(--color-border-secondary);
border-radius: var(--radius-full);
}
.modal-content::-webkit-scrollbar-thumb:hover {
background: var(--color-text-tertiary);
}
@media (width <= 768px) {
.modal-overlay {
padding: 1rem;
}
.modal-header,
.modal-content {
padding: 1.5rem;
}
}
.modal-close:focus-visible {
outline: 2px solid var(--color-accent);
outline-offset: 2px;
}