Commit bc5d2b9a authored by jiaxu.yan's avatar jiaxu.yan

feat:修改查看模式

parent 53460834
<template>
<div class="canvas-container">
<!-- 工具栏 -->
<div class="toolbar">
<button @click="toggleViewMode" :class="{ 'active': isViewMode }">
{{ isViewMode ? '退出查看模式' : '进入查看模式' }}
</button>
<button @click="addRandomPoint" :disabled="isViewMode">添加随机点</button>
<button @click="clearAll" :disabled="isViewMode">清除所有</button>
<span class="info-text">{{ infoText }}</span>
</div>
<!-- 画布 -->
<canvas
ref="canvas"
:width="width"
:height="height"
@click="handleCanvasClick"
@mousemove="handleMouseMove"
></canvas>
<!-- 查看模式弹窗 -->
<div
v-if="viewModal.visible"
class="view-modal"
:style="{
left: `${viewModal.position.x}px`,
top: `${viewModal.position.y}px`
}"
>
<div class="modal-header">
<h3>点位详情</h3>
<button class="close-btn" @click="closeViewModal">×</button>
</div>
<div class="modal-content">
<div class="detail-row">
<span class="detail-label">位置:</span>
<span class="detail-value">({{ Math.round(viewModal.point.x) }}, {{ Math.round(viewModal.point.y) }})</span>
</div>
<div class="detail-row">
<span class="detail-label">大小:</span>
<span class="detail-value">{{ viewModal.point.radius }}px</span>
</div>
<div class="detail-row">
<span class="detail-label">颜色:</span>
<span
class="color-preview"
:style="{ backgroundColor: viewModal.point.color }"
></span>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'InteractiveCanvas',
props: {
width: {
type: Number,
default: 800
},
height: {
type: Number,
default: 600
},
initialPoints: {
type: Array,
default: () => []
}
},
data() {
return {
ctx: null,
points: [],
isViewMode: false,
animationFrameId: null,
infoText: '点击画布添加点位或查看详情',
viewModal: {
visible: false,
point: null,
position: { x: 0, y: 0 }
},
hoveredPoint: null
}
},
mounted() {
this.initCanvas()
// 初始化点位
this.points = this.initialPoints.length
? this.initialPoints.map(p => ({ ...p }))
: this.generateRandomPoints(5)
this.startAnimation()
},
beforeDestroy() {
cancelAnimationFrame(this.animationFrameId)
},
methods: {
initCanvas() {
const canvas = this.$refs.canvas
this.ctx = canvas.getContext('2d')
},
startAnimation() {
const animate = () => {
this.drawCanvas()
this.animationFrameId = requestAnimationFrame(animate)
}
animate()
},
drawCanvas() {
// 清空画布
this.ctx.clearRect(0, 0, this.width, this.height)
// 绘制所有点位
this.points.forEach(point => {
this.drawPoint(point)
})
// 在查看模式下绘制连线
if (this.isViewMode && this.viewModal.visible) {
this.drawConnectorLine(this.viewModal.point)
}
// 绘制悬停效果
if (this.hoveredPoint) {
this.drawHoverEffect(this.hoveredPoint)
}
// 绘制模式提示
this.drawModeHint()
},
drawPoint(point) {
this.ctx.beginPath()
this.ctx.arc(point.x, point.y, point.radius, 0, Math.PI * 2)
// 查看模式下选中点位的特殊样式
if (this.isViewMode && this.viewModal.point === point) {
this.ctx.fillStyle = this.lightenColor(point.color, 20)
this.ctx.strokeStyle = '#e74c3c'
this.ctx.lineWidth = 3
}
// 悬停点位的样式
else if (this.hoveredPoint === point) {
this.ctx.fillStyle = this.lightenColor(point.color, 10)
this.ctx.strokeStyle = '#3498db'
this.ctx.lineWidth = 2
}
// 普通点位的样式
else {
this.ctx.fillStyle = point.color
this.ctx.strokeStyle = '#2c3e50'
this.ctx.lineWidth = 1
}
this.ctx.fill()
this.ctx.stroke()
// 激活状态的脉冲动画
if (point.isActive) {
point.radius = point.baseRadius * (1 + Math.sin(Date.now() / 300) * 0.2)
} else {
point.radius = point.baseRadius
}
},
drawConnectorLine(point) {
const canvasPos = this.$refs.canvas.getBoundingClientRect()
const pointScreenX = canvasPos.left + point.x
const pointScreenY = canvasPos.top + point.y
// 计算弹窗连接点位置 (右侧中间)
const modalConnectorX = this.viewModal.position.x
const modalConnectorY = this.viewModal.position.y + 40 // 40是标题高度
// 创建离屏canvas绘制箭头
const lineCanvas = document.createElement('canvas')
const lineCtx = lineCanvas.getContext('2d')
// 计算线长和角度
const dx = modalConnectorX - pointScreenX
const dy = modalConnectorY - pointScreenY
const lineLength = Math.sqrt(dx * dx + dy * dy)
const angle = Math.atan2(dy, dx)
lineCanvas.width = lineLength
lineCanvas.height = lineLength
// 绘制连线
lineCtx.beginPath()
lineCtx.moveTo(0, 0)
lineCtx.lineTo(lineLength, 0)
// 绘制箭头
const arrowSize = 10
lineCtx.moveTo(lineLength, 0)
lineCtx.lineTo(
lineLength - arrowSize * Math.cos(angle - Math.PI / 6),
-arrowSize * Math.sin(angle - Math.PI / 6)
)
lineCtx.moveTo(lineLength, 0)
lineCtx.lineTo(
lineLength - arrowSize * Math.cos(angle + Math.PI / 6),
-arrowSize * Math.sin(angle + Math.PI / 6)
)
lineCtx.strokeStyle = '#3498db'
lineCtx.lineWidth = 2
lineCtx.stroke()
// 在主canvas上绘制
this.ctx.save()
this.ctx.translate(pointScreenX - canvasPos.left, pointScreenY - canvasPos.top)
this.ctx.rotate(angle)
this.ctx.drawImage(lineCanvas, 0, -1) // -1是为了让线居中
this.ctx.restore()
},
drawHoverEffect(point) {
this.ctx.beginPath()
this.ctx.arc(point.x, point.y, point.radius + 8, 0, Math.PI * 2)
this.ctx.strokeStyle = 'rgba(52, 152, 219, 0.5)'
this.ctx.lineWidth = 2
this.ctx.stroke()
},
drawModeHint() {
this.ctx.fillStyle = 'rgba(0, 0, 0, 0.7)'
this.ctx.font = '16px Arial'
if (this.isViewMode) {
this.ctx.fillText('查看模式: 点击点位查看详情', 10, 30)
if (this.viewModal.visible) {
this.ctx.fillText('点击空白处关闭弹窗', 10, 60)
}
} else {
this.ctx.fillText('编辑模式: 点击添加点位', 10, 30)
}
},
toggleViewMode() {
this.isViewMode = !this.isViewMode
this.viewModal.visible = false
this.infoText = this.isViewMode
? '查看模式: 点击点位查看详情'
: '编辑模式: 点击添加点位'
},
handleCanvasClick(e) {
const rect = this.$refs.canvas.getBoundingClientRect()
const x = e.clientX - rect.left
const y = e.clientY - rect.top
if (this.isViewMode) {
// 查看模式下显示点位详情
const clickedPoint = this.getPointAtPosition(x, y)
if (clickedPoint) {
this.openViewModal(clickedPoint, e.clientX, e.clientY)
} else {
this.closeViewModal()
}
} else {
// 编辑模式下添加点位
this.addPoint(x, y)
}
},
handleMouseMove(e) {
const rect = this.$refs.canvas.getBoundingClientRect()
const x = e.clientX - rect.left
const y = e.clientY - rect.top
// 更新悬停状态
this.hoveredPoint = this.isViewMode ? this.getPointAtPosition(x, y) : null
},
openViewModal(point, clickX, clickY) {
// 计算弹窗位置 (确保在视口内)
const modalWidth = 250
const modalHeight = 150
const padding = 20
let modalX = clickX + padding
let modalY = clickY + padding
// 如果右边超出屏幕,向左显示
if (modalX + modalWidth > window.innerWidth) {
modalX = clickX - modalWidth - padding
}
// 如果底部超出屏幕,向上显示
if (modalY + modalHeight > window.innerHeight) {
modalY = clickY - modalHeight - padding
}
// 确保不会超出屏幕左边和上边
modalX = Math.max(padding, modalX)
modalY = Math.max(padding, modalY)
this.viewModal = {
visible: true,
point: point,
position: { x: modalX, y: modalY }
}
// 激活点位效果
this.points.forEach(p => p.isActive = false)
point.isActive = true
},
closeViewModal() {
this.viewModal.visible = false
if (this.viewModal.point) {
this.viewModal.point.isActive = false
}
},
addPoint(x, y) {
const newPoint = {
x: x,
y: y,
baseRadius: 15 + Math.random() * 10,
radius: 15 + Math.random() * 10,
color: this.getRandomColor(),
isActive: false
}
this.points.push(newPoint)
this.infoText = `添加了点位 (${Math.round(x)}, ${Math.round(y)})`
},
addRandomPoint() {
const x = Math.random() * (this.width - 40) + 20
const y = Math.random() * (this.height - 40) + 20
this.addPoint(x, y)
},
generateRandomPoints(count) {
return Array.from({ length: count }, () => {
const x = Math.random() * (this.width - 40) + 20
const y = Math.random() * (this.height - 40) + 20
return {
x: x,
y: y,
baseRadius: 10 + Math.random() * 10,
radius: 10 + Math.random() * 10,
color: this.getRandomColor(),
isActive: false
}
})
},
clearAll() {
this.points = []
this.infoText = '已清除所有点位'
},
getPointAtPosition(x, y) {
// 从最上层开始检查
for (let i = this.points.length - 1; i >= 0; i--) {
const point = this.points[i]
const distance = Math.sqrt(
Math.pow(x - point.x, 2) + Math.pow(y - point.y, 2)
if (distance <= point.radius) {
return point
}
}
return null
},
getRandomColor() {
const hue = Math.floor(Math.random() * 360)
return `hsl(${hue}, 70%, 60%)`
},
lightenColor(color, percent) {
// 简单实现颜色变亮效果
if (color.startsWith('hsl')) {
return color.replace(/\d+%\)/, `${Math.min(100, percent + 60)}%`)
}
return color
}
}
}
</script>
<style scoped>
.canvas-container {
font-family: Arial, sans-serif;
max-width: 100%;
display: flex;
flex-direction: column;
align-items: center;
}
.toolbar {
margin-bottom: 15px;
display: flex;
gap: 10px;
align-items: center;
flex-wrap: wrap;
justify-content: center;
}
.toolbar button {
padding: 8px 16px;
background-color: #3498db;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
transition: background-color 0.2s;
}
.toolbar button:hover:not(:disabled) {
background-color: #2980b9;
}
.toolbar button.active {
background-color: #2ecc71;
}
.toolbar button.active:hover {
background-color: #27ae60;
}
.toolbar button:disabled {
background-color: #95a5a6;
cursor: not-allowed;
}
.info-text {
margin-left: 15px;
color: #7f8c8d;
font-size: 14px;
}
canvas {
border: 1px solid #bdc3c7;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
background-color: white;
cursor: crosshair;
}
.view-modal {
position: fixed;
background-color: white;
border-radius: 8px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
z-index: 1000;
width: 250px;
overflow: hidden;
animation: modalFadeIn 0.2s ease-out;
}
@keyframes modalFadeIn {
from {
opacity: 0;
transform: translateY(10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.modal-header {
padding: 12px 15px;
background-color: #3498db;
color: white;
display: flex;
justify-content: space-between;
align-items: center;
}
.modal-header h3 {
margin: 0;
font-size: 16px;
}
.close-btn {
background: none;
border: none;
color: white;
font-size: 20px;
cursor: pointer;
line-height: 1;
padding: 0 0 4px 0;
width: 24px;
height: 24px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
}
.close-btn:hover {
background-color: rgba(255, 255, 255, 0.2);
}
.modal-content {
padding: 15px;
}
.detail-row {
display: flex;
margin-bottom: 10px;
align-items: center;
}
.detail-label {
font-weight: bold;
color: #7f8c8d;
width: 60px;
flex-shrink: 0;
}
.detail-value {
color: #2c3e50;
}
.color-preview {
width: 20px;
height: 20px;
border-radius: 4px;
border: 1px solid #ddd;
display: inline-block;
}
</style>
......@@ -45,6 +45,9 @@
<script>
import CanvasWithExternalBackground from '@/components/CanvasWithExternalBackground.vue'
import CanvasWithExternalBackground from '@/components/CanvasWithExternalBackground.vue'
import pic1 from '@/assets/image/变压器抠图-1.png'
import pic2 from '@/assets/image/变压器抠图-2.png'
export default {
......
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