index.vue 120 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253325432553256325732583259326032613262326332643265326632673268326932703271327232733274327532763277327832793280328132823283328432853286328732883289329032913292329332943295329632973298329933003301330233033304330533063307330833093310331133123313331433153316331733183319332033213322332333243325332633273328332933303331333233333334333533363337333833393340334133423343334433453346334733483349
  1. <template>
  2. <div class="app-container">
  3. <!-- 系统基础信息卡片 -->
  4. <el-card class="system-info-card">
  5. <div slot="header" class="card-header">
  6. <span class="title">
  7. <i class="el-icon-sunny"></i>
  8. {{ systemInfo.systemName || '智慧照明系统' }}
  9. </span>
  10. <el-tag :type="systemStatus === '1' ? 'success' : 'danger'" effect="dark">
  11. {{ systemStatus === '1' ? '正常' : '异常' }}
  12. </el-tag>
  13. </div>
  14. <el-row :gutter="20" style="position: relative;">
  15. <el-col :span="6">
  16. <div class="info-group">
  17. <div class="group-title">系统信息</div>
  18. <div class="info-item">
  19. <label>系统代码:</label>
  20. <span class="code">{{ systemInfo.systemCode }}</span>
  21. </div>
  22. <div class="info-item">
  23. <label>系统名称:</label>
  24. <span>{{ systemInfo.systemName }}</span>
  25. </div>
  26. <div class="info-item">
  27. <label>系统简称:</label>
  28. <span>{{ systemInfo.shortName || '-' }}</span>
  29. </div>
  30. <div class="info-item">
  31. <label>模型代码:</label>
  32. <span class="code">{{ systemInfo.modelCode }}</span>
  33. </div>
  34. </div>
  35. </el-col>
  36. <el-col :span="6">
  37. <div class="info-group">
  38. <div class="group-title">厂商信息</div>
  39. <div class="info-item">
  40. <label>对接厂商:</label>
  41. <span>{{ systemInfo.manFacturer || '-' }}</span>
  42. </div>
  43. <div class="info-item">
  44. <label>联系人:</label>
  45. <span>{{ systemInfo.contactPerson || '-' }}</span>
  46. </div>
  47. <div class="info-item">
  48. <label>联系电话:</label>
  49. <span>{{ systemInfo.contactNumber || '-' }}</span>
  50. </div>
  51. </div>
  52. </el-col>
  53. <el-col :span="6">
  54. <div class="info-group">
  55. <div class="group-title">维护信息</div>
  56. <div class="info-item">
  57. <label>维护人:</label>
  58. <span>{{ systemInfo.maintainerPerson || '-' }}</span>
  59. </div>
  60. <div class="info-item">
  61. <label>维护电话:</label>
  62. <span>{{ systemInfo.maintainerNumber || '-' }}</span>
  63. </div>
  64. </div>
  65. </el-col>
  66. <el-col :span="6">
  67. <div class="info-group">
  68. <div class="group-title">系统统计</div>
  69. <div class="info-item">
  70. <label>项目数量:</label>
  71. <span class="stat-number">{{ projectList.length }}</span>
  72. </div>
  73. <div class="info-item">
  74. <label>分组数量:</label>
  75. <span class="stat-number">{{ subsetList.length }}</span>
  76. </div>
  77. <div class="info-item">
  78. <label>设备总数:</label>
  79. <span class="stat-number">{{ deviceStats.total }}</span>
  80. </div>
  81. <div style="text-align: right; margin-top: 10px;">
  82. <el-button
  83. type="primary"
  84. size="mini"
  85. icon="el-icon-edit"
  86. @click="handleEditSystem">
  87. 编辑
  88. </el-button>
  89. </div>
  90. </div>
  91. </el-col>
  92. </el-row>
  93. </el-card>
  94. <!-- 系统详情标签页 -->
  95. <el-card class="detail-card">
  96. <el-tabs v-model="activeTab" @tab-click="handleTabClick">
  97. <!-- 系统总览标签页 -->
  98. <el-tab-pane label="系统总览" name="overview">
  99. <div class="tab-content">
  100. <!-- 快速操作面板 -->
  101. <div class="quick-actions">
  102. <h4 class="section-title">
  103. <i class="el-icon-s-operation"></i>
  104. 快速操作
  105. </h4>
  106. <el-row :gutter="20">
  107. <el-col :span="6" v-for="action in quickActions" :key="action.key">
  108. <el-button
  109. class="action-btn"
  110. :icon="action.icon"
  111. @click="handleQuickAction(action)"
  112. :loading="action.loading">
  113. {{ action.name }}
  114. </el-button>
  115. </el-col>
  116. </el-row>
  117. </div>
  118. <!-- 实时监控面板 -->
  119. <div class="monitor-panel">
  120. <h4 class="section-title">
  121. <i class="el-icon-monitor"></i>
  122. 实时监控
  123. </h4>
  124. <el-row :gutter="20">
  125. <el-col :xl="4" :lg="4" :md="8" :sm="12" :xs="24">
  126. <div class="monitor-card">
  127. <div class="monitor-icon online">
  128. <i class="el-icon-circle-check"></i>
  129. </div>
  130. <div class="monitor-info">
  131. <div class="monitor-value">{{ deviceStats.online }}</div>
  132. <div class="monitor-label">在线设备</div>
  133. </div>
  134. </div>
  135. </el-col>
  136. <el-col :xl="4" :lg="4" :md="8" :sm="12" :xs="24">
  137. <div class="monitor-card">
  138. <div class="monitor-icon offline">
  139. <i class="el-icon-circle-close"></i>
  140. </div>
  141. <div class="monitor-info">
  142. <div class="monitor-value">{{ deviceStats.offline }}</div>
  143. <div class="monitor-label">离线设备</div>
  144. </div>
  145. </div>
  146. </el-col>
  147. <el-col :xl="5" :lg="5" :md="8" :sm="12" :xs="24">
  148. <div class="monitor-card">
  149. <div class="monitor-icon lamps">
  150. <i class="el-icon-light"></i>
  151. </div>
  152. <div class="monitor-info">
  153. <div class="monitor-value">{{ lampStats.on }}</div>
  154. <div class="monitor-label">开启灯组</div>
  155. </div>
  156. </div>
  157. </el-col>
  158. <el-col :xl="5" :lg="5" :md="8" :sm="12" :xs="24">
  159. <div class="monitor-card">
  160. <div class="monitor-icon concentrator">
  161. <i class="el-icon-connection"></i>
  162. </div>
  163. <div class="monitor-info">
  164. <div class="monitor-value">{{ deviceStats.concentrators }}</div>
  165. <div class="monitor-label">集中器数量</div>
  166. </div>
  167. </div>
  168. </el-col>
  169. <el-col :xl="6" :lg="6" :md="8" :sm="12" :xs="24">
  170. <div class="monitor-card">
  171. <div class="monitor-icon power">
  172. <i class="el-icon-s-data"></i>
  173. </div>
  174. <div class="monitor-info">
  175. <div class="monitor-value">{{ totalPower.toFixed(2) }}W</div>
  176. <div class="monitor-label">总功率</div>
  177. </div>
  178. </div>
  179. </el-col>
  180. </el-row>
  181. </div>
  182. <!-- 集中器设备表格 -->
  183. <div class="concentrator-panel">
  184. <h4 class="section-title">
  185. <i class="el-icon-connection"></i>
  186. 集中器设备
  187. </h4>
  188. <el-table
  189. :data="concentratorList"
  190. border
  191. stripe
  192. v-loading="concentratorLoading"
  193. @row-click="handleConcentratorClick"
  194. ref="concentratorTable">
  195. <el-table-column type="expand">
  196. <template slot-scope="props">
  197. <div class="device-detail" @click.stop>
  198. <el-tabs v-model="props.row.detailTab">
  199. <!-- 基础属性标签页 -->
  200. <el-tab-pane label="基础属性" name="base">
  201. <el-descriptions :column="2" border size="small" class="concentrator-descriptions">
  202. <el-descriptions-item label="设备SN号">
  203. {{ getConcentratorAttr(props.row.deviceCode, 'deviceUid', 'Base') || '-' }}
  204. </el-descriptions-item>
  205. <el-descriptions-item label="设备型号ID">
  206. {{ getConcentratorAttr(props.row.deviceCode, 'deviceModelId', 'Base') || '-' }}
  207. </el-descriptions-item>
  208. <el-descriptions-item label="设备状态">
  209. <el-tag size="small" :type="getConcentratorAttr(props.row.deviceCode, 'deviceStatus', 'Base') === '1' ? 'success' : 'danger'">
  210. {{ getConcentratorAttr(props.row.deviceCode, 'deviceStatus', 'Base') === '1' ? '正常' : '异常' }}
  211. </el-tag>
  212. </el-descriptions-item>
  213. <el-descriptions-item label="信号强度">
  214. {{ getConcentratorAttr(props.row.deviceCode, 'csq', 'Base') || '-' }}
  215. </el-descriptions-item>
  216. </el-descriptions>
  217. <!-- 设备图片 -->
  218. <div v-if="getConcentratorAttr(props.row.deviceCode, 'imageUrl', 'Base')" class="device-image" style="margin-top: 20px;">
  219. <h5>设备图片</h5>
  220. <el-image
  221. style="width: 200px; height: 150px"
  222. :src="getConcentratorAttr(props.row.deviceCode, 'imageUrl', 'Base')"
  223. :preview-src-list="[getConcentratorAttr(props.row.deviceCode, 'imageUrl', 'Base')]"
  224. fit="cover">
  225. <div slot="error" class="image-slot">
  226. <i class="el-icon-picture-outline"></i>
  227. </div>
  228. </el-image>
  229. </div>
  230. </el-tab-pane>
  231. <!-- 电力属性标签页 -->
  232. <el-tab-pane label="电力属性" name="power">
  233. <div class="concentrator-detail">
  234. <!-- 功率参数 -->
  235. <div class="param-group">
  236. <div class="group-header">
  237. <span class="group-title">功率参数</span>
  238. </div>
  239. <el-row :gutter="20">
  240. <el-col :span="12">
  241. <div class="param-card">
  242. <div class="param-label">有功功率</div>
  243. <div class="param-value">
  244. <span class="value">{{ getConcentratorAttr(props.row.deviceCode, 'kwh') || '0.0' }}</span>
  245. <span class="unit">kW</span>
  246. </div>
  247. </div>
  248. </el-col>
  249. <el-col :span="12">
  250. <div class="param-card">
  251. <div class="param-label">无功功率</div>
  252. <div class="param-value">
  253. <span class="value">{{ getConcentratorAttr(props.row.deviceCode, 'kvarh') || '0.0' }}</span>
  254. <span class="unit">kVar</span>
  255. </div>
  256. </div>
  257. </el-col>
  258. </el-row>
  259. </div>
  260. <!-- 三相电压 -->
  261. <div class="param-group">
  262. <div class="group-header">
  263. <span class="group-title">三相电压</span>
  264. <el-tag size="mini" :type="getVoltageStatus(props.row.deviceCode).type">
  265. {{ getVoltageStatus(props.row.deviceCode).text }}
  266. </el-tag>
  267. </div>
  268. <el-row :gutter="20">
  269. <el-col :span="8">
  270. <div class="param-card phase-a" :class="getVoltageClass(getConcentratorAttr(props.row.deviceCode, 'ua'))">
  271. <div class="phase-label">A相</div>
  272. <div class="param-value">
  273. <span class="value">{{ getConcentratorAttr(props.row.deviceCode, 'ua') || '-' }}</span>
  274. <span class="unit">V</span>
  275. </div>
  276. <div class="voltage-indicator">
  277. <span class="normal-range">正常: 198-242V</span>
  278. </div>
  279. </div>
  280. </el-col>
  281. <el-col :span="8">
  282. <div class="param-card phase-b" :class="getVoltageClass(getConcentratorAttr(props.row.deviceCode, 'ub'))">
  283. <div class="phase-label">B相</div>
  284. <div class="param-value">
  285. <span class="value">{{ getConcentratorAttr(props.row.deviceCode, 'ub') || '-' }}</span>
  286. <span class="unit">V</span>
  287. </div>
  288. <div class="voltage-indicator">
  289. <span class="normal-range">正常: 198-242V</span>
  290. </div>
  291. </div>
  292. </el-col>
  293. <el-col :span="8">
  294. <div class="param-card phase-c" :class="getVoltageClass(getConcentratorAttr(props.row.deviceCode, 'uc'))">
  295. <div class="phase-label">C相</div>
  296. <div class="param-value">
  297. <span class="value">{{ getConcentratorAttr(props.row.deviceCode, 'uc') || '-' }}</span>
  298. <span class="unit">V</span>
  299. </div>
  300. <div class="voltage-indicator">
  301. <span class="normal-range">正常: 198-242V</span>
  302. </div>
  303. </div>
  304. </el-col>
  305. </el-row>
  306. </div>
  307. <!-- 三相电流 -->
  308. <div class="param-group">
  309. <div class="group-header">
  310. <span class="group-title">三相电流</span>
  311. </div>
  312. <el-row :gutter="20">
  313. <el-col :span="8">
  314. <div class="param-card phase-a">
  315. <div class="phase-label">A相</div>
  316. <div class="param-value">
  317. <span class="value">{{ getConcentratorAttr(props.row.deviceCode, 'ia') || '-' }}</span>
  318. <span class="unit">A</span>
  319. </div>
  320. </div>
  321. </el-col>
  322. <el-col :span="8">
  323. <div class="param-card phase-b">
  324. <div class="phase-label">B相</div>
  325. <div class="param-value">
  326. <span class="value">{{ getConcentratorAttr(props.row.deviceCode, 'ib') || '-' }}</span>
  327. <span class="unit">A</span>
  328. </div>
  329. </div>
  330. </el-col>
  331. <el-col :span="8">
  332. <div class="param-card phase-c">
  333. <div class="phase-label">C相</div>
  334. <div class="param-value">
  335. <span class="value">{{ getConcentratorAttr(props.row.deviceCode, 'ic') || '-' }}</span>
  336. <span class="unit">A</span>
  337. </div>
  338. </div>
  339. </el-col>
  340. </el-row>
  341. </div>
  342. <!-- 更新时间 -->
  343. <div class="update-time">
  344. <i class="el-icon-time"></i>
  345. 最后更新:{{ getConcentratorUpdateTime(props.row.deviceCode) || '暂无数据' }}
  346. </div>
  347. </div>
  348. </el-tab-pane>
  349. </el-tabs>
  350. </div>
  351. </template>
  352. </el-table-column>
  353. <el-table-column prop="deviceCode" label="设备代码" width="180"></el-table-column>
  354. <el-table-column prop="deviceName" label="设备名称"></el-table-column>
  355. <el-table-column prop="location" label="安装位置"></el-table-column>
  356. <el-table-column label="设备状态" width="100" align="center">
  357. <template slot-scope="scope">
  358. <el-tag :type="scope.row.deviceStatus === 1 ? 'success' : 'info'" size="small">
  359. {{ scope.row.deviceStatus === 1 ? '在线' : '离线' }}
  360. </el-tag>
  361. </template>
  362. </el-table-column>
  363. </el-table>
  364. </div>
  365. </div>
  366. </el-tab-pane>
  367. <!-- 系统属性标签页 -->
  368. <el-tab-pane label="系统属性" name="attributes">
  369. <div class="tab-content">
  370. <!-- 协议属性 -->
  371. <div class="attr-section" v-if="protocolAttrs.length > 0">
  372. <h4 class="section-title">
  373. <i class="el-icon-connection"></i>
  374. 协议信息
  375. </h4>
  376. <el-table :data="protocolAttrs" border stripe>
  377. <el-table-column prop="attrName" label="属性名称" width="200"></el-table-column>
  378. <el-table-column label="属性值">
  379. <template slot-scope="scope">
  380. <span v-if="scope.row.attrKey === 'url'" class="url-text">
  381. {{ scope.row.attrValue }}
  382. </span>
  383. <span v-else-if="scope.row.attrKey === 'password'" style="display: inline-flex; align-items: center;">
  384. <span>{{ passwordVisible ? scope.row.attrValue : '••••••••' }}</span>
  385. <i
  386. class="el-icon-view"
  387. @click.stop="passwordVisible = !passwordVisible"
  388. :title="passwordVisible ? '隐藏密码' : '显示密码'"
  389. :style="{
  390. marginLeft: '10px',
  391. cursor: 'pointer',
  392. fontSize: '16px',
  393. color: passwordVisible ? '#409EFF' : '#C0C4CC'
  394. }"
  395. ></i>
  396. </span>
  397. <span v-else>{{ scope.row.attrValue || '-' }}</span>
  398. </template>
  399. </el-table-column>
  400. </el-table>
  401. </div>
  402. <!-- 状态属性 -->
  403. <div class="attr-section" v-if="stateAttrs.length > 0">
  404. <h4 class="section-title">
  405. <i class="el-icon-info"></i>
  406. 状态信息
  407. </h4>
  408. <el-table :data="stateAttrs" border stripe>
  409. <el-table-column prop="attrName" label="属性名称" width="200"></el-table-column>
  410. <el-table-column label="属性值">
  411. <template slot-scope="scope">
  412. <el-tag v-if="scope.row.attrKey === 'interfaceStatus'"
  413. :type="scope.row.attrValue === '1' ? 'success' : 'danger'">
  414. {{ scope.row.attrValueName }}
  415. </el-tag>
  416. <span v-else>{{ scope.row.attrValueName || scope.row.attrValue || '-' }}</span>
  417. </template>
  418. </el-table-column>
  419. <el-table-column prop="updateTime" label="更新时间" width="180"></el-table-column>
  420. </el-table>
  421. </div>
  422. <!-- 基础属性 -->
  423. <div class="attr-section" v-if="baseAttrs.length > 0">
  424. <h4 class="section-title">
  425. <i class="el-icon-folder"></i>
  426. 基础信息
  427. </h4>
  428. <!-- 项目列表 -->
  429. <div v-if="projectList.length > 0" class="project-section">
  430. <h5>项目列表</h5>
  431. <el-table :data="projectList" border stripe size="small">
  432. <el-table-column prop="projectName" label="项目名称"></el-table-column>
  433. <el-table-column prop="projectAddress" label="项目地址"></el-table-column>
  434. <el-table-column prop="projectDeviceNum" label="设备数量" width="100" align="center">
  435. <template slot-scope="scope">
  436. <el-tag size="small">{{ scope.row.projectDeviceNum }}</el-tag>
  437. </template>
  438. </el-table-column>
  439. <el-table-column prop="projectSubsetNum" label="分组数量" width="100" align="center">
  440. <template slot-scope="scope">
  441. <el-tag size="small" type="info">{{ scope.row.projectSubsetNum }}</el-tag>
  442. </template>
  443. </el-table-column>
  444. </el-table>
  445. </div>
  446. <!-- 分组列表 -->
  447. <div v-if="subsetList.length > 0" class="project-section">
  448. <h5>分组列表</h5>
  449. <el-table :data="subsetList" border stripe size="small">
  450. <el-table-column prop="subsetName" label="分组名称"></el-table-column>
  451. <el-table-column prop="subsetId" label="分组ID"></el-table-column>
  452. <el-table-column prop="subsetDeviceNum" label="设备数量" width="100" align="center">
  453. <template slot-scope="scope">
  454. <el-tag size="small" :type="scope.row.subsetDeviceNum > 0 ? 'success' : 'info'">
  455. {{ scope.row.subsetDeviceNum }}
  456. </el-tag>
  457. </template>
  458. </el-table-column>
  459. </el-table>
  460. </div>
  461. </div>
  462. </div>
  463. </el-tab-pane>
  464. <!-- 系统能力标签页 -->
  465. <el-tab-pane label="系统能力" name="abilities">
  466. <div class="tab-content">
  467. <el-table :data="systemAbilities" border stripe v-loading="abilityLoading">
  468. <el-table-column prop="abilityName" label="能力名称" width="200"></el-table-column>
  469. <el-table-column prop="abilityKey" label="能力标识" width="200"></el-table-column>
  470. <el-table-column prop="abilityDesc" label="能力描述"></el-table-column>
  471. <el-table-column label="操作" width="200" align="center">
  472. <template slot-scope="scope">
  473. <!-- 根据参数类型展示不同的操作界面 -->
  474. <div v-if="getParamType(scope.row.paramDefinition) === 'Options'" style="display: inline-block;">
  475. <!-- Options类型:显示多个按钮 -->
  476. <el-button-group>
  477. <el-button
  478. v-for="option in parseOptions(scope.row.paramDefinition)"
  479. :key="option.value"
  480. size="mini"
  481. type="primary"
  482. @click="executeSystemAbilityWithParam(scope.row, option.value)"
  483. :loading="scope.row.executing && scope.row.executingValue === option.value">
  484. {{ option.key }}
  485. </el-button>
  486. </el-button-group>
  487. </div>
  488. <el-button
  489. v-else
  490. type="primary"
  491. size="mini"
  492. icon="el-icon-caret-right"
  493. @click="handleSystemAbilityClick(scope.row)"
  494. :loading="scope.row.executing">
  495. 执行
  496. </el-button>
  497. </template>
  498. </el-table-column>
  499. </el-table>
  500. </div>
  501. </el-tab-pane>
  502. <!-- 智慧灯杆标签页 -->
  503. <el-tab-pane label="智慧灯杆" name="devices">
  504. <div class="tab-content">
  505. <!-- 设备统计 -->
  506. <div class="device-stats">
  507. <el-row :gutter="20">
  508. <el-col :span="6">
  509. <div class="stat-item">
  510. <div class="stat-value">{{ lampPoleList.length }}</div>
  511. <div class="stat-label">灯杆总数</div>
  512. </div>
  513. </el-col>
  514. <el-col :span="6">
  515. <div class="stat-item online">
  516. <div class="stat-value">{{ lampPoleList.filter(p => p.deviceStatus === 1).length }}</div>
  517. <div class="stat-label">在线灯杆</div>
  518. </div>
  519. </el-col>
  520. <el-col :span="6">
  521. <div class="stat-item lamps">
  522. <div class="stat-value">{{ totalLampCount }}</div>
  523. <div class="stat-label">灯组总数</div>
  524. </div>
  525. </el-col>
  526. <el-col :span="6">
  527. <div class="stat-item controllers">
  528. <div class="stat-value">{{ onlineLampCount }}</div>
  529. <div class="stat-label">在线灯组</div>
  530. </div>
  531. </el-col>
  532. </el-row>
  533. </div>
  534. <!-- 智慧灯杆列表(两层嵌套:灯杆->灯组->详情) -->
  535. <el-table
  536. :data="lampPoleList"
  537. border
  538. stripe
  539. v-loading="lampPoleLoading"
  540. row-key="deviceCode"
  541. @row-click="handleLampPoleClick"
  542. ref="lampPoleTable"
  543. class="lamp-pole-main-table">
  544. <!-- 灯杆展开:显示灯组列表 -->
  545. <el-table-column type="expand">
  546. <template slot-scope="props">
  547. <div class="lamp-pole-detail" @click.stop>
  548. <!-- 灯组列表表格 -->
  549. <el-table
  550. :data="getLampGroupList(props.row.deviceCode)"
  551. border
  552. stripe
  553. size="small"
  554. row-key="deviceCode"
  555. @row-click="(row, column, event) => handleLampGroupClick(row, column, event, props.$index)"
  556. :ref="`lampGroupTable_${props.$index}`"
  557. class="lamp-group-table">
  558. <!-- 灯组展开:显示灯组属性和能力 -->
  559. <el-table-column type="expand" width="40">
  560. <template slot-scope="scope">
  561. <div class="lamp-group-detail" @click.stop>
  562. <el-tabs v-model="scope.row.detailTab">
  563. <!-- 灯组属性标签页 -->
  564. <el-tab-pane label="灯组属性" name="attrs">
  565. <!-- 状态属性 -->
  566. <div v-if="getLampGroupModelAttrs(scope.row.deviceCode, 'State').length > 0">
  567. <h5 class="attr-group-title">状态属性</h5>
  568. <el-descriptions :column="2" border size="small" class="lamp-group-descriptions">
  569. <el-descriptions-item
  570. v-for="attr in getLampGroupModelAttrs(scope.row.deviceCode, 'State')"
  571. :key="attr.attrKey"
  572. :label="attr.attrName">
  573. <template v-if="attr.attrValueType === 'Enum'">
  574. <el-tag size="small" :type="getLampGroupAttrValue(scope.row.deviceCode, attr.attrKey) === '1' ? 'success' : 'info'">
  575. {{ getLampGroupAttrEnumName(scope.row.deviceCode, attr.attrKey, attr.valueEnums) }}
  576. </el-tag>
  577. </template>
  578. <template v-else>
  579. {{ getLampGroupAttrValue(scope.row.deviceCode, attr.attrKey) || '-' }}
  580. <span v-if="attr.attrUnit">{{ attr.attrUnit }}</span>
  581. </template>
  582. </el-descriptions-item>
  583. </el-descriptions>
  584. </div>
  585. <!-- 计量属性 -->
  586. <div v-if="getLampGroupModelAttrs(scope.row.deviceCode, 'Measure').length > 0" style="margin-top: 20px;">
  587. <h5 class="attr-group-title">计量属性</h5>
  588. <el-descriptions :column="2" border size="small" class="lamp-group-descriptions">
  589. <el-descriptions-item
  590. v-for="attr in getLampGroupModelAttrs(scope.row.deviceCode, 'Measure')"
  591. :key="attr.attrKey"
  592. :label="attr.attrName">
  593. {{ getLampGroupAttrValue(scope.row.deviceCode, attr.attrKey) || '-' }}
  594. <span v-if="attr.attrUnit">{{ attr.attrUnit }}</span>
  595. </el-descriptions-item>
  596. </el-descriptions>
  597. </div>
  598. <!-- 基础属性 -->
  599. <div v-if="getLampGroupModelAttrs(scope.row.deviceCode, 'Base').length > 0" style="margin-top: 20px;">
  600. <h5 class="attr-group-title">基础属性</h5>
  601. <el-descriptions :column="2" border size="small" class="lamp-group-descriptions">
  602. <el-descriptions-item
  603. v-for="attr in getLampGroupModelAttrs(scope.row.deviceCode, 'Base')"
  604. :key="attr.attrKey"
  605. :label="attr.attrName">
  606. <template v-if="attr.attrValueType === 'Enum'">
  607. <el-tag size="small" :type="getLampGroupAttrValue(scope.row.deviceCode, attr.attrKey) === '0' ? 'success' : 'danger'">
  608. {{ getLampGroupAttrEnumName(scope.row.deviceCode, attr.attrKey, attr.valueEnums) }}
  609. </el-tag>
  610. </template>
  611. <template v-else-if="attr.attrKey === 'imageUrl'">
  612. <el-image
  613. v-if="getLampGroupAttrValue(scope.row.deviceCode, attr.attrKey)"
  614. style="width: 100px; height: 75px"
  615. :src="getLampGroupAttrValue(scope.row.deviceCode, attr.attrKey)"
  616. :preview-src-list="[getLampGroupAttrValue(scope.row.deviceCode, attr.attrKey)]"
  617. fit="cover">
  618. </el-image>
  619. <span v-else>-</span>
  620. </template>
  621. <template v-else>
  622. {{ getLampGroupAttrValue(scope.row.deviceCode, attr.attrKey) || '-' }}
  623. <span v-if="attr.attrUnit">{{ attr.attrUnit }}</span>
  624. </template>
  625. </el-descriptions-item>
  626. </el-descriptions>
  627. </div>
  628. <!-- 更新时间 -->
  629. <div class="update-time" style="margin-top: 15px;">
  630. <i class="el-icon-time"></i>
  631. 最后更新:{{ getLampGroupUpdateTime(scope.row.deviceCode) || '暂无数据' }}
  632. </div>
  633. </el-tab-pane>
  634. <!-- 灯组能力标签页 -->
  635. <el-tab-pane label="控制操作" name="abilities">
  636. <div class="ability-panel">
  637. <template v-if="lampGroupAbilities[scope.row.deviceCode] && lampGroupAbilities[scope.row.deviceCode].length > 0">
  638. <el-row :gutter="15">
  639. <el-col :span="8" v-for="ability in lampGroupAbilities[scope.row.deviceCode]" :key="ability.abilityKey">
  640. <div class="ability-card">
  641. <div class="ability-header">
  642. <i class="el-icon-magic-stick"></i>
  643. <span>{{ ability.abilityName }}</span>
  644. </div>
  645. <div class="ability-desc">{{ ability.abilityDesc }}</div>
  646. <!-- Options类型:多个按钮 -->
  647. <template v-if="getParamType(ability.paramDefinition) === 'Options'">
  648. <!-- 开关控制特殊处理:根据当前状态切换按钮样式 -->
  649. <template v-if="ability.abilityKey === 'lightControl'">
  650. <div class="light-control-buttons">
  651. <el-button
  652. v-for="option in parseOptions(ability.paramDefinition)"
  653. :key="option.value"
  654. :type="getLightControlButtonType(scope.row.deviceCode, option.value)"
  655. size="small"
  656. :icon="option.value === '1' ? 'el-icon-sunny' : 'el-icon-moon'"
  657. @click="executeLampGroupAbilityWithParam(scope.row, ability, option.value)"
  658. :loading="ability.executing && ability.executingValue === option.value">
  659. {{ option.key }}
  660. </el-button>
  661. </div>
  662. </template>
  663. <!-- 其他Options类型:显示所有按钮 -->
  664. <el-button-group v-else class="ability-buttons">
  665. <el-button
  666. v-for="option in parseOptions(ability.paramDefinition)"
  667. :key="option.value"
  668. size="small"
  669. @click="executeLampGroupAbilityWithParam(scope.row, ability, option.value)"
  670. :loading="ability.executing && ability.executingValue === option.value">
  671. {{ option.key }}
  672. </el-button>
  673. </el-button-group>
  674. </template>
  675. <!-- 其他类型:单个按钮 -->
  676. <el-button
  677. v-else
  678. type="primary"
  679. size="small"
  680. @click="handleLampGroupAbilityClick(scope.row, ability)"
  681. :loading="ability.executing"
  682. style="width: 100%; margin-top: 10px;">
  683. 执行
  684. </el-button>
  685. </div>
  686. </el-col>
  687. </el-row>
  688. </template>
  689. <el-empty v-else description="暂无可用操作" :image-size="80"></el-empty>
  690. </div>
  691. </el-tab-pane>
  692. </el-tabs>
  693. </div>
  694. </template>
  695. </el-table-column>
  696. <!-- 设备名称列(新增) -->
  697. <el-table-column label="设备名称" width="120">
  698. <template slot-scope="scope">
  699. {{ getLampGroupAttrValue(scope.row.deviceCode, 'deviceName') || '-' }}
  700. </template>
  701. </el-table-column>
  702. <el-table-column prop="deviceCode" label="灯组代码" min-width="180"></el-table-column>
  703. <el-table-column label="设备SN" width="110">
  704. <template slot-scope="scope">
  705. {{ getLampGroupAttrValue(scope.row.deviceCode, 'deviceUid') || '-' }}
  706. </template>
  707. </el-table-column>
  708. <el-table-column label="开关状态" width="100" align="center">
  709. <template slot-scope="scope">
  710. <el-tag size="small" :type="getLampGroupAttrValue(scope.row.deviceCode, 'Switch') === '1' ? 'success' : 'info'">
  711. <i :class="getLampGroupAttrValue(scope.row.deviceCode, 'Switch') === '1' ? 'el-icon-sunny' : 'el-icon-moon'"></i>
  712. {{ getLampGroupAttrValue(scope.row.deviceCode, 'Switch') === '1' ? '开启' : '关闭' }}
  713. </el-tag>
  714. </template>
  715. </el-table-column>
  716. <el-table-column label="设备状态" width="100" align="center">
  717. <template slot-scope="scope">
  718. <el-tag size="small" :type="getLampGroupAttrValue(scope.row.deviceCode, 'deviceStatus') === '1' ? 'success' : 'danger'">
  719. {{ getLampGroupAttrValue(scope.row.deviceCode, 'deviceStatus') === '1' ? '正常' : '异常' }}
  720. </el-tag>
  721. </template>
  722. </el-table-column>
  723. <el-table-column label="功率" width="90" align="center">
  724. <template slot-scope="scope">
  725. {{ getLampGroupAttrValue(scope.row.deviceCode, 'power') || '0' }}W
  726. </template>
  727. </el-table-column>
  728. <el-table-column label="电压" width="90" align="center">
  729. <template slot-scope="scope">
  730. {{ getLampGroupAttrValue(scope.row.deviceCode, 'voltage') || '0' }}V
  731. </template>
  732. </el-table-column>
  733. <el-table-column label="电流" width="90" align="center">
  734. <template slot-scope="scope">
  735. {{ getLampGroupAttrValue(scope.row.deviceCode, 'current') || '0' }}A
  736. </template>
  737. </el-table-column>
  738. <el-table-column label="操作" width="100" align="center">
  739. <template slot-scope="scope">
  740. <el-dropdown trigger="click" @command="handleLampGroupAbilityCommand">
  741. <el-button type="text" size="mini" @click.stop="loadLampGroupAbilities(scope.row)">
  742. 操作<i class="el-icon-arrow-down el-icon--right"></i>
  743. </el-button>
  744. <el-dropdown-menu slot="dropdown">
  745. <template v-if="currentLampGroupAbilities && currentLampGroupAbilities.length > 0">
  746. <el-dropdown-item
  747. v-for="ability in currentLampGroupAbilities"
  748. :key="ability.abilityKey"
  749. :command="{ ability: ability, device: scope.row }"
  750. >
  751. <i class="el-icon-caret-right" style="margin-right: 5px;"></i>
  752. {{ ability.abilityName }}
  753. <i v-if="getParamType(ability.paramDefinition) === 'Options'"
  754. class="el-icon-arrow-right"
  755. style="float: right; margin-left: 10px;"></i>
  756. </el-dropdown-item>
  757. </template>
  758. <el-dropdown-item v-else>
  759. 无可用操作
  760. </el-dropdown-item>
  761. </el-dropdown-menu>
  762. </el-dropdown>
  763. </template>
  764. </el-table-column>
  765. </el-table>
  766. </div>
  767. </template>
  768. </el-table-column>
  769. <el-table-column prop="deviceCode" label="灯杆代码" width="140"></el-table-column>
  770. <el-table-column prop="deviceName" label="灯杆名称" width="110"></el-table-column>
  771. <el-table-column prop="location" label="安装位置" min-width="120"></el-table-column>
  772. <el-table-column label="灯组数量" width="90" align="center">
  773. <template slot-scope="scope">
  774. <el-tag type="primary" size="small">{{ getLampGroupList(scope.row.deviceCode).length }}</el-tag>
  775. </template>
  776. </el-table-column>
  777. <el-table-column label="在线灯组" width="90" align="center">
  778. <template slot-scope="scope">
  779. <el-tag type="success" size="small">{{ getOnlineLampGroupCount(scope.row.deviceCode) }}</el-tag>
  780. </template>
  781. </el-table-column>
  782. <el-table-column label="开启灯组" width="90" align="center">
  783. <template slot-scope="scope">
  784. <el-tag type="success" size="small">{{ getOnSwitchLampGroupCount(scope.row.deviceCode) }}</el-tag>
  785. </template>
  786. </el-table-column>
  787. <el-table-column label="设备状态" width="90" align="center">
  788. <template slot-scope="scope">
  789. <el-tag :type="scope.row.deviceStatus === 1 ? 'success' : 'info'" size="small">
  790. {{ scope.row.deviceStatus === 1 ? '在线' : '离线' }}
  791. </el-tag>
  792. </template>
  793. </el-table-column>
  794. <el-table-column label="操作" width="100" align="center">
  795. <template slot-scope="scope">
  796. <el-dropdown trigger="click" @command="handleLampPoleAbilityCommand">
  797. <el-button type="text" size="mini" @click.stop="loadLampPoleAbilities(scope.row)">
  798. 操作<i class="el-icon-arrow-down el-icon--right"></i>
  799. </el-button>
  800. <el-dropdown-menu slot="dropdown">
  801. <template v-if="currentLampPoleAbilities && currentLampPoleAbilities.length > 0">
  802. <el-dropdown-item
  803. v-for="ability in currentLampPoleAbilities"
  804. :key="ability.abilityKey"
  805. :command="{ ability: ability, device: scope.row }"
  806. >
  807. <i class="el-icon-caret-right" style="margin-right: 5px;"></i>
  808. {{ ability.abilityName }}
  809. <i v-if="getParamType(ability.paramDefinition) === 'Options'"
  810. class="el-icon-arrow-right"
  811. style="float: right; margin-left: 10px;"></i>
  812. </el-dropdown-item>
  813. </template>
  814. <el-dropdown-item v-else disabled>
  815. 无可用操作
  816. </el-dropdown-item>
  817. </el-dropdown-menu>
  818. </el-dropdown>
  819. </template>
  820. </el-table-column>
  821. </el-table>
  822. </div>
  823. </el-tab-pane>
  824. <!-- 调用日志标签页 -->
  825. <el-tab-pane label="调用日志" name="callLogs">
  826. <div class="tab-content">
  827. <el-form :inline="true" :model="callLogQuery" class="log-filter">
  828. <el-form-item label="时间范围">
  829. <el-date-picker
  830. v-model="callLogQuery.dateRange"
  831. type="datetimerange"
  832. range-separator="至"
  833. start-placeholder="开始日期"
  834. end-placeholder="结束日期"
  835. value-format="yyyy-MM-dd HH:mm:ss">
  836. </el-date-picker>
  837. </el-form-item>
  838. <el-form-item label="能力标识">
  839. <el-input v-model="callLogQuery.abilityKey" placeholder="请输入能力标识" clearable></el-input>
  840. </el-form-item>
  841. <el-form-item label="调用状态">
  842. <el-select v-model="callLogQuery.callStatus" placeholder="全部" clearable>
  843. <el-option label="成功" :value="0"></el-option>
  844. <el-option label="进行中" :value="1"></el-option>
  845. <el-option label="失败" :value="2"></el-option>
  846. </el-select>
  847. </el-form-item>
  848. <el-form-item>
  849. <el-button type="primary" @click="queryCallLogs">查询</el-button>
  850. <el-button @click="resetCallLogQuery">重置</el-button>
  851. </el-form-item>
  852. </el-form>
  853. <el-table :data="callLogList" border stripe v-loading="logLoading">
  854. <el-table-column prop="objCode" label="对象代码" min-width="150"></el-table-column>
  855. <el-table-column prop="objName" label="对象名称" min-width="200"></el-table-column>
  856. <el-table-column prop="abilityName" label="能力名称" min-width="150"></el-table-column>
  857. <el-table-column prop="callTime" label="调用时间" width="180"></el-table-column>
  858. <el-table-column label="调用状态" width="100" align="center">
  859. <template slot-scope="scope">
  860. <el-tag size="small" :type="getCallStatusType(scope.row.callStatus)">
  861. {{ formatCallStatus(scope.row.callStatus) }}
  862. </el-tag>
  863. </template>
  864. </el-table-column>
  865. <el-table-column label="操作" width="80" align="center">
  866. <template slot-scope="scope">
  867. <el-button type="text" size="mini" @click="handleCallLogDetail(scope.row)">详情</el-button>
  868. </template>
  869. </el-table-column>
  870. </el-table>
  871. <pagination
  872. v-show="callLogTotal > 0"
  873. :total="callLogTotal"
  874. :page.sync="callLogQuery.pageNum"
  875. :limit.sync="callLogQuery.pageSize"
  876. @pagination="queryCallLogs"
  877. />
  878. </div>
  879. </el-tab-pane>
  880. <!-- 事件日志标签页 -->
  881. <el-tab-pane label="事件日志" name="eventLogs">
  882. <div class="tab-content">
  883. <el-form :inline="true" :model="eventLogQuery" class="log-filter">
  884. <el-form-item label="时间范围">
  885. <el-date-picker
  886. v-model="eventLogQuery.dateRange"
  887. type="datetimerange"
  888. range-separator="至"
  889. start-placeholder="开始日期"
  890. end-placeholder="结束日期"
  891. value-format="yyyy-MM-dd HH:mm:ss">
  892. </el-date-picker>
  893. </el-form-item>
  894. <el-form-item label="事件标识">
  895. <el-input v-model="eventLogQuery.eventKey" placeholder="请输入事件标识" clearable></el-input>
  896. </el-form-item>
  897. <el-form-item>
  898. <el-button type="primary" @click="queryEventLogs">查询</el-button>
  899. <el-button @click="resetEventLogQuery">重置</el-button>
  900. </el-form-item>
  901. </el-form>
  902. <el-table :data="eventLogList" border stripe v-loading="eventLogLoading">
  903. <el-table-column prop="objCode" label="对象编号" min-width="150"></el-table-column>
  904. <el-table-column prop="objName" label="对象名称" min-width="200"></el-table-column>
  905. <el-table-column prop="eventName" label="事件名称" min-width="200"></el-table-column>
  906. <el-table-column prop="eventTime" label="事件时间" width="180"></el-table-column>
  907. <el-table-column label="操作" width="80" align="center">
  908. <template slot-scope="scope">
  909. <el-button type="text" size="mini" @click="handleEventLogDetail(scope.row)">详情</el-button>
  910. </template>
  911. </el-table-column>
  912. </el-table>
  913. <pagination
  914. v-show="eventLogTotal > 0"
  915. :total="eventLogTotal"
  916. :page.sync="eventLogQuery.pageNum"
  917. :limit.sync="eventLogQuery.pageSize"
  918. @pagination="queryEventLogs"
  919. />
  920. </div>
  921. </el-tab-pane>
  922. </el-tabs>
  923. </el-card>
  924. <!-- 执行结果对话框 -->
  925. <el-dialog
  926. :title="executeDialog.title"
  927. :visible.sync="executeDialog.visible"
  928. width="600px">
  929. <div class="execute-result">
  930. <el-alert
  931. :title="executeDialog.status"
  932. :type="executeDialog.type"
  933. :description="executeDialog.message"
  934. show-icon>
  935. </el-alert>
  936. <div v-if="executeDialog.data" class="result-data">
  937. <pre>{{ JSON.stringify(executeDialog.data, null, 2) }}</pre>
  938. </div>
  939. </div>
  940. <span slot="footer">
  941. <el-button @click="executeDialog.visible = false">关闭</el-button>
  942. </span>
  943. </el-dialog>
  944. <!-- 调用日志详情弹窗 -->
  945. <el-dialog :visible.sync="callLogDetailDialog" title="调用日志详情" width="60%">
  946. <div v-if="callLogDetailData">
  947. <el-descriptions :column="2" border>
  948. <el-descriptions-item label="对象代码">{{ callLogDetailData.objCode }}</el-descriptions-item>
  949. <el-descriptions-item label="模型代码">{{ callLogDetailData.modelCode }}</el-descriptions-item>
  950. <el-descriptions-item label="能力标识">{{ callLogDetailData.abilityKey }}</el-descriptions-item>
  951. <el-descriptions-item label="调用时间">{{ callLogDetailData.callTime }}</el-descriptions-item>
  952. <el-descriptions-item label="响应时间">{{ callLogDetailData.resTime }}</el-descriptions-item>
  953. <el-descriptions-item label="调用结果">
  954. <el-tag :type="getCallStatusType(callLogDetailData.callStatus)">
  955. {{ formatCallStatus(callLogDetailData.callStatus) }}
  956. </el-tag>
  957. </el-descriptions-item>
  958. </el-descriptions>
  959. <div style="margin-top: 20px;">
  960. <h4>调用载体</h4>
  961. <pre style="white-space: pre-wrap; background-color: #f5f5f5; padding: 10px; border-radius: 4px; max-height: 300px; overflow: auto;">{{ callLogDetailData.callPayload }}</pre>
  962. </div>
  963. <div style="margin-top: 20px;">
  964. <h4>响应载体</h4>
  965. <pre style="white-space: pre-wrap; background-color: #f5f5f5; padding: 10px; border-radius: 4px; max-height: 300px; overflow: auto;">{{ callLogDetailData.resPayload }}</pre>
  966. </div>
  967. </div>
  968. </el-dialog>
  969. <!-- 事件日志详情弹窗 -->
  970. <el-dialog :visible.sync="eventLogDetailDialog" title="事件日志详情" width="60%">
  971. <div v-if="eventLogDetailData">
  972. <el-descriptions :column="2" border>
  973. <el-descriptions-item label="对象编号">{{ eventLogDetailData.objCode }}</el-descriptions-item>
  974. <el-descriptions-item label="对象名称">{{ eventLogDetailData.objName }}</el-descriptions-item>
  975. <el-descriptions-item label="事件名称">{{ eventLogDetailData.eventName }}</el-descriptions-item>
  976. <el-descriptions-item label="事件标识">{{ eventLogDetailData.eventKey }}</el-descriptions-item>
  977. <el-descriptions-item label="事件时间" :span="2">{{ eventLogDetailData.eventTime }}</el-descriptions-item>
  978. </el-descriptions>
  979. <div style="margin-top: 20px;">
  980. <h4>事件描述</h4>
  981. <div style="background-color: #f5f5f5; padding: 10px; border-radius: 4px;">
  982. {{ eventLogDetailData.eventDetail || '无' }}
  983. </div>
  984. </div>
  985. </div>
  986. </el-dialog>
  987. <!-- 能力执行弹窗(支持Slider和Input类型) -->
  988. <el-dialog :title="abilityDialogTitle" :visible.sync="abilityDialogVisible" width="500px" append-to-body :close-on-click-modal="false">
  989. <el-form ref="abilityForm" :model="abilityForm" label-width="100px">
  990. <el-form-item label="能力名称">
  991. <el-input v-model="abilityForm.abilityName" disabled></el-input>
  992. </el-form-item>
  993. <el-form-item label="能力描述">
  994. <el-input type="textarea" v-model="abilityForm.abilityDesc" disabled :rows="2"></el-input>
  995. </el-form-item>
  996. <!-- 根据参数类型显示不同的输入组件 -->
  997. <el-form-item label="参数设置" v-if="abilityParamType">
  998. <!-- Slider类型:显示滑块 -->
  999. <template v-if="abilityParamType === 'Slider'">
  1000. <div style="width: 100%;">
  1001. <div style="padding: 0 10px;">
  1002. <el-slider
  1003. v-model="abilitySliderValue"
  1004. :min="abilitySliderMin"
  1005. :max="abilitySliderMax"
  1006. :show-tooltip="true"
  1007. :format-tooltip="formatTooltip">
  1008. </el-slider>
  1009. </div>
  1010. <div style="display: flex; justify-content: space-between; padding: 0 10px; margin-top: -5px; margin-bottom: 20px;">
  1011. <span style="font-size: 12px; color: #909399;">{{ abilitySliderMin }}%</span>
  1012. <span style="font-size: 12px; color: #909399;">{{ Math.floor((abilitySliderMin + abilitySliderMax) / 2) }}%</span>
  1013. <span style="font-size: 12px; color: #909399;">{{ abilitySliderMax }}%</span>
  1014. </div>
  1015. <div style="display: flex; align-items: center; gap: 15px;">
  1016. <el-input-number
  1017. v-model="abilitySliderValue"
  1018. :min="abilitySliderMin"
  1019. :max="abilitySliderMax"
  1020. :step="1"
  1021. :precision="0"
  1022. controls-position="right"
  1023. style="flex: 0 0 120px;">
  1024. </el-input-number>
  1025. <span style="color: #909399; font-size: 13px;">
  1026. 调节范围:{{ abilitySliderMin }} - {{ abilitySliderMax }}
  1027. </span>
  1028. </div>
  1029. </div>
  1030. </template>
  1031. <!-- Input类型:显示输入框 -->
  1032. <template v-else-if="abilityParamType === 'Input'">
  1033. <el-input
  1034. v-model="abilityInputValue"
  1035. placeholder="请输入参数值"
  1036. clearable>
  1037. </el-input>
  1038. </template>
  1039. </el-form-item>
  1040. </el-form>
  1041. <div slot="footer" class="dialog-footer">
  1042. <el-button @click="abilityDialogVisible = false">取消</el-button>
  1043. <el-button type="primary" @click="executeAbilityWithParam" :loading="abilityExecuting">执行</el-button>
  1044. </div>
  1045. </el-dialog>
  1046. <!-- Options类型的快速执行弹窗 -->
  1047. <el-dialog :title="quickAbilityTitle" :visible.sync="quickAbilityDialogVisible" width="400px" append-to-body>
  1048. <div style="text-align: center; padding: 20px 0;">
  1049. <el-button-group>
  1050. <el-button
  1051. v-for="option in quickAbilityOptions"
  1052. :key="option.value"
  1053. type="primary"
  1054. size="medium"
  1055. @click="executeQuickAbility(option.value)"
  1056. :loading="quickAbilityExecuting && quickAbilityValue === option.value"
  1057. style="margin: 0 10px;">
  1058. {{ option.key }}
  1059. </el-button>
  1060. </el-button-group>
  1061. </div>
  1062. </el-dialog>
  1063. <!-- 编辑系统信息对话框 -->
  1064. <el-dialog title="编辑系统信息" :visible.sync="editSystemDialogVisible" width="600px" append-to-body :close-on-click-modal="false">
  1065. <el-form ref="editSystemForm" :model="editSystemForm" :rules="editSystemRules" label-width="100px">
  1066. <el-form-item label="系统代码" prop="systemCode">
  1067. <el-input v-model="editSystemForm.systemCode" placeholder="请输入系统代码" disabled />
  1068. </el-form-item>
  1069. <el-form-item label="系统名称" prop="systemName">
  1070. <el-input v-model="editSystemForm.systemName" placeholder="请输入系统名称" />
  1071. </el-form-item>
  1072. <el-form-item label="系统简称" prop="shortName">
  1073. <el-input v-model="editSystemForm.shortName" placeholder="请输入系统简称" />
  1074. </el-form-item>
  1075. <el-form-item label="对接厂商" prop="manFacturer">
  1076. <el-input v-model="editSystemForm.manFacturer" placeholder="请输入对接厂商" />
  1077. </el-form-item>
  1078. <el-form-item label="联系人" prop="contactPerson">
  1079. <el-input v-model="editSystemForm.contactPerson" placeholder="请输入联系人" />
  1080. </el-form-item>
  1081. <el-form-item label="联系电话" prop="contactNumber">
  1082. <el-input v-model="editSystemForm.contactNumber" placeholder="请输入联系电话" />
  1083. </el-form-item>
  1084. <el-form-item label="维护人" prop="maintainerPerson">
  1085. <el-input v-model="editSystemForm.maintainerPerson" placeholder="请输入维护人" />
  1086. </el-form-item>
  1087. <el-form-item label="维护电话" prop="maintainerNumber">
  1088. <el-input v-model="editSystemForm.maintainerNumber" placeholder="请输入维护电话" />
  1089. </el-form-item>
  1090. <el-form-item label="备注说明" prop="descr">
  1091. <el-input v-model="editSystemForm.descr" type="textarea" :rows="3" placeholder="请输入备注说明" />
  1092. </el-form-item>
  1093. </el-form>
  1094. <div slot="footer" class="dialog-footer">
  1095. <el-button @click="editSystemDialogVisible = false">取消</el-button>
  1096. <el-button type="primary" @click="submitEditSystem" :loading="editSystemSubmitting">保存</el-button>
  1097. </div>
  1098. </el-dialog>
  1099. </div>
  1100. </template>
  1101. <script>
  1102. import { getSubsystemByCode, updateSubsystem } from '@/api/adapter/subsystem'
  1103. import { getModelByCode } from '@/api/basecfg/objModel'
  1104. import { getObjAttr, getObjAttrBatch } from '@/api/basecfg/objAttribute'
  1105. import { callAbility } from '@/api/basecfg/objAbility'
  1106. import { getByCondition } from '@/api/device/device'
  1107. import { listCallLog, listEventLog, getCallLog, getEventLog } from '@/api/basecfg/objLog'
  1108. export default {
  1109. name: 'SmartLightingSystem',
  1110. data() {
  1111. return {
  1112. // 系统代码
  1113. systemCode: 'SYS_ZHZM',
  1114. // 系统信息
  1115. systemInfo: {},
  1116. // 系统状态
  1117. systemStatus: '1',
  1118. // 当前标签页
  1119. activeTab: 'overview',
  1120. // 协议属性
  1121. protocolAttrs: [],
  1122. // 状态属性
  1123. stateAttrs: [],
  1124. // 基础属性
  1125. baseAttrs: [],
  1126. // 项目列表
  1127. projectList: [],
  1128. // 分组列表
  1129. subsetList: [],
  1130. // 系统能力
  1131. systemAbilities: [],
  1132. abilityLoading: false,
  1133. // 智慧灯杆列表
  1134. lampPoleList: [],
  1135. lampPoleLoading: false,
  1136. lampPoleAttrs: {},
  1137. // 密码可见状态
  1138. passwordVisible: false,
  1139. // 灯杆物模型
  1140. lampPoleModel: null,
  1141. // 灯杆能力列表
  1142. lampPoleAbilities: {},
  1143. // 当前操作的灯杆能力列表
  1144. currentLampPoleAbilities: [],
  1145. // 灯组设备列表(所有灯组)
  1146. lampGroupList: [],
  1147. lampGroupAttrs: {},
  1148. lampGroupModel: null,
  1149. lampGroupAbilities: {},
  1150. // 当前操作的灯组能力列表
  1151. currentLampGroupAbilities: [],
  1152. // 灯组统计
  1153. totalLampCount: 0,
  1154. onlineLampCount: 0,
  1155. // 集中器设备列表
  1156. concentratorList: [],
  1157. concentratorLoading: false,
  1158. concentratorAttrs: {},
  1159. // 设备统计
  1160. deviceStats: {
  1161. total: 0,
  1162. online: 0,
  1163. offline: 0,
  1164. lamps: 0,
  1165. concentrators: 0
  1166. },
  1167. // 灯组统计
  1168. lampStats: {
  1169. on: 0,
  1170. off: 0
  1171. },
  1172. // 总功率
  1173. totalPower: 0,
  1174. // 快速操作
  1175. quickActions: [
  1176. { key: 'syncProject', name: '同步项目', icon: 'el-icon-refresh', loading: false },
  1177. { key: 'syncDevice', name: '同步设备', icon: 'el-icon-download', loading: false },
  1178. { key: 'allOn', name: '全部开灯', icon: 'el-icon-sunny', loading: false },
  1179. { key: 'allOff', name: '全部关灯', icon: 'el-icon-moon', loading: false }
  1180. ],
  1181. // 所有模型代码(系统 + 设备)
  1182. allModelCodes: [],
  1183. // 调用日志查询
  1184. callLogQuery: {
  1185. dateRange: [],
  1186. abilityKey: '',
  1187. callStatus: '',
  1188. pageNum: 1,
  1189. pageSize: 10
  1190. },
  1191. callLogList: [],
  1192. callLogTotal: 0,
  1193. logLoading: false,
  1194. // 事件日志查询
  1195. eventLogQuery: {
  1196. dateRange: [],
  1197. eventKey: '',
  1198. pageNum: 1,
  1199. pageSize: 10
  1200. },
  1201. eventLogList: [],
  1202. eventLogTotal: 0,
  1203. eventLogLoading: false,
  1204. // 调用日志详情
  1205. callLogDetailDialog: false,
  1206. callLogDetailData: null,
  1207. // 事件日志详情
  1208. eventLogDetailDialog: false,
  1209. eventLogDetailData: null,
  1210. // 执行结果对话框
  1211. executeDialog: {
  1212. visible: false,
  1213. title: '',
  1214. status: '',
  1215. type: 'success',
  1216. message: '',
  1217. data: null
  1218. },
  1219. // 能力执行相关
  1220. abilityDialogVisible: false,
  1221. abilityDialogTitle: '能力执行',
  1222. abilityForm: {
  1223. abilityName: '',
  1224. abilityKey: '',
  1225. abilityDesc: '',
  1226. paramDefinition: null,
  1227. modelCode: ''
  1228. },
  1229. abilityParamType: null,
  1230. abilitySliderValue: 50,
  1231. abilitySliderMin: 0,
  1232. abilitySliderMax: 100,
  1233. abilityInputValue: '',
  1234. abilityExecuting: false,
  1235. currentDevice: null,
  1236. currentObjType: null, // 1:设施, 2:设备, 3:系统
  1237. // 快速执行弹窗(Options类型)
  1238. quickAbilityDialogVisible: false,
  1239. quickAbilityTitle: '',
  1240. quickAbilityOptions: [],
  1241. quickAbilityExecuting: false,
  1242. quickAbilityValue: null,
  1243. quickAbilityData: null,
  1244. // 编辑系统信息
  1245. editSystemDialogVisible: false,
  1246. editSystemSubmitting: false,
  1247. editSystemForm: {
  1248. id: null,
  1249. systemCode: null,
  1250. systemName: null,
  1251. shortName: null,
  1252. manFacturer: null,
  1253. contactPerson: null,
  1254. contactNumber: null,
  1255. maintainerPerson: null,
  1256. maintainerNumber: null,
  1257. descr: null,
  1258. modelCode: null
  1259. },
  1260. editSystemRules: {
  1261. systemCode: [
  1262. { required: true, message: '系统代码不能为空', trigger: 'blur' }
  1263. ],
  1264. systemName: [
  1265. { required: true, message: '系统名称不能为空', trigger: 'blur' }
  1266. ]
  1267. }
  1268. }
  1269. },
  1270. created() {
  1271. this.loadSystemInfo()
  1272. this.initDateRange()
  1273. },
  1274. methods: {
  1275. // 格式化滑块tooltip
  1276. formatTooltip(val) {
  1277. return `${val}%`
  1278. },
  1279. // 解析参数定义类型
  1280. getParamType(paramDefinition) {
  1281. if (!paramDefinition) return null
  1282. try {
  1283. const def = JSON.parse(paramDefinition)
  1284. return def.type
  1285. } catch (e) {
  1286. return null
  1287. }
  1288. },
  1289. // 获取参数类型名称
  1290. getParamTypeName(paramDefinition) {
  1291. const type = this.getParamType(paramDefinition)
  1292. const typeNames = {
  1293. 'Options': '选项型',
  1294. 'Slider': '滑块型',
  1295. 'Input': '输入型'
  1296. }
  1297. return typeNames[type] || '无参数'
  1298. },
  1299. // 获取参数类型标签样式
  1300. getParamTypeTagType(paramDefinition) {
  1301. const type = this.getParamType(paramDefinition)
  1302. const typeStyles = {
  1303. 'Options': 'primary',
  1304. 'Slider': 'success',
  1305. 'Input': 'warning'
  1306. }
  1307. return typeStyles[type] || 'info'
  1308. },
  1309. // 解析Options类型的选项
  1310. parseOptions(paramDefinition) {
  1311. try {
  1312. const def = JSON.parse(paramDefinition)
  1313. if (def.type === 'Options' && def.list) {
  1314. return def.list
  1315. }
  1316. } catch (e) {
  1317. console.error('解析Options失败:', e)
  1318. }
  1319. return []
  1320. },
  1321. // 获取开关控制按钮的类型(根据当前状态)
  1322. getLightControlButtonType(deviceCode, optionValue) {
  1323. const currentState = this.getLampGroupAttrValue(deviceCode, 'Switch')
  1324. // 关灯状态(0):开灯按钮绿色,关灯按钮灰色
  1325. // 开灯状态(1):关灯按钮绿色,开灯按钮灰色
  1326. if (currentState === '0') {
  1327. // 当前关灯,开灯按钮高亮
  1328. return optionValue === '1' ? 'success' : 'info'
  1329. } else if (currentState === '1') {
  1330. // 当前开灯,关灯按钮高亮
  1331. return optionValue === '0' ? 'success' : 'info'
  1332. }
  1333. // 状态未知时,默认样式
  1334. return 'primary'
  1335. },
  1336. // 加载灯组能力列表(用于下拉菜单)
  1337. loadLampGroupAbilities(lampGroup) {
  1338. // 从已初始化的 lampGroupAbilities 中获取能力列表
  1339. const abilities = this.lampGroupAbilities[lampGroup.deviceCode]
  1340. if (!abilities || abilities.length === 0) {
  1341. this.currentLampGroupAbilities = []
  1342. console.warn('未找到灯组能力列表:', lampGroup.deviceCode)
  1343. return
  1344. }
  1345. // 过滤出 hiddenFlag 为 1 的能力(可在界面显示的)
  1346. this.currentLampGroupAbilities = abilities.filter(ability => ability.hiddenFlag === 1)
  1347. console.log('加载灯组能力:', lampGroup.deviceCode, this.currentLampGroupAbilities)
  1348. },
  1349. // 加载灯杆能力列表(用于下拉菜单)
  1350. loadLampPoleAbilities(lampPole) {
  1351. const abilities = this.lampPoleAbilities[lampPole.deviceCode]
  1352. if (!abilities || abilities.length === 0) {
  1353. this.currentLampPoleAbilities = []
  1354. console.warn('未找到灯杆能力列表:', lampPole.deviceCode)
  1355. return
  1356. }
  1357. this.currentLampPoleAbilities = abilities
  1358. console.log('加载灯杆能力:', lampPole.deviceCode, this.currentLampPoleAbilities)
  1359. },
  1360. // 处理灯组能力命令(下拉菜单点击)
  1361. handleLampGroupAbilityCommand(command) {
  1362. const { ability, device } = command
  1363. this.currentDevice = device
  1364. const paramType = this.getParamType(ability.paramDefinition)
  1365. if (paramType === 'Options') {
  1366. // Options类型:显示快速执行弹窗
  1367. this.quickAbilityTitle = ability.abilityName
  1368. this.quickAbilityOptions = this.parseOptions(ability.paramDefinition)
  1369. this.quickAbilityData = ability
  1370. this.quickAbilityDialogVisible = true
  1371. } else if (paramType === 'Slider') {
  1372. // Slider类型:显示滑块弹窗
  1373. this.showSliderDialog(ability, device, 2)
  1374. } else if (paramType === 'Input') {
  1375. // Input类型:显示输入弹窗
  1376. this.showInputDialog(ability, device, 2)
  1377. } else {
  1378. // 无参数:直接执行
  1379. this.executeDirectLampGroupAbility(ability, device)
  1380. }
  1381. },
  1382. // 处理灯杆能力命令(下拉菜单点击)
  1383. handleLampPoleAbilityCommand(command) {
  1384. const { ability, device } = command
  1385. this.currentDevice = device
  1386. const paramType = this.getParamType(ability.paramDefinition)
  1387. if (paramType === 'Options') {
  1388. // Options类型:显示快速执行弹窗
  1389. this.quickAbilityTitle = `${ability.abilityName} - ${device.deviceName || device.deviceCode}`
  1390. this.quickAbilityOptions = this.parseOptions(ability.paramDefinition)
  1391. this.quickAbilityData = ability
  1392. this.quickAbilityDialogVisible = true
  1393. } else if (paramType === 'Slider') {
  1394. // Slider类型:显示滑块弹窗
  1395. this.showSliderDialog(ability, device, 2)
  1396. } else if (paramType === 'Input') {
  1397. // Input类型:显示输入弹窗
  1398. this.showInputDialog(ability, device, 2)
  1399. } else {
  1400. // 无参数:直接执行
  1401. this.executeDirectLampPoleAbility(ability, device)
  1402. }
  1403. },
  1404. // 快速执行Options类型能力(通用方法)
  1405. async executeQuickAbility(value) {
  1406. if (!this.currentDevice || !this.quickAbilityData) {
  1407. this.$message.error('操作数据异常')
  1408. return
  1409. }
  1410. this.quickAbilityExecuting = true
  1411. this.quickAbilityValue = value
  1412. try {
  1413. // 判断是灯杆还是灯组
  1414. const isLampPole = this.currentDevice.deviceModel === 'M_Z010_DEV_SQUARE_LAMP_POST'
  1415. const modelCode = this.currentDevice.deviceModel || (isLampPole ? 'M_Z010_DEV_SQUARE_LAMP_POST' : 'M_Z010_DEV_SQUARE_LIGHT')
  1416. await callAbility({
  1417. objCode: this.currentDevice.deviceCode,
  1418. objType: 2,
  1419. modelCode: modelCode,
  1420. abilityKey: this.quickAbilityData.abilityKey,
  1421. abilityParam: value
  1422. })
  1423. this.$message.success(`${this.quickAbilityData.abilityName}执行成功`)
  1424. this.quickAbilityDialogVisible = false
  1425. // 根据设备类型刷新不同的数据
  1426. if (isLampPole) {
  1427. // 灯杆:刷新该灯杆下所有灯组的属性
  1428. await this.refreshLampPoleDevices(this.currentDevice.deviceCode)
  1429. } else {
  1430. // 灯组:刷新单个灯组属性
  1431. await this.loadSingleLampGroupAttr(this.currentDevice.deviceCode, modelCode)
  1432. }
  1433. } catch (error) {
  1434. this.$message.error(`${this.quickAbilityData.abilityName}执行失败:${error.message}`)
  1435. } finally {
  1436. this.quickAbilityExecuting = false
  1437. this.quickAbilityValue = null
  1438. }
  1439. },
  1440. // 直接执行灯组能力(无参数,从下拉菜单)
  1441. async executeDirectLampGroupAbility(ability, device) {
  1442. this.$confirm(`确认执行 ${ability.abilityName} 操作?`, '提示', {
  1443. confirmButtonText: '确定',
  1444. cancelButtonText: '取消',
  1445. type: 'warning'
  1446. }).then(async () => {
  1447. try {
  1448. const modelCode = device.deviceModel || 'M_Z010_DEV_SQUARE_LIGHT'
  1449. await callAbility({
  1450. objCode: device.deviceCode,
  1451. objType: 2,
  1452. modelCode: modelCode,
  1453. abilityKey: ability.abilityKey,
  1454. abilityParam: null
  1455. })
  1456. this.$message.success(`${ability.abilityName}执行成功`)
  1457. await this.loadSingleLampGroupAttr(device.deviceCode, modelCode)
  1458. } catch (error) {
  1459. this.$message.error(`${ability.abilityName}执行失败:${error.message}`)
  1460. }
  1461. })
  1462. },
  1463. // 直接执行灯杆能力(无参数,从下拉菜单)
  1464. async executeDirectLampPoleAbility(ability, device) {
  1465. this.$confirm(`确认执行 ${ability.abilityName} 操作?`, '提示', {
  1466. confirmButtonText: '确定',
  1467. cancelButtonText: '取消',
  1468. type: 'warning'
  1469. }).then(async () => {
  1470. try {
  1471. const modelCode = device.deviceModel || 'M_Z010_DEV_SQUARE_LAMP_POST'
  1472. await callAbility({
  1473. objCode: device.deviceCode,
  1474. objType: 2,
  1475. modelCode: modelCode,
  1476. abilityKey: ability.abilityKey,
  1477. abilityParam: null
  1478. })
  1479. this.$message.success(`${ability.abilityName}执行成功`)
  1480. // 刷新灯杆下所有灯组的属性
  1481. await this.refreshLampPoleDevices(device.deviceCode)
  1482. } catch (error) {
  1483. this.$message.error(`${ability.abilityName}执行失败:${error.message}`)
  1484. }
  1485. })
  1486. },
  1487. // 处理系统能力点击
  1488. handleSystemAbilityClick(ability) {
  1489. const paramType = this.getParamType(ability.paramDefinition)
  1490. if (paramType === 'Slider') {
  1491. this.showSliderDialog(ability, this.systemInfo, 3)
  1492. } else if (paramType === 'Input') {
  1493. this.showInputDialog(ability, this.systemInfo, 3)
  1494. } else {
  1495. // 无参数:直接执行
  1496. this.executeSystemAbility(ability)
  1497. }
  1498. },
  1499. // 执行系统能力(带Options参数)
  1500. executeSystemAbilityWithParam(ability, paramValue) {
  1501. ability.executing = true
  1502. ability.executingValue = paramValue
  1503. callAbility({
  1504. objCode: this.systemCode,
  1505. objType: 3,
  1506. modelCode: this.systemInfo.modelCode,
  1507. abilityKey: ability.abilityKey,
  1508. abilityParam: paramValue
  1509. }).then(res => {
  1510. this.executeDialog = {
  1511. visible: true,
  1512. title: '执行结果 - ' + ability.abilityName,
  1513. status: '执行成功',
  1514. type: 'success',
  1515. message: '系统能力执行完成',
  1516. data: res.data
  1517. }
  1518. }).catch(error => {
  1519. this.executeDialog = {
  1520. visible: true,
  1521. title: '执行结果 - ' + ability.abilityName,
  1522. status: '执行失败',
  1523. type: 'error',
  1524. message: error.message,
  1525. data: null
  1526. }
  1527. }).finally(() => {
  1528. ability.executing = false
  1529. ability.executingValue = null
  1530. })
  1531. },
  1532. // 执行系统能力(无参数)
  1533. executeSystemAbility(ability) {
  1534. ability.executing = true
  1535. callAbility({
  1536. objCode: this.systemCode,
  1537. objType: 3,
  1538. modelCode: this.systemInfo.modelCode,
  1539. abilityKey: ability.abilityKey,
  1540. abilityParam: null
  1541. }).then(res => {
  1542. this.executeDialog = {
  1543. visible: true,
  1544. title: '执行结果 - ' + ability.abilityName,
  1545. status: '执行成功',
  1546. type: 'success',
  1547. message: '系统能力执行完成',
  1548. data: res.data
  1549. }
  1550. // 刷新相关数据
  1551. if (ability.abilityKey === 'GetProjectList' || ability.abilityKey === 'GetProjectSubsetList') {
  1552. this.loadSystemInfo()
  1553. } else if (ability.abilityKey === 'GetDeviceList') {
  1554. this.loadLampPoleList()
  1555. this.loadConcentratorDevices()
  1556. }
  1557. }).catch(error => {
  1558. this.executeDialog = {
  1559. visible: true,
  1560. title: '执行结果 - ' + ability.abilityName,
  1561. status: '执行失败',
  1562. type: 'error',
  1563. message: error.message,
  1564. data: null
  1565. }
  1566. }).finally(() => {
  1567. ability.executing = false
  1568. })
  1569. },
  1570. // 显示滑块弹窗
  1571. showSliderDialog(ability, obj, objType) {
  1572. this.currentDevice = obj
  1573. this.currentObjType = objType
  1574. this.abilityForm = { ...ability }
  1575. // 根据对象类型设置标题
  1576. let objName
  1577. if (objType === 3) {
  1578. objName = this.systemInfo.systemName
  1579. } else if (objType === 2) {
  1580. objName = obj.deviceName || obj.deviceCode
  1581. }
  1582. this.abilityDialogTitle = `${ability.abilityName} - ${objName}`
  1583. this.abilityParamType = 'Slider'
  1584. try {
  1585. const def = JSON.parse(ability.paramDefinition)
  1586. this.abilitySliderMin = def.min || 0
  1587. this.abilitySliderMax = def.max || 100
  1588. this.abilitySliderValue = Math.floor((this.abilitySliderMin + this.abilitySliderMax) / 2)
  1589. } catch (e) {
  1590. console.error('解析Slider参数失败:', e)
  1591. this.abilitySliderMin = 0
  1592. this.abilitySliderMax = 100
  1593. this.abilitySliderValue = 50
  1594. }
  1595. this.abilityDialogVisible = true
  1596. },
  1597. // 显示输入弹窗
  1598. showInputDialog(ability, obj, objType) {
  1599. this.currentDevice = obj
  1600. this.currentObjType = objType
  1601. this.abilityForm = { ...ability }
  1602. // 根据对象类型设置标题
  1603. let objName
  1604. if (objType === 3) {
  1605. objName = this.systemInfo.systemName
  1606. } else if (objType === 2) {
  1607. objName = obj.deviceName || obj.deviceCode
  1608. }
  1609. this.abilityDialogTitle = `${ability.abilityName} - ${objName}`
  1610. this.abilityParamType = 'Input'
  1611. this.abilityInputValue = ''
  1612. this.abilityDialogVisible = true
  1613. },
  1614. // 执行带参数的能力
  1615. async executeAbilityWithParam() {
  1616. let paramValue = null
  1617. if (this.abilityParamType === 'Slider') {
  1618. paramValue = String(this.abilitySliderValue)
  1619. } else if (this.abilityParamType === 'Input') {
  1620. if (!this.abilityInputValue) {
  1621. this.$message.warning('请输入参数值')
  1622. return
  1623. }
  1624. paramValue = this.abilityInputValue
  1625. }
  1626. this.abilityExecuting = true
  1627. // 构建调用参数
  1628. const callParams = {
  1629. objType: this.currentObjType,
  1630. abilityKey: this.abilityForm.abilityKey,
  1631. abilityParam: paramValue
  1632. }
  1633. // 根据对象类型设置不同的参数
  1634. if (this.currentObjType === 3) {
  1635. // 系统
  1636. callParams.objCode = this.systemCode
  1637. callParams.modelCode = this.systemInfo.modelCode
  1638. } else if (this.currentObjType === 2) {
  1639. // 设备(包括灯组和灯杆)
  1640. callParams.objCode = this.currentDevice.deviceCode
  1641. const isLampPole = this.currentDevice.deviceModel === 'M_Z010_DEV_SQUARE_LAMP_POST'
  1642. callParams.modelCode = this.currentDevice.deviceModel || (isLampPole ? 'M_Z010_DEV_SQUARE_LAMP_POST' : 'M_Z010_DEV_SQUARE_LIGHT')
  1643. }
  1644. try {
  1645. await callAbility(callParams)
  1646. this.$message.success('执行成功')
  1647. this.abilityDialogVisible = false
  1648. // 根据对象类型刷新数据
  1649. if (this.currentObjType === 3) {
  1650. // 系统能力执行后的处理
  1651. if (this.abilityForm.abilityKey === 'GetProjectList' ||
  1652. this.abilityForm.abilityKey === 'GetProjectSubsetList') {
  1653. await this.loadSystemInfo()
  1654. } else if (this.abilityForm.abilityKey === 'GetDeviceList') {
  1655. await this.loadLampPoleList()
  1656. await this.loadConcentratorDevices()
  1657. }
  1658. } else if (this.currentObjType === 2) {
  1659. // 设备能力执行后,根据设备类型刷新
  1660. const isLampPole = this.currentDevice.deviceModel === 'M_Z010_DEV_SQUARE_LAMP_POST'
  1661. if (isLampPole) {
  1662. // 灯杆:刷新该灯杆下所有灯组
  1663. await this.refreshLampPoleDevices(this.currentDevice.deviceCode)
  1664. } else {
  1665. // 灯组:重新加载单个设备属性,传入modelCode
  1666. const modelCode = this.currentDevice.deviceModel || 'M_Z010_DEV_SQUARE_LIGHT'
  1667. await this.loadSingleLampGroupAttr(this.currentDevice.deviceCode, modelCode)
  1668. }
  1669. }
  1670. } catch (error) {
  1671. this.$message.error('执行失败:' + error.message)
  1672. } finally {
  1673. this.abilityExecuting = false
  1674. }
  1675. },
  1676. // 初始化时间范围(默认今天)
  1677. initDateRange() {
  1678. const now = new Date()
  1679. const startOfDay = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 0, 0, 0)
  1680. const endOfDay = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 23, 59, 59)
  1681. this.callLogQuery.dateRange = [
  1682. this.formatDate(startOfDay),
  1683. this.formatDate(endOfDay)
  1684. ]
  1685. this.eventLogQuery.dateRange = [
  1686. this.formatDate(startOfDay),
  1687. this.formatDate(endOfDay)
  1688. ]
  1689. },
  1690. // 格式化日期
  1691. formatDate(date) {
  1692. if (!date) return ''
  1693. const year = date.getFullYear()
  1694. const month = (date.getMonth() + 1).toString().padStart(2, '0')
  1695. const day = date.getDate().toString().padStart(2, '0')
  1696. const hours = date.getHours().toString().padStart(2, '0')
  1697. const minutes = date.getMinutes().toString().padStart(2, '0')
  1698. const seconds = date.getSeconds().toString().padStart(2, '0')
  1699. return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
  1700. },
  1701. // 加载系统信息
  1702. async loadSystemInfo() {
  1703. try {
  1704. // 1. 获取系统基础信息
  1705. const sysRes = await getSubsystemByCode(this.systemCode)
  1706. this.systemInfo = sysRes.data
  1707. // 2. 获取模型信息
  1708. if (this.systemInfo.modelCode) {
  1709. this.allModelCodes.push(this.systemInfo.modelCode)
  1710. const modelRes = await getModelByCode(this.systemInfo.modelCode)
  1711. const modelData = modelRes.data
  1712. // 获取能力列表,添加执行状态
  1713. this.systemAbilities = (modelData.abilityList || []).map(item => ({
  1714. ...item,
  1715. executing: false,
  1716. executingValue: null
  1717. }))
  1718. }
  1719. // 3. 获取系统属性值(系统不需要 modelCode)
  1720. const attrRes = await getObjAttr(3, this.systemCode)
  1721. const attrData = attrRes.data
  1722. this.protocolAttrs = attrData.Protocol || []
  1723. this.stateAttrs = attrData.State || []
  1724. this.baseAttrs = attrData.Base || []
  1725. // 解析项目列表和分组列表
  1726. this.parseBaseAttributes()
  1727. // 设置系统状态
  1728. const statusAttr = this.stateAttrs.find(attr => attr.attrKey === 'interfaceStatus')
  1729. if (statusAttr) {
  1730. this.systemStatus = statusAttr.attrValue
  1731. }
  1732. // 4. 加载智慧灯杆列表
  1733. await this.loadLampPoleList()
  1734. // 5. 加载集中器设备
  1735. await this.loadConcentratorDevices()
  1736. } catch (error) {
  1737. this.$message.error('加载系统信息失败:' + error.message)
  1738. console.error('加载系统信息错误:', error)
  1739. }
  1740. },
  1741. async loadDeviceAttrsBatch(modelCode, targetAttrsObj) {
  1742. if (!modelCode) {
  1743. console.warn('modelCode为空,跳过批量加载属性')
  1744. return
  1745. }
  1746. try {
  1747. const res = await getObjAttrBatch(2, modelCode)
  1748. // res.data 是一个对象,key是deviceCode,value是原来单个接口的data部分
  1749. if (res.data) {
  1750. Object.keys(res.data).forEach(deviceCode => {
  1751. this.$set(targetAttrsObj, deviceCode, res.data[deviceCode])
  1752. })
  1753. }
  1754. } catch (error) {
  1755. console.error(`批量加载设备属性失败(${modelCode}):`, error)
  1756. }
  1757. },
  1758. // 解析基础属性中的项目和分组信息
  1759. parseBaseAttributes() {
  1760. this.baseAttrs.forEach(attr => {
  1761. if (attr.attrKey === 'projectList' && attr.attrValue) {
  1762. try {
  1763. this.projectList = JSON.parse(attr.attrValue)
  1764. } catch (e) {
  1765. console.error('解析项目列表失败', e)
  1766. }
  1767. }
  1768. if (attr.attrKey === 'projectSubsetList' && attr.attrValue) {
  1769. try {
  1770. this.subsetList = JSON.parse(attr.attrValue)
  1771. } catch (e) {
  1772. console.error('解析分组列表失败', e)
  1773. }
  1774. }
  1775. })
  1776. },
  1777. // 加载智慧灯杆列表
  1778. async loadLampPoleList() {
  1779. this.lampPoleLoading = true
  1780. try {
  1781. // 1. 查询灯杆设备列表
  1782. const res = await getByCondition({
  1783. subsystemCode: this.systemCode,
  1784. deviceModel: 'M_Z010_DEV_SQUARE_LAMP_POST'
  1785. })
  1786. const poleData = res.data || res.rows || []
  1787. this.lampPoleList = poleData.map(pole => ({
  1788. ...pole,
  1789. detailTab: 'lamps'
  1790. }))
  1791. if (this.lampPoleList.length === 0) {
  1792. return
  1793. }
  1794. // 2. 加载灯杆物模型(用于获取能力列表)
  1795. await this.loadLampPoleModel()
  1796. // 3. 批量加载灯杆属性(获取subDev)
  1797. await this.loadDeviceAttrsBatch('M_Z010_DEV_SQUARE_LAMP_POST', this.lampPoleAttrs)
  1798. // 4. 收集所有灯组的deviceCode
  1799. const allLampGroupCodes = []
  1800. this.lampPoleList.forEach(pole => {
  1801. const lampGroups = this.getLampGroupCodesFromPole(pole.deviceCode)
  1802. allLampGroupCodes.push(...lampGroups)
  1803. })
  1804. // 5. 加载灯组物模型定义
  1805. await this.loadLampGroupModel()
  1806. // 6. 批量加载所有灯组的属性
  1807. if (allLampGroupCodes.length > 0) {
  1808. await this.loadDeviceAttrsBatch('M_Z010_DEV_SQUARE_LIGHT', this.lampGroupAttrs)
  1809. }
  1810. // 7. 构建灯组设备列表(用于统计和显示)
  1811. this.lampGroupList = allLampGroupCodes.map(item => ({
  1812. deviceCode: item.deviceCode,
  1813. deviceModel: item.modelCode,
  1814. detailTab: 'attrs'
  1815. }))
  1816. // 8. 统计数据
  1817. this.calculateLampGroupStats()
  1818. this.updateDeviceStats()
  1819. // 9. 为所有灯组初始化能力列表
  1820. this.initAllLampGroupAbilities()
  1821. // 10. 为所有灯杆初始化能力列表
  1822. this.initAllLampPoleAbilities()
  1823. } catch (error) {
  1824. this.$message.error('加载灯杆列表失败')
  1825. console.error('加载灯杆列表错误:', error)
  1826. } finally {
  1827. this.lampPoleLoading = false
  1828. }
  1829. },
  1830. // 为所有灯组初始化能力列表
  1831. initAllLampGroupAbilities() {
  1832. if (!this.lampGroupModel || !this.lampGroupModel.abilityList) {
  1833. console.warn('灯组模型或能力列表为空')
  1834. return
  1835. }
  1836. this.lampGroupList.forEach(lamp => {
  1837. const abilities = this.lampGroupModel.abilityList.map(ability => ({
  1838. ...ability,
  1839. executing: false,
  1840. executingValue: null
  1841. }))
  1842. this.$set(this.lampGroupAbilities, lamp.deviceCode, abilities)
  1843. })
  1844. console.log('已为', this.lampGroupList.length, '个灯组初始化能力列表')
  1845. },
  1846. // 为所有灯杆初始化能力列表
  1847. initAllLampPoleAbilities() {
  1848. if (!this.lampPoleModel || !this.lampPoleModel.abilityList) {
  1849. console.warn('灯杆模型或能力列表为空')
  1850. return
  1851. }
  1852. this.lampPoleList.forEach(pole => {
  1853. const abilities = this.lampPoleModel.abilityList
  1854. .filter(ability => ability.hiddenFlag === 1) // 只保留可显示的能力
  1855. .map(ability => ({
  1856. ...ability,
  1857. executing: false,
  1858. executingValue: null
  1859. }))
  1860. this.$set(this.lampPoleAbilities, pole.deviceCode, abilities)
  1861. })
  1862. console.log('已为', this.lampPoleList.length, '个灯杆初始化能力列表')
  1863. },
  1864. // 从灯杆属性中获取灯组代码列表(包含modelCode)
  1865. getLampGroupCodesFromPole(poleCode) {
  1866. const attrs = this.lampPoleAttrs[poleCode]
  1867. if (!attrs || !attrs.Base) return []
  1868. const subDevAttr = attrs.Base.find(a => a.attrKey === 'subDev')
  1869. if (subDevAttr && subDevAttr.attrValue) {
  1870. try {
  1871. const subDevData = JSON.parse(subDevAttr.attrValue)
  1872. // 新格式:subDev 是对象数组 [{deviceCode, modelCode}, ...]
  1873. if (Array.isArray(subDevData)) {
  1874. return subDevData.map(item => {
  1875. // 兼容新旧格式
  1876. if (typeof item === 'string') {
  1877. return { deviceCode: item, modelCode: 'M_Z010_DEV_SQUARE_LIGHT' }
  1878. }
  1879. return { deviceCode: item.deviceCode, modelCode: item.modelCode }
  1880. })
  1881. }
  1882. return []
  1883. } catch (e) {
  1884. console.error('解析灯组列表失败', e)
  1885. return []
  1886. }
  1887. }
  1888. return []
  1889. },
  1890. // 加载灯组物模型定义
  1891. async loadLampGroupModel() {
  1892. try {
  1893. const res = await getModelByCode('M_Z010_DEV_SQUARE_LIGHT')
  1894. this.lampGroupModel = res.data
  1895. } catch (error) {
  1896. console.error('加载灯组模型失败:', error)
  1897. }
  1898. },
  1899. // 加载灯杆物模型定义
  1900. async loadLampPoleModel() {
  1901. try {
  1902. const res = await getModelByCode('M_Z010_DEV_SQUARE_LAMP_POST')
  1903. this.lampPoleModel = res.data
  1904. console.log('灯杆物模型加载成功:', this.lampPoleModel)
  1905. } catch (error) {
  1906. console.error('加载灯杆模型失败:', error)
  1907. }
  1908. },
  1909. // 根据灯杆代码获取灯组列表
  1910. getLampGroupList(poleCode) {
  1911. const lampGroups = this.getLampGroupCodesFromPole(poleCode)
  1912. return lampGroups.map(item => ({
  1913. deviceCode: item.deviceCode,
  1914. deviceModel: item.modelCode,
  1915. detailTab: 'attrs'
  1916. }))
  1917. },
  1918. // 获取灯组模型属性(按属性组)
  1919. getLampGroupModelAttrs(deviceCode, attrGroup) {
  1920. if (!this.lampGroupModel || !this.lampGroupModel.attrList) return []
  1921. return this.lampGroupModel.attrList.filter(attr => attr.attrGroup === attrGroup)
  1922. },
  1923. // 获取灯组属性值
  1924. getLampGroupAttrValue(deviceCode, attrKey) {
  1925. const attrs = this.lampGroupAttrs[deviceCode]
  1926. if (!attrs) return null
  1927. // 搜索所有属性组
  1928. for (const group in attrs) {
  1929. if (attrs[group] && Array.isArray(attrs[group])) {
  1930. const attr = attrs[group].find(a => a.attrKey === attrKey)
  1931. if (attr) {
  1932. return attr.attrValue
  1933. }
  1934. }
  1935. }
  1936. return null
  1937. },
  1938. // 获取灯组枚举属性名称
  1939. getLampGroupAttrEnumName(deviceCode, attrKey, valueEnums) {
  1940. const value = this.getLampGroupAttrValue(deviceCode, attrKey)
  1941. if (!value || !valueEnums) return value || '-'
  1942. const enumItem = valueEnums.find(e => e.attrValue === value)
  1943. return enumItem ? enumItem.attrValueName : value
  1944. },
  1945. // 获取灯组更新时间
  1946. getLampGroupUpdateTime(deviceCode) {
  1947. const attrs = this.lampGroupAttrs[deviceCode]
  1948. if (!attrs) return null
  1949. // 从State组获取第一个有更新时间的属性
  1950. if (attrs.State && attrs.State.length > 0) {
  1951. const attrWithTime = attrs.State.find(a => a.updateTime)
  1952. return attrWithTime ? attrWithTime.updateTime : null
  1953. }
  1954. return null
  1955. },
  1956. // 获取灯杆下的在线灯组数量
  1957. getOnlineLampGroupCount(poleCode) {
  1958. const lampGroups = this.getLampGroupList(poleCode)
  1959. return lampGroups.filter(lamp => {
  1960. const deviceStatus = this.getLampGroupAttrValue(lamp.deviceCode, 'deviceStatus')
  1961. return deviceStatus === '1'
  1962. }).length
  1963. },
  1964. // 获取灯杆下的在线灯组数量
  1965. getOnSwitchLampGroupCount(poleCode) {
  1966. const lampGroups = this.getLampGroupList(poleCode)
  1967. return lampGroups.filter(lamp => {
  1968. const deviceStatus = this.getLampGroupAttrValue(lamp.deviceCode, 'Switch')
  1969. return deviceStatus === '1'
  1970. }).length
  1971. },
  1972. // 统计灯组数据
  1973. calculateLampGroupStats() {
  1974. this.totalLampCount = this.lampGroupList.length
  1975. this.onlineLampCount = this.lampGroupList.filter(lamp => {
  1976. const deviceStatus = this.getLampGroupAttrValue(lamp.deviceCode, 'deviceStatus')
  1977. return deviceStatus === '1'
  1978. }).length
  1979. // 统计开启的灯组和总功率
  1980. let onCount = 0
  1981. let totalPower = 0
  1982. this.lampGroupList.forEach(lamp => {
  1983. const lampOnOff = this.getLampGroupAttrValue(lamp.deviceCode, 'Switch')
  1984. if (lampOnOff === '1') {
  1985. onCount++
  1986. const power = parseFloat(this.getLampGroupAttrValue(lamp.deviceCode, 'power')) || 0
  1987. totalPower += power
  1988. }
  1989. })
  1990. this.lampStats.on = onCount
  1991. this.lampStats.off = this.totalLampCount - onCount
  1992. this.totalPower = totalPower
  1993. },
  1994. // 处理灯杆行点击
  1995. handleLampPoleClick(row, column, event) {
  1996. if (column && column.type === 'expand') {
  1997. return
  1998. }
  1999. this.$refs.lampPoleTable?.toggleRowExpansion(row)
  2000. },
  2001. // 处理灯组行点击
  2002. handleLampGroupClick(row, column, event, poleIndex) {
  2003. if (column && column.type === 'expand') {
  2004. return
  2005. }
  2006. const tableRef = this.$refs[`lampGroupTable_${poleIndex}`]
  2007. if (tableRef && tableRef[0]) {
  2008. tableRef[0].toggleRowExpansion(row)
  2009. }
  2010. },
  2011. // 处理灯组能力点击(在展开面板中)
  2012. handleLampGroupAbilityClick(lampGroup, ability) {
  2013. const paramType = this.getParamType(ability.paramDefinition)
  2014. if (paramType === 'Slider') {
  2015. this.showSliderDialog(ability, lampGroup, 2)
  2016. } else if (paramType === 'Input') {
  2017. this.showInputDialog(ability, lampGroup, 2)
  2018. } else {
  2019. // 无参数:直接执行
  2020. this.executeLampGroupAbility(lampGroup, ability)
  2021. }
  2022. },
  2023. // 执行灯组能力(无参数)- 在展开面板中调用
  2024. async executeLampGroupAbility(lampGroup, ability) {
  2025. ability.executing = true
  2026. try {
  2027. // 确保获取正确的modelCode
  2028. const modelCode = lampGroup.deviceModel || 'M_Z010_DEV_SQUARE_LIGHT'
  2029. console.log('执行灯组能力(无参数):', {
  2030. deviceCode: lampGroup.deviceCode,
  2031. modelCode: modelCode,
  2032. abilityKey: ability.abilityKey
  2033. })
  2034. await callAbility({
  2035. objCode: lampGroup.deviceCode,
  2036. objType: 2,
  2037. modelCode: modelCode,
  2038. abilityKey: ability.abilityKey,
  2039. abilityParam: null
  2040. })
  2041. this.$message.success(`${ability.abilityName}执行成功`)
  2042. // 重新加载单个灯组属性,必须传入modelCode
  2043. await this.loadSingleLampGroupAttr(lampGroup.deviceCode, modelCode)
  2044. } catch (error) {
  2045. console.error('执行灯组能力失败:', error)
  2046. this.$message.error(`${ability.abilityName}执行失败:${error.message}`)
  2047. } finally {
  2048. ability.executing = false
  2049. }
  2050. },
  2051. // 执行灯组能力(带参数)- 在展开面板中调用
  2052. async executeLampGroupAbilityWithParam(lampGroup, ability, paramValue) {
  2053. ability.executing = true
  2054. ability.executingValue = paramValue
  2055. try {
  2056. // 确保获取正确的modelCode
  2057. const modelCode = lampGroup.deviceModel || 'M_Z010_DEV_SQUARE_LIGHT'
  2058. console.log('执行灯组能力(带参数):', {
  2059. deviceCode: lampGroup.deviceCode,
  2060. modelCode: modelCode,
  2061. abilityKey: ability.abilityKey,
  2062. paramValue: paramValue
  2063. })
  2064. await callAbility({
  2065. objCode: lampGroup.deviceCode,
  2066. objType: 2,
  2067. modelCode: modelCode,
  2068. abilityKey: ability.abilityKey,
  2069. abilityParam: paramValue
  2070. })
  2071. this.$message.success(`${ability.abilityName}执行成功`)
  2072. // 重新加载单个灯组属性,必须传入modelCode
  2073. await this.loadSingleLampGroupAttr(lampGroup.deviceCode, modelCode)
  2074. } catch (error) {
  2075. console.error('执行灯组能力失败:', error)
  2076. this.$message.error(`${ability.abilityName}执行失败:${error.message}`)
  2077. } finally {
  2078. ability.executing = false
  2079. ability.executingValue = null
  2080. }
  2081. },
  2082. // 加载单个灯组属性 - 必须传入deviceCode和modelCode两个参数
  2083. async loadSingleLampGroupAttr(deviceCode, modelCode) {
  2084. if (!modelCode) {
  2085. console.error('loadSingleLampGroupAttr缺少modelCode参数!', { deviceCode, modelCode })
  2086. this.$message.error('加载灯组属性失败:缺少modelCode参数')
  2087. return
  2088. }
  2089. try {
  2090. console.log('加载灯组属性 - 调用getObjAttr:', {
  2091. objType: 2,
  2092. deviceCode: deviceCode,
  2093. modelCode: modelCode
  2094. })
  2095. // 必须传入modelCode参数
  2096. const res = await getObjAttr(2, deviceCode, modelCode)
  2097. this.$set(this.lampGroupAttrs, deviceCode, res.data || {})
  2098. // 重新统计
  2099. this.calculateLampGroupStats()
  2100. console.log('灯组属性加载成功:', deviceCode)
  2101. } catch (error) {
  2102. console.error('加载灯组属性失败:', { deviceCode, modelCode, error })
  2103. this.$message.error('加载灯组属性失败:' + error.message)
  2104. }
  2105. },
  2106. // 刷新灯杆下所有灯组设备的属性
  2107. async refreshLampPoleDevices(poleCode) {
  2108. try {
  2109. const lampGroups = this.getLampGroupCodesFromPole(poleCode)
  2110. if (lampGroups.length > 0) {
  2111. // 批量刷新所有灯组属性
  2112. await this.loadDeviceAttrsBatch('M_Z010_DEV_SQUARE_LIGHT', this.lampGroupAttrs)
  2113. // 重新统计
  2114. this.calculateLampGroupStats()
  2115. console.log(`已刷新灯杆 ${poleCode} 下的 ${lampGroups.length} 个灯组`)
  2116. }
  2117. } catch (error) {
  2118. console.error('刷新灯杆设备失败:', error)
  2119. this.$message.error('刷新灯杆设备失败:' + error.message)
  2120. }
  2121. },
  2122. // 加载集中器设备列表
  2123. async loadConcentratorDevices() {
  2124. this.concentratorLoading = true
  2125. try {
  2126. const res = await getByCondition({
  2127. subsystemCode: this.systemCode,
  2128. deviceModel: 'M_Z010_DEV_SQUARE_CONCENTRATOR'
  2129. })
  2130. const deviceData = res.data || res.rows || []
  2131. this.concentratorList = deviceData.map(device => ({
  2132. ...device,
  2133. detailTab: 'base'
  2134. }))
  2135. if (this.concentratorList.length === 0) {
  2136. this.updateDeviceStats()
  2137. return
  2138. }
  2139. // 批量加载所有集中器设备的属性
  2140. if (this.concentratorList[0].deviceModel) {
  2141. await this.loadDeviceAttrsBatch(this.concentratorList[0].deviceModel, this.concentratorAttrs)
  2142. }
  2143. // 更新设备统计
  2144. this.updateDeviceStats()
  2145. } catch (error) {
  2146. this.$message.error('加载集中器设备失败')
  2147. console.error('加载集中器设备错误:', error)
  2148. } finally {
  2149. this.concentratorLoading = false
  2150. }
  2151. },
  2152. // 获取集中器单个属性值
  2153. getConcentratorAttr(deviceCode, attrKey, attrGroup = 'State') {
  2154. const attrs = this.concentratorAttrs[deviceCode]
  2155. if (!attrs) return null
  2156. // 如果指定了属性组,则从该组查找
  2157. if (attrGroup && attrs[attrGroup]) {
  2158. const attr = attrs[attrGroup].find(a => a.attrKey === attrKey)
  2159. return attr ? attr.attrValue : null
  2160. }
  2161. // 如果没有指定组或该组不存在,默认从State组查找
  2162. if (attrs.State) {
  2163. const attr = attrs.State.find(a => a.attrKey === attrKey)
  2164. return attr ? attr.attrValue : null
  2165. }
  2166. return null
  2167. },
  2168. // 获取集中器更新时间
  2169. getConcentratorUpdateTime(deviceCode) {
  2170. const attrs = this.concentratorAttrs[deviceCode]
  2171. if (!attrs || !attrs.State || attrs.State.length === 0) return null
  2172. const attrWithTime = attrs.State.find(a => a.updateTime)
  2173. return attrWithTime ? attrWithTime.updateTime : null
  2174. },
  2175. // 获取电压状态
  2176. getVoltageStatus(deviceCode) {
  2177. const ua = parseFloat(this.getConcentratorAttr(deviceCode, 'ua'))
  2178. const ub = parseFloat(this.getConcentratorAttr(deviceCode, 'ub'))
  2179. const uc = parseFloat(this.getConcentratorAttr(deviceCode, 'uc'))
  2180. const isNormal = (voltage) => voltage >= 198 && voltage <= 242
  2181. const allNormal = isNormal(ua) && isNormal(ub) && isNormal(uc)
  2182. if (allNormal) {
  2183. return { type: 'success', text: '正常' }
  2184. } else {
  2185. const abnormalPhases = []
  2186. if (!isNormal(ua)) abnormalPhases.push('A')
  2187. if (!isNormal(ub)) abnormalPhases.push('B')
  2188. if (!isNormal(uc)) abnormalPhases.push('C')
  2189. return { type: 'warning', text: `${abnormalPhases.join('/')}相异常` }
  2190. }
  2191. },
  2192. // 获取电压样式类
  2193. getVoltageClass(voltageStr) {
  2194. const voltage = parseFloat(voltageStr)
  2195. if (!voltage || isNaN(voltage)) return ''
  2196. if (voltage >= 198 && voltage <= 242) {
  2197. return 'voltage-normal'
  2198. } else if (voltage < 198) {
  2199. return 'voltage-low'
  2200. } else {
  2201. return 'voltage-high'
  2202. }
  2203. },
  2204. // 更新设备统计
  2205. updateDeviceStats() {
  2206. const lampOnline = this.lampPoleList.filter(d => d.deviceStatus === 1).length
  2207. const lampOffline = this.lampPoleList.filter(d => d.deviceStatus !== 1).length
  2208. const concentratorOnline = this.concentratorList.filter(d => d.deviceStatus === 1).length
  2209. const concentratorOffline = this.concentratorList.filter(d => d.deviceStatus !== 1).length
  2210. this.deviceStats.total = this.lampPoleList.length + this.concentratorList.length
  2211. this.deviceStats.online = lampOnline + concentratorOnline
  2212. this.deviceStats.offline = lampOffline + concentratorOffline
  2213. this.deviceStats.concentrators = this.concentratorList.length
  2214. this.deviceStats.lamps = this.totalLampCount
  2215. },
  2216. // 处理集中器行点击
  2217. handleConcentratorClick(row, column, event) {
  2218. if (column && column.type === 'expand') {
  2219. return
  2220. }
  2221. this.$refs.concentratorTable?.toggleRowExpansion(row)
  2222. },
  2223. // 处理快速操作
  2224. async handleQuickAction(action) {
  2225. action.loading = true
  2226. try {
  2227. switch (action.key) {
  2228. case 'syncProject':
  2229. await this.executeAbilityByKey('GetProjectList', null)
  2230. break
  2231. case 'syncDevice':
  2232. await this.executeAbilityByKey('GetDeviceList', null)
  2233. break
  2234. case 'allOn':
  2235. await this.executeAbilityByKey('lightControlAll', "1")
  2236. break
  2237. case 'allOff':
  2238. await this.executeAbilityByKey('lightControlAll', "0")
  2239. break
  2240. }
  2241. this.$message.success(`${action.name}成功`)
  2242. } catch (error) {
  2243. this.$message.error(`${action.name}失败`)
  2244. } finally {
  2245. action.loading = false
  2246. }
  2247. },
  2248. // 根据key执行能力
  2249. async executeAbilityByKey(abilityKey, param) {
  2250. const ability = this.systemAbilities.find(a => a.abilityKey === abilityKey)
  2251. if (ability) {
  2252. await callAbility({
  2253. objCode: this.systemCode,
  2254. objType: 3,
  2255. modelCode: this.systemInfo.modelCode,
  2256. abilityKey: ability.abilityKey,
  2257. abilityParam: param
  2258. })
  2259. // 刷新数据
  2260. if (abilityKey === 'lightControlAll') {
  2261. // 全部开关灯后,批量刷新所有灯组设备属性
  2262. if (this.lampGroupList.length > 0) {
  2263. await this.loadDeviceAttrsBatch('M_Z010_DEV_SQUARE_LIGHT', this.lampGroupAttrs)
  2264. this.calculateLampGroupStats()
  2265. }
  2266. } else if (abilityKey === 'GetDeviceList') {
  2267. await this.loadLampPoleList()
  2268. await this.loadConcentratorDevices()
  2269. }
  2270. } else {
  2271. throw new Error(`能力 ${abilityKey} 未找到`)
  2272. }
  2273. },
  2274. // 标签页切换
  2275. handleTabClick(tab) {
  2276. if (tab.name === 'callLogs' && this.callLogList.length === 0) {
  2277. this.queryCallLogs()
  2278. } else if (tab.name === 'eventLogs' && this.eventLogList.length === 0) {
  2279. this.queryEventLogs()
  2280. }
  2281. },
  2282. // 查询调用日志
  2283. async queryCallLogs() {
  2284. this.logLoading = true
  2285. try {
  2286. const params = {
  2287. modelCodes: this.allModelCodes,
  2288. pageNum: this.callLogQuery.pageNum,
  2289. pageSize: this.callLogQuery.pageSize
  2290. }
  2291. if (this.callLogQuery.dateRange && this.callLogQuery.dateRange.length === 2) {
  2292. params.startRecTime = this.callLogQuery.dateRange[0]
  2293. params.endRecTime = this.callLogQuery.dateRange[1]
  2294. }
  2295. if (this.callLogQuery.abilityKey) {
  2296. params.abilityKey = this.callLogQuery.abilityKey
  2297. }
  2298. if (this.callLogQuery.callStatus !== '') {
  2299. params.callStatus = this.callLogQuery.callStatus
  2300. }
  2301. const res = await listCallLog(params)
  2302. this.callLogList = res.rows || []
  2303. this.callLogTotal = res.total || 0
  2304. } catch (error) {
  2305. this.$message.error('查询调用日志失败')
  2306. } finally {
  2307. this.logLoading = false
  2308. }
  2309. },
  2310. // 重置调用日志查询
  2311. resetCallLogQuery() {
  2312. this.initDateRange()
  2313. this.callLogQuery.abilityKey = ''
  2314. this.callLogQuery.callStatus = ''
  2315. this.callLogQuery.pageNum = 1
  2316. this.queryCallLogs()
  2317. },
  2318. // 查询事件日志
  2319. async queryEventLogs() {
  2320. this.eventLogLoading = true
  2321. try {
  2322. const params = {
  2323. modelCodes: this.allModelCodes,
  2324. pageNum: this.eventLogQuery.pageNum,
  2325. pageSize: this.eventLogQuery.pageSize
  2326. }
  2327. if (this.eventLogQuery.dateRange && this.eventLogQuery.dateRange.length === 2) {
  2328. params.startRecTime = this.eventLogQuery.dateRange[0]
  2329. params.endRecTime = this.eventLogQuery.dateRange[1]
  2330. }
  2331. if (this.eventLogQuery.eventKey) {
  2332. params.eventKey = this.eventLogQuery.eventKey
  2333. }
  2334. const res = await listEventLog(params)
  2335. this.eventLogList = res.rows || []
  2336. this.eventLogTotal = res.total || 0
  2337. } catch (error) {
  2338. this.$message.error('查询事件日志失败')
  2339. } finally {
  2340. this.eventLogLoading = false
  2341. }
  2342. },
  2343. // 重置事件日志查询
  2344. resetEventLogQuery() {
  2345. this.initDateRange()
  2346. this.eventLogQuery.eventKey = ''
  2347. this.eventLogQuery.pageNum = 1
  2348. this.queryEventLogs()
  2349. },
  2350. // 格式化调用状态
  2351. formatCallStatus(status) {
  2352. const statusMap = {
  2353. 0: '成功',
  2354. 1: '进行中',
  2355. 2: '失败'
  2356. }
  2357. return statusMap[status] || '未知'
  2358. },
  2359. // 获取调用状态类型
  2360. getCallStatusType(status) {
  2361. const typeMap = {
  2362. 0: 'success',
  2363. 1: 'warning',
  2364. 2: 'danger'
  2365. }
  2366. return typeMap[status] || 'info'
  2367. },
  2368. // 查看调用日志详情
  2369. async handleCallLogDetail(row) {
  2370. try {
  2371. const res = await getCallLog(row.id)
  2372. this.callLogDetailData = res.data
  2373. this.callLogDetailDialog = true
  2374. } catch (error) {
  2375. this.$message.error('获取调用日志详情失败')
  2376. }
  2377. },
  2378. // 查看事件日志详情
  2379. async handleEventLogDetail(row) {
  2380. try {
  2381. const res = await getEventLog(row.id)
  2382. this.eventLogDetailData = res.data
  2383. this.eventLogDetailDialog = true
  2384. } catch (error) {
  2385. this.$message.error('获取事件日志详情失败')
  2386. }
  2387. },
  2388. // 打开编辑系统信息对话框
  2389. handleEditSystem() {
  2390. // 填充表单数据
  2391. this.editSystemForm = {
  2392. id: this.systemInfo.id,
  2393. systemCode: this.systemInfo.systemCode,
  2394. systemName: this.systemInfo.systemName,
  2395. shortName: this.systemInfo.shortName,
  2396. manFacturer: this.systemInfo.manFacturer,
  2397. contactPerson: this.systemInfo.contactPerson,
  2398. contactNumber: this.systemInfo.contactNumber,
  2399. maintainerPerson: this.systemInfo.maintainerPerson,
  2400. maintainerNumber: this.systemInfo.maintainerNumber,
  2401. descr: this.systemInfo.descr,
  2402. modelCode: this.systemInfo.modelCode
  2403. }
  2404. this.editSystemDialogVisible = true
  2405. },
  2406. // 提交编辑系统信息
  2407. async submitEditSystem() {
  2408. this.$refs.editSystemForm.validate(async (valid) => {
  2409. if (!valid) {
  2410. return
  2411. }
  2412. this.editSystemSubmitting = true
  2413. try {
  2414. await updateSubsystem(this.editSystemForm)
  2415. this.$message.success('保存成功')
  2416. this.editSystemDialogVisible = false
  2417. // 重新加载系统信息
  2418. await this.loadSystemInfo()
  2419. } catch (error) {
  2420. this.$message.error('保存失败:' + error.message)
  2421. } finally {
  2422. this.editSystemSubmitting = false
  2423. }
  2424. })
  2425. }
  2426. }
  2427. }
  2428. </script>
  2429. <style lang="scss" scoped>
  2430. .app-container {
  2431. padding: 20px;
  2432. background: #f5f7fa;
  2433. min-height: calc(100vh - 84px);
  2434. }
  2435. .system-info-card {
  2436. margin-bottom: 20px;
  2437. .card-header {
  2438. display: flex;
  2439. justify-content: space-between;
  2440. align-items: center;
  2441. .title {
  2442. font-size: 18px;
  2443. font-weight: bold;
  2444. i {
  2445. margin-right: 8px;
  2446. color: #f39c12;
  2447. }
  2448. }
  2449. }
  2450. .info-group {
  2451. position: relative;
  2452. .group-title {
  2453. font-size: 14px;
  2454. font-weight: bold;
  2455. color: #606266;
  2456. margin-bottom: 12px;
  2457. padding-bottom: 6px;
  2458. border-bottom: 1px solid #EBEEF5;
  2459. }
  2460. }
  2461. .info-item {
  2462. margin-bottom: 12px;
  2463. font-size: 14px;
  2464. label {
  2465. color: #909399;
  2466. margin-right: 8px;
  2467. display: inline-block;
  2468. min-width: 80px;
  2469. }
  2470. .code {
  2471. font-family: monospace;
  2472. color: #f39c12;
  2473. background: #fff9e6;
  2474. padding: 2px 6px;
  2475. border-radius: 3px;
  2476. }
  2477. .stat-number {
  2478. color: #409EFF;
  2479. font-weight: bold;
  2480. font-size: 16px;
  2481. }
  2482. }
  2483. }
  2484. .detail-card {
  2485. .tab-content {
  2486. min-height: 400px;
  2487. }
  2488. .quick-actions {
  2489. margin-bottom: 30px;
  2490. .section-title {
  2491. margin: 0 0 15px 0;
  2492. color: #303133;
  2493. display: flex;
  2494. align-items: center;
  2495. i {
  2496. margin-right: 8px;
  2497. color: #409EFF;
  2498. }
  2499. }
  2500. .action-btn {
  2501. width: 100%;
  2502. height: 60px;
  2503. font-size: 14px;
  2504. border-radius: 8px;
  2505. transition: all 0.3s;
  2506. &:hover {
  2507. transform: translateY(-2px);
  2508. box-shadow: 0 4px 12px rgba(64, 158, 255, 0.3);
  2509. }
  2510. }
  2511. }
  2512. .monitor-panel {
  2513. margin-bottom: 30px;
  2514. .section-title {
  2515. margin: 20px 0 15px 0;
  2516. color: #303133;
  2517. display: flex;
  2518. align-items: center;
  2519. i {
  2520. margin-right: 8px;
  2521. color: #409EFF;
  2522. }
  2523. }
  2524. .monitor-card {
  2525. background: #fff;
  2526. border: 1px solid #EBEEF5;
  2527. border-radius: 8px;
  2528. padding: 20px;
  2529. display: flex;
  2530. align-items: center;
  2531. transition: all 0.3s;
  2532. cursor: pointer;
  2533. &:hover {
  2534. transform: translateY(-2px);
  2535. box-shadow: 0 4px 12px rgba(0,0,0,0.1);
  2536. }
  2537. .monitor-icon {
  2538. width: 50px;
  2539. height: 50px;
  2540. border-radius: 50%;
  2541. display: flex;
  2542. align-items: center;
  2543. justify-content: center;
  2544. font-size: 24px;
  2545. margin-right: 15px;
  2546. &.online {
  2547. background: #f0f9ff;
  2548. color: #67C23A;
  2549. }
  2550. &.offline {
  2551. background: #fef0f0;
  2552. color: #F56C6C;
  2553. }
  2554. &.lamps {
  2555. background: #fff9e6;
  2556. color: #f39c12;
  2557. }
  2558. &.concentrator {
  2559. background: #f4e4ff;
  2560. color: #9b59b6;
  2561. }
  2562. &.power {
  2563. background: #f0f2f5;
  2564. color: #909399;
  2565. }
  2566. }
  2567. .monitor-info {
  2568. .monitor-value {
  2569. font-size: 24px;
  2570. font-weight: bold;
  2571. color: #303133;
  2572. }
  2573. .monitor-label {
  2574. font-size: 14px;
  2575. color: #909399;
  2576. margin-top: 4px;
  2577. }
  2578. }
  2579. }
  2580. }
  2581. .concentrator-panel {
  2582. margin-top: 30px;
  2583. .section-title {
  2584. margin: 20px 0 15px 0;
  2585. color: #303133;
  2586. display: flex;
  2587. align-items: center;
  2588. i {
  2589. margin-right: 8px;
  2590. color: #9b59b6;
  2591. }
  2592. }
  2593. }
  2594. .concentrator-detail {
  2595. padding: 25px;
  2596. background: #f5f7fa;
  2597. h5 {
  2598. margin: 0 0 20px 0;
  2599. color: #303133;
  2600. font-size: 16px;
  2601. font-weight: 600;
  2602. display: flex;
  2603. align-items: center;
  2604. i {
  2605. margin-right: 8px;
  2606. color: #409EFF;
  2607. }
  2608. }
  2609. .param-group {
  2610. background: #fff;
  2611. border-radius: 8px;
  2612. padding: 15px;
  2613. margin-bottom: 20px;
  2614. border: 1px solid #e4e7ed;
  2615. .group-header {
  2616. display: flex;
  2617. justify-content: space-between;
  2618. align-items: center;
  2619. margin-bottom: 15px;
  2620. padding-bottom: 10px;
  2621. border-bottom: 2px solid #f0f2f5;
  2622. .group-title {
  2623. font-size: 14px;
  2624. font-weight: 600;
  2625. color: #606266;
  2626. }
  2627. }
  2628. .param-card {
  2629. background: #fafbfc;
  2630. border-radius: 6px;
  2631. padding: 15px;
  2632. border: 1px solid #ebeef5;
  2633. transition: all 0.3s;
  2634. position: relative;
  2635. &:hover {
  2636. transform: translateY(-2px);
  2637. box-shadow: 0 2px 8px rgba(0,0,0,0.08);
  2638. }
  2639. &.phase-a {
  2640. border-left: 4px solid #f39c12;
  2641. }
  2642. &.phase-b {
  2643. border-left: 4px solid #27ae60;
  2644. }
  2645. &.phase-c {
  2646. border-left: 4px solid #e74c3c;
  2647. }
  2648. &.voltage-normal {
  2649. background: linear-gradient(135deg, #f0f9ff 0%, #e6f7ff 100%);
  2650. .value {
  2651. color: #52c41a;
  2652. }
  2653. }
  2654. &.voltage-low {
  2655. background: linear-gradient(135deg, #fff7e6 0%, #ffe7ba 100%);
  2656. .value {
  2657. color: #fa8c16;
  2658. }
  2659. }
  2660. &.voltage-high {
  2661. background: linear-gradient(135deg, #fff1f0 0%, #ffccc7 100%);
  2662. .value {
  2663. color: #f5222d;
  2664. }
  2665. }
  2666. .phase-label {
  2667. font-size: 12px;
  2668. color: #909399;
  2669. margin-bottom: 8px;
  2670. font-weight: 600;
  2671. }
  2672. .param-label {
  2673. font-size: 12px;
  2674. color: #909399;
  2675. margin-bottom: 8px;
  2676. }
  2677. .param-value {
  2678. display: flex;
  2679. align-items: baseline;
  2680. .value {
  2681. font-size: 24px;
  2682. font-weight: 600;
  2683. color: #303133;
  2684. margin-right: 5px;
  2685. }
  2686. .unit {
  2687. font-size: 14px;
  2688. color: #606266;
  2689. }
  2690. }
  2691. .voltage-indicator {
  2692. margin-top: 8px;
  2693. .normal-range {
  2694. font-size: 11px;
  2695. color: #909399;
  2696. }
  2697. }
  2698. }
  2699. }
  2700. .update-time {
  2701. text-align: right;
  2702. font-size: 12px;
  2703. color: #909399;
  2704. margin-top: 10px;
  2705. i {
  2706. margin-right: 5px;
  2707. }
  2708. }
  2709. }
  2710. // 集中器描述列表样式
  2711. .concentrator-descriptions {
  2712. ::v-deep .el-descriptions__label {
  2713. background: #fafafa;
  2714. color: #606266;
  2715. font-weight: 500;
  2716. padding: 10px 12px;
  2717. width: 120px;
  2718. }
  2719. ::v-deep .el-descriptions__content {
  2720. background: #fff;
  2721. color: #303133;
  2722. padding: 10px 12px;
  2723. }
  2724. ::v-deep .el-descriptions-item__cell {
  2725. border-color: #e4e7ed;
  2726. }
  2727. }
  2728. .device-image {
  2729. h5 {
  2730. margin: 15px 0 10px 0;
  2731. color: #303133;
  2732. font-size: 14px;
  2733. font-weight: 600;
  2734. }
  2735. .el-image {
  2736. border-radius: 4px;
  2737. border: 1px solid #e4e7ed;
  2738. overflow: hidden;
  2739. }
  2740. }
  2741. .attr-section {
  2742. margin-bottom: 30px;
  2743. .section-title {
  2744. margin: 20px 0 15px 0;
  2745. color: #303133;
  2746. i {
  2747. margin-right: 8px;
  2748. color: #409EFF;
  2749. }
  2750. }
  2751. .url-text {
  2752. color: #409EFF;
  2753. font-family: monospace;
  2754. }
  2755. }
  2756. .project-section {
  2757. margin-bottom: 20px;
  2758. h5 {
  2759. margin: 15px 0 10px 0;
  2760. color: #606266;
  2761. font-size: 14px;
  2762. }
  2763. }
  2764. .device-stats {
  2765. margin-bottom: 20px;
  2766. .stat-item {
  2767. text-align: center;
  2768. padding: 20px;
  2769. background: #fff;
  2770. border: 1px solid #EBEEF5;
  2771. border-radius: 4px;
  2772. .stat-value {
  2773. font-size: 28px;
  2774. font-weight: bold;
  2775. color: #303133;
  2776. margin-bottom: 8px;
  2777. }
  2778. .stat-label {
  2779. font-size: 14px;
  2780. color: #909399;
  2781. }
  2782. &.online .stat-value {
  2783. color: #67C23A;
  2784. }
  2785. &.offline .stat-value {
  2786. color: #F56C6C;
  2787. }
  2788. &.lamps .stat-value {
  2789. color: #f39c12;
  2790. }
  2791. &.controllers .stat-value {
  2792. color: #409EFF;
  2793. }
  2794. }
  2795. }
  2796. // 灯杆主表格样式
  2797. .lamp-pole-main-table {
  2798. ::v-deep .el-table__expanded-cell {
  2799. padding: 15px 20px;
  2800. background: #fafbfc;
  2801. }
  2802. }
  2803. // 智慧灯杆展示样式
  2804. .lamp-pole-detail {
  2805. padding: 15px 20px;
  2806. background: #f9fafc;
  2807. .lamp-group-table {
  2808. border: 1px solid #e4e7ed;
  2809. border-radius: 4px;
  2810. overflow: hidden;
  2811. ::v-deep .el-table__header {
  2812. background: #f5f7fa;
  2813. th {
  2814. background: #f5f7fa !important;
  2815. font-weight: 600;
  2816. color: #606266;
  2817. padding: 12px 8px;
  2818. }
  2819. }
  2820. ::v-deep .el-table__body {
  2821. td {
  2822. padding: 12px 8px;
  2823. }
  2824. }
  2825. // 移除表格后多余空白
  2826. ::v-deep .el-table__empty-block {
  2827. display: none;
  2828. }
  2829. // 优化展开行样式
  2830. ::v-deep .el-table__expanded-cell {
  2831. padding: 15px 20px;
  2832. }
  2833. }
  2834. }
  2835. .lamp-group-detail {
  2836. padding: 15px;
  2837. background: #f5f7fa;
  2838. // Tab标签样式优化
  2839. ::v-deep .el-tabs__header {
  2840. margin-bottom: 15px;
  2841. }
  2842. ::v-deep .el-tabs__content {
  2843. padding: 0;
  2844. }
  2845. .attr-group-title {
  2846. margin: 15px 0 10px 0;
  2847. color: #303133;
  2848. font-size: 14px;
  2849. font-weight: 600;
  2850. padding-left: 10px;
  2851. border-left: 3px solid #409EFF;
  2852. }
  2853. // 优化描述列表样式
  2854. .lamp-group-descriptions {
  2855. ::v-deep .el-descriptions__label {
  2856. background: #fafafa;
  2857. color: #606266;
  2858. font-weight: 500;
  2859. padding: 10px 12px;
  2860. }
  2861. ::v-deep .el-descriptions__content {
  2862. background: #fff;
  2863. color: #303133;
  2864. padding: 10px 12px;
  2865. }
  2866. ::v-deep .el-descriptions-item__cell {
  2867. border-color: #e4e7ed;
  2868. }
  2869. }
  2870. // 灯组图片样式
  2871. .el-image {
  2872. border-radius: 4px;
  2873. border: 1px solid #e4e7ed;
  2874. overflow: hidden;
  2875. }
  2876. .update-time {
  2877. text-align: right;
  2878. font-size: 12px;
  2879. color: #909399;
  2880. padding-top: 10px;
  2881. margin-top: 10px;
  2882. border-top: 1px solid #e4e7ed;
  2883. i {
  2884. margin-right: 5px;
  2885. }
  2886. }
  2887. .ability-panel {
  2888. padding: 10px 0;
  2889. min-height: 200px;
  2890. .ability-card {
  2891. background: #fff;
  2892. border: 1px solid #e4e7ed;
  2893. border-radius: 8px;
  2894. padding: 15px;
  2895. margin-bottom: 15px;
  2896. transition: all 0.3s;
  2897. &:hover {
  2898. transform: translateY(-2px);
  2899. box-shadow: 0 4px 12px rgba(0,0,0,0.1);
  2900. border-color: #409EFF;
  2901. }
  2902. .ability-header {
  2903. display: flex;
  2904. align-items: center;
  2905. margin-bottom: 8px;
  2906. font-size: 14px;
  2907. font-weight: 600;
  2908. color: #303133;
  2909. i {
  2910. margin-right: 8px;
  2911. color: #409EFF;
  2912. font-size: 16px;
  2913. }
  2914. }
  2915. .ability-desc {
  2916. font-size: 12px;
  2917. color: #909399;
  2918. margin-bottom: 12px;
  2919. line-height: 1.5;
  2920. min-height: 20px;
  2921. }
  2922. .light-control-buttons {
  2923. display: flex;
  2924. gap: 8px;
  2925. .el-button {
  2926. flex: 1;
  2927. }
  2928. }
  2929. .ability-buttons {
  2930. width: 100%;
  2931. display: flex;
  2932. .el-button {
  2933. flex: 1;
  2934. }
  2935. }
  2936. // 单个按钮样式
  2937. > .el-button {
  2938. margin-top: 10px;
  2939. }
  2940. }
  2941. }
  2942. }
  2943. .log-filter {
  2944. margin-bottom: 20px;
  2945. padding: 15px;
  2946. background: #f5f7fa;
  2947. border-radius: 4px;
  2948. }
  2949. }
  2950. .execute-result {
  2951. .result-data {
  2952. margin-top: 20px;
  2953. pre {
  2954. background: #f5f7fa;
  2955. padding: 15px;
  2956. border-radius: 4px;
  2957. overflow: auto;
  2958. max-height: 400px;
  2959. font-family: monospace;
  2960. font-size: 12px;
  2961. }
  2962. }
  2963. }
  2964. .image-slot {
  2965. display: flex;
  2966. justify-content: center;
  2967. align-items: center;
  2968. width: 100%;
  2969. height: 100%;
  2970. background: #f5f7fa;
  2971. color: #909399;
  2972. font-size: 30px;
  2973. }
  2974. .dialog-footer {
  2975. text-align: right;
  2976. }
  2977. .device-detail,
  2978. .lamp-pole-detail,
  2979. .lamp-group-detail {
  2980. padding: 20px;
  2981. background: #f9fafc;
  2982. border-radius: 4px;
  2983. ::v-deep .el-tabs__header {
  2984. background: #fff;
  2985. border: 1px solid #e4e7ed;
  2986. border-radius: 4px;
  2987. margin-bottom: 15px;
  2988. padding: 0 10px;
  2989. }
  2990. ::v-deep .el-tabs__item {
  2991. height: 40px;
  2992. line-height: 40px;
  2993. font-size: 14px;
  2994. &.is-active {
  2995. color: #409EFF;
  2996. font-weight: bold;
  2997. }
  2998. }
  2999. ::v-deep .el-tabs__nav-wrap::after {
  3000. display: none;
  3001. }
  3002. }
  3003. // 密码字段样式
  3004. .password-field {
  3005. display: inline-flex;
  3006. align-items: center;
  3007. gap: 8px;
  3008. .password-toggle-icon {
  3009. cursor: pointer;
  3010. color: #909399;
  3011. font-size: 16px;
  3012. transition: color 0.3s;
  3013. &:hover {
  3014. color: #409EFF;
  3015. }
  3016. }
  3017. }
  3018. </style>