Skip to content

Commit

Permalink
feat(图片): 添加图片裁剪功能 (#543)
Browse files Browse the repository at this point in the history
* feat(图片): 添加图片裁剪功能

* fix: 图片裁剪兼容本地图片不存在src的情况

* chore: 裁剪细节调整
  • Loading branch information
x007xyz authored Nov 15, 2024
1 parent 526f45c commit 2bbdfaa
Show file tree
Hide file tree
Showing 7 changed files with 397 additions and 3 deletions.
2 changes: 1 addition & 1 deletion .eslintrc-auto-import.json
Original file line number Diff line number Diff line change
Expand Up @@ -71,4 +71,4 @@
"watchPostEffect": true,
"watchSyncEffect": true
}
}
}
5 changes: 4 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,8 @@
"src/language"
],
"i18n-ally.sourceLanguage": "zh",
"i18n-ally.keystyle": "nested" // 需要Prettier的配置文件
"i18n-ally.keystyle": "nested",
"[vue]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
} // 需要Prettier的配置文件
}
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
"view-ui-plus": "^1.3.7",
"vite-svg-loader": "^5.1.0",
"vue": "^3.2.25",
"vue-cropper": "^1.1.4",
"vue-i18n": "9.0.0",
"vue-masonry": "^0.16.0",
"vue-router": "^4.0.16",
Expand Down
290 changes: 290 additions & 0 deletions src/components/cropperDialog.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,290 @@
<template>
<Modal
v-model="visible"
@on-ok="onOk"
@on-cancel="onCancel"
title="图片裁剪"
width="80%"
style="height: 80%"
>
<div class="main">
<Spin size="large" fix :show="loading">图片加载中...</Spin>
<div class="options">
<label>裁剪比例</label>
<div class="flex mt-2 ratio-item-wrapper">
<div class="ratio-item" :class="{ active: !fixed }" @click="changeRatio()">自由</div>
<div
class="ratio-item"
:class="{ active: fixed && fixedRatio[0] === 1 && fixedRatio[1] === 1 }"
@click="changeRatio([1, 1])"
>
<div class="ratio-item-content" style="height: 70px; width: 70px">1:1</div>
</div>
<div
class="ratio-item"
:class="{ active: fixed && fixedRatio[0] === 2 && fixedRatio[1] === 3 }"
@click="changeRatio([2, 3])"
>
<div class="ratio-item-content" style="height: 70px; width: calc(70px * 2 / 3)">
2:3
</div>
</div>
<div
class="ratio-item"
:class="{ active: fixed && fixedRatio[0] === 3 && fixedRatio[1] === 2 }"
@click="changeRatio([3, 2])"
>
<div class="ratio-item-content" style="height: calc(70px * 2 / 3); width: 70px">
3:2
</div>
</div>
<div
class="ratio-item"
:class="{ active: fixed && fixedRatio[0] === 4 && fixedRatio[1] === 3 }"
@click="changeRatio([4, 3])"
>
<div class="ratio-item-content" style="height: calc(70px * 3 / 4); width: 70px">
4:3
</div>
</div>
<div
class="ratio-item"
:class="{ active: fixed && fixedRatio[0] === 3 && fixedRatio[1] === 4 }"
@click="changeRatio([3, 4])"
>
<div class="ratio-item-content" style="width: calc(70px * 3 / 4); height: 70px">
3:4
</div>
</div>
<div
class="ratio-item"
:class="{ active: fixed && fixedRatio[0] === 16 && fixedRatio[1] === 9 }"
@click="changeRatio([16, 9])"
>
<div class="ratio-item-content" style="height: calc(70px * 9 / 16); width: 70px">
16:9
</div>
</div>
<div
class="ratio-item"
:class="{ active: fixed && fixedRatio[0] === 9 && fixedRatio[1] === 16 }"
@click="changeRatio([9, 16])"
>
<div class="ratio-item-content" style="width: calc(70px * 9 / 16); height: 70px">
9:16
</div>
</div>
</div>
<label>当前尺寸</label>
<div class="flex mt-2">
<Input
v-model="cropperWidth"
type="number"
@change="changeCropperSize('width', Number($event.target.value))"
/>
<span class="mx-2">X</span>
<Input
v-model="cropperHeight"
type="number"
@change="changeCropperSize('height', Number($event.target.value))"
/>
</div>
</div>
<div class="cropper-wrapper">
<VueCropper
ref="cropperRef"
:img="img"
:outputSize="1"
outputType="png"
autoCrop
:fixed="fixed"
:fixedNumber="fixedRatio"
centerBox
:fixedBox="false"
full
@realTime="onPreview"
/>
</div>
<div class="preview-wrapper">
<div
style="height: 100px; width: 100px; border: 1px solid rgba(59, 130, 246, 0.5)"
:style="previewStyle"
>
<div :style="previews.div" v-if="previews">
<img :src="previews.url" :style="{ ...previews.img, maxWidth: previews.img.width }" />
</div>
</div>

