改成自己的router,基本完成网络通信
This commit is contained in:
父節點
d8c40cfe59
當前提交
41f5239ae6
11
README.md
11
README.md
@ -1,4 +1,9 @@
|
|||||||
# inMyHeartFrontEnd
|
# 新双心 - 前端
|
||||||
|
|
||||||
# 开发或编译前
|
## 维护者必读
|
||||||
请根据`env.example.json`的格式按照自己的需求编写`env.json`,或者直接运行`cp env.example.json env.json`,否则无法通过编译!
|
|
||||||
|
### 开发或编译前
|
||||||
|
请根据`env.example.json`的格式按照自己的需求编写`env.json`,或者直接运行`cp env.example.json env.json`,**否则无法正确处理网络请求!**
|
||||||
|
|
||||||
|
### 网络请求的用法
|
||||||
|
引入./src/helper/axios.js(请以相对路径引入),尽情使用其中的post、get方法,它们会返回一个Promise(或可被视作Promise的AsyncFunction),且不会以抛出异常的方式通知网络错误,它会在服务器返回内容中插入属性`networkStatus`,**当值为200时才可以视作正确的服务器响应**,当值为-1时说明这是用户的网络错误,**你不需要处理200以外的情况,代码能够发出弹窗并建议用户重试**
|
@ -9,8 +9,8 @@
|
|||||||
"react": "^17.0.2",
|
"react": "^17.0.2",
|
||||||
"react-dom": "^17.0.2",
|
"react-dom": "^17.0.2",
|
||||||
"react-dropzone": "^11.3.4",
|
"react-dropzone": "^11.3.4",
|
||||||
"react-router-dom": "^5.2.0",
|
"react-scripts": "4.0.3",
|
||||||
"react-scripts": "4.0.3"
|
"sweetalert2": "^11.0.20"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "react-scripts start",
|
"start": "react-scripts start",
|
||||||
|
@ -1,18 +1,19 @@
|
|||||||
import './App.css';
|
import './App.css';
|
||||||
import { HashRouter, Route } from 'react-router-dom';
|
import { Route, SingleRouter } from './components/SingleRouter/SingleRouter';
|
||||||
import { AppContainer } from './index/index';
|
import { AppContainer } from './index/index';
|
||||||
import { UploadContainer } from './upload/upload';
|
import { UploadContainer } from './upload/upload';
|
||||||
import { LogInContainer } from './login/login';
|
import { LogInContainer } from './login/login';
|
||||||
import { ReviewContainer } from './review/review';
|
import { ReviewContainer } from './review/review';
|
||||||
|
import History from './helper/history';
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
return (
|
return (
|
||||||
<HashRouter>
|
<SingleRouter history={History}>
|
||||||
<Route path="/" exact component={AppContainer} />
|
<Route path="/" component={AppContainer} />
|
||||||
<Route path="/upload" component={UploadContainer} />
|
<Route path="/upload" component={UploadContainer} />
|
||||||
<Route path="/login" component={LogInContainer} />
|
<Route path="/login" component={LogInContainer} />
|
||||||
<Route path="/review" component={ReviewContainer} />
|
<Route path="/review" component={ReviewContainer} />
|
||||||
</HashRouter>
|
</SingleRouter>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
9
src/components/SingleRouter/Frame.css
Normal file
9
src/components/SingleRouter/Frame.css
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
.full-window {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.full-window > div {
|
||||||
|
background-size: cover !important;
|
||||||
|
background-position: center !important;
|
||||||
|
}
|
63
src/components/SingleRouter/SingleRouter.js
Normal file
63
src/components/SingleRouter/SingleRouter.js
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
import './Frame.css';
|
||||||
|
import { Component } from 'react';
|
||||||
|
import History from '../../helper/history';
|
||||||
|
|
||||||
|
export class SingleRouter extends Component {
|
||||||
|
historyRemover = null;
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
page: null
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
this.historyRemover = History.init(() => {
|
||||||
|
this.setState({
|
||||||
|
page: this.getPage()
|
||||||
|
});
|
||||||
|
});
|
||||||
|
this.setState({
|
||||||
|
page: this.getPage()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
this.historyRemover?.();
|
||||||
|
}
|
||||||
|
|
||||||
|
getPage() {
|
||||||
|
const path = History.getHref();
|
||||||
|
return this.props.children.filter(component =>
|
||||||
|
component && (!component.props.path ||
|
||||||
|
path === component.props.path ||
|
||||||
|
path.replace(/[\d]*$/, ':id') === component.props.path)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div className="full-window">
|
||||||
|
{this.state.page}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Route(props) {
|
||||||
|
return (
|
||||||
|
<div path={props.path}>
|
||||||
|
{props.children ?? <props.component />}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Redirect(props) {
|
||||||
|
if (History.getHref().match(props.from))
|
||||||
|
History.force(props.to);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Link(props) {
|
||||||
|
return (<a href={`/#${props.to}`} {...props}>{props.children}</a>);
|
||||||
|
}
|
45
src/helper/alert.js
Normal file
45
src/helper/alert.js
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
import Swal from 'sweetalert2';
|
||||||
|
|
||||||
|
const SelfSwal = Swal.mixin({
|
||||||
|
customClass: {
|
||||||
|
confirmButton: 'btn-round-full-single',
|
||||||
|
cancelButton: 'btn-round-full-single',
|
||||||
|
},
|
||||||
|
buttonsStyling: false,
|
||||||
|
heightAuto: false
|
||||||
|
});
|
||||||
|
|
||||||
|
// ajax
|
||||||
|
export const success = content => {
|
||||||
|
return SelfSwal.fire({
|
||||||
|
html: content,
|
||||||
|
showConfirmButton: false,
|
||||||
|
timer: 1000,
|
||||||
|
icon: 'success'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
export const failed = (content, afterRetry) => {
|
||||||
|
return afterRetry ?
|
||||||
|
SelfSwal.fire({
|
||||||
|
html: content,
|
||||||
|
showCancelButton: true,
|
||||||
|
confirmButtonText: '重试',
|
||||||
|
cancelButtonText: '放弃'
|
||||||
|
})
|
||||||
|
.then(result => result.isConfirmed ? afterRetry() : null) :
|
||||||
|
SelfSwal.fire({
|
||||||
|
html: content,
|
||||||
|
showConfirmButton: false,
|
||||||
|
timer: 1000,
|
||||||
|
icon: 'error'
|
||||||
|
})
|
||||||
|
.then(() => null);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const alert = (content) => {
|
||||||
|
return SelfSwal.fire({
|
||||||
|
html: content,
|
||||||
|
confirmButtonText: '了解',
|
||||||
|
icon: 'warning'
|
||||||
|
});
|
||||||
|
}
|
@ -1,18 +1,62 @@
|
|||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
|
import { failed } from './alert';
|
||||||
|
|
||||||
export function get(url) {
|
export function get(url) {
|
||||||
return axios.get(url, {
|
return send(
|
||||||
|
axios.get(url, {
|
||||||
headers: {
|
headers: {
|
||||||
Authorization: 'Bearer ' + localStorage.getItem('jwt'),
|
Authorization: 'Bearer ' + localStorage.getItem('jwt'),
|
||||||
"Allow-Control-Allow-Origin": "*"
|
"Allow-Control-Allow-Origin": "*"
|
||||||
}
|
}
|
||||||
});
|
}),
|
||||||
|
() => get(url)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
export function post(url, data) {
|
export function post(url, data) {
|
||||||
return axios.post(url, data, {
|
return send(
|
||||||
|
axios.post(url, data, {
|
||||||
headers: {
|
headers: {
|
||||||
Authorization: 'Bearer ' + localStorage.getItem('jwt'),
|
Authorization: 'Bearer ' + localStorage.getItem('jwt'),
|
||||||
"Allow-Control-Allow-Origin": "*"
|
"Allow-Control-Allow-Origin": "*"
|
||||||
}
|
}
|
||||||
});
|
}),
|
||||||
|
() => post(url, data)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const waitToSend = [];
|
||||||
|
|
||||||
|
async function send(xhr, retryFunc) {
|
||||||
|
if (waitToSend.length) {
|
||||||
|
waitToSend.push(retryFunc);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const { data } = await xhr;
|
||||||
|
return {
|
||||||
|
...data,
|
||||||
|
networkStatus: 200
|
||||||
|
};
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
const failData = {
|
||||||
|
networkStatus: err?.response?.status ?? -1,
|
||||||
|
status: false
|
||||||
|
};
|
||||||
|
waitToSend.push(retryFunc);
|
||||||
|
if (err.message === 'Network Error')
|
||||||
|
return await failed('您的设备似乎断网了,请检查网络后重试或刷新', flushWaitList) || failData;
|
||||||
|
else if (err?.response?.status === 401)
|
||||||
|
window.history.replaceState({}, '', '#/login');
|
||||||
|
else if (err?.response?.status === 504)
|
||||||
|
return await failed('请求超时,请耐心等待几秒后重试或刷新', flushWaitList) || failData;
|
||||||
|
else
|
||||||
|
return await failed('服务器出现问题,请稍后重试或刷新', flushWaitList) || failData;
|
||||||
|
return failData;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function flushWaitList() {
|
||||||
|
waitToSend.forEach(retryFunc => retryFunc());
|
||||||
|
waitToSend.splice(0, waitToSend.length);
|
||||||
}
|
}
|
98
src/helper/history.js
Normal file
98
src/helper/history.js
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
const listeners = [];
|
||||||
|
const statics = {
|
||||||
|
nowDepth: null,
|
||||||
|
forceURL: null
|
||||||
|
};
|
||||||
|
|
||||||
|
export default class History {
|
||||||
|
static push(url, state) {
|
||||||
|
// console.log('href: push', url, 'over', this.getHref());
|
||||||
|
if (url === this.getHref() || this.getHref() === '/forceback') {
|
||||||
|
// console.log('href: push ignored.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
window.history.pushState({state, depth: ++statics.nowDepth}, null, '#' + url);
|
||||||
|
listeners.forEach(fn => fn());
|
||||||
|
}
|
||||||
|
static replace(url, state) {
|
||||||
|
// console.log('href: substitute', url, 'for', this.getHref());
|
||||||
|
if (url === this.getHref() || this.getHref() === '/forceback') {
|
||||||
|
// console.log('href: replace ignored.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
window.history.replaceState({state, depth: statics.nowDepth}, null, '#' + url);
|
||||||
|
listeners.forEach(fn => fn());
|
||||||
|
}
|
||||||
|
static force(url) {
|
||||||
|
// console.log('href: force', this.getHref(), 'to', url);
|
||||||
|
if (url === this.getHref()) return;
|
||||||
|
const depth = window.history.state?.depth || 0;
|
||||||
|
if (depth) {
|
||||||
|
this.go(-depth);
|
||||||
|
statics.forceURL = url;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
this.replace(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
static listen(listener) {
|
||||||
|
listeners.push(listener);
|
||||||
|
return () => {
|
||||||
|
for (let i in listeners)
|
||||||
|
if (listeners[i] === listener)
|
||||||
|
listeners.splice(i, 1);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
static go(d) {
|
||||||
|
window.history.go(d);
|
||||||
|
// console.log("go", d);
|
||||||
|
}
|
||||||
|
static back() {
|
||||||
|
if (statics.nowDepth === 0)
|
||||||
|
this.replace('/');
|
||||||
|
else
|
||||||
|
window.history.back();
|
||||||
|
// console.log("goback")
|
||||||
|
}
|
||||||
|
static forward() {
|
||||||
|
window.history.forward();
|
||||||
|
// console.log("goforward")
|
||||||
|
}
|
||||||
|
|
||||||
|
static getLocation() {
|
||||||
|
return document.location;
|
||||||
|
}
|
||||||
|
static getHref() {
|
||||||
|
return document.location.href.match(/#[^#]*$/)?.[0]?.replace('#', '') ?? '/';
|
||||||
|
}
|
||||||
|
static getId() {
|
||||||
|
return this.getHref().match(/[\d]*$/)?.[0] ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
static init(pageChangeHandler) {
|
||||||
|
if (statics.nowDepth != null) return;
|
||||||
|
statics.nowDepth = window.history.state?.depth || 0;
|
||||||
|
window.addEventListener('hashchange', () => {
|
||||||
|
// console.log('href: hash changed to:', this.getHref());
|
||||||
|
listeners.forEach(fn => fn());
|
||||||
|
});
|
||||||
|
return this.listen(() => {
|
||||||
|
const depth = window.history.state?.depth || 0;
|
||||||
|
if (depth > statics.nowDepth) {
|
||||||
|
this.go(statics.nowDepth - depth);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
statics.nowDepth = depth;
|
||||||
|
if (statics.forceURL) {
|
||||||
|
const replaceURL = statics.forceURL;
|
||||||
|
statics.forceURL = null;
|
||||||
|
this.replace(replaceURL);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// 触发换页
|
||||||
|
pageChangeHandler()
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
@ -4,6 +4,7 @@ import Spinner from '../components/Spinner/Spinner';
|
|||||||
import UploadUnit from '../components/UploadUnit/UploadUnit';
|
import UploadUnit from '../components/UploadUnit/UploadUnit';
|
||||||
import { post } from '../helper/axios';
|
import { post } from '../helper/axios';
|
||||||
import { apis } from '../helper/apis';
|
import { apis } from '../helper/apis';
|
||||||
|
import { alert } from '../helper/alert';
|
||||||
|
|
||||||
import './upload.css';
|
import './upload.css';
|
||||||
|
|
||||||
@ -39,8 +40,13 @@ export class UploadContainer extends Component {
|
|||||||
if (this.state.submitting && (!this.state.file || this.state.url !== "")) {
|
if (this.state.submitting && (!this.state.file || this.state.url !== "")) {
|
||||||
// upload using axios
|
// upload using axios
|
||||||
post(apis.submitMessage, { content: this.state.msg, image: this.state.url })
|
post(apis.submitMessage, { content: this.state.msg, image: this.state.url })
|
||||||
.then(res => {
|
.then(({ data, status, networkStatus }) => {
|
||||||
this.setState({ submitting: false, msg: "", url: "" });
|
if (networkStatus !== 200) return;
|
||||||
|
if (!status) return alert('提交内容失败:' + data);
|
||||||
|
this.setState({ submitting: false, msg: "", url: "", file: null });
|
||||||
|
alert('内容提交成功啦').then(({ isConfirmed }) => {
|
||||||
|
if (isConfirmed) window.close();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
載入中…
x
新增問題並參考
Block a user