| @ -0,0 +1,123 @@ | |||||
|  | # ---> Node | ||||
|  | # Logs | ||||
|  | logs | ||||
|  | *.log | ||||
|  | npm-debug.log* | ||||
|  | yarn-debug.log* | ||||
|  | yarn-error.log* | ||||
|  | lerna-debug.log* | ||||
|  | .pnpm-debug.log* | ||||
|  | 
 | ||||
|  | # Diagnostic reports (https://nodejs.org/api/report.html) | ||||
|  | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json | ||||
|  | 
 | ||||
|  | # Runtime data | ||||
|  | pids | ||||
|  | *.pid | ||||
|  | *.seed | ||||
|  | *.pid.lock | ||||
|  | 
 | ||||
|  | # Directory for instrumented libs generated by jscoverage/JSCover | ||||
|  | lib-cov | ||||
|  | 
 | ||||
|  | # Coverage directory used by tools like istanbul | ||||
|  | coverage | ||||
|  | *.lcov | ||||
|  | 
 | ||||
|  | # nyc test coverage | ||||
|  | .nyc_output | ||||
|  | 
 | ||||
|  | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) | ||||
|  | .grunt | ||||
|  | 
 | ||||
|  | # Bower dependency directory (https://bower.io/) | ||||
|  | bower_components | ||||
|  | 
 | ||||
|  | # node-waf configuration | ||||
|  | .lock-wscript | ||||
|  | 
 | ||||
|  | # Compiled binary addons (https://nodejs.org/api/addons.html) | ||||
|  | build/Release | ||||
|  | 
 | ||||
|  | # Dependency directories | ||||
|  | node_modules/ | ||||
|  | jspm_packages/ | ||||
|  | 
 | ||||
|  | # Snowpack dependency directory (https://snowpack.dev/) | ||||
|  | web_modules/ | ||||
|  | 
 | ||||
|  | # TypeScript cache | ||||
|  | *.tsbuildinfo | ||||
|  | 
 | ||||
|  | # Optional npm cache directory | ||||
|  | .npm | ||||
|  | 
 | ||||
|  | # Optional eslint cache | ||||
|  | .eslintcache | ||||
|  | 
 | ||||
|  | # Microbundle cache | ||||
|  | .rpt2_cache/ | ||||
|  | .rts2_cache_cjs/ | ||||
|  | .rts2_cache_es/ | ||||
|  | .rts2_cache_umd/ | ||||
|  | 
 | ||||
|  | # Optional REPL history | ||||
|  | .node_repl_history | ||||
|  | 
 | ||||
|  | # Output of 'npm pack' | ||||
|  | *.tgz | ||||
|  | 
 | ||||
|  | # Yarn Integrity file | ||||
|  | .yarn-integrity | ||||
|  | 
 | ||||
|  | # dotenv environment variables file | ||||
|  | .env | ||||
|  | .env.test | ||||
|  | .env.production | ||||
|  | 
 | ||||
|  | # parcel-bundler cache (https://parceljs.org/) | ||||
|  | .cache | ||||
|  | .parcel-cache | ||||
|  | 
 | ||||
|  | # Next.js build output | ||||
|  | .next | ||||
|  | out | ||||
|  | 
 | ||||
|  | # Nuxt.js build / generate output | ||||
|  | .nuxt | ||||
|  | dist | ||||
|  | 
 | ||||
|  | # Gatsby files | ||||
|  | .cache/ | ||||
|  | # Comment in the public line in if your project uses Gatsby and not Next.js | ||||
|  | # https://nextjs.org/blog/next-9-1#public-directory-support | ||||
|  | # public | ||||
|  | 
 | ||||
|  | # vuepress build output | ||||
|  | .vuepress/dist | ||||
|  | 
 | ||||
|  | # Serverless directories | ||||
|  | .serverless/ | ||||
|  | 
 | ||||
|  | # FuseBox cache | ||||
|  | .fusebox/ | ||||
|  | 
 | ||||
|  | # DynamoDB Local files | ||||
|  | .dynamodb/ | ||||
|  | 
 | ||||
|  | # TernJS port file | ||||
|  | .tern-port | ||||
|  | 
 | ||||
|  | # Stores VSCode versions used for testing VSCode extensions | ||||
|  | .vscode-test | ||||
|  | 
 | ||||
|  | # yarn v2 | ||||
|  | .yarn/cache | ||||
|  | .yarn/unplugged | ||||
|  | .yarn/build-state.yml | ||||
|  | .yarn/install-state.gz | ||||
|  | .pnp.* | ||||
|  | 
 | ||||
|  | *yarn.lock | ||||
|  | *package-lock.json | ||||
|  | *log/ | ||||
| @ -0,0 +1,17 @@ | |||||
|  | { | ||||
|  |   "presets": [ | ||||
|  |     "@babel/preset-react", | ||||
|  |     "@babel/preset-env" | ||||
|  |   ], | ||||
|  |   "plugins": [ | ||||
|  |     "@babel/plugin-proposal-class-properties", | ||||
|  |     "@babel/plugin-proposal-object-rest-spread", | ||||
|  |     // ["import", { | ||||
|  |     // //   "libraryName": "antd", | ||||
|  |     //   "libraryDirectory": "es" | ||||
|  |     // }] | ||||
|  |   ], | ||||
|  |   "env": { | ||||
|  |     "development": {} | ||||
|  |   } | ||||
|  | } | ||||
| @ -0,0 +1,18 @@ | |||||
|  | { | ||||
|  |     "version": "0.2.0", | ||||
|  |     "configurations": [ | ||||
|  |         { | ||||
|  |             "name": "Server", | ||||
|  |             "type": "node", | ||||
|  |             "request": "launch", | ||||
|  |             "program": "${workspaceRoot}/server.js", | ||||
|  |             "args": [ | ||||
|  |                 "-u http://127.0.0.1:4200" | ||||
|  |             ], | ||||
|  |             "outputCapture": "std", | ||||
|  |             "env": { | ||||
|  |                 "NODE_ENV": "development" | ||||
|  |             } | ||||
|  |         }         | ||||
|  |     ] | ||||
|  | } | ||||
| @ -0,0 +1,24 @@ | |||||
|  | FROM repository.anxinyun.cn/devops/node:12-dev as builder | ||||
|  | 
 | ||||
|  | COPY . /var/app | ||||
|  | 
 | ||||
|  | WORKDIR /var/app | ||||
|  | 
 | ||||
|  | EXPOSE 8080 | ||||
|  | 
 | ||||
|  | RUN npm config set registry=http://10.8.30.22:7000 | ||||
|  | RUN echo "{\"time\":\"$BUILD_TIMESTAMP\",\"build\": \"$BUILD_NUMBER\",\"revision\": \"$SVN_REVISION_1\",\"URL\":\"$SVN_URL_1\"}" > version.json | ||||
|  | RUN npm cache clean -f | ||||
|  | RUN npm install --registry http://10.8.30.22:7000 | ||||
|  | RUN npm run build | ||||
|  | RUN rm  -rf client/src | ||||
|  | RUN rm  -rf node_modules | ||||
|  | RUN npm install --production --registry http://10.8.30.22:7000 | ||||
|  | 
 | ||||
|  | FROM registry.cn-hangzhou.aliyuncs.com/fs-devops/node-16:7.22-06-20 | ||||
|  | 
 | ||||
|  | COPY --from=builder --chown=node /var/app  /home/node/app | ||||
|  | 
 | ||||
|  | WORKDIR /home/node/app | ||||
|  | 
 | ||||
|  | CMD ["node", "server.js"] | ||||
| After Width: | Height: | Size: 22 KiB | 
| After Width: | Height: | Size: 21 KiB | 
| After Width: | Height: | Size: 20 KiB | 
| After Width: | Height: | Size: 21 KiB | 
| After Width: | Height: | Size: 21 KiB | 
| After Width: | Height: | Size: 22 KiB | 
| After Width: | Height: | Size: 20 KiB | 
| After Width: | Height: | Size: 22 KiB | 
| After Width: | Height: | Size: 20 KiB | 
| After Width: | Height: | Size: 22 KiB | 
| After Width: | Height: | Size: 22 KiB | 
| After Width: | Height: | Size: 21 KiB | 
| After Width: | Height: | Size: 202 KiB | 
| After Width: | Height: | Size: 13 KiB | 
| After Width: | Height: | Size: 226 KiB | 
| After Width: | Height: | Size: 1.9 MiB | 
| After Width: | Height: | Size: 18 KiB | 
| After Width: | Height: | Size: 14 KiB | 
| After Width: | Height: | Size: 576 B | 
| After Width: | Height: | Size: 538 B | 
| After Width: | Height: | Size: 2.3 KiB | 
| After Width: | Height: | Size: 549 B | 
| @ -0,0 +1,15 @@ | |||||
|  | <!DOCTYPE html> | ||||
|  | <html> | ||||
|  | 
 | ||||
|  | <head> | ||||
|  |     <meta charset="UTF-8"> | ||||
|  |     <meta name="viewport" content="minimum-scale=1, initial-scale=1, width=device-width" /> | ||||
|  |     <meta content="upgrade-insecure-requests" http-equiv="Content-Security-Policy"> | ||||
|  |     <link rel="shortcut icon" href="/assets/images/favicon.ico"> | ||||
|  | </head> | ||||
|  | 
 | ||||
|  | <body> | ||||
|  |     <div id='IotAuthApp'></div> | ||||
|  | </body> | ||||
|  | 
 | ||||
|  | </html> | ||||
| @ -0,0 +1,46 @@ | |||||
|  | <!DOCTYPE html> | ||||
|  | <html> | ||||
|  | 
 | ||||
|  | <head> | ||||
|  |     <meta charset="UTF-8"> | ||||
|  |     <meta name="viewport" content="minimum-scale=1, initial-scale=1, width=device-width" /> | ||||
|  |     <meta content="upgrade-insecure-requests" http-equiv="Content-Security-Policy"> | ||||
|  |     <link rel="shortcut icon" href="/assets/images/favicon.ico"> | ||||
|  | </head> | ||||
|  | 
 | ||||
|  | <body> | ||||
|  |     <div id='IotAuthApp'></div> | ||||
|  | 
 | ||||
|  |     <!-- Webpack --> | ||||
|  |     <script type="text/javascript" src="http://localhost:5201/client/build/app.js"></script> | ||||
|  |     <!-- Vite --> | ||||
|  |     <!-- <script type="module"> | ||||
|  |         import RefreshRuntime from "http://localhost:5202/@react-refresh" | ||||
|  |         RefreshRuntime.injectIntoGlobalHook(window) | ||||
|  |         window.$RefreshReg$ = () => { } | ||||
|  |         window.$RefreshSig$ = () => (type) => type | ||||
|  |         window.__vite_plugin_react_preamble_installed__ = true | ||||
|  |         const global = window | ||||
|  |     </script> | ||||
|  |     <script type="module" src="http://localhost:5202/src/index.jsx"></script> --> | ||||
|  |     <!-- Vite End --> | ||||
|  |     <script> | ||||
|  |         //过滤掉一些无用的警告、没有价值的报错 | ||||
|  |         //代理console.warn方法 | ||||
|  |          | ||||
|  |         // const _consoleWarn = (...rest) => { | ||||
|  |         //     console.error(...rest) | ||||
|  |         // }; | ||||
|  |         // console.error = (...rest) => { | ||||
|  |         //     if ( | ||||
|  |         //         ![ | ||||
|  |         //             'Each child in a list should have a unique "key" prop', | ||||
|  |         //         ].some((item) => rest[0].indexOf(item) !== -1) | ||||
|  |         //     ) { | ||||
|  |         //         _consoleWarn(...rest); | ||||
|  |         //     } | ||||
|  |         // }; | ||||
|  |     </script> | ||||
|  | </body> | ||||
|  | 
 | ||||
|  | </html> | ||||
| @ -0,0 +1,20 @@ | |||||
|  | /** | ||||
|  |  * User: liuxinyi/liu.xinyi@free-sun.com.cn | ||||
|  |  * Date: 2016/2/22 | ||||
|  |  * Time: 15:29 | ||||
|  |  * | ||||
|  |  */ | ||||
|  | 'use strict'; | ||||
|  | 
 | ||||
|  | const views = require('koa-view'); | ||||
|  | const path = require('path'); | ||||
|  | 
 | ||||
|  | module.exports = { | ||||
|  |     entry: function (app, router, opt) { | ||||
|  |         app.use(views(__dirname)); | ||||
|  | 
 | ||||
|  |         router.get('(.*)', async function (ctx) { | ||||
|  |             await ctx.render(path.join(__dirname, './index')); | ||||
|  |         }); | ||||
|  |     } | ||||
|  | }; | ||||
| @ -0,0 +1,28 @@ | |||||
|  | 'use strict'; | ||||
|  | 
 | ||||
|  | import React, { useEffect } from 'react'; | ||||
|  | import Layout from './layout'; | ||||
|  | import Auth from './sections/auth'; | ||||
|  | import Edition from './sections/edition'; | ||||
|  | import MicroApp from './sections/microApp' | ||||
|  | 
 | ||||
|  | const App = props => { | ||||
|  |     const { projectName } = props | ||||
|  | 
 | ||||
|  |     useEffect(() => { | ||||
|  |         document.title = projectName; | ||||
|  |     }, []) | ||||
|  | 
 | ||||
|  |     return ( | ||||
|  |         <Layout | ||||
|  |             title={projectName} | ||||
|  |             sections={[ | ||||
|  |                 Auth, | ||||
|  |                 Edition, | ||||
|  |                 // MicroApp | ||||
|  |             ]} | ||||
|  |         /> | ||||
|  |     ) | ||||
|  | } | ||||
|  | 
 | ||||
|  | export default App; | ||||
| @ -0,0 +1,22 @@ | |||||
|  | import React from "react"; | ||||
|  | 
 | ||||
|  | const Coming = () => { | ||||
|  |    return ( | ||||
|  |       <div style={{ | ||||
|  |          height: 'calc(100% - 12px)', width: '100%', backgroundColor: '#fff', | ||||
|  |          display: 'flex', justifyContent: 'center', alignItems: 'center', | ||||
|  |          position: 'absolute', | ||||
|  |       }}> | ||||
|  |          <img | ||||
|  |             src='/assets/images/background/building.jpg' | ||||
|  |             style={{ | ||||
|  |                maxHeight: 228, | ||||
|  |                maxWidth: 645, | ||||
|  |                width: '80%' | ||||
|  |             }} | ||||
|  |          /> | ||||
|  |       </div> | ||||
|  |    ) | ||||
|  | } | ||||
|  | 
 | ||||
|  | export default Coming | ||||
| @ -0,0 +1,6 @@ | |||||
|  | 'use strict'; | ||||
|  | import Coming from './coming' | ||||
|  | 
 | ||||
|  | export { | ||||
|  |    Coming | ||||
|  | }; | ||||
| @ -0,0 +1,12 @@ | |||||
|  | 'use strict'; | ||||
|  | 
 | ||||
|  | import './public-path' | ||||
|  | import React from 'react'; | ||||
|  | import { render } from 'react-dom'; | ||||
|  | import App from './app'; | ||||
|  | import './index.less'; | ||||
|  | import microApp from '@micro-zoe/micro-app' | ||||
|  | 
 | ||||
|  | microApp.start() | ||||
|  | 
 | ||||
