授权登录以及上传头像

main
shuxiaoquan 2 years ago
parent 884ce456d5
commit 6bdc61c950
  1. 10
      common/api.js
  2. 2
      common/http.interceptor.js
  3. 45
      components/okingtz-cropper/components/okingtz-cropper/okingtz-cropper.vue
  4. 3
      main.js
  5. 55
      pages/login/login.vue
  6. 232
      pages/my/editInfo.vue
  7. 50
      pages/my/editInfo1.vue
  8. 9
      pages/my/my.vue
  9. 32
      pages/register/register.vue
  10. 27
      uni_modules/bt-cropper/changelog.md
  11. 1027
      uni_modules/bt-cropper/components/bt-cropper/bt-cropper.vue
  12. 22
      uni_modules/bt-cropper/components/bt-cropper/iconfont.css
  13. 225
      uni_modules/bt-cropper/components/bt-cropper/js/touchs.js
  14. 43
      uni_modules/bt-cropper/components/bt-cropper/utils/tools.js
  15. 81
      uni_modules/bt-cropper/package.json
  16. 109
      uni_modules/bt-cropper/readme.md

@ -1,4 +1,13 @@
const http = uni.$u.http
//用户信息
export const UserInfo = (params, config = {}) => http.post('/user/getinfo', params, config);
//上传base64
export const uploadBase64 = (params, config = {}) => http.post('/index/uploads', params, config);
//微信登录
export const wxapplogin= (params, config = {}) => http.post('/user/wxapplogin', params, config);
//修改用户信息
export const profileUser= (params, config = {}) => http.post('/user/profile', params, config);
//获取首页轮播图
export const getBanner = (params, config = {}) => http.post('/wx_adv/index', params, config);
//协议
@ -37,6 +46,7 @@ export const articalList = (params, config = {}) => http.post('/wx_article/index
//修改用户信息
export const modifyInfo = (params, config = {}) => http.post('/user/mobilelogin', params, config);
//第三方登录

@ -51,6 +51,8 @@ module.exports = (vm) => {
// 自定义参数
const custom = response.config?.custom
console.log(11,data)
if (data.code !== 1) {
// 如果没有显式定义custom的toast参数为false的话,默认对报错进行toast弹出提示
// if (custom.toast !== false) {

@ -43,6 +43,7 @@
</template>
<script>
import {pathToBase64,base64ToPath} from 'image-tools'
let sysInfo = uni.getSystemInfoSync();
let SCREEN_WIDTH = sysInfo.screenWidth
let PAGE_X, // x
@ -449,7 +450,7 @@
}
return '_www/' + path
},
pathToBase64(path) {
pathToBase641(path) {
const _this = this
return new Promise(function(resolve, reject) {
if (typeof window === 'object' && 'document' in window) {
@ -524,6 +525,7 @@
this.$emit('back')
},
//
getImageInfo() {
var _this = this;
@ -539,15 +541,34 @@
title: _this.saveLoading,
});
// #ifdef APP-PLUS || H5
_this.pathToBase64(_this.imageSrc).then(base64_avatar => {
//
_this.writeImage(base64_avatar)
}).catch(err=>{
console.log('转换64抱错',err)
})
console.log("imageSrc",_this.imageSrc)
if(_this.imageSrc.indexOf("http") >= 0){
_this.getImageInfoEx(_this.imageSrc,function(res){
pathToBase64(res.path).then(base64_avatar=>{
console.log("pathToBase64",base64_avatar)
_this.writeImage(base64_avatar)
}).catch(err=>{
console.log('转换64抱错',err)
})
})
}else{
pathToBase64(_this.imageSrc).then(base64_avatar=>{
console.log("pathToBase64",base64_avatar)
_this.writeImage(base64_avatar)
}).catch(err=>{
console.log('转换64抱错',err)
})
}
// _this.pathToBase64(_this.imageSrc).then(base64_avatar => {
// //
// _this.writeImage(base64_avatar)
// }).catch(err=>{
// console.log('64',err)
// })
// #endif
// #ifdef MP
_this.writeImage(_this.imageSrc)
//_this.writeImage(_this.imageSrc)
// #endif
},
writeImage(path){
@ -574,7 +595,13 @@
canvasId: 'myCanvas',
success: function (res) {
uni.hideLoading()
_this.$emit('uploadSuccess',res.tempFilePath)
if(res.tempFilePath.indexOf("data:image/png;base64") >= 0){
_this.$emit('uploadSuccess',res.tempFilePath)
}else{
pathToBase64(res.tempFilePath).then(res1 => {
_this.$emit('uploadSuccess',res1)
})
}
},
fail:function(err){
console.log('绘制图像抱错->',err)

@ -17,10 +17,11 @@ const app = new Vue({
...App
})
import {
baseUrl
baseUrl,
} from '@/common/config.js'
Vue.prototype.baseUrl = baseUrl
require('@/common/http.interceptor.js')(app)
app.$mount()
// #endif

@ -7,6 +7,7 @@
留学万象
</view>
<view class="contentForm">
<!-- #ifdef APP-NVUE || APP-PLUS -->
<view class="wx" @click="appwxLogin">
<image src="/static/wxIcon.png" mode="widthFix"></image>
<!-- <button type="primary" class="wxText">微信登录</button> -->
@ -14,6 +15,7 @@
微信登录
</view>
</view>
<!-- #endif -->
<view class="phone" @click="mobileNumLogin">
手机号登录/注册
</view>
@ -32,10 +34,11 @@
</template>
<script>
import {wxLoginHandle} from '@/common/api.js'
import {wxapplogin} from '@/common/api.js'
export default {
data() {
return {
isShowWeixin: true,
checked:[]
};
},
@ -68,54 +71,22 @@
console.log('-------获取openid(unionid)-----');
console.log(loginRes);
const params={
code: loginRes.code,
access_token: loginRes.authResult.access_token,
openid: loginRes.authResult.openid,
event:'login'
}
uni.$u.http.post('http://lxwx.njrenzhou.cn/api/user/wxlogin',params).then((res) => {
console.log(params)
wxapplogin(params,{}).then(res=>{
console.log(res)
}).catch((err) =>{
console.log(err)
that.$store.commit('login',res.userinfo)
uni.setStorageSync('source','phone')
uni.reLaunch({
url:'/pages/my/my'
})
})
}
});
return ;
uni.login({
provider: 'weixin',
"onlyAuthorize": true, //
success: function(loginRes) {
const {code} = loginRes
console.log('-------获取openid(unionid)-----',loginRes);
let params={
code:code,
event:'login'
}
uni.$u.http.post('http://lxwx.njrenzhou.cn/api/user/wxlogin',
params,
{
// header: {
// 'Content-Type': 'multipart/form-data',
// },
}).then((res) => {
console.log(res,"gggg111")
}).catch((res) =>{
console.log(res,"2222")
})
}
});
uni.getProvider({
service: 'oauth',
success: function(res) {
console.log(res.provider,"111");
//qq
if (~res.provider.indexOf('weixin')) {
}
}
});
}
}
}

@ -1,75 +1,84 @@
<template>
<view class="editInfo">
<view>
<u-navbar @leftClick="leftClick" height="50" :autoBack="true">
<view
class="u-nav-slot"
slot="left"
>
<u-icon
name="arrow-left"
size="19"
></u-icon>
<view class="title">
个人中心
</view>
</view>
</u-navbar>
<view class="editInfoContent">
<view class="editItem" style="margin-top:0" @click="modifyPhoto">
<view class="editTitle">
头像修改
</view>
<view class="touxiang">
<image :src="userInfo.avatar" mode="widthFix"></image>
<view class="editInfo">
<view class="a" v-if="uploadPic == false">
<u-navbar @leftClick="leftClick" height="50" :autoBack="true">
<view
class="u-nav-slot"
slot="left"
>
<u-icon
name="arrow-right"
size="20"
name="arrow-left"
size="19"
></u-icon>
<view class="title">
个人中心
</view>
</view>
</view>
<view class="editItem">
<view class="editTitle">
昵称修改
</u-navbar>
<view class="editInfoContent">
<view class="editItem" style="margin-top:0" @click="modifyPhoto">
<view class="editTitle">
头像修改
</view>
<view class="touxiang">
<u-avatar :src="userInfo.avatar" @c></u-avatar>
<u-icon
name="arrow-right"
size="20"
></u-icon>
</view>
</view>
<view class="input">
<u--input placeholder="请输入昵称" color="#999999" border="none" v-model="userInfo.nickname"
></u--input>
<view class="editItem">
<view class="editTitle">
昵称修改
</view>
<view class="input">
<u--input placeholder="请输入昵称" inputAlign="right" color="#999999" border="none" v-model="userInfo.nickname"></u--input>
</view>
</view>
<image class="editInfoIcon" src="../../static/edit.png" mode="widthFix"></image>
</view>
<view class="submitBtn" @click="modifyInfoHandle">
提交
</view>
</view>
<view class="submitBtn" @click="modifyInfoHandle">
提交
<view class="b" v-else>
<okingtz-cropper
@back="back"
:image="userInfo.avatar"
selectButtonBackgroundColor="#0076F6"
saveButtonBackgroundColor="#0076F6"
@uploadSuccess="uploadSuccess"></okingtz-cropper>
</view>
</view>
<!-- <view v-else>
<okingtz-cropper
@back="back"
selectButtonBackgroundColor="#0076F6"
saveButtonBackgroundColor="#0076F6"
@uploadSuccess="uploadSuccess"></okingtz-cropper>
</view> -->
</view>
</template>
<script>
// import { dataURLtoFile } from "@/common/common.js";
import { pathToBase64, base64ToPath } from 'image-tools'
import axios from 'axios'
import {modifyInfo,uploadPic} from '@/common/api.js'
// import OkingtzCropper from '@/components/okingtz-cropper/components/okingtz-cropper/okingtz-cropper'
export default{
data(){
return{
uploadPic:false,
userInfo:{}
import { profileUser, uploadBase64, UserInfo } from '@/common/api.js';
import OkingtzCropper from '@/components/okingtz-cropper/components/okingtz-cropper/okingtz-cropper'
export default {
data() {
return {
userInfo: {},
uploadPic: false,
imageSrc: ""
}
},
// components:{OkingtzCropper},
components: {
OkingtzCropper
},
methods:{
//
async uploadSuccess(res){
const data = await uploadBase64({base64img: res},{});
this.uploadPic = false
this.userInfo.avatar = this.baseUrl+data.url
this.userInfo.avatar1 = data.url
},
modifyPhoto(){
this.uploadPic = true
// this.imageSrc = this.userInfo.avatar
},
back(){
this.uploadPic = false
@ -79,64 +88,43 @@ export default{
url:'/pages/my/my'
})
},
uploadSuccess(tempFilePath){
},
// base64Blob
base64ToBlob (base64) {
const parts = base64.split(";base64,");
const contentType = parts[0].split(":")[1];
const raw = window.atob(parts[1]);
const rawLength = raw.length;
const uInt8Array = new Uint8Array(rawLength);
for (let i = 0; i < rawLength; i += 1) {
uInt8Array[i] = raw.charCodeAt(i);
}
return new Blob([uInt8Array], { type: contentType });
// const dataArr = base64.split(",");
// const byteString = atob(dataArr[1]);
// const options = {
// type: "image/jpeg",
// endings: "native"
// };
// const u8Arr = new Uint8Array(byteString.length);
// for (let i = 0; i < byteString.length; i++) {
// u8Arr[i] = byteString.charCodeAt(i);
// }
// return new File([u8Arr],options);//
},
modifyInfoHandle(){
//
async modifyInfoHandle(){
let params = {
nickname:this.userInfo.nickname,
avatar:this.userInfo.avatar
avatar:this.userInfo.avatar1
}
modifyInfo(params,{ custom: { auth: true }}).then(res=>{
console.log(res,)
}).catch((res)=>{
console.log(res,"mmm")
})
}
const res = await profileUser(params,{ custom: { auth: true }})
uni.showToast({
title:"修改成功"
})
this.getUserInfo();
setTimeout(()=>{
uni.navigateBack({
delta:1
})
},2000)
},
//
async getUserInfo(){
let res = await UserInfo({},{ custom: { auth: true }})
uni.setStorageSync("userInfo",res);
if(res.avatar){
res.avatar1 = res.avatar
if(res.avatar.indexOf("http") == -1 && res.avatar.indexOf("uploads/") == 0){
res.avatar = this.baseUrl + res.avatar
}
}
this.userInfo = res;
},
},
onShow() {
console.log(111)
// let userInfo = uni.getStorageSync('userInfo')
// if(userInfo){
// this.userInfo = userInfo;
// }else{
// let pages = getCurrentPages();
// uni.setStorageSync('currentPage',pages[0].route)
// uni.navigateTo({
// url:'/pages/login/login'
// })
// }
this.getUserInfo()
}
}
</script>
<style lang="scss" scoped>
.u-nav-slot{
display: flex;
@ -146,6 +134,43 @@ export default{
color: #222222;
}
}
.b{
width: 100%;
height: 100vh;
.cropper{
height: calc(100vh - 140rpx + env(safe-area-inset-bottom));
}
.btnGroup {
display: flex;
align-items: center;
justify-content: space-around;
background-color: #000000;
height: calc(100rpx + env(safe-area-inset-bottom));
padding-bottom: 20rpx;
padding-top: 20rpx;
width: 100%;
.btn {
width: 300rpx;
background-color: #007AFF;
color: #FFFFFF;
border-radius: 99px;
text-align: center;
color: #FFFFFF;
line-height: 70rpx;
&.choose {
margin-right: 20rpx;
background-color: #F0AD4E;
}
&.choose1 {
margin-left: 20rpx;
background-color: #fb3756;
}
}
}
}
.editInfoContent{
margin:0 24upx;
margin-top:50px;
@ -173,6 +198,7 @@ export default{
}
}
.input{
text-align: right;
color:#999999
}
.editInfoIcon{

@ -45,6 +45,7 @@
<view class="b" v-else>
<okingtz-cropper
@back="back"
:image="userInfo.avatar"
selectButtonBackgroundColor="#0076F6"
saveButtonBackgroundColor="#0076F6"
@uploadSuccess="uploadSuccess"></okingtz-cropper>
@ -54,6 +55,7 @@
<script>
import axios from 'axios'
import { profileUser, uploadBase64, UserInfo } from '@/common/api.js';
import OkingtzCropper from '@/components/okingtz-cropper/components/okingtz-cropper/okingtz-cropper'
export default {
data() {
@ -66,8 +68,13 @@ export default {
OkingtzCropper
},
methods:{
uploadSuccess(res){
console.log(res)
//
async uploadSuccess(res){
const data = await uploadBase64({base64img: res},{});
this.uploadPic = false
this.userInfo.avatar = this.baseUrl+data.url
this.userInfo.avatar1 = data.url
console.log(this.userInfo)
},
modifyPhoto(){
this.uploadPic = true
@ -80,22 +87,39 @@ export default {
url:'/pages/my/my'
})
},
modifyInfoHandle(){
//
async modifyInfoHandle(){
let params = {
nickname:this.userInfo.nickname,
avatar:this.userInfo.avatar
avatar:this.userInfo.avatar1
}
modifyInfo(params,{ custom: { auth: true }}).then(res=>{
console.log(res,)
}).catch((res)=>{
console.log(res,"mmm")
})
}
const res = await profileUser(params,{ custom: { auth: true }})
uni.showToast({
title:"修改成功"
})
this.getUserInfo();
setTimeout(()=>{
uni.navigateBack({
delta:1
})
},2000)
},
//
async getUserInfo(){
let res = await UserInfo({},{ custom: { auth: true }})
uni.setStorageSync("userInfo",res);
if(res.avatar){
res.avatar1 = res.avatar
if(res.avatar.indexOf("http") == -1 && res.avatar.indexOf("uploads/") == 0){
res.avatar = this.baseUrl + res.avatar
}
}
this.userInfo = res;
},
},
onShow() {
let userInfo = uni.getStorageSync('userInfo')
this.userInfo = userInfo;
console.log(this.baseUrl)
this.getUserInfo()
}
}
</script>

@ -66,9 +66,8 @@
},
methods:{
goEditInfo(){
console.log(11)
uni.navigateTo({
url:'/pages/my/editInfo1'
url:'/pages/my/editInfo'
})
},
goDetails(item){
@ -110,6 +109,12 @@
this.source = uni.getStorageSync('source')
console.log(this.source)
let info = JSON.parse(JSON.stringify(uni.getStorageSync('userInfo')))
if(info.avatar){
if(info.avatar.indexOf("http") == -1 && info.avatar.indexOf("uploads/") == 0){
info.avatar = this.baseUrl + info.avatar
}
}
console.log(info)
this.userInfo = info
if(Object.keys(this.userInfo).length==0){
this.isLogin = false

@ -48,7 +48,7 @@
</template>
<script>
import {wxapplogin} from '@/common/api.js'
export default {
data() {
return {
@ -82,6 +82,36 @@
},
appwxLogin(){
var that = this
if(this.checked.length==0){
uni.$u.toast('请先勾选同意用户协议和隐私政策')
}else{
uni.login({
provider: 'weixin',
success: function(loginRes) {
console.log('-------获取openid(unionid)-----');
console.log(loginRes);
const params={
access_token: loginRes.authResult.access_token,
openid: loginRes.authResult.openid,
event:'login'
}
console.log(params)
wxapplogin(params,{}).then(res=>{
console.log(res)
that.$store.commit('login',res.userinfo)
uni.setStorageSync('source','phone')
uni.reLaunch({
url:'/pages/my/my'
})
})
}
});
}
}
,
appwxLogin1(){
if(this.checked.length==0){
uni.$u.toast('请先勾选同意用户协议和隐私政策');
return;

@ -0,0 +1,27 @@
## 3.0.1(2022-11-03)
修复 撤销和重做不生效的问题
## 3.0.0(2022-11-03)
使用wxs重构代码,性能大提升
新增 支持蒙版裁剪,可以裁剪任何形状的图形(详情见demo示例)
新增 支持在弹窗中使用(详情见demo示例)
移除 由于插槽会导致许多问题,实际上开发者自己封装组件反而更简单,所以3.0版本以后移除插槽,2.0迁移教程见 demo:全屏裁剪
## 2.0.3(2022-08-21)
修复 在vue3 程序中报错的问题
新增 新增了图片初始化完成和加载失败的事件
## 2.0.2(2022-08-18)
新增 增加了原像素裁剪功能,即使用用户在裁剪框取景的大小作为输出像素,换句话说,输出的图片分辨率与输入图片分辨率一样
新增 增加了change事件,会在图像和裁剪框位置变化后触发
新增 增加了compress参数 压缩图片,压缩图片是为了提升流畅度,所以只会针对用户拖动的那张图片进行压缩,最终输出的图像品质并不会受到影响
修复 用户在没有传入图像时报错的问题
修复 ios在某些机型上拖动出现残留的问题
## 2.0.1(2022-07-20)
修复:ios打包成app的时候有几率裁剪不成功的问题
## 2.0.0(2022-07-13)
更新了2.0版本,增加了图片放大功能
## bt-cropper 图片裁切
========
### 2022年7月13日 发布2.0版本
* 1.完全重构了代码,并且优化了性能
* 2.支持app
* 3.修复demo在H5的情况下高度错误的问题

File diff suppressed because it is too large Load Diff

@ -0,0 +1,22 @@
@font-face {
font-family: "iconfont"; /* Project id 3311610 */
src: url('//at.alicdn.com/t/font_3311610_7wh8injedpd.woff2?t=1649382821379') format('woff2'),
url('//at.alicdn.com/t/font_3311610_7wh8injedpd.woff?t=1649382821379') format('woff'),
url('//at.alicdn.com/t/font_3311610_7wh8injedpd.ttf?t=1649382821379') format('truetype');
}
.iconfont {
font-family: "iconfont" !important;
font-size: 16px;
font-style: normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.icon-reset:before {
content: "\e611";
}
.icon-move:before {
content: "\e67b";
}

@ -0,0 +1,225 @@
var startTouchs = [];
var touchType = ''
var startDistance = 0;
var touchCenter = [];
var cropperRect = null;
var imageRect = null;
var directionX = 0;
var directionY = 0;
var ratio = 0;
// 操作时改变的对象
var changes = {
imageRect: null,
cropperRect: null
}
export default {
computed: {
imageStyle() {
const imageRect = this.imageRect
if (imageRect) {
return {
left: imageRect.left + 'px',
top: imageRect.top + 'px',
width: imageRect.width + 'px',
height: imageRect.height + 'px'
}
} else {
return {}
}
},
cropperStyle() {
const cropperRect = this.cropperRect
if (cropperRect) {
return {
left: cropperRect.left + 'px',
top: cropperRect.top + 'px',
width: cropperRect.width + 'px',
height: cropperRect.height + 'px'
}
} else {
return {}
}
}
},
methods: {
touchStart() {
let ev;
if (arguments.length == 3) {
directionX = arguments[0];
directionY = arguments[1];
ev = arguments[2];
touchType = "controller";
} else {
touchType = "image";
ev = arguments[0];
}
startTouchs = ev.touches;
changes = {
imageRect: this.imageRect,
cropperRect: this.cropperRect
};
ratio = this.ratio;
cropperRect = {
...changes.cropperRect
}
imageRect = {
...changes.imageRect
}
if (startTouchs.length == 2) {
const imageRect = this.imageRect
var x1 = startTouchs[0].clientX
var y1 = startTouchs[0].clientY
var x2 = startTouchs[1].clientX
var y2 = startTouchs[1].clientY
var distance = Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2)
startDistance = Math.sqrt(distance)
var leftPercent = ((x1 + x2) / 2 - imageRect.left) / imageRect.width
var topPercent = ((y1 + y2) / 2 - imageRect.top) / imageRect.height
touchCenter = [leftPercent, topPercent]
}
},
touchMove(ev) {
if(startTouchs.length!==ev.touches.length) return
var touches = ev.touches;
var changeX1 = touches[0].clientX - startTouchs[0].clientX;
var changeY1 = touches[0].clientY - startTouchs[0].clientY;
if (startTouchs.length == 1) {
if (touchType === 'image') {
changes.imageRect.left = imageRect.left + changeX1;
changes.imageRect.top = imageRect.top + changeY1;
// console.log(startTouchs.length,ev.touches.length)
} else if (touchType === 'controller') {
var changeX = changeX1 * directionX;
var changeY = changeY1 * directionY;
// 比例缩放控制
if (ratio !== 0) {
if (directionX * directionY !== 0) {
if (changeX / ratio > changeY) {
changeY = changeX / ratio
changeX = changeY * ratio
} else {
changeX = changeY * ratio
changeY = changeX / ratio
}
} else {
if (directionX == 0) {
changeX = changeY * ratio
} else {
changeY = changeX / ratio
}
}
}
var width = cropperRect.width + changeX
var height = cropperRect.height + changeY
var imageRight = imageRect.left + imageRect.width
var imageBottom = imageRect.top + imageRect.height
if (directionX != -1) {
if (cropperRect.left + width > imageRight) {
width = imageRight - cropperRect.left
if (ratio !== 0) {
height = width / ratio
}
}
} else {
var cLeft = cropperRect.left - changeX
if (cLeft < imageRect.left) {
width = cropperRect.left + cropperRect.width - imageRect.left
if (ratio !== 0) {
height = width / ratio
}
}
}
// 判断是否触底
if (directionY != -1) {
if (cropperRect.top + height > imageBottom) {
height = imageBottom - cropperRect.top
if (ratio !== 0) {
width = height * ratio
}
}
} else {
var cTop = cropperRect.top - changeY
if (cTop < imageRect.top) {
height = cropperRect.top + cropperRect.height - imageRect.top
if (ratio !== 0) {
width = height * ratio
}
}
}
if (directionX == -1) {
changes.cropperRect.left = cropperRect.left + cropperRect.width - width
}
if (directionY == -1) {
changes.cropperRect.top = cropperRect.top + cropperRect.height - height
}
// 边界控制
changes.cropperRect.width = width
changes.cropperRect.height = height
}
} else if (touches.length == 2 && startTouchs.length == 2) {
var changeX2 = touches[0].clientX - touches[1].clientX;
var changeY2 = touches[0].clientY - touches[1].clientY;
var distance = Math.pow(changeX2, 2) + Math.pow(changeY2, 2)
distance = Math.sqrt(distance)
// 放大比例
var scaleRate = distance / startDistance
this.imageScale(scaleRate)
}
},
touchEnd(ev) {
// console.log('end',ev)
if(ev.touches.length!==0) return
if (touchType === "image") {
var cropperLeft = cropperRect.left
var cropperRight = cropperRect.left + cropperRect.width
var cropperTop = cropperRect.top
var cropperBottom = cropperTop + cropperRect.height
var rate = changes.imageRect.width / changes.imageRect.height
var cropperRate = cropperRect.width / cropperRect.height
if (changes.imageRect.width < cropperRect.width || changes.imageRect.height < cropperRect.height) {
var scale = 1
if (rate < cropperRate) {
scale = cropperRect.width / changes.imageRect.width
} else {
scale = cropperRect.height / changes.imageRect.height
}
imageRect.width = changes.imageRect.width
imageRect.height = changes.imageRect.height
this.imageScale(scale)
}
// 边界控制start
if (cropperLeft < changes.imageRect.left) {
changes.imageRect.left = cropperLeft
}
if (cropperRight > changes.imageRect.left + changes.imageRect.width) {
changes.imageRect.left = cropperRight - changes.imageRect.width
}
if (cropperTop < changes.imageRect.top) {
changes.imageRect.top = cropperTop
}
if (cropperBottom > changes.imageRect.top + changes.imageRect.height) {
changes.imageRect.top = cropperBottom - changes.imageRect.height
}
// 边界控制end
}
this.updateData({
cropperRect: changes.cropperRect,
imageRect: changes.imageRect,
})
touchType = ""
startTouchs = []
return false;
},
imageScale(scaleRate) {
var cw = imageRect.width * (scaleRate - 1)
var ch = imageRect.height * (scaleRate - 1)
changes.imageRect = {
width: imageRect.width + cw,
height: imageRect.height + ch,
left: imageRect.left - cw * (touchCenter[0]),
top: imageRect.top - ch * (touchCenter[1])
}
}
}
}

@ -0,0 +1,43 @@
export function getTouchPoints(touchs) {
return Array.from(touchs).map(ev => {
return [ev.clientX, ev.clientY]
})
}
// 函数防抖
export function debounce(fn, wait = 200) {
var timer = null;
return function (){
if (timer !== null) {
clearTimeout(timer);
}
timer = setTimeout(fn.bind(this), wait);
}
}
/**
* @description 睡眠
* @param {number} time 等待时间毫秒数
*/
export function sleep(time = 200) {
return new Promise(resolve => {
setTimeout(resolve, time)
})
}
const systemInfo = uni.getSystemInfoSync();
export function parseUnit(size){
if(typeof size == 'number' || !isNaN(Number(size))){
return uni.upx2px(size)
}else if(typeof size === 'string') {
if(size.endsWith('rpx')){
return parseUnit(size.replace('rpx',''))
}else if(size.endsWith('px')){
return Number(size.replace('px',''))
}else if(size.endsWith('vw')){
return Number(size.replace('vw',''))*systemInfo.screenWidth/100
}else if(size.endsWith('vh')){
return Number(size.replace('vh',''))*systemInfo.screenHeight/100
}
}
return 0
}

@ -0,0 +1,81 @@
{
"id": "bt-cropper",
"displayName": "bt-cropper图片裁剪插件",
"version": "3.0.1",
"description": "一款好用的图片裁剪插件",
"keywords": [
"图片",
"图片裁剪",
"图片裁剪",
"头像裁剪",
"cropper"
],
"repository": "",
"engines": {
"HBuilderX": "^3.2.1"
},
"dcloudext": {
"sale": {
"regular": {
"price": "0.00"
},
"sourcecode": {
"price": "0.00"
}
},
"contact": {
"qq": "1097122362"
},
"declaration": {
"ads": "无",
"data": "插件不采集任何数据",
"permissions": "无"
},
"npmurl": "",
"type": "component-vue"
},
"uni_modules": {
"dependencies": [],
"encrypt": [],
"platforms": {
"cloud": {
"tcb": "y",
"aliyun": "y"
},
"client": {
"Vue": {
"vue2": "y",
"vue3": "y"
},
"App": {
"app-vue": "y",
"app-nvue": "n"
},
"H5-mobile": {
"Safari": "y",
"Android Browser": "y",
"微信浏览器(Android)": "y",
"QQ浏览器(Android)": "y"
},
"H5-pc": {
"Chrome": "n",
"IE": "n",
"Edge": "n",
"Firefox": "n",
"Safari": "n"
},
"小程序": {
"微信": "y",
"阿里": "u",
"百度": "u",
"字节跳动": "y",
"QQ": "y"
},
"快应用": {
"华为": "n",
"联盟": "n"
}
}
}
}
}

@ -0,0 +1,109 @@
## bt-cropper 图片裁切
> **组件名:bt-cropper**
图片裁切组件,在页面中裁切图片,输出裁切后的图片,支持app,小程序,H5
### [在线体验](https://static-a3b890b4-7cb2-4b29-aa78-e652572bdef6.bspapp.com/#/)
> **注意事项**
> 为了避免错误使用,给大家带来不好的开发体验,请在使用组件前仔细阅读下面的注意事项,可以帮你避免一些错误。
> - 组件需要依赖 `sass` 插件 ,请自行手动安装
> - 只测试了头条小程序,app-vue 安卓,微信小程序和H5 大部分平台应该都没问题了
> - 包裹层或裁剪器需要手动指定高度和宽度,推荐手动指定裁剪器的大小,尤其是头条小程序,js有时候获取不到容器的大小
> - 如使用过程中有任何问题,或者您有一些好的建议,欢迎联系作者微信:1097122362
### 安装方式
本组件符合[easycom](https://uniapp.dcloud.io/collocation/pages?id=easycom)规范,`HBuilderX 2.5.5`起,只需将本组件导入项目,在页面`template`中即可直接使用,无需在页面中`import`和注册`components`。
### 基本用法
**示例**
```html
<template>
<view class="container">
<bt-cropper ref="cropper" :imageSrc="imageSrc"></bt-cropper>
<button @click="crop">裁切</button>
</view>
</template>
<style>
.container{
/** 外层一定要指定大小 */
height:100vh;
}
</style>
```
```javascript
export default {
methods:{
crop(){
// 通过组件定义的ref调用cropper方法,返回一个promise对象
this.$refs.cropper.crop().then(([err,res])=>{
if(!err){
console.log(res)
}else{
console.err(err)
}
})
}
}
}
```
### 限定裁切比例
bt-cropper,指定ratio即可设置裁切框的宽高比,如果你想让用户自由缩放,将ratio设置为0即可
**示例**
```html
<bt-cropper ref="cropper" :ratio="16/9":imageSrc="imageSrc"></bt-cropper>
```
## API
### cropper Props
|属性名|类型|默认值|说明|
|:-:|:-:|:-:|:-:|
|ratio|number|0|裁切图像的宽高比,0表示自由比例|
|dWidth|number|0|生成的图片的宽度,单位:px,如果传入0的话就是按原像素的比例裁剪,也就是说,输出图片的清晰度和输入图片的清晰度一样|
|imageSrc|String|''|原图的路径,支持本地路径和网络路径,如果是网络路径,小程序要注意配置下载域名,H5要注意跨域问题|
|mask|String|''| 裁剪的蒙版url,配合蒙版可以裁剪出任何形状的图形 (示例见全屏裁剪demo) |
|fileType|String|'jpg'|目标文件的类型,只支持 'jpg' 或 'png'。默认为 'jpg'|
|quality|Number|1|图片的质量,取值范围为 (0, 1],不在范围内时当作1.0处理|
|showGrid|Boolean|false|是否显示中心网格线,默认不显示|
|initPosition|object|null|图片自定义的初始的位置,内容格式见change事件|
|autoZoom|Boolean|true|是否开启操作结束后自动放大到窗口大小|
|containerSize|object|null|手动指定容器大小,如果裁剪器放在大小会移动或缩放的dom中,则必须手动指定大小,可以带上单位,如果不带单位默认px,支持的单位有:rpx,px,vw,vm,示例:{width:100,height:1100}或者:{width:'100rpx',height:'100rpx'}|
|canvas2d|Boolean|false| 是开启新版的canvas |
### cropper Methods
|方法名称|说明|参数|
|:-:|:-:|:-:|
|crop|裁剪图片|开始绘制并开始裁剪图片,返回Promise对象|
|init|初始化|-|
|resetImage|重置裁剪框和图片的位置和大小到初始的位置和大小|-|
|redo|撤销,最多可以回退10步|-|
|resume|重做|-|
### cropper Events
|方法名称|说明|返回值|
|:-:|:-:|:-:|
|change|当裁剪框和图片的相对位置发生变化的时候触发,返回裁剪框与图片的相对位置|ev={left:number,top:number,width:number,height:number}|
|loadFail|当图片加载失败时触发| - |
|cropStart|当裁开始时触发| - |
## 帮助
在使用中如遇到无法解决的问题,请提 [Issues](https://gitee.com/xiaojiang1996/better-uni-cropper/issues) 或者加我 微信:1097122362。
Loading…
Cancel
Save