1.0.0.RELEASE

This commit is contained in:
smallchill 2019-02-13 17:16:39 +08:00
commit 633669b2dc
492 changed files with 33441 additions and 0 deletions

35
.dockerignore Normal file
View File

@ -0,0 +1,35 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
**/node_modules
/src/utils/request-temp.js
# 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/mock
.temp/**
# umi
.umi
.umi-production
# screenshot
screenshot
.firebase

16
.editorconfig Normal file
View File

@ -0,0 +1,16 @@
# 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

3
.eslintignore Normal file
View File

@ -0,0 +1,3 @@
/functions/mock/**
/scripts
/config

40
.eslintrc.js Normal file
View File

@ -0,0 +1,40 @@
module.exports = {
parser: 'babel-eslint',
extends: ['airbnb', 'prettier', 'plugin:compat/recommended'],
env: {
browser: true,
node: true,
es6: true,
mocha: true,
jest: true,
jasmine: true,
},
globals: {
APP_TYPE: true,
page: true,
},
rules: {
'react/jsx-filename-extension': [1, { extensions: ['.js'] }],
'react/jsx-wrap-multilines': 0,
'react/prop-types': 0,
'react/forbid-prop-types': 0,
'react/jsx-one-expression-per-line': 0,
'import/no-unresolved': [2, { ignore: ['^@/', '^umi/'] }],
'import/no-extraneous-dependencies': [
2,
{
optionalDependencies: true,
devDependencies: ['**/tests/**.js', '/mock/**.js', '**/**.test.js'],
},
],
'jsx-a11y/no-noninteractive-element-interactions': 0,
'jsx-a11y/click-events-have-key-events': 0,
'jsx-a11y/no-static-element-interactions': 0,
'jsx-a11y/anchor-is-valid': 0,
'no-nested-ternary': 0,
'linebreak-style': 0,
},
settings: {
polyfills: ['fetch', 'promises', 'url'],
},
};

5
.firebaserc Normal file
View File

@ -0,0 +1,5 @@
{
"projects": {
"default": "antd-pro"
}
}

38
.gitignore vendored Normal file
View File

