├── .eslintrc.cjs ├── .gitignore ├── .prettierrc ├── LICENSE ├── README.md ├── README.zh_CN.md ├── package.json ├── pnpm-lock.yaml ├── public ├── favicon.ico └── index.html ├── src ├── api │ ├── axios-interceptors.ts │ ├── caravan │ │ ├── Articles.ts │ │ ├── Labels.ts │ │ ├── Login.ts │ │ ├── MenuApi.ts │ │ ├── Placard.ts │ │ ├── Rbac.ts │ │ └── User.ts │ ├── microservice │ │ ├── log.ts │ │ ├── menu.ts │ │ ├── role.ts │ │ └── user.ts │ └── request.ts ├── assets │ ├── github │ │ └── fork.png │ ├── header │ │ └── user-poster.png │ ├── music │ │ └── play.png │ ├── prewview.png │ ├── qrcode-mini.png │ ├── qrcode.png │ ├── react.svg │ ├── sakura.png │ ├── snowflake.png │ └── user │ │ ├── avatarbg.png │ │ └── defaultAvatar.jpg ├── components │ ├── AvatarData │ │ ├── index.module.scss │ │ └── index.tsx │ ├── D3 │ │ └── Collision │ │ │ └── index.tsx │ ├── Echarts │ │ ├── AreaStackChart.tsx │ │ ├── BarChart.tsx │ │ ├── ChinaChart.tsx │ │ ├── DataFlowChart.tsx │ │ ├── HomeSenseChart.tsx │ │ ├── LineChart.tsx │ │ ├── LoginFrequencyChart.tsx │ │ ├── PictorialBarChart.tsx │ │ ├── PieBorderRadius.tsx │ │ ├── PieChart.tsx │ │ ├── Round.tsx │ │ ├── ScatterChart.tsx │ │ ├── ServerStatusChart.tsx │ │ └── index.ts │ ├── ErrorBoundary │ │ └── index.tsx │ ├── FineDay │ │ ├── index.module.scss │ │ └── index.tsx │ ├── Footer │ │ └── Copyright │ │ │ ├── index.module.scss │ │ │ └── index.tsx │ ├── Forms │ │ ├── LoginForm │ │ │ ├── index.module.scss │ │ │ └── index.tsx │ │ └── RegisterForm │ │ │ ├── index.module.scss │ │ │ └── index.tsx │ ├── Header │ │ ├── DefaultLayoutHeader │ │ │ ├── index.module.scss │ │ │ └── index.tsx │ │ ├── HeaderNotice │ │ │ ├── index.module.scss │ │ │ └── index.tsx │ │ └── ThemeColor │ │ │ ├── index.module.scss │ │ │ └── index.tsx │ ├── IconFont │ │ └── index.tsx │ ├── LoginLog │ │ └── index.tsx │ ├── Menu │ │ └── WebMenu │ │ │ ├── hack.ts │ │ │ ├── index.module.scss │ │ │ └── index.tsx │ ├── Modals │ │ ├── MenuEditModal.tsx │ │ ├── MessageModal.tsx │ │ ├── MetaDescModal │ │ │ ├── index.module.scss │ │ │ └── index.tsx │ │ ├── ModalMessage.tsx │ │ ├── OperationLabelModal │ │ │ └── index.tsx │ │ ├── OperationMenuModal │ │ │ └── index.tsx │ │ ├── OperationRoleModal.tsx │ │ ├── PlacardEditModal │ │ │ └── index.tsx │ │ ├── RoleAllocationMenuModal │ │ │ └── index.tsx │ │ ├── RoleAllocationUserModal │ │ │ └── index.tsx │ │ ├── UserEditModal │ │ │ ├── index.module.scss │ │ │ └── index.tsx │ │ └── index.tsx │ ├── OAuth2 │ │ └── GithubAuth.tsx │ ├── Owl │ │ ├── close-eyes.png │ │ ├── face.png │ │ ├── hand-down-left.png │ │ ├── hand-down-right.png │ │ ├── hand-up-left.png │ │ ├── hand-up-right.png │ │ ├── index.module.scss │ │ └── index.tsx │ ├── PageIntroduction │ │ ├── index.module.scss │ │ └── index.tsx │ ├── SocketCard │ │ ├── index.module.scss │ │ └── index.tsx │ ├── StarrySky │ │ ├── index.module.scss │ │ └── index.tsx │ ├── SuspendFallback │ │ ├── index.module.scss │ │ └── index.tsx │ ├── SystemInfo │ │ ├── index.module.scss │ │ └── index.tsx │ ├── TagsView │ │ ├── Contextmenu │ │ │ ├── index.module.scss │ │ │ └── index.tsx │ │ ├── MenuTag │ │ │ ├── index.module.scss │ │ │ └── index.tsx │ │ ├── index.module.scss │ │ └── index.tsx │ ├── Translatex │ │ └── index.tsx │ ├── TypingCard │ │ ├── Typing.tsx │ │ └── index.tsx │ ├── UserCard │ │ ├── index.module.scss │ │ └── index.tsx │ ├── UserInfoCard │ │ ├── index.module.scss │ │ └── index.tsx │ └── Weaterfall │ │ └── index.tsx ├── contexts │ ├── MenuTagContext.tsx │ └── ProjectContext.tsx ├── hooks │ └── useCalculativeWidth.tsx ├── layout │ ├── DefaultLayout │ │ ├── index.module.scss │ │ └── index.tsx │ └── ProjectLayout │ │ └── index.tsx ├── main.tsx ├── pages │ ├── About │ │ ├── index.module.scss │ │ └── index.tsx │ ├── Account │ │ ├── AccountBind │ │ │ ├── AccountBind.tsx │ │ │ └── index.module.scss │ │ ├── AccountPage │ │ │ └── accountpage.tsx │ │ ├── AccountSetting │ │ │ ├── AccountSetting.tsx │ │ │ └── index.module.scss │ │ ├── Base │ │ │ ├── BaseSetting.tsx │ │ │ ├── MetaDesc.tsx │ │ │ ├── basefrom.tsx │ │ │ └── style.scss │ │ ├── Messagecenter │ │ │ ├── MessageCenter.tsx │ │ │ └── style.scss │ │ ├── Notification │ │ │ ├── notification.tsx │ │ │ └── style.scss │ │ ├── SecuritySetting │ │ │ └── SecuritySetting.tsx │ │ ├── index.module.scss │ │ └── index.tsx │ ├── AnimateCard │ │ ├── index.module.scss │ │ └── index.tsx │ ├── BMap │ │ └── index.tsx │ ├── Chart │ │ ├── AreaChart │ │ │ └── index.tsx │ │ └── EchartsPie │ │ │ └── index.tsx │ ├── D3Example │ │ └── index.tsx │ ├── FailPage │ │ ├── FailContainer.tsx │ │ ├── NeedLogin.tsx │ │ ├── NotAuthPage.tsx │ │ └── NotFoundPage.tsx │ ├── LabelsAdmin │ │ ├── index.module.scss │ │ └── index.tsx │ ├── Login │ │ ├── index.module.scss │ │ ├── index.tsx │ │ └── snowflake.tsx │ ├── MenuAdmin │ │ ├── index.module.scss │ │ └── index.tsx │ ├── MusicChart │ │ ├── index.module.scss │ │ ├── index.tsx │ │ └── musicItem.tsx │ ├── PlacardAdmin │ │ ├── index.module.scss │ │ └── index.tsx │ ├── RoleAdmin │ │ ├── index.module.scss │ │ └── index.tsx │ ├── Root │ │ ├── index.module.scss │ │ ├── index.scss │ │ └── index.tsx │ ├── UserAdmin │ │ ├── components │ │ │ └── Table.tsx │ │ ├── index.module.scss │ │ └── index.tsx │ ├── UserProfile │ │ ├── index.module.scss │ │ └── index.tsx │ ├── WebsocketAdmin │ │ ├── index.module.scss │ │ └── index.tsx │ ├── Workbench │ │ ├── index.module.scss │ │ └── index.tsx │ └── Workplace │ │ ├── index.module.scss │ │ └── index.tsx ├── router │ ├── index.tsx │ └── router.tsx ├── styles │ ├── _themeMixin.scss │ ├── _themeVariable.scss │ └── reset.css ├── utils │ ├── Sakura.ts │ ├── core.ts │ ├── file.ts │ ├── menuUtils.ts │ ├── socketDispatch.ts │ └── ws.ts └── vite-env.d.ts ├── tsconfig.json ├── tsconfig.node.json ├── vite.config.ts └── vite.config.ts.timestamp-1734841201844-9b7e5a462699e.mjs /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { browser: true, es2020: true }, 4 | extends: ["eslint:recommended", "plugin:@typescript-eslint/recommended", "plugin:react-hooks/recommended"], 5 | ignorePatterns: ["dist", ".eslintrc.cjs"], 6 | parser: "@typescript-eslint/parser", 7 | plugins: ["react-refresh", "unused-imports"], 8 | rules: { 9 | "react-refresh/only-export-components": ["warn", { allowConstantExport: true }], 10 | "no-unused-vars": "off", 11 | "unused-imports/no-unused-imports": "error", 12 | "unused-imports/no-unused-vars": [ 13 | "error", 14 | { 15 | vars: "all", 16 | varsIgnorePattern: "^_", 17 | args: "after-used", 18 | argsIgnorePattern: "^_" 19 | } 20 | ], 21 | "@typescript-eslint/no-unused-vars": [1], 22 | "@typescript-eslint/no-explicit-any": "off", 23 | "max-len": [0, 200] 24 | } 25 | }; 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | analysis-chart.html 26 | package-lock.json 27 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 170, 3 | "tabWidth": 2, 4 | "useTabs": false, 5 | "semi": true, 6 | "trailingComma": "none", 7 | "bracketSpacing": true, 8 | "bracketSameLine": true, 9 | "arrowParens": "avoid", 10 | "endOfLine": "lf", 11 | "singleAttributePerLine": false 12 | } 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 韦钦可 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | English | [简体中文](./README.zh_CN.md) 2 | 3 |

Nest Admin

4 | 5 | - 预览: https://nest-admin.com 6 | - Github: https://github.com/weiqinke/react-admin-nest 7 | - 欢迎 fork star 8 | 9 | ## 📦 Install 10 | 11 | ```bash 12 | $ git clone https://gitee.com/weiqinke/react-admin-nest.git 13 | $ cd react-admin-nest 14 | $ npm install 15 | $ npm dev 16 | ``` 17 | 18 | ## 🔨 Build 19 | 20 | ```bash 21 | npm install 22 | npm run build 23 | ``` 24 | 25 | - :white_check_mark:**通用登录**:完成 26 | - :white_check_mark:browserHistory 27 | - :white_check_mark:国际化组件 I18N 28 | - :white_check_mark:完善 axios 拦截器 29 | - :white_check_mark:菜单支持 layoutfix 布局 30 | - :white_check_mark:没有登录时,默认跳转到首页 31 | - :white_check_mark:展开菜单仅支持展开两个大类或者仅展开一类菜单 32 | - :white_check_mark:通用切换菜单,右键菜单在点击别的元素时,能自动隐藏:完成 33 | - [x] 持续开发 34 | - [ ] 在 nginx 上适配 browserHistory 35 | -------------------------------------------------------------------------------- /README.zh_CN.md: -------------------------------------------------------------------------------- 1 | [English](./README.md) | 简体中文 2 | 3 |

