1.安装时间处理 及 压缩 模块
yarn add silly-datetime pump
2.文件保存路径
config/config.default.js
config.uploadDir = 'app/public/avatar/upload';
3.创建tools service
app/service/tools.js
'use strict';
const Service = require('egg').Service;
const path = require("path");
const sd = require('silly-datetime');
const mkdirp = require('mkdirp');
class ToolsService extends Service {
/**
* 获取文件上传目录
* @param {*} filename
*/
async getUploadFile(filename) {
// 1.获取当前日期
let day = sd.format(new Date(), 'YYYYMMDD');
// 2.创建图片保存的路径
let dir = path.join(this.config.uploadDir, day);
await mkdirp(dir); // 不存在就创建目录
let date = Date.now(); // 毫秒数
// 返回图片保存的路径
let uploadDir = path.join(dir, date + path.extname(filename));
// apppublicavatarupload202003121536895331666.png
return {
uploadDir,
saveDir: this.ctx.origin + uploadDir.slice(3).replace(/\/g, '/')
}
}
}
module.exports = ToolsService;
4.调用 controller
app/controller/article.js
// 保存头像/封面
async saveAvatar() {
const { ctx } = this;
const parts = ctx.multipart({ autoFields: true });
let files = {};
let stream;
while ((stream = await parts()) != null) {
if(!stream.filename){
break;
}
const fieldname = stream.fieldname; // file表单的名字
// 上传图片的目录
const dir = await this.service.tools.getUploadFile(stream.filename);
const target = dir.uploadDir;
const writeStream = fs.createWriteStream(target);
await pump(stream, writeStream);
files = Object.assign(files, {
[fieldname]: dir.saveDir
});
}
if(Object.keys(files).length > 0){
ctx.body = {
code: 200,
message: '图片上传成功',
data: files
}
}else{
ctx.body = {
code: 500,
message: '图片上传失败',
data: {}
}
}
}
5.配置路由
// 上传图片/头像/封面
router.post('/tools/saveavatar', controller.article.saveAvatar);
6.客户端页面逻辑代码
封装组件
src/components/UploadAvatar.js
/**
* 上传头像/封面
*/
import React from 'react';
import { Upload, Icon, message } from 'antd';
import { BaseUrl } from '../../utils/constants';
function getBase64(img, callback) {
const reader = new FileReader();
reader.addEventListener('load', () => callback(reader.result));
reader.readAsDataURL(img);
}
function beforeUpload(file) {
const isJpgOrPng = file.type === 'image/jpeg' || file.type === 'image/png';
if (!isJpgOrPng) {
message.error('仅支持JPG/PNG文件');
}
const isLt2M = file.size / 1024 / 1024 < 2;
if (!isLt2M) {
message.error('图片必须小于2MB!');
}
return isJpgOrPng && isLt2M;
}
class Avatar extends React.Component {
state = {
loading: false,
};
componentDidMount() {
if(this.props.value) this.setState({ imageUrl: this.props.value});
}
handleChange = info => {
const { name = "avatar"} = this.props;
if (info.file.status === 'uploading') {
this.setState({ loading: true });
return;
}
if (info.file.status === 'done') {
// 获取服务器返回值
const { response } = info.file;
if(response.data){
this.props.onChange && this.props.onChange(response.data[name]);
}
getBase64(info.file.originFileObj, imageUrl =>
this.setState({
imageUrl,
loading: false,
}),
);
}
};
render() {
const uploadButton = (
);
const { imageUrl } = this.state;
const { name = "avatar" } = this.props;
return (
name={name}
listType="picture-card"
className="avatar-uploader"
showUploadList={false}
action={`${BaseUrl}/tools/saveavatar`}
beforeUpload={beforeUpload}
onChange={this.handleChange}
>
{imageUrl ? : uploadButton}
);
}
}
export default Avatar;
调用
{getFieldDecorator('cover', {
rules: [{ required: true, message: '请上传封面' }],
initialValue: 'https://img2.mukewang.com/szimg/5d1032ab08719e0906000338.jpg'
})()}
.