@ -0,0 +1,38 @@
# 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
# production
/dist
/.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/mock
.temp/**
# umi
.umi
.umi-production
# screenshot
screenshot
.firebase

6
.gitpod.yml Normal file
View File

@ -0,0 +1,6 @@
ports:
- port: 8000
onOpen: open-preview
tasks:
- init: npm install
command: npm start

5
.prettierignore Normal file
View File

@ -0,0 +1,5 @@
**/*.md
**/*.svg
package.json
.umi
.umi-production

11
.prettierrc Normal file
View File

@ -0,0 +1,11 @@
{
"singleQuote": true,
"trailingComma": "es5",
"printWidth": 100,
"overrides": [
{
"files": ".prettierrc",
"options": { "parser": "json" }
}
]
}

13
.stylelintrc.json Normal file
View File

@ -0,0 +1,13 @@
{
"extends": [
"stylelint-config-standard",
"stylelint-config-css-modules",
"stylelint-config-rational-order",
"stylelint-config-prettier"
],
"plugins": ["stylelint-order", "stylelint-declaration-block-no-ignored-properties"],
"rules": {
"no-descending-specificity": null,
"plugin/declaration-block-no-ignored-properties": true
}
}

12
Dockerfile Normal file
View File

@ -0,0 +1,12 @@
FROM circleci/node:latest-browsers
WORKDIR /usr/src/app/
USER root
COPY package.json ./
RUN yarn
COPY ./ ./
RUN npm run test:all
CMD ["npm", "run", "build"]

11
Dockerfile.dev Normal file
View File

@ -0,0 +1,11 @@
FROM node:latest
WORKDIR /usr/src/app/
COPY package.json ./
RUN npm install --silent --no-cache
COPY ./ ./
CMD ["npm", "run", "start"]

11
Dockerfile.hub Normal file
View File

@ -0,0 +1,11 @@
FROM nginx
WORKDIR /usr/src/app/
COPY ./docker/nginx.conf /etc/nginx/conf.d/default.conf
COPY ./dist /usr/share/nginx/html/
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

21
LICENSE Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2019 bladex.vip
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

92
README.md Normal file
View File

@ -0,0 +1,92 @@
## 简介
Sword 是 [SpringBlade](https://gitee.com/smallc/SpringBlade)前端UI项目基于react 、ant design、dva用于快速构建系统中后台业务。
## 在线演示
演示地址:[http://sword.bladex.vip](http://sword.bladex.vip)
## 特性
- :gem: **优雅美观**:基于 Ant Design 体系精心设计
- :triangular_ruler: **常见设计模式**:提炼自中后台应用的典型页面和场景
- :rocket: **最新技术栈**:使用 React/umi/dva/antd 等前端前沿技术开发
- :iphone: **响应式**:针对不同屏幕大小设计
- :art: **主题**:可配置的主题满足多样化的品牌诉求
- :globe_with_meridians: **国际化**:内建业界通用的国际化方案
- :zap: **最佳实践**:良好的工程实践助您持续产出高质量代码
- :1234: **Mock 数据**:实用的本地数据调试方案
- :white_check_mark: **UI 测试**:自动化测试保障前端产品质量
## 支持环境
现代浏览器及 IE11。
| [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/edge/edge_48x48.png" alt="IE / Edge" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>IE / Edge | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/firefox/firefox_48x48.png" alt="Firefox" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Firefox | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/chrome/chrome_48x48.png" alt="Chrome" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Chrome | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/safari/safari_48x48.png" alt="Safari" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Safari | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/opera/opera_48x48.png" alt="Opera" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Opera |
| --------- | --------- | --------- | --------- | --------- |
| IE11, Edge| last 2 versions| last 2 versions| last 2 versions| last 2 versions
## 用户权益
* 允许免费用于学习、毕设、公司项目、私活等。
* 代码文件需保留相关license信息。
## 禁止事项
* 直接将本项目挂淘宝等商业平台出售。
* 业务代码50%以上相似度的二次开源,二次开源需先联系作者。
注意若禁止条款被发现有权追讨19999的授权费。
## 如何启动
```
$ git clone https://gitee.com/smallc/Sword.git
$ cd Sword
$ npm install
# mock模式
$ npm start
# 服务模式
$ npm run start:no-mock
# 访问 http://localhost:88
```
# 界面一览
<table>
<tr>
<td><img src="https://raw.githubusercontent.com/chillzhuang/blade-tool/master/pic/springblade-k8s.png"/></td>
<td><img src="https://raw.githubusercontent.com/chillzhuang/blade-tool/master/pic/springblade-harbor.png"/></td>
</tr>
<tr>
<td><img src="https://raw.githubusercontent.com/chillzhuang/blade-tool/master/pic/springblade-traefik.png"/></td>
<td><img src="https://raw.githubusercontent.com/chillzhuang/blade-tool/master/pic/springblade-traefik-health.png"/></td>
</tr>
<tr>
<td><img src="https://raw.githubusercontent.com/chillzhuang/blade-tool/master/pic/springblade-consul.png"/></td>
<td><img src="https://raw.githubusercontent.com/chillzhuang/blade-tool/master/pic/springblade-consul-nodes1.png"/></td>
</tr>
<tr>
<td><img src="https://raw.githubusercontent.com/chillzhuang/blade-tool/master/pic/springblade-admin1.png"/></td>
<td><img src="https://raw.githubusercontent.com/chillzhuang/blade-tool/master/pic/springblade-admin2.png"/></td>
</tr>
<tr>
<td><img src="https://raw.githubusercontent.com/chillzhuang/blade-tool/master/pic/springblade-swagger1.png"/></td>
<td><img src="https://raw.githubusercontent.com/chillzhuang/blade-tool/master/pic/springblade-swagger2.png"/></td>
</tr>
<tr>
<td><img src="https://raw.githubusercontent.com/chillzhuang/blade-tool/master/pic/sword-main.png"/></td>
<td><img src="https://raw.githubusercontent.com/chillzhuang/blade-tool/master/pic/sword-menu.png"/></td>
</tr>
<tr>
<td><img src="https://raw.githubusercontent.com/chillzhuang/blade-tool/master/pic/sword-menu-edit.png"/></td>
<td><img src="https://raw.githubusercontent.com/chillzhuang/blade-tool/master/pic/sword-menu-icon.png"/></td>
</tr>
<tr>
<td><img src="https://raw.githubusercontent.com/chillzhuang/blade-tool/master/pic/sword-role.png"/></td>
<td><img src="https://raw.githubusercontent.com/chillzhuang/blade-tool/master/pic/sword-user.png"/></td>
</tr>
<tr>
<td><img src="https://raw.githubusercontent.com/chillzhuang/blade-tool/master/pic/sword-dict.png "/></td>
<td><img src="https://raw.githubusercontent.com/chillzhuang/blade-tool/master/pic/sword-log.png"/></td>
</tr>
<tr>
<td><img src="https://raw.githubusercontent.com/chillzhuang/blade-tool/master/pic/sword-locale-cn.png"/></td>
<td><img src="https://raw.githubusercontent.com/chillzhuang/blade-tool/master/pic/sword-locale-us.png"/></td>
</tr>
</table>

74
azure-pipelines.yml Normal file
View File

@ -0,0 +1,74 @@
# Node.js
# Build a general Node.js project with npm.
# Add steps that analyze code, save build artifacts, deploy, and more:
# https://docs.microsoft.com/azure/devops/pipelines/languages/javascript
name: ant design pro
trigger:
- master
jobs:
- job: lintAndBuild
pool:
vmImage: 'Ubuntu-16.04'
steps:
- checkout: self
clean: false
- script: yarn install
displayName: install
- script: npm run lint
displayName: lint
- script: npm run build
env:
PROGRESS: none
displayName: build
- job: test
pool:
vmImage: 'Ubuntu-16.04'
container:
image: circleci/node:latest-browsers
options: '-u root'
steps:
- script: yarn install
displayName: install
- script: npm run test:all
env:
PROGRESS: none
displayName: test
- job: Windows
pool:
vmImage: 'vs2017-win2016'
steps:
- task: NodeTool@0
inputs:
versionSpec: '11.x'
- script: yarn install
displayName: install
- script: npm run lint
displayName: lint
- script: npm run build
env:
PROGRESS: none
displayName: build
- job: MacOS
pool:
vmImage: 'macOS-10.13'
steps:
- task: NodeTool@0
inputs:
versionSpec: '11.x'
- script: yarn install
displayName: install
- script: npm run lint
displayName: lint
- script: npm run
env:
PROGRESS: none
displayName: build

119
config/config.js Normal file
View File

@ -0,0 +1,119 @@
// https://umijs.org/config/
import os from 'os';
import pageRoutes from './router.config';
import webpackPlugin from './plugin.config';
import defaultSettings from '../src/defaultSettings';
import slash from 'slash2';
const { pwa, primaryColor } = defaultSettings;
const plugins = [
[
'umi-plugin-react',
{
antd: true,
dva: {
hmr: true,
},
locale: {
enable: true, // default false
default: 'zh-CN', // default zh-CN
baseNavigator: true, // default true, when it is true, will use `navigator.language` overwrite default
},
dynamicImport: {
loadingComponent: './components/PageLoading/index',
webpackChunkName: true,
},
pwa: pwa
? {
workboxPluginMode: 'InjectManifest',
workboxOptions: {
importWorkboxFrom: 'local',
},
}
: {},
...(!process.env.TEST && os.platform() === 'darwin'
? {
dll: {
include: ['dva', 'dva/router', 'dva/saga', 'dva/fetch'],
exclude: ['@babel/runtime'],
},
hardSource: false,
}
: {}),
},
],
];
// 针对 preview.pro.ant.design 的 GA 统计代码
// 业务上不需要这个
if (process.env.APP_TYPE === 'site') {
plugins.push([
'umi-plugin-ga',
{
code: 'UA-72788897-6',
},
]);
}
export default {
// add for transfer to umi
plugins,
history: 'hash',
define: {
APP_TYPE: process.env.APP_TYPE || '',
},
treeShaking: true,
targets: {
ie: 11,
},
// 路由配置
routes: pageRoutes,
// Theme for antd
// https://ant.design/docs/react/customize-theme-cn
theme: {
'primary-color': primaryColor,
},
externals: {
'@antv/data-set': 'DataSet',
},
proxy: {
'/api': {
target: 'http://localhost',
changeOrigin: true,
pathRewrite: { '^/api': '' },
},
},
ignoreMomentLocale: true,
lessLoaderOptions: {
javascriptEnabled: true,
},
disableRedirectHoist: true,
cssLoaderOptions: {
modules: true,
getLocalIdent: (context, localIdentName, 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: '/',
},
chainWebpack: webpackPlugin,
};

33
config/plugin.config.js Normal file
View File

@ -0,0 +1,33 @@
// Change theme plugin
import MergeLessPlugin from 'antd-pro-merge-less';
import AntDesignThemePlugin from 'antd-theme-webpack-plugin';
import path from 'path';
export default config => {
// pro 和 开发环境再添加这个插件
if (process.env.APP_TYPE === 'site' || process.env.NODE_ENV !== 'production') {
// 将所有 less 合并为一个供 themePlugin使用
const outFile = path.join(__dirname, '../.temp/ant-design-pro.less');
const stylesDir = path.join(__dirname, '../src/');
config.plugin('merge-less').use(MergeLessPlugin, [
{
stylesDir,
outFile,
},
]);
config.plugin('ant-design-theme').use(AntDesignThemePlugin, [
{
antDir: path.join(__dirname, '../node_modules/antd'),
stylesDir,
varFile: path.join(__dirname, '../node_modules/antd/lib/style/themes/default.less'),
mainLessFile: outFile, // themeVariables: ['@primary-color'],
indexFileName: 'index.html',
generateOne: true,
lessUrl: 'https://gw.alipayobjects.com/os/lib/less.js/3.8.1/less.min.js',
},
]);
}
};

216
config/router.config.js Normal file
View File

@ -0,0 +1,216 @@
export default [
// user
{
path: '/user',
component: '../layouts/UserLayout',
routes: [
{ path: '/user', redirect: '/user/login' },
{ path: '/user/login', component: './Login/Login' },
{ path: '/user/register', component: './Login/Register' },
{ path: '/user/register-result', component: './Login/RegisterResult' },
],
},
// app
{
path: '/',
component: '../layouts/BasicLayout',
Routes: ['src/pages/Authorized'],
authority: ['admin', 'user'],
routes: [
// dashboard
{ path: '/', redirect: '/dashboard/workplace' },
{
path: '/result',
routes: [
// result
{ path: '/result/success', component: './Result/Success' },
{ path: '/result/fail', component: './Result/Error' },
],
},
{
path: '/exception',
routes: [
// exception
{ path: '/exception/403', component: './Exception/403' },
{ path: '/exception/404', component: './Exception/404' },
{ path: '/exception/500', component: './Exception/500' },
{ path: '/exception/trigger', component: './Exception/TriggerException' },
],
},
{
path: '/account',
routes: [
{
path: '/account/center',
component: './Account/Center/Center',
routes: [
{ path: '/account/center', redirect: '/account/center/articles' },
{ path: '/account/center/articles', component: './Account/Center/Articles' },
{ path: '/account/center/applications', component: './Account/Center/Applications' },
{ path: '/account/center/projects', component: './Account/Center/Projects' },
],
},
{
path: '/account/settings',
component: './Account/Settings/Info',
routes: [
{ path: '/account/settings', redirect: '/account/settings/base' },
{ path: '/account/settings/base', component: './Account/Settings/BaseView' },
{ path: '/account/settings/security', component: './Account/Settings/SecurityView' },
{ path: '/account/settings/binding', component: './Account/Settings/BindingView' },
{
path: '/account/settings/notification',
component: './Account/Settings/NotificationView',
},
],
},
],
},
{
path: '/dashboard',
routes: [
{ path: '/dashboard/analysis', component: './Dashboard/Analysis' },
{ path: '/dashboard/monitor', component: './Dashboard/Monitor' },
{ path: '/dashboard/workplace', component: './Dashboard/Workplace' },
],
},
{
path: '/desk',
routes: [
{
path: '/desk/notice',
routes: [
{ path: '/desk/notice', redirect: '/desk/notice/list' },
{ path: '/desk/notice/list', component: './Desk/Notice/Notice' },
{ path: '/desk/notice/add', component: './Desk/Notice/NoticeAdd' },
{ path: '/desk/notice/edit/:id', component: './Desk/Notice/NoticeEdit' },
{ path: '/desk/notice/view/:id', component: './Desk/Notice/NoticeView' },
],
},
],
},
{
path: '/system',
routes: [
{
path: '/system/user',
routes: [
{ path: '/system/user', redirect: '/system/user/list' },
{ path: '/system/user/list', component: './System/User/User' },
{ path: '/system/user/add', component: './System/User/UserAdd' },
{ path: '/system/user/edit/:id', component: './System/User/UserEdit' },
{ path: '/system/user/view/:id', component: './System/User/UserView' },
],
},
{
path: '/system/dict',
routes: [
{ path: '/system/dict', redirect: '/system/dict/list' },
{ path: '/system/dict/list', component: './System/Dict/Dict' },
{ path: '/system/dict/add', component: './System/Dict/DictAdd' },
{ path: '/system/dict/add/:id', component: './System/Dict/DictAdd' },
{ path: '/system/dict/edit/:id', component: './System/Dict/DictEdit' },
{ path: '/system/dict/view/:id', component: './System/Dict/DictView' },
],
},
{
path: '/system/dept',
routes: [
{ path: '/system/dept', redirect: '/system/dept/list' },
{ path: '/system/dept/list', component: './System/Dept/Dept' },
{ path: '/system/dept/add', component: './System/Dept/DeptAdd' },
{ path: '/system/dept/add/:id', component: './System/Dept/DeptAdd' },
{ path: '/system/dept/edit/:id', component: './System/Dept/DeptEdit' },
{ path: '/system/dept/view/:id', component: './System/Dept/DeptView' },
],
},
{
path: '/system/role',
routes: [
{ path: '/system/role', redirect: '/system/role/list' },
{ path: '/system/role/list', component: './System/Role/Role' },
{ path: '/system/role/add', component: './System/Role/RoleAdd' },
{ path: '/system/role/add/:id', component: './System/Role/RoleAdd' },
{ path: '/system/role/edit/:id', component: './System/Role/RoleEdit' },
{ path: '/system/role/view/:id', component: './System/Role/RoleView' },
],
},
{
path: '/system/menu',
routes: [
{ path: '/system/menu', redirect: '/system/menu/list' },
{ path: '/system/menu/list', component: './System/Menu/Menu' },
{ path: '/system/menu/add', component: './System/Menu/MenuAdd' },
{ path: '/system/menu/add/:id', component: './System/Menu/MenuAdd' },
{ path: '/system/menu/edit/:id', component: './System/Menu/MenuEdit' },
{ path: '/system/menu/view/:id', component: './System/Menu/MenuView' },
],
},
{
path: '/system/param',
routes: [
{ path: '/system/param', redirect: '/system/param/list' },
{ path: '/system/param/list', component: './System/Param/Param' },
{ path: '/system/param/add', component: './System/Param/ParamAdd' },
{ path: '/system/param/edit/:id', component: './System/Param/ParamEdit' },
{ path: '/system/param/view/:id', component: './System/Param/ParamView' },
],
},
],
},
{
path: '/monitor',
routes: [
{
path: '/monitor/log',
routes: [
{
path: '/monitor/log/usual',
routes: [
{ path: '/monitor/log/usual', redirect: '/monitor/log/usual/list' },
{ path: '/monitor/log/usual/list', component: './Monitor/Log/LogUsual' },
{ path: '/monitor/log/usual/view/:id', component: './Monitor/Log/LogUsualView' },
],
},
{
path: '/monitor/log/api',
routes: [
{ path: '/monitor/log/api', redirect: '/monitor/log/api/list' },
{ path: '/monitor/log/api/list', component: './Monitor/Log/LogApi' },
{ path: '/monitor/log/api/view/:id', component: './Monitor/Log/LogApiView' },
],
},
{
path: '/monitor/log/error',
routes: [
{ path: '/monitor/log/error', redirect: '/monitor/log/error/list' },
{ path: '/monitor/log/error/list', component: './Monitor/Log/LogError' },
{ path: '/monitor/log/error/view/:id', component: './Monitor/Log/LogErrorView' },
],
},
],
},
],
},
{
path: '/tool',
routes: [
{
path: '/tool/code',
routes: [
{ path: '/tool/code', redirect: '/tool/code/list' },
{ path: '/tool/code/list', component: './System/Code/Code' },
{ path: '/tool/code/add', component: './System/Code/CodeAdd' },
{ path: '/tool/code/add/:id', component: './System/Code/CodeAdd' },
{ path: '/tool/code/edit/:id', component: './System/Code/CodeEdit' },
{ path: '/tool/code/view/:id', component: './System/Code/CodeView' },
],
},
],
},
{
component: '404',
},
],
},
];

View File

@ -0,0 +1,14 @@
version: "3.5"
services:
ant-design-pro_dev:
ports:
- 8000:8000
build:
context: ../
dockerfile: Dockerfile.dev
container_name: "ant-design-pro_dev"
volumes:
- ../src:/usr/src/app/src
- ../config:/usr/src/app/config
- ../mock:/usr/src/app/mock

21
docker/docker-compose.yml Normal file
View File

@ -0,0 +1,21 @@
version: "3.5"
services:
ant-design-pro_build:
build: ../
container_name: "ant-design-pro_build"
volumes:
- dist:/usr/src/app/dist
ant-design-pro_web:
image: nginx
ports:
- 80:80
container_name: "ant-design-pro_web"
restart: unless-stopped
volumes:
- dist:/usr/share/nginx/html:ro
- ./nginx.conf:/etc/nginx/conf.d/default.conf
volumes:
dist:

22
docker/nginx.conf Normal file
View File

@ -0,0 +1,22 @@
server {
listen 80;
# gzip config
gzip on;
gzip_min_length 1k;
gzip_comp_level 9;
gzip_types text/plain text/css text/javascript application/json application/javascript application/x-javascript application/xml;
gzip_vary on;
gzip_disable "MSIE [1-6]\.";
root /usr/share/nginx/html;
location / {
try_files $uri $uri/ /index.html;
}
location /api {
proxy_pass https://preview.pro.ant.design;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
}
}

13
firebase.json Normal file
View File

@ -0,0 +1,13 @@
{
"hosting": {
"public": "dist",
"rewrites": [
{ "source": "/api/**", "function": "api" },
{
"source": "**",
"destination": "/index.html"
}
],
"ignore": ["firebase.json", "**/.*", "**/node_modules/**"]
}
}

10
functions/index.js Normal file
View File

@ -0,0 +1,10 @@
// [START functionsimport]
const functions = require('firebase-functions');
const express = require('express');
const matchMock = require('./matchMock');
const app = express();
app.use(matchMock);
exports.api = functions.https.onRequest(app);

115
functions/matchMock.js Normal file
View File

@ -0,0 +1,115 @@
const pathToRegexp = require('path-to-regexp');
const bodyParser = require('body-parser');
const mockFile = require('./mock/index');
const BODY_PARSED_METHODS = ['post', 'put', 'patch'];
const debug = console.log;
function parseKey(key) {
let method = 'get';
let path = key;
if (key.indexOf(' ') > -1) {
const splited = key.split(' ');
method = splited[0].toLowerCase();
path = splited[1]; // eslint-disable-line
}
return {
method,
path,
};
}
function createHandler(method, path, handler) {
return (req, res, next) => {
function sendData() {
if (typeof handler === 'function') {
handler(req, res, next);
} else {
res.json(handler);
}
}
if (BODY_PARSED_METHODS.includes(method)) {
bodyParser.json({ limit: '5mb', strict: false })(req, res, () => {
bodyParser.urlencoded({ limit: '5mb', extended: true })(req, res, () => {
sendData();
});
});
} else {
sendData();
}
};
}
function normalizeConfig(config) {
return Object.keys(config).reduce((memo, key) => {
const handler = config[key];
const { method, path } = parseKey(key);
const keys = [];
const re = pathToRegexp(path, keys);
memo.push({
method,
path,
re,
keys,
handler: createHandler(method, path, handler),
});
return memo;
}, []);
}
const mockData = normalizeConfig(mockFile);
function matchMock(req) {
const { path: exceptPath } = req;
const exceptMethod = req.method.toLowerCase();
function decodeParam(val) {
if (typeof val !== 'string' || val.length === 0) {
return val;
}
try {
return decodeURIComponent(val);
} catch (err) {
if (err instanceof URIError) {
err.message = `Failed to decode param ' ${val} '`;
err.statusCode = 400;
err.status = 400;
}
throw err;
}
}
// eslint-disable-next-line no-restricted-syntax
for (const mock of mockData) {
const { method, re, keys } = mock;
if (method === exceptMethod) {
const match = re.exec(req.path);
if (match) {
const params = {};
for (let i = 1; i < match.length; i += 1) {
const key = keys[i - 1];
const prop = key.name;
const val = decodeParam(match[i]);
if (val !== undefined || !hasOwnProperty.call(params, prop)) {
params[prop] = val;
}
}
req.params = params;
return mock;
}
}
}
return mockData.filter(({ method, re }) => method === exceptMethod && re.test(exceptPath))[0];
}
module.exports = (req, res, next) => {
const match = matchMock(req);
if (match) {
debug(`mock matched: [${match.method}] ${match.path}`);
return match.handler(req, res, next);
}
return next();
};

23
functions/package.json Normal file
View File

@ -0,0 +1,23 @@
{
"name": "functions",
"description": "Cloud Functions for Firebase",
"scripts": {
"serve": "firebase serve --only functions",
"shell": "firebase functions:shell",
"start": "npm run shell",
"deploy": "npm run mock && firebase deploy --only functions",
"logs": "firebase functions:log",
"mock": "node ../scripts/generateMock.js"
},
"dependencies": {
"@babel/runtime": "^7.0.0",
"body-parser": "^1.18.3",
"express": "^4.16.4",
"firebase-admin": "^6.4.0",
"firebase-functions": "^2.1.0",
"mockjs": "^1.0.1-beta3",
"moment": "^2.22.2",
"path-to-regexp": "^3.0.0"
},
"private": true
}

11
jest-puppeteer.config.js Normal file
View File

@ -0,0 +1,11 @@
// ps https://github.com/GoogleChrome/puppeteer/issues/3120
module.exports = {
launch: {
args: [
'--disable-gpu',
'--disable-dev-shm-usage',
'--no-first-run',
'--no-zygote',
],
},
};

4
jest.config.js Normal file
View File

@ -0,0 +1,4 @@
module.exports = {
testURL: 'http://localhost:8000',
preset: 'jest-puppeteer',
};

10
jsconfig.json Normal file
View File

@ -0,0 +1,10 @@
{
"compilerOptions": {
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
}
}
}

341
mock/api.js Normal file
View File

@ -0,0 +1,341 @@
import mockjs from 'mockjs';
const titles = [
'Alipay',
'Angular',
'Ant Design',
'Ant Design Pro',
'Bootstrap',
'React',
'Vue',
'Webpack',
];
const avatars = [
'https://gw.alipayobjects.com/zos/rmsportal/WdGqmHpayyMjiEhcKoVE.png', // Alipay
'https://gw.alipayobjects.com/zos/rmsportal/zOsKZmFRdUtvpqCImOVY.png', // Angular
'https://gw.alipayobjects.com/zos/rmsportal/dURIMkkrRFpPgTuzkwnB.png', // Ant Design
'https://gw.alipayobjects.com/zos/rmsportal/sfjbOqnsXXJgNCjCzDBL.png', // Ant Design Pro
'https://gw.alipayobjects.com/zos/rmsportal/siCrBXXhmvTQGWPNLBow.png', // Bootstrap
'https://gw.alipayobjects.com/zos/rmsportal/kZzEzemZyKLKFsojXItE.png', // React
'https://gw.alipayobjects.com/zos/rmsportal/ComBAopevLwENQdKWiIn.png', // Vue
'https://gw.alipayobjects.com/zos/rmsportal/nxkuOJlFJuAUhzlMTCEe.png', // Webpack
];
const avatars2 = [
'https://gw.alipayobjects.com/zos/rmsportal/BiazfanxmamNRoxxVxka.png',
'https://gw.alipayobjects.com/zos/rmsportal/cnrhVkzwxjPwAaCfPbdc.png',
'https://gw.alipayobjects.com/zos/rmsportal/gaOngJwsRYRaVAuXXcmB.png',
'https://gw.alipayobjects.com/zos/rmsportal/ubnKSIfAJTxIgXOKlciN.png',
'https://gw.alipayobjects.com/zos/rmsportal/WhxKECPNujWoWEFNdnJE.png',
'https://gw.alipayobjects.com/zos/rmsportal/jZUIxmJycoymBprLOUbT.png',
'https://gw.alipayobjects.com/zos/rmsportal/psOgztMplJMGpVEqfcgF.png',
'https://gw.alipayobjects.com/zos/rmsportal/ZpBqSxLxVEXfcUNoPKrz.png',
'https://gw.alipayobjects.com/zos/rmsportal/laiEnJdGHVOhJrUShBaJ.png',
'https://gw.alipayobjects.com/zos/rmsportal/UrQsqscbKEpNuJcvBZBu.png',
];
const covers = [
'https://gw.alipayobjects.com/zos/rmsportal/uMfMFlvUuceEyPpotzlq.png',
'https://gw.alipayobjects.com/zos/rmsportal/iZBVOIhGJiAnhplqjvZW.png',
'https://gw.alipayobjects.com/zos/rmsportal/iXjVmWVHbCJAyqvDxdtx.png',
'https://gw.alipayobjects.com/zos/rmsportal/gLaIAoVWTtLbBWZNYEMg.png',
];
const desc = [
'那是一种内在的东西, 他们到达不了,也无法触及的',
'希望是一个好东西,也许是最好的,好东西是不会消亡的',
'生命就像一盒巧克力,结果往往出人意料',
'城镇中有那么多的酒馆,她却偏偏走进了我的酒馆',
'那时候我只会想自己想要什么,从不想自己拥有什么',
];
const user = [
'付小小',
'曲丽丽',
'林东东',
'周星星',
'吴加好',
'朱偏右',
'鱼酱',
'乐哥',
'谭小仪',
'仲尼',
];
function fakeList(count) {
const list = [];
for (let i = 0; i < count; i += 1) {
list.push({
id: `fake-list-${i}`,
owner: user[i % 10],
title: titles[i % 8],
avatar: avatars[i % 8],
cover: parseInt(i / 4, 10) % 2 === 0 ? covers[i % 4] : covers[3 - (i % 4)],
status: ['active', 'exception', 'normal'][i % 3],
percent: Math.ceil(Math.random() * 50) + 50,
logo: avatars[i % 8],
href: 'https://ant.design',
updatedAt: new Date(new Date().getTime() - 1000 * 60 * 60 * 2 * i),
createdAt: new Date(new Date().getTime() - 1000 * 60 * 60 * 2 * i),
subDescription: desc[i % 5],
description:
'在中台产品的研发过程中,会出现不同的设计规范和实现方式,但其中往往存在很多类似的页面和组件,这些类似的组件会被抽离成一套标准规范。',
activeUser: Math.ceil(Math.random() * 100000) + 100000,
newUser: Math.ceil(Math.random() * 1000) + 1000,
star: Math.ceil(Math.random() * 100) + 100,
like: Math.ceil(Math.random() * 100) + 100,
message: Math.ceil(Math.random() * 10) + 10,
content:
'段落示意:蚂蚁金服设计平台 ant.design用最小的工作量无缝接入蚂蚁金服生态提供跨越设计与开发的体验解决方案。蚂蚁金服设计平台 ant.design用最小的工作量无缝接入蚂蚁金服生态提供跨越设计与开发的体验解决方案。',
members: [
{
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/ZiESqWwCXBRQoaPONSJe.png',
name: '曲丽丽',
id: 'member1',
},
{
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/tBOxZPlITHqwlGjsJWaF.png',
name: '王昭君',
id: 'member2',
},
{
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/sBxjgqiuHMGRkIjqlQCd.png',
name: '董娜娜',
id: 'member3',
},
],
});
}
return list;
}
let sourceData;
function getFakeList(req, res) {
const params = req.query;
const count = params.count * 1 || 20;
const result = fakeList(count);
sourceData = result;
return res.json(result);
}
function postFakeList(req, res) {
const { /* url = '', */ body } = req;
// const params = getUrlParams(url);
const { method, id } = body;
// const count = (params.count * 1) || 20;
let result = sourceData;
switch (method) {
case 'delete':
result = result.filter(item => item.id !== id);
break;
case 'update':
result.forEach((item, i) => {
if (item.id === id) {
result[i] = Object.assign(item, body);
}
});
break;
case 'post':
result.unshift({
body,
id: `fake-list-${result.length}`,
createdAt: new Date().getTime(),
});
break;
default:
break;
}
return res.json(result);
}
const getNotice = {
code: 200,
success: true,
data: [
{
id: 'xxx1',
title: titles[0],
logo: avatars[0],
description: '那是一种内在的东西,他们到达不了,也无法触及的',
updatedAt: new Date(),
member: '科学搬砖组',
href: '',
memberLink: '',
},
{
id: 'xxx2',
title: titles[1],
logo: avatars[1],
description: '希望是一个好东西,也许是最好的,好东西是不会消亡的',
updatedAt: new Date('2017-07-24'),
member: '全组都是吴彦祖',
href: '',
memberLink: '',
},
{
id: 'xxx3',
title: titles[2],
logo: avatars[2],
description: '城镇中有那么多的酒馆,她却偏偏走进了我的酒馆',
updatedAt: new Date(),
member: '中二少女团',
href: '',
memberLink: '',
},
{
id: 'xxx4',
title: titles[3],
logo: avatars[3],
description: '那时候我只会想自己想要什么,从不想自己拥有什么',
updatedAt: new Date('2017-07-23'),
member: '程序员日常',
href: '',
memberLink: '',
},
{
id: 'xxx5',
title: titles[4],
logo: avatars[4],
description: '凛冬将至',
updatedAt: new Date('2017-07-23'),
member: '高逼格设计天团',
href: '',
memberLink: '',
},
{
id: 'xxx6',
title: titles[5],
logo: avatars[5],
description: '生命就像一盒巧克力,结果往往出人意料',
updatedAt: new Date('2017-07-23'),
member: '骗你来学计算机',
href: '',
memberLink: '',
},
],
msg: '操作成功',
};
const getActivities = [
{
id: 'trend-1',
updatedAt: new Date(),
user: {
name: '曲丽丽',
avatar: avatars2[0],
},
group: {
name: '高逼格设计天团',
link: 'http://github.com/',
},
project: {
name: '六月迭代',
link: 'http://github.com/',
},
template: '在 @{group} 新建项目 @{project}',
},
{
id: 'trend-2',
updatedAt: new Date(),
user: {
name: '付小小',
avatar: avatars2[1],
},
group: {
name: '高逼格设计天团',
link: 'http://github.com/',
},
project: {
name: '六月迭代',
link: 'http://github.com/',
},
template: '在 @{group} 新建项目 @{project}',
},
{
id: 'trend-3',
updatedAt: new Date(),
user: {
name: '林东东',
avatar: avatars2[2],
},
group: {
name: '中二少女团',
link: 'http://github.com/',
},
project: {
name: '六月迭代',
link: 'http://github.com/',
},
template: '在 @{group} 新建项目 @{project}',
},
{
id: 'trend-4',
updatedAt: new Date(),
user: {
name: '周星星',
avatar: avatars2[4],
},
project: {
name: '5 月日常迭代',
link: 'http://github.com/',
},
template: '将 @{project} 更新至已发布状态',
},
{
id: 'trend-5',
updatedAt: new Date(),
user: {
name: '朱偏右',
avatar: avatars2[3],
},
project: {
name: '工程效能',
link: 'http://github.com/',
},
comment: {
name: '留言',
link: 'http://github.com/',
},
template: '在 @{project} 发布了 @{comment}',
},
{
id: 'trend-6',
updatedAt: new Date(),
user: {
name: '乐哥',
avatar: avatars2[5],
},
group: {
name: '程序员日常',
link: 'http://github.com/',
},
project: {
name: '品牌迭代',
link: 'http://github.com/',
},
template: '在 @{group} 新建项目 @{project}',
},
];
function getFakeCaptcha(req, res) {
return res.json('captcha-xxx');
}
export default {
'GET /api/blade-desk/notice/notices': getNotice,
'GET /api/blade-desk/dashboard/activities': getActivities,
'POST /api/forms': (req, res) => {
res.send({ message: 'Ok' });
},
'GET /api/tags': mockjs.mock({
'list|100': [{ name: '@city', 'value|1-100': 150, 'type|0-2': 1 }],
}),
'GET /api/fake_list': getFakeList,
'POST /api/fake_list': postFakeList,
'GET /api/captcha': getFakeCaptcha,
};

