2025-06-30 10:21:25 +08:00
|
|
|
|
<template>
|
|
|
|
|
|
<view style="width: 100%;height: 100%;position: relative;">
|
|
|
|
|
|
<zero-loading v-show="loading && !noloading" position="absolute" v-if="loading && !noloading"></zero-loading>
|
|
|
|
|
|
<canvas :id="cId" type="2d" :style="{width:width,height:height}" :canvas-id="cId"></canvas>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<script>
|
|
|
|
|
|
export default {
|
|
|
|
|
|
data() {
|
|
|
|
|
|
return {
|
|
|
|
|
|
windowWidth: 0,
|
|
|
|
|
|
type: 'app',
|
|
|
|
|
|
ctx: null,
|
|
|
|
|
|
textArr: [],
|
|
|
|
|
|
imgArr: [],
|
|
|
|
|
|
lineArr: [],
|
|
|
|
|
|
circleArr: [],
|
|
|
|
|
|
rectArr: [],
|
|
|
|
|
|
loading: true,
|
|
|
|
|
|
canvasWidth: null,
|
|
|
|
|
|
canvasHeight: null
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
props: {
|
|
|
|
|
|
cId: {
|
|
|
|
|
|
type: [String, Number],
|
|
|
|
|
|
default: 'canvas'
|
|
|
|
|
|
},
|
|
|
|
|
|
width: {
|
|
|
|
|
|
type: [String, Number],
|
|
|
|
|
|
default: '100%'
|
|
|
|
|
|
},
|
|
|
|
|
|
height: {
|
|
|
|
|
|
type: [String, Number],
|
|
|
|
|
|
default: '100%'
|
|
|
|
|
|
},
|
|
|
|
|
|
canvasData: {
|
|
|
|
|
|
type: Array,
|
|
|
|
|
|
default: () => {
|
|
|
|
|
|
return []
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
noloading: {
|
|
|
|
|
|
type: Boolean,
|
|
|
|
|
|
default: false
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
watch: {
|
|
|
|
|
|
canvasData: {
|
|
|
|
|
|
handler(val) {
|
|
|
|
|
|
if (val && val.length) {
|
|
|
|
|
|
this.getSystemInfo()
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
deep: true,
|
|
|
|
|
|
immediate: true
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
mounted() {
|
|
|
|
|
|
// this.getSystemInfo()
|
|
|
|
|
|
},
|
|
|
|
|
|
methods: {
|
|
|
|
|
|
getSystemInfo() {
|
|
|
|
|
|
const self = this
|
|
|
|
|
|
uni.getSystemInfo({
|
|
|
|
|
|
success: function(res) {
|
2025-10-10 09:27:34 +08:00
|
|
|
|
// 获取屏幕尺寸
|
|
|
|
|
|
self.windowWidth = res.windowWidth
|
2025-06-30 10:21:25 +08:00
|
|
|
|
// const platform = res.hostName.toLowerCase()
|
|
|
|
|
|
if (res.uniPlatform || res.uniPlatform === 'app') {
|
|
|
|
|
|
self.type = 'app'
|
|
|
|
|
|
//获取app的canvas的dom
|
|
|
|
|
|
self.getAppDom()
|
|
|
|
|
|
} else {
|
|
|
|
|
|
self.type = 'wx'
|
|
|
|
|
|
//获取小程序的canvas的dom
|
|
|
|
|
|
self.getWXDom()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
},
|
|
|
|
|
|
clear() {
|
2025-10-10 09:27:34 +08:00
|
|
|
|
// if (this.type === 'wx') {
|
|
|
|
|
|
// this.ctx.globalCompositeOperation = 'destination-out';
|
|
|
|
|
|
// this.ctx.beginPath();
|
|
|
|
|
|
// this.ctx.fillStyle = 'red';
|
|
|
|
|
|
// this.ctx.fillRect(0, 0, 8000, 80000);
|
|
|
|
|
|
// this.ctx.fill();
|
|
|
|
|
|
// this.ctx.globalCompositeOperation = 'source-over';
|
|
|
|
|
|
// // this.ctx.draw();
|
|
|
|
|
|
// } else {
|
|
|
|
|
|
// // this.ctx.clearRect(2000, 2000, 2000, 2000);
|
|
|
|
|
|
// // this.ctx.fillStyle = '#ffffff'
|
|
|
|
|
|
// // this.ctx.draw(true);
|
|
|
|
|
|
// }
|
2025-06-30 10:21:25 +08:00
|
|
|
|
if (this.type === 'wx') {
|
2025-10-10 09:27:34 +08:00
|
|
|
|
this.ctx.clearRect(0, 0, this.canvasWidth, this.canvasHeight);
|
2025-06-30 10:21:25 +08:00
|
|
|
|
} else {
|
2025-10-10 09:27:34 +08:00
|
|
|
|
// App 平台
|
|
|
|
|
|
this.ctx.clearRect(0, 0, 8000, 8000);
|
2025-06-30 10:21:25 +08:00
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
//获取app的canvas DOM
|
|
|
|
|
|
getAppDom() {
|
|
|
|
|
|
var context = uni.createCanvasContext(`${this.cId}`)
|
|
|
|
|
|
this.ctx = context
|
|
|
|
|
|
this.draw(null)
|
|
|
|
|
|
},
|
|
|
|
|
|
//获取微信的canvas DOM
|
|
|
|
|
|
getWXDom() {
|
|
|
|
|
|
// const instance = getCurrentInstance()
|
|
|
|
|
|
let query = wx.createSelectorQuery().in(this)
|
|
|
|
|
|
query
|
|
|
|
|
|
.select(`#${this.cId}`)
|
|
|
|
|
|
.fields({
|
|
|
|
|
|
node: true,
|
|
|
|
|
|
size: true
|
|
|
|
|
|
}, () => {})
|
|
|
|
|
|
.exec(this.initCanvas.bind(this))
|
|
|
|
|
|
},
|
|
|
|
|
|
initCanvas(res) {
|
|
|
|
|
|
|
|
|
|
|
|
const width = res[0].width
|
|
|
|
|
|
const height = res[0].height
|
|
|
|
|
|
const canvas = res[0].node
|
|
|
|
|
|
this.ctx = canvas.getContext('2d')
|
|
|
|
|
|
|
|
|
|
|
|
const dpr = wx.getSystemInfoSync().pixelRatio
|
|
|
|
|
|
canvas.width = width * dpr
|
|
|
|
|
|
canvas.height = height * dpr
|
|
|
|
|
|
this.canvasWidth = canvas.width
|
|
|
|
|
|
this.canvasHeight = canvas.height
|
|
|
|
|
|
this.ctx.scale(dpr, dpr)
|
|
|
|
|
|
this.draw(canvas)
|
|
|
|
|
|
},
|
|
|
|
|
|
draw(canvas) {
|
|
|
|
|
|
this.textArr = []
|
|
|
|
|
|
this.circleArr = []
|
|
|
|
|
|
this.rectArr = []
|
|
|
|
|
|
this.imgArr = []
|
|
|
|
|
|
this.lineArr = []
|
|
|
|
|
|
this.canvasData.forEach((item) => {
|
|
|
|
|
|
if (item.type === 'text') {
|
|
|
|
|
|
this.textArr.push(item)
|
|
|
|
|
|
}
|
|
|
|
|
|
if (item.type === 'image') {
|
|
|
|
|
|
this.imgArr.push(item)
|
|
|
|
|
|
}
|
|
|
|
|
|
if (item.type === 'line') {
|
|
|
|
|
|
this.lineArr.push(item)
|
|
|
|
|
|
}
|
|
|
|
|
|
if (item.type === 'circle') {
|
|
|
|
|
|
this.circleArr.push(item)
|
|
|
|
|
|
}
|
|
|
|
|
|
if (item.type === 'rect') {
|
|
|
|
|
|
this.rectArr.push(item)
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
if (this.imgArr.length) {
|
|
|
|
|
|
this.drawImages(this.imgArr, canvas ? canvas : null)
|
|
|
|
|
|
}
|
|
|
|
|
|
if (this.lineArr.length) {
|
|
|
|
|
|
this.drawLine(this.lineArr)
|
|
|
|
|
|
}
|
|
|
|
|
|
if (this.circleArr.length) {
|
|
|
|
|
|
this.drawCircle(this.circleArr)
|
|
|
|
|
|
}
|
|
|
|
|
|
if (this.rectArr.length) {
|
|
|
|
|
|
this.drawRect(this.rectArr)
|
|
|
|
|
|
}
|
|
|
|
|
|
if (this.textArr.length) {
|
|
|
|
|
|
this.drawText(this.textArr)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// this.drawLine(this.lineArr)
|
|
|
|
|
|
// this.drawCircle(this.circleArr)
|
|
|
|
|
|
// this.drawRect(this.rectArr)
|
|
|
|
|
|
// this.drawText(this.textArr)
|
|
|
|
|
|
if (this.type === 'app') {
|
|
|
|
|
|
this.ctx.draw()
|
|
|
|
|
|
}
|
|
|
|
|
|
this.loading = false
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
drawRect(draw) {
|
|
|
|
|
|
draw.forEach((item, index) => {
|
|
|
|
|
|
let x = this.fitSize(item.coord[0][0])
|
|
|
|
|
|
let y = this.fitSize(item.coord[0][1])
|
|
|
|
|
|
let w = this.fitSize(item.coord[1][0])
|
|
|
|
|
|
let h = this.fitSize(item.coord[1][1])
|
|
|
|
|
|
this.ctx.beginPath();
|
|
|
|
|
|
if (this.type === 'app') {
|
|
|
|
|
|
if (item.rectType === 'stroke') {
|
|
|
|
|
|
this.ctx.setStrokeStyle(item.borderColor); // 设置边框颜色
|
|
|
|
|
|
this.ctx.setLineWidth(item.width); // 设置边框宽度
|
|
|
|
|
|
this.ctx.strokeRect(x, y, w, h); // 绘制无填充矩形
|
|
|
|
|
|
} else {
|
|
|
|
|
|
this.ctx.setFillStyle(item.background || 'transparent');
|
|
|
|
|
|
this.ctx.fillRect(x, y, w, h);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
if (item.rectType === 'stroke') {
|
|
|
|
|
|
this.ctx.strokeStyle = item.borderColor
|
|
|
|
|
|
this.ctx.lineWidth = item.width
|
|
|
|
|
|
this.ctx.strokeRect(x, y, w, h);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
this.ctx.fillStyle = item.background || 'transparent'
|
|
|
|
|
|
this.ctx.fillRect(x, y, w, h);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
countChineseAndEnglishCharacters(str, x) {
|
2025-10-10 09:27:34 +08:00
|
|
|
|
var chineseCount = str.match(/[\u4e00-\u9fa5]/g) ?
|
|
|
|
|
|
str.match(/[\u4e00-\u9fa5]/g).length :
|
|
|
|
|
|
0;
|
|
|
|
|
|
var englishCount = str.match(/[a-zA-Z]/g) ?
|
|
|
|
|
|
str.match(/[a-zA-Z]/g).length :
|
|
|
|
|
|
0;
|
|
|
|
|
|
var otherCount = str.length - chineseCount - englishCount;
|
|
|
|
|
|
const obj = {
|
|
|
|
|
|
otherCount: otherCount,
|
|
|
|
|
|
chineseCount: chineseCount,
|
|
|
|
|
|
englishCount: englishCount,
|
|
|
|
|
|
};
|
|
|
|
|
|
return (
|
|
|
|
|
|
obj.englishCount * 6 + obj.chineseCount * 12 + obj.otherCount * 7.5 + x
|
|
|
|
|
|
);
|
|
|
|
|
|
},
|
2025-06-30 10:21:25 +08:00
|
|
|
|
|
|
|
|
|
|
drawText(draw) {
|
|
|
|
|
|
draw.forEach((item, index) => {
|
|
|
|
|
|
if (item.font.length) {
|
|
|
|
|
|
if (item.font.length > 1) {
|
|
|
|
|
|
item.font.forEach((item2, index2) => {
|
|
|
|
|
|
if (this.type === 'app') {
|
|
|
|
|
|
this.ctx.setFontSize(item2.size)
|
|
|
|
|
|
} else {
|
|
|
|
|
|
this.ctx.fontSize = item2.size
|
|
|
|
|
|
}
|
|
|
|
|
|
this.ctx.fillStyle = item2.color
|
2025-10-10 09:27:34 +08:00
|
|
|
|
const x = this.countChineseAndEnglishCharacters(item.font[0].text, item
|
|
|
|
|
|
.coord[0][0])
|
|
|
|
|
|
this.ctx.fillText(item2.text, this.fitSize(index2 === 1 ? x : item.coord[0]
|
|
|
|
|
|
[0]), this.fitSize(item.coord[0][1]))
|
2025-06-30 10:21:25 +08:00
|
|
|
|
this.ctx.closePath()
|
|
|
|
|
|
})
|
|
|
|
|
|
} else {
|
|
|
|
|
|
if (this.type === 'app') {
|
|
|
|
|
|
this.ctx.setFontSize(item.font[0].size)
|
|
|
|
|
|
} else {
|
|
|
|
|
|
this.ctx.fontSize = item.font[0].size
|
|
|
|
|
|
}
|
|
|
|
|
|
this.ctx.fillStyle = item.font[0].color
|
|
|
|
|
|
this.ctx.fillText(item.font[0].text, this.fitSize(item.coord[0][0]), this.fitSize(item
|
|
|
|
|
|
.coord[0][1]))
|
|
|
|
|
|
this.ctx.closePath()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
drawImages(draw, canvas) {
|
|
|
|
|
|
let x,
|
|
|
|
|
|
y,
|
|
|
|
|
|
w,
|
|
|
|
|
|
h = 0
|
|
|
|
|
|
|
|
|
|
|
|
draw.forEach((item) => {
|
|
|
|
|
|
if (item.coord.length >= 2) x = item.coord[0][0]
|
|
|
|
|
|
y = item.coord[0][1]
|
|
|
|
|
|
w = item.coord[1][0]
|
|
|
|
|
|
h = item.coord[1][1]
|
|
|
|
|
|
if (this.type === 'wx') {
|
|
|
|
|
|
let img = canvas.createImage()
|
|
|
|
|
|
img.src = item.url
|
|
|
|
|
|
img.onload = () => {
|
|
|
|
|
|
this.ctx.drawImage(
|
|
|
|
|
|
img,
|
|
|
|
|
|
this.fitSize(item.coord[0][0]),
|
|
|
|
|
|
this.fitSize(item.coord[0][1]),
|
|
|
|
|
|
this.fitSize(item.coord[1][0]),
|
|
|
|
|
|
this.fitSize(item.coord[1][1])
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
this.drawLine(this.lineArr)
|
|
|
|
|
|
this.drawCircle(this.circleArr)
|
|
|
|
|
|
this.drawRect(this.rectArr)
|
|
|
|
|
|
this.drawText(this.textArr)
|
|
|
|
|
|
if (this.type === 'app') {
|
|
|
|
|
|
this.ctx.draw()
|
|
|
|
|
|
}
|
|
|
|
|
|
this.loading = false
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
this.ctx.drawImage(item.url, this.fitSize(x), this.fitSize(y), this.fitSize(w),
|
|
|
|
|
|
this
|
|
|
|
|
|
.fitSize(h))
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
},
|
|
|
|
|
|
drawLine(draw) {
|
|
|
|
|
|
draw.forEach((item) => {
|
|
|
|
|
|
let x1 = this.fitSize(item.coord[0][0])
|
|
|
|
|
|
let y1 = this.fitSize(item.coord[0][1])
|
|
|
|
|
|
let x2 = this.fitSize(item.coord[1][0])
|
|
|
|
|
|
let y2 = this.fitSize(item.coord[1][1])
|
|
|
|
|
|
this.ctx.beginPath()
|
|
|
|
|
|
this.ctx.moveTo(x1, y1)
|
|
|
|
|
|
this.ctx.lineTo(x2, y2)
|
|
|
|
|
|
if (this.type === 'app') {
|
|
|
|
|
|
this.ctx.setStrokeStyle(item.color)
|
|
|
|
|
|
this.ctx.setLineWidth(item.width)
|
|
|
|
|
|
} else {
|
|
|
|
|
|
this.ctx.strokeStyle = item.color
|
|
|
|
|
|
this.ctx.lineWidth = item.width
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (item.dash && item.dash.length > 0) {
|
|
|
|
|
|
//虚线
|
|
|
|
|
|
this.ctx.setLineDash(item.dash)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
this.ctx.stroke()
|
|
|
|
|
|
this.ctx.closePath()
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
drawCircle(draw) {
|
|
|
|
|
|
draw.forEach((item) => {
|
|
|
|
|
|
let x = this.fitSize(item.coord[0][0])
|
|
|
|
|
|
let y = this.fitSize(item.coord[0][1])
|
|
|
|
|
|
let r = this.fitSize(item.r)
|
|
|
|
|
|
this.ctx.beginPath()
|
|
|
|
|
|
this.ctx.arc(x, y, r, 0, Math.PI * 2, false)
|
|
|
|
|
|
this.ctx.fillStyle = item.color
|
|
|
|
|
|
this.ctx.fill()
|
|
|
|
|
|
this.ctx.closePath()
|
|
|
|
|
|
})
|
|
|
|
|
|
},
|
|
|
|
|
|
fitSize(coordinate) {
|
|
|
|
|
|
let newWindowWidth = this.windowWidth //这个windowWidth指的是该设备宽度,可以onLoad监听页面加载中获取
|
|
|
|
|
|
let v = 375 / newWindowWidth //375是设计稿的大小,得到的v值是:设计稿和设备宽度的比例关系,也可理解成在设计稿的大小基础上放大或缩小的倍数
|
|
|
|
|
|
return coordinate / v //返回的是当前坐标值或者大小与v的比例
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
<style>
|
|
|
|
|
|
</style>
|