Commit e868d298 authored by jason.xing's avatar jason.xing

init

parents
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
.idea
/.idea
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# production
/build
# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*
/.idea/workspace.xml
This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
## Available Scripts
In the project directory, you can run:
### `npm start`
Runs the app in the development mode.<br>
Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
The page will reload if you make edits.<br>
You will also see any lint errors in the console.
### `npm test`
Launches the test runner in the interactive watch mode.<br>
See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
### `npm run build`
Builds the app for production to the `build` folder.<br>
It correctly bundles React in production mode and optimizes the build for the best performance.
The build is minified and the filenames include the hashes.<br>
Your app is ready to be deployed!
See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
### `npm run eject`
**Note: this is a one-way operation. Once you `eject`, you can’t go back!**
If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
Instead, it will copy all the configuration files and the transitive dependencies (Webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own.
You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it.
## Learn More
You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
To learn React, check out the [React documentation](https://reactjs.org/).
### Code Splitting
This section has moved here: https://facebook.github.io/create-react-app/docs/code-splitting
### Analyzing the Bundle Size
This section has moved here: https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size
### Making a Progressive Web App
This section has moved here: https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app
### Advanced Configuration
This section has moved here: https://facebook.github.io/create-react-app/docs/advanced-configuration
### Deployment
This section has moved here: https://facebook.github.io/create-react-app/docs/deployment
### `npm run build` fails to minify
This section has moved here: https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify
const {override, fixBabelImports, getBabelLoader, addDecoratorsLegacy,disableEsLint} = require('customize-cra');
const fileLoaderMatcher = function (rule) {
return rule.loader && rule.loader.indexOf(`file-loader`) !== -1;
};
function addPlugin(config) {
config.module.rules[2].oneOf.unshift(
{
test: /\.less$/,
use: [
require.resolve('style-loader'),
require.resolve('css-loader'),
{
loader: require.resolve('postcss-loader'),
options: {
// Necessary for external CSS imports to work
// https://github.com/facebookincubator/create-react-app/issues/2677
ident: 'postcss',
plugins: () => [
require('postcss-flexbugs-fixes'),
require('postcss-preset-env')({
autoprefixer: {
flexbox: 'no-2009',
},
stage: 3,
}),
],
},
},
// 此处可对ant design进行主题配置
{
loader: require.resolve('less-loader'),
options: {
javascriptEnabled: true,
// theme vars, also can use theme.js instead of this.
modifyVars: {
"@primary-color": "#719CF0",
"@menu-highlight-color": "#719CF0",
"@menu-item-active-bg": "rgba(13,156,240,.3)",
"@layout-header-background": "#1E4B9E",
"@layout-sider-background": "#fff",
"@btn-padding-sm": "0 4px - 1px"
},
},
},
]
}
);
// file-loader exclude
let l = getBabelLoader(config, fileLoaderMatcher);
let reg = /\.less$/;
if(Array.isArray(l.exclude)) {
l.exclude.push(reg);
}else if(!!l.exclude){
l.exclude = [l.exclude,reg]
}else {
l.exclude = reg;
}
return config;
}
module.exports = override(
addPlugin,
fixBabelImports('import', { // 使用ant design的库
libraryName: 'antd',
style: true, // use less for customized theme
}),
addDecoratorsLegacy(), // 添加对装饰器的支持
disableEsLint(),
);
\ No newline at end of file
{
"name": "jifutong_kaihu_h5",
"version": "0.1.0",
"private": true,
"dependencies": {
"@babel/core": "7.2.2",
"@babel/plugin-transform-react-jsx": "^7.3.0",
"@svgr/webpack": "4.1.0",
"antd": "^3.18.1",
"babel-core": "7.0.0-bridge.0",
"babel-eslint": "9.0.0",
"babel-jest": "23.6.0",
"babel-loader": "8.0.5",
"babel-plugin-import": "^1.11.0",
"babel-plugin-named-asset-import": "^0.3.1",
"babel-preset-react-app": "^7.0.1",
"bfj": "6.1.1",
"bundle-loader": "^0.5.6",
"case-sensitive-paths-webpack-plugin": "2.2.0",
"classnames": "^2.2.6",
"clipboard": "^2.0.4",
"css-loader": "1.0.0",
"cssnano": "^4.1.10",
"customize-cra": "^0.2.12",
"dotenv": "6.0.0",
"dotenv-expand": "4.2.0",
"eslint": "5.12.0",
"eslint-config-react-app": "^3.0.7",
"eslint-loader": "2.1.1",
"eslint-plugin-flowtype": "2.50.1",
"eslint-plugin-import": "2.14.0",
"eslint-plugin-jsx-a11y": "6.1.2",
"eslint-plugin-react": "7.12.4",
"file-loader": "2.0.0",
"fork-ts-checker-webpack-plugin-alt": "0.4.14",
"fs-extra": "7.0.1",
"history": "^4.7.2",
"html-webpack-plugin": "4.0.0-alpha.2",
"i18next": "^15.0.5",
"i18next-xhr-backend": "^2.0.1",
"identity-obj-proxy": "3.0.0",
"jest": "23.6.0",
"jest-pnp-resolver": "1.0.2",
"jest-resolve": "23.6.0",
"jest-watch-typeahead": "^0.2.1",
"js-sha1": "^0.6.0",
"json-loader": "^0.5.7",
"less": "^3.9.0",
"less-loader": "^4.1.0",
"md5": "^2.2.1",
"mini-css-extract-plugin": "0.5.0",
"mobx": "^5.9.4",
"mobx-react": "^5.4.3",
"mobx-react-router": "^4.0.7",
"moment": "^2.24.0",
"node-forge": "0.7.6",
"node-sass": "^4.11.0",
"numeral": "^2.0.6",
"optimize-css-assets-webpack-plugin": "5.0.1",
"pinyin": "^2.8.3",
"pnp-webpack-plugin": "1.2.1",
"postcss-aspect-ratio-mini": "^1.0.1",
"postcss-cssnext": "^3.1.0",
"postcss-flexbugs-fixes": "4.1.0",
"postcss-loader": "3.0.0",
"postcss-preset-env": "6.5.0",
"postcss-px-to-viewport": "^1.1.0",
"postcss-safe-parser": "4.0.1",
"postcss-viewport-units": "^0.1.6",
"postcss-write-svg": "^3.0.1",
"prop-types": "^15.7.2",
"query-string": "^6.3.0",
"react": "^16.8.3",
"react-app-polyfill": "^0.2.1",
"react-app-rewired": "^2.1.0",
"react-clipboard.js": "^2.0.7",
"react-dev-utils": "^7.0.3",
"react-document-title": "^2.0.3",
"react-dom": "^16.8.3",
"react-i18next": "^10.2.0",
"react-loadable": "^5.5.0",
"react-router-dom": "^4.3.1",
"react-scripts": "^2.1.5",
"react-signature-canvas": "^1.0.1",
"react-slick": "^0.23.2",
"react-tagsinput": "^3.19.0",
"recharts": "^1.5.0",
"resolve": "1.10.0",
"sass-loader": "7.1.0",
"slick-carousel": "^1.8.1",
"style-loader": "0.23.1",
"terser-webpack-plugin": "1.2.2",
"url-loader": "1.1.2",
"webpack": "4.28.3",
"webpack-dev-server": "3.1.14",
"webpack-manifest-plugin": "2.0.4",
"whatwg-fetch": "^3.0.0",
"workbox-webpack-plugin": "3.6.3"
},
"scripts": {
"start": "react-app-rewired start",
"build": "react-app-rewired build",
"test": "react-app-rewired test"
},
"eslintConfig": {
"extends": "react-app"
},
"jest": {
"collectCoverageFrom": [
"src/**/*.{js,jsx,ts,tsx}",
"!src/**/*.d.ts"
],
"resolver": "jest-pnp-resolver",
"setupFiles": [
"react-app-polyfill/jsdom"
],
"testMatch": [
"<rootDir>/src/**/__tests__/**/*.{js,jsx,ts,tsx}",
"<rootDir>/src/**/?(*.)(spec|test).{js,jsx,ts,tsx}"
],
"testEnvironment": "jsdom",
"testURL": "http://localhost",
"transform": {
"^.+\\.(js|jsx|ts|tsx)$": "<rootDir>/node_modules/babel-jest",
"^.+\\.css$": "<rootDir>/config/jest/cssTransform.js",
"^(?!.*\\.(js|jsx|ts|tsx|css|json)$)": "<rootDir>/config/jest/fileTransform.js"
},
"transformIgnorePatterns": [
"[/\\\\]node_modules[/\\\\].+\\.(js|jsx|ts|tsx)$",
"^.+\\.module\\.(css|sass|scss)$"
],
"moduleNameMapper": {
"^react-native$": "react-native-web",
"^.+\\.module\\.(css|sass|scss)$": "identity-obj-proxy"
},
"moduleFileExtensions": [
"web.js",
"js",
"web.ts",
"ts",
"web.tsx",
"tsx",
"json",
"web.jsx",
"jsx",
"node"
],
"watchPlugins": [
"/Users/jason/Documents/project/jifutong_kaihu_h5/node_modules/jest-watch-typeahead/filename.js",
"/Users/jason/Documents/project/jifutong_kaihu_h5/node_modules/jest-watch-typeahead/testname.js"
]
},
"babel": {
"presets": [
"react-app"
]
},
"browserslist": [
">0.2%",
"not ie <= 11",
"not op_mini all"
]
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico" />
<meta
name="viewport"
content="width=device-width, initial-scale=1, shrink-to-fit=no, minimum-scale=1.0, maximum-scale=1.0"
/>
<meta name="theme-color" content="#000000" />
<!--
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<script src="//g.alicdn.com/fdilab/lib3rd/viewport-units-buggyfill/0.6.2/??viewport-units-buggyfill.hacks.min.js,viewport-units-buggyfill.min.js"></script>
<script>
window.onload = function () {
window.viewportUnitsBuggyfill.init({ hacks: window.viewportUnitsBuggyfillHacks });
}
</script>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</html>
{
"short_name": "React App",
"name": "Create React App Sample",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
}
],
"start_url": ".",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}
'use strict';
// Do this as the first thing so that any code reading it knows the right env.
process.env.BABEL_ENV = 'production';
process.env.NODE_ENV = 'production';
// Makes the script crash on unhandled rejections instead of silently
// ignoring them. In the future, promise rejections that are not handled will
// terminate the Node.js process with a non-zero exit code.
process.on('unhandledRejection', err => {
throw err;
});
// Ensure environment variables are read.
require('../config/env');
const path = require('path');
const chalk = require('react-dev-utils/chalk');
const fs = require('fs-extra');
const webpack = require('webpack');
const bfj = require('bfj');
const configFactory = require('../config/webpack.config');
const paths = require('../config/paths');
const checkRequiredFiles = require('react-dev-utils/checkRequiredFiles');
const formatWebpackMessages = require('react-dev-utils/formatWebpackMessages');
const printHostingInstructions = require('react-dev-utils/printHostingInstructions');
const FileSizeReporter = require('react-dev-utils/FileSizeReporter');
const printBuildError = require('react-dev-utils/printBuildError');
const measureFileSizesBeforeBuild =
FileSizeReporter.measureFileSizesBeforeBuild;
const printFileSizesAfterBuild = FileSizeReporter.printFileSizesAfterBuild;
const useYarn = fs.existsSync(paths.yarnLockFile);
// These sizes are pretty large. We'll warn for bundles exceeding them.
const WARN_AFTER_BUNDLE_GZIP_SIZE = 512 * 1024;
const WARN_AFTER_CHUNK_GZIP_SIZE = 1024 * 1024;
const isInteractive = process.stdout.isTTY;
// Warn and crash if required files are missing
if (!checkRequiredFiles([paths.appHtml, paths.appIndexJs])) {
process.exit(1);
}
// Process CLI arguments
const argv = process.argv.slice(2);
const writeStatsJson = argv.indexOf('--stats') !== -1;
// Generate configuration
const config = configFactory('production');
// We require that you explicitly set browsers and do not fall back to
// browserslist defaults.
const { checkBrowsers } = require('react-dev-utils/browsersHelper');
checkBrowsers(paths.appPath, isInteractive)
.then(() => {
// First, read the current file sizes in build directory.
// This lets us display how much they changed later.
return measureFileSizesBeforeBuild(paths.appBuild);
})
.then(previousFileSizes => {
// Remove all content but keep the directory so that
// if you're in it, you don't end up in Trash
fs.emptyDirSync(paths.appBuild);
// Merge with the public folder
copyPublicFolder();
// Start the webpack build
return build(previousFileSizes);
})
.then(
({ stats, previousFileSizes, warnings }) => {
if (warnings.length) {
console.log(chalk.yellow('Compiled with warnings.\n'));
console.log(warnings.join('\n\n'));
console.log(
'\nSearch for the ' +
chalk.underline(chalk.yellow('keywords')) +
' to learn more about each warning.'
);
console.log(
'To ignore, add ' +
chalk.cyan('// eslint-disable-next-line') +
' to the line before.\n'
);
} else {
console.log(chalk.green('Compiled successfully.\n'));
}
console.log('File sizes after gzip:\n');
printFileSizesAfterBuild(
stats,
previousFileSizes,
paths.appBuild,
WARN_AFTER_BUNDLE_GZIP_SIZE,
WARN_AFTER_CHUNK_GZIP_SIZE
);
console.log();
const appPackage = require(paths.appPackageJson);
const publicUrl = paths.publicUrl;
const publicPath = config.output.publicPath;
const buildFolder = path.relative(process.cwd(), paths.appBuild);
printHostingInstructions(
appPackage,
publicUrl,
publicPath,
buildFolder,
useYarn
);
},
err => {
console.log(chalk.red('Failed to compile.\n'));
printBuildError(err);
process.exit(1);
}
)
.catch(err => {
if (err && err.message) {
console.log(err.message);
}
process.exit(1);
});
// Create the production build and print the deployment instructions.
function build(previousFileSizes) {
console.log('Creating an optimized production build...');
let compiler = webpack(config);
return new Promise((resolve, reject) => {
compiler.run((err, stats) => {
let messages;
if (err) {
if (!err.message) {
return reject(err);
}
messages = formatWebpackMessages({
errors: [err.message],
warnings: [],
});
} else {
messages = formatWebpackMessages(
stats.toJson({ all: false, warnings: true, errors: true })
);
}
if (messages.errors.length) {
// Only keep the first error. Others are often indicative
// of the same problem, but confuse the reader with noise.
if (messages.errors.length > 1) {
messages.errors.length = 1;
}
return reject(new Error(messages.errors.join('\n\n')));
}
if (
process.env.CI &&
(typeof process.env.CI !== 'string' ||
process.env.CI.toLowerCase() !== 'false') &&
messages.warnings.length
) {
console.log(
chalk.yellow(
'\nTreating warnings as errors because process.env.CI = true.\n' +
'Most CI servers set it automatically.\n'
)
);
return reject(new Error(messages.warnings.join('\n\n')));
}
const resolveArgs = {
stats,
previousFileSizes,
warnings: messages.warnings,
};
if (writeStatsJson) {
return bfj
.write(paths.appBuild + '/bundle-stats.json', stats.toJson())
.then(() => resolve(resolveArgs))
.catch(error => reject(new Error(error)));
}
return resolve(resolveArgs);
});
});
}
function copyPublicFolder() {
fs.copySync(paths.appPublic, paths.appBuild, {
dereference: true,
filter: file => file !== paths.appHtml,
});
}
'use strict';
// Do this as the first thing so that any code reading it knows the right env.
process.env.BABEL_ENV = 'development';
process.env.NODE_ENV = 'development';
// Makes the script crash on unhandled rejections instead of silently
// ignoring them. In the future, promise rejections that are not handled will
// terminate the Node.js process with a non-zero exit code.
process.on('unhandledRejection', err => {
throw err;
});
// Ensure environment variables are read.
require('../config/env');
const fs = require('fs');
const chalk = require('react-dev-utils/chalk');
const webpack = require('webpack');
const WebpackDevServer = require('webpack-dev-server');
const clearConsole = require('react-dev-utils/clearConsole');
const checkRequiredFiles = require('react-dev-utils/checkRequiredFiles');
const {
choosePort,
createCompiler,
prepareProxy,
prepareUrls,
} = require('react-dev-utils/WebpackDevServerUtils');
const openBrowser = require('react-dev-utils/openBrowser');
const paths = require('../config/paths');
const configFactory = require('../config/webpack.config');
const createDevServerConfig = require('../config/webpackDevServer.config');
const useYarn = fs.existsSync(paths.yarnLockFile);
const isInteractive = process.stdout.isTTY;
// Warn and crash if required files are missing
if (!checkRequiredFiles([paths.appHtml, paths.appIndexJs])) {
process.exit(1);
}
// Tools like Cloud9 rely on this.
const DEFAULT_PORT = parseInt(process.env.PORT, 10) || 3000;
const HOST = process.env.HOST || '0.0.0.0';
if (process.env.HOST) {
console.log(
chalk.cyan(
`Attempting to bind to HOST environment variable: ${chalk.yellow(
chalk.bold(process.env.HOST)
)}`
)
);
console.log(
`If this was unintentional, check that you haven't mistakenly set it in your shell.`
);
console.log(
`Learn more here: ${chalk.yellow('http://bit.ly/CRA-advanced-config')}`
);
console.log();
}
// We require that you explictly set browsers and do not fall back to
// browserslist defaults.
const { checkBrowsers } = require('react-dev-utils/browsersHelper');
checkBrowsers(paths.appPath, isInteractive)
.then(() => {
// We attempt to use the default port but if it is busy, we offer the user to
// run on a different port. `choosePort()` Promise resolves to the next free port.
return choosePort(HOST, DEFAULT_PORT);
})
.then(port => {
if (port == null) {
// We have not found a port.
return;
}
const config = configFactory('development');
const protocol = process.env.HTTPS === 'true' ? 'https' : 'http';
const appName = require(paths.appPackageJson).name;
const urls = prepareUrls(protocol, HOST, port);
// Create a webpack compiler that is configured with custom messages.
const compiler = createCompiler(webpack, config, appName, urls, useYarn);
// Load proxy config
const proxySetting = require(paths.appPackageJson).proxy;
const proxyConfig = prepareProxy(proxySetting, paths.appPublic);
// Serve webpack assets generated by the compiler over a web server.
const serverConfig = createDevServerConfig(
proxyConfig,
urls.lanUrlForConfig
);
const devServer = new WebpackDevServer(compiler, serverConfig);
// Launch WebpackDevServer.
devServer.listen(port, HOST, err => {
if (err) {
return console.log(err);
}
if (isInteractive) {
clearConsole();
}
console.log(chalk.cyan('Starting the development server...\n'));
openBrowser(urls.localUrlForBrowser);
});
['SIGINT', 'SIGTERM'].forEach(function(sig) {
process.on(sig, function() {
devServer.close();
process.exit();
});
});
})
.catch(err => {
if (err && err.message) {
console.log(err.message);
}
process.exit(1);
});
'use strict';
// Do this as the first thing so that any code reading it knows the right env.
process.env.BABEL_ENV = 'test';
process.env.NODE_ENV = 'test';
process.env.PUBLIC_URL = '';
// Makes the script crash on unhandled rejections instead of silently
// ignoring them. In the future, promise rejections that are not handled will
// terminate the Node.js process with a non-zero exit code.
process.on('unhandledRejection', err => {
throw err;
});
// Ensure environment variables are read.
require('../config/env');
const jest = require('jest');
const execSync = require('child_process').execSync;
let argv = process.argv.slice(2);
function isInGitRepository() {
try {
execSync('git rev-parse --is-inside-work-tree', { stdio: 'ignore' });
return true;
} catch (e) {
return false;
}
}
function isInMercurialRepository() {
try {
execSync('hg --cwd . root', { stdio: 'ignore' });
return true;
} catch (e) {
return false;
}
}
// Watch unless on CI, in coverage mode, explicitly adding `--no-watch`,
// or explicitly running all tests
if (
!process.env.CI &&
argv.indexOf('--coverage') === -1 &&
argv.indexOf('--no-watch') === -1 &&
argv.indexOf('--watchAll') === -1
) {
// https://github.com/facebook/create-react-app/issues/5210
const hasSourceControl = isInGitRepository() || isInMercurialRepository();
argv.push(hasSourceControl ? '--watch' : '--watchAll');
}
// Jest doesn't have this option so we'll remove it
if (argv.indexOf('--no-watch') !== -1) {
argv = argv.filter(arg => arg !== '--no-watch');
}
jest.run(argv);
import React, {Component} from 'react';
import {Router, Route, Redirect, Switch} from "react-router-dom";
import './App.scss';
import {withTranslation} from "react-i18next";
import { history as browserHistory } from './common/history';
import { syncHistoryWithStore } from 'mobx-react-router';
import { Provider } from 'mobx-react';
import { rootRouter } from './router/rootRouter';
import NotFound from './page/404';
import {routerStore} from "./store/routerStore";
import mobxStore from './store/index';
const history = syncHistoryWithStore(browserHistory, routerStore);
class App extends Component {
state = {};
render() {
return (
<Provider { ...mobxStore }>
<Router history={history}>
<Switch>
<Redirect exact from={'/'} to={'/zh/login'}/>
{/*定义URL上第一个字段来定义语言*/}
<Route path={'/:lan'} children={({ match,...params }) => {
if(!match.params) {
return <Redirect to={'/zh/login'}/>
}
let lan = match.params.lan;
return (
<Switch>
{
rootRouter.map(({path, component, ...otherProps},index) => {
return <Route path={`/${lan+path }`} component={component} {...otherProps} key={index}/>
})
}
<Route path={'*'} component={NotFound}/>
</Switch>
);
}}/>
<Route path={'*'} component={NotFound}/>
</Switch>
</Router>
</Provider>
);
}
componentDidMount() {
// this.setLanguage();
}
setLanguage() {
let { i18n } = this.props;
try {
let lan = window.location.pathname.split('/')[1];
i18n.changeLanguage(lan);
}catch (e) {
}
}
}
export default withTranslation()(App);
#root {
position: absolute;
left: 0;
right: 0;
bottom: 0;
top: 0;
display: flex;
min-width: 960px;
.search-wrap {
display: none !important;
}
// 自定义ant 按钮之间的间距
.ant-btn {
margin: 4px;
& > span {
margin-left: 2px;
font-size: 14px;
}
}
// table 操作按钮的排列方式
.table-operation {
display: flex;
justify-content: center;
}
}
.ant-modal {
.ant-modal-header {
border: none;
}
.ant-modal-footer {
border: none;
}
.ant-modal-body {
padding-top: 0;
}
}
\ No newline at end of file
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
it('renders without crashing', () => {
const div = document.createElement('div');
ReactDOM.render(<App />, div);
ReactDOM.unmountComponentAtNode(div);
});
export const ROUTE_PRE_FIX = 'v1';
export const DEV_SITE_ID = 6604;
export const DOMIN = 'http://test11.thiztech.com';
export const PAGE_SIZE = 20;
export const COLOR = [
'#7170C7',
'#9D70C7',
'#C770C6',
'#C7709A',
'#709AC7',
'#4846B4',
'#363588',
'#C77170',
'#70C6C7',
'#868835',
'#B2B446',
'#C79D70',
'#70C79D',
'#70C771',
'#9AC770',
'#C6C770'
];
import 'whatwg-fetch';
import { MESSAGE_TYPE} from "./message";
import { DOMIN } from "./constant";
import { userStore } from '../store/userStore';
class Resource {
constructor(domain='') {
this.domian = domain;
}
resource({method = 'GET', params, headers, path, queryString = true}) {
let query = '';
if(queryString) {
query = params ? '?' + Object.keys(params).map(k => k + '=' + params[k]).join('&') : '';
}
return new Promise(((resolve, reject) => {
fetch(this.domian+path+query, {
method,
headers: {
'Content-Type': 'application/json',
...userStore.getToken()
},
credentials: 'include',
body:queryString ? null :JSON.stringify({...params})
// body:JSON.stringify({...params})
})
.then(this.responseHandler(resolve,reject))
.then(this.dataHandler(resolve,reject))
.catch((err) => {
this.handleError(err,resolve,reject);
})
}))
}
get = (path,params,headers) => {
return this.resource({ path,params,headers });
};
post = (path,params,headers) => {
return this.resource({ path,params,headers,method:'POST', queryString:false });
};
delete = (path,params,headers) => {
return this.resource({ path,params,headers,method:'DELETE', queryString:false });
};
postForm(url,params) {
return new Promise((resolve, reject) => {
fetch(this.domian+url,{
method:'post',
headers: {
...userStore.getToken()
},
body:params
}).then(this.responseHandler(resolve,reject))
.then(this.dataHandler(resolve,reject))
.catch((err) => {
this.handleError(err,resolve,reject);
});
});
}
// 处理code非0时的异常问题
dataHandler(resolve,reject) {
return (data) => {
if(!data.errCode) {
resolve(data);
}else {
this.handleErrorCode(data,resolve,reject);
}
}
}
// 处理response数据异常问题 例 400 500 404
responseHandler(resolve,reject) {
return (response) => {
if (response.status >= 200 && response.status < 300) {
return response.json();
} else if(response.status === 405) {
userStore.logout();
reject({type:MESSAGE_TYPE.ERROR, msg:'您的登录已失效,请重新登录', status: response.status});
}else {
reject({type:MESSAGE_TYPE.ERROR, msg:`服务器异常,错误码:${ response.status }`, status: response.status});
}
}
}
// 处理因网络出现的异常
handleError(err,resolve,reject) {
reject({type:MESSAGE_TYPE.ERROR,msg:'网络请求失败'});
}
// 后台接口调用成功 抛出提示信息
handleErrorCode({ errCode,errMsg },resolve,reject) {
reject({type:MESSAGE_TYPE.ERROR, msg:`${ errMsg }`, status:200, errCode});
}
}
// export const RESOURCE = new Resource('http://120.27.238.50:8082/DataCenter/api/v1');
export const RESOURCE= new Resource(DOMIN);
\ No newline at end of file
import createHistory from 'history/createBrowserHistory';
export const history = createHistory();
\ No newline at end of file
import { message } from 'antd'
export const MESSAGE_TYPE = {
ERROR:0,
INFO:1,
SUCCESS:2,
WARNING:3
};
export class Message {
static error(msg) {
message.error(msg);
message.config({
maxCount: 1,
});
}
static info(msg) {
message.info(msg);
message.config({
maxCount: 1,
});
}
static success(msg) {
message.success(msg);
message.config({
maxCount: 1,
});
}
static warning(msg) {
message.warning(msg);
message.config({
maxCount: 1,
});
}
static alert(params) {
if(typeof params !== "object" || params.type === undefined || params.msg === undefined) {
console.error('Message.alert参数{ type,msg }');
return;
}
let { type,msg } = params;
switch (type) {
case MESSAGE_TYPE.ERROR: Message.error(msg); break;
case MESSAGE_TYPE.INFO: Message.info(msg); break;
case MESSAGE_TYPE.SUCCESS: Message.success(msg); break;
case MESSAGE_TYPE.WARNING: Message.warning(msg); break;
default:
Message.error(msg);
}
}
}
\ No newline at end of file
import React from 'react';
import { routerStore } from '../store/routerStore';
import {ROUTE_PRE_FIX} from "./constant";
import numeral from 'numeral';
// 数字分割
export function numberSplit(num) {
return numeral(num).format('0,0');
}
// js跳转界面方法
export function navigate(path,params) {
if(!path) {
console.error('请指定pathname');
return;
}
routerStore.push({
pathname:`/zh/${ ROUTE_PRE_FIX }`+path,
state:params
});
}
// js返回登录页
export function navigate_login(params) {
routerStore.push({
pathname:`/zh/login`,
state:params
});
}
export function navigate_replace(path,params) {
if(!path) {
console.error('请指定pathname');
return;
}
routerStore.replace({
pathname:`/zh/${ ROUTE_PRE_FIX }`+path,
state:params
});
}
/**
* 判断当前的form表单是否有异常
* @param fieldsError
* @return {boolean}
*/
export function formHasErrors(fieldsError) {
return Object.keys(fieldsError).some(field => fieldsError[field]);
}
// 将js中的px单位转成vw单位
export function pxToVw(px,unit = 'vw') {
return px/375*100+unit;
}
// 将二进制文件转base 64
export function fileOrBlobToDataURL(file){
return new Promise(resolve => {
let a = new FileReader();
a.readAsDataURL(file);
a.onload = function (e){
resolve(e.target.result);
};
});
}
import React from 'react';
import './JFContent.scss';
export function JFContent({ children, className }) {
return <div className={`jf-content${ className ? ' ' + className : '' }`}>{ children }</div>
}
export function JFContentHeader({ children, className }) {
return <div className={`jf-content-header${ className ? ' ' + className : '' }`}>{ children }</div>
}
\ No newline at end of file
.jf-content {
padding: 0 20px 0 40px;
background: white;
}
.jf-content-header {
margin-top: 10px;
padding: 10px 20px;
background: white;
}
\ No newline at end of file
import React from 'react'
export default class JFErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = {hasError: false};
}
componentDidCatch(error, info) {
// Display fallback UI
this.setState({hasError: true});
// You can also log the error to an error reporting service
}
render() {
if (this.state.hasError) {
// You can render any custom fallback UI
return <h1>Something went wrong.</h1>;
}
return this.props.children;
}
}
import React from 'react';
import {Spin} from 'antd';
import propTypes from 'prop-types';
import './JFLoading.scss';
export function JFLoading({ mask, loading }) {
if(!loading) {
return null;
}
return (
<div className={`jf-loading${ mask ? ' mask' : '' }`}>
<Spin size="large"/>
</div>
);
}
JFLoading.propTypes = {
loading: propTypes.bool,
mask: propTypes.bool
};
\ No newline at end of file
.jf-loading {
position: absolute;
top: 50%;
left: 50%;
z-index: 1001;
transform: translate3d(-50%, -50%, 0);
display: flex;
justify-content: center;
align-items: center;
&.mask {
top:0;
bottom:0;
right:0;
left:0;
transform: translate3d(0,0,0);
}
}
import React, {Fragment} from 'react';
import PropTypes from 'prop-types';
import './JFPageHeader.scss';
export function JFPageHeader({ title, routes }) {
return (
<div className={'jf-page-header'}>
<div className={'left'}>
{ title }
</div>
<div className={'right'}>
{
routes.map(({ name },index) => {
return (
<Fragment key={index}>
<span>{ name }</span>{ index === routes.length-1 || '>' }
</Fragment>
)
})
}
</div>
</div>
);
}
JFPageHeader.propTypes = {
title: PropTypes.string,
routes: PropTypes.array
};
JFPageHeader.defaultProps = {
title:'位置',
routes: []
};
export default JFPageHeader;
.jf-page-header {
display: flex;
height: 54px;
background: white;
align-items: center;
font-size: 16px;
color: black;
padding-left: 20px;
margin-bottom: 10px;
.right {
span {
margin:0 8px;
&:hover {
text-decoration: underline;
cursor: pointer;
}
}
span:nth-child(1) {
margin-left: 0;
}
}
}
\ No newline at end of file
import React from 'react';
import {Pagination} from "antd";
import './JFpagination.scss';
export function JFPagination(props) {
return (
<div className={'ps-pagination'}>
<Pagination {...props}/>
</div>
);
}
export default JFPagination;
.ps-pagination {
display: flex;
justify-content: flex-end;
padding: 10px 0;
}
\ No newline at end of file
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import { zh_Hans } from './zh_Hans';
import { zh_Hant } from './zh_Hant';
i18n
// pass the i18n instance to react-i18next.
.use(initReactI18next)
// init i18next
// for all options read: https://www.i18next.com/overview/configuration-options
.init({
fallbackLng: 'zh_Hans',
lng:'zh_Hans',
ns: ['common'],
defaultNS: 'common',
interpolation: {
escapeValue: false, // not needed for react as it escapes by default
},
resources: {
zh_Hans,
zh_Hant
},
});
export default i18n;
\ No newline at end of file
export let zh_Hans = {
common:{
identityAuthentication:'身份认证',
baseInfo:'基本资料',
riskTest:'风险评测',
protocolStatement:'协议声明'
}
};
\ No newline at end of file
export let zh_Hant = {
common:{
identityAuthentication:'身份認證',
baseInfo:'基本資料',
riskTest:'風險評測',
protocolStatement:'協議聲明'
}
};
\ No newline at end of file
import React from 'react';
import ReactDOM from 'react-dom';
import './index.scss';
import App from './App';
import './i18n/i18n';
import { configure } from 'mobx';
import * as serviceWorker from './serviceWorker';
import moment from 'moment';
import 'moment/locale/zh-cn';
moment.locale('zh-cn');
configure({
enforceActions: 'always'
});
ReactDOM.render(<App />, document.getElementById('root'));
// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: http://bit.ly/CRA-PWA
serviceWorker.unregister();
/**
初始化CSS样式
*/
body, h1, h2, h3, h4, h5, h6, hr, p, blockquote, dl, dt, dd, ul, ol, li, pre, form, fieldset, legend, button, input, textarea, th, td { margin:0; padding:0; }
body, button, input, select, textarea { font:12px/1.5tahoma, arial, \5b8b\4f53; }
h1, h2, h3, h4, h5, h6{ font-size:100%; }
address, cite, dfn, em, var { font-style:normal; }
code, kbd, pre, samp { font-family:couriernew, courier, monospace; }
small{ font-size:12px; }
ul, ol { list-style:none; }
a { text-decoration:none!important; }
a:hover { text-decoration:underline; }
sup { vertical-align:text-top; }
sub{ vertical-align:text-bottom; }
legend { color:#000; }
fieldset, img { border:0; }
button, input, select, textarea { font-size:100%; }
table { border-collapse:collapse; border-spacing:0; }
* {
box-sizing: border-box;
}
img {
content: normal !important;
}
import React, {Component} from 'react';
import {Layout, Menu, Icon, Select} from 'antd';
import {NavRouter} from '../router/navRouter';
import './basicLayout.scss';
import ContentLayout from "./component/contentLayout";
import {ROUTE_PRE_FIX} from "../common/constant";
import { observer, inject } from 'mobx-react';
import JFUser from "./component/JFUser/JFUser";
import LanChange from "./component/LanChange/LanChange"
const {SubMenu} = Menu;
const {Header, Sider} = Layout;
@inject('appStore')
@observer
class BasicLayout extends Component {
rootKeys = NavRouter.map(({ key }) => key);
constructor(props) {
super(props);
this.state = {
openKeys: [],
selectedKeys: [],
};
};
componentWillReceiveProps(nextProps) {
if (nextProps.location.pathname !== this.props.location.pathname) {
this.changeMenuStatus();
}
};
componentDidMount() {
// this.props.setAppState({permission: permission()});
this.changeMenuStatus();
this.props.appStore.init();
};
//根据当前path改变menu状态
changeMenuStatus = () => {
let curPath = this.props.history.location.pathname.substring(this.props.history.location.pathname.indexOf('tsp') - 1);
let curMenu = curPath.split('/').slice(2);
let keys = this.getCurrentSelectedKeys(curMenu) || [];
this.setState({
selectedKeys: keys,
openKeys: keys
});
};
/**
*
* @param keys
* @return [k1,k2]
*/
getCurrentSelectedKeys(keys) {
let recursive = (nav,key) => {
for(let i = 0; i < nav.length; i++) {
if(nav[i].key === key){
return nav[i];
}else {
if( Array.isArray(nav[i].routes) ) {
let r = recursive(nav[i].routes,key);
if(r) {
return r;
}
}
}
}
};
for(let i=keys.length-1; i >= 0; i--) {
let r = recursive(NavRouter,keys[i]);
if(r) {
if(r.hidden) {
continue;
}else {
return keys.slice(0,i+1);
}
}else {
console.error('请检查路由的key值')
}
}
}
/*
* p 该用户拥有的权限
* rp 路由信息显示所需要的权限
* */
isShow(p, rp) {
// 路由信息无权限限制
if (!rp || !rp.length) {
return true;
}
// 权限比对
for (let i = 0; i < rp.length; i++) {
if (p.indexOf(rp[i]) === -1) {
return false;
}
}
return true;
}
onToggle = (openKeys) => {
const latestOpenKey = openKeys.find(key => this.state.openKeys.indexOf(key) === -1);
if (this.rootKeys.indexOf(latestOpenKey) === -1) {
this.setState({ openKeys });
} else {
this.setState({
openKeys: latestOpenKey ? [latestOpenKey] : [],
});
}
};
gotoUrl = item => {
const {history, location, lan} = this.props;
if (location.pathname === item.path) {
return;
} else {
history.push(`/${lan}/${ ROUTE_PRE_FIX }`+ item.path);
}
};
menuMap = (RouterTree, callback) => {
let {permission} = this.props;
let isArrFun = value => value && Array.isArray(value);
let routesIsArr = value =>
value && value.routes && Array.isArray(value.routes) && value.routes.length > 0;
let repeatRouter = (Arr) => {
if (isArrFun(Arr)) {
return Arr.map(ArrItem => {
if (routesIsArr(ArrItem)) {
return (
!ArrItem.hidden && this.isShow(permission, ArrItem.permission) && <SubMenu
key={ArrItem.key}
title={
<div>
{ArrItem.icon ? <Icon type={ArrItem.icon} theme="outlined"/> : null}
<span>{ArrItem.name}</span>
</div>
}
>
{repeatRouter(ArrItem.routes)}
</SubMenu>
);
}
return (
!ArrItem.hidden && this.isShow(permission, ArrItem.permission) &&
<Menu.Item key={ArrItem.key} onClick={() => callback(ArrItem)}>
{ArrItem.icon ? <Icon type={ArrItem.icon}/> : null}
<span>{ArrItem.name}</span>
</Menu.Item>
);
});
}
};
return repeatRouter(RouterTree);
};
render() {
let {lan, appStore} = this.props;
let {selectedKeys} = this.state;
return (
<Layout id={'basicLayout'}>
<Header className={'layout-header'}>
<div className={'left'}>
<div className={'logo'}><img alt='' src={require('../assets/images/logo.png')}/></div>
<div className={'headerText'}>
<span className={'taskCenter'}>管理控制台</span>
</div>
</div>
<div className={'right'}>
<Select style={{ width: 120 }} value={ appStore.selectedSite.siteId } onChange={(value) => { appStore.setSelectedSite(appStore.siteList.filter(({ siteId }) => value === siteId)[0])}} loading={appStore.layoutLoading}>
{
appStore.siteList.map(({ siteAddress, siteName, siteId },index) => {
return <Select.Option key={index} value={siteId}><span >{ siteName }</span></Select.Option>
})
}
</Select>
<span style={{ width:20 }}/>
<JFUser/>
{/* <LanChange/> */}
</div>
</Header>
{
appStore.layoutLoading ?
<div>loading</div>
:
(
appStore.siteList.length !== 0 && (
<Layout>
<Sider width={200}
collapsed={this.state.collapsed}
>
<Menu
mode="inline"
selectedKeys={this.state.selectedKeys}
openKeys={this.state.openKeys}
style={{height: '100%', borderRight: 0}}
onOpenChange={this.onToggle}
// inlineCollapsed={this.state.collapsed}
>
{
this.menuMap(NavRouter, this.gotoUrl)
}
</Menu>
{/*<JFSliderBtn open={this.state.collapsed} onToggle={() => this.setState({ collapsed:!this.state.collapsed })}/>*/}
</Sider>
<ContentLayout selectedKeys={selectedKeys} lan={lan}/>
</Layout>
)
)
}
</Layout>
);
}
}
export default function WrapperBasicLayout(props) {
return (
<BasicLayout permission={''} lan={'zh'} {...props}/>
);
}
#basicLayout {
.ant-layout {
.ant-layout-sider{
.ant-layout-sider-children {
//.ant-menu {
// overflow-y: auto;
// overflow-x: hidden;
//}
}
}
}
.nav-text {
a {
color: white !important;
}
}
.layout-header {
display: flex;
align-items: center;
height:50px;
line-height: 50px;
padding-left: 30px;
padding-right: 0px;
.left {
flex: 1;
display: flex;
align-items: center;
.logofont {
font-family: Arial, sans-serif;
line-height: 1em;
color: #faf5f5;
font-weight: bold;
font-size: 25px;
text-shadow: 0px 0px 0 rgb(197, 192, 192), 1px 1px 0 rgb(144, 139, 139), 2px 2px 1px rgba(0, 0, 0, 0.25), 2px 2px 1px rgba(0, 0, 0, 0.5), 0px 0px 1px rgba(0, 0, 0, .2);
}
.logo {
width: 25px;
img {
width: 100%;
}
}
}
.right {
flex: 1;
display: flex;
align-items: center;
justify-content: flex-end;
position: relative;
.headerName {
color: #ffffff;
cursor: pointer;
}
.loginout {
width: 100%;
text-align: right;
}
}
.headerText {
font-size: 16px;
height: 30px;
line-height: 30px;
color: #ffffff;
margin-left: 20px;
a {
text-decoration: none;
color: #ffffff;
}
a:hover {
color: #ffffff;
}
span {
width: 100px;
display: inline-block;
padding-left: 20px;
cursor: pointer;
font-size: 14px;
}
.helpCenter {
}
}
}
.ant-layout-content {
position: relative;
background: unset !important;
}
.layout-content {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
}
}
\ No newline at end of file
import React, {Component, Fragment} from 'react';
import {withRouter} from "react-router-dom";
import './JFUser.scss';
import {observer, inject} from 'mobx-react';
import ChangePasswordModel from '../../component/changePasswordModel/changePasswordModel';
@inject('userStore')
@observer
class JFUser extends Component {
constructor(props) {
super(props);
this.logout = this.logout.bind(this);
this.resetPassword = this.resetPassword.bind(this);
this.state = {
changePasswordModelVisible:false,
showMenu: false
}
};
handleOk = () => {
this.setState({
changePasswordModelVisible: false,
});
};
handleCancel = () => {
this.setState({
changePasswordModelVisible: false,
});
};
logout = () => {
this.props.userStore.logout();
};
resetPassword = () => {
this.setState({
changePasswordModelVisible: true
});
};
showMenu = () => {
this.setState({
showMenu: true
});
};
hideMenu = () => {
this.setState({
showMenu: false
});
};
render() {
let {showMenu} = this.state;
let { userStore } = this.props;
return (
<Fragment>
<ChangePasswordModel visible={this.state.changePasswordModelVisible} history={this.props.history} onOk={this.handleOk} onCancel={this.handleCancel}/>
<div onMouseEnter={this.showMenu} onMouseLeave={this.hideMenu} className={'JFUser'}>
<div className={showMenu ? 'userPhoto changeBg' : 'userPhoto'}>
<img src={require("../../../assets/images/rt.png")} style={{width: '30px', height: '30px'}}/>
</div>
<div className={showMenu ? 'dropdownMemu showMenu' : 'dropdownMemu'}>
<div className={'contentWrapper'}>
<div className={'userInfo'}>
<div className={'photo'}>
<img src={require("../../../assets/images/rt.png")} style={{width: '30px', height: '30px'}}/>
</div>
<div className={'mail'}>{ userStore.user.email }</div>
</div>
<div className={'link'}>
<div>
<span className={'password'} onClick={this.resetPassword}>修改密码</span>
<span className={'logout'} onClick={this.logout}>退出</span>
</div>
</div>
</div>
</div>
</div>
</Fragment>
);
}
}
export default withRouter(JFUser);
.JFUser{
.userPhoto{
width: 50px;
display: flex;
height: 50px;
flex-direction: row;
justify-content: center;
align-items: center;
cursor: pointer;
}
.dropdownMemu {
position: absolute;
top: 100%;
right: 0;
visibility: hidden;
float: left;
list-style: none;
background-color: white;
border: 1px #ddd solid;
color: #666;
background-clip: padding-box;
z-index: 1;
font-size: 12px;
width:200px;
height: 100px;
margin: 0;
transition: opacity .15s,visibility 0s .15s;
opacity: 0;
white-space: nowrap;
.userInfo{
display: flex;
flex-direction: row;
.photo{
width: 50px;
height: 50px;
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
}
.mail{
margin-left: 10px;
}
}
.link{
display: flex;
justify-content: flex-end;
flex-direction: row;
align-items: center;
padding-right: 20px;
div{
span{
display: inline-block;
height: 20px;
line-height: 20px;
cursor: pointer;
}
.password{
border-right: 1px solid #fff;
padding-right: 10px;
}
.password:hover{
color:#00C1DE;
}
.logout{
padding-left: 10px;
}
.logout:hover{
color:#00C1DE;
}
}
}
}
.showMenu {
visibility: visible;
opacity: 1;
}
.changeBg{
background-color: rgba(255,255,255,.2);
}
}
\ No newline at end of file
import React, {Component, Fragment} from 'react';
import {withRouter} from "react-router-dom";
import './LanChange.scss';
import {observer, inject} from 'mobx-react';
@inject('userStore')
@observer
class LanChange extends Component {
constructor(props) {
super(props);
this.logout = this.logout.bind(this);
this.resetPassword = this.resetPassword.bind(this);
this.state = {
changePasswordModelVisible:false,
showMenu: false
}
};
render() {
let {showMenu} = this.state;
let { userStore } = this.props;
return (
<div id="lan-switch">
<span></span>/<span>EN</span>
</div>
);
}
}
export default withRouter(LanChange);
#lan-switch {
color: #fff;
padding: 0 10px;
}
\ No newline at end of file
import React, {PureComponent, Component} from 'react';
import {Input, Modal, Button, Select, Form} from "antd";
import {RESOURCE} from "../../../common/fetch";
import './changePasswordModel.scss';
import {Message} from "../../../common/message";
import { inject, observer } from 'mobx-react';
let md5 = require('md5');
let sha1 = require('js-sha1');
const FormItem = Form.Item;
const {TextArea} = Input;
const Option = Select.Option;
const formItemLayout = {
labelCol: {span: 5},
wrapperCol: {span: 16}
};
@inject('userStore')
@observer
class ChangePasswordModel extends Component {
constructor(props) {
super(props);
this.state = {
loading: false,
confirmDirty: false,
showTip: true,
visible: false
}
}
static getDerivedStateFromProps(nextProps) {
return {
visible: nextProps.visible
}
}
handleSubmit = (form) => {
this.setState({loading: true});
form.validateFieldsAndScroll((err, {newPassword, oldPassword}) => {
if (!err) {
RESOURCE.post('/api/v1/credential/ResetPasswordByOldPassword', {newPassword:sha1(md5(newPassword)), oldPassword:sha1(md5(oldPassword))}).then(() => {
// history.replace('/login');
// Message.success('用户密码修改成功,请重新登录');
this.closeModel();
// clearStorageOfUser();
this.setState({loading: false});
}).catch((e) => {
Message.alert(e);
});
}
});
};
handleConfirmBlur = (e) => {
const value = e.target.value;
this.setState({confirmDirty: this.state.confirmDirty || !!value});
};
compareToFirstPassword = (rule, value, callback) => {
const form = this.props.form;
if (value && value !== form.getFieldValue('newPassword')) {
callback('两次输入的密码不一致!');
} else {
callback();
}
};
validateToNextPassword = (rule, value, callback) => {
const form = this.props.form;
if (value && this.state.confirmDirty) {
form.validateFields(['confirm'], {force: true});
}
callback();
};
isRightPwd = (rule, value, callback) => {
let myreg = /^(?![0-9]+$)(?![a-zA-Z]+$)[0-9A-Za-z]{6,16}$/;
if (myreg.test(value)) {
callback();
} else {
callback('密码长度为6到16个的英文字母、数字组合!');
}
};
closeModel = () => {
this.props.onCancel();
};
render() {
let { loading } = this.state;
let { userStore } = this.props;
let {form}=this.props;
const {getFieldDecorator} = this.props.form;
return (
<Modal
title="修改密码"
destroyOnClose={false}
visible={this.state.visible}
onOk={this.props.onOk}
onCancel={this.props.onCancel}
footer={null}
centered
>
<div id={'privilegeForm'}>
<div className={'container'}>
<Form onSubmit={(e) => {
e.preventDefault();
return this.handleSubmit(form)
}}>
<FormItem label="账号" {...formItemLayout}>
<span>{userStore.user.email}</span>
</FormItem>
<FormItem label="原密码" {...formItemLayout}>
{getFieldDecorator('oldPassword', {
rules: [{
required: true, message: '请输入原始密码!',
}],
})(
<Input type="password"/>
)}
</FormItem>
<FormItem label="新密码" {...formItemLayout}>
{getFieldDecorator('newPassword', {
rules: [{
validator: this.isRightPwd,
}, {
required: true, message: '请输入新密码!',
}, {
validator: this.validateToNextPassword,
}],
})(
<Input type="password"/>
)}
</FormItem>
<FormItem label="确认新密码" {...formItemLayout}>
{getFieldDecorator('confirm', {
rules: [{
validator: this.isRightPwd,
}, {
required: true, message: '请确认密码!',
}, {
validator: this.compareToFirstPassword,
}],
})(
<Input type="password" onBlur={this.handleConfirmBlur}/>
)}
</FormItem>
<div className={'btnStyle'}>
<Button type="primary" htmlType="submit" className={'sure'}>确认</Button></div>
</Form>
</div>
</div>
</Modal>
)
}
}
export default Form.create()(ChangePasswordModel);
\ No newline at end of file
#privilegeForm{
.container{
.btnStyle{
display: flex;
justify-content: flex-end;
.sure{
background-color: #0099cc;
color:#ffffff;
}
}
}
}
\ No newline at end of file
import React, {PureComponent} from 'react';
import {Layout} from 'antd';
import JFErrorBoundary from "../../component/JFErrorBoundary/JFErrorBoundary";
import {Route, Switch} from 'react-router-dom';
import {NavRouter} from "../../router/navRouter";
import {ROUTE_PRE_FIX} from "../../common/constant";
import NotFound from "../../page/404";
const {Content} = Layout;
class ContentLayout extends PureComponent {
constructor(props) {
super(props);
this.state = {
curRoute: this.props.selectedKeys
};
}
/*
* p 该用户拥有的权限
* rp 路由信息显示所需要的权限
* */
isShow(p, rp) {
// 路由信息无权限限制
if (!rp || !rp.length) {
return true;
}
// 权限比对
for (let i = 0; i < rp.length; i++) {
if (p.indexOf(rp[i]) === -1) {
return false;
}
}
return true;
}
navMap = () => {
let {permission, lan} = this.props;
let arr = [];
let repeatRouter = (nav) => {
for (let index = 0; index < nav.length; index++) {
if (nav[index]['routes']) {
this.isShow(permission, nav[index].permission) && repeatRouter(nav[index].routes);
} else {
this.isShow(permission, nav[index].permission) && arr.push(<Route {...nav[index]}
key={nav[index].path}
path={`/${lan}/${ ROUTE_PRE_FIX }` + nav[index].path}/>);
}
}
};
repeatRouter(NavRouter);
return arr;
};
render() {
return (<Layout style={{paddingLeft: '24px'}}>
<Content style={{background: '#fff', padding: 24, margin: 0, minHeight: 280}}>
<div className={"layout-content"}>
<JFErrorBoundary>
<Switch>
{
this.navMap()
}
<Route path={'*'} component={NotFound}/>
</Switch>
</JFErrorBoundary>
</div>
</Content>
</Layout>)
}
}
export default ContentLayout;
\ No newline at end of file
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 841.9 595.3">
<g fill="#61DAFB">
<path d="M666.3 296.5c0-32.5-40.7-63.3-103.1-82.4 14.4-63.6 8-114.2-20.2-130.4-6.5-3.8-14.1-5.6-22.4-5.6v22.3c4.6 0 8.3.9 11.4 2.6 13.6 7.8 19.5 37.5 14.9 75.7-1.1 9.4-2.9 19.3-5.1 29.4-19.6-4.8-41-8.5-63.5-10.9-13.5-18.5-27.5-35.3-41.6-50 32.6-30.3 63.2-46.9 84-46.9V78c-27.5 0-63.5 19.6-99.9 53.6-36.4-33.8-72.4-53.2-99.9-53.2v22.3c20.7 0 51.4 16.5 84 46.6-14 14.7-28 31.4-41.3 49.9-22.6 2.4-44 6.1-63.6 11-2.3-10-4-19.7-5.2-29-4.7-38.2 1.1-67.9 14.6-75.8 3-1.8 6.9-2.6 11.5-2.6V78.5c-8.4 0-16 1.8-22.6 5.6-28.1 16.2-34.4 66.7-19.9 130.1-62.2 19.2-102.7 49.9-102.7 82.3 0 32.5 40.7 63.3 103.1 82.4-14.4 63.6-8 114.2 20.2 130.4 6.5 3.8 14.1 5.6 22.5 5.6 27.5 0 63.5-19.6 99.9-53.6 36.4 33.8 72.4 53.2 99.9 53.2 8.4 0 16-1.8 22.6-5.6 28.1-16.2 34.4-66.7 19.9-130.1 62-19.1 102.5-49.9 102.5-82.3zm-130.2-66.7c-3.7 12.9-8.3 26.2-13.5 39.5-4.1-8-8.4-16-13.1-24-4.6-8-9.5-15.8-14.4-23.4 14.2 2.1 27.9 4.7 41 7.9zm-45.8 106.5c-7.8 13.5-15.8 26.3-24.1 38.2-14.9 1.3-30 2-45.2 2-15.1 0-30.2-.7-45-1.9-8.3-11.9-16.4-24.6-24.2-38-7.6-13.1-14.5-26.4-20.8-39.8 6.2-13.4 13.2-26.8 20.7-39.9 7.8-13.5 15.8-26.3 24.1-38.2 14.9-1.3 30-2 45.2-2 15.1 0 30.2.7 45 1.9 8.3 11.9 16.4 24.6 24.2 38 7.6 13.1 14.5 26.4 20.8 39.8-6.3 13.4-13.2 26.8-20.7 39.9zm32.3-13c5.4 13.4 10 26.8 13.8 39.8-13.1 3.2-26.9 5.9-41.2 8 4.9-7.7 9.8-15.6 14.4-23.7 4.6-8 8.9-16.1 13-24.1zM421.2 430c-9.3-9.6-18.6-20.3-27.8-32 9 .4 18.2.7 27.5.7 9.4 0 18.7-.2 27.8-.7-9 11.7-18.3 22.4-27.5 32zm-74.4-58.9c-14.2-2.1-27.9-4.7-41-7.9 3.7-12.9 8.3-26.2 13.5-39.5 4.1 8 8.4 16 13.1 24 4.7 8 9.5 15.8 14.4 23.4zM420.7 163c9.3 9.6 18.6 20.3 27.8 32-9-.4-18.2-.7-27.5-.7-9.4 0-18.7.2-27.8.7 9-11.7 18.3-22.4 27.5-32zm-74 58.9c-4.9 7.7-9.8 15.6-14.4 23.7-4.6 8-8.9 16-13 24-5.4-13.4-10-26.8-13.8-39.8 13.1-3.1 26.9-5.8 41.2-7.9zm-90.5 125.2c-35.4-15.1-58.3-34.9-58.3-50.6 0-15.7 22.9-35.6 58.3-50.6 8.6-3.7 18-7 27.7-10.1 5.7 19.6 13.2 40 22.5 60.9-9.2 20.8-16.6 41.1-22.2 60.6-9.9-3.1-19.3-6.5-28-10.2zM310 490c-13.6-7.8-19.5-37.5-14.9-75.7 1.1-9.4 2.9-19.3 5.1-29.4 19.6 4.8 41 8.5 63.5 10.9 13.5 18.5 27.5 35.3 41.6 50-32.6 30.3-63.2 46.9-84 46.9-4.5-.1-8.3-1-11.3-2.7zm237.2-76.2c4.7 38.2-1.1 67.9-14.6 75.8-3 1.8-6.9 2.6-11.5 2.6-20.7 0-51.4-16.5-84-46.6 14-14.7 28-31.4 41.3-49.9 22.6-2.4 44-6.1 63.6-11 2.3 10.1 4.1 19.8 5.2 29.1zm38.5-66.7c-8.6 3.7-18 7-27.7 10.1-5.7-19.6-13.2-40-22.5-60.9 9.2-20.8 16.6-41.1 22.2-60.6 9.9 3.1 19.3 6.5 28.1 10.2 35.4 15.1 58.3 34.9 58.3 50.6-.1 15.7-23 35.6-58.4 50.6zM320.8 78.4z"/>
<circle cx="420.9" cy="296.5" r="45.7"/>
<path d="M520.5 78.1z"/>
</g>
</svg>
import React from 'react';
export default () => {
return <div>404</div>;
}
import React, {Component} from 'react';
import {Button, Form, Icon, Input} from "antd";
import { observer, inject } from 'mobx-react';
import './login.scss';
@inject('userStore')
@observer
class Login extends Component {
render() {
let { userStore } = this.props;
return (
<div className={'ps-login'}>
<div className={'ps-login-content'}>
<WrappedLoginForm onSubmit={userStore.login} loading={userStore.loading}/>
</div>
</div>
);
}
}
class LoginForm extends React.Component {
handleSubmit = e => {
e.preventDefault();
this.props.form.validateFields((err, values) => {
if (!err) {
this.props.onSubmit(values);
}
});
};
render() {
const { getFieldDecorator } = this.props.form;
return (
<Form onSubmit={this.handleSubmit} className="login-form">
<Form.Item>
{getFieldDecorator('email', {
rules: [{ required: true, message: '请输入用户名!' }],
initialValue:'1021152950@qq.com'
})(
<Input
prefix={<Icon type="user" style={{ color: 'rgba(0,0,0,.25)' }} />}
placeholder="请输入用户名"
/>,
)}
</Form.Item>
<Form.Item>
{getFieldDecorator('password', {
rules: [{ required: true, message: '请输入密码!' }],
initialValue:'password123'
})(
<Input
prefix={<Icon type="lock" style={{ color: 'rgba(0,0,0,.25)' }} />}
type="password"
placeholder="请输入密码"
/>,
)}
</Form.Item>
<Form.Item>
<Button type="primary" htmlType="submit" className="login-form-button" loading={this.props.loading}>
登录
</Button>
</Form.Item>
</Form>
);
}
}
const WrappedLoginForm = Form.create({ name: 'login' })(LoginForm);
export default Login;
.ps-login {
flex: 1;
display: flex;
justify-content: center;
align-items: center;
.ps-login-content {
max-width: 400px;
min-width: 320px;
.login-form-button {
width:100%;
}
}
}
\ No newline at end of file
import React, {Component, Fragment} from 'react';
import './overall.scss';
import {JFLoading} from "../../component/JFLoading/JFLoading";
import {observer,inject} from "mobx-react/index";
import {JFPageHeader} from "../../component/JFPageHeader/JFPageHeader";
import {JFContent, JFContentHeader} from "../../component/JFContent/JFContent";
import {Icon, Switch} from 'antd';
import Clipboard from 'react-clipboard.js';
import {CartesianGrid, Line, LineChart, ResponsiveContainer, Tooltip, XAxis, YAxis} from "recharts";
import {Message} from "../../common/message";
import {numberSplit} from "../../common/utils";
@inject('overallStore', 'appStore')
@observer
class Overall extends Component {
componentDidMount() {
this.props.overallStore.init();
}
render() {
let obj = {};
let { overallStore, appStore } = this.props;
let pvData = overallStore.pvInfo
.filter(({ day,flows }) => { return flows.length !== 0 })
.map(({ day,flows }) => { return { day,...(function () {
obj = {};
for (let i =0; i<flows.length; i++) {
obj['f'+i] = flows[i].count
}
return obj;
}())}});
console.log(appStore.siteDetail.autoBlacklist)
return (
<Fragment>
<JFLoading loading={overallStore.loading} mask={true} />
<div className={'ps-overall'}>
<JFPageHeader routes={[{ name:'总览' }]}/>
<JFContent className={'ps-overall-content'}>
{
appStore.isFirstLogin ? (
<div className={'ps-content-left'}>
<JFContentHeader>
配置方法
</JFContentHeader>
<div>
<header>
流光机概要
</header>
<p>
流光机配置在客户的WAF和负载均衡的后面,客户网站和APP后端服务器的前面。客户
需要把需要保护的页面通过负载均衡导流到流光机,流光机对进来的流量进行清洗,然后
再发送给客户网站和APP后端服务器。
</p>
<h2>
部署前准备环节
</h2>
<p>
1.为我们准备一台单独的服务器,用于部署我们的流光机,流光机的数量以与我们的运维人员沟通为准。服务器的硬件要求:
</p>
<div className={'block'}>
CPU:4<br/>
内存:8G<br/>
硬盘:5006
</div>
<p>
2.在这台服务器上安装一些必要的软件
</p>
<div className={'block'}>
docker- compose version1.23.1或以上<br/>
Docker version18.09.3或以上<br/>
centos7.5或以上
</div>
<p>
3.确认这台服务器的IP.假如这台服务器的IP<span className={'ip-address'}>192.168.0.1</span>
</p>
<p>
4.因为我们流光机需要和外界通信,修改服务器的防火墙设置,打开如下的IP和端口
</p>
<div className={'block'}>
传日志,IP:47.110.92.152Port5044<br/>
下载新的威胁情报和配置,IP:47.11184.124Port:9000
</div>
<h2>
实际部署环节
</h2>
<p>
5.在之前准备好的服务器中(10.20.30.40)下载我们为你们准备的3个配置文件,并
运行我们的 docker image。在运行前请确保80端口没有被占用,因为我们的服务
将默认运行在80端口。
</p>
<div className={'block'}>
mkdir-p/apps/software/<br/>
cd/apps/software/<br/>
curl-0 104.248.208.183: 8000/woof/docker-compose yml<br/>
curl-O104.248.208.183:8000/woof/.eny<br/>
curi-o 104.248.208.183: 8000/woof/rp-cuprops<br/>
docker login --username docker deploy user@yuntiandun registry cn<br/>
hangzhou. aliyuncs com--password fdsp3222<br/>
#如果之前运行过则执行以下命令,否则的话就跳过,直接执行下一行<br/>
docker-compose-f docker-compose yml down<br/>
docker-compose-f docker-compose yml up-d
</div>
<p>
6.修改负载均衡服务器的 Nginx配置(如果没有负载均衡服务器,则修改DNS
置,将所有流量导向我们的流光机),把我们之前沟通过的,需要监控的流量导向
我们的流光机。将以下内容复制进你们 nginx.conf中。(以下配置仅做参考)
</p>
</div>
</div>
) : (
<div className={'ps-content-left second-login'}>
<div className={'overall-data-top'}>
<div className={'data-block'}>
<header>保护中API</header>
<span>
{ numberSplit(overallStore.protectedApi.count) }
</span>
</div>
<div className={'data-block'}>
<header>
Top 5 API
</header>
<ul>
{
overallStore.topFiveApi.map(({ count, url },index) => {
return (
<li key={index}>
<span>
{ url }
</span>
<span>
{ numberSplit(count) }
</span>
</li>
);
})
}
</ul>
</div>
</div>
<div className={'overall-data-center'}>
<div className={'data-block'}>
<header>
攻击流量
</header>
<div className={'data-block-content'}>
{ numberSplit(overallStore.attackFlow.count) }
</div>
<footer>
<span></span><span>{ overallStore.attackFlow.gains.indexOf('-') === -1 ? <Icon type="arrow-up" style={{ color: 'red' }}/> : <Icon type="arrow-down" style={{ color: 'green' }}/> }</span><span>{ overallStore.attackFlow.gains }</span>
</footer>
</div>
<div className={'data-block'}>
<header>
总流量
</header>
<div className={'data-block-content'}>
{ numberSplit(overallStore.totalFlow.count) }
</div>
<footer>
<span></span><span>{ overallStore.totalFlow.gains.indexOf('-') === -1 ? <Icon type="arrow-up" style={{ color: 'red' }}/> : <Icon type="arrow-down" style={{ color: 'green' }}/> }</span><span>{ overallStore.totalFlow.gains }</span>
</footer>
</div>
<div className={'data-block'}>
<header>
独立访问IP
</header>
<div className={'data-block-content'}>
{ numberSplit(overallStore.independentIp.count) }
</div>
</div>
</div>
<div className={'overall-data-bottom'}>
{
pvData.length > 0 && (
<ResponsiveContainer width={5.9/6*100+'%'} height={280} isAnimationActive={false}>
<LineChart
isAnimationActive={false}
data={pvData}
dataKey={''}
margin={{ top: 10, right: 10, left: 10, bottom: 10 }}
>
<CartesianGrid strokeDasharray="3 3" />
<XAxis dataKey="day" />
<YAxis />
<Tooltip formatter={(value) => [numberSplit(value)]}/>
{/*{*/}
{/*function () {*/}
{/*let arr = [];*/}
{/*for (let i in obj){*/}
{/*arr.push(<Line type="monotone" dataKey={ i } stroke="#8884d8" fill="#8884d8" />)*/}
{/*}*/}
{/*}()*/}
{/*}*/}
<Line type="monotone" dataKey="f0" stroke="#8884d8" fill="#8884d8" />
</LineChart>
</ResponsiveContainer>
)
}
</div>
</div>
)
}
<div className={'ps-content-right'}>
<div className={'ps-right-header'}>
站点信息
</div>
<div className={'ps-site-list'}>
<div className={'list-item'}>
站点名称:{ appStore.siteDetail.siteName }
</div>
<div className={'list-item'}>
站点ID{ appStore.siteDetail.siteId }
</div>
<div className={'list-item'}>
<div>
API KEY{ appStore.siteDetail.apiKey }
</div>
<Clipboard className={'copy-btn'} component={'span'} data-clipboard-text={appStore.siteDetail.apiKey} onSuccess={() => Message.success('复制完成')}>
<Icon type="copy" />
</Clipboard>
</div>
<div className={'list-item'}>
<div>
API SECRET{ appStore.siteDetail.secretKey }
</div>
<Clipboard className={'copy-btn'} component={'span'} data-clipboard-text={appStore.siteDetail.secretKey} onSuccess={() => Message.success('复制完成')}>
<Icon type="copy" />
</Clipboard>
</div>
</div>
<div className={'ps-right-header'}>
快速操作
</div>
<div className={'quick-sp'}>
<div className={'quick-sp-content'}>
<div>
拦截模式
</div>
<div>
<Switch checked={ appStore.siteDetail.autoBlacklist } onChange={(bool) => {overallStore.toggleBlockMode(bool ? 'yes': 'no')}} />
</div>
</div>
<p>
说明:开启拦截模式后,我们将使用大数据与人工智能模型自动识别攻击,并进行相应的防范。
</p>
</div>
<div className={'ps-right-header'}>
支持与服务
</div>
<div className={'help-list-content'}>
<div className={'help-list-item'}>
帮助中心
</div>
<div className={'help-list-item'}>
开发文档
</div>
<div className={'help-list-item'}>
联系我们
</div>
</div>
</div>
</JFContent>
</div>
</Fragment>
);
}
}
export default Overall;
.ps-overall {
.ps-overall-content {
display: flex;
padding-bottom: 40px;
.ps-content-left {
&.second-login {
margin: 0 -20px;
padding-right: 20px;
.overall-data-top {
padding: 20px 0 20px 0;
display: flex;
.data-block:nth-child(1) {
margin-right: 20px;
max-width:260px;
min-width:260px;
header {
margin-top: 20px;
font-weight: normal;
}
span {
color: black;
text-align: center;
display: block;
font-size: 60px;
margin-top: 20px;
}
}
.data-block:nth-child(2) {
header {
height: 20px;
font-size: 14px;
}
ul {
height:240px;
overflow: auto;
li {
padding: 8px 0;
display: flex;
justify-content: space-between;
span:nth-child(1) {
flex: 6;
}
span:nth-child(2) {
flex: 1;
}
}
}
}
}
.overall-data-center {
padding-top: 10px;
display: flex;
margin-right: -20px;
.data-block {
height:230px;
margin-right: 20px;
header {
font-weight: normal;
margin-top: 20px;
}
.data-block-content {
margin-top: 25px;
font-size: 26px;
}
footer {
margin-top: 25px;
}
}
}
.overall-data-bottom {
padding: 20px 0;
}
.data-block {
padding: 15px;
flex: 1;
border: 1px #ddd solid;
height:260px;
}
}
flex: 5;
header {
font-size: 18px;
font-weight: bold;
}
p{
padding-left: 20px;
margin: 10px 0;
line-height: 24px;
.ip-address {
color: red;
}
}
h2 {
font-weight: normal;
}
.block{
border: 1px #000 solid;
margin: 15px 0 15px 28px;
padding: 15px;
line-height: 24px;
}
}
.ps-content-right {
padding: 20px 0 0 20px;
flex: 1;
max-width: 400px;
.ps-right-header {
font-size: 16px;
color:#1E4B9E;
padding: 20px 0;
}
.ps-site-list {
.list-item {
padding: 5px 0;
display: flex;
justify-content: space-between;
div {
overflow: hidden;
text-overflow:ellipsis;
white-space: nowrap;
}
span {
min-width: 30px;
max-width: 30px;
cursor: pointer;
}
}
}
.quick-sp {
.quick-sp-content {
display: flex;
justify-content: space-between;
margin-bottom: 10px;
}
p {
color:#999;
}
}
.help-list-content {
.help-list-item {
padding: 5px 0;
}
}
}
}
}
\ No newline at end of file
import React from 'react';
import Loadable from 'react-loadable';
import {JFLoading} from "../component/JFLoading/JFLoading";
export const NavRouter = [
{
key: 'overall',
name: '总览',
path: '/overall',
icon: 'home',
component: Loadable({
loader: () => import('../page/overall/overall'),
loading: () => <JFLoading/>
})
}
];
import React from 'react';
import Loadable from 'react-loadable';
import {JFLoading} from "../component/JFLoading/JFLoading";
import {ROUTE_PRE_FIX} from "../common/constant";
export const rootRouter = [
{
path: '/login',
forceRefresh:true,
component: Loadable({
loader: () => import('../page/login/login'),
loading: () => <JFLoading/>
}),
},
{
path: '/'+ROUTE_PRE_FIX,
component: Loadable({
loader: () => import('../layout/basicLayout'),
loading: () => <JFLoading/>
}),
}
];
\ No newline at end of file
// This optional code is used to register a service worker.
// register() is not called by default.
// This lets the app load faster on subsequent visits in production, and gives
// it offline capabilities. However, it also means that developers (and users)
// will only see deployed updates on subsequent visits to a page, after all the
// existing tabs open on the page have been closed, since previously cached
// resources are updated in the background.
// To learn more about the benefits of this model and instructions on how to
// opt-in, read http://bit.ly/CRA-PWA
const isLocalhost = Boolean(
window.location.hostname === 'localhost' ||
// [::1] is the IPv6 localhost address.
window.location.hostname === '[::1]' ||
// 127.0.0.1/8 is considered localhost for IPv4.
window.location.hostname.match(
/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
)
);
export function register(config) {
if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
// The URL constructor is available in all browsers that support SW.
const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href);
if (publicUrl.origin !== window.location.origin) {
// Our service worker won't work if PUBLIC_URL is on a different origin
// from what our page is served on. This might happen if a CDN is used to
// serve assets; see https://github.com/facebook/create-react-app/issues/2374
return;
}
window.addEventListener('load', () => {
const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
if (isLocalhost) {
// This is running on localhost. Let's check if a service worker still exists or not.
checkValidServiceWorker(swUrl, config);
// Add some additional logging to localhost, pointing developers to the
// service worker/PWA documentation.
navigator.serviceWorker.ready.then(() => {
console.log(
'This web app is being served cache-first by a service ' +
'worker. To learn more, visit http://bit.ly/CRA-PWA'
);
});
} else {
// Is not localhost. Just register service worker
registerValidSW(swUrl, config);
}
});
}
}
function registerValidSW(swUrl, config) {
navigator.serviceWorker
.register(swUrl)
.then(registration => {
registration.onupdatefound = () => {
const installingWorker = registration.installing;
if (installingWorker == null) {
return;
}
installingWorker.onstatechange = () => {
if (installingWorker.state === 'installed') {
if (navigator.serviceWorker.controller) {
// At this point, the updated precached content has been fetched,
// but the previous service worker will still serve the older
// content until all client tabs are closed.
console.log(
'New content is available and will be used when all ' +
'tabs for this page are closed. See http://bit.ly/CRA-PWA.'
);
// Execute callback
if (config && config.onUpdate) {
config.onUpdate(registration);
}
} else {
// At this point, everything has been precached.
// It's the perfect time to display a
// "Content is cached for offline use." message.
console.log('Content is cached for offline use.');
// Execute callback
if (config && config.onSuccess) {
config.onSuccess(registration);
}
}
}
};
};
})
.catch(error => {
console.error('Error during service worker registration:', error);
});
}
function checkValidServiceWorker(swUrl, config) {
// Check if the service worker can be found. If it can't reload the page.
fetch(swUrl)
.then(response => {
// Ensure service worker exists, and that we really are getting a JS file.
const contentType = response.headers.get('content-type');
if (
response.status === 404 ||
(contentType != null && contentType.indexOf('javascript') === -1)
) {
// No service worker found. Probably a different app. Reload the page.
navigator.serviceWorker.ready.then(registration => {
registration.unregister().then(() => {
window.location.reload();
});
});
} else {
// Service worker found. Proceed as normal.
registerValidSW(swUrl, config);
}
})
.catch(() => {
console.log(
'No internet connection found. App is running in offline mode.'
);
});
}
export function unregister() {
if ('serviceWorker' in navigator) {
navigator.serviceWorker.ready.then(registration => {
registration.unregister();
});
}
}
import { observable, action, flow, runInAction } from 'mobx';
import {RESOURCE} from "../common/fetch";
import {Message} from "../common/message";
class app {
// 如果已经存在site 则更新页面即刻, 无site表示用户首次进入系统
preSite = '';
@observable siteList = [];
@observable selectedSite = {};
@observable layoutLoading = false;
@observable siteDetail = {};
// 获取site列表
@action
getSiteList = flow(function* () {
this.layoutLoading = true;
try {
let list = yield RESOURCE.post('/api/v1/site/GetSiteAddressList');
this.siteList = list.data;
this.setSelectedSite(list.data[0])
}catch (e) {
Message.alert(e);
}
this.layoutLoading = false;
}.bind(this));
// 设置选中的site
@action
setSelectedSite = flow(function* (site){
this.selectedSite = site;
this.layoutLoading = true;
try{
let siteDetail = yield this._getSiteDetail(site.siteId);
this.siteDetail = siteDetail.data;
}catch (e) {
Message.alert(e);
}
this.layoutLoading = false;
}.bind(this));
_getSiteDetail(siteId) {
return RESOURCE.get('/api/v1/site/SearchSite/'+siteId);
}
init() {
this.getSiteList();
}
}
export const appStore = new app();
\ No newline at end of file
import {routerStore} from "./routerStore";
import {userStore} from "./userStore";
import {appStore} from "./appStore";
import {overallStore} from "./overallStore";
export default {
appStore,
routerStore,
userStore,
overallStore
};
\ No newline at end of file
import { observable, action, flow } from 'mobx';
import { RESOURCE } from '../common/fetch';
import {appStore} from "./appStore";
import moment from 'moment';
import {Message} from "../common/message";
import {DEV_SITE_ID} from "../common/constant";
class Overall {
@observable loading = false;
@observable protectedApi = { count:0, gains:'0' };
@observable attackFlow = { count:0, gains:'0' };
@observable totalFlow = { count:0, gains:'0' };
@observable independentIp = { count:0, gains:'0' };
@observable pvInfo = [];
@observable topFiveApi = [];
@action
init = flow(function* () {
let tsStart = moment().startOf('day').valueOf();
let tsEnd = moment().valueOf();
let preStart = moment().subtract(1, 'd').startOf('day').valueOf();
let preEnd = moment().subtract(1, 'd').valueOf();
this.loading = true;
try {
let [protectedApi, attackFlow, totalFlow, independentIp, pvInfo, topFiveApi] = yield Promise.all([
this._getCount('protectedApi', tsStart, tsEnd, preStart, preEnd),
this._getCount('attackFlow', tsStart, tsEnd, preStart, preEnd),
this._getCount('totalFlow', tsStart, tsEnd, preStart, preEnd),
this._getCount('independentIp', tsStart, tsEnd, preStart, preEnd),
RESOURCE.post('/api/v1/overview/pvInfo',{
"siteId": DEV_SITE_ID || appStore.selectedSite.siteId
}),
RESOURCE.post('/api/v1/overview/topFiveApi',{
"siteId": DEV_SITE_ID || appStore.selectedSite.siteId,
tsEnd,
tsStart
}),
]);
this.protectedApi = protectedApi.data;
this.attackFlow = attackFlow.data;
this.totalFlow = totalFlow.data;
this.independentIp = independentIp.data;
this.pvInfo = pvInfo.data;
this.topFiveApi = topFiveApi.data;
}catch (e) {
Message.alert(e);
}
this.loading = false;
}.bind(this));
// 切换阻断模式状态
@action
toggleBlockMode = flow(function* (mode){
this.layoutLoading = true;
try {
yield RESOURCE.post('/api/v1/config/blockSwitch', {
blockSwitch: mode,
siteId: DEV_SITE_ID || appStore.selectedSite.siteId
})
} catch (e) {
Message.alert(e);
}
this.layoutLoading = false;
})
_getCount(type, tsStart, tsEnd, preStart, preEnd) {
return new Promise((resolve, reject) => {
Promise.all([
RESOURCE.post('/api/v1/overview/count',{
"siteId": DEV_SITE_ID || appStore.selectedSite.siteId,
tsEnd,
tsStart,
type
}),
RESOURCE.post('/api/v1/overview/count',{
"siteId": DEV_SITE_ID || appStore.selectedSite.siteId,
tsEnd:preEnd,
tsStart:preStart,
type
}),
]).then(([ tData, preData ]) => {
let tCount = tData.data.count;
let preCount = preData.data.count;
resolve({data:{ count:tCount, gains:preCount === 0 ? (tCount === 0 ? '0%' : '100%') : (parseFloat((tCount-preCount)/preCount)*100).toFixed(2)+'%'}})
},(e) => {
reject(e);
});
});
}
}
export const overallStore = new Overall();
\ No newline at end of file
import { RouterStore, syncHistoryWithStore } from 'mobx-react-router';
export const routerStore = new RouterStore();
\ No newline at end of file
import { observable, action, flow } from 'mobx';
import {RESOURCE} from "../common/fetch";
import {Message} from "../common/message";
import {navigate_login, navigate_replace} from "../common/utils";
let md5 = require('md5');
let sha1 = require('js-sha1');
class User {
@observable isFirstLogin = localStorage.getItem('isFirstLogin') === 'true';
@observable user = {};
@observable loading = false;
constructor() {
this.user = JSON.parse(localStorage.getItem('paisong') || "{}");
}
@action
login = flow(function* ({ email, password }){
this.loading = true;
try {
let data = yield RESOURCE.post('/api/v1/credential/Login',{ email, password:sha1(md5(password)) });
localStorage.setItem('paisong',JSON.stringify(data.data));
this.user = data.data;
let { data:{ firstLogin } } = yield RESOURCE.post('/api/v1/credential/IsFirstLogin',{ email: data.data.email});
this.isFirstLogin = firstLogin;
localStorage.setItem('isFirstLogin',this.isFirstLogin);
navigate_replace('/firewall');
}catch (e) {
if(e.status === 403) {
Message.error('用户名或密码错误!');
}else {
Message.alert(e);
}
}
this.loading = false;
}.bind(this));
getToken() {
let Authorization = this.user.loginToken;
return Authorization ? { Authorization } : {};
}
logout() {
navigate_login();
localStorage.removeItem('paisong');
localStorage.removeItem('isFirstLogin');
}
}
export const userStore = new User();
\ No newline at end of file
This source diff could not be displayed because it is too large. You can view the blob instead.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment