这是基于vue-vben-admin 模板适用于abp Vnext的前端管理项目
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.
 
 
 
 
 
 

240 lines
6.7 KiB

<template>
<BasicModal
v-bind="$attrs"
@register="registerModal"
:title="L('Objects:UploadFile')"
:width="800"
:min-height="300"
>
<BasicTable @register="registerTable">
<template #toolbar>
<input ref="btnRef" style="display: none" />
<a-button type="primary" @click="handleSelect">{{ L('Upload:SelectFile') }}</a-button>
</template>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'size'">
<span>{{ fileSize(record.size) }}</span>
</template>
<template v-else-if="column.key === 'status'">
<Tag v-if="record.completed" color="green">{{ L('Upload:Completed') }}</Tag>
<Tooltip v-else-if="record.error" :title="record.errorMsg">
<Tag color="red">{{ L('Upload:Error') }}</Tag>
</Tooltip>
<Tag v-else-if="record.paused" color="orange">{{ L('Upload:Pause') }}</Tag>
<span v-else>{{ record.progress }} {{ averageSpeed(record.averageSpeed) }}</span>
</template>
<template v-else-if="column.key === 'action'">
<TableAction
:stop-button-propagation="true"
:actions="[
{
ifShow: !record.completed,
color: 'warning',
label: '',
icon: record.paused
? 'ant-design:caret-right-outlined'
: 'ant-design:pause-outlined',
onClick: record.paused
? handleResume.bind(null, record)
: handlePause.bind(null, record),
},
{
color: 'error',
label: '',
icon: 'ant-design:delete-outlined',
onClick: handleCancel.bind(null, record),
},
]"
/>
</template>
</template>
</BasicTable>
</BasicModal>
</template>
<script lang="ts" setup>
import { computed, ref, unref, onMounted, onUnmounted, watch } from 'vue';
import { useLocalization } from '/@/hooks/abp/useLocalization';
import { Tag, Tooltip } from 'ant-design-vue';
import { BasicModal, useModalInner } from '/@/components/Modal';
import { BasicTable, TableAction, useTable } from '/@/components/Table';
import { uploadUrl } from '/@/api/oss-management/objects';
import { useUserStoreWithOut } from '/@/store/modules/user';
import { Result } from '/#/axios';
import Uploader from 'simple-uploader.js';
const emits = defineEmits(['file:uploaded', 'register']);
let uploader: any = null;
const { L } = useLocalization(['AbpOssManagement', 'AbpUi']);
const bucket = ref('');
const path = ref('');
const btnRef = ref<any>();
const [registerModal] = useModalInner((data) => {
path.value = data.path;
bucket.value = data.bucket;
});
const fileList = ref<Recordable[]>([]);
const [registerTable] = useTable({
rowKey: 'id',
columns: [
{
title: 'id',
dataIndex: 'id',
width: 1,
ifShow: false,
},
{
title: L('DisplayName:Name'),
dataIndex: 'name',
align: 'left',
width: 300,
sorter: true,
},
{
title: L('DisplayName:Size'),
dataIndex: 'size',
align: 'left',
width: 100,
sorter: true,
},
{
title: L('DisplayName:Status'),
dataIndex: 'status',
align: 'left',
width: 'auto',
sorter: true,
},
],
dataSource: fileList,
pagination: false,
striped: false,
useSearchForm: false,
showTableSetting: false,
bordered: false,
showIndexColumn: false,
canResize: false,
immediate: false,
actionColumn: {
width: 120,
title: L('Actions'),
dataIndex: 'action',
},
});
onMounted(() => {
const userStore = useUserStoreWithOut();
uploader = new Uploader({
target: uploadUrl,
testChunks: false,
// 加重试机制防止网络波动
maxChunkRetries: 3,
chunkRetryInterval: null,
successStatuses: [200, 201, 202, 204, 205],
permanentErrors: [400, 401, 403, 404, 415, 500, 501],
headers: {
// TODO: 使用缓存存储令牌类型?
Authorization: `Bearer ${userStore.getToken}`,
},
processParams: (params: any) => {
params.bucket = unref(bucket);
params.path = unref(path);
return params;
},
});
// uploader.on('fileSuccess', _fileSuccess);
uploader.on('filesSubmitted', _filesSubmitted);
uploader.on('fileError', _fileError);
uploader.on('fileSuccess', _fileSuccess);
uploader.on('fileProgress', _fileProgress);
});
onUnmounted(() => {
// uploader.off('fileSuccess', _fileSuccess);
uploader.off('filesSubmitted', _filesSubmitted);
uploader.off('fileError', _fileError);
uploader.off('fileSuccess', _fileSuccess);
uploader.off('fileProgress', _fileProgress);
uploader = null;
});
watch(
() => unref(btnRef),
(btn) => {
uploader.assignBrowse(btn);
},
);
function _filesSubmitted(_, files) {
files.forEach((f) => {
f.paused = true;
f.progress = '0 %';
});
fileList.value.push(...files);
}
function _fileProgress(_, file, chunk) {
// 2024-09-29 处理上传失败的包装错误
if (chunk.processedState?.res) {
try {
const result = JSON.parse(chunk.processedState.res) as Result<any>;
if (result.code !== '0') {
file.error = true;
file.errorMsg = result.message;
file.pause();
}
} catch (error) {
console.log('upload error ---> ', error);
}
}
if (file._prevUploadedSize) {
file.progress = `${Math.floor((file._prevUploadedSize / file.size) * 100)} %`;
}
}
function _fileError(_, file, message) {
file.paused = true;
if (message) {
const formatedError = JSON.parse(message);
file.errorMsg = formatedError.error?.message;
}
}
function _fileSuccess(_, file) {
emits('file:uploaded', unref(bucket), unref(path), file.name);
}
function handleSelect() {
unref(btnRef)?.click();
}
function handleResume(record) {
if (record.error) {
record.retry();
record.errorMsg = '';
}
record.resume();
}
function handlePause(record) {
record.pause();
}
function handleCancel(record) {
record.cancel();
uploader.removeFile(record);
fileList.value = fileList.value.filter((f) => f.id !== record.id);
}
const fileSize = computed(() => {
return (size) => {
return Uploader.utils.formatSize(size);
};
});
const averageSpeed = computed(() => {
return (speed) => {
return `${Uploader.utils.formatSize(speed)} / s`;
};
});
</script>