|
|
@@ -0,0 +1,674 @@
|
|
|
+<template>
|
|
|
+ <div class="canvas-container" ref="canvasContainer">
|
|
|
+ <div
|
|
|
+ class="canvas float-left"
|
|
|
+ ref="yfcontainer"
|
|
|
+ v-loading="loading"
|
|
|
+ >
|
|
|
+ <!-- 画布 -->
|
|
|
+ <canvas
|
|
|
+ :style="{width: canvasSize.width + 'px', height: canvasSize.height + 'px'}"
|
|
|
+ canvas-id="yfCanvas"
|
|
|
+ id="yfCanvas"
|
|
|
+ ></canvas>
|
|
|
+ <!-- 文本标签 -->
|
|
|
+ <!-- <div class="element-layer">
|
|
|
+ <span
|
|
|
+ class="label-link cursor"
|
|
|
+ v-for="tag in rectangles"
|
|
|
+ @click="deleteSpan($event, tag.id)"
|
|
|
+ :key="tag.id"
|
|
|
+ :style="linkStyle(tag)"
|
|
|
+ >
|
|
|
+ <span class="delete" @click.stop="deleteSpan($event, tag.id)">✖</span>
|
|
|
+ </span>
|
|
|
+ </div> -->
|
|
|
+ </div>
|
|
|
+
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script>
|
|
|
+import Panel from './comps/panel.vue'
|
|
|
+import Tools from './comps/tools.vue'
|
|
|
+import Annotation from './comps/Annotation.vue'
|
|
|
+let myRatio = 1
|
|
|
+export default {
|
|
|
+ props: {
|
|
|
+ markItem: {
|
|
|
+ type: Object,
|
|
|
+ default: () => ({
|
|
|
+ addr: 'https://bigdata-image.oss-cn-hangzhou.aliyuncs.com/Basics/cbd/666000000000001/2025/7/11/001.jpg',
|
|
|
+ mark:[],
|
|
|
+ is_mark:0,
|
|
|
+ label: "[{'71': [480, 47, 520, 91, 1.0]}, {'71': [513, 102, 545, 155, 1.0]}, {'71': [690, 238, 740, 272, 1.0]}, {'71': [298, 471, 335, 514, 1.0]}, {'71': [250, 379, 300, 407, 1.0]}, {'71': [106, 40, 143, 89, 1.0]}, {'71': [46, 12, 124, 58, 0.99]}, {'71': [2, 90, 41, 133, 0.98]}, {'71': [767, 189, 799, 237, 0.98]}, {'71': [721, 175, 758, 216, 0.92]}, {'71': [685, 132, 727, 166, 0.35]}]",
|
|
|
+ })
|
|
|
+ },
|
|
|
+ isInitFullScreen: {
|
|
|
+ type: Boolean,
|
|
|
+ default: false
|
|
|
+ },
|
|
|
+ reIdentify: {
|
|
|
+ type: Boolean,
|
|
|
+ default: true
|
|
|
+ },
|
|
|
+ title: {
|
|
|
+ type: String,
|
|
|
+ default: '目录'
|
|
|
+ },
|
|
|
+
|
|
|
+ typeName: {
|
|
|
+ type: Number,
|
|
|
+ default: 1 // 1: 测报灯 2:病虫害可视监测 3:吸虫塔 4:孢子仪
|
|
|
+ }
|
|
|
+ },
|
|
|
+ watch: {
|
|
|
+ markItem: {
|
|
|
+ handler(newVal) {
|
|
|
+ console.log('markItem变化:', newVal, this.ctx)
|
|
|
+ // 当markItem变化时,重新加载图片和标注
|
|
|
+ if (!newVal.addr && this.ctx) {
|
|
|
+ this.clearCanvas()
|
|
|
+ return
|
|
|
+ }
|
|
|
+ if (this.ctx) {
|
|
|
+ this.loadImage()
|
|
|
+ }
|
|
|
+ },
|
|
|
+ deep: true
|
|
|
+ }
|
|
|
+ },
|
|
|
+ components: {
|
|
|
+ Panel,
|
|
|
+ Tools,
|
|
|
+ Annotation
|
|
|
+ },
|
|
|
+ name: 'Draw',
|
|
|
+ computed: {
|
|
|
+ inputStyle() {
|
|
|
+ return {
|
|
|
+ left: `${this.inputPosition.x}px`,
|
|
|
+ top: `${this.inputPosition.y}px`
|
|
|
+ }
|
|
|
+ },
|
|
|
+ canvasStyle() {
|
|
|
+ return {
|
|
|
+ cursor: this.mode === 'draw' ? 'crosshair' : 'grab'
|
|
|
+ }
|
|
|
+ },
|
|
|
+ fullScreenHtml() {
|
|
|
+ return this.$refs.canvasContainer
|
|
|
+ }
|
|
|
+
|
|
|
+ },
|
|
|
+ data() {
|
|
|
+ return {
|
|
|
+ imgTimer: false, // 图片时间戳
|
|
|
+ ctx: null,
|
|
|
+ scale: 1, // 画布的缩放
|
|
|
+ imageScale: 1, // 图片的原始缩放
|
|
|
+ markScale: 1, // 绘制的缩放
|
|
|
+ baseScale: 0.31,
|
|
|
+ offsetX: 0,
|
|
|
+ offsetY: 0,
|
|
|
+ mode: 'drag', // 'draw' / 'drag'
|
|
|
+ modeLabels: {
|
|
|
+ draw: 'D绘制',
|
|
|
+ drag: 'S拖拽',
|
|
|
+ select: '空格选择'
|
|
|
+ },
|
|
|
+ isDragging: false,
|
|
|
+ isDrawing: false,
|
|
|
+ lastX: 0,
|
|
|
+ lastY: 0,
|
|
|
+ startX: 0,
|
|
|
+ startY: 0,
|
|
|
+ rectangles: [],
|
|
|
+ currentRect: null,
|
|
|
+ selectedRect: null,
|
|
|
+ backgroundImage: null,
|
|
|
+ imageLoaded: false,
|
|
|
+ originalImageSize: { width: 0, height: 0 },
|
|
|
+ isFullScreen: false,
|
|
|
+ imageRect: { x: 0, y: 0, width: 0, height: 0 }, // 存储图片显示区域
|
|
|
+ showInput: false,
|
|
|
+ currentLabel: '',
|
|
|
+ inputPosition: { x: 0, y: 0 },
|
|
|
+ currentRectId: null,
|
|
|
+
|
|
|
+ lastImageScale: 1,
|
|
|
+ loading: true,
|
|
|
+ isMoving: false,
|
|
|
+ colorMap: {}, // 用于存储文本和颜色的映射关系
|
|
|
+ tempColor: '#FFFF00', // 绘制时黄色半透明
|
|
|
+ finalColor: '#FF0000', // 完成后红色半透明
|
|
|
+ changeLeftActive: false,
|
|
|
+ changeRightActive: false,
|
|
|
+ colorIndex: 0,
|
|
|
+ originDataLength: 0, // 用于记录原始数据长度
|
|
|
+ pestLibrary: {},
|
|
|
+ canvasSize:{width:310,height:279}
|
|
|
+ }
|
|
|
+ },
|
|
|
+ created() {
|
|
|
+ this.getAllPestList()
|
|
|
+ },
|
|
|
+ mounted() {
|
|
|
+ // console.log('初始化markItem:', this.markItem)
|
|
|
+
|
|
|
+ uni.getSystemInfo({
|
|
|
+ success: (res) => {
|
|
|
+ this.canvasSize.width = res.windowWidth-(24*2+16*2)
|
|
|
+ }
|
|
|
+ })
|
|
|
+ this.$nextTick(()=>{
|
|
|
+
|
|
|
+ })
|
|
|
+ },
|
|
|
+ beforeDestroy() {
|
|
|
+
|
|
|
+ },
|
|
|
+ methods: {
|
|
|
+
|
|
|
+ // 切换上一个下一个
|
|
|
+ changeItem(type) {
|
|
|
+
|
|
|
+ this.$emit('changeItem', { type: type, isFullScreen: this.isFullScreen })
|
|
|
+ },
|
|
|
+ changeMode(modeType) {
|
|
|
+ this.mode = modeType
|
|
|
+ },
|
|
|
+
|
|
|
+ resetPest() {
|
|
|
+ // 通知业务上层处理逻辑
|
|
|
+ this.exitFullScreen()
|
|
|
+ this.$emit('resetPest')
|
|
|
+ },
|
|
|
+ // 查看原图
|
|
|
+ checkImagePreview() {
|
|
|
+ this.$refs.imgPreview.click()
|
|
|
+ },
|
|
|
+
|
|
|
+ linkStyle(tag) {
|
|
|
+ let x = Math.min(tag.startX, tag.endX)
|
|
|
+ let y = Math.min(tag.startY, tag.endY)
|
|
|
+
|
|
|
+ const top = y * this.scale + this.offsetY - 20 + 'px'
|
|
|
+ const left = x * this.scale + this.offsetX + 'px'
|
|
|
+ return {
|
|
|
+ top,
|
|
|
+ left,
|
|
|
+ color: tag.color,
|
|
|
+ 'pointer-events': this.isMoving ? 'none' : 'auto'
|
|
|
+ }
|
|
|
+ },
|
|
|
+ initCanvas() {
|
|
|
+
|
|
|
+ this.ctx = uni.createCanvasContext('yfCanvas', this)
|
|
|
+ console.log(this.ctx)
|
|
|
+ this.loadImage()
|
|
|
+
|
|
|
+ },
|
|
|
+
|
|
|
+ loadImage() {
|
|
|
+ if (!this.markItem.addr) {
|
|
|
+ this.loading = false
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ uni.getImageInfo({
|
|
|
+ src: this.markItem.addr,
|
|
|
+ success: (res) => {
|
|
|
+ this.imageLoaded = true
|
|
|
+ this.backgroundImage = res
|
|
|
+ this.originalImageSize = {
|
|
|
+ width: this.backgroundImage.width,
|
|
|
+ height: this.backgroundImage.height
|
|
|
+ }
|
|
|
+ this.rectangles = [] // 清除之前的标注
|
|
|
+ this.selectedRect = null
|
|
|
+ this.adjustImagePosition()
|
|
|
+ this.initMarkData()
|
|
|
+ this.draw()
|
|
|
+ this.loading = false
|
|
|
+ },
|
|
|
+ fail: (err) => {
|
|
|
+ this.loading = false
|
|
|
+ this.$message.error('图片加载失败')
|
|
|
+ }
|
|
|
+ })
|
|
|
+
|
|
|
+ },
|
|
|
+
|
|
|
+ adjustImagePosition() {
|
|
|
+ if (!this.imageLoaded) return
|
|
|
+
|
|
|
+ const canvas = this.canvasSize
|
|
|
+ const canvasAspect = canvas.width / canvas.height
|
|
|
+ const imageAspect = this.originalImageSize.width / this.originalImageSize.height
|
|
|
+
|
|
|
+ // 计算等比缩放的尺寸
|
|
|
+ if (imageAspect > canvasAspect) {
|
|
|
+ this.imageScale = canvas.width / this.originalImageSize.width
|
|
|
+ } else {
|
|
|
+ this.imageScale = canvas.height / this.originalImageSize.height
|
|
|
+ }
|
|
|
+ // this.imageScale = 0.8
|
|
|
+ // 根据图片的宽度来定义缩放比例
|
|
|
+ if (this.originalImageSize.width >= 5000) {
|
|
|
+ this.baseScale = 0.25
|
|
|
+ } else if (this.originalImageSize.width >= 4000) {
|
|
|
+ this.baseScale = 0.31
|
|
|
+ } else if (this.originalImageSize.width < 4000) {
|
|
|
+ this.baseScale = 0.4
|
|
|
+ }
|
|
|
+ // 用来处理图片等比例显示
|
|
|
+ this.markScale = 1 - (this.baseScale - this.imageScale) / this.baseScale
|
|
|
+ // 窗口大小改变的时候使用
|
|
|
+ myRatio = this.imageScale / this.lastImageScale
|
|
|
+
|
|
|
+ this.lastImageScale = this.imageScale
|
|
|
+ // 计算图片显示区域
|
|
|
+ this.imageRect = {
|
|
|
+ x: 0,
|
|
|
+ y: 0,
|
|
|
+ width: this.originalImageSize.width * this.imageScale,
|
|
|
+ height: this.originalImageSize.height * this.imageScale
|
|
|
+ }
|
|
|
+
|
|
|
+ // 初始位置居中
|
|
|
+ this.scale = 1
|
|
|
+ this.offsetX = (canvas.width - this.imageRect.width) / 2
|
|
|
+ this.offsetY = (canvas.height - this.imageRect.height) / 2
|
|
|
+ },
|
|
|
+
|
|
|
+ isPointInImage(x, y) {
|
|
|
+ return x >= 0 && x <= this.imageRect.width && y >= 0 && y <= this.imageRect.height
|
|
|
+ },
|
|
|
+ constrainRect(rect) {
|
|
|
+ // 确保矩形完全在图片区域内
|
|
|
+ const startX = Math.max(0, Math.min(this.imageRect.width, rect.startX))
|
|
|
+ const startY = Math.max(0, Math.min(this.imageRect.height, rect.startY))
|
|
|
+ const endX = Math.max(0, Math.min(this.imageRect.width, rect.endX))
|
|
|
+ const endY = Math.max(0, Math.min(this.imageRect.height, rect.endY))
|
|
|
+
|
|
|
+ return {
|
|
|
+ ...rect,
|
|
|
+ startX,
|
|
|
+ startY,
|
|
|
+ endX,
|
|
|
+ endY
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+
|
|
|
+ handleLabelChange() {
|
|
|
+ this.draw()
|
|
|
+ },
|
|
|
+
|
|
|
+ getRectAt(x, y) {
|
|
|
+ // 从后往前检查,这样最后绘制的矩形会优先被选中
|
|
|
+ for (let i = this.rectangles.length - 1; i >= 0; i--) {
|
|
|
+ const rect = this.rectangles[i]
|
|
|
+ const left = Math.min(rect.startX, rect.endX)
|
|
|
+ const right = Math.max(rect.startX, rect.endX)
|
|
|
+ const top = Math.min(rect.startY, rect.endY)
|
|
|
+ const bottom = Math.max(rect.startY, rect.endY)
|
|
|
+
|
|
|
+ if (x >= left && x <= right && y >= top && y <= bottom) {
|
|
|
+ return rect
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return null
|
|
|
+ },
|
|
|
+
|
|
|
+ deleteSelected() {
|
|
|
+ if (this.selectedRect) {
|
|
|
+ this.deleteRect(this.selectedRect.id)
|
|
|
+ this.selectedRect = null
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ deleteRect(id) {
|
|
|
+ this.rectangles = this.rectangles.filter(rect => rect.id !== id)
|
|
|
+ if (this.selectedRect && this.selectedRect.id === id) {
|
|
|
+ this.selectedRect = null
|
|
|
+ }
|
|
|
+ this.showInput = false
|
|
|
+ this.currentRectId = null
|
|
|
+ this.draw()
|
|
|
+ },
|
|
|
+ deleteSpan(e, id) {
|
|
|
+ e.preventDefault()
|
|
|
+ e.stopPropagation()
|
|
|
+ this.isDrawing = false
|
|
|
+ this.deleteRect(id)
|
|
|
+ },
|
|
|
+ clearCanvas() {
|
|
|
+ this.loading = false
|
|
|
+ this.imageLoaded = false
|
|
|
+ this.rectangles = []
|
|
|
+ this.selectedRect = null
|
|
|
+ this.showInput = false
|
|
|
+ this.currentRectId = null
|
|
|
+ const canvas = this.canvasSize
|
|
|
+ const ctx = this.ctx
|
|
|
+
|
|
|
+ // 清除画布
|
|
|
+ ctx.clearRect(0, 0, canvas.width, canvas.height)
|
|
|
+ },
|
|
|
+ // 修改后的 draw 方法
|
|
|
+ draw() {
|
|
|
+ const canvas = this.canvasSize
|
|
|
+ const ctx = this.ctx
|
|
|
+
|
|
|
+ // 清除画布
|
|
|
+ ctx.clearRect(0, 0, canvas.width, canvas.height)
|
|
|
+ console.log(this.offsetX, this.offsetY)
|
|
|
+ // 保存当前状态
|
|
|
+ ctx.save()
|
|
|
+
|
|
|
+ // 应用变换(缩放和平移)
|
|
|
+ ctx.translate(this.offsetX, this.offsetY)
|
|
|
+ ctx.scale(this.scale, this.scale)
|
|
|
+
|
|
|
+ // 绘制背景图片(不再除以this.scale)
|
|
|
+ if (this.imageLoaded && this.backgroundImage) {
|
|
|
+ const displayWidth = this.originalImageSize.width * this.imageScale
|
|
|
+ const displayHeight = this.originalImageSize.height * this.imageScale
|
|
|
+ // ctx.imageSmoothingEnabled = true
|
|
|
+ // ctx.imageSmoothingQuality = 'high'
|
|
|
+ ctx.drawImage(this.backgroundImage.path, 0, 0, displayWidth, displayHeight)
|
|
|
+
|
|
|
+ }
|
|
|
+ console.log('绘制::',this.rectangles)
|
|
|
+ // 绘制所有已保存的矩形(添加约束)
|
|
|
+ this.rectangles.forEach(rect => {
|
|
|
+ const constrainedRect = this.constrainRect(rect)
|
|
|
+ this.drawRectangle(constrainedRect, rect === this.selectedRect)
|
|
|
+ })
|
|
|
+
|
|
|
+ // 绘制当前正在绘制的矩形(添加约束)
|
|
|
+ if (this.currentRect) {
|
|
|
+ const constrainedRect = this.constrainRect(this.currentRect)
|
|
|
+ this.drawRectangle(constrainedRect, false)
|
|
|
+ }
|
|
|
+
|
|
|
+ // 恢复状态
|
|
|
+ ctx.restore()
|
|
|
+ ctx.draw()
|
|
|
+ },
|
|
|
+
|
|
|
+ drawRectangle(rect, isSelected) {
|
|
|
+ const ctx = this.ctx
|
|
|
+
|
|
|
+ const x = Math.min(rect.startX, rect.endX)
|
|
|
+ const y = Math.min(rect.startY, rect.endY)
|
|
|
+ const width = Math.abs(rect.endX - rect.startX)
|
|
|
+ const height = Math.abs(rect.endY - rect.startY)
|
|
|
+
|
|
|
+ // 绘制矩形填充
|
|
|
+
|
|
|
+ ctx.strokeStyle = rect.color
|
|
|
+ ctx.lineWidth = 2 / this.scale
|
|
|
+ ctx.strokeRect(x, y, width, height)
|
|
|
+ // 绘制标签文本
|
|
|
+ if (rect.text) {
|
|
|
+ ctx.fillStyle = rect.color
|
|
|
+ ctx.font = `${14 / this.scale}px Arial`
|
|
|
+ ctx.fillText(rect.text, x + 12 / this.scale, y - 6 / this.scale)
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ },
|
|
|
+
|
|
|
+ // 获取数据
|
|
|
+ getData() {
|
|
|
+ const data = this.rectangles.map(rect => {
|
|
|
+ return {
|
|
|
+ startX: parseFloat((rect.startX / this.markScale).toFixed(2)),
|
|
|
+ startY: parseFloat((rect.startY / this.markScale).toFixed(2)),
|
|
|
+ width: parseFloat((Math.abs(rect.startX - rect.endX) / this.markScale).toFixed(2)),
|
|
|
+ height: parseFloat((Math.abs(rect.startY - rect.endY) / this.markScale).toFixed(2)),
|
|
|
+ text: rect.text
|
|
|
+ }
|
|
|
+ })
|
|
|
+ this.originDataLength = data.length
|
|
|
+ return data
|
|
|
+ // return JSON.stringify(data)
|
|
|
+ },
|
|
|
+ // 处理渲染数据
|
|
|
+ async initMarkData() {
|
|
|
+
|
|
|
+ const colorList = [
|
|
|
+ '#FF0000',
|
|
|
+ '#00FFC2',
|
|
|
+ '#FF00A8',
|
|
|
+ '#120080',
|
|
|
+ '#BDFF00',
|
|
|
+ '#FFB800',
|
|
|
+ '#5E8000',
|
|
|
+ '#FF5C00',
|
|
|
+ '#00FF75',
|
|
|
+ '#00F0FF',
|
|
|
+ '#0094FF',
|
|
|
+ '#FFFFFF',
|
|
|
+ '#007880',
|
|
|
+ '#00C2FF',
|
|
|
+ '#C74C4C',
|
|
|
+ '#EB00FF'
|
|
|
+ ]
|
|
|
+ if (this.markItem.is_mark === 0) {
|
|
|
+ // 机器标注,取label
|
|
|
+ let aiLabel = []
|
|
|
+ if (this.markItem.label) {
|
|
|
+ aiLabel = JSON.parse(this.markItem.label.replace(/'/g, '"'))
|
|
|
+ }
|
|
|
+ this.rectangles = []
|
|
|
+ console.log('00000',aiLabel)
|
|
|
+ aiLabel.forEach((item, index) => {
|
|
|
+ for (let key in item) {
|
|
|
+ const [startX, startY, endX, endY] = item[key]
|
|
|
+ const text = this.pestLibrary[key]
|
|
|
+ if (!this.colorMap[text]) {
|
|
|
+ this.colorMap[text] = colorList[this.colorIndex % colorList.length]
|
|
|
+ this.colorIndex += 1
|
|
|
+ if (this.colorIndex >= colorList.length) {
|
|
|
+ this.colorIndex = 0
|
|
|
+ }
|
|
|
+ }
|
|
|
+ this.rectangles.push({
|
|
|
+ id: Date.now() + index,
|
|
|
+ text: text,
|
|
|
+ startX: startX * this.imageScale,
|
|
|
+ startY: startY * this.imageScale,
|
|
|
+ endX: endX * this.imageScale,
|
|
|
+ endY: endY * this.imageScale,
|
|
|
+ color: this.colorMap[text],
|
|
|
+ imageScale: this.imageScale
|
|
|
+ })
|
|
|
+ }
|
|
|
+ })
|
|
|
+ this.originDataLength = this.rectangles.length
|
|
|
+ } else {
|
|
|
+ // 人工标注,取mark
|
|
|
+ console.log('this.colorIndex', this.colorIndex)
|
|
|
+ this.rectangles = []
|
|
|
+ this.markItem.mark.map((item, index) => {
|
|
|
+ const { startX, startY, width, height, text } = item
|
|
|
+ if (!this.colorMap[text]) {
|
|
|
+ this.colorMap[text] = colorList[this.colorIndex % colorList.length]
|
|
|
+ this.colorIndex += 1
|
|
|
+ if (this.colorIndex >= colorList.length) {
|
|
|
+ this.colorIndex = 0
|
|
|
+ }
|
|
|
+ }
|
|
|
+ this.rectangles.push({
|
|
|
+ id: Date.now() + index,
|
|
|
+ text: text,
|
|
|
+ startX: Number(startX) * this.markScale,
|
|
|
+ startY: Number(startY) * this.markScale,
|
|
|
+ endX: Number(startX) * this.markScale + Number(width) * this.markScale,
|
|
|
+ endY: Number(startY) * this.markScale + Number(height) * this.markScale,
|
|
|
+ color: this.colorMap[text],
|
|
|
+ imageScale: this.imageScale
|
|
|
+ })
|
|
|
+ })
|
|
|
+ this.originDataLength = this.rectangles.length
|
|
|
+ }
|
|
|
+ console.log('原始rectangles', this.rectangles)
|
|
|
+ },
|
|
|
+ // 保存标注
|
|
|
+ saveLabel() {
|
|
|
+ this.$emit('saveLabel', this.getData())
|
|
|
+ },
|
|
|
+ async getAllPestList() {
|
|
|
+ const res = await this.$myRequest({
|
|
|
+ url: '/api/api_gateway?method=forecast.pest_info.pest_dict',
|
|
|
+ data: {
|
|
|
+ type_name: this.typeName
|
|
|
+ }
|
|
|
+ })
|
|
|
+ // console.log('获取到的病虫害数据', res)
|
|
|
+ this.pestLibrary = res
|
|
|
+ this.initCanvas()
|
|
|
+ },
|
|
|
+
|
|
|
+ }
|
|
|
+}
|
|
|
+</script>
|
|
|
+
|
|
|
+<style lang="less" scoped>
|
|
|
+@primary-color: #018b3f !important;
|
|
|
+/deep/ .el-button--primary {
|
|
|
+ background-color: @primary-color !important;
|
|
|
+}
|
|
|
+.canvas-container {
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ position: relative;
|
|
|
+ background: #ffffff;
|
|
|
+ padding-top: 10px;
|
|
|
+ .virtual-canvas {
|
|
|
+ /* 2025安全隐藏方案 */
|
|
|
+ position: absolute !important;
|
|
|
+ clip: rect(0 0 0 0) !important;
|
|
|
+ pointer-events: none !important;
|
|
|
+ opacity: 0 !important;
|
|
|
+ z-index: -9999 !important;
|
|
|
+ }
|
|
|
+ .canvas {
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ overflow: hidden;
|
|
|
+ background-color: #ffffff;
|
|
|
+ border: 1px solid #e4e7ed;
|
|
|
+ box-sizing: border-box;
|
|
|
+ position: relative;
|
|
|
+ }
|
|
|
+ .canvas-timer {
|
|
|
+ position: absolute;
|
|
|
+ left: 16px;
|
|
|
+ top: 16px;
|
|
|
+ z-index: 2;
|
|
|
+ color: #ffffff;
|
|
|
+ font-size: 23px;
|
|
|
+ }
|
|
|
+ .panel {
|
|
|
+ width: 30%;
|
|
|
+ height: calc(100% - 60px);
|
|
|
+ background: #ffffff;
|
|
|
+ padding-left: 10px;
|
|
|
+ box-sizing: border-box;
|
|
|
+ position: relative;
|
|
|
+ z-index: 3;
|
|
|
+ .save {
|
|
|
+ height: 60px;
|
|
|
+ button {
|
|
|
+ margin: 14px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ .controls {
|
|
|
+ // padding: 3px 15px;
|
|
|
+ width: 70%;
|
|
|
+ height: 64px;
|
|
|
+ box-sizing: border-box;
|
|
|
+ position: relative;
|
|
|
+ z-index: 3;
|
|
|
+ // background: rgba(0, 0, 0, 0.3);
|
|
|
+ }
|
|
|
+ .img-timer {
|
|
|
+ position: absolute;
|
|
|
+ left: 0;
|
|
|
+ top: 22px;
|
|
|
+ }
|
|
|
+ .info {
|
|
|
+ // width: 100%;
|
|
|
+ // color: #ffffff;
|
|
|
+ // overflow: hidden;
|
|
|
+ // text-align: center;
|
|
|
+ .tools {
|
|
|
+ // width: 365px;
|
|
|
+ height: 64px;
|
|
|
+ // padding: 0 10%;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ // background: rgba(0, 0, 0, 0.6);
|
|
|
+ border-radius: 4px;
|
|
|
+ gap: 10px;
|
|
|
+ // margin: 0 auto;
|
|
|
+ color: rgba(0, 0, 0, 0.6);
|
|
|
+ .active {
|
|
|
+ background: @primary-color;
|
|
|
+ color: #ffffff;
|
|
|
+ }
|
|
|
+ .split {
|
|
|
+ color: #b9b9b9;
|
|
|
+ margin: 0 5px;
|
|
|
+ font-size: 14px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ .float-right {
|
|
|
+ width: 30%;
|
|
|
+ }
|
|
|
+ .img-preview {
|
|
|
+ display: none;
|
|
|
+ width: 20px;
|
|
|
+ position: absolute;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .info span {
|
|
|
+ white-space: nowrap;
|
|
|
+ .active {
|
|
|
+ color: @primary-color;
|
|
|
+ margin-right: 5px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .info button {
|
|
|
+ font-size: 26px;
|
|
|
+ padding: 2px 4px;
|
|
|
+ background: none;
|
|
|
+ border: none;
|
|
|
+ border-radius: 3px;
|
|
|
+ cursor: pointer;
|
|
|
+ white-space: nowrap;
|
|
|
+ color: #434343;
|
|
|
+ }
|
|
|
+
|
|
|
+ .info button:disabled {
|
|
|
+ cursor: not-allowed;
|
|
|
+ }
|
|
|
+ .element-layer {
|
|
|
+ .label-link {
|
|
|
+ position: absolute;
|
|
|
+ z-index: 2;
|
|
|
+ user-select: none;
|
|
|
+ pointer-events: auto;
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+</style>
|