water-design-const-web/src/views/basic/model-manage/index.vue

1112 lines
46 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="app-container">
<el-form size="small" :inline="true" ref="queryForm" :model="queryParams">
<el-form-item label="项目名称" prop="projectId">
<!-- <el-input
clearable
placeholder="请输入项目名称"
v-model="queryParams.proName"
@keyup.enter.native="handleQuery"
/> -->
<el-select
placeholder="请选择项目名称"
style="width: 240px"
@change="handleProjectChange"
v-model="queryParams.projectId"
>
<el-option :key="item.id" :value="item.id" :label="item.name" v-for="item in projectSelectList" />
</el-select>
</el-form-item>
<el-form-item label="所属单位" prop="unit">
<el-input
clearable
placeholder="请输入所属单位"
v-model="queryParams.unit"
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="负责人" prop="chargePerson">
<el-input
clearable
placeholder="请输入负责人"
v-model="queryParams.chargePerson"
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">查询</el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="primary" plain icon="el-icon-plus" size="mini" @click="handleAddLevelTwo">
新增
</el-button>
<el-button type="primary" plain icon="el-icon-view" size="mini" @click="handleModelPreview">
模型预览
</el-button>
</el-col>
<!-- <right-toolbar :showSearch.sync="showSearch" @queryTable="getModelList"></right-toolbar> -->
</el-row>
<el-table border row-key="id" v-loading="loading" :data="modelList" default-expand-all>
<el-table-column label="序号" align="center" type="index" />
<el-table-column label="分类名称" align="center">
<template slot-scope="{ row }">
<span>{{ row.nodeName }}</span>
<el-tag style="margin-left: 10px" size="mini" type="primary">节点{{ row.nodelevel }}</el-tag>
</template>
</el-table-column>
<el-table-column label="所属项目" align="center" prop="proName" />
<el-table-column label="层级名称" align="center" prop="levelName" />
<el-table-column label="操作" align="center" width="180">
<template slot-scope="{ row }">
<el-button
size="mini"
type="text"
icon="el-icon-plus"
@click="handleAddChild(row)"
v-if="row.nodelevel < row.nodeCount"
>
新增
</el-button>
<el-button
size="mini"
type="text"
:icon="row.modelUrl ? 'el-icon-view' : 'el-icon-upload'"
@click="row.modelUrl ? handleViewModel(row) : handleUploadModel(row)"
v-if="row.nodeCount == row.nodelevel"
>
{{ row.modelUrl ? '查看模型' : '上传模型' }}
</el-button>
<!-- <el-button
size="mini"
type="text"
icon="el-icon-edit"
@click="handleEdit(row)"
v-if="row.level != 1"
>
修改
</el-button> -->
<el-button
size="mini"
type="text"
icon="el-icon-delete"
style="color: #f56c6c"
@click="handleDelete(row.id)"
v-if="row.nodeCount == row.nodelevel || row.children.length == 0"
>
删除
</el-button>
</template>
</el-table-column>
</el-table>
<!-- <pagination
v-show="total > 0"
:total="total"
:page.sync="queryParams.pageNum"
:limit.sync="queryParams.pageSize"
@pagination="getModelList"
/> -->
<el-dialog :title="title" :visible.sync="open" width="550px" append-to-body>
<el-form ref="form" :model="form" :rules="rules" label-width="100px">
<el-form-item label="项目名称" prop="proName">
<el-input v-model="form.proName" placeholder="请输入项目名称" />
</el-form-item>
<el-form-item label="层级配置" prop="level">
<el-select v-model="form.level" placeholder="请选择层级配置" style="width: 100%">
<el-option
:key="item.configId"
:value="item.configId"
:label="item.configName"
v-for="item in levelList"
/>
</el-select>
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input v-model="form.remark" type="textarea" placeholder="请输入备注" />
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="submitForm">确 定</el-button>
<el-button @click="cancel">取 消</el-button>
</div>
</el-dialog>
<el-dialog width="80%" append-to-body :title="useOrReturnFormTitle" :visible.sync="useOrReturnFormVisible">
<UseOrReturnForm
ref="useOrReturnFormComponentRef"
v-if="useOrReturnFormVisible"
:project-id="currentProjectId"
@closeUseOrReturnFormDialog="useOrReturnFormVisible = false"
@refreshParentList="getModelList"
/>
</el-dialog>
<!-- 添加或修改岗位对话框 -->
<el-dialog width="40%" append-to-body :title="addOrEditFormTitle" :visible.sync="addOrEditFormVisible">
<AddOrEditForm ref="addOrEditComponentRef" @closeAddOrEditFormDialog="closeAddOrEditFormDialog" />
</el-dialog>
<el-dialog
title="模型预览"
width="90%"
append-to-body
@close="onCloseMapView"
:visible.sync="modelPreviewVisible"
>
<!-- <dxf-viewer :entities="dxfPreviewUrl"></dxf-viewer> -->
<div id="map-container"> </div>
</el-dialog>
</div>
</template>
<script>
import AddOrEditForm from './addOrEditForm.vue'
// import UseOrReturnForm from './useOrReturnForm.vue'
import UseOrReturnForm from './useOrReturnFormNew.vue'
import UseRecordTable from './useRecordTable.vue'
import DxfViewer from './DxfViewer.vue'
// 引入相关的 API 接口
import { getModelListApi, delModelApi, openView } from '@/api/basic/model'
import {
listProject,
getProject,
delProject,
addProject,
updateProject,
getProjectSelectListApi,
} from '@/api/basic/project'
import { getLevelListApi } from '@/api/basic/level-manage.js'
export default {
name: 'DeviceManage',
components: {
AddOrEditForm,
UseOrReturnForm,
UseRecordTable,
DxfViewer,
},
data() {
return {
// 用于新增/修改项目的对话框
open: false,
title: '', // 对话框标题
form: {
proId: undefined,
proName: undefined,
proType: undefined,
unit: undefined,
chargePerson: undefined,
location: undefined,
remark: undefined,
level: undefined,
},
rules: {
proName: [{ required: true, message: '项目名称不能为空', trigger: 'blur' }],
level: [{ required: true, message: '请选择层级配置', trigger: 'change' }],
},
levelList: [], // 层级配置列表
viewDialogVisible: false,
dxfPreviewUrl: [],
total: 0, // 总条数
loading: false, // 加载中
addOrEditFormTitle: '新增', // 新增或编辑对话框标题 (旧的AddOrEditForm组件使用)
addOrEditFormVisible: false, // 新增或编辑对话框是否显示 (旧的AddOrEditForm组件使用)
useOrReturnFormTitle: '模型上传', // 领用或归还对话框标题
useOrReturnFormVisible: false, // 领用或归还对话框是否显示
useRecordFormVisible: false, // 使用记录对话框是否显示
// 设备列表
modelList: [],
currentProjectId: null,
showSearch: true, // 控制搜索栏显示隐藏
// 查询参数
queryParams: {
// pageNum: 1, // 如果您的API支持分页可以保留
// pageSize: 10, // 如果您的API支持分页可以保留
projectId: undefined,
unit: undefined,
chargePerson: undefined,
},
projectSelectList: [],
list: [
{
label: '项目1',
id: 1,
level: 1,
count: 2,
children: [
// {
// label: '节点1',
// id: 1,
// level: 2,
// },
],
},
{
label: '项目2',
id: 2,
level: 1,
count: 3,
children: [
{
label: '节点1',
id: 1,
level: 2,
},
],
},
],
map: null,
modelPreviewVisible: false,
modelPreviewInfoList: [],
}
},
created() {
this.getLevelList() // 获取层级配置数据
this.getProjectSelectList()
},
methods: {
/** 获取层级配置列表 */
async getLevelList() {
try {
const res = await getLevelListApi({
pageNum: 1,
pageSize: 1000,
})
this.levelList = res.rows
} catch (error) {
console.error('获取层级配置失败:', error)
}
},
// 打开新增项目对话框
handleAdd() {
this.reset() // 重置表单
this.open = true // 显示对话框
this.title = '新增项目' // 设置对话框标题
},
// 查看模型按钮
handleViewModel(row) {
/* const modelUrl = row.modelUrl
if (!modelUrl || !modelUrl.endsWith('.dxf')) {
this.$modal.msgWarning('模型地址无效或不是DXF文件')
return
} */
/* 新需求 重新做 上面建议不要删除 有需要直接可以查看 */
// openView({ id: row.id })
// .then((response) => {
// this.dxfPreviewUrl = response.data
// this.viewDialogVisible = true
// })
// .catch((error) => {
// // console.error('获取项目详情失败:', error)
// })
openView({ id: row.id })
.then((res) => {
this.modelPreviewInfoList = res.data
this.modelPreviewVisible = true
console.log('res', res.data)
this.initMap()
})
.catch((err) => {
console.error('获取模型详情失败:', err)
})
},
// 查询按钮
handleQuery() {
this.queryParams.pageNum = 1 // 假设有分页,查询时重置页码
this.getModelList()
},
// 重置按钮
resetQuery() {
this.resetForm('queryForm')
this.handleQuery()
},
// 新增设备 (如果您的旧组件AddOrEditForm.vue仍然需要可以保留此方法)
handleAddDevice() {
this.addOrEditFormTitle = '新增'
this.editForm = null
this.addOrEditFormVisible = true
},
// 新增子节点
handleAddChild(row) {
const { id, nodeName, nodelevel, nodeCount, projectId } = row
this.addOrEditFormTitle = '新增'
this.editForm = null
this.addOrEditFormVisible = true
const editForm = { deviceName: nodeName, id, level: nodelevel * 1 + 1, nodeCount, projectId }
this.$nextTick(() => {
this.$refs.addOrEditComponentRef.setFormData(editForm)
})
},
// 获取模型列表
// 生成 UUID备用当 nodeId 缺失时
generateUUID() {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
const r = (Math.random() * 16) | 0
const v = c === 'x' ? r : (r & 0x3) | 0x8
return v.toString(16)
})
},
// 获取模型列表
// 获取模型列表 - 最终优化版
// 获取模型列表 - 最终优化版
async getModelList() {
this.loading = true
const { rows: res } = await getModelListApi(this.queryParams)
console.log(res, '模型列表')
this.loading = false
this.modelList = this.onBuildTree(res)
// getModelListApi(this.queryParams)
// .then((response) => {
// const rawList = response.rows || []
// // 增强型递归处理
// const formatNodes = (nodes, level = 2) => {
// // 从第二级开始
// if (!Array.isArray(nodes)) return []
// return nodes
// .filter((item) => item && item.nodeId) // 过滤无效节点
// .map((item) => ({
// ...item,
// id: item.nodeId,
// name: item.nodeName || '未命名节点',
// level: level, // 添加层级标记
// // 递归处理子节点,层级+1
// nodes: formatNodes(item.nodes || item.children || [], level + 1),
// }))
// }
// // 处理项目层数据
// const formatted = rawList.map((project) => {
// const children = project.children || []
// // 获取层级名称(项目级才需要)
// let levelName = ''
// let nodeCount = ''
// if (this.levelList && this.levelList.length > 0) {
// const levelConfig = this.levelList.find(
// (item) => item.configId == project.level, // 注意类型匹配
// )
// levelName = levelConfig ? levelConfig.configName : ''
// }
// return {
// ...project,
// id: project.projectId,
// name: project.proName || '未命名项目',
// level: 1, // 项目级标记为1
// levelName: project.levelName, // 添加层级名称
// nodes: formatNodes(children),
// rawData: project,
// nodeCount: project.nodeCount,
// }
// })
// this.modelList = formatted
// this.total = response.total
// this.loading = false
// })
// .catch((error) => {
// console.error('获取模型列表失败:', error)
// this.loading = false
// this.$message.error('模型数据加载失败,请重试')
// })
},
// 上传按钮
handleUploadModel(row) {
this.$nextTick(() => {
this.currentProjectId = row.id || null
this.useOrReturnFormVisible = true
// 找到子组件并调用它的 resetForm 方法 (如果存在)
// 确保 useOrReturnFormComponentRef 存在且 resetForm 是一个函数
if (
this.$refs.useOrReturnFormComponentRef &&
typeof this.$refs.useOrReturnFormComponentRef.resetForm === 'function'
) {
this.$refs.useOrReturnFormComponentRef.resetForm()
}
})
},
// 查看记录按钮
handleViewRecord(row) {
console.log(row)
this.useRecordFormVisible = true
},
// 编辑按钮 (旧的AddOrEditForm组件使用)
handleEdit(row) {
const { id, proName, level, nodeCount } = row
this.addOrEditFormTitle = '编辑'
this.editForm = null
this.addOrEditFormVisible = true
const editForm = { deviceName: proName, id, level, nodeCount }
this.$nextTick(() => {
// 确保 addOrEditComponentRef 存在
if (this.$refs.addOrEditComponentRef) {
this.$refs.addOrEditComponentRef.setFormData(editForm)
}
})
},
/** 修改按钮操作 (使用新的弹窗) */
handleUpdate(row) {
this.reset()
const proId = row.id // 对于模型列表id 对应 projectId
getProject(proId)
.then((response) => {
this.form = response.data
this.open = true
this.title = '修改项目'
// 注意:这里 form.level 的赋值可能需要根据您的实际数据结构调整
// 比如,如果 response.data.level 是 configId则可以直接赋值
// this.form.level = response.data.level;
})
.catch((error) => {
console.error('获取项目详情失败:', error)
})
},
// 删除按钮
handleDelete(id) {
this.$modal
.confirm('是否确认删除当前节点数据吗?')
.then(async () => {
// 根据 row.level 判断是删除项目还是删除模型节点
// let res
// if (row.level === 1) {
// // 假设 level 1 是项目
// res = await delProject(row.id) // 使用 delProject API 删除项目
// } else {
// res = await delModelApi({ id: row.id }) // 使用 delModelApi 删除模型节点
// }
const res = await delModelApi({ id })
if (res.code === 200) {
this.$modal.msgSuccess('删除成功')
this.getModelList()
} else {
this.$modal.msgError(res.msg || '删除失败')
}
})
.catch(() => {
// 用户取消删除
})
},
// 关闭新增或编辑对话框 (针对旧的AddOrEditForm组件)
closeAddOrEditFormDialog(isSuccess) {
if (this.$refs.addOrEditComponentRef) {
this.$refs.addOrEditComponentRef.resetForm()
}
this.addOrEditFormVisible = false
if (isSuccess) {
this.getModelList()
}
},
// 关闭领用或归还对话框 (UseOrReturnForm)
closeUseOrReturnFormDialog() {
// 这个方法现在由 @closeUseOrReturnFormDialog="useOrReturnFormVisible = false" 直接处理,
// 如果还需要 resetForm可以取消注释下面两行并确保子组件有此方法
// if (this.$refs.useOrReturnFormComponentRef) {
// this.$refs.useOrReturnFormComponentRef.resetForm();
// }
this.useOrReturnFormVisible = false
},
// 新增/修改项目对话框的取消按钮
cancel() {
this.open = false
this.reset()
},
// 新增/修改项目对话框的表单重置
reset() {
this.form = {
proId: undefined,
proName: undefined,
proType: undefined,
unit: undefined,
chargePerson: undefined,
location: undefined,
remark: undefined,
level: undefined,
}
this.resetForm('form') // Element UI 的 resetForm 方法
},
/** 提交按钮 (新增/修改项目) */
submitForm: function () {
this.$refs['form'].validate((valid) => {
if (valid) {
if (this.form.proId != undefined) {
updateProject(this.form)
.then((response) => {
this.$modal.msgSuccess('修改成功')
this.open = false
this.getModelList() // 刷新列表
})
.catch((error) => {
console.error('修改项目失败:', error)
})
} else {
addProject(this.form)
.then((response) => {
this.$modal.msgSuccess('新增成功')
this.open = false
this.getModelList() // 刷新列表
})
.catch((error) => {
console.error('新增项目失败:', error)
})
}
}
})
},
// 获取所有的项目下拉选
async getProjectSelectList() {
const { data: res } = await getProjectSelectListApi()
console.log(res, '下拉选的所有数据')
this.projectSelectList = res
if (res.length > 0) {
this.queryParams.projectId = res[0].id
this.getModelList()
}
},
// 新增二级节点
handleAddLevelTwo() {
this.addOrEditFormTitle = '新增'
this.editForm = null
this.addOrEditFormVisible = true
const deviceName = this.projectSelectList.find((item) => item.id === this.queryParams.projectId).name
const nodeCount = this.projectSelectList.find((item) => item.id === this.queryParams.projectId).nodeCount
const editForm = {
deviceName,
level: 2,
nodeCount,
projectId: this.queryParams.projectId,
id: this.queryParams.projectId,
}
this.$nextTick(() => {
this.$refs.addOrEditComponentRef.setFormData(editForm)
})
},
// 项目切换
handleProjectChange() {
this.getModelList()
},
// 递归构建树形结构
onBuildTree(data) {
// 1. 筛选顶级节点nodelevel为"2"
const topNodes = data.filter((item) => item.nodelevel == '2')
return topNodes.map((node) => ({
...node,
children: this.findChildren(node, data), // 顶级节点的子节点
}))
},
// 查找子节点
findChildren(node, data) {
// 寻找parentId等于当前节点id的所有项作为子节点
const children = data.filter((item) => item.parentId == node.id)
// 如果有子节点,递归处理每个子节点的后代
if (children.length > 0) {
// 为每个子节点添加nodes属性并递归查找其后代
return children.map((child) => ({
...child,
children: this.findChildren(child, data), // 递归关键:子节点的子节点
}))
}
// 没有子节点时返回空数组或保留null根据需求调整
return []
},
// 模型预览
handleModelPreview() {
this.modelPreviewVisible = true
// 百度地图初始化
this.initMap()
},
// 预览弹框关闭
onCloseMapView() {
// 销毁地图实例
if (this.map) {
this.map = null
}
},
// 百度地图初始化
initMap() {
// 初始化开启地球模式
const lon = this.projectSelectList.find((item) => item.id == this.queryParams.projectId).lon * 1
const lat = this.projectSelectList.find((item) => item.id == this.queryParams.projectId).lat * 1
this.$nextTick(() => {
this.map = new BMapGL.Map('map-container') // 创建地图实例
this.map.setMapType(BMAP_EARTH_MAP) // 地球模式
this.map.setDisplayOptions({
poiText: false, // 隐藏POI文字
poiIcon: false, // 隐藏POI图标
building: false, // 隐藏建筑物
})
let point = new BMapGL.Point(lon, lat) // 创建点坐标
this.map.centerAndZoom(point, 16) // 初始化地图,设置中心点坐标和地图级别
this.map.enableScrollWheelZoom(true) // 启用滚轮放大缩小
this.drawModel(this.modelPreviewInfoList)
})
},
// drawModel方法
async drawModel(modelInfoList) {
// 清除现有覆盖物
this.map.clearOverlays()
for (const item of modelInfoList) {
console.log(item, 'item')
try {
// 处理新的LWPOLYLINE数据结构
if (item.entityType === 'LWPOLYLINE') {
console.log('处理LWPOLYLINE:', item)
const pointList = JSON.parse(item.geometry)
console.log(pointList, 'pointList')
const newPointList = pointList?.segments
console.log(newPointList, 'newPointList')
// 根据newPointList 中的type绘制线和半圆
newPointList.forEach((item) => {
if (item.type === 'line') {
console.log(item, 'item画直线, ')
const line = new BMapGL.Polyline(
[
new BMapGL.Point(item.start[0], item.start[1]),
new BMapGL.Point(item.end[0], item.end[1]),
],
{
strokeColor: 'red',
strokeWeight: 2,
strokeOpacity: 0.8,
},
)
this.map.addOverlay(line)
} else if (item.type === 'arc') {
console.log('画半圆', item)
// 绘制半圆弧线
if (item.start_point && item.end_point && item.center && item.radius) {
const line = new BMapGL.Polyline(
[
new BMapGL.Point(item.start_point[0], item.start_point[1]),
new BMapGL.Point(item.end_point[0], item.end_point[1]),
],
{
strokeColor: 'red',
strokeWeight: 2,
strokeOpacity: 0.8,
},
)
this.map.addOverlay(line)
// const arcPoints = this.generateArcPoints(
// item.center,
// item.radius,
// item.start_point,
// item.end_point,
// )
// const arcPolyline = new BMapGL.Polyline(arcPoints, {
// strokeColor: 'red',
// strokeWeight: 2,
// strokeOpacity: 0.8,
// })
// this.map.addOverlay(arcPolyline)
} else {
// 如果没有起点和终点,则绘制完整圆(备用方案)
const circle = new BMapGL.Circle(
new BMapGL.Point(item.center[0], item.center[1]),
item.radius,
{
strokeColor: 'red',
strokeWeight: 2,
fillColor: 'rgba(0,0,255,0.3)',
},
)
this.map.addOverlay(circle)
}
}
})
// if (pointList.points && pointList.points.length > 0) {
// // 提取坐标点和角度信息
// const pointsWithAngles = pointList.points.map((point) => {
// const [lng, lat, , , angle] = point // 提取经度、纬度和角度
// return {
// point: new BMapGL.Point(lng, lat),
// angle: angle || 40, // 角度信息
// }
// })
// console.log(pointsWithAngles, 'pointsWithAngles')
// // 根据颜色设置线条样式
// const getColorByIndex = (colorIndex) => {
// const colors = ['red', 'yellow', 'green', 'cyan', 'blue', 'magenta', 'white', 'gray']
// return colors[colorIndex] || 'red'
// }
// // 生成一个0-7的随机整数作为颜色索引
// const colorIndex = Math.floor(Math.random() * 7)
// // 创建带弧度的多段线
// const curvedLinePoints = this.createCurvedLine(pointsWithAngles)
// // 创建多段线(带弧度)
// const polyline = new BMapGL.Polyline(curvedLinePoints, {
// strokeColor: getColorByIndex(colorIndex),
// strokeWeight: 2,
// strokeOpacity: 0.8,
// strokeStyle: 'solid',
// })
// this.map.addOverlay(polyline)
// }
} else if (item.entityType === 'LINE') {
const geometry = JSON.parse(item.geometry)
const start = geometry.start
const end = geometry.end
// 转换坐标
const polyline = new BMapGL.Polyline(
[new BMapGL.Point(start[0], start[1]), new BMapGL.Point(end[0], end[1])],
{ strokeColor: 'red', strokeWeight: 2, strokeOpacity: 0.8 },
)
this.map.addOverlay(polyline)
} else if (item.entityType === 'CIRCLE') {
const geometry = JSON.parse(item.geometry)
const center = geometry.center
const radius = geometry.radius
// 转换中心点坐标
if (center.length > 0) {
const circle = new BMapGL.Circle(new BMapGL.Point(center[0], center[1]), radius, {
strokeColor: 'blue',
strokeWeight: 2,
fillColor: 'rgba(0,0,255,0.3)',
})
this.map.addOverlay(circle)
}
} else {
// 处理多边形或其他图形
const pointList = JSON.parse(item.geometry)
console.log(pointList, 'pointList')
if (pointList.points.length > 0) {
// 解析点坐标和角度(假设格式为 [x, y, ..., angle]
const path = pointList.points.map((p) => {
const [lng, lat, , , angle] = p // 提取角度(最后一个参数)
return {
point: new BMapGL.Point(lng, lat),
angle: angle || 0, // 角度默认为0
}
})
let overlay = null
if (path.length === 1) {
// 1. 单点:绘制带方向的标记(箭头)
const { point, angle } = path[0]
// 创建自定义箭头图标(根据角度旋转)
const arrowIcon = new BMapGL.Icon(
'//api.map.baidu.com/img/markers.png', // 可替换为箭头图片
new BMapGL.Size(20, 34),
{
anchor: new BMapGL.Size(10, 34), // 图标锚点
imageOffset: new BMapGL.Size(0, 0), // 图片偏移
},
)
overlay = new BMapGL.Marker(point, {
icon: arrowIcon,
rotation: (angle * 180) / Math.PI, // 转为角度制
})
} else if (path.length === 2) {
// 2. 线:绘制带箭头的线(终点显示角度方向)
const linePoints = path.map((p) => p.point)
const endAngle = (path[1].angle * 180) / Math.PI // 终点角度
// 绘制基础线条
overlay = new BMapGL.Polyline(linePoints, {
strokeColor: 'green',
strokeWeight: 2,
})
// 在终点添加带角度的箭头标记
const arrowMarker = new BMapGL.Marker(linePoints[1], {
icon: new BMapGL.Icon(
'//api.map.baidu.com/img/markers.png',
new BMapGL.Size(20, 34),
{ anchor: new BMapGL.Size(10, 34) },
),
rotation: endAngle,
})
this.map.addOverlay(arrowMarker) // 额外添加箭头标记
} else {
// 3. 多边形:保持原有逻辑,可在顶点添加角度标记
const polygonPoints = path.map((p) => p.point)
overlay = new BMapGL.Polygon(polygonPoints, {
strokeColor: '#3388ff',
strokeWeight: 2,
fillColor: 'rgba(51,136,255,0.2)',
})
}
this.map.addOverlay(overlay)
}
}
} catch (error) {
console.error('绘制图元失败:', item, error)
}
}
const points = this.map
.getOverlays()
.map((overlay) => {
if (overlay instanceof BMapGL.Marker) {
return overlay.getPosition()
} else if (overlay instanceof BMapGL.Polyline || overlay instanceof BMapGL.Polygon) {
return overlay.getPath()
} else if (overlay instanceof BMapGL.Circle) {
return overlay.getCenter()
}
})
.flat()
.filter(Boolean) // 过滤空值
if (points.length > 0) {
// 计算当前视图的最佳缩放级别
const viewport = this.map.getViewport(points)
// 调整缩放级别在最佳级别基础上增加n级值越大视角越近
const zoomOffset = 2 // 视角拉近的幅度建议1-3
// const adjustedZoom = Math.min(
// viewport.zoom + zoomOffset,
// this.map.getMaxZoom(), // 不超过地图最大缩放级别
// )
const adjustedZoom = Math.min(
16, // 直接设置一个较大的固定值如18
this.map.getMaxZoom(),
)
// 应用调整后的视图
this.map.setViewport(points, {
zoom: adjustedZoom, // 使用调整后的缩放级别
padding: [50, 50, 50, 50], // 增加边距,避免图形贴边
})
}
},
// 创建带弧度的线条
createCurvedLine(pointsWithAngles) {
if (pointsWithAngles.length < 2) {
return pointsWithAngles.map((p) => p.point)
}
const curvedPoints = []
for (let i = 0; i < pointsWithAngles.length - 1; i++) {
const currentPoint = pointsWithAngles[i]
const nextPoint = pointsWithAngles[i + 1]
// 添加当前点
curvedPoints.push(currentPoint.point)
// 如果当前点有角度信息,创建弧度
if (currentPoint.angle !== 0) {
const controlPoints = this.generateCurveControlPoints(
currentPoint.point,
nextPoint.point,
currentPoint.angle,
)
curvedPoints.push(...controlPoints)
}
}
// 添加最后一个点
curvedPoints.push(pointsWithAngles[pointsWithAngles.length - 1].point)
return curvedPoints
},
// 生成曲线控制点(基于角度)
generateCurveControlPoints(startPoint, endPoint, angle) {
const controlPoints = []
const segments = 5 // 在两点之间插入5个控制点来创建平滑曲线
// 计算起点和终点之间的距离
const distance = this.calculateDistance(startPoint, endPoint)
// 根据角度计算弧度半径
const radius = Math.abs(distance * 0.3) // 弧度半径为基础距离的30%
for (let i = 1; i < segments; i++) {
const t = i / segments // 插值参数 (0-1)
// 线性插值得到基础点
const baseLng = startPoint.lng + (endPoint.lng - startPoint.lng) * t
const baseLat = startPoint.lat + (endPoint.lat - startPoint.lat) * t
// 根据角度计算偏移
const offsetDistance = radius * Math.sin(t * Math.PI) // 使用正弦函数创建平滑弧度
const offsetLng = offsetDistance * Math.cos(angle) * 0.0001 // 经度偏移
const offsetLat = offsetDistance * Math.sin(angle) * 0.0001 // 纬度偏移
// 创建控制点
const controlPoint = new BMapGL.Point(baseLng + offsetLng, baseLat + offsetLat)
controlPoints.push(controlPoint)
}
return controlPoints
},
// 计算两点之间的距离(简化版本)
calculateDistance(point1, point2) {
const dx = point2.lng - point1.lng
const dy = point2.lat - point1.lat
return Math.sqrt(dx * dx + dy * dy)
},
// 生成圆弧点序列(用于绘制半圆弧线)
generateArcPoints(center, radius, startPoint, endPoint) {
// 将坐标转换为 BMapGL.Point 格式(如果还不是)
const centerPoint = Array.isArray(center) ? new BMapGL.Point(center[0], center[1]) : center
const start = Array.isArray(startPoint) ? new BMapGL.Point(startPoint[0], startPoint[1]) : startPoint
const end = Array.isArray(endPoint) ? new BMapGL.Point(endPoint[0], endPoint[1]) : endPoint
// 计算起点和终点相对于圆心的角度(弧度)
const startAngle = Math.atan2(start.lat - centerPoint.lat, start.lng - centerPoint.lng)
const endAngle = Math.atan2(end.lat - centerPoint.lat, end.lng - centerPoint.lng)
// 计算圆心角(弧度)
let angleDiff = endAngle - startAngle
// 标准化角度差到 [-π, π] 范围
if (angleDiff > Math.PI) {
angleDiff -= 2 * Math.PI
} else if (angleDiff < -Math.PI) {
angleDiff += 2 * Math.PI
}
// 确定绘制方向:默认按较小角度差的方向绘制
// 如果角度差大于 π180度则反向绘制
if (Math.abs(angleDiff) > Math.PI) {
angleDiff = angleDiff > 0 ? angleDiff - 2 * Math.PI : angleDiff + 2 * Math.PI
}
// 计算实际半径(度单位)
// 如果半径单位是米,需要转换为度
// 1度纬度约等于111公里1度经度在当前位置约等于111*cos(lat)公里
const latRad = (centerPoint.lat * Math.PI) / 180
const metersPerDegreeLat = 111000 // 每度纬度约111公里
// 判断半径单位:如果半径值很大(>1可能是米如果很小<0.1),可能是度
// 这里假设如果半径大于1则是米单位需要转换
let radiusInDegrees = radius
if (radius > 1) {
// 假设是米单位,转换为度
radiusInDegrees = radius / metersPerDegreeLat
}
// 生成圆弧上的点
const arcPoints = []
// 根据角度动态调整点的数量,确保圆弧平滑
const segments = Math.max(30, Math.abs(angleDiff) * 30)
// 添加起点
arcPoints.push(start)
// 生成圆弧中间的点
for (let i = 1; i < segments; i++) {
const t = i / segments // 插值参数 (0-1)
const angle = startAngle + angleDiff * t
// 计算圆弧上的点坐标
// 考虑经纬度的差异,使用正确的转换
const deltaLng = (radiusInDegrees * Math.cos(angle)) / Math.cos(latRad)
const deltaLat = radiusInDegrees * Math.sin(angle)
const x = centerPoint.lng + deltaLng
const y = centerPoint.lat + deltaLat
arcPoints.push(new BMapGL.Point(x, y))
}
// 添加终点
arcPoints.push(end)
return arcPoints
},
},
}
</script>
<style lang="scss" scoped>
/* 修改树形表格的展开图标为 +/- */
::v-deep .el-table .el-table__expand-icon {
transform: rotate(0deg); /* 取消默认的旋转效果 */
}
::v-deep .el-table .el-table__expand-icon .el-icon-arrow-right:before {
content: ''; /* 更大的加号 */
font-size: 16px; /* 调整大小 */
font-weight: bold;
border: 1px solid #409eff;
color: #409eff;
background-color: #f0f7ff;
}
::v-deep .el-table .el-table__expand-icon--expanded .el-icon-arrow-right:before {
content: ''; /* 更大的减号 */
font-size: 16px;
font-weight: bold;
border: 1px solid #409eff;
color: #409eff;
background-color: #f0f7ff;
}
#map-container {
min-height: 76vh;
}
</style>