14 changed files with 901 additions and 92 deletions
@ -0,0 +1,21 @@ |
|||||||
|
import { globalServer } from '../index' |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export const uploadFirmwareFile = (params: File, abort: AbortController) => |
||||||
|
globalServer({ |
||||||
|
url: 'api/upload', |
||||||
|
method: 'POST', |
||||||
|
data: params, |
||||||
|
signal: abort.signal, |
||||||
|
headers: { |
||||||
|
'Content-Type': 'multipart/form-data', |
||||||
|
}, |
||||||
|
}) |
||||||
|
|
||||||
|
|
||||||
|
export const getFirmwarePath = () => globalServer<string>({ |
||||||
|
url: 'api/package-path', |
||||||
|
method: 'GET', |
||||||
|
}) |
@ -0,0 +1,186 @@ |
|||||||
|
<template> |
||||||
|
<div class="flex justify-center items-center size-full"> |
||||||
|
<EdfsWrap 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="upload" |
||||||
|
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> |
||||||
|
</template> |
||||||
|
</el-upload> |
||||||
|
<div class="flex justify-center"> |
||||||
|
<el-button type="primary" @click="onSave" v-show="fileList.length && !loading" |
||||||
|
>确定上传</el-button |
||||||
|
> |
||||||
|
<el-button type="info" v-show="loading" @click="onClone">取消上传</el-button> |
||||||
|
</div> |
||||||
|
</EdfsWrap> |
||||||
|
</div> |
||||||
|
</template> |
||||||
|
|
||||||
|
<script setup lang="ts"> |
||||||
|
import { uploadFirmwareFile } from '@/api/module/firmware' |
||||||
|
import { useMessage } from '@/composables/useMessage' |
||||||
|
import type { |
||||||
|
UploadInstance, |
||||||
|
UploadProps, |
||||||
|
UploadRawFile, |
||||||
|
UploadUserFile, |
||||||
|
} from 'element-plus' |
||||||
|
|
||||||
|
const message = useMessage() |
||||||
|
|
||||||
|
const fileList = ref<UploadUserFile[]>([]) |
||||||
|
|
||||||
|
const upload = ref<UploadInstance>() |
||||||
|
const handleExceed: UploadProps['onExceed'] = files => { |
||||||
|
upload.value!.clearFiles() |
||||||
|
const file = files[0] as UploadRawFile |
||||||
|
|
||||||
|
upload.value!.handleStart(file) |
||||||
|
} |
||||||
|
|
||||||
|
const beforeAvatarUpload: UploadProps['beforeUpload'] = rawFile => { |
||||||
|
const accept = ['.zip', '.tar', '.tar.gz'] |
||||||
|
const fileTypes = rawFile.name.substring(rawFile.name.lastIndexOf('.')) |
||||||
|
if (!accept.includes(fileTypes)) { |
||||||
|
message.error('请上传 tar.gz 文件') |
||||||
|
return false |
||||||
|
} |
||||||
|
return true |
||||||
|
} |
||||||
|
function validate() { |
||||||
|
if (!fileList.value.length) { |
||||||
|
message.error('请上传文件') |
||||||
|
return true |
||||||
|
} |
||||||
|
if (beforeAvatarUpload(fileList.value[0].raw!)) { |
||||||
|
message.error('请上传 tar.gz 文件') |
||||||
|
return true |
||||||
|
} |
||||||
|
return false |
||||||
|
} |
||||||
|
const loading = ref(false) |
||||||
|
const abortController = ref<AbortController>() |
||||||
|
|
||||||
|
async function onSave() { |
||||||
|
if (loading.value) { |
||||||
|
message.error('文件正在上传中,请稍后') |
||||||
|
return |
||||||
|
} |
||||||
|
if (!validate()) return |
||||||
|
loading.value = true |
||||||
|
abortController.value = new AbortController() |
||||||
|
const userFile = fileList.value[0] as UploadUserFile |
||||||
|
const res = await uploadFirmwareFile(userFile.raw as File, abortController.value) |
||||||
|
if (res.code === 200) { |
||||||
|
message.success('上传成功') |
||||||
|
} else { |
||||||
|
message.error('上传失败') |
||||||
|
} |
||||||
|
// const file = userFile.raw as File |
||||||
|
// const fileName = userFile.name |
||||||
|
|
||||||
|
// const chunkList = await createChunksFromFileData(file, 1024) |
||||||
|
|
||||||
|
// try { |
||||||
|
// for (const chunk of chunkList) { |
||||||
|
// const { chunkData, offset } = chunk |
||||||
|
// const res = await uploadSftpFile( |
||||||
|
// { |
||||||
|
// fileName, |
||||||
|
// chunk: chunkData, |
||||||
|
// targetPath: props.curPath, |
||||||
|
// offset, |
||||||
|
// }, |
||||||
|
// abortController.value |
||||||
|
// ) |
||||||
|
|
||||||
|
// if (res.code === -777) { |
||||||
|
// return |
||||||
|
// } |
||||||
|
// if (![200, 0].includes(res.code)) { |
||||||
|
// // 如果是手动取消的请求,不提示错误 |
||||||
|
// message.error(`上传失败,${res.msg}`) |
||||||
|
// return |
||||||
|
// } |
||||||
|
// } |
||||||
|
// } catch (error) { |
||||||
|
// message.error('上传失败') |
||||||
|
// } |
||||||
|
abortController.value = undefined |
||||||
|
|
||||||
|
loading.value = false |
||||||
|
|
||||||
|
onClone() |
||||||
|
} |
||||||
|
|
||||||
|
async function readFileAsArrayBuffer(file: File): Promise<ArrayBuffer> { |
||||||
|
return new Promise((resolve, reject) => { |
||||||
|
const reader = new FileReader() |
||||||
|
reader.readAsArrayBuffer(file) |
||||||
|
reader.onload = () => resolve(reader.result as ArrayBuffer) |
||||||
|
reader.onerror = reject |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
async function createChunksFromFileData(file: File, chunkSize: number) { |
||||||
|
const fileData = await readFileAsArrayBuffer(file) |
||||||
|
|
||||||
|
const chunkList: { offset: number; chunkIndex: number; chunkData: Uint8Array }[] = [] |
||||||
|
let offset = 0 |
||||||
|
let chunkIndex = 0 |
||||||
|
|
||||||
|
while (offset < fileData.byteLength) { |
||||||
|
const chunkData = new Uint8Array(fileData.slice(offset, offset + chunkSize)) |
||||||
|
chunkList.push({ |
||||||
|
offset, |
||||||
|
chunkIndex, |
||||||
|
chunkData, |
||||||
|
}) |
||||||
|
|
||||||
|
offset += chunkSize |
||||||
|
chunkIndex++ |
||||||
|
} |
||||||
|
|
||||||
|
return chunkList |
||||||
|
} |
||||||
|
|
||||||
|
function clearData() { |
||||||
|
fileList.value = [] |
||||||
|
} |
||||||
|
function onClone() { |
||||||
|
if (loading.value) { |
||||||
|
abortController.value?.abort() |
||||||
|
loading.value = false |
||||||
|
} |
||||||
|
clearData() |
||||||
|
} |
||||||
|
</script> |
||||||
|
|
||||||
|
<style lang="scss" scoped> |
||||||
|
:deep(.el-upload) { |
||||||
|
height: calc(100% - 50px); |
||||||
|
width: 100%; |
||||||
|
} |
||||||
|
:deep(.el-upload-dragger) { |
||||||
|
height: 100%; |
||||||
|
width: 100%; |
||||||
|
display: flex; |
||||||
|
flex-direction: column; |
||||||
|
justify-content: center; |
||||||
|
align-items: center; |
||||||
|
} |
||||||
|
</style> |
@ -0,0 +1,102 @@ |
|||||||
|
<template> |
||||||
|
<EdfsDialog :title="'数据迁移'" :is-show="visible" width="40%" @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> |
||||||
|
客户端IP: |
||||||
|
</div> |
||||||
|
<el-input v-model="form.clientIp" class="flex-1" placeholder="请输入客户端IP" /> |
||||||
|
</el-row> |
||||||
|
</div> |
||||||
|
</EdfsDialog> |
||||||
|
</template> |
||||||
|
|
||||||
|
<script setup lang="ts"> |
||||||
|
import dayjs from 'dayjs' |
||||||
|
import { getPubInitData, type PublishMsg } from '@/utils/zmq' |
||||||
|
import { cloneDeep } from 'lodash-es' |
||||||
|
|
||||||
|
import { useMessage } from '@/composables/useMessage' |
||||||
|
import type { ISite } from '@/api/module/transfer' |
||||||
|
const message = useMessage() |
||||||
|
|
||||||
|
|
||||||
|
const emit = defineEmits<{ |
||||||
|
'on-save': [msg: PublishMsg<'import'>, site: ISite] |
||||||
|
}>() |
||||||
|
|
||||||
|
|
||||||
|
const visible = ref(false) |
||||||
|
const fromData = { |
||||||
|
clientIp: '', |
||||||
|
} |
||||||
|
|
||||||
|
const curSite = ref<ISite>() |
||||||
|
const form = ref(cloneDeep(fromData)) |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
function open(item: ISite) { |
||||||
|
curSite.value = item |
||||||
|
visible.value = true |
||||||
|
} |
||||||
|
|
||||||
|
function onSave() { |
||||||
|
if (!verifyData()) return |
||||||
|
if (!curSite.value) { |
||||||
|
message.error('请选择设备') |
||||||
|
return |
||||||
|
} |
||||||
|
const params = [ |
||||||
|
`${form.value.clientIp}`, |
||||||
|
'', |
||||||
|
'', |
||||||
|
'', |
||||||
|
'', |
||||||
|
'', |
||||||
|
`${curSite.value.export_root_path}`, |
||||||
|
] |
||||||
|
const msg = getPubInitData<'import'>('import', params) |
||||||
|
emit('on-save', msg, curSite.value) |
||||||
|
close() |
||||||
|
} |
||||||
|
|
||||||
|
function close() { |
||||||
|
form.value = cloneDeep(fromData) |
||||||
|
curSite.value = undefined |
||||||
|
visible.value = false |
||||||
|
} |
||||||
|
|
||||||
|
function verifyData() { |
||||||
|
if (!form.value.clientIp) { |
||||||
|
message.error('请输入客户端IP') |
||||||
|
return false |
||||||
|
} |
||||||
|
|
||||||
|
return true |
||||||
|
} |
||||||
|
|
||||||
|
defineExpose({ |
||||||
|
open, |
||||||
|
}) |
||||||
|
</script> |
||||||
|
|
||||||
|
<style lang="scss" scoped> |
||||||
|
.el-row { |
||||||
|
@apply h-32px; |
||||||
|
column-gap: 8px; |
||||||
|
|
||||||
|
.label { |
||||||
|
color: var(--label-color); |
||||||
|
line-height: 33px; |
||||||
|
text-align: right; |
||||||
|
width: 110px; |
||||||
|
|
||||||
|
.require { |
||||||
|
color: red; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
</style> |
Loading…
Reference in new issue