|
|
|
<template>
|
|
|
|
<div class="flex-col gap-16 wh-full">
|
|
|
|
<el-button type="primary" @click="onBack" class="w-150px">
|
|
|
|
<i class="i-line-md:arrow-left"></i>返回站点数据
|
|
|
|
</el-button>
|
|
|
|
<EdfsWrap title="设备列表" class="flex-1" useScrollBar>
|
|
|
|
<template #title-right>
|
|
|
|
<template v-if="env.VITE_APP_ENV == 'local'">
|
|
|
|
<template v-if="isBatchTransfer || isBatchUpgrade">
|
|
|
|
<el-button type="primary" @click="onBatchSave"> 确定{{ batchText }} </el-button>
|
|
|
|
<el-button type="info" @click="onBatchCancel"> 取消 </el-button>
|
|
|
|
</template>
|
|
|
|
<template v-else>
|
|
|
|
<el-button type="primary" @click="onBatchTransfer"> <i class="i-mdi:database-arrow-right-outline mr-1" />
|
|
|
|
批量迁移 </el-button>
|
|
|
|
<el-button v-if="isonLineTransfer" type="primary" @click="onBatchUpgrade"> <i
|
|
|
|
class="i-codicon:chip mr-1" />批量升级
|
|
|
|
</el-button>
|
|
|
|
</template>
|
|
|
|
</template>
|
|
|
|
|
|
|
|
</template>
|
|
|
|
<div class="device-list-wrap">
|
|
|
|
<el-checkbox-group v-model="checkDeviceList">
|
|
|
|
<div class="device-item" v-for="item in devices">
|
|
|
|
<div class="device-item-header">
|
|
|
|
<div class="flex items-center">
|
|
|
|
<el-checkbox :value="item.sn" v-if="(isBatchTransfer || isBatchUpgrade) && item.status !== '离线'">
|
|
|
|
<div>设备ID: {{ item.sn }}</div>
|
|
|
|
</el-checkbox>
|
|
|
|
<div v-else class="h-32 leading-32px">
|
|
|
|
<div>设备ID: {{ item.sn }}</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div class="flex items-center gap-col-2" v-if="!(isBatchTransfer || isBatchUpgrade)">
|
|
|
|
<template v-if="env.VITE_APP_ENV == 'local'">
|
|
|
|
<el-tooltip content="数据迁移" v-if="isonLineTransfer ? item.status === '在线' : true">
|
|
|
|
<i class="i-mdi:database-arrow-right-outline :hover:color-[#8ACE6A] color-[#4B9E5F] cursor-pointer text-20px"
|
|
|
|
@click="onTransfer(item)"></i>
|
|
|
|
</el-tooltip>
|
|
|
|
<el-tooltip content="固件升级" v-if="isonLineTransfer && item.status === '在线'">
|
|
|
|
<i class="i-codicon:chip :hover:color-[#8ACE6A] color-[#4B9E5F] cursor-pointer text-20px"
|
|
|
|
@click="onFirmwareUpload([item])"></i>
|
|
|
|
</el-tooltip>
|
|
|
|
</template>
|
|
|
|
|
|
|
|
<el-tooltip content="详情">
|
|
|
|
<div
|
|
|
|
class="i-material-symbols:info-outline :hover:color-[#8ACE6A] color-[#4B9E5F] cursor-pointer text-20px"
|
|
|
|
@click="onDeviceDetails(item)"></div>
|
|
|
|
</el-tooltip>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div class="device-item-body relative">
|
|
|
|
<template v-if="isonLineTransfer">
|
|
|
|
<template v-for="key in Object.keys(onlineDeviceMap)">
|
|
|
|
<div class="info-item" v-if="isonLineTransfer && key === 'status'">
|
|
|
|
<div>{{ onlineDeviceMap.status }}:</div>
|
|
|
|
<el-tag :type="item.status === '在线' ? 'success' : 'danger'">
|
|
|
|
{{ item.status }}
|
|
|
|
</el-tag>
|
|
|
|
</div>
|
|
|
|
<div class="info-item" v-else>
|
|
|
|
<div>{{ onlineDeviceMap[key as keyof typeof onlineDeviceMap] }}:</div>
|
|
|
|
<div>{{ item[key] }}</div>
|
|
|
|
</div>
|
|
|
|
</template>
|
|
|
|
</template>
|
|
|
|
<template v-else>
|
|
|
|
<template v-for="key in Object.keys(offlineDeviceMap)">
|
|
|
|
<div class="info-item" v-if="key === 'create_time'">
|
|
|
|
<div>
|
|
|
|
{{ offlineDeviceMap[key as keyof typeof offlineDeviceMap] }}:
|
|
|
|
</div>
|
|
|
|
<div>{{ dayjs(item[key]).format('YYYY-MM-DD HH:mm:ss') }}</div>
|
|
|
|
</div>
|
|
|
|
<div class="info-item" v-else>
|
|
|
|
<div>
|
|
|
|
{{ offlineDeviceMap[key as keyof typeof offlineDeviceMap] }}:
|
|
|
|
</div>
|
|
|
|
<div>{{ item[key] }}</div>
|
|
|
|
</div>
|
|
|
|
</template>
|
|
|
|
</template>
|
|
|
|
<div class="absolute l-0 t-0 w-full h-full z-10 bg-#FFF-90"
|
|
|
|
v-if="['updating', 'pending', 'rejected', 'timeout'].includes(item.upFirmware)">
|
|
|
|
<div class="i-material-symbols-light:close absolute-rt text-base text-gray-950 cursor-pointer"
|
|
|
|
v-if="['timeout', 'rejected'].includes(item.upFirmware)" @click="upFirmwareSucceed(item.sn)"></div>
|
|
|
|
<template v-if="item.upFirmware === 'updating'">
|
|
|
|
<div class="device-item-body">
|
|
|
|
<div class="info-item">
|
|
|
|
<div>当前步骤:</div>
|
|
|
|
<div>{{ item.upFirmwareStatus?.step ?? '--' }}</div>
|
|
|
|
</div>
|
|
|
|
<div class="info-item">
|
|
|
|
<div>当前进度:</div>
|
|
|
|
<el-progress class="flex-1" :stroke-width="12" :show-text="true"
|
|
|
|
:percentage="item.upFirmwareStatus?.progress || 0" />
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</template>
|
|
|
|
<template v-else-if="item.upFirmware === 'pending'">
|
|
|
|
<div class="w-full h-full flex items-center justify-center">
|
|
|
|
<div class="font-400 text-base text-[#4B9E5F]">等待升级中...</div>
|
|
|
|
</div>
|
|
|
|
</template>
|
|
|
|
<template v-if="item.upFirmware === 'rejected'">
|
|
|
|
<div class="device-item-body">
|
|
|
|
|
|
|
|
<div class="info-item">
|
|
|
|
<div>错误发生步骤:</div>
|
|
|
|
<div class="text-red">
|
|
|
|
{{
|
|
|
|
upgradeProgressStatusMap.find(
|
|
|
|
r => r.status == item.upFirmwareStatus?.step
|
|
|
|
)?.text ?? '--'
|
|
|
|
}}失败
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div class="info-item">
|
|
|
|
<div>错误信息:</div>
|
|
|
|
<div>{{ item.upFirmwareStatus?.errMsg ?? '--' }}</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</template>
|
|
|
|
<template v-if="item.upFirmware === 'timeout'">
|
|
|
|
<div class="w-full h-full flex items-center justify-center">
|
|
|
|
<div class="font-400 text-base text-[#E6A23C]">升级超时</div>
|
|
|
|
</div>
|
|
|
|
</template>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</el-checkbox-group>
|
|
|
|
</div>
|
|
|
|
</EdfsWrap>
|
|
|
|
<TransferMask v-model="isShowTransferMask" :transferLoading="transferLoading" @close="closeTransferMask">
|
|
|
|
<template v-if="curTransfer === 'export'">
|
|
|
|
<div class="flex-col gap-col-10 h-full w-56% justify-center">
|
|
|
|
<div class="flex items-center gap-col-1">
|
|
|
|
<div class="flex-1 flex items-center">
|
|
|
|
<el-progress :percentage="100" class="flex-1" :stroke-width="18" :text-inside="true"
|
|
|
|
:striped="transferStatus === 'progress'" :striped-flow="transferStatus === 'progress'" :duration="20"
|
|
|
|
:status="['progress', 'success', undefined].includes(transferStatus)
|
|
|
|
? 'success'
|
|
|
|
: 'exception'
|
|
|
|
">
|
|
|
|
{{
|
|
|
|
transferStatusMap[transferStatus as keyof typeof transferStatusMap] ?? ''
|
|
|
|
}}
|
|
|
|
|
|
|
|
</el-progress>
|
|
|
|
</div>
|
|
|
|
<el-button v-if="transferStatus === 'progress'" type="primary" @click="onStopTransfer">停止迁移</el-button>
|
|
|
|
</div>
|
|
|
|
<div class="transfer-log-wrap h-490 flex-col">
|
|
|
|
<div class="text-16px font-500">迁移日志</div>
|
|
|
|
<el-scrollbar class="flex-1">
|
|
|
|
<div v-for="i in curTransferLog" :class="i.status === 'failed' ? 'text-red-500' : ''"
|
|
|
|
class="text-gray-600">
|
|
|
|
{{ i.msg }}
|
|
|
|
</div>
|
|
|
|
</el-scrollbar>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</template>
|
|
|
|
<template v-else-if="curTransfer === 'import'">
|
|
|
|
<div class="flex-col gap-col-10 h-full w-56% justify-center">
|
|
|
|
<div class="flex items-center gap-col-1">
|
|
|
|
<div class="flex-1 items-center">
|
|
|
|
<el-progress :percentage="100" :status="['progress', 'success', undefined].includes(onOffDeviceTransferStatus)
|
|
|
|
? 'success'
|
|
|
|
: 'exception'
|
|
|
|
" class="flex-1" :stroke-width="18" :text-inside="true"
|
|
|
|
:striped="onOffDeviceTransferStatus === 'progress' || !onOffDeviceTransferStatus"
|
|
|
|
:striped-flow="onOffDeviceTransferStatus === 'progress' || !onOffDeviceTransferStatus" :duration="20">
|
|
|
|
|
|
|
|
<div class="text-16px font-500"> {{ !!onOffDeviceTransferStatus ? onOffDeviceTransferStatus ===
|
|
|
|
'progress'
|
|
|
|
? '数据导入中' : '数据导入完成' : '数据导入中' }}</div>
|
|
|
|
|
|
|
|
</el-progress>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div class="h-490 border-radius-8px bg-[#F9FAFB] p-10 flex-col">
|
|
|
|
<div class="text-16px font-500">迁移日志</div>
|
|
|
|
<el-scrollbar class="flex-1">
|
|
|
|
<div v-for="i in siteTransferLogList" :class="i.status === 'error' ? 'text-red-500' : ''"
|
|
|
|
class="text-gray-600">
|
|
|
|
【{{ i.device.sn }}】:{{ i.msg }}
|
|
|
|
</div>
|
|
|
|
</el-scrollbar>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</template>
|
|
|
|
</TransferMask>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<OnLineTransferDlg ref="onLineTransferDlgRef" @on-save="onLineDeviceTransfer" :is-batch-transfer="isBatchTransfer" />
|
|
|
|
<OffTransferDlg ref="offTransferDlg" :isBatchTransfer="false" :siteInfo="siteInfo" @on-save="onOffDeviceTransfer" />
|
|
|
|
<DeviceDrawer v-model="isShowDetails" ref="deviceDrawerRef" :siteInfo="siteInfo" :is-transfer="isonLineTransfer" />
|
|
|
|
</template>
|
|
|
|
|
|
|
|
<script setup lang="ts">
|
|
|
|
import dayjs from 'dayjs'
|
|
|
|
import TransferMask from './components/transferMask.vue'
|
|
|
|
import OnLineTransferDlg from './components/onLineTransferDlg.vue'
|
|
|
|
import ZMQWorker from '@/composables/useZMQJsonWorker'
|
|
|
|
import OffTransferDlg from './components/offTransferDlg.vue'
|
|
|
|
|
|
|
|
import {
|
|
|
|
getPubInitData,
|
|
|
|
ZmqMsgResultType,
|
|
|
|
type PublishMsg,
|
|
|
|
type PubMsgData,
|
|
|
|
type TimeoutMsg,
|
|
|
|
} from '@/utils/zmq'
|
|
|
|
import { useTransferDataStore } from '@/stores/transferData'
|
|
|
|
import { storeToRefs } from 'pinia'
|
|
|
|
import type {
|
|
|
|
IOfflineDevice,
|
|
|
|
IOnlineDevice,
|
|
|
|
IUpFirmwareStatus,
|
|
|
|
UpFirmwarsDevice,
|
|
|
|
} from './type'
|
|
|
|
import { useMessage } from '@/composables/useMessage'
|
|
|
|
import { getDeviceList, type ISite } from '@/api/module/transfer'
|
|
|
|
import DeviceDrawer from './components/deviceDrawer.vue'
|
|
|
|
|
|
|
|
import {
|
|
|
|
getFirmwareUpTopic,
|
|
|
|
getTransferTopic,
|
|
|
|
postFirmwareUpTopic,
|
|
|
|
postTransferTopic,
|
|
|
|
upgradeProgressStatusMap,
|
|
|
|
} from './utils'
|
|
|
|
import { getFirmwarePath } from '@/api/module/firmware'
|
|
|
|
const env = import.meta.env
|
|
|
|
const onLineTransferDlgRef = ref<typeof OnLineTransferDlg>()
|
|
|
|
const router = useRouter()
|
|
|
|
const route = useRoute()
|
|
|
|
const siteInfo = ref<ISite>(
|
|
|
|
route.query.site ? JSON.parse(route.query.site as string) : null
|
|
|
|
)
|
|
|
|
const type = ref<'export' | 'details'>(route.query.type as 'export' | 'details')
|
|
|
|
|
|
|
|
const isonLineTransfer = computed(() => type.value === 'export')
|
|
|
|
|
|
|
|
const isShowTransferMask = ref(false)
|
|
|
|
|
|
|
|
const message = useMessage()
|
|
|
|
|
|
|
|
const worker = ZMQWorker.getInstance()
|
|
|
|
|
|
|
|
const transferDataStore = useTransferDataStore()
|
|
|
|
const { upFirmwarePending, upFirmwareReset, upFirmwareStatus, upFirmwareSucceed, upFirmwareStatusReject, upFirmwareTimeout } =
|
|
|
|
transferDataStore
|
|
|
|
const { devicesMap } = storeToRefs(transferDataStore)
|
|
|
|
|
|
|
|
const transferStatusMap = {
|
|
|
|
progress: '迁移中',
|
|
|
|
success: '迁移成功',
|
|
|
|
failed: '迁移失败',
|
|
|
|
timeout: '迁移超时',
|
|
|
|
}
|
|
|
|
|
|
|
|
const exportPubDeviceMap = new Map<string, { device: IOnlineDevice; action: 'export' }>()
|
|
|
|
|
|
|
|
const curTransferLog = ref<
|
|
|
|
{ msg: string; host: string; status: 'success' | 'padding' | 'failed' }[]
|
|
|
|
>([])
|
|
|
|
|
|
|
|
const transferStatus = ref<'progress' | 'success' | 'failed' | 'timeout' | undefined>()
|
|
|
|
|
|
|
|
const devices = computed(() => {
|
|
|
|
return isonLineTransfer.value ? Array.from(devicesMap.value.values()) : offLineDeviceList.value
|
|
|
|
}) as Ref<any[]>
|
|
|
|
|
|
|
|
const transferLoading = ref(false)
|
|
|
|
|
|
|
|
const curTransfer = ref<'import' | 'export'>()
|
|
|
|
function openTransferMask(status: 'import' | 'export') {
|
|
|
|
isShowTransferMask.value = true
|
|
|
|
curTransfer.value = status
|
|
|
|
transferLoading.value = true
|
|
|
|
}
|
|
|
|
|
|
|
|
function closeTransferMask() {
|
|
|
|
curTransfer.value = undefined
|
|
|
|
isShowTransferMask.value = false
|
|
|
|
transferLoading.value = false
|
|
|
|
curTransferLog.value = []
|
|
|
|
transferStatus.value = undefined
|
|
|
|
siteTransferLogList.value = []
|
|
|
|
pubIdWithOffDevice.clear()
|
|
|
|
exportPubDeviceMap.clear()
|
|
|
|
}
|
|
|
|
|
|
|
|
function onLineDeviceTransfer(msg: PublishMsg<'export'>, device: IOnlineDevice) {
|
|
|
|
curTransferLog.value = []
|
|
|
|
openTransferMask('export')
|
|
|
|
worker.publish(postTransferTopic, msg, true, zmqTimeoutCb)
|
|
|
|
exportPubDeviceMap.set(msg.id, { device, action: 'export' })
|
|
|
|
worker.subscribe(getTransferTopic, zmqExportCb, msg.id)
|
|
|
|
|
|
|
|
if (isBatchTransfer.value) {
|
|
|
|
onBatchCancel()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const statusMap = {
|
|
|
|
200: 'success',
|
|
|
|
1002: 'padding',
|
|
|
|
1003: 'failed',
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function zmqExportCb(msg: PubMsgData) {
|
|
|
|
if (!isonLineTransfer.value) return
|
|
|
|
const { feedback, result, id } = msg
|
|
|
|
transferLoading.value = false
|
|
|
|
if (feedback && feedback[0]) {
|
|
|
|
const status = feedback[1]
|
|
|
|
? (statusMap[feedback[1] as keyof typeof statusMap] as
|
|
|
|
| 'success'
|
|
|
|
| 'padding'
|
|
|
|
| 'failed')
|
|
|
|
: 'failed'
|
|
|
|
curTransferLog.value.push({
|
|
|
|
msg: `主机【${feedback[0]}】: ${feedback[2]}`,
|
|
|
|
host: feedback[0],
|
|
|
|
status,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
transferStatus.value = 'progress'
|
|
|
|
if (result !== 'progress') {
|
|
|
|
|
|
|
|
const curMsgInfo = exportPubDeviceMap.get(id)!
|
|
|
|
if (!curMsgInfo) return
|
|
|
|
const { device, action } = curMsgInfo
|
|
|
|
if (device && action === 'export') {
|
|
|
|
if (result === 'success') {
|
|
|
|
const res = setTransferStatus()
|
|
|
|
if (res === 0) {
|
|
|
|
message.success(`迁移成功`)
|
|
|
|
transferStatus.value = 'success'
|
|
|
|
} else {
|
|
|
|
message.error(`迁移失败,请检查迁移日志`)
|
|
|
|
transferStatus.value = 'failed'
|
|
|
|
}
|
|
|
|
} else if (['failed', 'failure'].includes(result)) {
|
|
|
|
|
|
|
|
message.error(`迁移失败`)
|
|
|
|
transferStatus.value = 'failed'
|
|
|
|
}
|
|
|
|
exportPubDeviceMap.delete(msg.id)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function setTransferStatus() {
|
|
|
|
const failed = curTransferLog.value.filter(i => i.status === 'failed')
|
|
|
|
for (const f of failed) {
|
|
|
|
curTransferLog.value.forEach(j => {
|
|
|
|
if (f.host === j.host) {
|
|
|
|
j.status = 'failed'
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
return failed.length
|
|
|
|
}
|
|
|
|
|
|
|
|
function onStopTransfer() {
|
|
|
|
message.confirm('是否确认停止迁移?').then(() => {
|
|
|
|
const msg = getPubInitData<'cancel'>('cancel', [], 'no')
|
|
|
|
worker.publish(postTransferTopic, msg)
|
|
|
|
message.success('迁移已取消')
|
|
|
|
exportPubDeviceMap.clear()
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function zmqTimeoutCb(msg: TimeoutMsg) {
|
|
|
|
const { device, action } = exportPubDeviceMap.get(msg.timeoutId)!
|
|
|
|
if (device && action === 'export') {
|
|
|
|
message.error(`迁移超时,请重新稍后尝试`)
|
|
|
|
exportPubDeviceMap.delete(msg.timeoutId)
|
|
|
|
closeTransferMask()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const onlineDeviceMap: Record<
|
|
|
|
keyof Omit<
|
|
|
|
IOnlineDevice,
|
|
|
|
'lastUpdated' | 'sn' | 'isChecked' | 'upFirmware' | 'upFirmwareStatus'
|
|
|
|
>,
|
|
|
|
string
|
|
|
|
> = {
|
|
|
|
status: '状态',
|
|
|
|
site_id: '站点名称',
|
|
|
|
clientIp: '客户端IP',
|
|
|
|
footprint: '数据占用空间',
|
|
|
|
}
|
|
|
|
const offlineDeviceMap: Record<
|
|
|
|
keyof Pick<IOfflineDevice, 'site_id' | 'db' | 'create_time'>,
|
|
|
|
string
|
|
|
|
> = {
|
|
|
|
site_id: '站点名称',
|
|
|
|
db: '数据库',
|
|
|
|
create_time: '创建时间',
|
|
|
|
}
|
|
|
|
|
|
|
|
const checkDeviceList = ref<string[]>([])
|
|
|
|
|
|
|
|
const isBatchTransfer = ref(false)
|
|
|
|
function onBatchTransfer() {
|
|
|
|
checkDeviceList.value = []
|
|
|
|
isBatchTransfer.value = true
|
|
|
|
}
|
|
|
|
|
|
|
|
function onBatchTransferSave(checkDeviceList: IOnlineDevice[] | IOfflineDevice[]) {
|
|
|
|
if (isonLineTransfer.value) {
|
|
|
|
const checkList = checkDeviceList as IOnlineDevice[]
|
|
|
|
const clientIpList = checkList.map(item => item?.clientIp).join(',')
|
|
|
|
const pathList = checkList
|
|
|
|
.map(item => `${item?.site_id}/${item?.sn}`)
|
|
|
|
.filter(Boolean)
|
|
|
|
.join(',')
|
|
|
|
|
|
|
|
onLineTransferDlgRef.value?.open(checkList[0], clientIpList, pathList)
|
|
|
|
} else {
|
|
|
|
offTransferDlg.value?.open(checkDeviceList, true)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const isBatchUpgrade = ref(false)
|
|
|
|
function onBatchUpgrade() {
|
|
|
|
checkDeviceList.value = []
|
|
|
|
isBatchUpgrade.value = true
|
|
|
|
}
|
|
|
|
|
|
|
|
const batchText = computed(() => {
|
|
|
|
return isBatchTransfer.value ? '迁移' : '升级'
|
|
|
|
})
|
|
|
|
|
|
|
|
function onBatchSave() {
|
|
|
|
if (!checkDeviceList.value.length) {
|
|
|
|
message.error(`请选择要${batchText.value}的设备`)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
const checkList = isonLineTransfer.value
|
|
|
|
? getOnlineDeviceList()
|
|
|
|
: getOfflineDeviceList()
|
|
|
|
|
|
|
|
if (isBatchTransfer.value) {
|
|
|
|
onBatchTransferSave(checkList)
|
|
|
|
}
|
|
|
|
|
|
|
|
if (isBatchUpgrade.value && isonLineTransfer.value) {
|
|
|
|
onFirmwareUpload(checkList as IOnlineDevice[])
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function getOnlineDeviceList(): IOnlineDevice[] {
|
|
|
|
return checkDeviceList.value
|
|
|
|
.map(sn => {
|
|
|
|
return devicesMap.value.get(sn) ?? undefined
|
|
|
|
})
|
|
|
|
.filter(Boolean) as IOnlineDevice[]
|
|
|
|
}
|
|
|
|
|
|
|
|
function getOfflineDeviceList(): IOfflineDevice[] {
|
|
|
|
return offLineDeviceList.value.filter(item =>
|
|
|
|
checkDeviceList.value.includes(item.sn)
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
function onBatchCancel() {
|
|
|
|
isBatchTransfer.value = false
|
|
|
|
isBatchUpgrade.value = false
|
|
|
|
checkDeviceList.value = []
|
|
|
|
}
|
|
|
|
function onTransfer(item: IOnlineDevice) {
|
|
|
|
if (isonLineTransfer.value) {
|
|
|
|
onLineTransferDlgRef.value?.open(item)
|
|
|
|
} else {
|
|
|
|
offTransferDlg.value?.open([item])
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function onBack() {
|
|
|
|
router.push('/station')
|
|
|
|
}
|
|
|
|
|
|
|
|
// 监听页面刷新
|
|
|
|
window.onbeforeunload = function () {
|
|
|
|
stop()
|
|
|
|
}
|
|
|
|
|
|
|
|
onBeforeRouteLeave(async (to, from, next) => {
|
|
|
|
if (transferStatus.value === 'progress') {
|
|
|
|
try {
|
|
|
|
await message.confirm('当前迁移尚未完成,是否确认离开?')
|
|
|
|
window.location.href = to.fullPath
|
|
|
|
} catch (error) {
|
|
|
|
next(false)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
next()
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
const offLineDeviceList = ref<IOfflineDevice[]>([])
|
|
|
|
|
|
|
|
async function loadDeviceList() {
|
|
|
|
const res = await getDeviceList(siteInfo.value.name)
|
|
|
|
if (res.code === 200 || res.code === 0) {
|
|
|
|
offLineDeviceList.value = res.data
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const firmwarePath = ref('')
|
|
|
|
|
|
|
|
onMounted(async () => {
|
|
|
|
if (!isonLineTransfer.value) {
|
|
|
|
loadDeviceList()
|
|
|
|
} else {
|
|
|
|
const res = await getFirmwarePath()
|
|
|
|
if (res.code === 200 || res.code === 0) {
|
|
|
|
firmwarePath.value = res.data.path
|
|
|
|
}
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
const isShowDetails = ref(false)
|
|
|
|
const deviceDrawerRef = ref<typeof DeviceDrawer>()
|
|
|
|
function onDeviceDetails(item: IOfflineDevice) {
|
|
|
|
deviceDrawerRef.value?.openFullScreen()
|
|
|
|
deviceDrawerRef.value?.open(item)
|
|
|
|
}
|
|
|
|
|
|
|
|
const isCanFirmwareUpload = computed(() => !!firmwarePath.value && isonLineTransfer.value)
|
|
|
|
|
|
|
|
const upgradeSnList = ref<string[]>([])
|
|
|
|
const upgradePubDeviceMap = new Map<string, UpFirmwarsDevice>()
|
|
|
|
function onFirmwareUpload(devices: IOnlineDevice[]) {
|
|
|
|
upgradeSnList.value = []
|
|
|
|
if (!isCanFirmwareUpload.value) {
|
|
|
|
message.error('升级失败,请检查固件路径')
|
|
|
|
onBatchCancel()
|
|
|
|
return
|
|
|
|
}
|
|
|
|
let deviceSn = devices[0].sn
|
|
|
|
|
|
|
|
if (isBatchUpgrade.value) {
|
|
|
|
deviceSn = devices.map(item => item.sn).join(',')
|
|
|
|
}
|
|
|
|
const msg = getPubInitData<'upgrade'>('upgrade', [deviceSn, firmwarePath.value])
|
|
|
|
for (const device of devices) {
|
|
|
|
upgradePubDeviceMap.set(msg.id, {
|
|
|
|
device: device,
|
|
|
|
action: 'upgrade',
|
|
|
|
})
|
|
|
|
}
|
|
|
|
worker.publish(postFirmwareUpTopic, msg, true, firmwareUpTimeoutCb, true)
|
|
|
|
worker.subscribe(getFirmwareUpTopic, zmqUpgradeCb, msg.id)
|
|
|
|
upgradeSnList.value = deviceSn.split(',')
|
|
|
|
|
|
|
|
upFirmwarePending(upgradeSnList.value)
|
|
|
|
onBatchCancel()
|
|
|
|
}
|
|
|
|
function firmwareUpTimeoutCb(msg: TimeoutMsg) {
|
|
|
|
const { device, action } = upgradePubDeviceMap.get(msg.timeoutId)!
|
|
|
|
if (device && action === 'upgrade') {
|
|
|
|
const timeoutUpgradeSnList = upgradeSnList.value
|
|
|
|
if (timeoutUpgradeSnList.length === 0) {
|
|
|
|
upFirmwareReset(upgradeSnList.value)
|
|
|
|
upgradePubDeviceMap.delete(msg.timeoutId)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
for (const deviec of timeoutUpgradeSnList) {
|
|
|
|
upFirmwareTimeout(deviec)
|
|
|
|
}
|
|
|
|
upgradePubDeviceMap.delete(msg.timeoutId)
|
|
|
|
message.warning(`固件升级超时,请稍后重试`)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function zmqUpgradeCb(msg: PubMsgData) {
|
|
|
|
if (!isonLineTransfer.value) return
|
|
|
|
const status = msg.result
|
|
|
|
const deviceSn = msg.feedback[0]
|
|
|
|
const progressStatus = msg.feedback[1] as number
|
|
|
|
const progress = msg.feedback[2] || undefined
|
|
|
|
const curentDevice = upgradePubDeviceMap.get(msg.id)
|
|
|
|
if (curentDevice && curentDevice.action === 'upgrade') {
|
|
|
|
const { device } = curentDevice
|
|
|
|
if (device) {
|
|
|
|
if (status === 'progress') {
|
|
|
|
upFirmwareStatus(deviceSn, msg.feedback)
|
|
|
|
if (progressStatus === 4 && progress === 100) {
|
|
|
|
upFirmwareSucceed(deviceSn)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (status === 'success' || status === 'error') {
|
|
|
|
upFirmwareStatus(deviceSn, msg.feedback)
|
|
|
|
if (status === 'error') {
|
|
|
|
upFirmwareStatusReject(deviceSn, msg.feedback)
|
|
|
|
}
|
|
|
|
upgradeSnList.value = upgradeSnList.value.filter(item => item !== deviceSn)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// ================离线数据导入=========
|
|
|
|
|
|
|
|
const offTransferDlg = ref<typeof OffTransferDlg>()
|
|
|
|
|
|
|
|
const pubIdWithOffDevice = new Map<string, { offDevice: IOfflineDevice; action: 'import' }>()
|
|
|
|
const importQueue = ref<{ msg: PublishMsg<'import'>; offDevice: IOfflineDevice }[]>([])
|
|
|
|
const isImporting = ref(false)
|
|
|
|
|
|
|
|
async function processNextImport() {
|
|
|
|
if (importQueue.value.length === 0 || isImporting.value) {
|
|
|
|
onOffDeviceTransferStatus.value = 'success'
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
isImporting.value = true
|
|
|
|
const { msg, offDevice } = importQueue.value[0]
|
|
|
|
|
|
|
|
pubIdWithOffDevice.set(msg.id, { offDevice, action: 'import' })
|
|
|
|
worker.publish(postTransferTopic, msg, true, zmqImportTimeoutCb)
|
|
|
|
worker.subscribe(getTransferTopic, zmqImportCb, msg.id)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
function onOffDeviceTransfer(msg: PublishMsg<'import'>[], offDevice: IOfflineDevice[]) {
|
|
|
|
msg.forEach((m, index) => {
|
|
|
|
importQueue.value.push({ msg: m, offDevice: offDevice[index] })
|
|
|
|
})
|
|
|
|
|
|
|
|
processNextImport()
|
|
|
|
onBatchCancel()
|
|
|
|
openTransferMask('import')
|
|
|
|
}
|
|
|
|
|
|
|
|
const siteTransferLogList = ref<Array<{
|
|
|
|
msg: string
|
|
|
|
device: IOfflineDevice
|
|
|
|
status: 'success' | 'timeout' | 'error'
|
|
|
|
}>>([])
|
|
|
|
|
|
|
|
const onOffDeviceTransferStatus = ref<'progress' | 'success' | undefined>()
|
|
|
|
function zmqImportCb(msg: PubMsgData) {
|
|
|
|
const { id, feedback, result, code } = msg
|
|
|
|
transferLoading.value = false
|
|
|
|
const { offDevice, action } = pubIdWithOffDevice.get(id)!
|
|
|
|
if (action !== 'import' || !offDevice) return
|
|
|
|
if (code === ZmqMsgResultType.PROGRESS) {
|
|
|
|
onOffDeviceTransferStatus.value = 'progress'
|
|
|
|
const log: string = Array.isArray(feedback) ? feedback[0] || '' : ''
|
|
|
|
siteTransferLogList.value.push({
|
|
|
|
msg: log,
|
|
|
|
device: offDevice,
|
|
|
|
status: 'success'
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
if (code !== ZmqMsgResultType.PROGRESS) {
|
|
|
|
importQueue.value.shift()
|
|
|
|
isImporting.value = false
|
|
|
|
if (code === ZmqMsgResultType.ERROR) {
|
|
|
|
siteTransferLogList.value.push({
|
|
|
|
msg: `数据导入失败,请稍后重试消息结果:${result}`,
|
|
|
|
device: offDevice,
|
|
|
|
status: 'error'
|
|
|
|
})
|
|
|
|
}
|
|
|
|
processNextImport()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function zmqImportTimeoutCb(msg: TimeoutMsg) {
|
|
|
|
const { offDevice, action } = pubIdWithOffDevice.get(msg.timeoutId)!
|
|
|
|
if (offDevice && action === 'import') {
|
|
|
|
message.error(`站点:${offDevice.sn}数据导出超时,请稍后重试`)
|
|
|
|
transferLoading.value = false
|
|
|
|
|
|
|
|
pubIdWithOffDevice.delete(msg.timeoutId)
|
|
|
|
siteTransferLogList.value.push({
|
|
|
|
msg: `数据导入超时,请稍后重试`,
|
|
|
|
device: offDevice,
|
|
|
|
status: 'error'
|
|
|
|
})
|
|
|
|
importQueue.value.shift()
|
|
|
|
isImporting.value = false
|
|
|
|
|
|
|
|
processNextImport()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// ================ 离线数据导入 end =========
|
|
|
|
</script>
|
|
|
|
|
|
|
|
<style scoped lang="scss">
|
|
|
|
.transfer-log-wrap {
|
|
|
|
margin-top: 10px;
|
|
|
|
@apply border-radius-8px bg-[#F9FAFB] p-10;
|
|
|
|
|
|
|
|
:deep(.el-scrollbar) {
|
|
|
|
height: calc(100% - 20px);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
.device-list-wrap {
|
|
|
|
@apply wh-full;
|
|
|
|
|
|
|
|
:deep(.el-checkbox-group) {
|
|
|
|
@apply wh-full flex flex-wrap gap-col-6 gap-row-4;
|
|
|
|
}
|
|
|
|
|
|
|
|
:deep(.el-checkbox__inner) {
|
|
|
|
width: 18px;
|
|
|
|
height: 18px;
|
|
|
|
|
|
|
|
&::after {
|
|
|
|
left: 6px;
|
|
|
|
top: 3px;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
:deep(.el-checkbox__label) {
|
|
|
|
@apply text-14px font-500 text-[#313131];
|
|
|
|
}
|
|
|
|
|
|
|
|
:deep(.el-checkbox__input.is-checked + .el-checkbox__label) {
|
|
|
|
color: var(--el-color-primary);
|
|
|
|
}
|
|
|
|
|
|
|
|
.device-item {
|
|
|
|
@apply w-289 h-160 border border-solid border-[#E0E0E0] rounded-8px p-x-14 p-y-10 flex-col;
|
|
|
|
|
|
|
|
.device-item-header {
|
|
|
|
@apply w-full text-black text-18px font-500 flex items-center justify-between;
|
|
|
|
|
|
|
|
.info {
|
|
|
|
font-size: 14px;
|
|
|
|
color: #f1bf63;
|
|
|
|
cursor: pointer;
|
|
|
|
text-decoration: underline;
|
|
|
|
|
|
|
|
&:hover {
|
|
|
|
color: #8ace6a;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
.device-item-body {
|
|
|
|
@apply flex-col m-t-10 text-sm gap-6;
|
|
|
|
|
|
|
|
.info-item {
|
|
|
|
@apply flex items-center gap-col-2;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
</style>
|