index.vue 73 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057
  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-s-platform"></i>
  8. 能耗监测
  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">
  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>
  27. </el-col>
  28. <el-col :span="6">
  29. <div class="info-group">
  30. <div class="group-title">厂商信息</div>
  31. <div class="info-item">
  32. <label>对接厂商:</label>
  33. <span>{{ systemInfo.manFacturer || '-' }}</span>
  34. </div>
  35. <div class="info-item">
  36. <label>联系人:</label>
  37. <span>{{ systemInfo.contactPerson || '-' }}</span>
  38. </div>
  39. <div class="info-item">
  40. <label>联系电话:</label>
  41. <span>{{ systemInfo.contactNumber || '-' }}</span>
  42. </div>
  43. </div>
  44. </el-col>
  45. <el-col :span="6">
  46. <div class="info-group">
  47. <div class="group-title">维护信息</div>
  48. <div class="info-item">
  49. <label>维护人:</label>
  50. <span>{{ systemInfo.maintainerPerson || '-' }}</span>
  51. </div>
  52. <div class="info-item">
  53. <label>维护电话:</label>
  54. <span>{{ systemInfo.maintainerNumber || '-' }}</span>
  55. </div>
  56. </div>
  57. </el-col>
  58. <el-col :span="6">
  59. <div class="info-group">
  60. <div class="group-title">设备统计</div>
  61. <div class="info-item">
  62. <label>网关设备:</label>
  63. <span class="stat-number">{{ deviceStats.total }}</span>
  64. </div>
  65. <div class="info-item">
  66. <label>在线设备:</label>
  67. <span class="stat-number">{{ deviceStats.online }}</span>
  68. </div>
  69. <div class="info-item">
  70. <label>测点总数:</label>
  71. <span class="stat-number">{{ deviceStats.points }}</span>
  72. </div>
  73. <div style="text-align: right; margin-top: 10px;">
  74. <el-button
  75. type="primary"
  76. size="mini"
  77. icon="el-icon-edit"
  78. @click="handleEdit"
  79. v-hasPermi="['ems:subsystem:edit']">
  80. 编辑
  81. </el-button>
  82. </div>
  83. </div>
  84. </el-col>
  85. </el-row>
  86. </el-card>
  87. <!-- 系统详情标签页 -->
  88. <el-card class="detail-card">
  89. <el-tabs v-model="activeTab" @tab-click="handleTabClick">
  90. <!-- 系统属性标签页 -->
  91. <el-tab-pane label="系统属性" name="attributes">
  92. <div class="tab-content">
  93. <!-- 协议属性 -->
  94. <div class="attr-section" v-if="protocolAttrs.length > 0">
  95. <h4 class="section-title">
  96. <i class="el-icon-connection"></i>
  97. 协议信息
  98. </h4>
  99. <el-table :data="protocolAttrs" border stripe>
  100. <el-table-column prop="attrName" label="属性名称" width="200"></el-table-column>
  101. <el-table-column label="属性值">
  102. <template slot-scope="scope">
  103. <span v-if="scope.row.attrKey === 'url'" class="url-text">
  104. {{ scope.row.attrValue }}
  105. </span>
  106. <span v-else>{{ scope.row.attrValue || '-' }}</span>
  107. </template>
  108. </el-table-column>
  109. </el-table>
  110. </div>
  111. <!-- 状态属性 -->
  112. <div class="attr-section" v-if="stateAttrs.length > 0">
  113. <h4 class="section-title">
  114. <i class="el-icon-info"></i>
  115. 状态信息
  116. </h4>
  117. <el-table :data="stateAttrs" border stripe>
  118. <el-table-column prop="attrName" label="属性名称" width="200"></el-table-column>
  119. <el-table-column label="属性值">
  120. <template slot-scope="scope">
  121. <el-tag v-if="scope.row.attrKey === 'interfaceStatus'"
  122. :type="scope.row.attrValue === '1' ? 'success' : 'danger'">
  123. {{ scope.row.attrValueName }}
  124. </el-tag>
  125. <span v-else>{{ scope.row.attrValueName || scope.row.attrValue || '-' }}</span>
  126. </template>
  127. </el-table-column>
  128. <el-table-column prop="updateTime" label="更新时间" width="180"></el-table-column>
  129. </el-table>
  130. </div>
  131. </div>
  132. </el-tab-pane>
  133. <!-- 系统能力标签页 -->
  134. <el-tab-pane label="系统能力" name="abilities">
  135. <div class="tab-content">
  136. <el-table :data="systemAbilities" border stripe v-loading="abilityLoading">
  137. <el-table-column prop="abilityName" label="能力名称" width="200"></el-table-column>
  138. <el-table-column prop="abilityKey" label="能力标识" width="180"></el-table-column>
  139. <el-table-column prop="abilityDesc" label="能力描述"></el-table-column>
  140. <el-table-column label="操作" width="200" align="center">
  141. <template slot-scope="scope">
  142. <div v-if="getParamType(scope.row.paramDefinition) === 'Options'" style="display: inline-block;">
  143. <el-button-group>
  144. <el-button
  145. v-for="option in parseOptions(scope.row.paramDefinition)"
  146. :key="option.value"
  147. size="mini"
  148. type="primary"
  149. @click="executeSystemAbilityWithParam(scope.row, option.value)"
  150. :loading="scope.row.executing && scope.row.executingValue === option.value">
  151. {{ option.key }}
  152. </el-button>
  153. </el-button-group>
  154. </div>
  155. <el-button
  156. v-else
  157. type="primary"
  158. size="mini"
  159. icon="el-icon-caret-right"
  160. @click="handleSystemAbilityClick(scope.row)"
  161. :loading="scope.row.executing">
  162. 执行
  163. </el-button>
  164. </template>
  165. </el-table-column>
  166. </el-table>
  167. </div>
  168. </el-tab-pane>
  169. <!-- 能耗采集标签页 -->
  170. <el-tab-pane label="能耗采集" name="devBaGa">
  171. <div class="tab-content">
  172. <!-- 设备统计 -->
  173. <div class="device-stats">
  174. <el-row :gutter="20">
  175. <el-col :span="6">
  176. <div class="stat-item">
  177. <div class="stat-value">{{ deviceStats.total }}</div>
  178. <div class="stat-label">设备总数</div>
  179. </div>
  180. </el-col>
  181. <el-col :span="6">
  182. <div class="stat-item online">
  183. <div class="stat-value">{{ deviceStats.online }}</div>
  184. <div class="stat-label">在线设备</div>
  185. </div>
  186. </el-col>
  187. <el-col :span="6">
  188. <div class="stat-item offline">
  189. <div class="stat-value">{{ deviceStats.offline }}</div>
  190. <div class="stat-label">离线设备</div>
  191. </div>
  192. </el-col>
  193. <el-col :span="6">
  194. <div class="stat-item">
  195. <div class="stat-value">{{ deviceStats.points }}</div>
  196. <div class="stat-label">测点总数</div>
  197. </div>
  198. </el-col>
  199. </el-row>
  200. </div>
  201. <!-- 设备列表 -->
  202. <el-table
  203. :data="deviceList"
  204. border
  205. stripe
  206. v-loading="deviceLoading"
  207. @row-click="handleDeviceClick"
  208. ref="deviceTable">
  209. <el-table-column type="expand">
  210. <template slot-scope="props">
  211. <div class="device-detail">
  212. <el-tabs v-model="props.row.detailTab">
  213. <el-tab-pane label="设备属性" name="attrs">
  214. <el-descriptions :column="2" border size="small">
  215. <el-descriptions-item label="IP地址">
  216. {{ getDeviceAttr(props.row.deviceCode, 'ip') }}
  217. </el-descriptions-item>
  218. <el-descriptions-item label="网关地址">
  219. {{ getDeviceAttr(props.row.deviceCode, 'gateway') }}
  220. </el-descriptions-item>
  221. <el-descriptions-item label="子网掩码">
  222. {{ getDeviceAttr(props.row.deviceCode, 'subnetMask') }}
  223. </el-descriptions-item>
  224. </el-descriptions>
  225. </el-tab-pane>
  226. <el-tab-pane label="测点信息" name="points">
  227. <div v-for="(channel, idx) in getDeviceChannels(props.row.deviceCode)" :key="idx">
  228. <h5>{{ channel.name }} ({{ channel.points.length }}个测点)</h5>
  229. <el-table :data="channel.points" size="mini" max-height="200">
  230. <el-table-column prop="name" label="测点名称" min-width="180"></el-table-column>
  231. <el-table-column prop="key" label="测点标识" width="150"></el-table-column>
  232. <el-table-column prop="location" label="位置" min-width="120"></el-table-column>
  233. <el-table-column prop="desc" label="描述" min-width="120"></el-table-column>
  234. <el-table-column label="当前值" width="120">
  235. <template slot-scope="scope">
  236. <span>{{ scope.row.value || '-' }}</span>
  237. <span v-if="scope.row.unit" style="margin-left: 4px; color: #909399; font-size: 12px;">
  238. {{ scope.row.unit }}
  239. </span>
  240. </template>
  241. </el-table-column>
  242. <el-table-column prop="updateTime" label="更新时间" width="160"></el-table-column>
  243. </el-table>
  244. </div>
  245. <el-empty v-if="getDeviceChannels(props.row.deviceCode).length === 0" description="暂无测点数据"></el-empty>
  246. </el-tab-pane>
  247. </el-tabs>
  248. </div>
  249. </template>
  250. </el-table-column>
  251. <el-table-column prop="deviceCode" label="设备代码" width="150"></el-table-column>
  252. <el-table-column prop="deviceName" label="设备名称"></el-table-column>
  253. <el-table-column prop="location" label="安装位置"></el-table-column>
  254. <el-table-column prop="areaCode" label="区域" width="150">
  255. <template slot-scope="scope">
  256. {{ scope.row.areaCode.includes('3001') ? '北区' : '南区' }}
  257. </template>
  258. </el-table-column>
  259. <el-table-column label="设备状态" width="100" align="center">
  260. <template slot-scope="scope">
  261. <el-tag :type="scope.row.deviceStatus === 1 ? 'success' : 'info'">
  262. {{ scope.row.deviceStatus === 1 ? '在线' : '离线' }}
  263. </el-tag>
  264. </template>
  265. </el-table-column>
  266. <el-table-column label="操作" width="120" align="center">
  267. <template slot-scope="scope">
  268. <el-dropdown
  269. trigger="click"
  270. @command="handleDeviceCommand"
  271. @visible-change="(visible) => handleAbilityLoad(visible, scope.row)">
  272. <el-button type="text" size="mini">
  273. 操作<i class="el-icon-arrow-down el-icon--right"></i>
  274. </el-button>
  275. <el-dropdown-menu slot="dropdown">
  276. <template v-if="deviceAbilities[scope.row.deviceCode] && deviceAbilities[scope.row.deviceCode].length > 0">
  277. <template v-for="ability in deviceAbilities[scope.row.deviceCode]">
  278. <el-submenu
  279. v-if="getParamType(ability.paramDefinition) === 'Options'"
  280. :key="ability.abilityKey"
  281. :index="ability.abilityKey">
  282. <template slot="title">
  283. {{ ability.abilityName }}
  284. <i class="el-icon-arrow-right" style="float: right;"></i>
  285. </template>
  286. <el-dropdown-item
  287. v-for="option in parseOptions(ability.paramDefinition)"
  288. :key="option.value"
  289. :command="{device: scope.row, ability: ability, paramValue: option.value}">
  290. {{ option.key }}
  291. </el-dropdown-item>
  292. </el-submenu>
  293. <el-dropdown-item
  294. v-else
  295. :key="ability.abilityKey"
  296. :command="{device: scope.row, ability: ability}">
  297. {{ ability.abilityName }}
  298. </el-dropdown-item>
  299. </template>
  300. </template>
  301. <el-dropdown-item v-else disabled>
  302. 无可用操作
  303. </el-dropdown-item>
  304. </el-dropdown-menu>
  305. </el-dropdown>
  306. </template>
  307. </el-table-column>
  308. </el-table>
  309. </div>
  310. </el-tab-pane>
  311. <el-tab-pane label="楼控设备" name="baDevices">
  312. <div class="tab-content">
  313. <!-- 设备类型选择 -->
  314. <div class="ba-device-header">
  315. <el-select v-model="selectedBaDeviceModel" placeholder="请选择设备类型" @change="handleBaDeviceTypeChange" style="width: 200px;">
  316. <el-option
  317. v-for="item in baDeviceModels"
  318. :key="item.modelCode"
  319. :label="item.modelName"
  320. :value="item.modelCode">
  321. </el-option>
  322. </el-select>
  323. <el-button type="primary" icon="el-icon-refresh" @click="refreshBaDevices" :loading="baDeviceLoading" style="margin-left: 10px;">
  324. 刷新
  325. </el-button>
  326. </div>
  327. <!-- 设备列表 -->
  328. <el-table
  329. :data="baDeviceList"
  330. border
  331. stripe
  332. v-loading="baDeviceLoading"
  333. style="margin-top: 15px;">
  334. <el-table-column type="expand" width="50">
  335. <template slot-scope="props">
  336. <div class="ba-device-detail">
  337. <el-tabs v-model="props.row.detailTab">
  338. <!-- 基础属性 -->
  339. <el-tab-pane label="基础属性" name="base">
  340. <el-descriptions :column="2" border size="small" v-if="getBaDeviceAttrs(props.row.deviceCode, 'Base').length > 0">
  341. <el-descriptions-item
  342. v-for="attr in getBaDeviceAttrs(props.row.deviceCode, 'Base')"
  343. :key="attr.attrKey"
  344. :label="attr.attrName">
  345. {{ attr.attrValue || '-' }}
  346. </el-descriptions-item>
  347. </el-descriptions>
  348. <el-empty v-else description="暂无基础属性"></el-empty>
  349. </el-tab-pane>
  350. <!-- 状态属性 -->
  351. <el-tab-pane label="状态属性" name="state">
  352. <el-table
  353. :data="getBaDeviceAttrs(props.row.deviceCode, 'State')"
  354. border
  355. size="small"
  356. v-if="getBaDeviceAttrs(props.row.deviceCode, 'State').length > 0">
  357. <el-table-column prop="attrName" label="属性名称" width="200"></el-table-column>
  358. <el-table-column label="属性值" min-width="180">
  359. <template slot-scope="scope">
  360. <el-tag v-if="scope.row.attrValueType === 'Enum' && scope.row.attrValueName" size="small">
  361. {{ scope.row.attrValueName }}
  362. </el-tag>
  363. <span v-else>
  364. {{ formatBaAttrValue(scope.row) }}
  365. </span>
  366. </template>
  367. </el-table-column>
  368. <el-table-column prop="updateTime" label="更新时间" width="180"></el-table-column>
  369. </el-table>
  370. <el-empty v-else description="暂无状态数据"></el-empty>
  371. </el-tab-pane>
  372. </el-tabs>
  373. </div>
  374. </template>
  375. </el-table-column>
  376. <el-table-column prop="deviceCode" label="设备代码" width="160" show-overflow-tooltip></el-table-column>
  377. <el-table-column prop="deviceName" label="设备名称" width="200" show-overflow-tooltip></el-table-column>
  378. <el-table-column prop="location" label="安装位置" width="180" show-overflow-tooltip></el-table-column>
  379. <el-table-column prop="areaCode" label="区域" width="100" align="center">
  380. <template slot-scope="scope">
  381. {{ scope.row.areaCode && scope.row.areaCode.includes('3001') ? '北区' : '南区' }}
  382. </template>
  383. </el-table-column>
  384. <el-table-column label="设备状态" width="100" align="center">
  385. <template slot-scope="scope">
  386. <el-tag :type="scope.row.deviceStatus === 1 ? 'success' : 'info'">
  387. {{ scope.row.deviceStatus === 1 ? '在线' : '离线' }}
  388. </el-tag>
  389. </template>
  390. </el-table-column>
  391. <!-- 新增操作列 -->
  392. <el-table-column label="操作" min-width="220" align="center">
  393. <template slot-scope="scope">
  394. <!-- 获取当前设备模型的能力列表 -->
  395. <template v-if="getBaDeviceAbilities(scope.row.deviceModel).length > 0">
  396. <template v-for="ability in getBaDeviceAbilities(scope.row.deviceModel)">
  397. <!-- 如果能力有 Options 参数定义,展示多个按钮 -->
  398. <el-button-group v-if="getParamType(ability.paramDefinition) === 'Options'" :key="ability.abilityKey" style="margin: 2px;">
  399. <el-button
  400. v-for="option in parseOptions(ability.paramDefinition)"
  401. :key="option.value"
  402. size="mini"
  403. type="primary"
  404. @click="executeBaDeviceAbility(scope.row, ability, option.value)"
  405. :loading="isBaAbilityExecuting(scope.row.deviceCode, ability.abilityKey, option.value)">
  406. {{ option.key }}
  407. </el-button>
  408. </el-button-group>
  409. <!-- 如果能力有其他参数类型(Slider/Input),显示单个按钮 -->
  410. <el-button
  411. v-else-if="getParamType(ability.paramDefinition) === 'Slider' || getParamType(ability.paramDefinition) === 'Input'"
  412. :key="ability.abilityKey"
  413. size="mini"
  414. type="primary"
  415. @click="handleBaDeviceAbilityClick(scope.row, ability)"
  416. :loading="isBaAbilityExecuting(scope.row.deviceCode, ability.abilityKey)"
  417. style="margin: 2px;">
  418. {{ ability.abilityName }}
  419. </el-button>
  420. <!-- 如果能力无参数,直接执行 -->
  421. <el-button
  422. v-else
  423. :key="ability.abilityKey"
  424. size="mini"
  425. type="primary"
  426. @click="executeBaDeviceAbility(scope.row, ability, null)"
  427. :loading="isBaAbilityExecuting(scope.row.deviceCode, ability.abilityKey)"
  428. style="margin: 2px;">
  429. {{ ability.abilityName }}
  430. </el-button>
  431. </template>
  432. </template>
  433. <span v-else style="color: #909399; font-size: 12px;">无可用操作</span>
  434. </template>
  435. </el-table-column>
  436. </el-table>
  437. </div>
  438. </el-tab-pane>
  439. <!-- 调用日志标签页 -->
  440. <el-tab-pane label="调用日志" name="callLogs">
  441. <div class="tab-content">
  442. <el-form :inline="true" :model="callLogQuery" class="log-filter">
  443. <el-form-item label="模型类型">
  444. <el-select v-model="selectedLogModel" placeholder="请选择模型类型" clearable style="width: 200px;">
  445. <el-option
  446. v-for="item in logModelOptions"
  447. :key="item.modelCode"
  448. :label="item.modelName"
  449. :value="item.modelCode">
  450. </el-option>
  451. </el-select>
  452. </el-form-item>
  453. <el-form-item label="时间范围">
  454. <el-date-picker
  455. v-model="callLogQuery.dateRange"
  456. type="datetimerange"
  457. range-separator="至"
  458. start-placeholder="开始日期"
  459. end-placeholder="结束日期"
  460. value-format="yyyy-MM-dd HH:mm:ss">
  461. </el-date-picker>
  462. </el-form-item>
  463. <el-form-item label="能力标识">
  464. <el-input v-model="callLogQuery.abilityKey" placeholder="请输入能力标识" clearable></el-input>
  465. </el-form-item>
  466. <el-form-item label="调用状态">
  467. <el-select v-model="callLogQuery.callStatus" placeholder="全部" clearable>
  468. <el-option label="成功" :value="0"></el-option>
  469. <el-option label="进行中" :value="1"></el-option>
  470. <el-option label="失败" :value="2"></el-option>
  471. </el-select>
  472. </el-form-item>
  473. <el-form-item>
  474. <el-button type="primary" @click="queryCallLogs">查询</el-button>
  475. <el-button @click="resetCallLogQuery">重置</el-button>
  476. </el-form-item>
  477. </el-form>
  478. <el-table :data="callLogList" border stripe v-loading="logLoading">
  479. <el-table-column prop="objCode" label="对象代码" min-width="150"></el-table-column>
  480. <el-table-column prop="objName" label="对象名称" min-width="200"></el-table-column>
  481. <el-table-column label="能力名称" min-width="150">
  482. <template slot-scope="scope">
  483. {{ scope.row.abilityName || scope.row.abilityKey }}
  484. </template>
  485. </el-table-column>
  486. <el-table-column prop="callTime" label="调用时间" width="180"></el-table-column>
  487. <el-table-column label="调用状态" width="100" align="center">
  488. <template slot-scope="scope">
  489. <el-tag size="small" :type="getCallStatusType(scope.row.callStatus)">
  490. {{ formatCallStatus(scope.row.callStatus) }}
  491. </el-tag>
  492. </template>
  493. </el-table-column>
  494. <el-table-column label="操作" width="80" align="center">
  495. <template slot-scope="scope">
  496. <el-button type="text" size="mini" @click="handleCallLogDetail(scope.row)">详情</el-button>
  497. </template>
  498. </el-table-column>
  499. </el-table>
  500. <pagination
  501. v-show="callLogTotal > 0"
  502. :total="callLogTotal"
  503. :page.sync="callLogQuery.pageNum"
  504. :limit.sync="callLogQuery.pageSize"
  505. @pagination="queryCallLogs"
  506. />
  507. </div>
  508. </el-tab-pane>
  509. <!-- 事件日志标签页 -->
  510. <el-tab-pane label="事件日志" name="eventLogs">
  511. <div class="tab-content">
  512. <el-form :inline="true" :model="eventLogQuery" class="log-filter">
  513. <el-form-item label="模型类型">
  514. <el-select v-model="selectedEventLogModel" placeholder="请选择模型类型" clearable style="width: 200px;">
  515. <el-option
  516. v-for="item in logModelOptions"
  517. :key="item.modelCode"
  518. :label="item.modelName"
  519. :value="item.modelCode">
  520. </el-option>
  521. </el-select>
  522. </el-form-item>
  523. <el-form-item label="时间范围">
  524. <el-date-picker
  525. v-model="eventLogQuery.dateRange"
  526. type="datetimerange"
  527. range-separator="至"
  528. start-placeholder="开始日期"
  529. end-placeholder="结束日期"
  530. value-format="yyyy-MM-dd HH:mm:ss">
  531. </el-date-picker>
  532. </el-form-item>
  533. <el-form-item label="事件标识">
  534. <el-input v-model="eventLogQuery.eventKey" placeholder="请输入事件标识" clearable></el-input>
  535. </el-form-item>
  536. <el-form-item>
  537. <el-button type="primary" @click="queryEventLogs">查询</el-button>
  538. <el-button @click="resetEventLogQuery">重置</el-button>
  539. </el-form-item>
  540. </el-form>
  541. <el-table :data="eventLogList" border stripe v-loading="eventLogLoading">
  542. <el-table-column prop="objCode" label="对象编号" min-width="150"></el-table-column>
  543. <el-table-column prop="objName" label="对象名称" min-width="200"></el-table-column>
  544. <el-table-column prop="eventName" label="事件名称" min-width="200"></el-table-column>
  545. <el-table-column prop="eventTime" label="事件时间" width="180"></el-table-column>
  546. <el-table-column label="操作" width="80" align="center">
  547. <template slot-scope="scope">
  548. <el-button type="text" size="mini" @click="handleEventLogDetail(scope.row)">详情</el-button>
  549. </template>
  550. </el-table-column>
  551. </el-table>
  552. <pagination
  553. v-show="eventLogTotal > 0"
  554. :total="eventLogTotal"
  555. :page.sync="eventLogQuery.pageNum"
  556. :limit.sync="eventLogQuery.pageSize"
  557. @pagination="queryEventLogs"
  558. />
  559. </div>
  560. </el-tab-pane>
  561. </el-tabs>
  562. </el-card>
  563. <!-- 修改系统信息对话框 -->
  564. <el-dialog title="修改系统信息" :visible.sync="editDialogVisible" width="500px" append-to-body>
  565. <el-form ref="editForm" :model="editForm" :rules="editRules" label-width="80px">
  566. <el-form-item label="系统代码" prop="systemCode">
  567. <el-input v-model="editForm.systemCode" placeholder="请输入系统代码" disabled />
  568. </el-form-item>
  569. <el-form-item label="系统名称" prop="systemName">
  570. <el-input v-model="editForm.systemName" placeholder="请输入系统名称" />
  571. </el-form-item>
  572. <el-form-item label="系统简称" prop="shortName">
  573. <el-input v-model="editForm.shortName" placeholder="请输入系统简称" />
  574. </el-form-item>
  575. <el-form-item label="厂商" prop="manFacturer">
  576. <el-input v-model="editForm.manFacturer" placeholder="请输入厂商" />
  577. </el-form-item>
  578. <el-form-item label="联系人" prop="contactPerson">
  579. <el-input v-model="editForm.contactPerson" placeholder="请输入联系人" />
  580. </el-form-item>
  581. <el-form-item label="联系电话" prop="contactNumber">
  582. <el-input v-model="editForm.contactNumber" placeholder="请输入联系电话" />
  583. </el-form-item>
  584. <el-form-item label="维护人" prop="maintainerPerson">
  585. <el-input v-model="editForm.maintainerPerson" placeholder="请输入维护人" />
  586. </el-form-item>
  587. <el-form-item label="维护电话" prop="maintainerNumber">
  588. <el-input v-model="editForm.maintainerNumber" placeholder="请输入维护电话" />
  589. </el-form-item>
  590. <el-form-item label="备注说明" prop="descr">
  591. <el-input v-model="editForm.descr" type="textarea" placeholder="请输入内容" />
  592. </el-form-item>
  593. </el-form>
  594. <div slot="footer" class="dialog-footer">
  595. <el-button @click="cancelEdit">取 消</el-button>
  596. <el-button type="primary" @click="submitEdit" :loading="editLoading">确 定</el-button>
  597. </div>
  598. </el-dialog>
  599. <!-- 执行结果对话框 -->
  600. <el-dialog
  601. :title="executeDialog.title"
  602. :visible.sync="executeDialog.visible"
  603. width="600px">
  604. <div class="execute-result">
  605. <el-alert
  606. :title="executeDialog.status"
  607. :type="executeDialog.type"
  608. :description="executeDialog.message"
  609. show-icon>
  610. </el-alert>
  611. <div v-if="executeDialog.data" class="result-data">
  612. <pre>{{ JSON.stringify(executeDialog.data, null, 2) }}</pre>
  613. </div>
  614. </div>
  615. <span slot="footer">
  616. <el-button @click="executeDialog.visible = false">关闭</el-button>
  617. </span>
  618. </el-dialog>
  619. <!-- 调用日志详情弹窗 -->
  620. <el-dialog :visible.sync="callLogDetailDialog" title="调用日志详情" width="60%">
  621. <div v-if="callLogDetailData">
  622. <el-descriptions :column="2" border>
  623. <el-descriptions-item label="对象代码">{{ callLogDetailData.objCode }}</el-descriptions-item>
  624. <el-descriptions-item label="模型代码">{{ callLogDetailData.modelCode }}</el-descriptions-item>
  625. <el-descriptions-item label="能力标识">{{ callLogDetailData.abilityKey }}</el-descriptions-item>
  626. <el-descriptions-item label="调用时间">{{ callLogDetailData.callTime }}</el-descriptions-item>
  627. <el-descriptions-item label="响应时间">{{ callLogDetailData.resTime }}</el-descriptions-item>
  628. <el-descriptions-item label="调用结果">
  629. <el-tag :type="getCallStatusType(callLogDetailData.callStatus)">
  630. {{ formatCallStatus(callLogDetailData.callStatus) }}
  631. </el-tag>
  632. </el-descriptions-item>
  633. </el-descriptions>
  634. <div style="margin-top: 20px;">
  635. <h4>调用载体</h4>
  636. <pre style="white-space: pre-wrap; background-color: #f5f5f5; padding: 10px; border-radius: 4px; max-height: 300px; overflow: auto;">{{ callLogDetailData.callPayload }}</pre>
  637. </div>
  638. <div style="margin-top: 20px;">
  639. <h4>响应载体</h4>
  640. <pre style="white-space: pre-wrap; background-color: #f5f5f5; padding: 10px; border-radius: 4px; max-height: 300px; overflow: auto;">{{ callLogDetailData.resPayload }}</pre>
  641. </div>
  642. </div>
  643. </el-dialog>
  644. <!-- 事件日志详情弹窗 -->
  645. <el-dialog :visible.sync="eventLogDetailDialog" title="事件日志详情" width="60%">
  646. <div v-if="eventLogDetailData">
  647. <el-descriptions :column="2" border>
  648. <el-descriptions-item label="对象编号">{{ eventLogDetailData.objCode }}</el-descriptions-item>
  649. <el-descriptions-item label="对象名称">{{ eventLogDetailData.objName }}</el-descriptions-item>
  650. <el-descriptions-item label="事件名称">{{ eventLogDetailData.eventName }}</el-descriptions-item>
  651. <el-descriptions-item label="事件标识">{{ eventLogDetailData.eventKey }}</el-descriptions-item>
  652. <el-descriptions-item label="事件时间" :span="2">{{ eventLogDetailData.eventTime }}</el-descriptions-item>
  653. </el-descriptions>
  654. <div style="margin-top: 20px;">
  655. <h4>事件描述</h4>
  656. <div style="background-color: #f5f5f5; padding: 10px; border-radius: 4px;">
  657. {{ eventLogDetailData.eventDetail || '无' }}
  658. </div>
  659. </div>
  660. </div>
  661. </el-dialog>
  662. <!-- 能力执行弹窗 -->
  663. <el-dialog :title="abilityDialogTitle" :visible.sync="abilityDialogVisible" width="500px" append-to-body :close-on-click-modal="false">
  664. <el-form ref="abilityForm" :model="abilityForm" label-width="100px">
  665. <el-form-item label="能力名称">
  666. <el-input v-model="abilityForm.abilityName" disabled></el-input>
  667. </el-form-item>
  668. <el-form-item label="能力描述">
  669. <el-input type="textarea" v-model="abilityForm.abilityDesc" disabled :rows="2"></el-input>
  670. </el-form-item>
  671. <el-form-item label="参数设置" v-if="abilityParamType">
  672. <template v-if="abilityParamType === 'Slider'">
  673. <div style="width: 100%;">
  674. <div style="padding: 0 10px;">
  675. <el-slider
  676. v-model="abilitySliderValue"
  677. :min="abilitySliderMin"
  678. :max="abilitySliderMax"
  679. :show-tooltip="true"
  680. :format-tooltip="formatTooltip">
  681. </el-slider>
  682. </div>
  683. <div style="display: flex; justify-content: space-between; padding: 0 10px; margin-top: -5px; margin-bottom: 20px;">
  684. <span style="font-size: 12px; color: #909399;">{{ abilitySliderMin }}%</span>
  685. <span style="font-size: 12px; color: #909399;">{{ Math.floor((abilitySliderMin + abilitySliderMax) / 2) }}%</span>
  686. <span style="font-size: 12px; color: #909399;">{{ abilitySliderMax }}%</span>
  687. </div>
  688. <div style="display: flex; align-items: center; gap: 15px;">
  689. <el-input-number
  690. v-model="abilitySliderValue"
  691. :min="abilitySliderMin"
  692. :max="abilitySliderMax"
  693. :step="1"
  694. :precision="0"
  695. controls-position="right"
  696. style="flex: 0 0 120px;">
  697. </el-input-number>
  698. <span style="color: #909399; font-size: 13px;">
  699. 调节范围:{{ abilitySliderMin }} - {{ abilitySliderMax }}
  700. </span>
  701. </div>
  702. </div>
  703. </template>
  704. <template v-else-if="abilityParamType === 'Input'">
  705. <el-input
  706. v-model="abilityInputValue"
  707. placeholder="请输入参数值"
  708. clearable>
  709. </el-input>
  710. </template>
  711. </el-form-item>
  712. </el-form>
  713. <div slot="footer" class="dialog-footer">
  714. <el-button @click="abilityDialogVisible = false">取消</el-button>
  715. <el-button type="primary" @click="executeAbilityWithParam" :loading="abilityExecuting">执行</el-button>
  716. </div>
  717. </el-dialog>
  718. </div>
  719. </template>
  720. <script>
  721. import { getSubsystemByCode, updateSubsystem } from '@/api/adapter/subsystem'
  722. import { getModelByCode } from '@/api/basecfg/objModel'
  723. import { getObjAttr, getObjAttrBatch } from '@/api/basecfg/objAttribute'
  724. import { callAbility } from '@/api/basecfg/objAbility'
  725. import { getByCondition } from '@/api/device/device'
  726. import { listCallLog, listEventLog, getCallLog, getEventLog } from '@/api/basecfg/objLog'
  727. export default {
  728. name: 'SubsystemIntegration',
  729. data() {
  730. return {
  731. // 系统代码
  732. systemCode: 'SYS_BA',
  733. // 系统信息
  734. systemInfo: {},
  735. // 系统状态
  736. systemStatus: '1',
  737. // 当前标签页
  738. activeTab: 'attributes',
  739. // 协议属性
  740. protocolAttrs: [],
  741. // 状态属性
  742. stateAttrs: [],
  743. // 系统能力
  744. systemAbilities: [],
  745. abilityLoading: false,
  746. // 设备列表(能耗网关)
  747. deviceList: [],
  748. deviceLoading: false,
  749. deviceAttrs: {},
  750. deviceChannels: {},
  751. // 设备能力缓存
  752. deviceAbilities: {},
  753. // 设备统计
  754. deviceStats: {
  755. total: 0,
  756. online: 0,
  757. offline: 0,
  758. points: 0
  759. },
  760. // 所有模型代码(系统 + 设备)
  761. allModelCodes: [],
  762. // 子设备相关
  763. subDeviceAttrs: {},
  764. subDeviceGatewayMap: {},
  765. // 楼控设备相关
  766. baDeviceModels: [
  767. { modelCode: 'M_Z020_DEV_BA_XF', modelName: 'BA新风设备' },
  768. { modelCode: 'M_Z020_DEV_BA_AHU', modelName: 'BA空调设备' },
  769. { modelCode: 'M_Z020_DEV_BA_WT', modelName: 'BA水箱监测' },
  770. { modelCode: 'M_Z020_DEV_BA_WP', modelName: 'BA水泵监测' },
  771. { modelCode: 'M_Z020_DEV_BA_LIGHT', modelName: 'BA照明监测' },
  772. ],
  773. selectedBaDeviceModel: 'M_Z020_DEV_BA_XF',
  774. baDeviceList: [],
  775. baDeviceLoading: false,
  776. baDeviceAttrs: {},
  777. // 楼控设备能力执行状态
  778. baAbilityExecuting: {},
  779. // 日志模型选项
  780. logModelOptions: [
  781. { modelCode: 'ALL', modelName: '全部' },
  782. { modelCode: 'M_W4_SYS_BA', modelName: '系统日志' },
  783. { modelCode: 'M_W4_DEV_BA_GA', modelName: '能耗监测' },
  784. { modelCode: 'M_Z020_DEV_BA_XF', modelName: 'BA新风设备' },
  785. { modelCode: 'M_Z020_DEV_BA_AHU', modelName: 'BA空调设备' },
  786. { modelCode: 'M_Z020_DEV_BA_WT', modelName: 'BA水箱监测' },
  787. { modelCode: 'M_Z020_DEV_BA_WP', modelName: 'BA水泵监测' },
  788. { modelCode: 'M_Z020_DEV_BA_LIGHT', modelName: 'BA照明监测' },
  789. ],
  790. selectedLogModel: 'ALL', // 调用日志选择的模型
  791. selectedEventLogModel: 'ALL', // 事件日志选择的模型
  792. // 调用日志查询
  793. callLogQuery: {
  794. dateRange: [],
  795. abilityKey: '',
  796. callStatus: '',
  797. pageNum: 1,
  798. pageSize: 10
  799. },
  800. callLogList: [],
  801. callLogTotal: 0,
  802. logLoading: false,
  803. // 事件日志查询
  804. eventLogQuery: {
  805. dateRange: [],
  806. eventKey: '',
  807. pageNum: 1,
  808. pageSize: 10
  809. },
  810. eventLogList: [],
  811. eventLogTotal: 0,
  812. eventLogLoading: false,
  813. // 调用日志详情
  814. callLogDetailDialog: false,
  815. callLogDetailData: null,
  816. // 事件日志详情
  817. eventLogDetailDialog: false,
  818. eventLogDetailData: null,
  819. // 执行结果对话框
  820. executeDialog: {
  821. visible: false,
  822. title: '',
  823. status: '',
  824. type: 'success',
  825. message: '',
  826. data: null
  827. },
  828. // 能力执行相关
  829. abilityDialogVisible: false,
  830. abilityDialogTitle: '能力执行',
  831. abilityForm: {
  832. abilityName: '',
  833. abilityKey: '',
  834. abilityDesc: '',
  835. paramDefinition: null,
  836. modelCode: ''
  837. },
  838. abilityParamType: null,
  839. abilitySliderValue: 50,
  840. abilitySliderMin: 0,
  841. abilitySliderMax: 100,
  842. abilityInputValue: '',
  843. abilityExecuting: false,
  844. currentDevice: null,
  845. currentObjType: null,
  846. // 编辑系统信息相关
  847. editDialogVisible: false,
  848. editLoading: false,
  849. editForm: {},
  850. editRules: {
  851. systemCode: [
  852. { required: true, message: "系统代码不能为空", trigger: "blur" }
  853. ],
  854. systemName: [
  855. { required: true, message: "系统名称不能为空", trigger: "blur" }
  856. ]
  857. }
  858. }
  859. },
  860. created() {
  861. this.loadSystemInfo()
  862. this.initDateRange()
  863. },
  864. methods: {
  865. // ========== 楼控设备能力相关方法 ==========
  866. // 获取楼控设备的能力列表
  867. getBaDeviceAbilities(deviceModel) {
  868. if (!deviceModel) return []
  869. const modelData = this.baDeviceAttrs['_model_' + deviceModel]
  870. if (!modelData || !modelData.abilityList) return []
  871. // 只返回 hiddenFlag === 1 的能力
  872. return modelData.abilityList.filter(ability => ability.hiddenFlag === 1)
  873. },
  874. // 处理楼控设备能力点击(需要参数输入)
  875. handleBaDeviceAbilityClick(device, ability) {
  876. const paramType = this.getParamType(ability.paramDefinition)
  877. if (paramType === 'Slider') {
  878. this.showSliderDialog(ability, device, 2)
  879. } else if (paramType === 'Input') {
  880. this.showInputDialog(ability, device, 2)
  881. }
  882. },
  883. // 执行楼控设备能力
  884. async executeBaDeviceAbility(device, ability, paramValue) {
  885. // 设置执行状态
  886. const stateKey = `${device.deviceCode}_${ability.abilityKey}${paramValue !== null ? '_' + paramValue : ''}`
  887. this.$set(this.baAbilityExecuting, stateKey, true)
  888. try {
  889. const params = {
  890. objCode: device.deviceCode,
  891. objType: 2,
  892. modelCode: device.deviceModel,
  893. abilityKey: ability.abilityKey,
  894. abilityParam: paramValue
  895. }
  896. await callAbility(params)
  897. this.$message.success(`${ability.abilityName}执行成功`)
  898. // 刷新设备属性
  899. await this.loadBaDeviceAttrsBatch(device.deviceModel)
  900. } catch (error) {
  901. this.$message.error(`${ability.abilityName}执行失败:${error.message}`)
  902. } finally {
  903. this.$set(this.baAbilityExecuting, stateKey, false)
  904. }
  905. },
  906. // 判断楼控设备能力是否正在执行
  907. isBaAbilityExecuting(deviceCode, abilityKey, paramValue) {
  908. const stateKey = `${deviceCode}_${abilityKey}${paramValue !== undefined ? '_' + paramValue : ''}`
  909. return this.baAbilityExecuting[stateKey] || false
  910. },
  911. // ========== 原有方法保持不变 ==========
  912. // 打开编辑对话框
  913. handleEdit() {
  914. this.editForm = { ...this.systemInfo }
  915. this.editDialogVisible = true
  916. this.$nextTick(() => {
  917. this.$refs.editForm?.clearValidate()
  918. })
  919. },
  920. // 取消编辑
  921. cancelEdit() {
  922. this.editDialogVisible = false
  923. this.editForm = {}
  924. },
  925. // 提交编辑
  926. submitEdit() {
  927. this.$refs.editForm.validate(valid => {
  928. if (valid) {
  929. this.editLoading = true
  930. updateSubsystem(this.editForm).then(response => {
  931. this.$message.success("修改成功")
  932. this.editDialogVisible = false
  933. this.loadSystemInfo()
  934. }).catch(error => {
  935. this.$message.error("修改失败:" + error.message)
  936. }).finally(() => {
  937. this.editLoading = false
  938. })
  939. }
  940. })
  941. },
  942. // 格式化滑块tooltip
  943. formatTooltip(val) {
  944. return `${val}%`
  945. },
  946. // 解析参数定义类型
  947. getParamType(paramDefinition) {
  948. if (!paramDefinition) return null
  949. try {
  950. const def = JSON.parse(paramDefinition)
  951. return def.type
  952. } catch (e) {
  953. return null
  954. }
  955. },
  956. // 解析Options类型的选项
  957. parseOptions(paramDefinition) {
  958. try {
  959. const def = JSON.parse(paramDefinition)
  960. if (def.type === 'Options' && def.list) {
  961. return def.list
  962. }
  963. } catch (e) {
  964. console.error('解析Options失败:', e)
  965. }
  966. return []
  967. },
  968. // 处理系统能力点击
  969. handleSystemAbilityClick(ability) {
  970. const paramType = this.getParamType(ability.paramDefinition)
  971. if (paramType === 'Slider') {
  972. this.showSliderDialog(ability, this.systemInfo, 3)
  973. } else if (paramType === 'Input') {
  974. this.showInputDialog(ability, this.systemInfo, 3)
  975. } else {
  976. this.executeSystemAbility(ability)
  977. }
  978. },
  979. // 执行系统能力(带Options参数)
  980. executeSystemAbilityWithParam(ability, paramValue) {
  981. ability.executing = true
  982. ability.executingValue = paramValue
  983. callAbility({
  984. objCode: this.systemCode,
  985. objType: 3,
  986. modelCode: this.systemInfo.modelCode,
  987. abilityKey: ability.abilityKey,
  988. abilityParam: paramValue
  989. }).then(res => {
  990. this.executeDialog = {
  991. visible: true,
  992. title: '执行结果 - ' + ability.abilityName,
  993. status: '执行成功',
  994. type: 'success',
  995. message: '系统能力执行完成',
  996. data: res.data
  997. }
  998. }).catch(error => {
  999. this.executeDialog = {
  1000. visible: true,
  1001. title: '执行结果 - ' + ability.abilityName,
  1002. status: '执行失败',
  1003. type: 'error',
  1004. message: error.message,
  1005. data: null
  1006. }
  1007. }).finally(() => {
  1008. ability.executing = false
  1009. ability.executingValue = null
  1010. })
  1011. },
  1012. // 执行系统能力(无参数)
  1013. executeSystemAbility(ability) {
  1014. ability.executing = true
  1015. callAbility({
  1016. objCode: this.systemCode,
  1017. objType: 3,
  1018. modelCode: this.systemInfo.modelCode,
  1019. abilityKey: ability.abilityKey,
  1020. abilityParam: null
  1021. }).then(res => {
  1022. this.executeDialog = {
  1023. visible: true,
  1024. title: '执行结果 - ' + ability.abilityName,
  1025. status: '执行成功',
  1026. type: 'success',
  1027. message: '系统能力执行完成',
  1028. data: res.data
  1029. }
  1030. if (ability.abilityKey === 'MeterReadingGw' || ability.abilityKey === 'MeterReadingTotal') {
  1031. this.loadDevices()
  1032. }
  1033. }).catch(error => {
  1034. this.executeDialog = {
  1035. visible: true,
  1036. title: '执行结果 - ' + ability.abilityName,
  1037. status: '执行失败',
  1038. type: 'error',
  1039. message: error.message,
  1040. data: null
  1041. }
  1042. }).finally(() => {
  1043. ability.executing = false
  1044. })
  1045. },
  1046. // 显示滑块弹窗
  1047. showSliderDialog(ability, obj, objType) {
  1048. this.currentDevice = obj
  1049. this.currentObjType = objType
  1050. this.abilityForm = { ...ability }
  1051. const objName = objType === 3 ? this.systemInfo.systemName : obj.deviceName
  1052. this.abilityDialogTitle = `${ability.abilityName} - ${objName}`
  1053. this.abilityParamType = 'Slider'
  1054. try {
  1055. const def = JSON.parse(ability.paramDefinition)
  1056. this.abilitySliderMin = def.min || 0
  1057. this.abilitySliderMax = def.max || 100
  1058. this.abilitySliderValue = Math.floor((this.abilitySliderMin + this.abilitySliderMax) / 2)
  1059. } catch (e) {
  1060. console.error('解析Slider参数失败:', e)
  1061. this.abilitySliderMin = 0
  1062. this.abilitySliderMax = 100
  1063. this.abilitySliderValue = 50
  1064. }
  1065. this.abilityDialogVisible = true
  1066. },
  1067. // 显示输入弹窗
  1068. showInputDialog(ability, obj, objType) {
  1069. this.currentDevice = obj
  1070. this.currentObjType = objType
  1071. this.abilityForm = { ...ability }
  1072. const objName = objType === 3 ? this.systemInfo.systemName : obj.deviceName
  1073. this.abilityDialogTitle = `${ability.abilityName} - ${objName}`
  1074. this.abilityParamType = 'Input'
  1075. this.abilityInputValue = ''
  1076. this.abilityDialogVisible = true
  1077. },
  1078. // 执行带参数的能力
  1079. executeAbilityWithParam() {
  1080. let paramValue = null
  1081. if (this.abilityParamType === 'Slider') {
  1082. paramValue = String(this.abilitySliderValue)
  1083. } else if (this.abilityParamType === 'Input') {
  1084. if (!this.abilityInputValue) {
  1085. this.$message.warning('请输入参数值')
  1086. return
  1087. }
  1088. paramValue = this.abilityInputValue
  1089. }
  1090. this.abilityExecuting = true
  1091. const callParams = {
  1092. objType: this.currentObjType,
  1093. abilityKey: this.abilityForm.abilityKey,
  1094. abilityParam: paramValue
  1095. }
  1096. if (this.currentObjType === 3) {
  1097. callParams.objCode = this.systemCode
  1098. callParams.modelCode = this.systemInfo.modelCode
  1099. } else if (this.currentObjType === 2) {
  1100. callParams.objCode = this.currentDevice.deviceCode
  1101. callParams.modelCode = this.currentDevice.deviceModel
  1102. }
  1103. callAbility(callParams).then(response => {
  1104. this.$message.success('执行成功')
  1105. this.abilityDialogVisible = false
  1106. if (this.abilityForm.abilityKey.toLowerCase().includes('meter') ||
  1107. this.abilityForm.abilityKey.toLowerCase().includes('reading')) {
  1108. if (this.currentObjType === 3) {
  1109. this.loadDevices()
  1110. } else if (this.currentObjType === 2) {
  1111. this.loadDeviceAttrs(this.currentDevice.deviceCode)
  1112. }
  1113. }
  1114. }).catch(() => {
  1115. this.$message.error('执行失败')
  1116. }).finally(() => {
  1117. this.abilityExecuting = false
  1118. })
  1119. },
  1120. // 初始化时间范围
  1121. initDateRange() {
  1122. const now = new Date()
  1123. const startOfDay = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 0, 0, 0)
  1124. const endOfDay = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 23, 59, 59)
  1125. this.callLogQuery.dateRange = [
  1126. this.formatDate(startOfDay),
  1127. this.formatDate(endOfDay)
  1128. ]
  1129. this.eventLogQuery.dateRange = [
  1130. this.formatDate(startOfDay),
  1131. this.formatDate(endOfDay)
  1132. ]
  1133. },
  1134. // 格式化日期
  1135. formatDate(date) {
  1136. if (!date) return ''
  1137. const year = date.getFullYear()
  1138. const month = (date.getMonth() + 1).toString().padStart(2, '0')
  1139. const day = date.getDate().toString().padStart(2, '0')
  1140. const hours = date.getHours().toString().padStart(2, '0')
  1141. const minutes = date.getMinutes().toString().padStart(2, '0')
  1142. const seconds = date.getSeconds().toString().padStart(2, '0')
  1143. return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
  1144. },
  1145. // 加载系统信息
  1146. async loadSystemInfo() {
  1147. try {
  1148. const sysRes = await getSubsystemByCode(this.systemCode)
  1149. this.systemInfo = sysRes.data
  1150. if (this.systemInfo.modelCode) {
  1151. this.allModelCodes.push(this.systemInfo.modelCode)
  1152. const modelRes = await getModelByCode(this.systemInfo.modelCode)
  1153. const modelData = modelRes.data
  1154. this.systemAbilities = (modelData.abilityList || []).map(item => ({
  1155. ...item,
  1156. executing: false,
  1157. executingValue: null
  1158. }))
  1159. }
  1160. const attrRes = await getObjAttr(3, this.systemCode)
  1161. const attrData = attrRes.data
  1162. this.protocolAttrs = attrData.Protocol || []
  1163. this.stateAttrs = attrData.State || []
  1164. const statusAttr = this.stateAttrs.find(attr => attr.attrKey === 'interfaceStatus')
  1165. if (statusAttr) {
  1166. this.systemStatus = statusAttr.attrValue
  1167. }
  1168. await this.loadDevices()
  1169. } catch (error) {
  1170. this.$message.error('加载系统信息失败:' + error.message)
  1171. }
  1172. },
  1173. // 加载设备列表(能耗网关)
  1174. async loadDevices() {
  1175. this.deviceLoading = true
  1176. try {
  1177. const res = await getByCondition({
  1178. subsystemCode: this.systemCode,
  1179. deviceModel: 'M_W4_DEV_BA_GA'
  1180. })
  1181. const deviceData = res.data || res.rows || []
  1182. this.deviceList = deviceData.map(device => ({
  1183. ...device,
  1184. detailTab: 'attrs'
  1185. }))
  1186. const deviceModelCodes = new Set()
  1187. this.deviceList.forEach(device => {
  1188. if (device.deviceModel) {
  1189. deviceModelCodes.add(device.deviceModel)
  1190. }
  1191. })
  1192. deviceModelCodes.forEach(modelCode => {
  1193. if (!this.allModelCodes.includes(modelCode)) {
  1194. this.allModelCodes.push(modelCode)
  1195. }
  1196. })
  1197. this.deviceStats.total = this.deviceList.length
  1198. this.deviceStats.online = this.deviceList.filter(d => d.deviceStatus === 1).length
  1199. this.deviceStats.offline = this.deviceList.filter(d => d.deviceStatus !== 1).length
  1200. const devicesByModel = {}
  1201. this.deviceList.forEach(device => {
  1202. if (device.deviceModel) {
  1203. if (!devicesByModel[device.deviceModel]) {
  1204. devicesByModel[device.deviceModel] = []
  1205. }
  1206. devicesByModel[device.deviceModel].push(device)
  1207. }
  1208. })
  1209. for (const modelCode in devicesByModel) {
  1210. await this.loadDeviceAttrsBatch(modelCode, devicesByModel[modelCode])
  1211. }
  1212. await this.loadAllSubDeviceAttrs()
  1213. } catch (error) {
  1214. this.$message.error('加载设备列表失败')
  1215. } finally {
  1216. this.deviceLoading = false
  1217. }
  1218. },
  1219. // 批量加载网关设备属性
  1220. async loadDeviceAttrsBatch(modelCode, devices) {
  1221. try {
  1222. const res = await getObjAttrBatch(2, modelCode)
  1223. const batchData = res.data || {}
  1224. devices.forEach(device => {
  1225. const deviceCode = device.deviceCode
  1226. const attrData = batchData[deviceCode]
  1227. if (attrData) {
  1228. const baseAttrs = {}
  1229. if (attrData.Base) {
  1230. attrData.Base.forEach(attr => {
  1231. baseAttrs[attr.attrKey] = attr.attrValue
  1232. })
  1233. }
  1234. this.deviceAttrs[deviceCode] = baseAttrs
  1235. }
  1236. })
  1237. } catch (error) {
  1238. console.error(`批量加载模型 ${modelCode} 的设备属性失败:`, error)
  1239. }
  1240. },
  1241. // 加载所有子设备属性
  1242. async loadAllSubDeviceAttrs() {
  1243. const subDevicesByModel = {}
  1244. this.deviceList.forEach(device => {
  1245. const subDevAttr = this.deviceAttrs[device.deviceCode]?.subDev
  1246. if (subDevAttr) {
  1247. try {
  1248. const subDevices = JSON.parse(subDevAttr)
  1249. subDevices.forEach(subDev => {
  1250. if (!subDevicesByModel[subDev.modelCode]) {
  1251. subDevicesByModel[subDev.modelCode] = []
  1252. }
  1253. subDevicesByModel[subDev.modelCode].push(subDev.deviceCode)
  1254. this.subDeviceGatewayMap[subDev.deviceCode] = device.deviceCode
  1255. })
  1256. } catch (e) {
  1257. console.error(`解析设备 ${device.deviceCode} 的subDev失败:`, e)
  1258. }
  1259. }
  1260. })
  1261. let totalPoints = 0
  1262. for (const modelCode in subDevicesByModel) {
  1263. const pointsCount = await this.loadSubDeviceAttrsBatch(modelCode)
  1264. totalPoints += pointsCount
  1265. }
  1266. this.deviceStats.points = totalPoints
  1267. this.organizeChannelsFromSubDevices()
  1268. },
  1269. // 批量加载子设备属性
  1270. async loadSubDeviceAttrsBatch(modelCode) {
  1271. try {
  1272. const res = await getObjAttrBatch(2, modelCode)
  1273. const batchData = res.data || {}
  1274. let pointsCount = 0
  1275. for (const deviceCode in batchData) {
  1276. const attrData = batchData[deviceCode]
  1277. this.subDeviceAttrs[deviceCode] = attrData
  1278. pointsCount++
  1279. }
  1280. return pointsCount
  1281. } catch (error) {
  1282. console.error(`批量加载子设备模型 ${modelCode} 的属性失败:`, error)
  1283. return 0
  1284. }
  1285. },
  1286. // 将子设备组织成通道结构
  1287. organizeChannelsFromSubDevices() {
  1288. this.deviceList.forEach(device => {
  1289. const deviceCode = device.deviceCode
  1290. const subDevAttr = this.deviceAttrs[deviceCode]?.subDev
  1291. if (!subDevAttr) {
  1292. this.deviceChannels[deviceCode] = []
  1293. return
  1294. }
  1295. try {
  1296. const subDevices = JSON.parse(subDevAttr)
  1297. const channelMap = {}
  1298. subDevices.forEach(subDev => {
  1299. const subDevCode = subDev.deviceCode
  1300. const subDevAttrData = this.subDeviceAttrs?.[subDevCode]
  1301. if (!subDevAttrData) return
  1302. const interfaceAttr = subDevAttrData.Base?.find(attr => attr.attrKey === 'interface')
  1303. const channelName = interfaceAttr?.attrValue || '未知通道'
  1304. if (!channelMap[channelName]) {
  1305. channelMap[channelName] = {
  1306. name: this.formatChannelName(channelName),
  1307. key: channelName,
  1308. points: []
  1309. }
  1310. }
  1311. const point = {
  1312. key: subDevCode,
  1313. name: subDevAttrData.Base?.find(attr => attr.attrKey === 'deviceName')?.attrValue || subDevCode,
  1314. value: subDevAttrData.Measure?.find(attr => attr.attrKey === 'value')?.attrValue || '-',
  1315. updateTime: subDevAttrData.Measure?.find(attr => attr.attrKey === 'value')?.updateTime || '-',
  1316. unit: subDevAttrData.Measure?.find(attr => attr.attrKey === 'value')?.attrUnit || '',
  1317. location: subDevAttrData.Base?.find(attr => attr.attrKey === 'location')?.attrValue || '-',
  1318. desc: subDevAttrData.Base?.find(attr => attr.attrKey === 'desc')?.attrValue || '-'
  1319. }
  1320. channelMap[channelName].points.push(point)
  1321. })
  1322. const channels = Object.values(channelMap).sort((a, b) => {
  1323. return a.key.localeCompare(b.key)
  1324. })
  1325. this.deviceChannels[deviceCode] = channels
  1326. } catch (e) {
  1327. console.error(`组织设备 ${deviceCode} 的通道数据失败:`, e)
  1328. this.deviceChannels[deviceCode] = []
  1329. }
  1330. })
  1331. },
  1332. // 格式化通道名称
  1333. formatChannelName(channelKey) {
  1334. const match = channelKey.match(/(\d+)(D\d+)口/)
  1335. if (match) {
  1336. return `${match[2]}口通道`
  1337. }
  1338. return channelKey
  1339. },
  1340. // 获取设备属性
  1341. getDeviceAttr(deviceCode, attrKey) {
  1342. return this.deviceAttrs[deviceCode]?.[attrKey] || '-'
  1343. },
  1344. // 获取设备通道
  1345. getDeviceChannels(deviceCode) {
  1346. return this.deviceChannels[deviceCode] || []
  1347. },
  1348. // 单个加载设备属性(用于刷新)
  1349. async loadDeviceAttrs(deviceCode) {
  1350. try {
  1351. const res = await getObjAttr(2, deviceCode)
  1352. const attrData = res.data
  1353. const baseAttrs = {}
  1354. if (attrData.Base) {
  1355. attrData.Base.forEach(attr => {
  1356. baseAttrs[attr.attrKey] = attr.attrValue
  1357. })
  1358. }
  1359. this.deviceAttrs[deviceCode] = baseAttrs
  1360. const subDevAttr = baseAttrs.subDev
  1361. if (subDevAttr) {
  1362. const subDevices = JSON.parse(subDevAttr)
  1363. const subDevicesByModel = {}
  1364. subDevices.forEach(subDev => {
  1365. if (!subDevicesByModel[subDev.modelCode]) {
  1366. subDevicesByModel[subDev.modelCode] = []
  1367. }
  1368. subDevicesByModel[subDev.modelCode].push(subDev.deviceCode)
  1369. })
  1370. for (const modelCode in subDevicesByModel) {
  1371. await this.loadSubDeviceAttrsBatch(modelCode)
  1372. }
  1373. this.organizeChannelsFromSubDevices()
  1374. }
  1375. } catch (error) {
  1376. console.error(`加载设备 ${deviceCode} 属性失败:`, error)
  1377. }
  1378. },
  1379. // 加载设备能力
  1380. async handleAbilityLoad(visible, device) {
  1381. if (visible && !this.deviceAbilities[device.deviceCode]) {
  1382. try {
  1383. if (!device.deviceModel) {
  1384. this.$set(this.deviceAbilities, device.deviceCode, [])
  1385. return
  1386. }
  1387. const res = await getModelByCode(device.deviceModel)
  1388. const abilities = res.data?.abilityList?.filter(item => item.hiddenFlag === 1) || []
  1389. this.$set(this.deviceAbilities, device.deviceCode, abilities)
  1390. } catch (error) {
  1391. this.$set(this.deviceAbilities, device.deviceCode, [])
  1392. }
  1393. }
  1394. },
  1395. // 处理设备操作命令
  1396. async handleDeviceCommand(command) {
  1397. const { device, ability, paramValue } = command
  1398. if (paramValue !== undefined) {
  1399. try {
  1400. await callAbility({
  1401. objCode: device.deviceCode,
  1402. objType: 2,
  1403. modelCode: device.deviceModel,
  1404. abilityKey: ability.abilityKey,
  1405. abilityParam: paramValue
  1406. })
  1407. this.$message.success(`${ability.abilityName}执行成功`)
  1408. if (ability.abilityKey.toLowerCase().includes('meter') ||
  1409. ability.abilityKey.toLowerCase().includes('reading')) {
  1410. await this.loadDeviceAttrs(device.deviceCode)
  1411. }
  1412. } catch (error) {
  1413. this.$message.error(`${ability.abilityName}执行失败:${error.message}`)
  1414. }
  1415. } else {
  1416. const paramType = this.getParamType(ability.paramDefinition)
  1417. if (paramType === 'Slider') {
  1418. this.showSliderDialog(ability, device, 2)
  1419. } else if (paramType === 'Input') {
  1420. this.showInputDialog(ability, device, 2)
  1421. } else {
  1422. try {
  1423. await callAbility({
  1424. objCode: device.deviceCode,
  1425. objType: 2,
  1426. modelCode: device.deviceModel,
  1427. abilityKey: ability.abilityKey,
  1428. abilityParam: null
  1429. })
  1430. this.$message.success(`${ability.abilityName}执行成功`)
  1431. if (ability.abilityKey.toLowerCase().includes('meter') ||
  1432. ability.abilityKey.toLowerCase().includes('reading')) {
  1433. await this.loadDeviceAttrs(device.deviceCode)
  1434. }
  1435. } catch (error) {
  1436. this.$message.error(`${ability.abilityName}执行失败:${error.message}`)
  1437. }
  1438. }
  1439. }
  1440. },
  1441. // 处理设备行点击
  1442. handleDeviceClick(row) {
  1443. this.$refs.deviceTable?.toggleRowExpansion(row)
  1444. },
  1445. // ========== 楼控设备相关方法 ==========
  1446. // 处理楼控设备类型切换
  1447. handleBaDeviceTypeChange() {
  1448. this.loadBaDevices()
  1449. },
  1450. // 刷新楼控设备
  1451. refreshBaDevices() {
  1452. this.loadBaDevices()
  1453. },
  1454. // 加载楼控设备列表
  1455. async loadBaDevices() {
  1456. if (!this.selectedBaDeviceModel) return
  1457. this.baDeviceLoading = true
  1458. try {
  1459. // 1. 先查询设备物模型,获取属性定义和能力列表
  1460. const modelRes = await getModelByCode(this.selectedBaDeviceModel)
  1461. const modelData = modelRes.data
  1462. // 缓存当前模型的属性定义和能力列表
  1463. this.$set(this.baDeviceAttrs, '_model_' + this.selectedBaDeviceModel, modelData)
  1464. // 2. 查询指定模型的设备列表
  1465. const res = await getByCondition({
  1466. subsystemCode: this.systemCode,
  1467. deviceModel: this.selectedBaDeviceModel
  1468. })
  1469. const deviceData = res.data || res.rows || []
  1470. this.baDeviceList = deviceData.map(device => ({
  1471. ...device,
  1472. detailTab: 'state'
  1473. }))
  1474. // 3. 批量加载设备属性值
  1475. if (this.baDeviceList.length > 0) {
  1476. await this.loadBaDeviceAttrsBatch(this.selectedBaDeviceModel)
  1477. }
  1478. } catch (error) {
  1479. this.$message.error('加载楼控设备失败:' + error.message)
  1480. } finally {
  1481. this.baDeviceLoading = false
  1482. }
  1483. },
  1484. // 批量加载楼控设备属性
  1485. async loadBaDeviceAttrsBatch(modelCode) {
  1486. try {
  1487. const res = await getObjAttrBatch(2, modelCode)
  1488. const batchData = res.data || {}
  1489. // 缓存所有设备的属性数据
  1490. for (const deviceCode in batchData) {
  1491. this.baDeviceAttrs[deviceCode] = batchData[deviceCode]
  1492. }
  1493. } catch (error) {
  1494. console.error(`批量加载楼控设备属性失败:`, error)
  1495. }
  1496. },
  1497. // 获取楼控设备的属性(按组)
  1498. getBaDeviceAttrs(deviceCode, attrGroup) {
  1499. // 获取设备所属的模型
  1500. const device = this.baDeviceList.find(d => d.deviceCode === deviceCode)
  1501. if (!device) return []
  1502. // 获取模型定义
  1503. const modelData = this.baDeviceAttrs['_model_' + device.deviceModel]
  1504. if (!modelData || !modelData.attrList) return []
  1505. // 筛选出当前分组的属性定义
  1506. const modelAttrs = modelData.attrList.filter(attr => attr.attrGroup === attrGroup)
  1507. // 获取设备的实际属性值
  1508. const deviceAttrData = this.baDeviceAttrs[deviceCode]
  1509. const deviceAttrs = deviceAttrData && deviceAttrData[attrGroup] ? deviceAttrData[attrGroup] : []
  1510. // 将实际值映射到字典
  1511. const attrValueMap = {}
  1512. deviceAttrs.forEach(attr => {
  1513. attrValueMap[attr.attrKey] = attr
  1514. })
  1515. // 合并模型定义和实际值
  1516. const mergedAttrs = modelAttrs.map(modelAttr => {
  1517. const actualAttr = attrValueMap[modelAttr.attrKey]
  1518. if (actualAttr) {
  1519. // 有实际值,返回实际值
  1520. return actualAttr
  1521. } else {
  1522. // 没有实际值,返回模型定义的空属性
  1523. return {
  1524. objCode: deviceCode,
  1525. attrGroup: modelAttr.attrGroup,
  1526. attrKey: modelAttr.attrKey,
  1527. attrName: modelAttr.attrName,
  1528. attrValue: null,
  1529. attrValueName: null,
  1530. attrValueType: modelAttr.attrValueType,
  1531. attrUnit: modelAttr.attrUnit,
  1532. updateTime: null
  1533. }
  1534. }
  1535. })
  1536. return mergedAttrs
  1537. },
  1538. // 格式化楼控设备属性值
  1539. formatBaAttrValue(attr) {
  1540. if (!attr.attrValue && attr.attrValue !== 0) {
  1541. return '-'
  1542. }
  1543. // 如果有枚举名称,优先显示
  1544. if (attr.attrValueName) {
  1545. return attr.attrValueName
  1546. }
  1547. // 数值类型,保留两位小数
  1548. if (attr.attrValueType === 'Value') {
  1549. const numValue = parseFloat(attr.attrValue)
  1550. if (!isNaN(numValue)) {
  1551. return numValue.toFixed(2)
  1552. }
  1553. }
  1554. return attr.attrValue
  1555. },
  1556. // ========== 标签页切换 ==========
  1557. handleTabClick(tab) {
  1558. if (tab.name === 'callLogs' && this.callLogList.length === 0) {
  1559. this.queryCallLogs()
  1560. } else if (tab.name === 'eventLogs' && this.eventLogList.length === 0) {
  1561. this.queryEventLogs()
  1562. } else if (tab.name === 'baDevices' && this.baDeviceList.length === 0) {
  1563. this.loadBaDevices()
  1564. }
  1565. },
  1566. // ========== 日志查询相关 - 已优化 ==========
  1567. // 获取要查询的模型代码列表
  1568. getQueryModelCodes(selectedModel) {
  1569. if (selectedModel === 'ALL' || !selectedModel) {
  1570. // 返回所有模型代码(排除 'ALL' 选项)
  1571. return this.logModelOptions
  1572. .filter(item => item.modelCode !== 'ALL')
  1573. .map(item => item.modelCode)
  1574. } else {
  1575. // 返回单个选中的模型
  1576. return [selectedModel]
  1577. }
  1578. },
  1579. // 查询调用日志
  1580. async queryCallLogs() {
  1581. this.logLoading = true
  1582. try {
  1583. // 根据选择的模型类型获取要查询的modelCodes
  1584. const modelCodes = this.getQueryModelCodes(this.selectedLogModel)
  1585. const params = {
  1586. modelCodes: modelCodes,
  1587. pageNum: this.callLogQuery.pageNum,
  1588. pageSize: this.callLogQuery.pageSize
  1589. }
  1590. if (this.callLogQuery.dateRange && this.callLogQuery.dateRange.length === 2) {
  1591. params.startRecTime = this.callLogQuery.dateRange[0]
  1592. params.endRecTime = this.callLogQuery.dateRange[1]
  1593. }
  1594. if (this.callLogQuery.abilityKey) {
  1595. params.abilityKey = this.callLogQuery.abilityKey
  1596. }
  1597. if (this.callLogQuery.callStatus !== '') {
  1598. params.callStatus = this.callLogQuery.callStatus
  1599. }
  1600. const res = await listCallLog(params)
  1601. this.callLogList = res.rows || []
  1602. this.callLogTotal = res.total || 0
  1603. } catch (error) {
  1604. this.$message.error('查询调用日志失败')
  1605. } finally {
  1606. this.logLoading = false
  1607. }
  1608. },
  1609. // 重置调用日志查询
  1610. resetCallLogQuery() {
  1611. this.selectedLogModel = 'ALL'
  1612. this.initDateRange()
  1613. this.callLogQuery.abilityKey = ''
  1614. this.callLogQuery.callStatus = ''
  1615. this.callLogQuery.pageNum = 1
  1616. this.queryCallLogs()
  1617. },
  1618. // 查询事件日志
  1619. async queryEventLogs() {
  1620. this.eventLogLoading = true
  1621. try {
  1622. // 根据选择的模型类型获取要查询的modelCodes
  1623. const modelCodes = this.getQueryModelCodes(this.selectedEventLogModel)
  1624. const params = {
  1625. modelCodes: modelCodes,
  1626. pageNum: this.eventLogQuery.pageNum,
  1627. pageSize: this.eventLogQuery.pageSize
  1628. }
  1629. if (this.eventLogQuery.dateRange && this.eventLogQuery.dateRange.length === 2) {
  1630. params.startRecTime = this.eventLogQuery.dateRange[0]
  1631. params.endRecTime = this.eventLogQuery.dateRange[1]
  1632. }
  1633. if (this.eventLogQuery.eventKey) {
  1634. params.eventKey = this.eventLogQuery.eventKey
  1635. }
  1636. const res = await listEventLog(params)
  1637. this.eventLogList = res.rows || []
  1638. this.eventLogTotal = res.total || 0
  1639. } catch (error) {
  1640. this.$message.error('查询事件日志失败')
  1641. } finally {
  1642. this.eventLogLoading = false
  1643. }
  1644. },
  1645. // 重置事件日志查询
  1646. resetEventLogQuery() {
  1647. this.selectedEventLogModel = 'ALL'
  1648. this.initDateRange()
  1649. this.eventLogQuery.eventKey = ''
  1650. this.eventLogQuery.pageNum = 1
  1651. this.queryEventLogs()
  1652. },
  1653. // 格式化调用状态
  1654. formatCallStatus(status) {
  1655. const statusMap = {
  1656. 0: '成功',
  1657. 1: '进行中',
  1658. 2: '失败'
  1659. }
  1660. return statusMap[status] || '未知'
  1661. },
  1662. // 获取调用状态类型
  1663. getCallStatusType(status) {
  1664. const typeMap = {
  1665. 0: 'success',
  1666. 1: 'warning',
  1667. 2: 'danger'
  1668. }
  1669. return typeMap[status] || 'info'
  1670. },
  1671. // 查看调用日志详情
  1672. async handleCallLogDetail(row) {
  1673. try {
  1674. const res = await getCallLog(row.id)
  1675. this.callLogDetailData = res.data
  1676. this.callLogDetailDialog = true
  1677. } catch (error) {
  1678. this.$message.error('获取调用日志详情失败')
  1679. }
  1680. },
  1681. // 查看事件日志详情
  1682. async handleEventLogDetail(row) {
  1683. try {
  1684. const res = await getEventLog(row.id)
  1685. this.eventLogDetailData = res.data
  1686. this.eventLogDetailDialog = true
  1687. } catch (error) {
  1688. this.$message.error('获取事件日志详情失败')
  1689. }
  1690. }
  1691. }
  1692. }
  1693. </script>
  1694. <style lang="scss" scoped>
  1695. .app-container {
  1696. padding: 20px;
  1697. background: #f5f7fa;
  1698. min-height: calc(100vh - 84px);
  1699. }
  1700. .system-info-card {
  1701. margin-bottom: 20px;
  1702. .card-header {
  1703. display: flex;
  1704. justify-content: space-between;
  1705. align-items: center;
  1706. .title {
  1707. font-size: 18px;
  1708. font-weight: bold;
  1709. i {
  1710. margin-right: 8px;
  1711. color: #409EFF;
  1712. }
  1713. }
  1714. }
  1715. .info-group {
  1716. .group-title {
  1717. font-size: 14px;
  1718. font-weight: bold;
  1719. color: #606266;
  1720. margin-bottom: 12px;
  1721. padding-bottom: 6px;
  1722. border-bottom: 1px solid #EBEEF5;
  1723. }
  1724. }
  1725. .info-item {
  1726. margin-bottom: 12px;
  1727. font-size: 14px;
  1728. label {
  1729. color: #909399;
  1730. margin-right: 8px;
  1731. display: inline-block;
  1732. min-width: 80px;
  1733. }
  1734. .code {
  1735. font-family: monospace;
  1736. color: #409EFF;
  1737. background: #f0f9ff;
  1738. padding: 2px 6px;
  1739. border-radius: 3px;
  1740. }
  1741. .stat-number {
  1742. color: #409EFF;
  1743. font-weight: bold;
  1744. font-size: 16px;
  1745. }
  1746. }
  1747. }
  1748. .detail-card {
  1749. .tab-content {
  1750. min-height: 400px;
  1751. }
  1752. .attr-section {
  1753. margin-bottom: 30px;
  1754. .section-title {
  1755. margin: 20px 0 15px 0;
  1756. color: #303133;
  1757. i {
  1758. margin-right: 8px;
  1759. color: #409EFF;
  1760. }
  1761. }
  1762. .url-text {
  1763. color: #409EFF;
  1764. font-family: monospace;
  1765. }
  1766. }
  1767. .device-stats {
  1768. margin-bottom: 20px;
  1769. .stat-item {
  1770. text-align: center;
  1771. padding: 20px;
  1772. background: #fff;
  1773. border: 1px solid #EBEEF5;
  1774. border-radius: 4px;
  1775. .stat-value {
  1776. font-size: 28px;
  1777. font-weight: bold;
  1778. color: #303133;
  1779. margin-bottom: 8px;
  1780. }
  1781. .stat-label {
  1782. font-size: 14px;
  1783. color: #909399;
  1784. }
  1785. &.online .stat-value {
  1786. color: #67C23A;
  1787. }
  1788. &.offline .stat-value {
  1789. color: #F56C6C;
  1790. }
  1791. }
  1792. }
  1793. .device-detail {
  1794. padding: 20px;
  1795. background: #f5f7fa;
  1796. h5 {
  1797. margin: 15px 0 10px 0;
  1798. color: #303133;
  1799. }
  1800. }
  1801. .ba-device-header {
  1802. display: flex;
  1803. align-items: center;
  1804. margin-bottom: 15px;
  1805. }
  1806. .ba-device-detail {
  1807. padding: 20px;
  1808. background: #f5f7fa;
  1809. }
  1810. .log-filter {
  1811. margin-bottom: 20px;
  1812. padding: 15px;
  1813. background: #f5f7fa;
  1814. border-radius: 4px;
  1815. }
  1816. }
  1817. .execute-result {
  1818. .result-data {
  1819. margin-top: 20px;
  1820. pre {
  1821. background: #f5f7fa;
  1822. padding: 15px;
  1823. border-radius: 4px;
  1824. overflow: auto;
  1825. max-height: 400px;
  1826. font-family: monospace;
  1827. font-size: 12px;
  1828. }
  1829. }
  1830. }
  1831. .dialog-footer {
  1832. text-align: right;
  1833. }
  1834. ::v-deep .el-dropdown-menu {
  1835. .el-submenu__title {
  1836. padding-right: 40px !important;
  1837. &:hover {
  1838. background-color: #f5f7fa;
  1839. }
  1840. }
  1841. .el-submenu__icon-arrow {
  1842. position: absolute;
  1843. right: 20px;
  1844. }
  1845. }
  1846. </style>