|  | render((<App projectName="飞尚物联" />), document.getElementById('IotAuthApp')); | ||||
| @ -0,0 +1,38 @@ | |||||
|  | // webpack (vite 用 alias 兼容了) | ||||
|  | @import '~@douyinfe/semi-ui/dist/css/semi.min.css'; | ||||
|  | @import '~perfect-scrollbar/css/perfect-scrollbar.css'; | ||||
|  | @import '~nprogress/nprogress.css'; | ||||
|  | 
 | ||||
|  | 
 | ||||
|  | *, | ||||
|  | *::before, | ||||
|  | *::after { | ||||
|  |     box-sizing: border-box; | ||||
|  | } | ||||
|  | 
 | ||||
|  | html, | ||||
|  | body { | ||||
|  |     margin: 0; | ||||
|  |     height: 100%; | ||||
|  |     width: 100%; | ||||
|  | 
 | ||||
|  |     a:link { | ||||
|  |         text-decoration: none; | ||||
|  |         color: unset | ||||
|  |     } | ||||
|  | 
 | ||||
|  |     a:visited { | ||||
|  |         text-decoration: none; | ||||
|  |         color: unset | ||||
|  |     } | ||||
|  | 
 | ||||
|  |     a:hover { | ||||
|  |         text-decoration: none; | ||||
|  |         color: unset | ||||
|  |     } | ||||
|  | 
 | ||||
|  |     a:active { | ||||
|  |         text-decoration: none; | ||||
|  |         color: unset | ||||
|  |     } | ||||
|  | } | ||||
| @ -0,0 +1,44 @@ | |||||
|  | 'use strict'; | ||||
|  | import { RouteRequest } from '@peace/utils'; | ||||
|  | import { RouteTable } from '$utils' | ||||
|  | 
 | ||||
|  | export const INIT_LAYOUT = 'INIT_LAYOUT'; | ||||
|  | export function initLayout (title, copyright, sections, actions) { | ||||
|  |     return { | ||||
|  |         type: INIT_LAYOUT, | ||||
|  |         payload: { | ||||
|  |             title, | ||||
|  |             copyright, | ||||
|  |             sections, | ||||
|  |             actions | ||||
|  |         } | ||||
|  |     }; | ||||
|  | } | ||||
|  | 
 | ||||
|  | export const RESIZE = 'RESIZE'; | ||||
|  | export function resize (clientHeight, clientWidth) { | ||||
|  |     const headerHeight = 60 | ||||
|  |     const footerHeight = 0 | ||||
|  |     return { | ||||
|  |         type: RESIZE, | ||||
|  |         payload: { | ||||
|  |             clientHeight: clientHeight - headerHeight - footerHeight, | ||||
|  |             clientWidth: clientWidth | ||||
|  |         } | ||||
|  |     } | ||||
|  | } | ||||
|  | 
 | ||||
|  | export const INIT_API_ROOT = 'INIT_API_ROOT'; | ||||
|  | export function initApiRoot () { | ||||
|  |     return dispatch => { | ||||
|  |         RouteRequest.get(RouteTable.apiRoot).then(res => { | ||||
|  |             localStorage.setItem('apiRoot', res.root); | ||||
|  |             dispatch({ | ||||
|  |                 type: INIT_API_ROOT, | ||||
|  |                 payload: { | ||||
|  |                     apiRoot: res.root | ||||
|  |                 } | ||||
|  |             }) | ||||
|  |         }); | ||||
|  |     } | ||||
|  | } | ||||
| @ -0,0 +1,9 @@ | |||||
|  | 'use strict'; | ||||
|  | 
 | ||||
|  | import * as global from './global' | ||||
|  | import * as socket from './webSocket'; | ||||
|  | 
 | ||||
|  | export default { | ||||
|  |     ...global, | ||||
|  |     ...socket, | ||||
|  | }; | ||||
| @ -0,0 +1,33 @@ | |||||
|  | 'use strict'; | ||||
|  | import io from 'socket.io-client'; | ||||
|  | 
 | ||||
|  | export const INIT_WEB_SOCKET = 'INIT_WEB_SOCKET' | ||||
|  | export function initWebSocket ({ ioUrl, token }) { | ||||
|  |    if (!ioUrl) { | ||||
|  |       ioUrl = localStorage.getItem('apiRoot') | ||||
|  |    } | ||||
|  |    if (!token) { | ||||
|  |       const user = sessionStorage.getItem('user') | ||||
|  |       if (user) { | ||||
|  |          token = JSON.parse(user).token | ||||
|  |       } | ||||
|  |    } | ||||
|  |    if (!ioUrl || !token) { | ||||
|  |       return { | ||||
|  |          type: '', | ||||
|  |       } | ||||
|  |    } | ||||
|  |    return dispatch => { | ||||
|  |       const socket = io(ioUrl, { | ||||
|  |          query: { | ||||
|  |             token: token | ||||
|  |          }, | ||||
|  |       }); | ||||
|  |       dispatch({ | ||||
|  |          type: INIT_WEB_SOCKET, | ||||
|  |          payload: { | ||||
|  |             socket: socket | ||||
|  |          } | ||||
|  |       }) | ||||
|  |    } | ||||
|  | } | ||||
| @ -0,0 +1,15 @@ | |||||
|  | 'use strict'; | ||||
|  | import React from 'react'; | ||||
|  | import moment from 'moment' | ||||
|  | 
 | ||||
|  | export default class Footer extends React.Component { | ||||
|  |     render () { | ||||
|  |         // const { } = this.props; | ||||
|  | 
 | ||||
|  |         return ( | ||||
|  |             <div style={{ textAlign: 'center', lineHeight: '32px' }}> | ||||
|  |                 Copyright © {moment().year()} All Rights Reserved 版权所有· 江西飞尚科技有限公司 | ||||
|  |             </div> | ||||
|  |         ); | ||||
|  |     } | ||||
|  | }; | ||||
| @ -0,0 +1,88 @@ | |||||
|  | "use strict"; | ||||
|  | import React from "react"; | ||||
|  | import { connect } from "react-redux"; | ||||
|  | import { Nav, Avatar, Dropdown } from "@douyinfe/semi-ui"; | ||||
|  | 
 | ||||
|  | const Header = (props) => { | ||||
|  |     const { dispatch, history, user, actions, socket } = props; | ||||
|  | 
 | ||||
|  |     return ( | ||||
|  |         <> | ||||
|  |             <Nav | ||||
|  |                 mode={"horizontal"} | ||||
|  |                 onClick={({ itemKey }) => { | ||||
|  |                     if (itemKey == "logout") { | ||||
|  |                         dispatch(actions.auth.logout()); | ||||
|  |                         if (socket) { | ||||
|  |                             socket.disconnect(); | ||||
|  |                         } | ||||
|  |                         history.push(`/signin`); | ||||
|  |                     } | ||||
|  |                 }} | ||||
|  |                 style={{ | ||||
|  |                     height: 60, | ||||
|  |                     minWidth: 520, | ||||
|  |                     background: `url(${__webpack_public_path__}assets/images/background/header.png)`, | ||||
|  |                     backgroundSize: "100% 100%", | ||||
|  |                     color: "white", | ||||
|  |                 }} | ||||
|  |                 header={{ | ||||
|  |                     logo: ( | ||||
|  |                         <img | ||||
|  |                             src={`/assets/images/background/logo.png`} | ||||
|  |                             style={{ display: "inline-block", width: 280, height: 52 }} | ||||
|  |                         /> | ||||
|  |                     ), | ||||
|  |                     text: "", | ||||
|  |                 }} | ||||
|  |                 footer={ | ||||
|  |                     <Nav.Sub | ||||
|  |                         itemKey={"user"} | ||||
|  |                         dropdownStyle={{ | ||||
|  |                             position: 'relative' | ||||
|  |                         }} | ||||
|  |                         text={ | ||||
|  |                             <div | ||||
|  |                                 style={{ | ||||
|  |                                     marginLeft: 20, | ||||
|  |                                     display: "inline-block", | ||||
|  |                                     color: "white", | ||||
|  |                                 }} | ||||
|  |                             > | ||||
|  |                                 <img | ||||
|  |                                     src={`/assets/images/background/notice.png`} | ||||
|  |                                     style={{ | ||||
|  |                                         display: "inline-block", | ||||
|  |                                         width: 18, | ||||
|  |                                         height: 18, | ||||
|  |                                         position: "relative", | ||||
|  |                                         top: 6, | ||||
|  |                                         left: -10, | ||||
|  |                                     }} | ||||
|  |                                 /> | ||||
|  | 
 | ||||
|  |                                 <Avatar size="small" color="light-blue" style={{ margin: 4 }}> | ||||
|  |                                     <img src="/assets/images/avatar/6.png" /> | ||||
|  |                                 </Avatar> | ||||
|  |                                 {user && user.namePresent} | ||||
|  |                             </div> | ||||
|  |                         } | ||||
|  |                     > | ||||
|  |                         <Nav.Item itemKey={"logout"} text={"退出"} /> | ||||
|  |                     </Nav.Sub> | ||||
|  |                 } | ||||
|  |             /> | ||||
|  |         </> | ||||
|  |     ); | ||||
|  | }; | ||||
|  | 
 | ||||
|  | function mapStateToProps (state) { | ||||
|  |     const { global, auth, webSocket } = state; | ||||
|  |     return { | ||||
|  |         actions: global.actions, | ||||
|  |         user: auth.user, | ||||
|  |         socket: webSocket.socket, | ||||
|  |     }; | ||||
|  | } | ||||
|  | 
 | ||||
|  | export default connect(mapStateToProps)(Header); | ||||
| @ -0,0 +1,76 @@ | |||||
|  | import React, { useEffect, useState } from 'react'; | ||||
|  | import PerfectScrollbar from 'perfect-scrollbar'; | ||||
|  | import { connect } from 'react-redux'; | ||||
|  | import { Nav } from '@douyinfe/semi-ui'; | ||||
|  | import { push } from 'react-router-redux'; | ||||
|  | 
 | ||||
|  | let scrollbar = null | ||||
|  | const Sider = props => { | ||||
|  |    const { collapsed, clientHeight, dispatch } = props | ||||
|  |    const [items, setItems] = useState([]) | ||||
|  |    const [selectedKeys, setSelectedKeys] = useState([]) | ||||
|  |    const [openKeys, setOpenKeys] = useState([]) | ||||
|  | 
 | ||||
|  |    useEffect(() => { | ||||
|  |       const { sections, dispatch, user } = props; | ||||
|  |       let nextItems = [] | ||||
|  |       for (let c of sections) { | ||||
|  |          if (typeof c.getNavItem == 'function') { | ||||
|  |             let item = c.getNavItem(user, dispatch); | ||||
|  |             if (item) { | ||||
|  |                nextItems.push.apply(nextItems, item) | ||||
|  |             } | ||||
|  |          } | ||||
|  |       } | ||||
|  |       setItems(nextItems) | ||||
|  | 
 | ||||
|  |       const lastSelectedKeys = localStorage.getItem('fs_iot_auth_selected_sider') | ||||
|  |       if (lastSelectedKeys) { | ||||
|  |          setSelectedKeys(JSON.parse(lastSelectedKeys)) | ||||
|  |       } | ||||
|  |       const lastOpenKeys = localStorage.getItem('fs_iot_auth_open_sider') | ||||
|  |       if (lastOpenKeys) { | ||||
|  |          setOpenKeys(JSON.parse(lastOpenKeys)) | ||||
|  |       } | ||||
|  | 
 | ||||
|  |       scrollbar = new PerfectScrollbar('#page-slider', { suppressScrollX: true }); | ||||
|  |    }, []) | ||||
|  | 
 | ||||
|  |    useEffect(() => { | ||||
|  |       if (scrollbar) { | ||||
|  |          scrollbar.update(); | ||||
|  |       } | ||||
|  |    }) | ||||
|  | 
 | ||||
|  |    return ( | ||||
|  |       <div id={'page-slider'} style={{ height: clientHeight, position: 'relative' }}> | ||||
|  |          <Nav | ||||
|  |             style={{}} | ||||
|  |             selectedKeys={selectedKeys} | ||||
|  |             openKeys={openKeys} | ||||
|  |             onSelect={({ selectedItems, selectedKeys, }) => { | ||||
|  |                const selectItem = selectedItems[0] | ||||
|  |                if (selectItem.to) { | ||||
|  |                   dispatch(push(selectItem.to)) | ||||
|  |                } | ||||
|  |                setSelectedKeys(selectedKeys) | ||||
|  |                localStorage.setItem('fs_iot_auth_selected_sider', JSON.stringify(selectedKeys)) | ||||
|  |             }} | ||||
|  |             onOpenChange={({ openKeys }) => { | ||||
|  |                setOpenKeys(openKeys) | ||||
|  |                localStorage.setItem('fs_iot_auth_open_sider', JSON.stringify(openKeys)) | ||||
|  |             }} | ||||
|  |             items={items} | ||||
|  |          /> | ||||
|  |       </div> | ||||
|  |    ) | ||||
|  | } | ||||
|  | 
 | ||||
|  | function mapStateToProps (state) { | ||||
|  |    const { global } = state; | ||||
|  |    return { | ||||
|  |       clientHeight: global.clientHeight, | ||||
|  |    }; | ||||
|  | } | ||||
|  | 
 | ||||
|  | export default connect(mapStateToProps)(Sider); | ||||
| @ -0,0 +1,6 @@ | |||||
|  | 'use strict'; | ||||
|  | import Layout from './layout'; | ||||
|  | import NoMatch from './no-match'; | ||||
|  | 
 | ||||
|  | export { Layout }; | ||||
|  | export { NoMatch }; | ||||
| @ -0,0 +1,144 @@ | |||||
|  | 'use strict'; | ||||
|  | 
 | ||||
|  | import React, { useState, useEffect } from 'react'; | ||||
|  | import { connect } from 'react-redux'; | ||||
|  | import { Layout, Notification } from '@douyinfe/semi-ui'; | ||||
|  | import Sider from '../../components/sider'; | ||||
|  | import Header from '../../components/header'; | ||||
|  | import Footer from '../../components/footer'; | ||||
|  | import { resize } from '../../actions/global'; | ||||
|  | import * as NProgress from 'nprogress'; | ||||
|  | import PerfectScrollbar from 'perfect-scrollbar'; | ||||
|  | 
 | ||||
|  | NProgress.configure({ | ||||
|  |     template: ` | ||||
|  |     <div class="bar" style="height:2px" role="bar"> | ||||
|  |         <div class="peg"></div> | ||||
|  |     </div> | ||||
|  |     <div class="spinner" role="spinner"> | ||||
|  |         <div class="spinner-icon"></div> | ||||
|  |     </div> | ||||
|  |     ` | ||||
|  | }); | ||||
|  | 
 | ||||
|  | let scrollbar | ||||
|  | 
 | ||||