<div class="title">预览</div>
</div>
</div>
</Modal>
</template>

<script setup lang="ts">
import { ref } from 'vue';
import 'vue-cropper/dist/index.css';
import { VueCropper } from 'vue-cropper';
// import { blobToFile } from '../../views/creation/createShortVideo/components/framesContent/utils'
const visible = ref(false);
const cropperRef = ref();
const img = ref();
const previewStyle = ref({
width: '100px',
height: '100px',
overflow: 'hidden',
margin: '0',
});
const loading = ref(false);
const previews = ref();
const cropperWidth = ref(0);
const cropperHeight = ref(0);
function changeCropperSize(type: 'width' | 'height', value: number) {
if (fixed.value) {
if (type === 'width') {
cropperHeight.value = (value * fixedRatio.value[1]) / fixedRatio.value[0];
} else {
cropperWidth.value = (value * fixedRatio.value[0]) / fixedRatio.value[1];
}
}
nextTick(() => {
cropperRef.value.goAutoCrop(Number(cropperWidth.value), Number(cropperHeight.value));
});
}
function onPreview(_previews) {
if (_previews.w === 0 && _previews.h === 0) {
loading.value = true;
} else {
loading.value = false;
}
console.log('🚀 ~ onPreview ~ _previews:', _previews);
cropperWidth.value = _previews.w;
cropperHeight.value = _previews.h;
previewStyle.value = {
width: _previews.w + 'px',
height: _previews.h + 'px',
overflow: 'hidden',
margin: '0',
zoom: 100 / _previews.w,
};
previews.value = _previews;
}
function onCancel() {
visible.value = false;
}
const fixedRatio = ref([1, 1]);
const fixed = ref(false);
function changeRatio(ratio?: [number, number]) {
if (ratio) {
fixed.value = true;
fixedRatio.value = ratio;
// 宽度不变,按照比例修改高度
// cropperHeight.value = (cropperWidth.value * ratio[1]) / ratio[0];
} else {
fixed.value = false;
}
nextTick(() => {
cropperRef.value.goAutoCrop(9999, 9999);
});
}
let _callback = null;
function onOk() {
cropperRef.value.getCropData((data) => {
_callback && _callback(data);
visible.value = false;
});
}
defineExpose({
open(data, callback) {
img.value = data.img;
_callback = callback;
visible.value = true;
},
});
</script>

<style scoped lang="less">
.main {
display: flex;
align-items: stretch;
min-height: 600px;
position: relative;
}
.options {
width: 256px;
.mt-2 {
margin-top: 8px;
}
.flex {
display: flex;
align-items: center;
}
.mx-2 {
margin: 0 8px;
}
}
.cropper-wrapper {
flex: 1;
min-height: 100%;
padding: 0 16px;
}
.title {
text-align: center;
font-size: 14px;
color: #0a0a15;
margin-top: 12px;
}
.ratio-item-wrapper {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-bottom: 12px;
}
.ratio-item {
width: 80px;
height: 80px;
// border: 1px solid #99999f;
background-color: #fcfcfc;
border-radius: 4px;
text-align: center;
line-height: 30px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
&.active {
border: 1px solid #2d8cf0;
}
}
.ratio-item-content {
background-color: #f0f0f0;
display: flex;
align-items: center;
justify-content: center;
border-radius: 4px;
}
.preview-wrapper {
width: 100px;
}
</style>
Loading

0 comments on commit 2bbdfaa

Please sign in to comment.