196
mock/chart.js Normal file
View File

@ -0,0 +1,196 @@
import moment from 'moment';
// mock data
const visitData = [];
const beginDay = new Date().getTime();
const fakeY = [7, 5, 4, 2, 4, 7, 5, 6, 5, 9, 6, 3, 1, 5, 3, 6, 5];
for (let i = 0; i < fakeY.length; i += 1) {
visitData.push({
x: moment(new Date(beginDay + 1000 * 60 * 60 * 24 * i)).format('YYYY-MM-DD'),
y: fakeY[i],
});
}
const visitData2 = [];
const fakeY2 = [1, 6, 4, 8, 3, 7, 2];
for (let i = 0; i < fakeY2.length; i += 1) {
visitData2.push({
x: moment(new Date(beginDay + 1000 * 60 * 60 * 24 * i)).format('YYYY-MM-DD'),
y: fakeY2[i],
});
}
const salesData = [];
for (let i = 0; i < 12; i += 1) {
salesData.push({
x: `${i + 1}`,
y: Math.floor(Math.random() * 1000) + 200,
});
}
const searchData = [];
for (let i = 0; i < 50; i += 1) {
searchData.push({
index: i + 1,
keyword: `搜索关键词-${i}`,
count: Math.floor(Math.random() * 1000),
range: Math.floor(Math.random() * 100),
status: Math.floor((Math.random() * 10) % 2),
});
}
const salesTypeData = [
{
x: '家用电器',
y: 4544,
},
{
x: '食用酒水',
y: 3321,
},
{
x: '个护健康',
y: 3113,
},
{
x: '服饰箱包',
y: 2341,
},
{
x: '母婴产品',
y: 1231,
},
{
x: '其他',
y: 1231,
},
];
const salesTypeDataOnline = [
{
x: '家用电器',
y: 244,
},
{
x: '食用酒水',
y: 321,
},
{
x: '个护健康',
y: 311,
},
{
x: '服饰箱包',
y: 41,
},
{
x: '母婴产品',
y: 121,
},
{
x: '其他',
y: 111,
},
];
const salesTypeDataOffline = [
{
x: '家用电器',
y: 99,
},
{
x: '食用酒水',
y: 188,
},
{
x: '个护健康',
y: 344,
},
{
x: '服饰箱包',
y: 255,
},
{
x: '其他',
y: 65,
},
];
const offlineData = [];
for (let i = 0; i < 10; i += 1) {
offlineData.push({
name: `Stores ${i}`,
cvr: Math.ceil(Math.random() * 9) / 10,
});
}
const offlineChartData = [];
for (let i = 0; i < 20; i += 1) {
offlineChartData.push({
x: new Date().getTime() + 1000 * 60 * 30 * i,
y1: Math.floor(Math.random() * 100) + 10,
y2: Math.floor(Math.random() * 100) + 10,
});
}
const radarOriginData = [
{
name: '个人',
ref: 10,
koubei: 8,
output: 4,
contribute: 5,
hot: 7,
},
{
name: '团队',
ref: 3,
koubei: 9,
output: 6,
contribute: 3,
hot: 1,
},
{
name: '部门',
ref: 4,
koubei: 1,
output: 6,
contribute: 5,
hot: 7,
},
];
const radarData = [];
const radarTitleMap = {
ref: '引用',
koubei: '口碑',
output: '产量',
contribute: '贡献',
hot: '热度',
};
radarOriginData.forEach(item => {
Object.keys(item).forEach(key => {
if (key !== 'name') {
radarData.push({
name: item.name,
label: radarTitleMap[key],
value: item[key],
});
}
});
});
const getFakeChartData = {
visitData,
visitData2,
salesData,
searchData,
offlineData,
offlineChartData,
salesTypeData,
salesTypeDataOnline,
salesTypeDataOffline,
radarData,
};
export default {
'GET /api/fake_chart_data': getFakeChartData,
};

55
mock/code.js Normal file
View File

@ -0,0 +1,55 @@
import { delay } from 'roadhog-api-doc';
function getFakeList(req, res) {
const json = { code: 200, success: true, msg: '操作成功' };
const list = [];
list.push({
id: '1',
serviceName: 'blade-demo',
codeName: '通知公告',
tableName: 'blade_notice',
tablePrefix: 'blade_',
pkName: 'id',
packageName: 'org.springblade.desk',
});
json.data = {
total: 10,
size: 10,
current: 1,
searchCount: true,
pages: 1,
records: list,
};
return res.json(json);
}
function getFakeDetail(req, res) {
const json = { code: 200, success: true, msg: '操作成功' };
const detail = {
id: '1',
serviceName: 'blade-demo',
codeName: '通知公告',
tableName: 'blade_notice',
pkName: 'id',
tablePrefix: 'blade_',
packageName: 'org.springblade.desk',
apiPath: 'D:\\Develop\\WorkSpace\\Git\\SpringBlade\\blade-ops\\blade-develop',
webPath: 'D:\\Develop\\WorkSpace\\Git\\Sword',
};
json.data = detail;
return res.json(json);
}
function fakeSuccess(req, res) {
const json = { code: 200, success: true, msg: '操作成功' };
return res.json(json);
}
const proxy = {
'GET /api/blade-develop/code/list': getFakeList,
'GET /api/blade-develop/code/detail': getFakeDetail,
'POST /api/blade-develop/code/submit': fakeSuccess,
'POST /api/blade-develop/code/remove': fakeSuccess,
'POST /api/blade-develop/code/gen-code': fakeSuccess,
};
export default delay(proxy, 500);

83
mock/dept.js Normal file
View File

@ -0,0 +1,83 @@
import { delay } from 'roadhog-api-doc';
function getFakeList(req, res) {
const json = { code: 200, success: true, msg: '操作成功' };
const data = [];
data.push({
id: '1',
deptName: '刀锋科技',
fullName: '江苏刀锋科技有限公司',
sort: '1',
children: [
{
id: '2',
deptName: '常州刀锋',
fullName: '常州刀锋科技有限公司',
sort: '1',
},
{
id: '3',
deptName: '南京刀锋',
fullName: '南京刀锋科技有限公司',
sort: '2',
},
],
});
json.data = data;
return res.json(json);
}
function getFakeDetail(req, res) {
const json = { code: 200, success: true, msg: '操作成功' };
const detail = {
id: 2,
parentId: 1,
parentName: '江苏刀锋',
deptName: '常州刀锋',
fullName: '常州刀锋科技有限公司',
sort: 1,
nextSort: 4,
remark: '测试备注',
};
json.data = detail;
return res.json(json);
}
function getFakeTree(req, res) {
const json = { code: 200, success: true, msg: '操作成功' };
const list = [];
list.push({
title: '江苏刀锋',
value: '1',
key: '1',
children: [
{
title: '常州刀锋',
value: '2',
key: '2',
},
{
title: '南京刀锋',
value: '3',
key: '3',
},
],
});
json.data = list;
return res.json(json);
}
function fakeSuccess(req, res) {
const json = { code: 200, success: true, msg: '操作成功' };
return res.json(json);
}
const proxy = {
'GET /api/blade-system/dept/list': getFakeList,
'GET /api/blade-system/dept/detail': getFakeDetail,
'GET /api/blade-system/dept/tree': getFakeTree,
'POST /api/blade-system/dept/submit': fakeSuccess,
'POST /api/blade-system/dept/remove': fakeSuccess,
};
export default delay(proxy, 500);

218
mock/dict.js Normal file
View File

@ -0,0 +1,218 @@
import { delay } from 'roadhog-api-doc';
function getFakeDictionary(req, res) {
const params = req.query;
const { code } = params;
const json = { code: 200, success: true, msg: '操作成功' };
const dict = [];
if (code === 'notice') {
dict.push(
{
dictKey: '1',
dictValue: '发布通知',
},
{
dictKey: '2',
dictValue: '批转通知',
},
{
dictKey: '3',
dictValue: '转发通知',
},
{
dictKey: '4',
dictValue: '指示通知',
},
{
dictKey: '5',
dictValue: '任免通知',
},
{
dictKey: '6',
dictValue: '事务通知',
}
);
json.data = dict;
}
return res.json(json);
}
function getFakeList(req, res) {
const json = { code: 200, success: true, msg: '操作成功' };
const data = [];
data.push(
{
id: '1',
code: 'sex',
dictKey: '-1',
dictValue: '性别',
sort: '1',
children: [
{
id: '2',
code: 'sex',
dictKey: '1',
dictValue: '男',
sort: '1',
},
{
id: '3',
code: 'sex',
dictKey: '2',
dictValue: '女',
sort: '2',
},
],
},
{
id: '4',
code: 'notice',
dictKey: '-1',
dictValue: '通知类型',
sort: '1',
children: [
{
id: '5',
code: 'notice',
dictKey: '1',
dictValue: '发布通知',
sort: '1',
},
{
id: '6',
code: 'notice',
dictKey: '2',
dictValue: '批转通知',
sort: '2',
},
{
id: '7',
code: 'notice',
dictKey: '3',
dictValue: '转发通知',
sort: '3',
},
{
id: '8',
code: 'notice',
dictKey: '4',
dictValue: '指示通知',
sort: '4',
},
{
id: '9',
code: 'notice',
dictKey: '5',
dictValue: '任免通知',
sort: '5',
},
{
id: '10',
code: 'notice',
dictKey: '6',
dictValue: '事务通知',
sort: '6',
},
],
}
);
json.data = data;
return res.json(json);
}
function getFakeDetail(req, res) {
const json = { code: 200, success: true, msg: '操作成功' };
const detail = {
id: 2,
parentId: 1,
parentName: '性别',
code: 'sex',
dictKey: 1,
dictValue: '男',
sort: 1,
remark: '测试备注',
nextKey: 3,
nextSort: 3,
};
json.data = detail;
return res.json(json);
}
function getFakeTree(req, res) {
const json = { code: 200, success: true, msg: '操作成功' };
const list = [];
list.push(
{
title: '性别',
value: '1',
key: '1',
children: [
{
title: '男',
value: '2',
key: '2',
},
{
title: '女',
value: '3',
key: '3',
},
],
},
{
title: '通知类型',
value: '4',
key: '4',
children: [
{
title: '发布通知',
value: '5',
key: '5',
},
{
title: '批转通知',
value: '6',
key: '6',
},
{
title: '转发通知',
value: '7',
key: '7',
},
{
title: '指示通知',
value: '8',
key: '8',
},
{
title: '任免通知',
value: '9',
key: '9',
},
{
title: '事务通知',
value: '10',
key: '10',
},
],
}
);
json.data = list;
return res.json(json);
}
function fakeSuccess(req, res) {
const json = { code: 200, success: true, msg: '操作成功' };
return res.json(json);
}
const proxy = {
'GET /api/blade-system/dict/dictionary': getFakeDictionary,
'GET /api/blade-system/dict/list': getFakeList,
'GET /api/blade-system/dict/detail': getFakeDetail,
'GET /api/blade-system/dict/tree': getFakeTree,
'POST /api/blade-system/dict/submit': fakeSuccess,
'POST /api/blade-system/dict/remove': fakeSuccess,
};
export default delay(proxy, 500);

15
mock/geographic.js Normal file
View File

@ -0,0 +1,15 @@
import city from './geographic/city.json';
import province from './geographic/province.json';
function getProvince(req, res) {
return res.json(province);
}
function getCity(req, res) {
return res.json(city[req.params.province]);
}
export default {
'GET /api/geographic/province': getProvince,
'GET /api/geographic/city/:province': getCity,
};

1784
mock/geographic/city.json Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,138 @@
[
{
"name": "北京市",
"id": "110000"
},
{
"name": "天津市",
"id": "120000"
},
{
"name": "河北省",
"id": "130000"
},
{
"name": "山西省",
"id": "140000"
},
{
"name": "内蒙古自治区",
"id": "150000"
},
{
"name": "辽宁省",
"id": "210000"
},
{
"name": "吉林省",
"id": "220000"
},
{
"name": "黑龙江省",
"id": "230000"
},
{
"name": "上海市",
"id": "310000"
},
{
"name": "江苏省",
"id": "320000"
},
{
"name": "浙江省",
"id": "330000"
},
{
"name": "安徽省",
"id": "340000"
},
{
"name": "福建省",
"id": "350000"
},
{
"name": "江西省",
"id": "360000"
},
{
"name": "山东省",
"id": "370000"
},
{
"name": "河南省",
"id": "410000"
},
{
"name": "湖北省",
"id": "420000"
},
{
"name": "湖南省",
"id": "430000"
},
{
"name": "广东省",
"id": "440000"
},
{
"name": "广西壮族自治区",
"id": "450000"
},
{
"name": "海南省",
"id": "460000"
},
{
"name": "重庆市",
"id": "500000"
},
{
"name": "四川省",
"id": "510000"
},
{
"name": "贵州省",
"id": "520000"
},
{
"name": "云南省",
"id": "530000"
},
{
"name": "西藏自治区",
"id": "540000"
},
{
"name": "陕西省",
"id": "610000"
},
{
"name": "甘肃省",
"id": "620000"
},
{
"name": "青海省",
"id": "630000"
},
{
"name": "宁夏回族自治区",
"id": "640000"
},
{
"name": "新疆维吾尔自治区",
"id": "650000"
},
{
"name": "台湾省",
"id": "710000"
},
{
"name": "香港特别行政区",
"id": "810000"
},
{
"name": "澳门特别行政区",
"id": "820000"
}
]

324
mock/log.js Normal file
View File

@ -0,0 +1,324 @@
import { delay } from 'roadhog-api-doc';
function getFakeUsualList(req, res) {
const json = { code: 200, success: true, msg: '操作成功' };
const list = [];
list.push(
{
id: '1',
serviceId: 'blade-auth',
serverHost: 'blade',
serverIp: '192.168.0.1',
env: 'dev',
logLevel: 'info',
logId: 'test',
logData: '测试日志1',
method: 'get',
requestUri: '/token',
},
{
id: '2',
serviceId: 'blade-auth',
serverHost: 'blade',
serverIp: '192.168.0.1',
env: 'dev',
logLevel: 'info',
logId: 'test',
logData: '测试日志2',
method: 'get',
requestUri: '/token',
},
{
id: '3',
serviceId: 'blade-auth',
serverHost: 'blade',
serverIp: '192.168.0.1',
env: 'dev',
logLevel: 'info',
logId: 'test',
logData: '测试日志3',
method: 'get',
requestUri: '/token',
}
);
json.data = {
total: 10,
size: 10,
current: 1,
searchCount: true,
pages: 1,
records: list,
};
return res.json(json);
}
function getFakeUsualDetail(req, res) {
const json = { code: 200, success: true, msg: '操作成功' };
const detail = {
id: '3',
serviceId: 'blade-auth',
serverHost: 'blade',
serverIp: '192.168.0.1',
env: 'dev',
logLevel: 'info',
logId: 'test',
logData: '测试日志3',
method: 'get',
requestUri: '/token',
userAgent:
'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36',
params: "{'name':'test'}",
createBy: 'admin',
createTime: '2018-12-21 12:00:00',
};
json.data = detail;
return res.json(json);
}
function getFakeApiList(req, res) {
const json = { code: 200, success: true, msg: '操作成功' };
const list = [];
list.push(
{
id: '1',
serviceId: 'blade-auth',
serverHost: 'blade',
serverIp: '192.168.0.1',
env: 'dev',
title: '测试日志1',
method: 'get',
requestUri: '/token',
},
{
id: '2',
serviceId: 'blade-auth',
serverHost: 'blade',
serverIp: '192.168.0.1',
env: 'dev',
title: '测试日志2',
method: 'get',
requestUri: '/token',
},
{
id: '3',
serviceId: 'blade-auth',
serverHost: 'blade',
serverIp: '192.168.0.1',
env: 'dev',
title: '测试日志3',
method: 'get',
requestUri: '/token',
}
);
json.data = {
total: 10,
size: 10,
current: 1,
searchCount: true,
pages: 1,
records: list,
};
return res.json(json);
}
function getFakeApiDetail(req, res) {
const json = { code: 200, success: true, msg: '操作成功' };
const detail = {
id: '3',
serviceId: 'blade-auth',
serverHost: 'blade',
serverIp: '192.168.0.1',
env: 'dev',
title: '测试日志3',
method: 'get',
requestUri: '/token',
userAgent:
'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36',
remoteIp: '0:0:0:0:0:0:0:1',
methodClass: 'org.springblade.auth.controller.AuthController',
methodName: 'token',
time: '19',
params: "{'account':'test'}",
createBy: 'admin',
createTime: '2018-12-21 12:00:00',
};
json.data = detail;
return res.json(json);
}
function getFakeErrorList(req, res) {
const json = { code: 200, success: true, msg: '操作成功' };
const list = [];
list.push(
{
id: '1',
serviceId: 'blade-auth',
serverHost: 'blade',
serverIp: '192.168.0.1',
env: 'dev',
method: 'get',
requestUri: '/token',
},
{
id: '2',
serviceId: 'blade-auth',
serverHost: 'blade',
serverIp: '192.168.0.1',
env: 'dev',
method: 'get',
requestUri: '/token',
},
{
id: '3',
serviceId: 'blade-auth',
serverHost: 'blade',
serverIp: '192.168.0.1',
env: 'dev',
method: 'get',
requestUri: '/token',
}
);
json.data = {
total: 10,
size: 10,
current: 1,
searchCount: true,
pages: 1,
records: list,
};
return res.json(json);
}
function getFakeErrorDetail(req, res) {
const json = { code: 200, success: true, msg: '操作成功' };
const detail = {
id: '3',
serviceId: 'blade-auth',
serverHost: 'blade',
serverIp: '192.168.0.1',
env: 'dev',
method: 'get',
requestUri: '/token',
userAgent:
'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36',
stackTrace:
'java.lang.ArithmeticException: / by zero\n' +
'\tat org.springblade.auth.controller.AuthController.token(AuthController.java:58)\n' +
'\tat org.springblade.auth.controller.AuthController$$FastClassBySpringCGLIB$$98d484bd.invoke()\n' +
'\tat org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204)\n' +
'\tat org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:746)\n' +
'\tat org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)\n' +
'\tat org.springframework.aop.aspectj.MethodInvocationProceedingJoinPoint.proceed(MethodInvocationProceedingJoinPoint.java:88)\n' +
'\tat org.springblade.core.log.aspect.ApiLogAspect.around(ApiLogAspect.java:42)\n' +
'\tat sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)\n' +
'\tat sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)\n' +
'\tat sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)\n' +
'\tat java.lang.reflect.Method.invoke(Method.java:498)\n' +
'\tat org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethodWithGivenArgs(AbstractAspectJAdvice.java:644)\n' +
'\tat org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethod(AbstractAspectJAdvice.java:633)\n' +
'\tat org.springframework.aop.aspectj.AspectJAroundAdvice.invoke(AspectJAroundAdvice.java:70)\n' +
'\tat org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:174)\n' +
'\tat org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:92)\n' +
'\tat org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:185)\n' +
'\tat org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:688)\n' +
'\tat org.springblade.auth.controller.AuthController$$EnhancerBySpringCGLIB$$e4cbcf2e.token()\n' +
'\tat sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)\n' +
'\tat sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)\n' +
'\tat sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)\n' +
'\tat java.lang.reflect.Method.invoke(Method.java:498)\n' +
'\tat org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:209)\n' +
'\tat org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:136)\n' +
'\tat org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:102)\n' +
'\tat org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:891)\n' +
'\tat org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:797)\n' +
'\tat org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)\n' +
'\tat org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:991)\n' +
'\tat org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:925)\n' +
'\tat org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:974)\n' +
'\tat org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:877)\n' +
'\tat javax.servlet.http.HttpServlet.service(HttpServlet.java:707)\n' +
'\tat org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:851)\n' +
'\tat javax.servlet.http.HttpServlet.service(HttpServlet.java:790)\n' +
'\tat io.undertow.servlet.handlers.ServletHandler.handleRequest(ServletHandler.java:74)\n' +
'\tat io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:129)\n' +
'\tat org.springblade.core.tool.support.xss.XssFilter.doFilter(XssFilter.java:40)\n' +
'\tat io.undertow.servlet.core.ManagedFilter.doFilter(ManagedFilter.java:61)\n' +
'\tat io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:131)\n' +
'\tat org.springframework.boot.actuate.web.trace.servlet.HttpTraceFilter.doFilterInternal(HttpTraceFilter.java:90)\n' +
'\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)\n' +
'\tat io.undertow.servlet.core.ManagedFilter.doFilter(ManagedFilter.java:61)\n' +
'\tat io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:131)\n' +
'\tat org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:99)\n' +
'\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)\n' +
'\tat io.undertow.servlet.core.ManagedFilter.doFilter(ManagedFilter.java:61)\n' +
'\tat io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:131)\n' +
'\tat org.springframework.web.filter.HttpPutFormContentFilter.doFilterInternal(HttpPutFormContentFilter.java:109)\n' +
'\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)\n' +
'\tat io.undertow.servlet.core.ManagedFilter.doFilter(ManagedFilter.java:61)\n' +
'\tat io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:131)\n' +
'\tat org.springframework.web.filter.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:93)\n' +
'\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)\n' +
'\tat io.undertow.servlet.core.ManagedFilter.doFilter(ManagedFilter.java:61)\n' +
'\tat io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:131)\n' +
'\tat org.springframework.boot.actuate.metrics.web.servlet.WebMvcMetricsFilter.filterAndRecordMetrics(WebMvcMetricsFilter.java:155)\n' +
'\tat org.springframework.boot.actuate.metrics.web.servlet.WebMvcMetricsFilter.filterAndRecordMetrics(WebMvcMetricsFilter.java:123)\n' +
'\tat org.springframework.boot.actuate.metrics.web.servlet.WebMvcMetricsFilter.doFilterInternal(WebMvcMetricsFilter.java:108)\n' +
'\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)\n' +
'\tat io.undertow.servlet.core.ManagedFilter.doFilter(ManagedFilter.java:61)\n' +
'\tat io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:131)\n' +
'\tat org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:200)\n' +
'\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)\n' +
'\tat io.undertow.servlet.core.ManagedFilter.doFilter(ManagedFilter.java:61)\n' +
'\tat io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:131)\n' +
'\tat io.undertow.servlet.handlers.FilterHandler.handleRequest(FilterHandler.java:84)\n' +
'\tat io.undertow.servlet.handlers.security.ServletSecurityRoleHandler.handleRequest(ServletSecurityRoleHandler.java:62)\n' +
'\tat io.undertow.servlet.handlers.ServletChain$1.handleRequest(ServletChain.java:65)\n' +
'\tat io.undertow.servlet.handlers.ServletDispatchingHandler.handleRequest(ServletDispatchingHandler.java:36)\n' +
'\tat io.undertow.servlet.handlers.security.SSLInformationAssociationHandler.handleRequest(SSLInformationAssociationHandler.java:132)\n' +
'\tat io.undertow.servlet.handlers.security.ServletAuthenticationCallHandler.handleRequest(ServletAuthenticationCallHandler.java:57)\n' +
'\tat io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)\n' +
'\tat io.undertow.security.handlers.AbstractConfidentialityHandler.handleRequest(AbstractConfidentialityHandler.java:46)\n' +
'\tat io.undertow.servlet.handlers.security.ServletConfidentialityConstraintHandler.handleRequest(ServletConfidentialityConstraintHandler.java:64)\n' +
'\tat io.undertow.security.handlers.AuthenticationMechanismsHandler.handleRequest(AuthenticationMechanismsHandler.java:60)\n' +
'\tat io.undertow.servlet.handlers.security.CachedAuthenticatedSessionHandler.handleRequest(CachedAuthenticatedSessionHandler.java:77)\n' +
'\tat io.undertow.security.handlers.AbstractSecurityContextAssociationHandler.handleRequest(AbstractSecurityContextAssociationHandler.java:43)\n' +
'\tat io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)\n' +
'\tat io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)\n' +
'\tat io.undertow.servlet.handlers.SessionRestoringHandler.handleRequest(SessionRestoringHandler.java:119)\n' +
'\tat io.undertow.servlet.handlers.ServletInitialHandler.handleFirstRequest(ServletInitialHandler.java:292)\n' +
'\tat io.undertow.servlet.handlers.ServletInitialHandler.access$100(ServletInitialHandler.java:81)\n' +
'\tat io.undertow.servlet.handlers.ServletInitialHandler$2.call(ServletInitialHandler.java:138)\n' +
'\tat io.undertow.servlet.handlers.ServletInitialHandler$2.call(ServletInitialHandler.java:135)\n' +
'\tat io.undertow.servlet.core.ServletRequestContextThreadSetupAction$1.call(ServletRequestContextThreadSetupAction.java:48)\n' +
'\tat io.undertow.servlet.core.ContextClassLoaderSetupAction$1.call(ContextClassLoaderSetupAction.java:43)\n' +
'\tat io.undertow.servlet.handlers.ServletInitialHandler.dispatchRequest(ServletInitialHandler.java:272)\n' +
'\tat io.undertow.servlet.handlers.ServletInitialHandler.access$000(ServletInitialHandler.java:81)\n' +
'\tat io.undertow.servlet.handlers.ServletInitialHandler$1.handleRequest(ServletInitialHandler.java:104)\n' +
'\tat io.undertow.server.Connectors.executeRootHandler(Connectors.java:336)\n' +
'\tat io.undertow.server.HttpServerExchange$1.run(HttpServerExchange.java:830)\n' +
'\tat java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)\n' +
'\tat java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)\n' +
'\tat java.lang.Thread.run(Thread.java:748)\n',
exceptionName: 'java.lang.ArithmeticException',
message: '/ by zero',
lineNumber: '58',
methodClass: 'org.springblade.auth.controller.AuthController',
fileName: 'AuthController.java',
methodName: 'token',
params: "{name:'test'}",
createBy: 'admin',
createTime: '2018-12-21 12:00:00',
};
json.data = detail;
return res.json(json);
}
const proxy = {
'GET /api/blade-log/usual/list': getFakeUsualList,
'GET /api/blade-log/usual/detail': getFakeUsualDetail,
'GET /api/blade-log/api/list': getFakeApiList,
'GET /api/blade-log/api/detail': getFakeApiDetail,
'GET /api/blade-log/error/list': getFakeErrorList,
'GET /api/blade-log/error/detail': getFakeErrorDetail,
};
export default delay(proxy, 500);

878
mock/menu.js Normal file
View File

@ -0,0 +1,878 @@
import { delay } from 'roadhog-api-doc';
function getFakeRoutes(req, res) {
const json = {
code: 200,
success: true,
data: [
{
path: '/desk',
code: 'desk',
source: 'desktop',
children: [
{
path: '/desk/notice',
code: 'notice',
},
],
},
{
path: '/system',
code: 'system',
source: 'setting',
children: [
{
path: '/system/user',
code: 'user',
},
{
path: '/system/dept',
code: 'dept',
},
{
path: '/system/dict',
code: 'dict',
},
{
path: '/system/menu',
code: 'menu',
},
{
path: '/system/role',
code: 'role',
},
{
path: '/system/param',
code: 'param',
},
],
},
{
path: '/monitor',
code: 'monitor',
source: 'fund',
children: [
{
path: 'http://localhost/doc.html',
target: '_blank',
code: 'doc',
},
{
path: 'http://localhost:7002',
target: '_blank',
code: 'admin',
},
{
path: '/monitor/log',
code: 'log',
children: [
{
path: '/monitor/log/usual',
code: 'log_usual',
},
{
path: '/monitor/log/api',
code: 'log_api',
},
{
path: '/monitor/log/error',
code: 'log_error',
},
],
},
],
},
{
path: '/tool',
code: 'tool',
source: 'tool',
children: [
{
path: '/tool/code',
code: 'code',
},
],
},
],
msg: '操作成功',
};
return res.json(json);
}
function getFakeButtons(req, res) {
const json = {
code: 200,
success: true,
data: [
{
code: 'notice',
children: [
{
code: 'notice_add',
name: '新增',
path: '/desk/notice/add',
source: 'plus',
action: 1,
alias: 'add',
},
{
code: 'notice_edit',
name: '修改',
path: '/desk/notice/edit',
source: 'form',
action: 2,
alias: 'edit',
},
{
code: 'notice_delete',
name: '删除',
path: '/api/blade-system/dept/remove',
source: 'delete',
action: 3,
alias: 'delete',
},
{
code: 'notice_view',
name: '查看',
path: '/desk/notice/view',
source: 'file-text',
action: 2,
alias: 'view',
},
],
},
{
code: 'user',
children: [
{
code: 'user_add',
name: '新增',
path: '/system/user/add',
source: 'plus',
action: 1,
alias: 'add',
},
{
code: 'user_edit',
name: '修改',
path: '/system/user/edit',
source: 'form',
action: 2,
alias: 'edit',
},
{
code: 'user_delete',
name: '删除',
path: '/api/blade-system/user/remove',
source: 'delete',
action: 3,
alias: 'delete',
},
{
code: 'user_role',
name: '角色配置',
path: '',
source: 'user-add',
action: 1,
alias: 'role',
},
{
code: 'user_reset',
name: '密码重置',
path: '/api/blade-system/user/reset-password',
source: 'retweet',
action: 1,
alias: 'reset-password',
},
{
code: 'user_view',
name: '查看',
path: '/system/user/view',
source: 'file-text',
action: 2,
alias: 'view',
},
],
},
{
code: 'dept',
children: [
{
code: 'dept_add',
name: '新增',
path: '/system/dept/add',
source: 'plus',
action: 1,
alias: 'add',
},
{
code: 'dept_edit',
name: '修改',
path: '/system/dept/edit',
source: 'form',
action: 2,
alias: 'edit',
},
{
code: 'dept_delete',
name: '删除',
path: '/api/blade-system/dept/remove',
source: 'delete',
action: 3,
alias: 'delete',
},
{
code: 'dept_view',
name: '查看',
path: '/system/dept/view',
source: 'file-text',
action: 2,
alias: 'view',
},
],
},
{
code: 'dict',
children: [
{
code: 'dict_add',
name: '新增',
path: '/system/dict/add',
source: 'plus',
action: 1,
alias: 'add',
},
{
code: 'dict_edit',
name: '修改',
path: '/system/dict/edit',
source: 'form',
action: 2,
alias: 'edit',
},
{
code: 'dict_delete',
name: '删除',
path: '/api/blade-system/dict/remove',
source: 'delete',
action: 3,
alias: 'delete',
},
{
code: 'dict_view',
name: '查看',
path: '/system/dict/view',
source: 'file-text',
action: 2,
alias: 'view',
},
],
},
{
code: 'menu',
children: [
{
code: 'menu_add',
name: '新增',
path: '/system/menu/add',
source: 'plus',
action: 1,
alias: 'add',
},
{
code: 'menu_edit',
name: '修改',
path: '/system/menu/edit',
source: 'form',
action: 2,
alias: 'edit',
},
{
code: 'menu_delete',
name: '删除',
path: '/api/blade-system/menu/remove',
source: 'delete',
action: 3,
alias: 'delete',
},
{
code: 'menu_view',
name: '查看',
path: '/system/menu/view',
source: 'file-text',
action: 2,
alias: 'view',
},
],
},
{
code: 'role',
children: [
{
code: 'role_add',
name: '新增',
path: '/system/role/add',
source: 'plus',
action: 1,
alias: 'add',
},
{
code: 'role_edit',
name: '修改',
path: '/system/role/edit',
source: 'form',
action: 2,
alias: 'edit',
},
{
code: 'role_delete',
name: '删除',
path: '/api/blade-system/role/remove',
source: 'delete',
action: 3,
alias: 'delete',
},
{
code: 'role_view',
name: '查看',
path: '/system/role/view',
source: 'file-text',
action: 2,
alias: 'view',
},
],
},
{
code: 'param',
children: [
{
code: 'param_add',
name: '新增',
path: '/system/param/add',
source: 'plus',
action: 1,
alias: 'add',
},
{
code: 'param_edit',
name: '修改',
path: '/system/param/edit',
source: 'form',
action: 2,
alias: 'edit',
},
{
code: 'param_delete',
name: '删除',
path: '/api/blade-system/param/remove',
source: 'delete',
action: 3,
alias: 'delete',
},
{
code: 'param_view',
name: '查看',
path: '/system/param/view',
source: 'file-text',
action: 2,
alias: 'view',
},
],
},
{
code: 'log_usual',
children: [
{
code: 'log_usual_view',
name: '查看',
path: '/monitor/log/usual/view',
source: 'file-text',
action: 2,
alias: 'view',
},
],
},
{
code: 'log_error',
children: [
{
code: 'log_error_view',
name: '查看',
path: '/monitor/log/error/view',
source: 'file-text',
action: 2,
alias: 'view',
},
],
},
{
code: 'log_api',
children: [
{
code: 'log_api_view',
name: '查看',
path: '/monitor/log/api/view',
source: 'file-text',
action: 2,
alias: 'view',
},
],
},
{
code: 'code',
children: [
{
code: 'code_add',
name: '新增',
path: '/tool/code/add',
source: 'plus',
action: 1,
alias: 'add',
},
{
code: 'code_edit',
name: '修改',
path: '/tool/code/edit',
source: 'form',
action: 2,
alias: 'edit',
},
{
code: 'code_delete',
name: '删除',
path: '/api/blade-develop/code/remove',
source: 'delete',
action: 3,
alias: 'delete',
},
{
code: 'code_view',
name: '查看',
path: '/tool/code/view',
source: 'file-text',
action: 2,
alias: 'view',
},
],
},
],
msg: '操作成功',
};
return res.json(json);
}
function getFakeList(req, res) {
const json = { code: 200, success: true, msg: '操作成功' };
const data = [];
data.push(
{
id: '1',
code: 'desk',
parentId: '',
name: '工作台',
path: '/desk',
source: 'desktop',
category: '1',
categoryName: '菜单',
sort: '1',
children: [
{
id: '2',
code: 'notice',
parentId: 'desk',
name: '通知公告',
path: '/desk/notice',
source: 'desktop',
category: '1',
categoryName: '菜单',
sort: '2',
},
],
},
{
id: '3',
code: 'system',
parentId: '',
name: '系统管理',
path: '/system',
source: 'setting',
category: '1',
categoryName: '菜单',
sort: '3',
children: [
{
id: '4',
code: 'user',
parentId: 'system',
name: '用户管理',
path: '/system/user',
source: 'setting',
category: '1',
categoryName: '菜单',
sort: '4',
},
{
id: '5',
code: 'dept',
parentId: 'system',
name: '部门管理',
path: '/system/dept',
source: 'setting',
category: '1',
categoryName: '菜单',
sort: '5',
},
{
id: '6',
code: 'dict',
parentId: 'system',
name: '字典管理',
path: '/system/dict',
source: 'setting',
category: '1',
categoryName: '菜单',
sort: '6',
},
{
id: '7',
code: 'menu',
parentId: 'system',
name: '菜单管理',
path: '/system/menu',
source: 'setting',
category: '1',
categoryName: '菜单',
sort: '7',
},
{
id: '8',
code: 'role',
parentId: 'system',
name: '角色管理',
path: '/system/role',
source: 'setting',
category: '1',
categoryName: '菜单',
sort: '8',
},
{
id: '9',
code: 'parameter',
parentId: 'system',
name: '参数管理',
path: '/system/param',
source: 'setting',
category: '1',
categoryName: '菜单',
sort: '9',
},
{
id: '10',
code: 'log',
parentId: 'system',
name: '日志管理',
path: '/system/log',
source: 'setting',
category: '1',
categoryName: '菜单',
sort: '10',
},
],
}
);
json.data = data;
return res.json(json);
}
function getFakeDetail(req, res) {
const json = { code: 200, success: true, msg: '操作成功' };
const detail = {
id: '2',
code: 'notice',
parentId: 'desk',
parentName: '顶级',
name: '通知公告',
alias: 'menu',
path: '/desk/notice',
source: 'desktop',
category: '1',
categoryName: '菜单',
action: 1,
actionName: '否',
isOpen: 1,
isOpenName: '否',
sort: '2',
};
json.data = detail;
return res.json(json);
}
function getFakeTree(req, res) {
const json = { code: 200, success: true, msg: '操作成功' };
const list = [];
list.push(
{
value: 'desk',
key: 'desk',
title: '工作台',
children: [
{
value: 'notice',
key: 'notice',
title: '通知公告',
},
],
},
{
value: 'system',
key: 'system',
title: '系统管理',
children: [
{
value: 'user',
key: 'user',
title: '用户管理',
},
{
value: 'dept',
key: 'dept',
title: '部门管理',
},
{
value: 'dict',
key: 'dict',
title: '字典管理',
},
{
value: 'menu',
key: 'menu',
title: '菜单管理',
},
{
value: 'role',
key: 'role',
title: '角色管理',
},
{
value: 'param',
key: 'param',
title: '参数管理',
},
{
value: 'log',
key: 'log',
title: '日志管理',
},
],
}
);
json.data = list;
return res.json(json);
}
function getFakeGrantTree(req, res) {
const json = { code: 200, success: true, msg: '操作成功' };
const list = [];
list.push(
{
key: 'desk',
title: '工作台',
children: [
{
key: 'notice',
title: '通知公告',
children: [
{
key: 'notice_add',
title: '新增',
},
{
key: 'notice_edit',
title: '修改',
},
{
key: 'notice_delete',
title: '删除',
},
{
key: 'notice_view',
title: '查看',
},
],
},
],
},
{
key: 'system',
title: '系统管理',
children: [
{
key: 'user',
title: '用户管理',
children: [
{
key: 'user_add',
title: '新增',
},
{
key: 'user_edit',
title: '修改',
},
{
key: 'user_delete',
title: '删除',
},
{
key: 'user_view',
title: '查看',
},
],
},
{
key: 'dept',
title: '部门管理',
children: [
{
key: 'dept_add',
title: '新增',
},
{
key: 'dept_edit',
title: '修改',
},
{
key: 'dept_delete',
title: '删除',
},
{
key: 'dept_view',
title: '查看',
},
],
},
{
key: 'dict',
title: '字典管理',
children: [
{
key: 'dict_add',
title: '新增',
},
{
key: 'dict_edit',
title: '修改',
},
{
key: 'dict_delete',
title: '删除',
},
{
key: 'dict_view',
title: '查看',
},
],
},
{
key: 'menu',
title: '菜单管理',
children: [
{
key: 'menu_add',
title: '新增',
},
{
key: 'menu_edit',
title: '修改',
},
{
key: 'menu_delete',
title: '删除',
},
{
key: 'menu_view',
title: '查看',
},
],
},
{
key: 'role',
title: '角色管理',
children: [
{
key: 'role_add',
title: '新增',
},
{
key: 'role_edit',
title: '修改',
},
{
key: 'role_delete',
title: '删除',
},
{
key: 'role_view',
title: '查看',
},
],
},
{
key: 'param',
title: '参数管理',
children: [
{
key: 'param_add',
title: '新增',
},
{
key: 'param_edit',
title: '修改',
},
{
key: 'param_delete',
title: '删除',
},
{
key: 'param_view',
title: '查看',
},
],
},
],
}
);
json.data = list;
return res.json(json);
}
function getFakeAuthRoutes(req, res) {
const json = { code: 200, success: true, msg: '操作成功' };
json.data = {
'/form/advanced-form': { authority: ['admin', 'user'] },
};
return res.json(json);
}
function fakeSuccess(req, res) {
const json = { code: 200, success: true, msg: '操作成功' };
return res.json(json);
}
function getFakeRoleTreeKeys(req, res) {
const json = { code: 200, success: true, data: ['1'], msg: '操作成功' };
return res.json(json);
}
const proxy = {
'GET /api/blade-system/menu/routes': getFakeRoutes,
'GET /api/blade-system/menu/buttons': getFakeButtons,
'GET /api/blade-system/menu/list': getFakeList,
'GET /api/blade-system/menu/detail': getFakeDetail,
'GET /api/blade-system/menu/tree': getFakeTree,
'GET /api/blade-system/menu/grant-tree': getFakeGrantTree,
'GET /api/blade-system/menu/role-tree-keys': getFakeRoleTreeKeys,
'GET /api/blade-system/menu/auth-routes': getFakeAuthRoutes,
'POST /api/blade-system/menu/submit': fakeSuccess,
'POST /api/blade-system/menu/remove': fakeSuccess,
};
export default delay(proxy, 500);

149
mock/notice.js Normal file
View File

@ -0,0 +1,149 @@
import { delay } from 'roadhog-api-doc';
const proxy = {
'GET /api/blade-desk/notice/list': {
code: 200,
data: {
total: 15,
size: 10,
current: 1,
searchCount: true,
pages: 2,
records: [
{
id: '1',
title: '博客标题1',
categoryName: '批转通知',
content: '博客内容1',
date: '2018-05-08 12:00:00',
},
{
id: '2',
title: '博客标题2',
categoryName: '发布通知',
content: '博客内容2',
date: '2018-06-08 12:00:00',
},
{
id: '3',
title: '博客标题3',
categoryName: '任免通知',
content: '博客内容3',
date: '2018-07-08 12:00:00',
},
{
id: '4',
title: '博客标题4',
categoryName: '指示通知',
content: '博客内容4',
date: '2018-08-08 12:00:00',
},
{
id: '5',
title: '博客标题5',
categoryName: '转发通知',
content: '博客内容5',
date: '2018-09-08 12:00:00',
},
{
id: '6',
title: '博客标题1',
categoryName: '批转通知',
content: '博客内容1',
date: '2018-05-08 12:00:00',
},
{
id: '7',
title: '博客标题2',
categoryName: '发布通知',
content: '博客内容2',
date: '2018-06-08 12:00:00',
},
{
id: '8',
title: '博客标题3',
categoryName: '任免通知',
content: '博客内容3',
date: '2018-07-08 12:00:00',
},
{
id: '9',
title: '博客标题4',
categoryName: '指示通知',
content: '博客内容4',
date: '2018-08-08 12:00:00',
},
{
id: '10',
title: '博客标题5',
categoryName: '转发通知',
content: '博客内容5',
date: '2018-09-08 12:00:00',
},
{
id: '11',
title: '博客标题1',
categoryName: '批转通知',
content: '博客内容1',
date: '2018-05-08 12:00:00',
},
{
id: '12',
title: '博客标题2',
categoryName: '发布通知',
content: '博客内容2',
date: '2018-06-08 12:00:00',
},
{
id: '13',
title: '博客标题3',
categoryName: '任免通知',
content: '博客内容3',
date: '2018-07-08 12:00:00',
},
{
id: '14',
title: '博客标题4',
categoryName: '指示通知',
content: '博客内容4',
date: '2018-08-08 12:00:00',
},
{
id: '15',
title: '博客标题5',
categoryName: '转发通知',
content: '博客内容5',
date: '2018-09-08 12:00:00',
},
],
},
message: 'success',
success: true,
},
'POST /api/blade-desk/notice/submit': {
code: 200,
data: {},
message: 'success',
success: true,
},
'POST /api/blade-desk/notice/remove': {
code: 200,
data: {},
message: 'success',
success: true,
},
'GET /api/blade-desk/notice/detail': {
code: 200,
data: {
title: '通知标题详情',
category: '3',
categoryName: '转发通知',
date: '2018-12-31 23:33:33',
content: '通知公告内容详情',
},
message: 'success',
success: true,
},
};
export default delay(proxy, 500);

115
mock/notices.js Normal file
View File

@ -0,0 +1,115 @@
const fakeNotices = [
{
id: '000000001',
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/ThXAXghbEsBCCSDihZxY.png',
title: '你收到了 14 份新周报',
datetime: '2017-08-09',
type: 'notification',
},
{
id: '000000002',
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/OKJXDXrmkNshAMvwtvhu.png',
title: '你推荐的 曲妮妮 已通过第三轮面试',
datetime: '2017-08-08',
type: 'notification',
},
{
id: '000000003',
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/kISTdvpyTAhtGxpovNWd.png',
title: '这种模板可以区分多种通知类型',
datetime: '2017-08-07',
read: true,
type: 'notification',
},
{
id: '000000004',
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/GvqBnKhFgObvnSGkDsje.png',
title: '左侧图标用于区分不同的类型',
datetime: '2017-08-07',
type: 'notification',
},
{
id: '000000005',
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/ThXAXghbEsBCCSDihZxY.png',
title: '内容不要超过两行字,超出时自动截断',
datetime: '2017-08-07',
type: 'notification',
},
{
id: '000000006',
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/fcHMVNCjPOsbUGdEduuv.jpeg',
title: '曲丽丽 评论了你',
description: '描述信息描述信息描述信息',
datetime: '2017-08-07',
type: 'message',
clickClose: true,
},
{
id: '000000007',
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/fcHMVNCjPOsbUGdEduuv.jpeg',
title: '朱偏右 回复了你',
description: '这种模板用于提醒谁与你发生了互动,左侧放『谁』的头像',
datetime: '2017-08-07',
type: 'message',
clickClose: true,
},
{
id: '000000008',
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/fcHMVNCjPOsbUGdEduuv.jpeg',
title: '标题',
description: '这种模板用于提醒谁与你发生了互动,左侧放『谁』的头像',
datetime: '2017-08-07',
type: 'message',
clickClose: true,
},
{
id: '000000009',
title: '任务名称',
description: '任务需要在 2017-01-12 20:00 前启动',
extra: '未开始',
status: 'todo',
type: 'event',
},
{
id: '000000010',
title: '第三方紧急代码变更',
description: '冠霖提交于 2017-01-06需在 2017-01-07 前完成代码变更任务',
extra: '马上到期',
status: 'urgent',
type: 'event',
},
{
id: '000000011',
title: '信息安全考试',
description: '指派竹尔于 2017-01-09 前完成更新并发布',
extra: '已耗时 8 天',
status: 'doing',
type: 'event',
},
{
id: '000000012',
title: 'ABCD 版本发布',
description: '冠霖提交于 2017-01-06需在 2017-01-07 前完成代码变更任务',
extra: '进行中',
status: 'processing',
type: 'event',
},
];
const getNotices = (req, res) => {
if (req.query && req.query.type) {
const startFrom = parseInt(req.query.lastItemId, 10) + 1;
const result = fakeNotices
.filter(({ type }) => type === req.query.type)
.map((notice, index) => ({
...notice,
id: `0000000${startFrom + index}`,
}));
return res.json(startFrom > 24 ? result.concat(null) : result);
}
return res.json(fakeNotices);
};
export default {
'GET /api/blade-desk/notice/my-notices': getNotices,
};

57
mock/param.js Normal file
View File

@ -0,0 +1,57 @@
import { delay } from 'roadhog-api-doc';
function getFakeList(req, res) {
const json = { code: 200, success: true, msg: '操作成功' };
const list = [];
list.push(
{
id: '1',
paramName: '是否开启注册功能',
paramKey: 'account.registerUser',
paramValue: 'true',
remark: '描述',
},
{
id: '2',
paramName: '账号初始密码',
paramKey: 'account.initPassword',
paramValue: '123456',
remark: '描述',
}
);
json.data = {
total: 10,
size: 10,
current: 1,
searchCount: true,
pages: 1,
records: list,
};
return res.json(json);
}
function getFakeDetail(req, res) {
const json = { code: 200, success: true, msg: '操作成功' };
const detail = {
id: '1',
paramName: '是否开启注册功能',
paramKey: 'account.registerUser',
paramValue: 'true',
remark: '描述',
};
json.data = detail;
return res.json(json);
}
function fakeSuccess(req, res) {
const json = { code: 200, success: true, msg: '操作成功' };
return res.json(json);
}
const proxy = {
'GET /api/blade-system/param/list': getFakeList,
'GET /api/blade-system/param/detail': getFakeDetail,
'POST /api/blade-system/param/submit': fakeSuccess,
'POST /api/blade-system/param/remove': fakeSuccess,
};
export default delay(proxy, 500);

99
mock/role.js Normal file
View File

@ -0,0 +1,99 @@
import { delay } from 'roadhog-api-doc';
function getFakeList(req, res) {
const json = { code: 200, success: true, msg: '操作成功' };
const data = [];
data.push(
{
id: '1',
roleName: '超级管理员',
roleAlias: 'administrator',
sort: '1',
children: [
{
id: '2',
roleName: '管理员',
roleAlias: 'admin',
sort: '1',
},
],
},
{
id: '3',
roleName: '用户',
roleAlias: 'user',
sort: '2',
children: [
{
id: '4',
roleName: '普通用户',
roleAlias: 'user',
sort: '1',
},
{
id: '5',
roleName: '访客',
roleAlias: 'guest',
sort: '2',
},
],
}
);
json.data = data;
return res.json(json);
}
function getFakeDetail(req, res) {
const json = { code: 200, success: true, msg: '操作成功' };
const detail = {
id: 2,
parentId: 1,
parentName: '超级管理员',
roleName: '用户',
roleAlias: 'user',
sort: 1,
nextSort: 4,
remark: '测试备注',
};
json.data = detail;
return res.json(json);
}
function getFakeTree(req, res) {
const json = { code: 200, success: true, msg: '操作成功' };
const list = [];
list.push({
title: '超级管理员',
value: '1',
key: '1',
children: [
{
title: '用户',
value: '2',
key: '2',
},
{
title: '测试',
value: '3',
key: '3',
},
],
});
json.data = list;
return res.json(json);
}
function fakeSuccess(req, res) {
const json = { code: 200, success: true, msg: '操作成功' };
return res.json(json);
}
const proxy = {
'GET /api/blade-system/role/list': getFakeList,
'GET /api/blade-system/role/detail': getFakeDetail,
'GET /api/blade-system/role/tree': getFakeTree,
'POST /api/blade-system/role/submit': fakeSuccess,
'POST /api/blade-system/role/remove': fakeSuccess,
'POST /api/blade-system/role/grant': fakeSuccess,
};
export default delay(proxy, 500);

206
mock/user.js Normal file
View File

@ -0,0 +1,206 @@
import { delay } from 'roadhog-api-doc';
function getFakeList(req, res) {
const json = { code: 200, success: true, msg: '操作成功' };
const list = [];
list.push(
{
id: '1',
account: 'admin',
name: '超级管理员',
realName: '管理员',
phone: '13888888888',
email: 'admin@springblade.org',
roleName: '超级管理员',
deptName: '刀锋科技',
statusName: '启用',
},
{
id: '2',
account: 'user',
name: '系统用户',
realName: '用户',
phone: '13666666666',
email: 'user@springblade.org',
roleName: '用户',
deptName: '刀锋科技',
statusName: '启用',
}
);
json.data = {
total: 10,
size: 10,
current: 1,
searchCount: true,
pages: 1,
records: list,
};
return res.json(json);
}
function getFakeDetail(req, res) {
const json = { code: 200, success: true, msg: '操作成功' };
const detail = {
id: '1',
account: 'admin',
name: '超级管理员',
realName: '管理员',
phone: '13888888888',
email: 'admin@springblade.org',
roleId: 1,
roleName: '超级管理员',
deptId: 1,
deptName: '刀锋科技',
sex: 1,
sexName: '男',
birthday: '2018-12-31 23:33:33',
statusName: '启用',
};
json.data = detail;
return res.json(json);
}
function fakeSuccess(req, res) {
const json = { code: 200, success: true, msg: '操作成功' };
return res.json(json);
}
// 代码中会兼容本地 service mock 以及部署站点的静态数据
const proxy = {
'GET /api/blade-user/list': getFakeList,
'GET /api/blade-user/detail': getFakeDetail,
'POST /api/blade-user/grant': fakeSuccess,
'POST /api/blade-user/reset-password': fakeSuccess,
'POST /api/blade-user/submit': fakeSuccess,
'POST /api/blade-user/remove': fakeSuccess,
// 支持值为 Object 和 Array
'GET /api/currentUser': {
name: 'Serati Ma',
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/BiazfanxmamNRoxxVxka.png',
userid: '00000001',
email: 'antdesign@alipay.com',
signature: '海纳百川,有容乃大',
title: '交互专家',
group: '蚂蚁金服某某某事业群某某平台部某某技术部UED',
tags: [
{
key: '0',
label: '很有想法的',
},
{
key: '1',
label: '专注设计',
},
{
key: '2',
label: '辣~',
},
{
key: '3',
label: '大长腿',
},
{
key: '4',
label: '川妹子',
},
{
key: '5',
label: '海纳百川',
},
],
notifyCount: 12,
unreadCount: 11,
country: 'China',
geographic: {
province: {
label: '浙江省',
key: '330000',
},
city: {
label: '杭州市',
key: '330100',
},
},
address: '西湖区工专路 77 号',
phone: '0752-268888888',
},
// GET POST 可省略
'GET /api/users': [
{
key: '1',
name: 'John Brown',
age: 32,
address: 'New York No. 1 Lake Park',
},
{
key: '2',
name: 'Jim Green',
age: 42,
address: 'London No. 1 Lake Park',
},
{
key: '3',
name: 'Joe Black',
age: 32,
address: 'Sidney No. 1 Lake Park',
},
],
'POST /api/blade-auth/token': (req, res) => {
res.send({
code: 200,
success: true,
data: {
accessToken:
'eyJ0eXAiOiJKc29uV2ViVG9rZW4iLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJpc3N1c2VyIiwiYXVkIjoiYXVkaWVuY2UiLCJyb2xlSWQiOiIxIiwicm9sZU5hbWUiOiJhZG1pbiIsInVzZXJOYW1lIjoi566h55CG5ZGYIiwidXNlcklkIjoiMSIsImFjY291bnQiOiJhZG1pbiIsImV4cCI6MTU0NDEyMjc5OSwibmJmIjoxNTQ0MDkxOTE3fQ.STze1uvHEoDI-FAAoFaOXufML_75MY6A_r6ZIzdYzYk',
tokenType: 'bearer',
authority: 'admin',
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/BiazfanxmamNRoxxVxka.png',
userName: '管理员',
account: 'admin',
license: 'made by blade',
},
msg: '操作成功',
});
},
'POST /api/register': (req, res) => {
res.send({ status: 'ok', currentAuthority: 'user' });
},
'GET /api/500': (req, res) => {
res.status(500).send({
timestamp: 1513932555104,
status: 500,
error: 'error',
message: 'error',
path: '/base/category/list',
});
},
'GET /api/404': (req, res) => {
res.status(404).send({
timestamp: 1513932643431,
status: 404,
error: 'Not Found',
message: 'No message available',
path: '/base/category/list/2121212',
});
},
'GET /api/403': (req, res) => {
res.status(403).send({
timestamp: 1513932555104,
status: 403,
error: 'Unauthorized',
message: 'Unauthorized',
path: '/base/category/list',
});
},
'GET /api/401': (req, res) => {
res.status(401).send({
timestamp: 1513932555104,
status: 401,
error: 'Unauthorized',
message: 'Unauthorized',
path: '/base/category/list',
});
},
};
export default delay(proxy, 500);