|  | const LayoutContainer = props => { | ||||
|  |     const { | ||||
|  |         dispatch, msg, user, copyright, children, sections, clientWidth, clientHeight, | ||||
|  |         location, match, routes, history | ||||
|  |     } = props | ||||
|  |     const [collapsed, setCollapsed] = useState(false) | ||||
|  | 
 | ||||
|  |     NProgress.start(); | ||||
|  | 
 | ||||
|  |     const resize_ = () => { | ||||
|  |         dispatch(resize( | ||||
|  |             document.body.clientHeight, | ||||
|  |             document.body.clientWidth - (collapsed ? 120 : 240) | ||||
|  |         )); | ||||
|  |     } | ||||
|  | 
 | ||||
|  |     useEffect(() => { | ||||
|  |         scrollbar = new PerfectScrollbar('#page-content', { suppressScrollX: true }); | ||||
|  | 
 | ||||
|  |         window.addEventListener('resize', resize_); | ||||
|  |         return () => { | ||||
|  |             window.removeEventListener('resize', resize_); | ||||
|  |         } | ||||
|  |     }, []) | ||||
|  | 
 | ||||
|  |     useEffect(() => { | ||||
|  |         NProgress.done(); | ||||
|  |         if ((!user || !user.authorized) && location.pathname != '/cross') { | ||||
|  |             history.push('/signin'); | ||||
|  |         } | ||||
|  |         if (msg) { | ||||
|  |             if (msg.done) { | ||||
|  |                 Notification.success({ | ||||
|  |                     // title: msg.done, | ||||
|  |                     content: msg.done, | ||||
|  |                     duration: 2, | ||||
|  |                 }) | ||||
|  |             } | ||||
|  |             if (msg.error) { | ||||
|  |                 Notification.error({ | ||||
|  |                     // title: msg.error, | ||||
|  |                     content: msg.error, | ||||
|  |                     duration: 2, | ||||
|  |                 }) | ||||
|  |             } | ||||
|  |         } | ||||
|  |         const dom = document.getElementById('page-content'); | ||||
|  |         if (dom) { | ||||
|  |             scrollbar.update(); | ||||
|  |             dom.scrollTop = 0; | ||||
|  |         } | ||||
|  |     }) | ||||
|  | 
 | ||||
|  |     return ( | ||||
|  |         <Layout id="layout"> | ||||
|  |             <Layout.Header> | ||||
|  |                 <Header | ||||
|  |                     user={user} | ||||
|  |                     pathname={location.pathname} | ||||
|  |                     toggleCollapsed={() => { | ||||
|  |                         setCollapsed(!collapsed); | ||||
|  |                     }} | ||||
|  |                     collapsed={collapsed} | ||||
|  |                     history={history} | ||||
|  |                 /> | ||||
|  |             </Layout.Header> | ||||
|  |             <Layout> | ||||
|  |                 <Layout.Sider> | ||||
|  |                     <Sider | ||||
|  |                         sections={sections} | ||||
|  |                         dispatch={dispatch} | ||||
|  |                         user={user} | ||||
|  |                         pathname={location.pathname} | ||||
|  |                         collapsed={collapsed} | ||||
|  |                     /> | ||||
|  |                 </Layout.Sider> | ||||
|  |                 <Layout.Content> | ||||
|  |                     <div style={{ | ||||
|  |                         margin: '12px 12px 0px', | ||||
|  |                         background: "#F6FAFF", | ||||
|  |                     }}> | ||||
|  |                         <div id="page-content" style={{ | ||||
|  |                             height: clientHeight - 12, | ||||
|  |                             minWidth: 520, | ||||
|  |                             position: 'relative', | ||||
|  |                         }}> | ||||
|  |                             <div style={{ | ||||
|  |                                 minHeight: clientHeight - 32 - 12, | ||||
|  |                                 position: 'relative', | ||||
|  |                                 padding: '12px 8px', | ||||
|  |                             }}> | ||||
|  |                                 {children} | ||||
|  |                             </div> | ||||
|  |                             <Layout.Footer> | ||||
|  |                                 <Footer /> | ||||
|  |                             </Layout.Footer> | ||||
|  |                         </div> | ||||
|  |                     </div> | ||||
|  |                 </Layout.Content> | ||||
|  |             </Layout> | ||||
|  |         </Layout > | ||||
|  |     ) | ||||
|  | } | ||||
|  | 
 | ||||
|  | function mapStateToProps (state) { | ||||
|  |     const { global, auth, ajaxResponse } = state; | ||||
|  |     return { | ||||
|  |         title: global.title, | ||||
|  |         copyright: global.copyright, | ||||
|  |         sections: global.sections, | ||||
|  |         actions: global.actions, | ||||
|  |         clientWidth: global.clientWidth, | ||||
|  |         clientHeight: global.clientHeight, | ||||
|  |         msg: ajaxResponse.msg, | ||||
|  |         user: auth.user | ||||
|  |     }; | ||||
|  | } | ||||
|  | 
 | ||||
|  | export default connect(mapStateToProps)(LayoutContainer); | ||||
| @ -0,0 +1,18 @@ | |||||
|  | 'use strict'; | ||||
|  | 
 | ||||
|  | import React from 'react'; | ||||
|  | import moment from 'moment' | ||||
|  | 
 | ||||
|  | const NoMatch = props => { | ||||
|  |     return ( | ||||
|  |         <div style={{ textAlign: 'center', padding: 120 }}> | ||||
|  |             <p style={{ fontSize: 80, lineHeight: 1.5 }}>404</p> | ||||
|  |             <p style={{ fontSize: 32, lineHeight: 2 }}>PAGE NOT FOUND</p> | ||||
|  |             <p>很遗憾,您暂时无法访问该页面。</p> | ||||
|  |             <p>请检查您访问的链接地址是否正确。</p> | ||||
|  |             <p style={{ marginTop: 80 }}>Copyright © {moment().year()} 飞尚</p> | ||||
|  |         </div> | ||||
|  |     ) | ||||
|  | } | ||||
|  | 
 | ||||
|  | export default NoMatch; | ||||
| @ -0,0 +1,181 @@ | |||||
|  | 'use strict'; | ||||
|  | import React, { useEffect, useState } from 'react'; | ||||
|  | import moment from 'moment'; | ||||
|  | import configStore from './store'; | ||||
|  | import { Provider } from 'react-redux'; | ||||
|  | import { createBrowserHistory } from 'history'; | ||||
|  | import { ConnectedRouter } from 'connected-react-router' | ||||
|  | import { Layout, NoMatch } from './containers'; | ||||
|  | import { BrowserRouter, Switch, Route } from "react-router-dom"; | ||||
|  | import { ConfigProvider } from '@douyinfe/semi-ui'; | ||||
|  | import layoutActions from './actions'; | ||||
|  | import zhCN from '@douyinfe/semi-ui/lib/es/locale/source/zh_CN'; | ||||
|  | import { basicReducer } from '@peace/utils'; | ||||
|  | import 'moment/locale/zh-cn'; | ||||
|  | 
 | ||||
|  | moment.locale('zh-cn'); | ||||
|  | 
 | ||||
|  | const { initLayout, initApiRoot, resize, initWebSocket } = layoutActions; | ||||
|  | 
 | ||||
|  | const Root = props => { | ||||
|  |     const { sections, title, copyright } = props; | ||||
|  |     console.log(sections); | ||||
|  |     const [history, setHistory] = useState(null) | ||||
|  |     const [store, setStore] = useState(null) | ||||
|  |     const [outerRoutes, setOuterRoutes] = useState([]) | ||||
|  |     const [combineRoutes, setCombineRoutes] = useState([]) | ||||
|  |     const [innnerRoutes, setInnerRoutes] = useState([]) | ||||
|  | 
 | ||||
|  |     const flatRoutes = (routes) => { | ||||
|  |         const combineRoutes = []; | ||||
|  | 
 | ||||
|  |         function flat (routes, parentRoute) { | ||||
|  |             routes.forEach((route, i) => { | ||||
|  |                 let obj = { | ||||
|  |                     path: route.path, | ||||
|  |                     breadcrumb: route.breadcrumb, | ||||
|  |                     component: route.component || null, | ||||
|  |                     authCode: route.authCode || '', | ||||
|  |                     key: route.key | ||||
|  |                 } | ||||
|  |                 if (!route.path.startsWith("/")) { | ||||
|  |                     console.error('路由配置需以 "/" 开始:' + route.path); | ||||
|  |                 } | ||||
|  |                 if (route.path.length > 1 && route.path[route.path.length] == '/') { | ||||
|  |                     console.error('除根路由路由配置不可以以 "/" 结束:' + route.path); | ||||
|  |                 } | ||||
|  |                 if (parentRoute && parentRoute != '/') { | ||||
|  |                     obj.path = parentRoute + route.path; | ||||
|  |                 } | ||||
|  |                 if (route.exact) { | ||||
|  |                     obj.exact = true | ||||
|  |                 } | ||||
|  |                 if (route.hasOwnProperty('childRoutes')) { | ||||
|  |                     combineRoutes.push(obj); | ||||
|  |                     flat(route.childRoutes, obj.path) | ||||
|  |                 } else { | ||||
|  |                     combineRoutes.push(obj) | ||||
|  |                 } | ||||
|  |             }) | ||||
|  |         } | ||||
|  | 
 | ||||
|  |         flat(routes); | ||||
|  |         return combineRoutes; | ||||
|  |     } | ||||
|  | 
 | ||||
|  |     const initReducer = (reducers, reducerName, action) => { | ||||
|  |         let reducerParams = {} | ||||
|  |         const { actionType, initReducer, reducer } = action()() | ||||
|  |         if (initReducer || reducer) { | ||||
|  |             if (reducer) { | ||||
|  |                 if (reducer.name) { | ||||
|  |                     reducerName = reducer.name | ||||
|  |                 } | ||||
|  |                 if (reducer.params) { | ||||
|  |                     reducerParams = reducer.params | ||||
|  |                 } | ||||
|  |             } else { | ||||
|  |                 reducerName = `${reducerName}Rslt` | ||||
|  |             } | ||||
|  |             reducers[reducerName] = function (state, action) { | ||||
|  |                 return basicReducer(state, action, Object.assign({ actionType: actionType }, reducerParams)); | ||||
|  |             } | ||||
|  |         } | ||||
|  |     } | ||||
|  | 
 | ||||
|  |     useEffect(() => { | ||||
|  |         let innerRoutes = [] | ||||
|  |         let outerRoutes = [] | ||||
|  |         let reducers = {} | ||||
|  |         let actions = { | ||||
|  |             layout: layoutActions | ||||
|  |         } | ||||
|  | 
 | ||||
|  |         for (let s of sections) { | ||||
|  |             if (!s.key) console.warn('请给你的section添加一个key值,section name:' + s.name); | ||||
|  |             for (let r of s.routes) { | ||||
|  |                 if (r.type == 'inner' || r.type == 'home') { | ||||
|  |                     innerRoutes.push(r.route) | ||||
|  |                 } else if (r.type == 'outer') { | ||||
|  |                     outerRoutes.push(r.route) | ||||
|  |                 } | ||||
|  |             } | ||||
|  |             if (s.reducers) { | ||||
|  |                 reducers = { ...reducers, ...s.reducers } | ||||
|  |             } | ||||
|  |             if (s.actions) { | ||||
|  |                 actions = { ...actions, [s.key]: s.actions } | ||||
|  |                 if (s.key != 'auth') { | ||||
|  |                     for (let ak in s.actions) { | ||||
|  |                         let actions = s.actions[ak] | ||||
|  |                         if (actions && typeof actions == 'object') { | ||||
|  |                             for (let actionName in actions) { | ||||
|  |                                 initReducer(reducers, actionName, actions[actionName]) | ||||
|  |                             } | ||||
|  |                         } else if (typeof actions == 'function') { | ||||
|  |                             initReducer(reducers, ak, actions) | ||||
|  |                         } | ||||
|  |                     } | ||||
|  |                 } | ||||
|  |             } | ||||
|  |         } | ||||
|  | 
 | ||||
|  |         let history = createBrowserHistory(); | ||||
|  |         let store = configStore(reducers, history); | ||||
|  |         store.dispatch(initLayout(title, copyright, sections, actions)); | ||||
|  |         store.dispatch(resize(document.body.clientHeight, document.body.clientWidth)); | ||||
|  |         store.dispatch(actions.auth.initAuth()); | ||||
|  |         store.dispatch(initWebSocket({})) | ||||
|  |         store.dispatch(initApiRoot()) | ||||
|  | 
 | ||||
|  |         const combineRoutes = flatRoutes(innerRoutes); | ||||
|  | 
 | ||||
|  |         setInnerRoutes(combineRoutes) | ||||
|  |         setHistory(history) | ||||
|  |         setStore(store) | ||||
|  |         setOuterRoutes(outerRoutes.map(route => ( | ||||
|  |             <Route | ||||
|  |                 key={route.key} | ||||
|  |                 exact | ||||
|  |                 path={route.path} | ||||
|  |                 component={route.component} | ||||
|  |             /> | ||||
|  |         ))) | ||||
|  |         setCombineRoutes(combineRoutes.map(route => ( | ||||
|  |             <Route | ||||
|  |                 key={route.key} | ||||
|  |                 exact={route.hasOwnProperty('exact') ? route.exact : true} | ||||
|  |                 path={route.path} | ||||
|  |                 component={route.component} | ||||
|  |             /> | ||||
|  |         ))) | ||||
|  |     }, []) | ||||
|  | 
 | ||||
|  |     return ( | ||||
|  |         store ? | ||||
|  |             <ConfigProvider locale={zhCN}> | ||||
|  |                 <Provider store={store}> | ||||
|  |                     <BrowserRouter basename={window.__MICRO_APP_BASE_ROUTE__ || '/'}> | ||||
|  |                         <ConnectedRouter history={history}> | ||||
|  |                             <Switch> | ||||
|  |                                 {outerRoutes} | ||||
|  |                                 <Layout | ||||
|  |                                     history={history} | ||||
|  |                                     routes={innnerRoutes} | ||||
|  |                                 > | ||||
|  |                                     {combineRoutes} | ||||
|  |                                 </Layout> | ||||
|  |                                 <Route | ||||
|  |                                     path={'*'} | ||||
|  |                                     component={NoMatch} | ||||
|  |                                 /> | ||||
|  |                             </Switch> | ||||
|  |                         </ConnectedRouter> | ||||
|  |                     </BrowserRouter > | ||||
|  |                 </Provider> | ||||
|  |             </ConfigProvider > | ||||
|  |             : '' | ||||
|  |     ) | ||||
|  | } | ||||
|  | 
 | ||||
|  | export default Root; | ||||
| @ -0,0 +1,28 @@ | |||||
|  | /** | ||||
|  |  * Created by liu.xinyi | ||||
|  |  * on 2016/4/1. | ||||
|  |  */ | ||||
|  | 'use strict'; | ||||
|  | const initState = { | ||||
|  |     msg: null | ||||
|  | }; | ||||
|  | 
 | ||||
|  | import Immutable from 'immutable'; | ||||
|  | 
 | ||||
|  | /** | ||||
|  |  * 全局ajax响应处理: | ||||
|  |  * 判断action中是否有done字段,如果有,则修改store中的msg.done | ||||
|  |  * 判断action中是否有error字段,如果有,则修改store中msg.error | ||||
|  |  * 在layout中根据msg的值,呈现提示信息。 | ||||
|  | */ | ||||
|  | export default function ajaxResponse (state = initState, action) { | ||||
|  |     if (action.done) { | ||||
|  |         return Immutable.fromJS(state).set('msg', { done: action.done }).toJS(); | ||||
|  |     } | ||||
|  | 
 | ||||
|  |     if (action.error) { | ||||
|  |         return Immutable.fromJS(state).set('msg', { error: action.error }).toJS(); | ||||
|  |     } | ||||
|  | 
 | ||||
|  |     return { msg: null }; | ||||
|  | }; | ||||
| @ -0,0 +1,39 @@ | |||||
|  | 'use strict'; | ||||
|  | import Immutable from 'immutable'; | ||||
|  | import { INIT_LAYOUT, RESIZE, INIT_API_ROOT } from '../actions/global'; | ||||
|  | 
 | ||||
