1.上传附件接口联调

sws 2024-08-15
v1.0.0
sws 2024-08-15 19:06:56 +08:00
parent 97526f9713
commit e965ac6eb7
9 changed files with 1849 additions and 52 deletions

View File

@ -1,4 +1,5 @@
import request from '@/utils/request';
import { AxiosRequestConfig } from 'axios';
class UploadAPI {
/** 分类查询接口*/
@ -58,11 +59,15 @@ class UploadAPI {
});
}
// 附件上传
static uploadAttachment(data: any) {
static uploadAttachment(data: any, progress: any) {
return request({
url: `diyapi/attachmentupload`,
method: 'post',
data,
headers: {
'Content-Type': 'multipart/form-data',
},
onUploadProgress: progress,
});
}
}

View File

@ -49,7 +49,7 @@
<el-button @click="mult_del_event">{{ upload_type_name }}</el-button>
<!-- <el-cascader :show-all-levels="false" clearable></el-cascader> -->
<div class="right-classify ml-12">
<transform-category :data="type_data" :check-img-ids="check_img_ids" :placeholder="upload_type_name + '移动至'"></transform-category>
<transform-category :data="type_data_list" :check-img-ids="check_img_ids" :placeholder="upload_type_name + '移动至'"></transform-category>
</div>
</div>
<div class="right-search">
@ -248,7 +248,8 @@ watch(
() => dialog_visible.value,
(val) => {
if (val) {
type_data.value = upload_store.category;
type_data_list.value = upload_store.category;
type_data.value = [all_tree, ...upload_store.category];
get_attachment_list();
}
}
@ -257,14 +258,14 @@ watch(
//
const ext_img_name_list = ref(['.png', '.jpg', '.jpeg', '.bmp', '.webp', '.gif']);
const ext_video_name_list = ref(['.flv', '.swf', '.mkv', '.avi', '.rm', '.rmvb', '.mpeg', '.mpg', '.ogg', '.ogv', '.mov', '.wmv', '.mp4', '.webm']);
const ext_file_name_list = ref(['.png', 'jpg', 'jpeg', 'bmp', 'webp', 'gif', '.flv', '.swf', '.mkv', '.avi', '.rm', '.rmvb', '.mpeg', '.mpg', '.ogg', '.ogv', '.mov', '.wmv', '.mp4', '.webm', '.mp3', '.csv', '.wav', '.mid', '.cab', '.iso', '.ofd', '.xml', '.rar', '.zip', '.tar', '.gz', '.7z', '.bz2', '.doc', '.docx', '.xls', '.xlsx', '.ppt', '.pptx', '.pdf', '.txt', '.md', '.vsd']);
const ext_file_name_list = ref(['.png', '.jpg', '.jpeg', '.bmp', '.webp', '.gif', '.flv', '.swf', '.mkv', '.avi', '.rm', '.rmvb', '.mpeg', '.mpg', '.ogg', '.ogv', '.mov', '.wmv', '.mp4', '.webm', '.mp3', '.csv', '.wav', '.mid', '.cab', '.iso', '.ofd', '.xml', '.rar', '.zip', '.tar', '.gz', '.7z', '.bz2', '.doc', '.docx', '.xls', '.xlsx', '.ppt', '.pptx', '.pdf', '.txt', '.md', '.vsd','.sql']);
const ext_file_name_list_map = ref([
{ type: '.png', icon: 'error-img' },
{ type: 'jpg', icon: 'error-img' },
{ type: 'jpeg', icon: 'error-img' },
{ type: 'bmp', icon: 'error-img' },
{ type: 'webp', icon: 'error-img' },
{ type: 'gif', icon: 'error-img' },
{ type: '.jpg', icon: 'error-img' },
{ type: '.jpeg', icon: 'error-img' },
{ type: '.bmp', icon: 'error-img' },
{ type: '.webp', icon: 'error-img' },
{ type: '.gif', icon: 'error-img' },
{ type: '.flv', icon: 'video' },
{ type: '.swf', icon: 'video' },
{ type: '.mkv', icon: 'video' },
@ -287,6 +288,7 @@ const ext_file_name_list_map = ref([
{ type: '.iso', icon: 'file' },
{ type: '.ofd', icon: 'file' },
{ type: '.xml', icon: 'file' },
{ type: '.sql', icon: 'file' },
{ type: '.rar', icon: 'zip' },
{ type: '.zip', icon: 'zip' },
{ type: '.tar', icon: 'zip' },
@ -341,21 +343,22 @@ const filter_node = (value: string, data: any): boolean => {
return data.name.indexOf(value) !== -1;
};
const type_data = ref<Tree[]>([]);
const all_tree = {
id: '',
pid: '',
name: '全部',
path: '',
is_enable: 1,
sort: '',
};
const type_data_list = ref<Tree[]>([]);
//
const get_tree = () => {
UploadAPI.getTree().then((res) => {
const all_tree = {
id: '',
pid: '',
name: '全部',
path: '',
is_enable: 1,
sort: '',
};
// all_treeres.data.category_listtype_data.value,all_tree
type_data.value = [all_tree, ...res.data.category_list];
upload_store.set_category(type_data.value);
type_data_list.value = res.data.category_list;
upload_store.set_category(type_data_list.value);
});
};

View File

@ -0,0 +1,570 @@
<!-- 上传组件 -->
<template>
<el-dialog v-model="dialogVisible" class="radius-lg" width="1168" append-to-body>
<template #header>
<div class="title center re">
<div class="tc size-16 fw">{{ upload_type_name }}上传</div>
</div>
</template>
<div class="upload-content pa-20" @paste="handle_paste">
<el-form ref="ruleFormRef" :model="form" :rules="rules" label-width="85" status-icon>
<el-form-item label="上传方式">
<el-radio-group v-model="form.type" @change="upload_type_change">
<el-radio value="loc">本地上传</el-radio>
<el-radio value="scan">扫码上传</el-radio>
<el-radio v-if="upload_type !== 'file'" value="web"></el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="上传至分组" prop="category_id">
<div class="form-item-width">
<el-cascader v-model="form.category_id" class="w" :options="cascader_data" placeholder="请选择" :show-all-levels="false" @change="category_id_change"></el-cascader>
</div>
</el-form-item>
<template v-if="form.type == 'loc'">
<div class="flex-row jc-sb align-c mt-30">
<div class="flex-row">
<el-upload ref="fileUpload1" v-model:file-list="file_list" multiple action="#" :accept="exts_text" :auto-upload="false" :show-file-list="false" :on-change="upload_change" :before-upload="before_upload" :limit="limit" :on-exceed="handle_exceed">
<template #trigger>
<el-button @click="folder_mode(false)"> {{ upload_type_name }} </el-button>
<el-button @click="folder_mode(true)"> </el-button>
</template>
</el-upload>
</div>
<el-button @click="clear_list_event"></el-button>
</div>
<div class="table mt-10">
<div class="table-header">
<div class="table-row">
<div class="table-cell">文件名</div>
<div class="table-cell">文件大小</div>
<div class="table-cell">上传状态</div>
<div class="table-cell-oprate">操作</div>
</div>
</div>
<div id="dropzone" @dragover.prevent="handle_drag_in" @dragenter="handle_drag_in" @dragleave="handle_drag_leave" @drop.prevent="handle_drop">
<el-scrollbar v-if="!is_dragging && form.file.length > 0" height="341px">
<div class="table-body">
<div v-for="(item, index) in form.file" :key="item.file.name + item.file.size" class="table-row">
<div class="table-cell">
<el-image :src="file_to_base64(item.file)" class="preview-img radius-sm" fit="contain">
<template #error>
<div class="bg-f5 img flex-row jc-c align-c radius h w">
<icon name="error-img" size="12"></icon>
</div>
</template>
</el-image>
<div class="desc">{{ item.file.name }}</div>
</div>
<div class="table-cell">{{ annex_size_to_unit(item.file.size) }}</div>
<div class="table-cell">{{ item.status }}</div>
<div class="table-cell-oprate" @click="del_upload(index)"></div>
</div>
</div>
</el-scrollbar>
<div v-show="is_dragging || form.file.length < 1" class="folder-upload mt-20" :class="is_dragging ? 'active' : ''">
<el-upload ref="fileUpload2" v-model:file-list="file_list" :accept="exts_text" multiple action="#" :auto-upload="false" :show-file-list="false" :on-change="upload_change" :before-upload="before_upload" :limit="limit" :on-exceed="handle_exceed">
<div class="flex-col jc-c align-c">
<icon name="add" size="60" color="#dbeef6"></icon>
<p class="size-18 cr-c fw">请将需要上传的文件/文件夹拖到此处或粘贴</p>
</div>
</el-upload>
</div>
</div>
</div>
</template>
<template v-else-if="form.type == 'scan'">
<el-form-item label="二维码" class="mb-10">
<qrcode :src="form.qrcode" :is-mask="is_mask"></qrcode>
</el-form-item>
<div class="table">
<div class="table-header">
<div class="table-row">
<div class="table-cell">文件名</div>
<div class="table-cell">文件大小</div>
<div class="table-cell-oprate">操作</div>
</div>
</div>
<el-scrollbar height="224px">
<div class="table-body">
<div v-for="(item, index) in scan_file_list" :key="item.name + item.size" class="table-row">
<div class="table-cell">
<el-image :src="item.url" class="preview-img radius-sm" fit="contain">
<template #error>
<div class="bg-f5 img flex-row jc-c align-c radius h w">
<icon name="error-img" size="12"></icon>
</div>
</template>
</el-image>
<div class="desc">{{ item.name }}</div>
</div>
<div class="table-cell">{{ annex_size_to_unit(item.size) }}</div>
<div class="table-cell-oprate" @click="del_already_upload(index)"></div>
</div>
</div>
</el-scrollbar>
</div>
</template>
<template v-else-if="form.type == 'web'">
<el-form-item label="网络图片">
<div class="flex-row align-c gap-10">
<el-input v-model="form.web_image" class="form-item-width" placeholder="请输入网络图片地址" />
<div class="c-pointer cr-primary size-12" @click="extract_images"></div>
</div>
</el-form-item>
</template>
</el-form>
</div>
<template #footer>
<span class="dialog-footer">
<el-button class="plr-28 ptb-10" @click="close_dialog"></el-button>
<el-button v-if="form.type == 'loc'" class="plr-28 ptb-10" type="primary" @click="submit_event(ruleFormRef)"></el-button>
<el-button v-else-if="form.type == 'scan'" class="plr-28 ptb-10" type="primary" @click="close_dialog"></el-button>
<el-button v-else-if="form.type == 'web'" class="plr-28 ptb-10" type="primary" @click="close_all_dialog"></el-button>
</span>
</template>
</el-dialog>
</template>
<script lang="ts" setup>
import UploadAPI, { Tree } from '@/api/upload';
import { uploadrStore } from '@/store';
const upload_store = uploadrStore();
import type { UploadFile, UploadFiles, UploadUserFile, FormRules, FormInstance } from 'element-plus';
import { annex_size_to_unit, ext_name } from '@/utils';
/**
* @description: 图片执行上传弹窗
* @param close{String} 默认值
* @return {*} close
*/
const props = defineProps({
type: {
type: String,
default: 'img', // img/video/file
},
limit: {
type: Number,
default: 10000,
},
exts: {
type: Array,
default: () => ['.png', '.jpg', '.jpeg', '.bmp', '.webp', '.gif'],
},
fileSize: {
type: Number,
default: 1024 * 1024,
},
});
const dialogVisible = defineModel({ type: Boolean, default: false });
//
const upload_type = ref(props.type);
// name
const upload_type_name = computed(() => {
return upload_type.value === 'img' ? '图片' : upload_type.value === 'video' ? '视频' : '文件';
});
//
const exts_text = ref(props.exts.join(','));
const file_list = ref<UploadUserFile[]>([]);
interface fileData {
file: File;
status: string;
}
interface formData {
type: string;
category_id: string;
file: fileData[];
qrcode: string;
web_image: string;
}
const ruleFormRef = ref<FormInstance>();
const form = ref<formData>({
type: 'loc',
category_id: '',
file: [],
qrcode: '11223344',
web_image: '',
});
const rules = reactive<FormRules>({
category_id: [{ required: true, trigger: 'change', message: '请选择分组' }],
});
//
const is_mask = ref(true);
const timer = ref<number | null>(null);
//
const upload_type_change = (type: any) => {
//
if (timer.value) {
// timer.value null undefined
clearTimeout(timer.value);
timer.value = null; //
}
//
if (type === 'scan') {
timer.value = setInterval(() => {
console.log('timer-----定时调用');
//
}, 3000);
}
};
//
const category_id_change = (val: any) => {
console.log(val);
if (val && val.length > 0) {
is_mask.value = false;
}
};
// 使 // //
const cascader_data = computed(() => {
return upload_store.category.map((tree: Tree) => ({
value: tree.id,
label: tree.name,
children: tree.items?.map((item: Tree) => ({
value: item.id,
label: item.name,
})),
}));
});
//#region -----------------------------------------------start
//
const state = reactive({
uploadEle: null as HTMLInputElement | null,
uploadList: [],
});
const folder_mode = (type: boolean) => {
if (!state.uploadEle) {
state.uploadEle = document.querySelector('.el-upload__input') as HTMLInputElement;
}
nextTick(() => {
(state.uploadEle as HTMLInputElement).webkitdirectory = type;
// console.log(state.uploadEle);
});
};
//
const upload_change = async (uploadFile: UploadFile, uploadFiles: UploadFiles) => {
// console.log('', uploadFile, uploadFiles);
// //
const results = uploadFiles.flat(Infinity).filter((f: any) => validExt(f.name) && validSize(f.size));
const new_upload_files = results.filter((item: UploadFile) => {
return !form.value.file.find((item2: fileData) => {
return item2.file.name === item.name && item2.file.size === item.size;
});
});
// form.file
new_upload_files.forEach((item: UploadFile) => {
// item.status = 'ready';
const new_file_obj = {
status: '等待上传',
file: item.raw as File,
};
form.value.file.push(new_file_obj);
console.log(form.value.file);
});
};
// false Promise reject
const validExt = (name: string) => props.exts.includes(ext_name(name));
const validSize = (size: number) => size <= props.fileSize;
//
const before_upload = (file: any) => {
//
if (validExt(file.name) && validSize(file.size)) {
console.log('允许上传');
return true; //
} else {
console.log('不允许上传');
return false; //
}
};
//
const handle_exceed = (files: any, fileList: any) => {
ElMessageBox.alert(`最多上传 ${props.limit} 个文件!`);
};
//
const clear_list_event = () => {
form.value.file = [];
file_list.value = [];
};
const is_dragging = ref(false);
// dropzone DOM便dragLeave
const dropzone = ref<HTMLElement | null>(null);
onMounted(() => {
dropzone.value = document.getElementById('dropzone') as HTMLElement;
});
//
const handle_drag_in = (e: DragEvent) => {
e.preventDefault();
e.stopPropagation();
is_dragging.value = true;
};
//
const handle_drag_leave = (event: DragEvent) => {
event.preventDefault();
event.stopPropagation();
// 使dropzone
const leaveTimer = setTimeout(() => {
// dropzone
if (!document.getElementById('dropzone')?.contains(event.relatedTarget as Node)) {
is_dragging.value = false;
}
}, 50); // 50
// dropzone
event.currentTarget?.addEventListener(
'dragenter',
() => {
clearTimeout(leaveTimer);
},
{ once: true }
);
};
//
const handle_drop = async (event: any) => {
event.preventDefault();
event.stopPropagation();
is_dragging.value = false;
let results = await Promise.all([...event.dataTransfer.items].map((item) => handle_entry(item.webkitGetAsEntry())));
//
results = results.flat(Infinity).filter((f: any) => validExt(f.name) && validSize(f.size));
//
if (results.length + form.value.file.length <= props.limit) {
results.forEach((file: any) => {
if (!form.value.file.some((item: any) => item.file.name === file.name && item.file.size === file.size)) {
const new_file_obj = {
status: '等待上传',
file: file,
};
const new_file_obj_upload = {
name: file.name,
url: 'xxx',
file: file,
};
form.value.file.push(new_file_obj);
file_list.value.push(new_file_obj_upload);
}
});
} else {
ElMessageBox.alert(`最多上传 ${props.limit} 个文件!`);
}
};
/**
* 处理文件/目录并返回一个Promise
* 此函数旨在处理文件/目录无论是文件还是目录如果是文件则直接以文件形式解析并解决Promise如果是目录则递归处理目录中的所有文件
* @param {any} entry 文件/目录可以是文件或目录
* @returns {Promise<any[]>} 返回一个Promise解析为一个数组包含所有条目的文件对象
*/
const handle_entry = (entry: any) => {
return new Promise((resolve) => {
// entry
if (entry.isFile) {
// Promise
entry.file(resolve);
return;
}
// entry
const dirReader = entry.createReader();
dirReader.readEntries(async (entries: any) => {
// Promise.all
resolve(await Promise.all(entries.map(handle_entry)));
});
});
};
//
const del_upload = (index: number) => {
//
form.value.file.splice(index, 1);
file_list.value.splice(index, 1);
};
//
const handle_paste = (event: any) => {
console.log(event);
//
const files = event.clipboardData.files;
//
const results = [...files].filter((f: any) => validExt(f.name) && validSize(f.size));
//
if (results.length + form.value.file.length <= props.limit) {
console.log(results);
results.forEach((file: any) => {
//
//
if (!form.value.file.some((item: any) => item.file.name === file.name && item.file.size === file.size)) {
const new_file_obj = {
status: '等待上传',
file: file,
};
const new_file_obj_upload = {
name: file.name,
url: 'xxx',
file: file,
};
form.value.file.push(new_file_obj);
file_list.value.push(new_file_obj_upload);
} else {
ElMessageBox.alert(`文件 ${file.name} 已存在!`);
}
});
} else {
ElMessageBox.alert(`最多上传 ${props.limit} 个文件!`);
}
};
//
const submit_event = async (formEl: FormInstance | undefined) => {
if (!formEl) return;
await formEl.validate((valid, fields) => {
if (valid) {
const formData = new FormData();
formData.append('type', props.type == 'img' ? 'image' : props.type == 'video' ? 'video' : props.type == 'file' ? 'file' : '');
formData.append('category_id', form.value.category_id);
form.value.file.forEach((item: any) => {
formData.append('upfile', item.file);
item.status = '上传中';
});
// UploadAPI.uploadAttachment(formData).then((res) => {
// ElMessage.success('');
// form.value.file.forEach((item: any) => {
// item.status = '';
// });
// });
} else {
console.log('error submit!', fields);
}
});
};
//#endregion -----------------------------------------------end
//#region -----------------------------------------------start
interface scanFile {
name: string;
url: string;
size: number;
}
const scan_file_list = ref<scanFile[]>([
{ name: '1', url: '1', size: 0 },
{ name: '2', url: '2', size: 0 },
{ name: '3', url: '3', size: 0 },
{ name: '4', url: '4', size: 0 },
{ name: '5', url: '5', size: 0 },
]);
//
const del_already_upload = (index: number) => {
//
scan_file_list.value.splice(index, 1);
//
};
//#endregion -----------------------------------------------end
//#region -----------------------------------------------start
//
const extract_images = () => {
// 线
ElMessage({
type: 'success',
message: '提取成功!',
});
};
const emit = defineEmits(['close']);
//
const close_all_dialog = () => {
const new_form = JSON.parse(JSON.stringify(form.value));
emit('close', new_form);
close_dialog();
};
//#endregion -----------------------------------------------end
// filebase64
const file_to_base64 = (file: any) => {
return URL.createObjectURL(file);
};
//
const close_dialog = () => {
dialogVisible.value = false;
form.value = {
type: 'loc',
category_id: '',
file: [],
qrcode: '',
web_image: '',
};
scan_file_list.value = [];
// timer.value null undefined
if (timer.value !== null) {
clearTimeout(timer.value);
}
timer.value = null; //
};
</script>
<style lang="scss" scoped>
.upload-content {
height: 57.4rem;
gap: 4.5rem;
.table {
width: 100%;
height: 30rem;
.table-header,
.table-body {
.table-row {
display: flex;
width: 100%;
border-bottom: 0.1rem solid #eee;
color: #999;
font-size: 1.4rem;
.table-cell {
flex: 1;
padding: 1rem;
color: #666;
display: flex;
align-items: center;
word-break: break-all;
gap: 1rem;
.preview-img {
width: 2.8rem;
height: 2.8rem;
}
.desc {
flex: 1;
width: 0;
}
}
.table-cell-oprate {
padding: 1rem;
width: 5rem;
cursor: pointer;
display: flex;
align-items: center;
gap: 1rem;
word-break: break-all;
}
}
}
.table-body {
.table-cell,
.table-cell-oprate {
padding: 1.5rem 1rem !important;
}
.table-cell-oprate {
color: $cr-primary;
}
}
.folder-upload {
background: #fafcff;
border-radius: 0.2rem;
border: 0.1rem dashed #afdafa;
height: 32rem;
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
.active {
background-color: #eef5ff;
border-color: #35a9ff;
}
}
}
.form-item-width {
width: 33.5rem !important;
}
}
</style>

View File

@ -0,0 +1,583 @@
<!-- 上传组件 -->
<template>
<el-dialog v-model="dialogVisible" class="radius-lg" width="1168" append-to-body>
<template #header>
<div class="title center re">
<div class="tc size-16 fw">{{ upload_type_name }}上传</div>
</div>
</template>
<div class="upload-content pa-20" @paste="handle_paste">
<el-form ref="ruleFormRef" :model="form" :rules="rules" label-width="85" status-icon>
<el-form-item label="上传方式">
<el-radio-group v-model="form.type" @change="upload_type_change">
<el-radio value="loc">本地上传</el-radio>
<el-radio value="scan">扫码上传</el-radio>
<el-radio v-if="upload_type !== 'file'" value="web"></el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="上传至分组" prop="category_id">
<div class="form-item-width">
<el-cascader v-model="form.category_id" class="w" :options="cascader_data" placeholder="请选择" :show-all-levels="false" @change="category_id_change"></el-cascader>
</div>
</el-form-item>
<template v-if="form.type == 'loc'">
<div class="flex-row jc-sb align-c mt-30">
<div class="flex-row">
<el-upload ref="fileUpload1" v-model:file-list="file_list" multiple action="#" :accept="exts_text" :auto-upload="false" :show-file-list="false" :on-change="upload_change" :before-upload="before_upload" :limit="limit" :on-exceed="handle_exceed">
<template #trigger>
<el-button @click="folder_mode(false)"> {{ upload_type_name }} </el-button>
<el-button @click="folder_mode(true)"> </el-button>
</template>
</el-upload>
</div>
<el-button @click="clear_list_event"></el-button>
</div>
<div class="table mt-10">
<div class="table-header">
<div class="table-row">
<div class="table-cell">文件名</div>
<div class="table-cell">文件大小</div>
<div class="table-cell">上传状态</div>
<div class="table-cell-oprate">操作</div>
</div>
</div>
<div id="dropzone" @dragover.prevent="handle_drag_in" @dragenter="handle_drag_in" @dragleave="handle_drag_leave" @drop.prevent="handle_drop">
<el-scrollbar v-if="!is_dragging && form.file.length > 0" height="341px">
<div class="table-body">
<div v-for="(item, index) in form.file" :key="item.file.name + item.file.size" class="table-row">
<div class="table-cell">
<el-image :src="file_to_base64(item.file)" class="preview-img radius-sm" fit="contain">
<template #error>
<div class="bg-f5 img flex-row jc-c align-c radius h w">
<icon name="error-img" size="12"></icon>
</div>
</template>
</el-image>
<div class="desc">{{ item.file.name }}</div>
</div>
<div class="table-cell">{{ annex_size_to_unit(item.file.size) }}</div>
<div class="table-cell">{{ item.status }}</div>
<div class="table-cell-oprate" @click="del_upload(index)"></div>
</div>
</div>
</el-scrollbar>
<div v-show="is_dragging || form.file.length < 1" class="folder-upload mt-20" :class="is_dragging ? 'active' : ''">
<el-upload ref="fileUpload2" v-model:file-list="file_list" :accept="exts_text" multiple action="#" :auto-upload="false" :show-file-list="false" :on-change="upload_change" :before-upload="before_upload" :limit="limit" :on-exceed="handle_exceed">
<div class="flex-col jc-c align-c">
<icon name="add" size="60" color="#dbeef6"></icon>
<p class="size-18 cr-c fw">请将需要上传的文件/文件夹拖到此处或粘贴</p>
</div>
</el-upload>
</div>
</div>
</div>
</template>
<template v-else-if="form.type == 'scan'">
<el-form-item label="二维码" class="mb-10">
<qrcode :src="form.qrcode" :is-mask="is_mask"></qrcode>
</el-form-item>
<div class="table">
<div class="table-header">
<div class="table-row">
<div class="table-cell">文件名</div>
<div class="table-cell">文件大小</div>
<div class="table-cell-oprate">操作</div>
</div>
</div>
<el-scrollbar height="224px">
<div class="table-body">
<div v-for="(item, index) in scan_file_list" :key="item.name + item.size" class="table-row">
<div class="table-cell">
<el-image :src="item.url" class="preview-img radius-sm" fit="contain">
<template #error>
<div class="bg-f5 img flex-row jc-c align-c radius h w">
<icon name="error-img" size="12"></icon>
</div>
</template>
</el-image>
<div class="desc">{{ item.name }}</div>
</div>
<div class="table-cell">{{ annex_size_to_unit(item.size) }}</div>
<div class="table-cell-oprate" @click="del_already_upload(index)"></div>
</div>
</div>
</el-scrollbar>
</div>
</template>
<template v-else-if="form.type == 'web'">
<el-form-item label="网络图片">
<div class="flex-row align-c gap-10">
<el-input v-model="form.web_image" class="form-item-width" placeholder="请输入网络图片地址" />
<div class="c-pointer cr-primary size-12" @click="extract_images"></div>
</div>
</el-form-item>
</template>
</el-form>
</div>
<template #footer>
<span class="dialog-footer">
<el-button class="plr-28 ptb-10" @click="close_dialog"></el-button>
<el-button v-if="form.type == 'loc'" class="plr-28 ptb-10" type="primary" @click="submit_event(ruleFormRef)"></el-button>
<el-button v-else-if="form.type == 'scan'" class="plr-28 ptb-10" type="primary" @click="close_dialog"></el-button>
<el-button v-else-if="form.type == 'web'" class="plr-28 ptb-10" type="primary" @click="close_all_dialog"></el-button>
</span>
</template>
</el-dialog>
</template>
<script lang="ts" setup>
import UploadAPI, { Tree } from '@/api/upload';
import { uploadrStore } from '@/store';
const upload_store = uploadrStore();
import type { UploadFile, UploadFiles, UploadUserFile, FormRules, FormInstance } from 'element-plus';
import { annex_size_to_unit, ext_name } from '@/utils';
/**
* @description: 图片执行上传弹窗
* @param close{String} 默认值
* @return {*} close
*/
const props = defineProps({
type: {
type: String,
default: 'img', // img/video/file
},
limit: {
type: Number,
default: 10000,
},
exts: {
type: Array,
default: () => ['.png', '.jpg', '.jpeg', '.bmp', '.webp', '.gif'],
},
fileSize: {
type: Number,
default: 1024 * 1024 * 1024,
},
});
const dialogVisible = defineModel({ type: Boolean, default: false });
//
const upload_type = ref(props.type);
// name
const upload_type_name = computed(() => {
return upload_type.value === 'img' ? '图片' : upload_type.value === 'video' ? '视频' : '文件';
});
//
const exts_text = ref(props.exts.join(','));
const file_list = ref<UploadUserFile[]>([]);
interface fileData {
file: File;
status: string;
progress: number;
}
interface formData {
type: string;
category_id: string;
file: fileData[];
qrcode: string;
web_image: string;
}
const ruleFormRef = ref<FormInstance>();
const form = ref<formData>({
type: 'loc',
category_id: '',
file: [],
qrcode: '11223344',
web_image: '',
});
const rules = reactive<FormRules>({
category_id: [{ required: true, trigger: 'change', message: '请选择分组' }],
});
//
const is_mask = ref(true);
const timer = ref<number | null>(null);
//
const upload_type_change = (type: any) => {
//
if (timer.value) {
// timer.value null undefined
clearTimeout(timer.value);
timer.value = null; //
}
//
if (type === 'scan') {
timer.value = setInterval(() => {
console.log('timer-----定时调用');
//
}, 3000);
}
};
//
const category_id_change = (val: any) => {
console.log(val);
if (val && val.length > 0) {
is_mask.value = false;
}
};
// 使 // //
const cascader_data = computed(() => {
return upload_store.category.map((tree: Tree) => ({
value: tree.id,
label: tree.name,
children: tree.items?.map((item: Tree) => ({
value: item.id,
label: item.name,
})),
}));
});
//#region -----------------------------------------------start
//
const state = reactive({
uploadEle: null as HTMLInputElement | null,
uploadList: [],
});
const folder_mode = (type: boolean) => {
if (!state.uploadEle) {
state.uploadEle = document.querySelector('.el-upload__input') as HTMLInputElement;
}
nextTick(() => {
(state.uploadEle as HTMLInputElement).webkitdirectory = type;
// console.log(state.uploadEle);
});
};
//
const upload_change = async (uploadFile: UploadFile, uploadFiles: UploadFiles) => {
// console.log('', uploadFile, uploadFiles);
// //
const results = uploadFiles.flat(Infinity).filter((f: any) => validExt(f.name) && validSize(f.size));
const new_upload_files = results.filter((item: UploadFile) => {
return !form.value.file.find((item2: fileData) => {
return item2.file.name === item.name && item2.file.size === item.size;
});
});
// form.file
new_upload_files.forEach((item: UploadFile) => {
// item.status = 'ready';
const new_file_obj = {
status: '等待上传',
progress: 0,
file: item.raw as File,
};
form.value.file.push(new_file_obj);
});
};
// false Promise reject
const validExt = (name: string) => props.exts.includes(ext_name(name));
const validSize = (size: number) => size <= props.fileSize;
//
const before_upload = (file: any) => {
//
if (validExt(file.name) && validSize(file.size)) {
console.log('允许上传');
return true; //
} else {
console.log('不允许上传');
return false; //
}
};
//
const handle_exceed = (files: any, fileList: any) => {
ElMessageBox.alert(`最多上传 ${props.limit} 个文件!`);
};
//
const clear_list_event = () => {
form.value.file = [];
file_list.value = [];
};
const is_dragging = ref(false);
// dropzone DOM便dragLeave
const dropzone = ref<HTMLElement | null>(null);
onMounted(() => {
dropzone.value = document.getElementById('dropzone') as HTMLElement;
});
//
const handle_drag_in = (e: DragEvent) => {
e.preventDefault();
e.stopPropagation();
is_dragging.value = true;
};
//
const handle_drag_leave = (event: DragEvent) => {
event.preventDefault();
event.stopPropagation();
// 使dropzone
const leaveTimer = setTimeout(() => {
// dropzone
if (!document.getElementById('dropzone')?.contains(event.relatedTarget as Node)) {
is_dragging.value = false;
}
}, 50); // 50
// dropzone
event.currentTarget?.addEventListener(
'dragenter',
() => {
clearTimeout(leaveTimer);
},
{ once: true }
);
};
//
const handle_drop = async (event: any) => {
event.preventDefault();
event.stopPropagation();
is_dragging.value = false;
let results = await Promise.all([...event.dataTransfer.items].map((item) => handle_entry(item.webkitGetAsEntry())));
//
results = results.flat(Infinity).filter((f: any) => validExt(f.name) && validSize(f.size));
//
if (results.length + form.value.file.length <= props.limit) {
results.forEach((file: any) => {
if (!form.value.file.some((item: any) => item.file.name === file.name && item.file.size === file.size)) {
const new_file_obj = {
status: '等待上传',
progress: 0,
file: file,
};
const new_file_obj_upload = {
name: file.name,
url: 'xxx',
file: file,
};
form.value.file.push(new_file_obj);
file_list.value.push(new_file_obj_upload);
}
});
} else {
ElMessageBox.alert(`最多上传 ${props.limit} 个文件!`);
}
};
/**
* 处理文件/目录并返回一个Promise
* 此函数旨在处理文件/目录无论是文件还是目录如果是文件则直接以文件形式解析并解决Promise如果是目录则递归处理目录中的所有文件
* @param {any} entry 文件/目录可以是文件或目录
* @returns {Promise<any[]>} 返回一个Promise解析为一个数组包含所有条目的文件对象
*/
const handle_entry = (entry: any) => {
return new Promise((resolve) => {
// entry
if (entry.isFile) {
// Promise
entry.file(resolve);
return;
}
// entry
const dirReader = entry.createReader();
dirReader.readEntries(async (entries: any) => {
// Promise.all
resolve(await Promise.all(entries.map(handle_entry)));
});
});
};
//
const del_upload = (index: number) => {
//
form.value.file.splice(index, 1);
file_list.value.splice(index, 1);
};
//
const handle_paste = (event: any) => {
console.log(event);
//
const files = event.clipboardData.files;
//
const results = [...files].filter((f: any) => validExt(f.name) && validSize(f.size));
//
if (results.length + form.value.file.length <= props.limit) {
console.log(results);
results.forEach((file: any) => {
//
//
if (!form.value.file.some((item: any) => item.file.name === file.name && item.file.size === file.size)) {
const new_file_obj = {
status: '等待上传',
progress: 0,
file: file,
};
const new_file_obj_upload = {
name: file.name,
url: 'xxx',
file: file,
};
form.value.file.push(new_file_obj);
file_list.value.push(new_file_obj_upload);
} else {
ElMessageBox.alert(`文件 ${file.name} 已存在!`);
}
});
} else {
ElMessageBox.alert(`最多上传 ${props.limit} 个文件!`);
}
};
//
const submit_event = async (formEl: FormInstance | undefined) => {
if (!formEl) return;
await formEl.validate((valid, fields) => {
if (valid) {
form.value.file.forEach((item: any) => {
const formData = new FormData();
formData.append('type', props.type == 'img' ? 'image' : props.type == 'video' ? 'video' : props.type == 'file' ? 'file' : '');
formData.append('category_id', form.value.category_id);
formData.append('upfile', item.file);
if (item.status == '等待上传') {
item.status = '上传中';
// const on_upload_progress = (progressEvent: any) => {
// item.progress = Number(((progressEvent.loaded / progressEvent.total) * 100).toFixed(2));
// console.log('1', item.progress);
// };
const config = {
onUploadProgress: (progressEvent: any) => {
item.progress = Number(((progressEvent.loaded / progressEvent.total) * 100).toFixed(2));
console.log('1', item.progress);
},
};
UploadAPI.uploadAttachment(formData, config).then((res) => {
ElMessage.success('上传成功');
item.status = '上传成功';
});
}
});
} else {
console.log('error submit!', fields);
}
});
};
//#endregion -----------------------------------------------end
//#region -----------------------------------------------start
interface scanFile {
name: string;
url: string;
size: number;
}
const scan_file_list = ref<scanFile[]>([
{ name: '1', url: '1', size: 0 },
{ name: '2', url: '2', size: 0 },
{ name: '3', url: '3', size: 0 },
{ name: '4', url: '4', size: 0 },
{ name: '5', url: '5', size: 0 },
]);
//
const del_already_upload = (index: number) => {
//
scan_file_list.value.splice(index, 1);
//
};
//#endregion -----------------------------------------------end
//#region -----------------------------------------------start
//
const extract_images = () => {
// 线
ElMessage({
type: 'success',
message: '提取成功!',
});
};
const emit = defineEmits(['close']);
//
const close_all_dialog = () => {
const new_form = JSON.parse(JSON.stringify(form.value));
emit('close', new_form);
close_dialog();
};
//#endregion -----------------------------------------------end
// filebase64
const file_to_base64 = (file: any) => {
return URL.createObjectURL(file);
};
//
const close_dialog = () => {
dialogVisible.value = false;
form.value = {
type: 'loc',
category_id: '',
file: [],
qrcode: '',
web_image: '',
};
scan_file_list.value = [];
// timer.value null undefined
if (timer.value !== null) {
clearTimeout(timer.value);
}
timer.value = null; //
};
</script>
<style lang="scss" scoped>
.upload-content {
height: 57.4rem;
gap: 4.5rem;
.table {
width: 100%;
height: 30rem;
.table-header,
.table-body {
.table-row {
display: flex;
width: 100%;
border-bottom: 0.1rem solid #eee;
color: #999;
font-size: 1.4rem;
.table-cell {
flex: 1;
padding: 1rem;
color: #666;
display: flex;
align-items: center;
word-break: break-all;
gap: 1rem;
.preview-img {
width: 2.8rem;
height: 2.8rem;
}
.desc {
flex: 1;
width: 0;
}
}
.table-cell-oprate {
padding: 1rem;
width: 5rem;
cursor: pointer;
display: flex;
align-items: center;
gap: 1rem;
word-break: break-all;
}
}
}
.table-body {
.table-cell,
.table-cell-oprate {
padding: 1.5rem 1rem !important;
}
.table-cell-oprate {
color: $cr-primary;
}
}
.folder-upload {
background: #fafcff;
border-radius: 0.2rem;
border: 0.1rem dashed #afdafa;
height: 32rem;
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
.active {
background-color: #eef5ff;
border-color: #35a9ff;
}
}
}
.form-item-width {
width: 33.5rem !important;
}
}
</style>

View File

@ -0,0 +1,588 @@
<!-- 上传组件 -->
<template>
<el-dialog v-model="dialogVisible" class="radius-lg" width="1168" append-to-body>
<template #header>
<div class="title center re">
<div class="tc size-16 fw">{{ upload_type_name }}上传</div>
</div>
</template>
<div class="upload-content pa-20" @paste="handle_paste">
<el-form ref="ruleFormRef" :model="form" :rules="rules" label-width="85" status-icon>
<el-form-item label="上传方式">
<el-radio-group v-model="form.type" @change="upload_type_change">
<el-radio value="loc">本地上传</el-radio>
<el-radio value="scan">扫码上传</el-radio>
<el-radio v-if="upload_type !== 'file'" value="web"></el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="上传至分组" prop="category_id">
<div class="form-item-width">
<el-cascader v-model="form.category_id" class="w" :options="cascader_data" placeholder="请选择" :show-all-levels="false" @change="category_id_change"></el-cascader>
</div>
</el-form-item>
<template v-if="form.type == 'loc'">
<div class="flex-row jc-sb align-c mt-30">
<div class="flex-row">
<!-- base_url + 'diyapi/attachmentupload?token=b84c6cac0dacd1bc624393cd522dec37' -->
<el-upload ref="fileUpload1" v-model:file-list="file_list" multiple action="#" :accept="exts_text" :auto-upload="false" :show-file-list="false" :on-change="upload_change" :before-upload="before_upload" :limit="limit" :on-exceed="handle_exceed" :on-progress="handle_progress" :http-request="handle_request">
<template #trigger>
<el-button @click="folder_mode(false)"> {{ upload_type_name }} </el-button>
<el-button @click="folder_mode(true)"> </el-button>
</template>
</el-upload>
</div>
<el-button @click="clear_list_event"></el-button>
</div>
<div class="table mt-10">
<div class="table-header">
<div class="table-row">
<div class="table-cell">文件名</div>
<div class="table-cell">文件大小</div>
<div class="table-cell">上传状态</div>
<div class="table-cell-oprate">操作</div>
</div>
</div>
<div id="dropzone" @dragover.prevent="handle_drag_in" @dragenter="handle_drag_in" @dragleave="handle_drag_leave" @drop.prevent="handle_drop">
<el-scrollbar v-if="!is_dragging && form.file.length > 0" height="341px">
<div class="table-body">
<div v-for="(item, index) in form.file" :key="item.file.name + item.file.size" class="table-row">
<div class="table-cell">
<el-image :src="file_to_base64(item.file)" class="preview-img radius-sm" fit="contain">
<template #error>
<div class="bg-f5 img flex-row jc-c align-c radius h w">
<icon name="error-img" size="12"></icon>
</div>
</template>
</el-image>
<div class="desc">{{ item.file.name }}</div>
</div>
<div class="table-cell">{{ annex_size_to_unit(item.file.size) }}</div>
<div class="table-cell">{{ item.status }}</div>
<div class="table-cell-oprate" @click="del_upload(index)"></div>
</div>
</div>
</el-scrollbar>
<div v-show="is_dragging || form.file.length < 1" class="folder-upload mt-20" :class="is_dragging ? 'active' : ''">
<el-upload ref="fileUpload2" v-model:file-list="file_list" :accept="exts_text" multiple action="#" :auto-upload="false" :show-file-list="false" :on-change="upload_change" :before-upload="before_upload" :limit="limit" :on-exceed="handle_exceed">
<div class="flex-col jc-c align-c">
<icon name="add" size="60" color="#dbeef6"></icon>
<p class="size-18 cr-c fw">请将需要上传的文件/文件夹拖到此处或粘贴</p>
</div>
</el-upload>
</div>
</div>
</div>
</template>
<template v-else-if="form.type == 'scan'">
<el-form-item label="二维码" class="mb-10">
<qrcode :src="form.qrcode" :is-mask="is_mask"></qrcode>
</el-form-item>
<div class="table">
<div class="table-header">
<div class="table-row">
<div class="table-cell">文件名</div>
<div class="table-cell">文件大小</div>
<div class="table-cell-oprate">操作</div>
</div>
</div>
<el-scrollbar height="224px">
<div class="table-body">
<div v-for="(item, index) in scan_file_list" :key="item.name + item.size" class="table-row">
<div class="table-cell">
<el-image :src="item.url" class="preview-img radius-sm" fit="contain">
<template #error>
<div class="bg-f5 img flex-row jc-c align-c radius h w">
<icon name="error-img" size="12"></icon>
</div>
</template>
</el-image>
<div class="desc">{{ item.name }}</div>
</div>
<div class="table-cell">{{ annex_size_to_unit(item.size) }}</div>
<div class="table-cell-oprate" @click="del_already_upload(index)"></div>
</div>
</div>
</el-scrollbar>
</div>
</template>
<template v-else-if="form.type == 'web'">
<el-form-item label="网络图片">
<div class="flex-row align-c gap-10">
<el-input v-model="form.web_image" class="form-item-width" placeholder="请输入网络图片地址" />
<div class="c-pointer cr-primary size-12" @click="extract_images"></div>
</div>
</el-form-item>
</template>
</el-form>
</div>
<template #footer>
<span class="dialog-footer">
<el-button class="plr-28 ptb-10" @click="close_dialog"></el-button>
<el-button v-if="form.type == 'loc'" class="plr-28 ptb-10" type="primary" @click="submit_event(ruleFormRef)"></el-button>
<el-button v-else-if="form.type == 'scan'" class="plr-28 ptb-10" type="primary" @click="close_dialog"></el-button>
<el-button v-else-if="form.type == 'web'" class="plr-28 ptb-10" type="primary" @click="close_all_dialog"></el-button>
</span>
</template>
</el-dialog>
</template>
<script lang="ts" setup>
import UploadAPI, { Tree } from '@/api/upload';
import { uploadrStore } from '@/store';
const upload_store = uploadrStore();
import type { UploadFile, UploadFiles, UploadUserFile, FormRules, FormInstance, UploadInstance, UploadProgressEvent, UploadRequestOptions, UploadRequestHandler } from 'element-plus';
import { annex_size_to_unit, ext_name } from '@/utils';
/**
* @description: 图片执行上传弹窗
* @param close{String} 默认值
* @return {*} close
*/
const props = defineProps({
type: {
type: String,
default: 'img', // img/video/file
},
limit: {
type: Number,
default: 10000,
},
exts: {
type: Array,
default: () => ['.png', '.jpg', '.jpeg', '.bmp', '.webp', '.gif'],
},
fileSize: {
type: Number,
default: 1024 * 1024 * 1024,
},
});
const dialogVisible = defineModel({ type: Boolean, default: false });
//
const upload_type = ref(props.type);
// name
const upload_type_name = computed(() => {
return upload_type.value === 'img' ? '图片' : upload_type.value === 'video' ? '视频' : '文件';
});
//
const exts_text = ref(props.exts.join(','));
const fileUpload1 = ref<UploadInstance>();
const fileUpload2 = ref<UploadInstance>();
const base_url = import.meta.env.VITE_APP_BASE_API;
const file_list = ref<UploadUserFile[]>([]);
interface fileData {
file: File;
status: string;
}
interface formData {
type: string;
category_id: string;
file: fileData[];
qrcode: string;
web_image: string;
}
const ruleFormRef = ref<FormInstance>();
const form = ref<formData>({
type: 'loc',
category_id: '',
file: [],
qrcode: '11223344',
web_image: '',
});
const rules = reactive<FormRules>({
category_id: [{ required: true, trigger: 'change', message: '请选择分组' }],
});
//
const is_mask = ref(true);
const timer = ref<number | null>(null);
//
const upload_type_change = (type: any) => {
//
if (timer.value) {
// timer.value null undefined
clearTimeout(timer.value);
timer.value = null; //
}
//
if (type === 'scan') {
timer.value = setInterval(() => {
console.log('timer-----定时调用');
//
}, 3000);
}
};
//
const category_id_change = (val: any) => {
console.log(val);
if (val && val.length > 0) {
is_mask.value = false;
}
};
// 使 // //
const cascader_data = computed(() => {
return upload_store.category.map((tree: Tree) => ({
value: tree.id,
label: tree.name,
children: tree.items?.map((item: Tree) => ({
value: item.id,
label: item.name,
})),
}));
});
//#region -----------------------------------------------start
//
const state = reactive({
uploadEle: null as HTMLInputElement | null,
uploadList: [],
});
const folder_mode = (type: boolean) => {
if (!state.uploadEle) {
state.uploadEle = document.querySelector('.el-upload__input') as HTMLInputElement;
}
nextTick(() => {
(state.uploadEle as HTMLInputElement).webkitdirectory = type;
// console.log(state.uploadEle);
});
};
//
const upload_change = async (uploadFile: UploadFile, uploadFiles: UploadFiles) => {
// console.log('', uploadFile, uploadFiles);
// //
const results = uploadFiles.flat(Infinity).filter((f: any) => validExt(f.name) && validSize(f.size));
const new_upload_files = results.filter((item: UploadFile) => {
return !form.value.file.find((item2: fileData) => {
return item2.file.name === item.name && item2.file.size === item.size;
});
});
// form.file
new_upload_files.forEach((item: UploadFile) => {
// item.status = 'ready';
const new_file_obj = {
status: '等待上传',
file: item.raw as File,
};
form.value.file.push(new_file_obj);
});
};
// false Promise reject
const validExt = (name: string) => props.exts.includes(ext_name(name));
const validSize = (size: number) => size <= props.fileSize;
//
const before_upload = (file: any) => {
//
if (validExt(file.name) && validSize(file.size)) {
console.log('允许上传');
return true; //
} else {
console.log('不允许上传');
return false; //
}
};
//
const handle_exceed = (files: any, fileList: any) => {
ElMessageBox.alert(`最多上传 ${props.limit} 个文件!`);
};
const handle_progress = (event: UploadProgressEvent) => {
console.log(event.percent);
};
//
const handle_request = (options: UploadRequestOptions): Promise<any> => {
let formdata = new FormData();
console.log(options);
formdata.append('upfile', options.file);
formdata.append('type', props.type == 'img' ? 'image' : props.type == 'video' ? 'video' : props.type == 'file' ? 'file' : '');
formdata.append('category_id', form.value.category_id);
form.value.file.forEach((item: any) => {
formdata.append('upfile', item.file);
item.status = '上传中';
});
let number = 0;
return UploadAPI.uploadAttachment(formdata, number).then((res) => {
ElMessage.success('上传成功');
form.value.file.forEach((item: any) => {
item.status = '上传完成';
});
});
};
//
const clear_list_event = () => {
form.value.file = [];
file_list.value = [];
};
const is_dragging = ref(false);
// dropzone DOM便dragLeave
const dropzone = ref<HTMLElement | null>(null);
onMounted(() => {
dropzone.value = document.getElementById('dropzone') as HTMLElement;
});
//
const handle_drag_in = (e: DragEvent) => {
e.preventDefault();
e.stopPropagation();
is_dragging.value = true;
};
//
const handle_drag_leave = (event: DragEvent) => {
event.preventDefault();
event.stopPropagation();
// 使dropzone
const leaveTimer = setTimeout(() => {
// dropzone
if (!document.getElementById('dropzone')?.contains(event.relatedTarget as Node)) {
is_dragging.value = false;
}
}, 50); // 50
// dropzone
event.currentTarget?.addEventListener(
'dragenter',
() => {
clearTimeout(leaveTimer);
},
{ once: true }
);
};
//
const handle_drop = async (event: any) => {
event.preventDefault();
event.stopPropagation();
is_dragging.value = false;
let results = await Promise.all([...event.dataTransfer.items].map((item) => handle_entry(item.webkitGetAsEntry())));
//
results = results.flat(Infinity).filter((f: any) => validExt(f.name) && validSize(f.size));
//
if (results.length + form.value.file.length <= props.limit) {
results.forEach((file: any) => {
if (!form.value.file.some((item: any) => item.file.name === file.name && item.file.size === file.size)) {
const new_file_obj = {
status: '等待上传',
file: file,
};
const new_file_obj_upload = {
name: file.name,
url: 'xxx',
file: file,
};
form.value.file.push(new_file_obj);
file_list.value.push(new_file_obj_upload);
}
});
} else {
ElMessageBox.alert(`最多上传 ${props.limit} 个文件!`);
}
};
/**
* 处理文件/目录并返回一个Promise
* 此函数旨在处理文件/目录无论是文件还是目录如果是文件则直接以文件形式解析并解决Promise如果是目录则递归处理目录中的所有文件
* @param {any} entry 文件/目录可以是文件或目录
* @returns {Promise<any[]>} 返回一个Promise解析为一个数组包含所有条目的文件对象
*/
const handle_entry = (entry: any) => {
return new Promise((resolve) => {
// entry
if (entry.isFile) {
// Promise
entry.file(resolve);
return;
}
// entry
const dirReader = entry.createReader();
dirReader.readEntries(async (entries: any) => {
// Promise.all
resolve(await Promise.all(entries.map(handle_entry)));
});
});
};
//
const del_upload = (index: number) => {
//
form.value.file.splice(index, 1);
file_list.value.splice(index, 1);
};
//
const handle_paste = (event: any) => {
console.log(event);
//
const files = event.clipboardData.files;
//
const results = [...files].filter((f: any) => validExt(f.name) && validSize(f.size));
//
if (results.length + form.value.file.length <= props.limit) {
console.log(results);
results.forEach((file: any) => {
//
//
if (!form.value.file.some((item: any) => item.file.name === file.name && item.file.size === file.size)) {
const new_file_obj = {
status: '等待上传',
file: file,
};
const new_file_obj_upload = {
name: file.name,
url: 'xxx',
file: file,
};
form.value.file.push(new_file_obj);
file_list.value.push(new_file_obj_upload);
} else {
ElMessageBox.alert(`文件 ${file.name} 已存在!`);
}
});
} else {
ElMessageBox.alert(`最多上传 ${props.limit} 个文件!`);
}
};
//
const submit_event = async (formEl: FormInstance | undefined) => {
if (!formEl) return;
await formEl.validate((valid, fields) => {
if (valid) {
fileUpload1.value?.submit();
} else {
console.log('error submit!', fields);
}
});
};
//
const onProgress = (p: number) => {
console.log(p);
ElMessage.success(`上传进度:${p}%`);
};
//#endregion -----------------------------------------------end
//#region -----------------------------------------------start
interface scanFile {
name: string;
url: string;
size: number;
}
const scan_file_list = ref<scanFile[]>([
{ name: '1', url: '1', size: 0 },
{ name: '2', url: '2', size: 0 },
{ name: '3', url: '3', size: 0 },
{ name: '4', url: '4', size: 0 },
{ name: '5', url: '5', size: 0 },
]);
//
const del_already_upload = (index: number) => {
//
scan_file_list.value.splice(index, 1);
//
};
//#endregion -----------------------------------------------end
//#region -----------------------------------------------start
//
const extract_images = () => {
// 线
ElMessage({
type: 'success',
message: '提取成功!',
});
};
const emit = defineEmits(['close']);
//
const close_all_dialog = () => {
const new_form = JSON.parse(JSON.stringify(form.value));
emit('close', new_form);
close_dialog();
};
//#endregion -----------------------------------------------end
// filebase64
const file_to_base64 = (file: any) => {
return URL.createObjectURL(file);
};
//
const close_dialog = () => {
dialogVisible.value = false;
form.value = {
type: 'loc',
category_id: '',
file: [],
qrcode: '',
web_image: '',
};
scan_file_list.value = [];
// timer.value null undefined
if (timer.value !== null) {
clearTimeout(timer.value);
}
timer.value = null; //
};
</script>
<style lang="scss" scoped>
.upload-content {
height: 57.4rem;
gap: 4.5rem;
.table {
width: 100%;
height: 30rem;
.table-header,
.table-body {
.table-row {
display: flex;
width: 100%;
border-bottom: 0.1rem solid #eee;
color: #999;
font-size: 1.4rem;
.table-cell {
flex: 1;
padding: 1rem;
color: #666;
display: flex;
align-items: center;
word-break: break-all;
gap: 1rem;
.preview-img {
width: 2.8rem;
height: 2.8rem;
}
.desc {
flex: 1;
width: 0;
}
}
.table-cell-oprate {
padding: 1rem;
width: 5rem;
cursor: pointer;
display: flex;
align-items: center;
gap: 1rem;
word-break: break-all;
}
}
}
.table-body {
.table-cell,
.table-cell-oprate {
padding: 1.5rem 1rem !important;
}
.table-cell-oprate {
color: $cr-primary;
}
}
.folder-upload {
background: #fafcff;
border-radius: 0.2rem;
border: 0.1rem dashed #afdafa;
height: 32rem;
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
.active {
background-color: #eef5ff;
border-color: #35a9ff;
}
}
}
.form-item-width {
width: 33.5rem !important;
}
}
</style>

View File

@ -7,7 +7,8 @@
</div>
</template>
<div class="upload-content pa-20" @paste="handle_paste">
<el-form :model="form" label-width="75">
{{ exts }}
<el-form ref="ruleFormRef" :model="form" :rules="rules" label-width="85" status-icon>
<el-form-item label="上传方式">
<el-radio-group v-model="form.type" @change="upload_type_change">
<el-radio value="loc">本地上传</el-radio>
@ -15,9 +16,9 @@
<el-radio v-if="upload_type !== 'file'" value="web"></el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="上传至分组" prop="group">
<el-form-item label="上传至分组" prop="category_id">
<div class="form-item-width">
<el-cascader v-model="form.group" class="w" :options="cascader_data" placeholder="请选择" :show-all-levels="false" @change="group_change"></el-cascader>
<el-cascader v-model="form.category_id" class="w" :options="cascader_data" placeholder="请选择" :show-all-levels="false" @change="category_id_change"></el-cascader>
</div>
</el-form-item>
<template v-if="form.type == 'loc'">
@ -44,20 +45,23 @@
<div id="dropzone" @dragover.prevent="handle_drag_in" @dragenter="handle_drag_in" @dragleave="handle_drag_leave" @drop.prevent="handle_drop">
<el-scrollbar v-if="!is_dragging && form.file.length > 0" height="341px">
<div class="table-body">
<div v-for="(item, index) in form.file" :key="item.file.name + item.file.size" class="table-row">
<div class="table-cell">
<el-image :src="file_to_base64(item.file)" class="preview-img radius-sm" fit="contain">
<template #error>
<div class="bg-f5 img flex-row jc-c align-c radius h w">
<icon name="error-img" size="12"></icon>
</div>
</template>
</el-image>
<div class="desc">{{ item.file.name }}</div>
<div v-for="(item, index) in form.file" :key="item.file.name + item.file.size" class="re">
<div class="progress" :style="'width:' + item.progress + '%'"></div>
<div class="table-row">
<div class="table-cell">
<el-image :src="file_to_base64(item.file)" class="preview-img radius-sm" fit="contain">
<template #error>
<div class="bg-f5 img flex-row jc-c align-c radius h w">
<icon name="error-img" size="12"></icon>
</div>
</template>
</el-image>
<div class="desc">{{ item.file.name }}</div>
</div>
<div class="table-cell">{{ annex_size_to_unit(item.file.size) }}</div>
<div class="table-cell">{{ item.status }}{{ item.progress + '%' }}</div>
<div class="table-cell-oprate" @click="del_upload(index)"></div>
</div>
<div class="table-cell">{{ annex_size_to_unit(item.file.size) }}</div>
<div class="table-cell">{{ item.status }}</div>
<div class="table-cell-oprate" @click="del_upload(index)"></div>
</div>
</div>
</el-scrollbar>
@ -117,7 +121,7 @@
<template #footer>
<span class="dialog-footer">
<el-button class="plr-28 ptb-10" @click="close_dialog"></el-button>
<el-button v-if="form.type == 'loc'" class="plr-28 ptb-10" type="primary" @click="submit"></el-button>
<el-button v-if="form.type == 'loc'" class="plr-28 ptb-10" type="primary" @click="submit_event(ruleFormRef)"></el-button>
<el-button v-else-if="form.type == 'scan'" class="plr-28 ptb-10" type="primary" @click="close_dialog"></el-button>
<el-button v-else-if="form.type == 'web'" class="plr-28 ptb-10" type="primary" @click="close_all_dialog"></el-button>
</span>
@ -128,7 +132,7 @@
import UploadAPI, { Tree } from '@/api/upload';
import { uploadrStore } from '@/store';
const upload_store = uploadrStore();
import type { UploadFile, UploadFiles, UploadUserFile } from 'element-plus';
import type { UploadFile, UploadFiles, UploadUserFile, FormRules, FormInstance } from 'element-plus';
import { annex_size_to_unit, ext_name } from '@/utils';
/**
* @description: 图片执行上传弹窗
@ -150,7 +154,7 @@ const props = defineProps({
},
fileSize: {
type: Number,
default: 1024 * 1024,
default: 1024 * 1024 * 1024,
},
});
const dialogVisible = defineModel({ type: Boolean, default: false });
@ -166,21 +170,26 @@ const file_list = ref<UploadUserFile[]>([]);
interface fileData {
file: File;
status: string;
progress: number;
}
interface formData {
type: string;
group: string;
category_id: string;
file: fileData[];
qrcode: string;
web_image: string;
}
const ruleFormRef = ref<FormInstance>();
const form = ref<formData>({
type: 'loc',
group: '',
category_id: '',
file: [],
qrcode: '11223344',
web_image: '',
});
const rules = reactive<FormRules>({
category_id: [{ required: true, trigger: 'change', message: '请选择分组' }],
});
//
const is_mask = ref(true);
const timer = ref<number | null>(null);
@ -201,7 +210,8 @@ const upload_type_change = (type: any) => {
}
};
//
const group_change = (val: any) => {
const category_id_change = (val: any) => {
console.log(val);
if (val && val.length > 0) {
is_mask.value = false;
}
@ -250,6 +260,7 @@ const upload_change = async (uploadFile: UploadFile, uploadFiles: UploadFiles) =
// item.status = 'ready';
const new_file_obj = {
status: '等待上传',
progress: 0,
file: item.raw as File,
};
form.value.file.push(new_file_obj);
@ -326,6 +337,7 @@ const handle_drop = async (event: any) => {
if (!form.value.file.some((item: any) => item.file.name === file.name && item.file.size === file.size)) {
const new_file_obj = {
status: '等待上传',
progress: 0,
file: file,
};
const new_file_obj_upload = {
@ -385,6 +397,7 @@ const handle_paste = (event: any) => {
if (!form.value.file.some((item: any) => item.file.name === file.name && item.file.size === file.size)) {
const new_file_obj = {
status: '等待上传',
progress: 0,
file: file,
};
const new_file_obj_upload = {
@ -404,7 +417,34 @@ const handle_paste = (event: any) => {
};
//
const submit = () => {};
const submit_event = async (formEl: FormInstance | undefined) => {
if (!formEl) return;
await formEl.validate((valid, fields) => {
if (valid) {
form.value.file.forEach((item: any) => {
const formData = new FormData();
formData.append('type', props.type == 'img' ? 'image' : props.type == 'video' ? 'video' : props.type == 'file' ? 'file' : '');
formData.append('category_id', form.value.category_id);
formData.append('upfile', item.file);
if (item.status == '等待上传') {
item.status = '上传中';
const on_upload_progress = (progressEvent: any) => {
console.log('progressEvent', progressEvent);
item.progress = Number(((progressEvent.loaded / progressEvent.total) * 100).toFixed(2));
console.log('1', item.progress);
};
UploadAPI.uploadAttachment(formData, on_upload_progress).then((res) => {
ElMessage.success('上传成功');
item.status = '上传成功';
});
}
});
} else {
console.log('error submit!', fields);
}
});
};
//#endregion -----------------------------------------------end
//#region -----------------------------------------------start
@ -455,7 +495,7 @@ const close_dialog = () => {
dialogVisible.value = false;
form.value = {
type: 'loc',
group: '',
category_id: '',
file: [],
qrcode: '',
web_image: '',
@ -477,7 +517,17 @@ const close_dialog = () => {
height: 30rem;
.table-header,
.table-body {
.progress {
position: absolute;
inset: 0;
width: 0;
height: 100%;
background: #f3f9ff;
transition: width 0.5s linear;
}
.table-row {
position: relative;
z-index: 1;
display: flex;
width: 100%;
border-bottom: 0.1rem solid #eee;

View File

@ -23,7 +23,7 @@
</el-tooltip>
</el-radio-group>
</div>
<upload v-model="form.background_img_url" :limit="1"></upload>
<upload v-model="form.background_img_url" type="file" :limit="1"></upload>
</div>
</el-form-item>
</card-container>

View File

@ -28,7 +28,7 @@
<!-- 页面设置 -->
<page-settings :show-page="page_data.show_tabs" :page-data="page_data" @page_settings="page_settings"></page-settings>
<!-- 拖拽区 -->
<div class="model-drag" ref="scrollTop">
<div ref="scrollTop" class="model-drag">
<div class="seat" style="background: #fff"></div>
<div class="model-wall" :style="content_style">
<div :style="'padding-bottom:' + footer_nav_counter_store.padding_footer + 'px;'">
@ -182,13 +182,11 @@ watch(
page_settings();
}
);
watchEffect(() => {
const content = props.header.com_data?.content || {};
const content = props.header.com_data?.content || {};
const container_common_styles = gradient_computer(content) + background_computer(content);
content_style.value = container_common_styles;
})
});
watch(
() => props.footer,
(newValue) => {
@ -452,7 +450,7 @@ const activeCard = ref<HTMLElement | null>(null);
const scroll = () => {
nextTick(() => {
//
activeCard.value = document.querySelector(".plug-in-table.plug-in-border");
activeCard.value = document.querySelector('.plug-in-table.plug-in-border');
if (activeCard.value) {
//
const scrollY = activeCard.value.offsetTop;
@ -480,7 +478,7 @@ const page_settings = () => {
item.show_tabs = false;
});
}
emits('rightUpdate', page_data.value, diy_data.value, page_data.value, footer_nav.value);
};
//

View File

@ -12,7 +12,7 @@ service.interceptors.request.use(
(config: InternalAxiosRequestConfig) => {
// const userStore = useUserStoreHook();
// const accessToken = localStorage.getItem(TOKEN_KEY);
const accessToken = { token: '100a1e5ba70b3920a919f85ac10b7bb6' };
const accessToken = { token: 'b84c6cac0dacd1bc624393cd522dec37' };
if (accessToken.token) {
// config.headers.Authorization = accessToken.token;
// config.data = { ...config.data, token: accessToken.token };