Files
smart_storage_app/components/new-canvas/index.vue

388 lines
10 KiB
Vue
Raw Permalink Normal View History

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) {
2025-12-26 11:26:40 +08:00
// 等待 windowWidth 初始化完成后再绘制
if (this.windowWidth) {
this.draw(null); // 直接绘制
} else {
// 若 windowWidth 未获取,先获取再绘制
this.getSystemInfo().then(() => {
this.draw(null);
});
2025-06-30 10:21:25 +08:00
}
},
deep: true,
2025-12-26 11:26:40 +08:00
immediate: false
2025-06-30 10:21:25 +08:00
}
},
mounted() {
// this.getSystemInfo()
},
methods: {
getSystemInfo() {
const self = this
uni.getSystemInfo({
success: function(res) {
// 获取屏幕尺寸
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() {
// 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') {
this.ctx.clearRect(0, 0, this.canvasWidth, this.canvasHeight);
2025-06-30 10:21:25 +08:00
} else {
// 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);
}
}
})
},
2025-12-26 11:26:40 +08:00
// countChineseAndEnglishCharacters(str, x) {
// 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) {
2025-12-26 11:26:40 +08:00
draw.forEach((item, index) => {
if (item.font.length) {
// 记录前一个文本的结束位置(用于后续文本偏移)
let prevTextEndX = this.fitSize(item.coord[0][0]);
item.font.forEach((item2, index2) => {
// 设置字体大小和颜色
if (this.type === 'app') {
this.ctx.setFontSize(item2.size);
} else {
this.ctx.font = `${item2.size}px sans-serif`; // 小程序端补全font属性
}
this.ctx.fillStyle = item2.color;
// 计算当前文本的绘制X坐标
let currentX = prevTextEndX;
// 非第一个文本加上配置的偏移量默认8px
if (index2 > 0) {
const offsetX = item2.offsetX || 6;
currentX += this.fitSize(offsetX); // 适配设备尺寸的偏移量
}
// 绘制文本
this.ctx.fillText(
item2.text,
currentX,
this.fitSize(item.coord[0][1])
);
// 更新前一个文本的结束位置当前文本的X + 文本宽度)
const textWidth = this.getTextWidth(item2.text, item2.size);
prevTextEndX = currentX + textWidth;
this.ctx.closePath();
});
}
});
},
// 新增计算文本的实际绘制宽度适配app/小程序)
getTextWidth(text, fontSize) {
if (this.type === 'app') {
// App端通过measureText计算
return this.ctx.measureText(text).width;
} else {
// 小程序端手动计算(更稳定)
this.ctx.font = `${fontSize}px sans-serif`;
return this.ctx.measureText(text).width;
}
2025-06-30 10:21:25 +08:00
},
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) {
2025-12-26 11:26:40 +08:00
let newWindowWidth = this.windowWidth;
const designWidth = 375; // 设计稿宽度
// 修正比例计算:设备宽度 / 设计稿宽度 = 缩放比例
const scale = newWindowWidth / designWidth;
// 设计稿的坐标 * 缩放比例 = 设备实际坐标
return coordinate * scale;
2025-06-30 10:21:25 +08:00
}
},
}
</script>
<style>
</style>