123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588 |
- <!--
- * @Author: zhangyu <taohuzy@163.com>
- * @Date: 2019-11-28 10:05:28
- * @Info:
- * @LastEditTime : 2020-01-03 15:31:04
- -->
- <template>
- <div id="global-uploader">
- <!-- 上传 -->
- <uploader
- ref="uploader"
- :options="options"
- :fileStatusText="fileStatusText"
- :autoStart="false"
- @file-added="onFileAdded"
- @file-success="onFileSuccess"
- @file-progress="onFileProgress"
- @file-error="onFileError"
- @file-removed="onFileRemoved"
- class="uploader-app"
- >
- <uploader-unsupport></uploader-unsupport>
- <uploader-btn id="global-uploader-btn" :attrs="attrs" ref="uploadBtn">选择文件</uploader-btn>
- <uploader-list v-show="panelShow">
- <div class="file-panel" slot-scope="props" :class="{ collapse: collapse }">
- <div class="file-title">
- <h2>文件列表</h2>
- <div class="operate">
- <el-button @click="fileListShow" style="color: #999" type="text" :title="collapse ? '折叠' : '展开'">
- <i class="iconfont" :class="collapse ? 'icon-bottom' : 'icon-top'"></i>
- </el-button>
- <el-button @click="close" style="color: #999" type="text" title="关闭">
- <i class="iconfont icon-guanbi"></i>
- </el-button>
- </div>
- </div>
- <ul class="file-list" v-show="collapse">
- <el-scrollbar style="height: 100%">
- <li v-for="file in props.fileList" :key="file.id">
- <uploader-file :class="'file_' + file.id" ref="files" :file="file" :list="true"></uploader-file>
- </li>
- <div class="no-file" v-if="!props.fileList.length"><i class="iconfont icon-wushuju"></i> 暂无待上传文件</div>
- </el-scrollbar>
- </ul>
- </div>
- </uploader-list>
- </uploader>
- </div>
- </template>
- <script lang="ts">
- /**
- * 全局上传插件
- * 调用方法:Bus.$emit('openUploader', {}) 打开文件选择框,参数为需要传递的额外参数
- * 监听函数:Bus.$on('fileAdded', fn); 文件选择后的回调
- * Bus.$on('fileSuccess', fn); 文件上传成功的回调
- */
- import { Vue, Component, Ref } from "vue-property-decorator";
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
- // @ts-ignore
- import SparkMD5 from "spark-md5";
- import Bus from "@/utils/bus.ts";
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
- // @ts-ignore
- import uploader from "vue-simple-uploader";
- import { AppModule } from "@/store/modules/app";
- import { mergeModelFile, deleteModelFileList } from "@/api/modelapi";
- import { getUploadId, mergeMultipart } from "@/api/imageservice";
- // import request from "@/api/model/file.js";
- @Component({
- name: "GlobalUploader",
- })
- export default class extends Vue {
- @Ref("uploader") readonly uploaderComponent!: uploader;
- // 中英文按钮映射
- private fileStatusText: any = {
- success: "成功",
- error: "失败",
- uploading: "上传中",
- paused: "暂停",
- waiting: "等待",
- };
- // 分片上传配置
- private options: any = {
- target: "/image-service/common/multipart_upload",
- chunkSize: 1 * 1024 * 1024,
- fileParameterName: "file",
- allowDuplicateUploads: true, //允许重复上传
- maxChunkRetries: 3,
- testChunks: false, //是否开启服务器分片校验
- // 服务器分片校验函数,秒传及断点续传基础
- // checkChunkUploadedByResponse: function (chunk, message) {
- // let objMessage = JSON.parse(message);
- // if (objMessage.skipUpload) {
- // return true;
- // }
- // return (objMessage.uploaded || []).indexOf(chunk.offset + 1) >= 0
- // },
- // headers: {
- // Authorization: Ticket.get() && "Bearer " + Ticket.get().access_token
- // },
- query: this.getFileQuery,
- };
- private attrs: any = {
- // accept: ACCEPT_CONFIG.getAll()
- };
- private panelShow = false; // 选择文件后,展示上传panel
- private collapse = false;
- // Uploader实例
- get uploader() {
- return this.uploaderComponent.uploader;
- }
- get uploaderList(): any[] {
- return AppModule.uploaderList;
- }
- private setUploaderList(list: any[]) {
- AppModule.SetUploaderList(list);
- }
- mounted() {
- // 刷新或关闭浏览器提示
- window.addEventListener("beforeunload", (e) => {
- if (this.uploader.isUploading()) {
- // 判断是否有文件正在上传
- e.preventDefault();
- // 兼容IE8和Firefox之前的版本
- if (e) {
- e.returnValue = "您有文件正在上传,关闭会中断上传,是否继续?";
- }
- // Chrome, Safari, Firefox 4+, Opera 12+ , IE 9+
- return "您有文件正在上传,关闭会中断上传,是否继续?";
- }
- });
- Bus.$on("openUploader", async (query: any, file: any) => {
- if (query.uploadId) {
- // this.params = query || {};
- file.uploadId = query.uploadId;
- file.modelId = query.modelId ? query.modelId : "";
- this.uploader.addFile(file);
- this.setUploaderList(
- this.uploader.files.map((item: any) => {
- item.modelId = item.file.modelId ? item.file.modelId : "";
- return item;
- })
- );
- } else {
- const res = await getUploadId({
- systemId: query.systemId ? query.systemId : "revit",
- secret: query.secret ? query.secret : "63afbef6906c342b",
- overwrite: query.overwrite ? query.overwrite : false,
- key: file.name,
- });
- if (res.UploadId) {
- // this.params = Object.assign(query, { uploadId: res.UploadId }) || {};
- file = Object.assign(file, {
- ...query,
- uploadId: res.UploadId,
- });
- this.uploader.addFile(file);
- this.setUploaderList(
- this.uploader.files.map((item: any) => {
- item.modelId = item.file.modelId ? item.file.modelId : "";
- return item;
- })
- );
- } else {
- this.$message.error(`请求分片上传接口失败!`);
- }
- }
- // if (this.$refs.uploadBtn) {
- // document.getElementById('global-uploader-btn').click();
- // // $('#global-uploader-btn').click();
- // }
- });
- }
- destroyed() {
- Bus.$off("openUploader");
- }
- private onFileAdded(file: any) {
- this.panelShow = true;
- this.collapse = true;
- this.computeMD5(file);
- Bus.$emit("fileAdded");
- }
- /**
- * 文件上传中监听回调
- */
- private onFileProgress(rootFile: any, file: any, chunk: any) {
- console.log(`上传中 ${file.name},chunk:${chunk.startByte / 1024 / 1024} ~ ${chunk.endByte / 1024 / 1024}`);
- }
- /**
- * 文件上传成功后的监听回调
- * @info 每片上传成功后都会触发,检查所有分片是否都上传完成,完成后调用合并接口
- */
- private async onFileSuccess(rootFile: any, file: any, response: any) {
- let res = JSON.parse(response);
- // 服务器自定义的错误(即虽返回200,但是是错误的情况),这种错误是Uploader无法拦截的
- if (!res.Result || res.Result != "success") {
- this.$message({ message: res.ResultMsg ? res.ResultMsg : "上传失败!", type: "error" });
- // 文件状态设为“失败”
- this.statusSet(file.id, "failed");
- return;
- }
- // 如果服务端返回需要合并
- console.log("获取分片数", file.chunks.length);
- if (parseInt(res.TotalCount) >= file.chunks.length) {
- // 文件状态设为“合并中”
- this.statusSet(file.id, "merging");
- if (file.file.modelId) {
- //模型文件合并专用接口
- const res = await mergeModelFile({ ModelId: file.file.modelId, UploadId: file.file.uploadId, Md5: file.file.md5 });
- // 文件合并成功
- if (res.Result === "success") {
- Bus.$emit("modelStatusChange");
- Bus.$emit("fileSuccess");
- this.statusRemove(file.id);
- }
- } else {
- const res = await mergeMultipart({
- systemId: file.file.systemId ? file.file.systemId : "revit",
- secret: file.file.secret ? file.file.secret : "63afbef6906c342b",
- uploadId: file.file.uploadId,
- });
- // 文件合并成功
- if (res.Result === "success") {
- Bus.$emit("fileSuccess");
- this.statusRemove(file.id);
- }
- }
- } else {
- // 不需要合并
- Bus.$emit("fileSuccess");
- console.log("上传成功");
- }
- }
- /**
- * 文件上传失败回调
- */
- private onFileError(rootFile: any, file: any, response: any) {
- this.$message({
- message: response,
- type: "error",
- });
- }
- /**
- * 文件移除回调
- */
- private async onFileRemoved(file: any) {
- this.uploaderList.splice(
- this.uploaderList.findIndex((item) => {
- return item.id == file.id;
- }, 1)
- );
- this.setUploaderList(this.uploaderList);
- // 未上传完成的模型文件,调用模型删除回滚
- if (file.modelId && !file.isComplete()) {
- const res = await deleteModelFileList({ Id: file.modelId, Force: true });
- if (res.Result === "success") {
- console.log("删除错误模型:", file.modelId);
- Bus.$emit("modelStatusChange");
- }
- }
- }
- /**
- * 计算md5,实现断点续传及秒传
- * @param file
- */
- private computeMD5(file: any) {
- let fileReader = new FileReader();
- let time = new Date().getTime();
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
- // @ts-ignore
- let blobSlice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice;
- let currentChunk = 0;
- const chunkSize = 1 * 1024 * 1024;
- let chunks = Math.floor(file.size / chunkSize);
- let spark = new SparkMD5.ArrayBuffer();
- // 文件状态设为"计算MD5"
- this.statusSet(file.id, "md5");
- file.pause();
- loadNext();
- fileReader.onload = (e: any) => {
- spark.append(e.target.result);
- if (currentChunk < chunks) {
- currentChunk++;
- loadNext();
- // 实时展示MD5的计算进度
- this.$nextTick(() => {
- let dom: HTMLElement = document.getElementsByClassName(`myStatus_${file.id}`)[0] as HTMLElement;
- dom.innerText = "校验MD5 " + ((currentChunk / chunks) * 100).toFixed(0) + "%";
- // $(`.myStatus_${file.id}`).text('校验MD5 '+ ((currentChunk/chunks)*100).toFixed(0)+'%')
- });
- } else {
- let md5 = spark.end();
- file.file.md5 = md5;
- this.computeMD5Success(md5, file);
- console.log(`MD5计算完毕:${file.name} \nMD5:${md5} \n分片:${chunks} 大小:${file.size} 用时:${new Date().getTime() - time} ms`);
- }
- };
- fileReader.onerror = () => {
- this.error(`文件${file.name}读取出错,请检查该文件`);
- file.cancel();
- };
- function loadNext() {
- let start = currentChunk * chunkSize;
- let end = start + chunkSize >= file.size ? file.size : start + chunkSize;
- fileReader.readAsArrayBuffer(blobSlice.call(file.file, start, end));
- }
- }
- private computeMD5Success(md5: number, file: any) {
- // 将自定义参数直接加载uploader实例的opts上
- console.log(this.uploader);
- // Object.assign(this.uploader.opts, {
- // query: {
- // uploadId: file.file.uploadId,
- // md5
- // }
- // })
- // file.uniqueIdentifier = this.params.uploadId;
- // file.resume();//继续上传文件
- // 调用依赖中继续上传的方法达到状态刷新的目的(原生点击继续上传按钮)
- let dom: HTMLElement | null = document.querySelector(`.file_${file.id} .uploader-file-resume`);
- dom?.click();
- this.statusRemove(file.id);
- }
- /**
- * 获取文件参数
- */
- private getFileQuery(file: any) {
- return {
- uploadId: file.file.uploadId,
- md5: file.file.md5,
- };
- }
- /**
- * 显示文件列表
- */
- private fileListShow() {
- this.collapse = this.collapse ? false : true;
- // let $list = $('#global-uploader .file-list');
- // let $list = document.querySelector('#global-uploader .file-list')
- // if ($list.style.display == '' && $list.style.display == 'none') {
- // $list.style.display == 'block';//显示
- // this.collapse = false;
- // // $list.slideUp();//隐藏
- // // this.collapse = true;
- // } else {
- // $list.style.display == 'none';//隐藏
- // this.collapse = true;
- // // $list.slideDown();//显示
- // // this.collapse = false;
- // }
- }
- /**
- * 关闭文件上传器弹窗
- */
- private close() {
- if (this.uploader.isUploading()) {
- this.$alert("您有文件正在上传,关闭会中断上传,是否继续?", "提示", {
- confirmButtonText: "确定",
- callback: (action) => {
- console.log(action);
- if (action == "confirm") {
- this.uploader.cancel();
- this.panelShow = false;
- this.uploader.files = [];
- this.setUploaderList([]);
- Bus.$emit("modelStatusChange");
- }
- },
- });
- } else {
- this.uploader.cancel();
- this.panelShow = false;
- }
- }
- /**
- * 新增的自定义的状态: 'md5'、'transcoding'、'failed'
- * @param id
- * @param status
- */
- private statusSet(id: string, status: string) {
- let statusMap: any = {
- md5: {
- text: "校验MD5",
- bgc: "#fff",
- },
- merging: {
- text: "合并中",
- bgc: "#e2eeff",
- },
- transcoding: {
- text: "转码中",
- bgc: "#e2eeff",
- },
- failed: {
- text: "上传失败",
- bgc: "#e2eeff",
- },
- };
- this.$nextTick(() => {
- let node = document.createElement("P");
- let att = document.createAttribute("class");
- att.value = `status_style myStatus_${id}`;
- let text = document.createTextNode(`${statusMap[status].text}`);
- node.appendChild(text);
- node.setAttributeNode(att);
- node.style.backgroundColor = `${statusMap[status].bgc}`;
- document.querySelector(`.file_${id} .uploader-file-status`)?.appendChild(node);
- // $(`<p class="myStatus_${id}"></p>`).appendTo(`.file_${id} .uploader-file-status`).css({
- // 'position': 'absolute',
- // 'top': '0',
- // 'left': '0',
- // 'right': '0',
- // 'bottom': '0',
- // 'zIndex': '1',
- // 'backgroundColor': statusMap[status].bgc
- // }).text(statusMap[status].text);
- });
- }
- private statusRemove(id: string) {
- this.$nextTick(() => {
- let dom: Node = document.getElementsByClassName(`myStatus_${id}`)[0];
- dom.parentNode?.removeChild(dom);
- });
- }
- private error(msg: string) {
- this.$notify({
- title: "错误",
- message: msg,
- type: "error",
- duration: 2000,
- });
- }
- }
- </script>
- <style scoped lang="scss">
- #global-uploader {
- position: fixed;
- z-index: 101;
- top: auto;
- right: 10px;
- bottom: 0;
- .uploader-app {
- width: 633px;
- }
- .file-panel {
- background-color: #fff;
- border: 1px solid #e2e2e2;
- border-radius: 7px 7px 0 0;
- box-shadow: 0 0 10px rgba(0, 0, 0, 0.2);
- .file-title {
- display: flex;
- height: 40px;
- line-height: 40px;
- padding: 0 15px;
- border-bottom: 1px solid #ddd;
- h2 {
- font-size: 14px;
- font-style: normal;
- cursor: text;
- font-weight: 200;
- color: #666;
- }
- .operate {
- flex: 1;
- text-align: right;
- }
- }
- .file-list {
- position: relative;
- height: 350px;
- overflow-x: hidden;
- overflow-y: auto;
- background-color: #fff;
- ::v-deep .el-scrollbar__wrap {
- overflow-x: hidden;
- }
- > li {
- background-color: #fff;
- }
- ::v-deep .uploader-file-info i {
- display: none;
- }
- ::v-deep .status_style {
- position: absolute;
- top: 0;
- left: 0;
- right: 0;
- bottom: 0;
- z-index: 1;
- }
- }
- // &.collapse {
- // .file-title {
- // background-color: #E7ECF2;
- // }
- // }
- }
- .no-file {
- position: absolute;
- top: 50%;
- left: 50%;
- transform: translate(-50%, -50%);
- font-size: 16px;
- text-align: center;
- }
- ::v-deep .uploader-file-icon {
- &:before {
- content: "" !important;
- }
- // &[icon=image] {
- // background: url(./images/image-icon.png);
- // }
- // &[icon=video] {
- // background: url(./images/video-icon.png);
- // }
- // &[icon=document] {
- // background: url(./images/text-icon.png);
- // }
- }
- ::v-deep .uploader-file-actions > span {
- margin-right: 6px;
- }
- }
- /* 隐藏上传按钮 */
- #global-uploader-btn {
- position: absolute;
- clip: rect(0, 0, 0, 0);
- }
- </style>
|