├── src
├── vite-env.d.ts
├── App.css
├── assets
│ ├── background.png
│ └── logo.svg
├── pages
│ ├── dashboard
│ │ └── Dashboard.tsx
│ ├── login
│ │ ├── data.d.ts
│ │ ├── service.tsx
│ │ └── Login.tsx
│ ├── settings
│ │ ├── role
│ │ │ ├── data.d.ts
│ │ │ ├── service.tsx
│ │ │ ├── components
│ │ │ │ ├── CreateForm.tsx
│ │ │ │ ├── UpdateForm.tsx
│ │ │ │ └── PermissionForm.tsx
│ │ │ └── Role.tsx
│ │ ├── group
│ │ │ ├── data.d.ts
│ │ │ ├── service.tsx
│ │ │ ├── components
│ │ │ │ ├── UpdateForm.tsx
│ │ │ │ └── CreateForm.tsx
│ │ │ └── Group.tsx
│ │ ├── user
│ │ │ ├── data.d.ts
│ │ │ ├── service.tsx
│ │ │ ├── components
│ │ │ │ ├── UpdateForm.tsx
│ │ │ │ └── CreateForm.tsx
│ │ │ └── User.tsx
│ │ └── menu
│ │ │ ├── data.d.ts
│ │ │ ├── service.tsx
│ │ │ ├── components
│ │ │ ├── UpdateForm.tsx
│ │ │ └── CreateForm.tsx
│ │ │ └── Menu.tsx
│ └── 404.tsx
├── layouts
│ ├── service.tsx
│ ├── components
│ │ └── LogoutModal.tsx
│ └── BaseLayout.tsx
├── main.tsx
├── App.tsx
├── utils
│ ├── auth.tsx
│ ├── icons.tsx
│ └── request.tsx
├── index.css
└── routers
│ └── Routers.tsx
├── .env.development
├── .env.production
├── tsconfig.json
├── .dockerignore
├── vite.config.ts
├── index.html
├── Dockerfile
├── .gitignore
├── nginx.conf
├── tsconfig.node.json
├── tsconfig.app.json
├── package.json
├── LICENSE
├── .github
└── workflows
│ └── docker-publish.yml
├── public
└── icon.svg
└── README.md
/src/vite-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/src/App.css:
--------------------------------------------------------------------------------
1 | .App {
2 | height: 100%;
3 | margin: 0;
4 | padding: 0;
5 | }
--------------------------------------------------------------------------------
/src/assets/background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/basefas/react-antd-admin/HEAD/src/assets/background.png
--------------------------------------------------------------------------------
/.env.development:
--------------------------------------------------------------------------------
1 | # use in dev mode
2 | VITE_API_HOST=http://localhost
3 | VITE_API_PORT=8088
4 | VITE_API_TIMEOUT=5000
5 | VITE_PLATFORM_NAME=React Antd Admin
--------------------------------------------------------------------------------
/.env.production:
--------------------------------------------------------------------------------
1 | # use in build mode
2 | VITE_API_HOST=http://localhost
3 | VITE_API_PORT=8086
4 | VITE_API_TIMEOUT=5000
5 | VITE_PLATFORM_NAME=React Antd Admin
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "files": [],
3 | "references": [
4 | { "path": "./tsconfig.app.json" },
5 | { "path": "./tsconfig.node.json" }
6 | ]
7 | }
8 |
--------------------------------------------------------------------------------
/.dockerignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | build
3 |
4 | .DS_Store
5 | .idea
6 | .vscode
7 |
8 | .git
9 | .gitignore
10 | .dockerignore
11 | .editorconfig
12 | Dockerfile
13 | .local
--------------------------------------------------------------------------------
/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vite'
2 | import react from '@vitejs/plugin-react'
3 |
4 | // https://vite.dev/config/
5 | export default defineConfig({
6 | plugins: [react()],
7 | })
8 |
--------------------------------------------------------------------------------
/src/pages/dashboard/Dashboard.tsx:
--------------------------------------------------------------------------------
1 | import { FC } from 'react';
2 | import { Card } from "antd";
3 |
4 | const Dashboard: FC = () => (
5 |
6 | Dashboard~
7 |
8 | );
9 |
10 | export default Dashboard;
11 |
--------------------------------------------------------------------------------
/src/pages/login/data.d.ts:
--------------------------------------------------------------------------------
1 | export interface UserLogIn {
2 | username: string;
3 | password: string;
4 | }
5 |
6 | export interface UserLogInInfo {
7 | id: number;
8 | username: string;
9 | token: string;
10 | }
11 |
--------------------------------------------------------------------------------
/src/pages/settings/role/data.d.ts:
--------------------------------------------------------------------------------
1 | export interface RoleListItem {
2 | id: number;
3 | name: string;
4 | }
5 |
6 | export interface RoleCreateInfo {
7 | name: string;
8 | }
9 |
10 | export interface RoleUpdateInfo {
11 | name: string;
12 | }
13 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | React Antd Admin
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/src/pages/login/service.tsx:
--------------------------------------------------------------------------------
1 | import { post, ResponseData } from "../../utils/request";
2 | import { UserLogIn, UserLogInInfo } from "./data";
3 |
4 | export async function login(user: UserLogIn): Promise> {
5 | return await post('/api/v1/login', user)
6 | }
7 |
--------------------------------------------------------------------------------
/src/pages/404.tsx:
--------------------------------------------------------------------------------
1 | import { Result } from 'antd';
2 | import { FC } from 'react';
3 |
4 | const NoFoundPage: FC = () => (
5 |
10 | );
11 | export default NoFoundPage;
12 |
--------------------------------------------------------------------------------
/src/layouts/service.tsx:
--------------------------------------------------------------------------------
1 | import { get, ResponseData } from "../utils/request";
2 | import { MenuDataItem } from "@ant-design/pro-layout";
3 |
4 | export async function systemMenuList(): Promise> {
5 | return await get('/api/v1/menus', {type: "system"})
6 | }
7 |
--------------------------------------------------------------------------------
/src/main.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ReactDOM from 'react-dom/client'
3 | import App from './App'
4 | import './index.css'
5 |
6 | ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
7 |
8 |
9 | ,
10 | )
11 |
--------------------------------------------------------------------------------
/src/pages/settings/group/data.d.ts:
--------------------------------------------------------------------------------
1 | export interface GroupListItem {
2 | id: number;
3 | name: string;
4 | role_id?: number;
5 | role_name?: string;
6 | }
7 |
8 | export interface GroupCreateInfo {
9 | name: string;
10 | role_id: number;
11 | }
12 |
13 | export interface GroupUpdateInfo {
14 | name?: string;
15 | role_id?: number;
16 | }
17 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:lts-alpine AS builder
2 | WORKDIR /app
3 | COPY package.json package-lock.json ./
4 | RUN npm install
5 | COPY ./ ./
6 | RUN npm run build
7 |
8 | FROM nginx:stable-alpine
9 | COPY --from=builder /app/dist/ /usr/share/nginx/html
10 | COPY ./nginx.conf /etc/nginx/conf.d/default.conf
11 | EXPOSE 80
12 | ENTRYPOINT ["nginx", "-g", "daemon off;"]
13 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/src/App.tsx:
--------------------------------------------------------------------------------
1 | import { FC } from "react";
2 | import "./App.css";
3 | import { createBrowserRouter, RouterProvider } from "react-router-dom";
4 | import routers from "./routers/Routers";
5 |
6 | const App: FC = () => {
7 | return (
8 |
9 |
12 |
13 | );
14 | };
15 |
16 | export default App;
17 |
--------------------------------------------------------------------------------
/nginx.conf:
--------------------------------------------------------------------------------
1 | server {
2 | listen 80;
3 | # gzip config
4 | gzip on;
5 | gzip_min_length 1k;
6 | gzip_comp_level 6;
7 | gzip_types text/plain text/css test/xml text/javascript application/javascript application/x-javascript application/xml application/json;
8 | gzip_vary on;
9 | gzip_proxied any;
10 | gzip_disable "MSIE [1-6]\.";
11 |
12 | root /usr/share/nginx/html;
13 |
14 | location / {
15 | try_files $uri $uri/ /index.html;
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/pages/settings/user/data.d.ts:
--------------------------------------------------------------------------------
1 | export interface UserListItem {
2 | id: number;
3 | username: string;
4 | email: string;
5 | group_id: number;
6 | group_name: string;
7 | role_id: number;
8 | role_name: string;
9 | status: number;
10 | }
11 |
12 | export interface UserCreateInfo {
13 | username: string;
14 | email: string;
15 | group_id: number;
16 | role_id: number;
17 | }
18 |
19 | export interface UserUpdateInfo {
20 | id?: number;
21 | username?: string;
22 | email?: string;
23 | group_id?: number;
24 | role_id?: number;
25 | status?: number;
26 | }
27 |
--------------------------------------------------------------------------------
/src/utils/auth.tsx:
--------------------------------------------------------------------------------
1 | export function getToken() {
2 | return localStorage.getItem('token') || ''
3 | }
4 |
5 | export function setToken(token: string) {
6 | localStorage.setItem('token', token)
7 | }
8 |
9 | export function loggedIn(): boolean {
10 | return !!localStorage.getItem('token');
11 | }
12 |
13 | export function deleteToken() {
14 | localStorage.removeItem('token')
15 | localStorage.removeItem('username')
16 | }
17 |
18 | export function getCurrentUser() {
19 | return localStorage.getItem('username')
20 | }
21 |
22 | export function setCurrentUser(username: string) {
23 | localStorage.setItem('username', username)
24 | }
25 |
--------------------------------------------------------------------------------
/src/utils/icons.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | ApartmentOutlined,
3 | ContactsOutlined,
4 | ControlOutlined,
5 | HomeOutlined,
6 | LockOutlined,
7 | ProjectOutlined,
8 | SettingOutlined,
9 | TeamOutlined,
10 | UserOutlined,
11 | } from "@ant-design/icons";
12 |
13 |
14 | export const menuIcons: any = {
15 | HomeOutlined: ,
16 | ProjectOutlined: ,
17 | SettingOutlined: ,
18 | LockOutlined: ,
19 | UserOutlined: ,
20 | ControlOutlined: ,
21 | ContactsOutlined: ,
22 | ApartmentOutlined: ,
23 | TeamOutlined: ,
24 | };
25 |
--------------------------------------------------------------------------------
/tsconfig.node.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
4 | "target": "ES2022",
5 | "lib": ["ES2023"],
6 | "module": "ESNext",
7 | "skipLibCheck": true,
8 |
9 | /* Bundler mode */
10 | "moduleResolution": "bundler",
11 | "allowImportingTsExtensions": true,
12 | "isolatedModules": true,
13 | "moduleDetection": "force",
14 | "noEmit": true,
15 |
16 | /* Linting */
17 | "strict": true,
18 | "noUnusedLocals": true,
19 | "noUnusedParameters": true,
20 | "noFallthroughCasesInSwitch": true,
21 | "noUncheckedSideEffectImports": true
22 | },
23 | "include": ["vite.config.ts"]
24 | }
25 |
--------------------------------------------------------------------------------
/src/pages/settings/menu/data.d.ts:
--------------------------------------------------------------------------------
1 | export interface MenuListItem {
2 | id: number;
3 | name: string;
4 | path: string;
5 | type: number;
6 | method: string;
7 | icon: string;
8 | parent_id: number;
9 | order_id: number;
10 | children: MenuListItem[];
11 | funs: MenuListItem[];
12 | }
13 |
14 | export interface MenuCreateInfo {
15 | name: string;
16 | path: string;
17 | type: number;
18 | method: string;
19 | icon: string;
20 | parent_id: number;
21 | order_id: number;
22 | }
23 |
24 | export interface MenuUpdateInfo {
25 | name?: string;
26 | path?: string;
27 | type?: number;
28 | method?: string;
29 | icon?: string;
30 | parent_id?: number;
31 | order_id?: number;
32 | }
33 |
--------------------------------------------------------------------------------
/src/pages/settings/user/service.tsx:
--------------------------------------------------------------------------------
1 | import { del, get, post, put, ResponseData } from "../../../utils/request";
2 | import { UserCreateInfo, UserListItem, UserUpdateInfo } from "./data";
3 |
4 | export async function userList(): Promise> {
5 | return await get('/api/v1/users')
6 | }
7 |
8 | export async function createUser(user: UserCreateInfo): Promise> {
9 | return await post<{}>('/api/v1/users', user)
10 | }
11 |
12 | export async function updateUser(id: number, user: UserUpdateInfo): Promise> {
13 | return await put<{}>('/api/v1/users/' + id, user)
14 | }
15 |
16 | export async function deleteUser(id: number): Promise> {
17 | return await del<{}>('/api/v1/users/' + id)
18 | }
19 |
--------------------------------------------------------------------------------
/tsconfig.app.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
4 | "target": "ES2020",
5 | "useDefineForClassFields": true,
6 | "lib": ["ES2020", "DOM", "DOM.Iterable"],
7 | "module": "ESNext",
8 | "skipLibCheck": true,
9 |
10 | /* Bundler mode */
11 | "moduleResolution": "bundler",
12 | "allowImportingTsExtensions": true,
13 | "isolatedModules": true,
14 | "moduleDetection": "force",
15 | "noEmit": true,
16 | "jsx": "react-jsx",
17 |
18 | /* Linting */
19 | "strict": true,
20 | "noUnusedLocals": true,
21 | "noUnusedParameters": true,
22 | "noFallthroughCasesInSwitch": true,
23 | "noUncheckedSideEffectImports": true
24 | },
25 | "include": ["src"]
26 | }
27 |
--------------------------------------------------------------------------------
/src/pages/settings/group/service.tsx:
--------------------------------------------------------------------------------
1 | import { del, get, post, put, ResponseData } from "../../../utils/request";
2 | import { GroupCreateInfo, GroupListItem, GroupUpdateInfo } from "./data";
3 |
4 | export async function groupList(): Promise> {
5 | return await get('/api/v1/groups')
6 | }
7 |
8 | export async function createGroup(group: GroupCreateInfo): Promise> {
9 | return await post<{}>('/api/v1/groups', group)
10 | }
11 |
12 | export async function updateGroup(id: number, group: GroupUpdateInfo): Promise> {
13 | return await put<{}>('/api/v1/groups/' + id, group)
14 | }
15 |
16 | export async function deleteGroup(id: number): Promise> {
17 | return await del<{}>('/api/v1/groups/' + id)
18 | }
19 |
--------------------------------------------------------------------------------
/src/pages/settings/menu/service.tsx:
--------------------------------------------------------------------------------
1 | import { del, get, post, put, ResponseData } from "../../../utils/request";
2 | import { MenuCreateInfo, MenuListItem, MenuUpdateInfo } from "./data";
3 |
4 | export async function menuList(): Promise> {
5 | return await get('/api/v1/menus?type=tree')
6 | }
7 |
8 | export async function createMenu(menu: MenuCreateInfo): Promise> {
9 | return await post<{}>('/api/v1/menus', menu)
10 | }
11 |
12 | export async function updateMenu(id: number, menu: MenuUpdateInfo): Promise> {
13 | return await put<{}>('/api/v1/menus/' + id, menu)
14 | }
15 |
16 | export async function deleteMenu(id: number): Promise> {
17 | return await del<{}>('/api/v1/menus/' + id + '?type=tree')
18 | }
19 |
20 | export async function menuGet(id: number): Promise> {
21 | return await get('/api/v1/menus/' + id + '?type=tree')
22 | }
23 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-antd-admin",
3 | "private": true,
4 | "version": "0.1.0",
5 | "type": "module",
6 | "scripts": {
7 | "dev": "vite",
8 | "build": "tsc -b && vite build",
9 | "lint": "eslint .",
10 | "preview": "vite preview"
11 | },
12 | "dependencies": {
13 | "@ant-design/pro-layout": "7.21.2",
14 | "@ant-design/use-emotion-css": "1.0.4",
15 | "antd": "5.22.7",
16 | "axios": "1.7.9",
17 | "react": "^18.3.1",
18 | "react-dom": "^18.3.1",
19 | "react-router-dom": "6.28.1"
20 | },
21 | "devDependencies": {
22 | "@eslint/js": "^9.17.0",
23 | "@types/react": "^18.3.18",
24 | "@types/react-dom": "^18.3.5",
25 | "@vitejs/plugin-react": "^4.3.4",
26 | "eslint": "^9.17.0",
27 | "eslint-plugin-react-hooks": "^5.0.0",
28 | "eslint-plugin-react-refresh": "^0.4.16",
29 | "globals": "^15.14.0",
30 | "typescript": "~5.6.2",
31 | "typescript-eslint": "^8.18.2",
32 | "vite": "^6.0.9"
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/index.css:
--------------------------------------------------------------------------------
1 | /*! minireset.css v0.0.6 | MIT License | github.com/jgthms/minireset.css */
2 | html,
3 | body,
4 | p,
5 | ol,
6 | ul,
7 | li,
8 | dl,
9 | dt,
10 | dd,
11 | blockquote,
12 | figure,
13 | fieldset,
14 | legend,
15 | textarea,
16 | pre,
17 | iframe,
18 | hr,
19 | h1,
20 | h2,
21 | h3,
22 | h4,
23 | h5,
24 | h6 {
25 | margin: 0;
26 | padding: 0;
27 | }
28 |
29 | h1,
30 | h2,
31 | h3,
32 | h4,
33 | h5,
34 | h6 {
35 | font-size: 100%;
36 | font-weight: normal;
37 | }
38 |
39 | ul {
40 | list-style: none;
41 | }
42 |
43 | button,
44 | input,
45 | select {
46 | margin: 0;
47 | }
48 |
49 | html {
50 | box-sizing: border-box;
51 | }
52 |
53 | *, *::before, *::after {
54 | box-sizing: inherit;
55 | }
56 |
57 | img,
58 | video {
59 | height: auto;
60 | max-width: 100%;
61 | }
62 |
63 | iframe {
64 | border: 0;
65 | }
66 |
67 | table {
68 | border-collapse: collapse;
69 | border-spacing: 0;
70 | }
71 |
72 | td,
73 | th {
74 | padding: 0;
75 | }
76 |
77 | #root {
78 | height: 100%;
79 | }
80 |
--------------------------------------------------------------------------------
/src/pages/settings/role/service.tsx:
--------------------------------------------------------------------------------
1 | import { del, get, post, put, ResponseData } from "../../../utils/request";
2 | import { RoleCreateInfo, RoleListItem, RoleUpdateInfo } from "./data";
3 |
4 | export async function roleList(): Promise> {
5 | return await get('/api/v1/roles')
6 | }
7 |
8 | export async function createRole(role: RoleCreateInfo): Promise> {
9 | return await post<{}>('/api/v1/roles', role)
10 | }
11 |
12 | export async function updateRole(id: number, role: RoleUpdateInfo): Promise> {
13 | return await put<{}>('/api/v1/roles/' + id, role)
14 | }
15 |
16 | export async function deleteRole(id: number): Promise> {
17 | return await del<{}>('/api/v1/roles/' + id)
18 | }
19 |
20 | export async function roleMenus(id: number): Promise> {
21 | return await get('/api/v1/roles/' + id + '/menus')
22 | }
23 |
24 | export async function updateRoleMenus(id: number, menus: number[]): Promise> {
25 | return await put<{}>('/api/v1/roles/' + id + '/menus', menus)
26 | }
27 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 basefas
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 |
--------------------------------------------------------------------------------
/src/layouts/components/LogoutModal.tsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from 'react';
2 | import { useNavigate } from "react-router-dom";
3 | import { message, Modal } from "antd";
4 | import { deleteToken } from "../../utils/auth";
5 |
6 | interface LogoutModalProps {
7 | open: boolean;
8 | changeLogoutModalVisible: Function;
9 | }
10 |
11 | const LogoutModal: React.FC = (props) => {
12 | const navigate = useNavigate();
13 | const {open, changeLogoutModalVisible} = props
14 |
15 | useEffect(() => {
16 | changeLogoutModalVisible(open)
17 | }, [open, changeLogoutModalVisible]);
18 |
19 | const okHandle = () => {
20 | deleteToken();
21 | message.success("退出登录成功").then();
22 | changeLogoutModalVisible(false)
23 | navigate('/login')
24 | };
25 | const cancelHandle = () => {
26 | changeLogoutModalVisible(false)
27 | };
28 |
29 |
30 | return (
31 |
39 | 确定退出登录?
40 |
41 | )
42 |
43 | };
44 | export default LogoutModal;
45 |
--------------------------------------------------------------------------------
/src/pages/settings/role/components/CreateForm.tsx:
--------------------------------------------------------------------------------
1 | import { FC } from 'react';
2 | import { Form, Input, Modal } from 'antd';
3 | import { RoleCreateInfo } from "../data";
4 |
5 | const {Item} = Form;
6 |
7 | interface CreateFormProps {
8 | open: boolean;
9 | onOk: (role: RoleCreateInfo) => void;
10 | onCancel: () => void;
11 | }
12 |
13 | const CreateForm: FC = (props) => {
14 | const {open, onOk, onCancel} = props
15 | const [form] = Form.useForm();
16 |
17 | const ok = () => {
18 | form
19 | .validateFields()
20 | .then(values => {
21 | form.resetFields();
22 | onOk(values);
23 | })
24 | .catch(info => {
25 | console.log('参数校验失败:', info);
26 | });
27 | }
28 |
29 | const form_layout = {
30 | labelCol: {span: 4},
31 | };
32 |
33 | return (
34 |
40 |
46 |
47 | )
48 | }
49 | export default CreateForm;
50 |
--------------------------------------------------------------------------------
/.github/workflows/docker-publish.yml:
--------------------------------------------------------------------------------
1 | name: Docker
2 |
3 | on:
4 | push:
5 | tags:
6 | - "v*"
7 | workflow_dispatch: { }
8 |
9 | env:
10 | REGISTRY: ghcr.io
11 | IMAGE_NAME: ${{ github.repository }}
12 |
13 | jobs:
14 | build:
15 | runs-on: ubuntu-latest
16 | permissions:
17 | contents: read
18 | packages: write
19 |
20 | steps:
21 | - name: Checkout repository
22 | uses: actions/checkout@v3
23 |
24 | # Workaround: https://github.com/docker/build-push-action/issues/461
25 | - name: Setup Docker buildx
26 | uses: docker/setup-buildx-action@v2
27 |
28 | - name: Log into registry ${{ env.REGISTRY }}
29 | uses: docker/login-action@v2
30 | with:
31 | registry: ${{ env.REGISTRY }}
32 | username: ${{ github.actor }}
33 | password: ${{ secrets.GHCR_PAT }}
34 |
35 | - name: Extract Docker metadata
36 | id: meta
37 | uses: docker/metadata-action@v4
38 | with:
39 | images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
40 |
41 | - name: Build and push Docker image
42 | id: build-and-push
43 | uses: docker/build-push-action@v4
44 | with:
45 | context: .
46 | push: true
47 | tags: ${{ steps.meta.outputs.tags }}
48 | labels: ${{ steps.meta.outputs.labels }}
49 |
--------------------------------------------------------------------------------
/src/pages/settings/role/components/UpdateForm.tsx:
--------------------------------------------------------------------------------
1 | import { FC } from 'react';
2 | import { Form, Input, Modal } from 'antd';
3 | import { RoleListItem, RoleUpdateInfo } from "../data";
4 |
5 | const {Item} = Form;
6 |
7 | interface UpdateFormProps {
8 | open: boolean;
9 | role: RoleListItem;
10 | onOk: (id: number, user: RoleUpdateInfo) => void;
11 | onCancel: () => void;
12 | }
13 |
14 | const UpdateForm: FC = (props) => {
15 | const {open, role, onOk, onCancel} = props
16 | const [form] = Form.useForm();
17 |
18 | const ok = () => {
19 | form
20 | .validateFields()
21 | .then(values => {
22 | form.resetFields();
23 | onOk(role.id, values);
24 | })
25 | .catch(info => {
26 | console.log('参数校验失败:', info);
27 | });
28 | }
29 |
30 | const form_layout = {
31 | labelCol: {span: 4},
32 | };
33 |
34 | return (
35 |
42 |
53 |
54 | )
55 | }
56 | export default UpdateForm;
57 |
--------------------------------------------------------------------------------
/src/routers/Routers.tsx:
--------------------------------------------------------------------------------
1 | import {Navigate, redirect} from 'react-router-dom'
2 | import {loggedIn} from "../utils/auth";
3 | import BaseLayout from "../layouts/BaseLayout";
4 | import NoFoundPage from "../pages/404";
5 | import Login from "../pages/login/Login";
6 | import Menu from "../pages/settings/menu/Menu";
7 | import Role from "../pages/settings/role/Role";
8 | import Group from "../pages/settings/group/Group";
9 | import User from "../pages/settings/user/User";
10 | import Dashboard from "../pages/dashboard/Dashboard";
11 |
12 |
13 | const loaderBase = async () => {
14 | if (!loggedIn()) {
15 | return redirect("/login");
16 | }
17 | return null;
18 | };
19 |
20 | const loaderLogin = async () => {
21 | if (loggedIn()) {
22 | return redirect("/dashboard");
23 | }
24 | return null;
25 | };
26 |
27 | export default [
28 | {
29 | path: "/login",
30 | element: ,
31 | loader: loaderLogin,
32 | },
33 | {
34 | path: "/",
35 | element: ,
36 | loader: loaderBase,
37 | children: [
38 | {
39 | errorElement: ,
40 | children: [
41 | {
42 | index: true,
43 | element:
44 | },
45 | {path: "/dashboard", element: },
46 | {path: "/settings/users", element: },
47 | {path: "/settings/groups", element: },
48 | {path: "/settings/roles", element: },
49 | {path: "/settings/menus", element: },
50 | {path: "*", element: },
51 | ],
52 | }
53 | ]
54 | },
55 | ]
--------------------------------------------------------------------------------
/src/utils/request.tsx:
--------------------------------------------------------------------------------
1 | import axios from 'axios'
2 | import { deleteToken, getToken } from "./auth";
3 | import { message, Modal } from "antd";
4 |
5 | const {confirm} = Modal;
6 |
7 | export interface ResponseData {
8 | code: number;
9 | data: T;
10 | message: string;
11 | }
12 |
13 | const instance = axios.create({
14 | baseURL: import.meta.env.VITE_API_HOST + ':' + import.meta.env.VITE_API_PORT || 'http://localhost:8086',
15 | timeout: import.meta.env.VITE_API_TIMEOUT || 5000,
16 | });
17 |
18 | instance.interceptors.request.use(
19 | function (config) {
20 | config.headers["token"] = getToken();
21 | return config;
22 | },
23 | function (error) {
24 | return Promise.reject(error);
25 | });
26 |
27 | instance.interceptors.response.use(
28 | response => {
29 | if (response.data.code === -2) {
30 | confirm({
31 | title: ' Token 失效, 请重新登录!',
32 | onOk() {
33 | window.location.href = '/login'
34 | deleteToken()
35 | },
36 | });
37 | }
38 | return Promise.resolve(response.data);
39 | },
40 | error => {
41 | if (error.response) {
42 | message.error(error.response.status + ': ' + error.response.statusText).then()
43 | } else {
44 | message.error('服务器错误: ' + error.message).then()
45 | }
46 | return Promise.reject(error);
47 | });
48 |
49 | export function get(url: string, params?: any): Promise> {
50 | return instance.get(url, {params})
51 | }
52 |
53 | export function post(url: string, data?: any, params?: any): Promise> {
54 | return instance.post(url, data, {params})
55 | }
56 |
57 | export function put(url: string, data?: any, params?: any): Promise> {
58 | return instance.put(url, data, {params})
59 | }
60 |
61 | export function del(url: string, params?: any): Promise> {
62 | return instance.delete(url, {params})
63 | }
64 |
--------------------------------------------------------------------------------
/src/pages/settings/group/components/UpdateForm.tsx:
--------------------------------------------------------------------------------
1 | import { FC, useEffect, useState } from 'react';
2 | import { Form, Input, Modal, Select } from 'antd';
3 | import { GroupListItem, GroupUpdateInfo } from "../data";
4 | import { RoleListItem } from "../../role/data";
5 | import { roleList } from "../../role/service";
6 |
7 | const {Item} = Form;
8 | const {Option} = Select;
9 |
10 | interface UpdateFormProps {
11 | open: boolean;
12 | group: GroupListItem;
13 | onOk: (id: number, group: GroupUpdateInfo) => void;
14 | onCancel: () => void;
15 | }
16 |
17 | const UpdateForm: FC = (props) => {
18 | const {open, group, onOk, onCancel} = props
19 | const [roles, setRoles] = useState([])
20 | const [form] = Form.useForm();
21 |
22 | const getRoleList = async () => {
23 | const result = await roleList();
24 | setRoles(result.data);
25 | };
26 |
27 | useEffect(() => {
28 | getRoleList().then()
29 | }, []);
30 |
31 | const ok = () => {
32 | form
33 | .validateFields()
34 | .then(values => {
35 | form.resetFields();
36 | onOk(group.id, values);
37 | })
38 | .catch(info => {
39 | console.log('参数校验失败:', info);
40 | });
41 | }
42 |
43 | const form_layout = {
44 | labelCol: {span: 4},
45 | };
46 |
47 | return (
48 |
54 |
67 |
68 | )
69 | }
70 | export default UpdateForm;
71 |
--------------------------------------------------------------------------------
/src/pages/settings/group/components/CreateForm.tsx:
--------------------------------------------------------------------------------
1 | import { FC, useEffect, useState } from 'react';
2 | import { Form, Input, Modal, Select } from 'antd';
3 | import { roleList } from "../../role/service";
4 | import { GroupCreateInfo } from "../data";
5 | import { RoleListItem } from "../../role/data";
6 |
7 | const {Item} = Form;
8 | const {Option} = Select;
9 |
10 | interface CreateFormProps {
11 | open: boolean;
12 | onOk: (user: GroupCreateInfo) => void;
13 | onCancel: () => void;
14 | }
15 |
16 | const CreateForm: FC = (props) => {
17 | const {open, onOk, onCancel} = props
18 | const [roles, setRoles] = useState([])
19 | const [form] = Form.useForm();
20 |
21 | const getRoleList = async () => {
22 | const result = await roleList();
23 | setRoles(result.data);
24 | };
25 |
26 | useEffect(() => {
27 | getRoleList().then()
28 | }, []);
29 |
30 | const ok = () => {
31 | form
32 | .validateFields()
33 | .then(values => {
34 | form.resetFields();
35 | onOk(values);
36 | })
37 | .catch(info => {
38 | console.log('参数校验失败:', info);
39 | });
40 | }
41 |
42 | const form_layout = {
43 | labelCol: {span: 4},
44 | };
45 |
46 | return (
47 |
54 |
73 |
74 | )
75 | }
76 | export default CreateForm;
77 |
--------------------------------------------------------------------------------
/src/pages/login/Login.tsx:
--------------------------------------------------------------------------------
1 | import { FC } from 'react';
2 | import { useEmotionCss } from "@ant-design/use-emotion-css";
3 |
4 | import { Button, Card, Form, Input, message } from 'antd';
5 | import { LockOutlined, UserOutlined } from '@ant-design/icons';
6 | import { useNavigate } from "react-router-dom";
7 | import { setCurrentUser, setToken } from "../../utils/auth";
8 | import { UserLogIn } from "./data";
9 | import { login } from "./service";
10 | import bg from "../../assets/background.png"
11 |
12 | const {Item} = Form;
13 |
14 | const Login: FC = () => {
15 | const navigate = useNavigate();
16 |
17 | const containerClassName = useEmotionCss(() => {
18 | return {
19 | display: 'flex',
20 | flexDirection: 'column',
21 | height: '100vh',
22 | overflow: 'auto',
23 | backgroundImage: `url(${bg})`,
24 | backgroundSize: '100% 100%',
25 | };
26 | });
27 |
28 | const onFinish = (user: UserLogIn) => {
29 | Login(user).then()
30 | };
31 |
32 | const Login = async (user: UserLogIn) => {
33 | const result = await login(user)
34 | if (result.code === 0) {
35 | message.success("登录成功")
36 | setToken(result.data.token);
37 | setCurrentUser(result.data.username);
38 | navigate("/dashboard");
39 | } else {
40 | console.log("登录失败")
41 | message.error("登录失败: " + result.message);
42 | }
43 | };
44 |
45 | return (
46 |
47 |
50 |
82 |
83 |
84 | );
85 | };
86 | export default Login;
87 |
--------------------------------------------------------------------------------
/public/icon.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 | [](/LICENSE)
3 | [](https://github.com/basefas/react-antd-admin/releases)
4 | 
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
react-antd-admin
14 |
15 |
16 | 一个使用 React 和 Antd 开发管理系统
17 |
18 |
19 |
20 |
21 |
22 |
23 | ## 简介
24 |
25 | react-antd-admin 使用 vite 与 antd v5 开发,包含常用后台使用的基本模块,依赖项少,结构简单,同时提供完整功能的后端程序,可快速用于二次开发及功能扩展。
26 |
27 |
28 | | | url | introduction |
29 | |-----------|---------------------------------------------|--------------------------------------------|
30 | | backend | https://github.com/basefas/admin-go | 使用 Go & Gin 开发的后台管理系统后端 |
31 | | frontend | https://github.com/basefas/react-antd-admin | 使用 react & vite & antd 开发的后台管理系统前端|
32 |
33 |
34 | ## 页面截图
35 |
36 | ### 登录页面
37 |
38 | 
39 |
40 | ### 用户管理
41 |
42 | 
43 |
44 | ### 分组管理
45 |
46 | 
47 |
48 | ### 菜单管理
49 |
50 | 
51 |
52 | ### 角色及权限管理
53 |
54 | 
55 |
56 |
57 |
58 |
59 | ## 快速开始
60 |
61 | 1. 克隆项目到本地
62 |
63 | ```
64 | git clone https://github.com/basefas/react-antd-admin
65 | ```
66 |
67 | 2. 安装依赖
68 |
69 | ```
70 | npm install
71 | ```
72 |
73 | 3. 运行
74 |
75 | ```
76 | npm run dev
77 | ```
78 |
79 |
80 |
81 | ## Build
82 |
83 | 1. 本地编译
84 |
85 | ```
86 | npm run build
87 | ```
88 |
89 | 2. 本地查看编译结果
90 |
91 | ```
92 | npm run preview
93 | ```
94 |
95 |
96 | 2. 使用 docker 编译并打包 docker 镜像
97 |
98 | ```
99 | docker build -f Dockerfile -t react-antd-admin: .
100 | ```
101 |
102 | > 注:将 `` 替换为你需要的版本号
103 |
104 | 3. 修改配置
105 |
106 | 本地开发修改 `env.development` 文件修改配置
107 | 打包需在编译前修改 `env.production` 文件修改配置
108 | 可配置项如下
109 |
110 | ```
111 | # API 的 URL
112 | VITE_API_HOST=http://localhost
113 | # API 的 PORT
114 | VITE_API_PORT=8086
115 | # API 的 超时时间
116 | VITE_API_TIMEOUT=5000
117 | # 用于显示的平台名称
118 | VITE_PLATFORM_NAME=React Antd Admin
119 | ```
120 |
121 |
122 |
123 | ## 版权声明
124 |
125 | react-antd-admin 基于 MIT 协议, 详情请参考 [license](LICENSE)。
126 |
--------------------------------------------------------------------------------
/src/assets/logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/pages/settings/user/components/UpdateForm.tsx:
--------------------------------------------------------------------------------
1 | import { FC, useEffect, useState } from 'react';
2 | import { Form, Input, Modal, Select } from 'antd';
3 | import { groupList } from "../../group/service";
4 | import { roleList } from "../../role/service";
5 | import { GroupListItem } from "../../group/data";
6 | import { RoleListItem } from "../../role/data";
7 | import { UserListItem, UserUpdateInfo } from "../data";
8 |
9 | const {Item} = Form;
10 | const {Option} = Select;
11 |
12 | interface UpdateFormProps {
13 | open: boolean;
14 | user: UserListItem;
15 | onOk: (id: number, user: UserUpdateInfo) => void;
16 | onCancel: () => void;
17 | }
18 |
19 | const UpdateForm: FC = (props) => {
20 | const {open, user, onOk, onCancel} = props
21 | const [groups, setGroups] = useState([])
22 | const [roles, setRoles] = useState([])
23 | const [form] = Form.useForm();
24 |
25 | const getGroupList = async () => {
26 | const result = await groupList();
27 | setGroups(result.data);
28 | };
29 |
30 | const getRoleList = async () => {
31 | const result = await roleList();
32 | setRoles(result.data);
33 | };
34 |
35 | useEffect(() => {
36 | getGroupList().then()
37 | getRoleList().then()
38 | }, []);
39 |
40 | const ok = () => {
41 | form
42 | .validateFields()
43 | .then(values => {
44 | form.resetFields();
45 | onOk(user.id, values);
46 | })
47 | .catch(info => {
48 | console.log('参数校验失败:', info);
49 | });
50 | }
51 |
52 | const form_layout = {
53 | labelCol: {span: 4},
54 | };
55 |
56 | return (
57 |
64 |
98 |
99 | )
100 | }
101 | export default UpdateForm;
102 |
--------------------------------------------------------------------------------
/src/pages/settings/user/components/CreateForm.tsx:
--------------------------------------------------------------------------------
1 | import { FC, useEffect, useState } from 'react';
2 | import { Form, Input, Modal, Select } from 'antd';
3 | import { groupList } from "../../group/service";
4 | import { roleList } from "../../role/service";
5 | import { GroupListItem } from "../../group/data";
6 | import { RoleListItem } from "../../role/data";
7 | import { UserCreateInfo } from "../data";
8 |
9 | const {Item} = Form;
10 | const {Option} = Select;
11 |
12 | interface CreateFormProps {
13 | open: boolean;
14 | onOk: (user: UserCreateInfo) => void;
15 | onCancel: () => void;
16 | }
17 |
18 | const CreateForm: FC = (props) => {
19 | const {open, onOk, onCancel} = props
20 | const [groups, setGroups] = useState([])
21 | const [roles, setRoles] = useState([])
22 | const [form] = Form.useForm();
23 |
24 | const getGroupList = async () => {
25 | const result = await groupList();
26 | setGroups(result.data);
27 | };
28 |
29 | const getRoleList = async () => {
30 | const result = await roleList();
31 | setRoles(result.data);
32 | };
33 |
34 | useEffect(() => {
35 | getGroupList().then()
36 | getRoleList().then()
37 | }, []);
38 |
39 | const ok = () => {
40 | form
41 | .validateFields()
42 | .then(values => {
43 | form.resetFields();
44 | onOk(values);
45 | })
46 | .catch(info => {
47 | console.log('参数校验失败:', info);
48 | });
49 | }
50 |
51 | const form_layout = {
52 | labelCol: {span: 4},
53 | };
54 |
55 | return (
56 |
63 |
97 |
98 | )
99 | }
100 | export default CreateForm;
101 |
--------------------------------------------------------------------------------
/src/layouts/BaseLayout.tsx:
--------------------------------------------------------------------------------
1 | import { FC, useEffect, useState } from "react";
2 | import ProLayout, { MenuDataItem } from "@ant-design/pro-layout";
3 | import { Outlet, useLocation, useNavigate } from "react-router-dom";
4 | import logo from "../assets/logo.svg";
5 | import { menuIcons } from "../utils/icons";
6 | import { systemMenuList } from "./service";
7 | import { Dropdown, MenuProps } from "antd";
8 | import { LogoutOutlined } from "@ant-design/icons";
9 | import { getCurrentUser } from "../utils/auth";
10 | import LogoutModal from "./components/LogoutModal";
11 |
12 | const BaseLayout: FC = () => {
13 | const navigate = useNavigate();
14 | const location = useLocation();
15 | const [menuData, setMenuData] = useState([]);
16 | const [pathname, setPathname] = useState(location.pathname);
17 | const [loading, setLoading] = useState(true);
18 |
19 | const [logoutModalVisible, setLogoutModalVisible] = useState(false);
20 | const [user, setUser] = useState("User");
21 |
22 | const changeLogoutModalVisible = (status: boolean) => {
23 | setLogoutModalVisible(status)
24 | }
25 |
26 | const loopMenuItem = (menus: MenuDataItem[]): MenuDataItem[] => {
27 | if (menus != null && menus.length > 0) {
28 | return menus.map(({icon, children, locale, ...item}) => ({
29 | ...item,
30 | icon: icon && menuIcons[icon as string],
31 | children: children && loopMenuItem(children),
32 | }));
33 | } else {
34 | return [];
35 | }
36 | };
37 |
38 | const fetchData = async () => {
39 | const result = await systemMenuList();
40 | setMenuData(result.data);
41 | setLoading(false);
42 | };
43 |
44 | useEffect(() => {
45 | setMenuData([]);
46 | setLoading(true);
47 | fetchData().then();
48 | }, []);
49 |
50 | useEffect(() => {
51 | let currentUser = getCurrentUser();
52 | if (currentUser) {
53 | setUser(currentUser);
54 | }
55 |
56 | }, [getCurrentUser()]);
57 |
58 | const items: MenuProps['items'] = [
59 | {
60 | key: '1',
61 | label: (
62 |
63 |
64 | 退出登录
65 |
66 | ),
67 | onClick: () => {
68 | setLogoutModalVisible(true)
69 | }
70 | },
71 | ];
72 | return (
73 | loopMenuItem(menuData)}
81 | menuItemRender={(item, dom) => (
82 | {
83 | setPathname(item.path || "/dashboard");
84 | navigate(item.path || "/dashboard");
85 | }}>
86 | {dom}
87 |
88 | )}
89 | avatarProps={{
90 | icon: user.slice(0, 1).toUpperCase(),
91 | size: 'small',
92 | title: user,
93 | render: (_props, dom) => {
94 | return (
95 |
98 | {dom}
99 |
100 | );
101 | },
102 | }}
103 | actionsRender={() => {
104 | return [];
105 | }}
106 | token={{
107 | sider: {
108 | colorBgMenuItemSelected: '#e6f7ff',
109 | colorTextMenuSelected: '#1890ff',
110 | },
111 | }}
112 | >
113 |
114 | {logoutModalVisible ?
115 | : null}
118 |
119 | );
120 | };
121 |
122 | export default BaseLayout;
123 |
--------------------------------------------------------------------------------
/src/pages/settings/menu/components/UpdateForm.tsx:
--------------------------------------------------------------------------------
1 | import { FC } from 'react';
2 | import { Form, Input, InputNumber, Modal, Select, TreeSelect } from 'antd';
3 | import { menuIcons } from "../../../../utils/icons";
4 | import { MenuListItem, MenuUpdateInfo } from "../data";
5 | import { DataNode } from "antd/lib/tree";
6 | import { DownOutlined } from "@ant-design/icons";
7 |
8 | const {Item} = Form;
9 | const {Option} = Select;
10 |
11 | interface UpdateFormProps {
12 | open: boolean;
13 | menusSelect: DataNode[];
14 | menu: MenuListItem;
15 | formType: number;
16 | onOk: (id: number, menu: MenuUpdateInfo) => void;
17 | onCancel: () => void;
18 | }
19 |
20 | const UpdateForm: FC = (props) => {
21 | const {open, formType, menu, menusSelect, onOk, onCancel} = props
22 | const [form] = Form.useForm();
23 |
24 | const ok = () => {
25 | form
26 | .validateFields()
27 | .then(values => {
28 | form.resetFields();
29 | onOk(menu.id, values);
30 | })
31 | .catch(info => {
32 | console.log('参数校验失败:', info);
33 | });
34 | }
35 |
36 | const form_layout = {
37 | labelCol: {span: 4},
38 | };
39 |
40 | return (
41 |
48 |
108 |
109 | )
110 | }
111 | export default UpdateForm;
112 |
--------------------------------------------------------------------------------
/src/pages/settings/menu/components/CreateForm.tsx:
--------------------------------------------------------------------------------
1 | import { FC } from 'react';
2 | import { Form, Input, InputNumber, Modal, Select, TreeSelect } from 'antd';
3 | import { MenuCreateInfo } from "../data";
4 | import { menuIcons } from "../../../../utils/icons";
5 | import { DataNode } from "antd/lib/tree";
6 | import { DownOutlined } from "@ant-design/icons";
7 |
8 | const {Item} = Form;
9 | const {Option} = Select;
10 |
11 | interface CreateFormProps {
12 | open: boolean;
13 | menusSelect: DataNode[];
14 | menu: DataNode;
15 | formType: number;
16 | onOk: (menu: MenuCreateInfo) => void;
17 | onCancel: () => void;
18 | }
19 |
20 | const CreateForm: FC = (props) => {
21 | const {open, menusSelect, menu, formType, onOk, onCancel} = props
22 | const [form] = Form.useForm();
23 | const ok = () => {
24 | form
25 | .validateFields()
26 | .then(values => {
27 | form.resetFields();
28 | if (formType === 1) {
29 | if (values.menu_type === 1) {
30 | values.method = '-'
31 | }
32 | if (values.menu_type === 2) {
33 | values.method = 'GET'
34 | }
35 | if (values.parent_id === 0) {
36 | values.menu_type = 1
37 | }
38 | }
39 | if (formType === 2) {
40 | values.menu_type = 3
41 | values.icon = ''
42 | }
43 | onOk(values);
44 | })
45 | .catch(info => {
46 | console.log('参数校验失败:', info);
47 | });
48 | }
49 |
50 | const form_layout = {
51 | labelCol: {span: 4},
52 | };
53 |
54 | return (
55 |
61 |
111 |
112 | )
113 | }
114 | export default CreateForm;
115 |
--------------------------------------------------------------------------------
/src/pages/settings/group/Group.tsx:
--------------------------------------------------------------------------------
1 | import { FC, Fragment, useEffect, useState } from 'react';
2 | import { Button, Card, Divider, message, Modal, Table } from "antd";
3 | import { GroupCreateInfo, GroupListItem, GroupUpdateInfo } from "./data";
4 | import { createGroup, deleteGroup, groupList, updateGroup } from "./service";
5 | import { ColumnsType } from "antd/es/table";
6 | import dayjs from 'dayjs';
7 | import { ExclamationCircleOutlined, PlusOutlined } from "@ant-design/icons";
8 | import CreateForm from "./components/CreateForm";
9 | import UpdateForm from "./components/UpdateForm";
10 |
11 | const {confirm} = Modal;
12 |
13 | const Group: FC = () => {
14 | const [groups, setGroups] = useState([])
15 | const [groupUpdate, setGroupUpdate] = useState()
16 | const [loading, setLoading] = useState(false)
17 | const [createFormVisible, setCreateFormVisible] = useState(false)
18 | const [updateFormVisible, setUpdateFormVisible] = useState(false)
19 | const title = '分组';
20 |
21 | const getGroupList = async () => {
22 | const result = await groupList();
23 | setGroups(result.data);
24 | setLoading(false);
25 | };
26 |
27 | useEffect(() => {
28 | setLoading(true);
29 | getGroupList().then();
30 | }, []);
31 |
32 | const formatTime = (date: any) => {
33 | return date ? dayjs(date).format('YYYY-MM-DD HH:mm') : ''
34 | };
35 |
36 | const handleCreateGroup = async (group: GroupCreateInfo) => {
37 | const result = await createGroup(group);
38 | if (result.code === 0) {
39 | setCreateFormVisible(false)
40 | getGroupList().then()
41 | } else {
42 | message.error("创建分组失败");
43 | }
44 | };
45 |
46 | const handleUpdateGroup = async (id: number, group: GroupUpdateInfo) => {
47 | const result = await updateGroup(id, group);
48 | if (result.code === 0) {
49 | setUpdateFormVisible(false);
50 | getGroupList().then()
51 | } else {
52 | message.error("修改分组失败");
53 | }
54 | };
55 |
56 | const handleDeleteGroup = async (id: number) => {
57 | const result = await deleteGroup(id);
58 | if (result.code === 0) {
59 | getGroupList().then()
60 | } else {
61 | message.error("删除分组失败");
62 | }
63 | };
64 |
65 | const addGroup = (
66 |
71 | )
72 |
73 | function deleteModal(group: GroupListItem) {
74 | confirm({
75 | title: '确定删除分组 ' + group.name + ' ?',
76 | icon: ,
77 | onOk() {
78 | handleDeleteGroup(group.id).then()
79 | },
80 | });
81 | }
82 |
83 | const columns: ColumnsType = [
84 | {
85 | title: 'ID',
86 | dataIndex: 'id',
87 | key: 'id',
88 | align: 'center',
89 | },
90 | {
91 | title: '分组',
92 | dataIndex: 'name',
93 | key: 'name',
94 | align: 'center',
95 | },
96 | {
97 | title: '角色',
98 | dataIndex: 'role_name',
99 | key: 'role_name',
100 | align: 'center',
101 | },
102 | {
103 | title: '创建时间',
104 | dataIndex: 'create_time',
105 | key: 'create_time',
106 | align: 'center',
107 | render: formatTime
108 | },
109 | {
110 | title: '修改时间',
111 | dataIndex: 'update_time',
112 | key: 'update_time',
113 | align: 'center',
114 | render: formatTime
115 | },
116 | {
117 | title: '操作',
118 | key: 'action',
119 | align: 'center',
120 | render: (group) => (
121 |
122 |
128 |
129 |
133 |
134 | ),
135 | },
136 | ];
137 |
138 | return (
139 |
140 |
141 | groups.id}
145 | loading={loading}
146 | pagination={{
147 | hideOnSinglePage: true,
148 | pageSize: 10
149 | }}
150 | />
151 |
152 |
153 | {createFormVisible ?
154 | {
158 | setCreateFormVisible(false)
159 | }}
160 | /> : null}
161 |
162 | {updateFormVisible ?
163 | {
168 | setUpdateFormVisible(false)
169 | }}
170 | /> : null}
171 |
172 | );
173 | }
174 | export default Group;
175 |
--------------------------------------------------------------------------------
/src/pages/settings/user/User.tsx:
--------------------------------------------------------------------------------
1 | import { FC, Fragment, useEffect, useState } from 'react';
2 | import { Badge, Button, Card, Divider, message, Modal, Table } from "antd";
3 | import { createUser, deleteUser, updateUser, userList } from "./service";
4 | import { ExclamationCircleOutlined, PlusOutlined } from "@ant-design/icons";
5 | import { UserCreateInfo, UserListItem, UserUpdateInfo } from "./data";
6 | import { ColumnsType } from "antd/es/table";
7 | import CreateForm from "./components/CreateForm";
8 | import UpdateForm from "./components/UpdateForm";
9 |
10 | const {confirm} = Modal;
11 |
12 | const User: FC = () => {
13 | const [users, setUsers] = useState([])
14 | const [userUpdate, setUserUpdate] = useState()
15 | const [loading, setLoading] = useState(false)
16 | const [createFormVisible, setCreateFormVisible] = useState(false)
17 | const [updateFormVisible, setUpdateFormVisible] = useState(false)
18 | const title = '用户列表';
19 |
20 | const getUserList = async () => {
21 | const result = await userList();
22 | setUsers(result.data);
23 | setLoading(false);
24 | };
25 |
26 | useEffect(() => {
27 | setLoading(true);
28 | getUserList().then();
29 | }, []);
30 |
31 | const handleSwitchUserState = async (user: UserListItem) => {
32 | const result = await updateUser(user.id, {
33 | status: user.status === 1 ? 2 : 1
34 | } as UserUpdateInfo);
35 | if (result.code === 0) {
36 | getUserList().then()
37 | } else {
38 | message.error("修改用户状态失败");
39 | }
40 | };
41 |
42 | const handleCreateUser = async (user: UserCreateInfo) => {
43 | const result = await createUser(user);
44 | if (result.code === 0) {
45 | setCreateFormVisible(false)
46 | getUserList().then()
47 | message.success("创建成功");
48 | } else {
49 | message.error("创建用户失败");
50 | }
51 | };
52 |
53 | const handleUpdateUser = async (id: number, user: UserUpdateInfo) => {
54 | const result = await updateUser(id, user);
55 | if (result.code === 0) {
56 | setUpdateFormVisible(false);
57 | getUserList().then()
58 | } else {
59 | message.error("修改用户信息失败");
60 | }
61 | };
62 |
63 | const handleDeleteUser = async (id: number) => {
64 | const result = await deleteUser(id);
65 | if (result.code === 0) {
66 | getUserList().then()
67 | } else {
68 | message.error("删除用户失败");
69 | }
70 | };
71 |
72 | const addUser = (
73 |
76 | )
77 |
78 | function deleteModal(user: UserListItem) {
79 | confirm({
80 | title: '确定删除用户 ' + user.username + ' ?',
81 | icon: ,
82 | onOk() {
83 | handleDeleteUser(user.id).then()
84 | },
85 | });
86 | }
87 |
88 | const columns: ColumnsType = [
89 | {
90 | title: '用户 ID',
91 | dataIndex: 'id',
92 | key: 'id',
93 | align: 'center',
94 | },
95 | {
96 | title: '用户名',
97 | dataIndex: 'username',
98 | key: 'username',
99 | align: 'center',
100 | },
101 | {
102 | title: '邮箱',
103 | dataIndex: 'email',
104 | key: 'email',
105 | align: 'center',
106 | },
107 | {
108 | title: '分组',
109 | dataIndex: 'group_name',
110 | key: 'group_name',
111 | align: 'center',
112 | },
113 | {
114 | title: '角色',
115 | dataIndex: 'role_name',
116 | key: 'role_name',
117 | align: 'center',
118 | },
119 | {
120 | title: '状态',
121 | dataIndex: 'status',
122 | key: 'status',
123 | align: 'center',
124 | render: (status: number) => {
125 | if (status === 1) {
126 | return
127 | } else if (status === 2) {
128 | return
129 | } else {
130 | return
131 | }
132 | }
133 | },
134 | {
135 | title: '操作',
136 | key: 'action',
137 | align: 'center',
138 | render: (user: UserListItem) => (
139 |
140 |
143 |
144 |
151 |
152 |
156 |
157 | ),
158 | },
159 | ];
160 |
161 | return (
162 |
163 |
164 | users.id}
168 | loading={loading}
169 | pagination={{
170 | hideOnSinglePage: true,
171 | pageSize: 10
172 | }}
173 | />
174 |
175 | {createFormVisible ?
176 | {
180 | setCreateFormVisible(false)
181 | }}
182 | /> : null}
183 | {updateFormVisible ?
184 | {
189 | setUpdateFormVisible(false)
190 | }}
191 | /> : null}
192 |
193 | );
194 | }
195 | export default User;
196 |
--------------------------------------------------------------------------------
/src/pages/settings/role/Role.tsx:
--------------------------------------------------------------------------------
1 | import { FC, Fragment, useEffect, useState } from 'react';
2 | import dayjs from 'dayjs';
3 | import { Button, Card, Divider, message, Modal, Table } from 'antd';
4 | import { RoleCreateInfo, RoleListItem, RoleUpdateInfo } from "./data";
5 | import { ColumnsType } from "antd/es/table";
6 | import { createRole, deleteRole, roleList, updateRole, updateRoleMenus } from "./service";
7 | import { ExclamationCircleOutlined, PlusOutlined } from "@ant-design/icons";
8 | import { menuList } from "../menu/service";
9 | import { MenuListItem } from "../menu/data";
10 | import CreateForm from "./components/CreateForm";
11 | import UpdateForm from "./components/UpdateForm";
12 | import PermissionForm from "./components/PermissionForm";
13 |
14 | const {confirm} = Modal;
15 |
16 | const Role: FC = () => {
17 | const [roles, setRoles] = useState([])
18 | const [menus, setMenus] = useState([])
19 | const [roleUpdate, setRoleUpdate] = useState()
20 | const [rolePermission, setRolePermission] = useState()
21 | const [loading, setLoading] = useState(false)
22 | const [createFormVisible, setCreateFormVisible] = useState(false)
23 | const [updateFormVisible, setUpdateFormVisible] = useState(false)
24 | const [permissionFormVisible, setPermissionFormVisible] = useState(false)
25 | const title = '角色';
26 |
27 | const getRoleList = async () => {
28 | const result = await roleList();
29 | setRoles(result.data);
30 | setLoading(false);
31 | };
32 |
33 | const getMenuList = async () => {
34 | const result = await menuList();
35 | setMenus(result.data);
36 | };
37 |
38 | useEffect(() => {
39 | setLoading(true)
40 | getRoleList().then()
41 | getMenuList().then()
42 | }, []);
43 |
44 | const formatTime = (date: any) => {
45 | return date ? dayjs(date).format('YYYY-MM-DD HH:mm') : ''
46 | };
47 |
48 | const handleCreateRole = async (role: RoleCreateInfo) => {
49 | const result = await createRole(role);
50 | if (result.code === 0) {
51 | setCreateFormVisible(false)
52 | getRoleList().then()
53 | } else {
54 | message.error("创建角色失败");
55 | }
56 | };
57 |
58 | const handleUpdateRole = async (id: number, role: RoleUpdateInfo) => {
59 | const result = await updateRole(id, role);
60 | if (result.code === 0) {
61 | setUpdateFormVisible(false);
62 | getRoleList().then()
63 | } else {
64 | message.error("更新角色失败");
65 | }
66 | };
67 |
68 | const handleDeleteRole = async (id: number) => {
69 | const result = await deleteRole(id);
70 | if (result.code === 0) {
71 | getRoleList().then()
72 | } else {
73 | message.error("删除角色失败");
74 | }
75 | };
76 |
77 | const handleUpdatePermission = async (id: number, checkedList: number[]) => {
78 | const res = await updateRoleMenus(id, checkedList);
79 | if (res.code === 0) {
80 | setPermissionFormVisible(false);
81 | message.success("修改成功");
82 | } else {
83 | message.success("修改失败");
84 | }
85 | };
86 |
87 | const addRole = (
88 |
92 | );
93 |
94 | function deleteModal(role: RoleListItem) {
95 | confirm({
96 | title: '删除角色',
97 | content: '确定删除角色<' + role.name + '>?',
98 | icon: ,
99 | onOk() {
100 | handleDeleteRole(role.id).then()
101 | },
102 | });
103 | }
104 |
105 | const columns: ColumnsType = [
106 | {
107 | title: 'ID',
108 | dataIndex: 'id',
109 | key: 'id',
110 | align: 'center',
111 | },
112 | {
113 | title: '角色',
114 | dataIndex: 'name',
115 | key: 'name',
116 | align: 'center',
117 | },
118 | {
119 | title: '创建时间',
120 | dataIndex: 'create_time',
121 | key: 'create_time',
122 | align: 'center',
123 | render: formatTime
124 | },
125 | {
126 | title: '修改时间',
127 | dataIndex: 'update_time',
128 | key: 'update_time',
129 | align: 'center',
130 | render: formatTime
131 | },
132 | {
133 | title: '操作',
134 | key: 'action',
135 | align: 'center',
136 | render: (role: RoleListItem) => (
137 |
138 |
144 |
145 |
152 |
153 |
157 |
158 | ),
159 | },
160 | ];
161 |
162 | return (
163 |
164 |
165 | roles.id}
169 | loading={loading}
170 | pagination={{
171 | hideOnSinglePage: true,
172 | pageSize: 10
173 | }}
174 | />
175 |
176 | {createFormVisible ?
177 | {
181 | setCreateFormVisible(false)
182 | }}
183 | /> : null}
184 | {updateFormVisible ?
185 | {
190 | setUpdateFormVisible(false)
191 | }}
192 | /> : null}
193 | {permissionFormVisible ?
194 | {
200 | setPermissionFormVisible(false)
201 | }}
202 | /> : null}
203 |
204 | );
205 | }
206 | export default Role;
207 |
--------------------------------------------------------------------------------
/src/pages/settings/role/components/PermissionForm.tsx:
--------------------------------------------------------------------------------
1 | import { FC, useEffect, useState } from 'react';
2 | import { Checkbox, Modal, Table } from 'antd';
3 | import { MenuListItem } from "../../menu/data";
4 | import { RoleListItem } from "../data";
5 | import { CheckboxChangeEvent } from "antd/es/checkbox";
6 | import { roleMenus } from "../service";
7 |
8 | interface PermissionFormProps {
9 | open: boolean;
10 | role: RoleListItem;
11 | menus: MenuListItem[];
12 | onOk: (id: number, checkedList: number[]) => void;
13 | onCancel: () => void;
14 | }
15 |
16 | const PermissionForm: FC = (props) => {
17 | const {open, role, menus, onOk, onCancel} = props
18 |
19 | const [menusTree, setMenusTree] = useState()
20 | const [loading, setLoading] = useState(false)
21 | const [firstIn, setFirstIn] = useState(true)
22 | const [checkedList, setCheckedList] = useState([]);
23 | const [indeterminateList, setIndeterminateList] = useState([]);
24 | const [fMap, setFMap] = useState