9b990809 by charles

feat: 项目初始化

0 parents
Showing 150 changed files with 11803 additions and 0 deletions
# http://editorconfig.org
root = true
[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.md]
trim_trailing_whitespace = false
[Makefile]
indent_style = tab
/lambda/
/scripts
/config
.history
\ No newline at end of file
module.exports = {
extends: [require.resolve("@umijs/fabric/dist/eslint")],
globals: {
ANT_DESIGN_PRO_ONLY_DO_NOT_USE_IN_YOUR_PRODUCTION: true,
page: true,
REACT_APP_ENV: true
}
};
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
**/node_modules
# roadhog-api-doc ignore
/src/utils/request-temp.js
_roadhog-api-doc
/dist
# production
/.vscode
# misc
.DS_Store
npm-debug.log*
yarn-error.log
/coverage
.idea
yarn.lock
package-lock.json
*bak
.vscode
# visual studio code
.history
*.log
functions/*
.temp/**
# umi
.umi
.umi-production
# screenshot
screenshot
.firebase
.eslintcache
build
**/*.svg
package.json
.umi
.umi-production
/dist
.dockerignore
.DS_Store
.eslintignore
*.png
*.toml
docker
.editorconfig
Dockerfile*
.gitignore
.prettierignore
LICENSE
.eslintcache
*.lock
yarn-error.log
.history
\ No newline at end of file
const fabric = require("@umijs/fabric");
module.exports = {
...fabric.prettier
};
const fabric = require('@umijs/fabric');
module.exports = {
...fabric.stylelint,
};
# 项目名称
Follow is the quick guide for how to use.
## Environment Prepare
Install `node_modules`:
```bash
npm install
```
or
```bash
yarn
```
## Provided Scripts
Ant Design Pro provides some useful script to help you quick start and build with web project, code style check and test.
Scripts provided in `package.json`. It's safe to modify or add additional script:
### Start project
```bash
npm start
```
### Build project
```bash
npm run build
```
### Check code style
```bash
npm run lint
```
You can also use script to auto fix some lint error:
```bash
npm run lint:fix
```
### Test code
```bash
npm test
```
## More
You can view full document on our [official website](https://pro.ant.design). And welcome any feedback in our [github](https://github.com/ant-design/ant-design-pro).
import defaultSettings from './defaultSettings'; // https://umijs.org/config/
import slash from 'slash2';
import routes from './routerConfig';
import proxy from './proxy';
const { pwa, primaryColor } = defaultSettings; // preview.pro.ant.design only do not use in your production ;
// preview.pro.ant.design 专用环境变量,请不要在你的项目中使用它。
const { ANT_DESIGN_PRO_ONLY_DO_NOT_USE_IN_YOUR_PRODUCTION, REACT_APP_ENV } = process.env;
import { defineConfig, utils } from 'umi';
const { winPath } = utils;
export default defineConfig({
antd: {},
dva: {
hmr: true,
},
locale: {
default: 'zh-CN',
baseNavigator: true,
},
dynamicImport: {
// 无需 level, webpackChunkName 配置
// loadingComponent: './components/PageLoading/index'
loading: '@/components/PageLoading/index',
},
hash: true,
targets: {
ie: 11,
},
title: false,
// umi routes: https://umijs.org/zh/guide/router.html
routes: routes,
// Theme for antd: https://ant.design/docs/react/customize-theme-cn
theme: {
// ...darkTheme,
'primary-color': primaryColor,
},
define: {
REACT_APP_ENV: REACT_APP_ENV || false,
ANT_DESIGN_PRO_ONLY_DO_NOT_USE_IN_YOUR_PRODUCTION:
ANT_DESIGN_PRO_ONLY_DO_NOT_USE_IN_YOUR_PRODUCTION || '', // preview.pro.ant.design only do not use in your production ; preview.pro.ant.design 专用环境变量,请不要在你的项目中使用它。
},
ignoreMomentLocale: true,
lessLoader: {
javascriptEnabled: true,
},
cssLoader: {
modules: {
getLocalIdent: (context, _, localName) => {
if (
context.resourcePath.includes('node_modules') ||
context.resourcePath.includes('ant.design.pro.less') ||
context.resourcePath.includes('global.less')
) {
return localName;
}
const match = context.resourcePath.match(/src(.*)/);
if (match && match[1]) {
const antdProPath = match[1].replace('.less', '');
const arr = slash(antdProPath)
.split('/')
.map(a => a.replace(/([A-Z])/g, '-$1'))
.map(a => a.toLowerCase());
return `antd-pro${arr.join('-')}-${localName}`.replace(/--/g, '-');
}
return localName;
},
},
},
manifest: {
basePath: '/',
},
proxy: proxy[REACT_APP_ENV || 'dev'],
externals: {
react: 'React',
'react-dom': 'ReactDOM',
bizcharts: 'BizCharts',
mockjs: 'Mock',
'@antv/data-set': 'DataSet',
},
});
import { routeMaps } from './routerConfig';
export default {
navTheme: 'dark',
primaryColor: '#185DA2',
layout: 'sidemenu',
contentWidth: 'Fluid',
fixedHeader: false,
autoHideHeader: false,
fixSiderbar: false,
colorWeak: false,
menu: {
locale: true,
},
title: '一网打“净”数智在线',
pwa: false,
iconfontUrl: '',
//缓存token的key
tokenKey: 'yh_token',
//md5的key
md5Key: 'QHZT',
// 路由对象
routeMaps,
};
import path from 'path';
function getModulePackageName(module) {
if (!module.context) return null;
const nodeModulesPath = path.join(__dirname, '../node_modules/');
if (module.context.substring(0, nodeModulesPath.length) !== nodeModulesPath) {
return null;
}
const moduleRelativePath = module.context.substring(nodeModulesPath.length);
const [moduleDirName] = moduleRelativePath.split(path.sep);
let packageName = moduleDirName; // handle tree shaking
if (packageName && packageName.match('^_')) {
// eslint-disable-next-line prefer-destructuring
packageName = packageName.match(/^_(@?[^@]+)/)[1];
}
return packageName;
}
export const webpackPlugin = config => {
// optimize chunks
config.optimization // share the same chunks across different modules
.runtimeChunk(false)
.splitChunks({
chunks: 'async',
name: 'vendors',
maxInitialRequests: Infinity,
minSize: 0,
cacheGroups: {
vendors: {
test: module => {
const packageName = getModulePackageName(module) || '';
if (packageName) {
return [
'bizcharts',
'gg-editor',
'g6',
'@antv',
'gg-editor-core',
'bizcharts-plugin-slider',
].includes(packageName);
}
return false;
},
name(module) {
const packageName = getModulePackageName(module);
if (packageName) {
if (['bizcharts', '@antv_data-set'].indexOf(packageName) >= 0) {
return 'viz'; // visualization package
}
}
return 'misc';
},
},
},
});
};
/**
* 在生产环境 代理是无法生效的,所以这里没有生产环境的配置
* The agent cannot take effect in the production environment
* so there is no configuration of the production environment
* For details, please see
* https://pro.ant.design/docs/deploy
*/
export default {
dev: {
'/ql/api/': {
target: 'https://yh2.jimilicai.com',
changeOrigin: true,
pathRewrite: {
'^': '',
},
},
},
};
export default {
path: '/',
name: 'appliction',
component: '../layouts/BasicLayout',
routes: [
{ path: '/', redirect: '/screen' },
{
path: '/screen',
name: 'dataScreen',
component: './Screen',
icons: 'screen',
resourceCode: 'Home',
},
{
path: '/risk',
name: 'risk',
component: './Risk',
icons: 'risk',
resourceCode: 'RiskManage',
},
{
path: '/event',
name: 'event',
component: './Incident',
icons: 'event',
resourceCode: 'EventManage',
},
{
path: '/data',
name: 'data',
component: './Data',
icons: 'data',
resourceCode: 'DataManage',
},
{
path: '/system',
name: 'system',
icons: 'system',
resourceCode: 'SystemManage',
routes: [
{
path: '/system/account',
name: 'account',
component: './System/Account',
icons: '',
resourceCode: 'AccountManage',
},
{
path: '/system/role',
name: 'role',
component: './System/Role',
icons: '',
resourceCode: 'RoleManage',
},
{
path: '/system/menu',
name: 'menu',
component: './System/Menu',
icons: '',
resourceCode: 'MenuManage',
},
],
},
{
component: './404',
resourceCode: '404',
},
],
};
import user from './user';
import app from './app';
import { cloneDeep } from 'lodash';
const getRoutes = (routes, maps = {}) => {
routes = routes.map(item => {
if (item.auths) {
maps[item.path] = item.auths;
}
if (item.routes) {
item.routes = getRoutes(item.routes, maps).routes;
}
return item;
});
routes.push({
component: '../pages/404',
});
return {
routes,
maps,
};
};
const { routes, maps } = getRoutes([user, app]);
export const routeMaps = maps;
export const codeMappingForResource = (() => {
let menu = cloneDeep([app]);
let codeMapping = new Map();
const getItem = arr => {
arr.forEach(item => {
codeMapping.set(item?.resourceCode, item);
if (item.routes) {
getItem(item.routes);
}
});
};
getItem(menu);
return codeMapping;
})();
export default routes;
export default {
path: '/user',
component: '../layouts/UserLayout',
routes: [
{ path: '/user', name: 'login', redirect: '/user/login' },
{ path: '/user/login', name: 'login', component: './User/Login' },
{ path: '/user/risk', name: 'login', component: './User/Risk' },
],
};
export default {
theme: [
{
key: 'dark',
fileName: 'dark.css',
theme: 'dark',
},
{
key: 'dust',
fileName: 'dust.css',
modifyVars: {
'@primary-color': '#F5222D',
},
},
{
key: 'volcano',
fileName: 'volcano.css',
modifyVars: {
'@primary-color': '#FA541C',
},
},
{
key: 'sunset',
fileName: 'sunset.css',
modifyVars: {
'@primary-color': '#FAAD14',
},
},
{
key: 'cyan',
fileName: 'cyan.css',
modifyVars: {
'@primary-color': '#13C2C2',
},
},
{
key: 'green',
fileName: 'green.css',
modifyVars: {
'@primary-color': '#52C41A',
},
},
{
key: 'geekblue',
fileName: 'geekblue.css',
modifyVars: {
'@primary-color': '#2F54EB',
},
},
{
key: 'purple',
fileName: 'purple.css',
modifyVars: {
'@primary-color': '#722ED1',
},
},
{
key: 'dust',
theme: 'dark',
fileName: 'dark-dust.css',
modifyVars: {
'@primary-color': '#F5222D',
},
},
{
key: 'volcano',
theme: 'dark',
fileName: 'dark-volcano.css',
modifyVars: {
'@primary-color': '#FA541C',
},
},
{
key: 'sunset',
theme: 'dark',
fileName: 'dark-sunset.css',
modifyVars: {
'@primary-color': '#FAAD14',
},
},
{
key: 'cyan',
theme: 'dark',
fileName: 'dark-cyan.css',
modifyVars: {
'@primary-color': '#13C2C2',
},
},
{
key: 'green',
theme: 'dark',
fileName: 'dark-green.css',
modifyVars: {
'@primary-color': '#52C41A',
},
},
{
key: 'geekblue',
theme: 'dark',
fileName: 'dark-geekblue.css',
modifyVars: {
'@primary-color': '#2F54EB',
},
},
{
key: 'purple',
theme: 'dark',
fileName: 'dark-purple.css',
modifyVars: {
'@primary-color': '#722ED1',
},
},
],
};
// ps https://github.com/GoogleChrome/puppeteer/issues/3120
module.exports = {
launch: {
args: [
'--disable-gpu',
'--disable-dev-shm-usage',
'--no-first-run',
'--no-zygote',
'--no-sandbox',
],
},
};
module.exports = {
testURL: 'http://localhost:8000',
preset: 'jest-puppeteer',
extraSetupFiles: ['./tests/setupTests.js'],
globals: {
ANT_DESIGN_PRO_ONLY_DO_NOT_USE_IN_YOUR_PRODUCTION: false,
localStorage: null,
},
};
{
"compilerOptions": {
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
}
}
}
{
"name": "ant-design-pro",
"version": "1.0.0",
"private": true,
"description": "An out-of-box UI solution for enterprise applications",
"scripts": {
"analyze": "cross-env ANALYZE=1 umi build",
"build": "umi build",
"deploy": "npm run site && npm run gh-pages",
"dev": "npm run start:dev",
"fetch:blocks": "pro fetch-blocks --branch antd@4 && npm run prettier",
"format-imports": "cross-env import-sort --write '**/*.{js,jsx,ts,tsx}'",
"gh-pages": "cp CNAME ./dist/ && gh-pages -d dist",
"i18n-remove": "pro i18n-remove --locale=zh-CN --write",
"lint": "npm run lint:js && npm run lint:style && npm run lint:prettier",
"lint-staged": "lint-staged",
"lint-staged:js": "eslint --ext .js,.jsx,.ts,.tsx ",
"lint:fix": "eslint --fix --cache --ext .js,.jsx,.ts,.tsx --format=pretty ./src && npm run lint:style",
"lint:js": "eslint --cache --ext .js,.jsx,.ts,.tsx --format=pretty ./src",
"lint:prettier": "check-prettier lint",
"lint:style": "stylelint --fix \"src/**/*.less\" --syntax less",
"prettier": "prettier -c --write \"**/*\"",
"start": "umi dev",
"start:dev": "cross-env REACT_APP_ENV=dev MOCK=none umi dev",
"start:no-mock": "cross-env MOCK=none umi dev",
"start:no-ui": "cross-env UMI_UI=none umi dev",
"start:pre": "cross-env REACT_APP_ENV=pre umi dev",
"start:test": "cross-env REACT_APP_ENV=test MOCK=none umi dev",
"test": "umi test",
"test:all": "node ./tests/run-tests.js",
"test:component": "umi test ./src/components",
"tsc": "tsc",
"ui": "umi ui"
},
"lint-staged": {
"**/*.less": "stylelint --syntax less",
"**/*.{js,jsx,ts,tsx}": "npm run lint-staged:js",
"**/*.{js,jsx,tsx,ts,less,md,json}": [
"prettier --write"
]
},
"browserslist": [
"> 1%",
"last 2 versions",
"not ie <= 10"
],
"dependencies": {
"@ant-design/icons": "^4.0.0-rc.0",
"@ant-design/pro-layout": "^5.0.3",
"@antv/data-set": "^0.10.2",
"@umijs/preset-react": "^1.2.2",
"antd": "4.23.1",
"bizcharts": "^3.5.7",
"blueimp-md5": "^2.16.0",
"classnames": "^2.2.6",
"crypto-js": "^4.1.1",
"lodash": "^4.17.21",
"moment": "^2.24.0",
"omit.js": "^1.0.2",
"path-to-regexp": "2.4.0",
"qs": "^6.11.0",
"react": "^16.8.6",
"react-copy-to-clipboard": "^5.0.2",
"react-dom": "^16.8.6",
"react-helmet": "^5.2.1",
"react-redux": "^7.2.0",
"redux": "^4.0.1",
"styled-components": "^5.3.5",
"umi": "^3.0.0",
"umi-request": "^1.2.19",
"use-merge-value": "^1.0.1"
},
"devDependencies": {
"@ant-design/pro-cli": "^1.0.18",
"@types/classnames": "^2.2.7",
"@types/express": "^4.17.0",
"@types/history": "^4.7.2",
"@types/jest": "^25.1.0",
"@types/lodash": "^4.14.144",
"@types/qs": "^6.5.3",
"@types/react": "^16.9.17",
"@types/react-dom": "^16.8.4",
"@types/react-helmet": "^5.0.13",
"@umijs/fabric": "^2.0.2",
"chalk": "^3.0.0",
"check-prettier": "^1.0.3",
"cross-env": "^7.0.0",
"cross-port-killer": "^1.1.1",
"enzyme": "^3.11.0",
"express": "^4.17.1",
"gh-pages": "^2.0.1",
"husky": "^4.0.7",
"import-sort-cli": "^6.0.0",
"import-sort-parser-babylon": "^6.0.0",
"import-sort-parser-typescript": "^6.0.0",
"import-sort-style-module": "^6.0.0",
"jest-puppeteer": "^4.4.0",
"jsdom-global": "^3.0.2",
"lint-staged": "^10.0.0",
"mockjs": "^1.0.1-beta3",
"node-fetch": "^2.6.0",
"prettier": "^1.19.1",
"pro-download": "1.0.1",
"stylelint": "^13.0.0"
},
"optionalDependencies": {
"puppeteer": "^2.0.0"
},
"engines": {
"node": ">=10.0.0"
},
"checkFiles": [
"src/**/*.js*",
"src/**/*.ts*",
"src/**/*.less",
"config/**/*.js*",
"scripts/**/*.js"
]
}
No preview for this file type
import { createFromIconfontCN } from '@ant-design/icons';
/* 接口域名 */
const domain = window.location.hostname.toLowerCase();
export const urlConfig = {
'middletest.lipinclub.com': {
URL_API: 'http://middletest.lipinclub.com/api/',
},
'middle.lipinclub.com': {
URL_API: 'http://middle.lipinclub.com/api/',
},
}[domain] || {
URL_API: '/ql/api',
};
// 七牛云图片域名
export const IMG_URL = 'http://qn.qinwell.com/';
/* 自定义icon的地址 */
export const IconFontConfig = createFromIconfontCN({
scriptUrl: '//at.alicdn.com/t/font_1948876_xye4vujgq2j.js',
});
/**
* Auther: APIS
*/
import React, { useEffect, useState } from 'react';
import { connect } from 'umi';
const AuthBlock = props => {
const { auth, userAuths, children } = props;
if (userAuths.includes(auth) || !auth) {
return <>{children}</>;
} else {
return null;
}
};
export default connect(({ user }) => {
return {
userAuths: user.userAuths,
};
})(AuthBlock);
/**
* Auther: APIS
*/
import React, { useEffect, useState } from 'react';
import { connect } from 'umi';
import NoFoundPage from '@/pages/404';
/**
* 路由权限控制
* 路由权限控制需要在路由配置里面对应的路由设置'auths'字段即可,支持字符串和数组,不需要加权限的路由不设置即可。
* 原则上一个页面允许存在多个权限code,但是不允许一个code对应多个页面
* @param {*} props
*/
const AuthRouter = props=> {
const { userAuths, routeMaps, path, children } = props;
const currentAuths = routeMaps[path];
let hasAuth = true;
if (currentAuths) {
if (Array.isArray(currentAuths)) {
hasAuth = currentAuths.some(item=> userAuths.includes(item));
} else {
hasAuth = userAuths.includes(currentAuths);
}
}
return <>{hasAuth ? children : <NoFoundPage />}</>;
};
export default connect(({user, settings})=> {
return {
userAuths: user.userAuths,
routeMaps: settings.routeMaps
}
})(AuthRouter);
\ No newline at end of file
/**
* Auther: APIS
*/
import React, { useEffect, useState } from 'react';
import { message} from 'antd';
import { CopyToClipboard } from 'react-copy-to-clipboard';
const style = {
cursor: 'pointer'
};
const Template = props=> {
const { text, children } = props;
useEffect(()=> {
}, [text]);
const onCopy = (test, result)=> {
if (result) {
message.success('已复制到剪切板~');
} else {
message.error('复制失败,请手动进行复制~');
}
}
return (
<CopyToClipboard text={text} onCopy={onCopy} style={style} title='点击可复制'>
{children}
</CopyToClipboard>
);
};
export default Template;
\ No newline at end of file
/**
* Auther: APIS
*/
import React, { useEffect, useState } from 'react';
const FileExport = props=> {
const { url, dispatch } = props;
useEffect(()=> {
const linkExport = document.getElementById('J_HandleExport');
if (url && linkExport) {
linkExport.click();
setTimeout(()=> {
dispatch({type: 'global/changeState', payload: { urlFileExport: '' }});
}, 100);
}
}, [url]);
return <a href={url} download id="J_HandleExport"></a>
};
export default FileExport;
\ No newline at end of file
/**
* 省市区formItem组件
* @param {*省市区的标签的文本} label
* @param {*省市区的标签的字段名} name
* @param {*详细地址的标签的文本} alabel
* @param {*详细地址的字段名} aname
*/
import React, { useState, useEffect, forwardRef } from 'react';
import { connect } from 'dva';
import { Card, Button, Row, Col, Input, Popover, Form, Cascader, Select } from 'antd';
import InputFormItem from '@/components/FormItems/InputFormItem';
const { TextArea } = Input;
const { Option } = Select;
const CitySelects = forwardRef((props, ref) => {
const { value = {}, onChange, options = [], disabled } = props;
const { province, city, district } = value;
const [provinceData, setprovinceData] = useState([]);
const [cityData, setcityData] = useState([]);
const [districtData, setdistrictData] = useState([]);
useEffect(() => {
if (options.length > 0) {
if (province) {
const plists = provinceData.filter(_ => _.label === province);
setcityData(plists[0].children);
}
}
}, [province]);
useEffect(() => {
if (city && cityData.length > 0) {
const cityLists = cityData.filter(_ => _.label === city);
setdistrictData(cityLists[0].children);
}
}, [city]);
useEffect(() => {
if (cityData.length > 0) {
const cityLists = cityData.filter(_ => _.label === city);
setdistrictData(cityLists.length > 0 ? cityLists[0].children : []);
}
}, [cityData]);
useEffect(() => {
setprovinceData(options);
}, [options]);
const handleProvinceChange = province => {
const provinceLists = provinceData.filter(_ => _.label === province);
setcityData(provinceLists[0].children);
onChange({
...value,
province,
city: '',
});
};
const onSecondCityChange = city => {
const cityLists = cityData.filter(_ => _.label === city);
setdistrictData(cityLists[0].children);
onChange({
...value,
city,
district: '',
});
};
const onDistrictChange = district => {
onChange({
...value,
district,
});
};
return (
<>
<Select
className="mr-10"
value={province}
style={{ width: 150 }}
onChange={handleProvinceChange}
placeholder="请选择省份"
disabled={disabled}
>
{provinceData.map(province => (
<Option key={province.label}>{province.label}</Option>
))}
</Select>
<Select
className="mr-10"
value={city}
style={{ width: 150 }}
onChange={onSecondCityChange}
placeholder="请选择城市"
disabled={disabled}
>
{cityData.map(city => (
<Option key={city.label}>{city.label}</Option>
))}
</Select>
<Select
value={district}
style={{ width: 150 }}
onChange={onDistrictChange}
placeholder="请选择区县"
disabled={disabled}
>
{districtData.map(district => (
<Option key={district.label}>{district.label}</Option>
))}
</Select>
</>
);
});
const FormItem = props => {
const {
name,
label,
alabel,
aname,
wrapperCol = { span: 10 },
BaseProvinceList,
dispatch,
form,
disabled,
} = props;
const validator = (rule, value = {}) => {
const { province, city, district } = value;
if (!province && !city && !district) {
return Promise.reject('请选择省市区!');
} else if (!province) {
return Promise.reject('请选择省份!');
} else if (!city) {
return Promise.reject('请选择城市!');
} else if (!district) {
return Promise.reject('请选择区县!');
} else {
return Promise.resolve();
}
};
return (
<>
<Form.Item
label={label}
name={name}
required
rules={[
{
validator,
},
]}
>
<CitySelects disabled={disabled} options={BaseProvinceList} />
</Form.Item>
<InputFormItem disabled={disabled} label={alabel} name={aname} max={25} />
</>
);
};
const mapStateToProps = ({ global }) => {
return {
BaseProvinceList: global.BaseProvinceList,
};
};
export default connect(mapStateToProps)(FormItem);
/**
* Author: wjw
* Date:
* Description:
*/
import React, { useState, useEffect, forwardRef } from 'react';
import { connect } from 'dva';
import { Card, Button, Row, Col, Input } from 'antd';
import styles from './index.less';
const { TextArea } = Input;
const InputCom = forwardRef((props, ref) => {
const { value, onChange, placeholder, max, type = 'input', style, disabled, defaultLen = 0 } = props;
const onIptChange = ({ target: { value } }) => {
if (onChange) {
onChange(value);
}
};
const suffix = (
<span style={{ lineHeight: '20px' }}>
{(value) ? (value.length + defaultLen) : ((!value && defaultLen) ? defaultLen : 0) }/{!defaultLen ? max : 500}
</span>
);
const params = {
autoComplete: 'off',
maxLength: max,
placeholder,
value,
style,
onChange: onIptChange,
disabled,
};
return (
<div>
{type === 'input' && <Input {...params} suffix={suffix} />}
{type === 'textArea' && (
<div style={{ position: 'relative' }}>
<TextArea {...params} rows={4} />
<div className={styles.suffixttextarea}>{suffix}</div>
</div>
)}
</div>
);
});
export default InputCom;
.suffixttextarea {
position: absolute;
bottom : 5px;
right : 10px;
}
\ No newline at end of file
/**
* 带有长度统计formItem组件
* @param {*标签的文本} label
* @param {*字段名} name
* @param {*占位符} placeholder
* @param {*最小长度} min
* @param {*最大长度} max
* @param {*input框类型input、textArea} type
* @param {*规则} rules
* @param {*是否必填} required
* @param {*必填提示} message
*/
import React, { useState, useEffect, forwardRef } from 'react';
import { connect } from 'dva';
import { Card, Button, Row, Col, Input, Popover, Form, Cascader, Upload } from 'antd';
import InputCom from './InputCom';
const FormItem = props => {
const { label, name, placeholder, max, type, rules, required = true, min = 0, message, extra } = props;
const placeholderText = placeholder ? placeholder : `请输入${label}`;
const rlueMessage = message ? message : placeholderText;
return (
<Form.Item
label={label}
name={name}
extra={extra}
rules={rules ? rules : [{ required: required, message: rlueMessage, min }]}
>
<InputCom {...props} placeholder={placeholderText} />
</Form.Item>
);
};
export default FormItem;
/**
* Author: wjw
* Date:
* Description:
*/
import React, { useState, useEffect, forwardRef } from 'react';
import { connect } from 'umi';
import { Card, Button, Row, Col, Input, Popover, Form } from 'antd';
import { CheckCircleOutlined } from '@ant-design/icons';
const hasLength = value => {
return value.length >= 6 && value.length <= 16;
};
const hasletter = value => {
for (var i in value) {
var asc = value.charCodeAt(i);
if ((asc >= 65 && asc <= 90) || (asc >= 97 && asc <= 122)) {
return true;
}
}
return false;
};
const hasnum = value => {
var str = value;
var reg = new RegExp(/\d+/gi);
if (reg.test(str)) {
return true;
} else {
return false;
}
};
const PasswordFormItem = forwardRef((props, ref) => {
const {
value = '',
onChange,
placeholder = '6 - 16位密码,包含字母和数字',
form,
visible,
prefix,
} = props;
const onIptChange = ({ target: { value } }) => {
onChange(value);
};
const [isObj, setIsObj] = useState({
count: false,
letter: false,
num: false,
});
useEffect(() => {
const nobj = {
count: hasLength(value),
letter: hasletter(value),
num: hasnum(value),
};
setIsObj(nobj);
}, [value]);
const isTure = bool => {
return (
<div className="mr-5">
{bool ? (
<CheckCircleOutlined style={{ color: '#52c41a' }} />
) : (
<div
style={{
width: '14px',
height: '14px',
border: '1px solid #ccc',
borderRadius: '100%',
}}
></div>
)}
</div>
);
};
const styles = {
display: 'flex',
flexDirection: 'row',
justifyContent: 'flex-start',
alignItems: 'center',
};
const Content = props => {
return (
<div>
<div style={styles}>
{isTure(isObj.count)}输入6 - 16 位字符,特殊字符包括下划线,@,#等等
</div>
<div style={styles}>{isTure(isObj.letter)}包含字母</div>
<div style={styles}>{isTure(isObj.num)}包含数字</div>
</div>
);
};
return (
<Popover placement="right" content={<Content />} visible={visible}>
<Input.Password
prefix={prefix}
autoComplete="new-password"
placeholder={placeholder}
value={value}
onChange={onIptChange}
visibilityToggle={false}
/>
</Popover>
);
});
const FormItem = props => {
const { prefix, label } = props;
const [visible, setvisible] = useState(false);
const checkpassword = (rule, value) => {
if (!value || !(hasLength(value) && hasletter(value) && hasnum(value))) {
setvisible(true);
return Promise.reject('');
} else {
setvisible(false);
return Promise.resolve();
}
};
return (
<Form.Item required label={label} name="password" rules={[{ validator: checkpassword }]}>
<PasswordFormItem prefix={prefix} visible={visible} />
</Form.Item>
);
};
export default FormItem;
/**
* Author: wjw
* Date:
* Description:
*/
import React, { useState, useEffect, forwardRef } from 'react';
import { connect } from 'umi';
import { Card, Button, Icon, Row, Col, Input, Popover, Form, Select } from 'antd';
import { isPhone } from '@/utils/utils';
const { Option } = Select;
const FormItem = props => {
const { label = null, name, isPrefixSelector = false, cd, hasFeedback = true } = props;
const prefixSelector = (
<Form.Item name="prefix" noStyle>
<Select style={{ width: 70 }}>
<Option value="86">+86</Option>
</Select>
</Form.Item>
);
const validateTocheckMobile = (rule, value) => {
if (value) {
if (!isPhone(value)) {
return Promise.reject('请输入正确手机格式');
return;
} else {
if (cd) {
cd();
} else {
return Promise.resolve();
}
}
} else {
return Promise.resolve();
}
};
return (
<Form.Item
label={label}
hasFeedback={hasFeedback}
name={name}
rules={[
{
required: true,
message: '输入手机号',
},
{
validator: validateTocheckMobile,
},
]}
validateTrigger="onBlur"
>
<Input
addonBefore={isPrefixSelector ? prefixSelector : null}
style={{ width: '100%' }}
placeholder="11位手机号"
/>
</Form.Item>
);
};
export default FormItem;
/**
* Author: wjw
* Date:
* Description:
*/
import React, { useState, useEffect, forwardRef } from 'react';
import { connect } from 'dva';
import { Card, Button, Form, Radio } from 'antd';
const RadioFormItem = props => {
const { name, label, list = [], disabled } = props;
return (
<div>
<Form.Item label={label} name={name} rules={[{ required: true, message: '请选择' }]}>
<Radio.Group disabled={disabled}>
{list.map((item, index) => {
return (
<Radio key={index} value={item.value}>
{item.name}
</Radio>
);
})}
</Radio.Group>
</Form.Item>
</div>
);
};
export default RadioFormItem;
import React, { useState, useEffect, forwardRef } from 'react';
import { connect } from 'dva';
import { Card, Button, Upload, message } from 'antd';
import { urlConfig } from '@/common';
import styles from '../index.less';
import { getFileName, getFileType } from '@/utils/utils';
import { PlusOutlined, DownloadOutlined } from '@ant-design/icons';
// 上传图片的配置
const token = window.localStorage.getItem('qintaoyouxuan_token') || '';
const settings = {
name: 'file',
withCredentials: true,
headers: {
token,
},
};
const IMGUploadButton = ({ text }) => {
return (
<div>
<PlusOutlined />
<div className="ant-upload-text">{text}</div>
</div>
);
};
const UploadButton = ({ text }) => {
return (
<Button className={styles.textbtn}>
<DownloadOutlined />
{text}
</Button>
);
};
const UploadItem = forwardRef((props, ref) => {
const {
value,
onChange,
listType = 'text',
length = 1,
defaultFileList = [],
accept = 'image/jpg,image/jpeg,image/png',
maxSize = 2,
filemaxSize = 25,
dispatch,
style,
className,
text = '上传文件',
disabled,
children,
isError = false,
action = 'imgs/uploadImg',
} = props;
const serverURL = `${urlConfig.URL_API}${action}`;
const [fileList, setfileList] = useState(defaultFileList);
const [len, setlen] = useState(-1);
const [isupload, setIsupload] = useState(false);
useEffect(() => {
if (Array.isArray(value) && !isupload) {
let files = value.map((item, index) => {
let name = getFileName(item);
return {
uid: index,
name,
url: item,
thumbUrl: item,
status: 'done',
response: { status: 1, data: item },
percent: 100,
size: 0,
};
});
setfileList(files);
}
}, [value]);
useEffect(() => {
if (fileList.length >= len && len !== -1) {
let values = fileList.filter(item => item.url).map(i => i.url);
if (isError) {
values = fileList.map(i => {
const { status, response, url } = i;
return {
status,
url,
response,
};
});
}
if (onChange) {
onChange(values);
}
}
}, [fileList]);
const getisLtM = file => {
const { name, size } = file;
const type = getFileType(name);
const is = !!(type === '.pdf' || type === '.rar');
const MAX_SIZE = is ? filemaxSize : maxSize;
return {
MAX_SIZE,
isLtM: size / 1024 / 1024 < MAX_SIZE,
};
};
const getStatus = item => {
const { name, size, status, response, type } = item;
const MAX_SIZE = getisLtM(item).MAX_SIZE;
const isLtM = getisLtM(item).isLtM;
if (!isLtM) {
return {
...item,
status: status === 'removed' ? 'removed' : 'error',
response: `上传的文件大小不能超过${MAX_SIZE}M`,
url: item.url,
};
}
if (status === 'done') {
if (response && response.status !== 1) {
return {
...item,
status: 'error',
response: response.msg ? response.msg : response.data,
url: item.url,
};
}
}
return {
...item,
status: status,
response: response,
url:
item.status === 'done'
? item.response.data
: item.status === 'uploading'
? 'uploading'
: item.url,
};
};
const onFilechange = info => {
setIsupload(true);
let fileone = info.file;
let fileList = info.fileList;
fileone = getStatus(fileone);
if (fileone.status === 'done' && fileone.response) {
message.success('上传成功');
// onChange();
} else if (fileone.status === 'error' && fileone.response) {
message.error(`${fileone.response}`);
//不符合的文件删除
fileList = fileList.filter(_ => _.uid !== fileone.uid);
}
//长度限制
fileList = fileList
.filter((i, idx) => idx < length)
.map(item => {
return getStatus(item);
});
setfileList(fileList);
setlen(fileList.length);
};
const onRemove = file => {
setlen(fileList.length - 1);
};
const onPreview = file => {
const img = file.url;
if (img) {
handleAdd('PREVIEWIMG', { imgUrl: img });
}
};
const handleAdd = (modalType, r) => {
const payload = {
preImgDataModal: {
modalType,
modalShow: true,
modalData: r ? r : {},
},
};
dispatch({ type: 'global/changeState', payload });
};
//上传前判断文件大小是否符合
const imgupload = (file, fileList) => {
const isLtM = getisLtM(file).isLtM;
if (!isLtM) {
return false;
}
return isLtM;
};
return (
<Upload
action={serverURL}
// accept={accept}
multiple={!!(length > 1)}
onChange={onFilechange}
fileList={fileList}
{...settings}
beforeUpload={imgupload}
listType={listType}
onRemove={onRemove}
onPreview={listType === 'picture-card' ? onPreview : null}
style={style}
className={className}
disabled={disabled}
>
{!disabled && fileList.length < length && (
<div>
{listType === 'picture-card' ? (
<IMGUploadButton text={text} />
) : children ? (
children
) : (
<UploadButton text={text} />
)}
</div>
)}
</Upload>
);
});
const mapStateToProps = ({ global }) => {
return {
preImgDataModal: global.preImgDataModal,
};
};
export default connect(mapStateToProps)(UploadItem);
/**
* 上传图片组件
* @param {*标签的文本} label
* @param {*标签的字段名} name
* @param {*额外的提示信息} extra
* @param {*插槽} children
* @param {*value是否含有上传文件的当前状态{status:'done',url:'',response:''}} isError
*/
import React, { useState, useEffect, forwardRef } from 'react';
import { connect } from 'dva';
import { Card, Button, Icon, Row, Col, Input, Popover, Form, Cascader, Upload } from 'antd';
import UploadItem from './UploadItem';
const FormItem = props => {
const { name, label, extra, children, isError } = props;
const validator = (rule, value) => {
if (value && value.some(i => i.status === 'error')) {
return Promise.reject('您有文件不符合要求,请删除!');
} else if (value && value.some(i => i.status === 'uploading' || i === 'uploading')) {
return Promise.reject('您有文件正在上传!');
} else {
return Promise.resolve();
}
};
let rules = [
{ required: true, message: '请上传文件' },
{
validator: validator,
},
];
return (
<Form.Item label={label} extra={extra} name={name} rules={rules}>
<UploadItem {...props}>{children}</UploadItem>
</Form.Item>
);
};
export default FormItem;
.upfilebtn {
width: 70px;
height: 70px;
background: rgba(251, 251, 251, 1);
border: 1px dashed rgba(218, 218, 218, 1);
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
p {
font-size: 14px;
font-family: Source Han Sans CN;
font-weight: 400;
color: rgba(153, 153, 153, 1);
margin-top: 11px;
}
}
.textbtn:hover,
.textbtn:focus {
border: 1px solid #1890ff !important;
color: #1890ff !important;
}
import React from 'react';
import { connect } from 'umi';
import { LogoutOutlined, InfoCircleOutlined } from '@ant-design/icons';
import { Avatar, Menu, Spin } from 'antd';
import { IconFontConfig } from '@/common';
import HeaderDropdown from '../HeaderDropdown';
import styles from './index.less';
import ModalUpdatePassword from '../ModalUpdatePassword';
class AvatarDropdown extends React.Component {
onMenuClick = event => {
const { key } = event;
if (key === 'logout') {
const { dispatch } = this.props;
if (dispatch) {
dispatch({
type: 'login/logout',
});
}
return;
} else if (key === 'password') {
const { dispatch } = this.props;
dispatch({
type: 'user/changeState',
payload: {
dataModal: {
modalType: 'PASSWORD_UPDATE_MODAL',
modalShow: true,
modalData: {},
},
},
});
}
};
render() {
const {
currentUser = {
avatar: '',
name: '',
},
menu,
} = this.props;
const menuHeaderDropdown = (
<Menu className={styles.menu} selectedKeys={[]} onClick={this.onMenuClick}>
<Menu.Item key="password">
<InfoCircleOutlined />
修改密码
</Menu.Item>
<Menu.Item key="logout">
<LogoutOutlined />
退出登录
</Menu.Item>
</Menu>
);
return currentUser && currentUser.name ? (
<>
<HeaderDropdown overlay={menuHeaderDropdown}>
<span className={`${styles.action} ${styles.account}`}>
<IconFontConfig type="icon-header" style={{ fontSize: '28px' }} className="mr-10" />
<span className={styles.name}>{currentUser.name}</span>
</span>
</HeaderDropdown>
<ModalUpdatePassword />
</>
) : (
<Spin
size="small"
style={{
marginLeft: 8,
marginRight: 8,
lineHeight: '64px',
}}
/>
);
}
}
export default connect(({ user }) => ({
currentUser: user.currentUser,
}))(AvatarDropdown);
import { Tooltip, Tag } from 'antd';
import { QuestionCircleOutlined } from '@ant-design/icons';
import React from 'react';
import { connect } from 'umi';
import Avatar from './AvatarDropdown';
import styles from './index.less';
const ENVTagColor = {
dev: 'orange',
test: 'green',
pre: '#87d068',
};
const GlobalHeaderRight = props => {
const { theme, layout } = props;
let className = styles.right;
if (theme === 'dark' && layout === 'topmenu') {
className = `${styles.right} ${styles.dark}`;
}
return (
<div className={className}>
{/* <Tooltip title="使用文档">
<a
target="_blank"
href="https://pro.ant.design/docs/getting-started"
rel="noopener noreferrer"
className={styles.action}
>
<QuestionCircleOutlined />
</a>
</Tooltip> */}
<Avatar />
{REACT_APP_ENV && (
<span>
<Tag color={ENVTagColor[REACT_APP_ENV]}>{REACT_APP_ENV}</Tag>
</span>
)}
</div>
);
};
export default connect(({ settings }) => ({
theme: settings.navTheme,
layout: settings.layout,
}))(GlobalHeaderRight);
@import '~antd/es/style/themes/default.less';
@pro-header-hover-bg: rgba(0, 0, 0, 0.025);
.menu {
:global(.anticon) {
margin-right: 8px;
}
:global(.ant-dropdown-menu-item) {
min-width: 160px;
}
}
.right {
display: flex;
float: right;
height: @layout-header-height;
margin-left: auto;
overflow: hidden;
.action {
display: flex;
align-items: center;
height: 100%;
padding: 0 12px;
cursor: pointer;
transition: all 0.3s;
> span {
color: @text-color;
vertical-align: middle;
}
// &:hover {
// background: @pro-header-hover-bg;
// }
// &:global(.opened) {
// background: @pro-header-hover-bg;
// }
}
.search {
padding: 0 12px;
&:hover {
background: transparent;
}
}
.account {
.avatar {
margin: ~'calc((@{layout-header-height} - 24px) / 2)' 0;
margin-right: 8px;
color: @primary-color;
vertical-align: top;
background: rgba(255, 255, 255, 0.85);
}
}
}
.dark {
.action {
color: rgba(255, 255, 255, 0.85);
> span {
color: rgba(255, 255, 255, 0.85);
}
// &:hover,
// &:global(.opened) {
// background: @primary-color;
// }
}
}
:global(.ant-pro-global-header) {
img {
height: 50px;
}
.dark {
.action {
color: @text-color;
> span {
color: @text-color;
}
&:hover {
color: rgba(255, 255, 255, 0.85);
> span {
color: rgba(255, 255, 255, 0.85);
}
}
}
}
}
@media only screen and (max-width: @screen-md) {
:global(.ant-divider-vertical) {
vertical-align: unset;
}
.name {
display: none;
}
.right {
position: absolute;
top: 0;
right: 12px;
.account {
.avatar {
margin-right: 0;
}
}
}
}
import { Dropdown } from 'antd';
import React from 'react';
import classNames from 'classnames';
import styles from './index.less';
const HeaderDropdown = ({ overlayClassName: cls, ...restProps }) => (
<Dropdown overlayClassName={classNames(styles.container, cls)} {...restProps} />
);
export default HeaderDropdown;
@import '~antd/es/style/themes/default.less';
.container > * {
background-color: @popover-bg;
border-radius: 4px;
box-shadow: @shadow-1-down;
}
@media screen and (max-width: @screen-xs) {
.container {
width: 100% !important;
}
.container > * {
border-radius: 0 !important;
}
}
/**
* Auther: APIS
*/
import React, { useEffect, useState } from 'react';
import { Spin } from 'antd';
import styles from './index.less';
export const customLoadingParams = {
indicator: (
<img
className="loading-img"
src="https://youxuan-prod.oss-cn-zhangjiakou.aliyuncs.com/qintaoyouxuan/loading.gif"
/>
),
delay: 50,
};
const Loading = props => {
const { loading } = props;
return <Spin spinning={loading} {...customLoadingParams} className="wrap-loading"></Spin>;
};
export default Loading;
:global {
.ant-pro-basicLayout .ant-pro-basicLayout-is-children.ant-pro-basicLayout-fix-siderbar{
.wrap-loading{
left: -100px !important;
}
}
.wrap-loading{
position: fixed !important;
top: 0 !important;
left: 0px !important;
right: 0 !important;
bottom: 0 !important;
z-index: 999 !important;
&:before{
content: '';
display: block;
width: 100%;
height: 100%;
background: #FFF;
opacity: .6;
}
.ant-spin-dot.ant-spin-dot-spin{
position: absolute;
top: 40%;
left: 50%;
}
.loading-img{
position: absolute;
top: 40%;
left: 50%;
width: 30px;
height: auto;
opacity: .6;
}
}
}
\ No newline at end of file
/**
* Author: Charles
* Date: 2022.9.13
* Description: [修改密码]
*/
import React, { useEffect } from 'react';
import { connect } from 'umi';
import { Modal, Form, Input, Button } from 'antd';
const formItemLayout = { labelCol: { span: 4 }, wrapperCol: { span: 20 } };
const ModalUpdatePassword = props => {
const [form] = Form.useForm();
const {
dispatch,
dataModal: { modalType, modalShow },
} = props;
useEffect(() => {
if (modalType === 'PASSWORD_UPDATE_MODAL' && modalShow) {
form.resetFields();
}
}, [modalType, modalShow]);
/* 点击保存 */
const handleSave = () => {
form.validateFields().then(values => {
dispatch({ type: 'user/updatePassword', payload: values });
});
};
return (
<Modal
title="修改密码"
placement="right"
width={700}
maskClosable={false}
onCancel={() => {
dispatch({ type: 'user/cancelModal' });
}}
visible={modalType === 'PASSWORD_UPDATE_MODAL' && modalShow}
footer={
<div
style={{
textAlign: 'right',
}}
>
<Button
onClick={() => {
dispatch({ type: 'user/cancelModal' });
}}
className="mr-10"
>
取消
</Button>
<Button onClick={handleSave} type="primary">
保存
</Button>
</div>
}
>
<Form form={form} {...formItemLayout} name="password_set_modal">
<Form.Item
name="oldPassword"
label="原密码"
rules={[{ required: true, message: '请输入原密码' }]}
>
<Input.Password placeholder="请输入原密码" />
</Form.Item>
<Form.Item
name="newPassword"
label="新密码"
rules={[
{ required: true, message: '请输入新密码' },
{
pattern: /^(?![\d]+$)(?![a-zA-Z]+$)(?![!#$%^&*]+$)[\da-zA-Z!#$%^&@*]{6,16}$/,
message: '密码至少包含字母、数字、特殊符号的两种组合,限制6~16个字符~',
},
]}
>
<Input.Password placeholder="请输入新密码" />
</Form.Item>
<Form.Item
name="checkPassword"
label="确认新密码"
rules={[
{ required: true, message: '请输入确认新密码' },
({ getFieldValue }) => ({
validator(_, value) {
if (!value || getFieldValue('newPassword') === value) {
return Promise.resolve();
}
return Promise.reject(new Error('两次密码不一致,请重新输入'));
},
}),
]}
>
<Input.Password placeholder="请输入确认新密码" />
</Form.Item>
</Form>
</Modal>
);
};
export default connect(({ user }) => ({
...user,
}))(ModalUpdatePassword);
/**
* Auther: APIS
*/
import ModalPreImg from '@/pages/Modals/ModalPreImg';
import FileExport from '@/components/FileExport';
import Loading from '@/components/Loading';
const PageCommons = props=> {
const { urlFileExport, loading, dispatch } = props;
return (
<>
{/* 图片预览 */}
<ModalPreImg />
{/* 文件下载 */}
<FileExport url={urlFileExport} dispatch={dispatch} />
{/* 全局loading */}
<Loading loading={loading} />
</>
)
};
export default PageCommons;
\ No newline at end of file
import { PageLoading } from '@ant-design/pro-layout'; // loading components from code split
// https://umijs.org/plugin/umi-plugin-react.html#dynamicimport
export default PageLoading;
import React, { useEffect } from 'react';
import { Row, Col, Card, Typography, Tooltip, Spin } from 'antd';
import styles from './index.less';
import { ExclamationCircleOutlined } from '@ant-design/icons';
import Loading from '@/components/Loading';
const { Title } = Typography;
const QCard = props => {
const { title, des, children, extra, loading = false, ...otherProps } = props;
return (
<Card {...otherProps}>
<Loading loading={loading} />
<Row className={styles.main} type="flex" justify="space-between" align="middle">
<Col>
<Row type="flex" align="middle">
<Col>
<div className={styles.fk} />
</Col>
{title && (
<Col>
<div className={styles.title}>{title}</div>
</Col>
)}
{des && (
<Col>
<Tooltip title={des}>
<ExclamationCircleOutlined />
</Tooltip>
</Col>
)}
</Row>
</Col>
{extra && <Col>{extra}</Col>}
</Row>
{children}
</Card>
);
};
export default QCard;
.main {
margin-bottom: 20px;
.title {
font-size: 14px;
font-family: PingFang SC;
font-weight: 500;
color: rgba(51, 51, 51, 1);
margin: 0 8px;
}
.fk {
width: 3px;
height: 16px;
background: rgba(230, 15, 26, 1);
}
}
import styled, { css } from 'styled-components';
export const StyledPageContainer = styled.div`
width: 100%;
height: 100%;
`;
export const StyledPageHeader = styled.div<{ border: Boolean }>`
display: flex;
align-items: center;
justify-content: flex-end;
background-color: #fff;
padding: 8px 16px;
border-radius: 2px;
border-top: ${props => (props.border ? '1px solid rgba(0, 0, 0, 0.06)' : 'none')};
.ant-btn {
margin-left: 10px;
}
.ant-upload-list {
display: none;
}
`;
export const StyledPageContent = styled.div`
margin: 16px 16px 0;
.ant-form {
.ant-form-item {
margin-bottom: 16px;
}
}
`;
export const StyledEllipsisWrap = styled.div<{ maxLine: number }>`
overflow: hidden;
text-overflow: ellipsis;
${p =>
p.maxLine === 1
? css`
white-space: nowrap;
`
: css`
display: -webkit-box;
white-space: normal;
-webkit-line-clamp: ${p.maxLine};
-webkit-box-orient: vertical;
word-break: break-all;
`}
`;
export const StyledWapperTab = styled.div`
.ant-tabs {
background-color: #fff;
padding: 0px 16px;
}
.ant-tabs-nav {
margin: 0;
}
.ant-tabs-top > .ant-tabs-nav::before,
.ant-tabs-bottom > .ant-tabs-nav::before,
.ant-tabs-top > div > .ant-tabs-nav::before,
.ant-tabs-bottom > div > .ant-tabs-nav::before {
border-bottom: none;
}
`;
export const StyledPageFlex = styled.div`
display: flex;
`;
export const StyledPageLeft = styled.div`
width: 300px;
margin-right: 16px;
border-radius: 2px;
padding: 22px 16px;
background-color: #fff;
min-height: calc(100vh - 144px);
max-height: calc(100vh - 144px);
overflow-y: scroll;
.ant-tree .ant-tree-node-content-wrapper {
padding: 0 6px;
.ant-tree-title {
font-size: 15px;
}
}
`;
export const StyledPageRight = styled.div`
flex: 1;
border-radius: 2px;
`;
export const StyledWapperIframe = styled.div`
height: calc(100vh - 62px);
width: 100%;
background-color: #fff;
`;
export const StyledTitle = styled.div`
h1 {
font-size: 16px;
}
`;
export const StyledText = styled.div`
display: flex;
flex-wrap: wrap;
.item-text {
width: calc(50% - 10px);
margin-bottom: 10px;
margin-right: 10px;
.title {
color: #797e8f;
font-size: 14px;
margin-bottom: 4px;
}
.desc {
color: #000;
font-size: 14px;
}
}
`;
import moment from 'moment';
// paginationDefault
export const paginations = {
position: ['bottomCenter'],
defaultCurrent: 1,
showSizeChanger: true,
showQuickJumper: true,
pageSizeOptions: ['5', '10', '20', '50'],
};
// staticModal
export const staticModal = {
modalType: '',
modalShow: false,
modalData: {},
};
// 处理状态
export const mapStatus = {
1: {
label: '待办理',
value: '1',
},
2: {
label: '待整改',
value: '2',
},
3: {
label: '已办结',
value: '3',
},
4: {
label: '已整改',
value: '4',
},
};
// 风险类型
export const mapRiskType = {
1: {
label: '涉黄',
value: '1',
},
2: {
label: '涉非',
value: '2',
},
3: {
label: '涉政',
value: '3',
},
};
// 风险源类型
export const mapRiskSourceType = {
1: {
label: '文本',
value: '1',
},
2: {
label: '图片',
value: '2',
},
3: {
label: '音频',
value: '3',
},
4: {
label: '视频',
value: '4',
},
};
// 事件类型
export const mapEventType = {
1: {
label: '网吧',
value: '1',
},
2: {
label: '出版物',
value: '2',
},
3: {
label: '网络文化',
value: '3',
},
4: {
label: '印刷',
value: '4',
},
5: {
label: '电影',
value: '5',
},
6: {
label: '广电',
value: '6',
},
7: {
label: '互联网视听',
value: '7',
},
};
// 事件等级
export const mapEventLevel = {
1: {
label: '轻微',
value: '1',
},
2: {
label: '一般',
value: '2',
},
3: {
label: '严重',
value: '3',
},
};
// 风险标签
export const enumRiskLabel = [
{
label: '涉黄',
value: '1',
},
{
label: '涉非',
value: '2',
},
{
label: '涉政',
value: '3',
},
];
// 事件状态
export const mapEventStatus = {
1: {
label: '发起中',
value: '1',
},
2: {
label: '处理中',
value: '2',
},
3: {
label: '协同中',
value: '3',
},
4: {
label: '已办结',
value: '4',
},
5: {
label: '已关闭',
value: '5',
},
};
// 区域
export const enumArea = [
'百丈镇',
'黄湖镇',
'鸬鸟镇',
'径山镇',
'瓶窑镇',
'良渚街道',
'仁和街道',
'仓前街道',
'余杭街道',
'中泰街道',
'闲林街道',
'五常街道',
];
// 案件原由
export const mapCause = {
1: {
label: '行政案件办理',
value: '1',
},
2: {
label: '网络行政案件',
value: '2',
},
3: {
label: '刑事案件',
value: '3',
},
4: {
label: '保护未成年人重要案件',
value: '4',
},
0: {
label: '其他',
value: '0',
},
};
export const enumYear = (startYear = 1950, endYear = +moment().format('YYYY')) => {
let year = [];
for (let i = startYear; i <= endYear; i++) {
year.push({
label: `${i} 年`,
value: i + '',
});
}
return year;
};
// 手机号正则
export const phoneReg = /^[1][3,4,5,7,8][0-9]{9}$/;
const { uniq } = require('lodash');
const RouterConfig = require('../../config/config').default.routes;
const BASE_URL = `http://localhost:${process.env.PORT || 8000}`;
function formatter(routes, parentPath = '') {
const fixedParentPath = parentPath.replace(/\/{1,}/g, '/');
let result = [];
routes.forEach(item => {
if (item.path) {
result.push(`${fixedParentPath}/${item.path}`.replace(/\/{1,}/g, '/'));
}
if (item.routes) {
result = result.concat(
formatter(item.routes, item.path ? `${fixedParentPath}/${item.path}` : parentPath),
);
}
});
return uniq(result.filter(item => !!item));
}
beforeAll(async () => {
await page.goto(`${BASE_URL}`);
await page.evaluate(() => {
localStorage.setItem('antd-pro-authority', '["admin"]');
});
});
describe('Ant Design Pro E2E test', () => {
const testPage = path => async () => {
await page.goto(`${BASE_URL}${path}`);
await page.waitForSelector('footer', {
timeout: 2000,
});
const haveFooter = await page.evaluate(
() => document.getElementsByTagName('footer').length > 0,
);
expect(haveFooter).toBeTruthy();
};
const routers = formatter(RouterConfig);
routers.forEach(route => {
it(`test pages ${route}`, testPage(route));
});
});
const BASE_URL = `http://localhost:${process.env.PORT || 8000}`;
describe('Homepage', () => {
it('topmenu should have footer', async () => {
const params = '?navTheme=light&layout=topmenu';
await page.goto(`${BASE_URL}${params}`);
await page.waitForSelector('footer', {
timeout: 2000,
});
const haveFooter = await page.evaluate(
() => document.getElementsByTagName('footer').length > 0,
);
expect(haveFooter).toBeTruthy();
});
});
import { Button, message, notification } from 'antd';
import React from 'react';
import { formatMessage } from 'umi';
import defaultSettings from '../config/defaultSettings';
const { pwa } = defaultSettings; // if pwa is true
if (pwa) {
// Notify user if offline now
window.addEventListener('sw.offline', () => {
message.warning(
formatMessage({
id: 'app.pwa.offline',
}),
);
}); // Pop up a prompt on the page asking the user if they want to use the latest version
window.addEventListener('sw.updated', event => {
const e = event;
const reloadSW = async () => {
// Check if there is sw whose state is waiting in ServiceWorkerRegistration
// https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerRegistration
const worker = e.detail && e.detail.waiting;
if (!worker) {
return true;
} // Send skip-waiting event to waiting SW with MessageChannel
await new Promise((resolve, reject) => {
const channel = new MessageChannel();
channel.port1.onmessage = msgEvent => {
if (msgEvent.data.error) {
reject(msgEvent.data.error);
} else {
resolve(msgEvent.data);
}
};
worker.postMessage(
{
type: 'skip-waiting',
},
[channel.port2],
);
}); // Refresh current page to use the updated HTML and other assets after SW has skiped waiting
window.location.reload(true);
return true;
};
const key = `open${Date.now()}`;
const btn = (
<Button
type="primary"
onClick={() => {
notification.close(key);
reloadSW();
}}
>
{formatMessage({
id: 'app.pwa.serviceworker.updated.ok',
})}
</Button>
);
notification.open({
message: formatMessage({
id: 'app.pwa.serviceworker.updated',
}),
description: formatMessage({
id: 'app.pwa.serviceworker.updated.hint',
}),
btn,
key,
onClose: async () => {},
});
});
} else if ('serviceWorker' in navigator) {
// unregister service worker
const { serviceWorker } = navigator;
if (serviceWorker.getRegistrations) {
serviceWorker.getRegistrations().then(sws => {
sws.forEach(sw => {
sw.unregister();
});
});
}
serviceWorker.getRegistration().then(sw => {
if (sw) sw.unregister();
}); // remove all caches
if (window.caches && window.caches.keys) {
caches.keys().then(keys => {
keys.forEach(key => {
caches.delete(key);
});
});
}
}
@import '~antd/es/style/themes/default.less';
html,
body,
#root {
height: 100%;
overflow: hidden;
}
.colorWeak {
filter: invert(80%);
}
.ant-layout {
min-height: 100vh;
}
canvas {
display: block;
}
body {
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
/* 浏览器滚动条相关 */
::-webkit-scrollbar-thumb {
height: 50px;
background: rgba(160, 160, 160, 0.8);
border-radius: 3px;
transition: background 1s;
&:hover {
background: rgba(160, 160, 160, 1);
}
}
::-webkit-scrollbar {
width: 4px;
height: 4px;
}
ul,
ol {
list-style: none;
}
@media (max-width: @screen-xs) {
.ant-table {
width: 100%;
overflow-x: auto;
&-thead > tr,
&-tbody > tr {
> th,
> td {
white-space: pre;
> span {
display: block;
}
}
}
}
}
// 兼容IE11
@media screen and(-ms-high-contrast: active), (-ms-high-contrast: none) {
body .ant-design-pro > .ant-layout {
min-height: 100vh;
}
}
.mt-5 {
margin-top: 5px !important;
}
.mt-10 {
margin-top: 10px !important;
}
.mt-15 {
margin-top: 15px !important;
}
.mt-20 {
margin-top: 20px !important;
}
.mt-24 {
margin-top: 24px !important;
}
.ml-5 {
margin-left: 5px !important;
}
.ml-10 {
margin-left: 10px !important;
}
.ml-15 {
margin-left: 15px !important;
}
.ml-20 {
margin-left: 20px !important;
}
.mr-5 {
margin-right: 5px !important;
}
.mr-10 {
margin-right: 10px !important;
}
.mr-15 {
margin-right: 15px !important;
}
.mr-20 {
margin-right: 20px !important;
}
.mr-30 {
margin-right: 30px !important;
}
.mb-5 {
margin-bottom: 5px !important;
}
.mb-10 {
margin-bottom: 10px !important;
}
.mb-15 {
margin-bottom: 15px !important;
}
.mb-20 {
margin-bottom: 20px !important;
}
.tn-l {
text-align: left;
}
.tn-c {
text-align: center;
}
.tn-r {
text-align: right;
}
.ft-24 {
font-size: 24px;
}
.color-gray {
color: rgba(0, 0, 0, 0.35);
}
.color-primary {
color: @primary-color !important;
}
// 清除上下浮动
.clearfix {
&::before,
&::after {
content: ' ';
display: table;
}
&::after {
clear: both;
}
}
.left {
float: left;
}
.right {
float: right;
}
.fn-hide {
display: none !important;
}
// 单行字体溢出时显示省略号
.ellipsis {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.pointer {
cursor: pointer;
&:hover {
color: #40a9ff;
}
}
.wflex_r_s {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
}
.c9 {
color: #999 !important;
}
:global {
.ant-table-wrapper {
.ant-table-pagination.ant-pagination {
margin-top: 24px;
margin-bottom: 0px;
justify-content: flex-end;
}
}
.ant-pro-basicLayout-content {
margin: 0px;
}
.ant-tooltip-inner {
.anticon {
margin-right: 6px;
}
}
}
/* 权限相关样式 */
.view-tree-auth {
:global {
.ant-tree {
position: relative;
top: 3px;
}
.info-tree {
.ant-tree-list {
.ant-tree-treenode {
.ant-tree-node-content-wrapper {
.ant-tree-title {
span {
display: none;
&:hover {
opacity: 0.8;
}
&:first-child {
color: #185da2;
}
&:last-child {
color: #666;
}
}
&:hover {
span {
display: inline-block;
}
}
}
}
}
}
}
}
}
.box-info {
min-height: calc(~'100vh - 112px');
:global {
.ant-card {
min-height: calc(~'100vh - 195px');
margin-bottom: 70px;
.footer-btn {
position: fixed;
bottom: 0;
left: 0;
z-index: 10;
width: 100%;
height: 56px;
line-height: 56px;
margin-bottom: 0;
text-align: center;
background-color: #fff;
box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.03);
}
}
}
}
/**
* Ant Design Pro v4 use `@ant-design/pro-layout` to handle Layout.
* You can view component api by:
* https://github.com/ant-design/ant-design-pro-layout
*/
import ProLayout from '@ant-design/pro-layout';
import { Link, connect, useIntl, history } from 'umi';
import React from 'react';
import RightContent from '@/components/GlobalHeader/RightContent';
import AuthRouter from '@/components/Auth/AuthRouter';
import PageCommons from '@/components/PageCommons';
import YH from '@/assets/yh-logo.png';
import {
DesktopOutlined,
WarningOutlined,
SelectOutlined,
BarsOutlined,
SettingOutlined,
} from '@ant-design/icons';
import { StyledTitle } from '@/components/style';
import { codeMappingForResource } from '../../config/routerConfig';
const IconMap = {
screen: <DesktopOutlined />,
risk: <WarningOutlined />,
event: <SelectOutlined />,
data: <BarsOutlined />,
system: <SettingOutlined />,
};
const BasicLayout = props => {
const intl = useIntl();
const { formatMessage } = intl;
const {
dispatch,
children,
settings,
location: { pathname },
loading,
urlFileExport,
currentUser: { menus },
} = props;
const handleMenuCollapse = payload => {
if (dispatch) {
dispatch({
type: 'global/changeLayoutCollapsed',
payload,
});
}
};
// 重定向处理
// const isRedirect = pathname === '/';
// if (isRedirect && dataMenus.length) {
// history.replace({ pathname: dataMenus[0].path });
// };
// 菜单 loop
const loopMenuItem = menus => {
// console.log(menus, 'menus', codeMappingForResource);
return menus?.map(({ icon, children, ...item }) => ({
...(codeMappingForResource.get(item.resourceCode) || {}),
icon: icon && IconMap[icon],
routes: children && loopMenuItem(children),
}));
};
return (
<ProLayout
formatMessage={formatMessage}
menuHeaderRender={(logoDom, titleDom) => (
<Link to="/">
<img src={YH} />
<StyledTitle>{titleDom}</StyledTitle>
</Link>
)}
onCollapse={handleMenuCollapse}
menuItemRender={(menuItemProps, defaultDom) => {
if (menuItemProps.isUrl || menuItemProps.children || !menuItemProps.path) {
return defaultDom;
}
return (
<Link to={menuItemProps.path}>
{IconMap[menuItemProps.icons]}
{defaultDom}
</Link>
);
}}
subMenuItemRender={subProps => {
return (
<span className="ant-pro-menu-item">
{IconMap[subProps.icons]}
<span>{subProps.name}</span>
</span>
);
}}
menuDataRender={() => loopMenuItem(menus)}
rightContentRender={() => <RightContent />}
{...props}
{...settings}
>
{/* 路由权限 */}
<AuthRouter path={pathname}>{children}</AuthRouter>
<PageCommons loading={loading} urlFileExport={urlFileExport} dispatch={dispatch} />
</ProLayout>
);
};
export default connect(({ global, settings, user }) => ({
settings,
loading: global.loading,
collapsed: global.collapsed,
urlFileExport: global.urlFileExport,
currentUser: user.currentUser,
}))(BasicLayout);
import React from 'react';
const Layout = ({ children }) => <>{children}</>;
export default Layout;
import { connect } from 'umi';
import React from 'react';
import styles from './UserLayout.less';
const UserLayout = props => {
const { children } = props;
return (
<>
<div className={styles.container}>
<div className={styles.bgTop}>
<div>一网打“净”数智在线</div>
</div>
<div className={styles.content}>
<div className={styles.login}>{children}</div>
</div>
</div>
</>
);
};
export default connect(({ settings, user }) => ({
...settings,
currentUser: user.currentUser,
}))(UserLayout);
@import '~antd/es/style/themes/default.less';
.container {
width: 100%;
display: flex;
flex-direction: column;
height: 100vh;
overflow: auto;
position: relative;
background-color: #F5F5F5;
.bgTop {
background-color: @primary-color;
height: 400px;
div {
color: #fff;
text-align: center;
font-size: 22px;
position: absolute;
top: 170px;
left: 50%;
letter-spacing: 2px;
transform: translateX(-50%);
}
}
.content {
position: absolute;
background-color: #fff;
border-radius: 2px;
top: 220px;
left: 50%;
z-index: 2;
transform: translateX(-50%);
.login {
width: 400px;
box-shadow: 0 4px 50px 0 rgba(121,173,201,.17);
}
}
}
import component from './en-US/component';
import globalHeader from './en-US/globalHeader';
import menu from './en-US/menu';
import pwa from './en-US/pwa';
import settingDrawer from './en-US/settingDrawer';
import settings from './en-US/settings';
export default {
'navBar.lang': 'Languages',
'layout.user.link.help': 'Help',
'layout.user.link.privacy': 'Privacy',
'layout.user.link.terms': 'Terms',
'app.preview.down.block': 'Download this page to your local project',
'app.welcome.link.fetch-blocks': 'Get all block',
'app.welcome.link.block-list': 'Quickly build standard, pages based on `block` development',
...globalHeader,
...menu,
...settingDrawer,
...settings,
...pwa,
...component,
};
export default {
'component.tagSelect.expand': 'Expand',
'component.tagSelect.collapse': 'Collapse',
'component.tagSelect.all': 'All',
};
export default {
'component.globalHeader.search': 'Search',
'component.globalHeader.search.example1': 'Search example 1',
'component.globalHeader.search.example2': 'Search example 2',
'component.globalHeader.search.example3': 'Search example 3',
'component.globalHeader.help': 'Help',
'component.globalHeader.notification': 'Notification',
'component.globalHeader.notification.empty': 'You have viewed all notifications.',
'component.globalHeader.message': 'Message',
'component.globalHeader.message.empty': 'You have viewed all messsages.',
'component.globalHeader.event': 'Event',
'component.globalHeader.event.empty': 'You have viewed all events.',
'component.noticeIcon.clear': 'Clear',
'component.noticeIcon.cleared': 'Cleared',
'component.noticeIcon.empty': 'No notifications',
'component.noticeIcon.view-more': 'View more',
};
export default {
'menu.welcome': 'Welcome',
'menu.more-blocks': 'More Blocks',
'menu.home': 'Home',
'menu.admin': 'Admin',
'menu.admin.sub-page': 'Sub-Page',
'menu.login': 'Login',
'menu.register': 'Register',
'menu.register.result': 'Register Result',
'menu.dashboard': 'Dashboard',
'menu.dashboard.analysis': 'Analysis',
'menu.dashboard.monitor': 'Monitor',
'menu.dashboard.workplace': 'Workplace',
'menu.exception.403': '403',
'menu.exception.404': '404',
'menu.exception.500': '500',
'menu.form': 'Form',
'menu.form.basic-form': 'Basic Form',
'menu.form.step-form': 'Step Form',
'menu.form.step-form.info': 'Step Form(write transfer information)',
'menu.form.step-form.confirm': 'Step Form(confirm transfer information)',
'menu.form.step-form.result': 'Step Form(finished)',
'menu.form.advanced-form': 'Advanced Form',
'menu.list': 'List',
'menu.list.table-list': 'Search Table',
'menu.list.basic-list': 'Basic List',
'menu.list.card-list': 'Card List',
'menu.list.search-list': 'Search List',
'menu.list.search-list.articles': 'Search List(articles)',
'menu.list.search-list.projects': 'Search List(projects)',
'menu.list.search-list.applications': 'Search List(applications)',
'menu.profile': 'Profile',
'menu.profile.basic': 'Basic Profile',
'menu.profile.advanced': 'Advanced Profile',
'menu.result': 'Result',
'menu.result.success': 'Success',
'menu.result.fail': 'Fail',
'menu.exception': 'Exception',
'menu.exception.not-permission': '403',
'menu.exception.not-find': '404',
'menu.exception.server-error': '500',
'menu.exception.trigger': 'Trigger',
'menu.account': 'Account',
'menu.account.center': 'Account Center',
'menu.account.settings': 'Account Settings',
'menu.account.trigger': 'Trigger Error',
'menu.account.logout': 'Logout',
'menu.editor': 'Graphic Editor',
'menu.editor.flow': 'Flow Editor',
'menu.editor.mind': 'Mind Editor',
'menu.editor.koni': 'Koni Editor',
};
export default {
'app.pwa.offline': 'You are offline now',
'app.pwa.serviceworker.updated': 'New content is available',
'app.pwa.serviceworker.updated.hint': 'Please press the "Refresh" button to reload current page',
'app.pwa.serviceworker.updated.ok': 'Refresh',
};
export default {
'app.setting.pagestyle': 'Page style setting',
'app.setting.pagestyle.dark': 'Dark style',
'app.setting.pagestyle.light': 'Light style',
'app.setting.content-width': 'Content Width',
'app.setting.content-width.fixed': 'Fixed',
'app.setting.content-width.fluid': 'Fluid',
'app.setting.themecolor': 'Theme Color',
'app.setting.themecolor.dust': 'Dust Red',
'app.setting.themecolor.volcano': 'Volcano',
'app.setting.themecolor.sunset': 'Sunset Orange',
'app.setting.themecolor.cyan': 'Cyan',
'app.setting.themecolor.green': 'Polar Green',
'app.setting.themecolor.daybreak': 'Daybreak Blue (default)',
'app.setting.themecolor.geekblue': 'Geek Glue',
'app.setting.themecolor.purple': 'Golden Purple',
'app.setting.navigationmode': 'Navigation Mode',
'app.setting.sidemenu': 'Side Menu Layout',
'app.setting.topmenu': 'Top Menu Layout',
'app.setting.fixedheader': 'Fixed Header',
'app.setting.fixedsidebar': 'Fixed Sidebar',
'app.setting.fixedsidebar.hint': 'Works on Side Menu Layout',
'app.setting.hideheader': 'Hidden Header when scrolling',
'app.setting.hideheader.hint': 'Works when Hidden Header is enabled',
'app.setting.othersettings': 'Other Settings',
'app.setting.weakmode': 'Weak Mode',
'app.setting.copy': 'Copy Setting',
'app.setting.copyinfo': 'copy success,please replace defaultSettings in src/models/setting.js',
'app.setting.production.hint':
'Setting panel shows in development environment only, please manually modify',
};
export default {
'app.settings.menuMap.basic': 'Basic Settings',
'app.settings.menuMap.security': 'Security Settings',
'app.settings.menuMap.binding': 'Account Binding',
'app.settings.menuMap.notification': 'New Message Notification',
'app.settings.basic.avatar': 'Avatar',
'app.settings.basic.change-avatar': 'Change avatar',
'app.settings.basic.email': 'Email',
'app.settings.basic.email-message': 'Please input your email!',
'app.settings.basic.nickname': 'Nickname',
'app.settings.basic.nickname-message': 'Please input your Nickname!',
'app.settings.basic.profile': 'Personal profile',
'app.settings.basic.profile-message': 'Please input your personal profile!',
'app.settings.basic.profile-placeholder': 'Brief introduction to yourself',
'app.settings.basic.country': 'Country/Region',
'app.settings.basic.country-message': 'Please input your country!',
'app.settings.basic.geographic': 'Province or city',
'app.settings.basic.geographic-message': 'Please input your geographic info!',
'app.settings.basic.address': 'Street Address',
'app.settings.basic.address-message': 'Please input your address!',
'app.settings.basic.phone': 'Phone Number',
'app.settings.basic.phone-message': 'Please input your phone!',
'app.settings.basic.update': 'Update Information',
'app.settings.security.strong': 'Strong',
'app.settings.security.medium': 'Medium',
'app.settings.security.weak': 'Weak',
'app.settings.security.password': 'Account Password',
'app.settings.security.password-description': 'Current password strength',
'app.settings.security.phone': 'Security Phone',
'app.settings.security.phone-description': 'Bound phone',
'app.settings.security.question': 'Security Question',
'app.settings.security.question-description':
'The security question is not set, and the security policy can effectively protect the account security',
'app.settings.security.email': 'Backup Email',
'app.settings.security.email-description': 'Bound Email',
'app.settings.security.mfa': 'MFA Device',
'app.settings.security.mfa-description':
'Unbound MFA device, after binding, can be confirmed twice',
'app.settings.security.modify': 'Modify',
'app.settings.security.set': 'Set',
'app.settings.security.bind': 'Bind',
'app.settings.binding.taobao': 'Binding Taobao',
'app.settings.binding.taobao-description': 'Currently unbound Taobao account',
'app.settings.binding.alipay': 'Binding Alipay',
'app.settings.binding.alipay-description': 'Currently unbound Alipay account',
'app.settings.binding.dingding': 'Binding DingTalk',
'app.settings.binding.dingding-description': 'Currently unbound DingTalk account',
'app.settings.binding.bind': 'Bind',
'app.settings.notification.password': 'Account Password',
'app.settings.notification.password-description':
'Messages from other users will be notified in the form of a station letter',
'app.settings.notification.messages': 'System Messages',
'app.settings.notification.messages-description':
'System messages will be notified in the form of a station letter',
'app.settings.notification.todo': 'To-do Notification',
'app.settings.notification.todo-description':
'The to-do list will be notified in the form of a letter from the station',
'app.settings.open': 'Open',
'app.settings.close': 'Close',
};
import component from './pt-BR/component';
import globalHeader from './pt-BR/globalHeader';
import menu from './pt-BR/menu';
import pwa from './pt-BR/pwa';
import settingDrawer from './pt-BR/settingDrawer';
import settings from './pt-BR/settings';
export default {
'navBar.lang': 'Idiomas',
'layout.user.link.help': 'ajuda',
'layout.user.link.privacy': 'política de privacidade',
'layout.user.link.terms': 'termos de serviços',
'app.preview.down.block': 'Download this page to your local project',
...globalHeader,
...menu,
...settingDrawer,
...settings,
...pwa,
...component,
};
export default {
'component.tagSelect.expand': 'Expandir',
'component.tagSelect.collapse': 'Diminuir',
'component.tagSelect.all': 'Todas',
};
export default {
'component.globalHeader.search': 'Busca',
'component.globalHeader.search.example1': 'Exemplo de busca 1',
'component.globalHeader.search.example2': 'Exemplo de busca 2',
'component.globalHeader.search.example3': 'Exemplo de busca 3',
'component.globalHeader.help': 'Ajuda',
'component.globalHeader.notification': 'Notificação',
'component.globalHeader.notification.empty': 'Você visualizou todas as notificações.',
'component.globalHeader.message': 'Mensagem',
'component.globalHeader.message.empty': 'Você visualizou todas as mensagens.',
'component.globalHeader.event': 'Evento',
'component.globalHeader.event.empty': 'Você visualizou todos os eventos.',
'component.noticeIcon.clear': 'Limpar',
'component.noticeIcon.cleared': 'Limpo',
'component.noticeIcon.empty': 'Sem notificações',
'component.noticeIcon.loaded': 'Carregado',
'component.noticeIcon.view-more': 'Veja mais',
};
export default {
'menu.welcome': 'Welcome',
'menu.more-blocks': 'More Blocks',
'menu.home': 'Início',
'menu.login': 'Login',
'menu.admin': 'Admin',
'menu.admin.sub-page': 'Sub-Page',
'menu.register': 'Registro',
'menu.register.result': 'Resultado de registro',
'menu.dashboard': 'Dashboard',
'menu.dashboard.analysis': 'Análise',
'menu.dashboard.monitor': 'Monitor',
'menu.dashboard.workplace': 'Ambiente de Trabalho',
'menu.exception.403': '403',
'menu.exception.404': '404',
'menu.exception.500': '500',
'menu.form': 'Formulário',
'menu.form.basic-form': 'Formulário Básico',
'menu.form.step-form': 'Formulário Assistido',
'menu.form.step-form.info': 'Formulário Assistido(gravar informações de transferência)',
'menu.form.step-form.confirm': 'Formulário Assistido(confirmar informações de transferência)',
'menu.form.step-form.result': 'Formulário Assistido(finalizado)',
'menu.form.advanced-form': 'Formulário Avançado',
'menu.list': 'Lista',
'menu.list.table-list': 'Tabela de Busca',
'menu.list.basic-list': 'Lista Básica',
'menu.list.card-list': 'Lista de Card',
'menu.list.search-list': 'Lista de Busca',
'menu.list.search-list.articles': 'Lista de Busca(artigos)',
'menu.list.search-list.projects': 'Lista de Busca(projetos)',
'menu.list.search-list.applications': 'Lista de Busca(aplicações)',
'menu.profile': 'Perfil',
'menu.profile.basic': 'Perfil Básico',
'menu.profile.advanced': 'Perfil Avançado',
'menu.result': 'Resultado',
'menu.result.success': 'Sucesso',
'menu.result.fail': 'Falha',
'menu.exception': 'Exceção',
'menu.exception.not-permission': '403',
'menu.exception.not-find': '404',
'menu.exception.server-error': '500',
'menu.exception.trigger': 'Disparar',
'menu.account': 'Conta',
'menu.account.center': 'Central da Conta',
'menu.account.settings': 'Configurar Conta',
'menu.account.trigger': 'Disparar Erro',
'menu.account.logout': 'Sair',
'menu.editor': 'Graphic Editor',
'menu.editor.flow': 'Flow Editor',
'menu.editor.mind': 'Mind Editor',
'menu.editor.koni': 'Koni Editor',
};
export default {
'app.pwa.offline': 'Você está offline agora',
'app.pwa.serviceworker.updated': 'Novo conteúdo está disponível',
'app.pwa.serviceworker.updated.hint':
'Por favor, pressione o botão "Atualizar" para recarregar a página atual',
'app.pwa.serviceworker.updated.ok': 'Atualizar',
};
export default {
'app.setting.pagestyle': 'Configuração de estilo da página',
'app.setting.pagestyle.dark': 'Dark style',
'app.setting.pagestyle.light': 'Light style',
'app.setting.content-width': 'Largura do conteúdo',
'app.setting.content-width.fixed': 'Fixo',
'app.setting.content-width.fluid': 'Fluido',
'app.setting.themecolor': 'Cor do Tema',
'app.setting.themecolor.dust': 'Dust Red',
'app.setting.themecolor.volcano': 'Volcano',
'app.setting.themecolor.sunset': 'Sunset Orange',
'app.setting.themecolor.cyan': 'Cyan',
'app.setting.themecolor.green': 'Polar Green',
'app.setting.themecolor.daybreak': 'Daybreak Blue (default)',
'app.setting.themecolor.geekblue': 'Geek Glue',
'app.setting.themecolor.purple': 'Golden Purple',
'app.setting.navigationmode': 'Modo de Navegação',
'app.setting.sidemenu': 'Layout do Menu Lateral',
'app.setting.topmenu': 'Layout do Menu Superior',
'app.setting.fixedheader': 'Cabeçalho fixo',
'app.setting.fixedsidebar': 'Barra lateral fixa',
'app.setting.fixedsidebar.hint': 'Funciona no layout do menu lateral',
'app.setting.hideheader': 'Esconder o cabeçalho quando rolar',
'app.setting.hideheader.hint': 'Funciona quando o esconder cabeçalho está abilitado',
'app.setting.othersettings': 'Outras configurações',
'app.setting.weakmode': 'Weak Mode',
'app.setting.copy': 'Copiar Configuração',
'app.setting.copyinfo':
'copiado com sucesso,por favor trocar o defaultSettings em src/models/setting.js',
'app.setting.production.hint':
'O painel de configuração apenas é exibido no ambiente de desenvolvimento, por favor modifique manualmente o',
};
export default {
'app.settings.menuMap.basic': 'Configurações Básicas',
'app.settings.menuMap.security': 'Configurações de Segurança',
'app.settings.menuMap.binding': 'Vinculação de Conta',
'app.settings.menuMap.notification': 'Mensagens de Notificação',
'app.settings.basic.avatar': 'Avatar',
'app.settings.basic.change-avatar': 'Alterar avatar',
'app.settings.basic.email': 'Email',
'app.settings.basic.email-message': 'Por favor insira seu email!',
'app.settings.basic.nickname': 'Nome de usuário',
'app.settings.basic.nickname-message': 'Por favor insira seu nome de usuário!',
'app.settings.basic.profile': 'Perfil pessoal',
'app.settings.basic.profile-message': 'Por favor insira seu perfil pessoal!',
'app.settings.basic.profile-placeholder': 'Breve introdução sua',
'app.settings.basic.country': 'País/Região',
'app.settings.basic.country-message': 'Por favor insira país!',
'app.settings.basic.geographic': 'Província, estado ou cidade',
'app.settings.basic.geographic-message': 'Por favor insira suas informações geográficas!',
'app.settings.basic.address': 'Endereço',
'app.settings.basic.address-message': 'Por favor insira seu endereço!',
'app.settings.basic.phone': 'Número de telefone',
'app.settings.basic.phone-message': 'Por favor insira seu número de telefone!',
'app.settings.basic.update': 'Atualizar Informações',
'app.settings.security.strong': 'Forte',
'app.settings.security.medium': 'Média',
'app.settings.security.weak': 'Fraca',
'app.settings.security.password': 'Senha da Conta',
'app.settings.security.password-description': 'Força da senha',
'app.settings.security.phone': 'Telefone de Seguraça',
'app.settings.security.phone-description': 'Telefone vinculado',
'app.settings.security.question': 'Pergunta de Segurança',
'app.settings.security.question-description':
'A pergunta de segurança não está definida e a política de segurança pode proteger efetivamente a segurança da conta',
'app.settings.security.email': 'Email de Backup',
'app.settings.security.email-description': 'Email vinculado',
'app.settings.security.mfa': 'Dispositivo MFA',
'app.settings.security.mfa-description':
'O dispositivo MFA não vinculado, após a vinculação, pode ser confirmado duas vezes',
'app.settings.security.modify': 'Modificar',
'app.settings.security.set': 'Atribuir',
'app.settings.security.bind': 'Vincular',
'app.settings.binding.taobao': 'Vincular Taobao',
'app.settings.binding.taobao-description': 'Atualmente não vinculado à conta Taobao',
'app.settings.binding.alipay': 'Vincular Alipay',
'app.settings.binding.alipay-description': 'Atualmente não vinculado à conta Alipay',
'app.settings.binding.dingding': 'Vincular DingTalk',
'app.settings.binding.dingding-description': 'Atualmente não vinculado à conta DingTalk',
'app.settings.binding.bind': 'Vincular',
'app.settings.notification.password': 'Senha da Conta',
'app.settings.notification.password-description':
'Mensagens de outros usuários serão notificadas na forma de uma estação de letra',
'app.settings.notification.messages': 'Mensagens de Sistema',
'app.settings.notification.messages-description':
'Mensagens de sistema serão notificadas na forma de uma estação de letra',
'app.settings.notification.todo': 'Notificação de To-do',
'app.settings.notification.todo-description':
'A lista de to-do será notificada na forma de uma estação de letra',
'app.settings.open': 'Aberto',
'app.settings.close': 'Fechado',
};
import component from './zh-CN/component';
import globalHeader from './zh-CN/globalHeader';
import menu from './zh-CN/menu';
import pwa from './zh-CN/pwa';
import settingDrawer from './zh-CN/settingDrawer';
import settings from './zh-CN/settings';
export default {
'navBar.lang': '语言',
'layout.user.link.help': '帮助',
'layout.user.link.privacy': '隐私',
'layout.user.link.terms': '条款',
'app.preview.down.block': '下载此页面到本地项目',
'app.welcome.link.fetch-blocks': '获取全部区块',
'app.welcome.link.block-list': '基于 block 开发,快速构建标准页面',
...globalHeader,
...menu,
...settingDrawer,
...settings,
...pwa,
...component,
};
export default {
'component.tagSelect.expand': '展开',
'component.tagSelect.collapse': '收起',
'component.tagSelect.all': '全部',
};
export default {
'component.globalHeader.search': '站内搜索',
'component.globalHeader.search.example1': '搜索提示一',
'component.globalHeader.search.example2': '搜索提示二',
'component.globalHeader.search.example3': '搜索提示三',
'component.globalHeader.help': '使用文档',
'component.globalHeader.notification': '通知',
'component.globalHeader.notification.empty': '你已查看所有通知',
'component.globalHeader.message': '消息',
'component.globalHeader.message.empty': '您已读完所有消息',
'component.globalHeader.event': '待办',
'component.globalHeader.event.empty': '你已完成所有待办',
'component.noticeIcon.clear': '清空',
'component.noticeIcon.cleared': '清空了',
'component.noticeIcon.empty': '暂无数据',
'component.noticeIcon.view-more': '查看更多',
};
export default {
'menu.appliction': '应用列表',
'menu.info': '应用详情',
'menu.login': '登录',
'menu.risk': '风险管理',
'menu.event': '事件管理',
'menu.data': '数据管理',
'menu.system': '系统管理',
'menu.system.account': '账号管理',
'menu.system.role': '角色管理',
'menu.system.menu': '菜单管理',
'menu.dataScreen': '数智大屏',
};
export default {
'app.pwa.offline': '当前处于离线状态',
'app.pwa.serviceworker.updated': '有新内容',
'app.pwa.serviceworker.updated.hint': '请点击“刷新”按钮或者手动刷新页面',
'app.pwa.serviceworker.updated.ok': '刷新',
};
export default {
'app.setting.pagestyle': '整体风格设置',
'app.setting.pagestyle.dark': '暗色菜单风格',
'app.setting.pagestyle.light': '亮色菜单风格',
'app.setting.content-width': '内容区域宽度',
'app.setting.content-width.fixed': '定宽',
'app.setting.content-width.fluid': '流式',
'app.setting.themecolor': '主题色',
'app.setting.themecolor.dust': '薄暮',
'app.setting.themecolor.volcano': '火山',
'app.setting.themecolor.sunset': '日暮',
'app.setting.themecolor.cyan': '明青',
'app.setting.themecolor.green': '极光绿',
'app.setting.themecolor.daybreak': '拂晓蓝(默认)',
'app.setting.themecolor.geekblue': '极客蓝',
'app.setting.themecolor.purple': '酱紫',
'app.setting.navigationmode': '导航模式',
'app.setting.sidemenu': '侧边菜单布局',
'app.setting.topmenu': '顶部菜单布局',
'app.setting.fixedheader': '固定 Header',
'app.setting.fixedsidebar': '固定侧边菜单',
'app.setting.fixedsidebar.hint': '侧边菜单布局时可配置',
'app.setting.hideheader': '下滑时隐藏 Header',
'app.setting.hideheader.hint': '固定 Header 时可配置',
'app.setting.othersettings': '其他设置',
'app.setting.weakmode': '色弱模式',
'app.setting.copy': '拷贝设置',
'app.setting.copyinfo': '拷贝成功,请到 src/defaultSettings.js 中替换默认配置',
'app.setting.production.hint':
'配置栏只在开发环境用于预览,生产环境不会展现,请拷贝后手动修改配置文件',
};
export default {
'app.settings.menuMap.basic': '基本设置',
'app.settings.menuMap.security': '安全设置',
'app.settings.menuMap.binding': '账号绑定',
'app.settings.menuMap.notification': '新消息通知',
'app.settings.basic.avatar': '头像',
'app.settings.basic.change-avatar': '更换头像',
'app.settings.basic.email': '邮箱',
'app.settings.basic.email-message': '请输入您的邮箱!',
'app.settings.basic.nickname': '昵称',
'app.settings.basic.nickname-message': '请输入您的昵称!',
'app.settings.basic.profile': '个人简介',
'app.settings.basic.profile-message': '请输入个人简介!',
'app.settings.basic.profile-placeholder': '个人简介',
'app.settings.basic.country': '国家/地区',
'app.settings.basic.country-message': '请输入您的国家或地区!',
'app.settings.basic.geographic': '所在省市',
'app.settings.basic.geographic-message': '请输入您的所在省市!',
'app.settings.basic.address': '街道地址',
'app.settings.basic.address-message': '请输入您的街道地址!',
'app.settings.basic.phone': '联系电话',
'app.settings.basic.phone-message': '请输入您的联系电话!',
'app.settings.basic.update': '更新基本信息',
'app.settings.security.strong': '强',
'app.settings.security.medium': '中',
'app.settings.security.weak': '弱',
'app.settings.security.password': '账户密码',
'app.settings.security.password-description': '当前密码强度',
'app.settings.security.phone': '密保手机',
'app.settings.security.phone-description': '已绑定手机',
'app.settings.security.question': '密保问题',
'app.settings.security.question-description': '未设置密保问题,密保问题可有效保护账户安全',
'app.settings.security.email': '备用邮箱',
'app.settings.security.email-description': '已绑定邮箱',
'app.settings.security.mfa': 'MFA 设备',
'app.settings.security.mfa-description': '未绑定 MFA 设备,绑定后,可以进行二次确认',
'app.settings.security.modify': '修改',
'app.settings.security.set': '设置',
'app.settings.security.bind': '绑定',
'app.settings.binding.taobao': '绑定淘宝',
'app.settings.binding.taobao-description': '当前未绑定淘宝账号',
'app.settings.binding.alipay': '绑定支付宝',
'app.settings.binding.alipay-description': '当前未绑定支付宝账号',
'app.settings.binding.dingding': '绑定钉钉',
'app.settings.binding.dingding-description': '当前未绑定钉钉账号',
'app.settings.binding.bind': '绑定',
'app.settings.notification.password': '账户密码',
'app.settings.notification.password-description': '其他用户的消息将以站内信的形式通知',
'app.settings.notification.messages': '系统消息',
'app.settings.notification.messages-description': '系统消息将以站内信的形式通知',
'app.settings.notification.todo': '待办任务',
'app.settings.notification.todo-description': '待办任务将以站内信的形式通知',
'app.settings.open': '开',
'app.settings.close': '关',
};
import component from './zh-TW/component';
import globalHeader from './zh-TW/globalHeader';
import menu from './zh-TW/menu';
import pwa from './zh-TW/pwa';
import settingDrawer from './zh-TW/settingDrawer';
import settings from './zh-TW/settings';
export default {
'navBar.lang': '語言',
'layout.user.link.help': '幫助',
'layout.user.link.privacy': '隱私',
'layout.user.link.terms': '條款',
'app.preview.down.block': '下載此頁面到本地項目',
...globalHeader,
...menu,
...settingDrawer,
...settings,
...pwa,
...component,
};
export default {
'component.tagSelect.expand': '展開',
'component.tagSelect.collapse': '收起',
'component.tagSelect.all': '全部',
};
export default {
'component.globalHeader.search': '站內搜索',
'component.globalHeader.search.example1': '搜索提示壹',
'component.globalHeader.search.example2': '搜索提示二',
'component.globalHeader.search.example3': '搜索提示三',
'component.globalHeader.help': '使用手冊',
'component.globalHeader.notification': '通知',
'component.globalHeader.notification.empty': '妳已查看所有通知',
'component.globalHeader.message': '消息',
'component.globalHeader.message.empty': '您已讀完所有消息',
'component.globalHeader.event': '待辦',
'component.globalHeader.event.empty': '妳已完成所有待辦',
'component.noticeIcon.clear': '清空',
'component.noticeIcon.cleared': '清空了',
'component.noticeIcon.empty': '暫無資料',
'component.noticeIcon.view-more': '查看更多',
};
export default {
'menu.welcome': '歡迎',
'menu.more-blocks': '更多區塊',
'menu.home': '首頁',
'menu.login': '登錄',
'menu.admin': '权限',
'menu.admin.sub-page': '二级管理页',
'menu.exception.403': '403',
'menu.exception.404': '404',
'menu.exception.500': '500',
'menu.register': '註冊',
'menu.register.result': '註冊結果',
'menu.dashboard': 'Dashboard',
'menu.dashboard.analysis': '分析頁',
'menu.dashboard.monitor': '監控頁',
'menu.dashboard.workplace': '工作臺',
'menu.form': '表單頁',
'menu.form.basic-form': '基礎表單',
'menu.form.step-form': '分步表單',
'menu.form.step-form.info': '分步表單(填寫轉賬信息)',
'menu.form.step-form.confirm': '分步表單(確認轉賬信息)',
'menu.form.step-form.result': '分步表單(完成)',
'menu.form.advanced-form': '高級表單',
'menu.list': '列表頁',
'menu.list.table-list': '查詢表格',
'menu.list.basic-list': '標淮列表',
'menu.list.card-list': '卡片列表',
'menu.list.search-list': '搜索列表',
'menu.list.search-list.articles': '搜索列表(文章)',
'menu.list.search-list.projects': '搜索列表(項目)',
'menu.list.search-list.applications': '搜索列表(應用)',
'menu.profile': '詳情頁',
'menu.profile.basic': '基礎詳情頁',
'menu.profile.advanced': '高級詳情頁',
'menu.result': '結果頁',
'menu.result.success': '成功頁',
'menu.result.fail': '失敗頁',
'menu.account': '個人頁',
'menu.account.center': '個人中心',
'menu.account.settings': '個人設置',
'menu.account.trigger': '觸發報錯',
'menu.account.logout': '退出登錄',
'menu.exception': '异常页',
'menu.exception.not-permission': '403',
'menu.exception.not-find': '404',
'menu.exception.server-error': '500',
'menu.exception.trigger': '触发错误',
'menu.editor': '圖形編輯器',
'menu.editor.flow': '流程編輯器',
'menu.editor.mind': '腦圖編輯器',
'menu.editor.koni': '拓撲編輯器',
};
export default {
'app.pwa.offline': '當前處於離線狀態',
'app.pwa.serviceworker.updated': '有新內容',
'app.pwa.serviceworker.updated.hint': '請點擊“刷新”按鈕或者手動刷新頁面',
'app.pwa.serviceworker.updated.ok': '刷新',
};
export default {
'app.setting.pagestyle': '整體風格設置',
'app.setting.pagestyle.dark': '暗色菜單風格',
'app.setting.pagestyle.light': '亮色菜單風格',
'app.setting.content-width': '內容區域寬度',
'app.setting.content-width.fixed': '定寬',
'app.setting.content-width.fluid': '流式',
'app.setting.themecolor': '主題色',
'app.setting.themecolor.dust': '薄暮',
'app.setting.themecolor.volcano': '火山',
'app.setting.themecolor.sunset': '日暮',
'app.setting.themecolor.cyan': '明青',
'app.setting.themecolor.green': '極光綠',
'app.setting.themecolor.daybreak': '拂曉藍(默認)',
'app.setting.themecolor.geekblue': '極客藍',
'app.setting.themecolor.purple': '醬紫',
'app.setting.navigationmode': '導航模式',
'app.setting.sidemenu': '側邊菜單布局',
'app.setting.topmenu': '頂部菜單布局',
'app.setting.fixedheader': '固定 Header',
'app.setting.fixedsidebar': '固定側邊菜單',
'app.setting.fixedsidebar.hint': '側邊菜單布局時可配置',
'app.setting.hideheader': '下滑時隱藏 Header',
'app.setting.hideheader.hint': '固定 Header 時可配置',
'app.setting.othersettings': '其他設置',
'app.setting.weakmode': '色弱模式',
'app.setting.copy': '拷貝設置',
'app.setting.copyinfo': '拷貝成功,請到 src/defaultSettings.js 中替換默認配置',
'app.setting.production.hint':
'配置欄只在開發環境用於預覽,生產環境不會展現,請拷貝後手動修改配置文件',
};
export default {
'app.settings.menuMap.basic': '基本設置',
'app.settings.menuMap.security': '安全設置',
'app.settings.menuMap.binding': '賬號綁定',
'app.settings.menuMap.notification': '新消息通知',
'app.settings.basic.avatar': '頭像',
'app.settings.basic.change-avatar': '更換頭像',
'app.settings.basic.email': '郵箱',
'app.settings.basic.email-message': '請輸入您的郵箱!',
'app.settings.basic.nickname': '昵稱',
'app.settings.basic.nickname-message': '請輸入您的昵稱!',
'app.settings.basic.profile': '個人簡介',
'app.settings.basic.profile-message': '請輸入個人簡介!',
'app.settings.basic.profile-placeholder': '個人簡介',
'app.settings.basic.country': '國家/地區',
'app.settings.basic.country-message': '請輸入您的國家或地區!',
'app.settings.basic.geographic': '所在省市',
'app.settings.basic.geographic-message': '請輸入您的所在省市!',
'app.settings.basic.address': '街道地址',
'app.settings.basic.address-message': '請輸入您的街道地址!',
'app.settings.basic.phone': '聯系電話',
'app.settings.basic.phone-message': '請輸入您的聯系電話!',
'app.settings.basic.update': '更新基本信息',
'app.settings.security.strong': '強',
'app.settings.security.medium': '中',
'app.settings.security.weak': '弱',
'app.settings.security.password': '賬戶密碼',
'app.settings.security.password-description': '當前密碼強度',
'app.settings.security.phone': '密保手機',
'app.settings.security.phone-description': '已綁定手機',
'app.settings.security.question': '密保問題',
'app.settings.security.question-description': '未設置密保問題,密保問題可有效保護賬戶安全',
'app.settings.security.email': '備用郵箱',
'app.settings.security.email-description': '已綁定郵箱',
'app.settings.security.mfa': 'MFA 設備',
'app.settings.security.mfa-description': '未綁定 MFA 設備,綁定後,可以進行二次確認',
'app.settings.security.modify': '修改',
'app.settings.security.set': '設置',
'app.settings.security.bind': '綁定',
'app.settings.binding.taobao': '綁定淘寶',
'app.settings.binding.taobao-description': '當前未綁定淘寶賬號',
'app.settings.binding.alipay': '綁定支付寶',
'app.settings.binding.alipay-description': '當前未綁定支付寶賬號',
'app.settings.binding.dingding': '綁定釘釘',
'app.settings.binding.dingding-description': '當前未綁定釘釘賬號',
'app.settings.binding.bind': '綁定',
'app.settings.notification.password': '賬戶密碼',
'app.settings.notification.password-description': '其他用戶的消息將以站內信的形式通知',
'app.settings.notification.messages': '系統消息',
'app.settings.notification.messages-description': '系統消息將以站內信的形式通知',
'app.settings.notification.todo': '待辦任務',
'app.settings.notification.todo-description': '待辦任務將以站內信的形式通知',
'app.settings.open': '開',
'app.settings.close': '關',
};
{
"name": "Ant Design Pro",
"short_name": "Ant Design Pro",
"display": "standalone",
"start_url": "./?utm_source=homescreen",
"theme_color": "#002140",
"background_color": "#001529",
"icons": [
{
"src": "icons/icon-192x192.png",
"sizes": "192x192"
},
{
"src": "icons/icon-128x128.png",
"sizes": "128x128"
},
{
"src": "icons/icon-512x512.png",
"sizes": "512x512"
}
]
}
import { queryNotices } from '@/services/user';
const GlobalModel = {
namespace: 'global',
state: {
collapsed: false,
loading: false,
urlFileExport: '',
dataModal: {
modalType: '',
modalShow: false,
modalData: {},
},
preImgDataModal: {
modalType: '',
modalShow: false,
modalData: {},
},
},
effects: {},
reducers: {
changeLayoutCollapsed(
state = {
notices: [],
collapsed: true,
},
{ payload },
) {
return { ...state, collapsed: payload };
},
},
subscriptions: {
setup({ history, dispatch }) {
// Subscribe history(url) change, trigger `load` action if pathname is `/`
history.listen(({ pathname, search }) => {
// 路由发生变化的时候切换菜单
const whiteList = ['/user/login'];
if (!whiteList.includes(pathname)) {
// 获取用户信息
dispatch({ type: 'user/fetchCurrent' });
}
});
},
},
};
export default GlobalModel;
import { queryLogin, loginOut } from '@/services/login';
import { getUserInfo, getBosUserList } from '@/services/user';
import { message } from 'antd';
import { history } from 'umi';
import md5 from 'blueimp-md5';
import defaultSettings from '../../config/defaultSettings';
import { getDataMenus } from '@/utils/menu';
const { tokenKey, md5Key } = defaultSettings;
const Model = {
namespace: 'login',
state: {
status: undefined,
},
effects: {
//退出登录
*logout({ payload }, { call, put, select }) {
try {
const res = yield call(loginOut);
if (res.code === 0 && res.data) {
if (!payload) {
message.success('退出成功');
}
localStorage.removeItem(tokenKey);
setTimeout(() => {
window.location.href = '/user/login';
}, 500);
}
} catch (err) {
console.error(err);
}
},
//登录
*login({ payload }, { call, put }) {
try {
const res = yield call(queryLogin, payload);
if (res.code === 0) {
localStorage.setItem(tokenKey, res.data);
// const userinfo = yield call(getUserInfo);
// const { permissionVos = [] } = userinfo.data;
// const dataMenus = getDataMenus(permissionVos);
// console.log(dataMenus);
// if (dataMenus.length) {
// history.replace(dataMenus[0].path);
// } else {
// history.replace('/');
// }
history.replace('/');
message.success('登录成功');
}
} catch (err) {
console.error(err);
}
},
},
reducers: {
changeLoginStatus(state, { payload }) {
return { ...state, status: payload.status, type: payload.type };
},
},
};
export default Model;
import defaultSettings from '../../config/defaultSettings';
const updateColorWeak = colorWeak => {
const root = document.getElementById('root');
if (root) {
root.className = colorWeak ? 'colorWeak' : '';
}
};
const SettingModel = {
namespace: 'settings',
state: defaultSettings,
reducers: {
changeSetting(state = defaultSettings, { payload }) {
const { colorWeak, contentWidth } = payload;
if (state.contentWidth !== contentWidth && window.dispatchEvent) {
window.dispatchEvent(new Event('resize'));
}
updateColorWeak(!!colorWeak);
return { ...state, ...payload };
},
},
};
export default SettingModel;
import { getUserInfo, updatePassword } from '@/services/user';
import { getDataMenus } from '@/utils/menu';
import { message } from 'antd';
const UserModel = {
namespace: 'user',
state: {
currentUser: {
admin: {},
dataMenus: [],
username: '',
},
userAuths: [], // 权限
dataModal: {
modalType: '',
modalShow: false,
modalData: {},
},
},
effects: {
*fetchCurrent(_, { call, put, select }) {
const {
currentUser,
currentUser: { username },
} = yield select(state => state.user);
if (!currentUser || !username) {
try {
const res = yield call(getUserInfo);
// data.dataMenus = getDataMenus(data.permissionVos);
if (res.code === 0) {
res.data.menus = [
{
children: [],
id: 999999,
parentId: 0,
resourceCode: 'Home',
resourceNodeType: '1',
title: '数智大屏',
},
...(res.data.menus || []),
{
resourceCode: '404',
},
];
yield put({ type: 'saveCurrentUser', payload: res.data });
}
} catch (err) {
console.error(err, 'err');
}
}
},
/* 修改密码 */
*updatePassword({ payload }, { call, put, select }) {
try {
const res = yield call(updatePassword, payload);
if (res.code === 0) {
message.success('修改成功');
yield put({ type: 'cancelModal' });
yield put({ type: 'login/logout', payload: { message: 1 } });
}
} catch (err) {
console.error(err);
}
},
},
reducers: {
changeState(state, { payload }) {
return {
...state,
...payload,
};
},
cancelModal(state, { payload }) {
return {
...state,
dataModal: {
modalType: '',
modalShow: false,
modalData: {},
},
};
},
saveCurrentUser(state, { payload }) {
return {
...state,
currentUser: payload || {},
// userAuths: payload.authorities.map(_ => _.code),
};
},
},
};
export default UserModel;
import { Button, Result } from 'antd';
import React from 'react';
import { history } from 'umi';
const NoFoundPage = () => (
<Result
status="404"
title="404"
subTitle="Sorry, the page you visited does not exist."
extra={
<Button type="primary" onClick={() => history.push('/')}>
Back Home
</Button>
}
/>
);
export default NoFoundPage;
/**
* Author: llw
* Date: 2020.7.20
* Description: [应用详情]
*/
import React, { useEffect } from 'react';
import { connect, Link } from 'umi';
import { Modal, Card, Breadcrumb, Form, Input, Button, Tree, message, Spin } from 'antd';
import { IconFontConfig } from '@/common';
import { EditOutlined, DeleteOutlined, ExclamationCircleOutlined } from '@ant-design/icons';
import ModalSetAuth from './ModalSetAuth';
import { getAuthData, getAllMenuParams, getPermissionList, getDelAuthData, getDragData } from '@/utils/utils';
const { TreeNode } = Tree;
const layout = {labelCol: { span: 3 }, wrapperCol: { span: 12 }};
const ApplicationInfo = props => {
const [form] = Form.useForm();
let {
loading,
isRepeat,
dispatch,
dataModal,
oldInfo = {},
dataAuth = [],
dataMenuList = [],
dataExpandedKeys = [],
location: { query: { id } },
} = props;
/* 获取应用详情 */
useEffect(() => {
if (id) {
dispatch({type: 'Application/getApplicationInfo', payload: {id}}).then(res => {
const { name, url, code, apiDomain } = res.data || {};
form.setFieldsValue({
name, url, code, apiDomain
});
});
}
return (() => {
dispatch({type: 'Application/changeState', payload: {
dataMenuList: [],
dataAuth: [],
dataExpandedKeys: [],
dataApplicationInfo: {}
}})
})
}, []);
/* 添加权限 */
const handleAddTemp = () => {
dispatch({type: 'Application/changeState', payload: {
dataModal: {
modalType: 'AUTH_SET_MODAL',
modalShow: true,
modalData: {isEdit: false}
}
}})
};
/* 关闭弹框 */
const handleCancelModal = () => {
dispatch({type: 'Application/changeState', payload: {oldInfo: {}}});
dispatch({type: 'Application/cancelModal'});
};
/* 保存权限配置 */
const handleAuthSetOk = (values) => {
const { isEdit } = values;
const { allCode = [], allMenuName = [] } = getAllMenuParams(dataAuth);
if (oldInfo.name !== values.name && allMenuName.includes(values.name)) {
message.warning("菜单名称不能重复~")
return false;
}
if (oldInfo.code !== values.code && allCode.includes(values.code)) {
message.warning("code不能重复~")
return false;
}
/* 上述条件不成立时才会走下面生成tree */
let params = {
subPermissionList: [],
...values,
isEdit: undefined,
key: values.code,
oldCode: (oldInfo.parentCode && oldInfo.parentCode !== values.parentCode) ? oldInfo.parentCode : undefined
};
let dataSetAuth =
!isEdit
?
getAuthData(dataAuth, params)
:
getDelAuthData(dataAuth, params, 'EDIT');
const { data, dataMenu, dataExpandedKeys } = dataSetAuth;
handleCancelModal();
dispatch({type: 'Application/changeState', payload: {
dataAuth: JSON.parse(JSON.stringify(data)),
dataMenuList: JSON.parse(JSON.stringify(dataMenu)),
dataExpandedKeys: JSON.parse(JSON.stringify(dataExpandedKeys))
}});
};
// TreeNode的DOM
const renderTreeNode = (data) => {
return data.map((item) => {
if (item.subPermissionList && item.subPermissionList.length) {
return (
<TreeNode
title={
<>
{item.name}
<EditOutlined title="编辑" className="ml-10" onClick={() => handleTreeNode(item, "INFO")} />
<DeleteOutlined title="删除" className="ml-10" onClick={() => handleTreeNode(item, "DEL")} />
</>
}
key={item.code}
icon={<IconFontConfig type={(item.type === 3 || item.type === "BTN") ? "icon-btn" : "icon-menu"} style={{fontSize: '14px'}} />}
>
{renderTreeNode(item.subPermissionList)}
</TreeNode>
)
}
return (
<TreeNode
title={
<>
{item.name}
<EditOutlined title="编辑" className="ml-10" onClick={() => handleTreeNode(item, "INFO")} />
<DeleteOutlined title="删除" className="ml-10" onClick={() => handleTreeNode(item, "DEL")} />
</>
}
key={item.code}
icon={<IconFontConfig type={(item.type === 3 || item.type === "BTN") ? "icon-btn" : "icon-menu"} style={{fontSize: '14px'}} />}
/>
)
})
};
// 点击TreeNode
const handleTreeNode = (item, type) => {
if (type === "INFO") {
dispatch({type: 'Application/changeState', payload: {
oldInfo: {...item},
dataModal: {
modalType: 'AUTH_SET_MODAL',
modalShow: true,
modalData: {...item, isEdit: true}
}
}})
} else if (type === "DEL") {
const { data, dataMenu, dataExpandedKeys } = getDelAuthData(dataAuth, item, 'DEL');
Modal.confirm({
title: '确定删除吗?',
icon: <ExclamationCircleOutlined />,
okText: '确定',
cancelText: '取消',
onOk() {
dispatch({type: 'Application/changeState', payload: {
dataAuth: JSON.parse(JSON.stringify(data)),
dataMenuList: JSON.parse(JSON.stringify(dataMenu)),
dataExpandedKeys: JSON.parse(JSON.stringify(dataExpandedKeys))
}});
}
});
}
};
// 点击Tree
const handleOnExpand = (expandedKeys) => {
dispatch({type: 'Application/changeState', payload: {
dataExpandedKeys: expandedKeys
}});
};
// 保存应用
const handleSaveApplication = () => {
let permissionList = getPermissionList(dataAuth);
form.validateFields().then(values => {
dispatch({type: 'Application/updateApplication', payload: {
...values,
id,
permissionList
}})
})
};
/* 拖拽排序 */
const onDrop = info => {
const dropKey = info.node.props.eventKey;
const dragKey = info.dragNode.props.eventKey;
const dropPos = info.node.props.pos.split('-');
const dropPosition = info.dropPosition - Number(dropPos[dropPos.length - 1]);
const loop = (data, key, callback) => {
for (let i = 0; i < data.length; i++) {
if (data[i].key === key) {
return callback(data[i], i, data);
}
if (data[i].subPermissionList) {
loop(data[i].subPermissionList, key, callback);
}
}
};
let dataStateAuth = JSON.parse(JSON.stringify(dataAuth));
const data = [...dataStateAuth];
// Find dragObject
let dragObj;
loop(data, dragKey, (item, index, arr) => {
arr.splice(index, 1);
dragObj = item;
});
if (!info.dropToGap) {
// Drop on the content
loop(data, dropKey, item => {
item.subPermissionList = item.subPermissionList || [];
// where to insert 示例添加到尾部,可以是随意位置
item.subPermissionList.push(dragObj);
});
} else if (
(info.node.props.subPermissionList || []).length > 0 &&
info.node.props.expanded &&
dropPosition === 1
) {
loop(data, dropKey, item => {
item.subPermissionList = item.subPermissionList || [];
// where to insert 示例添加到头部,可以是随意位置
item.subPermissionList.unshift(dragObj);
});
} else {
let ar;
let i;
loop(data, dropKey, (item, index, arr) => {
ar = arr;
i = index;
});
if (dropPosition === -1) {
ar.splice(i, 0, dragObj);
} else {
ar.splice(i + 1, 0, dragObj);
}
}
let { dataDragAuth, dataMenu, dataExpandedKeys } = getDragData(data);
dispatch({type: 'Application/changeState', payload: {
dataAuth: JSON.parse(JSON.stringify(dataDragAuth.length > 0 ? dataDragAuth : dataAuth)),
dataMenuList: JSON.parse(JSON.stringify(dataMenu)),
dataExpandedKeys: JSON.parse(JSON.stringify(dataExpandedKeys))
}});
};
/* 清空所有权限 */
const handleRemoveAuth = () => {
Modal.confirm({
title: '确定清空该应用权限的相关数据吗?',
icon: <ExclamationCircleOutlined />,
okText: '确定',
cancelText: '取消',
onOk() {
dispatch({type: 'Application/changeState', payload: {
dataAuth: [],
dataMenuList: [],
dataExpandedKeys: []
}});
}
});
};
return (
<div className="box-info">
<Breadcrumb>
<Breadcrumb.Item><Link to="/appliction">应用列表</Link></Breadcrumb.Item>
<Breadcrumb.Item>{!id ? '新建应用' : '编辑应用'}</Breadcrumb.Item>
</Breadcrumb>
<Spin spinning={id ? loading : false}>
<Card bordered={false} className="mt-15">
<Form
{...layout}
form={form}
name="applicationInfo"
>
<Form.Item
label="应用名称"
name="name"
rules={[{ required: true, message: '请输入应用名称' }]}
>
<Input placeholder="请输入应用名称" />
</Form.Item>
<Form.Item
label="应用标识码"
name="code"
>
<Input placeholder="请输入应用标识码" />
</Form.Item>
<Form.Item
label="应用Api域名地址"
name="apiDomain"
>
<Input placeholder="请输入应用api域名地址" />
</Form.Item>
<Form.Item
label="应用链接地址"
name="url"
>
<Input placeholder="请输入应用链接地址" />
</Form.Item>
<Form.Item label="权限服务模板">
<Button onClick={handleAddTemp}>添加</Button>
</Form.Item>
<Form.Item label="权限预览" className="view-tree-auth">
{
dataAuth.length > 0 ?
(
<Tree
showLine
showIcon
draggable
selectedKeys={[]}
className="info-tree"
onExpand={handleOnExpand}
expandedKeys={dataExpandedKeys}
onDrop={onDrop}
>
{renderTreeNode(dataAuth)}
</Tree>
)
:
<span>暂无数据</span>
}
</Form.Item>
<Form.Item className="footer-btn" wrapperCol={{span: 24}}>
<Button disabled={!dataAuth.length} onClick={handleRemoveAuth} className="mr-10">清空权限</Button>
<Button loading={isRepeat} onClick={handleSaveApplication} type="primary" className="ml-10">保存应用</Button>
</Form.Item>
</Form>
</Card>
{/* 设置权限抽屉 */}
<ModalSetAuth
dataModal={dataModal}
dataMenuList={dataMenuList}
handleAuthSetOk={handleAuthSetOk}
handleCancelModal={handleCancelModal}
/>
</Spin>
</div>
)
};
export default connect(({ Application, loading }) => ({
...Application,
isRepeat: loading.effects["Application/updateApplication"],
loading: loading.effects["Application/getApplicationInfo"]
}))(ApplicationInfo);
\ No newline at end of file
/**
* Author: llw
* Date: 2020.7.20
* Description: [设置权限弹框]
*/
import React, { useEffect, useState } from 'react';
import { Drawer, Radio, Form, Select, Input, Button } from 'antd';
const { Option } = Select;
const formItemLayout = {labelCol: { span: 4 }, wrapperCol: { span: 20 }};
const ModalSetAuth = props => {
const [menuType, setType] = useState(1);
const [form] = Form.useForm();
let {
handleCancelModal,
handleAuthSetOk,
dataMenuList = [],
dataModal: {
modalType,
modalShow,
modalData,
modalData: { name, code, type, url, icon, parentCode, isEdit }
}
} = props;
dataMenuList = code ? dataMenuList.filter(item => code !== item.code) : dataMenuList;
/* 重置type */
useEffect(() => {
if (modalType === "AUTH_SET_MODAL" && modalShow) {
setType(type || 1);
setTimeout(() => {
form.resetFields();
form.setFieldsValue({
name,
code,
url,
icon,
parentCode,
type: type || 1,
});
}, 100)
}
}, [modalType, modalShow])
/* 点击保存 */
const handleSave = () => {
form.validateFields().then(values => {
handleAuthSetOk({
...modalData,
...values,
isEdit
});
})
};
/* 选择菜单类型 */
const handleChangeRadio = (value) => {
form.resetFields();
setType(value);
form.setFieldsValue({type: value});
};
return (
<Drawer
title="权限设置"
placement="right"
width={600}
maskClosable={false}
onClose={handleCancelModal}
visible={modalType === 'AUTH_SET_MODAL' && modalShow}
footer={
<div
style={{
textAlign: 'right',
}}
>
<Button onClick={handleCancelModal} className="mr-10">取消</Button>
<Button onClick={handleSave} type="primary">保存</Button>
</div>
}
>
<Form
form={form}
{...formItemLayout}
name="auth_set_modal"
initialValues={{ type: 1 }}
>
<Form.Item
name="type"
label="菜单类型"
>
<Radio.Group buttonStyle="solid" onChange={(e) => handleChangeRadio(e.target.value)}>
<Radio.Button disabled={(isEdit && type !== 1) ? true : false} value={1}>一级菜单</Radio.Button>
<Radio.Button disabled={(isEdit && type !== 2) ? true : false} value={2}>子菜单</Radio.Button>
<Radio.Button disabled={(isEdit && type !== 3) ? true : false} value={3}>按钮</Radio.Button>
</Radio.Group>
</Form.Item>
{
menuType !== 1 && <Form.Item
name="parentCode"
label={menuType === 2 ? "上级菜单" : "包含菜单"}
rules={
[{required: true, message: menuType === 2 ? "请选择上级菜单" : "请选择包含该按钮的菜单"}]
}
>
<Select
showSearch
placeholder={menuType === 2 ? '请选择上级菜单' : '请选择包含该按钮的菜单'}
optionFilterProp="children"
filterOption={(input, option) =>
option.children.toLowerCase().indexOf(input.toLowerCase()) >= 0
}
>
{
dataMenuList.map(item => {
return (
<Option key={item.code} value={item.code}>{item.name}</Option>
)
})
}
</Select>
</Form.Item>
}
<Form.Item
name="name"
label={menuType === 3 ? "按钮名称" : "菜单名称"}
rules={
[
{required: true, message: menuType === 3 ? '请输入按钮名称' : '请输入菜单名称'},
{min: 2, max: 15, message: '名称长度2~15个字符'}
]
}
>
<Input maxLength={15} placeholder={menuType === 3 ? "请输入按钮名称" : "请输入菜单名称"} />
</Form.Item>
<Form.Item
name="code"
label={menuType === 3 ? "按钮code" : "菜单code"}
rules={
[{required: true, message: menuType === 3 ? '请输入按钮code' : '请输入菜单code'}]
}
>
<Input maxLength={20} placeholder={menuType === 3 ? "请输入按钮code" : "请输入菜单code"} />
</Form.Item>
{
menuType !== 3 && <>
<Form.Item
name="url"
label="菜单URL"
rules={
[{required: true, message: '请输入菜单URL'}]
}
>
<Input placeholder="请输入菜单URL" />
</Form.Item>
<Form.Item
name="icon"
label="菜单Icon"
>
<Input placeholder="请输入菜单Icon" />
</Form.Item>
</>
}
</Form>
</Drawer>
)
};
export default ModalSetAuth;
\ No newline at end of file
/**
* Author: llw
* Date: 2020.7.16
* Description: [应用列表]
*/
import React, { useEffect } from 'react';
import { connect, Link } from 'umi';
import { Card, Form, DatePicker, Input, Button, Table, Divider, Modal } from 'antd';
import { ExclamationCircleOutlined } from '@ant-design/icons';
import { paginations } from '@/constants';
import moment from 'moment';
const { RangePicker } = DatePicker;
const FormItem = Form.Item;
/* SearchForm */
const SearchForm = props => {
const [form] = Form.useForm();
const {
dataSearch,
handleReset,
handleFinish,
dataSearch: { createTimeBegin, createTimeEnd }
} = props;
useEffect(() => {
form.setFieldsValue({
...dataSearch,
createTime: [createTimeBegin ? moment(createTimeBegin) : undefined, createTimeEnd ? moment(createTimeEnd) : undefined]
});
}, [dataSearch]);
/* 点击搜索 */
const onFinish = (values) => {
handleFinish(values);
};
/* 点击重置 */
const onReset = () => {
handleReset();
};
return (
<Form
name="Form_Application"
layout="inline"
form={form}
onFinish={onFinish}
>
<FormItem label="应用名称" name="name">
<Input placeholder="请输入应用名称" style={{width: "220px"}} />
</FormItem>
<FormItem label="创建时间" name="createTime">
<RangePicker allowClear={false} />
</FormItem>
<FormItem>
<Button type="primary" htmlType="submit" className="mr-15">搜索</Button>
<Button className="mr-15" htmlType="button" onClick={onReset}>重置</Button>
<Link to="/appliction/info"><Button>创建应用</Button></Link>
</FormItem>
</Form>
)
};
/* DataTable */
const DataTable = props => {
const {
loading,
handleDel,
handleGetList,
dataApplication: { data = [], page, size, totalItem }
} = props;
/* 点击分页 */
const handlePageChange = (page, size) => {
handleGetList(page, size);
};
const columns = [
{
title: '应用名称',
dataIndex: 'name',
align: 'center'
},
{
title: '应用标识码',
dataIndex: 'code',
align: 'center',
render(t, r) {
return t || '--'
}
},
{
title: '应用链接地址',
dataIndex: 'url',
align: 'center',
render(t, r) {
return t || '--'
}
},
{
title: '创建时间',
dataIndex: 'createTime',
align: 'center',
render(t, r) {
return t ? moment(t).format("YYYY-MM-DD HH:mm:ss") : '--'
}
},
{
title: '操作',
align: 'center',
render(t, r) {
return (
<>
<Link to={`/appliction/info?id=${r.id}`}>编辑</Link>
<Divider type="vertical" />
<a onClick={() => handleDel(r)}>删除</a>
</>
)
}
}
];
const pagination = {
...paginations,
total: totalItem,
pageSize: size,
current: page,
showSizeChanger: totalItem > 20,
onChange: (page, pageSize) => {
handlePageChange(page, pageSize);
},
onShowSizeChange: (page, pageSize) => {
handlePageChange(1, pageSize);
},
showTotal(total) {
return `总共 ${total} 条数据`;
},
};
return (
<Table
rowKey="id"
loading={loading}
dataSource={data}
columns={columns}
pagination={pagination}
/>
)
};
/* Main */
const Application = props => {
const { dispatch, loading, dataSearch, dataApplication, dataApplication: { size } } = props;
useEffect(() => {
handleGetList(1, 10);
}, [])
/* 应用列表 */
const handleGetList = (page, size) => {
dispatch({type: 'Application/getApplicationList', payload: {
page: page || 1,
size: size || 10
}});
};
/* 点击搜索 */
const handleFinish = (values) => {
const { createTime, name } = values;
dispatch({type: 'Application/changeState', payload: {
dataSearch: {
name,
createTimeBegin: createTime[0] ? createTime[0].startOf('day').valueOf() : undefined,
createTimeEnd: createTime[1] ? createTime[1].endOf('day').valueOf() : undefined,
}
}});
handleGetList(0, size);
};
/* 点击重置 */
const handleReset = () => {
dispatch({type: 'Application/resetSearch'});
handleGetList(0, 10);
};
/* 点击删除 */
const handleDel = ({ id }) => {
Modal.confirm({
title: '确定删除该应用吗?',
icon: <ExclamationCircleOutlined />,
okText: '确定',
cancelText: '取消',
onOk() {
dispatch({type: 'Application/delApplication', payload: {id}});
},
});
};
return (
<Card bordered={false}>
<SearchForm
dataSearch={dataSearch}
handleReset={handleReset}
handleFinish={handleFinish}
/>
<div className="mt-24">
<DataTable
loading={loading}
handleDel={handleDel}
handleGetList={handleGetList}
dataApplication={dataApplication}
/>
</div>
</Card>
)
};
export default connect(({ Application, loading }) => ({
...Application,
loading: loading.effects["Application/getApplicationList"]
}))(Application)
\ No newline at end of file
import { routerRedux } from 'dva';
import { message } from 'antd';
import { getPermissionList } from '@/utils/utils';
import * as services from '@/services/application';
/* SerachParams */
const staticSearch = {
name: undefined,
createTimeBegin: undefined,
createTimeEnd: undefined
};
export default {
namespace: 'Application',
state: {
dataSearch: {
...staticSearch
},
dataApplication: {
data: [],
page: 1,
size: 10,
totalItem: 0,
totalPage: 0,
},
dataModal: {
modalType: '',
modalShow: false,
modalData: {}
},
dataMenuList: [], // 获取所有的菜单
dataAuth: [], // 权限模块提交给后端的数据
dataExpandedKeys: [], // tree展开的数据
dataApplicationInfo: {}, // 应用相关信息
oldInfo: {}, // 上一次权限信息
},
effects: {
/* 获取应用列表 */
*getApplicationList({ payload }, { call, put, select }) {
const { dataSearch } = yield select(state => state.Application);
try {
const res = yield call(services.getApplicationList, {
page: 1,
size: 10,
...dataSearch,
...payload
});
if (res.status === 1) {
res.data.data = res.data.data || [];
yield put({type: 'changeState', payload: {
dataApplication: res.data
}})
}
} catch (err) {
console.error(err)
}
},
/* 删除应用 */
*delApplication({ payload }, { call, put, select }) {
const { dataApplication: { data = [], page, size } } = yield select(state => state.Application);
try {
const res = yield call(services.delApplication, payload);
if (res.status === 1) {
message.success("删除成功~");
yield put({type: 'getApplicationList', payload: {
size,
page: (data.length === 1 ? (page === 1 ? 1 : page - 1) : page)
}})
}
} catch (err) {
console.error(err)
}
},
/* 新增、修改应用 */
*updateApplication({ payload }, { call, put, select }) {
const { id } = payload;
try {
const res = yield call(services[!id ? "addApplication" : "updateApplication"], payload);
if (res.status === 1) {
message.success(!id ? "新增成功~" : "修改成功~");
yield put(routerRedux.push('/appliction'));
}
} catch (err) {
console.error(err)
}
},
/* 获取应用详情 */
*getApplicationInfo({ payload }, { call, put, select }) {
try {
const res = yield call(services.getApplicationInfo, payload);
if (res.status === 1) {
const { data, dataMenu, dataExpandedKeys } = getPermissionList(res.data.permissionList, true);
yield put({type: 'changeState', payload: {
dataApplicationInfo: res.data || {},
dataAuth: data,
dataMenuList: dataMenu,
dataExpandedKeys
}})
};
return res;
} catch (err) {
console.error(err)
}
}
},
reducers: {
changeState(state, { payload }) {
return {
...state,
...payload
}
},
cancelModal(state, { payload }) {
return {
...state,
dataModal: {
modalType: '',
modalShow: false,
modalData: {}
}
}
},
resetSearch(state, { payload }) {
return {
...state,
dataSearch: {
...staticSearch
}
}
}
}
};
/**
* Author: llw
* Date: 2022.9.14
* Description: [行政执法案件详情]
*/
import React, { useEffect } from 'react';
import { connect } from 'umi';
import { Drawer } from 'antd';
import { StyledText } from '@/components/style';
import { mapCause } from '@/constants';
const ModalEnforceMent = props => {
let {
dispatch,
dataModal: {
modalType,
modalShow,
modalData: { id },
},
enforcementInfo,
} = props;
useEffect(() => {
if (modalType === 'Enforce_Ment_Modal' && modalShow) {
dispatch({ type: 'Enforcement/getEventIllegalDetail', payload: { id } });
}
}, [modalType, modalShow]);
return (
<Drawer
title="详情"
placement="right"
width={900}
maskClosable={false}
onClose={() => {
dispatch({ type: 'Enforcement/cancelModal' });
}}
visible={modalType === 'Enforce_Ment_Modal' && modalShow}
footer={null}
>
<StyledText>
<div className="item-text">
<div className="title">立案单位</div>
<div className="desc">{enforcementInfo.company || '-'}</div>
</div>
<div className="item-text">
<div className="title">类型</div>
<div className="desc">
{(mapCause[enforcementInfo.type] && mapCause[enforcementInfo.type].label) || '-'}
</div>
</div>
<div className="item-text">
<div className="title">案件编号</div>
<div className="desc">{enforcementInfo.num || '-'}</div>
</div>
<div className="item-text">
<div className="title">呈批时间</div>
<div className="desc">{enforcementInfo.submitDate || '-'}</div>
</div>
<div className="item-text">
<div className="title">法定代表人及负责人</div>
<div className="desc">{enforcementInfo.placeUserName || '-'}</div>
</div>
<div className="item-text">
<div className="title">电话</div>
<div className="desc">{enforcementInfo.placeUserTel || '-'}</div>
</div>
<div className="item-text">
<div className="title">案件来源</div>
<div className="desc">{enforcementInfo.source || '-'}</div>
</div>
<div className="item-text">
<div className="title">执法人员及执法编号</div>
<div className="desc">{enforcementInfo.nameCode || '-'}</div>
</div>
<div className="item-text">
<div className="title">案由</div>
<div className="desc">{enforcementInfo.cause || '-'}</div>
</div>
<div className="item-text">
<div className="title">案发区域</div>
<div className="desc">{enforcementInfo.area || '-'}</div>
</div>
<div className="item-text" style={{ width: '100%' }}>
<div className="title">违法依据</div>
<div className="desc">{enforcementInfo.illegalBasis || '-'}</div>
</div>
<div className="item-text" style={{ width: '100%' }}>
<div className="title">处罚依据</div>
<div className="desc">{enforcementInfo.punishBasis || '-'}</div>
</div>
<div className="item-text" style={{ width: '100%' }}>
<div className="title">地址</div>
<div className="desc">{enforcementInfo.placeName || '-'}</div>
</div>
<div className="item-text" style={{ width: '100%' }}>
<div className="title">处罚内容</div>
<div className="desc">{enforcementInfo.contents || '-'}</div>
</div>
<div className="item-text" style={{ width: '100%' }}>
<div className="title">案卷相关文书</div>
<div className="desc">{enforcementInfo.instruments || '-'}</div>
</div>
</StyledText>
</Drawer>
);
};
export default connect(({ Enforcement }) => ({
...Enforcement,
}))(ModalEnforceMent);
/**
* Author: llw
* Date: 2022.9.14
* Description: [基层网络详情]
*/
import React, { useEffect } from 'react';
import { connect } from 'umi';
import { Drawer } from 'antd';
import { StyledText } from '@/components/style';
const ModalNetworkEvent = props => {
let {
dispatch,
dataModal: {
modalType,
modalShow,
modalData: { id },
},
baseGridInfo,
} = props;
useEffect(() => {
if (modalType === 'Network_Event_Modal' && modalShow) {
dispatch({ type: 'NetworkEvent/getBaseGridDetail', payload: { id } });
}
}, [modalType, modalShow]);
return (
<Drawer
title="详情"
placement="right"
width={600}
maskClosable={false}
onClose={() => {
dispatch({ type: 'NetworkEvent/cancelModal' });
}}
visible={modalType === 'Network_Event_Modal' && modalShow}
footer={null}
>
<StyledText>
<div className="item-text">
<div className="title">事件编号</div>
<div className="desc">{baseGridInfo.eventCode || '-'}</div>
</div>
<div className="item-text">
<div className="title">事件来源</div>
<div className="desc">{baseGridInfo.eventSource || '-'}</div>
</div>
<div className="item-text">
<div className="title">发起人</div>
<div className="desc">{baseGridInfo.sponsor || '-'}</div>
</div>
<div className="item-text">
<div className="title">发起组织</div>
<div className="desc">{baseGridInfo.sponsorOrg || '-'}</div>
</div>
<div className="item-text">
<div className="title">联系方式</div>
<div className="desc">{baseGridInfo.sponsorTel || '-'}</div>
</div>
<div className="item-text">
<div className="title">发起时间</div>
<div className="desc">{baseGridInfo.origTime || '-'}</div>
</div>
<div className="item-text">
<div className="title">处理时间</div>
<div className="desc">{baseGridInfo.processingTime || '-'}</div>
</div>
<div className="item-text">
<div className="title">截止时间</div>
<div className="desc">{baseGridInfo.deadTime || '-'}</div>
</div>
<div className="item-text">
<div className="title">事发时间</div>
<div className="desc">{baseGridInfo.incidentTime || '-'}</div>
</div>
<div className="item-text">
<div className="title">事发地址</div>
<div className="desc">{baseGridInfo.incidentAddress || '-'}</div>
</div>
<div className="item-text">
<div className="title">事发经度</div>
<div className="desc">{baseGridInfo.longitude || '-'}</div>
</div>
<div className="item-text">
<div className="title">事发维度</div>
<div className="desc">{baseGridInfo.latitude || '-'}</div>
</div>
<div className="item-text">
<div className="title">统一地址</div>
<div className="desc">{baseGridInfo.address || '-'}</div>
</div>
<div className="item-text">
<div className="title">事发区县</div>
<div className="desc">{baseGridInfo.county || '-'}</div>
</div>
<div className="item-text">
<div className="title">事发镇街</div>
<div className="desc">{baseGridInfo.town || '-'}</div>
</div>
<div className="item-text">
<div className="title">事发村社</div>
<div className="desc">{baseGridInfo.village || '-'}</div>
</div>
<div className="item-text">
<div className="title">事发网格</div>
<div className="desc">{baseGridInfo.grid || '-'}</div>
</div>
<div className="item-text">
<div className="title">事发小区</div>
<div className="desc">{baseGridInfo.quarters || '-'}</div>
</div>
<div className="item-text">
<div className="title">事项大类</div>
<div className="desc">{baseGridInfo.matterBig || '-'}</div>
</div>
<div className="item-text">
<div className="title">事项小类</div>
<div className="desc">{baseGridInfo.matterSmall || '-'}</div>
</div>
<div className="item-text">
<div className="title">事项细类</div>
<div className="desc">{baseGridInfo.matterFine || '-'}</div>
</div>
<div className="item-text">
<div className="title">事项编码</div>
<div className="desc">{baseGridInfo.matterCode || '-'}</div>
</div>
<div className="item-text">
<div className="title">事件级别</div>
<div className="desc">{baseGridInfo.matterLevel || '-'}</div>
</div>
<div className="item-text">
<div className="title">事项性质</div>
<div className="desc">{baseGridInfo.matterNature || '-'}</div>
</div>
<div className="item-text" style={{ width: '100%' }}>
<div className="title">事件状态</div>
<div className="desc">{baseGridInfo.stateName || '-'}</div>
</div>
<div className="item-text" style={{ width: '100%' }}>
<div className="title">事件详情</div>
<div className="desc">{baseGridInfo.matterDetailed || '-'}</div>
</div>
<div className="item-text" style={{ width: '100%' }}>
<div className="title">事件描述</div>
<div className="desc">{baseGridInfo.matterDescribe || '-'}</div>
</div>
</StyledText>
</Drawer>
);
};
export default connect(({ NetworkEvent }) => ({
...NetworkEvent,
}))(ModalNetworkEvent);
/**
* Author: Charles
* Date: 2022.9.13
* Description: [投诉举报列表]
*/
import React, { useEffect } from 'react';
import { connect } from 'umi';
import { Card, Form, Input, Button, Table, Tooltip, DatePicker } from 'antd';
import { paginations } from '@/constants';
import { StyledPageContainer, StyledPageContent, StyledEllipsisWrap } from '@/components/style';
import { ZoomInOutlined } from '@ant-design/icons';
import moment from 'moment';
const FormItem = Form.Item;
const { RangePicker } = DatePicker;
/* SearchForm */
const SearchForm = props => {
const [form] = Form.useForm();
const {
dataSearch,
dataSearch: { startDate, endDate },
handleReset,
handleFinish,
} = props;
useEffect(() => {
form.setFieldsValue({
...dataSearch,
createTime: [
startDate ? moment(startDate) : undefined,
endDate ? moment(endDate) : undefined,
],
});
}, [dataSearch]);
/* 点击搜索 */
const onFinish = values => {
handleFinish(values);
};
/* 点击重置 */
const onReset = () => {
handleReset();
};
return (
<Form name="Form_Complaint" layout="inline" form={form} onFinish={onFinish}>
<FormItem label="公司名称" name="keyWord">
<Input placeholder="请输入公司名称" style={{ width: '250px' }} />
</FormItem>
<FormItem label="投诉举报时间" name="createTime">
<RangePicker allowClear={false} style={{ width: '300px' }} />
</FormItem>
<FormItem>
<Button type="primary" htmlType="submit" className="mr-15">
搜索
</Button>
<Button className="mr-15" htmlType="button" onClick={onReset}>
重置
</Button>
</FormItem>
</Form>
);
};
/* DataTable */
const DataTable = props => {
const {
loading,
handleGetList,
dataComplaint: { records = [], current, size, total },
} = props;
/* 点击分页 */
const handlePageChange = (current, size) => {
handleGetList(current, size);
};
const columns = [
{
title: '序号',
dataIndex: 'id',
align: 'center',
fixed: 'left',
width: 80,
render(t, r, idx) {
return (current - 1) * size + idx + 1;
},
},
{
title: '公司名称',
dataIndex: 'name',
align: 'center',
width: 200,
render: (t, r) => {
return t ? (
<Tooltip placement="top" title={t}>
<StyledEllipsisWrap maxLine={1}>{t}</StyledEllipsisWrap>
</Tooltip>
) : (
'-'
);
},
},
{
title: '公司地址',
dataIndex: 'address',
align: 'center',
width: 220,
render: (t, r) => {
return t ? (
<Tooltip placement="top" title={t}>
<StyledEllipsisWrap maxLine={2}>{t}</StyledEllipsisWrap>
</Tooltip>
) : (
'-'
);
},
},
{
title: '通讯地址',
dataIndex: 'postalAddress',
align: 'center',
width: 220,
render: (t, r) => {
return t ? (
<Tooltip placement="top" title={t}>
<StyledEllipsisWrap maxLine={2}>{t}</StyledEllipsisWrap>
</Tooltip>
) : (
'-'
);
},
},
{
title: '所属地区',
dataIndex: 'region',
align: 'center',
width: 160,
render: (t, r) => {
return t ? (
<Tooltip placement="top" title={t}>
<StyledEllipsisWrap maxLine={2}>{t}</StyledEllipsisWrap>
</Tooltip>
) : (
'-'
);
},
},
{
title: '负责人',
dataIndex: 'person',
align: 'center',
width: 150,
},
{
title: '身份证号码',
dataIndex: 'personCode',
align: 'center',
width: 220,
},
{
title: '负责人电话',
dataIndex: 'personTel',
align: 'center',
width: 150,
},
{
title: '联系人',
dataIndex: 'contacts',
align: 'center',
width: 150,
},
{
title: '联系人电话',
dataIndex: 'contactsTel',
align: 'center',
width: 180,
},
{
title: '主营范围',
dataIndex: 'mainScope',
align: 'center',
width: 180,
render: (t, r) => {
return t ? (
<Tooltip placement="top" title={t}>
<StyledEllipsisWrap maxLine={2}>{t}</StyledEllipsisWrap>
</Tooltip>
) : (
'-'
);
},
},
{
title: '备注',
dataIndex: 'remarks',
align: 'center',
width: 200,
render: (t, r) => {
return t ? (
<Tooltip placement="top" title={t}>
<StyledEllipsisWrap maxLine={2}>{t}</StyledEllipsisWrap>
</Tooltip>
) : (
'-'
);
},
},
// {
// title: '操作',
// align: 'center',
// fixed: 'right',
// width: 80,
// render(t, r) {
// return (
// <Tooltip placement="top" title="查看详情">
// <ZoomInOutlined style={{ cursor: 'pointer', fontSize: 18 }} onClick={() => {}} />
// </Tooltip>
// );
// },
// },
];
const pagination = {
...paginations,
total: total,
pageSize: size,
current,
showSizeChanger: total > 10,
onChange: (current, pageSize) => {
handlePageChange(current, pageSize);
},
onShowSizeChange: (current, pageSize) => {
handlePageChange(1, pageSize);
},
showTotal(total) {
return `总共 ${total} 条数据`;
},
};
return (
<Table
rowKey="id"
loading={loading}
dataSource={records}
columns={columns}
pagination={pagination}
scroll={{ x: 2000, y: `calc(100vh - 350px)` }}
/>
);
};
/* Main */
const Complaint = props => {
const {
dispatch,
loading,
dataSearch,
dataComplaint,
dataComplaint: { size },
} = props;
useEffect(() => {
handleGetList(1, 10);
}, []);
/* 列表 */
const handleGetList = (current, size) => {
dispatch({
type: 'Complaint/getComplaintList',
payload: {
current: current || 1,
size: size || 10,
},
});
};
/* 点击搜索 */
const handleFinish = values => {
const { keyWord, createTime } = values;
dispatch({
type: 'Complaint/changeState',
payload: {
dataSearch: {
keyWord,
startDate: createTime[0]
? moment(createTime[0].startOf('day').valueOf()).format('YYYY-MM-DD HH:mm:ss')
: undefined,
endDate: createTime[1]
? moment(createTime[1].endOf('day').valueOf()).format('YYYY-MM-DD HH:mm:ss')
: undefined,
},
},
});
handleGetList(0, size);
};
/* 点击重置 */
const handleReset = () => {
dispatch({ type: 'Complaint/resetSearch' });
handleGetList(0, 10);
};
return (
<StyledPageContainer>
<StyledPageContent>
<Card bordered={false}>
<SearchForm
dataSearch={dataSearch}
handleReset={handleReset}
handleFinish={handleFinish}
/>
<div className="mt-16">
<DataTable
loading={loading}
handleGetList={handleGetList}
dataComplaint={dataComplaint}
/>
</div>
</Card>
</StyledPageContent>
</StyledPageContainer>
);
};
export default connect(({ Complaint, loading }) => ({
...Complaint,
loading: loading.effects['Complaint/getComplaintList'],
}))(Complaint);
/**
* Author: Charles
* Date: 2022.9.13
* Description: [行政执法列表]
*/
import React, { useEffect } from 'react';
import { connect } from 'umi';
import { Card, Form, Input, Button, Table, Tooltip, Upload, message, Select } from 'antd';
import { paginations } from '@/constants';
import {
StyledPageContainer,
StyledPageContent,
StyledEllipsisWrap,
StyledPageHeader,
} from '@/components/style';
import { ZoomInOutlined, UploadOutlined } from '@ant-design/icons';
import * as services from '@/services/data';
import { ExportFile } from '@/utils/utils';
import { mapCause, enumYear } from '@/constants';
import ModalEnforcement from './Modal/ModalEnforcement';
const FormItem = Form.Item;
const Option = Select.Option;
/* SearchForm */
const SearchForm = props => {
const [form] = Form.useForm();
const { dataSearch, handleReset, handleFinish } = props;
useEffect(() => {
form.setFieldsValue({
...dataSearch,
});
}, [dataSearch]);
/* 点击搜索 */
const onFinish = values => {
handleFinish(values);
};
/* 点击重置 */
const onReset = () => {
handleReset();
};
return (
<Form name="Form_Enforcement" layout="inline" form={form} onFinish={onFinish}>
<FormItem label="当事人或法人姓名" name="placeName">
<Input placeholder="请输入当事人或法人姓名" style={{ width: '220px' }} />
</FormItem>
<FormItem label="办案单位" name="company">
<Input placeholder="请输入办案单位" style={{ width: '220px' }} />
</FormItem>
<FormItem label="案件类型" name="type">
<Select placeholder="请选择案件类型" style={{ width: '150px' }}>
<Option value="ALL">全部</Option>
{Object.values(mapCause).map(item => {
return (
<Option key={item.value} value={item.value}>
{item.label}
</Option>
);
})}
</Select>
</FormItem>
<FormItem label="处罚年份" name="yearDate">
<Select placeholder="请选择处罚年份" style={{ width: '150px' }} showSearch>
<Option value="ALL">全部</Option>
{enumYear().map(item => {
return (
<Option key={item.value} value={item.value}>
{item.label}
</Option>
);
})}
</Select>
</FormItem>
<FormItem>
<Button type="primary" htmlType="submit" className="mr-15">
搜索
</Button>
<Button className="mr-15" htmlType="button" onClick={onReset}>
重置
</Button>
</FormItem>
</Form>
);
};
/* DataTable */
const DataTable = props => {
const {
dispatch,
loading,
handleGetList,
dataEnforcement: { records = [], current, size, total },
} = props;
/* 点击分页 */
const handlePageChange = (current, size) => {
handleGetList(current, size);
};
const columns = [
{
title: '序号',
dataIndex: 'id',
align: 'center',
fixed: 'left',
width: 80,
render(t, r, idx) {
return (current - 1) * size + idx + 1;
},
},
{
title: '统一编号',
dataIndex: 'num',
align: 'center',
width: 200,
},
{
title: '案件类型',
dataIndex: 'type',
align: 'center',
width: 120,
render: (t, r) => {
return (mapCause[t] && mapCause[t].label) || '-';
},
},
{
title: '当事人',
dataIndex: 'placeUserName',
align: 'center',
width: 160,
render: (t, r) => {
return t ? (
<Tooltip placement="top" title={t}>
<StyledEllipsisWrap maxLine={1}>{t}</StyledEllipsisWrap>
</Tooltip>
) : (
'-'
);
},
},
{
title: '案由',
dataIndex: 'cause',
align: 'center',
width: 220,
render: (t, r) => {
return t ? (
<Tooltip placement="top" title={t}>
<StyledEllipsisWrap maxLine={2}>{t}</StyledEllipsisWrap>
</Tooltip>
) : (
'-'
);
},
},
{
title: '办案单位',
dataIndex: 'company',
align: 'center',
width: 160,
render: (t, r) => {
return t ? (
<Tooltip placement="top" title={t}>
<StyledEllipsisWrap maxLine={1}>{t}</StyledEllipsisWrap>
</Tooltip>
) : (
'-'
);
},
},
{
title: '立案时间',
dataIndex: 'filingDate',
align: 'center',
width: 160,
render: (t, r) => {
return t || '-';
},
},
{
title: '处罚时间',
dataIndex: 'punishDate',
align: 'center',
width: 160,
render: (t, r) => {
return t || '-';
},
},
{
title: '结案时间',
dataIndex: 'closeDate',
align: 'center',
width: 160,
render: (t, r) => {
return t || '-';
},
},
{
title: '操作',
align: 'center',
fixed: 'right',
width: 80,
render(t, r) {
return (
<Tooltip placement="top" title="查看详情">
<ZoomInOutlined
style={{ cursor: 'pointer', fontSize: 18 }}
onClick={() => {
dispatch({
type: 'Enforcement/changeState',
payload: {
dataModal: {
modalType: 'Enforce_Ment_Modal',
modalShow: true,
modalData: r,
},
},
});
}}
/>
</Tooltip>
);
},
},
];
const pagination = {
...paginations,
total: total,
pageSize: size,
current,
showSizeChanger: total > 10,
onChange: (current, pageSize) => {
handlePageChange(current, pageSize);
},
onShowSizeChange: (current, pageSize) => {
handlePageChange(1, pageSize);
},
showTotal(total) {
return `总共 ${total} 条数据`;
},
};
return (
<Table
rowKey="id"
loading={loading}
dataSource={records}
columns={columns}
pagination={pagination}
scroll={{ x: 2000, y: `calc(100vh - 448px)` }}
/>
);
};
/* Main */
const Enforcement = props => {
const {
dispatch,
loading,
loadingUpload,
dataSearch,
dataEnforcement,
dataEnforcement: { size },
} = props;
useEffect(() => {
handleGetList(1, 10);
}, []);
/* 列表 */
const handleGetList = (current, size) => {
dispatch({
type: 'Enforcement/getEnforcementList',
payload: {
current: current || 1,
size: size || 10,
},
});
};
/* 点击搜索 */
const handleFinish = values => {
dispatch({
type: 'Enforcement/changeState',
payload: {
dataSearch: {
...values,
},
},
});
handleGetList(0, size);
};
/* 点击重置 */
const handleReset = () => {
dispatch({ type: 'Enforcement/resetSearch' });
handleGetList(0, 10);
};
/* 上传组件配置 */
const uploadProps = {
customRequest: file => {
const formData = new FormData();
formData.append('file', file.file);
dispatch({ type: 'Enforcement/importEventIllegal', payload: formData });
},
beforeUpload: file => {
const isLt50M = file.size / 1024 / 1024 < 50;
if (!isLt50M) {
message.error('文件大小需小于50M');
return false;
}
return isLt50M;
},
};
return (
<StyledPageContainer>
<StyledPageHeader border={true}>
<Upload {...uploadProps}>
<Button loading={loadingUpload} type="primary" icon={<UploadOutlined />}>
数据导入
</Button>
</Upload>
<Button
type="primary"
onClick={() => {
ExportFile('行政执法案件模版', services.exportEventIllegalTemplate, {});
}}
>
模版下载
</Button>
</StyledPageHeader>
<StyledPageContent>
<Card bordered={false}>
<SearchForm
dataSearch={dataSearch}
handleReset={handleReset}
handleFinish={handleFinish}
/>
<div className="mt-16">
<DataTable
loading={loading}
dispatch={dispatch}
handleGetList={handleGetList}
dataEnforcement={dataEnforcement}
/>
</div>
</Card>
</StyledPageContent>
<ModalEnforcement />
</StyledPageContainer>
);
};
export default connect(({ Enforcement, loading }) => ({
...Enforcement,
loading: loading.effects['Enforcement/getEnforcementList'],
loadingUpload: loading.effects['Enforcement/importEventIllegal'],
}))(Enforcement);
import { Tabs } from 'antd';
import React, { useState } from 'react';
import Place from './place';
import Complaint from './complaint';
import Enforcement from './enforcement';
import NetworkEvent from './networkEvent';
import { StyledWapperTab } from '@/components/style';
const Data = () => {
const [activeKey, setActiveKey] = useState('1');
const [dataTab, setDataTab] = useState(() => {
return [
{ label: '行政执法案件', value: '1' },
{ label: '基层网络事件', value: '2' },
{ label: '场所/网站备案信息', value: '3' },
{ label: '投诉举报事件', value: '4' },
];
});
return (
<StyledWapperTab>
<Tabs
defaultActiveKey={activeKey}
onChange={key => {
setActiveKey(key);
}}
>
{dataTab.map(item => {
return <Tabs.TabPane tab={item.label} key={item.value}></Tabs.TabPane>;
})}
</Tabs>
{activeKey === '1' ? <Enforcement /> : null}
{activeKey === '2' ? <NetworkEvent /> : null}
{activeKey === '3' ? <Place /> : null}
{activeKey === '4' ? <Complaint /> : null}
</StyledWapperTab>
);
};
export default Data;
import * as services from '@/services/data';
import moment from 'moment';
/* SerachParams */
const staticSearch = {
current: 1,
size: 10,
keyWord: undefined,
startDate: moment(new Date())
.add(-7, 'days')
.startOf('day'),
endDate: moment(new Date())
.add(0, 'days')
.endOf('day'),
};
export default {
namespace: 'Complaint',
state: {
dataSearch: {
...staticSearch,
},
dataComplaint: {
records: [],
current: 1,
size: 10,
total: 0,
totalPage: 0,
},
},
effects: {
/* 获取投诉举报列表 */
*getComplaintList({ payload }, { call, put, select }) {
const {
dataSearch,
dataSearch: { startDate, endDate },
} = yield select(state => state.Complaint);
try {
const res = yield call(services.getComplaintList, {
current: 1,
size: 10,
...dataSearch,
...payload,
startDate: moment(startDate).format('YYYY-MM-DD HH:mm:ss'),
endDate: moment(endDate).format('YYYY-MM-DD HH:mm:ss'),
});
if (res.code === 0) {
res.data.records =
res.data.records.map(item => {
return {
...(item.placeVo || {}),
...item,
};
}) || [];
yield put({
type: 'changeState',
payload: {
dataComplaint: res.data,
},
});
}
} catch (err) {
console.error(err);
}
},
},
reducers: {
changeState(state, { payload }) {
return {
...state,
...payload,
};
},
resetSearch(state, { payload }) {
return {
...state,
dataSearch: {
...staticSearch,
},
};
},
},
};
import * as services from '@/services/data';
import { message } from 'antd';
import moment from 'moment';
/* SerachParams */
const staticSearch = {
current: 1,
size: 10,
placeName: undefined,
company: undefined,
type: 'ALL',
yearDate: 'ALL',
};
export default {
namespace: 'Enforcement',
state: {
dataSearch: {
...staticSearch,
},
dataEnforcement: {
records: [],
current: 1,
size: 10,
total: 0,
totalPage: 0,
},
dataModal: {
modalType: '',
modalShow: false,
modalData: {},
},
enforcementInfo: {},
},
effects: {
/* 获取行政执法列表 */
*getEnforcementList({ payload }, { call, put, select }) {
const {
dataSearch,
dataSearch: { type, yearDate },
} = yield select(state => state.Enforcement);
try {
const res = yield call(services.getEventIllegalList, {
current: 1,
size: 10,
...dataSearch,
...payload,
type: type === 'ALL' ? '' : type,
yearDate: yearDate === 'ALL' ? '' : yearDate,
});
if (res.code === 0) {
res.data.records =
res.data.records.map(item => {
return {
...(item.placeVo || {}),
...item,
};
}) || [];
yield put({
type: 'changeState',
payload: {
dataEnforcement: res.data,
},
});
}
} catch (err) {
console.error(err);
}
},
/* 行政执法案件导入 */
*importEventIllegal({ payload }, { call, put, select }) {
try {
const res = yield call(services.importEventIllegal, payload);
if (res.code === 0) {
message.success('导入成功');
yield put({ type: 'getEnforcementList' });
}
} catch (err) {
console.error(err);
}
},
/* 获取详情 */
*getEventIllegalDetail({ payload }, { call, put, select }) {
try {
const res = yield call(services.getEventIllegalDetail, payload);
if (res.code === 0) {
yield put({ type: 'changeState', payload: { enforcementInfo: res.data } });
}
} catch (err) {
console.error(err);
}
},
},
reducers: {
changeState(state, { payload }) {
return {
...state,
...payload,
};
},
cancelModal(state, { payload }) {
return {
...state,
dataModal: {
modalType: '',
modalShow: false,
modalData: {},
},
};
},
resetSearch(state, { payload }) {
return {
...state,
dataSearch: {
...staticSearch,
},
};
},
},
};
import * as services from '@/services/data';
import moment from 'moment';
/* SerachParams */
const staticSearch = {
current: 1,
size: 10,
eventCode: undefined,
keyWord: undefined,
region: 'ALL',
sponsor: 'ALL',
handledBy: 'ALL',
sponsorOrg: 'ALL',
matterBig: 'ALL',
eventSource: 'ALL',
state: 'ALL',
startDate: moment(new Date())
.add(-7, 'days')
.startOf('day'),
endDate: moment(new Date())
.add(0, 'days')
.endOf('day'),
};
export default {
namespace: 'NetworkEvent',
state: {
dataSearch: {
...staticSearch,
},
dataNetworkEvent: {
records: [],
current: 1,
size: 10,
total: 0,
totalPage: 0,
},
dataModal: {
modalType: '',
modalShow: false,
modalData: {},
},
baseGridInfo: {}, // 基层网格详情
eventSourceList: [], // 事件源
matterBigList: [], // 事件类型
handledByList: [], // 处理人
sponsorList: [], // 发起人
sponsorOrgList: [], // 发起组织
},
effects: {
/* 获取基层网络列表 */
*getNetworkEventList({ payload }, { call, put, select }) {
const {
dataSearch,
dataSearch: {
region,
sponsor,
handledBy,
sponsorOrg,
matterBig,
eventSource,
state,
startDate,
endDate,
},
} = yield select(state => state.NetworkEvent);
try {
const res = yield call(services.getNetworkEventList, {
current: 1,
size: 10,
...dataSearch,
...payload,
startDate: moment(startDate).format('YYYY-MM-DD HH:mm:ss'),
endDate: moment(endDate).format('YYYY-MM-DD HH:mm:ss'),
region: region === 'ALL' ? '' : region,
sponsor: sponsor === 'ALL' ? '' : sponsor,
handledBy: handledBy === 'ALL' ? '' : handledBy,
sponsorOrg: sponsorOrg === 'ALL' ? '' : sponsorOrg,
matterBig: matterBig === 'ALL' ? '' : matterBig,
eventSource: eventSource === 'ALL' ? '' : eventSource,
state: state === 'ALL' ? '' : state,
});
if (res.code === 0) {
res.data.records =
res.data.records.map(item => {
return {
...(item.placeVo || {}),
...item,
};
}) || [];
yield put({
type: 'changeState',
payload: {
dataNetworkEvent: res.data,
},
});
}
} catch (err) {
console.error(err);
}
},
/* 获取搜索条件 */
*getBaseGridSponsor({ payload }, { call, put, select }) {
try {
const res = yield call(services.getBaseGridSponsor);
if (res.code === 0) {
res.data = res.data || {};
yield put({
type: 'changeState',
payload: {
eventSourceList: res.data.eventSourceList || [],
handledByList: res.data.handledByList || [],
sponsorList: res.data.sponsorList || [],
sponsorOrgList: res.data.sponsorOrgList || [],
matterBigList: res.data.matterBigList || [],
},
});
}
} catch (err) {
console.error(err);
}
},
/* 获取详情 */
*getBaseGridDetail({ payload }, { call, put, select }) {
try {
const res = yield call(services.getBaseGridDetail, payload);
if (res.code === 0) {
yield put({
type: 'changeState',
payload: { baseGridInfo: res.data || {} },
});
}
} catch (err) {
console.error(err);
}
},
},
reducers: {
changeState(state, { payload }) {
return {
...state,
...payload,
};
},
cancelModal(state, { payload }) {
return {
...state,
dataModal: {
modalType: '',
modalShow: false,
modalData: {},
},
};
},
resetSearch(state, { payload }) {
return {
...state,
dataSearch: {
...staticSearch,
},
};
},
},
};
import * as services from '@/services/data';
import { message } from 'antd';
/* SerachParams */
const staticSearch = {
current: 1,
size: 10,
name: undefined,
};
export default {
namespace: 'Place',
state: {
dataSearch: {
...staticSearch,
},
dataPlace: {
records: [],
current: 1,
size: 10,
total: 0,
totalPage: 0,
},
},
effects: {
/* 获取场所列表 */
*getPlaceList({ payload }, { call, put, select }) {
const { dataSearch } = yield select(state => state.Place);
try {
const res = yield call(services.getPlaceList, {
current: 1,
size: 10,
...dataSearch,
...payload,
});
if (res.code === 0) {
res.data.records = res.data.records || [];
yield put({
type: 'changeState',
payload: {
dataPlace: res.data,
},
});
}
} catch (err) {
console.error(err);
}
},
/* 场所导入 */
*importPlace({ payload }, { call, put, select }) {
try {
const res = yield call(services.importPlace, payload);
if (res.code === 0) {
message.success('导入成功');
yield put({ type: 'getPlaceList' });
}
} catch (err) {
console.error(err);
}
},
},
reducers: {
changeState(state, { payload }) {
return {
...state,
...payload,
};
},
resetSearch(state, { payload }) {
return {
...state,
dataSearch: {
...staticSearch,
},
};
},
},
};
/**
* Author: Charles
* Date: 2022.9.13
* Description: [基础网络事件列表]
*/
import React, { useEffect } from 'react';
import { connect } from 'umi';
import { Card, Form, Input, Button, Table, Tooltip, Select, DatePicker } from 'antd';
import { paginations, mapEventStatus, enumArea } from '@/constants';
import { StyledPageContainer, StyledPageContent, StyledEllipsisWrap } from '@/components/style';
import { ZoomInOutlined } from '@ant-design/icons';
import moment from 'moment';
import ModalNetworkEvent from './Modal/ModalNetworkEvent';
const { RangePicker } = DatePicker;
const FormItem = Form.Item;
const Option = Select.Option;
/* SearchForm */
const SearchForm = props => {
const [form] = Form.useForm();
const {
dataSearch,
dataSearch: { startDate, endDate },
handleReset,
handleFinish,
eventSourceList,
matterBigList,
handledByList,
sponsorList,
sponsorOrgList,
} = props;
useEffect(() => {
form.setFieldsValue({
...dataSearch,
createTime: [
startDate ? moment(startDate) : undefined,
endDate ? moment(endDate) : undefined,
],
});
}, [dataSearch]);
/* 点击搜索 */
const onFinish = values => {
handleFinish(values);
};
/* 点击重置 */
const onReset = () => {
handleReset();
};
return (
<Form name="Form_NetworkEvent" layout="inline" form={form} onFinish={onFinish}>
<FormItem label="事件编号" name="eventCode">
<Input placeholder="请输入事件编号" style={{ width: '200px' }} />
</FormItem>
<FormItem label="事件关键字" name="keyWord">
<Input placeholder="请输入事件关键字" style={{ width: '200px' }} />
</FormItem>
<FormItem label="区域" name="region">
<Select placeholder="请选择区域" style={{ width: '150px' }}>
<Option value="ALL">全部</Option>
{enumArea.map(item => {
return (
<Option key={item} value={item}>
{item}
</Option>
);
})}
</Select>
</FormItem>
<FormItem label="发起人" name="sponsor">
<Select placeholder="请选择发起人" style={{ width: '150px' }}>
<Option value="ALL">全部</Option>
{sponsorList.map(item => {
return (
<Option key={item} value={item}>
{item}
</Option>
);
})}
</Select>
</FormItem>
<FormItem label="处理人" name="handledBy">
<Select placeholder="请选择处理人" style={{ width: '150px' }}>
<Option value="ALL">全部</Option>
{handledByList.map(item => {
return (
<Option key={item} value={item}>
{item}
</Option>
);
})}
</Select>
</FormItem>
<FormItem label="发起组织" name="sponsorOrg">
<Select placeholder="请选择发起组织" style={{ width: '150px' }}>
<Option value="ALL">全部</Option>
{sponsorOrgList.map(item => {
return (
<Option key={item} value={item}>
{item}
</Option>
);
})}
</Select>
</FormItem>
<FormItem label="事件类型" name="matterBig">
<Select placeholder="请选择事件类型" style={{ width: '150px' }}>
<Option value="ALL">全部</Option>
{matterBigList.map(item => {
return (
<Option key={item} value={item}>
{item}
</Option>
);
})}
</Select>
</FormItem>
<FormItem label="事件来源" name="eventSource">
<Select placeholder="请选择事件来源" style={{ width: '150px' }}>
<Option value="ALL">全部</Option>
{eventSourceList.map(item => {
return (
<Option key={item} value={item}>
{item}
</Option>
);
})}
</Select>
</FormItem>
<FormItem label="事件状态" name="state">
<Select placeholder="请选择事件状态" style={{ width: '150px' }}>
<Option value="ALL">全部</Option>
{Object.values(mapEventStatus).map(item => {
const { label, value } = item;
return (
<Option key={value} value={value}>
{label}
</Option>
);
})}
</Select>
</FormItem>
<FormItem label="发起时间" name="createTime">
<RangePicker allowClear={false} style={{ width: '320px' }} />
</FormItem>
<FormItem>
<Button type="primary" htmlType="submit" className="mr-15">
搜索
</Button>
<Button className="mr-15" htmlType="button" onClick={onReset}>
重置
</Button>
</FormItem>
</Form>
);
};
/* DataTable */
const DataTable = props => {
const {
dispatch,
loading,
handleGetList,
dataNetworkEvent: { records = [], current, size, total },
} = props;
/* 点击分页 */
const handlePageChange = (current, size) => {
handleGetList(current, size);
};
const columns = [
{
title: '序号',
dataIndex: 'id',
align: 'center',
fixed: 'left',
width: 80,
render(t, r, idx) {
return (current - 1) * size + idx + 1;
},
},
{
title: '事件编号',
dataIndex: 'eventCode',
align: 'center',
width: 180,
render: (t, r) => {
return t || '-';
},
},
{
title: '镇街',
dataIndex: 'town',
align: 'center',
width: 180,
render: (t, r) => {
return t || '-';
},
},
{
title: '村庄',
dataIndex: 'village',
align: 'center',
width: 180,
render: (t, r) => {
return t || '-';
},
},
{
title: '网络',
dataIndex: 'grid',
align: 'center',
width: 160,
render: (t, r) => {
return t || '-';
},
},
{
title: '发起人',
dataIndex: 'sponsor',
align: 'center',
width: 140,
},
{
title: '事件来源',
dataIndex: 'eventSource',
align: 'center',
width: 140,
},
{
title: '事件级别',
dataIndex: 'matterLevel',
align: 'center',
width: 140,
},
{
title: '事件大类',
dataIndex: 'matterBig',
align: 'center',
width: 140,
},
{
title: '事件小类',
dataIndex: 'matterSmall',
align: 'center',
width: 140,
},
{
title: '事件描述',
dataIndex: 'matterDescribe',
align: 'center',
width: 220,
render: (t, r) => {
return t ? (
<Tooltip placement="top" title={t}>
<StyledEllipsisWrap maxLine={2}>{t}</StyledEllipsisWrap>
</Tooltip>
) : (
'-'
);
},
},
{
title: '发起事件时间',
dataIndex: 'origTime',
align: 'center',
width: 200,
},
{
title: '事件状态',
dataIndex: 'stateName',
align: 'center',
width: 160,
},
{
title: '操作',
align: 'center',
fixed: 'right',
width: 80,
render(t, r) {
return (
<Tooltip placement="top" title="查看详情">
<ZoomInOutlined
style={{ cursor: 'pointer', fontSize: 18 }}
onClick={() => {
dispatch({
type: 'NetworkEvent/changeState',
payload: {
dataModal: {
modalType: 'Network_Event_Modal',
modalShow: true,
modalData: r,
},
},
});
}}
/>
</Tooltip>
);
},
},
];
const pagination = {
...paginations,
total: total,
pageSize: size,
current,
showSizeChanger: total > 10,
onChange: (current, pageSize) => {
handlePageChange(current, pageSize);
},
onShowSizeChange: (current, pageSize) => {
handlePageChange(1, pageSize);
},
showTotal(total) {
return `总共 ${total} 条数据`;
},
};
return (
<Table
rowKey="id"
loading={loading}
dataSource={records}
columns={columns}
pagination={pagination}
scroll={{ x: 2000, y: `calc(100vh - 446px)` }}
/>
);
};
/* Main */
const NetworkEvent = props => {
const {
dispatch,
loading,
dataNetworkEvent,
dataNetworkEvent: { size },
} = props;
useEffect(() => {
handleGetList(1, 10);
dispatch({ type: 'NetworkEvent/getBaseGridSponsor' });
}, []);
/* 列表 */
const handleGetList = (current, size) => {
dispatch({
type: 'NetworkEvent/getNetworkEventList',
payload: {
current: current || 1,
size: size || 10,
},
});
};
/* 点击搜索 */
const handleFinish = values => {
const { createTime } = values;
dispatch({
type: 'NetworkEvent/changeState',
payload: {
dataSearch: {
...values,
startDate: createTime[0]
? moment(createTime[0].startOf('day').valueOf()).format('YYYY-MM-DD HH:mm:ss')
: undefined,
endDate: createTime[1]
? moment(createTime[1].endOf('day').valueOf()).format('YYYY-MM-DD HH:mm:ss')
: undefined,
createTime: undefined,
},
},
});
handleGetList(0, size);
};
/* 点击重置 */
const handleReset = () => {
dispatch({ type: 'NetworkEvent/resetSearch' });
handleGetList(0, 10);
};
return (
<StyledPageContainer>
<StyledPageContent>
<Card bordered={false}>
<SearchForm handleReset={handleReset} handleFinish={handleFinish} {...props} />
<div className="mt-16">
<DataTable
dispatch={dispatch}
loading={loading}
handleGetList={handleGetList}
dataNetworkEvent={dataNetworkEvent}
/>
</div>
</Card>
</StyledPageContent>
<ModalNetworkEvent />
</StyledPageContainer>
);
};
export default connect(({ NetworkEvent, loading }) => ({
...NetworkEvent,
loading: loading.effects['NetworkEvent/getNetworkEventList'],
}))(NetworkEvent);
/**
* Author: Charles
* Date: 2022.9.13
* Description: [场所列表]
*/
import React, { useEffect } from 'react';
import { connect } from 'umi';
import { Card, Form, Input, Button, Table, Tooltip, Upload, message } from 'antd';
import { paginations } from '@/constants';
import {
StyledPageContainer,
StyledPageContent,
StyledEllipsisWrap,
StyledPageHeader,
} from '@/components/style';
import { ZoomInOutlined, UploadOutlined } from '@ant-design/icons';
import * as services from '@/services/data';
import { ExportFile } from '@/utils/utils';
const FormItem = Form.Item;
/* SearchForm */
const SearchForm = props => {
const [form] = Form.useForm();
const { dataSearch, handleReset, handleFinish } = props;
useEffect(() => {
form.setFieldsValue({
...dataSearch,
});
}, [dataSearch]);
/* 点击搜索 */
const onFinish = values => {
handleFinish(values);
};
/* 点击重置 */
const onReset = () => {
handleReset();
};
return (
<Form name="Form_Place" layout="inline" form={form} onFinish={onFinish}>
<FormItem label="公司名称" name="name">
<Input placeholder="请输入公司名称" style={{ width: '250px' }} />
</FormItem>
<FormItem>
<Button type="primary" htmlType="submit" className="mr-15">
搜索
</Button>
<Button className="mr-15" htmlType="button" onClick={onReset}>
重置
</Button>
</FormItem>
</Form>
);
};
/* DataTable */
const DataTable = props => {
const {
loading,
handleGetList,
dataPlace: { records = [], current, size, total },
} = props;
/* 点击分页 */
const handlePageChange = (current, size) => {
handleGetList(current, size);
};
const columns = [
{
title: '序号',
dataIndex: 'id',
align: 'center',
fixed: 'left',
width: 80,
render(t, r, idx) {
return (current - 1) * size + idx + 1;
},
},
{
title: '公司名称',
dataIndex: 'name',
align: 'center',
width: 200,
render: (t, r) => {
return t ? (
<Tooltip placement="top" title={t}>
<StyledEllipsisWrap maxLine={1}>{t}</StyledEllipsisWrap>
</Tooltip>
) : (
'-'
);
},
},
{
title: '公司地址',
dataIndex: 'address',
align: 'center',
width: 220,
render: (t, r) => {
return t ? (
<Tooltip placement="top" title={t}>
<StyledEllipsisWrap maxLine={2}>{t}</StyledEllipsisWrap>
</Tooltip>
) : (
'-'
);
},
},
{
title: '通讯地址',
dataIndex: 'postalAddress',
align: 'center',
width: 220,
render: (t, r) => {
return t ? (
<Tooltip placement="top" title={t}>
<StyledEllipsisWrap maxLine={2}>{t}</StyledEllipsisWrap>
</Tooltip>
) : (
'-'
);
},
},
{
title: '所属地区',
dataIndex: 'region',
align: 'center',
width: 160,
render: (t, r) => {
return t ? (
<Tooltip placement="top" title={t}>
<StyledEllipsisWrap maxLine={2}>{t}</StyledEllipsisWrap>
</Tooltip>
) : (
'-'
);
},
},
{
title: '负责人',
dataIndex: 'person',
align: 'center',
width: 150,
},
{
title: '身份证号码',
dataIndex: 'personCode',
align: 'center',
width: 220,
},
{
title: '负责人电话',
dataIndex: 'personTel',
align: 'center',
width: 150,
},
{
title: '联系人',
dataIndex: 'contacts',
align: 'center',
width: 150,
},
{
title: '联系人电话',
dataIndex: 'contactsTel',
align: 'center',
width: 180,
},
{
title: '主营范围',
dataIndex: 'mainScope',
align: 'center',
width: 180,
render: (t, r) => {
return t ? (
<Tooltip placement="top" title={t}>
<StyledEllipsisWrap maxLine={2}>{t}</StyledEllipsisWrap>
</Tooltip>
) : (
'-'
);
},
},
{
title: '备注',
dataIndex: 'remarks',
align: 'center',
width: 200,
render: (t, r) => {
return t ? (
<Tooltip placement="top" title={t}>
<StyledEllipsisWrap maxLine={2}>{t}</StyledEllipsisWrap>
</Tooltip>
) : (
'-'
);
},
},
// {
// title: '操作',
// align: 'center',
// fixed: 'right',
// width: 80,
// render(t, r) {
// return (
// <Tooltip placement="top" title="查看详情">
// <ZoomInOutlined style={{ cursor: 'pointer', fontSize: 18 }} onClick={() => {}} />
// </Tooltip>
// );
// },
// },
];
const pagination = {
...paginations,
total: total,
pageSize: size,
current,
showSizeChanger: total > 10,
onChange: (current, pageSize) => {
handlePageChange(current, pageSize);
},
onShowSizeChange: (current, pageSize) => {
handlePageChange(1, pageSize);
},
showTotal(total) {
return `总共 ${total} 条数据`;
},
};
return (
<Table
rowKey="id"
loading={loading}
dataSource={records}
columns={columns}
pagination={pagination}
scroll={{ x: 2000, y: `calc(100vh - 400px)` }}
/>
);
};
/* Main */
const Place = props => {
const {
dispatch,
loading,
loadingUpload,
dataSearch,
dataPlace,
dataPlace: { size },
} = props;
useEffect(() => {
handleGetList(1, 10);
}, []);
/* 列表 */
const handleGetList = (current, size) => {
dispatch({
type: 'Place/getPlaceList',
payload: {
current: current || 1,
size: size || 10,
},
});
};
/* 点击搜索 */
const handleFinish = values => {
const { name } = values;
dispatch({
type: 'Place/changeState',
payload: {
dataSearch: { name },
},
});
handleGetList(0, size);
};
/* 点击重置 */
const handleReset = () => {
dispatch({ type: 'Place/resetSearch' });
handleGetList(0, 10);
};
/* 上传组件配置 */
const uploadProps = {
customRequest: file => {
const formData = new FormData();
formData.append('file', file.file);
dispatch({ type: 'Place/importPlace', payload: formData });
},
beforeUpload: file => {
const isLt50M = file.size / 1024 / 1024 < 50;
if (!isLt50M) {
message.error('文件大小需小于50M');
return false;
}
return isLt50M;
},
};
return (
<StyledPageContainer>
<StyledPageHeader border={true}>
<Upload {...uploadProps}>
<Button loading={loadingUpload} type="primary" icon={<UploadOutlined />}>
数据导入
</Button>
</Upload>
<Button
type="primary"
onClick={() => {
ExportFile('场所/网站备案信息模版', services.exportPlaceTemplate, {});
}}
>
模版下载
</Button>
</StyledPageHeader>
<StyledPageContent>
<Card bordered={false}>
<SearchForm
dataSearch={dataSearch}
handleReset={handleReset}
handleFinish={handleFinish}
/>
<div className="mt-16">
<DataTable loading={loading} handleGetList={handleGetList} dataPlace={dataPlace} />
</div>
</Card>
</StyledPageContent>
</StyledPageContainer>
);
};
export default connect(({ Place, loading }) => ({
...Place,
loading: loading.effects['Place/getPlaceList'],
loadingUpload: loading.effects['Place/importPlace'],
}))(Place);
/**
* Author: Charles
* Date: 2022.9.13
* Description: [事件风险详情]
*/
import React, { useEffect } from 'react';
import { connect } from 'umi';
import { Modal, Tooltip, Table } from 'antd';
import { paginations, mapStatus, mapRiskType, mapRiskSourceType } from '@/constants';
import { StyledEllipsisWrap } from '@/components/style';
const ModalEventInfo = props => {
const {
dispatch,
loading,
dataModal: {
modalType,
modalShow,
modalData: { id },
},
dataEventRiskList,
} = props;
useEffect(() => {
if (modalType === 'EVENT_INFO_MODAL' && modalShow) {
dispatch({ type: 'Event/getEventRiskList', payload: { eventId: id } });
}
}, [modalType, modalShow]);
const columns = [
{
title: '序号',
dataIndex: 'id',
align: 'center',
fixed: 'left',
width: 80,
render(t, r, idx) {
return idx + 1;
},
},
{
title: '风险源名称',
dataIndex: 'riskName',
align: 'center',
width: 160,
render: (t, r) => {
return (
<Tooltip placement="top" title={t}>
<StyledEllipsisWrap maxLine={1}>{t}</StyledEllipsisWrap>
</Tooltip>
);
},
},
{
title: '风险源地址',
dataIndex: 'riskUrl',
align: 'center',
width: 160,
render: (t, r) => {
return (
<Tooltip placement="top" title={t}>
<StyledEllipsisWrap maxLine={2}>{t}</StyledEllipsisWrap>
</Tooltip>
);
},
},
{
title: '风险站点',
dataIndex: 'riskHost',
align: 'center',
width: 160,
render: (t, r) => {
return (
<Tooltip placement="top" title={t}>
<StyledEllipsisWrap maxLine={2}>{t}</StyledEllipsisWrap>
</Tooltip>
);
},
},
{
title: '风险类型',
dataIndex: 'riskType',
align: 'center',
width: 160,
render: (t, r) => {
return mapRiskType[t] ? mapRiskType[t].label : '-';
},
},
{
title: '风险源类型',
dataIndex: 'fileFormat',
align: 'center',
width: 160,
render: (t, r) => {
return mapRiskSourceType[t] ? mapRiskSourceType[t].label : '-';
},
},
{
title: '检测时间',
dataIndex: 'checkTime',
align: 'center',
width: 160,
render: (t, r) => {
return t || '-';
},
},
{
title: '处理状态',
dataIndex: 'status',
align: 'center',
fixed: 'right',
width: 100,
render: (t, r) => {
return mapStatus[t] ? mapStatus[t].label : '-';
},
},
];
const pagination = {
...paginations,
total: dataEventRiskList.length,
pageSize: 500,
current: 1,
showSizeChanger: dataEventRiskList.length > 500,
showTotal(total) {
return `总共 ${total} 条数据`;
},
};
return (
<Modal
title="事件风险详情"
placement="right"
width={1000}
maskClosable={false}
onCancel={() => {
dispatch({ type: 'Event/cancelModal' });
}}
visible={modalType === 'EVENT_INFO_MODAL' && modalShow}
footer={null}
>
<Table
rowKey="id"
loading={loading}
dataSource={dataEventRiskList}
columns={columns}
pagination={pagination}
scroll={{ x: 1500, y: 600 }}
/>
</Modal>
);
};
export default connect(({ Event, loading }) => ({
...Event,
loading: loading.effects['Event/getEventRiskList'],
}))(ModalEventInfo);
/**
* Author: Charles
* Date: 2022.9.13
* Description: [事件日志]
*/
import React, { useEffect } from 'react';
import { connect } from 'umi';
import { Modal, Tooltip, Table } from 'antd';
import { paginations, mapStatus, mapRiskType, mapRiskSourceType } from '@/constants';
import { StyledEllipsisWrap } from '@/components/style';
const ModalEventLog = props => {
const {
dispatch,
loading,
dataModal: {
modalType,
modalShow,
modalData: { id },
},
dataEventLogList: { records = [], current, size, total },
} = props;
useEffect(() => {
if (modalType === 'View_Event_Log' && modalShow) {
dispatch({ type: 'Event/getEventRiskLog', payload: { eventId: id } });
}
}, [modalType, modalShow]);
/* 点击分页 */
const handlePageChange = (current, size) => {
dispatch({ type: 'Event/getEventRiskLog', payload: { eventId: id, current, size } });
};
const columns = [
{
title: '序号',
dataIndex: 'id',
align: 'center',
fixed: 'left',
width: 60,
render(t, r, idx) {
return idx + 1;
},
},
{
title: '发送内容',
dataIndex: 'content',
align: 'center',
width: 160,
render: (t, r) => {
return (
<Tooltip placement="top" title={t}>
<StyledEllipsisWrap maxLine={1}>{t || '-'}</StyledEllipsisWrap>
</Tooltip>
);
},
},
{
title: '接收者信息',
dataIndex: 'recipient',
align: 'center',
width: 160,
render: (t, r) => {
return r.type == 2
? `${r.recipientName || '-'}${r.recipient || '-'})`
: `${r.recipientName || '-'}`;
},
},
{
title: '状态',
dataIndex: 'status',
align: 'center',
width: 120,
},
{
title: '失败原因',
dataIndex: 'statusMessage:',
align: 'center',
width: 160,
render: (t, r) => {
return (
<Tooltip placement="top" title={t}>
<StyledEllipsisWrap maxLine={1}>{t || '-'}</StyledEllipsisWrap>
</Tooltip>
);
},
},
];
const pagination = {
...paginations,
total: total,
pageSize: size,
current,
showSizeChanger: total > 10,
onChange: (current, pageSize) => {
handlePageChange(current, pageSize);
},
onShowSizeChange: (current, pageSize) => {
handlePageChange(1, pageSize);
},
showTotal(total) {
return `总共 ${total} 条数据`;
},
};
return (
<Modal
title="事件日志"
placement="right"
width={1000}
maskClosable={false}
onCancel={() => {
dispatch({ type: 'Event/cancelModal' });
}}
visible={modalType === 'View_Event_Log' && modalShow}
footer={null}
>
<Table
rowKey="id"
loading={loading}
dataSource={records}
columns={columns}
pagination={pagination}
scroll={{ x: 1500, y: 600 }}
/>
</Modal>
);
};
export default connect(({ Event, loading }) => ({
...Event,
loading: loading.effects['Event/getEventRiskLog'],
}))(ModalEventLog);
/**
* Author: Charles
* Date: 2022.9.13
* Description: [事件列表]
*/
import React, { useEffect } from 'react';
import { connect } from 'umi';
import { Card, Form, Input, Button, Table, Select, DatePicker, Tooltip, Divider } from 'antd';
import { paginations, mapStatus, mapEventLevel, mapEventType, enumRiskLabel } from '@/constants';
import { ZoomInOutlined, SearchOutlined } from '@ant-design/icons';
import { StyledPageContainer, StyledPageContent, StyledEllipsisWrap } from '@/components/style';
import ModalEventInfo from './ModalEventInfo';
import ModalEventLog from './ModalEventLog';
import moment from 'moment';
const { RangePicker } = DatePicker;
const FormItem = Form.Item;
const { Option } = Select;
/* SearchForm */
const SearchForm = props => {
const [form] = Form.useForm();
const {
dataSearch,
dataSearch: { startTime, endTime },
handleReset,
handleFinish,
} = props;
useEffect(() => {
form.setFieldsValue({
...dataSearch,
createTime: [
startTime ? moment(startTime) : undefined,
endTime ? moment(endTime) : undefined,
],
});
}, [dataSearch]);
/* 点击搜索 */
const onFinish = values => {
handleFinish(values);
};
/* 点击重置 */
const onReset = () => {
handleReset();
};
return (
<Form name="Form_Event" layout="inline" form={form} onFinish={onFinish}>
<FormItem label="公司名称" name="companyName">
<Input placeholder="请输入公司名称" style={{ width: '250px' }} />
</FormItem>
<FormItem label="事件类型" name="eventType">
<Select placeholder="请选择事件类型" style={{ width: '150px' }}>
<Option value="ALL">全部</Option>
{Object.values(mapEventType).map(item => {
const { label, value } = item;
return (
<Option key={value} value={value}>
{label}
</Option>
);
})}
</Select>
</FormItem>
<FormItem label="事件等级" name="eventLevel">
<Select placeholder="请选择事件等级" style={{ width: '150px' }}>
<Option value="ALL">全部</Option>
{Object.values(mapEventLevel).map(item => {
const { label, value } = item;
return (
<Option key={value} value={value}>
{label}
</Option>
);
})}
</Select>
</FormItem>
<FormItem label="处理状态" name="status">
<Select placeholder="请选择处理状态" style={{ width: '150px' }}>
<Option value="ALL">全部</Option>
{Object.values(mapStatus).map(item => {
const { label, value } = item;
return (
<Option key={value} value={value}>
{label}
</Option>
);
})}
</Select>
</FormItem>
<FormItem label="创建时间" name="createTime">
<RangePicker allowClear={false} />
</FormItem>
<FormItem>
<Button type="primary" htmlType="submit" className="mr-15">
搜索
</Button>
<Button className="mr-15" htmlType="button" onClick={onReset}>
重置
</Button>
</FormItem>
</Form>
);
};
/* DataTable */
const DataTable = props => {
const {
dispatch,
loading,
handleGetList,
dataEvent: { records = [], current, size, total },
} = props;
/* 点击分页 */
const handlePageChange = (current, size) => {
handleGetList(current, size);
};
const columns = [
{
title: '序号',
dataIndex: 'id',
align: 'center',
fixed: 'left',
width: 80,
render(t, r, idx) {
return (current - 1) * size + idx + 1;
},
},
{
title: '事件编号',
dataIndex: 'serialNumber',
align: 'center',
width: 220,
},
{
title: '事件类型',
dataIndex: 'eventType',
align: 'center',
width: 160,
render: (t, r) => {
return mapEventType[t] ? mapEventType[t].label : '-';
},
},
{
title: '事件等级',
dataIndex: 'eventLevel',
align: 'center',
width: 160,
render: (t, r) => {
return mapEventLevel[t] ? mapEventLevel[t].label : '-';
},
},
{
title: '公司名称',
dataIndex: 'companyName',
align: 'center',
width: 160,
render: (t, r) => {
return t ? (
<Tooltip placement="top" title={t}>
<StyledEllipsisWrap maxLine={1}>{t}</StyledEllipsisWrap>
</Tooltip>
) : (
'-'
);
},
},
{
title: '公司地址',
dataIndex: 'companyAddress',
align: 'center',
width: 260,
render: (t, r) => {
return t ? (
<Tooltip placement="top" title={t}>
<StyledEllipsisWrap maxLine={2}>{t}</StyledEllipsisWrap>
</Tooltip>
) : (
'-'
);
},
},
{
title: '风险站点',
dataIndex: 'riskHost',
align: 'center',
width: 160,
render: (t, r) => {
return t ? (
<Tooltip placement="top" title={t}>
<StyledEllipsisWrap maxLine={2}>{t}</StyledEllipsisWrap>
</Tooltip>
) : (
'-'
);
},
},
{
title: '风险数量',
dataIndex: 'riskNumber',
align: 'center',
width: 120,
},
{
title: '风险标签',
dataIndex: 'riskLabel',
align: 'center',
width: 120,
render: (t, r) => {
let riskLabel = [];
if (t) {
for (let i = 0; i < enumRiskLabel.length; i++) {
for (let j = 0; j < t.split(',').length; j++) {
if (enumRiskLabel[i].value === t.split(',')[j]) {
riskLabel.push(enumRiskLabel[i].label);
}
}
}
}
return t ? riskLabel.join(',') : '-';
},
},
{
title: '限期整改',
dataIndex: 'deadlineDesc',
align: 'center',
width: 120,
},
{
title: '创建时间',
dataIndex: 'createTime',
align: 'center',
width: 160,
},
{
title: '处理状态',
dataIndex: 'status',
align: 'center',
width: 100,
render: (t, r) => {
return mapStatus[t] ? mapStatus[t].label : '-';
},
},
{
title: '操作',
align: 'center',
fixed: 'right',
width: 120,
render(t, r) {
return (
<>
<Tooltip placement="top" title="查看详情">
<ZoomInOutlined
style={{ cursor: 'pointer', fontSize: 18 }}
onClick={() => {
dispatch({
type: 'Event/getEventRiskLog',
payload: { eventId: r.id },
});
dispatch({
type: 'Event/changeState',
payload: {
dataModal: {
modalType: 'EVENT_INFO_MODAL',
modalShow: true,
modalData: r,
},
},
});
}}
/>
</Tooltip>
<Divider type="vertical" style={{ margin: '0 16px' }} />
<Tooltip
placement="top"
title="查看日志"
onClick={() => {
dispatch({
type: 'Event/changeState',
payload: {
dataModal: {
modalType: 'View_Event_Log',
modalShow: true,
modalData: r,
},
},
});
}}
>
<SearchOutlined style={{ cursor: 'pointer', fontSize: 20 }} />
</Tooltip>
</>
);
},
},
];
const pagination = {
...paginations,
total: total,
pageSize: size,
current,
showSizeChanger: total > 10,
onChange: (current, pageSize) => {
handlePageChange(current, pageSize);
},
onShowSizeChange: (current, pageSize) => {
handlePageChange(1, pageSize);
},
showTotal(total) {
return `总共 ${total} 条数据`;
},
};
return (
<Table
rowKey="id"
loading={loading}
dataSource={records}
columns={columns}
pagination={pagination}
scroll={{ x: 1500, y: `calc(100vh - 340px)` }}
/>
);
};
/* Main */
const Event = props => {
const {
dispatch,
loading,
dataSearch,
dataEvent,
dataEvent: { size },
} = props;
useEffect(() => {
handleGetList(1, 10);
}, []);
/* 账号列表 */
const handleGetList = (current, size) => {
dispatch({
type: 'Event/getEventList',
payload: {
current: current || 1,
size: size || 10,
},
});
};
/* 点击搜索 */
const handleFinish = values => {
const { status, eventType, eventLevel, companyName, createTime } = values;
dispatch({
type: 'Event/changeState',
payload: {
dataSearch: {
status,
eventType,
eventLevel,
companyName,
startTime: createTime[0]
? moment(createTime[0].startOf('day').valueOf()).format('YYYY-MM-DD HH:mm:ss')
: undefined,
endTime: createTime[1]
? moment(createTime[1].endOf('day').valueOf()).format('YYYY-MM-DD HH:mm:ss')
: undefined,
},
},
});
handleGetList(0, size);
};
/* 点击重置 */
const handleReset = () => {
dispatch({ type: 'Event/resetSearch' });
handleGetList(0, 10);
};
return (
<StyledPageContainer>
<StyledPageContent>
<Card bordered={false}>
<SearchForm
dataSearch={dataSearch}
handleReset={handleReset}
handleFinish={handleFinish}
/>
<div className="mt-16">
<DataTable
dispatch={dispatch}
loading={loading}
handleGetList={handleGetList}
dataEvent={dataEvent}
/>
</div>
</Card>
</StyledPageContent>
{/* 事件详情 */}
<ModalEventInfo />
{/* 事件日志 */}
<ModalEventLog />
</StyledPageContainer>
);
};
export default connect(({ Event, loading }) => ({
...Event,
loading: loading.effects['Event/getEventList'],
}))(Event);
import * as services from '@/services/incident';
import moment from 'moment';
/* SerachParams */
const staticSearch = {
current: 1,
size: 10,
status: 'ALL',
eventType: 'ALL',
eventLevel: 'ALL',
companyName: undefined,
startTime: moment(new Date())
.add(-7, 'days')
.startOf('day'),
endTime: moment(new Date())
.add(0, 'days')
.endOf('day'),
};
export default {
namespace: 'Event',
state: {
dataSearch: {
...staticSearch,
},
dataEvent: {
records: [],
current: 1,
size: 10,
total: 0,
totalPage: 0,
},
dataEventRiskList: [],
dataModal: {
modalType: '',
modalShow: false,
modalData: {},
},
dataEventLogList: {
records: [],
current: 1,
size: 10,
total: 0,
totalPage: 0,
},
},
effects: {
/* 获取事件列表 */
*getEventList({ payload }, { call, put, select }) {
const {
dataSearch,
dataSearch: { status, eventType, eventLevel, startTime, endTime },
} = yield select(state => state.Event);
try {
const res = yield call(services.getEventList, {
current: 1,
size: 10,
...dataSearch,
...payload,
startTime: moment(startTime).format('YYYY-MM-DD HH:mm:ss'),
endTime: moment(endTime).format('YYYY-MM-DD HH:mm:ss'),
status: status === 'ALL' ? '' : status,
eventType: eventType === 'ALL' ? '' : eventType,
eventLevel: eventLevel === 'ALL' ? '' : eventLevel,
});
if (res.code === 0) {
res.data.records = res.data.records || [];
yield put({
type: 'changeState',
payload: {
dataEvent: res.data,
},
});
}
} catch (err) {
console.error(err);
}
},
/* 事件详情 */
*getEventRiskList({ payload }, { call, put, select }) {
try {
const res = yield call(services.getEventRiskList, {
current: 1,
size: 10,
...payload,
});
if (res.code === 0) {
res.data.records = res.data.records || [];
yield put({
type: 'changeState',
payload: {
dataEventRiskList: res.data,
},
});
}
} catch (err) {
console.error(err);
}
},
/* 事件日志 */
*getEventRiskLog({ payload }, { call, put, select }) {
try {
const res = yield call(services.getEventRiskLog, {
current: 1,
size: 10,
...payload,
});
if (res.code === 0) {
res.data.records = res.data.records || [];
yield put({
type: 'changeState',
payload: {
dataEventLogList: res.data,
},
});
}
} catch (err) {
console.error(err);
}
},
},
reducers: {
changeState(state, { payload }) {
return {
...state,
...payload,
};
},
cancelModal(state, { payload }) {
return {
...state,
dataModal: {
modalType: '',
modalShow: false,
modalData: {},
},
};
},
resetSearch(state, { payload }) {
return {
...state,
dataSearch: {
...staticSearch,
},
};
},
},
};
/**
* Author: wjw
* Date: 2019.05.13
* Description: '图片预览'
*/
import React, { useState, useEffect } from 'react';
import { connect } from 'umi';
import { Modal } from 'antd';
const ModalPreImg = props => {
const {
preImgDataModal: {
modalType,
modalShow,
modalData: { imgUrl, title, type = 'imgurl' },
},
width,
dispatch,
} = props;
const params = {
width,
};
// const QRCode = require('qrcode.react');
const handleCancel = () => {
const payload = {
preImgDataModal: {
modalType: '',
modalShow: false,
modalData: {},
},
};
dispatch({ type: 'global/changeState', payload });
};
return (
<Modal
zIndex={10001}
{...params}
visible={modalType === 'PREVIEWIMG' && modalShow}
footer={null}
getContainer={()=> document.body}
onCancel={() => {
handleCancel();
}}
>
<div style={{ textAlign: 'center' }}>
<p>{title}</p>
{type === 'imgurl' && imgUrl && (
<img alt="example" style={{ width: '100%' }} src={imgUrl} />
)}
{/* {type === 'qrcode' && (
<div style={{ textAlign: 'center' }}>
{imgUrl ? <QRCode value={imgUrl} style={{ width: '250px', height: '250px' }} /> : ''}
</div>
)} */}
</div>
</Modal>
);
};
const mapStateToProps = ({ global }) => {
return {
preImgDataModal: global.preImgDataModal,
};
};
export default connect(mapStateToProps)(ModalPreImg);
/**
* Author: Charles
* Date: 2022.9.13
* Description: [风险列表]
*/
import React, { useEffect } from 'react';
import { connect } from 'umi';
import {
Card,
Form,
Input,
Button,
Table,
Select,
DatePicker,
Tooltip,
Upload,
message,
} from 'antd';
import { paginations, mapStatus, mapRiskType, mapRiskSourceType } from '@/constants';
import {
StyledPageContainer,
StyledPageContent,
StyledEllipsisWrap,
StyledPageHeader,
} from '@/components/style';
import { UploadOutlined } from '@ant-design/icons';
import moment from 'moment';
import * as services from '@/services/risk';
import { ExportFile } from '@/utils/utils';
const { RangePicker } = DatePicker;
const FormItem = Form.Item;
const { Option } = Select;
/* SearchForm */
const SearchForm = props => {
const [form] = Form.useForm();
const {
dataSearch,
dataSearch: { startTime, endTime },
handleReset,
handleFinish,
} = props;
useEffect(() => {
form.setFieldsValue({
...dataSearch,
createTime: [
startTime ? moment(startTime) : undefined,
endTime ? moment(endTime) : undefined,
],
});
}, [dataSearch]);
/* 点击搜索 */
const onFinish = values => {
handleFinish(values);
};
/* 点击重置 */
const onReset = () => {
handleReset();
};
return (
<Form name="Form_Risk" layout="inline" form={form} onFinish={onFinish}>
<FormItem label="网站地址" name="riskHost">
<Input placeholder="请输入网站地址" style={{ width: '250px' }} />
</FormItem>
<FormItem label="风险类型" name="riskType">
<Select placeholder="请选择风险类型" style={{ width: '150px' }}>
<Option value="ALL">全部</Option>
{Object.values(mapRiskType).map(item => {
const { label, value } = item;
return (
<Option key={value} value={value}>
{label}
</Option>
);
})}
</Select>
</FormItem>
<FormItem label="风险源类型" name="fileFormat">
<Select placeholder="请选择风险源类型" style={{ width: '150px' }}>
<Option value="ALL">全部</Option>
{Object.values(mapRiskSourceType).map(item => {
const { label, value } = item;
return (
<Option key={value} value={value}>
{label}
</Option>
);
})}
</Select>
</FormItem>
<FormItem label="处理状态" name="status">
<Select placeholder="请选择处理状态" style={{ width: '150px' }}>
<Option value="ALL">全部</Option>
{Object.values(mapStatus).map(item => {
const { label, value } = item;
return (
<Option key={value} value={value}>
{label}
</Option>
);
})}
</Select>
</FormItem>
<FormItem label="检测时间" name="createTime">
<RangePicker allowClear={false} />
</FormItem>
<FormItem>
<Button type="primary" htmlType="submit" className="mr-15">
搜索
</Button>
<Button className="mr-15" htmlType="button" onClick={onReset}>
重置
</Button>
</FormItem>
</Form>
);
};
/* DataTable */
const DataTable = props => {
const {
loading,
handleGetList,
dataRisk: { records = [], current, size, total },
} = props;
/* 点击分页 */
const handlePageChange = (current, size) => {
handleGetList(current, size);
};
const columns = [
{
title: '序号',
dataIndex: 'id',
align: 'center',
fixed: 'left',
width: 80,
render(t, r, idx) {
return (current - 1) * size + idx + 1;
},
},
{
title: '风险源名称',
dataIndex: 'riskName',
align: 'center',
width: 160,
render: (t, r) => {
return (
<Tooltip placement="top" title={t}>
<StyledEllipsisWrap maxLine={1}>{t}</StyledEllipsisWrap>
</Tooltip>
);
},
},
{
title: '风险源地址',
dataIndex: 'riskUrl',
align: 'center',
width: 160,
render: (t, r) => {
return (
<Tooltip placement="top" title={t}>
<StyledEllipsisWrap maxLine={2}>{t}</StyledEllipsisWrap>
</Tooltip>
);
},
},
{
title: '风险站点',
dataIndex: 'riskHost',
align: 'center',
width: 160,
render: (t, r) => {
return (
<Tooltip placement="top" title={t}>
<StyledEllipsisWrap maxLine={2}>{t}</StyledEllipsisWrap>
</Tooltip>
);
},
},
{
title: '关键词',
dataIndex: 'riskKeyword',
align: 'center',
width: 260,
render: (t, r) => {
return (
<Tooltip placement="top" title={t}>
<StyledEllipsisWrap maxLine={2}>{t}</StyledEllipsisWrap>
</Tooltip>
);
},
},
{
title: '风险类型',
dataIndex: 'riskType',
align: 'center',
width: 160,
render: (t, r) => {
return mapRiskType[t] ? mapRiskType[t].label : '-';
},
},
{
title: '风险源类型',
dataIndex: 'fileFormat',
align: 'center',
width: 160,
render: (t, r) => {
return mapRiskSourceType[t] ? mapRiskSourceType[t].label : '-';
},
},
{
title: '检测时间',
dataIndex: 'checkTime',
align: 'center',
width: 160,
render: (t, r) => {
return t ? moment(t).format('YYYY-MM-DD HH:mm:ss') : '-';
},
},
{
title: '处理状态',
dataIndex: 'status',
align: 'center',
fixed: 'right',
width: 100,
render: (t, r) => {
return mapStatus[t] ? mapStatus[t].label : '-';
},
},
];
const pagination = {
...paginations,
total: total,
pageSize: size,
current,
showSizeChanger: total > 10,
onChange: (current, pageSize) => {
handlePageChange(current, pageSize);
},
onShowSizeChange: (current, pageSize) => {
handlePageChange(1, pageSize);
},
showTotal(total) {
return `总共 ${total} 条数据`;
},
};
return (
<Table
rowKey="id"
loading={loading}
dataSource={records}
columns={columns}
pagination={pagination}
scroll={{ x: 1500, y: `calc(100vh - 400px)` }}
/>
);
};
/* Main */
const Risk = props => {
const {
dispatch,
loading,
loadingUpload,
dataSearch,
dataRisk,
dataRisk: { size },
} = props;
useEffect(() => {
handleGetList(1, 10);
}, []);
/* 账号列表 */
const handleGetList = (current, size) => {
dispatch({
type: 'Risk/getRiskList',
payload: {
current: current || 1,
size: size || 10,
},
});
};
/* 点击搜索 */
const handleFinish = values => {
const { status, riskType, fileFormat, riskHost, createTime } = values;
dispatch({
type: 'Risk/changeState',
payload: {
dataSearch: {
status,
riskType,
fileFormat,
riskHost,
startTime: createTime[0]
? moment(createTime[0].startOf('day').valueOf()).format('YYYY-MM-DD HH:mm:ss')
: undefined,
endTime: createTime[1]
? moment(createTime[1].endOf('day').valueOf()).format('YYYY-MM-DD HH:mm:ss')
: undefined,
},
},
});
handleGetList(0, size);
};
/* 点击重置 */
const handleReset = () => {
dispatch({ type: 'Risk/resetSearch' });
handleGetList(0, 10);
};
/* 上传组件配置 */
const uploadProps = {
customRequest: file => {
const formData = new FormData();
formData.append('file', file.file);
dispatch({ type: 'Risk/importRisk', payload: formData });
},
beforeUpload: file => {
const isLt50M = file.size / 1024 / 1024 < 50;
if (!isLt50M) {
message.error('文件大小需小于50M');
return false;
}
return isLt50M;
},
};
return (
<StyledPageContainer>
<StyledPageHeader>
<Upload {...uploadProps}>
<Button loading={loadingUpload} type="primary" icon={<UploadOutlined />}>
数据导入
</Button>
</Upload>
<Button
type="primary"
onClick={() => {
ExportFile('风险模版', services.exportRiskTemplate, {});
}}
>
模版下载
</Button>
</StyledPageHeader>
<StyledPageContent>
<Card bordered={false}>
<SearchForm
dataSearch={dataSearch}
handleReset={handleReset}
handleFinish={handleFinish}
/>
<div className="mt-16">
<DataTable loading={loading} handleGetList={handleGetList} dataRisk={dataRisk} />
</div>
</Card>
</StyledPageContent>
</StyledPageContainer>
);
};
export default connect(({ Risk, loading }) => ({
...Risk,
loading: loading.effects['Risk/getRiskList'],
loadingUpload: loading.effects['Risk/importRisk'],
}))(Risk);
import * as services from '@/services/risk';
import { message } from 'antd';
import moment from 'moment';
/* SerachParams */
const staticSearch = {
current: 1,
size: 10,
status: 'ALL',
riskType: 'ALL',
fileFormat: 'ALL',
riskHost: undefined,
startTime: moment(new Date())
.add(-7, 'days')
.startOf('day'),
endTime: moment(new Date())
.add(0, 'days')
.endOf('day'),
};
export default {
namespace: 'Risk',
state: {
dataSearch: {
...staticSearch,
},
dataRisk: {
records: [],
current: 1,
size: 10,
total: 0,
totalPage: 0,
},
},
effects: {
/* 获取风险列表 */
*getRiskList({ payload }, { call, put, select }) {
const {
dataSearch,
dataSearch: { status, riskType, fileFormat, startTime, endTime },
} = yield select(state => state.Risk);
try {
const res = yield call(services.getRiskList, {
current: 1,
size: 10,
...dataSearch,
...payload,
startTime: moment(startTime).format('YYYY-MM-DD HH:mm:ss'),
endTime: moment(endTime).format('YYYY-MM-DD HH:mm:ss'),
status: status === 'ALL' ? '' : status,
riskType: riskType === 'ALL' ? '' : riskType,
fileFormat: fileFormat === 'ALL' ? '' : fileFormat,
});
if (res.code === 0) {
res.data.records = res.data.records || [];
yield put({
type: 'changeState',
payload: {
dataRisk: res.data,
},
});
}
} catch (err) {
console.error(err);
}
},
/* 风险导入 */
*importRisk({ payload }, { call, put, select }) {
try {
const res = yield call(services.importRisk, payload);
if (res.code === 0) {
message.success('导入成功');
yield put({ type: 'getRiskList' });
}
} catch (err) {
console.error(err);
}
},
},
reducers: {
changeState(state, { payload }) {
return {
...state,
...payload,
};
},
resetSearch(state, { payload }) {
return {
...state,
dataSearch: {
...staticSearch,
},
};
},
},
};
import { StyledWapperIframe } from '@/components/style';
const Index = () => {
return (
<StyledWapperIframe>
<iframe
frameBorder="0"
src="https://yh2-screen.jimilicai.com/"
style={{ width: '100%', height: '100%' }}
allowfullscreen="true"
webkitallowfullscreen="true"
mozallowfullscreen="true"
></iframe>
</StyledWapperIframe>
);
};
export default Index;
/**
* Author: Charles
* Date: 2022.9.13
* Description: [重置密码]
*/
import React, { useEffect } from 'react';
import { Modal, Form, Input, Button } from 'antd';
import {aesEncrypt} from '@/utils/encrypt';
const formItemLayout = { labelCol: { span: 4 }, wrapperCol: { span: 20 } };
const ModalResetPassword = props => {
const [form] = Form.useForm();
const {
handleCancelModal,
handleOk,
dataModal: {
modalType,
modalShow,
modalData: { id },
},
} = props;
useEffect(() => {
if (modalType === 'PASSWORD_SET_MODAL' && modalShow) {
form.resetFields();
}
}, [modalType, modalShow]);
/* 点击保存 */
const handleSave = () => {
form.validateFields().then(values => {
const saveData = {
newPassword: aesEncrypt(values.newPassword),
checkPassword: aesEncrypt(values.checkPassword)
}
handleOk({ saveType: 'PASSWORD', id, ...saveData });
});
};
return (
<Modal
title="重置密码"
placement="right"
width={700}
maskClosable={false}
onCancel={handleCancelModal}
visible={modalType === 'PASSWORD_SET_MODAL' && modalShow}
footer={
<div
style={{
textAlign: 'right',
}}
>
<Button onClick={handleCancelModal} className="mr-10">
取消
</Button>
<Button onClick={handleSave} type="primary">
保存
</Button>
</div>
}
>
<Form form={form} {...formItemLayout} name="password_set_modal">
<Form.Item
name="newPassword"
label="密码"
rules={[
{ required: true, message: '请输入密码' },
{
pattern: /^(?![a-zA-Z]+$)(?![A-Z0-9]+$)(?![A-Z\W_]+$)(?![a-z0-9]+$)(?![a-z\W_]+$)(?![0-9\W_]+$)[a-zA-Z0-9\W_]{8,16}$/,
// pattern: /^(?![\d]+$)(?![a-zA-Z]+$)(?![!#$%^&*]+$)[\da-zA-Z!#$%^&@*]{8,16}$/,
message: '密码至少包含大小写字母、数字、特殊符号的三种组合,限制8~16个字符~',
},
]}
>
<Input.Password placeholder="请输入密码" />
</Form.Item>
<Form.Item
name="checkPassword"
label="密码确认"
rules={[
{ required: true, message: '请输入确认密码' },
({ getFieldValue }) => ({
validator(_, value) {
if (!value || getFieldValue('newPassword') === value) {
return Promise.resolve();
}
return Promise.reject(new Error('两次密码不一致,请重新输入'));
},
}),
]}
>
<Input.Password placeholder="请确认密码" />
</Form.Item>
</Form>
</Modal>
);
};
export default ModalResetPassword;
/**
* Author: Charles
* Date: 2022.9.13
* Description: [更新账号]
*/
import React, { useEffect } from 'react';
import { connect } from 'umi';
import { Modal, Form, Select, Input, Button } from 'antd';
import {aesEncrypt} from '@/utils/encrypt';
const { Option } = Select;
const formItemLayout = { labelCol: { span: 4 }, wrapperCol: { span: 20 } };
const ModalSetAccount = props => {
const [form] = Form.useForm();
const {
dispatch,
handleCancelModal,
handleOk,
dataRoleList,
dataModal: {
modalType,
modalShow,
modalData: { id, realName, username, roleId },
},
} = props;
useEffect(() => {
if (modalType === 'ACCOUNT_SET_MODAL' && modalShow) {
dispatch({ type: 'Account/getRoleSelect' });
form.resetFields();
form.setFieldsValue({
realName,
username,
roleId,
});
}
}, [modalType, modalShow]);
/* 点击保存 */
const handleSave = () => {
form.validateFields().then(values => {
const saveData = {...values}
saveData.password = aesEncrypt(values.password),
saveData.checkPassword = aesEncrypt(values.checkPassword),
handleOk({ saveType: 'ACCOUNT', id, ...saveData });
});
};
return (
<Modal
title={id ? '编辑账号' : '新增账号'}
placement="right"
width={700}
maskClosable={false}
onCancel={handleCancelModal}
visible={modalType === 'ACCOUNT_SET_MODAL' && modalShow}
footer={
<div
style={{
textAlign: 'right',
}}
>
<Button onClick={handleCancelModal} className="mr-10">
取消
</Button>
<Button onClick={handleSave} type="primary">
保存
</Button>
</div>
}
>
<Form form={form} {...formItemLayout} name="account_set_modal">
<Form.Item
name="username"
label="账号"
rules={[
{ required: true, message: '请输入账号名称, 由4~10位字母、数字、符号组成不包括空格' },
{
pattern: /^[0-9a-zA-Z]{4,10}$/,
message: '账号名称由4~10位字母、数字、符号组成不包括空格~',
},
]}
>
<Input placeholder="请输入账号" disabled={id ? true : false} />
</Form.Item>
<Form.Item
name="realName"
label="姓名"
rules={[
{ required: true, message: '请输入姓名, 由2~10位非空格字符组成' },
{ pattern: /^[\S]{2,10}$/, message: '昵称由2~10位非空格字符组成' },
]}
>
<Input placeholder="请输入姓名" />
</Form.Item>
{!id && (
<>
<Form.Item
name="password"
label="密码"
rules={[
{ required: true, message: '请输入密码' },
{
pattern: /^(?![a-zA-Z]+$)(?![A-Z0-9]+$)(?![A-Z\W_]+$)(?![a-z0-9]+$)(?![a-z\W_]+$)(?![0-9\W_]+$)[a-zA-Z0-9\W_]{8,16}$/,
// pattern: /^(?![\d]+$)(?![a-zA-Z]+$)(?![!#$%^&*]+$)[\da-zA-Z!#$%^&@*]{6,16}$/,
message: '密码至少包含大小写字母、数字、特殊符号的三种组合,限制8~16个字符~',
},
]}
>
<Input.Password placeholder="请输入密码" />
</Form.Item>
<Form.Item
name="checkPassword"
label="密码确认"
rules={[
{ required: true, message: '请输入确认密码' },
({ getFieldValue }) => ({
validator(_, value) {
if (!value || getFieldValue('password') === value) {
return Promise.resolve();
}
return Promise.reject(new Error('两次密码不一致,请重新输入'));
},
}),
]}
>
<Input.Password placeholder="请输入确认密码" />
</Form.Item>
</>
)}
<Form.Item
name="roleId"
label="角色"
rules={[{ required: true, message: '请选择关联的角色' }]}
>
<Select placeholder="请选择关联的角色">
{dataRoleList.map(item => {
return (
<Option key={item.id} value={item.id}>
{item.name}
</Option>
);
})}
</Select>
</Form.Item>
</Form>
</Modal>
);
};
export default connect(({ Account }) => ({
...Account,
}))(ModalSetAccount);
/**
* Author: Charles
* Date: 2022.9.13
* Description: [账号列表]
*/
import React, { useEffect } from 'react';
import { connect } from 'umi';
import { Card, Form, Input, Button, Table, Divider, Tooltip, Select, Switch } from 'antd';
import { EditOutlined, RedoOutlined } from '@ant-design/icons';
import { paginations } from '@/constants';
import ModalUpdateAccount from './ModalUpdateAccount';
import ModalResetPassword from './ModalResetPassword';
import { StyledPageContainer, StyledPageHeader, StyledPageContent } from '@/components/style';
const FormItem = Form.Item;
const { Option } = Select;
/* SearchForm */
const SearchForm = props => {
const [form] = Form.useForm();
const { dataSearch, handleReset, handleFinish } = props;
useEffect(() => {
form.setFieldsValue({
...dataSearch,
});
}, [dataSearch]);
/* 点击搜索 */
const onFinish = values => {
handleFinish(values);
};
/* 点击重置 */
const onReset = () => {
handleReset();
};
return (
<Form name="Form_Account" layout="inline" form={form} onFinish={onFinish}>
<FormItem label="账号名称" name="keyword">
<Input placeholder="请输入账号名称" style={{ width: '220px' }} />
</FormItem>
<FormItem label="账号状态" name="status">
<Select placeholder="请选择账号状态" style={{ width: '180px' }}>
<Option value="ALL">全部</Option>
<Option value="1">启用</Option>
<Option value="0">禁用</Option>
</Select>
</FormItem>
<FormItem>
<Button type="primary" htmlType="submit" className="mr-15">
搜索
</Button>
<Button className="mr-15" htmlType="button" onClick={onReset}>
重置
</Button>
</FormItem>
</Form>
);
};
/* DataTable */
const DataTable = props => {
const {
dispatch,
loading,
handleGetList,
currentUser: { name },
dataAccount: { records = [], current, size, total },
} = props;
/* 点击分页 */
const handlePageChange = (current, size) => {
handleGetList(current, size);
};
const columns = [
{
title: '序号',
dataIndex: 'id',
align: 'center',
width: 100,
render(t, r, idx) {
return (current - 1) * size + idx + 1;
},
},
{
title: '账号',
dataIndex: 'username',
align: 'center',
width: 140,
},
{
title: '姓名',
dataIndex: 'realName',
align: 'center',
width: 140,
},
{
title: '角色',
dataIndex: 'roleName',
align: 'center',
width: 140,
},
{
title: '账号状态',
dataIndex: 'status',
align: 'center',
width: 160,
render(t, r, idx) {
return (
<Switch
checkedChildren="启用"
unCheckedChildren="已禁用"
checked={t === 1}
disabled={name === r.username}
onChange={checked => {
dispatch({
type: 'Account/changeStatus',
payload: {
id: r.id,
status: checked ? 1 : 0,
},
});
}}
/>
);
},
},
{
title: '创建时间',
dataIndex: 'createTime',
align: 'center',
width: 200,
render(t, r, idx) {
return t || '-';
},
},
{
title: '操作',
align: 'center',
width: 160,
render(t, r) {
return (
<>
<Tooltip placement="top" title="编辑">
<EditOutlined
style={{ cursor: 'pointer', fontSize: 16 }}
onClick={() => {
dispatch({
type: 'Account/changeState',
payload: {
dataModal: {
modalType: 'ACCOUNT_SET_MODAL',
modalShow: true,
modalData: r,
},
},
});
}}
/>
</Tooltip>
<Divider type="vertical" style={{ margin: '0 16px' }} />
<Tooltip
placement="top"
title="重置密码"
onClick={() => {
dispatch({
type: 'Account/changeState',
payload: {
dataModal: {
modalType: 'PASSWORD_SET_MODAL',
modalShow: true,
modalData: r,
},
},
});
}}
>
<RedoOutlined style={{ cursor: 'pointer', fontSize: 16 }} />
</Tooltip>
</>
);
},
},
];
const pagination = {
...paginations,
total: total,
pageSize: size,
current,
showSizeChanger: total > 10,
onChange: (current, pageSize) => {
handlePageChange(current, pageSize);
},
onShowSizeChange: (current, pageSize) => {
handlePageChange(1, pageSize);
},
showTotal(total) {
return `总共 ${total} 条数据`;
},
};
return (
<Table
rowKey="id"
loading={loading}
dataSource={records}
columns={columns}
pagination={pagination}
scroll={{ y: `calc(100vh - 353px)` }}
/>
);
};
/* Main */
const Account = props => {
const {
dispatch,
loading,
dataSearch,
dataModal,
currentUser,
dataAccount,
dataAccount: { size },
} = props;
useEffect(() => {
handleGetList(1, 10);
}, []);
/* 账号列表 */
const handleGetList = (current, size) => {
dispatch({
type: 'Account/getAccountList',
payload: {
current: current || 1,
size: size || 10,
},
});
};
/* 点击搜索 */
const handleFinish = values => {
const { status, keyword } = values;
dispatch({
type: 'Account/changeState',
payload: {
dataSearch: {
keyword,
status,
},
},
});
handleGetList(0, size);
};
/* 点击重置 */
const handleReset = () => {
dispatch({ type: 'Account/resetSearch' });
handleGetList(0, 10);
};
// 关闭弹框
const handleCancelModal = () => {
dispatch({ type: 'Account/cancelModal' });
};
// 点击保存
const handleOk = values => {
const { saveType } = values;
if (saveType === 'ACCOUNT') {
dispatch({ type: 'Account/updateApplication', payload: { ...values, saveType: undefined } });
} else if (saveType === 'PASSWORD') {
dispatch({ type: 'Account/resetPassword', payload: { ...values, saveType: undefined } });
}
};
return (
<StyledPageContainer>
<StyledPageHeader>
<Button
type="primary"
onClick={() => {
dispatch({
type: 'Account/changeState',
payload: {
dataModal: {
modalType: 'ACCOUNT_SET_MODAL',
modalShow: true,
modalData: {},
},
},
});
}}
>
新增用户
</Button>
</StyledPageHeader>
<StyledPageContent>
<Card bordered={false}>
<SearchForm
dataSearch={dataSearch}
handleReset={handleReset}
handleFinish={handleFinish}
/>
<div className="mt-16">
<DataTable
dispatch={dispatch}
loading={loading}
handleGetList={handleGetList}
dataAccount={dataAccount}
currentUser={currentUser}
/>
</div>
</Card>
</StyledPageContent>
<ModalUpdateAccount
dataModal={dataModal}
handleCancelModal={handleCancelModal}
handleOk={handleOk}
/>
<ModalResetPassword
dataModal={dataModal}
handleCancelModal={handleCancelModal}
handleOk={handleOk}
/>
</StyledPageContainer>
);
};
export default connect(({ Account, user, loading }) => ({
...Account,
currentUser: user.currentUser,
loading: loading.effects['Account/getAccountList'],
}))(Account);
import { message } from 'antd';
import * as services from '@/services/account';
/* SerachParams */
const staticSearch = {
keyword: undefined,
current: 1,
size: 10,
status: 'ALL',
};
export default {
namespace: 'Account',
state: {
dataSearch: {
...staticSearch,
},
dataAccount: {
records: [],
current: 1,
size: 10,
total: 0,
totalPage: 0,
},
dataModal: {
modalType: '',
modalShow: false,
modalData: {},
},
dataRoleList: [],
},
effects: {
/* 获取账号列表 */
*getAccountList({ payload }, { call, put, select }) {
const {
dataSearch,
dataSearch: { status },
} = yield select(state => state.Account);
try {
const res = yield call(services.getAccountList, {
current: 1,
size: 10,
...dataSearch,
...payload,
status: status === 'ALL' ? '' : status,
});
if (res.code === 0) {
res.data.records = res.data.records || [];
yield put({
type: 'changeState',
payload: {
dataAccount: res.data,
},
});
}
} catch (err) {
console.error(err);
}
},
/* 新增、修改账号 */
*updateApplication({ payload }, { call, put, select }) {
const { id } = payload;
const {
dataAccount: { current, size },
} = yield select(state => state.Account);
try {
const res = yield call(services[!id ? 'addAccount' : 'updateAccount'], payload);
if (res.code === 0) {
message.success(!id ? '新增成功' : '修改成功');
yield put({
type: 'getAccountList',
payload: { size, current },
});
yield put({ type: 'cancelModal' });
}
} catch (err) {
console.error(err);
}
},
/* 重置密码 */
*resetPassword({ payload }, { call, put, select }) {
try {
const res = yield call(services.resetPassword, payload);
if (res.code === 0) {
message.success('重置成功');
yield put({ type: 'cancelModal' });
}
} catch (err) {
console.error(err);
}
},
/* 状态修改 */
*changeStatus({ payload }, { call, put, select }) {
const { status } = payload;
const {
dataAccount: { current, size },
} = yield select(state => state.Account);
try {
const res = yield call(services.changeStatus, payload);
if (res.code === 0) {
message.success(status == '1' ? '启用成功' : '禁用成功');
yield put({
type: 'getAccountList',
payload: { size, current },
});
}
} catch (err) {
console.error(err);
}
},
/* 获取角色列表 */
*getRoleSelect({ payload }, { call, put, select }) {
try {
const res = yield call(services.getRoleSelect);
if (res.code === 0) {
yield put({
type: 'changeState',
payload: {
dataRoleList: res.data,
},
});
}
} catch (err) {
console.error(err);
}
},
},
reducers: {
changeState(state, { payload }) {
return {
...state,
...payload,
};
},
cancelModal(state, { payload }) {
return {
...state,
dataModal: {
modalType: '',
modalShow: false,
modalData: {},
},
};
},
resetSearch(state, { payload }) {
return {
...state,
dataSearch: {
...staticSearch,
},
};
},
},
};
/**
* Author: llw
* Date: 2022.9.14
* Description: [菜单层级树]
*/
import React from 'react';
import { connect } from 'umi';
import { Tree } from 'antd';
const { TreeNode } = Tree;
const MenuTree = props => {
const { dispatch, dataSystemMenu, dataLevelMenu, dataExpandedKeys } = props;
// TreeNode的DOM
const renderTreeNode = data => {
return data.map(item => {
if (item.children && item.children.length) {
return (
<TreeNode title={item.title} key={item.resourceCode}>
{renderTreeNode(item.children)}
</TreeNode>
);
}
return <TreeNode title={item.title} key={item.resourceCode} />;
});
};
return (
<Tree
showLine
showIcon
className="info-tree"
expandedKeys={dataExpandedKeys}
onSelect={([key], e) => {
const meunInfo = dataLevelMenu.find(item => item.resourceCode === key);
dispatch({
type: 'SystemMenu/changeState',
payload: {
dataMenuInfo: !key || !meunInfo ? [] : [{ ...meunInfo, children: undefined }],
},
});
}}
>
{renderTreeNode(dataSystemMenu)}
</Tree>
);
};
export default connect(({ SystemMenu }) => ({
...SystemMenu,
}))(MenuTree);
/**
* Author: llw
* Date: 2022.9.14
* Description: [设置菜单弹框]
*/
import React, { useEffect, useState } from 'react';
import { connect } from 'umi';
import { Drawer, Radio, Form, Select, Input, Button } from 'antd';
const { Option } = Select;
const formItemLayout = { labelCol: { span: 4 }, wrapperCol: { span: 20 } };
const ModalSetMenu = props => {
const [menuType, setType] = useState(1);
const [form] = Form.useForm();
let {
dispatch,
handleCancelModal,
dataLevelMenu = [],
dataModal: {
modalType,
modalShow,
modalData: { id, title, resourceCode, resourceNodeType, isEdit, parentId },
},
} = props;
/* 重置resourceNodeType */
useEffect(() => {
if (modalType === 'MENU_SET_MODAL' && modalShow) {
setType(resourceNodeType ? +resourceNodeType : 1);
setTimeout(() => {
form.resetFields();
form.setFieldsValue({
parentId,
title,
resourceCode,
resourceNodeType: resourceNodeType ? +resourceNodeType : 1,
});
}, 100);
}
}, [modalType, modalShow]);
/* 点击保存 */
const handleSave = () => {
form.validateFields().then(values => {
dispatch({ type: 'SystemMenu/updateSystemMenu', payload: { ...values, id } });
});
};
/* 选择菜单类型 */
const handleChangeRadio = value => {
form.resetFields();
setType(value);
form.setFieldsValue({ resourceNodeType: value });
};
return (
<Drawer
title="菜单设置"
placement="right"
width={600}
maskClosable={false}
onClose={handleCancelModal}
visible={modalType === 'MENU_SET_MODAL' && modalShow}
footer={
<div
style={{
textAlign: 'right',
}}
>
<Button onClick={handleCancelModal} className="mr-10">
取消
</Button>
<Button onClick={handleSave} type="primary">
保存
</Button>
</div>
}
>
<Form
form={form}
{...formItemLayout}
name="auth_set_modal"
initialValues={{ resourceNodeType: 1 }}
>
<Form.Item name="resourceNodeType" label="菜单类型">
<Radio.Group buttonStyle="solid" onChange={e => handleChangeRadio(e.target.value)}>
<Radio.Button disabled={isEdit && +resourceNodeType !== 1 ? true : false} value={1}>
一级菜单
</Radio.Button>
<Radio.Button disabled={isEdit && +resourceNodeType !== 2 ? true : false} value={2}>
子菜单
</Radio.Button>
{/* <Radio.Button disabled={isEdit && resourceNodeType !== 3 ? true : false} value={3}>
按钮
</Radio.Button> */}
</Radio.Group>
</Form.Item>
{menuType !== 1 && (
<Form.Item
name="parentId"
label={menuType === 2 ? '上级菜单' : '包含菜单'}
rules={[
{
required: true,
message: menuType === 2 ? '请选择上级菜单' : '请选择包含该按钮的菜单',
},
]}
>
<Select
showSearch
placeholder={menuType === 2 ? '请选择上级菜单' : '请选择包含该按钮的菜单'}
optionFilterProp="children"
filterOption={(input, option) =>
option.children.toLowerCase().indexOf(input.toLowerCase()) >= 0
}
>
{dataLevelMenu
.filter(_ =>
isEdit ? _.resourceCode && _.resourceCode !== resourceCode : _.resourceCode,
)
.map(item => {
return (
<Option key={item.id} value={item.id}>
{item.title}
</Option>
);
})}
</Select>
</Form.Item>
)}
<Form.Item
name="title"
label={menuType === 3 ? '按钮名称' : '菜单名称'}
rules={[
{ required: true, message: menuType === 3 ? '请输入按钮名称' : '请输入菜单名称' },
{ min: 2, max: 15, message: '名称长度2~15个字符' },
]}
>
<Input
maxLength={15}
placeholder={menuType === 3 ? '请输入按钮名称' : '请输入菜单名称'}
/>
</Form.Item>
<Form.Item
name="resourceCode"
label={menuType === 3 ? '按钮code' : '菜单code'}
rules={[
{ required: true, message: menuType === 3 ? '请输入按钮code' : '请输入菜单code' },
]}
>
<Input
maxLength={20}
placeholder={menuType === 3 ? '请输入按钮code' : '请输入菜单code'}
/>
</Form.Item>
</Form>
</Drawer>
);
};
export default connect(({ SystemMenu }) => ({
...SystemMenu,
}))(ModalSetMenu);
/**
* Author: Charles
* Date: 2022.9.13
* Description: [菜单设置]
*/
import React, { useEffect } from 'react';
import { connect } from 'umi';
import { Card, Button, Table, Tooltip, Divider, Modal } from 'antd';
import ModalSetMenu from './ModalSetMenu';
import MenuTree from './MenuTree';
import { EditOutlined, DeleteOutlined, ExclamationCircleOutlined } from '@ant-design/icons';
import {
StyledPageContainer,
StyledPageHeader,
StyledPageContent,
StyledPageFlex,
StyledPageLeft,
StyledPageRight,
} from '@/components/style';
/* DataTable */
const DataTable = props => {
const { dispatch, dataMenuInfo } = props;
const columns = [
{
title: '菜单ID',
dataIndex: 'id',
align: 'center',
width: 120,
},
{
title: '菜单名称',
dataIndex: 'title',
align: 'center',
width: 160,
},
{
title: '菜单code',
dataIndex: 'resourceCode',
align: 'center',
width: 160,
},
{
title: '操作',
align: 'center',
width: 120,
render(t, r) {
return (
<>
<Tooltip placement="top" title="编辑">
<EditOutlined
style={{ cursor: 'pointer', fontSize: 16 }}
onClick={() => {
dispatch({
type: 'SystemMenu/changeState',
payload: {
dataModal: {
modalType: 'MENU_SET_MODAL',
modalShow: true,
modalData: { ...r, isEdit: true },
},
},
});
}}
/>
</Tooltip>
<Divider type="vertical" style={{ margin: '0 16px' }} />
<Tooltip
placement="top"
title="删除"
onClick={() => {
Modal.confirm({
title: '删除',
icon: <ExclamationCircleOutlined />,
content: '确定删除该菜单吗?',
centered: true,
onOk() {
dispatch({
type: 'SystemMenu/delSystemMenu',
payload: { id: r.id },
});
},
onCancel() {},
});
}}
>
<DeleteOutlined style={{ cursor: 'pointer', fontSize: 16 }} />
</Tooltip>
</>
);
},
},
];
return <Table rowKey="id" dataSource={dataMenuInfo} columns={columns} pagination={false} />;
};
/* Main */
const SystemMenu = props => {
const { dispatch, dataModal, dataMenuInfo } = props;
useEffect(() => {
dispatch({ type: 'SystemMenu/getAllSystemMenu' });
}, []);
// 关闭弹框
const handleCancelModal = () => {
dispatch({ type: 'SystemMenu/cancelModal' });
};
// 点击保存
const handleOk = values => {};
return (
<StyledPageContainer>
<StyledPageHeader>
<Button
type="primary"
onClick={() => {
dispatch({
type: 'SystemMenu/changeState',
payload: {
dataModal: {
modalType: 'MENU_SET_MODAL',
modalShow: true,
modalData: {},
},
},
});
}}
>
新增菜单
</Button>
</StyledPageHeader>
<StyledPageContent>
<StyledPageFlex>
<StyledPageLeft>
<MenuTree />
</StyledPageLeft>
<StyledPageRight>
<Card bordered={false}>
<div className="mt-16">
<DataTable dispatch={dispatch} dataMenuInfo={dataMenuInfo} />
</div>
</Card>
</StyledPageRight>
</StyledPageFlex>
</StyledPageContent>
<ModalSetMenu
dataModal={dataModal}
handleCancelModal={handleCancelModal}
handleOk={handleOk}
/>
</StyledPageContainer>
);
};
export default connect(({ SystemMenu }) => ({
...SystemMenu,
}))(SystemMenu);
import * as services from '@/services/menu';
import { message } from 'antd';
import { getPermissionList } from '@/utils/menu';
export default {
namespace: 'SystemMenu',
state: {
dataSystemMenu: [], // 层级菜单
dataLevelMenu: [], // 铺平菜单
dataMenuInfo: [], // 菜单详情
dataExpandedKeys: [], // 菜单code
dataModal: {
modalType: '',
modalShow: false,
modalData: {},
},
},
effects: {
/* 获取菜单层级 */
*getAllSystemMenu({ payload }, { call, put, select }) {
const { dataMenuInfo } = yield select(state => state.SystemMenu);
try {
const res = yield call(services.getAllMenu);
if (res.code === 0) {
const { dataMenu, dataExpandedKeys } = getPermissionList(res.data || []);
yield put({
type: 'changeState',
payload: {
dataSystemMenu: res.data || [],
dataLevelMenu: dataMenu,
dataExpandedKeys,
},
});
// 处理菜单选中进行编辑、删除的情况
if (payload && payload.isEdit && dataMenuInfo.length) {
const selectMenuInfo = dataMenu.find(
item => item.resourceCode === dataMenuInfo[0].resourceCode,
);
yield put({
type: 'changeState',
payload: {
dataMenuInfo: [{ ...selectMenuInfo, children: undefined }],
},
});
} else if (payload && payload.isDel && dataMenuInfo.length) {
const selectMenuInfo = dataMenu.find(
item => item.resourceCode === dataMenuInfo[0].resourceCode,
);
yield put({
type: 'changeState',
payload: {
dataMenuInfo: selectMenuInfo ? [{ ...selectMenuInfo, children: undefined }] : [],
},
});
}
}
} catch (err) {
console.error(err);
}
},
/* 新增、编辑菜单 */
*updateSystemMenu({ payload }, { call, put, select }) {
try {
const res = yield call(services[!payload.id ? 'addMenu' : 'updateMenu'], payload);
if (res.code === 0) {
message.success(!payload.id ? '新增成功' : '编辑成功');
yield put({ type: 'cancelModal' });
yield put({ type: 'getAllSystemMenu', payload: { isEdit: payload.id ? true : false } });
}
} catch (err) {
console.error(err);
}
},
/* 删除菜单 */
*delSystemMenu({ payload }, { call, put, select }) {
try {
const res = yield call(services.delMenu, payload);
if (res.code === 0) {
message.success('删除成功');
yield put({ type: 'getAllSystemMenu', payload: { isDel: true } });
}
} catch (err) {
console.error(err);
}
},
},
reducers: {
changeState(state, { payload }) {
return {
...state,
...payload,
};
},
cancelModal(state, { payload }) {
return {
...state,
dataModal: {
modalType: '',
modalShow: false,
modalData: {},
},
};
},
},
};
/**
* Author: Charles
* Date: 2022.9.13
* Description: [更新角色]
*/
import React, { useEffect, useState } from 'react';
import { connect } from 'umi';
import { Modal, Form, Input, Button, TreeSelect } from 'antd';
const formItemLayout = { labelCol: { span: 4 }, wrapperCol: { span: 20 } };
const ModalSetRole = props => {
const [form] = Form.useForm();
const {
dispatch,
dataUserMenu,
handleCancelModal,
handleOk,
dataRoleMenuId,
dataModal: {
modalType,
modalShow,
modalData: { id, name, description },
},
} = props;
useEffect(() => {
if (modalType === 'ROLE_SET_MODAL' && modalShow) {
dispatch({ type: 'Role/getUserMenu' });
if (id) {
dispatch({ type: 'Role/getRoleMenuId', payload: { id } });
}
form.resetFields();
form.setFieldsValue({
name,
description,
});
}
}, [modalType, modalShow]);
const [value, setValue] = useState([]);
useEffect(() => {
form.setFieldsValue({
menuIds: dataRoleMenuId,
});
}, [dataRoleMenuId]);
// treeSelect配置
const tProps = {
treeData: dataUserMenu,
value: dataRoleMenuId || [],
onChange: () => {},
treeCheckable: true,
showCheckedStrategy: TreeSelect.SHOW_ALL,
placeholder: '请选择菜单权限',
style: {
width: '100%',
},
fieldNames: {
label: 'title',
value: 'id',
},
};
/* 点击保存 */
const handleSave = () => {
form.validateFields().then(values => {
console.log(values, 'values');
handleOk({ id, ...values });
});
};
return (
<Modal
title={id ? '编辑角色' : '新增角色'}
placement="right"
width={700}
maskClosable={false}
onCancel={handleCancelModal}
visible={modalType === 'ROLE_SET_MODAL' && modalShow}
footer={
<div
style={{
textAlign: 'right',
}}
>
<Button onClick={handleCancelModal} className="mr-10">
取消
</Button>
<Button onClick={handleSave} type="primary">
保存
</Button>
</div>
}
>
<Form form={form} {...formItemLayout} name="role_set_modal">
<Form.Item
name="name"
label="角色名称"
rules={[
{ required: true, message: '请输入角色名称,由2~10位非空格的字符组成' },
{ pattern: /^[\S]{2,10}$/, message: '角色名称由2~10位非空格的字符组成' },
]}
>
<Input placeholder="请输入角色名称" />
</Form.Item>
<Form.Item
name="description"
label="角色描述"
rules={[
{ required: false, message: '请输入角色描述' },
{ max: 30, message: '角色描述字符长度不能超过30' },
]}
>
<Input.TextArea autoSize={{ minRows: 4, maxRows: 4 }} placeholder="请输入角色描述" />
</Form.Item>
<Form.Item
name="menuIds"
label="菜单权限"
rules={[{ required: true, message: '请选择菜单权限' }]}
>
<TreeSelect {...tProps} />
</Form.Item>
</Form>
</Modal>
);
};
export default connect(({ Role }) => ({
...Role,
}))(ModalSetRole);
/**
* Author: Charles
* Date: 2022.9.13
* Description: [角色列表]
*/
import React, { useEffect } from 'react';
import { connect } from 'umi';
import { Card, Form, Input, Button, Table, Divider, Tooltip, Modal } from 'antd';
import { EditOutlined, DeleteOutlined, ExclamationCircleOutlined } from '@ant-design/icons';
import { paginations } from '@/constants';
import ModalUpdateRole from './ModalUpdateRole';
import { StyledPageContainer, StyledPageHeader, StyledPageContent } from '@/components/style';
const FormItem = Form.Item;
/* SearchForm */
const SearchForm = props => {
const [form] = Form.useForm();
const { dataSearch, handleReset, handleFinish } = props;
useEffect(() => {
form.setFieldsValue({
...dataSearch,
});
}, [dataSearch]);
/* 点击搜索 */
const onFinish = values => {
handleFinish(values);
};
/* 点击重置 */
const onReset = () => {
handleReset();
};
return (
<Form name="Form_Role" layout="inline" form={form} onFinish={onFinish}>
<FormItem label="角色名称" name="search">
<Input placeholder="请输入角色名称" style={{ width: '220px' }} />
</FormItem>
<FormItem>
<Button type="primary" htmlType="submit" className="mr-15">
搜索
</Button>
<Button className="mr-15" htmlType="button" onClick={onReset}>
重置
</Button>
</FormItem>
</Form>
);
};
/* DataTable */
const DataTable = props => {
const {
dispatch,
loading,
handleGetList,
dataRole: { records = [], current, size, total },
} = props;
/* 点击分页 */
const handlePageChange = (current, size) => {
handleGetList(current, size);
};
const columns = [
{
title: '序号',
dataIndex: 'id',
align: 'center',
width: 120,
render(t, r, idx) {
return (current - 1) * size + idx + 1;
},
},
{
title: '角色名称',
dataIndex: 'name',
align: 'center',
width: 160,
},
{
title: '角色描述',
dataIndex: 'description',
align: 'center',
width: 200,
render: (t, r) => {
return t || '-';
},
},
{
title: '创建时间',
dataIndex: 'createTime',
align: 'center',
width: 200,
render(t, r, idx) {
return t || '-';
},
},
{
title: '操作',
align: 'center',
width: 150,
render(t, r) {
return (
<>
<Tooltip placement="top" title="编辑">
<EditOutlined
style={{ cursor: 'pointer', fontSize: 16 }}
onClick={() => {
dispatch({
type: 'Role/changeState',
payload: {
dataModal: {
modalType: 'ROLE_SET_MODAL',
modalShow: true,
modalData: r,
},
},
});
}}
/>
</Tooltip>
<Divider type="vertical" style={{ margin: '0 16px' }} />
<Tooltip
placement="top"
title="删除"
onClick={() => {
Modal.confirm({
title: '删除',
icon: <ExclamationCircleOutlined />,
content: '确定删除该角色吗?',
centered: true,
onOk() {
dispatch({
type: 'Role/delRole',
payload: { id: r.id },
});
},
onCancel() {},
});
}}
>
<DeleteOutlined style={{ cursor: 'pointer', fontSize: 16 }} />
</Tooltip>
</>
);
},
},
];
const pagination = {
...paginations,
total: total,
pageSize: size,
current,
showSizeChanger: total > 10,
onChange: (current, pageSize) => {
handlePageChange(current, pageSize);
},
onShowSizeChange: (current, pageSize) => {
handlePageChange(1, pageSize);
},
showTotal(total) {
return `总共 ${total} 条数据`;
},
};
return (
<Table
rowKey="id"
loading={loading}
dataSource={records}
columns={columns}
pagination={pagination}
scroll={{ y: `calc(100vh - 353px)` }}
/>
);
};
/* Main */
const Role = props => {
const {
dispatch,
loading,
dataSearch,
dataModal,
currentUser,
dataRole,
dataRole: { size },
} = props;
useEffect(() => {
handleGetList(1, 10);
}, []);
/* 账号列表 */
const handleGetList = (current, size) => {
dispatch({
type: 'Role/getRoleList',
payload: {
current: current || 1,
size: size || 10,
},
});
};
/* 点击搜索 */
const handleFinish = values => {
const { status, search } = values;
dispatch({
type: 'Role/changeState',
payload: {
dataSearch: {
search,
status,
},
},
});
handleGetList(0, size);
};
/* 点击重置 */
const handleReset = () => {
dispatch({ type: 'Role/resetSearch' });
handleGetList(0, 10);
};
// 关闭弹框
const handleCancelModal = () => {
dispatch({ type: 'Role/cancelModal' });
};
// 点击保存
const handleOk = values => {
dispatch({ type: 'Role/updateRole', payload: { ...values } });
};
return (
<StyledPageContainer>
<StyledPageHeader>
<Button
type="primary"
onClick={() => {
dispatch({
type: 'Role/changeState',
payload: {
dataModal: {
modalType: 'ROLE_SET_MODAL',
modalShow: true,
modalData: {},
},
},
});
}}
>
新增角色
</Button>
</StyledPageHeader>
<StyledPageContent>
<Card bordered={false}>
<SearchForm
dataSearch={dataSearch}
handleReset={handleReset}
handleFinish={handleFinish}
/>
<div className="mt-16">
<DataTable
dispatch={dispatch}
loading={loading}
handleGetList={handleGetList}
dataRole={dataRole}
currentUser={currentUser}
/>
</div>
</Card>
</StyledPageContent>
<ModalUpdateRole
dataModal={dataModal}
handleCancelModal={handleCancelModal}
handleOk={handleOk}
/>
</StyledPageContainer>
);
};
export default connect(({ Role, user, loading }) => ({
...Role,
loading: loading.effects['Role/getRoleList'],
}))(Role);
import { message } from 'antd';
import * as services from '@/services/role';
/* SerachParams */
const staticSearch = {
search: undefined,
current: 1,
size: 10,
};
export default {
namespace: 'Role',
state: {
dataSearch: {
...staticSearch,
},
dataRole: {
records: [],
current: 1,
size: 10,
total: 0,
totalPage: 0,
},
dataModal: {
modalType: '',
modalShow: false,
modalData: {},
},
dataUserMenu: [],
dataRoleMenuId: [],
},
effects: {
/* 获取角色列表 */
*getRoleList({ payload }, { call, put, select }) {
const { dataSearch } = yield select(state => state.Role);
try {
const res = yield call(services.getRoleList, {
current: 1,
size: 10,
...dataSearch,
...payload,
});
if (res.code === 0) {
res.data.records = res.data.records || [];
yield put({
type: 'changeState',
payload: {
dataRole: res.data,
},
});
}
} catch (err) {
console.error(err);
}
},
/* 新增、修改角色 */
*updateRole({ payload }, { call, put, select }) {
const { id } = payload;
const {
dataRole: { current, size },
} = yield select(state => state.Role);
const {
currentUser: { roleId },
} = yield select(state => state.user);
try {
const res = yield call(services[!id ? 'addRole' : 'updateRole'], payload);
if (res.code === 0) {
message.success(!id ? '新增成功' : '修改成功');
yield put({
type: 'getRoleList',
payload: { size, current },
});
yield put({ type: 'cancelModal' });
if (roleId === id) {
setTimeout(() => {
window.location.href = '/user/login';
}, 500);
}
}
} catch (err) {
console.error(err);
}
},
/* 删除角色 */
*delRole({ payload }, { call, put, select }) {
const {
dataRole: { records = [], current, size },
} = yield select(state => state.Role);
try {
const res = yield call(services.delRole, payload);
if (res.code === 0) {
message.success('删除成功');
yield put({
type: 'getRoleList',
payload: {
size,
current: records.length === 1 ? (current === 1 ? 1 : current - 1) : current,
},
});
}
} catch (err) {
console.error(err);
}
},
/* 获取用户菜单 */
*getUserMenu({ payload }, { call, put, select }) {
try {
const res = yield call(services.getUserMenu);
if (res.code === 0) {
yield put({
type: 'changeState',
payload: {
dataUserMenu: res.data || [],
},
});
}
} catch (err) {
console.error(err);
}
},
/* 获取角色菜单id集合 */
*getRoleMenuId({ payload }, { call, put, select }) {
try {
const res = yield call(services.getRoleMenuId, payload);
if (res.code === 0) {
yield put({
type: 'changeState',
payload: {
dataRoleMenuId: res.data.menuId || [],
},
});
}
} catch (err) {
console.error(err);
}
},
},
reducers: {
changeState(state, { payload }) {
return {
...state,
...payload,
};
},
cancelModal(state, { payload }) {
return {
...state,
dataModal: {
modalType: '',
modalShow: false,
modalData: {},
},
};
},
resetSearch(state, { payload }) {
return {
...state,
dataSearch: {
...staticSearch,
},
};
},
},
};
/**
* Author: llw
* Date: 2020-7-16
* Description: [登录页面]
*/
import React, { useEffect } from 'react';
import { connect } from 'umi';
import { IconFontConfig } from '@/common';
import { Form, Button, Input } from 'antd';
import loginless from './index.less';
import {aesEncrypt} from '@/utils/encrypt';
const Loginform = props => {
const { loading, dispatch } = props;
const handleSubmit = values => {
const loginData = {
username: values.username,
password: aesEncrypt(values.password)
};
dispatch({
type: 'login/login',
payload: loginData,
});
};
return (
<div className={loginless.main}>
<Form onFinish={handleSubmit}>
<Form.Item
name="username"
rules={[{ required: true, message: '请输入您的用户名' }]}
>
<Input
size="large"
placeholder="请输入您的用户名"
prefix={<IconFontConfig type="icon-user" />}
/>
</Form.Item>
<Form.Item
name="password"
rules={[{ required: true, message: '请输入您的密码' }]}
>
<Input.Password
size="large"
visibilityToggle={false}
placeholder="请输入您的密码"
prefix={<IconFontConfig type="icon-password" />}
/>
</Form.Item>
<Button
block
size="large"
type="primary"
htmlType="submit"
loading={loading}
className={loginless.loginbtn}
>
登录
</Button>
</Form>
</div>
);
};
export default connect(({ login, loading }) => ({
login,
loading: loading.effects['login/login'],
}))(Loginform);
.title {
font-size: 15px;
font-weight: 500;
color: rgba(51, 51, 51, 1);
margin-bottom: 25px;
}
.main {
width: 100%;
padding: 40px 25px;
.ipticon {
width: auto;
height: 18px;
margin-right: 11px;
}
:global(.ant-form-item) {
height: 62px;
margin-bottom: 0;
input {
font-size: 14px;
padding-left: 3px;
}
input:-webkit-autofill,
textarea:-webkit-autofill,
select:-webkit-autofill {
background-color: #fff !important;
background-image: none;
}
input:-webkit-autofill:hover {
background-color: #fff !important;
-webkit-box-shadow: 0 0 0px 1000px white inset;
}
input:-webkit-autofill {
background-color: #fff !important;
-webkit-box-shadow: 0 0 0px 1000px white inset;
}
input:-webkit-autofill:focus {
background-color: #fff !important;
/* style code */
-webkit-box-shadow: 0 0 0px 1000px white inset;
}
}
}
.loginbtn {
letter-spacing: -1px;
font-size: 15px;
margin-top: 10px;
height: 44px;
}
/**
* Author: Charles
* Date: 2022.9.13
* Description: [风险列表]
*/
import React, { useEffect } from 'react';
import { connect } from 'umi';
import { Table, Tooltip } from 'antd';
import { paginations, mapStatus, mapRiskType, mapRiskSourceType } from '@/constants';
import { StyledEllipsisWrap } from '@/components/style';
import moment from 'moment';
/* DataTable */
const DataTable = props => {
const {
loading,
handleGetList,
dataRisk: { records = [], current, size, total },
} = props;
/* 点击分页 */
const handlePageChange = (current, size) => {
handleGetList(current, size);
};
const columns = [
{
title: '序号',
dataIndex: 'id',
align: 'center',
fixed: 'left',
width: 80,
render(t, r, idx) {
return (current - 1) * size + idx + 1;
},
},
{
title: '风险源名称',
dataIndex: 'riskName',
align: 'center',
width: 160,
render: (t, r) => {
return (
<Tooltip placement="top" title={t}>
<StyledEllipsisWrap maxLine={1}>{t}</StyledEllipsisWrap>
</Tooltip>
);
},
},
{
title: '风险源地址',
dataIndex: 'riskUrl',
align: 'center',
width: 160,
render: (t, r) => {
return (
<Tooltip placement="top" title={t}>
<StyledEllipsisWrap maxLine={2}>{t}</StyledEllipsisWrap>
</Tooltip>
);
},
},
{
title: '风险站点',
dataIndex: 'riskHost',
align: 'center',
width: 160,
render: (t, r) => {
return (
<Tooltip placement="top" title={t}>
<StyledEllipsisWrap maxLine={2}>{t}</StyledEllipsisWrap>
</Tooltip>
);
},
},
{
title: '关键词',
dataIndex: 'riskKeyword',
align: 'center',
width: 260,
render: (t, r) => {
return (
<Tooltip placement="top" title={t}>
<StyledEllipsisWrap maxLine={2}>{t}</StyledEllipsisWrap>
</Tooltip>
);
},
},
{
title: '风险类型',
dataIndex: 'riskType',
align: 'center',
width: 160,
render: (t, r) => {
return mapRiskType[t] ? mapRiskType[t].label : '-';
},
},
{
title: '风险源类型',
dataIndex: 'fileFormat',
align: 'center',
width: 160,
render: (t, r) => {
return mapRiskSourceType[t] ? mapRiskSourceType[t].label : '-';
},
},
{
title: '检测时间',
dataIndex: 'checkTime',
align: 'center',
width: 160,
render: (t, r) => {
return t ? moment(t).format('YYYY-MM-DD HH:mm:ss') : '-';
},
},
{
title: '处理状态',
dataIndex: 'status',
align: 'center',
fixed: 'right',
width: 100,
render: (t, r) => {
return mapStatus[t] ? mapStatus[t].label : '-';
},
},
];
const pagination = {
...paginations,
total: total,
pageSize: size,
current,
showSizeChanger: total > 10,
onChange: (current, pageSize) => {
handlePageChange(current, pageSize);
},
onShowSizeChange: (current, pageSize) => {
handlePageChange(1, pageSize);
},
showTotal(total) {
return `总共 ${total} 条数据`;
},
};
return (
<Table
rowKey="id"
loading={loading}
dataSource={records}
columns={columns}
pagination={pagination}
scroll={{ x: 1500, y: `calc(100vh - 400px)` }}
/>
);
};
/* Main */
const RiskBlock = props => {
const {
dispatch,
loading,
loadingUpload,
dataSearch,
dataRisk,
dataRisk: { size },
} = props;
useEffect(() => {
handleGetList(1, 10);
}, []);
/* 账号列表 */
const handleGetList = (current, size) => {
dispatch({
type: 'RiskBlock/getEventRiskList',
payload: {
current: current || 1,
size: size || 10,
},
});
};
return (
<div className="mt-16">
<DataTable loading={loading} handleGetList={handleGetList} dataRisk={dataRisk} />
</div>
);
};
export default connect(({ RiskBlock, loading }) => ({
...RiskBlock,
loading: loading.effects['RiskBlock/getEventRiskList'],
}))(RiskBlock);
import * as services from '@/services/risk';
export default {
namespace: 'RiskBlock',
state: {
dataRisk: {
records: [],
current: 1,
size: 10,
total: 0,
totalPage: 0,
},
},
effects: {
/* 获取风险列表 */
*getEventRiskList({ payload }, { call, put, select }) {
try {
const res = yield call(services.getEventRiskList, {
current: 1,
size: 10,
...payload,
});
if (res.code === 0) {
res.data.records = res.data.records || [];
yield put({
type: 'changeState',
payload: {
dataRisk: res.data,
},
});
}
} catch (err) {
console.error(err);
}
},
},
reducers: {
changeState(state, { payload }) {
return {
...state,
...payload,
};
},
},
};
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta
name="viewport"
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0"
/>
<title>一网打“净”数智在线</title>
<link rel="icon" href="/favicon.ico" />
<!-- 一些CDN加载 -->
<script src="https://cdn.jsdelivr.net/combine/npm/react@16.8.6/umd/react.<%= context.env === 'production' ? 'production.min' : 'development'%>.js,npm/react-dom@16.8.6/umd/react-dom.<%= context.env === 'production' ? 'production.min' : 'development'%>.js"></script>
<script src="https://cdn.jsdelivr.net/npm/bizcharts@3.5.7/umd/BizCharts.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@antv/data-set@0.10.2/build/data-set.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/mockjs@1.0.1-beta3/dist/mock.min.js"></script>
<script>
(function(w, d, s, q, i) {
w[q] = w[q] || [];
var f = d.getElementsByTagName(s)[0],
j = d.createElement(s);
j.async = true;
j.id = 'beacon-aplus';
j.src = 'https://alidt.alicdn.com/alilog/mlog/aplus_cloud.js';
f.parentNode.insertBefore(j, f);
})(window, document, 'script', 'aplus_queue');
aplus_queue.push({
action: 'aplus.setMetaInfo',
arguments: ['aplus-rhost-v', 'alog.zjzwfw.gov.cn'],
});
aplus_queue.push({
action: 'aplus.setMetaInfo',
arguments: ['aplus-rhost-g', 'alog.zjzwfw.gov.cn'],
});
var u = navigator.userAgent;
var isAndroid = u.indexOf('Android') > -1;
var isIOS = !!u.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/);
aplus_queue.push({
action: 'aplus.setMetaInfo',
arguments: ['appId', isAndroid ? '28302650' : isIOS ? '28328447' : '47130293'],
});
</script>
</head>
<body>
<noscript>Out-of-the-box mid-stage front/design solution!</noscript>
<div id="root">
<style>
.page-loading-warp {
padding: 120px;
display: flex;
justify-content: center;
align-items: center;
}
.ant-spin {
-webkit-box-sizing: border-box;
box-sizing: border-box;
margin: 0;
padding: 0;
color: rgba(0, 0, 0, 0.65);
font-size: 14px;
font-variant: tabular-nums;
line-height: 1.5;
list-style: none;
-webkit-font-feature-settings: 'tnum';
font-feature-settings: 'tnum';
position: absolute;
display: none;
color: #1890ff;
text-align: center;
vertical-align: middle;
opacity: 0;
-webkit-transition: -webkit-transform 0.3s cubic-bezier(0.78, 0.14, 0.15, 0.86);
transition: -webkit-transform 0.3s cubic-bezier(0.78, 0.14, 0.15, 0.86);
transition: transform 0.3s cubic-bezier(0.78, 0.14, 0.15, 0.86);
transition: transform 0.3s cubic-bezier(0.78, 0.14, 0.15, 0.86),
-webkit-transform 0.3s cubic-bezier(0.78, 0.14, 0.15, 0.86);
}
.ant-spin-spinning {
position: static;
display: inline-block;
opacity: 1;
}
.ant-spin-dot {
position: relative;
display: inline-block;
font-size: 20px;
width: 20px;
height: 20px;
}
.ant-spin-dot-item {
position: absolute;
display: block;
width: 9px;
height: 9px;
background-color: #1890ff;
border-radius: 100%;
-webkit-transform: scale(0.75);
-ms-transform: scale(0.75);
transform: scale(0.75);
-webkit-transform-origin: 50% 50%;
-ms-transform-origin: 50% 50%;
transform-origin: 50% 50%;
opacity: 0.3;
-webkit-animation: antSpinMove 1s infinite linear alternate;
animation: antSpinMove 1s infinite linear alternate;
}
.ant-spin-dot-item:nth-child(1) {
top: 0;
left: 0;
}
.ant-spin-dot-item:nth-child(2) {
top: 0;
right: 0;
-webkit-animation-delay: 0.4s;
animation-delay: 0.4s;
}
.ant-spin-dot-item:nth-child(3) {
right: 0;
bottom: 0;
-webkit-animation-delay: 0.8s;
animation-delay: 0.8s;
}
.ant-spin-dot-item:nth-child(4) {
bottom: 0;
left: 0;
-webkit-animation-delay: 1.2s;
animation-delay: 1.2s;
}
.ant-spin-dot-spin {
-webkit-transform: rotate(45deg);
-ms-transform: rotate(45deg);
transform: rotate(45deg);
-webkit-animation: antRotate 1.2s infinite linear;
animation: antRotate 1.2s infinite linear;
}
.ant-spin-lg .ant-spin-dot {
font-size: 32px;
width: 32px;
height: 32px;
}
.ant-spin-lg .ant-spin-dot i {
width: 14px;
height: 14px;
}
@media all and (-ms-high-contrast: none), (-ms-high-contrast: active) {
.ant-spin-blur {
background: #fff;
opacity: 0.5;
}
}
@-webkit-keyframes antSpinMove {
to {
opacity: 1;
}
}
@keyframes antSpinMove {
to {
opacity: 1;
}
}
@-webkit-keyframes antRotate {
to {
-webkit-transform: rotate(405deg);
transform: rotate(405deg);
}
}
@keyframes antRotate {
to {
-webkit-transform: rotate(405deg);
transform: rotate(405deg);
}
}
</style>
<div class="page-loading-warp">
<div class="ant-spin ant-spin-lg ant-spin-spinning">
<span class="ant-spin-dot ant-spin-dot-spin"
><i class="ant-spin-dot-item"></i><i class="ant-spin-dot-item"></i
><i class="ant-spin-dot-item"></i><i class="ant-spin-dot-item"></i
></span>
</div>
</div>
</div>
</body>
</html>
/* eslint-disable eslint-comments/disable-enable-pair */
/* eslint-disable no-restricted-globals */
/* eslint-disable no-underscore-dangle */
/* globals workbox */
workbox.core.setCacheNameDetails({
prefix: 'antd-pro',
suffix: 'v1',
});
// Control all opened tabs ASAP
workbox.clientsClaim();
/**
* Use precaching list generated by workbox in build process.
* https://developers.google.com/web/tools/workbox/reference-docs/latest/workbox.precaching
*/
workbox.precaching.precacheAndRoute(self.__precacheManifest || []);
/**
* Register a navigation route.
* https://developers.google.com/web/tools/workbox/modules/workbox-routing#how_to_register_a_navigation_route
*/
workbox.routing.registerNavigationRoute('/index.html');
/**
* Use runtime cache:
* https://developers.google.com/web/tools/workbox/reference-docs/latest/workbox.routing#.registerRoute
*
* Workbox provides all common caching strategies including CacheFirst, NetworkFirst etc.
* https://developers.google.com/web/tools/workbox/reference-docs/latest/workbox.strategies
*/
/**
* Handle API requests
*/
workbox.routing.registerRoute(/\/api\//, workbox.strategies.networkFirst());
/**
* Handle third party requests
*/
workbox.routing.registerRoute(
/^https:\/\/gw.alipayobjects.com\//,
workbox.strategies.networkFirst(),
);
workbox.routing.registerRoute(
/^https:\/\/cdnjs.cloudflare.com\//,
workbox.strategies.networkFirst(),
);
workbox.routing.registerRoute(/\/color.less/, workbox.strategies.networkFirst());
/**
* Response to client after skipping waiting with MessageChannel
*/
addEventListener('message', event => {
const replyPort = event.ports[0];
const message = event.data;
if (replyPort && message && message.type === 'skip-waiting') {
event.waitUntil(
self.skipWaiting().then(
() =>
replyPort.postMessage({
error: null,
}),
error =>
replyPort.postMessage({
error,
}),
),
);
}
});
import request from '@/utils/request';
// 账号列表
export async function getAccountList(params) {
return request('/v1/user/page', { params, methods: 'GET' });
}
// 新增账号
export async function addAccount(data) {
return request('/v1/user', { data });
}
// 修改账号
export async function updateAccount(data) {
const { id } = data;
return request(`/v1/user/${id}`, { data: { ...data, id: undefined } });
}
// 重置密码
export async function resetPassword(data) {
const { id } = data;
return request(`/v1/user/${id}/password/reset`, { data: { ...data, id: undefined } });
}
// 角色下拉
export async function getRoleSelect(params) {
return request('/role/all', { params, methods: 'GET' });
}
// 状态修改
export async function changeStatus(data) {
const { id } = data;
return request(`/v1/user/${id}/status`, { data });
}
import request from '@/utils/request';
// 应用列表
export async function getApplicationList(data) {
return request('/web/tenant/list', { data });
}
// 删除应用
export async function delApplication(data) {
return request('web/tenant/delete', { data });
}
// 新增应用
export async function addApplication(data) {
return request('web/tenant/add', { data });
}
// 修改应用
export async function updateApplication(data) {
return request('web/tenant/update', { data });
}
// 应用详情
export async function getApplicationInfo(data) {
return request(`web/tenant/get?id=${data.id}`, { methods: 'get' });
}
import request from '@/utils/request';
// 场所网站备案信息
export async function getPlaceList(params) {
return request('/place/list', { params, methods: 'GET' });
}
// 行政执法案件
export async function getEventIllegalList(params) {
return request('/eventIllegal/list', { params, methods: 'GET' });
}
// 投诉举报事件
export async function getComplaintList(params) {
return request('/complaints/list', { params, methods: 'GET' });
}
// 基层网络事件
export async function getNetworkEventList(params) {
return request('/baseGrid/list', { params, methods: 'GET' });
}
// 场所导入
export async function importPlace(data) {
return request('/place/import', { data, requestType: 'form' });
}
// 场所导出模版
export async function exportPlaceTemplate(params) {
return request('/place/template', { params, methods: 'GET', responseType: 'blob' });
}
// 行政执法案件导入
export async function importEventIllegal(data) {
return request('/eventIllegal/import', { data, requestType: 'form' });
}
// 行政执法案件导出模版
export async function exportEventIllegalTemplate(params) {
return request('/eventIllegal/template', { params, methods: 'GET', responseType: 'blob' });
}
// 基层网格搜索下拉框
export async function getBaseGridSponsor(params) {
return request('/baseGrid/getSponsor', { params, methods: 'GET' });
}
// 投诉举报事件详情
export async function getComplaintsDetail(params) {
return request('/complaints/selectById', { params, methods: 'GET' });
}
// 基层网络详情
export async function getBaseGridDetail(params) {
return request('/baseGrid/getById', { params, methods: 'GET' });
}
// 行政执法案件详情
export async function getEventIllegalDetail(params) {
return request('/eventIllegal/selectById', { params, methods: 'GET' });
}
import request from '@/utils/request';
// 事件列表
export async function getEventList(params) {
return request('/event/list', { params, methods: 'GET' });
}
// 事件详情
export async function getEventDetail(params) {
return request('/event/detail', { params, methods: 'GET' });
}
// 事件风险列表
export async function getEventRiskList(params) {
return request('/risk/getEventRiskList', { params, methods: 'GET' });
}
// 事件日志
export async function getEventRiskLog(params) {
return request(`/event/${params?.eventId}/log/list`, { params, methods: 'GET' });
}
import request from '@/utils/request';
// 登录
export async function queryLogin(data) {
return request('/v1/user/auth/login', {
data,
headers: { 'Content-Type': 'application/json' },
});
}
// 退出登录
export async function loginOut(data) {
return request('/v1/user/auth/logout', { data });
}
import request from '@/utils/request';
// 新增菜单
export async function addMenu(data) {
return request('/menu', { data });
}
// 更新菜单
export async function updateMenu(data) {
const { id } = data;
return request(`/menu/${id}`, { data });
}
// 删除菜单
export async function delMenu(data) {
const { id } = data;
return request(`/menu/${id}/delete`);
}
// 获取所有的菜单
export async function getAllMenu(params) {
return request('/menu/tree', { params, methods: 'GET' });
}
import request from '@/utils/request';
// 风险列表
export async function getRiskList(params) {
return request('/risk/list', { params, methods: 'GET' });
}
// 风险导入
export async function importRisk(data) {
return request('/risk/import', { data, requestType: 'form' });
}
// 风险导出模版
export async function exportRiskTemplate(params) {
return request('/risk/template', { params, methods: 'GET', responseType: 'blob' });
}
// 事件
export async function getEventRiskList(params) {
const url = window.location.href;
const index = url.lastIndexOf('/');
const serialNumber = url.substring(index + 1, url.length);
if (!serialNumber) return false;
return request(`/event/sn/${serialNumber}`, { params, methods: 'GET' });
}
import request from '@/utils/request';
// 角色列表
export async function getRoleList(params) {
return request('/role', { params, methods: 'GET' });
}
// 新增角色
export async function addRole(data) {
return request('/role', { data });
}
// 修改角色
export async function updateRole(data) {
const { id } = data;
return request(`/role/${id}`, { data });
}
// 删除角色
export async function delRole(data) {
const { id } = data;
return request(`/role/${id}/delete`);
}
// 获取用户权限
export async function getUserMenu(params) {
return request('/menu/tree', { params, methods: 'GET' });
}
// 获取用户的meunId
export async function getRoleMenuId(params) {
const { id } = params;
return request(`/role/${id}`, { methods: 'GET' });
}
import request from '@/utils/request';
// 用户信息
export async function getUserInfo(params) {
return request('/v1/user/auth/me', { params, methods: 'GET' });
}
// 修改密码
export async function updatePassword(data) {
return request('/v1/user/password/reset', { data, methods: 'POST' });
}
// from vben
import { encrypt, decrypt } from 'crypto-js/aes';
import { parse } from 'crypto-js/enc-utf8';
import pkcs7 from 'crypto-js/pad-pkcs7';
import ECB from 'crypto-js/mode-ecb';
import UTF8 from 'crypto-js/enc-utf8';
const key = 'Q7mh4Gb!cl&\\*{[rz7j>DLIP$g7gYOBv';
export function aesEncrypt(cipherText) {
return encrypt(cipherText, parse(key), {
mode: ECB,
padding: pkcs7,
}).toString()
}
export function aesDecrypt(cipherText) {
return decrypt(cipherText, parse(key), {
mode: ECB,
padding: pkcs7,
}).toString(UTF8);
}
import { HomeOutlined } from '@ant-design/icons';
const mapsIcons = {
HomeOutlined: <HomeOutlined />,
};
export function getDataMenus(menus) {
//过滤非菜单类型START
function getMenu(menus) {
menus = menus.filter(_ => {
if (_.subList.length > 0) {
_.children = getMenu(_.subList);
_.name = _.name;
_.path = _.children.length ? _.children[0].url : _.url;
_.locale = `menu${_.url.replace(/\//g, '.')}`;
_.icon = mapsIcons[_.icon];
}
return _.type === 'MENU';
});
return menus;
}
menus = getMenu(menus);
//END
return menus;
}
/**
* 获得处理后的菜单
*/
export const getPermissionList = (data = []) => {
let dataMenu = []; // 菜单集合
let dataExpandedKeys = []; // 需要展开数的集合
function filterPermission(data) {
data.map(item => {
item.key = item.resourceCode;
dataExpandedKeys.push(item.resourceCode);
dataMenu.push(item);
if (item.children && item.children.length) {
filterPermission(item.children);
}
});
}
filterPermission(data);
return { dataMenu, dataExpandedKeys };
};
/**
* request 网络请求工具
* 更详细的 api 文档: https://github.com/umijs/umi-request
*/
import { extend } from 'umi-request';
import { message, notification } from 'antd';
import { urlConfig } from '@/common';
import defaultSettings from '../../config/defaultSettings';
const { tokenKey } = defaultSettings;
const codeMessage = {
200: '服务器成功返回请求的数据。',
201: '新建或修改数据成功。',
202: '一个请求已经进入后台排队(异步任务)。',
204: '删除数据成功。',
400: '发出的请求有错误,服务器没有进行新建或修改数据的操作。',
401: '登录已超时, 请重新登录',
403: '用户得到授权,但是访问是被禁止的。',
404: '发出的请求针对的是不存在的记录,服务器没有进行操作。',
406: '请求的格式不可得。',
410: '请求的资源被永久删除,且不会再得到的。',
422: '当创建一个对象时,发生一个验证错误。',
500: '服务器发生错误,请检查服务器。',
502: '网关错误。',
503: '服务不可用,服务器暂时过载或维护。',
504: '网关超时。',
};
/**
* 异常处理程序
*/
const errorHandler = error => {
const { response } = error;
if (response && response.status) {
const errorText = codeMessage[response.status] || response.statusText;
const { status, url } = response;
notification.error({
message: `请求错误 ${status}: ${url}`,
description: errorText,
});
}
};
/**
* 配置request请求时的默认参数
*/
const request = extend({
errorHandler,
// 默认错误处理
credentials: 'include',
prefix: urlConfig.URL_API, //接口公共
method: 'post', // 设置默认的请求类型
});
//请求拦截
request.interceptors.request.use((url, options) => {
return {
options: {
...options,
headers: {
...options.headers,
Authorization: localStorage.getItem(tokenKey) || '',
},
method: options.methods ? options.methods : 'post',
},
};
});
//响应拦截
request.interceptors.response.use(async response => {
if (
response.url.indexOf('/risk/template') > -1 ||
response.url.indexOf('/place/template') > -1 ||
response.url.indexOf('/eventIllegal/template') > -1
) {
return response;
} else {
const data = await response?.clone()?.json();
if ([4001, 40002].includes(data.code)) {
window.location.href = '/user/login';
} else if (data.code === 0) {
return response;
} else {
message.error(`${data.message}`);
}
return response;
}
});
export default request;
import { parse } from 'querystring';
import pathRegexp from 'path-to-regexp';
import { isArray } from 'util';
import { IMG_URL } from '@/common';
import { phoneReg } from '@/constants/reg';
import { message } from 'antd';
// 获取七牛图片
export function getImgUrl(imgname) {
return `${IMG_URL}${imgname}`;
}
// 获取七牛图片的文件名
export function getFileName(fileurl) {
let index1 = fileurl.lastIndexOf('/');
let index2 = fileurl.length;
fileurl = fileurl.substring(index1 + 1, index2);
return fileurl;
}
// 获取文件类型
export function getFileType(file) {
let filename = file;
let index1 = filename.lastIndexOf('.');
let index2 = filename.length;
let type = filename.substring(index1, index2);
return type;
}
/**
* 将对象转换为以key对应的值为内容的数组
* @param {Object} enums (将要转换的对象)
*/
export const objToArray = (enums = {}) => {
const arr = [];
Object.keys(enums).forEach(key => {
arr.push(enums[key]);
});
return arr;
};
/**
* 将数组转换为以指定字段为key的对象
* @param {Array} arrs (将要转换的数组)
* @param {String} key (以哪个字段作为对象的key)
*/
export const arrayToObj = (arrs = [], key = 'id') => {
const params = {};
for (let i = 0, len = arrs.length; i < len; i++) {
const item = arrs[i];
params[item[key]] = item;
}
return params;
};
// 将数字转换成金额显示
export const toMoney = num => {
const type = Object.prototype.toString.call(num);
switch (type) {
case '[object Number]': {
num = num.toFixed(2).replace('.00', '');
break;
}
case '[object String]': {
num -= 0;
num = num.toFixed(2).replace('.00', '');
break;
}
default: {
num = '--';
}
}
return num;
};
/**
*
* @param {*生成的独立id的长度} n
* @param {*和将要生成的随机数作比较去重的id集合} arr
* @param {*可用于生成随机数id的字符集合} str
*/
export const getUniqueId = (n, arr = [], comb = '123456789') => {
const random = n => {
let str = comb;
let result = '';
for (let i = 0; i < n; i++) {
result += str[parseInt(Math.random() * str.length)];
}
if (arr.includes(result)) {
random(n);
} else {
return result;
}
};
return random(n);
};
export const isPhone = phone => phoneReg.test(phone);
/**
* 判断数组里面是不是有重复项
*/
export const hasDuplicates = arr => {
return _.uniq(arr).length !== arr.length;
};
/**
* 获取权限配置数据
*/
export const getAuthData = (data = [], authObj) => {
let dataMenu = []; // 菜单集合(不包含按钮菜单)
let dataExpandedKeys = []; // 需要展开数的集合
let { parentCode } = authObj;
if (!parentCode) {
data.push({ ...authObj });
}
function authFilter(data) {
data = data.map(item => {
if (item.code === parentCode) {
item.subPermissionList.push({ ...authObj });
}
if (item.type !== 3) {
dataMenu.push({
name: item.name,
code: item.code,
url: item.url,
});
}
dataExpandedKeys.push(item.code);
const nextSubPermissionList = authFilter(item.subPermissionList);
return {
...item,
subPermissionList: nextSubPermissionList.length > 0 ? nextSubPermissionList : [],
};
});
return data;
}
data = authFilter(data);
return {
data,
dataMenu,
dataExpandedKeys,
};
};
/**
* 编辑、删除权限
*/
export const getDelAuthData = (data = [], authObj, operateType) => {
let { code, parentCode, oldCode } = authObj;
let index = 0; // 次数
function authFilter(data) {
data = data
.filter(_ => {
if (_.subPermissionList.length > 0) {
authFilter(_.subPermissionList);
}
return operateType === 'EDIT' ? _.code : _.code !== code;
})
.map(item => {
/* 编辑时菜单没有改变 */
if (!oldCode && code === item.code && operateType === 'EDIT') {
item = { ...authObj };
}
/* 编辑时改变了菜单 */
if (oldCode && item.code === oldCode) {
let subData =
item.subPermissionList.length > 1
? item.subPermissionList.filter(subItem => {
return subItem.code !== code;
})
: [];
item.subPermissionList = subData;
}
if (index < 1 && oldCode && item.code === parentCode && operateType === 'EDIT') {
index = index + 1;
item.subPermissionList.push({ ...authObj, oldCode: undefined });
}
const nextSubPermissionList = authFilter(item.subPermissionList);
return {
...item,
subPermissionList: nextSubPermissionList.length > 0 ? nextSubPermissionList : [],
};
});
return data;
}
data = authFilter(data);
const { dataMenu, dataExpandedKeys } = getEditCode(data);
return {
data,
dataMenu,
dataExpandedKeys,
};
};
/* 获取编辑之后的菜单和Code */
export const getEditCode = (data = []) => {
let dataMenu = []; // 菜单集合(不包含按钮菜单)
let dataExpandedKeys = []; // 需要展开数的集合
function allFilter(data) {
data.map(item => {
if (item.type !== 3) {
dataMenu.push({
name: item.name,
code: item.code,
url: item.url,
});
}
dataExpandedKeys.push(item.code);
allFilter(item.subPermissionList);
});
}
allFilter(data);
return { dataMenu, dataExpandedKeys };
};
/**
* 获取所有的code和除按钮菜单的菜单名称
*/
export const getAllMenuParams = (data = []) => {
let allCode = [];
let allMenuName = [];
function allFilter(data) {
data.map(item => {
if (item.type !== 3) {
allMenuName.push(item.name);
}
allCode.push(item.code);
allFilter(item.subPermissionList);
});
}
allFilter(data);
return { allCode, allMenuName };
};
/**
* 获得权限列表
*/
export const getPermissionList = (data = [], entry) => {
let dataMenu = []; // 菜单集合(不包含按钮菜单)
let dataExpandedKeys = []; // 需要展开数的集合
function filterPermission(data) {
data.map(item => {
if (entry) {
item.type =
item.type === 3 || item.type === 'BTN'
? 3
: (item.type === 'MENU' || item.type === 1) && item.parentId === 0
? 1
: 2;
item.key = item.code;
dataExpandedKeys.push(item.code);
if (item.type !== 3 && item.type !== 'BTN') {
dataMenu.push({
name: item.name,
code: item.code,
url: item.url,
});
}
/* 加上parentCode */
if (item.subPermissionList.length) {
item.subPermissionList.map(_ => {
_.parentCode = item.code;
});
}
} else {
item.type = item.type === 3 || item.type === 'BTN' ? 'BTN' : 'MENU';
if (item.parentCode) {
delete item.parentCode;
}
delete item.key;
}
filterPermission(item.subPermissionList);
});
}
filterPermission(data);
return !entry ? data : { data, dataMenu, dataExpandedKeys };
};
/* 获取拖拽之后的数据 */
export const getDragData = (data = []) => {
let dataMenu = []; // 菜单集合(不包含按钮菜单)
let dataExpandedKeys = []; // 需要展开数的集合
let isFlag = false;
let dragData = data.map(item => {
if (item.type === 3) {
message.warning('按钮不能做为一级菜单使用');
isFlag = true;
}
return {
...item,
type: 1,
parentId: 0,
parentCode: undefined,
};
});
function allFilter(data) {
data = data.map(item => {
item.subPermissionList.map(_ => {
if (_.parentId === 0) {
message.warning('一级菜单不能做为子菜单');
isFlag = true;
}
});
if (item.type !== 1) {
item.subPermissionList.map(_ => {
_.parentCode = item.code;
});
}
if (item.type !== 3) {
dataMenu.push({
name: item.name,
code: item.code,
url: item.url,
});
}
if (item.type === 3 && item.subPermissionList.length) {
message.warning('按钮下面不能有子类');
isFlag = true;
}
dataExpandedKeys.push(item.code);
const nextSubPermissionList = allFilter(item.subPermissionList);
return {
...item,
subPermissionList: nextSubPermissionList.length > 0 ? nextSubPermissionList : [],
};
});
return data;
}
data = allFilter(dragData);
let dataDragAuth = !isFlag ? data : [];
return { dataDragAuth, dataMenu, dataExpandedKeys };
};
// 导出文件(流的形式)
export const ExportFile = (name, fn, params = {}) => {
fn(params)
.then(resp => {
const blob = new Blob([resp]);
let downloadElement = document.createElement('a');
const href = window.URL.createObjectURL(blob); //创建下载的链接
downloadElement.href = href;
downloadElement.download = `${name}.xlsx`;
document.body.appendChild(downloadElement);
downloadElement.click();
document.body.removeChild(downloadElement);
window.URL.revokeObjectURL(href);
})
.catch(e => {
message.error(`导出错误: e`);
console.error(e);
});
};
/* eslint-disable eslint-comments/disable-enable-pair */
/* eslint-disable @typescript-eslint/no-var-requires */
/* eslint-disable eslint-comments/no-unlimited-disable */
const { spawn } = require('child_process');
// eslint-disable-next-line import/no-extraneous-dependencies
const { kill } = require('cross-port-killer');
const env = Object.create(process.env);
env.BROWSER = 'none';
env.TEST = true;
env.UMI_UI = 'none';
env.PROGRESS = 'none';
// flag to prevent multiple test
let once = false;
const startServer = spawn(/^win/.test(process.platform) ? 'npm.cmd' : 'npm', ['start'], {
env,
});
startServer.stderr.on('data', data => {
// eslint-disable-next-line
console.log(data.toString());
});
startServer.on('exit', () => {
kill(process.env.PORT || 8000);
});
console.log('Starting development server for e2e tests...');
startServer.stdout.on('data', data => {
console.log(data.toString());
// hack code , wait umi
if (
(!once && data.toString().indexOf('Compiled successfully') >= 0) ||
data.toString().indexOf('Theme generated successfully') >= 0
) {
// eslint-disable-next-line
once = true;
console.log('Development server is started, ready to run tests.');
const testCmd = spawn(
/^win/.test(process.platform) ? 'npm.cmd' : 'npm',
['test', '--', '--maxWorkers=1', '--runInBand'],
{
stdio: 'inherit',
},
);
testCmd.on('exit', code => {
startServer.kill();
process.exit(code);
});
}
});
import 'jsdom-global/register';
// browserMocks.js
const localStorageMock = (() => {
let store = {};
return {
getItem(key) {
return store[key] || null;
},
setItem(key, value) {
store[key] = value.toString();
},
clear() {
store = {};
},
};
})();
Object.defineProperty(window, 'localStorage', {
value: localStorageMock,
});
Styling with Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!