Compare commits

...

2 Commits

Author SHA1 Message Date
betaqi 1f6437b09b feat: 一些调整 4 weeks ago
betaqi 83aa271805 feat: 功能调整 1 month ago
  1. 7
      global.types/components.d.ts
  2. 3
      src/api/module/firmware/index.ts
  3. 70
      src/api/module/taks/index.ts
  4. 1
      src/components/Edfs-button.vue
  5. 47
      src/components/Edfs-table/index.vue
  6. 1
      src/components/Edfs-wrap.vue
  7. 10
      src/router/index.ts
  8. 25
      src/views/firmwareUpload/index.vue
  9. 7
      src/views/layout/index.vue
  10. 52
      src/views/stationData/components/offTransferDlg.vue
  11. 94
      src/views/stationData/components/onLineTransferDlg.vue
  12. 1
      src/views/stationData/index.vue
  13. 646
      src/views/stationData/transferData.vue
  14. 130
      src/views/taskList/index.vue
  15. 107
      src/views/taskList/infoDrawer.vue

7
global.types/components.d.ts vendored

@ -21,17 +21,24 @@ declare module 'vue' { @@ -21,17 +21,24 @@ declare module 'vue' {
ElConfigProvider: typeof import('element-plus/es')['ElConfigProvider']
ElContainer: typeof import('element-plus/es')['ElContainer']
ElDatePicker: typeof import('element-plus/es')['ElDatePicker']
ElDescriptions: typeof import('element-plus/es')['ElDescriptions']
ElDescriptionsItem: typeof import('element-plus/es')['ElDescriptionsItem']
ElDialog: typeof import('element-plus/es')['ElDialog']
ElDivider: typeof import('element-plus/es')['ElDivider']
ElDrawer: typeof import('element-plus/es')['ElDrawer']
ElDropdown: typeof import('element-plus/es')['ElDropdown']
ElDropdownItem: typeof import('element-plus/es')['ElDropdownItem']
ElDropdownMenu: typeof import('element-plus/es')['ElDropdownMenu']
ElHeader: typeof import('element-plus/es')['ElHeader']
ElInput: typeof import('element-plus/es')['ElInput']
ElMenu: typeof import('element-plus/es')['ElMenu']
ElMenuItem: typeof import('element-plus/es')['ElMenuItem']
ElPagination: typeof import('element-plus/es')['ElPagination']
ElProgress: typeof import('element-plus/es')['ElProgress']
ElRow: typeof import('element-plus/es')['ElRow']
ElScrollbar: typeof import('element-plus/es')['ElScrollbar']
ElSubMenu: typeof import('element-plus/es')['ElSubMenu']
ElTableColumn: typeof import('element-plus/es')['ElTableColumn']
ElTag: typeof import('element-plus/es')['ElTag']
ElTooltip: typeof import('element-plus/es')['ElTooltip']
ElTree: typeof import('element-plus/es')['ElTree']

3
src/api/module/firmware/index.ts

@ -1,8 +1,5 @@ @@ -1,8 +1,5 @@
import { globalServer } from '../index'
export const uploadFirmwareFile = (params: FormData, abort: AbortController) =>
globalServer({
url: 'api/upload',

70
src/api/module/taks/index.ts

@ -0,0 +1,70 @@ @@ -0,0 +1,70 @@
import { globalServer } from '../index'
interface Param {
page: number
size: number
}
export const getTaskList = (params: Param) =>
globalServer<{
tasks: TaskList[]
total: number
}>({
url: 'api/task/summary',
method: 'get',
data: params,
})
export const getTaskInfo = (taskId: string) => globalServer<{ details: TaskInfo[] }>({
url: 'api/task/detail',
method: 'GET',
params: { task: taskId },
})
export interface TaskCreateParams {
site: string
devices: {
sn: string
disk?: string
host?: string
}[],
mode: 'import' | 'export' | 'update'
startTime?: string
endTime?: string
}
export const createTask = (params: TaskCreateParams) => globalServer({
url: 'api/task/apply',
method: 'POST',
data: params,
})
export const cancelTask = (taskId: string) => globalServer({
url: 'api/task/cancel',
method: 'POST',
data: { task: taskId },
})
export type TaskInfo = {
finish: number;
id: string;
info: string;
site: string;
sn: string;
status: -1 | 0 | 1 | 2; // -1 失败, 0 未开始, 1 进行中, 2 成功
task_id: string;
total: number;
};
export interface TaskList {
endTime: string; // 结束时间(可能是空字符串)
id: string; // "task759956"
info: string; // ""
mode: string; // "export"
site: string; // "test1"
startTime: string; // 开始时间(可能是空字符串)
status: -1 | 0 | 1 | 2 | 3; // -1 失败, 0 未开始, 1 进行中, 2 取消, 3 成功
loading?: boolean
}

1
src/components/Edfs-button.vue

@ -13,6 +13,7 @@ @@ -13,6 +13,7 @@
:color="color"
:class="className"
@click="onClick"
:link="link"
:text="text"
class="edfs-button"
>

47
src/components/Edfs-table/index.vue

@ -1,36 +1,16 @@ @@ -1,36 +1,16 @@
<template>
<div class="edfs-table-components">
<el-table
:data="data"
:fit="fit"
:stripe="stripe"
:border="border"
v-loading="loading"
:show-header="showHeader"
:max-height="maxHeight"
:highlight-current-row="highlightCurrentRow"
:row-class-name="rowClassName"
@current-change="onCurrentChange"
ref="ELTableRef"
@row-click="onRowClick"
@row-dblclick="onRowDblclick"
@selection-change="handleSelectionChange"
class="edfs-table"
:span-method="spanMethod"
>
<el-table :data="data" :fit="fit" :stripe="stripe" :border="border" v-loading="loading" :show-header="showHeader"
:max-height="maxHeight" :highlight-current-row="highlightCurrentRow" :row-class-name="rowClassName"
@current-change="onCurrentChange" ref="ELTableRef" @row-click="onRowClick" @row-dblclick="onRowDblclick"
@selection-change="handleSelectionChange" @expand-change="expandChange" class="edfs-table"
:span-method="spanMethod">
<slot></slot>
</el-table>
<template v-if="usePaging">
<div class="pagination-block">
<el-pagination
v-model:current-page="currentPage"
v-model:page-size="pageSize"
:width-type="1"
layout="prev, pager, next, jumper"
:total="pageTotal"
background
@current-change="onPageCurrentChange"
/>
<el-pagination v-model:current-page="currentPage" v-model:page-size="pageSize" :width-type="1"
layout="prev, pager, next, jumper" :total="pageTotal" background @current-change="onPageCurrentChange" />
<!-- @size-change="onPageSizeChange" -->
</div>
@ -59,6 +39,7 @@ const emit = defineEmits<{ @@ -59,6 +39,7 @@ const emit = defineEmits<{
// 'page-size-change': [pageSize: number]
'page-current-change': [currentPage: number]
'selection-change': [selection: any[]]
'expand-change': [row: any, expandedRows: any[]]
}>()
function onCurrentChange(currentRow: any, oldCurrentRow: any) {
@ -74,6 +55,9 @@ function onRowDblclick(row: any, column: any, event: Event) { @@ -74,6 +55,9 @@ function onRowDblclick(row: any, column: any, event: Event) {
function handleSelectionChange(selection: any[]) {
emit('selection-change', selection)
}
function expandChange(row: any, expandedRows: any[]) {
emit('expand-change', row, expandedRows)
}
//
const pageSize = ref()
@ -125,6 +109,7 @@ defineExpose({ @@ -125,6 +109,7 @@ defineExpose({
display: flex;
flex-direction: column;
box-sizing: border-box;
// background-color: #fff;
.edfs-table {
width: 100%;
@ -155,21 +140,26 @@ defineExpose({ @@ -155,21 +140,26 @@ defineExpose({
align-items: center;
justify-content: end;
height: 60px;
:deep(.el-pagination__editor.el-input) {
width: 66px;
}
:deep(.el-input__wrapper) {
height: 30px;
margin-left: 8px;
padding: 0 10px;
.el-input__inner {
font-size: 14px;
height: 100%;
}
}
:deep(.el-pagination) {
// width: cvw(200);
}
:deep(.el-pagination),
:deep(.el-pagination .el-icon),
:deep(.el-pagination li) {
@ -181,6 +171,7 @@ defineExpose({ @@ -181,6 +171,7 @@ defineExpose({
:deep(.el-pagination li) {
background-color: transparent;
}
:deep(.el-pager) {
height: 28px;
}
@ -196,12 +187,14 @@ defineExpose({ @@ -196,12 +187,14 @@ defineExpose({
color: #619925;
}
}
:deep(.is-active) {
color: #619925 !important;
background: rgba(97, 153, 37, 0.06) !important;
border: 1px solid rgba(97, 153, 37, 1) !important;
border-radius: 2px;
}
:deep(.el-input__wrapper) {
background-color: var(--pagination-bg);
}

1
src/components/Edfs-wrap.vue

@ -1,7 +1,6 @@ @@ -1,7 +1,6 @@
<template>
<div class="edfs-wrap" :class="{ 'edfs-wrap-cvh': useCvh }">
<div class="wrap-title" v-if="title || customLeft">
{{}}
<div class="title-left" v-if="!customLeft">
<template v-if="shape === 'rect'">
<div

10
src/router/index.ts

@ -43,6 +43,16 @@ export const defaultRouter = [ @@ -43,6 +43,16 @@ export const defaultRouter = [
icon: 'i-mingcute:transfer-2-line',
},
},
{
path: '/task',
name: 'task',
component: () => import('@/views/taskList/index.vue'),
meta: {
title: '任务列表',
isShow: true,
icon: 'i-mingcute:task-line',
}
}
],
},
]

25
src/views/firmwareUpload/index.vue

@ -2,12 +2,12 @@ @@ -2,12 +2,12 @@
<div class="flex justify-center items-center size-full">
<EdfsWrap :title="title" style="width: 50%; height: 50%">
<el-upload v-model:fileList="fileList" v-loading="loading" element-loading-text="上传中..." drag action=""
accept=".tar.gz" :limit="1" :on-exceed="handleExceed" :auto-upload="false" ref="uploadRef"
class="h-[calc(100%-30px)] w-full">
accept=".zip,.tar,.tar.gz" :limit="1" :on-exceed="handleExceed" :auto-upload="false" ref="uploadRef"
:before-upload="beforeAvatarUpload" class="h-[calc(100%-30px)] w-full">
<div class="i-line-md:cloud-alt-upload-loop text-20px mx-auto"></div>
<div class="text">拖拽文件或者 <em>点击上传</em></div>
<template #tip v-if="!fileList.length">
<div class="el-upload__tip">上传限制一个文件新文件会覆盖旧文件</div>
<div class="el-upload__tip">上传限制一个文件新文件会覆盖旧文件仅支持 .zip.tar .tar.gz 格式</div>
</template>
</el-upload>
<div class="flex justify-center">
@ -43,14 +43,23 @@ const handleExceed: UploadProps['onExceed'] = files => { @@ -43,14 +43,23 @@ const handleExceed: UploadProps['onExceed'] = files => {
const uploadRef = ref<UploadInstance>()
const beforeAvatarUpload: UploadProps['beforeUpload'] = rawFile => {
const accept = ['.zip', '.tar', '.tar.gz']
const fileTypes = rawFile.name.substring(rawFile.name.indexOf('.'))
if (!accept.includes(fileTypes)) {
message.error('请上传 tar.gz 文件')
const fileName = rawFile.name.toLowerCase()
const supportedFormats = ['.zip', '.tar', '.tar.gz']
const isValidFormat = supportedFormats.some(format => fileName.endsWith(format))
if (!isValidFormat) {
message.error('请上传 .zip、.tar 或 .tar.gz 格式的文件')
uploadRef.value?.clearFiles?.()
return false
}
// const maxSize = 100 * 1024 * 1024
// if (rawFile.size > maxSize) {
// message.error('100MB')
// uploadRef.value?.clearFiles?.()
// return false
// }
return true
}
function validate() {

7
src/views/layout/index.vue

@ -50,10 +50,6 @@ @@ -50,10 +50,6 @@
{{ currentTime }}
</div>
</div>
<!-- <div class="flex items-center gap-col-2 p-r-20">
<el-avatar :src="circleUrl" class="avatar" />
<span class="username">John Doe</span>
</div> -->
</el-header>
<main class="main-wrap">
<RouterView />
@ -76,8 +72,9 @@ const { theme } = useTheme() @@ -76,8 +72,9 @@ const { theme } = useTheme()
const menuList = computed<any[]>(() => {
let data = defaultRouter[0].children
if (env.VITE_APP_ENV !== 'local') {
data = data.filter(item => item.name != 'firmware-upload')
data = data.filter(item => !['firmware-upload', 'task'].includes(item.name))
}
console.log(data)
return data
}
)

52
src/views/stationData/components/offTransferDlg.vue

@ -6,8 +6,7 @@ @@ -6,8 +6,7 @@
<span class="require">*</span>
云端IP:
</div>
<el-input v-model="form.clientIp" class="flex-1" placeholder="请输入云端IP:
" />
<el-input v-model="form.clientIp" class="flex-1" placeholder="请输入云端IP:" />
</el-row>
</div>
</EdfsDialog>
@ -20,11 +19,12 @@ import { cloneDeep } from 'lodash-es' @@ -20,11 +19,12 @@ import { cloneDeep } from 'lodash-es'
import { useMessage } from '@/composables/useMessage'
import type { IOfflineDevice } from '../type'
import type { ISite } from '@/api/module/transfer'
import { createTask, type TaskCreateParams } from '@/api/module/taks'
const message = useMessage()
const emit = defineEmits<{
'on-save': [msg: PublishMsg<'import'>[], site: IOfflineDevice[]]
'on-save': []
}>()
const props = defineProps<{
@ -37,48 +37,40 @@ const fromData = { @@ -37,48 +37,40 @@ const fromData = {
clientIp: '',
}
const curOffDeive = ref<IOfflineDevice[]>([])
const form = ref(cloneDeep(fromData))
const paramsData = ref<TaskCreateParams>()
const isBatchTransfer = ref(false)
function open(item: IOfflineDevice[], isBatch: boolean = false) {
curOffDeive.value = item
function open(parmas: TaskCreateParams) {
paramsData.value = parmas
visible.value = true
isBatchTransfer.value = isBatch
}
function onSave() {
async function onSave() {
if (verifyData()) return
if (!curOffDeive.value.length) {
message.error('请选择设备')
if (!paramsData.value) {
message.error('参数错误')
return
}
const msgList: PublishMsg<"import">[] = []
for (const device of curOffDeive.value) {
const params = [
`${form.value.clientIp}`,
'',
'',
'',
'',
'',
`${props.siteInfo!.name}/${device.sn}`,
]
const msg = getPubInitData<'import'>('import', params)
msgList.push(msg)
paramsData.value.devices.forEach((r) => {
r.host = form.value.clientIp
})
const res = await createTask(paramsData.value)
if (res.code !== 0) {
message.error(`任务创建失败`)
} else {
message.success('任务创建成功,请在任务列表中查看')
}
emit('on-save', msgList, curOffDeive.value)
emit('on-save')
close()
}
function close() {
form.value = cloneDeep(fromData)
curOffDeive.value = []
visible.value = false
isBatchTransfer.value = false
form.value = cloneDeep(fromData)
paramsData.value = undefined
}
const ipPattern =
/^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/

94
src/views/stationData/components/onLineTransferDlg.vue

@ -1,28 +1,15 @@ @@ -1,28 +1,15 @@
<template>
<EdfsDialog
:title="isBatchTransfer ? '批量迁移' : '数据迁移'"
:is-show="visible"
width="580px"
@on-close="close"
@on-save="onSave"
>
<EdfsDialog :title="isBatchTransfer ? '批量迁移' : '数据迁移'" :is-show="visible" width="580px" @on-close="close"
@on-save="onSave">
<div class="flex-col gap-10 w-80% m-x-30px">
<el-row>
<div class="label">
<span class="require">*</span>
数据开始时间:
</div>
<el-date-picker
v-model="startTime"
value-format="YYYY-MM-DD HH:mm"
format="YYYY-MM-DD HH:mm"
class="flex-1"
type="datetime"
placeholder="请选择开始时间"
:disabled-date="disabledDate"
:disabled-time="disabledStartTime"
@change="handleStartTimeChange"
/>
<el-date-picker v-model="startTime" value-format="YYYY-MM-DD HH:mm" format="YYYY-MM-DD HH:mm" class="flex-1"
type="datetime" placeholder="请选择开始时间" :disabled-date="disabledDate" :disabled-time="disabledStartTime"
@change="handleStartTimeChange" />
</el-row>
<el-row>
@ -30,17 +17,9 @@ @@ -30,17 +17,9 @@
<span class="require">*</span>
数据结束时间:
</div>
<el-date-picker
v-model="endTime"
class="flex-1"
value-format="YYYY-MM-DD HH:mm"
format="YYYY-MM-DD HH:mm"
type="datetime"
placeholder="请选择结束时间"
:disabled-date="disabledDate"
:disabled-time="disabledEndTime"
@change="handleEndTimeChange"
/>
<el-date-picker v-model="endTime" class="flex-1" value-format="YYYY-MM-DD HH:mm" format="YYYY-MM-DD HH:mm"
type="datetime" placeholder="请选择结束时间" :disabled-date="disabledDate" :disabled-time="disabledEndTime"
@change="handleEndTimeChange" />
</el-row>
</div>
</EdfsDialog>
@ -51,10 +30,11 @@ import dayjs from 'dayjs' @@ -51,10 +30,11 @@ import dayjs from 'dayjs'
import { getPubInitData, type PublishMsg } from '@/utils/zmq'
import type { IOnlineDevice } from '../type'
import { useMessage } from '@/composables/useMessage'
import { createTask, type TaskCreateParams } from '@/api/module/taks'
const message = useMessage()
const emit = defineEmits<{
'on-save': [PublishMsg<'export'>, IOnlineDevice]
'on-save': []
}>()
const props = defineProps<{
@ -115,40 +95,31 @@ function handleEndTimeChange(val: string) { @@ -115,40 +95,31 @@ function handleEndTimeChange(val: string) {
}
}
const curDevice = ref<IOnlineDevice>()
const paramsData = ref<TaskCreateParams>()
const batchClientIp = ref('')
const batchPath = ref('')
function open(item: IOnlineDevice, clientIps: string, paths: string) {
curDevice.value = item
function open(parmas: TaskCreateParams) {
paramsData.value = parmas
visible.value = true
batchClientIp.value = clientIps
batchPath.value = paths
}
function onSave() {
async function onSave() {
if (!verifyData()) return
if (!curDevice.value) {
message.error('请选择设备')
if (!paramsData.value) {
message.error('任务参数未定义')
return
}
const params = [
`${props.isBatchTransfer ? batchClientIp.value : curDevice.value.clientIp}`,
'',
'',
'',
'',
'',
`${
props.isBatchTransfer
? batchPath.value
: `${curDevice.value.site_id}/${curDevice.value.sn}`
}`,
`${dayjs(startTime.value).valueOf()},${dayjs(endTime.value).valueOf()}`,
]
const msg = getPubInitData<'export'>('export', params)
emit('on-save', msg, curDevice.value as IOnlineDevice)
const params = paramsData.value
params.startTime = dayjs(startTime.value).format('YYYY-MM-DD HH:mm:ss')
params.endTime = dayjs(endTime.value).format('YYYY-MM-DD HH:mm:ss')
const res = await createTask(params)
if (res.code !== 0) {
message.error(`任务创建失败`)
} else {
message.success('任务创建成功,请在任务列表中查看')
}
emit('on-save')
close()
}
@ -156,18 +127,16 @@ function close() { @@ -156,18 +127,16 @@ function close() {
startTime.value = ''
endTime.value = ''
visible.value = false
curDevice.value = undefined
batchClientIp.value = ''
batchPath.value = ''
paramsData.value = undefined
}
function verifyData() {
if(!startTime.value) {
if (!startTime.value) {
message.error('请选择开始时间')
return false
}
if(!endTime.value) {
if (!endTime.value) {
message.error('请选择结束时间')
return false
}
@ -190,6 +159,7 @@ defineExpose({ @@ -190,6 +159,7 @@ defineExpose({
line-height: 33px;
text-align: right;
width: 110px;
.require {
color: red;
}

1
src/views/stationData/index.vue

@ -78,7 +78,6 @@ @@ -78,7 +78,6 @@
</div>
</div>
</template>
</div>
</div>
</EdfsWrap>

646
src/views/stationData/transferData.vue

@ -3,18 +3,27 @@ @@ -3,18 +3,27 @@
<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>
<EdfsWrap
:title="isonLineTransfer ? `在线设备列表` : `历史设备列表`"
class="flex-1"
useScrollBar
:shapeColor="isonLineTransfer ? '#4B9E5F' : '#F1BF63'"
>
<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="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" />
{{ isonLineTransfer ? '数据迁移' : '数据导出' }} </el-button>
<el-button v-if="isonLineTransfer" type="primary" @click="onBatchUpgrade"> <i
class="i-codicon:chip mr-1" />批量升级
<el-button type="primary" @click="onBatchTransfer">
<i class="i-mdi:database-arrow-right-outline mr-1" />
{{ isonLineTransfer ? '数据迁移' : '数据导出' }}
</el-button>
<el-button v-if="isonLineTransfer" type="primary" @click="onBatchUpgrade">
<i class="i-codicon:chip mr-1" />批量升级
</el-button>
</template>
</template>
@ -24,7 +33,10 @@ @@ -24,7 +33,10 @@
<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 !== '离线'">
<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">
@ -32,26 +44,38 @@ @@ -32,26 +44,38 @@
</div>
</div>
<div class="flex items-center gap-col-2" v-if="!(isBatchTransfer || isBatchUpgrade)">
<div
class="flex items-center gap-col-2"
v-if="!(isBatchTransfer || isBatchUpgrade)"
>
<template v-if="env.VITE_APP_ENV == 'local'">
<el-tooltip :content="isonLineTransfer ? '数据迁移' : '数据导出'"
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
:content="isonLineTransfer ? '数据迁移' : '数据导出'"
v-if="isonLineTransfer ? item.status === '在线' : true"
>
<i
class="i-mdi:database-arrow-right-outline :hover:color-[#8ACE6A] color-[#4B9E5F] cursor-pointer text-20px"
@click="onBatchTransferSave([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
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>
@click="onDeviceDetails(item)"
></div>
</el-tooltip>
</div>
</div>
<div class="device-item-body relative">
@ -85,168 +109,45 @@ @@ -85,168 +109,45 @@
</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', 'sc'].includes(item.upFirmware) ||
(item.upFirmwareStatus?.step === 4 && item.upFirmwareStatus?.progress === 100)"
@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 === 4 && item.upFirmwareStatus?.progress === 100 ? '安装完成' :
upgradeProgressStatusMap.find(r => r.status === item.upFirmwareStatus?.step)?.text ??
'--'}}
</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" ref="transferLogScrollbar">
<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'
? '数据导入中' : `数据导入完成 (失败:${offLineTransferRes().error}个 超时:${offLineTransferRes().timeout}个) ` :
'数据导入中'
}}</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" ref="transferLogScrollbar">
<div v-for="i in siteTransferLogList"
:class="['error', 'timeout'].includes(i.status) ? '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" />
<OnLineTransferDlg
ref="onLineTransferDlgRef"
@on-save="onLineDeviceTransfer"
:is-batch-transfer="isBatchTransfer"
/>
<OffTransferDlg
ref="offTransferDlgRef"
: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 type { IOfflineDevice, IOnlineDevice } 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'
import { createTask, type TaskCreateParams } from '@/api/module/taks'
const env = import.meta.env
const onLineTransferDlgRef = ref<typeof OnLineTransferDlg>()
const router = useRouter()
@ -254,181 +155,30 @@ const route = useRoute() @@ -254,181 +155,30 @@ 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 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: '迁移超时',
stop: '迁移已终止',
}
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' | 'stop' | undefined>()
const transferStatus = ref<
'progress' | 'success' | 'failed' | 'timeout' | 'stop' | undefined
>()
const devices = computed(() => {
return isonLineTransfer.value ? Array.from(devicesMap.value.values()) : offLineDeviceList.value
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)
// 线
function onLineDeviceTransfer() {
if (isBatchTransfer.value) {
onBatchCancel()
}
}
const statusMap = {
200: 'success',
1002: 'padding',
1003: 'failed',
}
const zmqExportMSG = ref('')
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'
const isLineFeed = feedback[2] && feedback[2].includes('\n')
if (isLineFeed) {
if (zmqExportMSG.value) {
curTransferLog.value.at(-1)!.msg += feedback[2]
} else {
curTransferLog.value.push({
msg: `主机【${feedback[0]}】: ${feedback[2]}`,
host: feedback[0],
status,
})
}
zmqExportMSG.value = ''
} else {
if (!zmqExportMSG.value) {
curTransferLog.value.push({
msg: `主机【${feedback[0]}】: ${zmqExportMSG.value + feedback[2]}`,
host: feedback[0],
status,
})
}
zmqExportMSG.value += typeof feedback[2] === 'string' ? feedback : ''
curTransferLog.value.at(-1)!.msg = zmqExportMSG.value
}
}
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)
zmqExportMSG.value = ''
}
}
}
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()
transferStatus.value = 'stop'
})
}
function zmqTimeoutCb(msg: TimeoutMsg) {
const { device, action } = exportPubDeviceMap.get(msg.timeoutId)!
if (device && action === 'export') {
message.error(`迁移超时,请重新稍后尝试`)
exportPubDeviceMap.delete(msg.timeoutId)
closeTransferMask()
transferStatus.value === 'failed'
zmqExportMSG.value = ''
}
}
const onlineDeviceMap: Record<
keyof Omit<
IOnlineDevice,
@ -460,16 +210,38 @@ function onBatchTransfer() { @@ -460,16 +210,38 @@ function onBatchTransfer() {
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)
const lineDevices = checkDeviceList as IOnlineDevice[]
const site = lineDevices[0]?.site_id
if (!site) {
message.error('请选择站点')
return
}
const deivcesParms = lineDevices.map(item => ({
sn: item.sn,
host: item.clientIp,
disk: item.footprint,
}))
const parmas: TaskCreateParams = {
site,
devices: deivcesParms,
mode: 'export',
}
onLineTransferDlgRef.value?.open(parmas)
} else {
offTransferDlg.value?.open(checkDeviceList, true)
const offlineDevices = checkDeviceList as IOfflineDevice[]
const site = offlineDevices[0]?.site_id
const deivcesParms = offlineDevices.map(item => ({
sn: item.sn,
}))
const parmas: TaskCreateParams = {
site,
devices: deivcesParms,
mode: 'import',
}
offTransferDlgRef.value?.open(parmas)
}
}
@ -511,9 +283,7 @@ function getOnlineDeviceList(): IOnlineDevice[] { @@ -511,9 +283,7 @@ function getOnlineDeviceList(): IOnlineDevice[] {
}
function getOfflineDeviceList(): IOfflineDevice[] {
return offLineDeviceList.value.filter(item =>
checkDeviceList.value.includes(item.sn)
)
return offLineDeviceList.value.filter(item => checkDeviceList.value.includes(item.sn))
}
function onBatchCancel() {
@ -521,13 +291,6 @@ function onBatchCancel() { @@ -521,13 +291,6 @@ function onBatchCancel() {
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')
@ -583,202 +346,43 @@ function onDeviceDetails(item: IOfflineDevice) { @@ -583,202 +346,43 @@ function onDeviceDetails(item: IOfflineDevice) {
const isCanFirmwareUpload = computed(() => !!firmwarePath.value && isonLineTransfer.value)
const upgradeSnList = ref<string[]>([])
const upgradePubDeviceMap = new Map<string, UpFirmwarsDevice>()
function onFirmwareUpload(devices: IOnlineDevice[]) {
async 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.code
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 === ZmqMsgResultType.PROGRESS) {
upFirmwareStatus(deviceSn, msg.feedback)
if (progressStatus === 4 && progress === 100) {
upFirmwareSucceed(deviceSn)
}
}
}
if (status === ZmqMsgResultType.SUCCESS || status === ZmqMsgResultType.ERROR) {
upFirmwareStatus(deviceSn, msg.feedback)
if (status === ZmqMsgResultType.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'
const site = devices[0]?.site_id
if (!site) {
message.error('请选择站点')
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 offLineTransferRes = () => {
let timeoutNum = 0
let errorNum = 0
const findTimeout = siteTransferLogList.value.filter(i => i.status === 'timeout')
const uniqueTimeoutSn = new Set(findTimeout.map(i => i.device.sn))
timeoutNum = uniqueTimeoutSn.size
const findError = siteTransferLogList.value.filter(i => i.status === 'error')
errorNum = new Set(findError.map(i => i.device.sn)).size
return {
timeout: timeoutNum,
error: errorNum,
const deivcesParms = devices.map(item => ({
sn: item.sn,
host: item.clientIp,
disk: item.footprint,
}))
const parmas: TaskCreateParams = {
site,
devices: deivcesParms,
mode: 'update',
}
}
const importMsg = ref('')
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] || '' : ''
const isLineFeed = log && log.includes('\n')
if (isLineFeed) {
if (importMsg.value) {
siteTransferLogList.value.at(-1)!.msg += log
} else {
siteTransferLogList.value.push({
msg: log,
device: offDevice,
status: 'success'
})
}
importMsg.value = ''
} else {
if (!importMsg.value) {
siteTransferLogList.value.push({
msg: log,
device: offDevice,
status: 'success'
})
}
importMsg.value += typeof log === 'string' ? log : ''
siteTransferLogList.value.at(-1)!.msg = importMsg.value
}
const res = await createTask(parmas)
if (res.code !== 0) {
message.error(`任务创建失败`)
} else {
message.success('任务创建成功,请在任务列表中查看')
}
if (code !== ZmqMsgResultType.PROGRESS) {
importQueue.value.shift()
isImporting.value = false
if (code === ZmqMsgResultType.ERROR) {
siteTransferLogList.value.push({
msg: `数据导入失败,请稍后重试消息结果:${result}`,
device: offDevice,
status: 'error'
})
}
importMsg.value = ''
processNextImport()
}
onBatchCancel()
}
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: 'timeout'
})
importQueue.value.shift()
isImporting.value = false
importMsg.value = ''
processNextImport()
}
// ================ 线 =========
const offTransferDlgRef = ref<typeof OffTransferDlg>()
function onOffDeviceTransfer() {
onBatchCancel()
}
// ================ 线 end =========

130
src/views/taskList/index.vue

@ -0,0 +1,130 @@ @@ -0,0 +1,130 @@
<template>
<div class="task-list wh-full">
<EdfsWrap title="任务列表" class="h-full">
<EdfsTable class="wh-full" v-loading="loading" :data="list" ref="tableRef" :highlight-current-row="true"
:page-total="total" :current-page="queryParams.page" :page-size="queryParams.size" row-class-name="row"
@pageCurrentChange="handleJump">
<template v-for="(col, idx) in tableCol" :key="idx">
<el-table-column v-if="col.prop.endsWith('Time')" :label="col.label" :min-width="col.minWidth">
<template #default="scope">
{{ dayjs(scope.row.createTime).format('YYYY-MM-DD HH:mm:ss') }}
</template>
</el-table-column>
<el-table-column v-else-if="col.prop === 'status'" :prop="col.prop" :label="col.label"
:min-width="col.minWidth">
<template #default="scope">
{{taskStatus.find(r => r.value === scope.row.status)?.label || '--'}}
</template>
</el-table-column>
<el-table-column v-else-if="col.prop === 'mode'" :prop="col.prop" :label="col.label"
:min-width="col.minWidth">
<template #default="scope">
{{taskMode.find(r => r.value === scope.row.mode)?.label || '--'}}
</template>
</el-table-column>
<el-table-column v-else :prop="col.prop" :label="col.label" :min-width="col.minWidth" />
</template>
<el-table-column label="操作" width="210" align="center">
<template #default="scope">
<EdfsButton size="small" link type="primary" inner-text="详情" @click="onDetail(scope.row)" />
<EdfsButton size="small" link type="danger" v-if="[0, 1].includes(scope.row.mode)" inner-text="取消任务"
@click="onCancel(scope.row.id)" />
</template>
</el-table-column>
</EdfsTable>
</EdfsWrap>
</div>
<InfoDrawer ref="InfoDrawerRef" />
</template>
<script setup lang="ts">
import { cancelTask, getTaskInfo, getTaskList, type TaskInfo, type TaskList } from '@/api/module/taks'
import { useMessage } from '@/composables/useMessage'
import dayjs from 'dayjs'
import InfoDrawer from './infoDrawer.vue'
const message = useMessage()
const taskStatus = [{
value: -1,
label: '执行失败'
}, {
label: '等待执行',
value: 0
}, {
label: '执行中',
value: 1
}, {
label: '已取消',
value: 2
}, {
label: '执行成功',
value: 3
}]
const taskMode = [{
value: 'export',
label: '迁移'
}, {
value: 'import',
label: '导入'
}, {
value: 'update',
label: '升级'
}]
const tableCol = [
{ label: '任务ID', prop: 'id', minWidth: '10%' },
{ label: '站点', prop: 'site', minWidth: '16%' },
{ label: '任务类型', prop: 'mode', minWidth: '10%' },
{ label: '任务状态', prop: 'status', minWidth: '10%' },
{ label: '创建时间', prop: 'startTime', minWidth: '10%' },
]
const loading = ref(true)
const total = ref(0)
const list = ref<TaskList[]>([])
const queryParams = reactive({
page: 1,
size: undefined,
})
const tableRef = ref()
const getList = async () => {
if (!queryParams.size) {
queryParams.size = tableRef.value.getSize()
}
loading.value = true
const res = await getTaskList(queryParams as any)
if (res !== null && res.code === 0) {
list.value = res.data.tasks
total.value = res.data.total
}
loading.value = false
}
function handleJump(page: number) {
queryParams.page = page
getList()
}
const InfoDrawerRef = ref<typeof InfoDrawer>()
function onDetail(row: TaskList) {
InfoDrawerRef.value?.open(row)
}
const onCancel = async (id: string) => {
await message.delConfirm(`是否确认取消该任务?`)
const res = await cancelTask(id)
if (res.code !== 0) return
await getList()
}
onMounted(() => {
getList()
})
</script>
<style scoped></style>

107
src/views/taskList/infoDrawer.vue

@ -0,0 +1,107 @@ @@ -0,0 +1,107 @@
<template>
<div class="fault-rule-drawer">
<el-drawer v-model="isShowDrawer" title="任务详情" direction="rtl" size="50%" modal-class="model-dev-opn"
:before-close="handleBeforeClose">
<main class="wh-full" v-loading="loading">
<template v-for="detail in detailList">
<el-descriptions border :title="`${detail.sn}任务详情`">
<el-descriptions-item :label-width="60" label="状态"> <el-tag size="small">{{statusList.find(r => r.value ===
detail.status)?.label || '--'
}}</el-tag></el-descriptions-item>
<el-descriptions-item :label-width="100" label="总数数据量">{{ detail.total }}</el-descriptions-item>
<el-descriptions-item :label-width="110" label="已完成数据量">{{ detail.finish }}</el-descriptions-item>
<el-descriptions-item :label-width="60" label="信息">
{{ detail?.info ? detail.info : '暂无信息' }}
</el-descriptions-item>
<el-descriptions-item label="当前进度">
<template v-if="detail.total === 0">
<span>暂无进度</span>
</template>
<el-progress v-else style="width: 95%;" :percentage="Math.floor((detail.finish / detail.total) * 100)"
:text-inside="true" :stroke-width="20" />
</el-descriptions-item>
</el-descriptions>
</template>
</main>
</el-drawer>
</div>
</template>
<script setup lang="ts">
import { getSubTopic, type PubMsgData } from '@/utils/zmq'
import ZMQWorker from '@/composables/useZMQJsonWorker'
import { getTaskInfo, type TaskInfo, type TaskList } from '@/api/module/taks'
import { useMessage } from '@/composables/useMessage'
const message = useMessage()
const worker = ZMQWorker.getInstance()
const isShowDrawer = defineModel<boolean>()
const detailList = ref<TaskInfo[]>([])
const loading = ref(false)
async function onDetail(row: TaskList) {
loading.value = true
const res = await getTaskInfo(row.id);
if (res.code === 0) {
detailList.value = res?.data?.details ?? []
} else {
message.error('获取任务详情失败')
}
loading.value = false
}
const curentTaskId = ref('')
async function open(row: TaskList) {
isShowDrawer.value = true
curentTaskId.value = row.id
await onDetail(row)
worker.subscribe(getSubTopic('server', 'event', 'task'), zmqTaskCb)
}
const statusList = [
{ label: '未开始', value: 0 },
{ label: '进行中', value: 1 },
{ label: '成功', value: 2 },
{ label: '失败', value: -1 },
]
function zmqTaskCb(msg: PubMsgData) {
const { feedback, result, id } = msg
const taskId = feedback[0]
const deviceSN = feedback[1]
const deviceStatus = feedback[2] || '未知状态'
const finish = feedback[3] || 0
const total = feedback[4] || 0
if (curentTaskId.value !== taskId) return
const detail = detailList.value.find(item => item.sn === deviceSN)
if (!detail) return
detail.status = deviceStatus
detail.finish = finish
detail.total = total
}
function handleBeforeClose(done: () => void) {
isShowDrawer.value = false
curentTaskId.value = ''
done()
}
defineExpose({
open,
})
</script>
<style scoped lang="scss">
.fault-rule-drawer {
font-size: 16px;
:deep(.edfs-wrap) {
width: auto;
}
:deep(.el-drawer__header) {
color: var(--text-color);
}
}
</style>
Loading…
Cancel
Save