|  | function global (state = { | ||||
|  |     title: '', | ||||
|  |     copyright: '', | ||||
|  |     sections: [], | ||||
|  |     actions: {}, | ||||
|  |     plugins: {}, | ||||
|  |     clientHeight: 768, | ||||
|  |     clientWidth: 1024, | ||||
|  |     apiRoot: '', | ||||
|  | }, action) { | ||||
|  |     const payload = action.payload; | ||||
|  |     switch (action.type) { | ||||
|  |         case RESIZE: | ||||
|  |             return Immutable.fromJS(state).merge({ | ||||
|  |                 clientHeight: payload.clientHeight, | ||||
|  |                 clientWidth: payload.clientWidth | ||||
|  |             }).toJS(); | ||||
|  |         case INIT_LAYOUT: | ||||
|  |             return Immutable.fromJS(state).merge({ | ||||
|  |                 title: payload.title, | ||||
|  |                 copyright: payload.copyright, | ||||
|  |                 sections: payload.sections, | ||||
|  |                 actions: payload.actions, | ||||
|  |                 plugins: payload.plugins, | ||||
|  |             }).toJS(); | ||||
|  |         case INIT_API_ROOT: | ||||
|  |             return Immutable.fromJS(state).merge({ | ||||
|  |                 apiRoot: payload.apiRoot, | ||||
|  |             }).toJS(); | ||||
|  |         default: | ||||
|  |             return state; | ||||
|  |     } | ||||
|  | } | ||||
|  | 
 | ||||
|  | export default global; | ||||
| @ -0,0 +1,17 @@ | |||||
|  | /** | ||||
|  |  * User: liuxinyi/liu.xinyi@free-sun.com.cn | ||||
|  |  * Date: 2016/1/13 | ||||
|  |  * Time: 17:52 | ||||
|  |  * | ||||
|  |  */ | ||||
|  | 'use strict'; | ||||
|  | 
 | ||||
|  | import global from './global'; | ||||
|  | import webSocket from './webSocket' | ||||
|  | import ajaxResponse from './ajaxResponse'; | ||||
|  | 
 | ||||
|  | export default { | ||||
|  |     global, | ||||
|  |     webSocket, | ||||
|  |     ajaxResponse, | ||||
|  | }; | ||||
| @ -0,0 +1,21 @@ | |||||
|  | 'use strict'; | ||||
|  | import * as actionTypes from '../actions/webSocket'; | ||||
|  | import Immutable from 'immutable'; | ||||
|  | 
 | ||||
|  | const initState = { | ||||
|  |     socket: null, | ||||
|  | }; | ||||
|  | 
 | ||||
|  | function webSocket (state = initState, action) { | ||||
|  |     const payload = action.payload; | ||||
|  |     switch (action.type) { | ||||
|  |         case actionTypes.INIT_WEB_SOCKET: | ||||
|  |             return Immutable.fromJS(state).merge({ | ||||
|  |                 socket: payload.socket, | ||||
|  |             }).toJS(); | ||||
|  |         default: | ||||
|  |             return state; | ||||
|  |     } | ||||
|  | } | ||||
|  | 
 | ||||
|  | export default webSocket; | ||||
| @ -0,0 +1,13 @@ | |||||
|  | 'use strict'; | ||||
|  | 
 | ||||
|  | import storeProd from './store.prod' | ||||
|  | import storeDev from './store.dev' | ||||
|  | 
 | ||||
|  | let store = null; | ||||
|  | if (process.env.NODE_ENV == 'production') { | ||||
|  |     store = storeProd; | ||||
|  | } else { | ||||
|  |     store = storeDev; | ||||
|  | } | ||||
|  | 
 | ||||
|  | export default store; | ||||
| @ -0,0 +1,30 @@ | |||||
|  | /** | ||||
|  |  * Created by liu.xinyi | ||||
|  |  * on 2016/4/8. | ||||
|  |  */ | ||||
|  | 'use strict'; | ||||
|  | import { createStore, combineReducers, applyMiddleware, compose } from 'redux'; | ||||
|  | import reduxThunk from 'redux-thunk'; | ||||
|  | import { connectRouter, routerMiddleware } from 'connected-react-router'; | ||||
|  | import innerReducers from '../reducers'; | ||||
|  | 
 | ||||
|  | function configStore(reducers, history) { | ||||
|  |     const reducer = Object.assign({}, innerReducers, reducers, { | ||||
|  |         router: connectRouter(history) | ||||
|  |     }); | ||||
|  | 
 | ||||
|  |     const composeEnhancers = | ||||
|  |         typeof window === 'object' && | ||||
|  |             window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ? | ||||
|  |             window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({ | ||||
|  |                 // Specify extension’s options like name, actionsBlacklist, actionsCreators, serialize...
 | ||||
|  |             }) : compose; | ||||
|  | 
 | ||||
|  |     const enhancers = composeEnhancers( | ||||
|  |         applyMiddleware(routerMiddleware(history), reduxThunk) | ||||
|  |     ); | ||||
|  | 
 | ||||
|  |     return createStore(combineReducers(reducer), {}, enhancers); | ||||
|  | } | ||||
|  | 
 | ||||
|  | export default configStore; | ||||
| @ -0,0 +1,20 @@ | |||||
|  | /** | ||||
|  |  * Created by liu.xinyi | ||||
|  |  * on 2016/4/8. | ||||
|  |  */ | ||||
|  | 'use strict'; | ||||
|  | 
 | ||||
|  | import { createStore, combineReducers, applyMiddleware } from 'redux'; | ||||
|  | import reduxThunk from 'redux-thunk'; | ||||
|  | import { connectRouter, routerMiddleware } from 'connected-react-router'; | ||||
|  | import innerReducers from '../reducers'; | ||||
|  | 
 | ||||
|  | function configStore(reducers, history){ | ||||
|  |     const reducer = Object.assign({}, innerReducers, reducers, { | ||||
|  |         router: connectRouter(history) | ||||
|  |     }); | ||||
|  | 
 | ||||
|  |     return createStore(combineReducers(reducer), {}, applyMiddleware(routerMiddleware(history), reduxThunk)); | ||||
|  | } | ||||
|  | 
 | ||||
|  | export default configStore; | ||||
| @ -0,0 +1,7 @@ | |||||
|  | // __MICRO_APP_ENVIRONMENT__和__MICRO_APP_PUBLIC_PATH__是由micro-app注入的全局变量
 | ||||
|  | if (window.__MICRO_APP_ENVIRONMENT__) { | ||||
|  |     // eslint-disable-next-line
 | ||||
|  |     __webpack_public_path__ = window.__MICRO_APP_PUBLIC_PATH__ | ||||
|  | } else { | ||||
|  |     __webpack_public_path__ = '/' | ||||
|  | } | ||||
| @ -0,0 +1,92 @@ | |||||
|  | 'use strict'; | ||||
|  | 
 | ||||
|  | import { ApiTable } from '$utils' | ||||
|  | import { Request, basicAction } from '@peace/utils'; | ||||
|  | 
 | ||||
|  | export const INIT_AUTH = 'INIT_AUTH'; | ||||
|  | export function initAuth () { | ||||
|  |     const user = JSON.parse(sessionStorage.getItem('user')) || {}; | ||||
|  |     return { | ||||
|  |         type: INIT_AUTH, | ||||
|  |         payload: { | ||||
|  |             user: user | ||||
|  |         } | ||||
|  |     }; | ||||
|  | } | ||||
|  | 
 | ||||
|  | export const REQUEST_LOGIN = 'REQUEST_LOGIN'; | ||||
|  | export const LOGIN_SUCCESS = 'LOGIN_SUCCESS'; | ||||
|  | export const LOGIN_ERROR = 'LOGIN_ERROR'; | ||||
|  | export function login (username, password) { | ||||
|  |     return dispatch => { | ||||
|  |         dispatch({ type: REQUEST_LOGIN }); | ||||
|  | 
 | ||||
|  |         if (!username || !password) { | ||||
|  |             dispatch({ | ||||
|  |                 type: LOGIN_ERROR, | ||||
|  |                 payload: { error: '请输入账号名和密码' } | ||||
|  |             }); | ||||
|  |             return Promise.resolve(); | ||||
|  |         } | ||||
|  | 
 | ||||
|  |         // return dispatch({
 | ||||
|  |         //     type: LOGIN_SUCCESS,
 | ||||
|  |         //     payload: {
 | ||||
|  |         //         user: {
 | ||||
|  |         //             authorized: true,
 | ||||
|  |         //             namePresent: 'TEST'
 | ||||
|  |         //         }
 | ||||
|  |         //     },
 | ||||
|  |         // });
 | ||||
|  | 
 | ||||
|  |         return Request.post(ApiTable.login, { username, password }) | ||||
|  |             .then(user => { | ||||
|  |                 sessionStorage.setItem('user', JSON.stringify(user)); | ||||
|  |                 localStorage.setItem('fs_iot_cross_user', JSON.stringify(user)); | ||||
|  |                 return dispatch({ | ||||
|  |                     type: LOGIN_SUCCESS, | ||||
|  |                     payload: { user: user }, | ||||
|  |                 }); | ||||
|  |             }, error => { | ||||
|  |                 let { body } = error.response; | ||||
|  |                 return dispatch({ | ||||
|  |                     type: LOGIN_ERROR, | ||||
|  |                     payload: { | ||||
|  |                         error: body && body.message ? body.message : '登录失败' | ||||
|  |                     } | ||||
|  |                 }) | ||||
|  |             }); | ||||
|  |     } | ||||
|  | } | ||||
|  | 
 | ||||
|  | export const LOGOUT = 'LOGOUT'; | ||||
|  | export function logout () { | ||||
|  |     const user = JSON.parse(sessionStorage.getItem('user')) | ||||
|  |     Request.put(ApiTable.logout, { | ||||
|  |         token: user.token | ||||
|  |     }); | ||||
|  |     sessionStorage.removeItem('user'); | ||||
|  |     localStorage.removeItem('fs_iot_cross_user'); | ||||
|  |     return { | ||||
|  |         type: LOGOUT | ||||
|  |     }; | ||||
|  | } | ||||
|  | 
 | ||||
|  | export function crossCheck (data) { | ||||
|  |     return (dispatch) => | ||||
|  |         basicAction({ | ||||
|  |             type: "post", | ||||
|  |             dispatch: dispatch, | ||||
|  |             data: data, | ||||
|  |             actionType: "CROSS_CHECK", | ||||
|  |             url: `${ApiTable.crossCheck}`, | ||||
|  |             msg: {}, | ||||
|  |         }); | ||||
|  | } | ||||
|  | 
 | ||||
|  | export default { | ||||
|  |     initAuth, | ||||
|  |     login, | ||||
|  |     logout, | ||||
|  |     crossCheck, | ||||
|  | } | ||||
| @ -0,0 +1,7 @@ | |||||
|  | 'use strict'; | ||||
|  | 
 | ||||
|  | import auth from './auth'; | ||||
|  | 
 | ||||
|  | export default { | ||||
|  |     ...auth     | ||||
|  | }; | ||||
| @ -0,0 +1,76 @@ | |||||
|  | 'use strict'; | ||||
|  | import React, { useEffect, useRef } from 'react'; | ||||
|  | import { connect } from "react-redux"; | ||||
|  | import authAction from '../actions' | ||||
|  | 
 | ||||
|  | const Cross = ({ dispatch, actions }) => { | ||||
|  | 
 | ||||
|  |     useEffect(async () => { | ||||
|  |         function preLogout () { | ||||
|  |             localStorage.removeItem('fs_iot_cross_user') | ||||
|  |             sessionStorage.removeItem('user') | ||||
|  |         } | ||||
|  | 
 | ||||
|  |         function messageListen (e) { | ||||
|  |             // 此处需做 域名 验证 | ||||
|  |             const { data } = e | ||||
|  |             if (data && data.action) { | ||||
|  |                 if (data.action == 'logout') { | ||||
|  |                     // 子系统退出,清除本地缓存的登录信息 | ||||
|  |                     preLogout() | ||||
|  |                 } else if (data.action = 'login') { | ||||
|  |                     // 子系统登录,将登录信息记录到本地缓存 | ||||
|  |                     localStorage.setItem('fs_iot_cross_user', JSON.stringify(data.user)) | ||||
|  |                 } | ||||
|  |             } | ||||
|  |         } | ||||
|  |         function storageListen (e) { | ||||
|  |             if (e.key == 'fs_iot_cross_user') { | ||||
|  |                 if (!e.newValue) { | ||||
|  |                     // 鉴权系统本身退出 退出其他子系统 | ||||
|  |                     // IOT AUTH 退出 | ||||
|  |                     window.parent.postMessage({ action: 'logout' }, '*'); | ||||
|  |                 } | ||||
|  |             } | ||||
|  |         } | ||||
|  |         if (window.parent) { | ||||
|  |             window.addEventListener('message', messageListen); | ||||
|  |             window.addEventListener("storage", storageListen); | ||||
|  |             let user = localStorage.getItem('fs_iot_cross_user') | ||||
|  | 
 | ||||
|  |             if (user) { | ||||
|  |                 user = JSON.parse(user) | ||||
|  |                 const crossRslt = await dispatch(authAction.crossCheck({ token: user.token })) | ||||
|  |                 if (crossRslt.success && crossRslt.payload.data.cross) { | ||||
|  |                     // 查询到登录信息且没有过期,则将登录信息发送到子系统 | ||||
|  |                     window.parent.postMessage({ action: 'initUser', user: user }, '*'); | ||||
|  |                 } else { | ||||
|  |                     // 查询到的登录信息不符合条件,发送信息让子系统退出 | ||||
|  |                     window.parent.postMessage({ action: 'logout' }, '*'); | ||||
|  |                     preLogout() | ||||
|  |                 } | ||||
|  |             } else { | ||||
|  |                 // 没有登录信息,发送信息通知子系统进行其他处理 | ||||
|  |                 window.parent.postMessage({ action: 'initNotice' }, '*'); | ||||
|  |             } | ||||
|  |         } | ||||
|  |         return () => { | ||||
|  |             window.removeEventListener('message', messageListen); | ||||
|  |             window.removeEventListener('storage', storageListen); | ||||
|  |         } | ||||
|  |     }, []) | ||||
|  | 
 | ||||
|  |     return ( | ||||
|  |         <div>FS_IOT_AUTH_CROSS</div> | ||||
|  |     ) | ||||
|  | } | ||||
|  | 
 | ||||
|  | function mapStateToProps (state) { | ||||
|  |     const { global, auth, webSocket } = state; | ||||
|  |     return { | ||||
|  |         actions: global.actions, | ||||
|  |         user: auth.user, | ||||
|  |     }; | ||||
|  | } | ||||
|  | 
 | ||||
|  | export default connect(mapStateToProps)(Cross); | ||||
| @ -0,0 +1,5 @@ | |||||
|  | 'use strict'; | ||||
|  | import Login from './login'; | ||||
|  | import Cross from './cross'; | ||||
|  | 
 | ||||
