Files
file-tool-wails/frontend/src/components/image/ImageBrightnessTool.vue
2026-06-17 08:05:20 +08:00

81 lines
6.4 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script setup>
import { ref } from 'vue'
import { ElMessage } from 'element-plus'
const props = defineProps({
filePath: String,
filePreview: String,
imageInfo: Object,
originalWidth: Number,
originalHeight: Number,
processing: Boolean,
resultInfo: Object,
outputPath: String,
config: Object,
})
const emit = defineEmits(['update:outputPath', 'process', 'selectFile'])
const brightnessVal = ref(0)
const contrastVal = ref(0)
const saturationVal = ref(0)
function buildRequest() {
return { inputPath: props.filePath, outputPath: props.outputPath, format: 'brightness', brightness: brightnessVal.value, contrast: contrastVal.value, saturation: saturationVal.value }
}
function onProcess() { emit('process', buildRequest()) }
function getFileName() { return props.filePath ? props.filePath.split(/[/\\]/).pop() : '' }
function onDragOver(e) { e.preventDefault(); e.currentTarget.classList.add('dragover') }
function onDragLeave(e) { e.currentTarget.classList.remove('dragover') }
function onDrop(e) { e.preventDefault(); e.currentTarget.classList.remove('dragover'); if (e.dataTransfer?.files?.length) { const ext = e.dataTransfer.files[0].name.split('.').pop().toLowerCase(); if (!['jpg', 'jpeg', 'png', 'gif', 'bmp', 'tiff', 'tif', 'webp', 'ico'].includes(ext)) { ElMessage.warning('请选择图片文件'); return } emit('selectFile', e.dataTransfer.files[0].path) } }
</script>
<template>
<div v-if="!filePath" class="file-drop-zone" @click="$emit('selectFile')" @dragover="onDragOver" @dragleave="onDragLeave" @drop="onDrop">
<el-icon :size="40" class="drop-icon"><UploadFilled /></el-icon>
<div class="drop-text">点击选择图片</div>
<div class="drop-hint">支持拖拽图片到此处</div>
</div>
<div v-else class="image-workspace">
<div class="image-canvas-area">
<div class="canvas-container"><div class="canvas-wrapper"><img v-if="filePreview" :src="filePreview" class="preview-img" /></div></div>
<div class="image-info-bar"><span class="image-dims">{{ originalWidth }} × {{ originalHeight }} px</span><span class="image-filename">{{ getFileName() }}</span><el-button type="danger" text size="small" @click="$emit('selectFile')"><el-icon><Delete /></el-icon></el-button></div>
</div>
<div class="image-panel">
<div class="panel-section"><div class="panel-title">调整亮度</div>
<div class="param-group"><div class="param-label">亮度: {{ brightnessVal > 0 ? '+' : '' }}{{ brightnessVal }}</div><el-slider v-model="brightnessVal" :min="-100" :max="100" :step="1" /></div>
<div class="param-group"><div class="param-label">对比度: {{ contrastVal > 0 ? '+' : '' }}{{ contrastVal }}</div><el-slider v-model="contrastVal" :min="-100" :max="100" :step="1" /></div>
<div class="param-group"><div class="param-label">饱和度: {{ saturationVal > 0 ? '+' : '' }}{{ saturationVal }}</div><el-slider v-model="saturationVal" :min="-100" :max="100" :step="1" /></div>
</div>
<div class="panel-section"><div class="panel-title">输出路径</div><div class="output-row"><el-input :value="outputPath" :placeholder="config?.defaultOutputDir ? '默认: ' + config.defaultOutputDir : '默认: 与输入文件同目录'" readonly size="small" /></div></div>
<el-button class="process-btn" type="primary" size="large" :loading="processing" :disabled="!filePath" @click="onProcess"><el-icon v-if="!processing"><Check /></el-icon>{{ processing ? '处理中...' : '开始处理' }}</el-button>
</div>
</div>
</template>
<style scoped>
.file-drop-zone { border: 2px dashed var(--glass-border); border-radius: var(--radius-lg); padding: 80px 40px; text-align: center; cursor: pointer; transition: all var(--transition-normal); background: var(--glass-bg); backdrop-filter: blur(var(--glass-blur)); -webkit-backdrop-filter: blur(var(--glass-blur)); max-width: 600px; margin: 40px auto; }
.file-drop-zone:hover, .file-drop-zone.dragover { border-color: var(--accent-primary); background: rgba(59, 130, 246, 0.05); box-shadow: 0 0 20px rgba(59, 130, 246, 0.1); }
.drop-icon { color: var(--text-muted); margin-bottom: 8px; }
.drop-text { font-size: 15px; color: var(--text-primary); margin-bottom: 4px; }
.drop-hint { font-size: 12px; color: var(--text-muted); }
.image-workspace { display: grid; grid-template-columns: 1fr 320px; gap: 20px; height: calc(100vh - 160px); max-height: 800px; }
.image-canvas-area { display: flex; flex-direction: column; background: var(--glass-bg); backdrop-filter: blur(var(--glass-blur)); -webkit-backdrop-filter: blur(var(--glass-blur)); border: 1px solid var(--glass-border); border-radius: var(--radius-lg); overflow: hidden; }
.canvas-container { flex: 1; display: flex; align-items: center; justify-content: center; background: #1a1a25; overflow: hidden; min-height: 400px; }
.canvas-wrapper { position: relative; display: inline-block; }
.preview-img { max-width: 100%; max-height: 100%; object-fit: contain; }
.image-info-bar { display: flex; align-items: center; gap: 12px; padding: 10px 16px; background: var(--glass-bg); border-top: 1px solid var(--glass-border); font-size: 13px; }
.image-dims { color: var(--text-secondary); font-family: monospace; }
.image-filename { color: var(--text-primary); flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.image-panel { display: flex; flex-direction: column; gap: 16px; overflow-y: auto; background: var(--glass-bg); backdrop-filter: blur(var(--glass-blur)); -webkit-backdrop-filter: blur(var(--glass-blur)); border: 1px solid var(--glass-border); border-radius: var(--radius-lg); padding: 20px; }
.panel-section { display: flex; flex-direction: column; gap: 4px; }
.panel-title { font-size: 14px; font-weight: 600; color: var(--text-primary); margin-bottom: 8px; padding-bottom: 8px; border-bottom: 1px solid var(--glass-border); }
.param-group { margin-bottom: 12px; }
.param-label { font-size: 13px; font-weight: 500; color: var(--text-secondary); margin-bottom: 8px; display: flex; align-items: center; gap: 6px; }
.output-row { display: flex; gap: 8px; }
.output-row .el-input { flex: 1; }
.process-btn { width: 100%; height: 48px; font-size: 15px; font-weight: 600; border-radius: var(--radius-md); background: var(--gradient-primary); border: none; transition: all var(--transition-normal); }
.process-btn:hover:not(:disabled) { box-shadow: 0 4px 20px rgba(59, 130, 246, 0.4); transform: translateY(-1px); }
.process-btn:active:not(:disabled) { transform: translateY(0) scale(0.98); }
</style>