|
@@ -0,0 +1,342 @@
|
|
|
+<template>
|
|
|
+ <a-modal
|
|
|
+ v-model:visible="visible"
|
|
|
+ :width="500"
|
|
|
+ :title="title"
|
|
|
+ @before-open="modalBeforeOpen"
|
|
|
+ >
|
|
|
+ <slot></slot>
|
|
|
+ <div class="import-box">
|
|
|
+ <div class="import-temp">
|
|
|
+ <span>模板下载:</span>
|
|
|
+ <div class="temp-btn">
|
|
|
+ <a v-if="downloadUrl" :href="downloadUrl" :download="dfilename">{{
|
|
|
+ dfilename
|
|
|
+ }}</a>
|
|
|
+ <a-button
|
|
|
+ v-else-if="downloadHandle"
|
|
|
+ type="text"
|
|
|
+ @click="downloadHandle"
|
|
|
+ >{{ dfilename }}</a-button
|
|
|
+ >
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="import-body">
|
|
|
+ <a-upload
|
|
|
+ v-if="visible"
|
|
|
+ ref="uploadRef"
|
|
|
+ draggable
|
|
|
+ :action="uploadUrl"
|
|
|
+ :headers="headers"
|
|
|
+ :max-size="maxSize"
|
|
|
+ :format="format"
|
|
|
+ :accept="accept"
|
|
|
+ :data="uploadDataDict"
|
|
|
+ :show-file-list="true"
|
|
|
+ :auto-upload="autoUpload"
|
|
|
+ :custom-request="upload"
|
|
|
+ :disabled="disabled"
|
|
|
+ :on-before-upload="handleBeforeUpload"
|
|
|
+ :file-list="customFileList"
|
|
|
+ :show-remove-button="false"
|
|
|
+ :show-retry-button="false"
|
|
|
+ @change="handleFileChange"
|
|
|
+ @error="handleError"
|
|
|
+ @success="handleSuccess"
|
|
|
+ >
|
|
|
+ </a-upload>
|
|
|
+ <p
|
|
|
+ v-if="result.message"
|
|
|
+ :class="[
|
|
|
+ `tips-info`,
|
|
|
+ {
|
|
|
+ 'tips-success': result.success,
|
|
|
+ 'tips-error': !result.success,
|
|
|
+ },
|
|
|
+ ]"
|
|
|
+ >
|
|
|
+ {{ result.message }}
|
|
|
+ </p>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <template v-if="!autoUpload" #footer>
|
|
|
+ <a-button
|
|
|
+ type="primary"
|
|
|
+ :disabled="loading || !canUpload"
|
|
|
+ @click="confirm"
|
|
|
+ >确认</a-button
|
|
|
+ >
|
|
|
+ <a-button @click="close">取消</a-button>
|
|
|
+ </template>
|
|
|
+ </a-modal>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script setup lang="ts">
|
|
|
+ import { computed, ref } from 'vue';
|
|
|
+ import { fileMD5 } from '@/utils/md5';
|
|
|
+ import { FileItem, Message, RequestOption } from '@arco-design/web-vue';
|
|
|
+ import axios from 'axios';
|
|
|
+
|
|
|
+ import useModal from '@/hooks/modal';
|
|
|
+
|
|
|
+ defineOptions({
|
|
|
+ name: 'ImportDialog',
|
|
|
+ });
|
|
|
+
|
|
|
+ /* modal */
|
|
|
+ const { visible, open, close } = useModal();
|
|
|
+ defineExpose({ open, close });
|
|
|
+
|
|
|
+ interface PromiseFunc {
|
|
|
+ (): Promise<boolean>;
|
|
|
+ }
|
|
|
+
|
|
|
+ const props = withDefaults(
|
|
|
+ defineProps<{
|
|
|
+ title: string;
|
|
|
+ uploadUrl: string;
|
|
|
+ format?: string[];
|
|
|
+ uploadData?: Record<string, any>;
|
|
|
+ maxSize?: number;
|
|
|
+ uploadFilename?: string;
|
|
|
+ autoUpload?: boolean;
|
|
|
+ disabled?: boolean;
|
|
|
+ uploadFileAlias?: string;
|
|
|
+ downloadUrl?: string;
|
|
|
+ downloadFilename?: string;
|
|
|
+ downloadHandle?: PromiseFunc;
|
|
|
+ beforeSubmitHandle?: PromiseFunc;
|
|
|
+ }>(),
|
|
|
+ {
|
|
|
+ title: '文件上传',
|
|
|
+ uploadUrl: '',
|
|
|
+ format: () => ['xls', 'xlsx'],
|
|
|
+ uploadData: () => {
|
|
|
+ return {};
|
|
|
+ },
|
|
|
+ maxSize: 20 * 1024 * 1024,
|
|
|
+ uploadFileAlias: 'file',
|
|
|
+ autoUpload: true,
|
|
|
+ disabled: false,
|
|
|
+ }
|
|
|
+ );
|
|
|
+
|
|
|
+ const emit = defineEmits([
|
|
|
+ 'uploading',
|
|
|
+ 'uploadError',
|
|
|
+ 'uploadSuccess',
|
|
|
+ 'validError',
|
|
|
+ ]);
|
|
|
+
|
|
|
+ const uploadRef = ref(null);
|
|
|
+ const canUpload = ref(false);
|
|
|
+ const uploadDataDict = ref({ md5: '' });
|
|
|
+ const headers = ref({});
|
|
|
+ const result = ref({ success: true, message: '' });
|
|
|
+ const loading = ref(false);
|
|
|
+ const customFileList = ref<FileItem[]>([]);
|
|
|
+
|
|
|
+ const dfilename = computed(() => {
|
|
|
+ if (props.downloadFilename) return props.downloadFilename;
|
|
|
+ return props.downloadUrl ? props.downloadUrl.split('/').pop() : '';
|
|
|
+ });
|
|
|
+ const accept = computed(() => {
|
|
|
+ return props.format.map((el) => `.${el}`).join();
|
|
|
+ });
|
|
|
+
|
|
|
+ async function confirm() {
|
|
|
+ loading.value = true;
|
|
|
+ if (props.beforeSubmitHandle) {
|
|
|
+ let handleResult = true;
|
|
|
+ await props.beforeSubmitHandle().catch(() => {
|
|
|
+ handleResult = false;
|
|
|
+ });
|
|
|
+ if (!handleResult) return;
|
|
|
+ }
|
|
|
+ uploadRef.value?.submit();
|
|
|
+ }
|
|
|
+
|
|
|
+ function checkFileFormat(fileType) {
|
|
|
+ const fileFormat = fileType.split('.').pop().toLocaleLowerCase();
|
|
|
+ return props.format.some((item) => item.toLocaleLowerCase() === fileFormat);
|
|
|
+ }
|
|
|
+
|
|
|
+ function handleFileChange(fileList: FileItem[]) {
|
|
|
+ customFileList.value = fileList.length ? fileList.slice(-1) : [];
|
|
|
+ const lastUid = customFileList.value[0]?.uid;
|
|
|
+ if (lastUid !== fileList[0].uid) {
|
|
|
+ result.value = {
|
|
|
+ success: true,
|
|
|
+ message: '',
|
|
|
+ };
|
|
|
+ }
|
|
|
+
|
|
|
+ if (props.autoUpload) return;
|
|
|
+
|
|
|
+ canUpload.value = customFileList.value[0]?.status === 'init';
|
|
|
+ }
|
|
|
+
|
|
|
+ async function handleBeforeUpload(file: File) {
|
|
|
+ uploadDataDict.value = {
|
|
|
+ ...props.uploadData,
|
|
|
+ filename: file.name,
|
|
|
+ };
|
|
|
+
|
|
|
+ if (file.size > props.maxSize) {
|
|
|
+ handleExceededSize();
|
|
|
+ return Promise.reject(result.value);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!checkFileFormat(file.name)) {
|
|
|
+ handleFormatError();
|
|
|
+ return Promise.reject(result.value);
|
|
|
+ }
|
|
|
+
|
|
|
+ const md5 = await fileMD5(file);
|
|
|
+ headers.value.md5 = md5;
|
|
|
+
|
|
|
+ if (props.autoUpload) loading.value = true;
|
|
|
+
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ async function upload(option: RequestOption) {
|
|
|
+ const { onProgress, onError, onSuccess, fileItem, data } = option;
|
|
|
+
|
|
|
+ const formData = new FormData();
|
|
|
+ Object.entries(data).forEach(([k, v]) => {
|
|
|
+ formData.append(k, v);
|
|
|
+ });
|
|
|
+ formData.append(props.uploadFileAlias, fileItem.file);
|
|
|
+ emit('uploading');
|
|
|
+
|
|
|
+ const res = await axios
|
|
|
+ .post(option.action, formData, {
|
|
|
+ headers: option.headers,
|
|
|
+ onUploadProgress: ({ loaded, total }) => {
|
|
|
+ onProgress(Math.floor((100 * loaded) / total));
|
|
|
+ },
|
|
|
+ })
|
|
|
+ .catch((error: Error) => {
|
|
|
+ onError(error);
|
|
|
+ });
|
|
|
+ if (!res) return;
|
|
|
+ onSuccess(res);
|
|
|
+ }
|
|
|
+
|
|
|
+ function handleError(error: Error) {
|
|
|
+ console.log(error);
|
|
|
+
|
|
|
+ canUpload.value = false;
|
|
|
+ loading.value = false;
|
|
|
+ result.value = {
|
|
|
+ success: false,
|
|
|
+ message: '上传失败',
|
|
|
+ };
|
|
|
+ emit('uploadError', result.value);
|
|
|
+ }
|
|
|
+ function handleSuccess(fileItem: FileItem) {
|
|
|
+ canUpload.value = false;
|
|
|
+ loading.value = false;
|
|
|
+ result.value = {
|
|
|
+ success: true,
|
|
|
+ message: '上传成功!',
|
|
|
+ };
|
|
|
+ Message.success('上传成功!');
|
|
|
+ emit('uploadSuccess', {
|
|
|
+ ...result.value,
|
|
|
+ filename: fileItem.name,
|
|
|
+ response: fileItem.response,
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ function handleFormatError() {
|
|
|
+ const content = `只支持文件格式为${props.format.join('/')}`;
|
|
|
+ result.value = {
|
|
|
+ success: false,
|
|
|
+ message: content,
|
|
|
+ };
|
|
|
+ loading.value = false;
|
|
|
+ Message.error(content);
|
|
|
+ emit('validError', result.value);
|
|
|
+ }
|
|
|
+ function handleExceededSize() {
|
|
|
+ const content = `文件大小不能超过${Math.floor(props.maxSize / 1024)}M`;
|
|
|
+ result.value = {
|
|
|
+ success: false,
|
|
|
+ message: content,
|
|
|
+ };
|
|
|
+ loading.value = false;
|
|
|
+ Message.error(content);
|
|
|
+ emit('validError', result.value);
|
|
|
+ }
|
|
|
+
|
|
|
+ function modalBeforeOpen() {
|
|
|
+ canUpload.value = false;
|
|
|
+ result.value = {
|
|
|
+ success: true,
|
|
|
+ message: '',
|
|
|
+ };
|
|
|
+ headers.value = { md5: '' };
|
|
|
+ loading.value = false;
|
|
|
+ uploadDataDict.value = {};
|
|
|
+ }
|
|
|
+</script>
|
|
|
+
|
|
|
+<style lang="less">
|
|
|
+ .import-box {
|
|
|
+ .import-temp {
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ margin-bottom: 10px;
|
|
|
+
|
|
|
+ > span {
|
|
|
+ flex-grow: 0;
|
|
|
+ flex-shrink: 0;
|
|
|
+ line-height: 20px;
|
|
|
+ display: block;
|
|
|
+ }
|
|
|
+
|
|
|
+ .temp-btn {
|
|
|
+ flex-grow: 2;
|
|
|
+ text-align: left;
|
|
|
+
|
|
|
+ > a {
|
|
|
+ flex-grow: 2;
|
|
|
+ line-height: 20px;
|
|
|
+ color: var(--color-primary);
|
|
|
+
|
|
|
+ &:hover {
|
|
|
+ text-decoration: underline;
|
|
|
+ opacity: 0.8;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ .arco-btn {
|
|
|
+ line-height: 20px;
|
|
|
+ height: auto;
|
|
|
+ padding: 0;
|
|
|
+ background: transparent;
|
|
|
+
|
|
|
+ &:hover {
|
|
|
+ text-decoration: underline;
|
|
|
+ opacity: 0.8;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .arco-upload-list-item {
|
|
|
+ margin-top: 8px !important;
|
|
|
+ }
|
|
|
+ .arco-upload-progress {
|
|
|
+ display: none;
|
|
|
+ }
|
|
|
+
|
|
|
+ .tips-info {
|
|
|
+ max-height: 100px;
|
|
|
+ overflow: hidden;
|
|
|
+ margin-top: 5px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+</style>
|