Compare commits
10 Commits
088ec2f76a
...
master
Author | SHA1 | Date | |
---|---|---|---|
![]() |
04e2a9ec43 | ||
![]() |
e1c2e6e4aa | ||
![]() |
64a731b4a5 | ||
![]() |
85b63b23cd | ||
![]() |
f767dc30a1 | ||
![]() |
ed260de5f8 | ||
![]() |
ac895e5db6 | ||
![]() |
a8212be62e | ||
![]() |
b4f1734024 | ||
![]() |
d95c4ac587 |
@@ -3,6 +3,9 @@
|
||||
## 项目简介
|
||||
仓库管理操作端是一个基于Vue 3构建的现代化仓库管理系统前端界面,提供钥匙管理、巡检管理、车辆管理、环境监控等多项功能,采用空军蓝主题设计,结合3D地图展示,实现直观高效的仓库管理体验。
|
||||
|
||||
## 项目更新记录
|
||||
请查看[项目更新日志](UpdateLog.md)了解详细的项目更新历史。
|
||||
|
||||
## 技术栈
|
||||
- **前端框架**: Vue 3.4.21
|
||||
- **构建工具**: Vite 5.2.0
|
||||
|
149
UpdateLog.md
Normal file
149
UpdateLog.md
Normal file
@@ -0,0 +1,149 @@
|
||||
# 仓库管理操作端项目更新记录
|
||||
|
||||
## 2024年4月18日 - 路径管理功能优化
|
||||
|
||||
### 路径管理模块
|
||||
* 📝**[修改]**:为路径点增加地图模型ID属性支持
|
||||
* 📝**[修改]**:在PathManager.vue中更新模拟数据,为每个路径点添加modelId字段
|
||||
* 📝**[修改]**:在PathEditor.vue中增加modelId字段的显示和处理
|
||||
* 📝**[修改]**:实现设备选择时自动同步modelId的功能
|
||||
* 📝**[修改]**:优化PathEditor.vue中地图实例获取和应用方式
|
||||
* 📝**[修改]**:重构地图初始化逻辑,将其与组件生命周期解耦
|
||||
* 📝**[修改]**:实现基于Intersection Observer的组件可见性监测
|
||||
* 📝**[修改]**:确保地图只在组件实际显示时才初始化,避免DOM未渲染问题
|
||||
* 📝**[修改]**:为dialog组件添加ID标识,便于可见性监测
|
||||
* 📝**[修改]**:添加降级方案,确保即使在特殊情况下也能初始化地图
|
||||
|
||||
## 2024年4月17日 - 巡检路径管理功能开发
|
||||
|
||||
### 巡检路径模块
|
||||
* ➕**[新增]**:创建PathManager组件,用于管理巡检路径信息
|
||||
* ➕**[新增]**:创建PathEditor组件,包含路径编辑、地图显示和路径点配置功能
|
||||
* ➕**[新增]**:创建PathSchedule组件,用于管理巡检计划,包含周期、时间、人员和路线配置
|
||||
* 📝**[修改]**:在PathEditor组件中引入Map组件,实现地图展示功能
|
||||
* 📝**[修改]**:优化PathSchedule组件弹窗样式,设置为空军蓝、透明度40%并添加高斯模糊效果
|
||||
* 📝**[修改]**:将PathSchedule组件表单布局从两列改为单列,避免控件超出弹窗范围
|
||||
* ➕**[新增]**:创建PathLog组件,实现巡检日志管理功能,包含人员选择、日期时间、设备和排班记录配置
|
||||
* 📝**[修改]**:更新路由配置,添加巡检日志管理相关路由
|
||||
* 📝**[修改]**:更新LayoutView中的菜单配置,添加巡检日志管理菜单项
|
||||
* 📝**[修改]**:设置PathLog组件表格背景为透明样式,优化视觉效果
|
||||
* 📝**[修改]**:进一步优化PathLog组件表格表头透明度,确保表头完全透明
|
||||
* 📝**[修改]**:更新路由配置,添加巡检路径和计划管理相关路由
|
||||
|
||||
## 2024年4月17日 - 钥匙申请功能优化
|
||||
|
||||
### 钥匙管理模块
|
||||
* 📝**[修改]**:优化KeyApply组件,从userStore中获取当前登录用户名并显示在申请人字段
|
||||
* 📝**[修改]**:添加了错误处理和日志输出,方便调试
|
||||
|
||||
## 2024年4月15日 - 界面优化与路由参数修复
|
||||
|
||||
* ➕**[新增]** 钥匙管理模块功能
|
||||
* ➕**[新增]** 创建钥匙使用申请组件(KeyApply.vue)
|
||||
* ➕**[新增]** 创建钥匙取用记录组件(KeyRecord.vue)
|
||||
* 📝**[修改]** 更新路由配置,添加新组件路由
|
||||
* 📝**[修改]** 增强KeyManager组件,实现申请按钮与申请组件的绑定
|
||||
|
||||
* 🐛**[修复]** 钥匙管理模块问题修复
|
||||
* 🐛**[修复]** 增强KeyApply组件的路由参数处理逻辑,添加类型检查和错误处理
|
||||
* 📝**[修改]** 将KeyRecord组件的表格背景修改为透明
|
||||
* 📝**[修改]** 优化KeyManager组件中的handleApply函数,从keyList中提取并组织钥匙信息
|
||||
* 📝**[修改]** 改进KeyApply组件,优先使用直接传递的钥匙信息,同时保留后备提取逻辑
|
||||
|
||||
## 2025年10月12日 - 图标规范更新
|
||||
|
||||
* 📝**[修改]** 文档规范
|
||||
* ➕**[新增]** 修改新增功能图标为绿色+号(➕)
|
||||
* 📝**[修改]** 修改功能图标更换为橙色铅笔(📝)
|
||||
* 🐛**[新增]** 新增[修复]标签,配置紫色虫子图标(🐛)
|
||||
* 📝**[修改]** 更新所有示例格式以符合新规范
|
||||
|
||||
## 2025年10月12日 - 提示词规范优化
|
||||
|
||||
* 📝**[修改]** 文档规范
|
||||
* 修改图标表示法:新增使用➕图标,修改使用📝图标,新增修复使用🐛图标
|
||||
* 修改标签格式:变更类型使用方括号包裹
|
||||
|
||||
## 2025年10月12日 - 提示词规范更新
|
||||
|
||||
* ➕**[新增]** 文档规范
|
||||
* ➕**[新增]**:在TraePrompt_猫娘工程师.md中添加Git提交规范
|
||||
* 📝**[修改]**:明确要求Git提交前必须更新UpdateLog.md
|
||||
* 📝**[修改]**:规定使用YYYY年MM月DD日日期格式
|
||||
* 📝**[修改]**:定义模块分级输出(最多三级)与图标表示法
|
||||
* ➕**[新增]**:使用➕表示新增、📝表示修改、🐛表示修复、❌表示删除
|
||||
* ➕**[新增]**:添加了标准示例格式模板
|
||||
|
||||
## 2025年10月12日 - 全屏功能实现
|
||||
|
||||
**主要更新内容:**
|
||||
- ➕**[新增]** 在应用顶栏中添加了全屏切换按钮,位于用户头像和用户名右侧
|
||||
- ➕**[新增]** 实现了完整的全屏功能,包括:
|
||||
- 进入全屏模式
|
||||
- 退出全屏模式
|
||||
- 自动检测系统全屏状态变化
|
||||
- 按钮图标根据全屏状态动态切换
|
||||
- 增加了事件监听和清理逻辑,确保组件挂载时添加监听、卸载时移除监听
|
||||
- 按钮样式与系统整体设计保持一致,使用了相同的空军蓝配色方案
|
||||
|
||||
**修复问题:**
|
||||
- 修复了LayoutView.vue中Drawer的modal属性设置问题
|
||||
|
||||
**已知问题:**
|
||||
- 暂无重大已知问题
|
||||
|
||||
**后续计划:**
|
||||
- 继续完善用户界面交互体验
|
||||
- 实现更多功能模块
|
||||
|
||||
## 2025年10月12日 - 代码注释完善
|
||||
|
||||
**主要更新内容:**
|
||||
- 为项目核心文件添加了详细的注释文档,包括:
|
||||
- 视图组件: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地图显示效果和交互体验
|
||||
|
||||
## 2025年10月10日 - 项目初始化
|
||||
|
||||
**主要更新内容:**
|
||||
- 基于Vue 3 + Vite搭建了前端项目框架
|
||||
- 配置了技术栈:Vue 3.4.21、Vite 5.2.0、Element Plus 2.7.0、Tailwind CSS
|
||||
- 实现了核心功能模块的基础框架:
|
||||
- 登录页面 (LoginView.vue)
|
||||
- 钥匙管理功能 (KeyManager.vue)
|
||||
- 地图监控功能 (Map.vue)
|
||||
- 整体布局组件 (LayoutView.vue)
|
||||
- 创建了项目结构和基础配置文件
|
||||
- 添加了README.md文档,详细描述了项目技术栈和功能规划
|
||||
- 成功将项目代码提交到Gitea远程仓库
|
||||
- 完成了第一个版本的开发环境搭建和验证
|
||||
|
||||
**修复问题:**
|
||||
- 修复了KeyManager.vue中表格循环无法正常取数据的问题
|
||||
- 解决了图标导入和显示问题
|
||||
|
||||
**已知问题:**
|
||||
- 暂无重大已知问题
|
||||
|
||||
**后续计划:**
|
||||
- 按照README.md中规划的功能模块逐步实现
|
||||
- 完善钥匙管理、巡检管理、车辆管理等核心业务功能
|
||||
- 优化用户界面和交互体验
|
||||
- 增加数据接口对接和后端集成
|
||||
- 实现权限管理和用户认证功能
|
@@ -10,7 +10,7 @@
|
||||
<link rel="stylesheet" href="./fonts/iconfont.css" />
|
||||
<script>
|
||||
window.$config = {
|
||||
api: "./api.vgomap.com/",
|
||||
api: "/api.vgomap.com/",
|
||||
offline: true,
|
||||
mapTheme: "home",
|
||||
tenantId: "1958120048849719296",
|
||||
|
BIN
public/api.vgomap.com/MapData/1977947221534052352.zip
Normal file
BIN
public/api.vgomap.com/MapData/1977947221534052352.zip
Normal file
Binary file not shown.
@@ -0,0 +1 @@
|
||||
{"code":200,"data":{"bodyParts":[],"symptoms":[],"symptomCauses":[]},"message":null}
|
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
|
||||
{"code":200,"data":{"id":"1977947221534052352","name":"安徽省滁州市南谯区大柳镇北园桥","themeId":1,"cover":"","industry":"园区","center":[118.08292026198603,32.42616151903459],"address":"大柳镇","remark":"安徽省滁州市南谯区大柳镇北园桥","creationTime":"2025-10-14T11:59:00.7533897","templateId":"d6aee0aa-0c20-4ded-2cf3-08ddcf3ee146","needLogin":true,"other":"{\"theme\":\"\"}","version":null,"tenantId":"1977945000528449536"},"message":null}
|
1
public/api.vgomap.com/api/Map/Data/1977947221534052352
Normal file
1
public/api.vgomap.com/api/Map/Data/1977947221534052352
Normal file
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
|
||||
{"code":200,"data":[],"message":null}
|
@@ -54,6 +54,18 @@
|
||||
<div class="content unicode" style="display: block;">
|
||||
<ul class="icon_lists dib-box">
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">refresh</div>
|
||||
<div class="code-name">&#xe733;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">route-line</div>
|
||||
<div class="code-name">&#xe86e;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">访客</div>
|
||||
@@ -2382,9 +2394,9 @@
|
||||
<pre><code class="language-css"
|
||||
>@font-face {
|
||||
font-family: 'iconfont';
|
||||
src: url('iconfont.woff2?t=1756889729585') format('woff2'),
|
||||
url('iconfont.woff?t=1756889729585') format('woff'),
|
||||
url('iconfont.ttf?t=1756889729585') format('truetype');
|
||||
src: url('iconfont.woff2?t=1760587007493') format('woff2'),
|
||||
url('iconfont.woff?t=1760587007493') format('woff'),
|
||||
url('iconfont.ttf?t=1760587007493') format('truetype');
|
||||
}
|
||||
</code></pre>
|
||||
<h3 id="-iconfont-">第二步:定义使用 iconfont 的样式</h3>
|
||||
@@ -2410,6 +2422,24 @@
|
||||
<div class="content font-class">
|
||||
<ul class="icon_lists dib-box">
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont iconrefresh"></span>
|
||||
<div class="name">
|
||||
refresh
|
||||
</div>
|
||||
<div class="code-name">.iconrefresh
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont iconroute-line"></span>
|
||||
<div class="name">
|
||||
route-line
|
||||
</div>
|
||||
<div class="code-name">.iconroute-line
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont iconfangke"></span>
|
||||
<div class="name">
|
||||
@@ -5902,6 +5932,22 @@
|
||||
<div class="content symbol">
|
||||
<ul class="icon_lists dib-box">
|
||||
|
||||
<li class="dib">
|
||||
<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use xlink:href="#iconrefresh"></use>
|
||||
</svg>
|
||||
<div class="name">refresh</div>
|
||||
<div class="code-name">#iconrefresh</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use xlink:href="#iconroute-line"></use>
|
||||
</svg>
|
||||
<div class="name">route-line</div>
|
||||
<div class="code-name">#iconroute-line</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use xlink:href="#iconfangke"></use>
|
||||
|
@@ -1,8 +1,8 @@
|
||||
@font-face {
|
||||
font-family: "iconfont"; /* Project id 2223488 */
|
||||
src: url('iconfont.woff2?t=1756889729585') format('woff2'),
|
||||
url('iconfont.woff?t=1756889729585') format('woff'),
|
||||
url('iconfont.ttf?t=1756889729585') format('truetype');
|
||||
src: url('iconfont.woff2?t=1760587007493') format('woff2'),
|
||||
url('iconfont.woff?t=1760587007493') format('woff'),
|
||||
url('iconfont.ttf?t=1760587007493') format('truetype');
|
||||
}
|
||||
|
||||
.iconfont {
|
||||
@@ -13,6 +13,14 @@
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
.iconrefresh:before {
|
||||
content: "\e733";
|
||||
}
|
||||
|
||||
.iconroute-line:before {
|
||||
content: "\e86e";
|
||||
}
|
||||
|
||||
.iconfangke:before {
|
||||
content: "\e731";
|
||||
}
|
||||
|
File diff suppressed because one or more lines are too long
@@ -5,6 +5,20 @@
|
||||
"css_prefix_text": "icon",
|
||||
"description": "",
|
||||
"glyphs": [
|
||||
{
|
||||
"icon_id": "44044081",
|
||||
"name": "refresh",
|
||||
"font_class": "refresh",
|
||||
"unicode": "e733",
|
||||
"unicode_decimal": 59187
|
||||
},
|
||||
{
|
||||
"icon_id": "42232309",
|
||||
"name": "route-line",
|
||||
"font_class": "route-line",
|
||||
"unicode": "e86e",
|
||||
"unicode_decimal": 59502
|
||||
},
|
||||
{
|
||||
"icon_id": "41061831",
|
||||
"name": "访客",
|
||||
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
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>
|
56
src/UpdateLog.md
Normal file
56
src/UpdateLog.md
Normal file
@@ -0,0 +1,56 @@
|
||||
## 2024年6月8日 - 路径管理功能增强
|
||||
|
||||
### 状态管理
|
||||
- ➕**[新增]**:添加pathStore.js,用于管理全局路径点信息
|
||||
- 实现了pathPoints数组,存储{modelId, deviceName}结构的数据
|
||||
- 提供syncPathPoints方法,同步路径点数据
|
||||
- 提供clearPathPoints方法,清空路径点
|
||||
- 提供getPathPoints方法,获取路径点列表
|
||||
|
||||
### 路径编辑功能
|
||||
- 📝**[修改]**:更新useEditNavi.js
|
||||
- 导入pathStore实现全局数据同步
|
||||
- 在pathIdList变化时自动同步到全局store
|
||||
- 实现了从deviceList查找对应设备名称的功能
|
||||
|
||||
- 📝**[修改]**:更新PathEditor.vue
|
||||
- 导入pathStore并获取实例
|
||||
- 添加对话框可见性监听器,在打开时清空pathPoints数组
|
||||
- 根据编辑/新增状态,动态同步pathForm.points到pathStore
|
||||
- 新增对pathStore.pathPoints数组的深度监听,实现双向数据同步
|
||||
- 添加变更检测机制,避免循环更新问题
|
||||
- 实现全局状态到表单数据的智能转换和映射
|
||||
|
||||
## 2024-6-7
|
||||
|
||||
- 修复CarManager组件多个显示问题
|
||||
- 移除了formRef和applyFormRef的冗余初始化定义
|
||||
- 增强了表单提交的错误处理逻辑
|
||||
- 用原生alert替代可能不存在的$message组件
|
||||
- 优化了弹窗样式,提高背景不透明度至0.95,z-index设置为9999
|
||||
- 为表格和卡片添加边框样式确保可见
|
||||
- 添加完整的按钮样式确保操作按钮可见
|
||||
- 优化输入框、选择框等表单元素样式
|
||||
- 添加分页组件和确认对话框样式
|
||||
|
||||
2024-1-21
|
||||
|
||||
- 创建车辆信息管理(CarManager)组件,包含车辆信息表格展示、增删改查功能
|
||||
- 创建车辆出入库记录(CarUseLog)组件,实现车辆出入库记录的管理
|
||||
- 创建车辆出入营区记录(CarEntryExitLog)组件,实现车辆进出营区记录的管理
|
||||
- CarManager组件中添加了车辆申请功能,点击申请按钮可打开弹窗,展示车辆信息、申请人和多行申请事由
|
||||
- 更新路由配置,添加三个车辆管理组件的路由
|
||||
- 更新菜单配置,添加车辆管理相关菜单项
|
||||
- 对Car文件夹下的三个组件(CarManager、CarUseLog、CarEntryExitLog)进行样式修改
|
||||
- 将所有表格背景改为透明
|
||||
- 将所有表头背景改为透明
|
||||
- 调整表格边框颜色为半透明浅蓝色
|
||||
- 调整表格文字颜色为浅蓝色
|
||||
- 添加表格悬停效果
|
||||
- 修复CarManager弹窗无法正常打开的问题
|
||||
- 添加表单引用的初始化
|
||||
- 增加表单引用的安全检查
|
||||
- 提高弹窗不透明度,确保弹窗可见
|
||||
- 添加z-index属性,确保弹窗在最上层显示
|
||||
- PathLog组件中表格表头改为透明
|
||||
- 添加了表头透明度的CSS样式,确保表头在各种状态下都保持透明
|
BIN
src/assets/textures/corona.png
Normal file
BIN
src/assets/textures/corona.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 8.6 KiB |
BIN
src/assets/textures/moon.png
Normal file
BIN
src/assets/textures/moon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 51 KiB |
BIN
src/assets/textures/stars.png
Normal file
BIN
src/assets/textures/stars.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 7.5 KiB |
38
src/components/DisplayColor.vue
Normal file
38
src/components/DisplayColor.vue
Normal file
@@ -0,0 +1,38 @@
|
||||
<script setup>
|
||||
import { inject, ref, watch, computed } from "vue"
|
||||
import { useDisplayColor } from "@/hooks/useDisplayColor"
|
||||
|
||||
// 可以使用pinia等管理全局数据,这里只是方便演示, 直接注入了上层提供的数据
|
||||
const map = inject("map")
|
||||
|
||||
const { changeDisplayColor } = useDisplayColor(computed(() => map.value))
|
||||
|
||||
const isChangeColor = ref(true)
|
||||
|
||||
watch(isChangeColor, (value) => {
|
||||
changeDisplayColor(value)
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ul class="display-color">
|
||||
<li>
|
||||
<label for="defalut-map-color">默认</label>
|
||||
<input id="defalut-map-color" name="color" type="radio" :value="true" v-model="isChangeColor">
|
||||
</li>
|
||||
<li>
|
||||
<label for="changed-map-color">科技</label>
|
||||
<input id="changed-map-color" name="color" type="radio" :value="false" v-model="isChangeColor">
|
||||
</li>
|
||||
</ul>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.display-color {
|
||||
margin: 0;
|
||||
|
||||
li, label, input {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
</style>
|
61
src/components/DisplayRouteLine.vue
Normal file
61
src/components/DisplayRouteLine.vue
Normal file
@@ -0,0 +1,61 @@
|
||||
<script setup>
|
||||
import { inject } from "vue"
|
||||
import { useNavi } from "@/hooks/useNavi"
|
||||
|
||||
// 可以使用pinia等管理全局数据,这里只是方便演示, 直接注入了上层提供的数据
|
||||
const map = inject("map")
|
||||
|
||||
const {
|
||||
pathIdList,
|
||||
addPath,
|
||||
startNavi,
|
||||
pauseNavi,
|
||||
restoreNavi,
|
||||
stopNavi,
|
||||
} = useNavi(map)
|
||||
|
||||
map.value.on('click', (e) => {
|
||||
const polygon = e.object?.userData?.polygonData // 获取点位数据
|
||||
if(!polygon?.isNavi) return
|
||||
|
||||
addPath(polygon)
|
||||
})
|
||||
|
||||
defineExpose({
|
||||
addPath,
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="navi">
|
||||
<div class="path-list-status">
|
||||
<div>点击具有分类的标签或模型生成线路:</div>
|
||||
<div class="selected">{{ pathIdList.join(",") }}</div>
|
||||
<button @click="() => pathIdList.pop()">删除末尾</button>
|
||||
<button @click="() => pathIdList = []">清空</button>
|
||||
<button @click="() => startNavi()" :disabled="pathIdList.length < 2">开始导航</button>
|
||||
<button @click="() => pauseNavi()" :disabled="pathIdList.length < 2">暂停</button>
|
||||
<button @click="() => restoreNavi()" :disabled="pathIdList.length < 2">继续</button>
|
||||
<button @click="() => stopNavi()" :disabled="pathIdList.length < 2">结束导航</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.path-list-status {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.selected {
|
||||
min-height: 1.5em;
|
||||
width: 300px;
|
||||
padding: 0 5px;
|
||||
background-color: #fff;
|
||||
border: 1px solid #767676;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
overflow: auto;
|
||||
}
|
||||
</style>
|
61
src/components/EditDisplayRouteLine.vue
Normal file
61
src/components/EditDisplayRouteLine.vue
Normal file
@@ -0,0 +1,61 @@
|
||||
<script setup>
|
||||
import { inject } from "vue"
|
||||
import { useNavi } from "@/hooks/useEditNavi"
|
||||
|
||||
// 可以使用pinia等管理全局数据,这里只是方便演示, 直接注入了上层提供的数据
|
||||
const map = inject("editMap")
|
||||
|
||||
const {
|
||||
pathIdList,
|
||||
addPath,
|
||||
startNavi,
|
||||
pauseNavi,
|
||||
restoreNavi,
|
||||
stopNavi,
|
||||
} = useNavi(map)
|
||||
|
||||
map.value.on('click', (e) => {
|
||||
const polygon = e.object?.userData?.polygonData // 获取点位数据
|
||||
if(!polygon?.isNavi) return
|
||||
|
||||
addPath(polygon)
|
||||
})
|
||||
|
||||
defineExpose({
|
||||
addPath,
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="navi">
|
||||
<div class="path-list-status">
|
||||
<div>点击具有分类的标签或模型生成线路:</div>
|
||||
<div class="selected">{{ pathIdList.join(",") }}</div>
|
||||
<button @click="() => pathIdList.pop()">删除末尾</button>
|
||||
<button @click="() => pathIdList = []">清空</button>
|
||||
<button @click="() => startNavi()" :disabled="pathIdList.length < 2">开始导航</button>
|
||||
<button @click="() => pauseNavi()" :disabled="pathIdList.length < 2">暂停</button>
|
||||
<button @click="() => restoreNavi()" :disabled="pathIdList.length < 2">继续</button>
|
||||
<button @click="() => stopNavi()" :disabled="pathIdList.length < 2">结束导航</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.path-list-status {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.selected {
|
||||
min-height: 1.5em;
|
||||
width: 300px;
|
||||
padding: 0 5px;
|
||||
background-color: #fff;
|
||||
border: 1px solid #767676;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
overflow: auto;
|
||||
}
|
||||
</style>
|
125
src/components/EditorFilter.vue
Normal file
125
src/components/EditorFilter.vue
Normal file
@@ -0,0 +1,125 @@
|
||||
<script setup>
|
||||
import { inject, ref, watch, onBeforeUnmount } from "vue"
|
||||
import { useClassPolygon } from "@/hooks/useClassPolygon"
|
||||
import { useDeviceStore } from "@/stores/device" // 导入设备store
|
||||
|
||||
const { THREE } = VgoMap
|
||||
|
||||
// 获取设备store实例
|
||||
const deviceStore = useDeviceStore()
|
||||
|
||||
// 可以使用pinia等管理全局数据,这里只是方便演示, 直接注入了上层提供的数据
|
||||
const editMap = inject("editMap")
|
||||
const editPolygonDataAll = inject("editPolygonDataAll")
|
||||
const editRouteLineRef = inject("editRouteLineRef")
|
||||
console.log(editMap.value,'editorFilter.editMap.value')
|
||||
// 仅取有分类的点位
|
||||
const { classPoiList, classDict } = useClassPolygon(editPolygonDataAll)
|
||||
console.log(classPoiList.value,'classPoiList.value')
|
||||
console.log(classDict.value,'classDict.value')
|
||||
const markerGroup = new THREE.Group()
|
||||
editMap.value.scene.add(markerGroup)
|
||||
onBeforeUnmount(() => {
|
||||
markerGroup.dispose()
|
||||
})
|
||||
|
||||
// 根据有分类点位,创建marker
|
||||
|
||||
console.log(editMap.value,'editorFilter.editMap.value')
|
||||
console.log(editMap.value.status.floor,'editMap.value.status.floor')
|
||||
console.log(editMap.value.outerFloor,'editMap.value?.outerFloor')
|
||||
const markerStatus = ref({});
|
||||
(editMap.value.status?.floor ?? editMap.value?.outerFloor).on('floorModelAllLoaded', () => {
|
||||
console.log('lkasdfjalksdjfklasjdf')
|
||||
// 清理所有marker
|
||||
while (markerGroup.children.length) {
|
||||
markerGroup.children?.[0]?.dispose()
|
||||
}
|
||||
markerStatus.value = {}
|
||||
|
||||
// 创建新的marker
|
||||
const listGroup = Object.entries(classPoiList.value)
|
||||
listGroup.forEach(([key, list]) => {
|
||||
list.forEach((item) => {
|
||||
const model = editMap.value.getModelById(item.extrasModelId) // 获取点位关联的模型信息
|
||||
const modelName = model.userData.modelData.name
|
||||
const modelId=item.id
|
||||
console.log(modelName, ' ', item.id)
|
||||
|
||||
// 将设备信息添加到全局store
|
||||
//deviceStore.addDevice(modelId, modelName)
|
||||
|
||||
const marker = createDivMarker(modelName, item)
|
||||
|
||||
if (model) {
|
||||
marker.position.copy(model.position)
|
||||
} else {
|
||||
marker.position.copy(item.center).setZ(10)
|
||||
}
|
||||
markerGroup.add(marker)
|
||||
|
||||
if (!markerStatus.value?.[key]) {
|
||||
markerStatus.value[key] = []
|
||||
}
|
||||
markerStatus.value[key].push(marker)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
// 根据选中状态,控制marker显示
|
||||
const checkedStatus = ref({})
|
||||
watch(checkedStatus, (checked) => {
|
||||
for (let key in checked) {
|
||||
markerStatus.value?.[key]?.forEach((marker) => {
|
||||
marker.visible = checked[key]
|
||||
})
|
||||
}
|
||||
}, {
|
||||
deep: true,
|
||||
})
|
||||
|
||||
function createDivMarker(content, polygon) {
|
||||
const wrapper = document.createElement("div")
|
||||
const contentNode = document.createElement("div")
|
||||
contentNode.className = "marker"
|
||||
contentNode.textContent = content
|
||||
contentNode.onclick = (e) => {
|
||||
if(!polygon?.isNavi) return
|
||||
editRouteLineRef.value.addPath(polygon)
|
||||
}
|
||||
|
||||
wrapper.appendChild(contentNode)
|
||||
const floorId = editMap.status?.floor?.data?.id || '1'
|
||||
return editMap.value.addDomMarker(floorId, wrapper) // 之前记错了,这边直接传dom元素也是可以的
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ul class="filter">
|
||||
<li v-for="key in Object.keys(classPoiList)" :key="key">
|
||||
<label :for="key">{{ classDict[key].name }}</label>
|
||||
<input type="checkbox" :id="key" v-model="checkedStatus[key]">
|
||||
</li>
|
||||
</ul>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.filter {
|
||||
margin: 0;
|
||||
|
||||
li,
|
||||
label,
|
||||
input {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<style>
|
||||
.marker {
|
||||
background-color: #fff;
|
||||
padding: 2px 10px;
|
||||
border-radius: 20px;
|
||||
transform: scale(0.7);
|
||||
}
|
||||
</style>
|
@@ -1,6 +1,6 @@
|
||||
<script setup>
|
||||
import { inject, onBeforeUnmount } from "vue"
|
||||
import { useFence } from "../hooks/useFence"
|
||||
import { useFence } from "@/hooks/useFence"
|
||||
|
||||
const { THREE } = VgoMap
|
||||
|
||||
|
@@ -1,22 +1,20 @@
|
||||
<script setup>
|
||||
import { inject, computed, ref, watch, onBeforeUnmount } from "vue"
|
||||
import { inject, ref, watch, onBeforeUnmount } from "vue"
|
||||
import { useClassPolygon } from "@/hooks/useClassPolygon"
|
||||
import { useDeviceStore } from "@/stores/device" // 导入设备store
|
||||
|
||||
const { THREE } = VgoMap
|
||||
|
||||
// 获取设备store实例
|
||||
const deviceStore = useDeviceStore()
|
||||
|
||||
// 可以使用pinia等管理全局数据,这里只是方便演示, 直接注入了上层提供的数据
|
||||
const map = inject("map")
|
||||
const polygonDataAll = inject("polygonDataAll")
|
||||
|
||||
// 筛选出有分类的点位
|
||||
const shortcutList = computed(() => {
|
||||
return polygonDataAll.value.filter(i => i?.shortcutsId).reduce((result, item) => {
|
||||
if(!result?.[item.shortcutsId]) {
|
||||
result[item.shortcutsId] = []
|
||||
}
|
||||
result[item.shortcutsId].push(item)
|
||||
return result
|
||||
}, {})
|
||||
})
|
||||
const routeLineRef = inject("routeLineRef")
|
||||
console.log(map.value,'Filter.map.value')
|
||||
// 仅取有分类的点位
|
||||
const { classPoiList, classDict } = useClassPolygon(polygonDataAll)
|
||||
|
||||
const markerGroup = new THREE.Group()
|
||||
map.value.scene.add(markerGroup)
|
||||
@@ -24,38 +22,53 @@ onBeforeUnmount(() => {
|
||||
markerGroup.dispose()
|
||||
})
|
||||
|
||||
console.log(map.value.status.floor,'editMap.value.status.floor')
|
||||
console.log(map.value.outerFloor,'editMap.value?.outerFloor')
|
||||
// 根据有分类点位,创建marker
|
||||
const markerStatus = ref({})
|
||||
watch(shortcutList, (value) => {
|
||||
const markerStatus = ref({});
|
||||
|
||||
(map.value.status?.floor ?? map.value?.outerFloor).on('floorModelAllLoaded', () => {
|
||||
console.log('lkasdfjalksdjfklasjdf')
|
||||
// 清理所有marker
|
||||
while(markerGroup.children.length) {
|
||||
while (markerGroup.children.length) {
|
||||
markerGroup.children?.[0]?.dispose()
|
||||
}
|
||||
markerStatus.value = {}
|
||||
|
||||
// 创建新的marker
|
||||
const listGroup = Object.entries(value)
|
||||
listGroup.forEach(([key, list]) => {
|
||||
list.forEach((item) => {
|
||||
const marker = createDivMarker('设备类型' + item.shortcutsId)
|
||||
marker.position.copy(item.center).setZ(10)
|
||||
markerGroup.add(marker)
|
||||
|
||||
if(!markerStatus.value?.[key]) {
|
||||
markerStatus.value[key] = []
|
||||
}
|
||||
markerStatus.value[key].push(marker)
|
||||
})
|
||||
})
|
||||
}, {
|
||||
immediate: true,
|
||||
const listGroup = Object.entries(classPoiList.value)
|
||||
listGroup.forEach(([key, list]) => {
|
||||
list.forEach((item) => {
|
||||
const model = map.value.getModelById(item.extrasModelId) // 获取点位关联的模型信息
|
||||
const modelName = model.userData.modelData.name
|
||||
const modelId=item.id
|
||||
console.log(modelName, ' ', item.id)
|
||||
|
||||
// 将设备信息添加到全局store
|
||||
deviceStore.addDevice(modelId, modelName)
|
||||
|
||||
const marker = createDivMarker(modelName, item)
|
||||
|
||||
if (model) {
|
||||
marker.position.copy(model.position)
|
||||
} else {
|
||||
marker.position.copy(item.center).setZ(10)
|
||||
}
|
||||
markerGroup.add(marker)
|
||||
|
||||
if (!markerStatus.value?.[key]) {
|
||||
markerStatus.value[key] = []
|
||||
}
|
||||
markerStatus.value[key].push(marker)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
// 根据选中状态,控制marker显示
|
||||
const checkedStatus = ref({})
|
||||
watch(checkedStatus, (checked) => {
|
||||
for(let key in checked) {
|
||||
markerStatus.value[key].forEach((marker) => {
|
||||
for (let key in checked) {
|
||||
markerStatus.value?.[key]?.forEach((marker) => {
|
||||
marker.visible = checked[key]
|
||||
})
|
||||
}
|
||||
@@ -63,11 +76,15 @@ watch(checkedStatus, (checked) => {
|
||||
deep: true,
|
||||
})
|
||||
|
||||
function createDivMarker(content) {
|
||||
function createDivMarker(content, polygon) {
|
||||
const wrapper = document.createElement("div")
|
||||
const contentNode = document.createElement("div")
|
||||
contentNode.className = "marker"
|
||||
contentNode.textContent = content
|
||||
contentNode.onclick = (e) => {
|
||||
if(!polygon?.isNavi) return
|
||||
routeLineRef.value.addPath(polygon)
|
||||
}
|
||||
|
||||
wrapper.appendChild(contentNode)
|
||||
const floorId = map.status?.floor?.data?.id || '1'
|
||||
@@ -77,8 +94,8 @@ function createDivMarker(content) {
|
||||
|
||||
<template>
|
||||
<ul class="filter">
|
||||
<li v-for="key in Object.keys(shortcutList)" :key="key">
|
||||
<label :for="key">设备类型{{ key }}</label>
|
||||
<li v-for="key in Object.keys(classPoiList)" :key="key">
|
||||
<label :for="key">{{ classDict[key].name }}</label>
|
||||
<input type="checkbox" :id="key" v-model="checkedStatus[key]">
|
||||
</li>
|
||||
</ul>
|
||||
@@ -86,16 +103,14 @@ function createDivMarker(content) {
|
||||
|
||||
<style scoped>
|
||||
.filter {
|
||||
position: absolute;
|
||||
right: 50px;
|
||||
bottom: 20px;
|
||||
z-index: 10;
|
||||
margin: 0;
|
||||
|
||||
li, label, input {
|
||||
li,
|
||||
label,
|
||||
input {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
<style>
|
||||
@@ -105,4 +120,4 @@ function createDivMarker(content) {
|
||||
border-radius: 20px;
|
||||
transform: scale(0.7);
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
41
src/components/Sky.vue
Normal file
41
src/components/Sky.vue
Normal file
@@ -0,0 +1,41 @@
|
||||
<script setup>
|
||||
import { inject, ref, watch, onBeforeUnmount } from "vue"
|
||||
import { useSkyLight } from "@/hooks/useSkyLight"
|
||||
|
||||
const map = inject("map")
|
||||
const { skyGroup, init, setTime } = useSkyLight(map.value)
|
||||
const defaultTime = ref(420)
|
||||
|
||||
const timeOptions = Array.from({length: 24}).map((_, i) => ({
|
||||
label: `${i}时`,
|
||||
value: i * 60,
|
||||
}))
|
||||
|
||||
map.value.amap.maxPitch = 180
|
||||
window.$setTime = setTime
|
||||
|
||||
init()
|
||||
setTime(defaultTime.value)
|
||||
|
||||
watch(defaultTime, (value) => {
|
||||
setTime(value)
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
skyGroup.value?.dispose()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="sky-select">
|
||||
<select name="time" id="time" v-model="defaultTime">
|
||||
<option v-for="time in timeOptions" :key="time.value" :value="time.value">{{ time.label }}</option>
|
||||
</select>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
select {
|
||||
min-width: 100px;
|
||||
}
|
||||
</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'
|
||||
}
|
||||
}
|
57
src/hooks/useClassPolygon.js
Normal file
57
src/hooks/useClassPolygon.js
Normal file
@@ -0,0 +1,57 @@
|
||||
import { computed } from "vue"
|
||||
|
||||
export function useClassPolygon(allPolygon) {
|
||||
const classPoiList = computed(() => {
|
||||
return allPolygon.value.filter(i => i?.class?.length).reduce((result, item) => {
|
||||
item.class.forEach((classId) => {
|
||||
if (!result?.[classId]) {
|
||||
result[classId] = []
|
||||
}
|
||||
result[classId].push(item)
|
||||
})
|
||||
return result
|
||||
}, {})
|
||||
})
|
||||
const classDict = {
|
||||
"327": {
|
||||
"parentId": 0,
|
||||
"name": "摄像头",
|
||||
"sortNo": 0,
|
||||
"customerUserId": 0,
|
||||
"id": 327
|
||||
},
|
||||
"328": {
|
||||
"parentId": 0,
|
||||
"name": "烟感器",
|
||||
"sortNo": 0,
|
||||
"customerUserId": 0,
|
||||
"id": 328
|
||||
},
|
||||
"329": {
|
||||
"parentId": 0,
|
||||
"name": "温湿度感应器",
|
||||
"sortNo": 0,
|
||||
"customerUserId": 0,
|
||||
"id": 329
|
||||
},
|
||||
"330": {
|
||||
"parentId": 0,
|
||||
"name": "立式空调",
|
||||
"sortNo": 0,
|
||||
"customerUserId": 0,
|
||||
"id": 330
|
||||
},
|
||||
"331": {
|
||||
"parentId": 0,
|
||||
"name": "除湿机",
|
||||
"sortNo": 0,
|
||||
"customerUserId": 0,
|
||||
"id": 331
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
classPoiList,
|
||||
classDict,
|
||||
}
|
||||
}
|
66
src/hooks/useDisplayColor.js
Normal file
66
src/hooks/useDisplayColor.js
Normal file
@@ -0,0 +1,66 @@
|
||||
import { computed } from "vue"
|
||||
|
||||
export function useDisplayColor(map) {
|
||||
const replaceColorMap = computed(() => {
|
||||
return Object.fromEntries(map.value.mapData?.options?.tilecolorReplace?.map(r => [r.sourceColor?.toLocaleLowerCase(), r.targetColor]) ?? [])
|
||||
})
|
||||
const floor = map.value.getFloorById(getCurrentFloorId())
|
||||
const baseAddPolygon = floor.addPolygon.bind(floor)
|
||||
const baseAddFaceLine = floor.addFaceLine.bind(floor)
|
||||
const baseColorCache = {}
|
||||
|
||||
function overwriteAdd(isChange) {
|
||||
function addPolygon(data) {
|
||||
if(isChange) {
|
||||
return baseAddPolygon(data)
|
||||
}
|
||||
|
||||
const replace = {
|
||||
...data,
|
||||
color: replaceColorMap.value[data.color?.toLocaleLowerCase()] ?? data.color,
|
||||
}
|
||||
return baseAddPolygon(replace)
|
||||
}
|
||||
|
||||
function addFaceLine() {
|
||||
this.data.polygonData.filter(data => data.polygonType === 'faceline' && !data.parentArea)
|
||||
.forEach(data => {
|
||||
const replaceColor = replaceColorMap.value[data.color?.toLocaleLowerCase()]
|
||||
if(replaceColor && !isChange) {
|
||||
baseColorCache[data.id] = data.color
|
||||
data.color = replaceColor
|
||||
} else {
|
||||
data.color = baseColorCache?.[data.id] ?? data.color
|
||||
}
|
||||
})
|
||||
baseAddFaceLine()
|
||||
}
|
||||
|
||||
return [
|
||||
addPolygon,
|
||||
addFaceLine,
|
||||
].map(i => i.bind(floor))
|
||||
}
|
||||
|
||||
function changeDisplayColor(isChange) {
|
||||
([floor.addPolygon, floor.addFaceLine] = overwriteAdd(isChange))
|
||||
|
||||
floor.rectInstanceMesh?.clearInstances()
|
||||
floor.circleInstanceMesh?.clearInstances()
|
||||
floor.polygonGroup?.children?.forEach((polygon) =>{
|
||||
polygon.dispose()
|
||||
})
|
||||
|
||||
floor.initPolgon()
|
||||
map.value?.mapTile?.switchBaseColor?.(isChange)
|
||||
map.value?.mapTile?.init()
|
||||
}
|
||||
|
||||
function getCurrentFloorId() {
|
||||
return map.value.status?.floor?.data?.id ?? '1'
|
||||
}
|
||||
|
||||
return {
|
||||
changeDisplayColor,
|
||||
}
|
||||
}
|
96
src/hooks/useEditNavi.js
Normal file
96
src/hooks/useEditNavi.js
Normal file
@@ -0,0 +1,96 @@
|
||||
import { ref, watch } from "vue"
|
||||
import { usePathStore } from "@/stores/pathStore"
|
||||
|
||||
export function useNavi(map) {
|
||||
const pathIdList = ref([])
|
||||
|
||||
function addPath(polygon) {
|
||||
if(polygon?.id && pathIdList.value[pathIdList.value.length - 1] === polygon?.id) return // 不连续添加相同的项
|
||||
|
||||
const modelId = polygon.extrasModelId;
|
||||
const model = map.value.getModelById(modelId)
|
||||
const modelName = model.userData.modelData.name
|
||||
console.log(modelName, 'model.name', polygon.id,'polygon.id')
|
||||
|
||||
addPathId(polygon.id)
|
||||
}
|
||||
|
||||
function addPathId(id) {
|
||||
if(id && pathIdList.value[pathIdList.value.length - 1] === id) return
|
||||
|
||||
pathIdList.value.push(id)
|
||||
}
|
||||
|
||||
function startNavi() {
|
||||
if(pathIdList.value.length < 2) return
|
||||
map.value.navi.simulate()
|
||||
map.value.navi.setSimulateSpeed(10)
|
||||
}
|
||||
|
||||
function pauseNavi () {
|
||||
// 判断当前是否处于导航中
|
||||
if (map.value.navi.status.isSimulate) {
|
||||
map.value.navi.pauseSimulate()
|
||||
}
|
||||
}
|
||||
|
||||
function restoreNavi () {
|
||||
// 判断当前是否处于暂停中
|
||||
if (!map.value.navi.isSimulatePause) {
|
||||
map.value.navi.resumeSimulate()
|
||||
}
|
||||
}
|
||||
|
||||
function stopNavi () {
|
||||
// 退出模拟导航
|
||||
map.value.navi.stopSimulate()
|
||||
removePathLine()
|
||||
pathIdList.value = []
|
||||
}
|
||||
|
||||
function removePathLine() {
|
||||
// 销毁箭头
|
||||
map.value.navi.removeNaviArrow()
|
||||
// 销毁线路
|
||||
map.value.navi.removeNaviLine()
|
||||
//移除途径点
|
||||
map.value.navi.removeAllWaypoint()
|
||||
//移除起点
|
||||
map.value.navi.removeStart()
|
||||
//移除终点
|
||||
map.value.navi.removeEnd()
|
||||
}
|
||||
|
||||
watch(pathIdList, (pathIdList) => {
|
||||
// 同步路径点到全局store
|
||||
const pathStore = usePathStore()
|
||||
pathStore.syncPathPoints(pathIdList)
|
||||
|
||||
if(pathIdList?.length < 2) {
|
||||
removePathLine()
|
||||
return
|
||||
}
|
||||
const end = pathIdList[pathIdList.length - 1]
|
||||
const [start, ...passPoints]= pathIdList.slice(0, -1);
|
||||
|
||||
map.value.navi.removeAllWaypoint()
|
||||
|
||||
map.value.navi.setStart(start)
|
||||
map.value.navi.setWaypoints(passPoints)
|
||||
map.value.navi.setEnd(end)
|
||||
|
||||
map.value.navi.find()
|
||||
}, {
|
||||
deep: true,
|
||||
})
|
||||
|
||||
return {
|
||||
pathIdList,
|
||||
addPath,
|
||||
addPathId,
|
||||
startNavi,
|
||||
pauseNavi,
|
||||
restoreNavi,
|
||||
stopNavi,
|
||||
}
|
||||
}
|
91
src/hooks/useNavi.js
Normal file
91
src/hooks/useNavi.js
Normal file
@@ -0,0 +1,91 @@
|
||||
import { ref, watch } from "vue"
|
||||
|
||||
export function useNavi(map) {
|
||||
const pathIdList = ref([])
|
||||
|
||||
function addPath(polygon) {
|
||||
if(polygon?.id && pathIdList.value[pathIdList.value.length - 1] === polygon?.id) return // 不连续添加相同的项
|
||||
|
||||
const modelId = polygon.extrasModelId;
|
||||
const model = map.value.getModelById(modelId)
|
||||
const modelName = model.userData.modelData.name
|
||||
console.log(modelName, 'model.name', polygon.id,'polygon.id')
|
||||
|
||||
addPathId(polygon.id)
|
||||
}
|
||||
|
||||
function addPathId(id) {
|
||||
if(id && pathIdList.value[pathIdList.value.length - 1] === id) return
|
||||
|
||||
pathIdList.value.push(id)
|
||||
}
|
||||
|
||||
function startNavi() {
|
||||
if(pathIdList.value.length < 2) return
|
||||
map.value.navi.simulate()
|
||||
map.value.navi.setSimulateSpeed(10)
|
||||
}
|
||||
|
||||
function pauseNavi () {
|
||||
// 判断当前是否处于导航中
|
||||
if (map.value.navi.status.isSimulate) {
|
||||
map.value.navi.pauseSimulate()
|
||||
}
|
||||
}
|
||||
|
||||
function restoreNavi () {
|
||||
// 判断当前是否处于暂停中
|
||||
if (!map.value.navi.isSimulatePause) {
|
||||
map.value.navi.resumeSimulate()
|
||||
}
|
||||
}
|
||||
|
||||
function stopNavi () {
|
||||
// 退出模拟导航
|
||||
map.value.navi.stopSimulate()
|
||||
removePathLine()
|
||||
pathIdList.value = []
|
||||
}
|
||||
|
||||
function removePathLine() {
|
||||
// 销毁箭头
|
||||
map.value.navi.removeNaviArrow()
|
||||
// 销毁线路
|
||||
map.value.navi.removeNaviLine()
|
||||
//移除途径点
|
||||
map.value.navi.removeAllWaypoint()
|
||||
//移除起点
|
||||
map.value.navi.removeStart()
|
||||
//移除终点
|
||||
map.value.navi.removeEnd()
|
||||
}
|
||||
|
||||
watch(pathIdList, (pathIdList) => {
|
||||
if(pathIdList?.length < 2) {
|
||||
removePathLine()
|
||||
return
|
||||
}
|
||||
const end = pathIdList[pathIdList.length - 1]
|
||||
const [start, ...passPoints]= pathIdList.slice(0, -1);
|
||||
|
||||
map.value.navi.removeAllWaypoint()
|
||||
|
||||
map.value.navi.setStart(start)
|
||||
map.value.navi.setWaypoints(passPoints)
|
||||
map.value.navi.setEnd(end)
|
||||
|
||||
map.value.navi.find()
|
||||
}, {
|
||||
deep: true,
|
||||
})
|
||||
|
||||
return {
|
||||
pathIdList,
|
||||
addPath,
|
||||
addPathId,
|
||||
startNavi,
|
||||
pauseNavi,
|
||||
restoreNavi,
|
||||
stopNavi,
|
||||
}
|
||||
}
|
453
src/hooks/useSkyLight.js
Normal file
453
src/hooks/useSkyLight.js
Normal file
@@ -0,0 +1,453 @@
|
||||
import sunImg from "../assets/textures/corona.png"
|
||||
import starsImg from "../assets/textures/stars.png"
|
||||
import moonImg from "../assets/textures/moon.png"
|
||||
|
||||
const {
|
||||
TextureLoader,
|
||||
SphereGeometry,
|
||||
ShaderMaterial,
|
||||
MeshBasicMaterial,
|
||||
Color,
|
||||
RepeatWrapping,
|
||||
BackSide,
|
||||
Mesh,
|
||||
DirectionalLight,
|
||||
AmbientLight,
|
||||
AdditiveBlending,
|
||||
Group,
|
||||
} = window.VgoMap.THREE
|
||||
|
||||
export function useSkyLight(map) {
|
||||
let skyDome = null
|
||||
let sun = null
|
||||
let moon = null
|
||||
let stars = null
|
||||
let sunLight = null
|
||||
let moonLight = null
|
||||
let ambientLight = null
|
||||
const skyGroup = new Group()
|
||||
const loader = new TextureLoader()
|
||||
document.querySelector("#sky-mask").remove()
|
||||
|
||||
|
||||
// 加载贴图
|
||||
const sunTexture = loader.load(sunImg)
|
||||
const starsTexture = loader.load(starsImg)
|
||||
const moonTexture = loader.load(moonImg)
|
||||
|
||||
const skyColorKeyframes = [
|
||||
{
|
||||
time: 0,
|
||||
top: new Color(0x090b12),
|
||||
bottom: new Color(0x0f131d)
|
||||
},
|
||||
{
|
||||
time: 240,
|
||||
top: new Color(0x090b12),
|
||||
bottom: new Color(0x0f131d)
|
||||
},
|
||||
{
|
||||
time: 360,
|
||||
top: new Color(0x87ceeb),
|
||||
bottom: new Color(0xd8f2ff)
|
||||
},
|
||||
{
|
||||
time: 720,
|
||||
top: new Color(0x87ceeb),
|
||||
bottom: new Color(0xd8f2ff)
|
||||
},
|
||||
{
|
||||
time: 1020,
|
||||
top: new Color(0x87ceeb),
|
||||
bottom: new Color(0xd8f2ff)
|
||||
},
|
||||
{
|
||||
time: 1080,
|
||||
top: new Color(0x090b12),
|
||||
bottom: new Color(0x0f131d)
|
||||
},
|
||||
{
|
||||
time: 1440,
|
||||
top: new Color(0x090b12),
|
||||
bottom: new Color(0x0f131d)
|
||||
}
|
||||
]
|
||||
|
||||
// 初始化天空
|
||||
const init = () => {
|
||||
createSkyDome()
|
||||
createSun()
|
||||
createMoon()
|
||||
createStars()
|
||||
createLights()
|
||||
setupScene()
|
||||
}
|
||||
|
||||
// 创建天空穹顶
|
||||
const createSkyDome = () => {
|
||||
const geometry = new SphereGeometry(8000, 64, 64)
|
||||
const material = new ShaderMaterial({
|
||||
uniforms: {
|
||||
topColor: { value: new Color(0x87ceeb) },
|
||||
bottomColor: { value: new Color(0xd8f2ff) },
|
||||
sunsetColor: { value: new Color(0xff6b35) },
|
||||
twilightColor: { value: new Color(0xffa07a) },
|
||||
offset: { value: 0 },
|
||||
exponent: { value: 0.6 },
|
||||
sunPosition: { value: { x: 0, y: 0, z: -7900 } },
|
||||
isSunset: { value: 0.0 }
|
||||
},
|
||||
vertexShader: `
|
||||
varying vec3 vWorldPosition;
|
||||
void main() {
|
||||
vec4 worldPosition = modelMatrix * vec4(position, 1.0);
|
||||
vWorldPosition = worldPosition.xyz;
|
||||
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
|
||||
}
|
||||
`,
|
||||
fragmentShader: `
|
||||
uniform vec3 topColor;
|
||||
uniform vec3 bottomColor;
|
||||
uniform vec3 sunsetColor;
|
||||
uniform vec3 twilightColor;
|
||||
uniform float offset;
|
||||
uniform float exponent;
|
||||
uniform vec3 sunPosition;
|
||||
uniform float isSunset;
|
||||
varying vec3 vWorldPosition;
|
||||
|
||||
// 简单的云层噪声函数
|
||||
float hash(vec2 p) {
|
||||
return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453);
|
||||
}
|
||||
|
||||
float noise(vec2 p) {
|
||||
vec2 i = floor(p);
|
||||
vec2 f = fract(p);
|
||||
f = f * f * (3.0 - 2.0 * f);
|
||||
float a = hash(i);
|
||||
float b = hash(i + vec2(1.0, 0.0));
|
||||
float c = hash(i + vec2(0.0, 1.0));
|
||||
float d = hash(i + vec2(1.0, 1.0));
|
||||
return mix(mix(a, b, f.x), mix(c, d, f.x), f.y);
|
||||
}
|
||||
|
||||
void main() {
|
||||
vec3 normalized = normalize(vWorldPosition);
|
||||
float h = normalized.z;
|
||||
|
||||
// 基础天空颜色渐变
|
||||
vec3 skyColor = mix(bottomColor, topColor, max(pow(max(h, 0.0), exponent), 0.0));
|
||||
|
||||
// 黄昏效果:仅在太阳临近地平线时显示
|
||||
if (isSunset > 0.0) {
|
||||
// 计算与太阳方向的角度
|
||||
vec3 sunDir = normalize(sunPosition);
|
||||
float sunDot = dot(normalized, sunDir);
|
||||
|
||||
// 检查太阳是否临近地平线(z值接近0)
|
||||
float sunHorizonDist = abs(sunDir.z);
|
||||
float sunNearHorizon = smoothstep(0.25, 0.0, sunHorizonDist); // 进一步收紧太阳接近地平线的范围
|
||||
|
||||
// 地平线因子:极度接近地平线时值更大(进一步缩小范围)
|
||||
float horizonFactor = 1.0 - abs(h);
|
||||
horizonFactor = pow(horizonFactor, 6.0); // 提高幂次至6,大幅缩小范围
|
||||
|
||||
// 太阳方向因子:极度接近太阳方向才显示(进一步收紧范围)
|
||||
float sunFactor = smoothstep(0.5, 0.9, sunDot); // 从0.3-0.85收紧到0.5-0.9
|
||||
|
||||
// 云层仅在太阳附近显示的因子
|
||||
float cloudSunProximity = smoothstep(0.4, 0.8, sunDot); // 云层只在接近太阳方向显示
|
||||
|
||||
// 添加多层云层效果,增加密度
|
||||
vec2 cloudUV = vec2(atan(normalized.x, normalized.y) * 3.0, asin(normalized.z) * 6.0);
|
||||
|
||||
// 第一层云:较大的云团
|
||||
float cloudNoise1 = noise(cloudUV * 3.5);
|
||||
cloudNoise1 = pow(cloudNoise1, 1.8);
|
||||
|
||||
// 第二层云:中等云团
|
||||
float cloudNoise2 = noise(cloudUV * 6.0);
|
||||
cloudNoise2 = pow(cloudNoise2, 2.0);
|
||||
|
||||
// 第三层云:细小云丝
|
||||
float cloudNoise3 = noise(cloudUV * 10.0);
|
||||
cloudNoise3 = pow(cloudNoise3, 2.5);
|
||||
|
||||
// 组合多层云,增加密度
|
||||
float cloudNoise = cloudNoise1 * 0.5 + cloudNoise2 * 0.3 + cloudNoise3 * 0.2;
|
||||
|
||||
// 云层遮罩:仅在地平线和太阳附近显示
|
||||
float cloudLayer = smoothstep(0.35, 0.65, cloudNoise) * horizonFactor * cloudSunProximity * 0.8;
|
||||
|
||||
// 综合因子(添加太阳位置检测)
|
||||
float sunsetFactor = horizonFactor * sunFactor * sunNearHorizon * isSunset;
|
||||
|
||||
// 黄昏颜色渐变(柔和的橙色调)
|
||||
vec3 deepOrange = vec3(0.9, 0.5, 0.3);
|
||||
vec3 brightOrange = vec3(1.0, 0.65, 0.4);
|
||||
vec3 duskColor = mix(deepOrange, brightOrange, sunFactor * 0.6);
|
||||
|
||||
// 云层受黄昏颜色影响(降低对比度)
|
||||
vec3 cloudColor = mix(duskColor * 0.85, duskColor * 1.15, cloudNoise);
|
||||
|
||||
// 混合黄昏颜色和云层(降低混合强度)
|
||||
skyColor = mix(skyColor, duskColor, sunsetFactor * 0.35);
|
||||
skyColor = mix(skyColor, cloudColor, cloudLayer * sunsetFactor * isSunset * 0.6);
|
||||
}
|
||||
|
||||
gl_FragColor = vec4(skyColor, 1.0);
|
||||
}
|
||||
`,
|
||||
side: BackSide
|
||||
})
|
||||
|
||||
skyDome = new Mesh(geometry, material)
|
||||
skyDome.name = 'sky'
|
||||
}
|
||||
|
||||
// 创建太阳
|
||||
const createSun = () => {
|
||||
// 创建太阳核心
|
||||
const coreGeometry = new SphereGeometry(180, 32, 32)
|
||||
const coreMaterial = new MeshBasicMaterial({
|
||||
color: new Color(0xFFA500), // 调整为稍冷的橙色
|
||||
transparent: true,
|
||||
blending: AdditiveBlending
|
||||
})
|
||||
|
||||
const core = new Mesh(coreGeometry, coreMaterial)
|
||||
|
||||
// 创建日冕效果
|
||||
const coronaGeometry = new SphereGeometry(3200, 32, 32)
|
||||
const coronaMaterial = new MeshBasicMaterial({
|
||||
map: sunTexture,
|
||||
transparent: true,
|
||||
blending: AdditiveBlending,
|
||||
opacity: 0.05,
|
||||
color: new Color(0xFF8C00) // 调整为稍冷的橙色日冕
|
||||
})
|
||||
|
||||
const corona = new Mesh(coronaGeometry, coronaMaterial)
|
||||
|
||||
// 组合太阳
|
||||
sun = new Group()
|
||||
sun.add(core)
|
||||
sun.add(corona)
|
||||
sun.name = 'sun'
|
||||
sun.position.set(0, 0, -7900) // 初始位置调整为垂直地平线方向
|
||||
}
|
||||
|
||||
// 创建月亮
|
||||
const createMoon = () => {
|
||||
const geometry = new SphereGeometry(200, 32, 32) // 增大月亮尺寸
|
||||
const material = new MeshBasicMaterial({
|
||||
map: moonTexture,
|
||||
transparent: true,
|
||||
blending: AdditiveBlending
|
||||
})
|
||||
|
||||
moon = new Mesh(geometry, material)
|
||||
moon.name = 'moon'
|
||||
moon.position.set(0, 0, 7900) // 初始位置调整为垂直地平线方向
|
||||
}
|
||||
|
||||
// 创建星空
|
||||
const createStars = () => {
|
||||
starsTexture.wrapS = RepeatWrapping
|
||||
starsTexture.wrapT = RepeatWrapping
|
||||
starsTexture.repeat.set(10, 10)
|
||||
|
||||
const geometry = new SphereGeometry(10000, 32, 32)
|
||||
const material = new MeshBasicMaterial({
|
||||
map: starsTexture,
|
||||
transparent: true,
|
||||
blending: AdditiveBlending,
|
||||
side: BackSide,
|
||||
opacity: 0.8
|
||||
})
|
||||
|
||||
stars = new Mesh(geometry, material)
|
||||
stars.name = 'stars'
|
||||
}
|
||||
|
||||
// 创建光源
|
||||
const createLights = () => {
|
||||
// 太阳光
|
||||
sunLight = new DirectionalLight(0xffffff, 1)
|
||||
sunLight.position.set(0, 0, -1000)
|
||||
sunLight.castShadow = true
|
||||
|
||||
// 月光
|
||||
moonLight = new DirectionalLight(0x333366, 0.5)
|
||||
moonLight.position.set(0, 0, 1000)
|
||||
|
||||
// 环境光
|
||||
ambientLight = new AmbientLight(0x404040, 0.1)
|
||||
}
|
||||
|
||||
// 设置场景
|
||||
const setupScene = () => {
|
||||
skyGroup.add(skyDome)
|
||||
skyGroup.add(sun)
|
||||
skyGroup.add(moon)
|
||||
skyGroup.add(stars)
|
||||
skyGroup.add(sunLight)
|
||||
skyGroup.add(moonLight)
|
||||
skyGroup.add(ambientLight)
|
||||
map.scene.add(skyGroup)
|
||||
}
|
||||
|
||||
// 设置时间(分钟数,0-1440代表一天)
|
||||
const setTime = (minutes) => {
|
||||
if (minutes < 0 || minutes > 1440) return
|
||||
|
||||
// 计算太阳和月亮的位置(垂直方向圆周运动,自东向西)
|
||||
const angle = -(minutes / 1440) * Math.PI * 2 // 负号反转方向,使其自东向西
|
||||
const radius = -7900
|
||||
const sunY = Math.sin(angle) * radius
|
||||
const sunZ = Math.cos(angle) * radius
|
||||
const sunX = 0 // X轴位置保持为0,只在YZ平面旋转
|
||||
|
||||
// 更新太阳位置
|
||||
sun.position.set(sunX, sunY, sunZ)
|
||||
sunLight.position.set(sunX, sunY, sunZ)
|
||||
if(minutes > 1080 || minutes < 360) {
|
||||
sun.visible = false
|
||||
} else {
|
||||
sun.visible = true
|
||||
}
|
||||
|
||||
// 更新月亮位置(与太阳相对)
|
||||
moon.position.set(sunX, -sunY, -sunZ)
|
||||
moonLight.position.set(sunX, -sunY, -sunZ)
|
||||
|
||||
if(minutes > 360 && minutes < 1080 ) {
|
||||
moon.visible = false
|
||||
} else {
|
||||
moon.visible = true
|
||||
}
|
||||
|
||||
// 根据时间调整光照强度和颜色
|
||||
updateLighting(minutes)
|
||||
|
||||
// 根据时间调整天空颜色
|
||||
updateSkyColor(minutes)
|
||||
|
||||
// 根据时间调整星空可见度
|
||||
updateStarsVisibility(minutes)
|
||||
}
|
||||
|
||||
// 更新光照
|
||||
const updateLighting = (minutes) => {
|
||||
// 日出日落时段
|
||||
if (minutes > 360 && minutes < 480) { // 6:00-8:00
|
||||
const progress = (minutes - 360) / 120
|
||||
sunLight.intensity = progress
|
||||
sunLight.color.setRGB(1, progress * 0.7 + 0.3, progress * 0.3 + 0.7)
|
||||
moonLight.intensity = 0.2
|
||||
// ambientLight.intensity = 0.2 + progress * 0.3
|
||||
}
|
||||
// 正午时段
|
||||
else if (minutes >= 480 && minutes <= 960) { // 8:00-16:00
|
||||
sunLight.intensity = 1.5
|
||||
sunLight.color.setRGB(1, 1, 1)
|
||||
moonLight.intensity = 0
|
||||
// ambientLight.intensity = 0.5
|
||||
}
|
||||
// 日落时段
|
||||
else if (minutes > 960 && minutes < 1080) { // 16:00-18:00
|
||||
const progress = 1 - (minutes - 960) / 120
|
||||
sunLight.intensity = progress + 0.2
|
||||
sunLight.color.setRGB(1, progress * 0.7 + 0.3, progress * 0.3 + 0.7)
|
||||
moonLight.intensity = 0
|
||||
// ambientLight.intensity = 0.2 + progress * 0.3
|
||||
}
|
||||
// 夜晚时段
|
||||
else {
|
||||
sunLight.intensity = 0
|
||||
moonLight.intensity = 0.5
|
||||
// ambientLight.intensity = 0.1
|
||||
}
|
||||
}
|
||||
|
||||
// 更新天空颜色
|
||||
const updateSkyColor = (minutes) => {
|
||||
const material = skyDome.material
|
||||
const normalizedMinutes = minutes === 1440 ? 0 : minutes
|
||||
let currentFrame = skyColorKeyframes[0]
|
||||
let nextFrame = skyColorKeyframes[skyColorKeyframes.length - 1]
|
||||
|
||||
for (let i = 0; i < skyColorKeyframes.length - 1; i++) {
|
||||
const frame = skyColorKeyframes[i]
|
||||
const following = skyColorKeyframes[i + 1]
|
||||
if (normalizedMinutes >= frame.time && normalizedMinutes < following.time) {
|
||||
currentFrame = frame
|
||||
nextFrame = following
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
const segmentDuration = nextFrame.time - currentFrame.time || 1
|
||||
const progress = (normalizedMinutes - currentFrame.time) / segmentDuration
|
||||
|
||||
material.uniforms.topColor.value
|
||||
.copy(currentFrame.top)
|
||||
.lerp(nextFrame.top, progress)
|
||||
|
||||
material.uniforms.bottomColor.value
|
||||
.copy(currentFrame.bottom)
|
||||
.lerp(nextFrame.bottom, progress)
|
||||
|
||||
// 更新太阳位置用于黄昏效果
|
||||
material.uniforms.sunPosition.value = {
|
||||
x: sun.position.x,
|
||||
y: sun.position.y,
|
||||
z: sun.position.z
|
||||
}
|
||||
|
||||
// 仅在太阳临近地平线时显示黄昏效果(缩短时间范围)
|
||||
let sunsetIntensity = 0.0
|
||||
if (normalizedMinutes >= 270 && normalizedMinutes <= 330) {
|
||||
// 日出黄昏:270-300最强,300-330逐渐减弱
|
||||
if (normalizedMinutes <= 300) {
|
||||
sunsetIntensity = (normalizedMinutes - 270) / 30
|
||||
} else {
|
||||
sunsetIntensity = 1.0 - (normalizedMinutes - 300) / 30
|
||||
}
|
||||
} else if (normalizedMinutes >= 1050 && normalizedMinutes <= 1110) {
|
||||
// 日落黄昏:1050-1080最强,1080-1110逐渐减弱
|
||||
if (normalizedMinutes <= 1080) {
|
||||
sunsetIntensity = (normalizedMinutes - 1050) / 30
|
||||
} else {
|
||||
sunsetIntensity = 1.0 - (normalizedMinutes - 1080) / 30
|
||||
}
|
||||
}
|
||||
|
||||
material.uniforms.isSunset.value = sunsetIntensity
|
||||
}
|
||||
|
||||
// 更新星空可见度
|
||||
const updateStarsVisibility = (minutes) => {
|
||||
// 夜晚时显示星空 (18:00-6:00)
|
||||
if ((minutes >= 0 && minutes <= 360) || (minutes >= 1080 && minutes <= 1440)) {
|
||||
stars.visible = true
|
||||
// 在夜晚时段内,0:00-6:00 和 18:00-24:00 有不同的透明度变化
|
||||
let opacity = 0;
|
||||
if (minutes >= 1080) { // 18:00-24:00
|
||||
opacity = Math.min(1, (minutes - 1080) / 180); // 3小时渐显
|
||||
} else if (minutes <= 360) { // 0:00-6:00
|
||||
opacity = Math.min(1, (360 - minutes) / 180); // 3小时渐显
|
||||
}
|
||||
stars.material.opacity = opacity;
|
||||
} else {
|
||||
stars.visible = false;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
skyGroup,
|
||||
init,
|
||||
setTime,
|
||||
}
|
||||
}
|
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,124 @@
|
||||
/**
|
||||
/**
|
||||
* 应用路由配置文件
|
||||
* 定义应用的路由结构、组件映射和权限控制规则
|
||||
*/
|
||||
|
||||
// 导入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 KeyApply from '../views/Key/KeyApply.vue' // 钥匙使用申请组件
|
||||
import KeyRecord from '../views/Key/KeyRecord.vue' // 钥匙取用记录组件
|
||||
import PathManager from '../views/Path/PathManager.vue' // 巡检路径管理组件
|
||||
import PathSchedule from '../views/Path/PathSchedule.vue' // 巡检计划管理组件
|
||||
import PathLog from '../views/Path/PathLog.vue' // 巡检日志管理组件
|
||||
import CarManager from '../views/Car/CarManager.vue' // 车辆管理组件
|
||||
import CarUseLog from '../views/Car/CarUseLog.vue' // 车辆使用记录组件
|
||||
import CarEntryExitLog from '../views/Car/CarEntryExitLog.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 // 右侧显示钥匙管理视图
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/Key/KeyApply',
|
||||
name: 'keyApply',
|
||||
components: {
|
||||
map: HomeView, // 左侧显示地图视图
|
||||
right: KeyApply // 右侧显示钥匙使用申请视图
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/Key/KeyRecord',
|
||||
name: 'keyRecord',
|
||||
components: {
|
||||
map: HomeView, // 左侧显示地图视图
|
||||
right: KeyRecord // 右侧显示钥匙取用记录视图
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/Path/PathManager',
|
||||
name: 'pathManager',
|
||||
components: {
|
||||
map: HomeView, // 左侧显示地图视图
|
||||
right: PathManager // 右侧显示巡检路径管理视图
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/Path/PathSchedule',
|
||||
name: 'pathSchedule',
|
||||
components: {
|
||||
map: HomeView, // 左侧显示地图视图
|
||||
right: PathSchedule // 右侧显示巡检计划管理视图
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/Path/PathLog',
|
||||
name: 'pathLog',
|
||||
components: {
|
||||
map: HomeView, // 左侧显示地图视图
|
||||
right: PathLog // 右侧显示巡检日志管理视图
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/Car/CarManager',
|
||||
name: 'carManager',
|
||||
components: {
|
||||
map: HomeView, // 左侧显示地图视图
|
||||
right: CarManager // 右侧显示车辆管理视图
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/Car/CarUseLog',
|
||||
name: 'carUseLog',
|
||||
components: {
|
||||
map: HomeView, // 左侧显示地图视图
|
||||
right: CarUseLog // 右侧显示车辆使用记录视图
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/Car/CarEntryExitLog',
|
||||
name: 'carEntryExitLog',
|
||||
components: {
|
||||
map: HomeView, // 左侧显示地图视图
|
||||
right: CarEntryExitLog // 右侧显示车辆进出记录视图
|
||||
}
|
||||
}
|
||||
// 可以在这里添加更多子路由
|
||||
@@ -40,18 +127,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()
|
||||
}
|
||||
})
|
||||
|
37
src/stores/device.js
Normal file
37
src/stores/device.js
Normal file
@@ -0,0 +1,37 @@
|
||||
import { defineStore } from 'pinia'
|
||||
|
||||
// 定义全局设备数组的store
|
||||
export const useDeviceStore = defineStore('device', {
|
||||
state: () => ({
|
||||
// 全局设备数组:包含{ modelId, modelName }结构的对象
|
||||
deviceList: []
|
||||
}),
|
||||
|
||||
actions: {
|
||||
// 添加设备到全局数组
|
||||
addDevice(modelId, modelName) {
|
||||
// 检查是否已存在相同的modelId
|
||||
const existingIndex = this.deviceList.findIndex(device => device.modelId === modelId)
|
||||
|
||||
if (existingIndex === -1 && modelId && modelName) {
|
||||
this.deviceList.push({
|
||||
modelId,
|
||||
modelName
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
// 清空设备列表
|
||||
clearDevices() {
|
||||
this.deviceList = []
|
||||
},
|
||||
|
||||
// 获取用于Select组件的选项格式
|
||||
getSelectOptions() {
|
||||
return this.deviceList.map(device => ({
|
||||
value: device.modelId, // 设备id作为value
|
||||
label: device.modelName // 设备名称作为label
|
||||
}))
|
||||
}
|
||||
}
|
||||
})
|
49
src/stores/pathStore.js
Normal file
49
src/stores/pathStore.js
Normal file
@@ -0,0 +1,49 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { useDeviceStore } from './device'
|
||||
|
||||
// 定义路径点store
|
||||
export const usePathStore = defineStore('path', {
|
||||
state: () => ({
|
||||
// 路径点数组:包含{ modelId, deviceName }结构的对象
|
||||
pathPoints: []
|
||||
}),
|
||||
|
||||
actions: {
|
||||
// 同步路径点数据
|
||||
syncPathPoints(pathIdList) {
|
||||
const deviceStore = useDeviceStore()
|
||||
|
||||
// 清空当前路径点数组
|
||||
this.pathPoints = []
|
||||
|
||||
// 根据pathIdList中的id顺序,查找对应的设备信息并同步
|
||||
pathIdList.forEach(modelId => {
|
||||
// 从deviceList中查找对应的设备信息
|
||||
const device = deviceStore.deviceList.find(d => d.modelId === modelId)
|
||||
|
||||
if (device) {
|
||||
this.pathPoints.push({
|
||||
modelId: modelId,
|
||||
deviceName: device.modelName
|
||||
})
|
||||
} else {
|
||||
// 如果没有找到对应的设备信息,仍添加modelId但deviceName为空或默认值
|
||||
this.pathPoints.push({
|
||||
modelId: modelId,
|
||||
deviceName: `未知设备(${modelId})`
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
// 清空路径点
|
||||
clearPathPoints() {
|
||||
this.pathPoints = []
|
||||
},
|
||||
|
||||
// 获取路径点列表
|
||||
getPathPoints() {
|
||||
return this.pathPoints
|
||||
}
|
||||
}
|
||||
})
|
@@ -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() // 失败时自动登出
|
||||
}
|
||||
}
|
||||
}
|
||||
|
5
src/style.css
Normal file
5
src/style.css
Normal file
@@ -0,0 +1,5 @@
|
||||
html,
|
||||
body {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
391
src/views/Car/CarEntryExitLog.vue
Normal file
391
src/views/Car/CarEntryExitLog.vue
Normal file
@@ -0,0 +1,391 @@
|
||||
<template>
|
||||
<div class="car-entry-exit-log">
|
||||
<el-card class="mb-4">
|
||||
<div class="flex justify-between items-center mb-4">
|
||||
<h2 class="text-xl font-bold text-blue-400">车辆出入营区记录</h2>
|
||||
</div>
|
||||
|
||||
<!-- 搜索栏 -->
|
||||
<div class="search-bar mb-4 p-3 bg-blue-900/20 rounded">
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="8">
|
||||
<el-input
|
||||
v-model="searchForm.carId"
|
||||
placeholder="车牌号"
|
||||
prefix-icon="el-icon-search"
|
||||
size="small"
|
||||
/>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-select
|
||||
v-model="searchForm.action"
|
||||
placeholder="方向"
|
||||
size="small"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in actionOptions"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-col>
|
||||
<el-col :span="8" class="text-right">
|
||||
<el-button type="primary" size="small" @click="handleSearch">搜索</el-button>
|
||||
<el-button size="small" @click="handleReset">重置</el-button>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
|
||||
<!-- 出入营区记录表格 -->
|
||||
<el-table
|
||||
:data="logData"
|
||||
style="width: 100%"
|
||||
stripe
|
||||
:row-key="row => row.Id"
|
||||
>
|
||||
<el-table-column
|
||||
v-for="column in tableColumns"
|
||||
:key="column.field"
|
||||
:prop="column.field"
|
||||
:label="column.title"
|
||||
:width="column.width"
|
||||
:align="column.align"
|
||||
v-if="!column.hidden"
|
||||
>
|
||||
<template v-if="column.field === 'Action'" slot-scope="scope">
|
||||
<el-tag :type="getActionType(scope.row.Action)">{{ getActionLabel(scope.row.Action) }}</el-tag>
|
||||
</template>
|
||||
<template v-else-if="column.field === 'CreateDate'" slot-scope="scope">
|
||||
{{ formatDateTime(scope.row.CreateDate) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<!-- 分页 -->
|
||||
<div class="mt-4 flex justify-end">
|
||||
<el-pagination
|
||||
layout="prev, pager, next, jumper, sizes, total"
|
||||
:total="total"
|
||||
:page-size="pageSize"
|
||||
:page-sizes="[10, 20, 50, 100]"
|
||||
@current-change="handleCurrentChange"
|
||||
@size-change="handleSizeChange"
|
||||
/>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<!-- 新增记录弹窗 -->
|
||||
<el-dialog
|
||||
title="新增出入营区记录"
|
||||
:visible.sync="dialogVisible"
|
||||
width="450px"
|
||||
:close-on-click-modal="false"
|
||||
:close-on-press-escape="false"
|
||||
>
|
||||
<el-form
|
||||
:model="formData"
|
||||
:rules="rules"
|
||||
ref="formRef"
|
||||
label-width="100px"
|
||||
class="mt-4"
|
||||
>
|
||||
<el-form-item label="车牌号" prop="CarId">
|
||||
<el-input v-model="formData.CarId" placeholder="请输入车牌号" />
|
||||
</el-form-item>
|
||||
<el-form-item label="方向" prop="Action">
|
||||
<el-select v-model="formData.Action" placeholder="请选择方向">
|
||||
<el-option
|
||||
v-for="item in actionOptions"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="时间" prop="CreateDate">
|
||||
<el-date-picker
|
||||
v-model="formData.CreateDate"
|
||||
type="datetime"
|
||||
placeholder="请选择时间"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button @click="dialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="handleSubmit">确定</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 操作按钮 -->
|
||||
<div class="fixed bottom-4 right-4">
|
||||
<el-button type="primary" icon="el-icon-plus" circle size="medium" @click="handleAdd" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'CarEntryExitLog',
|
||||
data() {
|
||||
return {
|
||||
// 表格数据
|
||||
logData: [],
|
||||
total: 0,
|
||||
pageSize: 10,
|
||||
currentPage: 1,
|
||||
|
||||
// 搜索表单
|
||||
searchForm: {
|
||||
carId: '',
|
||||
action: ''
|
||||
},
|
||||
|
||||
// 新增记录对话框
|
||||
dialogVisible: false,
|
||||
formData: {
|
||||
Id: '',
|
||||
CarId: '',
|
||||
Action: '',
|
||||
CreateDate: ''
|
||||
},
|
||||
|
||||
// 验证规则
|
||||
rules: {
|
||||
CarId: [
|
||||
{ required: true, message: '请输入车牌号', trigger: 'blur' }
|
||||
],
|
||||
Action: [
|
||||
{ required: true, message: '请选择方向', trigger: 'change' }
|
||||
],
|
||||
CreateDate: [
|
||||
{ required: true, message: '请选择时间', trigger: 'change' }
|
||||
]
|
||||
},
|
||||
|
||||
// 出入方向选项
|
||||
actionOptions: [
|
||||
{ label: '出', value: '出' },
|
||||
{ label: '入', value: '入' }
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
tableColumns() {
|
||||
return [
|
||||
{field:'Id',title:'车辆记录ID',type:'int',width:110,hidden:true,require:true,align:'left'},
|
||||
{field:'CarId',title:'车牌号',type:'string',link:true,width:150,align:'left'},
|
||||
{field:'Action',title:'方向(出/入)',type:'string',bind:{ key:'出入方向',data:[]},width:150,align:'left'},
|
||||
{field:'CreateDate',title:'时间',type:'datetime',width:180,align:'left'}
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.loadData()
|
||||
},
|
||||
|
||||
methods: {
|
||||
// 加载数据
|
||||
loadData() {
|
||||
// 模拟数据加载
|
||||
this.logData = [
|
||||
{
|
||||
Id: 1,
|
||||
CarId: '京A12345',
|
||||
Action: '出',
|
||||
CreateDate: '2024-01-20 08:30:00'
|
||||
},
|
||||
{
|
||||
Id: 2,
|
||||
CarId: '京B54321',
|
||||
Action: '出',
|
||||
CreateDate: '2024-01-20 09:15:00'
|
||||
},
|
||||
{
|
||||
Id: 3,
|
||||
CarId: '京A12345',
|
||||
Action: '入',
|
||||
CreateDate: '2024-01-20 17:45:00'
|
||||
},
|
||||
{
|
||||
Id: 4,
|
||||
CarId: '京C67890',
|
||||
Action: '入',
|
||||
CreateDate: '2024-01-20 18:20:00'
|
||||
},
|
||||
{
|
||||
Id: 5,
|
||||
CarId: '京B54321',
|
||||
Action: '入',
|
||||
CreateDate: '2024-01-20 19:00:00'
|
||||
},
|
||||
{
|
||||
Id: 6,
|
||||
CarId: '京D11223',
|
||||
Action: '入',
|
||||
CreateDate: '2024-01-21 08:00:00'
|
||||
},
|
||||
{
|
||||
Id: 7,
|
||||
CarId: '京D11223',
|
||||
Action: '出',
|
||||
CreateDate: '2024-01-21 16:30:00'
|
||||
}
|
||||
]
|
||||
this.total = this.logData.length
|
||||
},
|
||||
|
||||
// 搜索
|
||||
handleSearch() {
|
||||
// 模拟搜索逻辑
|
||||
console.log('搜索条件:', this.searchForm)
|
||||
this.loadData()
|
||||
},
|
||||
|
||||
// 重置
|
||||
handleReset() {
|
||||
this.searchForm = {
|
||||
carId: '',
|
||||
action: ''
|
||||
}
|
||||
this.loadData()
|
||||
},
|
||||
|
||||
// 新增
|
||||
handleAdd() {
|
||||
this.formData = {
|
||||
Id: '',
|
||||
CarId: '',
|
||||
Action: '',
|
||||
CreateDate: new Date()
|
||||
}
|
||||
this.dialogVisible = true
|
||||
},
|
||||
|
||||
// 提交表单
|
||||
handleSubmit() {
|
||||
this.$refs.formRef.validate((valid) => {
|
||||
if (valid) {
|
||||
// 模拟提交操作
|
||||
console.log('提交数据:', this.formData)
|
||||
this.dialogVisible = false
|
||||
this.loadData()
|
||||
this.$message.success('记录添加成功')
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
// 分页处理
|
||||
handleCurrentChange(val) {
|
||||
this.currentPage = val
|
||||
this.loadData()
|
||||
},
|
||||
|
||||
handleSizeChange(val) {
|
||||
this.pageSize = val
|
||||
this.loadData()
|
||||
},
|
||||
|
||||
// 格式化日期时间
|
||||
formatDateTime(dateTime) {
|
||||
if (!dateTime) return ''
|
||||
const date = new Date(dateTime)
|
||||
return date.toLocaleString('zh-CN')
|
||||
},
|
||||
|
||||
// 获取动作标签
|
||||
getActionLabel(value) {
|
||||
const item = this.actionOptions.find(opt => opt.value === value)
|
||||
return item ? item.label : value
|
||||
},
|
||||
|
||||
// 获取动作类型(用于标签颜色)
|
||||
getActionType(value) {
|
||||
return value === '出' ? 'warning' : 'success'
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.car-entry-exit-log {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.search-bar {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
/* 表格背景透明 */
|
||||
:deep(.el-table) {
|
||||
background-color: transparent !important;
|
||||
}
|
||||
|
||||
:deep(.el-table__body-wrapper) {
|
||||
background-color: transparent !important;
|
||||
}
|
||||
|
||||
:deep(.el-table__row) {
|
||||
background-color: transparent !important;
|
||||
}
|
||||
|
||||
/* 表头透明 */
|
||||
:deep(.el-table__header) {
|
||||
background: transparent !important;
|
||||
}
|
||||
|
||||
:deep(.el-table__header) th {
|
||||
background: transparent !important;
|
||||
background-color: transparent !important;
|
||||
color: #68c9ff !important;
|
||||
border-color: rgba(104, 201, 255, 0.3) !important;
|
||||
}
|
||||
|
||||
/* 确保表头在各种状态下都保持透明 */
|
||||
:deep(.el-table th.is-leaf) {
|
||||
background: transparent !important;
|
||||
background-color: transparent !important;
|
||||
}
|
||||
|
||||
/* 表格边框 */
|
||||
:deep(.el-table__row td) {
|
||||
border-color: rgba(104, 201, 255, 0.2) !important;
|
||||
color: #91d5ff !important;
|
||||
}
|
||||
|
||||
/* 表格悬停效果 */
|
||||
:deep(.el-table__row:hover td) {
|
||||
background-color: rgba(104, 201, 255, 0.1) !important;
|
||||
}
|
||||
|
||||
:deep(.el-dialog) {
|
||||
background-color: rgba(0, 58, 97, 0.4) !important;
|
||||
backdrop-filter: blur(8px);
|
||||
border: 1px solid rgba(104, 201, 255, 0.3);
|
||||
}
|
||||
|
||||
:deep(.el-dialog__header) {
|
||||
background-color: rgba(0, 58, 97, 0.6) !important;
|
||||
border-bottom: 1px solid rgba(104, 201, 255, 0.3);
|
||||
}
|
||||
|
||||
:deep(.el-dialog__title) {
|
||||
color: #91d5ff !important;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
:deep(.el-dialog__footer) {
|
||||
background-color: rgba(0, 58, 97, 0.6) !important;
|
||||
border-top: 1px solid rgba(104, 201, 255, 0.3);
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
|
||||
:deep(.el-form-label) {
|
||||
color: #68c9ff !important;
|
||||
font-weight: 500;
|
||||
}
|
||||
</style>
|
742
src/views/Car/CarManager.vue
Normal file
742
src/views/Car/CarManager.vue
Normal file
@@ -0,0 +1,742 @@
|
||||
<template>
|
||||
<div class="car-manager">
|
||||
<el-card class="mb-4">
|
||||
<div class="flex justify-between items-center mb-4">
|
||||
<h2 class="text-xl font-bold text-blue-400">车辆信息管理</h2>
|
||||
<el-button type="primary" @click="handleAdd" icon="el-icon-plus">新增车辆</el-button>
|
||||
</div>
|
||||
|
||||
<!-- 搜索栏 -->
|
||||
<div class="search-bar mb-4 p-3 bg-blue-900/20 rounded">
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="6">
|
||||
<el-input
|
||||
v-model="searchForm.carNumber"
|
||||
placeholder="车牌号"
|
||||
prefix-icon="el-icon-search"
|
||||
size="small"
|
||||
/>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-select
|
||||
v-model="searchForm.carType"
|
||||
placeholder="车型"
|
||||
size="small"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in carTypeOptions"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-select
|
||||
v-model="searchForm.status"
|
||||
placeholder="车辆状态"
|
||||
size="small"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in carStatusOptions"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-col>
|
||||
<el-col :span="6" class="text-right">
|
||||
<el-button type="primary" size="small" @click="handleSearch">搜索</el-button>
|
||||
<el-button size="small" @click="handleReset">重置</el-button>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
|
||||
<!-- 车辆信息表格 -->
|
||||
<el-table
|
||||
:data="carData"
|
||||
style="width: 100%"
|
||||
stripe
|
||||
:row-key="row => row.CarId"
|
||||
>
|
||||
<el-table-column
|
||||
v-for="column in tableColumns"
|
||||
:key="column.field"
|
||||
:prop="column.field"
|
||||
:label="column.title"
|
||||
:width="column.width"
|
||||
:align="column.align"
|
||||
:sortable="column.sort"
|
||||
:show-overflow-tooltip="true"
|
||||
>
|
||||
<template v-if="column.bind && column.bind.key === '车辆类型'" slot-scope="scope">
|
||||
<el-tag>{{ getCarTypeLabel(scope.row.CarType) }}</el-tag>
|
||||
</template>
|
||||
<template v-else-if="column.bind && column.bind.key === '车辆状态'" slot-scope="scope">
|
||||
<el-tag>{{ getStatusLabel(scope.row.Status) }}</el-tag>
|
||||
</template>
|
||||
<template v-else-if="column.field === 'BuyTime'" slot-scope="scope">
|
||||
{{ formatDateTime(scope.row.BuyTime) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<!-- 操作列 -->
|
||||
<el-table-column label="操作" width="180" fixed="right">
|
||||
<template slot-scope="scope">
|
||||
<el-button size="small" type="primary" @click="handleEdit(scope.row)">编辑</el-button>
|
||||
<el-button size="small" type="danger" @click="handleDelete(scope.row)">删除</el-button>
|
||||
<el-button size="small" type="success" @click="handleApply(scope.row)">申请</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<!-- 分页 -->
|
||||
<div class="mt-4 flex justify-end">
|
||||
<el-pagination
|
||||
layout="prev, pager, next, jumper, sizes, total"
|
||||
:total="total"
|
||||
:page-size="pageSize"
|
||||
:page-sizes="[10, 20, 50, 100]"
|
||||
@current-change="handleCurrentChange"
|
||||
@size-change="handleSizeChange"
|
||||
/>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<!-- 新增/编辑车辆弹窗 -->
|
||||
<el-dialog
|
||||
:title="dialogTitle"
|
||||
:visible.sync="dialogVisible"
|
||||
width="600px"
|
||||
:close-on-click-modal="false"
|
||||
:close-on-press-escape="false"
|
||||
>
|
||||
<el-form
|
||||
:model="formData"
|
||||
:rules="rules"
|
||||
ref="formRef"
|
||||
label-width="120px"
|
||||
class="mt-4"
|
||||
>
|
||||
<el-form-item label="车牌号" prop="CarNumber">
|
||||
<el-input v-model="formData.CarNumber" placeholder="请输入车牌号" />
|
||||
</el-form-item>
|
||||
<el-form-item label="车型" prop="CarType">
|
||||
<el-select v-model="formData.CarType" placeholder="请选择车型">
|
||||
<el-option
|
||||
v-for="item in carTypeOptions"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="购买时间" prop="BuyTime">
|
||||
<el-date-picker
|
||||
v-model="formData.BuyTime"
|
||||
type="datetime"
|
||||
placeholder="请选择购买时间"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="使用年限(年)" prop="ServiceLife">
|
||||
<el-input v-model.number="formData.ServiceLife" placeholder="请输入使用年限" type="number" />
|
||||
</el-form-item>
|
||||
<el-form-item label="保养周期" prop="ServiceInterval">
|
||||
<el-input v-model="formData.ServiceInterval" placeholder="请输入保养周期" />
|
||||
</el-form-item>
|
||||
<el-form-item label="车辆状态" prop="Status">
|
||||
<el-select v-model="formData.Status" placeholder="请选择车辆状态">
|
||||
<el-option
|
||||
v-for="item in carStatusOptions"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="备注">
|
||||
<el-input v-model="formData.Memo" placeholder="请输入备注信息" type="textarea" rows="3" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button @click="dialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="handleSubmit">确定</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 车辆申请弹窗 -->
|
||||
<el-dialog
|
||||
title="车辆申请"
|
||||
:visible.sync="applyDialogVisible"
|
||||
width="500px"
|
||||
:close-on-click-modal="false"
|
||||
:close-on-press-escape="false"
|
||||
>
|
||||
<el-form
|
||||
:model="applyForm"
|
||||
:rules="applyRules"
|
||||
ref="applyFormRef"
|
||||
label-width="100px"
|
||||
class="mt-4"
|
||||
>
|
||||
<el-form-item label="申请车辆">
|
||||
<el-input v-model="applyForm.carNumber" disabled placeholder="车牌号" />
|
||||
</el-form-item>
|
||||
<el-form-item label="申请人" prop="applicant">
|
||||
<el-input v-model="applyForm.applicant" placeholder="申请人" />
|
||||
</el-form-item>
|
||||
<el-form-item label="申请事由" prop="reason">
|
||||
<el-input v-model="applyForm.reason" placeholder="请输入申请事由" type="textarea" rows="4" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button @click="applyDialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="handleApplySubmit">提交申请</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'CarManager',
|
||||
data() {
|
||||
return {
|
||||
// 表格数据
|
||||
carData: [],
|
||||
total: 0,
|
||||
pageSize: 10,
|
||||
currentPage: 1,
|
||||
|
||||
// 搜索表单
|
||||
searchForm: {
|
||||
carNumber: '',
|
||||
carType: '',
|
||||
status: ''
|
||||
},
|
||||
|
||||
// 新增/编辑对话框
|
||||
dialogVisible: false,
|
||||
dialogTitle: '',
|
||||
formData: {
|
||||
CarId: '',
|
||||
CarNumber: '',
|
||||
CarType: '',
|
||||
BuyTime: '',
|
||||
ServiceLife: '',
|
||||
ServiceInterval: '',
|
||||
Status: '',
|
||||
Memo: ''
|
||||
},
|
||||
|
||||
// 申请对话框
|
||||
applyDialogVisible: false,
|
||||
applyForm: {
|
||||
carId: '',
|
||||
carNumber: '',
|
||||
applicant: '',
|
||||
reason: ''
|
||||
},
|
||||
|
||||
// 验证规则
|
||||
rules: {
|
||||
CarNumber: [
|
||||
{ required: true, message: '请输入车牌号', trigger: 'blur' }
|
||||
],
|
||||
CarType: [
|
||||
{ required: true, message: '请选择车型', trigger: 'change' }
|
||||
],
|
||||
BuyTime: [
|
||||
{ required: true, message: '请选择购买时间', trigger: 'change' }
|
||||
],
|
||||
ServiceLife: [
|
||||
{ required: true, message: '请输入使用年限', trigger: 'blur' },
|
||||
{ type: 'number', message: '请输入有效的数字', trigger: 'blur' }
|
||||
],
|
||||
ServiceInterval: [
|
||||
{ required: true, message: '请输入保养周期', trigger: 'blur' }
|
||||
],
|
||||
Status: [
|
||||
{ required: true, message: '请选择车辆状态', trigger: 'change' }
|
||||
]
|
||||
},
|
||||
|
||||
applyRules: {
|
||||
applicant: [
|
||||
{ required: true, message: '请输入申请人', trigger: 'blur' }
|
||||
],
|
||||
reason: [
|
||||
{ required: true, message: '请输入申请事由', trigger: 'blur' }
|
||||
]
|
||||
},
|
||||
|
||||
// 车型选项
|
||||
carTypeOptions: [
|
||||
{ label: '轿车', value: '轿车' },
|
||||
{ label: 'SUV', value: 'SUV' },
|
||||
{ label: '货车', value: '货车' },
|
||||
{ label: '客车', value: '客车' },
|
||||
{ label: '其他', value: '其他' }
|
||||
],
|
||||
|
||||
// 车辆状态选项
|
||||
carStatusOptions: [
|
||||
{ label: '正常', value: 'normal' },
|
||||
{ label: '维修中', value: 'repairing' },
|
||||
{ label: '已报废', value: 'scrapped' },
|
||||
{ label: '已借出', value: 'borrowed' }
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
tableColumns() {
|
||||
return [
|
||||
{field:'CarId',title:'车辆ID',type:'int',width:110,hidden:true,require:true,align:'left'},
|
||||
{field:'CarNumber',title:'车牌号',type:'string',link:true,sort:true,width:150,align:'left'},
|
||||
{field:'CarType',title:'车型',type:'string',bind:{ key:'车辆类型',data:[]},width:150,align:'left'},
|
||||
{field:'BuyTime',title:'购买时间',type:'datetime',width:180,align:'left'},
|
||||
{field:'ServiceLife',title:'使用年限(年)',type:'int',width:130,align:'left'},
|
||||
{field:'ServiceInterval',title:'保养周期',type:'string',width:150,align:'left'},
|
||||
{field:'Status',title:'车辆状态',type:'string',bind:{ key:'车辆状态',data:[]},width:120,align:'left'},
|
||||
{field:'Memo',title:'备注',type:'string',width:200,align:'left'}
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.loadData()
|
||||
this.loadCurrentUser()
|
||||
},
|
||||
|
||||
methods: {
|
||||
// 加载数据
|
||||
loadData() {
|
||||
// 模拟数据加载
|
||||
this.carData = [
|
||||
{
|
||||
CarId: 1,
|
||||
CarNumber: '京A12345',
|
||||
CarType: '轿车',
|
||||
BuyTime: '2022-01-15 10:30:00',
|
||||
ServiceLife: 5,
|
||||
ServiceInterval: '每3个月',
|
||||
Status: 'normal',
|
||||
Memo: '公司行政用车'
|
||||
},
|
||||
{
|
||||
CarId: 2,
|
||||
CarNumber: '京B54321',
|
||||
CarType: 'SUV',
|
||||
BuyTime: '2023-03-20 14:20:00',
|
||||
ServiceLife: 3,
|
||||
ServiceInterval: '每6个月',
|
||||
Status: 'repairing',
|
||||
Memo: '项目组用车'
|
||||
},
|
||||
{
|
||||
CarId: 3,
|
||||
CarNumber: '京C67890',
|
||||
CarType: '货车',
|
||||
BuyTime: '2021-06-10 09:15:00',
|
||||
ServiceLife: 4,
|
||||
ServiceInterval: '每2个月',
|
||||
Status: 'normal',
|
||||
Memo: '物资运输用车'
|
||||
}
|
||||
]
|
||||
this.total = this.carData.length
|
||||
},
|
||||
|
||||
// 加载当前登录用户
|
||||
loadCurrentUser() {
|
||||
// 模拟获取当前登录用户
|
||||
// 实际项目中应该从用户存储或接口获取
|
||||
this.applyForm.applicant = '张三' // 默认申请人
|
||||
},
|
||||
|
||||
// 搜索
|
||||
handleSearch() {
|
||||
// 模拟搜索逻辑
|
||||
console.log('搜索条件:', this.searchForm)
|
||||
this.loadData()
|
||||
},
|
||||
|
||||
// 重置
|
||||
handleReset() {
|
||||
this.searchForm = {
|
||||
carNumber: '',
|
||||
carType: '',
|
||||
status: ''
|
||||
}
|
||||
this.loadData()
|
||||
},
|
||||
|
||||
// 新增
|
||||
handleAdd() {
|
||||
this.dialogTitle = '新增车辆'
|
||||
this.formData = {
|
||||
CarId: '',
|
||||
CarNumber: '',
|
||||
CarType: '',
|
||||
BuyTime: '',
|
||||
ServiceLife: '',
|
||||
ServiceInterval: '',
|
||||
Status: '',
|
||||
Memo: ''
|
||||
}
|
||||
this.dialogVisible = true
|
||||
},
|
||||
|
||||
// 编辑
|
||||
handleEdit(row) {
|
||||
this.dialogTitle = '编辑车辆'
|
||||
this.formData = { ...row }
|
||||
// 格式化日期时间
|
||||
if (row.BuyTime) {
|
||||
this.formData.BuyTime = new Date(row.BuyTime)
|
||||
}
|
||||
this.dialogVisible = true
|
||||
},
|
||||
|
||||
// 删除
|
||||
handleDelete(row) {
|
||||
this.$confirm(`确定要删除车辆【${row.CarNumber}】吗?`, '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
// 模拟删除操作
|
||||
console.log('删除车辆:', row)
|
||||
this.loadData()
|
||||
this.$message.success('删除成功')
|
||||
}).catch(() => {
|
||||
this.$message.info('已取消删除')
|
||||
})
|
||||
},
|
||||
|
||||
// 申请
|
||||
handleApply(row) {
|
||||
this.applyForm = {
|
||||
carId: row.CarId,
|
||||
carNumber: row.CarNumber,
|
||||
applicant: this.applyForm.applicant, // 保持当前用户
|
||||
reason: ''
|
||||
}
|
||||
this.applyDialogVisible = true
|
||||
},
|
||||
|
||||
// 提交表单
|
||||
handleSubmit() {
|
||||
// 确保表单引用存在
|
||||
if (!this.$refs.formRef) {
|
||||
console.warn('表单引用未找到')
|
||||
return
|
||||
}
|
||||
|
||||
this.$refs.formRef.validate((valid) => {
|
||||
if (valid) {
|
||||
// 模拟提交操作
|
||||
console.log('提交数据:', this.formData)
|
||||
this.dialogVisible = false
|
||||
this.loadData()
|
||||
// 模拟消息提示
|
||||
alert('操作成功')
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
// 提交申请
|
||||
handleApplySubmit() {
|
||||
// 确保表单引用存在
|
||||
if (!this.$refs.applyFormRef) {
|
||||
console.warn('申请表单引用未找到')
|
||||
return
|
||||
}
|
||||
|
||||
this.$refs.applyFormRef.validate((valid) => {
|
||||
if (valid) {
|
||||
// 模拟申请提交操作
|
||||
console.log('申请数据:', this.applyForm)
|
||||
this.applyDialogVisible = false
|
||||
// 模拟消息提示
|
||||
alert('申请已提交')
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
// 分页处理
|
||||
handleCurrentChange(val) {
|
||||
this.currentPage = val
|
||||
this.loadData()
|
||||
},
|
||||
|
||||
handleSizeChange(val) {
|
||||
this.pageSize = val
|
||||
this.loadData()
|
||||
},
|
||||
|
||||
// 格式化日期时间
|
||||
formatDateTime(dateTime) {
|
||||
if (!dateTime) return ''
|
||||
const date = new Date(dateTime)
|
||||
return date.toLocaleString('zh-CN')
|
||||
},
|
||||
|
||||
// 获取车型标签
|
||||
getCarTypeLabel(value) {
|
||||
const item = this.carTypeOptions.find(opt => opt.value === value)
|
||||
return item ? item.label : value
|
||||
},
|
||||
|
||||
// 获取状态标签
|
||||
getStatusLabel(value) {
|
||||
const item = this.carStatusOptions.find(opt => opt.value === value)
|
||||
return item ? item.label : value
|
||||
},
|
||||
|
||||
// 获取状态类型(用于标签颜色)
|
||||
getStatusType(value) {
|
||||
const statusMap = {
|
||||
'normal': 'success',
|
||||
'repairing': 'warning',
|
||||
'scrapped': 'danger',
|
||||
'borrowed': 'info'
|
||||
}
|
||||
return statusMap[value] || 'info'
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.car-manager {
|
||||
padding: 20px;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.search-bar {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
:deep(.el-card){
|
||||
background: transparent !important;
|
||||
border: 1px solid rgba(104, 201, 255, 0.3);
|
||||
}
|
||||
|
||||
/* 表格背景透明 */
|
||||
:deep(.el-table) {
|
||||
background-color: transparent !important;
|
||||
border: 1px solid rgba(104, 201, 255, 0.3);
|
||||
}
|
||||
|
||||
:deep(.el-table tr){
|
||||
background-color: transparent !important;
|
||||
}
|
||||
|
||||
:deep(.el-table__body-wrapper) {
|
||||
background-color: transparent !important;
|
||||
}
|
||||
|
||||
:deep(.el-table__row) {
|
||||
background-color: transparent !important;
|
||||
}
|
||||
|
||||
/* 表头透明 */
|
||||
:deep(.el-table__header) {
|
||||
background: transparent !important;
|
||||
}
|
||||
|
||||
:deep(.el-table__header) th {
|
||||
background: transparent !important;
|
||||
background-color: transparent !important;
|
||||
color: #68c9ff !important;
|
||||
border-color: rgba(104, 201, 255, 0.3) !important;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* 确保表头在各种状态下都保持透明 */
|
||||
:deep(.el-table th.is-leaf) {
|
||||
background: transparent !important;
|
||||
background-color: transparent !important;
|
||||
}
|
||||
|
||||
/* 表格边框 */
|
||||
:deep(.el-table__row td) {
|
||||
border-color: rgba(104, 201, 255, 0.2) !important;
|
||||
color: #91d5ff !important;
|
||||
}
|
||||
|
||||
/* 表格悬停效果 */
|
||||
:deep(.el-table__row:hover td) {
|
||||
background-color: rgba(104, 201, 255, 0.1) !important;
|
||||
}
|
||||
|
||||
/* 操作按钮样式 */
|
||||
:deep(.el-button) {
|
||||
color: #fff !important;
|
||||
border-color: rgba(104, 201, 255, 0.5) !important;
|
||||
background-color: rgba(0, 89, 158, 0.3) !important;
|
||||
}
|
||||
|
||||
:deep(.el-button:hover) {
|
||||
background-color: rgba(0, 89, 158, 0.5) !important;
|
||||
border-color: #68c9ff !important;
|
||||
}
|
||||
|
||||
:deep(.el-button--primary) {
|
||||
background-color: rgba(104, 201, 255, 0.3) !important;
|
||||
border-color: #68c9ff !important;
|
||||
}
|
||||
|
||||
:deep(.el-button--primary:hover) {
|
||||
background-color: rgba(104, 201, 255, 0.5) !important;
|
||||
}
|
||||
|
||||
:deep(.el-button--danger) {
|
||||
background-color: rgba(245, 108, 108, 0.3) !important;
|
||||
border-color: #f56c6c !important;
|
||||
}
|
||||
|
||||
:deep(.el-button--danger:hover) {
|
||||
background-color: rgba(245, 108, 108, 0.5) !important;
|
||||
}
|
||||
|
||||
:deep(.el-button--success) {
|
||||
background-color: rgba(103, 194, 58, 0.3) !important;
|
||||
border-color: #67c23a !important;
|
||||
}
|
||||
|
||||
:deep(.el-button--success:hover) {
|
||||
background-color: rgba(103, 194, 58, 0.5) !important;
|
||||
}
|
||||
|
||||
/* 对话框样式增强 */
|
||||
:deep(.el-dialog) {
|
||||
background-color: rgba(0, 58, 97, 0.95) !important; /* 增加不透明度,确保可见 */
|
||||
backdrop-filter: blur(8px);
|
||||
border: 1px solid rgba(104, 201, 255, 0.3);
|
||||
z-index: 9999 !important; /* 使用更高的z-index确保显示在最上层 */
|
||||
}
|
||||
|
||||
:deep(.el-dialog__header) {
|
||||
background-color: rgba(0, 58, 97, 0.8) !important;
|
||||
border-bottom: 1px solid rgba(104, 201, 255, 0.3);
|
||||
padding-top: 15px;
|
||||
padding-bottom: 15px;
|
||||
}
|
||||
|
||||
:deep(.el-dialog__title) {
|
||||
color: #91d5ff !important;
|
||||
font-weight: 600;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
:deep(.el-dialog__footer) {
|
||||
background-color: rgba(0, 58, 97, 0.8) !important;
|
||||
border-top: 1px solid rgba(104, 201, 255, 0.3);
|
||||
padding-top: 15px;
|
||||
padding-bottom: 15px;
|
||||
}
|
||||
|
||||
/* 表单样式 */
|
||||
:deep(.el-form-label) {
|
||||
color: #68c9ff !important;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* 输入框样式 */
|
||||
:deep(.el-input__inner) {
|
||||
background-color: rgba(0, 58, 97, 0.5) !important;
|
||||
border-color: rgba(104, 201, 255, 0.3) !important;
|
||||
color: #91d5ff !important;
|
||||
}
|
||||
|
||||
:deep(.el-input__inner:focus) {
|
||||
border-color: #68c9ff !important;
|
||||
box-shadow: 0 0 0 2px rgba(104, 201, 255, 0.2) !important;
|
||||
}
|
||||
|
||||
/* 选择框样式 */
|
||||
:deep(.el-select .el-input__inner) {
|
||||
background-color: rgba(0, 58, 97, 0.5) !important;
|
||||
border-color: rgba(104, 201, 255, 0.3) !important;
|
||||
color: #91d5ff !important;
|
||||
}
|
||||
|
||||
:deep(.el-select-dropdown) {
|
||||
background-color: rgba(0, 58, 97, 0.95) !important;
|
||||
border-color: rgba(104, 201, 255, 0.3) !important;
|
||||
}
|
||||
|
||||
:deep(.el-select-dropdown__item) {
|
||||
color: #91d5ff !important;
|
||||
}
|
||||
|
||||
:deep(.el-select-dropdown__item.hover) {
|
||||
background-color: rgba(104, 201, 255, 0.1) !important;
|
||||
}
|
||||
|
||||
/* 日期选择器样式 */
|
||||
:deep(.el-date-editor .el-input__inner) {
|
||||
background-color: rgba(0, 58, 97, 0.5) !important;
|
||||
border-color: rgba(104, 201, 255, 0.3) !important;
|
||||
color: #91d5ff !important;
|
||||
}
|
||||
|
||||
/* 标签样式 */
|
||||
:deep(.el-tag) {
|
||||
background-color: rgba(0, 89, 158, 0.3) !important;
|
||||
color: #91d5ff !important;
|
||||
border-color: rgba(104, 201, 255, 0.3) !important;
|
||||
}
|
||||
|
||||
/* 表格行样式确保内容可见 */
|
||||
:deep(.el-table .cell) {
|
||||
color: #91d5ff !important;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
/* 分页组件样式 */
|
||||
:deep(.el-pagination button) {
|
||||
background-color: rgba(0, 58, 97, 0.5) !important;
|
||||
border-color: rgba(104, 201, 255, 0.3) !important;
|
||||
color: #91d5ff !important;
|
||||
}
|
||||
|
||||
:deep(.el-pagination__sizes .el-input__inner) {
|
||||
background-color: rgba(0, 58, 97, 0.5) !important;
|
||||
border-color: rgba(104, 201, 255, 0.3) !important;
|
||||
color: #91d5ff !important;
|
||||
}
|
||||
|
||||
:deep(.el-pagination__total) {
|
||||
color: #91d5ff !important;
|
||||
}
|
||||
|
||||
:deep(.el-pagination .el-pager li) {
|
||||
color: #91d5ff !important;
|
||||
}
|
||||
|
||||
:deep(.el-pagination .el-pager li.active) {
|
||||
background-color: rgba(104, 201, 255, 0.3) !important;
|
||||
color: #fff !important;
|
||||
}
|
||||
|
||||
/* 确认对话框样式 */
|
||||
:deep(.el-message-box) {
|
||||
background-color: rgba(0, 58, 97, 0.95) !important;
|
||||
border-color: rgba(104, 201, 255, 0.3) !important;
|
||||
}
|
||||
|
||||
:deep(.el-message-box__title) {
|
||||
color: #91d5ff !important;
|
||||
}
|
||||
|
||||
:deep(.el-message-box__content) {
|
||||
color: #91d5ff !important;
|
||||
}
|
||||
</style>
|
462
src/views/Car/CarUseLog.vue
Normal file
462
src/views/Car/CarUseLog.vue
Normal file
@@ -0,0 +1,462 @@
|
||||
<template>
|
||||
<div class="car-use-log">
|
||||
<el-card class="mb-4">
|
||||
<div class="flex justify-between items-center mb-4">
|
||||
<h2 class="text-xl font-bold text-blue-400">车辆出入库记录</h2>
|
||||
</div>
|
||||
|
||||
<!-- 搜索栏 -->
|
||||
<div class="search-bar mb-4 p-3 bg-blue-900/20 rounded">
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="6">
|
||||
<el-select
|
||||
v-model="searchForm.carId"
|
||||
placeholder="选择车辆"
|
||||
size="small"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in carListOptions"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-select
|
||||
v-model="searchForm.action"
|
||||
placeholder="动作类型"
|
||||
size="small"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in actionOptions"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-select
|
||||
v-model="searchForm.actionUser"
|
||||
placeholder="动作执行人员"
|
||||
size="small"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in userListOptions"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-col>
|
||||
<el-col :span="6" class="text-right">
|
||||
<el-button type="primary" size="small" @click="handleSearch">搜索</el-button>
|
||||
<el-button size="small" @click="handleReset">重置</el-button>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
|
||||
<!-- 出入库记录表格 -->
|
||||
<el-table
|
||||
:data="logData"
|
||||
style="width: 100%"
|
||||
stripe
|
||||
:row-key="row => row.Id"
|
||||
>
|
||||
<el-table-column
|
||||
v-for="column in tableColumns"
|
||||
:key="column.field"
|
||||
:prop="column.field"
|
||||
:label="column.title"
|
||||
:width="column.width"
|
||||
:align="column.align"
|
||||
v-if="!column.hidden"
|
||||
>
|
||||
<template v-if="column.field === 'CarId'" slot-scope="scope">
|
||||
<el-tag>{{ getCarLabel(scope.row.CarId) }}</el-tag>
|
||||
</template>
|
||||
<template v-else-if="column.field === 'Action'" slot-scope="scope">
|
||||
<el-tag :type="getActionType(scope.row.Action)">{{ getActionLabel(scope.row.Action) }}</el-tag>
|
||||
</template>
|
||||
<template v-else-if="column.field === 'ActionUser'" slot-scope="scope">
|
||||
<el-tag>{{ getUserLabel(scope.row.ActionUser) }}</el-tag>
|
||||
</template>
|
||||
<template v-else-if="column.field === 'CreateDate'" slot-scope="scope">
|
||||
{{ formatDateTime(scope.row.CreateDate) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<!-- 分页 -->
|
||||
<div class="mt-4 flex justify-end">
|
||||
<el-pagination
|
||||
layout="prev, pager, next, jumper, sizes, total"
|
||||
:total="total"
|
||||
:page-size="pageSize"
|
||||
:page-sizes="[10, 20, 50, 100]"
|
||||
@current-change="handleCurrentChange"
|
||||
@size-change="handleSizeChange"
|
||||
/>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<!-- 新增记录弹窗 -->
|
||||
<el-dialog
|
||||
title="新增出入库记录"
|
||||
:visible.sync="dialogVisible"
|
||||
width="500px"
|
||||
:close-on-click-modal="false"
|
||||
:close-on-press-escape="false"
|
||||
>
|
||||
<el-form
|
||||
:model="formData"
|
||||
:rules="rules"
|
||||
ref="formRef"
|
||||
label-width="120px"
|
||||
class="mt-4"
|
||||
>
|
||||
<el-form-item label="车辆" prop="CarId">
|
||||
<el-select v-model="formData.CarId" placeholder="请选择车辆">
|
||||
<el-option
|
||||
v-for="item in carListOptions"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="动作" prop="Action">
|
||||
<el-select v-model="formData.Action" placeholder="请选择动作类型">
|
||||
<el-option
|
||||
v-for="item in actionOptions"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="动作执行人员" prop="ActionUser">
|
||||
<el-select v-model="formData.ActionUser" placeholder="请选择执行人员">
|
||||
<el-option
|
||||
v-for="item in userListOptions"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="动作时间" prop="CreateDate">
|
||||
<el-date-picker
|
||||
v-model="formData.CreateDate"
|
||||
type="datetime"
|
||||
placeholder="请选择动作时间"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button @click="dialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="handleSubmit">确定</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 操作按钮 -->
|
||||
<div class="fixed bottom-4 right-4">
|
||||
<el-button type="primary" icon="el-icon-plus" circle size="medium" @click="handleAdd" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'CarUseLog',
|
||||
data() {
|
||||
return {
|
||||
// 表格数据
|
||||
logData: [],
|
||||
total: 0,
|
||||
pageSize: 10,
|
||||
currentPage: 1,
|
||||
|
||||
// 搜索表单
|
||||
searchForm: {
|
||||
carId: '',
|
||||
action: '',
|
||||
actionUser: ''
|
||||
},
|
||||
|
||||
// 新增记录对话框
|
||||
dialogVisible: false,
|
||||
formData: {
|
||||
Id: '',
|
||||
CarId: '',
|
||||
Action: '',
|
||||
ActionUser: '',
|
||||
CreateDate: ''
|
||||
},
|
||||
|
||||
// 验证规则
|
||||
rules: {
|
||||
CarId: [
|
||||
{ required: true, message: '请选择车辆', trigger: 'change' }
|
||||
],
|
||||
Action: [
|
||||
{ required: true, message: '请选择动作类型', trigger: 'change' }
|
||||
],
|
||||
ActionUser: [
|
||||
{ required: true, message: '请选择执行人员', trigger: 'change' }
|
||||
],
|
||||
CreateDate: [
|
||||
{ required: true, message: '请选择动作时间', trigger: 'change' }
|
||||
]
|
||||
},
|
||||
|
||||
// 车辆列表选项
|
||||
carListOptions: [
|
||||
{ label: '京A12345', value: 1 },
|
||||
{ label: '京B54321', value: 2 },
|
||||
{ label: '京C67890', value: 3 }
|
||||
],
|
||||
|
||||
// 动作类型选项
|
||||
actionOptions: [
|
||||
{ label: '出库', value: '出库' },
|
||||
{ label: '入库', value: '入库' }
|
||||
],
|
||||
|
||||
// 人员列表选项
|
||||
userListOptions: [
|
||||
{ label: '张三', value: '张三' },
|
||||
{ label: '李四', value: '李四' },
|
||||
{ label: '王五', value: '王五' },
|
||||
{ label: '赵六', value: '赵六' }
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
tableColumns() {
|
||||
return [
|
||||
{field:'Id',title:'车辆记录ID',type:'int',width:110,hidden:true,require:true,align:'left'},
|
||||
{field:'CarId',title:'车辆',type:'int',bind:{ key:'车辆列表',data:[]},link:true,width:150,align:'left'},
|
||||
{field:'Action',title:'动作(出库/入库)',type:'string',bind:{ key:'车辆出入状态',data:[]},width:150,align:'left'},
|
||||
{field:'ActionUser',title:'动作执行人员',type:'string',bind:{ key:'人员列表',data:[]},width:150,align:'left'},
|
||||
{field:'CreateDate',title:'动作时间',type:'datetime',width:180,align:'left'}
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.loadData()
|
||||
},
|
||||
|
||||
methods: {
|
||||
// 加载数据
|
||||
loadData() {
|
||||
// 模拟数据加载
|
||||
this.logData = [
|
||||
{
|
||||
Id: 1,
|
||||
CarId: 1,
|
||||
Action: '出库',
|
||||
ActionUser: '张三',
|
||||
CreateDate: '2024-01-20 09:30:00'
|
||||
},
|
||||
{
|
||||
Id: 2,
|
||||
CarId: 1,
|
||||
Action: '入库',
|
||||
ActionUser: '张三',
|
||||
CreateDate: '2024-01-20 18:45:00'
|
||||
},
|
||||
{
|
||||
Id: 3,
|
||||
CarId: 2,
|
||||
Action: '出库',
|
||||
ActionUser: '李四',
|
||||
CreateDate: '2024-01-21 10:15:00'
|
||||
},
|
||||
{
|
||||
Id: 4,
|
||||
CarId: 3,
|
||||
Action: '出库',
|
||||
ActionUser: '王五',
|
||||
CreateDate: '2024-01-21 14:20:00'
|
||||
},
|
||||
{
|
||||
Id: 5,
|
||||
CarId: 2,
|
||||
Action: '入库',
|
||||
ActionUser: '李四',
|
||||
CreateDate: '2024-01-21 19:30:00'
|
||||
}
|
||||
]
|
||||
this.total = this.logData.length
|
||||
},
|
||||
|
||||
// 搜索
|
||||
handleSearch() {
|
||||
// 模拟搜索逻辑
|
||||
console.log('搜索条件:', this.searchForm)
|
||||
this.loadData()
|
||||
},
|
||||
|
||||
// 重置
|
||||
handleReset() {
|
||||
this.searchForm = {
|
||||
carId: '',
|
||||
action: '',
|
||||
actionUser: ''
|
||||
}
|
||||
this.loadData()
|
||||
},
|
||||
|
||||
// 新增
|
||||
handleAdd() {
|
||||
this.formData = {
|
||||
Id: '',
|
||||
CarId: '',
|
||||
Action: '',
|
||||
ActionUser: '',
|
||||
CreateDate: new Date()
|
||||
}
|
||||
this.dialogVisible = true
|
||||
},
|
||||
|
||||
// 提交表单
|
||||
handleSubmit() {
|
||||
this.$refs.formRef.validate((valid) => {
|
||||
if (valid) {
|
||||
// 模拟提交操作
|
||||
console.log('提交数据:', this.formData)
|
||||
this.dialogVisible = false
|
||||
this.loadData()
|
||||
this.$message.success('记录添加成功')
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
// 分页处理
|
||||
handleCurrentChange(val) {
|
||||
this.currentPage = val
|
||||
this.loadData()
|
||||
},
|
||||
|
||||
handleSizeChange(val) {
|
||||
this.pageSize = val
|
||||
this.loadData()
|
||||
},
|
||||
|
||||
// 格式化日期时间
|
||||
formatDateTime(dateTime) {
|
||||
if (!dateTime) return ''
|
||||
const date = new Date(dateTime)
|
||||
return date.toLocaleString('zh-CN')
|
||||
},
|
||||
|
||||
// 获取车辆标签
|
||||
getCarLabel(value) {
|
||||
const item = this.carListOptions.find(opt => opt.value === value)
|
||||
return item ? item.label : value
|
||||
},
|
||||
|
||||
// 获取动作标签
|
||||
getActionLabel(value) {
|
||||
const item = this.actionOptions.find(opt => opt.value === value)
|
||||
return item ? item.label : value
|
||||
},
|
||||
|
||||
// 获取动作类型(用于标签颜色)
|
||||
getActionType(value) {
|
||||
return value === '出库' ? 'warning' : 'success'
|
||||
},
|
||||
|
||||
// 获取用户标签
|
||||
getUserLabel(value) {
|
||||
const item = this.userListOptions.find(opt => opt.value === value)
|
||||
return item ? item.label : value
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.car-use-log {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.search-bar {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
/* 表格背景透明 */
|
||||
:deep(.el-table) {
|
||||
background-color: transparent !important;
|
||||
}
|
||||
|
||||
:deep(.el-table__body-wrapper) {
|
||||
background-color: transparent !important;
|
||||
}
|
||||
|
||||
:deep(.el-table__row) {
|
||||
background-color: transparent !important;
|
||||
}
|
||||
|
||||
/* 表头透明 */
|
||||
:deep(.el-table__header) {
|
||||
background: transparent !important;
|
||||
}
|
||||
|
||||
:deep(.el-table__header) th {
|
||||
background: transparent !important;
|
||||
background-color: transparent !important;
|
||||
color: #68c9ff !important;
|
||||
border-color: rgba(104, 201, 255, 0.3) !important;
|
||||
}
|
||||
|
||||
/* 确保表头在各种状态下都保持透明 */
|
||||
:deep(.el-table th.is-leaf) {
|
||||
background: transparent !important;
|
||||
background-color: transparent !important;
|
||||
}
|
||||
|
||||
/* 表格边框 */
|
||||
:deep(.el-table__row td) {
|
||||
border-color: rgba(104, 201, 255, 0.2) !important;
|
||||
color: #91d5ff !important;
|
||||
}
|
||||
|
||||
/* 表格悬停效果 */
|
||||
:deep(.el-table__row:hover td) {
|
||||
background-color: rgba(104, 201, 255, 0.1) !important;
|
||||
}
|
||||
|
||||
:deep(.el-dialog) {
|
||||
background-color: rgba(0, 58, 97, 0.4) !important;
|
||||
backdrop-filter: blur(8px);
|
||||
border: 1px solid rgba(104, 201, 255, 0.3);
|
||||
}
|
||||
|
||||
:deep(.el-dialog__header) {
|
||||
background-color: rgba(0, 58, 97, 0.6) !important;
|
||||
border-bottom: 1px solid rgba(104, 201, 255, 0.3);
|
||||
}
|
||||
|
||||
:deep(.el-dialog__title) {
|
||||
color: #91d5ff !important;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
:deep(.el-dialog__footer) {
|
||||
background-color: rgba(0, 58, 97, 0.6) !important;
|
||||
border-top: 1px solid rgba(104, 201, 255, 0.3);
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
|
||||
:deep(.el-form-label) {
|
||||
color: #68c9ff !important;
|
||||
font-weight: 500;
|
||||
}
|
||||
</style>
|
87
src/views/EditMap.vue
Normal file
87
src/views/EditMap.vue
Normal file
@@ -0,0 +1,87 @@
|
||||
<script setup>
|
||||
import { onMounted, ref, computed, provide, useTemplateRef } from "vue"
|
||||
import Filter from "@/components/EditorFilter.vue"
|
||||
import DisplayRouteLine from "@/components/EditDisplayRouteLine.vue"
|
||||
|
||||
// 在Map.vue中添加props定义
|
||||
const props = defineProps({
|
||||
elId: {
|
||||
type: String,
|
||||
default: 'mapContainer'
|
||||
}
|
||||
})
|
||||
|
||||
const { VgoMap } = window
|
||||
let mapId = "1977947221534052352"
|
||||
const isLoaded = ref(false)
|
||||
|
||||
const map = ref()
|
||||
console.log(map.value)
|
||||
const routeLineRef = useTemplateRef('routeLineRef')
|
||||
|
||||
provide("editMap", computed(() => map.value))
|
||||
provide('editRouteLineRef', computed(() => routeLineRef.value))
|
||||
provide('editPolygonDataAll', computed(() => {
|
||||
const outDoor = map.value?.mapData?.polygonData ?? []
|
||||
|
||||
const inDoor = map?.mapData?.build?.reduce((result, build) => {
|
||||
build.floor.forEach(fItem => {
|
||||
result.push(...fItem.polygonData)
|
||||
})
|
||||
return result
|
||||
}, []) ?? []
|
||||
|
||||
return [...outDoor, ...inDoor]
|
||||
}))
|
||||
|
||||
onMounted(() => {
|
||||
map.value = new VgoMap.Map({
|
||||
el: props.elId, // 使用传入的elId,
|
||||
id: mapId,
|
||||
})
|
||||
|
||||
map.value.on("loaded", () => {
|
||||
window.$editmap = map.value
|
||||
isLoaded.value = true
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="wrapper">
|
||||
<div :id="elId"></div>
|
||||
<div v-if="isLoaded" class="ui">
|
||||
<!-- <Fence/> -->
|
||||
<DisplayRouteLine ref="routeLineRef"/>
|
||||
<!-- <DisplayColor/> -->
|
||||
<Filter/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="css">
|
||||
html,
|
||||
body,
|
||||
.app,
|
||||
.wrapper {
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
#mapContainer {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.ui {
|
||||
position: absolute;
|
||||
right: 50px;
|
||||
bottom: 50px;
|
||||
z-index: 2;
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
gap: 30px;
|
||||
}
|
||||
</style>
|
@@ -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;
|
||||
|
234
src/views/Key/KeyApply.vue
Normal file
234
src/views/Key/KeyApply.vue
Normal file
@@ -0,0 +1,234 @@
|
||||
<template>
|
||||
<!-- 钥匙使用申请组件模板 -->
|
||||
<div class="p-4">
|
||||
<h2 class="text-xl font-bold text-blue-400 mb-4">钥匙使用申请</h2>
|
||||
|
||||
<!-- 钥匙信息展示区域 -->
|
||||
<el-form :model="formData" label-width="120px" class="mb-6">
|
||||
<el-form-item label="槽位信息">
|
||||
<el-input v-model="formData.keySlot" disabled></el-input>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="钥匙名称">
|
||||
<el-input v-model="formData.keyName" disabled></el-input>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="当前状态">
|
||||
<el-input v-model="formData.keyStatus" disabled></el-input>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="申请人">
|
||||
<el-input v-model="formData.applicant" disabled></el-input>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="申请事由" required>
|
||||
<el-input
|
||||
v-model="formData.reason"
|
||||
type="textarea"
|
||||
:rows="4"
|
||||
placeholder="请详细填写钥匙使用申请的具体事由..."
|
||||
></el-input>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<!-- 操作按钮区域 -->
|
||||
<div class="flex justify-end gap-4">
|
||||
<el-button @click="handleCancel">取消</el-button>
|
||||
<el-button type="primary" @click="handleSubmit">提交申请</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
/**
|
||||
* 钥匙使用申请组件
|
||||
* 用于用户提交钥匙使用申请,显示钥匙信息并收集申请事由
|
||||
*/
|
||||
import { ref, onMounted, reactive, computed } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { useUserStore } from '@/stores/user'
|
||||
|
||||
// 路由相关
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const userStore = useUserStore()
|
||||
|
||||
// 计算当前登录用户名称
|
||||
const currentUserName = computed(() => {
|
||||
if (userStore.userInfo && userStore.userInfo.name) {
|
||||
return userStore.userInfo.name
|
||||
}
|
||||
return '管理员' // 默认用户名
|
||||
})
|
||||
|
||||
// 表单数据
|
||||
const formData = reactive({
|
||||
keySlot: '', // 槽位信息
|
||||
keyName: '', // 钥匙名称
|
||||
keyStatus: '', // 当前状态
|
||||
applicant: '', // 申请人,将从用户状态中获取
|
||||
reason: '' // 申请事由
|
||||
})
|
||||
|
||||
/**
|
||||
* 组件挂载时初始化数据
|
||||
* 从路由参数中获取钥匙信息(如果有)
|
||||
*/
|
||||
onMounted(() => {
|
||||
// 设置当前登录用户为申请人
|
||||
formData.applicant = currentUserName.value
|
||||
console.log('当前登录用户:', formData.applicant)
|
||||
|
||||
// 从路由查询参数中获取钥匙信息
|
||||
console.log('路由查询参数:', route.query)
|
||||
const keyDataParam = route.query.keyData as string
|
||||
console.log('从路由查询参数获取的钥匙数据:', keyDataParam)
|
||||
if (keyDataParam) {
|
||||
try {
|
||||
const keyData = JSON.parse(keyDataParam)
|
||||
|
||||
// 直接使用提取好的钥匙信息
|
||||
formData.keySlot = keyData.keySlot || ''
|
||||
formData.keyName = keyData.keyName || ''
|
||||
formData.keyStatus = keyData.keyStatus || ''
|
||||
|
||||
// 如果直接获取失败,仍然保留从keyDatas中提取的逻辑作为后备
|
||||
if (!formData.keyName || !formData.keyStatus) {
|
||||
if (keyData.keyDatas && Array.isArray(keyData.keyDatas)) {
|
||||
// 提取钥匙名称
|
||||
if (!formData.keyName) {
|
||||
const nameData = keyData.keyDatas.find((item: any) => item.infotype === '钥匙名称:')
|
||||
if (nameData && nameData.status && nameData.status.length > 0) {
|
||||
formData.keyName = nameData.status[0].value
|
||||
}
|
||||
}
|
||||
|
||||
// 提取钥匙状态
|
||||
if (!formData.keyStatus) {
|
||||
const statusData = keyData.keyDatas.find((item: any) => item.infotype === '钥匙状态:')
|
||||
if (statusData && statusData.status && statusData.status.length > 0) {
|
||||
formData.keyStatus = statusData.status[0].value
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log('表单数据初始化完成:', formData)
|
||||
} catch (error) {
|
||||
console.error('解析钥匙数据失败:', error)
|
||||
ElMessage.error('获取钥匙信息失败,请重试')
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* 提交申请处理函数
|
||||
* 验证表单并提交数据
|
||||
*/
|
||||
const handleSubmit = () => {
|
||||
// 验证申请事由是否填写
|
||||
if (!formData.reason.trim()) {
|
||||
ElMessage.warning('请填写申请事由')
|
||||
return
|
||||
}
|
||||
|
||||
// 模拟提交申请
|
||||
console.log('提交申请数据:', formData)
|
||||
|
||||
// 显示成功提示
|
||||
ElMessage.success('钥匙使用申请已提交成功')
|
||||
|
||||
// 跳转回钥匙管理页面
|
||||
router.push('/Key/KeyManager')
|
||||
}
|
||||
|
||||
/**
|
||||
* 取消申请处理函数
|
||||
*/
|
||||
const handleCancel = () => {
|
||||
// 返回上一页或跳转回钥匙管理页面
|
||||
const fromParam = route.params.from as string
|
||||
if (fromParam === 'keyManager') {
|
||||
router.push('/Key/KeyManager')
|
||||
} else {
|
||||
router.back()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/**
|
||||
* 钥匙使用申请组件样式
|
||||
* 保持与系统整体风格一致,使用空军蓝配色和透明背景
|
||||
*/
|
||||
/* 设置页面背景为透明 */
|
||||
.p-4 {
|
||||
background-color: transparent !important;
|
||||
}
|
||||
|
||||
/* 表单样式 */
|
||||
:deep(.el-form) {
|
||||
background: rgba(12, 43, 77, 0.7) !important;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #68c9ff;
|
||||
backdrop-filter: blur(5px);
|
||||
}
|
||||
|
||||
/* 表单标签样式 */
|
||||
:deep(.el-form-item__label) {
|
||||
color: #68c9ff !important;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* 输入框样式 */
|
||||
:deep(.el-input__wrapper) {
|
||||
background: rgba(255, 255, 255, 0.05) !important;
|
||||
border-color: #68c9ff !important;
|
||||
}
|
||||
|
||||
:deep(.el-input__inner) {
|
||||
color: #b6dfff !important;
|
||||
background: transparent !important;
|
||||
}
|
||||
|
||||
:deep(.el-input.is-disabled .el-input__inner) {
|
||||
color: #88b6d9 !important;
|
||||
background: rgba(255, 255, 255, 0.03) !important;
|
||||
}
|
||||
|
||||
/* 标题样式 */
|
||||
.text-xl {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.font-bold {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.text-blue-400 {
|
||||
color: #68c9ff;
|
||||
}
|
||||
|
||||
.mb-4 {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.mb-6 {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
/* 按钮组样式 */
|
||||
.flex {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.justify-end {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.gap-4 {
|
||||
gap: 16px;
|
||||
}
|
||||
</style>
|
@@ -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,10 +30,11 @@
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
<!-- 卡片底部 - 操作按钮组 -->
|
||||
<template #footer>
|
||||
<div class="card-footer">
|
||||
<el-button-group>
|
||||
<el-button size="small" type="primary" :icon="Pointer">申请</el-button>
|
||||
<el-button size="small" type="primary" :icon="Pointer" @click="handleApply(k)">申请</el-button>
|
||||
<el-button size="small" type="success" :icon="Unlock">解锁</el-button>
|
||||
<el-button size="small" type="danger" :icon="Lock">锁定</el-button>
|
||||
</el-button-group>
|
||||
@@ -42,11 +47,26 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
/**
|
||||
* 钥匙管理组件脚本部分
|
||||
* 负责钥匙柜的选择、钥匙状态的展示和操作
|
||||
*/
|
||||
import { ref } from 'vue'
|
||||
import { Pointer, Unlock, Lock } from '@element-plus/icons-vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { Pointer, Unlock, Lock } from '@element-plus/icons-vue' // 导入操作按钮图标
|
||||
import { ElMessage } from 'element-plus'
|
||||
|
||||
const value = ref('')
|
||||
// 路由相关
|
||||
const router = useRouter()
|
||||
|
||||
/**
|
||||
* 状态定义
|
||||
*/
|
||||
const value = ref('') // 当前选择的钥匙柜ID
|
||||
|
||||
/**
|
||||
* 钥匙柜选项列表
|
||||
*/
|
||||
const options = [
|
||||
{
|
||||
value: '1',
|
||||
@@ -58,21 +78,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[] =
|
||||
[
|
||||
{
|
||||
@@ -176,19 +206,70 @@ const keyList: KeyDatas[] =
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
/**
|
||||
* 处理申请按钮点击事件
|
||||
* @param keyData 钥匙数据
|
||||
*/
|
||||
const handleApply = (keyData: any) => {
|
||||
// 检查钥匙状态是否允许申请
|
||||
const statusData = keyData.keyDatas.find((item: any) => item.infotype === '钥匙状态:')
|
||||
if (statusData && statusData.status.length > 0) {
|
||||
const statusValue = statusData.status[0].value
|
||||
if (statusValue.includes('离位')) {
|
||||
ElMessage.warning('该钥匙当前不在位,无法申请')
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 从keyData中提取所需的钥匙信息
|
||||
const extractedKeyInfo = {
|
||||
keySlot: keyData.keySlot || '',
|
||||
keyName: '',
|
||||
keyStatus: '',
|
||||
keyDatas: keyData.keyDatas
|
||||
}
|
||||
|
||||
// 提取钥匙名称
|
||||
const nameData = keyData.keyDatas.find((item: any) => item.infotype === '钥匙名称:')
|
||||
if (nameData && nameData.status && nameData.status.length > 0) {
|
||||
extractedKeyInfo.keyName = nameData.status[0].value
|
||||
}
|
||||
|
||||
// 提取钥匙状态
|
||||
if (statusData && statusData.status && statusData.status.length > 0) {
|
||||
extractedKeyInfo.keyStatus = statusData.status[0].value
|
||||
}
|
||||
|
||||
console.log('提取的钥匙信息:', extractedKeyInfo)
|
||||
|
||||
// 跳转到钥匙使用申请页面,并传递提取的钥匙数据
|
||||
router.push({
|
||||
path: '/Key/KeyApply',
|
||||
query: {
|
||||
keyData: JSON.stringify(extractedKeyInfo),
|
||||
from: 'keyManager'
|
||||
}
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 将KeyManager的背景色设置为全透明 */
|
||||
/**
|
||||
* 钥匙管理组件样式定义
|
||||
* 使用空军蓝为主色调,打造科技感界面
|
||||
*/
|
||||
/* 设置页面背景为透明 */
|
||||
:deep(.p-4) {
|
||||
background-color: transparent !important;
|
||||
}
|
||||
|
||||
/* 确保容器元素背景也是透明的 */
|
||||
/* 确保所有容器元素背景也是透明的 */
|
||||
div {
|
||||
background-color: transparent !important;
|
||||
}
|
||||
|
||||
/* 布局相关样式 */
|
||||
.el-row {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
@@ -208,7 +289,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 +313,7 @@ div {
|
||||
font-size: 14px !important;
|
||||
}
|
||||
|
||||
/* 卡片内容中的段落文本样式 */
|
||||
/* 卡片内容中的表格样式 */
|
||||
:deep(.el-card__body table tr) {
|
||||
color: #b6dfff !important;
|
||||
width: 100%;
|
||||
@@ -256,7 +337,7 @@ div {
|
||||
margin-top: 0 !important;
|
||||
}
|
||||
|
||||
/* 调整选择器样式以适应整体色调 */
|
||||
/* 钥匙柜选择器样式 */
|
||||
:deep(.el-select) {
|
||||
background: rgba(12, 43, 77, 0.7) !important;
|
||||
border: 1px solid #68c9ff !important;
|
||||
|
382
src/views/Key/KeyRecord.vue
Normal file
382
src/views/Key/KeyRecord.vue
Normal file
@@ -0,0 +1,382 @@
|
||||
<template>
|
||||
<!-- 钥匙取用记录组件模板 -->
|
||||
<div class="p-4">
|
||||
<h2 class="text-xl font-bold text-blue-400 mb-4">钥匙取用记录</h2>
|
||||
|
||||
<!-- 筛选条件区域 -->
|
||||
<div class="filter-container mb-4">
|
||||
<el-form :inline="true" :model="searchForm" class="mb-4">
|
||||
<el-form-item label="钥匙柜:">
|
||||
<el-select v-model="searchForm.cabinetId" placeholder="请选择钥匙柜" style="width: 180px">
|
||||
<el-option v-for="item in cabinetOptions" :key="item.value" :label="item.label" :value="item.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="钥匙名称:">
|
||||
<el-input v-model="searchForm.keyName" placeholder="请输入钥匙名称" style="width: 180px" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="申请人:">
|
||||
<el-input v-model="searchForm.applicant" placeholder="请输入申请人" style="width: 180px" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="日期范围:">
|
||||
<el-date-picker
|
||||
v-model="searchForm.dateRange"
|
||||
type="daterange"
|
||||
range-separator="至"
|
||||
start-placeholder="开始日期"
|
||||
end-placeholder="结束日期"
|
||||
style="width: 280px"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="handleSearch">查询</el-button>
|
||||
<el-button @click="handleReset">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
|
||||
<!-- 记录表格区域 -->
|
||||
<el-table :data="recordList" style="width: 100%" border>
|
||||
<el-table-column prop="id" label="记录ID" width="80" />
|
||||
<el-table-column prop="cabinetName" label="钥匙柜" width="150" />
|
||||
<el-table-column prop="keySlot" label="槽位" width="100" />
|
||||
<el-table-column prop="keyName" label="钥匙名称" width="180" />
|
||||
<el-table-column prop="keyType" label="钥匙类型" width="120" />
|
||||
<el-table-column prop="applicant" label="申请人" width="120" />
|
||||
<el-table-column prop="applyTime" label="申请时间" width="180" />
|
||||
<el-table-column prop="applyReason" label="申请事由" min-width="200" show-overflow-tooltip />
|
||||
<el-table-column prop="status" label="状态" width="100">
|
||||
<template #default="scope">
|
||||
<el-tag :type="getStatusType(scope.row.status)">{{ scope.row.status }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="operation" label="操作人" width="120" />
|
||||
<el-table-column prop="operationTime" label="操作时间" width="180" />
|
||||
</el-table>
|
||||
|
||||
<!-- 分页组件 -->
|
||||
<div class="pagination-container mt-4">
|
||||
<el-pagination
|
||||
v-model:current-page="pagination.currentPage"
|
||||
v-model:page-size="pagination.pageSize"
|
||||
:page-sizes="[10, 20, 50, 100]"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
:total="pagination.total"
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
/**
|
||||
* 钥匙取用记录组件
|
||||
* 用于展示和查询钥匙的使用申请和取用记录
|
||||
*/
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
|
||||
// 搜索表单数据
|
||||
const searchForm = reactive({
|
||||
cabinetId: '', // 钥匙柜ID
|
||||
keyName: '', // 钥匙名称
|
||||
applicant: '', // 申请人
|
||||
dateRange: null // 日期范围
|
||||
})
|
||||
|
||||
// 钥匙柜选项列表
|
||||
const cabinetOptions = ref([
|
||||
{ value: '1', label: '指挥中心钥匙柜1#' },
|
||||
{ value: '2', label: '指挥中心钥匙柜2#' }
|
||||
])
|
||||
|
||||
// 记录列表数据
|
||||
const recordList = ref([])
|
||||
|
||||
// 分页信息
|
||||
const pagination = reactive({
|
||||
currentPage: 1, // 当前页码
|
||||
pageSize: 10, // 每页条数
|
||||
total: 0 // 总记录数
|
||||
})
|
||||
|
||||
/**
|
||||
* 获取状态对应的标签类型
|
||||
* @param status 状态字符串
|
||||
* @returns 标签类型
|
||||
*/
|
||||
const getStatusType = (status: string): string => {
|
||||
const statusMap: Record<string, string> = {
|
||||
'申请中': 'info',
|
||||
'已批准': 'primary',
|
||||
'已领取': 'success',
|
||||
'已归还': 'warning',
|
||||
'已拒绝': 'danger'
|
||||
}
|
||||
return statusMap[status] || 'default'
|
||||
}
|
||||
|
||||
/**
|
||||
* 模拟获取记录数据
|
||||
*/
|
||||
const fetchRecords = () => {
|
||||
// 这里应该是实际的API调用,现在使用模拟数据
|
||||
const mockData = [
|
||||
{
|
||||
id: '1',
|
||||
cabinetName: '指挥中心钥匙柜1#',
|
||||
keySlot: '1号槽位',
|
||||
keyName: '苏A54321',
|
||||
keyType: '车辆钥匙',
|
||||
applicant: '值班员1',
|
||||
applyTime: '2025-10-10 09:30:00',
|
||||
applyReason: '外出执行任务需要用车',
|
||||
status: '已归还',
|
||||
operation: '管理员1',
|
||||
operationTime: '2025-10-10 11:30:00'
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
cabinetName: '指挥中心钥匙柜1#',
|
||||
keySlot: '3号槽位',
|
||||
keyName: '1#仓库1号门1号钥匙',
|
||||
keyType: '仓库钥匙',
|
||||
applicant: '值班员1',
|
||||
applyTime: '2025-10-12 10:00:00',
|
||||
applyReason: '需要进入仓库领取物资',
|
||||
status: '已领取',
|
||||
operation: '管理员1',
|
||||
operationTime: '2025-10-12 10:05:00'
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
cabinetName: '指挥中心钥匙柜2#',
|
||||
keySlot: '2号槽位',
|
||||
keyName: '苏A123456',
|
||||
keyType: '车辆钥匙',
|
||||
applicant: '值班员2',
|
||||
applyTime: '2025-10-12 14:20:00',
|
||||
applyReason: '外出巡逻需要用车',
|
||||
status: '申请中',
|
||||
operation: '',
|
||||
operationTime: ''
|
||||
},
|
||||
{
|
||||
id: '4',
|
||||
cabinetName: '指挥中心钥匙柜1#',
|
||||
keySlot: '3号槽位',
|
||||
keyName: '1#仓库1号门2号钥匙',
|
||||
keyType: '仓库钥匙',
|
||||
applicant: '值班员1',
|
||||
applyTime: '2025-10-12 09:15:00',
|
||||
applyReason: '需要进入仓库盘点物资',
|
||||
status: '已批准',
|
||||
operation: '管理员1',
|
||||
operationTime: '2025-10-12 09:20:00'
|
||||
}
|
||||
]
|
||||
|
||||
// 根据搜索条件过滤数据
|
||||
let filteredData = [...mockData]
|
||||
|
||||
if (searchForm.cabinetId) {
|
||||
filteredData = filteredData.filter(item => {
|
||||
const cabinetMap: Record<string, string> = {
|
||||
'1': '指挥中心钥匙柜1#',
|
||||
'2': '指挥中心钥匙柜2#'
|
||||
}
|
||||
return item.cabinetName === cabinetMap[searchForm.cabinetId]
|
||||
})
|
||||
}
|
||||
|
||||
if (searchForm.keyName) {
|
||||
filteredData = filteredData.filter(item =>
|
||||
item.keyName.includes(searchForm.keyName)
|
||||
)
|
||||
}
|
||||
|
||||
if (searchForm.applicant) {
|
||||
filteredData = filteredData.filter(item =>
|
||||
item.applicant.includes(searchForm.applicant)
|
||||
)
|
||||
}
|
||||
|
||||
// 更新分页信息
|
||||
pagination.total = filteredData.length
|
||||
|
||||
// 根据分页参数截取数据
|
||||
const start = (pagination.currentPage - 1) * pagination.pageSize
|
||||
const end = start + pagination.pageSize
|
||||
recordList.value = filteredData.slice(start, end)
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询按钮点击事件处理
|
||||
*/
|
||||
const handleSearch = () => {
|
||||
pagination.currentPage = 1
|
||||
fetchRecords()
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置按钮点击事件处理
|
||||
*/
|
||||
const handleReset = () => {
|
||||
// 重置搜索表单
|
||||
Object.assign(searchForm, {
|
||||
cabinetId: '',
|
||||
keyName: '',
|
||||
applicant: '',
|
||||
dateRange: null
|
||||
})
|
||||
|
||||
// 重置分页并重新获取数据
|
||||
pagination.currentPage = 1
|
||||
fetchRecords()
|
||||
}
|
||||
|
||||
/**
|
||||
* 每页条数变化处理
|
||||
* @param size 新的每页条数
|
||||
*/
|
||||
const handleSizeChange = (size: number) => {
|
||||
pagination.pageSize = size
|
||||
fetchRecords()
|
||||
}
|
||||
|
||||
/**
|
||||
* 页码变化处理
|
||||
* @param current 新的页码
|
||||
*/
|
||||
const handleCurrentChange = (current: number) => {
|
||||
pagination.currentPage = current
|
||||
fetchRecords()
|
||||
}
|
||||
|
||||
/**
|
||||
* 组件挂载时初始化数据
|
||||
*/
|
||||
onMounted(() => {
|
||||
fetchRecords()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/**
|
||||
* 钥匙取用记录组件样式
|
||||
* 保持与系统整体风格一致,使用空军蓝配色和透明背景
|
||||
*/
|
||||
/* 设置页面背景为透明 */
|
||||
.p-4 {
|
||||
background-color: transparent !important;
|
||||
}
|
||||
|
||||
/* 筛选容器样式 */
|
||||
.filter-container {
|
||||
background: rgba(12, 43, 77, 0.7) !important;
|
||||
padding: 16px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #68c9ff;
|
||||
backdrop-filter: blur(5px);
|
||||
}
|
||||
|
||||
/* 表格样式 */
|
||||
:deep(.el-table) {
|
||||
background: transparent !important;
|
||||
border: 1px solid #68c9ff;
|
||||
backdrop-filter: blur(5px);
|
||||
}
|
||||
|
||||
:deep(.el-table tr){
|
||||
background: transparent !important;
|
||||
}
|
||||
|
||||
:deep(.el-table__header th) {
|
||||
background: rgba(12, 43, 77, 0.7) !important;
|
||||
border-bottom: 1px solid #68c9ff !important;
|
||||
color: #68c9ff !important;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
:deep(.el-table__row) {
|
||||
color: #b6dfff !important;
|
||||
border-bottom: 1px solid rgba(104, 201, 255, 0.3) !important;
|
||||
}
|
||||
|
||||
:deep(.el-table__row:hover > td) {
|
||||
background: rgba(104, 201, 255, 0.1) !important;
|
||||
}
|
||||
|
||||
:deep(.el-table__row.el-table__row--striped > td) {
|
||||
background: rgba(255, 255, 255, 0.02) !important;
|
||||
}
|
||||
|
||||
/* 分页组件样式 */
|
||||
.pagination-container {
|
||||
background: rgba(12, 43, 77, 0.7) !important;
|
||||
padding: 16px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #68c9ff;
|
||||
backdrop-filter: blur(5px);
|
||||
}
|
||||
|
||||
:deep(.el-pagination.is-background .el-pager li) {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
color: #b6dfff;
|
||||
border: 1px solid #68c9ff;
|
||||
}
|
||||
|
||||
:deep(.el-pagination.is-background .el-pager li:hover:not(.disabled)) {
|
||||
background: rgba(104, 201, 255, 0.2);
|
||||
color: #68c9ff;
|
||||
}
|
||||
|
||||
:deep(.el-pagination.is-background .el-pager li.active) {
|
||||
background: #68c9ff;
|
||||
color: #0c2b4d;
|
||||
border-color: #68c9ff;
|
||||
}
|
||||
|
||||
/* 输入框和下拉框样式 */
|
||||
:deep(.el-input__wrapper) {
|
||||
background: rgba(255, 255, 255, 0.05) !important;
|
||||
border-color: #68c9ff !important;
|
||||
}
|
||||
|
||||
:deep(.el-input__inner) {
|
||||
color: #b6dfff !important;
|
||||
background: transparent !important;
|
||||
}
|
||||
|
||||
:deep(.el-select) {
|
||||
background: rgba(255, 255, 255, 0.05) !important;
|
||||
}
|
||||
|
||||
:deep(.el-date-editor) {
|
||||
background: rgba(255, 255, 255, 0.05) !important;
|
||||
}
|
||||
|
||||
/* 标题样式 */
|
||||
.text-xl {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.font-bold {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.text-blue-400 {
|
||||
color: #68c9ff;
|
||||
}
|
||||
|
||||
.mb-4 {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.mt-4 {
|
||||
margin-top: 16px;
|
||||
}
|
||||
</style>
|
@@ -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>
|
||||
@@ -25,17 +26,26 @@
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
<!-- 全屏按钮 -->
|
||||
<el-button
|
||||
icon="FullScreen"
|
||||
@click="toggleFullScreen"
|
||||
circle
|
||||
title="切换全屏"
|
||||
:icon="isFullScreen ? 'CloseFullScreen' : 'FullScreen'"
|
||||
style="background-color: rgba(104, 201, 255, 0.3); color: #68c9ff; border: 1px solid #68c9ff;"
|
||||
/>
|
||||
</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 +60,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 +115,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,14 +126,13 @@
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Drawer组件 - 用于显示页面内容 -->
|
||||
<!-- Drawer组件 - 从右侧滑出,用于显示具体功能页面内容 -->
|
||||
<el-drawer
|
||||
v-model="drawerVisible"
|
||||
:title="currentMenu?.name || '页面内容'"
|
||||
size="45%"
|
||||
size="800px"
|
||||
direction="rtl"
|
||||
:with-header="true"
|
||||
:modal="false"
|
||||
close-on-click-modal="false"
|
||||
close-on-press-escape="true"
|
||||
header-class="drawer-header-class"
|
||||
@@ -136,23 +145,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, onUnmounted } 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,47 +174,40 @@ const menuList = ref([
|
||||
path: '/',
|
||||
icon: 'House'
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
{ id: '2',
|
||||
name: '钥匙管理',
|
||||
icon: 'Key',
|
||||
children: [
|
||||
{ id: '2-1', name: '钥匙信息管理', path: '/Key/KeyManager' },
|
||||
{ id: '2-2', name: '钥匙使用申请', path: '/warehouse/add' },
|
||||
{ id: '2-3', name: '钥匙取用记录', path: '/warehouse/stats' }
|
||||
{ id: '2-2', name: '钥匙取用记录', path: '/Key/KeyRecord' }
|
||||
]
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
{ id: '3',
|
||||
name: '巡检管理',
|
||||
icon: 'View',
|
||||
children: [
|
||||
{ id: '3-1', name: '巡检路径管理', path: '/inventory/overview' },
|
||||
{ id: '3-2', name: '巡检排班管理', path: '/inventory/detail' },
|
||||
{ id: '3-3', name: '巡检记录', path: '/inventory/alerts' }
|
||||
{ id: '3-1', name: '巡检路径管理', path: '/Path/PathManager' },
|
||||
{ id: '3-2', name: '巡检排班管理', path: '/Path/PathSchedule' },
|
||||
{ id: '3-3', name: '巡检记录', path: '/Path/PathLog' }
|
||||
]
|
||||
},
|
||||
{
|
||||
id: '4',
|
||||
{ id: '4',
|
||||
name: '车辆管理',
|
||||
icon: 'Van',
|
||||
children: [
|
||||
{ id: '4-1', name: '车辆信息管理', path: '/inventory/overview' },
|
||||
{ id: '4-2', name: '车辆使用申请', path: '/inventory/detail' },
|
||||
{ id: '4-3', name: '车辆出入库记录', path: '/inventory/alerts' },
|
||||
{ id: '4-4', name: '车辆出入营区记录', path: '/inventory/alerts' }
|
||||
{ id: '4-1', name: '车辆信息管理', path: '/Car/CarManager' },
|
||||
{ id: '4-2', name: '车辆出入库记录', path: '/Car/CarUseLog' },
|
||||
{ id: '4-3', name: '车辆出入营区记录', path: '/Car/CarEntryExitLog' }
|
||||
]
|
||||
},
|
||||
{
|
||||
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 +215,7 @@ const menuList = ref([
|
||||
{ id: '6-2', name: '安全提示', path: '/inventory/detail' }
|
||||
]
|
||||
},
|
||||
{
|
||||
id: '7',
|
||||
{ id: '7',
|
||||
name: '访客管理',
|
||||
icon: 'User',
|
||||
children: [
|
||||
@@ -217,8 +223,7 @@ const menuList = ref([
|
||||
{ id: '7-2', name: '来访车辆管理', path: '/inventory/detail' }
|
||||
]
|
||||
},
|
||||
{
|
||||
id: '8',
|
||||
{ id: '8',
|
||||
name: '营区管理',
|
||||
icon: 'MapLocation',
|
||||
children: [
|
||||
@@ -226,8 +231,7 @@ const menuList = ref([
|
||||
{ id: '8-2', name: '人员区域管理', path: '/inventory/detail' }
|
||||
]
|
||||
},
|
||||
{
|
||||
id: '9',
|
||||
{ id: '9',
|
||||
name: '无人机管理',
|
||||
icon: 'Promotion',
|
||||
children: [
|
||||
@@ -236,53 +240,150 @@ const menuList = ref([
|
||||
}
|
||||
])
|
||||
|
||||
/**
|
||||
* 用户信息响应式数据
|
||||
* 用于显示当前登录用户的信息
|
||||
*/
|
||||
const userInfo = ref({
|
||||
name: '管理员'
|
||||
name: '管理员' // 默认用户名称
|
||||
})
|
||||
|
||||
// 切换侧边栏折叠状态
|
||||
/**
|
||||
* 全屏状态响应式数据
|
||||
* 用于跟踪应用的全屏状态
|
||||
*/
|
||||
const isFullScreen = ref(false)
|
||||
|
||||
/**
|
||||
* 切换侧边栏折叠状态
|
||||
* 控制侧边栏的展开和收起
|
||||
*/
|
||||
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') // 跳转到登录页面
|
||||
}
|
||||
|
||||
/**
|
||||
* 切换全屏功能
|
||||
* 控制应用在全屏和非全屏状态之间切换
|
||||
*/
|
||||
const toggleFullScreen = () => {
|
||||
// 如果当前是全屏状态,则退出全屏
|
||||
if (isFullScreen.value) {
|
||||
if (document.exitFullscreen) {
|
||||
document.exitFullscreen()
|
||||
} else if (document.mozCancelFullScreen) {
|
||||
document.mozCancelFullScreen()
|
||||
} else if (document.webkitExitFullscreen) {
|
||||
document.webkitExitFullscreen()
|
||||
} else if (document.msExitFullscreen) {
|
||||
document.msExitFullscreen()
|
||||
}
|
||||
}
|
||||
// 如果当前是非全屏状态,则进入全屏
|
||||
else {
|
||||
const docEl = document.documentElement
|
||||
if (docEl.requestFullscreen) {
|
||||
docEl.requestFullscreen()
|
||||
} else if (docEl.mozRequestFullScreen) {
|
||||
docEl.mozRequestFullScreen()
|
||||
} else if (docEl.webkitRequestFullscreen) {
|
||||
docEl.webkitRequestFullscreen()
|
||||
} else if (docEl.msRequestFullscreen) {
|
||||
docEl.msRequestFullscreen()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理全屏变化事件
|
||||
* 更新isFullScreen状态
|
||||
*/
|
||||
const handleFullScreenChange = () => {
|
||||
isFullScreen.value = !!(document.fullscreenElement ||
|
||||
document.mozFullScreenElement ||
|
||||
document.webkitFullscreenElement ||
|
||||
document.msFullscreenElement)
|
||||
}
|
||||
|
||||
/**
|
||||
* 组件生命周期钩子:挂载时执行
|
||||
* 初始化用户信息、菜单列表并添加全屏事件监听
|
||||
*/
|
||||
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 // 更新菜单列表
|
||||
}
|
||||
})
|
||||
|
||||
// 添加全屏变化事件监听
|
||||
document.addEventListener('fullscreenchange', handleFullScreenChange)
|
||||
document.addEventListener('webkitfullscreenchange', handleFullScreenChange)
|
||||
document.addEventListener('mozfullscreenchange', handleFullScreenChange)
|
||||
document.addEventListener('MSFullscreenChange', handleFullScreenChange)
|
||||
})
|
||||
|
||||
/**
|
||||
* 组件生命周期钩子:卸载时执行
|
||||
* 移除全屏事件监听
|
||||
*/
|
||||
onUnmounted(() => {
|
||||
// 移除全屏变化事件监听
|
||||
document.removeEventListener('fullscreenchange', handleFullScreenChange)
|
||||
document.removeEventListener('webkitfullscreenchange', handleFullScreenChange)
|
||||
document.removeEventListener('mozfullscreenchange', handleFullScreenChange)
|
||||
document.removeEventListener('MSFullscreenChange', handleFullScreenChange)
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/**
|
||||
* 布局组件样式定义
|
||||
* 包含菜单、顶栏、Drawer等自定义样式
|
||||
*/
|
||||
/* 布局样式 */
|
||||
.el-menu {
|
||||
border-right: none;
|
||||
@@ -293,7 +394,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,14 +1,29 @@
|
||||
<script setup>
|
||||
import { onMounted, ref, computed, provide } from "vue"
|
||||
import { onMounted, ref, computed, provide, useTemplateRef } from "vue"
|
||||
import Fence from "../components/Fence.vue"
|
||||
import Filter from "../components/Filter.vue"
|
||||
import Sky from "../components/Sky.vue"
|
||||
import DisplayColor from "../components/DisplayColor.vue"
|
||||
import DisplayRouteLine from "../components/DisplayRouteLine.vue"
|
||||
|
||||
// 在Map.vue中添加props定义
|
||||
const props = defineProps({
|
||||
elId: {
|
||||
type: String,
|
||||
default: 'mapContainer'
|
||||
}
|
||||
})
|
||||
|
||||
const { VgoMap } = window
|
||||
let mapId = '1958120048849719296'
|
||||
let mapId = "1977947221534052352"
|
||||
const isLoaded = ref(false)
|
||||
|
||||
const map = ref()
|
||||
const polygonDataAll = computed(() => {
|
||||
const routeLineRef = useTemplateRef('routeLineRef')
|
||||
|
||||
provide("map", computed(() => map.value))
|
||||
provide('routeLineRef', computed(() => routeLineRef.value))
|
||||
provide('polygonDataAll', computed(() => {
|
||||
const outDoor = map.value?.mapData?.polygonData ?? []
|
||||
|
||||
const inDoor = map?.mapData?.build?.reduce((result, build) => {
|
||||
@@ -19,18 +34,16 @@ const polygonDataAll = computed(() => {
|
||||
}, []) ?? []
|
||||
|
||||
return [...outDoor, ...inDoor]
|
||||
})
|
||||
|
||||
provide("map", computed(() => map.value))
|
||||
provide('polygonDataAll', polygonDataAll)
|
||||
}))
|
||||
|
||||
onMounted(() => {
|
||||
map.value = new VgoMap.Map({
|
||||
el: "mapContainer",
|
||||
el: props.elId, // 使用传入的elId,
|
||||
id: mapId,
|
||||
})
|
||||
|
||||
map.value.on("loaded", () => {
|
||||
window.$map = map.value
|
||||
isLoaded.value = true
|
||||
})
|
||||
})
|
||||
@@ -38,23 +51,40 @@ onMounted(() => {
|
||||
|
||||
<template>
|
||||
<div class="wrapper">
|
||||
<div id="mapContainer"></div>
|
||||
<div :id="elId"></div>
|
||||
<div v-if="isLoaded" class="ui">
|
||||
<Fence/>
|
||||
<!-- <Fence/> -->
|
||||
<DisplayRouteLine ref="routeLineRef"/>
|
||||
<!-- <DisplayColor/> -->
|
||||
<Sky/>
|
||||
<Filter/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
<style lang="css">
|
||||
html,
|
||||
body,
|
||||
.app,
|
||||
.wrapper {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
#mapContainer {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
</style>
|
||||
|
||||
.ui {
|
||||
position: absolute;
|
||||
right: 50px;
|
||||
bottom: 50px;
|
||||
z-index: 2;
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
gap: 30px;
|
||||
}
|
||||
</style>
|
||||
|
607
src/views/Path/PathEditor.vue
Normal file
607
src/views/Path/PathEditor.vue
Normal file
@@ -0,0 +1,607 @@
|
||||
<template>
|
||||
<!-- 巡检路径编辑组件 -->
|
||||
<el-dialog
|
||||
id="path-editor-root"
|
||||
v-model="dialogVisible"
|
||||
:title="isEdit ? '编辑巡检路径' : '新增巡检路径'"
|
||||
width="90%"
|
||||
max-width="1200px"
|
||||
destroy-on-close
|
||||
@closed="handleClose"
|
||||
>
|
||||
<!-- 表单容器 -->
|
||||
<el-form
|
||||
ref="pathFormRef"
|
||||
:model="pathForm"
|
||||
label-width="100px"
|
||||
:rules="formRules"
|
||||
>
|
||||
<!-- 基本信息区域 -->
|
||||
<div class="mb-6">
|
||||
<h3 class="text-lg font-medium text-blue-300 mb-4">基本信息</h3>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<el-form-item label="路径名称" prop="name">
|
||||
<el-input
|
||||
v-model="pathForm.name"
|
||||
placeholder="请输入路径名称"
|
||||
maxlength="50"
|
||||
show-word-limit
|
||||
/>
|
||||
</el-form-item>
|
||||
</div>
|
||||
<el-form-item label="路径说明" prop="description">
|
||||
<el-input
|
||||
v-model="pathForm.description"
|
||||
type="textarea"
|
||||
rows="3"
|
||||
placeholder="请输入路径说明"
|
||||
maxlength="200"
|
||||
show-word-limit
|
||||
/>
|
||||
</el-form-item>
|
||||
</div>
|
||||
|
||||
<!-- 地图展示区域 -->
|
||||
<!--建立 一个全局变量用于存放用户点击的设备名称列表,同步到编辑框的点位设备列表中-->
|
||||
<div class="mb-6">
|
||||
<h3 class="text-lg font-medium text-blue-300 mb-4">路径地图</h3>
|
||||
<div class="border border-blue-400 rounded-lg overflow-hidden relative">
|
||||
<!-- 地图容器,设置固定高度 -->
|
||||
<div class="path-map-container">
|
||||
<EditMap el-id="etsfs"/>
|
||||
</div>
|
||||
<!-- 地图操作提示 -->
|
||||
<div class="map-tip">
|
||||
<el-tooltip content="地图已加载,选择路径点后将显示路径连线" placement="top">
|
||||
<el-button size="small" type="primary" plain>
|
||||
<el-icon><HelpFilled /></el-icon> 地图使用提示
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 路径点表格区域 -->
|
||||
<div>
|
||||
<div class="flex justify-between items-center mb-4">
|
||||
<h3 class="text-lg font-medium text-blue-300">路径点配置</h3>
|
||||
<el-button type="primary" @click="handleAddPathPoint" icon="Plus">新增路径点</el-button>
|
||||
</div>
|
||||
|
||||
<el-table
|
||||
v-loading="loading"
|
||||
:data="pathForm.points"
|
||||
style="width: 100%"
|
||||
@sort-change="handleSortChange"
|
||||
>
|
||||
<el-table-column prop="index" label="序号" width="80" align="center" sortable="custom" />
|
||||
<el-table-column prop="deviceName" label="路径点设备" min-width="200" align="center">
|
||||
<template #default="scope">
|
||||
<el-select
|
||||
v-model="scope.row.deviceName"
|
||||
placeholder="请输入设备名称"
|
||||
style="width: 100%"
|
||||
@change="(value) => handleDeviceSelect(value, scope.row)"
|
||||
>
|
||||
<el-option
|
||||
v-for="device in deviceList"
|
||||
:key="device.value"
|
||||
:label="device.label"
|
||||
:value="device.value"
|
||||
/>
|
||||
</el-select>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="modelId" label="地图模型ID" min-width="150" align="center" />
|
||||
<el-table-column label="操作" width="120" align="center">
|
||||
<template #default="scope">
|
||||
<div class="flex gap-2 justify-center">
|
||||
<el-button
|
||||
size="small"
|
||||
@click="handleMoveUp(scope.row, scope.$index)"
|
||||
:disabled="scope.$index === 0"
|
||||
icon="ArrowUp"
|
||||
>上移</el-button>
|
||||
<el-button
|
||||
size="small"
|
||||
@click="handleMoveDown(scope.row, scope.$index)"
|
||||
:disabled="scope.$index === pathForm.points.length - 1"
|
||||
icon="ArrowDown"
|
||||
>下移</el-button>
|
||||
<el-button
|
||||
size="small"
|
||||
type="danger"
|
||||
@click="handleDeletePoint(scope.$index)"
|
||||
icon="Delete"
|
||||
>删除</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
</el-form>
|
||||
|
||||
<!-- 对话框底部按钮 -->
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="dialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="handleSave">保存</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
/**
|
||||
* 巡检路径编辑组件
|
||||
* 用于新增和编辑巡检路径信息,包含地图展示和路径点配置
|
||||
*/
|
||||
// 导入inject用于从Map组件获取map实例
|
||||
import { ref, reactive, computed, onMounted, watch, inject } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import EditMap from '../EditMap.vue' // 导入地图组件
|
||||
import { useDeviceStore } from '@/stores/device' // 导入设备store
|
||||
import { usePathStore } from '@/stores/pathStore' // 导入路径store
|
||||
import { useNavi } from '@/hooks/useNavi' // 导入导航hook
|
||||
|
||||
// Props定义
|
||||
const props = defineProps({
|
||||
visible: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
pathData: {
|
||||
type: Object,
|
||||
default: null
|
||||
}
|
||||
})
|
||||
|
||||
// Emits定义
|
||||
const emit = defineEmits(['update:visible', 'save'])
|
||||
|
||||
// 响应式数据
|
||||
const dialogVisible = computed({
|
||||
get: () => props.visible,
|
||||
set: (value) => emit('update:visible', value)
|
||||
})
|
||||
|
||||
const pathFormRef = ref()
|
||||
const loading = ref(false)
|
||||
|
||||
// 表单数据
|
||||
const pathForm = reactive({
|
||||
id: '',
|
||||
name: '',
|
||||
description: '',
|
||||
points: []
|
||||
})
|
||||
|
||||
// 表单验证规则
|
||||
const formRules = reactive({
|
||||
name: [
|
||||
{ required: true, message: '请输入路径名称', trigger: 'blur' },
|
||||
{ min: 2, max: 50, message: '路径名称长度应在2-50个字符之间', trigger: 'blur' }
|
||||
],
|
||||
description: [
|
||||
{ max: 200, message: '路径说明长度不能超过200个字符', trigger: 'blur' }
|
||||
]
|
||||
})
|
||||
|
||||
// 获取设备store实例
|
||||
const deviceStore = useDeviceStore()
|
||||
// 获取路径store实例
|
||||
const pathStore = usePathStore()
|
||||
|
||||
// 使用computed从store获取设备列表选项
|
||||
const deviceList = computed(() => {
|
||||
return deviceStore.getSelectOptions()
|
||||
})
|
||||
|
||||
// 计算属性
|
||||
const isEdit = computed(() => !!props.pathData)
|
||||
|
||||
/**
|
||||
* 重置表单
|
||||
*/
|
||||
const resetForm = () => {
|
||||
pathForm.id = ''
|
||||
pathForm.name = ''
|
||||
pathForm.description = ''
|
||||
pathForm.points = []
|
||||
|
||||
if (pathFormRef.value) {
|
||||
pathFormRef.value.resetFields()
|
||||
}
|
||||
}
|
||||
|
||||
// 监听对话框可见性变化
|
||||
watch(() => props.visible, (isVisible) => {
|
||||
if (isVisible) {
|
||||
// 对话框打开时,清空pathStore中的pathPoints数组
|
||||
pathStore.clearPathPoints()
|
||||
|
||||
// 判断是否为编辑状态
|
||||
if (isEdit.value && pathForm.points.length > 0) {
|
||||
// 编辑状态:将pathForm.points同步到pathStore的pathPoints数组
|
||||
const pathPointsData = pathForm.points.map(point => ({
|
||||
modelId: point.modelId,
|
||||
deviceName: point.deviceName
|
||||
}))
|
||||
// 直接设置pathStore的pathPoints数组
|
||||
pathStore.pathPoints = pathPointsData
|
||||
}
|
||||
// 新增状态:保持pathStore中的pathPoints数组为空(已通过clearPathPoints实现)
|
||||
}
|
||||
}, { immediate: true })
|
||||
|
||||
// 监听pathStore中pathPoints数组的变更,实现从全局到局部的同步
|
||||
watch(() => pathStore.pathPoints, (newPathPoints) => {
|
||||
if (newPathPoints && newPathPoints.length > 0) {
|
||||
// 确保不重复同步(避免循环更新)
|
||||
const currentModelIds = pathForm.points.map(p => p.modelId)
|
||||
const newModelIds = newPathPoints.map(p => p.modelId)
|
||||
|
||||
// 检查是否有变化需要同步
|
||||
const hasChanges = currentModelIds.length !== newModelIds.length ||
|
||||
!currentModelIds.every((id, index) => id === newModelIds[index])
|
||||
|
||||
if (hasChanges) {
|
||||
// 将全局pathPoints同步到表单的pathPoints中
|
||||
const synchronizedPoints = newPathPoints.map((point, index) => ({
|
||||
id: `${Date.now()}-${index}`, // 生成新的唯一ID
|
||||
index: index + 1, // 序号从1开始
|
||||
modelId: point.modelId,
|
||||
deviceName: point.deviceName
|
||||
}))
|
||||
|
||||
pathForm.points = synchronizedPoints
|
||||
}
|
||||
}
|
||||
}, { deep: true })
|
||||
|
||||
// 监听props变化
|
||||
watch(() => props.pathData, (newVal) => {
|
||||
if (newVal) {
|
||||
// 编辑模式:填充表单数据
|
||||
pathForm.id = newVal.id || ''
|
||||
pathForm.name = newVal.name || ''
|
||||
pathForm.description = newVal.description || ''
|
||||
pathForm.points = newVal.points ? JSON.parse(JSON.stringify(newVal.points)) : []
|
||||
|
||||
// 如果对话框已经可见且是编辑状态,同步到pathStore
|
||||
if (props.visible) {
|
||||
const pathPointsData = pathForm.points.map(point => ({
|
||||
modelId: point.modelId,
|
||||
deviceName: point.deviceName
|
||||
}))
|
||||
pathStore.pathPoints = pathPointsData
|
||||
}
|
||||
} else {
|
||||
// 新增模式:重置表单
|
||||
resetForm()
|
||||
}
|
||||
}, { immediate: true })
|
||||
|
||||
/**
|
||||
* 处理对话框关闭
|
||||
*/
|
||||
const handleClose = () => {
|
||||
resetForm()
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增路径点
|
||||
*/
|
||||
const handleAddPathPoint = () => {
|
||||
const newIndex = pathForm.points.length + 1
|
||||
pathForm.points.push({
|
||||
id: `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
|
||||
index: newIndex,
|
||||
deviceName: '',
|
||||
modelId: ''
|
||||
})
|
||||
|
||||
// 更新序号
|
||||
updatePointIndexes()
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除路径点
|
||||
*/
|
||||
const handleDeletePoint = (index) => {
|
||||
ElMessageBox.confirm(
|
||||
'确定要删除这个路径点吗?',
|
||||
'确认删除',
|
||||
{
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}
|
||||
).then(() => {
|
||||
pathForm.points.splice(index, 1)
|
||||
updatePointIndexes()
|
||||
ElMessage.success('路径点删除成功')
|
||||
}).catch(() => {
|
||||
// 用户取消删除
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 上移路径点
|
||||
*/
|
||||
const handleMoveUp = (row, index) => {
|
||||
if (index > 0) {
|
||||
// 交换位置
|
||||
const temp = pathForm.points[index]
|
||||
pathForm.points[index] = pathForm.points[index - 1]
|
||||
pathForm.points[index - 1] = temp
|
||||
|
||||
// 更新序号
|
||||
updatePointIndexes()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 下移路径点
|
||||
*/
|
||||
const handleMoveDown = (row, index) => {
|
||||
if (index < pathForm.points.length - 1) {
|
||||
// 交换位置
|
||||
const temp = pathForm.points[index]
|
||||
pathForm.points[index] = pathForm.points[index + 1]
|
||||
pathForm.points[index + 1] = temp
|
||||
|
||||
// 更新序号
|
||||
updatePointIndexes()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理设备选择变化
|
||||
*/
|
||||
const handleDeviceSelect = (value, row) => {
|
||||
// 查找对应的modelId
|
||||
const selectedDevice = deviceList.value.find(device => device.value === value)
|
||||
if (selectedDevice) {
|
||||
row.modelId = selectedDevice.value // 设备的value已经是modelId
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新路径点序号
|
||||
*/
|
||||
const updatePointIndexes = () => {
|
||||
pathForm.points.forEach((point, index) => {
|
||||
point.index = index + 1
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理排序变化
|
||||
*/
|
||||
const handleSortChange = ({ column, prop, order }) => {
|
||||
if (prop === 'index') {
|
||||
// 按序号排序
|
||||
pathForm.points.sort((a, b) => {
|
||||
return order === 'ascending' ? a.index - b.index : b.index - a.index
|
||||
})
|
||||
|
||||
// 更新序号
|
||||
updatePointIndexes()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证路径点
|
||||
*/
|
||||
const validatePoints = () => {
|
||||
// 检查是否有空设备名称
|
||||
for (let i = 0; i < pathForm.points.length; i++) {
|
||||
const point = pathForm.points[i]
|
||||
if (!point.deviceName || point.deviceName.trim() === '') {
|
||||
ElMessage.warning(`请为第${point.index}个路径点选择设备`)
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理保存
|
||||
*/
|
||||
const handleSave = async () => {
|
||||
// 验证表单
|
||||
if (!pathFormRef.value) {
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
await pathFormRef.value.validate()
|
||||
|
||||
// 验证路径点
|
||||
if (!validatePoints()) {
|
||||
return
|
||||
}
|
||||
|
||||
loading.value = true
|
||||
|
||||
// 准备保存数据
|
||||
const saveData = {
|
||||
...pathForm,
|
||||
pointCount: pathForm.points.length,
|
||||
updateTime: new Date().toLocaleString('zh-CN')
|
||||
}
|
||||
|
||||
if (!isEdit.value) {
|
||||
saveData.createTime = saveData.updateTime
|
||||
}
|
||||
|
||||
// 模拟API调用延迟
|
||||
await new Promise(resolve => setTimeout(resolve, 500))
|
||||
|
||||
// 将路径点信息添加到导航系统
|
||||
console.log(map.value)
|
||||
console.log(pathForm.points)
|
||||
if (map.value && pathForm.points.length > 0) {
|
||||
// 先清空现有路径
|
||||
//stopNavi()
|
||||
// 按顺序添加所有路径点的modelId到导航系统
|
||||
pathForm.points.forEach(point => {
|
||||
if (point.modelId) {
|
||||
addPathId(point.modelId)
|
||||
}
|
||||
})
|
||||
// 如果路径点数量足够,可以自动启动导航
|
||||
if (pathForm.points.length >= 2) {
|
||||
// 这里可以根据需要决定是否自动启动导航
|
||||
// startNavi()
|
||||
}
|
||||
}
|
||||
|
||||
// 触发保存事件
|
||||
emit('save', saveData)
|
||||
|
||||
ElMessage.success('保存成功')
|
||||
dialogVisible.value = false
|
||||
} catch (error) {
|
||||
console.error('保存失败:', error)
|
||||
ElMessage.error('保存失败,请重试')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/**
|
||||
* 巡检路径编辑组件样式
|
||||
*/
|
||||
.dialog-footer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
/* 标题样式 */
|
||||
.text-lg {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.font-medium {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.text-blue-300 {
|
||||
color: #91d5ff;
|
||||
}
|
||||
|
||||
.text-blue-200 {
|
||||
color: #b6dfff;
|
||||
}
|
||||
|
||||
/* 间距样式 */
|
||||
.mb-4 {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.mb-6 {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
/* 网格布局 */
|
||||
.grid {
|
||||
display: grid;
|
||||
}
|
||||
|
||||
.grid-cols-1 {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.md\:grid-cols-2 {
|
||||
grid-template-columns: 1fr 1fr;
|
||||
}
|
||||
|
||||
.gap-4 {
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
/* 地图容器样式 */
|
||||
.path-map-container {
|
||||
height: 400px;
|
||||
width: 100%;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* 确保Map组件在容器内正确显示 */
|
||||
:deep(.path-map-container .wrapper) {
|
||||
width: 100% !important;
|
||||
height: 100% !important;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
:deep(.path-map-container #mapContainer) {
|
||||
width: 100% !important;
|
||||
height: 100% !important;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* 地图提示样式 */
|
||||
.map-tip {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
/* 表单样式 */
|
||||
:deep(.el-form) {
|
||||
background: transparent !important;
|
||||
}
|
||||
|
||||
:deep(.el-form-item__label) {
|
||||
color: #68c9ff !important;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* 输入框样式 */
|
||||
:deep(.el-input__wrapper) {
|
||||
background: rgba(255, 255, 255, 0.05) !important;
|
||||
border-color: #68c9ff !important;
|
||||
}
|
||||
|
||||
:deep(.el-input__inner) {
|
||||
color: #b6dfff !important;
|
||||
background: transparent !important;
|
||||
}
|
||||
|
||||
/* 表格样式 */
|
||||
:deep(.el-table) {
|
||||
background: rgba(12, 43, 77, 0.7) !important;
|
||||
border: 1px solid #68c9ff;
|
||||
backdrop-filter: blur(5px);
|
||||
}
|
||||
|
||||
:deep(.el-table__header-wrapper th) {
|
||||
background: rgba(20, 60, 110, 0.8) !important;
|
||||
color: #68c9ff !important;
|
||||
border-bottom: 1px solid #68c9ff !important;
|
||||
}
|
||||
|
||||
:deep(.el-table__body-wrapper tr) {
|
||||
background: transparent !important;
|
||||
}
|
||||
|
||||
:deep(.el-table__body-wrapper tr:nth-child(even)) {
|
||||
background: rgba(20, 60, 110, 0.3) !important;
|
||||
}
|
||||
|
||||
:deep(.el-table__body-wrapper tr:hover) {
|
||||
background: rgba(104, 201, 255, 0.2) !important;
|
||||
}
|
||||
|
||||
:deep(.el-table__body-wrapper td) {
|
||||
color: #b6dfff !important;
|
||||
border-bottom: 1px solid rgba(104, 201, 255, 0.3) !important;
|
||||
}
|
||||
</style>
|
562
src/views/Path/PathLog.vue
Normal file
562
src/views/Path/PathLog.vue
Normal file
@@ -0,0 +1,562 @@
|
||||
<template>
|
||||
<div class="p-4">
|
||||
<h2 class="text-xl font-bold text-blue-400 mb-4">巡检日志管理</h2>
|
||||
|
||||
<!-- 工具栏 -->
|
||||
<div class="flex justify-between items-center mb-4">
|
||||
<div class="flex gap-4">
|
||||
<el-button type="primary" @click="handleAddLog" icon="Plus">新增日志</el-button>
|
||||
<el-button @click="handleRefresh" icon="Refresh">刷新</el-button>
|
||||
</div>
|
||||
|
||||
<!-- 搜索框 -->
|
||||
<el-input
|
||||
v-model="searchKeyword"
|
||||
placeholder="搜索日志信息"
|
||||
prefix-icon="Search"
|
||||
style="width: 240px"
|
||||
@input="handleSearch"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 日志列表表格 -->
|
||||
<el-table
|
||||
v-loading="loading"
|
||||
:data="logList"
|
||||
style="width: 100%"
|
||||
@row-click="handleRowClick"
|
||||
@selection-change="handleSelectionChange"
|
||||
>
|
||||
<el-table-column type="selection" width="55" />
|
||||
<el-table-column prop="CreateDate" label="记录日期时间" width="200" align="center" />
|
||||
<el-table-column prop="PersonNames" label="巡检人员" min-width="200" align="center">
|
||||
<template #default="scope">
|
||||
<div v-for="name in scope.row.PersonNames" :key="name" class="text-sm">
|
||||
{{ name }}
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="DeviceName" label="记录点位设备" min-width="200" />
|
||||
<el-table-column prop="ScheduleTitle" label="对应排班记录" min-width="200" />
|
||||
<el-table-column label="操作" width="150" align="center">
|
||||
<template #default="scope">
|
||||
<div class="flex gap-2 justify-center">
|
||||
<el-button
|
||||
size="small"
|
||||
@click="handleEdit(scope.row)"
|
||||
icon="Edit"
|
||||
>编辑</el-button>
|
||||
<el-button
|
||||
size="small"
|
||||
type="danger"
|
||||
@click="handleDelete(scope.row)"
|
||||
icon="Delete"
|
||||
>删除</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<!-- 分页 -->
|
||||
<div class="flex justify-end mt-4">
|
||||
<el-pagination
|
||||
v-model:current-page="currentPage"
|
||||
v-model:page-size="pageSize"
|
||||
:page-sizes="[10, 20, 50, 100]"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
:total="total"
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 日志编辑对话框 -->
|
||||
<el-dialog
|
||||
v-model="dialogVisible"
|
||||
:title="isEdit ? '编辑巡检日志' : '新增巡检日志'"
|
||||
width="70%"
|
||||
max-width="900px"
|
||||
destroy-on-close
|
||||
@closed="handleClose"
|
||||
>
|
||||
<!-- 表单容器 -->
|
||||
<el-form
|
||||
ref="logFormRef"
|
||||
:model="logForm"
|
||||
label-width="120px"
|
||||
:rules="formRules"
|
||||
>
|
||||
<!-- 第一行:人员选择和日期时间 -->
|
||||
<div class="grid grid-cols-1 gap-4 mb-4">
|
||||
<el-form-item label="人员1" prop="UserID1">
|
||||
<el-select
|
||||
v-model="logForm.UserID1"
|
||||
placeholder="选择巡检人员"
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-option
|
||||
v-for="user in userList"
|
||||
:key="user.value"
|
||||
:label="user.label"
|
||||
:value="user.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="人员2" prop="UserID2">
|
||||
<el-select
|
||||
v-model="logForm.UserID2"
|
||||
placeholder="选择巡检人员(可选)"
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-option
|
||||
v-for="user in userList"
|
||||
:key="user.value"
|
||||
:label="user.label"
|
||||
:value="user.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="人员3" prop="UserID3">
|
||||
<el-select
|
||||
v-model="logForm.UserID3"
|
||||
placeholder="选择巡检人员(可选)"
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-option
|
||||
v-for="user in userList"
|
||||
:key="user.value"
|
||||
:label="user.label"
|
||||
:value="user.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="记录日期时间" prop="CreateDate">
|
||||
<el-date-picker
|
||||
v-model="logForm.CreateDate"
|
||||
type="datetime"
|
||||
placeholder="选择日期时间"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</el-form-item>
|
||||
</div>
|
||||
|
||||
<!-- 第二行:设备和排班记录 -->
|
||||
<div class="grid grid-cols-1 gap-4">
|
||||
<el-form-item label="记录点位设备" prop="PointDeviceID">
|
||||
<el-select
|
||||
v-model="logForm.PointDeviceID"
|
||||
placeholder="选择点位设备"
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-option
|
||||
v-for="device in deviceList"
|
||||
:key="device.value"
|
||||
:label="device.label"
|
||||
:value="device.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="对应排班记录" prop="PatrolScheduleID">
|
||||
<el-select
|
||||
v-model="logForm.PatrolScheduleID"
|
||||
placeholder="选择对应排班记录"
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-option
|
||||
v-for="schedule in scheduleList"
|
||||
:key="schedule.value"
|
||||
:label="schedule.label"
|
||||
:value="schedule.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</div>
|
||||
</el-form>
|
||||
|
||||
<!-- 对话框底部按钮 -->
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="dialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="handleSave">保存</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
/**
|
||||
* 巡检日志管理组件
|
||||
* 用于管理巡检日志信息,包含人员、时间、设备和排班记录配置
|
||||
*/
|
||||
import { ref, reactive, computed, onMounted } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
|
||||
// 响应式数据
|
||||
const loading = ref(false)
|
||||
const logList = ref([])
|
||||
const currentPage = ref(1)
|
||||
const pageSize = ref(10)
|
||||
const total = ref(0)
|
||||
const searchKeyword = ref('')
|
||||
|
||||
// 编辑对话框相关
|
||||
const dialogVisible = ref(false)
|
||||
const isEdit = ref(false)
|
||||
const logFormRef = ref()
|
||||
const logForm = reactive({
|
||||
UserID1: '',
|
||||
UserID2: '',
|
||||
UserID3: '',
|
||||
CreateDate: '',
|
||||
PointDeviceID: '',
|
||||
PatrolScheduleID: ''
|
||||
})
|
||||
|
||||
// 表单验证规则
|
||||
const formRules = {
|
||||
UserID1: [
|
||||
{ required: true, message: '请选择第一位巡检人员', trigger: 'blur' }
|
||||
],
|
||||
CreateDate: [
|
||||
{ required: true, message: '请选择记录日期时间', trigger: 'blur' }
|
||||
],
|
||||
PointDeviceID: [
|
||||
{ required: true, message: '请选择记录点位设备', trigger: 'blur' }
|
||||
],
|
||||
PatrolScheduleID: [
|
||||
{ required: true, message: '请选择对应排班记录', trigger: 'blur' }
|
||||
]
|
||||
}
|
||||
|
||||
// 人员列表、设备列表和排班记录列表(模拟数据)
|
||||
const userList = ref([
|
||||
{ label: '张三', value: '1' },
|
||||
{ label: '李四', value: '2' },
|
||||
{ label: '王五', value: '3' },
|
||||
{ label: '赵六', value: '4' },
|
||||
{ label: '钱七', value: '5' }
|
||||
])
|
||||
|
||||
const deviceList = ref([
|
||||
{ label: '生产线A-设备1', value: '1' },
|
||||
{ label: '生产线A-设备2', value: '2' },
|
||||
{ label: '生产线B-设备1', value: '3' },
|
||||
{ label: '仓库-设备1', value: '4' },
|
||||
{ label: '办公区-设备1', value: '5' },
|
||||
{ label: '办公区-设备2', value: '6' }
|
||||
])
|
||||
|
||||
const scheduleList = ref([
|
||||
{ label: '周一生产线A例行巡检', value: '1' },
|
||||
{ label: '周三仓库安全巡检', value: '2' },
|
||||
{ label: '周五办公区环境巡检', value: '3' },
|
||||
{ label: '周二生产线B例行巡检', value: '4' },
|
||||
{ label: '周四全厂区安全巡检', value: '5' }
|
||||
])
|
||||
|
||||
// 获取选中人员的姓名
|
||||
const getPersonNames = (userIds: string[]) => {
|
||||
return userIds
|
||||
.filter(id => id)
|
||||
.map(id => {
|
||||
const user = userList.value.find(u => u.value === id)
|
||||
return user ? user.label : ''
|
||||
})
|
||||
.filter(name => name)
|
||||
}
|
||||
|
||||
// 初始化数据
|
||||
const initData = () => {
|
||||
loading.value = true
|
||||
// 模拟数据获取
|
||||
setTimeout(() => {
|
||||
logList.value = [
|
||||
{
|
||||
id: 1,
|
||||
UserID1: '1',
|
||||
UserID2: '2',
|
||||
UserID3: '',
|
||||
CreateDate: '2024-04-17 09:30:00',
|
||||
PointDeviceID: '1',
|
||||
PatrolScheduleID: '1',
|
||||
PersonNames: ['张三', '李四'],
|
||||
DeviceName: '生产线A-设备1',
|
||||
ScheduleTitle: '周一生产线A例行巡检'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
UserID1: '2',
|
||||
UserID2: '',
|
||||
UserID3: '',
|
||||
CreateDate: '2024-04-17 14:15:00',
|
||||
PointDeviceID: '3',
|
||||
PatrolScheduleID: '4',
|
||||
PersonNames: ['李四'],
|
||||
DeviceName: '生产线B-设备1',
|
||||
ScheduleTitle: '周二生产线B例行巡检'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
UserID1: '3',
|
||||
UserID2: '4',
|
||||
UserID3: '5',
|
||||
CreateDate: '2024-04-17 16:45:00',
|
||||
PointDeviceID: '5',
|
||||
PatrolScheduleID: '3',
|
||||
PersonNames: ['王五', '赵六', '钱七'],
|
||||
DeviceName: '办公区-设备1',
|
||||
ScheduleTitle: '周五办公区环境巡检'
|
||||
}
|
||||
]
|
||||
total.value = logList.value.length
|
||||
loading.value = false
|
||||
}, 500)
|
||||
}
|
||||
|
||||
// 刷新数据
|
||||
const handleRefresh = () => {
|
||||
currentPage.value = 1
|
||||
initData()
|
||||
}
|
||||
|
||||
// 搜索功能
|
||||
const handleSearch = () => {
|
||||
// 简单的前端搜索实现
|
||||
const filtered = logList.value.filter(item => {
|
||||
const searchStr = searchKeyword.value.toLowerCase()
|
||||
return item.DeviceName.toLowerCase().includes(searchStr) ||
|
||||
item.ScheduleTitle.toLowerCase().includes(searchStr) ||
|
||||
item.PersonNames.some(name => name.toLowerCase().includes(searchStr)) ||
|
||||
item.CreateDate.toLowerCase().includes(searchStr)
|
||||
})
|
||||
logList.value = filtered
|
||||
}
|
||||
|
||||
// 分页处理
|
||||
const handleSizeChange = (size: number) => {
|
||||
pageSize.value = size
|
||||
}
|
||||
|
||||
const handleCurrentChange = (current: number) => {
|
||||
currentPage.value = current
|
||||
}
|
||||
|
||||
// 行点击事件
|
||||
const handleRowClick = (row: any) => {
|
||||
// 可以添加行点击后的处理逻辑
|
||||
}
|
||||
|
||||
// 选择框变化事件
|
||||
const handleSelectionChange = (selection: any[]) => {
|
||||
// 可以添加选择框变化后的处理逻辑
|
||||
}
|
||||
|
||||
// 新增日志
|
||||
const handleAddLog = () => {
|
||||
isEdit.value = false
|
||||
// 重置表单
|
||||
logForm.UserID1 = ''
|
||||
logForm.UserID2 = ''
|
||||
logForm.UserID3 = ''
|
||||
logForm.CreateDate = ''
|
||||
logForm.PointDeviceID = ''
|
||||
logForm.PatrolScheduleID = ''
|
||||
|
||||
// 打开对话框
|
||||
dialogVisible.value = true
|
||||
}
|
||||
|
||||
// 编辑日志
|
||||
const handleEdit = (row: any) => {
|
||||
isEdit.value = true
|
||||
// 填充表单数据
|
||||
logForm.UserID1 = row.UserID1 || ''
|
||||
logForm.UserID2 = row.UserID2 || ''
|
||||
logForm.UserID3 = row.UserID3 || ''
|
||||
logForm.CreateDate = row.CreateDate || ''
|
||||
logForm.PointDeviceID = row.PointDeviceID || ''
|
||||
logForm.PatrolScheduleID = row.PatrolScheduleID || ''
|
||||
|
||||
// 打开对话框
|
||||
dialogVisible.value = true
|
||||
}
|
||||
|
||||
// 删除日志
|
||||
const handleDelete = (row: any) => {
|
||||
ElMessageBox.confirm(
|
||||
`确定要删除ID为${row.id}的巡检日志吗?`,
|
||||
'确认删除',
|
||||
{
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}
|
||||
).then(() => {
|
||||
// 模拟删除操作
|
||||
const index = logList.value.findIndex(item => item.id === row.id)
|
||||
if (index !== -1) {
|
||||
logList.value.splice(index, 1)
|
||||
total.value = logList.value.length
|
||||
ElMessage.success('删除成功')
|
||||
}
|
||||
}).catch(() => {
|
||||
// 取消删除
|
||||
})
|
||||
}
|
||||
|
||||
// 保存日志
|
||||
const handleSave = async () => {
|
||||
if (!logFormRef.value) return
|
||||
|
||||
try {
|
||||
await logFormRef.value.validate()
|
||||
|
||||
// 模拟保存操作
|
||||
const userIds = [logForm.UserID1, logForm.UserID2, logForm.UserID3]
|
||||
const personNames = getPersonNames(userIds)
|
||||
|
||||
const device = deviceList.value.find(d => d.value === logForm.PointDeviceID)
|
||||
const schedule = scheduleList.value.find(s => s.value === logForm.PatrolScheduleID)
|
||||
|
||||
const newLog = {
|
||||
id: isEdit.value ? logList.value.find(item =>
|
||||
item.UserID1 === logForm.UserID1 &&
|
||||
item.CreateDate === logForm.CreateDate
|
||||
)?.id : Date.now(),
|
||||
UserID1: logForm.UserID1,
|
||||
UserID2: logForm.UserID2,
|
||||
UserID3: logForm.UserID3,
|
||||
CreateDate: logForm.CreateDate,
|
||||
PointDeviceID: logForm.PointDeviceID,
|
||||
PatrolScheduleID: logForm.PatrolScheduleID,
|
||||
PersonNames: personNames,
|
||||
DeviceName: device ? device.label : '',
|
||||
ScheduleTitle: schedule ? schedule.label : ''
|
||||
}
|
||||
|
||||
if (isEdit.value) {
|
||||
// 编辑模式
|
||||
const index = logList.value.findIndex(item =>
|
||||
item.UserID1 === logForm.UserID1 &&
|
||||
item.CreateDate === logForm.CreateDate
|
||||
)
|
||||
if (index !== -1) {
|
||||
logList.value[index] = newLog
|
||||
}
|
||||
} else {
|
||||
// 新增模式
|
||||
logList.value.unshift(newLog)
|
||||
total.value = logList.value.length
|
||||
}
|
||||
|
||||
ElMessage.success(isEdit.value ? '编辑成功' : '新增成功')
|
||||
dialogVisible.value = false
|
||||
} catch (error) {
|
||||
// 表单验证失败
|
||||
}
|
||||
}
|
||||
|
||||
// 对话框关闭处理
|
||||
const handleClose = () => {
|
||||
if (logFormRef.value) {
|
||||
logFormRef.value.resetFields()
|
||||
}
|
||||
}
|
||||
|
||||
// 组件挂载时初始化数据
|
||||
onMounted(() => {
|
||||
initData()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 表单样式 */
|
||||
:deep(.el-form) {
|
||||
background: transparent !important;
|
||||
}
|
||||
|
||||
:deep(.el-form-item__label) {
|
||||
color: #68c9ff !important;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* 表格样式 - 设置为透明背景 */
|
||||
:deep(.el-table) {
|
||||
background: transparent !important;
|
||||
}
|
||||
|
||||
:deep(.el-table tr){
|
||||
background: transparent !important;
|
||||
}
|
||||
|
||||
:deep(.el-table__header-wrapper) {
|
||||
background: transparent !important;
|
||||
}
|
||||
|
||||
:deep(.el-table__header) {
|
||||
background: transparent !important;
|
||||
}
|
||||
|
||||
:deep(.el-table__header) th {
|
||||
background: transparent !important;
|
||||
background-color: transparent !important;
|
||||
color: #68c9ff !important;
|
||||
border-color: rgba(104, 201, 255, 0.3) !important;
|
||||
}
|
||||
|
||||
/* 确保表头在各种状态下都保持透明 */
|
||||
:deep(.el-table th.is-leaf) {
|
||||
background: transparent !important;
|
||||
background-color: transparent !important;
|
||||
}
|
||||
|
||||
:deep(.el-table__body-wrapper) {
|
||||
background: transparent !important;
|
||||
}
|
||||
|
||||
:deep(.el-table__row) {
|
||||
background: transparent !important;
|
||||
}
|
||||
|
||||
:deep(.el-table__row:nth-child(2n)) {
|
||||
background: rgba(0, 58, 97, 0.1) !important;
|
||||
}
|
||||
|
||||
:deep(.el-table__row:hover) {
|
||||
background: rgba(0, 58, 97, 0.2) !important;
|
||||
}
|
||||
|
||||
:deep(.el-table__body) td {
|
||||
border-color: rgba(104, 201, 255, 0.2) !important;
|
||||
color: #91d5ff !important;
|
||||
}
|
||||
|
||||
/* 对话框样式 */
|
||||
:deep(.el-dialog) {
|
||||
background-color: rgba(0, 58, 97, 0.4) !important; /* 空军蓝,透明度40% */
|
||||
backdrop-filter: blur(8px); /* 高斯模糊效果 */
|
||||
border: 1px solid rgba(104, 201, 255, 0.3);
|
||||
}
|
||||
|
||||
:deep(.el-dialog__header) {
|
||||
background-color: rgba(0, 58, 97, 0.6) !important;
|
||||
border-bottom: 1px solid rgba(104, 201, 255, 0.3);
|
||||
}
|
||||
|
||||
:deep(.el-dialog__title) {
|
||||
color: #91d5ff !important;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
:deep(.el-dialog__footer) {
|
||||
background-color: rgba(0, 58, 97, 0.6) !important;
|
||||
border-top: 1px solid rgba(104, 201, 255, 0.3);
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
</style>
|
382
src/views/Path/PathManager.vue
Normal file
382
src/views/Path/PathManager.vue
Normal file
@@ -0,0 +1,382 @@
|
||||
<template>
|
||||
<!-- 巡检路径管理组件模板 -->
|
||||
<div class="p-4">
|
||||
<h2 class="text-xl font-bold text-blue-400 mb-4">巡检路径管理</h2>
|
||||
|
||||
<!-- 工具栏 -->
|
||||
<div class="flex justify-between items-center mb-4">
|
||||
<div class="flex gap-4">
|
||||
<el-button type="primary" @click="handleAddPath" icon="Plus">新增路径</el-button>
|
||||
<el-button @click="handleRefresh" icon="Refresh">刷新</el-button>
|
||||
</div>
|
||||
|
||||
<!-- 搜索框 -->
|
||||
<el-input
|
||||
v-model="searchKeyword"
|
||||
placeholder="搜索路径名称"
|
||||
prefix-icon="Search"
|
||||
style="width: 240px"
|
||||
@input="handleSearch"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 路径列表表格 -->
|
||||
<el-table
|
||||
v-loading="loading"
|
||||
:data="pathList"
|
||||
style="width: 100%"
|
||||
@row-click="handleRowClick"
|
||||
@selection-change="handleSelectionChange"
|
||||
>
|
||||
<el-table-column type="selection" width="55" align="center" />
|
||||
<el-table-column prop="id" label="路径ID" width="100" align="center" />
|
||||
<el-table-column prop="name" label="路径名称" min-width="180" align="center" />
|
||||
<el-table-column prop="description" label="路径说明" min-width="250" align="center">
|
||||
<template #default="scope">
|
||||
<el-tooltip :content="scope.row.description" placement="top">
|
||||
<span>{{ scope.row.description.length > 20 ? scope.row.description.substring(0, 20) + '...' : scope.row.description }}</span>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="pointCount" label="路径点数量" width="120" align="center" />
|
||||
<el-table-column prop="createTime" label="创建时间" width="180" align="center" />
|
||||
<el-table-column prop="updateTime" label="更新时间" width="180" align="center" />
|
||||
<el-table-column label="操作" width="180" align="center" fixed="right">
|
||||
<template #default="scope">
|
||||
<el-button size="small" @click="handleEditPath(scope.row)" icon="Edit">编辑</el-button>
|
||||
<el-button size="small" type="danger" @click="handleDeletePath(scope.row)" icon="Delete">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<!-- 分页组件 -->
|
||||
<div class="mt-4 flex justify-end">
|
||||
<el-pagination
|
||||
v-model:current-page="pagination.currentPage"
|
||||
v-model:page-size="pagination.pageSize"
|
||||
:page-sizes="[10, 20, 50, 100]"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
:total="pagination.total"
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 路径编辑对话框 -->
|
||||
<PathEditor
|
||||
v-model:visible="editorVisible"
|
||||
:path-data="currentPath"
|
||||
@save="handleSavePath"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
/**
|
||||
* 巡检路径管理组件
|
||||
* 用于管理和维护巡检路径信息
|
||||
*/
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import PathEditor from './PathEditor.vue'
|
||||
|
||||
// 路由相关
|
||||
const router = useRouter()
|
||||
|
||||
// 状态管理
|
||||
const loading = ref(false)
|
||||
const searchKeyword = ref('')
|
||||
const editorVisible = ref(false)
|
||||
const selectedPaths = ref([])
|
||||
const currentPath = ref(null)
|
||||
|
||||
// 分页数据
|
||||
const pagination = reactive({
|
||||
currentPage: 1,
|
||||
pageSize: 10,
|
||||
total: 0
|
||||
})
|
||||
|
||||
// 路径列表数据
|
||||
const pathList = ref([])
|
||||
|
||||
/**
|
||||
* 初始化数据
|
||||
*/
|
||||
onMounted(() => {
|
||||
fetchPathList()
|
||||
})
|
||||
|
||||
/**
|
||||
* 获取路径列表数据
|
||||
*/
|
||||
const fetchPathList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
// 模拟API请求
|
||||
// 实际应用中应替换为真实的API调用
|
||||
const mockData = generateMockPaths()
|
||||
|
||||
// 应用搜索过滤
|
||||
let filteredData = mockData
|
||||
if (searchKeyword.value) {
|
||||
filteredData = mockData.filter(item =>
|
||||
item.name.toLowerCase().includes(searchKeyword.value.toLowerCase())
|
||||
)
|
||||
}
|
||||
|
||||
// 更新分页数据
|
||||
pagination.total = filteredData.length
|
||||
|
||||
// 应用分页
|
||||
const start = (pagination.currentPage - 1) * pagination.pageSize
|
||||
const end = start + pagination.pageSize
|
||||
pathList.value = filteredData.slice(start, end)
|
||||
|
||||
} catch (error) {
|
||||
console.error('获取路径列表失败:', error)
|
||||
ElMessage.error('获取路径列表失败,请重试')
|
||||
pathList.value = []
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成模拟数据
|
||||
* 实际应用中应通过API获取
|
||||
*/
|
||||
const generateMockPaths = () => {
|
||||
return [
|
||||
{
|
||||
id: '1',
|
||||
name: '新库区巡检路径',
|
||||
description: '覆盖新库区所有仓库区域的标准巡检路径',
|
||||
pointCount: 12,
|
||||
points: [
|
||||
{ id: '1-1', index: 1, deviceName: '摄像头1', modelId: 'mgt1aol3kro' },
|
||||
{ id: '1-2', index: 2, deviceName: '摄像头2', modelId: 'mgt1gz9s54s' },
|
||||
{ id: '1-3', index: 3, deviceName: '摄像头3', modelId: 'mgt1ho9dzhn' },
|
||||
{ id: '1-4', index: 4, deviceName: '摄像头4', modelId: 'mgt1n0wylmd' },
|
||||
{ id: '1-5', index: 5, deviceName: '摄像头5', modelId: 'mgt1nboirj' },
|
||||
{ id: '1-6', index: 6, deviceName: '摄像头6', modelId: 'mguoz4gy4x' },
|
||||
{ id: '1-7', index: 7, deviceName: '摄像头7', modelId: 'mgt2cbv7k2d' },
|
||||
{ id: '1-8', index: 8, deviceName: '摄像头8', modelId: 'mgt1njgpgpt' },
|
||||
{ id: '1-9', index: 9, deviceName: '摄像头9', modelId: 'mgt1nuwisti' },
|
||||
{ id: '1-10', index: 10, deviceName: '摄像头10', modelId: 'mgt1o6aqek' },
|
||||
{ id: '1-11', index: 11, deviceName: '摄像头11', modelId: 'mgulf1e5mi' },
|
||||
{ id: '1-12', index: 12, deviceName: '摄像头14', modelId: 'mgt1oylmwuf' }
|
||||
],
|
||||
createTime: '2024-04-01 10:30:00',
|
||||
updateTime: '2024-04-10 14:20:00'
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
name: '新库区入口巡检路径',
|
||||
description: '新库区入口安全检查专用路线',
|
||||
pointCount: 5,
|
||||
points: [
|
||||
{ id: '2-1', index: 1, deviceName: '摄像头18', modelId: 'mgt24lo3m4e' },
|
||||
{ id: '2-2', index: 2, deviceName: '摄像头17', modelId: 'mgt24kocdw' },
|
||||
{ id: '2-3', index: 3, deviceName: '摄像头16', modelId: 'mgt24gkj4on' },
|
||||
{ id: '2-4', index: 4, deviceName: '摄像头12', modelId: 'mgt1oi96hsf' },
|
||||
{ id: '2-5', index: 5, deviceName: '摄像头13', modelId: 'mgt1op9e61d' }
|
||||
],
|
||||
createTime: '2024-04-05 09:15:00',
|
||||
updateTime: '2024-04-05 09:15:00'
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理添加路径
|
||||
*/
|
||||
const handleAddPath = () => {
|
||||
currentPath.value = null
|
||||
editorVisible.value = true
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理编辑路径
|
||||
*/
|
||||
const handleEditPath = (path) => {
|
||||
currentPath.value = JSON.parse(JSON.stringify(path)) // 深拷贝
|
||||
editorVisible.value = true
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理删除路径
|
||||
*/
|
||||
const handleDeletePath = async (path) => {
|
||||
try {
|
||||
await ElMessageBox.confirm(
|
||||
`确定要删除路径「${path.name}」吗?`,
|
||||
'确认删除',
|
||||
{
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}
|
||||
)
|
||||
|
||||
// 模拟删除操作
|
||||
// 实际应用中应调用API删除
|
||||
ElMessage.success('路径删除成功')
|
||||
fetchPathList() // 重新获取列表
|
||||
} catch (error) {
|
||||
// 用户取消删除
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理保存路径
|
||||
*/
|
||||
const handleSavePath = (pathData) => {
|
||||
// 模拟保存操作
|
||||
// 实际应用中应调用API保存
|
||||
ElMessage.success('路径保存成功')
|
||||
editorVisible.value = false
|
||||
fetchPathList() // 重新获取列表
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理表格行点击
|
||||
*/
|
||||
const handleRowClick = (row) => {
|
||||
// 可以在这里处理点击行的逻辑,比如查看详情
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理选择变化
|
||||
*/
|
||||
const handleSelectionChange = (selection) => {
|
||||
selectedPaths.value = selection
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理搜索
|
||||
*/
|
||||
const handleSearch = () => {
|
||||
pagination.currentPage = 1 // 重置到第一页
|
||||
fetchPathList()
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理刷新
|
||||
*/
|
||||
const handleRefresh = () => {
|
||||
searchKeyword.value = ''
|
||||
pagination.currentPage = 1
|
||||
fetchPathList()
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理分页大小变化
|
||||
*/
|
||||
const handleSizeChange = (size) => {
|
||||
pagination.pageSize = size
|
||||
fetchPathList()
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理页码变化
|
||||
*/
|
||||
const handleCurrentChange = (current) => {
|
||||
pagination.currentPage = current
|
||||
fetchPathList()
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/**
|
||||
* 巡检路径管理组件样式
|
||||
* 保持与系统整体风格一致,使用空军蓝配色和透明背景
|
||||
*/
|
||||
/* 设置页面背景为透明 */
|
||||
.p-4 {
|
||||
background-color: transparent !important;
|
||||
}
|
||||
|
||||
/* 标题样式 */
|
||||
.text-xl {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.font-bold {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.text-blue-400 {
|
||||
color: #68c9ff;
|
||||
}
|
||||
|
||||
.mb-4 {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
/* 表格样式 */
|
||||
:deep(.el-table) {
|
||||
background: rgba(12, 43, 77, 0.7) !important;
|
||||
border: 1px solid #68c9ff;
|
||||
backdrop-filter: blur(5px);
|
||||
}
|
||||
|
||||
:deep(.el-table__header-wrapper th) {
|
||||
background: rgba(20, 60, 110, 0.8) !important;
|
||||
color: #68c9ff !important;
|
||||
border-bottom: 1px solid #68c9ff !important;
|
||||
}
|
||||
|
||||
:deep(.el-table__body-wrapper tr) {
|
||||
background: transparent !important;
|
||||
}
|
||||
|
||||
:deep(.el-table__body-wrapper tr:nth-child(even)) {
|
||||
background: rgba(20, 60, 110, 0.3) !important;
|
||||
}
|
||||
|
||||
:deep(.el-table__body-wrapper tr:hover) {
|
||||
background: rgba(104, 201, 255, 0.2) !important;
|
||||
}
|
||||
|
||||
:deep(.el-table__body-wrapper td) {
|
||||
color: #b6dfff !important;
|
||||
border-bottom: 1px solid rgba(104, 201, 255, 0.3) !important;
|
||||
}
|
||||
|
||||
/* 分页组件样式 */
|
||||
:deep(.el-pagination) {
|
||||
color: #68c9ff !important;
|
||||
}
|
||||
|
||||
:deep(.el-pagination button) {
|
||||
background: transparent !important;
|
||||
border-color: #68c9ff !important;
|
||||
color: #68c9ff !important;
|
||||
}
|
||||
|
||||
:deep(.el-pagination__sizes .el-input .el-input__wrapper) {
|
||||
background: transparent !important;
|
||||
border-color: #68c9ff !important;
|
||||
}
|
||||
|
||||
:deep(.el-pagination__sizes .el-input .el-input__inner) {
|
||||
background: transparent !important;
|
||||
color: #68c9ff !important;
|
||||
}
|
||||
|
||||
:deep(.el-pagination__jump .el-input .el-input__wrapper) {
|
||||
background: transparent !important;
|
||||
border-color: #68c9ff !important;
|
||||
}
|
||||
|
||||
:deep(.el-pagination__jump .el-input .el-input__inner) {
|
||||
background: transparent !important;
|
||||
color: #68c9ff !important;
|
||||
}
|
||||
|
||||
:deep(.el-pager li.active) {
|
||||
color: #68c9ff !important;
|
||||
}
|
||||
</style>
|
707
src/views/Path/PathSchedule.vue
Normal file
707
src/views/Path/PathSchedule.vue
Normal file
@@ -0,0 +1,707 @@
|
||||
<template>
|
||||
<div class="p-4">
|
||||
<h2 class="text-xl font-bold text-blue-400 mb-4">巡检计划管理</h2>
|
||||
|
||||
<!-- 工具栏 -->
|
||||
<div class="flex justify-between items-center mb-4">
|
||||
<div class="flex gap-4">
|
||||
<el-button type="primary" @click="handleAddSchedule" icon="Plus">新增计划</el-button>
|
||||
<el-button @click="handleRefresh" icon="Refresh">刷新</el-button>
|
||||
</div>
|
||||
|
||||
<!-- 搜索框 -->
|
||||
<el-input
|
||||
v-model="searchKeyword"
|
||||
placeholder="搜索计划标题"
|
||||
prefix-icon="Search"
|
||||
style="width: 240px"
|
||||
@input="handleSearch"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 计划列表表格 -->
|
||||
<el-table
|
||||
v-loading="loading"
|
||||
:data="scheduleList"
|
||||
style="width: 100%"
|
||||
@row-click="handleRowClick"
|
||||
@selection-change="handleSelectionChange"
|
||||
>
|
||||
<el-table-column type="selection" width="55" />
|
||||
<el-table-column prop="PatrolDay" label="巡检周期" width="100" align="center">
|
||||
<template #default="scope">
|
||||
{{ getDayOfWeek(scope.row.PatrolDay) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="PatrolTitle" label="计划标题" min-width="200" />
|
||||
<el-table-column prop="PatrolStartTime" label="开始时间" width="120" align="center" />
|
||||
<el-table-column prop="PatrolEndTime" label="结束时间" width="120" align="center" />
|
||||
<el-table-column prop="PatrolPersons" label="巡检人员" min-width="200" align="center">
|
||||
<template #default="scope">
|
||||
<div v-for="person in scope.row.PatrolPersons" :key="person.id" class="text-sm">
|
||||
{{ person.name }}
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="PatrolPathName" label="巡检路线" min-width="180" />
|
||||
<el-table-column label="操作" width="150" align="center">
|
||||
<template #default="scope">
|
||||
<div class="flex gap-2 justify-center">
|
||||
<el-button
|
||||
size="small"
|
||||
@click="handleEdit(scope.row)"
|
||||
icon="Edit"
|
||||
>编辑</el-button>
|
||||
<el-button
|
||||
size="small"
|
||||
type="danger"
|
||||
@click="handleDelete(scope.row)"
|
||||
icon="Delete"
|
||||
>删除</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<!-- 分页 -->
|
||||
<div class="flex justify-end mt-4">
|
||||
<el-pagination
|
||||
v-model:current-page="currentPage"
|
||||
v-model:page-size="pageSize"
|
||||
:page-sizes="[10, 20, 50, 100]"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
:total="total"
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 计划编辑对话框 -->
|
||||
<el-dialog
|
||||
v-model="dialogVisible"
|
||||
:title="isEdit ? '编辑巡检计划' : '新增巡检计划'"
|
||||
width="70%"
|
||||
max-width="900px"
|
||||
destroy-on-close
|
||||
@closed="handleClose"
|
||||
>
|
||||
<!-- 表单容器 -->
|
||||
<el-form
|
||||
ref="scheduleFormRef"
|
||||
:model="scheduleForm"
|
||||
label-width="120px"
|
||||
:rules="formRules"
|
||||
>
|
||||
<!-- 第一行:周期和标题 -->
|
||||
<div class="grid grid-cols-1 gap-4 mb-4">
|
||||
<el-form-item label="每周第几天" prop="PatrolDay">
|
||||
<el-input-number
|
||||
v-model="scheduleForm.PatrolDay"
|
||||
:min="1"
|
||||
:max="7"
|
||||
placeholder="请输入1-7"
|
||||
:step-strictly="true"
|
||||
>
|
||||
<template #append>
|
||||
<el-select v-model="scheduleForm.PatrolDay" placeholder="选择星期">
|
||||
<el-option label="周一" :value="1" />
|
||||
<el-option label="周二" :value="2" />
|
||||
<el-option label="周三" :value="3" />
|
||||
<el-option label="周四" :value="4" />
|
||||
<el-option label="周五" :value="5" />
|
||||
<el-option label="周六" :value="6" />
|
||||
<el-option label="周日" :value="7" />
|
||||
</el-select>
|
||||
</template>
|
||||
</el-input-number>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="标题" prop="PatrolTitle">
|
||||
<el-input
|
||||
v-model="scheduleForm.PatrolTitle"
|
||||
placeholder="请输入计划标题"
|
||||
maxlength="50"
|
||||
show-word-limit
|
||||
/>
|
||||
</el-form-item>
|
||||
</div>
|
||||
|
||||
<!-- 第二行:开始时间和结束时间 -->
|
||||
<div class="grid grid-cols-1 gap-4 mb-4">
|
||||
<el-form-item label="巡检开始时间" prop="PatrolStartTime">
|
||||
<el-time-picker
|
||||
v-model="scheduleForm.PatrolStartTime"
|
||||
type="time"
|
||||
placeholder="选择开始时间"
|
||||
value-format="HH:mm"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="巡检结束时间" prop="PatrolEndTime">
|
||||
<el-time-picker
|
||||
v-model="scheduleForm.PatrolEndTime"
|
||||
type="time"
|
||||
placeholder="选择结束时间"
|
||||
value-format="HH:mm"
|
||||
/>
|
||||
</el-form-item>
|
||||
</div>
|
||||
|
||||
<!-- 第三行:第一位和第二位巡检人员 -->
|
||||
<div class="grid grid-cols-1 gap-4 mb-4">
|
||||
<el-form-item label="第一位巡检人员" prop="UserId1">
|
||||
<el-select
|
||||
v-model="scheduleForm.UserId1"
|
||||
placeholder="选择巡检人员"
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-option
|
||||
v-for="user in userList"
|
||||
:key="user.value"
|
||||
:label="user.label"
|
||||
:value="user.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="第二位巡检人员" prop="UserId2">
|
||||
<el-select
|
||||
v-model="scheduleForm.UserId2"
|
||||
placeholder="选择巡检人员"
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-option
|
||||
v-for="user in userList"
|
||||
:key="user.value"
|
||||
:label="user.label"
|
||||
:value="user.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</div>
|
||||
|
||||
<!-- 第四行:第三位巡检人员和巡检路线 -->
|
||||
<div class="grid grid-cols-1 gap-4">
|
||||
<el-form-item label="第三位巡检人员" prop="UserId3">
|
||||
<el-select
|
||||
v-model="scheduleForm.UserId3"
|
||||
placeholder="选择巡检人员"
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-option
|
||||
v-for="user in userList"
|
||||
:key="user.value"
|
||||
:label="user.label"
|
||||
:value="user.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="巡检路线" prop="PatrolPathId">
|
||||
<el-select
|
||||
v-model="scheduleForm.PatrolPathId"
|
||||
placeholder="选择巡检路线"
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-option
|
||||
v-for="path in pathList"
|
||||
:key="path.value"
|
||||
:label="path.label"
|
||||
:value="path.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</div>
|
||||
</el-form>
|
||||
|
||||
<!-- 对话框底部按钮 -->
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="dialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="handleSave">保存</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
/**
|
||||
* 巡检计划管理组件
|
||||
* 用于管理巡检计划信息,包含计划的周期、时间、人员和路线配置
|
||||
*/
|
||||
import { ref, reactive, computed, onMounted } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
|
||||
// 响应式数据
|
||||
const loading = ref(false)
|
||||
const scheduleList = ref([])
|
||||
const currentPage = ref(1)
|
||||
const pageSize = ref(10)
|
||||
const total = ref(0)
|
||||
const searchKeyword = ref('')
|
||||
|
||||
// 编辑对话框相关
|
||||
const dialogVisible = ref(false)
|
||||
const isEdit = ref(false)
|
||||
const scheduleFormRef = ref()
|
||||
const scheduleForm = reactive({
|
||||
PatrolDay: null,
|
||||
PatrolTitle: '',
|
||||
PatrolStartTime: '',
|
||||
PatrolEndTime: '',
|
||||
UserId1: '',
|
||||
UserId2: '',
|
||||
UserId3: '',
|
||||
PatrolPathId: ''
|
||||
})
|
||||
|
||||
// 表单验证规则
|
||||
const formRules = {
|
||||
PatrolDay: [
|
||||
{ required: true, message: '请输入每周第几天', trigger: 'blur' },
|
||||
{ type: 'number', min: 1, max: 7, message: '请输入1-7之间的数字', trigger: 'blur' }
|
||||
],
|
||||
PatrolTitle: [
|
||||
{ required: true, message: '请输入计划标题', trigger: 'blur' }
|
||||
],
|
||||
PatrolStartTime: [
|
||||
{ required: true, message: '请选择巡检开始时间', trigger: 'blur' }
|
||||
],
|
||||
PatrolEndTime: [
|
||||
{ required: true, message: '请选择巡检结束时间', trigger: 'blur' }
|
||||
],
|
||||
UserId1: [
|
||||
{ required: true, message: '请选择第一位巡检人员', trigger: 'blur' }
|
||||
],
|
||||
PatrolPathId: [
|
||||
{ required: true, message: '请选择巡检路线', trigger: 'blur' }
|
||||
]
|
||||
}
|
||||
|
||||
// 人员列表和路线列表(模拟数据)
|
||||
const userList = ref([
|
||||
{ label: '张三', value: '1' },
|
||||
{ label: '李四', value: '2' },
|
||||
{ label: '王五', value: '3' },
|
||||
{ label: '赵六', value: '4' },
|
||||
{ label: '钱七', value: '5' }
|
||||
])
|
||||
|
||||
const pathList = ref([
|
||||
{ label: '生产线A巡检路线', value: '1' },
|
||||
{ label: '生产线B巡检路线', value: '2' },
|
||||
{ label: '仓库巡检路线', value: '3' },
|
||||
{ label: '办公区巡检路线', value: '4' }
|
||||
])
|
||||
|
||||
// 获取星期几的中文名称
|
||||
const getDayOfWeek = (day: number): string => {
|
||||
const days = ['', '周一', '周二', '周三', '周四', '周五', '周六', '周日']
|
||||
return days[day] || ''
|
||||
}
|
||||
|
||||
// 初始化数据
|
||||
const initData = () => {
|
||||
loading.value = true
|
||||
// 模拟数据获取
|
||||
setTimeout(() => {
|
||||
scheduleList.value = [
|
||||
{
|
||||
id: 1,
|
||||
PatrolDay: 1,
|
||||
PatrolTitle: '周一生产线A例行巡检',
|
||||
PatrolStartTime: '09:00',
|
||||
PatrolEndTime: '10:30',
|
||||
UserId1: '1',
|
||||
UserId2: '2',
|
||||
UserId3: '3',
|
||||
PatrolPathId: '1',
|
||||
PatrolPersons: [
|
||||
{ id: '1', name: '张三' },
|
||||
{ id: '2', name: '李四' },
|
||||
{ id: '3', name: '王五' }
|
||||
],
|
||||
PatrolPathName: '生产线A巡检路线'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
PatrolDay: 3,
|
||||
PatrolTitle: '周三仓库安全巡检',
|
||||
PatrolStartTime: '14:00',
|
||||
PatrolEndTime: '15:30',
|
||||
UserId1: '2',
|
||||
UserId2: '',
|
||||
UserId3: '',
|
||||
PatrolPathId: '3',
|
||||
PatrolPersons: [
|
||||
{ id: '2', name: '李四' }
|
||||
],
|
||||
PatrolPathName: '仓库巡检路线'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
PatrolDay: 5,
|
||||
PatrolTitle: '周五办公区环境巡检',
|
||||
PatrolStartTime: '15:00',
|
||||
PatrolEndTime: '16:00',
|
||||
UserId1: '3',
|
||||
UserId2: '4',
|
||||
UserId3: '',
|
||||
PatrolPathId: '4',
|
||||
PatrolPersons: [
|
||||
{ id: '3', name: '王五' },
|
||||
{ id: '4', name: '赵六' }
|
||||
],
|
||||
PatrolPathName: '办公区巡检路线'
|
||||
}
|
||||
]
|
||||
total.value = scheduleList.value.length
|
||||
loading.value = false
|
||||
}, 500)
|
||||
}
|
||||
|
||||
// 处理新增计划
|
||||
const handleAddSchedule = () => {
|
||||
isEdit.value = false
|
||||
// 重置表单
|
||||
Object.keys(scheduleForm).forEach(key => {
|
||||
scheduleForm[key] = ''
|
||||
})
|
||||
scheduleForm.PatrolDay = null
|
||||
dialogVisible.value = true
|
||||
}
|
||||
|
||||
// 处理编辑计划
|
||||
const handleEdit = (row: any) => {
|
||||
isEdit.value = true
|
||||
// 填充表单数据
|
||||
Object.keys(scheduleForm).forEach(key => {
|
||||
if (key in row) {
|
||||
scheduleForm[key] = row[key]
|
||||
}
|
||||
})
|
||||
dialogVisible.value = true
|
||||
}
|
||||
|
||||
// 处理删除计划
|
||||
const handleDelete = async (row: any) => {
|
||||
try {
|
||||
await ElMessageBox.confirm(
|
||||
`确定要删除计划「${row.PatrolTitle}」吗?`,
|
||||
'确认删除',
|
||||
{
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}
|
||||
)
|
||||
// 模拟删除操作
|
||||
loading.value = true
|
||||
setTimeout(() => {
|
||||
const index = scheduleList.value.findIndex(item => item.id === row.id)
|
||||
if (index > -1) {
|
||||
scheduleList.value.splice(index, 1)
|
||||
total.value = scheduleList.value.length
|
||||
ElMessage.success('删除成功')
|
||||
}
|
||||
loading.value = false
|
||||
}, 300)
|
||||
} catch {
|
||||
// 取消删除
|
||||
}
|
||||
}
|
||||
|
||||
// 保存计划
|
||||
const handleSave = async () => {
|
||||
if (!scheduleFormRef.value) return
|
||||
|
||||
try {
|
||||
await scheduleFormRef.value.validate()
|
||||
|
||||
// 构建人员信息
|
||||
const personIds = [scheduleForm.UserId1, scheduleForm.UserId2, scheduleForm.UserId3].filter(id => id)
|
||||
const PatrolPersons = personIds.map(id => {
|
||||
const user = userList.value.find(u => u.value === id)
|
||||
return { id, name: user?.label || '' }
|
||||
})
|
||||
|
||||
// 获取路线名称
|
||||
const path = pathList.value.find(p => p.value === scheduleForm.PatrolPathId)
|
||||
|
||||
// 保存数据
|
||||
loading.value = true
|
||||
setTimeout(() => {
|
||||
if (isEdit.value) {
|
||||
// 编辑模式
|
||||
const index = scheduleList.value.findIndex(item => item.id === scheduleForm.id)
|
||||
if (index > -1) {
|
||||
scheduleList.value[index] = {
|
||||
...scheduleForm,
|
||||
PatrolPersons,
|
||||
PatrolPathName: path?.label || ''
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 新增模式
|
||||
const newSchedule = {
|
||||
id: Date.now(), // 模拟生成ID
|
||||
...scheduleForm,
|
||||
PatrolPersons,
|
||||
PatrolPathName: path?.label || ''
|
||||
}
|
||||
scheduleList.value.unshift(newSchedule)
|
||||
total.value = scheduleList.value.length
|
||||
}
|
||||
|
||||
ElMessage.success(isEdit.value ? '修改成功' : '新增成功')
|
||||
dialogVisible.value = false
|
||||
loading.value = false
|
||||
}, 300)
|
||||
} catch (error) {
|
||||
// 表单验证失败
|
||||
}
|
||||
}
|
||||
|
||||
// 处理搜索
|
||||
const handleSearch = () => {
|
||||
// 这里可以根据关键词过滤数据
|
||||
// 目前简单处理,实际项目中可能需要重新请求数据
|
||||
}
|
||||
|
||||
// 处理刷新
|
||||
const handleRefresh = () => {
|
||||
searchKeyword.value = ''
|
||||
currentPage.value = 1
|
||||
initData()
|
||||
}
|
||||
|
||||
// 分页处理
|
||||
const handleSizeChange = (size: number) => {
|
||||
pageSize.value = size
|
||||
}
|
||||
|
||||
const handleCurrentChange = (current: number) => {
|
||||
currentPage.value = current
|
||||
}
|
||||
|
||||
// 处理行点击
|
||||
const handleRowClick = (row: any) => {
|
||||
// 可以在这里实现行点击后的操作
|
||||
}
|
||||
|
||||
// 处理选择变化
|
||||
const handleSelectionChange = (selection: any[]) => {
|
||||
// 可以在这里处理选中的数据
|
||||
}
|
||||
|
||||
// 处理对话框关闭
|
||||
const handleClose = () => {
|
||||
if (scheduleFormRef.value) {
|
||||
scheduleFormRef.value.resetFields()
|
||||
}
|
||||
}
|
||||
|
||||
// 组件挂载时初始化数据
|
||||
onMounted(() => {
|
||||
initData()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/**
|
||||
* 巡检计划管理组件样式
|
||||
*/
|
||||
.dialog-footer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
/* 标题样式 */
|
||||
.text-xl {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.text-lg {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.font-bold {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.font-medium {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.text-blue-400 {
|
||||
color: #69b1ff;
|
||||
}
|
||||
|
||||
.text-blue-300 {
|
||||
color: #91d5ff;
|
||||
}
|
||||
|
||||
.text-blue-200 {
|
||||
color: #b6dfff;
|
||||
}
|
||||
|
||||
/* 间距样式 */
|
||||
.mb-4 {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.mb-6 {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.mt-4 {
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
/* 网格布局 */
|
||||
.grid {
|
||||
display: grid;
|
||||
}
|
||||
|
||||
.grid-cols-1 {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.grid-cols-2 {
|
||||
grid-template-columns: 1fr 1fr;
|
||||
}
|
||||
|
||||
.gap-4 {
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
/* 对话框样式 */
|
||||
:deep(.el-dialog) {
|
||||
background-color: rgba(0, 58, 97, 0.4) !important; /* 空军蓝,透明度40% */
|
||||
backdrop-filter: blur(8px); /* 高斯模糊效果 */
|
||||
border: 1px solid rgba(104, 201, 255, 0.3);
|
||||
}
|
||||
|
||||
:deep(.el-dialog__header) {
|
||||
background-color: rgba(0, 58, 97, 0.6) !important;
|
||||
border-bottom: 1px solid rgba(104, 201, 255, 0.3);
|
||||
}
|
||||
|
||||
:deep(.el-dialog__title) {
|
||||
color: #91d5ff !important;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
:deep(.el-dialog__footer) {
|
||||
background-color: rgba(0, 58, 97, 0.6) !important;
|
||||
border-top: 1px solid rgba(104, 201, 255, 0.3);
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
|
||||
/* 表单样式 */
|
||||
:deep(.el-form) {
|
||||
background: transparent !important;
|
||||
}
|
||||
|
||||
:deep(.el-form-item__label) {
|
||||
color: #68c9ff !important;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* 输入框样式 */
|
||||
:deep(.el-input__wrapper) {
|
||||
background: rgba(255, 255, 255, 0.05) !important;
|
||||
border-color: #68c9ff !important;
|
||||
}
|
||||
|
||||
:deep(.el-input__inner) {
|
||||
color: #b6dfff !important;
|
||||
background: transparent !important;
|
||||
}
|
||||
|
||||
/* 表格样式 */
|
||||
:deep(.el-table) {
|
||||
background: rgba(12, 43, 77, 0.7) !important;
|
||||
border: 1px solid #68c9ff;
|
||||
backdrop-filter: blur(5px);
|
||||
}
|
||||
|
||||
:deep(.el-table__header-wrapper th) {
|
||||
background: rgba(20, 60, 110, 0.8) !important;
|
||||
color: #68c9ff !important;
|
||||
border-bottom: 1px solid #68c9ff !important;
|
||||
}
|
||||
|
||||
:deep(.el-table__body-wrapper tr) {
|
||||
background: transparent !important;
|
||||
}
|
||||
|
||||
:deep(.el-table__body-wrapper tr:nth-child(even)) {
|
||||
background: rgba(20, 60, 110, 0.3) !important;
|
||||
}
|
||||
|
||||
:deep(.el-table__body-wrapper tr:hover) {
|
||||
background: rgba(104, 201, 255, 0.2) !important;
|
||||
}
|
||||
|
||||
:deep(.el-table__body-wrapper td) {
|
||||
color: #b6dfff !important;
|
||||
border-bottom: 1px solid rgba(104, 201, 255, 0.3) !important;
|
||||
}
|
||||
|
||||
/* 选择器样式 */
|
||||
:deep(.el-select) {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
:deep(.el-select__wrapper) {
|
||||
background: rgba(255, 255, 255, 0.05) !important;
|
||||
border-color: #68c9ff !important;
|
||||
}
|
||||
|
||||
:deep(.el-select__placeholder) {
|
||||
color: #68c9ff !important;
|
||||
}
|
||||
|
||||
:deep(.el-select__input) {
|
||||
color: #b6dfff !important;
|
||||
}
|
||||
|
||||
:deep(.el-select-dropdown) {
|
||||
background: rgba(12, 43, 77, 0.95) !important;
|
||||
border: 1px solid #68c9ff !important;
|
||||
}
|
||||
|
||||
:deep(.el-select-dropdown__item) {
|
||||
color: #b6dfff !important;
|
||||
}
|
||||
|
||||
:deep(.el-select-dropdown__item:hover) {
|
||||
background: rgba(104, 201, 255, 0.2) !important;
|
||||
}
|
||||
|
||||
/* 时间选择器样式 */
|
||||
:deep(.el-time-picker) {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
:deep(.el-time-panel) {
|
||||
background: rgba(12, 43, 77, 0.95) !important;
|
||||
border: 1px solid #68c9ff !important;
|
||||
}
|
||||
|
||||
:deep(.el-time-spinner__item) {
|
||||
color: #b6dfff !important;
|
||||
}
|
||||
|
||||
:deep(.el-time-spinner__item:hover:not(.disabled)) {
|
||||
background: rgba(104, 201, 255, 0.2) !important;
|
||||
}
|
||||
|
||||
:deep(.el-time-spinner__item.active:not(.disabled)) {
|
||||
color: #68c9ff !important;
|
||||
background: rgba(104, 201, 255, 0.3) !important;
|
||||
}
|
||||
</style>
|
BIN
仓库管理系统大屏.jpg
Normal file
BIN
仓库管理系统大屏.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 539 KiB |
Reference in New Issue
Block a user