mirror of
https://github.com/Kuingsmile/PicList.git
synced 2026-05-07 05:52:45 +08:00
✨ Feature(custom): optimize UI of process page
This commit is contained in:
@@ -1,13 +1,16 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="image-process-settings">
|
<div class="no-scrollbar flex h-full flex-col gap-5 overflow-auto border-none p-3 text-main">
|
||||||
<!-- Tab Navigation -->
|
<!-- Tab Navigation -->
|
||||||
<div class="tab-navigation">
|
<div class="relative flex flex-wrap rounded-xl border border-border-secondary/50 p-2 shadow-sm">
|
||||||
<div class="tab-indicator" :style="tabIndicatorStyle" />
|
<div
|
||||||
|
class="absolute z-0 rounded-lg bg-accent shadow-md transition-all duration-medium ease-bounce"
|
||||||
|
:style="tabIndicatorStyle"
|
||||||
|
/>
|
||||||
<button
|
<button
|
||||||
v-for="tab in tabs"
|
v-for="tab in tabs"
|
||||||
ref="tabRefs"
|
ref="tabRefs"
|
||||||
:key="tab.id"
|
:key="tab.id"
|
||||||
class="tab-button"
|
class="relative z-1 flex flex-1 cursor-pointer items-center justify-center gap-2.5 rounded-lg bg-transparent px-5 py-3.5 text-sm font-semibold text-secondary transition-all duration-medium ease-in-out not-[.active]:hover:bg-accent/50 not-[.active]:hover:text-main [.active]:font-bold [.active]:text-white"
|
||||||
:class="{ active: activeTab === tab.id }"
|
:class="{ active: activeTab === tab.id }"
|
||||||
@click="activeTab = tab.id"
|
@click="activeTab = tab.id"
|
||||||
>
|
>
|
||||||
@@ -17,10 +20,19 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Settings Content -->
|
<!-- Settings Content -->
|
||||||
<div class="settings-content">
|
<div
|
||||||
<transition name="fade-slide" mode="out-in">
|
class="no-scrollbar flex flex-1 flex-col overflow-auto rounded-lg border border-border-secondary/50 p-4 shadow-sm"
|
||||||
|
>
|
||||||
|
<transition
|
||||||
|
name="fade-slide"
|
||||||
|
enter-active-class="transition-all duration-medium ease-apple"
|
||||||
|
leave-active-class="transition-all duration-medium ease-apple"
|
||||||
|
enter-from-class="opacity-0 translate-y-[12px]"
|
||||||
|
leave-to-class="opacity-0 -translate-y-[12px]"
|
||||||
|
mode="out-in"
|
||||||
|
>
|
||||||
<!-- General Settings Tab -->
|
<!-- General Settings Tab -->
|
||||||
<div v-if="activeTab === 'general'" key="general" class="tab-content">
|
<div v-if="activeTab === 'general'" key="general" class="flex flex-col gap-4">
|
||||||
<div class="settings-section">
|
<div class="settings-section">
|
||||||
<div class="section-header">
|
<div class="section-header">
|
||||||
<div class="section-icon">
|
<div class="section-icon">
|
||||||
@@ -33,13 +45,11 @@
|
|||||||
|
|
||||||
<div class="form-grid">
|
<div class="form-grid">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="switch-label">
|
<customSwitch
|
||||||
<input v-model="activeForm.compress.isRemoveExif" type="checkbox" class="switch-input" />
|
v-model="activeForm.compress.isRemoveExif"
|
||||||
<span class="switch-slider" />
|
:title="$t('pages.imageProcess.general.isRemoveExif')"
|
||||||
<div class="switch-content">
|
class="custom-switch"
|
||||||
<span class="switch-title">{{ $t('pages.imageProcess.general.isRemoveExif') }}</span>
|
/>
|
||||||
</div>
|
|
||||||
</label>
|
|
||||||
|
|
||||||
<PerPicbedSetting
|
<PerPicbedSetting
|
||||||
v-if="!configId"
|
v-if="!configId"
|
||||||
@@ -62,9 +72,14 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label>{{ $t('pages.imageProcess.general.quality') }}</label>
|
<customRange
|
||||||
<input v-model.number="activeForm.compress.quality" type="range" min="1" max="100" class="form-range" />
|
v-model.number="activeForm.compress.quality"
|
||||||
<div class="range-value">{{ activeForm.compress.quality }}%</div>
|
:title="$t('pages.imageProcess.general.quality')"
|
||||||
|
:min="1"
|
||||||
|
:max="100"
|
||||||
|
:step="1"
|
||||||
|
:show-value="`${activeForm.compress.quality}%`"
|
||||||
|
/>
|
||||||
|
|
||||||
<PerPicbedSetting
|
<PerPicbedSetting
|
||||||
v-if="!configId"
|
v-if="!configId"
|
||||||
@@ -97,13 +112,11 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="switch-label">
|
<customSwitch
|
||||||
<input v-model="activeForm.compress.isConvert" type="checkbox" class="switch-input" />
|
v-model="activeForm.compress.isConvert"
|
||||||
<span class="switch-slider" />
|
:title="$t('pages.imageProcess.general.isConvert')"
|
||||||
<div class="switch-content">
|
class="custom-switch"
|
||||||
<span class="switch-title">{{ $t('pages.imageProcess.general.isConvert') }}</span>
|
/>
|
||||||
</div>
|
|
||||||
</label>
|
|
||||||
|
|
||||||
<PerPicbedSetting
|
<PerPicbedSetting
|
||||||
v-if="!configId"
|
v-if="!configId"
|
||||||
@@ -121,7 +134,7 @@
|
|||||||
|
|
||||||
<div v-if="activeForm.compress.isConvert" class="form-grid">
|
<div v-if="activeForm.compress.isConvert" class="form-grid">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label>{{ $t('pages.imageProcess.general.destinationFormat') }}</label>
|
<label class="title-text">{{ $t('pages.imageProcess.general.destinationFormat') }}</label>
|
||||||
<select v-model="activeForm.compress.convertFormat" class="form-input">
|
<select v-model="activeForm.compress.convertFormat" class="form-input">
|
||||||
<option v-for="format in availableFormat" :key="format" :value="format">
|
<option v-for="format in availableFormat" :key="format" :value="format">
|
||||||
{{ format.toUpperCase() }}
|
{{ format.toUpperCase() }}
|
||||||
@@ -150,7 +163,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label>{{ $t('pages.imageProcess.general.specificFormatConversion') }}</label>
|
<label class="title-text">{{ $t('pages.imageProcess.general.specificFormatConversion') }}</label>
|
||||||
<textarea
|
<textarea
|
||||||
v-model="convertStr"
|
v-model="convertStr"
|
||||||
class="form-textarea"
|
class="form-textarea"
|
||||||
@@ -183,7 +196,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Watermark Tab -->
|
<!-- Watermark Tab -->
|
||||||
<div v-else-if="activeTab === 'watermark'" key="watermark" class="tab-content">
|
<div v-else-if="activeTab === 'watermark'" key="watermark" class="flex flex-col gap-4">
|
||||||
<div class="settings-section">
|
<div class="settings-section">
|
||||||
<div class="section-header">
|
<div class="section-header">
|
||||||
<div class="section-icon watermark-icon">
|
<div class="section-icon watermark-icon">
|
||||||
@@ -196,13 +209,11 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="switch-label">
|
<customSwitch
|
||||||
<input v-model="activeForm.watermark.isAddWatermark" type="checkbox" class="switch-input" />
|
v-model="activeForm.watermark.isAddWatermark"
|
||||||
<span class="switch-slider" />
|
:title="$t('pages.imageProcess.watermark.isAdd')"
|
||||||
<div class="switch-content">
|
class="custom-switch"
|
||||||
<span class="switch-title">{{ $t('pages.imageProcess.watermark.isAdd') }}</span>
|
/>
|
||||||
</div>
|
|
||||||
</label>
|
|
||||||
|
|
||||||
<PerPicbedSetting
|
<PerPicbedSetting
|
||||||
v-if="!configId"
|
v-if="!configId"
|
||||||
@@ -224,25 +235,23 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="activeForm.watermark.isAddWatermark" class="watermark-settings">
|
<div
|
||||||
|
v-if="activeForm.watermark.isAddWatermark"
|
||||||
|
class="mt-4 border-t border-t-border pt-3 transition-all duration-200 ease-apple"
|
||||||
|
>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label>{{ $t('pages.imageProcess.watermark.type') }}</label>
|
<label class="title-text">{{ $t('pages.imageProcess.watermark.type') }}</label>
|
||||||
<div class="radio-group">
|
<div class="flex flex-wrap gap-4">
|
||||||
<label class="radio-option">
|
<customRadioOption
|
||||||
<input v-model="activeForm.watermark.watermarkType" type="radio" value="text" class="radio-input" />
|
|
||||||
<span class="radio-indicator" />
|
|
||||||
<span class="radio-label">{{ $t('pages.imageProcess.watermark.text') }}</span>
|
|
||||||
</label>
|
|
||||||
<label class="radio-option">
|
|
||||||
<input
|
|
||||||
v-model="activeForm.watermark.watermarkType"
|
v-model="activeForm.watermark.watermarkType"
|
||||||
type="radio"
|
value="text"
|
||||||
value="image"
|
:title="$t('pages.imageProcess.watermark.text')"
|
||||||
class="radio-input"
|
/>
|
||||||
|
<customRadioOption
|
||||||
|
v-model="activeForm.watermark.watermarkType"
|
||||||
|
value="image"
|
||||||
|
:title="$t('pages.imageProcess.watermark.image')"
|
||||||
/>
|
/>
|
||||||
<span class="radio-indicator" />
|
|
||||||
<span class="radio-label">{{ $t('pages.imageProcess.watermark.image') }}</span>
|
|
||||||
</label>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<PerPicbedSetting
|
<PerPicbedSetting
|
||||||
@@ -271,13 +280,11 @@
|
|||||||
|
|
||||||
<div class="form-grid">
|
<div class="form-grid">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="switch-label">
|
<customSwitch
|
||||||
<input v-model="activeForm.watermark.isFullScreenWatermark" type="checkbox" class="switch-input" />
|
v-model="activeForm.watermark.isFullScreenWatermark"
|
||||||
<span class="switch-slider" />
|
:title="$t('pages.imageProcess.watermark.isFullScreen')"
|
||||||
<div class="switch-content">
|
class="custom-switch"
|
||||||
<span class="switch-title">{{ $t('pages.imageProcess.watermark.isFullScreen') }}</span>
|
/>
|
||||||
</div>
|
|
||||||
</label>
|
|
||||||
|
|
||||||
<PerPicbedSetting
|
<PerPicbedSetting
|
||||||
v-if="!configId"
|
v-if="!configId"
|
||||||
@@ -300,15 +307,14 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label>{{ $t('pages.imageProcess.watermark.degree') }}</label>
|
<customRange
|
||||||
<input
|
|
||||||
v-model.number="activeForm.watermark.watermarkDegree"
|
v-model.number="activeForm.watermark.watermarkDegree"
|
||||||
type="range"
|
:title="$t('pages.imageProcess.watermark.degree')"
|
||||||
min="-360"
|
:min="-360"
|
||||||
max="360"
|
:max="360"
|
||||||
class="form-range"
|
:step="1"
|
||||||
|
:show-value="`${activeForm.watermark.watermarkDegree}°`"
|
||||||
/>
|
/>
|
||||||
<div class="range-value">{{ activeForm.watermark.watermarkDegree }}°</div>
|
|
||||||
|
|
||||||
<PerPicbedSetting
|
<PerPicbedSetting
|
||||||
v-if="!configId"
|
v-if="!configId"
|
||||||
@@ -335,18 +341,14 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label>{{ $t('pages.imageProcess.watermark.scaleRatio') }}</label>
|
<customRange
|
||||||
<input
|
|
||||||
v-model.number="activeForm.watermark.watermarkScaleRatio"
|
v-model.number="activeForm.watermark.watermarkScaleRatio"
|
||||||
type="range"
|
:title="$t('pages.imageProcess.watermark.scaleRatio')"
|
||||||
min="0"
|
:min="0"
|
||||||
max="1"
|
:max="1"
|
||||||
step="0.01"
|
:step="0.01"
|
||||||
class="form-range"
|
:show-value="`${Math.round((activeForm.watermark.watermarkScaleRatio || 0) * 100)}%`"
|
||||||
/>
|
/>
|
||||||
<div class="range-value">
|
|
||||||
{{ Math.round((activeForm.watermark.watermarkScaleRatio || 0) * 100) }}%
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<PerPicbedSetting
|
<PerPicbedSetting
|
||||||
v-if="!configId"
|
v-if="!configId"
|
||||||
@@ -375,7 +377,7 @@
|
|||||||
|
|
||||||
<div v-if="activeForm.watermark.watermarkType === 'text'" class="form-grid">
|
<div v-if="activeForm.watermark.watermarkType === 'text'" class="form-grid">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label>{{ $t('pages.imageProcess.watermark.inputText') }}</label>
|
<label class="title-text">{{ $t('pages.imageProcess.watermark.inputText') }}</label>
|
||||||
<input
|
<input
|
||||||
v-model="activeForm.watermark.watermarkText"
|
v-model="activeForm.watermark.watermarkText"
|
||||||
type="text"
|
type="text"
|
||||||
@@ -406,7 +408,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label>{{ $t('pages.imageProcess.watermark.textFontPath') }}</label>
|
<label class="title-text">{{ $t('pages.imageProcess.watermark.textFontPath') }}</label>
|
||||||
<input
|
<input
|
||||||
v-model="activeForm.watermark.watermarkFontPath"
|
v-model="activeForm.watermark.watermarkFontPath"
|
||||||
type="text"
|
type="text"
|
||||||
@@ -436,13 +438,17 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label>{{ $t('pages.imageProcess.watermark.color') }}</label>
|
<label class="title-text">{{ $t('pages.imageProcess.watermark.color') }}</label>
|
||||||
<div class="color-input-group">
|
<div class="flex flex-wrap items-center gap-2">
|
||||||
<input v-model="activeForm.watermark.watermarkColor" type="color" class="form-color" />
|
<input
|
||||||
|
v-model="activeForm.watermark.watermarkColor"
|
||||||
|
type="color"
|
||||||
|
class="h-[48px] w-[48px] cursor-pointer overflow-hidden rounded-lg border border-border bg-bg p-0.5 transition-all duration-200 ease-apple hover:border-accent hover:shadow-sm focus:border-accent focus:shadow-sm focus:outline-none"
|
||||||
|
/>
|
||||||
<input
|
<input
|
||||||
v-model="activeForm.watermark.watermarkColor"
|
v-model="activeForm.watermark.watermarkColor"
|
||||||
type="text"
|
type="text"
|
||||||
class="form-input"
|
class="form-input flex-1"
|
||||||
placeholder="#CCCCCC73"
|
placeholder="#CCCCCC73"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -470,7 +476,7 @@
|
|||||||
|
|
||||||
<!-- Image Watermark Settings -->
|
<!-- Image Watermark Settings -->
|
||||||
<div v-if="activeForm.watermark.watermarkType === 'image'" class="form-group">
|
<div v-if="activeForm.watermark.watermarkType === 'image'" class="form-group">
|
||||||
<label>{{ $t('pages.imageProcess.watermark.imagePath') }}</label>
|
<label class="title-text">{{ $t('pages.imageProcess.watermark.imagePath') }}</label>
|
||||||
<input
|
<input
|
||||||
v-model="activeForm.watermark.watermarkImagePath"
|
v-model="activeForm.watermark.watermarkImagePath"
|
||||||
type="text"
|
type="text"
|
||||||
@@ -500,18 +506,14 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="activeForm.watermark.watermarkType === 'image'" class="form-group">
|
<div v-if="activeForm.watermark.watermarkType === 'image'" class="form-group">
|
||||||
<label>{{ $t('pages.imageProcess.watermark.imageOpacity') }}</label>
|
<customRange
|
||||||
<input
|
|
||||||
v-model.number="activeForm.watermark.watermarkImageOpacity"
|
v-model.number="activeForm.watermark.watermarkImageOpacity"
|
||||||
type="range"
|
:title="$t('pages.imageProcess.watermark.imageOpacity')"
|
||||||
min="0"
|
:min="0"
|
||||||
max="255"
|
:max="255"
|
||||||
step="1"
|
:step="1"
|
||||||
class="form-range"
|
:show-value="`${activeForm.watermark.watermarkImageOpacity || 0}`"
|
||||||
/>
|
/>
|
||||||
<div class="range-value">
|
|
||||||
{{ activeForm.watermark.watermarkImageOpacity || 0 }}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<PerPicbedSetting
|
<PerPicbedSetting
|
||||||
v-if="!configId"
|
v-if="!configId"
|
||||||
@@ -537,13 +539,13 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label>{{ $t('pages.imageProcess.watermark.position') }}</label>
|
<label class="title-text">{{ $t('pages.imageProcess.watermark.position') }}</label>
|
||||||
<div class="position-grid">
|
<div class="grid max-w-[320px] grid-cols-3 gap-2.5">
|
||||||
<button
|
<button
|
||||||
v-for="[key, label] in waterMarkPositionMap"
|
v-for="[key, label] in waterMarkPositionMap"
|
||||||
:key="key"
|
:key="key"
|
||||||
type="button"
|
type="button"
|
||||||
class="position-button"
|
class="rounded-lg border border-border-secondary bg-bg p-3 text-center text-sm font-semibold text-secondary transition-all duration-200 ease-apple hover:border-accent hover:bg-accent/8 hover:text-main [.active]:border-accent/10 [.active]:bg-accent/20 [.active]:text-main"
|
||||||
:class="{ active: activeForm.watermark.watermarkPosition === key }"
|
:class="{ active: activeForm.watermark.watermarkPosition === key }"
|
||||||
@click="activeForm.watermark.watermarkPosition = key as any"
|
@click="activeForm.watermark.watermarkPosition = key as any"
|
||||||
>
|
>
|
||||||
@@ -581,7 +583,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Transform Tab -->
|
<!-- Transform Tab -->
|
||||||
<div v-else-if="activeTab === 'transform'" key="transform" class="tab-content">
|
<div v-else-if="activeTab === 'transform'" key="transform" class="flex flex-col gap-4">
|
||||||
<div class="settings-section">
|
<div class="settings-section">
|
||||||
<div class="section-header">
|
<div class="section-header">
|
||||||
<div class="section-icon transform-icon">
|
<div class="section-icon transform-icon">
|
||||||
@@ -595,13 +597,11 @@
|
|||||||
|
|
||||||
<div class="form-grid">
|
<div class="form-grid">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="switch-label">
|
<customSwitch
|
||||||
<input v-model="activeForm.compress.isFlip" type="checkbox" class="switch-input" />
|
v-model="activeForm.compress.isFlip"
|
||||||
<span class="switch-slider" />
|
:title="$t('pages.imageProcess.transform.isFlip')"
|
||||||
<div class="switch-content">
|
class="custom-switch"
|
||||||
<span class="switch-title">{{ $t('pages.imageProcess.transform.isFlip') }}</span>
|
/>
|
||||||
</div>
|
|
||||||
</label>
|
|
||||||
|
|
||||||
<PerPicbedSetting
|
<PerPicbedSetting
|
||||||
v-if="!configId"
|
v-if="!configId"
|
||||||
@@ -618,13 +618,11 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="switch-label">
|
<customSwitch
|
||||||
<input v-model="activeForm.compress.isFlop" type="checkbox" class="switch-input" />
|
v-model="activeForm.compress.isFlop"
|
||||||
<span class="switch-slider" />
|
:title="$t('pages.imageProcess.transform.isFlop')"
|
||||||
<div class="switch-content">
|
class="custom-switch"
|
||||||
<span class="switch-title">{{ $t('pages.imageProcess.transform.isFlop') }}</span>
|
/>
|
||||||
</div>
|
|
||||||
</label>
|
|
||||||
|
|
||||||
<PerPicbedSetting
|
<PerPicbedSetting
|
||||||
v-if="!configId"
|
v-if="!configId"
|
||||||
@@ -654,13 +652,11 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="switch-label">
|
<customSwitch
|
||||||
<input v-model="activeForm.compress.isRotate" type="checkbox" class="switch-input" />
|
v-model="activeForm.compress.isRotate"
|
||||||
<span class="switch-slider" />
|
:title="$t('pages.imageProcess.transform.isRotate')"
|
||||||
<div class="switch-content">
|
class="custom-switch"
|
||||||
<span class="switch-title">{{ $t('pages.imageProcess.transform.isRotate') }}</span>
|
/>
|
||||||
</div>
|
|
||||||
</label>
|
|
||||||
|
|
||||||
<PerPicbedSetting
|
<PerPicbedSetting
|
||||||
v-if="!configId"
|
v-if="!configId"
|
||||||
@@ -677,15 +673,14 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="activeForm.compress.isRotate" class="form-group">
|
<div v-if="activeForm.compress.isRotate" class="form-group">
|
||||||
<label>{{ $t('pages.imageProcess.transform.rotationDegree') }}</label>
|
<customRange
|
||||||
<input
|
|
||||||
v-model.number="activeForm.compress.rotateDegree"
|
v-model.number="activeForm.compress.rotateDegree"
|
||||||
type="range"
|
:title="$t('pages.imageProcess.transform.rotationDegree')"
|
||||||
min="-360"
|
:min="-360"
|
||||||
max="360"
|
:max="360"
|
||||||
class="form-range"
|
:step="1"
|
||||||
|
:show-value="`${activeForm.compress.rotateDegree}°`"
|
||||||
/>
|
/>
|
||||||
<div class="range-value">{{ activeForm.compress.rotateDegree }}°</div>
|
|
||||||
|
|
||||||
<PerPicbedSetting
|
<PerPicbedSetting
|
||||||
v-if="!configId"
|
v-if="!configId"
|
||||||
@@ -724,13 +719,11 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="switch-label">
|
<customSwitch
|
||||||
<input v-model="activeForm.compress.isReSize" type="checkbox" class="switch-input" />
|
v-model="activeForm.compress.isReSize"
|
||||||
<span class="switch-slider" />
|
:title="$t('pages.imageProcess.transform.isResize')"
|
||||||
<div class="switch-content">
|
class="custom-switch"
|
||||||
<span class="switch-title">{{ $t('pages.imageProcess.transform.isResize') }}</span>
|
/>
|
||||||
</div>
|
|
||||||
</label>
|
|
||||||
|
|
||||||
<PerPicbedSetting
|
<PerPicbedSetting
|
||||||
v-if="!configId"
|
v-if="!configId"
|
||||||
@@ -746,10 +739,13 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="activeForm.compress.isReSize" class="resize-settings">
|
<div
|
||||||
|
v-if="activeForm.compress.isReSize"
|
||||||
|
class="mt-4 border-t border-t-border pt-3 transition-all duration-200 ease-apple"
|
||||||
|
>
|
||||||
<div class="form-grid">
|
<div class="form-grid">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label>{{ $t('pages.imageProcess.transform.resizeWidth') }}</label>
|
<label class="title-text">{{ $t('pages.imageProcess.transform.resizeWidth') }}</label>
|
||||||
<input v-model.number="activeForm.compress.reSizeWidth" type="number" min="0" class="form-input" />
|
<input v-model.number="activeForm.compress.reSizeWidth" type="number" min="0" class="form-input" />
|
||||||
|
|
||||||
<PerPicbedSetting
|
<PerPicbedSetting
|
||||||
@@ -775,7 +771,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label>{{ $t('pages.imageProcess.transform.resizeHeight') }}</label>
|
<label class="title-text">{{ $t('pages.imageProcess.transform.resizeHeight') }}</label>
|
||||||
<input v-model.number="activeForm.compress.reSizeHeight" type="number" min="0" class="form-input" />
|
<input v-model.number="activeForm.compress.reSizeHeight" type="number" min="0" class="form-input" />
|
||||||
|
|
||||||
<PerPicbedSetting
|
<PerPicbedSetting
|
||||||
@@ -808,15 +804,11 @@
|
|||||||
"
|
"
|
||||||
class="form-group"
|
class="form-group"
|
||||||
>
|
>
|
||||||
<label class="switch-label">
|
<customSwitch
|
||||||
<input v-model="activeForm.compress.skipReSizeOfSmallImg" type="checkbox" class="switch-input" />
|
v-model="activeForm.compress.skipReSizeOfSmallImg"
|
||||||
<span class="switch-slider" />
|
:title="$t('pages.imageProcess.transform.skipResizeOfSmallImgHeight')"
|
||||||
<div class="switch-content">
|
class="custom-switch"
|
||||||
<span class="switch-title">{{
|
/>
|
||||||
$t('pages.imageProcess.transform.skipResizeOfSmallImgHeight')
|
|
||||||
}}</span>
|
|
||||||
</div>
|
|
||||||
</label>
|
|
||||||
|
|
||||||
<PerPicbedSetting
|
<PerPicbedSetting
|
||||||
v-if="!configId"
|
v-if="!configId"
|
||||||
@@ -851,14 +843,12 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="switch-label">
|
<customSwitch
|
||||||
<input v-model="activeForm.compress.isReSizeByPercent" type="checkbox" class="switch-input" />
|
v-model="activeForm.compress.isReSizeByPercent"
|
||||||
<span class="switch-slider" />
|
:title="$t('pages.imageProcess.transform.isResizeByPercent')"
|
||||||
<div class="switch-content">
|
:description="$t('pages.imageProcess.transform.isResizeByPercentHint')"
|
||||||
<span class="switch-title">{{ $t('pages.imageProcess.transform.isResizeByPercent') }}</span>
|
class="custom-switch"
|
||||||
<span class="switch-description">{{ $t('pages.imageProcess.transform.isResizeByPercentHint') }}</span>
|
/>
|
||||||
</div>
|
|
||||||
</label>
|
|
||||||
|
|
||||||
<PerPicbedSetting
|
<PerPicbedSetting
|
||||||
v-if="!configId"
|
v-if="!configId"
|
||||||
@@ -881,15 +871,14 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="activeForm.compress.isReSizeByPercent" class="form-group">
|
<div v-if="activeForm.compress.isReSizeByPercent" class="form-group">
|
||||||
<label>{{ $t('pages.imageProcess.transform.resizePercent') }}</label>
|
<customRange
|
||||||
<input
|
|
||||||
v-model.number="activeForm.compress.reSizePercent"
|
v-model.number="activeForm.compress.reSizePercent"
|
||||||
type="range"
|
:title="$t('pages.imageProcess.transform.resizePercent')"
|
||||||
min="1"
|
:min="1"
|
||||||
max="500"
|
:max="500"
|
||||||
class="form-range"
|
:step="1"
|
||||||
|
:show-value="`${activeForm.compress.reSizePercent}%`"
|
||||||
/>
|
/>
|
||||||
<div class="range-value">{{ activeForm.compress.reSizePercent }}%</div>
|
|
||||||
|
|
||||||
<PerPicbedSetting
|
<PerPicbedSetting
|
||||||
v-if="!configId"
|
v-if="!configId"
|
||||||
@@ -918,7 +907,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Skip Process Tab -->
|
<!-- Skip Process Tab -->
|
||||||
<div v-else-if="activeTab === 'skipProcess'" key="skipProcess" class="tab-content">
|
<div v-else-if="activeTab === 'skipProcess'" key="skipProcess" class="flex flex-col gap-4">
|
||||||
<div class="settings-section">
|
<div class="settings-section">
|
||||||
<div class="section-header">
|
<div class="section-header">
|
||||||
<div class="section-icon">
|
<div class="section-icon">
|
||||||
@@ -935,51 +924,47 @@
|
|||||||
rows="3"
|
rows="3"
|
||||||
:placeholder="'zip,rar,7z,tar,gz'"
|
:placeholder="'zip,rar,7z,tar,gz'"
|
||||||
/>
|
/>
|
||||||
<small>{{ $t('pages.imageProcess.general.skipProcessExtListPlaceholder') }}</small>
|
<small class="mt-2 block rounded-sm bg-bg-secondary px-3 py-2 text-xs leading-[1.5] text-tertiary">{{
|
||||||
|
$t('pages.imageProcess.general.skipProcessExtListPlaceholder')
|
||||||
|
}}</small>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Rename Tab -->
|
<!-- Rename Tab -->
|
||||||
<div v-else-if="activeTab === 'rename'" key="rename" class="tab-content">
|
<div v-else-if="activeTab === 'rename'" key="rename" class="flex flex-col gap-4">
|
||||||
<div class="settings-section">
|
<div class="settings-section">
|
||||||
<div class="form-grid">
|
<div class="form-grid">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="switch-label">
|
<customSwitch
|
||||||
<input v-model="autoRenameComputed" type="checkbox" class="switch-input" />
|
v-model="autoRenameComputed"
|
||||||
<span class="switch-slider" />
|
:title="$t('pages.imageProcess.rename.renameTimestamp')"
|
||||||
<div class="switch-content">
|
description="YYYYMMDDHHmmssSSS"
|
||||||
<span class="switch-title">{{ $t('pages.imageProcess.rename.renameTimestamp') }}</span>
|
class="custom-switch"
|
||||||
<span class="switch-description">YYYYMMDDHHmmssSSS</span>
|
/>
|
||||||
</div>
|
|
||||||
</label>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="switch-label">
|
<customSwitch
|
||||||
<input v-model="manualRenameComputed" type="checkbox" class="switch-input" />
|
v-model="manualRenameComputed"
|
||||||
<span class="switch-slider" />
|
:title="$t('pages.imageProcess.rename.manualRename')"
|
||||||
<div class="switch-content">
|
class="custom-switch"
|
||||||
<span class="switch-title">{{ $t('pages.imageProcess.rename.manualRename') }}</span>
|
/>
|
||||||
</div>
|
|
||||||
</label>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="switch-label">
|
<customSwitch
|
||||||
<input v-model="renameSettingsComputed.rename.enable" type="checkbox" class="switch-input" />
|
v-model="renameSettingsComputed.rename.enable"
|
||||||
<span class="switch-slider" />
|
:title="$t('pages.settings.upload.enableAdvancedRname')"
|
||||||
<div class="switch-content">
|
:description="$t('pages.settings.upload.enableAdvancedRnameDesc')"
|
||||||
<div class="switch-title">{{ $t('pages.settings.upload.enableAdvancedRname') }}</div>
|
class="custom-switch"
|
||||||
<div class="switch-description">{{ $t('pages.settings.upload.enableAdvancedRnameDesc') }}</div>
|
/>
|
||||||
</div>
|
|
||||||
</label>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group rename-format-field">
|
<div class="form-group rename-format-field">
|
||||||
<label>
|
<label class="title-text mb-4 flex items-center gap-2">
|
||||||
<Edit :size="14" />
|
<Edit :size="14" class="text-accent" />
|
||||||
{{ $t('pages.settings.upload.advancedRnameFormat') }}
|
{{ $t('pages.settings.upload.advancedRnameFormat') }}
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
@@ -991,59 +976,8 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label>{{ $t('pages.settings.upload.availablePlaceholders') }}</label>
|
<label class="title-text">{{ $t('pages.settings.upload.availablePlaceholders') }}</label>
|
||||||
<div class="placeholder-help">
|
<placeholderTable :list="advancedRenameList" :title-list="advancedRenameTitleList" />
|
||||||
<div class="placeholder-category">
|
|
||||||
<div class="category-title">
|
|
||||||
{{ $t('pages.settings.upload.placeholder.categoryTime') }}
|
|
||||||
</div>
|
|
||||||
<div class="placeholder-grid">
|
|
||||||
<div
|
|
||||||
v-for="item in advancedRenameList.categoryTime"
|
|
||||||
:key="item.value"
|
|
||||||
class="placeholder-item"
|
|
||||||
@click="copyPlaceholder(item.value)"
|
|
||||||
>
|
|
||||||
<code>{{ item.value }}</code>
|
|
||||||
<span>{{ item.label }}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="placeholder-category">
|
|
||||||
<div class="category-title">
|
|
||||||
{{ $t('pages.settings.upload.placeholder.categoryHash') }}
|
|
||||||
</div>
|
|
||||||
<div class="placeholder-grid">
|
|
||||||
<div
|
|
||||||
v-for="item in advancedRenameList.categoryHash"
|
|
||||||
:key="item.value"
|
|
||||||
class="placeholder-item"
|
|
||||||
@click="copyPlaceholder(item.value)"
|
|
||||||
>
|
|
||||||
<code>{{ item.value }}</code>
|
|
||||||
<span>{{ item.label }}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="placeholder-category">
|
|
||||||
<div class="category-title">
|
|
||||||
{{ $t('pages.settings.upload.placeholder.categoryFile') }}
|
|
||||||
</div>
|
|
||||||
<div class="placeholder-grid">
|
|
||||||
<div
|
|
||||||
v-for="item in advancedRenameList.categoryFile"
|
|
||||||
:key="item.value"
|
|
||||||
class="placeholder-item"
|
|
||||||
@click="copyPlaceholder(item.value)"
|
|
||||||
>
|
|
||||||
<code>{{ item.value }}</code>
|
|
||||||
<span>{{ item.label }}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -1077,14 +1011,16 @@ import type {
|
|||||||
import { computed, nextTick, onBeforeMount, onBeforeUnmount, onMounted, ref, toRaw, useTemplateRef, watch } from 'vue'
|
import { computed, nextTick, onBeforeMount, onBeforeUnmount, onMounted, ref, toRaw, useTemplateRef, watch } from 'vue'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
|
|
||||||
|
import customRadioOption from '@/components/common/customRadioOption.vue'
|
||||||
|
import customRange from '@/components/common/customRange.vue'
|
||||||
|
import customSwitch from '@/components/common/customSwitch.vue'
|
||||||
|
import placeholderTable from '@/components/common/placeholderTable.vue'
|
||||||
import PerPicbedSetting from '@/components/PerPicbedSetting.vue'
|
import PerPicbedSetting from '@/components/PerPicbedSetting.vue'
|
||||||
import useMessage from '@/hooks/useMessage'
|
|
||||||
import { getRawData } from '@/utils/common'
|
import { getRawData } from '@/utils/common'
|
||||||
import { configPaths } from '@/utils/configPaths'
|
import { configPaths } from '@/utils/configPaths'
|
||||||
import { getConfig, saveConfig } from '@/utils/dataSender'
|
import { getConfig, saveConfig } from '@/utils/dataSender'
|
||||||
|
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
const message = useMessage()
|
|
||||||
const activeTab = useStorage<string>('image-process-setting-active-tab', 'general')
|
const activeTab = useStorage<string>('image-process-setting-active-tab', 'general')
|
||||||
|
|
||||||
// Tab indicator animation
|
// Tab indicator animation
|
||||||
@@ -1106,7 +1042,9 @@ function updateTabIndicator() {
|
|||||||
const activeTabEl = tabRefs.value[activeIndex]
|
const activeTabEl = tabRefs.value[activeIndex]
|
||||||
if (activeTabEl) {
|
if (activeTabEl) {
|
||||||
tabIndicatorStyle.value = {
|
tabIndicatorStyle.value = {
|
||||||
width: `${activeTabEl.offsetWidth}px`,
|
top: `${activeTabEl.offsetTop}px`,
|
||||||
|
height: `${activeTabEl.offsetHeight}px`,
|
||||||
|
width: `${activeTabEl.offsetWidth - 12}px`,
|
||||||
transform: `translateX(${activeTabEl.offsetLeft}px)`,
|
transform: `translateX(${activeTabEl.offsetLeft}px)`,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1166,10 +1104,11 @@ const advancedRenameList = computed(() => ({
|
|||||||
],
|
],
|
||||||
}))
|
}))
|
||||||
|
|
||||||
function copyPlaceholder(placeholder: string) {
|
const advancedRenameTitleList = computed(() => ({
|
||||||
window.electron.clipboard.writeText(placeholder)
|
categoryTime: t('pages.settings.upload.placeholder.categoryTime'),
|
||||||
message.success(t('pages.settings.upload.copySuccess', { content: placeholder }))
|
categoryHash: t('pages.settings.upload.placeholder.categoryHash'),
|
||||||
}
|
categoryFile: t('pages.settings.upload.placeholder.categoryFile'),
|
||||||
|
}))
|
||||||
|
|
||||||
const waterMarkPositionMap = new Map([
|
const waterMarkPositionMap = new Map([
|
||||||
['north', t('pages.imageProcess.watermark.positionOptions.top')],
|
['north', t('pages.imageProcess.watermark.positionOptions.top')],
|
||||||
|
|||||||
18
src/renderer/components/common/customRadioOption.vue
Normal file
18
src/renderer/components/common/customRadioOption.vue
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
<template>
|
||||||
|
<label
|
||||||
|
class="flex cursor-pointer items-center gap-2.5 rounded-lg border border-border bg-bg px-4 py-3.5 transition-all duration-200 ease-apple hover:border-accent-hover hover:bg-accent/8"
|
||||||
|
>
|
||||||
|
<input v-model="modelValue" type="radio" :value="value" class="peer hidden" />
|
||||||
|
<span
|
||||||
|
class="relative h-[18px] w-[18px] rounded-full border border-border bg-bg transition-all duration-200 ease-apple peer-checked:border-accent peer-checked:after:absolute peer-checked:after:top-1/2 peer-checked:after:left-1/2 peer-checked:after:h-[10px] peer-checked:after:w-[10px] peer-checked:after:-translate-x-1/2 peer-checked:after:-translate-y-1/2 peer-checked:after:transform peer-checked:after:rounded-full peer-checked:after:bg-accent peer-checked:after:content-['']"
|
||||||
|
/>
|
||||||
|
<span class="text-sm font-medium text-main">{{ title }}</span>
|
||||||
|
</label>
|
||||||
|
</template>
|
||||||
|
<script lang="ts" setup>
|
||||||
|
const modelValue = defineModel<string>()
|
||||||
|
const { value = '', title = '' } = defineProps<{
|
||||||
|
value?: string
|
||||||
|
title?: string
|
||||||
|
}>()
|
||||||
|
</script>
|
||||||
34
src/renderer/components/common/customRange.vue
Normal file
34
src/renderer/components/common/customRange.vue
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
<template>
|
||||||
|
<label class="text-sm font-semibold text-main">{{ title }}</label>
|
||||||
|
<input
|
||||||
|
v-model.number="modelValue"
|
||||||
|
type="range"
|
||||||
|
:min="min"
|
||||||
|
:max="max"
|
||||||
|
:step="step"
|
||||||
|
class="my-3 h-[8px] w-full appearance-none rounded-sm bg-linear-to-l from-accent transition-colors duration-150 ease-apple outline-none [&::--moz-range-thumb]:shadow-sm [&::-moz-range-thumb]:h-[22px] [&::-moz-range-thumb]:w-[22px] [&::-moz-range-thumb]:cursor-pointer [&::-moz-range-thumb]:rounded-full [&::-moz-range-thumb]:border [&::-moz-range-thumb]:border-border [&::-moz-range-thumb]:bg-white [&::-webkit-slider-thumb]:h-[22px] [&::-webkit-slider-thumb]:w-[22px] [&::-webkit-slider-thumb]:cursor-pointer [&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:rounded-full [&::-webkit-slider-thumb]:border [&::-webkit-slider-thumb]:border-border [&::-webkit-slider-thumb]:bg-white [&::-webkit-slider-thumb]:shadow-sm [&::-webkit-slider-thumb]:transition-all [&::-webkit-slider-thumb]:duration-200 [&::-webkit-slider-thumb]:ease-apple [&::-webkit-slider-thumb:hover]:scale-105 [&::-webkit-slider-thumb:hover]:shadow-md"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="mt-2 inline-flex min-w-14 items-center justify-center rounded-md bg-accent px-2 py-1.5 text-sm font-semibold text-white shadow-sm"
|
||||||
|
>
|
||||||
|
{{ showValue }}
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
const modelValue = defineModel<number>()
|
||||||
|
|
||||||
|
const {
|
||||||
|
title = '',
|
||||||
|
step = 1,
|
||||||
|
min = 1,
|
||||||
|
max = 100,
|
||||||
|
showValue = '',
|
||||||
|
} = defineProps<{
|
||||||
|
title?: string
|
||||||
|
min?: number
|
||||||
|
max?: number
|
||||||
|
step?: number
|
||||||
|
showValue?: string
|
||||||
|
}>()
|
||||||
|
</script>
|
||||||
22
src/renderer/components/common/customSwitch.vue
Normal file
22
src/renderer/components/common/customSwitch.vue
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
<template>
|
||||||
|
<label
|
||||||
|
class="flex cursor-pointer items-center gap-4 rounded-lg border border-border bg-bg p-4 transition-all duration-200 ease-apple hover:border-accent hover:bg-surface hover:shadow-sm"
|
||||||
|
>
|
||||||
|
<input v-model="modelValue" type="checkbox" class="peer hidden" />
|
||||||
|
<span
|
||||||
|
class="bg-linear-180-r relative h-[28px] w-[52px] shrink-0 rounded-full bg-gray-400/80 shadow-sm transition-all duration-medium ease-standard peer-checked:bg-accent before:absolute before:top-[3px] before:left-[3px] before:h-[22px] before:w-[22px] 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]"
|
||||||
|
/>
|
||||||
|
<div class="flex flex-1 flex-col gap-1">
|
||||||
|
<span class="text-[0.925rem] leading-[1.4] font-semibold text-secondary">{{ props.title }}</span>
|
||||||
|
<span class="text-xs text-secondary/90">{{ props.description }}</span>
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
const modelValue = defineModel<boolean>()
|
||||||
|
const props = defineProps<{
|
||||||
|
title?: string
|
||||||
|
description?: string
|
||||||
|
}>()
|
||||||
|
</script>
|
||||||
43
src/renderer/components/common/placeholderTable.vue
Normal file
43
src/renderer/components/common/placeholderTable.vue
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
<template>
|
||||||
|
<div class="mt-3 max-h-[400px] overflow-y-auto rounded-lg border border-border bg-bg-tertiary p-0 shadow-sm">
|
||||||
|
<template v-for="key in Object.keys(list)" :key="key">
|
||||||
|
<div class="border-b border-border last:border-0">
|
||||||
|
<div
|
||||||
|
class="bg-linear-150-r m-0 border-b border-border bg-accent/10 px-4 pt-3.5 pb-2 text-sm font-semibold tracking-wide text-secondary"
|
||||||
|
>
|
||||||
|
{{ $t(titleList[key]) }}
|
||||||
|
</div>
|
||||||
|
<div class="grid grid-cols-[repeat(auto-fill,minmax(240px,1fr))] gap-0 py-2">
|
||||||
|
<div
|
||||||
|
v-for="item in list[key]"
|
||||||
|
:key="item.value"
|
||||||
|
class="m-0 flex cursor-pointer items-center rounded-none px-4 py-2 text-sm leading-[1.4] hover:bg-accent/5"
|
||||||
|
@click="copyPlaceholder(item.value)"
|
||||||
|
>
|
||||||
|
<code
|
||||||
|
class="mr-3.5 min-w-[80px] shrink-0 rounded-md border border-white/20 bg-bg-secondary px-2 py-1 text-center font-['SF_Mono',Monaco,Menlo,'Ubuntu_Mono',monospace] text-base font-semibold text-main shadow-sm"
|
||||||
|
>{{ item.value }}</code
|
||||||
|
>
|
||||||
|
<span class="flex-1 font-medium text-main">{{ item.label }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { useI18n } from 'vue-i18n'
|
||||||
|
|
||||||
|
import useMessage from '@/hooks/useMessage'
|
||||||
|
|
||||||
|
const { t } = useI18n()
|
||||||
|
const message = useMessage()
|
||||||
|
function copyPlaceholder(placeholder: string) {
|
||||||
|
window.electron.clipboard.writeText(placeholder)
|
||||||
|
message.success(t('pages.settings.upload.copySuccess', { content: placeholder }))
|
||||||
|
}
|
||||||
|
const { list, titleList } = defineProps<{
|
||||||
|
list: Record<string, { label: string; value: string }[]>
|
||||||
|
titleList: Record<string, string>
|
||||||
|
}>()
|
||||||
|
</script>
|
||||||
@@ -1,715 +1,51 @@
|
|||||||
/* ==================== Base & Layout ==================== */
|
@import 'tailwindcss' reference;
|
||||||
.image-process-settings {
|
@import '../../assets/css/theme.css' reference;
|
||||||
overflow-y: auto;
|
@import '../../assets/css/utilities.css' reference;
|
||||||
padding: 1rem;
|
|
||||||
height: 100%;
|
|
||||||
min-height: 100vh;
|
|
||||||
color: var(--color-text-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ==================== Tab Navigation ==================== */
|
|
||||||
.tab-navigation {
|
|
||||||
position: relative;
|
|
||||||
display: flex;
|
|
||||||
margin-bottom: 2rem;
|
|
||||||
border: 1px solid var(--color-border);
|
|
||||||
border-radius: var(--radius-xl);
|
|
||||||
padding: 6px;
|
|
||||||
box-shadow:var(--shadow-sm);
|
|
||||||
}
|
|
||||||
|
|
||||||
.tab-indicator {
|
|
||||||
position: absolute;
|
|
||||||
top: 6px;
|
|
||||||
left: 6px;
|
|
||||||
z-index: 0;
|
|
||||||
border-radius: var(--radius-lg);
|
|
||||||
height: calc(100% - 12px);
|
|
||||||
background: var(--color-accent);
|
|
||||||
box-shadow: var(--shadow-md);
|
|
||||||
transition: all var(--transition-medium);
|
|
||||||
}
|
|
||||||
|
|
||||||
.tab-button {
|
|
||||||
position: relative;
|
|
||||||
z-index: 1;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
border: none;
|
|
||||||
border-radius: var(--radius-lg);
|
|
||||||
padding: 0.875rem 1.25rem;
|
|
||||||
font-size: 0.9rem;
|
|
||||||
font-weight: 600;
|
|
||||||
color: var(--color-text-secondary);
|
|
||||||
background: transparent;
|
|
||||||
transition: all 0.25s ease;
|
|
||||||
gap: 0.625rem;
|
|
||||||
cursor: pointer;
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tab-button:hover:not(.active) {
|
|
||||||
color: var(--color-text-primary);
|
|
||||||
background: var(--color-background-secondary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.tab-button.active {
|
|
||||||
color: white;
|
|
||||||
background: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ==================== Settings Content ==================== */
|
|
||||||
.settings-content {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tab-content {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 1.75rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Fade slide transition for tab content */
|
|
||||||
.fade-slide-enter-active,
|
|
||||||
.fade-slide-leave-active {
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.fade-slide-enter-from {
|
|
||||||
opacity: 0;
|
|
||||||
transform: translateY(12px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.fade-slide-leave-to {
|
|
||||||
opacity: 0;
|
|
||||||
transform: translateY(-12px);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ==================== Settings Section ==================== */
|
/* ==================== Settings Section ==================== */
|
||||||
.settings-section {
|
.settings-section {
|
||||||
border: 1px solid var(--color-border);
|
@apply rounded-xl border border-border bg-bg p-5 shadow-sm transition-all duration-200 ease-apple hover:shadow-md;
|
||||||
border-radius: var(--radius-xl);
|
|
||||||
padding: 1.75rem;
|
|
||||||
background: var(--color-background-primary);
|
|
||||||
box-shadow: var(--shadow-sm);
|
|
||||||
transition: all 0.25s ease;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.settings-section:hover {
|
|
||||||
box-shadow: var(--shadow-md);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Section Header with Icon */
|
|
||||||
.section-header {
|
.section-header {
|
||||||
display: flex;
|
@apply mb-6 flex items-center gap-1;
|
||||||
align-items: flex-start;
|
|
||||||
margin-bottom: 1.5rem;
|
|
||||||
gap: 1rem;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.section-icon {
|
.section-icon {
|
||||||
display: flex;
|
@apply flex h-[30px] w-[30px] shrink-0 items-center justify-center rounded-lg bg-bg-secondary text-accent;
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
border-radius: var(--radius-lg);
|
|
||||||
width: 30px;
|
|
||||||
height: 30px;
|
|
||||||
color: var(--color-accent);
|
|
||||||
background: var(--color-background-secondary);
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.section-title-group h2 {
|
.section-title-group {
|
||||||
margin: 0 0 0.25rem;
|
@apply [&_h2]:mb-0 [&_h2]:text-lg [&_h2]:leading-0 [&_h2]:font-semibold [&_h2]:tracking-tight [&_h2]:text-main;
|
||||||
font-size: 1.125rem;
|
@apply [&_p]:mt-6 [&_p]:text-sm [&_p]:leading-0 [&_p]:text-secondary;
|
||||||
font-weight: 600;
|
|
||||||
letter-spacing: -0.01em;
|
|
||||||
color: var(--color-text-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.section-title-group p {
|
|
||||||
margin: 0;
|
|
||||||
font-size: 0.85rem;
|
|
||||||
color: var(--color-text-secondary);
|
|
||||||
line-height: 1.5;
|
|
||||||
}
|
|
||||||
|
|
||||||
.settings-section h2 {
|
|
||||||
margin: 0 0 0.5rem;
|
|
||||||
font-size: 1.125rem;
|
|
||||||
font-weight: 600;
|
|
||||||
color: var(--color-text-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.settings-section p {
|
|
||||||
margin: 0 0 1.5rem;
|
|
||||||
font-size: 0.875rem;
|
|
||||||
color: var(--color-text-secondary);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ==================== Form Elements ==================== */
|
/* ==================== Form Elements ==================== */
|
||||||
|
.form-grid {
|
||||||
|
@apply grid grid-cols-[repeat(auto-fit,minmax(280px,1fr))] gap-4;
|
||||||
|
}
|
||||||
|
|
||||||
.form-group {
|
.form-group {
|
||||||
margin-bottom: 1.5rem;
|
@apply mb-6;
|
||||||
}
|
@apply last:mb-0;
|
||||||
|
@apply [&>:label:not(.custom-switch,.radio-option)]:mb-2.5
|
||||||
.form-group:last-child {
|
[&>:label:not(.custom-switch,.radio-option)]:block
|
||||||
margin-bottom: 0;
|
[&>:label:not(.custom-switch,.radio-option)]:text-sm
|
||||||
}
|
[&>:label:not(.custom-switch,.radio-option)]:font-semibold
|
||||||
|
[&>:label:not(.custom-switch,.radio-option)]:text-main;
|
||||||
.form-group > label:not(.switch-label, .radio-option) {
|
|
||||||
display: block;
|
|
||||||
margin-bottom: 0.625rem;
|
|
||||||
font-size: 0.875rem;
|
|
||||||
font-weight: 600;
|
|
||||||
color: var(--color-text-primary);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-input,
|
.form-input,
|
||||||
.form-textarea {
|
.form-textarea {
|
||||||
border: 1.5px solid var(--color-border);
|
@apply box-border w-full rounded-lg border border-border bg-bg-tertiary px-4 py-3.5 text-sm text-main transition-all duration-200 ease-apple;
|
||||||
border-radius: var(--radius-lg);
|
@apply hover:border-border-secondary;
|
||||||
padding: 0.875rem 1rem;
|
@apply focus:border-accent focus:bg-surface-elevated focus:shadow-sm focus:outline-none;
|
||||||
width: 100%;
|
|
||||||
font-size: 0.9rem;
|
|
||||||
color: var(--color-text-primary);
|
|
||||||
background: var(--color-background-tertiary);
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-input:hover,
|
|
||||||
.form-textarea:hover {
|
|
||||||
border-color: var(--color-border-secondary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-input:focus,
|
|
||||||
.form-textarea:focus {
|
|
||||||
border-color: var(--color-accent);
|
|
||||||
outline: none;
|
|
||||||
box-shadow: var(--shadow-sm);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-textarea {
|
.form-textarea {
|
||||||
min-height: 90px;
|
@apply min-h-[90px] resize-y font-[ui-monospace,SFMono-Regular,'SF_Mono',Menlo,Consolas,'Liberation_Mono',monospace];
|
||||||
font-family: ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, 'Liberation Mono', monospace;
|
|
||||||
resize: vertical;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ==================== Range Slider ==================== */
|
.title-text {
|
||||||
.form-range {
|
@apply text-base font-semibold text-main;
|
||||||
margin: 0.75rem 0;
|
|
||||||
border-radius: 4px;
|
|
||||||
width: 100%;
|
|
||||||
height: 8px;
|
|
||||||
background: linear-gradient(90deg, var(--color-accent) 0%, #e4e7ed 0%);
|
|
||||||
outline: none;
|
|
||||||
appearance: none;
|
|
||||||
transition: background 0.15s ease;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-range::-webkit-slider-thumb {
|
|
||||||
border: 2px solid var(--color-border);
|
|
||||||
border-radius: var(--radius-round);
|
|
||||||
width: 22px;
|
|
||||||
height: 22px;
|
|
||||||
background: var(--color-accent);
|
|
||||||
box-shadow: var(--shadow-sm);
|
|
||||||
appearance: none;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-range::-webkit-slider-thumb:hover {
|
|
||||||
transform: scale(1.1);
|
|
||||||
box-shadow: var(--shadow-md);
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-range::-moz-range-thumb {
|
|
||||||
border: 2px solid var(--color-border);
|
|
||||||
border-radius: var(--radius-round);
|
|
||||||
width: 22px;
|
|
||||||
height: 22px;
|
|
||||||
background: var(--color-accent);
|
|
||||||
box-shadow: var(--shadow-sm);
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.range-value {
|
|
||||||
display: inline-flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
margin-top: 0.5rem;
|
|
||||||
border-radius: var(--radius-md);
|
|
||||||
padding: 0.375rem 0.75rem;
|
|
||||||
min-width: 3.5rem;
|
|
||||||
font-size: 0.8rem;
|
|
||||||
font-weight: 600;
|
|
||||||
text-align: center;
|
|
||||||
color: white;
|
|
||||||
background: var(--color-accent);
|
|
||||||
box-shadow: var(--shadow-sm);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ==================== Color Input ==================== */
|
|
||||||
.form-color {
|
|
||||||
overflow: hidden;
|
|
||||||
border: 2px solid var(--color-border);
|
|
||||||
border-radius: var(--radius-lg);
|
|
||||||
padding: 2px;
|
|
||||||
width: 48px;
|
|
||||||
height: 48px;
|
|
||||||
background: var(--color-background-primary);
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-color:hover {
|
|
||||||
border-color: var(--color-accent);
|
|
||||||
box-shadow: var(--shadow-sm);
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-color:focus {
|
|
||||||
border-color: var(--color-accent);
|
|
||||||
outline: none;
|
|
||||||
box-shadow: 0 0 0 3px rgb(64 158 255 / 20%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.color-input-group {
|
|
||||||
display: flex;
|
|
||||||
gap: 1rem;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.color-input-group .form-input {
|
|
||||||
flex: 1;
|
|
||||||
font-family: ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, monospace;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ==================== Grid Layout ==================== */
|
|
||||||
.form-grid {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
|
|
||||||
gap: 1.75rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ==================== Switch Component ==================== */
|
|
||||||
.switch-label {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
border: 1.5px solid var(--color-border);
|
|
||||||
border-radius: var(--radius-lg);
|
|
||||||
padding: 1.125rem 1.25rem;
|
|
||||||
background: var(--color-background-primary);
|
|
||||||
transition: all 0.25s ease;
|
|
||||||
gap: 1rem;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.switch-label:hover {
|
|
||||||
border-color: var(--color-accent);
|
|
||||||
background: var(--color-surface);
|
|
||||||
box-shadow: 0 2px 8px rgb(64 158 255 / 10%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.switch-input {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.switch-slider {
|
|
||||||
position: relative;
|
|
||||||
border-radius: var(--radius-xl);
|
|
||||||
width: 52px;
|
|
||||||
height: 28px;
|
|
||||||
background: linear-gradient(180deg, #d0d3d9 0%, #c0c4cc 100%);
|
|
||||||
box-shadow: inset 0 1px 3px rgb(0 0 0 / 15%);
|
|
||||||
transition: all var(--transition-medium);
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.switch-slider::before {
|
|
||||||
position: absolute;
|
|
||||||
top: 3px;
|
|
||||||
left: 3px;
|
|
||||||
border-radius: var(--radius-round);
|
|
||||||
width: 22px;
|
|
||||||
height: 22px;
|
|
||||||
background: linear-gradient(180deg, #ffffff 0%, #f5f5f5 100%);
|
|
||||||
box-shadow:
|
|
||||||
0 2px 6px rgb(0 0 0 / 20%),
|
|
||||||
0 1px 2px rgb(0 0 0 / 10%);
|
|
||||||
transition: all var(--transition-medium);
|
|
||||||
content: '';
|
|
||||||
}
|
|
||||||
|
|
||||||
.switch-input:checked + .switch-slider {
|
|
||||||
background: var(--color-accent);
|
|
||||||
box-shadow:
|
|
||||||
inset 0 1px 3px rgb(0 0 0 / 10%),
|
|
||||||
0 2px 8px rgb(64 158 255 / 30%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.switch-input:checked + .switch-slider::before {
|
|
||||||
transform: translateX(24px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.switch-content {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 0.25rem;
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.switch-title {
|
|
||||||
font-size: 0.925rem;
|
|
||||||
font-weight: 600;
|
|
||||||
color: var(--color-text-primary);
|
|
||||||
line-height: 1.4;
|
|
||||||
}
|
|
||||||
|
|
||||||
.switch-description {
|
|
||||||
font-size: 0.75rem;
|
|
||||||
color: var(--color-text-secondary);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ==================== Radio Group ==================== */
|
|
||||||
.radio-group {
|
|
||||||
display: flex;
|
|
||||||
gap: 1rem;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.radio-option {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
border: 1.5px solid var(--color-border);
|
|
||||||
border-radius: var(--radius-lg);
|
|
||||||
padding: 0.75rem 1rem;
|
|
||||||
background: var(--color-background-primary);
|
|
||||||
transition: all 0.25s ease;
|
|
||||||
gap: 0.625rem;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.radio-option:hover {
|
|
||||||
border-color: var(--color-accent);
|
|
||||||
background: rgb(64 158 255 / 8%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.radio-input {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.radio-indicator {
|
|
||||||
position: relative;
|
|
||||||
border: 2px solid var(--color-border);
|
|
||||||
border-radius: var(--radius-round);
|
|
||||||
width: 18px;
|
|
||||||
height: 18px;
|
|
||||||
background: var(--color-background-primary);
|
|
||||||
transition: all 0.25s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.radio-input:checked + .radio-indicator {
|
|
||||||
border-color: var(--color-accent);
|
|
||||||
background: var(--color-background-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.radio-input:checked + .radio-indicator::after {
|
|
||||||
position: absolute;
|
|
||||||
top: 50%;
|
|
||||||
left: 50%;
|
|
||||||
border-radius: var(--radius-round);
|
|
||||||
width: 10px;
|
|
||||||
height: 10px;
|
|
||||||
background: var(--color-accent);
|
|
||||||
content: '';
|
|
||||||
transform: translate(-50%, -50%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.radio-label {
|
|
||||||
font-size: 0.9rem;
|
|
||||||
font-weight: 500;
|
|
||||||
color: var(--color-text-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ==================== Position Grid ==================== */
|
|
||||||
.position-grid {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(3, 1fr);
|
|
||||||
gap: 0.625rem;
|
|
||||||
max-width: 320px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.position-button {
|
|
||||||
border: 1.5px solid var(--color-border);
|
|
||||||
border-radius: var(--radius-lg);
|
|
||||||
padding: 0.875rem;
|
|
||||||
font-size: 0.8rem;
|
|
||||||
font-weight: 600;
|
|
||||||
text-align: center;
|
|
||||||
color: var(--color-text-secondary);
|
|
||||||
background: var(--color-background-primary);
|
|
||||||
transition: all 0.25s ease;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.position-button:hover {
|
|
||||||
border-color: var(--color-accent);
|
|
||||||
color: var(--color-text-primary);
|
|
||||||
background: rgb(64 158 255 / 8%);
|
|
||||||
transform: translateY(-1px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.position-button.active {
|
|
||||||
border-color: var(--color-accent);
|
|
||||||
color: white;
|
|
||||||
background: var(--color-background-secondary);
|
|
||||||
box-shadow: 0 4px 12px rgb(64 158 255 / 35%);
|
|
||||||
transform: translateY(-1px);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ==================== Buttons ==================== */
|
|
||||||
.btn {
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
border: 1.5px solid var(--color-border);
|
|
||||||
border-radius: var(--radius-lg);
|
|
||||||
padding: 0.75rem 1.25rem;
|
|
||||||
font-size: 0.875rem;
|
|
||||||
font-weight: 600;
|
|
||||||
text-decoration: none;
|
|
||||||
color: var(--color-text-primary);
|
|
||||||
background: var(--color-background-primary);
|
|
||||||
transition: all 0.25s ease;
|
|
||||||
gap: 0.5rem;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn:hover {
|
|
||||||
border-color: var(--color-accent);
|
|
||||||
background: var(--color-background-secondary);
|
|
||||||
transform: translateY(-1px);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ==================== Small Text / Hints ==================== */
|
|
||||||
small {
|
|
||||||
display: block;
|
|
||||||
margin-top: 0.5rem;
|
|
||||||
border-radius: var(--radius-sm);
|
|
||||||
padding: 0.5rem 0.75rem;
|
|
||||||
font-size: 0.8rem;
|
|
||||||
color: var(--color-text-tertiary);
|
|
||||||
background: var(--color-background-secondary);
|
|
||||||
line-height: 1.5;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ==================== Watermark and Resize settings groups ==================== */
|
|
||||||
.watermark-settings,
|
|
||||||
.resize-settings {
|
|
||||||
margin-top: 1.5rem;
|
|
||||||
border-top: 1px solid var(--color-border-secondary);
|
|
||||||
padding-top: 1.5rem;
|
|
||||||
animation: slide-down 0.3s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes slide-down {
|
|
||||||
from {
|
|
||||||
opacity: 0;
|
|
||||||
transform: translateY(-10px);
|
|
||||||
}
|
|
||||||
|
|
||||||
to {
|
|
||||||
opacity: 1;
|
|
||||||
transform: translateY(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ==================== Responsive Design ==================== */
|
|
||||||
@media (width <= 768px) {
|
|
||||||
.image-process-settings {
|
|
||||||
padding: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.settings-header {
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 1rem;
|
|
||||||
align-items: stretch;
|
|
||||||
padding: 1.25rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header-icon-wrapper {
|
|
||||||
width: 48px;
|
|
||||||
height: 48px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header-text h1 {
|
|
||||||
font-size: 1.375rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header-actions {
|
|
||||||
justify-content: stretch;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header-actions .btn {
|
|
||||||
flex: 1;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tab-navigation {
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tab-indicator {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tab-button.active {
|
|
||||||
background: var(--color-accent);
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-grid {
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
}
|
|
||||||
|
|
||||||
.radio-group {
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
.color-input-group {
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: stretch;
|
|
||||||
}
|
|
||||||
|
|
||||||
.position-grid {
|
|
||||||
max-width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.section-header {
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 0.75rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Placeholder Help Styles */
|
|
||||||
.placeholder-help {
|
|
||||||
overflow-y: auto;
|
|
||||||
margin-top: 0.75rem;
|
|
||||||
border: 1px solid var(--color-border);
|
|
||||||
border-radius: var(--radius-lg);
|
|
||||||
padding: 0;
|
|
||||||
max-height: 400px;
|
|
||||||
background: var(--color-background-tertiary);
|
|
||||||
box-shadow: 0 2px 8px rgb(0 0 0 / 6%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.placeholder-category {
|
|
||||||
border-bottom: 1px solid var(--color-border);
|
|
||||||
}
|
|
||||||
|
|
||||||
.placeholder-category:last-child {
|
|
||||||
border-bottom: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.category-title {
|
|
||||||
margin: 0;
|
|
||||||
border-bottom: 1px solid var(--color-border);
|
|
||||||
padding: 0.875rem 1rem 0.5rem;
|
|
||||||
font-size: 0.875rem;
|
|
||||||
font-weight: 600;
|
|
||||||
color: var(--color-text-secondary);
|
|
||||||
background: linear-gradient(135deg, var(--color-background-secondary) 0%, var(--color-background-tertiary) 100%);
|
|
||||||
letter-spacing: 0.02em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.placeholder-grid {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
|
|
||||||
gap: 0;
|
|
||||||
padding: 0.5rem 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.placeholder-item {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
margin: 0;
|
|
||||||
border-radius: 0;
|
|
||||||
padding: 0.5rem 1rem;
|
|
||||||
font-size: 0.875rem;
|
|
||||||
line-height: 1.4;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.placeholder-item:hover {
|
|
||||||
background: color-mix(in srgb, var(--color-accent), transparent 95%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.placeholder-item code {
|
|
||||||
margin-right: 0.875rem;
|
|
||||||
border: 1px solid rgb(255 255 255 / 20%);
|
|
||||||
border-radius: var(--radius-md);
|
|
||||||
padding: 0.3rem 0.6rem;
|
|
||||||
min-width: 80px;
|
|
||||||
font-size: 1rem;
|
|
||||||
font-family: 'SF Mono', Monaco, Menlo, 'Ubuntu Mono', monospace;
|
|
||||||
font-weight: 600;
|
|
||||||
text-align: center;
|
|
||||||
color: var(--color-text-primary);
|
|
||||||
background: var(--color-background-secondary);
|
|
||||||
box-shadow: 0 1px 3px rgb(0 0 0 / 12%), 0 1px 2px rgb(0 0 0 / 24%);
|
|
||||||
letter-spacing: 0.02em;
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.placeholder-item span {
|
|
||||||
font-weight: 500;
|
|
||||||
color: var(--color-text-primary);
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Scrollbar styling for macOS feel */
|
|
||||||
.placeholder-help::-webkit-scrollbar {
|
|
||||||
width: 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.placeholder-help::-webkit-scrollbar-track {
|
|
||||||
border-radius: var(--radius-lg);
|
|
||||||
background: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
.placeholder-help::-webkit-scrollbar-thumb {
|
|
||||||
border-radius: var(--radius-lg);
|
|
||||||
background: var(--color-border);
|
|
||||||
transition: background 0.2s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.placeholder-help::-webkit-scrollbar-thumb:hover {
|
|
||||||
background: var(--color-text-secondary);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Rename specific styles */
|
|
||||||
.rename-toggle-card {
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
border: 1px solid var(--color-border);
|
|
||||||
border-radius: var(--radius-lg);
|
|
||||||
padding: 0.25rem;
|
|
||||||
background: var(--color-background-tertiary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.rename-toggle-card .switch-label {
|
|
||||||
margin: 0;
|
|
||||||
border: none;
|
|
||||||
background: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rename-format-field label {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.375rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rename-format-field label svg {
|
|
||||||
color: var(--color-accent);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|||||||
@@ -247,12 +247,12 @@
|
|||||||
<transition name="modal">
|
<transition name="modal">
|
||||||
<div
|
<div
|
||||||
v-if="imageProcessDialogVisible"
|
v-if="imageProcessDialogVisible"
|
||||||
class="fixed inset-0 z-1000 flex items-center justify-center overflow-y-auto bg-black/50 p-4 max-md:p-4"
|
class="fixed inset-0 z-1000 flex items-center justify-center overflow-y-auto bg-black/30"
|
||||||
:class="{ 'advanced-animation': enableAdvancedAnimation }"
|
:class="{ 'advanced-animation': enableAdvancedAnimation }"
|
||||||
@click.stop
|
@click.stop
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="m-auto h-[85vh] max-h-[85vh] w-[90vw] max-w-[90vw] overflow-hidden rounded-2xl border border-border-secondary bg-bg-tertiary shadow-xl"
|
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
|
@click.stop
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
@@ -273,7 +273,7 @@
|
|||||||
<XIcon :size="20" />
|
<XIcon :size="20" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="no-scrollbar max-h-[calc(90vh-90px)] overflow-y-auto max-md:p-4">
|
<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" />
|
<ImageProcessSetting :config-id="PicBedId" :current-picbed-name="defaultPicBedG" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -104,10 +104,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.placeholder-grid {
|
.placeholder-grid {
|
||||||
display: grid;
|
@apply grid grid-cols-[repeat(auto-fit,minmax(150px,1fr))] gap-0 py-2;
|
||||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
|
||||||
gap: 0;
|
|
||||||
padding: 0.5rem 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.placeholder-item {
|
.placeholder-item {
|
||||||
@@ -139,6 +136,3 @@
|
|||||||
@apply m-0 border-b border-border pt-3.5 px-4 pb-2 text-sm font-semibold text-secondary bg-[linear-gradient(180deg,var(--color-background-secondary)_0%,var(--color-background-tertiary)_100%)];
|
@apply m-0 border-b border-border pt-3.5 px-4 pb-2 text-sm font-semibold text-secondary bg-[linear-gradient(180deg,var(--color-background-secondary)_0%,var(--color-background-tertiary)_100%)];
|
||||||
}
|
}
|
||||||
|
|
||||||
.placeholder-item:hover {
|
|
||||||
background: color-mix(in srgb, var(--color-accent), transparent 95%);
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user