Skip to content

Commit 3a28aeb

Browse files
feat: upload
1 parent 205fb72 commit 3a28aeb

23 files changed

+696
-2
lines changed

Diff for: README.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@
3636

3737
[Message 组件](./message-component/)
3838

39+
[Upload 组件](./upload-component/)
40+
3941
[基于 React Router 实现 keepalive](./router-keepalive/)
4042

4143
[context 的性能缺点和解决方案](./context-trap/)
@@ -49,5 +51,3 @@
4951

5052

5153

52-
53-

Diff for: upload-component/.eslintrc.cjs

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
module.exports = {
2+
root: true,
3+
env: { browser: true, es2020: true },
4+
extends: [
5+
'eslint:recommended',
6+
'plugin:@typescript-eslint/recommended',
7+
'plugin:react-hooks/recommended',
8+
],
9+
ignorePatterns: ['dist', '.eslintrc.cjs'],
10+
parser: '@typescript-eslint/parser',
11+
plugins: ['react-refresh'],
12+
rules: {
13+
'react-refresh/only-export-components': [
14+
'warn',
15+
{ allowConstantExport: true },
16+
],
17+
},
18+
}

Diff for: upload-component/.gitignore

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# Logs
2+
logs
3+
*.log
4+
npm-debug.log*
5+
yarn-debug.log*
6+
yarn-error.log*
7+
pnpm-debug.log*
8+
lerna-debug.log*
9+
10+
node_modules
11+
dist
12+
dist-ssr
13+
*.local
14+
15+
# Editor directories and files
16+
.vscode/*
17+
!.vscode/extensions.json
18+
.idea
19+
.DS_Store
20+
*.suo
21+
*.ntvs*
22+
*.njsproj
23+
*.sln
24+
*.sw?
25+
26+
uploads/

Diff for: upload-component/README.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# upload 组件
2+
3+
npm install
4+
5+
npm run dev

Diff for: upload-component/index.html

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<!doctype html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
6+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
7+
<title>Vite + React + TS</title>
8+
</head>
9+
<body>
10+
<div id="root"></div>
11+
<script type="module" src="/src/main.tsx"></script>
12+
</body>
13+
</html>

Diff for: upload-component/package.json

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
{
2+
"name": "upload-component",
3+
"private": true,
4+
"version": "0.0.0",
5+
"type": "module",
6+
"scripts": {
7+
"dev": "vite",
8+
"build": "tsc && vite build",
9+
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
10+
"preview": "vite preview"
11+
},
12+
"dependencies": {
13+
"antd": "^5.16.0",
14+
"axios": "^1.6.8",
15+
"cors": "^2.8.5",
16+
"express": "^4.19.2",
17+
"multer": "^1.4.5-lts.1",
18+
"react": "^18.2.0",
19+
"react-dom": "^18.2.0"
20+
},
21+
"devDependencies": {
22+
"@types/react": "^18.2.66",
23+
"@types/react-dom": "^18.2.22",
24+
"@typescript-eslint/eslint-plugin": "^7.2.0",
25+
"@typescript-eslint/parser": "^7.2.0",
26+
"@vitejs/plugin-react": "^4.2.1",
27+
"eslint": "^8.57.0",
28+
"eslint-plugin-react-hooks": "^4.6.0",
29+
"eslint-plugin-react-refresh": "^0.4.6",
30+
"typescript": "^5.2.2",
31+
"vite": "^5.2.0"
32+
}
33+
}

Diff for: upload-component/public/vite.svg

+1
Loading

Diff for: upload-component/server.js

+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import express from 'express';
2+
import multer from 'multer';
3+
import cors from 'cors';
4+
import path from 'path';
5+
import fs from 'fs';
6+
7+
const app = express()
8+
app.use(cors());
9+
10+
const storage = multer.diskStorage({
11+
destination: function (req, file, cb) {
12+
try {
13+
fs.mkdirSync(path.join(process.cwd(), 'uploads'));
14+
}catch(e) {}
15+
cb(null, path.join(process.cwd(), 'uploads'))
16+
},
17+
filename: function (req, file, cb) {
18+
const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9) + '-' + file.originalname
19+
cb(null, uniqueSuffix)
20+
}
21+
});
22+
const upload = multer({
23+
dest: 'uploads/',
24+
storage
25+
})
26+
27+
app.post('/upload', upload.single('file'), function (req, res, next) {
28+
console.log('req.file', req.file);
29+
console.log('req.body', req.body);
30+
31+
res.end(JSON.stringify({
32+
message: 'success'
33+
}));
34+
})
35+
36+
app.listen(3333);

Diff for: upload-component/src/App.css

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
#root {
2+
max-width: 1280px;
3+
margin: 0 auto;
4+
padding: 2rem;
5+
text-align: center;
6+
}
7+
8+
.logo {
9+
height: 6em;
10+
padding: 1.5em;
11+
will-change: filter;
12+
transition: filter 300ms;
13+
}
14+
.logo:hover {
15+
filter: drop-shadow(0 0 2em #646cffaa);
16+
}
17+
.logo.react:hover {
18+
filter: drop-shadow(0 0 2em #61dafbaa);
19+
}
20+
21+
@keyframes logo-spin {
22+
from {
23+
transform: rotate(0deg);
24+
}
25+
to {
26+
transform: rotate(360deg);
27+
}
28+
}
29+
30+
@media (prefers-reduced-motion: no-preference) {
31+
a:nth-of-type(2) .logo {
32+
animation: logo-spin infinite 20s linear;
33+
}
34+
}
35+
36+
.card {
37+
padding: 2em;
38+
}
39+
40+
.read-the-docs {
41+
color: #888;
42+
}

Diff for: upload-component/src/App.tsx

+39
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import React from 'react';
2+
import { InboxOutlined, UploadOutlined } from '@ant-design/icons';
3+
import { Button } from 'antd';
4+
import Upload, { UploadProps } from './Upload'
5+
6+
const props: UploadProps = {
7+
name: 'file',
8+
action: 'http://localhost:3333/upload',
9+
beforeUpload(file) {
10+
if(file.name.includes('1.image')) {
11+
return false;
12+
}
13+
return true;
14+
},
15+
onSuccess(ret) {
16+
console.log('onSuccess', ret);
17+
},
18+
onError(err) {
19+
console.log('onError', err);
20+
},
21+
onProgress(percentage, file) {
22+
console.log('onProgress', percentage);
23+
},
24+
onChange(file) {
25+
console.log('onChange', file);
26+
}
27+
};
28+
29+
const App: React.FC = () => (
30+
<Upload {...props} drag>
31+
{/* <Button icon={<UploadOutlined />}>Click to Upload</Button> */}
32+
<p>
33+
<InboxOutlined style={{fontSize: '50px'}}/>
34+
</p>
35+
<p>点击或者拖拽文件到此处</p>
36+
</Upload>
37+
);
38+
39+
export default App;

