373 lines
8.2 KiB
Vue
373 lines
8.2 KiB
Vue
<template>
|
|
<Popover 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>
|
|
</Popover>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { computed, ref, watch } from 'vue'
|
|
import Popover from '@/components/Popover/index.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>
|