|  | export { Login, Cross }; | ||||
| @ -0,0 +1,97 @@ | |||||
|  | 'use strict'; | ||||
|  | import React, { useEffect, useRef } from 'react'; | ||||
|  | import { connect } from 'react-redux'; | ||||
|  | import { push } from 'react-router-redux'; | ||||
|  | import { Form, Button, Toast } from '@douyinfe/semi-ui'; | ||||
|  | import { login, LOGIN_SUCCESS } from '../actions/auth'; | ||||
|  | import { IconLock, IconUser } from '@douyinfe/semi-icons'; | ||||
|  | import '../style.less' | ||||
|  | 
 | ||||
|  | const Login = props => { | ||||
|  |     const { dispatch, user, error, actions, apiRoot, isRequesting } = props | ||||
|  |     const form = useRef(); | ||||
|  | 
 | ||||
|  |     useEffect(() => { | ||||
|  |         if (error) { | ||||
|  |             Toast.error(error); | ||||
|  |             form.current.setValue('password', '') | ||||
|  |         } | ||||
|  |     }, [error]) | ||||
|  | 
 | ||||
|  |     useEffect(() => { | ||||
|  |         if (user && user.authorized) { | ||||
|  |             dispatch(push('/edition')); | ||||
|  |             // localStorage.setItem('fs_iot_auth_selected_sider', JSON.stringify([])) | ||||
|  |             // localStorage.setItem('fs_iot_auth_open_sider', JSON.stringify([])) | ||||
|  |         } | ||||
|  |     }, [user]) | ||||
|  | 
 | ||||
|  |     return ( | ||||
|  |         <div style={{ | ||||
|  |             height: '100vh', | ||||
|  |             backgroundImage: "url('/assets/images/background/loginBackground.png')", | ||||
|  |             backgroundSize: 'cover', | ||||
|  |             backgroundRepeat: 'no-repeat', | ||||
|  |             position: 'relative', | ||||
|  |         }}> | ||||
|  |             <div style={{ | ||||
|  |                 width: 446, | ||||
|  |                 height: 348, | ||||
|  |                 padding: '45px 60px', | ||||
|  |                 backgroundImage: "url('/assets/images/background/loginbg.png')", | ||||
|  |                 backgroundSize: '100% 100%', | ||||
|  |                 backgroundRepeat: 'no-repeat', | ||||
|  |                 position: 'absolute', | ||||
|  |                 top: '33.89%', | ||||
|  |                 right: '16.43%', | ||||
|  |             }}> | ||||
|  |                 <div style={{ width: 113, height: 24, marginTop: 3, marginLeft: 5 }}> | ||||
|  |                     <img src="/assets/images/background/user_login.png" alt="" style={{ width: '100%', height: '100%' }} /> | ||||
|  |                 </div> | ||||
|  |                 <Form | ||||
|  |                     onSubmit={values => { | ||||
|  |                         dispatch(login(values.username, values.password)).then(res => { | ||||
|  |                             const data = res.payload.user | ||||
|  |                             dispatch(actions.layout.initWebSocket({ ioUrl: apiRoot, token: data.token })) | ||||
|  |                         }) | ||||
|  |                     }} | ||||
|  |                     getFormApi={formApi => form.current = formApi} | ||||
|  |                 > | ||||
|  |                     <Form.Input | ||||
|  |                         className='inputbgc' | ||||
|  |                         field='username' | ||||
|  |                         noLabel={true} | ||||
|  |                         label='用户名' | ||||
|  |                         placeholder='请输入账号' | ||||
|  |                         prefix={<IconUser style={{ color: '#1859C1', marginRight: 14, marginLeft: 8 }} />} | ||||
|  |                         style={{ background: 'rgba(24, 89, 193, 0.08)', height: 40, marginTop: 26 }} | ||||
|  |                     /> | ||||
|  |                     <Form.Input | ||||
|  |                         field='password' | ||||
|  |                         noLabel={true} | ||||
|  |                         mode="password" | ||||
|  |                         autoComplete="" | ||||
|  |                         placeholder='请输入密码' | ||||
|  |                         label='密码' | ||||
|  |                         prefix={<IconLock style={{ color: '#1859C1', marginRight: 14, marginLeft: 8 }} />} | ||||
|  |                         style={{ background: 'rgba(24, 89, 193, 0.08)', height: 40 }} | ||||
|  |                     /> | ||||
|  |                     <Button htmlType='submit' block theme="solid" style={{ marginTop: 17, height: 40, backgroundColor: '#1859C1' }}>立即登录</Button> | ||||
|  |                 </Form> | ||||
|  |             </div> | ||||
|  |         </div> | ||||
|  |     ); | ||||
|  | } | ||||
|  | 
 | ||||
|  | function mapStateToProps (state) { | ||||
|  |     const { auth, global } = state; | ||||
|  |     return { | ||||
|  |         user: auth.user, | ||||
|  |         error: auth.error, | ||||
|  |         actions: global.actions, | ||||
|  |         apiRoot: global.apiRoot, | ||||
|  |         isRequesting: auth.isRequesting | ||||
|  |     } | ||||
|  | } | ||||
|  | 
 | ||||
|  | export default connect(mapStateToProps)(Login); | ||||
| @ -0,0 +1,12 @@ | |||||
|  | 'use strict'; | ||||
|  | 
 | ||||
|  | import routes from './routes'; | ||||
|  | import reducers from './reducers'; | ||||
|  | import actions from './actions'; | ||||
|  | 
 | ||||
|  | export default { | ||||
|  |     key: 'auth', | ||||
|  |     reducers: reducers, | ||||
|  |     routes: routes, | ||||
|  |     actions: actions | ||||
|  | }; | ||||
| @ -0,0 +1,40 @@ | |||||
|  | 'use strict'; | ||||
|  | import * as actionTypes from '../actions/auth'; | ||||
|  | import Immutable from 'immutable'; | ||||
|  | 
 | ||||
|  | const initState = { | ||||
|  |     user: {}, | ||||
|  |     isRequesting: false, | ||||
|  |     error: null | ||||
|  | }; | ||||
|  | 
 | ||||
|  | function auth (state = initState, action) { | ||||
|  |     const payload = action.payload; | ||||
|  |     switch (action.type) { | ||||
|  |         case actionTypes.INIT_AUTH: | ||||
|  |             return Immutable.fromJS(state).set('user', payload.user).toJS(); | ||||
|  |         case actionTypes.REQUEST_LOGIN: | ||||
|  |             return Immutable.fromJS(state).merge({ | ||||
|  |                 isRequesting: true, | ||||
|  |                 error: null | ||||
|  |             }).toJS(); | ||||
|  |         case actionTypes.LOGIN_SUCCESS: | ||||
|  |             return Immutable.fromJS(state).merge({ | ||||
|  |                 isRequesting: false, | ||||
|  |                 user: payload.user | ||||
|  |             }).toJS(); | ||||
|  |         case actionTypes.LOGIN_ERROR: | ||||
|  |             return Immutable.fromJS(state).merge({ | ||||
|  |                 isRequesting: false, | ||||
|  |                 error: payload.error | ||||
|  |             }).toJS(); | ||||
|  |         case actionTypes.LOGOUT: | ||||
|  |             return Immutable.fromJS(state).merge({ | ||||
|  |                 user: null | ||||
|  |             }).toJS(); | ||||
|  |         default: | ||||
|  |             return state; | ||||
|  |     } | ||||
|  | } | ||||
|  | 
 | ||||
|  | export default auth; | ||||
| @ -0,0 +1,6 @@ | |||||
|  | 'use strict'; | ||||
|  | import auth from './auth' | ||||
|  | 
 | ||||
|  | export default { | ||||
|  |     auth | ||||
|  | }; | ||||
| @ -0,0 +1,22 @@ | |||||
|  | 'use strict'; | ||||
|  | 
 | ||||
|  | import { Login, Cross } from './containers'; | ||||
|  | 
 | ||||
|  | export default [ | ||||
|  |     { | ||||
|  |         type: 'outer', | ||||
|  |         route: { | ||||
|  |             key: 'signin', | ||||
|  |             path: "/signin", | ||||
|  |             component: Login | ||||
|  |         } | ||||
|  |     }, | ||||
|  |     { | ||||
|  |         type: 'outer', | ||||
|  |         route: { | ||||
|  |             key: 'cross', | ||||
|  |             path: "/cross", | ||||
|  |             component: Cross | ||||
|  |         } | ||||
|  |     } | ||||
|  | ]; | ||||
| @ -0,0 +1,7 @@ | |||||
|  | input:-webkit-autofill{ | ||||
|  |   -webkit-text-fill-color:black !important; | ||||
|  |   -webkit-box-shadow: 0 0 0px 1000px transparent  inset !important; | ||||
|  |   box-shadow: 0 0 0px 1000px transparent  inset !important; | ||||
|  |   background-color:transparent; | ||||
|  |   transition: background-color 50000s ease-in-out 0s; | ||||
|  | } | ||||
| @ -0,0 +1,6 @@ | |||||
|  | 'use strict'; | ||||
|  | 
 | ||||
|  | 
 | ||||
|  | export default { | ||||
|  |        | ||||
|  | }; | ||||
| @ -0,0 +1,37 @@ | |||||
|  | 'use strict'; | ||||
|  | import React, { useEffect, useRef } from 'react'; | ||||
|  | import { connect } from 'react-redux'; | ||||
|  | import { push } from 'react-router-redux'; | ||||
|  | import { Form, Button, Toast } from '@douyinfe/semi-ui'; | ||||
|  | import { IconLock, IconUser } from '@douyinfe/semi-icons'; | ||||
|  | 
 | ||||
|  | const Administer = props => { | ||||
|  |    const { dispatch, user, error, actions, apiRoot, isRequesting } = props | ||||
|  | 
 | ||||
|  | 
 | ||||
|  | 
 | ||||
|  |    useEffect(() => { | ||||
|  | 
 | ||||
|  |    }, []) | ||||
|  | 
 | ||||
|  |    return ( | ||||
|  |       <div style={{ | ||||
|  | 
 | ||||
|  |       }}> | ||||
|  |          5204620542045204 | ||||
|  |       </div> | ||||
|  |    ); | ||||
|  | } | ||||
|  | 
 | ||||
|  | function mapStateToProps (state) { | ||||
|  |    const { auth, global } = state; | ||||
|  |    return { | ||||
|  |       user: auth.user, | ||||
|  |       error: auth.error, | ||||
|  |       actions: global.actions, | ||||
|  |       apiRoot: global.apiRoot, | ||||
|  |       isRequesting: auth.isRequesting | ||||
|  |    } | ||||
|  | } | ||||
|  | 
 | ||||
|  | export default connect(mapStateToProps)(Administer); | ||||
| @ -0,0 +1,4 @@ | |||||
|  | 'use strict'; | ||||
|  | import Administer from './administer.jsx'; | ||||
|  | 
 | ||||
|  | export { Administer }; | ||||
| @ -0,0 +1,12 @@ | |||||
|  | 'use strict'; | ||||
|  | 
 | ||||
|  | import routes from './routes'; | ||||
|  | import reducers from './reducers'; | ||||
|  | import actions from './actions'; | ||||
|  | 
 | ||||
|  | export default { | ||||
|  |     key: 'edition', | ||||
|  |     reducers: reducers, | ||||
|  |     routes: routes, | ||||
|  |     actions: actions | ||||
|  | }; | ||||
| @ -0,0 +1,13 @@ | |||||
|  | import React from 'react'; | ||||
|  | import { IconCode } from '@douyinfe/semi-icons'; | ||||
|  | 
 | ||||
|  | export function getNavItem (user, dispatch) { | ||||
|  |    return ( | ||||
|  |       [ | ||||
|  |          { | ||||
|  |             itemKey: 'edition', text: 'edition', icon: <IconCode />, | ||||
|  |             to: '/edition', | ||||
|  |          }, | ||||
|  |       ] | ||||
|  |    ); | ||||
|  | } | ||||
| @ -0,0 +1,6 @@ | |||||
|  | 'use strict'; | ||||
|  | 
 | ||||
|  | 
 | ||||
|  | export default { | ||||
|  |     | ||||
|  | }; | ||||
| @ -0,0 +1,15 @@ | |||||
|  | 'use strict'; | ||||
|  | 
 | ||||
|  | import { Administer } from './containers'; | ||||
|  | 
 | ||||
|  | export default [ | ||||
|  |     { | ||||
|  |         type: 'inner', | ||||
|  |         route: { | ||||
|  |             key: 'edition', | ||||
|  |             path: "/edition", | ||||
|  |             breadcrumb: '版本管理', | ||||
|  |             component: Administer, | ||||
|  |         } | ||||
|  |     } | ||||
|  | ]; | ||||
| @ -0,0 +1,5 @@ | |||||
|  | 'use strict'; | ||||
|  | 
 | ||||
|  | export default { | ||||
|  |     | ||||
|  | } | ||||
| @ -0,0 +1,5 @@ | |||||
|  | 'use strict'; | ||||
|  | 
 | ||||
|  | import MicroApp from './microApp'; | ||||
|  | 
 | ||||
|  | export { MicroApp }; | ||||
| @ -0,0 +1,37 @@ | |||||
|  | import React, { useEffect } from 'react'; | ||||
|  | import { connect } from 'react-redux'; | ||||
|  | import { Spin, Card } from '@douyinfe/semi-ui'; | ||||
|  | 
 | ||||
|  | const MicroApp = (props) => { | ||||
|  |     const { dispatch, actions, } = props | ||||
|  | 
 | ||||
|  |     return ( | ||||
|  |         <div> | ||||
|  |             <p>MicroApp</p> | ||||
|  |             <div style={{ | ||||
|  |                 height: 'calc(100% - 64px)', overflow: 'auto', position: 'absolute', | ||||
|  |             }}> | ||||
|  |                 <micro-app | ||||
|  |                     name='microapp-test' | ||||
|  |                     url='http://localhost:5000/' | ||||
|  |                     baseroute='/microApp' | ||||
|  |                     inline | ||||
|  |                 // disableSandbox | ||||
|  |                 // shadowDOM | ||||
|  |                 style={{height:'100%'}} | ||||
|  |                 > | ||||
|  |                     microApp | ||||
|  |                 </micro-app> | ||||
|  |             </div> | ||||
|  |         </div> | ||||
|  |     ) | ||||
|  | } | ||||
|  | 
 | ||||
|  | function mapStateToProps (state) { | ||||
|  |     const { auth, global } = state; | ||||
|  |     return { | ||||
|  | 
 | ||||
|  |     }; | ||||
|  | } | ||||
|  | 
 | ||||
|  | export default connect(mapStateToProps)(MicroApp); | ||||
| @ -0,0 +1,15 @@ | |||||
|  | 'use strict'; | ||||
|  | 
 | ||||
|  | import reducers from './reducers'; | ||||
|  | import routes from './routes'; | ||||
|  | import actions from './actions'; | ||||
|  | import { getNavItem } from './nav-item'; | ||||
|  | 
 | ||||
|  | export default { | ||||
|  |    key: 'microApp', | ||||
|  |    name: 'MicroApp', | ||||
|  |    reducers: reducers, | ||||
|  |    routes: routes, | ||||
|  |    actions: actions, | ||||
|  |    getNavItem: getNavItem | ||||
|  | }; | ||||
| @ -0,0 +1,13 @@ | |||||
|  | import React from 'react'; | ||||
|  | import { IconCode } from '@douyinfe/semi-icons'; | ||||
|  | 
 | ||||
|  | export function getNavItem (user, dispatch) { | ||||
|  |    return ( | ||||
|  |       [ | ||||
|  |          { | ||||
|  |             itemKey: 'MicroApp', text: 'MicroApp', icon: <IconCode />, | ||||
|  |             to: '/microApp', | ||||
|  |          }, | ||||
|  |       ] | ||||
|  |    ); | ||||
|  | } | ||||
| @ -0,0 +1,5 @@ | |||||
|  | 'use strict'; | ||||
|  | 
 | ||||
|  | export default { | ||||
|  | 
 | ||||
|  | } | ||||
| @ -0,0 +1,12 @@ | |||||
|  | 'use strict'; | ||||
|  | import { MicroApp, } from './containers'; | ||||
|  | 
 | ||||
|  | export default [{ | ||||
|  |    type: 'inner', | ||||
|  |    route: { | ||||
|  |       path: '/microApp', | ||||
|  |       key: 'microApp', | ||||
|  |       breadcrumb: 'microApp', | ||||
|  |       component: MicroApp, | ||||
|  |    } | ||||
|  | }]; | ||||
| @ -0,0 +1,5 @@ | |||||
|  | 'use strict'; | ||||
|  | 
 | ||||
|  | export const AuthorizationCode = { | ||||
|  |      | ||||
|  | }; | ||||
| @ -0,0 +1,10 @@ | |||||
|  | 'use strict'; | ||||
|  | 
 | ||||
|  | export const isAuthorized = (authcode) => { | ||||
|  |     if (JSON.parse(sessionStorage.getItem('user'))) { | ||||
|  |         const { resources } = JSON.parse(sessionStorage.getItem('user')); | ||||
|  |         return resources.includes(authcode); | ||||
|  |     } else { | ||||
|  |         return false; | ||||
|  |     } | ||||
|  | } | ||||
| @ -0,0 +1,13 @@ | |||||
|  | 'use strict'; | ||||
|  | import { isAuthorized } from './func'; | ||||
|  | import { AuthorizationCode } from './authCode'; | ||||
|  | import { ApiTable, RouteTable } from './webapi' | ||||
|  | 
 | ||||
|  | export { | ||||
|  |     isAuthorized, | ||||
|  | 
 | ||||
|  |     AuthorizationCode, | ||||
|  | 
 | ||||
|  |     ApiTable, | ||||
|  |     RouteTable, | ||||
|  | } | ||||
| @ -0,0 +1,14 @@ | |||||
|  | 'use strict'; | ||||
|  | 
 | ||||
|  | export const ApiTable = { | ||||
|  |     login: 'v1/login', | ||||
|  |     logout: 'logout', | ||||
|  | 
 | ||||
|  |     crossCheck: 'cross_token/check', | ||||
|  | }; | ||||
|  | 
 | ||||
|  | export const RouteTable = { | ||||
|  |     apiRoot: '/api/root', | ||||
|  |     fileUpload: '/_upload/new', | ||||
|  |     cleanUpUploadTrash: '/_upload/cleanup', | ||||
|  | }; | ||||
| @ -0,0 +1,91 @@ | |||||
|  | 'use strict'; | ||||
|  | /*jslint node:true*/ | ||||
|  | const path = require('path'); | ||||
|  | /*这种以CommonJS的同步形式去引入其它模块的方式代码更加简洁:获取组件*/ | ||||
|  | const os = require('os'); | ||||
|  | const moment = require('moment'); | ||||
|  | const args = require('args'); | ||||
|  | const dev = process.env.NODE_ENV == 'development' || process.env.NODE_ENV == 'developmentVite'; | ||||
|  | const vite = process.env.NODE_ENV == 'developmentVite'; | ||||
|  | 
 | ||||
|  | dev && console.log('\x1B[33m%s\x1b[0m', '请遵循并及时更新 readme.md,维护良好的开发环境,媛猿有责'); | ||||
|  | // // 启动参数
 | ||||
|  | args.option(['p', 'port'], '启动端口'); | ||||
|  | args.option(['u', 'api-url'], 'webapi的URL'); | ||||
|  | 
 | ||||
|  | const flags = args.parse(process.argv); | ||||
|  | 
 | ||||
|  | const API_URL = process.env.API_URL || 'http://10.8.30.183:4100'; | ||||
|  | 
 | ||||
|  | if (!API_URL) { | ||||
|  |     console.log('缺少启动参数,异常退出'); | ||||
|  |     args.showHelp(); | ||||
|  |     process.exit(-1); | ||||
|  | } | ||||
|  | 
 | ||||
|  | const product = { | ||||
|  |     port: flags.port || 8080, | ||||
|  |     staticDirs: [path.join(__dirname, './client')], | ||||
|  |     mws: [{ | ||||
|  |         entry: require('./middlewares/proxy').entry, | ||||
|  |         opts: { | ||||
|  |             host: API_URL, | ||||
|  |             match: /^\/_api\//, | ||||
|  |         } | ||||
|  |     }, { | ||||
|  |         entry: require('./routes').entry, | ||||
|  |         opts: { | ||||
|  |             apiUrl: API_URL, | ||||
|  |             staticRoot: './client', | ||||
|  |         } | ||||
|  |     }, { | ||||
|  |         entry: require('./client').entry,// 静态信息
 | ||||
|  |         opts: {} | ||||
|  |     }], | ||||
|  |     logger: { | ||||
|  |         level: 'debug', | ||||
|  |         json: false, | ||||
|  |         filename: path.join(__dirname, 'log', 'runtime.txt'), | ||||
|  |         colorize: true, | ||||
|  |         maxsize: 1024 * 1024 * 5, | ||||
|  |         rotationFormat: false, | ||||
|  |         zippedArchive: true, | ||||
|  |         maxFiles: 10, | ||||
|  |         prettyPrint: true, | ||||
|  |         label: '', | ||||
|  |         timestamp: () => moment().format('YYYY-MM-DD HH:mm:ss.SSS'), | ||||
|  |         eol: os.EOL, | ||||
|  |         tailable: true, | ||||
|  |         depth: null, | ||||
|  |         showLevel: true, | ||||
|  |         maxRetries: 1 | ||||
|  |     } | ||||
|  | }; | ||||
|  | 
 | ||||
|  | let config; | ||||
|  | if (dev) { | ||||
|  |     config = { | ||||
|  |         port: product.port, | ||||
|  |         staticDirs: product.staticDirs, | ||||
|  |         mws: product.mws | ||||
|  |             .concat([ | ||||
|  |                 vite ? | ||||
|  |                     { | ||||
|  |                         entry: require('./middlewares/vite-dev').entry, | ||||
|  |                         opts: {} | ||||
|  |                     } | ||||
|  |                     : | ||||
|  |                     { | ||||
|  |                         entry: require('./middlewares/webpack-dev').entry, | ||||
|  |                         opts: {} | ||||
|  |                     } | ||||
|  |             ]) | ||||
|  |         , | ||||
|  |         logger: product.logger | ||||
|  |     } | ||||
|  |     config.logger.filename = path.join(__dirname, 'log', 'development.txt'); | ||||
|  | } else { | ||||
|  |     config = product; | ||||
|  | } | ||||
|  | 
 | ||||
|  | module.exports = config;//区分开发和发布
 | ||||
| @ -0,0 +1,15 @@ | |||||
|  | { | ||||
|  | 
 | ||||
|  | 	"compilerOptions": { | ||||
|  | 		"target": "es6", | ||||
|  | 		"module": "commonjs", | ||||
|  | 		"allowSyntheticDefaultImports": true | ||||
|  | 	}, | ||||
|  | 	"exclude": [ | ||||
|  | 		"node_modules", | ||||
|  | 		"bower_components", | ||||
|  | 		"jspm_packages", | ||||
|  | 		"tmp", | ||||
|  | 		"temp" | ||||
|  | 	] | ||||
|  | } | ||||
| @ -0,0 +1,16 @@ | |||||
|  | 'use strict'; | ||||
|  | 
 | ||||
|  | const proxy = require('koa-proxy'); | ||||
|  | const convert = require('koa-convert'); | ||||
|  | 
 | ||||
|  | module.exports = { | ||||
|  |     entry: function (app, router, opts) { | ||||
|  |         app.use(convert(proxy({ | ||||
|  |             host: opts.host, | ||||
|  |             match: opts.match, | ||||
|  |             map: function (path) { | ||||
|  |                 return path.replace(opts.match, ''); | ||||
|  |             } | ||||
|  |         }))); | ||||
|  |     } | ||||
|  | }; | ||||
| @ -0,0 +1,25 @@ | |||||
|  | 'use strict'; | ||||
|  | 
 | ||||
|  | const express = require('express') | ||||
|  | const { createServer: createViteServer } = require('vite') | ||||
|  | 
 | ||||
|  | module.exports = { | ||||
|  |     entry: async function (app, router, opts) { | ||||
|  |         const server = express() | ||||
|  | 
 | ||||
|  |         // 以中间件模式创建 Vite 服务器
 | ||||
|  |         // 竟然会自动读 /vite.config.js 的配置
 | ||||
|  |         const vite = await createViteServer({}) | ||||
|  |         // 将 vite 的 connect 实例作中间件使用
 | ||||
|  |         server.use(vite.middlewares) | ||||
|  | 
 | ||||
|  |         server.use('*', async (req, res) => { | ||||
|  |             // 如果 `middlewareMode` 是 `'ssr'`,应在此为 `index.html` 提供服务.
 | ||||
|  |             // 如果 `middlewareMode` 是 `'html'`,则此处无需手动服务 `index.html`
 | ||||
|  |             // 因为 Vite 自会接管
 | ||||
|  |         }) | ||||
|  | 
 | ||||
|  |         server.listen(5102) | ||||
|  |         console.info('vite server.listen 5202'); | ||||
|  |     } | ||||
|  | }; | ||||
| @ -0,0 +1,56 @@ | |||||
|  | 'use strict'; | ||||
|  | const express = require('express') | ||||
|  | const webpack = require('webpack'); | ||||
|  | const devConfig = require('../webpack.config'); | ||||
|  | const middleware = require('webpack-dev-middleware'); | ||||
|  | const proxy = require('koa-better-http-proxy'); | ||||
|  | const url = require('url'); | ||||
|  | 
 | ||||
|  | const compiler = webpack(devConfig); | ||||
|  | 
 | ||||
|  | module.exports = { | ||||
|  |     entry: function (app, router, opts) { | ||||
|  |         app.use(proxy('http://localhost:5201', { | ||||
|  |             filter: function (ctx) { | ||||
|  |                 return /\/build/.test(url.parse(ctx.url).path); | ||||
|  |             }, | ||||
|  |             proxyReqPathResolver: function (ctx) { | ||||
|  |                 return 'client' + url.parse(ctx.url).path; | ||||
|  |             } | ||||
|  |         })); | ||||
|  | 
 | ||||
|  |         app.use(proxy('http://localhost:5201', { | ||||
|  |             filter: function (ctx) { | ||||
|  |                 return /\/$/.test(url.parse(ctx.url).path); | ||||
|  |             }, | ||||
|  |             proxyReqPathResolver: function (ctx) { | ||||
|  |                 return 'client/build/index.html'; | ||||
|  |             } | ||||
|  |         })); | ||||
|  | 
 | ||||
|  |         const server = express(); | ||||
|  | 
 | ||||
|  |         server.all("*", function (req, res, next) { | ||||
|  |             //设置允许跨域的域名,*代表允许任意域名跨域
 | ||||
|  |             res.header("Access-Control-Allow-Origin", "*"); | ||||
|  |             //允许的header类型
 | ||||
|  |             res.header("Access-Control-Allow-Headers", "content-type"); | ||||
|  |             //跨域允许的请求方式 
 | ||||
|  |             res.header("Access-Control-Allow-Methods", "DELETE,PUT,POST,GET,OPTIONS"); | ||||
|  |             if (req.method == 'OPTIONS') | ||||
|  |                res.sendStatus(200); //让options尝试请求快速结束
 | ||||
|  |             else | ||||
|  |                next(); | ||||
|  |          }); | ||||
|  |           | ||||
|  |         server.use(middleware(compiler)); | ||||
|  |         // server.use(require("webpack-hot-middleware")(compiler));
 | ||||
|  |         server.listen('5201', function (err) { | ||||
|  |             if (err) { | ||||
|  |                 console.error(err); | ||||
|  |             } else { | ||||
|  |                 console.info(`webpack-dev listen 5201`); | ||||
|  |             } | ||||
|  |         }) | ||||
|  |     } | ||||
|  | }; | ||||
| @ -0,0 +1,78 @@ | |||||
|  | { | ||||
|  |    "name": "fs-anxincloud-4.0", | ||||
|  |    "version": "1.0.0", | ||||
|  |    "description": "anxincloud-4.0", | ||||
|  |    "main": "server.js", | ||||
|  |    "scripts": { | ||||
|  |       "test": "mocha", | ||||
|  |       "start-vite": "cross-env NODE_ENV=developmentVite npm run start-params", | ||||
|  |       "start": "cross-env NODE_ENV=development npm run start-params", | ||||
|  |       "start-params": "node server -p 5200 -u http://localhost:4200", | ||||
|  |       "deploy": "export NODE_ENV=production&& npm run build && node server", | ||||
|  |       "build-dev": "cross-env NODE_ENV=development&&webpack --config webpack.config.js", | ||||
|  |       "build": "export NODE_ENV=production&&webpack --config webpack.config.prod.js" | ||||
|  |    }, | ||||
|  |    "keywords": [ | ||||
|  |       "app" | ||||
|  |    ], | ||||
|  |    "author": "", | ||||
|  |    "license": "ISC", | ||||
|  |    "devDependencies": { | ||||
|  |       "@babel/core": "^7.14.6", | ||||
|  |       "@babel/plugin-proposal-class-properties": "^7.14.5", | ||||
|  |       "@babel/plugin-proposal-object-rest-spread": "^7.14.7", | ||||
|  |       "@babel/plugin-transform-runtime": "^7.14.5", | ||||
|  |       "@babel/polyfill": "^7.12.1", | ||||
|  |       "@babel/preset-env": "^7.14.7", | ||||
|  |       "@babel/preset-react": "^7.14.5", | ||||
|  |       "babel-loader": "^8.2.2", | ||||
|  |       "babel-plugin-import": "^1.13.3", | ||||
|  |       "connected-react-router": "^6.8.0", | ||||
|  |       "css-loader": "^3.5.0", | ||||
|  |       "express": "^4.17.1", | ||||
|  |       "file-loader": "^6.0.0", | ||||
|  |       "html-webpack-plugin": "^4.5.0", | ||||
|  |       "immutable": "^4.0.0-rc.12", | ||||
|  |       "less": "^3.12.2", | ||||
|  |       "less-loader": "^7.0.2", | ||||
|  |       "nprogress": "^0.2.0", | ||||
|  |       "react": "^17.0.0", | ||||
|  |       "react-dom": "^17.0.0", | ||||
|  |       "react-redux": "^7.2.1", | ||||
|  |       "react-router-dom": "^5.2.0", | ||||
|  |       "react-router-redux": "^4.0.8", | ||||
|  |       "redux": "^4.0.5", | ||||
|  |       "redux-thunk": "^2.3.0", | ||||
|  |       "style-loader": "^2.0.0", | ||||
|  |       "vite": "^2.9.5" | ||||
|  |    }, | ||||
|  |    "dependencies": { | ||||
|  |       "@douyinfe/semi-ui": "^2.8.0", | ||||
|  |       "@fs/attachment": "^1.0.0", | ||||
|  |       "@micro-zoe/micro-app": "^1.0.0-alpha.1", | ||||
|  |       "@peace/components": "0.0.35", | ||||
|  |       "@peace/utils": "^0.0.48", | ||||
|  |       "@vitejs/plugin-react": "^1.3.1", | ||||
|  |       "@vitejs/plugin-react-refresh": "^1.3.6", | ||||
|  |       "args": "^5.0.1", | ||||
|  |       "babel-polyfill": "^6.26.0", | ||||
|  |       "copy-to-clipboard": "^3.3.1", | ||||
|  |       "cross-env": "^7.0.3", | ||||
|  |       "fs-web-server-scaffold": "^1.0.6", | ||||
|  |       "koa-better-http-proxy": "^0.2.5", | ||||
|  |       "koa-proxy": "^1.0.0-alpha.3", | ||||
|  |       "koa-view": "^2.1.4", | ||||
|  |       "moment": "^2.29.3", | ||||
|  |       "npm": "^7.20.6", | ||||
|  |       "perfect-scrollbar": "^1.5.5", | ||||
|  |       "socket.io-client": "^4.5.0", | ||||
|  |       "socket.io-parser": "^4.2.0", | ||||
|  |       "superagent": "^6.1.0", | ||||
|  |       "webpack": "^5.3.2", | ||||
|  |       "webpack-bundle-analyzer": "^4.1.0", | ||||
|  |       "webpack-cli": "^4.2.0", | ||||
|  |       "webpack-dev-middleware": "^4.0.2", | ||||
|  |       "webpack-dev-server": "^3.11.2", | ||||
|  |       "webpack-hot-middleware": "^2.25.0" | ||||
|  |    } | ||||
|  | } | ||||
| @ -0,0 +1,214 @@ | |||||
|  | 创建时间:2021/08/19 | ||||
|  | 
 | ||||
