前言
在Vue3项目中,如果我们想上传图片一般可以利用element-ui中的el-upload
,为了避免代码的重复,我们可以自己封装一个图片上传组件。
其中,主要实现思想为前端利用el-upload组件选择上传的图片,并利用其http-request
属性来自定义函数来实现文件上传请求:该请求函数使用七牛云的对象存储,在通过后端得到的上传凭证token后来实现文件上传。
后端代码
使用express框架,获取七牛云上传凭证并响应给前端
项目结构
1 2 3 4 5 6
| - routes |- token.js |- index.js - app.js - config.js - package.json
|
安装七牛云的SDK:
获取上传凭证
编写获取上传凭证的相关代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| /* config.js */ const qiniu = require('qiniu') // 创建上传凭证 const accessKey = '*****' // 这里填写七牛云的accessKey const secretKey = '*****'// 这里填写七牛云的secretKey const mac = new qiniu.auth.digest.Mac(accessKey, secretKey) const options = { scope: '*****', // 这里填写七牛云空间名称 expires: 60 * 60 * 24 * 7 // 这里是凭证的有效时间,默认是一小时 } const putPolicy = new qiniu.rs.PutPolicy(options) const uploadToken = putPolicy.uploadToken(mac) module.exports = { uploadToken }
|
配置路由
token.js
1 2 3 4 5 6 7 8
| const tokenRouter = require('express').Router() const qnconfig = require('../config') // 引入七牛云配置 tokenRouter.get('/qiniu', (req, res, next) => { res.status(200).send(qnconfig.uploadToken) }) module.exports = tokenRouter
|
index.js
1 2 3 4 5
| const token = require('./token') module.exports = routes = (app) => { app.use('/token', token) // 可以通过/token/qiniu的方式获取上传凭证 }
|
项目启动
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| const express = require('express') const bodyparse = require('body-parser') const routers = require('./route') // 创建服务 const app = express() // 解析数据 app.use(bodyparse.json()) // 路由 routes(app) // 监听3000端口 app.listen(3000, () => { console.log('this server are running on localhost:3000!') })
|
使用命令node app.js
启动项目,这时访问http://localhost:3000/token/qiniu
即可获取上传凭证了。
前端代码
配置跨域
由于前后端项目运行在不同的端口,因此需要解决跨域问题,这里在vite.config.js中解决如下:
1 2 3 4 5 6 7 8 9
| server: { proxy: { '/api': { target: 'http://localhost:3000', changeOrigin: true, rewrite: (path) => path.replace(/^/api/, '') } } }
|
父组件使用
我们希望子组件上传图片得到一串url后父组件能接受到,并且在展示上传图片时其尺寸应能指定或者有默认值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| <template> <Upload :url="imageUrl" @upload="changeUrl" /> </template> <script setup> import Upload from '@/components/Upload.vue' import { ref } from 'vue' const imageUrl = ref('') const changeUrl = (url) => { imageUrl.value = url } </script>
|
封装组件Upload.vue
这里只是简单使用axios,没有对其进行封装。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109
| <template> <!- action="https://upload-z2.qiniup.com":每个地区访问域名不同,具体可通过 https://developer.qiniu.com/kodo/1671/region-endpoint-fq 查看 -> <el-upload class="avatar-uploader" action="https://upload-z2.qiniup.com" :show-file-list="false" :http-request="up2qiniu" :before-upload="beforeUpload" > <img v-if="props.url" :src="props.url" class="avatar" :style="'width: ' + props.width + 'px;' + 'height: ' + props.height + 'px;'" /> <el-icon v-else class="avatar-uploader-icon" :style="'width: ' + props.width + 'px;' + 'height: ' + props.height + 'px;'" ><Plus /></el-icon> </el-upload> </template>
<script setup> import { ref } from 'vue' import { getQiniuToken } from '../api/token' import axios from 'axios' import { ElMessage } from 'element-plus'
const qiniuaddr = 'rlr92qkze.hn-bkt.clouddn.com' // 这里是七牛云存储对象中的CDN域名 const imageUrl = ref('') // 父组件传值时,须有图片的url;其次可选择图片的宽高(默认都为180) const props = defineProps({ url: String, width: { type: Number, default: 180 }, height: { type: Number, default: 180 } }) const emit = defineEmits(['upload'])
const beforeUpload = (rawFile) => { if (rawFile.type !== 'image/jpg' && rawFile.type !== 'image/png') { ElMessage.error('图片格式应该是png或jpg') return false } else if (rawFile.size / 1024 / 1024 > 2) { ElMessage.error('图片大小应该小于2MB') return false } return true }
/** * 上传图片至七牛云 * @param {*} req */ const up2qiniu = (req) => { const config = { headers: { 'Content-Type': 'multipart/form-data' } } const fileType = req.file.type === 'image/png' ? 'png' : 'jpg' // 重命名要上传的文件 const keyname = 'blog' + new Date().getTime() + '.' + fileType axios.get('/api/token/qiniu').then(res => { const formdata = new FormData() formdata.append('file', req.file) formdata.append('token', res.data) formdata.append('key', keyname) // 获取到凭证之后再将文件上传到七牛云空间 axios.post('https://upload-z2.qiniup.com', formdata, config).then((res) => { imageUrl.value = 'http://' + qiniuaddr + '/' + res.data.key emit('upload', imageUrl.value) // 向父组件传递图片的url }) }) } </script>
<style lang="scss" scoped> .avatar-uploader .avatar { width: 360px; height: 180px; display: block; } .avatar-uploader :deep(.el-upload) { border: 1px dashed var(--el-border-color); border-radius: 6px; cursor: pointer; position: relative; overflow: hidden; transition: var(--el-transition-duration-fast); }
.avatar-uploader :deep(.el-upload:hover) { border-color: var(--el-color-primary); }
.el-icon.avatar-uploader-icon { font-size: 28px; color: #8c939d; width: 360px; height: 180px; text-align: center; } </style>
|