Nest Admin

4 | 5 | - 预览: https://nest-admin.com 6 | - Github: https://github.com/weiqinke/react-admin-nest 7 | - 欢迎 fork star 8 | 9 | ## 📦 Install 10 | 11 | ```bash 12 | $ git clone https://gitee.com/weiqinke/react-admin-nest.git 13 | $ cd react-admin-nest 14 | $ npm install 15 | $ npm dev 16 | ``` 17 | 18 | ## 🔨 Build 19 | 20 | ```bash 21 | npm install 22 | npm run build 23 | ``` 24 | 25 | - :white_check_mark:**通用登录**:完成 26 | - :white_check_mark:browserHistory 27 | - :white_check_mark:国际化组件 I18N 28 | - :white_check_mark:完善 axios 拦截器 29 | - :white_check_mark:菜单支持 layoutfix 布局 30 | - :white_check_mark:没有登录时,默认跳转到首页 31 | - :white_check_mark:展开菜单仅支持展开两个大类或者仅展开一类菜单 32 | - :white_check_mark:通用切换菜单,右键菜单在点击别的元素时,能自动隐藏:完成 33 | - [x] 持续开发 34 | - [ ] 在 nginx 上适配 browserHistory 35 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nest-admin-web", 3 | "version": "2.0.0", 4 | "description": "", 5 | "author": "qkstart@foxmail.com", 6 | "type": "module", 7 | "scripts": { 8 | "dev": "vite --host", 9 | "build": "vite build", 10 | "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", 11 | "preview": "vite preview", 12 | "lint:fix": "eslint . --fix", 13 | "prettier": "prettier --write .", 14 | "test": "vitest" 15 | }, 16 | "dependencies": { 17 | "@ant-design/icons": "^5.2.6", 18 | "@react-spring/web": "^9.7.3", 19 | "animate.css": "^4.1.1", 20 | "antd": "^5.13.1", 21 | "axios": "^1.6.5", 22 | "bytes": "^3.1.2", 23 | "captcha-mini": "^1.1.0", 24 | "d3": "^7.8.5", 25 | "dayjs": "^1.11.10", 26 | "echarts": "^5.4.3", 27 | "echarts-wordcloud": "^2.1.0", 28 | "html2canvas": "^1.4.1", 29 | "lodash-es": "^4.17.21", 30 | "qs": "^6.11.2", 31 | "react": "^18.2.0", 32 | "react-bmap": "^1.0.131", 33 | "react-card-flip": "^1.2.2", 34 | "react-countup": "^6.5.0", 35 | "react-dom": "^18.2.0", 36 | "react-router-dom": "^6.21.2", 37 | "react-transition-group": "^4.4.5", 38 | "socket.io-client": "^4.7.4" 39 | }, 40 | "devDependencies": { 41 | "@types/bytes": "^3.1.4", 42 | "@types/d3": "^7.4.3", 43 | "@types/node": "^20.11.2", 44 | "@types/qs": "^6.9.11", 45 | "@types/react": "^18.2.43", 46 | "@types/react-dom": "^18.2.17", 47 | "@types/react-transition-group": "^4.4.10", 48 | "@typescript-eslint/eslint-plugin": "^6.19.0", 49 | "@typescript-eslint/parser": "^6.14.0", 50 | "@vitejs/plugin-react": "^4.2.1", 51 | "eslint": "^8.56.0", 52 | "eslint-plugin-react-hooks": "^4.6.0", 53 | "eslint-plugin-react-refresh": "^0.4.5", 54 | "eslint-plugin-unused-imports": "^3.0.0", 55 | "prettier": "3.2.4", 56 | "rollup-plugin-visualizer": "^5.12.0", 57 | "sass": "^1.69.7", 58 | "terser": "^5.37.0", 59 | "typescript": "^5.7.2", 60 | "vite": "^5.4.11", 61 | "vite-plugin-compression": "^0.5.1", 62 | "vite-plugin-html": "^3.2.2" 63 | }, 64 | "license": "MIT" 65 | } 66 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weiqinke/react-admin-nest/36c1cf592208959d9c3a771797b706018bcf06b9/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | <%- title %> 8 | 9 | 10 |
11 | <%- injectScript %> 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/api/axios-interceptors.ts: -------------------------------------------------------------------------------- 1 | import { message } from "antd"; 2 | 3 | // 401拦截 4 | const resp401 = { 5 | /** 6 | * 响应数据之前做点什么 7 | * @param response 响应对象 8 | * @param options 应用配置 包含: {router, i18n, store, message} 9 | * @returns {*} 10 | */ 11 | onFulfilled(response) { 12 | if (response.status === 400) { 13 | message.error({ 14 | content: "无此接口权限", 15 | duration: 1 16 | }); 17 | } 18 | if (response.status === 401) { 19 | message.error({ 20 | content: "无此接口权限", 21 | duration: 1 22 | }); 23 | } 24 | return response; 25 | } 26 | }; 27 | 28 | const resp403 = { 29 | onFulfilled(response) { 30 | if (response.status === 403) { 31 | message.error({ 32 | content: "请求被拒绝", 33 | duration: 1 34 | }); 35 | } 36 | return response; 37 | } 38 | }; 39 | 40 | const resp404 = { 41 | onFulfilled(response) { 42 | if (response.status === 404) { 43 | message.error({ 44 | content: "请求找不到", 45 | duration: 1 46 | }); 47 | } 48 | return response; 49 | } 50 | }; 51 | 52 | const resp415 = { 53 | onFulfilled(response) { 54 | if (response.status === 415) { 55 | message.error({ 56 | content: "请求方式不受支持", 57 | duration: 1 58 | }); 59 | } 60 | return response; 61 | } 62 | }; 63 | 64 | const resp20401 = { 65 | onFulfilled(response) { 66 | if (response.status === 200 && response.data.code === 20401) { 67 | // 为了处理返回数据为空,hash 68 | // $message.error({ 69 | // content: '获取到0条数据', 70 | // duration: 1 71 | // }); 72 | } 73 | if (response.status === 200 && response.data.code === 41701) { 74 | message.error({ 75 | content: "查询参数错误", 76 | duration: 1 77 | }); 78 | } 79 | if (response.data.code !== 200 && response.data.message) { 80 | message.error({ 81 | content: `${response.data.message}`, 82 | duration: 1 83 | }); 84 | } 85 | return response; 86 | } 87 | }; 88 | 89 | const resperror = { 90 | onRejected(error) { 91 | message.error({ 92 | content: "请求错误", 93 | duration: 1 94 | }); 95 | return Promise.reject(error); 96 | } 97 | }; 98 | 99 | const reqCommon = { 100 | /** 101 | * 发送请求之前做些什么 102 | * @param config axios config 103 | * @returns {*} 104 | */ 105 | onFulfilled(config) { 106 | // if (url.indexOf('login') === -1 && xsrfCookieName && !Cookie.get(xsrfCookieName)) { 107 | // message.warning('认证 token 已过期,请重新登录') 108 | // } 109 | const token = window.localStorage.getItem("token") || ""; 110 | if (token) { 111 | //将token放到请求头发送给服务器,将tokenkey放在请求头中 112 | // 后端用请求头 Authorization 来获取token 所以必须放在请求头里面,这种需要前后台协商 113 | config.headers["Authorization"] = "Bearer " + token; 114 | return config; 115 | } 116 | return config; 117 | }, 118 | /** 119 | * 请求出错时做点什么 120 | * @param error 错误对象 121 | * @returns {Promise} 122 | */ 123 | onRejected(error) { 124 | message.error({ 125 | content: error.message, 126 | duration: 1 127 | }); 128 | return Promise.reject(error); 129 | } 130 | }; 131 | 132 | export const requestInterce = [reqCommon]; // 请求拦截 133 | 134 | export const responseInterce = [resp401, resp403, resp404, resp415, resp20401, resperror]; // 响应拦截 135 | -------------------------------------------------------------------------------- /src/api/caravan/Articles.ts: -------------------------------------------------------------------------------- 1 | import { request } from "@/api/request"; 2 | const BASE_URL = REACT_APP_API_URL; 3 | 4 | /** 5 | * 获取当前用户所有文章 6 | * @param payload 7 | * @returns 8 | */ 9 | 10 | export const articles = (payload: any) => { 11 | return request(`${BASE_URL}articles`, "get", payload); 12 | }; 13 | 14 | export const findallarticle = (payload: any) => { 15 | return request(`${BASE_URL}articles/findallarticle`, "get", payload); 16 | }; 17 | /**获取一个文章主体 */ 18 | export const onearticlebody = (payload: any) => { 19 | return request(`${BASE_URL}articles/onearticlebody`, "get", payload); 20 | }; 21 | export const onearticle = (payload: any) => { 22 | return request(`${BASE_URL}articles/onearticle`, "post", payload); 23 | }; 24 | -------------------------------------------------------------------------------- /src/api/caravan/Labels.ts: -------------------------------------------------------------------------------- 1 | import { request } from "@/api/request"; 2 | const BASE_URL = REACT_APP_API_URL; 3 | /** 4 | * 获取当前用户所有标签 5 | * @param payload 6 | * @returns 7 | */ 8 | export const getalllabelsbyuser = (payload: any) => { 9 | return request(`${BASE_URL}labels/getalllabelsbyuser`, "GET", payload); 10 | }; 11 | 12 | //添加 13 | export const addnewlabelbyuser = (payload: any) => { 14 | return request(`${BASE_URL}labels/addnewlabelbyuser`, "POST", payload); 15 | }; 16 | 17 | //更新 18 | export const putlabelbyuser = (payload: any) => { 19 | return request(`${BASE_URL}labels/putlabelbyuser`, "PUT", payload); 20 | }; 21 | 22 | //删除 23 | export const deletelabelbyuser = (payload: any) => { 24 | return request(`${BASE_URL}labels/addnewlabelbyuser`, "DELETE", payload); 25 | }; 26 | -------------------------------------------------------------------------------- /src/api/caravan/Login.ts: -------------------------------------------------------------------------------- 1 | import { request } from "@/api/request"; 2 | 3 | const BASE_URL: string = REACT_APP_API_URL; 4 | 5 | /** 获取菜单列表接口 */ 6 | export const getMenuList = payload => { 7 | return request(`${BASE_URL}user/menu`, "GET", payload); 8 | }; 9 | /** 获取通知列表接口 */ 10 | export const getNoticeList = payload => { 11 | return request(`${BASE_URL}`, "GET", payload); 12 | }; 13 | /** 获取通知列表接口 */ 14 | export const getNoticeList2 = payload => { 15 | return request(`${BASE_URL}`, "GET", payload); 16 | }; 17 | 18 | /** OAuth2 获取code */ 19 | export const userTokenByOAuth2Code = payload => { 20 | return request(`${BASE_URL}user/userTokenByOAuth2Code`, "GET", payload); 21 | }; 22 | 23 | /** 登录接口 */ 24 | export const apiLogin = payload => { 25 | return request(`${BASE_URL}user/login`, "POST", payload); 26 | }; 27 | /** nest登录接口 */ 28 | export const accountlogin = payload => { 29 | return request(`${BASE_URL}user/accountlogin`, "POST", payload); 30 | }; 31 | 32 | /** 获取菜单接口 */ 33 | export const getUserMenus = payload => { 34 | return request(`${BASE_URL}menus/getUserMenus`, "get", payload); 35 | }; 36 | 37 | /** nest获取菜单接口 */ 38 | export const GetMenuByToken = payload => { 39 | return request(`${BASE_URL}user/getmenubytoken`, "GET", payload); 40 | }; 41 | 42 | /** 登出接口 */ 43 | export const apiLogout = payload => { 44 | return request(`${BASE_URL}user/logout`, "POST", payload); 45 | }; 46 | 47 | /** 登录接口 */ 48 | export const GetPlanPaged = payload => { 49 | return request(`${BASE_URL}PlanManage/GetPlanPaged`, "POST", payload); 50 | }; 51 | 52 | /** 注册接口 */ 53 | export const Zhuce = payload => { 54 | return request(`${BASE_URL}user/zhuce`, "POST", payload); 55 | }; 56 | 57 | /** 获取所有人员 */ 58 | export const findalluser = payload => { 59 | return request(`${BASE_URL}user/findalluser`, "POST", payload); 60 | }; 61 | 62 | /** 添加一个人员 */ 63 | export const addOneUser = payload => { 64 | return request(`${BASE_URL}user/addOneUser`, "POST", payload); 65 | }; 66 | 67 | /** 编辑一个人员 */ 68 | export const editOneUser = payload => { 69 | return request(`${BASE_URL}user/editOneUser`, "POST", payload); 70 | }; 71 | 72 | /** 切换一个人员状态 */ 73 | export const changeUserStatus = payload => { 74 | return request(`${BASE_URL}user/changeUserStatus`, "POST", payload); 75 | }; 76 | 77 | /** 78 | * 获取登录日志 79 | * */ 80 | export const getAccountLog = payload => { 81 | return request(`${BASE_URL}user/getAccountLog`, "POST", payload); 82 | }; 83 | /** 84 | * 获取我自己的信息 85 | * */ 86 | export const getmyuserinfo = () => { 87 | return request(`${BASE_URL}user/getmyuserinfo`, "POST", {}); 88 | }; 89 | 90 | /** 91 | * 更新我自己的信息 92 | * */ 93 | export const updateMyinfo = payload => { 94 | return request(`${BASE_URL}user/updateMyinfo`, "POST", payload); 95 | }; 96 | 97 | /** 98 | * 更新我自己的信息 99 | * */ 100 | export const updateUserAvatarUrl = payload => { 101 | return request(`${BASE_URL}user/updateUserAvatarUrl`, "POST", payload, { 102 | processData: false 103 | }); 104 | }; 105 | 106 | /*** 107 | * 获取指定时间段的登录日志 108 | * **/ 109 | export const findAccountLogs = (payload: any) => { 110 | return request(`${BASE_URL}Accountlog/findAccountLogs`, "GET", payload); 111 | }; 112 | -------------------------------------------------------------------------------- /src/api/caravan/MenuApi.ts: -------------------------------------------------------------------------------- 1 | import { request } from "@/api/request"; 2 | 3 | const BASE_URL = REACT_APP_API_URL; 4 | 5 | /** 获取菜单接口 */ 6 | export const getUserMenus = () => { 7 | return request(`${BASE_URL}menus/getUserMenus`, "GET", {}); 8 | }; 9 | 10 | /** 添加菜单接口 */ 11 | export const addMenuItem = (payload: any) => { 12 | return request(`${BASE_URL}menus/addMenuItem`, "POST", payload); 13 | }; 14 | 15 | /** 编辑菜单接口 */ 16 | export const editMenuItem = (payload: any) => { 17 | return request(`${BASE_URL}menus/editMenuItem`, "POST", payload); 18 | }; 19 | 20 | /** 删除菜单接口 */ 21 | export const delMenuItem = (payload: any) => { 22 | return request(`${BASE_URL}menus`, "DELETE", payload); 23 | }; 24 | 25 | /** 删除菜单接口 */ 26 | export const getAllMenus = payload => { 27 | return request(`${BASE_URL}menus/getAllMenus`, "GET", payload); 28 | }; 29 | -------------------------------------------------------------------------------- /src/api/caravan/Placard.ts: -------------------------------------------------------------------------------- 1 | import { request } from "@/api/request"; 2 | 3 | const BASE_URL = REACT_APP_API_URL; 4 | 5 | /** 获取系统通知接口 */ 6 | export const findLastTypePlacard = (payload: any) => { 7 | return request(`${BASE_URL}placard/findLastTypePlacard`, "GET", payload); 8 | }; 9 | 10 | /** 获取系统通知列表接口 */ 11 | export const getPlacardListByType = (payload: any) => { 12 | return request(`${BASE_URL}placard/getPlacardListByType`, "GET", payload); 13 | }; 14 | 15 | /** 获取所有公告和通知 */ 16 | export const getAllPlacard = (payload: any) => { 17 | return request(`${BASE_URL}placard/getAllPlacard`, "GET", payload); 18 | }; 19 | 20 | /** 添加一个公告 */ 21 | export const addPlacard = (payload: any) => { 22 | return request(`${BASE_URL}placard/addPlacard`, "POST", payload); 23 | }; 24 | /** 发布一个公告 */ 25 | export const broadcastPlacard = (payload: any) => { 26 | return request(`${BASE_URL}placard/broadcastPlacard`, "POST", payload); 27 | }; 28 | 29 | /** 已读一个公告 */ 30 | export const readOnePlacard = (payload: any) => { 31 | return request(`${BASE_URL}placard/readOnePlacard`, "PUT", payload); 32 | }; 33 | 34 | /** 删除一个公告 */ 35 | export const deleteOnePlacard = (payload: any) => { 36 | return request(`${BASE_URL}placard`, "DELETE", payload); 37 | }; 38 | -------------------------------------------------------------------------------- /src/api/caravan/Rbac.ts: -------------------------------------------------------------------------------- 1 | import { request } from "@/api/request"; 2 | 3 | const BASE_URL = REACT_APP_API_URL; 4 | 5 | /** nest获取所有角色接口 */ 6 | export const getallrole = (payload: any) => { 7 | return request(`${BASE_URL}role/getallrole`, "GET", payload); 8 | }; 9 | 10 | /** nest添加角色接口 */ 11 | export const addrole = (payload: any) => { 12 | return request(`${BASE_URL}role/addrole`, "POST", payload); 13 | }; 14 | 15 | /** nest更新角色接口 */ 16 | export const updaterole = (payload: any) => { 17 | return request(`${BASE_URL}role/updaterole`, "POST", payload); 18 | }; 19 | 20 | /** nest禁用角色接口 */ 21 | export const deleterole = (payload: any) => { 22 | return request(`${BASE_URL}role/deleterole`, "POST", payload); 23 | }; 24 | 25 | /** nest角色接口 */ 26 | export const getUsersByRoleCode = (payload: any) => { 27 | return request(`${BASE_URL}role/getUsersByRoleCode`, "POST", payload); 28 | }; 29 | 30 | /** nest角色接口 */ 31 | export const getMenusByRoleCode = (payload: any) => { 32 | return request(`${BASE_URL}role/getMenusByRoleCode`, "POST", payload); 33 | }; 34 | 35 | /** nest角色分配人接口 */ 36 | export const giveUser = (payload: any) => { 37 | return request(`${BASE_URL}role/giveUser`, "POST", payload); 38 | }; 39 | 40 | /** nest角色分配人接口 */ 41 | export const giveRoleMenus = (payload: any) => { 42 | return request(`${BASE_URL}role/giveRoleMenus`, "POST", payload); 43 | }; 44 | 45 | /** nest角色分配人接口 */ 46 | export const findAllMenu = (payload: any) => { 47 | return request(`${BASE_URL}role/findAllMenu`, "POST", payload); 48 | }; 49 | 50 | /** nest角色分配人接口 */ 51 | export const roleMenu = (payload: any) => { 52 | return request(`${BASE_URL}role/menu`, "GET", payload); 53 | }; 54 | -------------------------------------------------------------------------------- /src/api/caravan/User.ts: -------------------------------------------------------------------------------- 1 | import { request } from "@/api/request"; 2 | 3 | const BASE_URL = REACT_APP_API_URL; 4 | 5 | /** 获取菜单列表接口 */ 6 | export const getMenuList = (payload: any) => { 7 | return request(`${BASE_URL}user/menu`, "GET", payload); 8 | }; 9 | /** 获取通知列表接口 */ 10 | export const getNoticeList = (payload: any) => { 11 | return request(`${BASE_URL}`, "GET", payload); 12 | }; 13 | /** 获取通知列表接口 */ 14 | export const getNoticeList2 = (payload: any) => { 15 | return request(`${BASE_URL}`, "GET", payload); 16 | }; 17 | 18 | /** 登录接口 */ 19 | export const apiLogin = (payload: any) => { 20 | return request(`${BASE_URL}user/login`, "POST", payload); 21 | }; 22 | /** nest登录接口 */ 23 | export const accountlogin = (payload: any) => { 24 | return request(`${BASE_URL}user/accountlogin`, "POST", payload); 25 | }; 26 | 27 | /** nest注册接口 */ 28 | export const userRegister = (payload: any) => { 29 | return request(`${BASE_URL}user/register`, "POST", payload); 30 | }; 31 | 32 | /** nest用户菜单接口 */ 33 | export const userMenus = (payload: any) => { 34 | return request(`${BASE_URL}user/menus`, "GET", payload); 35 | }; 36 | 37 | /** nest获取菜单接口 */ 38 | export const GetMenuByToken = (payload: any) => { 39 | return request(`${BASE_URL}user/getmenubytoken`, "GET", payload); 40 | }; 41 | 42 | /** 登出接口 */ 43 | export const apiLogout = (payload: any) => { 44 | return request(`${BASE_URL}user/logout`, "POST", payload); 45 | }; 46 | 47 | /** 登录接口 */ 48 | export const GetPlanPaged = (payload: any) => { 49 | return request(`${BASE_URL}PlanManage/GetPlanPaged`, "POST", payload); 50 | }; 51 | 52 | /** 注册接口 */ 53 | export const Zhuce = (payload: any) => { 54 | return request(`${BASE_URL}user/zhuce`, "POST", payload); 55 | }; 56 | 57 | /** 获取所有人员 */ 58 | export const findalluser = (payload: any) => { 59 | return request(`${BASE_URL}user/findalluser`, "POST", payload); 60 | }; 61 | 62 | /** 添加一个人员 */ 63 | export const addOneUser = (payload: any) => { 64 | return request(`${BASE_URL}user/addOneUser`, "POST", payload); 65 | }; 66 | 67 | /** 编辑一个人员 */ 68 | export const editOneUser = (payload: any) => { 69 | return request(`${BASE_URL}user/editOneUser`, "POST", payload); 70 | }; 71 | 72 | /** 切换一个人员状态 */ 73 | export const changeUserStatus = (payload: any) => { 74 | return request(`${BASE_URL}user/changeUserStatus`, "POST", payload); 75 | }; 76 | 77 | /** 获取登录日志 */ 78 | export const getAccountLog = (payload: any) => { 79 | return request(`${BASE_URL}user/getAccountLog`, "POST", payload); 80 | }; 81 | /** 获取我自己的信息 */ 82 | export const getmyuserinfo = () => { 83 | return request(`${BASE_URL}user/getmyuserinfo`, "GET", {}); 84 | }; 85 | 86 | /** 获取一个用户的信息 */ 87 | export const getUserInfoByUid = uid => { 88 | return request(`${BASE_URL}user`, "GET", uid); 89 | }; 90 | 91 | /** 更新我自己的信息 */ 92 | export const updateMyinfo = (payload: any) => { 93 | return request(`${BASE_URL}user/updateMyinfo`, "POST", payload); 94 | }; 95 | 96 | /** 更新我自己的信息 */ 97 | export const updateUserAvatarUrl = (payload: any) => { 98 | return request(`${BASE_URL}user/updateUserAvatarUrl`, "POST", payload, { 99 | processData: false 100 | }); 101 | }; 102 | 103 | /** 获取消息接口 */ 104 | export const getallnotices = () => { 105 | return request(`${BASE_URL}notices/getallnotices`, "get", {}); 106 | }; 107 | /** 发送消息接口 */ 108 | export const sendToOne = (payload: any) => { 109 | return request(`${BASE_URL}notices/sendToOne`, "post", payload); 110 | }; 111 | /** 获取我的消息 */ 112 | export const getUidNotices = () => { 113 | return request(`${BASE_URL}notices/getUidNotices`, "get", {}); 114 | }; 115 | -------------------------------------------------------------------------------- /src/api/microservice/log.ts: -------------------------------------------------------------------------------- 1 | import { request } from "@/api/request"; 2 | 3 | const BASE_URL = REACT_APP_API_URL; 4 | 5 | /** 获取用户登录日志 */ 6 | export const loginlog = (payload: any) => { 7 | return request(`${BASE_URL}logs/loginlog`, "GET", payload); 8 | }; 9 | 10 | /** 获取用户消息 */ 11 | export const userNotice = (payload: any) => { 12 | return request(`${BASE_URL}logs/userNotice`, "GET", payload); 13 | }; 14 | 15 | /** 获取用户未读通知 */ 16 | export const userPlacard = (payload: any) => { 17 | return request(`${BASE_URL}logs/placard/user`, "GET", payload); 18 | }; 19 | 20 | /** 获取所有通知 */ 21 | export const getPlacard = (payload: any) => { 22 | return request(`${BASE_URL}logs/placard`, "GET", payload); 23 | }; 24 | 25 | /** 创建通知 */ 26 | export const createPlacard = (payload: any) => { 27 | return request(`${BASE_URL}logs/placard/create`, "POST", payload); 28 | }; 29 | 30 | /** 更新通知 */ 31 | export const updatePlacard = (payload: any) => { 32 | return request(`${BASE_URL}logs/placard/update`, "POST", payload); 33 | }; 34 | 35 | /** 推送通知 */ 36 | export const pushPlacard = (payload: any) => { 37 | return request(`${BASE_URL}logs/placard/push`, "POST", payload); 38 | }; 39 | 40 | /** 推送通知 */ 41 | export const submitPlacard = (payload: any) => { 42 | return request(`${BASE_URL}logs/placard/submit`, "POST", payload); 43 | }; 44 | 45 | /** 接口调用数量 */ 46 | export const apiCount = (payload: any) => { 47 | return request(`${BASE_URL}logs/apicount`, "GET", payload); 48 | }; 49 | -------------------------------------------------------------------------------- /src/api/microservice/menu.ts: -------------------------------------------------------------------------------- 1 | import { request } from "@/api/request"; 2 | 3 | const BASE_URL = REACT_APP_API_URL; 4 | 5 | /** 获取所有菜单 */ 6 | export const menuFind = (payload: any) => { 7 | return request(`${BASE_URL}menu/find`, "GET", payload); 8 | }; 9 | 10 | /** 添加菜单 */ 11 | export const createMenuItem = (payload: any) => { 12 | return request(`${BASE_URL}menu/create`, "POST", payload); 13 | }; 14 | 15 | /**修改菜单属性 */ 16 | export const updateMenuItem = (payload: any) => { 17 | return request(`${BASE_URL}menu/update`, "POST", payload); 18 | }; 19 | -------------------------------------------------------------------------------- /src/api/microservice/role.ts: -------------------------------------------------------------------------------- 1 | import { request } from "@/api/request"; 2 | 3 | const BASE_URL = REACT_APP_API_URL; 4 | 5 | /** 获取所有角色 */ 6 | export const findRoles = (payload: any) => { 7 | return request(`${BASE_URL}role/findRoles`, "GET", payload); 8 | }; 9 | 10 | /** 创建角色 */ 11 | export const createRole = payload => { 12 | return request(`${BASE_URL}role/create`, "POST", payload); 13 | }; 14 | 15 | /** 更新角色 */ 16 | export const updateRole = payload => { 17 | return request(`${BASE_URL}role/updateRole`, "POST", payload); 18 | }; 19 | 20 | /** 获取角色下的人员 */ 21 | export const findRoleUsers = payload => { 22 | return request(`${BASE_URL}role/findRoleUsers`, "GET", payload); 23 | }; 24 | 25 | /** 更新角色下的人员 */ 26 | export const updateRoleUsers = payload => { 27 | return request(`${BASE_URL}role/user/update`, "POST", payload); 28 | }; 29 | 30 | /** 获取角色下的菜单 */ 31 | export const findRoleMenus = payload => { 32 | return request(`${BASE_URL}role/menu`, "GET", payload); 33 | }; 34 | 35 | /** 更新角色下的菜单 */ 36 | export const updateRoleMenus = payload => { 37 | return request(`${BASE_URL}role/menu/update`, "POST", payload); 38 | }; 39 | -------------------------------------------------------------------------------- /src/api/microservice/user.ts: -------------------------------------------------------------------------------- 1 | import { request } from "@/api/request"; 2 | 3 | const BASE_URL = REACT_APP_API_URL; 4 | 5 | /** 用户登录 */ 6 | export const login = (payload: any) => { 7 | return request(`${BASE_URL}user/login`, "POST", payload); 8 | }; 9 | 10 | /** 用户注册 */ 11 | export const register = (payload: any) => { 12 | return request(`${BASE_URL}user/register`, "POST", payload); 13 | }; 14 | 15 | /** nest用户菜单接口 */ 16 | export const userMenus = (payload: any) => { 17 | return request(`${BASE_URL}user/menus`, "GET", payload); 18 | }; 19 | 20 | /** 创建一个用户 */ 21 | export const createUser = payload => { 22 | return request(`${BASE_URL}user/create`, "POST", payload); 23 | }; 24 | 25 | /** 获取所有用户 */ 26 | export const findUsers = payload => { 27 | return request(`${BASE_URL}user/findall`, "GET", payload); 28 | }; 29 | 30 | /** 更新用户 */ 31 | export const updateUser = (payload: any) => { 32 | return request(`${BASE_URL}user/update`, "POST", payload); 33 | }; 34 | 35 | /** 获取用户信息 */ 36 | export const userinfo = (payload: any) => { 37 | return request(`${BASE_URL}user/userinfo`, "GET", payload); 38 | }; 39 | 40 | /** OAuth2 获取code */ 41 | export const userOAuth2 = payload => { 42 | return request(`${BASE_URL}user/oauth2`, "POST", payload); 43 | }; 44 | -------------------------------------------------------------------------------- /src/api/request.ts: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | import { requestInterce, responseInterce } from "./axios-interceptors"; 3 | // 跨域认证信息 header 名 4 | const xsrfHeaderName = "Authorization"; 5 | const service = axios.create(); 6 | /**接口最大响应时间** */ 7 | service.defaults.timeout = 20000; 8 | // axios.defaults.withCredentials= true 9 | service.defaults.withCredentials = false; 10 | service.defaults.xsrfHeaderName = xsrfHeaderName; 11 | service.defaults.xsrfCookieName = xsrfHeaderName; 12 | // 加载请求拦截器 13 | requestInterce.forEach(item => { 14 | let { onFulfilled, onRejected } = item; 15 | if (!onFulfilled || typeof onFulfilled !== "function") { 16 | onFulfilled = config => config; 17 | } 18 | if (!onRejected || typeof onRejected !== "function") { 19 | onRejected = error => Promise.reject(error); 20 | } 21 | service.interceptors.request.use( 22 | config => onFulfilled(config), 23 | error => onRejected(error) 24 | ); 25 | }); 26 | // 加载响应拦截器 27 | responseInterce.forEach((item: any) => { 28 | let { onFulfilled, onRejected } = item; 29 | if (!onFulfilled || typeof onFulfilled !== "function") { 30 | onFulfilled = response => response; 31 | } 32 | if (!onRejected || typeof onRejected !== "function") { 33 | onRejected = error => Promise.reject(error); 34 | } 35 | service.interceptors.response.use( 36 | response => onFulfilled(response), 37 | error => onRejected(error) 38 | ); 39 | }); 40 | 41 | /** 42 | * axios请求 43 | * @param url 请求地址 44 | * @param method {string} http method 45 | * @param params 请求参数 46 | * @returns {Promise>} 47 | */ 48 | export const request = async (url, method, params = {}, options = {}) => { 49 | switch (method) { 50 | case "GET": 51 | return service.get(url, { params, ...options }); 52 | case "POST": 53 | return service.post(url, params, { ...options }); 54 | case "DELETE": 55 | return service.delete(url, { params }); 56 | case "PUT": 57 | return service.put(url, params, options); 58 | case "PATCH": 59 | return service.patch(url, params, options); 60 | default: 61 | return service.get(url, { params, ...options }); 62 | } 63 | }; 64 | -------------------------------------------------------------------------------- /src/assets/github/fork.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weiqinke/react-admin-nest/36c1cf592208959d9c3a771797b706018bcf06b9/src/assets/github/fork.png -------------------------------------------------------------------------------- /src/assets/header/user-poster.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weiqinke/react-admin-nest/36c1cf592208959d9c3a771797b706018bcf06b9/src/assets/header/user-poster.png -------------------------------------------------------------------------------- /src/assets/music/play.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weiqinke/react-admin-nest/36c1cf592208959d9c3a771797b706018bcf06b9/src/assets/music/play.png -------------------------------------------------------------------------------- /src/assets/prewview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weiqinke/react-admin-nest/36c1cf592208959d9c3a771797b706018bcf06b9/src/assets/prewview.png -------------------------------------------------------------------------------- /src/assets/qrcode-mini.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weiqinke/react-admin-nest/36c1cf592208959d9c3a771797b706018bcf06b9/src/assets/qrcode-mini.png -------------------------------------------------------------------------------- /src/assets/qrcode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weiqinke/react-admin-nest/36c1cf592208959d9c3a771797b706018bcf06b9/src/assets/qrcode.png -------------------------------------------------------------------------------- /src/assets/react.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/sakura.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weiqinke/react-admin-nest/36c1cf592208959d9c3a771797b706018bcf06b9/src/assets/sakura.png -------------------------------------------------------------------------------- /src/assets/snowflake.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weiqinke/react-admin-nest/36c1cf592208959d9c3a771797b706018bcf06b9/src/assets/snowflake.png -------------------------------------------------------------------------------- /src/assets/user/avatarbg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weiqinke/react-admin-nest/36c1cf592208959d9c3a771797b706018bcf06b9/src/assets/user/avatarbg.png -------------------------------------------------------------------------------- /src/assets/user/defaultAvatar.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weiqinke/react-admin-nest/36c1cf592208959d9c3a771797b706018bcf06b9/src/assets/user/defaultAvatar.jpg -------------------------------------------------------------------------------- /src/components/AvatarData/index.module.scss: -------------------------------------------------------------------------------- 1 | .container { 2 | position: absolute; 3 | bottom: -20px; 4 | left: 24px; 5 | z-index: 9; 6 | height: 130px; 7 | user-select: none; 8 | cursor: pointer; 9 | } 10 | 11 | .userAvatar { 12 | float: left; 13 | transition: 0s linear; 14 | transform:rotate(0deg); 15 | &:hover{ 16 | transition: 30s linear; 17 | transform:rotate(3600deg); 18 | } 19 | } 20 | 21 | .info { 22 | float: left; 23 | margin: 42px 0 0 20px; 24 | color: #fff; 25 | } 26 | 27 | .title { 28 | font-size: 22px; 29 | } 30 | .desc { 31 | margin-top: 6px; 32 | } 33 | -------------------------------------------------------------------------------- /src/components/AvatarData/index.tsx: -------------------------------------------------------------------------------- 1 | import { Avatar } from "antd"; 2 | import styles from "./index.module.scss"; 3 | 4 | const AvatarData = ({ info }) => { 5 | return ( 6 |
7 |
8 | 9 |
10 |
11 |
12 | {info?.nick} 13 |
14 |

