81 lines
6.4 KiB
Vue
81 lines
6.4 KiB
Vue
|
|
<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>
|