AI_Painting_V2.0/src/components/dialogBox/proportion/index.vue

371 lines
8.2 KiB
Vue

<template>
<el-popover trigger="click" placement="top" :width="400">
<div class="proportion-container">
<div class="section">
<h3>选择比例</h3>
<div class="proportion-options">
<div
v-for="item in proportionOptions"
:key="item.value"
class="proportion-item"
:class="{ active: proportion === item.value }"
:style="getProportionStyle(item.value)"
@click="selectProportion(item.value)"
>
{{ item.label }}
</div>
</div>
</div>
<div class="section">
<h3>选择分辨率</h3>
<div class="resolution-options">
<div
v-for="item in resolutionOptions"
:key="item.value"
class="resolution-item"
:class="{ active: resolution === item.value }"
@click="selectResolution(item.value)"
>
{{ item.label }}
</div>
</div>
</div>
<div class="section">
<h3>尺寸(px)</h3>
<div class="size-inputs">
<div class="input-group">
<label>W</label>
<input type="number" v-model.number="width" @input="updateWidth">
</div>
<div class="lock-icon"><img src="@/assets/dialog/lock.svg" alt=""></div>
<div class="input-group">
<label>H</label>
<input type="number" v-model.number="height" @input="updateHeight">
</div>
</div>
</div>
</div>
<template #reference>
<div class="choice-btn">
<img src="@/assets/dialog/proportion.svg" alt="" style="width: 16px;">
<span>{{ proportion }}</span>
</div>
</template>
</el-popover>
</template>
<script setup>
import { computed, ref, watch } from 'vue'
const props = defineProps({
modelValue: {
type: String,
default: '1:1'
},
resolution: {
type: String,
default: '2k'
}
})
const emit = defineEmits(['update:modelValue', 'update:resolution', 'update:width', 'update:height'])
const proportion = computed({
get: () => props.modelValue,
set: (value) => emit('update:modelValue', value)
})
const resolution = computed({
get: () => props.resolution,
set: (value) => emit('update:resolution', value)
})
const proportionOptions = [
{ value: '智能', label: '智能' },
{ value: '21:9', label: '21:9' },
{ value: '16:9', label: '16:9' },
{ value: '3:2', label: '3:2' },
{ value: '4:3', label: '4:3' },
{ value: '1:1', label: '1:1' },
{ value: '3:4', label: '3:4' },
{ value: '2:3', label: '2:3' },
{ value: '9:16', label: '9:16' }
]
const resolutionOptions = [
{ value: '1k', label: '标清 1K' },
{ value: '2k', label: '高清 2K' },
{ value: '4k', label: '超清 4K' }
]
const width = ref(2048)
const height = ref(2048)
const selectProportion = (value) => {
proportion.value = value
updateDimensionsByProportion(value)
}
const selectResolution = (value) => {
resolution.value = value
updateDimensionsByResolution(value)
}
const updateDimensionsByProportion = (proportionValue) => {
if (proportionValue === '智能') {
return
}
const [w, h] = proportionValue.split(':').map(Number)
const aspectRatio = w / h
if (width.value > height.value) {
height.value = Math.round(width.value / aspectRatio)
} else {
width.value = Math.round(height.value * aspectRatio)
}
emitUpdateDimensions()
}
const updateDimensionsByResolution = (resolutionValue) => {
let baseSize
switch (resolutionValue) {
case '1k':
baseSize = 1024
break
case '2k':
baseSize = 2048
break
case '4k':
baseSize = 4096
break
default:
baseSize = 2048
}
if (proportion.value === '智能') {
width.value = baseSize
height.value = baseSize
} else {
const [w, h] = proportion.value.split(':').map(Number)
const aspectRatio = w / h
if (aspectRatio > 1) {
width.value = baseSize
height.value = Math.round(baseSize / aspectRatio)
} else {
height.value = baseSize
width.value = Math.round(baseSize * aspectRatio)
}
}
emitUpdateDimensions()
}
const updateWidth = () => {
if (proportion.value !== '智能') {
const [w, h] = proportion.value.split(':').map(Number)
const aspectRatio = w / h
height.value = Math.round(width.value / aspectRatio)
}
emitUpdateDimensions()
}
const updateHeight = () => {
if (proportion.value !== '智能') {
const [w, h] = proportion.value.split(':').map(Number)
const aspectRatio = w / h
width.value = Math.round(height.value * aspectRatio)
}
emitUpdateDimensions()
}
const emitUpdateDimensions = () => {
emit('update:width', width.value)
emit('update:height', height.value)
}
const getProportionStyle = (value) => {
if (value === '智能') {
return {
'--width': '20px',
'--height': '20px'
}
}
const [w, h] = value.split(':').map(Number)
const aspectRatio = w / h
const baseSize = 20
if (aspectRatio > 1) {
return {
'--width': `${baseSize}px`,
'--height': `${Math.round(baseSize / aspectRatio)}px`
}
} else {
return {
'--width': `${Math.round(baseSize * aspectRatio)}px`,
'--height': `${baseSize}px`
}
}
}
watch(() => [props.modelValue, props.resolution], () => {
updateDimensionsByResolution(resolution.value)
}, { immediate: true })
</script>
<style lang="less" scoped>
.choice-btn{
display: flex;
height: 40px;
padding: 0 15px;
justify-content: center;
align-items: center;
gap: 5px;
border-radius: 10px;
border: 1px solid rgba(0, 0, 0, 0.10);
background: #ffffff;
cursor: pointer;
position: relative;
}
.choice-btn:hover{
background: #E5E7EB;
}
.proportion-container{
padding: 10px;
}
.section{
margin-bottom: 20px;
&:last-child{
margin-bottom: 0;
}
h3{
font-size: 14px;
font-weight: 500;
margin-bottom: 12px;
color: #666;
}
}
.proportion-options{
display: flex;
flex-wrap: nowrap;
justify-content: space-between;
margin-bottom: 16px;
background-color: #F8F9FA;
padding: 5px;
}
.proportion-item{
display: flex;
flex-direction: column;
align-items: center;
justify-content: flex-end;
gap: 4px;
padding: 5px;
width: auto;
cursor: pointer;
font-size: 12px;
transition: all 0.2s ease;
border-radius: 5px;
text-align: bottom;
&::before{
content: '';
width: var(--width, 20px);
height: var(--height, 20px);
background: #f0f0f0;
border-radius: 4px;
transition: all 0.2s ease;
}
&:hover{
background: #e0e0e0;
}
&.active{
background: #ffffff;
}
}
.resolution-options{
display: flex;
padding: 5px;
align-items: center;
align-self: stretch;
border-radius: 10px;
background: #F8F9FA;
gap: 10px;
}
.resolution-item{
flex: 1;
padding: 10px;
border-radius: 8px;
cursor: pointer;
font-size: 14px;
text-align: center;
transition: all 0.2s ease;
background: #f5f5f5;
color: #666;
&:hover{
background: #e0e0e0;
}
&.active{
background: #ffffff;
color: #000000;
font-weight: 500;
}
}
.size-inputs{
display: flex;
align-items: center;
gap: 10px;
}
.input-group{
flex: 1;
position: relative;
label{
position: absolute;
top: 50%;
left: 12px;
transform: translateY(-50%);
font-size: 14px;
color: #666;
pointer-events: none;
}
input{
width: 100%;
padding: 12px 12px 12px 30px;
border: 1px solid #e0e0e0;
border-radius: 8px;
font-size: 14px;
background: #f9f9f9;
&:focus{
outline: none;
border-color: #1890ff;
box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2);
}
}
}
.lock-icon{
font-size: 16px;
color: #999;
cursor: pointer;
&:hover{
color: #666;
}
}
</style>