diff --git a/.kiro/specs/odf-v1021-optical-box/tasks.md b/.kiro/specs/odf-v1021-optical-box/tasks.md index 0b57b00..891ae53 100644 --- a/.kiro/specs/odf-v1021-optical-box/tasks.md +++ b/.kiro/specs/odf-v1021-optical-box/tasks.md @@ -3,39 +3,39 @@ ## 任务列表 ### Task 1: 数据库迁移脚本 -- [ ] 创建 SQL 迁移脚本 `docs/v1.0.2.1/migration.sql` -- [ ] `odf_racks` 表新增 `rack_type`(INT, DEFAULT 0) -- [ ] `odf_racks` 表新增 `left_ports_count`(INT, NULL) -- [ ] `odf_racks` 表新增 `right_ports_count`(INT, NULL) +- [x] 创建 SQL 迁移脚本 `docs/v1.0.2.1/migration.sql` +- [x] `odf_racks` 表新增 `rack_type`(INT, DEFAULT 0) +- [x] `odf_racks` 表新增 `left_ports_count`(INT, NULL) +- [x] `odf_racks` 表新增 `right_ports_count`(INT, NULL) ### Task 2: 后端模型更新 -- [ ] `server/ZR.Model/Business/OdfRacks.cs` 新增 `RackType`、`LeftPortsCount`、`RightPortsCount` 属性 -- [ ] `server/ZR.Model/Business/Dto/OdfRacksDto.cs` 的 `OdfRacksDto` 和 `OdfRacksExpertDto` 新增对应属性 +- [x] `server/ZR.Model/Business/OdfRacks.cs` 新增 `RackType`、`LeftPortsCount`、`RightPortsCount` 属性 +- [x] `server/ZR.Model/Business/Dto/OdfRacksDto.cs` 的 `OdfRacksDto` 和 `OdfRacksExpertDto` 新增对应属性 ### Task 3: 管理后台 - 机架表单增加类型选择 -- [ ] `server/ZR.Vue/src/components/business/OdfRackForm.vue` 新增机架类型选择器 -- [ ] 新增左侧端子数、右侧端子数输入框 -- [ ] 表单验证规则更新 -- [ ] 表单重置逻辑更新 +- [x] `server/ZR.Vue/src/components/business/OdfRackForm.vue` 新增机架类型选择器 +- [x] 新增左侧端子数、右侧端子数输入框 +- [x] 表单验证规则更新 +- [x] 表单重置逻辑更新 ### Task 4: 管理后台 - 机架列表显示类型 -- [ ] `server/ZR.Vue/src/views/business/OdfRacks.vue` 表格新增"机架类型"列 -- [ ] 使用标签样式区分 ODF机架 / 光交箱 +- [x] `server/ZR.Vue/src/views/business/OdfRacks.vue` 表格新增"机架类型"列 +- [x] 使用标签样式区分 ODF机架 / 光交箱 ### Task 5: uni-app 机架列表页显示类型 -- [ ] `odf-uniapp/pages/rack/index.vue` 机架卡片新增类型标签 -- [ ] 样式适配 +- [x] `odf-uniapp/pages/rack/index.vue` 机架卡片新增类型标签 +- [x] 样式适配 ### Task 6: uni-app 机架详情页 - 类型显示与光交箱双侧布局 -- [ ] `odf-uniapp/pages/rack-detail/index.vue` 显示机架类型 -- [ ] 光交箱类型时实现左右双栏布局(左侧光交箱端子,右侧 ODF 端子) -- [ ] 左右区域不同背景色(左侧浅蓝 #E3F2FD,右侧浅橙 #FFF3E0) -- [ ] 中间分隔线 -- [ ] 光交箱端子命名:行按 A/B/C... 命名,端点为 A-1、A-2 格式 -- [ ] ODF 类型保持现有布局不变 -- [ ] 传递 rackType 参数到详情页 +- [x] `odf-uniapp/pages/rack-detail/index.vue` 显示机架类型 +- [x] 光交箱类型时实现左右双栏布局(左侧光交箱端子,右侧 ODF 端子) +- [x] 左右区域不同背景色(左侧浅蓝 #E3F2FD,右侧浅橙 #FFF3E0) +- [x] 中间分隔线 +- [x] 光交箱端子命名:行按 A/B/C... 命名,端点为 A-1、A-2 格式 +- [x] ODF 类型保持现有布局不变 +- [x] 传递 rackType 参数到详情页 ### Task 7: 导入导出功能适配 -- [ ] `server/ZR.Model/Business/Dto/OdfPortsDto.cs` 导入/导出 DTO 新增机架类型相关字段 -- [ ] 导入逻辑适配光交箱端子命名格式(A-1、B-2 等) -- [ ] 导出逻辑适配光交箱端子命名格式 +- [x] `server/ZR.Model/Business/Dto/OdfPortsDto.cs` 导入/导出 DTO 新增机架类型相关字段 +- [x] 导入逻辑适配光交箱端子命名格式(A-1、B-2 等) +- [x] 导出逻辑适配光交箱端子命名格式 diff --git a/docs/v1.0.2.1/migration.sql b/docs/v1.0.2.1/migration.sql new file mode 100644 index 0000000..08c7628 --- /dev/null +++ b/docs/v1.0.2.1/migration.sql @@ -0,0 +1,6 @@ +-- ODF v1.0.2.1 数据库迁移脚本 +-- 新增光交箱机架类型支持 + +-- SQL Server 语法 +ALTER TABLE odf_racks ADD rack_type INT NOT NULL DEFAULT 0; +-- rack_type: 机架类型,0=ODF机架, 1=光交箱 diff --git a/odf-uniapp/pages/rack-detail/index.vue b/odf-uniapp/pages/rack-detail/index.vue index 5fda85f..529bcd7 100644 --- a/odf-uniapp/pages/rack-detail/index.vue +++ b/odf-uniapp/pages/rack-detail/index.vue @@ -17,9 +17,10 @@ - + {{ roomName }} + 类型:{{ rackType === 1 ? '光交箱' : 'ODF' }} @@ -44,30 +45,102 @@ {{ frame.name }} - - - - - {{ row.name }} - + + + + + @@ -93,12 +166,24 @@ const statusBarHeight = uni.getSystemInfoSync().statusBarHeight || 0 const rackId = ref('') const rackName = ref('') const roomName = ref('') +const rackType = ref(0) const frameList = ref([]) const loading = ref(false) const showPortEdit = ref(false) const currentPortId = ref('') let pendingPortId = '' +// 光交箱侧端子命名(左栏) +function getOpticalBoxPortName(rowIndex, portIndex) { + const rowLetter = String.fromCharCode(65 + rowIndex) // A=65, B=66... + return `${rowLetter}-${portIndex + 1}` // A-1, A-2, B-1, B-2... +} + +// ODF侧端子命名(右栏) +function getOdfPortName(rowIndex, portIndex) { + return `${rowIndex + 1}-${portIndex + 1}` // 1-1, 1-2, 2-1, 2-2... +} + async function loadRackDetail() { loading.value = true try { @@ -141,6 +226,9 @@ onLoad((options) => { if (options.roomName) { roomName.value = decodeURIComponent(options.roomName) } + if (options.rackType) { + rackType.value = parseInt(options.rackType) + } if (options.portId) { pendingPortId = options.portId } @@ -198,6 +286,9 @@ onLoad((options) => { } .room-name-bar { + display: flex; + align-items: center; + justify-content: space-between; padding: 8rpx 24rpx 0; } @@ -207,6 +298,11 @@ onLoad((options) => { color: #1A73EC; } +.rack-type-text { + font-size: 26rpx; + color: #fff; +} + .legend-bar { display: flex; align-items: center; @@ -269,6 +365,7 @@ onLoad((options) => { margin-bottom: 16rpx; } +/* === ODF 类型:保持现有布局 === */ .port-scroll { width: 100%; white-space: nowrap; @@ -331,4 +428,70 @@ onLoad((options) => { margin-top: 6rpx; text-align: center; } + +/* === 光交箱类型:双栏布局 === */ +.optical-box-wrapper { + display: inline-flex; + flex-direction: row; + min-width: 100%; +} + +.optical-left-col { + background-color: #FFC0CB; + padding: 16rpx; + border-radius: 8rpx 0 0 8rpx; +} + +.optical-left-col .optical-port-row { + background-color: #FFB6C1; + border-radius: 8rpx; + padding: 12rpx; + margin-bottom: 12rpx; +} + +.optical-right-col { + background-color: #E0F7FA; + padding: 16rpx; + border-radius: 0 8rpx 8rpx 0; +} + +.optical-right-col .optical-port-row { + background-color: #B3E5FC; + border-radius: 8rpx; + padding: 12rpx; + margin-bottom: 12rpx; +} + +.optical-divider { + width: 4rpx; + background-color: #999; + flex-shrink: 0; +} + +.optical-col-header { + padding: 0 0 12rpx; + text-align: center; +} + +.optical-col-title { + font-size: 26rpx; + font-weight: 600; + color: #333; +} + +.optical-port-row { + display: flex; + flex-direction: row; + align-items: center; +} + +.optical-row-name { + font-size: 24rpx; + color: #666; + font-weight: 600; + margin-right: 12rpx; + flex-shrink: 0; + width: 32rpx; + text-align: center; +} diff --git a/odf-uniapp/pages/rack/index.vue b/odf-uniapp/pages/rack/index.vue index b6761f7..3a44638 100644 --- a/odf-uniapp/pages/rack/index.vue +++ b/odf-uniapp/pages/rack/index.vue @@ -29,11 +29,13 @@ {{ item.rackName }} + 类型:{{ item.rackType === 1 ? '光交箱' : 'ODF' }} @@ -90,7 +92,10 @@ function goSearch() { function goDetail(item) { uni.navigateTo({ - url: '/pages/rack-detail/index?rackId=' + item.id + '&rackName=' + encodeURIComponent(item.rackName) + '&roomName=' + encodeURIComponent(roomName.value) + url: '/pages/rack-detail/index?rackId=' + item.id + + '&rackName=' + encodeURIComponent(item.rackName) + + '&roomName=' + encodeURIComponent(roomName.value) + + '&rackType=' + (item.rackType || 0) }) } @@ -201,6 +206,7 @@ onReachBottom(() => { .rack-card { display: flex; align-items: center; + justify-content: space-between; padding: 32rpx 24rpx; margin-bottom: 20rpx; background-color: #fff; @@ -208,9 +214,18 @@ onReachBottom(() => { box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.06); } +.rack-card-optical { + border-left: 6rpx solid #1A73EC; +} + .rack-name { font-size: 30rpx; font-weight: 500; color: #333; } + +.rack-type { + font-size: 26rpx; + color: #666; +} diff --git a/server/ZR.Admin.WebApi/Controllers/Business/OdfPortsController.cs b/server/ZR.Admin.WebApi/Controllers/Business/OdfPortsController.cs index 14e873e..4cd1635 100644 --- a/server/ZR.Admin.WebApi/Controllers/Business/OdfPortsController.cs +++ b/server/ZR.Admin.WebApi/Controllers/Business/OdfPortsController.cs @@ -631,6 +631,7 @@ namespace ZR.Admin.WebApi.Controllers.Business DeptId = dept.DeptId, FrameCount = 0, SequenceNumber = sequenceNumber, + RackType = excelItem.RackType, }; await _OdfRacksService.InsertReturnEntityAsync(rack); addRackCount++; @@ -658,6 +659,18 @@ namespace ZR.Admin.WebApi.Controllers.Business } //添加端口 var port = _OdfPortsService.AsQueryable().Where(it => it.FrameId == frame.Id && it.RowNumber == excelItem.RowNumber && it.PortNumber == excelItem.PortNumber).First(); + // 生成端口名称:光交箱用 A-1 格式,ODF用 1-1 格式 + string portName; + if (rack.RackType == 1) + { + // 光交箱:行号转字母 (1=A, 2=B, 3=C...) + string rowLetter = ((char)(64 + excelItem.RowNumber)).ToString(); + portName = $"{rowLetter}-{excelItem.PortNumber}"; + } + else + { + portName = excelItem.RowNumber + "-" + excelItem.PortNumber; + } string remarks = ""; if (!string.IsNullOrEmpty(excelItem.Remarks)) { @@ -694,7 +707,7 @@ namespace ZR.Admin.WebApi.Controllers.Business RackName = rack.RackName, RoomId = room.Id, RoomName = room.RoomName, - Name = excelItem.RowNumber + "-" + excelItem.PortNumber, + Name = portName, RowNumber = excelItem.RowNumber, PortNumber = excelItem.PortNumber, Status = excelItem.Status, diff --git a/server/ZR.Model/Business/Dto/OdfPortsDto.cs b/server/ZR.Model/Business/Dto/OdfPortsDto.cs index 935fc1a..ecfbfe8 100644 --- a/server/ZR.Model/Business/Dto/OdfPortsDto.cs +++ b/server/ZR.Model/Business/Dto/OdfPortsDto.cs @@ -149,6 +149,13 @@ namespace ZR.Model.Business.Dto [ExcelColumnName("机架名称")] public string RackName { get; set; } + /// + /// 机架类型:0=ODF机架, 1=光交箱 + /// + [ExcelColumn(Name = "机架类型")] + [ExcelColumnName("机架类型")] + public int RackType { get; set; } + [Required(ErrorMessage = "框ID不能为空")] [ExcelColumn(Name = "框ID")] [ExcelColumnName("框ID")] @@ -279,6 +286,13 @@ namespace ZR.Model.Business.Dto [ExcelColumnName("机架名称")] public string RackName { get; set; } + /// + /// 机架类型:0=ODF机架, 1=光交箱 + /// + [ExcelColumn(Name = "机架类型")] + [ExcelColumnName("机架类型")] + public int RackType { get; set; } + [Required(ErrorMessage = "框ID不能为空")] [ExcelColumn(Name = "框ID")] [ExcelColumnName("框ID")] @@ -394,6 +408,13 @@ namespace ZR.Model.Business.Dto [ExcelColumnName("机架名称")] public string RackName { get; set; } + /// + /// 机架类型:0=ODF机架, 1=光交箱 + /// + [ExcelColumn(Name = "机架类型")] + [ExcelColumnName("机架类型")] + public int RackType { get; set; } + [Required(ErrorMessage = "机框名称不能为空")] @@ -660,6 +681,13 @@ namespace ZR.Model.Business.Dto [ExcelColumnName("机架名称")] public string RackName { get; set; } + /// + /// 机架类型:0=ODF机架, 1=光交箱 + /// + [ExcelColumn(Name = "机架类型", Width = 12)] + [ExcelColumnName("机架类型")] + public int RackType { get; set; } + [ExcelColumn(Name = "机框名称", Width = 15)] [ExcelColumnName("机框名称")] public string FrameName { get; set; } diff --git a/server/ZR.Model/Business/Dto/OdfRacksDto.cs b/server/ZR.Model/Business/Dto/OdfRacksDto.cs index 1f79057..fe84316 100644 --- a/server/ZR.Model/Business/Dto/OdfRacksDto.cs +++ b/server/ZR.Model/Business/Dto/OdfRacksDto.cs @@ -48,6 +48,9 @@ namespace ZR.Model.Business.Dto [ExcelColumnName("UpdatedAt")] public DateTime? UpdatedAt { get; set; } + [ExcelColumn(Name = "机架类型")] + [ExcelColumnName("机架类型")] + public int RackType { get; set; } } diff --git a/server/ZR.Model/Business/OdfRacks.cs b/server/ZR.Model/Business/OdfRacks.cs index f58a41b..aaf4ab8 100644 --- a/server/ZR.Model/Business/OdfRacks.cs +++ b/server/ZR.Model/Business/OdfRacks.cs @@ -47,5 +47,11 @@ namespace ZR.Model.Business /// 部门 /// public long DeptId { get; set; } + + /// + /// 机架类型:0=ODF机架, 1=光交箱 + /// + public int RackType { get; set; } + } } \ No newline at end of file diff --git a/server/ZR.Service/Business/OdfPortsService.cs b/server/ZR.Service/Business/OdfPortsService.cs index a2e88a4..908d31f 100644 --- a/server/ZR.Service/Business/OdfPortsService.cs +++ b/server/ZR.Service/Business/OdfPortsService.cs @@ -208,9 +208,24 @@ namespace ZR.Service.Business }) .ToPage(parm); + // 查询所有相关机架的类型信息,用于填充 RackType + var rackIds = response.Result.Select(r => r.RackName).Distinct().ToList(); + var racks = Context.Queryable() + .Where(r => rackIds.Contains(r.RackName)) + .Select(r => new { r.Id, r.RackName, r.RackType }) + .ToList(); + var rackTypeDict = racks.GroupBy(r => r.RackName) + .ToDictionary(g => g.Key, g => g.First().RackType); + // 反向解析 Remarks 字段,拆分出 业务名称、1号端口、2号端口、3号端口 foreach (var item in response.Result) { + // 填充机架类型 + if (rackTypeDict.TryGetValue(item.RackName, out var rackType)) + { + item.RackType = rackType; + } + ParseRemarksToFields(item); } diff --git a/server/ZR.Vue/src/components/business/OdfRackForm.vue b/server/ZR.Vue/src/components/business/OdfRackForm.vue index 04769d1..a6227df 100644 --- a/server/ZR.Vue/src/components/business/OdfRackForm.vue +++ b/server/ZR.Vue/src/components/business/OdfRackForm.vue @@ -42,6 +42,15 @@ + + + + + + + + +