{info?.signature}

15 |
16 |
17 | ); 18 | }; 19 | 20 | export default AvatarData; 21 | -------------------------------------------------------------------------------- /src/components/D3/Collision/index.tsx: -------------------------------------------------------------------------------- 1 | import * as d3 from "d3"; 2 | import { useEffect, useRef } from "react"; 3 | 4 | const width = 600; 5 | const height = 300; 6 | 7 | const createData = () => { 8 | const k = width / 200; 9 | const r = d3.randomUniform(k, k * 4); 10 | const n = 4; 11 | return Array.from({ length: 200 }, (_, i) => ({ 12 | r: r(), 13 | group: i && (i % n) + 1 14 | })); 15 | }; 16 | const data = createData(); 17 | const nodes = data.map(Object.create); 18 | 19 | const Collision = () => { 20 | const svgRef = useRef(null); 21 | 22 | const pointermoved = event => { 23 | const [x, y] = d3.pointer(event); 24 | nodes[0].fx = x - width / 2; 25 | nodes[0].fy = y - height / 2; 26 | }; 27 | 28 | useEffect(() => { 29 | const color = d3.scaleOrdinal(d3.schemeTableau10); 30 | const context = svgRef.current.getContext("2d"); 31 | d3.forceSimulation(nodes) 32 | .alphaTarget(0.3) // stay hot 33 | .velocityDecay(0.1) // low friction 34 | .force("x", d3.forceX().strength(0.01)) 35 | .force("y", d3.forceY().strength(0.01)) 36 | .force( 37 | "collide", 38 | d3 39 | .forceCollide() 40 | .radius(d => d.r + 1) 41 | .iterations(3) 42 | ) 43 | .force( 44 | "charge", 45 | d3.forceManyBody().strength((d, i) => (i ? 0 : (-width * 2) / 3)) 46 | ) 47 | .on("tick", () => { 48 | context.clearRect(0, 0, width, height); 49 | context.save(); 50 | context.translate(width / 2, height / 2); 51 | for (let i = 1; i < nodes.length; ++i) { 52 | const d = nodes[i]; 53 | context.beginPath(); 54 | context.moveTo(d.x + d.r, d.y); 55 | context.arc(d.x, d.y, d.r, 0, 2 * Math.PI); 56 | context.fillStyle = color(d.group); 57 | context.fill(); 58 | } 59 | context.restore(); 60 | }); 61 | 62 | d3.select(context.canvas) 63 | .on("touchmove", event => event.preventDefault()) 64 | .on("pointermove", pointermoved); 65 | }, []); 66 | 67 | return ; 68 | }; 69 | 70 | export default Collision; 71 | -------------------------------------------------------------------------------- /src/components/Echarts/BarChart.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC, useEffect, useRef } from "react"; 2 | import * as echarts from "echarts"; 3 | const BarChart: FC = () => { 4 | const ref = useRef(); 5 | 6 | /* { name:标题 , radius:[内圆直径,外圆直径] , 7 | startAngle:圆心角 value:数据 , 8 | color1: 颜色, color2: 颜色 } */ 9 | function getItem(data) { 10 | return { 11 | name: "景区资源", 12 | type: "pie", 13 | center: ["50%", "60%"], 14 | radius: data.radius, 15 | startAngle: data.startAngle, 16 | avoidLabelOverlap: false, 17 | label: { 18 | show: true, 19 | color: "#2196b0" 20 | // position: 'center' 21 | }, 22 | // 鼠标移入时文本状态 23 | emphasis: { 24 | label: { 25 | show: true, 26 | fontSize: 40, 27 | fontWeight: "bold" 28 | } 29 | }, 30 | labelLine: { 31 | show: true, 32 | length: 60, 33 | length2: 20 34 | }, 35 | data: [ 36 | { 37 | value: data.value, 38 | name: data.name, 39 | itemStyle: { 40 | // 渐变颜色 41 | color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ 42 | { 43 | offset: 0, 44 | color: data.color1 45 | }, 46 | { 47 | offset: 1, 48 | color: data.color2 49 | } 50 | ]) 51 | } 52 | }, 53 | { 54 | value: 100 - data.value, 55 | name: data.name, //设置name防止legend错位 56 | itemStyle: { 57 | // 颜色设置为none,则该片段不渲染 58 | color: "none" 59 | }, 60 | label: { show: false } 61 | } 62 | ] 63 | }; 64 | } 65 | 66 | useEffect(() => { 67 | const myChart: any = echarts.init(ref.current); 68 | myChart.setOption({ 69 | backgroundColor: "rgba(124,193,173,0.5)", 70 | // 1.鼠标移入提示 71 | tooltip: { 72 | trigger: "item" 73 | }, 74 | // 2.图例组件 75 | legend: { 76 | top: "5%", 77 | left: "center", 78 | // 文本样式 79 | textStyle: { 80 | color: "#1c91f5" 81 | } 82 | }, 83 | // 3.图表内容 84 | series: [ 85 | getItem({ 86 | name: "AAAA景区", 87 | radius: ["30%", "35%"], 88 | startAngle: -10, 89 | value: 70, 90 | color1: "#8915f9", 91 | color2: "#3d13fd" 92 | }), 93 | getItem({ 94 | name: "免费景区", 95 | radius: ["40%", "45%"], 96 | startAngle: 50, 97 | value: 50, 98 | color1: "#f72f48", 99 | color2: "#f44179" 100 | }), 101 | getItem({ 102 | name: "度假村", 103 | radius: ["50%", "55%"], 104 | startAngle: 260, 105 | value: 50, 106 | color1: "#1686f3", 107 | color2: "#32b8fc" 108 | }), 109 | getItem({ 110 | name: "文化古城", 111 | radius: ["60%", "65%"], 112 | startAngle: 150, 113 | value: 60, 114 | color1: "#2648f7", 115 | color2: "#2d8af9" 116 | }) 117 | ] 118 | }); 119 | window.addEventListener("resize", function () { 120 | myChart.resize(); 121 | }); 122 | return () => { 123 | myChart.dispose && myChart.dispose(); 124 | }; 125 | }, []); 126 | 127 | return
; 128 | }; 129 | 130 | export default BarChart; 131 | -------------------------------------------------------------------------------- /src/components/Echarts/ChinaChart.tsx: -------------------------------------------------------------------------------- 1 | import * as echarts from "echarts"; 2 | import React, { useEffect, useRef } from "react"; 3 | import axios from "axios"; 4 | 5 | const ChinaChart = () => { 6 | const ref = useRef(); 7 | 8 | useEffect(() => { 9 | const myChart = echarts.init(ref.current); 10 | axios.get("https://nest-admin.com/china.json").then(res => { 11 | echarts.registerMap("china", res.data); 12 | myChart.setOption({ 13 | backgroundColor: "rgba(124,193,173,0.5)", 14 | // 进行相关配置 15 | tooltip: { 16 | borderColor: "#7cc1ad", //边框颜色 17 | borderWidth: 1 18 | }, // 鼠标移到图里面的浮动提示框 19 | dataRange: { 20 | show: false, 21 | min: 10000000, 22 | max: 200000000, 23 | text: ["High", "Low"], 24 | realtime: true, 25 | calculable: true 26 | // color: ["#52dbdf", "#FF9B52", "#FFD068"], 27 | }, 28 | geo: { 29 | // 这个是重点配置区 30 | map: "china", // 表示中国地图 31 | roam: false, // 是否允许缩放 32 | zoom: 1, 33 | label: { 34 | show: true, // 是否显示对应地名 35 | color: "#000", 36 | fontSize: 11 37 | }, 38 | itemStyle: { 39 | areaColor: null, 40 | shadowOffsetX: 0, 41 | shadowOffsetY: 0, 42 | shadowBlur: 1, 43 | borderWidth: 0, 44 | shadowColor: "rgba(0, 0, 0, 0.5)" 45 | } 46 | }, 47 | series: [ 48 | { 49 | type: "scatter", 50 | coordinateSystem: "geo" // 对应上方配置 51 | }, 52 | { 53 | name: "访问次数", // 浮动框的标题 54 | type: "map", 55 | geoIndex: 0, 56 | data: [ 57 | { name: "北京", value: 21893095 }, 58 | { name: "天津", value: 13866009 }, 59 | { name: "河北", value: 74610235 }, 60 | { name: "山西", value: 34915616 }, 61 | { name: "内蒙古", value: 24049155 }, 62 | { name: "辽宁", value: 42591407 }, 63 | { name: "吉林", value: 24073453 }, 64 | { name: "黑龙江", value: 31850088 }, 65 | { name: "上海", value: 24870895 }, 66 | { name: "江苏", value: 84748016 }, 67 | { name: "浙江", value: 64567588 }, 68 | { name: "安徽", value: 61027171 }, 69 | { name: "福建", value: 41540086 }, 70 | { name: "江西", value: 45188635 }, 71 | { name: "山东", value: 101527453 }, 72 | { name: "河南", value: 99365519 }, 73 | { name: "湖北", value: 57752557 }, 74 | { name: "湖南", value: 66444864 }, 75 | { name: "广东", value: 126012510 }, 76 | { name: "广西", value: 50126804 }, 77 | { name: "海南", value: 10081232 }, 78 | { name: "重庆", value: 32054159 }, 79 | { name: "四川", value: 83674866 }, 80 | { name: "贵州", value: 38562148 }, 81 | { name: "云南", value: 47209277 }, 82 | { name: "西藏", value: 3648100 }, 83 | { name: "陕西", value: 39528999 }, 84 | { name: "甘肃", value: 25019831 }, 85 | { name: "青海", value: 5923957 }, 86 | { name: "宁夏", value: 7202654 }, 87 | { name: "新疆", value: 25852345 }, 88 | { name: "香港", value: 7508112 }, 89 | { name: "澳门", value: 683567 }, 90 | { name: "台湾", value: 23420442 } 91 | ] 92 | } 93 | ] 94 | }); 95 | }); 96 | window.addEventListener("resize", function () { 97 | myChart.resize(); 98 | }); 99 | return () => { 100 | myChart.dispose && myChart.dispose(); 101 | }; 102 | }, []); 103 | 104 | return
; 105 | }; 106 | 107 | export default ChinaChart; 108 | -------------------------------------------------------------------------------- /src/components/Echarts/LoginFrequencyChart.tsx: -------------------------------------------------------------------------------- 1 | import dayjs from "dayjs"; 2 | import * as echarts from "echarts"; 3 | import React, { useEffect, useRef } from "react"; 4 | 5 | const LoginFrequencyChart = ({ dataSource = [] }) => { 6 | const ref = useRef(); 7 | 8 | useEffect(() => { 9 | const payload = {}; 10 | const userData = {}; 11 | const newData = [...dataSource].reverse(); 12 | newData.map(v => { 13 | const time = dayjs(v.created).format("MM-DD"); 14 | if (!payload[time]) payload[time] = {}; 15 | if (!payload[time][v.name]) payload[time][v.name] = 1; 16 | else payload[time][v.name] += 1; 17 | 18 | if (!userData[v.name]) userData[v.name] = {}; 19 | if (!userData[v.name][time]) userData[v.name][time] = 1; 20 | else userData[v.name][time] += 1; 21 | }); 22 | const xAxisData = Object.keys(payload); 23 | const nameData = Object.keys(userData); 24 | const seriesData = []; 25 | nameData.map(name => { 26 | const data = []; 27 | xAxisData.filter(time => data.push(userData[name][time] || 0)); 28 | seriesData.push({ 29 | name, 30 | type: "bar", 31 | stack: "login", 32 | emphasis: { focus: "series" }, 33 | data: data 34 | }); 35 | }); 36 | 37 | const option = { 38 | title: { 39 | text: "最近15天登录趋势图" 40 | }, 41 | tooltip: { 42 | trigger: "axis", 43 | axisPointer: { type: "shadow" } 44 | }, 45 | legend: { 46 | data: nameData 47 | }, 48 | grid: { 49 | left: "3%", 50 | right: "4%", 51 | bottom: "3%", 52 | containLabel: true 53 | }, 54 | xAxis: { 55 | type: "category", 56 | data: xAxisData 57 | }, 58 | yAxis: { type: "value" }, 59 | series: seriesData 60 | }; 61 | 62 | const myChart = echarts.init(ref.current); 63 | myChart.setOption(option); 64 | window.addEventListener("resize", function () { 65 | myChart.resize(); 66 | }); 67 | return () => { 68 | myChart.dispose && myChart.dispose(); 69 | }; 70 | }, [dataSource]); 71 | 72 | return
; 73 | }; 74 | 75 | export default LoginFrequencyChart; 76 | -------------------------------------------------------------------------------- /src/components/Echarts/PieBorderRadius.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC, useEffect, useRef } from "react"; 2 | import * as echarts from "echarts"; 3 | 4 | const PieBorderRadius: FC = () => { 5 | const ref = useRef(); 6 | useEffect(() => { 7 | const myChart: any = echarts.init(ref.current); 8 | myChart.setOption({ 9 | title: { 10 | text: "UserAgent", 11 | left: 12, 12 | top: 12 13 | }, 14 | tooltip: { 15 | trigger: "item" 16 | }, 17 | series: [ 18 | { 19 | name: "Access From", 20 | type: "pie", 21 | radius: ["0%", "70%"], 22 | avoidLabelOverlap: false, 23 | itemStyle: { 24 | borderRadius: 100, 25 | borderColor: "#fff", 26 | borderWidth: 2 27 | }, 28 | data: [ 29 | { value: 200, name: "Edge" }, 30 | { value: 200, name: "Safari" }, 31 | { value: 200, name: "Firefox" }, 32 | { value: 200, name: "Other" }, 33 | { value: 200, name: "Chrome" } 34 | ], 35 | label: { 36 | show: true, 37 | formatter: function (params) { 38 | const name = params.name; 39 | const percent = params.percent; 40 | return name + "\n" + percent; 41 | } 42 | } 43 | } 44 | ] 45 | }); 46 | window.addEventListener("resize", function () { 47 | myChart.resize(); 48 | }); 49 | return () => { 50 | myChart.dispose && myChart.dispose(); 51 | }; 52 | }, []); 53 | 54 | return
; 55 | }; 56 | 57 | export default PieBorderRadius; 58 | -------------------------------------------------------------------------------- /src/components/Echarts/Round.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC, useEffect, useRef } from "react"; 2 | import * as echarts from "echarts"; 3 | 4 | const Round: FC = () => { 5 | const ref = useRef(); 6 | 7 | const bgPatternImg = new Image(); 8 | bgPatternImg.src = "https://dr.cdnux.com/18510675/i/2025/01/04/676423a20a391.webp"; 9 | const chartData = [ 10 | { 11 | value: 520 12 | }, 13 | { 14 | value: 280 15 | }, 16 | { 17 | value: 100 18 | }, 19 | { 20 | value: 100 21 | } 22 | ]; 23 | const colorList = [ 24 | new echarts.graphic.LinearGradient(0, 0, 0, 1, [ 25 | { 26 | offset: 0, 27 | color: "#CA8CCA" 28 | }, 29 | { 30 | offset: 1, 31 | color: "#EFA5BB" 32 | } 33 | ]), 34 | new echarts.graphic.LinearGradient(0, 0, 0, 1, [ 35 | { 36 | offset: 0, 37 | color: "#BFA4E4" 38 | }, 39 | { 40 | offset: 1, 41 | color: "#E29CE2" 42 | } 43 | ]), 44 | new echarts.graphic.LinearGradient(0, 0, 0, 1, [ 45 | { 46 | offset: 0, 47 | color: "#A8AAE5" 48 | }, 49 | { 50 | offset: 1, 51 | color: "#BEA3E3" 52 | } 53 | ]), 54 | new echarts.graphic.LinearGradient(0, 0, 0, 1, [ 55 | { 56 | offset: 0, 57 | color: "#A4D37D" 58 | }, 59 | { 60 | offset: 1, 61 | color: "#E5F2A7" 62 | } 63 | ]) 64 | ]; 65 | const sum = chartData.reduce((per, cur) => per + cur.value, 0); 66 | const gap = (1 * sum) / 100; 67 | const pieData1 = []; 68 | const pieData2 = []; 69 | const gapData = { 70 | name: "", 71 | value: gap, 72 | itemStyle: { 73 | color: "transparent" 74 | } 75 | }; 76 | for (let i = 0; i < chartData.length; i++) { 77 | pieData1.push({ 78 | ...chartData[i], 79 | itemStyle: { 80 | borderRadius: 100, 81 | shadowColor: "#2a2a34", 82 | shadowBlur: 5, 83 | shadowOffsetY: 0, 84 | shadowOffsetX: 0, 85 | borderColor: "#2a2a34", 86 | borderWidth: 2 87 | } 88 | }); 89 | pieData1.push(gapData); 90 | 91 | pieData2.push({ 92 | ...chartData[i], 93 | itemStyle: { 94 | borderRadius: 10, 95 | color: colorList[i], 96 | opacity: 0.1, 97 | shadowColor: "#000", 98 | shadowBlur: 1, 99 | shadowOffsetY: 5, 100 | shadowOffsetX: 0 101 | } 102 | }); 103 | pieData2.push(gapData); 104 | } 105 | 106 | const option = { 107 | backgroundColor: { 108 | image: bgPatternImg, 109 | repeat: "repeat" 110 | }, 111 | 112 | color: colorList, 113 | 114 | series: [ 115 | { 116 | type: "pie", 117 | z: 3, 118 | radius: ["44%", "51%"], 119 | center: ["50%", "50%"], 120 | label: { 121 | show: false 122 | }, 123 | labelLine: { 124 | show: false 125 | }, 126 | data: pieData1 127 | } 128 | ] 129 | }; 130 | 131 | useEffect(() => { 132 | const myChart: any = echarts.init(ref.current); 133 | myChart.setOption(option); 134 | window.addEventListener("resize", function () { 135 | myChart.resize(); 136 | }); 137 | return () => { 138 | myChart.dispose && myChart.dispose(); 139 | }; 140 | }, []); 141 | 142 | return
; 143 | }; 144 | 145 | export default Round; 146 | -------------------------------------------------------------------------------- /src/components/Echarts/index.ts: -------------------------------------------------------------------------------- 1 | // export const AreaStackChart = lazy(() => import(/* webpackChunkName: "AreaStackChart'"*/ "./AreaStackChart")); 2 | 3 | // export const BarChart = lazy(() => import(/* webpackChunkName: "BarChart'"*/ "./BarChart")); 4 | 5 | // export const LineChart = lazy(() => import(/* webpackChunkName: "LineChart'"*/ "./LineChart")); 6 | 7 | // export const PictorialBarChart = lazy(() => import(/* webpackChunkName: "PictorialBarChart'"*/ "./PictorialBarChart")); 8 | 9 | // export const PieChart = lazy(() => import(/* webpackChunkName: "PieChart'"*/ "./PieChart")); 10 | 11 | // export const ScatterChart = lazy(() => import(/* webpackChunkName: "ScatterChart'"*/ "./ScatterChart")); 12 | 13 | // export const LoginFrequencyChart = lazy(() => import(/* webpackChunkName: "ScatterChart'"*/ "./LoginFrequencyChart")); 14 | 15 | import AreaStackChart from "./AreaStackChart"; 16 | import BarChart from "./BarChart"; 17 | import LineChart from "./LineChart"; 18 | import PictorialBarChart from "./PictorialBarChart"; 19 | import PieChart from "./PieChart"; 20 | import ScatterChart from "./ScatterChart"; 21 | import LoginFrequencyChart from "./LoginFrequencyChart"; 22 | import ChinaChart from "./ChinaChart"; 23 | import PieBorderRadius from "./PieBorderRadius"; 24 | import DataFlowChart from "./DataFlowChart"; 25 | import HomeSenseChart from "./HomeSenseChart"; 26 | import ServerStatusChart from "./ServerStatusChart"; 27 | 28 | export { 29 | AreaStackChart, 30 | BarChart, 31 | LineChart, 32 | PictorialBarChart, 33 | PieChart, 34 | ScatterChart, 35 | LoginFrequencyChart, 36 | ChinaChart, 37 | PieBorderRadius, 38 | DataFlowChart, 39 | HomeSenseChart, 40 | ServerStatusChart 41 | }; 42 | -------------------------------------------------------------------------------- /src/components/FineDay/index.tsx: -------------------------------------------------------------------------------- 1 | import styles from "./index.module.scss"; 2 | 3 | const FineDay = () => { 4 | return ( 5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 | 14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 | 28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 | ); 45 | }; 46 | 47 | export default FineDay; 48 | -------------------------------------------------------------------------------- /src/components/Footer/Copyright/index.module.scss: -------------------------------------------------------------------------------- 1 | .copyright { 2 | height: 20px; 3 | padding-right: 12px; 4 | text-align: right; 5 | } 6 | 7 | .link { 8 | color: #1677ff; 9 | &:active { 10 | color: #1677ff; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/components/Footer/Copyright/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import styles from "./index.module.scss"; 3 | import { Link } from "react-router-dom"; 4 | 5 | const Copyright = () => { 6 | return ( 7 |
8 | 9 | 京ICP备17023235号 10 | 11 |
12 | ); 13 | }; 14 | 15 | export default Copyright; 16 | -------------------------------------------------------------------------------- /src/components/Forms/LoginForm/index.module.scss: -------------------------------------------------------------------------------- 1 | .container { 2 | user-select: none; 3 | width: 324px; 4 | min-height: 366px; 5 | padding: 40px; 6 | position: relative; 7 | background: rgba(164, 203, 200, 0.3); 8 | border-radius: 8px; 9 | &:hover { 10 | box-shadow: -4px 4px 16px 1px rgba(0, 0, 0, 0.15); 11 | background: rgba(164, 203, 200, 1); 12 | transition: 13 | box-shadow 1s, 14 | background 1s, 15 | transform 2s; 16 | } 17 | } 18 | 19 | .title { 20 | text-align: center; 21 | padding: 0px; 22 | margin: 0 0 20px 0; 23 | } 24 | 25 | .forgetPassword { 26 | float: right; 27 | padding-right: 12px; 28 | cursor: pointer; 29 | color: #1890ff; 30 | } 31 | 32 | .other { 33 | padding-bottom: 24px; 34 | } 35 | 36 | .submit { 37 | text-align: center; 38 | } 39 | 40 | .captchaLogin { 41 | margin-top: -11px; 42 | } 43 | 44 | .captchaContainer { 45 | position: absolute; 46 | top: 50%; 47 | right: 2px; 48 | width: calc(50% - 10px); 49 | transform: translateY(-50%); 50 | height: 32px; 51 | border-radius: 6px; 52 | } 53 | 54 | .captchaInput { 55 | width: calc(50%); 56 | } 57 | 58 | .ohter { 59 | display: flex; 60 | justify-content: center; 61 | gap: 15px; 62 | // justify-content: center; 63 | 64 | &:first-child { 65 | margin-left: auto; 66 | } 67 | 68 | &:last-child { 69 | margin-right: auto; 70 | } 71 | } 72 | 73 | .type { 74 | display: block; 75 | } 76 | 77 | .changeQR { 78 | cursor: pointer; 79 | position: absolute; 80 | width: 0; 81 | height: 0; 82 | top: 0px; 83 | right: 0px; 84 | border-top-left-radius: 8px; 85 | border-bottom: 30px solid transparent; 86 | border-right: 30px solid transparent; 87 | border-top: 30px solid rgba(7, 67, 36, 0.8); 88 | border-left: 30px solid rgba(7, 67, 36, 0.8); 89 | transform: rotate(90deg); 90 | } 91 | 92 | .code { 93 | float: right; 94 | margin-top: -22px; 95 | margin-right: -10px; 96 | width: 30px; 97 | height: 30px; 98 | background: url("/src/assets/qrcode-mini.png") no-repeat; 99 | background-size: 100%; 100 | } 101 | 102 | .card { 103 | text-align: center; 104 | margin-top: 45px; 105 | } 106 | 107 | .register { 108 | cursor: pointer; 109 | color: #1677ff; 110 | } 111 | -------------------------------------------------------------------------------- /src/components/Forms/RegisterForm/index.module.scss: -------------------------------------------------------------------------------- 1 | .container { 2 | user-select: none; 3 | padding: 40px; 4 | min-width: 324px; 5 | position: relative; 6 | background: rgba(164, 203, 200, 1); 7 | 8 | } 9 | 10 | .title { 11 | text-align: center; 12 | padding: 0px; 13 | margin: 0px 0px 12px 0px; 14 | } 15 | 16 | .forgetPassword { 17 | float: right; 18 | padding-right: 12px; 19 | cursor: pointer; 20 | color: #1890ff; 21 | } 22 | 23 | .other { 24 | padding-bottom: 24px; 25 | } 26 | 27 | .submit { 28 | text-align: center; 29 | } 30 | -------------------------------------------------------------------------------- /src/components/Header/DefaultLayoutHeader/index.module.scss: -------------------------------------------------------------------------------- 1 | @import "@/styles/themeMixin"; 2 | 3 | .layoutHeader { 4 | height: 50px; 5 | line-height: 50px; 6 | overflow: hidden; 7 | @include get-class-from-key("background-color"); 8 | @include get-class-from-key("color", "color-head"); 9 | .user { 10 | float: right; 11 | cursor: pointer; 12 | user-select: none; 13 | @include get-class-from-key("background-color"); 14 | @include get-class-from-key("color", "head-icon-color"); 15 | &:hover { 16 | @include get-class-from-key("background-color", "head-icon-bg"); 17 | @include get-class-from-key("color", "head-icon-bg-hover"); 18 | } 19 | } 20 | 21 | .nameContainer { 22 | padding: 0 12px; 23 | } 24 | 25 | .name { 26 | margin-left: 6px; 27 | } 28 | 29 | .logo { 30 | float: left; 31 | cursor: pointer; 32 | user-select: none; 33 | width: 200px; 34 | text-align: center; 35 | font-size: 30px; 36 | font-weight: bold; 37 | } 38 | 39 | .title { 40 | /*渐变背景*/ 41 | background-image: -webkit-linear-gradient( 42 | left, 43 | #3498db, 44 | #f47920 10%, 45 | #d71345 20%, 46 | #f7acbc 30%, 47 | #ffd400 40%, 48 | #3498db 50%, 49 | #f47920 60%, 50 | #d71345 70%, 51 | #f7acbc 80%, 52 | #ffd400 90%, 53 | #3498db 54 | ); 55 | color: transparent; /*文字填充色为透明*/ 56 | -webkit-text-fill-color: transparent; 57 | -webkit-background-clip: text; 58 | background-clip: text; 59 | background-size: 200% 100%; 60 | /* 动画 */ 61 | animation: masked-animation 4s infinite linear; 62 | } 63 | } 64 | 65 | @keyframes masked-animation { 66 | 0% { 67 | background-position: 0 0; 68 | } 69 | 100% { 70 | background-position: -100% 0; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/components/Header/HeaderNotice/index.module.scss: -------------------------------------------------------------------------------- 1 | @import "@/styles/themeMixin"; 2 | .notice { 3 | float: right; 4 | user-select: none; 5 | cursor: pointer; 6 | margin-right: 20px; 7 | 8 | .icon { 9 | font-size: 22px; 10 | @include get-class-from-key("color", "head-icon-color"); 11 | &:hover { 12 | @include get-class-from-key("background-color", "head-icon-bg"); 13 | @include get-class-from-key("color", "head-icon-bg-hover"); 14 | } 15 | } 16 | } 17 | 18 | .container { 19 | :global { 20 | .ant-tabs-tab { 21 | width: 130px; 22 | .ant-tabs-tab-btn { 23 | margin: 0 auto; 24 | } 25 | } 26 | } 27 | } 28 | 29 | .bottomBar { 30 | width: 100%; 31 | border-top: 1px solid #f0f0f0; 32 | border-radius: 0 0 2px 2px; 33 | transition: all 0.3s; 34 | } 35 | 36 | .dobtn { 37 | width: 49%; 38 | padding: 12px 0; 39 | float: left; 40 | cursor: pointer; 41 | text-align: center; 42 | } 43 | 44 | .doLeftBtn { 45 | @extend .dobtn; 46 | border-right: 1px solid #f0f0f0; 47 | } 48 | 49 | .tab { 50 | padding: 0 12px; 51 | } 52 | -------------------------------------------------------------------------------- /src/components/Header/ThemeColor/index.module.scss: -------------------------------------------------------------------------------- 1 | @import "@/styles/themeMixin"; 2 | 3 | .ThemeColor { 4 | float: right; 5 | cursor: pointer; 6 | user-select: none; 7 | margin-right: 10px; 8 | font-size: 22px; 9 | padding: 0 12px; 10 | @include get-class-from-key("color", "head-icon-color"); 11 | &:hover { 12 | @include get-class-from-key("background-color", "head-icon-bg"); 13 | @include get-class-from-key("color", "head-icon-bg-hover"); 14 | } 15 | } 16 | 17 | .themetooltip { 18 | @include get-class-from-key("color", "head-icon-color"); 19 | &:hover { 20 | @include get-class-from-key("background-color", "head-icon-bg"); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/components/Header/ThemeColor/index.tsx: -------------------------------------------------------------------------------- 1 | import { SunOutlined } from "@ant-design/icons"; 2 | import styles from "./index.module.scss"; 3 | import { useContext, useState } from "react"; 4 | import { Tooltip } from "antd"; 5 | import ProjectContext from "@/contexts/ProjectContext"; 6 | 7 | const ThemeColor = () => { 8 | const { theme, setTheme } = useContext(ProjectContext); 9 | const themeDict = { 10 | light: "dark", 11 | dark: "light" 12 | }; 13 | 14 | function handleChangeTheme() { 15 | const data = themeDict[theme]; 16 | setTheme(data); 17 | } 18 | return ( 19 | 20 | 21 | 22 | 23 | 24 | ); 25 | }; 26 | 27 | export default ThemeColor; 28 | -------------------------------------------------------------------------------- /src/components/IconFont/index.tsx: -------------------------------------------------------------------------------- 1 | import { createFromIconfontCN } from "@ant-design/icons"; 2 | 3 | const IconFont = createFromIconfontCN({ 4 | scriptUrl: [ 5 | "//at.alicdn.com/t/c/font_4414978_4e7y8rpfw8r.js" // icon-javascript, icon-java, icon-shoppingcart (overridden) 6 | ] 7 | }); 8 | 9 | export default IconFont; 10 | -------------------------------------------------------------------------------- /src/components/LoginLog/index.tsx: -------------------------------------------------------------------------------- 1 | import { loginlog } from "@/api/microservice/log"; 2 | import { LoginFrequencyChart } from "@/components/Echarts"; 3 | import { getDateTimeFormat } from "@/utils/core"; 4 | import { Table } from "antd"; 5 | import dayjs from "dayjs"; 6 | import { useEffect, useState } from "react"; 7 | 8 | const LoginLog = () => { 9 | const columns: any = [ 10 | { 11 | title: "昵称", 12 | dataIndex: "name" 13 | }, 14 | { 15 | title: "浏览器版本", 16 | dataIndex: "bowser", 17 | responsive: ["lg", "xl", "xxl"] 18 | }, 19 | { 20 | title: "来源", 21 | dataIndex: "host", 22 | responsive: ["lg", "xl", "xxl"] 23 | }, 24 | { 25 | title: "登录设备", 26 | dataIndex: "os", 27 | responsive: ["lg", "xl", "xxl"] 28 | }, 29 | { 30 | title: "国家", 31 | dataIndex: "country", 32 | responsive: ["md", "lg", "xl", "xxl"] 33 | }, 34 | { 35 | title: "省份", 36 | dataIndex: "province", 37 | responsive: ["lg", "xl", "xxl"] 38 | }, 39 | { 40 | title: "市区", 41 | dataIndex: "city", 42 | responsive: ["lg", "xl", "xxl"] 43 | }, 44 | { 45 | title: "登录时间", 46 | dataIndex: "created", 47 | render: item => { 48 | return {getDateTimeFormat(new Date(item).valueOf())}; 49 | } 50 | } 51 | ]; 52 | 53 | const [dataSource, setDataSource] = useState(); 54 | 55 | const getLog = () => { 56 | loginlog({ 57 | st: dayjs().subtract(15, "days").format("YYYY-MM-DD"), 58 | et: dayjs().add(1, "days").format("YYYY-MM-DD") 59 | }) 60 | .then(r => { 61 | if (r.data.code === 200) setDataSource(r?.data?.data?.reverse() || []); 62 | }) 63 | .catch(() => setDataSource([])); 64 | }; 65 | useEffect(getLog, []); 66 | 67 | return ( 68 |
69 |
74 | 75 | 76 |
82 | 83 |
84 | 85 | ); 86 | }; 87 | 88 | export default LoginLog; 89 | -------------------------------------------------------------------------------- /src/components/Menu/WebMenu/hack.ts: -------------------------------------------------------------------------------- 1 | const menuItems: any = [ 2 | { 3 | alwaysShow: true, 4 | component: "Layout", 5 | hidden: false, 6 | meta: { icon: "system", noCache: true, title: "统计面板" }, 7 | name: "dashboard", 8 | path: "/dashboard" 9 | }, 10 | { 11 | alwaysShow: true, 12 | children: [ 13 | { 14 | component: "system/user/index", 15 | hidden: false, 16 | meta: { icon: "peoples", noCache: true, title: "后台用户管理" }, 17 | name: "User", 18 | path: "user" 19 | } 20 | ], 21 | component: "Layout", 22 | hidden: false, 23 | meta: { icon: "system", noCache: true, title: "系统管理" }, 24 | name: "system", 25 | path: "/system", 26 | redirect: "noredirect" 27 | }, 28 | { 29 | alwaysShow: true, 30 | children: [ 31 | { 32 | component: "weapp/periodical/index", 33 | hidden: false, 34 | meta: { icon: "peoples", noCache: true, title: "期刊管理" }, 35 | name: "Periodical", 36 | path: "periodical" 37 | }, 38 | { 39 | component: "weapp/weappuser/index", 40 | hidden: false, 41 | meta: { icon: "peoples", noCache: true, title: "用户管理" }, 42 | name: "weappuser", 43 | path: "weappuser" 44 | }, 45 | { 46 | component: "weapp/banner/index", 47 | hidden: false, 48 | meta: { icon: "peoples", noCache: true, title: "轮播图管理" }, 49 | name: "banner", 50 | path: "banner" 51 | }, 52 | { 53 | component: "weapp/article/index", 54 | hidden: false, 55 | meta: { icon: "peoples", noCache: true, title: "文章管理" }, 56 | name: "article", 57 | path: "article" 58 | }, 59 | { 60 | component: "weapp/redeemcode/index", 61 | hidden: false, 62 | meta: { icon: "peoples", noCache: true, title: "兑换码管理" }, 63 | name: "redeemcode", 64 | path: "redeemcode" 65 | }, 66 | { 67 | component: "weapp/articleinfo/index", 68 | hidden: true, 69 | meta: { icon: "peoples", noCache: true, title: "文章详情" }, 70 | name: "articleinfo", 71 | path: "articleinfo" 72 | } 73 | ], 74 | component: "Layout", 75 | hidden: false, 76 | meta: { icon: "monitor", noCache: true, title: "小程序内容管理" }, 77 | name: "weapp", 78 | path: "/weapp", 79 | redirect: "noredirect" 80 | } 81 | ]; 82 | 83 | const creteMenu = (items: any, list: any[]) => { 84 | const parentPath: any = list?.length > 0 ? `${list.join("/")}/` : ""; 85 | for (let i = 0; i < items.length; i++) { 86 | const element = items[i]; 87 | element.label = element?.meta?.title; 88 | element.key = `${parentPath}${element.path}`; 89 | delete element.alwaysShow; 90 | if (element.children) { 91 | // 需要过滤掉不该显示的菜单 92 | const showChildrens = element?.children.filter(v => !v?.hidden); 93 | element.children = creteMenu(showChildrens, [...list, element.path]); 94 | } 95 | } 96 | 97 | return items; 98 | }; 99 | 100 | export const menus = creteMenu(menuItems, []); 101 | -------------------------------------------------------------------------------- /src/components/Menu/WebMenu/index.module.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weiqinke/react-admin-nest/36c1cf592208959d9c3a771797b706018bcf06b9/src/components/Menu/WebMenu/index.module.scss -------------------------------------------------------------------------------- /src/components/Menu/WebMenu/index.tsx: -------------------------------------------------------------------------------- 1 | import IconFont from "@/components/IconFont"; 2 | import MenuTagContext from "@/contexts/MenuTagContext"; 3 | import { formatURL, getLocalStorageMenus, getMenus, getOpenKeysByUrls, setLocalStorage } from "@/utils/menuUtils"; 4 | import { AppstoreOutlined } from "@ant-design/icons"; 5 | import { Menu } from "antd"; 6 | import { toNumber } from "lodash-es"; 7 | import { useContext, useLayoutEffect, useState } from "react"; 8 | import { useLocation, useNavigate } from "react-router-dom"; 9 | 10 | const createMenuIcon = item => { 11 | return item.map(v => { 12 | v.icon = v.icon ? : ; 13 | if (v.children) createMenuIcon(v.children); 14 | return v; 15 | }); 16 | }; 17 | 18 | const WebMenu = () => { 19 | const { addTag } = useContext(MenuTagContext); 20 | const navigate = useNavigate(); 21 | const { pathname } = useLocation(); 22 | const menusList = JSON.parse(getLocalStorageMenus() || ""); 23 | const [menuItems, setMenuItems] = useState([]); 24 | const [selectedKeys, setSelectedKeys] = useState([]); 25 | const [openKeys, setOpenkeys] = useState([]); 26 | 27 | // 点击 menuItem 触发的事件 28 | const onClick = props => { 29 | const { keyPath, key } = props; 30 | // key 是指 嵌套到最底层的那一层菜单 key 31 | // keyPath 是指 由深到浅组成的数组 32 | // keyPath 是当前所有的菜单,我们需要把菜单拼接起来,记住,从后往前 33 | const menuPath: string[] = keyPath.map(v => toNumber(v)).reverse(); 34 | let changeMenus = menusList.concat(); 35 | const urls = []; 36 | for (let index = 0; index < menuPath.length; index++) { 37 | const item = menuPath[index]; 38 | const result = changeMenus.find(v => v.id === parseInt(item)); 39 | urls.push(result?.url || ""); 40 | if (result.children) changeMenus = result.children; 41 | } 42 | const url = formatURL("/" + urls.join("/")); 43 | navigate(url); 44 | setOpenkeys(() => keyPath); // 修改当前组件的state 45 | setSelectedKeys(() => [key]); 46 | addTag(urls, key); 47 | }; 48 | 49 | useLayoutEffect(() => { 50 | // 说明: 51 | // (1) 52 | // 这里要考虑 ( 登陆第一次跳转的加载 ) 和 ( 刷新浏览器的加载 ) 53 | // 不管哪种情况,都获取当前的 pathname,当前pathname是 ( path和menu的keys要一致的原因 ) 54 | // 如果第一次刷新,是不是可以直接从地址栏解析出 id ? 55 | const menusList = JSON.parse(getLocalStorageMenus() || ""); 56 | const result = getMenus(menusList); 57 | const items = createMenuIcon(result); 58 | const data = pathname.split("/").filter(v => v); 59 | const [openKeys, currentKey] = getOpenKeysByUrls(data, items); 60 | setMenuItems(items); 61 | setOpenkeys(openKeys); 62 | setSelectedKeys(currentKey); 63 | }, [pathname]); 64 | 65 | // 展开/关闭的回调 66 | const onOpenChange = (openKeys: any) => { 67 | setOpenkeys(() => openKeys); 68 | setLocalStorage("cacheOpenKeys", openKeys); // 记住展开关闭的组,刷新持久化 69 | }; 70 | 71 | return ( 72 |
73 | 83 |
84 | ); 85 | }; 86 | 87 | export default WebMenu; 88 | -------------------------------------------------------------------------------- /src/components/Modals/MenuEditModal.tsx: -------------------------------------------------------------------------------- 1 | import { createMenuItem, updateMenuItem } from "@/api/microservice/menu"; 2 | import { Col, Form, Input, Modal, Row, Select, message } from "antd"; 3 | import { FC, useEffect } from "react"; 4 | 5 | const layout = { 6 | labelCol: { span: 8 }, 7 | wrapperCol: { span: 16 } 8 | }; 9 | 10 | const { Option } = Select; 11 | const { TextArea } = Input; 12 | 13 | const MenuEditModal: FC = ({ onOk, initMenuItem, parentUid, onCancel }) => { 14 | const [form] = Form.useForm(); 15 | 16 | const handleSubmit = () => {}; 17 | 18 | const creatRoleSubmit = async () => { 19 | const data = await form.validateFields(); 20 | let result: any = null; 21 | const payload = { 22 | parentUid, 23 | ...data, 24 | version: 1, 25 | delete:0, 26 | }; 27 | if (payload.id) { 28 | result = await updateMenuItem(payload); 29 | } else { 30 | result = await createMenuItem(payload); 31 | } 32 | if (result.data.code !== 200) { 33 | message.info("操作失败"); 34 | return; 35 | } 36 | message.info("操作成功"); 37 | onOk(true); 38 | }; 39 | 40 | const onGenderChange = (value: string) => { 41 | form.setFieldsValue({ type: value }); 42 | }; 43 | 44 | const hiddMenuChange = (value: string) => { 45 | form.setFieldsValue({ show: value }); 46 | }; 47 | 48 | useEffect(() => { 49 | form.resetFields(); 50 | form.getFieldsValue(); 51 | }, [form]); 52 | 53 | return ( 54 | 55 |
56 | 57 |
58 | 61 | 62 | 63 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 89 | 90 | 91 | 92 | 93 | 97 | 98 | 99 | 100 | 101 |