暂存
This commit is contained in:
204
components/addCerInfo.vue
Normal file
204
components/addCerInfo.vue
Normal file
@@ -0,0 +1,204 @@
|
|||||||
|
<template>
|
||||||
|
<u-popup key="4" :show="showSubmitInfoBlank" :round="10" @close="closeManager">
|
||||||
|
<view class="" style="height:30vh; overflow-y:scroll; padding: 20rpx;">
|
||||||
|
<h3 style="margin-bottom: 20rpx; text-align: center;">
|
||||||
|
完善资料
|
||||||
|
</h3>
|
||||||
|
<view class="">
|
||||||
|
<uni-forms :modelValue="submitInfo" :rules="submitInfoRules" ref="submitInfoForm">
|
||||||
|
<uni-forms-item label="姓 名" name="name" v-if="submitInfo.opName == 'name'">
|
||||||
|
<uni-easyinput type="text" v-model="submitInfo.name" placeholder="请输入姓名" />
|
||||||
|
</uni-forms-item>
|
||||||
|
<uni-forms-item label="证件照" name="photo" v-if="submitInfo.opName == 'photo'">
|
||||||
|
<view class="flex_box align-items_box">
|
||||||
|
<view class="flex_box align-items_box">
|
||||||
|
<image @click="preveImg(submitInfo.photo)" v-if="submitInfo.photo && submitInfo.photo != ''"
|
||||||
|
:src="submitInfo.photo" mode="widthFix" style="width:100rpx; height:140rpx;"></image>
|
||||||
|
<text v-else>请上传</text>
|
||||||
|
<input v-show="false" type="text" v-model="submitInfo.photo" />
|
||||||
|
</view>
|
||||||
|
<view class="flex_box align-items_box" style="padding-left: 20rpx;">
|
||||||
|
<text class="small_btn border_radius_10"
|
||||||
|
@click="choosePicBtn" style="display: block; height: 30px;">{{submitInfo.photo && submitInfo.photo != '' ? '修改照片':'选择照片'}}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</uni-forms-item>
|
||||||
|
</uni-forms>
|
||||||
|
<view class="btn_box"><button @click="submitForm">保 存</button></view>
|
||||||
|
<!-- <view class="tips">
|
||||||
|
<text>注:姓名处,请填写您本人的真实姓名,照片出推荐上传蓝底证件存照,将作为证书上照片使用,无其他用途,一经生成无法更改。</text>
|
||||||
|
</view> -->
|
||||||
|
<!-- <button @click="submitForm">提 交</button> -->
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<qf-image-cropper ref="qfImageCropper" v-if="showCropper" :width="245" :height="280" :radius="0"
|
||||||
|
@crop="handleCrop">
|
||||||
|
<template slot="slotNamess" slot-scope="slotProps"><text @click="cancleChoose" class="cancelBtn small_btn border_radius_10">取消</text></template>
|
||||||
|
</qf-image-cropper>
|
||||||
|
</view>
|
||||||
|
</u-popup>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import permission from "@/js_sdk/wa-permission/permission.js"
|
||||||
|
import $http from '@/config/requestConfig.js';
|
||||||
|
// 裁切工具
|
||||||
|
import QfImageCropper from '@/uni_modules/qf-image-cropper/components/qf-image-cropper/qf-image-cropper.vue';
|
||||||
|
export default {
|
||||||
|
props:['submitInfo'],
|
||||||
|
name:"addCerInfo",
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
showSubmitInfoBlank:true,
|
||||||
|
showCropper:false,
|
||||||
|
submitInfoRules:{
|
||||||
|
// name: {
|
||||||
|
// rules: [{
|
||||||
|
// required: true,
|
||||||
|
// errorMessage: '请输入实名制姓名',
|
||||||
|
// }
|
||||||
|
|
||||||
|
// ]
|
||||||
|
// },
|
||||||
|
// img: {
|
||||||
|
// rules: [{
|
||||||
|
// required: true,
|
||||||
|
// errorMessage: '请上传证书所用的证件照片',
|
||||||
|
// }
|
||||||
|
|
||||||
|
// ]
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
},methods:{
|
||||||
|
closeManager(){
|
||||||
|
this.$emit('close')
|
||||||
|
},
|
||||||
|
submitForm(){
|
||||||
|
// this.$emit('submit')
|
||||||
|
// this.$refs.submitInfoForm.validate().then(res => {
|
||||||
|
$http.request({
|
||||||
|
url: "common/user/updateUser",
|
||||||
|
method: "POST", // POST、GET、PUT、DELETE,具体说明查看官方文档
|
||||||
|
data: {
|
||||||
|
"id": this.submitInfo.userId,
|
||||||
|
'name':this.submitInfo.name,
|
||||||
|
'photo':this.submitInfo.photo
|
||||||
|
},
|
||||||
|
header: { //默认 无 说明:请求头
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.then(res => {
|
||||||
|
uni.showToast({
|
||||||
|
title: '操作成功',
|
||||||
|
icon: 'success'
|
||||||
|
})
|
||||||
|
this.closeManager('success')
|
||||||
|
}).catch(e => {
|
||||||
|
// console.log('表单错误信息:', err);
|
||||||
|
uni.showToast({
|
||||||
|
title: e.errMsg,
|
||||||
|
icon: 'none'
|
||||||
|
})
|
||||||
|
});
|
||||||
|
// }).catch(err => {
|
||||||
|
// console.log('表单错误信息:', err);
|
||||||
|
// uni.showToast({
|
||||||
|
// title: '页面有未填写的内容哦',
|
||||||
|
// icon: 'none'
|
||||||
|
// })
|
||||||
|
// })
|
||||||
|
},
|
||||||
|
async choosePicBtn() {
|
||||||
|
let ss = await this.checkPermision()
|
||||||
|
console.log('进入选择照片了吗');
|
||||||
|
this.showCropper = true
|
||||||
|
this.$refs.qfImageCropper.chooseImage({
|
||||||
|
sourceType: ['album']
|
||||||
|
});
|
||||||
|
},
|
||||||
|
async checkPermision() {
|
||||||
|
var result = await permission.premissionCheck("CAMERA_EXTERNAL_STORAGE")
|
||||||
|
if (result != 1) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
preveImg(url){
|
||||||
|
console.log('dianjile fangda tu');
|
||||||
|
uni.previewImage({
|
||||||
|
urls: [url],
|
||||||
|
current: 0
|
||||||
|
});
|
||||||
|
},
|
||||||
|
async handleCrop(e) {
|
||||||
|
var url = undefined
|
||||||
|
// uni.previewImage({
|
||||||
|
// urls: [e.tempFilePath],
|
||||||
|
// current: 0
|
||||||
|
// });
|
||||||
|
console.log('截取的图片', e.tempFilePath);
|
||||||
|
var dd = await this.uploadImg(e)
|
||||||
|
// var ss = JSON.stringify(dd) // 转成字符串
|
||||||
|
var cc = JSON.parse(dd.data)
|
||||||
|
if(cc && cc.url){
|
||||||
|
console.log('**********************',cc);
|
||||||
|
this.showCropper = false
|
||||||
|
this.submitInfo.photo = cc.url
|
||||||
|
}
|
||||||
|
console.log('外面看的',this.submitInfo.photo, cc.url);
|
||||||
|
},
|
||||||
|
|
||||||
|
uploadImg(e) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
uni.showLoading({
|
||||||
|
title: '正在上传'
|
||||||
|
})
|
||||||
|
uni.uploadFile({
|
||||||
|
url: this.$baseUrl + "oss/fileoss",
|
||||||
|
filePath: e.tempFilePath,
|
||||||
|
name: 'file',
|
||||||
|
success: (uploadFileRes) => {
|
||||||
|
console.log('上传结果', uploadFileRes.data);
|
||||||
|
uni.hideLoading()
|
||||||
|
uni.showToast({
|
||||||
|
title: '上传成功',
|
||||||
|
icon: 'success'
|
||||||
|
})
|
||||||
|
resolve(uploadFileRes)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})
|
||||||
|
},
|
||||||
|
/** 关闭图片选择 */
|
||||||
|
cancleChoose(){
|
||||||
|
console.log('点了关闭');
|
||||||
|
this.showCropper = false
|
||||||
|
},
|
||||||
|
},
|
||||||
|
components:{
|
||||||
|
QfImageCropper
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@import '@/style/mixin.scss';
|
||||||
|
.cancelBtn{background-color: #fff; z-index: 100; position:absolute; top: 30rpx; right: 30rpx;}
|
||||||
|
.tips{color: #999;
|
||||||
|
text{font-size:28rpx;}
|
||||||
|
}
|
||||||
|
.small_btn{color: #55aaff; border: #55aaff 1px solid ;}
|
||||||
|
.btn_box {
|
||||||
|
margin-top: 70rpx;
|
||||||
|
padding-bottom: 20rpx;
|
||||||
|
|
||||||
|
button {
|
||||||
|
font-size: 32rpx;
|
||||||
|
@include theme('btn_bg') color: #fff;
|
||||||
|
height: 80rpx;
|
||||||
|
line-height: 80rpx;
|
||||||
|
border-radius: 50rpx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -12,8 +12,8 @@
|
|||||||
"src" : "图片路径"
|
"src" : "图片路径"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"versionName" : "1.0.19",
|
"versionName" : "1.0.20",
|
||||||
"versionCode" : 1019,
|
"versionCode" : 1020,
|
||||||
"app-plus" : {
|
"app-plus" : {
|
||||||
"nvueCompiler" : "weex",
|
"nvueCompiler" : "weex",
|
||||||
"compatible" : {
|
"compatible" : {
|
||||||
|
|||||||
@@ -54,11 +54,12 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
">
|
">
|
||||||
<view :class="`${isHideCourseInfo ? 'hidden2' : ''}`"
|
<view :class="`${isHideCourseInfo ? 'hidden2' : ''}`"
|
||||||
style="width: calc(100% - 50rpx); font-size: 30rpx;" v-html="course.content">
|
style="font-size: 30rpx; text-align: justify;" v-html="course.content">
|
||||||
</view>
|
</view>
|
||||||
<text @click="isHideCourseInfo = !isHideCourseInfo"
|
<text @click="isHideCourseInfo = !isHideCourseInfo"
|
||||||
style="
|
style="
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
background-color: #f2f8ff; padding: 4rpx ;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
color: #838588;
|
color: #838588;
|
||||||
|
|||||||
@@ -32,11 +32,7 @@
|
|||||||
<view class="right_content">
|
<view class="right_content">
|
||||||
<view v-if="slotProps.row.type == 'sex'">
|
<view v-if="slotProps.row.type == 'sex'">
|
||||||
<text
|
<text
|
||||||
v-if="
|
v-if="userData[slotProps.row.indexValue]">
|
||||||
userData[slotProps.row.indexValue]
|
|
||||||
|
|
||||||
"
|
|
||||||
>
|
|
||||||
{{
|
{{
|
||||||
userData[slotProps.row.indexValue] == 2 ? "女" : "男"
|
userData[slotProps.row.indexValue] == 2 ? "女" : "男"
|
||||||
}}</text
|
}}</text
|
||||||
@@ -57,6 +53,26 @@
|
|||||||
>未设置</view
|
>未设置</view
|
||||||
>
|
>
|
||||||
</view>
|
</view>
|
||||||
|
<view v-if="slotProps.row.type == 'name'">
|
||||||
|
<text v-if="userData[slotProps.row.indexValue]">
|
||||||
|
{{ userData[slotProps.row.indexValue] }}</text
|
||||||
|
>
|
||||||
|
<view
|
||||||
|
v-else
|
||||||
|
@click="handleClickRightContent(slotProps.row, 'bind')"
|
||||||
|
>未设置</view
|
||||||
|
>
|
||||||
|
</view>
|
||||||
|
<view v-if="slotProps.row.type == 'photo'">
|
||||||
|
<view
|
||||||
|
v-if="userData[slotProps.row.indexValue] && userData[slotProps.row.indexValue] != null"
|
||||||
|
@click="handleClickRightContent(slotProps.row, 'bind')"
|
||||||
|
>修改</view>
|
||||||
|
<view
|
||||||
|
v-else
|
||||||
|
@click="handleClickRightContent(slotProps.row, 'bind')"
|
||||||
|
>上传</view>
|
||||||
|
</view>
|
||||||
<view v-if="slotProps.row.type == 'tel'">
|
<view v-if="slotProps.row.type == 'tel'">
|
||||||
<text v-if="userData[slotProps.row.indexValue]">
|
<text v-if="userData[slotProps.row.indexValue]">
|
||||||
{{ userData[slotProps.row.indexValue] }}</text
|
{{ userData[slotProps.row.indexValue] }}</text
|
||||||
@@ -93,20 +109,15 @@
|
|||||||
>
|
>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
|
|
||||||
<u-icon
|
<u-icon
|
||||||
v-if="
|
v-if="
|
||||||
userData[slotProps.row.indexValue]&&userData[slotProps.row.indexValue]!=''
|
userData[slotProps.row.indexValue]&&userData[slotProps.row.indexValue]!='' "
|
||||||
|
|
||||||
"
|
|
||||||
class="editIcon"
|
class="editIcon"
|
||||||
name="edit-pen-fill"
|
name="edit-pen-fill"
|
||||||
color="#258feb"
|
color="#258feb"
|
||||||
size="22"
|
size="22"
|
||||||
@click.native.stop="handleClickRightContent(slotProps.row)"
|
@click.native.stop="handleClickRightContent(slotProps.row)"
|
||||||
></u-icon>
|
></u-icon>
|
||||||
|
|
||||||
|
|
||||||
</view>
|
</view>
|
||||||
<text class="fdButtonBox aui-text-success">{{
|
<text class="fdButtonBox aui-text-success">{{
|
||||||
slotProps.row.content
|
slotProps.row.content
|
||||||
@@ -125,67 +136,6 @@
|
|||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- <view class="tabulate">
|
|
||||||
<view class="per_list">
|
|
||||||
<text class="biaoti">手机号</text>
|
|
||||||
<text class="neirong">{{ userMsage.tel }}</text>
|
|
||||||
<text
|
|
||||||
class="marPer"
|
|
||||||
v-if="!userMsage.tel"
|
|
||||||
@click="openTel()"
|
|
||||||
style="background-color: #ed901d"
|
|
||||||
>点击绑定</text
|
|
||||||
>
|
|
||||||
</view>
|
|
||||||
<view class="per_list">
|
|
||||||
<text class="biaoti">邮箱</text>
|
|
||||||
<text class="neirong">{{ userMsage.email }}</text>
|
|
||||||
|
|
||||||
<text
|
|
||||||
class="marPer"
|
|
||||||
v-if="!userMsage.email"
|
|
||||||
@click="openEmail"
|
|
||||||
style="background-color: #ed901d"
|
|
||||||
>点击绑定</text
|
|
||||||
>
|
|
||||||
</view>
|
|
||||||
<view class="per_list per_list_arrow" @click="avatarShow = true">
|
|
||||||
<text class="biaoti" style="margin-top: 40rpx">头像</text>
|
|
||||||
<text class="neirong" style="margin-top: 0">
|
|
||||||
<image :src="userMsage.avatar" class="per_mes_img"></image>
|
|
||||||
</text>
|
|
||||||
</view>
|
|
||||||
<view class="per_list per_list_arrow" @click="nicknameShow = true">
|
|
||||||
<text class="biaoti">昵称</text>
|
|
||||||
<text class="neirong">{{ userMsage.nickname }}</text>
|
|
||||||
</view>
|
|
||||||
<view class="per_list per_list_arrow" @click="passwordShow = true">
|
|
||||||
<text class="biaoti">密码</text>
|
|
||||||
<text v-if="userMsage.YNpass != ''" class="neirong">点击修改</text>
|
|
||||||
<text
|
|
||||||
class="marPer"
|
|
||||||
v-if="userMsage.YNpass != ''"
|
|
||||||
style="background-color: #92c78c; margin-right: 40rpx"
|
|
||||||
>已设定</text
|
|
||||||
>
|
|
||||||
<text v-if="userMsage.YNpass == ''" class="neirong">去设置</text>
|
|
||||||
<text
|
|
||||||
class="marPer"
|
|
||||||
v-if="userMsage.YNpass == ''"
|
|
||||||
style="background-color: #9d9d9d; margin-right: 40rpx"
|
|
||||||
>未设定</text
|
|
||||||
>
|
|
||||||
</view>
|
|
||||||
<view class="per_list per_list_arrow" @click="ageShow = true">
|
|
||||||
<text class="biaoti">年龄</text>
|
|
||||||
<text class="neirong">{{ userMsage.age }}</text>
|
|
||||||
</view>
|
|
||||||
<view class="per_list per_list_arrow" @click="sexShow = true">
|
|
||||||
<text class="biaoti">性别</text>
|
|
||||||
<text class="neirong" v-if="editForm.sex == 1">男</text>
|
|
||||||
<text class="neirong" v-if="editForm.sex == 0">女</text>
|
|
||||||
</view>
|
|
||||||
</view> -->
|
|
||||||
<u-popup :show="editModalShow" :round="10" @close="">
|
<u-popup :show="editModalShow" :round="10" @close="">
|
||||||
<view class="tanchu">
|
<view class="tanchu">
|
||||||
<view class="dp_title">{{ editModalTitle }}</view>
|
<view class="dp_title">{{ editModalTitle }}</view>
|
||||||
@@ -398,14 +348,15 @@
|
|||||||
<view @click="closeModal()" class="dp_canBtn"> 取消</view>
|
<view @click="closeModal()" class="dp_canBtn"> 取消</view>
|
||||||
</view>
|
</view>
|
||||||
</u-popup>
|
</u-popup>
|
||||||
|
<addCerInfo v-if="showSubmitInfoBlank" :submitInfo="submitInfo" @close="closeManager()" ></addCerInfo>
|
||||||
<!-- <music-play :playData="playData"></music-play> -->
|
<!-- <music-play :playData="playData"></music-play> -->
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import addCerInfo from "@/components/addCerInfo.vue";
|
||||||
import permission from "@/js_sdk/wa-permission/permission.js"
|
import permission from "@/js_sdk/wa-permission/permission.js"
|
||||||
import musicPlay from "@/components/music.vue";
|
// import musicPlay from "@/components/music.vue";
|
||||||
import $http from "@/config/requestConfig.js";
|
import $http from "@/config/requestConfig.js";
|
||||||
var clear;
|
var clear;
|
||||||
import { mapState } from "vuex";
|
import { mapState } from "vuex";
|
||||||
@@ -424,6 +375,9 @@ var enoughRegex = new RegExp("(?=.{8,}).*", "g");
|
|||||||
export default {
|
export default {
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
showCropper: false,
|
||||||
|
submitInfo: {},
|
||||||
|
showSubmitInfoBlank:false,
|
||||||
playData: {},
|
playData: {},
|
||||||
userData: {},
|
userData: {},
|
||||||
isPassWordPhone: true,
|
isPassWordPhone: true,
|
||||||
@@ -461,7 +415,16 @@ export default {
|
|||||||
indexValue: "sex",
|
indexValue: "sex",
|
||||||
type: "sex",
|
type: "sex",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: "真实姓名",
|
||||||
|
indexValue: "name",
|
||||||
|
type: "name",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "证件照",
|
||||||
|
indexValue: "photo",
|
||||||
|
type: "photo",
|
||||||
|
},
|
||||||
// {
|
// {
|
||||||
|
|
||||||
// "title": "清楚缓存",
|
// "title": "清楚缓存",
|
||||||
@@ -551,7 +514,8 @@ export default {
|
|||||||
this.getCountyCode();
|
this.getCountyCode();
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
musicPlay,
|
// musicPlay,
|
||||||
|
addCerInfo
|
||||||
},
|
},
|
||||||
//方法
|
//方法
|
||||||
methods: {
|
methods: {
|
||||||
@@ -561,13 +525,33 @@ export default {
|
|||||||
this.editForm.quCode = this.quCodeList[0].value;
|
this.editForm.quCode = this.quCodeList[0].value;
|
||||||
this.OpenClear();
|
this.OpenClear();
|
||||||
},
|
},
|
||||||
|
closeManager(data) {
|
||||||
|
console.log('成功的返回值',data);
|
||||||
|
this.submitInfo = {}
|
||||||
|
this.showSubmitInfoBlank = false
|
||||||
|
this.getData()
|
||||||
|
|
||||||
|
},
|
||||||
closeModal() {
|
closeModal() {
|
||||||
this.currentEditType = null;
|
this.currentEditType = null;
|
||||||
this.editForm = {};
|
this.editForm = {};
|
||||||
this.editModalShow = false;
|
this.editModalShow = false;
|
||||||
},
|
},
|
||||||
handleClickRightContent(row, type) {
|
handleClickRightContent(row, type) {
|
||||||
|
|
||||||
this.OpenClear();
|
this.OpenClear();
|
||||||
|
if(row.indexValue == 'photo' || row.indexValue == 'name'){
|
||||||
|
if(row.indexValue == 'photo'){
|
||||||
|
this.submitInfo.photo = this.userData.photo
|
||||||
|
this.submitInfo.opName = 'photo'
|
||||||
|
}else if(row.indexValue == 'name') {
|
||||||
|
this.submitInfo.name = this.userData.name
|
||||||
|
this.submitInfo.opName = 'name'
|
||||||
|
}
|
||||||
|
this.submitInfo.userId = this.userInfo.id
|
||||||
|
this.showSubmitInfoBlank = true
|
||||||
|
return
|
||||||
|
}
|
||||||
// this.currentEditType=null;
|
// this.currentEditType=null;
|
||||||
var title = "";
|
var title = "";
|
||||||
this.currentEditType = row.type;
|
this.currentEditType = row.type;
|
||||||
@@ -709,7 +693,7 @@ export default {
|
|||||||
if(that.userData.sex==0){
|
if(that.userData.sex==0){
|
||||||
that.userData.sex=2
|
that.userData.sex=2
|
||||||
}
|
}
|
||||||
console.log('that.userData at line 698:', that.userData)
|
console.log('that.userData at line 698个人中心:', that.userData)
|
||||||
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
<public-module></public-module>
|
<public-module></public-module>
|
||||||
<z-nav-bar title="班级详情"></z-nav-bar>
|
<z-nav-bar title="班级详情"></z-nav-bar>
|
||||||
<!-- <text class="small_btn retake border_radius_10" @click="goTest()">再考一次</text> -->
|
<!-- <text class="small_btn retake border_radius_10" @click="goTest()">再考一次</text> -->
|
||||||
|
<text class="" @click="showSubmit">提交证件资料</text>
|
||||||
<view class="mainContent" v-if="thisClass.id">
|
<view class="mainContent" v-if="thisClass.id">
|
||||||
<view :class="['classStatus',
|
<view :class="['classStatus',
|
||||||
thisClass.state == '0' ? 'daikai' :'',
|
thisClass.state == '0' ? 'daikai' :'',
|
||||||
@@ -40,119 +41,126 @@
|
|||||||
<view><text class="ciyao">管理成员</text><text class="mainTxt" @click="seeManager">点击查看</text></view>
|
<view><text class="ciyao">管理成员</text><text class="mainTxt" @click="seeManager">点击查看</text></view>
|
||||||
</view>
|
</view>
|
||||||
<!-- v-if="thisClass.state == '2'" -->
|
<!-- v-if="thisClass.state == '2'" -->
|
||||||
<view class="pingfenBox testBox" v-if="thisClass.state == '2'">
|
<template v-if="isHave">
|
||||||
<view class="scoreTitle">
|
<view class="pingfenBox testBox" v-if="thisClass.state == '2'">
|
||||||
<view class="titles flex_box">
|
<view class="scoreTitle">
|
||||||
<text>总成绩</text>
|
<view class="titles flex_box">
|
||||||
<!-- <uni-icons type="help" size="18" color="#fff"
|
<text>总成绩</text>
|
||||||
|
<!-- <uni-icons type="help" size="18" color="#fff"
|
||||||
@click="showTestTips = true"></uni-icons> -->
|
@click="showTestTips = true"></uni-icons> -->
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
<!-- v-if="thisClass.state == '3'" -->
|
||||||
<!-- v-if="thisClass.state == '3'" -->
|
<view class="optionsBox">
|
||||||
<view class="optionsBox">
|
<!-- v-show="thisClass.state == '2'" -->
|
||||||
<!-- v-show="thisClass.state == '2'" -->
|
<view class="chengji" style="text-align: center;">
|
||||||
<view class="chengji" style="text-align: center;">
|
<view style="margin-top: 20rpx;">总成绩:
|
||||||
<view style="margin-top: 20rpx;">总成绩:
|
<text class="scoreNumber greenScore">{{performanceScore.userScore}}</text>分
|
||||||
<text class="scoreNumber greenScore">{{performanceScore.userScore}}</text>分
|
|
||||||
<view class="">
|
|
||||||
<view class="">
|
<view class="">
|
||||||
<text class="c999">({{performanceScore.examScore}}分考试分 <uni-icons type="eye" size="20" color="#999"
|
<view class="">
|
||||||
@click="showPaperList = true"></uni-icons></text>
|
<text class="c999">({{performanceScore.examScore}}分考试分 <uni-icons type="eye"
|
||||||
<text class="c999">,{{performanceScore.usualScore}}分平时表现分)</text>
|
size="20" color="#999" @click="showPaperList = true"></uni-icons></text>
|
||||||
|
<text class="c999">,{{performanceScore.usualScore}}分平时表现分)</text>
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
<view>
|
||||||
<!-- <view>
|
|
||||||
恭喜您获得
|
恭喜您获得
|
||||||
<text class="greenScore">A证</text>,
|
<text class="yellowScore" v-if=" performanceScore.userScore >= 70"> A 证</text>
|
||||||
<text class="small_btn border_radius_10 zhengshu">查看证书</text>
|
<text class="blueScore" v-else-if="performanceScore.userScore >= 60 && performanceScore.userScore < 70"> B 证</text>
|
||||||
</view> -->
|
<text class="small_btn border_radius_10 zhengshu" @click="showSubmit">{{certificate && certificate.id ? '查看证书' : '生成证书'}}</text>
|
||||||
<!-- <view style="margin-top: 20rpx; text-align: center;">很遗憾,您的成绩未达标</view> -->
|
|
||||||
<view class="" style="margin-top: 20rpx; text-align: center;"
|
|
||||||
v-if="performanceScore.userScore < 60 && newCLass && newCLass.id">
|
|
||||||
<text class="small_btn chongxiu border_radius_10"
|
|
||||||
@click="onPageJump('/pages/miniClass/classInfo',newCLass.id)">加入新班级重修</text>
|
|
||||||
</view>
|
</view>
|
||||||
</view>
|
<!-- <view style="margin-top: 20rpx; text-align: center;">很遗憾,您的成绩未达标</view> -->
|
||||||
</view>
|
<view class="" style="margin-top: 20rpx; text-align: center;"
|
||||||
</view>
|
v-if="performanceScore.userScore < 60 && newCLass && newCLass.id">
|
||||||
<view class="pingfenBox testBox" v-if="(thisClass.state == '3') && classModel.isExam == 1">
|
<text class="small_btn chongxiu border_radius_10"
|
||||||
<view class="scoreTitle">
|
@click="onPageJump('/pages/miniClass/classInfo',newCLass.id)">加入新班级重修</text>
|
||||||
<view class="titles flex_box">
|
|
||||||
<text>笔试分</text><uni-icons type="help" size="18" color="#fff"
|
|
||||||
@click="showTestTips = true"></uni-icons>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
<view class="optionsBox" v-if="thisClass.state == '3'">
|
|
||||||
<view class=" ">
|
|
||||||
<view class="">
|
|
||||||
<!-- -->
|
|
||||||
<view class="gotoExams" @click="goTest" v-if="paperList.length <= 0">
|
|
||||||
<text>去考试</text>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<view class="" v-else-if="paperList.length > 0 && showCountDown">
|
|
||||||
<view class="flex_box flex_between align-items_box">
|
|
||||||
<view class="">
|
|
||||||
<uni-countdown @timeup="timeup" :font-size="20" :show-day="false"
|
|
||||||
:hour="timeDif.hour" :minute="timeDif.minutes" :second="timeDif.second"
|
|
||||||
color="#ffaa7f" />
|
|
||||||
</view>
|
|
||||||
<view class="">
|
|
||||||
<text class="small_btn retake border_radius_10"
|
|
||||||
@click="continueTest(examId)">继续考试</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
<view class="flex_box flex_center align-items_box userScore" v-else
|
|
||||||
style="color: #666; justify-content: space-around !important">
|
|
||||||
|
|
||||||
<view class="flex_box align-items_box">
|
|
||||||
<text><text class="PM_font mainTxt" style="font-size: 60rpx; ">{{paperSore}}</text>
|
|
||||||
分 </text>
|
|
||||||
<uni-icons type="eye" size="20" color="#999"
|
|
||||||
@click="showPaperList = true"></uni-icons>
|
|
||||||
</view>
|
|
||||||
<view class="" v-if="paperList.length == 1">
|
|
||||||
<text class="small_btn retake border_radius_10" @click="goTest()">再考一次</text>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
<view class="pingfenBox testBox" v-if="(thisClass.state == '3') && classModel.isExam == 1">
|
||||||
<view class="pingfenBox" v-if="thisClass.state == '1' || thisClass.state == '3' || thisClass.state == '2'">
|
<view class="scoreTitle">
|
||||||
<view class="scoreTitle">
|
<view class="titles flex_box">
|
||||||
<view class="titles flex_box">
|
<text>笔试分</text><uni-icons type="help" size="18" color="#fff"
|
||||||
<text>表现分</text><uni-icons type="help" size="18" color="#fff" @click="clickHelp"></uni-icons>
|
@click="showTestTips = true"></uni-icons>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="optionsBox" v-if="thisClass.state == '3'">
|
||||||
|
<view class=" ">
|
||||||
|
<view class="">
|
||||||
|
<!-- -->
|
||||||
|
<view class="gotoExams" @click="goTest" v-if="paperList.length <= 0">
|
||||||
|
<text>去考试</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="" v-else-if="paperList.length > 0 && showCountDown">
|
||||||
|
<view class="flex_box flex_between align-items_box">
|
||||||
|
<view class="">
|
||||||
|
<uni-countdown @timeup="timeup" :font-size="20" :show-day="false"
|
||||||
|
:hour="timeDif.hour" :minute="timeDif.minutes" :second="timeDif.second"
|
||||||
|
color="#ffaa7f" />
|
||||||
|
</view>
|
||||||
|
<view class="">
|
||||||
|
<text class="small_btn retake border_radius_10"
|
||||||
|
@click="continueTest(examId)">继续考试</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="flex_box flex_center align-items_box userScore" v-else
|
||||||
|
style="color: #666; justify-content: space-around !important">
|
||||||
|
|
||||||
|
<view class="flex_box align-items_box">
|
||||||
|
<text><text class="PM_font mainTxt"
|
||||||
|
style="font-size: 60rpx; ">{{paperSore}}</text>
|
||||||
|
分 </text>
|
||||||
|
<uni-icons type="eye" size="20" color="#999"
|
||||||
|
@click="showPaperList = true"></uni-icons>
|
||||||
|
</view>
|
||||||
|
<view class="" v-if="paperList.length == 1">
|
||||||
|
<text class="small_btn retake border_radius_10" @click="goTest()">再考一次</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<view :class="['otherItems' ,'flex_box' ,'userScore']">
|
<view class="pingfenBox"
|
||||||
|
v-if="thisClass.state == '1' || thisClass.state == '3' || thisClass.state == '2'">
|
||||||
|
<view class="scoreTitle">
|
||||||
|
<view class="titles flex_box">
|
||||||
|
<text>表现分</text><uni-icons type="help" size="18" color="#fff"
|
||||||
|
@click="clickHelp"></uni-icons>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view :class="['otherItems' ,'flex_box' ,'userScore']">
|
||||||
|
|
||||||
<view v-if="classModel.isTask == 1" :class="['ite', blankNumber < 3 ? 'flex_box' : '']">
|
<view v-if="classModel.isTask == 1" :class="['ite', blankNumber < 3 ? 'flex_box' : '']">
|
||||||
<text class="ciyao">作业得分</text>
|
<text class="ciyao">作业得分</text>
|
||||||
<text class="ciyao"><i class="mainTxt PM_font">{{performanceScore.task0Score}}</i>分</text>
|
<text class="ciyao"><i class="mainTxt PM_font">{{performanceScore.task0Score}}</i>分</text>
|
||||||
</view>
|
</view>
|
||||||
<view v-if="classModel.isMedicalcase == 1" :class="['ite', blankNumber < 3 ? 'flex_box' : '']">
|
<view v-if="classModel.isMedicalcase == 1" :class="['ite', blankNumber < 3 ? 'flex_box' : '']">
|
||||||
<text class="ciyao">医案得分</text>
|
<text class="ciyao">医案得分</text>
|
||||||
<text class="ciyao"><i class="mainTxt PM_font">{{performanceScore.task1Score}}</i>分</text>
|
<text class="ciyao"><i class="mainTxt PM_font">{{performanceScore.task1Score}}</i>分</text>
|
||||||
</view>
|
</view>
|
||||||
<view v-if="classModel.isExperience == 1" :class="['ite', blankNumber < 3 ? 'flex_box' : '']">
|
<view v-if="classModel.isExperience == 1" :class="['ite', blankNumber < 3 ? 'flex_box' : '']">
|
||||||
<text class="ciyao">心得得分</text>
|
<text class="ciyao">心得得分</text>
|
||||||
<text class="ciyao"><i class="mainTxt PM_font">{{performanceScore.experienceScore}}</i>分</text>
|
<text class="ciyao"><i
|
||||||
</view>
|
class="mainTxt PM_font">{{performanceScore.experienceScore}}</i>分</text>
|
||||||
<view v-if="classModel.isQuestion == 1" :class="['ite', blankNumber < 3 ? 'flex_box' : '']">
|
</view>
|
||||||
<text class="ciyao">思考题分</text>
|
<view v-if="classModel.isQuestion == 1" :class="['ite', blankNumber < 3 ? 'flex_box' : '']">
|
||||||
<text class="ciyao"><i class="mainTxt PM_font">{{performanceScore.questionScore}}</i>分</text>
|
<text class="ciyao">思考题分</text>
|
||||||
</view>
|
<text class="ciyao"><i
|
||||||
<!-- <view v-if="classModel.isExam == 1 && thisClass.state == '2'">
|
class="mainTxt PM_font">{{performanceScore.questionScore}}</i>分</text>
|
||||||
|
</view>
|
||||||
|
<!-- <view v-if="classModel.isExam == 1 && thisClass.state == '2'">
|
||||||
<text class="ciyao">考试分</text>
|
<text class="ciyao">考试分</text>
|
||||||
<text class="ciyao"><i class="mainTxt PM_font">{{performanceScore.questionScore}}</i>分</text>
|
<text class="ciyao"><i class="mainTxt PM_font">{{performanceScore.questionScore}}</i>分</text>
|
||||||
</view> -->
|
</view> -->
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
</template>
|
||||||
</view>
|
|
||||||
<view class="mainBox">
|
<view class="mainBox">
|
||||||
<view class="">
|
<view class="">
|
||||||
<u-tabs v-if="tabList.length > 0" :class="['tabList']" @click="tabClick" :current="curTagIndex"
|
<u-tabs v-if="tabList.length > 0" :class="['tabList']" @click="tabClick" :current="curTagIndex"
|
||||||
@@ -219,34 +227,6 @@
|
|||||||
<text v-else>匿名用户</text>
|
<text v-else>匿名用户</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<!-- <view class="" v-if="thisClass.state == '3' || thisClass.state == '2'">
|
|
||||||
<view class="scoreBox flex_box ">
|
|
||||||
<view class="item1">
|
|
||||||
<view class="ciyao">
|
|
||||||
平时成绩
|
|
||||||
</view>
|
|
||||||
<text class="score">{{item.student.score.peacetimeScore}}</text>
|
|
||||||
</view>
|
|
||||||
<template v-if="classModel.isExam == 1 && thisClass.state == '2'">
|
|
||||||
<template v-if="item.student.score.testScore.length == 0">
|
|
||||||
<view class="item1">
|
|
||||||
<view class="ciyao">
|
|
||||||
考试成绩
|
|
||||||
</view>
|
|
||||||
<text class="score">0</text>
|
|
||||||
</view>
|
|
||||||
</template>
|
|
||||||
<template v-else>
|
|
||||||
<view class="item1">
|
|
||||||
<view class="ciyao">
|
|
||||||
考试成绩
|
|
||||||
</view>
|
|
||||||
<text class="score">{{item.examScore}} </text>
|
|
||||||
</view>
|
|
||||||
</template>
|
|
||||||
</template>
|
|
||||||
</view>
|
|
||||||
</view> -->
|
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
@@ -262,8 +242,7 @@
|
|||||||
|
|
||||||
<!-- {{getTijiaoTitleList}} -->
|
<!-- {{getTijiaoTitleList}} -->
|
||||||
<!-- 班内提交 -->
|
<!-- 班内提交 -->
|
||||||
<view class="" v-if="tabId == '3' && thisClass.state != '0'">
|
<view class="" v-if="tabId == '3' && thisClass.state != '0' && isHave">
|
||||||
|
|
||||||
<view class="flex_box tijiaoTitle">
|
<view class="flex_box tijiaoTitle">
|
||||||
<text :class="tijiaoTitleId == item.id ? 'active' :''" @click="titleClick(item,index)"
|
<text :class="tijiaoTitleId == item.id ? 'active' :''" @click="titleClick(item,index)"
|
||||||
v-for="(item, index) in tijiaoTitleList" :key="index">{{item.name}}</text>
|
v-for="(item, index) in tijiaoTitleList" :key="index">{{item.name}}</text>
|
||||||
@@ -450,13 +429,28 @@
|
|||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</u-popup>
|
</u-popup>
|
||||||
|
<!-- <addCerInfo v-if="showSubmitInfoBlank" :submitInfo="submitInfo" @close="closeManager" ></addCerInfo> -->
|
||||||
|
<u-popup key="5" :show="showSubmitInfoBlank" :round="10" @close="closeManager">
|
||||||
|
<view class="guanli">
|
||||||
|
<h3>信息展示</h3>
|
||||||
|
<view class="tips border_radius_10">
|
||||||
|
{{submitInfo.name}}-{{submitInfo.photo}}
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</u-popup>
|
||||||
<!-- <z-navigation></z-navigation> -->
|
<!-- <z-navigation></z-navigation> -->
|
||||||
|
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import QfImageCropper from '@/uni_modules/qf-image-cropper/components/qf-image-cropper/qf-image-cropper.vue';
|
||||||
|
import addCerInfo from "@/components/addCerInfo.vue";
|
||||||
|
// import {
|
||||||
|
// chooseImage
|
||||||
|
// } from '@/node_modules/uview-ui';
|
||||||
import sutdentScoreList from "./components/sutdent_score_list.vue";
|
import sutdentScoreList from "./components/sutdent_score_list.vue";
|
||||||
|
// import permission from "@/js_sdk/wa-permission/permission.js"
|
||||||
import $http from '@/config/requestConfig.js';
|
import $http from '@/config/requestConfig.js';
|
||||||
import {
|
import {
|
||||||
mapState
|
mapState
|
||||||
@@ -464,6 +458,12 @@
|
|||||||
export default {
|
export default {
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
fileList1: [],
|
||||||
|
showCropper: false,
|
||||||
|
submitInfo: {
|
||||||
|
img: undefined, // 证件图
|
||||||
|
name: '' // 姓名
|
||||||
|
},
|
||||||
showAlert: false, // 是否显示考试周
|
showAlert: false, // 是否显示考试周
|
||||||
horizontal: 'right',
|
horizontal: 'right',
|
||||||
vertical: 'bottom',
|
vertical: 'bottom',
|
||||||
@@ -477,22 +477,9 @@
|
|||||||
buttonColor: '#007AFF',
|
buttonColor: '#007AFF',
|
||||||
iconColor: '#fff'
|
iconColor: '#fff'
|
||||||
},
|
},
|
||||||
fabContent: [{
|
|
||||||
iconPath: '/static/icon/pigai2.png',
|
|
||||||
selectedIconPath: '/static/icon/pigai2.png',
|
|
||||||
text: '发布作业',
|
|
||||||
active: false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
iconPath: '/static/icon/pigai1.png',
|
|
||||||
selectedIconPath: '/static/icon/pigai1.png',
|
|
||||||
text: '发布医案',
|
|
||||||
active: false
|
|
||||||
},
|
|
||||||
],
|
|
||||||
timeDif: {},
|
timeDif: {},
|
||||||
showCountDown: false,
|
showCountDown: false,
|
||||||
|
showSubmitInfoBlank: false,
|
||||||
thisClass: {},
|
thisClass: {},
|
||||||
students: [],
|
students: [],
|
||||||
tabId: '3',
|
tabId: '3',
|
||||||
@@ -608,11 +595,13 @@
|
|||||||
examId: undefined,
|
examId: undefined,
|
||||||
ingPaper: undefined, // 正在进行中的试卷信息
|
ingPaper: undefined, // 正在进行中的试卷信息
|
||||||
newCLass: undefined, // 同模型的待开班班级
|
newCLass: undefined, // 同模型的待开班班级
|
||||||
|
certificate:undefined, // 证书
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onLoad(e) {
|
onLoad(e) {
|
||||||
uni.hideTabBar();
|
uni.hideTabBar();
|
||||||
this.classId = e.id
|
this.classId = e.id
|
||||||
|
console.log('this.classId',this.classId );
|
||||||
this.getUserRole()
|
this.getUserRole()
|
||||||
this.getUserInfo()
|
this.getUserInfo()
|
||||||
uni.$on('refreshData', () => {
|
uni.$on('refreshData', () => {
|
||||||
@@ -669,7 +658,7 @@
|
|||||||
console.log('正在进行中的试卷信息', historyPaper);
|
console.log('正在进行中的试卷信息', historyPaper);
|
||||||
this.ingPaper = historyPaper
|
this.ingPaper = historyPaper
|
||||||
// console.log('对比----------------', historyPaper.classId, this.classId);
|
// console.log('对比----------------', historyPaper.classId, this.classId);
|
||||||
if (historyPaper&& historyPaper != null && historyPaper.classId == this.classId) {
|
if (historyPaper && historyPaper != null && historyPaper.classId == this.classId) {
|
||||||
var planEndTimeDate = new Date(historyPaper.planEndTime)
|
var planEndTimeDate = new Date(historyPaper.planEndTime)
|
||||||
paperEndTime = planEndTimeDate.getTime(); // 结束的时间戳
|
paperEndTime = planEndTimeDate.getTime(); // 结束的时间戳
|
||||||
this.examId = historyPaper.id
|
this.examId = historyPaper.id
|
||||||
@@ -694,7 +683,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
sutdentScoreList
|
sutdentScoreList,
|
||||||
|
QfImageCropper,
|
||||||
|
addCerInfo
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapState(["userInfo"]),
|
...mapState(["userInfo"]),
|
||||||
@@ -719,11 +710,12 @@
|
|||||||
|
|
||||||
tabList() {
|
tabList() {
|
||||||
var list = []
|
var list = []
|
||||||
if (this.thisClass.state == '0') {
|
if (this.thisClass.state == '0' || !this.isHave) {
|
||||||
list = this.tabList01
|
list = this.tabList01
|
||||||
} else if (this.thisClass.state == '1') {
|
} else if (this.thisClass.state == '1') {
|
||||||
list = this.tabList02
|
list = this.tabList02
|
||||||
} else {
|
} else {
|
||||||
|
console.log('走这里了吗?');
|
||||||
list = this.tabList03
|
list = this.tabList03
|
||||||
}
|
}
|
||||||
return list
|
return list
|
||||||
@@ -731,6 +723,10 @@
|
|||||||
|
|
||||||
// 获得提交项目的tab
|
// 获得提交项目的tab
|
||||||
getTijiaoTitleList() {
|
getTijiaoTitleList() {
|
||||||
|
if (!this.isHave) {
|
||||||
|
this.tabId = 0
|
||||||
|
// return
|
||||||
|
}
|
||||||
var _list = []
|
var _list = []
|
||||||
if (this.classModel.isTask == 1) {
|
if (this.classModel.isTask == 1) {
|
||||||
_list.push(this.defaultTijiaoTitleList[0])
|
_list.push(this.defaultTijiaoTitleList[0])
|
||||||
@@ -742,8 +738,10 @@
|
|||||||
_list.push(this.defaultTijiaoTitleList[2])
|
_list.push(this.defaultTijiaoTitleList[2])
|
||||||
}
|
}
|
||||||
// return _list
|
// return _list
|
||||||
this.tijiaoTitleList = _list
|
if(_list.length > 0){
|
||||||
this.tijiaoTitleId = this.tijiaoTitleList[0].id
|
this.tijiaoTitleList = _list
|
||||||
|
this.tijiaoTitleId = this.tijiaoTitleList[0].id
|
||||||
|
}
|
||||||
// this.tabId =
|
// this.tabId =
|
||||||
},
|
},
|
||||||
getBlankNumber() {
|
getBlankNumber() {
|
||||||
@@ -766,6 +764,55 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
async showSubmit() {
|
||||||
|
if(this.certificate && this.certificate.id){
|
||||||
|
// 已生成
|
||||||
|
uni.navigateTo({
|
||||||
|
url:'去个人中心'
|
||||||
|
})
|
||||||
|
}else{ // 未生成
|
||||||
|
var userObj = await this.getUserData()
|
||||||
|
if(userObj){
|
||||||
|
this.submitInfo.name = userObj.name
|
||||||
|
this.submitInfo.photo = userObj.photo
|
||||||
|
this.showSubmitInfoBlank = true
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
},
|
||||||
|
async getUserData() {
|
||||||
|
let that = this;
|
||||||
|
var Ss = undefined
|
||||||
|
// 获取个人信息
|
||||||
|
if (this.userInfo.id != undefined) {
|
||||||
|
await this.$http
|
||||||
|
.post('book/user/info/'+this.userInfo.id)
|
||||||
|
.then(async (res) => {
|
||||||
|
Ss = res.user;
|
||||||
|
console.log('that.userData at line 698个人中心:', res.user)
|
||||||
|
});
|
||||||
|
return Ss
|
||||||
|
}
|
||||||
|
},
|
||||||
|
chooseImage() {
|
||||||
|
let that = this
|
||||||
|
uni.chooseImage({
|
||||||
|
count: 6, //默认9
|
||||||
|
sizeType: ['original', 'compressed'], //可以指定是原图还是压缩图,默认二者都有
|
||||||
|
sourceType: ['album'], //从相册选择
|
||||||
|
success: function(res) {
|
||||||
|
console.log('选择的照片', JSON.stringify(res.tempFilePaths));
|
||||||
|
that.submitInfo.img = res.tempFilePaths[0];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
selectImage() {
|
||||||
|
this.chooseImage()
|
||||||
|
// .then(res => {
|
||||||
|
// this.submitInfo.img = res.tempFilePaths[0];
|
||||||
|
// });
|
||||||
|
},
|
||||||
// 选中了学生
|
// 选中了学生
|
||||||
chooseStudent(data) {
|
chooseStudent(data) {
|
||||||
return
|
return
|
||||||
@@ -852,10 +899,6 @@
|
|||||||
})
|
})
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
// 查询考试成绩
|
|
||||||
getPaperScore() {
|
|
||||||
console.log('获取卷面成绩');
|
|
||||||
},
|
|
||||||
// 考试周和结班状态下,学员的信息
|
// 考试周和结班状态下,学员的信息
|
||||||
async getStudentScoreList() {
|
async getStudentScoreList() {
|
||||||
console.log('进来了码?');
|
console.log('进来了码?');
|
||||||
@@ -871,7 +914,7 @@
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
.then(res => {
|
.then(res => {
|
||||||
console.log('考试周班状态下,学员的信息',this.classId , res, );
|
console.log('考试周班状态下,学员的信息', this.classId, res, );
|
||||||
// this.userMsg = res.user
|
// this.userMsg = res.user
|
||||||
// list = studentList
|
// list = studentList
|
||||||
if (res.code == 0) {
|
if (res.code == 0) {
|
||||||
@@ -904,7 +947,7 @@
|
|||||||
return list
|
return list
|
||||||
},
|
},
|
||||||
goTest() {
|
goTest() {
|
||||||
console.log('this.ingPaper',this.ingPaper);
|
console.log('this.ingPaper', this.ingPaper);
|
||||||
if (this.ingPaper) {
|
if (this.ingPaper) {
|
||||||
uni.showModal({
|
uni.showModal({
|
||||||
title: '提示',
|
title: '提示',
|
||||||
@@ -948,8 +991,8 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
},
|
},
|
||||||
// 查询平时表现分
|
// 查询平时表现分+ 最终成绩 + 可加入的新班级 + 证书信息
|
||||||
getScore() {
|
async getScore() {
|
||||||
$http.request({
|
$http.request({
|
||||||
url: "common/class/getUserScore",
|
url: "common/class/getUserScore",
|
||||||
method: "POST",
|
method: "POST",
|
||||||
@@ -961,12 +1004,20 @@
|
|||||||
'Content-Type': 'application/json'
|
'Content-Type': 'application/json'
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
.then((res) => {
|
.then(async (res) => {
|
||||||
if (res.code == 0) {
|
if (res.code == 0) {
|
||||||
this.performanceScore = res.result
|
this.performanceScore = res.result
|
||||||
console.log('平时成绩e-------', this.performanceScore);
|
console.log('平时成绩e-------', this.performanceScore);
|
||||||
if (this.performanceScore.userScore < 60) {
|
if (this.performanceScore.userScore < 60) {
|
||||||
this.getNewCLassInfo()
|
this.getNewCLassInfo()
|
||||||
|
}else{
|
||||||
|
// >= 60
|
||||||
|
if(this.thisClass.state == '2' ){
|
||||||
|
var certificate = await this.getCertificateInfo()
|
||||||
|
if(certificate && certificate.id){
|
||||||
|
this.certificate = certificate
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
@@ -981,6 +1032,36 @@
|
|||||||
})
|
})
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
// 查询证书获得情况
|
||||||
|
async getCertificateInfo(){
|
||||||
|
var _obj = undefined
|
||||||
|
$http.request({
|
||||||
|
url: "common/class/getUserCertificateByClassId",
|
||||||
|
method: "POST",
|
||||||
|
data: {
|
||||||
|
"classId": this.classId,
|
||||||
|
"userId": this.userInfo.id
|
||||||
|
},
|
||||||
|
header: { //默认 无 说明:请求头
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.then((res) => {
|
||||||
|
if (res.code == 0) {
|
||||||
|
// this.newCLass = res.result
|
||||||
|
console.log('证书数据', res);
|
||||||
|
_obj = res.userCertificate
|
||||||
|
}
|
||||||
|
}).catch(e => {
|
||||||
|
_obj = undefined
|
||||||
|
console.log('证书接口报错', e);
|
||||||
|
uni.showToast({
|
||||||
|
title: e.errMsg,
|
||||||
|
icon: 'none'
|
||||||
|
})
|
||||||
|
});
|
||||||
|
return _obj
|
||||||
|
},
|
||||||
// 可加入的同模型下待开班班级
|
// 可加入的同模型下待开班班级
|
||||||
getNewCLassInfo() {
|
getNewCLassInfo() {
|
||||||
$http.request({
|
$http.request({
|
||||||
@@ -1099,7 +1180,10 @@
|
|||||||
console.log('考试中999999999999999999', res);
|
console.log('考试中999999999999999999', res);
|
||||||
if (res.code == 0) {
|
if (res.code == 0) {
|
||||||
if (res.classExamUser != null) {
|
if (res.classExamUser != null) {
|
||||||
obj = {...res.classExamUser, planEndTime:res.planEndTime}
|
obj = {
|
||||||
|
...res.classExamUser,
|
||||||
|
planEndTime: res.planEndTime
|
||||||
|
}
|
||||||
// obj
|
// obj
|
||||||
} else {
|
} else {
|
||||||
obj = undefined
|
obj = undefined
|
||||||
@@ -1307,35 +1391,14 @@
|
|||||||
seeManager() {
|
seeManager() {
|
||||||
this.showMan = true
|
this.showMan = true
|
||||||
},
|
},
|
||||||
|
|
||||||
closeManager() {
|
closeManager() {
|
||||||
this.showMan = false
|
this.showMan = false
|
||||||
this.showTestTips = false
|
this.showTestTips = false
|
||||||
this.showPaperList = false
|
this.showPaperList = false
|
||||||
|
this.showSubmitInfoBlank = false
|
||||||
},
|
},
|
||||||
fabClick() {
|
|
||||||
// uni.showToast({
|
|
||||||
// title: '点击了悬浮按钮',
|
|
||||||
// icon: 'none'
|
|
||||||
// })
|
|
||||||
},
|
|
||||||
fabTrigger(e) {
|
|
||||||
console.log(e)
|
|
||||||
this.fabContent[e.index].active = !e.item.active
|
|
||||||
var _url = '/pages/miniClass/addHomeWork'
|
|
||||||
var _type = '0'
|
|
||||||
if (e.index == 0) {
|
|
||||||
// 发布作业/医案
|
|
||||||
_type = '0'
|
|
||||||
} else {
|
|
||||||
_type = '1'
|
|
||||||
}
|
|
||||||
this.$refs.fab.close()
|
|
||||||
setTimeout(() => {
|
|
||||||
uni.navigateTo({
|
|
||||||
url: `${_url}?id=${this.thisClass.id}&type=${_type}`
|
|
||||||
})
|
|
||||||
}, 200)
|
|
||||||
},
|
|
||||||
// 改变班级状态
|
// 改变班级状态
|
||||||
changeClassStatu(statusCode) {
|
changeClassStatu(statusCode) {
|
||||||
if (statusCode == '1') {
|
if (statusCode == '1') {
|
||||||
@@ -1467,7 +1530,7 @@
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
.then(async res => {
|
.then(async res => {
|
||||||
console.log('班级信息', res);
|
// console.log('班级信息', res);
|
||||||
this.refresh = false
|
this.refresh = false
|
||||||
if (res.code == 0) {
|
if (res.code == 0) {
|
||||||
// res = classData // 测试数据
|
// res = classData // 测试数据
|
||||||
@@ -1523,17 +1586,16 @@
|
|||||||
if (this.thisClass.state != 0) {
|
if (this.thisClass.state != 0) {
|
||||||
this.pPage = 0
|
this.pPage = 0
|
||||||
this.taskList = []
|
this.taskList = []
|
||||||
if (this.firstLoad) {
|
if(this.tijiaoTitleList.length > 0){
|
||||||
this.firstLoad = false
|
if (this.firstLoad) {
|
||||||
this.tijiaoTitleId = this.tijiaoTitleList[0].id
|
this.firstLoad = false
|
||||||
|
this.tijiaoTitleId = this.tijiaoTitleList[0].id
|
||||||
|
|
||||||
|
}
|
||||||
|
this.getList()
|
||||||
}
|
}
|
||||||
this.getList()
|
|
||||||
}
|
}
|
||||||
var dd = this.getBlankNumber
|
var dd = this.getBlankNumber
|
||||||
if (this.thisClass.state == '2') { // 结班状态下再查询卷面成绩
|
|
||||||
this.getPaperScore()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
uni.hideLoading()
|
uni.hideLoading()
|
||||||
}).catch(e => {
|
}).catch(e => {
|
||||||
@@ -1563,27 +1625,6 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
|
|
||||||
.fabBox {}
|
|
||||||
|
|
||||||
::v-deep .uni-fab__content--other-platform,
|
|
||||||
::v-deep .uni-fab--rightBottom {
|
|
||||||
bottom: 100px !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
::v-deep .uni-fab__content--other-platform {
|
|
||||||
box-shadow: none !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
::v-deep .fab-circle-icon {
|
|
||||||
display: none !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
::v-deep .uni-fab__circle::after {
|
|
||||||
content: '发布';
|
|
||||||
color: #fff;
|
|
||||||
font-weight: normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
.classStatus {
|
.classStatus {
|
||||||
padding: 10rpx;
|
padding: 10rpx;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
@@ -1716,7 +1757,9 @@
|
|||||||
.blueScore {
|
.blueScore {
|
||||||
color: #00aaff;
|
color: #00aaff;
|
||||||
}
|
}
|
||||||
|
.yellowScore{
|
||||||
|
color: #ffaa00;
|
||||||
|
}
|
||||||
.redScore {
|
.redScore {
|
||||||
color: #ff8a8c;
|
color: #ff8a8c;
|
||||||
}
|
}
|
||||||
|
|||||||
72
uni_modules/qf-image-cropper/changelog.md
Normal file
72
uni_modules/qf-image-cropper/changelog.md
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
## 2.2.5(2024-07-30)
|
||||||
|
* 修复 当 checkRange=true 时,拖动四个伸缩角放大图片时还可能会超出或未到边界的问题
|
||||||
|
* 修复 当 checkRange=false 时,图片旋转时会放大图片适应裁剪尺寸的问题
|
||||||
|
* 修复 当 checkRange=true 时,图片旋转 90° 或 270° 进行缩放可能会无法拖动图片的问题
|
||||||
|
## 2.2.4(2024-06-21)
|
||||||
|
* 新增 reverseRotatable 属性,是否支持逆向翻转
|
||||||
|
* 修复 `2.1.7` 版本导致旋转后图片没有自动适配裁剪框的问题
|
||||||
|
|
||||||
|
## 2.2.3(2024-06-21)
|
||||||
|
* 新增 gpu 属性,是否开启硬件加速,图片缩放过程中如果出现元素的“留影”或“重影”效果,可通过该方式解决或减轻这一问题
|
||||||
|
* 修复 组件使用 `v-if` 并设置 `src` 属性时可能会出现图片渲染位置存在偏差的问题
|
||||||
|
|
||||||
|
## 2.2.2(2024-06-21)
|
||||||
|
* 优化 组件实例 chooseImage 方法支持传参
|
||||||
|
* 修复 组件使用 `v-if` 时组件无非正常渲染的问题
|
||||||
|
|
||||||
|
## 2.2.1(2024-06-15)
|
||||||
|
* 修复 H5平台不支持手势拖动图片的问题
|
||||||
|
|
||||||
|
## 2.2.0(2024-05-31)
|
||||||
|
* 修复 APP平台 `vue2` 项目因 `2.1.9` 版本修复 `vue3` 项目bug而引发的问题
|
||||||
|
|
||||||
|
## 2.1.9(2024-05-29)
|
||||||
|
* 修复 APP平台 `vue3` 项目因 uniapp `renderjs` 中未支持条件编译,导致运行了H5平台代码报错的问题
|
||||||
|
|
||||||
|
## 2.1.8(2024-05-29)
|
||||||
|
* 新增 zIndex 属性,调整组件层级
|
||||||
|
* 新增 组件内容插槽
|
||||||
|
* 优化 微信小程序平台动态修改元素style时的多余内容
|
||||||
|
|
||||||
|
## 2.1.7(2024-05-28)
|
||||||
|
* 新增 checkRange 属性,当 checkRange=false 时允许图片位置超出裁剪边界
|
||||||
|
* 新增 minScale 属性,图片最小缩放倍数,当 minScale<0 时可使图片宽高不再受裁剪区域宽高限制
|
||||||
|
* 新增 backgroundColor 属性,生成图片背景色,如果裁剪区域没有完全包含在图片中时,不设置该属性生成图片存在一定的透明块
|
||||||
|
* 优化 动态修改图片宽高但没有传入src时,尺寸适应问题
|
||||||
|
* 修复 APP平台通过 `this.$ownerInstance` 获取组件实例时机过早,其值为 `undefined` 导致报错界面没有正常渲染的问题
|
||||||
|
|
||||||
|
## 2.1.6(2023-04-16)
|
||||||
|
* 修复 组件使用 v-show 指令会导致选择图片后初始位置严重偏位的问题
|
||||||
|
|
||||||
|
## 2.1.5(2023-04-15)
|
||||||
|
* 新增 兼容APP平台
|
||||||
|
|
||||||
|
## 2.1.4(2023-03-13)
|
||||||
|
* 新增 fileType 属性,用于指定生成文件的类型,只支持 'jpg' 或 'png',默认为 'png'
|
||||||
|
* 新增 delay 属性,微信小程序平台使用 `Canvas 2D` 绘制时控制图片从绘制到生成所需时间
|
||||||
|
* 优化 当生成图片的尺寸宽/高超过 Canvas 2D 最大限制(1365*1365)则将画布尺寸缩放在限制范围内绘制完成后输出目标尺寸
|
||||||
|
* 优化 旋转图标指示方向与实际旋转方向不符
|
||||||
|
|
||||||
|
## 2.1.3(2023-02-06)
|
||||||
|
* 优化 vue3支持
|
||||||
|
|
||||||
|
## 2.1.2(2023-02-03)
|
||||||
|
* 新增 navigation 属性,H5平台当 showAngle 为 true 时,使用插件的页面在 `page.json` 中配置了 "navigationStyle": "custom" 时,必须将此值设为 false ,否则四个可拉伸角的触发位置会有偏差
|
||||||
|
* 修复 H5平台部分设备(已知iPhone11以下机型)拍照的图片缩放时会闪动的问题
|
||||||
|
|
||||||
|
## 2.1.1(2022-12-06)
|
||||||
|
* 修复 横屏适配问题
|
||||||
|
|
||||||
|
## 2.1.0(2022-12-06)
|
||||||
|
* 新增 兼容H5平台,使用 renderjs 响应手势事件
|
||||||
|
|
||||||
|
## 2.0.0(2022-12-05)
|
||||||
|
* 重构 插件,使用 WXS 响应手势事件
|
||||||
|
* 新增 图片翻转
|
||||||
|
* 新增 拉伸裁剪框放大图片
|
||||||
|
* 新增 监听PC鼠标滚轮触发缩放
|
||||||
|
* 新增 圆形、圆角矩形的图片裁剪
|
||||||
|
* 优化 图片缩放,移动端以双指触摸中心点为缩放中心点,PC端以鼠标所在点为缩放中心点
|
||||||
|
* 优化 裁剪框样式
|
||||||
|
* 优化 图片位置拖动 支持边界回弹效果(滑动时可滑出边界,释放时回弹到边界)
|
||||||
|
* 优化 生成图片使用新版 Canvas 2D 接口
|
||||||
@@ -0,0 +1,855 @@
|
|||||||
|
/**
|
||||||
|
* 图片编辑器-手势监听
|
||||||
|
* 1. 支持编译到app-vue(uni-app 2.5.5及以上版本)、H5上
|
||||||
|
*/
|
||||||
|
/** 图片偏移量 */
|
||||||
|
var offset = { x: 0, y: 0 };
|
||||||
|
/** 图片缩放比例 */
|
||||||
|
var scale = 1;
|
||||||
|
/** 图片最小缩放比例 */
|
||||||
|
var minScale = 1;
|
||||||
|
/** 图片旋转角度 */
|
||||||
|
var rotate = 0;
|
||||||
|
/** 触摸点 */
|
||||||
|
var touches = [];
|
||||||
|
/** 图片布局信息 */
|
||||||
|
var img = {};
|
||||||
|
/** 系统信息 */
|
||||||
|
var sys = {};
|
||||||
|
/** 裁剪区域布局信息 */
|
||||||
|
var area = {};
|
||||||
|
/** 触摸行为类型 */
|
||||||
|
var touchType = '';
|
||||||
|
/** 操作角的位置 */
|
||||||
|
var activeAngle = 0;
|
||||||
|
/** 裁剪区域布局信息偏移量 */
|
||||||
|
var areaOffset = { left: 0, right: 0, top: 0, bottom: 0 };
|
||||||
|
/** 元素ID */
|
||||||
|
var elIds = {
|
||||||
|
'imageStyles': 'crop-image',
|
||||||
|
'maskStylesList': 'crop-mask-block',
|
||||||
|
'borderStyles': 'crop-border',
|
||||||
|
'circleBoxStyles': 'crop-circle-box',
|
||||||
|
'circleStyles': 'crop-circle',
|
||||||
|
'gridStylesList': 'crop-grid',
|
||||||
|
'angleStylesList': 'crop-angle',
|
||||||
|
}
|
||||||
|
/** 记录上次初始化时间戳,排除APP重复更新 */
|
||||||
|
var timestamp = 0;
|
||||||
|
/** vue3 renderjs 条件编译无效,以此方式区别 APP 和 H5 */
|
||||||
|
// #ifdef H5
|
||||||
|
var platform = 'H5';
|
||||||
|
// #endif
|
||||||
|
// #ifdef APP
|
||||||
|
var platform = 'APP';
|
||||||
|
// #endif
|
||||||
|
/** 容错值 */
|
||||||
|
var fault = 0.000001;
|
||||||
|
/**
|
||||||
|
* 获取a、b两数中的最小正数
|
||||||
|
* @param a
|
||||||
|
* @param b
|
||||||
|
*/
|
||||||
|
function minimum(a, b) {
|
||||||
|
if (a > 0 && b < 0) return a;
|
||||||
|
if (a < 0 && b > 0) return b;
|
||||||
|
if (a > 0 && b > 0) return Math.min(a, b);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 在容错访问内获取n近似值
|
||||||
|
* @param n
|
||||||
|
*/
|
||||||
|
function num(n) {
|
||||||
|
var m = parseFloat((n).toFixed(6));
|
||||||
|
return m === fault || m === -fault ? 0 : m;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 比较a值在容错值范围内是否等于b值
|
||||||
|
* @param a
|
||||||
|
* @param b
|
||||||
|
*/
|
||||||
|
function equalsByFault(a, b) {
|
||||||
|
return Math.abs(a - b) <= fault;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 比较a值在容错值范围内是否小于b值
|
||||||
|
* @param a
|
||||||
|
* @param b
|
||||||
|
*/
|
||||||
|
function lessThanByFault(a, b) {
|
||||||
|
var c = a - b;
|
||||||
|
return c < 0 ? c < -fault : c < fault;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 验证并获取有效最大值
|
||||||
|
* @param v
|
||||||
|
* @param max
|
||||||
|
* @param isInclude
|
||||||
|
* @param x
|
||||||
|
* @param y
|
||||||
|
* @param rate
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
function validMax(v, max, isInclude, x, y, rate) {
|
||||||
|
if(typeof max === 'number') {
|
||||||
|
if(isInclude && equalsByFault(max, y)) { // 宽高不等时,x轴用y轴值要做等比例转换
|
||||||
|
var n = num(max * rate);
|
||||||
|
if (n <= x) return n; // 转化后值在x轴最大值范围内
|
||||||
|
return x; // 转化后值超出x轴最大值范围则用最大值
|
||||||
|
}
|
||||||
|
return max;
|
||||||
|
}
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 样式对象转字符串
|
||||||
|
* @param {Object} style 样式对象
|
||||||
|
*/
|
||||||
|
function styleToString(style) {
|
||||||
|
if(typeof style === 'string') return style;
|
||||||
|
var str = '';
|
||||||
|
for (let k in style) {
|
||||||
|
str += k + ':' + style[k] + ';';
|
||||||
|
}
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {Object} instance 页面实例对象
|
||||||
|
* @param {Object} key 要修改样式的key
|
||||||
|
* @param {Object|Array} style 样式
|
||||||
|
*/
|
||||||
|
function setStyle(instance, key, style) {
|
||||||
|
// console.log('setStyle', instance, key, JSON.stringify(style))
|
||||||
|
// #ifdef APP-PLUS
|
||||||
|
if(platform === 'APP') {
|
||||||
|
if(Object.prototype.toString.call(style) === '[object Array]') {
|
||||||
|
for (var i = 0, len = style.length; i < len; i++) {
|
||||||
|
var el = window.document.getElementById(elIds[key] + '-' + (i + 1));
|
||||||
|
el && (el.style = styleToString(style[i]));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
var el = window.document.getElementById(elIds[key]);
|
||||||
|
el && (el.style = styleToString(style));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// #endif
|
||||||
|
// #ifdef H5
|
||||||
|
if(platform === 'H5') instance[key] = style;
|
||||||
|
// #endif
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 触发页面实例指定方法
|
||||||
|
* @param {Object} instance 页面实例对象
|
||||||
|
* @param {Object} name 方法名称
|
||||||
|
* @param {Object} obj 传递参数
|
||||||
|
*/
|
||||||
|
function callMethod(instance, name, obj) {
|
||||||
|
// #ifdef APP-PLUS
|
||||||
|
if(platform === 'APP') instance.callMethod(name, obj);
|
||||||
|
// #endif
|
||||||
|
// #ifdef H5
|
||||||
|
if(platform === 'H5') instance[name](obj);
|
||||||
|
// #endif
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 计算两点间距
|
||||||
|
* @param {Object} touches 触摸点信息
|
||||||
|
*/
|
||||||
|
function getDistanceByTouches(touches) {
|
||||||
|
// 根据勾股定理求两点间距离
|
||||||
|
var a = touches[1].pageX - touches[0].pageX;
|
||||||
|
var b = touches[1].pageY - touches[0].pageY;
|
||||||
|
var c = Math.sqrt(Math.pow(a, 2) + Math.pow(b, 2));
|
||||||
|
// 求两点间的中点坐标
|
||||||
|
// 1. a、b可能为负值
|
||||||
|
// 2. 在求a、b时,如用touches[1]减touches[0],则求中点坐标也得用touches[1]减a/2、b/2
|
||||||
|
// 3. 同理,在求a、b时,也可用touches[0]减touches[1],则求中点坐标也得用touches[0]减a/2、b/2
|
||||||
|
var x = touches[1].pageX - a / 2;
|
||||||
|
var y = touches[1].pageY - b / 2;
|
||||||
|
return { c, x, y };
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 修正取值
|
||||||
|
* @param {Object} a
|
||||||
|
* @param {Object} b
|
||||||
|
* @param {Object} c
|
||||||
|
* @param {Object} reverse 是否反向
|
||||||
|
*/
|
||||||
|
function correctValue(a, b, c, reverse) {
|
||||||
|
return num(reverse ? Math.max(Math.min(a, b), c) : Math.min(Math.max(a, b), c));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 旋转90°或270°时检查边界:限制 x、y 拖动范围,禁止滑出边界
|
||||||
|
* @param {Object} e 点坐标
|
||||||
|
* @param {Object} xReverse x是否反向
|
||||||
|
* @param {Object} yReverse y是否反向
|
||||||
|
*/
|
||||||
|
function checkRotateRange(e, xReverse, yReverse) {
|
||||||
|
var o = num((img.height - img.width) / 2); // 宽高差值一半
|
||||||
|
return {
|
||||||
|
x: correctValue(e.x, -img.height + o + area.width + area.left, area.left + o, xReverse),
|
||||||
|
y: correctValue(e.y, -img.width - o + area.height + area.top, area.top - o, yReverse)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查边界:限制 x、y 拖动范围,禁止滑出边界
|
||||||
|
* @param {Object} e 点坐标
|
||||||
|
*/
|
||||||
|
function checkRange(e) {
|
||||||
|
var r = rotate / 90 % 2;
|
||||||
|
if(r === 1) { // 因图片宽高可能不等,翻转 90° 或 270° 后图片宽高需反着计算,且左右和上下边界要根据差值做偏移
|
||||||
|
if (area.width === area.height) {
|
||||||
|
return checkRotateRange(e, img.height < area.height, img.width < area.width);
|
||||||
|
}
|
||||||
|
var isInclude = img.height < area.width && img.width < area.height; // 图片是否包含在裁剪区域内
|
||||||
|
if (img.width < area.height || img.height < area.width) {
|
||||||
|
if (area.width < area.height && img.width < img.height) {
|
||||||
|
return isInclude
|
||||||
|
? checkRotateRange(e, area.width < area.height, area.width < area.height)
|
||||||
|
: checkRotateRange(e, false, true);
|
||||||
|
}
|
||||||
|
if (area.height < area.width && img.height < img.width) {
|
||||||
|
return isInclude
|
||||||
|
? checkRotateRange(e, area.height < area.width, area.height < area.width)
|
||||||
|
: checkRotateRange(e, true, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (img.height >= area.width && img.width >= area.height) {
|
||||||
|
return checkRotateRange(e, false, false);
|
||||||
|
}
|
||||||
|
if (isInclude) {
|
||||||
|
return area.height < area.width
|
||||||
|
? checkRotateRange(e, true, true)
|
||||||
|
: checkRotateRange(e, area.width < area.height, area.width < area.height);
|
||||||
|
}
|
||||||
|
if (img.height < area.width && !img.width < area.height) {
|
||||||
|
return checkRotateRange(e, true, false);
|
||||||
|
}
|
||||||
|
if (!img.height < area.width && img.width < area.height) {
|
||||||
|
return checkRotateRange(e, false, true);
|
||||||
|
}
|
||||||
|
return checkRotateRange(e, img.height < area.height, img.width < area.width);
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
x: correctValue(e.x, -img.width + area.width + area.left, area.left, img.width < area.width),
|
||||||
|
y: correctValue(e.y, -img.height + area.height + area.top, area.top, img.height < area.height)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* 变更图片布局信息
|
||||||
|
* @param {Object} e 布局信息
|
||||||
|
*/
|
||||||
|
function changeImageRect(e) {
|
||||||
|
// console.log('changeImageRect', e)
|
||||||
|
offset.x += e.x || 0;
|
||||||
|
offset.y += e.y || 0;
|
||||||
|
if(e.check && area.checkRange) { // 检查边界
|
||||||
|
var point = checkRange(offset);
|
||||||
|
if(offset.x !== point.x || offset.y !== point.y) {
|
||||||
|
offset = point;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 因频繁修改 width/height 会造成大量的内存消耗,改为scale
|
||||||
|
// e.instance.imageStyles = {
|
||||||
|
// width: img.width + 'px',
|
||||||
|
// height: img.height + 'px',
|
||||||
|
// transform: 'translate(' + (offset.x + ox) + 'px, ' + (offset.y + ox) + 'px) rotate(' + rotate +'deg)'
|
||||||
|
// };
|
||||||
|
var ox = (img.width - img.oldWidth) / 2;
|
||||||
|
var oy = (img.height - img.oldHeight) / 2;
|
||||||
|
// e.instance.imageStyles = {
|
||||||
|
// width: img.oldWidth + 'px',
|
||||||
|
// height: img.oldHeight + 'px',
|
||||||
|
// transform: 'translate(' + (offset.x + ox) + 'px, ' + (offset.y + oy) + 'px) rotate(' + rotate +'deg) scale(' + scale + ')'
|
||||||
|
// };
|
||||||
|
setStyle(e.instance, 'imageStyles', {
|
||||||
|
width: img.oldWidth + 'px',
|
||||||
|
height: img.oldHeight + 'px',
|
||||||
|
transform: (img.gpu ? 'translateZ(0) ' : '') + 'translate(' + (offset.x + ox) + 'px, ' + (offset.y + oy) + 'px' + ') rotate(' + rotate +'deg) scale(' + scale + ')'
|
||||||
|
});
|
||||||
|
callMethod(e.instance, 'dataChange', {
|
||||||
|
width: img.width,
|
||||||
|
height: img.height,
|
||||||
|
x: offset.x,
|
||||||
|
y: offset.y,
|
||||||
|
rotate: rotate
|
||||||
|
});
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* 变更裁剪区域布局信息
|
||||||
|
* @param {Object} e 布局信息
|
||||||
|
*/
|
||||||
|
function changeAreaRect(e) {
|
||||||
|
// console.log('changeAreaRect', e)
|
||||||
|
// 变更蒙版样式
|
||||||
|
setStyle(e.instance, 'maskStylesList', [
|
||||||
|
{
|
||||||
|
left: 0,
|
||||||
|
width: (area.left + areaOffset.left) + 'px',
|
||||||
|
top: 0,
|
||||||
|
bottom: 0,
|
||||||
|
'z-index': area.zIndex + 2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
left: (area.right + areaOffset.right) + 'px',
|
||||||
|
right: 0,
|
||||||
|
top: 0,
|
||||||
|
bottom: 0,
|
||||||
|
'z-index': area.zIndex + 2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
left: (area.left + areaOffset.left) + 'px',
|
||||||
|
width: (area.width + areaOffset.right - areaOffset.left) + 'px',
|
||||||
|
top: 0,
|
||||||
|
height: (area.top + areaOffset.top) + 'px',
|
||||||
|
'z-index': area.zIndex + 2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
left: (area.left + areaOffset.left) + 'px',
|
||||||
|
width: (area.width + areaOffset.right - areaOffset.left) + 'px',
|
||||||
|
top: (area.bottom + areaOffset.bottom) + 'px',
|
||||||
|
// height: (area.top - areaOffset.bottom + sys.offsetBottom) + 'px',
|
||||||
|
bottom: 0,
|
||||||
|
'z-index': area.zIndex + 2
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
// 变更边框样式
|
||||||
|
if(area.showBorder) {
|
||||||
|
setStyle(e.instance, 'borderStyles', {
|
||||||
|
left: (area.left + areaOffset.left) + 'px',
|
||||||
|
top: (area.top + areaOffset.top) + 'px',
|
||||||
|
width: (area.width + areaOffset.right - areaOffset.left) + 'px',
|
||||||
|
height: (area.height + areaOffset.bottom - areaOffset.top) + 'px',
|
||||||
|
'z-index': area.zIndex + 3
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 变更参考线样式
|
||||||
|
if(area.showGrid) {
|
||||||
|
setStyle(e.instance, 'gridStylesList', [
|
||||||
|
{
|
||||||
|
'border-width': '1px 0 0 0',
|
||||||
|
left: (area.left + areaOffset.left) + 'px',
|
||||||
|
right: (area.right + areaOffset.right) + 'px',
|
||||||
|
top: (area.top + areaOffset.top + (area.height + areaOffset.bottom - areaOffset.top) / 3 - 0.5) + 'px',
|
||||||
|
width: (area.width + areaOffset.right - areaOffset.left) + 'px',
|
||||||
|
'z-index': area.zIndex + 3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'border-width': '1px 0 0 0',
|
||||||
|
left: (area.left + areaOffset.left) + 'px',
|
||||||
|
right: (area.right + areaOffset.right) + 'px',
|
||||||
|
top: (area.top + areaOffset.top + (area.height + areaOffset.bottom - areaOffset.top) * 2 / 3 - 0.5) + 'px',
|
||||||
|
width: (area.width + areaOffset.right - areaOffset.left) + 'px',
|
||||||
|
'z-index': area.zIndex + 3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'border-width': '0 1px 0 0',
|
||||||
|
top: (area.top + areaOffset.top) + 'px',
|
||||||
|
bottom: (area.bottom + areaOffset.bottom) + 'px',
|
||||||
|
left: (area.left + areaOffset.left + (area.width + areaOffset.right - areaOffset.left) / 3 - 0.5) + 'px',
|
||||||
|
height: (area.height + areaOffset.bottom - areaOffset.top) + 'px',
|
||||||
|
'z-index': area.zIndex + 3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'border-width': '0 1px 0 0',
|
||||||
|
top: (area.top + areaOffset.top) + 'px',
|
||||||
|
bottom: (area.bottom + areaOffset.bottom) + 'px',
|
||||||
|
left: (area.left + areaOffset.left + (area.width + areaOffset.right - areaOffset.left) * 2 / 3 - 0.5) + 'px',
|
||||||
|
height: (area.height + areaOffset.bottom - areaOffset.top) + 'px',
|
||||||
|
'z-index': area.zIndex + 3
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 变更四个伸缩角样式
|
||||||
|
if(area.showAngle) {
|
||||||
|
setStyle(e.instance, 'angleStylesList', [
|
||||||
|
{
|
||||||
|
'border-width': area.angleBorderWidth + 'px 0 0 ' + area.angleBorderWidth + 'px',
|
||||||
|
left: (area.left + areaOffset.left - area.angleBorderWidth) + 'px',
|
||||||
|
top: (area.top + areaOffset.top - area.angleBorderWidth) + 'px',
|
||||||
|
'z-index': area.zIndex + 3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'border-width': area.angleBorderWidth + 'px ' + area.angleBorderWidth + 'px 0 0',
|
||||||
|
left: (area.right + areaOffset.right - area.angleSize) + 'px',
|
||||||
|
top: (area.top + areaOffset.top - area.angleBorderWidth) + 'px',
|
||||||
|
'z-index': area.zIndex + 3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'border-width': '0 0 ' + area.angleBorderWidth + 'px ' + area.angleBorderWidth + 'px',
|
||||||
|
left: (area.left + areaOffset.left - area.angleBorderWidth) + 'px',
|
||||||
|
top: (area.bottom + areaOffset.bottom - area.angleSize) + 'px',
|
||||||
|
'z-index': area.zIndex + 3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'border-width': '0 ' + area.angleBorderWidth + 'px ' + area.angleBorderWidth + 'px 0',
|
||||||
|
left: (area.right + areaOffset.right - area.angleSize) + 'px',
|
||||||
|
top: (area.bottom + areaOffset.bottom - area.angleSize) + 'px',
|
||||||
|
'z-index': area.zIndex + 3
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 变更圆角样式
|
||||||
|
if(area.radius > 0) {
|
||||||
|
var radius = area.radius;
|
||||||
|
if(area.width === area.height && area.radius >= area.width / 2) { // 圆形
|
||||||
|
radius = (area.width / 2);
|
||||||
|
} else { // 圆角矩形
|
||||||
|
if(area.width !== area.height) { // 限制圆角半径不能超过短边的一半
|
||||||
|
radius = Math.min(area.width / 2, area.height / 2, radius);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setStyle(e.instance, 'circleBoxStyles', {
|
||||||
|
left: (area.left + areaOffset.left) + 'px',
|
||||||
|
top: (area.top + areaOffset.top) + 'px',
|
||||||
|
width: (area.width + areaOffset.right - areaOffset.left) + 'px',
|
||||||
|
height: (area.height + areaOffset.bottom - areaOffset.top) + 'px',
|
||||||
|
'z-index': area.zIndex + 2
|
||||||
|
});
|
||||||
|
setStyle(e.instance, 'circleStyles', {
|
||||||
|
'box-shadow': '0 0 0 ' + Math.max(area.width, area.height) + 'px rgba(51, 51, 51, 0.8)',
|
||||||
|
'border-radius': radius + 'px'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* 缩放图片
|
||||||
|
* @param {Object} e 布局信息
|
||||||
|
*/
|
||||||
|
function scaleImage(e) {
|
||||||
|
// console.log('scaleImage', e)
|
||||||
|
var last = scale;
|
||||||
|
scale = Math.min(Math.max(e.scale + scale, minScale), img.maxScale);
|
||||||
|
if(last !== scale) {
|
||||||
|
img.width = num(img.oldWidth * scale);
|
||||||
|
img.height = num(img.oldHeight * scale);
|
||||||
|
// 参考问题:有一个长4000px、宽4000px的四方形ABCD,A点的坐标固定在(-2000,-2000),
|
||||||
|
// 该四边形上有一个点E,坐标为(-100,-300),将该四方形复制一份并缩小到90%后,
|
||||||
|
// 新四边形的A点坐标为多少时可使新四边形的E点与原四边形的E点重合?
|
||||||
|
// 预期效果:从图中选取某点(参照物)为中心点进行缩放,缩放时无论图像怎么变化,该点位置始终固定不变
|
||||||
|
// 计算方法:以相同起点先计算缩放前后两点间的距离,再加上原图像偏移量即可
|
||||||
|
e.x = num((e.x - offset.x) * (1 - scale / last));
|
||||||
|
e.y = num((e.y - offset.y) * (1 - scale / last));
|
||||||
|
changeImageRect(e);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* 获取触摸点在哪个角
|
||||||
|
* @param {number} x 触摸点x轴坐标
|
||||||
|
* @param {number} y 触摸点y轴坐标
|
||||||
|
* @return {number} 角的位置:0=无;1=左上;2=右上;3=左下;4=右下;
|
||||||
|
*/
|
||||||
|
function getToucheAngle(x, y) {
|
||||||
|
// console.log('getToucheAngle', x, y, JSON.stringify(area))
|
||||||
|
var o = area.angleBorderWidth; // 需扩大触发范围则把 o 值加大即可
|
||||||
|
var oy = sys.navigation ? 0 : sys.windowTop;
|
||||||
|
if(y >= area.top - o + oy && y <= area.top + area.angleSize + o + oy) {
|
||||||
|
if(x >= area.left - o && x <= area.left + area.angleSize + o) {
|
||||||
|
return 1; // 左上角
|
||||||
|
} else if(x >= area.right - area.angleSize - o && x <= area.right + o) {
|
||||||
|
return 2; // 右上角
|
||||||
|
}
|
||||||
|
} else if(y >= area.bottom - area.angleSize - o + oy && y <= area.bottom + o + oy) {
|
||||||
|
if(x >= area.left - o && x <= area.left + area.angleSize + o) {
|
||||||
|
return 3; // 左下角
|
||||||
|
} else if(x >= area.right - area.angleSize - o && x <= area.right + o) {
|
||||||
|
return 4; // 右下角
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0; // 无触摸到角
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* 重置数据
|
||||||
|
*/
|
||||||
|
function resetData() {
|
||||||
|
offset = { x: 0, y: 0 };
|
||||||
|
scale = 1;
|
||||||
|
minScale = img.minScale;
|
||||||
|
rotate = 0;
|
||||||
|
};
|
||||||
|
function getTouchs(touches) {
|
||||||
|
var result = [];
|
||||||
|
var len = touches ? touches.length : 0
|
||||||
|
for (var i = 0; i < len; i++) {
|
||||||
|
result[i] = {
|
||||||
|
pageX: touches[i].pageX,
|
||||||
|
// h5无标题栏时,窗口顶部距离仍为标题栏高度,且触摸点y轴坐标还是有标题栏的值,即减去标题栏高度的值
|
||||||
|
pageY: touches[i].pageY + sys.windowTop
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
var mouseEvent = false;
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
imageStyles: {},
|
||||||
|
maskStylesList: [{}, {}, {}, {}],
|
||||||
|
borderStyles: {},
|
||||||
|
gridStylesList: [{}, {}, {}, {}],
|
||||||
|
angleStylesList: [{}, {}, {}, {}],
|
||||||
|
circleBoxStyles: {},
|
||||||
|
circleStyles: {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
// 监听 PC 端鼠标滚轮
|
||||||
|
// #ifdef H5
|
||||||
|
platform === 'H5' && window.addEventListener('mousewheel', async (e) => {
|
||||||
|
var touchs = getTouchs([e])
|
||||||
|
img.src && scaleImage({
|
||||||
|
instance: await this.getInstance(),
|
||||||
|
check: true,
|
||||||
|
// 鼠标向上滚动时,deltaY 固定 -100,鼠标向下滚动时,deltaY 固定 100
|
||||||
|
scale: e.deltaY > 0 ? -0.05 : 0.05,
|
||||||
|
x: touchs[0].pageX,
|
||||||
|
y: touchs[0].pageY
|
||||||
|
});
|
||||||
|
});
|
||||||
|
// #endif
|
||||||
|
},
|
||||||
|
// #ifdef H5
|
||||||
|
mounted() {
|
||||||
|
platform === 'H5' && this.initH5Events();
|
||||||
|
},
|
||||||
|
// #endif
|
||||||
|
setPlatform(p) {
|
||||||
|
platform = p;
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
// #ifdef H5
|
||||||
|
getTouchEvent(e) {
|
||||||
|
e.touches = [
|
||||||
|
{ pageX: e.pageX, pageY: e.pageY }
|
||||||
|
];
|
||||||
|
return e;
|
||||||
|
},
|
||||||
|
initH5Events() {
|
||||||
|
const preview = document.getElementById('pic-preview');
|
||||||
|
preview?.addEventListener('mousedown', (e, ev) => {
|
||||||
|
mouseEvent = true;
|
||||||
|
this.touchstart(this.getTouchEvent(e));
|
||||||
|
});
|
||||||
|
preview?.addEventListener('mousemove', (e) => {
|
||||||
|
if (!mouseEvent) return;
|
||||||
|
this.touchmove(this.getTouchEvent(e));
|
||||||
|
});
|
||||||
|
preview?.addEventListener('mouseup', (e) => {
|
||||||
|
mouseEvent = false;
|
||||||
|
this.touchend(this.getTouchEvent(e))
|
||||||
|
});
|
||||||
|
preview?.addEventListener('mouseleave', (e) => {
|
||||||
|
mouseEvent = false;
|
||||||
|
this.touchend(this.getTouchEvent(e))
|
||||||
|
});
|
||||||
|
},
|
||||||
|
// #endif
|
||||||
|
async getInstance() {
|
||||||
|
// #ifdef APP-PLUS
|
||||||
|
if(platform === 'APP')
|
||||||
|
return this.$ownerInstance
|
||||||
|
? Promise.resolve(this.$ownerInstance)
|
||||||
|
: new Promise((resolve) => {
|
||||||
|
setTimeout(async () => {
|
||||||
|
resolve(await this.getInstance());
|
||||||
|
});
|
||||||
|
});
|
||||||
|
// #endif
|
||||||
|
// #ifdef H5
|
||||||
|
if(platform === 'H5')
|
||||||
|
return Promise.resolve(this);
|
||||||
|
// #endif
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 初始化:观察数据变更
|
||||||
|
* @param {Object} newVal 新数据
|
||||||
|
* @param {Object} oldVal 旧数据
|
||||||
|
* @param {Object} o 组件实例对象
|
||||||
|
*/
|
||||||
|
initObserver: async function(newVal, oldVal, o, i) {
|
||||||
|
// console.log('initObserver', newVal, oldVal, o, i)
|
||||||
|
if(newVal && (!img.src || timestamp !== newVal.timestamp)) {
|
||||||
|
timestamp = newVal.timestamp;
|
||||||
|
img = newVal.img;
|
||||||
|
sys = newVal.sys;
|
||||||
|
area = newVal.area;
|
||||||
|
minScale = img.minScale;
|
||||||
|
resetData();
|
||||||
|
const instance = await this.getInstance()
|
||||||
|
img.src && changeImageRect({
|
||||||
|
instance,
|
||||||
|
x: (sys.windowWidth - img.width) / 2,
|
||||||
|
y: (sys.windowHeight + sys.windowTop - sys.offsetBottom - img.height) / 2
|
||||||
|
});
|
||||||
|
changeAreaRect({
|
||||||
|
instance
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 鼠标滚轮滚动
|
||||||
|
* @param {Object} e 事件对象
|
||||||
|
* @param {Object} o 组件实例对象
|
||||||
|
*/
|
||||||
|
mousewheel: function(e, o) {
|
||||||
|
// h5平台 wheel 事件无法判断滚轮滑动方向,需使用 mousewheel
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 触摸开始
|
||||||
|
* @param {Object} e 事件对象
|
||||||
|
* @param {Object} o 组件实例对象
|
||||||
|
*/
|
||||||
|
touchstart: function(e, o) {
|
||||||
|
if(!img.src) return;
|
||||||
|
touches = getTouchs(e.touches);
|
||||||
|
activeAngle = area.showAngle ? getToucheAngle(touches[0].pageX, touches[0].pageY) : 0;
|
||||||
|
if(touches.length === 1 && activeAngle !== 0) {
|
||||||
|
touchType = 'stretch'; // 伸缩裁剪区域
|
||||||
|
} else {
|
||||||
|
touchType = '';
|
||||||
|
}
|
||||||
|
// console.log('touchstart', e, activeAngle)
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 触摸移动
|
||||||
|
* @param {Object} e 事件对象
|
||||||
|
* @param {Object} o 组件实例对象
|
||||||
|
*/
|
||||||
|
touchmove: async function(e, o) {
|
||||||
|
if(!img.src) return;
|
||||||
|
// console.log('touchmove', e, o)
|
||||||
|
e.touches = getTouchs(e.touches);
|
||||||
|
if(touchType === 'stretch') { // 触摸四个角进行拉伸
|
||||||
|
var point = e.touches[0];
|
||||||
|
var start = touches[0];
|
||||||
|
var x = point.pageX - start.pageX;
|
||||||
|
var y = point.pageY - start.pageY;
|
||||||
|
if(x !== 0 || y !== 0) {
|
||||||
|
var maxX = num(area.width * (1 - area.minScale));
|
||||||
|
var maxY = num(area.height * (1 - area.minScale));
|
||||||
|
// console.log(x, y, maxX, maxY, offset, area)
|
||||||
|
touches[0] = point;
|
||||||
|
var r = rotate / 90 % 2;
|
||||||
|
var m = r === 1 ? num((img.height - img.width) / 2) : 0; // 宽高差值一半
|
||||||
|
var xCompare = r === 1 ? lessThanByFault(img.height, area.width) : lessThanByFault(img.width, area.width);
|
||||||
|
var yCompare = r === 1 ? lessThanByFault(img.width, area.height) : lessThanByFault(img.height, area.height)
|
||||||
|
var isInclude = xCompare && yCompare;
|
||||||
|
var isIntersect = area.checkRange && (xCompare || yCompare); // 图片是否包含在裁剪区域内
|
||||||
|
var isReverse = !isInclude || num((offset.x - area.left) / area.width) <= num((offset.y - area.top) / area.height) || (area.width > area.height && img.width < img.height && r === 1);
|
||||||
|
switch(activeAngle) {
|
||||||
|
case 1: // 左上角
|
||||||
|
x = num(x + areaOffset.left);
|
||||||
|
y = num(y + areaOffset.top);
|
||||||
|
if(x >= 0 && y >= 0) { // 有效滑动
|
||||||
|
var t = num(offset.y + m - area.top);
|
||||||
|
var l = num(offset.x - m - area.left);
|
||||||
|
// && (offset.x + img.width < area.right || offset.y + img.height < area.bottom)
|
||||||
|
var max = isIntersect && ((l >= 0) || (t >= 0))
|
||||||
|
? minimum(t, l)
|
||||||
|
: false;
|
||||||
|
if(x > y && isReverse) { // 以x轴滑动距离为缩放基准
|
||||||
|
maxX = validMax(maxX, max, isInclude, l, t, area.width / area.height);
|
||||||
|
if(x > maxX) x = maxX;
|
||||||
|
y = num(x * area.height / area.width);
|
||||||
|
} else { // 以y轴滑动距离为缩放基准
|
||||||
|
maxY = validMax(maxY, max, isInclude, t, l, area.height / area.width);
|
||||||
|
if(y > maxY) y = maxY;
|
||||||
|
x = num(y * area.width / area.height);
|
||||||
|
}
|
||||||
|
areaOffset.left = x;
|
||||||
|
areaOffset.top = y;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 2: // 右上角
|
||||||
|
x = num(x + areaOffset.right);
|
||||||
|
y = num(y + areaOffset.top);
|
||||||
|
if(x <= 0 && y >= 0) { // 有效滑动
|
||||||
|
var w = (r === 1 ? img.height : img.width);
|
||||||
|
var t = num(offset.y + m - area.top);
|
||||||
|
var l = num(area.right + m - offset.x - w);
|
||||||
|
var max = isIntersect && ((t >= 0) || (l >= 0))
|
||||||
|
? minimum(t, l)
|
||||||
|
: false;
|
||||||
|
if(-x > y && isReverse) { // 以x轴滑动距离为缩放基准
|
||||||
|
maxX = validMax(maxX, max, isInclude, l, t, area.width / area.height);
|
||||||
|
if(-x > maxX) x = -maxX;
|
||||||
|
y = num(-x * area.height / area.width);
|
||||||
|
} else { // 以y轴滑动距离为缩放基准
|
||||||
|
maxY = validMax(maxY, max, isInclude, t, l, area.height / area.width);
|
||||||
|
if(y > maxY) y = maxY;
|
||||||
|
x = num(-y * area.width / area.height);
|
||||||
|
}
|
||||||
|
areaOffset.right = x;
|
||||||
|
areaOffset.top = y;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 3: // 左下角
|
||||||
|
x += num(x + areaOffset.left);
|
||||||
|
y += num(y + areaOffset.bottom);
|
||||||
|
if(x >= 0 && y <= 0) { // 有效滑动
|
||||||
|
var w = (r === 1 ? img.width : img.height);
|
||||||
|
var t = num(area.bottom - m - offset.y - w);
|
||||||
|
var l = num(offset.x - m - area.left);
|
||||||
|
var max = isIntersect && ((l >= 0) || (t >= 0))
|
||||||
|
? minimum(t, l)
|
||||||
|
: false;
|
||||||
|
if(x > -y && isReverse) { // 以x轴滑动距离为缩放基准
|
||||||
|
maxX = validMax(maxX, max, isInclude, l, t, area.width / area.height);
|
||||||
|
if(x > maxX) x = maxX;
|
||||||
|
y = num(-x * area.height / area.width);
|
||||||
|
} else { // 以y轴滑动距离为缩放基准
|
||||||
|
maxY = validMax(maxY, max, isInclude, t, l, area.height / area.width);
|
||||||
|
if(-y > maxY) y = -maxY;
|
||||||
|
x = num(-y * area.width / area.height);
|
||||||
|
}
|
||||||
|
areaOffset.left = x;
|
||||||
|
areaOffset.bottom = y;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 4: // 右下角
|
||||||
|
x = num(x + areaOffset.right);
|
||||||
|
y = num(y + areaOffset.bottom);
|
||||||
|
if(x <= 0 && y <= 0) { // 有效滑动
|
||||||
|
var w = (r === 1 ? img.height : img.width);
|
||||||
|
var h = (r === 1 ? img.width : img.height);
|
||||||
|
var t = num(area.bottom - offset.y - h - m);
|
||||||
|
var l = num(area.right + m - offset.x - w);
|
||||||
|
var max = isIntersect && ((l >= 0) || (t >= 0))
|
||||||
|
? minimum(t, l)
|
||||||
|
: false;
|
||||||
|
if(-x > -y && isReverse) { // 以x轴滑动距离为缩放基准
|
||||||
|
maxX = validMax(maxX, max, isInclude, l, t, area.width / area.height);
|
||||||
|
if(-x > maxX) x = -maxX;
|
||||||
|
y = num(x * area.height / area.width);
|
||||||
|
} else { // 以y轴滑动距离为缩放基准
|
||||||
|
maxY = validMax(maxY, max, isInclude, t, l, area.height / area.width);
|
||||||
|
if(-y > maxY) y = -maxY;
|
||||||
|
x = num(y * area.width / area.height);
|
||||||
|
}
|
||||||
|
areaOffset.right = x;
|
||||||
|
areaOffset.bottom = y;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// console.log(x, y, JSON.stringify(areaOffset))
|
||||||
|
changeAreaRect({
|
||||||
|
instance: await this.getInstance(),
|
||||||
|
});
|
||||||
|
// this.draw();
|
||||||
|
}
|
||||||
|
} else if (e.touches.length == 2) { // 双点触摸缩放
|
||||||
|
var start = getDistanceByTouches(touches);
|
||||||
|
var end = getDistanceByTouches(e.touches);
|
||||||
|
scaleImage({
|
||||||
|
instance: await this.getInstance(),
|
||||||
|
check: !area.bounce,
|
||||||
|
scale: (end.c - start.c) / 100,
|
||||||
|
x: end.x,
|
||||||
|
y: end.y
|
||||||
|
});
|
||||||
|
touchType = 'scale';
|
||||||
|
} else if(touchType === 'scale') {// 从双点触摸变成单点触摸 / 从缩放变成拖动
|
||||||
|
touchType = 'move';
|
||||||
|
} else {
|
||||||
|
changeImageRect({
|
||||||
|
instance: await this.getInstance(),
|
||||||
|
check: !area.bounce,
|
||||||
|
x: e.touches[0].pageX - touches[0].pageX,
|
||||||
|
y: e.touches[0].pageY - touches[0].pageY
|
||||||
|
});
|
||||||
|
touchType = 'move';
|
||||||
|
}
|
||||||
|
touches = e.touches;
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 触摸结束
|
||||||
|
* @param {Object} e 事件对象
|
||||||
|
* @param {Object} o 组件实例对象
|
||||||
|
*/
|
||||||
|
touchend: async function(e, o) {
|
||||||
|
if(!img.src) return;
|
||||||
|
if(touchType === 'stretch') { // 拉伸裁剪区域的四个角缩放
|
||||||
|
// 裁剪区域宽度被缩放到多少
|
||||||
|
var left = areaOffset.left;
|
||||||
|
var right = areaOffset.right;
|
||||||
|
var top = areaOffset.top;
|
||||||
|
var bottom = areaOffset.bottom;
|
||||||
|
var w = area.width + right - left;
|
||||||
|
var h = area.height + bottom - top;
|
||||||
|
// 图像放大倍数
|
||||||
|
var p = scale * (area.width / w) - scale;
|
||||||
|
// 复原裁剪区域
|
||||||
|
areaOffset = { left: 0, right: 0, top: 0, bottom: 0 };
|
||||||
|
changeAreaRect({
|
||||||
|
instance: await this.getInstance(),
|
||||||
|
});
|
||||||
|
scaleImage({
|
||||||
|
instance: await this.getInstance(),
|
||||||
|
scale: p,
|
||||||
|
x: area.left + left + (1 === activeAngle || 3 === activeAngle ? w : 0),
|
||||||
|
y: area.top + top + (1 === activeAngle || 2 === activeAngle ? h : 0)
|
||||||
|
});
|
||||||
|
} else if (area.bounce) { // 检查边界并矫正,实现拖动到边界时有回弹效果
|
||||||
|
changeImageRect({
|
||||||
|
instance: await this.getInstance(),
|
||||||
|
check: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 顺时针翻转图片90°
|
||||||
|
* @param {Object} e 事件对象
|
||||||
|
* @param {Object} o 组件实例对象
|
||||||
|
*/
|
||||||
|
rotateImage: async function(r) {
|
||||||
|
rotate = (rotate + (r || 90)) % 360;
|
||||||
|
|
||||||
|
if(img.minScale >= 1 && area.checkRange) {
|
||||||
|
// 因图片宽高可能不等,翻转后图片宽高需足够填满裁剪区域
|
||||||
|
minScale = 1;
|
||||||
|
if(img.width < area.height) {
|
||||||
|
minScale = area.height / img.oldWidth;
|
||||||
|
} else if(img.height < area.width) {
|
||||||
|
minScale = area.width / img.oldHeight;
|
||||||
|
}
|
||||||
|
if(minScale !== 1) {
|
||||||
|
scaleImage({
|
||||||
|
instance: await this.getInstance(),
|
||||||
|
scale: minScale - scale,
|
||||||
|
x: sys.windowWidth / 2,
|
||||||
|
y: (sys.windowHeight - sys.offsetBottom) / 2
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 由于拖动画布后会导致图片位置偏移,翻转时的旋转中心点需是图片区域+偏移区域的中心点
|
||||||
|
// 翻转x轴中心点 = (超出裁剪区域右侧的图片宽度 - 超出裁剪区域左侧的图片宽度) / 2
|
||||||
|
// 翻转y轴中心点 = (超出裁剪区域下方的图片宽度 - 超出裁剪区域上方的图片宽度) / 2
|
||||||
|
var ox = ((offset.x + img.width - area.right) - (area.left - offset.x)) / 2;
|
||||||
|
var oy = ((offset.y + img.height - area.bottom) - (area.top - offset.y)) / 2;
|
||||||
|
changeImageRect({
|
||||||
|
instance: await this.getInstance(),
|
||||||
|
check: true,
|
||||||
|
x: -ox - oy,
|
||||||
|
y: -oy + ox
|
||||||
|
});
|
||||||
|
},
|
||||||
|
rotateImage90: function() {
|
||||||
|
this.rotateImage(90)
|
||||||
|
},
|
||||||
|
rotateImage270: function() {
|
||||||
|
this.rotateImage(270)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,746 @@
|
|||||||
|
<template>
|
||||||
|
<view class="image-cropper" :style="{ zIndex }" @wheel="cropper.mousewheel">
|
||||||
|
<slot name="slotNamess" />
|
||||||
|
<canvas v-if="use2d" type="2d" id="imgCanvas" class="img-canvas" :style="{
|
||||||
|
width: `${canvansWidth}px`,
|
||||||
|
height: `${canvansHeight}px`
|
||||||
|
}"></canvas>
|
||||||
|
<canvas v-else id="imgCanvas" canvas-id="imgCanvas" class="img-canvas" :style="{
|
||||||
|
width: `${canvansWidth}px`,
|
||||||
|
height: `${canvansHeight}px`
|
||||||
|
}"></canvas>
|
||||||
|
<view id="pic-preview" class="pic-preview" :change:init="cropper.initObserver" :init="initData" @touchstart="cropper.touchstart" @touchmove="cropper.touchmove" @touchend="cropper.touchend">
|
||||||
|
<image v-if="imgSrc" id="crop-image" class="crop-image" :style="cropper.imageStyles" :src="imgSrc" webp></image>
|
||||||
|
<view v-for="(item, index) in maskList" :key="item.id" :id="item.id" class="crop-mask-block" :style="cropper.maskStylesList[index]"></view>
|
||||||
|
<view v-if="showBorder" id="crop-border" class="crop-border" :style="cropper.borderStyles"></view>
|
||||||
|
<view v-if="radius > 0" id="crop-circle-box" class="crop-circle-box" :style="cropper.circleBoxStyles">
|
||||||
|
<view class="crop-circle" id="crop-circle" :style="cropper.circleStyles"></view>
|
||||||
|
</view>
|
||||||
|
<block v-if="showGrid">
|
||||||
|
<view v-for="(item, index) in gridList" :key="item.id" :id="item.id" class="crop-grid" :style="cropper.gridStylesList[index]"></view>
|
||||||
|
</block>
|
||||||
|
<block v-if="showAngle">
|
||||||
|
<view v-for="(item, index) in angleList" :key="item.id" :id="item.id" class="crop-angle" :style="cropper.angleStylesList[index]">
|
||||||
|
<view :style="[{
|
||||||
|
width: `${angleSize}px`,
|
||||||
|
height: `${angleSize}px`
|
||||||
|
}]"></view>
|
||||||
|
</view>
|
||||||
|
</block>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="fixed-bottom safe-area-inset-bottom" :style="{ zIndex: initData.area.zIndex + 99 }">
|
||||||
|
<!-- <view class="choose-btn" @click="cancleChoose">取消</view> -->
|
||||||
|
<view v-if="(rotatable || reverseRotatable) && !!imgSrc" class="action-bar">
|
||||||
|
<view v-if="reverseRotatable" class="rotate-icon" @click="cropper.rotateImage270"></view>
|
||||||
|
<view v-if="rotatable" class="rotate-icon is-reverse" @click="cropper.rotateImage90"></view>
|
||||||
|
</view>
|
||||||
|
<view v-if="!choosable" class="choose-btn" @click="cropClick">确定</view>
|
||||||
|
<block v-else-if="!!imgSrc">
|
||||||
|
<view class="rechoose" @click="chooseImage">重选</view>
|
||||||
|
<button class="button" size="mini" @click="cropClick">确定</button>
|
||||||
|
</block>
|
||||||
|
<view v-else class="choose-btn" @click="chooseImage">选择图片</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- #ifdef APP-VUE -->
|
||||||
|
<script module="cropper" lang="renderjs">
|
||||||
|
import cropper from './qf-image-cropper.render.js';
|
||||||
|
// vue3 app renderjs中条件编译无效
|
||||||
|
cropper.setPlatform('APP');
|
||||||
|
export default {
|
||||||
|
mixins: [ cropper ]
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<!-- #endif -->
|
||||||
|
<!-- #ifdef H5 -->
|
||||||
|
<script module="cropper" lang="renderjs">
|
||||||
|
import cropper from './qf-image-cropper.render.js';
|
||||||
|
export default {
|
||||||
|
mixins: [ cropper ]
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<!-- #endif -->
|
||||||
|
<!-- #ifdef MP-WEIXIN || MP-QQ -->
|
||||||
|
<script module="cropper" lang="wxs" src="./qf-image-cropper.wxs"></script>
|
||||||
|
<!-- #endif -->
|
||||||
|
<script>
|
||||||
|
/** 裁剪区域最大宽高所占屏幕宽度百分比 */
|
||||||
|
const AREA_SIZE = 75;
|
||||||
|
/** 图片默认宽高 */
|
||||||
|
const IMG_SIZE = 300;
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name:"qf-image-cropper",
|
||||||
|
// #ifdef MP-WEIXIN
|
||||||
|
options: {
|
||||||
|
// 表示启用样式隔离,在自定义组件内外,使用 class 指定的样式将不会相互影响
|
||||||
|
styleIsolation: "isolated"
|
||||||
|
},
|
||||||
|
// #endif
|
||||||
|
props: {
|
||||||
|
/** 图片资源地址 */
|
||||||
|
src: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
/** 裁剪宽度,有些平台或设备对于canvas的尺寸有限制,过大可能会导致无法正常绘制 */
|
||||||
|
width: {
|
||||||
|
type: Number,
|
||||||
|
default: IMG_SIZE
|
||||||
|
},
|
||||||
|
/** 裁剪高度,有些平台或设备对于canvas的尺寸有限制,过大可能会导致无法正常绘制 */
|
||||||
|
height: {
|
||||||
|
type: Number,
|
||||||
|
default: IMG_SIZE
|
||||||
|
},
|
||||||
|
/** 是否绘制裁剪区域边框 */
|
||||||
|
showBorder: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
/** 是否绘制裁剪区域网格参考线 */
|
||||||
|
showGrid: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
/** 是否展示四个支持伸缩的角 */
|
||||||
|
showAngle: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
/** 裁剪区域最小缩放倍数 */
|
||||||
|
areaScale: {
|
||||||
|
type: Number,
|
||||||
|
default: 0.3
|
||||||
|
},
|
||||||
|
/** 图片最小缩放倍数 */
|
||||||
|
minScale: {
|
||||||
|
type: Number,
|
||||||
|
default: 1
|
||||||
|
},
|
||||||
|
/** 图片最大缩放倍数 */
|
||||||
|
maxScale: {
|
||||||
|
type: Number,
|
||||||
|
default: 5
|
||||||
|
},
|
||||||
|
/** 检查图片位置是否超出裁剪边界,如果超出则会矫正位置 */
|
||||||
|
checkRange: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
/** 生成图片背景色:如果裁剪区域没有完全包含在图片中时,不设置该属性生成图片存在一定的透明块 */
|
||||||
|
backgroundColor: {
|
||||||
|
type: String
|
||||||
|
},
|
||||||
|
/** 是否有回弹效果:当 checkRange 为 true 时有效,拖动时可以拖出边界,释放时会弹回边界 */
|
||||||
|
bounce: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
/** 是否支持翻转 */
|
||||||
|
rotatable: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
/** 是否支持逆向翻转 */
|
||||||
|
reverseRotatable: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
/** 是否支持从本地选择素材 */
|
||||||
|
choosable: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
/** 是否开启硬件加速,图片缩放过程中如果出现元素的“留影”或“重影”效果,可通过该方式解决或减轻这一问题 */
|
||||||
|
gpu: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
/** 四个角尺寸,单位px */
|
||||||
|
angleSize: {
|
||||||
|
type: Number,
|
||||||
|
default: 20
|
||||||
|
},
|
||||||
|
/** 四个角边框宽度,单位px */
|
||||||
|
angleBorderWidth: {
|
||||||
|
type: Number,
|
||||||
|
default: 2
|
||||||
|
},
|
||||||
|
zIndex: {
|
||||||
|
type: [Number, String]
|
||||||
|
},
|
||||||
|
/** 裁剪图片圆角半径,单位px */
|
||||||
|
radius: {
|
||||||
|
type: Number,
|
||||||
|
default: 0
|
||||||
|
},
|
||||||
|
/** 生成文件的类型,只支持 'jpg' 或 'png'。默认为 'png' */
|
||||||
|
fileType: {
|
||||||
|
type: String,
|
||||||
|
default: 'png'
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 图片从绘制到生成所需时间,单位ms
|
||||||
|
* 微信小程序平台使用 `Canvas 2D` 绘制时有效
|
||||||
|
* 如绘制大图或出现裁剪图片空白等情况应适当调大该值,因 `Canvas 2d` 采用同步绘制,需自己把控绘制完成时间
|
||||||
|
*/
|
||||||
|
delay: {
|
||||||
|
type: Number,
|
||||||
|
default: 1000
|
||||||
|
},
|
||||||
|
// #ifdef H5
|
||||||
|
/**
|
||||||
|
* 页面是否是原生标题栏
|
||||||
|
* H5平台当 showAngle 为 true 时,使用插件的页面在 `page.json` 中配置了 "navigationStyle": "custom" 时,必须将此值设为 false ,否则四个可拉伸角的触发位置会有偏差。
|
||||||
|
* 注:因H5平台的窗口高度是包含标题栏的,而屏幕触摸点的坐标是不包含的
|
||||||
|
*/
|
||||||
|
navigation: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
}
|
||||||
|
// #endif
|
||||||
|
},
|
||||||
|
emits: ["crop"],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
// 用不同 id 使 v-for key 不重复
|
||||||
|
maskList: [
|
||||||
|
{ id: 'crop-mask-block-1' },
|
||||||
|
{ id: 'crop-mask-block-2' },
|
||||||
|
{ id: 'crop-mask-block-3' },
|
||||||
|
{ id: 'crop-mask-block-4' },
|
||||||
|
],
|
||||||
|
gridList: [
|
||||||
|
{ id: 'crop-grid-1' },
|
||||||
|
{ id: 'crop-grid-2' },
|
||||||
|
{ id: 'crop-grid-3' },
|
||||||
|
{ id: 'crop-grid-4' },
|
||||||
|
],
|
||||||
|
angleList: [
|
||||||
|
{ id: 'crop-angle-1' },
|
||||||
|
{ id: 'crop-angle-2' },
|
||||||
|
{ id: 'crop-angle-3' },
|
||||||
|
{ id: 'crop-angle-4' },
|
||||||
|
],
|
||||||
|
/** 本地缓存的图片路径 */
|
||||||
|
imgSrc: '',
|
||||||
|
/** 图片的裁剪宽度 */
|
||||||
|
imgWidth: IMG_SIZE,
|
||||||
|
/** 图片的裁剪高度 */
|
||||||
|
imgHeight: IMG_SIZE,
|
||||||
|
/** 裁剪区域最大宽度所占屏幕宽度百分比 */
|
||||||
|
widthPercent: AREA_SIZE,
|
||||||
|
/** 裁剪区域最大高度所占屏幕宽度百分比 */
|
||||||
|
heightPercent: AREA_SIZE,
|
||||||
|
/** 裁剪区域布局信息 */
|
||||||
|
area: {},
|
||||||
|
/** 未被缩放过的图片宽 */
|
||||||
|
oldWidth: 0,
|
||||||
|
/** 未被缩放过的图片高 */
|
||||||
|
oldHeight: 0,
|
||||||
|
/** 系统信息 */
|
||||||
|
sys: uni.getSystemInfoSync(),
|
||||||
|
scaleWidth: 0,
|
||||||
|
scaleHeight: 0,
|
||||||
|
rotate: 0,
|
||||||
|
offsetX: 0,
|
||||||
|
offsetY: 0,
|
||||||
|
use2d: false,
|
||||||
|
canvansWidth: 0,
|
||||||
|
canvansHeight: 0,
|
||||||
|
// imageStyles: {},
|
||||||
|
// maskStylesList: [{}, {}, {}, {}],
|
||||||
|
// borderStyles: {},
|
||||||
|
// gridStylesList: [{}, {}, {}, {}],
|
||||||
|
// angleStylesList: [{}, {}, {}, {}],
|
||||||
|
// circleBoxStyles: {},
|
||||||
|
// circleStyles: {},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
initData() {
|
||||||
|
// console.log('initData')
|
||||||
|
return {
|
||||||
|
timestamp: new Date().getTime(),
|
||||||
|
area: {
|
||||||
|
...this.area,
|
||||||
|
bounce: this.bounce,
|
||||||
|
showBorder: this.showBorder,
|
||||||
|
showGrid: this.showGrid,
|
||||||
|
showAngle: this.showAngle,
|
||||||
|
angleSize: this.angleSize,
|
||||||
|
angleBorderWidth: this.angleBorderWidth,
|
||||||
|
minScale: this.areaScale,
|
||||||
|
widthPercent: this.widthPercent,
|
||||||
|
heightPercent: this.heightPercent,
|
||||||
|
radius: this.radius,
|
||||||
|
checkRange: this.checkRange,
|
||||||
|
zIndex: +this.zIndex || 0,
|
||||||
|
},
|
||||||
|
sys: this.sys,
|
||||||
|
img: {
|
||||||
|
minScale: this.minScale,
|
||||||
|
maxScale: this.maxScale,
|
||||||
|
src: this.imgSrc,
|
||||||
|
width: this.oldWidth,
|
||||||
|
height: this.oldHeight,
|
||||||
|
oldWidth: this.oldWidth,
|
||||||
|
oldHeight: this.oldHeight,
|
||||||
|
gpu: this.gpu,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
imgProps() {
|
||||||
|
return {
|
||||||
|
width: this.width,
|
||||||
|
height: this.height,
|
||||||
|
src: this.src,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
imgProps: {
|
||||||
|
handler(val, oldVal) {
|
||||||
|
// 自定义裁剪尺,示例如下:
|
||||||
|
this.imgWidth = Number(val.width) || IMG_SIZE;
|
||||||
|
this.imgHeight = Number(val.height) || IMG_SIZE;
|
||||||
|
let use2d = true;
|
||||||
|
// #ifndef MP-WEIXIN
|
||||||
|
use2d = false;
|
||||||
|
// #endif
|
||||||
|
// if(use2d && (this.imgWidth > 1365 || this.imgHeight > 1365)) {
|
||||||
|
// use2d = false;
|
||||||
|
// }
|
||||||
|
let canvansWidth = this.imgWidth;
|
||||||
|
let canvansHeight = this.imgHeight;
|
||||||
|
let size = Math.max(canvansWidth, canvansHeight)
|
||||||
|
let scalc = 1;
|
||||||
|
if(size > 1365) {
|
||||||
|
scalc = 1365 / size;
|
||||||
|
}
|
||||||
|
this.canvansWidth = canvansWidth * scalc;
|
||||||
|
this.canvansHeight = canvansHeight * scalc;
|
||||||
|
this.use2d = use2d;
|
||||||
|
this.initArea();
|
||||||
|
const src = val.src || this.imgSrc;
|
||||||
|
src && this.initImage(src, oldVal === undefined);
|
||||||
|
},
|
||||||
|
immediate: true
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
/** 提供给wxs调用,用来接收图片变更数据 */
|
||||||
|
dataChange(e) {
|
||||||
|
// console.log('dataChange', e)
|
||||||
|
this.scaleWidth = e.width;
|
||||||
|
this.scaleHeight = e.height;
|
||||||
|
this.rotate = e.rotate;
|
||||||
|
this.offsetX = e.x;
|
||||||
|
this.offsetY = e.y;
|
||||||
|
},
|
||||||
|
/** 初始化裁剪区域布局信息 */
|
||||||
|
initArea() {
|
||||||
|
// 底部操作栏高度 = 底部底部操作栏内容高度 + 设备底部安全区域高度
|
||||||
|
this.sys.offsetBottom = uni.upx2px(100) + this.sys.safeAreaInsets.bottom;
|
||||||
|
// #ifndef H5
|
||||||
|
this.sys.windowTop = 0;
|
||||||
|
this.sys.navigation = true;
|
||||||
|
// #endif
|
||||||
|
// #ifdef H5
|
||||||
|
// h5平台的窗口高度是包含标题栏的
|
||||||
|
this.sys.windowTop = this.sys.windowTop || 44;
|
||||||
|
this.sys.navigation = this.navigation;
|
||||||
|
// #endif
|
||||||
|
let wp = this.widthPercent;
|
||||||
|
let hp = this.heightPercent;
|
||||||
|
if (this.imgWidth > this.imgHeight) {
|
||||||
|
hp = hp * this.imgHeight / this.imgWidth;
|
||||||
|
} else if (this.imgWidth < this.imgHeight) {
|
||||||
|
wp = wp * this.imgWidth / this.imgHeight;
|
||||||
|
}
|
||||||
|
const size = this.sys.windowWidth > this.sys.windowHeight ? this.sys.windowHeight : this.sys.windowWidth;
|
||||||
|
const width = size * wp / 100;
|
||||||
|
const height = size * hp / 100;
|
||||||
|
const left = (this.sys.windowWidth - width) / 2;
|
||||||
|
const right = left + width;
|
||||||
|
const top = (this.sys.windowHeight + this.sys.windowTop - this.sys.offsetBottom - height) / 2;
|
||||||
|
const bottom = this.sys.windowHeight + this.sys.windowTop - this.sys.offsetBottom - top;
|
||||||
|
this.area = { width, height, left, right, top, bottom };
|
||||||
|
this.scaleWidth = width;
|
||||||
|
this.scaleHeight = height;
|
||||||
|
},
|
||||||
|
/** 从本地选取图片 */
|
||||||
|
chooseImage(options) {
|
||||||
|
// #ifdef MP-WEIXIN || MP-JD
|
||||||
|
if(uni.chooseMedia) {
|
||||||
|
uni.chooseMedia({
|
||||||
|
...options,
|
||||||
|
count: 1,
|
||||||
|
mediaType: ['image'],
|
||||||
|
success: (res) => {
|
||||||
|
this.resetData();
|
||||||
|
this.initImage(res.tempFiles[0].tempFilePath);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// #endif
|
||||||
|
uni.chooseImage({
|
||||||
|
...options,
|
||||||
|
count: 1,
|
||||||
|
success: (res) => {
|
||||||
|
this.resetData();
|
||||||
|
this.initImage(res.tempFiles[0].path);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/** 重置数据 */
|
||||||
|
resetData() {
|
||||||
|
this.imgSrc = '';
|
||||||
|
this.rotate = 0;
|
||||||
|
this.offsetX = 0;
|
||||||
|
this.offsetY = 0;
|
||||||
|
this.initArea();
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 初始化图片信息
|
||||||
|
* @param {String} url 图片链接
|
||||||
|
*/
|
||||||
|
initImage(url, isFirst) {
|
||||||
|
uni.getImageInfo({
|
||||||
|
src: url,
|
||||||
|
success: async (res) => {
|
||||||
|
if (isFirst && this.src === url) await (new Promise((resolve) => setTimeout(resolve, 50)));
|
||||||
|
this.imgSrc = res.path;
|
||||||
|
let scale = res.width / res.height;
|
||||||
|
let areaScale = this.area.width / this.area.height;
|
||||||
|
if (scale > 1) { // 横向图片
|
||||||
|
if (scale >= areaScale) { // 图片宽不小于目标宽,则高固定,宽自适应
|
||||||
|
this.scaleWidth = (this.scaleHeight / res.height) * this.scaleWidth * (res.width / this.scaleWidth);
|
||||||
|
} else { // 否则宽固定、高自适应
|
||||||
|
this.scaleHeight = res.height * this.scaleWidth / res.width;
|
||||||
|
}
|
||||||
|
} else { // 纵向图片
|
||||||
|
if (scale <= areaScale) { // 图片高不小于目标高,宽固定,高自适应
|
||||||
|
this.scaleHeight = (this.scaleWidth / res.width) * this.scaleHeight / (this.scaleHeight / res.height);
|
||||||
|
} else { // 否则高固定,宽自适应
|
||||||
|
this.scaleWidth = res.width * this.scaleHeight / res.height;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 记录原始宽高,为缩放比列做限制
|
||||||
|
this.oldWidth = +this.scaleWidth.toFixed(2);
|
||||||
|
this.oldHeight = +this.scaleHeight.toFixed(2);
|
||||||
|
},
|
||||||
|
fail: (err) => {
|
||||||
|
console.error(err)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 剪切图片圆角
|
||||||
|
* @param {Object} ctx canvas 的绘图上下文对象
|
||||||
|
* @param {Number} radius 圆角半径
|
||||||
|
* @param {Number} scale 生成图片的实际尺寸与截取区域比
|
||||||
|
* @param {Function} drawImage 执行剪切时所调用的绘图方法,入参为是否执行了剪切
|
||||||
|
*/
|
||||||
|
drawClipImage(ctx, radius, scale, drawImage) {
|
||||||
|
if(radius > 0) {
|
||||||
|
ctx.save();
|
||||||
|
ctx.beginPath();
|
||||||
|
const w = this.canvansWidth;
|
||||||
|
const h = this.canvansHeight;
|
||||||
|
if(w === h && radius >= w / 2) { // 圆形
|
||||||
|
ctx.arc(w / 2, h / 2, w / 2, 0, 2 * Math.PI);
|
||||||
|
} else { // 圆角矩形
|
||||||
|
if(w !== h) { // 限制圆角半径不能超过短边的一半
|
||||||
|
radius = Math.min(w / 2, h / 2, radius);
|
||||||
|
// radius = Math.min(Math.max(w, h) / 2, radius);
|
||||||
|
}
|
||||||
|
ctx.moveTo(radius, 0);
|
||||||
|
ctx.arcTo(w, 0, w, h, radius);
|
||||||
|
ctx.arcTo(w, h, 0, h, radius);
|
||||||
|
ctx.arcTo(0, h, 0, 0, radius);
|
||||||
|
ctx.arcTo(0, 0, w, 0, radius);
|
||||||
|
ctx.closePath();
|
||||||
|
}
|
||||||
|
ctx.clip();
|
||||||
|
drawImage && drawImage(true);
|
||||||
|
ctx.restore();
|
||||||
|
} else {
|
||||||
|
drawImage && drawImage(false);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 旋转图片
|
||||||
|
* @param {Object} ctx canvas 的绘图上下文对象
|
||||||
|
* @param {Number} rotate 旋转角度
|
||||||
|
* @param {Number} scale 生成图片的实际尺寸与截取区域比
|
||||||
|
*/
|
||||||
|
drawRotateImage(ctx, rotate, scale) {
|
||||||
|
if(rotate !== 0) {
|
||||||
|
// 1. 以图片中心点为旋转中心点
|
||||||
|
const x = this.scaleWidth * scale / 2;
|
||||||
|
const y = this.scaleHeight * scale / 2;
|
||||||
|
ctx.translate(x, y);
|
||||||
|
// 2. 旋转画布
|
||||||
|
ctx.rotate(rotate * Math.PI / 180);
|
||||||
|
// 3. 旋转完画布后恢复设置旋转中心时所做的偏移
|
||||||
|
ctx.translate(-x, -y);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
drawImage(ctx, image, callback) {
|
||||||
|
// 生成图片的实际尺寸与截取区域比
|
||||||
|
const scale = this.canvansWidth / this.area.width;
|
||||||
|
if(this.backgroundColor) {
|
||||||
|
if(ctx.setFillStyle) ctx.setFillStyle(this.backgroundColor);
|
||||||
|
else ctx.fillStyle = this.backgroundColor;
|
||||||
|
ctx.fillRect(0, 0, this.canvansWidth, this.canvansHeight);
|
||||||
|
}
|
||||||
|
this.drawClipImage(ctx, this.radius, scale, () => {
|
||||||
|
this.drawRotateImage(ctx, this.rotate, scale);
|
||||||
|
const r = this.rotate / 90;
|
||||||
|
ctx.drawImage(
|
||||||
|
image,
|
||||||
|
[
|
||||||
|
(this.offsetX - this.area.left),
|
||||||
|
(this.offsetY - this.area.top),
|
||||||
|
-(this.offsetX - this.area.left),
|
||||||
|
-(this.offsetY - this.area.top)
|
||||||
|
][r] * scale,
|
||||||
|
[
|
||||||
|
(this.offsetY - this.area.top),
|
||||||
|
-(this.offsetX - this.area.left),
|
||||||
|
-(this.offsetY - this.area.top),
|
||||||
|
(this.offsetX - this.area.left)
|
||||||
|
][r] * scale,
|
||||||
|
this.scaleWidth * scale,
|
||||||
|
this.scaleHeight * scale
|
||||||
|
);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 绘图
|
||||||
|
* @param {Object} canvas
|
||||||
|
* @param {Object} ctx canvas 的绘图上下文对象
|
||||||
|
* @param {String} src 图片路径
|
||||||
|
* @param {Function} callback 开始绘制时回调
|
||||||
|
*/
|
||||||
|
draw2DImage(canvas, ctx, src, callback) {
|
||||||
|
// console.log('draw2DImage', canvas, ctx, src, callback)
|
||||||
|
if(canvas) {
|
||||||
|
const image = canvas.createImage();
|
||||||
|
image.onload = () => {
|
||||||
|
this.drawImage(ctx, image);
|
||||||
|
// 如果觉得`生成时间过长`或`出现生成图片空白`可尝试调整延迟时间
|
||||||
|
callback && setTimeout(callback, this.delay);
|
||||||
|
};
|
||||||
|
image.onerror = (err) => {
|
||||||
|
console.error(err)
|
||||||
|
uni.hideLoading();
|
||||||
|
};
|
||||||
|
image.src = src;
|
||||||
|
} else {
|
||||||
|
this.drawImage(ctx, src);
|
||||||
|
setTimeout(() => {
|
||||||
|
ctx.draw(false, callback);
|
||||||
|
}, 200);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 画布转图片到本地缓存
|
||||||
|
* @param {Object} canvas
|
||||||
|
* @param {String} canvasId
|
||||||
|
*/
|
||||||
|
canvasToTempFilePath(canvas, canvasId) {
|
||||||
|
// console.log('canvasToTempFilePath', canvas, canvasId)
|
||||||
|
uni.canvasToTempFilePath({
|
||||||
|
canvas,
|
||||||
|
canvasId,
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
width: this.canvansWidth,
|
||||||
|
height: this.canvansHeight,
|
||||||
|
destWidth: this.imgWidth, // 必要,保证生成图片宽度不受设备分辨率影响
|
||||||
|
destHeight: this.imgHeight, // 必要,保证生成图片高度不受设备分辨率影响
|
||||||
|
fileType: this.fileType, // 目标文件的类型,默认png
|
||||||
|
success: (res) => {
|
||||||
|
// 生成的图片临时文件路径
|
||||||
|
this.handleImage(res.tempFilePath);
|
||||||
|
},
|
||||||
|
fail: (err) => {
|
||||||
|
uni.hideLoading();
|
||||||
|
uni.showToast({ title: '裁剪失败,生成图片异常!', icon: 'none' });
|
||||||
|
}
|
||||||
|
}, this);
|
||||||
|
},
|
||||||
|
/** 确认裁剪 */
|
||||||
|
cropClick() {
|
||||||
|
uni.showLoading({ title: '裁剪中...', mask: true });
|
||||||
|
if(!this.use2d) {
|
||||||
|
const ctx = uni.createCanvasContext('imgCanvas', this);
|
||||||
|
ctx.clearRect(0, 0, this.canvansWidth, this.canvansHeight);
|
||||||
|
this.draw2DImage(null, ctx, this.imgSrc, () => {
|
||||||
|
this.canvasToTempFilePath(null, 'imgCanvas');
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// #ifdef MP-WEIXIN
|
||||||
|
const query = uni.createSelectorQuery().in(this);
|
||||||
|
query.select('#imgCanvas')
|
||||||
|
.fields({ node: true, size: true })
|
||||||
|
.exec((res) => {
|
||||||
|
const canvas = res[0].node;
|
||||||
|
|
||||||
|
const dpr = uni.getSystemInfoSync().pixelRatio;
|
||||||
|
canvas.width = res[0].width * dpr;
|
||||||
|
canvas.height = res[0].height * dpr;
|
||||||
|
const ctx = canvas.getContext('2d');
|
||||||
|
ctx.scale(dpr, dpr);
|
||||||
|
ctx.clearRect(0, 0, this.canvansWidth, this.canvansHeight);
|
||||||
|
|
||||||
|
this.draw2DImage(canvas, ctx, this.imgSrc, () => {
|
||||||
|
this.canvasToTempFilePath(canvas);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
// #endif
|
||||||
|
},
|
||||||
|
handleImage(tempFilePath){
|
||||||
|
// 在H5平台下,tempFilePath 为 base64
|
||||||
|
// console.log(tempFilePath)
|
||||||
|
uni.hideLoading();
|
||||||
|
this.$emit('crop', { tempFilePath });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.image-cropper {
|
||||||
|
position: fixed;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
background-color: #000;
|
||||||
|
.img-canvas {
|
||||||
|
position: absolute !important;
|
||||||
|
transform: translateX(-100%);
|
||||||
|
}
|
||||||
|
.pic-preview {
|
||||||
|
width: 100%;
|
||||||
|
flex: 1;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
.crop-mask-block {
|
||||||
|
background-color: rgba(51, 51, 51, 0.8);
|
||||||
|
z-index: 2;
|
||||||
|
position: fixed;
|
||||||
|
box-sizing: border-box;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
.crop-circle-box {
|
||||||
|
position: fixed;
|
||||||
|
box-sizing: border-box;
|
||||||
|
z-index: 2;
|
||||||
|
pointer-events: none;
|
||||||
|
overflow: hidden;
|
||||||
|
.crop-circle {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.crop-image {
|
||||||
|
padding: 0 !important;
|
||||||
|
margin: 0 !important;
|
||||||
|
border-radius: 0 !important;
|
||||||
|
display: block !important;
|
||||||
|
backface-visibility: hidden;
|
||||||
|
}
|
||||||
|
.crop-border {
|
||||||
|
position: fixed;
|
||||||
|
border: 1px solid #fff;
|
||||||
|
box-sizing: border-box;
|
||||||
|
z-index: 3;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
.crop-grid {
|
||||||
|
position: fixed;
|
||||||
|
z-index: 3;
|
||||||
|
border-style: dashed;
|
||||||
|
border-color: #fff;
|
||||||
|
pointer-events: none;
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
.crop-angle {
|
||||||
|
position: fixed;
|
||||||
|
z-index: 3;
|
||||||
|
border-style: solid;
|
||||||
|
border-color: #fff;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.fixed-bottom {
|
||||||
|
position: fixed;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
z-index: 99;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
background-color: $uni-bg-color-grey;
|
||||||
|
|
||||||
|
.action-bar {
|
||||||
|
position: absolute;
|
||||||
|
top: -90rpx;
|
||||||
|
left: 10rpx;
|
||||||
|
display: flex;
|
||||||
|
.rotate-icon {
|
||||||
|
background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAAAXNSR0IArs4c6QAABCFJREFUaEPtml3IpVMUx3//ko/ChTIyiGFSMyhllI8bc4F85yuNC2FCqLmQC1+FZORiEkUMNW7UjKjJULgxV+NzSkxDhEkZgwsyigv119J63p7zvOc8z37OmXdOb51dz82711r7/99r7bXXXucVi3xokeNnRqCvB20fDmwAlgK/5bcD+FTSr33tHXQP2H4MeHQE0A+B5yRtLiUyDQJrgVc6AAaBpyV93kXkoBMIQLbfBS5NcK8BRwDXNcD+AdwnaVMbiWkRCPBBohpxHuK7M7865sclRdgNHVMhkF6IMIpwirFEUhzo8M7lwIvASTXEqyVtH8ZgagQSbOzsDknv18HZXpHn5IL8+94IOUm7miSmSqAttjPdbgGuTrnNktYsGgLpoYuAD2qg1zRTbG8P2D4SOC6/Q7vSHPALsE/S7wWy80RsPw/ckxMfSTq/LtRJwPbxwF3ASiCUTxwHCPAnEBfVF8AWSTtL7Ng+LfWOTfmlkn6udFsJ5K15R6a4kvX6yGyUFBvTOWzHXXFzCt4g6c1OArYj9iIGh43YgR+BvztXh1PSa4cMkd0jaVmXDduPAE+k3HpJD7cSGFKvfAc8FQUX8IOk/V2L1udtB/hTgdOBW4Aba/M7Ja1qs2f7euCNlHlZUlx4/495IWQ7Jl+qGbxX0gt9AHfJ2o6zFBVoNVrDKe+F3Sm8VdK1bQQ+A85JgXckXdkFaJx527cC9TpnVdvBtl3h2iapuhsGPdBw1b9xnUvaNw7AEh3bnwDnpuwGSfeP0rN9NvAMELXRXFkxEEK2nwQeSiOtRVQJwC4Z29cAW1Nuu6TVXTrN+SaBt4ErUug2Sa/2NdhH3vZy4NvU2S/p6D768w5xI3WOrAD7LtISFpGdIhVXKfaYvjd20wP13L9M0p4DBbaFRKToSLExVkr6qs+aIwlI6iwz+izUQqC+ab29PiMwqRcmPXczD8w8MFj1zg7xXEqbpdHCw7FgWSjafZL+KcQxtpjteCeflwYulFR/J3TabSslVkj6utPChAK2f6q9uZdLitKieLQRuExSvX9ZbLRUMFs09efpUZL+KtUfVo1GW/umNHC3pOhRLtiwfSbwZS6wV9IJfRdreuBBYH0a2STp9r4G+8jbXgc8mzoDT8VSO00ClwDv1ZR7XyylC4ec7ejaLUmdsV6Aw7oSbwFXpdFdks7qA6pU1na0aR6owgeIR/1cx63UzjAC0YXYVjMQHlkn6ZtSo21ytuPZGKFagQ/xsXZ/3iGuFrYdjafXG0DiQMeBi47c9/GV3BO247UV38n5o0UAP6xmu7jFOGxjRr66On5NPBDOCBsDTapxjHY1dyOcolNXnYlx1himE53p2PmNkxosevfavhg4Izt2k7TXPwZ2S6p6QZPin/2rwcQ7OKmBohCadJGF1P8PG6aaQBKVX/8AAAAASUVORK5CYII=');
|
||||||
|
background-size: 60% 60%;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-position: center;
|
||||||
|
width: 80rpx;
|
||||||
|
height: 80rpx;
|
||||||
|
&.is-reverse {
|
||||||
|
transform: rotateY(180deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.rechoose {
|
||||||
|
color: $uni-color-primary;
|
||||||
|
padding: 0 $uni-spacing-row-lg;
|
||||||
|
line-height: 100rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.choose-btn {
|
||||||
|
color: $uni-color-primary;
|
||||||
|
text-align: center;
|
||||||
|
line-height: 100rpx;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button {
|
||||||
|
margin: auto $uni-spacing-row-lg auto auto;
|
||||||
|
background-color: $uni-color-primary;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.safe-area-inset-bottom {
|
||||||
|
padding-bottom: 0;
|
||||||
|
padding-bottom: constant(safe-area-inset-bottom); // 兼容 IOS<11.2
|
||||||
|
padding-bottom: env(safe-area-inset-bottom); // 兼容 IOS>=11.2
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,727 @@
|
|||||||
|
/**
|
||||||
|
* 图片编辑器-手势监听
|
||||||
|
* 1. wxs 暂不支持 es6 语法
|
||||||
|
* 2. 支持编译到微信小程序、QQ小程序、app-vue、H5上(uni-app 2.2.5及以上版本)
|
||||||
|
*/
|
||||||
|
/** 图片偏移量 */
|
||||||
|
var offset = { x: 0, y: 0 };
|
||||||
|
/** 图片缩放比例 */
|
||||||
|
var scale = 1;
|
||||||
|
/** 图片最小缩放比例 */
|
||||||
|
var minScale = 1;
|
||||||
|
/** 图片旋转角度 */
|
||||||
|
var rotate = 0;
|
||||||
|
/** 触摸点 */
|
||||||
|
var touches = [];
|
||||||
|
/** 图片布局信息 */
|
||||||
|
var img = {};
|
||||||
|
/** 系统信息 */
|
||||||
|
var sys = {};
|
||||||
|
/** 裁剪区域布局信息 */
|
||||||
|
var area = {};
|
||||||
|
/** 触摸行为类型 */
|
||||||
|
var touchType = '';
|
||||||
|
/** 操作角的位置 */
|
||||||
|
var activeAngle = 0;
|
||||||
|
/** 裁剪区域布局信息偏移量 */
|
||||||
|
var areaOffset = { left: 0, right: 0, top: 0, bottom: 0 };
|
||||||
|
/** 容错值 */
|
||||||
|
var fault = 0.000001;
|
||||||
|
/**
|
||||||
|
* 获取a、b两数中的最小正数
|
||||||
|
* @param a
|
||||||
|
* @param b
|
||||||
|
*/
|
||||||
|
function minimum(a, b) {
|
||||||
|
if (a > 0 && b < 0) return a;
|
||||||
|
if (a < 0 && b > 0) return b;
|
||||||
|
if (a > 0 && b > 0) return Math.min(a, b);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 在容错访问内获取n近似值
|
||||||
|
* @param n
|
||||||
|
*/
|
||||||
|
function num(n) {
|
||||||
|
var m = parseFloat((n).toFixed(6));
|
||||||
|
return m === fault || m === -fault ? 0 : m;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 比较a值在容错值范围内是否等于b值
|
||||||
|
* @param a
|
||||||
|
* @param b
|
||||||
|
*/
|
||||||
|
function equalsByFault(a, b) {
|
||||||
|
return Math.abs(a - b) <= fault;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 比较a值在容错值范围内是否小于b值
|
||||||
|
* @param a
|
||||||
|
* @param b
|
||||||
|
*/
|
||||||
|
function lessThanByFault(a, b) {
|
||||||
|
var c = a - b;
|
||||||
|
return c < 0 ? c < -fault : c < fault;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 验证并获取有效最大值
|
||||||
|
* @param v
|
||||||
|
* @param max
|
||||||
|
* @param isInclude
|
||||||
|
* @param x
|
||||||
|
* @param y
|
||||||
|
* @param rate
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
function validMax(v, max, isInclude, x, y, rate) {
|
||||||
|
if(typeof max === 'number') {
|
||||||
|
if(isInclude && equalsByFault(max, y)) { // 宽高不等时,x轴用y轴值要做等比例转换
|
||||||
|
var n = num(max * rate);
|
||||||
|
if (n <= x) return n; // 转化后值在x轴最大值范围内
|
||||||
|
return x; // 转化后值超出x轴最大值范围则用最大值
|
||||||
|
}
|
||||||
|
return max;
|
||||||
|
}
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 计算两点间距
|
||||||
|
* @param {Object} touches 触摸点信息
|
||||||
|
*/
|
||||||
|
function getDistanceByTouches(touches) {
|
||||||
|
// 根据勾股定理求两点间距离
|
||||||
|
var a = touches[1].pageX - touches[0].pageX;
|
||||||
|
var b = touches[1].pageY - touches[0].pageY;
|
||||||
|
var c = Math.sqrt(Math.pow(a, 2) + Math.pow(b, 2));
|
||||||
|
// 求两点间的中点坐标
|
||||||
|
// 1. a、b可能为负值
|
||||||
|
// 2. 在求a、b时,如用touches[1]减touches[0],则求中点坐标也得用touches[1]减a/2、b/2
|
||||||
|
// 3. 同理,在求a、b时,也可用touches[0]减touches[1],则求中点坐标也得用touches[0]减a/2、b/2
|
||||||
|
var x = touches[1].pageX - a / 2;
|
||||||
|
var y = touches[1].pageY - b / 2;
|
||||||
|
return { c, x, y };
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* 修正取值
|
||||||
|
* @param {Object} a
|
||||||
|
* @param {Object} b
|
||||||
|
* @param {Object} c
|
||||||
|
* @param {Object} reverse 是否反向
|
||||||
|
*/
|
||||||
|
function correctValue(a, b, c, reverse) {
|
||||||
|
return num(reverse ? Math.max(Math.min(a, b), c) : Math.min(Math.max(a, b), c));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 旋转90°或270°时检查边界:限制 x、y 拖动范围,禁止滑出边界
|
||||||
|
* @param {Object} e 点坐标
|
||||||
|
* @param {Object} xReverse x是否反向
|
||||||
|
* @param {Object} yReverse y是否反向
|
||||||
|
*/
|
||||||
|
function checkRotateRange(e, xReverse, yReverse) {
|
||||||
|
var o = num((img.height - img.width) / 2); // 宽高差值一半
|
||||||
|
return {
|
||||||
|
x: correctValue(e.x, -img.height + o + area.width + area.left, area.left + o, xReverse),
|
||||||
|
y: correctValue(e.y, -img.width - o + area.height + area.top, area.top - o, yReverse)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查边界:限制 x、y 拖动范围,禁止滑出边界
|
||||||
|
* @param {Object} e 点坐标
|
||||||
|
*/
|
||||||
|
function checkRange(e) {
|
||||||
|
var r = rotate / 90 % 2;
|
||||||
|
if(r === 1) { // 因图片宽高可能不等,翻转 90° 或 270° 后图片宽高需反着计算,且左右和上下边界要根据差值做偏移
|
||||||
|
if (area.width === area.height) {
|
||||||
|
return checkRotateRange(e, img.height < area.height, img.width < area.width);
|
||||||
|
}
|
||||||
|
var isInclude = img.height < area.width && img.width < area.height; // 图片是否包含在裁剪区域内
|
||||||
|
if (img.width < area.height || img.height < area.width) {
|
||||||
|
if (area.width < area.height && img.width < img.height) {
|
||||||
|
return isInclude
|
||||||
|
? checkRotateRange(e, area.width < area.height, area.width < area.height)
|
||||||
|
: checkRotateRange(e, false, true);
|
||||||
|
}
|
||||||
|
if (area.height < area.width && img.height < img.width) {
|
||||||
|
return isInclude
|
||||||
|
? checkRotateRange(e, area.height < area.width, area.height < area.width)
|
||||||
|
: checkRotateRange(e, true, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (img.height >= area.width && img.width >= area.height) {
|
||||||
|
return checkRotateRange(e, false, false);
|
||||||
|
}
|
||||||
|
if (isInclude) {
|
||||||
|
return area.height < area.width
|
||||||
|
? checkRotateRange(e, true, true)
|
||||||
|
: checkRotateRange(e, area.width < area.height, area.width < area.height);
|
||||||
|
}
|
||||||
|
if (img.height < area.width && !img.width < area.height) {
|
||||||
|
return checkRotateRange(e, true, false);
|
||||||
|
}
|
||||||
|
if (!img.height < area.width && img.width < area.height) {
|
||||||
|
return checkRotateRange(e, false, true);
|
||||||
|
}
|
||||||
|
return checkRotateRange(e, img.height < area.height, img.width < area.width);
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
x: correctValue(e.x, -img.width + area.width + area.left, area.left, img.width < area.width),
|
||||||
|
y: correctValue(e.y, -img.height + area.height + area.top, area.top, img.height < area.height)
|
||||||
|
};
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* 变更图片布局信息
|
||||||
|
* @param {Object} e 布局信息
|
||||||
|
*/
|
||||||
|
function changeImageRect(e) {
|
||||||
|
offset.x += e.x || 0;
|
||||||
|
offset.y += e.y || 0;
|
||||||
|
var image = e.instance.selectComponent('.crop-image');
|
||||||
|
if(e.check && area.checkRange) { // 检查边界
|
||||||
|
var point = checkRange(offset);
|
||||||
|
if(offset.x !== point.x || offset.y !== point.y) {
|
||||||
|
offset = point;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// image.setStyle({
|
||||||
|
// width: img.width + 'px',
|
||||||
|
// height: img.height + 'px',
|
||||||
|
// transform: 'translate(' + offset.x + 'px, ' + offset.y + 'px) rotate(' + rotate +'deg)'
|
||||||
|
// });
|
||||||
|
var ox = (img.width - img.oldWidth) / 2;
|
||||||
|
var oy = (img.height - img.oldHeight) / 2;
|
||||||
|
image.setStyle({
|
||||||
|
width: img.oldWidth + 'px',
|
||||||
|
height: img.oldHeight + 'px',
|
||||||
|
transform: (img.gpu ? 'translateZ(0) ' : '') + 'translate(' + (offset.x + ox) + 'px, ' + (offset.y + oy) + 'px) rotate(' + rotate +'deg) scale(' + scale + ')'
|
||||||
|
});
|
||||||
|
|
||||||
|
e.instance.callMethod('dataChange', {
|
||||||
|
width: img.width,
|
||||||
|
height: img.height,
|
||||||
|
x: offset.x,
|
||||||
|
y: offset.y,
|
||||||
|
rotate: rotate
|
||||||
|
});
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* 变更裁剪区域布局信息
|
||||||
|
* @param {Object} e 布局信息
|
||||||
|
*/
|
||||||
|
function changeAreaRect(e) {
|
||||||
|
// 变更蒙版样式
|
||||||
|
var masks = e.instance.selectAllComponents('.crop-mask-block');
|
||||||
|
var maskStyles = [
|
||||||
|
{
|
||||||
|
left: 0,
|
||||||
|
width: (area.left + areaOffset.left) + 'px',
|
||||||
|
top: 0,
|
||||||
|
bottom: 0,
|
||||||
|
'z-index': area.zIndex + 2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
left: (area.right + areaOffset.right) + 'px',
|
||||||
|
right: 0,
|
||||||
|
top: 0,
|
||||||
|
bottom: 0,
|
||||||
|
'z-index': area.zIndex + 2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
left: (area.left + areaOffset.left) + 'px',
|
||||||
|
width: (area.width + areaOffset.right - areaOffset.left) + 'px',
|
||||||
|
top: 0,
|
||||||
|
height: (area.top + areaOffset.top) + 'px',
|
||||||
|
'z-index': area.zIndex + 2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
left: (area.left + areaOffset.left) + 'px',
|
||||||
|
width: (area.width + areaOffset.right - areaOffset.left) + 'px',
|
||||||
|
top: (area.bottom + areaOffset.bottom) + 'px',
|
||||||
|
// height: (area.top - areaOffset.bottom + sys.offsetBottom) + 'px',
|
||||||
|
bottom: 0,
|
||||||
|
'z-index': area.zIndex + 2
|
||||||
|
}
|
||||||
|
];
|
||||||
|
var len = masks.length;
|
||||||
|
for (var i = 0; i < len; i++) {
|
||||||
|
masks[i].setStyle(maskStyles[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 变更边框样式
|
||||||
|
if(area.showBorder) {
|
||||||
|
var border = e.instance.selectComponent('.crop-border');
|
||||||
|
border.setStyle({
|
||||||
|
left: (area.left + areaOffset.left) + 'px',
|
||||||
|
top: (area.top + areaOffset.top) + 'px',
|
||||||
|
width: (area.width + areaOffset.right - areaOffset.left) + 'px',
|
||||||
|
height: (area.height + areaOffset.bottom - areaOffset.top) + 'px',
|
||||||
|
'z-index': area.zIndex + 3
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 变更参考线样式
|
||||||
|
if(area.showGrid) {
|
||||||
|
var grids = e.instance.selectAllComponents('.crop-grid');
|
||||||
|
var gridStyles = [
|
||||||
|
{
|
||||||
|
'border-width': '1px 0 0 0',
|
||||||
|
left: (area.left + areaOffset.left) + 'px',
|
||||||
|
right: (area.right + areaOffset.right) + 'px',
|
||||||
|
top: (area.top + areaOffset.top + (area.height + areaOffset.bottom - areaOffset.top) / 3 - 0.5) + 'px',
|
||||||
|
width: (area.width + areaOffset.right - areaOffset.left) + 'px',
|
||||||
|
'z-index': area.zIndex + 3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'border-width': '1px 0 0 0',
|
||||||
|
left: (area.left + areaOffset.left) + 'px',
|
||||||
|
right: (area.right + areaOffset.right) + 'px',
|
||||||
|
top: (area.top + areaOffset.top + (area.height + areaOffset.bottom - areaOffset.top) * 2 / 3 - 0.5) + 'px',
|
||||||
|
width: (area.width + areaOffset.right - areaOffset.left) + 'px',
|
||||||
|
'z-index': area.zIndex + 3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'border-width': '0 1px 0 0',
|
||||||
|
top: (area.top + areaOffset.top) + 'px',
|
||||||
|
bottom: (area.bottom + areaOffset.bottom) + 'px',
|
||||||
|
left: (area.left + areaOffset.left + (area.width + areaOffset.right - areaOffset.left) / 3 - 0.5) + 'px',
|
||||||
|
height: (area.height + areaOffset.bottom - areaOffset.top) + 'px',
|
||||||
|
'z-index': area.zIndex + 3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'border-width': '0 1px 0 0',
|
||||||
|
top: (area.top + areaOffset.top) + 'px',
|
||||||
|
bottom: (area.bottom + areaOffset.bottom) + 'px',
|
||||||
|
left: (area.left + areaOffset.left + (area.width + areaOffset.right - areaOffset.left) * 2 / 3 - 0.5) + 'px',
|
||||||
|
height: (area.height + areaOffset.bottom - areaOffset.top) + 'px',
|
||||||
|
'z-index': area.zIndex + 3
|
||||||
|
}
|
||||||
|
];
|
||||||
|
var len = grids.length;
|
||||||
|
for (var i = 0; i < len; i++) {
|
||||||
|
grids[i].setStyle(gridStyles[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 变更四个伸缩角样式
|
||||||
|
if(area.showAngle) {
|
||||||
|
var angles = e.instance.selectAllComponents('.crop-angle');
|
||||||
|
var angleStyles = [
|
||||||
|
{
|
||||||
|
'border-width': area.angleBorderWidth + 'px 0 0 ' + area.angleBorderWidth + 'px',
|
||||||
|
left: (area.left + areaOffset.left - area.angleBorderWidth) + 'px',
|
||||||
|
top: (area.top + areaOffset.top - area.angleBorderWidth) + 'px',
|
||||||
|
'z-index': area.zIndex + 3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'border-width': area.angleBorderWidth + 'px ' + area.angleBorderWidth + 'px 0 0',
|
||||||
|
left: (area.right + areaOffset.right - area.angleSize) + 'px',
|
||||||
|
top: (area.top + areaOffset.top - area.angleBorderWidth) + 'px',
|
||||||
|
'z-index': area.zIndex + 3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'border-width': '0 0 ' + area.angleBorderWidth + 'px ' + area.angleBorderWidth + 'px',
|
||||||
|
left: (area.left + areaOffset.left - area.angleBorderWidth) + 'px',
|
||||||
|
top: (area.bottom + areaOffset.bottom - area.angleSize) + 'px',
|
||||||
|
'z-index': area.zIndex + 3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'border-width': '0 ' + area.angleBorderWidth + 'px ' + area.angleBorderWidth + 'px 0',
|
||||||
|
left: (area.right + areaOffset.right - area.angleSize) + 'px',
|
||||||
|
top: (area.bottom + areaOffset.bottom - area.angleSize) + 'px',
|
||||||
|
'z-index': area.zIndex + 3
|
||||||
|
}
|
||||||
|
];
|
||||||
|
var len = angles.length;
|
||||||
|
for (var i = 0; i < len; i++) {
|
||||||
|
angles[i].setStyle(angleStyles[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 变更圆角样式
|
||||||
|
if(area.radius > 0) {
|
||||||
|
var circleBox = e.instance.selectComponent('.crop-circle-box');
|
||||||
|
var circle = e.instance.selectComponent('.crop-circle');
|
||||||
|
var radius = area.radius;
|
||||||
|
if(area.width === area.height && area.radius >= area.width / 2) { // 圆形
|
||||||
|
radius = (area.width / 2);
|
||||||
|
} else { // 圆角矩形
|
||||||
|
if(area.width !== area.height) { // 限制圆角半径不能超过短边的一半
|
||||||
|
radius = Math.min(area.width / 2, area.height / 2, radius);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
circleBox.setStyle({
|
||||||
|
left: (area.left + areaOffset.left) + 'px',
|
||||||
|
top: (area.top + areaOffset.top) + 'px',
|
||||||
|
width: (area.width + areaOffset.right - areaOffset.left) + 'px',
|
||||||
|
height: (area.height + areaOffset.bottom - areaOffset.top) + 'px',
|
||||||
|
'z-index': area.zIndex + 2
|
||||||
|
});
|
||||||
|
circle.setStyle({
|
||||||
|
'box-shadow': '0 0 0 ' + Math.max(area.width, area.height) + 'px rgba(51, 51, 51, 0.8)',
|
||||||
|
'border-radius': radius + 'px'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* 缩放图片
|
||||||
|
* @param {Object} e 布局信息
|
||||||
|
*/
|
||||||
|
function scaleImage(e) {
|
||||||
|
var last = scale;
|
||||||
|
scale = Math.min(Math.max(e.scale + scale, minScale), img.maxScale);
|
||||||
|
if(last !== scale) {
|
||||||
|
img.width = num(img.oldWidth * scale);
|
||||||
|
img.height = num(img.oldHeight * scale);
|
||||||
|
// 参考问题:有一个长4000px、宽4000px的四方形ABCD,A点的坐标固定在(-2000,-2000),
|
||||||
|
// 该四边形上有一个点E,坐标为(-100,-300),将该四方形复制一份并缩小到90%后,
|
||||||
|
// 新四边形的A点坐标为多少时可使新四边形的E点与原四边形的E点重合?
|
||||||
|
// 预期效果:从图中选取某点(参照物)为中心点进行缩放,缩放时无论图像怎么变化,该点位置始终固定不变
|
||||||
|
// 计算方法:以相同起点先计算缩放前后两点间的距离,再加上原图像偏移量即可
|
||||||
|
e.x = num((e.x - offset.x) * (1 - scale / last));
|
||||||
|
e.y = num((e.y - offset.y) * (1 - scale / last));
|
||||||
|
changeImageRect(e);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* 获取触摸点在哪个角
|
||||||
|
* @param {number} x 触摸点x轴坐标
|
||||||
|
* @param {number} y 触摸点y轴坐标
|
||||||
|
* @return {number} 角的位置:0=无;1=左上;2=右上;3=左下;4=右下;
|
||||||
|
*/
|
||||||
|
function getToucheAngle(x, y) {
|
||||||
|
// console.log('getToucheAngle', x, y, JSON.stringify(area))
|
||||||
|
var o = area.angleBorderWidth; // 需扩大触发范围则把 o 值加大即可
|
||||||
|
if(y >= area.top - o && y <= area.top + area.angleSize + o) {
|
||||||
|
if(x >= area.left - o && x <= area.left + area.angleSize + o) {
|
||||||
|
return 1; // 左上角
|
||||||
|
} else if(x >= area.right - area.angleSize - o && x <= area.right + o) {
|
||||||
|
return 2; // 右上角
|
||||||
|
}
|
||||||
|
} else if(y >= area.bottom - area.angleSize - o && y <= area.bottom + o) {
|
||||||
|
if(x >= area.left - o && x <= area.left + area.angleSize + o) {
|
||||||
|
return 3; // 左下角
|
||||||
|
} else if(x >= area.right - area.angleSize - o && x <= area.right + o) {
|
||||||
|
return 4; // 右下角
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0; // 无触摸到角
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* 重置数据
|
||||||
|
*/
|
||||||
|
function resetData() {
|
||||||
|
offset = { x: 0, y: 0 };
|
||||||
|
scale = 1;
|
||||||
|
minScale = img.minScale;
|
||||||
|
rotate = 0;
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* 顺时针翻转图片90°
|
||||||
|
* @param {Object} e 事件对象
|
||||||
|
* @param {Object} o 组件实例对象
|
||||||
|
*/
|
||||||
|
function rotateImage(e, o, r) {
|
||||||
|
rotate = (rotate + r) % 360;
|
||||||
|
if(img.minScale >= 1 && area.checkRange) {
|
||||||
|
// 因图片宽高可能不等,翻转后图片宽高需足够填满裁剪区域
|
||||||
|
minScale = 1;
|
||||||
|
if(img.width < area.height) {
|
||||||
|
minScale = area.height / img.oldWidth;
|
||||||
|
} else if(img.height < area.width) {
|
||||||
|
minScale = area.width / img.oldHeight;
|
||||||
|
}
|
||||||
|
if(minScale !== 1) {
|
||||||
|
scaleImage({
|
||||||
|
instance: o,
|
||||||
|
scale: minScale - scale,
|
||||||
|
x: sys.windowWidth / 2,
|
||||||
|
y: (sys.windowHeight - sys.offsetBottom) / 2
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 由于拖动画布后会导致图片位置偏移,翻转时的旋转中心点需是图片区域+偏移区域的中心点
|
||||||
|
// 翻转x轴中心点 = (超出裁剪区域右侧的图片宽度 - 超出裁剪区域左侧的图片宽度) / 2
|
||||||
|
// 翻转y轴中心点 = (超出裁剪区域下方的图片宽度 - 超出裁剪区域上方的图片宽度) / 2
|
||||||
|
var ox = ((offset.x + img.width - area.right) - (area.left - offset.x)) / 2;
|
||||||
|
var oy = ((offset.y + img.height - area.bottom) - (area.top - offset.y)) / 2;
|
||||||
|
changeImageRect({
|
||||||
|
instance: o,
|
||||||
|
check: true,
|
||||||
|
x: -ox - oy,
|
||||||
|
y: -oy + ox
|
||||||
|
});
|
||||||
|
};
|
||||||
|
module.exports = {
|
||||||
|
/**
|
||||||
|
* 初始化:观察数据变更
|
||||||
|
* @param {Object} newVal 新数据
|
||||||
|
* @param {Object} oldVal 旧数据
|
||||||
|
* @param {Object} o 组件实例对象
|
||||||
|
*/
|
||||||
|
initObserver: function(newVal, oldVal, o, i) {
|
||||||
|
if(newVal) {
|
||||||
|
img = newVal.img;
|
||||||
|
sys = newVal.sys;
|
||||||
|
area = newVal.area;
|
||||||
|
minScale = img.minScale;
|
||||||
|
resetData();
|
||||||
|
img.src && changeImageRect({
|
||||||
|
instance: o,
|
||||||
|
x: (sys.windowWidth - img.width) / 2,
|
||||||
|
y: (sys.windowHeight - sys.offsetBottom - img.height) / 2
|
||||||
|
});
|
||||||
|
changeAreaRect({
|
||||||
|
instance: o
|
||||||
|
});
|
||||||
|
// console.log('initRect', JSON.stringify(newVal))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 鼠标滚轮滚动
|
||||||
|
* @param {Object} e 事件对象
|
||||||
|
* @param {Object} o 组件实例对象
|
||||||
|
*/
|
||||||
|
mousewheel: function(e, o) {
|
||||||
|
if(!img.src) return;
|
||||||
|
scaleImage({
|
||||||
|
instance: o,
|
||||||
|
check: true,
|
||||||
|
// 鼠标向上滚动时,deltaY 固定 -100,鼠标向下滚动时,deltaY 固定 100
|
||||||
|
scale: e.detail.deltaY > 0 ? -0.05 : 0.05,
|
||||||
|
x: e.touches[0].pageX,
|
||||||
|
y: e.touches[0].pageY
|
||||||
|
});
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 触摸开始
|
||||||
|
* @param {Object} e 事件对象
|
||||||
|
* @param {Object} o 组件实例对象
|
||||||
|
*/
|
||||||
|
touchstart: function(e, o) {
|
||||||
|
if(!img.src) return;
|
||||||
|
touches = e.touches;
|
||||||
|
activeAngle = area.showAngle ? getToucheAngle(touches[0].pageX, touches[0].pageY) : 0;
|
||||||
|
if(touches.length === 1 && activeAngle !== 0) {
|
||||||
|
touchType = 'stretch'; // 伸缩裁剪区域
|
||||||
|
} else {
|
||||||
|
touchType = '';
|
||||||
|
}
|
||||||
|
// console.log('touchstart', JSON.stringify(e), activeAngle)
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 触摸移动
|
||||||
|
* @param {Object} e 事件对象
|
||||||
|
* @param {Object} o 组件实例对象
|
||||||
|
*/
|
||||||
|
touchmove: function(e, o) {
|
||||||
|
if(!img.src) return;
|
||||||
|
// console.log('touchmove', JSON.stringify(e), JSON.stringify(o))
|
||||||
|
if(touchType === 'stretch') { // 触摸四个角进行拉伸
|
||||||
|
var point = e.touches[0];
|
||||||
|
var start = touches[0];
|
||||||
|
var x = point.pageX - start.pageX;
|
||||||
|
var y = point.pageY - start.pageY;
|
||||||
|
if(x !== 0 || y !== 0) {
|
||||||
|
var maxX = num(area.width * (1 - area.minScale));
|
||||||
|
var maxY = num(area.height * (1 - area.minScale));
|
||||||
|
// console.log(x, y, maxX, maxY, offset, area)
|
||||||
|
touches[0] = point;
|
||||||
|
var r = rotate / 90 % 2;
|
||||||
|
var m = r === 1 ? num((img.height - img.width) / 2) : 0; // 宽高差值一半
|
||||||
|
var xCompare = r === 1 ? lessThanByFault(img.height, area.width) : lessThanByFault(img.width, area.width);
|
||||||
|
var yCompare = r === 1 ? lessThanByFault(img.width, area.height) : lessThanByFault(img.height, area.height)
|
||||||
|
var isInclude = xCompare && yCompare;
|
||||||
|
var isIntersect = area.checkRange && (xCompare || yCompare); // 图片是否包含在裁剪区域内
|
||||||
|
var isReverse = !isInclude || num((offset.x - area.left) / area.width) <= num((offset.y - area.top) / area.height) || (area.width > area.height && img.width < img.height && r === 1);
|
||||||
|
switch(activeAngle) {
|
||||||
|
case 1: // 左上角
|
||||||
|
x = num(x + areaOffset.left);
|
||||||
|
y = num(y + areaOffset.top);
|
||||||
|
if(x >= 0 && y >= 0) { // 有效滑动
|
||||||
|
var t = num(offset.y + m - area.top);
|
||||||
|
var l = num(offset.x - m - area.left);
|
||||||
|
// && (offset.x + img.width < area.right || offset.y + img.height < area.bottom)
|
||||||
|
var max = isIntersect && ((l >= 0) || (t >= 0))
|
||||||
|
? minimum(t, l)
|
||||||
|
: false;
|
||||||
|
if(x > y && isReverse) { // 以x轴滑动距离为缩放基准
|
||||||
|
maxX = validMax(maxX, max, isInclude, l, t, area.width / area.height);
|
||||||
|
if(x > maxX) x = maxX;
|
||||||
|
y = num(x * area.height / area.width);
|
||||||
|
} else { // 以y轴滑动距离为缩放基准
|
||||||
|
maxY = validMax(maxY, max, isInclude, t, l, area.height / area.width);
|
||||||
|
if(y > maxY) y = maxY;
|
||||||
|
x = num(y * area.width / area.height);
|
||||||
|
}
|
||||||
|
areaOffset.left = x;
|
||||||
|
areaOffset.top = y;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 2: // 右上角
|
||||||
|
x = num(x + areaOffset.right);
|
||||||
|
y = num(y + areaOffset.top);
|
||||||
|
if(x <= 0 && y >= 0) { // 有效滑动
|
||||||
|
var w = (r === 1 ? img.height : img.width);
|
||||||
|
var t = num(offset.y + m - area.top);
|
||||||
|
var l = num(area.right + m - offset.x - w);
|
||||||
|
var max = isIntersect && ((t >= 0) || (l >= 0))
|
||||||
|
? minimum(t, l)
|
||||||
|
: false;
|
||||||
|
// var max = isInclude && ((offset.x > 0 && offset.x + img.width <= area.right) || (offset.y > 0 && offset.y >= area.top))
|
||||||
|
// ? minimum(offset.y - area.top, area.right - offset.x - img.width)
|
||||||
|
// : false;
|
||||||
|
// console.log(offset.x, offset.y, img.width, img.height, area.top, area.right, m, max)
|
||||||
|
// console.log(offset.y + m - area.top, area.right + m - offset.x - w)
|
||||||
|
if(-x > y && isReverse) { // 以x轴滑动距离为缩放基准
|
||||||
|
maxX = validMax(maxX, max, isInclude, l, t, area.width / area.height);
|
||||||
|
if(-x > maxX) x = -maxX;
|
||||||
|
y = num(-x * area.height / area.width);
|
||||||
|
} else { // 以y轴滑动距离为缩放基准
|
||||||
|
maxY = validMax(maxY, max, isInclude, t, l, area.height / area.width);
|
||||||
|
if(y > maxY) y = maxY;
|
||||||
|
x = num(-y * area.width / area.height);
|
||||||
|
}
|
||||||
|
areaOffset.right = x;
|
||||||
|
areaOffset.top = y;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 3: // 左下角
|
||||||
|
x += num(x + areaOffset.left);
|
||||||
|
y += num(y + areaOffset.bottom);
|
||||||
|
if(x >= 0 && y <= 0) { // 有效滑动
|
||||||
|
var w = (r === 1 ? img.width : img.height);
|
||||||
|
var t = num(area.bottom - m - offset.y - w);
|
||||||
|
var l = num(offset.x - m - area.left);
|
||||||
|
var max = isIntersect && ((l >= 0) || (t >= 0))
|
||||||
|
? minimum(t, l)
|
||||||
|
: false;
|
||||||
|
if(x > -y && isReverse) { // 以x轴滑动距离为缩放基准
|
||||||
|
maxX = validMax(maxX, max, isInclude, l, t, area.width / area.height);
|
||||||
|
if(x > maxX) x = maxX;
|
||||||
|
y = num(-x * area.height / area.width);
|
||||||
|
} else { // 以y轴滑动距离为缩放基准
|
||||||
|
maxY = validMax(maxY, max, isInclude, t, l, area.height / area.width);
|
||||||
|
if(-y > maxY) y = -maxY;
|
||||||
|
x = num(-y * area.width / area.height);
|
||||||
|
}
|
||||||
|
areaOffset.left = x;
|
||||||
|
areaOffset.bottom = y;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 4: // 右下角
|
||||||
|
x = num(x + areaOffset.right);
|
||||||
|
y = num(y + areaOffset.bottom);
|
||||||
|
if(x <= 0 && y <= 0) { // 有效滑动
|
||||||
|
var w = (r === 1 ? img.height : img.width);
|
||||||
|
var h = (r === 1 ? img.width : img.height);
|
||||||
|
var t = num(area.bottom - offset.y - h - m);
|
||||||
|
var l = num(area.right + m - offset.x - w);
|
||||||
|
var max = isIntersect && ((l >= 0) || (t >= 0))
|
||||||
|
? minimum(t, l)
|
||||||
|
: false;
|
||||||
|
if(-x > -y && isReverse) { // 以x轴滑动距离为缩放基准
|
||||||
|
maxX = validMax(maxX, max, isInclude, l, t, area.width / area.height);
|
||||||
|
if(-x > maxX) x = -maxX;
|
||||||
|
y = num(x * area.height / area.width);
|
||||||
|
} else { // 以y轴滑动距离为缩放基准
|
||||||
|
maxY = validMax(maxY, max, isInclude, t, l, area.height / area.width);
|
||||||
|
if(-y > maxY) y = -maxY;
|
||||||
|
x = num(y * area.width / area.height);
|
||||||
|
}
|
||||||
|
areaOffset.right = x;
|
||||||
|
areaOffset.bottom = y;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// console.log(x, y, JSON.stringify(areaOffset))
|
||||||
|
changeAreaRect({
|
||||||
|
instance: o,
|
||||||
|
});
|
||||||
|
// this.draw();
|
||||||
|
}
|
||||||
|
} else if (e.touches.length == 2) { // 双点触摸缩放
|
||||||
|
var start = getDistanceByTouches(touches);
|
||||||
|
var end = getDistanceByTouches(e.touches);
|
||||||
|
scaleImage({
|
||||||
|
instance: o,
|
||||||
|
check: !area.bounce,
|
||||||
|
scale: (end.c - start.c) / 100,
|
||||||
|
x: end.x,
|
||||||
|
y: end.y
|
||||||
|
});
|
||||||
|
touchType = 'scale';
|
||||||
|
} else if(touchType === 'scale') {// 从双点触摸变成单点触摸 / 从缩放变成拖动
|
||||||
|
touchType = 'move';
|
||||||
|
} else {
|
||||||
|
changeImageRect({
|
||||||
|
instance: o,
|
||||||
|
check: !area.bounce,
|
||||||
|
x: e.touches[0].pageX - touches[0].pageX,
|
||||||
|
y: e.touches[0].pageY - touches[0].pageY
|
||||||
|
});
|
||||||
|
touchType = 'move';
|
||||||
|
}
|
||||||
|
touches = e.touches;
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 触摸结束
|
||||||
|
* @param {Object} e 事件对象
|
||||||
|
* @param {Object} o 组件实例对象
|
||||||
|
*/
|
||||||
|
touchend: function(e, o) {
|
||||||
|
if(!img.src) return;
|
||||||
|
if(touchType === 'stretch') { // 拉伸裁剪区域的四个角缩放
|
||||||
|
// 裁剪区域宽度被缩放到多少
|
||||||
|
var left = areaOffset.left;
|
||||||
|
var right = areaOffset.right;
|
||||||
|
var top = areaOffset.top;
|
||||||
|
var bottom = areaOffset.bottom;
|
||||||
|
var w = area.width + right - left;
|
||||||
|
var h = area.height + bottom - top;
|
||||||
|
// 图像放大倍数
|
||||||
|
var p = scale * (area.width / w) - scale;
|
||||||
|
// 复原裁剪区域
|
||||||
|
areaOffset = { left: 0, right: 0, top: 0, bottom: 0 };
|
||||||
|
changeAreaRect({
|
||||||
|
instance: o,
|
||||||
|
});
|
||||||
|
scaleImage({
|
||||||
|
instance: o,
|
||||||
|
scale: p,
|
||||||
|
x: area.left + left + (1 === activeAngle || 3 === activeAngle ? w : 0),
|
||||||
|
y: area.top + top + (1 === activeAngle || 2 === activeAngle ? h : 0)
|
||||||
|
});
|
||||||
|
} else if (area.bounce) { // 检查边界并矫正,实现拖动到边界时有回弹效果
|
||||||
|
changeImageRect({
|
||||||
|
instance: o,
|
||||||
|
check: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 顺时针翻转图片90°
|
||||||
|
* @param {Object} e 事件对象
|
||||||
|
* @param {Object} o 组件实例对象
|
||||||
|
*/
|
||||||
|
rotateImage: function(e, o) {
|
||||||
|
rotateImage(e, o, 90);
|
||||||
|
},
|
||||||
|
rotateImage90: function(e, o) {
|
||||||
|
rotateImage(e, o, 90)
|
||||||
|
},
|
||||||
|
rotateImage270: function(e, o) {
|
||||||
|
rotateImage(e, o, 270)
|
||||||
|
},
|
||||||
|
// 此处只用于对齐其他平台端的样式参数,防止异常,无作用
|
||||||
|
imageStyles: '',
|
||||||
|
maskStylesList: ['', '', '', ''],
|
||||||
|
borderStyles: '',
|
||||||
|
gridStylesList: ['', '', '', ''],
|
||||||
|
angleStylesList: ['', '', '', ''],
|
||||||
|
circleBoxStyles: '',
|
||||||
|
circleStyles: '',
|
||||||
|
}
|
||||||
81
uni_modules/qf-image-cropper/package.json
Normal file
81
uni_modules/qf-image-cropper/package.json
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
{
|
||||||
|
"id": "qf-image-cropper",
|
||||||
|
"displayName": "图片裁剪插件",
|
||||||
|
"version": "2.2.5",
|
||||||
|
"description": "图片裁剪插件,支持自定义尺寸、定点等比例缩放、拖动、图片翻转、剪切圆形/圆角图片、定制样式,功能多性能高体验好注释全。",
|
||||||
|
"keywords": [
|
||||||
|
"qf-image-cropper",
|
||||||
|
"图片裁剪",
|
||||||
|
"图片编辑",
|
||||||
|
"头像裁剪",
|
||||||
|
"小程序"
|
||||||
|
],
|
||||||
|
"repository": "",
|
||||||
|
"engines": {
|
||||||
|
"HBuilderX": "^3.1.0"
|
||||||
|
},
|
||||||
|
"dcloudext": {
|
||||||
|
"type": "component-vue",
|
||||||
|
"sale": {
|
||||||
|
"regular": {
|
||||||
|
"price": "0.00"
|
||||||
|
},
|
||||||
|
"sourcecode": {
|
||||||
|
"price": "0.00"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"contact": {
|
||||||
|
"qq": ""
|
||||||
|
},
|
||||||
|
"declaration": {
|
||||||
|
"ads": "无",
|
||||||
|
"data": "插件不采集任何数据",
|
||||||
|
"permissions": "无"
|
||||||
|
},
|
||||||
|
"npmurl": ""
|
||||||
|
},
|
||||||
|
"uni_modules": {
|
||||||
|
"dependencies": [],
|
||||||
|
"encrypt": [],
|
||||||
|
"platforms": {
|
||||||
|
"client": {
|
||||||
|
"Vue": {
|
||||||
|
"vue2": "y",
|
||||||
|
"vue3": "y"
|
||||||
|
},
|
||||||
|
"App": {
|
||||||
|
"app-vue": "y",
|
||||||
|
"app-nvue": "n"
|
||||||
|
},
|
||||||
|
"H5-mobile": {
|
||||||
|
"Safari": "y",
|
||||||
|
"Android Browser": "y",
|
||||||
|
"微信浏览器(Android)": "y",
|
||||||
|
"QQ浏览器(Android)": "u"
|
||||||
|
},
|
||||||
|
"H5-pc": {
|
||||||
|
"Chrome": "u",
|
||||||
|
"IE": "u",
|
||||||
|
"Edge": "u",
|
||||||
|
"Firefox": "u",
|
||||||
|
"Safari": "u"
|
||||||
|
},
|
||||||
|
"小程序": {
|
||||||
|
"微信": "y",
|
||||||
|
"阿里": "n",
|
||||||
|
"百度": "n",
|
||||||
|
"字节跳动": "n",
|
||||||
|
"QQ": "u",
|
||||||
|
"钉钉": "n",
|
||||||
|
"快手": "n",
|
||||||
|
"飞书": "n",
|
||||||
|
"京东": "n"
|
||||||
|
},
|
||||||
|
"快应用": {
|
||||||
|
"华为": "n",
|
||||||
|
"联盟": "n"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
97
uni_modules/qf-image-cropper/readme.md
Normal file
97
uni_modules/qf-image-cropper/readme.md
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
# qf-image-cropper
|
||||||
|
## 图片裁剪插件
|
||||||
|
uniapp微信小程序图片裁剪插件,支持自定义尺寸、定点等比例缩放、拖动、图片翻转、剪切圆形/圆角图片、定制样式,功能多性能高体验好注释全。
|
||||||
|
|
||||||
|
### 平台支持:
|
||||||
|
1. 支持微信小程序:移动端、PC端、开发者工具
|
||||||
|
2. 支持H5平台(2.1.0版本起)
|
||||||
|
3. 支持APP平台(2.1.5版本起):Android、IOS
|
||||||
|
4. 其他平台暂未测试兼容性未知
|
||||||
|
|
||||||
|
### 支持功能:
|
||||||
|
1. 自定义裁剪尺寸
|
||||||
|
2. 定点等比例缩放:移动端以双指触摸中心点为缩放中心点,PC端以鼠标所在点为缩放中心点
|
||||||
|
3. 自由拖动:支持限制滑出边界,也支持回弹效果(滑动时可滑出边界,释放时回弹到边界)
|
||||||
|
4. 图片翻转:在裁剪尺寸非 1:1 的情况下,翻转时宽高无法铺满裁剪区域时,图片会自动放大到合适尺寸
|
||||||
|
5. 裁剪生成新图片
|
||||||
|
6. 本地选择图片
|
||||||
|
7. 可定制样式:可自由选择是否渲染裁剪边框、可伸缩裁剪顶角、参考线
|
||||||
|
8. 裁剪圆角图片:圆形、圆角矩形
|
||||||
|
|
||||||
|
### 属性说明
|
||||||
|
| 属性名 | 类型 | 默认值 | 说明 |
|
||||||
|
|:---|:---|:---|:---|
|
||||||
|
| src | String | | 图片资源地址 |
|
||||||
|
| width | Number | 300 | 裁剪宽度 |
|
||||||
|
| height | Number | 300 | 裁剪高度 |
|
||||||
|
| showBorder | Boolean | true | 是否绘制裁剪区域边框 |
|
||||||
|
| showGrid | Boolean | true | 是否绘制裁剪区域网格参考线 |
|
||||||
|
| showAngle | Boolean | true | 是否展示四个支持伸缩的角 |
|
||||||
|
| areaScale | Number | 0.3 | 裁剪区域最小缩放倍数 |
|
||||||
|
| minScale | Number | 1 | 图片最小缩放倍数 |
|
||||||
|
| maxScale | Number | 5 | 图片最大缩放倍数 |
|
||||||
|
| checkRange | Boolean | true | 检查图片位置是否超出裁剪边界,如果超出则会矫正位置 |
|
||||||
|
| backgroundColor | String | | 生成图片背景色:如果裁剪区域没有完全包含在图片中时,不设置该属性则生成图片存在一定的透明块 |
|
||||||
|
| bounce | Boolean | true | 是否有回弹效果:当 checkRange 为 true 时有效,拖动时可以拖出边界,释放时会弹回边界 |
|
||||||
|
| rotatable | Boolean | true | 是否支持翻转 |
|
||||||
|
| reverseRotatable | Boolean | false | 是否支持逆向翻转 |
|
||||||
|
| choosable | Boolean | true | 是否支持从本地选择素材 |
|
||||||
|
| gpu | Boolean | false | 是否开启硬件加速,图片缩放过程中如果出现元素的“留影”或“重影”效果,可通过该方式解决或减轻这一问题 |
|
||||||
|
| angleSize | Number | 20 | 四个角尺寸,单位px |
|
||||||
|
| angleBorderWidth | Number | 2 | 四个角边框宽度,单位px |
|
||||||
|
| zIndex | Number/String | | 调整组件层级 |
|
||||||
|
| radius | Number | | 裁剪图片圆角半径,单位px |
|
||||||
|
| fileType | String | png | 生成文件的类型,只支持 'jpg' 或 'png'。默认为 'png' |
|
||||||
|
| delay | Number | 1000 | 图片从绘制到生成所需时间,单位ms<br>微信小程序平台使用 `Canvas 2D` 绘制时有效<br>如绘制大图或出现裁剪图片空白等情况应适当调大该值,因 `Canvas 2d` 采用同步绘制,需自己把控绘制完成时间 |
|
||||||
|
| navigation | Boolean | true | 页面是否是原生标题栏:<br>H5平台当 showAngle 为 true 时,使用插件的页面在 `page.json` 中配置了 `"navigationStyle": "custom"` 时,必须将此值设为 false ,否则四个可拉伸角的触发位置会有偏差。<br>注:因H5平台的窗口高度是包含标题栏的,而屏幕触摸点的坐标是不包含的 |
|
||||||
|
| @crop | EventHandle | | 剪裁完成后触发,event = { tempFilePath }。在H5平台下,tempFilePath 为 base64 |
|
||||||
|
|
||||||
|
### 基本用法
|
||||||
|
```
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<qf-image-cropper :width="500" :height="500" :radius="30" @crop="handleCrop"></qf-image-cropper>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import QfImageCropper from '@/components/qf-image-cropper/qf-image-cropper.vue';
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
QfImageCropper
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
handleCrop(e) {
|
||||||
|
uni.previewImage({
|
||||||
|
urls: [e.tempFilePath],
|
||||||
|
current: 0
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
通过ref组件实例可在进入页面后直接打开相册选择图片
|
||||||
|
```
|
||||||
|
mounted() {
|
||||||
|
this.$refs.qfImageCropper.chooseImage({ sourceType: ['album'] });
|
||||||
|
}
|
||||||
|
```
|
||||||
|
### 使用说明
|
||||||
|
1.建议在`pages.json`中将引用插件的页面添加一下配置禁止下拉刷新和禁止页面滑动,防止出现性能或页面抖动等问题。
|
||||||
|
```
|
||||||
|
{
|
||||||
|
"enablePullDownRefresh": false,
|
||||||
|
"disableScroll": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
2.建议使用本插件不要设置过大宽高的目标图片尺寸,建议1365x1365以内,否则可能会导致如下问题:
|
||||||
|
```
|
||||||
|
1.界面卡顿,内存占用过高
|
||||||
|
2.生成图片失真(模糊)
|
||||||
|
3.确定裁剪后一直显示 `裁剪中...`,该问题是由 `uni.canvasToTempFilePath` 无法回调导致,不同平台不同设备限制可能有所不同。
|
||||||
|
```
|
||||||
|
3.如裁剪后的图片存在偏移的问题,请检查是否受自己项目中父组件或全局样式影响。
|
||||||
|
4.src属性设置网络图片时,图片资源必须是能触发 `getImageInfo` API 的 success 回调才可用于插件裁剪。因此小程序平台获取网络图片信息需先配置download域名白名单才能生效。
|
||||||
|
5.如果组件无法正常渲染且使用了 `v-if` 时,可尝试将 `v-if` 替换为 `v-show`
|
||||||
|
6.如果App端导入组件后无法正常渲染,请尝试重新运行
|
||||||
Reference in New Issue
Block a user