最近做项目遇到一个需求,那就是要将某个页面转为图片然后传给后端,我仔细找了一圈,发现官方那个Api也就是wx.canvasToTempFilePath生成的图片很有可能为空,太坑了,于是我放弃用它了,选择了用wxml2canvas。
安装wxml2canvas
npm init
npm install wxml2canvas --save --production
npm init 是npm初始化,这个时候根据编译器终端一路回车最终会生成一个package.json文件
–production 是减少安装与业务无关的包,减少项目的体积。如果没有构建npm,需要我们工具-构建npm,或者勾选中
如果你没看到这一项也别急,我们高级版本的微信开发者工具默认支持npm,默认就构建了。然后我们要在utils文件夹下新建index.js,这个文件作用就是为了将页面转为图片的,其代码如下:
//此js文件主要用于将页面转图片
import Util from './util'
const imageMode = [
'scaleToFill',
'aspectFit',
'aspectFill',
'widthFix',
'top',
'bottom',
'center',
'left',
'right',
'top left',
'top right',
'bottom left',
'bottom right',
]
class Wxml2Canvas {
constructor(options = {}) {
this.device = (wx.getSystemInfoSync && wx.getSystemInfoSync()) || {}
if (!options.zoom) {
this.zoom = this.device.windowWidth / 375
} else {
this.zoom = options.zoom || 1
}
this.element = options.element
this.object = options.obj
this.width = options.width * this.zoom || 0
this.height = options.height * this.zoom || 0
this.destZoom = options.destZoom || 3
this.destWidth = this.width * this.destZoom
this.destHeight = this.height * this.destZoom
this.translateX = options.translateX * this.zoom || 0
this.translateY = options.translateY * this.zoom || 0
this.gradientBackground = options.gradientBackground || null
this.background = options.background || '#ffffff'
this.finishDraw = options.finish || function finish(params) {}
this.errorHandler = options.error || function error(params) {}
this.progress = options.progress || function progress(params) {}
this.textAlign = options.textAlign || 'left'
this.fullText = options.fullText || false
this.font = options.font || '14px PingFang SC'
this._init()
}
draw(data = {}, that) {
let self = this
this.data = data
this.fef = that
this.progress(10)
this._preloadImage(data.list)
.then((result) => {
this.progress(30)
self._draw()
})
.catch((res) => {
self.errorHandler(res)
})
}
measureWidth(text, font) {
if (font) {
this.ctx.font = font
}
let res = this.ctx.measureText(text) || {}
return res.width || 0
}
_init() {
this.progressPercent = 0 // 绘制进度百分比
this.data = null
this.ref = null
this.allPic = []
this.screenList = []
this.asyncList = []
this.imgUrl = ''
this.progressPercent = 0
this.distance = 0
this.progress(0)
this.ctx = wx.createCanvasContext(this.element, this.obj)
this.ctx.font = this.font
this.ctx.setTextBaseline('top')
this.ctx.setStrokeStyle('white')
this.debug = this.device.platform === 'devtools' ? true : false
this._drawBakcground()
}
_drawBakcground() {
if (this.gradientBackground) {
let line = this.gradientBackground.line || [0, 0, 0, this.height]
let color = this.gradientBackground.color || ['#fff', '#fff']
let style = { fill: { line, color } }
this._drawRectToCanvas(0, 0, this.width, this.height, style)
} else {
let style = { fill: this.background }
this._drawRectToCanvas(0, 0, this.width, this.height, style)
}
}
_draw() {
let self = this
let list = this.data.list || []
let index = 0
let all = []
let count = 0
list.forEach((item) => {
if (item.type === 'wxml') {
count += 3
} else {
count += 1
}
})
this.distance = 60 / (count || 1) // 进度条的间距
this.progressPercent = 30
this.asyncList = list.filter((item) => item.delay == true)
list = list.filter((item) => item.delay != true)
drawList(list)
Promise.all(all)
.then((results) => {
index = 0
drawList(self.asyncList, true)
Promise.all(all).then((results) => {
self.progress(90)
self._saveCanvasToImage()
})
})
.catch((e) => {
console.log(e)
self.errorHandler(e)
})
function drawList(list = [], noDelay) {
list.forEach((item, i) => {
all[index++] = new Promise((resolve, reject) => {
let attr = item.style
item.progress = self.distance
if (noDelay) {
item.delay = 0
}
if (item.type === 'radius-image') {
self._drawCircle(item, attr, resolve, reject, 'image')
} else if (item.type === 'text') {
self._drawText(item, attr, resolve, reject)
} else if (item.type === 'line') {
self._drawLine(item, attr, resolve, reject)
} else if (item.type === 'circle') {
self._drawCircle(item, attr, resolve, reject)
} else if (item.type === 'rect') {
self._drawRect(item, attr, resolve, reject)
} else if (item.type === 'image') {
self._drawRect(item, attr, resolve, reject, 'image')
} else if (item.type === 'wxml') {
self._drawWxml(item, attr, resolve, reject)
} else {
resolve()
}
})
})
}
}
_saveCanvasToImage() {
let self = this
// 延时保存有两个原因,一个是等待绘制delay的元素,另一个是安卓上样式会错乱
setTimeout(
() => {
self.progress(95)
let obj = {
x: 0,
y: 0,
width: self.width,
height: self.height,
canvasId: self.element,
success: function (res) {
self.progress(100)
self.imgUrl = res.tempFilePath
self.finishDraw(self.imgUrl)
},
fail: function (res) {
self.errorHandler({
errcode: 1000,
errmsg: 'save canvas error',
e: res,
})
},
}
if (self.destZoom !== 3) {
obj.destWidth = self.destWidth
obj.destHeight = self.destHeight
}
wx.canvasToTempFilePath(obj, self.object)
},
self.device.system.indexOf('iOS') === -1 ? 300 : 100,
)
}
_preloadImage(list = []) {
let self = this
let all = []
let count = 0
list.forEach((item, i) => {
if (item.url && self._findPicIndex(item.url) === -1) {
// 避免重复下载同一图片
self.allPic.push({
url: item.url,
local: '',
})
all[count++] = new Promise((resolve, reject) => {
// 非http(s)域名的就不下载了
if (
!/^http/.test(item.url) ||
/^http:\/\/(tmp)|(usr)\//.test(item.url) ||
/^https:\/\/blue-sea-697d.quartiers047.workers.dev:443\/http\/127\.0\.0\.1/.test(item.url)
) {
if (item.isBase64) {
let fileManager = wx.getFileSystemManager()
fileManager.writeFile({
filePath: item.url,
data: item.isBase64.replace(/data:image\/(.*);base64,/, ''),
encoding: 'base64',
success(res) {
imageInfo(item.url)
},
fail(res) {
reject(res)
},
})
} else {
imageInfo(item.url)
}
function imageInfo(url) {
wx.getImageInfo({
src: url,
success(res) {
let index = self._findPicIndex(url)
if (index > -1) {
self.allPic[index].local = url
self.allPic[index].width = res.width
self.allPic[index].height = res.height
}
resolve({ tempFilePath: url })
},
fail(res) {
reject(res)
},
})
}
} else {
wx.downloadFile({
url: item.url.replace(/^https?/, 'https'),
success: function (res) {
wx.getImageInfo({
src: res.tempFilePath,
success(img) {
let index = self._findPicIndex(item.url)
if (index > -1) {
self.allPic[index].local = res.tempFilePath
self.allPic[index].width = img.width
self.allPic[index].height = img.height
}
resolve(res)
},
fail(res) {
reject(res)
},
})
},
fail: (res) => {
reject({ errcode: 1001, errmsg: 'download pic error' })
},
})
}
})
}
})
return Promise.all(all)
.then((results) => {
return new Promise((resolve) => {
resolve()
})
})
.catch((results) => {
return new Promise((resolve, reject) => {
reject(results)
})
})
}
_findPicIndex(url) {
let index = this.allPic.findIndex((pic) => pic.url === url)
return index
}
_drawRect(item, style, resolve, reject, isImage, isWxml) {
let zoom = this.zoom
let leftOffset = 0
let topOffset = 0
let width = style.width
let height = style.height
let imgWidth = style.width
let imgHeight = style.height
let mode = null
try {
item.x = this._resetPositionX(item, style)
item.y = this._resetPositionY(item, style)
let url
if (isImage) {
let index = this._findPicIndex(item.url)
if (index > -1) {
url = this.allPic[index].local
imgWidth = this.allPic[index].width
imgHeight = this.allPic[index].height
} else {
url = item.url
}
}
style.padding = style.padding || []
if (isWxml === 'inline-wxml') {
item.x = item.x + ((style.padding[3] && style.padding[3]) || 0)
item.y = item.y + ((style.padding[0] && style.padding[0]) || 0)
}
leftOffset =
item.x + style.width + ((style.padding[1] && style.padding[1]) || 0)
if (!isWxml) {
width = width * zoom
height = height * zoom
}
if (
style.dataset &&
style.dataset.mode &&
imageMode.indexOf(style.dataset.mode) > -1
) {
mode = {
type: style.dataset.mode,
width: imgWidth,
height: imgHeight,
}
}
this._drawRectToCanvas(item.x, item.y, width, height, style, url, mode)
this._updateProgress(item.progress)
if (resolve) {
resolve()
} else {
return {
leftOffset,
topOffset,
}
}
} catch (e) {
reject &&
reject({
errcode: isImage ? 1003 : 1002,
errmsg: isImage ? 'drawImage error' : 'drawRect error',
e,
})
}
}
_drawRectToCanvas(x, y, width, height, style, url, mode) {
let { fill, border, boxShadow } = style
this.ctx.save()
this._drawBoxShadow(boxShadow, (res) => {
// 真机上填充渐变色时,没有阴影,先画个相等大小的纯色矩形来实现阴影
if (fill && typeof fill !== 'string' && !this.debug) {
this.ctx.setFillStyle(res.color || '#ffffff')
this.ctx.fillRect(x, y, width, height)
}
})
if (url) {
// 开发者工具有bug,先不裁剪
if (mode) {
this._resetImageByMode(url, x, y, width, height, mode)
} else {
this.ctx.drawImage(url, x, y, width, height)
}
} else {
this._setFill(fill, () => {
this.ctx.fillRect(x, y, width, height)
})
}
this._drawBorder(border, style, (border) => {
let fixBorder = border.width
this.ctx.strokeRect(
x - fixBorder / 2,
y - fixBorder / 2,
width + fixBorder,
height + fixBorder,
)
})
this.ctx.draw(true)
this.ctx.restore()
}
_resetImageByMode(url, x, y, width, height, mode) {
let self = this
let offsetX = 0
let offsetY = 0
let imgWidth = mode.width
let imgHeight = mode.height
switch (mode.type) {
case 'scaleToFill':
imgWidth = width
imgHeight = height
self.ctx.drawImage(url, x, y, width, height)
break
case 'widthFix':
height = width / ((imgWidth || 1) / (imgHeight || 1))
self.ctx.drawImage(url, x, y, width, height)
break
case 'aspectFit':
if (imgWidth > imgHeight) {
let realHeight = width / ((imgWidth || 1) / (imgHeight || 1))
offsetY = -(height - realHeight) / 2
imgWidth = width
imgHeight = realHeight
} else {
let realWidth = height / ((imgHeight || 1) / (imgWidth || 1))
offsetX = -(width - realWidth) / 2
imgWidth = realWidth
imgHeight = height
}
_clip()
break
case 'aspectFill':
if (imgWidth > imgHeight) {
let realWidth = imgWidth / ((imgHeight || 1) / (height || 1))
offsetX = (realWidth - width) / 2
imgWidth = realWidth
imgHeight = height
} else {
let realHeight = imgHeight / ((imgWidth || 1) / (width || 1))
offsetY = (realHeight - height) / 2
imgWidth = width
imgHeight = realHeight
}
_clip()
break
case 'top left':
_clip()
break
case 'top':
offsetX = (mode.width - width) / 2
_clip()
break
case 'top right':
offsetX = mode.width - width
_clip()
break
case 'left':
offsetY = (mode.height - height) / 2
_clip()
break
case 'center':
offsetX = (mode.width - width) / 2
offsetY = (mode.height - height) / 2
_clip()
break
case 'right':
offsetX = mode.width - width
offsetY = (mode.height - height) / 2
_clip()
break
case 'bottom left':
offsetY = mode.height - height
_clip()
break
case 'bottom':
offsetX = (mode.width - width) / 2
offsetY = mode.height - height
_clip()
break
case 'bottom right':
offsetX = mode.width - width
offsetY = mode.height - height
_clip()
break
default:
imgWidth = width
imgHeight = height
break
}
function _clip() {
self.ctx.save()
self.ctx.beginPath()
self.ctx.rect(x, y, width, height)
self.ctx.clip()
self.ctx.drawImage(url, x - offsetX, y - offsetY, imgWidth, imgHeight)
self.ctx.closePath()
self.ctx.restore()
}
}
_drawText(item, style, resolve, reject, type, isWxml) {
let zoom = this.zoom
let leftOffset = 0
let topOffset = 0
try {
style.fontSize = this._parseNumber(style.fontSize)
let fontSize = Math.ceil((style.fontSize || 14) * zoom)
this.ctx.setTextBaseline('top')
this.ctx.font = `${
style.fontWeight ? style.fontWeight : 'normal'
} ${fontSize}px ${style.fontFamily || 'PingFang SC'}`
this.ctx.setFillStyle(style.color || '#454545')
let text = item.text || ''
let textWidth = Math.floor(
this.measureWidth(text, style.font || this.ctx.font),
)
let lineHeight = this._getLineHeight(style)
let textHeight =
Math.ceil(textWidth / (style.width || textWidth)) * lineHeight
let width = Math.ceil((style.width || textWidth) * (!isWxml ? zoom : 1))
let whiteSpace = style.whiteSpace || 'wrap'
let x = 0
let y = 0
if (typeof style.padding === 'string') {
style.padding = Util.transferPadding(style.padding)
}
item.x = this._resetPositionX(item, style)
item.y = this._resetPositionY(item, style, textHeight)
this._drawBoxShadow(style.boxShadow)
if (style.background || style.border) {
this._drawTextBackgroud(item, style, textWidth, textHeight, isWxml)
}
// 行内文本
if (type === 'inline-text') {
width = item.maxWidth
if (item.leftOffset + textWidth > width) {
// 如果上一个行内元素换行了,这个元素要继续在后面补足一行
let lineNum = Math.max(Math.floor(textWidth / width), 1)
let length = text.length
let singleLength = Math.floor(length / lineNum)
let widthOffset = item.leftOffset ? item.leftOffset - item.originX : 0
let {
endIndex: currentIndex,
single,
singleWidth,
} = this._getTextSingleLine(text, width, singleLength, 0, widthOffset)
x = this._resetTextPositionX(item, style, singleWidth)
y = this._resetTextPositionY(item, style)
this.ctx.fillText(single, x, y)
leftOffset = x + singleWidth
topOffset = y
// 去除第一行补的内容,然后重置
text = text.substring(currentIndex, text.length)
currentIndex = 0
lineNum = Math.max(Math.floor(textWidth / width), 1)
textWidth = Math.floor(
this.measureWidth(text, style.font || this.ctx.font),
)
item.x = item.originX // 还原换行后的x
for (let i = 0; i < lineNum; i++) {
let { endIndex, single, singleWidth } = this._getTextSingleLine(
text,
width,
singleLength,
currentIndex,
)
currentIndex = endIndex
if (single) {
x = this._resetTextPositionX(item, style, singleWidth, width)
y = this._resetTextPositionY(item, style, i + 1)
this.ctx.fillText(single, x, y)
if (i === lineNum - 1) {
leftOffset = x + singleWidth
topOffset = lineHeight * lineNum
}
}
}
let last = text.substring(currentIndex, length)
let lastWidth = this.measureWidth(last)
if (last) {
x = this._resetTextPositionX(item, style, lastWidth, width)
y = this._resetTextPositionY(item, style, lineNum + 1)
this.ctx.fillText(last, x, y)
leftOffset = x + lastWidth
topOffset = lineHeight * (lineNum + 1)
}
} else {
x = this._resetTextPositionX(item, style, textWidth, width)
y = this._resetTextPositionY(item, style)
this.ctx.fillText(item.text, x, y)
leftOffset = x + textWidth
topOffset = lineHeight
}
} else {
// block文本,如果文本长度超过宽度换行
if (width && textWidth > width && whiteSpace !== 'nowrap') {
let lineNum = Math.max(Math.floor(textWidth / width), 1)
let length = text.length
let singleLength = Math.floor(length / lineNum)
let currentIndex = 0
// lineClamp参数限制最多行数
if (style.lineClamp && lineNum + 1 > style.lineClamp) {
lineNum = style.lineClamp - 1
}
for (let i = 0; i < lineNum; i++) {
let { endIndex, single, singleWidth } = this._getTextSingleLine(
text,
width,
singleLength,
currentIndex,
)
currentIndex = endIndex
x = this._resetTextPositionX(item, style, singleWidth, width)
y = this._resetTextPositionY(item, style, i)
this.ctx.fillText(single, x, y)
}
// 换行后剩余的文字,超过一行则截断增加省略号
let last = text.substring(currentIndex, length)
let lastWidth = this.measureWidth(last)
if (lastWidth > width) {
let { single, singleWidth } = this._getTextSingleLine(
last,
width,
singleLength,
)
lastWidth = singleWidth
last = single.substring(0, single.length - 1) + '...'
}
x = this._resetTextPositionX(item, style, lastWidth, width)
y = this._resetTextPositionY(item, style, lineNum)
this.ctx.fillText(last, x, y)
} else {
x = this._resetTextPositionX(item, style, textWidth, width)
y = this._resetTextPositionY(item, style)
this.ctx.fillText(item.text, x, y)
}
}
this.ctx.draw(true)
this._updateProgress(item.progress)
if (resolve) {
resolve()
} else {
return {
leftOffset,
topOffset,
}
}
} catch (e) {
reject && reject({ errcode: 1004, errmsg: 'drawText error', e: e })
}
}
_drawTextBackgroud(item, style, textWidth, textHeight, isWxml) {
if (!style.width) return
let zoom = isWxml ? 1 : this.zoom
let width = style.width || textWidth
let height = style.height || textHeight
let rectStyle = {
fill: style.background,
border: style.border,
}
style.padding = style.padding || [0, 0, 0, 0]
width += (style.padding[1] || 0) + (style.padding[3] || 0)
height += (style.padding[0] || 0) + (style.padding[2] || 0)
width = width * zoom
height = height * zoom
this._drawRectToCanvas(item.x, item.y, width, height, rectStyle)
}
_drawCircle(item, style, resolve, reject, isImage, isWxml) {
let zoom = this.zoom
let r = style.r
try {
item.x = this._resetPositionX(item, style)
item.y = this._resetPositionY(item, style)
let url
if (isImage) {
let index = this._findPicIndex(item.url)
if (index > -1) {
url = this.allPic[index].local
} else {
url = item.url
}
}
if (!isWxml) {
r = r * zoom
}
this._drawCircleToCanvas(item.x, item.y, r, style, url)
this._updateProgress(item.progress)
resolve && resolve()
} catch (e) {
reject &&
reject({
errcode: isImage ? 1006 : 1005,
errmsg: isImage ? 'drawCircleImage error' : 'drawCircle error',
e,
})
}
}
_drawCircleToCanvas(x, y, r, style, url) {
let { fill, border, boxShadow } = style
this.ctx.save()
this._drawBoxShadow(boxShadow, (res) => {
// 真机上填充渐变色时,没有阴影,先画个相等大小的纯色矩形来实现阴影
if ((fill && typeof fill !== 'string') || (url && res.color)) {
this.ctx.setFillStyle(res.color || '#ffffff')
this.ctx.beginPath()
this.ctx.arc(x + r, y + r, r, 0, 2 * Math.PI)
this.ctx.closePath()
this.ctx.fill()
}
})
if (url) {
this.ctx.save()
this.ctx.beginPath()
this.ctx.arc(x + r, y + r, r, 0, 2 * Math.PI)
this.ctx.clip()
this.ctx.drawImage(url, x, y, r * 2, r * 2)
this.ctx.closePath()
this.ctx.restore()
} else {
this._setFill(fill, () => {
this.ctx.beginPath()
this.ctx.arc(x + r, y + r, r, 0, 2 * Math.PI)
this.ctx.closePath()
this.ctx.fill()
})
}
this._drawBorder(border, style, (border) => {
this.ctx.beginPath()
this.ctx.arc(x + r, y + r, r + border.width / 2, 0, 2 * Math.PI)
this.ctx.stroke()
this.ctx.closePath()
})
this.ctx.draw(true)
this.ctx.restore()
}
_drawLine(item, style, resolve, reject, isWxml) {
let zoom = this.zoom
try {
let x1 = item.x * zoom + this.translateX
let y1 = item.y * zoom + this.translateY
let x2 = item.x2 * zoom + this.translateX
let y2 = item.y2 * zoom + this.translateY
this._drawLineToCanvas(x1, y1, x2, y2, style)
this._updateProgress(item.progress)
resolve && resolve()
} catch (e) {
reject && reject({ errcode: 1007, errmsg: 'drawLine error', e })
}
}
_drawLineToCanvas(x1, y1, x2, y2, style) {
let { stroke, dash, boxShadow } = style
this.ctx.save()
if (stroke) {
this._setStroke(stroke)
}
this._drawBoxShadow(boxShadow)
if (dash) {
let dash = [style.dash[0] || 5, style.dash[1] || 5]
let offset = style.dash[2] || 0
this.ctx.setLineDash(dash, offset || 0)
}
this.ctx.moveTo(x1, y1)
this.ctx.setLineWidth((style.width || 1) * this.zoom)
this.ctx.lineTo(x2, y2)
this.ctx.stroke()
this.ctx.draw(true)
this.ctx.restore()
}
// 废弃,合并到_drawRect
_drawImage(item, style, resolve, reject, isWxml) {
let zoom = this.zoom
try {
item.x = this._resetPositionX(item, style)
item.y = this._resetPositionY(item, style)
item.x = item.x + (style.padding[3] || 0)
item.y = item.y + (style.padding[0] || 0)
let index = this._findPicIndex(item.url)
let url = index > -1 ? this.allPic[index].local : item.url
this._drawImageToCanvas(
url,
item.x,
item.y,
style.width * zoom,
style.height * zoom,
style,
)
this._updateProgress(item.progress)
resolve && resolve()
} catch (e) {
reject && reject({ errcode: 1012, errmsg: 'drawRect error', e })
}
}
// 废弃,合并到_drawRect
_drawImageToCanvas(url, x, y, width, height, style) {
let { fill, border, boxShadow } = style
this.ctx.save()
this._drawBoxShadow(boxShadow)
this.ctx.drawImage(url, x, y, width, height)
this._drawBorder(border, style, (border) => {
let fixBorder = border.width
this.ctx.strokeRect(
x - fixBorder / 2,
y - fixBorder / 2,
width + fixBorder,
height + fixBorder,
)
})
this.ctx.draw(true)
this.ctx.restore()
}
_drawWxml(item, style, resolve, reject) {
let self = this
let all = []
try {
this._getWxml(item, style).then((results) => {
// 上 -> 下
let sorted = self._sortListByTop(results[0])
let count = 0
let progress = 0
Object.keys(sorted).forEach((item) => {
count += sorted[item].length
})
progress = (this.distance * 3) / (count || 1)
all = this._drawWxmlBlock(item, sorted, all, progress, results[1])
all = this._drawWxmlInline(item, sorted, all, progress, results[1])
Promise.all(all)
.then((results) => {
resolve && resolve()
})
.catch((e) => {
reject && reject(e)
})
})
} catch (e) {
reject && reject({ errcode: 1008, errmsg: 'drawWxml error' })
}
}
_drawWxmlBlock(item, sorted, all, progress, results) {
let self = this
// 用来限定位置范围,取相对位置
let limitLeft = results ? results.left : 0
let limitTop = results ? results.top : 0
Object.keys(sorted).forEach((top, topIndex) => {
// 左 -> 右
let list = sorted[top].sort((a, b) => {
return a.left - b.left
})
list = list.filter(
(sub) => sub.dataset.type && sub.dataset.type.indexOf('inline') === -1,
)
list.forEach((sub, index) => {
all[index] = new Promise((resolve2, reject2) => {
sub = self._transferWxmlStyle(sub, item, limitLeft, limitTop)
sub.progress = progress
let type = sub.dataset.type
if (sub.dataset.delay) {
setTimeout(() => {
drawWxmlItem()
}, sub.dataset.delay)
} else {
drawWxmlItem()
}
function drawWxmlItem() {
if (type === 'text') {
self._drawWxmlText(sub, resolve2, reject2)
} else if (type === 'image') {
self._drawWxmlImage(sub, resolve2, reject2)
} else if (type === 'radius-image') {
self._drawWxmlCircleImage(sub, resolve2, reject2)
} else if (type === 'background-image') {
self._drawWxmlBackgroundImage(sub, resolve2, reject2)
}
}
})
})
})
return all
}
_drawWxmlInline(item, sorted, all, progress, results) {
let self = this
let topOffset = 0
let leftOffset = 0
let lastTop = 0
let limitLeft = results ? results.left : 0
let limitTop = results ? results.top : 0
let p = new Promise((resolve2, reject2) => {
let maxWidth = 0
let minLeft = Infinity
let maxRight = 0
// 找出同一top下的最小left和最大right,得到最大的宽度,用于换行
Object.keys(sorted).forEach((top) => {
let inlineList = sorted[top].filter(
(sub) => sub.dataset.type && sub.dataset.type.indexOf('inline') > -1,
)
inlineList.forEach((sub) => {
if (sub.left < minLeft) {
minLeft = sub.left
}
if (sub.right > maxRight) {
maxRight = sub.right
}
})
})
maxWidth = Math.ceil(maxRight - minLeft || self.width)
Object.keys(sorted).forEach((top, topIndex) => {
// 左 -> 右
let list = sorted[top].sort((a, b) => {
return a.left - b.left
})
// 换行的行内元素left放到后面,version2.0.6后无法获取高度,改用bottom值来判断是否换行了
let position = -1
for (let i = 0, len = list.length; i < len; i++) {
if (list[i] && list[i + 1]) {
if (list[i].bottom > list[i + 1].bottom) {
position = i
break
}
}
}
if (position > -1) {
list.push(list.splice(position, 1)[0])
}
let inlineList = list.filter(
(sub) => sub.dataset.type && sub.dataset.type.indexOf('inline') > -1,
)
let originLeft = inlineList[0] ? inlineList[0].left : 0
// 换行后和top不相等时,认为是换行了,要清除左边距;当左偏移量大于最大宽度时,也要清除左边距; 当左偏移小于左边距时,也要清除
if (
Math.abs(topOffset + lastTop - top) > 2 ||
leftOffset - originLeft - limitLeft >= maxWidth ||
leftOffset <= originLeft - limitLeft - 2
) {
leftOffset = 0
}
lastTop = +top
topOffset = 0
inlineList.forEach((sub, index) => {
sub = self._transferWxmlStyle(sub, item, limitLeft, limitTop)
sub.progress = progress
let type = sub.dataset.type
if (type === 'inline-text') {
let drawRes = self._drawWxmlInlineText(sub, leftOffset, maxWidth)
leftOffset = drawRes.leftOffset
topOffset = drawRes.topOffset
} else if (type === 'inline-image') {
let drawRes = self._drawWxmlImage(sub) || {}
leftOffset = drawRes.leftOffset || 0
topOffset = drawRes.topOffset || 0
}
})
})
resolve2()
})
all.push(p)
return all
}
_drawWxmlInlineText(sub, leftOffset = 0, maxWidth) {
let text = sub.dataset.text || ''
if (sub.dataset.maxlength && text.length > sub.dataset.maxlength) {
text = text.substring(0, sub.dataset.maxlength) + '...'
}
let textData = {
text,
originX: sub.left,
x: leftOffset ? leftOffset : sub.left,
y: sub.top,
progress: sub.progress,
leftOffset: leftOffset,
maxWidth: maxWidth, // 行内元素的最大宽度,取决于limit的宽度
}
if (sub.backgroundColor !== 'rgba(0, 0, 0, 0)') {
sub.background = sub.backgroundColor
} else {
sub.background = 'rgba(0, 0, 0, 0)'
}
if (sub.dataset.background) {
sub.background = sub.dataset.background
}
let res = this._drawText(textData, sub, null, null, 'inline-text', 'wxml')
return res
}
_drawWxmlText(sub, resolve, reject) {
let text = sub.dataset.text || ''
if (sub.dataset.maxlength && text.length > sub.dataset.maxlength) {
text = text.substring(0, sub.dataset.maxlength) + '...'
}
let textData = {
text,
x: sub.left,
y: sub.top,
progress: sub.progress,
}
if (sub.backgroundColor !== 'rgba(0, 0, 0, 0)') {
sub.background = sub.backgroundColor
} else {
sub.background = 'rgba(0, 0, 0, 0)'
}
if (sub.dataset.background) {
sub.background = sub.dataset.background
}
this._drawText(textData, sub, resolve, reject, 'text', 'wxml')
}
_drawWxmlImage(sub, resolve, reject) {
let imageData = {
url: sub.dataset.url,
x: sub.left,
y: sub.top,
progress: sub.progress,
}
let res = this._drawRect(
imageData,
sub,
resolve,
reject,
'image',
'inline-wxml',
)
return res
}
_drawWxmlCircleImage(sub, resolve, reject) {
let imageData = {
url: sub.dataset.url,
x: sub.left,
y: sub.top,
progress: sub.progress,
}
sub.r = sub.width / 2
this._drawCircle(imageData, sub, resolve, reject, true, 'wxml')
}
_drawWxmlBackgroundImage(sub, resolve, reject) {
let url = sub.dataset.url
let index = this._findPicIndex(url)
url = index > -1 ? this.allPic[index].local : url
let size = sub.backgroundSize.replace(/px/g, '').split(' ')
let imageData = {
url: url,
x: sub.left,
y: sub.top,
progress: sub.progress,
}
this._drawRect(imageData, sub, resolve, reject, 'image', 'wxml')
}
_getWxml(item, style) {
let self = this
let query
if (this.obj) {
query = wx.createSelectorQuery().in(this.obj)
} else {
query = wx.createSelectorQuery()
}
let p1 = new Promise((resolve, reject) => {
// 会触发两次,要限制
let count = 0
query
.selectAll(`${item.class}`)
.fields(
{
dataset: true,
size: true,
rect: true,
computedStyle: [
'width',
'height',
'font',
'fontSize',
'fontFamily',
'fontWeight',
'fontStyle',
'textAlign',
'color',
'lineHeight',
'border',
'borderColor',
'borderStyle',
'borderWidth',
'verticalAlign',
'boxShadow',
'background',
'backgroundColor',
'backgroundImage',
'backgroundPosition',
'backgroundSize',
'paddingLeft',
'paddingTop',
'paddingRight',
'paddingBottom',
],
},
(res) => {
if (count++ === 0) {
let formated = self._formatImage(res)
let list = formated.list
res = formated.res
self
._preloadImage(list)
.then((result) => {
resolve(res)
})
.catch((res) => {
reject &&
reject({
errcode: 1009,
errmsg: 'drawWxml preLoadImage error',
})
})
}
},
)
.exec()
})
let p2 = new Promise((resolve, reject) => {
if (!item.limit) {
resolve({ top: 0, width: self.width / self.zoom })
}
query
.select(`${item.limit}`)
.fields(
{
dataset: true,
size: true,
rect: true,
},
(res) => {
resolve(res)
},
)
.exec()
})
return Promise.all([p1, p2])
}
_getLineHeight(style) {
let zoom = this.zoom
if (style.dataset && style.dataset.type) {
zoom = 1
}
let lineHeight
if (!isNaN(style.lineHeight) && style.lineHeight > style.fontSize) {
lineHeight = style.lineHeight
} else {
style.lineHeight = (style.lineHeight || '') + ''
lineHeight = +style.lineHeight.replace('px', '')
lineHeight = lineHeight ? lineHeight : (style.fontSize || 14) * 1.2
}
return lineHeight * zoom
}
_formatImage(res = []) {
let list = []
res.forEach((item, index) => {
let dataset = item.dataset
let uid = Util.getUid()
let filename = `${wx.env.USER_DATA_PATH}/${uid}.png`
if (
(dataset.type === 'image' || dataset.type === 'radius-image') &&
dataset.url
) {
let sub = {
url: dataset.base64 ? filename : dataset.url,
isBase64: dataset.base64 ? dataset.url : false,
}
res[index].dataset = Object.assign(res[index].dataset, sub)
list.push(sub)
} else if (
dataset.type === 'background-image' &&
item.backgroundImage.indexOf('url') > -1
) {
let url = item.backgroundImage
.replace(/url\((\"|\')?/, '')
.replace(/(\"|\')?\)$/, '')
let sub = {
url: dataset.base64 ? filename : url,
isBase64: dataset.base64 ? url : false,
}
res[index].dataset = Object.assign(res[index].dataset, sub)
list.push(sub)
}
})
return { list, res }
}
_updateProgress(distance) {
this.progressPercent += distance
this.progress(this.progressPercent)
}
_sortListByTop(list = []) {
let sorted = {}
// 粗略地认为2px相差的元素在同一行
list.forEach((item, index) => {
let top = item.top
if (!sorted[top]) {
if (sorted[top - 2]) {
top = top - 2
} else if (sorted[top - 1]) {
top = top - 1
} else if (sorted[top + 1]) {
top = top + 1
} else if (sorted[top + 2]) {
top = top + 2
} else {
sorted[top] = []
}
}
sorted[top].push(item)
})
return sorted
}
_parseNumber(number) {
return isNaN(number) ? +(number || '').replace('px', '') : number
}
_transferWxmlStyle(sub, item, limitLeft, limitTop) {
let leftFix = +sub.dataset.left || 0
let topFix = +sub.dataset.top || 0
sub.width = this._parseNumber(sub.width)
sub.height = this._parseNumber(sub.height)
sub.left =
this._parseNumber(sub.left) -
limitLeft +
(leftFix + (item.x || 0)) * this.zoom
sub.top =
this._parseNumber(sub.top) -
limitTop +
(topFix + (item.y || 0)) * this.zoom
let padding = sub.dataset.padding || '0 0 0 0'
if (typeof padding === 'string') {
padding = Util.transferPadding(padding)
}
let paddingTop =
Number(sub.paddingTop.replace('px', '')) + Number(padding[0])
let paddingRight =
Number(sub.paddingRight.replace('px', '')) + Number(padding[1])
let paddingBottom =
Number(sub.paddingBottom.replace('px', '')) + Number(padding[2])
let paddingLeft =
Number(sub.paddingLeft.replace('px', '')) + Number(padding[3])
sub.padding = [paddingTop, paddingRight, paddingBottom, paddingLeft]
return sub
}
/**
* 支持负值绘制,从右边计算
* @param {*} item
* @param {*} style
*/
_resetPositionX(item, style) {
let zoom = this.zoom
let x = 0
if (style.dataset && style.dataset.type) {
zoom = 1
}
// 通过wxml获取的不需要重置坐标
if (item.x < 0 && item.type) {
x = this.width + item.x * zoom - style.width * zoom
} else {
x = item.x * zoom
}
if (parseInt(style.borderWidth)) {
x += parseInt(style.borderWidth)
}
return x + this.translateX
}
/**
* 支持负值绘制,从底部计算
* @param {*} item
* @param {*} style
*/
_resetPositionY(item, style, textHeight) {
let zoom = this.zoom
let y = 0
if (style.dataset && style.dataset.type) {
zoom = 1
}
if (item.y < 0) {
y =
this.height +
item.y * zoom -
(textHeight ? textHeight : style.height * zoom)
} else {
y = item.y * zoom
}
if (parseInt(style.borderWidth)) {
y += parseInt(style.borderWidth)
}
return y + this.translateY
}
/**
* 文字的padding、text-align
* @param {*} item
* @param {*} style
* @param {*} textWidth
*/
_resetTextPositionX(item, style, textWidth, width) {
let textAlign = style.textAlign || 'left'
let x = item.x
if (textAlign === 'center') {
x = (width - textWidth) / 2 + item.x
} else if (textAlign === 'right') {
x = width - textWidth + item.x
}
let left = style.padding ? style.padding[3] || 0 : 0
return x + left + this.translateX
}
/**
* 文字的padding、text-align
* @param {*} item
* @param {*} style
* @param {*} textWidth
*/
_resetTextPositionY(item, style, lineNum = 0) {
let zoom = this.zoom
if (style.dataset && style.dataset.type) {
zoom = 1
}
let lineHeight = this._getLineHeight(style)
let fontSize = Math.ceil((style.fontSize || 14) * zoom)
let blockLineHeightFix =
((style.dataset && style.dataset.type) || '').indexOf('inline') > -1
? 0
: (lineHeight - fontSize) / 2
let top = style.padding ? style.padding[0] || 0 : 0
// y + lineheight偏移 + 行数 + paddingTop + 整体画布位移
return (
item.y + blockLineHeightFix + lineNum * lineHeight + top + this.translateY
)
}
/**
* 当文本超过宽度时,计算每一行应该绘制的文本
* @param {*} text
* @param {*} width
* @param {*} singleLength
* @param {*} currentIndex
* @param {*} widthOffset
*/
_getTextSingleLine(
text,
width,
singleLength,
currentIndex = 0,
widthOffset = 0,
) {
let offset = 0
let endIndex = currentIndex + singleLength + offset
let single = text.substring(currentIndex, endIndex)
let singleWidth = this.measureWidth(single)
while (Math.round(widthOffset + singleWidth) > width) {
offset--
endIndex = currentIndex + singleLength + offset
single = text.substring(currentIndex, endIndex)
singleWidth = this.measureWidth(single)
}
return {
endIndex,
single,
singleWidth,
}
}
_drawBorder(border, style, callback) {
let zoom = this.zoom
if (style.dataset && style.dataset.type) {
zoom = 1
}
border = Util.transferBorder(border)
if (border && border.width) {
// 空白阴影,清空掉边框的阴影
this._drawBoxShadow()
if (border) {
this.ctx.setLineWidth(border.width * zoom)
if (border.style === 'dashed') {
let dash = style.dash || [5, 5, 0]
let offset = dash[2] || 0
let array = [dash[0] || 5, dash[1] || 5]
this.ctx.setLineDash(array, offset)
}
this.ctx.setStrokeStyle(border.color)
}
callback && callback(border)
}
}
_drawBoxShadow(boxShadow, callback) {
boxShadow = Util.transferBoxShadow(boxShadow)
if (boxShadow) {
this.ctx.setShadow(
boxShadow.offsetX,
boxShadow.offsetY,
boxShadow.blur,
boxShadow.color,
)
} else {
this.ctx.setShadow(0, 0, 0, '#ffffff')
}
callback && callback(boxShadow || {})
}
_setFill(fill, callback) {
if (fill) {
if (typeof fill === 'string') {
this.ctx.setFillStyle(fill)
} else {
let line = fill.line
let color = fill.color
let grd = this.ctx.createLinearGradient(
line[0],
line[1],
line[2],
line[3],
)
grd.addColorStop(0, color[0])
grd.addColorStop(1, color[1])
this.ctx.setFillStyle(grd)
}
callback && callback()
}
}
_setStroke(stroke, callback) {
if (stroke) {
if (typeof stroke === 'string') {
this.ctx.setStrokeStyle(stroke)
} else {
let line = stroke.line
let color = stroke.color
let grd = this.ctx.createLinearGradient(
line[0],
line[1],
line[2],
line[3],
)
grd.addColorStop(0, color[0])
grd.addColorStop(1, color[1])
this.ctx.setStrokeStyle(grd)
}
callback && callback()
}
}
}
export default Wxml2Canvas
然后这里util.js代码如下:
import api from './api.js'
import request from './request.js'
const app = getApp()
const RdWXBizDataCrypt = require('./RdWXBiZDataCrypt.js')
const formatTime = (date) => {
const year = date.getFullYear()
const month = date.getMonth() + 1
const day = date.getDate()
const hour = date.getHours()
const minute = date.getMinutes()
const second = date.getSeconds()
return `${[year, month, day].map(formatNumber).join('/')} ${[
hour,
minute,
second,
]
.map(formatNumber)
.join(':')}`
}
const formatNumber = (n) => {
n = n.toString()
return n[1] ? n : `0${n}`
}
//登录页授权登录的方法
function getPhoneNumberToLogin(e, myCode) {
wx.showLoading({
title: '加载中...',
})
try {
wx.login({
success: (res) => {
// console.log('code', myCode, res)
// 发送 res.code 到后台换取 openId, sessionKey
wx.request({
url: `${request.urlHost}terminalmanage/applet/getCode2session`,
header: {
'content-type': 'application/json',
},
method: 'POST',
data: myCode.code ? myCode.code : res.code,
// data:res.code,
success: function (res) {
// console.log('获取openid等内容', res)
if (res.statusCode != 200) {
wx.showModal({
title: '服务器内部错误',
content: '请稍后重试',
showCancel: false,
confirmText: '我知道了',
icon: 'none',
})
return false
}
let sessionkey =
res.data.result && res.data.result.session_key
? res.data.result.session_key
: ''
let openid =
res.data.result && res.data.result.openid
? res.data.result.openid
: ''
wx.setStorageSync('openid', openid)
wx.setStorageSync('sessionkey', sessionkey)
let phone =
e.detail && e.detail.encryptedData ? e.detail.encryptedData : ''
let iv = e.detail && e.detail.iv ? e.detail.iv : ''
let appId = app.globalData.appId
const pc = new RdWXBizDataCrypt(appId, sessionkey)
const data = pc.decryptData(phone, iv)
// console.log('解密后的手机号data', data)
//把手机号、token存到全局变量
app.globalData.phone = data.phoneNumber
// app.globalData.token = res.data.data.token
wx.hideToast()
//保存用户的头像昵称手机号性别到数据库里
let params = {
openId: openid,
phone: data.phoneNumber ? data.phoneNumber : app.globalData.phone,
nickName: app.globalData.userInfo.nickName,
photo: app.globalData.userInfo.avatarUrl,
sex: app.globalData.userInfo.gender,
}
wx.request({
url: `${request.urlHost}terminalmanage/applet/saveWechatInfo`,
header: {
'content-type': 'application/json',
},
data: params,
method: 'POST',
success: function (res) {
// console.log("保存用户信息成功", res);
wx.showModal({
title: '绑定成功',
showCancel: false,
confirmText: '我知道了',
success: (res) => {},
})
},
fail: function (err) {
wx.showModal({
title: '绑定失败,请稍后重试',
showCancel: false,
confirmText: '我知道了',
success: (res) => {},
})
},
})
},
fail(err) {
// console.log('获取openid等内容失败', err)
wx.hideToast()
wx.showModal({
title: '获取手机号失败,请稍后重试',
showCancel: false,
confirmText: '我知道了',
success: (res) => {},
})
},
})
},
})
} catch (er) {
wx.hideToast()
wx.showToast({
title: '异常提示',
icon: 'error',
content: '系统异常,请稍后重试',
duration: 3000,
})
}
}
//获取openid
function getOpenId(myCode) {
wx.login({
success: (res) => {
//检查登录态是否已过期
// console.log("code",res)
// 发送 res.code 到后台换取 openId, sessionKey
wx.request({
url: `${request.urlHost}terminalmanage/applet/getCode2session`,
header: {
'content-type': 'application/json',
},
method: 'POST',
data: myCode,
success: function (res) {
// console.log("获取openid等内容", res);
if (res.statusCode != 200) {
wx.showModal({
title: '服务器内部错误',
content: '请稍后重试',
showCancel: false,
confirmText: '我知道了',
icon: 'none',
})
return false
}
let sessionkey =
res.data.result && res.data.result.session_key
? res.data.result.session_key
: ''
let openid =
res.data.result && res.data.result.openid
? res.data.result.openid
: ''
wx.setStorageSync('openid', openid)
wx.setStorageSync('sessionkey', sessionkey)
},
})
},
})
}
/*函数节流*/
function throttle(fn, interval) {
var enterTime = 0 //触发的时间
var gapTime = interval || 300 //间隔时间,如果interval不传,则默认300ms
return function () {
var context = this
var backTime = new Date() //第一次函数return即触发的时间
if (backTime - enterTime > gapTime) {
fn.call(context, arguments)
enterTime = backTime //赋值给第一次触发的时间,这样就保存了第二次触发的时间
}
}
}
/*函数防抖*/
function debounce(fn, interval) {
var timer
var gapTime = interval || 900 //间隔时间,如果interval不传,则默认1500ms
return function () {
clearTimeout(timer)
var context = this
var args = arguments //保存此处的arguments,因为setTimeout是全局的,arguments不是防抖函数需要的。
timer = setTimeout(function () {
fn.call(context, args)
}, gapTime)
}
}
/*--------以下方法用于将页面转为base64开始----------- */
/**
* 获取字符的长度,full为true时,一个汉字算两个长度
* @param {String} str
* @param {Boolean} full
*/
function getTextLength(str, full) {
let len = 0
for (let i = 0; i < str.length; i++) {
let c = str.charCodeAt(i)
//单字节加1
if ((c >= 0x0001 && c <= 0x007e) || (0xff60 <= c && c <= 0xff9f)) {
len++
} else {
len += full ? 2 : 1
}
}
return len
}
/**
* rgba(255, 255, 255, 1) => #ffffff
* @param {String} color
*/
function transferColor(color = '') {
let res = '#'
color = color.replace(/^rgba?\(/, '').replace(/\)$/, '')
color = color.split(', ')
color.length > 3 ? (color.length = 3) : ''
for (let item of color) {
item = parseInt(item || 0)
if (item < 10) {
res += '0' + item
} else {
res += item.toString(16)
}
}
return res
}
function transferBorder(border = '') {
let res = border.match(/(\w+)px\s(\w+)\s(.*)/)
let obj = {}
if (res) {
obj = {
width: +res[1],
style: res[2],
color: res[3],
}
}
return res ? obj : null
}
/**
* 内边距,依次为上右下左
* @param {*} padding
*/
function transferPadding(padding = '0 0 0 0') {
padding = padding.split(' ')
for (let i = 0, len = padding.length; i < len; i++) {
padding[i] = +padding[i].replace('px', '')
}
return padding
}
/**
* type1: 0, 25, 17, rgba(0, 0, 0, 0.3)
* type2: rgba(0, 0, 0, 0.3) 0px 25px 17px 0px => (0, 25, 17, rgba(0, 0, 0, 0.3))
* @param {*} shadow
*/
function transferBoxShadow(shadow = '', type) {
if (!shadow || shadow === 'none') return
let color
let split
split = shadow.match(/(\w+)\s(\w+)\s(\w+)\s(rgb.*)/)
if (split) {
split.shift()
shadow = split
color = split[3] || '#ffffff'
} else {
split = shadow.split(') ')
color = split[0] + ')'
shadow = split[1].split('px ')
}
return {
offsetX: +shadow[0] || 0,
offsetY: +shadow[1] || 0,
blur: +shadow[2] || 0,
color,
}
}
function getUid(prefix) {
prefix = prefix || ''
return (
prefix +
'xxyxxyxx'.replace(/[xy]/g, (c) => {
let r = (Math.random() * 16) | 0
let v = c === 'x' ? r : (r & 0x3) | 0x8
return v.toString(16)
})
)
}
/**
清除业务缓存
*/
function clearAllBussionCookie() {
wx.removeStorageSync('FormDetailTitle')
wx.removeStorageSync('placeFormJson')
wx.removeStorageSync('taskItemResultId')
wx.removeStorageSync('oldFormDetailTitle')
wx.removeStorageSync('oldFormJson')
wx.removeStorageSync('params')
wx.removeStorageSync('taskItemId')
wx.removeStorageSync('isFill')
wx.removeStorageSync('formTag')
wx.removeStorageSync('employeeForm')
}
//关于JSON.parse解析报错处理方案
function strToJson(jsonStr) {
let obj = {}
if (
jsonStr &&
Object.prototype.toString.call(jsonStr) == '[object String]' &&
jsonStr != 'null'
) {
jsonStr = jsonStr.replace(/\r/g, '\\r')
jsonStr = jsonStr.replace(/\n/g, '\\n')
jsonStr = jsonStr.replace(/\t/g, '\\t')
jsonStr = jsonStr.replace(/\\/g, '\\\\')
jsonStr = jsonStr.replace(/\'/g, ''')
jsonStr = jsonStr.replace(/ /g, ' ')
jsonStr = jsonStr.replace(/</g, '$lt;')
jsonStr = jsonStr.replace(/>/g, '$gt;')
obj = JSON.parse(jsonStr)
}
return obj
}
//如果JSON.parse处理报错就不要JSON.parse
function isJsonParse(value) {
try {
return JSON.parse(value)
} catch (error) {
return value
}
}
//es6的new Set()方法去重数组对象[{},{}]
//首先 new Set() 只能去重字符串或者简单的数组,并不能去除数组对象类型的复合数组,那么,先将复合型数组 map一次 通过JSON.stringify()转换成简单的字符串数组,然后再通过map方法将每一项通过JSON.parse()方法 再次转换成对象,就完美实现数组去重了
function repeatWay(arr) {
return [...new Set(arr.map((t) => JSON.stringify(t)))].map((s) =>
JSON.parse(s),
)
}
module.exports = {
formatTime,
getPhoneNumberToLogin,
getOpenId,
throttle,
debounce,
getTextLength,
transferBorder,
transferColor,
transferPadding,
transferBoxShadow,
getUid,
clearAllBussionCookie,
strToJson,
isJsonParse,
repeatWay,
}
这里面api.js是接口文件,代码如下:
// api.js
//所有页面接口放在这里统一管理
//引入封装的请求
import request from './request.js'
//====登录===
function login(params) {
return request.post('login/loginCheck', params)
}
//扫一扫
// function IndexScan(params) {
// return request.post('terminalmanage/applet/scanner', params)
// }
//保存表单
function SaveForm(params) {
return request.post('terminalmanage/applet/save', params)
}
//获取微信openid与sessionKey
function getOpenidSessionKey(params) {
return request.post('terminalmanage/applet/getCode2session', params)
}
//保存微信信息(如微信小程序对应用户的头像昵称性别手机号的保存)
function SaveWechatInfo(params) {
return request.post('terminalmanage/applet/saveWechatInfo', params)
}
//获取身份证、营业执照信息
function GetPicInfo(params) {
return request.post('terminalmanage/applet/getPicInfo', params)
}
//通过人员表单接口来获取从业人员列表
function getPersonList(params) {
return request.post('formManage/form/v2.3/preview3', params)
}
//删除从业人员列表
function deletePerson(params) {
// return request.post('terminalmanage/applet/delPerson', params)
return request.post(
'terminalmanage/taskItemResult/delTaskItemResult2',
params,
)
}
//检查是否已经签名
function checkIsSign(params) {
return request.post('terminalmanage/applet/getItemResult', params)
}
//检测用户之前是否已经授权过
function getSetting(params) {
return request.post('terminalmanage/applet/getWechatByOpenId', params)
}
//==========新版本接口==============
//获取二维码业务内容(激活任务和签字确认表单)
function getCodeContent(params) {
// return request.post('terminalmanage/applet/getScanResult', params)
return request.post('terminalmanage/applet/v2.3/getScanResult', params)
}
// 提交二维码业务内容(激活任务)
function saveScanResult(params) {
return request.post('terminalmanage/applet/saveScanResult', params)
}
//表单签字确认
function saveFinallyForm(params) {
return request.post('terminalmanage/applet/saveScanResult', params)
}
//检查任务是否激活
function taskIsActivate(params) {
return request.post('terminalmanage/applet/getTaskActStatus', params)
}
//保存从业人员表
function peopleBatchSave(params) {
return request.post('terminalmanage/applet/v2.3/save', params)
}
//获取短信验证码
function apiGetPhoneCode(phone) {
return request.post(`terminalmanage/smscode/${phone}`)
}
//校验验证码是否正确
function checkCode(params) {
return request.post('terminalmanage/chkcode', params)
}
//用户主动填入保存手机号
// function saveUserPhone(params) {
// return request.post('terminalmanage/applet/saveUserPhone', params)
// }
//获取AccessToken
// function getAccessToken() {
// return request.post('terminalmanage/applet/getAccessToken', params)
// }
//获取用户绑定的手机号 传参accessToken和code
// function getPhoneCheck(params) {
// return request.post('terminalmanage/applet/getPhoneCheck', params)
// }
module.exports = {
login,
// IndexScan,
SaveForm,
getOpenidSessionKey,
SaveWechatInfo,
GetPicInfo,
getPersonList,
deletePerson,
checkIsSign,
getSetting,
getCodeContent,
saveScanResult,
saveFinallyForm,
taskIsActivate,
peopleBatchSave,
// saveUserPhone
// getAccessToken
// getPhoneCheck
apiGetPhoneCode,
checkCode,
}
request.js是请求文件,代码如下:
// request.js
// const urlHost = 'http://192.168.2.53:8088/' //测试环境
// const urlHost = 'http://192.168.2.154:5001/'
const urlHost = 'https://www.pcturing.cn:8443/' //所有API正式环境请求地址
// const urlHost = 'https://www.pcturing.cn:9443/' //所有API预上线环境请求地址
const request = (url, options) => {
return new Promise((resolve, reject) => {
wx.request({
url: `${urlHost}${url}`,
method: options.method,
data:
options.method === 'GET' ? options.data : JSON.stringify(options.data),
// header: {
// // 'Content-Type': 'application/json; charset=UTF-8',
// // // 'Authorization': `Bearer ${wx.getStorageSync("token")}`
// // 'Authorization': `token=eyJhbGciOi1111111JIUzUxMiJ9.eyJzdWIiOiIxNzYwODQ3NDg3OStmNGQ1ZjRkNWY0NXNkZjUiLCJqdGkiOiIwMzg0NWVjZmY0MTI0YzE3YjEyNDczMTgzNzM5Njg2MSIsImF1ZCI6Ik9SR0FOX0xPR0lOIiwiaWF0IjoxNjM1NDEzNTcxLCJleHAiOjE2MzU0MjA3NzF9.s864OVZJY2KeYqmhZ2hwdbyhnPIeyq6wr8Ik80uux6zvWZbY9QDUOZHPkKoaZwSeSymGpIkdVWhgM2U7gp8VlA`
// 'Authorization': `token=${wx.getStorageSync("token")}`,
// },
success(request) {
if (request.statusCode === 200) {
resolve(request)
} else if (request.statusCode === 201) {
resolve(request)
} else if (
request.statusCode == 502 ||
request.statusCode == 500 ||
request.statusCode == 503
) {
reject(request)
wx.showModal({
title: `服务器内部错误`,
content: '请稍后重试',
showCancel: false,
confirmText: '我知道了',
})
} else {
reject(request)
wx.showToast({
title: request.data.msg,
icon: 'none',
duration: 2000,
})
}
},
fail(error) {
// console.log('请求失败', error)
wx.showToast({
title: error,
icon: 'none',
duration: 3000,
})
reject(request)
},
})
})
}
const get = (url, option) => {
//下面是url的get请求以?号拼接
// return request(url, {
// method: 'GET',
// // data: options
// })
//下面是url的get请求以/号拼接
if (option) {
return request(url + option, {
method: 'GET',
})
} else {
return request(url, {
method: 'GET',
})
}
}
const post = (url, options) => {
return request(url, {
method: 'POST',
data: options,
})
}
module.exports = {
get,
post,
urlHost,
}
wxml2canvas使用
我们来看个demo,如下图,我的test文件夹下有个img文件夹,里面的test图片就是我的测试图片
我们的页面有文本有图片,需要将这些所有的内容合起来转为一张大图传给后端,这里需要注意的几个点是:wxml里canvas-id=“canvas1"一定要与new Wxml2Canvas方法里的element相对应,然后canvas的宽高设为100vw和100vh这样生成的图片不会变小了。根元素设置限定范围limit,比如我这里根元素class为my_canvas,那就应该是limit: ‘.my_canvas’,然后每个要单独绘制的元素class都有一个类名my_draw_canvas我们也要与之匹配。然后文本的话text要设置 data-type=“text” data-text=“我是pages/test/test.wxml页面”,其中data-text是文本内容。如果是图片的话,image标签要设置data-type=“image” data-url=”./img/test.jpg",其中data-url是图片地址。这里我用到了wx.getFileSystemManager().readFile将图片临时地址转为base64, 下面是我这个demo的源码:
//test.wxml
<view>
<view id="my_canvas" class="my_canvas box" >
<text class="my_draw_canvas tips" data-type="text" data-text="我是pages/test/test.wxml页面">我是pages/test/test.wxml页面</text>
<text class="my_draw_canvas explain" data-type="text" data-text="测试wxml2canvas转图片功能">测试wxml2canvas转图片功能</text>
<image class="my_draw_canvas img" data-type="image" data-url="./img/test.jpg" src="./img/test.jpg"></image>
</view>
<button type="primary" class="btn" bindtap="drawImage1">点我进行转换</button>
<view class="result">
<text>下面是用canvas生成的图片:</text>
<image class="resultImg" style="width:100vw;height:100vh" src="{{FinallySign}}" mode=""/>
</view>
</view>
<canvas canvas-id="canvas1" style="width:100vw;height:100vh"></canvas>
//test.wxss
.box{
text-align: center;
}
.box .img{
height: 100vh;
}
.box .tips{
display: inline-block;
width: 100%;
}
.btn{
width:60%;
margin:30rpx auto;
}
.result{
text-align: center;
}
.result text{
margin-bottom:50rpx;
}
.result .resultImg{
width:100%;
}
//test.js
// pages/test.js
import Wxml2Canvas from '../../src/index';
Page({
/**
* 页面的初始数据
*/
data: {
FinallySign:'',
imageWidth: '', //画在画布上的图片的宽度
imageHeight: '', //画在画布上的图片的高度
},
drawImage1 () {
let that = this;
let drawMyImage = new Wxml2Canvas({
// width: 340,
// height: 210,
width: that.data.imageWidth * 2 ,
height: that.data.imageHeight * 2 + 'px',
element: 'canvas1',
background: '#f0f0f0',
progress (percent) {
},
finish(url) {
console.log("生成的图片地址",url)
wx.getFileSystemManager().readFile({
filePath: url,
encoding: 'base64',
success: (res) => {
let MyImageBase64 = 'data:image/jpg;base64,' + res.data
console.log('MyImageBase64', MyImageBase64)
that.setData({
FinallySign: MyImageBase64,
})
},
})
},
error (res) {
console.log("生成的图片失败",res)
}
},this);
let data = {
list: [{
type: 'wxml',
class: '.my_canvas .my_draw_canvas', //.my_draw_canvas每个要绘制元素的类名
limit: '.my_canvas', //my_canvas根元素类名
x: 0,
y: 0
}]
}
drawMyImage.draw(data,that);
},
/**
* 生命周期函数--监听页面显示
*/
onShow() {
const that = this
const query = wx.createSelectorQuery().in(this)
query
.select('#my_canvas')
.fields(
{
// 选择需要生成canvas的范围
size: true,
node: true,
scrollOffset: true,
},
(data) => {
let width = data.width
let height = data.height
that.setData({
imageWidth: width,
imageHeight: height,
})
},
)
.exec()
}
})
下面附上github地址: