commit
						8327fd09e8
					
				 46 changed files with 4859 additions and 0 deletions
			
			
		- 
					24.eslintrc.cjs
 - 
					24.gitignore
 - 
					30README.md
 - 
					13index.html
 - 
					21mock/departments.ts
 - 
					98mock/menus.ts
 - 
					47package.json
 - 
					1public/vite.svg
 - 
					20src/App.css
 - 
					11src/App.tsx
 - 
					35src/Auth.tsx
 - 
					1src/assets/react.svg
 - 
					74src/components/breadcrumb/index.tsx
 - 
					64src/components/error-boundary/index.tsx
 - 
					52src/components/icon/index.tsx
 - 
					23src/hooks/useFetch.ts
 - 
					0src/index.css
 - 
					22src/layout/FormPageLayout.tsx
 - 
					21src/layout/ListPageLayout.tsx
 - 
					144src/layout/RootLayout.tsx
 - 
					20src/layout/TreePageLayout.tsx
 - 
					69src/layout/_defaultProps.tsx
 - 
					10src/main.tsx
 - 
					25src/pages/dashboard/index.tsx
 - 
					10src/pages/list/index.tsx
 - 
					15src/pages/list/list.tsx
 - 
					146src/pages/list/tree.tsx
 - 
					11src/pages/system/departments/index.tsx
 - 
					60src/pages/system/menus/index.tsx
 - 
					11src/pages/system/roles/index.tsx
 - 
					11src/pages/system/users/index.tsx
 - 
					0src/patches/x-fetch.ts
 - 
					17src/request.ts
 - 
					149src/routes.tsx
 - 
					25src/service/system.ts
 - 
					44src/store/department.ts
 - 
					46src/store/system.ts
 - 
					9src/store/types/department.d.ts
 - 
					8src/store/user.ts
 - 
					66src/types.d.ts
 - 
					33src/utils/uuid.ts
 - 
					1src/vite-env.d.ts
 - 
					34tsconfig.json
 - 
					11tsconfig.node.json
 - 
					27vite.config.ts
 - 
					3276yarn.lock
 
@ -0,0 +1,24 @@ | 
			
		|||||
 | 
				module.exports = { | 
			
		||||
 | 
				    root: true, | 
			
		||||
 | 
				    env: { browser: true, es2020: true }, | 
			
		||||
 | 
				    extends: [ | 
			
		||||
 | 
				        'eslint:recommended', | 
			
		||||
 | 
				        'plugin:@typescript-eslint/recommended', | 
			
		||||
 | 
				        'plugin:react-hooks/recommended', | 
			
		||||
 | 
				    ], | 
			
		||||
 | 
				    ignorePatterns: [ 'dist', '.eslintrc.cjs' ], | 
			
		||||
 | 
				    parser: '@typescript-eslint/parser', | 
			
		||||
 | 
				    plugins: [ 'react-refresh' ], | 
			
		||||
 | 
				    rules: { | 
			
		||||
 | 
				        'react-refresh/only-export-components': [ | 
			
		||||
 | 
				            'warn', | 
			
		||||
 | 
				            { allowConstantExport: true }, | 
			
		||||
 | 
				        ], | 
			
		||||
 | 
				        '@typescript-eslint/ban-ts-comment': [ 'error', { | 
			
		||||
 | 
				            'ts-expect-error': 'allow-with-description', | 
			
		||||
 | 
				            'ts-ignore': 'allow-with-description', | 
			
		||||
 | 
				            'minimumDescriptionLength': 10 | 
			
		||||
 | 
				        } ], | 
			
		||||
 | 
				        '@typescript-eslint/no-explicit-any': 'off', | 
			
		||||
 | 
				    }, | 
			
		||||
 | 
				} | 
			
		||||
