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 }}
-
+
+
+
+
+
+
+
-
- {{ port.tips }}
+ {{ rowIdx + 1 }}
+
+
+
+ {{ port.tips }}
+
+ {{ getOpticalBoxPortName(rowIdx, portIdx) }}
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ rowIdx + 1 }}
+
+
+
+ {{ port.tips }}
+
+ {{ getOdfPortName(rowIdx, portIdx) }}
+
- {{ port.name }}
-
-
+
+
+
+
+
+
+
+
+ {{ row.name }}
+
+
+
+ {{ port.tips }}
+
+ {{ port.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 @@
+
+
+
+
+
+
+
+
+
@@ -114,6 +123,7 @@ const form = ref({
sequenceNumber: null,
rackName: null,
frameCount: 9, // 默认固定9框
+ rackType: 0, // 0=ODF机架, 1=光交箱
createdAt: null,
updatedAt: null
})
@@ -122,7 +132,8 @@ const form = ref({
const rules = {
roomId: [{ required: true, message: '机房不能为空', trigger: 'change', type: 'number' }],
sequenceNumber: [{ required: true, message: '序号不能为空', trigger: 'blur', type: 'number' }],
- rackName: [{ required: true, message: 'ODF名称不能为空', trigger: 'blur' }]
+ rackName: [{ required: true, message: 'ODF名称不能为空', trigger: 'blur' }],
+ rackType: [{ required: true, message: '机架类型不能为空', trigger: 'change', type: 'number' }]
}
// 字典选项
@@ -146,6 +157,7 @@ function resetForm() {
sequenceNumber: null,
rackName: null,
frameCount: 9, // 默认固定9框
+ rackType: 0, // 默认ODF机架
createdAt: null,
updatedAt: null
}
diff --git a/server/ZR.Vue/src/views/business/OdfRacks.vue b/server/ZR.Vue/src/views/business/OdfRacks.vue
index 85dd8a7..bab6533 100644
--- a/server/ZR.Vue/src/views/business/OdfRacks.vue
+++ b/server/ZR.Vue/src/views/business/OdfRacks.vue
@@ -61,6 +61,12 @@
+
+
+ 光交箱
+ ODF机架
+
+
@@ -118,6 +124,7 @@ const columns = ref([
{ visible: true, align: 'center', type: 'dict', prop: 'roomId', label: '机房', dictType: 'sql_odf_room' },
{ visible: true, align: 'center', type: '', prop: 'sequenceNumber', label: '序号' },
{ visible: true, align: 'center', type: '', prop: 'rackName', label: 'ODF名称', showOverflowTooltip: true },
+ { visible: true, align: 'center', type: '', prop: 'rackType', label: '机架类型' },
{ visible: true, align: 'center', type: '', prop: 'frameCount', label: '框数量(固定9框)' },
{ visible: true, align: 'center', type: '', prop: 'createdAt', label: '创建时间', showOverflowTooltip: true },
{ visible: true, align: 'center', type: '', prop: 'updatedAt', label: '修改时间', showOverflowTooltip: true }