Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
B
byq_pc
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
张伯涛
byq_pc
Commits
076a4d68
Commit
076a4d68
authored
Apr 28, 2025
by
jiaxu.yan
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
新增套组
parent
4e21d926
Changes
7
Show whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
1309 additions
and
301 deletions
+1309
-301
变压器抠图-1.png
src/assets/image/变压器抠图-1.png
+0
-0
变压器抠图-2.png
src/assets/image/变压器抠图-2.png
+0
-0
index.vue
src/components/card-box/index.vue
+46
-0
index.vue
src/views/index/index.vue
+62
-11
index copy 2.vue
src/views/makePie/index copy 2.vue
+664
-0
index.vue
src/views/makePie/index.vue
+536
-289
vue.config.js
vue.config.js
+1
-1
No files found.
src/assets/image/变压器抠图-1.png
0 → 100644
View file @
076a4d68
1.47 MB
src/assets/image/变压器抠图-2.png
0 → 100644
View file @
076a4d68
622 KB
src/components/card-box/index.vue
0 → 100644
View file @
076a4d68
<
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
>
src/views/index/index.vue
View file @
076a4d68
<
template
>
<
template
>
<div>
<div
class=
"pageindex"
>
<el-row
:gutter=
"20"
>
<el-col
:span=
"12"
>
<div
class=
"left-bar"
>
<div
class=
"left-bar"
>
<el-button
:type=
"type == 1"
@
click=
"getType(1)"
>
第一视角
</el-button>
<button
<el-button
:type=
"type == 2"
@
click=
"getType(2)"
>
第二视角
</el-button>
:class=
"type == 1 ? 'active-btn' : 'default-btn'"
<el-button
:type=
"type == 3"
@
click=
"getType(3)"
>
第三视角
</el-button>
@
click=
"getType(1)"
<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>
</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>
</div>
<pie>
</pie>
<pie
ref=
"zutai"
:backgroundImage=
"pic1"
>
</pie>
</el-col>
</el-row>
</div>
</div>
</
template
>
</
template
>
<
script
>
<
script
>
import
pie
from
'@/views/makePie/index.vue'
import
pie
from
'@/views/makePie/index.vue'
import
pic1
from
'@/assets/image/变压器抠图-1.png'
import
pic2
from
'@/assets/image/变压器抠图-2.png'
export
default
{
export
default
{
components
:
{
components
:
{
pie
:
pie
pie
:
pie
...
@@ -20,7 +56,9 @@ export default {
...
@@ -20,7 +56,9 @@ export default {
data
()
{
data
()
{
return
{
return
{
name
:
'特变电压器'
,
name
:
'特变电压器'
,
type
:
1
type
:
1
,
pic1
,
pic2
}
}
},
},
getType
(
e
)
{
getType
(
e
)
{
...
@@ -30,9 +68,22 @@ export default {
...
@@ -30,9 +68,22 @@ export default {
}
}
</
script
>
</
script
>
<
style
lang=
"scss"
scoped
>
<
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
{
.pageindex
{
width
:
100%
;
width
:
100%
;
height
:
100%
;
height
:
100%
;
.index-top
{
.index-top
{
display
:
flex
;
display
:
flex
;
.top-left
{
.top-left
{
...
...
src/views/makePie/index copy 2.vue
0 → 100644
View file @
076a4d68
<
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
>
src/views/makePie/index.vue
View file @
076a4d68
<
template
>
<
template
>
<div
class=
"container"
>
<div
class=
"container"
>
<h1>
Canvas点位交互演示
</h1>
<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/*"
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
<canvas
ref=
"canvas"
ref=
"canvas"
:width=
"width"
:width=
"width"
...
@@ -12,20 +30,8 @@
...
@@ -12,20 +30,8 @@
@
mouseleave=
"handleMouseLeave"
@
mouseleave=
"handleMouseLeave"
@
contextmenu
.
prevent=
"handleContextMenu"
@
contextmenu
.
prevent=
"handleContextMenu"
></canvas>
></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>
<!-- 右键菜单 -->
<!-- 右键菜单 -->
<div
<div
v-if=
"contextMenu.visible"
v-if=
"contextMenu.visible"
...
@@ -35,31 +41,87 @@
...
@@ -35,31 +41,87 @@
top: `${contextMenu.y}px`
top: `${contextMenu.y}px`
}"
}"
>
>
<div
class=
"menu-item"
@
click=
"changePointColor"
>
更改颜色
</div>
<div
class=
"menu-item"
@
click=
"changeColor"
>
更改颜色/图片
</div>
<div
class=
"menu-item"
@
click=
"deletePoint"
>
删除点位
</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
class=
"menu-item"
@
click=
"perintPoint"
>
设置点位看板
</div>
</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"
>
<div
v-if=
"editor.item.type === 'image'"
class=
"image-upload"
>
<!--
<button
@
click=
"addRandomPoint"
>
添加随机点位
</button>
-->
<label>
更换图片:
</label>
<button
@
click=
"clearAllPoints"
>
清除所有点位
</button>
<input
type=
"file"
accept=
"image/*"
@
change=
"handleImageChange"
/>
<button
@
click=
"toggleMode"
>
<div
class=
"preview"
>
切换模式:
{{
isDragCreationMode
?
'点击选择'
:
'拖拽创建'
}}
<img
:src=
"editor.imageSrc"
v-if=
"editor.imageSrc"
/>
</button>
</div>
<p
id=
"info"
>
{{
infoText
}}
</p>
</div>
<p
v-if=
"isDragCreationMode"
class=
"mode-indicator"
>
在拖拽创建模式下,按住鼠标拖动可以创建点位
<div
class=
"size-control"
>
</p>
<label>
大小:
</label>
<input
type=
"range"
v-model
.
number=
"editor.size"
:min=
"minSize"
:max=
"maxSize"
/>
<span>
{{
editor
.
size
}}
px
</span>
</div>
</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"
>
<div
class=
"button-group"
>
<button
@
click=
"confirm
ColorChange
"
>
确定
</button>
<button
@
click=
"confirm
Edit
"
>
确定
</button>
<button
@
click=
"cancel
ColorChange
"
>
取消
</button>
<button
@
click=
"cancel
Edit
"
>
取消
</button>
</div>
</div>
</div>
</div>
</div>
</div>
...
@@ -68,235 +130,368 @@
...
@@ -68,235 +130,368 @@
<
script
>
<
script
>
export
default
{
export
default
{
name
:
'
PointCanvasWithContextMenu
'
,
name
:
'
CanvasWithManualPoints
'
,
props
:
{
props
:
{
width
:
{
width
:
{
type
:
Number
,
type
:
Number
,
default
:
6
00
default
:
8
00
},
},
height
:
{
height
:
{
type
:
Number
,
type
:
Number
,
default
:
400
default
:
600
},
fixedSize
:
{
type
:
Number
,
default
:
10
},
},
initialPoints
:
{
backgroundImage
:
{
type
:
Array
,
type
:
String
,
default
:
()
=>
[]
default
:
''
}
}
},
},
data
()
{
data
()
{
return
{
return
{
ctx
:
null
,
ctx
:
null
,
points
:
[],
form
:
{},
isDragCreationMode
:
false
,
dialogFormVisible
:
false
,
items
:
[],
// 包含所有点位和图片
isDragging
:
false
,
isDragging
:
false
,
isAddMode
:
false
,
// 手动添加点位模式
dragStartX
:
0
,
dragStartX
:
0
,
dragStartY
:
0
,
dragStartY
:
0
,
currentDrag
Point
:
null
,
currentDrag
Item
:
null
,
animationFrameId
:
null
,
animationFrameId
:
null
,
infoText
:
'点击
画布上的点位会有交互效果
'
,
infoText
:
'点击
或右键点击元素进行交互
'
,
formData
:
[{
label
:
''
,
value
:
''
}],
// 右键菜单
相关
// 右键菜单
contextMenu
:
{
contextMenu
:
{
visible
:
false
,
visible
:
false
,
x
:
0
,
x
:
0
,
y
:
0
,
y
:
0
,
point
:
null
item
:
null
},
},
//
颜色选择器相关
//
编辑器
colorPicke
r
:
{
edito
r
:
{
visible
:
false
,
visible
:
false
,
selectedColor
:
'#ff0000'
,
item
:
null
,
point
:
null
color
:
'#ff0000'
,
}
imageSrc
:
''
,
size
:
this
.
fixedSize
},
// 常量
minSize
:
20
,
maxSize
:
100
}
}
},
},
mounted
()
{
mounted
()
{
this
.
initCanvas
()
this
.
initCanvas
()
this
.
drawAllItems
()
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
)
document
.
addEventListener
(
'click'
,
this
.
closeContextMenu
)
this
.
drawBackground
()
},
},
beforeDestroy
()
{
beforeDestroy
()
{
cancelAnimationFrame
(
this
.
animationFrameId
)
cancelAnimationFrame
(
this
.
animationFrameId
)
document
.
removeEventListener
(
'click'
,
this
.
closeContextMenu
)
document
.
removeEventListener
(
'click'
,
this
.
closeContextMenu
)
},
},
methods
:
{
methods
:
{
perintPoint
()
{
const
index
=
this
.
points
.
indexOf
(
this
.
contextMenu
.
point
)
this
.
$emit
(
'perintPoint'
,
this
.
points
[
index
])
},
initCanvas
()
{
initCanvas
()
{
const
canvas
=
this
.
$refs
.
canvas
const
canvas
=
this
.
$refs
.
canvas
this
.
ctx
=
canvas
.
getContext
(
'2d'
)
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
)
toggleAddMode
()
{
const
x
=
Math
.
random
()
*
(
this
.
width
-
radius
*
2
)
+
radius
this
.
isAddMode
=
!
this
.
isAddMode
const
y
=
Math
.
random
()
*
(
this
.
height
-
radius
*
2
)
+
radius
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
=
`添加了点位 (
${
Math
.
round
(
x
)}
,
${
Math
.
round
(
y
)}
)`
this
.
infoText
=
'添加了随机点位'
},
},
addPointAt
(
x
,
y
,
radius
=
10
)
{
// 添加随机点位
const
point
=
new
Point
(
x
,
y
,
radius
)
addRandomPoint
()
{
this
.
points
.
push
(
point
)
const
size
=
this
.
fixedSize
return
point
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
=
[]
uploadImage
()
{
this
.
ctx
.
clearRect
(
0
,
0
,
this
.
width
,
this
.
height
)
this
.
$refs
.
fileInput
.
click
()
this
.
infoText
=
'已清除所有点位'
},
},
toggleMode
()
{
handleImageUpload
(
e
)
{
this
.
isDragCreationMode
=
!
this
.
isDragCreationMode
const
file
=
e
.
target
.
files
[
0
]
this
.
isDragging
=
false
if
(
!
file
)
return
this
.
currentDragPoint
=
null
this
.
infoText
=
`当前模式:
${
const
reader
=
new
FileReader
()
this
.
isDragCreationMode
?
'拖拽创建点位'
:
'点击选择点位'
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
.
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
.
beginPath
()
this
.
ctx
.
moveTo
(
this
.
dragStartX
,
this
.
dragStartY
)
this
.
ctx
.
arc
(
this
.
ctx
.
lineTo
(
this
.
currentDragPoint
.
x
,
this
.
currentDragPoint
.
y
)
item
.
x
+
item
.
size
/
2
,
this
.
ctx
.
strokeStyle
=
'rgba(0, 0, 0, 0.3)'
item
.
y
+
item
.
size
/
2
,
this
.
ctx
.
setLineDash
([
5
,
3
])
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
.
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
rect
=
this
.
$refs
.
canvas
.
getBoundingClientRect
()
const
x
=
e
.
clientX
-
rect
.
left
const
x
=
e
.
clientX
-
rect
.
left
const
y
=
e
.
clientY
-
rect
.
top
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
))
{
for
(
let
i
=
this
.
items
.
length
-
1
;
i
>=
0
;
i
--
)
{
clickedPoint
=
this
.
points
[
i
]
const
item
=
this
.
items
[
i
]
clickedPoint
.
isActive
=
true
if
(
this
.
isPointInItem
(
x
,
y
,
item
))
{
this
.
infoText
=
`点击了点位 (
${
Math
.
round
(
item
.
isActive
=
true
clickedPoint
.
x
this
.
infoText
=
`选中了
${
)}
,
${
Math
.
round
(
clickedPoint
.
y
)}
)`
item
.
type
===
'point'
?
'点位'
:
'图片'
}
(
${
Math
.
round
(
item
.
x
)}
,
${
Math
.
round
(
item
.
y
)}
)`
break
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
)
{
handleMouseDown
(
e
)
{
if
(
!
this
.
isDragCreation
Mode
)
return
if
(
this
.
isAdd
Mode
)
return
const
rect
=
this
.
$refs
.
canvas
.
getBoundingClientRect
()
const
rect
=
this
.
$refs
.
canvas
.
getBoundingClientRect
()
this
.
dragStartX
=
e
.
clientX
-
rect
.
left
this
.
dragStartX
=
e
.
clientX
-
rect
.
left
this
.
dragStartY
=
e
.
clientY
-
rect
.
top
this
.
dragStartY
=
e
.
clientY
-
rect
.
top
let
clickedExisting
=
false
// 检查是否点击了元素
for
(
let
i
=
this
.
points
.
length
-
1
;
i
>=
0
;
i
--
)
{
for
(
let
i
=
this
.
items
.
length
-
1
;
i
>=
0
;
i
--
)
{
if
(
this
.
points
[
i
].
contains
(
this
.
dragStartX
,
this
.
dragStartY
))
{
const
item
=
this
.
items
[
i
]
clickedExisting
=
true
if
(
this
.
isPointInItem
(
this
.
dragStartX
,
this
.
dragStartY
,
item
))
{
this
.
isDragging
=
true
this
.
currentDragItem
=
item
item
.
isActive
=
true
break
break
}
}
}
}
if
(
!
clickedExisting
)
{
this
.
isDragging
=
true
this
.
currentDragPoint
=
this
.
addPointAt
(
this
.
dragStartX
,
this
.
dragStartY
,
5
)
this
.
infoText
=
'拖动鼠标调整点位大小'
}
},
},
// 拖拽移动
handleMouseMove
(
e
)
{
handleMouseMove
(
e
)
{
if
(
if
(
!
this
.
isDragging
||
!
this
.
currentDragItem
||
this
.
isAddMode
)
return
!
this
.
isDragCreationMode
||
!
this
.
isDragging
||
!
this
.
currentDragPoint
)
return
const
rect
=
this
.
$refs
.
canvas
.
getBoundingClientRect
()
const
rect
=
this
.
$refs
.
canvas
.
getBoundingClientRect
()
const
x
=
e
.
clientX
-
rect
.
left
const
x
=
e
.
clientX
-
rect
.
left
const
y
=
e
.
clientY
-
rect
.
top
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
()
{
handleMouseUp
()
{
if
(
!
this
.
isDragCreationMode
||
!
this
.
isDragging
)
return
this
.
isDragging
=
false
this
.
isDragging
=
false
this
.
infoText
=
`已创建点位 (
${
Math
.
round
(
this
.
currentDragItem
=
null
this
.
currentDragPoint
.
x
)}
,
${
Math
.
round
(
this
.
currentDragPoint
.
y
)}
), 半径:
${
Math
.
round
(
this
.
currentDragPoint
.
originalRadius
)}
`
this
.
currentDragPoint
=
null
},
},
// 鼠标离开画布
handleMouseLeave
()
{
handleMouseLeave
()
{
if
(
this
.
isDragging
)
{
if
(
this
.
currentDragPoint
)
{
this
.
points
.
pop
()
}
this
.
isDragging
=
false
this
.
isDragging
=
false
this
.
currentDragPoint
=
null
this
.
currentDragItem
=
null
}
},
},
// 右键菜单
相关方法
// 右键菜单
handleContextMenu
(
e
)
{
handleContextMenu
(
e
)
{
const
rect
=
this
.
$refs
.
canvas
.
getBoundingClientRect
()
const
rect
=
this
.
$refs
.
canvas
.
getBoundingClientRect
()
const
x
=
e
.
clientX
-
rect
.
left
const
x
=
e
.
clientX
-
rect
.
left
const
y
=
e
.
clientY
-
rect
.
top
const
y
=
e
.
clientY
-
rect
.
top
// 查找点击的点位
// 查找点击的元素
for
(
let
i
=
this
.
points
.
length
-
1
;
i
>=
0
;
i
--
)
{
for
(
let
i
=
this
.
items
.
length
-
1
;
i
>=
0
;
i
--
)
{
if
(
this
.
points
[
i
].
contains
(
x
,
y
))
{
const
item
=
this
.
items
[
i
]
if
(
this
.
isPointInItem
(
x
,
y
,
item
))
{
e
.
preventDefault
()
e
.
preventDefault
()
// 显示右键菜单
// 显示右键菜单
...
@@ -304,15 +499,13 @@ export default {
...
@@ -304,15 +499,13 @@ export default {
visible
:
true
,
visible
:
true
,
x
:
e
.
clientX
,
x
:
e
.
clientX
,
y
:
e
.
clientY
,
y
:
e
.
clientY
,
point
:
this
.
points
[
i
]
item
:
item
}
}
// 高亮选中的点位
// 高亮选中的元素
this
.
points
.
forEach
(
p
=>
(
p
.
isActive
=
false
))
this
.
items
.
forEach
(
i
=>
(
i
.
isActive
=
false
))
this
.
points
[
i
].
isActive
=
true
item
.
isActive
=
true
this
.
infoText
=
`右键点击了点位 (
${
Math
.
round
(
this
.
infoText
=
`右键点击了
${
item
.
type
===
'point'
?
'点位'
:
'图片'
}
`
this
.
points
[
i
].
x
)}
,
${
Math
.
round
(
this
.
points
[
i
].
y
)}
)`
return
return
}
}
...
@@ -327,144 +520,164 @@ export default {
...
@@ -327,144 +520,164 @@ export default {
},
},
// 右键菜单操作
// 右键菜单操作
changePointColor
()
{
changeColor
()
{
this
.
colorPicker
=
{
const
item
=
this
.
contextMenu
.
item
this
.
editor
=
{
visible
:
true
,
visible
:
true
,
selectedColor
:
this
.
contextMenu
.
point
.
color
,
item
:
item
,
point
:
this
.
contextMenu
.
point
color
:
item
.
type
===
'point'
?
item
.
color
:
'#ff0000'
,
imageSrc
:
item
.
type
===
'image'
?
item
.
image
.
src
:
''
,
size
:
item
.
size
}
}
this
.
closeContextMenu
()
this
.
closeContextMenu
()
},
},
delete
Point
()
{
delete
Item
()
{
const
index
=
this
.
points
.
indexOf
(
this
.
contextMenu
.
point
)
const
index
=
this
.
items
.
indexOf
(
this
.
contextMenu
.
item
)
if
(
index
!==
-
1
)
{
if
(
index
!==
-
1
)
{
this
.
points
.
splice
(
index
,
1
)
this
.
items
.
splice
(
index
,
1
)
this
.
infoText
=
`已删除点位 (
${
Math
.
round
(
this
.
infoText
=
'已删除元素'
this
.
contextMenu
.
point
.
x
)}
,
${
Math
.
round
(
this
.
contextMenu
.
point
.
y
)}
)`
}
}
this
.
closeContextMenu
()
this
.
closeContextMenu
()
},
},
clonePoint
()
{
cloneItem
()
{
const
original
=
this
.
contextMenu
.
point
const
original
=
this
.
contextMenu
.
item
const
newPoint
=
new
Point
(
const
offset
=
this
.
fixedSize
*
0.3
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
if
(
original
.
type
===
'point'
)
{
const
y
=
original
.
y
+
Math
.
sin
(
angle
)
*
distance
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
.
addPointAt
(
x
,
y
,
radius
)
this
.
infoText
=
'已复制元素'
this
.
infoText
=
`已在附近添加随机点位 (
${
Math
.
round
(
x
)}
,
${
Math
.
round
(
y
)}
)`
this
.
closeContextMenu
()
this
.
closeContextMenu
()
},
},
// 颜色选择器相关方法
bringToFront
()
{
confirmColorChange
()
{
const
item
=
this
.
contextMenu
.
item
this
.
colorPicker
.
point
.
color
=
this
.
colorPicker
.
selectedColor
const
index
=
this
.
items
.
indexOf
(
item
)
this
.
colorPicker
.
visible
=
false
if
(
index
!==
-
1
)
{
this
.
infoText
=
`已更改点位颜色为
${
this
.
colorPicker
.
selectedColor
}
`
this
.
items
.
splice
(
index
,
1
)
this
.
items
.
push
(
item
)
this
.
infoText
=
'已将元素置顶'
}
this
.
closeContextMenu
()
},
},
cancelColorChange
()
{
// 图片更改处理
this
.
colorPicker
.
visible
=
false
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
)
},
class
Point
{
// 编辑器确认
constructor
(
x
,
y
,
radius
=
10
)
{
confirmEdit
()
{
this
.
x
=
x
const
item
=
this
.
editor
.
item
this
.
y
=
y
this
.
radius
=
radius
this
.
originalRadius
=
radius
this
.
color
=
this
.
getRandomColor
()
this
.
isActive
=
false
}
getRandomColor
()
{
if
(
item
.
type
===
'point'
)
{
const
r
=
Math
.
floor
(
Math
.
random
()
*
200
+
55
)
item
.
color
=
this
.
editor
.
color
const
g
=
Math
.
floor
(
Math
.
random
()
*
200
+
55
)
const
b
=
Math
.
floor
(
Math
.
random
()
*
200
+
55
)
return
`rgb(
${
r
}
,
${
g
}
,
${
b
}
)`
}
}
draw
(
ctx
)
{
item
.
size
=
this
.
editor
.
size
ctx
.
beginPath
()
this
.
editor
.
visible
=
false
ctx
.
arc
(
this
.
x
,
this
.
y
,
this
.
radius
,
0
,
Math
.
PI
*
2
)
this
.
infoText
=
'已更新元素属性'
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
)
cancelEdit
()
{
}
else
{
this
.
editor
.
visible
=
false
this
.
radius
=
this
.
originalRadius
}
}
}
}
contains
(
x
,
y
)
{
const
distance
=
Math
.
sqrt
((
x
-
this
.
x
)
**
2
+
(
y
-
this
.
y
)
**
2
)
return
distance
<=
this
.
radius
}
}
}
</
script
>
</
script
>
<
style
scoped
>
<
style
scoped
>
.container
{
.container
{
display
:
flex
;
/* display: flex;
flex-direction
:
column
;
flex-direction: column; */
align-items
:
center
;
/* align-items: center; */
justify-content
:
center
;
font-family
:
Arial
,
sans-serif
;
text-align
:
center
;
max-width
:
1000px
;
position
:
relative
;
margin
:
0
auto
;
padding
:
20px
;
}
}
canvas
{
h1
{
background-color
:
white
;
margin-bottom
:
20px
;
box-shadow
:
0
0
10px
rgba
(
0
,
0
,
0
,
0.1
);
color
:
#333
;
cursor
:
default
;
}
}
.controls
{
.toolbar
{
margin-top
:
20px
;
margin-bottom
:
15px
;
display
:
flex
;
gap
:
10px
;
align-items
:
center
;
flex-wrap
:
wrap
;
}
}
button
{
.toolbar
button
{
padding
:
8px
16px
;
padding
:
8px
16px
;
margin
:
0
5px
;
background-color
:
#4caf50
;
color
:
white
;
border
:
none
;
border-radius
:
4px
;
cursor
:
pointer
;
cursor
:
pointer
;
font-size
:
14px
;
}
}
.mode-indicator
{
.toolbar
button
.active
{
margin-top
:
10px
;
background-color
:
#2196f3
;
font-weight
:
bold
;
color
:
#333
;
}
}
#info
{
.toolbar
button
:hover
{
margin-top
:
10px
;
background-color
:
#45a049
;
color
:
#333
;
}
.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 {
...
@@ -481,14 +694,15 @@ button {
.menu-item
{
.menu-item
{
padding
:
8px
12px
;
padding
:
8px
12px
;
cursor
:
pointer
;
cursor
:
pointer
;
font-size
:
14px
;
}
}
.menu-item
:hover
{
.menu-item
:hover
{
background-color
:
#f5f5f5
;
background-color
:
#f5f5f5
;
}
}
/*
颜色选择器弹窗
*/
/*
编辑器模态框
*/
.
color-picke
r-modal
{
.
edito
r-modal
{
position
:
fixed
;
position
:
fixed
;
top
:
0
;
top
:
0
;
left
:
0
;
left
:
0
;
...
@@ -501,34 +715,67 @@ button {
...
@@ -501,34 +715,67 @@ button {
z-index
:
1001
;
z-index
:
1001
;
}
}
.
color-picker
{
.
editor-content
{
background-color
:
white
;
background-color
:
white
;
padding
:
20px
;
padding
:
20px
;
border-radius
:
8px
;
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
;
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%
;
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
{
.button-group
{
display
:
flex
;
display
:
flex
;
justify-content
:
space-between
;
justify-content
:
flex-end
;
gap
:
10px
;
margin-top
:
20px
;
}
}
.button-group
button
{
.button-group
button
{
flex
:
1
;
padding
:
8px
16px
;
margin
:
0
5px
;
border
:
none
;
border-radius
:
4px
;
cursor
:
pointer
;
}
}
.right-bar
{
position
:
absolute
;
.button-group
button
:first-child
{
right
:
150px
;
background-color
:
#4caf50
;
top
:
20px
;
color
:
white
;
}
.button-group
button
:last-child
{
background-color
:
#f44336
;
color
:
white
;
}
}
</
style
>
</
style
>
vue.config.js
View file @
076a4d68
...
@@ -61,7 +61,7 @@ module.exports = {
...
@@ -61,7 +61,7 @@ module.exports = {
[
process
.
env
.
VUE_APP_BASE_API
]:
{
[
process
.
env
.
VUE_APP_BASE_API
]:
{
// target: `http://49.232.167.247:20014/`,
// target: `http://49.232.167.247:20014/`,
// target: `http://192.168.10.185:8084/`,
// target: `http://192.168.10.185:8084/`,
target
:
`http://1
92.168.1.19:808
2/`
,
target
:
`http://1
06.3.97.198:2016
2/`
,
pathRewrite
:
{
pathRewrite
:
{
[
'^'
+
process
.
env
.
VUE_APP_BASE_API
]:
''
[
'^'
+
process
.
env
.
VUE_APP_BASE_API
]:
''
}
}
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment