注:
1. 一般情况不会让前端直接操作oss(增删查),可是我遇到了这样的需求
2. 默认已经开通oss,在oss官网完成了配置
3. 需要使用到element-ui的upload组件
点击跳转oss的官方API
点击跳转element的官方API
需求描述
- 前端直接操作oss,类型可以是任何类型。本demo以图片和压缩包为例,图片可以预览、删除;压缩包可以下载、删除。
- 在表单填写时,一个表单可能有多个不同类型文件的需要上传;表单涉及新增/查看/编辑功能,复用性要强。
- 新增表单的具体需求:表单新增之后oss即可编辑,一旦新增过文件后要么删除已上传文件,要么提交表单,提交不成功根据后端提示重新修改表单的错误重新提交,或者删除放弃提交。
- 查看表单的具体需求:查看表单不可操作oss,但是对于图片可以进行预览,压缩包可以进行下载。
- 修改表单的具体需求:一旦修改过文件(删除,新增,修改)必须提交表单,提交不成功根据后端提示重新修改表单的错误重新提交,直到成功为止。
- F5强刷也要保证上述5条,私密麻生,我太难了,我没做
效果展示
新增
查看
、
编辑
图片预览(放大)
新增但不提交的提示
删除但不提交的提示
修改但不提交的提示
前期准备
- npm install ali-oss
- 需要运维提供
accessKeyId
,accessKeySecret
,bucket
- 其中
bucket
最好区分开发环境和生产环境 - oss官方推荐使用STS临时授权访问,我这里没有用,有兴趣的话可以移步OSS临时授权
utils.js(工具类)
import axios from 'axios'
import OSS from 'ali-oss'
import UUID4 from 'uuid/v4'
// 下载oss指定地址
export function downloadURL(url) {
let link = document.createElement('a');
link.style.display = 'none';
link.href = url;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}
// 上传oss
export function ossUpload(picForProject, fileBuffer) {
let bucketName = '';
// 判断环境 生产环境并且不为测试状态
process.env.NODE_ENV === 'production' ? bucketName = '填你自己申请的' : bucketName = '填你自己申请的';
let client = new OSS({
region: 'oss-cn-beijing',
secure: true, // https
accessKeyId: '填你自己申请的',
accessKeySecret: '填你自己申请的',
bucket: bucketName
});
const fileName = UUID4().toUpperCase().replace(/-/g, '')
const suffix = fileBuffer.name.substr(fileBuffer.name.indexOf('.'))
const url = `${picForProject}${fileName}${suffix}`
async function put() {
try {
await client.put(url, fileBuffer)
// await xxx(url) 在这里也可以直接把地址传给后端
return { 'code': 200, 'url': url }
} catch (e) {
return e.message
}
}
return put()
};
// 获取oss地址
export function showPrivateOss(picName) {
// 这里和上传oss的client是一样的,应该放在store里面同一管理的,demo这里写在这里了
let bucketName = '';
process.env.NODE_ENV === 'production' ? bucketName = '填你自己申请的' : bucketName = '填你自己申请的';
let client = new OSS({
secure: true, // https
accessKeyId: '填你自己申请的',
accessKeySecret: '填你自己申请的',
bucket: bucketName,
endpoint: 'oss-cn-beijing.aliyuncs.com'
});
async function getOssAddr() {
try {
let signUrl = client.signatureUrl(picName, { expires: 1800 }); // expires单位为秒
return { 'code': 200, 'url': signUrl }
} catch (e) {
return e.message
}
}
return getOssAddr()
};
// 删除oss
export function deletePrivateOss(picName) {
let bucketName = '';
process.env.NODE_ENV === 'production' ? bucketName = '填你自己申请的' : bucketName = '填你自己申请的';
let client = new OSS({
secure: true, // https
accessKeyId: '填你自己申请的',
accessKeySecret: '填你自己申请的',
bucket: bucketName,
endpoint: 'oss-cn-beijing.aliyuncs.com'
});
async function deleteOssAddr() {
try {
return client.delete(picName);
} catch (e) {
return e.message
}
}
return deleteOssAddr()
};
oss.vue(oss组件)
<template>
<div>
<el-upload
class="avatar-uploader"
action="oss地址"
:show-file-list="false"
:http-request="uploadToOss"
:before-upload="beforeAvatarUpload"
:disabled="disabled"
:accept="accept"
:typeName="typeName"
>
<div v-loading="loading">
<template v-if="haveUrl">
<img :src="imgUrl" class="avatar" />
<span class="show_icon" v-if="!disabled">
<i class="el-icon-view" @click.stop="handleView()" v-if="typeName==='图片'"></i>
<i class="el-icon-download" @click.stop="handleDownload()" v-else></i>
<i class="el-icon-delete" @click.stop="handleRemove()"></i>
</span>
<span class="show_icon" v-else>
<i class="el-icon-view" @click.stop="handleView()" v-if="typeName==='图片'"></i>
<i class="el-icon-download" @click.stop="handleDownload()" v-else></i>
</span>
</template>
<i v-else class="el-icon-plus avatar-uploader-icon"></i>
</div>
</el-upload>
<!--查看图片-->
<el-dialog title="照片详情" :visible.sync="showBigPic" width="720px" append-to-body>
<img :src="imgUrl" width="100%" />
</el-dialog>
</div>
</template>
<script>
// 引用工具类(下载指定地址的oss文件、上传oss、展示oss图片、删除指定地址的oss文件)
import {
downloadURL,
ossUpload,
showPrivateOss,
deletePrivateOss,
} from "@/assets/scripts/utils";
// 二次封装的element的message组件,代码略
import { errorMsg, successMsg } from "@/components/message";
export default {
props: {
//对文件的限制
limit: {
type: Object,
default: function () {
return {
size: 5,
types: ["image/jpeg", "image/png"],
};
},
},
// 是否有url
haveUrl: {
type: Boolean,
default: false,
},
// 是否禁用
disabled: {
type: Boolean,
default: false,
},
// url的名称
urlName: {
type: String,
default: "",
},
// 对应的后端字段名,同一页面可能有多个需要上传oss的
name: {
type: String,
default: "",
},
// 上传弹窗,过滤文件类型
accept: {
type: String,
default: "image/*",
},
// 上传文件类型(文件大小过大提示语也会用到)
typeName: {
type: String,
default: "图片",
},
},
data() {
return {
showBigPic: false, // 预览图片
imgUrl: null, // 图片地址
ossFileName: "", // oss文件名
loading: false, // 是否上传中
};
},
watch: {
urlName: {
handler(newValue, oldValue) {
if (newValue) {
if (this.typeName !== "图片" && this.haveUrl) {
this.imgUrl = require("@/assets/images/uploaded.jpg");
} else {
showPrivateOss(newValue).then((res) => {
if (res && res.code) {
this.imgUrl = res.url;
} else {
errorMsg(res);
}
});
}
}
},
immediate: true,
},
},
methods: {
handleView() {
this.showBigPic = true;
},
handleDownload() {
showPrivateOss(this.urlName).then((res) => {
if (res && res.code) {
downloadURL(res.url);
} else {
errorMsg(res);
}
});
},
handleRemove() {
let _this = this;
deletePrivateOss(this.urlName).then((res) => {
if (res.res.status === 204) {
successMsg();
// ossDone参数:url文件地址, haveUrl是否有地址, name字段名
this.$emit("callback", null, false, _this.name);
} else {
errorMsg(res);
}
});
},
// 上传oss
uploadToOss(elUpload) {
this.loading = true;
let _this = this;
ossUpload("back-image/" + this.ossFileName, elUpload.file).then((res) => {
if (res && res.code) {
// ossDone参数:url文件地址, haveUrl是否有地址, name字段名
this.$emit("callback", res.url, true, _this.name);
} else {
errorMsg(res);
}
this.loading = false;
});
},
// 图片上传前 验证图片大小
beforeAvatarUpload(file) {
let isSize = false;
if (this.limit.sizeType === "kb") {
isSize = file.size / 1024 < this.limit.size;
} else {
isSize = file.size / 1024 / 1024 < this.limit.size;
}
if (!isSize) {
errorMsg(
`${this.typeName}大小不能超过${this.limit.size}${
this.limit.sizeType ? this.limit.sizeType : "MB"
}!`
);
}
return isSize;
},
},
};
</script>
<style lang="less" scoped>
.show_icon {
display: inline-block;
position: absolute;
top: 0;
right: 0;
width: 108px;
height: 72px;
i {
display: none;
}
&:hover {
background-color: rgba(0, 0, 0, 0.5);
i {
display: inline;
line-height: 72px;
color: #fff;
font-size: 20px;
&:not(:last-child) {
margin-right: 10px;
}
}
}
}
</style>
addOrEdit.vue(表单组件;新增/查看/编辑为同一个组件)
/*
**这是一个弹窗组件,为了代码简洁,删除了其他表单内容
**父组件传来id,即判定是查看/编辑,需要请求后端详情
**:before-close需要带参数,重要!!
*/
<template>
<el-dialog
@open="setData"
:visible.sync="params.show"
:before-close="() => cancel(false)"
:close-on-click-modal="false"
width="1100px"
append-to-body
>
<div slot="title">
{{params.title}}
<span class="title_required">
<span>*</span>为必填项
</span>
</div>
<el-form
:model="addForm"
:inline="true"
ref="addForm"
label-position="left"
label-width="125px"
>
// 其他item略
<el-form-item
label="营业执照照片"
prop="businessLicenseUrl"
:rules="[{required: true,message: '不能为空'}]"
>
<oss
@callback="ossDone"
:haveUrl="haveUrl_businessLicenseUrl"
:disabled="isDisabled"
:urlName="addForm.businessLicenseUrl"
name="businessLicenseUrl"
/>
</el-form-item>
<el-form-item label="附件">
<oss
@callback="ossDone"
:limit="limit"
:haveUrl="haveUrl_attachment"
:urlName="addForm.attachment"
:disabled="isDisabled"
accept=".zip, .rar"
name="attachment"
typeName="压缩包"
/>
</el-form-item>
</el-form>
<span slot="footer">
<el-button @click="cancel(false)" class="cancelBtn">取 消</el-button>
<el-button
@click="submit"
type="primary"
class="confirmBtn"
v-if="this.params.title !=='查看'"
>确 定</el-button>
</span>
</el-dialog>
</template>
<script>
import oss from "@/components/oss";
import { methodPost } from "@/api"; // 新增/修改用【代码略】
import { GET_DETAILS } from "@/api/company"; //详情【代码略】
import { errorMsg, successMsg } from "@/components/message"; // 二次封装的message【代码略】
export default {
components: { oss },
data() {
return {
// 是否有执照
haveUrl_businessLicenseUrl: false,
// 是否有附件
haveUrl_attachment: false,
// 编辑时,附件的返回值(作为flag)
flag_attachment: null,
// 编辑时,执照的返回值(作为flag)
flag_businessLicenseUrl: null,
// 附件的大小
limit: {
size: 10
}
};
},
methods: {
// 操作oss后的$emit
ossDone(url, haveUrl, name) {
this.$set(this.addForm, `${name}`, url);
this.$set(this, `haveUrl_${name}`, haveUrl);
},
// 弹窗初始化
setData() {
this.addForm = {};
this.isDisabled = false;
if (this.params.title !== "新增") {
this.haveUrl_businessLicenseUrl = true;
this.getDetails({ id: this.params.id });
} else {
this.haveUrl_attachment = false;
this.haveUrl_businessLicenseUrl = false;
this.flag_attachment = null;
this.flag_businessLicenseUrl = null;
}
},
getDetails(id) {
GET_DETAILS(id).then((res) => {
if (res.code === 10000) {
this.addForm = res.result;
this.flag_attachment = res.result.attachment;
this.flag_businessLicenseUrl = res.result.businessLicenseUrl;
if (res.result.attachment) {
this.haveUrl_attachment = true;
} else {
this.haveUrl_attachment = false;
}
}
if (this.params.title === "查看") {
this.isDisabled = true;
}
});
},
submit() {
// 新增/编辑
this.$refs.addForm.validate((valid) => {
if (valid) {
let url = "/company/";
if (this.params.data) {
url += "update";
} else {
url += "insert";
}
methodPost(url, this.addForm, true).then((res) => {
if (res.code === 10000) {
successMsg();
this.cancel(true);
}
});
} else {
errorMsg();
}
});
},
cancel(refresh) {
if (this.params.title !== "新增") {
if (!refresh) {
if (this.flag_attachment !== this.addForm.attachment) {
errorMsg("修改附件后,请点击‘确定’按钮");
return;
}
if (
this.flag_businessLicenseUrl !== this.addForm.businessLicenseUrl
) {
errorMsg("修改营业执照后,请点击‘确定’按钮");
return;
}
}
} else {
if (this.addForm.businessLicenseUrl || this.addForm.attachment) {
errorMsg("点击‘确定’提交新增内容,或删除已上传文件");
return;
}
}
this.$refs.addForm.resetFields();
this.$emit("callback", refresh); // 通知父组件刷新list页,按实际取舍,父组件代码略
},
},
};
</script>
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。