@ -0,0 +1,24 @@ | 
			
		|||||
 | 
				# Logs | 
			
		||||
 | 
				logs | 
			
		||||
 | 
				*.log | 
			
		||||
 | 
				npm-debug.log* | 
			
		||||
 | 
				yarn-debug.log* | 
			
		||||
 | 
				yarn-error.log* | 
			
		||||
 | 
				pnpm-debug.log* | 
			
		||||
 | 
				lerna-debug.log* | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				node_modules | 
			
		||||
 | 
				dist | 
			
		||||
 | 
				dist-ssr | 
			
		||||
 | 
				*.local | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				# Editor directories and files | 
			
		||||
 | 
				.vscode/* | 
			
		||||
 | 
				!.vscode/extensions.json | 
			
		||||
 | 
				.idea | 
			
		||||
 | 
				.DS_Store | 
			
		||||
 | 
				*.suo | 
			
		||||
 | 
				*.ntvs* | 
			
		||||
 | 
				*.njsproj | 
			
		||||
 | 
				*.sln | 
			
		||||
 | 
				*.sw? | 
			
		||||
@ -0,0 +1,30 @@ | 
			
		|||||
 | 
				# React + TypeScript + Vite | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				Currently, two official plugins are available: | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh | 
			
		||||
 | 
				- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				## Expanding the ESLint configuration | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				If you are developing a production application, we recommend updating the configuration to enable type aware lint rules: | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				- Configure the top-level `parserOptions` property like this: | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				```js | 
			
		||||
 | 
				export default { | 
			
		||||
 | 
				  // other rules... | 
			
		||||
 | 
				  parserOptions: { | 
			
		||||
 | 
				    ecmaVersion: 'latest', | 
			
		||||
 | 
				    sourceType: 'module', | 
			
		||||
 | 
				    project: ['./tsconfig.json', './tsconfig.node.json'], | 
			
		||||
 | 
				    tsconfigRootDir: __dirname, | 
			
		||||
 | 
				  }, | 
			
		||||
 | 
				} | 
			
		||||
 | 
				``` | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				- Replace `plugin:@typescript-eslint/recommended` to `plugin:@typescript-eslint/recommended-type-checked` or `plugin:@typescript-eslint/strict-type-checked` | 
			
		||||
 | 
				- Optionally add `plugin:@typescript-eslint/stylistic-type-checked` | 
			
		||||
 | 
				- Install [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) and add `plugin:react/recommended` & `plugin:react/jsx-runtime` to the `extends` list | 
			
		||||
@ -0,0 +1,13 @@ | 
			
		|||||
 | 
				<!doctype html> | 
			
		||||
 | 
				<html lang="en"> | 
			
		||||
 | 
				  <head> | 
			
		||||
 | 
				    <meta charset="UTF-8" /> | 
			
		||||
 | 
				    <link rel="icon" type="image/svg+xml" href="/vite.svg" /> | 
			
		||||
 | 
				    <meta name="viewport" content="width=device-width, initial-scale=1.0" /> | 
			
		||||
 | 
				    <title>Crazy Pro</title> | 
			
		||||
 | 
				  </head> | 
			
		||||
 | 
				  <body> | 
			
		||||
 | 
				    <div id="root"></div> | 
			
		||||
 | 
				    <script type="module" src="/src/main.tsx"></script> | 
			
		||||
 | 
				  </body> | 
			
		||||
 | 
				</html> | 
			
		||||
@ -0,0 +1,21 @@ | 
			
		|||||
 | 
				export default [ | 
			
		||||
 | 
				    { | 
			
		||||
 | 
				        url: '/api/departments', | 
			
		||||
 | 
				        method: 'get', | 
			
		||||
 | 
				        response: () => { | 
			
		||||
 | 
				            return { | 
			
		||||
 | 
				                code: 200, | 
			
		||||
 | 
				                data: [ | 
			
		||||
 | 
				                    { | 
			
		||||
 | 
				                        id: '1', | 
			
		||||
 | 
				                        name: '开发部' | 
			
		||||
 | 
				                    }, | 
			
		||||
 | 
				                    { | 
			
		||||
 | 
				                        id: '2', | 
			
		||||
 | 
				                        name: '测试部' | 
			
		||||
 | 
				                    } | 
			
		||||
 | 
				                ] | 
			
		||||
 | 
				            } | 
			
		||||
 | 
				        } | 
			
		||||
 | 
				    } | 
			
		||||
 | 
				] | 
			
		||||
@ -0,0 +1,98 @@ | 
			
		|||||
 | 
				import { MockMethod } from 'vite-plugin-mock' | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				export default [ | 
			
		||||
 | 
				    { | 
			
		||||
 | 
				        url: '/api/menus', | 
			
		||||
 | 
				        method: 'get', | 
			
		||||
 | 
				        response: () => { | 
			
		||||
 | 
				            return { | 
			
		||||
 | 
				                code: 200, | 
			
		||||
 | 
				                message: 'Success', | 
			
		||||
 | 
				                data: [ | 
			
		||||
 | 
				                    { | 
			
		||||
 | 
				                        id:1, | 
			
		||||
 | 
				                        path: '/welcome', | 
			
		||||
 | 
				                        name: '欢迎', | 
			
		||||
 | 
				                        icon: 'ApplicationMenu', | 
			
		||||
 | 
				                        component: './pages/dashboard', | 
			
		||||
 | 
				                        type: 1, | 
			
		||||
 | 
				                        order: 1, | 
			
		||||
 | 
				                    }, | 
			
		||||
 | 
				                    { | 
			
		||||
 | 
				                        id:2, | 
			
		||||
 | 
				                        path: '/admin', | 
			
		||||
 | 
				                        name: '系统管理', | 
			
		||||
 | 
				                        icon: 'SettingTwo', | 
			
		||||
 | 
				                        access: 'canAdmin', | 
			
		||||
 | 
				                        type: 1, | 
			
		||||
 | 
				                        order: 2, | 
			
		||||
 | 
				                        children: [ | 
			
		||||
 | 
				                            { | 
			
		||||
 | 
				                                id:3, | 
			
		||||
 | 
				                                path: '/admin/menus', | 
			
		||||
 | 
				                                name: '导航管理', | 
			
		||||
 | 
				                                icon: 'AllApplication', | 
			
		||||
 | 
				                                component: './pages/system/menus', | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				                                type: 1, | 
			
		||||
 | 
				                            }, | 
			
		||||
 | 
				                            { | 
			
		||||
 | 
				                                id:4, | 
			
		||||
 | 
				                                path: '/admin/users', | 
			
		||||
 | 
				                                name: '用户管理', | 
			
		||||
 | 
				                                icon: 'PeoplesTwo', | 
			
		||||
 | 
				                                component: './pages/system/users', | 
			
		||||
 | 
				                                type: 1, | 
			
		||||
 | 
				                            }, | 
			
		||||
 | 
				                            { | 
			
		||||
 | 
				                                id:5, | 
			
		||||
 | 
				                                path: '/admin/departments', | 
			
		||||
 | 
				                                name: '部门管理', | 
			
		||||
 | 
				                                icon: 'Browser', | 
			
		||||
 | 
				                                component: './pages/system/departments', | 
			
		||||
 | 
				                                type: 1, | 
			
		||||
 | 
				                            }, | 
			
		||||
 | 
				                            { | 
			
		||||
 | 
				                                id:6, | 
			
		||||
 | 
				                                path: '/admin/roles', | 
			
		||||
 | 
				                                name: '角色管理', | 
			
		||||
 | 
				                                icon: 'Permissions', | 
			
		||||
 | 
				                                component: './pages/system/roles', | 
			
		||||
 | 
				                                type: 1, | 
			
		||||
 | 
				                            }, | 
			
		||||
 | 
				                        ], | 
			
		||||
 | 
				                    }, | 
			
		||||
 | 
				                    { | 
			
		||||
 | 
				                        id:7, | 
			
		||||
 | 
				                        name: '列表页', | 
			
		||||
 | 
				                        icon: 'ListView', | 
			
		||||
 | 
				                        path: '/list', | 
			
		||||
 | 
				                        type: 1, | 
			
		||||
 | 
				                        children: [ | 
			
		||||
 | 
				                            { | 
			
		||||
 | 
				                                id:8, | 
			
		||||
 | 
				                                path: '/list/index', | 
			
		||||
 | 
				                                name: '列表页面', | 
			
		||||
 | 
				                                component: './pages/list/list', | 
			
		||||
 | 
				                                type: 1, | 
			
		||||
 | 
				                            }, | 
			
		||||
 | 
				                            { | 
			
		||||
 | 
				                                id:9, | 
			
		||||
 | 
				                                path: '/list/tree', | 
			
		||||
 | 
				                                name: '树形列表页面', | 
			
		||||
 | 
				                                component: './pages/list/tree', | 
			
		||||
 | 
				                                type: 1, | 
			
		||||
 | 
				                            }, | 
			
		||||
 | 
				                        ], | 
			
		||||
 | 
				                    }, | 
			
		||||
 | 
				                    { | 
			
		||||
 | 
				                        id:10, | 
			
		||||
 | 
				                        path: 'https://ant.design', | 
			
		||||
 | 
				                        name: 'Ant Design 官网外链', | 
			
		||||
 | 
				                        type: 3, | 
			
		||||
 | 
				                    }, | 
			
		||||
 | 
				                ] | 
			
		||||
 | 
				            } | 
			
		||||
 | 
				        } | 
			
		||||
 | 
				    } | 
			
		||||
 | 
				] as MockMethod[] | 
			
		||||
@ -0,0 +1,47 @@ | 
			
		|||||
 | 
				{ | 
			
		||||
 | 
				  "name": "pro", | 
			
		||||
 | 
				  "private": true, | 
			
		||||
 | 
				  "version": "0.0.0", | 
			
		||||
 | 
				  "type": "module", | 
			
		||||
 | 
				  "scripts": { | 
			
		||||
 | 
				    "dev": "vite --host --port 3000", | 
			
		||||
 | 
				    "build": "tsc && vite build", | 
			
		||||
 | 
				    "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", | 
			
		||||
 | 
				    "preview": "vite preview" | 
			
		||||
 | 
				  }, | 
			
		||||
 | 
				  "dependencies": { | 
			
		||||
 | 
				    "@ant-design/icons": "^5.3.6", | 
			
		||||
 | 
				    "@ant-design/pro-components": "^2.7.0", | 
			
		||||
 | 
				    "@formily/antd-v5": "^1.2.0", | 
			
		||||
 | 
				    "@formily/core": "^2.3.1", | 
			
		||||
 | 
				    "@formily/react": "^2.3.1", | 
			
		||||
 | 
				    "@icon-park/react": "^1.4.2", | 
			
		||||
 | 
				    "@tanstack/query-core": "^5.29.0", | 
			
		||||
 | 
				    "@tanstack/react-query": "^5.29.2", | 
			
		||||
 | 
				    "@tanstack/react-router": "^1.26.20", | 
			
		||||
 | 
				    "antd": "^5.16.1", | 
			
		||||
 | 
				    "axios": "^1.6.8", | 
			
		||||
 | 
				    "dayjs": "^1.11.10", | 
			
		||||
 | 
				    "jotai": "^2.8.0", | 
			
		||||
 | 
				    "jotai-tanstack-query": "^0.8.5", | 
			
		||||
 | 
				    "react": "^18.2.0", | 
			
		||||
 | 
				    "react-dom": "^18.2.0", | 
			
		||||
 | 
				    "wonka": "^6.3.4" | 
			
		||||
 | 
				  }, | 
			
		||||
 | 
				  "devDependencies": { | 
			
		||||
 | 
				    "@tanstack/router-devtools": "^1.26.20", | 
			
		||||
 | 
				    "@tanstack/router-vite-plugin": "^1.26.16", | 
			
		||||
 | 
				    "@types/react": "^18.2.66", | 
			
		||||
 | 
				    "@types/react-dom": "^18.2.22", | 
			
		||||
 | 
				    "@typescript-eslint/eslint-plugin": "^7.2.0", | 
			
		||||
 | 
				    "@typescript-eslint/parser": "^7.2.0", | 
			
		||||
 | 
				    "@vitejs/plugin-react": "^4.2.1", | 
			
		||||
 | 
				    "eslint": "^8.57.0", | 
			
		||||
 | 
				    "eslint-plugin-react-hooks": "^4.6.0", | 
			
		||||
 | 
				    "eslint-plugin-react-refresh": "^0.4.6", | 
			
		||||
 | 
				    "mockjs": "^1.1.0", | 
			
		||||
 | 
				    "typescript": "^5.2.2", | 
			
		||||
 | 
				    "vite": "^5.2.0", | 
			
		||||
 | 
				    "vite-plugin-mock": "^3.0.1" | 
			
		||||
 | 
				  } | 
			
		||||
 | 
				} | 
			
		||||
@ -0,0 +1 @@ | 
			
		|||||
 | 
				<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg> | 
			
		||||
@ -0,0 +1,20 @@ | 
			
		|||||
 | 
				.i-icon { | 
			
		||||
 | 
				    display: flex; | 
			
		||||
 | 
				} | 
			
		||||
 | 
				.ant-tree-iconEle{ | 
			
		||||
 | 
				    .i-icon { | 
			
		||||
 | 
				        display: inherit; | 
			
		||||
 | 
				        line-height: 28px; | 
			
		||||
 | 
				    } | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				} | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				.top-breadcrumb { | 
			
		||||
 | 
				    .item { | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				        display: flex; | 
			
		||||
 | 
				        align-items: center; | 
			
		||||
 | 
				        justify-content: center; | 
			
		||||
 | 
				        gap: 5px; | 
			
		||||
 | 
				    } | 
			
		||||
 | 
				} | 
			
		||||
@ -0,0 +1,11 @@ | 
			
		|||||
 | 
				import './App.css' | 
			
		||||
 | 
				import { RootProvider } from './routes.tsx' | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				function App() { | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				    return ( | 
			
		||||
 | 
				            <RootProvider/> | 
			
		||||
 | 
				    ) | 
			
		||||
 | 
				} | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				export default App | 
			
		||||
@ -0,0 +1,35 @@ | 
			
		|||||
 | 
				import React from 'react' | 
			
		||||
 | 
				import { Props } from './types' | 
			
		||||
 | 
				import { useAtomValue } from 'jotai' | 
			
		||||
 | 
				import { authAtom } from './store/user.ts' | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				export type AuthProps = Props & { | 
			
		||||
 | 
				    //是否渲染没有权限的组件
 | 
			
		||||
 | 
				    noAuth?: React.ReactNode | 
			
		||||
 | 
				    //权限key
 | 
			
		||||
 | 
				    authKey?: string[] | 
			
		||||
 | 
				} | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				export const Auth: React.FC<AuthProps> = (props) => { | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				    const auth = useAtomValue(authAtom) | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				    if (!auth.isLogin) { | 
			
		||||
 | 
				        return null | 
			
		||||
 | 
				    } | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				    if (props.authKey && props.authKey.length > 0) { | 
			
		||||
 | 
				        if (props.authKey.some(key => !auth.authKey?.includes(key))) { | 
			
		||||
 | 
				            return props.noAuth || null | 
			
		||||
 | 
				        } | 
			
		||||
 | 
				    } | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				    return ( | 
			
		||||
 | 
				            <> | 
			
		||||
 | 
				                {props!.children} | 
			
		||||
 | 
				            </> | 
			
		||||
 | 
				    ) | 
			
		||||
 | 
				} | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				export default Auth | 
			
		||||
@ -0,0 +1 @@ | 
			
		|||||
 | 
				<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="35.93" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 228"><path fill="#00D8FF" d="M210.483 73.824a171.49 171.49 0 0 0-8.24-2.597c.465-1.9.893-3.777 1.273-5.621c6.238-30.281 2.16-54.676-11.769-62.708c-13.355-7.7-35.196.329-57.254 19.526a171.23 171.23 0 0 0-6.375 5.848a155.866 155.866 0 0 0-4.241-3.917C100.759 3.829 77.587-4.822 63.673 3.233C50.33 10.957 46.379 33.89 51.995 62.588a170.974 170.974 0 0 0 1.892 8.48c-3.28.932-6.445 1.924-9.474 2.98C17.309 83.498 0 98.307 0 113.668c0 15.865 18.582 31.778 46.812 41.427a145.52 145.52 0 0 0 6.921 2.165a167.467 167.467 0 0 0-2.01 9.138c-5.354 28.2-1.173 50.591 12.134 58.266c13.744 7.926 36.812-.22 59.273-19.855a145.567 145.567 0 0 0 5.342-4.923a168.064 168.064 0 0 0 6.92 6.314c21.758 18.722 43.246 26.282 56.54 18.586c13.731-7.949 18.194-32.003 12.4-61.268a145.016 145.016 0 0 0-1.535-6.842c1.62-.48 3.21-.974 4.76-1.488c29.348-9.723 48.443-25.443 48.443-41.52c0-15.417-17.868-30.326-45.517-39.844Zm-6.365 70.984c-1.4.463-2.836.91-4.3 1.345c-3.24-10.257-7.612-21.163-12.963-32.432c5.106-11 9.31-21.767 12.459-31.957c2.619.758 5.16 1.557 7.61 2.4c23.69 8.156 38.14 20.213 38.14 29.504c0 9.896-15.606 22.743-40.946 31.14Zm-10.514 20.834c2.562 12.94 2.927 24.64 1.23 33.787c-1.524 8.219-4.59 13.698-8.382 15.893c-8.067 4.67-25.32-1.4-43.927-17.412a156.726 156.726 0 0 1-6.437-5.87c7.214-7.889 14.423-17.06 21.459-27.246c12.376-1.098 24.068-2.894 34.671-5.345a134.17 134.17 0 0 1 1.386 6.193ZM87.276 214.515c-7.882 2.783-14.16 2.863-17.955.675c-8.075-4.657-11.432-22.636-6.853-46.752a156.923 156.923 0 0 1 1.869-8.499c10.486 2.32 22.093 3.988 34.498 4.994c7.084 9.967 14.501 19.128 21.976 27.15a134.668 134.668 0 0 1-4.877 4.492c-9.933 8.682-19.886 14.842-28.658 17.94ZM50.35 144.747c-12.483-4.267-22.792-9.812-29.858-15.863c-6.35-5.437-9.555-10.836-9.555-15.216c0-9.322 13.897-21.212 37.076-29.293c2.813-.98 5.757-1.905 8.812-2.773c3.204 10.42 7.406 21.315 12.477 32.332c-5.137 11.18-9.399 22.249-12.634 32.792a134.718 134.718 0 0 1-6.318-1.979Zm12.378-84.26c-4.811-24.587-1.616-43.134 6.425-47.789c8.564-4.958 27.502 2.111 47.463 19.835a144.318 144.318 0 0 1 3.841 3.545c-7.438 7.987-14.787 17.08-21.808 26.988c-12.04 1.116-23.565 2.908-34.161 5.309a160.342 160.342 0 0 1-1.76-7.887Zm110.427 27.268a347.8 347.8 0 0 0-7.785-12.803c8.168 1.033 15.994 2.404 23.343 4.08c-2.206 7.072-4.956 14.465-8.193 22.045a381.151 381.151 0 0 0-7.365-13.322Zm-45.032-43.861c5.044 5.465 10.096 11.566 15.065 18.186a322.04 322.04 0 0 0-30.257-.006c4.974-6.559 10.069-12.652 15.192-18.18ZM82.802 87.83a323.167 323.167 0 0 0-7.227 13.238c-3.184-7.553-5.909-14.98-8.134-22.152c7.304-1.634 15.093-2.97 23.209-3.984a321.524 321.524 0 0 0-7.848 12.897Zm8.081 65.352c-8.385-.936-16.291-2.203-23.593-3.793c2.26-7.3 5.045-14.885 8.298-22.6a321.187 321.187 0 0 0 7.257 13.246c2.594 4.48 5.28 8.868 8.038 13.147Zm37.542 31.03c-5.184-5.592-10.354-11.779-15.403-18.433c4.902.192 9.899.29 14.978.29c5.218 0 10.376-.117 15.453-.343c-4.985 6.774-10.018 12.97-15.028 18.486Zm52.198-57.817c3.422 7.8 6.306 15.345 8.596 22.52c-7.422 1.694-15.436 3.058-23.88 4.071a382.417 382.417 0 0 0 7.859-13.026a347.403 347.403 0 0 0 7.425-13.565Zm-16.898 8.101a358.557 358.557 0 0 1-12.281 19.815a329.4 329.4 0 0 1-23.444.823c-7.967 0-15.716-.248-23.178-.732a310.202 310.202 0 0 1-12.513-19.846h.001a307.41 307.41 0 0 1-10.923-20.627a310.278 310.278 0 0 1 10.89-20.637l-.001.001a307.318 307.318 0 0 1 12.413-19.761c7.613-.576 15.42-.876 23.31-.876H128c7.926 0 15.743.303 23.354.883a329.357 329.357 0 0 1 12.335 19.695a358.489 358.489 0 0 1 11.036 20.54a329.472 329.472 0 0 1-11 20.722Zm22.56-122.124c8.572 4.944 11.906 24.881 6.52 51.026c-.344 1.668-.73 3.367-1.15 5.09c-10.622-2.452-22.155-4.275-34.23-5.408c-7.034-10.017-14.323-19.124-21.64-27.008a160.789 160.789 0 0 1 5.888-5.4c18.9-16.447 36.564-22.941 44.612-18.3ZM128 90.808c12.625 0 22.86 10.235 22.86 22.86s-10.235 22.86-22.86 22.86s-22.86-10.235-22.86-22.86s10.235-22.86 22.86-22.86Z"></path></svg> | 
			
		||||
@ -0,0 +1,74 @@ | 
			
		|||||
 | 
				import { Breadcrumb, BreadcrumbProps, Dropdown } from 'antd' | 
			
		||||
 | 
				import { Link, useNavigate } from '@tanstack/react-router' | 
			
		||||
 | 
				import { DownOutlined } from '@ant-design/icons' | 
			
		||||
 | 
				import { getIcon } from '@/components/icon' | 
			
		||||
 | 
				import { useCallback } from 'react' | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				export const PageBreadcrumb = (props: BreadcrumbProps & { | 
			
		||||
 | 
				    showIcon?: boolean; | 
			
		||||
 | 
				}) => { | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				    const nav = useNavigate() | 
			
		||||
 | 
				    const { items = [], showIcon = true, ...other } = props | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				    const renderIcon = useCallback((icon: any) => { | 
			
		||||
 | 
				        if (icon && showIcon) { | 
			
		||||
 | 
				            return getIcon(icon) | 
			
		||||
 | 
				        } | 
			
		||||
 | 
				        return null | 
			
		||||
 | 
				    }, []) | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				    const itemRender = (route) => { | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				        const isLast = route?.path === items[items.length - 1]?.path | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				        if (route.children) { | 
			
		||||
 | 
				            const items = route.children.map((item) => { | 
			
		||||
 | 
				                return { | 
			
		||||
 | 
				                    ...item, | 
			
		||||
 | 
				                    key: item.path, | 
			
		||||
 | 
				                    label: item.name, | 
			
		||||
 | 
				                } | 
			
		||||
 | 
				            }) | 
			
		||||
 | 
				            return ( | 
			
		||||
 | 
				                    <Dropdown menu={{ | 
			
		||||
 | 
				                        items, onClick: (e) => { | 
			
		||||
 | 
				                            nav({ | 
			
		||||
 | 
				                                to: e.key | 
			
		||||
 | 
				                            }) | 
			
		||||
 | 
				                        } | 
			
		||||
 | 
				                    }} | 
			
		||||
 | 
				                              trigger={[ 'hover' ]}> | 
			
		||||
 | 
				                        { | 
			
		||||
 | 
				                            !route.component ? <a className={'item'}>{renderIcon(route.icon)}<span>{route.name}</span> | 
			
		||||
 | 
				                                        <DownOutlined/></a> | 
			
		||||
 | 
				                                    : <Link to={`/${route.path}`} | 
			
		||||
 | 
				                                            className={'item'}>{renderIcon(route.icon)}<span>{route.name}</span> | 
			
		||||
 | 
				                                        <DownOutlined/> | 
			
		||||
 | 
				                                    </Link> | 
			
		||||
 | 
				                        } | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				                    </Dropdown> | 
			
		||||
 | 
				            ) | 
			
		||||
 | 
				        } | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				        return isLast ? ( | 
			
		||||
 | 
				                <span className={'item'}>{renderIcon(route.icon)}<span>{route.name}</span></span> | 
			
		||||
 | 
				        ) : ( | 
			
		||||
 | 
				                <Link to={`/${route.path}`} | 
			
		||||
 | 
				                      className={'item'}>{renderIcon(route.icon)}<span>{route.name}</span></Link> | 
			
		||||
 | 
				        ) | 
			
		||||
 | 
				    } | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				    return ( | 
			
		||||
 | 
				            <> | 
			
		||||
 | 
				                <Breadcrumb {...other} | 
			
		||||
 | 
				                            items={items} | 
			
		||||
 | 
				                            itemRender={itemRender} | 
			
		||||
 | 
				                /> | 
			
		||||
 | 
				            </> | 
			
		||||
 | 
				    ) | 
			
		||||
 | 
				} | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				export default PageBreadcrumb | 
			
		||||
@ -0,0 +1,64 @@ | 
			
		|||||
 | 
				import React, { ErrorInfo } from 'react' | 
			
		||||
 | 
				import { Button, Result } from 'antd' | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				export class ErrorBoundary extends React.Component< | 
			
		||||
 | 
				        Record<string, any>, | 
			
		||||
 | 
				        { hasError: boolean; errorInfo: string } | 
			
		||||
 | 
				> { | 
			
		||||
 | 
				    state = { hasError: false, errorInfo: '' } | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				    static getDerivedStateFromError(error: Error) { | 
			
		||||
 | 
				        return { hasError: true, errorInfo: error.message } | 
			
		||||
 | 
				    } | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				    componentDidCatch(error: any, errorInfo: ErrorInfo) { | 
			
		||||
 | 
				        // You can also log the error to an error reporting service
 | 
			
		||||
 | 
				        // eslint-disable-next-line no-console
 | 
			
		||||
 | 
				        console.log(error, errorInfo) | 
			
		||||
 | 
				    } | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				    render() { | 
			
		||||
 | 
				        if (this.state.hasError) { | 
			
		||||
 | 
				            // You can render any custom fallback UI
 | 
			
		||||
 | 
				            return ( | 
			
		||||
 | 
				                    <Result | 
			
		||||
 | 
				                            style={{ | 
			
		||||
 | 
				                                height: '100%', | 
			
		||||
 | 
				                                background: '#fff', | 
			
		||||
 | 
				                            }} | 
			
		||||
 | 
				                            title="错误信息" | 
			
		||||
 | 
				                            extra={ | 
			
		||||
 | 
				                                <> | 
			
		||||
 | 
				                                    <div | 
			
		||||
 | 
				                                            style={{ | 
			
		||||
 | 
				                                                maxWidth: 620, | 
			
		||||
 | 
				                                                textAlign: 'start', | 
			
		||||
 | 
				                                                backgroundColor: 'rgba(255,229,100,0.3)', | 
			
		||||
 | 
				                                                borderInlineStartColor: '#ffe564', | 
			
		||||
 | 
				                                                borderInlineStartWidth: '9px', | 
			
		||||
 | 
				                                                borderInlineStartStyle: 'solid', | 
			
		||||
 | 
				                                                padding: '20px 45px 20px 26px', | 
			
		||||
 | 
				                                                margin: 'auto', | 
			
		||||
 | 
				                                                marginBlockEnd: '30px', | 
			
		||||
 | 
				                                                marginBlockStart: '20px', | 
			
		||||
 | 
				                                            }} | 
			
		||||
 | 
				                                    > | 
			
		||||
 | 
				                                        <p>{this.state.errorInfo}</p> | 
			
		||||
 | 
				                                    </div> | 
			
		||||
 | 
				                                    <Button | 
			
		||||
 | 
				                                            danger | 
			
		||||
 | 
				                                            type="primary" | 
			
		||||
 | 
				                                            onClick={() => { | 
			
		||||
 | 
				                                                window.location.reload() | 
			
		||||
 | 
				                                            }} | 
			
		||||
 | 
				                                    > | 
			
		||||
 | 
				                                        刷新页面 | 
			
		||||
 | 
				                                    </Button> | 
			
		||||
 | 
				                                </> | 
			
		||||
 | 
				                            } | 
			
		||||
 | 
				                    /> | 
			
		||||
 | 
				            ) | 
			
		||||
 | 
				        } | 
			
		||||
 | 
				        return this.props.children | 
			
		||||
 | 
				    } | 
			
		||||
 | 
				} | 
			
		||||
@ -0,0 +1,52 @@ | 
			
		|||||
 | 
				import IconAll, { ALL_ICON_KEYS, IconType, IIconAllProps } from '@icon-park/react/es/all' | 
			
		||||
 | 
				import React, { Fragment } from 'react' | 
			
		||||
 | 
				import * as AntIcons from '@ant-design/icons/es/icons' | 
			
		||||
 | 
				import { IconComponentProps } from '@ant-design/icons/es/components/Icon' | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				export function Icon(props: Partial<IIconAllProps | IconComponentProps>) { | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				    const { type, ...other } = props | 
			
		||||
 | 
				    const AntIcon = AntIcons[type as keyof typeof AntIcons] | 
			
		||||
 | 
				    if (AntIcon) { | 
			
		||||
 | 
				        return <AntIcon {...other}/> | 
			
		||||
 | 
				    } | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				    //如果是http或https链接,直接返回图片
 | 
			
		||||
 | 
				    if (type && (type.startsWith('http') || type.startsWith('https') || type.startsWith('data:image'))) { | 
			
		||||
 | 
				        // @ts-ignore 没有办法把所有的属性都传递给img
 | 
			
		||||
 | 
				        return <img src={type} alt="icon" width={16} height={16} {...other}/> | 
			
		||||
 | 
				    } | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				    if (ALL_ICON_KEYS.indexOf(type as IconType) < 0) { | 
			
		||||
 | 
				        return null | 
			
		||||
 | 
				    } | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				    return ( | 
			
		||||
 | 
				            <Fragment> | 
			
		||||
 | 
				                <IconAll type={type as IconType} | 
			
		||||
 | 
				                         theme="outline" size="20" fill="#868686" strokeWidth={3} | 
			
		||||
 | 
				                         {...other}/> | 
			
		||||
 | 
				            </Fragment> | 
			
		||||
 | 
				    ) | 
			
		||||
 | 
				} | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				// eslint-disable-next-line react-refresh/only-export-components
 | 
			
		||||
 | 
				export const getIcon = (type: string, props?: Partial<IIconAllProps>) => { | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				    if(React.isValidElement(type)){ | 
			
		||||
 | 
				        return type | 
			
		||||
 | 
				    } | 
			
		||||
 | 
				    //判断是否为json格式
 | 
			
		||||
 | 
				    if (type && type.startsWith('{') && type.endsWith('}')) { | 
			
		||||
 | 
				        try { | 
			
		||||
 | 
				            const obj = JSON.parse(type) | 
			
		||||
 | 
				            type = obj.type | 
			
		||||
 | 
				            props = obj | 
			
		||||
 | 
				        } catch (e) { /* empty */ | 
			
		||||
 | 
				        } | 
			
		||||
 | 
				    } | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				    return <Icon type={type} {...props}/> | 
			
		||||
 | 
				} | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				export default Icon | 
			
		||||
