feat: ODF v1.0.2 功能更新 - 签到、干线故障、光缆管理、用户模块权限
数据库: - 新增 odf_checkin/odf_cables/odf_cable_faults/odf_cable_fault_images/odf_user_modules 5张表 - 新增菜单权限和角色分配 SQL 脚本 后台 API (.NET/SqlSugar): - 新增实体模型、DTO、Service、Controller (签到/光缆/故障/图片/用户模块) 前端 APP (UniApp): - 新增 portal/checkin/trunk/cable/fault-list/fault-detail/fault-add/trunk-search/route-plan 9个页面 - 新增 permission/checkin/trunk 服务层 - 新增 navigation/watermark 工具函数 后台管理前端 (ZR.Vue): - 新增光缆管理/干线故障管理/签到记录管理/用户模块权限 4个管理页面 - 新增对应 API 模块和表单组件
This commit is contained in:
parent
13d10f4f9b
commit
7c4d7d5978
|
|
@ -6,23 +6,23 @@
|
|||
|
||||
## 任务
|
||||
|
||||
- [ ] 1. 数据库:创建新表和索引
|
||||
- [ ] 1.1 创建 odf_checkin 签到记录表(含索引 IX_odf_checkin_RoomId、IX_odf_checkin_CheckinTime)
|
||||
- [x] 1. 数据库:创建新表和索引
|
||||
- [x] 1.1 创建 odf_checkin 签到记录表(含索引 IX_odf_checkin_RoomId、IX_odf_checkin_CheckinTime)
|
||||
- 编写 SQL Server DDL 脚本,创建表和索引
|
||||
- _需求: 2.6_
|
||||
- [ ] 1.2 创建 odf_cables 光缆表(含索引 IX_odf_cables_DeptId、IX_odf_cables_CableName)
|
||||
- [x] 1.2 创建 odf_cables 光缆表(含索引 IX_odf_cables_DeptId、IX_odf_cables_CableName)
|
||||
- 编写 SQL Server DDL 脚本,创建表和索引
|
||||
- _需求: 4.1_
|
||||
- [ ] 1.3 创建 odf_cable_faults 干线故障表(含索引 IX_odf_cable_faults_CableId、IX_odf_cable_faults_FaultTime)
|
||||
- [x] 1.3 创建 odf_cable_faults 干线故障表(含索引 IX_odf_cable_faults_CableId、IX_odf_cable_faults_FaultTime)
|
||||
- 编写 SQL Server DDL 脚本,创建表和索引
|
||||
- _需求: 5.1, 7.9_
|
||||
- [ ] 1.4 创建 odf_cable_fault_images 故障图片表(含索引 IX_odf_cable_fault_images_FaultId)
|
||||
- [x] 1.4 创建 odf_cable_fault_images 故障图片表(含索引 IX_odf_cable_fault_images_FaultId)
|
||||
- 编写 SQL Server DDL 脚本,创建表和索引
|
||||
- _需求: 7.9_
|
||||
- [ ] 1.5 创建 odf_user_modules 用户功能版块权限表(含唯一索引和普通索引)
|
||||
- [x] 1.5 创建 odf_user_modules 用户功能版块权限表(含唯一索引和普通索引)
|
||||
- 编写 SQL Server DDL 脚本,创建表和索引
|
||||
- _需求: 8.1_
|
||||
- [ ] 1.6 插入后台管理菜单和权限 SQL 脚本
|
||||
- [x] 1.6 插入后台管理菜单和权限 SQL 脚本
|
||||
- 编写 INSERT INTO sys_menu SQL 脚本,添加以下一级菜单(parentId=0)和按钮权限:
|
||||
- 一级菜单「光缆管理」(MenuId=11190,OrderNum=5)及按钮权限(odfcables:query / odfcables:add / odfcables:edit / odfcables:delete / odfcables:export)
|
||||
- 一级菜单「干线故障管理」(MenuId=11200,OrderNum=6)及按钮权限(odfcablefaults:query / odfcablefaults:delete / odfcablefaults:export)
|
||||
|
|
@ -36,26 +36,26 @@
|
|||
- Role 1 (admin):超级管理员自动拥有所有权限,无需插入
|
||||
- _需求: 8.1_
|
||||
|
||||
- [ ] 2. 检查点 — 数据库脚本完成
|
||||
- [x] 2. 检查点 — 数据库脚本完成
|
||||
- 确认所有 5 张表和索引创建成功,菜单权限 SQL 脚本执行无误,如有问题请告知。
|
||||
|
||||
- [ ] 3. 后台 API:实体模型层
|
||||
- [ ] 3.1 创建 OdfCheckin 实体类(ZR.Model/Business/OdfCheckin.cs)
|
||||
- [x] 3. 后台 API:实体模型层
|
||||
- [x] 3.1 创建 OdfCheckin 实体类(ZR.Model/Business/OdfCheckin.cs)
|
||||
- 使用 [SugarTable("odf_checkin")] 注解,定义 Id、RoomId、Personnel、CheckinTime、WorkContent、UserId、CreatedAt 属性
|
||||
- _需求: 2.6_
|
||||
- [ ] 3.2 创建 OdfCables 实体类(ZR.Model/Business/OdfCables.cs)
|
||||
- [x] 3.2 创建 OdfCables 实体类(ZR.Model/Business/OdfCables.cs)
|
||||
- 使用 [SugarTable("odf_cables")] 注解,定义 Id、CableName、DeptId、DeptName、CreatedAt、UpdatedAt 属性
|
||||
- _需求: 4.1_
|
||||
- [ ] 3.3 创建 OdfCableFaults 实体类(ZR.Model/Business/OdfCableFaults.cs)
|
||||
- [x] 3.3 创建 OdfCableFaults 实体类(ZR.Model/Business/OdfCableFaults.cs)
|
||||
- 使用 [SugarTable("odf_cable_faults")] 注解,定义所有字段属性
|
||||
- _需求: 5.1, 7.9_
|
||||
- [ ] 3.4 创建 OdfCableFaultImages 实体类(ZR.Model/Business/OdfCableFaultImages.cs)
|
||||
- [x] 3.4 创建 OdfCableFaultImages 实体类(ZR.Model/Business/OdfCableFaultImages.cs)
|
||||
- 使用 [SugarTable("odf_cable_fault_images")] 注解
|
||||
- _需求: 7.9_
|
||||
- [ ] 3.5 创建 OdfUserModules 实体类(ZR.Model/Business/OdfUserModules.cs)
|
||||
- [x] 3.5 创建 OdfUserModules 实体类(ZR.Model/Business/OdfUserModules.cs)
|
||||
- 使用 [SugarTable("odf_user_modules")] 注解
|
||||
- _需求: 8.1_
|
||||
- [ ] 3.6 创建各实体的 Dto 和 QueryDto 类
|
||||
- [x] 3.6 创建各实体的 Dto 和 QueryDto 类
|
||||
- OdfCheckinDto、OdfCheckinQueryDto(继承 PagerInfo)
|
||||
- OdfCablesQueryDto(继承 PagerInfo)
|
||||
- OdfCableFaultsQueryDto(继承 PagerInfo,含 beginFaultTime/endFaultTime)
|
||||
|
|
@ -63,8 +63,8 @@
|
|||
- OdfUserModulesSaveDto(含 userId、modules 列表)
|
||||
- _需求: 2.6, 4.1, 5.1, 7.9, 8.1_
|
||||
|
||||
- [ ] 4. 后台 API:Service 层
|
||||
- [ ] 4.1 实现 IOdfUserModulesService / OdfUserModulesService
|
||||
- [x] 4. 后台 API:Service 层
|
||||
- [x] 4.1 实现 IOdfUserModulesService / OdfUserModulesService
|
||||
- GetUserModules(userId):按 UserId 查询 odf_user_modules,返回 ModuleCode 列表
|
||||
- GetUserList():查询 sys_user 返回用户列表(userId, userName)
|
||||
- SaveUserModules(userId, modules):先删后插,事务保证原子性
|
||||
|
|
@ -73,7 +73,7 @@
|
|||
- **Property 15: 用户模块权限 round-trip**
|
||||
- 使用 FsCheck 生成随机 UserId 和模块子集,验证插入后查询结果一致
|
||||
- **验证: 需求 8.1, 8.2**
|
||||
- [ ] 4.2 实现 IOdfCheckinService / OdfCheckinService
|
||||
- [x] 4.2 实现 IOdfCheckinService / OdfCheckinService
|
||||
- AddCheckin(dto):校验 RoomId 存在,插入 odf_checkin 记录
|
||||
- GetList(queryDto):分页查询,联查 odf_rooms 获取机房名称,联查 sys_user 获取提交人用户名
|
||||
- Export(queryDto):导出签到记录
|
||||
|
|
@ -82,7 +82,7 @@
|
|||
- **Property 11: 签到数据持久化 round-trip**
|
||||
- 使用 FsCheck 生成随机签到数据,验证插入后查询字段一致
|
||||
- **验证: 需求 2.6**
|
||||
- [ ] 4.3 实现 IOdfCablesService / OdfCablesService
|
||||
- [x] 4.3 实现 IOdfCablesService / OdfCablesService
|
||||
- GetList(queryDto):按 DeptId 过滤光缆列表(支持分页和 CableName 模糊查询)
|
||||
- Search(deptId, keyword):在指定公司范围内搜索光缆和故障
|
||||
- Add/Update/Delete/GetDetail/Export:光缆 CRUD 和导出
|
||||
|
|
@ -95,7 +95,7 @@
|
|||
- **Property 13: 搜索结果 DeptId 范围限定**
|
||||
- 使用 FsCheck 生成随机数据集,验证搜索结果均属于指定 DeptId
|
||||
- **验证: 需求 4.4**
|
||||
- [ ] 4.4 实现 IOdfCableFaultsService / OdfCableFaultsService
|
||||
- [x] 4.4 实现 IOdfCableFaultsService / OdfCableFaultsService
|
||||
- GetList(queryDto):按 CableId 分页查询故障列表,联查光缆名称,按 FaultTime DESC 排序
|
||||
- GetDetail(id):查询故障详情,联查光缆名称和图片列表
|
||||
- AddFault(dto):校验 CableId 存在和图片数量,插入故障记录,保存图片文件,插入图片记录
|
||||
|
|
@ -106,25 +106,25 @@
|
|||
- **Property 14: 故障新增 round-trip(含图片)**
|
||||
- 使用 FsCheck 生成随机故障数据和图片数量,验证插入后查询一致
|
||||
- **验证: 需求 7.9**
|
||||
- [ ] 4.5 实现 IOdfCableFaultImagesService / OdfCableFaultImagesService
|
||||
- [x] 4.5 实现 IOdfCableFaultImagesService / OdfCableFaultImagesService
|
||||
- GetByFaultId(faultId):按故障 ID 查询图片列表
|
||||
- BatchInsert(faultId, imageUrls):批量插入图片记录
|
||||
- DeleteByFaultId(faultId):按故障 ID 删除所有图片记录
|
||||
- _需求: 7.9_
|
||||
|
||||
- [ ] 5. 后台 API:Controller 层
|
||||
- [ ] 5.1 创建 OdfUserModulesController(路由 business/OdfUserModules)
|
||||
- [x] 5. 后台 API:Controller 层
|
||||
- [x] 5.1 创建 OdfUserModulesController(路由 business/OdfUserModules)
|
||||
- GET list — 获取当前登录用户的功能版块列表(APP 端调用,无权限限制,登录即可)
|
||||
- GET users — 获取用户列表(管理端调用,[ActionPermissionFilter(Permission = "odfusermodules:query")])
|
||||
- GET list?userId=xxx — 获取指定用户的模块权限(管理端调用,[ActionPermissionFilter(Permission = "odfusermodules:query")])
|
||||
- POST save — 批量保存用户模块权限(管理端调用,[ActionPermissionFilter(Permission = "odfusermodules:edit")])
|
||||
- _需求: 8.1, 8.2_
|
||||
- [ ] 5.2 创建 OdfCheckinController(路由 business/OdfCheckin)
|
||||
- [x] 5.2 创建 OdfCheckinController(路由 business/OdfCheckin)
|
||||
- POST submit — 提交签到记录(APP 端调用,[ActionPermissionFilter(Permission = "odfcheckin:list")])
|
||||
- GET list — 分页查询签到记录(管理端调用,[ActionPermissionFilter(Permission = "odfcheckin:list")],联查机房名称和提交人)
|
||||
- GET export — 导出签到记录(管理端调用,[ActionPermissionFilter(Permission = "odfcheckin:export")])
|
||||
- _需求: 2.6_
|
||||
- [ ] 5.3 创建 OdfCablesController(路由 business/OdfCables)
|
||||
- [x] 5.3 创建 OdfCablesController(路由 business/OdfCables)
|
||||
- GET list — 光缆列表([ActionPermissionFilter(Permission = "odfcables:list")])
|
||||
- GET search — 搜索光缆和故障([ActionPermissionFilter(Permission = "odfcables:query")])
|
||||
- POST — 新增光缆([ActionPermissionFilter(Permission = "odfcables:add")])
|
||||
|
|
@ -133,7 +133,7 @@
|
|||
- POST delete/{id} — 删除光缆([ActionPermissionFilter(Permission = "odfcables:delete")])
|
||||
- GET export — 导出光缆([ActionPermissionFilter(Permission = "odfcables:export")])
|
||||
- _需求: 4.1, 4.4_
|
||||
- [ ] 5.4 创建 OdfCableFaultsController(路由 business/OdfCableFaults)
|
||||
- [x] 5.4 创建 OdfCableFaultsController(路由 business/OdfCableFaults)
|
||||
- GET list — 故障列表分页查询([ActionPermissionFilter(Permission = "odfcablefaults:list")])
|
||||
- GET {id} — 故障详情含图片([ActionPermissionFilter(Permission = "odfcablefaults:query")])
|
||||
- POST add — 新增故障含图片上传(APP 端调用,[ActionPermissionFilter(Permission = "odfcablefaults:list")])
|
||||
|
|
@ -141,7 +141,7 @@
|
|||
- GET export — 导出故障列表([ActionPermissionFilter(Permission = "odfcablefaults:export")])
|
||||
- _需求: 5.1, 6.1, 7.9_
|
||||
|
||||
- [ ] 6. 后台 API:依赖注入注册
|
||||
- [x] 6. 后台 API:依赖注入注册
|
||||
- 在 Startup / Program.cs 中注册所有新增 Service 的依赖注入
|
||||
- IOdfCheckinService → OdfCheckinService
|
||||
- IOdfCablesService → OdfCablesService
|
||||
|
|
@ -150,36 +150,36 @@
|
|||
- IOdfUserModulesService → OdfUserModulesService
|
||||
- _需求: 2.6, 4.1, 5.1, 7.9, 8.1_
|
||||
|
||||
- [ ] 7. 检查点 — 后台 API 完成
|
||||
- [x] 7. 检查点 — 后台 API 完成
|
||||
- 确认所有 API 编译通过,依赖注入正确,如有问题请告知。
|
||||
|
||||
- [ ] 8. 前端 APP:基础设施和服务层
|
||||
- [ ] 8.1 创建 services/permission.js — 权限 API
|
||||
- [x] 8. 前端 APP:基础设施和服务层
|
||||
- [x] 8.1 创建 services/permission.js — 权限 API
|
||||
- getUserModules():GET /business/OdfUserModules/list,返回模块代码列表
|
||||
- _需求: 8.2_
|
||||
- [ ] 8.2 创建 services/checkin.js — 签到 API
|
||||
- [x] 8.2 创建 services/checkin.js — 签到 API
|
||||
- submitCheckin(data):POST /business/OdfCheckin/submit
|
||||
- _需求: 2.6_
|
||||
- [ ] 8.3 创建 services/trunk.js — 干线版块 API
|
||||
- [x] 8.3 创建 services/trunk.js — 干线版块 API
|
||||
- getCableList(deptId):GET /business/OdfCables/list
|
||||
- getFaultList(cableId, pageNum, pageSize):GET /business/OdfCableFaults/list
|
||||
- getFaultDetail(id):GET /business/OdfCableFaults/{id}
|
||||
- addFault(formData):POST /business/OdfCableFaults/add(multipart/form-data)
|
||||
- searchCablesAndFaults(deptId, keyword):GET /business/OdfCables/search
|
||||
- _需求: 4.1, 5.1, 6.1, 7.9, 4.4_
|
||||
- [ ] 8.4 扩展 store/index.js — 新增 modules 字段
|
||||
- [x] 8.4 扩展 store/index.js — 新增 modules 字段
|
||||
- 添加 modules: [] 字段
|
||||
- 添加 setModules(modules) 方法
|
||||
- 在 clearAuth() 中清除 modules
|
||||
- _需求: 8.2, 8.3_
|
||||
- [ ] 8.5 创建 utils/navigation.js — 导航工具函数
|
||||
- [x] 8.5 创建 utils/navigation.js — 导航工具函数
|
||||
- openNavigation(lat, lng, name):安卓端弹出导航 APP 选择列表,H5 端打开地图网页
|
||||
- _需求: 6.4, 6.5, 10.4_
|
||||
- [ ]* 8.5.1 编写属性测试 Property 6:导航 URL 构建正确性
|
||||
- **Property 6: 导航 URL 构建正确性**
|
||||
- 使用 fast-check 生成随机经纬度和地点名,验证 URL 包含正确参数
|
||||
- **验证: 需求 6.5, 10.4**
|
||||
- [ ] 8.6 创建 utils/watermark.js — 水印处理工具函数
|
||||
- [x] 8.6 创建 utils/watermark.js — 水印处理工具函数
|
||||
- addWatermark(imagePath, text):Canvas 叠加水印文字到照片左下角
|
||||
- _需求: 7.8_
|
||||
- [ ]* 8.6.1 编写属性测试 Property 7:水印叠加参数正确性
|
||||
|
|
@ -187,12 +187,12 @@
|
|||
- 使用 fast-check 生成随机时间和人员字符串,验证水印文本包含两者
|
||||
- **验证: 需求 7.8**
|
||||
|
||||
- [ ] 9. 前端 APP:登录页改造和功能入口页
|
||||
- [ ] 9.1 修改 pages/login/index.vue — 登录成功后跳转至 pages/portal/index
|
||||
- [x] 9. 前端 APP:登录页改造和功能入口页
|
||||
- [x] 9.1 修改 pages/login/index.vue — 登录成功后跳转至 pages/portal/index
|
||||
- 将 uni.reLaunch 目标从 home 改为 portal
|
||||
- 登录成功后调用 getUserModules() 获取权限并存入 store.modules
|
||||
- _需求: 1.1, 8.2_
|
||||
- [ ] 9.2 新建 pages/portal/index.vue — 功能入口列表页
|
||||
- [x] 9.2 新建 pages/portal/index.vue — 功能入口列表页
|
||||
- 自定义导航栏(刷新 + 标题"功能列表" + 设置图标)
|
||||
- CSS Grid 2 列布局展示功能版块卡片(机房、干线)
|
||||
- 根据 store.modules 过滤显示的入口卡片
|
||||
|
|
@ -203,17 +203,17 @@
|
|||
- **Property 1: 权限过滤正确性**
|
||||
- 使用 fast-check 生成随机权限子集,验证过滤函数只返回有权限的版块
|
||||
- **验证: 需求 1.1, 1.5, 8.3**
|
||||
- [ ] 9.3 更新 pages.json — 注册所有新增页面
|
||||
- [x] 9.3 更新 pages.json — 注册所有新增页面
|
||||
- 添加 portal、checkin、trunk、cable、fault-list、fault-detail、fault-add、trunk-search、route-plan 页面配置
|
||||
- 所有页面 navigationStyle 设为 custom
|
||||
- _需求: 1.1_
|
||||
|
||||
- [ ] 10. 前端 APP:签到功能
|
||||
- [ ] 10.1 改造 pages/rack/index.vue — 导航栏右侧添加签到按钮
|
||||
- [x] 10. 前端 APP:签到功能
|
||||
- [x] 10.1 改造 pages/rack/index.vue — 导航栏右侧添加签到按钮
|
||||
- 将 nav-icon-placeholder 替换为蓝色【签到】按钮
|
||||
- 点击签到按钮 → navigateTo checkin 页面,传递 roomId 参数
|
||||
- _需求: 2.1, 2.2_
|
||||
- [ ] 10.2 新建 pages/checkin/index.vue — 签到页
|
||||
- [x] 10.2 新建 pages/checkin/index.vue — 签到页
|
||||
- 自定义导航栏 + 表单区域(人员输入、时间 picker、工作内容 textarea)
|
||||
- 底部固定提交按钮
|
||||
- 点击时间字段弹出 picker mode="date" 年月日选择器
|
||||
|
|
@ -225,14 +225,14 @@
|
|||
- 使用 fast-check 生成随机签到数据,验证请求体包含所有必要字段
|
||||
- **验证: 需求 2.6**
|
||||
|
||||
- [ ] 11. 前端 APP:干线版块 — 公司列表和光缆列表
|
||||
- [ ] 11.1 新建 pages/trunk/index.vue — 干线页(公司列表)
|
||||
- [x] 11. 前端 APP:干线版块 — 公司列表和光缆列表
|
||||
- [x] 11.1 新建 pages/trunk/index.vue — 干线页(公司列表)
|
||||
- 自定义导航栏 + section-title "公司列表"
|
||||
- 复用 getCompanyList() 接口获取公司数据
|
||||
- 公司卡片列表(白色背景 + 图片占位 + 公司名称居中)
|
||||
- 点击公司卡片 → navigateTo cable 页面,传递 deptId
|
||||
- _需求: 3.1, 3.2, 3.3_
|
||||
- [ ] 11.2 新建 pages/cable/index.vue — 光缆列表页
|
||||
- [x] 11.2 新建 pages/cable/index.vue — 光缆列表页
|
||||
- 自定义导航栏 + section-title "光缆列表" + 搜索栏
|
||||
- 调用 getCableList(deptId) 获取光缆数据
|
||||
- 光缆卡片列表(同公司卡片样式)
|
||||
|
|
@ -248,8 +248,8 @@
|
|||
- 使用 fast-check 生成随机列表项数据,验证导航 URL 包含正确参数
|
||||
- **验证: 需求 3.3, 4.2, 5.2, 9.4, 9.5**
|
||||
|
||||
- [ ] 12. 前端 APP:干线版块 — 故障管理
|
||||
- [ ] 12.1 新建 pages/fault-list/index.vue — 故障列表页
|
||||
- [x] 12. 前端 APP:干线版块 — 故障管理
|
||||
- [x] 12.1 新建 pages/fault-list/index.vue — 故障列表页
|
||||
- 自定义导航栏 + section-title "故障列表"
|
||||
- 调用 getFaultList(cableId) 获取故障数据,支持分页加载(onReachBottom)
|
||||
- 故障卡片列表(显示故障时间、故障原因、表显故障里程、所属光缆)
|
||||
|
|
@ -261,7 +261,7 @@
|
|||
- **Property 3: 故障记录渲染完整性**
|
||||
- 使用 fast-check 生成随机故障数据,验证渲染输出包含所有必要字段
|
||||
- **验证: 需求 5.1, 6.1**
|
||||
- [ ] 12.2 新建 pages/fault-detail/index.vue — 故障详情页
|
||||
- [x] 12.2 新建 pages/fault-detail/index.vue — 故障详情页
|
||||
- 自定义导航栏 + 图片区域(横向滚动,点击预览)+ 信息展示区域
|
||||
- 调用 getFaultDetail(faultId) 获取完整数据
|
||||
- 展示所有字段:图片列表、故障时间、人员、故障原因、表显故障里程、所属光缆、地点、备注
|
||||
|
|
@ -269,7 +269,7 @@
|
|||
- 点击图片 → uni.previewImage 全屏预览
|
||||
- 点击导航 → 调用 openNavigation(lat, lng, name)
|
||||
- _需求: 6.1, 6.2, 6.3, 6.4, 6.5, 6.6_
|
||||
- [ ] 12.3 新建 pages/fault-add/index.vue — 新增故障页
|
||||
- [x] 12.3 新建 pages/fault-add/index.vue — 新增故障页
|
||||
- 自定义导航栏 + 拍照区域(横向滚动,仅相机拍摄)+ 表单区域
|
||||
- 拍摄第 1 张照片时自动获取时间填充故障时间字段
|
||||
- 自动填充所属光缆名称(从参数传入,不可编辑)
|
||||
|
|
@ -283,8 +283,8 @@
|
|||
- 使用 fast-check 生成随机故障表单数据,验证请求体包含所有必要字段
|
||||
- **验证: 需求 7.9**
|
||||
|
||||
- [ ] 13. 前端 APP:搜索结果页
|
||||
- [ ] 13.1 新建 pages/trunk-search/index.vue — 搜索结果页
|
||||
- [x] 13. 前端 APP:搜索结果页
|
||||
- [x] 13.1 新建 pages/trunk-search/index.vue — 搜索结果页
|
||||
- 自定义导航栏 + 搜索结果区域
|
||||
- 页面 onLoad 从参数获取 deptId 和 keyword,调用 searchCablesAndFaults 接口
|
||||
- 按「光缆」和「故障列表」两个分类展示结果
|
||||
|
|
@ -297,8 +297,8 @@
|
|||
- 使用 fast-check 生成随机搜索结果数据,验证分类展示逻辑正确
|
||||
- **验证: 需求 9.2, 9.3**
|
||||
|
||||
- [ ] 14. 前端 APP:路线规划(尝试性)
|
||||
- [ ] 14.1 新建 pages/route-plan/index.vue — 路线规划页
|
||||
- [x] 14. 前端 APP:路线规划(尝试性)
|
||||
- [x] 14.1 新建 pages/route-plan/index.vue — 路线规划页
|
||||
- 集成地图 SDK(如高德地图 UniApp 插件)
|
||||
- 输入起终点坐标,调用路线规划 API,展示路线总长度
|
||||
- 输入距离值,计算路线上对应坐标并标注
|
||||
|
|
@ -309,32 +309,32 @@
|
|||
- 使用 fast-check 生成随机路线点序列和距离值,验证定位计算正确
|
||||
- **验证: 需求 11.3**
|
||||
|
||||
- [ ] 15. 检查点 — 前端 APP 完成
|
||||
- [x] 15. 检查点 — 前端 APP 完成
|
||||
- 确认所有新增页面正常渲染,页面跳转和参数传递正确,API 调用正常,如有问题请告知。
|
||||
|
||||
- [ ] 16. 后台管理前端:API 层
|
||||
- [ ] 16.1 创建 api/business/odfcables.js — 光缆管理 API
|
||||
- [x] 16. 后台管理前端:API 层
|
||||
- [x] 16.1 创建 api/business/odfcables.js — 光缆管理 API
|
||||
- listOdfCables / addOdfCables / updateOdfCables / getOdfCables / delOdfCables / exportOdfCables
|
||||
- _需求: 4.1_
|
||||
- [ ] 16.2 创建 api/business/odfcablefaults.js — 干线故障管理 API
|
||||
- [x] 16.2 创建 api/business/odfcablefaults.js — 干线故障管理 API
|
||||
- listOdfCableFaults / getOdfCableFaults / delOdfCableFaults / exportOdfCableFaults
|
||||
- _需求: 5.1_
|
||||
- [ ] 16.3 创建 api/business/odfcheckin.js — 签到记录管理 API
|
||||
- [x] 16.3 创建 api/business/odfcheckin.js — 签到记录管理 API
|
||||
- listOdfCheckin / exportOdfCheckin
|
||||
- _需求: 2.6_
|
||||
- [ ] 16.4 创建 api/business/odfusermodules.js — 用户模块权限管理 API
|
||||
- [x] 16.4 创建 api/business/odfusermodules.js — 用户模块权限管理 API
|
||||
- listUsers / getUserModules / saveUserModules
|
||||
- _需求: 8.1_
|
||||
|
||||
- [ ] 17. 后台管理前端:光缆管理页面
|
||||
- [ ] 17.1 创建 views/business/OdfCables.vue — 光缆管理页面
|
||||
- [x] 17. 后台管理前端:光缆管理页面
|
||||
- [x] 17.1 创建 views/business/OdfCables.vue — 光缆管理页面
|
||||
- 查询表单(光缆名称 el-input + 所属部门 el-tree-select)
|
||||
- 工具栏按钮(新增/编辑/删除/导出,v-hasPermi 权限控制)
|
||||
- el-table 展示光缆列表(Id、光缆名称、所属部门、创建时间)
|
||||
- 操作列:详情、编辑、删除
|
||||
- 分页组件
|
||||
- _需求: 4.1_
|
||||
- [ ] 17.2 创建 components/business/OdfCableForm.vue — 光缆表单组件
|
||||
- [x] 17.2 创建 components/business/OdfCableForm.vue — 光缆表单组件
|
||||
- 支持 add / edit / view 三种模式
|
||||
- 表单字段:光缆名称(必填)、所属部门(el-tree-select)
|
||||
- v-model:visible 控制弹窗显隐,@success 回调刷新列表
|
||||
|
|
@ -344,8 +344,8 @@
|
|||
- 使用 fast-check 生成随机光缆名称和部门 ID,验证请求体字段完整
|
||||
- **验证: 后台管理前端 — 光缆管理**
|
||||
|
||||
- [ ] 18. 后台管理前端:干线故障管理页面
|
||||
- [ ] 18.1 创建 views/business/OdfCableFaults.vue — 干线故障管理页面
|
||||
- [x] 18. 后台管理前端:干线故障管理页面
|
||||
- [x] 18.1 创建 views/business/OdfCableFaults.vue — 干线故障管理页面
|
||||
- 查询表单(所属光缆 el-select 远程搜索 + 故障时间范围 el-date-picker + 故障原因 el-input)
|
||||
- 工具栏按钮(删除/导出,无新增/编辑)
|
||||
- el-table 展示故障列表(Id、故障时间、人员、故障原因、表显故障里程、所属光缆、地点、创建时间)
|
||||
|
|
@ -358,8 +358,8 @@
|
|||
- 使用 fast-check 生成随机查询条件组合,验证 API 请求参数完整
|
||||
- **验证: 后台管理前端 — 光缆管理、故障管理、签到记录管理**
|
||||
|
||||
- [ ] 19. 后台管理前端:签到记录管理页面
|
||||
- [ ] 19.1 创建 views/business/OdfCheckin.vue — 签到记录管理页面
|
||||
- [x] 19. 后台管理前端:签到记录管理页面
|
||||
- [x] 19.1 创建 views/business/OdfCheckin.vue — 签到记录管理页面
|
||||
- 查询表单(机房 el-select 远程搜索 + 人员 el-input + 签到时间范围 el-date-picker)
|
||||
- 工具栏按钮(仅导出,无增删改)
|
||||
- el-table 展示签到记录(Id、机房名称、人员、签到时间、工作内容、提交人、创建时间)
|
||||
|
|
@ -367,8 +367,8 @@
|
|||
- 分页组件
|
||||
- _需求: 2.6_
|
||||
|
||||
- [ ] 20. 后台管理前端:用户模块权限管理页面
|
||||
- [ ] 20.1 创建 views/business/OdfUserModules.vue — 用户模块权限管理页面
|
||||
- [x] 20. 后台管理前端:用户模块权限管理页面
|
||||
- [x] 20.1 创建 views/business/OdfUserModules.vue — 用户模块权限管理页面
|
||||
- 左右分栏布局(左侧 360px 用户列表 + 右侧模块权限配置)
|
||||
- 左侧:搜索框 + el-table 用户列表,点击行高亮选中
|
||||
- 右侧:标题"用户名 - 模块权限配置" + el-checkbox-group(odf/trunk/route)+ 保存按钮
|
||||
|
|
@ -386,7 +386,7 @@
|
|||
- 使用 fast-check 生成随机 userId 和模块子集,验证请求体正确
|
||||
- **验证: 后台管理前端 — 用户模块权限管理**
|
||||
|
||||
- [ ] 21. 后台管理前端:路由配置
|
||||
- [x] 21. 后台管理前端:路由配置
|
||||
- ZRAdmin 使用动态路由(从 sys_menu 读取 Component 字段自动生成路由),因此无需手动配置 Vue Router
|
||||
- 任务 1.6 的菜单 SQL 脚本已配置 Component 指向对应 Vue 文件,执行后即可通过菜单访问
|
||||
- 仅需确认 4 个 Vue 文件路径与菜单 Component 一致:
|
||||
|
|
@ -396,7 +396,7 @@
|
|||
- business/OdfUserModules → views/business/OdfUserModules.vue
|
||||
- _需求: 8.1_
|
||||
|
||||
- [ ] 22. 最终检查点 — 全部完成
|
||||
- [x] 22. 最终检查点 — 全部完成
|
||||
- 确认所有功能模块正常工作:数据库表、后台 API、前端 APP 页面、后台管理前端页面
|
||||
- 确认菜单权限 SQL 脚本已执行,后台管理页面可通过菜单访问
|
||||
- 确认所有测试通过,如有问题请告知。
|
||||
|
|
|
|||
|
|
@ -39,6 +39,42 @@
|
|||
{
|
||||
"path": "pages/change-password/index",
|
||||
"style": { "navigationStyle": "custom", "navigationBarTitleText": "" }
|
||||
},
|
||||
{
|
||||
"path": "pages/portal/index",
|
||||
"style": { "navigationStyle": "custom", "navigationBarTitleText": "" }
|
||||
},
|
||||
{
|
||||
"path": "pages/checkin/index",
|
||||
"style": { "navigationStyle": "custom", "navigationBarTitleText": "" }
|
||||
},
|
||||
{
|
||||
"path": "pages/trunk/index",
|
||||
"style": { "navigationStyle": "custom", "navigationBarTitleText": "" }
|
||||
},
|
||||
{
|
||||
"path": "pages/cable/index",
|
||||
"style": { "navigationStyle": "custom", "navigationBarTitleText": "" }
|
||||
},
|
||||
{
|
||||
"path": "pages/fault-list/index",
|
||||
"style": { "navigationStyle": "custom", "navigationBarTitleText": "" }
|
||||
},
|
||||
{
|
||||
"path": "pages/fault-detail/index",
|
||||
"style": { "navigationStyle": "custom", "navigationBarTitleText": "" }
|
||||
},
|
||||
{
|
||||
"path": "pages/fault-add/index",
|
||||
"style": { "navigationStyle": "custom", "navigationBarTitleText": "" }
|
||||
},
|
||||
{
|
||||
"path": "pages/trunk-search/index",
|
||||
"style": { "navigationStyle": "custom", "navigationBarTitleText": "" }
|
||||
},
|
||||
{
|
||||
"path": "pages/route-plan/index",
|
||||
"style": { "navigationStyle": "custom", "navigationBarTitleText": "" }
|
||||
}
|
||||
],
|
||||
"globalStyle": {
|
||||
|
|
|
|||
216
odf-uniapp/pages/cable/index.vue
Normal file
216
odf-uniapp/pages/cable/index.vue
Normal file
|
|
@ -0,0 +1,216 @@
|
|||
<template>
|
||||
<view class="cable-page">
|
||||
<image class="bg-image" src="/static/images/home_bg.png" mode="aspectFill" />
|
||||
|
||||
<view class="content">
|
||||
<!-- 顶部导航栏 -->
|
||||
<view class="nav-bar" :style="{ paddingTop: statusBarHeight + 'px' }">
|
||||
<view class="nav-bar-inner">
|
||||
<image
|
||||
class="nav-icon"
|
||||
src="/static/images/ic_back.png"
|
||||
mode="aspectFit"
|
||||
@click="goBack"
|
||||
/>
|
||||
<text class="nav-title">干线</text>
|
||||
<view class="nav-icon-placeholder" />
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 小标题 -->
|
||||
<text class="section-title">光缆列表</text>
|
||||
|
||||
<!-- 搜索栏 -->
|
||||
<view class="search-bar">
|
||||
<image class="search-icon" src="/static/images/ic_search.png" mode="aspectFit" />
|
||||
<input
|
||||
class="search-input"
|
||||
v-model="keyword"
|
||||
placeholder="只支持搜索本公司光缆和故障信息"
|
||||
placeholder-class="search-placeholder"
|
||||
confirm-type="search"
|
||||
@confirm="handleSearch"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<!-- 光缆列表 -->
|
||||
<scroll-view class="cable-list" scroll-y>
|
||||
<view
|
||||
class="cable-card"
|
||||
v-for="item in cableList"
|
||||
:key="item.id"
|
||||
@click="goFaultList(item)"
|
||||
>
|
||||
<view class="cable-image" />
|
||||
<text class="cable-name">{{ item.cableName }}</text>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import { onLoad, onPullDownRefresh } from '@dcloudio/uni-app'
|
||||
import { getCableList } from '@/services/trunk'
|
||||
|
||||
const statusBarHeight = uni.getSystemInfoSync().statusBarHeight || 0
|
||||
const cableList = ref([])
|
||||
const deptId = ref('')
|
||||
const keyword = ref('')
|
||||
|
||||
async function loadCableList() {
|
||||
const res = await getCableList(deptId.value)
|
||||
if (res.code === 200) {
|
||||
cableList.value = res.data || []
|
||||
}
|
||||
}
|
||||
|
||||
function goBack() {
|
||||
uni.navigateBack()
|
||||
}
|
||||
|
||||
function handleSearch() {
|
||||
const kw = keyword.value.trim()
|
||||
if (!kw) return
|
||||
uni.navigateTo({
|
||||
url: '/pages/trunk-search/index?deptId=' + deptId.value + '&keyword=' + encodeURIComponent(kw)
|
||||
})
|
||||
}
|
||||
|
||||
function goFaultList(item) {
|
||||
uni.navigateTo({
|
||||
url: '/pages/fault-list/index?cableId=' + item.id + '&cableName=' + encodeURIComponent(item.cableName)
|
||||
})
|
||||
}
|
||||
|
||||
onLoad((options) => {
|
||||
if (options.deptId) {
|
||||
deptId.value = options.deptId
|
||||
}
|
||||
loadCableList()
|
||||
})
|
||||
|
||||
onPullDownRefresh(() => {
|
||||
loadCableList().finally(() => {
|
||||
uni.stopPullDownRefresh()
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.cable-page {
|
||||
position: relative;
|
||||
min-height: 100vh;
|
||||
background-color: #F5F5F5;
|
||||
}
|
||||
|
||||
.bg-image {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 500rpx;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.content {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.nav-bar {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.nav-bar-inner {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
height: 88rpx;
|
||||
padding: 0 24rpx;
|
||||
}
|
||||
|
||||
.nav-icon {
|
||||
width: 44rpx;
|
||||
height: 44rpx;
|
||||
}
|
||||
|
||||
.nav-icon-placeholder {
|
||||
width: 44rpx;
|
||||
height: 44rpx;
|
||||
}
|
||||
|
||||
.nav-title {
|
||||
font-size: 34rpx;
|
||||
font-weight: 600;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 30rpx;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
padding: 16rpx 24rpx 8rpx;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.search-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin: 16rpx 24rpx;
|
||||
padding: 0 24rpx;
|
||||
height: 72rpx;
|
||||
background: #fff;
|
||||
border-radius: 36rpx;
|
||||
}
|
||||
|
||||
.search-icon {
|
||||
width: 32rpx;
|
||||
height: 32rpx;
|
||||
margin-right: 16rpx;
|
||||
}
|
||||
|
||||
.search-input {
|
||||
flex: 1;
|
||||
font-size: 26rpx;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.search-placeholder {
|
||||
color: #999;
|
||||
font-size: 26rpx;
|
||||
}
|
||||
|
||||
.cable-list {
|
||||
padding: 0 0 24rpx;
|
||||
height: calc(100vh - 500rpx);
|
||||
}
|
||||
|
||||
.cable-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
margin: 0 24rpx 20rpx;
|
||||
padding: 24rpx;
|
||||
background-color: #fff;
|
||||
border-radius: 12rpx;
|
||||
border: 1rpx solid #E8E8E8;
|
||||
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.cable-image {
|
||||
width: 100%;
|
||||
height: 160rpx;
|
||||
background: #F0F0F0;
|
||||
border-radius: 8rpx;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.cable-name {
|
||||
font-size: 30rpx;
|
||||
color: #333;
|
||||
font-weight: 500;
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
274
odf-uniapp/pages/checkin/index.vue
Normal file
274
odf-uniapp/pages/checkin/index.vue
Normal file
|
|
@ -0,0 +1,274 @@
|
|||
<template>
|
||||
<view class="checkin-page">
|
||||
<image class="bg-image" src="/static/images/home_bg.png" mode="aspectFill" />
|
||||
|
||||
<view class="content">
|
||||
<!-- 顶部导航栏 -->
|
||||
<view class="nav-bar" :style="{ paddingTop: statusBarHeight + 'px' }">
|
||||
<view class="nav-bar-inner">
|
||||
<image
|
||||
class="nav-icon"
|
||||
src="/static/images/ic_back.png"
|
||||
mode="aspectFit"
|
||||
@click="goBack"
|
||||
/>
|
||||
<text class="nav-title">签到</text>
|
||||
<view class="nav-icon-placeholder" />
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 表单区域 -->
|
||||
<view class="form-area">
|
||||
<view class="form-group">
|
||||
<text class="form-label">人员</text>
|
||||
<input
|
||||
class="form-input"
|
||||
v-model="form.personnel"
|
||||
placeholder="请输入"
|
||||
placeholder-class="input-placeholder"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<view class="form-group">
|
||||
<text class="form-label">时间</text>
|
||||
<picker mode="date" :value="form.checkinTime" @change="onDateChange">
|
||||
<view class="form-picker">
|
||||
<text :class="['picker-text', form.checkinTime ? 'picker-text-active' : '']">
|
||||
{{ form.checkinTime || '请选择年月日' }}
|
||||
</text>
|
||||
<text class="picker-arrow">▼</text>
|
||||
</view>
|
||||
</picker>
|
||||
</view>
|
||||
|
||||
<view class="form-group">
|
||||
<text class="form-label">工作内容</text>
|
||||
<textarea
|
||||
class="form-textarea"
|
||||
v-model="form.workContent"
|
||||
placeholder="请输入"
|
||||
placeholder-class="input-placeholder"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 底部固定提交按钮 -->
|
||||
<view class="bottom-bar">
|
||||
<view class="submit-btn" @click="handleSubmit">
|
||||
<text class="submit-btn-text">提交</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive } from 'vue'
|
||||
import { onLoad } from '@dcloudio/uni-app'
|
||||
import { submitCheckin } from '@/services/checkin'
|
||||
|
||||
const statusBarHeight = uni.getSystemInfoSync().statusBarHeight || 0
|
||||
const roomId = ref('')
|
||||
const submitting = ref(false)
|
||||
|
||||
const form = reactive({
|
||||
personnel: '',
|
||||
checkinTime: '',
|
||||
workContent: ''
|
||||
})
|
||||
|
||||
function goBack() {
|
||||
uni.navigateBack()
|
||||
}
|
||||
|
||||
function onDateChange(e) {
|
||||
form.checkinTime = e.detail.value
|
||||
}
|
||||
|
||||
async function handleSubmit() {
|
||||
if (!form.personnel.trim()) {
|
||||
uni.showToast({ title: '请输入人员', icon: 'none' })
|
||||
return
|
||||
}
|
||||
if (!form.checkinTime) {
|
||||
uni.showToast({ title: '请选择时间', icon: 'none' })
|
||||
return
|
||||
}
|
||||
if (!form.workContent.trim()) {
|
||||
uni.showToast({ title: '请输入工作内容', icon: 'none' })
|
||||
return
|
||||
}
|
||||
|
||||
if (submitting.value) return
|
||||
submitting.value = true
|
||||
|
||||
try {
|
||||
const res = await submitCheckin({
|
||||
roomId: roomId.value,
|
||||
personnel: form.personnel.trim(),
|
||||
checkinTime: form.checkinTime,
|
||||
workContent: form.workContent.trim()
|
||||
})
|
||||
if (res.code === 200) {
|
||||
uni.showToast({ title: '提交成功', icon: 'success' })
|
||||
setTimeout(() => {
|
||||
uni.navigateBack()
|
||||
}, 1500)
|
||||
} else {
|
||||
uni.showToast({ title: res.msg || '提交失败', icon: 'none' })
|
||||
}
|
||||
} catch (err) {
|
||||
uni.showToast({ title: '网络异常,请重试', icon: 'none' })
|
||||
} finally {
|
||||
submitting.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onLoad((options) => {
|
||||
if (options.roomId) {
|
||||
roomId.value = options.roomId
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.checkin-page {
|
||||
position: relative;
|
||||
min-height: 100vh;
|
||||
background-color: #F5F5F5;
|
||||
}
|
||||
|
||||
.bg-image {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 500rpx;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.content {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.nav-bar {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.nav-bar-inner {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
height: 88rpx;
|
||||
padding: 0 24rpx;
|
||||
}
|
||||
|
||||
.nav-icon {
|
||||
width: 44rpx;
|
||||
height: 44rpx;
|
||||
}
|
||||
|
||||
.nav-icon-placeholder {
|
||||
width: 44rpx;
|
||||
height: 44rpx;
|
||||
}
|
||||
|
||||
.nav-title {
|
||||
font-size: 34rpx;
|
||||
font-weight: 600;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.form-area {
|
||||
padding: 24rpx;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 32rpx;
|
||||
}
|
||||
|
||||
.form-label {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
margin-bottom: 12rpx;
|
||||
font-weight: 500;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.form-input {
|
||||
height: 80rpx;
|
||||
padding: 0 24rpx;
|
||||
background: #fff;
|
||||
border-radius: 12rpx;
|
||||
border: 1rpx solid #E8E8E8;
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.form-picker {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
height: 80rpx;
|
||||
padding: 0 24rpx;
|
||||
background: #fff;
|
||||
border-radius: 12rpx;
|
||||
border: 1rpx solid #E8E8E8;
|
||||
}
|
||||
|
||||
.picker-text {
|
||||
font-size: 28rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.picker-text-active {
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.picker-arrow {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.form-textarea {
|
||||
min-height: 200rpx;
|
||||
padding: 24rpx;
|
||||
background: #fff;
|
||||
border-radius: 12rpx;
|
||||
border: 1rpx solid #E8E8E8;
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.input-placeholder {
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.bottom-bar {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
padding: 24rpx;
|
||||
background: #fff;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.submit-btn {
|
||||
width: 100%;
|
||||
height: 88rpx;
|
||||
background: #1A73EC;
|
||||
border-radius: 20rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.submit-btn-text {
|
||||
color: #fff;
|
||||
font-size: 32rpx;
|
||||
}
|
||||
</style>
|
||||
440
odf-uniapp/pages/fault-add/index.vue
Normal file
440
odf-uniapp/pages/fault-add/index.vue
Normal file
|
|
@ -0,0 +1,440 @@
|
|||
<template>
|
||||
<view class="fault-add-page">
|
||||
<image class="bg-image" src="/static/images/home_bg.png" mode="aspectFill" />
|
||||
|
||||
<view class="content">
|
||||
<!-- 顶部导航栏 -->
|
||||
<view class="nav-bar" :style="{ paddingTop: statusBarHeight + 'px' }">
|
||||
<view class="nav-bar-inner">
|
||||
<image
|
||||
class="nav-icon"
|
||||
src="/static/images/ic_back.png"
|
||||
mode="aspectFit"
|
||||
@click="goBack"
|
||||
/>
|
||||
<text class="nav-title">新增故障</text>
|
||||
<view class="nav-icon-placeholder" />
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 拍照区域 -->
|
||||
<view class="photo-area">
|
||||
<scroll-view class="photo-scroll" scroll-x>
|
||||
<view class="photo-list">
|
||||
<view class="photo-add-btn" @click="takePhoto">
|
||||
<text class="plus-icon">+</text>
|
||||
<text class="add-text">点击拍摄</text>
|
||||
</view>
|
||||
<image
|
||||
class="photo-thumb"
|
||||
v-for="(photo, index) in photoList"
|
||||
:key="index"
|
||||
:src="photo"
|
||||
mode="aspectFill"
|
||||
/>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
|
||||
<!-- 表单区域 -->
|
||||
<view class="form-area">
|
||||
<view class="form-group">
|
||||
<text class="form-label">故障时间</text>
|
||||
<view class="form-display">
|
||||
<text class="display-text">{{ form.faultTime || '拍摄第一张照片后自动填充' }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="form-group">
|
||||
<text class="form-label">人员</text>
|
||||
<input
|
||||
class="form-input"
|
||||
v-model="form.personnel"
|
||||
placeholder="请输入"
|
||||
placeholder-class="input-placeholder"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<view class="form-group">
|
||||
<text class="form-label">故障原因</text>
|
||||
<input
|
||||
class="form-input"
|
||||
v-model="form.faultReason"
|
||||
placeholder="请输入"
|
||||
placeholder-class="input-placeholder"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<view class="form-group">
|
||||
<text class="form-label">表显故障里程</text>
|
||||
<input
|
||||
class="form-input"
|
||||
v-model="form.mileage"
|
||||
placeholder="请输入"
|
||||
placeholder-class="input-placeholder"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<view class="form-group">
|
||||
<text class="form-label">所属光缆</text>
|
||||
<view class="form-display">
|
||||
<text class="display-text">{{ form.cableName }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="form-group">
|
||||
<text class="form-label">地点</text>
|
||||
<view class="location-btn" @click="getLocation">
|
||||
<text class="location-btn-text">点击获取当前经纬度</text>
|
||||
</view>
|
||||
<text class="location-text">当前经度:{{ form.longitude }} 当前纬度:{{ form.latitude }}</text>
|
||||
</view>
|
||||
|
||||
<view class="form-group">
|
||||
<text class="form-label">备注</text>
|
||||
<textarea
|
||||
class="form-textarea"
|
||||
v-model="form.remark"
|
||||
placeholder="请输入"
|
||||
placeholder-class="input-placeholder"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 底部固定提交按钮 -->
|
||||
<view class="bottom-bar">
|
||||
<view class="submit-btn" @click="handleSubmit">
|
||||
<text class="submit-btn-text">提交故障</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive } from 'vue'
|
||||
import { onLoad } from '@dcloudio/uni-app'
|
||||
import { addFault } from '@/services/trunk'
|
||||
import { addWatermark } from '@/utils/watermark'
|
||||
|
||||
const statusBarHeight = uni.getSystemInfoSync().statusBarHeight || 0
|
||||
const photoList = ref([])
|
||||
const cableId = ref('')
|
||||
const submitting = ref(false)
|
||||
|
||||
const form = reactive({
|
||||
faultTime: '',
|
||||
personnel: '',
|
||||
faultReason: '',
|
||||
mileage: '',
|
||||
cableName: '',
|
||||
latitude: 0,
|
||||
longitude: 0,
|
||||
remark: ''
|
||||
})
|
||||
|
||||
function goBack() {
|
||||
uni.navigateBack()
|
||||
}
|
||||
|
||||
function takePhoto() {
|
||||
uni.chooseImage({
|
||||
count: 1,
|
||||
sourceType: ['camera'],
|
||||
success(res) {
|
||||
const tempPath = res.tempFilePaths[0]
|
||||
photoList.value.push(tempPath)
|
||||
// 第一张照片自动填充故障时间
|
||||
if (photoList.value.length === 1) {
|
||||
const now = new Date()
|
||||
const y = now.getFullYear()
|
||||
const m = String(now.getMonth() + 1).padStart(2, '0')
|
||||
const d = String(now.getDate()).padStart(2, '0')
|
||||
const h = String(now.getHours()).padStart(2, '0')
|
||||
const min = String(now.getMinutes()).padStart(2, '0')
|
||||
form.faultTime = `${y}/${m}/${d} ${h}:${min}`
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function getLocation() {
|
||||
uni.getLocation({
|
||||
type: 'gcj02',
|
||||
success(res) {
|
||||
form.latitude = res.latitude
|
||||
form.longitude = res.longitude
|
||||
uni.showToast({ title: '获取成功', icon: 'success' })
|
||||
},
|
||||
fail() {
|
||||
uni.showToast({ title: '获取位置失败', icon: 'none' })
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async function handleSubmit() {
|
||||
if (photoList.value.length === 0) {
|
||||
uni.showToast({ title: '请至少拍摄一张照片', icon: 'none' })
|
||||
return
|
||||
}
|
||||
|
||||
if (submitting.value) return
|
||||
submitting.value = true
|
||||
|
||||
try {
|
||||
// 水印处理
|
||||
const watermarkText = `${form.faultTime} ${form.personnel}`
|
||||
const watermarkedPhotos = []
|
||||
for (const photo of photoList.value) {
|
||||
try {
|
||||
const result = await addWatermark(photo, watermarkText)
|
||||
watermarkedPhotos.push(result)
|
||||
} catch (err) {
|
||||
// 水印失败则使用原图
|
||||
watermarkedPhotos.push(photo)
|
||||
}
|
||||
}
|
||||
|
||||
// 构建上传数据
|
||||
const files = watermarkedPhotos.map((path, index) => ({
|
||||
name: 'images',
|
||||
uri: path
|
||||
}))
|
||||
|
||||
const formData = {
|
||||
files,
|
||||
data: {
|
||||
cableId: cableId.value,
|
||||
faultTime: form.faultTime,
|
||||
personnel: form.personnel,
|
||||
faultReason: form.faultReason,
|
||||
mileage: form.mileage,
|
||||
latitude: String(form.latitude),
|
||||
longitude: String(form.longitude),
|
||||
remark: form.remark
|
||||
}
|
||||
}
|
||||
|
||||
const res = await addFault(formData)
|
||||
if (res.code === 200) {
|
||||
uni.showToast({ title: '提交成功', icon: 'success' })
|
||||
setTimeout(() => {
|
||||
uni.navigateBack()
|
||||
}, 1500)
|
||||
} else {
|
||||
uni.showToast({ title: res.msg || '提交失败', icon: 'none' })
|
||||
}
|
||||
} catch (err) {
|
||||
uni.showToast({ title: '网络异常,请重试', icon: 'none' })
|
||||
} finally {
|
||||
submitting.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onLoad((options) => {
|
||||
if (options.cableId) {
|
||||
cableId.value = options.cableId
|
||||
}
|
||||
if (options.cableName) {
|
||||
form.cableName = decodeURIComponent(options.cableName)
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.fault-add-page {
|
||||
position: relative;
|
||||
min-height: 100vh;
|
||||
background-color: #F5F5F5;
|
||||
padding-bottom: 120rpx;
|
||||
}
|
||||
|
||||
.bg-image {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 500rpx;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.content {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.nav-bar {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.nav-bar-inner {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
height: 88rpx;
|
||||
padding: 0 24rpx;
|
||||
}
|
||||
|
||||
.nav-icon {
|
||||
width: 44rpx;
|
||||
height: 44rpx;
|
||||
}
|
||||
|
||||
.nav-icon-placeholder {
|
||||
width: 44rpx;
|
||||
height: 44rpx;
|
||||
}
|
||||
|
||||
.nav-title {
|
||||
font-size: 34rpx;
|
||||
font-weight: 600;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.photo-area {
|
||||
padding: 24rpx;
|
||||
}
|
||||
|
||||
.photo-scroll {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.photo-list {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.photo-add-btn {
|
||||
width: 200rpx;
|
||||
height: 200rpx;
|
||||
background: #fff;
|
||||
border: 2rpx dashed #CCCCCC;
|
||||
border-radius: 12rpx;
|
||||
display: inline-flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.plus-icon {
|
||||
font-size: 48rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.add-text {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
margin-top: 8rpx;
|
||||
}
|
||||
|
||||
.photo-thumb {
|
||||
width: 200rpx;
|
||||
height: 200rpx;
|
||||
border-radius: 12rpx;
|
||||
margin-left: 16rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.form-area {
|
||||
padding: 0 24rpx;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 32rpx;
|
||||
}
|
||||
|
||||
.form-label {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
margin-bottom: 12rpx;
|
||||
font-weight: 500;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.form-input {
|
||||
height: 80rpx;
|
||||
padding: 0 24rpx;
|
||||
background: #fff;
|
||||
border-radius: 12rpx;
|
||||
border: 1rpx solid #E8E8E8;
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.form-display {
|
||||
height: 80rpx;
|
||||
padding: 0 24rpx;
|
||||
background: #F5F5F5;
|
||||
border-radius: 12rpx;
|
||||
border: 1rpx solid #E8E8E8;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.display-text {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.form-textarea {
|
||||
min-height: 200rpx;
|
||||
padding: 24rpx;
|
||||
background: #fff;
|
||||
border-radius: 12rpx;
|
||||
border: 1rpx solid #E8E8E8;
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.input-placeholder {
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.location-btn {
|
||||
background: #1A73EC;
|
||||
border-radius: 12rpx;
|
||||
padding: 16rpx 0;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.location-btn-text {
|
||||
color: #fff;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
.location-text {
|
||||
font-size: 26rpx;
|
||||
color: #999;
|
||||
margin-top: 12rpx;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.bottom-bar {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
padding: 24rpx;
|
||||
background: #fff;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.submit-btn {
|
||||
width: 100%;
|
||||
height: 88rpx;
|
||||
background: #1A73EC;
|
||||
border-radius: 20rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.submit-btn-text {
|
||||
color: #fff;
|
||||
font-size: 32rpx;
|
||||
}
|
||||
</style>
|
||||
274
odf-uniapp/pages/fault-detail/index.vue
Normal file
274
odf-uniapp/pages/fault-detail/index.vue
Normal file
|
|
@ -0,0 +1,274 @@
|
|||
<template>
|
||||
<view class="fault-detail-page">
|
||||
<image class="bg-image" src="/static/images/home_bg.png" mode="aspectFill" />
|
||||
|
||||
<view class="content">
|
||||
<!-- 顶部导航栏 -->
|
||||
<view class="nav-bar" :style="{ paddingTop: statusBarHeight + 'px' }">
|
||||
<view class="nav-bar-inner">
|
||||
<image
|
||||
class="nav-icon"
|
||||
src="/static/images/ic_back.png"
|
||||
mode="aspectFit"
|
||||
@click="goBack"
|
||||
/>
|
||||
<text class="nav-title">故障详情</text>
|
||||
<view class="nav-icon-placeholder" />
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 图片区域 -->
|
||||
<view class="image-area" v-if="imageList.length > 0">
|
||||
<scroll-view class="image-scroll" scroll-x>
|
||||
<view class="image-grid">
|
||||
<image
|
||||
class="image-item"
|
||||
v-for="(img, index) in imageList"
|
||||
:key="index"
|
||||
:src="img"
|
||||
mode="aspectFill"
|
||||
@click="previewImage(index)"
|
||||
/>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
|
||||
<!-- 信息展示区域 -->
|
||||
<view class="info-area">
|
||||
<view class="info-row">
|
||||
<text class="info-label">故障时间</text>
|
||||
<text class="info-value">{{ detail.faultTime }}</text>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<text class="info-label">人员</text>
|
||||
<text class="info-value">{{ detail.personnel }}</text>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<text class="info-label">故障原因</text>
|
||||
<text class="info-value">{{ detail.faultReason }}</text>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<text class="info-label">表显故障里程</text>
|
||||
<text class="info-value">{{ detail.mileage }}</text>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<text class="info-label">所属光缆</text>
|
||||
<text class="info-value">{{ detail.cableName }}</text>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<text class="info-label">地点</text>
|
||||
<text class="info-value">{{ detail.location }}</text>
|
||||
</view>
|
||||
<view class="info-row last-row">
|
||||
<text class="info-label">备注</text>
|
||||
<text class="info-value">{{ detail.remark }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 底部导航按钮 -->
|
||||
<view class="bottom-bar" v-if="hasLocation">
|
||||
<view class="navigate-btn" @click="handleNavigate">
|
||||
<text class="navigate-btn-text">导航至地点</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, computed } from 'vue'
|
||||
import { onLoad } from '@dcloudio/uni-app'
|
||||
import { getFaultDetail } from '@/services/trunk'
|
||||
import { openNavigation } from '@/utils/navigation'
|
||||
|
||||
const statusBarHeight = uni.getSystemInfoSync().statusBarHeight || 0
|
||||
const faultId = ref('')
|
||||
const imageList = ref([])
|
||||
|
||||
const detail = reactive({
|
||||
faultTime: '',
|
||||
personnel: '',
|
||||
faultReason: '',
|
||||
mileage: '',
|
||||
cableName: '',
|
||||
location: '',
|
||||
latitude: 0,
|
||||
longitude: 0,
|
||||
remark: ''
|
||||
})
|
||||
|
||||
const hasLocation = computed(() => {
|
||||
return detail.latitude && detail.longitude &&
|
||||
Number(detail.latitude) !== 0 && Number(detail.longitude) !== 0
|
||||
})
|
||||
|
||||
async function loadDetail() {
|
||||
try {
|
||||
const res = await getFaultDetail(faultId.value)
|
||||
if (res.code === 200 && res.data) {
|
||||
const d = res.data
|
||||
detail.faultTime = d.faultTime || ''
|
||||
detail.personnel = d.personnel || ''
|
||||
detail.faultReason = d.faultReason || ''
|
||||
detail.mileage = d.mileage || ''
|
||||
detail.cableName = d.cableName || ''
|
||||
detail.location = d.location || ''
|
||||
detail.latitude = d.latitude || 0
|
||||
detail.longitude = d.longitude || 0
|
||||
detail.remark = d.remark || ''
|
||||
imageList.value = (d.images || []).map(img => img.url)
|
||||
}
|
||||
} catch (err) {
|
||||
uni.showToast({ title: '加载失败', icon: 'none' })
|
||||
}
|
||||
}
|
||||
|
||||
function goBack() {
|
||||
uni.navigateBack()
|
||||
}
|
||||
|
||||
function previewImage(index) {
|
||||
uni.previewImage({
|
||||
urls: imageList.value,
|
||||
current: imageList.value[index]
|
||||
})
|
||||
}
|
||||
|
||||
function handleNavigate() {
|
||||
openNavigation(detail.latitude, detail.longitude, detail.location || '故障地点')
|
||||
}
|
||||
|
||||
onLoad((options) => {
|
||||
if (options.faultId) {
|
||||
faultId.value = options.faultId
|
||||
}
|
||||
loadDetail()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.fault-detail-page {
|
||||
position: relative;
|
||||
min-height: 100vh;
|
||||
background-color: #F5F5F5;
|
||||
padding-bottom: 120rpx;
|
||||
}
|
||||
|
||||
.bg-image {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 500rpx;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.content {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.nav-bar {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.nav-bar-inner {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
height: 88rpx;
|
||||
padding: 0 24rpx;
|
||||
}
|
||||
|
||||
.nav-icon {
|
||||
width: 44rpx;
|
||||
height: 44rpx;
|
||||
}
|
||||
|
||||
.nav-icon-placeholder {
|
||||
width: 44rpx;
|
||||
height: 44rpx;
|
||||
}
|
||||
|
||||
.nav-title {
|
||||
font-size: 34rpx;
|
||||
font-weight: 600;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.image-area {
|
||||
padding: 24rpx;
|
||||
}
|
||||
|
||||
.image-scroll {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.image-grid {
|
||||
display: inline-flex;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.image-item {
|
||||
width: 280rpx;
|
||||
height: 280rpx;
|
||||
border-radius: 8rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.info-area {
|
||||
background-color: #fff;
|
||||
margin: 0 24rpx;
|
||||
padding: 24rpx;
|
||||
border-radius: 12rpx;
|
||||
}
|
||||
|
||||
.info-row {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.info-row.last-row {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.info-label {
|
||||
font-size: 26rpx;
|
||||
color: #999;
|
||||
flex-shrink: 0;
|
||||
width: 180rpx;
|
||||
}
|
||||
|
||||
.info-value {
|
||||
font-size: 26rpx;
|
||||
color: #333;
|
||||
flex: 1;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.bottom-bar {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
padding: 24rpx;
|
||||
background: #fff;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.navigate-btn {
|
||||
width: 100%;
|
||||
height: 88rpx;
|
||||
background: #1A73EC;
|
||||
border-radius: 20rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.navigate-btn-text {
|
||||
color: #fff;
|
||||
font-size: 32rpx;
|
||||
}
|
||||
</style>
|
||||
245
odf-uniapp/pages/fault-list/index.vue
Normal file
245
odf-uniapp/pages/fault-list/index.vue
Normal file
|
|
@ -0,0 +1,245 @@
|
|||
<template>
|
||||
<view class="fault-list-page">
|
||||
<image class="bg-image" src="/static/images/home_bg.png" mode="aspectFill" />
|
||||
|
||||
<view class="content">
|
||||
<!-- 顶部导航栏 -->
|
||||
<view class="nav-bar" :style="{ paddingTop: statusBarHeight + 'px' }">
|
||||
<view class="nav-bar-inner">
|
||||
<image
|
||||
class="nav-icon"
|
||||
src="/static/images/ic_back.png"
|
||||
mode="aspectFit"
|
||||
@click="goBack"
|
||||
/>
|
||||
<text class="nav-title">干线</text>
|
||||
<view class="nav-icon-placeholder" />
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 小标题 -->
|
||||
<text class="section-title">故障列表</text>
|
||||
|
||||
<!-- 故障列表 -->
|
||||
<view class="fault-list">
|
||||
<view
|
||||
class="fault-card"
|
||||
v-for="item in faultList"
|
||||
:key="item.id"
|
||||
@click="goFaultDetail(item)"
|
||||
>
|
||||
<view class="fault-row">
|
||||
<text class="fault-label">故障时间:</text>
|
||||
<text class="fault-value">{{ item.faultTime }}</text>
|
||||
</view>
|
||||
<view class="fault-row">
|
||||
<text class="fault-label">故障原因:</text>
|
||||
<text class="fault-value">{{ item.faultReason }}</text>
|
||||
</view>
|
||||
<view class="fault-row">
|
||||
<text class="fault-label">表显故障里程:</text>
|
||||
<text class="fault-value">{{ item.mileage }}</text>
|
||||
</view>
|
||||
<view class="fault-row last-row">
|
||||
<text class="fault-label">所属光缆:</text>
|
||||
<text class="fault-value">{{ item.cableName }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 底部固定按钮 -->
|
||||
<view class="bottom-bar">
|
||||
<view class="add-fault-btn" @click="goFaultAdd">
|
||||
<text class="add-fault-btn-text">新增故障</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import { onLoad, onReachBottom } from '@dcloudio/uni-app'
|
||||
import { getFaultList } from '@/services/trunk'
|
||||
|
||||
const statusBarHeight = uni.getSystemInfoSync().statusBarHeight || 0
|
||||
const faultList = ref([])
|
||||
const cableId = ref('')
|
||||
const cableName = ref('')
|
||||
const pageNum = ref(1)
|
||||
const pageSize = ref(20)
|
||||
const totalPage = ref(1)
|
||||
const loading = ref(false)
|
||||
|
||||
async function loadFaultList(isLoadMore = false) {
|
||||
if (loading.value) return
|
||||
loading.value = true
|
||||
|
||||
try {
|
||||
const res = await getFaultList(cableId.value, pageNum.value, pageSize.value)
|
||||
if (res.code === 200) {
|
||||
const data = res.data || {}
|
||||
const list = data.result || []
|
||||
if (isLoadMore) {
|
||||
faultList.value = [...faultList.value, ...list]
|
||||
} else {
|
||||
faultList.value = list
|
||||
}
|
||||
totalPage.value = data.totalPage || 1
|
||||
}
|
||||
} catch (err) {
|
||||
uni.showToast({ title: '加载失败', icon: 'none' })
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function goBack() {
|
||||
uni.navigateBack()
|
||||
}
|
||||
|
||||
function goFaultDetail(item) {
|
||||
uni.navigateTo({ url: '/pages/fault-detail/index?faultId=' + item.id })
|
||||
}
|
||||
|
||||
function goFaultAdd() {
|
||||
uni.navigateTo({
|
||||
url: '/pages/fault-add/index?cableId=' + cableId.value + '&cableName=' + encodeURIComponent(cableName.value)
|
||||
})
|
||||
}
|
||||
|
||||
onLoad((options) => {
|
||||
if (options.cableId) {
|
||||
cableId.value = options.cableId
|
||||
}
|
||||
if (options.cableName) {
|
||||
cableName.value = decodeURIComponent(options.cableName)
|
||||
}
|
||||
loadFaultList()
|
||||
})
|
||||
|
||||
onReachBottom(() => {
|
||||
if (pageNum.value < totalPage.value) {
|
||||
pageNum.value++
|
||||
loadFaultList(true)
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.fault-list-page {
|
||||
position: relative;
|
||||
min-height: 100vh;
|
||||
background-color: #F5F5F5;
|
||||
padding-bottom: 120rpx;
|
||||
}
|
||||
|
||||
.bg-image {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 500rpx;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.content {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.nav-bar {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.nav-bar-inner {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
height: 88rpx;
|
||||
padding: 0 24rpx;
|
||||
}
|
||||
|
||||
.nav-icon {
|
||||
width: 44rpx;
|
||||
height: 44rpx;
|
||||
}
|
||||
|
||||
.nav-icon-placeholder {
|
||||
width: 44rpx;
|
||||
height: 44rpx;
|
||||
}
|
||||
|
||||
.nav-title {
|
||||
font-size: 34rpx;
|
||||
font-weight: 600;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 30rpx;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
padding: 16rpx 24rpx 8rpx;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.fault-list {
|
||||
padding: 0 0 24rpx;
|
||||
}
|
||||
|
||||
.fault-card {
|
||||
background-color: #fff;
|
||||
border-radius: 12rpx;
|
||||
border: 1rpx solid #E8E8E8;
|
||||
padding: 24rpx;
|
||||
margin: 0 24rpx 20rpx;
|
||||
}
|
||||
|
||||
.fault-row {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
margin-bottom: 12rpx;
|
||||
}
|
||||
|
||||
.fault-row.last-row {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.fault-label {
|
||||
font-size: 26rpx;
|
||||
color: #999;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.fault-value {
|
||||
font-size: 26rpx;
|
||||
color: #333;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.bottom-bar {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
padding: 24rpx;
|
||||
background: #fff;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.add-fault-btn {
|
||||
width: 100%;
|
||||
height: 88rpx;
|
||||
background: #1A73EC;
|
||||
border-radius: 20rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.add-fault-btn-text {
|
||||
color: #fff;
|
||||
font-size: 32rpx;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -31,6 +31,7 @@
|
|||
import { ref } from 'vue'
|
||||
import store from '@/store'
|
||||
import { appLogin, checkPermission } from '@/services/auth'
|
||||
import { getUserModules } from '@/services/permission'
|
||||
|
||||
const username = ref('')
|
||||
const password = ref('')
|
||||
|
|
@ -42,7 +43,12 @@ async function handleLogin() {
|
|||
store.setAuth(jwt, userId, userName)
|
||||
const permRes = await checkPermission()
|
||||
store.isPermission = permRes.code === 200
|
||||
uni.reLaunch({ url: '/pages/home/index' })
|
||||
// 获取用户功能版块权限
|
||||
const modulesRes = await getUserModules()
|
||||
if (modulesRes.code === 200) {
|
||||
store.setModules(modulesRes.data)
|
||||
}
|
||||
uni.reLaunch({ url: '/pages/portal/index' })
|
||||
} else {
|
||||
uni.showToast({ title: res.msg, icon: 'none' })
|
||||
}
|
||||
|
|
|
|||
172
odf-uniapp/pages/portal/index.vue
Normal file
172
odf-uniapp/pages/portal/index.vue
Normal file
|
|
@ -0,0 +1,172 @@
|
|||
<template>
|
||||
<view class="portal-page">
|
||||
<image class="bg-image" src="/static/images/home_bg.png" mode="aspectFill" />
|
||||
|
||||
<view class="content">
|
||||
<!-- 顶部导航栏 -->
|
||||
<view class="nav-bar" :style="{ paddingTop: statusBarHeight + 'px' }">
|
||||
<view class="nav-bar-inner">
|
||||
<image
|
||||
class="nav-icon"
|
||||
src="/static/images/ic_refresh.png"
|
||||
mode="aspectFit"
|
||||
@click="handleRefresh"
|
||||
/>
|
||||
<text class="nav-title">功能列表</text>
|
||||
<image
|
||||
class="nav-icon"
|
||||
src="/static/images/ic_set.png"
|
||||
mode="aspectFit"
|
||||
@click="goSettings"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 功能入口网格 -->
|
||||
<view class="module-grid" v-if="filteredModules.length > 0">
|
||||
<view
|
||||
class="module-card"
|
||||
v-for="item in filteredModules"
|
||||
:key="item.code"
|
||||
@click="handleModuleClick(item)"
|
||||
>
|
||||
<image class="module-image" :src="item.icon" mode="aspectFit" />
|
||||
<text class="module-name">{{ item.name }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 空权限提示 -->
|
||||
<view class="empty-state" v-else>
|
||||
<text class="empty-text">暂无可用功能模块</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed } from 'vue'
|
||||
import { onLoad } from '@dcloudio/uni-app'
|
||||
import store from '@/store'
|
||||
import { getUserModules } from '@/services/permission'
|
||||
|
||||
const statusBarHeight = uni.getSystemInfoSync().statusBarHeight || 0
|
||||
|
||||
// 所有可用功能版块定义
|
||||
const allModules = [
|
||||
{ code: 'odf', name: '机房', icon: '/static/images/ic_odf.png', url: '/pages/home/index' },
|
||||
{ code: 'trunk', name: '干线', icon: '/static/images/ic_trunk.png', url: '/pages/trunk/index' }
|
||||
]
|
||||
|
||||
// 根据权限过滤显示的版块
|
||||
const filteredModules = computed(() => {
|
||||
return allModules.filter(m => store.modules.includes(m.code))
|
||||
})
|
||||
|
||||
function handleModuleClick(item) {
|
||||
uni.navigateTo({ url: item.url })
|
||||
}
|
||||
|
||||
async function handleRefresh() {
|
||||
const res = await getUserModules()
|
||||
if (res.code === 200) {
|
||||
store.setModules(res.data)
|
||||
}
|
||||
}
|
||||
|
||||
function goSettings() {
|
||||
uni.navigateTo({ url: '/pages/change-password/index' })
|
||||
}
|
||||
|
||||
onLoad(() => {
|
||||
// 页面加载时刷新权限
|
||||
handleRefresh()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.portal-page {
|
||||
position: relative;
|
||||
min-height: 100vh;
|
||||
background-color: #F5F5F5;
|
||||
}
|
||||
|
||||
.bg-image {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 500rpx;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.content {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.nav-bar {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.nav-bar-inner {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
height: 88rpx;
|
||||
padding: 0 24rpx;
|
||||
}
|
||||
|
||||
.nav-icon {
|
||||
width: 44rpx;
|
||||
height: 44rpx;
|
||||
}
|
||||
|
||||
.nav-title {
|
||||
font-size: 34rpx;
|
||||
font-weight: 600;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.module-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 24rpx;
|
||||
padding: 24rpx;
|
||||
}
|
||||
|
||||
.module-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 32rpx;
|
||||
background-color: #fff;
|
||||
border-radius: 12rpx;
|
||||
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.module-image {
|
||||
width: 120rpx;
|
||||
height: 120rpx;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.module-name {
|
||||
font-size: 30rpx;
|
||||
color: #333;
|
||||
font-weight: 500;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 200rpx 0;
|
||||
}
|
||||
|
||||
.empty-text {
|
||||
font-size: 28rpx;
|
||||
color: #999;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -13,7 +13,9 @@
|
|||
@click="goBack"
|
||||
/>
|
||||
<text class="nav-title">机房详情</text>
|
||||
<view class="nav-icon-placeholder" />
|
||||
<view class="checkin-btn" @click="goCheckin">
|
||||
<text class="checkin-btn-text">签到</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
|
|
@ -68,6 +70,12 @@ function goBack() {
|
|||
uni.navigateBack()
|
||||
}
|
||||
|
||||
function goCheckin() {
|
||||
uni.navigateTo({
|
||||
url: '/pages/checkin/index?roomId=' + roomIdRef.value
|
||||
})
|
||||
}
|
||||
|
||||
function goDetail(item) {
|
||||
uni.navigateTo({
|
||||
url: '/pages/rack-detail/index?rackId=' + item.id + '&rackName=' + encodeURIComponent(item.rackName) + '&roomName=' + encodeURIComponent(roomName.value)
|
||||
|
|
@ -136,9 +144,15 @@ onReachBottom(() => {
|
|||
height: 44rpx;
|
||||
}
|
||||
|
||||
.nav-icon-placeholder {
|
||||
width: 44rpx;
|
||||
height: 44rpx;
|
||||
.checkin-btn {
|
||||
background-color: #1A73EC;
|
||||
border-radius: 8rpx;
|
||||
padding: 8rpx 24rpx;
|
||||
}
|
||||
|
||||
.checkin-btn-text {
|
||||
color: #fff;
|
||||
font-size: 26rpx;
|
||||
}
|
||||
|
||||
.nav-title {
|
||||
|
|
|
|||
553
odf-uniapp/pages/route-plan/index.vue
Normal file
553
odf-uniapp/pages/route-plan/index.vue
Normal file
|
|
@ -0,0 +1,553 @@
|
|||
<template>
|
||||
<view class="route-plan-page">
|
||||
<image class="bg-image" src="/static/images/home_bg.png" mode="aspectFill" />
|
||||
|
||||
<view class="content">
|
||||
<!-- 顶部导航栏 -->
|
||||
<view class="nav-bar" :style="{ paddingTop: statusBarHeight + 'px' }">
|
||||
<view class="nav-bar-inner">
|
||||
<image
|
||||
class="nav-icon"
|
||||
src="/static/images/ic_back.png"
|
||||
mode="aspectFit"
|
||||
@click="goBack"
|
||||
/>
|
||||
<text class="nav-title">路线规划</text>
|
||||
<view class="nav-icon-placeholder" />
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 起终点输入 -->
|
||||
<view class="form-area">
|
||||
<view class="form-group">
|
||||
<text class="form-label">起点经度</text>
|
||||
<input class="form-input" type="digit" v-model="startLng" placeholder="请输入起点经度" />
|
||||
</view>
|
||||
<view class="form-group">
|
||||
<text class="form-label">起点纬度</text>
|
||||
<input class="form-input" type="digit" v-model="startLat" placeholder="请输入起点纬度" />
|
||||
</view>
|
||||
<view class="form-group">
|
||||
<text class="form-label">终点经度</text>
|
||||
<input class="form-input" type="digit" v-model="endLng" placeholder="请输入终点经度" />
|
||||
</view>
|
||||
<view class="form-group">
|
||||
<text class="form-label">终点纬度</text>
|
||||
<input class="form-input" type="digit" v-model="endLat" placeholder="请输入终点纬度" />
|
||||
</view>
|
||||
|
||||
<view class="plan-btn" @click="planRoute">
|
||||
<text class="plan-btn-text">规划路线</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 路线结果 -->
|
||||
<view class="result-area" v-if="routeDistance > 0">
|
||||
<view class="result-card">
|
||||
<text class="result-label">路线总长度</text>
|
||||
<text class="result-value">{{ formatDistance(routeDistance) }}</text>
|
||||
</view>
|
||||
|
||||
<!-- 距离定位 -->
|
||||
<view class="form-group">
|
||||
<text class="form-label">输入距离(米)</text>
|
||||
<input class="form-input" type="digit" v-model="targetDistance" placeholder="请输入距离值" />
|
||||
</view>
|
||||
<view class="plan-btn" @click="locateByDistance">
|
||||
<text class="plan-btn-text">定位坐标</text>
|
||||
</view>
|
||||
|
||||
<!-- 定位结果 -->
|
||||
<view class="result-card" v-if="locatedPoint">
|
||||
<text class="result-label">定位坐标</text>
|
||||
<text class="result-value">经度: {{ locatedPoint.lng }},纬度: {{ locatedPoint.lat }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 地图展示 -->
|
||||
<view class="map-area" v-if="showMap">
|
||||
<map
|
||||
id="routeMap"
|
||||
class="route-map"
|
||||
:latitude="mapCenter.lat"
|
||||
:longitude="mapCenter.lng"
|
||||
:scale="mapScale"
|
||||
:markers="markers"
|
||||
:polyline="polyline"
|
||||
show-location
|
||||
/>
|
||||
</view>
|
||||
|
||||
<!-- 导航按钮 -->
|
||||
<view class="bottom-bar" v-if="locatedPoint">
|
||||
<view class="navigate-btn" @click="navigateToPoint">
|
||||
<text class="navigate-btn-text">导航至该位置</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 加载提示 -->
|
||||
<view class="loading-mask" v-if="loading">
|
||||
<text class="loading-text">路线规划中...</text>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive } from 'vue'
|
||||
import { onLoad } from '@dcloudio/uni-app'
|
||||
import { openNavigation } from '@/utils/navigation'
|
||||
|
||||
// 高德地图 Web Service API Key(需替换为实际申请的 Key)
|
||||
const AMAP_KEY = 'YOUR_AMAP_WEB_SERVICE_KEY'
|
||||
const AMAP_DRIVING_URL = 'https://restapi.amap.com/v3/direction/driving'
|
||||
|
||||
const statusBarHeight = uni.getSystemInfoSync().statusBarHeight || 0
|
||||
|
||||
// 起终点坐标
|
||||
const startLng = ref('')
|
||||
const startLat = ref('')
|
||||
const endLng = ref('')
|
||||
const endLat = ref('')
|
||||
|
||||
// 路线结果
|
||||
const routeDistance = ref(0) // 路线总长度(米)
|
||||
const routePolyline = ref([]) // 路线点序列 [{ lng, lat }, ...]
|
||||
const targetDistance = ref('')
|
||||
const locatedPoint = ref(null)
|
||||
|
||||
// 地图状态
|
||||
const showMap = ref(false)
|
||||
const loading = ref(false)
|
||||
const mapCenter = reactive({ lat: 39.9042, lng: 116.4074 })
|
||||
const mapScale = ref(12)
|
||||
const markers = ref([])
|
||||
const polyline = ref([])
|
||||
|
||||
function goBack() {
|
||||
uni.navigateBack()
|
||||
}
|
||||
|
||||
function formatDistance(meters) {
|
||||
if (meters >= 1000) {
|
||||
return (meters / 1000).toFixed(2) + ' km'
|
||||
}
|
||||
return meters + ' m'
|
||||
}
|
||||
|
||||
/**
|
||||
* 调用高德地图驾车路线规划 API
|
||||
*/
|
||||
async function planRoute() {
|
||||
if (!startLng.value || !startLat.value || !endLng.value || !endLat.value) {
|
||||
uni.showToast({ title: '请输入完整的起终点坐标', icon: 'none' })
|
||||
return
|
||||
}
|
||||
|
||||
loading.value = true
|
||||
locatedPoint.value = null
|
||||
|
||||
try {
|
||||
const origin = `${startLng.value},${startLat.value}`
|
||||
const destination = `${endLng.value},${endLat.value}`
|
||||
|
||||
const res = await amapRequest(AMAP_DRIVING_URL, {
|
||||
key: AMAP_KEY,
|
||||
origin,
|
||||
destination,
|
||||
extensions: 'all'
|
||||
})
|
||||
|
||||
if (res.status === '1' && res.route && res.route.paths && res.route.paths.length > 0) {
|
||||
const path = res.route.paths[0]
|
||||
routeDistance.value = parseInt(path.distance) || 0
|
||||
|
||||
// 解析路线点序列
|
||||
const points = []
|
||||
for (const step of path.steps) {
|
||||
const coords = step.polyline.split(';')
|
||||
for (const coord of coords) {
|
||||
const [lng, lat] = coord.split(',')
|
||||
points.push({ lng: parseFloat(lng), lat: parseFloat(lat) })
|
||||
}
|
||||
}
|
||||
routePolyline.value = points
|
||||
|
||||
// 更新地图
|
||||
updateMap(points)
|
||||
} else {
|
||||
const infocode = res.infocode || ''
|
||||
const info = res.info || '路线规划失败'
|
||||
uni.showToast({ title: `路线规划失败: ${info}(${infocode})`, icon: 'none', duration: 3000 })
|
||||
}
|
||||
} catch (err) {
|
||||
uni.showToast({ title: '网络异常或服务不可用,请稍后重试', icon: 'none', duration: 3000 })
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新地图显示
|
||||
*/
|
||||
function updateMap(points) {
|
||||
if (!points || points.length === 0) return
|
||||
|
||||
showMap.value = true
|
||||
|
||||
// 设置地图中心为路线中点
|
||||
const midIndex = Math.floor(points.length / 2)
|
||||
mapCenter.lat = points[midIndex].lat
|
||||
mapCenter.lng = points[midIndex].lng
|
||||
|
||||
// 起终点标记
|
||||
const startPoint = points[0]
|
||||
const endPoint = points[points.length - 1]
|
||||
markers.value = [
|
||||
{
|
||||
id: 1,
|
||||
latitude: startPoint.lat,
|
||||
longitude: startPoint.lng,
|
||||
title: '起点',
|
||||
iconPath: '/static/images/ic_back.png',
|
||||
width: 30,
|
||||
height: 30,
|
||||
callout: { content: '起点', display: 'ALWAYS', fontSize: 12, borderRadius: 4, padding: 4 }
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
latitude: endPoint.lat,
|
||||
longitude: endPoint.lng,
|
||||
title: '终点',
|
||||
iconPath: '/static/images/ic_back.png',
|
||||
width: 30,
|
||||
height: 30,
|
||||
callout: { content: '终点', display: 'ALWAYS', fontSize: 12, borderRadius: 4, padding: 4 }
|
||||
}
|
||||
]
|
||||
|
||||
// 路线折线
|
||||
polyline.value = [{
|
||||
points: points.map(p => ({ latitude: p.lat, longitude: p.lng })),
|
||||
color: '#1A73EC',
|
||||
width: 6,
|
||||
arrowLine: true
|
||||
}]
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据距离值计算路线上对应坐标
|
||||
* 沿路线点序列累加距离,找到目标距离对应的插值点
|
||||
*/
|
||||
function locateByDistance() {
|
||||
const dist = parseFloat(targetDistance.value)
|
||||
if (isNaN(dist) || dist < 0) {
|
||||
uni.showToast({ title: '请输入有效的距离值', icon: 'none' })
|
||||
return
|
||||
}
|
||||
if (dist > routeDistance.value) {
|
||||
uni.showToast({ title: '距离超出路线总长度', icon: 'none' })
|
||||
return
|
||||
}
|
||||
if (routePolyline.value.length < 2) {
|
||||
uni.showToast({ title: '路线数据不足', icon: 'none' })
|
||||
return
|
||||
}
|
||||
|
||||
const point = findPointAtDistance(routePolyline.value, dist)
|
||||
if (point) {
|
||||
locatedPoint.value = {
|
||||
lng: point.lng.toFixed(6),
|
||||
lat: point.lat.toFixed(6)
|
||||
}
|
||||
// 在地图上标注
|
||||
addLocatedMarker(point)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 在路线点序列上,根据沿线距离找到对应坐标(线性插值)
|
||||
*/
|
||||
export function findPointAtDistance(points, targetDist) {
|
||||
if (!points || points.length < 2) return null
|
||||
if (targetDist <= 0) return { lng: points[0].lng, lat: points[0].lat }
|
||||
|
||||
let accumulated = 0
|
||||
for (let i = 0; i < points.length - 1; i++) {
|
||||
const segDist = haversineDistance(points[i], points[i + 1])
|
||||
if (accumulated + segDist >= targetDist) {
|
||||
// 目标在这段线段上,线性插值
|
||||
const remaining = targetDist - accumulated
|
||||
const ratio = remaining / segDist
|
||||
return {
|
||||
lng: points[i].lng + (points[i + 1].lng - points[i].lng) * ratio,
|
||||
lat: points[i].lat + (points[i + 1].lat - points[i].lat) * ratio
|
||||
}
|
||||
}
|
||||
accumulated += segDist
|
||||
}
|
||||
// 距离等于或超过总长度,返回终点
|
||||
return { lng: points[points.length - 1].lng, lat: points[points.length - 1].lat }
|
||||
}
|
||||
|
||||
/**
|
||||
* Haversine 公式计算两点间距离(米)
|
||||
*/
|
||||
export function haversineDistance(p1, p2) {
|
||||
const R = 6371000 // 地球半径(米)
|
||||
const toRad = (deg) => deg * Math.PI / 180
|
||||
const dLat = toRad(p2.lat - p1.lat)
|
||||
const dLng = toRad(p2.lng - p1.lng)
|
||||
const a = Math.sin(dLat / 2) ** 2 +
|
||||
Math.cos(toRad(p1.lat)) * Math.cos(toRad(p2.lat)) * Math.sin(dLng / 2) ** 2
|
||||
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a))
|
||||
return R * c
|
||||
}
|
||||
|
||||
/**
|
||||
* 在地图上添加定位标记
|
||||
*/
|
||||
function addLocatedMarker(point) {
|
||||
// 保留起终点标记,添加定位点标记
|
||||
const existingMarkers = markers.value.filter(m => m.id <= 2)
|
||||
existingMarkers.push({
|
||||
id: 3,
|
||||
latitude: point.lat,
|
||||
longitude: point.lng,
|
||||
title: '定位点',
|
||||
iconPath: '/static/images/ic_back.png',
|
||||
width: 30,
|
||||
height: 30,
|
||||
callout: {
|
||||
content: `距起点 ${formatDistance(parseFloat(targetDistance.value))}`,
|
||||
display: 'ALWAYS',
|
||||
fontSize: 12,
|
||||
borderRadius: 4,
|
||||
padding: 4,
|
||||
bgColor: '#1A73EC',
|
||||
color: '#fff'
|
||||
}
|
||||
})
|
||||
markers.value = existingMarkers
|
||||
|
||||
// 移动地图中心到定位点
|
||||
mapCenter.lat = point.lat
|
||||
mapCenter.lng = point.lng
|
||||
}
|
||||
|
||||
/**
|
||||
* 导航至定位点
|
||||
*/
|
||||
function navigateToPoint() {
|
||||
if (!locatedPoint.value) return
|
||||
openNavigation(
|
||||
parseFloat(locatedPoint.value.lat),
|
||||
parseFloat(locatedPoint.value.lng),
|
||||
'路线定位点'
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* 高德地图 API 请求封装
|
||||
*/
|
||||
function amapRequest(url, params) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const queryStr = Object.entries(params)
|
||||
.map(([k, v]) => `${k}=${encodeURIComponent(v)}`)
|
||||
.join('&')
|
||||
|
||||
uni.request({
|
||||
url: `${url}?${queryStr}`,
|
||||
method: 'GET',
|
||||
timeout: 15000,
|
||||
success(res) {
|
||||
resolve(res.data)
|
||||
},
|
||||
fail(err) {
|
||||
reject(err)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
onLoad(() => {
|
||||
// 页面加载完成
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.route-plan-page {
|
||||
position: relative;
|
||||
min-height: 100vh;
|
||||
background-color: #F5F5F5;
|
||||
}
|
||||
|
||||
.bg-image {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 500rpx;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.content {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
padding-bottom: 140rpx;
|
||||
}
|
||||
|
||||
.nav-bar {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.nav-bar-inner {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
height: 88rpx;
|
||||
padding: 0 24rpx;
|
||||
}
|
||||
|
||||
.nav-icon {
|
||||
width: 44rpx;
|
||||
height: 44rpx;
|
||||
}
|
||||
|
||||
.nav-icon-placeholder {
|
||||
width: 44rpx;
|
||||
height: 44rpx;
|
||||
}
|
||||
|
||||
.nav-title {
|
||||
font-size: 34rpx;
|
||||
font-weight: 600;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.form-area {
|
||||
padding: 24rpx;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
|
||||
.form-label {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
margin-bottom: 12rpx;
|
||||
font-weight: 500;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.form-input {
|
||||
height: 80rpx;
|
||||
padding: 0 24rpx;
|
||||
background: #fff;
|
||||
border-radius: 12rpx;
|
||||
border: 1rpx solid #E8E8E8;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
.plan-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 88rpx;
|
||||
background: #1A73EC;
|
||||
border-radius: 12rpx;
|
||||
margin-top: 16rpx;
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
|
||||
.plan-btn-text {
|
||||
color: #fff;
|
||||
font-size: 32rpx;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.result-area {
|
||||
padding: 0 24rpx;
|
||||
}
|
||||
|
||||
.result-card {
|
||||
background: #fff;
|
||||
border-radius: 12rpx;
|
||||
padding: 24rpx;
|
||||
margin-bottom: 24rpx;
|
||||
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.result-label {
|
||||
font-size: 26rpx;
|
||||
color: #999;
|
||||
display: block;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.result-value {
|
||||
font-size: 34rpx;
|
||||
color: #333;
|
||||
font-weight: 600;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.map-area {
|
||||
padding: 0 24rpx;
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
|
||||
.route-map {
|
||||
width: 100%;
|
||||
height: 600rpx;
|
||||
border-radius: 12rpx;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.bottom-bar {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
padding: 24rpx;
|
||||
background: #fff;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.navigate-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
height: 88rpx;
|
||||
background: #1A73EC;
|
||||
border-radius: 20rpx;
|
||||
}
|
||||
|
||||
.navigate-btn-text {
|
||||
color: #fff;
|
||||
font-size: 32rpx;
|
||||
}
|
||||
|
||||
.loading-mask {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgba(0, 0, 0, 0.3);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 999;
|
||||
}
|
||||
|
||||
.loading-text {
|
||||
background: #fff;
|
||||
padding: 32rpx 48rpx;
|
||||
border-radius: 12rpx;
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
}
|
||||
</style>
|
||||
259
odf-uniapp/pages/trunk-search/index.vue
Normal file
259
odf-uniapp/pages/trunk-search/index.vue
Normal file
|
|
@ -0,0 +1,259 @@
|
|||
<template>
|
||||
<view class="trunk-search-page">
|
||||
<image class="bg-image" src="/static/images/home_bg.png" mode="aspectFill" />
|
||||
|
||||
<view class="content">
|
||||
<!-- 顶部导航栏 -->
|
||||
<view class="nav-bar" :style="{ paddingTop: statusBarHeight + 'px' }">
|
||||
<view class="nav-bar-inner">
|
||||
<image
|
||||
class="nav-icon"
|
||||
src="/static/images/ic_back.png"
|
||||
mode="aspectFit"
|
||||
@click="goBack"
|
||||
/>
|
||||
<text class="nav-title">搜索结果</text>
|
||||
<view class="nav-icon-placeholder" />
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 搜索结果区域 -->
|
||||
<view class="result-area" v-if="!loading">
|
||||
<!-- 光缆分类 -->
|
||||
<view class="section" v-if="cables.length > 0">
|
||||
<text class="section-title">光缆</text>
|
||||
<view
|
||||
class="cable-card"
|
||||
v-for="item in cables"
|
||||
:key="item.id"
|
||||
@click="goCableFaultList(item)"
|
||||
>
|
||||
<view class="cable-image" />
|
||||
<text class="cable-name">{{ item.cableName }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 故障分类 -->
|
||||
<view class="section" v-if="faults.length > 0">
|
||||
<text class="section-title">故障列表</text>
|
||||
<view
|
||||
class="fault-card"
|
||||
v-for="item in faults"
|
||||
:key="item.id"
|
||||
@click="goFaultDetail(item)"
|
||||
>
|
||||
<view class="fault-row">
|
||||
<text class="fault-label">故障时间:</text>
|
||||
<text class="fault-value">{{ item.faultTime }}</text>
|
||||
</view>
|
||||
<view class="fault-row">
|
||||
<text class="fault-label">故障原因:</text>
|
||||
<text class="fault-value">{{ item.faultReason }}</text>
|
||||
</view>
|
||||
<view class="fault-row">
|
||||
<text class="fault-label">表显故障里程:</text>
|
||||
<text class="fault-value">{{ item.mileage }}</text>
|
||||
</view>
|
||||
<view class="fault-row last-row">
|
||||
<text class="fault-label">所属光缆:</text>
|
||||
<text class="fault-value">{{ item.cableName }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 无结果 -->
|
||||
<view class="no-result" v-if="cables.length === 0 && faults.length === 0">
|
||||
<text class="no-result-text">暂无搜索结果</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import { onLoad } from '@dcloudio/uni-app'
|
||||
import { searchCablesAndFaults } from '@/services/trunk'
|
||||
|
||||
const statusBarHeight = uni.getSystemInfoSync().statusBarHeight || 0
|
||||
const cables = ref([])
|
||||
const faults = ref([])
|
||||
const loading = ref(true)
|
||||
|
||||
async function doSearch(deptId, keyword) {
|
||||
loading.value = true
|
||||
try {
|
||||
const res = await searchCablesAndFaults(deptId, keyword)
|
||||
if (res.code === 200 && res.data) {
|
||||
cables.value = res.data.cables || []
|
||||
faults.value = res.data.faults || []
|
||||
}
|
||||
} catch (err) {
|
||||
uni.showToast({ title: '搜索失败', icon: 'none' })
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function goBack() {
|
||||
uni.navigateBack()
|
||||
}
|
||||
|
||||
function goCableFaultList(item) {
|
||||
uni.navigateTo({
|
||||
url: '/pages/fault-list/index?cableId=' + item.id + '&cableName=' + encodeURIComponent(item.cableName)
|
||||
})
|
||||
}
|
||||
|
||||
function goFaultDetail(item) {
|
||||
uni.navigateTo({ url: '/pages/fault-detail/index?faultId=' + item.id })
|
||||
}
|
||||
|
||||
onLoad((options) => {
|
||||
const deptId = options.deptId || ''
|
||||
const keyword = decodeURIComponent(options.keyword || '')
|
||||
if (deptId && keyword) {
|
||||
doSearch(deptId, keyword)
|
||||
} else {
|
||||
loading.value = false
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.trunk-search-page {
|
||||
position: relative;
|
||||
min-height: 100vh;
|
||||
background-color: #F5F5F5;
|
||||
}
|
||||
|
||||
.bg-image {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 500rpx;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.content {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.nav-bar {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.nav-bar-inner {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
height: 88rpx;
|
||||
padding: 0 24rpx;
|
||||
}
|
||||
|
||||
.nav-icon {
|
||||
width: 44rpx;
|
||||
height: 44rpx;
|
||||
}
|
||||
|
||||
.nav-icon-placeholder {
|
||||
width: 44rpx;
|
||||
height: 44rpx;
|
||||
}
|
||||
|
||||
.nav-title {
|
||||
font-size: 34rpx;
|
||||
font-weight: 600;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.result-area {
|
||||
padding: 16rpx 0;
|
||||
}
|
||||
|
||||
.section {
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 30rpx;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
padding: 16rpx 24rpx 8rpx;
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* 光缆卡片 — 复用 cable 页样式 */
|
||||
.cable-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
margin: 0 24rpx 20rpx;
|
||||
padding: 24rpx;
|
||||
background-color: #fff;
|
||||
border-radius: 12rpx;
|
||||
border: 1rpx solid #E8E8E8;
|
||||
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.cable-image {
|
||||
width: 100%;
|
||||
height: 160rpx;
|
||||
background: #F0F0F0;
|
||||
border-radius: 8rpx;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.cable-name {
|
||||
font-size: 30rpx;
|
||||
color: #333;
|
||||
font-weight: 500;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* 故障卡片 — 复用 fault-list 页样式 */
|
||||
.fault-card {
|
||||
background-color: #fff;
|
||||
border-radius: 12rpx;
|
||||
border: 1rpx solid #E8E8E8;
|
||||
padding: 24rpx;
|
||||
margin: 0 24rpx 20rpx;
|
||||
}
|
||||
|
||||
.fault-row {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
margin-bottom: 12rpx;
|
||||
}
|
||||
|
||||
.fault-row.last-row {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.fault-label {
|
||||
font-size: 26rpx;
|
||||
color: #999;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.fault-value {
|
||||
font-size: 26rpx;
|
||||
color: #333;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
/* 无结果 */
|
||||
.no-result {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 200rpx 0;
|
||||
}
|
||||
|
||||
.no-result-text {
|
||||
font-size: 28rpx;
|
||||
color: #999;
|
||||
}
|
||||
</style>
|
||||
161
odf-uniapp/pages/trunk/index.vue
Normal file
161
odf-uniapp/pages/trunk/index.vue
Normal file
|
|
@ -0,0 +1,161 @@
|
|||
<template>
|
||||
<view class="trunk-page">
|
||||
<image class="bg-image" src="/static/images/home_bg.png" mode="aspectFill" />
|
||||
|
||||
<view class="content">
|
||||
<!-- 顶部导航栏 -->
|
||||
<view class="nav-bar" :style="{ paddingTop: statusBarHeight + 'px' }">
|
||||
<view class="nav-bar-inner">
|
||||
<image
|
||||
class="nav-icon"
|
||||
src="/static/images/ic_back.png"
|
||||
mode="aspectFit"
|
||||
@click="goBack"
|
||||
/>
|
||||
<text class="nav-title">干线</text>
|
||||
<view class="nav-icon-placeholder" />
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 小标题 -->
|
||||
<text class="section-title">公司列表</text>
|
||||
|
||||
<!-- 公司列表 -->
|
||||
<scroll-view class="company-list" scroll-y>
|
||||
<view
|
||||
class="company-card"
|
||||
v-for="item in companyList"
|
||||
:key="item.deptId"
|
||||
@click="goCable(item)"
|
||||
>
|
||||
<view class="company-image" />
|
||||
<text class="company-name">{{ item.deptName }}</text>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import { onLoad, onPullDownRefresh } from '@dcloudio/uni-app'
|
||||
import { getCompanyList } from '@/services/home'
|
||||
|
||||
const statusBarHeight = uni.getSystemInfoSync().statusBarHeight || 0
|
||||
const companyList = ref([])
|
||||
|
||||
async function loadCompanyList() {
|
||||
const res = await getCompanyList()
|
||||
if (res.code === 200) {
|
||||
companyList.value = res.data || []
|
||||
}
|
||||
}
|
||||
|
||||
function goBack() {
|
||||
uni.navigateBack()
|
||||
}
|
||||
|
||||
function goCable(item) {
|
||||
uni.navigateTo({ url: '/pages/cable/index?deptId=' + item.deptId })
|
||||
}
|
||||
|
||||
onLoad(() => {
|
||||
loadCompanyList()
|
||||
})
|
||||
|
||||
onPullDownRefresh(() => {
|
||||
loadCompanyList().finally(() => {
|
||||
uni.stopPullDownRefresh()
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.trunk-page {
|
||||
position: relative;
|
||||
min-height: 100vh;
|
||||
background-color: #F5F5F5;
|
||||
}
|
||||
|
||||
.bg-image {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 500rpx;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.content {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.nav-bar {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.nav-bar-inner {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
height: 88rpx;
|
||||
padding: 0 24rpx;
|
||||
}
|
||||
|
||||
.nav-icon {
|
||||
width: 44rpx;
|
||||
height: 44rpx;
|
||||
}
|
||||
|
||||
.nav-icon-placeholder {
|
||||
width: 44rpx;
|
||||
height: 44rpx;
|
||||
}
|
||||
|
||||
.nav-title {
|
||||
font-size: 34rpx;
|
||||
font-weight: 600;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 30rpx;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
padding: 16rpx 24rpx 8rpx;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.company-list {
|
||||
padding: 0 0 24rpx;
|
||||
height: calc(100vh - 400rpx);
|
||||
}
|
||||
|
||||
.company-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
margin: 0 24rpx 20rpx;
|
||||
padding: 24rpx;
|
||||
background-color: #fff;
|
||||
border-radius: 12rpx;
|
||||
border: 1rpx solid #E8E8E8;
|
||||
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.company-image {
|
||||
width: 100%;
|
||||
height: 160rpx;
|
||||
background: #F0F0F0;
|
||||
border-radius: 8rpx;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.company-name {
|
||||
font-size: 30rpx;
|
||||
color: #333;
|
||||
font-weight: 500;
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
3
odf-uniapp/services/checkin.js
Normal file
3
odf-uniapp/services/checkin.js
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
import { post } from './api'
|
||||
|
||||
export const submitCheckin = (data) => post('/business/OdfCheckin/submit', data)
|
||||
3
odf-uniapp/services/permission.js
Normal file
3
odf-uniapp/services/permission.js
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
import { get } from './api'
|
||||
|
||||
export const getUserModules = () => get('/business/OdfUserModules/list')
|
||||
48
odf-uniapp/services/trunk.js
Normal file
48
odf-uniapp/services/trunk.js
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
import { get } from './api'
|
||||
import store from '@/store'
|
||||
|
||||
const BASE_URL = 'http://49.233.115.141:11082'
|
||||
|
||||
export const getCableList = (deptId) =>
|
||||
get('/business/OdfCables/list', { deptId })
|
||||
|
||||
export const getFaultList = (cableId, pageNum, pageSize) =>
|
||||
get('/business/OdfCableFaults/list', { cableId, pageNum, pageSize })
|
||||
|
||||
export const getFaultDetail = (id) =>
|
||||
get(`/business/OdfCableFaults/${id}`)
|
||||
|
||||
/**
|
||||
* 新增故障(multipart/form-data,含图片上传)
|
||||
* @param {FormData|object} formData - 包含故障信息和图片的 FormData
|
||||
* @returns {Promise}
|
||||
*/
|
||||
export function addFault(formData) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const header = {
|
||||
'Authorization': `Bearer ${store.token}`,
|
||||
'Userid': store.userId,
|
||||
'Username': store.userName
|
||||
}
|
||||
uni.uploadFile({
|
||||
url: BASE_URL + '/business/OdfCableFaults/add',
|
||||
files: formData.files || [],
|
||||
formData: formData.data || {},
|
||||
header,
|
||||
success(res) {
|
||||
try {
|
||||
const result = JSON.parse(res.data)
|
||||
resolve({ code: result.code, msg: result.msg, data: result.data })
|
||||
} catch (e) {
|
||||
reject({ code: -1, msg: '解析响应失败' })
|
||||
}
|
||||
},
|
||||
fail(err) {
|
||||
reject({ code: -1, msg: err.errMsg || '网络异常' })
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
export const searchCablesAndFaults = (deptId, keyword) =>
|
||||
get('/business/OdfCables/search', { deptId, keyword })
|
||||
|
|
@ -8,6 +8,9 @@ const store = reactive({
|
|||
userName: uni.getStorageSync('userName') || '',
|
||||
isPermission: false,
|
||||
|
||||
// 功能版块权限列表
|
||||
modules: JSON.parse(uni.getStorageSync('modules') || '[]'),
|
||||
|
||||
// 字典数据
|
||||
dictUnitTypes: [], // 设备型号列表
|
||||
dictBusinessTypes: [], // 业务类型列表
|
||||
|
|
@ -22,15 +25,23 @@ const store = reactive({
|
|||
uni.setStorageSync('userName', userName)
|
||||
},
|
||||
|
||||
// 设置功能版块权限
|
||||
setModules(modules) {
|
||||
this.modules = modules || []
|
||||
uni.setStorageSync('modules', JSON.stringify(this.modules))
|
||||
},
|
||||
|
||||
// 清除认证信息
|
||||
clearAuth() {
|
||||
this.token = ''
|
||||
this.userId = ''
|
||||
this.userName = ''
|
||||
this.isPermission = false
|
||||
this.modules = []
|
||||
uni.removeStorageSync('token')
|
||||
uni.removeStorageSync('userId')
|
||||
uni.removeStorageSync('userName')
|
||||
uni.removeStorageSync('modules')
|
||||
}
|
||||
})
|
||||
|
||||
|
|
|
|||
54
odf-uniapp/utils/navigation.js
Normal file
54
odf-uniapp/utils/navigation.js
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
/**
|
||||
* 打开导航 APP
|
||||
* @param {number} lat - 纬度
|
||||
* @param {number} lng - 经度
|
||||
* @param {string} name - 地点名称
|
||||
*/
|
||||
export function openNavigation(lat, lng, name) {
|
||||
// #ifdef APP-PLUS
|
||||
const apps = []
|
||||
|
||||
// 检测高德地图
|
||||
if (plus.runtime.isApplicationExist({ pname: 'com.autonavi.minimap', action: '' })) {
|
||||
apps.push({
|
||||
title: '高德地图',
|
||||
scheme: `androidamap://navi?sourceApplication=odf&lat=${lat}&lon=${lng}&dev=0&style=2`
|
||||
})
|
||||
}
|
||||
|
||||
// 检测百度地图
|
||||
if (plus.runtime.isApplicationExist({ pname: 'com.baidu.BaiduMap', action: '' })) {
|
||||
apps.push({
|
||||
title: '百度地图',
|
||||
scheme: `baidumap://map/navi?location=${lat},${lng}&src=odf&coord_type=gcj02`
|
||||
})
|
||||
}
|
||||
|
||||
// 检测腾讯地图
|
||||
if (plus.runtime.isApplicationExist({ pname: 'com.tencent.map', action: '' })) {
|
||||
apps.push({
|
||||
title: '腾讯地图',
|
||||
scheme: `qqmap://map/routeplan?type=drive&to=${encodeURIComponent(name)}&tocoord=${lat},${lng}&referer=odf`
|
||||
})
|
||||
}
|
||||
|
||||
if (apps.length === 0) {
|
||||
uni.showToast({ title: '未检测到导航应用', icon: 'none' })
|
||||
return
|
||||
}
|
||||
|
||||
uni.showActionSheet({
|
||||
itemList: apps.map(a => a.title),
|
||||
success(res) {
|
||||
const selected = apps[res.tapIndex]
|
||||
plus.runtime.openURL(selected.scheme, (err) => {
|
||||
uni.showToast({ title: '打开导航失败', icon: 'none' })
|
||||
})
|
||||
}
|
||||
})
|
||||
// #endif
|
||||
|
||||
// #ifdef H5
|
||||
window.open(`https://uri.amap.com/navigation?to=${lng},${lat},${encodeURIComponent(name)}&mode=car&src=odf`)
|
||||
// #endif
|
||||
}
|
||||
108
odf-uniapp/utils/watermark.js
Normal file
108
odf-uniapp/utils/watermark.js
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
/**
|
||||
* 在照片左下角叠加水印文字
|
||||
* @param {string} imagePath - 原始图片路径
|
||||
* @param {string} text - 水印文字(如 "2025/06/15 12:00 张三")
|
||||
* @returns {Promise<string>} 带水印的临时文件路径
|
||||
*/
|
||||
export function addWatermark(imagePath, text) {
|
||||
return new Promise((resolve, reject) => {
|
||||
uni.getImageInfo({
|
||||
src: imagePath,
|
||||
success(imgInfo) {
|
||||
const canvasId = 'watermarkCanvas'
|
||||
const width = imgInfo.width
|
||||
const height = imgInfo.height
|
||||
|
||||
// 使用 OffscreenCanvas(APP-PLUS 和 H5 均支持)
|
||||
// #ifdef APP-PLUS
|
||||
const bitmap = new plus.nativeObj.Bitmap('watermark')
|
||||
bitmap.load(imagePath, () => {
|
||||
const canvas = new plus.nativeObj.View('watermarkView', {
|
||||
left: '0px', top: '0px',
|
||||
width: width + 'px', height: height + 'px'
|
||||
})
|
||||
|
||||
// 绘制原图
|
||||
canvas.drawBitmap(bitmap, {}, { left: '0px', top: '0px', width: width + 'px', height: height + 'px' })
|
||||
|
||||
// 水印参数
|
||||
const fontSize = Math.max(Math.floor(width * 0.03), 14)
|
||||
const padding = Math.floor(fontSize * 0.8)
|
||||
const textX = padding
|
||||
const textY = height - padding
|
||||
|
||||
// 绘制半透明背景
|
||||
const bgHeight = fontSize + padding * 2
|
||||
canvas.drawRect(
|
||||
{ color: 'rgba(0,0,0,0.4)' },
|
||||
{ left: '0px', top: (height - bgHeight) + 'px', width: width + 'px', height: bgHeight + 'px' }
|
||||
)
|
||||
|
||||
// 绘制水印文字
|
||||
canvas.drawText(text, {
|
||||
left: textX + 'px',
|
||||
top: (height - bgHeight + padding) + 'px',
|
||||
width: (width - textX * 2) + 'px',
|
||||
height: fontSize + 'px'
|
||||
}, {
|
||||
size: fontSize + 'px',
|
||||
color: '#ffffff'
|
||||
})
|
||||
|
||||
// 导出
|
||||
const tempPath = `_doc/watermark_${Date.now()}.jpg`
|
||||
canvas.toBitmap(tempPath, {}, () => {
|
||||
bitmap.clear()
|
||||
resolve(tempPath)
|
||||
}, (err) => {
|
||||
bitmap.clear()
|
||||
reject(err)
|
||||
})
|
||||
}, (err) => {
|
||||
reject(err)
|
||||
})
|
||||
// #endif
|
||||
|
||||
// #ifndef APP-PLUS
|
||||
// H5 / 小程序端使用 Canvas 2D
|
||||
const canvas = uni.createOffscreenCanvas({ type: '2d', width, height })
|
||||
const ctx = canvas.getContext('2d')
|
||||
const img = canvas.createImage()
|
||||
|
||||
img.onload = () => {
|
||||
// 绘制原图
|
||||
ctx.drawImage(img, 0, 0, width, height)
|
||||
|
||||
// 水印参数
|
||||
const fontSize = Math.max(Math.floor(width * 0.03), 14)
|
||||
const padding = Math.floor(fontSize * 0.8)
|
||||
|
||||
// 绘制半透明背景
|
||||
const bgHeight = fontSize + padding * 2
|
||||
ctx.fillStyle = 'rgba(0,0,0,0.4)'
|
||||
ctx.fillRect(0, height - bgHeight, width, bgHeight)
|
||||
|
||||
// 绘制水印文字
|
||||
ctx.fillStyle = '#ffffff'
|
||||
ctx.font = `${fontSize}px sans-serif`
|
||||
ctx.textBaseline = 'middle'
|
||||
ctx.fillText(text, padding, height - bgHeight / 2)
|
||||
|
||||
// 导出为临时文件
|
||||
const tempFilePath = canvas.toDataURL('image/jpeg', 0.9)
|
||||
resolve(tempFilePath)
|
||||
}
|
||||
|
||||
img.onerror = (err) => {
|
||||
reject(err || new Error('图片加载失败'))
|
||||
}
|
||||
|
||||
img.src = imagePath
|
||||
// #endif
|
||||
},
|
||||
fail(err) {
|
||||
reject(err)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
|
@ -0,0 +1,95 @@
|
|||
using Microsoft.AspNetCore.Mvc;
|
||||
using ZR.Model.Business.Dto;
|
||||
using ZR.Service.Business.IBusinessService;
|
||||
|
||||
//创建时间:2025-09-21
|
||||
namespace ZR.Admin.WebApi.Controllers.Business
|
||||
{
|
||||
/// <summary>
|
||||
/// 干线故障管理
|
||||
/// </summary>
|
||||
[Route("business/OdfCableFaults")]
|
||||
public class OdfCableFaultsController : BaseController
|
||||
{
|
||||
/// <summary>
|
||||
/// 干线故障接口
|
||||
/// </summary>
|
||||
private readonly IOdfCableFaultsService _OdfCableFaultsService;
|
||||
|
||||
public OdfCableFaultsController(IOdfCableFaultsService OdfCableFaultsService)
|
||||
{
|
||||
_OdfCableFaultsService = OdfCableFaultsService;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 故障列表分页查询
|
||||
/// </summary>
|
||||
/// <param name="parm"></param>
|
||||
/// <returns></returns>
|
||||
[HttpGet("list")]
|
||||
[ActionPermissionFilter(Permission = "odfcablefaults:list")]
|
||||
public IActionResult GetList([FromQuery] OdfCableFaultsQueryDto parm)
|
||||
{
|
||||
var response = _OdfCableFaultsService.GetList(parm);
|
||||
return SUCCESS(response);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 故障详情(含图片)
|
||||
/// </summary>
|
||||
/// <param name="id"></param>
|
||||
/// <returns></returns>
|
||||
[HttpGet("{id}")]
|
||||
[ActionPermissionFilter(Permission = "odfcablefaults:query")]
|
||||
public IActionResult GetDetail(int id)
|
||||
{
|
||||
var response = _OdfCableFaultsService.GetDetail(id);
|
||||
return SUCCESS(response);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 新增故障(含图片上传,APP端调用)
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[HttpPost("add")]
|
||||
[ActionPermissionFilter(Permission = "odfcablefaults:list")]
|
||||
[Log(Title = "干线故障", BusinessType = BusinessType.INSERT)]
|
||||
public async Task<IActionResult> Add([FromForm] OdfCableFaultAddDto dto)
|
||||
{
|
||||
dto.UserId = HttpContext.GetUId();
|
||||
var response = await _OdfCableFaultsService.AddFault(dto);
|
||||
return ToResponse(response);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 删除故障并级联删除图片
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[HttpPost("delete/{id}")]
|
||||
[ActionPermissionFilter(Permission = "odfcablefaults:delete")]
|
||||
[Log(Title = "干线故障", BusinessType = BusinessType.DELETE)]
|
||||
public IActionResult Delete(int id)
|
||||
{
|
||||
var response = _OdfCableFaultsService.Delete(id);
|
||||
return ToResponse(response);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 导出故障列表
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[Log(Title = "干线故障", BusinessType = BusinessType.EXPORT, IsSaveResponseData = false)]
|
||||
[HttpGet("export")]
|
||||
[ActionPermissionFilter(Permission = "odfcablefaults:export")]
|
||||
public IActionResult Export([FromQuery] OdfCableFaultsQueryDto parm)
|
||||
{
|
||||
var list = _OdfCableFaultsService.ExportList(parm);
|
||||
if (list == null || list.Result == null || list.Result.Count <= 0)
|
||||
{
|
||||
return ToResponse(ResultCode.FAIL, "没有要导出的数据");
|
||||
}
|
||||
var result = ExportExcelMini(list.Result, "故障列表", "故障列表");
|
||||
return ExportExcel(result.Item2, result.Item1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,123 @@
|
|||
using Microsoft.AspNetCore.Mvc;
|
||||
using ZR.Model.Business;
|
||||
using ZR.Model.Business.Dto;
|
||||
using ZR.Service.Business.IBusinessService;
|
||||
|
||||
//创建时间:2025-09-21
|
||||
namespace ZR.Admin.WebApi.Controllers.Business
|
||||
{
|
||||
/// <summary>
|
||||
/// 光缆管理
|
||||
/// </summary>
|
||||
[Route("business/OdfCables")]
|
||||
public class OdfCablesController : BaseController
|
||||
{
|
||||
/// <summary>
|
||||
/// 光缆管理接口
|
||||
/// </summary>
|
||||
private readonly IOdfCablesService _OdfCablesService;
|
||||
|
||||
public OdfCablesController(IOdfCablesService OdfCablesService)
|
||||
{
|
||||
_OdfCablesService = OdfCablesService;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查询光缆列表
|
||||
/// </summary>
|
||||
/// <param name="parm"></param>
|
||||
/// <returns></returns>
|
||||
[HttpGet("list")]
|
||||
[ActionPermissionFilter(Permission = "odfcables:list")]
|
||||
public IActionResult GetList([FromQuery] OdfCablesQueryDto parm)
|
||||
{
|
||||
var response = _OdfCablesService.GetList(parm);
|
||||
return SUCCESS(response);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 搜索光缆和故障(限定公司范围)
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[HttpGet("search")]
|
||||
[ActionPermissionFilter(Permission = "odfcables:query")]
|
||||
public IActionResult Search([FromQuery] long deptId, [FromQuery] string keyword)
|
||||
{
|
||||
var response = _OdfCablesService.Search(deptId, keyword);
|
||||
return SUCCESS(response);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 新增光缆
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[HttpPost]
|
||||
[ActionPermissionFilter(Permission = "odfcables:add")]
|
||||
[Log(Title = "光缆管理", BusinessType = BusinessType.INSERT)]
|
||||
public IActionResult Add([FromBody] OdfCables parm)
|
||||
{
|
||||
parm.CreatedAt = DateTime.Now;
|
||||
parm.UpdatedAt = DateTime.Now;
|
||||
var response = _OdfCablesService.Add(parm);
|
||||
return SUCCESS(response);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 修改光缆
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[HttpPut]
|
||||
[ActionPermissionFilter(Permission = "odfcables:edit")]
|
||||
[Log(Title = "光缆管理", BusinessType = BusinessType.UPDATE)]
|
||||
public IActionResult Update([FromBody] OdfCables parm)
|
||||
{
|
||||
parm.UpdatedAt = DateTime.Now;
|
||||
var response = _OdfCablesService.Update(parm);
|
||||
return ToResponse(response);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查询光缆详情
|
||||
/// </summary>
|
||||
/// <param name="id"></param>
|
||||
/// <returns></returns>
|
||||
[HttpGet("{id}")]
|
||||
[ActionPermissionFilter(Permission = "odfcables:query")]
|
||||
public IActionResult GetDetail(int id)
|
||||
{
|
||||
var response = _OdfCablesService.GetDetail(id);
|
||||
return SUCCESS(response);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 删除光缆
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[HttpPost("delete/{id}")]
|
||||
[ActionPermissionFilter(Permission = "odfcables:delete")]
|
||||
[Log(Title = "光缆管理", BusinessType = BusinessType.DELETE)]
|
||||
public IActionResult Delete(int id)
|
||||
{
|
||||
var response = _OdfCablesService.Delete(id);
|
||||
return ToResponse(response);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 导出光缆列表
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[Log(Title = "光缆管理", BusinessType = BusinessType.EXPORT, IsSaveResponseData = false)]
|
||||
[HttpGet("export")]
|
||||
[ActionPermissionFilter(Permission = "odfcables:export")]
|
||||
public IActionResult Export([FromQuery] OdfCablesQueryDto parm)
|
||||
{
|
||||
var list = _OdfCablesService.ExportList(parm);
|
||||
if (list == null || list.Result == null || list.Result.Count <= 0)
|
||||
{
|
||||
return ToResponse(ResultCode.FAIL, "没有要导出的数据");
|
||||
}
|
||||
var result = ExportExcelMini(list.Result, "光缆列表", "光缆列表");
|
||||
return ExportExcel(result.Item2, result.Item1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,69 @@
|
|||
using Microsoft.AspNetCore.Mvc;
|
||||
using ZR.Model.Business.Dto;
|
||||
using ZR.Service.Business.IBusinessService;
|
||||
|
||||
//创建时间:2025-09-21
|
||||
namespace ZR.Admin.WebApi.Controllers.Business
|
||||
{
|
||||
/// <summary>
|
||||
/// 签到记录
|
||||
/// </summary>
|
||||
[Route("business/OdfCheckin")]
|
||||
public class OdfCheckinController : BaseController
|
||||
{
|
||||
/// <summary>
|
||||
/// 签到记录接口
|
||||
/// </summary>
|
||||
private readonly IOdfCheckinService _OdfCheckinService;
|
||||
|
||||
public OdfCheckinController(IOdfCheckinService OdfCheckinService)
|
||||
{
|
||||
_OdfCheckinService = OdfCheckinService;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 提交签到记录(APP端调用)
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[HttpPost("submit")]
|
||||
[ActionPermissionFilter(Permission = "odfcheckin:list")]
|
||||
[Log(Title = "签到记录", BusinessType = BusinessType.INSERT)]
|
||||
public IActionResult Submit([FromBody] OdfCheckinDto dto)
|
||||
{
|
||||
dto.UserId = HttpContext.GetUId();
|
||||
var response = _OdfCheckinService.AddCheckin(dto);
|
||||
return SUCCESS(response);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 分页查询签到记录(管理端调用,联查机房名称和提交人)
|
||||
/// </summary>
|
||||
/// <param name="parm"></param>
|
||||
/// <returns></returns>
|
||||
[HttpGet("list")]
|
||||
[ActionPermissionFilter(Permission = "odfcheckin:list")]
|
||||
public IActionResult GetList([FromQuery] OdfCheckinQueryDto parm)
|
||||
{
|
||||
var response = _OdfCheckinService.GetList(parm);
|
||||
return SUCCESS(response);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 导出签到记录(管理端调用)
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[Log(Title = "签到记录", BusinessType = BusinessType.EXPORT, IsSaveResponseData = false)]
|
||||
[HttpGet("export")]
|
||||
[ActionPermissionFilter(Permission = "odfcheckin:export")]
|
||||
public IActionResult Export([FromQuery] OdfCheckinQueryDto parm)
|
||||
{
|
||||
var list = _OdfCheckinService.ExportList(parm);
|
||||
if (list == null || list.Result == null || list.Result.Count <= 0)
|
||||
{
|
||||
return ToResponse(ResultCode.FAIL, "没有要导出的数据");
|
||||
}
|
||||
var result = ExportExcelMini(list.Result, "签到记录", "签到记录");
|
||||
return ExportExcel(result.Item2, result.Item1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
using Microsoft.AspNetCore.Mvc;
|
||||
using ZR.Model.Business.Dto;
|
||||
using ZR.Service.Business.IBusinessService;
|
||||
|
||||
//创建时间:2025-09-21
|
||||
namespace ZR.Admin.WebApi.Controllers.Business
|
||||
{
|
||||
/// <summary>
|
||||
/// 用户模块权限
|
||||
/// </summary>
|
||||
[Route("business/OdfUserModules")]
|
||||
public class OdfUserModulesController : BaseController
|
||||
{
|
||||
/// <summary>
|
||||
/// 用户模块权限接口
|
||||
/// </summary>
|
||||
private readonly IOdfUserModulesService _OdfUserModulesService;
|
||||
|
||||
public OdfUserModulesController(IOdfUserModulesService OdfUserModulesService)
|
||||
{
|
||||
_OdfUserModulesService = OdfUserModulesService;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前登录用户的功能版块列表(APP端调用,登录即可)
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[HttpGet("list")]
|
||||
public IActionResult GetUserModules([FromQuery] long? userId)
|
||||
{
|
||||
// 管理端传 userId 参数时查指定用户,APP端不传则查当前登录用户
|
||||
long uid = userId ?? HttpContext.GetUId();
|
||||
var modules = _OdfUserModulesService.GetUserModules(uid);
|
||||
return SUCCESS(modules);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取用户列表(管理端调用)
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[HttpGet("users")]
|
||||
[ActionPermissionFilter(Permission = "odfusermodules:query")]
|
||||
public IActionResult GetUserList()
|
||||
{
|
||||
var list = _OdfUserModulesService.GetUserList();
|
||||
return SUCCESS(list);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 批量保存用户模块权限(管理端调用)
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[HttpPost("save")]
|
||||
[ActionPermissionFilter(Permission = "odfusermodules:edit")]
|
||||
[Log(Title = "用户模块权限", BusinessType = BusinessType.UPDATE)]
|
||||
public IActionResult SaveUserModules([FromBody] OdfUserModulesSaveDto dto)
|
||||
{
|
||||
var response = _OdfUserModulesService.SaveUserModules(dto.UserId, dto.Modules);
|
||||
return ToResponse(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
52
server/ZR.Model/Business/Dto/OdfCableFaultsDto.cs
Normal file
52
server/ZR.Model/Business/Dto/OdfCableFaultsDto.cs
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace ZR.Model.Business.Dto
|
||||
{
|
||||
/// <summary>
|
||||
/// 干线故障查询对象
|
||||
/// </summary>
|
||||
public class OdfCableFaultsQueryDto : PagerInfo
|
||||
{
|
||||
public int? CableId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 故障时间范围 - 开始
|
||||
/// </summary>
|
||||
public DateTime? BeginFaultTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 故障时间范围 - 结束
|
||||
/// </summary>
|
||||
public DateTime? EndFaultTime { get; set; }
|
||||
|
||||
public string FaultReason { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 新增故障输入对象(含图片上传)
|
||||
/// </summary>
|
||||
public class OdfCableFaultAddDto
|
||||
{
|
||||
public int CableId { get; set; }
|
||||
|
||||
public string FaultTime { get; set; }
|
||||
|
||||
public string Personnel { get; set; }
|
||||
|
||||
public string FaultReason { get; set; }
|
||||
|
||||
public string Mileage { get; set; }
|
||||
|
||||
public string Location { get; set; }
|
||||
|
||||
public decimal Latitude { get; set; }
|
||||
|
||||
public decimal Longitude { get; set; }
|
||||
|
||||
public string Remark { get; set; }
|
||||
|
||||
public long? UserId { get; set; }
|
||||
|
||||
public IFormFile[] Images { get; set; }
|
||||
}
|
||||
}
|
||||
13
server/ZR.Model/Business/Dto/OdfCablesDto.cs
Normal file
13
server/ZR.Model/Business/Dto/OdfCablesDto.cs
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
|
||||
namespace ZR.Model.Business.Dto
|
||||
{
|
||||
/// <summary>
|
||||
/// 光缆列表查询对象
|
||||
/// </summary>
|
||||
public class OdfCablesQueryDto : PagerInfo
|
||||
{
|
||||
public long? DeptId { get; set; }
|
||||
|
||||
public string CableName { get; set; }
|
||||
}
|
||||
}
|
||||
78
server/ZR.Model/Business/Dto/OdfCheckinDto.cs
Normal file
78
server/ZR.Model/Business/Dto/OdfCheckinDto.cs
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
using MiniExcelLibs.Attributes;
|
||||
|
||||
namespace ZR.Model.Business.Dto
|
||||
{
|
||||
/// <summary>
|
||||
/// 签到记录输入对象
|
||||
/// </summary>
|
||||
public class OdfCheckinDto
|
||||
{
|
||||
public int RoomId { get; set; }
|
||||
|
||||
public string Personnel { get; set; }
|
||||
|
||||
public string CheckinTime { get; set; }
|
||||
|
||||
public string WorkContent { get; set; }
|
||||
|
||||
public long? UserId { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 签到记录列表/导出对象
|
||||
/// </summary>
|
||||
public class OdfCheckinListDto
|
||||
{
|
||||
[ExcelColumn(Name = "Id")]
|
||||
public int Id { get; set; }
|
||||
|
||||
[ExcelColumn(Name = "机房ID")]
|
||||
public int RoomId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 机房名称(联查 odf_rooms)
|
||||
/// </summary>
|
||||
[ExcelColumn(Name = "机房名称")]
|
||||
public string RoomName { get; set; }
|
||||
|
||||
[ExcelColumn(Name = "人员")]
|
||||
public string Personnel { get; set; }
|
||||
|
||||
[ExcelColumn(Name = "签到时间", Format = "yyyy-MM-dd HH:mm:ss", Width = 20)]
|
||||
public DateTime CheckinTime { get; set; }
|
||||
|
||||
[ExcelColumn(Name = "工作内容")]
|
||||
public string WorkContent { get; set; }
|
||||
|
||||
public long? UserId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 提交人用户名(联查 sys_user.NickName)
|
||||
/// </summary>
|
||||
[ExcelColumn(Name = "提交人")]
|
||||
public string UserName { get; set; }
|
||||
|
||||
[ExcelColumn(Name = "创建时间", Format = "yyyy-MM-dd HH:mm:ss", Width = 20)]
|
||||
public DateTime? CreatedAt { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 签到记录查询对象
|
||||
/// </summary>
|
||||
public class OdfCheckinQueryDto : PagerInfo
|
||||
{
|
||||
public int? RoomId { get; set; }
|
||||
|
||||
public string Personnel { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 签到时间范围 - 开始
|
||||
/// </summary>
|
||||
public DateTime? BeginCheckinTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 签到时间范围 - 结束
|
||||
/// </summary>
|
||||
public DateTime? EndCheckinTime { get; set; }
|
||||
}
|
||||
}
|
||||
13
server/ZR.Model/Business/Dto/OdfUserModulesDto.cs
Normal file
13
server/ZR.Model/Business/Dto/OdfUserModulesDto.cs
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
|
||||
namespace ZR.Model.Business.Dto
|
||||
{
|
||||
/// <summary>
|
||||
/// 用户模块权限保存对象
|
||||
/// </summary>
|
||||
public class OdfUserModulesSaveDto
|
||||
{
|
||||
public long UserId { get; set; }
|
||||
|
||||
public List<string> Modules { get; set; }
|
||||
}
|
||||
}
|
||||
30
server/ZR.Model/Business/OdfCableFaultImages.cs
Normal file
30
server/ZR.Model/Business/OdfCableFaultImages.cs
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
namespace ZR.Model.Business
|
||||
{
|
||||
/// <summary>
|
||||
/// 故障图片
|
||||
/// </summary>
|
||||
[SugarTable("odf_cable_fault_images")]
|
||||
public class OdfCableFaultImages
|
||||
{
|
||||
/// <summary>
|
||||
/// Id
|
||||
/// </summary>
|
||||
[SugarColumn(IsPrimaryKey = true, IsIdentity = true)]
|
||||
public int Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 关联故障ID
|
||||
/// </summary>
|
||||
public int FaultId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 图片访问URL
|
||||
/// </summary>
|
||||
public string ImageUrl { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 创建时间
|
||||
/// </summary>
|
||||
public DateTime? CreatedAt { get; set; }
|
||||
}
|
||||
}
|
||||
75
server/ZR.Model/Business/OdfCableFaults.cs
Normal file
75
server/ZR.Model/Business/OdfCableFaults.cs
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
namespace ZR.Model.Business
|
||||
{
|
||||
/// <summary>
|
||||
/// 干线故障
|
||||
/// </summary>
|
||||
[SugarTable("odf_cable_faults")]
|
||||
public class OdfCableFaults
|
||||
{
|
||||
/// <summary>
|
||||
/// Id
|
||||
/// </summary>
|
||||
[SugarColumn(IsPrimaryKey = true, IsIdentity = true)]
|
||||
public int Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 关联光缆ID
|
||||
/// </summary>
|
||||
public int CableId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 故障时间
|
||||
/// </summary>
|
||||
public DateTime FaultTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 人员
|
||||
/// </summary>
|
||||
public string? Personnel { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 故障原因
|
||||
/// </summary>
|
||||
public string? FaultReason { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 表显故障里程
|
||||
/// </summary>
|
||||
public string? Mileage { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 地点描述
|
||||
/// </summary>
|
||||
public string? Location { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 纬度
|
||||
/// </summary>
|
||||
public decimal Latitude { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 经度
|
||||
/// </summary>
|
||||
public decimal Longitude { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 备注
|
||||
/// </summary>
|
||||
public string? Remark { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 提交人用户ID
|
||||
/// </summary>
|
||||
public long? UserId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 创建时间
|
||||
/// </summary>
|
||||
public DateTime? CreatedAt { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 更新时间
|
||||
/// </summary>
|
||||
public DateTime? UpdatedAt { get; set; }
|
||||
}
|
||||
}
|
||||
40
server/ZR.Model/Business/OdfCables.cs
Normal file
40
server/ZR.Model/Business/OdfCables.cs
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
namespace ZR.Model.Business
|
||||
{
|
||||
/// <summary>
|
||||
/// 光缆
|
||||
/// </summary>
|
||||
[SugarTable("odf_cables")]
|
||||
public class OdfCables
|
||||
{
|
||||
/// <summary>
|
||||
/// Id
|
||||
/// </summary>
|
||||
[SugarColumn(IsPrimaryKey = true, IsIdentity = true)]
|
||||
public int Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 光缆名称
|
||||
/// </summary>
|
||||
public string CableName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 所属公司/部门ID
|
||||
/// </summary>
|
||||
public long DeptId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 部门名称(冗余)
|
||||
/// </summary>
|
||||
public string DeptName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 创建时间
|
||||
/// </summary>
|
||||
public DateTime? CreatedAt { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 更新时间
|
||||
/// </summary>
|
||||
public DateTime? UpdatedAt { get; set; }
|
||||
}
|
||||
}
|
||||
46
server/ZR.Model/Business/OdfCheckin.cs
Normal file
46
server/ZR.Model/Business/OdfCheckin.cs
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
|
||||
namespace ZR.Model.Business
|
||||
{
|
||||
/// <summary>
|
||||
/// 签到记录
|
||||
/// </summary>
|
||||
[SugarTable("odf_checkin")]
|
||||
public class OdfCheckin
|
||||
{
|
||||
/// <summary>
|
||||
/// Id
|
||||
/// </summary>
|
||||
[SugarColumn(IsPrimaryKey = true, IsIdentity = true)]
|
||||
public int Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 关联机房ID
|
||||
/// </summary>
|
||||
public int RoomId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 签到人员
|
||||
/// </summary>
|
||||
public string Personnel { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 签到时间
|
||||
/// </summary>
|
||||
public DateTime CheckinTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 工作内容
|
||||
/// </summary>
|
||||
public string WorkContent { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 提交人用户ID
|
||||
/// </summary>
|
||||
public long? UserId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 记录创建时间
|
||||
/// </summary>
|
||||
public DateTime? CreatedAt { get; set; }
|
||||
}
|
||||
}
|
||||
30
server/ZR.Model/Business/OdfUserModules.cs
Normal file
30
server/ZR.Model/Business/OdfUserModules.cs
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
namespace ZR.Model.Business
|
||||
{
|
||||
/// <summary>
|
||||
/// 用户功能版块权限
|
||||
/// </summary>
|
||||
[SugarTable("odf_user_modules")]
|
||||
public class OdfUserModules
|
||||
{
|
||||
/// <summary>
|
||||
/// Id
|
||||
/// </summary>
|
||||
[SugarColumn(IsPrimaryKey = true, IsIdentity = true)]
|
||||
public int Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 用户ID
|
||||
/// </summary>
|
||||
public long UserId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 模块标识
|
||||
/// </summary>
|
||||
public string ModuleCode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 创建时间
|
||||
/// </summary>
|
||||
public DateTime? CreatedAt { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -7,6 +7,7 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Http.Features" Version="5.0.17" />
|
||||
<PackageReference Include="MiniExcel" Version="1.41.1" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
<PackageReference Include="SqlSugarCoreNoDrive" Version="5.1.4.193" />
|
||||
|
|
|
|||
|
|
@ -0,0 +1,32 @@
|
|||
using ZR.Model.Business;
|
||||
|
||||
namespace ZR.Service.Business.IBusinessService
|
||||
{
|
||||
/// <summary>
|
||||
/// 故障图片service接口
|
||||
/// </summary>
|
||||
public interface IOdfCableFaultImagesService : IBaseService<OdfCableFaultImages>
|
||||
{
|
||||
/// <summary>
|
||||
/// 按故障 ID 查询图片列表
|
||||
/// </summary>
|
||||
/// <param name="faultId"></param>
|
||||
/// <returns></returns>
|
||||
List<OdfCableFaultImages> GetByFaultId(int faultId);
|
||||
|
||||
/// <summary>
|
||||
/// 批量插入图片记录
|
||||
/// </summary>
|
||||
/// <param name="faultId"></param>
|
||||
/// <param name="imageUrls"></param>
|
||||
/// <returns></returns>
|
||||
int BatchInsert(int faultId, List<string> imageUrls);
|
||||
|
||||
/// <summary>
|
||||
/// 按故障 ID 删除所有图片记录
|
||||
/// </summary>
|
||||
/// <param name="faultId"></param>
|
||||
/// <returns></returns>
|
||||
int DeleteByFaultId(int faultId);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
using ZR.Model;
|
||||
using ZR.Model.Business;
|
||||
using ZR.Model.Business.Dto;
|
||||
|
||||
namespace ZR.Service.Business.IBusinessService
|
||||
{
|
||||
/// <summary>
|
||||
/// 干线故障service接口
|
||||
/// </summary>
|
||||
public interface IOdfCableFaultsService : IBaseService<OdfCableFaults>
|
||||
{
|
||||
/// <summary>
|
||||
/// 按 CableId 分页查询故障列表,联查光缆名称,按 FaultTime DESC 排序
|
||||
/// </summary>
|
||||
PagedInfo<dynamic> GetList(OdfCableFaultsQueryDto parm);
|
||||
|
||||
/// <summary>
|
||||
/// 查询故障详情,联查光缆名称和图片列表
|
||||
/// </summary>
|
||||
object GetDetail(int id);
|
||||
|
||||
/// <summary>
|
||||
/// 新增故障(含图片上传)
|
||||
/// </summary>
|
||||
Task<int> AddFault(OdfCableFaultAddDto dto);
|
||||
|
||||
/// <summary>
|
||||
/// 删除故障记录并级联删除关联图片
|
||||
/// </summary>
|
||||
int Delete(int id);
|
||||
|
||||
/// <summary>
|
||||
/// 导出故障列表
|
||||
/// </summary>
|
||||
PagedInfo<dynamic> ExportList(OdfCableFaultsQueryDto parm);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
using ZR.Model;
|
||||
using ZR.Model.Business;
|
||||
using ZR.Model.Business.Dto;
|
||||
|
||||
namespace ZR.Service.Business.IBusinessService
|
||||
{
|
||||
/// <summary>
|
||||
/// 光缆管理service接口
|
||||
/// </summary>
|
||||
public interface IOdfCablesService : IBaseService<OdfCables>
|
||||
{
|
||||
/// <summary>
|
||||
/// 按 DeptId 过滤光缆列表(支持分页和 CableName 模糊查询)
|
||||
/// </summary>
|
||||
/// <param name="parm"></param>
|
||||
/// <returns></returns>
|
||||
PagedInfo<OdfCables> GetList(OdfCablesQueryDto parm);
|
||||
|
||||
/// <summary>
|
||||
/// 在指定公司范围内搜索光缆和故障
|
||||
/// </summary>
|
||||
/// <param name="deptId"></param>
|
||||
/// <param name="keyword"></param>
|
||||
/// <returns></returns>
|
||||
object Search(long deptId, string keyword);
|
||||
|
||||
/// <summary>
|
||||
/// 新增光缆
|
||||
/// </summary>
|
||||
/// <param name="model"></param>
|
||||
/// <returns></returns>
|
||||
OdfCables Add(OdfCables model);
|
||||
|
||||
/// <summary>
|
||||
/// 修改光缆
|
||||
/// </summary>
|
||||
/// <param name="model"></param>
|
||||
/// <returns></returns>
|
||||
int Update(OdfCables model);
|
||||
|
||||
/// <summary>
|
||||
/// 删除光缆
|
||||
/// </summary>
|
||||
/// <param name="id"></param>
|
||||
/// <returns></returns>
|
||||
int Delete(int id);
|
||||
|
||||
/// <summary>
|
||||
/// 获取光缆详情
|
||||
/// </summary>
|
||||
/// <param name="id"></param>
|
||||
/// <returns></returns>
|
||||
OdfCables GetDetail(int id);
|
||||
|
||||
/// <summary>
|
||||
/// 导出光缆列表
|
||||
/// </summary>
|
||||
/// <param name="parm"></param>
|
||||
/// <returns></returns>
|
||||
PagedInfo<OdfCables> ExportList(OdfCablesQueryDto parm);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
using ZR.Model;
|
||||
using ZR.Model.Business;
|
||||
using ZR.Model.Business.Dto;
|
||||
|
||||
namespace ZR.Service.Business.IBusinessService
|
||||
{
|
||||
/// <summary>
|
||||
/// 签到记录service接口
|
||||
/// </summary>
|
||||
public interface IOdfCheckinService : IBaseService<OdfCheckin>
|
||||
{
|
||||
/// <summary>
|
||||
/// 新增签到记录
|
||||
/// </summary>
|
||||
/// <param name="dto"></param>
|
||||
/// <returns></returns>
|
||||
OdfCheckin AddCheckin(OdfCheckinDto dto);
|
||||
|
||||
/// <summary>
|
||||
/// 分页查询签到记录(联查机房名称和提交人)
|
||||
/// </summary>
|
||||
/// <param name="parm"></param>
|
||||
/// <returns></returns>
|
||||
PagedInfo<OdfCheckinListDto> GetList(OdfCheckinQueryDto parm);
|
||||
|
||||
/// <summary>
|
||||
/// 导出签到记录
|
||||
/// </summary>
|
||||
/// <param name="parm"></param>
|
||||
/// <returns></returns>
|
||||
PagedInfo<OdfCheckinListDto> ExportList(OdfCheckinQueryDto parm);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
using ZR.Model.Business;
|
||||
|
||||
namespace ZR.Service.Business.IBusinessService
|
||||
{
|
||||
/// <summary>
|
||||
/// 用户模块权限service接口
|
||||
/// </summary>
|
||||
public interface IOdfUserModulesService : IBaseService<OdfUserModules>
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取用户的功能版块列表
|
||||
/// </summary>
|
||||
/// <param name="userId"></param>
|
||||
/// <returns></returns>
|
||||
List<string> GetUserModules(long userId);
|
||||
|
||||
/// <summary>
|
||||
/// 获取用户列表(userId, userName)
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
List<dynamic> GetUserList();
|
||||
|
||||
/// <summary>
|
||||
/// 保存用户模块权限(先删后插,事务保证原子性)
|
||||
/// </summary>
|
||||
/// <param name="userId"></param>
|
||||
/// <param name="modules"></param>
|
||||
/// <returns></returns>
|
||||
int SaveUserModules(long userId, List<string> modules);
|
||||
}
|
||||
}
|
||||
62
server/ZR.Service/Business/OdfCableFaultImagesService.cs
Normal file
62
server/ZR.Service/Business/OdfCableFaultImagesService.cs
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
using Infrastructure.Attribute;
|
||||
using ZR.Model.Business;
|
||||
using ZR.Repository;
|
||||
using ZR.Service.Business.IBusinessService;
|
||||
|
||||
namespace ZR.Service.Business
|
||||
{
|
||||
/// <summary>
|
||||
/// 故障图片Service业务层处理
|
||||
/// </summary>
|
||||
[AppService(ServiceType = typeof(IOdfCableFaultImagesService), ServiceLifetime = LifeTime.Transient)]
|
||||
public class OdfCableFaultImagesService : BaseService<OdfCableFaultImages>, IOdfCableFaultImagesService
|
||||
{
|
||||
/// <summary>
|
||||
/// 按故障 ID 查询图片列表
|
||||
/// </summary>
|
||||
/// <param name="faultId"></param>
|
||||
/// <returns></returns>
|
||||
public List<OdfCableFaultImages> GetByFaultId(int faultId)
|
||||
{
|
||||
return Queryable()
|
||||
.Where(x => x.FaultId == faultId)
|
||||
.OrderBy(x => x.Id)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 批量插入图片记录
|
||||
/// </summary>
|
||||
/// <param name="faultId"></param>
|
||||
/// <param name="imageUrls"></param>
|
||||
/// <returns></returns>
|
||||
public int BatchInsert(int faultId, List<string> imageUrls)
|
||||
{
|
||||
if (imageUrls == null || imageUrls.Count == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
var list = imageUrls.Select(url => new OdfCableFaultImages
|
||||
{
|
||||
FaultId = faultId,
|
||||
ImageUrl = url,
|
||||
CreatedAt = DateTime.Now
|
||||
}).ToList();
|
||||
|
||||
return Insert(list);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 按故障 ID 删除所有图片记录
|
||||
/// </summary>
|
||||
/// <param name="faultId"></param>
|
||||
/// <returns></returns>
|
||||
public int DeleteByFaultId(int faultId)
|
||||
{
|
||||
return Deleteable()
|
||||
.Where(x => x.FaultId == faultId)
|
||||
.ExecuteCommand();
|
||||
}
|
||||
}
|
||||
}
|
||||
223
server/ZR.Service/Business/OdfCableFaultsService.cs
Normal file
223
server/ZR.Service/Business/OdfCableFaultsService.cs
Normal file
|
|
@ -0,0 +1,223 @@
|
|||
using Infrastructure;
|
||||
using Infrastructure.Attribute;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using ZR.Model;
|
||||
using ZR.Model.Business;
|
||||
using ZR.Model.Business.Dto;
|
||||
using ZR.Model.Dto;
|
||||
using ZR.Repository;
|
||||
using ZR.Service.Business.IBusinessService;
|
||||
|
||||
namespace ZR.Service.Business
|
||||
{
|
||||
/// <summary>
|
||||
/// 干线故障Service业务层处理
|
||||
/// </summary>
|
||||
[AppService(ServiceType = typeof(IOdfCableFaultsService), ServiceLifetime = LifeTime.Transient)]
|
||||
public class OdfCableFaultsService : BaseService<OdfCableFaults>, IOdfCableFaultsService
|
||||
{
|
||||
/// <summary>
|
||||
/// 按 CableId 分页查询故障列表,联查光缆名称,按 FaultTime DESC 排序
|
||||
/// </summary>
|
||||
public PagedInfo<dynamic> GetList(OdfCableFaultsQueryDto parm)
|
||||
{
|
||||
var predicate = QueryExp(parm);
|
||||
|
||||
var response = Queryable()
|
||||
.Where(predicate.ToExpression())
|
||||
.LeftJoin<OdfCables>((f, c) => f.CableId == c.Id)
|
||||
.OrderByDescending((f, c) => f.FaultTime)
|
||||
.Select((f, c) => new
|
||||
{
|
||||
f.Id,
|
||||
f.CableId,
|
||||
f.FaultTime,
|
||||
f.Personnel,
|
||||
f.FaultReason,
|
||||
f.Mileage,
|
||||
f.Location,
|
||||
f.Latitude,
|
||||
f.Longitude,
|
||||
f.Remark,
|
||||
f.CreatedAt,
|
||||
CableName = c.CableName
|
||||
})
|
||||
.ToPage<dynamic>(parm);
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查询故障详情,联查光缆名称和图片列表
|
||||
/// </summary>
|
||||
public object GetDetail(int id)
|
||||
{
|
||||
var fault = Queryable()
|
||||
.LeftJoin<OdfCables>((f, c) => f.CableId == c.Id)
|
||||
.Where((f, c) => f.Id == id)
|
||||
.Select((f, c) => new
|
||||
{
|
||||
f.Id,
|
||||
f.CableId,
|
||||
f.FaultTime,
|
||||
f.Personnel,
|
||||
f.FaultReason,
|
||||
f.Mileage,
|
||||
f.Location,
|
||||
f.Latitude,
|
||||
f.Longitude,
|
||||
f.Remark,
|
||||
f.UserId,
|
||||
f.CreatedAt,
|
||||
CableName = c.CableName
|
||||
})
|
||||
.First();
|
||||
|
||||
if (fault == null)
|
||||
{
|
||||
throw new CustomException("故障记录不存在");
|
||||
}
|
||||
|
||||
// 查询关联图片列表
|
||||
var images = Context.Queryable<OdfCableFaultImages>()
|
||||
.Where(img => img.FaultId == id)
|
||||
.OrderBy(img => img.Id)
|
||||
.Select(img => new { img.Id, img.ImageUrl })
|
||||
.ToList();
|
||||
|
||||
return new
|
||||
{
|
||||
fault.Id,
|
||||
fault.CableId,
|
||||
fault.FaultTime,
|
||||
fault.Personnel,
|
||||
fault.FaultReason,
|
||||
fault.Mileage,
|
||||
fault.Location,
|
||||
fault.Latitude,
|
||||
fault.Longitude,
|
||||
fault.Remark,
|
||||
fault.CableName,
|
||||
fault.CreatedAt,
|
||||
Images = images
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 新增故障(含图片上传)
|
||||
/// </summary>
|
||||
public async Task<int> AddFault(OdfCableFaultAddDto dto)
|
||||
{
|
||||
// 校验 CableId 存在
|
||||
var cable = Context.Queryable<OdfCables>()
|
||||
.Where(c => c.Id == dto.CableId)
|
||||
.First();
|
||||
if (cable == null)
|
||||
{
|
||||
throw new CustomException("光缆不存在");
|
||||
}
|
||||
|
||||
// 校验至少 1 张图片
|
||||
if (dto.Images == null || dto.Images.Length == 0)
|
||||
{
|
||||
throw new CustomException("请至少上传一张图片");
|
||||
}
|
||||
|
||||
// 插入故障记录
|
||||
var model = new OdfCableFaults
|
||||
{
|
||||
CableId = dto.CableId,
|
||||
FaultTime = DateTime.Parse(dto.FaultTime),
|
||||
Personnel = dto.Personnel,
|
||||
FaultReason = dto.FaultReason,
|
||||
Mileage = dto.Mileage,
|
||||
Location = dto.Location,
|
||||
Latitude = dto.Latitude,
|
||||
Longitude = dto.Longitude,
|
||||
Remark = dto.Remark,
|
||||
UserId = dto.UserId,
|
||||
CreatedAt = DateTime.Now,
|
||||
UpdatedAt = DateTime.Now
|
||||
};
|
||||
|
||||
var faultEntity = Insertable(model).ExecuteReturnEntity();
|
||||
int faultId = faultEntity.Id;
|
||||
|
||||
// 保存图片文件并插入图片记录
|
||||
IWebHostEnvironment webHostEnvironment = App.WebHostEnvironment;
|
||||
string webRootPath = webHostEnvironment.WebRootPath;
|
||||
string uploadDir = Path.Combine("uploads", "fault");
|
||||
string fullDir = Path.Combine(webRootPath, uploadDir);
|
||||
|
||||
if (!Directory.Exists(fullDir))
|
||||
{
|
||||
Directory.CreateDirectory(fullDir);
|
||||
}
|
||||
|
||||
foreach (var image in dto.Images)
|
||||
{
|
||||
string fileExt = Path.GetExtension(image.FileName);
|
||||
string fileName = $"{DateTime.Now:yyyyMMdd}_{Guid.NewGuid():N}{fileExt}";
|
||||
string filePath = Path.Combine(fullDir, fileName);
|
||||
|
||||
using (var stream = new FileStream(filePath, FileMode.Create))
|
||||
{
|
||||
await image.CopyToAsync(stream);
|
||||
}
|
||||
|
||||
string imageUrl = $"/{uploadDir}/{fileName}".Replace("\\", "/");
|
||||
|
||||
var imageRecord = new OdfCableFaultImages
|
||||
{
|
||||
FaultId = faultId,
|
||||
ImageUrl = imageUrl,
|
||||
CreatedAt = DateTime.Now
|
||||
};
|
||||
|
||||
Context.Insertable(imageRecord).ExecuteCommand();
|
||||
}
|
||||
|
||||
return faultId;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 删除故障记录并级联删除关联图片
|
||||
/// </summary>
|
||||
public int Delete(int id)
|
||||
{
|
||||
// 先删除关联图片记录
|
||||
Context.Deleteable<OdfCableFaultImages>()
|
||||
.Where(img => img.FaultId == id)
|
||||
.ExecuteCommand();
|
||||
|
||||
// 再删除故障记录
|
||||
return base.Delete(id);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 导出故障列表
|
||||
/// </summary>
|
||||
public PagedInfo<dynamic> ExportList(OdfCableFaultsQueryDto parm)
|
||||
{
|
||||
parm.PageNum = 1;
|
||||
parm.PageSize = 100000;
|
||||
return GetList(parm);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查询表达式
|
||||
/// </summary>
|
||||
private static Expressionable<OdfCableFaults> QueryExp(OdfCableFaultsQueryDto parm)
|
||||
{
|
||||
var predicate = Expressionable.Create<OdfCableFaults>();
|
||||
|
||||
predicate = predicate.AndIF(parm.CableId != null, it => it.CableId == parm.CableId);
|
||||
predicate = predicate.AndIF(parm.BeginFaultTime != null, it => it.FaultTime >= parm.BeginFaultTime);
|
||||
predicate = predicate.AndIF(parm.EndFaultTime != null, it => it.FaultTime <= parm.EndFaultTime);
|
||||
predicate = predicate.AndIF(!string.IsNullOrEmpty(parm.FaultReason), it => it.FaultReason.Contains(parm.FaultReason));
|
||||
|
||||
return predicate;
|
||||
}
|
||||
}
|
||||
}
|
||||
128
server/ZR.Service/Business/OdfCablesService.cs
Normal file
128
server/ZR.Service/Business/OdfCablesService.cs
Normal file
|
|
@ -0,0 +1,128 @@
|
|||
using Infrastructure.Attribute;
|
||||
using ZR.Model;
|
||||
using ZR.Model.Business;
|
||||
using ZR.Model.Business.Dto;
|
||||
using ZR.Repository;
|
||||
using ZR.Service.Business.IBusinessService;
|
||||
|
||||
namespace ZR.Service.Business
|
||||
{
|
||||
/// <summary>
|
||||
/// 光缆管理Service业务层处理
|
||||
/// </summary>
|
||||
[AppService(ServiceType = typeof(IOdfCablesService), ServiceLifetime = LifeTime.Transient)]
|
||||
public class OdfCablesService : BaseService<OdfCables>, IOdfCablesService
|
||||
{
|
||||
/// <summary>
|
||||
/// 按 DeptId 过滤光缆列表(支持分页和 CableName 模糊查询)
|
||||
/// </summary>
|
||||
/// <param name="parm"></param>
|
||||
/// <returns></returns>
|
||||
public PagedInfo<OdfCables> GetList(OdfCablesQueryDto parm)
|
||||
{
|
||||
var predicate = Expressionable.Create<OdfCables>();
|
||||
|
||||
predicate = predicate.AndIF(parm.DeptId != null, it => it.DeptId == parm.DeptId);
|
||||
predicate = predicate.AndIF(!string.IsNullOrEmpty(parm.CableName), it => it.CableName.Contains(parm.CableName));
|
||||
|
||||
var response = Queryable()
|
||||
.Where(predicate.ToExpression())
|
||||
.OrderByDescending(it => it.CreatedAt)
|
||||
.ToPage(parm);
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 在指定公司范围内搜索光缆和故障
|
||||
/// </summary>
|
||||
/// <param name="deptId"></param>
|
||||
/// <param name="keyword"></param>
|
||||
/// <returns></returns>
|
||||
public object Search(long deptId, string keyword)
|
||||
{
|
||||
// 搜索光缆:按 DeptId 过滤,CableName LIKE keyword
|
||||
var cables = Queryable()
|
||||
.Where(c => c.DeptId == deptId)
|
||||
.WhereIF(!string.IsNullOrEmpty(keyword), c => c.CableName.Contains(keyword))
|
||||
.Select(c => new { c.Id, c.CableName })
|
||||
.ToList();
|
||||
|
||||
// 搜索故障:联查 odf_cables 按 DeptId 过滤,对 FaultReason/Mileage/Location LIKE keyword
|
||||
var faults = Context.Queryable<OdfCableFaults>()
|
||||
.LeftJoin<OdfCables>((f, c) => f.CableId == c.Id)
|
||||
.Where((f, c) => c.DeptId == deptId)
|
||||
.WhereIF(!string.IsNullOrEmpty(keyword), (f, c) =>
|
||||
f.FaultReason.Contains(keyword) ||
|
||||
f.Mileage.Contains(keyword) ||
|
||||
f.Location.Contains(keyword))
|
||||
.OrderByDescending((f, c) => f.FaultTime)
|
||||
.Select((f, c) => new
|
||||
{
|
||||
f.Id,
|
||||
f.FaultTime,
|
||||
f.FaultReason,
|
||||
f.Mileage,
|
||||
CableName = c.CableName
|
||||
})
|
||||
.ToList();
|
||||
|
||||
return new { cables, faults };
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 新增光缆
|
||||
/// </summary>
|
||||
/// <param name="model"></param>
|
||||
/// <returns></returns>
|
||||
public OdfCables Add(OdfCables model)
|
||||
{
|
||||
model.CreatedAt = DateTime.Now;
|
||||
model.UpdatedAt = DateTime.Now;
|
||||
return Insertable(model).ExecuteReturnEntity();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 修改光缆
|
||||
/// </summary>
|
||||
/// <param name="model"></param>
|
||||
/// <returns></returns>
|
||||
public int Update(OdfCables model)
|
||||
{
|
||||
model.UpdatedAt = DateTime.Now;
|
||||
return base.Update(model, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 删除光缆
|
||||
/// </summary>
|
||||
/// <param name="id"></param>
|
||||
/// <returns></returns>
|
||||
public int Delete(int id)
|
||||
{
|
||||
return base.Delete(id);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取光缆详情
|
||||
/// </summary>
|
||||
/// <param name="id"></param>
|
||||
/// <returns></returns>
|
||||
public OdfCables GetDetail(int id)
|
||||
{
|
||||
return GetFirst(x => x.Id == id);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 导出光缆列表
|
||||
/// </summary>
|
||||
/// <param name="parm"></param>
|
||||
/// <returns></returns>
|
||||
public PagedInfo<OdfCables> ExportList(OdfCablesQueryDto parm)
|
||||
{
|
||||
parm.PageNum = 1;
|
||||
parm.PageSize = 100000;
|
||||
return GetList(parm);
|
||||
}
|
||||
}
|
||||
}
|
||||
113
server/ZR.Service/Business/OdfCheckinService.cs
Normal file
113
server/ZR.Service/Business/OdfCheckinService.cs
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
using Infrastructure.Attribute;
|
||||
using ZR.Model;
|
||||
using ZR.Model.Business;
|
||||
using ZR.Model.Business.Dto;
|
||||
using ZR.Model.System;
|
||||
using ZR.Repository;
|
||||
using ZR.Service.Business.IBusinessService;
|
||||
|
||||
namespace ZR.Service.Business
|
||||
{
|
||||
/// <summary>
|
||||
/// 签到记录Service业务层处理
|
||||
/// </summary>
|
||||
[AppService(ServiceType = typeof(IOdfCheckinService), ServiceLifetime = LifeTime.Transient)]
|
||||
public class OdfCheckinService : BaseService<OdfCheckin>, IOdfCheckinService
|
||||
{
|
||||
/// <summary>
|
||||
/// 新增签到记录
|
||||
/// </summary>
|
||||
/// <param name="dto"></param>
|
||||
/// <returns></returns>
|
||||
public OdfCheckin AddCheckin(OdfCheckinDto dto)
|
||||
{
|
||||
// 校验 RoomId 存在
|
||||
var room = Context.Queryable<OdfRooms>()
|
||||
.Where(r => r.Id == dto.RoomId)
|
||||
.First();
|
||||
if (room == null)
|
||||
{
|
||||
throw new CustomException("机房不存在");
|
||||
}
|
||||
|
||||
var model = new OdfCheckin
|
||||
{
|
||||
RoomId = dto.RoomId,
|
||||
Personnel = dto.Personnel,
|
||||
CheckinTime = DateTime.Parse(dto.CheckinTime),
|
||||
WorkContent = dto.WorkContent,
|
||||
UserId = dto.UserId,
|
||||
CreatedAt = DateTime.Now
|
||||
};
|
||||
|
||||
return Insertable(model).ExecuteReturnEntity();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 分页查询签到记录(联查机房名称和提交人用户名)
|
||||
/// </summary>
|
||||
/// <param name="parm"></param>
|
||||
/// <returns></returns>
|
||||
public PagedInfo<OdfCheckinListDto> GetList(OdfCheckinQueryDto parm)
|
||||
{
|
||||
var predicate = QueryExp(parm);
|
||||
|
||||
var response = Queryable()
|
||||
.Where(predicate.ToExpression())
|
||||
.LeftJoin<OdfRooms>((it, r) => it.RoomId == r.Id)
|
||||
.LeftJoin<SysUser>((it, r, u) => it.UserId == u.UserId)
|
||||
.OrderByDescending((it, r, u) => it.CheckinTime)
|
||||
.Select((it, r, u) => new OdfCheckinListDto()
|
||||
{
|
||||
RoomName = r.RoomName,
|
||||
UserName = u.NickName
|
||||
}, true)
|
||||
.ToPage(parm);
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 导出签到记录
|
||||
/// </summary>
|
||||
/// <param name="parm"></param>
|
||||
/// <returns></returns>
|
||||
public PagedInfo<OdfCheckinListDto> ExportList(OdfCheckinQueryDto parm)
|
||||
{
|
||||
parm.PageNum = 1;
|
||||
parm.PageSize = 100000;
|
||||
var predicate = QueryExp(parm);
|
||||
|
||||
var response = Queryable()
|
||||
.Where(predicate.ToExpression())
|
||||
.LeftJoin<OdfRooms>((it, r) => it.RoomId == r.Id)
|
||||
.LeftJoin<SysUser>((it, r, u) => it.UserId == u.UserId)
|
||||
.OrderByDescending((it, r, u) => it.CheckinTime)
|
||||
.Select((it, r, u) => new OdfCheckinListDto()
|
||||
{
|
||||
RoomName = r.RoomName,
|
||||
UserName = u.NickName
|
||||
}, true)
|
||||
.ToPage(parm);
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查询表达式
|
||||
/// </summary>
|
||||
/// <param name="parm"></param>
|
||||
/// <returns></returns>
|
||||
private static Expressionable<OdfCheckin> QueryExp(OdfCheckinQueryDto parm)
|
||||
{
|
||||
var predicate = Expressionable.Create<OdfCheckin>();
|
||||
|
||||
predicate = predicate.AndIF(parm.RoomId != null, it => it.RoomId == parm.RoomId);
|
||||
predicate = predicate.AndIF(!string.IsNullOrEmpty(parm.Personnel), it => it.Personnel.Contains(parm.Personnel));
|
||||
predicate = predicate.AndIF(parm.BeginCheckinTime != null, it => it.CheckinTime >= parm.BeginCheckinTime);
|
||||
predicate = predicate.AndIF(parm.EndCheckinTime != null, it => it.CheckinTime <= parm.EndCheckinTime);
|
||||
|
||||
return predicate;
|
||||
}
|
||||
}
|
||||
}
|
||||
74
server/ZR.Service/Business/OdfUserModulesService.cs
Normal file
74
server/ZR.Service/Business/OdfUserModulesService.cs
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
using Infrastructure.Attribute;
|
||||
using ZR.Model.Business;
|
||||
using ZR.Model.System;
|
||||
using ZR.Repository;
|
||||
using ZR.Service.Business.IBusinessService;
|
||||
|
||||
namespace ZR.Service.Business
|
||||
{
|
||||
/// <summary>
|
||||
/// 用户模块权限Service业务层处理
|
||||
/// </summary>
|
||||
[AppService(ServiceType = typeof(IOdfUserModulesService), ServiceLifetime = LifeTime.Transient)]
|
||||
public class OdfUserModulesService : BaseService<OdfUserModules>, IOdfUserModulesService
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取用户的功能版块列表
|
||||
/// </summary>
|
||||
/// <param name="userId"></param>
|
||||
/// <returns></returns>
|
||||
public List<string> GetUserModules(long userId)
|
||||
{
|
||||
return Queryable()
|
||||
.Where(x => x.UserId == userId)
|
||||
.Select(x => x.ModuleCode)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取用户列表(userId, userName)
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public List<dynamic> GetUserList()
|
||||
{
|
||||
return Context.Queryable<SysUser>()
|
||||
.Where(u => u.DelFlag == 0 && u.Status == 0)
|
||||
.Select(u => new { u.UserId, u.NickName })
|
||||
.ToList()
|
||||
.Select(u => (dynamic)new { u.UserId, userName = u.NickName })
|
||||
.ToList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 保存用户模块权限(先删后插,事务保证原子性)
|
||||
/// </summary>
|
||||
/// <param name="userId"></param>
|
||||
/// <param name="modules"></param>
|
||||
/// <returns></returns>
|
||||
public int SaveUserModules(long userId, List<string> modules)
|
||||
{
|
||||
var result = UseTran(() =>
|
||||
{
|
||||
// 先删除该用户的所有模块权限
|
||||
Deleteable()
|
||||
.Where(x => x.UserId == userId)
|
||||
.ExecuteCommand();
|
||||
|
||||
// 再插入新的模块权限
|
||||
if (modules != null && modules.Count > 0)
|
||||
{
|
||||
var list = modules.Select(m => new OdfUserModules
|
||||
{
|
||||
UserId = userId,
|
||||
ModuleCode = m,
|
||||
CreatedAt = DateTime.Now
|
||||
}).ToList();
|
||||
|
||||
Insert(list);
|
||||
}
|
||||
});
|
||||
|
||||
return result.IsSuccess ? 1 : 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
41
server/ZR.Vue/src/api/business/odfcablefaults.js
Normal file
41
server/ZR.Vue/src/api/business/odfcablefaults.js
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
import request from '@/utils/request'
|
||||
import { downFile } from '@/utils/request'
|
||||
|
||||
/**
|
||||
* 干线故障列表分页查询
|
||||
* @param {查询条件} data
|
||||
*/
|
||||
export function listOdfCableFaults(query) {
|
||||
return request({
|
||||
url: 'business/OdfCableFaults/list',
|
||||
method: 'get',
|
||||
params: query,
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取干线故障详情
|
||||
* @param {Id}
|
||||
*/
|
||||
export function getOdfCableFaults(id) {
|
||||
return request({
|
||||
url: 'business/OdfCableFaults/' + id,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除干线故障
|
||||
* @param {主键} pid
|
||||
*/
|
||||
export function delOdfCableFaults(pid) {
|
||||
return request({
|
||||
url: 'business/OdfCableFaults/delete/' + pid,
|
||||
method: 'POST'
|
||||
})
|
||||
}
|
||||
|
||||
// 导出干线故障列表
|
||||
export async function exportOdfCableFaults(query) {
|
||||
await downFile('business/OdfCableFaults/export', { ...query })
|
||||
}
|
||||
65
server/ZR.Vue/src/api/business/odfcables.js
Normal file
65
server/ZR.Vue/src/api/business/odfcables.js
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
import request from '@/utils/request'
|
||||
import { downFile } from '@/utils/request'
|
||||
|
||||
/**
|
||||
* 光缆列表分页查询
|
||||
* @param {查询条件} data
|
||||
*/
|
||||
export function listOdfCables(query) {
|
||||
return request({
|
||||
url: 'business/OdfCables/list',
|
||||
method: 'get',
|
||||
params: query,
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增光缆
|
||||
* @param data
|
||||
*/
|
||||
export function addOdfCables(data) {
|
||||
return request({
|
||||
url: 'business/OdfCables',
|
||||
method: 'post',
|
||||
data: data,
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改光缆
|
||||
* @param data
|
||||
*/
|
||||
export function updateOdfCables(data) {
|
||||
return request({
|
||||
url: 'business/OdfCables',
|
||||
method: 'PUT',
|
||||
data: data,
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取光缆详情
|
||||
* @param {Id}
|
||||
*/
|
||||
export function getOdfCables(id) {
|
||||
return request({
|
||||
url: 'business/OdfCables/' + id,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除光缆
|
||||
* @param {主键} pid
|
||||
*/
|
||||
export function delOdfCables(pid) {
|
||||
return request({
|
||||
url: 'business/OdfCables/delete/' + pid,
|
||||
method: 'POST'
|
||||
})
|
||||
}
|
||||
|
||||
// 导出光缆
|
||||
export async function exportOdfCables(query) {
|
||||
await downFile('business/OdfCables/export', { ...query })
|
||||
}
|
||||
19
server/ZR.Vue/src/api/business/odfcheckin.js
Normal file
19
server/ZR.Vue/src/api/business/odfcheckin.js
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
import request from '@/utils/request'
|
||||
import { downFile } from '@/utils/request'
|
||||
|
||||
/**
|
||||
* 签到记录列表分页查询
|
||||
* @param {查询条件} data
|
||||
*/
|
||||
export function listOdfCheckin(query) {
|
||||
return request({
|
||||
url: 'business/OdfCheckin/list',
|
||||
method: 'get',
|
||||
params: query,
|
||||
})
|
||||
}
|
||||
|
||||
// 导出签到记录
|
||||
export async function exportOdfCheckin(query) {
|
||||
await downFile('business/OdfCheckin/export', { ...query })
|
||||
}
|
||||
37
server/ZR.Vue/src/api/business/odfusermodules.js
Normal file
37
server/ZR.Vue/src/api/business/odfusermodules.js
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
import request from '@/utils/request'
|
||||
|
||||
/**
|
||||
* 获取用户列表
|
||||
* @param {查询条件} query
|
||||
*/
|
||||
export function listUsers(query) {
|
||||
return request({
|
||||
url: 'business/OdfUserModules/users',
|
||||
method: 'get',
|
||||
params: query,
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定用户的模块权限
|
||||
* @param {用户ID} userId
|
||||
*/
|
||||
export function getUserModules(userId) {
|
||||
return request({
|
||||
url: 'business/OdfUserModules/list',
|
||||
method: 'get',
|
||||
params: { userId },
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量保存用户模块权限
|
||||
* @param data { userId, modules: [...] }
|
||||
*/
|
||||
export function saveUserModules(data) {
|
||||
return request({
|
||||
url: 'business/OdfUserModules/save',
|
||||
method: 'post',
|
||||
data: data,
|
||||
})
|
||||
}
|
||||
199
server/ZR.Vue/src/components/business/OdfCableForm.vue
Normal file
199
server/ZR.Vue/src/components/business/OdfCableForm.vue
Normal file
|
|
@ -0,0 +1,199 @@
|
|||
<!--
|
||||
* @Descripttion: 光缆表单组件
|
||||
* @Author: (admin)
|
||||
* @Date: (2025-01-16)
|
||||
-->
|
||||
<template>
|
||||
<el-dialog :title="title" :lock-scroll="false" v-model="dialogVisible" @close="handleClose">
|
||||
<el-form ref="formRef" :model="form" :rules="rules" label-width="100px">
|
||||
<el-row :gutter="20">
|
||||
<el-col :lg="12">
|
||||
<el-form-item label="光缆名称" prop="cableName">
|
||||
<el-input v-model="form.cableName" :disabled="isView" placeholder="请输入光缆名称" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<el-col :lg="12">
|
||||
<el-form-item label="所属部门" prop="deptId">
|
||||
<el-tree-select
|
||||
v-model="form.deptId"
|
||||
:data="deptOptions"
|
||||
:disabled="isView"
|
||||
:props="{ value: 'id', label: 'label', children: 'children' }"
|
||||
value-key="id"
|
||||
placeholder="请选择所属部门"
|
||||
check-strictly
|
||||
:render-after-expand="false" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<!-- 查看模式下显示时间信息 -->
|
||||
<template v-if="isView && isEdit">
|
||||
<el-col :lg="12">
|
||||
<el-form-item label="创建时间">
|
||||
<el-input v-model="form.createdAt" disabled />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :lg="12">
|
||||
<el-form-item label="修改时间">
|
||||
<el-input v-model="form.updatedAt" disabled />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</template>
|
||||
</el-row>
|
||||
</el-form>
|
||||
|
||||
<template #footer v-if="!isView">
|
||||
<el-button text @click="handleClose">{{ $t('btn.cancel') }}</el-button>
|
||||
<el-button type="primary" :loading="submitLoading" @click="submitForm">{{ $t('btn.submit') }}</el-button>
|
||||
</template>
|
||||
|
||||
<template #footer v-else>
|
||||
<el-button text @click="handleClose">{{ $t('btn.close') }}</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup name="odfCableForm">
|
||||
import { addOdfCables, updateOdfCables, getOdfCables } from '@/api/business/odfcables.js'
|
||||
import { treeselect } from '@/api/system/dept'
|
||||
|
||||
const props = defineProps({
|
||||
visible: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
id: {
|
||||
type: [String, Number],
|
||||
default: null
|
||||
},
|
||||
mode: {
|
||||
type: String,
|
||||
default: 'edit', // 'add', 'edit', 'view'
|
||||
validator: (value) => ['add', 'edit', 'view'].includes(value)
|
||||
}
|
||||
})
|
||||
|
||||
const emit = defineEmits(['update:visible', 'success'])
|
||||
|
||||
const { proxy } = getCurrentInstance()
|
||||
const formRef = ref()
|
||||
const submitLoading = ref(false)
|
||||
const deptOptions = ref([])
|
||||
|
||||
// 计算属性
|
||||
const dialogVisible = computed({
|
||||
get: () => props.visible,
|
||||
set: (value) => emit('update:visible', value)
|
||||
})
|
||||
|
||||
const isEdit = computed(() => !!props.id)
|
||||
const isView = computed(() => props.mode === 'view')
|
||||
const title = computed(() => {
|
||||
if (props.mode === 'view') return '查看光缆'
|
||||
return isEdit.value ? '修改光缆' : '添加光缆'
|
||||
})
|
||||
|
||||
// 表单数据
|
||||
const form = ref({
|
||||
id: null,
|
||||
cableName: null,
|
||||
deptId: 0,
|
||||
deptName: null,
|
||||
createdAt: null,
|
||||
updatedAt: null
|
||||
})
|
||||
|
||||
// 表单验证规则
|
||||
const rules = {
|
||||
cableName: [{ required: true, message: '光缆名称不能为空', trigger: 'blur' }]
|
||||
}
|
||||
|
||||
// 获取部门树数据
|
||||
function getDeptTreeData() {
|
||||
treeselect().then((response) => {
|
||||
deptOptions.value = [{ id: 0, label: '未知部门', children: [] }, ...response.data]
|
||||
})
|
||||
}
|
||||
|
||||
// 重置表单
|
||||
function resetForm() {
|
||||
form.value = {
|
||||
id: null,
|
||||
cableName: null,
|
||||
deptId: 0,
|
||||
deptName: null,
|
||||
createdAt: null,
|
||||
updatedAt: null
|
||||
}
|
||||
nextTick(() => {
|
||||
proxy.resetForm('formRef')
|
||||
})
|
||||
}
|
||||
|
||||
// 加载数据
|
||||
function loadData() {
|
||||
if (props.id) {
|
||||
getOdfCables(props.id).then((res) => {
|
||||
const { code, data } = res
|
||||
if (code == 200) {
|
||||
form.value = { ...data }
|
||||
}
|
||||
})
|
||||
} else {
|
||||
resetForm()
|
||||
}
|
||||
}
|
||||
|
||||
// 提交表单
|
||||
function submitForm() {
|
||||
proxy.$refs['formRef'].validate((valid) => {
|
||||
if (valid) {
|
||||
submitLoading.value = true
|
||||
|
||||
if (isEdit.value) {
|
||||
updateOdfCables(form.value)
|
||||
.then((res) => {
|
||||
proxy.$modal.msgSuccess('修改成功')
|
||||
handleSuccess()
|
||||
})
|
||||
.finally(() => {
|
||||
submitLoading.value = false
|
||||
})
|
||||
} else {
|
||||
addOdfCables(form.value)
|
||||
.then((res) => {
|
||||
proxy.$modal.msgSuccess('新增成功')
|
||||
handleSuccess()
|
||||
})
|
||||
.finally(() => {
|
||||
submitLoading.value = false
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 成功处理
|
||||
function handleSuccess() {
|
||||
dialogVisible.value = false
|
||||
emit('success')
|
||||
}
|
||||
|
||||
// 关闭对话框
|
||||
function handleClose() {
|
||||
dialogVisible.value = false
|
||||
resetForm()
|
||||
}
|
||||
|
||||
// 监听对话框显示状态
|
||||
watch(
|
||||
() => props.visible,
|
||||
(newVal) => {
|
||||
if (newVal) {
|
||||
getDeptTreeData()
|
||||
loadData()
|
||||
}
|
||||
}
|
||||
)
|
||||
</script>
|
||||
301
server/ZR.Vue/src/views/business/OdfCableFaults.vue
Normal file
301
server/ZR.Vue/src/views/business/OdfCableFaults.vue
Normal file
|
|
@ -0,0 +1,301 @@
|
|||
<!--
|
||||
* @Descripttion: (干线故障管理/odf_cable_faults)
|
||||
* @Author: (admin)
|
||||
* @Date: (2025-08-05)
|
||||
-->
|
||||
<template>
|
||||
<div>
|
||||
<el-form :model="queryParams" label-position="right" inline ref="queryRef" v-show="showSearch" @submit.prevent>
|
||||
<el-form-item label="所属光缆" prop="cableId">
|
||||
<el-select
|
||||
v-model="queryParams.cableId"
|
||||
filterable
|
||||
remote
|
||||
reserve-keyword
|
||||
clearable
|
||||
placeholder="请输入光缆名称搜索"
|
||||
:remote-method="remoteCableSearch"
|
||||
:loading="cableSearchLoading">
|
||||
<el-option v-for="item in cableOptions" :key="item.id" :label="item.cableName" :value="item.id" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="故障时间" prop="faultTimeRange">
|
||||
<el-date-picker
|
||||
v-model="faultTimeRange"
|
||||
type="daterange"
|
||||
range-separator="-"
|
||||
start-placeholder="开始日期"
|
||||
end-placeholder="结束日期"
|
||||
value-format="YYYY-MM-DD"
|
||||
@change="handleFaultTimeChange" />
|
||||
</el-form-item>
|
||||
<el-form-item label="故障原因" prop="faultReason">
|
||||
<el-input v-model="queryParams.faultReason" placeholder="请输入故障原因" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button icon="search" type="primary" @click="handleQuery">{{ $t('btn.search') }}</el-button>
|
||||
<el-button icon="refresh" @click="resetQuery">{{ $t('btn.reset') }}</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<!-- 工具区域 -->
|
||||
<el-row :gutter="15" class="mb10">
|
||||
<el-col :span="1.5">
|
||||
<el-button type="danger" :disabled="multiple" v-hasPermi="['odfcablefaults:delete']" plain icon="delete" @click="handleDelete">
|
||||
{{ $t('btn.delete') }}
|
||||
</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button type="warning" v-hasPermi="['odfcablefaults:export']" plain icon="download" @click="handleExport">
|
||||
{{ $t('btn.export') }}
|
||||
</el-button>
|
||||
</el-col>
|
||||
|
||||
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList" :columns="columns"></right-toolbar>
|
||||
</el-row>
|
||||
|
||||
<el-table
|
||||
:data="dataList"
|
||||
v-loading="loading"
|
||||
ref="table"
|
||||
border
|
||||
header-cell-class-name="el-table-header-cell"
|
||||
highlight-current-row
|
||||
@sort-change="sortChange"
|
||||
@selection-change="handleSelectionChange">
|
||||
<el-table-column type="selection" width="50" align="center" />
|
||||
<el-table-column prop="id" label="Id" align="center" v-if="columns.showColumn('id')" />
|
||||
<el-table-column prop="faultTime" label="故障时间" align="center" :show-overflow-tooltip="true" v-if="columns.showColumn('faultTime')" />
|
||||
<el-table-column prop="personnel" label="人员" align="center" :show-overflow-tooltip="true" v-if="columns.showColumn('personnel')" />
|
||||
<el-table-column prop="faultReason" label="故障原因" align="center" :show-overflow-tooltip="true" v-if="columns.showColumn('faultReason')" />
|
||||
<el-table-column prop="mileage" label="表显故障里程" align="center" :show-overflow-tooltip="true" v-if="columns.showColumn('mileage')" />
|
||||
<el-table-column prop="cableName" label="所属光缆" align="center" :show-overflow-tooltip="true" v-if="columns.showColumn('cableName')" />
|
||||
<el-table-column prop="location" label="地点" align="center" :show-overflow-tooltip="true" v-if="columns.showColumn('location')" />
|
||||
<el-table-column prop="createdAt" label="创建时间" align="center" :show-overflow-tooltip="true" v-if="columns.showColumn('createdAt')" />
|
||||
<el-table-column label="操作" width="120">
|
||||
<template #default="scope">
|
||||
<el-button type="primary" size="small" icon="view" title="详情" @click="handlePreview(scope.row)"></el-button>
|
||||
<el-button
|
||||
type="danger"
|
||||
size="small"
|
||||
icon="delete"
|
||||
title="删除"
|
||||
v-hasPermi="['odfcablefaults:delete']"
|
||||
@click="handleDelete(scope.row)"></el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<pagination :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
|
||||
|
||||
<!-- 详情弹窗 -->
|
||||
<el-dialog v-model="detailVisible" title="故障详情" width="700px" destroy-on-close>
|
||||
<el-descriptions :column="2" border v-if="detailData">
|
||||
<el-descriptions-item label="Id">{{ detailData.id }}</el-descriptions-item>
|
||||
<el-descriptions-item label="故障时间">{{ detailData.faultTime }}</el-descriptions-item>
|
||||
<el-descriptions-item label="人员">{{ detailData.personnel }}</el-descriptions-item>
|
||||
<el-descriptions-item label="表显故障里程">{{ detailData.mileage }}</el-descriptions-item>
|
||||
<el-descriptions-item label="所属光缆">{{ detailData.cableName }}</el-descriptions-item>
|
||||
<el-descriptions-item label="地点">{{ detailData.location }}</el-descriptions-item>
|
||||
<el-descriptions-item label="纬度">{{ detailData.latitude }}</el-descriptions-item>
|
||||
<el-descriptions-item label="经度">{{ detailData.longitude }}</el-descriptions-item>
|
||||
<el-descriptions-item label="故障原因" :span="2">{{ detailData.faultReason }}</el-descriptions-item>
|
||||
<el-descriptions-item label="备注" :span="2">{{ detailData.remark }}</el-descriptions-item>
|
||||
<el-descriptions-item label="创建时间">{{ detailData.createdAt }}</el-descriptions-item>
|
||||
<el-descriptions-item label="更新时间">{{ detailData.updatedAt }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
<div v-if="detailData && detailData.images && detailData.images.length > 0" style="margin-top: 16px;">
|
||||
<div style="font-weight: bold; margin-bottom: 8px;">故障图片</div>
|
||||
<div style="display: flex; flex-wrap: wrap; gap: 8px;">
|
||||
<el-image
|
||||
v-for="(img, index) in detailData.images"
|
||||
:key="index"
|
||||
:src="img.imageUrl"
|
||||
:preview-src-list="detailData.images.map(i => i.imageUrl)"
|
||||
:initial-index="index"
|
||||
fit="cover"
|
||||
style="width: 120px; height: 120px; border-radius: 4px;" />
|
||||
</div>
|
||||
</div>
|
||||
<template #footer>
|
||||
<el-button @click="detailVisible = false">关闭</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup name="odfcablefaults">
|
||||
import { listOdfCableFaults, getOdfCableFaults, delOdfCableFaults } from '@/api/business/odfcablefaults.js'
|
||||
import { listOdfCables } from '@/api/business/odfcables.js'
|
||||
|
||||
const { proxy } = getCurrentInstance()
|
||||
const ids = ref([])
|
||||
const loading = ref(false)
|
||||
const showSearch = ref(true)
|
||||
const detailVisible = ref(false)
|
||||
const detailData = ref(null)
|
||||
const detailLoading = ref(false)
|
||||
const cableOptions = ref([])
|
||||
const cableSearchLoading = ref(false)
|
||||
const faultTimeRange = ref(null)
|
||||
|
||||
const queryParams = reactive({
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
sort: undefined,
|
||||
sortType: undefined,
|
||||
cableId: undefined,
|
||||
beginFaultTime: undefined,
|
||||
endFaultTime: undefined,
|
||||
faultReason: undefined
|
||||
})
|
||||
|
||||
const columns = ref([
|
||||
{ visible: true, align: 'center', type: '', prop: 'id', label: 'Id' },
|
||||
{ visible: true, align: 'center', type: '', prop: 'faultTime', label: '故障时间', showOverflowTooltip: true },
|
||||
{ visible: true, align: 'center', type: '', prop: 'personnel', label: '人员', showOverflowTooltip: true },
|
||||
{ visible: true, align: 'center', type: '', prop: 'faultReason', label: '故障原因', showOverflowTooltip: true },
|
||||
{ visible: true, align: 'center', type: '', prop: 'mileage', label: '表显故障里程', showOverflowTooltip: true },
|
||||
{ visible: true, align: 'center', type: '', prop: 'cableName', label: '所属光缆', showOverflowTooltip: true },
|
||||
{ visible: true, align: 'center', type: '', prop: 'location', label: '地点', showOverflowTooltip: true },
|
||||
{ visible: false, align: 'center', type: '', prop: 'latitude', label: '纬度' },
|
||||
{ visible: false, align: 'center', type: '', prop: 'longitude', label: '经度' },
|
||||
{ visible: true, align: 'center', type: '', prop: 'createdAt', label: '创建时间', showOverflowTooltip: true }
|
||||
])
|
||||
|
||||
const total = ref(0)
|
||||
const dataList = ref([])
|
||||
const queryRef = ref()
|
||||
|
||||
const state = reactive({
|
||||
single: true,
|
||||
multiple: true
|
||||
})
|
||||
|
||||
const { single, multiple } = toRefs(state)
|
||||
|
||||
// 远程搜索光缆
|
||||
function remoteCableSearch(query) {
|
||||
if (query) {
|
||||
cableSearchLoading.value = true
|
||||
listOdfCables({ cableName: query, pageNum: 1, pageSize: 50 }).then((res) => {
|
||||
if (res.code == 200) {
|
||||
cableOptions.value = res.data.result || []
|
||||
}
|
||||
cableSearchLoading.value = false
|
||||
}).catch(() => {
|
||||
cableSearchLoading.value = false
|
||||
})
|
||||
} else {
|
||||
cableOptions.value = []
|
||||
}
|
||||
}
|
||||
|
||||
// 故障时间范围变化
|
||||
function handleFaultTimeChange(val) {
|
||||
if (val) {
|
||||
queryParams.beginFaultTime = val[0]
|
||||
queryParams.endFaultTime = val[1]
|
||||
} else {
|
||||
queryParams.beginFaultTime = undefined
|
||||
queryParams.endFaultTime = undefined
|
||||
}
|
||||
}
|
||||
|
||||
function getList() {
|
||||
loading.value = true
|
||||
listOdfCableFaults(queryParams).then((res) => {
|
||||
const { code, data } = res
|
||||
if (code == 200) {
|
||||
dataList.value = data.result
|
||||
total.value = data.totalNum
|
||||
loading.value = false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 查询
|
||||
function handleQuery() {
|
||||
queryParams.pageNum = 1
|
||||
getList()
|
||||
}
|
||||
|
||||
// 重置查询操作
|
||||
function resetQuery() {
|
||||
faultTimeRange.value = null
|
||||
queryParams.beginFaultTime = undefined
|
||||
queryParams.endFaultTime = undefined
|
||||
proxy.resetForm('queryRef')
|
||||
handleQuery()
|
||||
}
|
||||
|
||||
// 多选框选中数据
|
||||
function handleSelectionChange(selection) {
|
||||
ids.value = selection.map((item) => item.id)
|
||||
state.single = selection.length != 1
|
||||
state.multiple = !selection.length
|
||||
}
|
||||
|
||||
// 自定义排序
|
||||
function sortChange(column) {
|
||||
var sort = undefined
|
||||
var sortType = undefined
|
||||
|
||||
if (column.prop != null && column.order != null) {
|
||||
sort = column.prop
|
||||
sortType = column.order
|
||||
}
|
||||
queryParams.sort = sort
|
||||
queryParams.sortType = sortType
|
||||
handleQuery()
|
||||
}
|
||||
|
||||
/**
|
||||
* 查看详情
|
||||
* @param {*} row
|
||||
*/
|
||||
function handlePreview(row) {
|
||||
detailLoading.value = true
|
||||
detailVisible.value = true
|
||||
getOdfCableFaults(row.id).then((res) => {
|
||||
if (res.code == 200) {
|
||||
detailData.value = res.data
|
||||
}
|
||||
detailLoading.value = false
|
||||
}).catch(() => {
|
||||
detailLoading.value = false
|
||||
})
|
||||
}
|
||||
|
||||
// 删除按钮操作
|
||||
function handleDelete(row) {
|
||||
const Ids = row.id || ids.value
|
||||
|
||||
proxy
|
||||
.$confirm('是否确认删除参数编号为"' + Ids + '"的数据项?', '警告', {
|
||||
confirmButtonText: proxy.$t('common.ok'),
|
||||
cancelButtonText: proxy.$t('common.cancel'),
|
||||
type: 'warning'
|
||||
})
|
||||
.then(function () {
|
||||
return delOdfCableFaults(Ids)
|
||||
})
|
||||
.then(() => {
|
||||
getList()
|
||||
proxy.$modal.msgSuccess('删除成功')
|
||||
})
|
||||
}
|
||||
|
||||
// 导出按钮操作
|
||||
function handleExport() {
|
||||
proxy
|
||||
.$confirm('是否确认导出干线故障列表数据项?', '警告', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
})
|
||||
.then(async () => {
|
||||
await proxy.downFile('/business/OdfCableFaults/export', { ...queryParams })
|
||||
})
|
||||
}
|
||||
|
||||
handleQuery()
|
||||
</script>
|
||||
254
server/ZR.Vue/src/views/business/OdfCables.vue
Normal file
254
server/ZR.Vue/src/views/business/OdfCables.vue
Normal file
|
|
@ -0,0 +1,254 @@
|
|||
<!--
|
||||
* @Descripttion: (光缆管理/odf_cables)
|
||||
* @Author: (admin)
|
||||
* @Date: (2025-08-05)
|
||||
-->
|
||||
<template>
|
||||
<div>
|
||||
<el-form :model="queryParams" label-position="right" inline ref="queryRef" v-show="showSearch" @submit.prevent>
|
||||
<el-form-item label="光缆名称" prop="cableName">
|
||||
<el-input v-model="queryParams.cableName" placeholder="请输入光缆名称" />
|
||||
</el-form-item>
|
||||
<el-form-item label="所属部门" prop="deptId">
|
||||
<el-tree-select
|
||||
v-model="queryParams.deptId"
|
||||
:data="deptOptions"
|
||||
:props="{ value: 'id', label: 'label', children: 'children' }"
|
||||
value-key="id"
|
||||
placeholder="请选择所属部门"
|
||||
check-strictly
|
||||
clearable
|
||||
:render-after-expand="false" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button icon="search" type="primary" @click="handleQuery">{{ $t('btn.search') }}</el-button>
|
||||
<el-button icon="refresh" @click="resetQuery">{{ $t('btn.reset') }}</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<!-- 工具区域 -->
|
||||
<el-row :gutter="15" class="mb10">
|
||||
<el-col :span="1.5">
|
||||
<el-button type="primary" v-hasPermi="['odfcables:add']" plain icon="plus" @click="handleAdd">
|
||||
{{ $t('btn.add') }}
|
||||
</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button type="success" :disabled="single" v-hasPermi="['odfcables:edit']" plain icon="edit" @click="handleUpdate">
|
||||
{{ $t('btn.edit') }}
|
||||
</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button type="danger" :disabled="multiple" v-hasPermi="['odfcables:delete']" plain icon="delete" @click="handleDelete">
|
||||
{{ $t('btn.delete') }}
|
||||
</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button type="warning" v-hasPermi="['odfcables:export']" plain icon="download" @click="handleExport">
|
||||
{{ $t('btn.export') }}
|
||||
</el-button>
|
||||
</el-col>
|
||||
|
||||
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList" :columns="columns"></right-toolbar>
|
||||
</el-row>
|
||||
|
||||
<el-table
|
||||
:data="dataList"
|
||||
v-loading="loading"
|
||||
ref="table"
|
||||
border
|
||||
header-cell-class-name="el-table-header-cell"
|
||||
highlight-current-row
|
||||
@sort-change="sortChange"
|
||||
@selection-change="handleSelectionChange">
|
||||
<el-table-column type="selection" width="50" align="center" />
|
||||
<el-table-column prop="id" label="Id" align="center" v-if="columns.showColumn('id')" />
|
||||
<el-table-column prop="cableName" label="光缆名称" align="center" :show-overflow-tooltip="true" v-if="columns.showColumn('cableName')" />
|
||||
<el-table-column prop="deptName" label="所属部门" align="center" :show-overflow-tooltip="true" v-if="columns.showColumn('deptName')" />
|
||||
<el-table-column prop="createdAt" label="创建时间" :show-overflow-tooltip="true" v-if="columns.showColumn('createdAt')" />
|
||||
<el-table-column prop="updatedAt" label="修改时间" :show-overflow-tooltip="true" v-if="columns.showColumn('updatedAt')" />
|
||||
<el-table-column label="操作" width="160">
|
||||
<template #default="scope">
|
||||
<el-button type="primary" size="small" icon="view" title="详情" @click="handlePreview(scope.row)"></el-button>
|
||||
<el-button
|
||||
type="success"
|
||||
size="small"
|
||||
icon="edit"
|
||||
title="编辑"
|
||||
v-hasPermi="['odfcables:edit']"
|
||||
@click="handleUpdate(scope.row)"></el-button>
|
||||
<el-button
|
||||
type="danger"
|
||||
size="small"
|
||||
icon="delete"
|
||||
title="删除"
|
||||
v-hasPermi="['odfcables:delete']"
|
||||
@click="handleDelete(scope.row)"></el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<pagination :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
|
||||
|
||||
<!-- 光缆表单组件 -->
|
||||
<OdfCableForm v-model:visible="formVisible" :id="currentId" :mode="formMode" @success="handleFormSuccess" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup name="odfcables">
|
||||
import { listOdfCables, delOdfCables } from '@/api/business/odfcables.js'
|
||||
import OdfCableForm from '@/components/business/OdfCableForm.vue'
|
||||
import { treeselect } from '@/api/system/dept'
|
||||
|
||||
const { proxy } = getCurrentInstance()
|
||||
const ids = ref([])
|
||||
const loading = ref(false)
|
||||
const showSearch = ref(true)
|
||||
const formVisible = ref(false)
|
||||
const currentId = ref(null)
|
||||
const formMode = ref('edit') // 'add', 'edit', 'view'
|
||||
const deptOptions = ref([])
|
||||
|
||||
const queryParams = reactive({
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
sort: undefined,
|
||||
sortType: undefined,
|
||||
cableName: undefined,
|
||||
deptId: undefined
|
||||
})
|
||||
|
||||
const columns = ref([
|
||||
{ visible: true, align: 'center', type: '', prop: 'id', label: 'Id' },
|
||||
{ visible: true, align: 'center', type: '', prop: 'cableName', label: '光缆名称', showOverflowTooltip: true },
|
||||
{ visible: true, align: 'center', type: '', prop: 'deptName', label: '所属部门', showOverflowTooltip: true },
|
||||
{ visible: true, align: 'center', type: '', prop: 'createdAt', label: '创建时间', showOverflowTooltip: true },
|
||||
{ visible: true, align: 'center', type: '', prop: 'updatedAt', label: '修改时间', showOverflowTooltip: true }
|
||||
])
|
||||
|
||||
const total = ref(0)
|
||||
const dataList = ref([])
|
||||
const queryRef = ref()
|
||||
|
||||
const state = reactive({
|
||||
single: true,
|
||||
multiple: true
|
||||
})
|
||||
|
||||
const { single, multiple } = toRefs(state)
|
||||
|
||||
// 获取部门树数据
|
||||
function getDeptTreeData() {
|
||||
treeselect().then((response) => {
|
||||
deptOptions.value = response.data
|
||||
})
|
||||
}
|
||||
|
||||
function getList() {
|
||||
loading.value = true
|
||||
listOdfCables(queryParams).then((res) => {
|
||||
const { code, data } = res
|
||||
if (code == 200) {
|
||||
dataList.value = data.result
|
||||
total.value = data.totalNum
|
||||
loading.value = false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 查询
|
||||
function handleQuery() {
|
||||
queryParams.pageNum = 1
|
||||
getList()
|
||||
}
|
||||
|
||||
// 重置查询操作
|
||||
function resetQuery() {
|
||||
proxy.resetForm('queryRef')
|
||||
handleQuery()
|
||||
}
|
||||
|
||||
// 多选框选中数据
|
||||
function handleSelectionChange(selection) {
|
||||
ids.value = selection.map((item) => item.id)
|
||||
state.single = selection.length != 1
|
||||
state.multiple = !selection.length
|
||||
}
|
||||
|
||||
// 自定义排序
|
||||
function sortChange(column) {
|
||||
var sort = undefined
|
||||
var sortType = undefined
|
||||
|
||||
if (column.prop != null && column.order != null) {
|
||||
sort = column.prop
|
||||
sortType = column.order
|
||||
}
|
||||
queryParams.sort = sort
|
||||
queryParams.sortType = sortType
|
||||
handleQuery()
|
||||
}
|
||||
|
||||
/**
|
||||
* 查看
|
||||
* @param {*} row
|
||||
*/
|
||||
function handlePreview(row) {
|
||||
currentId.value = row.id
|
||||
formMode.value = 'view'
|
||||
formVisible.value = true
|
||||
}
|
||||
|
||||
// 添加按钮操作
|
||||
function handleAdd() {
|
||||
currentId.value = null
|
||||
formMode.value = 'add'
|
||||
formVisible.value = true
|
||||
}
|
||||
|
||||
// 修改按钮操作
|
||||
function handleUpdate(row) {
|
||||
const id = row?.id || ids.value[0]
|
||||
currentId.value = id
|
||||
formMode.value = 'edit'
|
||||
formVisible.value = true
|
||||
}
|
||||
|
||||
// 表单成功处理
|
||||
function handleFormSuccess() {
|
||||
getList()
|
||||
}
|
||||
|
||||
// 删除按钮操作
|
||||
function handleDelete(row) {
|
||||
const Ids = row.id || ids.value
|
||||
|
||||
proxy
|
||||
.$confirm('是否确认删除参数编号为"' + Ids + '"的数据项?', '警告', {
|
||||
confirmButtonText: proxy.$t('common.ok'),
|
||||
cancelButtonText: proxy.$t('common.cancel'),
|
||||
type: 'warning'
|
||||
})
|
||||
.then(function () {
|
||||
return delOdfCables(Ids)
|
||||
})
|
||||
.then(() => {
|
||||
getList()
|
||||
proxy.$modal.msgSuccess('删除成功')
|
||||
})
|
||||
}
|
||||
|
||||
// 导出按钮操作
|
||||
function handleExport() {
|
||||
proxy
|
||||
.$confirm('是否确认导出光缆列表数据项?', '警告', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
})
|
||||
.then(async () => {
|
||||
await proxy.downFile('/business/OdfCables/export', { ...queryParams })
|
||||
})
|
||||
}
|
||||
|
||||
getDeptTreeData()
|
||||
handleQuery()
|
||||
</script>
|
||||
181
server/ZR.Vue/src/views/business/OdfCheckin.vue
Normal file
181
server/ZR.Vue/src/views/business/OdfCheckin.vue
Normal file
|
|
@ -0,0 +1,181 @@
|
|||
<!--
|
||||
* @Descripttion: (签到记录管理/odf_checkin)
|
||||
* @Author: (admin)
|
||||
* @Date: (2025-08-05)
|
||||
-->
|
||||
<template>
|
||||
<div>
|
||||
<el-form :model="queryParams" label-position="right" inline ref="queryRef" v-show="showSearch" @submit.prevent>
|
||||
<el-form-item label="机房" prop="roomId">
|
||||
<el-select clearable v-model="queryParams.roomId" placeholder="请选择机房">
|
||||
<el-option v-for="item in options.sql_odf_room" :key="item.dictValue" :label="item.dictLabel" :value="item.dictValue">
|
||||
<span class="fl">{{ item.dictLabel }}</span>
|
||||
<span class="fr" style="color: var(--el-text-color-secondary)">{{ item.dictValue }}</span>
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="人员" prop="personnel">
|
||||
<el-input v-model="queryParams.personnel" placeholder="请输入人员" />
|
||||
</el-form-item>
|
||||
<el-form-item label="签到时间" prop="checkinTimeRange">
|
||||
<el-date-picker
|
||||
v-model="checkinTimeRange"
|
||||
type="daterange"
|
||||
range-separator="-"
|
||||
start-placeholder="开始日期"
|
||||
end-placeholder="结束日期"
|
||||
value-format="YYYY-MM-DD"
|
||||
@change="handleCheckinTimeChange" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button icon="search" type="primary" @click="handleQuery">{{ $t('btn.search') }}</el-button>
|
||||
<el-button icon="refresh" @click="resetQuery">{{ $t('btn.reset') }}</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<!-- 工具区域 -->
|
||||
<el-row :gutter="15" class="mb10">
|
||||
<el-col :span="1.5">
|
||||
<el-button type="warning" v-hasPermi="['odfcheckin:export']" plain icon="download" @click="handleExport">
|
||||
{{ $t('btn.export') }}
|
||||
</el-button>
|
||||
</el-col>
|
||||
|
||||
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList" :columns="columns"></right-toolbar>
|
||||
</el-row>
|
||||
|
||||
<el-table
|
||||
:data="dataList"
|
||||
v-loading="loading"
|
||||
ref="table"
|
||||
border
|
||||
header-cell-class-name="el-table-header-cell"
|
||||
highlight-current-row
|
||||
@sort-change="sortChange">
|
||||
<el-table-column prop="id" label="Id" align="center" v-if="columns.showColumn('id')" />
|
||||
<el-table-column prop="roomName" label="机房名称" align="center" :show-overflow-tooltip="true" v-if="columns.showColumn('roomName')" />
|
||||
<el-table-column prop="personnel" label="人员" align="center" :show-overflow-tooltip="true" v-if="columns.showColumn('personnel')" />
|
||||
<el-table-column prop="checkinTime" label="签到时间" align="center" :show-overflow-tooltip="true" v-if="columns.showColumn('checkinTime')" />
|
||||
<el-table-column prop="workContent" label="工作内容" align="center" :show-overflow-tooltip="true" v-if="columns.showColumn('workContent')" />
|
||||
<el-table-column prop="userName" label="提交人" align="center" :show-overflow-tooltip="true" v-if="columns.showColumn('userName')" />
|
||||
<el-table-column prop="createdAt" label="创建时间" align="center" :show-overflow-tooltip="true" v-if="columns.showColumn('createdAt')" />
|
||||
</el-table>
|
||||
<pagination :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup name="odfcheckin">
|
||||
import { listOdfCheckin, exportOdfCheckin } from '@/api/business/odfcheckin.js'
|
||||
|
||||
const { proxy } = getCurrentInstance()
|
||||
const loading = ref(false)
|
||||
const showSearch = ref(true)
|
||||
const checkinTimeRange = ref(null)
|
||||
|
||||
const queryParams = reactive({
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
sort: undefined,
|
||||
sortType: undefined,
|
||||
roomId: undefined,
|
||||
personnel: undefined,
|
||||
beginCheckinTime: undefined,
|
||||
endCheckinTime: undefined
|
||||
})
|
||||
|
||||
const columns = ref([
|
||||
{ visible: true, align: 'center', type: '', prop: 'id', label: 'Id' },
|
||||
{ visible: true, align: 'center', type: '', prop: 'roomName', label: '机房名称', showOverflowTooltip: true },
|
||||
{ visible: true, align: 'center', type: '', prop: 'personnel', label: '人员', showOverflowTooltip: true },
|
||||
{ visible: true, align: 'center', type: '', prop: 'checkinTime', label: '签到时间', showOverflowTooltip: true },
|
||||
{ visible: true, align: 'center', type: '', prop: 'workContent', label: '工作内容', showOverflowTooltip: true },
|
||||
{ visible: true, align: 'center', type: '', prop: 'userName', label: '提交人', showOverflowTooltip: true },
|
||||
{ visible: true, align: 'center', type: '', prop: 'createdAt', label: '创建时间', showOverflowTooltip: true }
|
||||
])
|
||||
|
||||
const total = ref(0)
|
||||
const dataList = ref([])
|
||||
const queryRef = ref()
|
||||
|
||||
const state = reactive({
|
||||
options: {
|
||||
sql_odf_room: []
|
||||
}
|
||||
})
|
||||
|
||||
const { options } = toRefs(state)
|
||||
|
||||
var dictParams = ['sql_odf_room']
|
||||
|
||||
proxy.getDicts(dictParams).then((response) => {
|
||||
response.data.forEach((element) => {
|
||||
state.options[element.dictType] = element.list
|
||||
})
|
||||
})
|
||||
|
||||
// 签到时间范围变化
|
||||
function handleCheckinTimeChange(val) {
|
||||
if (val) {
|
||||
queryParams.beginCheckinTime = val[0]
|
||||
queryParams.endCheckinTime = val[1]
|
||||
} else {
|
||||
queryParams.beginCheckinTime = undefined
|
||||
queryParams.endCheckinTime = undefined
|
||||
}
|
||||
}
|
||||
|
||||
function getList() {
|
||||
loading.value = true
|
||||
listOdfCheckin(queryParams).then((res) => {
|
||||
const { code, data } = res
|
||||
if (code == 200) {
|
||||
dataList.value = data.result
|
||||
total.value = data.totalNum
|
||||
loading.value = false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 查询
|
||||
function handleQuery() {
|
||||
queryParams.pageNum = 1
|
||||
getList()
|
||||
}
|
||||
|
||||
// 重置查询操作
|
||||
function resetQuery() {
|
||||
checkinTimeRange.value = null
|
||||
queryParams.beginCheckinTime = undefined
|
||||
queryParams.endCheckinTime = undefined
|
||||
proxy.resetForm('queryRef')
|
||||
handleQuery()
|
||||
}
|
||||
|
||||
// 自定义排序
|
||||
function sortChange(column) {
|
||||
var sort = undefined
|
||||
var sortType = undefined
|
||||
|
||||
if (column.prop != null && column.order != null) {
|
||||
sort = column.prop
|
||||
sortType = column.order
|
||||
}
|
||||
queryParams.sort = sort
|
||||
queryParams.sortType = sortType
|
||||
handleQuery()
|
||||
}
|
||||
|
||||
// 导出按钮操作
|
||||
function handleExport() {
|
||||
proxy
|
||||
.$confirm('是否确认导出签到记录列表数据项?', '警告', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
})
|
||||
.then(async () => {
|
||||
await proxy.downFile('/business/OdfCheckin/export', { ...queryParams })
|
||||
})
|
||||
}
|
||||
|
||||
handleQuery()
|
||||
</script>
|
||||
130
server/ZR.Vue/src/views/business/OdfUserModules.vue
Normal file
130
server/ZR.Vue/src/views/business/OdfUserModules.vue
Normal file
|
|
@ -0,0 +1,130 @@
|
|||
<!--
|
||||
* @Descripttion: (用户模块权限管理/odf_user_modules)
|
||||
* @Author: (admin)
|
||||
* @Date: (2025-08-05)
|
||||
-->
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<el-row :gutter="20">
|
||||
<!-- 左侧:用户列表 -->
|
||||
<el-col :span="8" style="min-width: 360px; max-width: 360px;">
|
||||
<el-card shadow="never">
|
||||
<template #header>
|
||||
<el-input v-model="searchKeyword" placeholder="搜索用户" clearable prefix-icon="search" />
|
||||
</template>
|
||||
<el-table
|
||||
:data="filteredUserList"
|
||||
v-loading="userLoading"
|
||||
border
|
||||
highlight-current-row
|
||||
@current-change="handleUserChange"
|
||||
style="width: 100%"
|
||||
max-height="600">
|
||||
<el-table-column prop="userId" label="用户ID" align="center" width="80" />
|
||||
<el-table-column prop="userName" label="用户名" align="center" />
|
||||
</el-table>
|
||||
</el-card>
|
||||
</el-col>
|
||||
|
||||
<!-- 右侧:模块权限配置 -->
|
||||
<el-col :span="16" style="flex: 1;">
|
||||
<el-card shadow="never">
|
||||
<template #header>
|
||||
<span>{{ currentUser ? currentUser.userName + ' - 模块权限配置' : '请选择用户' }}</span>
|
||||
</template>
|
||||
<div v-if="currentUser">
|
||||
<el-checkbox-group v-model="selectedModules" :disabled="moduleLoading">
|
||||
<el-checkbox label="odf">机房版块</el-checkbox>
|
||||
<el-checkbox label="trunk">干线版块</el-checkbox>
|
||||
<el-checkbox label="route">路线规划</el-checkbox>
|
||||
</el-checkbox-group>
|
||||
<div style="margin-top: 20px;">
|
||||
<el-button
|
||||
type="primary"
|
||||
:loading="saveLoading"
|
||||
v-hasPermi="['odfusermodules:edit']"
|
||||
@click="handleSave">
|
||||
保存
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<el-empty v-else description="请在左侧选择用户" />
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup name="odfusermodules">
|
||||
import { listUsers, getUserModules, saveUserModules } from '@/api/business/odfusermodules.js'
|
||||
|
||||
const { proxy } = getCurrentInstance()
|
||||
|
||||
const userLoading = ref(false)
|
||||
const moduleLoading = ref(false)
|
||||
const saveLoading = ref(false)
|
||||
const searchKeyword = ref('')
|
||||
const userList = ref([])
|
||||
const currentUser = ref(null)
|
||||
const selectedModules = ref([])
|
||||
|
||||
// 前端搜索过滤用户列表
|
||||
const filteredUserList = computed(() => {
|
||||
if (!searchKeyword.value) return userList.value
|
||||
const keyword = searchKeyword.value.toLowerCase()
|
||||
return userList.value.filter((u) => u.userName && u.userName.toLowerCase().includes(keyword))
|
||||
})
|
||||
|
||||
// 获取用户列表
|
||||
function getUserList() {
|
||||
userLoading.value = true
|
||||
listUsers()
|
||||
.then((res) => {
|
||||
if (res.code == 200) {
|
||||
userList.value = res.data || []
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
userLoading.value = false
|
||||
})
|
||||
}
|
||||
|
||||
// 点击用户行
|
||||
function handleUserChange(row) {
|
||||
if (!row) return
|
||||
currentUser.value = row
|
||||
loadUserModules(row.userId)
|
||||
}
|
||||
|
||||
// 加载用户模块权限
|
||||
function loadUserModules(userId) {
|
||||
moduleLoading.value = true
|
||||
selectedModules.value = []
|
||||
getUserModules(userId)
|
||||
.then((res) => {
|
||||
if (res.code == 200) {
|
||||
selectedModules.value = res.data || []
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
moduleLoading.value = false
|
||||
})
|
||||
}
|
||||
|
||||
// 保存
|
||||
function handleSave() {
|
||||
if (!currentUser.value) return
|
||||
saveLoading.value = true
|
||||
saveUserModules({ userId: currentUser.value.userId, modules: selectedModules.value })
|
||||
.then((res) => {
|
||||
if (res.code == 200) {
|
||||
proxy.$modal.msgSuccess('保存成功')
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
saveLoading.value = false
|
||||
})
|
||||
}
|
||||
|
||||
getUserList()
|
||||
</script>
|
||||
20
sql/v1.0.2/01_create_odf_checkin.sql
Normal file
20
sql/v1.0.2/01_create_odf_checkin.sql
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
-- =============================================
|
||||
-- ODF v1.0.2 - 创建签到记录表 odf_checkin
|
||||
-- 需求: 2.6 (ODF 机架列表页签到功能)
|
||||
-- =============================================
|
||||
|
||||
CREATE TABLE odf_checkin (
|
||||
Id INT IDENTITY(1,1) PRIMARY KEY,
|
||||
RoomId INT NOT NULL, -- 关联机房 odf_rooms.Id
|
||||
Personnel NVARCHAR(200) NOT NULL, -- 签到人员
|
||||
CheckinTime DATETIME NOT NULL, -- 签到时间(用户选择)
|
||||
WorkContent NVARCHAR(MAX) NOT NULL, -- 工作内容
|
||||
UserId BIGINT NULL, -- 提交人 sys_user.UserId
|
||||
CreatedAt DATETIME DEFAULT GETDATE() -- 记录创建时间
|
||||
);
|
||||
|
||||
-- 索引:按机房查询签到记录
|
||||
CREATE INDEX IX_odf_checkin_RoomId ON odf_checkin(RoomId);
|
||||
|
||||
-- 索引:按时间倒序查询
|
||||
CREATE INDEX IX_odf_checkin_CheckinTime ON odf_checkin(CheckinTime DESC);
|
||||
19
sql/v1.0.2/02_create_odf_cables.sql
Normal file
19
sql/v1.0.2/02_create_odf_cables.sql
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
-- =============================================
|
||||
-- ODF v1.0.2 - 创建光缆表 odf_cables
|
||||
-- 需求: 4.1 (干线版块 — 光缆列表页)
|
||||
-- =============================================
|
||||
|
||||
CREATE TABLE odf_cables (
|
||||
Id INT IDENTITY(1,1) PRIMARY KEY,
|
||||
CableName NVARCHAR(200) NOT NULL, -- 光缆名称
|
||||
DeptId BIGINT NOT NULL, -- 所属公司/部门 sys_dept.DeptId
|
||||
DeptName NVARCHAR(100) NULL, -- 冗余:部门名称
|
||||
CreatedAt DATETIME DEFAULT GETDATE(),
|
||||
UpdatedAt DATETIME DEFAULT GETDATE()
|
||||
);
|
||||
|
||||
-- 索引:按公司查询光缆列表
|
||||
CREATE INDEX IX_odf_cables_DeptId ON odf_cables(DeptId);
|
||||
|
||||
-- 索引:按名称模糊搜索
|
||||
CREATE INDEX IX_odf_cables_CableName ON odf_cables(CableName);
|
||||
26
sql/v1.0.2/03_create_odf_cable_faults.sql
Normal file
26
sql/v1.0.2/03_create_odf_cable_faults.sql
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
-- =============================================
|
||||
-- ODF v1.0.2 - 创建干线故障表 odf_cable_faults
|
||||
-- 需求: 5.1, 7.9
|
||||
-- =============================================
|
||||
|
||||
CREATE TABLE odf_cable_faults (
|
||||
Id INT IDENTITY(1,1) PRIMARY KEY,
|
||||
CableId INT NOT NULL, -- 关联光缆 odf_cables.Id
|
||||
FaultTime DATETIME NOT NULL, -- 故障时间
|
||||
Personnel NVARCHAR(200) NULL, -- 人员
|
||||
FaultReason NVARCHAR(MAX) NULL, -- 故障原因
|
||||
Mileage NVARCHAR(200) NULL, -- 表显故障里程
|
||||
Location NVARCHAR(500) NULL, -- 地点描述
|
||||
Latitude DECIMAL(10,7) DEFAULT 0, -- 纬度
|
||||
Longitude DECIMAL(10,7) DEFAULT 0, -- 经度
|
||||
Remark NVARCHAR(MAX) NULL, -- 备注
|
||||
UserId BIGINT NULL, -- 提交人 sys_user.UserId
|
||||
CreatedAt DATETIME DEFAULT GETDATE(),
|
||||
UpdatedAt DATETIME DEFAULT GETDATE()
|
||||
);
|
||||
|
||||
-- 索引:按光缆查询故障列表
|
||||
CREATE INDEX IX_odf_cable_faults_CableId ON odf_cable_faults(CableId);
|
||||
|
||||
-- 索引:按故障时间倒序
|
||||
CREATE INDEX IX_odf_cable_faults_FaultTime ON odf_cable_faults(FaultTime DESC);
|
||||
14
sql/v1.0.2/04_create_odf_cable_fault_images.sql
Normal file
14
sql/v1.0.2/04_create_odf_cable_fault_images.sql
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
-- =============================================
|
||||
-- ODF v1.0.2 - 创建故障图片表 odf_cable_fault_images
|
||||
-- 需求: 7.9
|
||||
-- =============================================
|
||||
|
||||
CREATE TABLE odf_cable_fault_images (
|
||||
Id INT IDENTITY(1,1) PRIMARY KEY,
|
||||
FaultId INT NOT NULL, -- 关联故障 odf_cable_faults.Id
|
||||
ImageUrl NVARCHAR(500) NOT NULL, -- 图片访问 URL
|
||||
CreatedAt DATETIME DEFAULT GETDATE()
|
||||
);
|
||||
|
||||
-- 索引:按故障 ID 查询图片
|
||||
CREATE INDEX IX_odf_cable_fault_images_FaultId ON odf_cable_fault_images(FaultId);
|
||||
17
sql/v1.0.2/05_create_odf_user_modules.sql
Normal file
17
sql/v1.0.2/05_create_odf_user_modules.sql
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
-- =============================================
|
||||
-- ODF v1.0.2 - 创建用户功能版块权限表 odf_user_modules
|
||||
-- 需求: 8.1
|
||||
-- =============================================
|
||||
|
||||
CREATE TABLE odf_user_modules (
|
||||
Id INT IDENTITY(1,1) PRIMARY KEY,
|
||||
UserId BIGINT NOT NULL, -- 用户 sys_user.UserId
|
||||
ModuleCode NVARCHAR(50) NOT NULL, -- 模块标识:'odf', 'trunk', 'route'
|
||||
CreatedAt DATETIME DEFAULT GETDATE()
|
||||
);
|
||||
|
||||
-- 唯一索引:同一用户同一模块不重复
|
||||
CREATE UNIQUE INDEX UX_odf_user_modules_User_Module ON odf_user_modules(UserId, ModuleCode);
|
||||
|
||||
-- 索引:按用户查询
|
||||
CREATE INDEX IX_odf_user_modules_UserId ON odf_user_modules(UserId);
|
||||
116
sql/v1.0.2/06_insert_menus_and_permissions.sql
Normal file
116
sql/v1.0.2/06_insert_menus_and_permissions.sql
Normal file
|
|
@ -0,0 +1,116 @@
|
|||
-- =============================================
|
||||
-- ODF v1.0.2 菜单权限初始化脚本
|
||||
-- 新增 4 个一级菜单及按钮权限
|
||||
-- =============================================
|
||||
|
||||
-- 1. 光缆管理(一级菜单)
|
||||
INSERT INTO sys_menu (MenuId, MenuName, ParentId, OrderNum, Path, Component, IsCache, IsFrame, MenuType, Visible, Status, Perms, Icon, Create_by, Create_time)
|
||||
VALUES (11190, N'光缆管理', 0, 5, 'OdfCables', 'business/OdfCables', 0, 0, 'C', '0', '0', 'odfcables:list', 'icon1', 'admin', GETDATE());
|
||||
|
||||
-- 光缆管理 - 按钮权限
|
||||
INSERT INTO sys_menu (MenuId, MenuName, ParentId, OrderNum, Path, Component, MenuType, Perms, Create_by, Create_time)
|
||||
VALUES (11191, N'查询', 11190, 1, '#', NULL, 'F', 'odfcables:query', 'admin', GETDATE());
|
||||
INSERT INTO sys_menu (MenuId, MenuName, ParentId, OrderNum, Path, Component, MenuType, Perms, Create_by, Create_time)
|
||||
VALUES (11192, N'新增', 11190, 2, '#', NULL, 'F', 'odfcables:add', 'admin', GETDATE());
|
||||
INSERT INTO sys_menu (MenuId, MenuName, ParentId, OrderNum, Path, Component, MenuType, Perms, Create_by, Create_time)
|
||||
VALUES (11193, N'删除', 11190, 3, '#', NULL, 'F', 'odfcables:delete', 'admin', GETDATE());
|
||||
INSERT INTO sys_menu (MenuId, MenuName, ParentId, OrderNum, Path, Component, MenuType, Perms, Create_by, Create_time)
|
||||
VALUES (11194, N'修改', 11190, 4, '#', NULL, 'F', 'odfcables:edit', 'admin', GETDATE());
|
||||
INSERT INTO sys_menu (MenuId, MenuName, ParentId, OrderNum, Path, Component, MenuType, Perms, Create_by, Create_time)
|
||||
VALUES (11195, N'导出', 11190, 5, '#', NULL, 'F', 'odfcables:export', 'admin', GETDATE());
|
||||
|
||||
-- 2. 干线故障管理(一级菜单)
|
||||
INSERT INTO sys_menu (MenuId, MenuName, ParentId, OrderNum, Path, Component, IsCache, IsFrame, MenuType, Visible, Status, Perms, Icon, Create_by, Create_time)
|
||||
VALUES (11200, N'干线故障管理', 0, 6, 'OdfCableFaults', 'business/OdfCableFaults', 0, 0, 'C', '0', '0', 'odfcablefaults:list', 'icon1', 'admin', GETDATE());
|
||||
|
||||
-- 干线故障管理 - 按钮权限
|
||||
INSERT INTO sys_menu (MenuId, MenuName, ParentId, OrderNum, Path, Component, MenuType, Perms, Create_by, Create_time)
|
||||
VALUES (11201, N'查询', 11200, 1, '#', NULL, 'F', 'odfcablefaults:query', 'admin', GETDATE());
|
||||
INSERT INTO sys_menu (MenuId, MenuName, ParentId, OrderNum, Path, Component, MenuType, Perms, Create_by, Create_time)
|
||||
VALUES (11202, N'删除', 11200, 2, '#', NULL, 'F', 'odfcablefaults:delete', 'admin', GETDATE());
|
||||
INSERT INTO sys_menu (MenuId, MenuName, ParentId, OrderNum, Path, Component, MenuType, Perms, Create_by, Create_time)
|
||||
VALUES (11203, N'导出', 11200, 3, '#', NULL, 'F', 'odfcablefaults:export', 'admin', GETDATE());
|
||||
|
||||
-- 3. 签到记录管理(一级菜单)
|
||||
INSERT INTO sys_menu (MenuId, MenuName, ParentId, OrderNum, Path, Component, IsCache, IsFrame, MenuType, Visible, Status, Perms, Icon, Create_by, Create_time)
|
||||
VALUES (11210, N'签到记录管理', 0, 7, 'OdfCheckin', 'business/OdfCheckin', 0, 0, 'C', '0', '0', 'odfcheckin:list', 'icon1', 'admin', GETDATE());
|
||||
|
||||
-- 签到记录管理 - 按钮权限
|
||||
INSERT INTO sys_menu (MenuId, MenuName, ParentId, OrderNum, Path, Component, MenuType, Perms, Create_by, Create_time)
|
||||
VALUES (11211, N'查询', 11210, 1, '#', NULL, 'F', 'odfcheckin:query', 'admin', GETDATE());
|
||||
INSERT INTO sys_menu (MenuId, MenuName, ParentId, OrderNum, Path, Component, MenuType, Perms, Create_by, Create_time)
|
||||
VALUES (11212, N'导出', 11210, 2, '#', NULL, 'F', 'odfcheckin:export', 'admin', GETDATE());
|
||||
|
||||
-- 4. 用户模块权限(一级菜单)
|
||||
INSERT INTO sys_menu (MenuId, MenuName, ParentId, OrderNum, Path, Component, IsCache, IsFrame, MenuType, Visible, Status, Perms, Icon, Create_by, Create_time)
|
||||
VALUES (11220, N'用户模块权限', 0, 8, 'OdfUserModules', 'business/OdfUserModules', 0, 0, 'C', '0', '0', 'odfusermodules:list', 'icon1', 'admin', GETDATE());
|
||||
|
||||
-- 用户模块权限 - 按钮权限
|
||||
INSERT INTO sys_menu (MenuId, MenuName, ParentId, OrderNum, Path, Component, MenuType, Perms, Create_by, Create_time)
|
||||
VALUES (11221, N'查询', 11220, 1, '#', NULL, 'F', 'odfusermodules:query', 'admin', GETDATE());
|
||||
INSERT INTO sys_menu (MenuId, MenuName, ParentId, OrderNum, Path, Component, MenuType, Perms, Create_by, Create_time)
|
||||
VALUES (11222, N'修改', 11220, 2, '#', NULL, 'F', 'odfusermodules:edit', 'admin', GETDATE());
|
||||
|
||||
-- =============================================
|
||||
-- 角色菜单权限分配
|
||||
-- Role 2 (common): 所有菜单 + 全部按钮权限
|
||||
-- Role 3 (editor): 所有菜单 + 仅查询权限
|
||||
-- Role 4 (lock): 所有菜单 + 仅查询权限
|
||||
-- Role 1 (admin): 超级管理员自动拥有所有权限,无需插入
|
||||
-- =============================================
|
||||
|
||||
-- Role 2 (common) — 光缆管理:全部权限
|
||||
INSERT INTO sys_role_menu (Role_id, Menu_id, Create_by, Create_time) VALUES (2, 11190, 'admin', GETDATE());
|
||||
INSERT INTO sys_role_menu (Role_id, Menu_id, Create_by, Create_time) VALUES (2, 11191, 'admin', GETDATE());
|
||||
INSERT INTO sys_role_menu (Role_id, Menu_id, Create_by, Create_time) VALUES (2, 11192, 'admin', GETDATE());
|
||||
INSERT INTO sys_role_menu (Role_id, Menu_id, Create_by, Create_time) VALUES (2, 11193, 'admin', GETDATE());
|
||||
INSERT INTO sys_role_menu (Role_id, Menu_id, Create_by, Create_time) VALUES (2, 11194, 'admin', GETDATE());
|
||||
INSERT INTO sys_role_menu (Role_id, Menu_id, Create_by, Create_time) VALUES (2, 11195, 'admin', GETDATE());
|
||||
|
||||
-- Role 2 (common) — 干线故障管理:全部权限
|
||||
INSERT INTO sys_role_menu (Role_id, Menu_id, Create_by, Create_time) VALUES (2, 11200, 'admin', GETDATE());
|
||||
INSERT INTO sys_role_menu (Role_id, Menu_id, Create_by, Create_time) VALUES (2, 11201, 'admin', GETDATE());
|
||||
INSERT INTO sys_role_menu (Role_id, Menu_id, Create_by, Create_time) VALUES (2, 11202, 'admin', GETDATE());
|
||||
INSERT INTO sys_role_menu (Role_id, Menu_id, Create_by, Create_time) VALUES (2, 11203, 'admin', GETDATE());
|
||||
|
||||
-- Role 2 (common) — 签到记录管理:全部权限
|
||||
INSERT INTO sys_role_menu (Role_id, Menu_id, Create_by, Create_time) VALUES (2, 11210, 'admin', GETDATE());
|
||||
INSERT INTO sys_role_menu (Role_id, Menu_id, Create_by, Create_time) VALUES (2, 11211, 'admin', GETDATE());
|
||||
INSERT INTO sys_role_menu (Role_id, Menu_id, Create_by, Create_time) VALUES (2, 11212, 'admin', GETDATE());
|
||||
|
||||
-- Role 2 (common) — 用户模块权限:全部权限
|
||||
INSERT INTO sys_role_menu (Role_id, Menu_id, Create_by, Create_time) VALUES (2, 11220, 'admin', GETDATE());
|
||||
INSERT INTO sys_role_menu (Role_id, Menu_id, Create_by, Create_time) VALUES (2, 11221, 'admin', GETDATE());
|
||||
INSERT INTO sys_role_menu (Role_id, Menu_id, Create_by, Create_time) VALUES (2, 11222, 'admin', GETDATE());
|
||||
|
||||
-- Role 3 (editor) — 光缆管理:菜单 + 查询
|
||||
INSERT INTO sys_role_menu (Role_id, Menu_id, Create_by, Create_time) VALUES (3, 11190, 'admin', GETDATE());
|
||||
INSERT INTO sys_role_menu (Role_id, Menu_id, Create_by, Create_time) VALUES (3, 11191, 'admin', GETDATE());
|
||||
|
||||
-- Role 3 (editor) — 干线故障管理:菜单 + 查询
|
||||
INSERT INTO sys_role_menu (Role_id, Menu_id, Create_by, Create_time) VALUES (3, 11200, 'admin', GETDATE());
|
||||
INSERT INTO sys_role_menu (Role_id, Menu_id, Create_by, Create_time) VALUES (3, 11201, 'admin', GETDATE());
|
||||
|
||||
-- Role 3 (editor) — 签到记录管理:菜单 + 查询
|
||||
INSERT INTO sys_role_menu (Role_id, Menu_id, Create_by, Create_time) VALUES (3, 11210, 'admin', GETDATE());
|
||||
INSERT INTO sys_role_menu (Role_id, Menu_id, Create_by, Create_time) VALUES (3, 11211, 'admin', GETDATE());
|
||||
|
||||
-- Role 3 (editor) — 用户模块权限:菜单 + 查询
|
||||
INSERT INTO sys_role_menu (Role_id, Menu_id, Create_by, Create_time) VALUES (3, 11220, 'admin', GETDATE());
|
||||
INSERT INTO sys_role_menu (Role_id, Menu_id, Create_by, Create_time) VALUES (3, 11221, 'admin', GETDATE());
|
||||
|
||||
-- Role 4 (lock) — 光缆管理:菜单 + 查询
|
||||
INSERT INTO sys_role_menu (Role_id, Menu_id, Create_by, Create_time) VALUES (4, 11190, 'admin', GETDATE());
|
||||
INSERT INTO sys_role_menu (Role_id, Menu_id, Create_by, Create_time) VALUES (4, 11191, 'admin', GETDATE());
|
||||
|
||||
-- Role 4 (lock) — 干线故障管理:菜单 + 查询
|
||||
INSERT INTO sys_role_menu (Role_id, Menu_id, Create_by, Create_time) VALUES (4, 11200, 'admin', GETDATE());
|
||||
INSERT INTO sys_role_menu (Role_id, Menu_id, Create_by, Create_time) VALUES (4, 11201, 'admin', GETDATE());
|
||||
|
||||
-- Role 4 (lock) — 签到记录管理:菜单 + 查询
|
||||
INSERT INTO sys_role_menu (Role_id, Menu_id, Create_by, Create_time) VALUES (4, 11210, 'admin', GETDATE());
|
||||
INSERT INTO sys_role_menu (Role_id, Menu_id, Create_by, Create_time) VALUES (4, 11211, 'admin', GETDATE());
|
||||
|
||||
-- Role 4 (lock) — 用户模块权限:菜单 + 查询
|
||||
INSERT INTO sys_role_menu (Role_id, Menu_id, Create_by, Create_time) VALUES (4, 11220, 'admin', GETDATE());
|
||||
INSERT INTO sys_role_menu (Role_id, Menu_id, Create_by, Create_time) VALUES (4, 11221, 'admin', GETDATE());
|
||||
Loading…
Reference in New Issue
Block a user