├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── assets └── demo.gif ├── bin ├── command │ ├── add.js │ └── addBlock.js └── create-code ├── demo-schema ├── addressBook │ ├── config.js │ ├── dataSchema.js │ └── querySchema.js ├── bus │ ├── config.js │ ├── dataSchema.js │ └── querySchema.js ├── normalList │ ├── config.js │ ├── dataSchema.js │ └── querySchema.js └── tableList │ ├── config.js │ ├── dataSchema.js │ └── querySchema.js ├── lib ├── downloadNpm.js ├── extractTarball.js ├── log.js ├── react-antd-table │ ├── config.js │ ├── genCode │ │ ├── genDetail │ │ │ ├── genDetailItem.js │ │ │ └── index.js │ │ ├── genIndex │ │ │ ├── genIndexColumn.js │ │ │ └── index.js │ │ ├── genModels │ │ │ ├── index.js │ │ │ └── modelUtils.js │ │ ├── genServices │ │ │ ├── index.js │ │ │ └── serviceUtils.js │ │ ├── genTableFilter │ │ │ ├── index.js │ │ │ └── tableFilterUtils.js │ │ ├── genTableForm │ │ │ ├── index.js │ │ │ └── tableFormUtils.js │ │ ├── genUpsert │ │ │ ├── genFormItemUtil.js │ │ │ ├── genUpsertUtil.js │ │ │ └── index.js │ │ ├── genUtils │ │ │ └── index.js │ │ └── index.js │ ├── generator │ │ ├── addressBook.js │ │ ├── banner.js │ │ ├── bus.js │ │ ├── deep.js │ │ ├── normalList.js │ │ ├── place.js │ │ ├── questionnaire.js │ │ ├── resource.js │ │ └── tableList.js │ ├── index.js │ ├── scripts │ │ ├── generateCode.js │ │ ├── index.js │ │ └── utils.js │ └── toCopyStaticFiles │ │ ├── ITableComponents │ │ ├── CheckboxGroup │ │ │ └── index.js │ │ ├── ColumnAction │ │ │ └── index.js │ │ ├── DescriptionList │ │ │ ├── Description.d.ts │ │ │ ├── Description.js │ │ │ ├── DescriptionList.js │ │ │ ├── demo │ │ │ │ ├── basic.md │ │ │ │ └── vertical.md │ │ │ ├── index.d.ts │ │ │ ├── index.en-US.md │ │ │ ├── index.js │ │ │ ├── index.less │ │ │ ├── index.zh-CN.md │ │ │ └── responsive.js │ │ ├── Editor │ │ │ └── index.js │ │ ├── FileUpload │ │ │ ├── index.js │ │ │ └── index.less │ │ ├── FooterToolbar │ │ │ ├── demo │ │ │ │ └── basic.md │ │ │ ├── index.d.ts │ │ │ ├── index.en-US.md │ │ │ ├── index.js │ │ │ ├── index.less │ │ │ └── index.zh-CN.md │ │ ├── ImageViewer │ │ │ ├── index.js │ │ │ └── index.less │ │ ├── RadioGroup │ │ │ └── index.js │ │ ├── SelectGroup │ │ │ └── index.js │ │ ├── StandardTable │ │ │ ├── index.d.ts │ │ │ ├── index.js │ │ │ └── index.less │ │ ├── TableAction │ │ │ ├── index.js │ │ │ └── index.less │ │ ├── TableColumnRender │ │ │ ├── index.js │ │ │ ├── index.less │ │ │ └── utils.js │ │ └── utils │ │ │ └── index.js │ │ ├── components │ │ ├── TableFilter │ │ │ └── index.less │ │ └── TableForm │ │ │ └── index.less │ │ └── upsert │ │ └── index.less ├── scripts │ ├── ice-react-material.js │ └── react-antd-table.js └── utils.js └── package.json /.eslintignore: -------------------------------------------------------------------------------- 1 | # dependencies 2 | **/node_modules 3 | # roadhog-api-doc ignore 4 | /src/utils/request-temp.js 5 | _roadhog-api-doc 6 | 7 | # production 8 | /dist 9 | /.vscode 10 | 11 | # misc 12 | .DS_Store 13 | npm-debug.log* 14 | yarn-error.log 15 | 16 | /coverage 17 | .idea 18 | yarn.lock 19 | package-lock.json 20 | *bak 21 | .vscode 22 | 23 | # visual studio code 24 | .history 25 | *.log 26 | 27 | functions/mock 28 | .temp/** 29 | 30 | # umi 31 | .umi 32 | .umi-production 33 | 34 | # screenshot 35 | screenshot 36 | src/utils/config.js 37 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parser: 'babel-eslint', 3 | extends: ['airbnb', 'prettier', 'plugin:compat/recommended'], 4 | plugins: [ 5 | "react-hooks" 6 | ], 7 | env: { 8 | browser: true, 9 | node: true, 10 | es6: true, 11 | mocha: true, 12 | jest: true, 13 | jasmine: true, 14 | }, 15 | globals: { 16 | APP_TYPE: true, 17 | }, 18 | rules: { 19 | 'react/jsx-filename-extension': [1, { extensions: ['.js'] }], 20 | 'react/jsx-wrap-multilines': 0, 21 | 'react/prop-types': 0, 22 | 'react/forbid-prop-types': 0, 23 | 'react/jsx-one-expression-per-line': 0, 24 | 'import/no-unresolved': [2, { ignore: ['^@/', '^umi/'] }], 25 | 'import/no-extraneous-dependencies': [2, { optionalDependencies: true }], 26 | 'jsx-a11y/no-noninteractive-element-interactions': 0, 27 | 'jsx-a11y/click-events-have-key-events': 0, 28 | 'jsx-a11y/no-static-element-interactions': 0, 29 | 'jsx-a11y/anchor-is-valid': 0, 30 | 'no-underscore-dangle': 0, 31 | "indent": ['error', 2, { "ignoredNodes": ["TemplateLiteral *"] }], 32 | "react-hooks/rules-of-hooks": "error", // 检查 Hook 的规则 33 | // "react-hooks/exhaustive-deps": "warn", // 检查 effect 的依赖 34 | "react/no-array-index-key": 0, 35 | 'no-trailing-spaces': ["error", { "skipBlankLines": true }], 36 | "react/destructuring-assignment": 0, 37 | "comma-spacing": ["error", { "before": false, "after": true }], 38 | "object-curly-spacing": ["error", "always"], 39 | "no-multiple-empty-lines": ["error", { "max": 1, "maxEOF": 1 }], 40 | "global-require": 0 41 | }, 42 | settings: { 43 | polyfills: ['fetch', 'promises', 'url'], 44 | }, 45 | }; 46 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .idea/ 3 | .ipr 4 | .iws 5 | *~ 6 | ~* 7 | *.diff 8 | *.patch 9 | *.bak 10 | .DS_Store 11 | Thumbs.db 12 | .project 13 | .*proj 14 | .svn/ 15 | *.swp 16 | *.swo 17 | *.log 18 | *.log.* 19 | *.json.gzip 20 | node_modules/ 21 | _ant_tpl_data.json 22 | logs/ 23 | run/ 24 | .buildpath 25 | .settings 26 | npm-debug.log 27 | nohup.out 28 | _site 29 | _data 30 | dist 31 | /es 32 | elasticsearch-* 33 | config/base.yaml 34 | /.vscode/ 35 | /coverage 36 | yarn.lock 37 | /.history 38 | app/view/layout/.* 39 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | #### 0.2.1 2 | 3 | * `create-code add`新增阿里飞冰react物料源当前487个区块的快捷添加,具体可查看`create-code --help`. 4 | 5 | #### 0.2.0 6 | 7 | * 新增属性`onlyTableListMode`仅生成列表功能. 8 | * 新增属性`showAutoIncrementIndex`支持将`primary`为`true`的主键字段设置为递增渲染. 9 | * 优化多选时列表展示,默认不显示`"选中0项"`,选中元素后展示. 10 | * 优化`dataSchema`中`disabled`的属性配置,若值为`true`则在编辑模式下将渲染为文本展示. 11 | * 优化生成代码中都包含重复的`utils.js`的功能,将其移入到`components/ITableComponents`中共用. 12 | * 修复配置`base route`之后跳转`404`的bug. 13 | * 修复不显示新增按钮时,按钮被遮挡的bug. 14 | 15 | #### 0.1.4 16 | 17 | * 支持generatePath配置,可以指定代码生成的目录 18 | 19 | #### 0.1.3 20 | 21 | * 修复eslint获取路径问题 22 | * 新增项目目录src文件夹存在判断 23 | 24 | #### 0.1.2 25 | 26 | * 修复代码生成路径错误的问题 27 | 28 | #### 0.1.1 29 | 30 | * 修复eslint putout文件执行路径问题 31 | 32 | #### 0.1.0 33 | 34 | * init project 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017-2018 Alipay 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 13 | all 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 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # create-code 2 | 3 | ### 基于umi、antd自动生成CRUD代码的工具. 4 | 5 | 基本介绍及实现思路见[Babel 在提升前端效率的实践](https://juejin.im/post/5ce2aaea6fb9a07eac05a608) 6 | 7 | ## 特性 8 | 9 | ### react-antd-table 10 | 11 | * 通过json文件配置在本地项目生成基础增删改查代码. 12 | * 已应用于生产线,通用的增删改查功能相比普通开发至少提效8倍以上. 13 | * 数据新增、编辑支持模态框及新页面(包含分组)两种展现,更多配置参考[基础配置api](https://www.yuque.com/ssisl/gabiv1/fweu9a). 14 | * 表单基础支持业务场景常用的15种控件,详细查看[dataSchema配置api](https://www.yuque.com/ssisl/gabiv1/tv69bu). 15 | * 仅包含初始化代码生成,所以更便于根据自己的业务场景扩展维护. 16 | * 组件编写使用React Hooks语法. 17 | 18 | ### ice-react-materials 19 | 20 | * 支持阿里飞冰react物料源当前487个区块的快捷添加. 21 | 22 | ### 安装 23 | 24 | ```bash 25 | $ npm install create-code -g 26 | ``` 27 | 28 | ### 在项目中使用 29 | 30 | ```bash 31 | # 在项目根目录下创建schema文件夹,创建测试`normalList`文件夹。在文件夹下添加配置文件`config.js`、`dataScheme.js`、`querySchema.js`。也可以从本项目`demo-schema`中拷贝测试数据。 32 | $ create-code add 33 | # 选择 `react-antd-table` 类型 34 | # 输入要生成的代码的配置文件路径,例如: `normalList`、 `../bus` 35 | # 配置generatePath路径,默认/**/src/pages,可自定义路径,值为绝对路径 36 | # 开始生成代码 37 | ``` 38 | 39 | ### 演示 40 | 41 | ![](assets/demo.gif) 42 | 43 | ### 配置API 44 | 45 | * [基础配置](https://www.yuque.com/ssisl/gabiv1/fweu9a) 46 | * [筛选项配置](https://www.yuque.com/ssisl/gabiv1/wi2rga) 47 | * [表单项配置](https://www.yuque.com/ssisl/gabiv1/tv69bu) 48 | 49 | ### 项目依赖 50 | 51 | > 因本工具会在当前项目中生成代码,需要项目提供基础依赖。 52 | 53 | - `react` 54 | - `antd` 55 | - `classnames` 56 | - `querystring` 57 | - `lodash` 58 | - `dva` 59 | - `umi` 60 | 61 | **lodash、classnames可视本地项目需要添加** 62 | 63 | > 其它依赖会根据配置项需要添加。 64 | 65 | - `braft-editor`] 富文本内置了 预览、图片上传、颜色选择插件,详细参考[https://braft.margox.cn/demos/antd-form](https://braft.margox.cn/demos/antd-form) 66 | - `braft-extensions` 富文本颜色选择使用sketch-color 67 | - `antd-img-crop` 图片裁剪 68 | - `moment` 日期格式化转化 69 | 70 | ### 常见问题: 71 | 1. 格式化代码未完全修复,需要手动修复! 72 | eslint格式化失败,一般是因为所需依赖未找到。 73 | 2. 文件目录: (**/src)不存在,请确认文件路径是否正确. 74 | 请确认配置文件是否在根目录下的文件夹下。如:`schema/test` 75 | 76 | 77 | ### [CHANGELOG](CHANGELOG.md) 78 | 79 | 80 | -------------------------------------------------------------------------------- /assets/demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/acpplife/create-code/7bc1a69ff949dd01dad08832b4d6fc0ec9515b62/assets/demo.gif -------------------------------------------------------------------------------- /bin/command/add.js: -------------------------------------------------------------------------------- 1 | const inquirer = require('inquirer') 2 | const doWithTable = require('../../lib/scripts/react-antd-table') 3 | const doWithIceReactMaterial = require('../../lib/scripts/ice-react-material') 4 | 5 | module.exports = function main () { 6 | const questions = [ 7 | { 8 | type: 'list', 9 | name: 'type', 10 | message: 'which type of block do you want to add ?', 11 | choices: [ 12 | { 13 | name: 'react-antd-table (基于antd、umi的列表CRUD)', 14 | value: 'react-antd-table', 15 | }, 16 | { 17 | name: 'ice-react-materials (飞冰react物料源,包含较多常用的组件)', 18 | value: 'ice-react-materials', 19 | }, 20 | { 21 | name: 'more (more blocks is on the way!)', 22 | value: 'more', 23 | disabled: true 24 | }, 25 | ] 26 | } 27 | ] 28 | 29 | inquirer.prompt(questions) 30 | .then((nt) => { 31 | const { type } = nt 32 | 33 | switch(type) { 34 | case 'react-antd-table': 35 | doWithTable() 36 | break 37 | case 'ice-react-materials': 38 | doWithIceReactMaterial() 39 | break 40 | 41 | default: 42 | } 43 | }) 44 | } -------------------------------------------------------------------------------- /bin/command/addBlock.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const { tmpdir } = require('os'); 3 | const fse = require('fs-extra'); 4 | const camelCase = require('camelcase'); 5 | const downloadNpm = require('../../lib/downloadNpm'); 6 | const log = require('../../lib/log'); 7 | 8 | async function addBlock(options = {}) { 9 | const { npmName, destDir, tempDir } = options; 10 | let { name: blockDirName } = options; 11 | 12 | // download npm block 13 | if (!blockDirName) { 14 | // @icedesign/example-block | example-block 15 | const name = npmName.split('/')[1] || npmName.split('/')[0]; 16 | blockDirName = camelCase(name, { pascalCase: true }); 17 | } 18 | const blockDirPath = path.resolve(destDir, blockDirName); 19 | 20 | return fse.pathExists(blockDirPath) 21 | .then((exists) => { 22 | if (exists) { 23 | return Promise.reject(new Error(`${blockDirPath} already exists, you can use cli -n option to custom block directory name`)); 24 | } 25 | return Promise.resolve(); 26 | }) 27 | .then(() => downloadNpm({ 28 | npmName, 29 | destDir: tempDir, 30 | })) 31 | .then(() => { 32 | log.info('create block directory……'); 33 | return fse.mkdirp(blockDirPath); 34 | }) 35 | .then(() => { 36 | log.info('copy block src files to dest blockDir'); 37 | return fse.copy(path.join(tempDir, 'src'), blockDirPath, { 38 | overwrite: false, 39 | errorOnExist: true, 40 | }); 41 | }) 42 | .then(() => blockDirPath); 43 | } 44 | 45 | module.exports = (options) => { 46 | const destDir = process.cwd(); 47 | const tempDir = path.resolve(tmpdir(), 'iceworks_temp'); 48 | options.destDir = destDir; 49 | options.tempDir = tempDir; 50 | 51 | return fse.ensureDir(tempDir).then(() => addBlock(options)).catch((err) => { 52 | fse.removeSync(tempDir); 53 | log.error(`add block error: ${err.message}`); 54 | console.error(err); 55 | process.exit(1); 56 | }).then((blockDirPath) => { 57 | fse.removeSync(tempDir); 58 | log.info('add block success, you can import and use block in your page code', blockDirPath); 59 | }); 60 | }; 61 | 62 | -------------------------------------------------------------------------------- /bin/create-code: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const program = require('commander'); 4 | const chalk = require('chalk'); 5 | const utils = require('../lib/utils') 6 | const pkg = require('../package.json'); 7 | 8 | const { log } = console 9 | 10 | // commander passes the Command object itself as options, 11 | // extract only actual options into a fresh object. 12 | function cleanArgs(cmd) { 13 | const args = {}; 14 | if (cmd) { 15 | cmd.options.forEach((o) => { 16 | const key = utils.camelize(o.long.replace(/^--/, '')); 17 | // if an option is not present and Command has a method with the same name 18 | // it should not be copied 19 | if (typeof cmd[key] !== 'function' && typeof cmd[key] !== 'undefined') { 20 | args[key] = cmd[key]; 21 | } 22 | }); 23 | if (cmd.parent && cmd.parent.rawArgs) { 24 | // eslint-disable-next-line prefer-destructuring 25 | args.command = cmd.parent.rawArgs[2]; 26 | } 27 | } 28 | return args; 29 | } 30 | 31 | program.version(pkg.version) 32 | .option('add', 'add a new block.') 33 | .option('a', 'add a new block.') 34 | 35 | program 36 | .command('addBlock ') 37 | .description('add block to current directory') 38 | .option( 39 | '-n, --name ', 40 | 'Specify the block directory name like CustomBlock' 41 | ) 42 | .action((npmName, cmd) => { 43 | const options = cleanArgs(cmd); 44 | options.npmName = npmName; 45 | // eslint-disable-next-line global-require 46 | require('./command/addBlock')(options); 47 | }); 48 | 49 | // add some useful info on help 50 | program.on('--help', () => { 51 | log(''); 52 | log('Examples:'); 53 | log() 54 | log(' $ create-code add') 55 | log(' $ create-code add @icedesign/user-landing-block'); 56 | log(' $ create-code add @icedesign/user-landing-block -n CustomBlock'); 57 | log() 58 | log(`Run ${chalk.cyan('create-code --help')} for detailed usage of given command.`); 59 | }); 60 | 61 | program.parse(process.argv); 62 | 63 | if (program.add || program.a) { 64 | require('./command/add')(process.argv); 65 | } 66 | 67 | if (!process.argv.slice(2).length) { 68 | program.outputHelp(); 69 | } 70 | 71 | -------------------------------------------------------------------------------- /demo-schema/addressBook/config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | namespace: 'addressBook', 3 | showExport: false, // 是否显示导出 4 | showCreate: true, // 是否显示创建 5 | showDetail: true, // 是否显示查看 6 | showUpdate: true, // 是否显示修改 7 | showDelete: true, // 是否显示删除 8 | showBatchDelete: true, // 是否显示批量删除: multiSelection 需为 true 9 | multiSelection: true, // 是否允许多选 10 | defaultDateFormat: 'YYYY-MM-DD HH:mm:ss', // 日期格式 11 | newRouterMode: false, 12 | baseRouterPath: '/content', 13 | upload: { // 上传相关配置 14 | uploadUrl: '/manage/pic/upload', 15 | imageSizeLimit: 1500, // 默认的图片大小限制, 单位KB 16 | 17 | fileSizeLimit: 10240, // 默认的文件大小限制, 单位KB 18 | }, 19 | pagination: { 20 | pageSize: 10, 21 | showSizeChanger: false, 22 | pageSizeOptions: ['10', '20', '50', '100'], 23 | showQuickJumper: false, 24 | showTotal: true, 25 | }, 26 | dictionary: [ 27 | { 28 | key: 'tag', 29 | url: '/manage/addressbook/gettags' 30 | }, 31 | ] 32 | } 33 | -------------------------------------------------------------------------------- /demo-schema/addressBook/dataSchema.js: -------------------------------------------------------------------------------- 1 | module.exports = [ 2 | { 3 | key: 'id', 4 | title: '编号', 5 | showInForm: false, 6 | primary: true 7 | }, 8 | { 9 | key: 'name', 10 | title: '姓名', 11 | placeholder: '请输入姓名', 12 | validator: [{ required: true, message: '请输入姓名' }], 13 | }, 14 | { 15 | key: 'mobile', 16 | title: '手机号', 17 | placeholder: '请输入手机号', 18 | validator: [{ required: true, message: '请输入手机号' }], 19 | }, 20 | { 21 | key: 'tag', 22 | title: '类型', 23 | showType: 'multiSelect', 24 | placeholder: '请选择类型', 25 | options: 'tag', 26 | validator: [{ required: true, message: '请选择类型' }], 27 | }, 28 | { 29 | key: 'gmtCreate', 30 | title: '创建时间', 31 | showInForm: false 32 | }, 33 | { 34 | key: 'gmtModify', 35 | title: '修改时间', 36 | showInTable: false, 37 | showInForm: false 38 | }, 39 | { 40 | key: '__actions', 41 | title: '操作', 42 | width: 130, 43 | actions: [ 44 | { 45 | name: '查看', 46 | type: 'detail' 47 | }, 48 | { 49 | name: '编辑', 50 | type: 'update', 51 | }, 52 | { type: 'newLine' }, 53 | { 54 | name: '删除', 55 | type: 'delete', 56 | }, 57 | ] 58 | } 59 | ] 60 | -------------------------------------------------------------------------------- /demo-schema/addressBook/querySchema.js: -------------------------------------------------------------------------------- 1 | module.exports = [ 2 | { 3 | key: 'name', 4 | title: '姓名', 5 | placeholder: '请输入姓名', 6 | }, 7 | { 8 | key: 'mobile', 9 | title: '手机号', 10 | placeholder: '请输入手机号', 11 | }, 12 | { 13 | key: 'tag', 14 | title: '类型', 15 | options: 'tag', 16 | placeholder: '请选择类型', 17 | showType: 'multiSelect', 18 | }, 19 | ]; 20 | -------------------------------------------------------------------------------- /demo-schema/bus/config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | namespace: 'bus', 3 | showExport: true, // 是否显示导出 4 | showCreate: true, // 是否显示创建 5 | showDetail: true, // 是否显示查看 6 | showUpdate: true, // 是否显示修改 7 | showDelete: true, // 是否显示删除 8 | showBatchDelete: true, // 是否显示批量删除: multiSelection 需为 true 9 | multiSelection: false, // 是否允许多选 10 | defaultDateFormat: 'YYYY-MM-DD', // 日期格式 11 | newRouterMode: true, 12 | singleDataMode: true, 13 | } 14 | -------------------------------------------------------------------------------- /demo-schema/bus/dataSchema.js: -------------------------------------------------------------------------------- 1 | module.exports = [ 2 | { 3 | key: 'key', 4 | title: '编号', 5 | placeholder: '请输入...', 6 | validator: [{ required: true, message: '请输入id' }], 7 | showInForm: false, 8 | primary: true 9 | }, 10 | { 11 | key: 'publishDate', 12 | title: '发布时间', 13 | showType: 'datePicker', 14 | placeholder: '请选择发布时间', 15 | validator: [{ required: true, message: '请选择发布时间' }], 16 | }, 17 | { 18 | key: 'editor', 19 | title: '内容', 20 | showType: 'editor', 21 | placeholder: '请填写内容', 22 | validator: [{ required: true }], 23 | }, 24 | ] 25 | -------------------------------------------------------------------------------- /demo-schema/bus/querySchema.js: -------------------------------------------------------------------------------- 1 | module.exports = [ 2 | 3 | ]; 4 | -------------------------------------------------------------------------------- /demo-schema/normalList/config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | namespace: 'normalList', 3 | showExport: false, // 是否显示导出 4 | showCreate: true, // 是否显示创建 5 | showDetail: true, // 是否显示查看 6 | showUpdate: true, // 是否显示修改 7 | showDelete: true, // 是否显示删除 8 | showBatchDelete: true, // 是否显示批量删除: multiSelection 需为 true 9 | multiSelection: true, // 是否允许多选 10 | defaultDateFormat: 'YYYY-MM-DD HH:mm:ss', // 日期格式 11 | newRouterMode: true, 12 | upload: { // 上传相关配置 13 | uploadUrl: '/manage/pic/upload', 14 | imageSizeLimit: 1500, // 默认的图片大小限制, 单位KB 15 | 16 | fileSizeLimit: 10240, // 默认的文件大小限制, 单位KB 17 | }, 18 | pagination: { 19 | pageSize: 10, 20 | showSizeChanger: false, 21 | pageSizeOptions: ['10', '20', '50', '100'], 22 | showQuickJumper: false, 23 | showTotal: true, 24 | }, 25 | } 26 | -------------------------------------------------------------------------------- /demo-schema/normalList/dataSchema.js: -------------------------------------------------------------------------------- 1 | module.exports = [ 2 | { 3 | key: 'id', 4 | title: '编号', 5 | showInForm: false, 6 | primary: true 7 | }, 8 | { 9 | key: 'title', 10 | title: '标题', 11 | placeholder: '请输入标题', 12 | validator: [{ required: true, message: '请输入标题' }], 13 | }, 14 | { 15 | key: 'subTitle', 16 | title: '副标题', 17 | placeholder: '请输入副标题', 18 | validator: [{ required: true, message: '请输入副标题' }], 19 | }, 20 | { 21 | key: 'commonUrl', 22 | title: '图片', 23 | placeholder: '请上传图片', 24 | showType: 'image', 25 | max: 1, 26 | validator: [{ required: true, message: '请上传一张图片' }], 27 | }, 28 | { 29 | key: 'outerUrl', 30 | title: '跳转地址', 31 | showType: 'link', 32 | placeholder: '请输入跳转地址', 33 | }, 34 | { 35 | key: 'content', 36 | title: '内容', 37 | showType: 'editor', 38 | placeholder: '请输入内容', 39 | showInTable: false 40 | }, 41 | { 42 | key: 'gmtCreate', 43 | title: '创建时间', 44 | showInForm: false 45 | }, 46 | { 47 | key: 'gmtModify', 48 | title: '修改时间', 49 | showInTable: false, 50 | showInForm: false 51 | }, 52 | { 53 | key: '__actions', 54 | title: '操作', 55 | width: 130, 56 | actions: [ 57 | { 58 | name: '查看', 59 | type: 'detail' 60 | }, 61 | { 62 | name: '编辑', 63 | type: 'update', 64 | }, 65 | { type: 'newLine' }, 66 | { 67 | name: '删除', 68 | type: 'delete', 69 | }, 70 | ] 71 | } 72 | ] 73 | -------------------------------------------------------------------------------- /demo-schema/normalList/querySchema.js: -------------------------------------------------------------------------------- 1 | module.exports = [ 2 | { 3 | key: 'keyword', 4 | title: '关键字', 5 | placeholder: '请输入标题、副标题、内容', 6 | }, 7 | { 8 | key: 'gmtCreate', 9 | title: '创建时间', 10 | placeholderBegin: '开始日期', 11 | placeholderEnd: '结束日期', 12 | format: 'YYYY-MM-DD', 13 | showType: 'rangePicker' 14 | }, 15 | ]; 16 | -------------------------------------------------------------------------------- /demo-schema/tableList/config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | namespace: 'tableList', 3 | showExport: true, // 是否显示导出 4 | showCreate: true, // 是否显示创建 5 | showDetail: true, // 是否显示查看 6 | showUpdate: true, // 是否显示修改 7 | showDelete: true, // 是否显示删除 8 | showBatchDelete: true, // 是否显示批量删除: multiSelection 需为 true 9 | multiSelection: false, // 是否允许多选 10 | defaultDateFormat: 'YYYY-MM-DD', // 日期格式 11 | newRouterMode: true, 12 | upload: { // 上传相关配置 13 | uploadUrl: '/manage/pic/upload', 14 | imageSizeLimit: 1500, // 默认的图片大小限制, 单位KB 15 | 16 | fileSizeLimit: 10240, // 默认的文件大小限制, 单位KB 17 | }, 18 | pagination: { 19 | pageSize: 10, 20 | showSizeChanger: false, 21 | pageSizeOptions: ['10', '20', '50', '100'], 22 | showQuickJumper: false, 23 | showTotal: true, 24 | }, 25 | dictionary: [ 26 | { 27 | key: 'dicData', 28 | url: '/api/getDicData' 29 | }, 30 | { 31 | key: 'treeData', 32 | url: '/api/getTreeData' 33 | }, 34 | ] 35 | } 36 | -------------------------------------------------------------------------------- /demo-schema/tableList/dataSchema.js: -------------------------------------------------------------------------------- 1 | const status = ['关闭', '运行中', '已上线', '异常']; 2 | 3 | module.exports = [ 4 | { 5 | key: 'key', 6 | title: '编号', 7 | placeholder: '请输入...', 8 | validator: [{ required: true, message: '请输入id' }], 9 | // defaultValue: 1, 10 | showInForm: false, 11 | primary: true 12 | }, 13 | { 14 | key: 'name', 15 | title: '输入框', 16 | placeholder: '请输入...', 17 | validator: [{ required: true, message: '请输入' }], 18 | // defaultValue: '标题', 19 | group: '基础信息' 20 | }, 21 | { 22 | key: 'textarea', 23 | title: '多行输入框', 24 | placeholder: '请输入...', 25 | validator: [{ required: true, message: '请输入' }], 26 | // defaultValue: '多行输入框', 27 | showType: 'textarea' 28 | }, 29 | { 30 | key: 'image', 31 | title: '图片', 32 | showType: 'image', 33 | max: 5, 34 | // defaultValue: 'https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png', 35 | placeholder: '请上传图片', 36 | validator: [{ required: true }], 37 | group: '其它信息' 38 | }, 39 | { 40 | key: 'editorArea', 41 | title: '富文本', 42 | showType: 'editor', 43 | placeholder: '请填写内容', 44 | validator: [{ required: true }], 45 | group: '其它信息' 46 | }, 47 | { 48 | key: 'editorArea2', 49 | title: '富文本2', 50 | showType: 'editor', 51 | placeholder: '请填写富文本2内容', 52 | group: '其它信息' 53 | }, 54 | { 55 | key: 'file', 56 | title: '文件', 57 | showType: 'file', 58 | accept: '.pdf', 59 | sizeLimit: 20480, 60 | sorter: true, 61 | // defaultValue: 'https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png', 62 | placeholder: '请上传pdf格式文件, 大小不要超过20M', 63 | validator: [{ required: true, message: '至少选择一个文件' }], 64 | group: '其它信息' 65 | }, 66 | { 67 | key: 'inputNumber', 68 | title: '数字', 69 | showType: 'inputNumber', 70 | placeholder: '请输入数字', 71 | // defaultValue: 23, 72 | min: 10, 73 | validator: [{ required: true, message: '请输入数字' }] 74 | }, 75 | { 76 | key: 'select', 77 | title: '下拉框', 78 | options: 'dicData', 79 | placeholder: '请选择...', 80 | showType: 'select', 81 | filters: status.map((item, i) => ({ value: i, text: item })), 82 | validator: [{ required: true }], 83 | // defaultValue: ["0"] 84 | }, 85 | { 86 | key: 'multiSelect', 87 | title: '多选下拉框', 88 | options: [{ key: "0", value: '关闭' }, { key: "1", value: '运行中' }], 89 | placeholder: '请选择...', 90 | showType: 'multiSelect', 91 | validator: [{ required: true }], 92 | // defaultValue: ["0", "1"], 93 | showInTable: false 94 | }, 95 | { 96 | key: 'radio', 97 | title: '单选框', 98 | options: [{ key: "0", value: '关闭' }, { key: "1", value: '运行中' }], 99 | showType: 'radio', 100 | // defaultValue: "0", 101 | showInTable: false, 102 | validator: [{ required: true }], 103 | group: '其它信息' 104 | }, 105 | { 106 | key: 'checkbox', 107 | title: '复选框', 108 | options: [{ key: "0", value: '关闭' }, { key: "1", value: '运行中' }], 109 | showType: 'checkbox', 110 | // defaultValue: ["0", "1"], 111 | validator: [{ required: true }], 112 | group: '其它信息' 113 | }, 114 | { 115 | key: 'datePicker', 116 | title: '日期选择', 117 | placeholder: '请选择日期', 118 | showType: 'datePicker', 119 | format: 'YYYY-MM-DD', 120 | validator: [{ required: true }], 121 | // defaultValue: "2019-04-20", 122 | group: '基础信息' 123 | }, 124 | { 125 | key: 'rangePicker', 126 | title: '日期范围选择', 127 | placeholderBegin: '开始日期', 128 | placeholderEnd: '结束日期', 129 | // defaultValueBegin: "2019-02-20", 130 | // defaultValueEnd: "2019-04-28", 131 | format: 'YYYY-MM-DD', 132 | showType: 'rangePicker', 133 | showInTable: false, 134 | validator: [{ required: true }], 135 | group: '基础信息' 136 | }, 137 | { 138 | key: 'cascader', 139 | title: '级联选择', 140 | placeholder: '请选择城市', 141 | options: 'treeData', 142 | showType: 'cascader', 143 | validator: [{ required: true }], 144 | // defaultValue: ['zhejiang', 'hangzhou', 'xihu'] 145 | }, 146 | { 147 | key: '__actions', 148 | title: '操作', 149 | width: 130, 150 | actions: [ 151 | { 152 | name: '查看', 153 | type: 'detail' 154 | }, 155 | { 156 | name: '编辑', 157 | type: 'update', 158 | }, 159 | { type: 'newLine' }, 160 | { 161 | name: '删除', 162 | type: 'delete', 163 | }, 164 | ] 165 | } 166 | ] 167 | -------------------------------------------------------------------------------- /demo-schema/tableList/querySchema.js: -------------------------------------------------------------------------------- 1 | module.exports = [ 2 | { 3 | key: 'name', 4 | title: '默认字段', 5 | placeholder: '请输入默认字段', 6 | showInSimpleMode: false 7 | // defaultValue: '我是默认值' 8 | }, 9 | { 10 | key: 'nameWithAddon', 11 | title: '包含标签', 12 | placeholder: '请输入值', 13 | addonBefore: "user", 14 | addonAfter: '.me', 15 | }, 16 | { 17 | key: 'number', 18 | title: '数字', 19 | placeholder: '请输入数字', 20 | showType: 'inputNumber', 21 | // defaultValue: 23 22 | }, 23 | { 24 | key: 'select', 25 | title: '下拉框', 26 | options: 'dicData', 27 | placeholder: '请选择...', 28 | showType: 'select', 29 | // defaultValue: ["0"] 30 | }, 31 | { 32 | key: 'multiSelect', 33 | title: '多选下拉框', 34 | options: [{ key: "0", value: '关闭' }, { key: "1", value: '运行中' }], 35 | placeholder: '请选择...', 36 | showType: 'multiSelect', 37 | // defaultValue: ["0", "1"] 38 | }, 39 | { 40 | key: 'radio', 41 | title: '单选框', 42 | options: [{ key: "0", value: '关闭' }, { key: "1", value: '运行中' }], 43 | showType: 'radio', 44 | // defaultValue: "0" 45 | }, 46 | { 47 | key: 'checkbox', 48 | title: '复选框', 49 | options: [{ key: "0", value: '关闭' }, { key: "1", value: '运行中' }], 50 | showType: 'checkbox', 51 | // defaultValue: ["0", "1"] 52 | }, 53 | { 54 | key: 'datePicker', 55 | title: '日期选择', 56 | placeholder: '请选择日期', 57 | showType: 'datePicker', 58 | format: 'YYYY-MM-DD', 59 | // defaultValue: "2019-04-20" 60 | }, 61 | { 62 | key: 'rangePicker', 63 | title: '日期范围选择', 64 | placeholderBegin: '开始日期', 65 | placeholderEnd: '结束日期', 66 | // defaultValueBegin: "2019-02-20", 67 | // defaultValueEnd: "2019-04-28", 68 | format: 'YYYY-MM-DD', 69 | showType: 'rangePicker' 70 | }, 71 | { 72 | key: 'cascader', 73 | title: '级联选择', 74 | placeholder: '请选择城市', 75 | options: 'treeData', 76 | showType: 'cascader', 77 | // defaultValue: ['zhejiang', 'hangzhou', 'xihu'] 78 | }, 79 | ]; 80 | -------------------------------------------------------------------------------- /lib/downloadNpm.js: -------------------------------------------------------------------------------- 1 | const { getNpmTarball } = require('ice-npm-utils'); 2 | const extractTarball = require('./extractTarball'); 3 | const log = require('./log'); 4 | 5 | module.exports = ({ npmName, destDir }) => { 6 | return getNpmTarball(npmName, 'latest') 7 | .then((url) => { 8 | log.verbose('getNpmTarball', url); 9 | return extractTarball(url, destDir); 10 | }); 11 | }; 12 | 13 | -------------------------------------------------------------------------------- /lib/extractTarball.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const mkdirp = require('mkdirp'); 3 | const path = require('path'); 4 | const request = require('request'); 5 | const progress = require('request-progress'); 6 | const zlib = require('zlib'); 7 | const tar = require('tar'); 8 | 9 | /** 10 | * Download tarbar content to the specified directory 11 | * @param {string} tarballURL tarball url 12 | * @param {string} destDir target directory 13 | */ 14 | module.exports = function extractTarball( 15 | tarballURL, 16 | destDir, 17 | progressFunc = () => {} 18 | ) { 19 | return new Promise((resolve, reject) => { 20 | const allFiles = []; 21 | const allWriteStream = []; 22 | const directoryCollector = []; 23 | 24 | progress( 25 | request({ 26 | url: tarballURL, 27 | timeout: 10000, 28 | }) 29 | ) 30 | .on('progress', (state) => { 31 | progressFunc(state); 32 | }) 33 | .on('error', (error = {}) => { 34 | error.name = 'download-tarball-error'; 35 | error.data = { 36 | url: tarballURL, 37 | }; 38 | console.error(error); 39 | reject(error); 40 | }) 41 | .pipe(zlib.Unzip()) 42 | .on('error', (error) => { 43 | reject(error); 44 | }) 45 | .pipe(tar.Parse()) 46 | .on('entry', (entry) => { 47 | const realPath = entry.path.replace(/^package\//, ''); 48 | 49 | let filename = path.basename(realPath); 50 | 51 | // _gitignore -> .gitignore 52 | // Special logic:_package.json -> package.json 53 | if (filename === '_package.json') { 54 | filename = filename.replace(/^_/, ''); 55 | } else { 56 | filename = filename.replace(/^_/, '.'); 57 | } 58 | 59 | const destPath = path.join(destDir, path.dirname(realPath), filename); 60 | 61 | const needCreateDir = path.dirname(destPath); 62 | if (!directoryCollector.includes(needCreateDir)) { 63 | directoryCollector.push(needCreateDir); 64 | mkdirp.sync(path.dirname(destPath)); 65 | } 66 | 67 | allFiles.push(destPath); 68 | const writeStream = new Promise((streamResolve) => { 69 | entry 70 | .pipe(fs.createWriteStream(destPath)) 71 | .on('finish', () => streamResolve()); 72 | }); 73 | allWriteStream.push(writeStream); 74 | }) 75 | .on('end', () => { 76 | progressFunc({ 77 | percent: 1, 78 | }); 79 | Promise.all(allWriteStream) 80 | .then(() => resolve(allFiles)) 81 | .catch((error) => { 82 | reject(error); 83 | }); 84 | }); 85 | }); 86 | }; 87 | -------------------------------------------------------------------------------- /lib/log.js: -------------------------------------------------------------------------------- 1 | const npmlog = require('npmlog'); 2 | 3 | const envs = ['verbose', 'info', 'error', 'warn']; 4 | const logLevel = 5 | envs.indexOf(process.env.LOG_LEVEL) !== -1 ? process.env.LOG_LEVEL : 'info'; 6 | 7 | npmlog.level = logLevel; 8 | 9 | module.exports = npmlog; 10 | -------------------------------------------------------------------------------- /lib/react-antd-table/config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | // 脚本执行根路径 3 | rootPath: __dirname, 4 | } -------------------------------------------------------------------------------- /lib/react-antd-table/genCode/genDetail/genDetailItem.js: -------------------------------------------------------------------------------- 1 | const groupBy = require('lodash/groupBy') 2 | const t = require('@babel/types') 3 | const generate = require('@babel/generator').default 4 | const utils = require('../genUtils') 5 | 6 | const { renderInForm, unescapeToChinese, getBlankLine, getInjectKey } = utils 7 | 8 | const blankLine = getBlankLine() 9 | 10 | module.exports = (DataSchema) => { 11 | // 若数据中包含富文本编辑器,则自动分组,每个富文本单独占一整行展示。 12 | const groupFilter = (item) => item.group || item.showType === 'editor' 13 | const hasGroup = DataSchema.some(item => groupFilter(item)) 14 | 15 | const render = (rendererData, title, options = {}) => { 16 | const childArray = [blankLine] 17 | const { col } = options 18 | 19 | rendererData.filter(item => renderInForm(item, 'detail')).forEach((item) => { 20 | const injectKey = getInjectKey(item.key) 21 | 22 | const attrArray = [ 23 | t.jsxAttribute(t.jsxIdentifier('value'), t.jSXExpressionContainer(t.memberExpression(t.identifier('current'), t.identifier(item.key)))), 24 | t.jsxAttribute(t.jsxIdentifier('mode'), t.stringLiteral('detail')) 25 | ] 26 | if (item.showType) { 27 | attrArray.push(t.jsxAttribute(t.jsxIdentifier('showType'), t.stringLiteral(item.showType))) 28 | } 29 | if (item.format) { 30 | attrArray.push(t.jsxAttribute(t.jsxIdentifier('format'), t.stringLiteral(item.format))) 31 | } 32 | if (item.options) { 33 | attrArray.push(t.jsxAttribute(t.jsxIdentifier('options'), t.jSXExpressionContainer( 34 | t.identifier(injectKey) 35 | ))) 36 | } 37 | childArray.push( 38 | t.jsxElement( 39 | t.jsxOpeningElement(t.jsxIdentifier('Description'), [ 40 | t.jsxAttribute(t.jsxIdentifier('term'), t.stringLiteral(item.title)), 41 | ]), 42 | t.jsxClosingElement(t.jsxIdentifier('Description')), 43 | [ 44 | blankLine, 45 | t.JSXElement(t.jsxOpeningElement(t.jsxIdentifier('TableColumnRender'), attrArray, true), 46 | null, 47 | [], 48 | true 49 | ), 50 | blankLine 51 | ] 52 | ), 53 | blankLine 54 | ) 55 | }) 56 | 57 | const ast = t.jsxElement( 58 | t.jsxOpeningElement(t.jsxIdentifier('DescriptionList'), [ 59 | t.jsxAttribute(t.jsxIdentifier('size'), t.stringLiteral('large')), 60 | t.jsxAttribute(t.jsxIdentifier('title'), t.stringLiteral(title && title !== 'undefined' ? title : '')), 61 | ...(col ? [ 62 | t.jsxAttribute(t.jsxIdentifier('col'), t.jsxExpressionContainer(t.numericLiteral(col))) 63 | ] : []) 64 | ]), 65 | t.jsxClosingElement(t.jsxIdentifier('DescriptionList')), 66 | [ 67 | blankLine, 68 | ...childArray, 69 | blankLine, 70 | ] 71 | ) 72 | 73 | return ast 74 | } 75 | let ast; 76 | 77 | if (hasGroup) { 78 | /** 79 | * 展示顺序优先级为 80 | * 1. 普通未分组数据(非富文本编辑器) 81 | * 2. 分组数据(非富文本编辑器) 82 | * 3. 富文本编辑器(自成分组) 83 | */ 84 | // 未分组普通数据 85 | const otherData = DataSchema.filter(item => !item.group && item.showType !== 'editor') 86 | // 分组数据 87 | const filteredData = DataSchema.filter(item => item.group && item.showType !== 'editor') 88 | // 未分组富文本数据 89 | const editorData = DataSchema.filter(item => item.showType === 'editor') 90 | 91 | const groupedData = groupBy(filteredData, 'group') 92 | const keys = Object.keys(groupedData) 93 | const jsxBodyArr = [blankLine] 94 | 95 | if (otherData && otherData.length) { 96 | jsxBodyArr.push(render(otherData), blankLine) 97 | } 98 | keys.forEach(item => { 99 | jsxBodyArr.push(render(groupedData[item], item), blankLine) 100 | }) 101 | if (editorData && editorData.length) { 102 | jsxBodyArr.push(render(editorData, '', { col: 1 })) 103 | } 104 | jsxBodyArr.push(blankLine) 105 | 106 | ast = t.jsxFragment(t.jsxOpeningFragment(), t.jsxClosingFragment(), jsxBodyArr) 107 | } else { 108 | ast = render(DataSchema) 109 | } 110 | 111 | const result = unescapeToChinese(generate(ast).code) 112 | 113 | return result 114 | } -------------------------------------------------------------------------------- /lib/react-antd-table/genCode/genDetail/index.js: -------------------------------------------------------------------------------- 1 | const genDetailItem = require('./genDetailItem') 2 | const genUtils = require('../genUtils') 3 | 4 | const { getDicArray, getPrimaryKey, injectVariables } = genUtils 5 | 6 | module.exports = (tableConfig) => { 7 | 8 | const { 9 | Config: { 10 | namespace, 11 | dictionary, 12 | singleDataMode, 13 | baseRouterPath 14 | }, 15 | DataSchema 16 | } = tableConfig 17 | const primaryKey = getPrimaryKey(DataSchema) 18 | const dicArray = getDicArray(dictionary) 19 | const injectValues = injectVariables(DataSchema) 20 | 21 | return ` 22 | import React, { useEffect, Fragment } from 'react'; 23 | import { connect } from 'dva'; 24 | import { Card, Button } from 'antd'; 25 | import router from 'umi/router' 26 | import FooterToolbar from '@/components/ITableComponents/FooterToolbar'; 27 | import DescriptionList from '@/components/ITableComponents/DescriptionList'; 28 | import TableColumnRender from '@/components/ITableComponents/TableColumnRender' 29 | 30 | const { Description } = DescriptionList; 31 | 32 | function Detail(props) { 33 | const { dispatch, match, loading } = props; 34 | const { params } = match; 35 | const data = props.${namespace} 36 | const { current } = data 37 | 38 | ${injectValues} 39 | 40 | // Effect 41 | useEffect(() => { 42 | if (params.id) { 43 | dispatch({ 44 | type: '${namespace}/findById', 45 | payload: { ${primaryKey}: params.id } 46 | }); 47 | ${dicArray && dicArray.length ? 48 | dicArray.map(item => (`dispatch({ type: "${namespace}/__${item}" });`)).join('\n') 49 | : ''} 50 | } 51 | }, [dispatch, params.id]) 52 | 53 | // 获取详情内容 54 | const content = \n${genDetailItem(DataSchema)} 55 | 56 | function handleClick () { 57 | ${!singleDataMode ? 'router.goBack()' : 58 | `router.push(\`${baseRouterPath || ''}/${namespace}/upsert/\${params.id}\`)`} 59 | } 60 | 61 | return ( 62 | 63 | 64 | {content} 65 | 66 | 67 | 70 | 71 | 72 | ); 73 | } 74 | 75 | export default connect((state) => { 76 | const { loading } = state 77 | return { 78 | ${namespace}: state.${namespace}, 79 | loading: loading.effects['${namespace}/findById'], 80 | } 81 | })(Detail) 82 | ` 83 | } -------------------------------------------------------------------------------- /lib/react-antd-table/genCode/genIndex/genIndexColumn.js: -------------------------------------------------------------------------------- 1 | const _ = require('lodash') 2 | const t = require('@babel/types') 3 | const generate = require('@babel/generator').default 4 | const utils = require('../genUtils') 5 | 6 | const { 7 | getInjectKey, 8 | getPrimaryKey, 9 | unescapeToChinese, 10 | getItemFilterExpression, 11 | getBlankLine 12 | } = utils 13 | 14 | module.exports = function genIndexColumn (DataSchema, Config) { 15 | 16 | const array = [] 17 | const { 18 | showDetail, 19 | showUpdate, 20 | showDelete, 21 | showAutoIncrementIndex 22 | } = Config 23 | const primaryKey = getPrimaryKey(DataSchema) 24 | const blankLine = getBlankLine() 25 | 26 | DataSchema 27 | .filter(item => item.showInTable !== false) // 筛选数据是否在table中显示 28 | .forEach((item) => { 29 | const exArray = [ 30 | t.objectProperty(t.identifier('title'), t.stringLiteral(item.title)), 31 | t.objectProperty(t.identifier('dataIndex'), t.stringLiteral(item.key)), 32 | ] 33 | const funcArray = [ 34 | t.jsxAttribute(t.jsxIdentifier('value'), t.jSXExpressionContainer(t.identifier('val'))), 35 | ] 36 | 37 | if (item.format) { 38 | funcArray.push(t.jsxAttribute(t.jsxIdentifier('format'), t.stringLiteral(item.format))) 39 | } 40 | if (item.options) { 41 | funcArray.push(t.jsxAttribute(t.jsxIdentifier('options'), t.jSXExpressionContainer( 42 | t.identifier(getInjectKey(item.key)) 43 | ))) 44 | } 45 | if (item.primary && showAutoIncrementIndex) { 46 | funcArray.push(t.jsxAttribute(t.jsxIdentifier('showType'), t.stringLiteral('autoIncrement'))) 47 | funcArray.push(t.jsxAttribute(t.jsxIdentifier('index'), t.jSXExpressionContainer(t.identifier('index')))) 48 | funcArray.push(t.jsxAttribute(t.jsxIdentifier('pagination'), t.jSXExpressionContainer(t.identifier('pagination')))) 49 | } else if (item.showType) { 50 | funcArray.push(t.jsxAttribute(t.jsxIdentifier('showType'), t.stringLiteral(item.showType))) 51 | } 52 | 53 | if (item.sorter) { 54 | exArray.push(t.objectProperty(t.identifier('sorter'), t.booleanLiteral(item.sorter))) 55 | } 56 | if (item.width) { 57 | exArray.push(t.objectProperty(t.identifier('width'), _.isNumber(item.width) ? t.numericLiteral(item.width) : t.stringLiteral(item.width))) 58 | } 59 | if (item.filters) { 60 | exArray.push(t.objectProperty(t.identifier('filters'), getItemFilterExpression(item.filters))) 61 | } 62 | // render actions 63 | if (item.actions) { 64 | const actionsExpressionArray = [] 65 | item.actions.forEach((action, i) => { 66 | const attrArray = [ 67 | t.jsxAttribute(t.jsxIdentifier('type'), t.stringLiteral(action.type)), 68 | t.jsxAttribute(t.jsxIdentifier('record'), t.jSXExpressionContainer(t.identifier('record'))), 69 | ] 70 | if (action.type === 'delete') { 71 | attrArray.push(t.jsxAttribute(t.jsxIdentifier('visible'), t.jSXExpressionContainer(t.booleanLiteral(!!showDelete)))) 72 | attrArray.push(t.jsxAttribute(t.jsxIdentifier('name'), t.stringLiteral(action.name))) 73 | attrArray.push(t.jsxAttribute(t.jsxIdentifier('primaryKey'), t.stringLiteral(primaryKey))) 74 | attrArray.push(t.jsxAttribute(t.jsxIdentifier('onClick'), t.jSXExpressionContainer(t.identifier('handleRemove')))) 75 | } 76 | if (action.type === 'detail') { 77 | attrArray.push(t.jsxAttribute(t.jsxIdentifier('visible'), t.jSXExpressionContainer(t.booleanLiteral(!!showDetail)))) 78 | attrArray.push(t.jsxAttribute(t.jsxIdentifier('name'), t.stringLiteral(action.name))) 79 | attrArray.push(t.jsxAttribute(t.jsxIdentifier('onClick'), t.jSXExpressionContainer(t.identifier('handleUpdateModalVisible')))) 80 | } 81 | if (action.type === 'update') { 82 | attrArray.push(t.jsxAttribute(t.jsxIdentifier('visible'), t.jSXExpressionContainer(t.booleanLiteral(!!showUpdate)))) 83 | attrArray.push(t.jsxAttribute(t.jsxIdentifier('name'), t.stringLiteral(action.name))) 84 | attrArray.push(t.jsxAttribute(t.jsxIdentifier('onClick'), t.jSXExpressionContainer(t.identifier('handleUpdateModalVisible')))) 85 | } 86 | // ColumnAction Divider 87 | actionsExpressionArray.push(blankLine, t.jSXElement( 88 | t.jSXOpeningElement(t.jsxIdentifier('ColumnAction'), attrArray, true), 89 | null, 90 | [], 91 | true 92 | )) 93 | 94 | const len = item.actions.length 95 | const next = item.actions[i + 1] 96 | if (!(i === len - 1 || 97 | action.type === 'newLine' || 98 | (next && next.type === 'newLine'))) { 99 | actionsExpressionArray.push(blankLine, 100 | t.jSXElement( 101 | t.jSXOpeningElement(t.jsxIdentifier('Divider'), [ 102 | t.jsxAttribute(t.jsxIdentifier('type'), t.stringLiteral('vertical')) 103 | ], true), 104 | null, 105 | [], 106 | true 107 | ) 108 | ) 109 | } 110 | }) 111 | exArray.push(t.objectProperty(t.identifier('render'), t.arrowFunctionExpression([ 112 | t.identifier('_'), 113 | t.identifier('record'), 114 | ], t.BlockStatement([ 115 | t.returnStatement( 116 | t.jSXFragment(t.jsxOpeningFragment(), t.jsxClosingFragment(), [ 117 | ...actionsExpressionArray, 118 | blankLine 119 | ]) 120 | ) 121 | ])))) 122 | } else { 123 | exArray.push(t.objectProperty(t.identifier('render'), t.arrowFunctionExpression([ 124 | t.identifier('val'), 125 | t.identifier('record'), 126 | t.identifier('index'), 127 | ], t.BlockStatement([ 128 | t.returnStatement( 129 | t.jSXElement( 130 | t.jSXOpeningElement(t.jsxIdentifier('TableColumnRender'), funcArray, true), 131 | null, 132 | [], 133 | true 134 | ) 135 | ) 136 | ])))) 137 | } 138 | 139 | const ast = t.objectExpression(exArray) 140 | array.push(ast) 141 | }) 142 | 143 | const ast = t.arrayExpression(array) 144 | let { code } = generate(ast) 145 | // 解决返回<>不换行导致eslint不能自动修复的问题 146 | code = code.replace(/return <>([\s\S]*)<\/>/, 'return (\n<>$1)') 147 | 148 | return unescapeToChinese(code) 149 | } -------------------------------------------------------------------------------- /lib/react-antd-table/genCode/genIndex/index.js: -------------------------------------------------------------------------------- 1 | const genIndexColumn = require('./genIndexColumn') 2 | const genUtils = require('../genUtils') 3 | 4 | const { getDicArray, getPrimaryKey, injectVariables } = genUtils 5 | 6 | module.exports = (tableConfig) => { 7 | const { Config, DataSchema } = tableConfig 8 | const { 9 | namespace, 10 | newRouterMode, 11 | dictionary, 12 | multiSelection, 13 | showCreate, 14 | showExport, 15 | showBatchDelete, 16 | baseRouterPath, 17 | onlyTableListMode 18 | } = Config 19 | const primaryKey = getPrimaryKey(DataSchema) 20 | const dicArray = getDicArray(dictionary) 21 | const injectValues = injectVariables(DataSchema) 22 | 23 | return ` 24 | import React, { ${multiSelection || !newRouterMode ? 'useState,' : ''} useEffect, useCallback } from 'react'; 25 | ${newRouterMode ? "import router from 'umi/router';" : ''} 26 | import { connect } from 'dva'; 27 | import { Card, message, Modal, Divider } from 'antd'; 28 | import { routerRedux } from 'dva/router' 29 | import StandardTable from '@/components/ITableComponents/StandardTable'; 30 | ${!newRouterMode && !onlyTableListMode ? "import TableForm from './components/TableForm'" : ''} 31 | import TableFilter from './components/TableFilter' 32 | import TableAction from '@/components/ITableComponents/TableAction' 33 | import ColumnAction from '@/components/ITableComponents/ColumnAction' 34 | import TableColumnRender from '@/components/ITableComponents/TableColumnRender' 35 | import utils from '@/components/ITableComponents/utils' 36 | 37 | const { getFormatParamsFromTable } = utils 38 | 39 | function Table (props) { 40 | ${multiSelection ? 'const [selectedRows, setSelectedRows] = useState([])' : ''} 41 | ${!newRouterMode ? ` 42 | const [modalVisible, setModalVisible] = useState(false) 43 | const [current, setCurrent] = useState({}) 44 | const [modalType, setModalType] = useState('create')` : ''} 45 | 46 | const { 47 | dispatch, 48 | loading, 49 | location, 50 | ${namespace}, 51 | } = props; 52 | const data = props.${namespace}; 53 | const { pagination } = data; 54 | 55 | ${injectValues} 56 | 57 | const dispatchFetch = useCallback((params = {}) => { 58 | dispatch({ 59 | type: "${namespace}/fetch", 60 | payload: { 61 | ...location.query, 62 | ...params 63 | } 64 | }); 65 | ${dicArray && dicArray.length ? 66 | dicArray.map(item => (`dispatch({ type: "${namespace}/__${item}" });`)).join('\n') 67 | : ''} 68 | }, [dispatch, location.query]) 69 | 70 | // 初始化请求数据' 71 | useEffect(() => { 72 | dispatchFetch() 73 | }, [dispatchFetch]) 74 | 75 | function handleUpdateModalVisible(item, type) { 76 | ${ newRouterMode ? 77 | `router.push(\`${baseRouterPath || ''}/${namespace}/\${type === 'detail' ? 'detail' : 'upsert'}/\${item.${primaryKey}}\`)` : 78 | `setModalVisible(true) 79 | setModalType(type) 80 | setCurrent(item)` 81 | } 82 | } 83 | 84 | function handleRemove(keys, callback) { 85 | const isArray = Array.isArray(keys) 86 | Modal.confirm({ 87 | title: \`确定要删除\${isArray ? \`所选的\${keys.length}条\` : '当前'}记录吗?\`, 88 | okText: '确定', 89 | cancelText: '取消', 90 | onOk () { 91 | dispatch({ 92 | type: "${namespace}/remove", 93 | payload: { 94 | ${primaryKey}: isArray ? keys.map(row => row.${primaryKey}).join() : keys, 95 | }, 96 | callback: () => { 97 | if (callback) callback() 98 | message.success('删除成功') 99 | }, 100 | }); 101 | } 102 | }) 103 | } 104 | 105 | function dispatchRouterChange (params = {}) { 106 | dispatch(routerRedux.push({ 107 | pathname: location.pathname, 108 | query: params, 109 | })) 110 | } 111 | 112 | function handleStandardTableChange (_pagination, filtersArg, sorter) { 113 | const params = getFormatParamsFromTable(_pagination, filtersArg, sorter, location.query) 114 | dispatchRouterChange(params) 115 | }; 116 | 117 | ${!newRouterMode ? ` 118 | function handleModalVisible (flag) { 119 | setModalVisible(!!flag) 120 | setCurrent({}) 121 | setModalType('create') 122 | };` : ''} 123 | 124 | 125 | function handleMenuClick (key) { 126 | switch (key) { 127 | case 'create': { 128 | ${newRouterMode ? 129 | `router.push("${baseRouterPath || ''}/${namespace}/upsert")` : 130 | 'handleModalVisible(true)' 131 | } 132 | break; 133 | } 134 | ${showExport ? ` 135 | case 'export': 136 | Modal.confirm({ 137 | title: '确定要导出所有数据吗?', 138 | okText: '确定', 139 | cancelText: '取消', 140 | onOk (){ 141 | message.success('导出成功!') 142 | } 143 | }) 144 | break; 145 | ` : ''} 146 | ${multiSelection ? ` 147 | case 'remove': 148 | if (selectedRows.length === 0) return; 149 | handleRemove(selectedRows, () => { 150 | setSelectedRows([]) 151 | }) 152 | break; 153 | ` : ''} 154 | default: 155 | break; 156 | } 157 | }; 158 | 159 | function handleSearch (values) { 160 | dispatchRouterChange({ 161 | ...values, 162 | pageNum:1, 163 | }) 164 | }; 165 | 166 | ${!newRouterMode ? ` 167 | function handleSubmit (values, type, callback) { 168 | dispatch({ 169 | type: "${namespace}/submit", 170 | payload: values, 171 | actionType: type, 172 | callback: () => { 173 | handleModalVisible(); 174 | if (callback) callback(); 175 | } 176 | }); 177 | }; 178 | ` : ''} 179 | 180 | const columns = ${genIndexColumn(DataSchema, Config)} 181 | 182 | return ( 183 |
184 | 185 | 190 | 197 | 210 | 211 | ${!newRouterMode && !onlyTableListMode ? 212 | `` 219 | : '' 220 | } 221 |
222 | ); 223 | } 224 | 225 | export default connect((state) => { 226 | const { loading } = state 227 | return { 228 | ${namespace}: state.${namespace}, 229 | loading: loading.models.${namespace}, 230 | } 231 | })(Table); 232 | ` 233 | } -------------------------------------------------------------------------------- /lib/react-antd-table/genCode/genModels/index.js: -------------------------------------------------------------------------------- 1 | const modelUtils = require('./modelUtils') 2 | const genUtils = require('../genUtils') 3 | 4 | const { dynamicImport, dynamicYieldFunction, dynamicReducerFunction } = modelUtils 5 | const { getDicArray } = genUtils 6 | 7 | module.exports = (tableConfig) => { 8 | 9 | const { 10 | Config: { 11 | dictionary, 12 | namespace, 13 | newRouterMode, 14 | baseRouterPath 15 | }, 16 | } = tableConfig 17 | const dicArray = getDicArray(dictionary) 18 | 19 | return ` 20 | import { message } from 'antd'; 21 | import { routerRedux } from 'dva/router' 22 | import { parse } from 'querystring' 23 | ${dynamicImport(dicArray, namespace)} 24 | 25 | function *responseHandler (response, { put }, callback, successMsg) { 26 | if (response && response.errorCode === 0) { 27 | if (successMsg) message.success(successMsg) 28 | yield put({ type: 'requery' }) 29 | if (callback) callback(); 30 | } else { 31 | message.error(response && response.errorMessage || '请求失败') 32 | } 33 | } 34 | 35 | export default { 36 | namespace: '${namespace}', 37 | 38 | state: { 39 | data: { 40 | list: [], 41 | pagination: {}, 42 | }, 43 | current: {} 44 | }, 45 | 46 | effects: { 47 | *requery (_, { put }) { 48 | yield put(routerRedux.push({ 49 | pathname: '${baseRouterPath ? `${baseRouterPath.endsWith('/') ? `${baseRouterPath}${namespace}` : `${baseRouterPath}/${namespace}`}` : `/${namespace}`}', 50 | query: parse(window.location.search.substr(1)) 51 | })) 52 | }, 53 | *goBack (_, { put }) { 54 | yield put(routerRedux.goBack()) 55 | }, 56 | *fetch({ payload }, { call, put }) { 57 | const response = yield call(queryData, payload); 58 | if (response && response.errorCode === 0) { 59 | const { data } = response 60 | yield put({ 61 | type: 'save', 62 | payload: { 63 | list: data.recordList, 64 | pagination: { 65 | current: data.currentPage, 66 | numPerPage: data.numPerPage, 67 | total: data.totalCount 68 | } 69 | }, 70 | }); 71 | } else { 72 | message.error(response && response.errorMessage || '请求失败') 73 | } 74 | }, 75 | *findById({ payload }, { call, put }) { 76 | const response = yield call(findById, payload); 77 | if (response && response.errorCode === 0) { 78 | yield put({ 79 | type: 'updateCurrent', 80 | payload: response.data, 81 | }); 82 | } else { 83 | message.error(response && response.errorMessage || '请求失败') 84 | } 85 | }, 86 | *submit({ payload, actionType, callback }, { call, put }) { 87 | const isCreateMode = actionType === 'create' 88 | const successMsg = isCreateMode ? '添加成功' : '更新成功' 89 | 90 | const response = yield call(isCreateMode ? addData : updateData, payload); 91 | if (response && response.errorCode === 0) { 92 | if (successMsg) message.success(successMsg) 93 | yield put({ type: ${newRouterMode ? '\'goBack\'' : '\'requery\''} }) 94 | if (callback) callback(); 95 | } else { 96 | message.error(response && response.errorMessage || '请求失败') 97 | } 98 | }, 99 | *remove({ payload, callback }, { call, put }) { 100 | const response = yield call(removeData, payload); 101 | yield responseHandler(response, { put }, callback) 102 | }, 103 | ${dynamicYieldFunction(dicArray)} 104 | }, 105 | 106 | reducers: { 107 | save(state, action) { 108 | return { 109 | ...state, 110 | data: action.payload, 111 | }; 112 | }, 113 | updateCurrent(state, action) { 114 | return { 115 | ...state, 116 | current: action.payload, 117 | }; 118 | }, 119 | resetCurrent(state) { 120 | return { 121 | ...state, 122 | current: {}, 123 | }; 124 | }, 125 | ${dynamicReducerFunction(dicArray)} 126 | }, 127 | }; 128 | ` 129 | } -------------------------------------------------------------------------------- /lib/react-antd-table/genCode/genModels/modelUtils.js: -------------------------------------------------------------------------------- 1 | 2 | const t = require('@babel/types') 3 | const generate = require('@babel/generator').default 4 | const genUtils = require('../genUtils') 5 | 6 | const { 7 | getInjectVariableKey, 8 | unescapeToChinese, 9 | getUpdateHumpKey 10 | } = genUtils 11 | 12 | // get function 13 | function getYieldFunctionDeclaration (name) { 14 | const injectVariableKey = getInjectVariableKey(name) 15 | const updateFuncKey = getUpdateHumpKey(name) 16 | 17 | const ast = t.functionDeclaration( 18 | t.identifier(injectVariableKey), // id 19 | [ // params 20 | t.objectPattern([ // object property 21 | t.objectProperty(t.identifier('payload'), t.identifier('payload')) 22 | ]), 23 | t.objectPattern([ // object property 24 | t.objectProperty(t.identifier('call'), t.identifier('call')), 25 | t.objectProperty(t.identifier('put'), t.identifier('put')) 26 | ]), 27 | ], 28 | t.blockStatement( 29 | [ // body 30 | t.variableDeclaration("const", [ // declaration 31 | t.variableDeclarator( 32 | t.identifier('response'), 33 | t.yieldExpression( 34 | t.callExpression( 35 | t.identifier('call'), 36 | [ 37 | t.identifier(injectVariableKey), 38 | t.identifier('payload') 39 | ] 40 | ) 41 | ) 42 | ) 43 | ]), 44 | t.ifStatement( // IfStatement 45 | t.logicalExpression( 46 | "&&", 47 | t.identifier('response'), 48 | t.binaryExpression( 49 | "===", 50 | t.memberExpression( 51 | t.identifier('response'), 52 | t.identifier('errorCode') 53 | ), 54 | t.numericLiteral(0) 55 | ) 56 | ), 57 | t.blockStatement( // consequent 58 | [ 59 | t.expressionStatement( 60 | t.yieldExpression( 61 | t.callExpression( 62 | t.identifier('put'), 63 | [ 64 | t.objectExpression( 65 | [ 66 | t.objectProperty( 67 | t.identifier('type'), 68 | t.stringLiteral(updateFuncKey) 69 | ), 70 | t.objectProperty( 71 | t.identifier('payload'), 72 | t.memberExpression( 73 | t.identifier('response'), 74 | t.identifier('data') 75 | ) 76 | ), 77 | ] 78 | ) 79 | ] 80 | ) 81 | ) 82 | ) 83 | ] 84 | ), 85 | t.blockStatement( // alternate 86 | [ 87 | t.expressionStatement( 88 | t.callExpression( 89 | t.memberExpression( 90 | t.identifier('message'), 91 | t.identifier('error') 92 | ), 93 | [ 94 | t.logicalExpression( 95 | "||", 96 | t.logicalExpression( 97 | "&&", 98 | t.identifier('response'), 99 | t.memberExpression( 100 | t.identifier('response'), 101 | t.identifier('errorMessage') 102 | ) 103 | ), 104 | t.stringLiteral('请求失败') 105 | ) 106 | ] 107 | ) 108 | ) 109 | ] 110 | ) 111 | ) 112 | ] 113 | ) 114 | ) 115 | 116 | const result = unescapeToChinese(generate(ast).code) 117 | return result 118 | } 119 | 120 | function getReducerFunctionDeclaration (name) { 121 | const updateFuncKey = getUpdateHumpKey(name) 122 | 123 | const ast = t.functionDeclaration( 124 | t.identifier(updateFuncKey), 125 | [ 126 | t.identifier('state'), 127 | t.identifier('action') 128 | ], 129 | t.blockStatement([ 130 | t.returnStatement( 131 | t.callExpression( 132 | t.memberExpression( 133 | t.identifier('Object'), 134 | t.identifier('assign') 135 | ), 136 | [ 137 | t.objectExpression([]), 138 | t.identifier('state'), 139 | t.objectExpression([ 140 | t.objectProperty( 141 | t.identifier(name), 142 | t.memberExpression( 143 | t.identifier('action'), 144 | t.identifier('payload') 145 | ) 146 | ) 147 | ]) 148 | ] 149 | ) 150 | ) 151 | ]) 152 | ) 153 | 154 | const result = unescapeToChinese(generate(ast).code) 155 | return result 156 | } 157 | 158 | module.exports = { 159 | dynamicImport (dicArray, namespace) { 160 | let baseImport = [ 161 | 'queryData', 'removeData', 'addData', 'updateData', 'findById' 162 | ] 163 | if (dicArray && dicArray.length) { 164 | baseImport = baseImport.concat(dicArray.map(key => getInjectVariableKey(key))) 165 | } 166 | const _importDeclarationArray = [] 167 | 168 | baseImport.forEach(specifier => { 169 | _importDeclarationArray.push(t.importSpecifier(t.identifier(specifier), t.identifier(specifier))) 170 | }) 171 | const newImport = t.importDeclaration( 172 | _importDeclarationArray, 173 | t.stringLiteral(`../services/${namespace}`) 174 | ) 175 | const { code } = generate(newImport) 176 | 177 | return code 178 | }, 179 | dynamicYieldFunction (dicArray) { 180 | if (!(dicArray && dicArray.length)) { 181 | return '' 182 | } 183 | const result = [] 184 | dicArray.forEach(name => { 185 | const functionCode = getYieldFunctionDeclaration(name) 186 | // 因未找到生成yield function中的*的方法,采用拼接+字符串替换的方式实现 187 | result.push(`${getInjectVariableKey(name)}:${functionCode.replace(/function.*\(/, "function* (")}`) 188 | }) 189 | 190 | return result.join(',\n') 191 | }, 192 | dynamicReducerFunction (dicArray) { 193 | if (!(dicArray && dicArray.length)) { 194 | return '' 195 | } 196 | const result = [] 197 | dicArray.forEach(name => { 198 | const functionCode = getReducerFunctionDeclaration(name) 199 | result.push(`${getUpdateHumpKey(name)}:${functionCode.replace(/function.*\(/, "function (")}`) 200 | }) 201 | 202 | return result.join(',\n') 203 | }, 204 | } 205 | -------------------------------------------------------------------------------- /lib/react-antd-table/genCode/genServices/index.js: -------------------------------------------------------------------------------- 1 | const serviceUtils = require('./serviceUtils') 2 | const genUtils = require('../genUtils') 3 | 4 | const { dynamicAsyncFunction } = serviceUtils 5 | const { getPrimaryKey } = genUtils 6 | 7 | module.exports = (tableConfig) => { 8 | 9 | const { 10 | Config: { 11 | dictionary, 12 | namespace, 13 | }, 14 | DataSchema, 15 | } = tableConfig 16 | const primaryKey = getPrimaryKey(DataSchema) 17 | 18 | return ` 19 | import { stringify } from 'querystring'; 20 | import request from '@/utils/request'; 21 | 22 | export async function queryData(params) { 23 | return request({ 24 | url: '/${namespace}/list', 25 | method: 'post', 26 | baseUrlType: 'authCenter', 27 | data: params 28 | }) 29 | } 30 | 31 | export async function findById(params) { 32 | return request({ 33 | url: \`/${namespace}/findById/\${params.${primaryKey}}\`, 34 | method: 'get', 35 | baseUrlType: 'authCenter', 36 | }) 37 | } 38 | 39 | export async function removeData(params) { 40 | return request({ 41 | url: \`/${namespace}/delBatch?\${stringify(params)}\`, 42 | method: 'post', 43 | baseUrlType: 'authCenter', 44 | }) 45 | } 46 | 47 | export async function addData(params) { 48 | return request({ 49 | url: '/${namespace}/add', 50 | method: 'post', 51 | baseUrlType: 'authCenter', 52 | data: params 53 | }) 54 | } 55 | 56 | export async function updateData(params) { 57 | return request({ 58 | url: '/${namespace}/updateById', 59 | method: 'post', 60 | baseUrlType: 'authCenter', 61 | data: params 62 | }) 63 | } 64 | 65 | ${dynamicAsyncFunction(dictionary)} 66 | ` 67 | } -------------------------------------------------------------------------------- /lib/react-antd-table/genCode/genServices/serviceUtils.js: -------------------------------------------------------------------------------- 1 | const t = require('@babel/types') 2 | const generate = require('@babel/generator').default 3 | const genUtils = require('../genUtils') 4 | 5 | const { 6 | getInjectVariableKey, 7 | unescapeToChinese, 8 | } = genUtils 9 | 10 | function getAsyncFunctionDeclaration(item) { 11 | const { key, url } = item; 12 | const variableKey = getInjectVariableKey(key) 13 | 14 | const ast = t.exportNamedDeclaration( 15 | t.functionDeclaration( 16 | t.identifier(variableKey), 17 | [], 18 | t.blockStatement([ 19 | t.returnStatement( 20 | t.callExpression( 21 | t.identifier('request'), 22 | [ 23 | t.objectExpression([ 24 | t.objectProperty( 25 | t.identifier('url'), 26 | t.stringLiteral(url) 27 | ), 28 | t.objectProperty( 29 | t.identifier('method'), 30 | t.stringLiteral('get') 31 | ), 32 | t.objectProperty( 33 | t.identifier('baseUrlType'), 34 | t.stringLiteral('authCenter') 35 | ) 36 | ]) 37 | ] 38 | ) 39 | ) 40 | ]) 41 | ), 42 | [] 43 | ) 44 | 45 | return unescapeToChinese(generate(ast).code) 46 | } 47 | 48 | module.exports = { 49 | dynamicAsyncFunction (dicArray) { 50 | if (!(dicArray && dicArray.length)) { 51 | return '' 52 | } 53 | const result = [] 54 | dicArray.forEach(item => { 55 | const { key, url } = item; 56 | if (!key) throw new Error('字典项key值不能为空!') 57 | if (!url) throw new Error('字典项url值不能为空!') 58 | 59 | const functionCode = getAsyncFunctionDeclaration(item) 60 | result.push(`${functionCode.replace(/export.*function/, "export async function")}`) 61 | }) 62 | 63 | return result.join('\n\n') 64 | }, 65 | } 66 | -------------------------------------------------------------------------------- /lib/react-antd-table/genCode/genTableFilter/index.js: -------------------------------------------------------------------------------- 1 | const tableFilterUtils = require('./tableFilterUtils') 2 | const genUtils = require('../genUtils') 3 | 4 | const { hasModeSwitch, getSimpleFormData, getAdvancedFormData } = tableFilterUtils 5 | const { injectVariables } = genUtils 6 | 7 | module.exports = (tableConfig) => { 8 | 9 | const { 10 | Config: { 11 | namespace, 12 | defaultDateFormat 13 | }, 14 | QuerySchema 15 | } = tableConfig 16 | const injectValues = injectVariables(QuerySchema) 17 | const switchMode = hasModeSwitch(QuerySchema) 18 | 19 | return ` 20 | import React, { useState } from 'react' 21 | import { connect } from 'dva'; 22 | import moment from 'moment'; 23 | import { Row, Col, Form, Button, Input, InputNumber, ${switchMode ? 'Icon,': ''} DatePicker, Cascader } from 'antd' 24 | import RadioGroup from '@/components/ITableComponents/RadioGroup'; 25 | import CheckboxGroup from '@/components/ITableComponents/CheckboxGroup'; 26 | import SelectGroup from '@/components/ITableComponents/SelectGroup'; 27 | import utils from '@/components/ITableComponents/utils' 28 | import styles from './index.less'; 29 | 30 | const FormItem = Form.Item 31 | const { RangePicker } = DatePicker 32 | const { getFormatFormValues } = utils 33 | 34 | const TableFilter = (props) => { 35 | ${switchMode ? 'const [expandForm, setExpandForm] = useState(false)' : ''} 36 | 37 | const { 38 | data, 39 | handleSearch, 40 | handleReset, 41 | form, 42 | form: { getFieldDecorator } 43 | } = props 44 | const { query } = props.location 45 | 46 | ${injectValues} 47 | 48 | ${switchMode ? ` 49 | function toggleForm () { 50 | setExpandForm(!expandForm) 51 | }; 52 | ` : ''} 53 | 54 | function onReset () { 55 | form.resetFields() 56 | handleReset() 57 | } 58 | function onSearch (e) { 59 | e.preventDefault(); 60 | 61 | form.validateFields((err, fieldsValue) => { 62 | if (err) return; 63 | const values = getFormatFormValues(fieldsValue, '${defaultDateFormat}', 'tableFilter') 64 | 65 | handleSearch(values) 66 | }); 67 | } 68 | function getFilterFromActions () { 69 | return ( 70 |
71 | 74 | 77 | ${switchMode ? 78 | ` 79 | { expandForm ? '收起' : '展开' } 80 | ` 81 | : ''} 82 |
83 | ) 84 | } 85 | function getFormData() { 86 | const simpleFormData = \n${getSimpleFormData(QuerySchema)} 87 | ${switchMode ? `const advancedFromData = \n${getAdvancedFormData(QuerySchema)}` : ''} 88 | 89 | ${switchMode ? 'return expandForm ? advancedFromData: simpleFormData' 90 | : 'return simpleFormData'} 91 | } 92 | 93 | function renderForm () { 94 | const formData = getFormData() 95 | return ( 96 |
97 | {formData} 98 | { getFilterFromActions() } 99 |
100 | ); 101 | } 102 | 103 | return (
{renderForm()}
); 104 | } 105 | 106 | export default connect((state) => { 107 | const { loading, routing } = state 108 | return { 109 | ${namespace}: state.${namespace}, 110 | loading: loading.models.${namespace}, 111 | location: routing.location 112 | } 113 | })(Form.create()(TableFilter)); 114 | ` 115 | } -------------------------------------------------------------------------------- /lib/react-antd-table/genCode/genTableFilter/tableFilterUtils.js: -------------------------------------------------------------------------------- 1 | const t = require('@babel/types') 2 | const generate = require('@babel/generator').default 3 | const genUtils = require('../genUtils') 4 | const genFormItemUtil = require('../genUpsert/genFormItemUtil') 5 | 6 | const { getBlankLine, unescapeToChinese } = genUtils 7 | 8 | const col16Array = ['rangePicker'] 9 | const blankLine = getBlankLine() 10 | 11 | function getItemCol(item) { 12 | const { showType } = item 13 | const col16 = col16Array 14 | if (col16.indexOf(showType) !== -1) { 15 | return 16 16 | } 17 | return 8 18 | } 19 | 20 | function conditionChunk (array, size) { 21 | const length = array === null ? 0 : array.length; 22 | if (!length || size < 1) { 23 | return [] 24 | } 25 | let index = 0; 26 | let resIndex = 0; 27 | const result = []; 28 | 29 | while(index < length) { 30 | const sliceArray = array.slice(index, index + size) 31 | 32 | const hasCol16Data = (item) => { 33 | const col16 = col16Array 34 | return col16.indexOf(item.showType) !== -1 35 | } 36 | // 针对size=3的情况,逐一判断三个元素中是否有需要多列展示的情况,并且同一行中不能包含多个多列展示的 37 | const [first, second, third] = sliceArray 38 | if (hasCol16Data(first)) { 39 | if (second) { 40 | result[resIndex] = array.slice(index, index += hasCol16Data(second) ? 1 : 2) 41 | } else { 42 | result[resIndex] = array.slice(index, index += 1) 43 | } 44 | } else if (second) { 45 | if (hasCol16Data(second)) { 46 | result[resIndex] = array.slice(index, index += 2) 47 | } else if (third) { 48 | result[resIndex] = array.slice(index, index += hasCol16Data(third) ? 2 : 3) 49 | } else { 50 | result[resIndex] = array.slice(index, index += 2) 51 | } 52 | } else { 53 | result[resIndex] = array.slice(index, index += 1) 54 | } 55 | 56 | resIndex += 1 57 | } 58 | return result; 59 | } 60 | 61 | function hasModeSwitch(QuerySchema) {return QuerySchema.some(item => item.showInSimpleMode)} 62 | 63 | function getFilterItemCode (dataList) { 64 | const rowArray = [] 65 | if (dataList && dataList.length) { 66 | conditionChunk(dataList, 3).forEach(rowItem => { 67 | const colArray = [] 68 | rowItem.forEach(item => { 69 | colArray.push( 70 | t.jsxElement( 71 | t.jsxOpeningElement(t.jsxIdentifier('Col'), [ 72 | t.jsxAttribute(t.jsxIdentifier('md'), t.jsxExpressionContainer(t.numericLiteral(getItemCol(item)))), 73 | t.jsxAttribute(t.jsxIdentifier('sm'), t.jsxExpressionContainer(t.numericLiteral(24))), 74 | ]), 75 | t.jsxClosingElement(t.jsxIdentifier('Col')), 76 | [ 77 | blankLine, 78 | genFormItemUtil(item, true), 79 | blankLine 80 | ] 81 | ), blankLine 82 | ) 83 | }) 84 | rowArray.push(t.jsxElement( 85 | t.jsxOpeningElement(t.jsxIdentifier('Row'), [ 86 | t.jsxAttribute(t.jsxIdentifier('gutter'), t.jsxExpressionContainer(t.numericLiteral(24))), 87 | ]), 88 | t.jsxClosingElement(t.jsxIdentifier('Row')), 89 | [ 90 | blankLine, 91 | ...colArray, 92 | blankLine 93 | ] 94 | ), blankLine) 95 | }) 96 | } 97 | 98 | const ast = t.jsxFragment( 99 | t.jsxOpeningFragment(), 100 | t.jsxClosingFragment(), 101 | [ 102 | blankLine, 103 | ...rowArray, 104 | blankLine 105 | ] 106 | ) 107 | 108 | return unescapeToChinese(generate(ast).code) 109 | } 110 | 111 | function getAdvancedFormData(QuerySchema) {return getFilterItemCode(QuerySchema)} 112 | 113 | module.exports = { 114 | hasModeSwitch, 115 | getSimpleFormData (QuerySchema) { 116 | const switchMode = hasModeSwitch(QuerySchema) 117 | if (switchMode) { 118 | return getFilterItemCode(QuerySchema.filter(item => item.showInSimpleMode)) 119 | } 120 | return getAdvancedFormData(QuerySchema) 121 | }, 122 | getAdvancedFormData, 123 | } 124 | -------------------------------------------------------------------------------- /lib/react-antd-table/genCode/genTableForm/index.js: -------------------------------------------------------------------------------- 1 | const tableFormUtils = require('./tableFormUtils') 2 | const genUpsertUtil = require('../genUpsert/genUpsertUtil') 3 | const genUtils = require('../genUtils') 4 | 5 | const { getFormItem, getFormDetailItem } = tableFormUtils 6 | const { injectVariables, getPrimaryKey, getEditorKeys } = genUtils 7 | const { injectEditorEffects } = genUpsertUtil 8 | 9 | module.exports = (tableConfig) => { 10 | 11 | const { 12 | Config, 13 | Config: { 14 | namespace, 15 | defaultDateFormat 16 | }, 17 | DataSchema 18 | } = tableConfig 19 | const primaryKey = getPrimaryKey(DataSchema) 20 | const injectValues = injectVariables(DataSchema) 21 | const editorKeys = getEditorKeys(DataSchema) 22 | 23 | return ` 24 | import React, { useEffect } from 'react' 25 | import { connect } from 'dva'; 26 | import moment from 'moment'; 27 | ${editorKeys && editorKeys.length ? ` 28 | import BraftEditor from 'braft-editor' 29 | ` : ''} 30 | import Editor from '@/components/ITableComponents/Editor'; 31 | import { Modal, Form, Input, InputNumber, DatePicker, Cascader } from 'antd' 32 | import RadioGroup from '@/components/ITableComponents/RadioGroup'; 33 | import CheckboxGroup from '@/components/ITableComponents/CheckboxGroup'; 34 | import SelectGroup from '@/components/ITableComponents/SelectGroup'; 35 | import FileUpload from '@/components/ITableComponents/FileUpload'; 36 | import TableColumnRender from '@/components/ITableComponents/TableColumnRender'; 37 | import utils from '@/components/ITableComponents/utils' 38 | import styles from './index.less' 39 | 40 | const { getFormatFormValues } = utils; 41 | const FormItem = Form.Item; 42 | const { TextArea } = Input; 43 | const { RangePicker } = DatePicker; 44 | 45 | const formLayout = { 46 | labelCol: { span: 7 }, 47 | wrapperCol: { span: 13 }, 48 | }; 49 | 50 | const TableModal = (props) => { 51 | const { 52 | confirmLoading, 53 | current, 54 | modalType, 55 | modalVisible, 56 | form, 57 | form: { getFieldDecorator }, 58 | handleSubmit, 59 | handleModalVisible 60 | } = props; 61 | const data = props.${namespace} 62 | const isEditMode = modalType === 'edit' 63 | const isDetailMode = modalType === 'detail' 64 | 65 | ${injectValues} 66 | ${injectEditorEffects(editorKeys, 'isEditMode')} 67 | 68 | const onSubmit = (e) => { 69 | e.preventDefault() 70 | form.validateFields((err, fieldsValue) => { 71 | if (err) return; 72 | const values = getFormatFormValues(fieldsValue, '${defaultDateFormat}') 73 | // 编辑模式下,若主键的showInForm 设为false,则无法从fieldsValue中获取主键的值,需将current中对应字段获取到并拼接到当前值中 74 | if (isEditMode) { 75 | values.${primaryKey} = current.${primaryKey} 76 | } 77 | handleSubmit(values, modalType, () => { 78 | form.resetFields(); 79 | }); 80 | }); 81 | }; 82 | const getTile = () => { 83 | if (isDetailMode) return '查看详情' 84 | if (isEditMode) return '编辑' 85 | return '新增' 86 | } 87 | 88 | let modalContent; 89 | if (isDetailMode) { 90 | modalContent = \n${getFormDetailItem(DataSchema)} 91 | } else { 92 | modalContent = \n${getFormItem(DataSchema, Config)} 93 | } 94 | 95 | const modalProps = { 96 | title: getTile(), 97 | visible: modalVisible, 98 | onOk: onSubmit, 99 | confirmLoading, 100 | onCancel: () => handleModalVisible(), 101 | maskClosable: isDetailMode, // 只有查看详情时点击蒙层可以关闭 102 | } 103 | if (isDetailMode) modalProps.footer = null 104 | 105 | return ( 106 | 115 | {modalContent} 116 | 117 | ); 118 | } 119 | 120 | export default connect((params) => { 121 | const { loading } = params 122 | return { 123 | ${namespace}: params.${namespace}, 124 | confirmLoading: loading.effects['${namespace}/submit'] 125 | } 126 | })(Form.create()(TableModal)); 127 | ` 128 | } -------------------------------------------------------------------------------- /lib/react-antd-table/genCode/genTableForm/tableFormUtils.js: -------------------------------------------------------------------------------- 1 | const t = require('@babel/types') 2 | const generate = require('@babel/generator').default 3 | const genUtils = require('../genUtils') 4 | const genFormItemUtil = require('../genUpsert/genFormItemUtil') 5 | 6 | const { getInjectKey, getBlankLine, unescapeToChinese, renderInForm } = genUtils 7 | 8 | const blankLine = getBlankLine() 9 | 10 | module.exports = { 11 | getFormItem (DataSchema, Config) { 12 | const formArray = [blankLine] 13 | 14 | DataSchema.filter(item => renderInForm(item, 'edit')).forEach(item => { 15 | formArray.push(genFormItemUtil(item, false, true, Config), blankLine) 16 | }) 17 | const ast = t.jsxElement( 18 | t.jsxOpeningElement(t.jsxIdentifier('Form'), []), 19 | t.jsxClosingElement(t.jsxIdentifier('Form')), 20 | formArray 21 | ) 22 | const result = unescapeToChinese(generate(ast).code) 23 | 24 | return result; 25 | }, 26 | getFormDetailItem (DataSchema) { 27 | const formArray = [blankLine] 28 | 29 | DataSchema.filter(item => renderInForm(item, 'detail')).forEach(item => { 30 | const attrArray = [ 31 | t.jsxAttribute(t.jsxIdentifier('value'), t.jSXExpressionContainer(t.memberExpression(t.identifier('current'), t.identifier(item.key)))), 32 | t.jsxAttribute(t.jsxIdentifier('mode'), t.stringLiteral('detail')) 33 | ] 34 | if (item.showType) { 35 | attrArray.push(t.jsxAttribute(t.jsxIdentifier('showType'), t.stringLiteral(item.showType))) 36 | } 37 | if (item.format) { 38 | attrArray.push(t.jsxAttribute(t.jsxIdentifier('format'), t.stringLiteral(item.format))) 39 | } 40 | if (item.options) { 41 | const injectKey = getInjectKey(item.key) 42 | attrArray.push(t.jsxAttribute(t.jsxIdentifier('options'), t.jSXExpressionContainer( 43 | t.identifier(injectKey) 44 | ))) 45 | } 46 | formArray.push(t.jsxElement( 47 | t.jsxOpeningElement(t.jsxIdentifier('FormItem'), [ 48 | t.jsxAttribute(t.jsxIdentifier('label'), t.stringLiteral(item.title)), 49 | t.jsxSpreadAttribute(t.identifier('formLayout')) 50 | ]), 51 | t.jsxClosingElement(t.jsxIdentifier('FormItem')), 52 | [ 53 | blankLine, 54 | t.jsxElement( 55 | t.jsxOpeningElement(t.jsxIdentifier('span'), [ 56 | t.jsxAttribute(t.jsxIdentifier('className'), t.stringLiteral('ant-form-text')), 57 | ]), 58 | t.jsxClosingElement(t.jsxIdentifier('span')), 59 | [ 60 | blankLine, 61 | t.jSXElement( 62 | t.jSXOpeningElement(t.jsxIdentifier('TableColumnRender'), attrArray, true), 63 | null, 64 | [], 65 | true 66 | ), 67 | blankLine 68 | ] 69 | ), 70 | blankLine 71 | ] 72 | ), blankLine) 73 | }) 74 | const ast = t.jsxElement( 75 | t.jsxOpeningElement(t.jsxIdentifier('Form'), []), 76 | t.jsxClosingElement(t.jsxIdentifier('Form')), 77 | formArray 78 | ) 79 | const result = unescapeToChinese(generate(ast).code) 80 | 81 | return result; 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /lib/react-antd-table/genCode/genUpsert/genFormItemUtil.js: -------------------------------------------------------------------------------- 1 | const t = require('@babel/types') 2 | const utils = require('../genUtils') 3 | 4 | const { 5 | defaultDateFormat, 6 | getInjectKey, 7 | getBlankLine, 8 | getItemRulesExpression, 9 | getDefaultValueExpression, 10 | getFilterDefaultValueExpression, 11 | getUploadUrl 12 | } = utils 13 | 14 | const blankLine = getBlankLine() 15 | 16 | function getInputJsxExpression (item) { 17 | const { placeholder = '', primary } = item 18 | const disabled = !!primary 19 | const attrArray = [ 20 | t.jsxAttribute(t.jsxIdentifier('disabled'), t.jsxExpressionContainer(t.booleanLiteral(disabled))), 21 | t.jsxAttribute(t.jsxIdentifier('placeholder'), t.stringLiteral(placeholder)), 22 | ] 23 | if (item.addonBefore) { 24 | attrArray.push(t.jsxAttribute(t.jsxIdentifier('addonBefore'), t.stringLiteral(item.addonBefore))) 25 | } 26 | if (item.addonAfter) { 27 | attrArray.push(t.jsxAttribute(t.jsxIdentifier('addonAfter'), t.stringLiteral(item.addonAfter))) 28 | } 29 | return [ 30 | t.jsxElement(t.jsxOpeningElement(t.jsxIdentifier('Input'), attrArray, true), null, []) 31 | ] 32 | } 33 | function getInputNumberJsxExpression (item) { 34 | const { placeholder = '', min, max } = item 35 | const attrArray = [ 36 | t.jsxAttribute(t.jsxIdentifier('placeholder'), t.stringLiteral(placeholder)), 37 | t.jsxAttribute(t.jsxIdentifier('style'), t.jsxExpressionContainer(t.objectExpression([t.objectProperty(t.identifier('width'), t.stringLiteral('100%'))]))) 38 | ] 39 | if (typeof min !== 'undefined') { 40 | attrArray.push(t.jsxAttribute(t.jsxIdentifier('min'), t.jsxExpressionContainer(t.numericLiteral(item.min)))) 41 | } 42 | if (typeof max !== 'undefined') { 43 | attrArray.push(t.jsxAttribute(t.jsxIdentifier('max'), t.jsxExpressionContainer(t.numericLiteral(item.max)))) 44 | } 45 | 46 | return [ 47 | t.jsxElement(t.jsxOpeningElement(t.jsxIdentifier('InputNumber'), attrArray, true), null, []) 48 | ] 49 | } 50 | function getTextareaJsxExpression (item) { 51 | const { placeholder = '' } = item 52 | const attrArray = [ 53 | t.jsxAttribute(t.jsxIdentifier('placeholder'), t.stringLiteral(placeholder)), 54 | t.jsxAttribute(t.jsxIdentifier('rows'), t.jsxExpressionContainer(t.numericLiteral(4))), 55 | ] 56 | 57 | return [ 58 | t.jsxElement(t.jsxOpeningElement(t.jsxIdentifier('TextArea'), attrArray, true), null, []) 59 | ] 60 | } 61 | function getRadioJsxExpression (item) { 62 | const { key, placeholder = '' } = item 63 | const injectKey = getInjectKey(key) 64 | 65 | const attrArray = [ 66 | t.jsxAttribute(t.jsxIdentifier('placeholder'), t.stringLiteral(placeholder)), 67 | t.jsxAttribute(t.jsxIdentifier('source'), t.jsxExpressionContainer(t.identifier(injectKey))) 68 | ] 69 | 70 | return [ 71 | t.jsxElement( 72 | t.jsxOpeningElement(t.jsxIdentifier('RadioGroup'), attrArray, true), 73 | null, 74 | [] 75 | ) 76 | ] 77 | } 78 | function getCheckboxJsxExpression (item) { 79 | const { key, placeholder = '' } = item 80 | const injectKey = getInjectKey(key) 81 | 82 | const attrArray = [ 83 | t.jsxAttribute(t.jsxIdentifier('placeholder'), t.stringLiteral(placeholder)), 84 | t.jsxAttribute(t.jsxIdentifier('source'), t.jsxExpressionContainer(t.identifier(injectKey))) 85 | ] 86 | 87 | return [ 88 | t.jsxElement( 89 | t.jsxOpeningElement(t.jsxIdentifier('CheckboxGroup'), attrArray, true), 90 | null, 91 | [] 92 | ) 93 | ] 94 | } 95 | function getDatePickerJsxExpression (item) { 96 | const { placeholder = '', format = defaultDateFormat } = item 97 | 98 | const attrArray = [ 99 | t.jsxAttribute(t.jsxIdentifier('placeholder'), t.stringLiteral(placeholder)), 100 | t.jsxAttribute(t.jsxIdentifier('format'), t.stringLiteral(format)), 101 | t.jsxAttribute(t.jsxIdentifier('showTime'), t.jsxExpressionContainer(t.booleanLiteral(true))), 102 | t.jsxAttribute(t.jsxIdentifier('style'), t.jsxExpressionContainer(t.objectExpression([t.objectProperty(t.identifier('width'), t.stringLiteral('100%'))]))), 103 | ] 104 | 105 | return [ 106 | t.jsxElement( 107 | t.jsxOpeningElement(t.jsxIdentifier('DatePicker'), attrArray, true), 108 | null, 109 | [] 110 | ) 111 | ] 112 | } 113 | function getSelectJsxExpression (item) { 114 | const { key, placeholder = '' } = item 115 | const injectKey = getInjectKey(key) 116 | 117 | const attrArray = [ 118 | t.jsxAttribute(t.jsxIdentifier('placeholder'), t.stringLiteral(placeholder)), 119 | t.jsxAttribute(t.jsxIdentifier('source'), t.jsxExpressionContainer(t.identifier(injectKey))) 120 | ] 121 | 122 | return [ 123 | t.jsxElement( 124 | t.jsxOpeningElement(t.jsxIdentifier('SelectGroup'), attrArray, true), 125 | null, 126 | [] 127 | ) 128 | ] 129 | } 130 | function getInputSelectJsxExpression (item) { 131 | const { key, placeholder = '' } = item 132 | const injectKey = getInjectKey(key) 133 | 134 | const attrArray = [ 135 | t.jsxAttribute(t.jsxIdentifier('placeholder'), t.stringLiteral(placeholder)), 136 | t.jsxAttribute(t.jsxIdentifier('mode'), t.stringLiteral('tags')), 137 | t.jsxAttribute(t.jsxIdentifier('source'), t.jsxExpressionContainer(t.identifier(injectKey))) 138 | ] 139 | 140 | return [ 141 | t.jsxElement( 142 | t.jsxOpeningElement(t.jsxIdentifier('SelectGroup'), attrArray, true), 143 | null, 144 | [] 145 | ) 146 | ] 147 | } 148 | function getMultiSelectJsxExpression (item) { 149 | const { key, placeholder = '' } = item 150 | const injectKey = getInjectKey(key) 151 | 152 | const attrArray = [ 153 | t.jsxAttribute(t.jsxIdentifier('multiple'), t.jsxExpressionContainer(t.booleanLiteral(true))), 154 | t.jsxAttribute(t.jsxIdentifier('placeholder'), t.stringLiteral(placeholder)), 155 | t.jsxAttribute(t.jsxIdentifier('source'), t.jsxExpressionContainer(t.identifier(injectKey))) 156 | ] 157 | 158 | return [ 159 | t.jsxElement( 160 | t.jsxOpeningElement(t.jsxIdentifier('SelectGroup'), attrArray, true), 161 | null, 162 | [] 163 | ) 164 | ] 165 | } 166 | 167 | function getRangePickerJsxExpression (item) { 168 | const { 169 | format = defaultDateFormat, 170 | placeholderBegin = '开始日期', 171 | placeholderEnd = '结束日期', 172 | } = item 173 | 174 | const attrArray = [ 175 | t.jsxAttribute(t.jsxIdentifier('showTime'), t.jsxExpressionContainer(t.booleanLiteral(true))), 176 | t.jsxAttribute(t.jsxIdentifier('format'), t.stringLiteral(format)), 177 | t.jsxAttribute( 178 | t.jsxIdentifier('placeholder'), 179 | t.jsxExpressionContainer( 180 | t.arrayExpression([ 181 | t.stringLiteral(placeholderBegin), 182 | t.stringLiteral(placeholderEnd), 183 | ]) 184 | ) 185 | ) 186 | ] 187 | 188 | return [ 189 | t.jsxElement( 190 | t.jsxOpeningElement(t.jsxIdentifier('RangePicker'), attrArray, true), 191 | null, 192 | [] 193 | ) 194 | ] 195 | } 196 | 197 | function getCascadertJsxExpression (item) { 198 | const { key, placeholder = '' } = item 199 | const injectKey = getInjectKey(key) 200 | 201 | const attrArray = [ 202 | t.jsxAttribute(t.jsxIdentifier('options'), t.jsxExpressionContainer(t.identifier(injectKey))), 203 | t.jsxAttribute(t.jsxIdentifier('placeholder'), t.stringLiteral(placeholder)), 204 | ] 205 | 206 | return [ 207 | t.jsxElement( 208 | t.jsxOpeningElement(t.jsxIdentifier('Cascader'), attrArray, true), 209 | null, 210 | [] 211 | ) 212 | ] 213 | } 214 | 215 | function getImageJsxExpression (item, Config) { 216 | const { 217 | placeholder = '', max, url, sizeLimit, accept 218 | } = item 219 | const uploadUrl = getUploadUrl(true, url, Config) 220 | const { 221 | imageSizeLimit, 222 | } = Config 223 | 224 | const attrArray = [ 225 | t.jsxAttribute(t.jsxIdentifier('type'), t.stringLiteral('image')), 226 | t.jsxAttribute(t.jsxIdentifier('url'), t.stringLiteral(uploadUrl)), 227 | t.jsxAttribute(t.jsxIdentifier('placeholder'), t.stringLiteral(placeholder)), 228 | ] 229 | if (max) { 230 | attrArray.push(t.jsxAttribute(t.jsxIdentifier('max'), t.jsxExpressionContainer(t.numericLiteral(max)))) 231 | } 232 | 233 | if (sizeLimit) { 234 | attrArray.push(t.jsxAttribute(t.jsxIdentifier('sizeLimit'), t.jsxExpressionContainer(t.numericLiteral(sizeLimit)))) 235 | } else if (imageSizeLimit) { 236 | attrArray.push(t.jsxAttribute(t.jsxIdentifier('sizeLimit'), t.jsxExpressionContainer(t.numericLiteral(imageSizeLimit)))) 237 | } 238 | if (accept) { 239 | attrArray.push(t.jsxAttribute(t.jsxIdentifier('accept'), t.stringLiteral(accept))) 240 | } 241 | 242 | return [ 243 | t.jsxElement( 244 | t.jsxOpeningElement(t.jsxIdentifier('FileUpload'), attrArray, true), 245 | null, 246 | [] 247 | ) 248 | ] 249 | } 250 | function getFileJsxExpression (item, Config) { 251 | const { 252 | placeholder = '', max, url, sizeLimit, accept 253 | } = item 254 | const uploadUrl = getUploadUrl(false, url, Config) 255 | const { 256 | fileSizeLimit 257 | } = Config 258 | 259 | const attrArray = [ 260 | t.jsxAttribute(t.jsxIdentifier('url'), t.stringLiteral(uploadUrl)), 261 | t.jsxAttribute(t.jsxIdentifier('placeholder'), t.stringLiteral(placeholder)), 262 | ] 263 | if (max) { 264 | attrArray.push(t.jsxAttribute(t.jsxIdentifier('max'), t.jsxExpressionContainer(t.numericLiteral(max)))) 265 | } 266 | if (sizeLimit) { 267 | attrArray.push(t.jsxAttribute(t.jsxIdentifier('sizeLimit'), t.jsxExpressionContainer(t.numericLiteral(sizeLimit)))) 268 | } else if (fileSizeLimit) { 269 | attrArray.push(t.jsxAttribute(t.jsxIdentifier('sizeLimit'), t.jsxExpressionContainer(t.numericLiteral(fileSizeLimit)))) 270 | } 271 | if (accept) { 272 | attrArray.push(t.jsxAttribute(t.jsxIdentifier('accept'), t.stringLiteral(accept))) 273 | } 274 | 275 | return [ 276 | t.jsxElement( 277 | t.jsxOpeningElement(t.jsxIdentifier('FileUpload'), attrArray, true), 278 | null, 279 | [] 280 | ) 281 | ] 282 | } 283 | 284 | function getEditorJsxExpression (item) { 285 | const { placeholder = '' } = item 286 | 287 | const attrArray = [ 288 | t.jsxAttribute(t.jsxIdentifier('placeholder'), t.stringLiteral(placeholder)), 289 | ] 290 | 291 | return [ 292 | t.jsxElement( 293 | t.jsxOpeningElement(t.jsxIdentifier('Editor'), attrArray, true), 294 | null, 295 | [] 296 | ) 297 | ] 298 | } 299 | 300 | // 获取对应类型的Field 301 | module.exports = (item, isTableFilter, hasLayout, Config) => { 302 | const { showType, title, key } = item 303 | 304 | const configArray = [] 305 | 306 | if (item.validator && item.validator.length) { 307 | configArray.push(t.objectProperty(t.identifier('rules'), getItemRulesExpression(item.validator))) 308 | } 309 | if (showType === 'editor') { 310 | configArray.push(t.objectProperty(t.identifier('validateTrigger'), t.arrayExpression([t.stringLiteral('onBlur')]))) 311 | } else if (isTableFilter) { // editor 不需要 initialValue。必须由form.setFieldsValue赋值 312 | configArray.push(t.objectProperty(t.identifier('initialValue'), getFilterDefaultValueExpression(item))) 313 | } else { 314 | configArray.push(t.objectProperty(t.identifier('initialValue'), getDefaultValueExpression(item))) 315 | } 316 | 317 | let itemExpression = [] 318 | 319 | switch (showType) { 320 | case 'input': 321 | itemExpression = getInputJsxExpression(item) 322 | break; 323 | case 'inputNumber': 324 | itemExpression = getInputNumberJsxExpression(item) 325 | break; 326 | case 'textarea': 327 | itemExpression = getTextareaJsxExpression(item) 328 | break; 329 | case 'radio': 330 | itemExpression = getRadioJsxExpression(item) 331 | break; 332 | case 'checkbox': 333 | itemExpression = getCheckboxJsxExpression(item) 334 | break; 335 | case 'datePicker': 336 | itemExpression = getDatePickerJsxExpression(item) 337 | break; 338 | case 'select': 339 | itemExpression = getSelectJsxExpression(item) 340 | break; 341 | case 'inputSelect': 342 | itemExpression = getInputSelectJsxExpression(item) 343 | break; 344 | case 'multiSelect': 345 | itemExpression = getMultiSelectJsxExpression(item) 346 | break; 347 | case 'rangePicker': 348 | itemExpression = getRangePickerJsxExpression(item) 349 | break; 350 | case 'cascader': 351 | itemExpression = getCascadertJsxExpression(item) 352 | break; 353 | case 'image': 354 | itemExpression = getImageJsxExpression(item, Config) 355 | break; 356 | case 'file': 357 | itemExpression = getFileJsxExpression(item, Config) 358 | break; 359 | case 'editor': 360 | itemExpression = getEditorJsxExpression(item) 361 | break; 362 | default: 363 | itemExpression = getInputJsxExpression(item) 364 | } 365 | 366 | const expression = t.jsxExpressionContainer( 367 | t.callExpression( 368 | t.callExpression(t.identifier('getFieldDecorator'), [ 369 | t.stringLiteral(key), 370 | t.objectExpression(configArray) 371 | ]), 372 | itemExpression 373 | ) 374 | ) 375 | 376 | const formItemAttrArray = [ 377 | t.jsxAttribute(t.jsxIdentifier('label'), t.stringLiteral(title)) 378 | ] 379 | if (hasLayout) { 380 | formItemAttrArray.push( 381 | t.jsxSpreadAttribute(t.identifier('formLayout')) 382 | ) 383 | } 384 | 385 | // 若属性为disabled,编辑模式下将渲染为文本 386 | let disabledModeExpress = null 387 | if (item.disabled) { 388 | const injectKey = getInjectKey(item.key) 389 | const attrArray = [ 390 | t.jsxAttribute(t.jsxIdentifier('value'), t.jSXExpressionContainer(t.memberExpression(t.identifier('current'), t.identifier(item.key)))), 391 | t.jsxAttribute(t.jsxIdentifier('mode'), t.stringLiteral('detail')) 392 | ] 393 | if (item.showType) { 394 | attrArray.push(t.jsxAttribute(t.jsxIdentifier('showType'), t.stringLiteral(item.showType))) 395 | } 396 | if (item.format) { 397 | attrArray.push(t.jsxAttribute(t.jsxIdentifier('format'), t.stringLiteral(item.format))) 398 | } 399 | if (item.options) { 400 | attrArray.push(t.jsxAttribute(t.jsxIdentifier('options'), t.jSXExpressionContainer( 401 | t.identifier(injectKey) 402 | ))) 403 | } 404 | 405 | disabledModeExpress = t.jsxExpressionContainer( 406 | t.conditionalExpression( 407 | t.identifier('isEditMode'), 408 | t.JSXElement(t.jsxOpeningElement(t.jsxIdentifier('TableColumnRender'), attrArray, true), 409 | null, 410 | [], 411 | true 412 | ), 413 | t.callExpression( 414 | t.callExpression(t.identifier('getFieldDecorator'), [ 415 | t.stringLiteral(key), 416 | t.objectExpression(configArray) 417 | ]), 418 | itemExpression 419 | ) 420 | ) 421 | ) 422 | } 423 | 424 | const formItemExpression = t.jsxElement( 425 | t.jsxOpeningElement(t.jsxIdentifier('FormItem'), formItemAttrArray), 426 | t.jsxClosingElement(t.jsxIdentifier('FormItem')), 427 | [ 428 | blankLine, 429 | item.disabled ? disabledModeExpress : expression, 430 | blankLine, 431 | ] 432 | ) 433 | return formItemExpression 434 | 435 | } 436 | -------------------------------------------------------------------------------- /lib/react-antd-table/genCode/genUpsert/genUpsertUtil.js: -------------------------------------------------------------------------------- 1 | const groupBy = require('lodash/groupBy') 2 | const chunk = require('lodash/chunk') 3 | const t = require('@babel/types') 4 | const generate = require('@babel/generator').default 5 | const utils = require('../genUtils') 6 | const genFormItemUtil = require('./genFormItemUtil') 7 | 8 | const { 9 | renderInForm, 10 | unescapeToChinese, 11 | getBlankLine, 12 | } = utils 13 | 14 | const blankLine = getBlankLine() 15 | 16 | const genUpsertUtils = { 17 | getFieldLabels (DataSchema) { 18 | const filterForm = DataSchema.filter(item => renderInForm(item)) 19 | const array = filterForm.map(item => ( 20 | t.objectProperty(t.identifier(item.key), t.stringLiteral(item.title)) 21 | )) 22 | const ast = t.objectExpression(array) 23 | 24 | const result = unescapeToChinese(generate(ast).code) 25 | 26 | return result 27 | }, 28 | getFormItemByGroup (DataSchema, Config) { 29 | const hasGroup = DataSchema.some(item => item.group || item.showType === 'editor') 30 | 31 | const render = (rendererData, title) => { 32 | const rowArray = [blankLine] 33 | chunk(rendererData.filter(item => renderInForm(item)), 3).forEach(pItem => { 34 | const colArray = [blankLine] 35 | pItem.forEach(item => { 36 | const formItemArray = [] 37 | formItemArray.push( 38 | blankLine, 39 | genFormItemUtil(item, false, false, Config), 40 | blankLine 41 | ) 42 | let colAttrs = [ 43 | t.jsxAttribute(t.jsxIdentifier('lg'), t.jsxExpressionContainer(t.numericLiteral(8))), 44 | t.jsxAttribute(t.jsxIdentifier('md'), t.jsxExpressionContainer(t.numericLiteral(12))), 45 | t.jsxAttribute(t.jsxIdentifier('sm'), t.jsxExpressionContainer(t.numericLiteral(24))), 46 | ] 47 | // 富文本占整行 48 | if (item.showType === 'editor') { 49 | colAttrs = [ 50 | t.jsxAttribute(t.jsxIdentifier('span'), t.jsxExpressionContainer(t.numericLiteral(24))) 51 | ] 52 | } 53 | colArray.push( 54 | t.jsxElement( 55 | t.jsxOpeningElement(t.jsxIdentifier('Col'), colAttrs), 56 | t.jsxClosingElement(t.jsxIdentifier('Col')), 57 | [ 58 | blankLine, 59 | ...formItemArray, 60 | blankLine, 61 | ] 62 | ), blankLine 63 | ) 64 | }) 65 | 66 | rowArray.push( 67 | t.jsxElement( 68 | t.jsxOpeningElement(t.jsxIdentifier('Row'), [ 69 | t.jsxAttribute(t.jsxIdentifier('gutter'), t.jsxExpressionContainer(t.numericLiteral(16))), 70 | ]), 71 | t.jsxClosingElement(t.jsxIdentifier('Row')), 72 | [ 73 | ...colArray, 74 | blankLine, 75 | ] 76 | ), blankLine 77 | ) 78 | }) 79 | 80 | const ast = t.jsxElement( 81 | t.jsxOpeningElement(t.jsxIdentifier('Card'), [ 82 | t.jsxAttribute(t.jsxIdentifier('bordered'), t.jsxExpressionContainer(t.booleanLiteral(false))), 83 | t.jsxAttribute(t.jsxIdentifier('title'), t.stringLiteral(title && title !== 'undefined' ? title : '')), 84 | ]), 85 | t.jsxClosingElement(t.jsxIdentifier('Card')), 86 | [ 87 | ...rowArray, 88 | blankLine, 89 | ] 90 | ) 91 | 92 | return ast 93 | } 94 | 95 | let ast; 96 | if (hasGroup) { 97 | // 分组数据 98 | const filteredData = DataSchema.filter(item => item.group && item.showType !== 'editor') 99 | // 未分组数据 100 | const otherData = DataSchema.filter(item => !item.group && item.showType !== 'editor') 101 | // 未分组富文本数据 102 | const editorData = DataSchema.filter(item => item.showType === 'editor') 103 | 104 | const groupedData = groupBy(filteredData, 'group') 105 | const keys = Object.keys(groupedData) 106 | const jsxBodyArr = [blankLine] 107 | 108 | if (otherData && otherData.length) { 109 | jsxBodyArr.push(render(otherData), blankLine) 110 | } 111 | keys.forEach(item => { 112 | jsxBodyArr.push(render(groupedData[item], item), blankLine) 113 | }) 114 | if (editorData && editorData.length) { 115 | jsxBodyArr.push(render(editorData)) 116 | } 117 | jsxBodyArr.push(blankLine) 118 | 119 | ast = t.jsxFragment(t.jsxOpeningFragment(), t.jsxClosingFragment(), 120 | jsxBodyArr 121 | ) 122 | } else { 123 | ast = render(DataSchema) 124 | } 125 | 126 | const result = unescapeToChinese(generate(ast).code) 127 | 128 | return result 129 | }, 130 | /** 131 | * keys:要赋值的key的集合 132 | * condition: 模态框查看详情和编辑复用的模态框,查看模式下不需要useEffect 133 | */ 134 | injectEditorEffects(keys, condition) { 135 | if (!(keys && keys.length)) { 136 | return '' 137 | } 138 | // 对象属性集合 139 | const objectPropertyArray = keys.map(item => ( 140 | t.objectProperty( 141 | t.identifier(item), 142 | t.callExpression( 143 | t.memberExpression( 144 | t.identifier('BraftEditor'), 145 | t.identifier('createEditorState') 146 | ), 147 | [ 148 | t.memberExpression( 149 | t.identifier('current'), 150 | t.identifier(item) 151 | ) 152 | ] 153 | ) 154 | ) 155 | )) 156 | // 对象依赖集合 157 | const dependenceArray = keys.map(item => ( 158 | t.memberExpression( 159 | t.identifier('current'), 160 | t.identifier(item) 161 | ) 162 | )) 163 | 164 | const statement = t.expressionStatement( 165 | t.callExpression( 166 | t.memberExpression( 167 | t.memberExpression( 168 | t.identifier('props'), 169 | t.identifier('form') 170 | ), 171 | t.identifier('setFieldsValue') 172 | ), 173 | [ 174 | t.objectExpression(objectPropertyArray) 175 | ] 176 | ) 177 | ) 178 | 179 | const ast = t.expressionStatement( 180 | t.callExpression( 181 | t.identifier('useEffect'), 182 | [ 183 | t.arrowFunctionExpression( 184 | [], 185 | t.blockStatement( 186 | [ 187 | condition ? 188 | t.ifStatement( 189 | t.identifier(condition), 190 | t.blockStatement([statement]) 191 | ) 192 | : statement, 193 | ] 194 | ) 195 | ), 196 | t.arrayExpression(dependenceArray) 197 | ] 198 | ) 199 | ) 200 | 201 | return unescapeToChinese(generate(ast).code) 202 | } 203 | } 204 | 205 | module.exports = genUpsertUtils -------------------------------------------------------------------------------- /lib/react-antd-table/genCode/genUpsert/index.js: -------------------------------------------------------------------------------- 1 | const genUpsertUtil = require('./genUpsertUtil') 2 | const utils = require('../genUtils') 3 | 4 | const { getDicArray, injectVariables, getPrimaryKey, getEditorKeys } = utils 5 | const { getFieldLabels, getFormItemByGroup, injectEditorEffects } = genUpsertUtil 6 | 7 | module.exports = (tableConfig) => { 8 | 9 | const { 10 | Config, 11 | Config: { 12 | namespace, 13 | dictionary, 14 | defaultDateFormat 15 | }, 16 | DataSchema 17 | } = tableConfig 18 | const primaryKey = getPrimaryKey(DataSchema) 19 | const dicArray = getDicArray(dictionary) 20 | const injectValues = injectVariables(DataSchema) 21 | const editorKeys = getEditorKeys(DataSchema) 22 | 23 | return ` 24 | import React, { useEffect } from 'react'; 25 | import { Row, Col, Button, Form, Icon, Popover, DatePicker, InputNumber, Cascader, Card, Input } from 'antd'; 26 | import { connect } from 'dva'; 27 | import router from 'umi/router'; 28 | import moment from 'moment'; 29 | ${editorKeys && editorKeys.length ? ` 30 | import BraftEditor from 'braft-editor' 31 | ` : ''} 32 | import Editor from '@/components/ITableComponents/Editor'; 33 | import FooterToolbar from '@/components/ITableComponents/FooterToolbar'; 34 | import RadioGroup from '@/components/ITableComponents/RadioGroup'; 35 | import CheckboxGroup from '@/components/ITableComponents/CheckboxGroup'; 36 | import SelectGroup from '@/components/ITableComponents/SelectGroup'; 37 | import FileUpload from '@/components/ITableComponents/FileUpload'; 38 | import TableColumnRender from '@/components/ITableComponents/TableColumnRender'; 39 | import utils from '@/components/ITableComponents/utils'; 40 | import styles from './index.less'; 41 | 42 | const { getFormatFormValues } = utils; 43 | const FormItem = Form.Item; 44 | const { RangePicker } = DatePicker; 45 | const { TextArea } = Input; 46 | 47 | // 获取提示信息对应的title 48 | const fieldLabels = ${getFieldLabels(DataSchema)}; 49 | 50 | function UpsertForm(props) { 51 | const { 52 | dispatch, match, loading, 53 | form: { 54 | validateFieldsAndScroll, 55 | getFieldDecorator 56 | }, 57 | } = props; 58 | const { params } = match; 59 | const data = props.${namespace} 60 | const { current } = data 61 | const isEditMode = !!params.id 62 | 63 | ${injectValues} 64 | 65 | // Effect 66 | useEffect(() => { 67 | if (params.id) { 68 | dispatch({ 69 | type: '${namespace}/findById', 70 | payload: { ${primaryKey}: params.id } 71 | }); 72 | } else { 73 | dispatch({ 74 | type: '${namespace}/resetCurrent', 75 | }); 76 | } 77 | ${dicArray && dicArray.length ? 78 | dicArray.map(item => (`dispatch({ type: "${namespace}/__${item}" });`)).join('\n') 79 | : ''} 80 | }, [dispatch, params.id]) 81 | 82 | ${injectEditorEffects(editorKeys)} 83 | 84 | // 获取table内容 85 | const content = \n${getFormItemByGroup(DataSchema, Config)} 86 | // 返回 87 | function goBack () { 88 | router.goBack() 89 | } 90 | // 提交时获取校验信息 91 | function getErrorInfo () { 92 | const { 93 | form: { getFieldsError }, 94 | } = props; 95 | const errors = getFieldsError(); 96 | const errorCount = Object.keys(errors).filter(key => errors[key]).length; 97 | if (!errors || errorCount === 0) { 98 | return null; 99 | } 100 | const scrollToField = fieldKey => { 101 | const labelNode = document.querySelector(\`label[for="\${fieldKey}"]\`); 102 | if (labelNode) { 103 | labelNode.scrollIntoView(true); 104 | } 105 | }; 106 | const errorList = Object.keys(errors).map(key => { 107 | if (!errors[key]) { 108 | return null; 109 | } 110 | return ( 111 |
  • scrollToField(key)}> 112 | 113 |
    {errors[key][0]}
    114 |
    {fieldLabels[key]}
    115 |
  • 116 | ); 117 | }); 118 | return ( 119 | 120 | trigger.parentNode} 126 | > 127 | 128 | 129 | {errorCount} 130 | 131 | ); 132 | }; 133 | // 提交 134 | function handleSubmit (values, type) { 135 | dispatch({ 136 | type: '${namespace}/submit', 137 | payload: values, 138 | actionType: type, 139 | }); 140 | }; 141 | // 校验 142 | function validate () { 143 | validateFieldsAndScroll((error, fieldsValue) => { 144 | if (!error) { 145 | const values = getFormatFormValues(fieldsValue, '${defaultDateFormat}') 146 | // 编辑模式下,若主键的showInForm 设为false,则无法从fieldsValue中获取主键的值,需将current中对应字段获取到并拼接到当前值中 147 | if (isEditMode) { 148 | values.${primaryKey} = current.${primaryKey} 149 | } 150 | handleSubmit(values, isEditMode ? 'edit' : 'create'); 151 | } 152 | }); 153 | }; 154 | 155 | return ( 156 |
    157 |
    158 |
    159 | {content} 160 |
    161 |
    162 | 163 | {getErrorInfo()} 164 | 167 | 170 | 171 |
    172 | ); 173 | } 174 | 175 | export default connect((state) => { 176 | const { loading } = state 177 | return { 178 | ${namespace}: state.${namespace}, 179 | loading: loading.effects['${namespace}/submit'], 180 | } 181 | })(Form.create()(UpsertForm)) 182 | ` 183 | } -------------------------------------------------------------------------------- /lib/react-antd-table/genCode/genUtils/index.js: -------------------------------------------------------------------------------- 1 | const t = require('@babel/types') 2 | const _ = require('lodash') 3 | const generate = require('@babel/generator').default 4 | 5 | const defaultDateFormat = 'YYYY-MM-DD' 6 | 7 | function getValueExpression (value) { 8 | if (_.isBoolean(value)) { 9 | return t.booleanLiteral(value) 10 | } 11 | return t.stringLiteral(value) 12 | } 13 | 14 | function getItemOptionsExpression(options = []) { 15 | if (Array.isArray(options)) { 16 | if (options.length) { 17 | return t.arrayExpression(options.map(item => ( 18 | t.objectExpression([ 19 | t.objectProperty(t.identifier('key'), t.stringLiteral(item.key)), 20 | t.objectProperty(t.identifier('value'), t.stringLiteral(item.value)), 21 | ]) 22 | ))) 23 | 24 | } 25 | return t.arrayExpression([]) 26 | } 27 | return t.stringLiteral(options) 28 | } 29 | 30 | function unescapeToChinese (code ) { 31 | return unescape(code.replace(/\\u/g, '%u')) 32 | } 33 | 34 | function getInjectKey(key) { 35 | return `__${key}Options` 36 | } 37 | 38 | function getBlankLine() { 39 | return t.jsxText('\n') 40 | } 41 | 42 | function getInjectVariableKey (key) { 43 | return `__${key}` 44 | } 45 | 46 | const utils = { 47 | defaultDateFormat, 48 | getInjectKey, 49 | getInjectVariableKey, 50 | getUpdateHumpKey (key) { 51 | return `__update${key[0].toUpperCase()}${key.slice(1)}` 52 | }, 53 | unescapeToChinese, 54 | renderInForm (item, mode) { 55 | // create 模式下筛选排除primary 为true的字段(新增时主键不能填写) 56 | if (mode === 'create') { 57 | return item.key !== '__actions' && !item.actions && item.showInForm !== false 58 | && item.primary !== true 59 | } 60 | return item.key !== '__actions' && !item.actions && item.showInForm !== false 61 | }, 62 | getItemOptionsExpression, 63 | getItemFilterExpression(filters = []) { 64 | if (filters.length) { 65 | return t.arrayExpression(filters.map(item => ( 66 | t.objectExpression([ 67 | t.objectProperty(t.identifier('value'), t.stringLiteral(item.value.toString())), 68 | t.objectProperty(t.identifier('text'), t.stringLiteral(item.text)), 69 | ]) 70 | ))) 71 | } 72 | return t.arrayExpression([]) 73 | }, 74 | getBlankLine, 75 | getDicArray (dictionary) { 76 | if (dictionary && dictionary.length) { 77 | return dictionary.map(item => item.key) 78 | } 79 | return [] 80 | }, 81 | getItemRulesExpression(validator = []) { 82 | if (validator.length) { 83 | return t.arrayExpression(validator.map(item => { 84 | const array = [] 85 | const keys = Object.keys(item) 86 | keys.forEach(key => { 87 | array.push( 88 | t.objectProperty( 89 | t.identifier(key), 90 | getValueExpression(item[key]) 91 | ) 92 | ) 93 | }) 94 | return t.objectExpression(array) 95 | })) 96 | } 97 | return t.arrayExpression([]) 98 | }, 99 | /** 100 | * 获取FormItem initialValue 101 | * @param {String} item 102 | */ 103 | getDefaultValueExpression (item) { 104 | const { key, showType } = item; 105 | 106 | switch (showType) { 107 | case 'datePicker': 108 | return t.conditionalExpression( 109 | t.memberExpression(t.identifier('current'), t.identifier(key)), 110 | t.callExpression( 111 | t.identifier('moment'), 112 | [ 113 | t.memberExpression(t.identifier('current'), t.identifier(key)) 114 | ] 115 | ), 116 | t.nullLiteral() 117 | ) 118 | case 'rangePicker': 119 | return t.conditionalExpression( 120 | t.logicalExpression( 121 | "&&", 122 | t.memberExpression(t.identifier('current'), t.identifier(key)), 123 | t.binaryExpression( 124 | "===", 125 | t.memberExpression(t.memberExpression(t.identifier('current'), t.identifier(key)), t.identifier('length')), 126 | t.numericLiteral(2) 127 | ) 128 | ), 129 | t.arrayExpression([ 130 | t.callExpression( 131 | t.identifier('moment'), 132 | [ 133 | t.memberExpression(t.memberExpression(t.identifier('current'), t.identifier(key)), t.numericLiteral(0), true) 134 | ] 135 | ), 136 | t.callExpression( 137 | t.identifier('moment'), 138 | [ 139 | t.memberExpression(t.memberExpression(t.identifier('current'), t.identifier(key)), t.numericLiteral(1), true) 140 | ] 141 | ), 142 | ]), 143 | t.arrayExpression([]) 144 | ) 145 | case 'select': 146 | return t.logicalExpression( 147 | "||", 148 | t.memberExpression(t.identifier('current'), t.identifier(key)), 149 | t.arrayExpression([]) 150 | ) 151 | case 'inputSelect': 152 | return t.logicalExpression( 153 | "||", 154 | t.logicalExpression( 155 | "&&", 156 | t.memberExpression( 157 | t.identifier('current'), 158 | t.identifier(key) 159 | ), 160 | t.callExpression( 161 | t.memberExpression( 162 | t.memberExpression( 163 | t.identifier('current'), 164 | t.identifier(key), 165 | ), 166 | t.identifier('split') 167 | ), 168 | [ 169 | t.stringLiteral(',') 170 | ] 171 | ) 172 | ), 173 | 174 | t.arrayExpression([]) 175 | ) 176 | case 'multiSelect': 177 | return t.logicalExpression( 178 | "||", 179 | t.memberExpression(t.identifier('current'), t.identifier(key)), 180 | t.arrayExpression([]) 181 | ) 182 | default: 183 | return t.logicalExpression( 184 | "||", 185 | t.memberExpression(t.identifier('current'), t.identifier(key)), 186 | t.nullLiteral() 187 | ) 188 | } 189 | }, 190 | // 获取筛选项默认值。需从query中取值,并且注意string转换 191 | getFilterDefaultValueExpression (item) { 192 | const { key, showType } = item; 193 | 194 | switch (showType) { 195 | case 'datePicker': 196 | return t.conditionalExpression( 197 | t.memberExpression(t.identifier('query'), t.identifier(key)), 198 | t.callExpression( 199 | t.identifier('moment'), 200 | [ 201 | t.memberExpression(t.identifier('query'), t.identifier(key)) 202 | ] 203 | ), 204 | t.nullLiteral() 205 | ) 206 | case 'rangePicker': { 207 | const _expression = t.callExpression( 208 | t.memberExpression( 209 | t.memberExpression( 210 | t.identifier('query'), 211 | t.identifier(key) 212 | ), 213 | t.identifier('split') 214 | ), 215 | [t.stringLiteral(',')] 216 | ) 217 | return t.conditionalExpression( 218 | t.logicalExpression( 219 | "&&", 220 | t.memberExpression(t.identifier('query'), t.identifier(key)), 221 | t.binaryExpression( 222 | "===", 223 | t.memberExpression( 224 | _expression, 225 | t.identifier('length') 226 | ), 227 | t.numericLiteral(2) 228 | ) 229 | ), 230 | t.arrayExpression([ 231 | t.callExpression( 232 | t.identifier('moment'), 233 | [ 234 | t.memberExpression( 235 | _expression 236 | , t.numericLiteral(0), true) 237 | ] 238 | ), 239 | t.callExpression( 240 | t.identifier('moment'), 241 | [ 242 | t.memberExpression( 243 | _expression 244 | , t.numericLiteral(1), true) 245 | ] 246 | ), 247 | ]), 248 | t.arrayExpression([]) 249 | ) 250 | } 251 | case 'checkbox': 252 | return t.conditionalExpression( 253 | t.memberExpression(t.identifier('query'), t.identifier(key)), 254 | t.callExpression( 255 | t.memberExpression( 256 | t.memberExpression( 257 | t.identifier('query'), 258 | t.identifier(key) 259 | ), 260 | t.identifier('split') 261 | ), 262 | [t.stringLiteral(',')] 263 | ), 264 | t.arrayExpression([]) 265 | ) 266 | case 'multiSelect': 267 | return t.conditionalExpression( 268 | t.memberExpression(t.identifier('query'), t.identifier(key)), 269 | t.callExpression( 270 | t.memberExpression( 271 | t.memberExpression( 272 | t.identifier('query'), 273 | t.identifier(key) 274 | ), 275 | t.identifier('split') 276 | ), 277 | [t.stringLiteral(',')] 278 | ), 279 | t.arrayExpression([]) 280 | ) 281 | case 'cascader': 282 | return t.conditionalExpression( 283 | t.memberExpression(t.identifier('query'), t.identifier(key)), 284 | t.callExpression( 285 | t.memberExpression( 286 | t.memberExpression( 287 | t.identifier('query'), 288 | t.identifier(key) 289 | ), 290 | t.identifier('split') 291 | ), 292 | [t.stringLiteral(',')] 293 | ), 294 | t.arrayExpression([]) 295 | ) 296 | case 'select': 297 | return t.logicalExpression( 298 | "||", 299 | t.memberExpression(t.identifier('query'), t.identifier(key)), 300 | t.arrayExpression([]) 301 | ) 302 | default: 303 | return t.logicalExpression( 304 | "||", 305 | t.memberExpression(t.identifier('query'), t.identifier(key)), 306 | t.nullLiteral() 307 | ) 308 | } 309 | 310 | }, 311 | // 字典类型的值额外嵌入的变量 312 | injectVariables(data) { 313 | const result = [] 314 | data.filter(item => item.options).forEach(item => { 315 | const { options, key } = item; 316 | if (options) { 317 | let value = null 318 | if (Array.isArray(options)) { 319 | value = getItemOptionsExpression(options) 320 | } else { 321 | value = t.memberExpression(t.identifier('data'), t.identifier(options)) 322 | } 323 | 324 | const ast = t.variableDeclaration( 325 | "const", 326 | [ 327 | t.variableDeclarator( 328 | t.identifier(getInjectKey(key)), 329 | value 330 | ) 331 | ] 332 | ) 333 | result.push(unescapeToChinese(generate(ast).code)) 334 | result.push('\n') 335 | } 336 | }) 337 | return result.join('') 338 | }, 339 | getPrimaryKey (DataSchema) { 340 | const data = _.find(DataSchema, o => o.primary) 341 | if (data) { 342 | return data.key 343 | } 344 | throw new Error("当前数据未设置主键,请在dataSchema.js中选择一列配置 primary 为 true ") 345 | }, 346 | getEditorKeys (DataSchema) { 347 | return DataSchema 348 | .filter(item => item.showType === 'editor') 349 | .map(item => item.key) 350 | }, 351 | getUploadUrl (forImage, definedUrl, Config) { 352 | const { 353 | upload: { 354 | uploadUrl, 355 | imageApiUrl, 356 | fileApiUrl, 357 | image, 358 | file, 359 | } 360 | } = Config 361 | 362 | if (definedUrl) { 363 | if (definedUrl.startsWith('http')) { 364 | return definedUrl; 365 | } 366 | return `${(forImage ? imageApiUrl : fileApiUrl) || uploadUrl || ''}${definedUrl}`; 367 | } 368 | return `${(forImage ? imageApiUrl : fileApiUrl) || uploadUrl || ''}${forImage ? (image || '') : (file || '')}`; 369 | } 370 | } 371 | 372 | module.exports = utils -------------------------------------------------------------------------------- /lib/react-antd-table/genCode/index.js: -------------------------------------------------------------------------------- 1 | module.exports = function generator(config) { 2 | return { 3 | indexCode: require('./genIndex')(config), 4 | detailCode: require('./genDetail')(config), 5 | modelsCode: require('./genModels')(config), 6 | servicesCode: require('./genServices')(config), 7 | tableFilterCode: require('./genTableFilter')(config), 8 | tableFormCode: require('./genTableForm')(config), 9 | upsertCode: require('./genUpsert')(config), 10 | } 11 | } -------------------------------------------------------------------------------- /lib/react-antd-table/generator/addressBook.js: -------------------------------------------------------------------------------- 1 | const program = require('commander') 2 | 3 | // 需要生成的配置json路径 4 | const Config = require('../schema/addressBook/config') 5 | const DataSchema = require('../schema/addressBook/dataSchema') 6 | const QuerySchema = require('../schema/addressBook/querySchema') 7 | 8 | const genIndex = require('../genCode/genIndex') 9 | const genDetail = require('../genCode/genDetail') 10 | const genModels = require('../genCode/genModels') 11 | const genServices = require('../genCode/genServices') 12 | const genTableFilter = require('../genCode/genTableFilter') 13 | const genTableForm = require('../genCode/genTableForm') 14 | const genUpsert = require('../genCode/genUpsert') 15 | 16 | const insert = require('../scripts') 17 | 18 | const config = { Config, DataSchema, QuerySchema } 19 | 20 | const resultCode = { 21 | indexCode: genIndex(config), 22 | detailCode: genDetail(config), 23 | modelsCode: genModels(config), 24 | servicesCode: genServices(config), 25 | tableFilterCode: genTableFilter(config), 26 | tableFormCode: genTableForm(config), 27 | upsertCode: genUpsert(config), 28 | } 29 | 30 | program 31 | .option('-i, --ignore ', '忽视某些模块的生成,值包含model、service。\n多个以逗号分隔,无空格。', (value) => value.split(','), []) 32 | .parse(process.argv); 33 | 34 | let ignore = [] 35 | 36 | if (program.ignore) { 37 | ({ ignore } = program) 38 | } 39 | 40 | insert(resultCode, Config, ignore) 41 | 42 | module.exports = resultCode -------------------------------------------------------------------------------- /lib/react-antd-table/generator/banner.js: -------------------------------------------------------------------------------- 1 | const program = require('commander') 2 | 3 | // 需要生成的配置json路径 4 | const Config = require('../schema/banner/config') 5 | const DataSchema = require('../schema/banner/dataSchema') 6 | const QuerySchema = require('../schema/banner/querySchema') 7 | 8 | const genIndex = require('../genCode/genIndex') 9 | const genDetail = require('../genCode/genDetail') 10 | const genModels = require('../genCode/genModels') 11 | const genServices = require('../genCode/genServices') 12 | const genTableFilter = require('../genCode/genTableFilter') 13 | const genTableForm = require('../genCode/genTableForm') 14 | const genUpsert = require('../genCode/genUpsert') 15 | 16 | const insert = require('../scripts') 17 | 18 | const config = { Config, DataSchema, QuerySchema } 19 | 20 | const resultCode = { 21 | indexCode: genIndex(config), 22 | detailCode: genDetail(config), 23 | modelsCode: genModels(config), 24 | servicesCode: genServices(config), 25 | tableFilterCode: genTableFilter(config), 26 | tableFormCode: genTableForm(config), 27 | upsertCode: genUpsert(config), 28 | } 29 | 30 | program 31 | .option('-i, --ignore ', '忽视某些模块的生成,值包含model、service。\n多个以逗号分隔,无空格。', (value) => value.split(','), []) 32 | .parse(process.argv); 33 | 34 | let ignore = [] 35 | 36 | if (program.ignore) { 37 | ({ ignore } = program) 38 | } 39 | 40 | insert(resultCode, Config, ignore) 41 | 42 | module.exports = resultCode -------------------------------------------------------------------------------- /lib/react-antd-table/generator/bus.js: -------------------------------------------------------------------------------- 1 | const generator = require('../genCode') 2 | const insert = require('../scripts') 3 | 4 | const config = { 5 | Config: require('../schema/bus/config'), 6 | DataSchema: require('../schema/bus/dataSchema'), 7 | QuerySchema: require('../schema/bus/querySchema') 8 | } 9 | 10 | insert(generator(config), config.Config) -------------------------------------------------------------------------------- /lib/react-antd-table/generator/deep.js: -------------------------------------------------------------------------------- 1 | // 需要生成的配置json路径 2 | const Config = require('../schema/deep/config') 3 | const DataSchema = require('../schema/deep/dataSchema') 4 | const QuerySchema = require('../schema/deep/querySchema') 5 | 6 | const genIndex = require('../genCode/genIndex') 7 | const genDetail = require('../genCode/genDetail') 8 | const genModels = require('../genCode/genModels') 9 | const genServices = require('../genCode/genServices') 10 | const genTableFilter = require('../genCode/genTableFilter') 11 | const genTableForm = require('../genCode/genTableForm') 12 | const genUpsert = require('../genCode/genUpsert') 13 | 14 | const insert = require('../scripts') 15 | 16 | const config = { Config, DataSchema, QuerySchema } 17 | 18 | const resultCode = { 19 | indexCode: genIndex(config), 20 | detailCode: genDetail(config), 21 | modelsCode: genModels(config), 22 | servicesCode: genServices(config), 23 | tableFilterCode: genTableFilter(config), 24 | tableFormCode: genTableForm(config), 25 | upsertCode: genUpsert(config), 26 | } 27 | 28 | insert(resultCode, Config) 29 | 30 | module.exports = resultCode -------------------------------------------------------------------------------- /lib/react-antd-table/generator/normalList.js: -------------------------------------------------------------------------------- 1 | const program = require('commander') 2 | 3 | // 需要生成的配置json路径 4 | const Config = require('../schema/normalList/config') 5 | const DataSchema = require('../schema/normalList/dataSchema') 6 | const QuerySchema = require('../schema/normalList/querySchema') 7 | 8 | const genIndex = require('../genCode/genIndex') 9 | const genDetail = require('../genCode/genDetail') 10 | const genModels = require('../genCode/genModels') 11 | const genServices = require('../genCode/genServices') 12 | const genTableFilter = require('../genCode/genTableFilter') 13 | const genTableForm = require('../genCode/genTableForm') 14 | const genUpsert = require('../genCode/genUpsert') 15 | 16 | const insert = require('../scripts') 17 | 18 | const config = { Config, DataSchema, QuerySchema } 19 | 20 | const resultCode = { 21 | indexCode: genIndex(config), 22 | detailCode: genDetail(config), 23 | modelsCode: genModels(config), 24 | servicesCode: genServices(config), 25 | tableFilterCode: genTableFilter(config), 26 | tableFormCode: genTableForm(config), 27 | upsertCode: genUpsert(config), 28 | } 29 | 30 | program 31 | .option('-i, --ignore ', '忽视某些模块的生成,值包含model、service。\n多个以逗号分隔,无空格。', (value) => value.split(','), []) 32 | .parse(process.argv); 33 | 34 | let ignore = [] 35 | 36 | if (program.ignore) { 37 | ({ ignore } = program) 38 | } 39 | 40 | insert(resultCode, Config, ignore) 41 | 42 | module.exports = resultCode -------------------------------------------------------------------------------- /lib/react-antd-table/generator/place.js: -------------------------------------------------------------------------------- 1 | const program = require('commander') 2 | 3 | // 需要生成的配置json路径 4 | const Config = require('../schema/place/config') 5 | const DataSchema = require('../schema/place/dataSchema') 6 | const QuerySchema = require('../schema/place/querySchema') 7 | 8 | const genIndex = require('../genCode/genIndex') 9 | const genDetail = require('../genCode/genDetail') 10 | const genModels = require('../genCode/genModels') 11 | const genServices = require('../genCode/genServices') 12 | const genTableFilter = require('../genCode/genTableFilter') 13 | const genTableForm = require('../genCode/genTableForm') 14 | const genUpsert = require('../genCode/genUpsert') 15 | 16 | const insert = require('../scripts') 17 | 18 | const config = { Config, DataSchema, QuerySchema } 19 | 20 | const resultCode = { 21 | indexCode: genIndex(config), 22 | detailCode: genDetail(config), 23 | modelsCode: genModels(config), 24 | servicesCode: genServices(config), 25 | tableFilterCode: genTableFilter(config), 26 | tableFormCode: genTableForm(config), 27 | upsertCode: genUpsert(config), 28 | } 29 | 30 | program 31 | .option('-i, --ignore ', '忽视某些模块的生成,值包含model、service。\n多个以逗号分隔,无空格。', (value) => value.split(','), []) 32 | .parse(process.argv); 33 | 34 | let ignore = [] 35 | 36 | if (program.ignore) { 37 | ({ ignore } = program) 38 | } 39 | 40 | insert(resultCode, Config, ignore) 41 | 42 | module.exports = resultCode -------------------------------------------------------------------------------- /lib/react-antd-table/generator/questionnaire.js: -------------------------------------------------------------------------------- 1 | const program = require('commander') 2 | 3 | // 需要生成的配置json路径 4 | const Config = require('../schema/questionnaire/config') 5 | const DataSchema = require('../schema/questionnaire/dataSchema') 6 | const QuerySchema = require('../schema/questionnaire/querySchema') 7 | 8 | const genIndex = require('../genCode/genIndex') 9 | const genDetail = require('../genCode/genDetail') 10 | const genModels = require('../genCode/genModels') 11 | const genServices = require('../genCode/genServices') 12 | const genTableFilter = require('../genCode/genTableFilter') 13 | const genTableForm = require('../genCode/genTableForm') 14 | const genUpsert = require('../genCode/genUpsert') 15 | 16 | const insert = require('../scripts') 17 | 18 | const config = { Config, DataSchema, QuerySchema } 19 | 20 | const resultCode = { 21 | indexCode: genIndex(config), 22 | detailCode: genDetail(config), 23 | modelsCode: genModels(config), 24 | servicesCode: genServices(config), 25 | tableFilterCode: genTableFilter(config), 26 | tableFormCode: genTableForm(config), 27 | upsertCode: genUpsert(config), 28 | } 29 | 30 | program 31 | .option('-i, --ignore ', '忽视某些模块的生成,值包含model、service。\n多个以逗号分隔,无空格。', (value) => value.split(','), []) 32 | .parse(process.argv); 33 | 34 | let ignore = [] 35 | 36 | if (program.ignore) { 37 | ({ ignore } = program) 38 | } 39 | 40 | insert(resultCode, Config, ignore) 41 | 42 | module.exports = resultCode -------------------------------------------------------------------------------- /lib/react-antd-table/generator/resource.js: -------------------------------------------------------------------------------- 1 | const program = require('commander') 2 | 3 | // 需要生成的配置json路径 4 | const Config = require('../schema/resource/config') 5 | const DataSchema = require('../schema/resource/dataSchema') 6 | const QuerySchema = require('../schema/resource/querySchema') 7 | 8 | const genIndex = require('../genCode/genIndex') 9 | const genDetail = require('../genCode/genDetail') 10 | const genModels = require('../genCode/genModels') 11 | const genServices = require('../genCode/genServices') 12 | const genTableFilter = require('../genCode/genTableFilter') 13 | const genTableForm = require('../genCode/genTableForm') 14 | const genUpsert = require('../genCode/genUpsert') 15 | 16 | const insert = require('../scripts') 17 | 18 | const config = { Config, DataSchema, QuerySchema } 19 | 20 | const resultCode = { 21 | indexCode: genIndex(config), 22 | detailCode: genDetail(config), 23 | modelsCode: genModels(config), 24 | servicesCode: genServices(config), 25 | tableFilterCode: genTableFilter(config), 26 | tableFormCode: genTableForm(config), 27 | upsertCode: genUpsert(config), 28 | } 29 | 30 | program 31 | .option('-i, --ignore ', '忽视某些模块的生成,值包含model、service。\n多个以逗号分隔,无空格。', (value) => value.split(','), []) 32 | .parse(process.argv); 33 | 34 | let ignore = [] 35 | 36 | if (program.ignore) { 37 | ({ ignore } = program) 38 | } 39 | 40 | insert(resultCode, Config, ignore) 41 | 42 | module.exports = resultCode -------------------------------------------------------------------------------- /lib/react-antd-table/generator/tableList.js: -------------------------------------------------------------------------------- 1 | // 需要生成的配置json路径 2 | const Config = require('../schema/tableList/config') 3 | const DataSchema = require('../schema/tableList/dataSchema') 4 | const QuerySchema = require('../schema/tableList/querySchema') 5 | 6 | const genIndex = require('../genCode/genIndex') 7 | const genDetail = require('../genCode/genDetail') 8 | const genModels = require('../genCode/genModels') 9 | const genServices = require('../genCode/genServices') 10 | const genTableFilter = require('../genCode/genTableFilter') 11 | const genTableForm = require('../genCode/genTableForm') 12 | const genUpsert = require('../genCode/genUpsert') 13 | 14 | const insert = require('../scripts') 15 | 16 | const config = { Config, DataSchema, QuerySchema } 17 | 18 | const resultCode = { 19 | indexCode: genIndex(config), 20 | detailCode: genDetail(config), 21 | modelsCode: genModels(config), 22 | servicesCode: genServices(config), 23 | tableFilterCode: genTableFilter(config), 24 | tableFormCode: genTableForm(config), 25 | upsertCode: genUpsert(config), 26 | } 27 | 28 | insert(resultCode, Config) 29 | 30 | module.exports = resultCode -------------------------------------------------------------------------------- /lib/react-antd-table/index.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/no-dynamic-require */ 2 | const path = require('path') 3 | const generator = require('./genCode') 4 | const insert = require('./scripts') 5 | 6 | module.exports = function main (configFolderPath, generateFolderPath) { 7 | const config = { 8 | Config: require(path.join(configFolderPath, 'config')), 9 | DataSchema: require(path.join(configFolderPath, 'dataSchema')), 10 | QuerySchema: require(path.join(configFolderPath, 'querySchema')) 11 | } 12 | insert(generator(config), config.Config, { 13 | configFolderPath, 14 | generateFolderPath 15 | }) 16 | } -------------------------------------------------------------------------------- /lib/react-antd-table/scripts/generateCode.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | const chalk = require('chalk') 3 | const { exec } = require('child_process') 4 | const utils = require('./utils') 5 | 6 | const { log } = console 7 | const { 8 | recursiveMkdirSync, 9 | getEslintPath, 10 | getPutoutPath 11 | } = utils 12 | 13 | module.exports = (filePath, basePath, fileCode) => { 14 | const eslintPath = getEslintPath(basePath) 15 | const putoutPath = getPutoutPath() 16 | const fileFolderPath = filePath.substr(0, filePath.lastIndexOf('/')) 17 | const fileName = filePath.substr(filePath.lastIndexOf('/') + 1) 18 | 19 | // if parent folder exists 20 | if (!fs.existsSync(fileFolderPath)) { 21 | recursiveMkdirSync(fileFolderPath) 22 | } 23 | // write file 24 | fs.writeFileSync(filePath, fileCode) 25 | // 首先通过output插件完成移除未使用的变量 https://github.com/coderaiser/putout 26 | exec(`cd ${fileFolderPath} && node ${putoutPath} ${fileName} --fix`, (error) => { 27 | if (error) { 28 | log(chalk.red(`移除未使用变量失败!(${filePath})`)) 29 | log(chalk.red(error)) 30 | } 31 | // 移除未使用变量之后 run eslint fix即可 32 | exec(`${eslintPath} ${filePath} --fix`, (err) => { 33 | if (err) { 34 | log(chalk.red(`格式化代码未完全修复,需要手动修复!(${filePath})`)) 35 | return 36 | } 37 | log(chalk.green(`格式化代码完成!(${filePath})`)) 38 | }) 39 | }) 40 | } 41 | -------------------------------------------------------------------------------- /lib/react-antd-table/scripts/index.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | const path = require('path') 3 | const chalk = require('chalk') 4 | const utils = require('./utils') 5 | const generateCode = require('./generateCode') 6 | const config = require('../config') 7 | 8 | const { copyDir, getFileMapper, recursiveMkdirSync } = utils 9 | const { rootPath } = config 10 | const { log } = console 11 | 12 | function insert (code, tableConfig, options, ignore = []) { 13 | 14 | const { 15 | namespace, 16 | newRouterMode, 17 | singleDataMode, 18 | onlyTableListMode 19 | } = tableConfig 20 | const { configFolderPath, generateFolderPath } = options 21 | log('configFolderPath', configFolderPath) 22 | log(chalk.blue(`开始生成namespace为${namespace}模块代码:`)) 23 | 24 | const srcPath = generateFolderPath 25 | const baseFilePath = path.resolve(srcPath, namespace) 26 | const componentsPath = path.resolve(configFolderPath, '../../src/components/ITableComponents') 27 | 28 | const copyFilePath = path.resolve(rootPath, 'toCopyStaticFiles') 29 | const fileMapper = getFileMapper(namespace) 30 | 31 | if (!fs.existsSync(srcPath)) { 32 | log(chalk.red(`文件目录: (${srcPath})不存在,请确认文件路径是否正确.`)) 33 | return 34 | } 35 | 36 | // 先判断父级文件夹是否存在,若不存在则创建 37 | if (!fs.existsSync(baseFilePath)) { 38 | log(chalk.blue(`文件目录: (${baseFilePath})不存在,自动创建...`)) 39 | recursiveMkdirSync(baseFilePath) 40 | } 41 | 42 | // 判断依赖组件`src/components/ITableComponents`是否存在 43 | if (!fs.existsSync(componentsPath)) { 44 | log(chalk.blue(`依赖组件路径: (${componentsPath})不存在,自动创建并拷贝文件...`)) 45 | recursiveMkdirSync(componentsPath) 46 | copyDir(path.resolve(copyFilePath, 'ITableComponents'), path.resolve(srcPath, 'components')) 47 | } 48 | 49 | // 遍历模块生成代码 50 | const filenames = Object.keys(fileMapper) 51 | for (let i = 0;i < filenames.length; i += 1) { 52 | const filename = filenames[i] 53 | const fileCode = code[fileMapper[filename]] 54 | const filePath = `${baseFilePath}/${filename}` 55 | 56 | if (fileMapper[filename] === 'modelsCode') { 57 | if (ignore.indexOf('model') === -1) { 58 | generateCode(filePath, configFolderPath, fileCode) 59 | } 60 | } else if (fileMapper[filename] === 'servicesCode') { 61 | if (ignore.indexOf('service') === -1) { 62 | generateCode(filePath, configFolderPath, fileCode) 63 | } 64 | } else if (onlyTableListMode) { 65 | // 仅列表模式 66 | if (fileMapper[filename] !== 'upsertCode' && 67 | fileMapper[filename] !== 'tableFormCode') { 68 | generateCode(filePath, configFolderPath, fileCode) 69 | } 70 | } else if (singleDataMode) { 71 | if (fileMapper[filename] !== 'indexCode' && 72 | fileMapper[filename] !== 'tableFilterCode' && 73 | fileMapper[filename] !== 'tableFormCode') { 74 | // 单数据页面仅生成upsert 及detail 文件夹 75 | generateCode(filePath, configFolderPath, fileCode) 76 | } 77 | } else if (newRouterMode) { 78 | if (fileMapper[filename] !== 'tableFormCode') { 79 | generateCode(filePath, configFolderPath, fileCode) 80 | } 81 | } else if (fileMapper[filename] !== 'detailCode' && 82 | fileMapper[filename] !== 'upsertCode') { 83 | // 非新页面模式下不单独生成upsert 及detail 文件夹 84 | generateCode(filePath, configFolderPath, fileCode) 85 | } 86 | } 87 | 88 | // 复制静态资源 89 | // copyDir(path.resolve(copyFilePath, 'utils'), `${baseFilePath}`) 90 | if (onlyTableListMode) { 91 | // 仅列表模式下只拷贝筛选模块 92 | copyDir(path.resolve(copyFilePath, 'components/TableFilter'), `${baseFilePath}/components`) 93 | } else if (singleDataMode) { 94 | // 单数据页面只存在detail、upsert 95 | copyDir(path.resolve(copyFilePath, 'upsert'), `${baseFilePath}`) 96 | } else { 97 | copyDir(path.resolve(copyFilePath, 'components/TableFilter'), `${baseFilePath}/components`) 98 | if (newRouterMode) { 99 | // 新页面模式添加upsert生成 100 | copyDir(path.resolve(copyFilePath, 'upsert'), `${baseFilePath}`) 101 | } else { 102 | // 非新页面生成TableForm组件代码 103 | copyDir(path.resolve(copyFilePath, 'components/TableForm'), `${baseFilePath}/components`) 104 | } 105 | } 106 | 107 | log(chalk.green(`代码生成成功!`)) 108 | log(chalk.blue(`代码路径:${baseFilePath}`)) 109 | log(chalk.green('正在进行eslint格式化代码...')) 110 | } 111 | 112 | module.exports = insert -------------------------------------------------------------------------------- /lib/react-antd-table/scripts/utils.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | const path = require('path') 3 | const childProcess = require('child_process') 4 | const { rootPath } = require('../config') 5 | 6 | // 文件路径和代码对应关系 7 | function getFileMapper (namespace) { 8 | return { 9 | 'index.js': 'indexCode', 10 | 'detail/index.js': 'detailCode', 11 | [`models/${namespace}.js`]: 'modelsCode', 12 | [`services/${namespace}.js`]: 'servicesCode', 13 | 'components/TableFilter/index.js': 'tableFilterCode', 14 | 'components/TableForm/index.js': 'tableFormCode', 15 | 'upsert/index.js': 'upsertCode', 16 | } 17 | } 18 | 19 | function recursiveMkdirSync(dirname) { 20 | if (fs.existsSync(dirname)) { 21 | return true; 22 | } 23 | if (recursiveMkdirSync(path.dirname(dirname))) { 24 | fs.mkdirSync(dirname); 25 | return true; 26 | } 27 | return true; 28 | } 29 | 30 | function copyDir(src, dist) { 31 | childProcess.spawn('cp', ['-r', src, dist]); 32 | } 33 | 34 | function getEslintPath (basePath) { 35 | return path.resolve(basePath, '../../node_modules/.bin/eslint') 36 | } 37 | 38 | function getPutoutPath() { 39 | return path.resolve(rootPath, '../../node_modules/putout/bin/putout') 40 | } 41 | 42 | module.exports = { 43 | getFileMapper, 44 | recursiveMkdirSync, 45 | copyDir, 46 | getEslintPath, 47 | getPutoutPath 48 | } 49 | -------------------------------------------------------------------------------- /lib/react-antd-table/toCopyStaticFiles/ITableComponents/CheckboxGroup/index.js: -------------------------------------------------------------------------------- 1 | import React, { forwardRef } from 'react' 2 | import { Checkbox } from 'antd' 3 | 4 | // antd form getFieldDecorator 包裹的组件必须有ref。RadioGroup SelectGroup相同 5 | 6 | const CheckboxGroup = (props, ref) => { 7 | const { source, value, onChange } = props 8 | 9 | return ( 10 | 16 | { 17 | source && source.map(i => ( 18 | {i.value} 19 | )) 20 | } 21 | 22 | ) 23 | } 24 | 25 | export default forwardRef(CheckboxGroup) -------------------------------------------------------------------------------- /lib/react-antd-table/toCopyStaticFiles/ITableComponents/ColumnAction/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const ColumnAction = (props) => { 4 | const { 5 | // 本期暂不支持以下字段配置 6 | // keys, 7 | // visible, 8 | // component, 9 | // render, 10 | primaryKey, 11 | name, 12 | type, 13 | record, 14 | visible, 15 | onClick 16 | } = props 17 | 18 | function getAction () { 19 | switch(type) { 20 | case 'detail': 21 | return visible ? onClick(record, 'detail')}>{name || '查看'} : null 22 | case 'update': 23 | return visible ? onClick(record, 'edit')}>{name || '编辑'} : null 24 | case 'delete': 25 | return visible ? onClick(record[primaryKey])}>{name || '删除'} : null 26 | case 'newLine': 27 | return
    28 | default: 29 | return null 30 | } 31 | } 32 | 33 | return getAction(); 34 | } 35 | 36 | export default ColumnAction -------------------------------------------------------------------------------- /lib/react-antd-table/toCopyStaticFiles/ITableComponents/DescriptionList/Description.d.ts: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { ColProps } from 'antd/es/col'; 3 | 4 | export interface DescriptionProps extends ColProps { 5 | column?: number; 6 | key?: string | number; 7 | style?: React.CSSProperties; 8 | term?: React.ReactNode; 9 | } 10 | 11 | export default class Description extends React.Component {} 12 | -------------------------------------------------------------------------------- /lib/react-antd-table/toCopyStaticFiles/ITableComponents/DescriptionList/Description.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Col } from 'antd'; 3 | import styles from './index.less'; 4 | import responsive from './responsive'; 5 | 6 | const Description = ({ term, column, children, ...restProps }) => ( 7 | 8 | {term &&
    {term}
    } 9 | {children !== null && children !== undefined &&
    {children}
    } 10 | 11 | ); 12 | 13 | Description.defaultProps = { 14 | term: '', 15 | }; 16 | 17 | export default Description; 18 | -------------------------------------------------------------------------------- /lib/react-antd-table/toCopyStaticFiles/ITableComponents/DescriptionList/DescriptionList.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import classNames from 'classnames'; 3 | import { Row } from 'antd'; 4 | import styles from './index.less'; 5 | 6 | const DescriptionList = ({ 7 | className, 8 | title, 9 | col = 3, 10 | layout = 'horizontal', 11 | gutter = 32, 12 | children, 13 | size, 14 | ...restProps 15 | }) => { 16 | const clsString = classNames(styles.descriptionList, styles[layout], className, { 17 | [styles.small]: size === 'small', 18 | [styles.large]: size === 'large', 19 | }); 20 | const column = col > 4 ? 4 : col; 21 | return ( 22 |
    23 | {title ?
    {title}
    : null} 24 | 25 | {React.Children.map(children, child => 26 | child ? React.cloneElement(child, { column }) : child 27 | )} 28 | 29 |
    30 | ); 31 | }; 32 | 33 | export default DescriptionList; 34 | -------------------------------------------------------------------------------- /lib/react-antd-table/toCopyStaticFiles/ITableComponents/DescriptionList/demo/basic.md: -------------------------------------------------------------------------------- 1 | --- 2 | order: 0 3 | title: 4 | zh-CN: 基本 5 | en-US: Basic 6 | --- 7 | 8 | ## zh-CN 9 | 10 | 基本描述列表。 11 | 12 | ## en-US 13 | 14 | Basic DescriptionList. 15 | 16 | ````jsx 17 | import DescriptionList from 'ant-design-pro/lib/DescriptionList'; 18 | 19 | const { Description } = DescriptionList; 20 | 21 | ReactDOM.render( 22 | 23 | 24 | A free, open source, cross-platform, 25 | graphical web browser developed by the 26 | Mozilla Corporation and hundreds of 27 | volunteers. 28 | 29 | 30 | A free, open source, cross-platform, 31 | graphical web browser developed by the 32 | Mozilla Corporation and hundreds of 33 | volunteers. 34 | 35 | 36 | A free, open source, cross-platform, 37 | graphical web browser developed by the 38 | Mozilla Corporation and hundreds of 39 | volunteers. 40 | 41 | 42 | , mountNode); 43 | ```` 44 | -------------------------------------------------------------------------------- /lib/react-antd-table/toCopyStaticFiles/ITableComponents/DescriptionList/demo/vertical.md: -------------------------------------------------------------------------------- 1 | --- 2 | order: 1 3 | title: 4 | zh-CN: 垂直型 5 | en-US: Vertical 6 | --- 7 | 8 | ## zh-CN 9 | 10 | 垂直布局。 11 | 12 | ## en-US 13 | 14 | Vertical layout. 15 | 16 | ````jsx 17 | import DescriptionList from 'ant-design-pro/lib/DescriptionList'; 18 | 19 | const { Description } = DescriptionList; 20 | 21 | ReactDOM.render( 22 | 23 | 24 | A free, open source, cross-platform, 25 | graphical web browser developed by the 26 | Mozilla Corporation and hundreds of 27 | volunteers. 28 | 29 | 30 | A free, open source, cross-platform, 31 | graphical web browser developed by the 32 | Mozilla Corporation and hundreds of 33 | volunteers. 34 | 35 | 36 | A free, open source, cross-platform, 37 | graphical web browser developed by the 38 | Mozilla Corporation and hundreds of 39 | volunteers. 40 | 41 | 42 | , mountNode); 43 | ```` 44 | -------------------------------------------------------------------------------- /lib/react-antd-table/toCopyStaticFiles/ITableComponents/DescriptionList/index.d.ts: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Description, { DescriptionProps } from './Description'; 3 | 4 | export interface DescriptionListProps { 5 | className?: string; 6 | col?: number; 7 | description?: DescriptionProps[]; 8 | gutter?: number; 9 | layout?: 'horizontal' | 'vertical'; 10 | size?: 'large' | 'small'; 11 | style?: React.CSSProperties; 12 | title?: React.ReactNode; 13 | } 14 | 15 | export default class DescriptionList extends React.Component { 16 | public static Description: typeof Description; 17 | } 18 | -------------------------------------------------------------------------------- /lib/react-antd-table/toCopyStaticFiles/ITableComponents/DescriptionList/index.en-US.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: DescriptionList 3 | cols: 1 4 | order: 4 5 | --- 6 | 7 | Groups display multiple read-only fields, which are common to informational displays on detail pages. 8 | 9 | ## API 10 | 11 | ### DescriptionList 12 | 13 | | Property | Description | Type | Default | 14 | |----------|------------------------------------------|-------------|---------| 15 | | layout | type of layout | Enum{'horizontal', 'vertical'} | 'horizontal' | 16 | | col | specify the maximum number of columns to display, the final columns number is determined by col setting combined with [Responsive Rules](/components/DescriptionList#Responsive-Rules) | number(0 < col <= 4) | 3 | 17 | | title | title | ReactNode | - | 18 | | gutter | specify the distance between two items, unit is `px` | number | 32 | 19 | | size | size of list | Enum{'large', 'small'} | - | 20 | 21 | #### Responsive Rules 22 | 23 | | Window Width | Columns Number | 24 | |---------------------|---------------------------------------------| 25 | | `≥768px` | `col` | 26 | | `≥576px` | `col < 2 ? col : 2` | 27 | | `<576px` | `1` | 28 | 29 | ### DescriptionList.Description 30 | 31 | | Property | Description | Type | Default | 32 | |----------|------------------------------------------|-------------|-------| 33 | | term | item title | ReactNode | - | 34 | -------------------------------------------------------------------------------- /lib/react-antd-table/toCopyStaticFiles/ITableComponents/DescriptionList/index.js: -------------------------------------------------------------------------------- 1 | import DescriptionList from './DescriptionList'; 2 | import Description from './Description'; 3 | 4 | DescriptionList.Description = Description; 5 | export default DescriptionList; 6 | -------------------------------------------------------------------------------- /lib/react-antd-table/toCopyStaticFiles/ITableComponents/DescriptionList/index.less: -------------------------------------------------------------------------------- 1 | @import '~antd/lib/style/themes/default.less'; 2 | 3 | .descriptionList { 4 | margin-bottom: 32px; 5 | // offset the padding-bottom of last row 6 | :global { 7 | .ant-row { 8 | margin-bottom: -16px; 9 | overflow: hidden; 10 | } 11 | } 12 | // fix margin top error of following descriptionList 13 | & + & { 14 | :global { 15 | .ant-row { 16 | margin-top: 16px; 17 | } 18 | } 19 | } 20 | 21 | .title { 22 | margin-bottom: 16px; 23 | color: @heading-color; 24 | font-weight: 500; 25 | font-size: 14px; 26 | } 27 | 28 | .term { 29 | display: table-cell; 30 | padding-bottom: 16px; 31 | color: @heading-color; 32 | // Line-height is 22px IE dom height will calculate error 33 | line-height: 20px; 34 | white-space: nowrap; 35 | vertical-align: top; 36 | 37 | &::after { 38 | position: relative; 39 | top: -0.5px; 40 | margin: 0 8px 0 2px; 41 | content: ':'; 42 | } 43 | } 44 | 45 | .detail { 46 | display: table-cell; 47 | width: 100%; 48 | padding-bottom: 16px; 49 | color: @text-color; 50 | line-height: 20px; 51 | } 52 | 53 | &.small { 54 | // offset the padding-bottom of last row 55 | :global { 56 | .ant-row { 57 | margin-bottom: -8px; 58 | } 59 | } 60 | // fix margin top error of following descriptionList 61 | & + .descriptionList { 62 | :global { 63 | .ant-row { 64 | margin-top: 8px; 65 | } 66 | } 67 | } 68 | .title { 69 | margin-bottom: 12px; 70 | color: @text-color; 71 | } 72 | .term, 73 | .detail { 74 | padding-bottom: 8px; 75 | } 76 | } 77 | 78 | &.large { 79 | .title { 80 | font-size: 16px; 81 | } 82 | } 83 | 84 | &.vertical { 85 | .term { 86 | display: block; 87 | padding-bottom: 8px; 88 | } 89 | 90 | .detail { 91 | display: block; 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /lib/react-antd-table/toCopyStaticFiles/ITableComponents/DescriptionList/index.zh-CN.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: DescriptionList 3 | subtitle: 描述列表 4 | cols: 1 5 | order: 4 6 | --- 7 | 8 | 成组展示多个只读字段,常见于详情页的信息展示。 9 | 10 | ## API 11 | 12 | ### DescriptionList 13 | 14 | | 参数 | 说明 | 类型 | 默认值 | 15 | |----------|------------------------------------------|-------------|-------| 16 | | layout | 布局方式 | Enum{'horizontal', 'vertical'} | 'horizontal' | 17 | | col | 指定信息最多分几列展示,最终一行几列由 col 配置结合[响应式规则](/components/DescriptionList#响应式规则)决定 | number(0 < col <= 4) | 3 | 18 | | title | 列表标题 | ReactNode | - | 19 | | gutter | 列表项间距,单位为 `px` | number | 32 | 20 | | size | 列表型号 | Enum{'large', 'small'} | - | 21 | 22 | #### 响应式规则 23 | 24 | | 窗口宽度 | 展示列数 | 25 | |---------------------|---------------------------------------------| 26 | | `≥768px` | `col` | 27 | | `≥576px` | `col < 2 ? col : 2` | 28 | | `<576px` | `1` | 29 | 30 | ### DescriptionList.Description 31 | 32 | | 参数 | 说明 | 类型 | 默认值 | 33 | |----------|------------------------------------------|-------------|-------| 34 | | term | 列表项标题 | ReactNode | - | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /lib/react-antd-table/toCopyStaticFiles/ITableComponents/DescriptionList/responsive.js: -------------------------------------------------------------------------------- 1 | export default { 2 | 1: { xs: 24 }, 3 | 2: { xs: 24, sm: 12 }, 4 | 3: { xs: 24, sm: 12, md: 8 }, 5 | 4: { xs: 24, sm: 12, md: 6 }, 6 | }; 7 | -------------------------------------------------------------------------------- /lib/react-antd-table/toCopyStaticFiles/ITableComponents/Editor/index.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from 'react' 2 | import BraftEditor from 'braft-editor' 3 | import ColorPicker from 'braft-extensions/dist/color-picker' 4 | 5 | import 'braft-editor/dist/index.css' 6 | import 'braft-extensions/dist/color-picker.css' 7 | 8 | // TODO 添加自己的上传文件的地址 9 | const fileUploadUrl = '' 10 | 11 | BraftEditor.use(ColorPicker({ 12 | theme: 'light' // 支持dark和light两种主题,默认为dark 13 | })) 14 | 15 | /** 16 | * 富文本编辑器 17 | * 此处不能用function来定义,尝试使用forwardRef仍会造成死循环 18 | */ 19 | export default class Editor extends PureComponent{ 20 | constructor(props){ 21 | super(props); 22 | this.state = { 23 | editorState: BraftEditor.createEditorState(null), 24 | }; 25 | } 26 | 27 | componentWillReceiveProps(nextProps) { 28 | if ('value' in nextProps) { 29 | const editorState = nextProps.value; 30 | this.setState({ 31 | editorState, 32 | }); 33 | } 34 | } 35 | 36 | triggerChange = (editorState) => { 37 | const { onChange } = this.props; 38 | if (onChange) { 39 | onChange(editorState); 40 | } 41 | } 42 | 43 | handleEditorChange = (editorState) => { 44 | this.setState({ 45 | editorState, 46 | }); 47 | this.triggerChange( 48 | editorState, 49 | ); 50 | }; 51 | 52 | // 图片上传 53 | uploadFn = (param) => { 54 | 55 | const serverURL = fileUploadUrl 56 | const xhr = new XMLHttpRequest 57 | const fd = new FormData() 58 | 59 | const successFn = (response) => { 60 | const { target } = response; 61 | if (target.response) { 62 | const res = JSON.parse(target.response) 63 | if (res) { 64 | if (res.errorCode === 0) { 65 | // 调用param.progress告知编辑器上传成功后的文件地址 66 | param.success({ 67 | url: res.data 68 | }) 69 | } 70 | } 71 | } 72 | } 73 | 74 | const progressFn = (event) => { 75 | // 调用param.progress告知编辑器当前的上传进度 76 | param.progress(event.loaded / event.total * 100) 77 | } 78 | 79 | const errorFn = () => { 80 | // 调用param.progress告知编辑器上传发生了问题 81 | param.error({ 82 | msg: 'unable to upload.' 83 | }) 84 | } 85 | 86 | xhr.upload.addEventListener("progress", progressFn, false) 87 | xhr.addEventListener("load", successFn, false) 88 | xhr.addEventListener("error", errorFn, false) 89 | xhr.addEventListener("abort", errorFn, false) 90 | 91 | fd.append('file', param.file) 92 | xhr.open('POST', serverURL, true) 93 | xhr.send(fd) 94 | 95 | } 96 | 97 | // 预览 98 | preview = () => { 99 | 100 | if (window.previewWindow) { 101 | window.previewWindow.close() 102 | } 103 | 104 | window.previewWindow = window.open() 105 | window.previewWindow.document.write(this.buildPreviewHtml()) 106 | window.previewWindow.document.close() 107 | } 108 | 109 | // 构建预览html 110 | buildPreviewHtml () { 111 | return ` 112 | 113 | 114 | 115 | 预览 116 | 159 | 160 | 161 |
    ${this.state.editorState.toHTML()}
    162 | 163 | 164 | ` 165 | } 166 | 167 | render(){ 168 | const { editorState } = this.state; 169 | const { placeholder='请输入描述', } = this.props; 170 | const extendControls = [ 171 | { 172 | key: 'custom-button', 173 | type: 'button', 174 | text: '预览', 175 | onClick: this.preview 176 | } 177 | ] 178 | const imageControls = [ 179 | 'float-left', // 设置图片左浮动 180 | 'float-right', // 设置图片右浮动 181 | 'align-left', // 设置图片居左 182 | 'align-center', // 设置图片居中 183 | 'align-right', // 设置图片居右 184 | 'link', // 设置图片超链接 185 | // 'size', // 设置图片尺寸 禁用图片大小调整 186 | 'remove' // 删除图片 187 | ] 188 | 189 | return ( 190 | 209 | ); 210 | } 211 | 212 | } -------------------------------------------------------------------------------- /lib/react-antd-table/toCopyStaticFiles/ITableComponents/FileUpload/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import qs from 'querystring' 3 | import ImgCrop from "antd-img-crop"; 4 | import { Upload, Icon, Modal, message, Button, Tooltip } from 'antd'; 5 | import './index.less'; 6 | 7 | function isString(str) { 8 | return typeof(str) === 'string' || str instanceof String 9 | } 10 | 11 | // 读取全局api路径,若上传地址非http开头,则会拼接全局api地址 12 | // TODO 添加自己的上传文件的地址 13 | const fileUploadUrl = '' 14 | 15 | /** 16 | * 可以上传图片, 也可以上传普通文件, 样式会不一样, 通过props中传入的type字段判断 17 | */ 18 | class FileUpload extends React.Component { 19 | 20 | // 注意这个组件不能做成PureComponent, 会有bug, 因为上传的过程中会不断触发onChange, 进而导致状态不断变化 21 | constructor(props) { 22 | super(props) 23 | this.state = { 24 | previewVisible: false, // 是否显示图片预览modal 25 | previewImage: '', // 要预览的图片 26 | fileList: props.value ? [ 27 | { 28 | uid: -1, 29 | name: props.value.substr(props.value.lastIndexOf('/') + 1), 30 | status: 'done', 31 | url: props.value 32 | } 33 | ] : [], // 已经上传的文件列表 34 | }; 35 | } 36 | 37 | componentWillMount() { 38 | const { value, max, url, type, sizeLimit, accept } = this.props; 39 | // 当前是要上传文件还是普通图片 40 | const forImage = type === 'image'; 41 | if (forImage) { 42 | this.listType = 'picture-card'; // 对于图片类型的上传, 要显示缩略图 43 | } else { 44 | this.listType = 'text'; // 对于其他类型的上传, 只显示个文件名就可以了 45 | } 46 | 47 | // 组件第一次加载的时候, 设置默认值 48 | this.forceUpdateStateByValue(value, max); 49 | 50 | // 是否自定义了图片上传的路径 51 | if (url.startsWith('http')) { 52 | 53 | this.uploadUrl = url 54 | } else { 55 | this.uploadUrl = `${fileUploadUrl}${url}` 56 | } 57 | 58 | // 上传时的文件大小限制 59 | if (sizeLimit) { 60 | this.sizeLimit = sizeLimit; 61 | } 62 | 63 | // 允许上传的文件类型 64 | if (accept) { 65 | this.accept = accept; 66 | } else if (forImage) { 67 | this.accept = '.jpg,.png,.gif,.jpeg'; // 上传图片时有默认的accept 68 | } 69 | 70 | this.forImage = forImage; 71 | } 72 | 73 | componentWillReceiveProps(nextProps) { 74 | // 如果上层通过props传过来一个value, 要不要根据value更新文件列表? 75 | // 对于普通的controlled-components而言, 是应该更新的, 相当于本身没有状态, 完全被上层控制 76 | // 但这个组件不是完全的controlled-components...只会向外暴露value, 但也有自己的状态 77 | 78 | // 传进来的value有两种情况: 79 | // 1. 本身状态变化后, 通过onChange回调向外暴露, 状态又会通过this.props.value的形式回传, 这种情况不需要更新 80 | // 2. 外界直接setFieldValue, 直接改变这个组件的状态, 这种情况下需要更新 81 | if (this.needRender(nextProps)) { 82 | const { value, max } = nextProps; 83 | this.forceUpdateStateByValue(value, max); 84 | } 85 | } 86 | 87 | /** 88 | * 调用上传接口之前校验一次 89 | * 90 | * @param file 91 | * @returns {boolean} 92 | */ 93 | beforeUpload = (file) => { 94 | if (this.sizeLimit) { 95 | if (file.size / 1024 > this.sizeLimit) { 96 | message.error(`${this.forImage ? '图片' : '文件'}过大,最大只允许${this.sizeLimit}KB`); 97 | return false; 98 | } 99 | } 100 | 101 | return true; 102 | }; 103 | 104 | /** 105 | * 点击预览按钮 106 | * 107 | * @param file 108 | */ 109 | handlePreview = (file) => { 110 | this.setState({ 111 | previewImage: file.url || file.thumbUrl, 112 | previewVisible: true, 113 | }); 114 | }; 115 | 116 | /** 117 | * 预览界面点击取消按钮 118 | */ 119 | handleCancel = () => this.setState({ previewVisible: false }); 120 | 121 | /** 122 | * 上传文件时的回调, 注意上传过程中会被调用多次 123 | * 删除图片时也会触发这个方法 124 | * 参考: https://ant.design/components/upload-cn/#onChange 125 | * 126 | * @param fileList 127 | */ 128 | handleChange = ({ file, fileList }) => { 129 | // 还要自己处理一下fileList 130 | // eslint-disable-next-line no-restricted-syntax 131 | for (const tmp of fileList) { 132 | if (tmp.status === 'done' && !tmp.url && tmp.response && 133 | // 兼容几种常见返回判断 134 | (tmp.response.code && tmp.response.code.toString() === '200' || 135 | tmp.response.errorCode === 0)) { 136 | tmp.url = tmp.response.data; // 服务端返回的url 137 | } 138 | } 139 | 140 | // 上传失败 141 | if (file.status === 'error') { 142 | message.error(`${file.name}上传失败`, 2.5); 143 | } 144 | // 上传成功 or 删除图片 145 | else if (file.status === 'done' || file.status === 'removed') { 146 | this.notifyFileChange(); 147 | } 148 | // 其实还有正在上传(uploading)/错误(error)的状态, 不过这里不关心 149 | 150 | // 注意对于controlled components而言, 这步setState必不可少 151 | // 见https://github.com/ant-design/ant-design/issues/2423 152 | this.setState({ fileList }); 153 | 154 | // 其实这里可能有点小问题 155 | // notifyFileChange方法会通知上层组件, 文件列表变化了, 对于antd的FormItem而言, 新的值又会通过props.value的形式回传, 导致re-render 156 | // 也就是说, 在已经调用过notifyFileChange的情况下, 其实不需要上面的手动setState再触发re-render, 有点重复, 效率上可能会受点影响 157 | // 但我还是决定保留setState, 因为props.value其实是antd中受控表单组件的特殊逻辑, 而这个组件可能不只用于FormItem 158 | }; 159 | 160 | /** 161 | * 文件列表变化后, 通知上层 162 | */ 163 | notifyFileChange = () => { 164 | const { onChange, max } = this.props; 165 | const { fileList } = this.state 166 | 167 | if (onChange) { 168 | // 传给回调的参数可能是个string, 也可能是个array, 要判断一下 169 | let res; 170 | if (max === 1) { 171 | // 这里要判断下状态, 因为文件被删除后状态会变为removed 172 | // 只返回给上层"正确"的图片 173 | if (fileList.length > 0 && fileList[0].status === 'done') { 174 | res = fileList[0].url; 175 | } else { 176 | res = ''; 177 | } 178 | } else { 179 | res = fileList.filter(file => file.status === 'done').map(file => file.url); // 注意先filter再map, 因为map必须是一一对应的 180 | // 如果res是undefined, 那对应的, 后端收到的就是null; 如果res是空的数组, 后端收到的就是一个空的List. 注意这两种区别. 181 | } 182 | 183 | // 这个回调配合getValueFromEvent可以定制如何从组件中取值, 很方便, 参考: https://ant.design/components/form-cn/#getFieldDecorator(id,-options)-参数 184 | // 但是我这次没用到, 因为默认的getValueFromEvent已经可以满足需求 185 | onChange(res); 186 | } 187 | }; 188 | 189 | /** 190 | * 强制更新state中的上传文件列表 191 | * 192 | * @param value 193 | * @param max 194 | */ 195 | forceUpdateStateByValue(value, max) { 196 | const { fileList } = this.state 197 | // 首先清空文件列表 198 | fileList.length = 0; 199 | // 注意传进来的value可能是个空字符串 200 | if (isString(value) && value.length > 0) { 201 | fileList.push({ 202 | uid: -1, 203 | name: value.substr(value.lastIndexOf('/') + 1), // 取url中的最后一部分作为文件名 204 | status: 'done', 205 | url: value, 206 | }); 207 | } else if (value instanceof Array) { 208 | if (max === 1 && value.length > 0) { 209 | // 如果max=1, 正常情况下value应该是个string的 210 | // 但如果传进来一个数组, 就只取第一个元素 211 | fileList.push({ 212 | uid: -1, 213 | name: value[0].substr(value[0].lastIndexOf('/') + 1), 214 | status: 'done', 215 | url: value[0], 216 | }); 217 | } else { 218 | for (let i = 0; i < value.length; i += 1) { 219 | fileList.push({ 220 | uid: -1 - i, 221 | name: value[i].substr(value[i].lastIndexOf('/') + 1), 222 | status: 'done', 223 | url: value[i], 224 | }); 225 | } 226 | } 227 | } 228 | } 229 | 230 | /** 231 | * 将props中传过来的value跟当前的state相比较, 看是否要更新state 232 | * @param nextProps 233 | * @returns {boolean} 234 | */ 235 | needRender(nextProps) { 236 | const { value } = nextProps; 237 | const { fileList } = this.state 238 | if (!value) { 239 | return true; 240 | } 241 | 242 | const fileArray = fileList.filter(file => file.status === 'done'); 243 | if (isString(value)) { 244 | if (fileArray.length !== 1 || value !== fileArray[0].url) { // 当前没有上传文件, 或者已经上传的文件和外界传过来的不是同一个文件, 需要替换 245 | return true; 246 | } 247 | } 248 | else if (value instanceof Array) { 249 | // 两个数组对应的文件url必须完全一样, 才认为是同样的数据, 不需更新 250 | if (value.length !== fileArray.length) { 251 | return true; 252 | } 253 | for (let i = 0; i < value.length; i += 1) { 254 | if (value[i] !== fileArray[i].url) { 255 | return true; 256 | } 257 | } 258 | } 259 | 260 | return false; 261 | } 262 | 263 | /** 264 | * 上传按钮的样式, 跟文件类型/当前状态都有关 265 | */ 266 | renderUploadButton() { 267 | const { fileList } = this.state; 268 | const { max, placeholder } = this.props 269 | const disabled = fileList.length >= max; 270 | 271 | if (this.forImage) { 272 | const button = ( 273 |
    274 | 275 |
    上传图片
    276 |
    277 | ); 278 | // 对于图片而言, 如果文件数量达到max, 上传按钮直接消失 279 | if (disabled) { 280 | return null; 281 | } 282 | // 是否有提示语 283 | if (placeholder) { 284 | return ( 285 | 286 | {button} 287 | 288 | ); 289 | } 290 | return button; 291 | 292 | } 293 | // 对于普通文件而言, 如果数量达到max, 上传按钮不可用 294 | const button = ; 295 | // 是否要有提示语 296 | if (placeholder && !disabled) { 297 | return ( 298 | 299 | {button} 300 | 301 | ); 302 | } 303 | return button; 304 | } 305 | 306 | render() { 307 | const { previewVisible, previewImage, fileList } = this.state; 308 | const { crop } = this.props 309 | // 上传接口需求额外字段 310 | const extraParams = { 311 | path: 'manage_universal' 312 | } 313 | const uploadProps = { 314 | action: `${this.uploadUrl}?${qs.stringify(extraParams)}`, 315 | listType: this.listType, 316 | fileList, 317 | onPreview: this.forImage ? this.handlePreview : undefined, 318 | onChange: this.handleChange, 319 | beforeUpload: this.beforeUpload, 320 | accept: this.accept, 321 | } 322 | 323 | return ( 324 |
    325 | { 326 | crop ? ( 327 | 331 | 332 | {this.renderUploadButton()} 333 | 334 | 335 | ) : ( 336 | 337 | {this.renderUploadButton()} 338 | 339 | ) 340 | } 341 | {/* 只有上传图片时才需要这个预览modal */} 342 | {this.forImage && 343 | 344 | 图片加载失败 345 | } 346 |
    347 | ); 348 | } 349 | } 350 | 351 | export default FileUpload; 352 | -------------------------------------------------------------------------------- /lib/react-antd-table/toCopyStaticFiles/ITableComponents/FileUpload/index.less: -------------------------------------------------------------------------------- 1 | .ant-upload-select-picture-card i { 2 | font-size: 28px; 3 | color: #999; 4 | } 5 | 6 | .ant-upload-select-picture-card .ant-upload-text { 7 | margin-top: 8px; 8 | font-size: 12px; 9 | color: #666; 10 | } 11 | 12 | // 不知为何需要微调下样式才能居中, 跟antd官网上不太一样 13 | .ant-upload-select-picture-card .ant-upload { 14 | margin-left: 0; 15 | } 16 | -------------------------------------------------------------------------------- /lib/react-antd-table/toCopyStaticFiles/ITableComponents/FooterToolbar/demo/basic.md: -------------------------------------------------------------------------------- 1 | --- 2 | order: 0 3 | title: 4 | zh-CN: 演示 5 | en-US: demo 6 | iframe: 400 7 | --- 8 | 9 | ## zh-CN 10 | 11 | 浮动固定页脚。 12 | 13 | ## en-US 14 | 15 | Fixed to the footer. 16 | 17 | ````jsx 18 | import FooterToolbar from 'ant-design-pro/lib/FooterToolbar'; 19 | import { Button } from 'antd'; 20 | 21 | ReactDOM.render( 22 |
    23 |

    Content Content Content Content

    24 |

    Content Content Content Content

    25 |

    Content Content Content Content

    26 |

    Content Content Content Content

    27 |

    Content Content Content Content

    28 |

    Content Content Content Content

    29 |

    Content Content Content Content

    30 |

    Content Content Content Content

    31 |

    Content Content Content Content

    32 |

    Content Content Content Content

    33 |

    Content Content Content Content

    34 |

    Content Content Content Content

    35 |

    Content Content Content Content

    36 |

    Content Content Content Content

    37 |

    Content Content Content Content

    38 | 39 | 40 | 41 | 42 |
    43 | , mountNode); 44 | ```` -------------------------------------------------------------------------------- /lib/react-antd-table/toCopyStaticFiles/ITableComponents/FooterToolbar/index.d.ts: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | export interface FooterToolbarProps { 3 | extra: React.ReactNode; 4 | style?: React.CSSProperties; 5 | className?: string; 6 | } 7 | 8 | export default class FooterToolbar extends React.Component {} 9 | -------------------------------------------------------------------------------- /lib/react-antd-table/toCopyStaticFiles/ITableComponents/FooterToolbar/index.en-US.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: FooterToolbar 3 | cols: 1 4 | order: 6 5 | --- 6 | 7 | A toolbar fixed at the bottom. 8 | 9 | ## Usage 10 | 11 | It is fixed at the bottom of the content area and does not move along with the scroll bar, which is usually used for data collection and submission for long pages. 12 | 13 | ## API 14 | 15 | Property | Description | Type | Default 16 | ---------|-------------|------|-------- 17 | children | toolbar content, align to the right | ReactNode | - 18 | extra | extra information, align to the left | ReactNode | - -------------------------------------------------------------------------------- /lib/react-antd-table/toCopyStaticFiles/ITableComponents/FooterToolbar/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import classNames from 'classnames'; 3 | import styles from './index.less'; 4 | 5 | export default class FooterToolbar extends Component { 6 | state = { 7 | width: undefined, 8 | }; 9 | 10 | componentDidMount() { 11 | window.addEventListener('resize', this.resizeFooterToolbar); 12 | this.resizeFooterToolbar(); 13 | } 14 | 15 | componentWillUnmount() { 16 | window.removeEventListener('resize', this.resizeFooterToolbar); 17 | } 18 | 19 | resizeFooterToolbar = () => { 20 | const sider = document.querySelector('.ant-layout-sider'); 21 | if (sider == null) { 22 | return; 23 | } 24 | const { isMobile } = this.context; 25 | const width = isMobile ? null : `calc(100% - ${sider.style.width})`; 26 | const { width: stateWidth } = this.state; 27 | if (stateWidth !== width) { 28 | this.setState({ width }); 29 | } 30 | }; 31 | 32 | render() { 33 | const { children, className, extra, ...restProps } = this.props; 34 | const { width } = this.state; 35 | return ( 36 |
    37 |
    {extra}
    38 |
    {children}
    39 |
    40 | ); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /lib/react-antd-table/toCopyStaticFiles/ITableComponents/FooterToolbar/index.less: -------------------------------------------------------------------------------- 1 | @import '~antd/lib/style/themes/default.less'; 2 | 3 | .toolbar { 4 | position: fixed; 5 | right: 0; 6 | bottom: 0; 7 | z-index: 9; 8 | width: 100%; 9 | height: 56px; 10 | padding: 0 24px; 11 | line-height: 56px; 12 | background: #fff; 13 | border-top: 1px solid @border-color-split; 14 | box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.03); 15 | 16 | &::after { 17 | display: block; 18 | clear: both; 19 | content: ''; 20 | } 21 | 22 | .left { 23 | float: left; 24 | } 25 | 26 | .right { 27 | float: right; 28 | } 29 | 30 | button + button { 31 | margin-left: 8px; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /lib/react-antd-table/toCopyStaticFiles/ITableComponents/FooterToolbar/index.zh-CN.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: FooterToolbar 3 | subtitle: 底部工具栏 4 | cols: 1 5 | order: 6 6 | --- 7 | 8 | 固定在底部的工具栏。 9 | 10 | ## 何时使用 11 | 12 | 固定在内容区域的底部,不随滚动条移动,常用于长页面的数据搜集和提交工作。 13 | 14 | ## API 15 | 16 | 参数 | 说明 | 类型 | 默认值 17 | ----|------|-----|------ 18 | children | 工具栏内容,向右对齐 | ReactNode | - 19 | extra | 额外信息,向左对齐 | ReactNode | - 20 | -------------------------------------------------------------------------------- /lib/react-antd-table/toCopyStaticFiles/ITableComponents/ImageViewer/index.js: -------------------------------------------------------------------------------- 1 | import React, { useState, Fragment } from 'react' 2 | import { Modal } from 'antd' 3 | import styles from './index.less'; 4 | 5 | export default (props) => { 6 | const { src } = props 7 | const [visible, setVisible] = useState(false) 8 | const modalProps = { 9 | visible, 10 | onCancel: () => setVisible(false), 11 | footer: null 12 | } 13 | 14 | return ( 15 | 16 | setVisible(true)} alt="preview" /> 17 | 18 | preview 19 | 20 | 21 | ); 22 | } 23 | -------------------------------------------------------------------------------- /lib/react-antd-table/toCopyStaticFiles/ITableComponents/ImageViewer/index.less: -------------------------------------------------------------------------------- 1 | .img { 2 | width: 50px; 3 | cursor: pointer; 4 | } 5 | .modalImg { 6 | width: 100% 7 | } -------------------------------------------------------------------------------- /lib/react-antd-table/toCopyStaticFiles/ITableComponents/RadioGroup/index.js: -------------------------------------------------------------------------------- 1 | import React, { forwardRef } from 'react' 2 | import { Radio } from 'antd' 3 | 4 | const RadioGroup = (props, ref) => { 5 | const { source, value, onChange } = props 6 | 7 | return ( 8 | 14 | { 15 | source && source.map(i => ( 16 | {i.value} 17 | )) 18 | } 19 | 20 | ) 21 | } 22 | 23 | export default forwardRef(RadioGroup) -------------------------------------------------------------------------------- /lib/react-antd-table/toCopyStaticFiles/ITableComponents/SelectGroup/index.js: -------------------------------------------------------------------------------- 1 | import React, { forwardRef } from 'react' 2 | import { Select } from 'antd' 3 | 4 | const SelectOption = (props, ref) => { 5 | const { source, placeholder, multiple, value, onChange, mode } = props 6 | const otherProps = {} 7 | if (mode) { 8 | otherProps.mode = mode 9 | } else if (multiple) { 10 | otherProps.mode = 'multiple' 11 | } 12 | 13 | return ( 14 | 26 | ) 27 | } 28 | 29 | export default forwardRef(SelectOption) -------------------------------------------------------------------------------- /lib/react-antd-table/toCopyStaticFiles/ITableComponents/StandardTable/index.d.ts: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { PaginationConfig, SorterResult, TableCurrentDataSource } from 'antd/lib/table'; 3 | 4 | export interface StandardTableProps { 5 | columns: any; 6 | onSelectRow: (row: any) => void; 7 | data: any; 8 | rowKey?: string; 9 | selectedRows: any[]; 10 | onChange?: ( 11 | pagination: PaginationConfig, 12 | filters: Record, 13 | sorter: SorterResult, 14 | extra?: TableCurrentDataSource 15 | ) => void; 16 | loading?: boolean; 17 | } 18 | 19 | export default class StandardTable extends React.Component {} 20 | -------------------------------------------------------------------------------- /lib/react-antd-table/toCopyStaticFiles/ITableComponents/StandardTable/index.js: -------------------------------------------------------------------------------- 1 | import React, { Fragment } from 'react'; 2 | import { Table, Alert } from 'antd'; 3 | import styles from './index.less'; 4 | 5 | export default (props) => { 6 | const { selectedRows, onSelectRow, multiSelection, paginationConfig } = props 7 | 8 | function handleRowSelectChange (_, _selectedRows) { 9 | onSelectRow(_selectedRows) 10 | }; 11 | 12 | function handleTableChange (pagination, filters, sorter) { 13 | const { onChange } = props; 14 | if (onChange) { 15 | onChange(pagination, filters, sorter); 16 | } 17 | }; 18 | 19 | function cleanSelectedKeys () { 20 | handleRowSelectChange([], []); 21 | }; 22 | 23 | const { data = {}, rowKey, ...rest } = props; 24 | const { list = [], pagination } = data; 25 | 26 | const paginationProps = { 27 | showSizeChanger: paginationConfig.showSizeChanger, 28 | showQuickJumper: paginationConfig.showQuickJumper, 29 | pageSizeOptions: paginationConfig.pageSizeOptions, 30 | ...pagination, 31 | }; 32 | if (paginationConfig.showTotal) paginationProps.showTotal = (total) => `共 ${total} 条` 33 | 34 | const rowSelection = { 35 | selectedRowKeys: selectedRows && selectedRows.map(item => item[rowKey]), 36 | onChange: handleRowSelectChange, 37 | getCheckboxProps: record => ({ 38 | disabled: record.disabled, 39 | }), 40 | }; 41 | 42 | return ( 43 |
    44 | { 45 | multiSelection && selectedRows.length ? ( 46 |
    47 | 50 | 已选择 {selectedRows.length} 项   51 | 52 | 清空 53 | 54 | 55 | } 56 | type="info" 57 | showIcon 58 | /> 59 |
    60 | ) : null 61 | } 62 | 70 | 71 | ); 72 | } 73 | 74 | -------------------------------------------------------------------------------- /lib/react-antd-table/toCopyStaticFiles/ITableComponents/StandardTable/index.less: -------------------------------------------------------------------------------- 1 | @import '~antd/lib/style/themes/default.less'; 2 | 3 | .standardTable { 4 | :global { 5 | .ant-table-pagination { 6 | margin-top: 24px; 7 | } 8 | } 9 | 10 | .tableAlert { 11 | margin-bottom: 16px; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /lib/react-antd-table/toCopyStaticFiles/ITableComponents/TableAction/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Button } from 'antd' 3 | import styles from './index.less' 4 | 5 | const TableAction = (props) => { 6 | const { 7 | selectedRows, 8 | handleMenuClick, 9 | showCreate, 10 | showExport, 11 | showBatchDelete, 12 | ...otherProps 13 | } = props 14 | 15 | return ( 16 |
    20 | { 21 | showCreate ? ( 22 | 25 | ) : null 26 | } 27 | { 28 | showExport ? ( 29 | 32 | ) : null 33 | } 34 | {selectedRows && selectedRows.length > 0 && showBatchDelete && ( 35 | 38 | )} 39 |
    40 | ) 41 | } 42 | 43 | export default TableAction -------------------------------------------------------------------------------- /lib/react-antd-table/toCopyStaticFiles/ITableComponents/TableAction/index.less: -------------------------------------------------------------------------------- 1 | .tableListOperator { 2 | height: 32px; 3 | margin-bottom: 16px; 4 | margin-top: -32px; 5 | button { 6 | margin-right: 8px; 7 | } 8 | } 9 | 10 | -------------------------------------------------------------------------------- /lib/react-antd-table/toCopyStaticFiles/ITableComponents/TableColumnRender/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import moment from 'moment' 3 | import find from 'lodash/find' 4 | import ImageViewer from '../ImageViewer' 5 | import utils from '../utils' 6 | 7 | const { showTypeUtils } = utils 8 | 9 | function getArrayValue (value, options) { 10 | return value.map((i) => { 11 | const _t = find(options, o => o.key.toString() === i.toString()) 12 | return _t ? _t.value : '' 13 | }).join() 14 | } 15 | 16 | export default function renderLabelHandler(props) { 17 | const { 18 | value, 19 | index, 20 | showType = 'input', 21 | format, 22 | options, 23 | mode, 24 | pagination = {} 25 | } = props 26 | if (!value) { 27 | return null 28 | } 29 | 30 | // 某些状态值可能为数字0,此处额外加判断 31 | if (!value && value !== 0) { 32 | return null 33 | } 34 | 35 | // 是否是自增项 36 | if (showType === 'autoIncrement') { 37 | return ((Number(pagination.current) - 1) * Number(pagination.numPerPage)) + index + 1 38 | } 39 | 40 | if (showTypeUtils.isDate(showType)) { 41 | 42 | const dateFormat = format || 'YYYY-MM-DD' 43 | // rangePicker 44 | if (Array.isArray(value)) { 45 | return value.map(i => (moment(i).format(dateFormat))).join(' - ') 46 | } 47 | // datePicker 48 | return ({moment(value).format(dateFormat)}) 49 | } 50 | 51 | // 字典项 52 | if (showTypeUtils.isAssociate(showType)) { 53 | 54 | if (Array.isArray(value)) { 55 | return getArrayValue(value, options) 56 | } 57 | if (showType === 'inputSelect') { 58 | return value 59 | } 60 | if (value && value.toString().indexOf(',') !== -1) { 61 | return getArrayValue(value.toString().split(','), options) 62 | } 63 | const _t = find(options, o => o.key.toString() === value.toString()) 64 | return _t ? _t.value : null 65 | } 66 | 67 | // 树状数据 68 | if (showTypeUtils.isCascader(showType)) { 69 | return utils.getLabelFromCascaderArr(value, options) 70 | } 71 | 72 | // 图片 73 | if (showTypeUtils.isImage(showType)) { 74 | return () 75 | } 76 | 77 | // 文件 78 | if (showTypeUtils.isFile(showType)) { 79 | return ({value.substr(value.lastIndexOf('/') + 1)}) 80 | } 81 | 82 | // 链接 83 | if (showTypeUtils.isLink(showType)) { 84 | return ({value}) 85 | } 86 | 87 | // 富文本 88 | if (showTypeUtils.isEditor(showType)) { 89 | if (!value) return '' 90 | // 富文本区分在列表中展示和在详情中展示的情况: 91 | // 列表中提取标签部分内容展示 92 | // 详情中需要解析为html展示 93 | if (mode === 'detail') { 94 | // 展示需额外添加样式,否则img,audio,video等标签会超出容器宽度 95 | const extraStyle = ` 96 | 117 | ` 118 | // eslint-disable-next-line react/no-danger 119 | return 120 | } 121 | return value.replace(/<[^>]+>/g, '').substr(0, 10) 122 | } 123 | 124 | return value 125 | } -------------------------------------------------------------------------------- /lib/react-antd-table/toCopyStaticFiles/ITableComponents/TableColumnRender/index.less: -------------------------------------------------------------------------------- 1 | .img { 2 | width: 50px; 3 | cursor: pointer; 4 | } 5 | .modalImg { 6 | width: 100% 7 | } -------------------------------------------------------------------------------- /lib/react-antd-table/toCopyStaticFiles/ITableComponents/TableColumnRender/utils.js: -------------------------------------------------------------------------------- 1 | import find from 'lodash/find' 2 | 3 | const utils = { 4 | _getNodeLabelFromValue (value, array) { 5 | const _t = find(array, o => o.value === value) 6 | if (_t) return _t.label 7 | for(let i = 0;i < array.length; i+=1) { 8 | const { children } = array[i] 9 | if (children && children.length) { 10 | const label = this._getNodeLabelFromValue(value, children) 11 | if (label) return label 12 | } 13 | } 14 | return '' 15 | }, 16 | getLabelFromCascaderArr (value, array) { 17 | if (!(value && value.length && array && array.length)) { 18 | return '' 19 | } 20 | return value.map((item) => this._getNodeLabelFromValue(item, array)) 21 | }, 22 | showTypeUtils: { 23 | isAssociate: (showType) => { 24 | // 字典项,值需映射 25 | const associatedArr = ['select', 'radio', 'checkbox', 'multiSelect', 'inputSelect'] 26 | return associatedArr.indexOf(showType) !== -1 27 | }, 28 | isDate: (showType) => { 29 | // 日期,格式化 30 | const dateArr = ['datePicker', 'rangePicker'] 31 | return dateArr.indexOf(showType) !== -1 32 | }, 33 | isCascader: (showType) => { 34 | // 树状数据 35 | const cascaderArr = ['cascader'] 36 | return cascaderArr.indexOf(showType) !== -1 37 | }, 38 | isImage: (showType) => { 39 | // 图片 40 | const imageArr = ['image'] 41 | return imageArr.indexOf(showType) !== -1 42 | }, 43 | isFile: (showType) => { 44 | // 文件 45 | const fileArr = ['file'] 46 | return fileArr.indexOf(showType) !== -1 47 | }, 48 | isLink: (showType) => { 49 | // 链接 50 | const linkArr = ['link'] 51 | return linkArr.indexOf(showType) !== -1 52 | }, 53 | isEditor: (showType) => { 54 | // 富文本 55 | const fileArr = ['editor'] 56 | return fileArr.indexOf(showType) !== -1 57 | }, 58 | } 59 | } 60 | 61 | export default utils -------------------------------------------------------------------------------- /lib/react-antd-table/toCopyStaticFiles/ITableComponents/utils/index.js: -------------------------------------------------------------------------------- 1 | import find from 'lodash/find' 2 | import moment from 'moment' 3 | 4 | const defaultDateFormat = 'YYYY-MM-DD' 5 | 6 | const utils = { 7 | getValue: (obj) => (Object.keys(obj).map(key => obj[key]).join(',')), 8 | // 根据特定条件拆分数组 9 | _getNodeLabelFromValue (value, array) { 10 | const _t = find(array, o => o.value === value) 11 | if (_t) return _t.label 12 | for(let i = 0;i < array.length; i+=1) { 13 | const { children } = array[i] 14 | if (children && children.length) { 15 | const label = this._getNodeLabelFromValue(value, children) 16 | if (label) return label 17 | } 18 | } 19 | return '' 20 | }, 21 | getLabelFromCascaderArr (value, array) { 22 | if (!(value && value.length && array && array.length)) { 23 | return '' 24 | } 25 | return value.map((item) => this._getNodeLabelFromValue(item, array)) 26 | }, 27 | getFormatParamsFromTable (pagination, filtersArg, sorter, formValues) { 28 | 29 | const filters = Object.keys(filtersArg).reduce((obj, key) => { 30 | const newObj = { ...obj }; 31 | newObj[key] = utils.getValue(filtersArg[key]); 32 | return newObj; 33 | }, {}); 34 | 35 | const params = { 36 | ...formValues, 37 | ...filters, 38 | pageNum: pagination.current, 39 | numPerPage: pagination.numPerPage, 40 | }; 41 | if (sorter.field) { 42 | params.sorter = `${sorter.field}_${sorter.order}`; 43 | } 44 | return params 45 | }, 46 | getFormatFormValues (fieldsValue, dateFormat = defaultDateFormat, mode) { 47 | const values = { 48 | ...fieldsValue 49 | } 50 | // 格式化处理其中的数组类型的值以及日期moment格式的值 51 | const keys = Object.keys(fieldsValue) 52 | for(let i=0; i < keys.length; i += 1) { 53 | const key = keys[i] 54 | const value = fieldsValue[key] 55 | if (!value) { 56 | delete values[key] 57 | } else if (Array.isArray(value)) { // 数组类型的value 58 | if (value.length) { 59 | const isMomentValue = value.some(item => moment.isMoment(item)) 60 | if (isMomentValue) { 61 | // 日期格式的数组在tableFilter 模式下要根据字段拆分。 62 | // 比如createTime,拆分为createBeginTime和createEndTime,并删除createTime字段 63 | if (mode === 'tableFilter' && value.length === 2) { 64 | values[`${key}BeginTime`] = moment(value[0]).format(dateFormat) 65 | values[`${key}EndTime`] = moment(value[1]).format(dateFormat) 66 | delete values[key] 67 | } else { 68 | values[key] = value.map(item => moment(item).format(dateFormat)).join() 69 | } 70 | } else { 71 | values[key] = value.join() 72 | } 73 | } else { 74 | delete values[key] 75 | } 76 | } else if (value.convertOptions) { // braft-editor value 77 | values[key] = value.toHTML() 78 | } else if (moment.isMoment(value)) { // 普通日期格式 79 | values[key] = moment(value).format(dateFormat) 80 | } 81 | } 82 | return values 83 | } 84 | } 85 | 86 | export default utils -------------------------------------------------------------------------------- /lib/react-antd-table/toCopyStaticFiles/components/TableFilter/index.less: -------------------------------------------------------------------------------- 1 | .tableListForm { 2 | :global { 3 | .ant-form-item { 4 | // Table Filter布局 5 | display: flex !important; 6 | margin-right: 0!important; 7 | margin-bottom: 24px!important; 8 | > .ant-form-item-label { 9 | width: auto; 10 | padding-right: 8px; 11 | line-height: 32px; 12 | } 13 | .ant-form-item-control { 14 | line-height: 32px; 15 | } 16 | } 17 | .ant-form-item-control-wrapper { 18 | flex: 1; 19 | } 20 | } 21 | .button { 22 | display: flex; 23 | justify-content: flex-end; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /lib/react-antd-table/toCopyStaticFiles/components/TableForm/index.less: -------------------------------------------------------------------------------- 1 | .standardListForm { 2 | :global { 3 | .ant-form-item { 4 | margin-bottom: 12px; 5 | &:last-child { 6 | margin-bottom: 32px; 7 | padding-top: 4px; 8 | } 9 | } 10 | } 11 | } -------------------------------------------------------------------------------- /lib/react-antd-table/toCopyStaticFiles/upsert/index.less: -------------------------------------------------------------------------------- 1 | @import '~antd/lib/style/themes/default.less'; 2 | 3 | .form { 4 | padding-bottom: 50px; 5 | } 6 | 7 | .card { 8 | margin-bottom: 24px; 9 | } 10 | 11 | .heading { 12 | margin: 0 0 16px 0; 13 | font-size: 14px; 14 | line-height: 22px; 15 | } 16 | 17 | .steps:global(.ant-steps) { 18 | max-width: 750px; 19 | margin: 16px auto; 20 | } 21 | 22 | .errorIcon { 23 | margin-right: 24px; 24 | color: @error-color; 25 | cursor: pointer; 26 | i { 27 | margin-right: 4px; 28 | } 29 | } 30 | 31 | .errorPopover { 32 | :global { 33 | .ant-popover-inner-content { 34 | min-width: 256px; 35 | max-height: 290px; 36 | padding: 0; 37 | overflow: auto; 38 | } 39 | } 40 | } 41 | 42 | .errorListItem { 43 | padding: 8px 16px; 44 | list-style: none; 45 | border-bottom: 1px solid @border-color-split; 46 | cursor: pointer; 47 | transition: all 0.3s; 48 | &:hover { 49 | background: @primary-1; 50 | } 51 | &:last-child { 52 | border: 0; 53 | } 54 | .errorIcon { 55 | float: left; 56 | margin-top: 4px; 57 | margin-right: 12px; 58 | padding-bottom: 22px; 59 | color: @error-color; 60 | } 61 | .errorField { 62 | margin-top: 2px; 63 | color: @text-color-secondary; 64 | font-size: 12px; 65 | } 66 | } 67 | 68 | .editable { 69 | td { 70 | padding-top: 13px !important; 71 | padding-bottom: 12.5px !important; 72 | } 73 | } 74 | 75 | // custom footer for fixed footer toolbar 76 | .updateForm + div { 77 | padding-bottom: 64px; 78 | } 79 | 80 | .updateForm { 81 | :global { 82 | .ant-form .ant-row:last-child .ant-form-item { 83 | margin-bottom: 24px; 84 | } 85 | .ant-table td { 86 | transition: none !important; 87 | } 88 | } 89 | } 90 | 91 | .optional { 92 | color: @text-color-secondary; 93 | font-style: normal; 94 | } 95 | -------------------------------------------------------------------------------- /lib/scripts/ice-react-material.js: -------------------------------------------------------------------------------- 1 | const inquirer = require('inquirer') 2 | const axios = require('axios') 3 | const { exec } = require('child_process'); 4 | const chalk = require('chalk') 5 | const path = require('path') 6 | const utils = require('../utils') 7 | 8 | const { log } = console 9 | 10 | module.exports = function doWithReactMaterial () { 11 | const categories = [] 12 | log(chalk.blue('正在获取服务器资源...')) 13 | axios.get('https://ice.alicdn.com/assets/materials/react-materials.json') 14 | .then(result => { 15 | if (!result) { 16 | log.error('数据获取异常') 17 | process.exit(1) 18 | } 19 | const { blocks } = result.data 20 | log(chalk.green(`成功获取服务器上react相关区块${blocks.length}个.`)) 21 | blocks.forEach(item => { 22 | if (item && item.categories && item.categories.length) { 23 | item.categories.forEach(category => { 24 | if (!categories.includes(category)) { 25 | categories.push(category) 26 | } 27 | }) 28 | } 29 | }) 30 | if (!(categories && categories.length)) { 31 | return 32 | } 33 | 34 | inquirer.prompt([ 35 | { 36 | type: 'rawlist', 37 | name: 'category', 38 | message: '请选择代码块类型?', 39 | choices: categories, 40 | pageSize: 10 41 | } 42 | ]).then(answers => { 43 | inquirer.prompt([ 44 | { 45 | type: 'rawlist', 46 | name: 'blockName', 47 | message: '请选择代码块?', 48 | choices: blocks 49 | .filter(item => item.categories.includes(answers.category)) 50 | .map(item => ({ 51 | value: item.name, 52 | name: `${item.title}(${item.description || item.title})`, 53 | })), 54 | pageSize: 10 55 | } 56 | ]).then(subAnswers => { 57 | const { blockName } = subAnswers 58 | const filterArray = blocks.filter(item => item.name === blockName) 59 | if (filterArray && filterArray.length) { 60 | const { name, source } = filterArray[0] 61 | if (source && source.npm) { 62 | const { npm } = source 63 | log(chalk.blue('正在下载中...')) 64 | exec(`node ${path.resolve(__dirname, '../../bin/create-code')} addBlock ${npm}`, (error) => { 65 | if (error) { 66 | log(chalk.red(error)) 67 | } 68 | log(chalk.green('下载成功!')) 69 | log(chalk.green(`文件路径: (${process.cwd()}/${utils.getDownloadPkgName(name)}/index.jsx)`)) 70 | }) 71 | } 72 | } 73 | }) 74 | }) 75 | }) 76 | } -------------------------------------------------------------------------------- /lib/scripts/react-antd-table.js: -------------------------------------------------------------------------------- 1 | const inquirer = require('inquirer') 2 | const chalk = require('chalk') 3 | const path = require('path') 4 | const exists = require('fs').existsSync; 5 | const tableInsert = require('../react-antd-table') 6 | 7 | const cwd = process.cwd(); 8 | const { log } = console 9 | 10 | module.exports = function doWithTable () { 11 | const tableQuestions = [ 12 | { 13 | type: 'input', 14 | name: 'folderPath', 15 | message: 'input your table config folder path:', 16 | }, 17 | ] 18 | inquirer.prompt(tableQuestions) 19 | .then((answers) => { 20 | const { folderPath } = answers 21 | const configFolderPath = path.join(cwd, folderPath || './') 22 | // 判断输入的路径是否存在 23 | if (!exists(configFolderPath)) { 24 | log(chalk.red(`folder path: ${configFolderPath} not exists.`)); 25 | process.exit(1) 26 | } 27 | // 判断路径下是否存在config.js 28 | const tableConfigFilePath = path.join(configFolderPath, 'config.js') 29 | if (!exists(tableConfigFilePath)) { 30 | log(chalk.red(`file: ${tableConfigFilePath} not exists.`)); 31 | process.exit(1) 32 | } 33 | 34 | const nextQuestions = [ 35 | { 36 | type: 'input', 37 | name: 'generatePath', 38 | message: 'input your generatePath path:', 39 | default: path.join(configFolderPath, '../../src/pages') 40 | }, 41 | ] 42 | inquirer.prompt(nextQuestions) 43 | .then((nextAnswers) => { 44 | // 执行插入脚本 45 | tableInsert(configFolderPath, nextAnswers.generatePath) 46 | }) 47 | 48 | }) 49 | } -------------------------------------------------------------------------------- /lib/utils.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | camelize (str) { 3 | return str.replace(/-(\w)/g, (_, c) => (c ? c.toUpperCase() : '')); 4 | }, 5 | getDownloadPkgName (str) { 6 | const camel = this.camelize(str) 7 | if (!camel) return '' 8 | return `${camel.substring(0, 1).toUpperCase()}${camel.substring(1)}Block` 9 | } 10 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "create-code", 3 | "version": "0.2.6", 4 | "description": "The command tool based on umi antd to create code blocks", 5 | "bin": { 6 | "create-code": "./bin/create-code" 7 | }, 8 | "repository": { 9 | "type": "github", 10 | "url": "https://github.com/acpplife/create-code.git" 11 | }, 12 | "dependencies": { 13 | "@babel/generator": "^7.4.4", 14 | "@babel/types": "^7.4.4", 15 | "axios": "^0.19.0", 16 | "camelcase": "^5.3.1", 17 | "chalk": "^2.1.0", 18 | "commander": "^2.11.0", 19 | "fs-extra": "^8.1.0", 20 | "ice-npm-utils": "^1.3.0", 21 | "inquirer": "^6.5.0", 22 | "lodash": "^4.17.11", 23 | "mkdirp": "^0.5.1", 24 | "npmlog": "^4.1.2", 25 | "putout": "^4.23.4", 26 | "request": "^2.88.0", 27 | "request-progress": "^3.0.0", 28 | "tar": "^2.0.0" 29 | }, 30 | "devDependencies": { 31 | "babel-eslint": "^9.0.0", 32 | "eslint": "^5.4.0", 33 | "eslint-config-airbnb": "^17.0.0", 34 | "eslint-config-prettier": "^3.0.1", 35 | "eslint-plugin-babel": "^5.1.0", 36 | "eslint-plugin-compat": "^2.7.0", 37 | "eslint-plugin-import": "^2.8.0", 38 | "eslint-plugin-jsx-a11y": "^6.0.3", 39 | "eslint-plugin-markdown": "^1.0.0-beta.6", 40 | "eslint-plugin-react": "^7.11.1", 41 | "eslint-plugin-react-hooks": "^1.6.0" 42 | }, 43 | "files": [ 44 | "bin", 45 | "demo-schema", 46 | "lib" 47 | ] 48 | } 49 | --------------------------------------------------------------------------------