在之前的文章里,在实现组件的时候,有提到Upload组件,当时并没有对这个组件进行实现。
那我们现在思考一下,如果我们想实现Upload组件和Image组件,我们应该怎么去做。
Upload组件有一个很重要的属性是action,也就是上传的服务器位置。所以我们实现Upload组件之前,应该有一个服务器用来保存上传的图片。之后在Image组件里进行展示。
那这整套流程在我们的三个项目里是怎么样的呢?
XinBuilderServer:实现图片上传的功能,用来保存上传的图片。
AppBuilder: 实现图片上传的前端UI,支持查看上传的图片。
XinBuilder:在设计器里,支持Upload组件和Image组件。
OK,大体流程我们有了,现在我们开始进行实现。
现在我们来说一下,Upload上传的接口和之前我们实现的接口有什么区别。对于之前我们添加页面等接口,都是将数据存在数据库里面。
但是对于图片,我们不应该存在数据库里,因为他不是一个字符串或者JSON。所以我们采用服务端的存储,就是在服务端创建一个文件夹用来保存上传的图片。我们来到XinBuilderServer项目中,创建uploadImage模块:
先来到controller中,我们实现一下上传图片的接口:
import { Controller, Post,UseInterceptors,UploadedFile } from '@nestjs/common';
import { UploadService } from './upload-image.service';
// FileInterceptor用于单文件上传,FilesInterceptor用于多文件上传
import {FileInterceptor} from '@nestjs/platform-express'
import { ApiTags, ApiOperation } from '@nestjs/swagger'
@Controller('upload')
@ApiTags('图片管理')
export class UploadController {
constructor(private readonly uploadService: UploadService) {}
@Post('album')
@ApiOperation({summary: '添加图片'})
// UseInterceptors 处理文件的中间件,file是一个标识名
@UseInterceptors(FileInterceptor('file'))
// UploadedFile装饰器是用于读取文件的
upload (@UploadedFile() file) {
console.log("file:",file)
return file
}
}
定义好接口之后我们来到module中,对上传的图片进行处理,当你拿到图片之后,我们应该存放在定义好的目录里面:
import { Module } from '@nestjs/common';
import { UploadService } from './upload-image.service';
import { UploadController } from './upload-image.controller';
//文件上传需要的包
import { MulterModule } from '@nestjs/platform-express';
import { diskStorage } from 'multer';
import { join } from 'path';
const path = require('path')
const iconv = require('iconv-lite');
@Module({
//里面有register 和 registerAsync 两个方法,前者是同步的,后者是异步的
imports: [MulterModule.register({
//图片上传完要存放的位置
storage: diskStorage({
destination: join(path.resolve(__dirname, '../../') + '/public', 'images'),//存放的文件路径
filename: (req, file, callback) => {
//重新定义文件名,file.originalname 文件的原始名称
// extname 获取文件后缀
let fileName = iconv.decode(file.originalname, 'utf-8');
//返回新的名称,参数1是错误,这里用null就好
return callback(null, fileName)
}
}),
}
)],
controllers: [UploadController],
providers: [UploadService]
})
export class UploadModule { }
这段代码的意思就是,我们存放在了src下的public下的images中:
OK,这样上传图片的接口,我们就写好了(不要问为什么这么写,博主也不太懂,单纯的在网上找的。。。o.O)
有了上传图片的接口之后,我们还要写一个获取图片列表的接口,我们来到service中:
import { Injectable } from '@nestjs/common';
const fs = require('fs')
import { join, extname } from 'path';
const path = require('path')
@Injectable()
export class UploadService {
findAllImage() {
const imagePath = join(path.resolve(__dirname, '../..') + '/public', 'images')
const list = fs.readdirSync(imagePath);
return list
}
}
只需要获取到public下的文件目录即可。
然后回到controller中,我们补充一下接口:
@Post('findAllImage')
@ApiOperation({summary: '获取图片列表'})
findAllImage () {
return this.uploadService.findAllImage()
}
OK,现在我们可以将图片上传到服务器上了,但是我们怎么访问呢,比如我知道了文件名称,我们怎么访问这个文件呢?
我们要修改一下访问服务器的静态路径,来到main.ts中:
我们给app添加一下静态路径,也就是当你访问localhost:4000的时候,访问的就是public目录 app.useStaticAssets('public');
到这里,我们上传图片和查看图片列表的接口,就完成了。
相关的代码提交在github上:
https://github.com/TeacherXin/XinBuilderServer2
commit: fix: 第三节,实现图片上传和查看图片列表的接口
OK,现在我们有了上传图片和查看图片列表的接口,我们来到AppBuilder中,把前端的UI界面进行实现。
我们在主页面中,添加一个图片管理,作为入口。
增加一个路由用来写图片相关的页面:
app-builder\src\index.tsx
import React, { Suspense } from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import Page from './routes/page';
import UploadImage from './routes/uploadImage';
import { HashRouter as Router, Routes , Route} from "react-router-dom";
const root = ReactDOM.createRoot(
document.getElementById('root') as HTMLElement
);
root.render(
<Router>
<Suspense>
<Routes>
<Route path={'/'} element={<Page />}></Route>
<Route path={'/uploadImage'} element={<UploadImage />}></Route>
</Routes>
</Suspense>
</Router>
);
在UploadImage组件中,我们要做到的效果是:
可以上传查看图片,由于组件的实现不是很难,所以这里我把源码贴出来,附加注释。
import { useState, useEffect } from 'react'
import { Divider, Image, message, Upload } from 'antd'
import { FileImageOutlined, LoadingOutlined, PlusOutlined } from '@ant-design/icons'
import axios from 'axios'
import './index.css'
const getBase64 = (img: any,callback: Function) => {
const reader = new FileReader();
reader.addEventListener("load", () => callback(reader.result));
reader.readAsDataURL(img);
}
const beforeUpload =(file: any) => {
const isJpgOrPng = file.type === "image/jpeg" || file.type === "image/png";
if (!isJpgOrPng) {
message.error("You can only upload JPG/PNG file!");
}
const isLt2M = file.size / 1024 / 1024 < 2;
if (!isLt2M) {
message.error("Image must smaller than 2MB!");
}
return isJpgOrPng && isLt2M;
}
export default function UploadImage() {
const [imageList, setImageList] = useState([])
const [img, setImg] = useState("");
const [loading, setLoading] = useState(false);
useEffect(() => {
getImageList()
}, [])
/**
* 获取图片列表
*/
const getImageList = () => {
axios.post('http://localhost:4000/upload/findAllImage')
.then(res => {
if(res.data) {
setImageList(res.data)
}
})
}
/**
* 上传图片的按钮
*/
const uploadButton = (
<div>
{loading ? <LoadingOutlined /> : <PlusOutlined />}
<div style={{ marginTop: 8 }}>Upload</div>
</div>
);
/**
* 上传图片后,更新展示图片
* @param info 上传图片的信息
* @returns
*/
const handleChange = (info: any) => {
if (info.file.status === "uploading") {
setLoading(true);
return;
}
if (info.file.status === "done") {
getBase64(info.file.originFileObj, () => {
setLoading(false);
setImg(`http://localhost:4000/images/` + info.file.response.filename);
getImageList()
});
}
};
/**
* 点击图片列表中的某一项时,更新Image组件
* @param imageName 图片名称
* @returns
*/
const getImage = (imageName: string) => {
return () => {
setImg(`http://localhost:4000/images/` + imageName);
}
}
return (
<div className='PageList'>
<div className='pageLeft'>
<div className='leftHeader'>XinBuilder</div>
<div className='leftDiscribe'>图片管理平台</div>
<Divider />
<div>
{
imageList.map((item, index) => {
return <div style={img.includes(item) ? {backgroundColor:'#edeaeb'} : {}} onClick={getImage(item)} key={index} className='imageItem'>
<FileImageOutlined style={{marginRight:'10px'}}/>
{item}
</div>
})
}
</div>
</div>
<div className='imageRight'>
<Upload
name="file"
listType="picture-card"
showUploadList={false}
action={`http://localhost:4000/upload/album`}
beforeUpload={beforeUpload}
onChange={handleChange}
>
{
uploadButton
}
</Upload>
<Image
width={500}
src={img}
/>
</div>
</div>
)
}
相关的代码提交在github上:
https://github.com/TeacherXin/AppBuilder
commit: fix: 第二节:实现上传图片以及查看图片列表详情的路由页面
有了上面的基础,我们就可以在下一篇中实现Upload组件和Image组件了。