├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── .prettierignore ├── .prettierrc ├── .stylelintrc.json ├── .umirc.js ├── LICENSE ├── README.md ├── _scripts ├── BlockGenerator │ ├── index.js │ ├── package.json │ └── templates │ │ ├── package.json │ │ └── src │ │ ├── _mock.js │ │ ├── index.js │ │ ├── model.js │ │ ├── service.js │ │ └── style.less ├── build.js ├── create.js ├── lint-prettier.js └── prettier.js ├── blocks ├── .DS_Store ├── demo │ ├── package.json │ ├── snapshot.png │ └── src │ │ ├── index.js │ │ └── index.less ├── layout-antd-col-12-12 │ ├── package.json │ ├── snapshot.png │ └── src │ │ └── index.js ├── layout-holy-grail │ ├── package.json │ ├── snapshot.png │ └── src │ │ ├── index.js │ │ └── index.less └── table │ ├── .DS_Store │ ├── package.json │ ├── snapshot.png │ └── src │ ├── index.tsx │ └── typing.d.ts ├── jsconfig.json ├── now.json ├── package.json ├── plugin.js ├── templates ├── blank │ ├── package.json │ ├── snapshot.png │ └── src │ │ ├── _mock.js │ │ ├── index.js │ │ ├── model.js │ │ ├── service.js │ │ └── style.less └── user-dashboard │ ├── package.json │ ├── snapshot.png │ └── src │ ├── _mock.js │ ├── components │ ├── UserModal.js │ ├── Users.css │ └── Users.js │ ├── index.js │ ├── model.js │ ├── service.js │ └── utils │ └── constants.js ├── tsconfig.json └── typings.d.ts /.eslintignore: -------------------------------------------------------------------------------- 1 | /functions/mock/** 2 | /scripts 3 | /config -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: [require.resolve('@umijs/fabric/dist/eslint')], 3 | rules: { 4 | 'import/no-extraneous-dependencies': 0, 5 | }, 6 | }; 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # npm related 3 | /yarn.lock 4 | /node_modules 5 | /blocks/**/node_modules 6 | /blocks/**/yarn.lock 7 | /templates/**/node_modules 8 | /templates/**/yarn.lock 9 | 10 | # doc 11 | /dist 12 | 13 | # umi related 14 | .umi 15 | .umi-production 16 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | **/*.md 2 | **/*.svg 3 | **/*.ejs 4 | **/*.html 5 | package.json 6 | .umi 7 | .umi-production 8 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "es5", 4 | "printWidth": 100, 5 | "overrides": [ 6 | { 7 | "files": ".prettierrc", 8 | "options": { "parser": "json" } 9 | } 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /.stylelintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["stylelint-config-standard", "stylelint-config-prettier"], 3 | "rules": { 4 | "declaration-empty-line-before": null, 5 | "no-descending-specificity": null, 6 | "selector-pseudo-class-no-unknown": null, 7 | "selector-pseudo-element-colon-notation": null 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /.umirc.js: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: [ 3 | [ 4 | 'umi-plugin-block-dev', 5 | { 6 | layout: process.env.LAYOUT || 'ant-design-pro', 7 | menu: { 8 | name: process.env.BLOCK, 9 | icon: 'home', 10 | }, 11 | }, 12 | ], 13 | [ 14 | 'umi-plugin-react', 15 | { 16 | dva: true, 17 | antd: true, 18 | }, 19 | ], 20 | // require.resolve('./plugin'), 21 | ], 22 | }; 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 umijs 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # umi-blocks 2 | 3 | Official block collection from umi community. 4 | 5 | ## Usage 6 | 7 | ### Install Dependencies 8 | 9 | ```bash 10 | $ yarn 11 | ``` 12 | 13 | ### View Block or Template 14 | 15 | > Specify the block via BLOCK env. 16 | 17 | ```bash 18 | yarn start templates/user-dashboard 19 | ``` 20 | 21 | ### Create Block 22 | 23 | ```bash 24 | # Create 25 | $ BLOCK=templates/my-template yarn run create 26 | 27 | # Debug 28 | $ BLOCK=templates/my-template yarn start 29 | ``` 30 | 31 | You can visit umi [doc](https://umijs.org/guide/block.html) for find out more info about umi block. 32 | 33 | If you are done, make a PR for this. 34 | 35 | ## LICENSE 36 | 37 | MIT 38 | -------------------------------------------------------------------------------- /_scripts/BlockGenerator/index.js: -------------------------------------------------------------------------------- 1 | const Generator = require('yeoman-generator'); 2 | const { statSync } = require('fs'); 3 | const glob = require('glob'); 4 | 5 | class BlockGenerator extends Generator { 6 | writing() { 7 | glob 8 | .sync('**/*', { 9 | cwd: this.templatePath(), 10 | dot: true, 11 | }) 12 | .forEach(file => { 13 | const filePath = this.templatePath(file); 14 | if (statSync(filePath).isFile()) { 15 | this.fs.copyTpl( 16 | this.templatePath(filePath), 17 | this.destinationPath(file.replace(/^_/, '.')), 18 | { 19 | name: process.env.BLOCK.split('/')[1], 20 | } 21 | ); 22 | } 23 | }); 24 | } 25 | } 26 | 27 | module.exports = BlockGenerator; 28 | -------------------------------------------------------------------------------- /_scripts/BlockGenerator/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "block-generator" 3 | } 4 | -------------------------------------------------------------------------------- /_scripts/BlockGenerator/templates/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "<%= name %>", 3 | "description": "", 4 | "dependencies": { 5 | "umi-request": "^1.0.0" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /_scripts/BlockGenerator/templates/src/_mock.js: -------------------------------------------------------------------------------- 1 | export default { 2 | 'GET /api/BLOCK_NAME/text': { 3 | text: 'I am a blank block', 4 | }, 5 | }; 6 | -------------------------------------------------------------------------------- /_scripts/BlockGenerator/templates/src/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Button } from 'antd'; 3 | import { connect } from 'dva'; 4 | 5 | import styles from './style.less'; 6 | 7 | @connect(({ BLOCK_NAME_CAMEL_CASE }) => BLOCK_NAME_CAMEL_CASE) 8 | class Page extends Component { 9 | componentDidMount() { 10 | const { dispatch } = this.props; 11 | dispatch({ 12 | type: 'BLOCK_NAME_CAMEL_CASE/fetch', 13 | }); 14 | } 15 | 16 | render() { 17 | const { text } = this.props; 18 | return ( 19 |
20 | 21 |
22 | ); 23 | } 24 | } 25 | 26 | export default Page; 27 | -------------------------------------------------------------------------------- /_scripts/BlockGenerator/templates/src/model.js: -------------------------------------------------------------------------------- 1 | import { getText } from './service'; 2 | 3 | export default { 4 | namespace: 'BLOCK_NAME_CAMEL_CASE', 5 | state: { 6 | text: 'loading...', 7 | }, 8 | 9 | effects: { 10 | *fetch(_, { call, put }) { 11 | const { text } = yield call(getText); 12 | yield put({ 13 | type: 'save', 14 | payload: { 15 | text, 16 | }, 17 | }); 18 | }, 19 | }, 20 | 21 | reducers: { 22 | save(state, { payload }) { 23 | return { 24 | ...state, 25 | ...payload, 26 | }; 27 | }, 28 | }, 29 | }; 30 | -------------------------------------------------------------------------------- /_scripts/BlockGenerator/templates/src/service.js: -------------------------------------------------------------------------------- 1 | import request from 'umi-request'; 2 | 3 | export function getText() { 4 | return request('/api/BLOCK_NAME/text'); 5 | } 6 | -------------------------------------------------------------------------------- /_scripts/BlockGenerator/templates/src/style.less: -------------------------------------------------------------------------------- 1 | .container { 2 | padding: 8px; 3 | } 4 | -------------------------------------------------------------------------------- /_scripts/build.js: -------------------------------------------------------------------------------- 1 | const { readdirSync, readFileSync, writeFileSync, existsSync } = require('fs'); 2 | const { join, basename } = require('path'); 3 | const mkdirp = require('mkdirp'); 4 | 5 | function haveDependencies(pkg, depName) { 6 | if (pkg.dependencies && pkg.dependencies[depName]) { 7 | return true; 8 | } 9 | if (pkg.devDependencies && pkg.devDependencies[depName]) { 10 | return true; 11 | } 12 | return false; 13 | } 14 | 15 | const EXT_NAMES = ['.tsx', '.ts', '.jsx', '.js']; 16 | 17 | function getFile(cwd, fileName) { 18 | for (const ext of EXT_NAMES) { 19 | const file = join(cwd, `${fileName}${ext}`); 20 | if (existsSync(file)) { 21 | return file; 22 | } 23 | } 24 | } 25 | 26 | function haveImport(cwd, name) { 27 | const indexFile = getFile(cwd, 'src/index'); 28 | if (!indexFile) return false; 29 | const content = readFileSync(indexFile, 'utf-8'); 30 | return content.includes(`'${name}'`) || content.includes(`"${name}"`); 31 | } 32 | 33 | function parseJSON(root) { 34 | const dirs = readdirSync(root); 35 | const type = basename(root); 36 | const list = dirs.reduce((memo = [], dir) => { 37 | if (dir.charAt(0) === '.') return; 38 | const absDirPath = join(root, dir); 39 | const pkg = require(join(absDirPath, 'package.json')); 40 | const url = `https://github.com/umijs/umi-blocks/tree/master/${type}/${dir}`; 41 | const img = `https://github.com/umijs/umi-blocks/blob/master/${type}/${dir}/snapshot.png?raw=true`; 42 | const features = []; 43 | if (haveDependencies(pkg, 'antd') || haveImport(absDirPath, 'antd')) { 44 | features.push('antd'); 45 | } 46 | if (getFile(absDirPath, 'src/model')) { 47 | features.push('dva'); 48 | } 49 | memo.push({ 50 | name: pkg.name, 51 | description: pkg.description, 52 | url, 53 | tags: [], 54 | img, 55 | previewUrl: '', 56 | features, 57 | ...(pkg.block && pkg.block.category ? { category: pkg.block.category } : {}), 58 | }); 59 | return memo; 60 | }, []); 61 | return { list }; 62 | } 63 | 64 | function generate(root) { 65 | const dist = join(root, '..', 'dist'); 66 | const type = basename(root); 67 | 68 | console.log(`Generate json for ${type}`); 69 | mkdirp.sync(dist); 70 | 71 | const json = parseJSON(root); 72 | writeFileSync(join(dist, `${type}.json`), JSON.stringify(json, null, 2), 'utf-8'); 73 | } 74 | 75 | generate(join(__dirname, '..', 'templates')); 76 | generate(join(__dirname, '..', 'blocks')); 77 | -------------------------------------------------------------------------------- /_scripts/create.js: -------------------------------------------------------------------------------- 1 | const { join } = require('path'); 2 | const BlockGenerator = require('./BlockGenerator'); 3 | 4 | if (!process.env.BLOCK) { 5 | console.log(` 6 | Please specify the BLOCK env, 7 | 8 | e.g. 9 | 10 | $ BLOCK=templates/new-template yarn run create 11 | $ BLOCK=blocks/new-block yarn run create 12 | `); 13 | process.exit(1); 14 | } 15 | 16 | const generator = new BlockGenerator({ 17 | name: 'foo', 18 | env: { 19 | cwd: join(__dirname, '..', process.env.BLOCK), 20 | }, 21 | resolved: require.resolve(require.resolve('./BlockGenerator')), 22 | }); 23 | 24 | generator.run(() => { 25 | console.log('done'); 26 | }); 27 | -------------------------------------------------------------------------------- /_scripts/lint-prettier.js: -------------------------------------------------------------------------------- 1 | /** 2 | * copy to https://github.com/facebook/react/blob/master/scripts/prettier/index.js 3 | * prettier api doc https://prettier.io/docs/en/api.html 4 | *----------*****-------------- 5 | * lint file is prettier 6 | *----------*****-------------- 7 | */ 8 | 9 | const prettier = require('prettier'); 10 | const fs = require('fs'); 11 | 12 | const prettierConfigPath = require.resolve('../.prettierrc'); 13 | 14 | const files = process.argv.slice(2); 15 | 16 | let didError = false; 17 | let didWarn = false; 18 | 19 | files.forEach(file => { 20 | const options = prettier.resolveConfig.sync(file, { 21 | config: prettierConfigPath, 22 | }); 23 | try { 24 | const fileInfo = prettier.getFileInfo.sync(file); 25 | if (fileInfo.ignored) { 26 | return; 27 | } 28 | const input = fs.readFileSync(file, 'utf8'); 29 | const withParserOptions = { 30 | ...options, 31 | parser: fileInfo.inferredParser, 32 | }; 33 | const isPrettier = prettier.check(input, withParserOptions); 34 | if (!isPrettier) { 35 | console.log( 36 | `\x1b[31m ${file} is no prettier, please use npm run prettier and git add !\x1b[0m` 37 | ); 38 | didWarn = true; 39 | } 40 | } catch (e) { 41 | didError = true; 42 | } 43 | }); 44 | 45 | if (didWarn || didError) { 46 | process.exit(1); 47 | } 48 | console.log('\x1b[32m lint prettier success!\x1b[0m'); 49 | -------------------------------------------------------------------------------- /_scripts/prettier.js: -------------------------------------------------------------------------------- 1 | /** 2 | * copy to https://github.com/facebook/react/blob/master/scripts/prettier/index.js 3 | * prettier api doc https://prettier.io/docs/en/api.html 4 | *----------*****-------------- 5 | * prettier all js and all ts. 6 | *----------*****-------------- 7 | */ 8 | 9 | const glob = require('glob'); 10 | const prettier = require('prettier'); 11 | const fs = require('fs'); 12 | 13 | const prettierConfigPath = require.resolve('../.prettierrc'); 14 | 15 | let didError = false; 16 | 17 | let files = []; 18 | const jsFiles = glob.sync('ant-design-pro/**/*.js*', { 19 | ignore: ['**/node_modules/**', 'build/**'], 20 | }); 21 | const tsFiles = glob.sync('ant-design-pro/**/*.ts*', { 22 | ignore: ['**/node_modules/**', 'build/**'], 23 | }); 24 | files = files.concat(jsFiles); 25 | files = files.concat(tsFiles); 26 | if (!files.length) { 27 | return; 28 | } 29 | 30 | files.forEach(file => { 31 | const options = prettier.resolveConfig.sync(file, { 32 | config: prettierConfigPath, 33 | }); 34 | const fileInfo = prettier.getFileInfo.sync(file); 35 | if (fileInfo.ignored) { 36 | return; 37 | } 38 | try { 39 | const input = fs.readFileSync(file, 'utf8'); 40 | const withParserOptions = { 41 | ...options, 42 | parser: fileInfo.inferredParser, 43 | }; 44 | const output = prettier.format(input, withParserOptions); 45 | if (output !== input) { 46 | fs.writeFileSync(file, output, 'utf8'); 47 | console.log(`\x1b[34m ${file} is prettier`); 48 | } 49 | } catch (e) { 50 | didError = true; 51 | } 52 | }); 53 | 54 | if (didError) { 55 | process.exit(1); 56 | } 57 | console.log('\x1b[32m prettier success!'); 58 | -------------------------------------------------------------------------------- /blocks/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/umijs/umi-blocks/f73b2e5a9dc082deba64c7bfbe426bc75f5f5fa0/blocks/.DS_Store -------------------------------------------------------------------------------- /blocks/demo/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Demo", 3 | "description": "Demo block of umi, with antd.", 4 | "dependencies": { 5 | "antd": "^3.10.9" 6 | }, 7 | "block": { 8 | "category": "脚手架" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /blocks/demo/snapshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/umijs/umi-blocks/f73b2e5a9dc082deba64c7bfbe426bc75f5f5fa0/blocks/demo/snapshot.png -------------------------------------------------------------------------------- /blocks/demo/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Button } from 'antd'; 3 | import styles from './index.less'; 4 | 5 | export default () => { 6 | return ; 7 | }; 8 | -------------------------------------------------------------------------------- /blocks/demo/src/index.less: -------------------------------------------------------------------------------- 1 | .container { 2 | color: blue; 3 | } 4 | -------------------------------------------------------------------------------- /blocks/layout-antd-col-12-12/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "两栏布局 Col 12-12", 3 | "description": "Antd 的 col-12-12 布局。", 4 | "dependencies": { 5 | "antd": "^3.10.9" 6 | }, 7 | "block": { 8 | "category": "布局" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /blocks/layout-antd-col-12-12/snapshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/umijs/umi-blocks/f73b2e5a9dc082deba64c7bfbe426bc75f5f5fa0/blocks/layout-antd-col-12-12/snapshot.png -------------------------------------------------------------------------------- /blocks/layout-antd-col-12-12/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Row, Col } from 'antd'; 3 | 4 | export default () => { 5 | return ( 6 | 7 | 8 | INSERT_BLOCK_PLACEHOLDER:Col 12 9 | 10 | 11 | INSERT_BLOCK_PLACEHOLDER:Col 12 12 | 13 | 14 | ); 15 | }; 16 | -------------------------------------------------------------------------------- /blocks/layout-holy-grail/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "圣杯布局", 3 | "description": "包含简单的响应式。", 4 | "block": { 5 | "category": "布局" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /blocks/layout-holy-grail/snapshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/umijs/umi-blocks/f73b2e5a9dc082deba64c7bfbe426bc75f5f5fa0/blocks/layout-holy-grail/snapshot.png -------------------------------------------------------------------------------- /blocks/layout-holy-grail/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import styles from './index.less'; 3 | 4 | export default function() { 5 | return ( 6 |
7 |
INSERT_BLOCK_PLACEHOLDER:HEADER
8 |
9 |
INSERT_BLOCK_PLACEHOLDER:CONTENT
10 | 11 | 12 |
13 | 14 |
15 | ); 16 | } 17 | -------------------------------------------------------------------------------- /blocks/layout-holy-grail/src/index.less: -------------------------------------------------------------------------------- 1 | .wrapper { 2 | display: flex; 3 | min-height: 100vh; 4 | flex-direction: column; 5 | 6 | header, 7 | footer { 8 | background: #82caff; 9 | flex: 0 0 6em; 10 | } 11 | } 12 | 13 | .body { 14 | display: flex; 15 | flex: 1; 16 | flex-direction: column; 17 | 18 | main { 19 | flex: 1; 20 | background: #fecccc; 21 | } 22 | aside, 23 | nav { 24 | flex: 0 0 12em; 25 | background: #c9c; 26 | } 27 | nav { 28 | order: -1; 29 | } 30 | } 31 | 32 | @media (min-width: 768px) { 33 | .body { 34 | flex-direction: row; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /blocks/table/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/umijs/umi-blocks/f73b2e5a9dc082deba64c7bfbe426bc75f5f5fa0/blocks/table/.DS_Store -------------------------------------------------------------------------------- /blocks/table/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "table", 3 | "description": "查询表格是每一个项目都需要的基本页面。", 4 | "dependencies": { 5 | "@ant-design/pro-table": "^1.0.31", 6 | "@umijs/ui-flag": "1.0.2", 7 | "antd": "^3.10.9" 8 | }, 9 | "peerDependencies": { 10 | "react": "^16.8.6", 11 | "react-dom": "^16.8.6" 12 | }, 13 | "devDependencies": { 14 | "umi": "^2.13.0" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /blocks/table/snapshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/umijs/umi-blocks/f73b2e5a9dc082deba64c7bfbe426bc75f5f5fa0/blocks/table/snapshot.png -------------------------------------------------------------------------------- /blocks/table/src/index.tsx: -------------------------------------------------------------------------------- 1 | import { Button, Divider, Dropdown, Form, Icon, Menu } from 'antd'; 2 | import React, { useState, useRef } from 'react'; 3 | import ProTable, { ProColumns, ActionType } from '@ant-design/pro-table'; 4 | import UmiUIFlag from '@umijs/ui-flag'; 5 | import { FormComponentProps } from 'antd/es/form'; 6 | import { TableListItem, TableListParams } from './typing.d'; 7 | 8 | export async function queryRule(params?: TableListParams) { 9 | console.log(params); 10 | return { data: [], success: true }; 11 | } 12 | 13 | const TableList: React.FC = () => { 14 | const [sorter, setSorter] = useState({}); 15 | const actionRef = useRef(); 16 | const columns: ProColumns[] = [ 17 | { 18 | title: '规则名称', 19 | dataIndex: 'name', 20 | }, 21 | { 22 | title: '描述', 23 | dataIndex: 'desc', 24 | }, 25 | { 26 | title: '服务调用次数', 27 | dataIndex: 'callNo', 28 | sorter: true, 29 | align: 'right', 30 | renderText: (val: string) => `${val} 万`, 31 | }, 32 | { 33 | title: '状态', 34 | dataIndex: 'status', 35 | valueEnum: { 36 | 0: { text: '关闭', status: 'Default' }, 37 | 1: { text: '运行中', status: 'Processing' }, 38 | 2: { text: '已上线', status: 'Success' }, 39 | 3: { text: '异常', status: 'Error' }, 40 | }, 41 | }, 42 | { 43 | title: '上次调度时间', 44 | dataIndex: 'updatedAt', 45 | sorter: true, 46 | valueType: 'dateTime', 47 | }, 48 | { 49 | title: '操作', 50 | dataIndex: 'option', 51 | valueType: 'option', 52 | render: () => ( 53 | <> 54 | 配置 55 | 56 | 订阅警报 57 | 58 | ), 59 | }, 60 | ]; 61 | 62 | return ( 63 | 64 | headerTitle="查询表格" 65 | actionRef={actionRef} 66 | rowKey="key" 67 | onChange={(_, _filter, _sorter) => { 68 | setSorter(`${_sorter.field}_${_sorter.order}`); 69 | }} 70 | params={{ 71 | sorter, 72 | }} 73 | toolBarRender={(action, { selectedRows }) => [ 74 | <> 75 | 76 | , 77 | , 80 | selectedRows && selectedRows.length > 0 && ( 81 | { 85 | if (e.key === 'remove') { 86 | console.log('remove'); 87 | action.reload(); 88 | } 89 | }} 90 | selectedKeys={[]} 91 | > 92 | 批量删除 93 | 批量审批 94 | 95 | } 96 | > 97 | 100 | 101 | ), 102 | ]} 103 | tableAlertRender={(selectedRowKeys, selectedRows) => ( 104 |
105 | 已选择 {selectedRowKeys.length} 项   106 | 107 | 服务调用次数总计 {selectedRows.reduce((pre, item) => pre + item.callNo, 0)} 万 108 | 109 |
110 | )} 111 | request={params => queryRule(params)} 112 | columns={columns} 113 | /> 114 | ); 115 | }; 116 | 117 | export default Form.create()(TableList); 118 | -------------------------------------------------------------------------------- /blocks/table/src/typing.d.ts: -------------------------------------------------------------------------------- 1 | export interface TableListItem { 2 | key: number; 3 | disabled?: boolean; 4 | href: string; 5 | avatar: string; 6 | name: string; 7 | title: string; 8 | owner: string; 9 | desc: string; 10 | callNo: number; 11 | status: number; 12 | updatedAt: Date; 13 | createdAt: Date; 14 | progress: number; 15 | } 16 | 17 | export interface TableListParams { 18 | sorter?: string; 19 | status?: string; 20 | name?: string; 21 | desc?: string; 22 | key?: number; 23 | pageSize?: number; 24 | currentPage?: number; 25 | } 26 | -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "emitDecoratorMetadata": true, 4 | "experimentalDecorators": true, 5 | "baseUrl": ".", 6 | "paths": { 7 | "@/*": ["./src/*"] 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /now.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "blocks", 3 | "version": 2, 4 | "scope": "umijs", 5 | "builds": [ 6 | { 7 | "src": "./package.json", 8 | "use": "@now/static-build", 9 | "config": { 10 | "distDir": "./dist" 11 | } 12 | } 13 | ], 14 | "alias": ["blocks.umijs.org"] 15 | } 16 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "umi-blocks", 3 | "version": "1.0.0", 4 | "files": [ 5 | "blocks", 6 | "dist", 7 | "templates" 8 | ], 9 | "scripts": { 10 | "build": "node ./_scripts/build.js", 11 | "create": "node ./_scripts/create.js", 12 | "lint": "eslint --ext .js src mock tests && npm run lint:style", 13 | "lint-staged": "lint-staged", 14 | "lint-staged:js": "eslint --ext .js", 15 | "lint:fix": "eslint --fix --ext .js src mock tests && npm run lint:style", 16 | "lint:style": "stylelint \"src/**/*.less\" --syntax less", 17 | "prettier": "prettier -c --write \"**/*\"", 18 | "screenshot": "pro screenshot", 19 | "start": "umi block_dev" 20 | }, 21 | "husky": { 22 | "hooks": { 23 | "pre-commit": "npm run lint-staged" 24 | } 25 | }, 26 | "lint-staged": { 27 | "**/*.less": "stylelint --syntax less", 28 | "x/**/*.{js,jsx}": "npm run lint-staged:js", 29 | "x/**/*.{js,ts,tsx,json,jsx,less}": [ 30 | "node ./_scripts/lint-prettier.js", 31 | "git add" 32 | ] 33 | }, 34 | "devDependencies": { 35 | "@ant-design/pro-cli": "^1.0.5", 36 | "@ant-design/pro-layout": "^4.5.16", 37 | "@umijs/fabric": "^2.0.0", 38 | "cross-env": "^6.0.0", 39 | "eslint": "^6.8.0", 40 | "glob": "^7.1.4", 41 | "husky": "^4.0.7", 42 | "import-sort-cli": "^6.0.0", 43 | "import-sort-parser-babylon": "^6.0.0", 44 | "import-sort-parser-typescript": "^6.0.0", 45 | "import-sort-style-module": "^6.0.0", 46 | "lint-staged": "^9.5.0", 47 | "mkdirp": "^0.5.1", 48 | "prettier": "1.19.1", 49 | "stylelint": "^13.0.0", 50 | "stylelint-config-prettier": "^8.0.0", 51 | "stylelint-config-standard": "^19.0.0", 52 | "typescript": "^3.5.1", 53 | "umi": "^2.13.0", 54 | "umi-plugin-block-dev": "^3.0.1", 55 | "umi-plugin-react": "^1.15.0", 56 | "umi-request": "^1.2.17", 57 | "yeoman-generator": "^4.1.0" 58 | }, 59 | "engines": { 60 | "node": "12.x" 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /plugin.js: -------------------------------------------------------------------------------- 1 | import { join } from 'path'; 2 | import { readdirSync } from 'fs'; 3 | 4 | if (process.env.BLOCK) { 5 | process.env.PAGES_PATH = join(process.env.BLOCK, 'src'); 6 | } else { 7 | console.log(` 8 | Please specify the BLOCK env, 9 | 10 | e.g. 11 | 12 | $ BLOCK=templates/user-dashboard yarn start 13 | $ BLOCK=blocks/blank yarn start 14 | `); 15 | process.exit(1); 16 | } 17 | 18 | function getRoutes(type) { 19 | const dirs = readdirSync(join(__dirname, type)); 20 | const routes = []; 21 | for (const dir of dirs) { 22 | if (dir.charAt(0) === '.') continue; 23 | routes.push({ 24 | path: `/${type}/${dir}`, 25 | component: join(type, dir, 'src', 'index.js'), 26 | }); 27 | } 28 | return routes; 29 | } 30 | 31 | function getRoute() { 32 | return { 33 | path: '/', 34 | component: join(process.env.BLOCK, 'src', 'index.js'), 35 | }; 36 | } 37 | 38 | export default api => { 39 | api.modifyRoutes(routes => { 40 | routes[0].routes = [ 41 | getRoute(), 42 | // ...getRoutes('blocks'), 43 | // ...getRoutes('templates'), 44 | ...routes[0].routes.slice(1), 45 | ]; 46 | return routes; 47 | }); 48 | }; 49 | -------------------------------------------------------------------------------- /templates/blank/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Blank", 3 | "description": "Blank block for quick start a umi page develop, with mock, dva, service and antd.", 4 | "dependencies": { 5 | "umi-request": "^1.0.0" 6 | }, 7 | "block": { 8 | "category": "脚手架" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /templates/blank/snapshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/umijs/umi-blocks/f73b2e5a9dc082deba64c7bfbe426bc75f5f5fa0/templates/blank/snapshot.png -------------------------------------------------------------------------------- /templates/blank/src/_mock.js: -------------------------------------------------------------------------------- 1 | export default { 2 | 'GET /api/BLOCK_NAME/text': { 3 | text: 'I am a blank block', 4 | }, 5 | }; 6 | -------------------------------------------------------------------------------- /templates/blank/src/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Button } from 'antd'; 3 | import { connect } from 'dva'; 4 | 5 | import styles from './style.less'; 6 | 7 | @connect(({ BLOCK_NAME_CAMEL_CASE }) => BLOCK_NAME_CAMEL_CASE) 8 | class Page extends Component { 9 | componentDidMount() { 10 | const { dispatch } = this.props; 11 | dispatch({ 12 | type: 'BLOCK_NAME_CAMEL_CASE/fetch', 13 | }); 14 | } 15 | 16 | render() { 17 | const { text } = this.props; 18 | return ( 19 |
20 | 21 |
22 | ); 23 | } 24 | } 25 | 26 | export default Page; 27 | -------------------------------------------------------------------------------- /templates/blank/src/model.js: -------------------------------------------------------------------------------- 1 | import { getText } from './service'; 2 | 3 | export default { 4 | namespace: 'BLOCK_NAME_CAMEL_CASE', 5 | state: { 6 | text: 'loading...', 7 | }, 8 | 9 | effects: { 10 | *fetch(_, { call, put }) { 11 | const { text } = yield call(getText); 12 | yield put({ 13 | type: 'save', 14 | payload: { 15 | text, 16 | }, 17 | }); 18 | }, 19 | }, 20 | 21 | reducers: { 22 | save(state, { payload }) { 23 | return { 24 | ...state, 25 | ...payload, 26 | }; 27 | }, 28 | }, 29 | }; 30 | -------------------------------------------------------------------------------- /templates/blank/src/service.js: -------------------------------------------------------------------------------- 1 | import request from 'umi-request'; 2 | 3 | export function getText() { 4 | return request('/api/BLOCK_NAME/text'); 5 | } 6 | -------------------------------------------------------------------------------- /templates/blank/src/style.less: -------------------------------------------------------------------------------- 1 | .container { 2 | padding: 8px; 3 | } 4 | -------------------------------------------------------------------------------- /templates/user-dashboard/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "UserDashboard", 3 | "description": "User dashboard example with umi, antd and dva.", 4 | "dependencies": { 5 | "antd": "^3.23.4", 6 | "umi-request": "^1.0.0" 7 | }, 8 | "block": { 9 | "category": "表格" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /templates/user-dashboard/snapshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/umijs/umi-blocks/f73b2e5a9dc082deba64c7bfbe426bc75f5f5fa0/templates/user-dashboard/snapshot.png -------------------------------------------------------------------------------- /templates/user-dashboard/src/_mock.js: -------------------------------------------------------------------------------- 1 | const PAGE_SIZE = 3; 2 | let data = [ 3 | { 4 | id: 1, 5 | name: 'Leanne Graham', 6 | email: 'Sincere@april.biz', 7 | website: 'hildegard.org', 8 | }, 9 | { 10 | id: 2, 11 | name: 'Ervin Howell', 12 | email: 'Shanna@melissa.tv', 13 | website: 'anastasia.net', 14 | }, 15 | { 16 | id: 3, 17 | name: 'Clementine Bauch', 18 | email: 'Nathan@yesenia.net', 19 | website: 'ramiro.info', 20 | }, 21 | { 22 | id: 4, 23 | name: 'Patricia Lebsack', 24 | email: 'Julianne.OConner@kory.org', 25 | website: 'kale.biz', 26 | }, 27 | { 28 | id: 5, 29 | name: 'Chelsey Dietrich', 30 | email: 'Lucio_Hettinger@annie.ca', 31 | website: 'demarco.info', 32 | }, 33 | { 34 | id: 6, 35 | name: 'Mrs. Dennis Schulist', 36 | email: 'Karley_Dach@jasper.info', 37 | website: 'ola.org', 38 | }, 39 | { 40 | id: 7, 41 | name: 'Kurtis Weissnat', 42 | email: 'Telly.Hoeger@billy.biz', 43 | website: 'elvis.io', 44 | }, 45 | { 46 | id: 8, 47 | name: 'Nicholas Runolfsdottir V', 48 | email: 'Sherwood@rosamond.me', 49 | website: 'jacynthe.com', 50 | }, 51 | { 52 | id: 9, 53 | name: 'Glenna Reichert', 54 | email: 'Chaim_McDermott@dana.io', 55 | website: 'conrad.com', 56 | }, 57 | ]; 58 | 59 | function uid(len) { 60 | len = len || 7; 61 | return Math.random() 62 | .toString(35) 63 | .substr(2, len); 64 | } 65 | 66 | function getData(page) { 67 | const start = (page - 1) * PAGE_SIZE; 68 | return { 69 | status: 'success', 70 | total: data.length, 71 | page, 72 | data: data.slice(start, start + 3), 73 | }; 74 | } 75 | 76 | export default { 77 | 'GET /api/BLOCK_NAME': (req, res) => { 78 | res.json(getData(parseInt(req.query.page, 10) || 1)); 79 | }, 80 | 'DELETE /api/BLOCK_NAME/:id': (req, res) => { 81 | data = data.filter(item => `${item.id}` !== `${req.params.id}`); 82 | res.end('ok'); 83 | }, 84 | 'PATCH /api/BLOCK_NAME/:id': (req, res) => { 85 | data.forEach(item => { 86 | if (`${item.id}` === `${req.params.id}`) { 87 | Object.assign(item, req.body); 88 | } 89 | }); 90 | res.end('ok'); 91 | }, 92 | 'POST /api/BLOCK_NAME': (req, res) => { 93 | data.push({ 94 | ...req.body, 95 | id: uid(), 96 | }); 97 | res.end('ok'); 98 | }, 99 | }; 100 | -------------------------------------------------------------------------------- /templates/user-dashboard/src/components/UserModal.js: -------------------------------------------------------------------------------- 1 | import { Component } from 'react'; 2 | import { Modal, Form, Input } from 'antd'; 3 | 4 | const FormItem = Form.Item; 5 | 6 | class UserEditModal extends Component { 7 | constructor(props) { 8 | super(props); 9 | this.state = { 10 | visible: false, 11 | }; 12 | } 13 | 14 | showModelHandler = e => { 15 | if (e) e.stopPropagation(); 16 | this.setState({ 17 | visible: true, 18 | }); 19 | }; 20 | 21 | hideModelHandler = () => { 22 | this.setState({ 23 | visible: false, 24 | }); 25 | }; 26 | 27 | okHandler = () => { 28 | const { onOk } = this.props; 29 | this.props.form.validateFields((err, values) => { 30 | if (!err) { 31 | onOk(values); 32 | this.hideModelHandler(); 33 | } 34 | }); 35 | }; 36 | 37 | render() { 38 | const { children } = this.props; 39 | const { getFieldDecorator } = this.props.form; 40 | const { name, email, website } = this.props.record; 41 | const formItemLayout = { 42 | labelCol: { span: 6 }, 43 | wrapperCol: { span: 14 }, 44 | }; 45 | 46 | return ( 47 | 48 | {children} 49 | 55 |
56 | 57 | {getFieldDecorator('name', { 58 | initialValue: name, 59 | })()} 60 | 61 | 62 | {getFieldDecorator('email', { 63 | initialValue: email, 64 | })()} 65 | 66 | 67 | {getFieldDecorator('website', { 68 | initialValue: website, 69 | })()} 70 | 71 |
72 |
73 |
74 | ); 75 | } 76 | } 77 | 78 | export default Form.create()(UserEditModal); 79 | -------------------------------------------------------------------------------- /templates/user-dashboard/src/components/Users.css: -------------------------------------------------------------------------------- 1 | .normal { 2 | } 3 | 4 | .operation a { 5 | margin: 0 0.5em; 6 | } 7 | 8 | .create { 9 | margin: 1.5em 0; 10 | } 11 | -------------------------------------------------------------------------------- /templates/user-dashboard/src/components/Users.js: -------------------------------------------------------------------------------- 1 | import { connect } from 'dva'; 2 | import { Table, Pagination, Popconfirm, Button } from 'antd'; 3 | import { routerRedux } from 'dva/router'; 4 | import styles from './Users.css'; 5 | import { PAGE_SIZE } from '../utils/constants'; 6 | import UserModal from './UserModal'; 7 | 8 | function Users({ dispatch, list: dataSource, loading, total, page: current }) { 9 | function deleteHandler(id) { 10 | dispatch({ 11 | type: 'BLOCK_NAME/remove', 12 | payload: id, 13 | }); 14 | } 15 | 16 | function pageChangeHandler(page) { 17 | dispatch({ 18 | type: 'BLOCK_NAME/fetch', 19 | payload: { page }, 20 | }); 21 | } 22 | 23 | function editHandler(id, values) { 24 | dispatch({ 25 | type: 'BLOCK_NAME/patch', 26 | payload: { id, values }, 27 | }); 28 | } 29 | 30 | function createHandler(values) { 31 | dispatch({ 32 | type: 'BLOCK_NAME/create', 33 | payload: values, 34 | }); 35 | } 36 | 37 | const columns = [ 38 | { 39 | title: 'Name', 40 | dataIndex: 'name', 41 | key: 'name', 42 | render: text => {text}, 43 | }, 44 | { 45 | title: 'Email', 46 | dataIndex: 'email', 47 | key: 'email', 48 | }, 49 | { 50 | title: 'Website', 51 | dataIndex: 'website', 52 | key: 'website', 53 | }, 54 | { 55 | title: 'Operation', 56 | key: 'operation', 57 | render: (text, record) => ( 58 | 59 | 60 | Edit 61 | 62 | 63 | Delete 64 | 65 | 66 | ), 67 | }, 68 | ]; 69 | 70 | return ( 71 |
72 |
73 |
74 | 75 | 76 | 77 |
78 | record.id} 83 | pagination={false} 84 | /> 85 | 92 | 93 | 94 | ); 95 | } 96 | 97 | function mapStateToProps(state) { 98 | const { list, total, page } = state['BLOCK_NAME']; 99 | return { 100 | list, 101 | total, 102 | page, 103 | loading: state.loading.models['BLOCK_NAME'], 104 | }; 105 | } 106 | 107 | export default connect(mapStateToProps)(Users); 108 | -------------------------------------------------------------------------------- /templates/user-dashboard/src/index.js: -------------------------------------------------------------------------------- 1 | import { Component } from 'react'; 2 | import { connect } from 'dva'; 3 | import Users from './components/Users'; 4 | 5 | class BLOCK_NAME_CAMEL_CASE extends Component { 6 | componentDidMount() { 7 | this.props.dispatch({ 8 | type: 'BLOCK_NAME/fetch', 9 | payload: { 10 | page: 1, 11 | }, 12 | }); 13 | } 14 | 15 | render() { 16 | return ( 17 |
18 | 19 |
20 | ); 21 | } 22 | } 23 | 24 | export default connect()(BLOCK_NAME_CAMEL_CASE); 25 | -------------------------------------------------------------------------------- /templates/user-dashboard/src/model.js: -------------------------------------------------------------------------------- 1 | import * as usersService from './service'; 2 | 3 | export default { 4 | namespace: 'BLOCK_NAME', 5 | state: { 6 | list: [], 7 | total: null, 8 | page: null, 9 | }, 10 | reducers: { 11 | save(state, { payload: { data: list, total, page } }) { 12 | return { ...state, list, total, page }; 13 | }, 14 | }, 15 | effects: { 16 | *fetch({ payload: { page = 1 } }, { call, put }) { 17 | const { data, total, page: currentPage } = yield call(usersService.fetch, { page }); 18 | yield put({ 19 | type: 'save', 20 | payload: { 21 | data, 22 | total, 23 | page: currentPage, 24 | }, 25 | }); 26 | }, 27 | *remove({ payload: id }, { call, put, select }) { 28 | yield call(usersService.remove, id); 29 | const page = yield select(state => state['BLOCK_NAME'].page); 30 | yield put({ type: 'fetch', payload: { page } }); 31 | }, 32 | *patch({ payload: { id, values } }, { call, put, select }) { 33 | yield call(usersService.patch, id, values); 34 | const page = yield select(state => state['BLOCK_NAME'].page); 35 | yield put({ type: 'fetch', payload: { page } }); 36 | }, 37 | *create({ payload: values }, { call, put, select }) { 38 | yield call(usersService.create, values); 39 | const page = yield select(state => state['BLOCK_NAME'].page); 40 | yield put({ type: 'fetch', payload: { page } }); 41 | }, 42 | }, 43 | }; 44 | -------------------------------------------------------------------------------- /templates/user-dashboard/src/service.js: -------------------------------------------------------------------------------- 1 | import request from 'umi-request'; 2 | 3 | const API_PREFIX = `/api/BLOCK_NAME`; 4 | 5 | export function fetch({ page = 1 }) { 6 | return request(`${API_PREFIX}?page=${page}`); 7 | } 8 | 9 | export function remove(id) { 10 | return request(`${API_PREFIX}/${id}`, { 11 | method: 'DELETE', 12 | }); 13 | } 14 | 15 | export function patch(id, values) { 16 | // TODO: 17 | // use umi-request after the issue is closed 18 | // https://github.com/umijs/umi-request/issues/5 19 | return window.fetch(`${API_PREFIX}/${id}`, { 20 | method: 'PATCH', 21 | headers: { 22 | 'Content-Type': 'application/json', 23 | }, 24 | body: JSON.stringify(values), 25 | }); 26 | } 27 | 28 | export function create(values) { 29 | return request(API_PREFIX, { 30 | method: 'POST', 31 | data: values, 32 | }); 33 | } 34 | -------------------------------------------------------------------------------- /templates/user-dashboard/src/utils/constants.js: -------------------------------------------------------------------------------- 1 | export const PAGE_SIZE = 3; 2 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "build/dist", 4 | "module": "esnext", 5 | "target": "esnext", 6 | "lib": ["esnext", "dom"], 7 | "sourceMap": true, 8 | "baseUrl": ".", 9 | "jsx": "react", 10 | "allowSyntheticDefaultImports": true, 11 | "moduleResolution": "node", 12 | "rootDirs": ["/src", "/test", "/mock", "./typings"], 13 | "forceConsistentCasingInFileNames": true, 14 | "noImplicitReturns": true, 15 | "suppressImplicitAnyIndexErrors": true, 16 | "noUnusedLocals": true, 17 | "allowJs": true, 18 | "resolveJsonModule": true, 19 | "experimentalDecorators": true, 20 | "strict": true, 21 | "paths": { 22 | "@/*": ["./src/*"] 23 | } 24 | }, 25 | "include": [".", "./typings.d"], 26 | "exclude": ["node_modules", "_scripts", "jest", "tslint:latest", "tslint-config-prettier"] 27 | } 28 | -------------------------------------------------------------------------------- /typings.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.css'; 2 | declare module '*.less'; 3 | declare module '*.scss'; 4 | declare module '*.sass'; 5 | declare module '*.svg'; 6 | declare module '*.png'; 7 | declare module '*.jpg'; 8 | declare module '*.jpeg'; 9 | declare module '*.gif'; 10 | declare module '*.bmp'; 11 | declare module '*.tiff'; 12 | declare module 'mockjs'; 13 | 14 | declare module '@umijs/ui-flag'; 15 | --------------------------------------------------------------------------------