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)); // app\public\avatar\upload\20200312\1536895331666.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 = ( <div> <Icon type={this.state.loading ? 'loading' : 'plus'} /> <div className="ant-upload-text">上传</div> </div> ); const { imageUrl } = this.state; const { name = "avatar" } = this.props; return ( <Upload name={name} listType="picture-card" className="avatar-uploader" showUploadList={false} action={`${BaseUrl}/tools/saveavatar`} beforeUpload={beforeUpload} onChange={this.handleChange} > {imageUrl ? <img src={imageUrl} alt="avatar" style={{ width: '100%' }} /> : uploadButton} </Upload> ); } } export default Avatar;
调用
<Form.Item label="封面"> {getFieldDecorator('cover', { rules: [{ required: true, message: '请上传封面' }], initialValue: 'https://img2.mukewang.com/szimg/5d1032ab08719e0906000338.jpg' })(<UploadAvatar name={"cover"} />)} </Form.Item>
.