Commit 076a4d68 authored by jiaxu.yan's avatar jiaxu.yan

新增套组

parent 4e21d926
<template>
<div class="container">
<div v-if="title">
<h1>{{ title }}</h1>
<div><solt name="left-btn"></solt></div>
</div>
<div>
<solt name="right-btn"></solt>
</div>
<!-- 这里可以添加变压器运行状态的具体内容 -->
<!-- 例如:电压、电流、温度等监测数据 -->
</div>
</template>
<script>
export default {
name: 'TransformerStatus',
props: {
title: {
type: String,
default: ''
}
},
methods: {
getType(e) {
console.log(e)
}
}
}
</script>
<style>
body {
font-family: Arial, sans-serif;
margin: 20px;
line-height: 1.6;
}
h1 {
color: #333;
border-bottom: 1px solid #eee;
padding-bottom: 10px;
}
.container {
max-width: 800px;
background: #EBF1FF;
margin: 0 auto;
}
</style>
<template>
<div>
<div class="left-bar">
<el-button :type="type == 1" @click="getType(1)">第一视角</el-button>
<el-button :type="type == 2" @click="getType(2)">第二视角</el-button>
<el-button :type="type == 3" @click="getType(3)">第三视角</el-button>
<el-button :type="type == 4" @click="getType(4)">第四视角</el-button>
<el-button :type="type == 5" @click="getType(5)">第五视角</el-button>
<el-button :type="type == 6" @click="getType(6)">第六视角</el-button>
</div>
<pie> </pie>
<div class="pageindex">
<el-row :gutter="20">
<el-col :span="12">
<div class="left-bar">
<button
:class="type == 1 ? 'active-btn' : 'default-btn'"
@click="getType(1)"
>
第一视角
</button>
<button
:class="type == 2 ? 'active-btn' : 'default-btn'"
@click="getType(2)"
>
第二视角
</button>
<!-- <button
:class="type == 3 ? 'active-btn' : 'default-btn'"
@click="getType(3)"
>
第三视角
</button>
<button
:class="type == 4 ? 'active-btn' : 'default-btn'"
@click="getType(4)"
>
第四视角
</button>
<button
:class="type == 5 ? 'active-btn' : 'default-btn'"
@click="getType(5)"
>
第五视角
</button>
<button
:class="type == 6 ? 'active-btn' : 'default-btn'"
@click="getType(6)"
>
第六视角
</button> -->
</div>
<pie ref="zutai" :backgroundImage="pic1"> </pie>
</el-col>
</el-row>
</div>
</template>
<script>
import pie from '@/views/makePie/index.vue'
import pic1 from '@/assets/image/变压器抠图-1.png'
import pic2 from '@/assets/image/变压器抠图-2.png'
export default {
components: {
pie: pie
......@@ -20,7 +56,9 @@ export default {
data() {
return {
name: '特变电压器',
type: 1
type: 1,
pic1,
pic2
}
},
getType(e) {
......@@ -30,9 +68,22 @@ export default {
}
</script>
<style lang="scss" scoped>
.default-btn {
width: 145px;
height: 58px;
background: #ffffff;
border-radius: 29px;
}
.active-btn {
width: 145px;
height: 58px;
background: #ffffff;
border-radius: 29px;
}
.pageindex {
width: 100%;
height: 100%;
.index-top {
display: flex;
.top-left {
......
<template>
<div class="container">
<canvas
ref="canvas"
:width="width"
:height="height"
:style="{ background: background }"
@click="handleClick"
@mousedown="handleMouseDown"
@mousemove="handleMouseMove"
@mouseup="handleMouseUp"
@mouseleave="handleMouseLeave"
@contextmenu.prevent="handleContextMenu"
></canvas>
<div class="right-bar">
<el-upload
ref="upload"
:action="upload_url"
:before-upload="beforeUpload"
:show-file-list="false"
:auto-upload="true"
:headers="headers"
accept="image/*"
:on-success="handleSuccess"
>
<el-button slot="trigger">上传文件</el-button>
</el-upload>
</div>
<!-- 右键菜单 -->
<div
v-if="contextMenu.visible"
class="context-menu"
:style="{
left: `${contextMenu.x}px`,
top: `${contextMenu.y}px`
}"
>
<div class="menu-item" @click="changePointColor">更改颜色</div>
<div class="menu-item" @click="deletePoint">删除点位</div>
<div class="menu-item" @click="perintPoint">设置点位看板</div>
</div>
<el-dialog title="看板展示" :visible.sync="dialogFormVisible">
<el-form ref="form" :model="form" :inline="true">
<div v-for="(item, index) in formData" :key="index">
<el-row :gutter="20">
<el-col :span="10">
<el-form-item label="项目名称">
<el-input
v-model="item.label"
style="width: 100%"
placeholder="请输入项目名称"
/>
</el-form-item>
</el-col>
<el-col :span="10">
<el-form-item label="项目内容">
<el-select
v-model="item.value"
style="width: 100%"
placeholder="请选择项目内容"
>
<el-option label="区域一" value="shanghai"></el-option>
<el-option label="区域二" value="beijing"></el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="4">
<el-button @click="formData.splice(i, 1)" style="margin-top: 45px"
>删除</el-button
>
</el-col>
</el-row>
</div>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="formData.push({ label: '', value: '' })"
>增加项目</el-button
>
<el-button @click="dialogFormVisible = false">取 消</el-button>
<el-button type="primary" @click="dialogFormVisible = false"
>确 定</el-button
>
</div>
</el-dialog>
<div class="controls">
<button @click="uploadImage">上传图片</button>
<input
type="file"
ref="fileInput"
accept="image/*"
style="display: none"
@change="handleImageUpload"
/>
<!-- <button @click="addRandomPoint">添加随机点位</button> -->
<button @click="clearAllPoints">清除所有点位</button>
<button @click="toggleMode">
切换模式: {{ isDragCreationMode ? '点击选择' : '拖拽创建' }}
</button>
<p id="info">{{ infoText }}</p>
<p v-if="isDragCreationMode" class="mode-indicator">
在拖拽创建模式下,按住鼠标拖动可以创建点位
</p>
</div>
<!-- 颜色选择器弹窗 -->
<div v-if="colorPicker.visible" class="color-picker-modal">
<div class="color-picker">
<h3>选择新颜色</h3>
<input type="color" v-model="colorPicker.selectedColor" />
<div class="button-group">
<button @click="confirmColorChange">确定</button>
<button @click="cancelColorChange">取消</button>
</div>
</div>
</div>
</div>
</template>
<script>
import { getToken } from '@/utils/auth'
export default {
name: 'PointCanvasWithContextMenu',
props: {
width: {
type: Number,
default: 600
},
height: {
type: Number,
default: 400
},
initialPoints: {
type: Array,
default: () => []
}
},
data() {
return {
// 设置上传的请求头部
headers: { Authorization: 'Bearer ' + getToken() },
// 上传的地址
upload_url: process.env.VUE_APP_BASE_API + '/system/user/profile/avatar',
ctx: null,
points: [],
formData: [{ label: '', value: '' }],
dialogFormVisible: false,
isDragCreationMode: false,
isDragging: false,
dragStartX: 0,
dragStartY: 0,
currentDragPoint: null,
animationFrameId: null,
infoText: '点击画布上的点位会有交互效果',
// 右键菜单相关
contextMenu: {
visible: false,
x: 0,
y: 0,
point: null
},
// 颜色选择器相关
colorPicker: {
visible: false,
selectedColor: '#ff0000',
point: null
}
}
},
mounted() {
this.initCanvas()
this.drawAllPoints()
// 添加初始点位
if (this.initialPoints.length > 0) {
this.points = this.initialPoints.map(p => new Point(p.x, p.y, p.radius))
} else {
for (let i = 0; i < 5; i++) {
// this.addRandomPoint()
}
}
// 点击其他地方关闭右键菜单
document.addEventListener('click', this.closeContextMenu)
},
beforeDestroy() {
cancelAnimationFrame(this.animationFrameId)
document.removeEventListener('click', this.closeContextMenu)
},
methods: {
handleSuccess(e, file) {
const bgImg = new Image()
bgImg.src = URL.createObjectURL(file.raw)
bgImg.crossOrigin = 'Anonymous' // 解决跨域问题(如果需要)
let self = this
bgImg.onload = function () {
// 绘制背景(铺满 Canvas)
self.ctx.drawImage(bgImg, 0, 0, this.width, this.height)
// 在背景上绘制其他内容
self.ctx.fillStyle = 'rgba(255, 255, 255, 0.7)'
self.ctx.fillRect(50, 50, 200, 100)
self.ctx.fillStyle = 'black'
self.ctx.font = '24px Arial'
self.ctx.fillText('Canvas Background', 70, 100)
}
// 处理图片加载错误
bgImg.onerror = function () {
console.error('背景图片加载失败!')
// 可以设置一个纯色备用背景
self.ctx.fillStyle = 'lightgray'
self.ctx.fillRect(0, 0, this.width, this.height)
}
},
beforeUpload(file) {
const isImage = file.type.startsWith('image/')
if (!isImage) {
this.$message.error('只能上传图片文件!')
}
return isImage // 返回 false 会阻止上传
},
handleImageUpload(e) {
const file = e.target.files[0]
if (!file) return
const reader = new FileReader()
reader.onload = event => {
const img = new Image()
img.onload = () => {
const size = this.fixedSize
const x = Math.random() * (this.width - size)
const y = Math.random() * (this.height - size)
this.items.push({
type: 'image',
x,
y,
size,
image: img,
isActive: false
})
this.infoText = `添加了图片 (${Math.round(x)}, ${Math.round(y)})`
}
img.src = event.target.result
}
reader.readAsDataURL(file)
// 清空input以便重复选择同一文件
e.target.value = ''
},
perintPoint() {
this.dialogFormVisible = true
// const index = this.points.indexOf(this.contextMenu.point)
// this.$emit('perintPoint', this.points[index])
},
uploadImage() {
this.$refs.fileInput.click()
},
initCanvas() {
const canvas = this.$refs.canvas
this.ctx = canvas.getContext('2d')
var img = new Image()
var self = this
img.onload = function () {
self.ctx.drawImage(img, 0, 0, canvas.width, canvas.height)
}
img.src = this.background
},
addRandomPoint() {
const radius = Math.floor(Math.random() * 15 + 5)
const x = Math.random() * (this.width - radius * 2) + radius
const y = Math.random() * (this.height - radius * 2) + radius
this.points.push(new Point(x, y, radius))
this.infoText = '添加了随机点位'
},
addPointAt(x, y, radius = 10) {
const point = new Point(x, y, radius)
this.points.push(point)
return point
},
clearAllPoints() {
this.points = []
this.ctx.clearRect(0, 0, this.width, this.height)
this.infoText = '已清除所有点位'
},
toggleMode() {
this.isDragCreationMode = !this.isDragCreationMode
this.isDragging = false
this.currentDragPoint = null
this.infoText = `当前模式: ${
this.isDragCreationMode ? '拖拽创建点位' : '点击选择点位'
}`
},
drawAllPoints() {
this.ctx.clearRect(0, 0, this.width, this.height)
this.points.forEach(point => point.draw(this.ctx))
if (this.isDragCreationMode && this.isDragging && this.currentDragPoint) {
this.currentDragPoint.draw(this.ctx)
this.ctx.beginPath()
this.ctx.moveTo(this.dragStartX, this.dragStartY)
this.ctx.lineTo(this.currentDragPoint.x, this.currentDragPoint.y)
this.ctx.strokeStyle = 'rgba(0, 0, 0, 0.3)'
this.ctx.setLineDash([5, 3])
this.ctx.stroke()
this.ctx.setLineDash([])
}
this.animationFrameId = requestAnimationFrame(this.drawAllPoints)
},
handleClick(e) {
if (this.isDragCreationMode) return
const rect = this.$refs.canvas.getBoundingClientRect()
const x = e.clientX - rect.left
const y = e.clientY - rect.top
let clickedPoint = null
this.points.forEach(point => {
point.isActive = false
})
for (let i = this.points.length - 1; i >= 0; i--) {
if (this.points[i].contains(x, y)) {
clickedPoint = this.points[i]
clickedPoint.isActive = true
this.infoText = `点击了点位 (${Math.round(
clickedPoint.x
)}, ${Math.round(clickedPoint.y)})`
break
}
}
if (!clickedPoint) {
this.infoText = '点击空白区域'
}
},
handleMouseDown(e) {
if (!this.isDragCreationMode) return
const rect = this.$refs.canvas.getBoundingClientRect()
this.dragStartX = e.clientX - rect.left
this.dragStartY = e.clientY - rect.top
let clickedExisting = false
for (let i = this.points.length - 1; i >= 0; i--) {
if (this.points[i].contains(this.dragStartX, this.dragStartY)) {
clickedExisting = true
break
}
}
if (!clickedExisting) {
this.isDragging = true
this.currentDragPoint = this.addPointAt(
this.dragStartX,
this.dragStartY,
5
)
this.infoText = '拖动鼠标调整点位大小'
}
},
handleMouseMove(e) {
if (
!this.isDragCreationMode ||
!this.isDragging ||
!this.currentDragPoint
)
return
const rect = this.$refs.canvas.getBoundingClientRect()
const x = e.clientX - rect.left
const y = e.clientY - rect.top
const distance = Math.sqrt(
Math.pow(x - this.dragStartX, 2) + Math.pow(y - this.dragStartY, 2)
)
this.currentDragPoint.originalRadius = Math.min(Math.max(distance, 5), 50)
this.currentDragPoint.x = this.dragStartX
this.currentDragPoint.y = this.dragStartY
},
handleMouseUp() {
if (!this.isDragCreationMode || !this.isDragging) return
this.isDragging = false
this.infoText = `已创建点位 (${Math.round(
this.currentDragPoint.x
)}, ${Math.round(this.currentDragPoint.y)}), 半径: ${Math.round(
this.currentDragPoint.originalRadius
)}`
this.currentDragPoint = null
},
handleMouseLeave() {
if (this.isDragging) {
if (this.currentDragPoint) {
this.points.pop()
}
this.isDragging = false
this.currentDragPoint = null
}
},
// 右键菜单相关方法
handleContextMenu(e) {
const rect = this.$refs.canvas.getBoundingClientRect()
const x = e.clientX - rect.left
const y = e.clientY - rect.top
// 查找点击的点位
for (let i = this.points.length - 1; i >= 0; i--) {
if (this.points[i].contains(x, y)) {
e.preventDefault()
// 显示右键菜单
this.contextMenu = {
visible: true,
x: e.clientX,
y: e.clientY,
point: this.points[i]
}
// 高亮选中的点位
this.points.forEach(p => (p.isActive = false))
this.points[i].isActive = true
this.infoText = `右键点击了点位 (${Math.round(
this.points[i].x
)}, ${Math.round(this.points[i].y)})`
return
}
}
// 如果点击的是空白区域,隐藏右键菜单
this.closeContextMenu()
},
closeContextMenu() {
this.contextMenu.visible = false
},
// 右键菜单操作
changePointColor() {
this.colorPicker = {
visible: true,
selectedColor: this.contextMenu.point.color,
point: this.contextMenu.point
}
this.closeContextMenu()
},
deletePoint() {
const index = this.points.indexOf(this.contextMenu.point)
if (index !== -1) {
this.points.splice(index, 1)
this.infoText = `已删除点位 (${Math.round(
this.contextMenu.point.x
)}, ${Math.round(this.contextMenu.point.y)})`
}
this.closeContextMenu()
},
clonePoint() {
const original = this.contextMenu.point
const newPoint = new Point(
original.x + original.radius * 1.5,
original.y + original.radius * 1.5,
original.originalRadius
)
newPoint.color = original.color
this.points.push(newPoint)
this.infoText = `已复制点位到 (${Math.round(newPoint.x)}, ${Math.round(
newPoint.y
)})`
this.closeContextMenu()
},
addRandomPointNearby() {
const original = this.contextMenu.point
const radius = Math.floor(Math.random() * 10 + 5)
const angle = Math.random() * Math.PI * 2
const distance = original.radius * 2 + Math.random() * 50
const x = original.x + Math.cos(angle) * distance
const y = original.y + Math.sin(angle) * distance
this.addPointAt(x, y, radius)
this.infoText = `已在附近添加随机点位 (${Math.round(x)}, ${Math.round(
y
)})`
this.closeContextMenu()
},
// 颜色选择器相关方法
confirmColorChange() {
this.colorPicker.point.color = this.colorPicker.selectedColor
this.colorPicker.visible = false
this.infoText = `已更改点位颜色为 ${this.colorPicker.selectedColor}`
},
cancelColorChange() {
this.colorPicker.visible = false
}
}
}
class Point {
constructor(x, y, radius = 10) {
this.x = x
this.y = y
this.radius = radius
this.originalRadius = radius
this.color = this.getRandomColor()
this.isActive = false
}
getRandomColor() {
const r = Math.floor(Math.random() * 200 + 55)
const g = Math.floor(Math.random() * 200 + 55)
const b = Math.floor(Math.random() * 200 + 55)
return `rgb(${r}, ${g}, ${b})`
}
draw(ctx) {
ctx.beginPath()
ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2)
ctx.fillStyle = this.isActive ? '#ff0000' : this.color
ctx.fill()
ctx.strokeStyle = '#333'
ctx.stroke()
if (this.isActive) {
this.radius = this.originalRadius * (1 + Math.sin(Date.now() / 200) * 0.2)
} else {
this.radius = this.originalRadius
}
}
contains(x, y) {
const distance = Math.sqrt((x - this.x) ** 2 + (y - this.y) ** 2)
return distance <= this.radius
}
}
</script>
<style scoped>
.container {
/* display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
text-align: center;
position: relative; */
}
canvas {
background-color: white;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
cursor: default;
}
.controls {
margin-top: 20px;
}
button {
padding: 8px 16px;
margin: 0 5px;
cursor: pointer;
}
.mode-indicator {
margin-top: 10px;
font-weight: bold;
color: #333;
}
#info {
margin-top: 10px;
color: #333;
}
/* 右键菜单样式 */
.context-menu {
position: fixed;
background-color: white;
border: 1px solid #ddd;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);
border-radius: 4px;
z-index: 1000;
min-width: 150px;
}
.menu-item {
padding: 8px 12px;
cursor: pointer;
}
.menu-item:hover {
background-color: #f5f5f5;
}
/* 颜色选择器弹窗 */
.color-picker-modal {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
justify-content: center;
align-items: center;
z-index: 1001;
}
.color-picker {
background-color: white;
padding: 20px;
border-radius: 8px;
box-shadow: 0 0 20px rgba(0, 0, 0, 0.3);
}
.color-picker h3 {
margin-top: 0;
}
.color-picker input[type='color'] {
width: 100%;
margin: 10px 0;
}
.button-group {
display: flex;
justify-content: space-between;
}
.button-group button {
flex: 1;
margin: 0 5px;
}
.right-bar {
position: absolute;
right: 0px;
top: 20px;
}
</style>
<template>
<div class="container">
<h1>Canvas点位交互演示</h1>
<canvas
ref="canvas"
:width="width"
:height="height"
@click="handleClick"
@mousedown="handleMouseDown"
@mousemove="handleMouseMove"
@mouseup="handleMouseUp"
@mouseleave="handleMouseLeave"
@contextmenu.prevent="handleContextMenu"
></canvas>
<div class="right-bar">
<el-upload
ref="upload"
:action="upload_url"
:before-upload="beforeUpload"
:show-file-list="false"
:auto-upload="true"
:headers="headers"
<div class="toolbar">
<button @click="toggleAddMode" :class="{ active: isAddMode }">
{{ isAddMode ? '退出添加模式' : '手动添加点位' }}
</button>
<!-- <button @click="addRandomPoint">添加随机点位</button> -->
<button @click="uploadImage">上传图片</button>
<input
type="file"
ref="fileInput"
accept="image/*"
:on-success="handleSuccess"
>
<el-button slot="trigger">上传文件</el-button>
</el-upload>
style="display: none"
@change="handleImageUpload"
/>
<button @click="clearAll">清除所有</button>
<span class="info">{{ infoText }}</span>
</div>
<div class="canvas-container">
<!-- <img :src="background" alt="Background" style="width: 100%; height: 100%; position: absolute;z-index: 1; "> -->
<canvas
ref="canvas"
:width="width"
:height="height"
@click="handleClick"
@mousedown="handleMouseDown"
@mousemove="handleMouseMove"
@mouseup="handleMouseUp"
@mouseleave="handleMouseLeave"
@contextmenu.prevent="handleContextMenu"
></canvas>
</div>
<!-- 右键菜单 -->
<div
v-if="contextMenu.visible"
......@@ -35,31 +41,87 @@
top: `${contextMenu.y}px`
}"
>
<div class="menu-item" @click="changePointColor">更改颜色</div>
<div class="menu-item" @click="deletePoint">删除点位</div>
<div class="menu-item" @click="changeColor">更改颜色/图片</div>
<div class="menu-item" @click="deleteItem">删除</div>
<div class="menu-item" @click="cloneItem">复制</div>
<div class="menu-item" @click="bringToFront">置顶</div>
<div class="menu-item" @click="perintPoint">设置点位看板</div>
</div>
<el-dialog title="看板展示" :visible.sync="dialogFormVisible">
<el-form ref="form" :model="form" :inline="true">
<div v-for="(item, index) in formData" :key="index">
<el-row :gutter="20">
<el-col :span="10">
<el-form-item label="项目名称">
<el-input
v-model="item.label"
style="width: 100%"
placeholder="请输入项目名称"
/>
</el-form-item>
</el-col>
<el-col :span="10">
<el-form-item label="项目内容">
<el-select
v-model="item.value"
style="width: 100%"
placeholder="请选择项目内容"
>
<el-option label="区域一" value="shanghai"></el-option>
<el-option label="区域二" value="beijing"></el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="4">
<el-button @click="formData.splice(i, 1)" style="margin-top: 45px"
>删除</el-button
>
</el-col>
</el-row>
</div>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="formData.push({ label: '', value: '' })"
>增加项目</el-button
>
<el-button @click="dialogFormVisible = false">取 消</el-button>
<el-button type="primary" @click="dialogFormVisible = false"
>确 定</el-button
>
</div>
</el-dialog>
<!-- 颜色/图片选择器 -->
<div v-if="editor.visible" class="editor-modal">
<div class="editor-content">
<h3>编辑 {{ editor.item.type === 'point' ? '点位' : '图片' }}</h3>
<div v-if="editor.item.type === 'point'" class="color-picker">
<label>选择颜色:</label>
<input type="color" v-model="editor.color" />
</div>
<div class="controls">
<!-- <button @click="addRandomPoint">添加随机点位</button> -->
<button @click="clearAllPoints">清除所有点位</button>
<button @click="toggleMode">
切换模式: {{ isDragCreationMode ? '点击选择' : '拖拽创建' }}
</button>
<p id="info">{{ infoText }}</p>
<p v-if="isDragCreationMode" class="mode-indicator">
在拖拽创建模式下,按住鼠标拖动可以创建点位
</p>
</div>
<div v-if="editor.item.type === 'image'" class="image-upload">
<label>更换图片:</label>
<input type="file" accept="image/*" @change="handleImageChange" />
<div class="preview">
<img :src="editor.imageSrc" v-if="editor.imageSrc" />
</div>
</div>
<div class="size-control">
<label>大小:</label>
<input
type="range"
v-model.number="editor.size"
:min="minSize"
:max="maxSize"
/>
<span>{{ editor.size }}px</span>
</div>
<!-- 颜色选择器弹窗 -->
<div v-if="colorPicker.visible" class="color-picker-modal">
<div class="color-picker">
<h3>选择新颜色</h3>
<input type="color" v-model="colorPicker.selectedColor" />
<div class="button-group">
<button @click="confirmColorChange">确定</button>
<button @click="cancelColorChange">取消</button>
<button @click="confirmEdit">确定</button>
<button @click="cancelEdit">取消</button>
</div>
</div>
</div>
......@@ -68,235 +130,368 @@
<script>
export default {
name: 'PointCanvasWithContextMenu',
name: 'CanvasWithManualPoints',
props: {
width: {
type: Number,
default: 600
default: 800
},
height: {
type: Number,
default: 400
default: 600
},
fixedSize: {
type: Number,
default: 10
},
initialPoints: {
type: Array,
default: () => []
backgroundImage: {
type: String,
default: ''
}
},
data() {
return {
ctx: null,
points: [],
isDragCreationMode: false,
form: {},
dialogFormVisible: false,
items: [], // 包含所有点位和图片
isDragging: false,
isAddMode: false, // 手动添加点位模式
dragStartX: 0,
dragStartY: 0,
currentDragPoint: null,
currentDragItem: null,
animationFrameId: null,
infoText: '点击画布上的点位会有交互效果',
// 右键菜单相关
infoText: '点击或右键点击元素进行交互',
formData: [{ label: '', value: '' }],
// 右键菜单
contextMenu: {
visible: false,
x: 0,
y: 0,
point: null
item: null
},
// 颜色选择器相关
colorPicker: {
// 编辑器
editor: {
visible: false,
selectedColor: '#ff0000',
point: null
}
item: null,
color: '#ff0000',
imageSrc: '',
size: this.fixedSize
},
// 常量
minSize: 20,
maxSize: 100
}
},
mounted() {
this.initCanvas()
this.drawAllPoints()
// 添加初始点位
if (this.initialPoints.length > 0) {
this.points = this.initialPoints.map(p => new Point(p.x, p.y, p.radius))
} else {
for (let i = 0; i < 5; i++) {
this.addRandomPoint()
}
}
// 点击其他地方关闭右键菜单
this.drawAllItems()
document.addEventListener('click', this.closeContextMenu)
this.drawBackground()
},
beforeDestroy() {
cancelAnimationFrame(this.animationFrameId)
document.removeEventListener('click', this.closeContextMenu)
},
methods: {
perintPoint() {
const index = this.points.indexOf(this.contextMenu.point)
this.$emit('perintPoint', this.points[index])
},
initCanvas() {
const canvas = this.$refs.canvas
this.ctx = canvas.getContext('2d')
},
perintPoint() {
this.dialogFormVisible = true
// const index = this.points.indexOf(this.contextMenu.point)
// this.$emit('perintPoint', this.points[index])
},
addRandomPoint() {
const radius = Math.floor(Math.random() * 15 + 5)
const x = Math.random() * (this.width - radius * 2) + radius
const y = Math.random() * (this.height - radius * 2) + radius
// 切换手动添加点位模式
toggleAddMode() {
this.isAddMode = !this.isAddMode
this.infoText = this.isAddMode
? '手动添加模式: 点击画布添加点位'
: '已退出手动添加模式'
},
// 手动添加点位
addPointAt(x, y) {
// 调整坐标使点击位置成为点位的中心
const adjustedX = x - this.fixedSize / 2
const adjustedY = y - this.fixedSize / 2
this.items.push({
type: 'point',
x: adjustedX,
y: adjustedY,
size: this.fixedSize,
color: this.getRandomColor(),
isActive: false
})
this.points.push(new Point(x, y, radius))
this.infoText = '添加了随机点位'
this.infoText = `添加了点位 (${Math.round(x)}, ${Math.round(y)})`
},
addPointAt(x, y, radius = 10) {
const point = new Point(x, y, radius)
this.points.push(point)
return point
// 添加随机点位
addRandomPoint() {
const size = this.fixedSize
const x = Math.random() * (this.width - size)
const y = Math.random() * (this.height - size)
this.items.push({
type: 'point',
x,
y,
size,
color: this.getRandomColor(),
isActive: false
})
this.infoText = `添加了随机点位 (${Math.round(x)}, ${Math.round(y)})`
},
clearAllPoints() {
this.points = []
this.ctx.clearRect(0, 0, this.width, this.height)
this.infoText = '已清除所有点位'
// 上传图片
uploadImage() {
this.$refs.fileInput.click()
},
toggleMode() {
this.isDragCreationMode = !this.isDragCreationMode
this.isDragging = false
this.currentDragPoint = null
this.infoText = `当前模式: ${
this.isDragCreationMode ? '拖拽创建点位' : '点击选择点位'
}`
handleImageUpload(e) {
const file = e.target.files[0]
if (!file) return
const reader = new FileReader()
reader.onload = event => {
const img = new Image()
img.onload = () => {
const size = this.fixedSize
const x = Math.random() * (this.width - size)
const y = Math.random() * (this.height - size)
this.items.push({
type: 'image',
x,
y,
size,
image: img,
isActive: false
})
this.infoText = `添加了图片 (${Math.round(x)}, ${Math.round(y)})`
}
img.src = event.target.result
}
reader.readAsDataURL(file)
// 清空input以便重复选择同一文件
e.target.value = ''
},
drawAllPoints() {
// 绘制所有元素
drawAllItems() {
this.ctx.clearRect(0, 0, this.width, this.height)
this.points.forEach(point => point.draw(this.ctx))
if (this.isDragCreationMode && this.isDragging && this.currentDragPoint) {
this.currentDragPoint.draw(this.ctx)
// 先绘制非激活状态的元素
this.items
.filter(item => !item.isActive)
.forEach(item => {
this.drawItem(item)
})
// 最后绘制激活状态的元素(显示在最上层)
this.items
.filter(item => item.isActive)
.forEach(item => {
this.drawItem(item)
})
// 在添加模式下显示提示
if (this.isAddMode) {
this.ctx.fillStyle = 'rgba(0, 0, 0, 0.7)'
this.ctx.font = '16px Arial'
this.ctx.fillText('点击画布添加点位', 10, 30)
}
this.animationFrameId = requestAnimationFrame(this.drawAllItems)
},
// 绘制单个元素
drawItem(item) {
this.ctx.save()
if (item.type === 'point') {
// 绘制点位
this.ctx.beginPath()
this.ctx.moveTo(this.dragStartX, this.dragStartY)
this.ctx.lineTo(this.currentDragPoint.x, this.currentDragPoint.y)
this.ctx.strokeStyle = 'rgba(0, 0, 0, 0.3)'
this.ctx.setLineDash([5, 3])
this.ctx.arc(
item.x + item.size / 2,
item.y + item.size / 2,
item.size / 2,
0,
Math.PI * 2
)
this.ctx.fillStyle = item.isActive ? '#ff0000' : item.color
this.ctx.fill()
this.ctx.strokeStyle = '#333'
this.ctx.stroke()
this.ctx.setLineDash([])
// 激活状态动画
if (item.isActive) {
item.size = this.fixedSize * (1 + Math.sin(Date.now() / 300) * 0.1)
} else {
item.size = this.fixedSize
}
} else if (item.type === 'image') {
// 绘制图片
this.ctx.drawImage(item.image, item.x, item.y, item.size, item.size)
// 绘制边框
if (item.isActive) {
this.ctx.strokeStyle = '#ff0000'
this.ctx.lineWidth = 2
this.ctx.strokeRect(item.x, item.y, item.size, item.size)
}
}
this.animationFrameId = requestAnimationFrame(this.drawAllPoints)
this.ctx.restore()
},
handleClick(e) {
if (this.isDragCreationMode) return
// 获取随机颜色
getRandomColor() {
const r = Math.floor(Math.random() * 200 + 55)
const g = Math.floor(Math.random() * 200 + 55)
const b = Math.floor(Math.random() * 200 + 55)
return `rgb(${r}, ${g}, ${b})`
},
drawBackground() {
const canvas = this.$refs.canvas;
const ctx = canvas.getContext('2d');
const image = new Image();
image.src = this.background; // 设置你的图片路径
image.onload = () => {
ctx.drawImage(image, 0, 0, canvas.width, canvas.height);
};
},
// 清除所有
clearAll() {
this.items = []
this.infoText = '已清除所有元素'
},
// 点击处理
handleClick(e) {
const rect = this.$refs.canvas.getBoundingClientRect()
const x = e.clientX - rect.left
const y = e.clientY - rect.top
let clickedPoint = null
// 手动添加模式
if (this.isAddMode) {
this.addPointAt(x, y)
return
}
this.points.forEach(point => {
point.isActive = false
// 取消所有元素的激活状态
this.items.forEach(item => {
item.isActive = false
})
for (let i = this.points.length - 1; i >= 0; i--) {
if (this.points[i].contains(x, y)) {
clickedPoint = this.points[i]
clickedPoint.isActive = true
this.infoText = `点击了点位 (${Math.round(
clickedPoint.x
)}, ${Math.round(clickedPoint.y)})`
// 检查点击的元素(从最上层开始检查)
for (let i = this.items.length - 1; i >= 0; i--) {
const item = this.items[i]
if (this.isPointInItem(x, y, item)) {
item.isActive = true
this.infoText = `选中了${
item.type === 'point' ? '点位' : '图片'
} (${Math.round(item.x)}, ${Math.round(item.y)})`
break
}
}
},
if (!clickedPoint) {
this.infoText = '点击空白区域'
}
// 检查点是否在元素内
isPointInItem(x, y, item) {
return (
x >= item.x &&
x <= item.x + item.size &&
y >= item.y &&
y <= item.y + item.size
)
},
// 拖拽开始
handleMouseDown(e) {
if (!this.isDragCreationMode) return
if (this.isAddMode) return
const rect = this.$refs.canvas.getBoundingClientRect()
this.dragStartX = e.clientX - rect.left
this.dragStartY = e.clientY - rect.top
let clickedExisting = false
for (let i = this.points.length - 1; i >= 0; i--) {
if (this.points[i].contains(this.dragStartX, this.dragStartY)) {
clickedExisting = true
// 检查是否点击了元素
for (let i = this.items.length - 1; i >= 0; i--) {
const item = this.items[i]
if (this.isPointInItem(this.dragStartX, this.dragStartY, item)) {
this.isDragging = true
this.currentDragItem = item
item.isActive = true
break
}
}
if (!clickedExisting) {
this.isDragging = true
this.currentDragPoint = this.addPointAt(
this.dragStartX,
this.dragStartY,
5
)
this.infoText = '拖动鼠标调整点位大小'
}
},
// 拖拽移动
handleMouseMove(e) {
if (
!this.isDragCreationMode ||
!this.isDragging ||
!this.currentDragPoint
)
return
if (!this.isDragging || !this.currentDragItem || this.isAddMode) return
const rect = this.$refs.canvas.getBoundingClientRect()
const x = e.clientX - rect.left
const y = e.clientY - rect.top
const distance = Math.sqrt(
Math.pow(x - this.dragStartX, 2) + Math.pow(y - this.dragStartY, 2)
// 计算移动距离
const dx = x - this.dragStartX
const dy = y - this.dragStartY
// 更新元素位置
this.currentDragItem.x += dx
this.currentDragItem.y += dy
// 限制在画布内
this.currentDragItem.x = Math.max(
0,
Math.min(this.width - this.currentDragItem.size, this.currentDragItem.x)
)
this.currentDragItem.y = Math.max(
0,
Math.min(
this.height - this.currentDragItem.size,
this.currentDragItem.y
)
)
this.currentDragPoint.originalRadius = Math.min(Math.max(distance, 5), 50)
this.currentDragPoint.x = this.dragStartX
this.currentDragPoint.y = this.dragStartY
// 更新起始位置
this.dragStartX = x
this.dragStartY = y
},
// 拖拽结束
handleMouseUp() {
if (!this.isDragCreationMode || !this.isDragging) return
this.isDragging = false
this.infoText = `已创建点位 (${Math.round(
this.currentDragPoint.x
)}, ${Math.round(this.currentDragPoint.y)}), 半径: ${Math.round(
this.currentDragPoint.originalRadius
)}`
this.currentDragPoint = null
this.currentDragItem = null
},
// 鼠标离开画布
handleMouseLeave() {
if (this.isDragging) {
if (this.currentDragPoint) {
this.points.pop()
}
this.isDragging = false
this.currentDragPoint = null
}
this.isDragging = false
this.currentDragItem = null
},
// 右键菜单相关方法
// 右键菜单
handleContextMenu(e) {
const rect = this.$refs.canvas.getBoundingClientRect()
const x = e.clientX - rect.left
const y = e.clientY - rect.top
// 查找点击的点位
for (let i = this.points.length - 1; i >= 0; i--) {
if (this.points[i].contains(x, y)) {
// 查找点击的元素
for (let i = this.items.length - 1; i >= 0; i--) {
const item = this.items[i]
if (this.isPointInItem(x, y, item)) {
e.preventDefault()
// 显示右键菜单
......@@ -304,15 +499,13 @@ export default {
visible: true,
x: e.clientX,
y: e.clientY,
point: this.points[i]
item: item
}
// 高亮选中的点位
this.points.forEach(p => (p.isActive = false))
this.points[i].isActive = true
this.infoText = `右键点击了点位 (${Math.round(
this.points[i].x
)}, ${Math.round(this.points[i].y)})`
// 高亮选中的元素
this.items.forEach(i => (i.isActive = false))
item.isActive = true
this.infoText = `右键点击了${item.type === 'point' ? '点位' : '图片'}`
return
}
......@@ -327,144 +520,164 @@ export default {
},
// 右键菜单操作
changePointColor() {
this.colorPicker = {
changeColor() {
const item = this.contextMenu.item
this.editor = {
visible: true,
selectedColor: this.contextMenu.point.color,
point: this.contextMenu.point
item: item,
color: item.type === 'point' ? item.color : '#ff0000',
imageSrc: item.type === 'image' ? item.image.src : '',
size: item.size
}
this.closeContextMenu()
},
deletePoint() {
const index = this.points.indexOf(this.contextMenu.point)
deleteItem() {
const index = this.items.indexOf(this.contextMenu.item)
if (index !== -1) {
this.points.splice(index, 1)
this.infoText = `已删除点位 (${Math.round(
this.contextMenu.point.x
)}, ${Math.round(this.contextMenu.point.y)})`
this.items.splice(index, 1)
this.infoText = '已删除元素'
}
this.closeContextMenu()
},
clonePoint() {
const original = this.contextMenu.point
const newPoint = new Point(
original.x + original.radius * 1.5,
original.y + original.radius * 1.5,
original.originalRadius
)
newPoint.color = original.color
this.points.push(newPoint)
this.infoText = `已复制点位到 (${Math.round(newPoint.x)}, ${Math.round(
newPoint.y
)})`
cloneItem() {
const original = this.contextMenu.item
const offset = this.fixedSize * 0.3
if (original.type === 'point') {
this.items.push({
...original,
x: original.x + offset,
y: original.y + offset,
isActive: false
})
} else if (original.type === 'image') {
const img = new Image()
img.src = original.image.src
this.items.push({
...original,
x: original.x + offset,
y: original.y + offset,
image: img,
isActive: false
})
}
this.infoText = '已复制元素'
this.closeContextMenu()
},
addRandomPointNearby() {
const original = this.contextMenu.point
const radius = Math.floor(Math.random() * 10 + 5)
const angle = Math.random() * Math.PI * 2
const distance = original.radius * 2 + Math.random() * 50
const x = original.x + Math.cos(angle) * distance
const y = original.y + Math.sin(angle) * distance
this.addPointAt(x, y, radius)
this.infoText = `已在附近添加随机点位 (${Math.round(x)}, ${Math.round(
y
)})`
bringToFront() {
const item = this.contextMenu.item
const index = this.items.indexOf(item)
if (index !== -1) {
this.items.splice(index, 1)
this.items.push(item)
this.infoText = '已将元素置顶'
}
this.closeContextMenu()
},
// 颜色选择器相关方法
confirmColorChange() {
this.colorPicker.point.color = this.colorPicker.selectedColor
this.colorPicker.visible = false
this.infoText = `已更改点位颜色为 ${this.colorPicker.selectedColor}`
// 图片更改处理
handleImageChange(e) {
const file = e.target.files[0]
if (!file) return
const reader = new FileReader()
reader.onload = event => {
const img = new Image()
img.onload = () => {
this.editor.imageSrc = event.target.result
this.editor.item.image = img
}
img.src = event.target.result
}
reader.readAsDataURL(file)
},
cancelColorChange() {
this.colorPicker.visible = false
}
}
}
// 编辑器确认
confirmEdit() {
const item = this.editor.item
class Point {
constructor(x, y, radius = 10) {
this.x = x
this.y = y
this.radius = radius
this.originalRadius = radius
this.color = this.getRandomColor()
this.isActive = false
}
if (item.type === 'point') {
item.color = this.editor.color
}
getRandomColor() {
const r = Math.floor(Math.random() * 200 + 55)
const g = Math.floor(Math.random() * 200 + 55)
const b = Math.floor(Math.random() * 200 + 55)
return `rgb(${r}, ${g}, ${b})`
}
item.size = this.editor.size
this.editor.visible = false
this.infoText = '已更新元素属性'
},
draw(ctx) {
ctx.beginPath()
ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2)
ctx.fillStyle = this.isActive ? '#ff0000' : this.color
ctx.fill()
ctx.strokeStyle = '#333'
ctx.stroke()
if (this.isActive) {
this.radius = this.originalRadius * (1 + Math.sin(Date.now() / 200) * 0.2)
} else {
this.radius = this.originalRadius
// 编辑器取消
cancelEdit() {
this.editor.visible = false
}
}
contains(x, y) {
const distance = Math.sqrt((x - this.x) ** 2 + (y - this.y) ** 2)
return distance <= this.radius
}
}
</script>
<style scoped>
.container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
text-align: center;
position: relative;
/* display: flex;
flex-direction: column; */
/* align-items: center; */
font-family: Arial, sans-serif;
max-width: 1000px;
margin: 0 auto;
padding: 20px;
}
canvas {
background-color: white;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
cursor: default;
h1 {
margin-bottom: 20px;
color: #333;
}
.controls {
margin-top: 20px;
.toolbar {
margin-bottom: 15px;
display: flex;
gap: 10px;
align-items: center;
flex-wrap: wrap;
}
button {
.toolbar button {
padding: 8px 16px;
margin: 0 5px;
background-color: #4caf50;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
}
.mode-indicator {
margin-top: 10px;
font-weight: bold;
color: #333;
.toolbar button.active {
background-color: #2196f3;
}
#info {
margin-top: 10px;
color: #333;
.toolbar button:hover {
background-color: #45a049;
}
.toolbar button.active:hover {
background-color: #0b7dda;
}
.toolbar .info {
margin-left: auto;
color: #666;
font-size: 14px;
}
.canvas-container {
border: 1px solid #ddd;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
}
canvas {
background-color: white;
display: block;
cursor: crosshair;
}
/* 右键菜单样式 */
......@@ -481,14 +694,15 @@ button {
.menu-item {
padding: 8px 12px;
cursor: pointer;
font-size: 14px;
}
.menu-item:hover {
background-color: #f5f5f5;
}
/* 颜色选择器弹窗 */
.color-picker-modal {
/* 编辑器模态框 */
.editor-modal {
position: fixed;
top: 0;
left: 0;
......@@ -501,34 +715,67 @@ button {
z-index: 1001;
}
.color-picker {
.editor-content {
background-color: white;
padding: 20px;
border-radius: 8px;
box-shadow: 0 0 20px rgba(0, 0, 0, 0.3);
width: 350px;
max-width: 90%;
}
.color-picker h3 {
.editor-content h3 {
margin-top: 0;
color: #333;
}
.color-picker input[type='color'] {
.color-picker,
.image-upload,
.size-control {
margin-bottom: 15px;
}
.color-picker input[type='color'],
.size-control input[type='range'] {
margin-top: 5px;
width: 100%;
margin: 10px 0;
}
.image-upload .preview {
margin-top: 10px;
width: 100px;
height: 100px;
border: 1px solid #ddd;
display: flex;
align-items: center;
justify-content: center;
}
.image-upload .preview img {
max-width: 100%;
max-height: 100%;
}
.button-group {
display: flex;
justify-content: space-between;
justify-content: flex-end;
gap: 10px;
margin-top: 20px;
}
.button-group button {
flex: 1;
margin: 0 5px;
padding: 8px 16px;
border: none;
border-radius: 4px;
cursor: pointer;
}
.button-group button:first-child {
background-color: #4caf50;
color: white;
}
.right-bar {
position: absolute;
right: 150px;
top: 20px;
.button-group button:last-child {
background-color: #f44336;
color: white;
}
</style>
......@@ -61,7 +61,7 @@ module.exports = {
[process.env.VUE_APP_BASE_API]: {
// target: `http://49.232.167.247:20014/`,
// target: `http://192.168.10.185:8084/`,
target: `http://192.168.1.19:8082/`,
target: `http://106.3.97.198:20162/`,
pathRewrite: {
['^' + process.env.VUE_APP_BASE_API]: ''
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment