├── .editorconfig ├── .gitignore ├── .husky └── pre-commit ├── .prettierrc.js ├── README.md ├── craco.config.js ├── deploy ├── deploy.test.sh ├── machine │ ├── jenkins.sh │ └── nginx.conf ├── nginx.conf ├── nginx.sub-app.conf └── rancher │ ├── Dockerfile │ ├── deployment.yaml │ └── nginx.conf ├── docs ├── .nojekyll ├── AJAX.md ├── GEN.md ├── LAYOUT.md ├── MENU.md ├── MOCK.md ├── MODAL_PAGE.md ├── MODEL.md ├── NGINX.md ├── OTHER.md ├── PAGE.md ├── PERMISSION.md ├── PROXY.md ├── QIANKUN.md ├── README.md ├── ROUTE.md ├── START.md ├── STYLE.md ├── _coverpage.md ├── _sidebar.md ├── build │ ├── asset-manifest.json │ ├── favicon.ico │ ├── index.html │ ├── logo192.png │ ├── logo512.png │ ├── manifest.json │ ├── robots.txt │ └── static │ │ ├── css │ │ ├── 3.6178f109.chunk.css │ │ ├── 3.6178f109.chunk.css.map │ │ ├── 4.185ef8b6.chunk.css │ │ ├── 4.185ef8b6.chunk.css.map │ │ ├── 5.185ef8b6.chunk.css │ │ ├── 5.185ef8b6.chunk.css.map │ │ ├── 6.9a1388c0.chunk.css │ │ └── 6.9a1388c0.chunk.css.map │ │ ├── js │ │ ├── 0.c00a972e.chunk.js │ │ ├── 0.c00a972e.chunk.js.map │ │ ├── 10.515a043b.chunk.js │ │ ├── 10.515a043b.chunk.js.map │ │ ├── 11.df3576a8.chunk.js │ │ ├── 11.df3576a8.chunk.js.map │ │ ├── 12.99cc42a7.chunk.js │ │ ├── 12.99cc42a7.chunk.js.map │ │ ├── 13.bff1e6dc.chunk.js │ │ ├── 13.bff1e6dc.chunk.js.map │ │ ├── 14.e62adf89.chunk.js │ │ ├── 14.e62adf89.chunk.js.map │ │ ├── 15.510c57f0.chunk.js │ │ ├── 15.510c57f0.chunk.js.map │ │ ├── 3.a462f62e.chunk.js │ │ ├── 3.a462f62e.chunk.js.LICENSE.txt │ │ ├── 3.a462f62e.chunk.js.map │ │ ├── 4.dafdb96a.chunk.js │ │ ├── 4.dafdb96a.chunk.js.map │ │ ├── 5.0bf26507.chunk.js │ │ ├── 5.0bf26507.chunk.js.map │ │ ├── 6.fa51b8dd.chunk.js │ │ ├── 6.fa51b8dd.chunk.js.map │ │ ├── 7.019a448a.chunk.js │ │ ├── 7.019a448a.chunk.js.map │ │ ├── 8.91f49de0.chunk.js │ │ ├── 8.91f49de0.chunk.js.map │ │ ├── 9.5fa78ebf.chunk.js │ │ ├── 9.5fa78ebf.chunk.js.map │ │ ├── main.e9dfe8d6.chunk.js │ │ ├── main.e9dfe8d6.chunk.js.map │ │ ├── runtime-main.bd4dd1cc.js │ │ └── runtime-main.bd4dd1cc.js.map │ │ └── media │ │ ├── defaultErrorImage.d83d2d78.png │ │ └── login-bg.3ab1656e.jpg ├── img.png ├── imgs │ ├── 404.jpg │ ├── layout-setting.jpg │ ├── login.jpg │ ├── logo.ico │ ├── logo.png │ ├── menu.jpg │ ├── role.jpg │ └── users.jpg ├── index.html └── lib │ ├── css │ └── dark-mode@0.6.1.css │ ├── docsify.js │ ├── docsify.min.js │ ├── js │ ├── codefund.js │ ├── dark-mode@0.6.1.js │ ├── prism-bash.min.js │ ├── prism-markdown.min.js │ └── prism-nginx.min.js │ ├── plugins │ ├── disqus.js │ ├── disqus.min.js │ ├── emoji.js │ ├── emoji.min.js │ ├── external-script.js │ ├── external-script.min.js │ ├── front-matter.js │ ├── front-matter.min.js │ ├── ga.js │ ├── ga.min.js │ ├── gitalk.js │ ├── gitalk.min.js │ ├── matomo.js │ ├── matomo.min.js │ ├── search.js │ ├── search.min.js │ ├── zoom-image.js │ └── zoom-image.min.js │ └── themes │ ├── buble.css │ ├── dark.css │ ├── dolphin.css │ ├── pure.css │ └── vue.css ├── package.json ├── public ├── favicon.ico ├── index.html ├── logo192.png ├── logo512.png ├── manifest.json └── robots.txt ├── src ├── App.jsx ├── App.less ├── api │ ├── index.js │ └── system.js ├── commons │ ├── ajax.js │ ├── config-hoc.js │ ├── handle-error.js │ ├── handle-success.js │ └── index.js ├── components │ ├── error │ │ └── Error404.jsx │ ├── footer │ │ ├── index.jsx │ │ └── style.less │ ├── generator │ │ ├── index.jsx │ │ └── style.less │ ├── header │ │ ├── PasswordModal.js │ │ ├── index.jsx │ │ └── style.less │ ├── icon │ │ └── index.jsx │ ├── iframe │ │ └── index.jsx │ ├── index.js │ ├── layout │ │ ├── index.jsx │ │ └── layout-setting │ │ │ ├── SettingPage.jsx │ │ │ └── index.jsx │ ├── logo │ │ ├── index.jsx │ │ ├── logo.png │ │ └── style.less │ ├── permission │ │ └── index.jsx │ ├── proxy │ │ └── index.jsx │ └── sub-app │ │ ├── SubError.jsx │ │ └── index.jsx ├── config │ ├── config.development.js │ ├── config.production.js │ └── index.js ├── index.js ├── mock │ ├── index.js │ ├── mock-menus.js │ ├── mock-roles.js │ ├── mock-users.js │ ├── mockdata │ │ ├── organizations.js │ │ ├── roles.js │ │ └── user.js │ ├── util.js │ └── web-sql │ │ ├── index.js │ │ └── init-sql.js ├── models │ ├── demo.js │ ├── index.js │ └── models.js ├── options │ ├── index.js │ └── system.js ├── pages │ ├── home │ │ ├── index.jsx │ │ └── style.less │ ├── login │ │ ├── index-full.jsx │ │ ├── index-portal.jsx │ │ ├── index.jsx │ │ ├── login-bg.jpg │ │ └── style.less │ ├── menus │ │ ├── ActionEdit.jsx │ │ ├── MenuEdit.jsx │ │ ├── MenuTableSelect.jsx │ │ ├── SystemSelect.jsx │ │ ├── index.jsx │ │ └── style.less │ ├── page-configs.js │ ├── role │ │ ├── EditModal.jsx │ │ ├── RoleSelectTable.jsx │ │ └── index.jsx │ └── user │ │ ├── EditModal.jsx │ │ └── index.jsx ├── qiankun.js ├── router │ ├── AppRouter.jsx │ └── routes.js ├── setupProxy.js ├── setupProxyConfig.json ├── setupTests.js └── theme.less ├── webpack.config.js └── yarn.lock /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 4 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | 15 | [Makefile] 16 | indent_style = tab 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | /deploy/rancher/build 14 | 15 | # misc 16 | .DS_Store 17 | .env.local 18 | .env.development.local 19 | .env.test.local 20 | .env.production.local 21 | 22 | # log 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | 27 | # ide 28 | .idea 29 | 30 | # ra-lib 31 | /src/ra-lib 32 | /src/pages/local 33 | /server 34 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npm run lint 5 | -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | //此处的规则供参考,其中多半其实都是默认值,可以根据个人习惯改写 2 | module.exports = { 3 | printWidth: 120, // 单行长度 4 | tabWidth: 4, // 缩进长度 5 | useTabs: false, // 使用空格代替tab缩进 6 | semi: true, // 句末使用分号 7 | singleQuote: true, // 使用单引号 8 | quoteProps: 'as-needed', // 仅在必需时为对象的key添加引号 9 | jsxSingleQuote: false, // jsx中使用单引号 10 | trailingComma: 'all', // 多行时尽可能打印尾随逗号 11 | bracketSpacing: false, // 在对象前后添加空格-eg: { foo: bar } 12 | jsxBracketSameLine: false, // 多属性html标签的‘>’折行放置 13 | arrowParens: 'always', // 单参数箭头函数参数周围使用圆括号-eg: (x) => x 14 | requirePragma: false, // 无需顶部注释即可格式化 15 | insertPragma: false, // 在已被prettier格式化的文件顶部加上标注 16 | proseWrap: 'preserve', // 不知道怎么翻译 17 | htmlWhitespaceSensitivity: 'ignore', // 对HTML全局空白不敏感 18 | vueIndentScriptAndStyle: false, // 不对vue中的script及style标签缩进 19 | endOfLine: 'lf', // 结束行形式 20 | embeddedLanguageFormatting: 'auto', // 对引用代码进行格式化 21 | }; 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # React Admin 2 | 3 | 基于[React17.x](https://reactjs.org)、[Ant Design4.x](https://ant.design/)的管理系统架构。 4 | 5 | - [在线预览](https://sxfad.github.io/react-admin/build) 6 | - [查看文档](https://sxfad.github.io/react-admin/#/) 7 | - [组件库ra-lib文档](https://sxfad.github.io/ra-lib/) 8 | 9 | ## 安装依赖 10 | 11 | - node v12.14.0 12 | - yarn 1.22.10 13 | 14 | ```bash 15 | yarn 16 | ``` 17 | 18 | 注:如果由于网络等原因安装不成功,可以尝试 `tyarn` 或 `cnpm` 或 `npm` 或 `yarn --registry https://registry.npm.taobao.org` 19 | 20 | 设置环境变量,windows平台可以使用 [cross-env](https://github.com/kentcdodds/cross-env#) 21 | 22 | ## 开发启动 23 | 24 | 如果您是第一次使用,想快速预览效果,可以是用mock方式启动:`REACT_APP_MOCK=true yarn start` 25 | 26 | ``` 27 | # 正常启动开发模式 28 | yarn start 29 | 30 | # 自定义端口 31 | PORT=3001 yarn start 32 | 33 | # HTTPS 方式启动 34 | HTTPS=true yarn start 35 | 36 | # 开启本地mock 37 | REACT_APP_MOCK=true yarn start 38 | ``` 39 | 40 | ## 开发代理 & 测试代理 41 | 42 | 修改`src/setupProxyConfig.json`,页面右上角头部有下拉,可以快速切换代理。 43 | 44 | ```json 45 | [ 46 | { 47 | "name": "张三", 48 | "disabled": false, 49 | "baseUrl": "/zhangsan", 50 | "target": "http://127.0.0.1:8080" 51 | }, 52 | { 53 | "name": "测试环境", 54 | "baseUrl": "/api", 55 | "target": "http://127.0.0.1:8080" 56 | } 57 | ] 58 | ``` 59 | 60 | ## 生产构建 61 | 62 | ``` 63 | # 正常构建 64 | yarn build 65 | 66 | # 构建到指定目录 67 | BUILD_PATH=./dist yarn build 68 | 69 | # 指定配置环境 70 | REACT_APP_CONFIG_ENV=test yarn build 71 | 72 | # 打包大小分析 73 | yarn build:analyzer 74 | 75 | # 打包时间分析 76 | yarn build:time 77 | ``` 78 | 79 | ## 样式 80 | 81 | - 支持less及css 82 | - src下less进行css module处理,css不进行css module处理 83 | - css module 应用样式写法 84 | ```jsx 85 | import styles from './style.module.less'; 86 | 87 |
88 |

89 |
90 | ``` 91 | - 项目中添加了[classnames](https://github.com/JedWatson/classnames)扩展,可以直接按照[classnames](https://github.com/JedWatson/classnames)方式在className中编写样式; 92 | ```jsx 93 | import styles from './style.module.less'; 94 | 95 | const isActive = true; 96 | 97 |
98 |

