mirror of
https://gitee.com/wot-design-uni/wot-design-uni.git
synced 2025-12-08 01:58:52 +08:00
refactor: ♻️ Cropper组件支持支付宝小程序
This commit is contained in:
parent
0b2df6e7b2
commit
cd03fa292d
@ -1,3 +1,12 @@
|
|||||||
|
<!--
|
||||||
|
* @Author: weisheng
|
||||||
|
* @Date: 2023-06-13 11:47:12
|
||||||
|
* @LastEditTime: 2023-06-26 23:22:26
|
||||||
|
* @LastEditors: weisheng
|
||||||
|
* @Description:
|
||||||
|
* @FilePath: \wot-design-uni\src\pages\imgCropper\Index.vue
|
||||||
|
* 记得注释
|
||||||
|
-->
|
||||||
<template>
|
<template>
|
||||||
<demo-block title="基本用法" style="text-align: center">
|
<demo-block title="基本用法" style="text-align: center">
|
||||||
<wd-img-cropper
|
<wd-img-cropper
|
||||||
@ -28,16 +37,10 @@ const show = ref<boolean>(false)
|
|||||||
function upload() {
|
function upload() {
|
||||||
uni.chooseImage({
|
uni.chooseImage({
|
||||||
count: 1,
|
count: 1,
|
||||||
sizeType: ['original', 'compressed'],
|
success: (res) => {
|
||||||
sourceType: ['album', 'camera'],
|
|
||||||
success(res) {
|
|
||||||
const tempFilePaths = res.tempFilePaths[0]
|
const tempFilePaths = res.tempFilePaths[0]
|
||||||
src.value = tempFilePaths
|
src.value = tempFilePaths
|
||||||
show.value = true
|
show.value = true
|
||||||
// that.setData({
|
|
||||||
// show: true,
|
|
||||||
// src: tempFilePaths
|
|
||||||
// })
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@ -329,3 +329,20 @@ export function deepMerge(target = {}, source = {}) {
|
|||||||
}
|
}
|
||||||
return target
|
return target
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置参数
|
||||||
|
* @param path 路径(无参数)
|
||||||
|
* @param params (参数)
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export function setUrlParams(path: string, params: Record<string, string>) {
|
||||||
|
for (const key in params) {
|
||||||
|
if (path.indexOf('?') > -1) {
|
||||||
|
path = path + `&${key}=${params[key]}`
|
||||||
|
} else {
|
||||||
|
path = path + `?${key}=${params[key]}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<view @click="handleClick" :class="rootClass" :style="rootStyle">
|
<view @click="handleClick" data-eventsync="true" :class="rootClass" :style="rootStyle">
|
||||||
<image v-if="isImageUrl" class="wd-icon__image" :src="name"></image>
|
<image v-if="isImageUrl" class="wd-icon__image" :src="name"></image>
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
|
<!-- 绘制的图片canvas -->
|
||||||
<view id="wd-img-cropper" v-if="modelValue" :class="`wd-img-cropper ${customClass}`" @touchmove="preventTouchMove">
|
<view id="wd-img-cropper" v-if="modelValue" :class="`wd-img-cropper ${customClass}`" @touchmove="preventTouchMove">
|
||||||
<!-- 展示在用户面前的裁剪框 -->
|
<!-- 展示在用户面前的裁剪框 -->
|
||||||
<view class="wd-img-cropper__wrapper">
|
<view class="wd-img-cropper__wrapper">
|
||||||
@ -32,11 +33,11 @@
|
|||||||
</view>
|
</view>
|
||||||
<!-- 展示的传过来的图片: 控制图片的旋转角度(rotate)、缩放程度(imgScale)、移动位置(translate) -->
|
<!-- 展示的传过来的图片: 控制图片的旋转角度(rotate)、缩放程度(imgScale)、移动位置(translate) -->
|
||||||
<image
|
<image
|
||||||
|
:prop="isAnimation"
|
||||||
|
:change:prop="animation ? animation.setAnimation : ''"
|
||||||
class="wd-img-cropper__img"
|
class="wd-img-cropper__img"
|
||||||
:src="imgSrc"
|
:src="imgSrc"
|
||||||
:style="`width: ${picWidth ? picWidth + 'px' : 'auto'}; height: ${picHeight ? picHeight + 'px' : 'auto'}; transform: translate(${
|
:style="imageStyle"
|
||||||
imgLeft - picWidth / 2
|
|
||||||
}px, ${imgTop - picHeight / 2}px) scale(${imgScale}) rotate(${imgAngle}deg); transition-duration:${isAnimation ? 0.4 : 0}s`"
|
|
||||||
:lazy-load="false"
|
:lazy-load="false"
|
||||||
@touchstart="handleImgTouchStart"
|
@touchstart="handleImgTouchStart"
|
||||||
@touchmove="handleImgTouchMove"
|
@touchmove="handleImgTouchMove"
|
||||||
@ -48,13 +49,14 @@
|
|||||||
<!-- 绘制的图片canvas -->
|
<!-- 绘制的图片canvas -->
|
||||||
<canvas
|
<canvas
|
||||||
canvas-id="wd-img-cropper-canvas"
|
canvas-id="wd-img-cropper-canvas"
|
||||||
|
id="wd-img-cropper-canvas"
|
||||||
class="wd-img-cropper__canvas"
|
class="wd-img-cropper__canvas"
|
||||||
disable-scroll="true"
|
:disable-scroll="true"
|
||||||
:style="`width: ${Number(canvasWidth) * canvasScale}px; height: ${Number(canvasHeight) * canvasScale}px;`"
|
:style="`width: ${Number(canvasWidth) * canvasScale}px; height: ${Number(canvasHeight) * canvasScale}px;`"
|
||||||
/>
|
/>
|
||||||
<!-- 下方按钮 -->
|
<!-- 下方按钮 -->
|
||||||
<view class="wd-img-cropper__footer">
|
<view class="wd-img-cropper__footer">
|
||||||
<wd-icon v-if="!disabledRotate" name="rotate" size="24px" color="#fff" @click="handleRotate"></wd-icon>
|
<wd-icon v-if="!disabledRotate" name="rotate" size="24px" color="#fff" data-eventsync="true" @click="handleRotate"></wd-icon>
|
||||||
<view class="wd-img-cropper__footer--button">
|
<view class="wd-img-cropper__footer--button">
|
||||||
<view class="is-cancel" @click="handleCancel">{{ cancelButtonText }}</view>
|
<view class="is-cancel" @click="handleCancel">{{ cancelButtonText }}</view>
|
||||||
<wd-button size="small" :custom-style="buttonStyle" @click="handleConfirm">{{ confirmButtonText }}</wd-button>
|
<wd-button size="small" :custom-style="buttonStyle" @click="handleConfirm">{{ confirmButtonText }}</wd-button>
|
||||||
@ -65,7 +67,7 @@
|
|||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed, getCurrentInstance, ref, watch } from 'vue'
|
import { computed, getCurrentInstance, ref, watch } from 'vue'
|
||||||
import { objToStyle } from '../common/util'
|
import { addUnit, objToStyle } from '../common/util'
|
||||||
|
|
||||||
// 延时动画设置
|
// 延时动画设置
|
||||||
let CHANGE_TIME: any | null = null
|
let CHANGE_TIME: any | null = null
|
||||||
@ -230,15 +232,15 @@ watch(
|
|||||||
immediate: true
|
immediate: true
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => isAnimation.value,
|
() => isAnimation.value,
|
||||||
(newValue) => {
|
(newValue) => {
|
||||||
// 开启过渡300毫秒之后自动关闭
|
// 开启过渡300毫秒之后自动关闭
|
||||||
clearTimeout(CHANGE_TIME)
|
CHANGE_TIME && clearTimeout(CHANGE_TIME)
|
||||||
if (newValue) {
|
if (newValue) {
|
||||||
CHANGE_TIME = setTimeout(() => {
|
CHANGE_TIME = setTimeout(() => {
|
||||||
isAnimation.value = false
|
revertIsAnimation(false)
|
||||||
|
clearTimeout(CHANGE_TIME)
|
||||||
}, 300)
|
}, 300)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -261,15 +263,34 @@ const buttonStyle = computed(() => {
|
|||||||
return objToStyle(style)
|
return objToStyle(style)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const imageStyle = computed(() => {
|
||||||
|
const style: Record<string, string | number> = {
|
||||||
|
width: picWidth.value ? addUnit(picWidth.value) : 'auto',
|
||||||
|
height: picHeight.value ? addUnit(picHeight.value) : 'auto',
|
||||||
|
transform: `translate(${addUnit(imgLeft.value - picWidth.value / 2)}, ${addUnit(imgTop.value - picHeight.value / 2)}) scale(${
|
||||||
|
imgScale.value
|
||||||
|
}) rotate(${imgAngle.value}deg)`,
|
||||||
|
'transition-duration': (isAnimation.value ? 0.4 : 0) + 's'
|
||||||
|
}
|
||||||
|
return objToStyle(style)
|
||||||
|
})
|
||||||
|
|
||||||
const emit = defineEmits(['imgloaded', 'imgloaderror', 'cancel', 'confirm', 'update:modelValue'])
|
const emit = defineEmits(['imgloaded', 'imgloaderror', 'cancel', 'confirm', 'update:modelValue'])
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 逆转是否使用动画
|
||||||
|
*/
|
||||||
|
function revertIsAnimation(animation: boolean) {
|
||||||
|
isAnimation.value = animation
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description 对外暴露:控制旋转角度
|
* @description 对外暴露:控制旋转角度
|
||||||
* @param {Number} angle 角度
|
* @param {Number} angle 角度
|
||||||
*/
|
*/
|
||||||
function setRoate(angle: number) {
|
function setRoate(angle: number) {
|
||||||
if (!angle || props.disabledRotate) return
|
if (!angle || props.disabledRotate) return
|
||||||
isAnimation.value = true
|
revertIsAnimation(true)
|
||||||
imgAngle.value = angle
|
imgAngle.value = angle
|
||||||
// 设置旋转后需要判定旋转后宽高是否不符合贴边的标准
|
// 设置旋转后需要判定旋转后宽高是否不符合贴边的标准
|
||||||
detectImgPosIsEdge()
|
detectImgPosIsEdge()
|
||||||
@ -382,28 +403,30 @@ function detectImgPosIsEdge(scale?: number) {
|
|||||||
const currentScale = scale || imgScale.value
|
const currentScale = scale || imgScale.value
|
||||||
let currentImgLeft = imgLeft.value
|
let currentImgLeft = imgLeft.value
|
||||||
let currentImgTop = imgTop.value
|
let currentImgTop = imgTop.value
|
||||||
|
let currentPicWidth = picWidth.value
|
||||||
|
let currentPicHeight = picHeight.value
|
||||||
|
|
||||||
// 翻转后宽高切换
|
// 翻转后宽高切换
|
||||||
if ((currentScale / 90) % 2) {
|
if ((imgAngle.value / 90) % 2) {
|
||||||
picWidth.value = picHeight.value
|
currentPicWidth = picHeight.value
|
||||||
picHeight.value = picWidth.value
|
currentPicHeight = picWidth.value
|
||||||
}
|
}
|
||||||
// 左
|
// 左
|
||||||
currentImgLeft =
|
currentImgLeft =
|
||||||
(picWidth.value * currentScale) / 2 + cutLeft.value >= currentImgLeft ? currentImgLeft : (picWidth.value * imgScale.value) / 2 + cutLeft.value
|
(currentPicWidth * currentScale) / 2 + cutLeft.value >= currentImgLeft ? currentImgLeft : (currentPicWidth * imgScale.value) / 2 + cutLeft.value
|
||||||
// 右
|
// 右
|
||||||
currentImgLeft =
|
currentImgLeft =
|
||||||
cutLeft.value + cutWidth.value - (picWidth.value * currentScale) / 2 <= currentImgLeft
|
cutLeft.value + cutWidth.value - (currentPicWidth * currentScale) / 2 <= currentImgLeft
|
||||||
? currentImgLeft
|
? currentImgLeft
|
||||||
: cutLeft.value + cutWidth.value - (picWidth.value * currentScale) / 2
|
: cutLeft.value + cutWidth.value - (currentPicWidth * currentScale) / 2
|
||||||
// 上
|
// 上
|
||||||
currentImgTop =
|
currentImgTop =
|
||||||
(picHeight.value * currentScale) / 2 + cutTop.value >= currentImgTop ? currentImgTop : (picHeight.value * currentScale) / 2 + cutTop.value
|
(currentPicHeight * currentScale) / 2 + cutTop.value >= currentImgTop ? currentImgTop : (currentPicHeight * currentScale) / 2 + cutTop.value
|
||||||
// 下
|
// 下
|
||||||
currentImgTop =
|
currentImgTop =
|
||||||
cutTop.value + cutHeight.value - (picHeight.value * currentScale) / 2 <= currentImgTop
|
cutTop.value + cutHeight.value - (currentPicHeight * currentScale) / 2 <= currentImgTop
|
||||||
? currentImgTop
|
? currentImgTop
|
||||||
: cutTop.value + cutHeight.value - (picHeight.value * currentScale) / 2
|
: cutTop.value + cutHeight.value - (currentPicHeight * currentScale) / 2
|
||||||
|
|
||||||
imgScale.value = currentScale
|
imgScale.value = currentScale
|
||||||
imgTop.value = currentImgTop
|
imgTop.value = currentImgTop
|
||||||
@ -541,29 +564,32 @@ function handleConfirm(event) {
|
|||||||
*/
|
*/
|
||||||
function canvasToImage() {
|
function canvasToImage() {
|
||||||
const { fileType, quality, exportScale } = props
|
const { fileType, quality, exportScale } = props
|
||||||
uni.canvasToTempFilePath(
|
try {
|
||||||
{
|
uni.canvasToTempFilePath(
|
||||||
width: cutWidth.value * exportScale,
|
{
|
||||||
height: Math.round(cutHeight.value * exportScale),
|
width: cutWidth.value * exportScale,
|
||||||
destWidth: cutWidth.value * exportScale,
|
height: Math.round(cutHeight.value * exportScale),
|
||||||
destHeight: Math.round(cutHeight.value * exportScale),
|
destWidth: cutWidth.value * exportScale,
|
||||||
fileType,
|
destHeight: Math.round(cutHeight.value * exportScale),
|
||||||
quality,
|
fileType,
|
||||||
canvasId: 'wd-img-cropper-canvas',
|
quality,
|
||||||
success(res) {
|
canvasId: 'wd-img-cropper-canvas',
|
||||||
emit('update:modelValue', false)
|
success: (res) => {
|
||||||
emit('confirm', {
|
emit('confirm', {
|
||||||
tempFilePath: res.tempFilePath,
|
tempFilePath: res.tempFilePath,
|
||||||
width: cutWidth.value * exportScale,
|
width: cutWidth.value * exportScale,
|
||||||
height: cutHeight.value * exportScale
|
height: cutHeight.value * exportScale
|
||||||
})
|
})
|
||||||
|
},
|
||||||
|
complete: () => {
|
||||||
|
emit('update:modelValue', false)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
fail() {
|
proxy
|
||||||
emit('update:modelValue', false)
|
)
|
||||||
}
|
} catch (error) {
|
||||||
},
|
console.log(error)
|
||||||
proxy
|
}
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -587,6 +613,9 @@ function draw() {
|
|||||||
}
|
}
|
||||||
// drawImage 的 旋转是根据以当前图片的图片水平垂直方向为x、y轴,并在x y轴上移动
|
// drawImage 的 旋转是根据以当前图片的图片水平垂直方向为x、y轴,并在x y轴上移动
|
||||||
ctx.value!.drawImage(props.imgSrc, -width / 2, -height / 2, width, height)
|
ctx.value!.drawImage(props.imgSrc, -width / 2, -height / 2, width, height)
|
||||||
|
|
||||||
|
ctx.value!.restore()
|
||||||
|
|
||||||
// 绘制图片
|
// 绘制图片
|
||||||
ctx.value!.draw(false, () => {
|
ctx.value!.draw(false, () => {
|
||||||
canvasToImage()
|
canvasToImage()
|
||||||
@ -598,8 +627,31 @@ function draw() {
|
|||||||
draw()
|
draw()
|
||||||
}
|
}
|
||||||
function preventTouchMove() {}
|
function preventTouchMove() {}
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
revertIsAnimation
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<!-- #ifdef MP-WEIXIN || MP-QQ -->
|
||||||
|
<script module="animation" lang="wxs">
|
||||||
|
|
||||||
|
function setAnimation(newValue, oldValue, ownerInstance){
|
||||||
|
if (newValue) {
|
||||||
|
var id = ownerInstance.setTimeout(function() {
|
||||||
|
ownerInstance.callMethod('revertIsAnimation',false)
|
||||||
|
ownerInstance.clearTimeout(id)
|
||||||
|
},300)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports= {
|
||||||
|
setAnimation:setAnimation,
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<!-- #endif -->
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@import './index.scss';
|
@import './index.scss';
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user