├── src ├── components │ ├── left │ │ ├── left.less │ │ └── left.tsx │ ├── header │ │ ├── header.tsx │ │ └── header.less │ └── content │ │ ├── content.less │ │ └── content.tsx ├── sessionStorage │ └── api.js ├── store │ ├── index.js │ └── reducer.js ├── index.tsx ├── admin │ ├── admin.less │ └── admin.tsx ├── react-app-env.d.ts ├── config.js └── serviceWorker.ts ├── public ├── favicon.ico ├── manifest.json └── index.html ├── config ├── jest │ ├── cssTransform.js │ └── fileTransform.js ├── pnpTs.js ├── modules.js ├── paths.js ├── env.js ├── webpackDevServer.config.js └── webpack.config.js ├── .gitignore ├── tsconfig.json ├── scripts ├── test.js ├── start.js └── build.js ├── README.md └── package.json /src/components/left/left.less: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EdenStrive/X-admin/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /src/sessionStorage/api.js: -------------------------------------------------------------------------------- 1 | const setStorage = (key,value) =>{ 2 | sessionStorage.setItem(key, value); 3 | } 4 | 5 | const getStorage = (key)=>{ 6 | return sessionStorage.getItem(key); 7 | } 8 | 9 | export { setStorage, getStorage } -------------------------------------------------------------------------------- /src/store/index.js: -------------------------------------------------------------------------------- 1 | import { createStore } from 'redux'; 2 | import reducer from './reducer' 3 | 4 | const store = createStore( reducer 5 | , window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__() 6 | ); 7 | 8 | export default store; -------------------------------------------------------------------------------- /src/components/header/header.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import './header.less'; 3 | 4 | const Header: React.FC= () => { 5 | return ( 6 |
7 |
X-admin
8 |
9 |
10 | ); 11 | } 12 | 13 | export default Header; 14 | -------------------------------------------------------------------------------- /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 | "start_url": ".", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /config/jest/cssTransform.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // This is a custom Jest transformer turning style imports into empty objects. 4 | // http://facebook.github.io/jest/docs/en/webpack.html 5 | 6 | module.exports = { 7 | process() { 8 | return 'module.exports = {};'; 9 | }, 10 | getCacheKey() { 11 | // The output is always the same. 12 | return 'cssTransform'; 13 | }, 14 | }; 15 | -------------------------------------------------------------------------------- /src/components/content/content.less: -------------------------------------------------------------------------------- 1 | .editable-cell { 2 | position: relative; 3 | } 4 | .editable-cell-value-wrap { 5 | padding: 5px 12px; 6 | cursor: pointer; 7 | } 8 | .editable-row:hover .editable-cell-value-wrap { 9 | border: 1px solid #d9d9d9; 10 | border-radius: 4px; 11 | padding: 4px 11px; 12 | } 13 | .t_content{ 14 | border: 1px solid #e4e1e1; 15 | margin-top: 24px; 16 | width: 90%; 17 | margin-left: 5%; 18 | border-radius: 3px; 19 | } -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | X-admin 10 | 11 | 12 |
13 | 14 | 15 | -------------------------------------------------------------------------------- /src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './admin/admin'; 4 | import * as serviceWorker from './serviceWorker'; 5 | 6 | ReactDOM.render(, document.getElementById('root')); 7 | 8 | // If you want your app to work offline and load faster, you can change 9 | // unregister() to register() below. Note this comes with some pitfalls. 10 | // Learn more about service workers: https://bit.ly/CRA-PWA 11 | serviceWorker.unregister(); 12 | -------------------------------------------------------------------------------- /.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 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | 25 | 26 | # 27 | node_modules/ 28 | 29 | # 30 | dist/ 31 | 32 | # 33 | package-lock.json -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "strict": true, 14 | "forceConsistentCasingInFileNames": true, 15 | "module": "esnext", 16 | "moduleResolution": "node", 17 | "resolveJsonModule": true, 18 | "isolatedModules": true, 19 | "noEmit": true, 20 | "jsx": "preserve" 21 | }, 22 | "include": [ 23 | "src" 24 | ] 25 | } 26 | -------------------------------------------------------------------------------- /src/components/header/header.less: -------------------------------------------------------------------------------- 1 | .h_body{ 2 | position: relative; 3 | top: 0px; 4 | left: 0px; 5 | // border:1px solid black; 6 | height: 80px; 7 | width: 100%; 8 | display: flex; 9 | flex-direction: row; 10 | div:nth-child(1){ 11 | width: 230px; 12 | font-size: 30px; 13 | // border:1px solid rgb(190, 35, 35); 14 | text-align: center; 15 | line-height: 70px; 16 | color: rgb(255,255,255); 17 | background: rgb(117, 113, 249); 18 | min-width: 230px; 19 | } 20 | div:nth-child(2){ 21 | // border:10px solid rgb(47, 82, 158); 22 | width: calc(100vw - 230px); 23 | min-width: 970px; 24 | background: rgb(235, 245, 255) 25 | } 26 | } -------------------------------------------------------------------------------- /config/pnpTs.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { resolveModuleName } = require('ts-pnp'); 4 | 5 | exports.resolveModuleName = ( 6 | typescript, 7 | moduleName, 8 | containingFile, 9 | compilerOptions, 10 | resolutionHost 11 | ) => { 12 | return resolveModuleName( 13 | moduleName, 14 | containingFile, 15 | compilerOptions, 16 | resolutionHost, 17 | typescript.resolveModuleName 18 | ); 19 | }; 20 | 21 | exports.resolveTypeReferenceDirective = ( 22 | typescript, 23 | moduleName, 24 | containingFile, 25 | compilerOptions, 26 | resolutionHost 27 | ) => { 28 | return resolveModuleName( 29 | moduleName, 30 | containingFile, 31 | compilerOptions, 32 | resolutionHost, 33 | typescript.resolveTypeReferenceDirective 34 | ); 35 | }; 36 | -------------------------------------------------------------------------------- /src/admin/admin.less: -------------------------------------------------------------------------------- 1 | 2 | *{ 3 | margin: 0px; 4 | padding: 0px; 5 | border: 0px; 6 | } 7 | 8 | .father{ 9 | position: relative; 10 | width: 100%; 11 | height: 100%; 12 | min-width: 1200px; 13 | 14 | .headers{ 15 | position: relative; 16 | top: 0px; 17 | left: 0px; 18 | // border:1px solid black; 19 | height: 80px; 20 | width: 100%; 21 | } 22 | } 23 | 24 | 25 | .bodys{ 26 | position: relative; 27 | width: 100%; 28 | height: 100%; 29 | display: flex; 30 | flex-direction: row; 31 | .left{ 32 | position: relative; 33 | width: 230px; 34 | left: 0px; 35 | min-height: calc(100vh - 80px); 36 | // border:1px solid red 37 | } 38 | .right{ 39 | position: relative; 40 | height: 100%; 41 | // border:1px solid green; 42 | width: calc(100% - 230px); 43 | min-height: calc(100vh - 80px); 44 | background-color: rgb(255, 255, 255) 45 | } 46 | } 47 | 48 | .xxx{ 49 | font-size: 25px; 50 | margin-top: 30px; 51 | margin-left: 20px; 52 | } -------------------------------------------------------------------------------- /src/admin/admin.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import Header from '../components/header/header' 3 | import Left from '../components/left/left' 4 | import store from '../store/index' 5 | import Content from '../components/content/content' 6 | import './admin.less'; 7 | 8 | const App: React.FC= () => { 9 | const [welcome, setWelco] = useState(false) 10 | 11 | const viewTable = ()=>{ 12 | setWelco(true) 13 | console.log("最后删除的元素为:",store.getState().delete) 14 | console.log("最后添加的元素为:",store.getState().add) 15 | } 16 | 17 | store.subscribe(viewTable) 18 | 19 | return ( 20 |
21 | {/* 后台管理头部 */} 22 |
23 |
24 |
25 | 26 | {/* 后台管理底部 */} 27 |
28 | 29 | {/* body左导航 */} 30 |
31 | 32 |
33 | 34 | {/* 内容 */} 35 |
36 | 37 | {welcome ? :

点击左侧导航进行每个表格的新增、编辑与删除

} 38 |
39 | 40 |
41 |
42 | ); 43 | } 44 | 45 | export default App; 46 | -------------------------------------------------------------------------------- /config/jest/fileTransform.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const path = require('path'); 4 | const camelcase = require('camelcase'); 5 | 6 | // This is a custom Jest transformer turning file imports into filenames. 7 | // http://facebook.github.io/jest/docs/en/webpack.html 8 | 9 | module.exports = { 10 | process(src, filename) { 11 | const assetFilename = JSON.stringify(path.basename(filename)); 12 | 13 | if (filename.match(/\.svg$/)) { 14 | // Based on how SVGR generates a component name: 15 | // https://github.com/smooth-code/svgr/blob/01b194cf967347d43d4cbe6b434404731b87cf27/packages/core/src/state.js#L6 16 | const pascalCaseFileName = camelcase(path.parse(filename).name, { 17 | pascalCase: true, 18 | }); 19 | const componentName = `Svg${pascalCaseFileName}`; 20 | return `const React = require('react'); 21 | module.exports = { 22 | __esModule: true, 23 | default: ${assetFilename}, 24 | ReactComponent: React.forwardRef(function ${componentName}(props, ref) { 25 | return { 26 | $$typeof: Symbol.for('react.element'), 27 | type: 'svg', 28 | ref: ref, 29 | key: null, 30 | props: Object.assign({}, props, { 31 | children: ${assetFilename} 32 | }) 33 | }; 34 | }), 35 | };`; 36 | } 37 | 38 | return `module.exports = ${assetFilename};`; 39 | }, 40 | }; 41 | -------------------------------------------------------------------------------- /src/store/reducer.js: -------------------------------------------------------------------------------- 1 | //由于同级组件之间传参非常的麻烦,所以此处用redux 2 | const defaultState = { 3 | admin: 0, 4 | content:[ 5 | [ 6 | { 7 | key: 0 8 | } 9 | ] 10 | ], 11 | delete:null, 12 | add:null 13 | } 14 | 15 | 16 | 17 | 18 | 19 | //行为 20 | export default (state = defaultState, action)=>{ 21 | //reducer 可以接受state,但是绝对不能修改state 22 | const newState = JSON.parse(JSON.stringify(state)) //对数据进行深拷贝 23 | 24 | if (action.type == "change_admin") { 25 | newState.admin = action.value 26 | if (newState.content[action.value] == undefined) { 27 | newState.content[action.value] = [ 28 | { 29 | key: 0 30 | } 31 | ] 32 | } 33 | }else if(action.type == "change_content"){ 34 | newState.content[action.value] = action.content 35 | }else if(action.type == "change_session"){ 36 | newState.admin = action.value 37 | newState.content[action.value] = JSON.parse(action.local) 38 | }else if(action.type == "bug_bug"){ 39 | newState.content[0] = JSON.parse(action.value) 40 | }else if(action.type == "delete"){ 41 | newState.delete = action.key 42 | }else if(action.type == "add"){ 43 | newState.add = action.key 44 | } 45 | //新的newState会自动替换老的state 46 | return newState 47 | } -------------------------------------------------------------------------------- /src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | /// 4 | 5 | declare namespace NodeJS { 6 | interface ProcessEnv { 7 | readonly NODE_ENV: 'development' | 'production' | 'test'; 8 | readonly PUBLIC_URL: string; 9 | } 10 | } 11 | 12 | declare module '*.bmp' { 13 | const src: string; 14 | export default src; 15 | } 16 | 17 | declare module '*.gif' { 18 | const src: string; 19 | export default src; 20 | } 21 | 22 | declare module '*.jpg' { 23 | const src: string; 24 | export default src; 25 | } 26 | 27 | declare module '*.jpeg' { 28 | const src: string; 29 | export default src; 30 | } 31 | 32 | declare module '*.png' { 33 | const src: string; 34 | export default src; 35 | } 36 | 37 | declare module '*.webp' { 38 | const src: string; 39 | export default src; 40 | } 41 | 42 | declare module '*.svg' { 43 | import * as React from 'react'; 44 | 45 | export const ReactComponent: React.FunctionComponent>; 46 | 47 | const src: string; 48 | export default src; 49 | } 50 | 51 | declare module '*.module.css' { 52 | const classes: { [key: string]: string }; 53 | export default classes; 54 | } 55 | 56 | declare module '*.module.scss' { 57 | const classes: { [key: string]: string }; 58 | export default classes; 59 | } 60 | 61 | declare module '*.module.sass' { 62 | const classes: { [key: string]: string }; 63 | export default classes; 64 | } 65 | -------------------------------------------------------------------------------- /scripts/test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Do this as the first thing so that any code reading it knows the right env. 4 | process.env.BABEL_ENV = 'test'; 5 | process.env.NODE_ENV = 'test'; 6 | process.env.PUBLIC_URL = ''; 7 | 8 | // Makes the script crash on unhandled rejections instead of silently 9 | // ignoring them. In the future, promise rejections that are not handled will 10 | // terminate the Node.js process with a non-zero exit code. 11 | process.on('unhandledRejection', err => { 12 | throw err; 13 | }); 14 | 15 | // Ensure environment variables are read. 16 | require('../config/env'); 17 | 18 | 19 | const jest = require('jest'); 20 | const execSync = require('child_process').execSync; 21 | let argv = process.argv.slice(2); 22 | 23 | function isInGitRepository() { 24 | try { 25 | execSync('git rev-parse --is-inside-work-tree', { stdio: 'ignore' }); 26 | return true; 27 | } catch (e) { 28 | return false; 29 | } 30 | } 31 | 32 | function isInMercurialRepository() { 33 | try { 34 | execSync('hg --cwd . root', { stdio: 'ignore' }); 35 | return true; 36 | } catch (e) { 37 | return false; 38 | } 39 | } 40 | 41 | // Watch unless on CI or explicitly running all tests 42 | if ( 43 | !process.env.CI && 44 | argv.indexOf('--watchAll') === -1 45 | ) { 46 | // https://github.com/facebook/create-react-app/issues/5210 47 | const hasSourceControl = isInGitRepository() || isInMercurialRepository(); 48 | argv.push(hasSourceControl ? '--watch' : '--watchAll'); 49 | } 50 | 51 | 52 | jest.run(argv); 53 | -------------------------------------------------------------------------------- /src/config.js: -------------------------------------------------------------------------------- 1 | const obj = { 2 | //左边导航内容 3 | sub:[ 4 | ["sub1","mail","Part One",[ 5 | 6 | [0,"班级"], 7 | [1,"水果"] 8 | 9 | ]], 10 | ["sub2","appstore","Part Two",[ 11 | 12 | [2,"工单"], 13 | 14 | ]] 15 | ], 16 | 17 | //表格内容 18 | content:[ 19 | [ 20 | { 21 | title: '姓名', 22 | dataIndex: '姓名', 23 | width: '30%', 24 | editable: true, 25 | }, 26 | { 27 | title: '年龄', 28 | dataIndex: '年龄', 29 | editable: true, 30 | }, 31 | { 32 | title: '学号', 33 | dataIndex: '学号', 34 | editable: true, 35 | } 36 | ], 37 | 38 | [ 39 | { 40 | title: '果名', 41 | dataIndex: '果名', 42 | width: '20%', 43 | editable: true, 44 | }, 45 | { 46 | title: '大小', 47 | dataIndex: '大小', 48 | editable: true, 49 | }, 50 | { 51 | title: '价格', 52 | dataIndex: '价格', 53 | editable: true, 54 | } 55 | ], 56 | 57 | [ 58 | { 59 | title: '名称', 60 | dataIndex: '名称', 61 | width: '20%', 62 | editable: true, 63 | }, 64 | { 65 | title: '价格', 66 | dataIndex: '价格', 67 | editable: true, 68 | } 69 | 70 | ] 71 | 72 | 73 | 74 | ] 75 | } 76 | export default obj -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # X-admin 2 | 基于React(hook)+Typescript+antd的后台管理系统的脚手架,只需配置文件即可 3 | 4 | 效果预览地址: 5 | - [点击这里](http://www.xingwenpeng.cn:8010/) 6 | 7 | 2019-06-24 8 | 9 | > * 添加项目整体目录完成 10 | 11 | 2019-07-06 12 | 13 | > * 实践react hook搭配antd(Ts写法) 14 | 15 | > * 添加配置文件config.js,实现配置文件实现动态增加左侧导航功能 16 | 17 | 2019-07-08 18 | 19 | > * 基本完成脚手架(后续进行完善) 20 | 21 | 2019-07-09 22 | 23 | - [x] 功能已全部实现 24 | - [x] 暴露接口 25 | 26 | 2019-07-12 27 | 28 | - [ ] 反馈有bug,解决中(无法及时响应删除的添加的反应,逐条处理) 29 | 30 | ------ 31 | **框架使用:** 32 | 33 | 环境要求: 34 | 35 | > * node版本 10+ 36 | > * npm版本 6+ 37 | 38 | ```javascript 39 | 1.首先git clone下来 40 | 2.使用脚手架目录打开cmd,输入命令npm install(如果失败请使用管理员权限打开cmd) 41 | 3.在命令行中输入npm start 42 | ``` 43 | 44 | 打开界面:![cmd-markdown-logo](http://i1.cy.com/x/jiemian.jpg) 45 | 46 | 红色框框处使我们第一个需要配置的地方: 47 | 48 | 1.打开src目录下面的config.js 49 | 2.进行配置: 50 | 51 | ![cmd-markdown-logo](http://i1.cy.com/x/peizhi.png) 52 | 53 | sub对应的为为左侧导航。content为导航所对应的表格字段 54 | 55 | 白色箭头所指数组的前三个元素分别对应导航key值,导航的图标,以及导航的名称。 56 | 57 | 数组第四个元素为数组,对应为导航子元素的key值以及子元素的名称。 58 | 59 | 注意:每次添加完子元素时。都要在下面content数组中对应的key值中添加相应的表格字段,比如班级的ke y为0,那就在content数组中0位置添加表格字段。 60 | 61 | 表格字段可选width,title,以及是否可以被编辑editable 62 | 63 | ---- 64 | 65 | 1.都设置完成后打开界面 66 | ![cmd-markdown-logo](http://i1.cy.com/x/zhanshi.png) 67 | 68 | 2. 如下图可以添加表格内容,删除内容,直接点击空表格进行编辑(刷新页面不会使数据丢失) 69 | 70 | ![cmd-markdown-logo](http://i1.cy.com/x/shanchu.png) 71 | 72 | 3.在admin.tsx中暴露新增数据与删除数据的接口,删除元素为子导航的key+被删除数据的key,添加为被删除数据的key+子导航的key。 73 | 74 | ![cmd-markdown-logo](http://i1.cy.com/x/jiekou.png) 75 | 76 | ---- 77 | 78 | 总结:框架还在完善中,有很多需要完善的地方,框架扩展性比较强。都看到这里了,大佬点个start吧 79 | ![cmd-markdown-logo](http://i1.cy.com/x/kule.png) 80 | -------------------------------------------------------------------------------- /config/modules.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const fs = require('fs'); 4 | const path = require('path'); 5 | const paths = require('./paths'); 6 | const chalk = require('react-dev-utils/chalk'); 7 | 8 | /** 9 | * Get the baseUrl of a compilerOptions object. 10 | * 11 | * @param {Object} options 12 | */ 13 | function getAdditionalModulePaths(options = {}) { 14 | const baseUrl = options.baseUrl; 15 | 16 | // We need to explicitly check for null and undefined (and not a falsy value) because 17 | // TypeScript treats an empty string as `.`. 18 | if (baseUrl == null) { 19 | // If there's no baseUrl set we respect NODE_PATH 20 | // Note that NODE_PATH is deprecated and will be removed 21 | // in the next major release of create-react-app. 22 | 23 | const nodePath = process.env.NODE_PATH || ''; 24 | return nodePath.split(path.delimiter).filter(Boolean); 25 | } 26 | 27 | const baseUrlResolved = path.resolve(paths.appPath, baseUrl); 28 | 29 | // We don't need to do anything if `baseUrl` is set to `node_modules`. This is 30 | // the default behavior. 31 | if (path.relative(paths.appNodeModules, baseUrlResolved) === '') { 32 | return null; 33 | } 34 | 35 | // Allow the user set the `baseUrl` to `appSrc`. 36 | if (path.relative(paths.appSrc, baseUrlResolved) === '') { 37 | return [paths.appSrc]; 38 | } 39 | 40 | // Otherwise, throw an error. 41 | throw new Error( 42 | chalk.red.bold( 43 | "Your project's `baseUrl` can only be set to `src` or `node_modules`." + 44 | ' Create React App does not support other values at this time.' 45 | ) 46 | ); 47 | } 48 | 49 | function getModules() { 50 | // Check if TypeScript is setup 51 | const hasTsConfig = fs.existsSync(paths.appTsConfig); 52 | const hasJsConfig = fs.existsSync(paths.appJsConfig); 53 | 54 | if (hasTsConfig && hasJsConfig) { 55 | throw new Error( 56 | 'You have both a tsconfig.json and a jsconfig.json. If you are using TypeScript please remove your jsconfig.json file.' 57 | ); 58 | } 59 | 60 | let config; 61 | 62 | // If there's a tsconfig.json we assume it's a 63 | // TypeScript project and set up the config 64 | // based on tsconfig.json 65 | if (hasTsConfig) { 66 | config = require(paths.appTsConfig); 67 | // Otherwise we'll check if there is jsconfig.json 68 | // for non TS projects. 69 | } else if (hasJsConfig) { 70 | config = require(paths.appJsConfig); 71 | } 72 | 73 | config = config || {}; 74 | const options = config.compilerOptions || {}; 75 | 76 | const additionalModulePaths = getAdditionalModulePaths(options); 77 | 78 | return { 79 | additionalModulePaths: additionalModulePaths, 80 | hasTsConfig, 81 | }; 82 | } 83 | 84 | module.exports = getModules(); 85 | -------------------------------------------------------------------------------- /src/components/left/left.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react'; 2 | import { Menu, Icon } from 'antd'; 3 | import { getStorage } from '../../sessionStorage/api' 4 | import obj from '../../config' 5 | import store from '../../store/index' 6 | import 'antd/dist/antd.css'; 7 | import './left.less'; 8 | 9 | const { SubMenu } = Menu; 10 | const Left: React.FC= () => { 11 | 12 | //初始化状态 openKeys为 sub1 同时定义状态为string 13 | const [ openKeys , setOpenKeys ] = useState(["sub1"]) 14 | const [ rootSubmenuKeys, setRoot] = useState([]) //切换 15 | const [ meau , setMeau ] = useState([]) 16 | 17 | //副作用 18 | const onOpenChange = (openKeys:string[]) =>{ 19 | const latestOpenKey:any = openKeys.find(key => openKeys.indexOf(key) === -1); 20 | if(rootSubmenuKeys.indexOf(latestOpenKey) === -1){ 21 | //hook特性不需要加花括号 22 | setOpenKeys(openKeys); 23 | }else { 24 | setOpenKeys( 25 | latestOpenKey ? [latestOpenKey] : [] 26 | ); 27 | } 28 | } 29 | 30 | const change_content = (key:any)=>{ 31 | let keys = key.key 32 | if (getStorage(keys) == undefined) { 33 | const action = { 34 | type:"change_admin", 35 | value:keys 36 | } 37 | store.dispatch(action) 38 | }else{ 39 | const action = { 40 | type:"change_session", 41 | value:keys, 42 | local: getStorage(keys) 43 | } 44 | store.dispatch(action) 45 | } 46 | } 47 | 48 | useEffect(()=>{ 49 | // console.log(obj.sub) //sub就是配置文件中的左边导航 50 | let sub:any[] = obj.sub 51 | let total_arr:any[] = [] 52 | let root:any[] = [] 53 | 54 | for(let id = 0; id < sub.length; id++){ 55 | let child_arr:any[] = [] 56 | let big_key = sub[id][0] //big key 57 | let big_type = sub[id][1]//big type 58 | let big_name = sub[id][2]//big name 59 | let child = sub[id][3] 60 | for(let id in child){ 61 | let key = child[id][0] 62 | let content = child[id][1] 63 | let childs = ({content}) 64 | child_arr.push(childs) 65 | } 66 | let part = ( {big_name} } > 67 | {child_arr} 68 | ) 69 | total_arr.push(part) 70 | root.push(big_key) 71 | } 72 | setRoot(root) 73 | setMeau(total_arr) 74 | },[]) 75 | 76 | return ( 77 |
78 | 79 | {meau} 80 | 81 |
82 | ); 83 | } 84 | 85 | export default Left; 86 | -------------------------------------------------------------------------------- /config/paths.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const path = require('path'); 4 | const fs = require('fs'); 5 | const url = require('url'); 6 | 7 | // Make sure any symlinks in the project folder are resolved: 8 | // https://github.com/facebook/create-react-app/issues/637 9 | const appDirectory = fs.realpathSync(process.cwd()); 10 | const resolveApp = relativePath => path.resolve(appDirectory, relativePath); 11 | 12 | const envPublicUrl = process.env.PUBLIC_URL; 13 | 14 | function ensureSlash(inputPath, needsSlash) { 15 | const hasSlash = inputPath.endsWith('/'); 16 | if (hasSlash && !needsSlash) { 17 | return inputPath.substr(0, inputPath.length - 1); 18 | } else if (!hasSlash && needsSlash) { 19 | return `${inputPath}/`; 20 | } else { 21 | return inputPath; 22 | } 23 | } 24 | 25 | const getPublicUrl = appPackageJson => 26 | envPublicUrl || require(appPackageJson).homepage; 27 | 28 | // We use `PUBLIC_URL` environment variable or "homepage" field to infer 29 | // "public path" at which the app is served. 30 | // Webpack needs to know it to put the right