13
netlify.toml Normal file
View File

@ -0,0 +1,13 @@
[[redirects]]
from = "/api/*"
to = "https://us-central1-antd-pro.cloudfunctions.net/api/api/:splat"
status = 200
force = true
[redirects.headers]
X-From = "Netlify"
X-Api-Key = "some-api-key-string"
[[redirects]]
from = "/*"
to = "/index.html"
status = 200

128
package.json Normal file
View File

@ -0,0 +1,128 @@
{
"name": "sword",
"version": "1.0.0",
"description": "An out-of-box UI solution for enterprise applications",
"private": true,
"scripts": {
"presite": "cd functions && npm install",
"start": "cross-env APP_TYPE=site PORT=8888 umi dev",
"start:no-mock": "cross-env MOCK=none PORT=8888 umi dev",
"build": "umi build",
"site": "npm run presite && cross-env APP_TYPE=site npm run build && firebase deploy && npm run docker:push",
"analyze": "cross-env ANALYZE=1 umi build",
"lint:style": "stylelint 'src/**/*.less' --syntax less",
"lint:prettier": "check-prettier lint",
"lint": "eslint --ext .js src mock tests && npm run lint:style && npm run lint:prettier",
"lint:fix": "eslint --fix --ext .js src mock tests && stylelint --fix 'src/**/*.less' --syntax less",
"lint-staged": "lint-staged",
"lint-staged:js": "eslint --ext .js",
"tslint": "npm run tslint:fix",
"tslint:fix": "tslint --fix 'src/**/*.ts*'",
"test": "umi test",
"test:component": "umi test ./src/components",
"test:all": "node ./tests/run-tests.js",
"prettier": "node ./scripts/prettier.js",
"docker:dev": "docker-compose -f ./docker/docker-compose.dev.yml up",
"docker:build": "docker-compose -f ./docker/docker-compose.dev.yml build",
"docker-prod:dev": "docker-compose -f ./docker/docker-compose.yml up",
"docker-prod:build": "docker-compose -f ./docker/docker-compose.yml build",
"docker-hub:build": "docker build -f Dockerfile.hub -t ant-design-pro ./",
"docker:tag": "docker tag ant-design-pro antdesign/ant-design-pro",
"docker:push": "npm run docker-hub:build && npm run docker:tag && docker push antdesign/ant-design-pro"
},
"dependencies": {
"@antv/data-set": "^0.10.1",
"@babel/runtime": "^7.3.1",
"antd": "^3.13.0",
"bizcharts": "^3.4.3",
"bizcharts-plugin-slider": "^2.1.1-beta.1",
"classnames": "^2.2.6",
"dva": "^2.4.1",
"enquire-js": "^0.2.1",
"hash.js": "^1.1.7",
"lodash": "^4.17.11",
"lodash-decorators": "^6.0.1",
"memoize-one": "^5.0.0",
"moment": "^2.24.0",
"numeral": "^2.0.6",
"nzh": "^1.0.4",
"omit.js": "^1.0.0",
"path-to-regexp": "^3.0.0",
"prop-types": "^15.6.2",
"qs": "^6.6.0",
"rc-animate": "^2.6.0",
"react": "^16.7.0",
"react-container-query": "^0.11.0",
"react-copy-to-clipboard": "^5.0.1",
"react-document-title": "^2.0.3",
"react-dom": "^16.7.0",
"react-fittext": "^1.0.0",
"react-media": "^1.9.2",
"react-router-dom": "^4.3.1",
"roadhog-api-doc": "^1.1.2"
},
"devDependencies": {
"@types/react": "^16.8.1",
"@types/react-dom": "^16.0.11",
"antd-pro-merge-less": "^1.0.0",
"antd-theme-webpack-plugin": "^1.2.0",
"babel-eslint": "^10.0.1",
"chalk": "^2.4.2",
"check-prettier": "^1.0.1",
"cross-env": "^5.2.0",
"cross-port-killer": "^1.0.1",
"enzyme": "3.8.0",
"eslint": "^5.13.0",
"eslint-config-airbnb": "^17.1.0",
"eslint-config-prettier": "^4.0.0",
"eslint-plugin-babel": "^5.3.0",
"eslint-plugin-compat": "^2.6.3",
"eslint-plugin-import": "^2.16.0",
"eslint-plugin-jsx-a11y": "^6.2.0",
"eslint-plugin-markdown": "^1.0.0",
"eslint-plugin-react": "^7.12.4",
"gh-pages": "^2.0.1",
"jest-puppeteer": "^3.9.0",
"less": "^3.9.0",
"lint-staged": "^8.1.1",
"merge-umi-mock-data": "^1.0.4",
"mockjs": "^1.0.1-beta3",
"prettier": "1.16.3",
"slash2": "^2.0.0",
"stylelint": "^9.10.1",
"stylelint-config-prettier": "^4.0.0",
"stylelint-config-standard": "^18.2.0",
"tslint": "^5.12.1",
"tslint-config-prettier": "^1.17.0",
"tslint-react": "^3.6.0",
"umi": "^2.4.4",
"umi-plugin-ga": "^1.1.3",
"umi-plugin-react": "^1.4.2"
},
"optionalDependencies": {
"puppeteer": "^1.12.1"
},
"lint-staged": {
"**/*.{js,ts,tsx,json,jsx,less}": [
"node ./scripts/lint-prettier.js",
"git add"
],
"**/*.{js,jsx}": "npm run lint-staged:js",
"**/*.less": "stylelint --syntax less"
},
"engines": {
"node": ">=8.0.0"
},
"browserslist": [
"> 1%",
"last 2 versions",
"not ie <= 10"
],
"checkFiles": [
"src/**/*.js*",
"src/**/*.ts*",
"src/**/*.less",
"config/**/*.js*",
"scripts/**/*.js"
]
}

BIN
public/favicon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

3
scripts/generateMock.js Normal file
View File

@ -0,0 +1,3 @@
const generateMock = require('merge-umi-mock-data');
const path = require('path');
generateMock(path.join(__dirname, '../mock'), path.join(__dirname, '../functions/mock/index.js'));

View File

@ -0,0 +1,23 @@
const glob = require('glob');
const getPrettierFiles = () => {
let files = [];
const configFiles = glob.sync('config/**/*.js*', { ignore: ['**/node_modules/**', 'build/**'] });
const mockFiles = glob.sync('mock/**/*.js*', { ignore: ['**/node_modules/**', 'build/**'] });
const jsFiles = glob.sync('src/**/*.js*', { ignore: ['**/node_modules/**', 'build/**'] });
const scriptFiles = glob.sync('scripts/**/*.js');
const tsFiles = glob.sync('src/**/*.ts*', { ignore: ['**/node_modules/**', 'build/**'] });
const lessFiles = glob.sync('src/**/*.less*', { ignore: ['**/node_modules/**', 'build/**'] });
files = files.concat(configFiles);
files = files.concat(mockFiles);
files = files.concat(jsFiles);
files = files.concat(scriptFiles);
files = files.concat(tsFiles);
files = files.concat(lessFiles);
if (!files.length) {
return;
}
return files;
};
module.exports = getPrettierFiles;

50
scripts/lint-prettier.js Normal file
View File

@ -0,0 +1,50 @@
/**
* copy to https://github.com/facebook/react/blob/master/scripts/prettier/index.js
* prettier api doc https://prettier.io/docs/en/api.html
*----------*****--------------
* lint file is prettier
*----------*****--------------
*/
const prettier = require('prettier');
const fs = require('fs');
const chalk = require('chalk');
const prettierConfigPath = require.resolve('../.prettierrc');
const files = process.argv.slice(2);
let didError = false;
files.forEach(file => {
Promise.all([
prettier.resolveConfig(file, {
config: prettierConfigPath,
}),
prettier.getFileInfo(file),
])
.then(resolves => {
const [options, fileInfo] = resolves;
if (fileInfo.ignored) {
return;
}
const input = fs.readFileSync(file, 'utf8');
const withParserOptions = {
...options,
parser: fileInfo.inferredParser,
};
const output = prettier.format(input, withParserOptions);
if (output !== input) {
fs.writeFileSync(file, output, 'utf8');
console.log(chalk.green(`${file} is prettier`));
}
})
.catch(e => {
didError = true;
})
.finally(() => {
if (didError) {
process.exit(1);
}
console.log(chalk.hex('#1890FF')('prettier success!'));
});
});

46
scripts/prettier.js Normal file
View File

@ -0,0 +1,46 @@
/**
* copy to https://github.com/facebook/react/blob/master/scripts/prettier/index.js
* prettier api doc https://prettier.io/docs/en/api.html
*----------*****--------------
* prettier all js and all ts.
*----------*****--------------
*/
const prettier = require('prettier');
const fs = require('fs');
const getPrettierFiles = require('./getPrettierFiles');
const prettierConfigPath = require.resolve('../.prettierrc');
const chalk = require('chalk');
let didError = false;
const files = getPrettierFiles();
files.forEach(file => {
const options = prettier.resolveConfig.sync(file, {
config: prettierConfigPath,
});
const fileInfo = prettier.getFileInfo.sync(file);
if (fileInfo.ignored) {
return;
}
try {
const input = fs.readFileSync(file, 'utf8');
const withParserOptions = {
...options,
parser: fileInfo.inferredParser,
};
const output = prettier.format(input, withParserOptions);
if (output !== input) {
fs.writeFileSync(file, output, 'utf8');
console.log(chalk.green(`${file} is prettier`));
}
} catch (e) {
didError = true;
}
});
if (didError) {
process.exit(1);
}
console.log(chalk.hex('#1890FF')('prettier success!'));

36
src/actions/code.js Normal file
View File

@ -0,0 +1,36 @@
export const CODE_NAMESPACE = 'code';
export function CODE_LIST(payload) {
return {
type: `${CODE_NAMESPACE}/fetchList`,
payload,
};
}
export function CODE_DETAIL(id) {
return {
type: `${CODE_NAMESPACE}/fetchDetail`,
payload: { id },
};
}
export function CODE_CLEAR_DETAIL() {
return {
type: `${CODE_NAMESPACE}/clearDetail`,
payload: {},
};
}
export function CODE_SUBMIT(payload) {
return {
type: `${CODE_NAMESPACE}/submit`,
payload,
};
}
export function CODE_REMOVE(payload) {
return {
type: `${CODE_NAMESPACE}/remove`,
payload,
};
}

43
src/actions/dept.js Normal file
View File

@ -0,0 +1,43 @@
export const DEPT_NAMESPACE = 'dept';
export function DEPT_LIST(payload) {
return {
type: `${DEPT_NAMESPACE}/fetchList`,
payload,
};
}
export function DEPT_INIT() {
return {
type: `${DEPT_NAMESPACE}/fetchInit`,
payload: {},
};
}
export function DEPT_DETAIL(id) {
return {
type: `${DEPT_NAMESPACE}/fetchDetail`,
payload: { id },
};
}
export function DEPT_CLEAR_DETAIL() {
return {
type: `${DEPT_NAMESPACE}/clearDetail`,
payload: {},
};
}
export function DEPT_SUBMIT(payload) {
return {
type: `${DEPT_NAMESPACE}/submit`,
payload,
};
}
export function DEPT_REMOVE(payload) {
return {
type: `${DEPT_NAMESPACE}/remove`,
payload,
};
}

43
src/actions/dict.js Normal file
View File

@ -0,0 +1,43 @@
export const DICT_NAMESPACE = 'dict';
export function DICT_LIST(payload) {
return {
type: `${DICT_NAMESPACE}/fetchList`,
payload,
};
}
export function DICT_INIT() {
return {
type: `${DICT_NAMESPACE}/fetchInit`,
payload: { code: 'DICT' },
};
}
export function DICT_DETAIL(id) {
return {
type: `${DICT_NAMESPACE}/fetchDetail`,
payload: { id },
};
}
export function DICT_CLEAR_DETAIL() {
return {
type: `${DICT_NAMESPACE}/clearDetail`,
payload: {},
};
}
export function DICT_SUBMIT(payload) {
return {
type: `${DICT_NAMESPACE}/submit`,
payload,
};
}
export function DICT_REMOVE(payload) {
return {
type: `${DICT_NAMESPACE}/remove`,
payload,
};
}

43
src/actions/log.js Normal file
View File

@ -0,0 +1,43 @@
export const LOG_NAMESPACE = 'log';
export function LOG_USUAL_LIST(payload) {
return {
type: `${LOG_NAMESPACE}/fetchUsualList`,
payload,
};
}
export function LOG_USUAL_DETAIL(id) {
return {
type: `${LOG_NAMESPACE}/fetchUsualDetail`,
payload: { id },
};
}
export function LOG_API_LIST(payload) {
return {
type: `${LOG_NAMESPACE}/fetchApiList`,
payload,
};
}
export function LOG_API_DETAIL(id) {
return {
type: `${LOG_NAMESPACE}/fetchApiDetail`,
payload: { id },
};
}
export function LOG_ERROR_LIST(payload) {
return {
type: `${LOG_NAMESPACE}/fetchErrorList`,
payload,
};
}
export function LOG_ERROR_DETAIL(id) {
return {
type: `${LOG_NAMESPACE}/fetchErrorDetail`,
payload: { id },
};
}

61
src/actions/menu.js Normal file
View File

@ -0,0 +1,61 @@
import { getAuthority } from '../utils/authority';
export const MENU_NAMESPACE = 'menu';
export function MENU_REFRESH_DATA() {
return {
type: `${MENU_NAMESPACE}/fetchMenuData`,
payload: { authority: getAuthority() },
};
}
export function MENU_LIST(payload) {
return {
type: `${MENU_NAMESPACE}/fetchList`,
payload,
};
}
export function MENU_INIT() {
return {
type: `${MENU_NAMESPACE}/fetchInit`,
payload: {},
};
}
export function MENU_DETAIL(id) {
return {
type: `${MENU_NAMESPACE}/fetchDetail`,
payload: { id },
};
}
export function MENU_CLEAR_DETAIL() {
return {
type: `${MENU_NAMESPACE}/clearDetail`,
payload: {},
};
}
export function MENU_SUBMIT(payload) {
return {
type: `${MENU_NAMESPACE}/submit`,
payload,
};
}
export function MENU_REMOVE(payload) {
return {
type: `${MENU_NAMESPACE}/remove`,
payload,
};
}
export function MENU_SELECT_ICON(icon) {
return {
type: `${MENU_NAMESPACE}/selectIcon`,
payload: {
source: icon,
},
};
}

36
src/actions/notice.js Normal file
View File

@ -0,0 +1,36 @@
export const NOTICE_NAMESPACE = 'notice';
export function NOTICE_LIST(payload) {
return {
type: `${NOTICE_NAMESPACE}/fetchList`,
payload,
};
}
export function NOTICE_INIT() {
return {
type: `${NOTICE_NAMESPACE}/fetchInit`,
payload: { code: 'notice' },
};
}
export function NOTICE_DETAIL(id) {
return {
type: `${NOTICE_NAMESPACE}/fetchDetail`,
payload: { id },
};
}
export function NOTICE_SUBMIT(payload) {
return {
type: `${NOTICE_NAMESPACE}/submit`,
payload,
};
}
export function NOTICE_REMOVE(payload) {
return {
type: `${NOTICE_NAMESPACE}/remove`,
payload,
};
}

36
src/actions/param.js Normal file
View File

@ -0,0 +1,36 @@
export const PARAM_NAMESPACE = 'param';
export function PARAM_LIST(payload) {
return {
type: `${PARAM_NAMESPACE}/fetchList`,
payload,
};
}
export function PARAM_DETAIL(id) {
return {
type: `${PARAM_NAMESPACE}/fetchDetail`,
payload: { id },
};
}
export function PARAM_CLEAR_DETAIL() {
return {
type: `${PARAM_NAMESPACE}/clearDetail`,
payload: {},
};
}
export function PARAM_SUBMIT(payload) {
return {
type: `${PARAM_NAMESPACE}/submit`,
payload,
};
}
export function PARAM_REMOVE(payload) {
return {
type: `${PARAM_NAMESPACE}/remove`,
payload,
};
}

72
src/actions/role.js Normal file
View File

@ -0,0 +1,72 @@
export const ROLE_NAMESPACE = 'role';
export function ROLE_LIST(payload) {
return {
type: `${ROLE_NAMESPACE}/fetchList`,
payload,
};
}
export function ROLE_INIT() {
return {
type: `${ROLE_NAMESPACE}/fetchInit`,
payload: {},
};
}
export function ROLE_DETAIL(id) {
return {
type: `${ROLE_NAMESPACE}/fetchDetail`,
payload: { id },
};
}
export function ROLE_CLEAR_DETAIL() {
return {
type: `${ROLE_NAMESPACE}/clearDetail`,
payload: {},
};
}
export function ROLE_GRANT_TREE(payload) {
return {
type: `${ROLE_NAMESPACE}/grantTree`,
payload,
};
}
export function ROLE_TREE_KEYS(payload) {
return {
type: `${ROLE_NAMESPACE}/roleTreeKeys`,
payload,
};
}
export function ROLE_SET_TREE_KEYS(payload) {
return {
type: `${ROLE_NAMESPACE}/setRoleTreeKeys`,
payload,
};
}
export function ROLE_GRANT(payload, callback) {
return {
type: `${ROLE_NAMESPACE}/grant`,
payload,
callback,
};
}
export function ROLE_SUBMIT(payload) {
return {
type: `${ROLE_NAMESPACE}/submit`,
payload,
};
}
export function ROLE_REMOVE(payload) {
return {
type: `${ROLE_NAMESPACE}/remove`,
payload,
};
}

44
src/actions/user.js Normal file
View File

@ -0,0 +1,44 @@
export const USER_NAMESPACE = 'user';
export function USER_LIST(payload) {
return {
type: `${USER_NAMESPACE}/fetchList`,
payload,
};
}
export function USER_INIT() {
return {
type: `${USER_NAMESPACE}/fetchInit`,
payload: {},
};
}
export function USER_DETAIL(id) {
return {
type: `${USER_NAMESPACE}/fetchDetail`,
payload: { id },
};
}
export function USER_ROLE_GRANT(payload, callback) {
return {
type: `${USER_NAMESPACE}/grant`,
payload,
callback,
};
}
export function USER_SUBMIT(payload) {
return {
type: `${USER_NAMESPACE}/submit`,
payload,
};
}
export function USER_REMOVE(payload) {
return {
type: `${USER_NAMESPACE}/remove`,
payload,
};
}

