forked from natuka/web.puabadge.com
修改:优化cartoon的图片裁剪比例为3:4
This commit is contained in:
1 parent
505c585772
commit
482f6a57ce
1 file changed
+275
-66
+275
-66
@@ -177,7 +177,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="photo-upload-body">
|
||||
<div class="photo-upload-body" @click="triggerPick">
|
||||
<div v-if="!picture" class="photo-upload-box">
|
||||
<div class="photo-upload-area">
|
||||
<div class="photo-upload-plus">
|
||||
@@ -195,10 +195,60 @@
|
||||
</div>
|
||||
</div>
|
||||
<img class="photo-upload-img" v-if="picture" :src="picture" alt="" srcset="">
|
||||
<div class="photo-upload-box-1">
|
||||
<h5-cropper :option="option" @getbase64Data="getbase64Data" @imgorigoinf="imgorigoinf"></h5-cropper>
|
||||
<input
|
||||
ref="fileInput"
|
||||
type="file"
|
||||
accept="image/*"
|
||||
class="photo-upload-input"
|
||||
@change="onFileChange"
|
||||
@click.stop
|
||||
>
|
||||
</div>
|
||||
|
||||
<Teleport to="body">
|
||||
<div v-if="cropping" class="ct-cropper">
|
||||
<div class="ct-cropper__stage">
|
||||
<VueCropper
|
||||
ref="cropperRef"
|
||||
:img="cropImg"
|
||||
:auto-crop="true"
|
||||
:auto-crop-width="9999"
|
||||
:auto-crop-height="9999"
|
||||
:fixed="true"
|
||||
:fixed-number="config.upload.crop.ratio"
|
||||
:fixed-box="false"
|
||||
:center-box="false"
|
||||
:can-move="true"
|
||||
:can-move-box="true"
|
||||
:can-scale="true"
|
||||
:info="true"
|
||||
:info-true="true"
|
||||
:full="false"
|
||||
:high="true"
|
||||
:enlarge="config.upload.crop.enlarge"
|
||||
:output-type="config.upload.crop.outputType"
|
||||
:output-size="1"
|
||||
:fill-color="config.upload.crop.fillColor"
|
||||
mode="contain"
|
||||
/>
|
||||
</div>
|
||||
<div class="ct-cropper__bar">
|
||||
<button class="ct-cropper__btn" type="button" @click="onCropCancel">{{ toValueWithout("取消") }}</button>
|
||||
<div class="ct-cropper__zoom">
|
||||
<button class="ct-cropper__zoom-btn" type="button" aria-label="缩小" @click="onCropZoom(-1)">-</button>
|
||||
<button class="ct-cropper__zoom-btn" type="button" aria-label="放大" @click="onCropZoom(1)">+</button>
|
||||
</div>
|
||||
<button
|
||||
class="ct-cropper__btn ct-cropper__btn--ok"
|
||||
type="button"
|
||||
:disabled="cropConfirming"
|
||||
@click="onCropConfirm"
|
||||
>
|
||||
{{ toValueWithout("确定") }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</Teleport>
|
||||
<div :style="`height: 120px;`"></div>
|
||||
<div class="design-action-bar">
|
||||
<div class="design-left">
|
||||
@@ -214,9 +264,10 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { nextTick, onMounted, onUnmounted, ref } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import H5Cropper from 'vue-cropper-h5'
|
||||
import "vue-cropper-h5/dist/style.css"
|
||||
import { VueCropper } from 'vue-cropper'
|
||||
import 'vue-cropper/dist/index.css'
|
||||
import { showLoadingToast, showToast, closeToast, showFailToast, showSuccessToast } from 'vant';
|
||||
import type { UploaderFileListItem } from 'vant'
|
||||
import * as badgeApi from '@/api/badge'
|
||||
@@ -237,25 +288,11 @@ const config = cartoonConfig
|
||||
const referPicture = ref<UploaderFileListItem[]>([])
|
||||
const router = useRouter()
|
||||
|
||||
// 初始化裁剪器配置
|
||||
const option = ref({
|
||||
...config.upload.cropOptions
|
||||
})
|
||||
|
||||
// 判断是否是APP
|
||||
const openApp = () => {
|
||||
const ua = navigator.userAgent;
|
||||
const isIOS = /iPhone|iPad|iPod/i.test(ua);
|
||||
const isAndroid = /Android/i.test(ua);
|
||||
|
||||
if (isIOS) {
|
||||
option.value.ceilbutton = true
|
||||
} else if (isAndroid) {
|
||||
option.value.ceilbutton = false
|
||||
} else {
|
||||
option.value.ceilbutton = true
|
||||
}
|
||||
}
|
||||
const fileInput = ref<HTMLInputElement | null>(null)
|
||||
const cropperRef = ref<any>(null)
|
||||
const cropping = ref(false)
|
||||
const cropImg = ref('')
|
||||
const cropConfirming = ref(false)
|
||||
|
||||
const imgurl = ref('')
|
||||
|
||||
@@ -437,31 +474,57 @@ const picture = ref(null)
|
||||
const imageWidth = ref(0);
|
||||
const imageHeight = ref(0);
|
||||
|
||||
function getbase64Data(data: string) {
|
||||
picture.value = data;
|
||||
let lockedScrollY = 0
|
||||
|
||||
const img = new Image();
|
||||
img.src = data;
|
||||
img.onload = () => {
|
||||
imageWidth.value = img.naturalWidth;
|
||||
imageHeight.value = img.naturalHeight;
|
||||
if (imageWidth.value < config.upload.minWidth || imageHeight.value < config.upload.minHeight) {
|
||||
showToast(toValueWithout(`请上传尺寸大于${config.upload.minWidth}*${config.upload.minHeight}像素的照片`))
|
||||
imgurl.value = null
|
||||
picture.value = null
|
||||
return false;
|
||||
}
|
||||
const blob = base64ToBlob(data);
|
||||
if (blob.size > config.upload.maxSize) {
|
||||
showToast(toValueWithout(`照片大小不能超过${config.upload.maxSize / 1024 / 1024}M`))
|
||||
imgurl.value = null
|
||||
picture.value = null
|
||||
return false;
|
||||
}
|
||||
imgurl.value = blob;
|
||||
function preventTouchScroll(e: TouchEvent) {
|
||||
const host = document.querySelector('.ct-cropper')
|
||||
if (host && e.target instanceof Node && host.contains(e.target)) return
|
||||
e.preventDefault()
|
||||
}
|
||||
|
||||
function setBodyScrollLocked(locked: boolean) {
|
||||
const html = document.documentElement
|
||||
const body = document.body
|
||||
if (locked) {
|
||||
lockedScrollY = window.scrollY || window.pageYOffset || 0
|
||||
html.style.overflow = 'hidden'
|
||||
body.style.overflow = 'hidden'
|
||||
body.style.position = 'fixed'
|
||||
body.style.top = `-${lockedScrollY}px`
|
||||
body.style.left = '0'
|
||||
body.style.right = '0'
|
||||
body.style.width = '100%'
|
||||
document.addEventListener('touchmove', preventTouchScroll, { passive: false })
|
||||
} else {
|
||||
html.style.overflow = ''
|
||||
body.style.overflow = ''
|
||||
body.style.position = ''
|
||||
body.style.top = ''
|
||||
body.style.left = ''
|
||||
body.style.right = ''
|
||||
body.style.width = ''
|
||||
document.removeEventListener('touchmove', preventTouchScroll)
|
||||
window.scrollTo(0, lockedScrollY)
|
||||
}
|
||||
}
|
||||
|
||||
function openCropping(dataUrl: string) {
|
||||
cropImg.value = dataUrl
|
||||
cropping.value = true
|
||||
setBodyScrollLocked(true)
|
||||
}
|
||||
|
||||
function closeCropping() {
|
||||
cropping.value = false
|
||||
cropImg.value = ''
|
||||
cropConfirming.value = false
|
||||
setBodyScrollLocked(false)
|
||||
}
|
||||
|
||||
function triggerPick() {
|
||||
fileInput.value?.click()
|
||||
}
|
||||
|
||||
function base64ToBlob(base64: string) {
|
||||
const arr = base64.split(',');
|
||||
const mime = arr[0].match(/:(.*?);/)?.[1] || 'image/jpeg';
|
||||
@@ -476,29 +539,98 @@ function base64ToBlob(base64: string) {
|
||||
});
|
||||
}
|
||||
|
||||
function imgorigoinf(data: File) {
|
||||
const img = new Image();
|
||||
const objectUrl = URL.createObjectURL(data);
|
||||
img.src = objectUrl;
|
||||
function applyCroppedData(data: string) {
|
||||
picture.value = data
|
||||
const img = new Image()
|
||||
img.src = data
|
||||
img.onload = () => {
|
||||
imageWidth.value = img.naturalWidth;
|
||||
imageHeight.value = img.naturalHeight;
|
||||
imageWidth.value = img.naturalWidth
|
||||
imageHeight.value = img.naturalHeight
|
||||
if (imageWidth.value < config.upload.minWidth || imageHeight.value < config.upload.minHeight) {
|
||||
showToast(toValueWithout(`请上传尺寸大于${config.upload.minWidth}*${config.upload.minHeight}像素的照片`))
|
||||
setTimeout(() => {
|
||||
const btn = document.querySelector('.btn')
|
||||
if (btn) {
|
||||
(btn as HTMLElement).click()
|
||||
}
|
||||
}, 1500);
|
||||
imgurl.value = null
|
||||
picture.value = null
|
||||
return
|
||||
}
|
||||
const blob = base64ToBlob(data)
|
||||
if (blob.size > config.upload.maxSize) {
|
||||
showToast(toValueWithout(`照片大小不能超过${config.upload.maxSize / 1024 / 1024}M`))
|
||||
imgurl.value = null
|
||||
picture.value = null
|
||||
return
|
||||
}
|
||||
imgurl.value = blob
|
||||
}
|
||||
img.onerror = () => {
|
||||
showToast(toValueWithout('裁剪图片加载失败'))
|
||||
imgurl.value = null
|
||||
picture.value = null
|
||||
}
|
||||
}
|
||||
|
||||
function onCropCancel() {
|
||||
closeCropping()
|
||||
}
|
||||
|
||||
/** 裁剪页缩放:正数放大、负数缩小;也支持双指捏合 */
|
||||
function onCropZoom(step: number) {
|
||||
const cropper = cropperRef.value as any
|
||||
cropper?.changeScale?.(step)
|
||||
}
|
||||
|
||||
function onCropConfirm() {
|
||||
if (cropConfirming.value) return
|
||||
const cropper = cropperRef.value as any
|
||||
if (!cropper?.getCropData) {
|
||||
showToast(toValueWithout('裁剪组件未就绪'))
|
||||
return
|
||||
}
|
||||
cropConfirming.value = true
|
||||
cropper.getCropData((data: string) => {
|
||||
closeCropping()
|
||||
if (!data) {
|
||||
showToast(toValueWithout('裁剪失败'))
|
||||
return
|
||||
}
|
||||
applyCroppedData(data)
|
||||
})
|
||||
}
|
||||
|
||||
function onFileChange(e: Event) {
|
||||
const input = e.target as HTMLInputElement
|
||||
const file = input.files?.[0]
|
||||
input.value = ''
|
||||
if (!file) return
|
||||
if (file.size > config.upload.maxSize) {
|
||||
showToast(toValueWithout(`照片大小不能超过${config.upload.maxSize / 1024 / 1024}M`))
|
||||
return
|
||||
}
|
||||
const objectUrl = URL.createObjectURL(file)
|
||||
const img = new Image()
|
||||
img.src = objectUrl
|
||||
img.onload = () => {
|
||||
imageWidth.value = img.naturalWidth
|
||||
imageHeight.value = img.naturalHeight
|
||||
URL.revokeObjectURL(objectUrl)
|
||||
if (imageWidth.value < config.upload.minWidth || imageHeight.value < config.upload.minHeight) {
|
||||
showToast(toValueWithout(`请上传尺寸大于${config.upload.minWidth}*${config.upload.minHeight}像素的照片`))
|
||||
return
|
||||
}
|
||||
const reader = new FileReader()
|
||||
reader.onload = (ev) => {
|
||||
const dataUrl = String(ev.target?.result || '')
|
||||
if (!dataUrl) {
|
||||
showToast(toValueWithout('图片读取失败'))
|
||||
return
|
||||
}
|
||||
nextTick(() => openCropping(dataUrl))
|
||||
}
|
||||
reader.onerror = () => showToast(toValueWithout('图片读取失败'))
|
||||
reader.readAsDataURL(file)
|
||||
}
|
||||
img.onerror = () => {
|
||||
URL.revokeObjectURL(objectUrl)
|
||||
showToast(toValueWithout('图片加载失败'))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -697,10 +829,13 @@ const createLog = () => {
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
openApp()
|
||||
getOrderStat()
|
||||
getSizeList()
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
if (cropping.value) closeCropping()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@@ -843,6 +978,7 @@ onMounted(() => {
|
||||
height: 80vw;
|
||||
margin: 16px auto 0 auto;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
}
|
||||
.photo-upload-box {
|
||||
margin: 16px auto 0 auto;
|
||||
@@ -856,21 +992,94 @@ onMounted(() => {
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.photo-upload-box-1 {
|
||||
width: 80vw;
|
||||
height: 70vw;
|
||||
border-radius: 12px;
|
||||
background: rgba(0, 0, 0, 0);
|
||||
.photo-upload-input {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
inset: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
.photo-upload-img {
|
||||
max-width: 80vw;
|
||||
max-height: 80vw;
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
/* 全屏裁剪层:3:4 + 可缩放 */
|
||||
.ct-cropper {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 2147483000;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: #000;
|
||||
overscroll-behavior: none;
|
||||
}
|
||||
.ct-cropper__stage {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
width: 100%;
|
||||
position: relative;
|
||||
}
|
||||
.ct-cropper__stage :deep(.vue-cropper) {
|
||||
width: 100% !important;
|
||||
height: 100% !important;
|
||||
background: #111 !important;
|
||||
touch-action: none;
|
||||
}
|
||||
.ct-cropper__stage :deep(.cropper-view-box) {
|
||||
outline: 2px solid #fff !important;
|
||||
}
|
||||
.ct-cropper__stage :deep(.crop-point) {
|
||||
background-color: #50cf54 !important;
|
||||
opacity: 0.95;
|
||||
}
|
||||
.ct-cropper__bar {
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
width: 100%;
|
||||
padding: 12px 16px calc(12px + env(safe-area-inset-bottom));
|
||||
box-sizing: border-box;
|
||||
background: #111;
|
||||
}
|
||||
.ct-cropper__zoom {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
.ct-cropper__zoom-btn {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.35);
|
||||
border-radius: 50%;
|
||||
background: rgba(255, 255, 255, 0.08);
|
||||
color: #fff;
|
||||
font-size: 22px;
|
||||
line-height: 1;
|
||||
padding: 0;
|
||||
}
|
||||
.ct-cropper__btn {
|
||||
height: 40px;
|
||||
line-height: 40px;
|
||||
padding: 0 20px;
|
||||
border: none;
|
||||
border-radius: 20px;
|
||||
background: transparent;
|
||||
color: #fff;
|
||||
font-size: 15px;
|
||||
}
|
||||
.ct-cropper__btn--ok {
|
||||
background: #50cf54;
|
||||
color: #fff;
|
||||
font-weight: 600;
|
||||
}
|
||||
.ct-cropper__btn:disabled {
|
||||
opacity: 0.6;
|
||||
}
|
||||
.photo-upload-header {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
|
||||
Reference in new issue
Block a user