From d222978ae40f843511309e9186bd6c4d4a76d469 Mon Sep 17 00:00:00 2001 From: v6ole Date: Thu, 9 Apr 2026 11:33:04 +0800 Subject: [PATCH] =?UTF-8?q?```=20feat(import):=20=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E5=8C=BA=E5=9F=9F=E5=AD=97=E6=AE=B5=E6=94=AF=E6=8C=81=E5=92=8C?= =?UTF-8?q?=E6=94=B9=E8=BF=9B=E6=A8=A1=E6=9D=BF=E8=B7=AF=E5=BE=84=E5=A4=84?= =?UTF-8?q?=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 在OLT设备导入功能中添加region字段支持,从Excel模板读取区域信息 - 修复模板文件路径问题,使用相对路径动态构建模板文件路径 - 更新导入服务中的验证错误格式,包含行号和MAC地址信息 feat(check): 增强设备检测服务的端口信息同步 - 在CheckService中添加端口和OLT归属信息的同步逻辑 - 改进设备状态检查时的端口信息更新策略,避免用None覆盖现有值 - 扩展返回数据结构,包含端口ID、槽位号、端口号和OLT位置信息 feat(frontend): 添加设备列表刷新冷却机制和端口验证 - 实现设备状态刷新的60秒冷却时间限制,防止频繁操作 - 改进业务下发按钮的启用条件,同时支持port_id或slot_number+port_number组合 - 优化导入结果显示,显示具体的MAC地址信息 feat(olt): 添加重复MAC记录批量清除功能 - 新增批量清除重复MAC记录的功能,支持按OLT分组处理 - 实现SSH端口清除和自动忽略的批量操作流程 - 添加批量操作的进度提示和错误处理机制 ``` --- QUICKSTART.md | 2 + backend/app/api/v1/import_data.py | 2 +- backend/app/api/v1/olt.py | 12 +- backend/app/services/check_service.py | 34 +- backend/app/services/import_service.py | 4 +- backend/templates/OLT设备导入模板.xlsx | Bin 5158 -> 5282 bytes docs/现场ONU故障排查指南.md | 899 +++++++++++++++++++++++++ frontend/src/views/DeviceList.vue | 26 +- frontend/src/views/OltManage.vue | 52 ++ 9 files changed, 1016 insertions(+), 15 deletions(-) create mode 100644 docs/现场ONU故障排查指南.md diff --git a/QUICKSTART.md b/QUICKSTART.md index 474e829..928fcf7 100644 --- a/QUICKSTART.md +++ b/QUICKSTART.md @@ -35,3 +35,5 @@ cp backend/.env.example backend/.env ## 下一步开发 参考 `开发计划.md` 继续第一阶段第2周任务 + +1. 定期检测还需要加入环路检测功能,并且将环路的状态记录到数据库中,在OLT界面上显示 diff --git a/backend/app/api/v1/import_data.py b/backend/app/api/v1/import_data.py index a1d755e..eb7abcd 100644 --- a/backend/app/api/v1/import_data.py +++ b/backend/app/api/v1/import_data.py @@ -12,7 +12,7 @@ router = APIRouter(prefix="/api/import", tags=["数据导入"]) @router.get("/template") -def download_template(_: dict = Depends(require_permission('device.import'))): +def download_template(): """下载导入数据模板""" wb = Workbook() ws = wb.active diff --git a/backend/app/api/v1/olt.py b/backend/app/api/v1/olt.py index 85f2896..1683bc3 100644 --- a/backend/app/api/v1/olt.py +++ b/backend/app/api/v1/olt.py @@ -170,6 +170,7 @@ async def import_devices( username=str(row['用户名']).strip(), password=str(row['密码']).strip(), slot_command=str(row['槽位命令']).strip() if '槽位命令' in df.columns and str(row['槽位命令']) != 'nan' else 'display onu slot', + region=str(row['区域']).strip() if '区域' in df.columns and str(row['区域']) != 'nan' else '城区', location=str(row['安装位置']).strip() if '安装位置' in df.columns and str(row['安装位置']) != 'nan' else '', description=str(row['描述']).strip() if '描述' in df.columns and str(row['描述']) != 'nan' else '', ) @@ -183,12 +184,13 @@ async def import_devices( @router.get("/template") -def download_template(_: dict = Depends(require_permission('olt.manage'))): +def download_template(): from fastapi.responses import FileResponse - return FileResponse( - path="/home/v6ole/pyproject/H3ConuMS2/backend/templates/OLT设备导入模板.xlsx", - filename="OLT设备导入模板.xlsx" - ) + import os + # __file__ is at /app/api/v1/olt.py → go up 4 levels to reach + base = os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))) + path = os.path.join(base, "templates", "OLT设备导入模板.xlsx") + return FileResponse(path=path, filename="OLT设备导入模板.xlsx") @router.get("/duplicate-macs") diff --git a/backend/app/services/check_service.py b/backend/app/services/check_service.py index 8a89bf2..5ec7c8b 100644 --- a/backend/app/services/check_service.py +++ b/backend/app/services/check_service.py @@ -58,8 +58,17 @@ class CheckService: else: offline_count += 1 + # 有值时同步端口和 OLT 归属(不用 None 覆盖现有值) + update_fields = {"olt_id": olt_id} + if onu_info.slot_number is not None: + update_fields["slot_number"] = onu_info.slot_number + if onu_info.port_number is not None: + update_fields["port_number"] = onu_info.port_number + if onu_info.port_id: + update_fields["port_id"] = onu_info.port_id if onu_info.model and not onu_model: - self.db.query(ONUDevice).filter(ONUDevice.id == onu_id).update({"model": onu_info.model}) + update_fields["model"] = onu_info.model + self.db.query(ONUDevice).filter(ONUDevice.id == onu_id).update(update_fields) self.db.add(DeviceStatusHistory( onu_device_id=onu_id, @@ -116,6 +125,10 @@ class CheckService: distance_m = parse_distance(info.distance_str) if info.model and not device.model: device.model = info.model + # 同步端口信息到数据库 + device.slot_number = info.slot_number + device.port_number = info.port_number + device.port_id = info.port_id self.db.add(DeviceStatusHistory( onu_device_id=device.id, @@ -125,7 +138,17 @@ class CheckService: response_data=None, )) self.db.commit() - return {"status": status, "distance_m": distance_m, "model": device.model} + + olt_location = olt.location if olt else None + return { + "status": status, + "distance_m": distance_m, + "model": device.model, + "port_id": device.port_id, + "slot_number": device.slot_number, + "port_number": device.port_number, + "olt_location": olt_location, + } finally: ssh.close() @@ -177,6 +200,13 @@ class CheckService: onu_id = onu.id else: onu_id, onu_model = entry + # 同步端口和 OLT 归属(扫描结果以当前 OLT 为准) + self.db.query(ONUDevice).filter(ONUDevice.id == onu_id).update({ + "olt_id": olt_id, + "slot_number": onu_info.slot_number, + "port_number": onu_info.port_number, + "port_id": onu_info.port_id, + }) if onu_info.model and not onu_model: self.db.query(ONUDevice).filter(ONUDevice.id == onu_id).update({"model": onu_info.model}) diff --git a/backend/app/services/import_service.py b/backend/app/services/import_service.py index d2849f0..0f9e765 100644 --- a/backend/app/services/import_service.py +++ b/backend/app/services/import_service.py @@ -53,7 +53,7 @@ class ImportService: for idx, record in enumerate(records): mac = record.get('mac_address', '').strip() if not mac: - invalid.append({'record': record, 'error': f'第{idx+2}行: MAC地址缺失'}) + invalid.append({'row': idx + 2, 'mac': mac, 'reason': 'MAC地址缺失'}) continue # 标准化MAC地址:统一使用横杠分隔小写格式 @@ -63,7 +63,7 @@ class ImportService: # 验证基本格式:12个十六进制字符(可能有分隔符) hex_chars = mac_clean.replace('-', '') if len(hex_chars) != 12 or not all(c in '0123456789ABCDEF' for c in hex_chars): - invalid.append({'record': record, 'error': f'第{idx+2}行: MAC地址格式错误 "{mac}"'}) + invalid.append({'row': idx + 2, 'mac': mac, 'reason': f'MAC地址格式错误 "{mac}"'}) continue # 转换为标准格式 34dc-99c8-56e0(小写4位分组) diff --git a/backend/templates/OLT设备导入模板.xlsx b/backend/templates/OLT设备导入模板.xlsx index 740b2f62abb20cd3f9bc17d04cd7c6bf5dc8d0ab..9155f3f312fb44925aa2db2d9e51709b59d474de 100644 GIT binary patch delta 2302 zcmZ8j2{hE}8~%-LCd*i6Y=hiNgoZK3z6@zJbS-7FXU!x_mV~$rV@VhjgMS#=k}aYz zp>9dCT_z1#vZh95DM@6)Rp;E!@&C?u&ikG3JnwnW_rA}0zUTIlQe`3AliWOT001C> zEhQyW=m}&uxMm!`5uQ1$N|G}(8b#Tdux)xptxLp5B{tXvGZd& zBA5a_%}G%o1gD?Vm`tx*pbdF;K#!YpDa4pmoYp(MS=F*SsGNT@MA-C0+4v#Iz{t$? z>{GWq_#`|J+-ufw{+N>bm*E`6?enZ=xHJ*taTOYEX`3TnAAVEH+M6`;L>$!tDLXmM zixJRp*m=f=)?W(!Gu%R`!xnmZz{-H|dHcuL{1Ie85q=F(;of7Z&pT1y_m;7_?aAqO zZtrZJsUc5a{9fp~&nb;Mwsv*;%;**Gh;Q;T?mVygVkdDN-aXHE&2yz665#^?x)K1u z0O0;MNR1ErvmsLfi;xs90C0lP^da)3T$iA6lvMJLWwu3|g0TWr8C+Xlwtzc4cth`cYzT&*_P0eD+tKY9Ws?>)f z?Zoe77nmQKQ)!QO)~{sm~G|P6be_r1A`uoMFcT)q0PZ{0I{3j`r zL75MQbGIFMK6cE7*Dv*Pz*AkuF`^7fzkj3t7B6)ndaWvL@)^1!)rx25Y)+I>Dkp?+ z@9<>smP#|$QPPIECP7wRmkEp@T`I|YIC=#65vpxaSwnPqNnMuB0KoxUaIsw+4=@4R zfCHdEmg2thn3N!WpfHBj{}7biVz-XAALBl`RIU3MT#as&Cxgr z`3^6tQz1IB;dty)(GdkjF!Wm7?&k8UH!;F*8jU6xe=?IoTIp{dg@V>4HuyxpwIIlJ z>J?J``tbbu6r}1fDRfi%8WE7OCB8UHv^BRm`SKO`6!x?ZSTz z3%^}Gl*qdU0ZDE_Ocm!sO}cT|V~fbx#1UNa5!d}5Ts?X6-3whU<0NcW*-4ght2&{s z+8jqm3UNoZwDi7PKJZ$r(RWf(PjY;sx2{z?2ulkmOS>Q9FIg9g){@`0O5XwPWpJBK zLu}_%m=efD}3-R)wPws%q`WW8T;7OLqK{gt&i@v$p2J)e>p{ z+E^mngSn!2X+`%OD-TvdJz;5uf~d6xMq+Mn!Euwbc-{|FRk@aFLzV(wiv*ohBYu|E zP(1lWk6#XsjFMi~z9$*&**a4kb)k7yB|W^rxu-KLku>1(KQYsU^G_L`rn;(HG9zN` z8$Ee2lq}pqAEga%74ohRQ-@|u3YOBtcxaXh8ln3qBJ_~%Lt%E~FYGxht&Uh@{XTCx z6I4;6xu}iQNcL-C&@wd}e)mHZ z@!lb(+-N9%Au&iCV+tf02?v_6Ui3WJ_ujo)Z%JV6ZPB@eTqD)ha>iJ9$ab4Uvazr8 zeNVdZ+M>K2RdVgEd0`_G8^gYnNp78(UJNEN4xD!jCgeYI40=%k_AjnWyBej^>q>3V zcgVR}3!+y9Zoo2>-6wc_m=8Qd^W~h*oVwx=tQv&BKmyl&UD`~2b!(n|fsZkZ-ZoWz zmG+n08c=0Zle-e(dq=l`TC~=A4hMD?XAL!fIb+i%0AX~8p8F)4K8M0`NwL9cz#K_e zf;~^_Jx^iJp^i2;NjjC$wR%I2%AZcxVRtcmmfkTjOctiEK2;(~hs;&ulH1;OH@TBf z@E_sG%{J$`HcCYILQLG!2Y)%TPin-$TxB@a~pvb@24gV5-4p|znh z$tw*xX+tYwv+};rG8>1NJMA8bz+FG^9=Bn=(V#2v2wZgSf4Ghfnu=ykim1;Y@0+n# z_4dv*a0q+)LQ&860<{vDQ<84T9WZlCZ1%Wb)eoZE&xXL8%#edAZj3?1rASD(>6`26 zN+-RN>q!ldSH<}J8+A`4EvrckpN)rL5JL}=FcO)N0P6x(uZgUX(^g`Ny! zJ@Qef^Xa`UwNgs+Ixl6e;S9czrSDxHHZNCy)TOoY4pA$6wm9@j%!iq*3ulU*veCx4 zNRhISC3*{D20w}r%6+JK#&xeiBl`o4M^d!9PdCdJ%I(>DwVmT8=*u^Pq__{i0 zq+)B%A&-gG#V_0$C@}QRFnA9(#QV2Vy5MNcb+>4zg)7qzXm+uoO5q%h^(ya^Px(;G zyg3n%4m_wcJ#w0s48M&59cQwgfZB|2c z*>3?7005y~U-93H1I^GtFxg+}f52ow_i2VO#MwPV-jyxk|KPhZ4n&6nKxBY=L{O-| zS5T0@T4Z2=?MV<9ocp(7aaGt~P+GeS-q>XcVo$QX2<@@3ER6{h{8e)bCd0)eO5?x| K^I41i1pW)Wg#JVT delta 2211 zcmZXWc|6oz7r+we{u0kT-?OoIdtIm#!R3$ zC85oqJL|kC`rfCoW2+b!_aW-R1R7`USp21N-!st#rL*_D)cmb|@F$F^#Y(4p9`7qL+~Z}LsJd7d!<*`_rnyyb(RO5rikD+_3Z(U?9I33K z4;Jqc)lnJ2%TBGY)Gft*v*`}(obEq~Dd(a?Bl=_3IDWgnL#ji%GzCkpV{ z)6l$IncpXlF&S$Q)RAJg*E-uQ(5-v!6C37`1Ih&-XH_4u~Vv0f25wMDct@hD3(ksosRC}%jIfTUvuW^ekbmQf)Vm%U^T z*H1A!_Vk!TlqE z$Y(s65>O7MfJ11>!SfoYy1O2rA3fKgScilma>qh1Lq1n%oY$3qO`cdL4;idKEf>;k zDD2baBs4~Zt+N|ZH&k^SSbUcRkpfl2X;so$bA&h6tS=xp*09VH_ep4|>PDaZhS;if z)n)E5^Pmwp(t(eJ)_Kf;ml85~H2bNVtup3SddRqe)>c~Jw{H8Z%Jy!Uv6K2aA03;D z4JHZ+ZZmV^&j&!ilQuYGW%7InsQtVk5dY5e3-VC)^AGlL^-~G*^ytU--s)9@ZHll2 z@_#rrcmVSwDRP&8R`p%9m&^KOJ;p3+0oFan0KT5QkIjJGeka2-s_&(Ut^y;*1(Y)@4&YSC(svNnxSqpnM++`-8$zEpyD z>9JLvkQuPWRuN9*N1*yDNpCFlNyo97H9Jdov()DKuLfBRY`1eP^TQ+Wu~t5(DvsfA z#=HeyRVrIYP*WqVC905gll7(7m74P|M#`2E;dJ?M#kKVKeGvlzlN)w*H;G1?>yo^$ zE^bs2Cj_FnOk20!{2@w21zHDyX0Bd$Tgr23J)1Ev8suJ82~ z<(EpXx!7=T=5E0T<_|ioyqZ_(TvY+u6*AY#FC$fkt zS55H=Kptke4S8e0#W8Ynb$)R@(qmKpu2p(ujbOFHK}EQki1WE6aSJ;Y=bZNTY_p%d8S{-U1XhMBz>{~b!hKj#i_b^auELYH7)Lpy*`hjx1 z!@w$6ZW-%8YAB9_;%cCJAI!Z9NgkOYw=45gfXmcu%;h8-<7vCY-2*1l}!YOUi{V+bU{( zlVMVU3A78rS5S$e%Ge5&9zg9Q`xEKt^t=}@=s{$&xm@whv$=|SXuYLeZEGLpyguKy zME&vQnTX}bM*dB@ip$7Lb=dQt*(B`A-`$LY4MBF?Tqr1L3f>(6cp;sCYB%s$==iV6 z9-%+}UBBeunv%Qm`F94|3DK_>2~GgQ;S#^Lr^6B2_?`50+1J&>PgP~Nw=xHF2t)q) zG5lYc|96ZrFb_u_+BGGEOryQrSy40°C) + - 湿度是否合适(30%-70%) + - 通风是否良好 + - 是否有灰尘堆积 + +#### 步骤2:初步外观检查 +1. **电源检查** + - 电源指示灯状态 + - 电源适配器是否发热 + - 电源线连接是否牢固 + - 用万用表测量输出电压(应为12V±5%) + +2. **指示灯状态分析** + +| 指示灯 | 正常状态 | 故障状态 | 可能原因 | +|-------|---------|---------|---------| +| PWR | 常亮 | 不亮/闪烁 | 电源故障 | +| PON | 常亮 | 闪烁/不亮 | 光路问题 | +| LOS | 不亮 | 常亮/闪烁 | 光信号丢失 | +| LAN | 常亮/闪烁 | 不亮 | 网线/配置问题 | + +3. **物理连接检查** + - 光纤连接器是否插紧 + - 网线连接是否牢固 + - 所有接口是否有松动 + - 检查线缆是否有破损 + +#### 步骤3:光路测试 +1. **使用红光笔检查** + - 在OLT端发射红光 + - 在ONU端观察是否有红光 + - 确认光纤通路是否完整 + +2. **光功率测量** + - 连接光功率计到ONU接收端 + - 记录接收光功率 + - 参考标准值:-8dBm ~ -27dBm + + **光功率判断标准:** + - 优于-8dBm:光功率过强,可能损坏设备 + - -8dBm ~ -27dBm:正常范围 + - -27dBm ~ -30dBm:临界值,可能不稳定 + - 低于-30dBm:光功率不足 + +3. **光纤清洁** + - 使用专用清洁笔清洁光纤端面 + - 检查连接器是否有划伤 + - 重新插拔光纤连接器 + +#### 步骤4:网络连接测试 +1. **本地连接测试** + - 笔记本电脑直连ONU LAN口 + - 设置静态IP(如192.168.1.100) + - Ping ONU管理地址(通常192.168.1.1) + +2. **上网测试** + - 如果能够Ping通ONU,尝试访问网页 + - 测试DNS解析(nslookup www.baidu.com) + - 测试下载速度 + +3. **抓包分析(如需要)** + - 使用Wireshark抓取网络包 + - 分析DHCP、PPPoE等协议交互 + - 查找异常流量 + +#### 步骤5:设备诊断 +1. **登录ONU管理界面** + - 默认地址:192.168.1.1 + - 用户名/密码:admin/admin 或 查看设备标签 + - 检查设备状态信息 + +2. **查看关键信息** + - 光模块信息(发射/接收功率) + - 温度信息 + - 运行时间 + - 错误日志 + +3. **配置检查** + - VLAN配置是否正确 + - 业务VLAN是否匹配 + - MAC地址学习情况 + +#### 步骤6:OLT端检查 +1. **通过H3ConuMS2系统检查** + - 登录系统查看设备状态 + - 检查OLT端口状态 + - 查看ONU注册状态 + +2. **SSH登录OLT(如需要)** + ```bash + # 连接OLT + ssh admin@olt_ip + + # 查看ONU状态 + display onu slot X/X + + # 查看端口信息 + display interface gigabitethernet X/X/X + + # 查看告警信息 + display alarm active + ``` + +3. **OLT端诊断命令** + - `display ont info` - 查看ONU信息 + - `display ont optical-info` - 查看光信息 + - `display ont version` - 查看版本信息 + - `display ont statistic` - 查看统计信息 + +### 2.2 故障决策树 + +```mermaid +graph TD + A[开始排查] --> B{电源指示灯亮?} + B -->|否| C[检查电源] + B -->|是| D{PON指示灯状态?} + + D -->|不亮| E[检查光路] + D -->|闪烁| F[检查注册状态] + D -->|常亮| G{LAN指示灯状态?} + + G -->|不亮| H[检查网线/配置] + G -->|正常| I[测试网络连接] + + C --> J[更换电源适配器] + J --> K{问题解决?} + K -->|是| L[记录并结束] + K -->|否| M[更换ONU设备] + + E --> N[测试光功率] + N --> O{光功率正常?} + O -->|否| P[检查光纤/清洁连接器] + O -->|是| Q[检查OLT配置] + + F --> R[检查ONU注册] + R --> S{注册成功?} + S -->|否| T[检查SN/MAC绑定] + S -->|是| U[检查业务配置] + + H --> V[更换网线] + V --> W{问题解决?} + W -->|是| X[记录并结束] + W -->|否| Y[检查交换机配置] + + I --> Z[Ping测试] + Z --> AA{能Ping通?} + AA -->|否| BB[检查IP配置] + AA -->|是| CC[测试上网] + + CC --> DD{能上网?} + DD -->|否| EE[检查PPPoE/DHCP] + DD -->|是| FF[故障排除完成] +``` + +--- + +## 第三章:常见故障处理 + +### 3.1 电源类故障 + +#### 故障现象 +- 所有指示灯不亮 +- 设备无任何反应 +- 电源适配器发热异常 + +#### 处理步骤 +1. **检查电源插座** + - 使用其他设备测试插座 + - 检查插座电压(220V±10%) + +2. **检查电源适配器** + - 测量输出电压(应为12V) + - 检查输出电流(应≥1A) + - 观察指示灯状态 + +3. **检查设备电源接口** + - 检查接口是否松动 + - 检查内部保险丝 + - 检查电源电路 + +#### 解决方案 +1. **更换电源适配器** +2. **更换电源线** +3. **更换ONU设备** + +### 3.2 光路类故障 + +#### 故障现象 +- PON指示灯不亮或闪烁 +- LOS指示灯亮 +- 光功率异常 + +#### 处理步骤 +1. **检查光纤连接** + - 确认光纤类型(单模/多模) + - 检查连接器类型(SC/LC) + - 清洁光纤端面 + +2. **测量光功率** + - 在ONU端测量接收功率 + - 在分光器端测量输出功率 + - 计算链路衰减 + +3. **检查光纤质量** + - 检查光纤是否有弯折 + - 检查接头是否有损伤 + - 检查光纤长度是否超限 + +#### 解决方案 +1. **重新制作光纤接头** +2. **更换光纤跳线** +3. **调整光功率(增加衰减器)** +4. **重新熔接光纤** + +### 3.3 注册类故障 + +#### 故障现象 +- PON指示灯闪烁 +- OLT无法发现ONU +- 注册超时 + +#### 处理步骤 +1. **检查SN/MAC地址** + - 核对ONU标签上的SN + - 检查OLT端SN绑定 + - 检查MAC地址过滤 + +2. **检查OLT配置** + - 检查端口使能状态 + - 检查ONU模板 + - 检查带宽配置 + +3. **检查距离限制** + - 确认光纤长度 + - 检查光功率预算 + - 确认分光比 + +#### 解决方案 +1. **重新配置SN/MAC** +2. **重启ONU设备** +3. **重置ONU到出厂设置** +4. **修改OLT配置** + +### 3.4 业务类故障 + +#### 故障现象 +- PON灯常亮但无法上网 +- 能获取IP但无法访问外网 +- 网速慢或不稳定 + +#### 处理步骤 +1. **检查VLAN配置** + - 检查业务VLAN ID + - 检查VLAN透传 + - 检查优先级标记 + +2. **检查IP配置** + - 检查DHCP获取情况 + - 检查PPPoE拨号 + - 检查静态IP配置 + +3. **检查路由配置** + - 检查默认网关 + - 检查DNS服务器 + - 检查路由表 + +#### 解决方案 +1. **重新配置VLAN** +2. **重启网络服务** +3. **更新设备固件** +4. **优化网络配置** + +### 3.5 性能类故障 + +#### 故障现象 +- 网速达不到标称值 +- 延迟高、抖动大 +- 频繁掉线 + +#### 处理步骤 +1. **进行网络测试** + - 测试上下行带宽 + - 测试网络延迟 + - 测试丢包率 + +2. **检查设备负载** + - 检查CPU使用率 + - 检查内存使用率 + - 检查温度状态 + +3. **检查干扰因素** + - 检查同频干扰 + - 检查电源干扰 + - 检查环境干扰 + +#### 解决方案 +1. **优化设备位置** +2. **更换更高性能设备** +3. **调整信道/频率** +4. **增加散热措施** + +--- + +## 第四章:记录与报告 + +### 4.1 故障记录规范 + +#### 基本信息 +```yaml +故障单号: [系统生成] +报告人: [维护人员姓名] +报告时间: [YYYY-MM-DD HH:MM] +故障地点: [详细地址] +联系人: [用户姓名电话] +``` + +#### 故障信息 +```yaml +设备信息: + - 设备型号: [H3C XXX] + - 设备SN: [XXXXXXXX] + - MAC地址: [XX:XX:XX:XX:XX:XX] + - 安装日期: [YYYY-MM-DD] + +故障现象: + - 用户描述: [用户原话] + - 实际现象: [现场观察] + - 影响范围: [单用户/多用户] + +处理过程: + - 到达时间: [HH:MM] + - 开始处理: [HH:MM] + - 解决时间: [HH:MM] + - 总耗时: [X小时X分钟] +``` + +#### 技术数据 +```yaml +测试数据: + - 光功率: [接收: -XX dBm, 发射: -XX dBm] + - 网络测试: [下载: XX Mbps, 上传: XX Mbps] + - 延迟测试: [平均: XX ms, 最大: XX ms] + - 丢包率: [X%] + +更换部件: + - 部件名称: [如: 电源适配器] + - 部件型号: [如: H3C-12V-1A] + - 更换原因: [如: 输出电压不稳定] + - 旧件SN: [XXXXXXXX] + - 新件SN: [XXXXXXXX] +``` + +### 4.2 H3ConuMS2系统更新 + +#### 设备状态更新 +1. **登录系统** + - 进入设备管理界面 + - 找到故障设备 + +2. **更新信息** + - 更新设备状态(在线/离线) + - 更新最后检查时间 + - 更新维护记录 + +3. **添加备注** + - 描述故障现象 + - 记录处理过程 + - 添加技术数据 + +#### 生成报告 +1. **导出数据** + - 导出设备历史状态 + - 导出故障处理记录 + - 导出性能测试数据 + +2. **生成报告** + - 使用系统报告功能 + - 选择报告模板 + - 自定义报告内容 + +3. **提交报告** + - 提交给上级主管 + - 发送给用户确认 + - 归档到知识库 + +### 4.3 知识积累 + +#### 故障案例库 +1. **记录典型案例** + - 罕见故障现象 + - 复杂处理过程 + - 创新解决方案 + +2. **分析故障原因** + - 根本原因分析 + - 预防措施建议 + - 改进方案设计 + +3. **分享经验** + - 团队内部分享 + - 编写技术文档 + - 培训新员工 + +#### 持续改进 +1. **统计分析** + - 统计故障类型分布 + - 分析故障发生频率 + - 计算平均修复时间 + +2. **优化流程** + - 根据数据优化排查步骤 + - 改进工具配置 + - 更新技术标准 + +3. **预防维护** + - 制定定期检查计划 + - 预测性维护 + - 设备生命周期管理 + +--- + +## 第五章:预防措施 + +### 5.1 定期维护计划 + +#### 月度检查 +1. **设备清洁** + - 清洁设备表面灰尘 + - 清洁散热孔 + - 检查风扇运转 + +2. **连接检查** + - 检查所有连接器 + - 检查线缆状态 + - 检查标签完整性 + +3. **性能测试** + - 测试网络速度 + - 测试光功率 + - 测试设备温度 + +#### 季度检查 +1. **全面诊断** + - 完整的功能测试 + - 详细的性能测试 + - 安全漏洞扫描 + +2. **配置备份** + - 备份设备配置 + - 备份系统数据 + - 验证备份完整性 + +3. **固件更新** + - 检查固件版本 + - 评估更新必要性 + - 制定更新计划 + +#### 年度检查 +1. **设备评估** + - 评估设备性能 + - 预测剩余寿命 + - 制定更换计划 + +2. **环境评估** + - 评估安装环境 + - 检查配套设施 + - 提出改进建议 + +3. **文档更新** + - 更新技术文档 + - 更新操作手册 + - 更新维护记录 + +### 5.2 设备安装规范 + +#### 环境要求 +1. **温度控制** + - 工作温度:0°C ~ 40°C + - 存储温度:-40°C ~ 70°C + - 避免阳光直射 + +2. **湿度控制** + - 工作湿度:10% ~ 90% RH + - 避免冷凝 + - 保持通风 + +3. **电源要求** + - 稳定电压:220V ±10% + - 可靠接地 + - 防雷保护 + +#### 安装规范 +1. **设备固定** + - 使用合适支架 + - 保持水平安装 + - 预留散热空间 + +2. **线缆管理** + - 分类绑扎线缆 + - 预留适当余量 + - 避免弯折过度 + +3. **标签标识** + - 清晰设备标签 + - 完整线缆标签 + - 准确位置标签 + +### 5.3 备件管理 + +#### 备件库存 +1. **常用备件** + - ONU设备:按设备总数10%储备 + - 电源适配器:按设备总数15%储备 + - 光纤跳线:按设备总数20%储备 + - 网线:按需储备 + +2. **关键备件** + - 光模块 + - 分光器 + - 交换机 + +3. **工具备件** + - 测试工具 + - 维修工具 + - 安全装备 + +#### 库存管理 +1. **入库管理** + - 记录备件信息 + - 测试备件功能 + - 分类存放 + +2. **出库管理** + - 记录领用信息 + - 跟踪使用情况 + - 及时补充库存 + +3. **定期检查** + - 检查备件状态 + - 测试备件功能 + - 更新库存记录 + +### 5.4 培训计划 + +#### 新员工培训 +1. **基础培训(第1周)** + - 公司制度介绍 + - 安全规范培训 + - 工具使用培训 + +2. **技术培训(第2-3周)** + - 网络基础知识 + - 光纤通信原理 + - 设备操作培训 + +3. **实操培训(第4周)** + - 跟随老员工现场学习 + - 模拟故障处理 + - 独立完成简单任务 + +#### 在职培训 +1. **月度培训** + - 新技术介绍 + - 案例分享 + - 经验交流 + +2. **季度培训** + - 技能考核 + - 应急演练 + - 安全培训 + +3. **年度培训** + - 技术更新培训 + - 认证考试 + - 职业发展规划 + +--- + +## 第六章:应急处理 + +### 6.1 紧急情况处理 + +#### 重大故障 +1. **大面积断网** + - 立即上报主管 + - 启动应急预案 + - 协调多方资源 + +2. **设备损坏** + - 评估影响范围 + - 准备替代方案 + - 紧急采购设备 + +3. **安全事故** + - 确保人员安全 + - 保护现场证据 + - 按规定报告 + +#### 沟通协调 +1. **内部沟通** + - 建立应急联络群 + - 定期更新进展 + - 协调技术支持 + +2. **用户沟通** + - 及时通知用户 + - 说明处理进展 + - 提供临时方案 + +3. **供应商沟通** + - 联系技术支持 + - 协调备件供应 + - 请求现场支持 + +### 6.2 应急预案 + +#### 预案制定 +1. **风险评估** + - 识别潜在风险 + - 评估影响程度 + - 制定应对措施 + +2. **资源准备** + - 准备应急设备 + - 准备应急人员 + - 准备应急资金 + +3. **流程设计** + - 设计处理流程 + - 明确责任分工 + - 设定时间要求 + +#### 预案演练 +1. **桌面演练** + - 模拟故障场景 + - 讨论处理方案 + - 优化处理流程 + +2. **实战演练** + - 模拟真实故障 + - 实际处理操作 + - 评估处理效果 + +3. **演练总结** + - 分析演练过程 + - 发现存在问题 + - 提出改进建议 + +--- + +## 第七章:附录 + +### 7.1 常用命令参考 + +#### OLT常用命令 +```bash +# 查看ONU状态 +display onu slot X/X + +# 查看光信息 +display ont optical-info X/X/X X + +# 查看版本信息 +display ont version X/X/X X + +# 查看统计信息 +display ont statistic X/X/X X + +# 重启ONU +ont reboot X/X/X X + +# 查看告警 +display alarm active + +# 查看端口状态 +display interface gigabitethernet X/X/X +``` + +#### ONU管理命令 +```bash +# 登录ONU(Telnet/SSH) +telnet 192.168.1.1 + +# 查看系统信息 +show system + +# 查看接口状态 +show interface + +# 查看光模块信息 +show optical-module + +# 查看日志 +show log + +# 重启设备 +reboot + +# 恢复出厂设置 +factory-reset +``` + +### 7.2 技术参数参考 + +#### 光功率标准 +| 项目 | 标准值 | 警告值 | 危险值 | +|------|--------|--------|--------| +| 接收光功率 | -8 ~ -27 dBm | -27 ~ -30 dBm | < -30 dBm | +| 发射光功率 | 0 ~ +5 dBm | +5 ~ +7 dBm | > +7 dBm | +| 链路衰减 | < 25 dB | 25 ~ 28 dB | > 28 dB | + +#### 温度标准 +| 项目 | 正常范围 | 警告范围 | 危险范围 | +|------|----------|----------|----------| +| 工作温度 | 0°C ~ 40°C | 40°C ~ 50°C | > 50°C | +| 存储温度 | -40°C ~ 70°C | - | - | +| 表面温度 | < 60°C | 60°C ~ 70°C | > 70°C | + +### 7.3 联系信息 + +#### 内部联系人 +| 部门 | 联系人 | 电话 | 职责 | +|------|--------|------|------| +| 技术支持 | 张三 | 138XXXXXXX | 技术指导 | +| 备件管理 | 李四 | 139XXXXXXX | 备件供应 | +| 项目管理 | 王五 | 137XXXXXXX | 项目协调 | + +#### 供应商联系人 +| 供应商 | 产品 | 联系人 | 电话 | 支持时间 | +|--------|------|--------|------|----------| +| H3C | OLT设备 | 技术支持 | 400-XXXX | 7x24 | +| 华为 | ONU设备 | 技术支持 | 400-XXXX | 7x24 | +| 中兴 | 光模块 | 技术支持 | 400-XXXX | 工作日 | + +### 7.4 模板下载 + +#### 故障报告模板 +[下载链接:故障报告模板.docx] + +#### 维护记录模板 +[下载链接:维护记录模板.xlsx] + +#### 备件清单模板 +[下载链接:备件清单模板.xlsx] + +--- + +## 第八章:更新记录 + +| 版本 | 更新日期 | 更新内容 | 更新人 | +|------|----------|----------|--------| +| v1.0 | 2026-04-08 | 初始版本创建 | 系统团队 | +| v1.1 | - | 计划更新:添加更多案例 | - | +| v1.2 | - | 计划更新:优化流程图 | - | + +--- + +## 使用说明 + +1. **新员工必读**:入职后一周内完成学习 +2. **现场携带**:建议打印关键章节随身携带 +3. **定期复习**:每季度复习一次 +4. **反馈建议**:发现不足及时反馈 +5. **持续改进**:根据实际情况更新内容 + +## 版权声明 + +本指南版权归H3ConuMS2项目组所有,未经许可不得外传。 + +--- + +**祝您工作顺利,排查无忧!** + +如有任何问题或建议,请联系: +- 邮箱:support@h3conums2.com +- 电话:400-XXX-XXXX +- 内部通讯:企业微信/钉钉群 \ No newline at end of file diff --git a/frontend/src/views/DeviceList.vue b/frontend/src/views/DeviceList.vue index f92e54c..25b309b 100644 --- a/frontend/src/views/DeviceList.vue +++ b/frontend/src/views/DeviceList.vue @@ -10,8 +10,8 @@ 清空状态 - - {{ refreshing ? '更新中…' : '全部更新' }} + + {{ refreshing ? '更新中…' : refreshCooldown > 0 ? `冷却 ${refreshCooldown}s` : '全部更新' }} 数据导入 @@ -203,7 +203,7 @@ v-if="can('device.edit')" class="card-action-btn card-action-btn--primary" @click.stop="openProvisionFromCard(device)" - :disabled="device.status !== 'online' || !device.port_id" + :disabled="device.status !== 'online' || (!device.port_id && !(device.slot_number && device.port_number))" > @@ -347,7 +347,7 @@ 更新 更换 编辑 - + 业务下发 关闭 @@ -480,7 +480,7 @@ save force />
- 第 {{ f.row }} 行:{{ f.reason }} + 第 {{ f.row }} 行:{{ f.reason }}
@@ -521,6 +521,8 @@ const provisionVisible = ref(false) const provisionLoading = ref(false) const provisionResult = ref(null) const refreshing = ref(false) +const refreshCooldown = ref(0) +let cooldownTimer = null const clearing = ref(false) const editVisible = ref(false) @@ -636,6 +638,7 @@ const clearStatus = async () => { } const refreshAllStatus = async () => { + if (refreshCooldown.value > 0) return refreshing.value = true checkResultVisible.value = true checkInProgress.value = true @@ -654,6 +657,15 @@ const refreshAllStatus = async () => { } finally { checkInProgress.value = false refreshing.value = false + clearInterval(cooldownTimer) + refreshCooldown.value = 60 + cooldownTimer = setInterval(() => { + refreshCooldown.value-- + if (refreshCooldown.value <= 0) { + clearInterval(cooldownTimer) + refreshCooldown.value = 0 + } + }, 1000) } } @@ -691,6 +703,10 @@ const doRefreshDevice = async () => { selectedDevice.value.status = data.status selectedDevice.value.distance_m = data.distance_m if (data.model) selectedDevice.value.model = data.model + if (data.port_id !== undefined) selectedDevice.value.port_id = data.port_id + if (data.slot_number !== undefined) selectedDevice.value.slot_number = data.slot_number + if (data.port_number !== undefined) selectedDevice.value.port_number = data.port_number + if (data.olt_location !== undefined) selectedDevice.value.olt_location = data.olt_location ElMessage.success(`更新完成:${data.status === 'online' ? '在线' : '离线'}${data.distance_m ? ',距离 ' + formatDistance(data.distance_m) : ''}`) loadDevices() } catch (error) { diff --git a/frontend/src/views/OltManage.vue b/frontend/src/views/OltManage.vue index 05d88ed..aec78cf 100644 --- a/frontend/src/views/OltManage.vue +++ b/frontend/src/views/OltManage.vue @@ -454,6 +454,7 @@ @@ -533,6 +534,7 @@ const scanResult = ref({ total: 0, new: 0, devices: [], duplicates: [] }) const dupList = ref([]) const duplicateCount = ref(0) const clearingPort = ref('') +const batchClearing = ref(false) const newDevList = ref([]) const newDeviceCount = ref(0) const newDevVisible = ref(false) @@ -766,6 +768,56 @@ const deleteDupRecord = async (id) => { } } +const batchClearAll = async () => { + const total = dupList.value.length + if (total === 0) return + try { + await ElMessageBox.confirm( + `将对 ${total} 条重复 MAC 记录的所有离线端口执行 SSH 清除,然后自动忽略全部记录。此操作不可撤销,确认继续?`, + '批量清除确认', + { type: 'warning', confirmButtonText: '确认清除', confirmButtonClass: 'el-button--danger' } + ) + } catch { + return + } + batchClearing.value = true + + // 按 olt_id 分组 + const groups = {} + for (const row of dupList.value) { + const key = row.olt_id ?? '__unknown__' + if (!groups[key]) groups[key] = [] + groups[key].push(row) + } + + let cleared = 0, failed = 0 + + // 每个 OLT 一个并发任务,组内串行处理完所有重复 MAC 再结束 + await Promise.all( + Object.values(groups).map(async (rows) => { + for (const row of rows) { + const offlinePorts = (row.ports || []).filter(p => p.status !== 'online') + for (const p of offlinePorts) { + try { + await request.post(`/olt/duplicate-macs/${row.id}/clear-port`, { port_id: p.port_id }) + cleared++ + } catch { + failed++ + } + } + try { + await request.delete(`/olt/duplicate-macs/${row.id}`) + } catch {} + } + }) + ) + + dupList.value = [] + duplicateCount.value = 0 + batchClearing.value = false + ElMessage.success(`批量清除完成:清除 ${cleared} 个端口${failed ? `,${failed} 个失败` : ''},已忽略全部 ${total} 条记录`) +} + const fetchNewDeviceCount = async () => { try { const { data } = await request.get('/olt/new-devices')