添加代码详细注释,完善项目文档
This commit is contained in:
23
UpdateLog.md
23
UpdateLog.md
@@ -1,5 +1,28 @@
|
||||
# 仓库管理操作端项目更新记录
|
||||
|
||||
## 2024年9月17日 - 代码注释完善
|
||||
|
||||
**主要更新内容:**
|
||||
- 为项目核心文件添加了详细的注释文档,包括:
|
||||
- 视图组件:LayoutView.vue、HomeView.vue、Map.vue、LoginView.vue
|
||||
- 功能组件:Fence.vue、Filter.vue
|
||||
- 自定义钩子:useFence.js
|
||||
- 配置文件:main.js、router/index.js、stores/user.js、config/index.js
|
||||
- 优化了注释格式和内容组织,提高代码可读性和可维护性
|
||||
- 为每个文件添加了文件级注释、类级注释、函数级注释、参数和返回值注释
|
||||
- 补充了关键业务逻辑的说明和实现思路
|
||||
|
||||
**修复问题:**
|
||||
- 修复了部分文件中的注释格式问题
|
||||
|
||||
**已知问题:**
|
||||
- 暂无重大已知问题
|
||||
|
||||
**后续计划:**
|
||||
- 继续完善剩余文件的代码注释
|
||||
- 实现更多业务功能模块
|
||||
- 优化3D地图显示效果和交互体验
|
||||
|
||||
## 2024年9月16日 - 项目初始化
|
||||
|
||||
**主要更新内容:**
|
||||
|
18
src/App.vue
18
src/App.vue
@@ -1,11 +1,27 @@
|
||||
<template>
|
||||
<!-- 应用根组件的路由视图容器 -->
|
||||
<!-- 作为所有路由组件的挂载点,根据当前激活的路由动态渲染对应的组件内容 -->
|
||||
<router-view />
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
// App组件作为路由容器
|
||||
/**
|
||||
* 应用的根组件(App.vue)
|
||||
* 作为整个应用的入口组件,主要职责是提供路由视图容器
|
||||
* Vue Router会根据当前的URL路径,在此处动态渲染对应的页面组件
|
||||
*
|
||||
* 组件特点:
|
||||
* - 结构简洁,仅包含router-view作为路由出口
|
||||
* - 通过Vue Router的配置实现页面间的切换
|
||||
* - 所有子组件通过路由系统挂载到此处
|
||||
*/
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/**
|
||||
* 根组件样式
|
||||
* 当前保持为空,全局样式定义在assets/main.css中
|
||||
* 如需为根组件添加特定样式,可以在此处定义
|
||||
*/
|
||||
</style>
|
@@ -1,50 +1,104 @@
|
||||
<script setup>
|
||||
/**
|
||||
* 围栏组件
|
||||
* 在3D地图上生成特定区域的围栏,用于标识仓库中的重点监控区域
|
||||
* 基于THREE.js实现3D围栏的渲染和管理
|
||||
*/
|
||||
import { inject, onBeforeUnmount } from "vue"
|
||||
import { useFence } from "../hooks/useFence"
|
||||
import { useFence } from "../hooks/useFence" // 导入围栏生成自定义钩子
|
||||
|
||||
/**
|
||||
* 从VgoMap获取THREE.js实例
|
||||
* THREE.js是一个JavaScript 3D库,用于3D模型的渲染、动画和交互
|
||||
* 此处用于创建围栏的3D几何体并添加到地图场景中
|
||||
*/
|
||||
const { THREE } = VgoMap
|
||||
|
||||
// 可以使用pinia等管理全局数据,这里只是方便演示, 直接注入了上层提供的数据
|
||||
/**
|
||||
* 注入上级组件(Map.vue)提供的数据
|
||||
* 使用Vue的provide/inject机制获取父组件共享的数据
|
||||
*/
|
||||
// 从父组件注入地图实例,用于将围栏添加到地图场景
|
||||
const map = inject("map")
|
||||
// 从父组件注入所有多边形数据,包含仓库中所有建筑的点位信息
|
||||
const polygonDataAll = inject("polygonDataAll")
|
||||
|
||||
// 为了演示,随意取了几个模型,获取模型形状包含的所有点位
|
||||
/**
|
||||
* 筛选出需要包含在围栏内的建筑点位
|
||||
* 从所有多边形数据中筛选出指定名称的建筑
|
||||
*/
|
||||
const inFencePoints = polygonDataAll.value.filter(p => {
|
||||
// 筛选条件:建筑名称包含在指定的重点区域列表中
|
||||
// 这里选择了'保卫部', '智慧大楼', '能源部办公楼'作为重点监控区域
|
||||
return ['保卫部', '智慧大楼', '能源部办公楼'].includes(p.name)
|
||||
}).map(p => p.points).flat()
|
||||
}).map(p => p.points).flat() // 提取所有筛选出建筑的点位数据并扁平化数组
|
||||
|
||||
// 获取生成围栏函数,高60,并封顶
|
||||
/**
|
||||
* 使用自定义hook创建围栏生成函数
|
||||
* 从useFence钩子中解构出generateFence函数,用于生成围栏几何体
|
||||
* @param {Object} options - 围栏配置参数
|
||||
* @param {number} options.height - 围栏高度,此处设置为60单位
|
||||
* @param {boolean} options.isCloseTop - 是否封闭顶部,true表示生成封闭的围栏
|
||||
*/
|
||||
const { generateFence } = useFence({
|
||||
height: 60,
|
||||
isCloseTop: true,
|
||||
height: 60, // 围栏高度,单位与地图坐标系一致
|
||||
isCloseTop: true, // 是否封闭顶部,设置为true可生成顶部封闭的围栏
|
||||
})
|
||||
|
||||
// 建立一个围栏组,便于销毁所有围栏
|
||||
const fenceGroup = new THREE.Group()
|
||||
map.value.scene.add(fenceGroup)
|
||||
/**
|
||||
* 创建围栏组,用于统一管理和销毁围栏对象
|
||||
* 使用THREE.Group可以方便地组织和管理多个3D对象
|
||||
*/
|
||||
const fenceGroup = new THREE.Group() // 创建THREE.js组对象用于集中管理所有围栏相关的3D对象
|
||||
map.value.scene.add(fenceGroup) // 将围栏组添加到地图场景中,使其可见
|
||||
|
||||
// 生成围栏,并添加到围栏组
|
||||
/**
|
||||
* 生成围栏并添加到围栏组
|
||||
* 使用getMinBoxPoints计算出的最小包围矩形作为围栏的边界
|
||||
*/
|
||||
// 调用generateFence函数生成围栏几何体,传入通过getMinBoxPoints计算的最小矩形顶点
|
||||
const fence = generateFence(getMinBoxPoints(inFencePoints))
|
||||
// 将生成的围栏几何体添加到围栏组中
|
||||
fenceGroup.add(fence)
|
||||
|
||||
// 清理围栏
|
||||
/**
|
||||
* 组件生命周期钩子:卸载前执行
|
||||
* 清理围栏资源,避免内存泄漏
|
||||
*/
|
||||
onBeforeUnmount(() => {
|
||||
fenceGroup.dispose()
|
||||
// 安全检查:确保fenceGroup存在
|
||||
if (fenceGroup) {
|
||||
fenceGroup.clear() // 清除围栏组中的所有子对象
|
||||
// 安全检查:确保map和map.scene存在
|
||||
if (map.value && map.value.scene) {
|
||||
map.value.scene.remove(fenceGroup) // 从地图场景中移除围栏组
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// 根据所有包含的点位, 获取最小的矩形顶点数组
|
||||
/**
|
||||
* 根据所有包含的点位,计算并获取最小包围矩形的顶点数组
|
||||
* 算法原理:使用THREE.Box2计算点集的最小包围盒,然后提取四个顶点
|
||||
* @param {Array<THREE.Vector3>} points - 输入的点位数组,包含需要包围的所有点
|
||||
* @returns {Array<THREE.Vector3>} - 最小矩形的四个顶点坐标(按顺时针顺序)
|
||||
*/
|
||||
function getMinBoxPoints(points) {
|
||||
const box = new THREE.Box2()
|
||||
box.setFromPoints(points)
|
||||
const box = new THREE.Box2() // 创建二维包围盒对象
|
||||
box.setFromPoints(points) // 根据输入点集计算最小包围盒
|
||||
|
||||
// 返回最小矩形的四个顶点(按顺时针顺序)
|
||||
// 注意:这里z坐标统一设置为0,表示在地图的平面上
|
||||
return [
|
||||
new THREE.Vector3(box.min.x, box.min.y, 0), // 左下角 (0)
|
||||
new THREE.Vector3(box.max.x, box.min.y, 0), // 右下角 (1)
|
||||
new THREE.Vector3(box.max.x, box.max.y, 0), // 右上角 (2)
|
||||
new THREE.Vector3(box.min.x, box.max.y, 0) // 左上角 (3)
|
||||
new THREE.Vector3(box.min.x, box.min.y, 0), // 左下角顶点
|
||||
new THREE.Vector3(box.max.x, box.min.y, 0), // 右下角顶点
|
||||
new THREE.Vector3(box.max.x, box.max.y, 0), // 右上角顶点
|
||||
new THREE.Vector3(box.min.x, box.max.y, 0) // 左上角顶点
|
||||
];
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<!-- 围栏组件容器 -->
|
||||
<!-- 注意:此组件主要通过JavaScript在3D场景中渲染围栏,DOM元素仅作为组件的挂载点 -->
|
||||
<div class="fence"></div>
|
||||
</template>
|
@@ -1,96 +1,202 @@
|
||||
<script setup>
|
||||
/**
|
||||
* 筛选组件
|
||||
* 在3D地图上创建可筛选的设备标记点,并提供分类筛选功能
|
||||
* 用于对地图上的不同类型设备进行显示/隐藏控制
|
||||
* 基于THREE.js实现3D标记点的渲染和管理
|
||||
*/
|
||||
import { inject, computed, ref, watch, onBeforeUnmount } from "vue"
|
||||
|
||||
/**
|
||||
* 从VgoMap获取THREE.js实例
|
||||
* THREE.js是一个JavaScript 3D库,用于3D模型的渲染、动画和交互
|
||||
* 此处用于创建和管理3D标记点对象
|
||||
*/
|
||||
const { THREE } = VgoMap
|
||||
|
||||
// 可以使用pinia等管理全局数据,这里只是方便演示, 直接注入了上层提供的数据
|
||||
/**
|
||||
* 注入上级组件(Map.vue)提供的数据
|
||||
* 使用Vue的provide/inject机制获取父组件共享的数据
|
||||
*/
|
||||
// 从父组件注入地图实例,用于将标记点添加到地图场景
|
||||
const map = inject("map")
|
||||
// 从父组件注入所有多边形数据,包含仓库中所有设备的点位信息
|
||||
const polygonDataAll = inject("polygonDataAll")
|
||||
|
||||
// 筛选出有分类的点位
|
||||
/**
|
||||
* 计算属性:筛选出有分类的点位
|
||||
* 将点位按shortcutsId进行分组,用于生成筛选选项
|
||||
* @returns {Object} - 按shortcutsId分组的点位对象,键为分类ID,值为该分类下的点位数组
|
||||
*/
|
||||
const shortcutList = computed(() => {
|
||||
// 筛选出有shortcutsId属性的点位,并按shortcutsId进行分组
|
||||
return polygonDataAll.value.filter(i => i?.shortcutsId).reduce((result, item) => {
|
||||
// 如果该分类ID对应的数组不存在,则创建一个新数组
|
||||
if(!result?.[item.shortcutsId]) {
|
||||
result[item.shortcutsId] = []
|
||||
}
|
||||
// 将当前点位添加到对应分类的数组中
|
||||
result[item.shortcutsId].push(item)
|
||||
return result
|
||||
}, {})
|
||||
})
|
||||
|
||||
const markerGroup = new THREE.Group()
|
||||
map.value.scene.add(markerGroup)
|
||||
/**
|
||||
* 创建标记组,用于统一管理和销毁所有标记点
|
||||
* 使用THREE.Group可以方便地组织和管理多个3D对象
|
||||
*/
|
||||
const markerGroup = new THREE.Group() // 创建THREE.js组对象,用于集中管理所有标记点
|
||||
map.value.scene.add(markerGroup) // 将标记组添加到地图场景中,使其可见
|
||||
|
||||
/**
|
||||
* 组件生命周期钩子:卸载前执行
|
||||
* 清理标记点资源,避免内存泄漏
|
||||
*/
|
||||
onBeforeUnmount(() => {
|
||||
markerGroup.dispose()
|
||||
// 安全检查:确保markerGroup存在
|
||||
if (markerGroup) {
|
||||
markerGroup.clear() // 清除标记组中的所有子对象
|
||||
// 安全检查:确保map和map.scene存在
|
||||
if (map.value && map.value.scene) {
|
||||
map.value.scene.remove(markerGroup) // 从地图场景中移除标记组
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// 根据有分类点位,创建marker
|
||||
/**
|
||||
* 标记点状态引用
|
||||
* 存储各个分类下的标记点对象,用于后续根据用户选择控制显示/隐藏
|
||||
* 结构为:{分类ID: [标记点对象1, 标记点对象2, ...]}
|
||||
*/
|
||||
const markerStatus = ref({})
|
||||
|
||||
/**
|
||||
* 监听shortcutList变化,创建或更新标记点
|
||||
* 当shortcutList变化时,清理旧标记点并创建新标记点
|
||||
* @param {Object} value - 变化后的shortcutList值
|
||||
*/
|
||||
watch(shortcutList, (value) => {
|
||||
// 清理所有marker
|
||||
// 清理所有旧的标记点
|
||||
while(markerGroup.children.length) {
|
||||
markerGroup.children?.[0]?.dispose()
|
||||
const child = markerGroup.children[0]
|
||||
if (child && child.dispose) {
|
||||
child.dispose()
|
||||
}
|
||||
markerGroup.remove(child)
|
||||
}
|
||||
// 重置标记点状态对象
|
||||
markerStatus.value = {}
|
||||
|
||||
// 创建新的marker
|
||||
const listGroup = Object.entries(value)
|
||||
// 创建新的标记点
|
||||
const listGroup = Object.entries(value) // 转换为[key, value]格式的数组,便于遍历
|
||||
|
||||
// 遍历每个分类及其对应的点位列表
|
||||
listGroup.forEach(([key, list]) => {
|
||||
// 遍历该分类下的每个点位
|
||||
list.forEach((item) => {
|
||||
// 创建标记点,显示"设备类型+分类ID"
|
||||
const marker = createDivMarker('设备类型' + item.shortcutsId)
|
||||
|
||||
// 设置标记点位置(在点位中心,Z轴方向上移10单位,使其悬浮在点位上方)
|
||||
marker.position.copy(item.center).setZ(10)
|
||||
|
||||
// 将标记点添加到标记组
|
||||
markerGroup.add(marker)
|
||||
|
||||
// 将标记点添加到对应分类的状态数组中,便于后续控制显示/隐藏
|
||||
if(!markerStatus.value?.[key]) {
|
||||
markerStatus.value[key] = []
|
||||
}
|
||||
markerStatus.value[key].push(marker)
|
||||
|
||||
// 初始化时默认选中所有分类
|
||||
checkedStatus.value[key] = true
|
||||
})
|
||||
})
|
||||
}, {
|
||||
immediate: true,
|
||||
immediate: true, // 立即执行一次以初始化标记点
|
||||
})
|
||||
|
||||
// 根据选中状态,控制marker显示
|
||||
/**
|
||||
* 选中状态引用
|
||||
* 存储各个设备类型分类的选中状态,用于控制对应分类标记点的显示/隐藏
|
||||
* 结构为:{分类ID: boolean}
|
||||
*/
|
||||
const checkedStatus = ref({})
|
||||
|
||||
/**
|
||||
* 监听checkedStatus变化,控制标记点的显示/隐藏
|
||||
* 当用户通过复选框改变选中状态时,更新对应分类标记点的可见性
|
||||
* @param {Object} checked - 变化后的checkedStatus值
|
||||
*/
|
||||
watch(checkedStatus, (checked) => {
|
||||
// 遍历所有分类的选中状态
|
||||
for(let key in checked) {
|
||||
markerStatus.value[key].forEach((marker) => {
|
||||
marker.visible = checked[key]
|
||||
})
|
||||
// 安全检查:确保该分类下有标记点
|
||||
if (markerStatus.value[key] && Array.isArray(markerStatus.value[key])) {
|
||||
// 遍历该分类下的所有标记点
|
||||
markerStatus.value[key].forEach((marker) => {
|
||||
// 根据选中状态设置标记点的可见性
|
||||
marker.visible = checked[key]
|
||||
})
|
||||
}
|
||||
}
|
||||
}, {
|
||||
deep: true,
|
||||
deep: true, // 深度监听,确保能捕获到对象属性的变化
|
||||
})
|
||||
|
||||
/**
|
||||
* 创建DOM标记点
|
||||
* 在地图上创建一个自定义DOM元素作为标记点
|
||||
* @param {string} content - 标记点显示的文本内容
|
||||
* @returns {Object} - 创建的标记点对象,可用于进一步操作(如设置位置、可见性等)
|
||||
*/
|
||||
function createDivMarker(content) {
|
||||
// 创建包装元素
|
||||
const wrapper = document.createElement("div")
|
||||
|
||||
// 创建内容元素
|
||||
const contentNode = document.createElement("div")
|
||||
contentNode.className = "marker"
|
||||
contentNode.textContent = content
|
||||
contentNode.className = "marker" // 设置样式类,用于自定义标记点外观
|
||||
contentNode.textContent = content // 设置显示文本
|
||||
|
||||
// 将内容元素添加到包装元素中
|
||||
wrapper.appendChild(contentNode)
|
||||
|
||||
// 获取当前楼层ID,如果获取失败则默认为'1'
|
||||
const floorId = map.status?.floor?.data?.id || '1'
|
||||
return map.value.addDomMarker(floorId, wrapper) // 之前记错了,这边直接传dom元素也是可以的
|
||||
|
||||
// 使用地图API添加DOM标记点到指定楼层
|
||||
return map.value.addDomMarker(floorId, wrapper)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<!-- 筛选组件界面 -->
|
||||
<!-- 用于显示设备类型筛选选项,允许用户控制不同类型设备标记点的显示/隐藏 -->
|
||||
<ul class="filter">
|
||||
<!-- 遍历shortcutList的所有键,为每个设备类型生成筛选选项 -->
|
||||
<li v-for="key in Object.keys(shortcutList)" :key="key">
|
||||
<!-- 筛选选项标签,显示"设备类型+分类ID" -->
|
||||
<label :for="key">设备类型{{ key }}</label>
|
||||
<!-- 筛选复选框,与checkedStatus中的对应状态双向绑定 -->
|
||||
<input type="checkbox" :id="key" v-model="checkedStatus[key]">
|
||||
</li>
|
||||
</ul>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
/**
|
||||
* 筛选组件样式
|
||||
* 设置筛选面板的位置、层级和交互样式
|
||||
*/
|
||||
.filter {
|
||||
position: absolute;
|
||||
right: 50px;
|
||||
bottom: 20px;
|
||||
z-index: 10;
|
||||
position: absolute; /* 绝对定位,固定在地图右下角 */
|
||||
right: 50px; /* 距离右侧50px */
|
||||
bottom: 20px; /* 距离底部20px */
|
||||
z-index: 10; /* 设置较高层级,确保在地图上层显示 */
|
||||
|
||||
/* 为列表项、标签和输入框添加指针样式,表示可交互 */
|
||||
li, label, input {
|
||||
cursor: pointer;
|
||||
}
|
||||
@@ -99,10 +205,14 @@ function createDivMarker(content) {
|
||||
</style>
|
||||
|
||||
<style>
|
||||
/**
|
||||
* 全局样式 - 标记点样式
|
||||
* 定义地图上设备标记点的外观样式
|
||||
*/
|
||||
.marker {
|
||||
background-color: #fff;
|
||||
padding: 2px 10px;
|
||||
border-radius: 20px;
|
||||
transform: scale(0.7);
|
||||
background-color: #fff; /* 白色背景,提高可见度 */
|
||||
padding: 2px 10px; /* 内边距:上下2px,左右10px */
|
||||
border-radius: 20px; /* 圆角边框,使标记点看起来更柔和 */
|
||||
transform: scale(0.7); /* 缩小标记点显示尺寸,避免遮挡过多地图内容 */
|
||||
}
|
||||
</style>
|
@@ -1,74 +1,143 @@
|
||||
// 系统配置文件
|
||||
// 这个文件包含所有全局配置项,可以在整个应用中访问
|
||||
/**
|
||||
* 全局配置文件
|
||||
* 包含应用的所有全局配置项,可在整个应用中访问
|
||||
* 支持通过外部配置文件(window.APP_CONFIG)覆盖默认配置
|
||||
*/
|
||||
|
||||
// 优先使用外部配置文件中的配置,如果没有则使用默认值
|
||||
/**
|
||||
* 获取外部配置
|
||||
* 优先使用外部配置文件中的配置,如果没有则使用默认值
|
||||
* @type {Object} 外部配置对象
|
||||
*/
|
||||
const externalConfig = typeof window !== 'undefined' && window.APP_CONFIG || {};
|
||||
|
||||
/**
|
||||
* 导出全局配置对象
|
||||
* 包含应用基本信息、API配置、布局配置、主题配置等
|
||||
* @returns {Object} 全局配置对象
|
||||
*/
|
||||
export default {
|
||||
// 应用基本信息
|
||||
/**
|
||||
* 应用基本信息配置
|
||||
* 包含应用名称、版本和描述等基本信息
|
||||
*/
|
||||
app: {
|
||||
// 合并外部配置中的app配置
|
||||
...externalConfig.app,
|
||||
// 应用名称(优先使用外部配置,否则使用默认值)
|
||||
name: externalConfig.app?.name || '仓库管理操作端',
|
||||
// 应用版本号(优先使用外部配置,否则使用默认值)
|
||||
version: externalConfig.app?.version || '1.0.0',
|
||||
// 应用描述信息(优先使用外部配置,否则使用默认值)
|
||||
description: externalConfig.app?.description || '现代化仓库管理系统前端操作界面'
|
||||
},
|
||||
|
||||
// API配置
|
||||
/**
|
||||
* API配置
|
||||
* 包含API请求相关的配置,如基础URL、超时时间等
|
||||
*/
|
||||
api: {
|
||||
// 合并外部配置中的api配置
|
||||
...externalConfig.api,
|
||||
// 优先使用外部配置,其次使用环境变量,最后使用默认值
|
||||
// API基础URL(优先使用外部配置,其次使用环境变量,最后使用默认值)
|
||||
baseUrl: externalConfig.api?.baseUrl || import.meta.env.VITE_API_BASE_URL || '/api',
|
||||
// API请求超时时间(毫秒)(优先使用外部配置,否则使用默认值)
|
||||
timeout: externalConfig.api?.timeout || 30000, // 30秒超时
|
||||
// 请求失败重试次数(优先使用外部配置,否则使用默认值)
|
||||
retryCount: externalConfig.api?.retryCount || 3, // 请求失败重试次数
|
||||
// 重试间隔时间(毫秒)(优先使用外部配置,否则使用默认值)
|
||||
retryDelay: externalConfig.api?.retryDelay || 1000 // 重试间隔(毫秒)
|
||||
},
|
||||
|
||||
// 布局配置
|
||||
/**
|
||||
* 布局配置
|
||||
* 包含应用界面布局相关的配置,如侧边栏宽度、头部高度等
|
||||
*/
|
||||
layout: {
|
||||
sidebarWidth: 240, // 侧边栏宽度
|
||||
headerHeight: 60, // 头部高度
|
||||
transitionDuration: 300, // 过渡动画时长
|
||||
showBreadcrumb: true // 是否显示面包屑
|
||||
// 侧边栏宽度(像素)
|
||||
sidebarWidth: 240,
|
||||
// 头部导航栏高度(像素)
|
||||
headerHeight: 60,
|
||||
// 过渡动画时长(毫秒)
|
||||
transitionDuration: 300,
|
||||
// 是否显示面包屑导航
|
||||
showBreadcrumb: true
|
||||
},
|
||||
|
||||
// 主题配置
|
||||
/**
|
||||
* 主题配置
|
||||
* 包含应用界面主题相关的配置,如颜色、字体大小等
|
||||
*/
|
||||
theme: {
|
||||
primaryColor: '#1890ff', // 主色调
|
||||
successColor: '#52c41a', // 成功色
|
||||
warningColor: '#faad14', // 警告色
|
||||
errorColor: '#f5222d', // 错误色
|
||||
fontSize: 14, // 基础字体大小
|
||||
darkMode: false // 是否开启暗黑模式
|
||||
// 主色调(蓝色)
|
||||
primaryColor: '#1890ff',
|
||||
// 成功状态颜色(绿色)
|
||||
successColor: '#52c41a',
|
||||
// 警告状态颜色(黄色)
|
||||
warningColor: '#faad14',
|
||||
// 错误状态颜色(红色)
|
||||
errorColor: '#f5222d',
|
||||
// 基础字体大小(像素)
|
||||
fontSize: 14,
|
||||
// 是否开启暗黑模式
|
||||
darkMode: false
|
||||
},
|
||||
|
||||
// 缓存配置
|
||||
/**
|
||||
* 缓存配置
|
||||
* 包含应用数据缓存相关的配置
|
||||
*/
|
||||
cache: {
|
||||
// 是否启用缓存
|
||||
enable: true,
|
||||
defaultExpireTime: 24 * 60 * 60 * 1000 // 默认过期时间(24小时)
|
||||
// 默认缓存过期时间(毫秒)(24小时)
|
||||
defaultExpireTime: 24 * 60 * 60 * 1000
|
||||
},
|
||||
|
||||
// 分页配置
|
||||
/**
|
||||
* 分页配置
|
||||
* 包含数据分页显示相关的配置
|
||||
*/
|
||||
pagination: {
|
||||
// 可选的每页记录数
|
||||
pageSizes: [10, 20, 50, 100],
|
||||
// 默认每页记录数
|
||||
defaultPageSize: 20
|
||||
},
|
||||
|
||||
// 上传配置
|
||||
/**
|
||||
* 上传配置
|
||||
* 包含文件上传相关的配置,如最大文件大小、允许的文件类型等
|
||||
*/
|
||||
upload: {
|
||||
maxSize: 10 * 1024 * 1024, // 最大文件大小(10MB)
|
||||
// 最大文件大小(字节)(10MB)
|
||||
maxSize: 10 * 1024 * 1024,
|
||||
// 允许上传的文件类型
|
||||
allowedTypes: ['jpg', 'jpeg', 'png', 'gif', 'pdf', 'doc', 'docx', 'xls', 'xlsx']
|
||||
},
|
||||
|
||||
// 日期时间格式
|
||||
/**
|
||||
* 日期时间格式配置
|
||||
* 包含日期和时间的格式化配置
|
||||
*/
|
||||
date: {
|
||||
// 日期格式
|
||||
format: 'YYYY-MM-DD',
|
||||
// 时间格式
|
||||
timeFormat: 'HH:mm:ss',
|
||||
// 日期时间格式
|
||||
dateTimeFormat: 'YYYY-MM-DD HH:mm:ss'
|
||||
},
|
||||
|
||||
// 权限配置
|
||||
/**
|
||||
* 权限配置
|
||||
* 包含应用权限相关的配置,如超级管理员角色、默认角色等
|
||||
*/
|
||||
permission: {
|
||||
// 超级管理员角色名称
|
||||
superAdminRole: 'admin',
|
||||
// 默认用户角色名称
|
||||
defaultRole: 'user'
|
||||
}
|
||||
}
|
@@ -1,63 +1,136 @@
|
||||
/**
|
||||
* 围栏生成Hook
|
||||
* 提供在3D地图上生成围栏的功能
|
||||
* 基于THREE.js实现3D围栏的创建、纹理生成和材质配置
|
||||
*
|
||||
* @param {Object} options - 围栏配置选项
|
||||
* @param {number} options.height - 围栏高度,默认值为20
|
||||
* @param {string} options.color - 围栏颜色,默认值为橙色'#ff7f50'
|
||||
* @param {boolean} options.isCloseTop - 是否封闭顶部,默认值为false(不封闭)
|
||||
* @returns {Object} - 包含生成围栏函数的对象
|
||||
*/
|
||||
export function useFence({height = 20, isCloseTop = false, color = '#ff7f50'}) {
|
||||
const { THREE } = window.VgoMap
|
||||
|
||||
const tex = generateTexture(64)
|
||||
if(tex) {
|
||||
tex.wrapT = tex.wrapS = THREE.RepeatWrapping
|
||||
}
|
||||
/**
|
||||
* 生成围栏材质纹理
|
||||
* 使用64x64的HTML5 Canvas创建垂直渐变纹理
|
||||
* 纹理将用于围栏的侧面材质
|
||||
*/
|
||||
const tex = generateTexture(64)
|
||||
// 设置纹理的重复模式为REPEAT
|
||||
if(tex) {
|
||||
tex.wrapT = tex.wrapS = THREE.RepeatWrapping
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成围栏函数
|
||||
* 创建一个3D围栏网格对象,使用THREE.js的ExtrudeGeometry实现挤出效果
|
||||
*
|
||||
* @param {Array<THREE.Vector3>} points - 围栏的顶点数组,按顺时针或逆时针顺序排列
|
||||
* @returns {THREE.Mesh} - 生成的围栏3D网格对象
|
||||
*/
|
||||
function generateFence(points) {
|
||||
// 创建THREE.js形状对象
|
||||
const shape = new THREE.Shape()
|
||||
// 根据输入的顶点数组设置形状
|
||||
shape.setFromPoints(points)
|
||||
|
||||
// 创建挤出几何体,将2D形状挤出为3D围栏
|
||||
const geo = new THREE.ExtrudeGeometry(shape, {
|
||||
steps: 2,
|
||||
depth: height,
|
||||
bevelEnabled: false,
|
||||
steps: 2, // 挤出的层数
|
||||
depth: height, // 挤出的深度(围栏高度)
|
||||
bevelEnabled: false, // 禁用斜角
|
||||
})
|
||||
|
||||
// 创建围栏材质数组
|
||||
const mater = [
|
||||
// 顶部材质
|
||||
new THREE.MeshBasicMaterial({
|
||||
color,
|
||||
transparent: true,
|
||||
opacity: 0.1,
|
||||
visible: isCloseTop,
|
||||
visible: isCloseTop, // 根据配置决定是否显示顶部
|
||||
}),
|
||||
// 侧面材质
|
||||
new THREE.MeshBasicMaterial({
|
||||
map: tex,
|
||||
map: tex, // 使用渐变纹理
|
||||
transparent: true,
|
||||
color,
|
||||
opacity: 0.1,
|
||||
side: 2,
|
||||
side: 2, // 设置为双面显示(THREE.DoubleSide)
|
||||
})
|
||||
]
|
||||
|
||||
// 创建围栏网格对象
|
||||
const mesh = new THREE.Mesh(geo, mater)
|
||||
|
||||
// 统一设置所有材质的透明度为0.7
|
||||
mesh.material.forEach(m => m.opacity = 0.7)
|
||||
|
||||
return mesh
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成纹理函数
|
||||
* 创建一个垂直渐变的HTML5 Canvas纹理,用于围栏侧面的材质
|
||||
*
|
||||
* @param {number} size - 纹理大小,默认值为64
|
||||
* @returns {THREE.Texture|null} - 生成的THREE.js纹理对象,如果创建失败则返回null
|
||||
*/
|
||||
function generateTexture (size = 64) {
|
||||
// 创建HTML5 Canvas元素
|
||||
let canvas = document.createElement('canvas')
|
||||
canvas.width = size
|
||||
canvas.height = size
|
||||
|
||||
// 获取Canvas 2D上下文
|
||||
let ctx = canvas.getContext('2d')
|
||||
if(!ctx) return
|
||||
if(!ctx) return null // 如果无法获取上下文,则返回null
|
||||
|
||||
// 创建垂直线性渐变
|
||||
let linearGradient = ctx.createLinearGradient(0, 0, 0, size)
|
||||
// 顶部:完全透明
|
||||
linearGradient.addColorStop(0.2, hexToRgba(color, 0.0))
|
||||
// 中部:半透明
|
||||
linearGradient.addColorStop(0.8, hexToRgba(color, 0.5))
|
||||
// 底部:不透明
|
||||
linearGradient.addColorStop(1.0, hexToRgba(color, 1.0))
|
||||
|
||||
// 使用渐变填充整个Canvas
|
||||
ctx.fillStyle = linearGradient
|
||||
ctx.fillRect(0, 0, size, size)
|
||||
|
||||
// 创建THREE.js纹理对象
|
||||
let texture = new THREE.Texture(canvas)
|
||||
texture.needsUpdate = true // 必须
|
||||
texture.needsUpdate = true // 必须设置为true,通知THREE.js更新纹理
|
||||
|
||||
return texture
|
||||
}
|
||||
|
||||
/**
|
||||
* 将十六进制颜色转换为RGBA格式
|
||||
* 用于在Canvas绘制时设置带透明度的颜色
|
||||
*
|
||||
* @param {string} hex - 十六进制颜色值,格式为'#RRGGBB'
|
||||
* @param {number} opacity - 透明度,取值范围为0-1,默认值为1(完全不透明)
|
||||
* @returns {string} - RGBA格式的颜色字符串,格式为'rgba(R,G,B,A)'
|
||||
*/
|
||||
function hexToRgba (hex, opacity = 1) {
|
||||
return 'rgba(' + parseInt('0x' + hex.slice(1, 3)) + ',' + parseInt('0x' + hex.slice(3, 5)) + ',' +
|
||||
parseInt('0x' + hex.slice(5, 7)) + ',' + opacity + ')'
|
||||
// 提取R、G、B分量并转换为十进制
|
||||
const r = parseInt('0x' + hex.slice(1, 3))
|
||||
const g = parseInt('0x' + hex.slice(3, 5))
|
||||
const b = parseInt('0x' + hex.slice(5, 7))
|
||||
|
||||
// 构造并返回RGBA格式的颜色字符串
|
||||
return `rgba(${r},${g},${b},${opacity})`
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回hook提供的函数
|
||||
* 将内部实现的generateFence函数暴露给外部使用
|
||||
*/
|
||||
return {
|
||||
generateFence,
|
||||
generateFence, // 生成围栏的主要函数
|
||||
}
|
||||
}
|
62
src/main.js
62
src/main.js
@@ -1,29 +1,71 @@
|
||||
/**
|
||||
/**
|
||||
* 应用入口文件
|
||||
* 负责创建Vue应用实例、配置插件、注册全局组件和属性
|
||||
* 是整个Vue应用的启动点和配置中心
|
||||
*/
|
||||
|
||||
// 导入Vue的createApp函数,用于创建应用实例
|
||||
import { createApp } from 'vue'
|
||||
|
||||
// 导入应用的根组件
|
||||
import App from './App.vue'
|
||||
|
||||
// 导入路由配置
|
||||
import router from './router'
|
||||
|
||||
// 导入Element Plus UI组件库
|
||||
import ElementPlus from 'element-plus'
|
||||
|
||||
// 导入Element Plus的CSS样式
|
||||
import 'element-plus/dist/index.css'
|
||||
|
||||
// 导入全局自定义样式
|
||||
import './assets/main.css'
|
||||
|
||||
// 导入Pinia状态管理库
|
||||
import { createPinia } from 'pinia'
|
||||
|
||||
// 导入Element Plus的所有图标组件
|
||||
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
|
||||
|
||||
// 导入全局配置文件
|
||||
import config from './config'
|
||||
|
||||
const app = createApp(App)
|
||||
const pinia = createPinia()
|
||||
/**
|
||||
* 创建Vue应用实例和状态管理实例
|
||||
*/
|
||||
const app = createApp(App) // 创建Vue应用实例,以App组件为根组件
|
||||
const pinia = createPinia() // 创建Pinia状态管理实例
|
||||
|
||||
// 全局注册配置
|
||||
/**
|
||||
* 全局注册配置对象
|
||||
* 使配置在整个应用中可访问
|
||||
*/
|
||||
// 为Options API提供全局配置属性
|
||||
app.config.globalProperties.$config = config
|
||||
// 为Composition API提供配置
|
||||
|
||||
// 为Composition API提供全局配置,通过inject('config')获取
|
||||
app.provide('config', config)
|
||||
|
||||
// 全局注册所有图标
|
||||
/**
|
||||
* 全局注册所有Element Plus图标组件
|
||||
* 使所有图标组件在应用中无需单独导入即可使用
|
||||
*/
|
||||
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
|
||||
app.component(key, component)
|
||||
app.component(key, component) // 注册每个图标组件
|
||||
}
|
||||
|
||||
app.use(ElementPlus)
|
||||
app.use(router)
|
||||
app.use(pinia)
|
||||
/**
|
||||
* 应用插件配置
|
||||
* 依次注册Element Plus、Vue Router和Pinia插件
|
||||
*/
|
||||
app.use(ElementPlus) // 注册Element Plus UI组件库
|
||||
app.use(router) // 注册Vue Router路由管理
|
||||
app.use(pinia) // 注册Pinia状态管理
|
||||
|
||||
app.mount('#app')
|
||||
/**
|
||||
* 将Vue应用实例挂载到DOM元素
|
||||
* 使应用在id为'app'的DOM元素中渲染
|
||||
*/
|
||||
app.mount('#app') // 将应用挂载到HTML中的#app元素上
|
@@ -1,37 +1,52 @@
|
||||
/**
|
||||
/**
|
||||
* 应用路由配置文件
|
||||
* 定义应用的路由结构、组件映射和权限控制规则
|
||||
*/
|
||||
|
||||
// 导入Vue Router核心函数
|
||||
import { createRouter, createWebHistory } from 'vue-router'
|
||||
import LoginView from '../views/LoginView.vue'
|
||||
import LayoutView from '../views/LayoutView.vue'
|
||||
import HomeView from '../views/HomeView.vue'
|
||||
import KeyManager from '../views/Key/KeyManager.vue'
|
||||
|
||||
// 导入页面组件
|
||||
import LoginView from '../views/LoginView.vue' // 登录页面组件
|
||||
import LayoutView from '../views/LayoutView.vue' // 主布局组件
|
||||
import HomeView from '../views/HomeView.vue' // 首页组件
|
||||
import KeyManager from '../views/Key/KeyManager.vue' // 钥匙管理组件
|
||||
|
||||
// 导入用户状态管理
|
||||
import { useUserStore } from '../stores/user'
|
||||
|
||||
/**
|
||||
* 创建路由实例
|
||||
* 使用HTML5 History模式管理路由,需要服务器端配置支持
|
||||
*/
|
||||
const router = createRouter({
|
||||
history: createWebHistory(import.meta.env.BASE_URL),
|
||||
routes: [
|
||||
{
|
||||
path: '/login',
|
||||
name: 'login',
|
||||
component: LoginView
|
||||
component: LoginView // 登录页面,无需身份验证即可访问
|
||||
},
|
||||
{
|
||||
path: '/',
|
||||
name: 'layout',
|
||||
component: LayoutView,
|
||||
meta: { requiresAuth: true },
|
||||
component: LayoutView, // 主布局页面,包含侧边栏、顶栏等公共布局元素
|
||||
meta: { requiresAuth: true }, // 标记此路由需要登录权限
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
name: 'home',
|
||||
components: {
|
||||
map: HomeView
|
||||
map: HomeView // 默认视图,渲染首页组件到map视图容器
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/Key/KeyManager',
|
||||
name: 'keyManager',
|
||||
components: {
|
||||
map: HomeView,
|
||||
right: KeyManager
|
||||
map: HomeView, // 左侧显示地图视图
|
||||
right: KeyManager // 右侧显示钥匙管理视图
|
||||
}
|
||||
}
|
||||
// 可以在这里添加更多子路由
|
||||
@@ -40,18 +55,26 @@ const router = createRouter({
|
||||
]
|
||||
})
|
||||
|
||||
// 路由守卫
|
||||
/**
|
||||
* 全局前置路由守卫
|
||||
* 用于拦截所有路由跳转请求,实现基于角色的权限控制
|
||||
*/
|
||||
router.beforeEach((to, from, next) => {
|
||||
const userStore = useUserStore()
|
||||
const isLoggedIn = userStore.isLoggedIn
|
||||
const userStore = useUserStore() // 获取用户状态管理实例
|
||||
const isLoggedIn = userStore.isLoggedIn // 检查用户是否已登录
|
||||
|
||||
// 检查路由是否需要身份验证
|
||||
if (to.matched.some(record => record.meta.requiresAuth)) {
|
||||
// 需要身份验证的路由
|
||||
if (!isLoggedIn) {
|
||||
// 未登录时重定向到登录页面
|
||||
next('/login')
|
||||
} else {
|
||||
// 已登录用户允许访问
|
||||
next()
|
||||
}
|
||||
} else {
|
||||
// 不需要身份验证的路由直接放行
|
||||
next()
|
||||
}
|
||||
})
|
||||
|
@@ -1,28 +1,63 @@
|
||||
/**
|
||||
* 用户状态管理模块
|
||||
* 使用Pinia实现的用户状态管理,负责管理用户的登录状态、认证token和用户信息
|
||||
*/
|
||||
|
||||
// 导入Pinia的store定义函数
|
||||
import { defineStore } from 'pinia'
|
||||
|
||||
// 导入Axios用于API请求
|
||||
import axios from 'axios'
|
||||
|
||||
/**
|
||||
* 用户状态管理store定义
|
||||
* 使用Pinia的defineStore函数创建用户状态管理模块
|
||||
* @returns {Object} - 用户store实例,提供用户状态管理的各种方法和属性
|
||||
*/
|
||||
export const useUserStore = defineStore('user', {
|
||||
/**
|
||||
* 状态定义
|
||||
* 初始化用户相关的状态数据
|
||||
*/
|
||||
state: () => ({
|
||||
token: '',
|
||||
userInfo: null,
|
||||
menuList: []
|
||||
token: '', // 用户认证令牌,用于API请求认证
|
||||
userInfo: null, // 用户详细信息对象
|
||||
menuList: [] // 用户可访问的菜单列表
|
||||
}),
|
||||
|
||||
/**
|
||||
* 计算属性(getters)
|
||||
* 提供基于state的派生状态,用于获取用户登录状态等信息
|
||||
*/
|
||||
getters: {
|
||||
/**
|
||||
* 检查用户是否已登录
|
||||
* @param {Object} state - store当前状态
|
||||
* @returns {boolean} - 用户是否已登录的布尔值
|
||||
*/
|
||||
isLoggedIn: (state) => !!state.token
|
||||
},
|
||||
|
||||
/**
|
||||
* 动作方法(actions)
|
||||
* 提供修改状态的方法,包括登录、登出和初始化用户信息
|
||||
*/
|
||||
actions: {
|
||||
// 用户登录
|
||||
/**
|
||||
* 用户登录方法
|
||||
* 处理用户登录请求,保存登录状态和用户信息
|
||||
* @param {Object} credentials - 用户登录凭据,包含用户名和密码等信息
|
||||
* @returns {Promise<Object>} - 登录结果对象,包含成功状态和错误信息
|
||||
*/
|
||||
async login(credentials) {
|
||||
try {
|
||||
// 模拟登录请求
|
||||
const response = await axios.post('/api/login', credentials)
|
||||
const { token, userInfo, menuList } = response.data
|
||||
|
||||
this.token = token
|
||||
this.userInfo = userInfo
|
||||
this.menuList = menuList
|
||||
this.token = token // 保存token到状态
|
||||
this.userInfo = userInfo // 保存用户信息
|
||||
this.menuList = menuList // 保存菜单列表
|
||||
|
||||
// 保存token到localStorage
|
||||
localStorage.setItem('token', token)
|
||||
@@ -34,15 +69,22 @@ export const useUserStore = defineStore('user', {
|
||||
}
|
||||
},
|
||||
|
||||
// 用户登出
|
||||
/**
|
||||
* 用户登出方法
|
||||
* 清除用户所有状态信息并移除本地存储的token
|
||||
*/
|
||||
logout() {
|
||||
this.token = ''
|
||||
this.userInfo = null
|
||||
this.menuList = []
|
||||
localStorage.removeItem('token')
|
||||
this.token = '' // 清除token
|
||||
this.userInfo = null // 清除用户信息
|
||||
this.menuList = [] // 清除菜单列表
|
||||
localStorage.removeItem('token') // 移除本地存储的token
|
||||
},
|
||||
|
||||
// 初始化用户信息
|
||||
/**
|
||||
* 初始化用户信息方法
|
||||
* 从本地存储恢复用户登录状态并获取最新用户信息
|
||||
* @returns {Promise<void>} - 初始化过程的Promise
|
||||
*/
|
||||
async initUserInfo() {
|
||||
const token = localStorage.getItem('token')
|
||||
if (token) {
|
||||
@@ -50,11 +92,11 @@ export const useUserStore = defineStore('user', {
|
||||
try {
|
||||
// 模拟获取用户信息
|
||||
const response = await axios.get('/api/user/info')
|
||||
this.userInfo = response.data.userInfo
|
||||
this.menuList = response.data.menuList
|
||||
this.userInfo = response.data.userInfo // 保存用户信息
|
||||
this.menuList = response.data.menuList // 保存菜单列表
|
||||
} catch (error) {
|
||||
console.error('获取用户信息失败:', error)
|
||||
this.logout()
|
||||
this.logout() // 失败时自动登出
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,18 +1,21 @@
|
||||
<template>
|
||||
<!-- 首页组件 -->
|
||||
<div class="w-full h-full relative">
|
||||
<!-- 3D地图容器 - 充满整个页面 -->
|
||||
<!-- 3D地图容器 - 充满整个页面,展示仓库3D可视化效果 -->
|
||||
<div id="map3d-container" class="absolute inset-0 z-0 bg-gradient-to-br from-blue-950 via-[rgba(12,43,77,0.9)] to-cyan-950">
|
||||
<Map/>
|
||||
<Map/> <!-- 3D地图组件 -->
|
||||
</div>
|
||||
<!-- 悬浮信息卡片 - 科技感UI -->
|
||||
<!-- 悬浮信息卡片 - 科技感UI,显示系统状态信息 -->
|
||||
<div class="absolute top-24 right-8 z-10 bg-[rgba(12,43,77,0.85)] backdrop-blur-sm rounded-lg border border-cyan-700/50 shadow-lg p-4 w-64">
|
||||
<!-- 卡片标题和状态指示器 -->
|
||||
<div class="flex items-center justify-between mb-3">
|
||||
<h3 class="text-cyan-300 font-medium">系统状态</h3>
|
||||
<div class="w-2 h-2 bg-green-400 rounded-full animate-pulse"></div>
|
||||
<div class="w-2 h-2 bg-green-400 rounded-full animate-pulse"></div> <!-- 绿色脉冲动画表示系统正常运行 -->
|
||||
</div>
|
||||
<!-- 最后更新时间 -->
|
||||
<div class="text-xs text-cyan-100/70 mb-4">最后更新: {{ currentDate }}</div>
|
||||
|
||||
<!-- 系统信息列表 -->
|
||||
<!-- 系统信息列表 - 显示API地址、版本等信息 -->
|
||||
<div class="space-y-3">
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="text-sm text-cyan-100/80">API地址</span>
|
||||
@@ -32,10 +35,21 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
/**
|
||||
* 首页组件脚本部分
|
||||
* 使用组合式API实现首页功能
|
||||
*/
|
||||
|
||||
// 导入Vue组合式API
|
||||
import { ref, onMounted, inject } from 'vue'
|
||||
|
||||
// 导入3D地图组件
|
||||
import Map from "@/views/Map.vue"
|
||||
|
||||
// 通过inject获取全局配置,并提供默认值以防止页面空白
|
||||
/**
|
||||
* 通过inject获取全局配置,并提供默认值以防止页面空白
|
||||
* @type {Object} config - 全局配置对象
|
||||
*/
|
||||
const config = inject('config', {
|
||||
app: {
|
||||
name: '仓库管理操作端',
|
||||
@@ -49,15 +63,21 @@ const config = inject('config', {
|
||||
}
|
||||
})
|
||||
|
||||
// 提取常用配置项(带安全检查)
|
||||
const appName = config?.app?.name || '仓库管理操作端'
|
||||
const appVersion = config?.app?.version || '1.0.0'
|
||||
const apiBaseUrl = config?.api?.baseUrl || '/api'
|
||||
const defaultPageSize = config?.pagination?.defaultPageSize || 20
|
||||
/**
|
||||
* 提取常用配置项(带安全检查)
|
||||
*/
|
||||
const appName = config?.app?.name || '仓库管理操作端' // 应用名称
|
||||
const appVersion = config?.app?.version || '1.0.0' // 应用版本
|
||||
const apiBaseUrl = config?.api?.baseUrl || '/api' // API基础地址
|
||||
const defaultPageSize = config?.pagination?.defaultPageSize || 20 // 默认分页大小
|
||||
|
||||
const currentDate = ref('')
|
||||
const currentDate = ref('') // 当前日期字符串
|
||||
|
||||
// 格式化当前日期
|
||||
/**
|
||||
* 格式化当前日期为指定格式
|
||||
* @param {Date} date - 日期对象
|
||||
* @returns {string} - 格式化后的日期字符串
|
||||
*/
|
||||
const formatDate = (date) => {
|
||||
const year = date.getFullYear()
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0')
|
||||
@@ -70,8 +90,11 @@ const formatDate = (date) => {
|
||||
return `${year}年${month}月${day}日 星期${weekDay} ${hours}:${minutes}`
|
||||
}
|
||||
|
||||
/**
|
||||
* 组件挂载时的初始化操作
|
||||
*/
|
||||
onMounted(() => {
|
||||
currentDate.value = formatDate(new Date())
|
||||
currentDate.value = formatDate(new Date()) // 设置当前日期
|
||||
|
||||
// 模拟3D地图加载进度
|
||||
setTimeout(() => {
|
||||
@@ -82,12 +105,18 @@ onMounted(() => {
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 首页样式 */
|
||||
/**
|
||||
* 首页组件样式定义
|
||||
*/
|
||||
/* 地图容器样式 */
|
||||
#map3d-container {
|
||||
transition: all 0.5s ease;
|
||||
}
|
||||
|
||||
/* 自定义动画 */
|
||||
/**
|
||||
* 自定义动画定义
|
||||
*/
|
||||
/* 慢速旋转动画 */
|
||||
@keyframes spin-slow {
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
@@ -97,6 +126,7 @@ onMounted(() => {
|
||||
}
|
||||
}
|
||||
|
||||
/* 慢速反向旋转动画 */
|
||||
@keyframes spin-slow-reverse {
|
||||
from {
|
||||
transform: rotate(360deg);
|
||||
@@ -106,6 +136,7 @@ onMounted(() => {
|
||||
}
|
||||
}
|
||||
|
||||
/* 极慢速旋转动画 */
|
||||
@keyframes spin-very-slow {
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
@@ -115,6 +146,7 @@ onMounted(() => {
|
||||
}
|
||||
}
|
||||
|
||||
/* 动画类定义 */
|
||||
.animate-spin-slow {
|
||||
animation: spin-slow 20s linear infinite;
|
||||
}
|
||||
@@ -127,7 +159,9 @@ onMounted(() => {
|
||||
animation: spin-very-slow 40s linear infinite;
|
||||
}
|
||||
|
||||
/* 科技感进度条样式 */
|
||||
/**
|
||||
* 科技感进度条样式定制
|
||||
*/
|
||||
:deep(.el-progress-bar__outer) {
|
||||
background-color: rgba(104, 201, 255, 0.15) !important;
|
||||
border-radius: 10px !important;
|
||||
|
@@ -1,19 +1,23 @@
|
||||
<template>
|
||||
<!-- 钥匙管理组件模板 -->
|
||||
<div class="p-4">
|
||||
<!--选择钥匙柜-->
|
||||
<!-- 钥匙柜选择器 -->
|
||||
<el-select v-model="value" placeholder="请选择钥匙柜" style="width: 240px">
|
||||
<el-option v-for="item in options" :key="item.value" :label="item.label" :value="item.value" />
|
||||
</el-select>
|
||||
<el-divider />
|
||||
<!--显示钥匙柜及其中的钥匙状态-->
|
||||
<!-- 钥匙柜及钥匙状态展示区域 -->
|
||||
<el-row :gutter="20">
|
||||
<!-- 遍历钥匙列表,每个钥匙显示为一个卡片 -->
|
||||
<el-col :span="8" v-for="k in keyList" :key="k">
|
||||
<el-card shadow="always">
|
||||
<!-- 卡片头部 - 显示槽位号 -->
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>{{ k.keySlot }}</span>
|
||||
</div>
|
||||
</template>
|
||||
<!-- 卡片内容 - 显示钥匙详细信息 -->
|
||||
<div class="flex gap-2">
|
||||
<el-table :data="k.keyDatas" :show-header="false" style="width: 100%">
|
||||
<el-table-column prop="infotype" label="项目" />
|
||||
@@ -26,6 +30,7 @@
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
<!-- 卡片底部 - 操作按钮组 -->
|
||||
<template #footer>
|
||||
<div class="card-footer">
|
||||
<el-button-group>
|
||||
@@ -42,11 +47,21 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
/**
|
||||
* 钥匙管理组件脚本部分
|
||||
* 负责钥匙柜的选择、钥匙状态的展示和操作
|
||||
*/
|
||||
import { ref } from 'vue'
|
||||
import { Pointer, Unlock, Lock } from '@element-plus/icons-vue'
|
||||
import { Pointer, Unlock, Lock } from '@element-plus/icons-vue' // 导入操作按钮图标
|
||||
|
||||
const value = ref('')
|
||||
/**
|
||||
* 状态定义
|
||||
*/
|
||||
const value = ref('') // 当前选择的钥匙柜ID
|
||||
|
||||
/**
|
||||
* 钥匙柜选项列表
|
||||
*/
|
||||
const options = [
|
||||
{
|
||||
value: '1',
|
||||
@@ -58,21 +73,31 @@ const options = [
|
||||
}
|
||||
]
|
||||
|
||||
/**
|
||||
* 数据类型定义
|
||||
*/
|
||||
// 状态标签类型接口
|
||||
type Status = {
|
||||
type: string
|
||||
value: string
|
||||
type: string // 标签类型(primary/success/warning/danger/info)
|
||||
value: string // 标签显示内容
|
||||
}
|
||||
|
||||
// 钥匙数据项类型接口
|
||||
type KeyData = {
|
||||
infotype: string
|
||||
status: Status[]
|
||||
infotype: string // 信息类型(如:钥匙名称、钥匙状态等)
|
||||
status: Status[] // 状态标签数组
|
||||
}
|
||||
|
||||
// 钥匙槽位及其中钥匙数据的接口
|
||||
type KeyDatas = {
|
||||
keySlot: string
|
||||
keyDatas: KeyData[]
|
||||
keySlot: string // 槽位名称
|
||||
keyDatas: KeyData[] // 钥匙详细数据
|
||||
}
|
||||
|
||||
/**
|
||||
* 钥匙列表数据
|
||||
* 模拟从后端获取的钥匙柜中的钥匙数据
|
||||
*/
|
||||
const keyList: KeyDatas[] =
|
||||
[
|
||||
{
|
||||
@@ -179,16 +204,21 @@ const keyList: KeyDatas[] =
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 将KeyManager的背景色设置为全透明 */
|
||||
/**
|
||||
* 钥匙管理组件样式定义
|
||||
* 使用空军蓝为主色调,打造科技感界面
|
||||
*/
|
||||
/* 设置页面背景为透明 */
|
||||
:deep(.p-4) {
|
||||
background-color: transparent !important;
|
||||
}
|
||||
|
||||
/* 确保容器元素背景也是透明的 */
|
||||
/* 确保所有容器元素背景也是透明的 */
|
||||
div {
|
||||
background-color: transparent !important;
|
||||
}
|
||||
|
||||
/* 布局相关样式 */
|
||||
.el-row {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
@@ -208,7 +238,7 @@ div {
|
||||
|
||||
/* 空军蓝为主色调的卡片样式 */
|
||||
:deep(.el-card) {
|
||||
/* 空军蓝半透明背景 */
|
||||
/* 空军蓝半透明渐变背景 */
|
||||
background: linear-gradient(to right, rgba(12, 43, 77, 0.95), rgba(12, 43, 77, 0.7)) !important;
|
||||
backdrop-filter: blur(5px) !important;
|
||||
border: 1px solid #68c9ff !important;
|
||||
@@ -232,7 +262,7 @@ div {
|
||||
font-size: 14px !important;
|
||||
}
|
||||
|
||||
/* 卡片内容中的段落文本样式 */
|
||||
/* 卡片内容中的表格样式 */
|
||||
:deep(.el-card__body table tr) {
|
||||
color: #b6dfff !important;
|
||||
width: 100%;
|
||||
@@ -256,7 +286,7 @@ div {
|
||||
margin-top: 0 !important;
|
||||
}
|
||||
|
||||
/* 调整选择器样式以适应整体色调 */
|
||||
/* 钥匙柜选择器样式 */
|
||||
:deep(.el-select) {
|
||||
background: rgba(12, 43, 77, 0.7) !important;
|
||||
border: 1px solid #68c9ff !important;
|
||||
|
@@ -1,6 +1,7 @@
|
||||
<template>
|
||||
<!-- 主布局组件 -->
|
||||
<div class="h-screen flex flex-col relative overflow-hidden">
|
||||
<!-- 顶栏 - 悬浮在最上层 -->
|
||||
<!-- 顶栏 - 悬浮在最上层,显示系统标题和用户信息 -->
|
||||
<header class="h-16 bg-[rgba(20,60,110,0.85)] backdrop-blur-sm shadow-lg flex items-center justify-between px-6 z-30">
|
||||
<div class="flex items-center">
|
||||
<el-icon class="text-cyan-300 mr-2"><Database /></el-icon>
|
||||
@@ -28,14 +29,14 @@
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- 3D地图容器 - 最底层,全屏显示 -->
|
||||
<!-- 3D地图容器 - 最底层,全屏显示,用于展示仓库的3D地图视图 -->
|
||||
<div class="fixed inset-0 z-0">
|
||||
<router-view name="map" />
|
||||
</div>
|
||||
|
||||
<!-- 侧边栏容器 - 固定定位,不占用主内容空间 -->
|
||||
<!-- 侧边栏容器 - 固定定位,不占用主内容空间,用于显示系统功能菜单 -->
|
||||
<div class="fixed left-0 top-16 h-[calc(100%-4rem)] z-20">
|
||||
<!-- 左侧菜单栏 -->
|
||||
<!-- 左侧菜单栏 - 可折叠,显示多级菜单 -->
|
||||
<aside class="bg-gradient-to-r from-[rgba(20,60,110,1)] to-[rgba(20,60,110,0.5)] backdrop-blur-sm border-r border-[#68c9ff] overflow-y-auto transition-all duration-300 ease-in-out h-full"
|
||||
:style="{ width: isCollapsed ? '64px' : '250px' }">
|
||||
<!-- 菜单内容 -->
|
||||
@@ -50,8 +51,8 @@
|
||||
v-if="menuList && menuList.length > 0"
|
||||
unique-opened="true"
|
||||
>
|
||||
<!-- 二级菜单结构 -->
|
||||
<template v-for="menu in menuList" :key="menu.id">
|
||||
<!-- 二级菜单结构 - 动态渲染菜单列表 -->
|
||||
<template v-for="menu in menuList" :key="menu.id">
|
||||
<!-- 有子菜单的一级菜单 -->
|
||||
<el-sub-menu
|
||||
v-if="menu.children && menu.children.length > 0"
|
||||
@@ -105,8 +106,8 @@
|
||||
</aside>
|
||||
</div>
|
||||
|
||||
<!-- 折叠按钮 - 固定定位,不占用主内容空间 -->
|
||||
<div class="fixed left-[250px] top-1/2 -translate-y-1/2 z-30 transition-all duration-300 ease-in-out"
|
||||
<!-- 折叠按钮 - 固定定位,不占用主内容空间,用于切换侧边栏折叠状态 -->
|
||||
<div class="fixed left-[250px] top-1/2 -translate-y-1/2 z-30 transition-all duration-300 ease-in-out"
|
||||
:style="{ left: isCollapsed ? '64px' : '250px' }">
|
||||
<el-button
|
||||
@click="toggleCollapse"
|
||||
@@ -116,7 +117,7 @@
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Drawer组件 - 用于显示页面内容 -->
|
||||
<!-- Drawer组件 - 从右侧滑出,用于显示具体功能页面内容 -->
|
||||
<el-drawer
|
||||
v-model="drawerVisible"
|
||||
:title="currentMenu?.name || '页面内容'"
|
||||
@@ -136,23 +137,28 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted, computed } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useUserStore } from '@/stores/user'
|
||||
import { ElMessage, ElDrawer } from 'element-plus'
|
||||
/**
|
||||
* 主布局组件脚本部分
|
||||
* 使用组合式API实现布局功能和交互逻辑
|
||||
* 管理侧边栏折叠状态、菜单导航和用户认证
|
||||
*/
|
||||
import { ref, onMounted, computed } from 'vue' // 导入Vue组合式API
|
||||
import { useRouter } from 'vue-router' // 导入路由相关函数
|
||||
import { useUserStore } from '@/stores/user' // 导入用户状态管理
|
||||
import { ElMessage, ElDrawer } from 'element-plus' // 导入Element Plus组件
|
||||
|
||||
const router = useRouter()
|
||||
const userStore = useUserStore()
|
||||
const activeMenu = computed(() => router.currentRoute.value.path)
|
||||
const router = useRouter() // 路由实例
|
||||
const userStore = useUserStore() // 用户状态管理
|
||||
const activeMenu = computed(() => router.currentRoute.value.path) // 当前激活的菜单路径
|
||||
|
||||
// 侧边栏折叠状态
|
||||
const isCollapsed = ref(false)
|
||||
const isCollapsed = ref(false) // 侧边栏是否折叠
|
||||
|
||||
// Drawer相关状态
|
||||
const drawerVisible = ref(false)
|
||||
const currentMenu = ref(null)
|
||||
const drawerVisible = ref(false) // Drawer是否可见
|
||||
const currentMenu = ref(null) // 当前选中的菜单项
|
||||
|
||||
// 模拟菜单数据(二级菜单结构)
|
||||
// 模拟菜单数据,实际应用中应从userStore获取
|
||||
const menuList = ref([
|
||||
{
|
||||
id: '1',
|
||||
@@ -160,8 +166,7 @@ const menuList = ref([
|
||||
path: '/',
|
||||
icon: 'House'
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
{ id: '2',
|
||||
name: '钥匙管理',
|
||||
icon: 'Key',
|
||||
children: [
|
||||
@@ -170,8 +175,7 @@ const menuList = ref([
|
||||
{ id: '2-3', name: '钥匙取用记录', path: '/warehouse/stats' }
|
||||
]
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
{ id: '3',
|
||||
name: '巡检管理',
|
||||
icon: 'View',
|
||||
children: [
|
||||
@@ -180,8 +184,7 @@ const menuList = ref([
|
||||
{ id: '3-3', name: '巡检记录', path: '/inventory/alerts' }
|
||||
]
|
||||
},
|
||||
{
|
||||
id: '4',
|
||||
{ id: '4',
|
||||
name: '车辆管理',
|
||||
icon: 'Van',
|
||||
children: [
|
||||
@@ -191,16 +194,14 @@ const menuList = ref([
|
||||
{ id: '4-4', name: '车辆出入营区记录', path: '/inventory/alerts' }
|
||||
]
|
||||
},
|
||||
{
|
||||
id: '5',
|
||||
{ id: '5',
|
||||
name: '环境监控',
|
||||
icon: 'Odometer',
|
||||
children: [
|
||||
{ id: '5-1', name: '环境变量管理', path: '/inventory/overview' }
|
||||
]
|
||||
},
|
||||
{
|
||||
id: '6',
|
||||
{ id: '6',
|
||||
name: '信息管理',
|
||||
icon: 'Document',
|
||||
children: [
|
||||
@@ -208,8 +209,7 @@ const menuList = ref([
|
||||
{ id: '6-2', name: '安全提示', path: '/inventory/detail' }
|
||||
]
|
||||
},
|
||||
{
|
||||
id: '7',
|
||||
{ id: '7',
|
||||
name: '访客管理',
|
||||
icon: 'User',
|
||||
children: [
|
||||
@@ -217,8 +217,7 @@ const menuList = ref([
|
||||
{ id: '7-2', name: '来访车辆管理', path: '/inventory/detail' }
|
||||
]
|
||||
},
|
||||
{
|
||||
id: '8',
|
||||
{ id: '8',
|
||||
name: '营区管理',
|
||||
icon: 'MapLocation',
|
||||
children: [
|
||||
@@ -226,8 +225,7 @@ const menuList = ref([
|
||||
{ id: '8-2', name: '人员区域管理', path: '/inventory/detail' }
|
||||
]
|
||||
},
|
||||
{
|
||||
id: '9',
|
||||
{ id: '9',
|
||||
name: '无人机管理',
|
||||
icon: 'Promotion',
|
||||
children: [
|
||||
@@ -236,53 +234,83 @@ const menuList = ref([
|
||||
}
|
||||
])
|
||||
|
||||
/**
|
||||
* 用户信息响应式数据
|
||||
* 用于显示当前登录用户的信息
|
||||
*/
|
||||
const userInfo = ref({
|
||||
name: '管理员'
|
||||
name: '管理员' // 默认用户名称
|
||||
})
|
||||
|
||||
// 切换侧边栏折叠状态
|
||||
/**
|
||||
* 切换侧边栏折叠状态
|
||||
* 控制侧边栏的展开和收起
|
||||
*/
|
||||
const toggleCollapse = () => {
|
||||
isCollapsed.value = !isCollapsed.value
|
||||
}
|
||||
|
||||
// 处理一级菜单点击事件(在折叠状态下自动展开)
|
||||
/**
|
||||
* 处理一级菜单点击事件
|
||||
* 在折叠状态下点击一级菜单时自动展开侧边栏
|
||||
*/
|
||||
const handleSubMenuClick = () => {
|
||||
if (isCollapsed.value) {
|
||||
toggleCollapse()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理菜单项点击事件
|
||||
* @param {Object} menu - 菜单对象,包含路径和名称等信息
|
||||
*/
|
||||
const handleMenuClick = (menu) => {
|
||||
currentMenu.value = menu
|
||||
drawerVisible.value = true
|
||||
// 首页菜单仍然使用路由跳转
|
||||
currentMenu.value = menu // 保存当前选中的菜单项
|
||||
drawerVisible.value = true // 显示右侧抽屉
|
||||
|
||||
// 首页菜单特殊处理:不显示抽屉,直接路由跳转
|
||||
if (menu.path === '/') {
|
||||
drawerVisible.value = false
|
||||
}
|
||||
router.push(menu.path)
|
||||
|
||||
router.push(menu.path) // 跳转到对应路由
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理用户退出登录
|
||||
* 清除用户状态并跳转到登录页面
|
||||
*/
|
||||
const handleLogout = () => {
|
||||
userStore.logout()
|
||||
ElMessage.success('退出登录成功')
|
||||
router.push('/login')
|
||||
userStore.logout() // 调用用户store的logout方法,清除用户状态
|
||||
ElMessage.success('退出登录成功') // 显示成功提示信息
|
||||
router.push('/login') // 跳转到登录页面
|
||||
}
|
||||
|
||||
/**
|
||||
* 组件生命周期钩子:挂载时执行
|
||||
* 初始化用户信息和菜单列表
|
||||
*/
|
||||
onMounted(() => {
|
||||
// 初始化用户信息
|
||||
userStore.initUserInfo().then(() => {
|
||||
// 更新用户信息
|
||||
if (userStore.userInfo) {
|
||||
userInfo.value = userStore.userInfo
|
||||
}
|
||||
|
||||
// 安全检查:确保userStore.menuList存在并有值
|
||||
if (userStore.menuList && userStore.menuList.length > 0) {
|
||||
menuList.value = userStore.menuList
|
||||
menuList.value = userStore.menuList // 更新菜单列表
|
||||
}
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/**
|
||||
* 布局组件样式定义
|
||||
* 包含菜单、顶栏、Drawer等自定义样式
|
||||
*/
|
||||
/* 布局样式 */
|
||||
.el-menu {
|
||||
border-right: none;
|
||||
@@ -293,7 +321,7 @@ onMounted(() => {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
/* 空军蓝背景 */
|
||||
/* 空军蓝背景 - 用户下拉菜单样式 */
|
||||
:deep(.el-dropdown-menu) {
|
||||
background-color: rgba(12, 43, 77, 0.95) !important;
|
||||
border: 1px solid #68c9ff !important;
|
||||
|
@@ -30,6 +30,7 @@
|
||||
<el-checkbox v-model="loginForm.remember" class="text-gray-600">记住密码</el-checkbox>
|
||||
</el-form-item>
|
||||
|
||||
<!-- 登录按钮 -->
|
||||
<el-form-item>
|
||||
<el-button
|
||||
type="primary"
|
||||
@@ -46,22 +47,40 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
/**
|
||||
* 登录页面脚本部分
|
||||
* 使用组合式API实现登录逻辑
|
||||
*/
|
||||
|
||||
// 导入Vue组合式API
|
||||
import { ref } from 'vue'
|
||||
|
||||
// 导入Vue Router
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
// 导入用户状态管理
|
||||
import { useUserStore } from '@/stores/user'
|
||||
|
||||
// 导入Element Plus消息提示
|
||||
import { ElMessage } from 'element-plus'
|
||||
|
||||
const router = useRouter()
|
||||
const userStore = useUserStore()
|
||||
const loginFormRef = ref(null)
|
||||
const loading = ref(false)
|
||||
const router = useRouter() // 路由实例
|
||||
const userStore = useUserStore() // 用户状态管理
|
||||
const loginFormRef = ref(null) // 表单引用
|
||||
const loading = ref(false) // 登录按钮加载状态
|
||||
|
||||
/**
|
||||
* 登录表单数据
|
||||
*/
|
||||
const loginForm = ref({
|
||||
username: '',
|
||||
password: '',
|
||||
remember: false
|
||||
username: '', // 用户名
|
||||
password: '', // 密码
|
||||
remember: false // 是否记住密码
|
||||
})
|
||||
|
||||
/**
|
||||
* 表单验证规则
|
||||
*/
|
||||
const loginRules = {
|
||||
username: [
|
||||
{ required: true, message: '请输入用户名', trigger: 'blur' }
|
||||
@@ -71,10 +90,15 @@ const loginRules = {
|
||||
]
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理登录逻辑
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
const handleLogin = async () => {
|
||||
try {
|
||||
// 验证表单
|
||||
await loginFormRef.value.validate()
|
||||
loading.value = true
|
||||
loading.value = true // 设置按钮加载状态
|
||||
|
||||
// 模拟登录API调用
|
||||
// 由于是演示环境,我们直接模拟成功登录
|
||||
@@ -101,18 +125,18 @@ const handleLogin = async () => {
|
||||
userStore.token = mockResponse.token
|
||||
userStore.userInfo = mockResponse.userInfo
|
||||
userStore.menuList = mockResponse.menuList
|
||||
localStorage.setItem('token', mockResponse.token)
|
||||
localStorage.setItem('token', mockResponse.token) // 保存token到本地存储
|
||||
|
||||
ElMessage.success('登录成功')
|
||||
router.push('/')
|
||||
ElMessage.success('登录成功') // 显示成功提示
|
||||
router.push('/') // 跳转到首页
|
||||
} catch (error) {
|
||||
ElMessage.error(error.message || '登录失败')
|
||||
ElMessage.error(error.message || '登录失败') // 显示错误提示
|
||||
} finally {
|
||||
loading.value = false
|
||||
loading.value = false // 重置加载状态
|
||||
}
|
||||
}, 1000)
|
||||
} catch (error) {
|
||||
loading.value = false
|
||||
loading.value = false // 重置加载状态
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
@@ -1,16 +1,34 @@
|
||||
<script setup>
|
||||
/**
|
||||
* 3D地图主组件
|
||||
* 负责初始化VgoMap 3D地图引擎,管理地图状态,并为子组件提供共享数据
|
||||
*/
|
||||
|
||||
// 导入Vue组合式API
|
||||
import { onMounted, ref, computed, provide } from "vue"
|
||||
import Fence from "../components/Fence.vue"
|
||||
import Filter from "../components/Filter.vue"
|
||||
|
||||
const { VgoMap } = window
|
||||
let mapId = '1958120048849719296'
|
||||
const isLoaded = ref(false)
|
||||
// 导入子组件
|
||||
import Fence from "../components/Fence.vue" // 3D地图围栏组件,用于绘制区域边界
|
||||
import Filter from "../components/Filter.vue" // 地图筛选组件,用于分类显示/隐藏标记点
|
||||
|
||||
const map = ref()
|
||||
/**
|
||||
* 地图相关常量和状态定义
|
||||
*/
|
||||
const { VgoMap } = window // 从全局对象获取VgoMap 3D地图引擎实例
|
||||
let mapId = '1958120048849719296' // 地图资源唯一标识ID
|
||||
const isLoaded = ref(false) // 地图加载完成状态标记
|
||||
const map = ref() // 地图实例引用,用于操作地图API
|
||||
|
||||
/**
|
||||
* 计算所有多边形数据
|
||||
* 合并室外和室内的多边形数据,为围栏和筛选组件提供完整的地图数据
|
||||
* @returns {Array} - 合并后的多边形数据数组
|
||||
*/
|
||||
const polygonDataAll = computed(() => {
|
||||
// 获取室外多边形数据
|
||||
const outDoor = map.value?.mapData?.polygonData ?? []
|
||||
|
||||
// 获取室内多边形数据(遍历建筑和楼层)
|
||||
const inDoor = map?.mapData?.build?.reduce((result, build) => {
|
||||
build.floor.forEach(fItem => {
|
||||
result.push(...fItem.polygonData)
|
||||
@@ -18,43 +36,61 @@ const polygonDataAll = computed(() => {
|
||||
return result
|
||||
}, []) ?? []
|
||||
|
||||
// 合并室内外数据并返回
|
||||
return [...outDoor, ...inDoor]
|
||||
})
|
||||
|
||||
provide("map", computed(() => map.value))
|
||||
provide('polygonDataAll', polygonDataAll)
|
||||
/**
|
||||
* 使用Vue的provide API为子组件提供共享数据
|
||||
* 使Fence和Filter组件能够访问地图实例和多边形数据
|
||||
*/
|
||||
provide("map", computed(() => map.value)) // 提供地图实例
|
||||
provide('polygonDataAll', polygonDataAll) // 提供多边形数据
|
||||
|
||||
/**
|
||||
* 组件生命周期钩子:挂载后执行
|
||||
* 初始化3D地图实例并设置事件监听器
|
||||
*/
|
||||
onMounted(() => {
|
||||
// 创建地图实例
|
||||
map.value = new VgoMap.Map({
|
||||
el: "mapContainer",
|
||||
id: mapId,
|
||||
el: "mapContainer", // 地图容器元素ID
|
||||
id: mapId, // 地图ID
|
||||
})
|
||||
|
||||
// 监听地图加载完成事件
|
||||
map.value.on("loaded", () => {
|
||||
isLoaded.value = true
|
||||
isLoaded.value = true // 更新地图加载状态
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<!-- 地图组件模板 -->
|
||||
<div class="wrapper">
|
||||
<!-- 地图渲染容器,VgoMap将在此处渲染3D地图 -->
|
||||
<div id="mapContainer"></div>
|
||||
<!-- UI组件容器 - 当地图加载完成后显示,确保组件在地图数据准备就绪后初始化 -->
|
||||
<div v-if="isLoaded" class="ui">
|
||||
<Fence/>
|
||||
<Filter/>
|
||||
<Fence/> <!-- 围栏组件,用于在地图上绘制区域边界 -->
|
||||
<Filter/> <!-- 筛选组件,用于控制地图上不同类型标记点的显示/隐藏 -->
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
/**
|
||||
* 地图组件样式定义
|
||||
* 设置地图容器的基本布局样式
|
||||
*/
|
||||
.wrapper {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
position: relative; /* 相对定位,为子组件的绝对定位提供基准 */
|
||||
}
|
||||
|
||||
#mapContainer {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
height: 100%; /* 使地图充满整个父容器 */
|
||||
}
|
||||
</style>
|
Reference in New Issue
Block a user