@ -0,0 +1,23 @@ | 
			
		|||||
 | 
				import { atomFamily } from 'jotai/utils' | 
			
		||||
 | 
				import { IDataProps } from '@/types' | 
			
		||||
 | 
				import { useMemo, useRef } from 'react' | 
			
		||||
 | 
				import { generateUUID } from '@/utils/uuid' | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				export type FetchProps<T> = IDataProps<T> & { | 
			
		||||
 | 
				    key?: string | 
			
		||||
 | 
				    initialValue?: T | 
			
		||||
 | 
				} | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				const factoryAtom = atomFamily((param: FetchProps<any>) => param.initialValue) | 
			
		||||
 | 
				export const useInnerAtom = <T>(props: FetchProps<T>) => { | 
			
		||||
 | 
				    const { initialValue, key } = props | 
			
		||||
 | 
				    // 生成唯一的key
 | 
			
		||||
 | 
				    const _key = useRef(key) | 
			
		||||
 | 
				    if (!_key.current) { | 
			
		||||
 | 
				        _key.current = generateUUID() | 
			
		||||
 | 
				    } | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				    return useMemo(() => factoryAtom({ initialValue }), [ _key.current ]) | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				} | 
			
		||||
@ -0,0 +1,22 @@ | 
			
		|||||
 | 
				import React from 'react' | 
			
		||||
 | 
				import { createLazyRoute, Outlet } from '@tanstack/react-router' | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				interface IFormPageLayoutProps { | 
			
		||||
 | 
				    children: React.ReactNode | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				} | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				const FormPageLayout: React.FC<IFormPageLayoutProps> = (props) => { | 
			
		||||
 | 
				    return ( | 
			
		||||
 | 
				            <> | 
			
		||||
 | 
				                {props.children} | 
			
		||||
 | 
				                <Outlet/> | 
			
		||||
 | 
				            </> | 
			
		||||
 | 
				    ) | 
			
		||||
 | 
				} | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				export default FormPageLayout | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				export const GenRoute = (id: string) => createLazyRoute(id)({ | 
			
		||||
 | 
				    component: FormPageLayout, | 
			
		||||
 | 
				}) | 
			
		||||
@ -0,0 +1,21 @@ | 
			
		|||||
 | 
				import React from 'react' | 
			
		||||
 | 
				import { createLazyRoute, Outlet } from '@tanstack/react-router' | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				interface IListPageLayoutProps { | 
			
		||||
 | 
				    children: React.ReactNode | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				} | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				const ListPageLayout: React.FC<IListPageLayoutProps> = (props) => { | 
			
		||||
 | 
				    return ( | 
			
		||||
 | 
				            <>{props.children} | 
			
		||||
 | 
				                <Outlet/> | 
			
		||||
 | 
				            </> | 
			
		||||
 | 
				    ) | 
			
		||||
 | 
				} | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				export default ListPageLayout | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				export const GenRoute = (id: string) => createLazyRoute(id)({ | 
			
		||||
 | 
				    component: ListPageLayout, | 
			
		||||
 | 
				}) | 
			
		||||
@ -0,0 +1,144 @@ | 
			
		|||||
 | 
				import { | 
			
		||||
 | 
				    ProConfigProvider, | 
			
		||||
 | 
				    ProLayout, | 
			
		||||
 | 
				} from '@ant-design/pro-components' | 
			
		||||
 | 
				import { ConfigProvider, Dropdown } from 'antd' | 
			
		||||
 | 
				import { useState } from 'react' | 
			
		||||
 | 
				import defaultProps from './_defaultProps' | 
			
		||||
 | 
				import { Link, Outlet, useRouteContext } from '@tanstack/react-router' | 
			
		||||
 | 
				import Icon from '../components/icon' | 
			
		||||
 | 
				import { MenuItem } from '@/types' | 
			
		||||
 | 
				import PageBreadcrumb from '@/components/breadcrumb' | 
			
		||||
 | 
				import { ErrorBoundary } from '@/components/error-boundary' | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				//根据menuData生成Breadcrumb所需的数据
 | 
			
		||||
 | 
				const getBreadcrumbData = (menuData: MenuItem[], pathname: string) => { | 
			
		||||
 | 
				    const breadcrumbData: any[] = [] | 
			
		||||
 | 
				    const findItem = (menuData: any[], pathname: string) => { | 
			
		||||
 | 
				        for (let i = 0; i < menuData.length; i++) { | 
			
		||||
 | 
				            if (menuData[i].path === pathname) { | 
			
		||||
 | 
				                breadcrumbData.push(menuData[i]) | 
			
		||||
 | 
				                return true | 
			
		||||
 | 
				            } | 
			
		||||
 | 
				            if (menuData[i].children) { | 
			
		||||
 | 
				                if (findItem(menuData[i].children, pathname)) { | 
			
		||||
 | 
				                    breadcrumbData.push(menuData[i]) | 
			
		||||
 | 
				                    return true | 
			
		||||
 | 
				                } | 
			
		||||
 | 
				            } | 
			
		||||
 | 
				        } | 
			
		||||
 | 
				        return false | 
			
		||||
 | 
				    } | 
			
		||||
 | 
				    findItem(menuData, pathname) | 
			
		||||
 | 
				    return breadcrumbData.reverse() | 
			
		||||
 | 
				} | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				export default () => { | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				    const { menuData } = useRouteContext({ | 
			
		||||
 | 
				        from: undefined, | 
			
		||||
 | 
				        strict: false, | 
			
		||||
 | 
				        select: (state) => state | 
			
		||||
 | 
				    }) | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				    const items = getBreadcrumbData(menuData, location.pathname) | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				    const [ pathname, setPathname ] = useState(location.pathname) | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				    return ( | 
			
		||||
 | 
				            <div | 
			
		||||
 | 
				                    id="crazy-pro-layout" | 
			
		||||
 | 
				                    style={{ | 
			
		||||
 | 
				                        height: '100vh', | 
			
		||||
 | 
				                        // overflow: 'auto',
 | 
			
		||||
 | 
				                    }} | 
			
		||||
 | 
				            > | 
			
		||||
 | 
				                <ProConfigProvider hashed={false}> | 
			
		||||
 | 
				                    <ConfigProvider | 
			
		||||
 | 
				                            getTargetContainer={() => { | 
			
		||||
 | 
				                                return document.getElementById('crazy-pro-layout') || document.body | 
			
		||||
 | 
				                            }} | 
			
		||||
 | 
				                    > | 
			
		||||
 | 
				                        <ProLayout | 
			
		||||
 | 
				                                headerContentRender={() => <PageBreadcrumb | 
			
		||||
 | 
				                                        className={'top-breadcrumb'} | 
			
		||||
 | 
				                                        showIcon={false} | 
			
		||||
 | 
				                                        items={items}/>} | 
			
		||||
 | 
				                                title="Crazy Pro" | 
			
		||||
 | 
				                                {...defaultProps} | 
			
		||||
 | 
				                                route={{ | 
			
		||||
 | 
				                                    path: '/', | 
			
		||||
 | 
				                                    routes: menuData | 
			
		||||
 | 
				                                }} | 
			
		||||
 | 
				                                location={{ | 
			
		||||
 | 
				                                    pathname, | 
			
		||||
 | 
				                                }} | 
			
		||||
 | 
				                                token={{ | 
			
		||||
 | 
				                                    header: { | 
			
		||||
 | 
				                                        colorBgMenuItemSelected: 'rgba(0,0,0,0.04)', | 
			
		||||
 | 
				                                    }, | 
			
		||||
 | 
				                                }} | 
			
		||||
 | 
				                                menu={{ | 
			
		||||
 | 
				                                    collapsedShowGroupTitle: true, | 
			
		||||
 | 
				                                }} | 
			
		||||
 | 
				                                avatarProps={{ | 
			
		||||
 | 
				                                    src: 'https://gw.alipayobjects.com/zos/antfincdn/efFD%24IOql2/weixintupian_20170331104822.jpg', | 
			
		||||
 | 
				                                    size: 'small', | 
			
		||||
 | 
				                                    title: '管理员', | 
			
		||||
 | 
				                                    render: (_, dom) => { | 
			
		||||
 | 
				                                        return ( | 
			
		||||
 | 
				                                                <Dropdown | 
			
		||||
 | 
				                                                        menu={{ | 
			
		||||
 | 
				                                                            items: [ | 
			
		||||
 | 
				                                                                { | 
			
		||||
 | 
				                                                                    key: 'logout', | 
			
		||||
 | 
				                                                                    icon: <Icon type={'Logout'}/>, | 
			
		||||
 | 
				                                                                    label: '退出登录', | 
			
		||||
 | 
				                                                                }, | 
			
		||||
 | 
				                                                            ], | 
			
		||||
 | 
				                                                        }} | 
			
		||||
 | 
				                                                > | 
			
		||||
 | 
				                                                    {dom} | 
			
		||||
 | 
				                                                </Dropdown> | 
			
		||||
 | 
				                                        ) | 
			
		||||
 | 
				                                    }, | 
			
		||||
 | 
				                                }} | 
			
		||||
 | 
				                                actionsRender={(props) => { | 
			
		||||
 | 
				                                    if (props.isMobile) return [] | 
			
		||||
 | 
				                                    if (typeof window === 'undefined') return [] | 
			
		||||
 | 
				                                    return [] | 
			
		||||
 | 
				                                }} | 
			
		||||
 | 
				                                menuRender={(_, defaultDom) => ( | 
			
		||||
 | 
				                                        <> | 
			
		||||
 | 
				                                            {defaultDom} | 
			
		||||
 | 
				                                        </> | 
			
		||||
 | 
				                                )} | 
			
		||||
 | 
				                                menuItemRender={(item, dom) => { | 
			
		||||
 | 
				                                    return <div onClick={() => { | 
			
		||||
 | 
				                                        setPathname(item.path || '/welcome') | 
			
		||||
 | 
				                                    }} | 
			
		||||
 | 
				                                    > | 
			
		||||
 | 
				                                        <Link to={item.path} target={item.type === 3 ? '_blank': '_self' }> | 
			
		||||
 | 
				                                            {dom} | 
			
		||||
 | 
				                                        </Link> | 
			
		||||
 | 
				                                    </div> | 
			
		||||
 | 
				                                }} | 
			
		||||
 | 
				                                {...{ | 
			
		||||
 | 
				                                    'layout': 'mix', | 
			
		||||
 | 
				                                    'navTheme': 'light', | 
			
		||||
 | 
				                                    'contentWidth': 'Fluid', | 
			
		||||
 | 
				                                    'fixSiderbar': true, | 
			
		||||
 | 
				                                    'colorPrimary': '#1677FF', | 
			
		||||
 | 
				                                    'siderMenuType': 'group', | 
			
		||||
 | 
				                                    // layout: 'side',
 | 
			
		||||
 | 
				                                }} | 
			
		||||
 | 
				                                ErrorBoundary={ErrorBoundary} | 
			
		||||
 | 
				                        > | 
			
		||||
 | 
				                            <Outlet/> | 
			
		||||
 | 
				                        </ProLayout> | 
			
		||||
 | 
				                    </ConfigProvider> | 
			
		||||
 | 
				                </ProConfigProvider> | 
			
		||||
 | 
				            </div> | 
			
		||||
 | 
				    ) | 
			
		||||
 | 
				} | 
			
		||||
