<template> <view class="camera"> <view class="camera-container" v-if="showType === 1"> <cover-view class="header-box" :style="{'height':navHeight/2 + 'px'}"> <cover-view class="inner"> <cover-image @click="setCameraInfo('devicePosition')" class="icon" mode="aspectFit" src="/static/photo/icon-switch.png"></cover-image> <cover-image @click="setCameraInfo('flash')" class="icon" mode="aspectFit" :src="'/static/photo/icon-flash-' + cameraInfo.flash +'.png'"></cover-image> <cover-image v-if="currentItem.partnerCompanyId" @click="changeType(3)" class="icon" mode="aspectFit" src="/static/photo/icon-setting.png"></cover-image> </cover-view> </cover-view> <camera class="camera-box" v-if="cameraChange&&!standardShow" :device-position="cameraInfo.devicePosition" :flash="cameraInfo.flash" :style="{'height':cameraHeight/2 + 'px','top':parseInt(navHeight/2) + 'px'}" @error="cameraError"> <cover-view class="mark-box"> <cover-view class="txt big" v-if="titleTxt">{{titleTxt}}</cover-view> <cover-view class="txt" v-for="(txt,index) in settingTxtArr" :key="index">{{txt || '获取中'}}</cover-view> <!-- <cover-view class="txt u-m-t-10">维度:{{waterInfo.latitude || '正在获取中...'}}</cover-view> <cover-view class="txt u-m-t-10">地址:{{waterInfo.address || '正在获取中...'}}</cover-view> <cover-view class="txt u-m-t-10">时间:{{waterInfo.date || '正在获取中...'}}</cover-view> --> </cover-view> <cover-image class="mark-water" mode="aspectFit" :src="waterInfo.markImage"></cover-image> </camera> <cover-view class="bottom-box" v-if="!standardShow"> <cover-image class="icon" src='/static/photo/icon-back.png' mode="" @click="goBack"></cover-image> <cover-image class='icon-take icon' src='/static/photo/icon-take.png' @click="$u.debounce(takePhoto, 500)"> </cover-image> <cover-image class='icon' src='/static/photo/icon-standard.png' @click="standardShow = true" v-if="standardStatus"> </cover-image> <view class='icon' v-else></view> </cover-view> <canvas canvas-id="canvas" id="photoCanvas" class="canvas" :style="{'width':750 + 'rpx','height': cameraHeight + 'rpx'}"></canvas> <view v-if="standardShow" class="page-mask" :z-index="10000"> <photo-standard :currentItem="currentItem" /> <view class="btn-close"> <image @click="closeShadow" class="icon icon-close" mode="aspectFit" src="/static/photo/icon-close.png"></image> </view> </view> </view> <photo-preview v-if="showType === 2" @changeType="changeType" @finish="finish" :upload="upload" /> <photo-setting v-if="showType === 3" @changeType="changeType" @changeWatermark="changeWatermark"/> </view> </template> <script> const formatSize = function(size, pointLength, units) { var unit; units = units || [ 'B', 'K', 'M', 'G' ]; while ( (unit = units.shift()) && size > 1024 ) { size = size / 1024; } return (unit === 'B' ? size : size.toFixed( pointLength === undefined ? 2 : pointLength)) + unit; } let r = require("@/libs/bmap-wx.min.js"); let h = new r.BMapWX({ // ak: "rpVq5d3yxaRCoKzVwmFMo0o4iC524CAG", ak: "iXsGdyEcm1iQONoUPyEp0hrBTLCuOaW2" }); import standard from '@/components/photo/standard.vue' import preview from '@/components/photo/preview.vue' import setting from '@/components/photo/setting.vue' export default { props: { currentItem: { // 对应的规范item default () { return { _id: '', // 规格ID system_id: '', // 系统ID brand_id: '', // 品牌ID classify_id: '', // 分类ID images: [], // 拍照要求示意图 main_point: [] // 要点 } } }, upload: { // 是否默认上传 true:会自动上传好图片并返回绝对路径 false: 只返回临时路径,需要在父组件做上传动作 type: Boolean, default () { return true } }, num: { type: Number, default () { return 10 } } }, components: { 'photo-standard': standard, 'photo-preview': preview, 'photo-setting': setting, }, data() { return { showType: 1, standardShow: false, cameraInfo: { devicePosition: 'back', flash: 'off' }, waterInfo: { longitude: '', // 经度 latitude: '', // 维度 address: '', //地址 date: '', //日期 markImage: 'https://qn-static.banshouhui.com/live_mark.png' }, tempImgs: { 'markImage': 'https://qn-static.banshouhui.com/live_mark.png', // 拍照水印 'origin': '', // 无水印照片 'mixWater': '', // 有水印照片 }, authSetting: { camera: false, // 相机授权 location: false, // 位置授权 }, navHeight: 130, cameraWidth: 750, cameraHeight: 1000, // (4 / 3 * 750) 图片比例4:3 ratio: 0, cameraChange: false, waterSetting: null, shadowInitShow: false, // 第一次进入页面需显示弹窗 standardStatus: false, // 是否显示拍照要求 } }, created(e) { const photo = getApp().globalData.photo getApp().trackPage('拍照页') photo.allImages = [] const length = (this.num > 10 || this.num < 0 || typeof this.num !== 'number') ? 0 : 10 - this.num photo.currentItemLength = length // this.ctx = uni.createCameraContext(); this.updateTime() const locationData = uni.getStorageSync('locationData') const validityTime = 60000 if (Date.now() - locationData.locationTime < validityTime) { // 1分钟内不重复请求位置信息 this.waterInfo.latitude = locationData.waterInfo.latitude this.waterInfo.longitude = locationData.waterInfo.longitude this.baiduLocation() } else { this.getLocation() } this.getMarkPath() this.initData() photo.currentItem = this.currentItem if (this.currentItem.images && this.currentItem.images.length > 0) { this.shadowInitShow = false this.standardShow = true this.standardStatus = true } }, computed: { titleTxt() { const waterSetting = this.waterSetting let txt = '' if (!waterSetting || !waterSetting.lowerLeft) { return '' } txt = waterSetting.lowerLeft && waterSetting.lowerLeft.title || '' return txt }, settingTxtArr() { let arr = [] // 包含经纬度 const waterInfo = this.waterInfo arr = ['经度:' + waterInfo.longitude, '纬度:' + waterInfo.latitude, '地址:' + waterInfo.address, '时间:' + waterInfo.date ] let settingTxtArr = [] const typeArr = ['经度', '纬度', '地址', '时间'] const waterSetting = this.waterSetting if (!waterSetting || !waterSetting.lowerLeft || !waterSetting.lowerLeft.body) { return arr } const settingArr = waterSetting.lowerLeft.body settingArr.map(item => { const idx = typeArr.indexOf(item.watemarkType) if (item.enable && (idx > -1 || (item.watemarkValue && item.watemarkText))) { if (idx > -1) { settingTxtArr.push(arr[idx]) } else { settingTxtArr.push(item.watemarkText + ':' + item.watemarkValue) } } }) return settingTxtArr } }, watch: {}, mounted() { // 默认开启弹窗 const photo = getApp().globalData.photo this.waterSetting = photo.waterSetting this.standardShow = this.shadowInitShow this.cameraChange = true // this.initData() }, destroyed(){ this.cameraChange = false }, methods: { closeShadow() { this.shadowInitShow = false this.standardShow = false }, goBack() { uni.showTabBar() this.$emit('close') }, finish() { uni.showTabBar() const photo = getApp().globalData.photo this.$emit('close', photo.allImages) }, changeType(type) { this.showType = type }, changeWatermark(watermark){ this.waterSetting = watermark }, updateTime() { this.waterInfo.date = this.getTime() }, setCameraInfo(type) { // 设置相机前置后置、闪关灯等 const cameraInfo = this.cameraInfo let val = cameraInfo[type] switch (type) { case 'devicePosition': val = val === 'back' ? 'front' : 'back' break; case 'flash': val = val === 'off' ? 'torch' : 'off' break; defalut: break; } this.cameraInfo[type] = val }, initData() { const globalData = getApp().globalData this.navHeight = globalData.navHeight this.cameraHeight = parseInt(globalData.safeHeight*2 - globalData.navHeight - 200) this.ratio = globalData.screenHeight / globalData.rpxScreenHeight this.waterSetting = globalData.photo.waterSetting }, baiduLocation() { // 百度sdk解析经纬度 let waterInfo = this.waterInfo const t = function() { let t = { success: function(res) { let e = res.wxMarkerData[0]; let i = e.address; waterInfo.address = i }, fail: function(err) { console.log(err, 'err') } }; if (waterInfo.latitude && waterInfo.longitude) { let e = waterInfo.latitude + "," + waterInfo.longitude; t.location = e; } h.regeocoding(t); }; t() }, cameraError(e) { this.showSettingModal('camera') }, //获取位置 getLocation() { let that = this const waterInfo = this.waterInfo uni.getLocation({ type: 'gcj02', success: function(res) { waterInfo.longitude = parseFloat(res.longitude).toFixed(4) waterInfo.latitude = parseFloat(res.latitude).toFixed(3) const data = { waterInfo: { latitude: waterInfo.latitude, longitude: waterInfo.longitude }, locationTime: Date.now() } uni.setStorage({ key: 'locationData', data: data, }) that.baiduLocation() }, fail(err) { that.showSettingModal('location') // that.showLocationAuthModal('') } }); }, showSettingModal(type) { type = type || 'camera' const self = this let authSetting = { 'camera': { content: '将无法进行拍照', method: '' }, 'location': { content: '将无法定位当前位置', method: '' }, 'writePhotosAlbum': { content: '将无法保存图片到本地', method: '' } } let content = authSetting[type].content uni.showModal({ content: '拒绝授权' + content + ',是否去设置打开?', confirmText: "确认", cancelText: '取消', success(res) { if (res.confirm) { self.openSetting() } else if(res.cancel) { if (type === 'camera' || type === 'location') self.goBack() } }, fail() { } }) }, openSetting() { // 打开设置 let self = this this.cameraChange = false wx.openSetting({ success(res) { const authSetting = res.authSetting self.cameraChange = true if (authSetting['scope.userLocation']) { // 位置授权成功 self.authSetting.location = true self.getLocation() } else { self.showSettingModal('location') } if (res.authSetting['scope.camera']) { self.authSetting.camera = true } if (res.authSetting['scope.writePhotosAlbum']) { self.authSetting.writePhotosAlbum = true } } }) }, //获取 水印 本地缓存路径 getMarkPath: function() { uni.downloadFile({ url: this.waterInfo.markImage, //仅为示例,并非真实的资源 success: (res) => { if (res.statusCode === 200) { this.tempImgs.markImage = res.tempFilePath } }, fail() { console.log('水印图片 下载失败') } }) }, /* ---------------------- 拍照流程 ---------------------- */ // 第一步:拍照 takePhoto() { wx.createCameraContext().takePhoto({ quality: "high" }).then(res => { this.tempImgs.origin = res.tempImagePath this.drawCanvas(res.tempImagePath) }) }, // 第二步:绘制画布 drawCanvas: async function(tempPath) { uni.showLoading({ title: '数据处理中...', icon: 'loading' }) const waterSetting = this.waterSetting const globalTempPath = getApp().globalData.photo.tempPath const ratio = this.ratio // const ratio = 0.5 let canvasWidth = this.cameraWidth * ratio let canvasHeight = this.cameraHeight * ratio const waterInfo = this.waterInfo this.showImage = true const settingTxtArr = this.settingTxtArr //将原图画到canvas上面 let canvas = uni.createCanvasContext("canvas", this) canvas.rect(0, 0, canvasWidth, canvasHeight) canvas.drawImage(tempPath, 0, 0, canvasWidth, canvasHeight) // this.canvas.draw() let txtHeight = 18 let h = 1 //加入 水印文字 for (let i = settingTxtArr.length - 1; i >= 0; i--) { txtHeight = 20 canvas.setFontSize(15) canvas.setFillStyle('#f2f2f2') let txt = settingTxtArr[i] if(txt.indexOf('时间')>=0){ canvas.fillText(txt, 10, canvasHeight - txtHeight * h++); }else{ if(txt.length>22){ canvas.fillText(txt.substr(22,22), 10, canvasHeight - txtHeight * h++); canvas.fillText(txt.substr(0,22), 10, canvasHeight - txtHeight * h++); }else{ canvas.fillText(txt, 10, canvasHeight - txtHeight * h++); } } // canvas.fillText(txt, 10, canvasHeight - txtHeight * h++); } const title = this.titleTxt if (title) { canvas.save() let titleHeight = title.length > 14 ? 60 : 30 let rectWidth = title.length > 14 ? 215 : title.length*10 canvas.setFillStyle('rgba(34, 114, 255, 0.6)') canvas.fillRect(10, canvasHeight - txtHeight * h - titleHeight, 215, titleHeight) canvas.restore() canvas.setFontSize(20) canvas.setFillStyle('#f2f2f2') // canvas.setTextAlign('center') if (title.length > 14) { canvas.fillText(title.substr(14, 14), 10, canvasHeight - txtHeight * h++ + 20); } canvas.fillText(title.substr(0, 14), 10, canvasHeight - txtHeight * h++ - titleHeight / 2 + 10); } // if (this.waterSetting && this.waterSetting.mid_text) { //加入 水印图片 canvas.drawImage(this.tempImgs.markImage, canvasWidth / 2 - 100, canvasHeight / 2 - 25, 200, 50) // } // canvas.drawImage(this.tempImgs.markImage, canvasWidth / 2 - 100, canvasHeight / 2 - 25, 200, 50) canvas.draw(false, setTimeout(() => { uni.canvasToTempFilePath({ width: canvasWidth, height: canvasHeight, canvasId: 'canvas', success: async (res) => { const size = await this.getImageSize(res.tempFilePath) this.tempImgs.mixWater = res.tempFilePath globalTempPath.origin = this.tempImgs.origin globalTempPath.size = formatSize(size) || '' globalTempPath.mixWater = res.tempFilePath let timer = setTimeout(()=>{ //this.goPage('/pages/photo/preview') this.changeType(2) },0) // this.saveToLocal(res.tempFilePath) }, fail: (res) => { console.log(res); }, complete: () => { uni.hideLoading(); } }, this); }, 50)) }, // 第三步:保存到本地 saveToLocal(tempImagePath) { //has_origin //是否保存原图 let that = this uni.saveImageToPhotosAlbum({ filePath: tempImagePath, success() { uni.showToast({ icon: 'none', title: '已将照片保存到本地相册' }) }, fail() {} }) }, getImageSize(path) { return new Promise((resolve, reject) => { wx.getFileInfo({ filePath: path, success (res) { resolve(res.size) }, fail() { resolve(0) } }) }) }, showPreviewFn(img) { uni.previewImage({ current: 0, urls: [img] }); }, //获取当前时间 getTime: function() { let d = new Date(); let year = d.getFullYear(); let month = d.getMonth() + 1; let date = d.getDate(); let days = new Array("日", "一", "二", "三", "四", "五", "六"); let day = d.getDay(); let hour = (d.getHours() < 10) ? ("0" + d.getHours()) : d.getHours(); let min = (d.getMinutes() < 10) ? ("0" + d.getMinutes()) : d.getMinutes(); let sec = (d.getSeconds() < 10) ? ("0" + d.getSeconds()) : d.getSeconds(); let now = year + "年" + month + "月" + date + "日 " + hour + ":" + min + ":" + sec; return now }, } } </script> <style scoped lang="scss"> .camera { position:fixed; padding:0; margin:0; top:0; left:0; width: 100%; height: 100%; z-index: 999999; } .camera-container { // padding-top:var(--status-bar-height); min-height: 100vh; background-color: black; } .header-box { // padding:10rpx 20rpx; position: relative; .inner { display: flex; position: absolute; left: 0; right: 0; bottom: 0; } .icon { margin: 20rpx; display: block; width: 44rpx; height: 44rpx; } } .camera-box { position: absolute; left:0; width: 100%; // height: 100vh; z-index: 1; } .mark-water { position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%); width: 360rpx; height: 90rpx; z-index: 6; } .mark-box { position: absolute; left: 0; bottom: 0; padding: 20rpx; .txt { max-width: 500rpx; max-height: 80rpx; overflow: hidden; margin-top: 5rpx; line-height: 40rpx; white-space: normal; font-size: 28rpx; color: #fff; &.big { padding: 10rpx; margin-bottom: 10rpx; width: 430rpx; line-height: 40rpx; font-size: 32rpx; background-color: rgba(34, 114, 255, 0.6); } } } .bottom-box { position: absolute; left: 0; right: 0; bottom:0; // bottom: constant(safe-area-inset-bottom); // bottom: env(safe-area-inset-bottom); z-index: 5; box-sizing: border-box; height: 200rpx; display: flex; justify-content: space-between; padding: 20rpx 78rpx; align-items: center; background-color: black; .txt { white-space: nowrap; line-height: 60rpx; color: #fff; } .icon { display: block; width: 92rpx; height: 92rpx; &.icon-take { width: 144rpx; height: 144rpx; } } } .canvas { position: fixed; left: -750rpx; top: 0; } // 拍照规范 .page-mask { position: fixed; bottom: 0; bottom: constant(safe-area-inset-bottom); bottom: env(safe-area-inset-bottom); z-index: 1000; width: 750rpx; height: 100vh; // padding: 0 30rpx; box-sizing: border-box; background-color: rgba(0, 0, 0, 0.8); .icon { width: 92rpx; height: 92rpx; } .btn-close { position: absolute; bottom: 0; bottom: constant(safe-area-inset-bottom); bottom: env(safe-area-inset-bottom); left: 0; right: 0; height: 200rpx; z-index: 100; display: flex; justify-content: center; align-items: center; } } </style>