目录
一、自定义相机(Camera.vue)
1、图例
2、代码
二、图片预览(Preview.vue)
1、图例
2、代码
三、裁剪插件 (Cropping.vue)
1、图例
2、代码
四、父组件
五、链接
封装自定义相机,并使用剪切插件对自定义相机拍照的图片实现剪切功能,且如不需要剪切功能也可禁用使用图片仅预览功能,进行后续上传服务器操作(图片上传功能后续补充)
一、自定义相机(Camera.vue)
注: 如不开启裁剪模式 isCrop:true 则默认为仅预览状态!(可根据自身业务需求更改)
1、图例
2、代码
<template>
<view class="camera">
<!-- 相机组件 -->
<view v-show="isShow">
<camera v-if="isAuth" :device-position="devPosition?'front':'back'" flash="off" @error="error" />
<view class="footer">
<view class="back" v-if="back">
<view @click="Back">
<image :src="require('../Camera/IconFont/back.png')" mode="aspectFill" />
</view>
</view>
<view class="album" v-else>
<view @click="Album">
<image :src="require('../Camera/IconFont/album.png')" mode="aspectFill" />
</view>
</view>
<view class="takePhoto">
<view @click="TakePhoto">
<image :src="require('../Camera/IconFont/takePhoto.png')" mode="aspectFill" />
</view>
</view>
<view class="devPosition">
<view @click="CameraPosition">
<image :src="require('../Camera/IconFont/devPosition.png')" mode="aspectFill" />
</view>
</view>
</view>
</view>
<!-- 裁剪 -->
<CroppingImg ref="croppingRef" v-if="isCrop" @close="closeCrop" />
<!-- 预览 -->
<PreViewImg v-else ref="previewRef" />
</view>
</template>
<script>
import PreViewImg from './Preview/index.vue'
import CroppingImg from './Cropping/index.vue'
export default {
components: {
PreViewImg,
CroppingImg
},
data() {
return {
isAuth: false, //用户是否授权调用相机权限
isShow: true,
devFront: ""
}
},
props: {
/**
* 是否开启裁剪(使用裁剪功能将会禁用照片仅预览功能)
* @param {default:false}
*/
isCrop: {
type: Boolean,
default: false
},
/**
* 前后置摄像头状态
* 默认前置状态
* @param {default:true}
*/
devPosition: {
type: Boolean,
default: true
},
/**
* 是否开启返回(开启返回按钮,选择相册功能将禁用,仅可拍照)
* @param {default:false}
*/
back: {
type: Boolean,
default: false
},
/**
* 图片预览临时路径
* @param {default:""}
*/
tempImagePath: {
type: String,
default: ""
}
},
watch: {
// 监听前后置镜头转换状态变化
devPosition: {
deep: true,
immediate: true,
handler(newValue, oldValue) {
return newValue
}
}
},
methods: {
/**
* 打开相机回调函数方法
* @param {Boolean} isAuth
*/
open() {
const that = this
uni.getSetting({
success: res => {
if (res.authSetting['scope.camera']) {
// 用户已经授权
that.isAuth = true
} else {
// 用户还没有授权,向用户发起授权请求
uni.authorize({
scope: 'scope.camera',
success() { // 用户同意授权
that.isAuth = true
},
fail() { // 用户不同意授权
that.OpenSetting().then(res => {
that.isAuth = true
})
}
})
}
},
fail: res => {
uni.$u.toast('获取用户授权信息失败' + res)
}
})
},
/**
* 打开授权设置界面
* @param {type} = [value]
*/
OpenSetting() {
const that = this
let promise = new Promise((resolve, reject) => {
uni.showModal({
title: '授权',
content: '请先授权获取摄像头权限',
success(res) {
if (res.confirm) {
uni.openSetting({
success(res) {
if (res.authSetting['scope.camera']) { // 用户打开了授权开关
resolve(true)
} else { // 用户没有打开授权开关, 继续打开设置页面
that.OpenSetting().then(res => {
resolve(true)
})
}
},
fail(res) {
uni.$u.toast('错误代码:' + res)
}
})
} else if (res.cancel) {
that.OpenSetting().then(res => {
resolve(true)
})
}
}
})
})
return promise;
},
/**
* 返回回调函数方法
* @param {$emit} Back
*/
Back() {
const that = this
that.$emit('Back')
},
/**
* 选择相册照片回调函数方法
* @param {String} tempFilePaths[0]
*/
async Album() {
const that = this
uni.chooseImage({
sourceType: ['album'],
success(res) {
if (res.errMsg == 'chooseImage:ok') {
that.setMode(res.tempFilePaths[0])
}
},
fail(res) {
uni.$u.toast('错误代码:' + res)
}
})
},
/**
* 拍照回调函数方法
* @param {String} tempImagePath
*/
TakePhoto() {
const that = this
const ctx = uni.createCameraContext()
ctx.takePhoto({
quality: 'high',
success: (res) => {
that.setMode(res.tempImagePath)
}
})
},
/**
* 判断是否使用裁剪或仅预览模式
* @param {Object} data
*/
setMode(data) {
if (this.isCrop) {
this.$refs.croppingRef.open(data) //裁剪
this.isShow = false
} else {
this.$refs.previewRef.open(data) //仅预览
}
},
/**
* 切换前后置摄像头回调函数方法
* @param {$emit} CameraPosition
*/
CameraPosition() {
this.$emit("CameraPosition")
},
/**
* 关闭照片裁剪功能回调函数方法
* @param {Boolean} isShow
*/
closeCrop() {
this.isShow = true
}
}
}
</script>
<style lang="scss" scoped>
.camera {
width: 100%;
height: 100%;
position: relative;
}
.camera camera {
height: 100vh;
}
.footer {
position: fixed;
left: 0;
right: 0;
bottom: 0;
background: #FFF;
border-radius: 10rpx 10rpx 0 0;
display: flex;
justify-content: center;
align-items: center;
padding-top: 20rpx;
padding-bottom: 30rpx;
box-shadow: 0 0 15rpx rgba(244, 244, 244, 0.4);
z-index: 5;
.back,
.album,
.takePhoto,
.devPosition {
margin: auto;
image {
width: 75rpx;
height: 75rpx;
}
}
.album {
image {
width: 85rpx;
height: 85rpx;
}
}
.takePhoto {
image {
width: 120rpx;
height: 120rpx;
}
}
}
</style>
二、图片预览(Preview.vue)
1、图例
2、代码
<template>
<u-transition :show="isShow" mode="zoom-out">
<view class="preview">
<image :src="tempFilePaths" mode="aspectFill" />
<view class="footer">
<view class="btn-1">
<u-button shape="circle" type="warning" text="重新拍照" @click="close" />
</view>
<view class="btn-2">
<u-button shape="circle" type="primary" text="确认选择" @click="confirm" />
</view>
</view>
</view>
</u-transition>
</template>
<script>
export default {
data() {
return {
isShow: false,
tempFilePaths: "" //预览图片临时路径
}
},
methods: {
// 打开预览
open(tempFilePaths) {
this.isShow = true
this.tempFilePaths = tempFilePaths
},
// 确认选择照片进行后续操作回调函数
confirm() {
let pages = getCurrentPages() //获取当前页面栈实例
let prevPage = pages[pages.length - 2] //获取上一页面栈实例,-3 == 上上一个页面栈实例
//返回上一页并传递所需当前页面栈参数
prevPage.$vm.getChildValue(JSON.stringify({
tempFilePath: this.tempFilePaths
}))
// 返回页面栈
uni.navigateBack({
delta: 1
})
this.close()
},
// 关闭相片预览
close() {
this.isShow = false
}
}
}
</script>
<style lang="scss" scoped>
.preview {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 20;
background: #000;
padding: 160rpx 40rpx;
image {
width: 100%;
height: 100%;
}
.footer {
position: fixed;
left: 0;
right: 0;
bottom: 55rpx;
display: flex;
align-items: center;
.btn-1,
.btn-2 {
width: 40%;
margin: auto;
}
}
}
</style>
三、裁剪插件 (Cropping.vue)
插件:bt-cropper图片裁剪插件 - DCloud 插件市场
1、图例
2、代码
<template>
<view class="container" v-show="isShow">
<bt-cropper ref="cropper" :fileType="'jpg'" :ratio="16/20" :imageSrc="tempImagePath" />
<view class="footer">
<view class="btn-1">
<u-button shape="circle" type="warning" text="重新拍照" @click="close" />
</view>
<view class="btn-2">
<u-button shape="circle" type="primary" text="确定选择" @click="crop" />
</view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
isShow: false,
tempImagePath: "",
}
},
methods: {
// 打开回调函数方法
open(tempImagePath) {
this.isShow = true
this.tempImagePath = tempImagePath
},
// 裁剪回调函数方法
crop() {
// 通过组件定义的ref调用cropper方法,返回一个promise对象
this.$refs.cropper.crop().then((res) => {
let pages = getCurrentPages() //获取当前页面栈实例
let prevPage = pages[pages.length - 2] //获取上一页面栈实例,-3 == 上上一个页面栈实例
//返回上一页并传递所需当前页面栈参数
prevPage.$vm.getChildValue(JSON.stringify({
tempFilePath: res
}))
// 返回页面栈
uni.navigateBack({
delta: 1
})
})
},
// 重新拍照,关闭裁剪
close() {
this.isShow = false
this.$emit('close')
}
}
}
</script>
<style lang="scss" scoped>
.container {
/** 外层一定要指定大小 */
height: 100vh;
z-index: 20;
.footer {
width: 100%;
position: fixed;
left: 0;
right: 0;
bottom: 0;
z-index: 30;
background: #000;
display: flex;
align-items: center;
padding-top: 25rpx;
padding-bottom: 50rpx;
.btn-1,
.btn-2 {
width: 40%;
margin: auto;
}
}
}
</style>
四、父组件
组件命名可根据真是喜好更改(无限制) ,只需注意引用方式即可。全局引入或按需引用皆可
<BaseCamera ref="BaseCamera" :dev-position="devPosition" @CameraPosition="CameraPosition" />
<script>
import BaseCamera from "@/components/Camera/index.vue"
export default {
components: {
BaseCamera
},
data() {
return {
devPosition: true
}
},
onLoad() {
this.$refs.BaseCamera.open()//调用相机
},
methods: {
// 切换前后置镜头回调函数方法
CameraPosition() {
this.devPosition = !this.devPosition
},
}
}
</script>
五、项目链接
Gitee地址: https://gitee.com/huangzixing/components.git