41
src/app.js Normal file
View File

@ -0,0 +1,41 @@
import { routesAuthority } from './services/menu';
export const dva = {
config: {
onError(err) {
err.preventDefault();
},
},
};
let authRoutes = {
'/form/advanced-form': { authority: ['admin', 'user'] },
};
function ergodicRoutes(routes, authKey, authority) {
routes.forEach(element => {
if (element.path === authKey) {
if (!element.authority) element.authority = []; // eslint-disable-line
Object.assign(element.authority, authority || []);
} else if (element.routes) {
ergodicRoutes(element.routes, authKey, authority);
}
return element;
});
}
export function patchRoutes(routes) {
Object.keys(authRoutes).map(authKey =>
ergodicRoutes(routes, authKey, authRoutes[authKey].authority)
);
window.g_routes = routes;
}
export function render(oldRender) {
routesAuthority().then(response => {
if (response) {
authRoutes = response.data;
}
oldRender();
});
}

160
src/assets/logo.svg Normal file
View File

@ -0,0 +1,160 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="87px" height="86px" viewBox="0 0 87 86" enable-background="new 0 0 87 86" xml:space="preserve"> <image id="image0" width="87" height="86" x="0" y="0"
xlink:href="data:image/png;base64,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
MzErMDg6MDC8c1XdAAAAAElFTkSuQmCC" />
</svg>

View File

@ -0,0 +1,101 @@
import React, { Component } from 'react';
import { MiniArea } from '../Charts';
import NumberInfo from '../NumberInfo';
import styles from './index.less';
function fixedZero(val) {
return val * 1 < 10 ? `0${val}` : val;
}
function getActiveData() {
const activeData = [];
for (let i = 0; i < 24; i += 1) {
activeData.push({
x: `${fixedZero(i)}:00`,
y: Math.floor(Math.random() * 200) + i * 50,
});
}
return activeData;
}
export default class ActiveChart extends Component {
state = {
activeData: getActiveData(),
};
componentDidMount() {
this.loopData();
}
componentWillUnmount() {
clearTimeout(this.timer);
cancelAnimationFrame(this.requestRef);
}
loopData = () => {
this.requestRef = requestAnimationFrame(() => {
this.timer = setTimeout(() => {
this.setState(
{
activeData: getActiveData(),
},
() => {
this.loopData();
}
);
}, 1000);
});
};
render() {
const { activeData = [] } = this.state;
return (
<div className={styles.activeChart}>
<NumberInfo subTitle="目标评估" total="有望达到预期" />
<div style={{ marginTop: 32 }}>
<MiniArea
animate={false}
line
borderWidth={2}
height={84}
scale={{
y: {
tickCount: 3,
},
}}
yAxis={{
tickLine: false,
label: false,
title: false,
line: false,
}}
data={activeData}
/>
</div>
{activeData && (
<div>
<div className={styles.activeChartGrid}>
<p>{[...activeData].sort()[activeData.length - 1].y + 200} 亿元</p>
<p>{[...activeData].sort()[Math.floor(activeData.length / 2)].y} 亿元</p>
</div>
<div className={styles.dashedLine}>
<div className={styles.line} />
</div>
<div className={styles.dashedLine}>
<div className={styles.line} />
</div>
</div>
)}
{activeData && (
<div className={styles.activeChartLegend}>
<span>00:00</span>
<span>{activeData[Math.floor(activeData.length / 2)].x}</span>
<span>{activeData[activeData.length - 1].x}</span>
</div>
)}
</div>
);
}
}

View File

