整体思路(模型特殊不考虑,别人封装不具备参考性)
- 图片上传采用单独的组件,其他三种类型采用一个上传组件(仅仅文件格式不同)
- 文件上传采用前端直接上传阿里云的方式
- 图片预览使用elementUI自带的image预览
- dwg预览采用 kkfileview 方式,需要后台部署,前端使用
npm install js-base64
,应该支持所有文件的预览,我们这里仅用作CAD文件的预览
- 文档类型的直接在浏览器打开预览,如果不支持需要拼接前缀
"https://view.officeapps.live.com/op/view.aspx?src="
- 图片下载为压缩包
- 文档类的相同方法下载
上传
js
复制代码
<el-form
style="width: 650px"
:model="form"
:rules="rules"
ref="form"
label-width="180px"
class="demo-form"
:disabled="type == 3"
>
<el-form-item label="样板格式" prop="modelType">
<el-radio-group v-model="form.modelType">
<el-radio label="1">图片样板</el-radio>
<el-radio label="2">三维样板</el-radio>
<el-radio label="3">图纸样板</el-radio>
<el-radio label="4">文档样板</el-radio>
</el-radio-group>
</el-form-item>
<!-- 1 图片样板 2 三维样板 3 图纸样板 4 文档样板 -->
<el-form-item
v-if="form.modelType == 1"
label="样板文件上传"
prop="url"
>
<ImageUpload
v-model="form.url"
tip="请上传图片:"
:limit="10"
:showIcon="type == 3 ? true : false"
></ImageUpload>
</el-form-item>
<el-form-item
v-if="form.modelType != 1"
label="样板文件上传"
prop="url"
>
<ModelUpload
v-if="type != 3"
v-model="form.url"
:limit="1"
:fileSize="
form.modelType == 3 ? 50 : form.modelType == 4 ? 10 : 5120
"
:isShowUploadModel="true"
ref="videoUploadRef"
@input="uploadTemplate"
:fileType="
form.modelType == 3
? ['.dwg']
: form.modelType == 4
? ['.doc', '.docx', '.xls', '.xlsx', '.ppt', '.pptx', '.pdf']
: ['.rvt', '.ifc', '.obj', '.stl', '.fbx', '.3DS']
"
></ModelUpload>
<div v-else class="blue point" @click="preview(form)">
{{ form.url?.split("/")[form.url?.split("/").length - 1] }}
</div>
</el-form-item>
ModelUpload.vue
js
复制代码
<template>
<div class="component-upload-image">
<el-upload
action=""
:http-request="beforeUpload"
class="avatar-uploader"
:limit="limit"
:on-remove="handleDelete"
:on-error="handleUploadError"
:on-exceed="handleExceed"
name="file"
:show-file-list="true"
:file-list="fileList"
ref="uploadRef"
:on-preview="handlePreview"
:data="otherQuery"
>
<el-button
size="small"
type="primary"
v-if="!modelFlag"
:disabled="disabled"
>点击上传</el-button
>
<!-- 上传提示 -->
<div class="el-upload__tip" slot="tip" v-if="showTip">
<template v-if="fileSize">
大小不超过 <b style="color: #f56c6c">{{ fileSize }}MB</b>
</template>
<template v-if="fileType">
文件类型支持
<b style="color: #f56c6c">{{ fileType.join(" / ") }}</b> 格式
</template>
</div>
<el-progress
v-if="modelFlag == true"
type="circle"
:percentage="modelUploadPercent"
style="margin-top: 7px"
></el-progress>
</el-upload>
</div>
</template>
<script>
export default {
props: {
value: [String, Object, Array],
// 图片数量限制
limit: {
type: Number,
default: 1,
},
// 大小限制(MB)
fileSize: {
type: Number,
default: 5120,
},
fileType: {
type: Array,
default: () => [".rvt", ".ifc", ".dwg", ".obj", ".stl", ".fbx", ".3DS"],
},
// 是否显示提示
isShowTip: {
type: Boolean,
default: true,
},
// 是否显示进度条
isShowUploadModel: {
type: Boolean,
default: false,
},
// 是否显示重新上传按钮
isShowBtn: {
type: Boolean,
default: true,
},
// 是否禁用上传按钮
disabled: {
type: Boolean,
default: false,
},
},
data() {
return {
number: 0,
hideUpload: false,
fileList: [],
otherQuery: {}, //上传文件传递额外参数
uploadList: [],
modelFlag: false,
modelUploadPercent: 0,
isCancel: false,
};
},
watch: {
value: {
handler(val) {
if (val) {
console.log(val, "val");
if (typeof val == "string") {
// 首先将值转为数组
const list = Array.isArray(val) ? val : this.value.split(",");
// 然后将数组转为对象数组
this.fileList = list.map((item) => {
if (typeof item === "string") {
item = {
name: item.substring(item.lastIndexOf("/") + 14),
url: item,
};
}
return item;
});
} else {
val.forEach((el) => {
el.name = el.fileName;
});
this.fileList = val;
}
} else {
this.fileList = [];
return [];
}
},
deep: true,
immediate: true,
},
},
computed: {
// 是否显示提示
showTip() {
return this.isShowTip && (this.fileType || this.fileSize);
},
},
methods: {
//自定义上传方法..
Upload(file, data) {
console.log(file);
let OSS = require("ali-oss");
let client = new OSS({
region: data.region,
accessKeyId: data.accessKeyId,
accessKeySecret: data.accessKeySecret,
// accessKeyId: "",
// accessKeySecret: "",
bucket: "cscec83-openfile",
});
// let cdnUrl = data.cdnUrl;
let cdnUrl = "https://cscec83-openfile.oss-cn-shanghai.aliyuncs.com/";
this.number++;
this.isCancel = false;
const progress = (p, _checkpoint) => {
console.log(p);
// console.log(_checkpoint);
this.modelFlag = true;
this.modelUploadPercent = Number((Number(p) * 100).toFixed(1));
console.log(this.isCancel);
if (this.isCancel) {
client.cancel();
}
};
let fileName = "model/" + new Date().getTime() + file.file.name;
client
.multipartUpload(fileName, file.file, {
progress,
// 设置并发上传的分片数量。
// parallel: 4,
// 设置分片大小。默认值为1 MB,最小值为100 KB。
partSize: 5 * 1024 * 1024,
})
.then((res) => {
// console.log(res, "res");
this.modelFlag = false;
if (res.name) {
let obj = {
fileName: res.name,
name: res.name,
size: this.otherQuery.size,
url: cdnUrl + res.name,
};
console.log(cdnUrl + res.name);
this.uploadList.push(obj);
if (this.uploadList.length === this.number) {
this.fileList = this.fileList.concat(this.uploadList);
this.uploadList = [];
this.number = 0;
// let list = this.fileList.map((item) => {
// return {
// ...item,
// modelUrl: item.url,
// };
// });
this.$emit("input", this.fileList);
}
} else {
this.$modal.msgError("上传失败,请重试");
this.cancel();
}
})
.catch((err) => {
console.log(err);
if (err.name == "cancel") {
this.$message("上传取消");
} else {
this.$modal.msgError(err);
}
this.cancel();
});
},
handleDelete(file) {
const findex = this.fileList
.map((f) => f.fileName)
.indexOf(file.fileName);
if (findex > -1) {
this.fileList.splice(findex, 1);
this.$emit("input", this.fileList);
}
this.cancel();
},
// 上传前loading加载
beforeUpload(file) {
console.log(this.fileType, file.file.name);
this.otherQuery = {};
this.isCancel = false;
var fileSize = file.file.size / 1024 / 1024 < this.fileSize; //控制大小 修改50的值即可
let fileType = file.file.name.substring(file.file.name.lastIndexOf("."));
let isContinue = true;
if (
this.fileType.indexOf(fileType) == -1 //控制格式
) {
this.$modal.msgError(
`文件格式不正确, 请上传${this.fileType.join("/")}格式文件!`
);
this.fileList = [];
isContinue = false;
}
if (!fileSize) {
this.$modal.msgError(`上传视频大小不能超过 ${this.fileSize} MB!`);
this.fileList = [];
isContinue = false;
}
this.otherQuery.fileName = file.file.name;
this.otherQuery.size = (file.file.size / 1024 / 1024).toFixed(2);
if (!isContinue) return;
this.$axios.get("/file/ossFile/getOssParameter").then((res) => {
if (res.code == 200) {
this.Upload(file, res.data);
}
});
},
// 预览
handlePreview(file) {
window.open(file.url, "_blank");
},
// 文件个数超出
handleExceed() {
this.$message.error(`上传文件数量不能超过 ${this.limit} 个!`);
},
// 上传失败
handleUploadError() {
this.$modal.msgError("上传失败,请重试");
// this.loading.close();
},
cancel(type) {
this.isCancel = true;
this.modelFlag = false;
},
},
};
</script>
<style scoped lang="css">
::v-deep.hideUpload .el-upload--picture-card {
display: none;
}
::v-deep .el-upload--picture-card {
width: 104px;
height: 104px;
line-height: 104px;
}
::v-deep .el-upload-list--picture-card .el-upload-list__item {
width: 104px;
height: 104px;
}
.avatar-uploader-icon {
border: 1px dashed #d9d9d9 !important;
}
.avatar-uploader .el-upload {
border: 1px dashed #d9d9d9 !important;
border-radius: 6px !important;
position: relative !important;
overflow: hidden !important;
}
.avatar-uploader .el-upload:hover {
border: 1px dashed #d9d9d9 !important;
border-color: #409eff;
}
.avatar-uploader-icon {
font-size: 28px;
color: #8c939d;
width: 300px;
height: 178px;
line-height: 178px;
text-align: center;
}
.avatar {
width: 300px;
height: 178px;
display: block;
}
</style>
ImageUpload.vue
js
复制代码
<template>
<div class="component-upload-image">
<el-upload
multiple
:action="uploadImgUrl"
list-type="picture-card"
:on-success="handleUploadSuccess"
:before-upload="handleBeforeUpload"
:limit="limit"
:on-error="handleUploadError"
:on-exceed="handleExceed"
name="file"
:disabled="showIcon"
:on-remove="handleRemove"
:show-file-list="true"
:headers="headers"
:file-list="fileList"
:on-preview="handlePictureCardPreview"
:class="{
hideUpload: this.fileList.length >= this.limit || this.showIcon,
}"
>
<i class="el-icon-plus"></i>
</el-upload>
<!-- 上传提示 -->
<div class="el-upload__tip" slot="tip" v-if="showTip">
{{ tip }}
<template v-if="fileSize">
大小不超过 <b style="color: #f56c6c">{{ fileSize }}MB</b>
</template>
<template v-if="fileType">
格式为 <b style="color: #f56c6c">{{ fileType.join("/") }}</b>
</template>
</div>
<el-dialog
:visible.sync="dialogVisible"
title="预览"
width="800"
append-to-body
>
<img
:src="dialogImageUrl"
style="display: block; max-width: 100%; margin: 0 auto"
/>
</el-dialog>
</div>
</template>
<script>
export default {
props: {
value: [String, Object, Array],
// 图片数量限制
limit: {
type: Number,
default: 5,
},
// 大小限制(MB)
fileSize: {
type: Number,
default: 10,
},
// 文件类型, 例如['png', 'jpg', 'jpeg']
fileType: {
type: Array,
default: () => ["png", "jpg", "jpeg"],
},
tip: {
type: String,
},
// 是否显示提示
isShowTip: {
type: Boolean,
default: true,
},
// 是否禁用添加图片按钮
showIcon: {
type: Boolean,
default: false,
},
},
data() {
return {
number: 0,
uploadList: [],
dialogImageUrl: "",
dialogVisible: false,
hideUpload: false,
uploadImgUrl: "/prod-api" + "/file/upload", // 上传的图片服务器地址
headers: {
Authorization: this.$store.state.token,
},
fileList: [],
};
},
watch: {
value: {
handler(val) {
if (val) {
// 首先将值转为数组
const list = Array.isArray(val) ? val : this.value.split(",");
// 然后将数组转为对象数组
this.fileList = list.map((item) => {
if (typeof item === "string") {
item = { name: item, url: item };
}
return item;
});
} else {
this.fileList = [];
return [];
}
},
deep: true,
immediate: true,
},
},
computed: {
// 是否显示提示
showTip() {
return this.isShowTip && (this.fileType || this.fileSize);
},
},
methods: {
// 删除图片
handleRemove(file, fileList) {
const findex = this.fileList.map((f) => f.name).indexOf(file.name);
if (findex > -1) {
this.fileList.splice(findex, 1);
this.$emit("input", this.listToString(this.fileList));
}
},
// 上传成功回调
handleUploadSuccess(res) {
this.uploadList.push({ name: res.data.url, url: res.data.url });
if (this.uploadList.length === this.number) {
this.fileList = this.fileList.concat(this.uploadList);
this.uploadList = [];
this.number = 0;
this.$emit("input", this.listToString(this.fileList));
this.$modal.closeLoading();
}
},
// 上传前loading加载
handleBeforeUpload(file) {
let isImg = false;
if (this.fileType.length) {
let fileExtension = "";
if (file.name.lastIndexOf(".") > -1) {
fileExtension = file.name.slice(file.name.lastIndexOf(".") + 1);
}
isImg = this.fileType.some((type) => {
if (file.type.indexOf(type) > -1) return true;
if (fileExtension && fileExtension.indexOf(type) > -1) return true;
return false;
});
} else {
isImg = file.type.indexOf("image") > -1;
}
if (!isImg) {
this.$modal.msgError(
`文件格式不正确, 请上传${this.fileType.join("/")}图片格式文件!`
);
return false;
}
if (this.fileSize) {
const isLt = file.size / 1024 / 1024 < this.fileSize;
if (!isLt) {
this.$modal.msgError(`上传头像图片大小不能超过 ${this.fileSize} MB!`);
return false;
}
}
this.$modal.loading("正在上传图片,请稍候...");
this.number++;
},
// 文件个数超出
handleExceed() {
this.$modal.msgError(`上传文件数量不能超过 ${this.limit} 个!`);
},
// 上传失败
handleUploadError() {
this.$modal.msgError("上传图片失败,请重试");
this.$modal.closeLoading();
},
// 预览
handlePictureCardPreview(file) {
this.dialogImageUrl = file.url;
this.dialogVisible = true;
},
// 对象转成指定字符串分隔
listToString(list, separator) {
let strs = "";
separator = separator || ",";
for (let i in list) {
strs += list[i].url + separator;
}
return strs != "" ? strs.substr(0, strs.length - 1) : "";
},
},
};
</script>
<style scoped >
::v-deep.hideUpload .el-upload--picture-card {
display: none;
}
::v-deep .el-upload--picture-card {
width: 86px;
height: 86px;
line-height: 86px;
}
::v-deep .el-upload-list--picture-card .el-upload-list__item {
width: 86px;
height: 86px;
}
::v-deep .el-list-enter-active,
::v-deep .el-list-leave-active {
transition: all 0s;
}
::v-deep .el-list-enter,
.el-list-leave-active {
opacity: 0;
transform: translateY(0);
}
</style>
预览和下载
js
复制代码
<template>
<div id="index">
<el-card shadow="always" style="margin-top: 20px">
<div class="model pd-16">
<div class="flex mb-20">
<div class="title-left bold size-22">工程样板</div>
<div
class="flex_r point border-blue blue plr-10 ptb-4 bg-white radius-4 size-14"
@click="toTemplate"
>
查看更多 <i class="el-icon-right"></i>
</div>
</div>
<div class="flex_l">
<div
class="center mr-20 plr-20 ptb-10"
@click="changeTab(1)"
:style="
tab == 1
? 'background: linear-gradient(210.65deg, #5FA4FE 0%, #367EF8 100%);border-radius: 25px;color:#fff'
: ''
"
>
<img
:src="`${
tab == 1
? require('../../assets/img/zhi-tab-1-1.png')
: require('../../assets/img/zhi-tab-1.png')
}`"
alt=""
style="width: 27px"
/>
<div class="ml-16 size-18">图片区</div>
</div>
<div
class="center mr-20 plr-20 ptb-10"
@click="changeTab(2)"
:style="
tab == 2
? 'background: linear-gradient(210.65deg, #5FA4FE 0%, #367EF8 100%);border-radius: 25px;color:#fff'
: ''
"
>
<img
:src="`${
tab == 2
? require('../../assets/img/zhi-tab-4-1.png')
: require('../../assets/img/zhi-tab-4.png')
}`"
alt=""
style="width: 27px"
/>
<div class="ml-16 size-18">三维模型区</div>
</div>
<div
class="center mr-20 plr-20 ptb-10"
@click="changeTab(3)"
:style="
tab == 3
? 'background: linear-gradient(210.65deg, #5FA4FE 0%, #367EF8 100%);border-radius: 25px;color:#fff'
: ''
"
>
<img
:src="`${
tab == 3
? require('../../assets/img/zhi-tab-3-1.png')
: require('../../assets/img/zhi-tab-3.png')
}`"
alt=""
style="width: 27px"
/>
<div class="ml-16 size-18">图纸区</div>
</div>
<div
class="center mr-20 plr-20 ptb-10"
@click="changeTab(4)"
:style="
tab == 4
? 'background: linear-gradient(210.65deg, #5FA4FE 0%, #367EF8 100%);border-radius: 25px;color:#fff'
: ''
"
>
<img
:src="`${
tab == 4
? require('../../assets/img/zhi-tab-2-1.png')
: require('../../assets/img/zhi-tab-2.png')
}`"
alt=""
style="width: 27px"
/>
<div class="ml-16 size-18">文档区</div>
</div>
</div>
<el-row :gutter="20" class="mt-30">
<div v-if="templateList.length == 0" class="text-center">
<img src="../../assets/img/null.png" alt="" style="width: 4rem" />
<div>暂无数据</div>
</div>
<el-col :span="6" v-for="(item, i) in templateList" :key="i + 'c'">
<el-card shadow="hover" class="point course mb-12">
<el-image
v-if="tab == 1"
style="width: 100%; height: 170px; border-radius: 5px"
mode="aspectFill"
:src="item.pictureUrl"
:preview-src-list="[item.url.split(',')]"
>
</el-image>
<img
@click="preview(item)"
v-if="tab == 2 || tab == 3 || tab == 4"
:src="
tab == 4
? require(`../../assets/img/doc-${
item.docType || 'word'
}.png`)
: item.pictureUrl
"
style="width: 100%; height: 170px; border-radius: 5px"
mode="aspectFill"
/>
<div class="flex mtb-10 mlr-12">
<div class="line-1 mr-10">{{ item.modelName }}</div>
<img
style="width: 22px"
src="../../assets/img/download.png"
alt=""
@click.stop="download(item)"
/>
</div>
</el-card>
</el-col>
</el-row>
</div>
</el-card>
<modelPop ref="modelPopRef"></modelPop>
</div>
</div>
</template>
<script>
// npm install js-base64
import { Base64 } from "js-base64";
export default {
// layout: "default-all",
data() {
return {
tab: 1,
templateList: [],
};
},
mounted() {
this.getTemplateList();
},
methods: {
changeTab(tab) {
this.tab = tab;
this.getTemplateList();
},
// 下载
download(item) {
// 1 图片样板 2 三维样板 3 图纸样板 4 文档样板
if (this.tab == 1) {
this.$downloadZip.zip(
"/qualityTrain/sample/downloadAndZip?sampleId=" + item.sampleId,
item.modelName + "样板文件.zip"
);
} else if (this.tab == 2 || this.tab == 3) {
window.open(item.url);
} else if (this.tab == 4) {
fetch(item.url)
.then((res) => res.blob())
.then((blob) => {
const a = document.createElement("a");
const objectUrl = window.URL.createObjectURL(blob);
a.download =
item.modelName +
(item.docType == "ppt"
? ".ppt"
: item.docType == "excel"
? ".xls"
: item.docType == "word"
? ".doc"
: ".pdf");
a.href = objectUrl;
a.click();
});
}
},
getTemplateList() {
this.$axios
.get("/qualityTrain/sample/homePage", {
params: {
modelType: this.tab,
},
})
.then((res) => {
this.templateList = res.data;
});
},
// 预览
preview(item) {
// 1 图片样板 2 三维样板 3 图纸样板 4 文档样板
if (this.tab == 2) {
this.$refs.modelPopRef.init(item.sampleId, item.modelName, false, true);
} else if (this.tab == 3) {
let base = location.protocol + "//" + location.hostname + ":8012";
let url =
(base.includes("192.168.2.89") ? "http://192.168.0.19:8012" : base) +
"/onlinePreview?url=" +
encodeURIComponent(Base64.encode(item.url));
window.open(url, "_blank");
} else {
console.log(item.docType);
window.open(
(item.docType != "pdf"
? "https://view.officeapps.live.com/op/view.aspx?src="
: "") + item.url
);
}
},
},
};
</script>
downloadZip.js
js
复制代码
import axios from 'axios'
import { saveAs } from 'file-saver'
import { Loading, Message } from 'element-ui';
export default ({ store }, inject) => {
const zip = (url, name) => {
let loadingInstance = Loading.service({
lock: true,
text: '正在下载...',
spinner: 'el-icon-loading',
background: 'rgba(0, 0, 0, 0.7)'
});
const fullUrl = '/prod-api' + url;
axios({
method: 'get',
url: fullUrl,
responseType: 'blob',
timeout: 600000,
headers: { 'Authorization': store.state.token }
}).then(async (res) => {
const isLogin = await blobValidate(res.data);
loadingInstance.close();
if (isLogin) {
const blob = new Blob([res.data], { type: 'application/zip' })
saveAs(blob, name)
} else {
printErrMsg(res.data);
}
}).catch(() => {
loadingInstance.close();
});
};
// 验证是否为blob格式
async function blobValidate(data) {
try {
const text = await data.text();
JSON.parse(text);
return false;
} catch (error) {
return true;
}
}
let errorCode = {
'401': '认证失败,无法访问系统资源',
'403': '当前操作没有权限',
'404': '访问资源不存在',
'default': '系统未知错误,请反馈给管理员'
}
async function printErrMsg(data) {
const resText = await data.text();
const rspObj = JSON.parse(resText);
const errMsg = errorCode[rspObj.code] || rspObj.msg || errorCode['default']
Message.error(errMsg);
}
// 注入到全局上下文中
inject('downloadZip', {
zip,
});
}