125 lines
9.0 KiB
Vue
125 lines
9.0 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 angleSlider = ref(0)
|
||
const rotationBgColor = ref('white')
|
||
const flipH = ref(false)
|
||
const flipV = ref(false)
|
||
|
||
function buildRequest() {
|
||
return {
|
||
inputPath: props.filePath,
|
||
outputPath: props.outputPath,
|
||
format: 'rotate',
|
||
angle: angleSlider.value,
|
||
bgColor: rotationBgColor.value,
|
||
flipH: flipH.value,
|
||
flipV: flipV.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">角度: {{ angleSlider }}°</div>
|
||
<el-slider v-model="angleSlider" :min="0" :max="360" :step="1" />
|
||
<div class="slider-hint"><span>0°</span><span>180°</span><span>360°</span></div>
|
||
</div>
|
||
<div class="param-group">
|
||
<div class="param-label">背景颜色</div>
|
||
<div class="tag-group">
|
||
<div class="tag-option" :class="{ active: rotationBgColor === 'white' }" @click="rotationBgColor = 'white'"><span class="bg-mini-swatch" style="background:#fff"></span>白色</div>
|
||
<div class="tag-option" :class="{ active: rotationBgColor === 'transparent' }" @click="rotationBgColor = 'transparent'"><span class="bg-mini-swatch bg-checker"></span>透明</div>
|
||
<div class="tag-option" :class="{ active: rotationBgColor === 'black' }" @click="rotationBgColor = 'black'"><span class="bg-mini-swatch" style="background:#1a1a1a"></span>黑色</div>
|
||
</div>
|
||
</div>
|
||
<div class="param-group">
|
||
<div class="param-label">翻转</div>
|
||
<div class="flip-btns">
|
||
<button class="flip-btn" :class="{ active: flipH }" @click="flipH = !flipH"><el-icon><DCaret /></el-icon> 水平</button>
|
||
<button class="flip-btn" :class="{ active: flipV }" @click="flipV = !flipV"><el-icon style="transform:rotate(90deg)"><DCaret /></el-icon> 垂直</button>
|
||
</div>
|
||
</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; }
|
||
.tag-group { display: flex; gap: 6px; flex-wrap: wrap; }
|
||
.tag-option { display: flex; align-items: center; gap: 4px; padding: 7px 14px; border-radius: var(--radius-sm); font-size: 13px; font-weight: 500; cursor: pointer; background: var(--bg-surface); color: var(--text-secondary); border: 1px solid var(--glass-border); transition: all var(--transition-normal); user-select: none; }
|
||
.tag-option:hover { color: var(--text-primary); border-color: rgba(255, 255, 255, 0.15); background: rgba(255, 255, 255, 0.05); }
|
||
.tag-option.active { background: var(--accent-primary); color: #ffffff; border-color: var(--accent-primary); box-shadow: 0 2px 8px rgba(59, 130, 246, 0.3); }
|
||
.slider-hint { display: flex; justify-content: space-between; font-size: 11px; color: var(--text-muted); margin-top: 4px; }
|
||
.flip-btns { display: flex; gap: 8px; }
|
||
.flip-btn { display: inline-flex; align-items: center; gap: 6px; padding: 8px 14px; border-radius: var(--radius-sm); font-size: 13px; cursor: pointer; background: var(--bg-surface); border: 1px solid var(--glass-border); color: var(--text-secondary); transition: all var(--transition-normal); }
|
||
.flip-btn.active { background: var(--accent-primary); color: #fff; border-color: var(--accent-primary); }
|
||
.flip-btn:hover:not(.active) { color: var(--text-primary); border-color: rgba(255, 255, 255, 0.15); }
|
||
.bg-mini-swatch { width: 14px; height: 14px; border-radius: 4px; border: 1px solid rgba(255, 255, 255, 0.1); display: inline-block; }
|
||
.bg-checker { background-color: #fff; background-image: linear-gradient(45deg, #ccc 25%, transparent 25%, transparent 75%, #ccc 75%, #ccc), linear-gradient(45deg, #ccc 25%, transparent 25%, transparent 75%, #ccc 75%, #ccc); background-size: 8px 8px; background-position: 0 0, 4px 4px; }
|
||
.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>
|