|
|
@@ -0,0 +1,280 @@
|
|
|
+
|
|
|
+import { DeviceTypeEnum } from '@/enum/common'
|
|
|
+import { useEmitter, useId } from '@/hooks'
|
|
|
+import { Ref } from 'vue'
|
|
|
+
|
|
|
+interface DrawData {
|
|
|
+ id?: string,
|
|
|
+ Points: {x: number, y: number}[],
|
|
|
+ ctxStyle?: any,
|
|
|
+ isLineHeight: boolean
|
|
|
+ name: string
|
|
|
+ hidden: boolean
|
|
|
+ [key: string]: any
|
|
|
+}
|
|
|
+
|
|
|
+interface CtxProperty {
|
|
|
+ lineWidth: number,
|
|
|
+ fillStyle: string,
|
|
|
+ strokeStyle: string,
|
|
|
+ lineCap: string,
|
|
|
+ lineJoin: string,
|
|
|
+}
|
|
|
+
|
|
|
+export enum DrawTypeEnum {
|
|
|
+ 'rect' = 0,
|
|
|
+ 'line' = 2
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * 绘制热区的步骤
|
|
|
+ * 1. 点击canvas之后,创建一个a点, 从a点到鼠标一直有一条唯一的线段,点会一直跟随鼠标
|
|
|
+ * 2. 再次点击创建一个b点, a点与b点之间连接一条线段, 然后b点有一条线跟随鼠标移动
|
|
|
+ * 3. 重复1 、2 步骤形成一个区域,然后双击结束绘制,同时弹出一个框,输入这个区域的名字,点击确定结束
|
|
|
+ *
|
|
|
+ *
|
|
|
+ * 支持对区域的 删除 高亮
|
|
|
+ * 绘画的drawRecords 从外部传入
|
|
|
+ */
|
|
|
+
|
|
|
+export class DrawController {
|
|
|
+ el: HTMLCanvasElement
|
|
|
+ ctx: CanvasRenderingContext2D
|
|
|
+ isDrawing: boolean // 是否正在绘画状态
|
|
|
+ isStarting = false // 是否开始绘画
|
|
|
+ drawRecord: DrawData[] = [] // 绘画的全部数据
|
|
|
+ drawType: DrawTypeEnum = DrawTypeEnum.rect // 绘制的图形有两种类型 0 是绘制区域, 2 是绘制线段
|
|
|
+ drawEndCallback: () => void = () => {}
|
|
|
+ ctxProperty: CtxProperty | null = null
|
|
|
+ bgUrl = null
|
|
|
+
|
|
|
+ private mouseupCount = 0
|
|
|
+
|
|
|
+ constructor (
|
|
|
+ el: Ref<HTMLCanvasElement>
|
|
|
+ ) {
|
|
|
+ console.log('初始化drawController')
|
|
|
+
|
|
|
+ this.el = el.value
|
|
|
+ this.ctx = el.value.getContext('2d')!
|
|
|
+ this.isDrawing = false
|
|
|
+ this.el.addEventListener('mousedown', (event: MouseEvent) => this.mousedown(event))
|
|
|
+ this.el.addEventListener('mousemove', (event: MouseEvent) => this.mousemove(event))
|
|
|
+ this.el.addEventListener('mouseup', (event: MouseEvent) => this.mouseup(event))
|
|
|
+ this.el.addEventListener('dblclick', (event: MouseEvent) => this.dblclick(event))
|
|
|
+ }
|
|
|
+
|
|
|
+ getDrawRecordLast () {
|
|
|
+ return this.drawRecord[this.drawRecord.length - 1]
|
|
|
+ }
|
|
|
+
|
|
|
+ getDrawRecord () {
|
|
|
+ return this.drawRecord
|
|
|
+ }
|
|
|
+
|
|
|
+ setDrawRecord (records) {
|
|
|
+ this.drawRecord = records
|
|
|
+ this.clearCanvar()
|
|
|
+ if (records.length !== 0) this.drawRectByDrawData(records)
|
|
|
+ }
|
|
|
+
|
|
|
+ setProperty (records: CtxProperty) {
|
|
|
+ this.ctxProperty = records
|
|
|
+ }
|
|
|
+
|
|
|
+ get drawRecordsLen () {
|
|
|
+ return this.getDrawRecord().length
|
|
|
+ }
|
|
|
+
|
|
|
+ start () {
|
|
|
+ this.isStarting = true
|
|
|
+ }
|
|
|
+
|
|
|
+ // 取消绘制
|
|
|
+ cacelDraw () {
|
|
|
+ this.isStarting = false
|
|
|
+ this.isDrawing = false
|
|
|
+ this.mouseupCount = 0
|
|
|
+ }
|
|
|
+
|
|
|
+ // 传入绘制结束的回调
|
|
|
+ drawEnd (cb : () => void) {
|
|
|
+ this.drawEndCallback = cb
|
|
|
+ }
|
|
|
+
|
|
|
+ // 设置每次绘制的类型 0 是区域 2 是线段
|
|
|
+ setDrawType (type: DrawTypeEnum) {
|
|
|
+ this.drawType = type
|
|
|
+ }
|
|
|
+
|
|
|
+ // 隐藏绘制的线段
|
|
|
+ hiddenDrawRecordsByIndex (index: number) {
|
|
|
+ this.clearCanvar()
|
|
|
+ this.drawRecord[index].hidden = true
|
|
|
+ this.drawRectByDrawData()
|
|
|
+ }
|
|
|
+
|
|
|
+ // 这里直接传入 index即可
|
|
|
+ showDrawRecordsByIndex (index: number) {
|
|
|
+ this.clearCanvar()
|
|
|
+ this.drawRecord[index].hidden = false
|
|
|
+ this.drawRectByDrawData()
|
|
|
+ }
|
|
|
+
|
|
|
+ // 高亮
|
|
|
+ showLineHieght (index: number) {
|
|
|
+ this.clearCanvar()
|
|
|
+ this.drawRecord[index].isLineHeight = true
|
|
|
+ this.drawRectByDrawData()
|
|
|
+ }
|
|
|
+
|
|
|
+ // 取消高亮
|
|
|
+ hiddenlLineHieght (index: number) {
|
|
|
+ this.clearCanvar()
|
|
|
+ this.drawRecord[index].isLineHeight = false
|
|
|
+ this.drawRectByDrawData()
|
|
|
+ }
|
|
|
+
|
|
|
+ // 删除
|
|
|
+ delLine (id: string) {
|
|
|
+ this.clearCanvar()
|
|
|
+ const index = this.drawRecord.findIndex(item => item.id === id)
|
|
|
+ this.drawRecord.splice(index, 1)
|
|
|
+ this.drawRectByDrawData()
|
|
|
+ }
|
|
|
+
|
|
|
+ // 缩放
|
|
|
+ scale (scaleX, scaleY) {
|
|
|
+ this.ctx.scale(scaleX, scaleY)
|
|
|
+ }
|
|
|
+
|
|
|
+ // 设置背景图片
|
|
|
+ setBgImage (url: CanvasImageSource) {
|
|
|
+ this.bgUrl = url as any
|
|
|
+ }
|
|
|
+
|
|
|
+ setDrawImage () {
|
|
|
+ if (this.bgUrl) {
|
|
|
+ this.ctx.drawImage(this.bgUrl as unknown as CanvasImageSource, 0, 0, this.el.width, this.el.height)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 清空画布
|
|
|
+ clearCanvar () {
|
|
|
+ this.ctx.clearRect(0, 0, this.el.width, this.el.height)
|
|
|
+ this.setDrawImage()
|
|
|
+ }
|
|
|
+
|
|
|
+ // 设置ctx的一些绘制属性
|
|
|
+ private setCtxProperty (records: CtxProperty) {
|
|
|
+ for (const key in records) {
|
|
|
+ this.ctx[key] = records[key]
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private mousedown (event: MouseEvent) {
|
|
|
+ if (!this.isStarting) return
|
|
|
+ const canvasRect = this.el.getBoundingClientRect()
|
|
|
+ const x = event.clientX - canvasRect.left
|
|
|
+ const y = event.clientY - canvasRect.top
|
|
|
+ this.createDraw(x, y)
|
|
|
+
|
|
|
+ this.isDrawing = true
|
|
|
+ }
|
|
|
+
|
|
|
+ private mousemove (event: MouseEvent) {
|
|
|
+ if (!this.isDrawing) return ''
|
|
|
+ const canvasRect = this.el.getBoundingClientRect()
|
|
|
+ const x = event.clientX - canvasRect.left
|
|
|
+ const y = event.clientY - canvasRect.top
|
|
|
+
|
|
|
+ const lastRecords = this.drawRecord[this.drawRecord.length - 1]
|
|
|
+
|
|
|
+ this.clearCanvar()
|
|
|
+
|
|
|
+ this.drawRectByDrawData(this.drawRecord.slice(0, this.drawRecord.length - 1))
|
|
|
+ this.setCtxProperty(this.ctxProperty!)
|
|
|
+ this.ctx.beginPath()
|
|
|
+
|
|
|
+ this.ctx.moveTo(lastRecords.Points[0].x, lastRecords.Points[0].y)
|
|
|
+
|
|
|
+ lastRecords.Points.slice(1).forEach(point => {
|
|
|
+ this.ctx.lineTo(point.x, point.y)
|
|
|
+ })
|
|
|
+
|
|
|
+ this.ctx.lineTo(x, y)
|
|
|
+
|
|
|
+ this.ctx.lineTo(lastRecords.Points[0].x, lastRecords.Points[0].y)
|
|
|
+
|
|
|
+ this.ctx.stroke()
|
|
|
+
|
|
|
+ this.ctx.fill()
|
|
|
+ }
|
|
|
+
|
|
|
+ private mouseup (event: MouseEvent) {
|
|
|
+ this.mouseupCount++
|
|
|
+ if (this.drawType === DrawTypeEnum.line && this.mouseupCount === 2) {
|
|
|
+ this.handleDrawed()
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 双击代表本次绘画结束
|
|
|
+ private dblclick (event: MouseEvent) {
|
|
|
+ if (this.drawType === DrawTypeEnum.rect && this.isStarting) {
|
|
|
+ this.handleDrawed()
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 绘制结束
|
|
|
+ private handleDrawed () {
|
|
|
+ this.isStarting = false
|
|
|
+ this.isDrawing = false
|
|
|
+ this.mouseupCount = 0
|
|
|
+ this.drawEndCallback()
|
|
|
+ }
|
|
|
+
|
|
|
+ // 创建每次绘制图形的基本的数据结构
|
|
|
+ private createDraw (x: number, y: number) {
|
|
|
+ if (this.isDrawing) {
|
|
|
+ this.drawRecord[this.drawRecord.length - 1].Points.push({ x, y })
|
|
|
+ } else {
|
|
|
+ const drawItem = {
|
|
|
+ id: useId(),
|
|
|
+ Points: [{ x, y }],
|
|
|
+ ctxStyle: this.ctxProperty,
|
|
|
+ isLineHeight: false,
|
|
|
+ name: '',
|
|
|
+ hidden: false
|
|
|
+ }
|
|
|
+ this.drawRecord.push(drawItem)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 根据 传入的drawData来绘制
|
|
|
+ drawRectByDrawData (records?: DrawData[]) {
|
|
|
+ const _records = records || this.drawRecord
|
|
|
+ _records.forEach(item => {
|
|
|
+ if (item.hidden) return
|
|
|
+ this.setCtxProperty(item.ctxStyle)
|
|
|
+ if (item.isLineHeight) {
|
|
|
+ this.ctx.strokeStyle = 'rgba(255, 215, 0, 1)'
|
|
|
+ this.ctx.setLineDash([10, 10])
|
|
|
+ } else {
|
|
|
+ this.ctx.setLineDash([])
|
|
|
+ }
|
|
|
+ this.ctx.beginPath()
|
|
|
+
|
|
|
+ this.ctx.moveTo(item.Points[0].x, item.Points[0].y) // 一个矩形的起始点
|
|
|
+ item.Points.slice(1).forEach(point => this.ctx.lineTo(point.x, point.y)) // 绘制中间路径
|
|
|
+ this.ctx.lineTo(item.Points[0].x, item.Points[0].y) // 最后一个点连接到起始点
|
|
|
+ this.ctx.fill()
|
|
|
+ // 根据绘制图形的中间的点, 与第一个y点,来确定绘制的图形的名字渲染的地方
|
|
|
+ const pointX = item.Points.map(point => point.x).sort((a, b) => a - b)
|
|
|
+ const pointY = item.Points.map(point => point.y).sort((a, b) => a - b)
|
|
|
+ const midX = (pointX[0] + pointX[pointX.length - 1]) / 2
|
|
|
+ this.ctx.font = '20px Arial'
|
|
|
+ this.ctx.fillText(item.name, midX, pointY[0] - 20)
|
|
|
+ this.ctx.stroke()
|
|
|
+ })
|
|
|
+ }
|
|
|
+}
|