Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
G
gk
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
micro_app
gk
Commits
3d8b0a0b
Commit
3d8b0a0b
authored
Aug 05, 2025
by
daniel
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
fix(location): 修复赛事配置
parent
25984274
Hide whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
2166 additions
and
3 deletions
+2166
-3
location.jsp
src/main/webapp/WEB-INF/views/cmpt/location.jsp
+770
-0
locationPoint.jsp
src/main/webapp/WEB-INF/views/cmpt/locationPoint.jsp
+1393
-0
index.js
src/main/webapp/static/js/index.js
+3
-3
No files found.
src/main/webapp/WEB-INF/views/cmpt/location.jsp
0 → 100644
View file @
3d8b0a0b
<%@ page
language=
"java"
contentType=
"text/html; charset=utf-8"
pageEncoding=
"utf-8"
%>
<%@ taglib
prefix=
"c"
uri=
"http://java.sun.com/jsp/jstl/core"
%>
<%@ taglib
prefix=
"form"
uri=
"http://www.springframework.org/tags/form"
%>
<%@ taglib
prefix=
"fmt"
uri=
"http://java.sun.com/jsp/jstl/fmt"
%>
<%@ taglib
prefix=
"fn"
uri=
"http://java.sun.com/jsp/jstl/functions"
%>
<%@ taglib
prefix=
"fns"
uri=
"/WEB-INF/tlds/fns.tld"
%>
<%@ taglib
prefix=
"fnc"
uri=
"/WEB-INF/tlds/fnc.tld"
%>
<c:set
var=
"ctx"
value=
"
${
pageContext
.
request
.
contextPath
}${
fns:
getAdminPath
()
}
"
/>
<c:set
var=
"path"
value=
"
${
pageContext
.
request
.
contextPath
}${
fns:
getPath
()
}
"
/>
<c:set
var=
"jaaproot"
value=
"
${
pageContext
.
request
.
contextPath
}
"
/>
<c:set
var=
"ctxStatic"
value=
"
${
pageContext
.
request
.
contextPath
}
/static"
/>
<!DOCTYPE html>
<html>
<head>
<meta
name=
"viewport"
content=
"width=device-width, initial-scale=1.0"
>
<title>
领物导航
</title>
<!-- ... 保留原有的 Vue 引入 ... -->
<script
src=
"https://unpkg.com/vue@3/dist/vue.global.prod.js"
></script>
<!-- 添加 Naive UI 的引入 -->
<!-- 会使用最新版本,你最好指定一个版本 -->
<script
src=
"https://unpkg.com/naive-ui"
></script>
<!-- <script nomodule
src="https://unpkg.com/ionicons@7.1.0/dist/ionicons/ionicons.js"></script> -->
<script
type=
"module"
src=
"https://unpkg.com/ionicons@7.1.0/dist/ionicons/ionicons.esm.js"
></script>
<script
nomodule
src=
"https://unpkg.com/ionicons@7.1.0/dist/ionicons/ionicons.js"
></script>
<!-- Favicon-->
<link
rel=
"shortcut icon"
href=
"https://img.geexek.com/files/home/favicon.ico"
>
<!-- 自定义css -->
<link
rel=
"stylesheet"
href=
"${ctxStatic}/css/base.css"
>
<!-- Bootstrap CSS-->
<link
rel=
"stylesheet"
href=
"${ctxStatic}/newdermon/vendor/bootstrap/css/bootstrap.min.css"
>
<!-- theme stylesheet-->
<link
rel=
"stylesheet"
href=
"${ctxStatic}/newdermon/css/style.default.css?v=
<%=
System
.
currentTimeMillis
()
%>
"
id=
"theme-stylesheet"
>
<!-- Custom stylesheet - for your changes-->
<link
rel=
"stylesheet"
href=
"${ctxStatic}/newdermon/css/custom.css?v=
<%=
System
.
currentTimeMillis
()
%>
"
>
<!-- Favicon-->
<link
rel=
"shortcut icon"
href=
"https://img.geexek.com/files/home/favicon.ico"
>
<link
rel=
"stylesheet"
href=
"${ctxStatic}/cmpt/setting.css?v=
<%=
System
.
currentTimeMillis
()
%>
"
>
<script
src=
"${ctxStatic}/newdermon/vendor/jquery/jquery.min.js"
></script>
<style>
#app
{
padding
:
0
20px
20px
20px
;
}
.layout
{
min-height
:
100vh
;
}
.carousel-img
{
width
:
100%
;
height
:
240px
;
object-fit
:
cover
;
}
[
v-cloak
]
{
display
:
none
;
}
.n-message
.n-message__icon
>*
{
position
:
absolute
;
left
:
0
;
top
:
-5px
!important
;
right
:
0
;
bottom
:
0
;
}
</style>
</head>
<body>
<jsp:include
page=
"../common/header.jsp"
></jsp:include>
<div
class=
"page-content d-flex align-items-stretch main-body"
>
<jsp:include
page=
"../common/menu.jsp"
></jsp:include>
<div
id=
"app"
class=
"flex-1"
v-cloak
>
<n-config-provider
:locale=
"locale"
:date-locale=
"dateLocale"
>
<n-modal-provider>
<n-message-provider>
<n-notification-provider>
<my-app></my-app>
</n-notification-provider>
</n-message-provider>
</n-modal-provider>
</n-config-provider>
</div>
</div>
<script
type=
"text/x-template"
id=
"my-app"
>
<
n
-
space
vertical
>
<!--
顶部的搜索框
-->
<
n
-
card
:
bordered
=
"false"
>
<
div
class
=
"w-full flex flex-row items-center gap-x-2"
>
<
div
style
=
"width: 200px"
>
<
n
-
tabs
type
=
"segment"
@
update
:
value
=
"updateStatus"
:
value
=
"query.status"
animated
pane
-
style
=
"padding:0;"
>
<
n
-
tab
-
pane
v
-
for
=
"item in [{name:'全部',value:''},...StatusEnum.data.map(v=>({name:v.name,value:v.value}))]"
:
name
=
"item.value"
:
tab
=
"item.name"
><
/n-tab-pane
>
<
/n-tabs
>
<
/div
>
<
n
-
button
type
=
"primary"
@
click
=
"edit(null)"
>
新增分类
<
/n-button
>
<
div
class
=
"ml-auto"
>
<
n
-
input
v
-
model
:
value
=
"keyword"
placeholder
=
"请输入搜索内容"
:
clearable
=
"true"
@
change
=
"fetchData"
/>
<
/div
>
<
/div
>
<
/n-card
>
<!--
搜索框
-->
<
n
-
data
-
table
:
columns
=
"columns"
:
loading
=
"loading"
default
-
expand
-
all
:
row
-
key
=
"rowKey"
:
data
=
"listTree"
:
pagination
=
"pagination"
:
bordered
=
"false"
:
remote
=
"true"
/>
<
n
-
modal
:
mask
-
closable
=
"false"
:
loading
=
"loading"
style
=
"width: 600px"
:
title
=
"formData.id?'编辑分类':'新增分类'"
preset
=
"card"
draggable
v
-
model
:
show
=
"showModal"
>
<
n
-
form
ref
=
"formRef"
:
model
=
"formData"
:
rules
=
"rules"
size
=
"small"
label
-
placement
=
"left"
label
-
width
=
"auto"
>
<
n
-
form
-
item
label
=
"分类名"
path
=
"name"
>
<
n
-
input
v
-
model
:
value
=
"formData.name"
placeholder
=
"请输入分类"
/>
<
/n-form-item
>
<
n
-
form
-
item
label
=
"分类图标"
path
=
"icon"
>
<
n
-
upload
:
action
=
"uploadUrl"
:
max
=
"1"
v
-
model
:
file
-
list
=
"fileList"
list
-
type
=
"image-card"
@
finish
=
"uploadFinish"
>
点击上传
<
/n-upload
>
<
/n-form-item
>
<
n
-
form
-
item
label
=
"排序"
path
=
"sortOrder"
>
<
n
-
input
-
number
v
-
model
:
value
=
"formData.sortOrder"
placeholder
=
"请输入排序"
/>
<
/n-form-item
>
<
n
-
form
-
item
label
=
"上级"
path
=
"parentId"
>
<
n
-
tree
-
select
v
-
model
:
value
=
"formData.parentId"
placeholder
=
"请选择上级"
:
options
=
"treeData"
/>
<
/n-form-item
>
<
n
-
form
-
item
label
=
"状态"
path
=
"status"
>
<
n
-
radio
-
group
v
-
model
:
value
=
"formData.status"
name
=
"radiogroup"
>
<
n
-
space
>
<
n
-
radio
v
-
for
=
"item in StatusEnum.data"
:
key
=
"item.value"
:
value
=
"item.value"
>
{{
item
.
name
}}
<
/n-radio
>
<
/n-space
>
<
/n-radio-group
>
<
/n-form-item
>
<
/n-form
>
<
template
#
footer
>
<
div
class
=
"flex gap-x-2 flex-row items-center"
>
<
n
-
button
class
=
"flex-1"
:
loading
=
"loading"
@
click
=
"onCancel"
>
取消
<
/n-button
>
<
n
-
button
class
=
"flex-1"
:
loading
=
"loading"
type
=
'primary'
@
click
=
"onSubmit"
>
保存
<
/n-button
>
<
/div
>
<
/template
>
<
/n-modal
>
<
n
-
modal
v
-
model
:
show
=
"showDelModal"
preset
=
"dialog"
title
=
"确认"
content
=
"你确定要删除该分类吗?"
positive
-
text
=
"确认"
negative
-
text
=
"算了"
@
positive
-
click
=
"onDelete"
/>
<
/n-space
>
</script>
<script>
// 枚举class
class
SuperEnum
{
constructor
(
param
)
{
const
keyObj
=
{};
const
valueObj
=
{};
const
nameObj
=
{};
if
(
!
Array
.
isArray
(
param
))
{
throw
new
TypeError
(
'param is not an array!'
);
}
param
.
forEach
((
element
)
=>
{
if
(
!
element
.
key
||
!
element
.
name
)
{
return
;
}
// 使用对象存储键值对,方便快速查找
keyObj
[
element
.
key
]
=
element
;
valueObj
[
element
.
value
]
=
element
;
nameObj
[
element
.
name
]
=
element
;
// 动态添加属性
this
[
element
.
key
]
=
element
;
this
[
element
.
name
]
=
element
;
if
(
element
.
key
!==
element
.
value
)
{
this
[
element
.
value
]
=
element
;
}
});
// 保存源数组
this
.
data
=
param
;
this
.
keyObj
=
keyObj
;
this
.
valueObj
=
valueObj
;
this
.
nameObj
=
nameObj
;
// 防止被修改
Object
.
freeze
(
this
);
}
getColorByKey
(
key
)
{
const
prop
=
this
.
keyOf
(
key
);
if
(
!
prop
)
{
const
error
=
new
Error
(
'No enum constant'
);
error
.
code
=
'ENUM_NOT_FOUND'
;
throw
error
;
}
return
prop
.
color
;
}
getData
()
{
return
this
.
data
;
}
getKeyByName
(
name
)
{
const
prop
=
this
.
nameOf
(
name
);
if
(
!
prop
)
{
const
error
=
new
Error
(
'No enum constant'
);
error
.
code
=
'ENUM_NOT_FOUND'
;
throw
error
;
}
return
prop
.
key
;
}
getNameByKey
(
key
)
{
const
prop
=
this
.
keyOf
(
key
);
if
(
!
prop
)
{
const
error
=
new
Error
(
'No enum constant'
);
error
.
code
=
'ENUM_NOT_FOUND'
;
throw
error
;
}
return
prop
.
name
;
}
getNameByValue
(
value
)
{
const
prop
=
this
.
valueOf
(
value
);
if
(
!
prop
)
{
const
error
=
new
Error
(
'No enum constant'
);
error
.
code
=
'ENUM_NOT_FOUND'
;
throw
error
;
}
return
prop
.
name
;
}
getValueByKey
(
key
)
{
const
prop
=
this
.
keyOf
(
key
);
if
(
!
prop
)
{
const
error
=
new
Error
(
'No enum constant'
);
error
.
code
=
'ENUM_NOT_FOUND'
;
throw
error
;
}
return
prop
.
value
;
}
getValueByName
(
name
)
{
const
prop
=
this
.
nameOf
(
name
);
if
(
!
prop
)
{
const
error
=
new
Error
(
'No enum constant'
);
error
.
code
=
'ENUM_NOT_FOUND'
;
throw
error
;
}
return
prop
.
value
;
}
keyOf
(
key
)
{
return
this
.
keyObj
[
key
];
}
nameOf
(
name
)
{
return
this
.
nameObj
[
name
];
}
valueOf
(
value
)
{
return
this
.
valueObj
[
value
];
}
}
// to 方法,封装try catch
const
to
=
async
(
promise
,
errorExt
,
)
=>
{
try
{
const
data
=
await
promise
const
result
=
[
null
,
data
]
return
result
}
catch
(
error
)
{
if
(
errorExt
)
{
const
parsedError
=
Object
.
assign
({},
error
,
errorExt
)
return
[
parsedError
,
undefined
]
}
return
[
error
,
undefined
]
}
}
// 状态
const
StatusEnum
=
new
SuperEnum
([
{
key
:
'OPEN'
,
name
:
'启用'
,
color
:
'success'
,
value
:
1
},
// 增值
{
key
:
'CLOSE'
,
name
:
'禁用'
,
color
:
'warning'
,
value
:
0
},
])
// 添加树形数据构建方法
const
buildTreeData
=
(
categories
)
=>
{
// 创建分类ID到节点的映射
const
nodeMap
=
{}
// 创建顶级节点(保持您现有的顶级节点结构)
const
root
=
{
label
:
'顶级'
,
key
:
0
,
children
:
[]
}
nodeMap
[
0
]
=
root
// 首先创建所有节点并添加到映射
categories
.
forEach
(
category
=>
{
const
node
=
{
label
:
category
.
name
,
key
:
category
.
id
,
icon
:
category
.
icon
||
undefined
,
children
:
[]
}
nodeMap
[
category
.
id
]
=
node
})
// 然后建立父子关系
categories
.
forEach
(
category
=>
{
const
node
=
nodeMap
[
category
.
id
]
const
parentId
=
category
.
parentId
||
0
// 默认为顶级节点的子节点
const
parentNode
=
nodeMap
[
parentId
]
||
root
if
(
parentNode
)
{
parentNode
.
children
.
push
(
node
)
// 按sortOrder排序子节点
parentNode
.
children
.
sort
((
a
,
b
)
=>
(
a
.
sortOrder
||
100
)
-
(
b
.
sortOrder
||
100
))
}
})
// 创建包含顶级节点和其子节点的同级数组
const
topLevelNodes
=
[
root
,
...
root
.
children
];
// 清空顶级节点的children(避免重复显示)
root
.
children
=
[];
// 对所有顶级节点按sortOrder排序
topLevelNodes
.
sort
((
a
,
b
)
=>
(
a
.
sortOrder
||
100
)
-
(
b
.
sortOrder
||
100
));
return
topLevelNodes
;
}
// 新增:将categories转换为标准树形结构(子级嵌套在children中)
const
buildNestedTreeData
=
(
categories
)
=>
{
const
nodeMap
=
{};
const
rootNodes
=
[];
// 1. 创建所有节点并存储到映射表
categories
.
forEach
(
category
=>
{
const
node
=
{
...
category
,
// 复制原始属性(id, parentId, name等)
children
:
[]
// 初始化children数组
};
nodeMap
[
category
.
id
]
=
node
;
});
// 2. 建立父子关系
categories
.
forEach
(
category
=>
{
const
node
=
nodeMap
[
category
.
id
];
const
parentId
=
category
.
parentId
;
if
(
parentId
===
null
||
parentId
===
undefined
||
!
nodeMap
[
parentId
])
{
// 无父节点或父节点不存在,作为根节点
rootNodes
.
push
(
node
);
}
else
{
// 添加到父节点的children数组
nodeMap
[
parentId
].
children
.
push
(
node
);
// 按sortOrder排序子节点
nodeMap
[
parentId
].
children
.
sort
((
a
,
b
)
=>
(
a
.
sortOrder
||
100
)
-
(
b
.
sortOrder
||
100
)
);
}
});
// 3. 对根节点按sortOrder排序
return
rootNodes
.
sort
((
a
,
b
)
=>
(
a
.
sortOrder
||
100
)
-
(
b
.
sortOrder
||
100
)
);
}
</script>
<script>
const
{
createApp
,
defineComponent
,
reactive
,
ref
,
onMounted
,
h
}
=
Vue
;
const
{
useMessage
,
useNotification
,
dateZhCN
,
NButton
,
NTag
,
zhCN
,
NSpace
,
NImageGroup
,
NImage
,
NText
,
NSwitch
}
=
naive
// 添加全局 loading 指令
const
uploadUrl
=
window
.
location
.
origin
+
'/api-common/v1/upload?folder='
+
'gk/location'
+
'&cmptId='
+
getCId
()
const
MyApp
=
defineComponent
({
name
:
'MyApp'
,
template
:
'#my-app'
,
setup
()
{
const
message
=
useMessage
()
const
notification
=
useNotification
()
const
loading
=
ref
(
false
)
/***----------------分类相关 start -----------------*/
const
columns
=
[
{
title
:
'分类名称'
,
key
:
'name'
,
},
{
title
:
'parent'
,
key
:
'parentId'
,
render
:
(
row
)
=>
{
return
h
(
NText
,
{
},
row
.
parentId
===
0
?
'顶级'
:
list
.
value
.
find
(
v
=>
v
.
id
===
row
.
parentId
)?.
name
??
'--'
)
}
},
{
title
:
'排序'
,
key
:
'sortOrder'
,
},
{
title
:
'图标'
,
key
:
'icon'
,
render
:
(
row
)
=>
{
return
h
(
NImage
,
{
height
:
"50"
,
src
:
row
.
icon
})
}
},
{
title
:
'状态'
,
key
:
'status'
,
width
:
250
,
render
:
(
row
)
=>
{
// return h(NSpace, {
// vertical: true,
// }, [
// h(NSpace, {}, [
// h(NTag, {
// size: "small",
// type: StatusEnum[row.status].color
// }, StatusEnum[row.status].name),
// ]),
// ])
return
h
(
NSwitch
,
{
defaultValue
:
row
.
status
,
uncheckedValue
:
StatusEnum
.
CLOSE
.
value
,
checkedValue
:
StatusEnum
.
OPEN
.
value
,
'onUpdate:value'
:
async
(
val
)
=>
{
// 更新数据
const
params
=
{
id
:
row
.
id
,
status
:
val
}
loading
.
value
=
true
await
fetch
(
'/gk/location-category'
,
{
method
:
'PUT'
,
headers
:
{
'Content-Type'
:
'application/json'
},
body
:
JSON
.
stringify
(
params
)
})
message
.
success
(
"更新成功"
)
loading
.
value
=
false
row
.
status
=
val
}
})
}
},
{
title
:
'操作'
,
key
:
'actions'
,
width
:
250
,
align
:
'center'
,
render
:
(
row
)
=>
{
// 两个按钮,一个 编辑 一个 删除
return
h
(
NSpace
,
{},
[
h
(
NButton
,
{
type
:
'primary'
,
size
:
'small'
,
onClick
:
()
=>
{
// 编辑
edit
(
row
)
}
},
'编辑'
),
h
(
NButton
,
{
type
:
'primary'
,
size
:
'small'
,
onClick
:
()
=>
{
let
params
=
window
.
location
.
href
.
split
(
'?'
)[
1
]
window
.
location
.
href
=
'/gk/location-point/index/'
+
getCId
()
+
'?categoryId='
+
row
.
id
+
"&"
+
params
}
},
'地图标点'
),
h
(
NButton
,
{
type
:
'error'
,
size
:
'small'
,
onClick
:
()
=>
{
editRecord
.
value
=
row
showDelModal
.
value
=
true
}
},
'删除'
)
])
}
}
]
// 分页
const
pagination
=
reactive
({
pageSize
:
20
,
page
:
1
,
pageCount
:
0
,
showSizePicker
:
true
,
itemCount
:
0
,
pageSizes
:
[
20
,
50
,
200
,
500
],
prefix
:
(
info
)
=>
"共"
+
info
.
itemCount
+
"条数据"
,
onChange
:
(
page
)
=>
{
pagination
.
page
=
page
fetchData
()
},
onUpdatePageSize
:
(
pageSize
)
=>
{
pagination
.
pageSize
=
pageSize
pagination
.
page
=
1
fetchData
()
}
})
// 查询的参数
const
query
=
reactive
({
status
:
''
,
})
const
keyword
=
ref
(
''
)
// 结果列表
const
fileList
=
ref
([])
// 分类弹框
const
showModal
=
ref
(
false
)
// 删除分类
const
showDelModal
=
ref
(
false
)
const
editRecord
=
ref
()
// 分类的formRef
const
formRef
=
ref
(
null
)
// 分类的数据列表
const
list
=
ref
([])
const
listTree
=
ref
([])
// 分类的树数据
const
treeData
=
ref
([])
// 分类表单
const
formData
=
reactive
({
id
:
null
,
parentId
:
0
,
name
:
""
,
icon
:
""
,
sortOrder
:
100
,
status
:
StatusEnum
.
OPEN
.
value
,
})
// 分类的校验规则
const
rules
=
{
name
:
{
required
:
true
,
message
:
'请输入分类名称'
,
trigger
:
'blur'
,
},
}
// 状态更新
const
updateStatus
=
(
value
)
=>
{
pagination
.
page
=
1
query
.
status
=
value
fetchData
()
}
// 上传完成
const
uploadFinish
=
({
file
,
event
})
=>
{
const
response
=
JSON
.
parse
(
event
?.
target
?.
response
)
file
.
url
=
response
.
content
return
file
}
// 重置form数据
const
resetForm
=
()
=>
{
Object
.
assign
(
formData
,
{
id
:
null
,
parentId
:
0
,
name
:
""
,
icon
:
""
,
sortOrder
:
100
,
status
:
StatusEnum
.
OPEN
.
value
,
})
}
// 删除分类
const
onDelete
=
()
=>
{
if
(
!
editRecord
.
value
)
{
showDelModal
.
value
=
false
return
}
loading
.
value
=
true
const
messageReactive
=
message
.
loading
(
'删除中...'
,
{
duration
:
0
})
// 发起请求
fetch
(
"/gk/location-category/"
+
editRecord
.
value
.
id
,
{
method
:
'DELETE'
,
headers
:
{
'Content-Type'
:
'application/json'
}
}).
then
(
res
=>
res
.
json
()).
then
(
data
=>
{
if
(
data
.
code
===
200
)
{
message
.
success
(
'删除成功'
)
fetchData
()
}
else
{
message
.
error
(
data
.
msg
)
}
}).
finally
(()
=>
{
loading
.
value
=
true
messageReactive
.
destroy
()
showDelModal
.
value
=
false
})
}
// 编辑分类
const
edit
=
(
record
)
=>
{
resetForm
()
fileList
.
value
=
[]
editRecord
.
value
=
record
// 数据初始化
record
&&
Object
.
assign
(
formData
,
record
)
if
(
record
?.
icon
)
{
fileList
.
value
=
[
{
url
:
record
?.
icon
,
status
:
'finished'
,
name
:
record
?.
icon
,
id
:
record
.
id
,
uid
:
record
.
id
}
]
}
showModal
.
value
=
true
}
// 取消操作
const
onCancel
=
()
=>
{
showModal
.
value
=
false
resetForm
()
}
// 提交
const
onSubmit
=
(
e
)
=>
{
e
.
preventDefault
()
formRef
.
value
?.
validate
(
async
(
errors
)
=>
{
if
(
errors
)
return
message
.
error
(
'请检查表单是否填写完整'
)
// 使用fetch发起请求
const
params
=
{
...
editRecord
.
value
,
...
formData
,
cmptId
:
getCId
(),
icon
:
fileList
.
value
.
map
(
v
=>
v
.
url
).
join
()
}
loading
.
value
=
true
const
messageReactive
=
message
.
loading
(
formData
.
id
?
'数据更新中...'
:
'数据添加中...'
,
{
duration
:
0
})
if
(
editRecord
.
value
&&
editRecord
.
value
.
parentId
===
editRecord
.
value
.
id
)
{
return
message
.
error
(
'不能将分类设置为自己的父分类'
)
}
// 发起请求
const
[
err
,
data
]
=
await
to
(
fetch
(
"/gk/location-category"
,
{
method
:
editRecord
.
value
?
'PUT'
:
'POST'
,
headers
:
{
'Content-Type'
:
'application/json'
},
body
:
JSON
.
stringify
(
params
)
}).
then
(
res
=>
res
.
json
()))
loading
.
value
=
false
messageReactive
.
destroy
()
if
(
data
.
code
===
200
)
{
message
.
success
(
'操作成功'
)
showModal
.
value
=
false
resetForm
()
fetchData
()
}
else
{
message
.
error
(
data
.
msg
)
}
})
}
// 获取分类分页列表
const
fetchData
=
async
()
=>
{
loading
.
value
=
true
let
params
=
{
...
query
,
page
:
pagination
.
page
,
size
:
pagination
.
pageSize
,
cmptId
:
getCId
(),
keyword
:
keyword
.
value
}
// 发起请求
const
[
err
,
res
]
=
await
to
(
fetch
(
"/gk/location-category/page"
,
{
method
:
"POST"
,
headers
:
{
'Content-Type'
:
'application/json'
},
body
:
JSON
.
stringify
(
params
)
}).
then
(
res
=>
res
.
json
()))
loading
.
value
=
false
if
(
res
.
code
===
200
)
{
list
.
value
=
res
.
content
.
content
listTree
.
value
=
buildNestedTreeData
(
res
.
content
.
content
)
treeData
.
value
=
buildTreeData
(
res
.
content
.
content
)
pagination
.
itemCount
=
res
.
content
.
total
}
else
{
message
.
error
(
res
.
msg
)
}
}
/***---------------- 分类相关 end -----------------*/
onMounted
(()
=>
{
fetchData
()
})
return
{
// 分类相关
updateStatus
,
showModal
,
formRef
,
formData
,
onCancel
,
onSubmit
,
fileList
,
listTree
,
rowKey
:
(
row
)
=>
row
.
id
,
query
,
pagination
,
columns
,
onDelete
,
showDelModal
,
rules
,
fileList
,
keyword
,
fetchData
,
treeData
,
edit
,
// 公共数据
uploadUrl
,
uploadFinish
,
loading
,
StatusEnum
,
}
}
})
const
app
=
Vue
.
createApp
({
setup
()
{
return
{
locale
:
zhCN
,
dateLocale
:
dateZhCN
}
}
})
app
.
component
(
'my-app'
,
MyApp
)
app
.
use
(
naive
)
app
.
mount
(
'#app'
)
</script>
</body>
</html>
\ No newline at end of file
src/main/webapp/WEB-INF/views/cmpt/locationPoint.jsp
0 → 100644
View file @
3d8b0a0b
<%@ page
language=
"java"
contentType=
"text/html; charset=utf-8"
pageEncoding=
"utf-8"
%>
<%@ taglib
prefix=
"c"
uri=
"http://java.sun.com/jsp/jstl/core"
%>
<%@ taglib
prefix=
"form"
uri=
"http://www.springframework.org/tags/form"
%>
<%@ taglib
prefix=
"fmt"
uri=
"http://java.sun.com/jsp/jstl/fmt"
%>
<%@ taglib
prefix=
"fn"
uri=
"http://java.sun.com/jsp/jstl/functions"
%>
<%@ taglib
prefix=
"fns"
uri=
"/WEB-INF/tlds/fns.tld"
%>
<%@ taglib
prefix=
"fnc"
uri=
"/WEB-INF/tlds/fnc.tld"
%>
<c:set
var=
"ctx"
value=
"
${
pageContext
.
request
.
contextPath
}${
fns:
getAdminPath
()
}
"
/>
<c:set
var=
"path"
value=
"
${
pageContext
.
request
.
contextPath
}${
fns:
getPath
()
}
"
/>
<c:set
var=
"jaaproot"
value=
"
${
pageContext
.
request
.
contextPath
}
"
/>
<c:set
var=
"ctxStatic"
value=
"
${
pageContext
.
request
.
contextPath
}
/static"
/>
<!DOCTYPE html>
<html>
<head>
<meta
name=
"viewport"
content=
"width=device-width, initial-scale=1.0"
>
<title>
点位标注
</title>
<!-- ... 保留原有的 Vue 引入 ... -->
<script
src=
"https://unpkg.com/vue@3/dist/vue.global.prod.js"
></script>
<!-- 添加 Naive UI 的引入 -->
<!-- 会使用最新版本,你最好指定一个版本 -->
<script
src=
"https://unpkg.com/naive-ui"
></script>
<script
type=
"module"
src=
"https://unpkg.com/ionicons@7.1.0/dist/ionicons/ionicons.esm.js"
></script>
<script
nomodule
src=
"https://unpkg.com/ionicons@7.1.0/dist/ionicons/ionicons.js"
></script>
<!-- 添加Mapbox GL JS CSS -->
<link
href=
"https://api.mapbox.com/mapbox-gl-js/v3.3.0/mapbox-gl.css"
rel=
"stylesheet"
>
<!-- 添加Mapbox GL JS -->
<script
src=
"https://api.mapbox.com/mapbox-gl-js/v3.3.0/mapbox-gl.js"
></script>
<!-- Favicon-->
<link
rel=
"shortcut icon"
href=
"https://img.geexek.com/files/home/favicon.ico"
>
<!-- 自定义css -->
<link
rel=
"stylesheet"
href=
"${ctxStatic}/css/base.css"
>
<!-- 添加天地图API -->
<script
src=
"https://api.tianditu.gov.cn/api?v=4.0&tk=16a595e56ea0cec57ad8c07368d7f1ac"
></script>
<!-- 引入togeojson -->
<script
src=
"https://unpkg.com/@tmcw/togeojson@7.1.2/dist/togeojson.umd.js"
></script>
<!-- 引入turf -->
<script
src=
"https://cdn.jsdelivr.net/npm/@turf/turf@7/turf.min.js"
></script>
<style>
#app
{
padding
:
20px
;
}
.layout
{
min-height
:
100vh
;
}
.carousel-img
{
width
:
100%
;
height
:
240px
;
object-fit
:
cover
;
}
[
v-cloak
]
{
display
:
none
;
}
</style>
</head>
<body>
<div
class=
"page-content d-flex align-items-stretch main-body"
>
<div
id=
"app"
class=
"flex-1"
v-cloak
>
<n-config-provider
:locale=
"locale"
:date-locale=
"dateLocale"
>
<n-modal-provider>
<n-message-provider>
<n-notification-provider>
<my-app></my-app>
</n-notification-provider>
</n-message-provider>
</n-modal-provider>
</n-config-provider>
</div>
</div>
<script
type=
"text/x-template"
id=
"my-app"
>
<
div
style
=
"position: fixed; top: 0; left: 0; right: 0; bottom: 0; margin: 0; padding: 0;"
>
<
div
id
=
"map"
style
=
"width: 100%; height: 100%;z-index: 0;"
><
/div
>
<
div
class
=
"fixed left-0 top-0 z-10 flex flex-row justify-center items-center p-3"
>
<!--
<
input
type
=
"file"
accept
=
".gpx,.kml"
@
change
=
"handleFileChange"
style
=
"display: none"
ref
=
"fileInputRef"
/>
<
n
-
card
>
<
div
class
=
"flex flex-col items-start gap-y-2"
>
<
n
-
button
@
click
=
"fileInputRef?.click()"
type
=
"primary"
>
导入轨迹
<
/n-button
>
<
span
class
=
"text-gray text-sm"
>
只能上传
GPX
或
KML
文件,且不超过
10
M
。
<
/span
>
<
span
class
=
"text-gray text-blue-600"
v
-
if
=
"fileName"
>
{{
fileName
}}
<
/span
>
<
/div
>
<
/n-card> --
>
<
n
-
button
@
click
=
"onBack"
type
=
"primary"
>
返回
<
/n-button
>
<
/div
>
<
div
class
=
"fixed right-3 top-0 z-10 p-3 flex flex-col gap-y-1"
>
<
div
class
=
"flex flex-row items-center mb-2 w-full gap-x-1 bg-white p-2 rounded-sm"
>
<
span
>
点位拾取
<
/span
>
<
n
-
switch
v
-
model
:
value
=
"isAddMode"
>
<
template
#
checked
>
开启
<
/template
>
<
template
#
unchecked
>
关闭
<
/template
>
<
/n-switch
>
<
/div
>
<
n
-
collapse
accordion
class
=
"bg-white p-2 rounded-sm"
style
=
"min-width:250px;"
>
<
n
-
collapse
-
item
title
=
"点位列表"
>
<
n
-
space
vertical
style
=
"width:100%;"
>
<
div
v
-
for
=
"(marker, index) in markers"
:
key
=
"marker.id"
class
=
"flex flex-row items-center gap-x-2 py-1"
>
<
span
class
=
"flex flex-1 flex-row items-center gap-x-1 text-xs"
>
<
span
class
=
"line-clamp-1 flex-1 font-bold text-md"
>
{{
marker
.
name
}}
<
/span
>
<
n
-
tag
size
=
"small"
:
bordered
=
"false"
:
type
=
"StatusEnum[marker.status].color"
>
{{
StatusEnum
[
marker
.
status
].
name
}}
<
/n-tag
>
<
/span
>
<
ion
-
icon
@
click
=
"editCheckPoint(marker)"
class
=
"cursor-pointer"
name
=
"create-outline"
><
/ion-icon
>
<
n
-
popconfirm
@
positive
-
click
=
"deleteCheckPoint(index)"
>
<
template
#
trigger
>
<
ion
-
icon
class
=
"text-red cursor-pointer"
name
=
"trash-outline"
><
/ion-icon
>
<
/template
>
是否删除该标注点
<
/n-popconfirm
>
<
/div
>
<
/n-space
>
<
/n-collapse-item
>
<
/n-collapse
>
<
/div
>
<
n
-
modal
:
mask
-
closable
=
"false"
:
loading
=
"loading"
style
=
"width: 400px"
:
title
=
"isEditMarker?'编辑标记点':'新增标记点'"
preset
=
"card"
draggable
v
-
model
:
show
=
"showModal"
>
<
n
-
form
ref
=
"markerFormRef"
:
model
=
"markerForm"
:
rules
=
"markerRules"
label
-
placement
=
"left"
label
-
width
=
"auto"
>
<
n
-
form
-
item
label
=
"点位名称"
path
=
"name"
>
<
n
-
input
v
-
model
:
value
=
"markerForm.name"
placeholder
=
"请输入点位名称"
/>
<
/n-form-item
>
<
n
-
form
-
item
label
=
"排序"
path
=
"sortOrder"
>
<
n
-
input
-
number
v
-
model
:
value
=
"markerForm.sortOrder"
placeholder
=
"请输入排序"
/>
<
/n-form-item
>
<
n
-
form
-
item
label
=
"经度"
path
=
"longitude"
>
<
n
-
input
v
-
model
:
value
=
"markerForm.longitude"
placeholder
=
"请输入经度"
/>
<
/n-form-item
>
<
n
-
form
-
item
label
=
"纬度"
path
=
"latitude"
>
<
n
-
input
v
-
model
:
value
=
"markerForm.latitude"
placeholder
=
"请输入纬度"
/>
<
/n-form-item
>
<
n
-
form
-
item
label
=
"分类图标"
path
=
"icon"
>
<
n
-
upload
:
action
=
"uploadUrl"
:
max
=
"1"
v
-
model
:
file
-
list
=
"fileList"
list
-
type
=
"image-card"
@
finish
=
"uploadFinish"
>
点击上传
<
/n-upload
>
<
span
>
推荐像素
50
x50px
<
/span
>
<
/n-form-item
>
<
n
-
form
-
item
label
=
"描述"
path
=
"description"
>
<
n
-
input
v
-
model
:
value
=
"markerForm.description"
placeholder
=
"请输入描述"
/>
<
/n-form-item
>
<
n
-
form
-
item
label
=
"状态"
path
=
"status"
>
<
n
-
switch
:
checked
-
value
=
"1"
:
unchecked
-
value
=
"0"
v
-
model
:
value
=
"markerForm.status"
>
<
template
#
checked
>
开启
<
/template
>
<
template
#
unchecked
>
关闭
<
/template
>
<
/n-switch
>
<
/n-form-item
>
<
/n-form
>
<
template
#
footer
>
<
div
class
=
"flex gap-x-2 flex-row items-center"
>
<
n
-
button
class
=
"flex-1"
:
loading
=
"loading"
@
click
=
"onMarkerCancel"
>
取消
<
/n-button
>
<
n
-
button
class
=
"flex-1"
:
loading
=
"loading"
type
=
'primary'
@
click
=
"onSubmit"
>
保存
<
/n-button
>
<
/div
>
<
/template
>
<
/n-modal
>
<
/div
>
</script>
<script>
const
cmpt
=
JSON
.
parse
(
'${fns:toJson(cmpt)}'
);
// 枚举class
class
SuperEnum
{
constructor
(
param
)
{
const
keyObj
=
{};
const
valueObj
=
{};
const
nameObj
=
{};
if
(
!
Array
.
isArray
(
param
))
{
throw
new
TypeError
(
'param is not an array!'
);
}
param
.
forEach
((
element
)
=>
{
if
(
!
element
.
key
||
!
element
.
name
)
{
return
;
}
// 使用对象存储键值对,方便快速查找
keyObj
[
element
.
key
]
=
element
;
valueObj
[
element
.
value
]
=
element
;
nameObj
[
element
.
name
]
=
element
;
// 动态添加属性
this
[
element
.
key
]
=
element
;
this
[
element
.
name
]
=
element
;
if
(
element
.
key
!==
element
.
value
)
{
this
[
element
.
value
]
=
element
;
}
});
// 保存源数组
this
.
data
=
param
;
this
.
keyObj
=
keyObj
;
this
.
valueObj
=
valueObj
;
this
.
nameObj
=
nameObj
;
// 防止被修改
Object
.
freeze
(
this
);
}
getColorByKey
(
key
)
{
const
prop
=
this
.
keyOf
(
key
);
if
(
!
prop
)
{
const
error
=
new
Error
(
'No enum constant'
);
error
.
code
=
'ENUM_NOT_FOUND'
;
throw
error
;
}
return
prop
.
color
;
}
getData
()
{
return
this
.
data
;
}
getKeyByName
(
name
)
{
const
prop
=
this
.
nameOf
(
name
);
if
(
!
prop
)
{
const
error
=
new
Error
(
'No enum constant'
);
error
.
code
=
'ENUM_NOT_FOUND'
;
throw
error
;
}
return
prop
.
key
;
}
getNameByKey
(
key
)
{
const
prop
=
this
.
keyOf
(
key
);
if
(
!
prop
)
{
const
error
=
new
Error
(
'No enum constant'
);
error
.
code
=
'ENUM_NOT_FOUND'
;
throw
error
;
}
return
prop
.
name
;
}
getNameByValue
(
value
)
{
const
prop
=
this
.
valueOf
(
value
);
if
(
!
prop
)
{
const
error
=
new
Error
(
'No enum constant'
);
error
.
code
=
'ENUM_NOT_FOUND'
;
throw
error
;
}
return
prop
.
name
;
}
getValueByKey
(
key
)
{
const
prop
=
this
.
keyOf
(
key
);
if
(
!
prop
)
{
const
error
=
new
Error
(
'No enum constant'
);
error
.
code
=
'ENUM_NOT_FOUND'
;
throw
error
;
}
return
prop
.
value
;
}
getValueByName
(
name
)
{
const
prop
=
this
.
nameOf
(
name
);
if
(
!
prop
)
{
const
error
=
new
Error
(
'No enum constant'
);
error
.
code
=
'ENUM_NOT_FOUND'
;
throw
error
;
}
return
prop
.
value
;
}
keyOf
(
key
)
{
return
this
.
keyObj
[
key
];
}
nameOf
(
name
)
{
return
this
.
nameObj
[
name
];
}
valueOf
(
value
)
{
return
this
.
valueObj
[
value
];
}
}
// to 方法,封装try catch
const
to
=
async
(
promise
,
errorExt
,
)
=>
{
try
{
const
data
=
await
promise
const
result
=
[
null
,
data
]
return
result
}
catch
(
error
)
{
if
(
errorExt
)
{
const
parsedError
=
Object
.
assign
({},
error
,
errorExt
)
return
[
parsedError
,
undefined
]
}
return
[
error
,
undefined
]
}
}
const
tk
=
"16a595e56ea0cec57ad8c07368d7f1ac"
// 地图用到的图标
const
mapIcons
=
{
0
:
'https://geexekimgoss-sxk.oss-cn-hangzhou.aliyuncs.com/gk/common/map-icon/icon-cp.png'
,
1
:
'https://geexekimgoss-sxk.oss-cn-hangzhou.aliyuncs.com/gk/common/map-icon/icon-start.png'
,
2
:
'https://geexekimgoss-sxk.oss-cn-hangzhou.aliyuncs.com/gk/common/map-icon/icon-end.png'
}
// 地图图标枚举
const
MapIconEnum
=
{
'ICON_CP'
:
mapIcons
[
0
],
'ICON_START_POINT'
:
mapIcons
[
1
],
'ICON_END_POINT'
:
mapIcons
[
2
],
}
// 打卡点类型
const
CheckPointTypeEnum
=
new
SuperEnum
([
{
key
:
'MARKER'
,
name
:
'标记点'
,
color
:
'warning'
,
value
:
0
},
{
key
:
'START'
,
name
:
'起点'
,
color
:
'primary'
,
value
:
1
},
{
key
:
'END'
,
name
:
'终点'
,
color
:
'success'
,
value
:
2
},
{
key
:
'CHECK_POINT'
,
name
:
'打卡点'
,
color
:
'info'
,
value
:
3
},
])
// 状态枚举
const
StatusEnum
=
new
SuperEnum
([
{
key
:
'CLOSE'
,
name
:
'关闭'
,
color
:
'error'
,
value
:
0
},
{
key
:
'OPEN'
,
name
:
'开启'
,
color
:
'success'
,
value
:
1
},
])
const
mapBoxToken
=
'pk.eyJ1IjoieGluZ3NhaSIsImEiOiJjbTc0ZXJ4ZjgwOWF3MmtyMjFxN2E2MHo0In0.pknVy3Lq6fLcXiwvrVLcFg'
// 添加全局 loading 指令
const
uploadUrl
=
window
.
location
.
origin
+
'/api-common/v1/upload?folder='
+
'gk/location'
+
'&cmptId='
+
cmpt
.
cmptId
// mapKey
const
MapKeyEnum
=
{
// cp点标注相关
CP_ID
:
'cp_id'
,
CP_KEY
:
'cp_'
,
CP_SOURCE_ID
:
'cp_source_id'
,
KM_CIRCLE_ID
:
'km_circle_id'
,
// 公里数相关
KM_KEY
:
'km_'
,
KM_SOURCE_ID
:
'km_source_id'
,
KM_TEXT_ID
:
'km_text_id'
,
// 轨迹相关
LINE_ID
:
'line_id'
,
LINE_KEY
:
'line_'
,
LINE_SOURCE_ID
:
'line_source_id'
,
// 定义的字段
NAME_KEY
:
'name'
,
}
// 点位类型
const
pointTypes
=
[
{
id
:
0
,
name
:
'计时点'
,
},
{
id
:
1
,
name
:
'起点'
,
},
{
id
:
2
,
name
:
'终点'
,
}
]
/**
* GPS公共配置信息
*/
const
GpsConfig
=
{
// 地图配置
map
:
{
zoom
:
8
,
center
:
[
106.67573046471102
,
26.411047728805663
],
centerPadding
:
[
100
,
100
,
100
,
100
],
maxZoom
:
20
,
minZoom
:
3
,
centerZoom
:
10
,
speed
:
0.5
,
curve
:
1.42
,
threePitch
:
45
,
defaultPitch
:
0
,
minTileCacheSize
:
20
*
1024
*
1024
,
// 20MB
maxTileCacheSize
:
100
*
1024
*
1024
,
// 100MB
duration
:
1000
,
},
// 文字配置
text
:
{
textColor
:
'#ffffff'
,
padding
:
2
,
textAlign
:
'center'
,
textBaseline
:
'middle'
,
lineWidth
:
1
,
lineHeight
:
1.5
,
backgroundRadius
:
5
,
haloWidth
:
25
,
haloPadding
:
8
,
},
km
:
{
circleOpacity
:
0.5
,
circleRadius
:
12
,
font
:
[
'Arial Unicode MS Regular'
],
fontSize
:
14
,
circleBorderWidth
:
4
,
circleBorderColor
:
'#94e697'
,
textColor
:
'#6defde'
,
circleColor
:
'#ffffff'
,
textOffset
:
[
0
,
0.1
],
},
// cp点的配置信息
cp
:
{
anchor
:
[
0.5
,
1
],
scale
:
0.15
,
fontSize
:
14
,
iconSize
:
1
,
iconCommonSize
:
0.2
,
textColor
:
'#eba236'
,
haloWidth
:
2
,
font
:
[
'Arial Unicode MS Regular'
],
textOffset
:
[
0
,
-
0.2
],
},
// 轨迹配置
path
:
{
width
:
5
,
color
:
"#7367F0"
},
};
// 获取天地图的地址
const
getTDTUrls
=
()
=>
{
const
services
=
Array
.
from
({
length
:
7
},
(
_
,
i
)
=>
i
+
1
);
const
baseUrl
=
'https://t%s.tianditu.gov.cn'
;
const
generateUrls
=
(
path
)
=>
{
return
services
.
map
((
v
)
=>
baseUrl
.
replace
(
'%s'
,
v
.
toString
())
+
path
);
};
// 天地图卫星影像服务地址
const
satellitePath
=
"/img_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=img&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&tk="
+
tk
;
const
TDTSatelliteUrls
=
generateUrls
(
satellitePath
);
// 获取原始地图数据集合
const
dataServerPath
=
"/DataServer?T=img_w&x={x}&y={y}&l={z}&tk="
+
tk
;
const
TDTDataServerUrls
=
generateUrls
(
dataServerPath
);
// 天地图注记服务地址
const
annotationPath
=
"/cia_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=cia&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&tk="
+
tk
;
const
TDTAnnotationUrls
=
generateUrls
(
annotationPath
);
return
{
TDTSatelliteUrls
,
TDTDataServerUrls
,
TDTAnnotationUrls
,
};
}
// mapbox的字体
const
glyphs
=
'mapbox://fonts/mapbox/{fontstack}/{range}.pbf'
;
// 获取地址
const
{
TDTDataServerUrls
,
TDTAnnotationUrls
,
TDTSatelliteUrls
}
=
getTDTUrls
();
// 构建样式
const
defaultStyle
=
{
version
:
8
,
sources
:
{
tianmap
:
{
type
:
'raster'
,
tiles
:
TDTSatelliteUrls
,
tileSize
:
256
,
attribution
:
'天地图影像图'
,
},
// 添加注记图层的数据源
tianmapAnnotation
:
{
type
:
'raster'
,
tiles
:
TDTAnnotationUrls
,
tileSize
:
256
,
attribution
:
'天地图注记图层'
,
},
},
layers
:
[
{
id
:
'tianmap-layer'
,
type
:
'raster'
,
source
:
'tianmap'
,
},
{
id
:
'tianmap-annotation-layer'
,
type
:
'raster'
,
source
:
'tianmapAnnotation'
,
},
],
// 添加 glyphs 属性到地图样式
glyphs
,
};
// 获取url参数
const
getQueryParams
=
()
=>
{
const
params
=
{};
window
.
location
.
search
.
substring
(
1
).
split
(
'&'
).
forEach
(
pair
=>
{
const
[
key
,
value
]
=
pair
.
split
(
'='
);
if
(
key
)
params
[
key
]
=
decodeURIComponent
(
value
||
''
);
});
return
params
;
}
const
urlParams
=
getQueryParams
()
</script>
<script>
const
{
createApp
,
defineComponent
,
ref
,
onMounted
,
reactive
}
=
Vue
;
const
{
useMessage
,
useNotification
,
dateZhCN
,
zhCN
}
=
naive
const
MyApp
=
defineComponent
({
name
:
'MyApp'
,
template
:
'#my-app'
,
setup
()
{
const
loading
=
ref
(
false
)
const
message
=
useMessage
()
const
notification
=
useNotification
()
onMounted
(()
=>
{
fetchData
()
});
const
fetchData
=
async
()
=>
{
loading
.
value
=
true
let
params
=
{
categoryId
:
urlParams
.
categoryId
,
page
:
1
,
size
:
100
,
}
// 发起请求
const
[
err
,
res
]
=
await
to
(
fetch
(
"/gk/location-point/page"
,
{
method
:
"POST"
,
headers
:
{
'Content-Type'
:
'application/json'
},
body
:
JSON
.
stringify
(
params
)
}).
then
(
res
=>
res
.
json
()))
loading
.
value
=
false
if
(
res
.
code
===
200
)
{
markers
.
value
=
res
.
content
.
content
trackCoordinates
.
value
=
res
.
content
.
content
.
map
(
v
=>
[
v
.
longitude
,
v
.
latitude
])
}
else
{
message
.
error
(
res
.
msg
)
}
// 初始化地图
initMap
();
}
/**------------------------ 地图初始化 start ------------------------**/
// 地图对象
let
map
=
ref
(
null
);
// 初始化地图
const
initMap
=
()
=>
{
// 确保容器已完全渲染
setTimeout
(()
=>
{
mapboxgl
.
config
.
MAX_PARALLEL_IMAGE_REQUESTS
=
256
;
if
(
!
map
.
value
)
{
map
.
value
=
new
mapboxgl
.
Map
({
container
:
'map'
,
style
:
defaultStyle
,
container
:
'map'
,
center
:
GpsConfig
.
map
.
center
,
zoom
:
GpsConfig
.
map
.
zoom
,
accessToken
:
mapBoxToken
,
minZoom
:
GpsConfig
.
map
.
minZoom
,
maxZoom
:
GpsConfig
.
map
.
maxZoom
,
pitch
:
GpsConfig
.
map
.
defaultPitch
,
// 初始倾斜角度,0 表示 2D 模式
antialias
:
true
,
// 抗锯齿
bearing
:
0
,
// 初始旋转角度
interactive
:
true
,
// 可交互
attributionControl
:
false
,
minTileCacheSize
:
GpsConfig
.
map
.
minTileCacheSize
,
maxTileCacheSize
:
GpsConfig
.
map
.
maxTileCacheSize
,
// 调整瓦片缓存大小
});
// 添加resize事件监听器
window
.
addEventListener
(
'resize'
,
()
=>
{
map
.
value
.
resize
();
});
// 监听 pitchChange 事件
map
.
value
.
on
(
'load'
,
()
=>
{
loadMapIcons
().
then
(()
=>
{
refreshLiveMap
();
destroyEvent
();
initMapEvent
();
initMapControls
();
});
});
}
else
{
loadMapIcons
().
then
(()
=>
{
refreshLiveMap
();
destroyEvent
();
initMapEvent
();
});
}
},
0
);
}
/**
* 刷新地图
*/
const
refreshLiveMap
=
()
=>
{
clearMap
();
// initPolyline();
initMarkers
();
initCenter
();
};
/**
* 清除地图
*/
const
clearMap
=
()
=>
{
if
(
!
map
.
value
)
return
;
const
layers
=
map
.
value
.
getStyle
()?.
layers
;
layers
.
forEach
((
layer
)
=>
{
if
(
layer
.
id
.
includes
(
MapKeyEnum
.
KM_KEY
)
||
layer
.
id
.
includes
(
MapKeyEnum
.
CP_KEY
)
||
layer
.
id
.
includes
(
MapKeyEnum
.
LINE_KEY
)
)
{
// 删除图层
map
.
value
?.
removeLayer
(
layer
.
id
);
// 检查并删除对应的数据源
if
(
map
.
value
?.
getSource
(
layer
.
id
))
{
map
.
value
.
removeSource
(
layer
.
id
);
}
}
});
};
/**
* 初始化中心
* */
const
initCenter
=
()
=>
{
const
allCoordinates
=
trackCoordinates
.
value
.
map
(
(
v
)
=>
[
v
[
0
],
v
[
1
]],
);
if
(
allCoordinates
.
length
>
0
)
{
const
bounds
=
allCoordinates
?.
reduce
(
(
bounds
,
coord
)
=>
{
return
bounds
.
extend
(
coord
);
},
new
mapboxgl
.
LngLatBounds
(
allCoordinates
[
0
],
allCoordinates
[
0
]),
);
map
.
value
?.
fitBounds
(
bounds
,
{
padding
:
GpsConfig
.
map
.
centerPadding
,
duration
:
GpsConfig
.
map
.
duration
,
essential
:
true
,
maxZoom
:
15
// 添加最大缩放级别限制
});
}
};
/**
* 初始化轨迹
*/
const
initPolyline
=
()
=>
{
if
(
!
map
.
value
)
return
;
if
(
!
trackCoordinates
.
value
.
length
)
return
// 添加 polyline 到地图
const
coordinates
=
trackCoordinates
.
value
.
map
((
v
)
=>
[
v
[
0
],
v
[
1
]]);
const
sourceId
=
MapKeyEnum
.
LINE_SOURCE_ID
;
const
layerId
=
MapKeyEnum
.
LINE_ID
// 移除旧的轨迹图层和数据源
if
(
map
.
value
?.
getLayer
(
layerId
))
{
map
.
value
?.
removeLayer
(
layerId
);
}
if
(
map
.
value
?.
getSource
(
sourceId
))
{
map
.
value
?.
removeSource
(
sourceId
);
}
if
(
!
map
.
value
?.
getSource
(
sourceId
))
{
map
.
value
?.
addSource
(
sourceId
,
{
type
:
'geojson'
,
data
:
{
type
:
'Feature'
,
geometry
:
{
type
:
'LineString'
,
coordinates
,
},
},
});
map
.
value
?.
addLayer
({
id
:
layerId
,
type
:
'line'
,
source
:
sourceId
,
paint
:
{
'line-color'
:
GpsConfig
.
path
.
color
,
'line-width'
:
GpsConfig
.
path
.
width
,
},
});
}
};
/**
* 初始化标记点
*/
const
initMarkers
=
()
=>
{
// const kms = trackCoordinates.value?.length ? calculateDistanceWithTurf(
// trackCoordinates.value.map((v) => [v.longitude, v.latitude]),
// ) : [];
// 构建cp点
const
cpFeatures
=
markers
.
value
.
map
((
cp
)
=>
{
const
{
latitude
,
longitude
,
name
,
ID
,
type
}
=
cp
;
let
icon
=
cp
.
icon
;
if
(
!
icon
)
{
switch
(
type
)
{
case
pointTypes
[
0
].
value
:
{
icon
=
MapIconEnum
.
ICON_CP
;
break
;
}
case
pointTypes
[
2
].
value
:
{
icon
=
MapIconEnum
.
ICON_END_POINT
;
break
;
}
case
pointTypes
[
1
].
value
:
{
icon
=
MapIconEnum
.
ICON_START_POINT
;
break
;
}
default
:
{
icon
=
MapIconEnum
.
ICON_CP
;
}
}
}
return
{
type
:
'Feature'
,
geometry
:
{
type
:
'Point'
,
coordinates
:
[
longitude
,
latitude
],
},
properties
:
{
text
:
name
,
color
:
GpsConfig
.
cp
.
textColor
,
[
MapKeyEnum
.
NAME_KEY
]:
cp
.
id
,
icon
,
},
};
});
// 公里数的展示
// const kmFeatures = kms.map((km, kmIndex) => {
// const kmId = MapKeyEnum.KM_KEY + kmIndex;
// return {
// type: 'Feature',
// geometry: {
// type: 'Point',
// coordinates: km,
// },
// properties: {
// text: kmIndex + 1,
// color: GpsConfig.text.textColor,
// id: kmId,
// circleColor: GpsConfig.km.circleColor,
// },
// };
// });
// 异步添加/更新 数据
if
(
!
map
.
value
)
return
;
// 强制移除旧的数据源和图层
if
(
map
.
value
.
getLayer
(
MapKeyEnum
.
KM_CIRCLE_ID
))
{
map
.
value
.
removeLayer
(
MapKeyEnum
.
KM_CIRCLE_ID
);
}
if
(
map
.
value
.
getLayer
(
MapKeyEnum
.
KM_TEXT_ID
))
{
map
.
value
.
removeLayer
(
MapKeyEnum
.
KM_TEXT_ID
);
}
if
(
map
.
value
.
getLayer
(
MapKeyEnum
.
CP_ID
))
{
map
.
value
.
removeLayer
(
MapKeyEnum
.
CP_ID
);
}
if
(
map
.
value
.
getSource
(
MapKeyEnum
.
KM_SOURCE_ID
))
{
map
.
value
.
removeSource
(
MapKeyEnum
.
KM_SOURCE_ID
);
}
if
(
map
.
value
.
getSource
(
MapKeyEnum
.
CP_SOURCE_ID
))
{
map
.
value
.
removeSource
(
MapKeyEnum
.
CP_SOURCE_ID
);
}
// 添加公里数图层
// if (map.value.getSource(MapKeyEnum.KM_SOURCE_ID)) {
// // 如果 sourceId 存在,设置数据
// (map.value.getSource(MapKeyEnum.KM_SOURCE_ID)).setData({
// type: 'FeatureCollection',
// features: kmFeatures,
// });
// } else {
// // 如果 sourceId 不存在,添加 source
// map.value.addSource(MapKeyEnum.KM_SOURCE_ID, {
// type: 'geojson',
// data: {
// type: 'FeatureCollection',
// features: kmFeatures,
// },
// });
// // 添加图层
// map.value.addLayer({
// id: MapKeyEnum.KM_CIRCLE_ID,
// type: 'circle',
// source: MapKeyEnum.KM_SOURCE_ID,
// paint: {
// 'circle-radius': GpsConfig.km.circleRadius,
// // 允许文本相互重叠
// 'circle-color': ['get', 'circleColor'],
// 'circle-stroke-width': GpsConfig.km.circleBorderWidth, // 边框宽度
// 'circle-stroke-color': GpsConfig.km.circleBorderColor, // 边框颜色,这里设置为白色
// },
// });
// map.value.addLayer({
// id: MapKeyEnum.KM_TEXT_ID,
// type: 'symbol',
// source: MapKeyEnum.KM_SOURCE_ID,
// minzoom: 0,
// maxzoom: 24,
// layout: {
// 'text-field': ['get', 'text'],
// 'text-anchor': 'center',
// 'text-size': GpsConfig.km.fontSize,
// 'text-font': GpsConfig.km.font,
// 'text-padding': GpsConfig.text.padding,
// // 允许图标相互重叠
// 'icon-allow-overlap': true,
// // 允许文本相互重叠
// 'text-allow-overlap': true,
// },
// paint: {
// 'text-color': GpsConfig.km.textColor,
// },
// });
// }
// 添加cp点图层
if
(
map
.
value
.
getSource
(
MapKeyEnum
.
CP_SOURCE_ID
))
{
// 如果 sourceId 存在,设置数据
(
map
.
value
.
getSource
(
MapKeyEnum
.
CP_SOURCE_ID
)).
setData
({
type
:
'FeatureCollection'
,
features
:
cpFeatures
,
});
}
else
{
// 如果 sourceId 不存在,添加 source
map
.
value
.
addSource
(
MapKeyEnum
.
CP_SOURCE_ID
,
{
type
:
'geojson'
,
data
:
{
type
:
'FeatureCollection'
,
features
:
cpFeatures
,
},
});
map
.
value
.
addLayer
({
id
:
MapKeyEnum
.
CP_ID
,
type
:
'symbol'
,
source
:
MapKeyEnum
.
CP_SOURCE_ID
,
// 设置图层在所有缩放级别显示
layout
:
{
'icon-image'
:
[
'get'
,
'icon'
],
'icon-anchor'
:
'bottom'
,
'text-anchor'
:
'top'
,
'text-size'
:
GpsConfig
.
cp
.
fontSize
,
'text-padding'
:
GpsConfig
.
text
.
padding
,
'text-font'
:
GpsConfig
.
cp
.
font
,
'text-field'
:
[
'get'
,
'text'
],
'text-justify'
:
'center'
,
'text-pitch-alignment'
:
'viewport'
,
'text-line-height'
:
GpsConfig
.
text
.
lineHeight
,
'icon-size'
:
GpsConfig
.
cp
.
iconSize
,
'text-offset'
:
GpsConfig
.
cp
.
textOffset
,
// 允许图标相互重叠
'icon-allow-overlap'
:
true
,
// 允许文本相互重叠
'text-allow-overlap'
:
true
,
},
paint
:
{
'text-color'
:
GpsConfig
.
cp
.
textColor
,
// 添加字体描边颜色
'text-halo-color'
:
GpsConfig
.
text
.
textColor
,
// 可根据需要修改颜色
// 添加字体描边宽度
'text-halo-width'
:
GpsConfig
.
cp
.
haloWidth
,
// 可根据需要修改宽度
},
});
}
};
/**
* 计算总距离和公里点
*/
function
calculateDistanceWithTurf
(
coordinates
,
)
{
// 1. 创建LineString
const
line
=
turf
.
lineString
(
coordinates
);
// 2. 计算总长度(公里)
const
totalDistance
=
turf
.
length
(
line
,
{
units
:
'kilometers'
});
// 3. 计算每公里的点位
const
kmPoints
=
[];
for
(
let
km
=
1
;
km
<=
Math
.
floor
(
totalDistance
);
km
++
)
{
const
along
=
turf
.
along
(
line
,
km
,
{
units
:
'kilometers'
});
kmPoints
.
push
(
along
.
geometry
.
coordinates
);
}
return
kmPoints
;
}
/**
* 初始化地图单位
*/
const
initMapControls
=
()
=>
{
if
(
!
map
.
value
)
return
;
// 添加比例尺控件到地图
map
.
value
.
addControl
(
new
mapboxgl
.
ScaleControl
(),
'bottom-right'
);
};
/**
* 加载静态资源
*/
const
loadMapIcons
=
()
=>
{
// 加载图标图片
const
icons
=
Object
.
values
(
mapIcons
);
const
loadIcons
=
[...
markers
.
value
.
filter
(
v
=>
v
.
icon
).
map
(
v
=>
v
.
icon
),
...
icons
].
map
((
icon
)
=>
{
// 检查图片是否已经存在
if
(
map
.
value
?.
hasImage
(
icon
))
{
return
Promise
.
resolve
(
true
);
}
return
new
Promise
((
resolve
,
reject
)
=>
{
map
.
value
?.
loadImage
(
icon
,
(
error
,
image
)
=>
{
if
(
error
)
{
// console.error(`Failed to load image: ${icon}`, error);
reject
(
error
);
}
else
{
if
(
map
.
value
?.
hasImage
(
icon
))
{
return
resolve
(
true
);
}
map
.
value
?.
addImage
(
icon
,
image
);
resolve
(
true
);
}
});
});
});
return
Promise
.
all
(
loadIcons
);
};
// 销毁之前的事件监听器
const
destroyEvent
=
()
=>
{
if
(
!
map
.
value
)
return
;
// 移除地图事件
map
.
value
.
off
(
'click'
,
MapKeyEnum
.
CP_ID
,
onCp
);
map
.
value
.
off
(
'click'
,
onMapClick
);
map
.
value
.
off
(
'mouseenter'
);
map
.
value
.
off
(
'mouseleave'
);
map
.
value
?.
off
(
'mousemove'
,
handleMapMouseMove
);
};
/**
* 添加地图点击事件处理函数
* @param event 地图点击事件
*/
const
onCp
=
(
event
)
=>
{
event
.
preventDefault
()
try
{
if
(
!
map
.
value
)
return
;
// 点击标记点·
// 筛选出 players-icons 图层的要素
const
features
=
map
.
value
.
queryRenderedFeatures
(
event
.
point
,
{
layers
:
[
MapKeyEnum
.
CP_ID
],
});
if
(
features
.
length
>
0
)
{
const
feature
=
features
[
0
];
if
(
!
feature
?.
properties
)
return
;
const
id
=
feature
.
properties
[
MapKeyEnum
.
NAME_KEY
];
// emits('onMarker', id);
let
marker
=
markers
.
value
.
find
(
v
=>
v
.
id
===
id
)
editCheckPoint
(
marker
)
}
}
catch
(
error
)
{
console
.
error
(
'地图点击事件处理出错:'
,
error
);
}
};
// 点位拾取
const
onMapClick
=
(
e
)
=>
{
if
(
!
map
.
value
||
!
isAddMode
.
value
)
return
;
const
features
=
map
.
value
.
queryRenderedFeatures
(
e
.
point
,
{
layers
:
[
MapKeyEnum
.
CP_ID
]
});
if
(
features
.
length
)
return
// 获取点击位置的坐标
const
coordinates
=
e
.
lngLat
;
markerForm
.
latitude
=
coordinates
.
lat
;
markerForm
.
longitude
=
coordinates
.
lng
;
markerForm
.
name
=
'新增标记点'
editCheckPoint
(
null
)
// 可以在这里添加自定义逻辑,如:
// 1. 显示坐标信息
// 2. 在地图上添加标记
// 3. 触发其他业务逻辑
}
/**
* 提取 mousemove 事件处理函数
* @param e
*/
const
handleMapMouseMove
=
(
e
)
=>
{
// 否则光标是十字准线。
if
(
map
.
value
?.
getCanvas
())
{
map
.
value
.
getCanvas
().
style
.
cursor
=
!
isAddMode
.
value
?
'pointer'
:
'crosshair'
;
}
};
// 初始化 地图事件
const
initMapEvent
=
()
=>
{
if
(
!
map
.
value
)
return
;
map
.
value
.
on
(
'click'
,
[
MapKeyEnum
.
CP_ID
],
onCp
);
// 鼠标悬停在聚合点上时改变鼠标样式
map
.
value
.
on
(
'mouseenter'
,
[
MapKeyEnum
.
CP_ID
],
()
=>
{
if
(
!
map
.
value
)
return
;
map
.
value
.
getCanvas
().
style
.
cursor
=
'pointer'
;
});
// 鼠标离开聚合点时恢复鼠标样式
map
.
value
.
on
(
'mouseleave'
,
[
MapKeyEnum
.
CP_ID
],
()
=>
{
if
(
!
map
.
value
)
return
;
map
.
value
.
getCanvas
().
style
.
cursor
=
''
;
});
// 测距的时候监听鼠标悬浮事件
map
.
value
?.
on
(
'mousemove'
,
handleMapMouseMove
);
// 添加地图点击拾取点位事件
map
.
value
.
on
(
'click'
,
onMapClick
);
};
/**------------------------ 地图初始化 end ------------------------**/
/**------------------------ 轨迹导入 start ------------------------**/
// 轨迹的名称
const
fileName
=
ref
(
''
)
// 轨迹上传的input
const
fileInputRef
=
ref
(
null
);
// 确保是二维数组类型
const
trackCoordinates
=
ref
([]);
const
markers
=
ref
([]);
// 处理文件选择
const
handleFileChange
=
async
(
event
)
=>
{
const
input
=
event
.
target
;
if
(
input
.
files
&&
input
.
files
.
length
>
0
)
{
const
file
=
input
.
files
[
0
];
const
fileType
=
file
.
name
.
split
(
'.'
).
pop
()?.
toLowerCase
();
if
(
fileType
===
'gpx'
||
fileType
===
'kml'
)
{
if
(
file
.
size
<=
10
*
1024
*
1024
)
{
// 检查文件大小不超过 10M
message
.
success
(
`
${
file
.
name
}
文件选择成功`
);
}
else
{
message
.
error
(
`
${
file
.
name
}
文件大小超过 10M,请重新选择`
);
}
}
else
{
message
.
error
(
`只能上传 GPX 或 KML 文件,请重新选择`
);
}
if
(
file
)
{
fileName
.
value
=
file
.
name
;
const
text
=
await
file
.
text
();
if
(
file
.
name
.
endsWith
(
'.gpx'
))
{
parseGPX
(
text
);
}
else
if
(
file
.
name
.
endsWith
(
'.kml'
))
{
parseKML
(
text
);
}
}
}
};
// 解析gpx
async
function
parseGPX
(
gpxData
)
{
try
{
const
goxDom
=
new
DOMParser
().
parseFromString
(
gpxData
,
"text/xml"
);
const
geoJSON
=
toGeoJSON
.
gpx
(
goxDom
);
trackCoordinates
.
value
=
geoJSON
.
features
.
filter
((
f
)
=>
f
.
geometry
?.
type
===
'LineString'
)
.
flatMap
((
f
)
=>
f
.
geometry
?.
coordinates
?.
map
((
coord
)
=>
[
coord
[
0
],
coord
[
1
],
coord
[
2
]
||
0
,
]),
)
??
[];
markers
.
value
=
geoJSON
.
features
.
filter
((
f
)
=>
f
.
geometry
?.
type
===
'Point'
&&
f
.
properties
?.
name
)
.
map
(
(
p
,
index
)
=>
({
name
:
p
.
properties
?.
name
||
'未命名'
,
longitude
:
p
.
geometry
.
coordinates
[
0
],
latitude
:
p
.
geometry
.
coordinates
[
1
],
altitude
:
p
.
geometry
.
coordinates
[
2
]
||
0
,
status
:
StatusEnum
.
OPEN
.
value
,
type
:
CheckPointTypeEnum
.
MARKER
.
value
,
id
:
index
+
1
}),
);
if
(
trackCoordinates
.
value
.
length
>
0
)
{
importMarkersAndTrackers
();
}
else
{
message
.
error
(
'GPX 文件中未找到有效的轨迹数据'
);
}
importMarkersAndTrackers
();
}
catch
(
error
)
{
console
.
error
(
'Error parsing GPX:'
,
error
);
}
}
// 解析kml
async
function
parseKML
(
kmlData
)
{
try
{
const
kmlDom
=
new
DOMParser
().
parseFromString
(
kmlData
,
"text/xml"
);
const
geoJSON
=
toGeoJSON
.
kml
(
kmlDom
);
// 安全提取并转换轨迹坐标
trackCoordinates
.
value
=
geoJSON
.
features
.
filter
((
f
)
=>
f
.
geometry
?.
type
===
'LineString'
)
.
flatMap
((
f
)
=>
f
.
geometry
?.
coordinates
?.
map
((
coord
)
=>
[
coord
[
0
],
coord
[
1
],
coord
[
2
]
||
0
,
]),
)
??
[];
markers
.
value
=
geoJSON
.
features
.
filter
((
f
)
=>
f
.
geometry
?.
type
===
'Point'
&&
f
.
properties
?.
name
)
.
map
(
(
p
,
index
)
=>
({
name
:
p
.
properties
?.
name
||
'未命名'
,
longitude
:
p
.
geometry
.
coordinates
[
0
],
latitude
:
p
.
geometry
.
coordinates
[
1
],
altitude
:
p
.
geometry
.
coordinates
[
2
]
||
0
,
status
:
StatusEnum
.
OPEN
.
value
,
type
:
CheckPointTypeEnum
.
MARKER
.
value
,
id
:
index
+
1
}),
)
.
filter
(
Boolean
);
if
(
trackCoordinates
.
value
.
length
>
0
)
{
importMarkersAndTrackers
();
}
else
{
message
.
error
(
'KML 文件中未找到有效的轨迹数据'
);
}
}
catch
(
error
)
{
console
.
error
(
'Error parsing KML:'
,
error
);
}
}
// 保存标记点
const
importMarkersAndTrackers
=
async
()
=>
{
// TODO 保存标记点
refreshLiveMap
();
}
/**------------------------ 轨迹导入 end ------------------------**/
/**------------------------ marker start ------------------------**/
// 结果列表
const
fileList
=
ref
([])
// 上传完成
const
uploadFinish
=
({
file
,
event
})
=>
{
const
response
=
JSON
.
parse
(
event
?.
target
?.
response
)
file
.
url
=
response
.
content
return
file
}
const
isEditMarker
=
ref
(
false
)
const
defaultMarkerForm
=
{
id
:
undefined
,
name
:
''
,
longitude
:
0
,
latitude
:
0
,
altitude
:
0
,
icon
:
''
,
status
:
StatusEnum
.
OPEN
.
value
,
sortOrder
:
100
,
categoryId
:
urlParams
.
categoryId
,
}
const
markerForm
=
reactive
({
id
:
undefined
,
name
:
''
,
longitude
:
0
,
latitude
:
0
,
altitude
:
0
,
icon
:
''
,
status
:
StatusEnum
.
OPEN
.
value
,
sortOrder
:
100
,
categoryId
:
urlParams
.
categoryId
,
})
// 点位表单
const
markerFormRef
=
ref
()
// 是否显示弹框
const
showModal
=
ref
(
false
)
// 是否是点位添加模式
const
isAddMode
=
ref
(
false
)
// 表单规则
const
markerRules
=
{
name
:
{
required
:
true
,
message
:
'请输入点位名称'
,
trigger
:
[
'blur'
,
'input'
],
},
longitude
:
{
required
:
true
,
message
:
'请输入经度'
,
trigger
:
[
'blur'
,
'input'
],
type
:
'number'
},
latitude
:
{
required
:
true
,
message
:
'请输入纬度'
,
trigger
:
[
'blur'
,
'input'
],
type
:
'number'
},
}
// 重置
const
resetForm
=
()
=>
{
fileList
.
value
=
[]
Object
.
assign
(
markerForm
,
defaultMarkerForm
)
}
// 编辑点位
const
editCheckPoint
=
(
record
)
=>
{
isEditMarker
.
value
=
!!
record
if
(
record
)
{
Object
.
assign
(
markerForm
,
{
...
record
})
}
if
(
record
?.
icon
)
{
fileList
.
value
=
[
{
url
:
record
?.
icon
,
status
:
'finished'
,
name
:
record
?.
icon
,
id
:
record
.
id
,
uid
:
record
.
id
}
]
}
showModal
.
value
=
true
}
// 删除点位
const
deleteCheckPoint
=
(
index
)
=>
{
let
marker
=
markers
.
value
[
index
]
loading
.
value
=
true
const
messageReactive
=
message
.
loading
(
'删除中...'
,
{
duration
:
0
})
// 发起请求
fetch
(
"/gk/location-point/"
+
marker
.
id
,
{
method
:
'DELETE'
,
headers
:
{
'Content-Type'
:
'application/json'
}
}).
then
(
res
=>
res
.
json
()).
then
(
data
=>
{
if
(
data
.
code
===
200
)
{
message
.
success
(
'删除成功'
)
fetchData
()
}
else
{
message
.
error
(
data
.
msg
)
}
}).
finally
(()
=>
{
loading
.
value
=
true
messageReactive
.
destroy
()
refreshLiveMap
()
})
}
// 取消编辑点
const
onMarkerCancel
=
()
=>
{
showModal
.
value
=
false
resetForm
()
}
// 提交
const
onSubmit
=
(
e
)
=>
{
e
.
preventDefault
()
markerFormRef
.
value
?.
validate
(
async
(
errors
)
=>
{
if
(
errors
)
return
message
.
error
(
'请检查表单是否填写完整'
)
// 使用fetch发起请求
const
params
=
{
...
markerForm
,
categoryId
:
urlParams
.
categoryId
,
icon
:
fileList
.
value
.
map
(
v
=>
v
.
url
).
join
()
}
loading
.
value
=
true
const
messageReactive
=
message
.
loading
(
isEditMarker
.
value
?
'数据更新中...'
:
'数据添加中...'
,
{
duration
:
0
})
// 发起请求
const
[
err
,
data
]
=
await
to
(
fetch
(
"/gk/location-point"
,
{
method
:
isAddMode
.
value
?
'POST'
:
'PUT'
,
headers
:
{
'Content-Type'
:
'application/json'
},
body
:
JSON
.
stringify
(
params
)
}).
then
(
res
=>
res
.
json
()))
loading
.
value
=
false
messageReactive
.
destroy
()
if
(
data
.
code
===
200
)
{
message
.
success
(
'操作成功'
)
showModal
.
value
=
false
resetForm
()
fetchData
()
}
else
{
message
.
error
(
data
.
msg
)
}
})
}
// 返回
const
onBack
=
()
=>
{
// 回退
window
.
history
.
back
()
}
/**------------------------ marker end ------------------------**/
// 对外返回的信息
return
{
// 轨迹导入模块
fileInputRef
,
fileName
,
handleFileChange
,
// 公共
CheckPointTypeEnum
,
StatusEnum
,
// 点位编辑模块
markers
,
markerForm
,
markerFormRef
,
isEditMarker
,
showModal
,
editCheckPoint
,
deleteCheckPoint
,
onMarkerCancel
,
onSubmit
,
markerRules
,
isAddMode
,
fileList
,
onBack
,
uploadUrl
,
uploadFinish
}
}
})
const
app
=
Vue
.
createApp
({
setup
()
{
return
{
locale
:
zhCN
,
dateLocale
:
dateZhCN
}
}
})
app
.
component
(
'my-app'
,
MyApp
)
app
.
use
(
naive
)
app
.
mount
(
'#app'
)
</script>
</body>
</html>
\ No newline at end of file
src/main/webapp/static/js/index.js
View file @
3d8b0a0b
...
@@ -18,7 +18,7 @@ $(function () {
...
@@ -18,7 +18,7 @@ $(function () {
// 为当前点击的图片添加 active-style 样式
// 为当前点击的图片添加 active-style 样式
$
(
this
).
addClass
(
'active-style'
);
$
(
this
).
addClass
(
'active-style'
);
// 将选中的样式值存储到隐藏的 input 中
// 将选中的样式值存储到隐藏的 input 中
$
(
"#detailStyle"
).
val
(
selectedStyle
);
$
(
"#detailStyle"
).
val
(
selectedStyle
??
'1'
);
});
});
// by ggb 2025-05-30 是否显示 底部的tab
// by ggb 2025-05-30 是否显示 底部的tab
$
(
'#isShowTabSwitch'
).
change
(
function
()
{
$
(
'#isShowTabSwitch'
).
change
(
function
()
{
...
@@ -38,7 +38,7 @@ $(function () {
...
@@ -38,7 +38,7 @@ $(function () {
return
;
return
;
}
}
const
param
=
{
const
param
=
{
detailStyle
:
detailStyle
??
'
style-
1'
,
detailStyle
:
detailStyle
??
'1'
,
cmptId
,
cmptId
,
logo
,
logo
,
cmptbgColor
:
"#"
+
bg
,
cmptbgColor
:
"#"
+
bg
,
...
@@ -165,7 +165,7 @@ function changeLogoImg(imgDomNName, inputDomName, top, left, width, height) {
...
@@ -165,7 +165,7 @@ function changeLogoImg(imgDomNName, inputDomName, top, left, width, height) {
$
(
'#cmptLogo'
).
addClass
(
'cmpt-logo'
);
$
(
'#cmptLogo'
).
addClass
(
'cmpt-logo'
);
$
(
'#cmptLogo'
).
removeClass
(
"logo"
);
$
(
'#cmptLogo'
).
removeClass
(
"logo"
);
}
else
{
}
else
{
layer
.
msg
(
res
.
msg
,
{
icon
:
2
});
layer
.
msg
(
res
.
msg
,
{
icon
:
2
});
}
}
},
},
beforeSend
:
()
=>
{
beforeSend
:
()
=>
{
...
...
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