- 上节课说到,我们要实现新增商品缺少了一个【
cover
】字段,我们需要封装一个上传组件来实现图片上传
- 基于【
element-ui
】的【el-upload
】来实现封装
- 在【
src/components
】文件夹下新建一个公共文件夹【Common
】,在这个文件内新建一个公共文件【UploadImg.vue
】
- 然后在需要引入的页面,引入这个图片上传组件
<el-form-item label="图片上传">
<UploadImg></UploadImg>
</el-form-item>
import UploadImg from '@/components/Common/UploadImg.vue';
components: { UploadImg }
- 下面开始封装图片上传组件
- 先敲【
vbase
】快速生成组件模版
<template>
<div>
</div>
</template>
<script>
export default {
}
</script>
<style lang="scss" scoped>
</style>
<el-upload
class="upload-demo"
action=""
:http-request="uploadImgMainImg"
:file-list="fileList"
accept="image/png, image/jpeg, image/jpg"
:limit="limit"
list-type="picture-card"
:on-remove="handleRemove"
:on-change="handleEditChange"
:class="{ hide: hideUploadEdit }"
>
<i slot="default" class="el-icon-plus"></i>
</el-upload>
- 首先来解释下参数的定义
-
action
是必选参数,上传的地址
- 但是我们要实现自己的上传方法就要用到
http-request
-
http-request
是覆盖默认的上传行为,可以自定义上传的实现
-
http-request
定义的一个上传方法uploadImgMainImg
,当执行上传的时候会调用此方法
-
file-list
是上传的文件列表, 例如: [{name: 'food.jpg', url: 'https://xxx.cdn.com/xxx.jpg'}]
-
accept
是规定上传的文件格式
-
limit
是最大允许上传个数
-
list-type
是文件列表的类型,也就是组件的样式
-
on-remove
是移除已上传文件触发的钩子函数
-
on-change
是文件状态改变时的钩子,添加文件、上传成功和上传失败时都会被调用
- 其中
on-remove
、on-change
两个钩子函数会回调两个参数,一个是当前上传的文件file,一个是已经上传的文件列表fileList
-
:class="{ hide: hideUploadEdit }"
是动态绑定class
-
hideUploadEdit
是一个变量,或者是true或者是false,当为true的时候,给这个组件的dom上绑定一个class类名叫hide
-
<i slot="default" class="el-icon-plus"></i>
是一个icon图标插槽
image.png
- 看上图报错
"hideUploadEdit" is not defined
,是因为组件身上使用了一个hideUploadEdit
变量,但是在data内我们没有定义这个变量,所以导致报错
- 需要在data中定义下
data() {
return {
hideUploadEdit:false,//让他默认为false
}
},
image.png
- 看上图报错
"fileList" is not defined
,是因为组件身上使用了一个fileList
变量,但是在data内我们没有定义这个变量,所以导致报错
- 需要在data中定义下
data() {
return {
fileList: [],,//让他默认为空数组
}
},
image.png
- 看上图报错
"limit" is not defined
,是因为组件身上使用了一个limit
变量,但是在data内我们没有定义这个变量,所以导致报错
- 需要在data中定义下
data() {
return {
limit:1,//让他默认为1,就是只能上传一张图片
}
},
image.png
- 看上图报错
"handleRemove" is not defined
,是因为组件身上定义了一个handleRemove
方法,但是在methods内我们没有映射这个方法,所以导致报错
- 需要在
methods
中映射下
methods: {
/** 文件移除触发的钩子函数 */
handleRemove(file,fileList){
}
},
image.png
- 看上图报错
"handleEditChange" is not defined
,是因为组件身上定义了一个handleEditChange
方法,但是在methods内我们没有映射这个方法,所以导致报错
- 需要在
methods
中映射下
methods: {
/** 文件上传触发的钩子函数 */
handleEditChange(file,fileList){
}
},
image.png
- 看上图报错
"uploadImgMainImg" is not defined
,是因为组件身上定义了一个uploadImgMainImg
方法,但是在methods内我们没有映射这个方法,所以导致报错
- 需要在
methods
中映射下
methods: {
/** 文件上传触发的自定义方法 ,回调file文件对象,在这个方法内执行上传方法 */
uploadImgMainImg(file){
alert("开始上传")
}
},
- 可以先来测试下,执行上传后触发的
handleEditChange
方法和uploadImgMainImg
方法的回调参数是什么?
image.png
image.png
- 当我们上传大于等于
limit
张数的图片时,即1张,上传按钮不应该在出现菜合理
image.png
- 这个时候,我们需要处理下这种情况
- 就是在
on-change
回调的方法里,通过判断上传的图片的张数是否大于等于limit
的张数,如果条件成立,就把hideUploadEdit
设置为true
methods:{
handleEditChange(file,fileList){
console.log(file);
console.log(fileList);
this.hideUploadEdit = fileList.length>=this.limit
},
}
- 这时候,就给上传组件的dom元素上绑定了一个class类名
hide
image.png
- 我们可以通过
display:none
来动态隐藏这个上传按钮区域,记住要根据hide
这个class类名来走位
.hide{
::v-deep .el-upload--picture-card{
display: none;
}
}
image.png
- 然而,当删除图片时,上传按钮区域还要在显示出来,不然没办法再次上传了,其实很简单,只需要在
:on-remove="handleRemove"
定义的方法里,将hideUploadEdit
改为false即可
methods:{
handleRemove(file,fileList){
console.log(file);
console.log(fileList);
this.hideUploadEdit = false
}
}
- 下面我们就来实现上传
- 先说下上传背景 :我们采用的是客户端直传七牛云服务器,但是需要后端提供一个上传token,即
uploadToken
,所以需要请求一个接口来获取上传token
- 然后前端还需要安装七牛云js插件
cnpm install qiniu-js --save
/**
*
* @returns 获取上传token
*/
export function getQiniuUpToken(params) {
return request({
url: '/api/getQiniuUpToken',
method: 'get',
params
})
}
import {getQiniuUpToken} from '@/api/user';
import * as qiniu from 'qiniu-js';
image.png
- 在
:http-request="uploadImgMainImg"
触发的方法内直接请求获取上传token的接口方法,拿到【图片``baseUrl``图片路径前缀
】,和【七牛云上传token
】,然后在根据七牛云上传流程调用qiniu-js的方法实现上传,代码如下
methods:{
uploadImgMainImg(file) {
console.log(file);
let _this = this;
let baseUrl;
let config = { useCdnDomain: true, region: qiniu.region.z0 };
let putExtra = { fname: file.file.name, params: {}, mimeType: null };
//开始上传 token 需要找后端获取(单独的方法)
getQiniuUpToken().then((res) => {
let upToken = res.data.uploadToken;
baseUrl = res.data.baseUrl;
let headers = qiniu.getHeadersForMkFile(upToken);
//file 是获取到的文件对象
//key 是文件名字,传null将使用hash值来当作文件名
let observable = qiniu.upload(
file.file,
file.file.name,
upToken,
headers,
putExtra,
config
);
this.subscription = observable.subscribe(observe);
});
let observe = {
next(res) {
// console.log('已上传大小,单位为字节:' + res.total.loaded)
// console.log('本次上传的总量控制信息,单位为字节:' + res.total.size)
// console.log('当前上传进度,范围:0~100:' + res.total.percent);
console.log(res);
},
error(err) {
// console.log(err.code)
// console.log(err.message)
// console.log(err.isRequestError)
// console.log(err.reqId)
console.log(err);
},
/*完成后的操作*/
complete(res) {
//上传成功以后会返回key 和 hash key就是文件名了!
console.log(res);
let fileUrl = baseUrl + res.key;
_this.$message.success("上传成功");
console.log(fileUrl);
},
};
},
}
image.png
- 下面想要把这个图片url传递给父组件的
ruleFrom的cover字段
,需要用到自定义事件
,供父组件调用
- 在刚才的上传回调监听
complete
内自定义事件
complete(res) {
//上传成功以后会返回key 和 hash key就是文件名了!
console.log(res);
let fileUrl = baseUrl + res.key;
_this.$message.success("上传成功");
console.log(fileUrl);
_this.$emit("onSuccessFun", fileUrl);
},
image.png
- 然后父组件可以触发
onSuccessFun
这个自定义事件,接收到图片链接,并赋值给cover
<el-form-item label="图片上传">
<UploadImg @onSuccessFun="handleSuccessFun"></UploadImg>
</el-form-item>
methods:{
handleSuccessFun(url){
this.ruleForm.cover = url
}
}
- 上传成功后,ruleForm内的cover就有值了
image.png
image.png
- 然后处理下提交表单的方法,弹出新增成功提示,并回退到列表页面
submitForm(formName) {
// this.$refs.ruleForm
this.$refs.ruleForm.validate((valid) => {
if (valid) {
// alert('submit!');
// 接口请求
// console.log(this.ruleForm);
addShop(this.ruleForm)
.then((res) => {
console.log(res);
this.$message.success("新增成功")
this.$router.go(-1)
})
.catch((err) => {
console.log(err);
});
} else {
console.log("error submit!!");
return false;
}
});
},
- 最后要完善一点,就是在上传图片成功后,如果用户发现上传错了,需要删除重新上传,这时候,就需要处理下这种情况,因为如果只是单纯的删掉上传区域的图片,
ruleForm
内cover
的值并没有被删掉,如果直接提交新增的话,会带着被删掉的图,新增成功,很明显,这是不合理的,怎么处理呢?
- 就是要在上传组件的
:on-remove="handleRemove"
触发的方法内,也自定义一个事件 this.$emit("onRemoveFun")
methods:{
handleRemove(file, fileList) {
console.log(file);
console.log(fileList);
this.hideUploadEdit = false;
this.$emit("onRemoveFun")
},
}
- 父组件调用这个方法
@onRemoveFun="handleRemoveFun"
<el-form-item label="图片上传">
<UploadImg @onSuccessFun="handleSuccessFun" @onRemoveFun="handleRemoveFun"></UploadImg>
</el-form-item>
- 在methods里也映射下
handleRemoveFun
handleRemoveFun(){
this.ruleForm.cover = "";
}
- 下面实现编辑修改商品信息
- 先找到【编辑】按钮,给它定义一个点击事件
@click="handleUpdate(scope.row)"
,因为要拿到当前行的数据ID,所以要传参数,然后在methods
里映射这个方法
image.png
- 然后跳转页面,跳转到新增商品的页面,因为页面都是一样的布局,我们完全可以复用一个页面,
只不过要传递当前行的id过去
methods:{
/** 点击编辑按钮触发的方法 */
handleUpdate(row){
this.$router.push("/shopModel/addShop?id="+row._id)
}
}
- 跳转过去后,地址栏会有一个id参数,我们需要获取到这个id参数来请求接口,【
进行数据回显
】,也是通过地址栏是否有id这个参数
来判定当前页面是新增商品页面
还是修改商品信息页面
image.png
- 然后在mounted生命周期里获取地址栏参数,能获取到就请求
获取指定商品详情的接口
,在这之前,先根据接口文档,新建api接口方法
/**
*
* @returns 获取指定商品信息 /api/query/goods/:id
*/
export function getShopDetail(id) {
return request({
url: '/api/query/goods/'+id,
method: 'get',
})
}
import { getShopDetail } from "@/api/user";
- 然后在mounted生命周期里获取地址栏参数,能获取到就请求
获取指定商品详情的接口
image.png
mounted() {
let id = this.$route.query.id
console.log(id);
},
image.png
mounted() {
this.getShopMenuListFun();
let id = this.$route.query.id
console.log(id);
if(id){
getShopDetail(id).then(res=>{
console.log(res);
})
}
},
image.png
- 然后做数据回显,将
res.data
赋值给ruleForm
,但是图片
要单独处理下
mounted() {
this.getShopMenuListFun();
let id = this.$route.query.id
console.log(id);
if(id){
getShopDetail(id).then(res=>{
console.log(res);
this.ruleForm = res.data //将`res.data`赋值给`ruleForm`
})
}
},
image.png
- 上传图片组件的数据回显,要用到
fileList
,直接拿到该组件的fileList,然后赋值即可,前提是先获取到上传组件的fileList
- 要想获取到上传组件的
fileList
,就要先获取到这个组件
- 通过【
ref
】来获取组件,在通过this.$refs.ref的名称.fileList
来获取
<el-form-item label="图片上传">
<UploadImg @onSuccessFun="handleSuccessFun"
@onRemoveFun="handleRemoveFun"
ref="uploadCom"></UploadImg>
</el-form-item>
image.png
mounted() {
this.getShopMenuListFun();
let id = this.$route.query.id
console.log(id);
if(id){
getShopDetail(id).then(res=>{
console.log(res);
this.ruleForm = res.data
this.$refs.uploadCom.fileList = [{name:'key.png',url:res.data.cover}]
})
}
},
image.png
- 可以直接将上传组件的
hideUploadEdit
设置成true
即可
mounted() {
this.getShopMenuListFun();
let id = this.$route.query.id
console.log(id);
if(id){
getShopDetail(id).then(res=>{
console.log(res);
this.ruleForm = res.data
this.$refs.uploadCom.fileList = [{name:'key.png',url:res.data.cover}]
this.$refs.uploadCom.hideUploadEdit = true
})
}
},
image.png
- 再往下,就处理下【
立即创建
】按钮,修改信息不能调用新增按钮
,要根据id来区分,如果地址栏有id,就要调用修改商品信息
的接口
- 修改商品信息的接口,我们之前已经创建好了,直接引入到这个页面来
import { updateShop } from "@/api/user";
image.png
<el-form-item>
<el-button type="primary" v-if="!$route.query.id" @click="submitForm">立即创建</el-button>
<el-button type="primary" v-if="$route.query.id" @click="submitUpdate">立即修改</el-button>
<el-button>重置</el-button>
</el-form-item>
- 在methods里映射
submitUpdate
方法
methods:{
submitUpdate(){
updateShop(this.ruleForm,this.$route.query.id).then(res=>{
this.$message.success("修改成功");
this.$router.go(-1)
})
}
}
网友评论