parent
0add403b7e
commit
51b2d54925
|
|
@ -70,6 +70,22 @@ class UploadAPI {
|
|||
onUploadProgress: progress,
|
||||
});
|
||||
}
|
||||
// 扫码上传
|
||||
static uploadQrcode(data: any) {
|
||||
return request({
|
||||
url: `diyapi/attachmentscanuploaddata`,
|
||||
method: 'post',
|
||||
data,
|
||||
});
|
||||
}
|
||||
// 提取链接 --------附件远程下载
|
||||
static getAttachmentCatch(data: any) {
|
||||
return request({
|
||||
url: `diyapi/attachmentcatch`,
|
||||
method: 'post',
|
||||
data,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default UploadAPI;
|
||||
|
|
|
|||
|
|
@ -51,10 +51,17 @@ const generateQRCode = async (text: string, margin: number) => {
|
|||
const clipboard_event = async () => {
|
||||
try {
|
||||
await navigator.clipboard.writeText(props.src);
|
||||
ElMessage.success('复制成功');
|
||||
} catch (error) {
|
||||
console.error('复制失败', error);
|
||||
}
|
||||
};
|
||||
watch(
|
||||
() => props.src,
|
||||
(newValue) => {
|
||||
generateQRCode(newValue.trim(), 2);
|
||||
}
|
||||
);
|
||||
|
||||
// 在组件挂载后自动调用生成二维码方法
|
||||
onMounted(() => {
|
||||
|
|
|
|||
|
|
@ -1,570 +0,0 @@
|
|||
<!-- 上传组件 -->
|
||||
<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
|
||||
|
||||
// file转换成base64
|
||||
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>
|
||||
|
|
@ -1,583 +0,0 @@
|
|||
<!-- 上传组件 -->
|
||||
<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
|
||||
|
||||
// file转换成base64
|
||||
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>
|
||||
|
|
@ -1,588 +0,0 @@
|
|||
<!-- 上传组件 -->
|
||||
<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
|
||||
|
||||
// file转换成base64
|
||||
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>
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
<!-- 上传组件 -->
|
||||
<template>
|
||||
<el-dialog v-model="dialogVisible" class="radius-lg" width="1168" append-to-body>
|
||||
<el-dialog v-model="dialogVisible" class="radius-lg" width="1168" append-to-body @close="close_dialog">
|
||||
<template #header>
|
||||
<div class="title center re">
|
||||
<div class="tc size-16 fw">{{ upload_type_name }}上传</div>
|
||||
|
|
@ -46,7 +46,7 @@
|
|||
<div class="table-body">
|
||||
<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-row" :class="item.status == '上传失败' ? 'bg-table-rwo-error' : ''">
|
||||
<div class="table-cell">
|
||||
<template v-if="type == 'video'">
|
||||
<div class="preview-img radius-sm">
|
||||
|
|
@ -72,7 +72,7 @@
|
|||
<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" :class="item.status">{{ item.status == 'loading' ? '上传中' : item.status == 'success' ? '上传成功' : item.status == 'error' ? '上传失败' : '等待上传' }}{{ item.status == 'loading' ? '(' + item.progress + '%)' : '' }}</div>
|
||||
<div class="table-cell-oprate" @click="del_upload(index)">移除</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -103,7 +103,7 @@
|
|||
</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 v-for="(item, index) in scan_file_list" :key="index" class="table-row">
|
||||
<div class="table-cell">
|
||||
<el-image :src="item.url" class="preview-img radius-sm" fit="contain">
|
||||
<template #error>
|
||||
|
|
@ -112,10 +112,10 @@
|
|||
</div>
|
||||
</template>
|
||||
</el-image>
|
||||
<div class="desc">{{ item.name }}</div>
|
||||
<div class="desc">{{ item.title }}</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 class="table-cell-oprate" @click="del_already_upload(item.id, index)">删除</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-scrollbar>
|
||||
|
|
@ -125,7 +125,7 @@
|
|||
<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 class="c-pointer cr-primary size-12" @click="extract_images(ruleFormRef)">提取图片</div>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</template>
|
||||
|
|
@ -146,8 +146,9 @@ 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';
|
||||
import { annex_size_to_unit, ext_name, get_math } from '@/utils';
|
||||
import { ext_img_name_list, ext_video_name_list, ext_file_name_list, ext_file_name_list_map } from './index';
|
||||
const app = getCurrentInstance();
|
||||
/**
|
||||
* @description: 图片执行上传弹窗
|
||||
* @param close{String} 默认值
|
||||
|
|
@ -188,7 +189,7 @@ interface fileData {
|
|||
}
|
||||
interface formData {
|
||||
type: string;
|
||||
category_id: string;
|
||||
category_id: string[];
|
||||
file: fileData[];
|
||||
qrcode: string;
|
||||
web_image: string;
|
||||
|
|
@ -196,9 +197,9 @@ interface formData {
|
|||
const ruleFormRef = ref<FormInstance>();
|
||||
const form = ref<formData>({
|
||||
type: 'loc',
|
||||
category_id: '',
|
||||
category_id: [],
|
||||
file: [],
|
||||
qrcode: '11223344',
|
||||
qrcode: '******',
|
||||
web_image: '',
|
||||
});
|
||||
const rules = reactive<FormRules>({
|
||||
|
|
@ -210,24 +211,34 @@ const timer = ref<number | null>(null);
|
|||
// 上传方式
|
||||
const upload_type_change = (type: any) => {
|
||||
// 清除之前的定时器(如果存在)
|
||||
if (timer.value) {
|
||||
if (timer.value && type !== 'scan') {
|
||||
// 直接检查 timer.value 是否存在(不是 null 或 undefined)
|
||||
clearTimeout(timer.value);
|
||||
timer.value = null; // 清除引用,防止内存泄漏
|
||||
}
|
||||
// 如果需要设置新的定时器
|
||||
if (type === 'scan') {
|
||||
timer.value = setInterval(() => {
|
||||
console.log('timer-----定时调用');
|
||||
// 此处写定时调用接口,获取文件列表
|
||||
if (type == 'scan') {
|
||||
timer.value = setInterval(async () => {
|
||||
if (scan_uuid.value.toString().length > 0) {
|
||||
const { data } = await UploadAPI.uploadQrcode({ key: scan_uuid.value });
|
||||
scan_file_list.value = data;
|
||||
}
|
||||
}, 3000);
|
||||
}
|
||||
};
|
||||
const scan_uuid = ref('');
|
||||
// 选择分组
|
||||
const category_id_change = (val: any) => {
|
||||
console.log(val);
|
||||
if (val && val.length > 0) {
|
||||
scan_file_list.value = [];
|
||||
is_mask.value = false;
|
||||
scan_uuid.value = get_math();
|
||||
let new_url = '';
|
||||
if (import.meta.env.VITE_APP_BASE_API == '/dev-api') {
|
||||
new_url = get_before_string(import.meta.env.VITE_APP_BASE_API_URL);
|
||||
} else {
|
||||
new_url = window.location.origin + '/';
|
||||
}
|
||||
form.value.qrcode = new_url + '?s=ueditor/scanupload/key/' + scan_uuid.value + '/cid/' + val[val.length - 1] + '/type/upload' + (props.type == 'file' ? 'file' : props.type == 'video' ? 'video' : 'image');
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -249,6 +260,12 @@ const get_after_string = (str: string) => {
|
|||
str = str.substring(index, str.length);
|
||||
return str;
|
||||
};
|
||||
// 获取字符串中‘/’迁民所有字符
|
||||
const get_before_string = (str: string) => {
|
||||
let index = str.lastIndexOf('admin.php');
|
||||
str = str.substring(0, index);
|
||||
return str;
|
||||
};
|
||||
|
||||
//#region 本地上传 -----------------------------------------------start
|
||||
|
||||
|
|
@ -268,7 +285,7 @@ const folder_mode = (type: boolean) => {
|
|||
};
|
||||
// 文件状态改变时的钩子,添加文件、上传成功和上传失败时都会被调用
|
||||
const upload_change = async (uploadFile: UploadFile, uploadFiles: UploadFiles) => {
|
||||
console.log('文件状态改变时的钩子', uploadFile, 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) => {
|
||||
|
|
@ -280,7 +297,7 @@ const upload_change = async (uploadFile: UploadFile, uploadFiles: UploadFiles) =
|
|||
new_upload_files.forEach((item: UploadFile) => {
|
||||
// item.status = 'ready';
|
||||
const new_file_obj = {
|
||||
status: '等待上传',
|
||||
status: 'ready',
|
||||
progress: 0,
|
||||
file: item.raw as File,
|
||||
};
|
||||
|
|
@ -357,7 +374,7 @@ const handle_drop = async (event: any) => {
|
|||
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: '等待上传',
|
||||
status: 'ready',
|
||||
progress: 0,
|
||||
file: file,
|
||||
};
|
||||
|
|
@ -417,7 +434,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: '等待上传',
|
||||
status: 'ready',
|
||||
progress: 0,
|
||||
file: file,
|
||||
};
|
||||
|
|
@ -445,19 +462,24 @@ const submit_event = async (formEl: FormInstance | undefined) => {
|
|||
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('category_id', form.value.category_id[form.value.category_id.length - 1]);
|
||||
formData.append('upfile', item.file);
|
||||
if (item.status == '等待上传') {
|
||||
item.status = '上传中';
|
||||
if (item.status == 'ready') {
|
||||
item.status = 'loading';
|
||||
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 = '上传成功';
|
||||
});
|
||||
UploadAPI.uploadAttachment(formData, on_upload_progress)
|
||||
.then((res) => {
|
||||
ElMessage.success('上传成功');
|
||||
item.status = 'success';
|
||||
})
|
||||
.catch((err) => {
|
||||
item.status = 'error';
|
||||
item.progress = 0;
|
||||
});
|
||||
}
|
||||
});
|
||||
} else {
|
||||
|
|
@ -469,33 +491,49 @@ const submit_event = async (formEl: FormInstance | undefined) => {
|
|||
//#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 scan_file_list = ref<uploadList[]>([]);
|
||||
// 删除已上传的文件
|
||||
const del_already_upload = (index: number) => {
|
||||
const del_already_upload = (id: number | undefined, index: number) => {
|
||||
// 根据下标删除文件
|
||||
scan_file_list.value.splice(index, 1);
|
||||
// scan_file_list.value.splice(index, 1);
|
||||
// 调接口真实删除
|
||||
if (id !== undefined) {
|
||||
app?.appContext.config.globalProperties.$common.message_box('删除后不可恢复,确定继续吗?', 'warning').then(() => {
|
||||
// 调用删除接口,然后,更新数据
|
||||
UploadAPI.delAttachment({ ids: id }).then((res) => {
|
||||
ElMessage.success('删除成功!');
|
||||
scan_file_list.value.splice(index, 1);
|
||||
});
|
||||
});
|
||||
} else {
|
||||
ElMessage.warning('请先选择图片!');
|
||||
}
|
||||
};
|
||||
//#endregion 扫码上传 -----------------------------------------------end
|
||||
|
||||
//#region 网络上传 -----------------------------------------------start
|
||||
// 提取图片
|
||||
const extract_images = () => {
|
||||
// 此处调用接口获取提取后的图片更新输入框的地址,改为在线地址
|
||||
ElMessage({
|
||||
type: 'success',
|
||||
message: '提取成功!',
|
||||
const extract_images = async (formEl: FormInstance | undefined) => {
|
||||
if (!formEl) return;
|
||||
await formEl.validate((valid, fields) => {
|
||||
if (valid) {
|
||||
// 此处调用接口获取提取后的图片更新输入框的地址,改为在线地址
|
||||
if (form.value.web_image) {
|
||||
const new_data = {
|
||||
type: props.type == 'img' ? 'image' : props.type == 'video' ? 'video' : props.type == 'file' ? 'file' : '',
|
||||
category_id: form.value.category_id[form.value.category_id.length - 1],
|
||||
source: form.value.web_image,
|
||||
};
|
||||
UploadAPI.getAttachmentCatch(new_data).then((res) => {
|
||||
form.value.web_image = res.data[0].url;
|
||||
ElMessage.success('提取成功!');
|
||||
});
|
||||
} else {
|
||||
ElMessage.warning('请输入地址后再提取!');
|
||||
}
|
||||
} else {
|
||||
console.log('error submit!', fields);
|
||||
}
|
||||
});
|
||||
};
|
||||
const emit = defineEmits(['close']);
|
||||
|
|
@ -516,12 +554,13 @@ const close_dialog = () => {
|
|||
dialogVisible.value = false;
|
||||
form.value = {
|
||||
type: 'loc',
|
||||
category_id: '',
|
||||
category_id: [],
|
||||
file: [],
|
||||
qrcode: '',
|
||||
web_image: '',
|
||||
};
|
||||
scan_file_list.value = [];
|
||||
scan_uuid.value = '';
|
||||
// 直接检查 timer.value 是否存在(不是 null 或 undefined)
|
||||
if (timer.value !== null) {
|
||||
clearTimeout(timer.value);
|
||||
|
|
@ -554,6 +593,9 @@ const close_dialog = () => {
|
|||
border-bottom: 0.1rem solid #eee;
|
||||
color: #999;
|
||||
font-size: 1.4rem;
|
||||
&.bg-table-rwo-error {
|
||||
background: #f3f9ff;
|
||||
}
|
||||
.table-cell {
|
||||
flex: 1;
|
||||
padding: 1rem;
|
||||
|
|
@ -562,6 +604,15 @@ const close_dialog = () => {
|
|||
align-items: center;
|
||||
word-break: break-all;
|
||||
gap: 1rem;
|
||||
&.loading {
|
||||
color: $cr-primary;
|
||||
}
|
||||
&.success {
|
||||
color: $cr-success;
|
||||
}
|
||||
&.error {
|
||||
color: $cr-error;
|
||||
}
|
||||
.preview-img {
|
||||
width: 2.8rem;
|
||||
height: 2.8rem;
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@
|
|||
</el-tooltip>
|
||||
</el-radio-group>
|
||||
</div>
|
||||
<upload v-model="form.background_img_url" type="file" :limit="1"></upload>
|
||||
<upload v-model="form.background_img_url" :limit="1"></upload>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</card-container>
|
||||
|
|
@ -32,7 +32,7 @@
|
|||
</template>
|
||||
<script setup lang="ts">
|
||||
interface Props {
|
||||
value: page_content
|
||||
value: page_content;
|
||||
}
|
||||
const props = defineProps<Props>();
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import axios, { InternalAxiosRequestConfig, AxiosResponse } from 'axios';
|
|||
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||
// 创建 axios 实例
|
||||
const service = axios.create({
|
||||
baseURL: import.meta.env.VITE_APP_BASE_API,
|
||||
baseURL: import.meta.env.VITE_APP_BASE_API == '/dev-api' ? import.meta.env.VITE_APP_BASE_API : window.location.origin,
|
||||
timeout: 50000,
|
||||
headers: { 'Content-Type': 'application/json;charset=utf-8' },
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in New Issue