14 changed files with 901 additions and 92 deletions
@ -0,0 +1,21 @@
@@ -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 @@
@@ -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 @@
@@ -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