99 |
100 | ``` 101 | - 主题变量修改 theme.less [antd 样式变量](https://ant.design/docs/react/customize-theme-cn) 102 | 103 | ## 代码规范 104 | 105 | 代码规范使用 [prettier](https://prettier.io/) [参考知乎这片文章](https://zhuanlan.zhihu.com/p/81764012) 106 | 107 | 团队多人开发,无论使用webstorm还是vscode,都使用prettier(配置文件:.prettierrc.js)进行代码格式化。 108 | 109 | IDE格式化快捷键可以配置成prettier 110 | 111 | git commit 时会根据prettier进行代码格式化,确保提交到仓库的代码都符合规范 112 | 113 | -------------------------------------------------------------------------------- /deploy/deploy.test.sh: -------------------------------------------------------------------------------- 1 | # 本地发布测试环境脚本 2 | 3 | # 构建前端 4 | yarn build 5 | 6 | echo "开始上传..." 7 | # 删除原文件 8 | sshpass -p 123456 ssh app@xxx.xxx.xxx.xxx "rm -rf /home/app/nginx/html/*" 9 | # 上传新文件 10 | sshpass -p 123456 scp -r ./build/* app@xxx.xxx.xxx.xxx:/home/app/nginx/html 11 | echo "上传成功!" 12 | 13 | # 拷贝ng配置 14 | # sshpass -p 123456 scp ./deploy/nginx.test.conf app@xxx.xxx.xxx.xxx:/home/app/nginx/conf 15 | # 重启ng,使配置生效 16 | # sshpass -p 123456 ssh app@xxx.xxx.xxx.xxx "nginx -s reload" 17 | -------------------------------------------------------------------------------- /deploy/machine/jenkins.sh: -------------------------------------------------------------------------------- 1 | # 安装依赖 2 | yarn 3 | 4 | # 前端构建 5 | yarn build 6 | 7 | # 虚拟机部署 8 | # 直接copy,如果scp出现权限问题,需要将 ~/.ssh/id_rsa.pub内容添加到目标机的~/.ssh/authorized_keys文件中 9 | # nginx 配置修改后,要在目标机器上执行 nginx -s reload 10 | TARGET="172.16.xxx.xxx" 11 | scp -r build/* app@$TARGET:/home/app/nginx/html 12 | ssh app@$TARGET "chmod 775 -R /home/app/nginx" 13 | 14 | scp deploy/machine/nginx.conf app@$TARGET:/home/app/nginx/conf 15 | ssh app@$TARGET "nginx -s reload" # 重启ng,一般是ng配置改变了之后需要 16 | -------------------------------------------------------------------------------- /deploy/machine/nginx.conf: -------------------------------------------------------------------------------- 1 | # 后端服务地址 2 | upstream api_service { 3 | server 172.16.40.72:8080; 4 | keepalive 2000; 5 | } 6 | 7 | server { 8 | listen 80; 9 | # server_name xxx.com; # 注意修改为相应的server_name 10 | 11 | # http body 大小限制,可以控制文件上传大小 12 | client_max_body_size 10M; 13 | 14 | # gzip config 15 | gzip on; 16 | gzip_min_length 1k; 17 | gzip_comp_level 9; 18 | gzip_types text/plain application/javascript application/x-javascript text/css application/xml text/javascript application/x-httpd-php image/jpeg image/gif image/png; 19 | gzip_vary on; 20 | gzip_disable "MSIE [1-6]\."; 21 | add_header Access-Control-Allow-Origin *; # 如果是微前端子项目,要设置允许跨域 22 | 23 | root /home/app/nginx/html; # 前端静态文件目录 24 | location / { 25 | index index.html; 26 | try_files $uri $uri/ /index.html; #react-router 防止页面刷新出现404 27 | } 28 | 29 | # 静态文件缓存,启用Cache-Control: max-age、Expires 30 | location ~ ^/static/(css|js|media)/ { 31 | expires 10y; 32 | access_log off; 33 | } 34 | 35 | # 代理ajax请求 36 | location ^~/api { 37 | rewrite ^/api/(.*)$ /$1 break; # 如果后端接口不是统一以api开头,去掉api前缀 38 | proxy_pass http://api_service/; 39 | proxy_set_header Host $http_host; 40 | proxy_set_header Connection close; 41 | proxy_set_header X-Real-IP $remote_addr; 42 | proxy_set_header X-Forwarded-Server $host; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /deploy/nginx.conf: -------------------------------------------------------------------------------- 1 | # 负载均衡 后端服务地址 可写多个server 2 | upstream api_service { 3 | server 172.16.40.72:8080; 4 | keepalive 2000; 5 | } 6 | 7 | server { 8 | listen 80; 9 | # server_name xxx.com; # 注意修改为相应的server_name 10 | 11 | # http body 大小限制,可以控制文件上传大小 12 | client_max_body_size 10M; 13 | 14 | # gzip config 15 | gzip on; 16 | gzip_min_length 1k; 17 | gzip_comp_level 9; 18 | gzip_types text/plain application/javascript application/x-javascript text/css application/xml text/javascript application/x-httpd-php image/jpeg image/gif image/png; 19 | gzip_vary on; 20 | gzip_disable "MSIE [1-6]\."; 21 | 22 | add_header Access-Control-Allow-Origin *; # 如果是微前端子项目,要设置允许跨域 23 | 24 | root /usr/share/nginx/html; # 前端静态文件目录 25 | location / { 26 | index index.html; 27 | try_files $uri $uri/ /index.html; #react-router 防止页面刷新出现404 28 | } 29 | 30 | # 静态文件缓存,启用Cache-Control: max-age、Expires 31 | location ~ ^/static/(css|js|media)/ { 32 | expires 1y; # 可以同时设置 Expires 和 Cache-control mas-age ,规范规定,最大一年 33 | access_log off; 34 | } 35 | 36 | # 代理ajax请求 37 | location ^~/api { 38 | rewrite ^/api/(.*)$ /$1 break; # 如果后端接口不是统一以api开头,去掉api前缀 39 | proxy_pass http://api_service/; 40 | proxy_set_header Host $http_host; 41 | proxy_set_header Connection close; 42 | proxy_set_header X-Real-IP $remote_addr; 43 | proxy_set_header X-Forwarded-Server $host; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /deploy/nginx.sub-app.conf: -------------------------------------------------------------------------------- 1 | # 后端服务地址 2 | upstream audit_api_service { 3 | server 172.16.40.72:8080; 4 | keepalive 2000; 5 | } 6 | 7 | server { 8 | listen 5101; 9 | # server_name xxx.com; # 注意修改为相应的server_name 10 | 11 | # http body 大小限制,可以控制文件上传大小 12 | client_max_body_size 10M; 13 | 14 | # gzip config 15 | gzip on; 16 | gzip_min_length 1k; 17 | gzip_comp_level 9; 18 | gzip_types text/plain application/javascript application/x-javascript text/css application/xml text/javascript application/x-httpd-php image/jpeg image/gif image/png; 19 | gzip_vary on; 20 | gzip_disable "MSIE [1-6]\."; 21 | 22 | # 如果是微前端子项目,要设置允许跨域 23 | add_header Access-Control-Allow-Origin *; 24 | add_header Access-Control-Allow-Methods *; 25 | add_header Access-Control-Allow-Headers *; 26 | 27 | # 删除接口服务器header 28 | proxy_hide_header Access-Control-Allow-Origin; 29 | proxy_hide_header Access-Control-Allow-Methods; 30 | proxy_hide_header Access-Control-Allow-Headers; 31 | 32 | root /usr/share/nginx/html/audit-front; # 前端静态文件目录 33 | location / { 34 | index index.html; 35 | try_files $uri $uri/ /index.html; #react-router 防止页面刷新出现404 36 | } 37 | 38 | # 静态文件缓存,启用Cache-Control: max-age、Expires 39 | location ~ ^/static/(css|js|media)/ { 40 | expires 10y; 41 | access_log off; 42 | } 43 | 44 | # 代理ajax请求 45 | location ^~/api { 46 | rewrite ^/api/(.*)$ /$1 break; # 如果后端接口不是统一以api开头,去掉api前缀 47 | proxy_pass http://audit_api_service/; 48 | proxy_set_header Host $http_host; 49 | proxy_set_header Connection close; 50 | proxy_set_header X-Real-IP $remote_addr; 51 | proxy_set_header X-Forwarded-Server $host; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /deploy/rancher/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM bh-harbor.suixingpay.com/zhaikun/nginx:alpine 2 | 3 | RUN rm -rf /etc/nginx/conf.d/* \ 4 | && rm -rf /usr/share/nginx/html/* 5 | 6 | ADD build/ /usr/share/nginx/html 7 | ADD nginx.conf /etc/nginx/conf.d/ 8 | 9 | RUN chmod 775 -R /usr/share/nginx/html \ 10 | && export LC_ALL=en_US.UTF-8 \ 11 | && echo "设置时区" \ 12 | && ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime 13 | 14 | EXPOSE 80 15 | -------------------------------------------------------------------------------- /deploy/rancher/deployment.yaml: -------------------------------------------------------------------------------- 1 | # 配合jenkins使用 http://172.16.175.93:8080/jenkins/job/front-center/configure 2 | # 注意:rancher 要手动创建命名空间 3 | apiVersion: apps/v1 4 | kind: Deployment 5 | metadata: 6 | name: JOB_BASE_NAME 7 | namespace: NAMESPACE_NAME 8 | labels: 9 | app: JOB_BASE_NAME 10 | spec: 11 | replicas: 1 12 | selector: 13 | matchLabels: 14 | app: JOB_BASE_NAME 15 | template: 16 | metadata: 17 | labels: 18 | app: JOB_BASE_NAME 19 | spec: 20 | imagePullSecrets: 21 | - name: bh-harbor 22 | containers: 23 | - image: bh-harbor.suixingpay.com/zhaikun/JOB_BASE_NAME:BUILD_ID 24 | imagePullPolicy: IfNotPresent 25 | name: JOB_BASE_NAME 26 | ports: 27 | - containerPort: 80 28 | name: web 29 | --- 30 | apiVersion: v1 31 | kind: Service 32 | metadata: 33 | name: JOB_BASE_NAME 34 | namespace: NAMESPACE_NAME 35 | spec: 36 | selector: 37 | app: JOB_BASE_NAME 38 | type: NodePort 39 | ports: 40 | - port: 80 41 | protocol: TCP 42 | targetPort: 80 43 | -------------------------------------------------------------------------------- /deploy/rancher/nginx.conf: -------------------------------------------------------------------------------- 1 | server { 2 | listen 80; 3 | # server_name xxx.com; # 注意修改为相应的server_name 4 | 5 | # http body 大小限制,可以控制文件上传大小 6 | client_max_body_size 10M; 7 | 8 | # gzip config 9 | gzip on; 10 | gzip_min_length 1k; 11 | gzip_comp_level 9; 12 | gzip_types text/plain application/javascript application/x-javascript text/css application/xml text/javascript application/x-httpd-php image/jpeg image/gif image/png; 13 | gzip_vary on; 14 | gzip_disable "MSIE [1-6]\."; 15 | add_header Access-Control-Allow-Origin *; # 如果是微前端子项目,要设置允许跨域 16 | 17 | root /usr/share/nginx/html; # 前端静态文件目录 18 | location / { 19 | index index.html; 20 | try_files $uri $uri/ /index.html; #react-router 防止页面刷新出现404 21 | } 22 | 23 | # 静态文件缓存,启用Cache-Control: max-age、Expires 24 | location ~ ^/static/(css|js|media)/ { 25 | expires 10y; 26 | access_log off; 27 | } 28 | 29 | 30 | # 门户代理 31 | location ^~/portal { 32 | rewrite ^/portal/(.*)$ /$1 break; # 如果后端接口不是统一以api开头,去掉api前缀 33 | proxy_pass http://172.16.143.44:32328/; 34 | proxy_set_header Host $http_host; 35 | proxy_set_header Connection close; 36 | proxy_set_header X-Real-IP $remote_addr; 37 | proxy_set_header X-Forwarded-Server $host; 38 | } 39 | 40 | # 代理ajax请求 张三 41 | location ^~/zhangsan { 42 | rewrite ^/zhangsan/(.*)$ /$1 break; # 如果后端接口不是统一以api开头,去掉api前缀 43 | proxy_pass http://127.0.0.1:8080; 44 | proxy_set_header Host $http_host; 45 | proxy_set_header Connection close; 46 | proxy_set_header X-Real-IP $remote_addr; 47 | proxy_set_header X-Forwarded-Server $host; 48 | } 49 | 50 | # 代理ajax请求 测试环境 51 | location ^~/api { 52 | rewrite ^/api/(.*)$ /$1 break; # 如果后端接口不是统一以api开头,去掉api前缀 53 | proxy_pass http://127.0.0.1:8080; 54 | proxy_set_header Host $http_host; 55 | proxy_set_header Connection close; 56 | proxy_set_header X-Real-IP $remote_addr; 57 | proxy_set_header X-Forwarded-Server $host; 58 | } 59 | } -------------------------------------------------------------------------------- /docs/.nojekyll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sxfad/react-admin/83660823b2b3d315fece226a7841c41a66676136/docs/.nojekyll -------------------------------------------------------------------------------- /docs/AJAX.md: -------------------------------------------------------------------------------- 1 | # Ajax 请求 2 | 系统的ajax请求基于[axios](https://github.com/axios/axios)进行了二次封装,参见[@ra-lib/ajax](https://sxfad.github.io/ra-lib/ajax)。 3 | 4 | ## 方法 5 | 基于restful规范,提供了5个方法: 6 | 7 | - get 获取服务端数据,参数拼接在url上,以 query string 方式发送给后端 8 | - post 新增数据,参数以body形式发送给后端 9 | - put 修改数据,参数以body形式发送给后端 10 | - del 删除数据,参数拼接在url上,以 query string 方式发送给后端 11 | - patch 修改部分数据,参数以body形式发送给后端 12 | 13 | ## 调用方式 14 | 可以通过三种方式,让React组件拿到ajax对象 15 | 16 | - config装饰器ajax属性 17 | ```js 18 | import React, {Component} from 'react'; 19 | import config from 'src/commons/config-hoc'; 20 | 21 | @config({ 22 | // ajax: true, // 默认为true 23 | ... 24 | }) 25 | export default class SomePage extend Component { 26 | componentDidMount() { 27 | this.props.ajax 28 | .get(...) 29 | .then(...) 30 | } 31 | ... 32 | } 33 | ``` 34 | 注: hooks `props.ajax.useGet` 35 | - ajax装饰器 36 | ```js 37 | import React, {Component} from 'react'; 38 | import {ajaxHoc} from 'src/commpons/ajax'; 39 | 40 | @ajaxHoc() 41 | export default class SomePage extend Component { 42 | componentDidMount() { 43 | this.props.ajax 44 | .get(...) 45 | .then(...) 46 | } 47 | ... 48 | } 49 | ``` 50 | 51 | - 直接引入ajax对象 52 | ```js 53 | import React, {Component} from 'react'; 54 | import ajax from 'src/commpons/ajax'; 55 | 56 | export default class SomePage extend Component { 57 | componentDidMount() { 58 | ajax.post(...).then(...); 59 | 60 | // 组件卸载或者其他什么情况,需要打算ajax请求,可以用如下方式 61 | const ajaxFunc = ajax.get(...); 62 | ajaxFunc.then(...).finally(...); 63 | ajaxFunc.cancel(); 64 | } 65 | ... 66 | } 67 | ``` 68 | 注:config、ajaxHoc方式做了封装,页面被卸载之后会**自动打断**未完成的请求 69 | 70 | ## 参数 71 | 所有的ajax方法参数统一,都能够接受三个参数: 72 | 73 | 参数|说明 74 | ---|--- 75 | url|请求地址 76 | params|请求传递给后端的参数 77 | options|请求配置,即axios的配置, 78 | 79 | options配置 80 | 81 | 参数|说明 82 | ---|--- 83 | axios配置|可以接受axios参数 84 | successTip|扩展的参数,成功提示 85 | errorTip|扩展的参数,失败提示 86 | noEmpty|扩展的参数,过滤掉 ''、null、undefined的参数,不提交给后端 87 | originResponse|扩展参数,.then中可以拿到完整的response,而不只是response.data 88 | 89 | 注:全局默认参数可以在src/commons/ajax.js中进行配置,默认baseURL='/api'、timeout=1000 * 60。 90 | 91 | ## 请求结果提示 92 | 系统对ajax失败做了自动提示,开发人员可通过src/commons/handle-error.js进行配置; 93 | 94 | 成功提示默认不显示,如果需要成功提示,可以配置successTip参数,或者.then()中自行处理; 95 | 96 | 成功提示在src/commons/handle-success.js中配置; 97 | ```js 98 | this.props.ajax.del('/user/1', null, {successTip: '删除成功!', errorTip: '删除失败!', noEmpty: true}); 99 | ``` 100 | 101 | ## loading处理 102 | 系统扩展了promise,提供了finally方法,用于无论成功还是失败,都要进行的处理。一般用于关闭loading 103 | ```js 104 | this.setState({loading: true}); 105 | this.props.ajax 106 | .get('/url') 107 | .then(...) 108 | .finally(() => this.setState({loading: false})); 109 | ``` 110 | 111 | -------------------------------------------------------------------------------- /docs/GEN.md: -------------------------------------------------------------------------------- 1 | # 代码生成工具 2 | 为了方便开发,系统配套提供了基于配置(页面)+模版的代码生成工具,可以快速生成增删改查相关代码。 3 | 4 | ## 特性 5 | 1. 基于文本配置文件,简单直接; 6 | 1. 可以基于swagger文档、数据库、配置文件,多种方式生成代码; 7 | 1. 基于模版,生成代码质量高,而且很容易扩展我们所需要的模版; 8 | 9 | ## 使用说明 10 | 基于默认配置文件 /script/gen/config.conf 进行代码生成 11 | ```bash 12 | $ yarn gen 13 | ``` 14 | 15 | 指定配置文件 16 | ```bash 17 | $ yarn gen ./my-confog.conf 18 | ``` 19 | 20 | 忽略文件覆盖提示 21 | ```bash 22 | $ yarn gen -y 23 | ``` 24 | 25 | 基于数据库表,快速生成 26 | ```bash 27 | $ yarn gen -t users 28 | ``` 29 | 30 | ## 配置文件编写说明 31 | 配置文件通过自定义脚本读取,需要遵循一些简单的编写规则 32 | 33 | 1. 脚本读取文件文本内容,配置文件可以是任何类型; 34 | 1. 脚本通过空格拆分各个配置项,空格个数不限制; 35 | 1. \#\#\#\#\#\# (六个#号)所包裹的内容是用来区分配置模块的,不要随意改动, 配置块的顺序可以随意调整; 36 | 1. 配置获取优先级为:接口 > 数据库表 > 当前配置文件; 37 | 1. 接口配置中的url行注释掉,将不基于接口进行生成; 38 | 1. 数据库配置中,tableName行注释掉,将不基于数据库表生成; 39 | 1. 三种注释方式 # ; //,可以基于需求,使用任意一种方式进行代码注释; 40 | 41 | ### form表单元素关键字说明: 42 | 1. q: 作为查询条件(query) ; 43 | 1. f: 作为表单(form); 44 | 1. r: 是否必填(required); 45 | 1. 5: 单独数字,最大可输入长度; 46 | 1. form表单可用类型: 47 | 1. input hidden number textarea password mobile email 48 | 1. select select-tree checkbox checkbox-group radio radio-group switch 49 | 1. date time date-time date-range cascader icon-picker 50 | 51 | ### 数据库链接各式 52 | ``` 53 | mysql://user:password@host:port/database?querystring 54 | ``` 55 | ### ajax请求地址说明 56 | 1. 请求关键字:查询、修改、添加、删除、详情、批量删除; 57 | 1. 各式:请求类型 method url dataPath(获取接口数据的key 比如: data.list); 58 | 1. 基于接口生成页面时: 59 | 1. 具体的地址在《基础配置》中填写; 60 | 1. 通过「查询」接口获取查询条件、表头信息; 61 | 1. 通过「修改」接口获取表单信息; 62 | 1.没有填写的请求将基于「查询」或者「目录」RestFul风格生成; 63 | 64 | ### 页面类型配置 65 | 1. 列表页、弹框编辑、页面编辑 系统默认模板和文件名,可以不指定模板和文件名; 66 | 1. 自定义页面: 67 | 1. 需要同时指定模板和目标文件名比如:自定义页面 ./src/template.js->customer.jsx; 68 | 1. 模板路径相对项目根目录开始写起; 69 | 70 | ## 代码生成页面 71 | 系统提供了代码生成页面,分为「快速生成」和「单独生成」,进入系统后,点击「代码生成」,可以打开代码生成页面。 72 | -------------------------------------------------------------------------------- /docs/LAYOUT.md: -------------------------------------------------------------------------------- 1 | # 导航布局 2 | 为了满足不同系统的需求,提供了四种导航布局: 3 | - 头部菜单 4 | - 左侧菜单 5 | - 头部+左侧菜单 6 | - tab页方式 7 | 8 | ## 更改方式 9 | - 用户可以通过 页面头部右上角设置 页面进行选择(如果您为用户提供了此页面); 10 | - 开发人员可以通过修改`src/config`指定布局方式; 11 | 12 | ## 不需要导航 13 | 有些页面可能不需要显示导航,可以通过如下任意一种方式进行设置: 14 | 15 | - 页面配置高级组件 16 | ```js 17 | @config({ 18 | header: false, 19 | side: false, 20 | pageHeader: false, 21 | tab: false, 22 | }) 23 | ``` 24 | 25 | 注: 26 | 27 | 1. tab基于页面地址,每当使用`this.props.history.push('/some/path')`,就会选中或者新打开一个tab页(`/path` 与 `/path?name=Tom`属于不同url地址,会对应两个tab页); 28 | 1. 没有菜单对应的页面,需要单独设置title,否则无法显示tab标签页; 29 | 30 | -------------------------------------------------------------------------------- /docs/MENU.md: -------------------------------------------------------------------------------- 1 | # 菜单配置 2 | 3 | 在`/src/menus.js`文件中配置菜单数据,前端硬编码或异步加载菜单数据。 4 | 5 | ## 菜单特性 6 | 7 | 为了简化开发,让开发着专注于业务逻辑,系统对菜单进行了一些封装。 8 | 9 | - 菜单支持头部、左侧、头部+左侧**三种布局方式**,详见[导航布局](LAYOUT.md); 10 | - 系统会基于路由path**自动选中**对应的菜单; 11 | - 无菜单对应的二级页面也可以**选中相应父级**菜单,详见[系统路由](ROUTE.md); 12 | - 左侧菜单会**自动滚动**到可视范围内; 13 | - 左侧菜单支持**展开收起**; 14 | - 页面标题、tab标签页标题、面包屑基于菜单状态**自动生成**,但也提供了对应的修改方式,详见[页面开发](PAGE.md); 15 | - 通过菜单配置,支持内嵌**iframe**打开第三方页面、**a标签**方式打开第三方页面; 16 | 17 | ## 菜单字段说明 18 | 19 | 开发人员可以根据自己的需要,配置所需字段。 20 | 21 | 字段|必须|说明 22 | ---|---|--- 23 | id|是|需要唯一 24 | parentId|否|用于关联父级 25 | path|是|菜单对应的路由地址 或 第三方网站地址 26 | title|是|菜单标题 27 | icon|否|菜单图标配置 28 | target|否|配合url使用,菜单将为a标签 `{title}` 29 | order|否|菜单排序,数值越大越靠前显示 30 | -------------------------------------------------------------------------------- /docs/MOCK.md: -------------------------------------------------------------------------------- 1 | # Mock 模拟数据 2 | 前后端并行开发,为了方便后端快速开发,不需要等待后端接口,系统提供了mock功能。基于[mockjs](http://mockjs.com/) 3 | 4 | ## 编写模拟数据 5 | 在/src/mock目录下进行mock数据编写,比如: 6 | ```js 7 | import {getUsersByPageSize} from './mockdata/user'; 8 | 9 | export default { 10 | 'post /mock/login': (config) => { 11 | const { 12 | userName, 13 | password, 14 | } = JSON.parse(config.data); 15 | return new Promise((resolve, reject) => { 16 | if (userName !== 'test' || password !== '111') { 17 | setTimeout(() => { 18 | reject({ 19 | code: 1001, 20 | message: '用户名或密码错误', 21 | }); 22 | }, 1000); 23 | } else { 24 | setTimeout(() => { 25 | resolve([200, { 26 | id: '1234567890abcde', 27 | name: 'MOCK 用户', 28 | loginName: 'MOCK 登录名', 29 | }]); 30 | }, 1000); 31 | } 32 | }); 33 | }, 34 | 'post /mock/logout': {}, 35 | 36 | 'get /mock/user-center': (config) => { 37 | const { 38 | pageSize, 39 | pageNum, 40 | } = config.params; 41 | 42 | 43 | return new Promise((resolve) => { 44 | setTimeout(() => { 45 | resolve([200, { 46 | pageNum, 47 | pageSize, 48 | total: 888, 49 | list: getUsersByPageSize(pageSize), 50 | }]); 51 | }, 1000); 52 | }); 53 | }, 54 | 'get re:/mock/user-center/.+': {id: 1, name: '熊大', age: 22, job: '前端'}, 55 | 'post /mock/user-center': true, 56 | 'put /mock/user-center': true, 57 | 'delete re:/mock/user-center/.+': 'id', 58 | } 59 | ``` 60 | 61 | ## 简化 62 | 为了方便mock接口编写,系统提供了简化脚本(/src/mock/simplify.js),上面的例子就是简化写法 63 | 64 | 对象的key由 method url delay,各部分组成,以空格隔开 65 | 66 | 字段|说明 67 | ---|--- 68 | method| 请求方法 get post等 69 | url|请求的url 70 | delay|模拟延迟,毫秒 默认1000 71 | 72 | ## 调用 73 | 系统封装的ajax可以通过以下两种方式,自动区分是mock数据,还是真实后端数据,无需其他配置 74 | 75 | mock请求: 76 | - url以/mock/开头的请求 77 | - /src/mock/url-config.js中配置的请求 78 | 79 | ```js 80 | this.props.ajax.get('/mock/users').then(...); 81 | ``` 82 | 如果后端真实接口准备好之后,去掉url中的/mock即可 83 | 84 | 注:mock功能只有开发模式下开启了,生产模式不会开启mock功能,如果其他环境要开启mock 使用MOCK=true参数,比如 `MOCK=true yarn build` 85 | -------------------------------------------------------------------------------- /docs/MODAL_PAGE.md: -------------------------------------------------------------------------------- 1 | # 弹框页面开发 2 | 添加、修改等场景,往往会用到弹框,antd Modal组件使用不当会产生脏数据问题(两次弹框渲染数据互相干扰) 3 | 4 | 系统提供了基于modal封装的高阶组件,每次弹框关闭,都会销毁弹框内容,避免互相干扰 5 | 6 | 7 | ## modal高阶组件 8 | modal高阶组件集成到了config中,也可以单独引用:`import {modal} from '@ra-lib/hoc';` 9 | 10 | ```jsx 11 | import React from 'react'; 12 | import config from 'src/commons/config-hoc'; 13 | import {ModalContent} from 'src/library/components'; 14 | 15 | export default config({ 16 | modal: { 17 | title: '弹框标题', 18 | }, 19 | })(props => { 20 | const {onOk, onCancel} = props; 21 | 22 | return ( 23 | 27 | 弹框内容 28 | 29 | ); 30 | }); 31 | ``` 32 | 33 | modal所有参数说明如下: 34 | 35 | 1. 如果是string,作为modal的title 36 | 1. 如果是函数,函数返回值作为 Modal参数 37 | 1. 如果是对象,为Modal相关配置,具体参考 [antd Modal组件](https://ant-design.gitee.io/components/modal-cn/#API) 38 | 1. options.fullScreen boolean 默认false,是否全屏显示弹框 39 | 40 | ## ModalContent 41 | 弹框内容通过 ModalContent包裹,具体参数如下: 42 | 43 | 参数|类型|默认值|说明 44 | ---|---|---|--- 45 | fitHeight|boolean|false|是否使用屏幕垂直方向剩余空间 46 | otherHeight|number|-|除了主体内容之外的其他高度,用于计算主体高度; 47 | loading|boolean|false|加载中 48 | loadingTip|-|-|加载提示文案 49 | footer|-|-|底部 50 | okText|string|-|确定按钮文案 51 | onOk|function|-|确定按钮事件 52 | cancelText|string|-|取消按钮文案 53 | onCancel|function|-|取消按钮事件 54 | resetText|string|-|重置按钮文案 55 | onReset|function|-|重置按钮事件 56 | style|object|-|最外层容器样式 57 | bodyStyle|object|-|内容容器样式 58 | -------------------------------------------------------------------------------- /docs/MODEL.md: -------------------------------------------------------------------------------- 1 | # models(redux) 封装 2 | 基于[redux](https://redux.js.org/)进行封装,不改变redux源码,可以结合使用redux社区中其他解决方案。 3 | 4 | 注:一般情况下,用不到redux~ 5 | 6 | 参见[@ra-lib/model](https://sxfad.github.io/ra-lib/model) 7 | -------------------------------------------------------------------------------- /docs/NGINX.md: -------------------------------------------------------------------------------- 1 | # nginx配置参考 2 | 这里只是参考文件,根据自己的项目需求自行配置 3 | 4 | ## 一个域名对应单个项目 5 | ### 目录结构 6 | ``` 7 | . 8 | ├── /usr/local/nginx/html 9 | │ ├── static 10 | │ ├── index.html 11 | │   └── favicon.ico 12 | ``` 13 | 14 | ### nginx 配置 15 | ```nginx 16 | # 后端服务地址 17 | upstream api_service { 18 | server xxx.xxx.xxx.xxx:xxxx; 19 | keepalive 2000; 20 | } 21 | 22 | server { 23 | listen 80; 24 | server_name www.shubin.wang shubin.wang; # 域名地址 25 | root /usr/local/nginx/html; # 前端静态文件目录 26 | location / { 27 | index index.html; 28 | try_files $uri $uri/ /index.html; #react-router 防止页面刷新出现404 29 | } 30 | 31 | # 静态文件缓存,启用Cache-Control: max-age、Expires 32 | location ~ ^/static/(css|js|media)/ { 33 | expires 10y; 34 | access_log off; 35 | add_header Cache-Control "public"; 36 | } 37 | 38 | # 代理ajax请求 前端ajax请求以 /api 开头 39 | location ^~/api { 40 | rewrite ^/api/(.*)$ /$1 break; # 如果后端接口不是统一以api开头,去掉api前缀 41 | proxy_pass http://api_service/; 42 | proxy_set_header Host $http_host; 43 | proxy_set_header Connection close; 44 | proxy_set_header X-Real-IP $remote_addr; 45 | proxy_set_header X-Forwarded-Server $host; 46 | } 47 | } 48 | ``` 49 | 50 | ## 一个域名对应多个项目 51 | 多个项目挂载到同一个域名下,可以通过子目录方式区分 52 | 53 | 比如,如下地址各对应一个项目 54 | 55 | - http://shubin.wang 56 | - http://shubin.wang/project1 57 | - http://shubin.wang/project2 58 | 59 | 前端项目构建时,添加BASE_NAME PUBLIC_URL参数 60 | ```bash 61 | BASE_NAME=/project1 PUBLIC_URL=/project1 yarn build 62 | ``` 63 | ### nginx 静态文件目录结构 64 | ``` 65 | . 66 | ├── /home/ubuntu/react-admin 67 | │   ├── build // 主项目 静态文件目录 68 | │ │ ├── static 69 | │ │ ├── index.html 70 | │ │   └── favicon.ico 71 | │   ├── project1 // 子项目静态目录 名称与 location /project1 location ~ ^/project1/static/.* 配置对应 72 | │ │ ├── static 73 | │ │ ├── index.html 74 | │ │   └── favicon.ico 75 | ``` 76 | 77 | ### nginx 配置 78 | ```nginx 79 | upstream api_service { 80 | server xxx.xxx.xxx.xxx:xxxx; 81 | keepalive 2000; 82 | } 83 | 84 | upstream api_service_project1 { 85 | server xxx.xxx.xxx.xxx:xxxx; 86 | keepalive 2000; 87 | } 88 | server { 89 | listen 80; 90 | server_name www.shubin.wang shubin.wang; # 域名地址 91 | # Allow file uploads 92 | client_max_body_size 100M; 93 | 94 | # 主项目配置,访问地址 http://www.shubin.wang 95 | location / { 96 | root /home/ubuntu/react-admin/build; 97 | index index.html; 98 | try_files $uri $uri/ /index.html; 99 | } 100 | # 静态文件缓存,启用Cache-Control: max-age、Expires 101 | location ~ ^/static/.* { 102 | root /home/ubuntu/react-admin/build; 103 | expires 20y; 104 | access_log off; 105 | add_header Cache-Control "public"; 106 | } 107 | # 代理ajax请求 前端ajax请求以/api开头 108 | location ^~/api { 109 | rewrite ^/api/(.*)$ /$1 break; # 如果后端接口不是统一以api开头,去掉api前缀 110 | proxy_pass http://api_service/; 111 | proxy_set_header Host $http_host; 112 | proxy_set_header Connection close; 113 | proxy_set_header X-Real-IP $remote_addr; 114 | proxy_set_header X-Forwarded-Server $host; 115 | } 116 | 117 | # 子项目配置 访问地址 http://www.shubin.wang/project1 118 | location /project1 { 119 | root /home/ubuntu/react-admin; 120 | index index.html; 121 | try_files $uri $uri/ /project1/index.html; 122 | } 123 | # 静态文件缓存,启用Cache-Control: max-age、Expires 124 | location ~ ^/project1/static/.* { 125 | root /home/ubuntu/react-admin; 126 | expires 10y; 127 | access_log off; 128 | add_header Cache-Control "public"; 129 | } 130 | # 代理ajax请求 前端ajax请求以 /project1_api 开头 131 | location ^~/project1_api { 132 | rewrite ^/api/(.*)$ /$1 break; # 如果后端接口不是统一以api开头,去掉api前缀 133 | proxy_pass http://api_service_project1/; 134 | proxy_set_header Host $http_host; 135 | proxy_set_header Connection close; 136 | proxy_set_header X-Real-IP $remote_addr; 137 | proxy_set_header X-Forwarded-Server $host; 138 | } 139 | } 140 | ``` 141 | -------------------------------------------------------------------------------- /docs/OTHER.md: -------------------------------------------------------------------------------- 1 | # 其他 2 | 3 | 其他的一些说明 4 | 5 | ## 页面打印 6 | 7 | 通过给元素添加相应的class,控制打印内容: 8 | 9 | - `.just-print` 只在打印时显示 10 | - `.no-print` 在打印时不显示 11 | 12 | ## 组件 13 | 14 | 目录`src/library/antd`中基于Ant Design 扩展了一些常用组件 15 | 16 | 编写这些组件时,注意一下几点: 17 | 18 | - 通用组件不使用css module,方便使用过程中的样式覆盖; 19 | - 统一各个组件的目录结构,便于文档、demo生成; 20 | - `src/pages/example/antd`、`src/menus-ant-design-example.js` 通过脚本 `src/library/antd/generator-demos.js`生成; 21 | 22 | ## Webpack 23 | 24 | ### 使用了alias {src:'/path/to/src'} 25 | 26 | - 方便路径书写,不必关心相对路径结构 27 | - 复制粘贴到其他文件,不必修改路径 28 | 29 | ### 支持判断运算符 30 | 31 | ```js 32 | const name = res?.data?.user?.name || '匿名'; 33 | ``` 34 | 35 | ## ESLint 说明 36 | 37 | 如果前端项目,不是git根目录,在提交的时候,会报错 `Not a git repository` 38 | 39 | 修改package.json,lint-staged 如下即可 40 | 41 | ```json 42 | "lint-staged": { 43 | "gitDir": "../", 44 | "linters": { 45 | "**/*.{js,jsx}": "lint-staged:js", 46 | "**/*.less": "stylelint --syntax less" 47 | } 48 | }, 49 | ``` 50 | -------------------------------------------------------------------------------- /docs/PAGE.md: -------------------------------------------------------------------------------- 1 | # 页面开发 2 | 3 | 页面指的是路由对应的页面组件。业务开发接触最多的就是页面,通过一些封装,简化开发。 4 | 5 | ## 配置高阶组件 6 | 7 | 将组件所需要的一些功能,通过配置装饰器的方式实现,用法如下: 8 | 9 | hooks: 10 | 11 | ``` 12 | import config from 'src/commons/config-hoc'; 13 | 14 | export default config({ 15 | path: '/page/path', 16 | ... 17 | })(function SomePage(props) { 18 | ... 19 | }); 20 | ``` 21 | 22 | class: 23 | 24 | ``` 25 | import React, {Component} from 'react'; 26 | import config from 'src/commons/config-hoc'; 27 | 28 | @config({ 29 | path: '/page/path', 30 | title: '页面title', // 默认基于菜单 31 | ajax: true, // 默认true 32 | ... 33 | }) 34 | export default class SomePage extend Component { 35 | componentDidMount(){ 36 | this.props.ajax 37 | .get(...) 38 | .then(...) 39 | } 40 | ... 41 | } 42 | ``` 43 | 44 | 所有参数如下: 45 | 46 | ```js 47 | // config 所有可用参数,以及默认值 48 | const { 49 | // 路由地址 50 | path, 51 | // 是否需要登录 52 | auth, 53 | // 是否显示顶部 54 | header, 55 | // 是否显示标签 56 | tab, 57 | // 是否显示页面头部 58 | pageHeader, 59 | // 是否显示侧边栏 60 | side, 61 | // 侧边栏是否收起 62 | sideCollapsed, 63 | // 设置选中菜单,默认基于 window.location.pathname选中 用于设置非菜单的子页面,菜单选中状态 64 | selectedMenuPath, 65 | // 设置页面、tab标题,默认基于选中菜单,也可以通过query string 设置 /xxx?title=页面标题 66 | title, 67 | // 自定义面包屑导航,默认基于选中菜单,false:不显示,[{icon, title, path}, ...] 68 | breadcrumb, 69 | // 基于菜单,追加面包屑导航 70 | appendBreadcrumb, 71 | // 页面保持,不销毁,需要设置config.KEEP_PAGE_ALIVE === true 才生效 72 | keepAlive, 73 | // 是否添加withRouter高级组件 74 | router = false, 75 | // props是否注入ajax 76 | ajax = CONFIG_HOC.ajax, 77 | // 连接models,扩展 props.action 78 | connect = CONFIG_HOC.connect, 79 | // 弹框高阶组件 80 | modal, 81 | // 抽屉高级组件 82 | drawer, 83 | ...others 84 | } = options; 85 | ``` 86 | 87 | ## 页面保持 88 | 89 | 页面渲染一次之后会保持状态,再次跳转到此页面不会重新创建或重新渲染。 90 | 91 | ### 开启方式 92 | 93 | `/src/config CONFIG_HOC.keepAlive`设置为true 94 | 95 | 注:部分不需要keepAlive页面,可以在config高阶组件中设置`keepAlive: false` 96 | 97 | ### 页面显示/隐藏事件 98 | 99 | 开启keepAlive功能的页面,会接受到 active props。可以根据active值变化,判断当前页面状态 100 | 101 | active值说明 102 | 103 | - `undefined`页面初始化,第一次加载 104 | - `false` 页面被隐藏 105 | - `true` 页面被显示 106 | 107 | ## 页面容器PageContent 108 | 109 | 系统提供了页面的跟节点PageContent,有如下特性: 110 | 111 | - 添加了margin padding 样式; 112 | - 支持页面loading; 113 | - 添加最小高度,始终使页面撑满全屏 114 | - fitHeight功能,是页面撑满全屏,内容过长时,PageContent将显示滚动条 115 | 116 | 显示loading 117 | 118 | ```js 119 | const {loading} = this.state; 120 | 121 | 122 | ... 123 | 124 | ``` 125 | 126 | 127 | -------------------------------------------------------------------------------- /docs/PERMISSION.md: -------------------------------------------------------------------------------- 1 | # 权限控制 2 | 3 | 系统菜单、具体功能点都可以进行权限控制。 4 | 5 | ## 菜单权限 6 | 7 | 如果菜单由后端提供(一般系统都是后端提供),可以通过userId获取用户的菜单权限;页面只显示获取到的菜单; 8 | 9 | 系统提供了一个基础的菜单、权限管理页面,需要后端配合存储数据。 10 | 11 | ## 功能权限 12 | 13 | 函数可以通过 `src/commons#hasPermission`方法进行权限判断 14 | 15 | 组件可以通过`src/components/permission`组件对功能的权限进行控制 16 | 17 | ```js 18 | import React, {Component} from 'react'; 19 | import Permission from 'src/components'; 20 | import {hasPermission} from 'src/commons/index'; 21 | 22 | export default class SomePage extends Component { 23 | 24 | someFunc() { 25 | const show = hasPermission('USER_ADD'); 26 | } 27 | 28 | render() { 29 | return ( 30 |
31 | 32 | 33 | 34 |
35 | ); 36 | } 37 | } 38 | ``` 39 | 40 | 注:权限的code前端使用时会硬编码,注意语义化、唯一性。 41 | 42 | ## 角色 43 | 44 | 一般系统都会提供角色管理功能,系统中提供了一个基础的角色管理功能,稍作修改即可使用。 45 | -------------------------------------------------------------------------------- /docs/PROXY.md: -------------------------------------------------------------------------------- 1 | # 开发代理 2 | 开发时,要与后端进行接口对接,可以通过代理与后端进行连接,开发代理配置在`src/setupProxy.js`中编写 3 | 4 | ```js 5 | const proxy = require('http-proxy-middleware'); 6 | 7 | // 前端web服务代理配置 8 | module.exports = function(app) { 9 | app.use(proxy('/api', 10 | { 11 | target: 'http://localhost:8081/', // 目标服务器 12 | pathRewrite: { 13 | '^/api': '', // 如果后端接口无前缀,可以通过这种方式去掉 14 | }, 15 | changeOrigin: true, 16 | secure: false, // 是否验证证书 17 | ws: true, // 启用websocket 18 | }, 19 | )); 20 | }; 21 | 22 | ``` 23 | 24 | 注:更多代理配置请参考[http-proxy-middleware](https://github.com/chimurai/http-proxy-middleware) 25 | -------------------------------------------------------------------------------- /docs/QIANKUN.md: -------------------------------------------------------------------------------- 1 | # 微前端 2 | 3 | 当前框架既可以作为乾坤主项目,又可以作为乾坤子项目 4 | 5 | ## 作为乾坤子系统时 6 | 7 | **新创建子项目时注意修改 package.json 文件中 name 属性** 8 | 9 | 约定:package.json name 作为: 10 | 11 | - 子系统的BASE_NAME 12 | - 子系统注册到主系统中的 name 、 activeRule 13 | - ant、ra-lib组件库class前缀 14 | 15 | ## 服务器设置 16 | 17 | ### 开发模式 18 | 19 | - 开发devServer 20 | 21 | ```js 22 | devServer: (devServerConfig, {env, paths, proxy, allowedHost}) => { 23 | if (!devServerConfig.headers) devServerConfig.headers = {}; 24 | devServerConfig.headers['Access-Control-Allow-Origin'] = '*'; 25 | return devServerConfig; 26 | } 27 | ``` 28 | 29 | - 开发代理服务器 30 | 31 | ```js 32 | module.exports = function(app) { 33 | app.use(proxy('/api', 34 | { 35 | target: 'http://172.16.40.72:8080', 36 | pathRewrite: { 37 | '^/api': '', // 如果后端接口无前缀,可以通过这种方式去掉 38 | }, 39 | changeOrigin: true, 40 | secure: false, // 是否验证证书 41 | ws: true, // 启用websocket 42 | // 作为子系统时,需要设置允许跨域 43 | onProxyRes(proxyRes, req, res) { 44 | proxyRes.headers['Access-Control-Allow-Origin'] = '*'; 45 | proxyRes.headers['Access-Control-Allow-Methods'] = '*'; 46 | proxyRes.headers['Access-Control-Allow-Headers'] = '*'; 47 | }, 48 | }, 49 | )); 50 | }; 51 | ``` 52 | 53 | ### 生产(测试)部署 54 | 55 | - nginx 配置 56 | 57 | ``` 58 | # 如果是微前端子项目,要设置允许跨域 59 | add_header Access-Control-Allow-Origin *; 60 | add_header Access-Control-Allow-Methods *; 61 | add_header Access-Control-Allow-Headers *; 62 | 63 | # 删除接口服务器header 64 | proxy_hide_header Access-Control-Allow-Origin; 65 | proxy_hide_header Access-Control-Allow-Methods; 66 | proxy_hide_header Access-Control-Allow-Headers; 67 | ``` 68 | 69 | - 后端接口服务 70 | 71 | 如果使用ng,在ng上设置即可,不需要特殊设置,如果前端直接请求后端接口服务,则需要设置跨域 72 | 73 | ## 乾坤微前端的坑 74 | 75 | -[ ] 子系统卸载时,控制台会报提醒 `[qiankun] Set window.event while sandbox destroyed or inactive in xxx! ` 76 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # 简介 2 | 3 | react-admin是基于[React17.x](https://reactjs.org)、[Ant Design4.x](https://ant.design/)的管理系统架构。 4 | 采用前后端分离,内置了许多管理系统常用功能,通过一些脚本、封装帮助开发人员快速开发管理系统,集中精力处理业务逻辑。 5 | 6 | ## 项目结构 7 | 8 | ``` 9 | . 10 | ├── build // 构建生成的静态文件目录 11 | ├── docs // 说明不稳当 12 | ├── node_modules // 项目依赖 13 | ├── public // 静态文件,非webpack构建文件,构建时会原样copy到build目录中 14 | ├── src // 项目源码目录 15 | │   ├── commons // 项目公共文件目录,一些公共js 16 | │   ├── components // 项目公共组件 17 | │   ├── config // 项目配置 18 | │   ├── mock // 开发时mock数据 19 | │   ├── models // 数据管理,基于redux,复杂的页面会用到 20 | │   ├── pages // 页面文件 21 | │   ├── router // 路由相关 22 | │   ├── App.jsx // 根组件,做一些项目一些初始化工作 23 | │   ├── App.less // 全局样式 24 | │   ├── index.js // 项目入口文件 25 | │   ├── menus.js // 菜单配置文件 26 | │   ├── reportWebVitals.js 27 | │   ├── setupProxy.js // 开发模式,接口对接代理设置 28 | │   ├── setupTests.js // 测试相关 29 | │   └── theme.less // 主题变量 30 | ├── craco.config.js // webpack等构建相关配置 31 | ├── package.json 32 | ├── README.md 33 | └── yarn.lock 34 | ``` 35 | 36 | ## 预览 37 | 38 | 部分页面截图,完整项目预览地址[戳这里](https://sxfad.github.io/react-admin/build/#/login) 39 |
40 | 41 | ![登录](imgs/login.jpg) 42 | ![登录](imgs/layout-setting.jpg) 43 | ![用户](imgs/users.jpg) 44 | ![菜单&权限](imgs/menu.jpg) 45 | ![角色管理](imgs/role.jpg) 46 | ![页面不存在](imgs/404.jpg) 47 | 48 |
49 | 50 | -------------------------------------------------------------------------------- /docs/ROUTE.md: -------------------------------------------------------------------------------- 1 | # 系统路由 2 | 系统路由使用 [react-router](https://reacttraining.com/react-router/web/guides/quick-start)。 3 | 路由使用最多的场景就是一个path对应一个页面,但是开发人员不得不去编写配置文件,多人协作时,配置文件还经常冲突。 4 | 系统通过脚本,简化路由配置。 5 | 6 | ## 路由配置方式 7 | 通过config-loader 将高阶组件参数填充到`/src/pages/page-configs.js`文件。 8 | 9 | 页面config装饰器 10 | ```js 11 | @config({ 12 | path: '/path', 13 | }) 14 | export default class SomePage extends React.Component { 15 | ... 16 | } 17 | ``` 18 | 19 | 比如SomePage.jsx中有上面其中任意一种写法,config-loader最终会在`/src/pages/page-configs.js`文件中填充如下路由配置: 20 | ```js 21 | { 22 | path: '/path', 23 | component: () => import('/path/to/SomePage.jsx'), 24 | }, 25 | ``` 26 | 27 | ## 二级页面 28 | 29 | 二级页面如果要保持父级菜单的选中状态,在config高阶组件中设置 selectedMenuPath 参数 30 | -------------------------------------------------------------------------------- /docs/START.md: -------------------------------------------------------------------------------- 1 | # 快速开始 2 | 3 | 从[github](https://github.com/sxfad/react-admin)上clone代码,通过下面介绍的命令,进行开发或者生产构建。 4 | 5 | ## 环境 6 | 7 | - [yarn](https://yarnpkg.com) v1.22.10 8 | - [node](https://nodejs.org) v12.14.0 9 | 10 | ## 下载 11 | 12 | ```bash 13 | $ git clone https://github.com/sxfad/react-admin.git 14 | ``` 15 | 16 | ## 安装依赖 17 | 18 | ```bash 19 | $ cd react-admin 20 | $ yarn 21 | // 使用国内淘宝镜像镜像 22 | $ yarn --registry https://registry.npm.taobao.org 23 | ``` 24 | 25 | 注:首次使用yarn安装依赖可能比较慢,可以切换到国内镜像,或者科学上网。 26 | 27 | ## 开发启动 28 | 29 | ```bash 30 | $ cd react-admin 31 | $ yarn start 32 | 33 | # 指定端口 34 | $ PORT=8080 yarn start 35 | 36 | # HTTPS方式启动 37 | $ HTTPS=true yarn start 38 | ``` 39 | 40 | 注:启动会有点慢,耐心等待一会儿,启动成功后会自动打开浏览器。 windows环境下可以使用 [cross-env](https://www.npmjs.com/package/cross-env)设置命令行变量。 41 | 42 | ## 生产构建 43 | 44 | ```bash 45 | $ cd react-admin 46 | 47 | $ yarn build 48 | 49 | # 构建输入到指定目录 50 | $ BUILD_PATH=../dist yarn build 51 | ``` 52 | 53 | 注:默认构建生成的文件在 `/react-admin/build` 目录下;[nginx配置参考](NGINX.md)。 54 | 55 | ## 域名子目录发布项目 56 | 57 | 如果项目需要挂载到域名的一个子目录下,比如 `http://xxx.com/react-admin`。添加命令行参数 REACT_APP_BASE_NAME=/react-admin 58 | 59 | 注:如果前端构建完成的静态页面,放在nginx静态目录的react-admin目录下,添加命令行参数 PUBLIC_URL=/react-admin; 60 | 61 | 62 | -------------------------------------------------------------------------------- /docs/STYLE.md: -------------------------------------------------------------------------------- 1 | # 样式 2 | 系统使用[less](http://lesscss.org/)进行样式的编写。 3 | 4 | ## css 模块化 5 | 为了避免多人合作样式冲突,系统对src下的less文件启用了Css Module,css文件没有使用Css Module。 6 | 7 | style.less 8 | ```less 9 | .root{ 10 | width: 100%; 11 | height: 100%; 12 | } 13 | ``` 14 | Some.jsx 15 | ```jsx 16 | import styles from '/path/to/style.less'; 17 | 18 | export default class Some extends React.Component { 19 | render() { 20 | return ( 21 |
22 | ); 23 | } 24 | } 25 | ``` 26 | 27 | ## 主题 28 | 使用less,通过样式覆盖来实现。 29 | 30 | ### 编写主题 31 | - less文件中使用主题相关变量; 32 | - 编写`/src/theme.less`通过[less-loader](https://github.com/webpack-contrib/less-loader)的`modifyVars`覆盖less中的变量; 33 | 34 | ### 参考 35 | - Ant Design 主题 参考:https://ant-design.gitee.io/docs/react/customize-theme-cn 36 | -------------------------------------------------------------------------------- /docs/_coverpage.md: -------------------------------------------------------------------------------- 1 | ![logo](imgs/logo.ico) 2 | 3 | # React-Admin 4 | 5 | > 管理系统架构 6 | 7 | - 基于[React17.x](https://reactjs.org)、[Ant Design4.x](https://ant.design/) 8 | - 简单 · 高效 9 | 10 | [GitHub](https://github.com/sxfad/react-admin) 11 | [快速开始](#简介) 12 | -------------------------------------------------------------------------------- /docs/_sidebar.md: -------------------------------------------------------------------------------- 1 | * [简介](README.md) 2 | * [快速开始](START.md) 3 | * [菜单配置](MENU.md) 4 | * [页面开发](PAGE.md) 5 | * [弹框页面开发](MODAL_PAGE.md) 6 | * [系统路由](ROUTE.md) 7 | * [Ajax请求](AJAX.md) 8 | * [Mock模拟数据](MOCK.md) 9 | * [样式&主题](STYLE.md) 10 | * [导航布局](LAYOUT.md) 11 | * [Model(Redux)](MODEL.md) 12 | * [权限控制](PERMISSION.md) 13 | * [Nginx配置](NGINX.md) 14 | * [开发代理](PROXY.md) 15 | * [微前端](QIANKUN.md) 16 | [comment]: <> (* [代码生成](GEN.md)) 17 | * [其他](OTHER.md) 18 | -------------------------------------------------------------------------------- /docs/build/asset-manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "files": { 3 | "static/js/0.c00a972e.chunk.js": "/react-admin/build/static/js/0.c00a972e.chunk.js", 4 | "static/js/0.c00a972e.chunk.js.map": "/react-admin/build/static/js/0.c00a972e.chunk.js.map", 5 | "main.js": "/react-admin/build/static/js/main.e9dfe8d6.chunk.js", 6 | "main.js.map": "/react-admin/build/static/js/main.e9dfe8d6.chunk.js.map", 7 | "runtime-main.js": "/react-admin/build/static/js/runtime-main.bd4dd1cc.js", 8 | "runtime-main.js.map": "/react-admin/build/static/js/runtime-main.bd4dd1cc.js.map", 9 | "static/css/3.6178f109.chunk.css": "/react-admin/build/static/css/3.6178f109.chunk.css", 10 | "static/js/3.a462f62e.chunk.js": "/react-admin/build/static/js/3.a462f62e.chunk.js", 11 | "static/js/3.a462f62e.chunk.js.map": "/react-admin/build/static/js/3.a462f62e.chunk.js.map", 12 | "static/css/4.185ef8b6.chunk.css": "/react-admin/build/static/css/4.185ef8b6.chunk.css", 13 | "static/js/4.dafdb96a.chunk.js": "/react-admin/build/static/js/4.dafdb96a.chunk.js", 14 | "static/js/4.dafdb96a.chunk.js.map": "/react-admin/build/static/js/4.dafdb96a.chunk.js.map", 15 | "static/css/5.185ef8b6.chunk.css": "/react-admin/build/static/css/5.185ef8b6.chunk.css", 16 | "static/js/5.0bf26507.chunk.js": "/react-admin/build/static/js/5.0bf26507.chunk.js", 17 | "static/js/5.0bf26507.chunk.js.map": "/react-admin/build/static/js/5.0bf26507.chunk.js.map", 18 | "static/css/6.9a1388c0.chunk.css": "/react-admin/build/static/css/6.9a1388c0.chunk.css", 19 | "static/js/6.fa51b8dd.chunk.js": "/react-admin/build/static/js/6.fa51b8dd.chunk.js", 20 | "static/js/6.fa51b8dd.chunk.js.map": "/react-admin/build/static/js/6.fa51b8dd.chunk.js.map", 21 | "static/js/7.019a448a.chunk.js": "/react-admin/build/static/js/7.019a448a.chunk.js", 22 | "static/js/7.019a448a.chunk.js.map": "/react-admin/build/static/js/7.019a448a.chunk.js.map", 23 | "static/js/8.91f49de0.chunk.js": "/react-admin/build/static/js/8.91f49de0.chunk.js", 24 | "static/js/8.91f49de0.chunk.js.map": "/react-admin/build/static/js/8.91f49de0.chunk.js.map", 25 | "static/js/9.5fa78ebf.chunk.js": "/react-admin/build/static/js/9.5fa78ebf.chunk.js", 26 | "static/js/9.5fa78ebf.chunk.js.map": "/react-admin/build/static/js/9.5fa78ebf.chunk.js.map", 27 | "static/js/10.515a043b.chunk.js": "/react-admin/build/static/js/10.515a043b.chunk.js", 28 | "static/js/10.515a043b.chunk.js.map": "/react-admin/build/static/js/10.515a043b.chunk.js.map", 29 | "static/js/11.df3576a8.chunk.js": "/react-admin/build/static/js/11.df3576a8.chunk.js", 30 | "static/js/11.df3576a8.chunk.js.map": "/react-admin/build/static/js/11.df3576a8.chunk.js.map", 31 | "static/js/12.99cc42a7.chunk.js": "/react-admin/build/static/js/12.99cc42a7.chunk.js", 32 | "static/js/12.99cc42a7.chunk.js.map": "/react-admin/build/static/js/12.99cc42a7.chunk.js.map", 33 | "static/js/13.bff1e6dc.chunk.js": "/react-admin/build/static/js/13.bff1e6dc.chunk.js", 34 | "static/js/13.bff1e6dc.chunk.js.map": "/react-admin/build/static/js/13.bff1e6dc.chunk.js.map", 35 | "static/js/14.e62adf89.chunk.js": "/react-admin/build/static/js/14.e62adf89.chunk.js", 36 | "static/js/14.e62adf89.chunk.js.map": "/react-admin/build/static/js/14.e62adf89.chunk.js.map", 37 | "static/js/15.510c57f0.chunk.js": "/react-admin/build/static/js/15.510c57f0.chunk.js", 38 | "static/js/15.510c57f0.chunk.js.map": "/react-admin/build/static/js/15.510c57f0.chunk.js.map", 39 | "index.html": "/react-admin/build/index.html", 40 | "static/css/3.6178f109.chunk.css.map": "/react-admin/build/static/css/3.6178f109.chunk.css.map", 41 | "static/css/4.185ef8b6.chunk.css.map": "/react-admin/build/static/css/4.185ef8b6.chunk.css.map", 42 | "static/css/5.185ef8b6.chunk.css.map": "/react-admin/build/static/css/5.185ef8b6.chunk.css.map", 43 | "static/css/6.9a1388c0.chunk.css.map": "/react-admin/build/static/css/6.9a1388c0.chunk.css.map", 44 | "static/js/3.a462f62e.chunk.js.LICENSE.txt": "/react-admin/build/static/js/3.a462f62e.chunk.js.LICENSE.txt", 45 | "static/media/defaultErrorImage.d83d2d78.png": "/react-admin/build/static/media/defaultErrorImage.d83d2d78.png", 46 | "static/media/login-bg.jpg": "/react-admin/build/static/media/login-bg.3ab1656e.jpg" 47 | }, 48 | "entrypoints": [ 49 | "static/js/runtime-main.bd4dd1cc.js", 50 | "static/css/3.6178f109.chunk.css", 51 | "static/js/3.a462f62e.chunk.js", 52 | "static/js/main.e9dfe8d6.chunk.js" 53 | ] 54 | } -------------------------------------------------------------------------------- /docs/build/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sxfad/react-admin/83660823b2b3d315fece226a7841c41a66676136/docs/build/favicon.ico -------------------------------------------------------------------------------- /docs/build/index.html: -------------------------------------------------------------------------------- 1 | 加载中...
-------------------------------------------------------------------------------- /docs/build/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sxfad/react-admin/83660823b2b3d315fece226a7841c41a66676136/docs/build/logo192.png -------------------------------------------------------------------------------- /docs/build/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sxfad/react-admin/83660823b2b3d315fece226a7841c41a66676136/docs/build/logo512.png -------------------------------------------------------------------------------- /docs/build/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /docs/build/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /docs/build/static/css/6.9a1388c0.chunk.css: -------------------------------------------------------------------------------- 1 | .react-admin-alert{box-sizing:border-box;margin:0;color:rgba(0,0,0,.65);font-size:14px;font-variant:tabular-nums;line-height:1.5715;list-style:none;font-feature-settings:"tnum","tnum";position:relative;display:flex;align-items:center;padding:8px 15px;word-wrap:break-word;border-radius:2px}.react-admin-alert-content{flex:1 1;min-width:0}.react-admin-alert-icon{margin-right:8px}.react-admin-alert-description{display:none;font-size:14px;line-height:22px}.react-admin-alert-success{background-color:#f6ffed;border:1px solid #b7eb8f}.react-admin-alert-success .react-admin-alert-icon{color:#52c41a}.react-admin-alert-info{background-color:#e6f7ff;border:1px solid #91d5ff}.react-admin-alert-info .react-admin-alert-icon{color:#1890ff}.react-admin-alert-warning{background-color:#fffbe6;border:1px solid #ffe58f}.react-admin-alert-warning .react-admin-alert-icon{color:#faad14}.react-admin-alert-error{background-color:#fff2f0;border:1px solid #ffccc7}.react-admin-alert-error .react-admin-alert-icon{color:#ff4d4f}.react-admin-alert-error .react-admin-alert-description>pre{margin:0;padding:0}.react-admin-alert-action{margin-left:8px}.react-admin-alert-close-icon{margin-left:8px;padding:0;overflow:hidden;font-size:12px;line-height:12px;background-color:transparent;border:none;outline:none;cursor:pointer}.react-admin-alert-close-icon .anticon-close{color:rgba(0,0,0,.45);transition:color .3s}.react-admin-alert-close-icon .anticon-close:hover{color:rgba(0,0,0,.75)}.react-admin-alert-close-text{color:rgba(0,0,0,.45);transition:color .3s}.react-admin-alert-close-text:hover{color:rgba(0,0,0,.75)}.react-admin-alert-with-description{align-items:flex-start;padding:15px 15px 15px 24px}.react-admin-alert-with-description.react-admin-alert-no-icon{padding:15px}.react-admin-alert-with-description .react-admin-alert-icon{margin-right:15px;font-size:24px}.react-admin-alert-with-description .react-admin-alert-message{display:block;margin-bottom:4px;color:rgba(0,0,0,.85);font-size:16px}.react-admin-alert-message{color:rgba(0,0,0,.85)}.react-admin-alert-with-description .react-admin-alert-description{display:block}.react-admin-alert.react-admin-alert-motion-leave{overflow:hidden;opacity:1;transition:max-height .3s cubic-bezier(.78,.14,.15,.86),opacity .3s cubic-bezier(.78,.14,.15,.86),padding-top .3s cubic-bezier(.78,.14,.15,.86),padding-bottom .3s cubic-bezier(.78,.14,.15,.86),margin-bottom .3s cubic-bezier(.78,.14,.15,.86)}.react-admin-alert.react-admin-alert-motion-leave-active{max-height:0;margin-bottom:0!important;padding-top:0;padding-bottom:0;opacity:0}.react-admin-alert-banner{margin-bottom:0;border:0;border-radius:0}.react-admin-alert.react-admin-alert-rtl{direction:rtl}.react-admin-alert-rtl.react-admin-alert.react-admin-alert-no-icon{padding:8px 15px}.react-admin-alert-rtl .react-admin-alert-icon{margin-right:auto;margin-left:8px}.react-admin-alert-rtl .react-admin-alert-action,.react-admin-alert-rtl .react-admin-alert-close-icon{margin-right:8px;margin-left:auto}.react-admin-alert-rtl.react-admin-alert-with-description .react-admin-alert-icon{margin-right:auto;margin-left:15px} 2 | /*# sourceMappingURL=6.9a1388c0.chunk.css.map */ -------------------------------------------------------------------------------- /docs/build/static/js/11.df3576a8.chunk.js: -------------------------------------------------------------------------------- 1 | (this["webpackJsonp_react-admin"]=this["webpackJsonp_react-admin"]||[]).push([[11],{958:function(e,n,t){"use strict";t.r(n);t(104);var r=t(37),a=t(5),s=t.n(a),c=t(14),i=t(1),o=t.n(i),l=t(13),u=t(163),A=t(995),p=t.n(A),f=t(9);n.default=Object(u.a)({path:"/",title:"\u9996\u9875"})((function(e){return Object(f.jsxs)(l.m,{className:o()(p.a.root),children:[Object(f.jsx)("h1",{children:"\u9996\u9875"}),Object(f.jsx)(r.a,{onClick:Object(c.a)(s.a.mark((function n(){return s.a.wrap((function(n){for(;;)switch(n.prev=n.next){case 0:return n.next=2,e.ajax.post("/initDB",null,{successTip:"\u6570\u636e\u5e93\u91cd\u7f6e\u6210\u529f\uff01"});case 2:setTimeout((function(){return window.location.reload()}),2e3);case 3:case"end":return n.stop()}}),n)}))),children:"\u91cd\u7f6e\u6570\u636e\u5e93"})]})}))},995:function(e,n,t){var r=t(190),a=t(996);"string"===typeof(a=a.__esModule?a.default:a)&&(a=[[e.i,a,""]]);var s={insert:"head",singleton:!1};r(a,s);e.exports=a.locals||{}},996:function(e,n,t){"use strict";t.r(n);var r=t(111),a=t.n(r)()(!0);a.push([e.i,".root_IP7AU {\n display: flex;\n flex-direction: column;\n justify-content: center;\n align-items: center;\n}\n","",{version:3,sources:["webpack://src/pages/home/style.less"],names:[],mappings:"AAEA;EACI,aAAA;EACA,sBAAA;EACA,uBAAA;EACA,mBAAA;AAIJ",sourcesContent:['@import "src/theme.less";\n\n.root {\n display: flex;\n flex-direction: column;\n justify-content: center;\n align-items: center;\n}\n\n@packageName: react-admin;'],sourceRoot:""}]),a.locals={antPrefix:"react-admin",raLibPrefix:"react-admin-ra",primaryColor:"#1890ff",root:"root_IP7AU"},n.default=a}}]); 2 | //# sourceMappingURL=11.df3576a8.chunk.js.map -------------------------------------------------------------------------------- /docs/build/static/js/11.df3576a8.chunk.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["pages/home/index.jsx","webpack://react-admin/./src/pages/home/style.less?5718","pages/home/style.less"],"names":["config","path","title","props","styles","root","onClick","a","ajax","post","successTip","setTimeout","window","location","reload","api","content","__esModule","default","module","i","options","exports","locals","___CSS_LOADER_EXPORT___","push"],"mappings":"iOAMeA,sBAAO,CAClBC,KAAM,IACNC,MAAO,gBAFIF,EAGZ,SAAcG,GAGb,sBACK,IADL,eAC4BC,IAAOC,MADnC,UAEQ,8CAEI,mBACIC,QAAO,sBAAE,sBAAAC,EAAA,sEACCJ,EAAMK,KAAKC,KAAK,UAAW,KAAM,CAAEC,WAAY,qDADhD,OAELC,YAAW,kBAAMC,OAAOC,SAASC,WAAU,KAFtC,2CADb,mD,oBChBhB,IAAIC,EAAM,EAAQ,KACFC,EAAU,EAAQ,KAIC,kBAFvBA,EAAUA,EAAQC,WAAaD,EAAQE,QAAUF,KAG/CA,EAAU,CAAC,CAACG,EAAOC,EAAIJ,EAAS,MAG9C,IAAIK,EAAU,CAEd,OAAiB,OACjB,WAAoB,GAEPN,EAAIC,EAASK,GAI1BF,EAAOG,QAAUN,EAAQO,QAAU,I,iCClBnC,oBAEIC,EAFJ,MAE8B,IAA4B,GAE1DA,EAAwBC,KAAK,CAACN,EAAOC,EAAI,sHAAuH,GAAG,CAAC,QAAU,EAAE,QAAU,CAAC,uCAAuC,MAAQ,GAAG,SAAW,uDAAuD,eAAiB,CAAC,kLAAoL,WAAa,MAElgBI,EAAwBD,OAAS,CAChC,UAAa,cACb,YAAe,iBACf,aAAgB,UAChB,KAAQ,cAEM","file":"static/js/11.df3576a8.chunk.js","sourcesContent":["// import {Redirect} from 'react-router-dom';\nimport { Button } from 'antd';\nimport { PageContent } from '@ra-lib/admin';\nimport config from 'src/commons/config-hoc';\nimport styles from './style.less';\n\nexport default config({\n path: '/',\n title: '首页',\n})(function Home(props) {\n // 如果其他页面作为首页,直接重定向,config中不要设置title,否则tab页中会多个首页\n // return ;\n return (\n \n

首页

\n {process.env.REACT_APP_MOCK ? (\n {\n await props.ajax.post('/initDB', null, { successTip: '数据库重置成功!' });\n setTimeout(() => window.location.reload(), 2000);\n }}\n >\n 重置数据库\n \n ) : null}\n
\n );\n});\n","var api = require(\"!../../../node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js\");\n var content = require(\"!!../../../node_modules/css-loader/dist/cjs.js??ref--5-oneOf-10-1!../../../node_modules/less-loader/dist/cjs.js??ref--5-oneOf-10-2!./style.less\");\n\n content = content.__esModule ? content.default : content;\n\n if (typeof content === 'string') {\n content = [[module.id, content, '']];\n }\n\nvar options = {};\n\noptions.insert = \"head\";\noptions.singleton = false;\n\nvar update = api(content, options);\n\n\n\nmodule.exports = content.locals || {};","// Imports\nimport ___CSS_LOADER_API_IMPORT___ from \"../../../node_modules/css-loader/dist/runtime/api.js\";\nvar ___CSS_LOADER_EXPORT___ = ___CSS_LOADER_API_IMPORT___(true);\n// Module\n___CSS_LOADER_EXPORT___.push([module.id, \".root_IP7AU {\\n display: flex;\\n flex-direction: column;\\n justify-content: center;\\n align-items: center;\\n}\\n\", \"\",{\"version\":3,\"sources\":[\"webpack://src/pages/home/style.less\"],\"names\":[],\"mappings\":\"AAEA;EACI,aAAA;EACA,sBAAA;EACA,uBAAA;EACA,mBAAA;AAIJ\",\"sourcesContent\":[\"@import \\\"src/theme.less\\\";\\n\\n.root {\\n display: flex;\\n flex-direction: column;\\n justify-content: center;\\n align-items: center;\\n}\\n\\n@packageName: react-admin;\"],\"sourceRoot\":\"\"}]);\n// Exports\n___CSS_LOADER_EXPORT___.locals = {\n\t\"antPrefix\": \"react-admin\",\n\t\"raLibPrefix\": \"react-admin-ra\",\n\t\"primaryColor\": \"#1890ff\",\n\t\"root\": \"root_IP7AU\"\n};\nexport default ___CSS_LOADER_EXPORT___;\n"],"sourceRoot":""} -------------------------------------------------------------------------------- /docs/build/static/js/13.bff1e6dc.chunk.js: -------------------------------------------------------------------------------- 1 | (this["webpackJsonp_react-admin"]=this["webpackJsonp_react-admin"]||[]).push([[13],{1002:function(e,t,n){"use strict";n.r(t),n.d(t,"default",(function(){return d}));n(364);var i=n(255),c=n(13),r=n(9);function d(e){var t,n=((null===e||void 0===e||null===(t=e.match)||void 0===t?void 0:t.params)||{}).src;if((n=window.decodeURIComponent(n))&&"undefined"!==n){var d=Object(c.P)({token:Object(c.J)()});n="".concat(n).concat(n.includes("?")?"&":"?").concat(d)}return Object(r.jsx)(c.m,{fitHeight:!0,style:{padding:0,display:"flex"},children:n&&"undefined"!==n?Object(r.jsx)("iframe",{allowFullScreen:!0,title:n,src:n,style:{border:0,width:"100%",height:"100%",boxSizing:"border-box"}},n):Object(r.jsx)("div",{style:{height:"100%",width:"100%",display:"flex",alignItems:"center",justifyContent:"center"},children:Object(r.jsx)(i.a,{status:"error",title:"\u9875\u9762\u52a0\u8f7d\u5931\u8d25",subTitle:"\u4f20\u9012\u6b63\u786e\u7684 src\uff0c\u5f53\u524d\u83b7\u53d6\u5230\u300c".concat(n,"\u300d")})})})}}}]); 2 | //# sourceMappingURL=13.bff1e6dc.chunk.js.map -------------------------------------------------------------------------------- /docs/build/static/js/13.bff1e6dc.chunk.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["components/iframe/index.jsx"],"names":["IFrame","props","src","match","params","window","decodeURIComponent","queryStr","queryStringify","token","getToken","includes","fitHeight","style","padding","display","allowFullScreen","title","border","width","height","boxSizing","alignItems","justifyContent","status","subTitle"],"mappings":"wMAGe,SAASA,EAAOC,GAAQ,IAAD,EAC5BC,IAAa,OAALD,QAAK,IAALA,GAAA,UAAAA,EAAOE,aAAP,eAAcC,SAAU,IAAhCF,IAKN,IAHAA,EAAMG,OAAOC,mBAAmBJ,KAGb,cAARA,EAAqB,CAC5B,IAAMK,EAAWC,YAAe,CAC5BC,MAAOC,gBAGXR,EAAG,UAAMA,GAAN,OAAYA,EAAIS,SAAS,KAAO,IAAM,KAAtC,OAA4CJ,GAGnD,OACI,cAAC,IAAD,CACIK,WAAS,EACTC,MAAO,CACHC,QAAS,EACTC,QAAS,QAJjB,SAOKb,GAAe,cAARA,EACJ,wBAEIc,iBAAe,EACfC,MAAOf,EACPA,IAAKA,EACLW,MAAO,CACHK,OAAQ,EACRC,MAAO,OACPC,OAAQ,OACRC,UAAW,eARVnB,GAYT,qBACIW,MAAO,CACHO,OAAQ,OACRD,MAAO,OACPJ,QAAS,OACTO,WAAY,SACZC,eAAgB,UANxB,SASI,mBAAQC,OAAO,QAAQP,MAAM,uCAASQ,SAAQ,sFAAqBvB,EAArB","file":"static/js/13.bff1e6dc.chunk.js","sourcesContent":["import { Result } from 'antd';\nimport { getToken, queryStringify, PageContent } from '@ra-lib/admin';\n\nexport default function IFrame(props) {\n let { src } = props?.match?.params || {};\n\n src = window.decodeURIComponent(src);\n\n // 通过src 传递token\n if (src && src !== 'undefined') {\n const queryStr = queryStringify({\n token: getToken(),\n });\n\n src = `${src}${src.includes('?') ? '&' : '?'}${queryStr}`;\n }\n\n return (\n \n {src && src !== 'undefined' ? (\n \n ) : (\n \n \n \n )}\n \n );\n}\n"],"sourceRoot":""} -------------------------------------------------------------------------------- /docs/build/static/js/14.e62adf89.chunk.js: -------------------------------------------------------------------------------- 1 | (this["webpackJsonp_react-admin"]=this["webpackJsonp_react-admin"]||[]).push([[14],{959:function(t,n,e){"use strict";e.r(n),e.d(n,"default",(function(){return j}));var c,s=e(365),a=e(366),i=e(367),o=e(368),r=e(0),u=e.n(r),l=e(163),h=e(9);function b(){console.log(this)}var j=Object(l.a)({path:"/test/class"})(c=function(t){Object(i.a)(e,t);var n=Object(o.a)(e);function e(){var t;Object(s.a)(this,e);for(var c=arguments.length,a=new Array(c),i=0;i\n \n \n \n {count}\n \n );\n }\n}\n"],"sourceRoot":""} -------------------------------------------------------------------------------- /docs/build/static/js/15.510c57f0.chunk.js: -------------------------------------------------------------------------------- 1 | (this["webpackJsonp_react-admin"]=this["webpackJsonp_react-admin"]||[]).push([[15],{960:function(e,n,t){"use strict";t.r(n);var c=t(13),i=t(163),s=t(9);n.default=Object(i.a)({path:"/auto-deps",connect:function(e){return{name:e.demo.present.name,user:e.demo.present.user}}})((function(e){var n=e.name,t=e.user;return Object(s.jsxs)(c.m,{children:[Object(s.jsx)("div",{onClick:function(){e.action.demo.setName("\u662f\u554a")},children:"setName"}),Object(s.jsx)("div",{onClick:function(){e.action.demo.getUser()},children:"getUser"}),Object(s.jsx)("div",{children:n}),Object(s.jsx)("div",{children:JSON.stringify(t)})]})}))}}]); 2 | //# sourceMappingURL=15.510c57f0.chunk.js.map -------------------------------------------------------------------------------- /docs/build/static/js/15.510c57f0.chunk.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["pages/local/TestHooks.jsx"],"names":["config","path","connect","state","name","demo","present","user","props","onClick","action","setName","getUser","JSON","stringify"],"mappings":"qHAAA,mCAGeA,sBAAO,CAClBC,KAAM,aACNC,QAAS,SAACC,GACN,MAAO,CACHC,KAAMD,EAAME,KAAKC,QAAQF,KACzBG,KAAMJ,EAAME,KAAKC,QAAQC,QALtBP,EAQZ,SAAkBQ,GAAQ,IACjBJ,EAAeI,EAAfJ,KAAMG,EAASC,EAATD,KAUd,OACI,eAAC,IAAD,WACI,qBAAKE,QAVb,WACID,EAAME,OAAOL,KAAKM,QAAQ,iBAStB,qBACA,qBAAKF,QAPb,WACID,EAAME,OAAOL,KAAKO,WAMd,qBACA,8BAAMR,IACN,8BAAMS,KAAKC,UAAUP","file":"static/js/15.510c57f0.chunk.js","sourcesContent":["import { PageContent } from '@ra-lib/admin';\nimport config from 'src/commons/config-hoc';\n\nexport default config({\n path: '/auto-deps',\n connect: (state) => {\n return {\n name: state.demo.present.name,\n user: state.demo.present.user,\n };\n },\n})(function AutoDeps(props) {\n const { name, user } = props;\n\n function handleClick() {\n props.action.demo.setName('是啊');\n }\n\n function handleClick2() {\n props.action.demo.getUser();\n }\n\n return (\n \n
setName
\n
getUser
\n
{name}
\n
{JSON.stringify(user)}
\n
\n );\n});\n"],"sourceRoot":""} -------------------------------------------------------------------------------- /docs/build/static/js/3.a462f62e.chunk.js.LICENSE.txt: -------------------------------------------------------------------------------- 1 | /* 2 | object-assign 3 | (c) Sindre Sorhus 4 | @license MIT 5 | */ 6 | 7 | /* NProgress, (c) 2013, 2014 Rico Sta. Cruz - http://ricostacruz.com/nprogress 8 | * @license MIT */ 9 | 10 | /*! 11 | Copyright (c) 2018 Jed Watson. 12 | Licensed under the MIT License (MIT), see 13 | http://jedwatson.github.io/classnames 14 | */ 15 | 16 | /*! 17 | * Determine if an object is a Buffer 18 | * 19 | * @author Feross Aboukhadijeh 20 | * @license MIT 21 | */ 22 | 23 | /*! ***************************************************************************** 24 | Copyright (c) Microsoft Corporation. 25 | 26 | Permission to use, copy, modify, and/or distribute this software for any 27 | purpose with or without fee is hereby granted. 28 | 29 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 30 | REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 31 | AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 32 | INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 33 | LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR 34 | OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 35 | PERFORMANCE OF THIS SOFTWARE. 36 | ***************************************************************************** */ 37 | 38 | /** @license React v0.20.2 39 | * scheduler.production.min.js 40 | * 41 | * Copyright (c) Facebook, Inc. and its affiliates. 42 | * 43 | * This source code is licensed under the MIT license found in the 44 | * LICENSE file in the root directory of this source tree. 45 | */ 46 | 47 | /** @license React v16.13.1 48 | * react-is.production.min.js 49 | * 50 | * Copyright (c) Facebook, Inc. and its affiliates. 51 | * 52 | * This source code is licensed under the MIT license found in the 53 | * LICENSE file in the root directory of this source tree. 54 | */ 55 | 56 | /** @license React v17.0.2 57 | * react-dom.production.min.js 58 | * 59 | * Copyright (c) Facebook, Inc. and its affiliates. 60 | * 61 | * This source code is licensed under the MIT license found in the 62 | * LICENSE file in the root directory of this source tree. 63 | */ 64 | 65 | /** @license React v17.0.2 66 | * react-jsx-runtime.production.min.js 67 | * 68 | * Copyright (c) Facebook, Inc. and its affiliates. 69 | * 70 | * This source code is licensed under the MIT license found in the 71 | * LICENSE file in the root directory of this source tree. 72 | */ 73 | 74 | /** @license React v17.0.2 75 | * react.production.min.js 76 | * 77 | * Copyright (c) Facebook, Inc. and its affiliates. 78 | * 79 | * This source code is licensed under the MIT license found in the 80 | * LICENSE file in the root directory of this source tree. 81 | */ 82 | 83 | //! moment.js 84 | 85 | //! moment.js locale configuration 86 | -------------------------------------------------------------------------------- /docs/build/static/js/runtime-main.bd4dd1cc.js: -------------------------------------------------------------------------------- 1 | !function(e){function t(t){for(var n,o,u=t[0],f=t[1],i=t[2],l=0,s=[];l 2 | 3 | 4 | 5 | 6 | docsify 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 24 | 25 | 26 | 27 |
Loading ...
28 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 76 | 77 | 78 | -------------------------------------------------------------------------------- /docs/lib/css/dark-mode@0.6.1.css: -------------------------------------------------------------------------------- 1 | :root{--text_color:var(--theme-color);--docsify_dark_mode_bg:#fff;--docsify_dark_mode_btn:var(--theme-color)}.sidebar,.sidebar-toggle,aside,body,html,main{background:#fff;background:var(--docsify_dark_mode_bg)}#dark_mode>input[type=checkbox]{height:0;width:0;visibility:hidden}#dark_mode>label{cursor:pointer;text-indent:-9999px;width:55px;height:30px;background:var(--theme-color);background:var(--docsify_dark_mode_btn);margin:0 auto;display:flex;justify-content:center;align-items:center;border-radius:100px;position:relative}#dark_mode>label:after{content:"";background:#fff;width:20px;height:20px;border-radius:50%;position:absolute;top:5px;left:4px;transition:.32s cubic-bezier(.68,-.55,.27,1.55)}#dark_mode>input:checked+label{background:var(--theme-color);background:var(--docsify_dark_mode_btn)}#dark_mode>input:checked+label:after{left:calc(100% - 5px);transform:translateX(-100%)}html.transition,html.transition *,html.transition :after,html.transition :before{transition:.42s cubic-bezier(.68,-.55,.27,1.55)!important;transition-delay:0!important}#dark_mode{position:absolute;right:0;top:0}p{color:var(--theme-color);color:var(--text_color)} -------------------------------------------------------------------------------- /docs/lib/js/codefund.js: -------------------------------------------------------------------------------- 1 | ;(function(window) { 2 | window.DocsifyCodefund = { 3 | scriptEl: null, 4 | create: function(codefundId) { 5 | return function(hook, vm) { 6 | hook.ready(function() { 7 | window.DocsifyCodefund.injectCodeFundStyle(); 8 | }) 9 | 10 | hook.doneEach(function () { 11 | window.DocsifyCodefund.injectCodeFundScript(codefundId); 12 | window.DocsifyCodefund.injectCodeFundContainer(); 13 | }) 14 | } 15 | }, 16 | 17 | injectCodeFundContainer() { 18 | if (document.getElementById("codefund") === null) { 19 | let nav = document.getElementsByClassName('sidebar-nav'); 20 | let codefundContainer = document.createElement('div'); 21 | codefundContainer.id = 'codefund'; 22 | nav[0].insertBefore(codefundContainer, nav[0].firstChild); 23 | } 24 | 25 | if (document.getElementsByClassName('cf-wrapper').length === 0) { 26 | window._codefund && window._codefund.serve(); 27 | } 28 | }, 29 | 30 | injectCodeFundScript(codefundId) { 31 | if (window.DocsifyCodefund.scriptEl) { 32 | window.DocsifyCodefund.scriptEl.remove() 33 | } 34 | 35 | var script = document.createElement('script') 36 | script.src = "https://codefund.io/properties/" + codefundId + "/funder.js?template=docsify" 37 | script.async = "async" 38 | document.body.appendChild(script) 39 | window.DocsifyCodefund.scriptEl = script 40 | }, 41 | 42 | injectCodeFundStyle() { 43 | var style = document.createElement('style'); 44 | window.DocsifyCodefund.injectCodeFundContainer(); 45 | var css = '#cf span.cf-wrapper { background-color: transparent!important } #cf a.cf-text { color: #444!important; font-weight: 400!important; font-size: 10px!important }'; 46 | 47 | style.type = 'text/css'; 48 | if (style.styleSheet){ 49 | style.styleSheet.cssText = css; 50 | } else { 51 | style.appendChild(document.createTextNode(css)); 52 | } 53 | document.head.appendChild(style); 54 | } 55 | } 56 | })(window) -------------------------------------------------------------------------------- /docs/lib/js/dark-mode@0.6.1.js: -------------------------------------------------------------------------------- 1 | "use strict";window.$docsify.plugins=[].concat((t,e)=>{var o,n=()=>{document.documentElement.classList.add("transition"),window.setTimeout(()=>{document.documentElement.classList.remove("transition")},800)},c=({background:t,toggleBtnBg:e,textColor:o})=>{document.documentElement.style.setProperty("--docsify_dark_mode_bg",t),document.documentElement.style.setProperty("--docsify_dark_mode_btn",e),document.documentElement.style.setProperty("--text_color",o)};o={dark:{background:"#1c2022",toggleBtnBg:"#34495e",textColor:"#b4b4b4"},light:{background:"white",toggleBtnBg:"var(--theme-color)",textColor:"var(--theme-color)"},...e.config.darkMode},t.afterEach((function(t,e){e(t=`
\n \n \n
${t}`)})),t.doneEach((function(){var t;localStorage.getItem("DOCSIFY_DARK_MODE")?(t=localStorage.getItem("DOCSIFY_DARK_MODE"),c(o[`${t}`])):(t="light",c(o.light)),document.querySelector("input[name=mode]").addEventListener("change",(function(){"light"===t?(n(),c(o.dark),localStorage.setItem("DOCSIFY_DARK_MODE","dark"),t="dark"):(n(),c(o.light),localStorage.setItem("DOCSIFY_DARK_MODE","light"),t="light")}))}))},window.$docsify.plugins); -------------------------------------------------------------------------------- /docs/lib/js/prism-markdown.min.js: -------------------------------------------------------------------------------- 1 | !function(d){function n(n,e){return n=n.replace(//g,"(?:\\\\.|[^\\\\\\n\r]|(?:\r?\n|\r)(?!\r?\n|\r))"),e&&(n=n+"|"+n.replace(/_/g,"\\*")),RegExp("((?:^|[^\\\\])(?:\\\\{2})*)(?:"+n+")")}var e="(?:\\\\.|``.+?``|`[^`\r\\n]+`|[^\\\\|\r\\n`])+",t="\\|?__(?:\\|__)+\\|?(?:(?:\r?\n|\r)|$)".replace(/__/g,e),a="\\|?[ \t]*:?-{3,}:?[ \t]*(?:\\|[ \t]*:?-{3,}:?[ \t]*)+\\|?(?:\r?\n|\r)";d.languages.markdown=d.languages.extend("markup",{}),d.languages.insertBefore("markdown","prolog",{blockquote:{pattern:/^>(?:[\t ]*>)*/m,alias:"punctuation"},table:{pattern:RegExp("^"+t+a+"(?:"+t+")*","m"),inside:{"table-data-rows":{pattern:RegExp("^("+t+a+")(?:"+t+")*$"),lookbehind:!0,inside:{"table-data":{pattern:RegExp(e),inside:d.languages.markdown},punctuation:/\|/}},"table-line":{pattern:RegExp("^("+t+")"+a+"$"),lookbehind:!0,inside:{punctuation:/\||:?-{3,}:?/}},"table-header-row":{pattern:RegExp("^"+t+"$"),inside:{"table-header":{pattern:RegExp(e),alias:"important",inside:d.languages.markdown},punctuation:/\|/}}}},code:[{pattern:/(^[ \t]*(?:\r?\n|\r))(?: {4}|\t).+(?:(?:\r?\n|\r)(?: {4}|\t).+)*/m,lookbehind:!0,alias:"keyword"},{pattern:/``.+?``|`[^`\r\n]+`/,alias:"keyword"},{pattern:/^```[\s\S]*?^```$/m,greedy:!0,inside:{"code-block":{pattern:/^(```.*(?:\r?\n|\r))[\s\S]+?(?=(?:\r?\n|\r)^```$)/m,lookbehind:!0},"code-language":{pattern:/^(```).+/,lookbehind:!0},punctuation:/```/}}],title:[{pattern:/\S.*(?:\r?\n|\r)(?:==+|--+)(?=[ \t]*$)/m,alias:"important",inside:{punctuation:/==+$|--+$/}},{pattern:/(^\s*)#+.+/m,lookbehind:!0,alias:"important",inside:{punctuation:/^#+|#+$/}}],hr:{pattern:/(^\s*)([*-])(?:[\t ]*\2){2,}(?=\s*$)/m,lookbehind:!0,alias:"punctuation"},list:{pattern:/(^\s*)(?:[*+-]|\d+\.)(?=[\t ].)/m,lookbehind:!0,alias:"punctuation"},"url-reference":{pattern:/!?\[[^\]]+\]:[\t ]+(?:\S+|<(?:\\.|[^>\\])+>)(?:[\t ]+(?:"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|\((?:\\.|[^)\\])*\)))?/,inside:{variable:{pattern:/^(!?\[)[^\]]+/,lookbehind:!0},string:/(?:"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|\((?:\\.|[^)\\])*\))$/,punctuation:/^[\[\]!:]|[<>]/},alias:"url"},bold:{pattern:n("__(?:(?!_)|_(?:(?!_))+_)+__",!0),lookbehind:!0,greedy:!0,inside:{content:{pattern:/(^..)[\s\S]+(?=..$)/,lookbehind:!0,inside:{}},punctuation:/\*\*|__/}},italic:{pattern:n("_(?:(?!_)|__(?:(?!_))+__)+_",!0),lookbehind:!0,greedy:!0,inside:{content:{pattern:/(^.)[\s\S]+(?=.$)/,lookbehind:!0,inside:{}},punctuation:/[*_]/}},strike:{pattern:n("(~~?)(?:(?!~))+?\\2",!1),lookbehind:!0,greedy:!0,inside:{content:{pattern:/(^~~?)[\s\S]+(?=\1$)/,lookbehind:!0,inside:{}},punctuation:/~~?/}},url:{pattern:n('!?\\[(?:(?!\\]))+\\](?:\\([^\\s)]+(?:[\t ]+"(?:\\\\.|[^"\\\\])*")?\\)| ?\\[(?:(?!\\]))+\\])',!1),lookbehind:!0,greedy:!0,inside:{variable:{pattern:/(\[)[^\]]+(?=\]$)/,lookbehind:!0},content:{pattern:/(^!?\[)[^\]]+(?=\])/,lookbehind:!0,inside:{}},string:{pattern:/"(?:\\.|[^"\\])*"(?=\)$)/}}}}),["url","bold","italic","strike"].forEach(function(e){["url","bold","italic","strike"].forEach(function(n){e!==n&&(d.languages.markdown[e].inside.content.inside[n]=d.languages.markdown[n])})}),d.hooks.add("after-tokenize",function(n){"markdown"!==n.language&&"md"!==n.language||!function n(e){if(e&&"string"!=typeof e)for(var t=0,a=e.length;t"==d[0]?null!=u?u[v]=_(l.shift()):r[v]=_(l.shift()):null!=u?u[v]=N(d):r[v]=N(d)}else null!=u?u[v]=e(l):r[v]=e(l)}else{if(g.match(/^-\s*$/)){f&&(f=!1,void 0===r.length&&(r=[])),null!=u&&r.push(u),u={},f=!0;continue}if(t=g.match(/^-\s*(.*)/)){null!=u?u.push(N(t[1])):(f&&(f=!1,void 0===r.length&&(r=[])),r.push(N(t[1])));continue}}}null!=u&&(f&&(f=!1,void 0===r.length&&(r=[])),r.push(u))}for(h=s.length-1;0<=h;--h)n.splice.call(n,s[h],1);return r}(e.children)}function l(e){b=[],y=[],t=(new Date).getTime();var n=r(function(e){var n,t=R.regLevel,r=R.invalidLine,i=e.split("\n"),l=0,u=0,a=[],s=new m(-1),f=new m(0);s.addChild(f);var h=[],o="";a.push(f),h.push(l);for(var c=0,p=i.length;c 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 14 | 15 | 24 | 加载中... 25 | 26 | 27 | 28 |
29 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sxfad/react-admin/83660823b2b3d315fece226a7841c41a66676136/public/logo192.png -------------------------------------------------------------------------------- /public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sxfad/react-admin/83660823b2b3d315fece226a7841c41a66676136/public/logo512.png -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /src/App.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect, useCallback } from 'react'; 2 | import { ConfigProvider } from 'antd'; 3 | import { Helmet } from 'react-helmet'; 4 | import { Provider } from 'react-redux'; 5 | import zhCN from 'antd/lib/locale-provider/zh_CN'; 6 | import moment from 'moment'; 7 | import 'moment/locale/zh-cn'; // 解决antd日期相关组件国际化问题 8 | import { ComponentProvider, Loading, getLoginUser, setLoginUser /*queryParse,*/ } from '@ra-lib/admin'; 9 | import { Generator } from 'src/components'; 10 | import { isNoAuthPage } from 'src/commons'; 11 | import AppRouter from './router/AppRouter'; 12 | import { APP_NAME, CONFIG_HOC, IS_MOBILE } from 'src/config'; 13 | import { store } from 'src/models'; 14 | import api from 'src/api'; 15 | import theme from 'src/theme.less'; 16 | import './App.less'; 17 | 18 | // 设置语言 19 | moment.locale('zh-cn'); 20 | 21 | // 设置 Modal、Message、Notification rootPrefixCls。 22 | ConfigProvider.config({ 23 | prefixCls: theme.antPrefix, 24 | }); 25 | 26 | export default function App(props) { 27 | const { children } = props; 28 | const [loading, setLoading] = useState(true); 29 | const [menus, setMenus] = useState([]); 30 | const [collectedMenus, setCollectedMenus] = useState(CONFIG_HOC.showCollectedMenus ? [] : null); 31 | const handleMenuCollect = useCallback(async (menu, collected) => { 32 | await api.saveCollectedMenu({ menuId: menu.id, collected }); 33 | 34 | const collectedMenus = await api.getCollectedMenus(); 35 | setCollectedMenus(collectedMenus); 36 | }, []); 37 | 38 | // 一些初始化工作 39 | useEffect(() => { 40 | // 不需要登录的页面不请求 41 | if (isNoAuthPage()) return setLoading(false); 42 | 43 | // 获取用户菜单、权限等 44 | (async () => { 45 | try { 46 | let loginUser = getLoginUser(); 47 | if (!loginUser) { 48 | // 嵌入iframe等方式,没有经过登录页面,没有设置loginUser,需要请求loginUser 49 | // 发请求,获取loginUser 50 | // loginUser = await api.getLoginUser(); 51 | // 52 | // const {token} = queryParse(); 53 | // if (token) loginUser.token = token; 54 | // 55 | // setLoginUser(loginUser); 56 | 57 | return setLoading(false); 58 | } 59 | 60 | // 用户收藏菜单 使用then catch 防止报错后续接口阻断 61 | // 用户收藏菜单 62 | if (CONFIG_HOC.showCollectedMenus) { 63 | await api.getCollectedMenus().then(setCollectedMenus).catch(console.error); 64 | } 65 | 66 | // 获取用户菜单 67 | await api.getMenus().then(setMenus).catch(console.error); 68 | 69 | // 获取用户权限 70 | await api 71 | .getPermissions() 72 | .then((res) => { 73 | loginUser.permissions = res; 74 | setLoginUser(loginUser); 75 | }) 76 | .catch(console.error); 77 | } finally { 78 | setLoading(false); 79 | } 80 | })(); 81 | }, []); 82 | 83 | // 加载完成后渲染,确保能拿到permissions等数据 84 | return ( 85 | 86 | 87 | 88 | 93 | {loading ? ( 94 | 95 | ) : children ? ( 96 | children 97 | ) : ( 98 | 99 | )} 100 | {process.env.NODE_ENV === 'development' ? : null} 101 | 102 | 103 | 104 | ); 105 | } 106 | -------------------------------------------------------------------------------- /src/App.less: -------------------------------------------------------------------------------- 1 | @import 'src/theme'; 2 | 3 | :global { 4 | // 全局样式,轻易不要编写 5 | body { 6 | margin: 0; 7 | padding: 0; 8 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif; 9 | -webkit-font-smoothing: antialiased; 10 | -moz-osx-font-smoothing: grayscale; 11 | min-height: 100vh; 12 | /*overflow-y: scroll;*/ 13 | } 14 | 15 | #root { 16 | overflow: auto; // BFC PageContent margin-bottom 不会被合并 17 | } 18 | 19 | code { 20 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', 21 | monospace; 22 | } 23 | 24 | /* 只在打印时显示 */ 25 | 26 | .just-print { 27 | display: none !important; 28 | } 29 | 30 | @media print { 31 | body { 32 | padding: 0 !important; 33 | background: none; 34 | } 35 | 36 | /* 打印时不显示 */ 37 | .no-print { 38 | display: none !important; 39 | } 40 | 41 | .just-print { 42 | display: block !important; 43 | } 44 | 45 | .@{ant-prefix}-message { 46 | display: none !important; 47 | } 48 | 49 | .@{ant-prefix}-modal-mask { 50 | display: none !important; 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/api/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 使用 require.context 自动引入所有model文件 3 | * */ 4 | const result = {}; 5 | 6 | // src/models目录下,不支持子文件夹 7 | const req = require.context('./', false, /\.js$/); 8 | req.keys().forEach((key) => { 9 | if (['./index.js'].includes(key)) return; 10 | 11 | const model = req(key); 12 | 13 | const options = model.default; 14 | Object.entries(options).forEach(([k, value]) => { 15 | if (k in result) throw Error(`${key} 文件中 key 「${k}」已被使用!请更换!`); 16 | result[k] = value; 17 | }); 18 | }); 19 | 20 | export default result; 21 | -------------------------------------------------------------------------------- /src/api/system.js: -------------------------------------------------------------------------------- 1 | import ajax from 'src/commons/ajax'; 2 | import { getLoginUser, isLoginPage, formatMenus, getContainerId } from '@ra-lib/admin'; 3 | import { isNoAuthPage } from 'src/commons'; 4 | import { IS_SUB } from 'src/config'; 5 | 6 | export default { 7 | /** 8 | * 获取菜单 9 | * @returns {Promise<*[]|*>} 10 | */ 11 | async getMenuData() { 12 | // 非登录页面,不加载菜单 13 | if (isNoAuthPage()) return []; 14 | 15 | // 作为子应用,不加载 16 | if (IS_SUB) return []; 17 | 18 | // 获取服务端数据,并做缓存,防止多次调用接口 19 | return (this.getMenuData.__CACHE = 20 | this.getMenuData.__CACHE || 21 | ajax 22 | .get('/authority/queryUserMenus', { userId: getLoginUser()?.id }) 23 | .then((res) => res.map((item) => ({ ...item, order: item.order ?? item.ord ?? item.sort }))) 24 | .catch(() => [])); 25 | 26 | // 前端硬编码菜单 27 | // return [ 28 | // {id: 1, title: '系统管理', order: 900, type: 1}, 29 | // {id: 2, parentId: 1, title: '用户管理', path: '/users', order: 900, type: 1}, 30 | // {id: 3, parentId: 1, title: '角色管理', path: '/roles', order: 900, type: 1}, 31 | // {id: 4, parentId: 1, title: '菜单管理', path: '/menus', order: 900, type: 1}, 32 | // ]; 33 | }, 34 | /** 35 | * 获取系统菜单 36 | * @returns {Promise} 37 | */ 38 | async getMenus() { 39 | // mock时,做个延迟处理,否则菜单请求无法走mock 40 | if (process.env.REACT_APP_MOCK) await new Promise((resolve) => setTimeout(resolve)); 41 | 42 | const serverMenus = await this.getMenuData(); 43 | const menus = serverMenus 44 | .filter((item) => !item.type || item.type === 1) 45 | .map((item) => { 46 | return { 47 | ...item, 48 | id: `${item.id}`, 49 | parentId: `${item.parentId}`, 50 | }; 51 | }); 52 | 53 | return formatMenus(menus); 54 | }, 55 | /** 56 | * 获取用户收藏菜单 57 | * @returns {Promise<*>} 58 | */ 59 | async getCollectedMenus() { 60 | // 登录页面,不加载 61 | if (isLoginPage()) return []; 62 | 63 | // 作为子应用,不加载 64 | if (IS_SUB) return []; 65 | 66 | const loginUser = getLoginUser(); 67 | const data = await ajax.get('/authority/queryUserCollectedMenus', { userId: loginUser?.id }); 68 | // const data = []; 69 | 70 | const menus = data.filter((item) => item.type === 1).map((item) => ({ ...item, isCollectedMenu: true })); 71 | 72 | return formatMenus(menus); 73 | }, 74 | /** 75 | * 保存用户收藏菜单 76 | * @param menuId 77 | * @param collected 78 | * @returns {Promise} 79 | */ 80 | async saveCollectedMenu({ menuId, collected }) { 81 | await ajax.post('/authority/addUserCollectMenu', { userId: getLoginUser()?.id, menuId, collected }); 82 | }, 83 | /** 84 | * 获取用户权限码 85 | * @returns {Promise<*[string]>} 86 | */ 87 | async getPermissions() { 88 | const serverMenus = await this.getMenuData(); 89 | return serverMenus.filter((item) => item.type === 2).map((item) => item.code); 90 | }, 91 | /** 92 | * 获取子应用配置 93 | * @returns {Promise<*[{title, name, entry}]>} 94 | */ 95 | async getSubApps() { 96 | // 从菜单数据中获取需要注册的乾坤子项目 97 | const menuTreeData = (await this.getMenus()) || []; 98 | 99 | // 传递给子应用的数据 100 | const loginUser = getLoginUser(); 101 | const props = { 102 | mainApp: { 103 | loginUser: loginUser, 104 | token: loginUser?.token, 105 | }, 106 | }; 107 | let result = []; 108 | const loop = (nodes) => 109 | nodes.forEach((node) => { 110 | const { _target, children } = node; 111 | if (_target === 'qiankun') { 112 | const { title, name, entry } = node; 113 | const container = `#${getContainerId(name)}`; 114 | const activeRule = `/${name}`; 115 | 116 | result.push({ 117 | title, 118 | name, 119 | entry, 120 | container, 121 | activeRule, 122 | props, 123 | }); 124 | } 125 | if (children?.length) loop(children); 126 | }); 127 | loop(menuTreeData); 128 | 129 | return result; 130 | }, 131 | }; 132 | -------------------------------------------------------------------------------- /src/commons/ajax.js: -------------------------------------------------------------------------------- 1 | import { getToken, Ajax, createAjaxHoc, createAjaxHooks } from '@ra-lib/admin'; 2 | import { AJAX_PREFIX, AJAX_TIMEOUT } from 'src/config'; 3 | import handleError from './handle-error'; 4 | import handleSuccess from './handle-success'; 5 | 6 | // 创建Ajax实例,设置默认值 7 | const ajax = new Ajax({ 8 | baseURL: AJAX_PREFIX, 9 | timeout: AJAX_TIMEOUT, 10 | onError: handleError, 11 | onSuccess: handleSuccess, 12 | // withCredentials: true, // 跨域携带cookie,对应后端 Access-Control-Allow-Origin不可以为 '*',需要指定为具体域名 13 | }); 14 | 15 | // 请求拦截 16 | ajax.instance.interceptors.request.use( 17 | (cfg) => { 18 | if (!cfg.headers) cfg.headers = {}; 19 | // 这里每次请求都会动态获取,放到创建实例中,只加载一次,有时候会出问题。 20 | cfg.headers['auth-token'] = getToken(); 21 | return cfg; 22 | }, 23 | (error) => { 24 | // Do something with request error 25 | return Promise.reject(error); 26 | }, 27 | ); 28 | 29 | // 响应拦截 30 | ajax.instance.interceptors.response.use( 31 | (res) => { 32 | // Do something before response 33 | 34 | // 后端自定义失败,前端直接抛出,走handleError逻辑 35 | // if (typeof res.data === 'object' && 'code' in res.data && res.data.code !== 0) return Promise.reject(res.data); 36 | 37 | return res; 38 | }, 39 | (error) => { 40 | // Do something with response error 41 | return Promise.reject(error); 42 | }, 43 | ); 44 | 45 | const hooks = createAjaxHooks(ajax); 46 | const hoc = createAjaxHoc(ajax); 47 | 48 | export default ajax; 49 | 50 | export const ajaxHoc = hoc; 51 | 52 | export const get = ajax.get; 53 | export const post = ajax.post; 54 | export const put = ajax.put; 55 | export const del = ajax.del; 56 | export const patch = ajax.patch; 57 | export const download = ajax.download; 58 | 59 | export const useGet = hooks.useGet; 60 | export const usePost = hooks.usePost; 61 | export const usePut = hooks.usePut; 62 | export const useDel = hooks.useDel; 63 | export const usePatch = hooks.usePatch; 64 | export const useDownload = hooks.useDownload; 65 | -------------------------------------------------------------------------------- /src/commons/config-hoc.js: -------------------------------------------------------------------------------- 1 | import { withRouter } from 'react-router-dom'; 2 | import { createConfigHoc, modal as modalHoc, drawer as drawerHoc, getQuery, getLoginUser } from '@ra-lib/admin'; 3 | import { ajaxHoc } from 'src/commons/ajax'; 4 | import { connect as reduxConnect } from 'src/models'; 5 | import { CONFIG_HOC, IS_MOBILE } from 'src/config'; 6 | import { layoutHoc } from 'src/components/layout'; 7 | import React from 'react'; 8 | 9 | // 公共高阶组件,注入一些常用数据,比如 query loginUser等 10 | function commonHoc(options) { 11 | const { query, loginUser } = options; 12 | return (WrappedComponent) => { 13 | const componentName = WrappedComponent.displayName || WrappedComponent.name || 'Component'; 14 | 15 | const WithLayout = (props) => { 16 | // 默认添加属性到props中的属性 17 | const extendProps = {}; 18 | if (query !== false) extendProps.query = getQuery(); 19 | if (loginUser !== false) extendProps.loginUser = getLoginUser(); 20 | 21 | return ; 22 | }; 23 | 24 | WithLayout.displayName = `WithCommon(${componentName})`; 25 | 26 | return WithLayout; 27 | }; 28 | } 29 | 30 | export default function configHoc(options = {}) { 31 | // config 所有可用参数,以及默认值 32 | const { 33 | // 路由地址 34 | path, 35 | // 是否需要登录 36 | auth, 37 | // 是否显示顶部 38 | header, 39 | // 是否显示标签 40 | tab, 41 | // 是否显示页面头部 42 | pageHeader, 43 | // 是否显示侧边栏 44 | side, 45 | // 侧边栏是否收起 46 | sideCollapsed, 47 | // 设置选中菜单,默认基于 window.location选中 用于设置非菜单的子页面,菜单选中状态 48 | selectedMenuPath, 49 | // 设置页面、tab标题,默认基于选中菜单,也可以通过query string 设置 /xxx?title=页面标题 50 | title, 51 | // 自定义面包屑导航,默认基于选中菜单,false:不显示,[{icon, title, path}, ...] 52 | breadcrumb, 53 | // 基于菜单,追加面包屑导航 54 | appendBreadcrumb, 55 | // 页面保持,不销毁,需要设置config.KEEP_PAGE_ALIVE === true 才生效 56 | keepAlive, 57 | // 是否添加withRouter高级组件 58 | router = true, 59 | // props是否注入ajax 60 | ajax = CONFIG_HOC.ajax, 61 | // 连接models,扩展 props.action 62 | connect = CONFIG_HOC.connect, 63 | // 弹框高阶组件 64 | modal, 65 | // 抽屉高级组件 66 | drawer, 67 | ...others 68 | } = options; 69 | 70 | // config 传递 参数校验 71 | if (modal && drawer) throw Error('[config hoc] modal and drawer config can not be used together!'); 72 | 73 | const hoc = []; 74 | 75 | // 公共高阶组件 76 | hoc.push(commonHoc(options)); 77 | 78 | // 弹框高阶组件 79 | if (modal) hoc.push(modalHoc(modal, IS_MOBILE)); 80 | 81 | // 抽屉高阶组件 82 | if (drawer) hoc.push(drawerHoc(drawer)); 83 | 84 | // redux 连接高阶组件 85 | if (connect === true) hoc.push(reduxConnect()); 86 | if (typeof connect === 'function') hoc.push(reduxConnect(connect)); 87 | 88 | // ajax高阶组件 89 | if (ajax) hoc.push(ajaxHoc()); 90 | 91 | // 路由高阶组件 92 | if (router) hoc.push(withRouter); 93 | 94 | // 放到最后,一些函数式配置,可以获取到更多的props数据 95 | hoc.push(layoutHoc(options)); 96 | 97 | return createConfigHoc({ 98 | hoc, 99 | onConstructor: () => void 0, 100 | onDidMount: () => void 0, 101 | onUnmount: () => void 0, 102 | })({ ...options, ...others }); 103 | } 104 | -------------------------------------------------------------------------------- /src/commons/handle-error.js: -------------------------------------------------------------------------------- 1 | import { notification, Modal } from 'antd'; 2 | import { toLogin } from './index'; 3 | 4 | const ERROR_SERVER = '系统开小差了,请稍后再试或联系管理员!'; 5 | const ERROR_NOT_FOUND = '您访问的资源不存在!'; 6 | const ERROR_FORBIDDEN = '您无权访问!'; 7 | const ERROR_UNKNOWN = '未知错误'; 8 | const TIP_TITLE = '温馨提示'; 9 | const TIP = '失败'; 10 | 11 | function getErrorTip(error, tip) { 12 | if (tip && tip !== true) return tip; 13 | 14 | // http 状态码相关 15 | if (error?.response) { 16 | const { status } = error.response; 17 | 18 | if (status === 401) return toLogin(); 19 | if (status === 403) return ERROR_FORBIDDEN; 20 | if (status === 404) return ERROR_NOT_FOUND; 21 | if (status >= 500) return ERROR_SERVER; 22 | } 23 | 24 | // 后端自定义信息 25 | const data = error?.response?.data || error; 26 | 27 | if (typeof data === 'string') return data; 28 | if (data?.message) return data.message; 29 | if (data?.msg) return data.msg; 30 | 31 | return ERROR_UNKNOWN; 32 | } 33 | 34 | export default function handleError({ error, tip, options = {} }) { 35 | const description = getErrorTip(error, tip); 36 | const { errorModal } = options; 37 | 38 | if (!description && !errorModal) return; 39 | 40 | // 避免卡顿 41 | setTimeout(() => { 42 | // 弹框提示 43 | if (errorModal) { 44 | // 详细配置 45 | if (typeof errorModal === 'object') { 46 | return Modal.error({ 47 | title: TIP_TITLE, 48 | content: description, 49 | ...errorModal, 50 | }); 51 | } 52 | 53 | return Modal.error({ 54 | title: TIP_TITLE, 55 | content: description, 56 | }); 57 | } 58 | 59 | // 右上角滑出提示 60 | notification.error({ 61 | message: TIP, 62 | description, 63 | duration: 2, 64 | }); 65 | }); 66 | } 67 | -------------------------------------------------------------------------------- /src/commons/handle-success.js: -------------------------------------------------------------------------------- 1 | import { notification, Modal } from 'antd'; 2 | 3 | const TIP_TITLE = '温馨提示'; 4 | const TIP = '成功'; 5 | 6 | export default function handleSuccess({ tip, options = {} }) { 7 | const { successModal } = options; 8 | 9 | if (!tip && !successModal) return; 10 | 11 | // 避免卡顿 12 | setTimeout(() => { 13 | // 弹框方式显示提示 14 | if (successModal) { 15 | // 详细配置 16 | if (typeof successModal === 'object') { 17 | return Modal.success({ 18 | title: TIP_TITLE, 19 | content: tip, 20 | ...successModal, 21 | }); 22 | } 23 | 24 | return Modal.success({ 25 | title: TIP_TITLE, 26 | content: successModal, 27 | }); 28 | } 29 | 30 | notification.success({ 31 | message: TIP, 32 | description: tip, 33 | duration: 2, 34 | }); 35 | }); 36 | } 37 | -------------------------------------------------------------------------------- /src/commons/index.js: -------------------------------------------------------------------------------- 1 | import {match} from 'path-to-regexp'; 2 | import moment from 'moment'; 3 | import {isActiveApp} from '../qiankun'; 4 | import api from 'src/api'; 5 | import {BASE_NAME, HASH_ROUTER, IS_SUB, NO_AUTH_ROUTES} from '../config'; 6 | import {getMainApp, isLoginPage, getParentOrigin} from '@ra-lib/admin'; 7 | import pageConfigs from 'src/pages/page-configs'; 8 | 9 | /** 10 | * 浏览器跳转,携带baseName hash等 11 | * @param href 12 | * @returns {string|*} 13 | */ 14 | export function locationHref(href) { 15 | if (href?.startsWith('http')) return (window.location.href = href); 16 | 17 | if (href && BASE_NAME && href.startsWith(BASE_NAME)) href = href.replace(BASE_NAME, ''); 18 | 19 | const hash = HASH_ROUTER ? '#' : ''; 20 | 21 | return (window.location.href = `${BASE_NAME}${hash}${href}`); 22 | } 23 | 24 | /** 25 | * 进入首页 26 | */ 27 | export function toHome() { 28 | // 跳转页面,优先跳转上次登出页面 29 | let lastHref = window.sessionStorage.getItem('last-href') || '/'; 30 | 31 | const url = lastHref.startsWith('http') ? new URL(lastHref) : {pathname: '/'}; 32 | 33 | // 上次是非登录页面,直接跳转首页 34 | if (isNoAuthPage(url.pathname)) lastHref = '/'; 35 | 36 | locationHref(lastHref); 37 | 38 | if (HASH_ROUTER) window.location.reload(); 39 | } 40 | 41 | /** 42 | * 跳转到登录页面 43 | */ 44 | export function toLogin() { 45 | const loginPath = '/login'; 46 | 47 | // 判断当前页面是否已经是login页面,如果是,直接返回,不进行跳转,防止出现跳转死循环 48 | if (isLoginPage()) return null; 49 | 50 | // 清除相关数据 51 | window.sessionStorage.clear(); 52 | window.sessionStorage.setItem('last-href', window.location.href); 53 | 54 | if (IS_SUB) { 55 | // 微前端,跳转主应用登录 56 | const mainToLogin = getMainApp()?.toLogin; 57 | if (mainToLogin) return mainToLogin(); 58 | 59 | // 嵌入iframe中 60 | const parentOrigin = getParentOrigin(); 61 | if (parentOrigin) return (window.location.href = `${parentOrigin}/error-401.html`); 62 | } 63 | 64 | locationHref(loginPath); 65 | 66 | if (HASH_ROUTER) window.location.reload(); 67 | 68 | return null; 69 | } 70 | 71 | /** 72 | * 检测路由配置冲突 73 | * @param result 74 | * @returns {string|boolean} 75 | */ 76 | export async function checkPath(result) { 77 | const subApps = await api.getSubApps(); 78 | 79 | const hasHome = result.some(({path}) => path === '/'); 80 | if (!hasHome) throw Error(`必须含有首页路由,path: '/', 如果需要其他页面做首页,可以进行 Redirect`); 81 | 82 | result 83 | .filter(({path}) => !!path) 84 | .forEach(({path, filePath}) => { 85 | // 是否与子项目配置冲突 86 | const app = subApps.find((item) => isActiveApp(item, path)); 87 | if (app) 88 | throw Error( 89 | `路由地址:「${path}」 与 子项目 「${ 90 | app.title || app.name 91 | }」 激活规则配置冲突,对应文件文件如下:\n${filePath}`, 92 | ); 93 | 94 | // 自身路由配置是否冲突 95 | const exit = result.find(({filePath: f, path: p}) => { 96 | if (f === filePath) return false; 97 | 98 | if (!p || !path) return false; 99 | 100 | if (p === path) return true; 101 | 102 | return match(path, {decode: decodeURIComponent})(p) || match(p, {decode: decodeURIComponent})(path); 103 | }); 104 | if (exit) 105 | throw Error( 106 | `路由地址:「${path}」 与 「${exit.path}」 配置冲突,对应文件文件如下:\n${filePath}\n${exit.filePath}`, 107 | ); 108 | }); 109 | } 110 | 111 | /** 112 | * 基于 window.location.pathname pageConfig 获取当前页面config高级组件参数 113 | * @returns {{}|*} 114 | */ 115 | export function getCurrentPageConfig() { 116 | let {pathname, hash} = window.location; 117 | if (HASH_ROUTER) { 118 | pathname = hash.replace('#', '').split('?')[0]; 119 | } else if (BASE_NAME) { 120 | pathname = pathname.replace(BASE_NAME, ''); 121 | } 122 | 123 | const config = pageConfigs.find(({path}) => path && match(path, {decode: decodeURIComponent})(pathname)); 124 | 125 | return config || {}; 126 | } 127 | 128 | /** 129 | * 加载js文件 130 | * @param url 131 | * @returns {Promise} 132 | */ 133 | export function loadScript(url) { 134 | return new Promise((resolve, reject) => { 135 | const script = document.createElement('script'); 136 | script.onload = resolve; 137 | script.src = url; 138 | script.onerror = reject; 139 | document.head.appendChild(script); 140 | }); 141 | } 142 | 143 | /** 144 | * 不需要登录的页面配置 145 | * @param pathname 146 | * @returns {boolean} 147 | */ 148 | export function isNoAuthPage(pathname) { 149 | return NO_AUTH_ROUTES.includes(pathname || window.location.pathname); 150 | } 151 | 152 | // table column 渲染时间 153 | export function renderTime(format = 'YYYY-MM-DD HH:mm:ss') { 154 | return (value) => { 155 | if (!value) return '-'; 156 | 157 | return moment(value).format(format); 158 | }; 159 | } 160 | 161 | // table column 渲染日期 162 | export function renderDate(format = 'YYYY-MM-DD') { 163 | return (value) => { 164 | if (!value) return '-'; 165 | 166 | return moment(value).format(format); 167 | }; 168 | } 169 | -------------------------------------------------------------------------------- /src/components/error/Error404.jsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from 'react'; 2 | import { Error404 } from '@ra-lib/admin'; 3 | import { getCurrentActiveSubApp } from 'src/qiankun'; 4 | import { IS_SUB } from 'src/config'; 5 | 6 | export default function MyError404() { 7 | const [isSubApp, setIsSubApp] = useState(true); 8 | 9 | const { pathname } = window.location; 10 | 11 | // 检测是否是子应用 12 | useEffect(() => { 13 | (async () => { 14 | const subApp = await getCurrentActiveSubApp(); 15 | setIsSubApp(!!subApp); 16 | })(); 17 | }, [pathname]); 18 | 19 | if (isSubApp && !IS_SUB) return null; 20 | 21 | return ; 22 | } 23 | -------------------------------------------------------------------------------- /src/components/footer/index.jsx: -------------------------------------------------------------------------------- 1 | // import styles from './style.less'; 2 | 3 | export default function Footer(props) { 4 | return null; 5 | // return ( 6 | //
7 | // 京公网安备00000000000001号 京ICP证000001号 ©2021 xxx 8 | //
9 | // ); 10 | } 11 | -------------------------------------------------------------------------------- /src/components/footer/style.less: -------------------------------------------------------------------------------- 1 | .root { 2 | display: flex; 3 | align-items: center; 4 | justify-content: center; 5 | font-size: 12px; 6 | padding-bottom: 8px; 7 | color: #bbb; 8 | } 9 | -------------------------------------------------------------------------------- /src/components/generator/index.jsx: -------------------------------------------------------------------------------- 1 | import { useState } from 'react'; 2 | import { Modal, Button } from 'antd'; 3 | import { CodeOutlined } from '@ant-design/icons'; 4 | import s from './style.less'; 5 | 6 | export default function Generator() { 7 | const [visible, setVisible] = useState(false); 8 | return ( 9 | <> 10 | 13 | 24 |