From 866e4cd2fbac451f0181c4618f32d2ddcdd85f83 Mon Sep 17 00:00:00 2001 From: felinae98 <731499577@qq.com> Date: Wed, 15 Dec 2021 22:29:54 +0800 Subject: [PATCH] fresh available --- admin-frontend/package.json | 2 + admin-frontend/src/App.tsx | 45 ++++----- admin-frontend/src/api/utils.ts | 2 +- admin-frontend/src/component/addSubsModal.tsx | 7 +- .../src/component/subscribeCard.tsx | 10 +- admin-frontend/src/index.tsx | 8 +- admin-frontend/src/pages/admin/index.tsx | 7 +- admin-frontend/src/pages/auth.tsx | 35 +++---- admin-frontend/src/store/globalConfSlice.ts | 35 +++++++ admin-frontend/src/store/hooks.ts | 5 + admin-frontend/src/store/index.ts | 15 +++ admin-frontend/src/store/loginSlice.ts | 99 +++++++++++++++++++ admin-frontend/src/utils/context.ts | 17 ---- admin-frontend/src/utils/type.ts | 3 +- admin-frontend/yarn.lock | 5 + 15 files changed, 219 insertions(+), 76 deletions(-) create mode 100644 admin-frontend/src/store/globalConfSlice.ts create mode 100644 admin-frontend/src/store/hooks.ts create mode 100644 admin-frontend/src/store/index.ts create mode 100644 admin-frontend/src/store/loginSlice.ts delete mode 100644 admin-frontend/src/utils/context.ts diff --git a/admin-frontend/package.json b/admin-frontend/package.json index 133f9b2..42adc92 100644 --- a/admin-frontend/package.json +++ b/admin-frontend/package.json @@ -16,6 +16,7 @@ "@types/react-dom": "^17.0.0", "antd": "^4.16.13", "axios": "^0.21.4", + "jwt-decode": "^3.1.2", "lodash": "^4.17.21", "react": "^17.0.2", "react-dom": "^17.0.2", @@ -51,6 +52,7 @@ }, "devDependencies": { "@types/lodash": "^4.14.175", + "@types/react-redux": "^7.1.20", "@types/react-router-dom": "^5.3.0", "react-app-rewired": "^2.1.8", "redux-devtools": "^3.7.0" diff --git a/admin-frontend/src/App.tsx b/admin-frontend/src/App.tsx index 0446598..aacb1e7 100644 --- a/admin-frontend/src/App.tsx +++ b/admin-frontend/src/App.tsx @@ -1,45 +1,36 @@ -import React, { useContext, useEffect, useState } from 'react'; -import { BrowserRouter as Router, Route, Switch } from 'react-router-dom'; -import './App.css'; -import { LoginContext, loginContextDefault, GlobalConfContext } from './utils/context'; -import { LoginStatus, GlobalConf, AllPlatformConf } from './utils/type'; -import { Admin } from './pages/admin'; -import { getGlobalConf } from './api/config'; -import { Auth } from './pages/auth'; import 'antd/dist/antd.css'; +import React, {useEffect} from 'react'; +import {useDispatch, useSelector} from 'react-redux'; +import {BrowserRouter as Router, Route, Switch} from 'react-router-dom'; +import './App.css'; +import {Admin} from './pages/admin'; +import {Auth} from './pages/auth'; +import {getGlobalConf} from './store/globalConfSlice'; +import {useAppSelector} from './store/hooks'; +import {loadLoginState, loginSelector} from './store/loginSlice'; function LoginSwitch() { - const {login, save} = useContext(LoginContext); + const login = useSelector(loginSelector) if (login.login) { return ; } else { return (
not login -
) } } function App() { - const [loginStatus, setLogin] = useState(loginContextDefault.login); - const [globalConf, setGlobalConf] = useState({platformConf: {} as AllPlatformConf, loaded: false}); - // const globalConfContext = useContext(GlobalConfContext); - const save = (login: LoginStatus) => setLogin(_ => login); + const dispatch = useDispatch() + const globalConf = useAppSelector(state => state.globalConf) useEffect(() => { - const fetchGlobalConf = async () => { - const res = await getGlobalConf(); - setGlobalConf(_ => {return {...res, loaded: true}}); - }; - fetchGlobalConf(); - }, []); - return ( - - + dispatch(getGlobalConf()); + dispatch(loadLoginState()) + }, [dispatch]); + return <> { globalConf.loaded && @@ -52,9 +43,7 @@ function App() { } - - - ); + ; } export default App; diff --git a/admin-frontend/src/api/utils.ts b/admin-frontend/src/api/utils.ts index 5c00cde..751cdd2 100644 --- a/admin-frontend/src/api/utils.ts +++ b/admin-frontend/src/api/utils.ts @@ -8,7 +8,7 @@ export const baseUrl = '/bison/api/' axios.interceptors.request.use(function (config) { if (config.url && config.url.startsWith(baseUrl) && config.url !== `${baseUrl}auth` && config.url !== `${baseUrl}global_conf`) { - const token = sessionStorage.getItem('token'); + const token = localStorage.getItem('token'); if (token) { config.headers['Authorization'] = `Bearer ${token}`; } else { diff --git a/admin-frontend/src/component/addSubsModal.tsx b/admin-frontend/src/component/addSubsModal.tsx index ef53a76..b51113e 100644 --- a/admin-frontend/src/component/addSubsModal.tsx +++ b/admin-frontend/src/component/addSubsModal.tsx @@ -1,8 +1,9 @@ import {Form, Input, Modal, Select, Tag} from 'antd'; -import React, {useContext, useEffect, useState} from "react"; +import React, {useEffect, useState} from "react"; +import {useSelector} from 'react-redux'; import {addSubscribe, getTargetName, updateSubscribe} from 'src/api/config'; import {InputTag} from 'src/component/inputTag'; -import {GlobalConfContext} from "src/utils/context"; +import {platformConfSelector} from 'src/store/globalConfSlice'; import {CategoryConfig, SubscribeConfig} from 'src/utils/type'; interface InputTagCustomProp { @@ -49,7 +50,7 @@ export function AddModal({ showModal, groupNumber, setShowModal, refresh, initVal }: AddModalProp) { const [ confirmLoading, setConfirmLoading ] = useState(false); - const { platformConf } = useContext(GlobalConfContext); + const platformConf = useSelector(platformConfSelector) const [ hasTarget, setHasTarget ] = useState(false); const [ categories, setCategories ] = useState({} as CategoryConfig); const [ enabledTag, setEnableTag ] = useState(false); diff --git a/admin-frontend/src/component/subscribeCard.tsx b/admin-frontend/src/component/subscribeCard.tsx index 71c5ac8..395c58d 100644 --- a/admin-frontend/src/component/subscribeCard.tsx +++ b/admin-frontend/src/component/subscribeCard.tsx @@ -1,9 +1,10 @@ import {CopyOutlined, DeleteOutlined, EditOutlined} from '@ant-design/icons'; import {Card, Col, Form, message, Popconfirm, Select, Tag, Tooltip} from 'antd'; import Modal from 'antd/lib/modal/Modal'; -import React, {useContext, useState} from "react"; +import React, {useState} from "react"; +import {useSelector} from 'react-redux'; import {addSubscribe, delSubscribe} from 'src/api/config'; -import {GlobalConfContext} from "src/utils/context"; +import {platformConfSelector} from 'src/store/globalConfSlice'; import {PlatformConfig, SubscribeConfig, SubscribeResp} from 'src/utils/type'; import {AddModal} from './addSubsModal'; @@ -57,10 +58,11 @@ interface SubscribeCardProp { reload: () => void } export function SubscribeCard({groupNumber, config, reload, groupSubscribes}: SubscribeCardProp) { - const globalConf = useContext(GlobalConfContext); + // const globalConf = useSelector() + const platformConfs = useSelector(platformConfSelector) const [showModal, setShowModal] = useState(false) const [showEditModal, setShowEditModal] = useState(false) - const platformConf = globalConf.platformConf[config.platformName] as PlatformConfig; + const platformConf = platformConfs[config.platformName] as PlatformConfig; const handleDelete = (groupNumber: string, platformName: string, target: string) => () => { delSubscribe(groupNumber, platformName, target).then(() => { reload() diff --git a/admin-frontend/src/index.tsx b/admin-frontend/src/index.tsx index ef2edf8..f56de60 100644 --- a/admin-frontend/src/index.tsx +++ b/admin-frontend/src/index.tsx @@ -1,12 +1,16 @@ import React from 'react'; import ReactDOM from 'react-dom'; -import './index.css'; +import {Provider} from 'react-redux'; import App from './App'; +import './index.css'; import reportWebVitals from './reportWebVitals'; +import store from './store'; ReactDOM.render( - + + + , document.getElementById('root') ); diff --git a/admin-frontend/src/pages/admin/index.tsx b/admin-frontend/src/pages/admin/index.tsx index 38e998e..4d860be 100644 --- a/admin-frontend/src/pages/admin/index.tsx +++ b/admin-frontend/src/pages/admin/index.tsx @@ -1,12 +1,13 @@ import {BugOutlined, SettingOutlined} from '@ant-design/icons'; import {Layout, Menu} from 'antd'; -import React, {useContext, useState} from "react"; -import {LoginContext} from "src/utils/context"; +import React, {useState} from "react"; +import {useSelector} from 'react-redux'; +import {loginSelector} from 'src/store/loginSlice'; import './admin.css'; import {ConfigPage} from './configPage'; export function Admin() { - const { login } = useContext(LoginContext); + const login = useSelector(loginSelector) const [ tab, changeTab ] = useState("manage"); return ( diff --git a/admin-frontend/src/pages/auth.tsx b/admin-frontend/src/pages/auth.tsx index c8894be..299c58d 100644 --- a/admin-frontend/src/pages/auth.tsx +++ b/admin-frontend/src/pages/auth.tsx @@ -1,27 +1,28 @@ -import React, {useContext, useEffect, useState} from "react"; -import { useParams } from "react-router"; -import { auth } from '../api/config'; -import { LoginContext } from '../utils/context'; -import { Redirect } from 'react-router-dom' +import React, {useEffect} from "react"; +import {useDispatch, useSelector} from "react-redux"; +import {useParams} from "react-router"; +import {Redirect} from 'react-router-dom'; +import {login, loginSelector} from 'src/store/loginSlice'; + interface AuthParam { code: string } export function Auth() { const { code } = useParams(); - const [ content, contentUpdate ] = useState(
Logining...
); - const { save } = useContext(LoginContext); + const dispatch = useDispatch(); + const loginState = useSelector(loginSelector) useEffect(() => { const loginFun = async () => { - const resp = await auth(code); - if (resp.status === 200) { - save({login: true, type: resp.type, name: resp.name, id: resp.id, token: resp.token}); - contentUpdate(_ => ); - sessionStorage.setItem('token', resp.token); - } else { - contentUpdate(_ =>
登录失败,请重新获取连接
); - } + dispatch(login(code)); } loginFun(); - }, [code, save]) - return content; + }, [code, dispatch]) + return <> + { loginState.login ? + : + loginState.failed ? +
登录失败,请重新获取连接
: +
Logining...
+ } +; } diff --git a/admin-frontend/src/store/globalConfSlice.ts b/admin-frontend/src/store/globalConfSlice.ts new file mode 100644 index 0000000..914066a --- /dev/null +++ b/admin-frontend/src/store/globalConfSlice.ts @@ -0,0 +1,35 @@ +import {CaseReducer, createAsyncThunk, createSlice, PayloadAction} from "@reduxjs/toolkit"; +import {getGlobalConf as getGlobalConfApi} from "src/api/config"; +import {GlobalConf} from "src/utils/type"; +import {RootState} from "."; + + +const initialState: GlobalConf = { + platformConf: {}, + loaded: false +} + +const setGlobalConf: CaseReducer> = (_, action) => { + return {...action.payload, loaded: true} +} + +export const getGlobalConf = createAsyncThunk( + "globalConf/set", + getGlobalConfApi, + { + condition: (_, { getState }) => !(getState() as RootState).globalConf.loaded + } +); + +export const globalConfSlice = createSlice({ + name: "globalConf", + initialState, + reducers: {}, + extraReducers: (builder) => { + builder.addCase(getGlobalConf.fulfilled, setGlobalConf) + } +}) + +export const platformConfSelector = (state: RootState) => state.globalConf.platformConf + +export default globalConfSlice.reducer diff --git a/admin-frontend/src/store/hooks.ts b/admin-frontend/src/store/hooks.ts new file mode 100644 index 0000000..99221df --- /dev/null +++ b/admin-frontend/src/store/hooks.ts @@ -0,0 +1,5 @@ +import {TypedUseSelectorHook, useDispatch, useSelector} from "react-redux"; +import {AppDispatch, RootState} from "."; + +export const useAppDispacher = () => useDispatch() +export const useAppSelector: TypedUseSelectorHook = useSelector diff --git a/admin-frontend/src/store/index.ts b/admin-frontend/src/store/index.ts new file mode 100644 index 0000000..654f73f --- /dev/null +++ b/admin-frontend/src/store/index.ts @@ -0,0 +1,15 @@ +import {configureStore} from "@reduxjs/toolkit"; +import loginSlice from "./loginSlice"; +import globalConfSlice from "./globalConfSlice"; + +const store = configureStore({ + reducer: { + login: loginSlice, + globalConf: globalConfSlice, + } +}) + +export default store; + +export type RootState = ReturnType; +export type AppDispatch = typeof store.dispatch; diff --git a/admin-frontend/src/store/loginSlice.ts b/admin-frontend/src/store/loginSlice.ts new file mode 100644 index 0000000..9898d7a --- /dev/null +++ b/admin-frontend/src/store/loginSlice.ts @@ -0,0 +1,99 @@ +import { AnyAction, CaseReducer, createAsyncThunk, createSlice, PayloadAction, ThunkAction } from "@reduxjs/toolkit"; +import jwt_decode from 'jwt-decode'; +import { LoginStatus, TokenResp } from "src/utils/type"; +import { auth } from "src/api/config"; +import {RootState} from "."; + +const initialState: LoginStatus = { + login: false, + type: '', + name: '', + id: '123', + // groups: [], + token: '', + failed: false +} + +interface storedInfo { + type: string + name: string + id: string +} + +const loginAction: CaseReducer> = (_, action) => { + return { + login: true, + failed: false, + type: action.payload.type, + name: action.payload.name, + id: action.payload.id, + token: action.payload.token + } +} + +export const login = createAsyncThunk( + "auth/login", + async (code: string) => { + let res = await auth(code); + if (res.status !== 200) { + throw Error("Login Error") + } else { + localStorage.setItem('loginInfo', JSON.stringify({ + 'type': res.type, + 'name': res.name, + id: res.id, + })) + localStorage.setItem('token', res.token) + } + return res + }, + { + condition: (_: string, { getState }) => { + const { login } = getState() as { login: LoginStatus } + return !login.login; + } + } +) + + +export const loginSlice = createSlice({ + name: 'auth', + initialState, + reducers: { + doLogin: loginAction + }, + extraReducers: (builder) => { + builder.addCase(login.fulfilled, loginAction); + builder.addCase(login.rejected, (stat) => { + stat.failed = true + }) + } +}) + +export const { doLogin } = loginSlice.actions + +export const loadLoginState = (): ThunkAction => + (dispatch, getState) => { + if (getState().login.login) { + return + } + const infoJson = localStorage.getItem('loginInfo') + const jwtToken = localStorage.getItem('token'); + if (infoJson && jwtToken) { + const decodedJwt = jwt_decode(jwtToken) as { exp: number }; + if (decodedJwt.exp < Date.now() / 1000) { + return + } + const info = JSON.parse(infoJson) as storedInfo + const payload: TokenResp = { + ...info, + status: 200, + token: jwtToken, + } + dispatch(doLogin(payload)) + } + } + +export const loginSelector = (state: RootState) => state.login + +export default loginSlice.reducer diff --git a/admin-frontend/src/utils/context.ts b/admin-frontend/src/utils/context.ts deleted file mode 100644 index ca96c34..0000000 --- a/admin-frontend/src/utils/context.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { createContext } from "react"; -import { LoginContextType, GlobalConf } from "./type"; - -export const loginContextDefault: LoginContextType = { - login: { - login: false, - type: '', - name: '', - id: '123', - // groups: [], - token: '' - }, - save: () => {} -}; - -export const LoginContext = createContext(loginContextDefault); -export const GlobalConfContext = createContext({platformConf: {}, loaded: false}); diff --git a/admin-frontend/src/utils/type.ts b/admin-frontend/src/utils/type.ts index fc4a838..e514ed0 100644 --- a/admin-frontend/src/utils/type.ts +++ b/admin-frontend/src/utils/type.ts @@ -9,7 +9,8 @@ export interface LoginStatus { name: string id: string // groups: Array - token: string + token: string, + failed: boolean, } export type LoginContextType = { diff --git a/admin-frontend/yarn.lock b/admin-frontend/yarn.lock index a5c8ae2..5912b3a 100644 --- a/admin-frontend/yarn.lock +++ b/admin-frontend/yarn.lock @@ -7138,6 +7138,11 @@ jsonfile@^6.0.1: array-includes "^3.1.3" object.assign "^4.1.2" +jwt-decode@^3.1.2: + version "3.1.2" + resolved "https://registry.npm.taobao.org/jwt-decode/download/jwt-decode-3.1.2.tgz#3fb319f3675a2df0c2895c8f5e9fa4b67b04ed59" + integrity sha1-P7MZ82daLfDCiVyPXp+ktnsE7Vk= + killable@^1.0.1: version "1.0.1" resolved "https://registry.nlark.com/killable/download/killable-1.0.1.tgz#4c8ce441187a061c7474fb87ca08e2a638194892"