From e93c5426bdfeb5b4c07ae8a018fb1bc1d17bdec7 Mon Sep 17 00:00:00 2001 From: jungleford Date: Tue, 25 Mar 2025 16:47:13 +0800 Subject: [PATCH 1/4] fix: console waring: Default and named imports from CSS files are deprecated. Use the ?inline query instead --- src/hooks/useTheme.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/hooks/useTheme.ts b/src/hooks/useTheme.ts index 4b34097..c791904 100644 --- a/src/hooks/useTheme.ts +++ b/src/hooks/useTheme.ts @@ -1,6 +1,6 @@ import { ThemeConfigProp } from "@/redux/interface"; -import darkTheme from "@/styles/theme/theme-dark.less"; -import defaultTheme from "@/styles/theme/theme-default.less"; +import darkTheme from "@/styles/theme/theme-dark.less?inline"; +import defaultTheme from "@/styles/theme/theme-default.less?inline"; /** * @description 全局主题设置 From 7dcf17fabca2c12b5a132e539b5ae391a76d5cb1 Mon Sep 17 00:00:00 2001 From: jungleford Date: Thu, 27 Mar 2025 17:54:49 +0800 Subject: [PATCH 2/4] =?UTF-8?q?fix:=20DiscState.disc=E7=B1=BB=E5=9E=8B?= =?UTF-8?q?=E4=B8=8EgetDiscListAction=20dispatch=E5=87=BA=E6=9D=A5?= =?UTF-8?q?=E7=9A=84discList=E7=B1=BB=E5=9E=8B=E4=B8=8D=E5=8C=B9=E9=85=8D?= =?UTF-8?q?=E3=80=82=E8=99=BD=E7=84=B6=E4=B8=8D=E5=BD=B1=E5=93=8D=E5=B7=B2?= =?UTF-8?q?=E6=9C=89=E8=BF=87=E7=A8=8B=EF=BC=8C=E4=BD=86=E5=9C=A8RTK?= =?UTF-8?q?=E6=9B=BF=E6=8D=A2=E5=AE=9E=E7=8E=B0=E4=B8=AD=E5=87=BA=E7=8E=B0?= =?UTF-8?q?=E7=B1=BB=E5=9E=8B=E9=94=99=E8=AF=AF=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/redux/interface/index.ts | 2 +- src/redux/modules/disc/reducer.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/redux/interface/index.ts b/src/redux/interface/index.ts index 4a689f5..de06ea5 100644 --- a/src/redux/interface/index.ts +++ b/src/redux/interface/index.ts @@ -49,5 +49,5 @@ export interface AuthState { /**discState */ export interface DiscState { - disc: MapItem[]; + disc: MapItem; } diff --git a/src/redux/modules/disc/reducer.ts b/src/redux/modules/disc/reducer.ts index ea4273c..f8f2c13 100644 --- a/src/redux/modules/disc/reducer.ts +++ b/src/redux/modules/disc/reducer.ts @@ -5,7 +5,7 @@ import { DiscState } from "@/redux/interface"; import * as types from "@/redux/mutation-types"; const discState: DiscState = { - disc: [] + disc: {} }; // disc reducer From 0b4dd2e5b94ea0b599cfe1c41939a6784e634eaa Mon Sep 17 00:00:00 2001 From: jungleford Date: Mon, 31 Mar 2025 17:01:38 +0800 Subject: [PATCH 3/4] =?UTF-8?q?refactor:=20=E2=99=BB=EF=B8=8F=20=20=20redu?= =?UTF-8?q?x=E9=83=A8=E5=88=86=E9=87=8D=E6=9E=84=EF=BC=9A=E5=85=A8?= =?UTF-8?q?=E5=B1=80=E7=8A=B6=E6=80=81=E7=AE=A1=E7=90=86=E7=9A=84=E4=BD=BF?= =?UTF-8?q?=E7=94=A8=E6=96=B9=E5=BC=8F=E5=8F=98=E6=9B=B4=EF=BC=8C=E5=B0=86?= =?UTF-8?q?=E4=BC=A0=E7=BB=9F=E7=9A=84Connect=20API=E6=9B=BF=E6=8D=A2?= =?UTF-8?q?=E6=88=90=E5=AE=98=E6=96=B9=E6=8E=A8=E8=8D=90=E7=9A=84RTK?= =?UTF-8?q?=E6=96=B9=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package-lock.json | 35 ++- package.json | 1 + src/App.tsx | 13 +- src/api/index.ts | 4 +- src/components/SwitchDark/index.tsx | 19 +- src/hooks/useRTK.ts | 7 + src/layouts/components/Footer/index.tsx | 12 +- .../Header/components/AssemblySize.tsx | 17 +- .../Header/components/AvatarIcon.tsx | 32 +-- .../Header/components/BreadcrumbNav.tsx | 14 +- .../Header/components/CollapseIcon.tsx | 17 +- .../components/Header/components/Theme.tsx | 27 +- src/layouts/components/Header/index.tsx | 28 +-- .../components/Menu/components/Logo.tsx | 13 +- src/layouts/components/Menu/index.tsx | 17 +- src/layouts/components/Tabs/index.tsx | 30 ++- src/layouts/index.tsx | 21 +- src/main.tsx | 2 +- src/routers/utils/authRouter.tsx | 13 +- src/rtk/index.ts | 11 + src/rtk/slices/auth.ts | 27 ++ src/rtk/slices/breadcrumb.ts | 23 ++ src/rtk/slices/disc.ts | 76 ++++++ src/rtk/slices/global.ts | 52 ++++ src/rtk/slices/menu.ts | 60 +++++ src/rtk/slices/tabs.ts | 29 +++ src/rtk/store.ts | 49 ++++ src/views/article/edit/index.tsx | 181 ++++++------- src/views/article/list/index.tsx | 111 ++++---- src/views/author/whitelist/index.tsx | 237 ++++++++---------- src/views/author/zsxqlist/index.tsx | 47 ++-- src/views/category/index.tsx | 71 +++--- src/views/column/article/index.tsx | 146 +++++------ .../column/setting/articlesort/index.tsx | 205 +++++++-------- src/views/column/setting/index.tsx | 14 +- src/views/config/index.tsx | 14 +- src/views/global/index.tsx | 38 +-- src/views/login/components/LoginForm.tsx | 26 +- src/views/resume/index.tsx | 12 +- src/views/tag/index.tsx | 12 +- 40 files changed, 1026 insertions(+), 737 deletions(-) create mode 100644 src/hooks/useRTK.ts create mode 100644 src/rtk/index.ts create mode 100644 src/rtk/slices/auth.ts create mode 100644 src/rtk/slices/breadcrumb.ts create mode 100644 src/rtk/slices/disc.ts create mode 100644 src/rtk/slices/global.ts create mode 100644 src/rtk/slices/menu.ts create mode 100644 src/rtk/slices/tabs.ts create mode 100644 src/rtk/store.ts diff --git a/package-lock.json b/package-lock.json index 037205b..8f013f7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,6 +18,7 @@ "@dnd-kit/core": "^6.1.0", "@dnd-kit/modifiers": "^7.0.0", "@dnd-kit/sortable": "^8.0.0", + "@reduxjs/toolkit": "^1.9.7", "antd": "^5.6.2", "antd-img-crop": "^4.12.2", "axios": "^0.27.2", @@ -1776,6 +1777,30 @@ "react-dom": ">=16.9.0" } }, + "node_modules/@reduxjs/toolkit": { + "version": "1.9.7", + "resolved": "https://r.cnpmjs.org/@reduxjs/toolkit/-/toolkit-1.9.7.tgz", + "integrity": "sha512-t7v8ZPxhhKgOKtU+uyJT13lu4vL7az5aFi4IdoDs/eS548edn2M8Ik9h8fxgvMjGoAUVFSt6ZC1P5cWmQ014QQ==", + "license": "MIT", + "dependencies": { + "immer": "^9.0.21", + "redux": "^4.2.1", + "redux-thunk": "^2.4.2", + "reselect": "^4.1.8" + }, + "peerDependencies": { + "react": "^16.9.0 || ^17.0.0 || ^18", + "react-redux": "^7.2.1 || ^8.0.2" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + }, + "react-redux": { + "optional": true + } + } + }, "node_modules/@remix-run/router": { "version": "1.3.2", "license": "MIT", @@ -7176,7 +7201,9 @@ } }, "node_modules/immer": { - "version": "9.0.19", + "version": "9.0.21", + "resolved": "https://r.cnpmjs.org/immer/-/immer-9.0.21.tgz", + "integrity": "sha512-bc4NBHqOqSfRW7POMkHd51LvClaeMXpm8dx0e8oE2GORbq5aRK7Bxl4FyzVLdGtLmvLKL7BTDBG5ACQm4HWjTA==", "license": "MIT", "funding": { "type": "opencollective", @@ -11892,6 +11919,12 @@ "node": ">=0.10.0" } }, + "node_modules/reselect": { + "version": "4.1.8", + "resolved": "https://r.cnpmjs.org/reselect/-/reselect-4.1.8.tgz", + "integrity": "sha512-ab9EmR80F/zQTMNeneUr4cv+jSwPJgIlvEmVwLerwrWVbpLlBuls9XHzIeTFy4cegU2NHBp3va0LKOzU5qFEYQ==", + "license": "MIT" + }, "node_modules/resize-observer-polyfill": { "version": "1.5.1", "resolved": "https://registry.npmmirror.com/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz", diff --git a/package.json b/package.json index b1dd4c9..672efa3 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ "@dnd-kit/core": "^6.1.0", "@dnd-kit/modifiers": "^7.0.0", "@dnd-kit/sortable": "^8.0.0", + "@reduxjs/toolkit": "^1.9.7", "antd": "^5.6.2", "antd-img-crop": "^4.12.2", "axios": "^0.27.2", diff --git a/src/App.tsx b/src/App.tsx index a18b081..be47850 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,16 +1,19 @@ -import { connect } from "react-redux"; +/* eslint-disable simple-import-sort/imports */ import { HashRouter } from "react-router-dom"; import { ConfigProvider } from "antd"; import zhCN from "antd/lib/locale/zh_CN"; +import type { RootState } from "@/rtk"; +import { useAppSelector } from "@/hooks/useRTK"; import useTheme from "@/hooks/useTheme"; import Router from "@/routers/index"; import AuthRouter from "@/routers/utils/authRouter"; import "./index.scss"; -const App = (props: any) => { - const { assemblySize, themeConfig } = props; +const App = () => { + const assemblySize = useAppSelector((state: RootState) => state.global.assemblySize); + const themeConfig = useAppSelector((state: RootState) => state.global.themeConfig); // 全局使用主题 useTheme(themeConfig); @@ -26,6 +29,4 @@ const App = (props: any) => { ); }; -const mapStateToProps = (state: any) => state.global; -const mapDispatchToProps = {}; -export default connect(mapStateToProps, mapDispatchToProps)(App); +export default App; diff --git a/src/api/index.ts b/src/api/index.ts index 29a6dcc..ff473b1 100644 --- a/src/api/index.ts +++ b/src/api/index.ts @@ -1,3 +1,4 @@ +/* eslint-disable simple-import-sort/imports */ import { message } from "antd"; import axios, { AxiosError, AxiosInstance, AxiosRequestConfig, AxiosResponse } from "axios"; @@ -6,8 +7,7 @@ import { LOGIN_URL } from "@/config/config"; import NProgress from "@/config/nprogress"; import { showFullScreenLoading, tryHideFullScreenLoading } from "@/config/serviceLoading"; import { ResultEnum } from "@/enums/httpEnum"; -import { store } from "@/redux"; -import { setToken } from "@/redux/modules/global/action"; +import { store, setToken } from "@/rtk"; import { AxiosCanceler } from "./helper/axiosCancel"; import { checkStatus } from "./helper/checkStatus"; diff --git a/src/components/SwitchDark/index.tsx b/src/components/SwitchDark/index.tsx index 8ea04c3..ccb6422 100644 --- a/src/components/SwitchDark/index.tsx +++ b/src/components/SwitchDark/index.tsx @@ -1,12 +1,17 @@ -import { connect } from "react-redux"; +/* eslint-disable simple-import-sort/imports */ import { Switch } from "antd"; -import { setThemeConfig } from "@/redux/modules/global/action"; +import { useAppDispatch, useAppSelector } from "@/hooks/useRTK"; +import type { AppDispatch, RootState } from "@/rtk"; +import { setThemeConfig } from "@/rtk"; + +const SwitchDark = () => { + const global = useAppSelector((state: RootState) => state.global); + const { themeConfig } = global; + const dispatch: AppDispatch = useAppDispatch(); -const SwitchDark = (props: any) => { - const { setThemeConfig, themeConfig } = props; const onChange = (checked: boolean) => { - setThemeConfig({ ...themeConfig, isDark: checked }); + dispatch(setThemeConfig({ ...themeConfig, isDark: checked })); }; return ( @@ -20,6 +25,4 @@ const SwitchDark = (props: any) => { ); }; -const mapStateToProps = (state: any) => state.global; -const mapDispatchToProps = { setThemeConfig }; -export default connect(mapStateToProps, mapDispatchToProps)(SwitchDark); +export default SwitchDark; diff --git a/src/hooks/useRTK.ts b/src/hooks/useRTK.ts new file mode 100644 index 0000000..18b973a --- /dev/null +++ b/src/hooks/useRTK.ts @@ -0,0 +1,7 @@ +/* eslint-disable simple-import-sort/imports */ +import { useDispatch, useSelector, TypedUseSelectorHook } from "react-redux"; + +import type { RootState, AppDispatch } from "@/rtk"; + +export const useAppDispatch = () => useDispatch(); +export const useAppSelector: TypedUseSelectorHook = useSelector; diff --git a/src/layouts/components/Footer/index.tsx b/src/layouts/components/Footer/index.tsx index 8509407..bfe974e 100644 --- a/src/layouts/components/Footer/index.tsx +++ b/src/layouts/components/Footer/index.tsx @@ -1,11 +1,12 @@ -import { connect } from "react-redux"; - +import { useAppSelector } from "@/hooks/useRTK"; +import type { RootState } from "@/rtk"; import { baseDomain } from "@/utils/util"; import "./index.less"; -const LayoutFooter = (props: any) => { - const { themeConfig } = props; +const LayoutFooter = () => { + const global = useAppSelector((state: RootState) => state.global); + const { themeConfig } = global; // 定义一个自动获取年份的方法 const getYear = () => { @@ -25,5 +26,4 @@ const LayoutFooter = (props: any) => { ); }; -const mapStateToProps = (state: any) => state.global; -export default connect(mapStateToProps)(LayoutFooter); +export default LayoutFooter; diff --git a/src/layouts/components/Header/components/AssemblySize.tsx b/src/layouts/components/Header/components/AssemblySize.tsx index 544bfff..254ca44 100644 --- a/src/layouts/components/Header/components/AssemblySize.tsx +++ b/src/layouts/components/Header/components/AssemblySize.tsx @@ -1,14 +1,17 @@ -import { connect } from "react-redux"; import { Dropdown, Menu } from "antd"; -import { setAssemblySize } from "@/redux/modules/global/action"; +import { useAppDispatch, useAppSelector } from "@/hooks/useRTK"; +import type { AppDispatch, RootState } from "@/rtk"; +import { setAssemblySize } from "@/rtk"; -const AssemblySize = (props: any) => { - const { assemblySize, setAssemblySize } = props; +const AssemblySize = () => { + const global = useAppSelector((state: RootState) => state.global); + const { assemblySize } = global; + const dispatch: AppDispatch = useAppDispatch(); // 切换组件大小 const onClick = (e: MenuInfo) => { - setAssemblySize(e.key); + dispatch(setAssemblySize(e.key)); }; const menu = ( @@ -42,6 +45,4 @@ const AssemblySize = (props: any) => { ); }; -const mapStateToProps = (state: any) => state.global; -const mapDispatchToProps = { setAssemblySize }; -export default connect(mapStateToProps, mapDispatchToProps)(AssemblySize); +export default AssemblySize; diff --git a/src/layouts/components/Header/components/AvatarIcon.tsx b/src/layouts/components/Header/components/AvatarIcon.tsx index 3acf02a..964034c 100644 --- a/src/layouts/components/Header/components/AvatarIcon.tsx +++ b/src/layouts/components/Header/components/AvatarIcon.tsx @@ -1,6 +1,5 @@ /* eslint-disable prettier/prettier */ import { useRef } from "react"; -import { connect, useDispatch } from "react-redux"; import { useNavigate } from "react-router-dom"; import { ExclamationCircleOutlined } from "@ant-design/icons"; import { Avatar, Dropdown, MenuProps, message, Modal } from "antd"; @@ -8,14 +7,17 @@ import { Avatar, Dropdown, MenuProps, message, Modal } from "antd"; import { logoutApi } from "@/api/modules/login"; import loginPng from "@/assets/images/logo_md.png"; import { HOME_URL, LOGIN_URL } from "@/config/config"; -import { setToken, setUserInfo } from "@/redux/modules/global/action"; +import { useAppDispatch, useAppSelector } from "@/hooks/useRTK"; +import type { AppDispatch, RootState } from "@/rtk"; +import { setToken, setUserInfo } from "@/rtk"; import InfoModal from "./InfoModal"; import PasswordModal from "./PasswordModal"; -const AvatarIcon = (props: any) => { - const { userInfo, setToken, setUserInfo } = props; +const AvatarIcon = () => { + const { userInfo } = useAppSelector((state: RootState) => state.global); + const dispatch: AppDispatch = useAppDispatch(); console.log("AvatarIcon setToken setUserInfo", setToken, setUserInfo); - console.log("AvatarIcon userInfo", userInfo ); + console.log("AvatarIcon userInfo", userInfo); const navigate = useNavigate(); @@ -39,8 +41,8 @@ const AvatarIcon = (props: any) => { const { status, result } = await logoutApi(); if (status && status.code == 0 && result) { // 退出,清除 token,清除用户信息,跳转到登录页 - setToken(""); - setUserInfo({}); + dispatch(setToken("")); + dispatch(setUserInfo({})); message.success("退出登录成功!"); navigate(LOGIN_URL); } else { @@ -59,12 +61,13 @@ const AvatarIcon = (props: any) => { { key: "2", label: 个人信息, - onClick: () => infoRef.current!.showModal({ - photo: userInfo.photo, - profile: userInfo.profile, - role: userInfo.role, - userName: userInfo.userName, - }) + onClick: () => + infoRef.current!.showModal({ + photo: userInfo.photo, + profile: userInfo.profile, + role: userInfo.role, + userName: userInfo.userName + }) }, { key: "3", @@ -91,5 +94,4 @@ const AvatarIcon = (props: any) => { ); }; -const mapDispatchToProps = { setToken, setUserInfo }; -export default connect(null, mapDispatchToProps)(AvatarIcon); +export default AvatarIcon; diff --git a/src/layouts/components/Header/components/BreadcrumbNav.tsx b/src/layouts/components/Header/components/BreadcrumbNav.tsx index 35feb82..25bdb9e 100644 --- a/src/layouts/components/Header/components/BreadcrumbNav.tsx +++ b/src/layouts/components/Header/components/BreadcrumbNav.tsx @@ -1,13 +1,16 @@ -import { connect } from "react-redux"; import { useLocation } from "react-router-dom"; import { Breadcrumb } from "antd"; import { HOME_URL } from "@/config/config"; +import { useAppSelector } from "@/hooks/useRTK"; +import type { RootState } from "@/rtk"; -const BreadcrumbNav = (props: any) => { +const BreadcrumbNav = () => { const { pathname } = useLocation(); - const { themeConfig } = props.global; - const breadcrumbList = props.breadcrumb.breadcrumbList[pathname] || []; + const global = useAppSelector((state: RootState) => state.global); + const { themeConfig } = global; + const breadcrumb = useAppSelector((state: RootState) => state.breadcrumb); + const breadcrumbList = breadcrumb.breadcrumbList[pathname] || []; console.log({ breadcrumbList }); return ( @@ -24,5 +27,4 @@ const BreadcrumbNav = (props: any) => { ); }; -const mapStateToProps = (state: any) => state; -export default connect(mapStateToProps)(BreadcrumbNav); +export default BreadcrumbNav; diff --git a/src/layouts/components/Header/components/CollapseIcon.tsx b/src/layouts/components/Header/components/CollapseIcon.tsx index 79f99df..aef39a8 100644 --- a/src/layouts/components/Header/components/CollapseIcon.tsx +++ b/src/layouts/components/Header/components/CollapseIcon.tsx @@ -1,15 +1,18 @@ -import { connect } from "react-redux"; import { MenuFoldOutlined, MenuUnfoldOutlined } from "@ant-design/icons"; -import { updateCollapse } from "@/redux/modules/menu/action"; +import { useAppDispatch, useAppSelector } from "@/hooks/useRTK"; +import type { AppDispatch, RootState } from "@/rtk"; +import { updateCollapse } from "@/rtk"; -const CollapseIcon = (props: any) => { - const { isCollapse, updateCollapse } = props; +const CollapseIcon = () => { + const menu = useAppSelector((state: RootState) => state.menu); + const { isCollapse } = menu; + const dispatch: AppDispatch = useAppDispatch(); return (
{ - updateCollapse(!isCollapse); + dispatch(updateCollapse(!isCollapse)); }} > {isCollapse ? : } @@ -17,6 +20,4 @@ const CollapseIcon = (props: any) => { ); }; -const mapStateToProps = (state: any) => state.menu; -const mapDispatchToProps = { updateCollapse }; -export default connect(mapStateToProps, mapDispatchToProps)(CollapseIcon); +export default CollapseIcon; diff --git a/src/layouts/components/Header/components/Theme.tsx b/src/layouts/components/Header/components/Theme.tsx index be0e4c1..098cb56 100644 --- a/src/layouts/components/Header/components/Theme.tsx +++ b/src/layouts/components/Header/components/Theme.tsx @@ -1,26 +1,27 @@ import { useState } from "react"; -import { connect } from "react-redux"; import { FireOutlined, SettingOutlined } from "@ant-design/icons"; import { Divider, Drawer, Switch } from "antd"; import SwitchDark from "@/components/SwitchDark"; -import { setThemeConfig } from "@/redux/modules/global/action"; -import { updateCollapse } from "@/redux/modules/menu/action"; +import { useAppDispatch, useAppSelector } from "@/hooks/useRTK"; +import type { AppDispatch, RootState } from "@/rtk"; +import { setThemeConfig, updateCollapse } from "@/rtk"; -const Theme = (props: any) => { +const Theme = () => { const [visible, setVisible] = useState(false); - const { setThemeConfig, updateCollapse } = props; - const { isCollapse } = props.menu; - const { themeConfig } = props.global; + const global = useAppSelector((state: RootState) => state.global); + const menu = useAppSelector((state: RootState) => state.menu); + const { isCollapse } = menu; + const { themeConfig } = global; const { weakOrGray, breadcrumb, tabs, footer } = themeConfig; + const dispatch: AppDispatch = useAppDispatch(); const setWeakOrGray = (checked: boolean, theme: string) => { - if (checked) return setThemeConfig({ ...themeConfig, weakOrGray: theme }); - setThemeConfig({ ...themeConfig, weakOrGray: "" }); + dispatch(setThemeConfig({ ...themeConfig, weakOrGray: checked ? theme : "" })); }; const onChange = (checked: boolean, keyName: string) => { - return setThemeConfig({ ...themeConfig, [keyName]: !checked }); + dispatch(setThemeConfig({ ...themeConfig, [keyName]: !checked })); }; return ( @@ -78,7 +79,7 @@ const Theme = (props: any) => { { - updateCollapse(e); + dispatch(updateCollapse(e)); }} />
@@ -114,6 +115,4 @@ const Theme = (props: any) => { ); }; -const mapStateToProps = (state: any) => state; -const mapDispatchToProps = { setThemeConfig, updateCollapse }; -export default connect(mapStateToProps, mapDispatchToProps)(Theme); +export default Theme; diff --git a/src/layouts/components/Header/index.tsx b/src/layouts/components/Header/index.tsx index 067c001..d1dee3b 100644 --- a/src/layouts/components/Header/index.tsx +++ b/src/layouts/components/Header/index.tsx @@ -1,11 +1,10 @@ import { useEffect } from "react"; -import { connect } from "react-redux"; import { Layout } from "antd"; import { loginUserInfo } from "@/api/modules/login"; -import { getDiscListAction } from "@/redux/modules/disc/action"; -import { setToken, setUserInfo } from "@/redux/modules/global/action"; -import { setTabsList } from "@/redux/modules/tabs/action"; +import { useAppDispatch, useAppSelector } from "@/hooks/useRTK"; +import type { AppDispatch, RootState } from "@/rtk"; +import { getDiscListAction, setTabsList, setToken, setUserInfo } from "@/rtk"; import AssemblySize from "./components/AssemblySize"; import AvatarIcon from "./components/AvatarIcon"; import BreadcrumbNav from "./components/BreadcrumbNav"; @@ -15,11 +14,12 @@ import Theme from "./components/Theme"; import "./index.less"; -const LayoutHeader = (props: any) => { - const { setToken, setUserInfo, setTabsList, getDiscListAction } = props; +const LayoutHeader = () => { + const global = useAppSelector((state: RootState) => state.global); + const dispatch: AppDispatch = useAppDispatch(); - // 尝试从 props 中获取用户信息,如果没有登录行为,则从后端直接获取 - let { userInfo } = props || {}; + // 尝试从 store 中获取用户信息,如果没有登录行为,则从后端直接获取 + let { userInfo } = global || {}; console.log("LayoutHeader userInfo", userInfo); const { Header } = Layout; @@ -36,12 +36,12 @@ const LayoutHeader = (props: any) => { if (status && status.code == 0 && result && result.userId > 0) { // 保存 token 到 Redux 的状态中 - setToken(String(result.userId)); + dispatch(setToken(String(result.userId))); // 保存用户登录信息 - setUserInfo(result); - setTabsList([]); + dispatch(setUserInfo(result)); + dispatch(setTabsList([])); // 获取字典数据 - getDiscListAction(); + dispatch(getDiscListAction()); } } catch (e) { console.log("初始化用户身份异常!", e); @@ -69,6 +69,4 @@ const LayoutHeader = (props: any) => { ); }; -const mapStateToProps = (state: any) => state.global; -const mapDispatchToProps = { setToken, setUserInfo, getDiscListAction, setTabsList }; -export default connect(mapStateToProps, mapDispatchToProps)(LayoutHeader); +export default LayoutHeader; diff --git a/src/layouts/components/Menu/components/Logo.tsx b/src/layouts/components/Menu/components/Logo.tsx index 800a2c2..9298841 100644 --- a/src/layouts/components/Menu/components/Logo.tsx +++ b/src/layouts/components/Menu/components/Logo.tsx @@ -1,9 +1,13 @@ -import { connect } from "react-redux"; +/* eslint-disable simple-import-sort/imports */ +import { useAppSelector } from "@/hooks/useRTK"; +import type { RootState } from "@/rtk"; import logo from "@/assets/images/logo.svg"; import logoMd from "@/assets/images/logo_md.png"; -const Logo = (props: any) => { - const { isCollapse } = props; + +const Logo = () => { + const menu = useAppSelector((state: RootState) => state.menu); + const { isCollapse } = menu; return (
logo @@ -11,5 +15,4 @@ const Logo = (props: any) => { ); }; -const mapStateToProps = (state: any) => state.menu; -export default connect(mapStateToProps)(Logo); +export default Logo; diff --git a/src/layouts/components/Menu/index.tsx b/src/layouts/components/Menu/index.tsx index 9176347..cfec778 100644 --- a/src/layouts/components/Menu/index.tsx +++ b/src/layouts/components/Menu/index.tsx @@ -1,16 +1,16 @@ +/* eslint-disable simple-import-sort/imports */ /** * 菜单控制 */ import React, { useEffect, useState } from "react"; -import { connect } from "react-redux"; import { useLocation, useNavigate } from "react-router-dom"; import * as Icons from "@ant-design/icons"; import type { MenuProps } from "antd"; import { Menu, Spin } from "antd"; -import { setAuthRouter } from "@/redux/modules/auth/action"; -import { setBreadcrumbList } from "@/redux/modules/breadcrumb/action"; -import { setMenuList } from "@/redux/modules/menu/action"; +import { useAppSelector } from "@/hooks/useRTK"; +import type { RootState } from "@/rtk"; +// import { setAuthRouter, setBreadcrumbList, setMenuList } from "@/rtk"; import { currentMenuList } from "@/routers/route"; import { getOpenKeys, searchRoute } from "@/utils/util"; import Logo from "./components/Logo"; @@ -19,7 +19,8 @@ import "./index.less"; const LayoutMenu = (props: any) => { const { pathname } = useLocation(); - const { isCollapse } = props; + const menu = useAppSelector((state: RootState) => state.menu); + const { isCollapse } = menu; const [selectedKeys, setSelectedKeys] = useState([pathname]); const [openKeys, setOpenKeys] = useState([]); @@ -79,7 +80,7 @@ const LayoutMenu = (props: any) => { // 点击当前菜单跳转页面 const navigate = useNavigate(); const clickMenu: MenuProps["onClick"] = ({ key }: { key: string }) => { - const route = searchRoute(key, props.menuList); + const route = searchRoute(key, menu.menuList); console.log({ route, props }); if (route.isLink) window.open(route.isLink, "_blank"); @@ -108,6 +109,4 @@ const LayoutMenu = (props: any) => { ); }; -const mapStateToProps = (state: any) => state.menu; -const mapDispatchToProps = { setMenuList, setBreadcrumbList, setAuthRouter }; -export default connect(mapStateToProps, mapDispatchToProps)(LayoutMenu); +export default LayoutMenu; diff --git a/src/layouts/components/Tabs/index.tsx b/src/layouts/components/Tabs/index.tsx index 0d8ace4..7e33803 100644 --- a/src/layouts/components/Tabs/index.tsx +++ b/src/layouts/components/Tabs/index.tsx @@ -1,21 +1,25 @@ +/* eslint-disable simple-import-sort/imports */ import { useEffect, useState } from "react"; -import { connect } from "react-redux"; import { useLocation, useNavigate } from "react-router-dom"; import { HomeFilled } from "@ant-design/icons"; import { message, Tabs } from "antd"; +import type { AppDispatch, RootState } from "@/rtk"; +import { setTabsList } from "@/rtk"; +import { useAppDispatch, useAppSelector } from "@/hooks/useRTK"; import { HOME_URL } from "@/config/config"; -import { setTabsList } from "@/redux/modules/tabs/action"; import { routerArray } from "@/routers"; import { searchRoute } from "@/utils/util"; import MoreButton from "./components/MoreButton"; import "./index.less"; -const LayoutTabs = (props: any) => { - const { tabsList } = props.tabs; - const { themeConfig } = props.global; - const { setTabsList } = props; +const LayoutTabs = () => { + const dispatch: AppDispatch = useAppDispatch(); + const global = useAppSelector((state: RootState) => state.global); + const tabs = useAppSelector((state: RootState) => state.tabs); + const { tabsList } = tabs; + const { themeConfig } = global; const { TabPane } = Tabs; const { pathname } = useLocation(); const navigate = useNavigate(); @@ -37,7 +41,7 @@ const LayoutTabs = (props: any) => { if (tabsList.every((item: any) => item.path !== route.path)) { newTabsList.push({ title: route.meta!.title, path: route.path }); } - setTabsList(newTabsList); + dispatch(setTabsList(newTabsList)); setActiveValue(pathname); }; @@ -53,7 +57,11 @@ const LayoutTabs = (props: any) => { }); } message.success("你删除了Tabs标签 😆😆😆"); - setTabsList(tabsList.filter((item: Menu.MenuOptions) => item.path !== tabPath)); + dispatch(setTabsList(tabsList.filter((item: Menu.MenuOptions) => item.path !== tabPath))); + }; + + const setTabs = (list: Menu.MenuOptions[]) => { + dispatch(setTabsList(list)); }; return ( @@ -85,13 +93,11 @@ const LayoutTabs = (props: any) => { ); })} - +
)} ); }; -const mapStateToProps = (state: any) => state; -const mapDispatchToProps = { setTabsList }; -export default connect(mapStateToProps, mapDispatchToProps)(LayoutTabs); +export default LayoutTabs; diff --git a/src/layouts/index.tsx b/src/layouts/index.tsx index 1d1dac0..be8799f 100644 --- a/src/layouts/index.tsx +++ b/src/layouts/index.tsx @@ -1,9 +1,10 @@ import { useEffect } from "react"; -import { connect } from "react-redux"; import { Outlet } from "react-router-dom"; import { Layout } from "antd"; -import { updateCollapse } from "@/redux/modules/menu/action"; +import { useAppDispatch, useAppSelector } from "@/hooks/useRTK"; +import type { AppDispatch, RootState } from "@/rtk"; +import { updateCollapse } from "@/rtk"; import LayoutFooter from "./components/Footer"; import LayoutHeader from "./components/Header"; import LayoutMenu from "./components/Menu"; @@ -11,17 +12,19 @@ import LayoutTabs from "./components/Tabs"; import "./index.less"; -const LayoutIndex = (props: any) => { +const LayoutIndex = () => { + const menu = useAppSelector((state: RootState) => state.menu); const { Sider, Content } = Layout; - const { isCollapse, updateCollapse, setAuthButtons } = props; + const { isCollapse } = menu; + const dispatch: AppDispatch = useAppDispatch(); // 监听窗口大小变化 const listeningWindow = () => { window.onresize = () => { return (() => { let screenWidth = document.body.clientWidth; - if (!isCollapse && screenWidth < 1200) updateCollapse(true); - if (!isCollapse && screenWidth > 1200) updateCollapse(false); + if (!isCollapse && screenWidth < 1200) dispatch(updateCollapse(true)); + if (!isCollapse && screenWidth > 1200) dispatch(updateCollapse(false)); })(); }; }; @@ -33,7 +36,7 @@ const LayoutIndex = (props: any) => { return ( // 这里不用 Layout 组件原因是切换页面时样式会先错乱然后在正常显示,造成页面闪屏效果
- + @@ -48,6 +51,4 @@ const LayoutIndex = (props: any) => { ); }; -const mapStateToProps = (state: any) => state.menu; -const mapDispatchToProps = { updateCollapse }; -export default connect(mapStateToProps, mapDispatchToProps)(LayoutIndex); +export default LayoutIndex; diff --git a/src/main.tsx b/src/main.tsx index 1eb8a51..e27ca67 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -4,7 +4,7 @@ import { Provider } from "react-redux"; import { PersistGate } from "redux-persist/integration/react"; import App from "@/App"; -import { persistor, store } from "@/redux"; +import { persistor, store } from "@/rtk"; import "@/styles/reset.less"; import "@/assets/iconfont/iconfont.less"; diff --git a/src/routers/utils/authRouter.tsx b/src/routers/utils/authRouter.tsx index 2afb4e0..03190ac 100644 --- a/src/routers/utils/authRouter.tsx +++ b/src/routers/utils/authRouter.tsx @@ -1,11 +1,14 @@ +/* eslint-disable simple-import-sort/imports */ // 路由权限控制 import { Navigate, useLocation } from "react-router-dom"; import { AxiosCanceler } from "@/api/helper/axiosCancel"; import { HOME_URL, LOGIN_URL } from "@/config/config"; -import { store } from "@/redux/index"; -import { rootRouter } from "@/routers/index"; +import { useAppSelector } from "@/hooks/useRTK"; +import type { RootState } from "@/rtk"; +import { store } from "@/rtk"; +import { rootRouter } from "@/routers"; import { searchRoute } from "@/utils/util"; const axiosCanceler = new AxiosCanceler(); @@ -14,6 +17,8 @@ const axiosCanceler = new AxiosCanceler(); * @description 路由守卫组件 * */ const AuthRouter = (props: { children: JSX.Element }) => { + const global = useAppSelector((state: RootState) => state.global); + const auth = useAppSelector((state: RootState) => state.auth); const { pathname } = useLocation(); const route = searchRoute(pathname, rootRouter); console.log({ route }); @@ -26,11 +31,11 @@ const AuthRouter = (props: { children: JSX.Element }) => { if (!route.meta?.requiresAuth) return props.children; // * 判断是否有Token - const token = store.getState().global.token; + const { token } = global; if (!token) return ; // * Dynamic Router(动态路由,根据后端返回的菜单数据生成的一维数组) - const dynamicRouter = store.getState().auth.authRouter; + const dynamicRouter = auth.authRouter; // * Static Router(静态路由,必须配置首页地址,否则不能进首页获取菜单、按钮权限等数据),获取数据的时候会loading,所有配置首页地址也没问题 const staticRouter = [HOME_URL, "/403"]; const routerList = dynamicRouter.concat(staticRouter); diff --git a/src/rtk/index.ts b/src/rtk/index.ts new file mode 100644 index 0000000..12d263e --- /dev/null +++ b/src/rtk/index.ts @@ -0,0 +1,11 @@ +/* eslint-disable simple-import-sort/imports */ +// 导出 store +export * from "./store"; + +// 导出 action +export { setAuthButtons, setAuthRouter } from "./slices/auth"; +export { setBreadcrumbList } from "./slices/breadcrumb"; +export { getDiscListAction } from "./slices/disc"; +export { setAssemblySize, setThemeConfig, setToken, setUserInfo } from "./slices/global"; +export { getMenuListAction, setMenuList, updateCollapse } from "./slices/menu"; +export { setTabsActive, setTabsList } from "./slices/tabs"; diff --git a/src/rtk/slices/auth.ts b/src/rtk/slices/auth.ts new file mode 100644 index 0000000..8f202ef --- /dev/null +++ b/src/rtk/slices/auth.ts @@ -0,0 +1,27 @@ +/* eslint-disable simple-import-sort/imports */ +/* eslint-disable prettier/prettier */ +import { createSlice } from "@reduxjs/toolkit"; + +import { AuthState } from "@/redux/interface"; + +export const authSlice = createSlice({ + name: "auth", + + initialState: { + authButtons: {}, + authRouter: [] + } as AuthState, + + reducers: { + setAuthButtons: (state, action) => { + state.authButtons = action.payload; + }, + setAuthRouter: (state, action) => { + state.authRouter = action.payload; + } + } +}); + +export const { setAuthButtons, setAuthRouter } = authSlice.actions; + +export default authSlice.reducer; diff --git a/src/rtk/slices/breadcrumb.ts b/src/rtk/slices/breadcrumb.ts new file mode 100644 index 0000000..a932e39 --- /dev/null +++ b/src/rtk/slices/breadcrumb.ts @@ -0,0 +1,23 @@ +/* eslint-disable simple-import-sort/imports */ +/* eslint-disable prettier/prettier */ +import { createSlice } from "@reduxjs/toolkit"; + +import { BreadcrumbState } from "@/redux/interface"; + +export const breadcrumbSlice = createSlice({ + name: "breadcrumb", + + initialState: { + breadcrumbList: {} + } as BreadcrumbState, + + reducers: { + setBreadcrumbList: (state, action) => { + state.breadcrumbList = action.payload; + } + } +}); + +export const { setBreadcrumbList } = breadcrumbSlice.actions; + +export default breadcrumbSlice.reducer; diff --git a/src/rtk/slices/disc.ts b/src/rtk/slices/disc.ts new file mode 100644 index 0000000..1a385a4 --- /dev/null +++ b/src/rtk/slices/disc.ts @@ -0,0 +1,76 @@ +/* eslint-disable simple-import-sort/imports */ +/* eslint-disable prettier/prettier */ +import { toPairs } from "lodash"; +import { createAsyncThunk, createSlice } from "@reduxjs/toolkit"; + +import { MapItem } from "@/typings/common"; +import { DiscState } from "@/redux/interface"; +import { getDiscListApi } from "@/api/modules/common"; + +export const discSlice = createSlice({ + name: "disc", + + initialState: { + disc: {} as MapItem + } as DiscState, + + reducers: { + // setDiscList: (state, action) => { + // state.disc = action.payload; + // } + }, + + // 处理异步 + extraReducers: builder => { + builder + // .addCase(getDiscListAction.pending, (state, action) => { + // // ... + // }) + .addCase(getDiscListAction.fulfilled, (state, action) => { + state.disc = action.payload; + }); + // .addCase(getDiscListAction.rejected, (state, action) => { + // // ... + // }) + } +}); + +const dictTransform = (dict = {}, keys = ["id", "title"]) => { + console.log("字典 d", dict); + + return toPairs(dict).map(item => { + return { + [keys[0]]: item[0], + [keys[1]]: item[1] + }; + }); +}; + +// 获取字典数据 +// 异步 action creator +// 外部可以直接调用 dispatch(getDiscListAction()) +export const getDiscListAction = createAsyncThunk("disc/getDiscList", async (_, { rejectWithValue }): Promise => { + try { + const { result } = (await getDiscListApi()) || {}; + console.log("获取字典,getDiscListAction"); + + let dictionaryMap = {}; + for (const key in result as object) { + if (Object.getOwnPropertyDescriptor(result, key)) { + // @ts-ignore + dictionaryMap[key] = result[key]; + // @ts-ignore + dictionaryMap[`${key}List`] = dictTransform(result[key], ["value", "label"]); + } + } + + console.log("字典", dictionaryMap); + + return dictionaryMap as MapItem; + } catch (error) { + rejectWithValue(error); + return {} as MapItem; + } +}); + +export default discSlice.reducer; diff --git a/src/rtk/slices/global.ts b/src/rtk/slices/global.ts new file mode 100644 index 0000000..8acd82b --- /dev/null +++ b/src/rtk/slices/global.ts @@ -0,0 +1,52 @@ +/* eslint-disable simple-import-sort/imports */ +/* eslint-disable prettier/prettier */ +import { createSlice } from "@reduxjs/toolkit"; + +import { GlobalState, ThemeConfigProp } from "@/redux/interface"; + +export const globalSlice = createSlice({ + name: "global", + + initialState: { + token: "", + userInfo: {}, + assemblySize: "middle", + themeConfig: { + // 默认 primary 主题颜色 + primary: "#1890ff", + // 深色模式 + isDark: false, + // 色弱模式(weak) || 灰色模式(gray) + weakOrGray: "", + // 面包屑导航 + breadcrumb: true, + // 标签页 + tabs: true, + // 页脚 + footer: true + } as ThemeConfigProp + } as GlobalState, + + reducers: { + setToken: (state, action) => { + // Redux Toolkit 允许我们在 reducers 中编写 mutating 逻辑。 + // 它实际上并没有 mutate state 因为它使用了 Immer 库, + // 它检测到草稿 state 的变化并产生一个全新的基于这些更改的不可变 state + state.token = action.payload; + }, + setUserInfo: (state, action) => { + state.userInfo = action.payload; + }, + setAssemblySize: (state, action) => { + state.assemblySize = action.payload; + }, + setThemeConfig: (state, action) => { + state.themeConfig = action.payload; + } + } +}); + +// 为每个 case reducer 函数生成 Action creators +export const { setToken, setUserInfo, setAssemblySize, setThemeConfig } = globalSlice.actions; + +export default globalSlice.reducer; diff --git a/src/rtk/slices/menu.ts b/src/rtk/slices/menu.ts new file mode 100644 index 0000000..453b793 --- /dev/null +++ b/src/rtk/slices/menu.ts @@ -0,0 +1,60 @@ +/* eslint-disable simple-import-sort/imports */ +/* eslint-disable prettier/prettier */ +import { createAsyncThunk, createSlice } from "@reduxjs/toolkit"; + +import { MenuState } from "@/redux/interface"; +import { getMenuList } from "@/api/modules/login"; + +export const menuSlice = createSlice({ + name: "menu", + + initialState: { + isCollapse: false, + menuList: [] as Menu.MenuOptions[] + } as MenuState, + + reducers: { + updateCollapse: (state, action) => { + // Redux Toolkit 允许我们在 reducers 中编写 mutating 逻辑。 + // 它实际上并没有 mutate state 因为它使用了 Immer 库, + // 它检测到草稿 state 的变化并产生一个全新的基于这些更改的不可变 state + state.isCollapse = action.payload; + }, + setMenuList: (state, action) => { + state.menuList = action.payload; + } + }, + + // 处理异步 + extraReducers: builder => { + builder + // .addCase(getDiscListAction.pending, (state, action) => { + // // ... + // }) + .addCase(getMenuListAction.fulfilled, (state, action) => { + state.menuList = action.payload; + }); + // .addCase(getDiscListAction.rejected, (state, action) => { + // // ... + // }) + } +}); + +// 外部可以直接调用 dispatch(getMenuListAction()) +export const getMenuListAction = createAsyncThunk( + "menu/getMenuList", + async (_, { rejectWithValue }): Promise => { + try { + const res = await getMenuList(); + return (res.data as Menu.MenuOptions[]) ?? []; + } catch (error) { + rejectWithValue(error); + return []; + } + } +); + +// 为每个 case reducer 函数生成 Action creators +export const { updateCollapse, setMenuList } = menuSlice.actions; + +export default menuSlice.reducer; diff --git a/src/rtk/slices/tabs.ts b/src/rtk/slices/tabs.ts new file mode 100644 index 0000000..5e9b292 --- /dev/null +++ b/src/rtk/slices/tabs.ts @@ -0,0 +1,29 @@ +/* eslint-disable simple-import-sort/imports */ +/* eslint-disable prettier/prettier */ +import { createSlice } from "@reduxjs/toolkit"; + +import { TabsState } from "@/redux/interface"; +import { HOME_URL } from "@/config/config"; + +export const tabsSlice = createSlice({ + name: "tabs", + + initialState: { + // tabsActive 其实没啥用,使用 pathname 就可以了😂 + tabsActive: HOME_URL, + tabsList: [{ title: "首页", path: HOME_URL }] as Menu.MenuOptions[] + } as TabsState, + + reducers: { + setTabsList: (state, action) => { + state.tabsList = action.payload; + }, + setTabsActive: (state, action) => { + state.tabsActive = action.payload; + } + } +}); + +export const { setTabsList, setTabsActive } = tabsSlice.actions; + +export default tabsSlice.reducer; diff --git a/src/rtk/store.ts b/src/rtk/store.ts new file mode 100644 index 0000000..5f98bfe --- /dev/null +++ b/src/rtk/store.ts @@ -0,0 +1,49 @@ +import { combineReducers, configureStore, Store } from "@reduxjs/toolkit"; +import { persistReducer, persistStore } from "redux-persist"; +import storage from "redux-persist/lib/storage"; + +import auth from "@/rtk/slices/auth"; +import breadcrumb from "@/rtk/slices/breadcrumb"; +import disc from "@/rtk/slices/disc"; +import global from "@/rtk/slices/global"; +import menu from "@/rtk/slices/menu"; +import tabs from "@/rtk/slices/tabs"; + +// 对标 @/redux/index.ts +// 创建reducer(拆分reducer) +const reducer = combineReducers({ + global, + menu, + tabs, + auth, + breadcrumb, + disc +}); + +// redux 持久化配置 +const persistConfig = { + key: "rtk-state", + storage // 默认使用 localStorage +}; +const persistReducerConfig = persistReducer(persistConfig, reducer); + +// 创建 store +const store: Store = configureStore({ + reducer: persistReducerConfig, + // RTK 在开发环境默认开启 redux-devtools + devTools: { + name: "技术派 Store", // 在 DevTools 中显示的名称 + // maxAge: 30, // 限制历史记录条数 + trace: true // 启用 action 堆栈跟踪 + } +}); + +export type RootState = ReturnType; + +// 定义自定义的 dispatch 类型 +export type AppDispatch = typeof store.dispatch; + +// 创建持久化 store +const persistor = persistStore(store); + +export { persistor, store }; diff --git a/src/views/article/edit/index.tsx b/src/views/article/edit/index.tsx index 1024b96..209629a 100644 --- a/src/views/article/edit/index.tsx +++ b/src/views/article/edit/index.tsx @@ -1,17 +1,19 @@ +/* eslint-disable simple-import-sort/imports */ /* eslint-disable prettier/prettier */ import { FC, useCallback, useEffect, useRef, useState } from "react"; -import { connect } from "react-redux"; import { useLocation, useNavigate } from "react-router"; -import gemoji from '@bytemd/plugin-gemoji'; -import gfm from '@bytemd/plugin-gfm'; +import gemoji from "@bytemd/plugin-gemoji"; +import gfm from "@bytemd/plugin-gfm"; import highlight from "@bytemd/plugin-highlight"; -import math from '@bytemd/plugin-math'; -import mediumZoom from '@bytemd/plugin-medium-zoom'; -import { Editor } from '@bytemd/react'; -import { Button, Drawer, Form, Input, message,Radio, Space, UploadFile } from "antd"; +import math from "@bytemd/plugin-math"; +import mediumZoom from "@bytemd/plugin-medium-zoom"; +import { Editor } from "@bytemd/react"; +import { Button, Drawer, Form, Input, message, Radio, Space, UploadFile } from "antd"; import TextArea from "antd/es/input/TextArea"; -import zhHans from 'bytemd/locales/zh_Hans.json'; +import zhHans from "bytemd/locales/zh_Hans.json"; +import { useAppSelector } from "@/hooks/useRTK"; +import type { RootState } from "@/rtk"; import { getArticleApi, saveArticleApi, saveImgApi } from "@/api/modules/article"; import { uploadImgApi } from "@/api/modules/common"; import { ContentInterWrap, ContentWrap } from "@/components/common-wrap"; @@ -22,10 +24,10 @@ import DebounceSelect from "@/views/article/components/debounceselect/index"; import ImgUpload from "@/views/column/setting/components/imgupload"; import Search from "./search"; -import 'katex/dist/katex.css'; -import 'highlight.js/styles/default.css'; -import 'bytemd/dist/index.css'; -import 'juejin-markdown-themes/dist/juejin.css'; +import "katex/dist/katex.css"; +import "highlight.js/styles/default.css"; +import "bytemd/dist/index.css"; +import "juejin-markdown-themes/dist/juejin.css"; import "./index.scss"; const plugins = [ @@ -33,9 +35,9 @@ const plugins = [ highlight(), gemoji(), math(), - mediumZoom(), + mediumZoom() // Add more plugins here -] +]; interface IProps {} @@ -45,14 +47,14 @@ interface TagValue { } interface ImageInfo { - img: string; - alt: string; - src: string; + img: string; + alt: string; + src: string; index: number; // 图片在文本中的位置 } export interface IFormType { - articleId: number;// 文章id + articleId: number; // 文章id status: number; // 文章状态 content: string; // 文章内容 cover: string; // 封面 @@ -60,14 +62,16 @@ export interface IFormType { } const defaultInitForm: IFormType = { - articleId: 0,// 后台默认为 0 + articleId: 0, // 后台默认为 0 status: 0, content: "", cover: "", - tagIds: [], -} + tagIds: [] +}; + +const ArticleEdit: FC = () => { + const disc = useAppSelector((state: RootState) => state.disc.disc); -const ArticleEdit: FC = props => { const [formRef] = Form.useForm(); const [form, setForm] = useState(defaultInitForm); @@ -75,9 +79,9 @@ const ArticleEdit: FC = props => { // 抽屉 const [isOpenDrawerShow, setIsOpenDrawerShow] = useState(false); // 文章内容 - const [content, setContent] = useState(''); + const [content, setContent] = useState(""); // 上一次转链后的内容 - const [lastContent, setLastContent] = useState(''); + const [lastContent, setLastContent] = useState(""); // 放图片的路径,和上传时间,30s 内防止重复提交 const lastUploadTimes = useRef>(new Map()); @@ -91,14 +95,14 @@ const ArticleEdit: FC = props => { const navigate = useNavigate(); // 取出来 articleId 和 status // 当前的状态,用于新增还是更新,新增的时候不传递 id,更新的时候传递 id - const { articleId, status } = location.state || {}; + const { articleId, status } = location.state || {}; console.log("看看前面是否把参数传递了过来", articleId, status); // 声明一个 coverList,封面 const [coverList, setCoverList] = useState([]); //@ts-ignore - const { CategoryTypeList, CategoryType} = props || {}; + const { CategoryTypeList, CategoryType } = disc || {}; console.log("CategoryTypeList", CategoryTypeList, CategoryType); const onSure = useCallback(() => { @@ -123,9 +127,9 @@ const ArticleEdit: FC = props => { }; const goBack = () => { - // 跳转到文章列表页 - navigate("/article/list/index"); - }; + // 跳转到文章列表页 + navigate("/article/list/index"); + }; // 重置表单 const resetFrom = () => { @@ -153,7 +157,7 @@ const ArticleEdit: FC = props => { // 更新上传时间 lastUploadTimes.current.set(url, now); return true; - } + }; // 如果是外网的图片链接,转成内网的图片链接 const uploadImages = async (newVal: string) => { @@ -172,18 +176,17 @@ const ArticleEdit: FC = props => { } // 正则表达式 - const reg = /!\[(.*?)\]\((.*?)\)/mg; + const reg = /!\[(.*?)\]\((.*?)\)/gm; let match; let uploadTasks = []; - let imageInfos:ImageInfo[] = []; // 用于存储图片信息和它们在文本中的位置 + let imageInfos: ImageInfo[] = []; // 用于存储图片信息和它们在文本中的位置 while ((match = reg.exec(add)) !== null) { const [img, alt, src] = match; console.log("img, alt, src", match, img, alt, src); // 如果是外网的图片链接,转成内网的图片链接 - if (src.length > 0 && src.startsWith("http") - && src.indexOf("saveError") < 0) { + if (src.length > 0 && src.startsWith("http") && src.indexOf("saveError") < 0) { // 收集图片信息 imageInfos.push({ img, alt, src, index: match.index }); // 判断图片的链接是否已经上传过了 @@ -202,19 +205,19 @@ const ArticleEdit: FC = props => { // 替换所有图片链接 let newContent = newVal; results.forEach((result, i) => { - if (result.status && result.status.code === 0 && result.result) { - // 重新组织图片的路径 - const newSrc = `![${imageInfos[i].alt}](${result.result.imagePath})`; - console.log("newSrc", newSrc); - // 替换后的内容 - newContent = newContent.replace(imageInfos[i].img, newSrc); - console.log("newContent", newContent); - } + if (result.status && result.status.code === 0 && result.result) { + // 重新组织图片的路径 + const newSrc = `![${imageInfos[i].alt}](${result.result.imagePath})`; + console.log("newSrc", newSrc); + // 替换后的内容 + newContent = newContent.replace(imageInfos[i].img, newSrc); + console.log("newContent", newContent); + } }); setLastContent(newVal); return newContent; - } + }; const handleReplaceImgUrl = async () => { const { content } = form; @@ -223,7 +226,7 @@ const ArticleEdit: FC = props => { setContent(newContent); handleChange({ content: newContent }); } - } + }; // 编辑或者新增时提交数据到服务器端 const handleSubmit = async () => { @@ -255,7 +258,7 @@ const ArticleEdit: FC = props => { source: 2, // 草稿还是发布 actionType: "post", - articleId: status === UpdateEnum.Save ? UpdateEnum.Save : articleId, + articleId: status === UpdateEnum.Save ? UpdateEnum.Save : articleId }; console.log("submit 之前的所有值:", newValues); @@ -274,11 +277,9 @@ const ArticleEdit: FC = props => { useEffect(() => { const getArticle = async () => { console.log("此时是否还有 ", articleId, status); - const { status: resultStatus, result } = await getArticleApi( - articleId - ); + const { status: resultStatus, result } = await getArticleApi(articleId); const { code } = resultStatus || {}; - if (code === 0 && status === UpdateEnum.Edit) { + if (code === 0 && status === UpdateEnum.Edit) { console.log("result", result); // 如果 status 为编辑,就请求数据 @@ -313,11 +314,11 @@ const ArticleEdit: FC = props => { // 保存的时候需要 handleChange({ content: result?.content, - articleId: result?.articleId, + articleId: result?.articleId }); - } + } }; - + getArticle(); }, []); @@ -352,34 +353,25 @@ const ArticleEdit: FC = props => { handleFormRefChange={handleFormRefChange} /> - - + - - - + options={CategoryTypeList} + > + + {/*用下拉框做一个教程的选择 */} { - console.log('选中的值:', selectedValues); - // @ts-ignore - const keys = selectedValues.map(item => Number(item.key)); - - console.log('keysString', keys); - handleChange({ tagIds: keys }); - } - } + onChange={selectedValues => { + console.log("选中的值:", selectedValues); + // @ts-ignore + const keys = selectedValues.map(item => Number(item.key)); + + console.log("keysString", keys); + handleChange({ tagIds: keys }); + }} /> @@ -388,46 +380,41 @@ const ArticleEdit: FC = props => { return (
- + { + uploadImages={files => { return Promise.all( - files.map((file) => { + files.map(file => { // 限制图片大小,不超过 5M if (file.size > 5 * 1024 * 1024) { - return { - url: "图片大小不能超过 5M", - } + return { + url: "图片大小不能超过 5M" + }; } const formData = new FormData(); - formData.append('image', file); + formData.append("image", file); return uploadImgApi(formData).then(({ status, result }) => { const { code, msg } = status || {}; const { imagePath } = result || {}; if (code === 0) { return { - url: imagePath, - } + url: imagePath + }; } return { - url: msg, - } - }) + url: msg + }; + }); }) - ) + ); }} - onChange={(v) => { + onChange={v => { // 右侧的预览更新 setContent(v); handleChange({ content: v }); @@ -457,6 +444,4 @@ const ArticleEdit: FC = props => { ); }; -const mapStateToProps = (state: any) => state.disc.disc; -const mapDispatchToProps = {}; -export default connect(mapStateToProps, mapDispatchToProps)(ArticleEdit); +export default ArticleEdit; diff --git a/src/views/article/list/index.tsx b/src/views/article/list/index.tsx index 75de4d8..c5934d7 100644 --- a/src/views/article/list/index.tsx +++ b/src/views/article/list/index.tsx @@ -1,12 +1,14 @@ +/* eslint-disable simple-import-sort/imports */ /* eslint-disable prettier/prettier */ import { FC, useCallback, useEffect, useState } from "react"; -import { connect } from "react-redux"; import { useNavigate } from "react-router"; import { DeleteOutlined, EditOutlined, HighlightOutlined } from "@ant-design/icons"; import { Avatar, Button, Form, Input, message, Modal, Select, Switch, Table, Tooltip } from "antd"; import type { ColumnsType } from "antd/es/table"; import dayjs from "dayjs"; +import { useAppSelector } from "@/hooks/useRTK"; +import type { RootState } from "@/rtk"; import { delArticleApi, getArticleListApi, operateArticleApi, updateArticleApi } from "@/api/modules/article"; import { ContentInterWrap, ContentWrap } from "@/components/common-wrap"; import { initPagination, IPagination, UpdateEnum } from "@/enums/common"; @@ -61,11 +63,13 @@ const defaultSearchForm = { userName: "", title: "", status: -1, - toppingStat : -1, - officalStat : -1 + toppingStat: -1, + officalStat: -1 }; -const Article: FC = props => { +const Article: FC = () => { + const disc = useAppSelector((state: RootState) => state.disc.disc); + const [formRef] = Form.useForm(); // 编辑表单 const [form, setForm] = useState(defaultInitForm); @@ -93,7 +97,7 @@ const Article: FC = props => { // 一些配置项 //@ts-ignore - const { PushStatusList, ToppingStatusList, OfficalStatusList} = props || {}; + const { PushStatusList, ToppingStatusList, OfficalStatusList } = disc || {}; const { articleId } = form; @@ -102,7 +106,7 @@ const Article: FC = props => { const onSure = useCallback(() => { setQuery(prev => prev + 1); }, []); - + // 编辑表单值改变 const handleChange = (item: MapItem) => { setForm({ ...form, ...item }); @@ -112,7 +116,7 @@ const Article: FC = props => { const handleSearchChange = (item: MapItem) => { // 当 status 的值为 -1 时,重新显示 setSearchForm({ ...searchForm, ...item }); - console.log("查询条件变化了",searchForm); + console.log("查询条件变化了", searchForm); }; // 当点击查询按钮的时候触发 @@ -157,7 +161,7 @@ const Article: FC = props => { } else if (operateType === 1) { operateDesc = "推荐"; } - + const { status } = await operateArticleApi({ articleId, operateType }); const { code, msg } = status || {}; if (code === 0) { @@ -172,7 +176,7 @@ const Article: FC = props => { const handleStatusChange = async (articleId: number, status: number) => { // 将 articleId 和 status 作为参数传递给 updateArticleApi const newValues = { articleId, status }; - const { status: successStatus } = await updateArticleApi(newValues) || {}; + const { status: successStatus } = (await updateArticleApi(newValues)) || {}; const { code, msg } = successStatus || {}; if (code === 0) { message.success("状态操作成功"); @@ -186,10 +190,12 @@ const Article: FC = props => { // 导航到文章编辑页面 const handleEdit = (articleId: number) => { console.log("articleId", articleId); - navigate("/article/edit/index", { state: { - articleId, - status: UpdateEnum.Edit - }}); + navigate("/article/edit/index", { + state: { + articleId, + status: UpdateEnum.Edit + } + }); }; const handleSubmit = async () => { @@ -198,7 +204,7 @@ const Article: FC = props => { const { status } = form; const newValues = { ...values, articleId, status }; console.log("编辑 时提交的 newValues:", newValues); - + const { status: successStatus } = (await updateArticleApi(newValues)) || {}; const { code, msg } = successStatus || {}; if (code === 0) { @@ -213,8 +219,8 @@ const Article: FC = props => { // 数据请求,这是一个钩子,query, current, pageSize, search 有变化的时候就会自动触发 useEffect(() => { const getSortList = async () => { - const { status, result } = await getArticleListApi({ - pageNumber: current, + const { status, result } = await getArticleListApi({ + pageNumber: current, pageSize, ...searchForm }); @@ -238,10 +244,7 @@ const Article: FC = props => { key: "title", render(value, item) { return ( - + {value} ); @@ -254,20 +257,26 @@ const Article: FC = props => { width: 120, render: (value: string) => { const time = dayjs(value); - return {time.format('MM-DD HH:mm')}; + return ( + + {time.format("MM-DD HH:mm")} + + ); } - }, + }, { title: "作者", dataIndex: "authorName", width: 110, key: "authorName", render(value) { - return <> - - {value.slice(0, 3)} - - ; + return ( + <> + + {value.slice(0, 3)} + + + ); } }, { @@ -278,12 +287,9 @@ const Article: FC = props => { const { articleId, toppingStat } = item; // 返回的是 0 和 1 const isTopped = toppingStat === 1; - + const topStatus = isTopped ? 4 : 3; // 3-置顶;4-取消置顶 - return handleOperate(articleId, topStatus)} - />; + return handleOperate(articleId, topStatus)} />; } }, { @@ -295,10 +301,7 @@ const Article: FC = props => { const { articleId, officalStat } = item; const isOffical = officalStat === 1; const officalStatus = isOffical ? 2 : 1; // 1-官方推荐;2-取消官方推荐 - return handleOperate(articleId, officalStatus)} - />; + return handleOperate(articleId, officalStatus)} />; } }, { @@ -307,14 +310,15 @@ const Article: FC = props => { key: "status", render(_, item) { const { articleId, status } = item; - return ; + return ( + + ); } }, { @@ -351,12 +355,7 @@ const Article: FC = props => { > - +
); @@ -375,12 +374,12 @@ const Article: FC = props => { }} /> - + > { @@ -415,6 +414,4 @@ const Article: FC = props => { ); }; -const mapStateToProps = (state: any) => state.disc.disc; -const mapDispatchToProps = {}; -export default connect(mapStateToProps, mapDispatchToProps)(Article); +export default Article; diff --git a/src/views/author/whitelist/index.tsx b/src/views/author/whitelist/index.tsx index 0cac20f..a6aaaba 100644 --- a/src/views/author/whitelist/index.tsx +++ b/src/views/author/whitelist/index.tsx @@ -1,12 +1,14 @@ +/* eslint-disable simple-import-sort/imports */ /* eslint-disable prettier/prettier */ import { FC, useCallback, useEffect, useRef, useState } from "react"; -import Highlighter from 'react-highlight-words'; -import { connect } from "react-redux"; +import Highlighter from "react-highlight-words"; import { DeleteOutlined, PlusOutlined, SearchOutlined } from "@ant-design/icons"; import { Avatar, Button, Drawer, Form, Input, InputRef, message, Modal, Space, Table } from "antd"; -import type { ColumnsType,ColumnType } from "antd/es/table"; +import type { ColumnsType, ColumnType } from "antd/es/table"; import { FilterConfirmProps } from "antd/es/table/interface"; +import { useAppSelector } from "@/hooks/useRTK"; +import type { RootState } from "@/rtk"; import { getAuthorWhiteListApi, resetAuthorWhiteApi, updateAuthorWhiteApi } from "@/api/modules/author"; import { ContentInterWrap, ContentWrap } from "@/components/common-wrap"; import { MapItem } from "@/typings/common"; @@ -31,10 +33,12 @@ export interface IFormType { const defaultInitForm: IFormType = { authorId: -1, - authorName: "", + authorName: "" }; -const AuthorWhiteList: FC = props => { +const AuthorWhiteList: FC = () => { + const disc = useAppSelector((state: RootState) => state.disc.disc); + const [formRef] = Form.useForm(); // form值 const [form, setForm] = useState(defaultInitForm); @@ -47,9 +51,9 @@ const AuthorWhiteList: FC = props => { const { authorName, authorId } = form; - const [searchText, setSearchText] = useState(''); - const [searchedColumn, setSearchedColumn] = useState(''); - const searchInput = useRef(null); + const [searchText, setSearchText] = useState(""); + const [searchedColumn, setSearchedColumn] = useState(""); + const searchInput = useRef(null); const onSure = useCallback(() => { setQuery(prev => prev + 1); @@ -61,7 +65,7 @@ const AuthorWhiteList: FC = props => { console.log("handleChange item setForm", item, form); formRef.setFieldsValue({ ...item }); }; - + // 抽屉关闭 const handleClose = () => { setIsDrawerOpen(false); @@ -81,20 +85,16 @@ const AuthorWhiteList: FC = props => { type DataIndex = keyof DataType; - const handleSearch = ( - selectedKeys: string[], - confirm: (param?: FilterConfirmProps) => void, - dataIndex: DataIndex, - ) => { - confirm(); - setSearchText(selectedKeys[0]); - setSearchedColumn(dataIndex); - }; + const handleSearch = (selectedKeys: string[], confirm: (param?: FilterConfirmProps) => void, dataIndex: DataIndex) => { + confirm(); + setSearchText(selectedKeys[0]); + setSearchedColumn(dataIndex); + }; const handleReset = (clearFilters: () => void) => { - clearFilters(); - setSearchText(''); - }; + clearFilters(); + setSearchText(""); + }; // 删除 const handleDel = (userId: number) => { @@ -106,7 +106,7 @@ const AuthorWhiteList: FC = props => { onOk: async () => { const { status } = await delAuthorWhiteApi(userId); const { code, msg } = status || {}; - + if (code === 0) { message.success("删除成功"); onSure(); @@ -129,7 +129,6 @@ const AuthorWhiteList: FC = props => { } else { message.error(msg); } - }; // 数据请求 @@ -149,81 +148,75 @@ const AuthorWhiteList: FC = props => { }, [query]); const getColumnSearchProps = (dataIndex: DataIndex): ColumnType => ({ - filterDropdown: ({ setSelectedKeys, selectedKeys, confirm, clearFilters, close }) => ( -
e.stopPropagation()}> - setSelectedKeys(e.target.value ? [e.target.value] : [])} - onPressEnter={() => handleSearch(selectedKeys as string[], confirm, dataIndex)} - style={{ marginBottom: 8, display: 'block' }} - /> - - - - - - -
- ), - filterIcon: (filtered: boolean) => ( - - ), - onFilter: (value, record) => - record[dataIndex] - .toString() - .toLowerCase() - .includes((value as string).toLowerCase()), - onFilterDropdownOpenChange: (visible) => { - if (visible) { - setTimeout(() => searchInput.current?.select(), 100); - } - }, - render: (text) => - searchedColumn === dataIndex ? ( - - ) : ( - text - ), - }); + filterDropdown: ({ setSelectedKeys, selectedKeys, confirm, clearFilters, close }) => ( +
e.stopPropagation()}> + setSelectedKeys(e.target.value ? [e.target.value] : [])} + onPressEnter={() => handleSearch(selectedKeys as string[], confirm, dataIndex)} + style={{ marginBottom: 8, display: "block" }} + /> + + + + + + +
+ ), + filterIcon: (filtered: boolean) => , + onFilter: (value, record) => + record[dataIndex] + .toString() + .toLowerCase() + .includes((value as string).toLowerCase()), + onFilterDropdownOpenChange: visible => { + if (visible) { + setTimeout(() => searchInput.current?.select(), 100); + } + }, + render: text => + searchedColumn === dataIndex ? ( + + ) : ( + text + ) + }); // 表头设置 const columns: ColumnsType = [ @@ -231,7 +224,7 @@ const AuthorWhiteList: FC = props => { title: "作者名称", dataIndex: "userName", key: "userName", - ...getColumnSearchProps('userName'), + ...getColumnSearchProps("userName") }, { title: "作者头像", @@ -265,12 +258,7 @@ const AuthorWhiteList: FC = props => { // 编辑表单 const reviseDrawerContent = ( -
+ @@ -283,48 +271,39 @@ const AuthorWhiteList: FC = props => { {/* 新增 */}
-
- -
+
-
- + {/* 表格 */} {/* 抽屉 */} - - - - - } - > + + + + + } + > {reviseDrawerContent} ); }; -const mapStateToProps = (state: any) => state.disc.disc; -const mapDispatchToProps = {}; -export default connect(mapStateToProps, mapDispatchToProps)(AuthorWhiteList); +export default AuthorWhiteList; diff --git a/src/views/author/zsxqlist/index.tsx b/src/views/author/zsxqlist/index.tsx index c7611b9..9d9d065 100644 --- a/src/views/author/zsxqlist/index.tsx +++ b/src/views/author/zsxqlist/index.tsx @@ -1,11 +1,19 @@ +/* eslint-disable simple-import-sort/imports */ /* eslint-disable prettier/prettier */ import { FC, useCallback, useEffect, useState } from "react"; -import { connect } from "react-redux"; import { DeleteOutlined, EditOutlined, UndoOutlined } from "@ant-design/icons"; import { Avatar, Badge, Button, Form, Input, message, Modal, RadioChangeEvent, Select, Table, Tag, Tooltip } from "antd"; import type { ColumnsType } from "antd/es/table"; -import { getZsxqWhiteListApi, operateBatchZsxqWhiteApi, operateZsxqWhiteApi, resetAuthorWhiteApi, updateZsxqWhiteApi } from "@/api/modules/author"; +import type { RootState } from "@/rtk"; +import { useAppSelector } from "@/hooks/useRTK"; +import { + getZsxqWhiteListApi, + operateBatchZsxqWhiteApi, + operateZsxqWhiteApi, + resetAuthorWhiteApi, + updateZsxqWhiteApi +} from "@/api/modules/author"; import { ContentInterWrap, ContentWrap } from "@/components/common-wrap"; import { initPagination, IPagination } from "@/enums/common"; import { MapItem } from "@/typings/common"; @@ -65,7 +73,9 @@ const defaultSearchForm = { state: -1 }; -const Zsxqlist: FC = props => { +const Zsxqlist: FC = () => { + const disc = useAppSelector((state: RootState) => state.disc.disc); + const [formRef] = Form.useForm(); // 编辑表单 const [form, setForm] = useState(defaultInitForm); @@ -97,20 +107,20 @@ const Zsxqlist: FC = props => { // 一些配置项,从字典里取出来 //@ts-ignore - const { UserAIStatList, UserAiStrategy, UserAiStrategyList, LoginType, LoginTypeList } = props || {}; + const { UserAIStatList, UserAiStrategy, UserAiStrategyList, LoginType, LoginTypeList } = disc || {}; console.log("UserAiStrategyList", UserAiStrategyList, LoginTypeList); const colorStrategys = ["#f50", "#2db7f5", "#87d068", "#108ee9"]; const colorLoginTypes = ["#1890ff", "#7265e6"]; //@ts-ignore const colorStrategyMap = UserAiStrategyList.reduce((acc, strategy, index) => { - acc[strategy.value] = colorStrategys[index % colorStrategys.length]; - return acc; + acc[strategy.value] = colorStrategys[index % colorStrategys.length]; + return acc; }, {} as { [key: string]: string }); //@ts-ignore const colorLoginTypeMap = LoginTypeList.reduce((acc, loginType, index) => { - acc[loginType.value] = colorLoginTypes[index % colorLoginTypes.length]; - return acc; + acc[loginType.value] = colorLoginTypes[index % colorLoginTypes.length]; + return acc; }, {} as { [key: string]: string }); const { id } = form; @@ -314,7 +324,7 @@ const Zsxqlist: FC = props => { title: "用户昵称", dataIndex: "name", width: 120, - key: "name", + key: "name" }, { title: "注册类型", @@ -341,7 +351,7 @@ const Zsxqlist: FC = props => { // 如果 desc 的长度大于 5,那么就截取前 5 个字符 let len = desc.length; if (len > 8) { - len = 8 + len = 8; } return ( <> @@ -367,7 +377,7 @@ const Zsxqlist: FC = props => { title: "邀请编号", dataIndex: "inviteCode", key: "inviteCode", - width: 80, + width: 80 }, { title: "状态", @@ -413,12 +423,10 @@ const Zsxqlist: FC = props => { }); console.log("formRef item", formRef.getFieldsValue()); }} - > - + > - + ); @@ -454,10 +462,7 @@ const Zsxqlist: FC = props => { /> - + ); @@ -486,6 +491,4 @@ const Zsxqlist: FC = props => { ); }; -const mapStateToProps = (state: any) => state.disc.disc; -const mapDispatchToProps = {}; -export default connect(mapStateToProps, mapDispatchToProps)(Zsxqlist); +export default Zsxqlist; diff --git a/src/views/category/index.tsx b/src/views/category/index.tsx index 112a41c..df6fac8 100644 --- a/src/views/category/index.tsx +++ b/src/views/category/index.tsx @@ -1,10 +1,12 @@ +/* eslint-disable simple-import-sort/imports */ /* eslint-disable prettier/prettier */ import { FC, useCallback, useEffect, useState } from "react"; -import { connect } from "react-redux"; import { DeleteOutlined, EditOutlined } from "@ant-design/icons"; -import { Button, Drawer, Form, Input, InputNumber,message, Modal, Space, Switch,Table } from "antd"; +import { Button, Drawer, Form, Input, InputNumber, message, Modal, Space, Switch, Table } from "antd"; import type { ColumnsType } from "antd/es/table"; +import { useAppSelector } from "@/hooks/useRTK"; +import type { RootState } from "@/rtk"; import { delCategoryApi, getCategoryListApi, operateCategoryApi, updateCategoryApi } from "@/api/modules/category"; import { ContentInterWrap, ContentWrap } from "@/components/common-wrap"; import { initPagination, IPagination, UpdateEnum } from "@/enums/common"; @@ -33,7 +35,9 @@ const defaultInitForm: IFormType = { rank: -1 }; -const Category: FC = props => { +const Category: FC = () => { + const disc = useAppSelector((state: RootState) => state.disc.disc); + const [formRef] = Form.useForm(); // form值 const [form, setForm] = useState(defaultInitForm); @@ -107,7 +111,7 @@ const Category: FC = props => { onOk: async () => { const { status } = await delCategoryApi(categoryId); const { code, msg } = status || {}; - + if (code === 0) { message.success("删除成功"); onSure(); @@ -120,9 +124,9 @@ const Category: FC = props => { const handleSubmit = async () => { const values = await formRef.validateFields(); - const newValues = { - ...values, - categoryId: status === UpdateEnum.Save ? UpdateEnum.Save : categoryId + const newValues = { + ...values, + categoryId: status === UpdateEnum.Save ? UpdateEnum.Save : categoryId }; const { status: successStatus } = (await updateCategoryApi(newValues)) || {}; @@ -134,7 +138,6 @@ const Category: FC = props => { } else { message.error(msg); } - }; // 上线/下线 @@ -152,10 +155,10 @@ const Category: FC = props => { // 数据请求 useEffect(() => { const getSortList = async () => { - const { status, result } = await getCategoryListApi({ + const { status, result } = await getCategoryListApi({ ...searchForm, - pageNumber: current, - pageSize + pageNumber: current, + pageSize }); const { code } = status || {}; //@ts-ignore @@ -189,9 +192,8 @@ const Category: FC = props => { onChange={() => { const pushStatus = status === 0 ? 1 : 0; handleOperate(item.categoryId, pushStatus); - } - } - /> + }} + /> ); } }, @@ -221,7 +223,7 @@ const Category: FC = props => { > 编辑 - + @@ -233,12 +235,7 @@ const Category: FC = props => { // 编辑表单 const reviseDrawerContent = ( -
+ = props => {
{/* 搜索 */} - + {/* 表格 */}
{/* 抽屉 */} - - - - - } - > + + + + + } + > {reviseDrawerContent} ); }; -const mapStateToProps = (state: any) => state.disc.disc; -const mapDispatchToProps = {}; -export default connect(mapStateToProps, mapDispatchToProps)(Category); +export default Category; diff --git a/src/views/column/article/index.tsx b/src/views/column/article/index.tsx index 329fee0..968c479 100644 --- a/src/views/column/article/index.tsx +++ b/src/views/column/article/index.tsx @@ -1,14 +1,20 @@ /* eslint-disable react/jsx-no-comment-textnodes */ /* eslint-disable prettier/prettier */ import { FC, useCallback, useEffect, useState } from "react"; -import { connect } from "react-redux"; import { DeleteOutlined, EyeOutlined } from "@ant-design/icons"; -import { Button, Descriptions, Drawer, Form, Image,Input, message, Modal, Space, Table } from "antd"; +import { Button, Descriptions, Drawer, Form, Image, Input, message, Modal, Space, Table } from "antd"; import type { ColumnsType } from "antd/es/table"; -import { delColumnArticleApi, getColumnArticleListApi, getColumnByNameListApi, updateColumnArticleApi } from "@/api/modules/column"; +import { + delColumnArticleApi, + getColumnArticleListApi, + getColumnByNameListApi, + updateColumnArticleApi +} from "@/api/modules/column"; import { ContentInterWrap, ContentWrap } from "@/components/common-wrap"; import { initPagination, IPagination, UpdateEnum } from "@/enums/common"; +import { useAppSelector } from "@/hooks/useRTK"; +import type { RootState } from "@/rtk"; import { MapItem } from "@/typings/common"; import { baseDomain } from "@/utils/util"; import { getCompleteUrl } from "@/utils/util"; @@ -60,7 +66,7 @@ const defaultInitForm: IFormType = { // 查询表单默认值 const defaultSearchForm = { articleTitle: "", - columnId: -1, + columnId: -1 }; // Usage of DebounceSelect @@ -70,7 +76,8 @@ interface ColumnValue { value: string; } -const ColumnArticle: FC = props => { +const ColumnArticle: FC = () => { + const disc = useAppSelector((state: RootState) => state.disc.disc); const [formRef] = Form.useForm(); // form值(详情和新增的时候会用到) @@ -134,7 +141,7 @@ const ColumnArticle: FC = props => { const handleSearchChange = (item: MapItem) => { // 当 status 的值为 -1 时,重新显示 setSearchForm({ ...searchForm, ...item }); - console.log("查询条件变化了",searchForm); + console.log("查询条件变化了", searchForm); }; // 当点击查询按钮的时候触发 @@ -165,7 +172,7 @@ const ColumnArticle: FC = props => { const handleCloseDetailDrawer = () => { setIsDetailDrawerShow(false); }; - + // 删除 const handleDel = (id: number) => { Modal.warning({ @@ -191,10 +198,10 @@ const ColumnArticle: FC = props => { const values = await formRef.validateFields(); const newValues = { ...values, - columnId: columnId, + columnId: columnId }; console.log("提交的值:", newValues); - + const { status: successStatus } = (await updateColumnArticleApi(newValues)) || {}; const { code, msg } = successStatus || {}; if (code === 0) { @@ -212,11 +219,11 @@ const ColumnArticle: FC = props => { const getSortList = async () => { const newValues = { ...searchForm, - pageNumber: current, - pageSize, + pageNumber: current, + pageSize }; console.log("查询教程列表之前的所有值:", newValues); - + const { status, result } = await getColumnArticleListApi(newValues); const { code } = status || {}; // @ts-ignore @@ -232,7 +239,7 @@ const ColumnArticle: FC = props => { // 教程下拉框,可根据教程查询 async function fetchColumnList(key: string): Promise { - console.log('根据教程名查询', key); + console.log("根据教程名查询", key); const { status, result } = await getColumnByNameListApi(key); const { code } = status || {}; //@ts-ignore @@ -241,13 +248,12 @@ const ColumnArticle: FC = props => { const newList = items.map((item: MapItem) => ({ key: item?.columnId, // label 这里我想把教程封面也加上 - label:
- - {item?.column} -
, + label: ( +
+ + {item?.column} +
+ ), value: item?.column })); console.log("教程列表", newList); @@ -255,7 +261,7 @@ const ColumnArticle: FC = props => { } // 没查到数据时,返回空数组 return []; - }; + } // 表头设置 const columns: ColumnsType = [ @@ -265,10 +271,7 @@ const ColumnArticle: FC = props => { key: "column", render(value, item) { return ( - + {value} ); @@ -280,10 +283,7 @@ const ColumnArticle: FC = props => { key: "shortTitle", render(value, item) { return ( - + {value} ); @@ -315,11 +315,7 @@ const ColumnArticle: FC = props => { > 详情 - @@ -330,16 +326,8 @@ const ColumnArticle: FC = props => { // 编辑表单 const reviseDrawerContent = ( - - + + {/*用下拉框做一个教程的选择 */} = props => { optionLabelProp="value" // 是否在输入框聚焦时自动调用搜索方法 showSearch={true} - onChange={ - (value, option) => { - console.log("添加教程文章时教程搜索的值改变", value, option) - if(option) + onChange={(value, option) => { + console.log("添加教程文章时教程搜索的值改变", value, option); + if (option) //@ts-ignore - handleChange({ columnId: option.key, columnName: option.value}) - else - handleChange({ columnId: -1 }) - } - } + handleChange({ columnId: option.key, columnName: option.value }); + else handleChange({ columnId: -1 }); + }} fetchOptions={fetchColumnList} /> - - + + /> - + = props => { onChange={e => handleChange({ shortTitle: e.target.value })} /> - ); @@ -399,20 +375,16 @@ const ColumnArticle: FC = props => { + /> {/* 表格 */}
{/* 抽屉 */} - + {detailInfo.map(({ label, title }) => ( @@ -422,27 +394,25 @@ const ColumnArticle: FC = props => { {/* 把弹窗修改为抽屉 */} - - - - - } - onClose={handleCloseDrawer} - open={isOpenDrawerShow}> + + + + + } + onClose={handleCloseDrawer} + open={isOpenDrawerShow} + > {reviseDrawerContent} ); }; -const mapStateToProps = (state: any) => state.disc.disc; -const mapDispatchToProps = {}; -export default connect(mapStateToProps, mapDispatchToProps)(ColumnArticle); - +export default ColumnArticle; diff --git a/src/views/column/setting/articlesort/index.tsx b/src/views/column/setting/articlesort/index.tsx index 49d94af..d8144d5 100644 --- a/src/views/column/setting/articlesort/index.tsx +++ b/src/views/column/setting/articlesort/index.tsx @@ -2,24 +2,27 @@ /* eslint-disable prettier/prettier */ import { FC, useCallback, useEffect, useState } from "react"; import React from "react"; -import { connect } from "react-redux"; import { useLocation, useNavigate } from "react-router-dom"; import { DeleteOutlined, EyeOutlined, SwapOutlined } from "@ant-design/icons"; -import type { DragEndEvent } from '@dnd-kit/core'; -import { DndContext, PointerSensor, useSensor, useSensors } from '@dnd-kit/core'; -import { restrictToVerticalAxis } from '@dnd-kit/modifiers'; -import { - SortableContext, - useSortable, - verticalListSortingStrategy, -} from '@dnd-kit/sortable'; -import { CSS } from '@dnd-kit/utilities'; +import type { DragEndEvent } from "@dnd-kit/core"; +import { DndContext, PointerSensor, useSensor, useSensors } from "@dnd-kit/core"; +import { restrictToVerticalAxis } from "@dnd-kit/modifiers"; +import { SortableContext, useSortable, verticalListSortingStrategy } from "@dnd-kit/sortable"; +import { CSS } from "@dnd-kit/utilities"; import { Button, Descriptions, Drawer, Form, Input, InputNumber, message, Modal, Space, Table, Tooltip } from "antd"; import type { ColumnsType } from "antd/es/table"; -import { delColumnArticleApi, getColumnArticleListApi, sortColumnArticleApi, sortColumnArticleByIDApi,updateColumnArticleApi } from "@/api/modules/column"; +import { + delColumnArticleApi, + getColumnArticleListApi, + sortColumnArticleApi, + sortColumnArticleByIDApi, + updateColumnArticleApi +} from "@/api/modules/column"; import { ContentInterWrap, ContentWrap } from "@/components/common-wrap"; import { initPagination, IPagination, UpdateEnum } from "@/enums/common"; +import { useAppSelector } from "@/hooks/useRTK"; +import type { RootState } from "@/rtk"; import { MapItem } from "@/typings/common"; import { baseDomain } from "@/utils/util"; import TableSelect from "@/views/column/article/components/tableselect/TableSelect"; @@ -82,10 +85,11 @@ const defaultInitForm: IFormType = { // 查询表单默认值 const defaultSearchForm = { articleTitle: "", - columnId: -1, + columnId: -1 }; -const ColumnArticle: FC = props => { +const ColumnArticle: FC = () => { + const disc = useAppSelector((state: RootState) => state.disc.disc); const [formRef] = Form.useForm(); // 调整文章书序的表单 @@ -121,27 +125,27 @@ const ColumnArticle: FC = props => { const location = useLocation(); const navigate = useNavigate(); - const { columnId: columnIdParam } = location.state || {}; + const { columnId: columnIdParam } = location.state || {}; // 拖拽相关 1 interface RowProps extends React.HTMLAttributes { - 'data-row-key': string; + "data-row-key": string; } // 拖拽相关 2 const Row = (props: RowProps) => { const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({ - id: props['data-row-key'], + id: props["data-row-key"] }); - + const style: React.CSSProperties = { ...props.style, transform: CSS.Transform.toString(transform && { ...transform, scaleY: 1 }), transition, - cursor: 'move', - ...(isDragging ? { position: 'relative', zIndex: 9999 } : {}), + cursor: "move", + ...(isDragging ? { position: "relative", zIndex: 9999 } : {}) }; - + return ; }; @@ -169,13 +173,13 @@ const ColumnArticle: FC = props => { // 拖拽相关 3 const sensors = useSensors( - useSensor(PointerSensor, { - activationConstraint: { - // https://docs.dndkit.com/api-documentation/sensors/pointer#activation-constraints - distance: 1, - }, - }), - ); + useSensor(PointerSensor, { + activationConstraint: { + // https://docs.dndkit.com/api-documentation/sensors/pointer#activation-constraints + distance: 1 + } + }) + ); const onSure = useCallback(() => { setQuery(prev => prev + 1); @@ -196,7 +200,7 @@ const ColumnArticle: FC = props => { const handleSearchChange = (item: MapItem) => { // 当 status 的值为 -1 时,重新显示 setSearchForm({ ...searchForm, ...item }); - console.log("查询条件变化了",searchForm); + console.log("查询条件变化了", searchForm); }; // 当点击查询按钮的时候触发 @@ -214,8 +218,8 @@ const ColumnArticle: FC = props => { }; const goBack = () => { - navigate(-1); // 返回上一个页面 - }; + navigate(-1); // 返回上一个页面 + }; // 关闭抽屉时触发 const handleCloseDrawer = () => { @@ -228,7 +232,7 @@ const ColumnArticle: FC = props => { // 关闭详情抽屉 setIsDetailDrawerShow(false); }; - + // 删除 const handleDel = (id: number) => { Modal.warning({ @@ -254,10 +258,10 @@ const ColumnArticle: FC = props => { const values = await formRef.validateFields(); const newValues = { ...values, - columnId: columnIdParam, + columnId: columnIdParam }; console.log("提交的值:", newValues); - + const { status: successStatus } = (await updateColumnArticleApi(newValues)) || {}; const { code, msg } = successStatus || {}; if (code === 0) { @@ -277,10 +281,10 @@ const ColumnArticle: FC = props => { const values = await articleSortFormRef.validateFields(); const newValues = { ...values, - id: articleSortForm.id, + id: articleSortForm.id }; console.log("提交的值:", newValues); - + const { status: successStatus } = (await sortColumnArticleByIDApi(newValues)) || {}; const { code, msg } = successStatus || {}; if (code === 0) { @@ -293,7 +297,7 @@ const ColumnArticle: FC = props => { const onDragEnd = async ({ active, over }: DragEndEvent) => { console.log("active over", active, over); - if (over != null && active.id !== over.id) { + if (over != null && active.id !== over.id) { // 此时,我需要把两个 ID 发送到服务器端等待更新后再在前端调整顺序 const { status: successStatus } = (await sortColumnArticleApi(Number(active.id), Number(over.id))) || {}; const { code, msg } = successStatus || {}; @@ -303,20 +307,20 @@ const ColumnArticle: FC = props => { } else { message.error(msg); } - } - }; + } + }; // 数据请求 useEffect(() => { const getSortList = async () => { const newValues = { ...searchForm, - pageNumber: current, + pageNumber: current, pageSize, columnId: columnIdParam }; console.log("查询教程列表之前的所有值:", newValues); - + const { status, result } = await getColumnArticleListApi(newValues); const { code } = status || {}; // @ts-ignore @@ -342,7 +346,7 @@ const ColumnArticle: FC = props => { { title: "专栏名称", dataIndex: "column", - key: "column", + key: "column" }, { title: "教程ID", @@ -355,10 +359,7 @@ const ColumnArticle: FC = props => { key: "shortTitle", render(value, item) { return ( - + {value} ); @@ -366,7 +367,7 @@ const ColumnArticle: FC = props => { }, { title: "操作", - width: 150, + width: 150, render: (_, item) => { // 删除的时候用 const { id, sort } = item; @@ -387,7 +388,7 @@ const ColumnArticle: FC = props => { - + ); @@ -413,46 +409,25 @@ const ColumnArticle: FC = props => { // 调整顺序的表单 const articleSortContent = ( -
- - handleArticleSortChange({ sort: value })} - /> - - +
+ + handleArticleSortChange({ sort: value })} /> + + ); // 编辑表单 const reviseDrawerContent = ( -
- - + + + /> - + = props => { onChange={e => handleChange({ shortTitle: e.target.value })} /> - ); @@ -468,38 +442,32 @@ const ColumnArticle: FC = props => {
{/* 搜索 */} - + {/* 表格 */} i.key)} + items={tableData.map(i => i.key)} strategy={verticalListSortingStrategy} > -
+ columns={columns} + dataSource={tableData} + pagination={paginationInfo} + /> {/* 抽屉 */} - + {detailInfo.map(({ label, title }) => ( @@ -509,37 +477,36 @@ const ColumnArticle: FC = props => { {/* 调整顺序的抽屉 */} - + onCancel={handleCloseDrawer} + open={isSortDrawerShow} + > {articleSortContent} {/* 把弹窗修改为抽屉 */} - - - - - } - onClose={handleCloseDrawer} - open={isOpenDrawerShow}> + + + + + } + onClose={handleCloseDrawer} + open={isOpenDrawerShow} + > {reviseDrawerContent} ); }; -const mapStateToProps = (state: any) => state.disc.disc; -const mapDispatchToProps = {}; -export default connect(mapStateToProps, mapDispatchToProps)(ColumnArticle); - +export default ColumnArticle; diff --git a/src/views/column/setting/index.tsx b/src/views/column/setting/index.tsx index 0dff372..fcc10c2 100644 --- a/src/views/column/setting/index.tsx +++ b/src/views/column/setting/index.tsx @@ -1,5 +1,5 @@ +/* eslint-disable simple-import-sort/imports */ import { FC, useCallback, useEffect, useState } from "react"; -import { connect } from "react-redux"; import { DeleteOutlined, EditOutlined, EyeOutlined, SwapOutlined } from "@ant-design/icons"; import { Avatar, @@ -23,6 +23,8 @@ import type { ColumnsType } from "antd/es/table"; import TextArea from "antd/lib/input/TextArea"; import dayjs, { Dayjs } from "dayjs"; +import { useAppSelector } from "@/hooks/useRTK"; +import type { RootState } from "@/rtk"; import { delColumnApi, getColumnListApi, updateColumnApi } from "@/api/modules/column"; import { ContentInterWrap, ContentWrap } from "@/components/common-wrap"; import { initPagination, IPagination, UpdateEnum } from "@/enums/common"; @@ -95,10 +97,12 @@ const defaultSearchForm = { column: "" }; -const Column: FC = props => { +const Column: FC = () => { + const disc = useAppSelector((state: RootState) => state.disc.disc); + const dateFormat = "YYYY/MM/DD"; // @ts-ignore - const { ColumnStatus, ColumnStatusList, ColumnType, ColumnTypeList } = props || {}; + const { ColumnStatus, ColumnStatusList, ColumnType, ColumnTypeList } = disc || {}; // form值,临时保存一些值 const [form, setForm] = useState(defaultInitForm); @@ -615,6 +619,4 @@ const Column: FC = props => { ); }; -const mapStateToProps = (state: any) => state.disc.disc; -const mapDispatchToProps = {}; -export default connect(mapStateToProps, mapDispatchToProps)(Column); +export default Column; diff --git a/src/views/config/index.tsx b/src/views/config/index.tsx index de9f307..f8becd4 100644 --- a/src/views/config/index.tsx +++ b/src/views/config/index.tsx @@ -1,7 +1,7 @@ /* eslint-disable react/jsx-no-undef */ +/* eslint-disable simple-import-sort/imports */ /* eslint-disable prettier/prettier */ import { FC, useCallback, useEffect, useState } from "react"; -import { connect } from "react-redux"; import { DeleteOutlined, EditOutlined, EyeOutlined } from "@ant-design/icons"; import { Button, @@ -22,6 +22,8 @@ import { } from "antd"; import type { ColumnsType } from "antd/es/table"; +import { useAppSelector } from "@/hooks/useRTK"; +import type { RootState } from "@/rtk"; import { delConfigApi, getConfigListApi, operateConfigApi, updateConfigApi } from "@/api/modules/config"; import { ContentInterWrap, ContentWrap } from "@/components/common-wrap"; import { initPagination, IPagination, UpdateEnum } from "@/enums/common"; @@ -84,7 +86,9 @@ const defaultSearchForm: ISearchFormType = { name: "" }; -const Banner: FC = props => { +const Banner: FC = () => { + const disc = useAppSelector((state: RootState) => state.disc.disc); + const [formRef] = Form.useForm(); // form值 const [form, setForm] = useState(defaultInitForm); @@ -110,7 +114,7 @@ const Banner: FC = props => { const { current, pageSize } = pagination; //@ts-ignore - const { ConfigType, ConfigTypeList, ArticleTag, ArticleTagList } = props || {}; + const { ConfigType, ConfigTypeList, ArticleTag, ArticleTagList } = disc || {}; const { configId, type, name, content, bannerUrl, jumpUrl, rank, tags } = form; @@ -502,6 +506,4 @@ const Banner: FC = props => { ); }; -const mapStateToProps = (state: any) => state.disc.disc; -const mapDispatchToProps = {}; -export default connect(mapStateToProps, mapDispatchToProps)(Banner); +export default Banner; diff --git a/src/views/global/index.tsx b/src/views/global/index.tsx index a8354d8..172c227 100644 --- a/src/views/global/index.tsx +++ b/src/views/global/index.tsx @@ -1,10 +1,12 @@ +/* eslint-disable simple-import-sort/imports */ /* eslint-disable prettier/prettier */ import { FC, useCallback, useEffect, useState } from "react"; -import { connect } from "react-redux"; import { DeleteOutlined, EditOutlined } from "@ant-design/icons"; import { Button, Drawer, Form, Input, message, Modal, Space, Switch, Table } from "antd"; import type { ColumnsType } from "antd/es/table"; +import { useAppSelector } from "@/hooks/useRTK"; +import type { RootState } from "@/rtk"; import { delGlobalConfigApi, getGlobalConfigListApi, updateGlobalConfigApi } from "@/api/modules/global"; import { ContentInterWrap, ContentWrap } from "@/components/common-wrap"; import { initPagination, IPagination, UpdateEnum } from "@/enums/common"; @@ -36,7 +38,9 @@ const defaultInitForm: IFormType = { comment: "" }; -const GlobalConfig: FC = props => { +const GlobalConfig: FC = () => { + const disc = useAppSelector((state: RootState) => state.disc.disc); + const [formRef] = Form.useForm(); // form值 const [form, setForm] = useState(defaultInitForm); @@ -172,21 +176,23 @@ const GlobalConfig: FC = props => { dataIndex: "value", width: 400, key: "value", - render: (text) => ( -
+ render: text => ( +
{text}
) - }, + }, { title: "备注", dataIndex: "comment", @@ -283,6 +289,4 @@ const GlobalConfig: FC = props => { ); }; -const mapStateToProps = (state: any) => state.disc.disc; -const mapDispatchToProps = {}; -export default connect(mapStateToProps, mapDispatchToProps)(GlobalConfig); +export default GlobalConfig; diff --git a/src/views/login/components/LoginForm.tsx b/src/views/login/components/LoginForm.tsx index 6fba447..7138189 100644 --- a/src/views/login/components/LoginForm.tsx +++ b/src/views/login/components/LoginForm.tsx @@ -1,6 +1,6 @@ +/* eslint-disable simple-import-sort/imports */ /* eslint-disable prettier/prettier */ import { useState } from "react"; -import { connect, useDispatch } from "react-redux"; import { useNavigate } from "react-router-dom"; import { CloseCircleOutlined, LockOutlined, UserOutlined } from "@ant-design/icons"; import { Button, Form, Input, message } from "antd"; @@ -8,19 +8,16 @@ import { Button, Form, Input, message } from "antd"; import { Login } from "@/api/interface"; import { loginApi } from "@/api/modules/login"; import { HOME_URL } from "@/config/config"; -import { AppDispatch, store } from "@/redux"; -import { getDiscListAction } from "@/redux/modules/disc/action"; -import { setToken, setUserInfo } from "@/redux/modules/global/action"; -import { setTabsList } from "@/redux/modules/tabs/action"; - -const LoginForm = (props: any) => { - const { setToken, setTabsList, setUserInfo } = props; +import { useAppDispatch } from "@/hooks/useRTK"; +import type { AppDispatch } from "@/rtk"; +import { store, setToken, setTabsList, setUserInfo, getDiscListAction } from "@/rtk"; +const LoginForm = () => { const navigate = useNavigate(); const [form] = Form.useForm(); const [loading, setLoading] = useState(false); - const dispatch: AppDispatch = useDispatch(); + const dispatch: AppDispatch = useAppDispatch(); // 登录 const onFinish = async (loginForm: Login.ReqLoginForm) => { @@ -33,13 +30,13 @@ const LoginForm = (props: any) => { message.success("登录成功"); // 用户登录信息 - setUserInfo(result); + dispatch(setUserInfo(result)); // 使用 dispatch 来调用 setToken action,将 token 保存到 Redux 的状态中 - store.dispatch(setToken(result.userId)); + dispatch(setToken(result.userId)); // tab 清空,可以采用 tab 的方式打开页面 - setTabsList([]); + dispatch(setTabsList([])); // 强制 getDiscListAction 获取字典数据先执行 - // 否则 naviage 跳转到首页后,字典数据还没有获取到,会导致页面渲染不出来 + // 否则 navigate 跳转到首页后,字典数据还没有获取到,会导致页面渲染不出来 await dispatch(getDiscListAction()); // 跳转到首页 @@ -90,5 +87,4 @@ const LoginForm = (props: any) => { ); }; -const mapDispatchToProps = { setToken, setTabsList, setUserInfo, getDiscListAction }; -export default connect(null, mapDispatchToProps)(LoginForm); +export default LoginForm; diff --git a/src/views/resume/index.tsx b/src/views/resume/index.tsx index 3c8fcf9..b86e15a 100644 --- a/src/views/resume/index.tsx +++ b/src/views/resume/index.tsx @@ -1,11 +1,13 @@ +/* eslint-disable simple-import-sort/imports */ /* eslint-disable prettier/prettier */ import { FC, useCallback, useEffect, useRef, useState } from "react"; -import { connect } from "react-redux"; import { DeleteOutlined, DownloadOutlined, EditOutlined, InboxOutlined, UploadOutlined } from "@ant-design/icons"; import { Button, Drawer, Form, Input, message, Modal, Space, Table, Tag, Upload } from "antd"; import type { ColumnsType } from "antd/es/table"; import dayjs from "dayjs"; +import { useAppSelector } from "@/hooks/useRTK"; +import type { RootState } from "@/rtk"; import { uploadFileUrl } from "@/api/modules/common"; import { delResumeApi, downResumeApi, getResumeListApi, replayResumeApi } from "@/api/modules/resume"; import { ContentInterWrap, ContentWrap } from "@/components/common-wrap"; @@ -45,7 +47,9 @@ const defaultInitForm: IFormType = { type: 0 }; -const Resume: FC = props => { +const Resume: FC = () => { + const disc = useAppSelector((state: RootState) => state.disc.disc); + const [formRef] = Form.useForm(); // form值 const [form, setForm] = useState(defaultInitForm); @@ -417,6 +421,4 @@ const Resume: FC = props => { ); }; -const mapStateToProps = (state: any) => state.disc.disc; -const mapDispatchToProps = {}; -export default connect(mapStateToProps, mapDispatchToProps)(Resume); +export default Resume; diff --git a/src/views/tag/index.tsx b/src/views/tag/index.tsx index 59be37e..2ff03d0 100644 --- a/src/views/tag/index.tsx +++ b/src/views/tag/index.tsx @@ -1,10 +1,12 @@ +/* eslint-disable simple-import-sort/imports */ /* eslint-disable prettier/prettier */ import { FC, useCallback, useEffect, useState } from "react"; -import { connect } from "react-redux"; import { DeleteOutlined, EditOutlined } from "@ant-design/icons"; import { Button, Drawer, Form, Input, message, Modal, Space, Switch, Table } from "antd"; import type { ColumnsType } from "antd/es/table"; +import { useAppSelector } from "@/hooks/useRTK"; +import type { RootState } from "@/rtk"; import { delTagApi, getTagListApi, operateTagApi, updateTagApi } from "@/api/modules/tag"; import { ContentInterWrap, ContentWrap } from "@/components/common-wrap"; import { initPagination, IPagination, UpdateEnum } from "@/enums/common"; @@ -31,7 +33,9 @@ const defaultInitForm: IFormType = { tag: "" }; -const Tag: FC = props => { +const Tag: FC = () => { + const disc = useAppSelector((state: RootState) => state.disc.disc); + const [formRef] = Form.useForm(); // form值 const [form, setForm] = useState(defaultInitForm); @@ -263,6 +267,4 @@ const Tag: FC = props => { ); }; -const mapStateToProps = (state: any) => state.disc.disc; -const mapDispatchToProps = {}; -export default connect(mapStateToProps, mapDispatchToProps)(Tag); +export default Tag; From 20151a62e2bda6b3ba1feee4298b9d37707f2874 Mon Sep 17 00:00:00 2001 From: jungleford Date: Mon, 5 May 2025 18:21:39 +0800 Subject: [PATCH 4/4] =?UTF-8?q?refactor:=20=E2=99=BB=EF=B8=8F=20=20=20redu?= =?UTF-8?q?x=E9=83=A8=E5=88=86=E9=87=8D=E6=9E=84=EF=BC=9AConnect=20API?= =?UTF-8?q?=E6=9B=BF=E6=8D=A2RTK=E4=B9=8B=E5=90=8E=EF=BC=8C=E7=A7=BB?= =?UTF-8?q?=E9=99=A4=E5=8E=9F=E6=9C=89=E7=9A=84actions/reducers=EF=BC=8C?= =?UTF-8?q?=E4=BB=A5=E5=8F=8Aredux=E5=8C=85=E4=BE=9D=E8=B5=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package-lock.json | 1 - package.json | 1 - src/redux/index.ts | 59 ------------------------- src/redux/modules/auth/action.ts | 13 ------ src/redux/modules/auth/reducer.ts | 27 ----------- src/redux/modules/breadcrumb/action.ts | 7 --- src/redux/modules/breadcrumb/reducer.ts | 23 ---------- src/redux/modules/disc/action.ts | 50 --------------------- src/redux/modules/disc/reducer.ts | 23 ---------- src/redux/modules/global/action.ts | 28 ------------ src/redux/modules/global/reducer.ts | 50 --------------------- src/redux/modules/menu/action.ts | 51 --------------------- src/redux/modules/menu/reducer.ts | 27 ----------- src/redux/modules/tabs/action.ts | 13 ------ src/redux/modules/tabs/reducer.ts | 29 ------------ src/redux/mutation-types.ts | 24 ---------- 16 files changed, 426 deletions(-) delete mode 100644 src/redux/index.ts delete mode 100644 src/redux/modules/auth/action.ts delete mode 100644 src/redux/modules/auth/reducer.ts delete mode 100644 src/redux/modules/breadcrumb/action.ts delete mode 100644 src/redux/modules/breadcrumb/reducer.ts delete mode 100644 src/redux/modules/disc/action.ts delete mode 100644 src/redux/modules/disc/reducer.ts delete mode 100644 src/redux/modules/global/action.ts delete mode 100644 src/redux/modules/global/reducer.ts delete mode 100644 src/redux/modules/menu/action.ts delete mode 100644 src/redux/modules/menu/reducer.ts delete mode 100644 src/redux/modules/tabs/action.ts delete mode 100644 src/redux/modules/tabs/reducer.ts delete mode 100644 src/redux/mutation-types.ts diff --git a/package-lock.json b/package-lock.json index 8f013f7..8416b89 100644 --- a/package-lock.json +++ b/package-lock.json @@ -43,7 +43,6 @@ "react-redux": "^8.0.2", "react-router-dom": "^6.3.0", "react-transition-group": "^4.4.2", - "redux": "^4.2.0", "redux-persist": "^6.0.0", "redux-promise": "^0.6.0", "redux-thunk": "^2.4.2", diff --git a/package.json b/package.json index 672efa3..21b9515 100644 --- a/package.json +++ b/package.json @@ -53,7 +53,6 @@ "react-redux": "^8.0.2", "react-router-dom": "^6.3.0", "react-transition-group": "^4.4.2", - "redux": "^4.2.0", "redux-persist": "^6.0.0", "redux-promise": "^0.6.0", "redux-thunk": "^2.4.2", diff --git a/src/redux/index.ts b/src/redux/index.ts deleted file mode 100644 index 213db65..0000000 --- a/src/redux/index.ts +++ /dev/null @@ -1,59 +0,0 @@ -/* eslint-disable simple-import-sort/imports */ -/* eslint-disable prettier/prettier */ -import { Action, combineReducers, compose, legacy_createStore as createStore, Store } from "redux"; -import { ThunkAction, ThunkDispatch } from 'redux-thunk'; -import { applyMiddleware } from "redux"; -import { persistReducer, persistStore } from "redux-persist"; -import storage from "redux-persist/lib/storage"; -import reduxPromise from "redux-promise"; -import reduxThunk from "redux-thunk"; - -import auth from "./modules/auth/reducer"; -import breadcrumb from "./modules/breadcrumb/reducer"; -import disc from "./modules/disc/reducer"; -import global from "./modules/global/reducer"; -import menu from "./modules/menu/reducer"; -import tabs from "./modules/tabs/reducer"; - -// 创建reducer(拆分reducer) -const reducer = combineReducers({ - global, - menu, - tabs, - auth, - breadcrumb, - disc -}); - -export type RootState = ReturnType; -// 定义自定义的 thunk action 类型 -export type AppThunk = ThunkAction< - ReturnType, - RootState, - unknown, - Action ->; - -// 定义自定义的 dispatch 类型 -export type AppDispatch = ThunkDispatch>; - -// redux 持久化配置 -const persistConfig = { - key: "redux-state", - storage: storage -}; -const persistReducerConfig = persistReducer(persistConfig, reducer); - -// 开启 redux-devtools -const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose; - -// 使用 redux 中间件 -const middleWares = applyMiddleware(reduxThunk, reduxPromise); - -// 创建 store -const store: Store = createStore(persistReducerConfig, composeEnhancers(middleWares)); - -// 创建持久化 store -const persistor = persistStore(store); - -export { persistor, store }; diff --git a/src/redux/modules/auth/action.ts b/src/redux/modules/auth/action.ts deleted file mode 100644 index eec5356..0000000 --- a/src/redux/modules/auth/action.ts +++ /dev/null @@ -1,13 +0,0 @@ -import * as types from "@/redux/mutation-types"; - -// * setAuthButtons -export const setAuthButtons = (authButtons: { [propName: string]: any }) => ({ - type: types.SET_AUTH_BUTTONS, - authButtons -}); - -// * setAuthRouter -export const setAuthRouter = (authRouter: string[]) => ({ - type: types.SET_AUTH_ROUTER, - authRouter -}); diff --git a/src/redux/modules/auth/reducer.ts b/src/redux/modules/auth/reducer.ts deleted file mode 100644 index 458e835..0000000 --- a/src/redux/modules/auth/reducer.ts +++ /dev/null @@ -1,27 +0,0 @@ -import produce from "immer"; -import { AnyAction } from "redux"; - -import { AuthState } from "@/redux/interface"; -import * as types from "@/redux/mutation-types"; - -const authState: AuthState = { - authButtons: {}, - authRouter: [] -}; - -// auth reducer -const auth = (state: AuthState = authState, action: AnyAction) => - produce(state, draftState => { - switch (action.type) { - case types.SET_AUTH_BUTTONS: - draftState.authButtons = action.authButtons; - break; - case types.SET_AUTH_ROUTER: - draftState.authRouter = action.authRouter; - break; - default: - return draftState; - } - }); - -export default auth; diff --git a/src/redux/modules/breadcrumb/action.ts b/src/redux/modules/breadcrumb/action.ts deleted file mode 100644 index 9144477..0000000 --- a/src/redux/modules/breadcrumb/action.ts +++ /dev/null @@ -1,7 +0,0 @@ -import * as types from "@/redux/mutation-types"; - -// * setBreadcrumbList -export const setBreadcrumbList = (breadcrumbList: { [propName: string]: any }) => ({ - type: types.SET_BREADCRUMB_LIST, - breadcrumbList -}); diff --git a/src/redux/modules/breadcrumb/reducer.ts b/src/redux/modules/breadcrumb/reducer.ts deleted file mode 100644 index 9beca7c..0000000 --- a/src/redux/modules/breadcrumb/reducer.ts +++ /dev/null @@ -1,23 +0,0 @@ -import produce from "immer"; -import { AnyAction } from "redux"; - -import { BreadcrumbState } from "@/redux/interface"; -import * as types from "@/redux/mutation-types"; - -const breadcrumbState: BreadcrumbState = { - breadcrumbList: {} -}; - -// breadcrumb reducer -const breadcrumb = (state: BreadcrumbState = breadcrumbState, action: AnyAction) => - produce(state, draftState => { - switch (action.type) { - case types.SET_BREADCRUMB_LIST: - draftState.breadcrumbList = action.breadcrumbList; - break; - default: - return draftState; - } - }); - -export default breadcrumb; diff --git a/src/redux/modules/disc/action.ts b/src/redux/modules/disc/action.ts deleted file mode 100644 index f13b679..0000000 --- a/src/redux/modules/disc/action.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { toPairs } from "lodash"; -import { Dispatch } from "redux"; - -import { getDiscListApi } from "@/api/modules/common"; -import * as types from "@/redux/mutation-types"; - -// * setDiscList -// export const setDiscList = discList => ({ -// type: types.UPDATE_DISC, -// discList -// }); -// * redux-promise《async/await》 -const dictTransform = (dict = {}, keys = ["id", "title"]) => { - console.log("字典 d", dict); - - return toPairs(dict).map(item => { - return { - [keys[0]]: item[0], - [keys[1]]: item[1] - }; - }); -}; - -// * redux-thunk -// 获取字典数据 -// 异步 action creator -export const getDiscListAction = () => { - return async (dispatch: Dispatch) => { - const { result } = (await getDiscListApi()) || {}; - console.log("获取字典,getDiscListAction"); - - let dictionaryMap = {}; - for (const key in result as object) { - if (Object.getOwnPropertyDescriptor(result, key)) { - // @ts-ignore - dictionaryMap[key] = result[key]; - // @ts-ignore - dictionaryMap[`${key}List`] = dictTransform(result[key], ["value", "label"]); - } - } - - console.log("字典", dictionaryMap); - - // 分发 action 更新 state - dispatch({ - type: types.UPDATE_DISC, - discList: dictionaryMap - }); - }; -}; diff --git a/src/redux/modules/disc/reducer.ts b/src/redux/modules/disc/reducer.ts deleted file mode 100644 index f8f2c13..0000000 --- a/src/redux/modules/disc/reducer.ts +++ /dev/null @@ -1,23 +0,0 @@ -import produce from "immer"; -import { AnyAction } from "redux"; - -import { DiscState } from "@/redux/interface"; -import * as types from "@/redux/mutation-types"; - -const discState: DiscState = { - disc: {} -}; - -// disc reducer -const disc = (state: DiscState = discState, action: AnyAction) => - produce(state, draftState => { - switch (action.type) { - case types.UPDATE_DISC: - draftState.disc = action.discList; - break; - default: - return draftState; - } - }); - -export default disc; diff --git a/src/redux/modules/global/action.ts b/src/redux/modules/global/action.ts deleted file mode 100644 index 3fac709..0000000 --- a/src/redux/modules/global/action.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { ThemeConfigProp } from "@/redux/interface/index"; -import * as types from "@/redux/mutation-types"; -import { MapItem } from "@/typings/common"; - -// * setToken -export const setToken = (token: string) => ({ - type: types.SET_TOKEN, - token -}); - -export const setUserInfo = (userInfo: MapItem) => { - return { - type: types.USER_INFO, - userInfo - }; -}; - -// * setAssemblySize -export const setAssemblySize = (assemblySize: string) => ({ - type: types.SET_ASSEMBLY_SIZE, - assemblySize -}); - -// * setThemeConfig -export const setThemeConfig = (themeConfig: ThemeConfigProp) => ({ - type: types.SET_THEME_CONFIG, - themeConfig -}); diff --git a/src/redux/modules/global/reducer.ts b/src/redux/modules/global/reducer.ts deleted file mode 100644 index 2c1340f..0000000 --- a/src/redux/modules/global/reducer.ts +++ /dev/null @@ -1,50 +0,0 @@ -import produce from "immer"; -import { AnyAction } from "redux"; - -import { GlobalState } from "@/redux/interface"; -import * as types from "@/redux/mutation-types"; - -const globalState: GlobalState = { - token: "", - userInfo: {}, - assemblySize: "middle", - themeConfig: { - // 默认 primary 主题颜色 - primary: "#1890ff", - // 深色模式 - isDark: false, - // 色弱模式(weak) || 灰色模式(gray) - weakOrGray: "", - // 面包屑导航 - breadcrumb: true, - // 标签页 - tabs: true, - // 页脚 - footer: true - } -}; - -// global reducer -const global = (state: GlobalState = globalState, action: AnyAction) => { - console.log({ action }); - - return produce(state, draftState => { - switch (action.type) { - case types.SET_TOKEN: - draftState.token = action.token; - break; - case types.USER_INFO: - draftState.userInfo = action.userInfo; - break; - case types.SET_ASSEMBLY_SIZE: - draftState.assemblySize = action.assemblySize; - break; - case types.SET_THEME_CONFIG: - draftState.themeConfig = action.themeConfig; - break; - default: - return draftState; - } - }); -}; -export default global; diff --git a/src/redux/modules/menu/action.ts b/src/redux/modules/menu/action.ts deleted file mode 100644 index 60dc845..0000000 --- a/src/redux/modules/menu/action.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { Dispatch } from "react"; - -import { getMenuList } from "@/api/modules/login"; -import * as types from "@/redux/mutation-types"; - -// * updateCollapse -export const updateCollapse = (isCollapse: boolean) => ({ - type: types.UPDATE_COLLAPSE, - isCollapse -}); - -// * setMenuList -export const setMenuList = (menuList: Menu.MenuOptions[]) => ({ - type: types.SET_MENU_LIST, - menuList -}); - -// ? 下面方法仅为测试使用,不参与任何功能开发 -interface MenuProps { - type: string; - menuList: Menu.MenuOptions[]; -} -// * redux-thunk -export const getMenuListActionThunk = () => { - return async (dispatch: Dispatch) => { - const res = await getMenuList(); - dispatch({ - type: types.SET_MENU_LIST, - menuList: (res.data as Menu.MenuOptions[]) ?? [] - }); - }; -}; - -// * redux-promise《async/await》 -export const getMenuListAction = async (): Promise => { - const res = await getMenuList(); - return { - type: types.SET_MENU_LIST, - menuList: res.data ? res.data : [] - }; -}; - -// * redux-promise《.then/.catch》 -export const getMenuListActionPromise = (): Promise => { - return getMenuList().then(res => { - return { - type: types.SET_MENU_LIST, - menuList: res.data ? res.data : [] - }; - }); -}; diff --git a/src/redux/modules/menu/reducer.ts b/src/redux/modules/menu/reducer.ts deleted file mode 100644 index 15f6231..0000000 --- a/src/redux/modules/menu/reducer.ts +++ /dev/null @@ -1,27 +0,0 @@ -import produce from "immer"; -import { AnyAction } from "redux"; - -import { MenuState } from "@/redux/interface"; -import * as types from "@/redux/mutation-types"; - -const menuState: MenuState = { - isCollapse: false, - menuList: [] -}; - -// menu reducer -const menu = (state: MenuState = menuState, action: AnyAction) => - produce(state, draftState => { - switch (action.type) { - case types.UPDATE_COLLAPSE: - draftState.isCollapse = action.isCollapse; - break; - case types.SET_MENU_LIST: - draftState.menuList = action.menuList; - break; - default: - return draftState; - } - }); - -export default menu; diff --git a/src/redux/modules/tabs/action.ts b/src/redux/modules/tabs/action.ts deleted file mode 100644 index 2c5319b..0000000 --- a/src/redux/modules/tabs/action.ts +++ /dev/null @@ -1,13 +0,0 @@ -import * as types from "@/redux/mutation-types"; - -// * setTabsList -export const setTabsList = (tabsList: Menu.MenuOptions[]) => ({ - type: types.SET_TABS_LIST, - tabsList -}); - -// * setTabsActive -export const setTabsActive = (tabsActive: string) => ({ - type: types.SET_TABS_ACTIVE, - tabsActive -}); diff --git a/src/redux/modules/tabs/reducer.ts b/src/redux/modules/tabs/reducer.ts deleted file mode 100644 index 9da0619..0000000 --- a/src/redux/modules/tabs/reducer.ts +++ /dev/null @@ -1,29 +0,0 @@ -import produce from "immer"; -import { AnyAction } from "redux"; - -import { HOME_URL } from "@/config/config"; -import { TabsState } from "@/redux/interface"; -import * as types from "@/redux/mutation-types"; - -const tabsState: TabsState = { - // tabsActive 其实没啥用,使用 pathname 就可以了😂 - tabsActive: HOME_URL, - tabsList: [{ title: "首页", path: HOME_URL }] -}; - -// tabs reducer -const tabs = (state: TabsState = tabsState, action: AnyAction) => - produce(state, draftState => { - switch (action.type) { - case types.SET_TABS_LIST: - draftState.tabsList = action.tabsList; - break; - case types.SET_TABS_ACTIVE: - draftState.tabsActive = action.tabsActive; - break; - default: - return draftState; - } - }); - -export default tabs; diff --git a/src/redux/mutation-types.ts b/src/redux/mutation-types.ts deleted file mode 100644 index 7d56045..0000000 --- a/src/redux/mutation-types.ts +++ /dev/null @@ -1,24 +0,0 @@ -// 更新 menu 折叠状态 -export const UPDATE_COLLAPSE = "UPDATE_ASIDE_COLLAPSE"; -// 设置 menuList -export const SET_MENU_LIST = "SET_MENU_LIST"; -// 设置 tabsList -export const SET_TABS_LIST = "SET_TABS_LIST"; -// 设置 tabsActive -export const SET_TABS_ACTIVE = "SET_TABS_ACTIVE"; -// 设置 breadcrumb -export const SET_BREADCRUMB_LIST = "SET_BREADCRUMB_LIST"; -// 设置 authButtons -export const SET_AUTH_BUTTONS = "SET_AUTH_BUTTONS"; -// 设置 authRouter -export const SET_AUTH_ROUTER = "SET_AUTH_ROUTER"; -// 设置 token -export const SET_TOKEN = "SET_TOKEN"; -// 设置 userInfo -export const USER_INFO = "USER_INFO"; -// 设置 assemblySize -export const SET_ASSEMBLY_SIZE = "SET_ASSEMBLY_SIZE"; -// 设置 setThemeConfig -export const SET_THEME_CONFIG = "SET_THEME_CONFIG"; -// 设置字典值 -export const UPDATE_DISC = "UPDATE_DISC";