|  | ## 1. 文档维护: | ||||
|  | 
 | ||||
|  | - 文档相关内容若有更改,请及时更新文档,以备后来者查询; | ||||
|  | 
 | ||||
|  | ## 2. 项目开发: | ||||
|  | 
 | ||||
|  | - 请遵循此文档约定的目录结构与约定 | ||||
|  | 
 | ||||
|  | ```js | ||||
|  |     |-- .babelrc | ||||
|  |     |-- config.js | ||||
|  |     |-- Dockerfile | ||||
|  |     |-- jsconfig.json | ||||
|  |     |-- package.json | ||||
|  |     |-- readme.md | ||||
|  |     |-- server.js | ||||
|  |     |-- webpack.config.js | ||||
|  |     |-- webpack.config.prod.js | ||||
|  |     |-- .vscode | ||||
|  |     |   |-- launch.json | ||||
|  |     |   |-- settings.json | ||||
|  |     |-- client | ||||
|  |     |   |-- index.ejs | ||||
|  |     |   |-- index.html                      // 当前 html 文件 | ||||
|  |     |   |-- index.js | ||||
|  |     |   |-- assets                          // 资源文件 | ||||
|  |     |   |   |-- images | ||||
|  |     |   |       |-- avatar | ||||
|  |     |   |-- src                             // 项目代码 | ||||
|  |     |       |-- app.js                      // 由此开始并加载模块 | ||||
|  |     |       |-- index.js                     | ||||
|  |     |       |-- components                  // 公用组件 | ||||
|  |     |       |   |-- index.js                // 由此导出组件 | ||||
|  |     |       |   |-- Upload | ||||
|  |     |       |       |-- index.js | ||||
|  |     |       |-- layout                      // 项目布局以及初始化等操作 | ||||
|  |     |       |   |-- index.js | ||||
|  |     |       |   |-- actions | ||||
|  |     |       |   |   |-- global.js | ||||
|  |     |       |   |-- components | ||||
|  |     |       |   |   |-- footer | ||||
|  |     |       |   |   |   |-- index.js | ||||
|  |     |       |   |   |-- header | ||||
|  |     |       |   |   |   |-- index.js | ||||
|  |     |       |   |   |-- sider | ||||
|  |     |       |   |       |-- index.js | ||||
|  |     |       |   |-- containers | ||||
|  |     |       |   |   |-- index.js | ||||
|  |     |       |   |   |-- layout | ||||
|  |     |       |   |   |   |-- index.js | ||||
|  |     |       |   |   |   |-- index.less | ||||
|  |     |       |   |   |-- no-match | ||||
|  |     |       |   |       |-- index.js | ||||
|  |     |       |   |-- reducers | ||||
|  |     |       |   |   |-- ajaxResponse.js | ||||
|  |     |       |   |   |-- global.js            // 全局数据,主要包含屏幕可视宽高、所有的 action 等 | ||||
|  |     |       |   |   |-- index.js | ||||
|  |     |       |   |-- store | ||||
|  |     |       |       |-- index.js | ||||
|  |     |       |       |-- store.dev.js | ||||
|  |     |       |       |-- store.prod.js | ||||
|  |     |       |-- sections                     // 各功能模块 | ||||
|  |     |       |   |-- auth                     // 比较特别的 Auth 模块,目前 action、reducer 依然采用原始写法;包含登录、忘记密码等项目基本功能页面 | ||||
|  |     |       |   |   |-- index.js | ||||
|  |     |       |   |   |-- routes.js | ||||
|  |     |       |   |   |-- actions | ||||
|  |     |       |   |   |   |-- auth.js | ||||
|  |     |       |   |   |   |-- index.js | ||||
|  |     |       |   |   |-- components | ||||
|  |     |       |   |   |-- containers | ||||
|  |     |       |   |   |   |-- index.js | ||||
|  |     |       |   |   |   |-- login.js | ||||
|  |     |       |   |   |-- reducers | ||||
|  |     |       |   |   |   |-- auth.js | ||||
|  |     |       |   |   |   |-- index.js | ||||
|  |     |       |   |   |-- __tests__ | ||||
|  |     |       |   |-- example                   // 示例模块,一般的功能模块应遵循此结构 | ||||
|  |     |       |       |-- index.js              // 由此导出该模块信息,应包括一个 key 值,actions 等 | ||||
|  |     |       |       |-- nav-item.js           // 用于生成菜单项,此文件内可以进行权限判断 | ||||
|  |     |       |       |-- routes.js             // 路由文件 | ||||
|  |     |       |       |-- style.less            // 样式文件,若样式并不是非常多,每个模块一个样式文件即可 | ||||
|  |     |       |       |-- actions                  | ||||
|  |     |       |       |   |-- example.js        // 具体的 action 操作 | ||||
|  |     |       |       |   |-- index.js          // 由此导出该项目的 action | ||||
|  |     |       |       |-- components            // 组件 | ||||
|  |     |       |       |-- containers            // 容器,此文件夹内应只包括该模块第一层级的页面 | ||||
|  |     |       |       |   |-- example.js | ||||
|  |     |       |       |   |-- index.js | ||||
|  |     |       |       |-- reducers              // 若采用封装后的 action 写法,则 reducer 可不写 | ||||
|  |     |       |           |-- index.js | ||||
|  |     |       |-- utils                         // | ||||
|  |     |           |-- authCode.js | ||||
|  |     |           |-- func.js                   // 常用函数 | ||||
|  |     |           |-- index.js | ||||
|  |     |           |-- webapi.js                 // api 路由 | ||||
|  |     |-- log | ||||
|  |     |-- middlewares | ||||
|  |     |   |-- proxy.js | ||||
|  |     |   |-- webpack-dev.js | ||||
|  |     |-- routes | ||||
|  |     |   |-- index.js | ||||
|  |     |   |-- attachment | ||||
|  |     |-- typings | ||||
|  |         |-- node | ||||
|  |         |   |-- node.d.ts | ||||
|  |         |-- react | ||||
|  |             |-- react.d.ts | ||||
|  | ``` | ||||
|  | 
 | ||||
|  | - 封装后一般 action 写法: | ||||
|  | 
 | ||||
|  |     `@peace/utils 的 actionHelp 中有详细注释` | ||||
|  | 
 | ||||
|  |     ``` js | ||||
|  |     'use strict'; | ||||
|  | 
 | ||||
|  |     import { basicAction } from '@peace/utils' | ||||
|  |     import { ApiTable } from '$utils' | ||||
|  | 
 | ||||
|  |     export function getMembers(orgId) { | ||||
|  |         return dispatch => basicAction({ | ||||
|  |             type: 'get', | ||||
|  |             dispatch: dispatch, | ||||
|  |             actionType: 'GET_MEMBERS', | ||||
|  |             url: `${ApiTable.getEnterprisesMembers.replace('{enterpriseId}', orgId)}`, | ||||
|  |             msg: { error: '获取用户列表失败' }, | ||||
|  |             reducer: { name: 'members' } | ||||
|  |         }); | ||||
|  |     } | ||||
|  |     ``` | ||||
|  | 
 | ||||
|  | 1. 若 type=post,则可以使用 data 属性发送对象格式数据; | ||||
|  | 
 | ||||
|  | 2. reducer.name 会作为该 action 对应的 reducer 的名字,从 state 里可以解构此变量,获得该 action 异步或其他操作获得的数据; | ||||
|  | 
 | ||||
|  | 3. msg 可以发送 `{ option:'获取用户列表' }` ,则 actionHelp 会自动将其处理为失败和成功两种情况; | ||||
|  | 
 | ||||
|  |     若单独写 success 或 error 的 key,则只在成功或失败的时候进行提示; | ||||
|  | 
 | ||||
|  | 4. 后续可以优化:type=get 时候, | ||||
|  | 
 | ||||
|  |     使用 query 属性将数据传递,在 @peace/utils 的 actionHelp 中将其添加到路由后面;eg. `{ enterpriseId: orgId }` | ||||
|  | 
 | ||||
|  |     使用 replace 属性传递对象数据,对象数据中将被替换的值为key,替换的值为 value,然后再 actionHelp 中更改路由;eg. `{ "{enterpriseId}": orgId}` | ||||
|  | 
 | ||||
|  | 5. 最终取得的 reducer 中的数据格式一般为: | ||||
|  |     ``` js | ||||
|  |     { | ||||
|  |         data: xxx,           // 接口返回的数据格式 | ||||
|  |         isRequesting: false, // 请求状态 | ||||
|  |         success: true,       // 以此判断请求是否成功,不用再以 payload.type 判断 | ||||
|  |     } | ||||
|  |     ``` | ||||
|  | 
 | ||||
|  | - actions 的引用 | ||||
|  | 
 | ||||
|  |     从 reducer 的 state.global.actions 里引用具体 action | ||||
|  | 
 | ||||
|  |     ```js | ||||
|  |     const Example = (props) => { | ||||
|  |         const { dispatch, actions, user, loading } = props | ||||
|  | 
 | ||||
|  |         useEffect(() => { | ||||
|  |             dispatch(actions.example.getMembers(user.orgId)) | ||||
|  |         }, []) | ||||
|  | 
 | ||||
|  |         return ( | ||||
|  |             <Spin tip="biubiubiu~" spinning={loading}> | ||||
|  |                 example | ||||
|  |             </Spin> | ||||
|  |         ) | ||||
|  |     } | ||||
|  | 
 | ||||
|  |     function mapStateToProps(state) { | ||||
|  |         const { auth, global, members } = state; | ||||
|  |         return { | ||||
|  |             loading: members.isRequesting, | ||||
|  |             user: auth.user, | ||||
|  |             actions: global.actions, | ||||
|  |             members: members.data | ||||
|  |         }; | ||||
|  |     } | ||||
|  | 
 | ||||
|  |     export default connect(mapStateToProps)(Example); | ||||
|  |     ``` | ||||
|  | 
 | ||||
|  | - 一般路由配置 | ||||
|  |     ```js | ||||
|  |     'use strict'; | ||||
|  |     import { Example, } from './containers'; | ||||
|  | 
 | ||||
|  |     export default [{ | ||||
|  |         type: 'inner',                // 是否在layout 内,如果为outer,则看不到 header、footer、sider等布局,比如登陆页面 | ||||
|  |         route: { | ||||
|  |             path: '/example', | ||||
|  |             key: 'example', | ||||
|  |             breadcrumb: '栗子', | ||||
|  |                                       // 不设置 component 则面包屑禁止跳转 | ||||
|  |             childRoutes: [{           | ||||
|  |                 path: '/e1',          // 自路由不必复写父路由内容,会自动拼接; 则此处组件的实际路由为 /example/e1 | ||||
|  |                 key: 'e1',           | ||||
|  |                 component: Example, | ||||
|  |                 breadcrumb: '棒子', | ||||
|  |             }] | ||||
|  |         } | ||||
|  |     }]; | ||||
|  |     ``` | ||||
|  | - cross-env 的使用限制 | ||||
|  | 
 | ||||
|  |     cross-env 可以统一不同操作系统下环境变量的导出方式,不用再在 windows 下写 set;linux 下写 export; 可以统一以 cross-env NODE_ENV=DEV 代替; | ||||
|  | 
 | ||||
|  |     但是这样的话就不能在同一条运行的命令中使用 && 切割,因为会把命令切割为两个环境,则最终拿不到我们设置的变量; | ||||
| @ -0,0 +1,92 @@ | |||||
|  | 'use strict'; | ||||
|  | const request = require('superagent'); | ||||
|  | const parse = require('async-busboy'); | ||||
|  | const path = require('path') | ||||
|  | const fs = require('fs'); | ||||
|  | 
 | ||||
|  | const ext = { | ||||
|  |     project: [".txt", ".dwg", ".doc", ".docx", ".xls", ".xlsx", ".pdf", ".png", ".jpg", ".svg"], | ||||
|  |     report: [".doc", ".docx", ".xls", ".xlsx", ".pdf"], | ||||
|  |     data: [".txt", ".xls", ".xlsx"], | ||||
|  |     image: [".png", ".jpg", ".svg"], | ||||
|  |     three: [".js"], | ||||
|  |     video: [".mp4"], | ||||
|  |     bpmn: [".bpmn", ".bpmn20.xml", ".zip", ".bar"], | ||||
|  |     app: [".apk"] | ||||
|  | } | ||||
|  | 
 | ||||
