公司項目需要實現一個隨手拍功能,需求是點擊首頁的懸浮按鈕會進入攝像頭界面,只能拍照,不能從相冊選取圖片,拍照后加水印然后上傳。
步驟划分:1.打開相機,2.圖片加水印,3.圖片上傳,4.app權限申請問題,因為用戶可能禁止了app相應的權限,如果不做權限處理會導致該功能不正常。
為快速實現功能,使用了兩個插件,插件地址:
https://ext.dcloud.net.cn/plugin?id=3597
https://ext.dcloud.net.cn/plugin?id=594
水印開發過程中遇到的問題:
1.圖片會被縮小
2.大圖會被裁剪
導致該問題的原因是因為畫布大小和圖片大小不一致,我的解決方案:
uni.chooseImage({ count: 1, //默認9 sourceType: ['camera'], //只能拍照 success: function(res) { uni.getImageInfo({ src: res.tempFilePaths[0], success: function(image) { var s = 1; if (image.width > image.height) { s = uni.getSystemInfoSync().windowWidth / image .width } else { s = uni.getSystemInfoSync().windowHeight / image.height } that.imgh = image.height * s that.imgw = image.width * s that.watermark1(res.tempFilePaths[0]) } });
水印處理
watermark1(img) { let that = this this.$nextTick(async () => { uni.showLoading({ title: '圖片處理中...' }) that.ispost = false //圖片上傳中 // 初始化 await this.$refs.rCanvas.init({ canvas_id: "rCanvas", scale: 2, canvas_width: this.imgw, canvas_height: this.imgh, hidden: true }) // 畫圖 await this.$refs.rCanvas.drawImage({ url: img, x: 0, y: 0, w: this.imgw, h: this.imgh }).catch(err_msg => { uni.showToast({ title: err_msg, icon: "none" }) }) await this.$refs.rCanvas.fillRoundRect({ text: "•", max_width: 0, x: this.txtx - 35, y: this.imgh - this.txty - 20, w: 215, h: 75, fill_color: "#000000", font_size: 20 }).catch(err_msg => { uni.showToast({ title: err_msg, icon: "none" }) }) // 畫文字 await this.$refs.rCanvas.drawText({ text: "地點:" + this.address, max_width: 0, x: this.txtx, y: this.imgh - this.txty, font_color: "#F5F5F5", font_size: 13 }).catch(err_msg => { uni.showToast({ title: err_msg, icon: "none" }) }) await this.$refs.rCanvas.drawText({ text: "•", max_width: 0, x: this.txtx - 15, y: this.imgh - this.txty + 2, font_color: "#FFF100", font_size: 20 }).catch(err_msg => { uni.showToast({ title: err_msg, icon: "none" }) }) await this.$refs.rCanvas.drawText({ text: "時間:" + this.ctime, max_width: 0, x: this.txtx, y: this.imgh - this.txty + this.txth, font_color: "#F5F5F5", font_size: 13 }).catch(err_msg => { uni.showToast({ title: err_msg, icon: "none" }) }) await this.$refs.rCanvas.drawText({ text: "•", max_width: 0, x: this.txtx - 15, y: this.imgh - this.txty + 2 + this.txth, font_color: "#FFF100", font_size: 20 }).catch(err_msg => { uni.showToast({ title: err_msg, icon: "none" }) }) await this.$refs.rCanvas.drawText({ text: "拍攝人:" + this.cname, max_width: 0, x: this.txtx, y: this.imgh - this.txty + this.txth + this.txth, font_color: "#F5F5F5", font_size: 13 }).catch(err_msg => { uni.showToast({ title: err_msg, icon: "none" }) }) await this.$refs.rCanvas.drawText({ text: "•", max_width: 0, x: this.txtx - 15, y: this.imgh - this.txty + 2 + this.txth + this.txth, font_color: "#FFF100", font_size: 20 }).catch(err_msg => { uni.showToast({ title: err_msg, icon: "none" }) }) // 生成海報 await this.$refs.rCanvas.draw((res) => { //res.tempFilePath:生成成功,返回base64圖片 // 保存圖片 this.$refs.rCanvas.saveImage(res.tempFilePath) uni.hideLoading() // console.log(res.tempFilePaths) let imgs = [{ name: "images" + new Date().getTime(), uri: res.tempFilePath }] // console.log('imgs='+JSON.stringify(imgs)) uni.showLoading({ title: '上傳中...' }) // 上傳到雲存儲器獲取鏈接 uni.uploadFile({ url: apiUrl + '/api/tools/oss/uploadBatch', files: imgs, header: { Authorization: getUser().token }, success: (uploadFileRes) => { uni.hideLoading() that.ispost = true //圖片上傳完成 // console.log('12='+JSON.stringify(uploadFileRes.data)) let str = JSON.parse(uploadFileRes.data) str.data.forEach(item => { that.img.push(item) }) }, fail: (re) => { uni.hideLoading() that.ispost = true //圖片上傳完成 } }); }) }) },
因為原插件只只能一個透明度,而我又不想反復繪制,就修改了一下,修改后的插件代碼
export default{ data(){ return{ system_info:{}, //system info canvas_width:0, //canvas width px canvas_height:0, //canvas height px ctx:null, //canvas object canvas_id:null, //canvas id hidden:false,//Whether to hide canvas scale:1,//canvas scale r_canvas_scale:1, if_ctx:true } }, methods:{ /** * save r-canvas.vue object * @param {Object} that */ // saveThis(that){ // rCanvasThis = that // }, /** * Draw round rect text * @param {Object} config * @param {Number} config.x x坐標 * @param {Number} config.y y坐標 * @param {Number} config.w 寬度 * @param {Number} config.h 高度 * @param {Number} config.radius 圓角弧度 * @param {String} config.fill_color 矩形顏色 */ fillRoundRect(config) { return new Promise((resolve,reject)=>{ let x = this.compatibilitySize(parseFloat(config.x)*this.scale) let y = this.compatibilitySize(parseFloat(config.y)*this.scale) let w = this.compatibilitySize(parseFloat(config.w)*this.scale) let h = this.compatibilitySize(parseFloat(config.h)*this.scale) let radius = config.radius?parseFloat(config.radius)*this.scale:10*this.scale let fill_color = config.fill_color || "black" // The diameter of the circle must be less than the width and height of the rectangle if (2 * radius > w || 2 * radius > h) { reject("The diameter of the circle must be less than the width and height of the rectangle") return false; } this.ctx.setGlobalAlpha(0.6); this.ctx.save(); this.ctx.translate(x, y); // this.drawRoundRectPath({ w: w, h: h, radius: radius }); this.ctx.fillStyle = fill_color this.ctx.fill(); this.ctx.restore(); resolve() this.ctx.setGlobalAlpha(1); }) }, /** * Draws the sides of a rounded rectangle * @param {Object} config * @param {Number} config.w 寬度 * @param {Number} config.h 高度 * @param {Number} config.radius 圓角弧度 */ drawRoundRectPath(config) { this.ctx.beginPath(0); this.ctx.arc(config.w - config.radius, config.h - config.radius, config.radius, 0, Math.PI / 2); this.ctx.lineTo(config.radius, config.h); this.ctx.arc(config.radius, config.h - config.radius, config.radius, Math.PI / 2, Math.PI); this.ctx.lineTo(0, config.radius); this.ctx.arc(config.radius, config.radius, config.radius, Math.PI, Math.PI * 3 / 2); this.ctx.lineTo(config.w - config.radius, 0); this.ctx.arc(config.w - config.radius, config.radius, config.radius, Math.PI * 3 / 2, Math.PI * 2); this.ctx.lineTo(config.w, config.h - config.radius); this.ctx.closePath(); }, /** * Draw special Text,line wrapping is not supported * @param {Object} config * @param {String} config.text 文字 * @param {Number} config.x x坐標 * @param {Number} config.y y坐標 * @param {String} config.font_color 文字顏色 * @param {String} config.font_family 文字字體 * @param {Number} config.font_size 文字大小(px) */ drawSpecialText(params){ let general = params.general let list = params.list return new Promise(async (resolve,reject)=>{ if(!general){ reject("general cannot be empty:101") return; }else if(list && list.length>0){ for(let i in list){ if(i != 0){ let font_size = list[i-1].font_size?parseFloat(list[i-1].font_size):20 this.ctx.setFontSize(font_size) general.x = parseFloat(general.x) + this.ctx.measureText(list[i-1].text).width } list[i].x = general.x list[i].y = general.y + (list[i].margin_top?parseFloat(list[i].margin_top):0) await this.drawText(list[i]) } resolve() }else{ reject("The length of config arr is less than 0") return; } }) }, /** * array delete empty * @param {Object} arr */ arrDeleteEmpty(arr){ let newArr = [] for(let i in arr){ if(arr[i]){ newArr.push(arr[i]) } } return newArr }, /** * Draw Text,support line * @param {Object} config * @param {String} config.text 文字 * @param {Number} config.max_width 文字最大寬度(大於寬度自動換行) * @param {Number} config.line_height 文字上下行間距 * @param {Number} config.x x坐標 * @param {Number} config.y y坐標 * @param {String} config.font_color 文字顏色 * @param {String} config.font_family 文字字體 默認值:Arial * @param {String} config.text_align 文字對齊方式(left/center/right) * @param {Number} config.font_size 文字大小(px) * @param {Boolean} config.line_through_height 中划線大小 * @param {Boolean} config.line_through_color 中划線顏色 * @param {String} config.font_style 規定文字樣式 * @param {String} config.font_variant 規定字體變體 * @param {String} config.font_weight 規定字體粗細 * @param {String} config.line_through_cap 線末端類型 * @param {String} config.line_clamp 最大行數 * @param {String} config.line_clamp_hint 超過line_clamp后,尾部顯示的自定義標識 如 ... * @param {String} config.is_line_break 是否開啟換行符換行 * */ drawText(config,configuration = {}){ configuration['line_num'] = configuration.line_num?configuration.line_num:0 configuration['text_width'] = configuration.text_width?configuration.text_width:0 return new Promise(async (resolve,reject)=>{ if(config.text){ let draw_width = 0,draw_height = 0,draw_x = config.x,draw_y = config.y let font_size = config.font_size?(parseFloat(config.font_size)*this.scale):(20*this.scale) let font_color = config.font_color || "#000" let font_family = config.font_family || "Arial" let line_height = config.line_height || config.font_size || 20 let text_align = config.text_align || "left" let font_weight = config.font_weight || "normal" let font_variant = config.font_variant || "normal" let font_style = config.font_style || "normal" let line_clamp_hint = config.line_clamp_hint || '...' let lineBreakJoinText = "" let max_width = config.max_width?parseFloat(config.max_width)*this.scale:0 // checkout is line break if(config.is_line_break){ let splitTextArr = config.text.split(/[\n]/g) if(splitTextArr && splitTextArr.length > 0){ let newSplitTextArr = this.arrDeleteEmpty(splitTextArr) if(newSplitTextArr && newSplitTextArr.length > 0){ lineBreakJoinText = newSplitTextArr.slice(1).join("\n") config.text = newSplitTextArr[0] }else{ reject("Text cannot be empty:103") return } }else{ reject("Text cannot be empty:102") return } } this.ctx.setFillStyle(font_color) // color this.ctx.textAlign = text_align; this.ctx.font = `${font_style} ${font_variant} ${font_weight} ${parseInt(font_size)}px ${font_family}` if(configuration.text_width >= this.ctx.measureText(config.text).width){ draw_width = configuration.text_width }else if(max_width > 0){ draw_width = max_width < this.ctx.measureText(config.text).width ? this.resetCompatibilitySize(max_width) : this.resetCompatibilitySize(this.ctx.measureText(config.text).width) }else{ draw_width = this.resetCompatibilitySize(this.ctx.measureText(config.text).width) } configuration.text_width = draw_width if( max_width && this.compatibilitySize(this.ctx.measureText(config.text).width) > this.compatibilitySize(max_width)){ let current_text = "" let text_arr = config.text.split("") for(let i in text_arr){ if( this.compatibilitySize(this.ctx.measureText(current_text+text_arr[i]).width) > this.compatibilitySize(max_width) ){ // Hyphenation that is greater than the drawable width continues to draw if(config.line_clamp && parseInt(config.line_clamp) == 1){ // Subtracting the current_text tail width from the line_clamp_hint width let current_text_arr = current_text.split('') let json_current_text = '' while(true){ current_text_arr = current_text_arr.slice(1) json_current_text = current_text_arr.join('') if(this.compatibilitySize(this.ctx.measureText(json_current_text).width) <= this.compatibilitySize(this.ctx.measureText(line_clamp_hint).width)){ current_text = current_text.replace(json_current_text,'') break; } } configuration.line_num += 1 this.ctx.setFontSize(parseInt(this.compatibilitySize(font_size))) // font size this.ctx.fillText(current_text + line_clamp_hint, this.compatibilitySize(parseFloat(config.x)*this.scale), this.compatibilitySize(parseFloat(config.y)*this.scale)); }else{ configuration.line_num += 1 this.ctx.setFontSize(parseInt(this.compatibilitySize(font_size))) // font size this.ctx.fillText(current_text, this.compatibilitySize(parseFloat(config.x)*this.scale), this.compatibilitySize(parseFloat(config.y)*this.scale)); config.text = text_arr.slice(i).join("") config.y = config.y + line_height if(config.line_clamp){ config.line_clamp = parseInt(config.line_clamp) - 1 } await this.drawText(config,configuration) } break; }else{ current_text = current_text+text_arr[i] } } }else{ if(config.line_through_height){ let x = parseFloat(config.x)*this.scale let w let y = parseFloat(config.y)*this.scale - (font_size / 2.6) if(text_align == "left"){ w = this.ctx.measureText(config.text).width/1.1 + parseFloat(config.x)*this.scale }else if(text_align == "right"){ w = parseFloat(config.x)*this.scale - this.ctx.measureText(config.text).width/1.1 }else if(text_align == "center"){ x = parseFloat(config.x)*this.scale - this.ctx.measureText(config.text).width / 1.1 / 2 w = parseFloat(config.x)*this.scale + this.ctx.measureText(config.text).width / 1.1 / 2 } this.drawLineTo({ x:x, y:y, w:w, h:y, line_width:config.line_through_height, line_color:config.line_through_color, line_cap:config.line_through_cap }) } configuration.line_num += 1 this.ctx.setFontSize(parseInt(this.compatibilitySize(font_size))) // font size this.ctx.fillText(config.text, this.compatibilitySize(parseFloat(config.x)*this.scale), this.compatibilitySize(parseFloat(config.y)*this.scale)); if(config.line_clamp){ config.line_clamp = parseInt(config.line_clamp) - 1 } } if(lineBreakJoinText){ await this.drawText({...config,text:lineBreakJoinText,y:config.y + line_height},configuration) } draw_height = config.font_size * configuration.line_num draw_width = configuration.text_width resolve({draw_width,draw_height,draw_x,draw_y}) }else{ reject("Text cannot be empty:101") } }) }, /** * Draw Line * @param {Object} config * @param {Object} config.x x坐標 * @param {Object} config.y y坐標 * @param {Object} config.w 線的寬度 * @param {Object} config.h 線的高度 * @param {Object} config.line_width 線的寬度 * @param {Object} config.line_color 線條顏色 */ drawLineTo(config){ let x = this.compatibilitySize(config.x) let y = this.compatibilitySize(config.y) let w = this.compatibilitySize(config.w) let h = this.compatibilitySize(config.h) let line_width = config.line_width?parseFloat(config.line_width)*this.scale:1*this.scale let line_color = config.line_color || "black" let line_cap = config.line_cap || "butt" this.ctx.beginPath() this.ctx.lineCap = line_cap this.ctx.lineWidth = line_width this.ctx.strokeStyle = line_color this.ctx.moveTo(x,y) this.ctx.lineTo(w,h) this.ctx.stroke() }, /** * Compatibility px * @param {Object} size */ compatibilitySize(size) { let canvasSize = (parseFloat(size) / 750) * this.system_info.windowWidth canvasSize = parseFloat(canvasSize * 2) return canvasSize }, /** * Restore compatibility px * @param {Object} size */ resetCompatibilitySize(size) { let canvasSize = (parseFloat(size/2)/this.system_info.windowWidth) * 750 return canvasSize }, /** * Init canvas */ init(config){ return new Promise(async (resolve,reject)=>{ if(!config.canvas_id){ reject("Canvas ID cannot be empty, please refer to the usage example") return; } this.hidden = config.hidden this.canvas_id = config.canvas_id let system_info = await uni.getSystemInfoSync() this.system_info = system_info this.scale = config.scale&&parseFloat(config.scale)>0?parseInt(config.scale):1 this.canvas_width = (config.canvas_width ? this.compatibilitySize(config.canvas_width) : system_info.windowWidth) * this.scale this.canvas_height = (config.canvas_height ? this.compatibilitySize(config.canvas_height) : system_info.windowHeight) * this.scale, this.r_canvas_scale = 1/this.scale this.ctx = uni.createCanvasContext(this.canvas_id,this) this.setCanvasConfig({ global_alpha:config.global_alpha?parseFloat(config.global_alpha):1, backgroundColor:config.background_color?config.background_color:"#fff" }) resolve() }) }, /** * clear canvas all path */ clearCanvas(){ return new Promise(async (resolve,reject)=>{ if(!this.ctx){ reject("canvas is not initialized:101") return }else{ this.ctx.clearRect(0,0,parseFloat(this.canvas_width)*this.scale,parseFloat(this.canvas_height)*this.scale) await this.draw() resolve() } }) }, /** * Set canvas config * @param {Object} config */ setCanvasConfig(config){ this.ctx.globalAlpha = config.global_alpha this.ctx.fillStyle = config.backgroundColor this.ctx.fillRect(0, 0, parseFloat(this.canvas_width)*this.scale, parseFloat(this.canvas_height)*this.scale) }, /** * Draw to filepath */ draw(callback){ return new Promise((resolve,reject)=>{ let stop = setTimeout(()=>{ this.ctx.draw(false,setTimeout(()=>{ uni.canvasToTempFilePath({ canvasId: this.canvas_id, quality: 1, success: (res)=>{ // console.log('res',res) resolve(res) callback && callback(res) }, fail:(err)=>{ reject(JSON.stringify(err)|| "Failed to generate poster:101") } },this) },300)) clearTimeout(stop) },300) }) }, /** * draw rect * @param {Number} config.x x坐標 * @param {Number} config.y y坐標 * @param {Number} config.w 圖形寬度(px) * @param {Number} config.h 圖形高度(px) * @param {Number} config.color 圖形顏色 * @param {Number} config.is_radius 是否開啟圓圖(1.1.6及以下版本廢棄,請使用border_radius) * */ drawRect(config){ return new Promise(async (resolve,reject)=>{ let color = config.color || 'white' config['color'] = color this.ctx.fillStyle = color; if(config.is_radius || config.border_radius){ this.setNativeBorderRadius(config) this.ctx.fill() this.ctx.restore() }else{ this.ctx.fillRect(this.compatibilitySize(parseFloat(config.x)*this.scale),this.compatibilitySize(parseFloat(config.y)*this.scale),this.compatibilitySize(parseFloat(config.w)*this.scale),this.compatibilitySize(parseFloat(config.h)*this.scale)) } resolve() }) }, /** * Draw image * @param {Object} config * @param {String} config.url 圖片鏈接 * @param {Number} config.x x坐標 * @param {Number} config.y y坐標 * @param {Number} config.w 圖片寬度(px) * @param {Number} config.h 圖片高度(px) * @param {Number} config.border_width 邊大小 * @param {Number} config.border_color 邊顏色 * @param {Number} config.is_radius 是否開啟圓圖(1.1.6及以下版本廢棄,請使用border_radius) * @param {Number} config.border_radius 圓角弧度 */ drawImage(config){ return new Promise(async (resolve,reject)=>{ if(config.url){ let type = 0 // 1、network image 2、native image 3、base64 image let image_url let reg = /^https?/ig; if(reg.test(config.url)){ type = 1 }else{ if((config.url.indexOf("data:image/png;base64") != -1) || config.url.indexOf("data:image/jpeg;base64") != -1 || config.url.indexOf("data:image/gif;base64") != -1){ type = 3 }else{ type = 2 } } if(type == 1){ // network image await this.downLoadNetworkFile(config.url).then(res=>{ // two function image_url = res }).catch(err=>{ reject(err) return; }) }else if(type == 2){ // native image const imageInfoResult = await uni.getImageInfo({ src: config.url }); try{ if(imageInfoResult.length <= 1){ reject(imageInfoResult[0].errMsg + ':404') return } }catch(e){ reject(e+':500') return } let base64 = await this.urlToBase64({url:imageInfoResult[1].path}) // #ifdef MP-WEIXIN await this.base64ToNative({url:base64}).then(res=>{ image_url = res }).catch(err=>{ reject(JSON.stringify(err)+":501") return; }) // #endif // #ifndef MP-WEIXIN image_url = base64 // #endif }else if(type == 3){ // #ifdef MP-WEIXIN await this.base64ToNative({url:config.url}).then(res=>{ image_url = res }).catch(err=>{ reject(JSON.stringify(err)+":500") return; }) // #endif // #ifndef MP-WEIXIN image_url = config.url // #endif }else{ reject("Other Type Errors:101") return } if(config.border_width){ let border_radius = 0 if(config.border_radius){ let multiple = config.w / config.border_radius border_radius = (parseFloat(config.w) + parseFloat(config.border_width)) / multiple } // drawRect await this.drawRect({ x:parseFloat(config.x) - parseFloat(config.border_width)/2, y:parseFloat(config.y) - parseFloat(config.border_width)/2, w:parseFloat(config.w) + parseFloat(config.border_width), h:parseFloat(config.h) + parseFloat(config.border_width), color:config.border_color, border_radius:border_radius, border_width:config.border_width, is_radius:config.is_radius }) } if(config.border_radius){ config.color = config.color?config.color:'rgba(0,0,0,0)' // 圓角有白邊,+0.5的誤差 config.w = config.w + 0.3 config.h = config.h + 0.3 this.setNativeBorderRadius(config) }else if(config.is_radius){ //已廢棄 is_radius this.ctx.setStrokeStyle("rgba(0,0,0,0)") this.ctx.save() this.ctx.beginPath() this.ctx.arc(this.compatibilitySize(parseFloat(config.x)*this.scale+parseFloat(config.w)*this.scale/2), this.compatibilitySize(parseFloat(config.y)*this.scale+parseFloat(config.h)*this.scale/2), this.compatibilitySize(parseFloat(config.w)*this.scale/2), 0, 2 * Math.PI, false) this.ctx.stroke(); this.ctx.clip() } await this.ctx.drawImage(image_url,this.compatibilitySize(parseFloat(config.x)*this.scale),this.compatibilitySize(parseFloat(config.y)*this.scale),this.compatibilitySize(parseFloat(config.w)*this.scale),this.compatibilitySize(parseFloat(config.h)*this.scale)) this.ctx.restore() //Restore previously saved drawing context resolve() }else{ let err_msg = "Links cannot be empty:101" reject(err_msg) } }) }, /** * base64 to native available path * @param {Object} config */ base64ToNative(config){ return new Promise((resolve,reject)=>{ let fileName = new Date().getTime() var filePath = `${wx.env.USER_DATA_PATH}/${fileName}_rCanvas.png` wx.getFileSystemManager().writeFile({ filePath: filePath, data: config.url.replace(/^data:\S+\/\S+;base64,/, ''), encoding: 'base64', success: function() { resolve(filePath) }, fail: function(error) { reject(error) } }) }) }, /** * native url to base64 * @param {Object} config */ urlToBase64(config){ return new Promise(async (resolve,reject)=>{ if (typeof window != 'undefined') { await this.downLoadNetworkFile(config.url).then(res=>{ // two function resolve(res) }).catch(err=>{ reject(err) }) }else if (typeof plus != 'undefined') { plus.io.resolveLocalFileSystemURL(config.url,(obj)=>{ obj.file((file)=>{ let fileReader = new plus.io.FileReader() fileReader.onload = (res)=>{ resolve(res.target.result) } fileReader.onerror = (err)=>{ reject(err) } fileReader.readAsDataURL(file) }, (err)=>{ reject(err) }) },(err)=>{ reject(err) }) }else if(typeof wx != 'undefined'){ wx.getFileSystemManager().readFile({ filePath: config.url, encoding: 'base64', success: function(res) { resolve('data:image/png;base64,' + res.data) }, fail: function(error) { reject(error) } }) } }) }, setNativeBorderRadius(config){ let border_radius = config.border_radius?(parseFloat(config.border_radius)*this.scale):(20*this.scale) if ((parseFloat(config.w)*this.scale) < 2 * border_radius) border_radius = (parseFloat(config.w)*this.scale) / 2; if ((parseFloat(config.h)*this.scale) < 2 * border_radius) border_radius = (parseFloat(config.h)*this.scale) / 2; this.ctx.beginPath(); this.ctx.moveTo(this.compatibilitySize((parseFloat(config.x)*this.scale) + border_radius), this.compatibilitySize((parseFloat(config.y)*this.scale))); this.ctx.arcTo(this.compatibilitySize((parseFloat(config.x)*this.scale) + (parseFloat(config.w)*this.scale)), this.compatibilitySize((parseFloat(config.y)*this.scale)), this.compatibilitySize((parseFloat(config.x)*this.scale) + (parseFloat(config.w)*this.scale)), this.compatibilitySize((parseFloat(config.y)*this.scale) + (parseFloat(config.h)*this.scale)), this.compatibilitySize(border_radius)); this.ctx.arcTo(this.compatibilitySize((parseFloat(config.x)*this.scale) + (parseFloat(config.w)*this.scale)), this.compatibilitySize((parseFloat(config.y)*this.scale) + (parseFloat(config.h)*this.scale)), this.compatibilitySize((parseFloat(config.x)*this.scale)), this.compatibilitySize((parseFloat(config.y)*this.scale) + (parseFloat(config.h)*this.scale)), this.compatibilitySize(border_radius)); this.ctx.arcTo((this.compatibilitySize(parseFloat(config.x)*this.scale)), this.compatibilitySize((parseFloat(config.y)*this.scale) + (parseFloat(config.h)*this.scale)), this.compatibilitySize((parseFloat(config.x)*this.scale)), this.compatibilitySize((parseFloat(config.y)*this.scale)), this.compatibilitySize(border_radius)); this.ctx.arcTo(this.compatibilitySize((parseFloat(config.x)*this.scale)), this.compatibilitySize((parseFloat(config.y)*this.scale)), this.compatibilitySize((parseFloat(config.x)*this.scale) + (parseFloat(config.w)*this.scale)), this.compatibilitySize((parseFloat(config.y)*this.scale)), this.compatibilitySize(border_radius)); this.ctx.closePath(); this.ctx.strokeStyle = config.color || config.border_color || 'rgba(0,0,0,0)'; // 設置繪制邊框的顏色 this.ctx.stroke(); this.ctx.save() this.ctx.clip(); }, /** * Download network file * @param {Object} url : download url */ downLoadNetworkFile(url){ return new Promise((resolve,reject)=>{ uni.downloadFile({ url, success:(res)=>{ if(res.statusCode == 200){ resolve(res.tempFilePath) }else{ reject("Download Image Fail:102") } }, fail:(err)=>{ reject("Download Image Fail:101") } }) }) }, /** * Save image to natice * @param {Object} filePath : native imageUrl */ saveImage(filePath){ return new Promise((resolve,reject)=>{ if(!filePath){ reject("FilePath cannot be null:101") return; } // #ifdef H5 var createA = document.createElement("a"); createA.download = filePath; createA.href = filePath; document.body.appendChild(createA); createA.click(); createA.remove(); resolve() // #endif // #ifndef H5 uni.saveImageToPhotosAlbum({ filePath: filePath, success:(res)=>{ resolve(res) }, fail:(err)=>{ reject(err) } }) // #endif }) } } }
截至到這里,相機拍照+加水印功能已經搞定,接下來是權限,權限插件我沒有去改動,但是封裝了一個彈窗,話不多說,上代碼
import permision from "@/js_sdk/wa-permission/permission.js" export const showmod = (con, callBack) => { uni.showModal({ title: "溫馨提示", content: con, showCancel: true, confirmText: '去授權', cancelText: '算了', success: res => { if (res.confirm) { permision.gotoAppPermissionSetting() } else callBack && callBack() } }) }
這里需要注意的是,ios只顯示你使用過的權限,什么意思呢?就是說,如果你在首頁申請全部權限,但是你之前第一次沒有使用該權限的代碼(比如打開相機,打開相冊的代碼),那么就算用戶點擊授權,APP的授權列表里面也不會出現該權限的開關。
所以只能在需要用到某權限的地方再來判斷,而且為了提升用戶體驗,建議是被拒絕權限后再去申請
由於該插件本身不支持查詢用戶是否為第一次授權,所以我做了緩存處理
async getper(that) { //獲取緩存中是否是第一次請求權限 const iscamera = uni.getStorageSync('iscamera'); if (iscamera) { if (uni.getSystemInfoSync().platform == 'ios') { if (!permision.judgeIosPermission("camera")) return showmod("該功能需要使用相機,請給APP授權", function() { uni.navigateBack({ delta: 1 }); }); } else { if (await permision.requestAndroidPermission( 'android.permission.CAMERA') != 1) return showmod("該功能需要使用相機,請給APP授權", function() { uni.navigateBack({ delta: 1 }); }); } //你的邏輯代碼 } else { uni.setStorageSync('iscamera', true) //你的邏輯代碼 } },
水印效果如下