2021年4月8日 23:18 by wst
vue完成图片上传功能,商品由封面图片(单张)和内容图片(多张)组成;
1. 显示图片预览,即响应中生成的图片url;
2. 实时修改图片,即通过已上传图片列表来删除或增加;
3. 封面图片和内容图片没有关系,相互独立;
4. 完全以来element ui 的upload组件实现;
5. 前后端分离开发;
问题1:图片上传的action存在跨域问题。
方案:设置开发代理(vue.config.js)
module.exports = {
baseUrl: './',
assetsDir: 'static',
productionSourceMap: false,
devServer: {
proxy: {
'/api':{
target:'https://api.xxxxx.com',
// target:'',
changeOrigin:true,
pathRewrite:{
'^/api':''
}
}
}
}
}
问题2:自己实现http-request,即自定义上传函数。但是和后续的函数无法对接,如上传成功回调。
方案:
(1)自定义(请求级别)上传功能,这里的request是经过axios重写过的,想知道怎么重写请百度。
export function upload_image(query){
return request({
url: '/boss/admin/upload/image',
method: 'post',
data: query,
headers: {'Content-Type': 'multipart/form-data'}
})
}
(2)组装formData数据并上传。
import { upload_image } from "../../api/index";
upLoadImage(param){
let formData = new FormData();
formData.append("file", param.file);
formData.append("files", param.files);
upload_image(formData).then(res=>{
this.form.file = res.data.file
})
}
问题3:为什么衔接不起来,问题出在哪里,百度、谷歌无果。只能去看源码。
方案:定制化action--"/api/boss/admin/upload/image"。思考:为什么不是"/boss/admin/upload/image"?
<el-form-item label="商品封面">
<el-upload
:action="uploadActionUrl"
:headers="myHeader"
:before-upload="beforeAvatarUpload"
:file-list="[form.file]"
:on-remove="handelAvatarRemove"
:on-exceed="handleAvatarExceed"
:on-success="upLoadAvatarSuccess"
accept="image/png, image/jpg, image/jpeg"
list-type="picture-card"
multiple
:limit="1"
>
<el-button size="small" type="primary">点击上传</el-button>
<div slot="tip" class="el-upload__tip">请上传图片格式文件</div>
</el-upload>
</el-form-item>
问题4:为了功能更友好,添加各种提示;
方案:(1)上传前图片格式和大小的验证beforeAvatarUpload;(2)上传成功回调upLoadAvatarSuccess;
(3)已卡片形式显示已上传的图片list-type="picture-card";(4)其他操作,如上传个数限制、headers定制;
<template>
<div>
<!-- 导航条 -->
<div class="crumbs">
<el-breadcrumb separator="/">
<el-breadcrumb-item>
<i class="el-icon-lx-goods"></i> 商品管理
</el-breadcrumb-item>
<el-breadcrumb-item>我的商品</el-breadcrumb-item>
</el-breadcrumb>
</div>
<div class="container">
<!-- 搜索框 -->
<div class="handle-box">
<el-button
type="primary"
icon="el-icon-delete"
class="handle-del mr10"
@click="delAllSelection"
>批量删除</el-button
>
<el-input
v-model="query.com_name"
placeholder="商品名称"
class="handle-input mr10"
></el-input>
<el-input
v-model="query.com_id"
placeholder="商品ID"
class="handle-input mr10"
></el-input>
<el-select
v-model="query.status"
placeholder="上架状态"
class="handle-select mr10"
>
<el-option key="1" label="已上架" value="1"></el-option>
<el-option key="2" label="已下架" value="0"></el-option>
</el-select>
<el-select
v-model="query.display_type"
placeholder="展示类型"
class="handle-select mr10"
>
<el-option key="0" label="全部商品" value="0"></el-option>
<el-option key="1" label="新人商品" value="1"></el-option>
<el-option key="2" label="热门商品" value="2"></el-option>
<el-option key="3" label="促销商品" value="3"></el-option>
</el-select>
<el-date-picker
class="ml10 mr10"
v-model="query.date"
value-format="yyyy-MM-dd"
type="daterange"
:unlink-panels="true"
:clearable="false"
>
</el-date-picker>
<el-button type="primary" icon="el-icon-search" @click="handleSearch"
>搜索</el-button
>
<el-button type="primary" icon="el-icon-close" @click="handleReset"
>重置</el-button
>
<el-button type="primary" @click="handleAdd"
><i class="el-icon-plus"></i
></el-button>
</div>
<!-- 表格内容 -->
<el-table
:data="tableData"
border
class="table"
ref="multipleTable"
header-cell-class-name="table-header"
@selection-change="handleSelectionChange"
>
<el-table-column
type="selection"
width="55"
align="center"
></el-table-column>
<el-table-column
prop="id"
label="ID"
width="55"
align="center"
></el-table-column>
<el-table-column prop="name" label="商品名"></el-table-column>
<el-table-column label="成本">
<template slot-scope="scope">¥{{ scope.row.cost }}</template>
</el-table-column>
<el-table-column label="售价">
<template slot-scope="scope">¥{{ scope.row.price }}</template>
</el-table-column>
<el-table-column label="商品封面" align="center">
<template slot-scope="scope">
<el-image
class="table-td-thumb"
:src="scope.row.pic_url"
:preview-src-list="[scope.row.pic_url]"
></el-image>
</template>
</el-table-column>
<el-table-column
prop="service_time"
label="服务时长(分钟)"
></el-table-column>
<el-table-column prop="display_type" label="展示类型"></el-table-column>
<el-table-column label="上架状态" align="center">
<template slot-scope="scope">
<el-tag
:type="
scope.row.status === '成功'
? 'success'
: scope.row.status === '失败'
? 'danger'
: ''
"
>{{ scope.row.status == 1 ? "已上架" : "已下架" }}</el-tag
>
</template>
</el-table-column>
<el-table-column prop="create_time" label="注册时间"></el-table-column>
<el-table-column label="操作" width="180" align="center">
<template slot-scope="scope">
<el-button
type="text"
icon="el-icon-edit"
@click="handleEdit(scope.$index, scope.row)"
>编辑</el-button
>
<el-button
type="text"
icon="el-icon-delete"
class="red"
@click="handleDelete(scope.$index, scope.row)"
>删除</el-button
>
</template>
</el-table-column>
</el-table>
<div class="pagination">
<el-pagination
background
layout="total, prev, pager, next"
:current-page="query.pageIndex"
:page-size="query.pageSize"
:total="dataCount"
@current-change="handlePageChange"
></el-pagination>
</div>
</div>
<!-- 编辑弹出框 -->
<el-dialog title="编辑" :visible.sync="editVisible" width="50%">
<el-form ref="form" :model="form" label-width="70px">
<el-form-item label="商品名">
<el-input v-model="form.name"></el-input>
</el-form-item>
<!-- 图片封面 -->
<el-form-item label="商品封面">
<el-upload
:action="uploadActionUrl"
:headers="myHeader"
:before-upload="beforeAvatarUpload"
:file-list="[form.file]"
:on-remove="handelAvatarRemove"
:on-exceed="handleAvatarExceed"
:on-success="upLoadAvatarSuccess"
accept="image/png, image/jpg, image/jpeg"
list-type="picture-card"
multiple
:limit="1"
>
<el-button size="small" type="primary">点击上传</el-button>
<div slot="tip" class="el-upload__tip">请上传图片格式文件</div>
</el-upload>
</el-form-item>
<!-- 图片列表 -->
<el-form-item label="内容图片">
<el-upload
:action="uploadActionUrl"
:on-remove="onRemoveImg"
:on-success="upLoadSuccess"
:on-error="uploadError"
:on-exceed="handleExceed"
:before-upload="beforeAvatarUpload"
accept="image/jpeg,image/gif,image/png"
list-type="picture-card"
multiple
:limit="3"
:headers="myHeader"
:file-list="form.files">
<el-button size="small" type="primary">点击上传</el-button>
<div slot="tip" class="el-upload__tip">请上传图片格式文件,可多选,但不能超过<el-tag type="danger">3</el-tag>张.</div>
</el-upload>
</el-form-item>
<el-form-item label="成本">
<el-input v-model="form.cost"></el-input>
</el-form-item>
<el-form-item label="售价">
<el-input v-model="form.price"></el-input>
</el-form-item>
<el-form-item label="服务时长">
<el-input v-model="form.service_time"></el-input>
</el-form-item>
<el-form-item label="商品简介">
<el-input type="textarea" rows="5" v-model="form.desc"></el-input>
</el-form-item>
<el-form-item label="商品描述">
<el-input type="textarea" rows="5" v-model="form.content"></el-input>
</el-form-item>
<el-form-item label="商品亮点">
<el-checkbox-group v-model="form.tags">
<el-checkbox label="快速" name="type"></el-checkbox>
<el-checkbox label="实惠" name="type"></el-checkbox>
<el-checkbox label="热心" name="type"></el-checkbox>
</el-checkbox-group>
</el-form-item>
<el-form-item label="展示类型">
<el-select v-model="form.display_type" placeholder="展示类型">
<el-option key="bbk" label="新人商品" value="1"></el-option>
<el-option key="xtc" label="热门商品" value="2"></el-option>
<el-option key="imoo" label="促销商品" value="3"></el-option>
</el-select>
</el-form-item>
<el-form-item label="上架状态">
<el-select v-model="form.status" placeholder="上架状态">
<el-option key="bbk" label="已上线" value="1"></el-option>
<el-option key="xtc" label="已下线" value="0"></el-option>
</el-select>
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="editVisible = false">取 消</el-button>
<el-button type="primary" @click="saveEdit">确 定</el-button>
</span>
</el-dialog>
</div>
</template>
<script>
import { admin_commodity, upload_image } from "../../api/index";
import { prevDate } from "element-ui/lib/utils/date-util";
import { parseTime } from "@/utils/date";
export default {
name: "basetable",
data() {
return {
myHeader: {session: 'test'},
uploadActionUrl: "/api/boss/admin/upload/image",
query: {
status: null,
display_type: null,
com_name: null,
com_id: null,
pageIndex: 1,
pageSize: 10,
date: [
// 默认时间区间
parseTime(prevDate(new Date(), 30), "{y}-{m}-{d}"),
parseTime(new Date(), "{y}-{m}-{d}"),
],
},
tableData: [],
multipleSelection: [],
delList: [],
editVisible: false,
dataCount: 0,
form: {},
idx: -1,
id: -1,
};
},
created() {
this.getData();
},
methods: {
// 获取 easy-mock 的模拟数据
getData() {
var query = {
status: this.query.status,
display_type: this.query.display_type,
name: this.query.com_name,
id: this.query.com_id,
start: this.query.date[0],
end: this.query.date[1],
page_size: this.query.pageSize,
page_index: this.query.pageIndex,
};
// console.log("query:", query);
admin_commodity(query).then((res) => {
// console.log(res);
this.tableData = res.data;
this.dataCount =
this.query.pageIndex == 1 ? res.data.total : this.dataCount;
});
},
// 触发搜索按钮
handleSearch() {
if (this.query.status == null) {
this.$message.error("请选择上架状态");
return false;
} else if (this.query.display_type == null) {
this.$message.error("请选择展示类型");
return false;
}
this.$set(this.query, "pageIndex", 1);
this.getData();
},
// 重置操作
handleReset() {
this.query.status = null;
this.query.display_type = null;
this.query.com_name = null;
this.query.com_id = null;
},
// 删除操作
handleDelete(index, row) {
// 二次确认删除
this.$confirm("确定要删除吗?", "提示", {
type: "warning",
})
.then(() => {
this.$message.success("删除成功");
this.tableData.splice(index, 1);
})
.catch(() => {});
},
// 多选操作
handleSelectionChange(val) {
this.multipleSelection = val;
},
delAllSelection() {
const length = this.multipleSelection.length;
let str = "";
this.delList = this.delList.concat(this.multipleSelection);
for (let i = 0; i < length; i++) {
str += this.multipleSelection[i].name + " ";
}
this.$message.error(`删除了${str}`);
this.multipleSelection = [];
},
// 编辑操作
handleEdit(index, row) {
this.idx = index;
this.form = row;
this.form.file = {name: row.pic_url.split("/")[row.pic_url.split("/").length-1], url: row.pic_url};
this.form.files = row.pic_list.map(x=>{return {name:x.split("/")[x.split("/").length-1], url:x}});
this.editVisible = true;
},
// 添加操作
handleAdd(index, row) {
this.form.file = null;
this.editVisible = true;
},
// 保存编辑
saveEdit() {
this.editVisible = false;
this.$message.success(`修改第 ${this.idx + 1} 行成功`);
this.$set(this.tableData, this.idx, this.form);
},
// 分页导航
handlePageChange(val) {
this.$set(this.query, "pageIndex", val);
this.getData();
},
// ---单张图片开始---
handleAvatarExceed(files, fileList) { //上传个数超出时的处理
this.$message.warning(`当前限制选择 1 个文件,本次选择了 ${files.length} 个文件,共选择了 ${files.length + fileList.length} 个文件`);
},
upLoadAvatarImage(param){ //自定义上传动作
let formData = new FormData();
formData.append("file", param.file);
upload_image(formData).then(res=>{
console.log("upLoadAvatarImage res:", res)
this.form.file = res.data.file
this.form.file.status = 'success'
this.form.file.uid = new Date().getTime()
// 上传成功提示
this.$message({
center: true,
message: '图片上传成功',
type: 'success',
})
})
},
handelAvatarRemove(file, fileList){ //删除列表里的图片
this.form.file = {url: null, name: null}
},
beforeAvatarUpload(file) { // 上传的验证
const isIMAGE = (file.type === 'image/jpeg' || file.type === 'image/png');
const isLt1M = file.size / 1024 / 1024 < 1;
if (!isIMAGE) {
this.$message.error({showClose: true, message: '只能上传jpg/png图片!'});
return false;
}
if (!isLt1M) {
this.$message.error({showClose: true, message: '上传文件大小不能超过 1MB!'});
return false;
}
},
upLoadAvatarSuccess(res, file){
this.form.file = res.data.file
this.$message({
center: true,
message: '图片['+res.data.file.name+']上传成功',
type: 'success',
})
},
// --结束---
// ---多张图片开始---
upLoadImage(param){ //上传前回调
let formData = new FormData();
formData.append("file", param.file);
upload_image(formData).then(res=>{
var index = this.form.files.filter(item=>item.name===res.data.file.name)
var i = this.form.files.indexOf(index)
var ele = res.data.file
ele.uid = new Date().getTime()
ele.status = 'success'
this.form.files.splice(i, 1, ele)
})
},
onRemoveImg(file, fileList){
this.form.files = fileList
},
//files是本次选择的文件
//fileList是当前uploader对象中待上传的文件列表
handleExceed(files, fileList) {
this.$message.warning(`当前限制选择 3 个文件,本次选择了 ${files.length} 个文件,共选择了 ${files.length + fileList.length} 个文件`);
},
upLoadSuccess(res, file){
file.url = res.data.file.url
this.$message({
center: true,
message: '图片['+res.data.file.name+']上传成功',
type: 'success',
})
},
uploadError(err, file, fileList){
console.log("err:", err)
}
},
};
</script>
<style scoped>
.handle-box {
margin-bottom: 20px;
}
.handle-select {
width: 120px;
}
.handle-input {
width: 300px;
display: inline-block;
}
.table {
width: 100%;
font-size: 14px;
}
.red {
color: #ff0000;
}
.mr10 {
margin-right: 10px;
}
.table-td-thumb {
display: block;
margin: auto;
width: 40px;
height: 40px;
}
.table-td-mid {
margin: auto;
width: 320px;
height: auto;
}
</style>