@ -0,0 +1,20 @@ | 
			
		|||||
 | 
				import React from 'react' | 
			
		||||
 | 
				import { createLazyRoute, Outlet } from '@tanstack/react-router' | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				interface ITreePageLayoutProps { | 
			
		||||
 | 
				    children: React.ReactNode | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				} | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				const TreePageLayout: React.FC<ITreePageLayoutProps> = (props) => { | 
			
		||||
 | 
				    return ( | 
			
		||||
 | 
				            <> | 
			
		||||
 | 
				                {props.children} | 
			
		||||
 | 
				                <Outlet/> | 
			
		||||
 | 
				            </> | 
			
		||||
 | 
				    ) | 
			
		||||
 | 
				} | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				export const GenRoute = (id: string) => createLazyRoute(id)({ | 
			
		||||
 | 
				    component: TreePageLayout, | 
			
		||||
 | 
				}) | 
			
		||||
@ -0,0 +1,69 @@ | 
			
		|||||
 | 
				export default { | 
			
		||||
 | 
				    route: { | 
			
		||||
 | 
				        path: '/', | 
			
		||||
 | 
				        routes: [], | 
			
		||||
 | 
				    }, | 
			
		||||
 | 
				    location: { | 
			
		||||
 | 
				        // pathname: '/',
 | 
			
		||||
 | 
				    }, | 
			
		||||
 | 
				    bgLayoutImgList: [ | 
			
		||||
 | 
				        { | 
			
		||||
 | 
				            src: 'https://img.alicdn.com/imgextra/i2/O1CN01O4etvp1DvpFLKfuWq_!!6000000000279-2-tps-609-606.png', | 
			
		||||
 | 
				            left: 85, | 
			
		||||
 | 
				            bottom: 100, | 
			
		||||
 | 
				            height: '303px', | 
			
		||||
 | 
				        }, | 
			
		||||
 | 
				        { | 
			
		||||
 | 
				            src: 'https://img.alicdn.com/imgextra/i2/O1CN01O4etvp1DvpFLKfuWq_!!6000000000279-2-tps-609-606.png', | 
			
		||||
 | 
				            bottom: -68, | 
			
		||||
 | 
				            right: -45, | 
			
		||||
 | 
				            height: '303px', | 
			
		||||
 | 
				        }, | 
			
		||||
 | 
				        { | 
			
		||||
 | 
				            src: 'https://img.alicdn.com/imgextra/i3/O1CN018NxReL1shX85Yz6Cx_!!6000000005798-2-tps-884-496.png', | 
			
		||||
 | 
				            bottom: 0, | 
			
		||||
 | 
				            left: 0, | 
			
		||||
 | 
				            width: '331px', | 
			
		||||
 | 
				        }, | 
			
		||||
 | 
				    ], | 
			
		||||
 | 
				    appList: [ | 
			
		||||
 | 
				        { | 
			
		||||
 | 
				            icon: 'https://gw.alipayobjects.com/zos/rmsportal/KDpgvguMpGfqaHPjicRK.svg', | 
			
		||||
 | 
				            title: 'Ant Design', | 
			
		||||
 | 
				            desc: '杭州市较知名的 UI 设计语言', | 
			
		||||
 | 
				            url: 'https://ant.design', | 
			
		||||
 | 
				        }, | 
			
		||||
 | 
				        { | 
			
		||||
 | 
				            icon: 'https://gw.alipayobjects.com/zos/antfincdn/FLrTNDvlna/antv.png', | 
			
		||||
 | 
				            title: 'AntV', | 
			
		||||
 | 
				            desc: '蚂蚁集团全新一代数据可视化解决方案', | 
			
		||||
 | 
				            url: 'https://antv.vision/', | 
			
		||||
 | 
				            target: '_blank', | 
			
		||||
 | 
				        }, | 
			
		||||
 | 
				        { | 
			
		||||
 | 
				            icon: 'https://img.alicdn.com/tfs/TB1zomHwxv1gK0jSZFFXXb0sXXa-200-200.png', | 
			
		||||
 | 
				            title: 'umi', | 
			
		||||
 | 
				            desc: '插件化的企业级前端应用框架。', | 
			
		||||
 | 
				            url: 'https://umijs.org/zh-CN/docs', | 
			
		||||
 | 
				        }, | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				        { | 
			
		||||
 | 
				            icon: 'https://gw.alipayobjects.com/zos/rmsportal/XuVpGqBFxXplzvLjJBZB.svg', | 
			
		||||
 | 
				            title: '语雀', | 
			
		||||
 | 
				            desc: '知识创作与分享工具', | 
			
		||||
 | 
				            url: 'https://www.yuque.com/', | 
			
		||||
 | 
				        }, | 
			
		||||
 | 
				        { | 
			
		||||
 | 
				            icon: 'https://gw.alipayobjects.com/zos/rmsportal/LFooOLwmxGLsltmUjTAP.svg', | 
			
		||||
 | 
				            title: 'Kitchen ', | 
			
		||||
 | 
				            desc: 'Sketch 工具集', | 
			
		||||
 | 
				            url: 'https://kitchen.alipay.com/', | 
			
		||||
 | 
				        }, | 
			
		||||
 | 
				        { | 
			
		||||
 | 
				            icon: 'https://gw.alipayobjects.com/zos/bmw-prod/d3e3eb39-1cd7-4aa5-827c-877deced6b7e/lalxt4g3_w256_h256.png', | 
			
		||||
 | 
				            title: 'dumi', | 
			
		||||
 | 
				            desc: '为组件开发场景而生的文档工具', | 
			
		||||
 | 
				            url: 'https://d.umijs.org/zh-CN', | 
			
		||||
 | 
				        }, | 
			
		||||
 | 
				    ], | 
			
		||||
 | 
				} | 
			
		||||
@ -0,0 +1,10 @@ | 
			
		|||||
 | 
				import ReactDOM from 'react-dom/client' | 
			
		||||
 | 
				import App from './App.tsx' | 
			
		||||
 | 
				import './index.css' | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				ReactDOM.createRoot(document.getElementById('root')!).render( | 
			
		||||
 | 
				        // <React.StrictMode>
 | 
			
		||||
 | 
				     <App/> | 
			
		||||
 | 
				        // </React.StrictMode>,
 | 
			
		||||
 | 
				) | 
			
		||||
@ -0,0 +1,25 @@ | 
			
		|||||
 | 
				 import { ProCard } from '@ant-design/pro-components' | 
			
		||||
 | 
				 import { createLazyRoute } from '@tanstack/react-router' | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				const Index = () => { | 
			
		||||
 | 
				    return ( | 
			
		||||
 | 
				            <> | 
			
		||||
 | 
				                <ProCard | 
			
		||||
 | 
				                        style={{ | 
			
		||||
 | 
				                            height: '100vh', | 
			
		||||
 | 
				                            minHeight: 800, | 
			
		||||
 | 
				                        }} | 
			
		||||
 | 
				                > | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				                    <h1>Dashboard</h1> | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				                </ProCard> | 
			
		||||
 | 
				            </> | 
			
		||||
 | 
				    ) | 
			
		||||
 | 
				} | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				 export const Route = createLazyRoute('/welcome')({ | 
			
		||||
 | 
				     component: Index, | 
			
		||||
 | 
				 }) | 
			
		||||
 | 
				export default Index | 
			
		||||
@ -0,0 +1,10 @@ | 
			
		|||||
 | 
				
 | 
			
		||||
 | 
				const Index = () => { | 
			
		||||
 | 
				    return ( | 
			
		||||
 | 
				            <div> | 
			
		||||
 | 
				           普通页面 | 
			
		||||
 | 
				            </div> | 
			
		||||
 | 
				    ) | 
			
		||||
 | 
				} | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				export default Index | 
			
		||||
@ -0,0 +1,15 @@ | 
			
		|||||
 | 
				import { createLazyRoute } from '@tanstack/react-router' | 
			
		||||
 | 
				import { ProCard } from '@ant-design/pro-components' | 
			
		||||
 | 
				const List = () => { | 
			
		||||
 | 
				    return ( | 
			
		||||
 | 
				            <ProCard> | 
			
		||||
 | 
				                列表页面 | 
			
		||||
 | 
				            </ProCard> | 
			
		||||
 | 
				    ) | 
			
		||||
 | 
				} | 
			
		||||
 | 
				export const Route = createLazyRoute('/list/index')({ | 
			
		||||
 | 
				    component: List, | 
			
		||||
 | 
				}) | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				export default List | 
			
		||||
