You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
775 lines
25 KiB
775 lines
25 KiB
<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>
|
|
|