@@ -0,0 +1,75 @@ | |||
<template> | |||
<view> | |||
<view class="html2canvas" :prop="domId" :change:prop="html2canvas.create"> | |||
<slot></slot> | |||
</view> | |||
</view> | |||
</template> | |||
<script> | |||
import { base64ToPath } from '@/static/libs/image-tools.js'; | |||
export default { | |||
name: 'html2canvas', | |||
props: { | |||
domId: { | |||
type: String, | |||
required: true | |||
} | |||
}, | |||
methods: { | |||
async renderFinish(base64) { | |||
try{ | |||
const imgPath = await base64ToPath(base64, '.jpeg'); | |||
this.$emit('renderFinish', imgPath); | |||
}catch(e){ | |||
//TODO handle the exception | |||
console.log('html2canvas error', e) | |||
} | |||
}, | |||
showLoading() { | |||
uni.showToast({ | |||
title: "正在生成海报", | |||
icon: "none", | |||
mask: true, | |||
duration: 100000 | |||
}) | |||
}, | |||
hideLoading() { | |||
uni.hideToast(); | |||
} | |||
} | |||
} | |||
</script> | |||
<script module="html2canvas" lang="renderjs"> | |||
import html2canvas from 'html2canvas'; | |||
export default { | |||
methods: { | |||
async create(domId) { | |||
try { | |||
this.$ownerInstance.callMethod('showLoading', true); | |||
const timeout = setTimeout(async ()=> { | |||
const shareContent = document.querySelector(domId); | |||
const canvas = await html2canvas(shareContent,{ | |||
width: shareContent.offsetWidth,//设置canvas尺寸与所截图尺寸相同,防止白边 | |||
height: shareContent.offsetHeight,//防止白边 | |||
logging: true, | |||
useCORS: true | |||
}); | |||
const base64 = canvas.toDataURL('image/jpeg', 1); | |||
this.$ownerInstance.callMethod('renderFinish', base64); | |||
this.$ownerInstance.callMethod('hideLoading', true); | |||
clearTimeout(timeout); | |||
}, 500); | |||
} catch(error){ | |||
console.log(error) | |||
} | |||
} | |||
} | |||
} | |||
</script> | |||
<style lang="scss"> | |||
</style> |
@@ -0,0 +1,735 @@ | |||
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.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() | |||
}) | |||
}, | |||
/** | |||
* 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.ctx.measureText(config.text).width | |||
} | |||
configuration.text_width = draw_width / this.scale | |||
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) | |||
}, | |||
/** | |||
* set canvas width | |||
* @param {Object} width | |||
*/ | |||
setCanvasWidth(width){ | |||
if(!width){ | |||
uni.showToast({ | |||
title:'setCanvasWidth:width error', | |||
icon:'none' | |||
}) | |||
} | |||
this.canvas_width = this.compatibilitySize(parseFloat(width)) * this.scale | |||
this.ctx.width = this.canvas_width | |||
}, | |||
/** | |||
* set canvas height | |||
* @param {Object} height | |||
*/ | |||
setCanvasHeight(height){ | |||
if(!height){ | |||
uni.showToast({ | |||
title:'setCanvasWidth:height error', | |||
icon:'none' | |||
}) | |||
} | |||
this.canvas_height = this.compatibilitySize(parseFloat(height)) * this.scale | |||
this.ctx.height = this.canvas_height | |||
}, | |||
/** | |||
* 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) | |||
* @param {Number} config.border_width 边框大小 | |||
* @param {Number} config.border_color 边框颜色 | |||
* | |||
*/ | |||
drawRect(config){ | |||
return new Promise(async (resolve,reject)=>{ | |||
if(!config.border_width || config.border_width <=0){ | |||
config.border_width = 0 | |||
}else{ | |||
config.border_width = parseFloat(config.border_width) | |||
} | |||
if(parseFloat(config.border_width) > 0){ | |||
let sub_config = JSON.parse(JSON.stringify(config)) | |||
sub_config.border_width = 0 | |||
sub_config.w = config.w + config.border_width | |||
sub_config.h = config.h + config.border_width | |||
sub_config.color = config.border_color || 'black' | |||
if(sub_config.border_radius){ | |||
sub_config.border_radius = parseFloat(sub_config.border_radius) + parseFloat(config.border_width) / 2 | |||
} | |||
await this.drawRect(sub_config) | |||
} | |||
let color = config.color || 'white' | |||
config.x = (parseFloat(config.x) + config.border_width / 2) | |||
config.y = (parseFloat(config.y) + config.border_width / 2) | |||
config['color'] = color | |||
this.ctx.fillStyle = color; | |||
if(config.is_radius || config.border_radius){ | |||
this.setNativeBorderRadius(config) | |||
this.ctx.fill() | |||
}else{ | |||
console.log('config.border_width',config.border_width) | |||
this.ctx.fillRect(this.compatibilitySize(config.x*this.scale),this.compatibilitySize(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 | |||
}) | |||
} | |||
} | |||
} |
@@ -0,0 +1,26 @@ | |||
<template> | |||
<view> | |||
<view class="r-canvas-component" :style="{width:canvas_width/scale+'px',height:canvas_height/scale+'px'}" :class="{'hidden':hidden}"> | |||
<canvas class="r-canvas" v-if="canvas_id" :canvas-id="canvas_id" :id="canvas_id" :style="{width:canvas_width+'px',height:canvas_height+'px','transform': `scale(${r_canvas_scale})`}"></canvas> | |||
</view> | |||
</view> | |||
</template> | |||
<script> | |||
import rCanvasJS from "./r-canvas.js" | |||
export default { | |||
mixins:[rCanvasJS] | |||
} | |||
</script> | |||
<style> | |||
.r-canvas{ | |||
transform-origin: 0 0; | |||
} | |||
.r-canvas-component{ | |||
overflow: hidden; | |||
} | |||
.r-canvas-component.hidden{ | |||
position: fixed; | |||
top:-5000upx; | |||
} | |||
</style> |
@@ -12,6 +12,7 @@ let zaudio = new ZAudio({ | |||
Vue.prototype.$zaudio = zaudio | |||
Vue.prototype.$dayjs = dayjs; | |||
Vue.prototype.$store = store; | |||
import common from 'utils/common.js' | |||
Vue.prototype.$noMultipleClicks = common.noMultipleClicks; | |||
@@ -1,3 +1,49 @@ | |||
{ | |||
"lockfileVersion": 1 | |||
"requires": true, | |||
"lockfileVersion": 1, | |||
"dependencies": { | |||
"base64-arraybuffer": { | |||
"version": "1.0.2", | |||
"resolved": "https://registry.npmmirror.com/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz", | |||
"integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==" | |||
}, | |||
"css-line-break": { | |||
"version": "2.1.0", | |||
"resolved": "https://registry.npmmirror.com/css-line-break/-/css-line-break-2.1.0.tgz", | |||
"integrity": "sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==", | |||
"requires": { | |||
"utrie": "^1.0.2" | |||
} | |||
}, | |||
"html2canvas": { | |||
"version": "1.4.1", | |||
"resolved": "https://registry.npmmirror.com/html2canvas/-/html2canvas-1.4.1.tgz", | |||
"integrity": "sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==", | |||
"requires": { | |||
"css-line-break": "^2.1.0", | |||
"text-segmentation": "^1.0.3" | |||
} | |||
}, | |||
"text-segmentation": { | |||
"version": "1.0.3", | |||
"resolved": "https://registry.npmmirror.com/text-segmentation/-/text-segmentation-1.0.3.tgz", | |||
"integrity": "sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==", | |||
"requires": { | |||
"utrie": "^1.0.2" | |||
} | |||
}, | |||
"utrie": { | |||
"version": "1.0.2", | |||
"resolved": "https://registry.npmmirror.com/utrie/-/utrie-1.0.2.tgz", | |||
"integrity": "sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==", | |||
"requires": { | |||
"base64-arraybuffer": "^1.0.2" | |||
} | |||
}, | |||
"wxml2canvas": { | |||
"version": "1.0.1", | |||
"resolved": "https://registry.npmmirror.com/wxml2canvas/-/wxml2canvas-1.0.1.tgz", | |||
"integrity": "sha512-AdWvxgTjJtW/m6Cki1cwGO0HOERKU8O9V3RcCz8UyqJbrPF7e8Nv27/epYiIs64HlbPTKWTLl7ECjQi6UVducA==" | |||
} | |||
} | |||
} |
@@ -4,9 +4,9 @@ | |||
<view class="m-nav"> | |||
<view class="m-n-lside"> | |||
<image src="@/static/images/notice.png" mode=""></image> | |||
全部102条消息 | |||
全部{{ count }}条消息 | |||
</view> | |||
<view class="m-n-rside"> | |||
<view class="m-n-rside" @click="allRead"> | |||
全部已读 | |||
</view> | |||
</view> | |||
@@ -48,7 +48,7 @@ | |||
:scroll-y="true" class="scroll-Y" @scrolltolower="lower"> | |||
<template v-if="updateAnnList.length!=0"> | |||
<block v-for="(item,index) in updateAnnList" :key="index"> | |||
<view class="upgradeItem" @click="goDetail(item.content,item.id,item.title)"> | |||
<view class="upgradeItem" @click="goDetail(item)"> | |||
<view class="right"> | |||
<view class="title"> | |||
{{item.title}} | |||
@@ -85,10 +85,13 @@ | |||
updateAnnList: [], | |||
sysItemList: [], | |||
list: [{ | |||
name: '接待报告' | |||
name: '接待报告', | |||
id: 1, // 1日报 2周报 3其他(非3为接待报告,3是系统消息) | |||
}, { | |||
name: '升级公告', | |||
id: 3, // 1日报 2周报 3其他(非3为接待报告,3是系统消息) | |||
}], | |||
count: 0, // 全部条数 | |||
current: 0, | |||
pageNum: 1, | |||
pageSize: 10, | |||
@@ -108,7 +111,7 @@ | |||
}, | |||
onPullDownRefresh() { | |||
this.updateInit() | |||
this.initPage() | |||
setTimeout(function() { | |||
uni.stopPullDownRefresh(); | |||
}, 1000); | |||
@@ -139,11 +142,32 @@ | |||
this.updateInit() | |||
}, | |||
initPage() { | |||
this.updateAnnList = [] | |||
this.pageNum = 1 | |||
this.updateInit() | |||
}, | |||
// 全部标记已读 | |||
allRead() { | |||
this.$u.get('/zkMessage/updateState', { accountId: uni.getStorageSync('weapp_session_userInfo_data').accountId, projectId: uni.getStorageSync('buildingID').id, }).then(res => { | |||
uni.showToast({ | |||
title: '全部已读成功!', | |||
duration: 2000 | |||
}); | |||
this.initPage() | |||
}).catch(e => { | |||
console.log(e) | |||
}) | |||
}, | |||
updateInit() { | |||
uni.request({ | |||
url: config.service.updateList, | |||
method: "GET", | |||
data: { | |||
messageType: this.list[this.current].id, | |||
projectId: uni.getStorageSync('buildingID').id, | |||
id: uni.getStorageSync('weapp_session_userInfo_data').accountId, | |||
num: this.pageNum, | |||
size: this.pageSize | |||
@@ -161,6 +185,7 @@ | |||
duration: 2000 | |||
}); | |||
} else { | |||
this.count = res.data.count | |||
if (this.pageNum != 1) { | |||
this.updateAnnList = [...this.updateAnnList, ...res.data.data.list.results]; | |||
} else { | |||
@@ -171,30 +196,29 @@ | |||
} | |||
}) | |||
}, | |||
goDetail(text, id, title) { | |||
let link = encodeURIComponent(JSON.stringify(text)) | |||
goDetail(data) { | |||
this.$store.commit('setMessageObj', data) | |||
uni.navigateTo({ | |||
url: "./messageDetail?content=" + link + "&id=" + id + "&tit=" + title | |||
url: "./messageDetail" | |||
}) | |||
}, | |||
// 跳转日报 | |||
goReception(item, index) { | |||
if (index == 2) { | |||
goReception(data, index) { | |||
this.$store.commit('setMessageObj', data) | |||
if (data.title == '周报') { | |||
uni.navigateTo({ | |||
url: `/pages/mine/reportExcel/weekReport` | |||
url: `/pages/mine/reportExcel/weekReport?id=${data.id}` | |||
}) | |||
} else { | |||
uni.navigateTo({ | |||
url: `/pages/mine/reportExcel/dayReport` | |||
url: `/pages/mine/reportExcel/dayReport?id=${data.id}` | |||
}) | |||
} | |||
}, | |||
change(index) { | |||
this.current = index; | |||
if (this.current == 1) { | |||
this.updateInit() | |||
} | |||
this.initPage() | |||
} | |||
} | |||
} | |||
@@ -2,73 +2,427 @@ | |||
<view class="pages"> | |||
<!-- 导航栏 --> | |||
<view class="nav-header"> | |||
<u-navbar title="数智工牌日报" titles="05.02"></u-navbar> | |||
<u-navbar title="数智工牌日报" :titles="$options.filters.fomatDate(weekObj.createTime)"></u-navbar> | |||
</view> | |||
<!-- 日报内容部分 --> | |||
<view class="container"> | |||
<view class="container" ref="lists"> | |||
<!-- 头部日报卡 --> | |||
<view class="c-head-card"> | |||
<view class="c-title-text"> | |||
桃源避暑山庄新型项目数智工牌日报 | |||
<text class="date">01月27日</text> | |||
{{ projectName }}数智工牌日报 | |||
<text class="date">{{ weekObj.createTime | fomatDate }}</text> | |||
</view> | |||
<view class="creative-time"> | |||
生成时间:2022-01-27 22:00 | |||
生成时间:{{ weekObj.createTime || '--' }} | |||
</view> | |||
</view> | |||
<!-- 循环渲染的数据 --> | |||
<view class="dateList"> | |||
<!-- --> | |||
<view class="arrs"> | |||
<block v-for="(data, index) in 11" :key="index"> | |||
<view class="arrs-items"> | |||
<view class="left"> | |||
{{ index+1 }} | |||
</view> | |||
<view class="right"> | |||
<view class="r-title"> | |||
<text>接待量:</text> | |||
<text class="num">879次</text> | |||
<view class="arrs-items index1"> | |||
<view class="left"> | |||
1 | |||
</view> | |||
<view class="right"> | |||
<view class="r-title"> | |||
<text>接待量:</text> | |||
</view> | |||
<view class="r-box"> | |||
<view class="r-box-item"> | |||
<text>接待量</text><text>{{ weekObj.receptionCount || 0 }}</text> | |||
</view> | |||
<view class="r-box"> | |||
<view class="r-box-item"> | |||
<text>接待量</text><text>100</text> | |||
</view> | |||
<view class="r-box-item"> | |||
<text>有效接待</text><text>100</text> | |||
</view> | |||
<view class="r-box-item-lang"> | |||
<view>有效接待率<text>80%</text></view> | |||
<view class="contrast">对比昨天<text class="down">-5%</text></view> | |||
</view> | |||
<view class="r-box-item"> | |||
<text>有效接待</text><text>{{ weekObj.activeCustomer || 0 }}</text> | |||
</view> | |||
<view class="r-box-item-lang"> | |||
<view>有效接待率<text>{{ weekObj.validReceptionRate || 0 }}%</text></view> | |||
<view class="contrast">对比昨天<text | |||
:class="{down: weekObj.validReceptionRatePK < 0, up: weekObj.validReceptionRatePK > 0}">{{ weekObj.validReceptionRatePK || 0 }}</text> | |||
</view> | |||
</view> | |||
</view> | |||
</view> | |||
</view> | |||
<view class="arrs-items index2"> | |||
<view class="left"> | |||
2 | |||
</view> | |||
<view class="right"> | |||
<view class="r-title"> | |||
<text>销讲执行率:</text> | |||
</view> | |||
<view class="r-box"> | |||
<view class="r-box-item-lang"> | |||
<view>销讲执行率<text>{{ weekObj.fraction || 0 }}%</text></view> | |||
<view class="contrast">对比昨天<text | |||
:class="{down: weekObj.fractionPK < 0, up: weekObj.fractionPK > 0}">{{ weekObj.fractionPK || 0 }}</text> | |||
</view> | |||
</view> | |||
</view> | |||
</view> | |||
</view> | |||
<view class="arrs-items index3"> | |||
<view class="left"> | |||
3 | |||
</view> | |||
<view class="right"> | |||
<view class="r-title"> | |||
<text>平均接待时长:</text> | |||
</view> | |||
<view class="r-box"> | |||
<view class="r-box-item-lang"> | |||
<view>平均接待时长<text>{{ weekObj.avgDuration || 0 }}m</text></view> | |||
<view class="contrast">对比昨天<text | |||
:class="{down: weekObj.avgDurationPK < 0, up: weekObj.avgDurationPK > 0}">{{ weekObj.avgDurationPK }}</text> | |||
</view> | |||
</view> | |||
<view class="ranking"> | |||
<block v-for="(rank, rankIndex) in 3" :key="rankIndex"> | |||
<view class="ranking-item"> | |||
<view class="serial"> | |||
{{ rankIndex+1 }} | |||
</view> | |||
<view class="lside"> | |||
产品设计 | |||
</view> | |||
<view class="rside"> | |||
(100%) | |||
</view> | |||
</view> | |||
</view> | |||
</view> | |||
<view class="arrs-items index4"> | |||
<view class="left"> | |||
4 | |||
</view> | |||
<view class="right"> | |||
<view class="r-title"> | |||
<text>销讲维度执行前三:</text> | |||
</view> | |||
<view class="ranking"> | |||
<block v-for="(rank, rankIndex) in carryOutTop" :key="rankIndex"> | |||
<view class="ranking-item"> | |||
<view class="serial"> | |||
{{ rankIndex+1 }} | |||
</view> | |||
</block> | |||
</view> | |||
<view class="lside"> | |||
{{ rank.title || '--' }} | |||
</view> | |||
<view class="rside"> | |||
({{ rank.value || 0 }}%) | |||
</view> | |||
</view> | |||
</block> | |||
<template v-if="carryOutTop.length == 0"> | |||
<view class="empty"> | |||
暂无数据 | |||
</view> | |||
</template> | |||
</view> | |||
</view> | |||
</block> | |||
</view> | |||
<view class="arrs-items index5"> | |||
<view class="left"> | |||
5 | |||
</view> | |||
<view class="right"> | |||
<view class="r-title"> | |||
<text>销讲维度执行弱项前三:</text> | |||
</view> | |||
<view class="ranking"> | |||
<block v-for="(rank, rankIndex) in carryOutLast" :key="rankIndex"> | |||
<view class="ranking-item"> | |||
<view class="serial"> | |||
{{ rankIndex+1 }} | |||
</view> | |||
<view class="lside"> | |||
{{ rank.title || '--' }} | |||
</view> | |||
<view class="rside"> | |||
({{ rank.value || 0 }}%) | |||
</view> | |||
</view> | |||
</block> | |||
<template v-if="carryOutLast.length == 0"> | |||
<view class="empty"> | |||
暂无数据 | |||
</view> | |||
</template> | |||
</view> | |||
</view> | |||
</view> | |||
<view class="arrs-items index6"> | |||
<view class="left"> | |||
6 | |||
</view> | |||
<view class="right"> | |||
<view class="r-title"> | |||
<text>置业顾问平均执行率排名:</text> | |||
</view> | |||
<view class="ranking"> | |||
<block v-for="(rank, rankIndex) in consultant" :key="rankIndex"> | |||
<view class="ranking-item"> | |||
<view class="serial"> | |||
{{ rankIndex+1 }} | |||
</view> | |||
<view class="lside"> | |||
{{ rank.title || '--' }} | |||
</view> | |||
<view class="rside"> | |||
({{ rank.value || 0 }}%) | |||
</view> | |||
</view> | |||
</block> | |||
<template v-if="consultant.length == 0"> | |||
<view class="empty"> | |||
暂无数据 | |||
</view> | |||
</template> | |||
</view> | |||
</view> | |||
</view> | |||
<view class="arrs-items index7"> | |||
<view class="left"> | |||
7 | |||
</view> | |||
<view class="right"> | |||
<view class="r-title"> | |||
<text>置业顾问平均接访录音排名:</text> | |||
</view> | |||
<view class="ranking"> | |||
<block v-for="(rank, rankIndex) in recording" :key="rankIndex"> | |||
<view class="ranking-item"> | |||
<view class="serial"> | |||
{{ rankIndex+1 }} | |||
</view> | |||
<view class="lside"> | |||
{{ rank.title || '--' }} | |||
</view> | |||
<view class="rside"> | |||
({{ rank.value || 0 }}%) | |||
</view> | |||
</view> | |||
</block> | |||
<template v-if="recording.length == 0"> | |||
<view class="empty"> | |||
暂无数据 | |||
</view> | |||
</template> | |||
</view> | |||
</view> | |||
</view> | |||
<view class="arrs-items index8"> | |||
<view class="left"> | |||
8 | |||
</view> | |||
<view class="right"> | |||
<view class="r-title"> | |||
<text>执行率最低的顾问:</text> | |||
</view> | |||
<view class="ranking"> | |||
<block v-for="(rank, rankIndex) in lowest" :key="rankIndex"> | |||
<view class="ranking-item"> | |||
<view class="lside"> | |||
{{ rank.title || '--' }} | |||
</view> | |||
<view class="rside"> | |||
{{ rank.value || 0 }}% | |||
</view> | |||
</view> | |||
</block> | |||
</view> | |||
</view> | |||
</view> | |||
<view class="arrs-items index9"> | |||
<view class="left"> | |||
9 | |||
</view> | |||
<view class="right"> | |||
<view class="r-title"> | |||
<text>客户画像触达:</text> | |||
<text class="num">{{ weekObj.reachSum || 0 }}次</text> | |||
</view> | |||
</view> | |||
</view> | |||
<view class="arrs-items index10"> | |||
<view class="left"> | |||
10 | |||
</view> | |||
<view class="right"> | |||
<view class="r-title"> | |||
<text>未标记接待数:</text> | |||
<text class="num up" | |||
style="font-weight: 500;font-size: 34rpx;">{{ weekObj.unlabelledReceptionNum || 0 }}次</text> | |||
</view> | |||
</view> | |||
</view> | |||
<view class="arrs-items index11"> | |||
<view class="left"> | |||
11 | |||
</view> | |||
<view class="right"> | |||
<view class="r-title"> | |||
<text>设备情况:</text> | |||
</view> | |||
<view class="r-box"> | |||
<view class="r-box-item-lang"> | |||
<view>在线<text>{{ weekObj.equipmentInfo.onlineNum || 0 }}</text></view> | |||
<view style="margin-left: 24rpx;"> | |||
离线<text>{{ weekObj.equipmentInfo.offlineNum || 0 }}</text></view> | |||
</view> | |||
</view> | |||
</view> | |||
</view> | |||
</view> | |||
</view> | |||
</view> | |||
<!-- 底部按钮 --> | |||
<view class="nav-footer"> | |||
<view class="footer-item" @click="copy"> | |||
复制 | |||
</view> | |||
<view class="footer-item full" style="margin-left: 22rpx;"> | |||
<button open-type="share" class="fulls"> | |||
分享给好友 | |||
</button> | |||
</view> | |||
</view> | |||
</view> | |||
</template> | |||
<script> | |||
export default { | |||
data() { | |||
return { | |||
needList: ['XJTop', 'ZXLTop', 'avgJds'], // 需要转换数组的内容 | |||
carryOutTop: [], // 销讲维度执行前三: | |||
carryOutLast: [], // 销讲维度执行倒三: | |||
consultant: [], // 置业顾问排名 | |||
recording: [], // 录音排名 | |||
lowest: [], // 执行率最低的顾问 | |||
building: uni.getStorageSync('buildingID'), | |||
id: '', // 消息id | |||
projectName: '', // 项目名称 | |||
weekObj: {}, // 日报详情 | |||
} | |||
}, | |||
onLoad(option) { | |||
if (option.id) this.id = option.id | |||
this.getMessage() | |||
}, | |||
onShareAppMessage() { | |||
return { | |||
title: `${this.projectName}数智工牌日报`, | |||
path: `/pages/mine/reportExcel/dayReport?id=${this.id}` | |||
} | |||
}, | |||
methods: { | |||
// 获取日报详情 | |||
getMessage() { | |||
this.$u.get('/api/zkMessage/findById', { | |||
id: this.id | |||
}).then(res => { | |||
console.log(res) | |||
let data = JSON.parse(res.zkMessage.content) | |||
this.weekObj = { | |||
...res.zkMessage, | |||
...data | |||
} | |||
console.log(this.weekObj, 'this.weekObj') | |||
this.projectName = res.projectName | |||
this.init() | |||
}).catch(e => { | |||
console.log(e) | |||
}) | |||
}, | |||
copy() { | |||
let str = `` | |||
uni.setClipboardData({ | |||
data: str | |||
}) | |||
}, | |||
// 分割数组排名前三,倒三 | |||
getTopThree() { | |||
if (this.weekObj.XJTopList && this.weekObj.XJTopList.length > 0) { | |||
this.carryOutTop = this.weekObj.XJTopList.reverse().slice(0, 3) | |||
this.carryOutLast = this.weekObj.XJTopList.slice(0, 3) | |||
} | |||
if (this.weekObj.ZXLTopList && this.weekObj.ZXLTopList.length > 0) { | |||
this.consultant = this.weekObj.ZXLTopList.reverse().slice(0, 3) | |||
this.lowest.push(this.weekObj.ZXLTopList[0]) | |||
} | |||
if (this.weekObj.avgJdsList && this.weekObj.avgJdsList.length > 0) { | |||
console.log(this.weekObj.avgJdsList.slice(0, 3)) | |||
this.recording = this.weekObj.avgJdsList.reverse().slice(0, 3) | |||
} | |||
}, | |||
init() { | |||
// 把对象转成数组并在后续的步骤方便处理 | |||
this.needList.forEach(item => { | |||
if (this.weekObj[item] && Object.keys(this.weekObj[item]).length > 0) { | |||
this.weekObj[item + 'List'] = [] // 销讲执行 | |||
for (let i in this.weekObj[item]) { | |||
this.weekObj[item + 'List'].push({ | |||
title: i, | |||
value: this.weekObj[item][i] | |||
}) | |||
} | |||
} | |||
}) | |||
this.sortInitArr() | |||
}, | |||
// 排序对象转换后的数组 | |||
sortInitArr() { | |||
this.needList.forEach(item => { | |||
if (this.weekObj[item + 'List']) { | |||
this.bubbleSort(this.weekObj[item + 'List']) | |||
} | |||
}) | |||
this.getTopThree() | |||
}, | |||
// 冒泡排序 | |||
bubbleSort(arr) { | |||
for (let i = 0; i < arr.length - 1; i += 1) { | |||
//通过 arr.length 次把第一位放到最后,完成排序 | |||
//-i是因为最后的位置是会动态改变的,当完成一次后,最后一位会变成倒数第二位 | |||
for (let j = 0; j < arr.length - 1 - i; j += 1) { | |||
if (arr[j].value > arr[j + 1].value) { | |||
const temp = arr[j]; | |||
arr[j] = arr[j + 1]; | |||
arr[j + 1] = temp; | |||
} | |||
} | |||
} | |||
}, | |||
}, | |||
filters: { | |||
fomatDate(date) { | |||
if (!date) return '--' | |||
let arr = date.split(' ') | |||
let str = arr[0] | |||
let result = str.split('-') | |||
return `${result[1]}-${result[2]}` | |||
} | |||
} | |||
} | |||
</script> | |||
<style lang="scss" scoped> | |||
@@ -77,17 +431,17 @@ | |||
min-height: 100vh; | |||
display: flex; | |||
flex-direction: column; | |||
.nav-header { | |||
flex-shrink: 0; | |||
} | |||
.container { | |||
padding: 30rpx 30rpx 0; | |||
flex-grow: 1; | |||
display: flex; | |||
flex-direction: column; | |||
.c-head-card { | |||
padding: 30rpx; | |||
width: 100%; | |||
@@ -97,13 +451,13 @@ | |||
box-shadow: 10rpx 10rpx #2671E2; | |||
display: flex; | |||
flex-direction: column; | |||
.c-title-text { | |||
position: relative; | |||
flex-grow: 1; | |||
font-size: 48rpx; | |||
color: #303030; | |||
.date { | |||
position: absolute; | |||
right: 0; | |||
@@ -112,26 +466,26 @@ | |||
color: #303030; | |||
} | |||
} | |||
.creative-time { | |||
margin: 20rpx 0 0 0; | |||
flex-shrink: 0; | |||
} | |||
} | |||
.dateList { | |||
width: 100%; | |||
.arrs { | |||
width: 100%; | |||
.arrs-items { | |||
margin: 40rpx 0 0 0; | |||
display: flex; | |||
.left { | |||
flex-shrink: 0; | |||
margin-right: 12rpx; | |||
@@ -139,70 +493,71 @@ | |||
height: 44rpx; | |||
display: flex; | |||
justify-content: center; | |||
align-items: center; | |||
border-radius: 8rpx; | |||
border: 1rpx solid #999999; | |||
font-size: 32rpx; | |||
} | |||
.right { | |||
flex-grow: 1; | |||
.r-title { | |||
font-size: 32rpx; | |||
height: 44rpx; | |||
display: flex; | |||
align-items: center; | |||
.num { | |||
font-size: 34rpx; | |||
} | |||
} | |||
.r-box { | |||
padding: 21rpx 0 0 0; | |||
display: flex; | |||
flex-wrap: wrap; | |||
.r-box-item { | |||
margin-right: 24rpx; | |||
} | |||
.r-box-item-lang { | |||
margin-top: 19rpx; | |||
width: 100%; | |||
display: flex; | |||
align-items: center; | |||
.contrast { | |||
margin: 0 0 0 24rpx; | |||
display: flex; | |||
align-items: center; | |||
} | |||
.down { | |||
color: #43CD80; | |||
font-size: 34rpx; | |||
} | |||
.up { | |||
font-size: 34rpx; | |||
color: #E7483C; | |||
} | |||
} | |||
} | |||
.ranking { | |||
padding: 21rpx 0 0 0; | |||
display: flex; | |||
flex-direction: column; | |||
.ranking-item { | |||
margin-bottom: 22rpx; | |||
display: flex; | |||
align-items: center; | |||
font-size: 30rpx; | |||
.serial { | |||
flex-shrink: 0; | |||
width: 42rpx; | |||
@@ -214,24 +569,67 @@ | |||
align-items: center; | |||
color: #fff; | |||
} | |||
.lside { | |||
margin: 0 20rpx; | |||
color: #505050; | |||
} | |||
.rside { | |||
flex-shrink: 0; | |||
font-size: 32rpx; | |||
} | |||
} | |||
} | |||
} | |||
} | |||
} | |||
} | |||
} | |||
.nav-footer { | |||
margin: 32rpx 0; | |||
width: 100%; | |||
display: flex; | |||
justify-content: center; | |||
.footer-item { | |||
width: 334rpx; | |||
height: 88rpx; | |||
display: flex; | |||
justify-content: center; | |||
align-items: center; | |||
color: #2671E2; | |||
border: 2rpx solid #2671E2; | |||
border-radius: 8rpx; | |||
overflow: hidden; | |||
font-size: 32rpx; | |||
&.full { | |||
background: #2671E2; | |||
color: #fff; | |||
.fulls { | |||
width: 100%; | |||
height: 100%; | |||
background: transparent; | |||
color: #fff; | |||
} | |||
} | |||
} | |||
} | |||
.empty { | |||
width: 100%; | |||
height: 240rpx; | |||
display: flex; | |||
justify-content: center; | |||
align-items: center; | |||
flex-direction: column; | |||
} | |||
} | |||
</style> | |||
</style> |
@@ -0,0 +1,152 @@ | |||
function getLocalFilePath(path) { | |||
if (path.indexOf('_www') === 0 || path.indexOf('_doc') === 0 || path.indexOf('_documents') === 0 || path.indexOf('_downloads') === 0) { | |||
return path | |||
} | |||
if (path.indexOf('file://') === 0) { | |||
return path | |||
} | |||
if (path.indexOf('/storage/emulated/0/') === 0) { | |||
return path | |||
} | |||
if (path.indexOf('/') === 0) { | |||
var localFilePath = plus.io.convertAbsoluteFileSystem(path) | |||
if (localFilePath !== path) { | |||
return localFilePath | |||
} else { | |||
path = path.substr(1) | |||
} | |||
} | |||
return '_www/' + path | |||
} | |||
export function pathToBase64(path) { | |||
return new Promise(function(resolve, reject) { | |||
if (typeof window === 'object' && 'document' in window) { | |||
if (typeof FileReader === 'function') { | |||
var xhr = new XMLHttpRequest() | |||
xhr.open('GET', path, true) | |||
xhr.responseType = 'blob' | |||
xhr.onload = function() { | |||
if (this.status === 200) { | |||
let fileReader = new FileReader() | |||
fileReader.onload = function(e) { | |||
resolve(e.target.result) | |||
} | |||
fileReader.onerror = reject | |||
fileReader.readAsDataURL(this.response) | |||
} | |||
} | |||
xhr.onerror = reject | |||
xhr.send() | |||
return | |||
} | |||
var canvas = document.createElement('canvas') | |||
var c2x = canvas.getContext('2d') | |||
var img = new Image | |||
img.onload = function() { | |||
canvas.width = img.width | |||
canvas.height = img.height | |||
c2x.drawImage(img, 0, 0) | |||
resolve(canvas.toDataURL()) | |||
canvas.height = canvas.width = 0 | |||
} | |||
img.onerror = reject | |||
img.src = path | |||
return | |||
} | |||
if (typeof plus === 'object') { | |||
plus.io.resolveLocalFileSystemURL(getLocalFilePath(path), function(entry) { | |||
entry.file(function(file) { | |||
var fileReader = new plus.io.FileReader() | |||
fileReader.onload = function(data) { | |||
resolve(data.target.result) | |||
} | |||
fileReader.onerror = function(error) { | |||
reject(error) | |||
} | |||
fileReader.readAsDataURL(file) | |||
}, function(error) { | |||
reject(error) | |||
}) | |||
}, function(error) { | |||
reject(error) | |||
}) | |||
return | |||
} | |||
if (typeof wx === 'object' && wx.canIUse('getFileSystemManager')) { | |||
wx.getFileSystemManager().readFile({ | |||
filePath: path, | |||
encoding: 'base64', | |||
success: function(res) { | |||
resolve('data:image/png;base64,' + res.data) | |||
}, | |||
fail: function(error) { | |||
reject(error) | |||
} | |||
}) | |||
return | |||
} | |||
reject(new Error('not support')) | |||
}) | |||
} | |||
export function base64ToPath(base64, extName) { | |||
return new Promise(function(resolve, reject) { | |||
if (typeof window === 'object' && 'document' in window) { | |||
base64 = base64.split(',') | |||
var type = base64[0].match(/:(.*?);/)[1] | |||
var str = atob(base64[1]) | |||
var n = str.length | |||
var array = new Uint8Array(n) | |||
while (n--) { | |||
array[n] = str.charCodeAt(n) | |||
} | |||
return resolve((window.URL || window.webkitURL).createObjectURL(new Blob([array], { type: type }))) | |||
} | |||
var fileName; | |||
if (!extName) { | |||
extName = base64.match(/data\:\S+\/(\S+);/) | |||
if (extName) { | |||
extName = extName[1] | |||
} else { | |||
reject(new Error('base64 error')) | |||
} | |||
fileName = Date.now() + '.' + extName; | |||
} else { | |||
fileName = Date.now() + extName; | |||
} | |||
if (typeof plus === 'object') { | |||
var bitmap = new plus.nativeObj.Bitmap('bitmap' + Date.now()) | |||
bitmap.loadBase64Data(base64, function() { | |||
var filePath = '_doc/uniapp_temp/' + fileName | |||
bitmap.save(filePath, {}, function() { | |||
bitmap.clear() | |||
resolve(filePath) | |||
}, function(error) { | |||
bitmap.clear() | |||
reject(error) | |||
}) | |||
}, function(error) { | |||
bitmap.clear() | |||
reject(error) | |||
}) | |||
return | |||
} | |||
if (typeof wx === 'object' && wx.canIUse('getFileSystemManager')) { | |||
var filePath = wx.env.USER_DATA_PATH + '/' + fileName | |||
wx.getFileSystemManager().writeFile({ | |||
filePath: filePath, | |||
data: base64.replace(/^data:\S+\/\S+;base64,/, ''), | |||
encoding: 'base64', | |||
success: function() { | |||
resolve(filePath) | |||
}, | |||
fail: function(error) { | |||
reject(error) | |||
} | |||
}) | |||
return | |||
} | |||
reject(new Error('not support')) | |||
}) | |||
} |
@@ -6,9 +6,16 @@ Vue.use(Vuex) | |||
export default new Vuex.Store({ | |||
state: { | |||
bgAudioMannager: null, | |||
messageObj: {} , // 用户信息 | |||
}, | |||
mutations: { | |||
// 日报详情 | |||
setMessageObj(state, obj) { | |||
state.messageObj = obj | |||
}, | |||
createAudio(state) { | |||
state.bgAudioMannager = uni.getBackgroundAudioManager(); | |||
}, | |||
@@ -1,17 +1,6 @@ | |||
// http.js使用域名 | |||
// const baseUrl = 'http://192.168.31.57:8080/autoSR/api';// 本地 | |||
// const baseUrl = 'http://127.0.0.1:8080/api';// 本地 | |||
// const baseUrl = 'http://121.42.63.138:9091/autoSR/api';// 测试站 | |||
// const baseUrl = 'http://192.168.31.89:9090/api';// sh | |||
// const baseUrl = 'http://121.42.63.138:9091/autoSR/api';// 测试站 | |||
// const baseUrl = 'http://192.168.31.92:8080/api';// 测试站 | |||
// const baseUrl = 'http://127.0.0.1:8080/autoSR/api';// 本地 | |||
// const baseUrl = 'http://192.168.31.244:8080/autoSR/api';// 本地 | |||
const baseUrl = 'http://81.70.55.170:9090/autoSR/api';// 测试站 | |||
const baseUrl = 'http://81.70.55.170:9090/autoSR/api';// 最新测试 | |||
// const baseUrl = 'http://192.168.31.210:8080/api'; // 泽明 | |||
// const baseUrl = 'http://192.168.31.167:8080/autoSR/api'; // 长龙 | |||
// const baseUrl = 'http://192.168.31.134:8080/autoSR/api'; // 佳豪 | |||
// const baseUrl = 'http://10.2.1.104:8081/autoSR/api'; // 刘敏 | |||
// const baseUrl = 'https://zkgj.quhouse.com/api'; // 质控正式 | |||
// const baseUrl = 'https://hfju.com/api'; // 数智正式 | |||