@ -0,0 +1,146 @@ | 
			
		|||||
 | 
				import { LightFilter, PageContainer, ProCard, ProColumns, ProTable } from '@ant-design/pro-components' | 
			
		||||
 | 
				import { Tree, Input, Space, Button } from 'antd' | 
			
		||||
 | 
				import { createLazyRoute } from '@tanstack/react-router' | 
			
		||||
 | 
				import { departmentAtom } from '../../store/department.ts' | 
			
		||||
 | 
				import { useAtomValue } from 'jotai' | 
			
		||||
 | 
				import { getIcon } from '../../components/icon' | 
			
		||||
 | 
				import dayjs from 'dayjs' | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				//递归渲染树形结构,将name->title, id->key
 | 
			
		||||
 | 
				const renderTree = (data: any[]) => { | 
			
		||||
 | 
				    return data?.map((item) => { | 
			
		||||
 | 
				        if (item.children) { | 
			
		||||
 | 
				            return { | 
			
		||||
 | 
				                title: item.name, | 
			
		||||
 | 
				                key: item.id, | 
			
		||||
 | 
				                children: renderTree(item.children), | 
			
		||||
 | 
				            } | 
			
		||||
 | 
				        } | 
			
		||||
 | 
				        return { | 
			
		||||
 | 
				            title: item.name, | 
			
		||||
 | 
				            key: item.id, | 
			
		||||
 | 
				        } | 
			
		||||
 | 
				    }) | 
			
		||||
 | 
				} | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				const columns: ProColumns[] = [ | 
			
		||||
 | 
				    { | 
			
		||||
 | 
				        title: '姓名', | 
			
		||||
 | 
				        dataIndex: 'name', | 
			
		||||
 | 
				        render: (_) => <a>{_}</a>, | 
			
		||||
 | 
				        formItemProps: { | 
			
		||||
 | 
				            lightProps: { | 
			
		||||
 | 
				                labelFormatter: (value) => `app-${value}`, | 
			
		||||
 | 
				            }, | 
			
		||||
 | 
				        }, | 
			
		||||
 | 
				    }, | 
			
		||||
 | 
				    { | 
			
		||||
 | 
				        title: '帐号', | 
			
		||||
 | 
				        dataIndex: 'account', | 
			
		||||
 | 
				    }, | 
			
		||||
 | 
				    { | 
			
		||||
 | 
				        title: '创建者', | 
			
		||||
 | 
				        dataIndex: 'creator', | 
			
		||||
 | 
				        valueType: 'select', | 
			
		||||
 | 
				        search: false, | 
			
		||||
 | 
				        valueEnum: { | 
			
		||||
 | 
				            all: { text: '全部' }, | 
			
		||||
 | 
				            付小小: { text: '付小小' }, | 
			
		||||
 | 
				            曲丽丽: { text: '曲丽丽' }, | 
			
		||||
 | 
				            林东东: { text: '林东东' }, | 
			
		||||
 | 
				            陈帅帅: { text: '陈帅帅' }, | 
			
		||||
 | 
				            兼某某: { text: '兼某某' }, | 
			
		||||
 | 
				        }, | 
			
		||||
 | 
				    }, | 
			
		||||
 | 
				    //操作
 | 
			
		||||
 | 
				    { | 
			
		||||
 | 
				        title: '操作', | 
			
		||||
 | 
				        valueType: 'option', | 
			
		||||
 | 
				        render: (_, record) => { | 
			
		||||
 | 
				            return [ | 
			
		||||
 | 
				                <a key="editable" onClick={() => { | 
			
		||||
 | 
				                    alert('edit') | 
			
		||||
 | 
				                }}>编辑</a>, | 
			
		||||
 | 
				                <a key="delete" onClick={() => { | 
			
		||||
 | 
				                    alert('delete') | 
			
		||||
 | 
				                }}>删除</a>, | 
			
		||||
 | 
				            ] | 
			
		||||
 | 
				        } | 
			
		||||
 | 
				    }, | 
			
		||||
 | 
				] | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				const TreePage = () => { | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				    const { data, isError, isPending } = useAtomValue(departmentAtom) | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				    if (isError) { | 
			
		||||
 | 
				        return <div>Error</div> | 
			
		||||
 | 
				    } | 
			
		||||
 | 
				    // if (isPending){
 | 
			
		||||
 | 
				    //     return <div>Loading</div>
 | 
			
		||||
 | 
				    // }
 | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				    return ( | 
			
		||||
 | 
				            <PageContainer breadcrumbRender={false}> | 
			
		||||
 | 
				                <ProCard split="vertical"> | 
			
		||||
 | 
				                    <ProCard title="部门" | 
			
		||||
 | 
				                             colSpan="25%" | 
			
		||||
 | 
				                             loading={isPending} | 
			
		||||
 | 
				                             extra={<> | 
			
		||||
 | 
				                                 <Button size={'small'} icon={getIcon('Plus')} shape={'circle'}/> | 
			
		||||
 | 
				                             </>} | 
			
		||||
 | 
				                    > | 
			
		||||
 | 
				                        <Tree showLine={true} treeData={renderTree(data)}/> | 
			
		||||
 | 
				                    </ProCard> | 
			
		||||
 | 
				                    <ProCard headerBordered> | 
			
		||||
 | 
				                        <div style={{ height: 360 }}> | 
			
		||||
 | 
				                            <ProTable | 
			
		||||
 | 
				                                    rowKey="account" | 
			
		||||
 | 
				                                    headerTitle={'帐号列表'} | 
			
		||||
 | 
				                                    columns={columns} | 
			
		||||
 | 
				                                    dataSource={[ | 
			
		||||
 | 
				                                        { | 
			
		||||
 | 
				                                            name: '张三', | 
			
		||||
 | 
				                                            account: 'zhangsan', | 
			
		||||
 | 
				                                        }, | 
			
		||||
 | 
				                                        { | 
			
		||||
 | 
				                                            name: '李四', | 
			
		||||
 | 
				                                            account: 'lisi', | 
			
		||||
 | 
				                                        }, | 
			
		||||
 | 
				                                    ]} | 
			
		||||
 | 
				                                    // pagination={false}
 | 
			
		||||
 | 
				                                    options={{ | 
			
		||||
 | 
				                                        search: true, | 
			
		||||
 | 
				                                    }} | 
			
		||||
 | 
				                                    search={false} | 
			
		||||
 | 
				                                    toolbar={{ | 
			
		||||
 | 
				                                        search: { | 
			
		||||
 | 
				                                            onSearch: (value: string) => { | 
			
		||||
 | 
				                                                alert(value) | 
			
		||||
 | 
				                                            }, | 
			
		||||
 | 
				                                        }, | 
			
		||||
 | 
				                                        actions: [ | 
			
		||||
 | 
				                                            <Button | 
			
		||||
 | 
				                                                    key="primary" | 
			
		||||
 | 
				                                                    type="primary" | 
			
		||||
 | 
				                                                    onClick={() => { | 
			
		||||
 | 
				                                                        alert('add') | 
			
		||||
 | 
				                                                    }} | 
			
		||||
 | 
				                                            > | 
			
		||||
 | 
				                                                添加 | 
			
		||||
 | 
				                                            </Button>, | 
			
		||||
 | 
				                                        ], | 
			
		||||
 | 
				                                    }} | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				                            /> | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				                        </div> | 
			
		||||
 | 
				                    </ProCard> | 
			
		||||
 | 
				                </ProCard> | 
			
		||||
 | 
				            </PageContainer> | 
			
		||||
 | 
				    ) | 
			
		||||
 | 
				} | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				export default TreePage | 
			
		||||
@ -0,0 +1,11 @@ | 
			
		|||||
 | 
				import { PageContainer } from '@ant-design/pro-components' | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				const Departments = () => { | 
			
		||||
 | 
				    return ( | 
			
		||||
 | 
				            <PageContainer breadcrumbRender={false}> | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				            </PageContainer> | 
			
		||||
 | 
				    ) | 
			
		||||
 | 
				} | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				export default Departments | 
			
		||||
@ -0,0 +1,60 @@ | 
			
		|||||
 | 
				import { PageContainer, ProCard } from '@ant-design/pro-components' | 
			
		||||
 | 
				import { Button, Space, Tree } from 'antd' | 
			
		||||
 | 
				import { useAtom, useAtomValue } from 'jotai' | 
			
		||||
 | 
				import { menuDataAtom, selectedMenuAtom, selectedMenuIdAtom } from '@/store/system.ts' | 
			
		||||
 | 
				import { formatterMenuData } from '@/utils/uuid.ts' | 
			
		||||
 | 
				import { CloseOutlined, PlusOutlined } from '@ant-design/icons' | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				const Menus = () => { | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				    const { data, isLoading } = useAtomValue(menuDataAtom) | 
			
		||||
 | 
				    const [ currentMenu, setCurrentMenu ] = useAtom(selectedMenuAtom) | 
			
		||||
 | 
				    const [ selectedKey, setSelectedKey ] = useAtom(selectedMenuIdAtom) | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				    const treeData = formatterMenuData(data!) | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				    return ( | 
			
		||||
 | 
				            <PageContainer | 
			
		||||
 | 
				                    breadcrumbRender={false} title={false}> | 
			
		||||
 | 
				                <ProCard split={'vertical'} | 
			
		||||
 | 
				                         style={{ | 
			
		||||
 | 
				                             height: 'calc(100vh - 164px)', | 
			
		||||
 | 
				                             overflow: 'auto', | 
			
		||||
 | 
				                         }} | 
			
		||||
 | 
				                > | 
			
		||||
 | 
				                    <ProCard title={'导航'} | 
			
		||||
 | 
				                             colSpan={'350px'} | 
			
		||||
 | 
				                             extra={ | 
			
		||||
 | 
				                                 <Space> | 
			
		||||
 | 
				                                     <Button type="primary" size={'small'} icon={<PlusOutlined/>} shape={'circle'}/> | 
			
		||||
 | 
				                                     <Button type="default" danger={true} size={'small'} icon={<CloseOutlined/>} | 
			
		||||
 | 
				                                             shape={'circle'}/> | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				                                 </Space> | 
			
		||||
 | 
				                             } | 
			
		||||
 | 
				                             loading={isLoading}> | 
			
		||||
 | 
				                        <Tree treeData={treeData} | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				                              onSelect={(item) => { | 
			
		||||
 | 
				                                  setSelectedKey(item[0]) | 
			
		||||
 | 
				                                  setCurrentMenu( data?.find((menu) => menu.id === item[0])!) | 
			
		||||
 | 
				                              }} | 
			
		||||
 | 
				                              checkable={true} showIcon={true}/> | 
			
		||||
 | 
				                    </ProCard> | 
			
		||||
 | 
				                    <ProCard title={'配置'}> | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				                        {selectedKey} | 
			
		||||
 | 
				                        { | 
			
		||||
 | 
				                            JSON.stringify(currentMenu) | 
			
		||||
 | 
				                        } | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				                    </ProCard> | 
			
		||||
 | 
				                    <ProCard title={'按钮'} colSpan={7}> | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				                    </ProCard> | 
			
		||||
 | 
				                </ProCard> | 
			
		||||
 | 
				            </PageContainer> | 
			
		||||
 | 
				    ) | 
			
		||||
 | 
				} | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				export default Menus | 
			
		||||
@ -0,0 +1,11 @@ | 
			
		|||||
 | 
				import { PageContainer } from '@ant-design/pro-components' | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				const Roles = () => { | 
			
		||||
 | 
				    return ( | 
			
		||||
 | 
				            <PageContainer breadcrumbRender={false}> | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				            </PageContainer> | 
			
		||||
 | 
				    ) | 
			
		||||
 | 
				} | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				export default Roles | 
			
		||||
@ -0,0 +1,11 @@ | 
			
		|||||
 | 
				import { PageContainer } from '@ant-design/pro-components' | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				const Users = () => { | 
			
		||||
 | 
				    return ( | 
			
		||||
 | 
				            <PageContainer breadcrumbRender={false}> | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				            </PageContainer> | 
			
		||||
 | 
				    ) | 
			
		||||
 | 
				} | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				export default Users | 
			
		||||
@ -0,0 +1,17 @@ | 
			
		|||||
 | 
				import axios from 'axios' | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				export const request = axios.create({ | 
			
		||||
 | 
				    baseURL: '/api', | 
			
		||||
 | 
				    timeout: 1000, | 
			
		||||
 | 
				    headers: { | 
			
		||||
 | 
				        'Content-Type': 'application/json', | 
			
		||||
 | 
				    }, | 
			
		||||
 | 
				}) | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				//拦截response,返回data
 | 
			
		||||
 | 
				request.interceptors.response.use((response) => { | 
			
		||||
 | 
				    // console.log('response', response.data)
 | 
			
		||||
 | 
				    return response.data | 
			
		||||
 | 
				}) | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				export default request | 
			
		||||