Diff for: upload-component/src/Upload/Dragger.tsx

+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import { FC, useState, DragEvent, PropsWithChildren } from 'react'
2+
import classNames from 'classnames'
3+
4+
interface DraggerProps extends PropsWithChildren{
5+
onFile: (files: FileList) => void;
6+
}
7+
8+
export const Dragger: FC<DraggerProps> = (props) => {
9+
10+
const { onFile, children } = props
11+
12+
const [ dragOver, setDragOver ] = useState(false)
13+
14+
const cs = classNames('upload-dragger', {
15+
'is-dragover': dragOver
16+
})
17+
18+
const handleDrop = (e: DragEvent<HTMLElement>) => {
19+
e.preventDefault()
20+
setDragOver(false)
21+
onFile(e.dataTransfer.files)
22+
}
23+
24+
const handleDrag = (e: DragEvent<HTMLElement>, over: boolean) => {
25+
e.preventDefault()
26+
setDragOver(over)
27+
}
28+
29+
return (
30+
<div
31+
className={cs}
32+
onDragOver={e => { handleDrag(e, true)}}
33+
onDragLeave={e => { handleDrag(e, false)}}
34+
onDrop={handleDrop}
35+
>
36+
{children}
37+
</div>
38+
)
39+
}
40+
41+
export default Dragger;

Diff for: upload-component/src/Upload/UploadList.tsx

+63
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import { FC } from 'react'
2+
import { Progress } from 'antd';
3+
import { CheckOutlined, CloseOutlined, DeleteOutlined, FileOutlined, LoadingOutlined } from '@ant-design/icons';
4+
5+
export interface UploadFile {
6+
uid: string;
7+
size: number;
8+
name: string;
9+
status?: 'ready' | 'uploading' | 'success' | 'error';
10+
percent?: number;
11+
raw?: File;
12+
response?: any;
13+
error?: any;
14+
}
15+
16+
interface UploadListProps {
17+
fileList: UploadFile[];
18+
onRemove: (file: UploadFile) => void;
19+
}
20+
21+
export const UploadList: FC<UploadListProps> = (props) => {
22+
const {
23+
fileList,
24+
onRemove,
25+
} = props;
26+
27+
return (
28+
<ul className="upload-list">
29+
{
30+
fileList.map(item => {
31+
return (
32+
<li className={`upload-list-item upload-list-item-${item.status}`} key={item.uid}>
33+
<span className='file-name'>
34+
{
35+
(item.status === 'uploading' || item.status === 'ready') &&
36+
<LoadingOutlined />
37+
}
38+
{
39+
item.status === 'success' &&
40+
<CheckOutlined />
41+
}
42+
{
43+
item.status === 'error' &&
44+
<CloseOutlined />
45+
}
46+
{item.name}
47+
</span>
48+
<span className="file-actions">
49+
<DeleteOutlined onClick={() => { onRemove(item)}}/>
50+
</span>
51+
{
52+
item.status === 'uploading' &&
53+
<Progress percent={item.percent || 0}/>
54+
}
55+
</li>
56+
)
57+
})
58+
}
59+
</ul>
60+
)
61+
}
62+
63+
export default UploadList;

0 commit comments

Comments
 (0)