|  | module.exports = { | ||||
|  |     entry: function (app, router, opts) { | ||||
|  | 
 | ||||
|  |         const getApiRoot = async function (ctx) { | ||||
|  |             const { apiUrl } = opts; | ||||
|  | 
 | ||||
|  |             ctx.status = 200; | ||||
|  |             ctx.body = { root: apiUrl }; | ||||
|  |         }; | ||||
|  | 
 | ||||
|  |         let upload = async function (ctx, next) { | ||||
|  |             try { | ||||
|  |                 const { files } = await parse(ctx.req); | ||||
|  |                 const file = files[0]; | ||||
|  |                 const extname = path.extname(file.filename).toLowerCase(); | ||||
|  |                 const fileType = ctx.query.type || "image"; | ||||
|  |                 const fileFolder = ctx.query.fileFolder || 'common'; | ||||
|  |                 if (ext[fileType].indexOf(extname) < 0) { | ||||
|  |                     ctx.status = 400; | ||||
|  |                     ctx.body = JSON.stringify({ name: 'UploadFailed', message: '文件格式无效' }); | ||||
|  |                     return; | ||||
|  |                 } | ||||
|  |                 const date = new Date().toLocaleDateString(); | ||||
|  |                 const time = new Date().getTime(); | ||||
|  |                 let fileName = time + '_' + file.filename; | ||||
|  |                 let saveFile = path.join(__dirname, '../../', `/client/assets/files/${fileFolder}`, fileName); | ||||
|  |                 const pathUrl = `./client/assets/files/${fileFolder}`; | ||||
|  | 
 | ||||
|  |                 const res1 = fs.existsSync(`./client/assets/files/${fileFolder}`); | ||||
|  |                 !res1 && fs.mkdirSync(`./client/assets/files/${fileFolder}`); | ||||
|  |                 const res = fs.existsSync(pathUrl); | ||||
|  |                 !res && fs.mkdirSync(pathUrl); | ||||
|  |                 let stream = fs.createWriteStream(saveFile); | ||||
|  |                 fs.createReadStream(file.path).pipe(stream); | ||||
|  |                 stream.on('error', function (err) { | ||||
|  |                     app.fs.logger.log('error', '[Upload Heatmap]', err); | ||||
|  |                 }); | ||||
|  |                 ctx.status = 200; | ||||
|  |                 ctx.body = { filename: path.join(`/assets/files/${fileFolder}`, fileName), name: 'UploadSuccess', message: '上传成功' }; | ||||
|  |             } catch (err) { | ||||
|  |                 ctx.status = 500; | ||||
|  |                 ctx.fs.logger.error(err); | ||||
|  |                 ctx.body = { err: 'upload error.' }; | ||||
|  |             } | ||||
|  |         } | ||||
|  | 
 | ||||
|  |         let remove = async function (ctx, next) { | ||||
|  |             try { | ||||
|  |                 const fkeys = ctx.request.body; | ||||
|  |                 let removeUrl = path.join(__dirname, '../../', './client', fkeys.url); | ||||
|  |                 const res = fs.existsSync(removeUrl); | ||||
|  |                 if (!res) { | ||||
|  |                     ctx.status = 400; | ||||
|  |                     ctx.body = JSON.stringify({ name: 'DeleteFailed', message: '文件地址不存在' }); | ||||
|  |                     return; | ||||
|  |                 } | ||||
|  |                 fs.unlink(removeUrl, function (error) { | ||||
|  |                     if (error) { | ||||
|  |                         console.log(error); | ||||
|  |                     } | ||||
|  |                 }) | ||||
|  |                 ctx.status = 200; | ||||
|  |                 ctx.body = { name: 'DeleteSuccess.', message: '删除成功' }; | ||||
|  |             } catch (err) { | ||||
|  |                 ctx.status = 500; | ||||
|  |                 ctx.fs.logger.error(err); | ||||
|  |                 ctx.body = { err: 'upload cleanup error.' }; | ||||
|  |             } | ||||
|  |         } | ||||
|  | 
 | ||||
|  |         router.get('/api/root', getApiRoot); | ||||
|  |         router.post('/_upload/new', upload); | ||||
|  |         router.delete('/_upload/cleanup', remove); | ||||
|  |     } | ||||
|  | }; | ||||
| @ -0,0 +1,20 @@ | |||||
|  | /** | ||||
|  |  * Created by liu.xinyi | ||||
|  |  * on 2016/7/7. | ||||
|  |  */ | ||||
|  | 'use strict'; | ||||
|  | const path = require('path'); | ||||
|  | const fs = require('fs'); | ||||
|  | 
 | ||||
|  | module.exports = { | ||||
|  |     entry: function (app, router, opts) { | ||||
|  |         fs.readdirSync(__dirname).forEach(function (dir) { | ||||
|  |             if(fs.lstatSync(path.join(__dirname, dir)).isDirectory()){ | ||||
|  |                 fs.readdirSync(path.join(__dirname, dir)).forEach(function (api) { | ||||
|  |                     require(`./${dir}/${api}`).entry(app, router, opts); | ||||
|  |                     app.fs.logger.log('info', '[Router]', 'Inject api:', dir + '/' + path.basename(api, '.js')); | ||||
|  |                 }); | ||||
|  |             } | ||||
|  |         }); | ||||
|  |     } | ||||
|  | }; | ||||
| @ -0,0 +1,8 @@ | |||||
|  | 'use strict'; | ||||
|  | /*jslint node:true*/ | ||||
|  | //from koa
 | ||||
|  | 
 | ||||
|  | const scaffold = require('fs-web-server-scaffold'); | ||||
|  | const config = require('./config.js'); | ||||
|  | 
 | ||||
|  | module.exports = scaffold(config); | ||||
| @ -0,0 +1,33 @@ | |||||
|  | import path from 'path'; | ||||
|  | import { defineConfig } from 'vite' | ||||
|  | import react from '@vitejs/plugin-react' | ||||
|  | import reactRefresh from '@vitejs/plugin-react-refresh' | ||||
|  | 
 | ||||
|  | // https://vitejs.dev/config/
 | ||||
|  | export default defineConfig({ | ||||
|  |    root: './client/', | ||||
|  |    plugins: [react({})], | ||||
|  |    // plugins: [reactRefresh({})],
 | ||||
|  |    resolve: { | ||||
|  |       alias: [ | ||||
|  |          { | ||||
|  |             find: '$utils', replacement: path.join('/src/utils'), | ||||
|  |          }, | ||||
|  |          { | ||||
|  |             find: '$components', replacement: path.join('/src/components'), | ||||
|  |          }, | ||||
|  |          // 针对以 ~/[包名称]开头的,替换为 node_modules/@[包名称]
 | ||||
|  |          { | ||||
|  |             find: /^(~)(?!\/)(.+)/, replacement: path.join('node_modules/$2'), | ||||
|  |          }, | ||||
|  |       ], | ||||
|  |    }, | ||||
|  |    cors: true, | ||||
|  |    server: { | ||||
|  |       hmr: { | ||||
|  |          protocol: 'ws', | ||||
|  |          host: 'localhost' | ||||
|  |       }, | ||||
|  |       middlewareMode: 'html', | ||||
|  |    } | ||||
|  | }) | ||||
| @ -0,0 +1,75 @@ | |||||
|  | const path = require('path'); | ||||
|  | const webpack = require('webpack'); | ||||
|  | const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin; | ||||
|  | 
 | ||||
|  | const PATHS = { | ||||
|  |     app: path.join(__dirname, 'client/src'), | ||||
|  |     build: path.join(__dirname, 'client/build') | ||||
|  | }; | ||||
|  | 
 | ||||
|  | module.exports = { | ||||
|  |     mode: "development", | ||||
|  |     devtool: 'source-map', | ||||
|  |     devServer: { | ||||
|  |         historyApiFallback: true, | ||||
|  |         // 为 MicroApp 配置跨域
 | ||||
|  |         'Access-Control-Allow-Origin': '*', | ||||
|  |         allowedHosts: ['127.0.0.1:5200'], | ||||
|  |     }, | ||||
|  |     entry: { | ||||
|  |         app: ["@babel/polyfill", PATHS.app], | ||||
|  |        | ||||
|  |     }, | ||||
|  |     output: { | ||||
|  |         publicPath: '/client/build/', | ||||
|  |         path: PATHS.build, | ||||
|  |         filename: '[name].js' | ||||
|  |     }, | ||||
|  |     resolve: { | ||||
|  |         modules: [path.resolve(__dirname, 'client/src'), path.resolve(__dirname, 'node_modules')], | ||||
|  |         extensions: ['.js', '.jsx'], | ||||
|  |         alias: { | ||||
|  |             crypto: false, | ||||
|  |             $utils: path.resolve(__dirname, 'client/src/utils/'), | ||||
|  |             $components: path.resolve(__dirname, 'client/src/components/'), | ||||
|  |         } | ||||
|  |     }, | ||||
|  |     plugins: [ | ||||
|  |         new webpack.HotModuleReplacementPlugin(), | ||||
|  |         new BundleAnalyzerPlugin({ | ||||
|  |             analyzerPort: 8200 | ||||
|  |         }), | ||||
|  |     ], | ||||
|  |     module: { | ||||
|  |         rules: [{ | ||||
|  |             test: /\.css$/, | ||||
|  |             use: ['style-loader', { | ||||
|  |                 loader: 'css-loader', | ||||
|  |                 options: { | ||||
|  |                     modules: true | ||||
|  |                 } | ||||
|  |             }] | ||||
|  |         }, | ||||
|  |         { | ||||
|  |             test: /\.less$/, | ||||
|  |             use: ['style-loader', 'css-loader', { | ||||
|  |                 loader: 'less-loader', | ||||
|  |                 options: { | ||||
|  |                     lessOptions: { | ||||
|  |                         javascriptEnabled: true | ||||
|  |                     } | ||||
|  |                 } | ||||
|  |             }] | ||||
|  |         }, | ||||
|  |         { | ||||
|  |             test: /\.(js|jsx)$/, | ||||
|  |             use: 'babel-loader', | ||||
|  |             include: [PATHS.app, path.resolve(__dirname, 'node_modules', '@peace')], | ||||
|  |         }, | ||||
|  |         { | ||||
|  |             test: /\.(eot|woff|woff2|svg|ttf)([\?]?.*)$/, | ||||
|  |             loader: "file-loader" | ||||
|  |         } | ||||
|  |         ] | ||||
|  |     } | ||||
|  | }; | ||||
| @ -0,0 +1,76 @@ | |||||
|  | var path = require('path'); | ||||
|  | var webpack = require('webpack'); | ||||
|  | var HtmlWebpackPlugin = require('html-webpack-plugin'); | ||||
|  | 
 | ||||
|  | const PATHS = { | ||||
|  |     app: path.join(__dirname, 'client/src'), | ||||
|  |     build: path.join(__dirname, 'client/build') | ||||
|  | }; | ||||
|  | 
 | ||||
|  | module.exports = { | ||||
|  |     mode: "production", | ||||
|  |     entry: { | ||||
|  |         app: ["babel-polyfill", PATHS.app] | ||||
|  |     }, | ||||
|  |     output: { | ||||
|  |         path: PATHS.build, | ||||
|  |         publicPath: '/build', | ||||
|  |         filename: '[name].[hash:5].js' | ||||
|  |     }, | ||||
|  |     resolve: { | ||||
|  |         modules: [path.resolve(__dirname, 'client/src'), path.resolve(__dirname, 'node_modules')], | ||||
|  |         extensions: ['.js', '.jsx'], | ||||
|  |         alias: { | ||||
|  |             crypto: false, | ||||
|  |             $utils: path.resolve(__dirname, 'client/src/utils/'), | ||||
|  |             $components: path.resolve(__dirname, 'client/src/components/'), | ||||
|  |         } | ||||
|  |     }, | ||||
|  |     plugins: [ | ||||
|  |         new HtmlWebpackPlugin({ | ||||
|  |             filename: '../index.html', | ||||
|  |             template: './client/index.ejs' | ||||
|  |         }) | ||||
|  |     ], | ||||
|  |     optimization: { | ||||
|  |         splitChunks: { | ||||
|  |             cacheGroups: { | ||||
|  |                 commons: { | ||||
|  |                     test: /[\\/]node_modules[\\/]/, | ||||
|  |                     name: "vendors", | ||||
|  |                     chunks: "all" | ||||
|  |                 } | ||||
|  |             } | ||||
|  |         } | ||||
|  |     }, | ||||
|  |     module: { | ||||
|  |         rules: [{ | ||||
|  |             test: /\.css$/, | ||||
|  |             use: ['style-loader', { | ||||
|  |                 loader: 'css-loader', | ||||
|  |                 options: { | ||||
|  |                     modules: true | ||||
|  |                 } | ||||
|  |             }] | ||||
|  |         }, | ||||
|  |         { | ||||
|  |             test: /\.less$/, | ||||
|  |             use: ['style-loader', 'css-loader', | ||||
|  |                 { | ||||
|  |                     loader: 'less-loader', options: { | ||||
|  |                         lessOptions: { | ||||
|  |                             javascriptEnabled: true | ||||
|  |                         } | ||||
|  |                     } | ||||
|  |                 }] | ||||
|  |         }, | ||||
|  |         { | ||||
|  |             test: /\.jsx?$/, | ||||
|  |             use: 'babel-loader', | ||||
|  |             include: [PATHS.app, path.resolve(__dirname, 'node_modules', '@peace')], | ||||
|  |         },{ | ||||
|  |             test: /\.(eot|woff|woff2|svg|ttf)([\?]?.*)$/, | ||||
|  |             loader: "file-loader" | ||||
|  |         }] | ||||
|  |     } | ||||
|  | }; | ||||
| @ -0,0 +1,19 @@ | |||||
|  | pipeline { | ||||
|  |     agent { | ||||
|  |         node{ | ||||
|  |             label 'jnlp-slave' | ||||
|  |         } | ||||
|  |     } | ||||
|  |      | ||||
|  |     stages { | ||||
|  |         stage('Testing iot_auth ......') { | ||||
|  |             steps { | ||||
|  |                 sh 'switch-auth.sh  anxinyun'  | ||||
|  |                 buildName "#${BUILD_NUMBER}  ~/fs-cloud/${JOB_NAME}:${IMAGE_VERSION}" | ||||
|  |                 buildDescription "registry.cn-hangzhou.aliyuncs.com/${CLOUD}/${JOB_NAME}:${IMAGE_VERSION}" | ||||
|  |                 sh 'docker build -t registry.cn-hangzhou.aliyuncs.com/${CLOUD}/${JOB_NAME}:${IMAGE_VERSION} ./code/api'  | ||||
|  |                 sh 'docker push registry.cn-hangzhou.aliyuncs.com/${CLOUD}/${JOB_NAME}:${IMAGE_VERSION}'  | ||||
|  |             } | ||||
|  |         } | ||||
|  |     } | ||||
|  | } | ||||
| @ -0,0 +1,19 @@ | |||||
|  | pipeline { | ||||
|  |     agent { | ||||
|  |         node{ | ||||
|  |             label 'jnlp-slave' | ||||
|  |         } | ||||
|  |     } | ||||
|  |      | ||||
|  |     stages { | ||||
|  |         stage('Testing iot_auth ......') { | ||||
|  |             steps { | ||||
|  |                 sh 'switch-auth.sh  anxinyun'  | ||||
|  |                 buildName "#${BUILD_NUMBER}  ~/fs-cloud/${JOB_NAME}:${IMAGE_VERSION}" | ||||
|  |                 buildDescription "registry.cn-hangzhou.aliyuncs.com/${CLOUD}/${JOB_NAME}:${IMAGE_VERSION}" | ||||
|  |                 sh 'docker build -t registry.cn-hangzhou.aliyuncs.com/${CLOUD}/${JOB_NAME}:${IMAGE_VERSION} ./code/web'  | ||||
|  |                 sh 'docker push registry.cn-hangzhou.aliyuncs.com/${CLOUD}/${JOB_NAME}:${IMAGE_VERSION}'  | ||||
|  |             } | ||||
|  |         } | ||||
|  |     } | ||||
|  | } | ||||