| @ -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}'  | |||
|             } | |||
|         } | |||
|     } | |||
| } | |||