@ -0,0 +1,51 @@
.activeChart {
position: relative;
}
.activeChartGrid {
p {
position: absolute;
top: 80px;
}
p:last-child {
top: 115px;
}
}
.activeChartLegend {
position: relative;
height: 20px;
margin-top: 8px;
font-size: 0;
line-height: 20px;
span {
display: inline-block;
width: 33.33%;
font-size: 12px;
text-align: center;
}
span:first-child {
text-align: left;
}
span:last-child {
text-align: right;
}
}
.dashedLine {
position: relative;
top: -70px;
left: -3px;
height: 1px;
.line {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-image: linear-gradient(to right, transparent 50%, #e9e9e9 50%);
background-size: 6px;
}
}
.dashedLine:last-child {
top: -36px;
}

View File

@ -0,0 +1,54 @@
import React from 'react';
export default ({
expendAll = false,
expandedRowRender,
updateExpandRowKeys,
initExpandedRowKeys,
}) => {
return WrappedComponent => {
return class extends React.PureComponent {
constructor(props) {
super(props);
this.state = {
expandedRowKeys: initExpandedRowKeys || [],
expandRowByClick: false,
};
}
componentDidMount() {
if (initExpandedRowKeys) {
this.onExpandedRowsChange(initExpandedRowKeys);
}
}
onExpandedRowsChange = rows => {
if (updateExpandRowKeys) updateExpandRowKeys(rows);
this.setState({
expandedRowKeys: rows,
});
};
expandRow = row => {
this.setState({
expandedRowKeys: row ? [row] : [],
});
};
render() {
const { expandedRowKeys } = this.state;
// const expandRowByClick = true;
return (
<WrappedComponent
expandedRowRender={expandedRowRender}
defaultExpandAllRows={expendAll}
expandedRowKeys={expandedRowKeys}
onExpandedRowsChange={this.onExpandedRowsChange}
expandRow={this.expandRow}
/>
);
}
};
};
};

View File

@ -0,0 +1,17 @@
import React from 'react';
import moment from 'moment';
import { Avatar } from 'antd';
import styles from './index.less';
const ArticleListContent = ({ data: { content, updatedAt, avatar, owner, href } }) => (
<div className={styles.listContent}>
<div className={styles.description}>{content}</div>
<div className={styles.extra}>
<Avatar src={avatar} size="small" />
<a href={href}>{owner}</a> <a href={href}>{href}</a>
<em>{moment(updatedAt).format('YYYY-MM-DD HH:mm')}</em>
</div>
</div>
);
export default ArticleListContent;

View File

@ -0,0 +1,38 @@
@import '~antd/lib/style/themes/default.less';
.listContent {
.description {
max-width: 720px;
line-height: 22px;
}
.extra {
margin-top: 16px;
color: @text-color-secondary;
line-height: 22px;
& > :global(.ant-avatar) {
position: relative;
top: 1px;
width: 20px;
height: 20px;
margin-right: 8px;
vertical-align: top;
}
& > em {
margin-left: 16px;
color: @disabled-color;
font-style: normal;
}
}
}
@media screen and (max-width: @screen-xs) {
.listContent {
.extra {
& > em {
display: block;
margin-top: 8px;
margin-left: 0;
}
}
}
}

View File

@ -0,0 +1,8 @@
import CheckPermissions from './CheckPermissions';
const Authorized = ({ children, authority, noMatch = null }) => {
const childrenRender = typeof children === 'undefined' ? null : children;
return CheckPermissions(authority, childrenRender, noMatch);
};
export default Authorized;

View File

@ -0,0 +1,13 @@
import * as React from 'react';
import { RouteProps } from 'react-router';
type authorityFN = (currentAuthority?: string) => boolean;
type authority = string | string[] | authorityFN | Promise<any>;
export interface IAuthorizedRouteProps extends RouteProps {
authority: authority;
}
export { authority };
export class AuthorizedRoute extends React.Component<IAuthorizedRouteProps, any> {}

View File

@ -0,0 +1,15 @@
import React from 'react';
import { Route, Redirect } from 'react-router-dom';
import Authorized from './Authorized';
// TODO: umi只会返回render和rest
const AuthorizedRoute = ({ component: Component, render, authority, redirectPath, ...rest }) => (
<Authorized
authority={authority}
noMatch={<Route {...rest} render={() => <Redirect to={{ pathname: redirectPath }} />} />}
>
<Route {...rest} render={props => (Component ? <Component {...props} /> : render(props))} />
</Authorized>
);
export default AuthorizedRoute;

View File

@ -0,0 +1,88 @@
import React from 'react';
import PromiseRender from './PromiseRender';
import { CURRENT } from './renderAuthorize';
function isPromise(obj) {
return (
!!obj &&
(typeof obj === 'object' || typeof obj === 'function') &&
typeof obj.then === 'function'
);
}
/**
* 通用权限检查方法
* Common check permissions method
* @param { 权限判定 Permission judgment type string |array | Promise | Function } authority
* @param { 你的权限 Your permission description type:string} currentAuthority
* @param { 通过的组件 Passing components } target
* @param { 未通过的组件 no pass components } Exception
*/
const checkPermissions = (authority, currentAuthority, target, Exception) => {
// 没有判定权限.默认查看所有
// Retirement authority, return target;
if (!authority) {
return target;
}
// 数组处理
if (Array.isArray(authority)) {
if (authority.indexOf(currentAuthority) >= 0) {
return target;
}
if (Array.isArray(currentAuthority)) {
for (let i = 0; i < currentAuthority.length; i += 1) {
const element = currentAuthority[i];
if (authority.indexOf(element) >= 0) {
return target;
}
}
}
return Exception;
}
// string 处理
if (typeof authority === 'string') {
if (authority === currentAuthority) {
return target;
}
if (Array.isArray(currentAuthority)) {
for (let i = 0; i < currentAuthority.length; i += 1) {
const element = currentAuthority[i];
if (authority === element) {
return target;
}
}
}
return Exception;
}
// Promise 处理
if (isPromise(authority)) {
return <PromiseRender ok={target} error={Exception} promise={authority} />;
}
// Function 处理
if (typeof authority === 'function') {
try {
const bool = authority(currentAuthority);
// 函数执行后返回值是 Promise
if (isPromise(bool)) {
return <PromiseRender ok={target} error={Exception} promise={bool} />;
}
if (bool) {
return target;
}
return Exception;
} catch (error) {
throw error;
}
}
throw new Error('unsupported parameters');
};
export { checkPermissions };
const check = (authority, target, Exception) =>
checkPermissions(authority, CURRENT, target, Exception);
export default check;

View File

@ -0,0 +1,55 @@
import { checkPermissions } from './CheckPermissions';
const target = 'ok';
const error = 'error';
describe('test CheckPermissions', () => {
it('Correct string permission authentication', () => {
expect(checkPermissions('user', 'user', target, error)).toEqual('ok');
});
it('Correct string permission authentication', () => {
expect(checkPermissions('user', 'NULL', target, error)).toEqual('error');
});
it('authority is undefined , return ok', () => {
expect(checkPermissions(null, 'NULL', target, error)).toEqual('ok');
});
it('currentAuthority is undefined , return error', () => {
expect(checkPermissions('admin', null, target, error)).toEqual('error');
});
it('Wrong string permission authentication', () => {
expect(checkPermissions('admin', 'user', target, error)).toEqual('error');
});
it('Correct Array permission authentication', () => {
expect(checkPermissions(['user', 'admin'], 'user', target, error)).toEqual('ok');
});
it('Wrong Array permission authentication,currentAuthority error', () => {
expect(checkPermissions(['user', 'admin'], 'user,admin', target, error)).toEqual('error');
});
it('Wrong Array permission authentication', () => {
expect(checkPermissions(['user', 'admin'], 'guest', target, error)).toEqual('error');
});
it('Wrong Function permission authentication', () => {
expect(checkPermissions(() => false, 'guest', target, error)).toEqual('error');
});
it('Correct Function permission authentication', () => {
expect(checkPermissions(() => true, 'guest', target, error)).toEqual('ok');
});
it('authority is string, currentAuthority is array, return ok', () => {
expect(checkPermissions('user', ['user'], target, error)).toEqual('ok');
});
it('authority is string, currentAuthority is array, return ok', () => {
expect(checkPermissions('user', ['user', 'admin'], target, error)).toEqual('ok');
});
it('authority is array, currentAuthority is array, return ok', () => {
expect(checkPermissions(['user', 'admin'], ['user', 'admin'], target, error)).toEqual('ok');
});
it('Wrong Function permission authentication', () => {
expect(checkPermissions(() => false, ['user'], target, error)).toEqual('error');
});
it('Correct Function permission authentication', () => {
expect(checkPermissions(() => true, ['user'], target, error)).toEqual('ok');
});
it('authority is undefined , return ok', () => {
expect(checkPermissions(null, ['user'], target, error)).toEqual('ok');
});
});

View File

@ -0,0 +1,65 @@
import React from 'react';
import { Spin } from 'antd';
export default class PromiseRender extends React.PureComponent {
state = {
component: null,
};
componentDidMount() {
this.setRenderComponent(this.props);
}
componentDidUpdate(nextProps) {
// new Props enter
this.setRenderComponent(nextProps);
}
// set render Component : ok or error
setRenderComponent(props) {
const ok = this.checkIsInstantiation(props.ok);
const error = this.checkIsInstantiation(props.error);
props.promise
.then(() => {
this.setState({
component: ok,
});
})
.catch(() => {
this.setState({
component: error,
});
});
}
// Determine whether the incoming component has been instantiated
// AuthorizedRoute is already instantiated
// Authorized render is already instantiated, children is no instantiated
// Secured is not instantiated
checkIsInstantiation = target => {
if (!React.isValidElement(target)) {
return target;
}
return () => target;
};
render() {
const { component: Component } = this.state;
const { ok, error, promise, ...rest } = this.props;
return Component ? (
<Component {...rest} />
) : (
<div
style={{
width: '100%',
height: '100%',
margin: 'auto',
paddingTop: 50,
textAlign: 'center',
}}
>
<Spin size="large" />
</div>
);
}
}

View File

@ -0,0 +1,55 @@
import React from 'react';
import Exception from '../Exception';
import CheckPermissions from './CheckPermissions';
/**
* 默认不能访问任何页面
* default is "NULL"
*/
const Exception403 = () => <Exception type="403" />;
// Determine whether the incoming component has been instantiated
// AuthorizedRoute is already instantiated
// Authorized render is already instantiated, children is no instantiated
// Secured is not instantiated
const checkIsInstantiation = target => {
if (!React.isValidElement(target)) {
return target;
}
return () => target;
};
/**
* 用于判断是否拥有权限访问此view权限
* authority 支持传入 string, function:()=>boolean|Promise
* e.g. 'user' 只有user用户能访问
* e.g. 'user,admin' user和 admin 都能访问
* e.g. ()=>boolean 返回true能访问,返回false不能访问
* e.g. Promise then 能访问 catch不能访问
* e.g. authority support incoming string, function: () => boolean | Promise
* e.g. 'user' only user user can access
* e.g. 'user, admin' user and admin can access
* e.g. () => boolean true to be able to visit, return false can not be accessed
* e.g. Promise then can not access the visit to catch
* @param {string | function | Promise} authority
* @param {ReactNode} error 非必需参数
*/
const authorize = (authority, error) => {
/**
* conversion into a class
* 防止传入字符串时找不到staticContext造成报错
* String parameters can cause staticContext not found error
*/
let classError = false;
if (error) {
classError = () => error;
}
if (!authority) {
throw new Error('authority is required');
}
return function decideAuthority(target) {
const component = CheckPermissions(authority, target, classError || Exception403);
return checkIsInstantiation(component);
};
};
export default authorize;

View File

@ -0,0 +1,23 @@
---
order: 1
title:
zh-CN: 使用数组作为参数
en-US: Use Array as a parameter
---
Use Array as a parameter
```jsx
import RenderAuthorized from 'ant-design-pro/lib/Authorized';
import { Alert } from 'antd';
const Authorized = RenderAuthorized('user');
const noMatch = <Alert message="No permission." type="error" showIcon />;
ReactDOM.render(
<Authorized authority={['user', 'admin']} noMatch={noMatch}>
<Alert message="Use Array as a parameter passed!" type="success" showIcon />
</Authorized>,
mountNode,
);
```

View File

@ -0,0 +1,31 @@
---
order: 2
title:
zh-CN: 使用方法作为参数
en-US: Use function as a parameter
---
Use Function as a parameter
```jsx
import RenderAuthorized from 'ant-design-pro/lib/Authorized';
import { Alert } from 'antd';
const Authorized = RenderAuthorized('user');
const noMatch = <Alert message="No permission." type="error" showIcon />;
const havePermission = () => {
return false;
};
ReactDOM.render(
<Authorized authority={havePermission} noMatch={noMatch}>
<Alert
message="Use Function as a parameter passed!"
type="success"
showIcon
/>
</Authorized>,
mountNode,
);
```

View File

@ -0,0 +1,25 @@
---
order: 0
title:
zh-CN: 基本使用
en-US: Basic use
---
Basic use
```jsx
import RenderAuthorized from 'ant-design-pro/lib/Authorized';
import { Alert } from 'antd';
const Authorized = RenderAuthorized('user');
const noMatch = <Alert message="No permission." type="error" showIcon />;
ReactDOM.render(
<div>
<Authorized authority="admin" noMatch={noMatch}>
<Alert message="user Passed!" type="success" showIcon />
</Authorized>
</div>,
mountNode,
);
```

View File

@ -0,0 +1,28 @@
---
order: 3
title:
zh-CN: 注解基本使用
en-US: Basic use secured
---
secured demo used
```jsx
import RenderAuthorized from 'ant-design-pro/lib/Authorized';
import { Alert } from 'antd';
const { Secured } = RenderAuthorized('user');
@Secured('admin')
class TestSecuredString extends React.Component {
render() {
<Alert message="user Passed!" type="success" showIcon />;
}
}
ReactDOM.render(
<div>
<TestSecuredString />
</div>,
mountNode,
);
```

32
src/components/Authorized/index.d.ts vendored Normal file
View File

@ -0,0 +1,32 @@
import * as React from 'react';
import AuthorizedRoute, { authority } from './AuthorizedRoute';
export type IReactComponent<P = any> =
| React.StatelessComponent<P>
| React.ComponentClass<P>
| React.ClassicComponentClass<P>;
type Secured = (
authority: authority,
error?: React.ReactNode
) => <T extends IReactComponent>(target: T) => T;
type check = <T extends IReactComponent, S extends IReactComponent>(
authority: authority,
target: T,
Exception: S
) => T | S;
export interface IAuthorizedProps {
authority: authority;
noMatch?: React.ReactNode;
}
export class Authorized extends React.Component<IAuthorizedProps, any> {
public static Secured: Secured;
public static AuthorizedRoute: typeof AuthorizedRoute;
public static check: check;
}
declare function renderAuthorize(currentAuthority: string): typeof Authorized;
export default renderAuthorize;

View File

@ -0,0 +1,11 @@
import Authorized from './Authorized';
import AuthorizedRoute from './AuthorizedRoute';
import Secured from './Secured';
import check from './CheckPermissions';
import renderAuthorize from './renderAuthorize';
Authorized.Secured = Secured;
Authorized.AuthorizedRoute = AuthorizedRoute;
Authorized.check = check;
export default renderAuthorize(Authorized);

View File

@ -0,0 +1,58 @@
---
title:
en-US: Authorized
zh-CN: Authorized
subtitle: 权限
cols: 1
order: 15
---
权限组件,通过比对现有权限与准入权限,决定相关元素的展示。
## API
### RenderAuthorized
`RenderAuthorized: (currentAuthority: string | () => string) => Authorized`
权限组件默认 export RenderAuthorized 函数,它接收当前权限作为参数,返回一个权限对象,该对象提供以下几种使用方式。
### Authorized
最基础的权限控制。
| 参数 | 说明 | 类型 | 默认值 |
|----------|------------------------------------------|-------------|-------|
| children | 正常渲染的元素,权限判断通过时展示 | ReactNode | - |
| authority | 准入权限/权限判断 | `string | array | Promise | (currentAuthority) => boolean | Promise` | - |
| noMatch | 权限异常渲染元素,权限判断不通过时展示 | ReactNode | - |
### Authorized.AuthorizedRoute
| 参数 | 说明 | 类型 | 默认值 |
|----------|------------------------------------------|-------------|-------|
| authority | 准入权限/权限判断 | `string | array | Promise | (currentAuthority) => boolean | Promise` | - |
| redirectPath | 权限异常时重定向的页面路由 | string | - |
其余参数与 `Route` 相同。
### Authorized.Secured
注解方式,`@Authorized.Secured(authority, error)`
| 参数 | 说明 | 类型 | 默认值 |
|----------|------------------------------------------|-------------|-------|
| authority | 准入权限/权限判断 | `string | Promise | (currentAuthority) => boolean | Promise` | - |
| error | 权限异常时渲染元素 | ReactNode | <Exception type="403" /> |
### Authorized.check
函数形式的 Authorized用于某些不能被 HOC 包裹的组件。 `Authorized.check(authority, target, Exception)`
注意:传入一个 Promise 时,无论正确还是错误返回的都是一个 ReactClass。
| 参数 | 说明 | 类型 | 默认值 |
|----------|------------------------------------------|-------------|-------|
| authority | 准入权限/权限判断 | `string | Promise | (currentAuthority) => boolean | Promise` | - |
| target | 权限判断通过时渲染的元素 | ReactNode | - |
| Exception | 权限异常时渲染元素 | ReactNode | - |

View File

@ -0,0 +1,25 @@
/* eslint-disable import/no-mutable-exports */
let CURRENT = 'NULL';
/**
* use authority or getAuthority
* @param {string|()=>String} currentAuthority
*/
const renderAuthorize = Authorized => currentAuthority => {
if (currentAuthority) {
if (typeof currentAuthority === 'function') {
CURRENT = currentAuthority();
}
if (
Object.prototype.toString.call(currentAuthority) === '[object String]' ||
Array.isArray(currentAuthority)
) {
CURRENT = currentAuthority;
}
} else {
CURRENT = 'NULL';
}
return Authorized;
};
export { CURRENT };
export default Authorized => renderAuthorize(Authorized);

View File

@ -0,0 +1,10 @@
import * as React from 'react';
export interface IAvatarItemProps {
tips: React.ReactNode;
src: string;
style?: React.CSSProperties;
}
export default class AvatarItem extends React.Component<IAvatarItemProps, any> {
constructor(props: IAvatarItemProps);
}

View File

@ -0,0 +1,24 @@
---
order: 0
title:
zh-CN: 要显示的最大项目
en-US: Max Items to Show
---
`maxLength` attribute specifies the maximum number of items to show while `excessItemsStyle` style the excess
item component.
````jsx
import AvatarList from 'ant-design-pro/lib/AvatarList';
ReactDOM.render(
<AvatarList size="mini" maxLength={3} excessItemsStyle={{ color: '#f56a00', backgroundColor: '#fde3cf' }}>
<AvatarList.Item tips="Jake" src="https://gw.alipayobjects.com/zos/rmsportal/zOsKZmFRdUtvpqCImOVY.png" />
<AvatarList.Item tips="Andy" src="https://gw.alipayobjects.com/zos/rmsportal/sfjbOqnsXXJgNCjCzDBL.png" />
<AvatarList.Item tips="Niko" src="https://gw.alipayobjects.com/zos/rmsportal/kZzEzemZyKLKFsojXItE.png" />
<AvatarList.Item tips="Niko" src="https://gw.alipayobjects.com/zos/rmsportal/kZzEzemZyKLKFsojXItE.png" />
<AvatarList.Item tips="Niko" src="https://gw.alipayobjects.com/zos/rmsportal/kZzEzemZyKLKFsojXItE.png" />
<AvatarList.Item tips="Niko" src="https://gw.alipayobjects.com/zos/rmsportal/kZzEzemZyKLKFsojXItE.png" />
</AvatarList>
, mountNode);
````

View File

@ -0,0 +1,20 @@
---
order: 0
title:
zh-CN: 基础样例
en-US: Basic Usage
---
Simplest of usage.
````jsx
import AvatarList from 'ant-design-pro/lib/AvatarList';
ReactDOM.render(
<AvatarList size="mini">
<AvatarList.Item tips="Jake" src="https://gw.alipayobjects.com/zos/rmsportal/zOsKZmFRdUtvpqCImOVY.png" />
<AvatarList.Item tips="Andy" src="https://gw.alipayobjects.com/zos/rmsportal/sfjbOqnsXXJgNCjCzDBL.png" />
<AvatarList.Item tips="Niko" src="https://gw.alipayobjects.com/zos/rmsportal/kZzEzemZyKLKFsojXItE.png" />
</AvatarList>
, mountNode);
````

14
src/components/AvatarList/index.d.ts vendored Normal file
View File

@ -0,0 +1,14 @@
import * as React from 'react';
import AvatarItem from './AvatarItem';
export interface IAvatarListProps {
size?: 'large' | 'small' | 'mini' | 'default';
maxLength?: number;
excessItemsStyle?: React.CSSProperties;
style?: React.CSSProperties;
children: React.ReactElement<AvatarItem> | Array<React.ReactElement<AvatarItem>>;
}
export default class AvatarList extends React.Component<IAvatarListProps, any> {
public static Item: typeof AvatarItem;
}

View File

@ -0,0 +1,24 @@
---
title: AvatarList
order: 1
cols: 1
---
A list of user's avatar for project or group member list frequently. If a large or small AvatarList is desired, set the `size` property to either `large` or `small` and `mini` respectively. Omit the `size` property for a AvatarList with the default size.
## API
### AvatarList
| Property | Description | Type | Default |
| ---------------- | --------------------- | ---------------------------------- | --------- |
| size | size of list | `large`、`small` 、`mini`, `default` | `default` |
| maxLength | max items to show | number | - |
| excessItemsStyle | the excess item style | CSSProperties | - |
### AvatarList.Item
| Property | Description | Type | Default |
| -------- | -------------------------------------------- | --------- | ------- |
| tips | title tips for avatar item | ReactNode | - |
| src | the address of the image for an image avatar | string | - |

View File

@ -0,0 +1,61 @@
import React from 'react';
import { Tooltip, Avatar } from 'antd';
import classNames from 'classnames';
import styles from './index.less';
const avatarSizeToClassName = size =>
classNames(styles.avatarItem, {
[styles.avatarItemLarge]: size === 'large',
[styles.avatarItemSmall]: size === 'small',
[styles.avatarItemMini]: size === 'mini',
});
const AvatarList = ({ children, size, maxLength, excessItemsStyle, ...other }) => {
const numOfChildren = React.Children.count(children);
const numToShow = maxLength >= numOfChildren ? numOfChildren : maxLength;
const childrenWithProps = React.Children.toArray(children)
.slice(0, numToShow)
.map(child =>
React.cloneElement(child, {
size,
})
);
if (numToShow < numOfChildren) {
const cls = avatarSizeToClassName(size);
childrenWithProps.push(
<li key="exceed" className={cls}>
<Avatar size={size} style={excessItemsStyle}>{`+${numOfChildren - maxLength}`}</Avatar>
</li>
);
}
return (
<div {...other} className={styles.avatarList}>
<ul> {childrenWithProps} </ul>
</div>
);
};
const Item = ({ src, size, tips, onClick = () => {} }) => {
const cls = avatarSizeToClassName(size);
return (
<li className={cls} onClick={onClick}>
{tips ? (
<Tooltip title={tips}>
<Avatar src={src} size={size} style={{ cursor: 'pointer' }} />
</Tooltip>
) : (
<Avatar src={src} size={size} />
)}
</li>
);
};
AvatarList.Item = Item;
export default AvatarList;

View File

@ -0,0 +1,50 @@
@import '~antd/lib/style/themes/default.less';
.avatarList {
display: inline-block;
ul {
display: inline-block;
margin-left: 8px;
font-size: 0;
}
}
.avatarItem {
display: inline-block;
width: @avatar-size-base;
height: @avatar-size-base;
margin-left: -8px;
font-size: @font-size-base;
:global {
.ant-avatar {
border: 1px solid #fff;
}
}
}
.avatarItemLarge {
width: @avatar-size-lg;
height: @avatar-size-lg;
}
.avatarItemSmall {
width: @avatar-size-sm;
height: @avatar-size-sm;
}
.avatarItemMini {
width: 20px;
height: 20px;
:global {
.ant-avatar {
width: 20px;
height: 20px;
line-height: 20px;
.ant-avatar-string {
font-size: 12px;
line-height: 18px;
}
}
}
}

View File

@ -0,0 +1,29 @@
import React from 'react';
import range from 'lodash/range';
import { mount } from 'enzyme';
import AvatarList from './index';
const renderItems = numItems =>
range(numItems).map(i => (
<AvatarList.Item
key={i}
tips="Jake"
src="https://gw.alipayobjects.com/zos/rmsportal/zOsKZmFRdUtvpqCImOVY.png"
/>
));
describe('AvatarList', () => {
it('renders all items', () => {
const wrapper = mount(<AvatarList>{renderItems(4)}</AvatarList>);
expect(wrapper.find('AvatarList').length).toBe(1);
expect(wrapper.find('Item').length).toBe(4);
expect(wrapper.findWhere(node => node.key() === 'exceed').length).toBe(0);
});
it('renders max of 3 items', () => {
const wrapper = mount(<AvatarList maxLength={3}>{renderItems(4)}</AvatarList>);
expect(wrapper.find('AvatarList').length).toBe(1);
expect(wrapper.find('Item').length).toBe(3);
expect(wrapper.findWhere(node => node.key() === 'exceed').length).toBe(1);
});
});

View File

@ -0,0 +1,25 @@
---
title: AvatarList
subtitle: 用户头像列表
order: 1
cols: 1
---
一组用户头像,常用在项目/团队成员列表。可通过设置 `size` 属性来指定头像大小。
## API
### AvatarList
| 参数 | 说明 | 类型 | 默认值 |
| ---------------- | -------- | ---------------------------------- | --------- |
| size | 头像大小 | `large`、`small` 、`mini`, `default` | `default` |
| maxLength | 要显示的最大项目 | number | - |
| excessItemsStyle | 多余的项目风格 | CSSProperties | - |
### AvatarList.Item
| 参数 | 说明 | 类型 | 默认值 |
| ---- | ------ | --------- | --- |
| tips | 头像展示文案 | ReactNode | - |
| src | 头像图片连接 | string | - |

15
src/components/Charts/Bar/index.d.ts vendored Normal file
View File

@ -0,0 +1,15 @@
import * as React from 'react';
export interface IBarProps {
title: React.ReactNode;
color?: string;
padding?: [number, number, number, number];
height: number;
data: Array<{
x: string;
y: number;
}>;
autoLabel?: boolean;
style?: React.CSSProperties;
}
export default class Bar extends React.Component<IBarProps, any> {}

View File

@ -0,0 +1,113 @@
import React, { Component } from 'react';
import { Chart, Axis, Tooltip, Geom } from 'bizcharts';
import Debounce from 'lodash-decorators/debounce';
import Bind from 'lodash-decorators/bind';
import autoHeight from '../autoHeight';
import styles from '../index.less';
@autoHeight()
class Bar extends Component {
state = {
autoHideXLabels: false,
};
componentDidMount() {
window.addEventListener('resize', this.resize, { passive: true });
}
componentWillUnmount() {
window.removeEventListener('resize', this.resize);
}
handleRoot = n => {
this.root = n;
};
handleRef = n => {
this.node = n;
};
@Bind()
@Debounce(400)
resize() {
if (!this.node) {
return;
}
const canvasWidth = this.node.parentNode.clientWidth;
const { data = [], autoLabel = true } = this.props;
if (!autoLabel) {
return;
}
const minWidth = data.length * 30;
const { autoHideXLabels } = this.state;
if (canvasWidth <= minWidth) {
if (!autoHideXLabels) {
this.setState({
autoHideXLabels: true,
});
}
} else if (autoHideXLabels) {
this.setState({
autoHideXLabels: false,
});
}
}
render() {
const {
height,
title,
forceFit = true,
data,
color = 'rgba(24, 144, 255, 0.85)',
padding,
} = this.props;
const { autoHideXLabels } = this.state;
const scale = {
x: {
type: 'cat',
},
y: {
min: 0,
},
};
const tooltip = [
'x*y',
(x, y) => ({
name: x,
value: y,
}),
];
return (
<div className={styles.chart} style={{ height }} ref={this.handleRoot}>
<div ref={this.handleRef}>
{title && <h4 style={{ marginBottom: 20 }}>{title}</h4>}
<Chart
scale={scale}
height={title ? height - 41 : height}
forceFit={forceFit}
data={data}
padding={padding || 'auto'}
>
<Axis
name="x"
title={false}
label={autoHideXLabels ? false : {}}
tickLine={autoHideXLabels ? false : {}}
/>
<Axis name="y" min={0} />
<Tooltip showTitle={false} crosshairs={false} />
<Geom type="interval" position="x*y" color={color} tooltip={tooltip} />
</Chart>
</div>
</div>
);
}
}
export default Bar;

View File

@ -0,0 +1,14 @@
import { CardProps } from 'antd/lib/card';
import * as React from 'react';
export interface IChartCardProps extends CardProps {
title: React.ReactNode;
action?: React.ReactNode;
total?: React.ReactNode | number | (() => React.ReactNode | number);
footer?: React.ReactNode;
contentHeight?: number;
avatar?: React.ReactNode;
style?: React.CSSProperties;
}
export default class ChartCard extends React.Component<IChartCardProps, any> {}

View File

@ -0,0 +1,82 @@
import React from 'react';
import { Card } from 'antd';
import classNames from 'classnames';
import styles from './index.less';
const renderTotal = total => {
let totalDom;
switch (typeof total) {
case 'undefined':
totalDom = null;
break;
case 'function':
totalDom = <div className={styles.total}>{total()}</div>;
break;
default:
totalDom = <div className={styles.total}>{total}</div>;
}
return totalDom;
};
class ChartCard extends React.PureComponent {
renderConnet = () => {
const { contentHeight, title, avatar, action, total, footer, children, loading } = this.props;
if (loading) {
return false;
}
return (
<div className={styles.chartCard}>
<div
className={classNames(styles.chartTop, {
[styles.chartTopMargin]: !children && !footer,
})}
>
<div className={styles.avatar}>{avatar}</div>
<div className={styles.metaWrap}>
<div className={styles.meta}>
<span className={styles.title}>{title}</span>
<span className={styles.action}>{action}</span>
</div>
{renderTotal(total)}
</div>
</div>
{children && (
<div className={styles.content} style={{ height: contentHeight || 'auto' }}>
<div className={contentHeight && styles.contentFixed}>{children}</div>
</div>
)}
{footer && (
<div
className={classNames(styles.footer, {
[styles.footerMargin]: !children,
})}
>
{footer}
</div>
)}
</div>
);
};
render() {
const {
loading = false,
contentHeight,
title,
avatar,
action,
total,
footer,
children,
...rest
} = this.props;
return (
<Card loading={loading} bodyStyle={{ padding: '20px 24px 8px 24px' }} {...rest}>
{this.renderConnet()}
</Card>
);
}
}
export default ChartCard;

View File

@ -0,0 +1,75 @@
@import '~antd/lib/style/themes/default.less';
.chartCard {
position: relative;
.chartTop {
position: relative;
overflow: hidden;
width: 100%;
}
.chartTopMargin {
margin-bottom: 12px;
}
.chartTopHasMargin {
margin-bottom: 20px;
}
.metaWrap {
float: left;
}
.avatar {
position: relative;
top: 4px;
float: left;
margin-right: 20px;
img {
border-radius: 100%;
}
}
.meta {
color: @text-color-secondary;
font-size: @font-size-base;
line-height: 22px;
height: 22px;
}
.action {
cursor: pointer;
position: absolute;
top: 4px;
right: 0;
line-height: 1;
}
.total {
overflow: hidden;
text-overflow: ellipsis;
word-break: break-all;
white-space: nowrap;
color: @heading-color;
margin-top: 4px;
margin-bottom: 0;
font-size: 30px;
line-height: 38px;
height: 38px;
}
.content {
margin-bottom: 12px;
position: relative;
width: 100%;
}
.contentFixed {
position: absolute;
left: 0;
bottom: 0;
width: 100%;
}
.footer {
border-top: 1px solid @border-color-split;
padding-top: 9px;
margin-top: 8px;
& > * {
position: relative;
}
}
.footerMargin {
margin-top: 20px;
}
}

View File

@ -0,0 +1,8 @@
import * as React from 'react';
export interface IFieldProps {
label: React.ReactNode;
value: React.ReactNode;
style?: React.CSSProperties;
}
export default class Field extends React.Component<IFieldProps, any> {}

Some files were not shown because too many files have changed in this diff Show More