| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057 |
- <template>
- <div class="app-container">
- <!-- 系统基础信息卡片 -->
- <el-card class="system-info-card">
- <div slot="header" class="card-header">
- <span class="title">
- <i class="el-icon-s-platform"></i>
- 能耗监测
- </span>
- <el-tag :type="systemStatus === '1' ? 'success' : 'danger'" effect="dark">
- {{ systemStatus === '1' ? '正常' : '异常' }}
- </el-tag>
- </div>
- <el-row :gutter="20">
- <el-col :span="6">
- <div class="info-group">
- <div class="group-title">系统信息</div>
- <div class="info-item">
- <label>系统代码:</label>
- <span class="code">{{ systemInfo.systemCode }}</span>
- </div>
- <div class="info-item">
- <label>系统名称:</label>
- <span>{{ systemInfo.systemName }}</span>
- </div>
- </div>
- </el-col>
- <el-col :span="6">
- <div class="info-group">
- <div class="group-title">厂商信息</div>
- <div class="info-item">
- <label>对接厂商:</label>
- <span>{{ systemInfo.manFacturer || '-' }}</span>
- </div>
- <div class="info-item">
- <label>联系人:</label>
- <span>{{ systemInfo.contactPerson || '-' }}</span>
- </div>
- <div class="info-item">
- <label>联系电话:</label>
- <span>{{ systemInfo.contactNumber || '-' }}</span>
- </div>
- </div>
- </el-col>
- <el-col :span="6">
- <div class="info-group">
- <div class="group-title">维护信息</div>
- <div class="info-item">
- <label>维护人:</label>
- <span>{{ systemInfo.maintainerPerson || '-' }}</span>
- </div>
- <div class="info-item">
- <label>维护电话:</label>
- <span>{{ systemInfo.maintainerNumber || '-' }}</span>
- </div>
- </div>
- </el-col>
- <el-col :span="6">
- <div class="info-group">
- <div class="group-title">设备统计</div>
- <div class="info-item">
- <label>网关设备:</label>
- <span class="stat-number">{{ deviceStats.total }}</span>
- </div>
- <div class="info-item">
- <label>在线设备:</label>
- <span class="stat-number">{{ deviceStats.online }}</span>
- </div>
- <div class="info-item">
- <label>测点总数:</label>
- <span class="stat-number">{{ deviceStats.points }}</span>
- </div>
- <div style="text-align: right; margin-top: 10px;">
- <el-button
- type="primary"
- size="mini"
- icon="el-icon-edit"
- @click="handleEdit"
- v-hasPermi="['ems:subsystem:edit']">
- 编辑
- </el-button>
- </div>
- </div>
- </el-col>
- </el-row>
- </el-card>
- <!-- 系统详情标签页 -->
- <el-card class="detail-card">
- <el-tabs v-model="activeTab" @tab-click="handleTabClick">
- <!-- 系统属性标签页 -->
- <el-tab-pane label="系统属性" name="attributes">
- <div class="tab-content">
- <!-- 协议属性 -->
- <div class="attr-section" v-if="protocolAttrs.length > 0">
- <h4 class="section-title">
- <i class="el-icon-connection"></i>
- 协议信息
- </h4>
- <el-table :data="protocolAttrs" border stripe>
- <el-table-column prop="attrName" label="属性名称" width="200"></el-table-column>
- <el-table-column label="属性值">
- <template slot-scope="scope">
- <span v-if="scope.row.attrKey === 'url'" class="url-text">
- {{ scope.row.attrValue }}
- </span>
- <span v-else>{{ scope.row.attrValue || '-' }}</span>
- </template>
- </el-table-column>
- </el-table>
- </div>
- <!-- 状态属性 -->
- <div class="attr-section" v-if="stateAttrs.length > 0">
- <h4 class="section-title">
- <i class="el-icon-info"></i>
- 状态信息
- </h4>
- <el-table :data="stateAttrs" border stripe>
- <el-table-column prop="attrName" label="属性名称" width="200"></el-table-column>
- <el-table-column label="属性值">
- <template slot-scope="scope">
- <el-tag v-if="scope.row.attrKey === 'interfaceStatus'"
- :type="scope.row.attrValue === '1' ? 'success' : 'danger'">
- {{ scope.row.attrValueName }}
- </el-tag>
- <span v-else>{{ scope.row.attrValueName || scope.row.attrValue || '-' }}</span>
- </template>
- </el-table-column>
- <el-table-column prop="updateTime" label="更新时间" width="180"></el-table-column>
- </el-table>
- </div>
- </div>
- </el-tab-pane>
- <!-- 系统能力标签页 -->
- <el-tab-pane label="系统能力" name="abilities">
- <div class="tab-content">
- <el-table :data="systemAbilities" border stripe v-loading="abilityLoading">
- <el-table-column prop="abilityName" label="能力名称" width="200"></el-table-column>
- <el-table-column prop="abilityKey" label="能力标识" width="180"></el-table-column>
- <el-table-column prop="abilityDesc" label="能力描述"></el-table-column>
- <el-table-column label="操作" width="200" align="center">
- <template slot-scope="scope">
- <div v-if="getParamType(scope.row.paramDefinition) === 'Options'" style="display: inline-block;">
- <el-button-group>
- <el-button
- v-for="option in parseOptions(scope.row.paramDefinition)"
- :key="option.value"
- size="mini"
- type="primary"
- @click="executeSystemAbilityWithParam(scope.row, option.value)"
- :loading="scope.row.executing && scope.row.executingValue === option.value">
- {{ option.key }}
- </el-button>
- </el-button-group>
- </div>
- <el-button
- v-else
- type="primary"
- size="mini"
- icon="el-icon-caret-right"
- @click="handleSystemAbilityClick(scope.row)"
- :loading="scope.row.executing">
- 执行
- </el-button>
- </template>
- </el-table-column>
- </el-table>
- </div>
- </el-tab-pane>
- <!-- 能耗采集标签页 -->
- <el-tab-pane label="能耗采集" name="devBaGa">
- <div class="tab-content">
- <!-- 设备统计 -->
- <div class="device-stats">
- <el-row :gutter="20">
- <el-col :span="6">
- <div class="stat-item">
- <div class="stat-value">{{ deviceStats.total }}</div>
- <div class="stat-label">设备总数</div>
- </div>
- </el-col>
- <el-col :span="6">
- <div class="stat-item online">
- <div class="stat-value">{{ deviceStats.online }}</div>
- <div class="stat-label">在线设备</div>
- </div>
- </el-col>
- <el-col :span="6">
- <div class="stat-item offline">
- <div class="stat-value">{{ deviceStats.offline }}</div>
- <div class="stat-label">离线设备</div>
- </div>
- </el-col>
- <el-col :span="6">
- <div class="stat-item">
- <div class="stat-value">{{ deviceStats.points }}</div>
- <div class="stat-label">测点总数</div>
- </div>
- </el-col>
- </el-row>
- </div>
- <!-- 设备列表 -->
- <el-table
- :data="deviceList"
- border
- stripe
- v-loading="deviceLoading"
- @row-click="handleDeviceClick"
- ref="deviceTable">
- <el-table-column type="expand">
- <template slot-scope="props">
- <div class="device-detail">
- <el-tabs v-model="props.row.detailTab">
- <el-tab-pane label="设备属性" name="attrs">
- <el-descriptions :column="2" border size="small">
- <el-descriptions-item label="IP地址">
- {{ getDeviceAttr(props.row.deviceCode, 'ip') }}
- </el-descriptions-item>
- <el-descriptions-item label="网关地址">
- {{ getDeviceAttr(props.row.deviceCode, 'gateway') }}
- </el-descriptions-item>
- <el-descriptions-item label="子网掩码">
- {{ getDeviceAttr(props.row.deviceCode, 'subnetMask') }}
- </el-descriptions-item>
- </el-descriptions>
- </el-tab-pane>
- <el-tab-pane label="测点信息" name="points">
- <div v-for="(channel, idx) in getDeviceChannels(props.row.deviceCode)" :key="idx">
- <h5>{{ channel.name }} ({{ channel.points.length }}个测点)</h5>
- <el-table :data="channel.points" size="mini" max-height="200">
- <el-table-column prop="name" label="测点名称" min-width="180"></el-table-column>
- <el-table-column prop="key" label="测点标识" width="150"></el-table-column>
- <el-table-column prop="location" label="位置" min-width="120"></el-table-column>
- <el-table-column prop="desc" label="描述" min-width="120"></el-table-column>
- <el-table-column label="当前值" width="120">
- <template slot-scope="scope">
- <span>{{ scope.row.value || '-' }}</span>
- <span v-if="scope.row.unit" style="margin-left: 4px; color: #909399; font-size: 12px;">
- {{ scope.row.unit }}
- </span>
- </template>
- </el-table-column>
- <el-table-column prop="updateTime" label="更新时间" width="160"></el-table-column>
- </el-table>
- </div>
- <el-empty v-if="getDeviceChannels(props.row.deviceCode).length === 0" description="暂无测点数据"></el-empty>
- </el-tab-pane>
- </el-tabs>
- </div>
- </template>
- </el-table-column>
- <el-table-column prop="deviceCode" label="设备代码" width="150"></el-table-column>
- <el-table-column prop="deviceName" label="设备名称"></el-table-column>
- <el-table-column prop="location" label="安装位置"></el-table-column>
- <el-table-column prop="areaCode" label="区域" width="150">
- <template slot-scope="scope">
- {{ scope.row.areaCode.includes('3001') ? '北区' : '南区' }}
- </template>
- </el-table-column>
- <el-table-column label="设备状态" width="100" align="center">
- <template slot-scope="scope">
- <el-tag :type="scope.row.deviceStatus === 1 ? 'success' : 'info'">
- {{ scope.row.deviceStatus === 1 ? '在线' : '离线' }}
- </el-tag>
- </template>
- </el-table-column>
- <el-table-column label="操作" width="120" align="center">
- <template slot-scope="scope">
- <el-dropdown
- trigger="click"
- @command="handleDeviceCommand"
- @visible-change="(visible) => handleAbilityLoad(visible, scope.row)">
- <el-button type="text" size="mini">
- 操作<i class="el-icon-arrow-down el-icon--right"></i>
- </el-button>
- <el-dropdown-menu slot="dropdown">
- <template v-if="deviceAbilities[scope.row.deviceCode] && deviceAbilities[scope.row.deviceCode].length > 0">
- <template v-for="ability in deviceAbilities[scope.row.deviceCode]">
- <el-submenu
- v-if="getParamType(ability.paramDefinition) === 'Options'"
- :key="ability.abilityKey"
- :index="ability.abilityKey">
- <template slot="title">
- {{ ability.abilityName }}
- <i class="el-icon-arrow-right" style="float: right;"></i>
- </template>
- <el-dropdown-item
- v-for="option in parseOptions(ability.paramDefinition)"
- :key="option.value"
- :command="{device: scope.row, ability: ability, paramValue: option.value}">
- {{ option.key }}
- </el-dropdown-item>
- </el-submenu>
- <el-dropdown-item
- v-else
- :key="ability.abilityKey"
- :command="{device: scope.row, ability: ability}">
- {{ ability.abilityName }}
- </el-dropdown-item>
- </template>
- </template>
- <el-dropdown-item v-else disabled>
- 无可用操作
- </el-dropdown-item>
- </el-dropdown-menu>
- </el-dropdown>
- </template>
- </el-table-column>
- </el-table>
- </div>
- </el-tab-pane>
- <el-tab-pane label="楼控设备" name="baDevices">
- <div class="tab-content">
- <!-- 设备类型选择 -->
- <div class="ba-device-header">
- <el-select v-model="selectedBaDeviceModel" placeholder="请选择设备类型" @change="handleBaDeviceTypeChange" style="width: 200px;">
- <el-option
- v-for="item in baDeviceModels"
- :key="item.modelCode"
- :label="item.modelName"
- :value="item.modelCode">
- </el-option>
- </el-select>
- <el-button type="primary" icon="el-icon-refresh" @click="refreshBaDevices" :loading="baDeviceLoading" style="margin-left: 10px;">
- 刷新
- </el-button>
- </div>
- <!-- 设备列表 -->
- <el-table
- :data="baDeviceList"
- border
- stripe
- v-loading="baDeviceLoading"
- style="margin-top: 15px;">
- <el-table-column type="expand" width="50">
- <template slot-scope="props">
- <div class="ba-device-detail">
- <el-tabs v-model="props.row.detailTab">
- <!-- 基础属性 -->
- <el-tab-pane label="基础属性" name="base">
- <el-descriptions :column="2" border size="small" v-if="getBaDeviceAttrs(props.row.deviceCode, 'Base').length > 0">
- <el-descriptions-item
- v-for="attr in getBaDeviceAttrs(props.row.deviceCode, 'Base')"
- :key="attr.attrKey"
- :label="attr.attrName">
- {{ attr.attrValue || '-' }}
- </el-descriptions-item>
- </el-descriptions>
- <el-empty v-else description="暂无基础属性"></el-empty>
- </el-tab-pane>
- <!-- 状态属性 -->
- <el-tab-pane label="状态属性" name="state">
- <el-table
- :data="getBaDeviceAttrs(props.row.deviceCode, 'State')"
- border
- size="small"
- v-if="getBaDeviceAttrs(props.row.deviceCode, 'State').length > 0">
- <el-table-column prop="attrName" label="属性名称" width="200"></el-table-column>
- <el-table-column label="属性值" min-width="180">
- <template slot-scope="scope">
- <el-tag v-if="scope.row.attrValueType === 'Enum' && scope.row.attrValueName" size="small">
- {{ scope.row.attrValueName }}
- </el-tag>
- <span v-else>
- {{ formatBaAttrValue(scope.row) }}
- </span>
- </template>
- </el-table-column>
- <el-table-column prop="updateTime" label="更新时间" width="180"></el-table-column>
- </el-table>
- <el-empty v-else description="暂无状态数据"></el-empty>
- </el-tab-pane>
- </el-tabs>
- </div>
- </template>
- </el-table-column>
- <el-table-column prop="deviceCode" label="设备代码" width="160" show-overflow-tooltip></el-table-column>
- <el-table-column prop="deviceName" label="设备名称" width="200" show-overflow-tooltip></el-table-column>
- <el-table-column prop="location" label="安装位置" width="180" show-overflow-tooltip></el-table-column>
- <el-table-column prop="areaCode" label="区域" width="100" align="center">
- <template slot-scope="scope">
- {{ scope.row.areaCode && scope.row.areaCode.includes('3001') ? '北区' : '南区' }}
- </template>
- </el-table-column>
- <el-table-column label="设备状态" width="100" align="center">
- <template slot-scope="scope">
- <el-tag :type="scope.row.deviceStatus === 1 ? 'success' : 'info'">
- {{ scope.row.deviceStatus === 1 ? '在线' : '离线' }}
- </el-tag>
- </template>
- </el-table-column>
- <!-- 新增操作列 -->
- <el-table-column label="操作" min-width="220" align="center">
- <template slot-scope="scope">
- <!-- 获取当前设备模型的能力列表 -->
- <template v-if="getBaDeviceAbilities(scope.row.deviceModel).length > 0">
- <template v-for="ability in getBaDeviceAbilities(scope.row.deviceModel)">
- <!-- 如果能力有 Options 参数定义,展示多个按钮 -->
- <el-button-group v-if="getParamType(ability.paramDefinition) === 'Options'" :key="ability.abilityKey" style="margin: 2px;">
- <el-button
- v-for="option in parseOptions(ability.paramDefinition)"
- :key="option.value"
- size="mini"
- type="primary"
- @click="executeBaDeviceAbility(scope.row, ability, option.value)"
- :loading="isBaAbilityExecuting(scope.row.deviceCode, ability.abilityKey, option.value)">
- {{ option.key }}
- </el-button>
- </el-button-group>
- <!-- 如果能力有其他参数类型(Slider/Input),显示单个按钮 -->
- <el-button
- v-else-if="getParamType(ability.paramDefinition) === 'Slider' || getParamType(ability.paramDefinition) === 'Input'"
- :key="ability.abilityKey"
- size="mini"
- type="primary"
- @click="handleBaDeviceAbilityClick(scope.row, ability)"
- :loading="isBaAbilityExecuting(scope.row.deviceCode, ability.abilityKey)"
- style="margin: 2px;">
- {{ ability.abilityName }}
- </el-button>
- <!-- 如果能力无参数,直接执行 -->
- <el-button
- v-else
- :key="ability.abilityKey"
- size="mini"
- type="primary"
- @click="executeBaDeviceAbility(scope.row, ability, null)"
- :loading="isBaAbilityExecuting(scope.row.deviceCode, ability.abilityKey)"
- style="margin: 2px;">
- {{ ability.abilityName }}
- </el-button>
- </template>
- </template>
- <span v-else style="color: #909399; font-size: 12px;">无可用操作</span>
- </template>
- </el-table-column>
- </el-table>
- </div>
- </el-tab-pane>
- <!-- 调用日志标签页 -->
- <el-tab-pane label="调用日志" name="callLogs">
- <div class="tab-content">
- <el-form :inline="true" :model="callLogQuery" class="log-filter">
- <el-form-item label="模型类型">
- <el-select v-model="selectedLogModel" placeholder="请选择模型类型" clearable style="width: 200px;">
- <el-option
- v-for="item in logModelOptions"
- :key="item.modelCode"
- :label="item.modelName"
- :value="item.modelCode">
- </el-option>
- </el-select>
- </el-form-item>
- <el-form-item label="时间范围">
- <el-date-picker
- v-model="callLogQuery.dateRange"
- type="datetimerange"
- range-separator="至"
- start-placeholder="开始日期"
- end-placeholder="结束日期"
- value-format="yyyy-MM-dd HH:mm:ss">
- </el-date-picker>
- </el-form-item>
- <el-form-item label="能力标识">
- <el-input v-model="callLogQuery.abilityKey" placeholder="请输入能力标识" clearable></el-input>
- </el-form-item>
- <el-form-item label="调用状态">
- <el-select v-model="callLogQuery.callStatus" placeholder="全部" clearable>
- <el-option label="成功" :value="0"></el-option>
- <el-option label="进行中" :value="1"></el-option>
- <el-option label="失败" :value="2"></el-option>
- </el-select>
- </el-form-item>
- <el-form-item>
- <el-button type="primary" @click="queryCallLogs">查询</el-button>
- <el-button @click="resetCallLogQuery">重置</el-button>
- </el-form-item>
- </el-form>
- <el-table :data="callLogList" border stripe v-loading="logLoading">
- <el-table-column prop="objCode" label="对象代码" min-width="150"></el-table-column>
- <el-table-column prop="objName" label="对象名称" min-width="200"></el-table-column>
- <el-table-column label="能力名称" min-width="150">
- <template slot-scope="scope">
- {{ scope.row.abilityName || scope.row.abilityKey }}
- </template>
- </el-table-column>
- <el-table-column prop="callTime" label="调用时间" width="180"></el-table-column>
- <el-table-column label="调用状态" width="100" align="center">
- <template slot-scope="scope">
- <el-tag size="small" :type="getCallStatusType(scope.row.callStatus)">
- {{ formatCallStatus(scope.row.callStatus) }}
- </el-tag>
- </template>
- </el-table-column>
- <el-table-column label="操作" width="80" align="center">
- <template slot-scope="scope">
- <el-button type="text" size="mini" @click="handleCallLogDetail(scope.row)">详情</el-button>
- </template>
- </el-table-column>
- </el-table>
- <pagination
- v-show="callLogTotal > 0"
- :total="callLogTotal"
- :page.sync="callLogQuery.pageNum"
- :limit.sync="callLogQuery.pageSize"
- @pagination="queryCallLogs"
- />
- </div>
- </el-tab-pane>
- <!-- 事件日志标签页 -->
- <el-tab-pane label="事件日志" name="eventLogs">
- <div class="tab-content">
- <el-form :inline="true" :model="eventLogQuery" class="log-filter">
- <el-form-item label="模型类型">
- <el-select v-model="selectedEventLogModel" placeholder="请选择模型类型" clearable style="width: 200px;">
- <el-option
- v-for="item in logModelOptions"
- :key="item.modelCode"
- :label="item.modelName"
- :value="item.modelCode">
- </el-option>
- </el-select>
- </el-form-item>
- <el-form-item label="时间范围">
- <el-date-picker
- v-model="eventLogQuery.dateRange"
- type="datetimerange"
- range-separator="至"
- start-placeholder="开始日期"
- end-placeholder="结束日期"
- value-format="yyyy-MM-dd HH:mm:ss">
- </el-date-picker>
- </el-form-item>
- <el-form-item label="事件标识">
- <el-input v-model="eventLogQuery.eventKey" placeholder="请输入事件标识" clearable></el-input>
- </el-form-item>
- <el-form-item>
- <el-button type="primary" @click="queryEventLogs">查询</el-button>
- <el-button @click="resetEventLogQuery">重置</el-button>
- </el-form-item>
- </el-form>
- <el-table :data="eventLogList" border stripe v-loading="eventLogLoading">
- <el-table-column prop="objCode" label="对象编号" min-width="150"></el-table-column>
- <el-table-column prop="objName" label="对象名称" min-width="200"></el-table-column>
- <el-table-column prop="eventName" label="事件名称" min-width="200"></el-table-column>
- <el-table-column prop="eventTime" label="事件时间" width="180"></el-table-column>
- <el-table-column label="操作" width="80" align="center">
- <template slot-scope="scope">
- <el-button type="text" size="mini" @click="handleEventLogDetail(scope.row)">详情</el-button>
- </template>
- </el-table-column>
- </el-table>
- <pagination
- v-show="eventLogTotal > 0"
- :total="eventLogTotal"
- :page.sync="eventLogQuery.pageNum"
- :limit.sync="eventLogQuery.pageSize"
- @pagination="queryEventLogs"
- />
- </div>
- </el-tab-pane>
- </el-tabs>
- </el-card>
- <!-- 修改系统信息对话框 -->
- <el-dialog title="修改系统信息" :visible.sync="editDialogVisible" width="500px" append-to-body>
- <el-form ref="editForm" :model="editForm" :rules="editRules" label-width="80px">
- <el-form-item label="系统代码" prop="systemCode">
- <el-input v-model="editForm.systemCode" placeholder="请输入系统代码" disabled />
- </el-form-item>
- <el-form-item label="系统名称" prop="systemName">
- <el-input v-model="editForm.systemName" placeholder="请输入系统名称" />
- </el-form-item>
- <el-form-item label="系统简称" prop="shortName">
- <el-input v-model="editForm.shortName" placeholder="请输入系统简称" />
- </el-form-item>
- <el-form-item label="厂商" prop="manFacturer">
- <el-input v-model="editForm.manFacturer" placeholder="请输入厂商" />
- </el-form-item>
- <el-form-item label="联系人" prop="contactPerson">
- <el-input v-model="editForm.contactPerson" placeholder="请输入联系人" />
- </el-form-item>
- <el-form-item label="联系电话" prop="contactNumber">
- <el-input v-model="editForm.contactNumber" placeholder="请输入联系电话" />
- </el-form-item>
- <el-form-item label="维护人" prop="maintainerPerson">
- <el-input v-model="editForm.maintainerPerson" placeholder="请输入维护人" />
- </el-form-item>
- <el-form-item label="维护电话" prop="maintainerNumber">
- <el-input v-model="editForm.maintainerNumber" placeholder="请输入维护电话" />
- </el-form-item>
- <el-form-item label="备注说明" prop="descr">
- <el-input v-model="editForm.descr" type="textarea" placeholder="请输入内容" />
- </el-form-item>
- </el-form>
- <div slot="footer" class="dialog-footer">
- <el-button @click="cancelEdit">取 消</el-button>
- <el-button type="primary" @click="submitEdit" :loading="editLoading">确 定</el-button>
- </div>
- </el-dialog>
- <!-- 执行结果对话框 -->
- <el-dialog
- :title="executeDialog.title"
- :visible.sync="executeDialog.visible"
- width="600px">
- <div class="execute-result">
- <el-alert
- :title="executeDialog.status"
- :type="executeDialog.type"
- :description="executeDialog.message"
- show-icon>
- </el-alert>
- <div v-if="executeDialog.data" class="result-data">
- <pre>{{ JSON.stringify(executeDialog.data, null, 2) }}</pre>
- </div>
- </div>
- <span slot="footer">
- <el-button @click="executeDialog.visible = false">关闭</el-button>
- </span>
- </el-dialog>
- <!-- 调用日志详情弹窗 -->
- <el-dialog :visible.sync="callLogDetailDialog" title="调用日志详情" width="60%">
- <div v-if="callLogDetailData">
- <el-descriptions :column="2" border>
- <el-descriptions-item label="对象代码">{{ callLogDetailData.objCode }}</el-descriptions-item>
- <el-descriptions-item label="模型代码">{{ callLogDetailData.modelCode }}</el-descriptions-item>
- <el-descriptions-item label="能力标识">{{ callLogDetailData.abilityKey }}</el-descriptions-item>
- <el-descriptions-item label="调用时间">{{ callLogDetailData.callTime }}</el-descriptions-item>
- <el-descriptions-item label="响应时间">{{ callLogDetailData.resTime }}</el-descriptions-item>
- <el-descriptions-item label="调用结果">
- <el-tag :type="getCallStatusType(callLogDetailData.callStatus)">
- {{ formatCallStatus(callLogDetailData.callStatus) }}
- </el-tag>
- </el-descriptions-item>
- </el-descriptions>
- <div style="margin-top: 20px;">
- <h4>调用载体</h4>
- <pre style="white-space: pre-wrap; background-color: #f5f5f5; padding: 10px; border-radius: 4px; max-height: 300px; overflow: auto;">{{ callLogDetailData.callPayload }}</pre>
- </div>
- <div style="margin-top: 20px;">
- <h4>响应载体</h4>
- <pre style="white-space: pre-wrap; background-color: #f5f5f5; padding: 10px; border-radius: 4px; max-height: 300px; overflow: auto;">{{ callLogDetailData.resPayload }}</pre>
- </div>
- </div>
- </el-dialog>
- <!-- 事件日志详情弹窗 -->
- <el-dialog :visible.sync="eventLogDetailDialog" title="事件日志详情" width="60%">
- <div v-if="eventLogDetailData">
- <el-descriptions :column="2" border>
- <el-descriptions-item label="对象编号">{{ eventLogDetailData.objCode }}</el-descriptions-item>
- <el-descriptions-item label="对象名称">{{ eventLogDetailData.objName }}</el-descriptions-item>
- <el-descriptions-item label="事件名称">{{ eventLogDetailData.eventName }}</el-descriptions-item>
- <el-descriptions-item label="事件标识">{{ eventLogDetailData.eventKey }}</el-descriptions-item>
- <el-descriptions-item label="事件时间" :span="2">{{ eventLogDetailData.eventTime }}</el-descriptions-item>
- </el-descriptions>
- <div style="margin-top: 20px;">
- <h4>事件描述</h4>
- <div style="background-color: #f5f5f5; padding: 10px; border-radius: 4px;">
- {{ eventLogDetailData.eventDetail || '无' }}
- </div>
- </div>
- </div>
- </el-dialog>
- <!-- 能力执行弹窗 -->
- <el-dialog :title="abilityDialogTitle" :visible.sync="abilityDialogVisible" width="500px" append-to-body :close-on-click-modal="false">
- <el-form ref="abilityForm" :model="abilityForm" label-width="100px">
- <el-form-item label="能力名称">
- <el-input v-model="abilityForm.abilityName" disabled></el-input>
- </el-form-item>
- <el-form-item label="能力描述">
- <el-input type="textarea" v-model="abilityForm.abilityDesc" disabled :rows="2"></el-input>
- </el-form-item>
- <el-form-item label="参数设置" v-if="abilityParamType">
- <template v-if="abilityParamType === 'Slider'">
- <div style="width: 100%;">
- <div style="padding: 0 10px;">
- <el-slider
- v-model="abilitySliderValue"
- :min="abilitySliderMin"
- :max="abilitySliderMax"
- :show-tooltip="true"
- :format-tooltip="formatTooltip">
- </el-slider>
- </div>
- <div style="display: flex; justify-content: space-between; padding: 0 10px; margin-top: -5px; margin-bottom: 20px;">
- <span style="font-size: 12px; color: #909399;">{{ abilitySliderMin }}%</span>
- <span style="font-size: 12px; color: #909399;">{{ Math.floor((abilitySliderMin + abilitySliderMax) / 2) }}%</span>
- <span style="font-size: 12px; color: #909399;">{{ abilitySliderMax }}%</span>
- </div>
- <div style="display: flex; align-items: center; gap: 15px;">
- <el-input-number
- v-model="abilitySliderValue"
- :min="abilitySliderMin"
- :max="abilitySliderMax"
- :step="1"
- :precision="0"
- controls-position="right"
- style="flex: 0 0 120px;">
- </el-input-number>
- <span style="color: #909399; font-size: 13px;">
- 调节范围:{{ abilitySliderMin }} - {{ abilitySliderMax }}
- </span>
- </div>
- </div>
- </template>
- <template v-else-if="abilityParamType === 'Input'">
- <el-input
- v-model="abilityInputValue"
- placeholder="请输入参数值"
- clearable>
- </el-input>
- </template>
- </el-form-item>
- </el-form>
- <div slot="footer" class="dialog-footer">
- <el-button @click="abilityDialogVisible = false">取消</el-button>
- <el-button type="primary" @click="executeAbilityWithParam" :loading="abilityExecuting">执行</el-button>
- </div>
- </el-dialog>
- </div>
- </template>
- <script>
- import { getSubsystemByCode, updateSubsystem } from '@/api/adapter/subsystem'
- import { getModelByCode } from '@/api/basecfg/objModel'
- import { getObjAttr, getObjAttrBatch } from '@/api/basecfg/objAttribute'
- import { callAbility } from '@/api/basecfg/objAbility'
- import { getByCondition } from '@/api/device/device'
- import { listCallLog, listEventLog, getCallLog, getEventLog } from '@/api/basecfg/objLog'
- export default {
- name: 'SubsystemIntegration',
- data() {
- return {
- // 系统代码
- systemCode: 'SYS_BA',
- // 系统信息
- systemInfo: {},
- // 系统状态
- systemStatus: '1',
- // 当前标签页
- activeTab: 'attributes',
- // 协议属性
- protocolAttrs: [],
- // 状态属性
- stateAttrs: [],
- // 系统能力
- systemAbilities: [],
- abilityLoading: false,
- // 设备列表(能耗网关)
- deviceList: [],
- deviceLoading: false,
- deviceAttrs: {},
- deviceChannels: {},
- // 设备能力缓存
- deviceAbilities: {},
- // 设备统计
- deviceStats: {
- total: 0,
- online: 0,
- offline: 0,
- points: 0
- },
- // 所有模型代码(系统 + 设备)
- allModelCodes: [],
- // 子设备相关
- subDeviceAttrs: {},
- subDeviceGatewayMap: {},
- // 楼控设备相关
- baDeviceModels: [
- { modelCode: 'M_Z020_DEV_BA_XF', modelName: 'BA新风设备' },
- { modelCode: 'M_Z020_DEV_BA_AHU', modelName: 'BA空调设备' },
- { modelCode: 'M_Z020_DEV_BA_WT', modelName: 'BA水箱监测' },
- { modelCode: 'M_Z020_DEV_BA_WP', modelName: 'BA水泵监测' },
- { modelCode: 'M_Z020_DEV_BA_LIGHT', modelName: 'BA照明监测' },
- ],
- selectedBaDeviceModel: 'M_Z020_DEV_BA_XF',
- baDeviceList: [],
- baDeviceLoading: false,
- baDeviceAttrs: {},
- // 楼控设备能力执行状态
- baAbilityExecuting: {},
- // 日志模型选项
- logModelOptions: [
- { modelCode: 'ALL', modelName: '全部' },
- { modelCode: 'M_W4_SYS_BA', modelName: '系统日志' },
- { modelCode: 'M_W4_DEV_BA_GA', modelName: '能耗监测' },
- { modelCode: 'M_Z020_DEV_BA_XF', modelName: 'BA新风设备' },
- { modelCode: 'M_Z020_DEV_BA_AHU', modelName: 'BA空调设备' },
- { modelCode: 'M_Z020_DEV_BA_WT', modelName: 'BA水箱监测' },
- { modelCode: 'M_Z020_DEV_BA_WP', modelName: 'BA水泵监测' },
- { modelCode: 'M_Z020_DEV_BA_LIGHT', modelName: 'BA照明监测' },
- ],
- selectedLogModel: 'ALL', // 调用日志选择的模型
- selectedEventLogModel: 'ALL', // 事件日志选择的模型
- // 调用日志查询
- callLogQuery: {
- dateRange: [],
- abilityKey: '',
- callStatus: '',
- pageNum: 1,
- pageSize: 10
- },
- callLogList: [],
- callLogTotal: 0,
- logLoading: false,
- // 事件日志查询
- eventLogQuery: {
- dateRange: [],
- eventKey: '',
- pageNum: 1,
- pageSize: 10
- },
- eventLogList: [],
- eventLogTotal: 0,
- eventLogLoading: false,
- // 调用日志详情
- callLogDetailDialog: false,
- callLogDetailData: null,
- // 事件日志详情
- eventLogDetailDialog: false,
- eventLogDetailData: null,
- // 执行结果对话框
- executeDialog: {
- visible: false,
- title: '',
- status: '',
- type: 'success',
- message: '',
- data: null
- },
- // 能力执行相关
- abilityDialogVisible: false,
- abilityDialogTitle: '能力执行',
- abilityForm: {
- abilityName: '',
- abilityKey: '',
- abilityDesc: '',
- paramDefinition: null,
- modelCode: ''
- },
- abilityParamType: null,
- abilitySliderValue: 50,
- abilitySliderMin: 0,
- abilitySliderMax: 100,
- abilityInputValue: '',
- abilityExecuting: false,
- currentDevice: null,
- currentObjType: null,
- // 编辑系统信息相关
- editDialogVisible: false,
- editLoading: false,
- editForm: {},
- editRules: {
- systemCode: [
- { required: true, message: "系统代码不能为空", trigger: "blur" }
- ],
- systemName: [
- { required: true, message: "系统名称不能为空", trigger: "blur" }
- ]
- }
- }
- },
- created() {
- this.loadSystemInfo()
- this.initDateRange()
- },
- methods: {
- // ========== 楼控设备能力相关方法 ==========
- // 获取楼控设备的能力列表
- getBaDeviceAbilities(deviceModel) {
- if (!deviceModel) return []
- const modelData = this.baDeviceAttrs['_model_' + deviceModel]
- if (!modelData || !modelData.abilityList) return []
- // 只返回 hiddenFlag === 1 的能力
- return modelData.abilityList.filter(ability => ability.hiddenFlag === 1)
- },
- // 处理楼控设备能力点击(需要参数输入)
- handleBaDeviceAbilityClick(device, ability) {
- const paramType = this.getParamType(ability.paramDefinition)
- if (paramType === 'Slider') {
- this.showSliderDialog(ability, device, 2)
- } else if (paramType === 'Input') {
- this.showInputDialog(ability, device, 2)
- }
- },
- // 执行楼控设备能力
- async executeBaDeviceAbility(device, ability, paramValue) {
- // 设置执行状态
- const stateKey = `${device.deviceCode}_${ability.abilityKey}${paramValue !== null ? '_' + paramValue : ''}`
- this.$set(this.baAbilityExecuting, stateKey, true)
- try {
- const params = {
- objCode: device.deviceCode,
- objType: 2,
- modelCode: device.deviceModel,
- abilityKey: ability.abilityKey,
- abilityParam: paramValue
- }
- await callAbility(params)
- this.$message.success(`${ability.abilityName}执行成功`)
- // 刷新设备属性
- await this.loadBaDeviceAttrsBatch(device.deviceModel)
- } catch (error) {
- this.$message.error(`${ability.abilityName}执行失败:${error.message}`)
- } finally {
- this.$set(this.baAbilityExecuting, stateKey, false)
- }
- },
- // 判断楼控设备能力是否正在执行
- isBaAbilityExecuting(deviceCode, abilityKey, paramValue) {
- const stateKey = `${deviceCode}_${abilityKey}${paramValue !== undefined ? '_' + paramValue : ''}`
- return this.baAbilityExecuting[stateKey] || false
- },
- // ========== 原有方法保持不变 ==========
- // 打开编辑对话框
- handleEdit() {
- this.editForm = { ...this.systemInfo }
- this.editDialogVisible = true
- this.$nextTick(() => {
- this.$refs.editForm?.clearValidate()
- })
- },
- // 取消编辑
- cancelEdit() {
- this.editDialogVisible = false
- this.editForm = {}
- },
- // 提交编辑
- submitEdit() {
- this.$refs.editForm.validate(valid => {
- if (valid) {
- this.editLoading = true
- updateSubsystem(this.editForm).then(response => {
- this.$message.success("修改成功")
- this.editDialogVisible = false
- this.loadSystemInfo()
- }).catch(error => {
- this.$message.error("修改失败:" + error.message)
- }).finally(() => {
- this.editLoading = false
- })
- }
- })
- },
- // 格式化滑块tooltip
- formatTooltip(val) {
- return `${val}%`
- },
- // 解析参数定义类型
- getParamType(paramDefinition) {
- if (!paramDefinition) return null
- try {
- const def = JSON.parse(paramDefinition)
- return def.type
- } catch (e) {
- return null
- }
- },
- // 解析Options类型的选项
- parseOptions(paramDefinition) {
- try {
- const def = JSON.parse(paramDefinition)
- if (def.type === 'Options' && def.list) {
- return def.list
- }
- } catch (e) {
- console.error('解析Options失败:', e)
- }
- return []
- },
- // 处理系统能力点击
- handleSystemAbilityClick(ability) {
- const paramType = this.getParamType(ability.paramDefinition)
- if (paramType === 'Slider') {
- this.showSliderDialog(ability, this.systemInfo, 3)
- } else if (paramType === 'Input') {
- this.showInputDialog(ability, this.systemInfo, 3)
- } else {
- this.executeSystemAbility(ability)
- }
- },
- // 执行系统能力(带Options参数)
- executeSystemAbilityWithParam(ability, paramValue) {
- ability.executing = true
- ability.executingValue = paramValue
- callAbility({
- objCode: this.systemCode,
- objType: 3,
- modelCode: this.systemInfo.modelCode,
- abilityKey: ability.abilityKey,
- abilityParam: paramValue
- }).then(res => {
- this.executeDialog = {
- visible: true,
- title: '执行结果 - ' + ability.abilityName,
- status: '执行成功',
- type: 'success',
- message: '系统能力执行完成',
- data: res.data
- }
- }).catch(error => {
- this.executeDialog = {
- visible: true,
- title: '执行结果 - ' + ability.abilityName,
- status: '执行失败',
- type: 'error',
- message: error.message,
- data: null
- }
- }).finally(() => {
- ability.executing = false
- ability.executingValue = null
- })
- },
- // 执行系统能力(无参数)
- executeSystemAbility(ability) {
- ability.executing = true
- callAbility({
- objCode: this.systemCode,
- objType: 3,
- modelCode: this.systemInfo.modelCode,
- abilityKey: ability.abilityKey,
- abilityParam: null
- }).then(res => {
- this.executeDialog = {
- visible: true,
- title: '执行结果 - ' + ability.abilityName,
- status: '执行成功',
- type: 'success',
- message: '系统能力执行完成',
- data: res.data
- }
- if (ability.abilityKey === 'MeterReadingGw' || ability.abilityKey === 'MeterReadingTotal') {
- this.loadDevices()
- }
- }).catch(error => {
- this.executeDialog = {
- visible: true,
- title: '执行结果 - ' + ability.abilityName,
- status: '执行失败',
- type: 'error',
- message: error.message,
- data: null
- }
- }).finally(() => {
- ability.executing = false
- })
- },
- // 显示滑块弹窗
- showSliderDialog(ability, obj, objType) {
- this.currentDevice = obj
- this.currentObjType = objType
- this.abilityForm = { ...ability }
- const objName = objType === 3 ? this.systemInfo.systemName : obj.deviceName
- this.abilityDialogTitle = `${ability.abilityName} - ${objName}`
- this.abilityParamType = 'Slider'
- try {
- const def = JSON.parse(ability.paramDefinition)
- this.abilitySliderMin = def.min || 0
- this.abilitySliderMax = def.max || 100
- this.abilitySliderValue = Math.floor((this.abilitySliderMin + this.abilitySliderMax) / 2)
- } catch (e) {
- console.error('解析Slider参数失败:', e)
- this.abilitySliderMin = 0
- this.abilitySliderMax = 100
- this.abilitySliderValue = 50
- }
- this.abilityDialogVisible = true
- },
- // 显示输入弹窗
- showInputDialog(ability, obj, objType) {
- this.currentDevice = obj
- this.currentObjType = objType
- this.abilityForm = { ...ability }
- const objName = objType === 3 ? this.systemInfo.systemName : obj.deviceName
- this.abilityDialogTitle = `${ability.abilityName} - ${objName}`
- this.abilityParamType = 'Input'
- this.abilityInputValue = ''
- this.abilityDialogVisible = true
- },
- // 执行带参数的能力
- executeAbilityWithParam() {
- let paramValue = null
- if (this.abilityParamType === 'Slider') {
- paramValue = String(this.abilitySliderValue)
- } else if (this.abilityParamType === 'Input') {
- if (!this.abilityInputValue) {
- this.$message.warning('请输入参数值')
- return
- }
- paramValue = this.abilityInputValue
- }
- this.abilityExecuting = true
- const callParams = {
- objType: this.currentObjType,
- abilityKey: this.abilityForm.abilityKey,
- abilityParam: paramValue
- }
- if (this.currentObjType === 3) {
- callParams.objCode = this.systemCode
- callParams.modelCode = this.systemInfo.modelCode
- } else if (this.currentObjType === 2) {
- callParams.objCode = this.currentDevice.deviceCode
- callParams.modelCode = this.currentDevice.deviceModel
- }
- callAbility(callParams).then(response => {
- this.$message.success('执行成功')
- this.abilityDialogVisible = false
- if (this.abilityForm.abilityKey.toLowerCase().includes('meter') ||
- this.abilityForm.abilityKey.toLowerCase().includes('reading')) {
- if (this.currentObjType === 3) {
- this.loadDevices()
- } else if (this.currentObjType === 2) {
- this.loadDeviceAttrs(this.currentDevice.deviceCode)
- }
- }
- }).catch(() => {
- this.$message.error('执行失败')
- }).finally(() => {
- this.abilityExecuting = false
- })
- },
- // 初始化时间范围
- initDateRange() {
- const now = new Date()
- const startOfDay = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 0, 0, 0)
- const endOfDay = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 23, 59, 59)
- this.callLogQuery.dateRange = [
- this.formatDate(startOfDay),
- this.formatDate(endOfDay)
- ]
- this.eventLogQuery.dateRange = [
- this.formatDate(startOfDay),
- this.formatDate(endOfDay)
- ]
- },
- // 格式化日期
- formatDate(date) {
- if (!date) return ''
- const year = date.getFullYear()
- const month = (date.getMonth() + 1).toString().padStart(2, '0')
- const day = date.getDate().toString().padStart(2, '0')
- const hours = date.getHours().toString().padStart(2, '0')
- const minutes = date.getMinutes().toString().padStart(2, '0')
- const seconds = date.getSeconds().toString().padStart(2, '0')
- return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
- },
- // 加载系统信息
- async loadSystemInfo() {
- try {
- const sysRes = await getSubsystemByCode(this.systemCode)
- this.systemInfo = sysRes.data
- if (this.systemInfo.modelCode) {
- this.allModelCodes.push(this.systemInfo.modelCode)
- const modelRes = await getModelByCode(this.systemInfo.modelCode)
- const modelData = modelRes.data
- this.systemAbilities = (modelData.abilityList || []).map(item => ({
- ...item,
- executing: false,
- executingValue: null
- }))
- }
- const attrRes = await getObjAttr(3, this.systemCode)
- const attrData = attrRes.data
- this.protocolAttrs = attrData.Protocol || []
- this.stateAttrs = attrData.State || []
- const statusAttr = this.stateAttrs.find(attr => attr.attrKey === 'interfaceStatus')
- if (statusAttr) {
- this.systemStatus = statusAttr.attrValue
- }
- await this.loadDevices()
- } catch (error) {
- this.$message.error('加载系统信息失败:' + error.message)
- }
- },
- // 加载设备列表(能耗网关)
- async loadDevices() {
- this.deviceLoading = true
- try {
- const res = await getByCondition({
- subsystemCode: this.systemCode,
- deviceModel: 'M_W4_DEV_BA_GA'
- })
- const deviceData = res.data || res.rows || []
- this.deviceList = deviceData.map(device => ({
- ...device,
- detailTab: 'attrs'
- }))
- const deviceModelCodes = new Set()
- this.deviceList.forEach(device => {
- if (device.deviceModel) {
- deviceModelCodes.add(device.deviceModel)
- }
- })
- deviceModelCodes.forEach(modelCode => {
- if (!this.allModelCodes.includes(modelCode)) {
- this.allModelCodes.push(modelCode)
- }
- })
- this.deviceStats.total = this.deviceList.length
- this.deviceStats.online = this.deviceList.filter(d => d.deviceStatus === 1).length
- this.deviceStats.offline = this.deviceList.filter(d => d.deviceStatus !== 1).length
- const devicesByModel = {}
- this.deviceList.forEach(device => {
- if (device.deviceModel) {
- if (!devicesByModel[device.deviceModel]) {
- devicesByModel[device.deviceModel] = []
- }
- devicesByModel[device.deviceModel].push(device)
- }
- })
- for (const modelCode in devicesByModel) {
- await this.loadDeviceAttrsBatch(modelCode, devicesByModel[modelCode])
- }
- await this.loadAllSubDeviceAttrs()
- } catch (error) {
- this.$message.error('加载设备列表失败')
- } finally {
- this.deviceLoading = false
- }
- },
- // 批量加载网关设备属性
- async loadDeviceAttrsBatch(modelCode, devices) {
- try {
- const res = await getObjAttrBatch(2, modelCode)
- const batchData = res.data || {}
- devices.forEach(device => {
- const deviceCode = device.deviceCode
- const attrData = batchData[deviceCode]
- if (attrData) {
- const baseAttrs = {}
- if (attrData.Base) {
- attrData.Base.forEach(attr => {
- baseAttrs[attr.attrKey] = attr.attrValue
- })
- }
- this.deviceAttrs[deviceCode] = baseAttrs
- }
- })
- } catch (error) {
- console.error(`批量加载模型 ${modelCode} 的设备属性失败:`, error)
- }
- },
- // 加载所有子设备属性
- async loadAllSubDeviceAttrs() {
- const subDevicesByModel = {}
- this.deviceList.forEach(device => {
- const subDevAttr = this.deviceAttrs[device.deviceCode]?.subDev
- if (subDevAttr) {
- try {
- const subDevices = JSON.parse(subDevAttr)
- subDevices.forEach(subDev => {
- if (!subDevicesByModel[subDev.modelCode]) {
- subDevicesByModel[subDev.modelCode] = []
- }
- subDevicesByModel[subDev.modelCode].push(subDev.deviceCode)
- this.subDeviceGatewayMap[subDev.deviceCode] = device.deviceCode
- })
- } catch (e) {
- console.error(`解析设备 ${device.deviceCode} 的subDev失败:`, e)
- }
- }
- })
- let totalPoints = 0
- for (const modelCode in subDevicesByModel) {
- const pointsCount = await this.loadSubDeviceAttrsBatch(modelCode)
- totalPoints += pointsCount
- }
- this.deviceStats.points = totalPoints
- this.organizeChannelsFromSubDevices()
- },
- // 批量加载子设备属性
- async loadSubDeviceAttrsBatch(modelCode) {
- try {
- const res = await getObjAttrBatch(2, modelCode)
- const batchData = res.data || {}
- let pointsCount = 0
- for (const deviceCode in batchData) {
- const attrData = batchData[deviceCode]
- this.subDeviceAttrs[deviceCode] = attrData
- pointsCount++
- }
- return pointsCount
- } catch (error) {
- console.error(`批量加载子设备模型 ${modelCode} 的属性失败:`, error)
- return 0
- }
- },
- // 将子设备组织成通道结构
- organizeChannelsFromSubDevices() {
- this.deviceList.forEach(device => {
- const deviceCode = device.deviceCode
- const subDevAttr = this.deviceAttrs[deviceCode]?.subDev
- if (!subDevAttr) {
- this.deviceChannels[deviceCode] = []
- return
- }
- try {
- const subDevices = JSON.parse(subDevAttr)
- const channelMap = {}
- subDevices.forEach(subDev => {
- const subDevCode = subDev.deviceCode
- const subDevAttrData = this.subDeviceAttrs?.[subDevCode]
- if (!subDevAttrData) return
- const interfaceAttr = subDevAttrData.Base?.find(attr => attr.attrKey === 'interface')
- const channelName = interfaceAttr?.attrValue || '未知通道'
- if (!channelMap[channelName]) {
- channelMap[channelName] = {
- name: this.formatChannelName(channelName),
- key: channelName,
- points: []
- }
- }
- const point = {
- key: subDevCode,
- name: subDevAttrData.Base?.find(attr => attr.attrKey === 'deviceName')?.attrValue || subDevCode,
- value: subDevAttrData.Measure?.find(attr => attr.attrKey === 'value')?.attrValue || '-',
- updateTime: subDevAttrData.Measure?.find(attr => attr.attrKey === 'value')?.updateTime || '-',
- unit: subDevAttrData.Measure?.find(attr => attr.attrKey === 'value')?.attrUnit || '',
- location: subDevAttrData.Base?.find(attr => attr.attrKey === 'location')?.attrValue || '-',
- desc: subDevAttrData.Base?.find(attr => attr.attrKey === 'desc')?.attrValue || '-'
- }
- channelMap[channelName].points.push(point)
- })
- const channels = Object.values(channelMap).sort((a, b) => {
- return a.key.localeCompare(b.key)
- })
- this.deviceChannels[deviceCode] = channels
- } catch (e) {
- console.error(`组织设备 ${deviceCode} 的通道数据失败:`, e)
- this.deviceChannels[deviceCode] = []
- }
- })
- },
- // 格式化通道名称
- formatChannelName(channelKey) {
- const match = channelKey.match(/(\d+)(D\d+)口/)
- if (match) {
- return `${match[2]}口通道`
- }
- return channelKey
- },
- // 获取设备属性
- getDeviceAttr(deviceCode, attrKey) {
- return this.deviceAttrs[deviceCode]?.[attrKey] || '-'
- },
- // 获取设备通道
- getDeviceChannels(deviceCode) {
- return this.deviceChannels[deviceCode] || []
- },
- // 单个加载设备属性(用于刷新)
- async loadDeviceAttrs(deviceCode) {
- try {
- const res = await getObjAttr(2, deviceCode)
- const attrData = res.data
- const baseAttrs = {}
- if (attrData.Base) {
- attrData.Base.forEach(attr => {
- baseAttrs[attr.attrKey] = attr.attrValue
- })
- }
- this.deviceAttrs[deviceCode] = baseAttrs
- const subDevAttr = baseAttrs.subDev
- if (subDevAttr) {
- const subDevices = JSON.parse(subDevAttr)
- const subDevicesByModel = {}
- subDevices.forEach(subDev => {
- if (!subDevicesByModel[subDev.modelCode]) {
- subDevicesByModel[subDev.modelCode] = []
- }
- subDevicesByModel[subDev.modelCode].push(subDev.deviceCode)
- })
- for (const modelCode in subDevicesByModel) {
- await this.loadSubDeviceAttrsBatch(modelCode)
- }
- this.organizeChannelsFromSubDevices()
- }
- } catch (error) {
- console.error(`加载设备 ${deviceCode} 属性失败:`, error)
- }
- },
- // 加载设备能力
- async handleAbilityLoad(visible, device) {
- if (visible && !this.deviceAbilities[device.deviceCode]) {
- try {
- if (!device.deviceModel) {
- this.$set(this.deviceAbilities, device.deviceCode, [])
- return
- }
- const res = await getModelByCode(device.deviceModel)
- const abilities = res.data?.abilityList?.filter(item => item.hiddenFlag === 1) || []
- this.$set(this.deviceAbilities, device.deviceCode, abilities)
- } catch (error) {
- this.$set(this.deviceAbilities, device.deviceCode, [])
- }
- }
- },
- // 处理设备操作命令
- async handleDeviceCommand(command) {
- const { device, ability, paramValue } = command
- if (paramValue !== undefined) {
- try {
- await callAbility({
- objCode: device.deviceCode,
- objType: 2,
- modelCode: device.deviceModel,
- abilityKey: ability.abilityKey,
- abilityParam: paramValue
- })
- this.$message.success(`${ability.abilityName}执行成功`)
- if (ability.abilityKey.toLowerCase().includes('meter') ||
- ability.abilityKey.toLowerCase().includes('reading')) {
- await this.loadDeviceAttrs(device.deviceCode)
- }
- } catch (error) {
- this.$message.error(`${ability.abilityName}执行失败:${error.message}`)
- }
- } else {
- const paramType = this.getParamType(ability.paramDefinition)
- if (paramType === 'Slider') {
- this.showSliderDialog(ability, device, 2)
- } else if (paramType === 'Input') {
- this.showInputDialog(ability, device, 2)
- } else {
- try {
- await callAbility({
- objCode: device.deviceCode,
- objType: 2,
- modelCode: device.deviceModel,
- abilityKey: ability.abilityKey,
- abilityParam: null
- })
- this.$message.success(`${ability.abilityName}执行成功`)
- if (ability.abilityKey.toLowerCase().includes('meter') ||
- ability.abilityKey.toLowerCase().includes('reading')) {
- await this.loadDeviceAttrs(device.deviceCode)
- }
- } catch (error) {
- this.$message.error(`${ability.abilityName}执行失败:${error.message}`)
- }
- }
- }
- },
- // 处理设备行点击
- handleDeviceClick(row) {
- this.$refs.deviceTable?.toggleRowExpansion(row)
- },
- // ========== 楼控设备相关方法 ==========
- // 处理楼控设备类型切换
- handleBaDeviceTypeChange() {
- this.loadBaDevices()
- },
- // 刷新楼控设备
- refreshBaDevices() {
- this.loadBaDevices()
- },
- // 加载楼控设备列表
- async loadBaDevices() {
- if (!this.selectedBaDeviceModel) return
- this.baDeviceLoading = true
- try {
- // 1. 先查询设备物模型,获取属性定义和能力列表
- const modelRes = await getModelByCode(this.selectedBaDeviceModel)
- const modelData = modelRes.data
- // 缓存当前模型的属性定义和能力列表
- this.$set(this.baDeviceAttrs, '_model_' + this.selectedBaDeviceModel, modelData)
- // 2. 查询指定模型的设备列表
- const res = await getByCondition({
- subsystemCode: this.systemCode,
- deviceModel: this.selectedBaDeviceModel
- })
- const deviceData = res.data || res.rows || []
- this.baDeviceList = deviceData.map(device => ({
- ...device,
- detailTab: 'state'
- }))
- // 3. 批量加载设备属性值
- if (this.baDeviceList.length > 0) {
- await this.loadBaDeviceAttrsBatch(this.selectedBaDeviceModel)
- }
- } catch (error) {
- this.$message.error('加载楼控设备失败:' + error.message)
- } finally {
- this.baDeviceLoading = false
- }
- },
- // 批量加载楼控设备属性
- async loadBaDeviceAttrsBatch(modelCode) {
- try {
- const res = await getObjAttrBatch(2, modelCode)
- const batchData = res.data || {}
- // 缓存所有设备的属性数据
- for (const deviceCode in batchData) {
- this.baDeviceAttrs[deviceCode] = batchData[deviceCode]
- }
- } catch (error) {
- console.error(`批量加载楼控设备属性失败:`, error)
- }
- },
- // 获取楼控设备的属性(按组)
- getBaDeviceAttrs(deviceCode, attrGroup) {
- // 获取设备所属的模型
- const device = this.baDeviceList.find(d => d.deviceCode === deviceCode)
- if (!device) return []
- // 获取模型定义
- const modelData = this.baDeviceAttrs['_model_' + device.deviceModel]
- if (!modelData || !modelData.attrList) return []
- // 筛选出当前分组的属性定义
- const modelAttrs = modelData.attrList.filter(attr => attr.attrGroup === attrGroup)
- // 获取设备的实际属性值
- const deviceAttrData = this.baDeviceAttrs[deviceCode]
- const deviceAttrs = deviceAttrData && deviceAttrData[attrGroup] ? deviceAttrData[attrGroup] : []
- // 将实际值映射到字典
- const attrValueMap = {}
- deviceAttrs.forEach(attr => {
- attrValueMap[attr.attrKey] = attr
- })
- // 合并模型定义和实际值
- const mergedAttrs = modelAttrs.map(modelAttr => {
- const actualAttr = attrValueMap[modelAttr.attrKey]
- if (actualAttr) {
- // 有实际值,返回实际值
- return actualAttr
- } else {
- // 没有实际值,返回模型定义的空属性
- return {
- objCode: deviceCode,
- attrGroup: modelAttr.attrGroup,
- attrKey: modelAttr.attrKey,
- attrName: modelAttr.attrName,
- attrValue: null,
- attrValueName: null,
- attrValueType: modelAttr.attrValueType,
- attrUnit: modelAttr.attrUnit,
- updateTime: null
- }
- }
- })
- return mergedAttrs
- },
- // 格式化楼控设备属性值
- formatBaAttrValue(attr) {
- if (!attr.attrValue && attr.attrValue !== 0) {
- return '-'
- }
- // 如果有枚举名称,优先显示
- if (attr.attrValueName) {
- return attr.attrValueName
- }
- // 数值类型,保留两位小数
- if (attr.attrValueType === 'Value') {
- const numValue = parseFloat(attr.attrValue)
- if (!isNaN(numValue)) {
- return numValue.toFixed(2)
- }
- }
- return attr.attrValue
- },
- // ========== 标签页切换 ==========
- handleTabClick(tab) {
- if (tab.name === 'callLogs' && this.callLogList.length === 0) {
- this.queryCallLogs()
- } else if (tab.name === 'eventLogs' && this.eventLogList.length === 0) {
- this.queryEventLogs()
- } else if (tab.name === 'baDevices' && this.baDeviceList.length === 0) {
- this.loadBaDevices()
- }
- },
- // ========== 日志查询相关 - 已优化 ==========
- // 获取要查询的模型代码列表
- getQueryModelCodes(selectedModel) {
- if (selectedModel === 'ALL' || !selectedModel) {
- // 返回所有模型代码(排除 'ALL' 选项)
- return this.logModelOptions
- .filter(item => item.modelCode !== 'ALL')
- .map(item => item.modelCode)
- } else {
- // 返回单个选中的模型
- return [selectedModel]
- }
- },
- // 查询调用日志
- async queryCallLogs() {
- this.logLoading = true
- try {
- // 根据选择的模型类型获取要查询的modelCodes
- const modelCodes = this.getQueryModelCodes(this.selectedLogModel)
- const params = {
- modelCodes: modelCodes,
- pageNum: this.callLogQuery.pageNum,
- pageSize: this.callLogQuery.pageSize
- }
- if (this.callLogQuery.dateRange && this.callLogQuery.dateRange.length === 2) {
- params.startRecTime = this.callLogQuery.dateRange[0]
- params.endRecTime = this.callLogQuery.dateRange[1]
- }
- if (this.callLogQuery.abilityKey) {
- params.abilityKey = this.callLogQuery.abilityKey
- }
- if (this.callLogQuery.callStatus !== '') {
- params.callStatus = this.callLogQuery.callStatus
- }
- const res = await listCallLog(params)
- this.callLogList = res.rows || []
- this.callLogTotal = res.total || 0
- } catch (error) {
- this.$message.error('查询调用日志失败')
- } finally {
- this.logLoading = false
- }
- },
- // 重置调用日志查询
- resetCallLogQuery() {
- this.selectedLogModel = 'ALL'
- this.initDateRange()
- this.callLogQuery.abilityKey = ''
- this.callLogQuery.callStatus = ''
- this.callLogQuery.pageNum = 1
- this.queryCallLogs()
- },
- // 查询事件日志
- async queryEventLogs() {
- this.eventLogLoading = true
- try {
- // 根据选择的模型类型获取要查询的modelCodes
- const modelCodes = this.getQueryModelCodes(this.selectedEventLogModel)
- const params = {
- modelCodes: modelCodes,
- pageNum: this.eventLogQuery.pageNum,
- pageSize: this.eventLogQuery.pageSize
- }
- if (this.eventLogQuery.dateRange && this.eventLogQuery.dateRange.length === 2) {
- params.startRecTime = this.eventLogQuery.dateRange[0]
- params.endRecTime = this.eventLogQuery.dateRange[1]
- }
- if (this.eventLogQuery.eventKey) {
- params.eventKey = this.eventLogQuery.eventKey
- }
- const res = await listEventLog(params)
- this.eventLogList = res.rows || []
- this.eventLogTotal = res.total || 0
- } catch (error) {
- this.$message.error('查询事件日志失败')
- } finally {
- this.eventLogLoading = false
- }
- },
- // 重置事件日志查询
- resetEventLogQuery() {
- this.selectedEventLogModel = 'ALL'
- this.initDateRange()
- this.eventLogQuery.eventKey = ''
- this.eventLogQuery.pageNum = 1
- this.queryEventLogs()
- },
- // 格式化调用状态
- formatCallStatus(status) {
- const statusMap = {
- 0: '成功',
- 1: '进行中',
- 2: '失败'
- }
- return statusMap[status] || '未知'
- },
- // 获取调用状态类型
- getCallStatusType(status) {
- const typeMap = {
- 0: 'success',
- 1: 'warning',
- 2: 'danger'
- }
- return typeMap[status] || 'info'
- },
- // 查看调用日志详情
- async handleCallLogDetail(row) {
- try {
- const res = await getCallLog(row.id)
- this.callLogDetailData = res.data
- this.callLogDetailDialog = true
- } catch (error) {
- this.$message.error('获取调用日志详情失败')
- }
- },
- // 查看事件日志详情
- async handleEventLogDetail(row) {
- try {
- const res = await getEventLog(row.id)
- this.eventLogDetailData = res.data
- this.eventLogDetailDialog = true
- } catch (error) {
- this.$message.error('获取事件日志详情失败')
- }
- }
- }
- }
- </script>
- <style lang="scss" scoped>
- .app-container {
- padding: 20px;
- background: #f5f7fa;
- min-height: calc(100vh - 84px);
- }
- .system-info-card {
- margin-bottom: 20px;
- .card-header {
- display: flex;
- justify-content: space-between;
- align-items: center;
- .title {
- font-size: 18px;
- font-weight: bold;
- i {
- margin-right: 8px;
- color: #409EFF;
- }
- }
- }
- .info-group {
- .group-title {
- font-size: 14px;
- font-weight: bold;
- color: #606266;
- margin-bottom: 12px;
- padding-bottom: 6px;
- border-bottom: 1px solid #EBEEF5;
- }
- }
- .info-item {
- margin-bottom: 12px;
- font-size: 14px;
- label {
- color: #909399;
- margin-right: 8px;
- display: inline-block;
- min-width: 80px;
- }
- .code {
- font-family: monospace;
- color: #409EFF;
- background: #f0f9ff;
- padding: 2px 6px;
- border-radius: 3px;
- }
- .stat-number {
- color: #409EFF;
- font-weight: bold;
- font-size: 16px;
- }
- }
- }
- .detail-card {
- .tab-content {
- min-height: 400px;
- }
- .attr-section {
- margin-bottom: 30px;
- .section-title {
- margin: 20px 0 15px 0;
- color: #303133;
- i {
- margin-right: 8px;
- color: #409EFF;
- }
- }
- .url-text {
- color: #409EFF;
- font-family: monospace;
- }
- }
- .device-stats {
- margin-bottom: 20px;
- .stat-item {
- text-align: center;
- padding: 20px;
- background: #fff;
- border: 1px solid #EBEEF5;
- border-radius: 4px;
- .stat-value {
- font-size: 28px;
- font-weight: bold;
- color: #303133;
- margin-bottom: 8px;
- }
- .stat-label {
- font-size: 14px;
- color: #909399;
- }
- &.online .stat-value {
- color: #67C23A;
- }
- &.offline .stat-value {
- color: #F56C6C;
- }
- }
- }
- .device-detail {
- padding: 20px;
- background: #f5f7fa;
- h5 {
- margin: 15px 0 10px 0;
- color: #303133;
- }
- }
- .ba-device-header {
- display: flex;
- align-items: center;
- margin-bottom: 15px;
- }
- .ba-device-detail {
- padding: 20px;
- background: #f5f7fa;
- }
- .log-filter {
- margin-bottom: 20px;
- padding: 15px;
- background: #f5f7fa;
- border-radius: 4px;
- }
- }
- .execute-result {
- .result-data {
- margin-top: 20px;
- pre {
- background: #f5f7fa;
- padding: 15px;
- border-radius: 4px;
- overflow: auto;
- max-height: 400px;
- font-family: monospace;
- font-size: 12px;
- }
- }
- }
- .dialog-footer {
- text-align: right;
- }
- ::v-deep .el-dropdown-menu {
- .el-submenu__title {
- padding-right: 40px !important;
- &:hover {
- background-color: #f5f7fa;
- }
- }
- .el-submenu__icon-arrow {
- position: absolute;
- right: 20px;
- }
- }
- </style>
|