@ -0,0 +1,149 @@ | 
			
		|||||
 | 
				import { | 
			
		||||
 | 
				    createRouter, | 
			
		||||
 | 
				    createRoute, | 
			
		||||
 | 
				    RouterProvider, AnyRoute, redirect, createRootRouteWithContext, createLazyRoute, | 
			
		||||
 | 
				} from '@tanstack/react-router' | 
			
		||||
 | 
				import { TanStackRouterDevtools } from '@tanstack/router-devtools' | 
			
		||||
 | 
				import RootLayout from './layout/RootLayout' | 
			
		||||
 | 
				import ListPageLayout from '@/layout/ListPageLayout.tsx' | 
			
		||||
 | 
				import { QueryClient, QueryClientProvider } from '@tanstack/react-query' | 
			
		||||
 | 
				import { useAtomValue } from 'jotai' | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				import { IRootContext, MenuItem } from './types' | 
			
		||||
 | 
				import { menuDataAtom } from './store/system.ts' | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				export const queryClient = new QueryClient({ | 
			
		||||
 | 
				    defaultOptions: { | 
			
		||||
 | 
				        queries: { | 
			
		||||
 | 
				            retry: false, | 
			
		||||
 | 
				        } | 
			
		||||
 | 
				    } | 
			
		||||
 | 
				}) | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				const rootRoute = createRootRouteWithContext<IRootContext>()({ | 
			
		||||
 | 
				    component: () => ( | 
			
		||||
 | 
				            <> | 
			
		||||
 | 
				                <RootLayout/> | 
			
		||||
 | 
				                <TanStackRouterDevtools position={'bottom-right'}/> | 
			
		||||
 | 
				            </> | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				    ), | 
			
		||||
 | 
				    beforeLoad: ({ location }) => { | 
			
		||||
 | 
				        if (location.pathname === '/') { | 
			
		||||
 | 
				            return redirect({ to: '/welcome' }) | 
			
		||||
 | 
				        } | 
			
		||||
 | 
				    }, | 
			
		||||
 | 
				    notFoundComponent: () => <div>404 Not Found</div>, | 
			
		||||
 | 
				}) | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				const generateDynamicRoutes = (menuData: MenuItem[]) => { | 
			
		||||
 | 
				    // 递归生成路由,如果有routes则递归生成子路由
 | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				    const generateRoutes = (menu: MenuItem, parentRoute: AnyRoute) => { | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				        const path = menu.path?.replace(parentRoute.options?.path, '') | 
			
		||||
 | 
				        const isLayout = menu.children && menu.children.length > 0 && menu.type === 1 | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				        if (isLayout && !menu.component) { | 
			
		||||
 | 
				            //没有component的layout,直接返回
 | 
			
		||||
 | 
				            return createRoute({ | 
			
		||||
 | 
				                getParentRoute: () => parentRoute, | 
			
		||||
 | 
				                id: path!, | 
			
		||||
 | 
				                component: ListPageLayout, | 
			
		||||
 | 
				            }) | 
			
		||||
 | 
				        } | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				        // @ts-ignore 添加menu属性,方便后面获取
 | 
			
		||||
 | 
				        const options = { | 
			
		||||
 | 
				            getParentRoute: () => parentRoute, | 
			
		||||
 | 
				            menu, | 
			
		||||
 | 
				        } as any | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				        if (isLayout) { | 
			
		||||
 | 
				            options.id = path! | 
			
		||||
 | 
				        } else { | 
			
		||||
 | 
				            options.path = path! | 
			
		||||
 | 
				        } | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				        //删除掉parentRoute的path,避免重复
 | 
			
		||||
 | 
				        const route = createRoute(options).lazy(async () => { | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				            // @ts-ignore 获取route中的menu属性
 | 
			
		||||
 | 
				            const menu = route.options.menu as MenuItem | 
			
		||||
 | 
				            let component = menu.component | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				            // menu.type
 | 
			
		||||
 | 
				            // 1,组件(页面),2,IFrame,3,外链接,4,按钮
 | 
			
		||||
 | 
				            if (menu.type === 2) { | 
			
		||||
 | 
				                component = '@/components/Iframe' | 
			
		||||
 | 
				            } | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				            if (!component) { | 
			
		||||
 | 
				                return createLazyRoute(menu.path)({ | 
			
		||||
 | 
				                    component: () => <div>404 Not Found</div> | 
			
		||||
 | 
				                }) | 
			
		||||
 | 
				            } | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				            const d = await import(`${component}`) | 
			
		||||
 | 
				            if (d.Route) { | 
			
		||||
 | 
				                return d.Route | 
			
		||||
 | 
				            } | 
			
		||||
 | 
				            if (d.GenRoute) { | 
			
		||||
 | 
				                return d.GenRoute(menu.path) | 
			
		||||
 | 
				            } | 
			
		||||
 | 
				            return createLazyRoute(menu.path)({ | 
			
		||||
 | 
				                component: d.default || d | 
			
		||||
 | 
				            }) | 
			
		||||
 | 
				        }) | 
			
		||||
 | 
				        return route | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				    } | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				    // 对menuData递归生成路由,只处理type =1 的菜单
 | 
			
		||||
 | 
				    const did = (menus: MenuItem[], parentRoute: AnyRoute) => { | 
			
		||||
 | 
				        return menus.filter((item) => item.type === 1).map((item) => { | 
			
		||||
 | 
				            // 如果有children则递归生成子路由,同样只处理type =1 的菜单
 | 
			
		||||
 | 
				            const route = generateRoutes(item, parentRoute) | 
			
		||||
 | 
				            if (item.children && item.children.length > 0) { | 
			
		||||
 | 
				                const children = did(item.children, route) | 
			
		||||
 | 
				                if (children.length > 0) { | 
			
		||||
 | 
				                    route.addChildren(children) | 
			
		||||
 | 
				                } | 
			
		||||
 | 
				            } | 
			
		||||
 | 
				            return route | 
			
		||||
 | 
				        }) | 
			
		||||
 | 
				    } | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				    return did(menuData, rootRoute) | 
			
		||||
 | 
				} | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				export const RootProvider = () => { | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				    const { data, isError, isPending } = useAtomValue(menuDataAtom) | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				    if (isError) { | 
			
		||||
 | 
				        return <div>Error</div> | 
			
		||||
 | 
				    } | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				    if (isPending) { | 
			
		||||
 | 
				        return <div>Loading...</div> | 
			
		||||
 | 
				    } | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				    const dynamicRoutes = generateDynamicRoutes(data!) | 
			
		||||
 | 
				    const routeTree = rootRoute.addChildren(dynamicRoutes) | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				    const router = createRouter({ | 
			
		||||
 | 
				        routeTree, | 
			
		||||
 | 
				        context: { queryClient, menuData: data }, | 
			
		||||
 | 
				        defaultPreload: 'intent' | 
			
		||||
 | 
				    }) | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				    return ( | 
			
		||||
 | 
				            <QueryClientProvider client={queryClient}> | 
			
		||||
 | 
				                <RouterProvider router={router}/> | 
			
		||||
 | 
				            </QueryClientProvider> | 
			
		||||
 | 
				    ) | 
			
		||||
 | 
				} | 
			
		||||
@ -0,0 +1,25 @@ | 
			
		|||||
 | 
				import request from '../request.ts' | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				const systemServ = { | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				    menus: { | 
			
		||||
 | 
				        list: () => { | 
			
		||||
 | 
				            return request.get('/menus') | 
			
		||||
 | 
				        }, | 
			
		||||
 | 
				        add: (data: any) => { | 
			
		||||
 | 
				            return request.post('/menus', data) | 
			
		||||
 | 
				        }, | 
			
		||||
 | 
				        update: (id: number| string, data: any) => { | 
			
		||||
 | 
				            return request.put(`/menus/${id}`, data) | 
			
		||||
 | 
				        }, | 
			
		||||
 | 
				        delete: (id: number| string) => { | 
			
		||||
 | 
				            return request.delete(`/menus/${id}`) | 
			
		||||
 | 
				        }, | 
			
		||||
 | 
				        info: (id: number| string) => { | 
			
		||||
 | 
				            return request.get(`/menus/${id}`) | 
			
		||||
 | 
				        } | 
			
		||||
 | 
				    } | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				} | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				export default systemServ | 
			
		||||
@ -0,0 +1,44 @@ | 
			
		|||||
 | 
				import { atom } from 'jotai' | 
			
		||||
 | 
				import { IDepartment } from './types/department' | 
			
		||||
 | 
				import { QueryClient } from '@tanstack/query-core' | 
			
		||||
 | 
				import { atomWithMutation, atomWithQuery } from 'jotai-tanstack-query' | 
			
		||||
 | 
				import { IApiResult } from '../types' | 
			
		||||
 | 
				import request from '../request.ts' | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				export const departmentSearchAtom = atom<Partial<IDepartment>>({}) | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				export const departmentAtom = atomWithQuery<IApiResult<IDepartment[]>, any, IDepartment[]>((get) => ({ | 
			
		||||
 | 
				    queryKey: [ 'departments', get(departmentSearchAtom) ], | 
			
		||||
 | 
				    queryFn: async ({ queryKey: [ , departSearch ] }) => { | 
			
		||||
 | 
				        await new Promise(resolve => setTimeout(resolve, 5000)) | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				        return await request('/departments', { | 
			
		||||
 | 
				            data: departSearch, | 
			
		||||
 | 
				        }) | 
			
		||||
 | 
				    }, | 
			
		||||
 | 
				    select: data => data.data, | 
			
		||||
 | 
				})) | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				export const departmentDetailAtom = atomWithQuery<IApiResult<IDepartment>>((get) => ({ | 
			
		||||
 | 
				    queryKey: [ 'department', get(departmentSearchAtom) ], | 
			
		||||
 | 
				    queryFn: async ({ queryKey: [ , departSearch ] }) => { | 
			
		||||
 | 
				        return await request(`/departments/${(departSearch as IDepartment).id}`) | 
			
		||||
 | 
				    }, | 
			
		||||
 | 
				    select: data => data, | 
			
		||||
 | 
				})) | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				//add use atomWithMutation
 | 
			
		||||
 | 
				export const departmentAddAtom = (client: QueryClient) => atomWithMutation<IApiResult<IDepartment>, any, IDepartment>(() => ({ | 
			
		||||
 | 
				    mutationKey: [ 'addDepartment', ], | 
			
		||||
 | 
				    mutationFn: async (depart: IDepartment) => { | 
			
		||||
 | 
				        return await request.post('/departments', depart) | 
			
		||||
 | 
				    }, | 
			
		||||
 | 
				    onSuccess: (result) => { | 
			
		||||
 | 
				        console.log(result) | 
			
		||||
 | 
				    }, | 
			
		||||
 | 
				    onSettled: () => { | 
			
		||||
 | 
				        //清空列表的缓存
 | 
			
		||||
 | 
				        void client.invalidateQueries({ queryKey: [ 'departments' ] }) | 
			
		||||
 | 
				    } | 
			
		||||
 | 
				})) | 
			
		||||
