diff --git a/.kiro/specs/odf-v102-update/tasks.md b/.kiro/specs/odf-v102-update/tasks.md
index 033a032..ae603f4 100644
--- a/.kiro/specs/odf-v102-update/tasks.md
+++ b/.kiro/specs/odf-v102-update/tasks.md
@@ -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 脚本已执行,后台管理页面可通过菜单访问
- 确认所有测试通过,如有问题请告知。
diff --git a/odf-uniapp/pages.json b/odf-uniapp/pages.json
index 477bff5..e7df498 100644
--- a/odf-uniapp/pages.json
+++ b/odf-uniapp/pages.json
@@ -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": {
diff --git a/odf-uniapp/pages/cable/index.vue b/odf-uniapp/pages/cable/index.vue
new file mode 100644
index 0000000..3dd6d58
--- /dev/null
+++ b/odf-uniapp/pages/cable/index.vue
@@ -0,0 +1,216 @@
+
+
+
+
+
+
+
+
+
+ 干线
+
+
+
+
+
+ 光缆列表
+
+
+
+
+
+
+
+
+
+
+
+ {{ item.cableName }}
+
+
+
+
+
+
+
+
+
diff --git a/odf-uniapp/pages/checkin/index.vue b/odf-uniapp/pages/checkin/index.vue
new file mode 100644
index 0000000..0b3eff4
--- /dev/null
+++ b/odf-uniapp/pages/checkin/index.vue
@@ -0,0 +1,274 @@
+
+
+
+
+
+
+
+
+
+ 签到
+
+
+
+
+
+
+
+ 人员
+
+
+
+
+ 时间
+
+
+
+ {{ form.checkinTime || '请选择年月日' }}
+
+ ▼
+
+
+
+
+
+ 工作内容
+
+
+
+
+
+
+
+
+ 提交
+
+
+
+
+
+
+
+
diff --git a/odf-uniapp/pages/fault-add/index.vue b/odf-uniapp/pages/fault-add/index.vue
new file mode 100644
index 0000000..f024f1b
--- /dev/null
+++ b/odf-uniapp/pages/fault-add/index.vue
@@ -0,0 +1,440 @@
+
+
+
+
+
+
+
+
+
+ 新增故障
+
+
+
+
+
+
+
+
+
+ +
+ 点击拍摄
+
+
+
+
+
+
+
+
+
+ 故障时间
+
+ {{ form.faultTime || '拍摄第一张照片后自动填充' }}
+
+
+
+
+ 人员
+
+
+
+
+ 故障原因
+
+
+
+
+ 表显故障里程
+
+
+
+
+ 所属光缆
+
+ {{ form.cableName }}
+
+
+
+
+ 地点
+
+ 点击获取当前经纬度
+
+ 当前经度:{{ form.longitude }} 当前纬度:{{ form.latitude }}
+
+
+
+ 备注
+
+
+
+
+
+
+
+
+ 提交故障
+
+
+
+
+
+
+
+
diff --git a/odf-uniapp/pages/fault-detail/index.vue b/odf-uniapp/pages/fault-detail/index.vue
new file mode 100644
index 0000000..6666aef
--- /dev/null
+++ b/odf-uniapp/pages/fault-detail/index.vue
@@ -0,0 +1,274 @@
+
+
+
+
+
+
+
+
+
+ 故障详情
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 故障时间
+ {{ detail.faultTime }}
+
+
+ 人员
+ {{ detail.personnel }}
+
+
+ 故障原因
+ {{ detail.faultReason }}
+
+
+ 表显故障里程
+ {{ detail.mileage }}
+
+
+ 所属光缆
+ {{ detail.cableName }}
+
+
+ 地点
+ {{ detail.location }}
+
+
+ 备注
+ {{ detail.remark }}
+
+
+
+
+
+
+
+ 导航至地点
+
+
+
+
+
+
+
+
diff --git a/odf-uniapp/pages/fault-list/index.vue b/odf-uniapp/pages/fault-list/index.vue
new file mode 100644
index 0000000..c38414d
--- /dev/null
+++ b/odf-uniapp/pages/fault-list/index.vue
@@ -0,0 +1,245 @@
+
+
+
+
+
+
+
+
+
+ 干线
+
+
+
+
+
+ 故障列表
+
+
+
+
+
+ 故障时间:
+ {{ item.faultTime }}
+
+
+ 故障原因:
+ {{ item.faultReason }}
+
+
+ 表显故障里程:
+ {{ item.mileage }}
+
+
+ 所属光缆:
+ {{ item.cableName }}
+
+
+
+
+
+
+
+
+ 新增故障
+
+
+
+
+
+
+
+
diff --git a/odf-uniapp/pages/login/index.vue b/odf-uniapp/pages/login/index.vue
index cc002f1..d0a9e3b 100644
--- a/odf-uniapp/pages/login/index.vue
+++ b/odf-uniapp/pages/login/index.vue
@@ -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' })
}
diff --git a/odf-uniapp/pages/portal/index.vue b/odf-uniapp/pages/portal/index.vue
new file mode 100644
index 0000000..d526e85
--- /dev/null
+++ b/odf-uniapp/pages/portal/index.vue
@@ -0,0 +1,172 @@
+
+
+
+
+
+
+
+
+
+ 功能列表
+
+
+
+
+
+
+
+
+ {{ item.name }}
+
+
+
+
+
+ 暂无可用功能模块
+
+
+
+
+
+
+
+
diff --git a/odf-uniapp/pages/rack/index.vue b/odf-uniapp/pages/rack/index.vue
index dbcee94..c5a4f5b 100644
--- a/odf-uniapp/pages/rack/index.vue
+++ b/odf-uniapp/pages/rack/index.vue
@@ -13,7 +13,9 @@
@click="goBack"
/>
机房详情
-
+
+ 签到
+
@@ -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 {
diff --git a/odf-uniapp/pages/route-plan/index.vue b/odf-uniapp/pages/route-plan/index.vue
new file mode 100644
index 0000000..cc56ffc
--- /dev/null
+++ b/odf-uniapp/pages/route-plan/index.vue
@@ -0,0 +1,553 @@
+
+
+
+
+
+
+
+
+
+ 路线规划
+
+
+
+
+
+
+
+ 起点经度
+
+
+
+ 起点纬度
+
+
+
+ 终点经度
+
+
+
+ 终点纬度
+
+
+
+
+ 规划路线
+
+
+
+
+
+
+ 路线总长度
+ {{ formatDistance(routeDistance) }}
+
+
+
+
+ 输入距离(米)
+
+
+
+ 定位坐标
+
+
+
+
+ 定位坐标
+ 经度: {{ locatedPoint.lng }},纬度: {{ locatedPoint.lat }}
+
+
+
+
+
+
+
+
+
+
+
+ 导航至该位置
+
+
+
+
+
+
+ 路线规划中...
+
+
+
+
+
+
+
diff --git a/odf-uniapp/pages/trunk-search/index.vue b/odf-uniapp/pages/trunk-search/index.vue
new file mode 100644
index 0000000..75c3b93
--- /dev/null
+++ b/odf-uniapp/pages/trunk-search/index.vue
@@ -0,0 +1,259 @@
+
+
+
+
+
+
+
+
+
+ 搜索结果
+
+
+
+
+
+
+
+
+ 光缆
+
+
+ {{ item.cableName }}
+
+
+
+
+
+ 故障列表
+
+
+ 故障时间:
+ {{ item.faultTime }}
+
+
+ 故障原因:
+ {{ item.faultReason }}
+
+
+ 表显故障里程:
+ {{ item.mileage }}
+
+
+ 所属光缆:
+ {{ item.cableName }}
+
+
+
+
+
+
+ 暂无搜索结果
+
+
+
+
+
+
+
+
+
diff --git a/odf-uniapp/pages/trunk/index.vue b/odf-uniapp/pages/trunk/index.vue
new file mode 100644
index 0000000..6b0eb29
--- /dev/null
+++ b/odf-uniapp/pages/trunk/index.vue
@@ -0,0 +1,161 @@
+
+
+
+
+
+
+
+
+
+ 干线
+
+
+
+
+
+ 公司列表
+
+
+
+
+
+ {{ item.deptName }}
+
+
+
+
+
+
+
+
+
diff --git a/odf-uniapp/services/checkin.js b/odf-uniapp/services/checkin.js
new file mode 100644
index 0000000..1e656f8
--- /dev/null
+++ b/odf-uniapp/services/checkin.js
@@ -0,0 +1,3 @@
+import { post } from './api'
+
+export const submitCheckin = (data) => post('/business/OdfCheckin/submit', data)
diff --git a/odf-uniapp/services/permission.js b/odf-uniapp/services/permission.js
new file mode 100644
index 0000000..5e27a11
--- /dev/null
+++ b/odf-uniapp/services/permission.js
@@ -0,0 +1,3 @@
+import { get } from './api'
+
+export const getUserModules = () => get('/business/OdfUserModules/list')
diff --git a/odf-uniapp/services/trunk.js b/odf-uniapp/services/trunk.js
new file mode 100644
index 0000000..83ab69f
--- /dev/null
+++ b/odf-uniapp/services/trunk.js
@@ -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 })
diff --git a/odf-uniapp/store/index.js b/odf-uniapp/store/index.js
index 9917879..a5b8a44 100644
--- a/odf-uniapp/store/index.js
+++ b/odf-uniapp/store/index.js
@@ -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')
}
})
diff --git a/odf-uniapp/utils/navigation.js b/odf-uniapp/utils/navigation.js
new file mode 100644
index 0000000..5924820
--- /dev/null
+++ b/odf-uniapp/utils/navigation.js
@@ -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
+}
diff --git a/odf-uniapp/utils/watermark.js b/odf-uniapp/utils/watermark.js
new file mode 100644
index 0000000..feb813f
--- /dev/null
+++ b/odf-uniapp/utils/watermark.js
@@ -0,0 +1,108 @@
+/**
+ * 在照片左下角叠加水印文字
+ * @param {string} imagePath - 原始图片路径
+ * @param {string} text - 水印文字(如 "2025/06/15 12:00 张三")
+ * @returns {Promise} 带水印的临时文件路径
+ */
+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)
+ }
+ })
+ })
+}
diff --git a/server/ZR.Admin.WebApi/Controllers/Business/OdfCableFaultsController.cs b/server/ZR.Admin.WebApi/Controllers/Business/OdfCableFaultsController.cs
new file mode 100644
index 0000000..a2c08cf
--- /dev/null
+++ b/server/ZR.Admin.WebApi/Controllers/Business/OdfCableFaultsController.cs
@@ -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
+{
+ ///
+ /// 干线故障管理
+ ///
+ [Route("business/OdfCableFaults")]
+ public class OdfCableFaultsController : BaseController
+ {
+ ///
+ /// 干线故障接口
+ ///
+ private readonly IOdfCableFaultsService _OdfCableFaultsService;
+
+ public OdfCableFaultsController(IOdfCableFaultsService OdfCableFaultsService)
+ {
+ _OdfCableFaultsService = OdfCableFaultsService;
+ }
+
+ ///
+ /// 故障列表分页查询
+ ///
+ ///
+ ///
+ [HttpGet("list")]
+ [ActionPermissionFilter(Permission = "odfcablefaults:list")]
+ public IActionResult GetList([FromQuery] OdfCableFaultsQueryDto parm)
+ {
+ var response = _OdfCableFaultsService.GetList(parm);
+ return SUCCESS(response);
+ }
+
+ ///
+ /// 故障详情(含图片)
+ ///
+ ///
+ ///
+ [HttpGet("{id}")]
+ [ActionPermissionFilter(Permission = "odfcablefaults:query")]
+ public IActionResult GetDetail(int id)
+ {
+ var response = _OdfCableFaultsService.GetDetail(id);
+ return SUCCESS(response);
+ }
+
+ ///
+ /// 新增故障(含图片上传,APP端调用)
+ ///
+ ///
+ [HttpPost("add")]
+ [ActionPermissionFilter(Permission = "odfcablefaults:list")]
+ [Log(Title = "干线故障", BusinessType = BusinessType.INSERT)]
+ public async Task Add([FromForm] OdfCableFaultAddDto dto)
+ {
+ dto.UserId = HttpContext.GetUId();
+ var response = await _OdfCableFaultsService.AddFault(dto);
+ return ToResponse(response);
+ }
+
+ ///
+ /// 删除故障并级联删除图片
+ ///
+ ///
+ [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);
+ }
+
+ ///
+ /// 导出故障列表
+ ///
+ ///
+ [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);
+ }
+ }
+}
diff --git a/server/ZR.Admin.WebApi/Controllers/Business/OdfCablesController.cs b/server/ZR.Admin.WebApi/Controllers/Business/OdfCablesController.cs
new file mode 100644
index 0000000..8543765
--- /dev/null
+++ b/server/ZR.Admin.WebApi/Controllers/Business/OdfCablesController.cs
@@ -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
+{
+ ///
+ /// 光缆管理
+ ///
+ [Route("business/OdfCables")]
+ public class OdfCablesController : BaseController
+ {
+ ///
+ /// 光缆管理接口
+ ///
+ private readonly IOdfCablesService _OdfCablesService;
+
+ public OdfCablesController(IOdfCablesService OdfCablesService)
+ {
+ _OdfCablesService = OdfCablesService;
+ }
+
+ ///
+ /// 查询光缆列表
+ ///
+ ///
+ ///
+ [HttpGet("list")]
+ [ActionPermissionFilter(Permission = "odfcables:list")]
+ public IActionResult GetList([FromQuery] OdfCablesQueryDto parm)
+ {
+ var response = _OdfCablesService.GetList(parm);
+ return SUCCESS(response);
+ }
+
+ ///
+ /// 搜索光缆和故障(限定公司范围)
+ ///
+ ///
+ [HttpGet("search")]
+ [ActionPermissionFilter(Permission = "odfcables:query")]
+ public IActionResult Search([FromQuery] long deptId, [FromQuery] string keyword)
+ {
+ var response = _OdfCablesService.Search(deptId, keyword);
+ return SUCCESS(response);
+ }
+
+ ///
+ /// 新增光缆
+ ///
+ ///
+ [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);
+ }
+
+ ///
+ /// 修改光缆
+ ///
+ ///
+ [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);
+ }
+
+ ///
+ /// 查询光缆详情
+ ///
+ ///
+ ///
+ [HttpGet("{id}")]
+ [ActionPermissionFilter(Permission = "odfcables:query")]
+ public IActionResult GetDetail(int id)
+ {
+ var response = _OdfCablesService.GetDetail(id);
+ return SUCCESS(response);
+ }
+
+ ///
+ /// 删除光缆
+ ///
+ ///
+ [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);
+ }
+
+ ///
+ /// 导出光缆列表
+ ///
+ ///
+ [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);
+ }
+ }
+}
diff --git a/server/ZR.Admin.WebApi/Controllers/Business/OdfCheckinController.cs b/server/ZR.Admin.WebApi/Controllers/Business/OdfCheckinController.cs
new file mode 100644
index 0000000..18b3fd8
--- /dev/null
+++ b/server/ZR.Admin.WebApi/Controllers/Business/OdfCheckinController.cs
@@ -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
+{
+ ///
+ /// 签到记录
+ ///
+ [Route("business/OdfCheckin")]
+ public class OdfCheckinController : BaseController
+ {
+ ///
+ /// 签到记录接口
+ ///
+ private readonly IOdfCheckinService _OdfCheckinService;
+
+ public OdfCheckinController(IOdfCheckinService OdfCheckinService)
+ {
+ _OdfCheckinService = OdfCheckinService;
+ }
+
+ ///
+ /// 提交签到记录(APP端调用)
+ ///
+ ///
+ [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);
+ }
+
+ ///
+ /// 分页查询签到记录(管理端调用,联查机房名称和提交人)
+ ///
+ ///
+ ///
+ [HttpGet("list")]
+ [ActionPermissionFilter(Permission = "odfcheckin:list")]
+ public IActionResult GetList([FromQuery] OdfCheckinQueryDto parm)
+ {
+ var response = _OdfCheckinService.GetList(parm);
+ return SUCCESS(response);
+ }
+
+ ///
+ /// 导出签到记录(管理端调用)
+ ///
+ ///
+ [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);
+ }
+ }
+}
diff --git a/server/ZR.Admin.WebApi/Controllers/Business/OdfUserModulesController.cs b/server/ZR.Admin.WebApi/Controllers/Business/OdfUserModulesController.cs
new file mode 100644
index 0000000..65a31c0
--- /dev/null
+++ b/server/ZR.Admin.WebApi/Controllers/Business/OdfUserModulesController.cs
@@ -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
+{
+ ///
+ /// 用户模块权限
+ ///
+ [Route("business/OdfUserModules")]
+ public class OdfUserModulesController : BaseController
+ {
+ ///
+ /// 用户模块权限接口
+ ///
+ private readonly IOdfUserModulesService _OdfUserModulesService;
+
+ public OdfUserModulesController(IOdfUserModulesService OdfUserModulesService)
+ {
+ _OdfUserModulesService = OdfUserModulesService;
+ }
+
+ ///
+ /// 获取当前登录用户的功能版块列表(APP端调用,登录即可)
+ ///
+ ///
+ [HttpGet("list")]
+ public IActionResult GetUserModules([FromQuery] long? userId)
+ {
+ // 管理端传 userId 参数时查指定用户,APP端不传则查当前登录用户
+ long uid = userId ?? HttpContext.GetUId();
+ var modules = _OdfUserModulesService.GetUserModules(uid);
+ return SUCCESS(modules);
+ }
+
+ ///
+ /// 获取用户列表(管理端调用)
+ ///
+ ///
+ [HttpGet("users")]
+ [ActionPermissionFilter(Permission = "odfusermodules:query")]
+ public IActionResult GetUserList()
+ {
+ var list = _OdfUserModulesService.GetUserList();
+ return SUCCESS(list);
+ }
+
+ ///
+ /// 批量保存用户模块权限(管理端调用)
+ ///
+ ///
+ [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);
+ }
+ }
+}
diff --git a/server/ZR.Model/Business/Dto/OdfCableFaultsDto.cs b/server/ZR.Model/Business/Dto/OdfCableFaultsDto.cs
new file mode 100644
index 0000000..5747daa
--- /dev/null
+++ b/server/ZR.Model/Business/Dto/OdfCableFaultsDto.cs
@@ -0,0 +1,52 @@
+using Microsoft.AspNetCore.Http;
+
+namespace ZR.Model.Business.Dto
+{
+ ///
+ /// 干线故障查询对象
+ ///
+ public class OdfCableFaultsQueryDto : PagerInfo
+ {
+ public int? CableId { get; set; }
+
+ ///
+ /// 故障时间范围 - 开始
+ ///
+ public DateTime? BeginFaultTime { get; set; }
+
+ ///
+ /// 故障时间范围 - 结束
+ ///
+ public DateTime? EndFaultTime { get; set; }
+
+ public string FaultReason { get; set; }
+ }
+
+ ///
+ /// 新增故障输入对象(含图片上传)
+ ///
+ 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; }
+ }
+}
diff --git a/server/ZR.Model/Business/Dto/OdfCablesDto.cs b/server/ZR.Model/Business/Dto/OdfCablesDto.cs
new file mode 100644
index 0000000..3bbe59a
--- /dev/null
+++ b/server/ZR.Model/Business/Dto/OdfCablesDto.cs
@@ -0,0 +1,13 @@
+
+namespace ZR.Model.Business.Dto
+{
+ ///
+ /// 光缆列表查询对象
+ ///
+ public class OdfCablesQueryDto : PagerInfo
+ {
+ public long? DeptId { get; set; }
+
+ public string CableName { get; set; }
+ }
+}
diff --git a/server/ZR.Model/Business/Dto/OdfCheckinDto.cs b/server/ZR.Model/Business/Dto/OdfCheckinDto.cs
new file mode 100644
index 0000000..410a0ea
--- /dev/null
+++ b/server/ZR.Model/Business/Dto/OdfCheckinDto.cs
@@ -0,0 +1,78 @@
+using MiniExcelLibs.Attributes;
+
+namespace ZR.Model.Business.Dto
+{
+ ///
+ /// 签到记录输入对象
+ ///
+ 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; }
+ }
+
+ ///
+ /// 签到记录列表/导出对象
+ ///
+ public class OdfCheckinListDto
+ {
+ [ExcelColumn(Name = "Id")]
+ public int Id { get; set; }
+
+ [ExcelColumn(Name = "机房ID")]
+ public int RoomId { get; set; }
+
+ ///
+ /// 机房名称(联查 odf_rooms)
+ ///
+ [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; }
+
+ ///
+ /// 提交人用户名(联查 sys_user.NickName)
+ ///
+ [ExcelColumn(Name = "提交人")]
+ public string UserName { get; set; }
+
+ [ExcelColumn(Name = "创建时间", Format = "yyyy-MM-dd HH:mm:ss", Width = 20)]
+ public DateTime? CreatedAt { get; set; }
+ }
+
+ ///
+ /// 签到记录查询对象
+ ///
+ public class OdfCheckinQueryDto : PagerInfo
+ {
+ public int? RoomId { get; set; }
+
+ public string Personnel { get; set; }
+
+ ///
+ /// 签到时间范围 - 开始
+ ///
+ public DateTime? BeginCheckinTime { get; set; }
+
+ ///
+ /// 签到时间范围 - 结束
+ ///
+ public DateTime? EndCheckinTime { get; set; }
+ }
+}
diff --git a/server/ZR.Model/Business/Dto/OdfUserModulesDto.cs b/server/ZR.Model/Business/Dto/OdfUserModulesDto.cs
new file mode 100644
index 0000000..4b6f863
--- /dev/null
+++ b/server/ZR.Model/Business/Dto/OdfUserModulesDto.cs
@@ -0,0 +1,13 @@
+
+namespace ZR.Model.Business.Dto
+{
+ ///
+ /// 用户模块权限保存对象
+ ///
+ public class OdfUserModulesSaveDto
+ {
+ public long UserId { get; set; }
+
+ public List Modules { get; set; }
+ }
+}
diff --git a/server/ZR.Model/Business/OdfCableFaultImages.cs b/server/ZR.Model/Business/OdfCableFaultImages.cs
new file mode 100644
index 0000000..620fd74
--- /dev/null
+++ b/server/ZR.Model/Business/OdfCableFaultImages.cs
@@ -0,0 +1,30 @@
+namespace ZR.Model.Business
+{
+ ///
+ /// 故障图片
+ ///
+ [SugarTable("odf_cable_fault_images")]
+ public class OdfCableFaultImages
+ {
+ ///
+ /// Id
+ ///
+ [SugarColumn(IsPrimaryKey = true, IsIdentity = true)]
+ public int Id { get; set; }
+
+ ///
+ /// 关联故障ID
+ ///
+ public int FaultId { get; set; }
+
+ ///
+ /// 图片访问URL
+ ///
+ public string ImageUrl { get; set; }
+
+ ///
+ /// 创建时间
+ ///
+ public DateTime? CreatedAt { get; set; }
+ }
+}
diff --git a/server/ZR.Model/Business/OdfCableFaults.cs b/server/ZR.Model/Business/OdfCableFaults.cs
new file mode 100644
index 0000000..af2c60a
--- /dev/null
+++ b/server/ZR.Model/Business/OdfCableFaults.cs
@@ -0,0 +1,75 @@
+namespace ZR.Model.Business
+{
+ ///
+ /// 干线故障
+ ///
+ [SugarTable("odf_cable_faults")]
+ public class OdfCableFaults
+ {
+ ///
+ /// Id
+ ///
+ [SugarColumn(IsPrimaryKey = true, IsIdentity = true)]
+ public int Id { get; set; }
+
+ ///
+ /// 关联光缆ID
+ ///
+ public int CableId { get; set; }
+
+ ///
+ /// 故障时间
+ ///
+ public DateTime 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; }
+
+ ///
+ /// 提交人用户ID
+ ///
+ public long? UserId { get; set; }
+
+ ///
+ /// 创建时间
+ ///
+ public DateTime? CreatedAt { get; set; }
+
+ ///
+ /// 更新时间
+ ///
+ public DateTime? UpdatedAt { get; set; }
+ }
+}
diff --git a/server/ZR.Model/Business/OdfCables.cs b/server/ZR.Model/Business/OdfCables.cs
new file mode 100644
index 0000000..ed9f241
--- /dev/null
+++ b/server/ZR.Model/Business/OdfCables.cs
@@ -0,0 +1,40 @@
+namespace ZR.Model.Business
+{
+ ///
+ /// 光缆
+ ///
+ [SugarTable("odf_cables")]
+ public class OdfCables
+ {
+ ///
+ /// Id
+ ///
+ [SugarColumn(IsPrimaryKey = true, IsIdentity = true)]
+ public int Id { get; set; }
+
+ ///
+ /// 光缆名称
+ ///
+ public string CableName { get; set; }
+
+ ///
+ /// 所属公司/部门ID
+ ///
+ public long DeptId { get; set; }
+
+ ///
+ /// 部门名称(冗余)
+ ///
+ public string DeptName { get; set; }
+
+ ///
+ /// 创建时间
+ ///
+ public DateTime? CreatedAt { get; set; }
+
+ ///
+ /// 更新时间
+ ///
+ public DateTime? UpdatedAt { get; set; }
+ }
+}
diff --git a/server/ZR.Model/Business/OdfCheckin.cs b/server/ZR.Model/Business/OdfCheckin.cs
new file mode 100644
index 0000000..aa0bb1b
--- /dev/null
+++ b/server/ZR.Model/Business/OdfCheckin.cs
@@ -0,0 +1,46 @@
+
+namespace ZR.Model.Business
+{
+ ///
+ /// 签到记录
+ ///
+ [SugarTable("odf_checkin")]
+ public class OdfCheckin
+ {
+ ///
+ /// Id
+ ///
+ [SugarColumn(IsPrimaryKey = true, IsIdentity = true)]
+ public int Id { get; set; }
+
+ ///
+ /// 关联机房ID
+ ///
+ public int RoomId { get; set; }
+
+ ///
+ /// 签到人员
+ ///
+ public string Personnel { get; set; }
+
+ ///
+ /// 签到时间
+ ///
+ public DateTime CheckinTime { get; set; }
+
+ ///
+ /// 工作内容
+ ///
+ public string WorkContent { get; set; }
+
+ ///
+ /// 提交人用户ID
+ ///
+ public long? UserId { get; set; }
+
+ ///
+ /// 记录创建时间
+ ///
+ public DateTime? CreatedAt { get; set; }
+ }
+}
diff --git a/server/ZR.Model/Business/OdfUserModules.cs b/server/ZR.Model/Business/OdfUserModules.cs
new file mode 100644
index 0000000..21f890d
--- /dev/null
+++ b/server/ZR.Model/Business/OdfUserModules.cs
@@ -0,0 +1,30 @@
+namespace ZR.Model.Business
+{
+ ///
+ /// 用户功能版块权限
+ ///
+ [SugarTable("odf_user_modules")]
+ public class OdfUserModules
+ {
+ ///
+ /// Id
+ ///
+ [SugarColumn(IsPrimaryKey = true, IsIdentity = true)]
+ public int Id { get; set; }
+
+ ///
+ /// 用户ID
+ ///
+ public long UserId { get; set; }
+
+ ///
+ /// 模块标识
+ ///
+ public string ModuleCode { get; set; }
+
+ ///
+ /// 创建时间
+ ///
+ public DateTime? CreatedAt { get; set; }
+ }
+}
diff --git a/server/ZR.Model/ZR.Model.csproj b/server/ZR.Model/ZR.Model.csproj
index f58dbcf..516d4ce 100644
--- a/server/ZR.Model/ZR.Model.csproj
+++ b/server/ZR.Model/ZR.Model.csproj
@@ -7,6 +7,7 @@
+
diff --git a/server/ZR.Service/Business/IBusinessService/IOdfCableFaultImagesService.cs b/server/ZR.Service/Business/IBusinessService/IOdfCableFaultImagesService.cs
new file mode 100644
index 0000000..0193e88
--- /dev/null
+++ b/server/ZR.Service/Business/IBusinessService/IOdfCableFaultImagesService.cs
@@ -0,0 +1,32 @@
+using ZR.Model.Business;
+
+namespace ZR.Service.Business.IBusinessService
+{
+ ///
+ /// 故障图片service接口
+ ///
+ public interface IOdfCableFaultImagesService : IBaseService
+ {
+ ///
+ /// 按故障 ID 查询图片列表
+ ///
+ ///
+ ///
+ List GetByFaultId(int faultId);
+
+ ///
+ /// 批量插入图片记录
+ ///
+ ///
+ ///
+ ///
+ int BatchInsert(int faultId, List imageUrls);
+
+ ///
+ /// 按故障 ID 删除所有图片记录
+ ///
+ ///
+ ///
+ int DeleteByFaultId(int faultId);
+ }
+}
diff --git a/server/ZR.Service/Business/IBusinessService/IOdfCableFaultsService.cs b/server/ZR.Service/Business/IBusinessService/IOdfCableFaultsService.cs
new file mode 100644
index 0000000..ea1bf3e
--- /dev/null
+++ b/server/ZR.Service/Business/IBusinessService/IOdfCableFaultsService.cs
@@ -0,0 +1,37 @@
+using ZR.Model;
+using ZR.Model.Business;
+using ZR.Model.Business.Dto;
+
+namespace ZR.Service.Business.IBusinessService
+{
+ ///
+ /// 干线故障service接口
+ ///
+ public interface IOdfCableFaultsService : IBaseService
+ {
+ ///
+ /// 按 CableId 分页查询故障列表,联查光缆名称,按 FaultTime DESC 排序
+ ///
+ PagedInfo GetList(OdfCableFaultsQueryDto parm);
+
+ ///
+ /// 查询故障详情,联查光缆名称和图片列表
+ ///
+ object GetDetail(int id);
+
+ ///
+ /// 新增故障(含图片上传)
+ ///
+ Task AddFault(OdfCableFaultAddDto dto);
+
+ ///
+ /// 删除故障记录并级联删除关联图片
+ ///
+ int Delete(int id);
+
+ ///
+ /// 导出故障列表
+ ///
+ PagedInfo ExportList(OdfCableFaultsQueryDto parm);
+ }
+}
diff --git a/server/ZR.Service/Business/IBusinessService/IOdfCablesService.cs b/server/ZR.Service/Business/IBusinessService/IOdfCablesService.cs
new file mode 100644
index 0000000..693cc53
--- /dev/null
+++ b/server/ZR.Service/Business/IBusinessService/IOdfCablesService.cs
@@ -0,0 +1,62 @@
+using ZR.Model;
+using ZR.Model.Business;
+using ZR.Model.Business.Dto;
+
+namespace ZR.Service.Business.IBusinessService
+{
+ ///
+ /// 光缆管理service接口
+ ///
+ public interface IOdfCablesService : IBaseService
+ {
+ ///
+ /// 按 DeptId 过滤光缆列表(支持分页和 CableName 模糊查询)
+ ///
+ ///
+ ///
+ PagedInfo GetList(OdfCablesQueryDto parm);
+
+ ///
+ /// 在指定公司范围内搜索光缆和故障
+ ///
+ ///
+ ///
+ ///
+ object Search(long deptId, string keyword);
+
+ ///
+ /// 新增光缆
+ ///
+ ///
+ ///
+ OdfCables Add(OdfCables model);
+
+ ///
+ /// 修改光缆
+ ///
+ ///
+ ///
+ int Update(OdfCables model);
+
+ ///
+ /// 删除光缆
+ ///
+ ///
+ ///
+ int Delete(int id);
+
+ ///
+ /// 获取光缆详情
+ ///
+ ///
+ ///
+ OdfCables GetDetail(int id);
+
+ ///
+ /// 导出光缆列表
+ ///
+ ///
+ ///
+ PagedInfo ExportList(OdfCablesQueryDto parm);
+ }
+}
diff --git a/server/ZR.Service/Business/IBusinessService/IOdfCheckinService.cs b/server/ZR.Service/Business/IBusinessService/IOdfCheckinService.cs
new file mode 100644
index 0000000..90d6db1
--- /dev/null
+++ b/server/ZR.Service/Business/IBusinessService/IOdfCheckinService.cs
@@ -0,0 +1,33 @@
+using ZR.Model;
+using ZR.Model.Business;
+using ZR.Model.Business.Dto;
+
+namespace ZR.Service.Business.IBusinessService
+{
+ ///
+ /// 签到记录service接口
+ ///
+ public interface IOdfCheckinService : IBaseService
+ {
+ ///
+ /// 新增签到记录
+ ///
+ ///
+ ///
+ OdfCheckin AddCheckin(OdfCheckinDto dto);
+
+ ///
+ /// 分页查询签到记录(联查机房名称和提交人)
+ ///
+ ///
+ ///
+ PagedInfo GetList(OdfCheckinQueryDto parm);
+
+ ///
+ /// 导出签到记录
+ ///
+ ///
+ ///
+ PagedInfo ExportList(OdfCheckinQueryDto parm);
+ }
+}
diff --git a/server/ZR.Service/Business/IBusinessService/IOdfUserModulesService.cs b/server/ZR.Service/Business/IBusinessService/IOdfUserModulesService.cs
new file mode 100644
index 0000000..0a1c9b6
--- /dev/null
+++ b/server/ZR.Service/Business/IBusinessService/IOdfUserModulesService.cs
@@ -0,0 +1,31 @@
+using ZR.Model.Business;
+
+namespace ZR.Service.Business.IBusinessService
+{
+ ///
+ /// 用户模块权限service接口
+ ///
+ public interface IOdfUserModulesService : IBaseService
+ {
+ ///
+ /// 获取用户的功能版块列表
+ ///
+ ///
+ ///
+ List GetUserModules(long userId);
+
+ ///
+ /// 获取用户列表(userId, userName)
+ ///
+ ///
+ List GetUserList();
+
+ ///
+ /// 保存用户模块权限(先删后插,事务保证原子性)
+ ///
+ ///
+ ///
+ ///
+ int SaveUserModules(long userId, List modules);
+ }
+}
diff --git a/server/ZR.Service/Business/OdfCableFaultImagesService.cs b/server/ZR.Service/Business/OdfCableFaultImagesService.cs
new file mode 100644
index 0000000..68674f9
--- /dev/null
+++ b/server/ZR.Service/Business/OdfCableFaultImagesService.cs
@@ -0,0 +1,62 @@
+using Infrastructure.Attribute;
+using ZR.Model.Business;
+using ZR.Repository;
+using ZR.Service.Business.IBusinessService;
+
+namespace ZR.Service.Business
+{
+ ///
+ /// 故障图片Service业务层处理
+ ///
+ [AppService(ServiceType = typeof(IOdfCableFaultImagesService), ServiceLifetime = LifeTime.Transient)]
+ public class OdfCableFaultImagesService : BaseService, IOdfCableFaultImagesService
+ {
+ ///
+ /// 按故障 ID 查询图片列表
+ ///
+ ///
+ ///
+ public List GetByFaultId(int faultId)
+ {
+ return Queryable()
+ .Where(x => x.FaultId == faultId)
+ .OrderBy(x => x.Id)
+ .ToList();
+ }
+
+ ///
+ /// 批量插入图片记录
+ ///
+ ///
+ ///
+ ///
+ public int BatchInsert(int faultId, List 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);
+ }
+
+ ///
+ /// 按故障 ID 删除所有图片记录
+ ///
+ ///
+ ///
+ public int DeleteByFaultId(int faultId)
+ {
+ return Deleteable()
+ .Where(x => x.FaultId == faultId)
+ .ExecuteCommand();
+ }
+ }
+}
diff --git a/server/ZR.Service/Business/OdfCableFaultsService.cs b/server/ZR.Service/Business/OdfCableFaultsService.cs
new file mode 100644
index 0000000..1d6c2c1
--- /dev/null
+++ b/server/ZR.Service/Business/OdfCableFaultsService.cs
@@ -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
+{
+ ///
+ /// 干线故障Service业务层处理
+ ///
+ [AppService(ServiceType = typeof(IOdfCableFaultsService), ServiceLifetime = LifeTime.Transient)]
+ public class OdfCableFaultsService : BaseService, IOdfCableFaultsService
+ {
+ ///
+ /// 按 CableId 分页查询故障列表,联查光缆名称,按 FaultTime DESC 排序
+ ///
+ public PagedInfo GetList(OdfCableFaultsQueryDto parm)
+ {
+ var predicate = QueryExp(parm);
+
+ var response = Queryable()
+ .Where(predicate.ToExpression())
+ .LeftJoin((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(parm);
+
+ return response;
+ }
+
+ ///
+ /// 查询故障详情,联查光缆名称和图片列表
+ ///
+ public object GetDetail(int id)
+ {
+ var fault = Queryable()
+ .LeftJoin((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()
+ .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
+ };
+ }
+
+ ///
+ /// 新增故障(含图片上传)
+ ///
+ public async Task AddFault(OdfCableFaultAddDto dto)
+ {
+ // 校验 CableId 存在
+ var cable = Context.Queryable()
+ .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;
+ }
+
+ ///
+ /// 删除故障记录并级联删除关联图片
+ ///
+ public int Delete(int id)
+ {
+ // 先删除关联图片记录
+ Context.Deleteable()
+ .Where(img => img.FaultId == id)
+ .ExecuteCommand();
+
+ // 再删除故障记录
+ return base.Delete(id);
+ }
+
+ ///
+ /// 导出故障列表
+ ///
+ public PagedInfo ExportList(OdfCableFaultsQueryDto parm)
+ {
+ parm.PageNum = 1;
+ parm.PageSize = 100000;
+ return GetList(parm);
+ }
+
+ ///
+ /// 查询表达式
+ ///
+ private static Expressionable QueryExp(OdfCableFaultsQueryDto parm)
+ {
+ var predicate = Expressionable.Create();
+
+ 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;
+ }
+ }
+}
diff --git a/server/ZR.Service/Business/OdfCablesService.cs b/server/ZR.Service/Business/OdfCablesService.cs
new file mode 100644
index 0000000..b99d525
--- /dev/null
+++ b/server/ZR.Service/Business/OdfCablesService.cs
@@ -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
+{
+ ///
+ /// 光缆管理Service业务层处理
+ ///
+ [AppService(ServiceType = typeof(IOdfCablesService), ServiceLifetime = LifeTime.Transient)]
+ public class OdfCablesService : BaseService, IOdfCablesService
+ {
+ ///
+ /// 按 DeptId 过滤光缆列表(支持分页和 CableName 模糊查询)
+ ///
+ ///
+ ///
+ public PagedInfo GetList(OdfCablesQueryDto parm)
+ {
+ var predicate = Expressionable.Create();
+
+ 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;
+ }
+
+ ///
+ /// 在指定公司范围内搜索光缆和故障
+ ///
+ ///
+ ///
+ ///
+ 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()
+ .LeftJoin((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 };
+ }
+
+ ///
+ /// 新增光缆
+ ///
+ ///
+ ///
+ public OdfCables Add(OdfCables model)
+ {
+ model.CreatedAt = DateTime.Now;
+ model.UpdatedAt = DateTime.Now;
+ return Insertable(model).ExecuteReturnEntity();
+ }
+
+ ///
+ /// 修改光缆
+ ///
+ ///
+ ///
+ public int Update(OdfCables model)
+ {
+ model.UpdatedAt = DateTime.Now;
+ return base.Update(model, true);
+ }
+
+ ///
+ /// 删除光缆
+ ///
+ ///
+ ///
+ public int Delete(int id)
+ {
+ return base.Delete(id);
+ }
+
+ ///
+ /// 获取光缆详情
+ ///
+ ///
+ ///
+ public OdfCables GetDetail(int id)
+ {
+ return GetFirst(x => x.Id == id);
+ }
+
+ ///
+ /// 导出光缆列表
+ ///
+ ///
+ ///
+ public PagedInfo ExportList(OdfCablesQueryDto parm)
+ {
+ parm.PageNum = 1;
+ parm.PageSize = 100000;
+ return GetList(parm);
+ }
+ }
+}
diff --git a/server/ZR.Service/Business/OdfCheckinService.cs b/server/ZR.Service/Business/OdfCheckinService.cs
new file mode 100644
index 0000000..b2613cd
--- /dev/null
+++ b/server/ZR.Service/Business/OdfCheckinService.cs
@@ -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
+{
+ ///
+ /// 签到记录Service业务层处理
+ ///
+ [AppService(ServiceType = typeof(IOdfCheckinService), ServiceLifetime = LifeTime.Transient)]
+ public class OdfCheckinService : BaseService, IOdfCheckinService
+ {
+ ///
+ /// 新增签到记录
+ ///
+ ///
+ ///
+ public OdfCheckin AddCheckin(OdfCheckinDto dto)
+ {
+ // 校验 RoomId 存在
+ var room = Context.Queryable()
+ .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();
+ }
+
+ ///
+ /// 分页查询签到记录(联查机房名称和提交人用户名)
+ ///
+ ///
+ ///
+ public PagedInfo GetList(OdfCheckinQueryDto parm)
+ {
+ var predicate = QueryExp(parm);
+
+ var response = Queryable()
+ .Where(predicate.ToExpression())
+ .LeftJoin((it, r) => it.RoomId == r.Id)
+ .LeftJoin((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;
+ }
+
+ ///
+ /// 导出签到记录
+ ///
+ ///
+ ///
+ public PagedInfo ExportList(OdfCheckinQueryDto parm)
+ {
+ parm.PageNum = 1;
+ parm.PageSize = 100000;
+ var predicate = QueryExp(parm);
+
+ var response = Queryable()
+ .Where(predicate.ToExpression())
+ .LeftJoin((it, r) => it.RoomId == r.Id)
+ .LeftJoin((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;
+ }
+
+ ///
+ /// 查询表达式
+ ///
+ ///
+ ///
+ private static Expressionable QueryExp(OdfCheckinQueryDto parm)
+ {
+ var predicate = Expressionable.Create();
+
+ 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;
+ }
+ }
+}
diff --git a/server/ZR.Service/Business/OdfUserModulesService.cs b/server/ZR.Service/Business/OdfUserModulesService.cs
new file mode 100644
index 0000000..40ef494
--- /dev/null
+++ b/server/ZR.Service/Business/OdfUserModulesService.cs
@@ -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
+{
+ ///
+ /// 用户模块权限Service业务层处理
+ ///
+ [AppService(ServiceType = typeof(IOdfUserModulesService), ServiceLifetime = LifeTime.Transient)]
+ public class OdfUserModulesService : BaseService, IOdfUserModulesService
+ {
+ ///
+ /// 获取用户的功能版块列表
+ ///
+ ///
+ ///
+ public List GetUserModules(long userId)
+ {
+ return Queryable()
+ .Where(x => x.UserId == userId)
+ .Select(x => x.ModuleCode)
+ .ToList();
+ }
+
+ ///
+ /// 获取用户列表(userId, userName)
+ ///
+ ///
+ public List GetUserList()
+ {
+ return Context.Queryable()
+ .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();
+ }
+
+ ///
+ /// 保存用户模块权限(先删后插,事务保证原子性)
+ ///
+ ///
+ ///
+ ///
+ public int SaveUserModules(long userId, List 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;
+ }
+ }
+}
diff --git a/server/ZR.Vue/src/api/business/odfcablefaults.js b/server/ZR.Vue/src/api/business/odfcablefaults.js
new file mode 100644
index 0000000..4bff28f
--- /dev/null
+++ b/server/ZR.Vue/src/api/business/odfcablefaults.js
@@ -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 })
+}
diff --git a/server/ZR.Vue/src/api/business/odfcables.js b/server/ZR.Vue/src/api/business/odfcables.js
new file mode 100644
index 0000000..1a34180
--- /dev/null
+++ b/server/ZR.Vue/src/api/business/odfcables.js
@@ -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 })
+}
diff --git a/server/ZR.Vue/src/api/business/odfcheckin.js b/server/ZR.Vue/src/api/business/odfcheckin.js
new file mode 100644
index 0000000..3d74fba
--- /dev/null
+++ b/server/ZR.Vue/src/api/business/odfcheckin.js
@@ -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 })
+}
diff --git a/server/ZR.Vue/src/api/business/odfusermodules.js b/server/ZR.Vue/src/api/business/odfusermodules.js
new file mode 100644
index 0000000..84b8b5e
--- /dev/null
+++ b/server/ZR.Vue/src/api/business/odfusermodules.js
@@ -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,
+ })
+}
diff --git a/server/ZR.Vue/src/components/business/OdfCableForm.vue b/server/ZR.Vue/src/components/business/OdfCableForm.vue
new file mode 100644
index 0000000..e846d74
--- /dev/null
+++ b/server/ZR.Vue/src/components/business/OdfCableForm.vue
@@ -0,0 +1,199 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ $t('btn.cancel') }}
+ {{ $t('btn.submit') }}
+
+
+
+ {{ $t('btn.close') }}
+
+
+
+
+
diff --git a/server/ZR.Vue/src/views/business/OdfCableFaults.vue b/server/ZR.Vue/src/views/business/OdfCableFaults.vue
new file mode 100644
index 0000000..1021835
--- /dev/null
+++ b/server/ZR.Vue/src/views/business/OdfCableFaults.vue
@@ -0,0 +1,301 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ $t('btn.search') }}
+ {{ $t('btn.reset') }}
+
+
+
+
+
+
+ {{ $t('btn.delete') }}
+
+
+
+
+ {{ $t('btn.export') }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ detailData.id }}
+ {{ detailData.faultTime }}
+ {{ detailData.personnel }}
+ {{ detailData.mileage }}
+ {{ detailData.cableName }}
+ {{ detailData.location }}
+ {{ detailData.latitude }}
+ {{ detailData.longitude }}
+ {{ detailData.faultReason }}
+ {{ detailData.remark }}
+ {{ detailData.createdAt }}
+ {{ detailData.updatedAt }}
+
+
+
+ 关闭
+
+
+
+
+
+
diff --git a/server/ZR.Vue/src/views/business/OdfCables.vue b/server/ZR.Vue/src/views/business/OdfCables.vue
new file mode 100644
index 0000000..307a4e6
--- /dev/null
+++ b/server/ZR.Vue/src/views/business/OdfCables.vue
@@ -0,0 +1,254 @@
+
+
+
+
+
+
+
+
+
+
+
+ {{ $t('btn.search') }}
+ {{ $t('btn.reset') }}
+
+
+
+
+
+
+ {{ $t('btn.add') }}
+
+
+
+
+ {{ $t('btn.edit') }}
+
+
+
+
+ {{ $t('btn.delete') }}
+
+
+
+
+ {{ $t('btn.export') }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/server/ZR.Vue/src/views/business/OdfCheckin.vue b/server/ZR.Vue/src/views/business/OdfCheckin.vue
new file mode 100644
index 0000000..9a8a2ee
--- /dev/null
+++ b/server/ZR.Vue/src/views/business/OdfCheckin.vue
@@ -0,0 +1,181 @@
+
+
+
+
+
+
+
+ {{ item.dictLabel }}
+ {{ item.dictValue }}
+
+
+
+
+
+
+
+
+
+
+ {{ $t('btn.search') }}
+ {{ $t('btn.reset') }}
+
+
+
+
+
+
+ {{ $t('btn.export') }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/server/ZR.Vue/src/views/business/OdfUserModules.vue b/server/ZR.Vue/src/views/business/OdfUserModules.vue
new file mode 100644
index 0000000..ce01766
--- /dev/null
+++ b/server/ZR.Vue/src/views/business/OdfUserModules.vue
@@ -0,0 +1,130 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ currentUser ? currentUser.userName + ' - 模块权限配置' : '请选择用户' }}
+
+
+
+ 机房版块
+ 干线版块
+ 路线规划
+
+
+
+ 保存
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sql/v1.0.2/01_create_odf_checkin.sql b/sql/v1.0.2/01_create_odf_checkin.sql
new file mode 100644
index 0000000..4534291
--- /dev/null
+++ b/sql/v1.0.2/01_create_odf_checkin.sql
@@ -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);
diff --git a/sql/v1.0.2/02_create_odf_cables.sql b/sql/v1.0.2/02_create_odf_cables.sql
new file mode 100644
index 0000000..0db2393
--- /dev/null
+++ b/sql/v1.0.2/02_create_odf_cables.sql
@@ -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);
diff --git a/sql/v1.0.2/03_create_odf_cable_faults.sql b/sql/v1.0.2/03_create_odf_cable_faults.sql
new file mode 100644
index 0000000..862ff4a
--- /dev/null
+++ b/sql/v1.0.2/03_create_odf_cable_faults.sql
@@ -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);
diff --git a/sql/v1.0.2/04_create_odf_cable_fault_images.sql b/sql/v1.0.2/04_create_odf_cable_fault_images.sql
new file mode 100644
index 0000000..f4efc06
--- /dev/null
+++ b/sql/v1.0.2/04_create_odf_cable_fault_images.sql
@@ -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);
diff --git a/sql/v1.0.2/05_create_odf_user_modules.sql b/sql/v1.0.2/05_create_odf_user_modules.sql
new file mode 100644
index 0000000..e062bed
--- /dev/null
+++ b/sql/v1.0.2/05_create_odf_user_modules.sql
@@ -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);
diff --git a/sql/v1.0.2/06_insert_menus_and_permissions.sql b/sql/v1.0.2/06_insert_menus_and_permissions.sql
new file mode 100644
index 0000000..372eb28
--- /dev/null
+++ b/sql/v1.0.2/06_insert_menus_and_permissions.sql
@@ -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());