@ -0,0 +1,46 @@ | 
			
		|||||
 | 
				import { atomWithQuery } from 'jotai-tanstack-query' | 
			
		||||
 | 
				import systemServ from '../service/system.ts' | 
			
		||||
 | 
				import { MenuItem } from '../types' | 
			
		||||
 | 
				import { getIcon } from '../components/icon' | 
			
		||||
 | 
				import { atom } from 'jotai/index' | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				// 格式化菜单数据, 把children转换成routes
 | 
			
		||||
 | 
				export const formatMenuData = (data: MenuItem[]) => { | 
			
		||||
 | 
				    const result: MenuItem[] = [] | 
			
		||||
 | 
				    for (const item of data) { | 
			
		||||
 | 
				        if (item.icon && typeof item.icon === 'string') { | 
			
		||||
 | 
				            item.icon = getIcon(item.icon as string, { size: '14', theme: 'filled' }) | 
			
		||||
 | 
				        } | 
			
		||||
 | 
				        if (!item.children || !item.children.length) { | 
			
		||||
 | 
				            result.push(item) | 
			
		||||
 | 
				        } else { | 
			
		||||
 | 
				            const { children, ...other } = item | 
			
		||||
 | 
				            result.push({ | 
			
		||||
 | 
				                ...other, | 
			
		||||
 | 
				                children, | 
			
		||||
 | 
				                routes: formatMenuData(children), | 
			
		||||
 | 
				            }) | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				        } | 
			
		||||
 | 
				    } | 
			
		||||
 | 
				    return result | 
			
		||||
 | 
				} | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				export const menuDataAtom = atomWithQuery(() => ({ | 
			
		||||
 | 
				    queryKey: [ 'menus' ], | 
			
		||||
 | 
				    queryFn: async () => { | 
			
		||||
 | 
				        return await systemServ.menus.list() | 
			
		||||
 | 
				    }, | 
			
		||||
 | 
				    select: data => formatMenuData(data.data ?? []), | 
			
		||||
 | 
				})) | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				export const selectedMenuIdAtom = atom<string | number>(0) | 
			
		||||
 | 
				export const selectedMenuAtom = atom<MenuItem>({}) | 
			
		||||
 | 
				export const byIdMenuAtom = atomWithQuery((get) => ({ | 
			
		||||
 | 
				    queryKey: [ 'selectedMenu', get(selectedMenuIdAtom) ], | 
			
		||||
 | 
				    queryFn: async ({ queryKey: [ , id ] }) => { | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				        return await systemServ.menus.info(id) | 
			
		||||
 | 
				    }, | 
			
		||||
 | 
				    select: data => data.data, | 
			
		||||
 | 
				})) | 
			
		||||
@ -0,0 +1,9 @@ | 
			
		|||||
 | 
				
 | 
			
		||||
 | 
				export interface IDepartment { | 
			
		||||
 | 
				    id: string | 
			
		||||
 | 
				    name: string | 
			
		||||
 | 
				    parentId: number | 
			
		||||
 | 
				    order: number | 
			
		||||
 | 
				    createAt: string | 
			
		||||
 | 
				    updateAt: string | 
			
		||||
 | 
				} | 
			
		||||
@ -0,0 +1,8 @@ | 
			
		|||||
 | 
				import { atom } from 'jotai/index' | 
			
		||||
 | 
				import { IAuth } from '../types' | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				export const authAtom = atom<IAuth>({ | 
			
		||||
 | 
				    isLogin: false, | 
			
		||||
 | 
				    authKey: [] | 
			
		||||
 | 
				}) | 
			
		||||
 | 
				
 | 
			
		||||
@ -0,0 +1,66 @@ | 
			
		|||||
 | 
				import { Attributes, ReactNode } from 'react' | 
			
		||||
 | 
				import { QueryClient } from '@tanstack/react-query' | 
			
		||||
 | 
				import { Router } from '@tanstack/react-router' | 
			
		||||
 | 
				import { RouteOptions } from '@tanstack/react-router/src/route.ts' | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				export type LayoutType = 'list' | 'form' | 'tree' | 'normal' | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				export type TRouter = { | 
			
		||||
 | 
				    router: Router & { | 
			
		||||
 | 
				        context: IRootContext | 
			
		||||
 | 
				    } | 
			
		||||
 | 
				} | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				export type IApiResult<T = any> = { | 
			
		||||
 | 
				    code: number; | 
			
		||||
 | 
				    data: T; | 
			
		||||
 | 
				    message: string; | 
			
		||||
 | 
				} | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				export type IDataProps<T = any> = { | 
			
		||||
 | 
				    value?: T; | 
			
		||||
 | 
				    onChange?: (value: T) => void; | 
			
		||||
 | 
				} | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				export type Props = Attributes & { | 
			
		||||
 | 
				    children?: ReactNode | 
			
		||||
 | 
				}; | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				export interface IRootContext { | 
			
		||||
 | 
				    menuData: MenuItem[]; | 
			
		||||
 | 
				    queryClient: QueryClient; | 
			
		||||
 | 
				} | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				interface MenuItem { | 
			
		||||
 | 
				    id?: number | string; | 
			
		||||
 | 
				    key: string; | 
			
		||||
 | 
				    title: string; | 
			
		||||
 | 
				    name: string; | 
			
		||||
 | 
				    path?: string; | 
			
		||||
 | 
				    icon?: string | ReactNode; | 
			
		||||
 | 
				    component?: string; | 
			
		||||
 | 
				    type: number | string; | 
			
		||||
 | 
				    order: number; | 
			
		||||
 | 
				    hideInMenu?: boolean; | 
			
		||||
 | 
				    children?: MenuItem[]; | 
			
		||||
 | 
				    routes?: MenuItem[]; | 
			
		||||
 | 
				} | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				interface IAuth { | 
			
		||||
 | 
				    isLogin: boolean; | 
			
		||||
 | 
				    authKey?: string[]; | 
			
		||||
 | 
				} | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				declare module '@tanstack/react-router' { | 
			
		||||
 | 
				    interface Register { | 
			
		||||
 | 
				        router: TRouter | 
			
		||||
 | 
				    } | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				    interface AnyRoute { | 
			
		||||
 | 
				        options: RouteOptions & { | 
			
		||||
 | 
				            menu: MenuItem | 
			
		||||
 | 
				        } | 
			
		||||
 | 
				    } | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				} | 
			
		||||
@ -0,0 +1,33 @@ | 
			
		|||||
 | 
				import { MenuItem } from '@/types' | 
			
		||||
 | 
				import { TreeDataNode } from 'antd' | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				export function generateUUID() { | 
			
		||||
 | 
				    if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') { | 
			
		||||
 | 
				        return crypto.randomUUID(); | 
			
		||||
 | 
				    } else { | 
			
		||||
 | 
				        return Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15); | 
			
		||||
 | 
				    } | 
			
		||||
 | 
				} | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				//把MenuItem[]转换成antd树形结构
 | 
			
		||||
 | 
				export const formatterMenuData = (data: MenuItem[]): TreeDataNode[] => { | 
			
		||||
 | 
				    const result: TreeDataNode[] = [] | 
			
		||||
 | 
				    for (const item of data) { | 
			
		||||
 | 
				        if (item.children && item.children.length) { | 
			
		||||
 | 
				            const { children, ...other } = item | 
			
		||||
 | 
				            result.push({ | 
			
		||||
 | 
				                ...other, | 
			
		||||
 | 
				                key: item.id!, | 
			
		||||
 | 
				                title: item.name!, | 
			
		||||
 | 
				                children: formatterMenuData(children), | 
			
		||||
 | 
				            }) | 
			
		||||
 | 
				        } else { | 
			
		||||
 | 
				            result.push({ | 
			
		||||
 | 
				                ...item, | 
			
		||||
 | 
				                key: item.id!, | 
			
		||||
 | 
				                title: item.name!, | 
			
		||||
 | 
				            }) | 
			
		||||
 | 
				        } | 
			
		||||
 | 
				    } | 
			
		||||
 | 
				    return result | 
			
		||||
 | 
				} | 
			
		||||
@ -0,0 +1 @@ | 
			
		|||||
 | 
				/// <reference types="vite/client" />
 | 
			
		||||
@ -0,0 +1,34 @@ | 
			
		|||||
 | 
				{ | 
			
		||||
 | 
				  "compilerOptions": { | 
			
		||||
 | 
				    "target": "ES2020", | 
			
		||||
 | 
				    "useDefineForClassFields": true, | 
			
		||||
 | 
				    "lib": ["ES2020", "DOM", "DOM.Iterable"], | 
			
		||||
 | 
				    "module": "ESNext", | 
			
		||||
 | 
				    "skipLibCheck": true, | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				    /* Bundler mode */ | 
			
		||||
 | 
				    "moduleResolution": "bundler", | 
			
		||||
 | 
				    "allowImportingTsExtensions": true, | 
			
		||||
 | 
				    "resolveJsonModule": true, | 
			
		||||
 | 
				    "isolatedModules": true, | 
			
		||||
 | 
				    "noEmit": true, | 
			
		||||
 | 
				    "jsx": "react-jsx", | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				    /* Linting */ | 
			
		||||
 | 
				    "noImplicitAny": false, | 
			
		||||
 | 
				    "strict": true, | 
			
		||||
 | 
				    "noUnusedLocals": true, | 
			
		||||
 | 
				    "noUnusedParameters": true, | 
			
		||||
 | 
				    "noFallthroughCasesInSwitch": true, | 
			
		||||
 | 
				    "allowSyntheticDefaultImports": true, | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				    /* Type checking */ | 
			
		||||
 | 
				    "strictNullChecks": true, | 
			
		||||
 | 
				    "files": [ "src/**/*.d.ts"], | 
			
		||||
 | 
				    "paths": { | 
			
		||||
 | 
				      "@/*": ["src/*"] | 
			
		||||
 | 
				    } | 
			
		||||
 | 
				  }, | 
			
		||||
 | 
				  "include": ["src"], | 
			
		||||
 | 
				  "references": [{ "path": "./tsconfig.node.json" }] | 
			
		||||
 | 
				} | 
			
		||||
@ -0,0 +1,11 @@ | 
			
		|||||
 | 
				{ | 
			
		||||
 | 
				  "compilerOptions": { | 
			
		||||
 | 
				    "composite": true, | 
			
		||||
 | 
				    "skipLibCheck": true, | 
			
		||||
 | 
				    "module": "ESNext", | 
			
		||||
 | 
				    "moduleResolution": "bundler", | 
			
		||||
 | 
				    "allowSyntheticDefaultImports": true, | 
			
		||||
 | 
				    "strict": true | 
			
		||||
 | 
				  }, | 
			
		||||
 | 
				  "include": ["vite.config.ts"] | 
			
		||||
 | 
				} | 
			
		||||
@ -0,0 +1,27 @@ | 
			
		|||||
 | 
				import { defineConfig } from 'vite' | 
			
		||||
 | 
				import react from '@vitejs/plugin-react' | 
			
		||||
 | 
				import { viteMockServe } from 'vite-plugin-mock' | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				//import { TanStackRouterVite } from '@tanstack/router-vite-plugin'
 | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				// https://vitejs.dev/config/
 | 
			
		||||
 | 
				export default defineConfig({ | 
			
		||||
 | 
				    //定义别名的路径
 | 
			
		||||
 | 
				    resolve: { | 
			
		||||
 | 
				        alias: { | 
			
		||||
 | 
				            '@': '/src', | 
			
		||||
 | 
				        }, | 
			
		||||
 | 
				    }, | 
			
		||||
 | 
				    plugins: [ | 
			
		||||
 | 
				        react(), | 
			
		||||
 | 
				        viteMockServe({ | 
			
		||||
 | 
				            // 是否启用 mock 功能(默认值:process.env.NODE_ENV !== 'production')
 | 
			
		||||
 | 
				            enable: true, | 
			
		||||
 | 
				
 | 
			
		||||
 | 
				            // mock 文件的根路径,默认值:'mocks'
 | 
			
		||||
 | 
				            mockPath: 'mock', | 
			
		||||
 | 
				            logger: true, | 
			
		||||
 | 
				        }), | 
			
		||||
 | 
				        //TanStackRouterVite(),
 | 
			
		||||
 | 
				    ], | 
			
		||||
 | 
				}) | 
			
		||||
						
							
						
						
							3276
	
						
						yarn.lock
						
							File diff suppressed because it is too large
							
							
								
									View File
								
							
						
					
				File diff suppressed because it is too large
							
							
								
									View File
								
							
						
						Write
						Preview
					
					
					Loading…
					
					Cancel
						Save
					
		Reference in new issue