├── .eslintignore ├── .eslintrc.js ├── .github └── workflows │ ├── check-merge.yml │ └── npm-package.yml ├── .gitignore ├── .husky ├── commit-msg └── pre-commit ├── .lintstagedrc.json ├── .prettierrc.yml ├── LICENSE ├── README.md ├── assets ├── hello.png └── logo.png ├── bin └── fe ├── build ├── README.md ├── config.js ├── index.js ├── snowpack │ ├── config.js │ ├── index.js │ └── utils.js ├── template │ ├── babel │ │ └── index.js │ ├── empty │ │ ├── index.js │ │ ├── indexHtml.js │ │ └── indexJs.js │ ├── ignore │ │ └── index.js │ ├── lint │ │ └── index.js │ ├── react17 │ │ ├── app.js │ │ ├── index.js │ │ ├── indexHtml.js │ │ ├── indexJs.js │ │ └── styles.scss │ ├── readme │ │ └── index.js │ ├── style │ │ ├── index.js │ │ ├── less.js │ │ └── sass.js │ ├── tsconfig │ │ ├── app.js │ │ ├── defaultConfig.js │ │ └── index.js │ ├── unitTest │ │ └── index.js │ ├── utils.js │ ├── vue2 │ │ ├── app.js │ │ ├── index.js │ │ ├── indexHtml.js │ │ └── indexJs.js │ └── vue3 │ │ ├── app.js │ │ ├── index.js │ │ ├── indexHtml.js │ │ └── indexJs.js ├── utils.js ├── vite │ ├── config.js │ ├── index.js │ └── utils.js └── webpack │ ├── config.js │ ├── index.js │ └── utils.js ├── commitlint.config.js ├── constants └── concept.js ├── dependencies.config.js ├── docs └── zh-CN.md ├── examples ├── react-demo │ ├── .editorconfig │ ├── .eslintignore │ ├── .gitignore │ ├── .prettierignore │ ├── .prettierrc │ ├── .umirc.ts │ ├── README.md │ ├── mock │ │ └── api.ts │ ├── package.json │ ├── public │ │ └── mock │ │ │ ├── delete.json │ │ │ ├── list.json │ │ │ └── modify.json │ ├── src │ │ ├── components │ │ │ ├── Container │ │ │ │ ├── Modal.tsx │ │ │ │ └── ModalTypes.ts │ │ │ ├── Crud.tsx │ │ │ ├── CrudTypes.ts │ │ │ ├── Form │ │ │ │ ├── FormTypes.ts │ │ │ │ ├── README.md │ │ │ │ ├── constant.ts │ │ │ │ ├── form-components │ │ │ │ │ ├── f-checkbox │ │ │ │ │ │ └── index.tsx │ │ │ │ │ └── f-switch │ │ │ │ │ │ └── index.tsx │ │ │ │ ├── formUtils.ts │ │ │ │ ├── index.tsx │ │ │ │ └── mappting.ts │ │ │ ├── Table │ │ │ │ ├── Table.tsx │ │ │ │ ├── TableTypes.ts │ │ │ │ ├── hooks │ │ │ │ │ ├── useActionType.ts │ │ │ │ │ └── usePrevious.ts │ │ │ │ ├── index.tsx │ │ │ │ ├── table.md │ │ │ │ └── utils │ │ │ │ │ ├── index.ts │ │ │ │ │ └── useFetchData.tsx │ │ │ ├── ToolBar │ │ │ │ ├── BatchOperation.tsx │ │ │ │ ├── FilterSearch.tsx │ │ │ │ ├── ToolbarTypes.ts │ │ │ │ ├── index.tsx │ │ │ │ └── utils.ts │ │ │ ├── index.ts │ │ │ └── service.ts │ │ └── pages │ │ │ ├── FormDemo.tsx │ │ │ ├── Table.tsx │ │ │ ├── index.tsx │ │ │ ├── role.tsx │ │ │ └── toolbar.tsx │ ├── tsconfig.json │ └── typings.d.ts ├── vue2-demo │ ├── .browserslistrc │ ├── .eslintignore │ ├── .eslintrc.js │ ├── .gitignore │ ├── README.md │ ├── babel.config.js │ ├── mock │ │ ├── api │ │ │ └── table.js │ │ └── index.js │ ├── package.json │ ├── public │ │ ├── favicon.ico │ │ └── index.html │ ├── src │ │ ├── App.vue │ │ ├── assets │ │ │ └── logo.png │ │ ├── components │ │ │ └── HelloWorld.vue │ │ ├── main.ts │ │ ├── router │ │ │ └── index.ts │ │ ├── shims-tsx.d.ts │ │ ├── shims-vue.d.ts │ │ └── views │ │ │ ├── About.vue │ │ │ ├── Home.vue │ │ │ └── TableDemo.vue │ ├── tsconfig.json │ ├── vue.config.js │ └── yarn.lock └── vue3-demo │ ├── .editorconfig │ ├── .eslintignore │ ├── .eslintrc.js │ ├── .gitignore │ ├── .prettierrc │ ├── README.md │ ├── babel.config.js │ ├── mock │ ├── api │ │ └── table.js │ └── index.js │ ├── package.json │ ├── public │ ├── favicon.ico │ └── index.html │ ├── src │ ├── App.vue │ ├── assets │ │ └── logo.png │ ├── components │ │ ├── Button │ │ │ └── index.vue │ │ ├── CrudTypes.ts │ │ ├── Table │ │ │ ├── Form │ │ │ │ └── constant.ts │ │ │ ├── ToolBar │ │ │ │ ├── filterSearch.vue │ │ │ │ └── index.vue │ │ │ ├── config.ts │ │ │ └── index.vue │ │ ├── crud.vue │ │ ├── index.ts │ │ └── shims-vue.d.ts │ ├── main.ts │ ├── router.ts │ ├── shims-vue.d.ts │ └── views │ │ ├── Index.vue │ │ └── TableDemo.vue │ ├── tsconfig.json │ └── vue.config.js ├── lib ├── api2code │ ├── generateCRUD │ │ ├── genService.js │ │ ├── getApiSet.js │ │ └── index.js │ ├── generateCrud │ │ ├── apiConfig.ts │ │ ├── emitCode.js │ │ ├── genModel.js │ │ ├── genService.js │ │ ├── getApiSet.js │ │ ├── index.js │ │ ├── schema.json │ │ ├── spinner.js │ │ └── validateApiSet.js │ ├── generateInterface │ │ └── index.js │ ├── parser │ │ ├── index.js │ │ └── openapi │ │ │ ├── index.js │ │ │ ├── oraState.js │ │ │ ├── parseRef.js │ │ │ └── parser │ │ │ ├── getModels.js │ │ │ ├── getServer.js │ │ │ ├── getServices.js │ │ │ └── parseParams.js │ └── template │ │ ├── helpers.js │ │ ├── model.hbs │ │ ├── registerTemplates.js │ │ └── service.hbs ├── defaultConfig.js ├── loadConfig.js ├── react │ ├── README.md │ ├── components │ │ ├── Container │ │ │ ├── Modal.tsx │ │ │ └── ModalTypes.ts │ │ ├── Crud.tsx │ │ ├── CrudTypes.ts │ │ ├── Form │ │ │ ├── FormTypes.ts │ │ │ ├── README.md │ │ │ ├── constant.ts │ │ │ ├── form-components │ │ │ │ ├── f-checkbox │ │ │ │ │ └── index.tsx │ │ │ │ └── f-switch │ │ │ │ │ └── index.tsx │ │ │ ├── formUtils.ts │ │ │ ├── index.tsx │ │ │ └── mappting.ts │ │ ├── Table │ │ │ ├── Table.tsx │ │ │ ├── TableTypes.ts │ │ │ ├── hooks │ │ │ │ ├── useActionType.ts │ │ │ │ └── usePrevious.ts │ │ │ ├── index.tsx │ │ │ ├── table.md │ │ │ └── utils │ │ │ │ ├── index.ts │ │ │ │ └── useFetchData.tsx │ │ ├── ToolBar │ │ │ ├── BatchOperation.tsx │ │ │ ├── FilterSearch.tsx │ │ │ ├── ToolbarTypes.ts │ │ │ ├── index.tsx │ │ │ └── utils.ts │ │ ├── index.ts │ │ └── service.ts │ ├── jsx │ │ ├── components │ │ │ ├── Container │ │ │ │ ├── Modal.jsx │ │ │ │ └── ModalTypes.js │ │ │ ├── Crud.jsx │ │ │ ├── CrudTypes.js │ │ │ ├── Form │ │ │ │ ├── FormTypes.js │ │ │ │ ├── constant.js │ │ │ │ ├── form-components │ │ │ │ │ ├── f-checkbox │ │ │ │ │ │ └── index.jsx │ │ │ │ │ └── f-switch │ │ │ │ │ │ └── index.jsx │ │ │ │ ├── formUtils.js │ │ │ │ ├── index.jsx │ │ │ │ └── mappting.js │ │ │ ├── Table │ │ │ │ ├── Table.jsx │ │ │ │ ├── TableTypes.js │ │ │ │ ├── hooks │ │ │ │ │ ├── useActionType.js │ │ │ │ │ └── usePrevious.js │ │ │ │ ├── index.jsx │ │ │ │ └── utils │ │ │ │ │ ├── index.js │ │ │ │ │ └── useFetchData.jsx │ │ │ ├── ToolBar │ │ │ │ ├── BatchOperation.jsx │ │ │ │ ├── FilterSearch.jsx │ │ │ │ ├── ToolbarTypes.js │ │ │ │ ├── index.jsx │ │ │ │ └── utils.js │ │ │ ├── index.js │ │ │ └── service.js │ │ └── template │ │ │ └── crud.jsx │ ├── mock │ │ ├── delete.json │ │ ├── list.json │ │ └── modify.json │ ├── package.json │ ├── template │ │ └── crud.tsx │ ├── tsconfig.json │ └── yarn.lock ├── utils │ ├── constants.js │ ├── file.js │ ├── fileSystem.js │ ├── index.js │ ├── log.js │ ├── output2File.js │ ├── quicktypeJSON.js │ ├── react2Code.js │ ├── request.js │ └── vue2Code.js ├── vue2 │ ├── .gitignore │ ├── .prettierrc │ ├── README.md │ ├── components │ │ ├── CrudTypes.ts │ │ ├── Form │ │ │ ├── config.ts │ │ │ ├── constant.ts │ │ │ └── index.vue │ │ ├── Table │ │ │ ├── ToolBar │ │ │ │ ├── filterSearch.vue │ │ │ │ └── index.vue │ │ │ ├── config.ts │ │ │ └── index.vue │ │ ├── crud.vue │ │ ├── index.ts │ │ └── shims-vue.d.ts │ ├── package.json │ └── tsconfig.json └── vue3 │ ├── .gitignore │ ├── .prettierrc │ ├── README.md │ ├── components │ ├── CrudTypes.ts │ ├── Table │ │ ├── Form │ │ │ ├── constant.ts │ │ │ └── index.vue │ │ ├── ToolBar │ │ │ ├── filterSearch.vue │ │ │ └── index.vue │ │ ├── config.tsx │ │ └── index.vue │ ├── crud.vue │ ├── index.ts │ └── shims-vue.d.ts │ ├── package.json │ └── tsconfig.json ├── mock ├── delete.json ├── list.json └── modify.json ├── mocks ├── allApi.json ├── api.json ├── apiConfig.json └── openapi.json ├── package.json ├── scripts ├── api2code.js ├── envir2code.js ├── react2code.js └── vue2code.js ├── tsconfig.eslint.json ├── utils.js └── yarn.lock /.eslintignore: -------------------------------------------------------------------------------- 1 | bin/fe 2 | /**/*.d.ts 3 | yarn.lock 4 | examples 5 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | browser: true, 4 | commonjs: true, 5 | es2021: true, 6 | node: true, 7 | }, 8 | extends: ['airbnb-base', 'airbnb-typescript', 'plugin:prettier/recommended'], 9 | parserOptions: { 10 | ecmaVersion: 12, 11 | project: './tsconfig.eslint.json', 12 | }, 13 | rules: { 14 | 'no-console': ['warn', { allow: ['warn', 'error'] }], 15 | 'no-prototype-builtins': 'off', 16 | 'no-restricted-syntax': 'off', 17 | 'no-unused-expressions': 'off', 18 | '@typescript-eslint/no-unused-expressions': [ 19 | 'error', 20 | { allowShortCircuit: true, allowTernary: true }, 21 | ], 22 | 'import/no-unresolved': [ 23 | 2, 24 | { 25 | ignore: [ 26 | 'vue', 27 | 'react', 28 | '@crud', 29 | 'react-dom', 30 | 'vite-plugin-vue2', 31 | 'mockjs', 32 | 'umi-request', 33 | ], 34 | }, 35 | ], 36 | 'global-require': 'off', 37 | 'import/no-dynamic-require': 'off', 38 | 'react/react-in-jsx-scope': 'off', 39 | 'no-shadow': 'off', 40 | 'react/jsx-props-no-spreading': 'off', 41 | '@typescript-eslint/no-shadow': ['error'], 42 | 'import/prefer-default-export': 'off', 43 | 'react/destructuring-assignment': [0, 'always'], 44 | 'import/no-extraneous-dependencies': 0, 45 | 'no-param-reassign': 0, 46 | 'no-nested-ternary': 'off', 47 | }, 48 | settings: { 49 | 'import/resolver': { 50 | node: { 51 | extensions: ['.js', '.jsx', '.vue', '.tsx', '.ts'], 52 | }, 53 | }, 54 | }, 55 | }; 56 | -------------------------------------------------------------------------------- /.github/workflows/check-merge.yml: -------------------------------------------------------------------------------- 1 | # This workflow will do a clean install of node dependencies, cache/restore them, build the source code and run tests across different versions of node 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions 3 | 4 | name: Check Merge 5 | 6 | on: 7 | pull_request: 8 | branches: [main] 9 | 10 | jobs: 11 | eslint: 12 | runs-on: ubuntu-latest 13 | strategy: 14 | matrix: 15 | node-version: [14.x] 16 | 17 | steps: 18 | - name: Get yarn cache directory path 19 | id: yarn-cache-dir-path 20 | run: echo "::set-output name=dir::$(yarn cache dir)" 21 | 22 | - uses: actions/cache@v2 23 | id: yarn-cache 24 | with: 25 | path: ${{ steps.yarn-cache-dir-path.outputs.dir }} 26 | key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} 27 | restore-keys: | 28 | ${{ runner.os }}-yarn- 29 | 30 | - uses: actions/checkout@v2 31 | - name: Install packages 32 | run: yarn install 33 | - name: ESLint 34 | run: npm run eslint 35 | -------------------------------------------------------------------------------- /.github/workflows/npm-package.yml: -------------------------------------------------------------------------------- 1 | name: Publish package 2 | 3 | on: 4 | release: 5 | types: [created] 6 | 7 | jobs: 8 | publish: 9 | runs-on: ubuntu-latest 10 | permissions: 11 | contents: read 12 | packages: write 13 | steps: 14 | - uses: actions/checkout@v2 15 | # Setup .npmrc file to publish to GitHub Packages 16 | - uses: actions/setup-node@v2 17 | with: 18 | node-version: '14.x' 19 | registry-url: 'https://registry.npmjs.org' 20 | - run: yarn install 21 | - run: npm publish --access public 22 | env: 23 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | yarn-error.log 3 | .DS_Store 4 | Icon? 5 | dist 6 | .idea 7 | package-lock.json 8 | __test__ -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npx --no-install commitlint --edit "$1" 5 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npx lint-staged 5 | -------------------------------------------------------------------------------- /.lintstagedrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "*.{ts,js,jsx,tsx}": [ 3 | "eslint --fix" 4 | ] 5 | } -------------------------------------------------------------------------------- /.prettierrc.yml: -------------------------------------------------------------------------------- 1 | singleQuote: true 2 | trailingComma: all 3 | semi: true 4 | bracketSpacing: true 5 | useTabs: false 6 | tabWidth: 2 7 | arrowParens: avoid 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 fe-code 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 🤖 前端代码 no-code 🌈 2 | 3 | 很多前端同学,每日不仅要忙于内卷,还要忙于奔波在重复的业务逻辑期间,所以我们索性开发了这样一款工具,帮助大家可以直接根据接口生成 CURD 的全部代码,让大家生活多些快乐,功能 todolist: 4 | 5 | > - 根据接口生成 ts 的接口声明文档 6 | > - 根据接口直接生成 Vue+TS(js 代码) 7 | > - 根据接口直接生成 React+TS(js 代码) 8 | > - 定制选择指定 UI 组件库,直接根据接口生成 9 | > - 将 JavaScript 代码直接生成 TypeScript 代码 10 | 11 | logo 12 | 13 | --- 14 | 15 | ## 目录 16 | 17 | - [🤖 前端代码 no-code 🌈](#-前端代码-no-code-) 18 | - [目录](#目录) 19 | - [开始](#开始) 20 | - [安装](#安装) 21 | - [基础命令](#基础命令) 22 | - [`envir2code`](#envir2code) 23 | - [例子 🌰](#例子-) 24 | - [`api2code`](#api2code) 25 | - [参数](#参数) 26 | - [例子 🌰](#例子--1) 27 | - [`react2code`](#react2code) 28 | - [参数](#参数-1) 29 | - [例子 🌰](#例子--2) 30 | - [`vue2code`](#vue2code) 31 | - [参数](#参数-2) 32 | - [例子 🌰](#例子--3) 33 | 34 | ## 开始 35 | 36 | ### 安装 37 | ```bash 38 | npm install fe-code 39 | ``` 40 | 41 | ### 基础命令 42 | 43 | ```shell 44 | #查看版本号 45 | fe-code -V 46 | #查看帮助文档 47 | fe-code --help 48 | #接口生成TS代码帮助文档 49 | fe-code api2code --help 50 | ``` 51 | 52 | ### `envir2code` 53 | 描述:初始化基于(`webpack`/`vite`/`snowpack`)的`vue`/`react`脚手架 54 | 55 | 缩写: `e2c` 56 | 57 | #### 例子 🌰 58 | 59 | ```bash 60 | fe-code envir2code 61 | 62 | # or 简写 63 | fe-code e2c 64 | ``` 65 | 66 | 67 | ### `api2code` 68 | 描述:通过自定义结构或openAPI的json生成crud代码 69 | 缩写:`a2c` 70 | 71 | #### 参数 72 | 73 | ```bash 74 | Options: 75 | -i, --input (可选)输入的json路径 76 | -o, --output (必填)输出interface的文件路径 77 | -h, --help 查看帮助 78 | ``` 79 | 80 | #### 例子 🌰 81 | 1. 通过本地 json 方式生成 interface 82 | ```bash 83 | fe-code a2c -o src/index.ts -i /data.json 84 | ``` 85 | 2. 通过自定义json或openAPI生成 crud代码 86 | ```bash 87 | fe-code a2c -i ./mocks/apiConfig.json -o api/ 88 | ``` 89 | > 示例json在项目目录`mocks/apiConfig.json`中。 90 | 91 | ### `react2code` 92 | 描述:生成react组件代码 93 | 94 | 缩写: `r2c` 95 | 96 | #### 参数 97 | 98 | ```bash 99 | Options: 100 | -o, --output (必填)输出 react crud 模板代码的文件路径 101 | -h, --help 查看帮助 102 | ``` 103 | 104 | #### 例子 🌰 105 | 106 | > 1. 生成代码默认请求为 mock json(该目录需要映射到根目录 /mock,否则将影响展示), 请求这块的处理请结合实际情况自行修改; 107 | > 2. 生成代码字段属性是为了显示各种表单展示类型,这里为模拟字段,请根据实际情况自行配置; 108 | 109 | ```bash 110 | fe-code react2code -o crud-demo 111 | 112 | # or 简写 113 | fe-code r2c -o crud-demo 114 | ``` 115 | 116 | ### `vue2code` 117 | 118 | 缩写: `v2c` 119 | 120 | #### 参数 121 | 122 | ```bash 123 | Options: 124 | -o, --output (必填)输出 react crud 模板代码的文件路径 125 | -h, --help 查看帮助 126 | ``` 127 | 128 | #### 例子 🌰 129 | 130 | ```bash 131 | fe-code vue2code -o crud-demo 132 | 133 | # or 简写 134 | fe-code v2c -o crud-demo 135 | ``` 136 | -------------------------------------------------------------------------------- /assets/hello.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lgwebdream/fe-code/4a49a289e953bfd99f37875c966dd53590eb1401/assets/hello.png -------------------------------------------------------------------------------- /assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lgwebdream/fe-code/4a49a289e953bfd99f37875c966dd53590eb1401/assets/logo.png -------------------------------------------------------------------------------- /bin/fe: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | const { program } = require('commander'); 3 | const figlet = require('figlet'); 4 | const Printer = require('@darkobits/lolcatjs'); 5 | const shell = require('shelljs'); 6 | const chalk = require('chalk'); 7 | const inquirer = require('inquirer'); 8 | const ora = require('ora'); 9 | const download = require('download-git-repo'); 10 | const { version } = require('../package.json'); 11 | const api2code = require('../scripts/api2code'); 12 | const vue2code = require('../scripts/vue2code'); 13 | const react2code = require('../scripts/react2code'); 14 | const envir2code = require('../scripts/envir2code'); 15 | const loadConfig = require('../lib/loadConfig'); 16 | 17 | const versionStr = figlet.textSync('Fe Code'); 18 | 19 | program.version( 20 | Printer.fromString( 21 | ` \n 前端代码生成器${version}\n www.yidengfe.com \n${versionStr}`, 22 | ), 23 | ); 24 | 25 | // 接口生成代码部分 26 | api2code(program); 27 | // 生成Vue代码部分 28 | vue2code(program); 29 | // 生成react代码部分 30 | react2code(program); 31 | // 生成运行环境代码部分 32 | envir2code(program); 33 | program 34 | .usage('[对应Commands]') 35 | .arguments('') 36 | .action((cmd, otherParmas) => { 37 | const whiteList = ['api2ts']; 38 | if (whiteList.indexOf(cmd) < 0) { 39 | console.log(`🐝 ${chalk.blue(`${cmd}`)} ${chalk.red('暂未支持~')}`); 40 | } 41 | }); 42 | program.parse(process.argv); 43 | -------------------------------------------------------------------------------- /build/README.md: -------------------------------------------------------------------------------- 1 | # Structure 2 | 3 | - snowpack 4 | - generate package.json 5 | - generate snowpack.config.json 6 | - other config 7 | 8 | - webpack 9 | - generate package.json 10 | - generate webpack.config.json 11 | - other config 12 | 13 | - vite 14 | - generate package.json 15 | - generate vite.config.json 16 | - other config 17 | 18 | - template: src, common 19 | - ignore: create `.gitignore` for all 20 | - readme: create `README.md` for all 21 | - empty: src template for empty - no uiFramework, no mainFramework, support for typescript(optional) 22 | - react17: src template for react 17 - mainFramework: react 17, support for antd(optional), typescript(optional) 23 | - vue2: src template for vue 2 - - mainFramework: vue 2, support for element-ui(optional), typescript(optional) 24 | - lint: support for eslint(optional), premitter(optional) 25 | - style: support for sass(optional), less(optional), stuly(optional) 26 | - unitTest: support for jest(optional), mocha(optional) 27 | 28 | - index.js: generate all template 29 | 30 | - utils: help functions for all 31 | 32 | - README.md 33 | 34 | ## TODO list 35 | 36 | 君鸿:~~empty framework +tsconfig~~ vite 整合 37 | 贤明:~~vue +~~ webpack 整合 38 | ~~仙伟:react + snowpack 整合~~ 39 | - transpiler: typescript: 差整合 - snowpack done 40 | 41 | - typescript: template, ~~react.~~ vue(any), empty 42 | - style: sass/postcss/less: 43 | - react: sass, less done 44 | 45 | 46 | 改动: 47 | 1. template/react 调用方式从之前`const { newIndex: reactNewIndex } = require('./template/react17');` 48 | 改为 `const reactSrcFile = require('./template/react17');`, 49 | 50 | 新增: 51 | 1. 调用 `require('./template/react17')`,需要新增 `isSass` 和 `isLess` 属性 52 | 2. src下`style`模板支持snowpack打包。单独调用为`require('./template/style')`,需要传递`isSass` 和 `isLess` 属性 53 | 3. 新增react ts, sass, less的模板 54 | 4. `.fecoderc.json`的`featureList`新增sass,less属性 55 | 56 | - test: jest/mocha/chai/ should/expect - template 57 | react/vue - jest 58 | ~~- lint: eslint/prettier - alone: format all generation files~~ 59 | - framework: vue3 - ts - sass - less 60 | 61 | 新增: 62 | 1. 添加 `mergeBasicDependencies` 方法处理3个打包工具共同使用的依赖包。 相关引用代码已统一处理好 63 | 2. 把 `getSrcTemplate` 这部分代码提取成utils, 降低对 `index.js` 的耦合 64 | 3. 移除 `beauty-js` , 使用 `prettier` 统一处理格式化问题。相关代码在 `script/envir2code.js` 65 | 66 | - plugins: 67 | - webpack 68 | - html-webpack-plugin 69 | - webpack bundle analyzer 70 | - clean webpack plugin 71 | - minicss extract 72 | - snowpack 73 | - vite 74 | 75 | - global nodejs runtime issue check 76 | -------------------------------------------------------------------------------- /build/config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | sassName: 'styles.scss', 3 | lessName: 'styles.less', 4 | }; 5 | -------------------------------------------------------------------------------- /build/index.js: -------------------------------------------------------------------------------- 1 | const { join } = require('path'); 2 | const { outputFileSync, ensureDirSync, writeJsonSync } = require('fs-extra'); 3 | const { transformArr2TrueObj, formatCode } = require('../utils'); 4 | const getBabel = require('./template/babel'); 5 | const getIgnore = require('./template/ignore'); 6 | const getLint = require('./template/lint'); 7 | const getReadMe = require('./template/readme'); 8 | const { getSrcTemplate } = require('./utils'); 9 | const { app: getTsConfig } = require('./template/tsconfig'); 10 | 11 | const rootPath = process.cwd(); 12 | const [, , inputConfigPath] = process.argv; 13 | const config = require(inputConfigPath); 14 | const { 15 | buildTool, 16 | projectName, 17 | featureList, 18 | mainFramework, 19 | templatePath, 20 | uiFramework: ui, 21 | } = config; 22 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 23 | const { name: main, version } = mainFramework; 24 | const runner = require(join(__dirname, buildTool)); 25 | const $featureChecks = transformArr2TrueObj(featureList); 26 | const { 27 | typescript: isTypescript, 28 | sass: isSass, 29 | less: isLess, 30 | lint: isLint, 31 | prettier: isPrettier, 32 | } = $featureChecks; 33 | const $resolveRoot = join(rootPath, projectName); 34 | 35 | ensureDirSync($resolveRoot); 36 | 37 | // generate diff 38 | runner({ 39 | ...config, 40 | $featureChecks, 41 | $resolveRoot, 42 | }); 43 | 44 | // generate src template 45 | getSrcTemplate({ 46 | ui, 47 | main, 48 | projectName, 49 | buildTool, 50 | isTypescript, 51 | isSass, 52 | isLess, 53 | }).forEach(({ file, text }) => { 54 | if (buildTool === 'webpack' && file === 'index.html') { 55 | outputFileSync(join($resolveRoot, file), text); 56 | } else { 57 | outputFileSync(join($resolveRoot, templatePath, file), text); 58 | } 59 | }); 60 | 61 | // generate .gitignore, readme.md, eslint, TsConfig files 62 | [ 63 | getIgnore(), 64 | getReadMe({ projectName }), 65 | getLint({ main, isPrettier, isLint }), 66 | ...getTsConfig({ 67 | main, 68 | buildTool, 69 | isTypescript, 70 | includePath: templatePath, 71 | }), 72 | ].forEach(item => { 73 | if (item) { 74 | const { file, text } = item; 75 | outputFileSync(join($resolveRoot, file), text); 76 | } 77 | }); 78 | 79 | if (buildTool === 'webpack') { 80 | // generate .babelrc 81 | const babel = getBabel(); 82 | writeJsonSync(join($resolveRoot, babel.file), babel.text); 83 | } 84 | 85 | // format code 86 | isPrettier && formatCode($resolveRoot); 87 | -------------------------------------------------------------------------------- /build/snowpack/config.js: -------------------------------------------------------------------------------- 1 | const { devDependencies } = require('../../dependencies.config'); 2 | 3 | module.exports = { 4 | templatePackageJson: { 5 | name: '', 6 | version: '1.0.0', 7 | description: '', 8 | main: 'index.js', 9 | keywords: [], 10 | author: '', 11 | license: 'ISC', 12 | scripts: { 13 | clean: 'rm dist/bundle.js', 14 | start: 'snowpack dev', 15 | build: 'snowpack build', 16 | }, 17 | dependencies: {}, 18 | devDependencies: { 19 | snowpack: devDependencies.snowpack, 20 | }, 21 | }, 22 | templateSnowpackConfig: { 23 | mount: { 24 | src: '/', 25 | }, 26 | plugins: [], 27 | packageOptions: [], 28 | }, 29 | PACKAGE_JSON: 'package.json', 30 | SNOWPACK_CONFIG_JSON: 'snowpack.config.json', 31 | }; 32 | -------------------------------------------------------------------------------- /build/snowpack/index.js: -------------------------------------------------------------------------------- 1 | const { writeJsonSync } = require('fs-extra'); 2 | const { join } = require('path'); 3 | const { getPackageJson, getSnowpackConfigJson } = require('./utils'); 4 | const { SNOWPACK_CONFIG_JSON, PACKAGE_JSON } = require('./config'); 5 | 6 | module.exports = ({ 7 | mainFramework: { name: main }, 8 | uiFramework: ui, 9 | projectName, 10 | $resolveRoot, 11 | $featureChecks, 12 | }) => { 13 | const { 14 | typescript: isTypescript, 15 | sass: isSass, 16 | less: isLess, 17 | prettier: isPrettier, 18 | lint: isLint, 19 | } = $featureChecks; 20 | // generate package.json 21 | writeJsonSync( 22 | join($resolveRoot, PACKAGE_JSON), 23 | getPackageJson({ 24 | ui, 25 | isLint, 26 | isPrettier, 27 | main, 28 | projectName, 29 | isTypescript, 30 | isSass, 31 | isLess, 32 | }), 33 | ); 34 | 35 | // generate snowpack.config.json 36 | writeJsonSync( 37 | join($resolveRoot, SNOWPACK_CONFIG_JSON), 38 | getSnowpackConfigJson({ ui, main, isTypescript, isSass, isLess }), 39 | ); 40 | }; 41 | -------------------------------------------------------------------------------- /build/snowpack/utils.js: -------------------------------------------------------------------------------- 1 | const { templatePackageJson, templateSnowpackConfig } = require('./config'); 2 | const { dependencies, devDependencies } = require('../../dependencies.config'); 3 | const { mergeBasicDependencies } = require('../utils'); 4 | 5 | module.exports = { 6 | getPackageJson({ 7 | ui, 8 | main, 9 | projectName, 10 | isTypescript, 11 | isSass, 12 | isLess, 13 | isLint, 14 | isPrettier, 15 | }) { 16 | const result = JSON.parse(JSON.stringify(templatePackageJson)); 17 | 18 | if (isTypescript) { 19 | result.devDependencies['@snowpack/plugin-typescript'] = 20 | devDependencies['@snowpack/plugin-typescript']; 21 | } 22 | if (isSass) { 23 | result.devDependencies['@snowpack/plugin-sass'] = 24 | devDependencies['@snowpack/plugin-sass']; 25 | } 26 | if (isLess) { 27 | result.devDependencies['snowpack-plugin-less'] = 28 | devDependencies['snowpack-plugin-less']; 29 | } 30 | if (main === 'vue') { 31 | result.dependencies['@morgul/snowpack-plugin-vue2'] = 32 | dependencies['@morgul/snowpack-plugin-vue2']; 33 | } 34 | 35 | return mergeBasicDependencies(result, { 36 | isLint, 37 | isPrettier, 38 | projectName, 39 | ui, 40 | main, 41 | isTypescript, 42 | }); 43 | }, 44 | 45 | getSnowpackConfigJson({ ui, main, isTypescript, isSass, isLess }) { 46 | const result = JSON.parse(JSON.stringify(templateSnowpackConfig)); 47 | 48 | if (main === 'vue') { 49 | result.plugins.push('@morgul/snowpack-plugin-vue2'); 50 | } 51 | if (isTypescript) { 52 | result.plugins.push('@snowpack/plugin-typescript'); 53 | } 54 | if (ui === 'antd') { 55 | result.packageOptions.push('antd'); 56 | } else if (ui === 'element') { 57 | result.packageOptions.push('element-ui'); 58 | } 59 | if (isSass) { 60 | result.plugins.push('@snowpack/plugin-sass'); 61 | } 62 | if (isLess) { 63 | result.plugins.push('snowpack-plugin-less'); 64 | } 65 | return result; 66 | }, 67 | }; 68 | -------------------------------------------------------------------------------- /build/template/babel/index.js: -------------------------------------------------------------------------------- 1 | module.exports = () => { 2 | return { 3 | file: '.babelrc', 4 | text: { 5 | presets: [ 6 | [ 7 | '@babel/preset-env', 8 | { 9 | modules: false, 10 | }, 11 | ], 12 | '@babel/preset-react', 13 | ], 14 | }, 15 | }; 16 | }; 17 | -------------------------------------------------------------------------------- /build/template/empty/index.js: -------------------------------------------------------------------------------- 1 | const indexJs = require('./indexJs'); 2 | const indexHtml = require('./indexHtml'); 3 | 4 | module.exports = ({ projectName, buildTool, main, isTypescript }) => { 5 | return [ 6 | indexHtml({ projectName, buildTool, main, isTypescript }), 7 | indexJs({ isTypescript }), 8 | ]; 9 | }; 10 | -------------------------------------------------------------------------------- /build/template/empty/indexHtml.js: -------------------------------------------------------------------------------- 1 | const { getScript } = require('../utils'); 2 | 3 | module.exports = ({ projectName, buildTool, main, isTypescript }) => { 4 | return { 5 | file: 'index.html', 6 | text: ` 7 | 8 | 9 | 10 | 11 | 12 | ${projectName} 13 | ${getScript({ buildTool, main, isTypescript })} 14 | 15 | 16 |
17 | 18 | `, 19 | }; 20 | }; 21 | -------------------------------------------------------------------------------- /build/template/empty/indexJs.js: -------------------------------------------------------------------------------- 1 | module.exports = ({ isTypescript }) => { 2 | let file = 'index.js'; 3 | let text = `const mountNode = document.getElementById('app'); 4 | mountNode.innerHTML = 'hello world'; 5 | console.log('empty')`; 6 | if (isTypescript) { 7 | file = 'index.ts'; 8 | text = `const mountNode: HTMLElement | null = document.getElementById('app'); 9 | mountNode!.innerHTML = 'hello world'; 10 | 11 | `; 12 | } 13 | 14 | return { 15 | text, 16 | file, 17 | }; 18 | }; 19 | -------------------------------------------------------------------------------- /build/template/ignore/index.js: -------------------------------------------------------------------------------- 1 | module.exports = () => { 2 | return { 3 | file: '.gitignore', 4 | text: `node_modules/ 5 | coverage/ 6 | dist/* 7 | *.log 8 | 9 | .DS_Store 10 | .idea 11 | `, 12 | }; 13 | }; 14 | -------------------------------------------------------------------------------- /build/template/lint/index.js: -------------------------------------------------------------------------------- 1 | const lint = { 2 | rules: {}, 3 | env: { 4 | es6: true, 5 | browser: true, 6 | }, 7 | parserOptions: { 8 | ecmaVersion: 2018, 9 | sourceType: 'module', 10 | }, 11 | extends: ['eslint:recommended'], 12 | globals: { 13 | Atomics: 'readonly', 14 | SharedArrayBuffer: 'readonly', 15 | }, 16 | plugins: [], 17 | }; 18 | module.exports = ({ main, isPrettier, isLint }) => { 19 | if (!isLint) { 20 | return null; 21 | } 22 | if (main === 'react') { 23 | lint.plugins.push('react'); 24 | lint.parserOptions.ecmaFeatures = { 25 | jsx: true, 26 | }; 27 | } 28 | if (isPrettier) { 29 | lint.extends.push('plugin:prettier/recommended'); 30 | } 31 | return { 32 | file: '.eslintrc.json', 33 | text: JSON.stringify(lint), 34 | }; 35 | }; 36 | -------------------------------------------------------------------------------- /build/template/react17/app.js: -------------------------------------------------------------------------------- 1 | module.exports = ({ ui, isTypescript }) => { 2 | let text = `import React from 'react'; 3 | 4 | `; 5 | let file; 6 | if (ui === 'antd') { 7 | text += `import { Button } from 'antd'; 8 | 9 | `; 10 | } 11 | if (isTypescript) { 12 | file = 'App.tsx'; 13 | if (ui === 'antd') { 14 | text += `const App: React.FC = () => { 15 | return ( 16 | <> 17 | 18 | 19 | ); 20 | }; 21 | `; 22 | } else { 23 | text += `const App: React.FC = () => { 24 | return ( 25 | <> 26 |

Hello world!

27 | 28 | ); 29 | }; 30 | `; 31 | } 32 | } else { 33 | file = 'App.jsx'; 34 | if (ui === 'antd') { 35 | text += `const App = () => { 36 | return ( 37 | <> 38 | 39 | 40 | ); 41 | }; 42 | `; 43 | } else { 44 | text += `const App = () => { 45 | return ( 46 | <> 47 |

Hello world!

48 | 49 | ); 50 | }; 51 | `; 52 | } 53 | } 54 | text += ` 55 | export default App; 56 | `; 57 | return { 58 | text, 59 | file, 60 | }; 61 | }; 62 | -------------------------------------------------------------------------------- /build/template/react17/index.js: -------------------------------------------------------------------------------- 1 | const indexJs = require('./indexJs'); 2 | const indexHtml = require('./indexHtml'); 3 | const app = require('./app'); 4 | 5 | module.exports = ({ 6 | ui, 7 | projectName, 8 | main, 9 | buildTool, 10 | isTypescript, 11 | isSass, 12 | isLess, 13 | }) => { 14 | return [ 15 | indexHtml({ projectName, buildTool, main, isTypescript }), 16 | indexJs({ ui, isTypescript, isSass, isLess }), 17 | app({ ui, isTypescript }), 18 | ]; 19 | }; 20 | -------------------------------------------------------------------------------- /build/template/react17/indexHtml.js: -------------------------------------------------------------------------------- 1 | const { getScript } = require('../utils'); 2 | 3 | module.exports = ({ projectName, buildTool, main, isTypescript }) => { 4 | const isWebpack = 5 | buildTool !== 'webpack' 6 | ? `\n${getScript({ buildTool, main, isTypescript })}` 7 | : ''; 8 | return { 9 | file: 'index.html', 10 | text: ` 11 | 12 | 13 | 14 | ${projectName} ${isWebpack} 15 | 16 | 17 |
18 | 19 | `, 20 | }; 21 | }; 22 | -------------------------------------------------------------------------------- /build/template/react17/indexJs.js: -------------------------------------------------------------------------------- 1 | const { sassName, lessName } = require('../../config'); 2 | 3 | module.exports = ({ ui, isTypescript, isSass, isLess }) => { 4 | let text = `import React from 'react'; 5 | import ReactDOM from 'react-dom'; 6 | import App from './App'; 7 | `; 8 | 9 | let file = 'index.jsx'; 10 | if (ui === 'antd') { 11 | text += `import 'antd/dist/antd.css'; 12 | `; 13 | } 14 | if (isSass) { 15 | text += `import './${sassName}'; 16 | `; 17 | } 18 | 19 | if (isLess) { 20 | text += `import './${lessName}'; 21 | `; 22 | } 23 | 24 | text += ` 25 | const mountNode = document.getElementById('app'); 26 | ReactDOM.render(, mountNode); 27 | `; 28 | 29 | if (isTypescript) { 30 | file = 'index.tsx'; 31 | } 32 | return { 33 | text, 34 | file, 35 | }; 36 | }; 37 | -------------------------------------------------------------------------------- /build/template/react17/styles.scss: -------------------------------------------------------------------------------- 1 | $primary-color: blue; 2 | 3 | h1 { 4 | color: $primary-color; 5 | } 6 | -------------------------------------------------------------------------------- /build/template/readme/index.js: -------------------------------------------------------------------------------- 1 | module.exports = ({ projectName }) => { 2 | return { 3 | file: 'README.md', 4 | text: `# ${projectName} 5 | 6 | This is a/an ${projectName}. 7 | 8 | ## How to run on localhost 9 | 10 | First install dependencies: 11 | 12 | \`\`\`sh 13 | npm install 14 | \`\`\` 15 | 16 | To run in dev mode mode: 17 | 18 | \`\`\`sh 19 | npm start 20 | \`\`\` 21 | 22 | Then go to http://localhost:8080 23 | 24 | To create a production build: 25 | 26 | \`\`\`sh 27 | npm run build 28 | \`\`\` 29 | 30 | `, 31 | }; 32 | }; 33 | -------------------------------------------------------------------------------- /build/template/style/index.js: -------------------------------------------------------------------------------- 1 | const sass = require('./sass'); 2 | const less = require('./less'); 3 | 4 | module.exports = ({ isSass, isLess }) => { 5 | return [sass({ isSass }), less({ isLess })].filter(Boolean); 6 | }; 7 | -------------------------------------------------------------------------------- /build/template/style/less.js: -------------------------------------------------------------------------------- 1 | const { lessName } = require('../../config'); 2 | 3 | module.exports = ({ isLess }) => { 4 | if (isLess) { 5 | return { 6 | file: lessName, 7 | text: `@primary-color: lightBlue; 8 | 9 | h1 { 10 | color: @primary-color; 11 | }`, 12 | }; 13 | } 14 | return null; 15 | }; 16 | -------------------------------------------------------------------------------- /build/template/style/sass.js: -------------------------------------------------------------------------------- 1 | const { sassName } = require('../../config'); 2 | 3 | module.exports = ({ isSass }) => { 4 | if (isSass) { 5 | return { 6 | file: sassName, 7 | text: `$primary-color: blue; 8 | 9 | h1 { 10 | color: $primary-color; 11 | }`, 12 | }; 13 | } 14 | return null; 15 | }; 16 | -------------------------------------------------------------------------------- /build/template/tsconfig/app.js: -------------------------------------------------------------------------------- 1 | const config = require('./defaultConfig'); 2 | 3 | const vueShim = { 4 | text: `declare module "*.vue" { 5 | import Vue from 'vue' 6 | export default Vue 7 | }`, 8 | file: 'vue-shim.d.ts', 9 | }; 10 | const viteVueEnv = { 11 | text: `/// `, 12 | file: 'vite-env.d.ts', 13 | }; 14 | const viteVueShims = { 15 | text: `declare module '*.vue' { 16 | import { DefineComponent } from 'vue' 17 | // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types 18 | const component: DefineComponent<{}, {}, any> 19 | export default component 20 | }`, 21 | file: 'shims-vue.d.ts', 22 | }; 23 | 24 | module.exports = ({ main, includePath, buildTool, isTypescript }) => { 25 | const result = []; 26 | if (!isTypescript) return result; 27 | if (main === 'react') { 28 | config.compilerOptions.jsx = 'react'; 29 | } 30 | config.include.push(`./${includePath}/**/*`); 31 | result.push({ 32 | text: JSON.stringify(config), 33 | file: 'tsconfig.json', 34 | }); 35 | if (buildTool === 'snowpack') { 36 | if (main === 'vue') { 37 | result.push(vueShim); 38 | } 39 | } else if (buildTool === 'vite') { 40 | if (main === 'vue') { 41 | result.push(viteVueShims); 42 | } 43 | result.push(viteVueEnv); 44 | } 45 | 46 | return result; 47 | }; 48 | -------------------------------------------------------------------------------- /build/template/tsconfig/defaultConfig.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | compilerOptions: { 3 | outDir: './dist/', 4 | sourceMap: true, 5 | strict: true, 6 | noImplicitReturns: true, 7 | noImplicitAny: true, 8 | module: 'es6', 9 | moduleResolution: 'node', 10 | target: 'es5', 11 | allowJs: true, 12 | allowSyntheticDefaultImports: true, 13 | }, 14 | include: [], 15 | }; 16 | -------------------------------------------------------------------------------- /build/template/tsconfig/index.js: -------------------------------------------------------------------------------- 1 | const app = require('./app'); 2 | 3 | module.exports = { 4 | app, 5 | }; 6 | -------------------------------------------------------------------------------- /build/template/unitTest/index.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lgwebdream/fe-code/4a49a289e953bfd99f37875c966dd53590eb1401/build/template/unitTest/index.js -------------------------------------------------------------------------------- /build/template/utils.js: -------------------------------------------------------------------------------- 1 | module.exports.getScript = ({ buildTool, main, isTypescript }) => { 2 | const viteScriptObj = { 3 | emptyJs: '', 4 | emptyTs: '', 5 | reactJs: '', 6 | reactTs: '', 7 | }; 8 | let viteScript = ''; 9 | let isTs = ''; 10 | switch (main) { 11 | case 'react': 12 | isTs = isTypescript ? 'reactTs' : 'reactJs'; 13 | viteScript = viteScriptObj[isTs]; 14 | break; 15 | case 'none': 16 | isTs = isTypescript ? 'emptyTs' : 'emptyJs'; 17 | viteScript = viteScriptObj[isTs]; 18 | break; 19 | default: 20 | viteScript = viteScriptObj.emptyJs; 21 | } 22 | 23 | const scripts = { 24 | snowpack: '', 25 | vite: viteScript, 26 | webpack: '', 27 | }; 28 | return scripts[buildTool]; 29 | }; 30 | -------------------------------------------------------------------------------- /build/template/vue2/app.js: -------------------------------------------------------------------------------- 1 | module.exports = ({ isTypescript, ui }) => { 2 | let text = ''; 3 | if (ui === 'element') { 4 | text += ` 9 | `; 10 | } else { 11 | text += ` 16 | `; 17 | } 18 | if (isTypescript) { 19 | text += ` 20 | 21 | `; 35 | } else { 36 | text += ` 37 | 38 | 49 | `; 50 | } 51 | 52 | return { 53 | text, 54 | file: 'App.vue', 55 | }; 56 | }; 57 | -------------------------------------------------------------------------------- /build/template/vue2/index.js: -------------------------------------------------------------------------------- 1 | const indexJs = require('./indexJs'); 2 | const indexHtml = require('./indexHtml'); 3 | const app = require('./app'); 4 | 5 | module.exports = ({ 6 | ui, 7 | projectName, 8 | main, 9 | buildTool, 10 | isTypescript, 11 | isSass, 12 | isLess, 13 | }) => { 14 | return [ 15 | indexHtml({ projectName, buildTool, main, isTypescript }), 16 | indexJs({ ui, isSass, isLess }), 17 | app({ ui, isTypescript }), 18 | ]; 19 | }; 20 | -------------------------------------------------------------------------------- /build/template/vue2/indexHtml.js: -------------------------------------------------------------------------------- 1 | const { getScript } = require('../utils'); 2 | 3 | module.exports = ({ projectName, buildTool, main, isTypescript }) => { 4 | const isWebpack = 5 | buildTool !== 'webpack' 6 | ? `\n${getScript({ buildTool, main, isTypescript })}` 7 | : ''; 8 | return { 9 | file: 'index.html', 10 | text: ` 11 | 12 | 13 | 14 | ${projectName} ${isWebpack} 15 | 16 | 17 |
18 | 19 | `, 20 | }; 21 | }; 22 | -------------------------------------------------------------------------------- /build/template/vue2/indexJs.js: -------------------------------------------------------------------------------- 1 | const { sassName, lessName } = require('../../config'); 2 | 3 | module.exports = ({ ui, isSass, isLess }) => { 4 | let text = `import Vue from 'vue'; 5 | import App from './App.vue'; 6 | `; 7 | 8 | if (ui === 'element') { 9 | text += ` 10 | import ElementUI from 'element-ui'; 11 | import 'element-ui/lib/theme-chalk/index.css'; 12 | `; 13 | } 14 | if (isSass) { 15 | text += `import './${sassName}'; 16 | `; 17 | } 18 | if (isLess) { 19 | text += `import './${lessName}'; 20 | `; 21 | } 22 | if (ui === 'element') { 23 | text += ` 24 | Vue.use(ElementUI); 25 | `; 26 | } 27 | 28 | text += ` 29 | new Vue({ 30 | el: '#app', 31 | render: h => h(App), 32 | });`; 33 | return { 34 | text, 35 | file: 'index.js', 36 | }; 37 | }; 38 | -------------------------------------------------------------------------------- /build/template/vue3/app.js: -------------------------------------------------------------------------------- 1 | module.exports = ({ isTypescript, ui }) => { 2 | let text = ''; 3 | if (ui === 'element') { 4 | text += ` 9 | `; 10 | } else { 11 | text += ` 16 | `; 17 | } 18 | if (isTypescript) { 19 | text += ` 20 | 21 | `; 31 | } else { 32 | text += ` 33 | 34 | 45 | `; 46 | } 47 | 48 | return { 49 | text, 50 | file: 'App.vue', 51 | }; 52 | }; 53 | -------------------------------------------------------------------------------- /build/template/vue3/index.js: -------------------------------------------------------------------------------- 1 | const indexJs = require('./indexJs'); 2 | const indexHtml = require('./indexHtml'); 3 | const app = require('./app'); 4 | 5 | module.exports = ({ 6 | ui, 7 | projectName, 8 | main, 9 | buildTool, 10 | isTypescript, 11 | isSass, 12 | isLess, 13 | }) => { 14 | return [ 15 | indexHtml({ projectName, buildTool, main, isTypescript }), 16 | indexJs({ ui, isSass, isLess }), 17 | app({ ui, isTypescript }), 18 | ]; 19 | }; 20 | -------------------------------------------------------------------------------- /build/template/vue3/indexHtml.js: -------------------------------------------------------------------------------- 1 | const { getScript } = require('../utils'); 2 | 3 | module.exports = ({ projectName, buildTool, main, isTypescript }) => { 4 | const isWebpack = 5 | buildTool !== 'webpack' 6 | ? `\n${getScript({ buildTool, main, isTypescript })}` 7 | : ''; 8 | return { 9 | file: 'index.html', 10 | text: ` 11 | 12 | 13 | 14 | ${projectName} ${isWebpack} 15 | 16 | 17 |
18 | 19 | `, 20 | }; 21 | }; 22 | -------------------------------------------------------------------------------- /build/template/vue3/indexJs.js: -------------------------------------------------------------------------------- 1 | const { sassName, lessName } = require('../../config'); 2 | 3 | module.exports = ({ ui, isSass, isLess }) => { 4 | let text = `import Vue from 'vue'; 5 | import App from './App.vue'; 6 | `; 7 | 8 | if (ui === 'element') { 9 | text += ` 10 | import ElementUI from 'element-ui'; 11 | import 'element-ui/lib/theme-chalk/index.css'; 12 | `; 13 | } 14 | if (isSass) { 15 | text += `import './${sassName}'; 16 | `; 17 | } 18 | if (isLess) { 19 | text += `import './${lessName}'; 20 | `; 21 | } 22 | if (ui === 'element') { 23 | text += ` 24 | Vue.use(ElementUI); 25 | `; 26 | } 27 | 28 | text += ` 29 | new Vue({ 30 | el: '#app', 31 | render: h => h(App), 32 | });`; 33 | return { 34 | text, 35 | file: 'index.js', 36 | }; 37 | }; 38 | -------------------------------------------------------------------------------- /build/vite/config.js: -------------------------------------------------------------------------------- 1 | const { devDependencies } = require('../../dependencies.config'); 2 | 3 | module.exports = { 4 | templatePackageJson: { 5 | name: '', 6 | version: '1.0.0', 7 | description: '', 8 | main: 'index.js', 9 | keywords: [], 10 | author: '', 11 | license: 'ISC', 12 | scripts: { 13 | clean: 'rm dist/bundle.js', 14 | dev: 'vite', 15 | build: 'vite build', 16 | }, 17 | dependencies: {}, 18 | devDependencies: { 19 | vite: devDependencies.vite, 20 | }, 21 | }, 22 | templateViteConfig: { 23 | plugins: {}, 24 | packageOptions: [], 25 | }, 26 | PACKAGE_JSON: 'package.json', 27 | VITE_CONFIG_JS: 'vite.config.js', 28 | }; 29 | -------------------------------------------------------------------------------- /build/vite/index.js: -------------------------------------------------------------------------------- 1 | const { writeJsonSync, outputFileSync } = require('fs-extra'); 2 | const { join } = require('path'); 3 | const { getPackageJson, getViteConfigJs } = require('./utils'); 4 | const { PACKAGE_JSON, VITE_CONFIG_JS } = require('./config'); 5 | 6 | module.exports = ({ 7 | mainFramework: { name: main }, 8 | uiFramework: ui, 9 | projectName, 10 | $resolveRoot, 11 | $featureChecks: { typescript: isTypescript, sass: isSass, less: isLess }, 12 | }) => { 13 | // generate package.json 14 | writeJsonSync( 15 | join($resolveRoot, PACKAGE_JSON), 16 | getPackageJson({ ui, main, projectName, isTypescript, isSass, isLess }), 17 | ); 18 | 19 | // generate vite.config.js 20 | const viteConfig = getViteConfigJs({ 21 | ui, 22 | main, 23 | isTypescript, 24 | sass: isSass, 25 | less: isLess, 26 | }); 27 | outputFileSync(join($resolveRoot, VITE_CONFIG_JS), viteConfig); 28 | }; 29 | -------------------------------------------------------------------------------- /build/webpack/config.js: -------------------------------------------------------------------------------- 1 | const { devDependencies } = require('../../dependencies.config'); 2 | 3 | module.exports = { 4 | templatePackageJson: { 5 | name: '', 6 | version: '1.0.0', 7 | description: '', 8 | main: 'index.js', 9 | keywords: [], 10 | author: '', 11 | license: 'ISC', 12 | scripts: { 13 | clean: 'rm dist/bundle.js', 14 | dev: 'webpack serve --mode development', 15 | build: 'webpack --mode production', 16 | }, 17 | dependencies: {}, 18 | devDependencies: { 19 | webpack: devDependencies.webpack, 20 | 'webpack-cli': devDependencies['webpack-cli'], 21 | 'html-webpack-plugin': devDependencies['html-webpack-plugin'], 22 | 'webpack-dev-server': devDependencies['webpack-dev-server'], 23 | 'css-loader': devDependencies['css-loader'], 24 | 'style-loader': devDependencies['style-loader'], 25 | 'babel-loader': devDependencies['babel-loader'], 26 | '@babel/core': devDependencies['@babel/core'], 27 | '@babel/preset-env': devDependencies['@babel/preset-env'], 28 | }, 29 | }, 30 | templateWebpackConfig: { 31 | plugins: { 32 | path: 'path', 33 | HtmlWebpackPlugin: 'html-webpack-plugin', 34 | }, 35 | packageOptions: [], 36 | }, 37 | PACKAGE_JSON: 'package.json', 38 | WEBPACK_CONFIG_JS: 'webpack.config.js', 39 | }; 40 | -------------------------------------------------------------------------------- /build/webpack/index.js: -------------------------------------------------------------------------------- 1 | const { writeJsonSync, outputFileSync } = require('fs-extra'); 2 | const { join } = require('path'); 3 | const { getPackageJson, getWebpackConfigJs } = require('./utils'); 4 | const { PACKAGE_JSON, WEBPACK_CONFIG_JS } = require('./config'); 5 | 6 | module.exports = ({ 7 | mainFramework: { name: main }, 8 | uiFramework: ui, 9 | projectName, 10 | $resolveRoot, 11 | $featureChecks, 12 | }) => { 13 | const { 14 | typescript: isTypescript, 15 | sass: isSass, 16 | less: isLess, 17 | prettier: isPrettier, 18 | lint: isLint, 19 | } = $featureChecks; 20 | // generate package.json 21 | writeJsonSync( 22 | join($resolveRoot, PACKAGE_JSON), 23 | getPackageJson({ 24 | isPrettier, 25 | isLint, 26 | ui, 27 | main, 28 | projectName, 29 | isTypescript, 30 | isSass, 31 | isLess, 32 | }), 33 | ); 34 | 35 | // generate webpack.config.js 36 | outputFileSync( 37 | join($resolveRoot, WEBPACK_CONFIG_JS), 38 | getWebpackConfigJs({ 39 | ui, 40 | main, 41 | isTypescript, 42 | isSass, 43 | isLess, 44 | }), 45 | ); 46 | }; 47 | -------------------------------------------------------------------------------- /commitlint.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { extends: ['@commitlint/config-conventional'] }; 2 | -------------------------------------------------------------------------------- /constants/concept.js: -------------------------------------------------------------------------------- 1 | /** 2 | * defination of useful concept used in case of spelling error 3 | */ 4 | module.exports = { 5 | GET: 'GET', 6 | POST: 'POST', 7 | DELETE: 'DELETE', 8 | Vue: 'Vue', 9 | React: 'React', 10 | JQuery: 'JQuery', 11 | JS: 'JavaScript', 12 | TS: 'TypeScript', 13 | Service: 'service', 14 | CodeSnippet: 'code-snippets', 15 | Axios: 'axios', 16 | Fetch: 'fetch', 17 | }; 18 | -------------------------------------------------------------------------------- /dependencies.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | dependencies: { 3 | react: '^17.0.2', 4 | vue: '^2.6.14', 5 | antd: '^4.16.8', 6 | 'react-dom': '^17.0.2', 7 | 'element-ui': '^2.15.3', 8 | '@morgul/snowpack-plugin-vue2': '^0.4.2', 9 | '@vitejs/plugin-react-refresh': '^1.3.1', 10 | }, 11 | devDependencies: { 12 | snowpack: '^3.8.2', 13 | typescript: '^4.3.5', 14 | vite: '^2.0.5', 15 | less: '^4.1.1', 16 | sass: '^1.37.0', 17 | webpack: '^5.46.0', 18 | uglifyjs: '^2.4.11', 19 | eslint: '^7.32.0', 20 | prettier: '^2.3.2', 21 | 'eslint-plugin-react': '^7.24.0', 22 | 'eslint-config-prettier': '^8.3.0', 23 | 'eslint-plugin-prettier': '^3.4.0', 24 | '@snowpack/plugin-typescript': '^1.2.1', 25 | 'vite-plugin-vue2': '^1.4.0', 26 | 'vue-template-compiler': '^2.6.14', 27 | 'vite-plugin-html': '^2.0.7', 28 | '@types/react': '^17.0.0', 29 | '@types/react-dom': '^17.0.0', 30 | '@vue/compiler-sfc': '^3.0.5', 31 | 'vue-tsc': '^0.2.2', 32 | 'webpack-cli': '^4.7.2', 33 | '@snowpack/plugin-sass': '^1.4.0', 34 | 'snowpack-plugin-less': '^1.0.7', 35 | 'ts-loader': '^9.2.4', 36 | '@babel/preset-react': '^7.14.5', 37 | 'babel-loader': '^8.2.2', 38 | '@babel/core': '^7.14.8', 39 | '@babel/preset-env': '^7.14.8', 40 | '@hot-loader/react-dom': '^17.0.1+4.13.0', 41 | 'webpack-dev-server': '^3.11.2', 42 | 'css-loader': '^6.2.0', 43 | 'sass-loader': '^12.1.0', 44 | 'node-sass': '^6.0.1', 45 | 'less-loader': '^10.0.1', 46 | 'style-loader': '^3.2.1', 47 | 'html-webpack-plugin': '^5.3.2', 48 | 'vue-loader': '^15.9.8', 49 | }, 50 | componentDependencies: { 51 | react: { 52 | dependencies: { 53 | classnames: '^2.3.1', 54 | }, 55 | devDependencies: {}, 56 | }, 57 | vue: {}, 58 | }, 59 | }; 60 | -------------------------------------------------------------------------------- /docs/zh-CN.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lgwebdream/fe-code/4a49a289e953bfd99f37875c966dd53590eb1401/docs/zh-CN.md -------------------------------------------------------------------------------- /examples/react-demo/.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | 15 | [Makefile] 16 | indent_style = tab 17 | -------------------------------------------------------------------------------- /examples/react-demo/.eslintignore: -------------------------------------------------------------------------------- 1 | /lambda/ 2 | /scripts 3 | /config 4 | /public 5 | .history -------------------------------------------------------------------------------- /examples/react-demo/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /npm-debug.log* 6 | /yarn-error.log 7 | /yarn.lock 8 | /package-lock.json 9 | 10 | # production 11 | /dist 12 | 13 | # misc 14 | .DS_Store 15 | 16 | # umi 17 | /src/.umi 18 | /src/.umi-production 19 | /src/.umi-test 20 | /.env.local 21 | -------------------------------------------------------------------------------- /examples/react-demo/.prettierignore: -------------------------------------------------------------------------------- 1 | **/*.md 2 | **/*.svg 3 | **/*.ejs 4 | **/*.html 5 | package.json 6 | .umi 7 | .umi-production 8 | .umi-test 9 | -------------------------------------------------------------------------------- /examples/react-demo/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "all", 4 | "printWidth": 80, 5 | "overrides": [ 6 | { 7 | "files": ".prettierrc", 8 | "options": { "parser": "json" } 9 | } 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /examples/react-demo/.umirc.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'umi'; 2 | import { join } from 'path'; 3 | 4 | const reactLibPath = join(process.cwd(), '../../lib/react/'); 5 | 6 | const pageRouters = [ 7 | { path: '/', component: '@/pages/index' }, 8 | { path: '/table', component: '@/pages/Table' }, 9 | { path: '/toolbar', component: '@/pages/toolbar' }, 10 | { path: '/role', component: '@/pages/role' }, 11 | { path: '/formDemo', component: '@/pages/FormDemo' }, 12 | ]; 13 | 14 | export default defineConfig({ 15 | nodeModulesTransform: { 16 | type: 'none', 17 | }, 18 | routes: pageRouters, 19 | fastRefresh: {}, 20 | alias: { 21 | '@crud': reactLibPath, 22 | }, 23 | chainWebpack: (config) => { 24 | config.module.rules 25 | .values() 26 | .map((it) => it.include.add(reactLibPath).add(process.cwd())); 27 | }, 28 | webpack5: {}, 29 | mfsu: {}, 30 | }); 31 | -------------------------------------------------------------------------------- /examples/react-demo/README.md: -------------------------------------------------------------------------------- 1 | # umi project 2 | 3 | ## Getting Started 4 | 5 | Install dependencies, 6 | 7 | ```bash 8 | $ yarn 9 | ``` 10 | 11 | Start the dev server, 12 | 13 | ```bash 14 | $ yarn start 15 | ``` 16 | -------------------------------------------------------------------------------- /examples/react-demo/mock/api.ts: -------------------------------------------------------------------------------- 1 | import Mock from 'mockjs'; 2 | 3 | export default { 4 | 'POST /api/json/list': { 5 | data: Mock.mock({ 6 | 'data|100': [ 7 | { 8 | 'id|+1': 1, 9 | 'key|+1': 0, 10 | name: '@cname', 11 | 'age|1-100': 100, 12 | 'score|1-32': 32, 13 | 'level|1-5': 5, 14 | birthday: '@date', 15 | address: '@county(true)', 16 | description: '@cparagraph(1, 3)', 17 | title: '@pick(["cto","ceo","coo"])', 18 | department: '@pick(["light","light2"])', 19 | mapping: '@pick(["优秀","良好","差"])', 20 | flag: '@pick([true,false])', 21 | 'tags|1-2': ['@pick(["外向","善于沟通","脾气差"])'], 22 | }, 23 | ], 24 | }), 25 | code: 200, 26 | msg: '服务器成功返回数据', 27 | }, 28 | 'POST /api/json/add': { 29 | result: { id: 2, name: '张三', age: 18, title: 'cto' }, 30 | success: true, 31 | }, 32 | 'POST /api/json/edit': { 33 | result: { id: 3, name: '张三', age: 18, title: 'cto' }, 34 | success: true, 35 | }, 36 | 'POST /api/json/delete': { 37 | result: 3, 38 | success: true, 39 | }, 40 | 'POST /api/json/export': { 41 | result: 'hello world', 42 | success: true, 43 | }, 44 | }; 45 | -------------------------------------------------------------------------------- /examples/react-demo/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "scripts": { 4 | "start": "umi dev", 5 | "build": "umi build", 6 | "postinstall": "umi generate tmp", 7 | "test": "umi-test", 8 | "test:coverage": "umi-test --coverage" 9 | }, 10 | "dependencies": { 11 | "@ant-design/icons": "^4.6.2", 12 | "@ant-design/pro-layout": "^6.5.0", 13 | "antd": "^4.16.8", 14 | "classnames": "^2.3.1", 15 | "mockjs": "^1.1.0", 16 | "react": "17.x", 17 | "react-dom": "17.x", 18 | "umi": "^4.0.0", 19 | "umi-request": "^1.3.9" 20 | }, 21 | "devDependencies": { 22 | "@types/classnames": "^2.3.1", 23 | "@types/mockjs": "^1.0.4", 24 | "@types/react": "^17.0.0", 25 | "@types/react-dom": "^17.0.0", 26 | "@umijs/preset-react": "1.x", 27 | "@umijs/test": "^3.5.10", 28 | "yorkie": "^2.0.0" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /examples/react-demo/public/mock/delete.json: -------------------------------------------------------------------------------- 1 | {"result":3,"success":true} -------------------------------------------------------------------------------- /examples/react-demo/public/mock/modify.json: -------------------------------------------------------------------------------- 1 | {"result":{"id":3,"name":"张三","age":18,"title":"cto"},"success":true} -------------------------------------------------------------------------------- /examples/react-demo/src/components/Container/Modal.tsx: -------------------------------------------------------------------------------- 1 | import { Form, Modal } from 'antd'; 2 | import React, { useEffect, useState } from 'react'; 3 | import FForm from '../Form'; 4 | import { ICrudModalProps } from './ModalTypes'; 5 | 6 | const FCrudModal = (props: ICrudModalProps): React.ReactElement => { 7 | const { data, columns, onOk, visible, onCancel } = props; 8 | const [form] = Form.useForm(); 9 | const [isInit, setInit] = useState(false); 10 | 11 | useEffect(() => { 12 | setInit(true); 13 | }, []); 14 | 15 | useEffect(() => { 16 | if (!isInit) return; 17 | 18 | if (visible) { 19 | form?.setFieldsValue(data); 20 | } else { 21 | form?.resetFields(); 22 | } 23 | }, [data, visible]); 24 | 25 | return ( 26 | { 31 | form.validateFields().then(() => { 32 | onOk && onOk({ ...form.getFieldsValue() }); 33 | }); 34 | }} 35 | onCancel={e => { 36 | onCancel && onCancel(e); 37 | }} 38 | > 39 | 45 | 46 | ); 47 | }; 48 | 49 | export default FCrudModal; 50 | -------------------------------------------------------------------------------- /examples/react-demo/src/components/Container/ModalTypes.ts: -------------------------------------------------------------------------------- 1 | import { ModalProps } from 'antd'; 2 | import { ICrudColumn } from '../CrudTypes'; 3 | 4 | export interface ICrudModalProps extends ModalProps { 5 | /** 表单 */ 6 | data?: Record; 7 | /** 表单字段列表 */ 8 | columns?: ICrudColumn[]; 9 | } 10 | -------------------------------------------------------------------------------- /examples/react-demo/src/components/Form/FormTypes.ts: -------------------------------------------------------------------------------- 1 | import { 2 | FormProps, 3 | FormItemProps, 4 | InputProps, 5 | InputNumberProps, 6 | SelectProps, 7 | DatePickerProps, 8 | TimePickerProps, 9 | TreeSelectProps, 10 | CascaderProps, 11 | } from 'antd'; 12 | import { IFormComTypeEnum } from './constant'; 13 | 14 | export { IFormComTypeEnum }; 15 | 16 | export interface IFormItemProps extends FormItemProps { 17 | // TODO 18 | isList?: boolean; // 是否是List 19 | // TODO 20 | visibleOn?: string; // 显示联动 21 | key?: string; // 唯一值 22 | } 23 | 24 | export type IFormComponentProps = 25 | | InputProps 26 | | InputNumberProps 27 | | SelectProps 28 | | DatePickerProps 29 | | TimePickerProps 30 | | TreeSelectProps 31 | | CascaderProps; 32 | 33 | export type IFormProps = FormProps; 34 | -------------------------------------------------------------------------------- /examples/react-demo/src/components/Form/README.md: -------------------------------------------------------------------------------- 1 | ## form 支持组件 2 | 3 | ```js 4 | export enum IFormComTypeEnum { 5 | Input = 'Input', 6 | InputNumber = 'InputNumber', 7 | Select = 'Select', 8 | DatePicker = 'DatePicker', // 日期选择器 9 | TimePicker = 'TimePicker', //时间选择框 10 | List = 'FormList' 11 | } 12 | ``` 13 | 14 | ## FormRender 入参 15 | 16 | ```js 17 | { 18 | layout: '', 19 | initialValues: '', 20 | onValuesChange: '', 21 | labelCol: { span: 4 }, 22 | wrapperCol: { span: 14 }, 23 | } 24 | ``` 25 | 26 | ## form schema数据格式 27 | 28 | ```js 29 | export const schema = [ 30 | { 31 | comType: IFormComTypeEnum.Input, 32 | comProps: { 33 | 34 | }, 35 | itemProps: { 36 | name: "email", 37 | label: "E-mail", 38 | rules: [ 39 | { 40 | type: 'email', 41 | message: 'The input is not valid E-mail!', 42 | }, 43 | { 44 | required: true, 45 | message: 'Please input your E-mail!' 46 | } 47 | ] 48 | } 49 | }, 50 | ] 51 | 52 | ``` 53 | 54 | 55 | ## FSelect (TODO) 56 | 57 | - 支持动态Options 58 | -------------------------------------------------------------------------------- /examples/react-demo/src/components/Form/constant.ts: -------------------------------------------------------------------------------- 1 | // 支持的组件类型 2 | export enum IFormComTypeEnum { 3 | Input = 'Input', 4 | InputNumber = 'InputNumber', 5 | Select = 'Select', 6 | DatePicker = 'DatePicker', // 日期选择器 7 | TimePicker = 'TimePicker', // 时间选择框 8 | RadioGroup = 'RadioGroup', // 单选框 9 | TreeSelect = 'TreeSelect', 10 | Cascader = 'Cascader', 11 | Switch = 'Switch', 12 | CheckboxGroup = 'CheckboxGroup', 13 | Slider = 'Slider', 14 | Rate = 'Rate', 15 | Checkbox = 'Checkbox', 16 | } 17 | -------------------------------------------------------------------------------- /examples/react-demo/src/components/Form/form-components/f-checkbox/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Checkbox } from 'antd'; 3 | 4 | interface IFCheckboxProps { 5 | onChange: (value: boolean) => void; 6 | value: boolean; 7 | [key: string]: any; 8 | } 9 | 10 | export default function FCheckbox(props: IFCheckboxProps) { 11 | const { value, onChange, ...rest } = props; 12 | 13 | const onCheckboxChange = (e: { target: { checked: boolean } }) => { 14 | onChange(e.target.checked); 15 | }; 16 | 17 | return ; 18 | } 19 | -------------------------------------------------------------------------------- /examples/react-demo/src/components/Form/form-components/f-switch/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Switch } from 'antd'; 3 | 4 | interface IFSwitchProps { 5 | onChange: (value: boolean) => void; 6 | value: boolean; 7 | [key: string]: any; 8 | } 9 | 10 | export default function FSwitch(props: IFSwitchProps) { 11 | const { value, onChange, ...rest } = props; 12 | 13 | const onSwitchChange = (e: boolean) => { 14 | onChange(e); 15 | }; 16 | 17 | return ; 18 | } 19 | -------------------------------------------------------------------------------- /examples/react-demo/src/components/Form/formUtils.ts: -------------------------------------------------------------------------------- 1 | import { IFormComTypeEnum } from './constant'; 2 | import Mapping from './mappting'; 3 | 4 | export function findComByName(name: IFormComTypeEnum): any { 5 | if (Mapping[name]) { 6 | return Mapping[name]; 7 | } 8 | console.error(`未注册${name}组件`); 9 | return null; 10 | } 11 | -------------------------------------------------------------------------------- /examples/react-demo/src/components/Form/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Form } from 'antd'; 3 | import { IFormProps } from './FormTypes'; 4 | import { findComByName } from './formUtils'; 5 | import { ICrudColumn } from '../CrudTypes'; 6 | 7 | const { Item } = Form; 8 | 9 | const formatColumn = (column: ICrudColumn) => { 10 | const item = { ...column }; 11 | delete item.dataIndex; 12 | delete item.itemProps; 13 | delete item.fieldProps; 14 | return item; 15 | }; 16 | export interface IFFormProps extends IFormProps { 17 | schema: ICrudColumn[]; 18 | children?: React.ReactNode; 19 | } 20 | 21 | const FForm: React.FC = (props: IFFormProps) => { 22 | const { schema, children } = props; 23 | return ( 24 |
25 | {schema?.map(item => { 26 | const { type, fieldProps, itemProps } = item; 27 | const FComponent = findComByName(type); 28 | 29 | if (!FComponent) return null; 30 | 31 | const temp = formatColumn(item); 32 | return ( 33 | 40 | {/* TODO 完善更多表单 */} 41 | 42 | 43 | ); 44 | })} 45 | {children} 46 |
47 | ); 48 | }; 49 | 50 | export default FForm; 51 | -------------------------------------------------------------------------------- /examples/react-demo/src/components/Form/mappting.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Input, 3 | InputNumber, 4 | Select, 5 | DatePicker, 6 | TimePicker, 7 | Radio, 8 | TreeSelect, 9 | Cascader, 10 | Checkbox, 11 | Slider, 12 | Rate, 13 | } from 'antd'; 14 | import { IFormComTypeEnum } from './constant'; 15 | import FCheckbox from './form-components/f-checkbox'; 16 | import FSwitch from './form-components/f-switch'; 17 | 18 | const Mapping = { 19 | [IFormComTypeEnum.Input]: Input, 20 | [IFormComTypeEnum.InputNumber]: InputNumber, 21 | [IFormComTypeEnum.Select]: Select, 22 | [IFormComTypeEnum.DatePicker]: DatePicker, // 日期选择器 23 | [IFormComTypeEnum.TimePicker]: TimePicker, // 时间选择框 24 | [IFormComTypeEnum.RadioGroup]: Radio.Group, // 单选 25 | [IFormComTypeEnum.TreeSelect]: TreeSelect, 26 | [IFormComTypeEnum.Cascader]: Cascader, 27 | [IFormComTypeEnum.Switch]: FSwitch, 28 | [IFormComTypeEnum.CheckboxGroup]: Checkbox.Group, 29 | [IFormComTypeEnum.Slider]: Slider, 30 | [IFormComTypeEnum.Rate]: Rate, 31 | [IFormComTypeEnum.Checkbox]: FCheckbox, 32 | }; 33 | 34 | export default Mapping; 35 | -------------------------------------------------------------------------------- /examples/react-demo/src/components/Table/hooks/useActionType.ts: -------------------------------------------------------------------------------- 1 | import { ActionType, UseFetchActions } from '../TableTypes'; 2 | /** 3 | * 获取用户的 action 信息 4 | * 5 | * @param actionRef 6 | * @param counter 7 | * @param onCleanSelected 8 | */ 9 | export function useActionType( 10 | ref: React.MutableRefObject, 11 | action: UseFetchActions, 12 | props: { 13 | onCleanSelected: () => void; 14 | }, 15 | ) { 16 | const userAction: ActionType = { 17 | reload: async (resetRowSelected?: boolean) => { 18 | // 如果为 true,清空选择状态 19 | if (resetRowSelected) { 20 | await props.onCleanSelected(); 21 | } 22 | action?.reload(); 23 | }, 24 | /** 刷新并且重置 */ 25 | reloadAndRest: async () => { 26 | props.onCleanSelected(); 27 | await action.setPageInfo({ 28 | current: 1, 29 | }); 30 | }, 31 | }; 32 | ref.current = userAction; 33 | } 34 | -------------------------------------------------------------------------------- /examples/react-demo/src/components/Table/hooks/usePrevious.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useRef } from 'react'; 2 | 3 | const usePrevious = (state: T): T | undefined => { 4 | const ref = useRef(); 5 | 6 | useEffect(() => { 7 | ref.current = state; 8 | }); 9 | 10 | return ref.current; 11 | }; 12 | 13 | export default usePrevious; 14 | -------------------------------------------------------------------------------- /examples/react-demo/src/components/Table/index.tsx: -------------------------------------------------------------------------------- 1 | import CrudTable from './Table'; 2 | 3 | export default CrudTable; 4 | -------------------------------------------------------------------------------- /examples/react-demo/src/components/Table/table.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lgwebdream/fe-code/4a49a289e953bfd99f37875c966dd53590eb1401/examples/react-demo/src/components/Table/table.md -------------------------------------------------------------------------------- /examples/react-demo/src/components/Table/utils/index.ts: -------------------------------------------------------------------------------- 1 | import type { ColumnType, SortOrder } from 'antd/lib/table/interface'; 2 | 3 | export { default as useFetchData } from './useFetchData'; 4 | 5 | function parseDataIndex( 6 | dataIndex: ColumnType<'string'>['dataIndex'], 7 | ): string | undefined { 8 | if (Array.isArray(dataIndex)) { 9 | return dataIndex.join(','); 10 | } 11 | return dataIndex?.toString(); 12 | } 13 | 14 | export function parseDefaultColumnConfig(columns: ColumnType[]) { 15 | const filter: Record = {}; 16 | const sort: Record = {}; 17 | columns.forEach(column => { 18 | // 转换 dataIndex 19 | const dataIndex = parseDataIndex(column.dataIndex); 20 | if (!dataIndex) { 21 | return; 22 | } 23 | // 当 column 启用 filters 功能时,取出默认的筛选值 24 | if (column.filters) { 25 | const defaultFilteredValue = 26 | column.defaultFilteredValue as React.ReactText[]; 27 | if (defaultFilteredValue === undefined) { 28 | filter[dataIndex] = null; 29 | } else { 30 | filter[dataIndex] = column.defaultFilteredValue as React.ReactText[]; 31 | } 32 | } 33 | // 当 column 启用 sorter 功能时,取出默认的排序值 34 | if (column.sorter && column.defaultSortOrder) { 35 | sort[dataIndex] = column.defaultSortOrder!; 36 | } 37 | }); 38 | return { sort, filter }; 39 | } 40 | -------------------------------------------------------------------------------- /examples/react-demo/src/components/ToolBar/BatchOperation.tsx: -------------------------------------------------------------------------------- 1 | import React, { useMemo } from 'react'; 2 | import { Button } from 'antd'; 3 | import type { ICrudToolbar } from '../CrudTypes'; 4 | 5 | const { Group } = Button; 6 | 7 | export interface BatchOperationProps { 8 | selectedRowKeys?: (string | number)[]; 9 | selectedRows?: T[]; 10 | options?: ICrudToolbar[]; 11 | } 12 | 13 | export function BatchButtonGroup({ 14 | options, 15 | args, 16 | }: { 17 | options: ICrudToolbar[]; 18 | args: { row: T[] | T; rowKey?: (string | number)[] | number }; 19 | }) { 20 | return ( 21 | 22 | {options?.map((it, idx) => { 23 | const { 24 | key, 25 | type, 26 | label, 27 | children, 28 | icon, 29 | danger, 30 | disabled, 31 | style, 32 | className, 33 | onClick, 34 | request, 35 | } = it; 36 | return it.render ? ( 37 | 38 | {it.render(args.row, args.rowKey)} 39 | 40 | ) : ( 41 | 55 | ); 56 | })} 57 | 58 | ); 59 | } 60 | 61 | const BatchOperation = (props: BatchOperationProps) => { 62 | const { selectedRows, selectedRowKeys, options } = props; 63 | 64 | const Buttons = useMemo(() => { 65 | return ( 66 | 70 | ); 71 | }, [options, selectedRows, selectedRowKeys]); 72 | 73 | return
{Buttons}
; 74 | }; 75 | 76 | export default BatchOperation; 77 | -------------------------------------------------------------------------------- /examples/react-demo/src/components/ToolBar/FilterSearch.tsx: -------------------------------------------------------------------------------- 1 | import classnames from 'classnames'; 2 | import { Form, Button } from 'antd'; 3 | import React, { useMemo } from 'react'; 4 | import { getClassName } from './utils'; 5 | import type { SearchOptions } from './ToolbarTypes'; 6 | import FForm from '../Form/index'; 7 | 8 | const { useForm } = Form; 9 | 10 | const FilterSearch = (props: SearchOptions) => { 11 | const [formInstance] = useForm(); 12 | const { columns, style, className, prefixCls, render, onSearch, onReset } = 13 | props; 14 | 15 | const nextClassName = classnames( 16 | getClassName('filter-search', prefixCls), 17 | className, 18 | ); 19 | 20 | const nextStyle = useMemo( 21 | () => ({ 22 | marginBottom: 10, 23 | ...(style || {}), 24 | }), 25 | [], 26 | ); 27 | 28 | const onResetClick = () => { 29 | formInstance.resetFields(); 30 | onReset?.(); 31 | }; 32 | 33 | const onSearchClick = async () => { 34 | const flag = await formInstance.validateFields(); 35 | flag && onSearch?.(formInstance.getFieldsValue()); 36 | }; 37 | 38 | return ( 39 |
40 | {render ? ( 41 | render() 42 | ) : ( 43 | <> 44 | {columns.length ? ( 45 | 46 | 47 | 48 | 51 | 52 | 53 | ) : null} 54 | 55 | )} 56 |
57 | ); 58 | }; 59 | 60 | export default FilterSearch; 61 | -------------------------------------------------------------------------------- /examples/react-demo/src/components/ToolBar/ToolbarTypes.ts: -------------------------------------------------------------------------------- 1 | import React, { CSSProperties, ReactElement } from 'react'; 2 | import type { LabelTooltipType } from 'antd/lib/form/FormItemLabel'; 3 | import { ICrudColumn, ICrudToolbar } from '../CrudTypes'; 4 | 5 | export interface ToolBarOptions { 6 | headerTitle?: React.ReactNode; 7 | tooltip?: string | LabelTooltipType; 8 | tip?: string; 9 | className?: string; 10 | style?: CSSProperties; 11 | prefixCls?: string; 12 | render?: (rows: { 13 | selectedRowKeys?: (string | number)[]; 14 | selectedRows?: T[]; 15 | }) => ReactElement; 16 | } 17 | 18 | export interface SearchOptions { 19 | prefixCls?: string; 20 | className?: string; 21 | style?: CSSProperties; 22 | columns?: ICrudColumn[]; 23 | onSearch?: (values: T) => void; 24 | onReset?: () => void; 25 | render?: () => ReactElement; 26 | } 27 | 28 | export interface ToolBarProps { 29 | selectedRowKeys?: (string | number)[]; 30 | selectedRows?: T[]; 31 | toolbarOptions?: ToolBarOptions; 32 | batchOptions?: ICrudToolbar[]; 33 | searchOptions?: SearchOptions; 34 | } 35 | -------------------------------------------------------------------------------- /examples/react-demo/src/components/ToolBar/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useMemo } from 'react'; 2 | import classnames from 'classnames'; 3 | import BatchOperation from './BatchOperation'; 4 | import FilberSearch from './FilterSearch'; 5 | import { getClassName } from './utils'; 6 | import { ToolBarProps } from './ToolbarTypes'; 7 | 8 | const ToolBar = (props: ToolBarProps) => { 9 | const { 10 | selectedRowKeys, 11 | selectedRows, 12 | searchOptions, 13 | toolbarOptions, 14 | batchOptions, 15 | } = props; 16 | 17 | const { prefixCls, style, className, render } = toolbarOptions || {}; 18 | 19 | const nextClassName = classnames( 20 | getClassName('toolbar', prefixCls), 21 | className, 22 | ); 23 | 24 | const nextStyle = useMemo( 25 | () => ({ 26 | padding: 10, 27 | ...style, 28 | }), 29 | [style], 30 | ); 31 | 32 | const dynamicRender = render?.({ selectedRowKeys, selectedRows }); 33 | 34 | return ( 35 |
36 | {dynamicRender || ( 37 | <> 38 | 39 | 44 | 45 | )} 46 |
47 | ); 48 | }; 49 | 50 | ToolBar.BatchOperation = BatchOperation; 51 | ToolBar.FilberSearch = FilberSearch; 52 | 53 | export default ToolBar; 54 | -------------------------------------------------------------------------------- /examples/react-demo/src/components/ToolBar/utils.ts: -------------------------------------------------------------------------------- 1 | function getClassName(suffixCls: string, prefixCls?: string) { 2 | return prefixCls ? `${prefixCls}-${suffixCls}` : suffixCls; 3 | } 4 | 5 | export { getClassName }; 6 | -------------------------------------------------------------------------------- /examples/react-demo/src/components/index.ts: -------------------------------------------------------------------------------- 1 | export * from './Form/FormTypes'; 2 | export * from './ToolBar/ToolbarTypes'; 3 | export * from './Table/TableTypes'; 4 | export * from './CrudTypes'; 5 | 6 | export { default as FCrud } from './Crud'; 7 | -------------------------------------------------------------------------------- /examples/react-demo/src/components/service.ts: -------------------------------------------------------------------------------- 1 | export interface FetcherResult { 2 | data?: { 3 | data: T[] | undefined; 4 | }; 5 | code: number; 6 | msg: string; 7 | msgTimeout?: number; 8 | errors?: { 9 | [propName: string]: string; 10 | }; 11 | [propName: string]: any; // 为了兼容其他返回格式 12 | } 13 | 14 | export interface FetchOptions { 15 | method?: 'get' | 'post' | 'put' | 'patch' | 'delete'; 16 | successMessage?: string; 17 | errorMessage?: string; 18 | autoAppend?: boolean; 19 | beforeSend?: (data: any) => any; 20 | onSuccess?: (json: Payload) => any; 21 | onFailed?: (json: Payload) => any; 22 | silent?: boolean; 23 | [propName: string]: any; 24 | } 25 | 26 | export interface Payload { 27 | ok: boolean; 28 | msg: string; 29 | msgTimeout?: number; 30 | data: any; 31 | status: number; 32 | errors?: { 33 | [propName: string]: string; 34 | }; 35 | } 36 | -------------------------------------------------------------------------------- /examples/react-demo/src/pages/Table.tsx: -------------------------------------------------------------------------------- 1 | import { FCrud } from '@crud/components/index'; 2 | import request from 'umi-request'; 3 | 4 | declare namespace API { 5 | // 后端接口 6 | type ListItem = {}; 7 | 8 | // 页面请求参数 9 | type PageParams = {}; 10 | } 11 | 12 | const columns = [ 13 | { 14 | title: '姓名', 15 | dataIndex: 'name', 16 | key: 'name', 17 | }, 18 | { 19 | title: '年龄', 20 | dataIndex: 'age', 21 | key: 'age', 22 | }, 23 | { 24 | title: '住址', 25 | dataIndex: 'address', 26 | key: 'address', 27 | }, 28 | { 29 | title: '描述', 30 | dataIndex: 'description', 31 | key: 'description', 32 | }, 33 | ]; 34 | 35 | export default function IndexPage() { 36 | return ( 37 | 38 | request={async (params = {}) => { 39 | return request<{}>('/api/json/list', { 40 | method: 'post', 41 | data: params, 42 | }); 43 | }} 44 | columns={columns} 45 | /> 46 | ); 47 | } 48 | -------------------------------------------------------------------------------- /examples/react-demo/src/pages/toolbar.tsx: -------------------------------------------------------------------------------- 1 | import { Button } from 'antd'; 2 | import { FCrud, ICrudToolbar, IFormComTypeEnum } from '@crud/components/index'; 3 | 4 | const toolbar: ICrudToolbar[] = [ 5 | { 6 | label: '添加', 7 | type: 'primary', 8 | }, 9 | { 10 | label: '导出', 11 | type: 'ghost', 12 | }, 13 | { 14 | label: '删除', 15 | type: 'dashed', 16 | }, 17 | { 18 | render: (row) => { 19 | console.log(row); 20 | return ; 21 | }, 22 | }, 23 | ]; 24 | 25 | const columns = [ 26 | { title: 'ID', dataIndex: 'id', readonly: true }, 27 | { 28 | title: '姓名', 29 | dataIndex: 'name', 30 | type: IFormComTypeEnum.Input, 31 | rules: [{ message: '姓名不能为空', required: true }], 32 | }, 33 | { 34 | title: '年龄', 35 | dataIndex: 'age', 36 | type: IFormComTypeEnum.InputNumber, 37 | }, 38 | { title: '地址', dataIndex: 'address', type: IFormComTypeEnum.Input }, 39 | { 40 | title: '职位', 41 | dataIndex: 'title', 42 | type: IFormComTypeEnum.Select, 43 | rules: [{ message: '职位不能为空', required: true }], 44 | options: [ 45 | { label: 'CTO', value: 'cto' }, 46 | { label: 'COO', value: 'coo' }, 47 | { label: 'CFO', value: 'cfo' }, 48 | ], 49 | }, 50 | ]; 51 | 52 | export default function ToolBarPage() { 53 | return ( 54 | console.log('onReset'), 59 | onSearch: () => console.log('onSearch'), 60 | }} 61 | /> 62 | ); 63 | } 64 | -------------------------------------------------------------------------------- /examples/react-demo/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "module": "esnext", 5 | "moduleResolution": "node", 6 | "resolveJsonModule": true, 7 | "importHelpers": true, 8 | "jsx": "react-jsx", 9 | "esModuleInterop": true, 10 | "sourceMap": true, 11 | "baseUrl": "./", 12 | "strict": true, 13 | "paths": { 14 | "@/*": ["src/*"], 15 | "@@/*": ["src/.umi/*"], 16 | "@crud/*": ["../../lib/react/*"], 17 | }, 18 | "allowSyntheticDefaultImports": true 19 | }, 20 | "include": [ 21 | "mock/**/*", 22 | "src/**/*", 23 | "config/**/*", 24 | ".umirc.ts", 25 | "typings.d.ts", 26 | "../../lib/react/*.tsx" 27 | ], 28 | "exclude": [ 29 | "node_modules", 30 | "es", 31 | "dist", 32 | "typings", 33 | "**/__test__", 34 | "test", 35 | "docs", 36 | "tests" 37 | ] 38 | } 39 | -------------------------------------------------------------------------------- /examples/react-demo/typings.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.css'; 2 | declare module '*.less'; 3 | declare module '*.png'; 4 | declare module '*.svg' { 5 | export function ReactComponent( 6 | props: React.SVGProps, 7 | ): React.ReactElement; 8 | const url: string; 9 | export default url; 10 | } 11 | -------------------------------------------------------------------------------- /examples/vue2-demo/.browserslistrc: -------------------------------------------------------------------------------- 1 | > 1% 2 | last 2 versions 3 | not dead 4 | -------------------------------------------------------------------------------- /examples/vue2-demo/.eslintignore: -------------------------------------------------------------------------------- 1 | /lambda/ 2 | /scripts 3 | /config 4 | /public 5 | .history 6 | node_modules 7 | ./components/ 8 | -------------------------------------------------------------------------------- /examples/vue2-demo/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { 4 | node: true, 5 | }, 6 | extends: [ 7 | 'plugin:vue/essential', 8 | 'eslint:recommended', 9 | // '@vue/typescript/recommended', 10 | // '@vue/prettier/@typescript-eslint', 11 | ], 12 | parserOptions: { 13 | ecmaVersion: 2020, 14 | parser: '@typescript-eslint/parser', 15 | }, 16 | rules: { 17 | 'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off', 18 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off', 19 | 'no-unused-vars': 'off', 20 | '@typescript-eslint/no-unused-vars': 'off', 21 | '@typescript-eslint/no-explicit-any': 'off', 22 | 'prefer-const': 'off', 23 | 'vue/no-multiple-template-root': 'off', 24 | quotes: [1, 'single'], 25 | }, 26 | }; 27 | -------------------------------------------------------------------------------- /examples/vue2-demo/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | 5 | 6 | # local env files 7 | .env.local 8 | .env.*.local 9 | 10 | # Log files 11 | npm-debug.log* 12 | yarn-debug.log* 13 | yarn-error.log* 14 | pnpm-debug.log* 15 | 16 | # Editor directories and files 17 | .idea 18 | .vscode 19 | *.suo 20 | *.ntvs* 21 | *.njsproj 22 | *.sln 23 | *.sw? 24 | -------------------------------------------------------------------------------- /examples/vue2-demo/README.md: -------------------------------------------------------------------------------- 1 | # vue2-demo 2 | 3 | ## Project setup 4 | ``` 5 | yarn install 6 | ``` 7 | 8 | ### Compiles and hot-reloads for development 9 | ``` 10 | yarn serve 11 | ``` 12 | 13 | ### Compiles and minifies for production 14 | ``` 15 | yarn build 16 | ``` 17 | 18 | ### Lints and fixes files 19 | ``` 20 | yarn lint 21 | ``` 22 | 23 | ### Customize configuration 24 | See [Configuration Reference](https://cli.vuejs.org/config/). 25 | -------------------------------------------------------------------------------- /examples/vue2-demo/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@vue/cli-plugin-babel/preset' 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /examples/vue2-demo/mock/index.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const fs = require('fs'); 3 | const getPathInfo = p => path.parse(p); 4 | 5 | function autoLoadFile(directory, useSubdirectories = false, extList = ['.js']) { 6 | const filesList = []; 7 | // 递归读取文件 8 | function readFileList(directory, useSubdirectories, extList) { 9 | const files = fs.readdirSync(directory); 10 | files.forEach(item => { 11 | const fullPath = path.join(directory, item); 12 | const stat = fs.statSync(fullPath); 13 | if (stat.isDirectory() && useSubdirectories) { 14 | readFileList(path.join(directory, item), useSubdirectories, extList); 15 | } else { 16 | const info = getPathInfo(fullPath); 17 | extList.includes(info.ext) && filesList.push(fullPath); 18 | } 19 | }); 20 | } 21 | readFileList(directory, useSubdirectories, extList); 22 | // 生成需要的对象 23 | const res = filesList.map(item => ({ 24 | path: item, 25 | data: require(item), 26 | ...getPathInfo(item), 27 | })); 28 | 29 | return res; 30 | } 31 | 32 | const UseMock = app => { 33 | const modulesFiles = autoLoadFile(path.join(__dirname, './api'), true); 34 | modulesFiles.forEach(item => item.data(app)); 35 | }; 36 | 37 | module.exports = app => { 38 | UseMock(app); 39 | }; 40 | -------------------------------------------------------------------------------- /examples/vue2-demo/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue2-demo", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "start": "npm run link:npm && npm run serve", 7 | "serve": "vue-cli-service serve", 8 | "build": "vue-cli-service build", 9 | "lint": "vue-cli-service lint" 10 | }, 11 | "dependencies": { 12 | "core-js": "^3.6.5", 13 | "vue": "^2.6.11", 14 | "vue-router": "^3.2.0", 15 | "element-ui": "^2.15.3" 16 | }, 17 | "devDependencies": { 18 | "@fe-code/vue2": "file:../../lib/vue2", 19 | "@typescript-eslint/eslint-plugin": "^4.18.0", 20 | "@typescript-eslint/parser": "^4.18.0", 21 | "@vue/cli-plugin-babel": "~4.5.0", 22 | "@vue/cli-plugin-eslint": "~4.5.0", 23 | "@vue/cli-plugin-router": "~4.5.0", 24 | "@vue/cli-plugin-typescript": "~4.5.0", 25 | "@vue/cli-service": "~4.5.0", 26 | "@vue/eslint-config-typescript": "^7.0.0", 27 | "element-ui": "^2.15.3", 28 | "eslint": "^6.7.2", 29 | "eslint-plugin-vue": "^6.2.2", 30 | "node-sass": "5", 31 | "sass-loader": "10.1.1", 32 | "typescript": "~4.1.5", 33 | "vue-template-compiler": "^2.6.11" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /examples/vue2-demo/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lgwebdream/fe-code/4a49a289e953bfd99f37875c966dd53590eb1401/examples/vue2-demo/public/favicon.ico -------------------------------------------------------------------------------- /examples/vue2-demo/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | <%= htmlWebpackPlugin.options.title %> 9 | 10 | 11 | 14 |
15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /examples/vue2-demo/src/App.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 29 | -------------------------------------------------------------------------------- /examples/vue2-demo/src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lgwebdream/fe-code/4a49a289e953bfd99f37875c966dd53590eb1401/examples/vue2-demo/src/assets/logo.png -------------------------------------------------------------------------------- /examples/vue2-demo/src/components/HelloWorld.vue: -------------------------------------------------------------------------------- 1 | 34 | 35 | 45 | 46 | 47 | 63 | -------------------------------------------------------------------------------- /examples/vue2-demo/src/main.ts: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import App from './App.vue' 3 | import router from './router' 4 | 5 | import xui from '@fe-code/vue2'; // 导入组件库 6 | 7 | Vue.use(xui) 8 | 9 | 10 | 11 | Vue.config.productionTip = false 12 | 13 | new Vue({ 14 | router, 15 | render: h => h(App) 16 | }).$mount('#app') 17 | -------------------------------------------------------------------------------- /examples/vue2-demo/src/router/index.ts: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import VueRouter, { RouteConfig } from 'vue-router' 3 | import Home from '../views/Home.vue' 4 | 5 | Vue.use(VueRouter) 6 | 7 | const routes: Array = [ 8 | { 9 | path: '/', 10 | name: 'Home', 11 | component: Home 12 | }, 13 | { 14 | path: '/table', 15 | name: 'Table', 16 | component: () => import('@/views/TableDemo.vue'), 17 | }, 18 | ] 19 | 20 | const router = new VueRouter({ 21 | mode: 'history', 22 | base: process.env.BASE_URL, 23 | routes 24 | }) 25 | 26 | export default router 27 | -------------------------------------------------------------------------------- /examples/vue2-demo/src/shims-tsx.d.ts: -------------------------------------------------------------------------------- 1 | import Vue, { VNode } from 'vue' 2 | 3 | declare global { 4 | namespace JSX { 5 | // tslint:disable no-empty-interface 6 | interface Element extends VNode {} 7 | // tslint:disable no-empty-interface 8 | interface ElementClass extends Vue {} 9 | interface IntrinsicElements { 10 | [elem: string]: any 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /examples/vue2-demo/src/shims-vue.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.vue' { 2 | import Vue from 'vue' 3 | export default Vue 4 | } 5 | -------------------------------------------------------------------------------- /examples/vue2-demo/src/views/About.vue: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /examples/vue2-demo/src/views/Home.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 19 | -------------------------------------------------------------------------------- /examples/vue2-demo/src/views/TableDemo.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 18 | 19 | 20 | 22 | -------------------------------------------------------------------------------- /examples/vue2-demo/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "module": "esnext", 5 | "strict": false, 6 | "jsx": "preserve", 7 | "importHelpers": true, 8 | "moduleResolution": "node", 9 | "experimentalDecorators": true, 10 | "esModuleInterop": true, 11 | "allowSyntheticDefaultImports": true, 12 | "sourceMap": true, 13 | "baseUrl": ".", 14 | "types": ["webpack-env"], 15 | "paths": { 16 | "@/*": ["src/*"] 17 | }, 18 | "lib": ["esnext", "dom", "dom.iterable"] 19 | }, 20 | "include": [ 21 | "src/**/*.ts", 22 | "src/**/*.tsx", 23 | "src/**/*.vue", 24 | "../../lib/vue2/*.vue", 25 | "../../lib/vue2/*.ts", 26 | "../../lib/vue2/*.tsx" 27 | ], 28 | "exclude": ["node_modules"] 29 | } 30 | -------------------------------------------------------------------------------- /examples/vue2-demo/vue.config.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | const path = require('path'); 3 | let MockRouter = require('./mock/index'); 4 | 5 | module.exports = { 6 | pages: { 7 | index: { 8 | entry: 'src/main.ts', 9 | template: 'public/index.html', 10 | filename: 'index.html', 11 | }, 12 | }, 13 | // chainWebpack: config => {}, 14 | // transpileDependencies: ['@fe-code/vue'], 15 | devServer: { 16 | before(app) { 17 | MockRouter(app); 18 | }, 19 | }, 20 | // configureWebpack: { 21 | // resolve: { 22 | // symlinks: false, 23 | // alias: { 24 | // vue$: 'vue/dist/vue.esm-bundler.js', 25 | // vue: path.resolve(__dirname, `./node_modules/vue`), 26 | // }, 27 | // }, 28 | // }, 29 | // pluginOptions: { 30 | // 'style-resources-loader': { 31 | // preProcessor: 'scss', 32 | // patterns: [], 33 | // }, 34 | // }, 35 | }; 36 | -------------------------------------------------------------------------------- /examples/vue3-demo/.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | 15 | [Makefile] 16 | indent_style = tab 17 | -------------------------------------------------------------------------------- /examples/vue3-demo/.eslintignore: -------------------------------------------------------------------------------- 1 | /lambda/ 2 | /scripts 3 | /config 4 | /public 5 | .history 6 | node_modules 7 | ./components/ 8 | -------------------------------------------------------------------------------- /examples/vue3-demo/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { 4 | node: true, 5 | }, 6 | extends: [ 7 | 'plugin:vue/essential', 8 | 'eslint:recommended', 9 | // '@vue/typescript/recommended', 10 | // '@vue/prettier/@typescript-eslint', 11 | ], 12 | parserOptions: { 13 | ecmaVersion: 2020, 14 | parser: '@typescript-eslint/parser', 15 | }, 16 | rules: { 17 | 'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off', 18 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off', 19 | 'no-unused-vars': 'off', 20 | '@typescript-eslint/no-unused-vars': 'off', 21 | '@typescript-eslint/no-explicit-any': 'off', 22 | 'prefer-const': 'off', 23 | 'vue/no-multiple-template-root': 'off', 24 | quotes: [1, 'single'], 25 | }, 26 | }; 27 | -------------------------------------------------------------------------------- /examples/vue3-demo/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | 5 | 6 | # local env files 7 | .env.local 8 | .env.*.local 9 | 10 | # Log files 11 | npm-debug.log* 12 | yarn-debug.log* 13 | yarn-error.log* 14 | pnpm-debug.log* 15 | yarn.lock* 16 | # Editor directories and files 17 | .idea 18 | .vscode 19 | *.suo 20 | *.ntvs* 21 | *.njsproj 22 | *.sln 23 | *.sw? 24 | 25 | -------------------------------------------------------------------------------- /examples/vue3-demo/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "semi": true, 4 | "bracketSpacing": true, 5 | "trailingComma": "all", 6 | "printWidth": 100, 7 | "proseWrap": "never", 8 | "arrowParens": "avoid", 9 | "overrides": [ 10 | { 11 | "files": ".prettierrc", 12 | "options": { 13 | "parser": "json" 14 | } 15 | } 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /examples/vue3-demo/README.md: -------------------------------------------------------------------------------- 1 | # vue-demo使用 2 | 3 | ## 演示项目 4 | 5 | 1. 在lib/vue项目中安装依赖 6 | 7 | 2. 在examples/vue-demo中安装依赖并启动 8 | 9 | ```BASH 10 | yarn install 11 | yarn serve 12 | ``` 13 | 14 | ## 开发使用 15 | 16 | 1. 在lib/vue项目中安装依赖,并link 17 | 18 | ```BASH 19 | yarn install 20 | npm link 21 | ``` 22 | 23 | 2. 在vue-demo项目中 安装依赖并link组件 24 | 25 | ```BASH 26 | yarn install 27 | yarn start # 自动关联lib/vue中的组件 28 | ``` 29 | -------------------------------------------------------------------------------- /examples/vue3-demo/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: ['@vue/cli-plugin-babel/preset'], 3 | }; 4 | -------------------------------------------------------------------------------- /examples/vue3-demo/mock/index.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const fs = require('fs'); 3 | const getPathInfo = p => path.parse(p); 4 | 5 | function autoLoadFile(directory, useSubdirectories = false, extList = ['.js']) { 6 | const filesList = []; 7 | // 递归读取文件 8 | function readFileList(directory, useSubdirectories, extList) { 9 | const files = fs.readdirSync(directory); 10 | files.forEach(item => { 11 | const fullPath = path.join(directory, item); 12 | const stat = fs.statSync(fullPath); 13 | if (stat.isDirectory() && useSubdirectories) { 14 | readFileList(path.join(directory, item), useSubdirectories, extList); 15 | } else { 16 | const info = getPathInfo(fullPath); 17 | extList.includes(info.ext) && filesList.push(fullPath); 18 | } 19 | }); 20 | } 21 | readFileList(directory, useSubdirectories, extList); 22 | // 生成需要的对象 23 | const res = filesList.map(item => ({ 24 | path: item, 25 | data: require(item), 26 | ...getPathInfo(item), 27 | })); 28 | 29 | return res; 30 | } 31 | 32 | const UseMock = app => { 33 | const modulesFiles = autoLoadFile(path.join(__dirname, './api'), true); 34 | modulesFiles.forEach(item => item.data(app)); 35 | }; 36 | 37 | module.exports = app => { 38 | UseMock(app); 39 | }; 40 | -------------------------------------------------------------------------------- /examples/vue3-demo/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@fe-code/vue", 3 | "version": "0.1.0", 4 | "description": "", 5 | "license": "ISC", 6 | "scripts": { 7 | "start": "npm run link:npm && npm run serve", 8 | "serve": "vue-cli-service serve", 9 | "build": "vue-cli-service build", 10 | "link:npm": "npm link @fe-code/vue3", 11 | "lint": "vue-cli-service lint" 12 | }, 13 | "dependencies": { 14 | "@fe-code/vue3": "file:../../lib/vue3", 15 | "@vue/babel-helper-vue-jsx-merge-props": "^1.2.1", 16 | "@vue/babel-preset-jsx": "^1.2.4", 17 | "@vue/cli-plugin-typescript": "^5.0.0", 18 | "ant-design-vue": "^1.7.6", 19 | "core-js": "^3.6.5", 20 | "element-plus": "^1.0.2-beta.55", 21 | "vue": "3.1.5", 22 | "vue-router": "^4.0.10" 23 | }, 24 | "devDependencies": { 25 | "@fe-code/vue3": "file:../../lib/vue3", 26 | "@typescript-eslint/eslint-plugin": "^4.28.4", 27 | "@typescript-eslint/parser": "^4.28.4", 28 | "@vue/cli-plugin-babel": "~4.5.0", 29 | "@vue/cli-plugin-eslint": "~4.5.0", 30 | "@vue/cli-service": "~4.5.0", 31 | "@vue/compiler-sfc": "^3.0.0", 32 | "@vue/eslint-config-prettier": "^6.0.0", 33 | "@vue/eslint-config-typescript": "^7.0.0", 34 | "axios": "^0.21.1", 35 | "babel-plugin-import": "^1.13.3", 36 | "element-ui": "^2.15.5", 37 | "eslint": "^7.31.0", 38 | "eslint-plugin-prettier": "^3.4.0", 39 | "eslint-plugin-vue": "^7.14.0", 40 | "node-sass": "5", 41 | "sass-loader": "10.1.1", 42 | "style-resources-loader": "^1.4.1", 43 | "typescript": "^4.3.5" 44 | }, 45 | "eslintConfig": { 46 | "root": true, 47 | "env": { 48 | "node": true 49 | }, 50 | "extends": [ 51 | "plugin:vue/vue3-essential", 52 | "eslint:recommended" 53 | ], 54 | "parserOptions": { 55 | "parser": "babel-eslint" 56 | }, 57 | "rules": {} 58 | }, 59 | "browserslist": [ 60 | "> 1%", 61 | "last 2 versions", 62 | "not dead" 63 | ] 64 | } 65 | -------------------------------------------------------------------------------- /examples/vue3-demo/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lgwebdream/fe-code/4a49a289e953bfd99f37875c966dd53590eb1401/examples/vue3-demo/public/favicon.ico -------------------------------------------------------------------------------- /examples/vue3-demo/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | <%= htmlWebpackPlugin.options.title %> 9 | 10 | 11 | 14 |
15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /examples/vue3-demo/src/App.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 11 | 12 | 22 | -------------------------------------------------------------------------------- /examples/vue3-demo/src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lgwebdream/fe-code/4a49a289e953bfd99f37875c966dd53590eb1401/examples/vue3-demo/src/assets/logo.png -------------------------------------------------------------------------------- /examples/vue3-demo/src/components/Button/index.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 26 | 27 | 35 | -------------------------------------------------------------------------------- /examples/vue3-demo/src/components/CrudTypes.ts: -------------------------------------------------------------------------------- 1 | /** CRUD 主体配置定义 */ 2 | export interface ICrud

{ 3 | /** 业务标题,用作表单弹框、信息提示等场景展示 */ 4 | title?: string; 5 | 6 | /** toolbar 批量操作按钮 */ 7 | batchToolbar?: ICrudColumnToolbar[]; 8 | 9 | /** 列表数据请求 Promise */ 10 | request?: ICrudListRequest; 11 | 12 | /** 字段属性 */ 13 | columns: ICrudColumn[]; 14 | 15 | /** 搜索 */ 16 | searchConfigs: ISearch[]; 17 | } 18 | 19 | /** 列表数据请求 Promise */ 20 | export type ICrudListRequest = (params: P) => Promise; 21 | 22 | /** 字段定义 */ 23 | export interface ICrudColumn { 24 | /** 属性名称 */ 25 | title?: string; 26 | 27 | placeholder?: string; 28 | 29 | /** 属性字段名 */ 30 | dataIndex: string; 31 | 32 | /** 是否只读 */ 33 | readonly?: boolean; 34 | } 35 | 36 | export type ICrudColumnToolbar = { 37 | /** 内置绑定方法 */ 38 | toolbarType?: ICrudToolbarTypeEnum; 39 | } & ICrudToolbar; 40 | 41 | /** 内置功能类型 */ 42 | export enum ICrudToolbarTypeEnum { 43 | /** 添加 */ 44 | Add = 'add', 45 | /** 编辑 */ 46 | Edit = 'edit', 47 | /** 删除 */ 48 | Delete = 'delete', 49 | /** 批量删除 */ 50 | DeleteBatch = 'deleteBatch', 51 | } 52 | 53 | /** form item 类型 */ 54 | export enum ICurdFromItemTypeEnum { 55 | Input = 'input', 56 | Select = 'select', 57 | Picker = 'picker', 58 | } 59 | 60 | /** 操作按钮定义 */ 61 | export type ICrudToolbar = { 62 | className?: string; 63 | key?: string; 64 | 65 | /** 按钮文本展示 */ 66 | label?: string; 67 | type?: string; 68 | 69 | plain?: boolean; 70 | 71 | /** 请求 Promise */ 72 | request?: (row: T[] | T, rowKey?: (string | number)[] | number) => Promise; 73 | 74 | /** 覆盖渲染,优先级最高,覆盖 ToolbarType 内部定义方法 */ 75 | render?: (row?: T | T[], index?: (string | number)[] | number) => any; 76 | }; 77 | 78 | export type ISearch = { 79 | type: string; 80 | label: string; 81 | value: string; 82 | prop: string; 83 | placeholder: string; 84 | dataType?: string; 85 | data?: Array; 86 | }; 87 | -------------------------------------------------------------------------------- /examples/vue3-demo/src/components/Table/Form/constant.ts: -------------------------------------------------------------------------------- 1 | // 支持的组件类型 2 | export enum IFormComTypeEnum { 3 | Input = 'Input', 4 | InputNumber = 'InputNumber', 5 | Select = 'Select', 6 | DatePicker = 'DatePicker', // 日期选择器 7 | TimePicker = 'TimePicker', // 时间选择框 8 | RadioGroup = 'RadioGroup', // 单选框 9 | TreeSelect = 'TreeSelect', 10 | Cascader = 'Cascader', 11 | Switch = 'Switch', 12 | CheckboxGroup = 'CheckboxGroup', 13 | Slider = 'Slider', 14 | Rate = 'Rate', 15 | Checkbox = 'Checkbox', 16 | } 17 | -------------------------------------------------------------------------------- /examples/vue3-demo/src/components/Table/ToolBar/filterSearch.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 57 | 58 | 63 | -------------------------------------------------------------------------------- /examples/vue3-demo/src/components/Table/config.ts: -------------------------------------------------------------------------------- 1 | import { ElMessage } from 'element-plus'; 2 | import { 3 | ICrud, 4 | ICurdFromItemTypeEnum, 5 | ICrudToolbarTypeEnum, 6 | } from '../CrudTypes'; 7 | 8 | import axios from 'axios'; 9 | 10 | 11 | const apiConfig = {'list':'/api/json/list','add':'/api/json/add','edit':'/api/json/edit','delete':'/api/json/delete','batchDelete':'/api/json/delete'} 12 | 13 | 14 | const TableProps: ICrud = { 15 | title:'标题', 16 | columns: [ 17 | { dataIndex: 'date', title: '日期', readonly: true }, 18 | { dataIndex: 'name', title: '姓名' }, 19 | { dataIndex: 'address', title: '地址' }, 20 | ], 21 | 22 | request: async params => { return axios.get(apiConfig.list, { params })}, 23 | 24 | batchToolbar: [{ 25 | label: '添加', 26 | type: 'primary', 27 | toolbarType: ICrudToolbarTypeEnum.Add, 28 | request: (row) => 29 | axios(apiConfig.add, { method: 'post', data: row }).then(() => { 30 | ElMessage.success('添加成功'); 31 | }), 32 | },{ 33 | label: '删除', 34 | type: 'link', 35 | toolbarType: ICrudToolbarTypeEnum.Delete, 36 | request: (row) => 37 | axios(apiConfig.delete, { method: 'post', data: row }).then(() => { 38 | ElMessage.success('删除成功'); 39 | }), 40 | },{ 41 | label: '批量删除', 42 | type: 'dashed', 43 | toolbarType: ICrudToolbarTypeEnum.DeleteBatch, 44 | request: (row) => 45 | axios(apiConfig.delete, { method: 'post', data: row }).then(() => { 46 | ElMessage.success('删除成功'); 47 | }), 48 | }], 49 | searchConfigs: [ 50 | { 51 | type: ICurdFromItemTypeEnum.Input, 52 | label: '审批人', 53 | value: '', 54 | prop: 'user', 55 | placeholder: '审批人', 56 | }, 57 | { 58 | type: ICurdFromItemTypeEnum.Select, 59 | label: '活动区域', 60 | value: '', 61 | prop: 'region', 62 | placeholder: '活动区域', 63 | data: [ 64 | { 65 | label: '上海', 66 | value: 'shanghai', 67 | }, 68 | { 69 | label: '北京', 70 | value: 'beijing', 71 | }, 72 | ], 73 | }, 74 | ], 75 | }; 76 | 77 | 78 | 79 | export default TableProps 80 | -------------------------------------------------------------------------------- /examples/vue3-demo/src/components/crud.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | -------------------------------------------------------------------------------- /examples/vue3-demo/src/components/index.ts: -------------------------------------------------------------------------------- 1 | import { ComponentOptions } from 'vue'; 2 | import Table from './Table/index.vue'; 3 | 4 | // 组件列表 5 | const components = [Table]; 6 | 7 | const install: any = function (Vue: ComponentOptions) { 8 | if (install.installed) return; 9 | components.map(component => Vue.component(component.name, component)); 10 | }; 11 | 12 | if (typeof window !== 'undefined' && window.Vue) { 13 | install(window.Vue); 14 | } 15 | 16 | export default { 17 | // 导出的对象必须具有 install,才能被 Vue.use() 方法安装 18 | install, 19 | Table, 20 | }; 21 | -------------------------------------------------------------------------------- /examples/vue3-demo/src/components/shims-vue.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.vue' { 2 | import { ComponentOptions } from 'vue'; 3 | const componentOptions: ComponentOptions; 4 | export default componentOptions; 5 | } 6 | 7 | declare interface Window { 8 | Vue: any; 9 | } 10 | 11 | declare module '*.css'; 12 | declare module '*.less'; 13 | declare module '*.png'; 14 | declare module '*.svg'; 15 | -------------------------------------------------------------------------------- /examples/vue3-demo/src/main.ts: -------------------------------------------------------------------------------- 1 | import { createApp } from 'vue'; 2 | import App from './App.vue'; 3 | // import xui from './components'; // 导入组件库 4 | import xui from '@fe-code/vue3'; // 导入组件库 5 | import ElementPlus from 'element-plus'; 6 | import { router } from './router'; 7 | import 'element-plus/lib/theme-chalk/index.css'; 8 | 9 | const app = createApp(App); 10 | app.use(router); 11 | app.use(ElementPlus); 12 | app.use(xui); 13 | 14 | app.mount('#app'); 15 | -------------------------------------------------------------------------------- /examples/vue3-demo/src/router.ts: -------------------------------------------------------------------------------- 1 | import { createRouter, createWebHistory } from 'vue-router'; 2 | 3 | export const router = createRouter({ 4 | history: createWebHistory(), 5 | routes: [ 6 | { 7 | path: '/', 8 | name: 'Home', 9 | component: () => import('@/views/TableDemo.vue'), 10 | }, 11 | { 12 | path: '/table', 13 | name: 'Table', 14 | component: () => import('@/views/TableDemo.vue'), 15 | }, 16 | ], 17 | }); 18 | -------------------------------------------------------------------------------- /examples/vue3-demo/src/shims-vue.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.vue' { 2 | import { ComponentOptions } from 'vue'; 3 | const componentOptions: ComponentOptions; 4 | export default componentOptions; 5 | } 6 | 7 | declare module 'vue/types/vue' { 8 | import VueRouter, { Route } from 'vue-router'; 9 | interface Vue { 10 | $router: VueRouter; // 这表示this下有这个东西 11 | $route: Route; 12 | $http: any; 13 | $Message: any; 14 | $Modal: any; 15 | } 16 | } 17 | declare interface Window { 18 | Vue: any; 19 | } 20 | 21 | declare module '*.css'; 22 | declare module '*.less'; 23 | declare module '*.png'; 24 | declare module '*.svg'; 25 | -------------------------------------------------------------------------------- /examples/vue3-demo/src/views/Index.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 16 | 17 | 19 | -------------------------------------------------------------------------------- /examples/vue3-demo/src/views/TableDemo.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 18 | 19 | 20 | 22 | -------------------------------------------------------------------------------- /examples/vue3-demo/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "module": "esnext", 5 | "strict": false, 6 | "jsx": "preserve", 7 | "importHelpers": true, 8 | "moduleResolution": "node", 9 | "experimentalDecorators": true, 10 | "esModuleInterop": true, 11 | "allowSyntheticDefaultImports": true, 12 | "sourceMap": true, 13 | "baseUrl": ".", 14 | "types": ["webpack-env"], 15 | "paths": { 16 | "@/*": ["src/*"] 17 | }, 18 | "lib": ["esnext", "dom", "dom.iterable"] 19 | }, 20 | "include": [ 21 | "src/**/*.ts", 22 | "src/**/*.tsx", 23 | "src/**/*.vue", 24 | "../../lib/vue3/*.vue", 25 | "../../lib/vue3/*.ts", 26 | "../../lib/vue3/*.tsx" 27 | ], 28 | "exclude": ["node_modules"] 29 | } 30 | -------------------------------------------------------------------------------- /examples/vue3-demo/vue.config.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | const path = require('path'); 3 | let MockRouter = require('./mock/index'); 4 | 5 | module.exports = { 6 | pages: { 7 | index: { 8 | entry: 'src/main.ts', 9 | template: 'public/index.html', 10 | filename: 'index.html', 11 | }, 12 | }, 13 | // chainWebpack: config => {}, 14 | transpileDependencies: ['@fe-code/vue'], 15 | devServer: { 16 | before(app) { 17 | MockRouter(app); 18 | }, 19 | }, 20 | configureWebpack: { 21 | resolve: { 22 | symlinks: false, 23 | alias: { 24 | vue$: 'vue/dist/vue.esm-bundler.js', 25 | vue: path.resolve(__dirname, `./node_modules/vue`), 26 | }, 27 | }, 28 | }, 29 | pluginOptions: { 30 | 'style-resources-loader': { 31 | preProcessor: 'scss', 32 | patterns: [], 33 | }, 34 | }, 35 | }; 36 | -------------------------------------------------------------------------------- /lib/api2code/generateCRUD/genService.js: -------------------------------------------------------------------------------- 1 | const camelCase = require('camelcase'); 2 | const registerTemplates = require('../template/registerTemplates'); 3 | const { extMap, languages } = require('../../utils/constants'); 4 | 5 | const genServiceCode = async (apis, language, modelsConfig) => { 6 | const services = []; 7 | const models = await Promise.all(modelsConfig); 8 | for (const serviceName in apis) { 9 | if (apis.hasOwnProperty(serviceName)) { 10 | const filename = `${camelCase(serviceName, { pascalCase: true })}Service`; 11 | const eachService = apis[serviceName]; 12 | services.push( 13 | new Promise((resolve, reject) => { 14 | registerTemplates 15 | .service({ 16 | serviceName: filename, 17 | service: eachService, 18 | isTs: language === languages.Typescript, 19 | modelConfig: models.find( 20 | model => model.serviceName === serviceName, 21 | ), 22 | }) 23 | .then(code => resolve({ filename, code, ext: extMap[language] })) 24 | .catch(err => reject(err)); 25 | }), 26 | ); 27 | } 28 | } 29 | return services; 30 | }; 31 | 32 | module.exports = genServiceCode; 33 | -------------------------------------------------------------------------------- /lib/api2code/generateCRUD/getApiSet.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const axios = require('axios'); 3 | const yaml = require('js-yaml'); 4 | const { exists, readFile } = require('../../utils/fileSystem'); 5 | const { parserMap } = require('../parser'); 6 | 7 | const readFromNetwork = url => 8 | new Promise((resolve, reject) => { 9 | axios 10 | .get(url) 11 | .then(res => resolve(res.data)) 12 | .catch(err => reject(new Error(err))); 13 | }); 14 | 15 | const readFromDisk = async input => { 16 | const filePath = path.resolve(process.cwd(), input); 17 | const fileExists = await exists(filePath); 18 | if (fileExists) { 19 | try { 20 | const content = await readFile(filePath, 'utf8'); 21 | return JSON.parse(content.toString()); 22 | } catch (e) { 23 | throw new Error(`Could not read file: "${filePath}"`); 24 | } 25 | } 26 | throw new Error(`Could not find file: "${filePath}"`); 27 | }; 28 | 29 | const readApiConfig = input => { 30 | return /https?:\/\//.test(input) 31 | ? readFromNetwork(input) 32 | : readFromDisk(input); 33 | }; 34 | 35 | const getApiSet = async (input, jsonType) => { 36 | const extension = path.extname(input).toLowerCase(); 37 | const isYaml = /.ya?ml$/.test(extension); 38 | const apiConfig = await readApiConfig(input); 39 | return parserMap[jsonType](isYaml ? yaml.load(apiConfig) : apiConfig); 40 | }; 41 | 42 | module.exports = getApiSet; 43 | -------------------------------------------------------------------------------- /lib/api2code/generateCRUD/index.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const getApiSet = require('./getApiSet'); 3 | // const validateApiSet = require('./validateApiSet'); 4 | const genServiceCode = require('./genService'); 5 | const genModelCode = require('./genModel'); 6 | const emitCode = require('./emitCode'); 7 | const { formatCode } = require('../../../utils'); 8 | const spinner = require('./spinner'); 9 | const { mkdir } = require('../../utils/fileSystem'); 10 | const { languages } = require('../../utils/constants'); 11 | 12 | const generateCrud = async options => { 13 | const { input, output, jsonType, language } = options; 14 | const isTs = languages.Typescript === language; 15 | spinner.getApiSet(); 16 | const apiSet = await getApiSet(input, jsonType); 17 | 18 | // await validateApiSet(apiSet); 19 | 20 | spinner.genCode(); 21 | const modelsConfig = await genModelCode(apiSet.apis, isTs); 22 | const servicesConfig = await genServiceCode( 23 | apiSet.apis, 24 | language, 25 | modelsConfig, 26 | ); 27 | 28 | spinner.emitCode(); 29 | const outputBaseDir = path.resolve(process.cwd(), output); 30 | await mkdir(outputBaseDir, { recursive: true }); 31 | await Promise.all([ 32 | emitCode(servicesConfig, outputBaseDir, 'services'), 33 | isTs && emitCode(modelsConfig, outputBaseDir, 'models'), 34 | ]); 35 | 36 | spinner.formatCode(); 37 | formatCode(outputBaseDir); 38 | 39 | spinner.success(); 40 | }; 41 | 42 | module.exports = generateCrud; 43 | -------------------------------------------------------------------------------- /lib/api2code/generateCrud/apiConfig.ts: -------------------------------------------------------------------------------- 1 | type Properties = Record | Record[] | T; 2 | 3 | interface ResponseParams { 4 | required: boolean; // 是否必传 5 | type: 'number' | 'string' | 'boolean' | 'array' | 'object' | 'enum'; // 数据类型 6 | description?: string; // 字段描述 7 | example?: unknown; // 示例数据 8 | properties?: Properties; // 当type为引用类型时有该值 9 | } 10 | 11 | interface RequestParams extends ResponseParams { 12 | in: 'path' | 'query' | 'body'; // 该参数的所属位置 path为动态路由上的参数 13 | properties?: Properties; // 当type为引用类型时有该值 14 | } 15 | 16 | // 每个接口中间类型格式 17 | interface Api { 18 | method: 'GET' | 'POST' | 'DELETE'; 19 | domain?: string; // 接口部署的域名, 例如"http://z100.com" 20 | path: string; // 请求路径,例如 "/order/v1/submit", 21 | description: string; // 功能描述,用于生成注释,例如"提交新的订单", 22 | requestParams: Record; // key对应的是字段名称 GET和POST共用,处理时区别对待,用于反解析请求参数的类型定义 23 | response: Record; // key对应的是字段名称 响应数据 24 | } 25 | 26 | // 接口信息对象 27 | export interface ApiConfig { 28 | apis: Record; // string会用于生成服务类的名称 29 | version: string; // api版本 30 | } 31 | -------------------------------------------------------------------------------- /lib/api2code/generateCrud/emitCode.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const { mkdir, writeFile } = require('../../utils/fileSystem'); 3 | 4 | const emitCode = async (codeConfig, output, emitType) => { 5 | const dir = path.resolve(output, emitType); 6 | await mkdir(dir, { recursive: true }); 7 | 8 | const emitStatus = []; 9 | for (const code of await Promise.all(codeConfig)) { 10 | const filePath = path.resolve(dir, `${code.filename}${code.ext || '.ts'}`); 11 | const awaitFilePath = new Promise((resolve, reject) => { 12 | writeFile(filePath, code.code) 13 | .then(() => resolve(filePath)) 14 | .catch(err => reject(err)); 15 | }); 16 | emitStatus.push(awaitFilePath); 17 | } 18 | await Promise.all(emitStatus); 19 | }; 20 | 21 | module.exports = emitCode; 22 | -------------------------------------------------------------------------------- /lib/api2code/generateCrud/genModel.js: -------------------------------------------------------------------------------- 1 | const camelCase = require('camelcase'); 2 | const registerTemplates = require('../template/registerTemplates'); 3 | 4 | // const refTypes = ['object', 'array', 'enum']; 5 | 6 | // const parseType = () => {}; 7 | 8 | const genModelCode = async (apis, isTs) => { 9 | const models = []; 10 | if (!isTs) return models; 11 | for (const serviceName in apis) { 12 | if (apis.hasOwnProperty(serviceName)) { 13 | const filename = `${camelCase(serviceName, { pascalCase: true })}`; 14 | const eachService = apis[serviceName]; 15 | 16 | const interfacesConfig = []; 17 | for (const eachApi of eachService) { 18 | const baseName = camelCase( 19 | eachApi.path.split('/').filter(i => i), 20 | { pascalCase: true }, 21 | ); 22 | 23 | const { path } = eachApi; 24 | 25 | if (eachApi.method === 'POST') { 26 | const interfaceName = `${baseName}Body`; 27 | interfacesConfig.push({ 28 | interfaceName, 29 | data: eachApi.requestParams, 30 | path, 31 | type: 'body', 32 | }); 33 | } 34 | 35 | interfacesConfig.push({ 36 | interfaceName: `${baseName}Response`, 37 | data: eachApi.response, 38 | path, 39 | type: 'response', 40 | }); 41 | } 42 | models.push( 43 | new Promise((resolve, reject) => { 44 | registerTemplates 45 | .model({ interfacesConfig }) 46 | .then(code => 47 | resolve({ 48 | code, 49 | filename, 50 | serviceName, 51 | interfacesConfig, 52 | }), 53 | ) 54 | .catch(err => reject(err)); 55 | }), 56 | ); 57 | } 58 | } 59 | return models; 60 | }; 61 | 62 | module.exports = genModelCode; 63 | -------------------------------------------------------------------------------- /lib/api2code/generateCrud/genService.js: -------------------------------------------------------------------------------- 1 | const camelCase = require('camelcase'); 2 | const registerTemplates = require('../template/registerTemplates'); 3 | const { extMap, languages } = require('../../utils/constants'); 4 | 5 | const genServiceCode = async (apis, language, modelsConfig) => { 6 | const services = []; 7 | const models = await Promise.all(modelsConfig); 8 | for (const serviceName in apis) { 9 | if (apis.hasOwnProperty(serviceName)) { 10 | const filename = `${camelCase(serviceName, { pascalCase: true })}Service`; 11 | const eachService = apis[serviceName]; 12 | services.push( 13 | new Promise((resolve, reject) => { 14 | registerTemplates 15 | .service({ 16 | serviceName: filename, 17 | service: eachService, 18 | isTs: language === languages.Typescript, 19 | modelConfig: models.find( 20 | model => model.serviceName === serviceName, 21 | ), 22 | }) 23 | .then(code => resolve({ filename, code, ext: extMap[language] })) 24 | .catch(err => reject(err)); 25 | }), 26 | ); 27 | } 28 | } 29 | return services; 30 | }; 31 | 32 | module.exports = genServiceCode; 33 | -------------------------------------------------------------------------------- /lib/api2code/generateCrud/getApiSet.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const axios = require('axios'); 3 | const yaml = require('js-yaml'); 4 | const { exists, readFile } = require('../../utils/fileSystem'); 5 | const { parserMap } = require('../parser'); 6 | 7 | const readFromNetwork = url => 8 | new Promise((resolve, reject) => { 9 | axios 10 | .get(url) 11 | .then(res => resolve(res.data)) 12 | .catch(err => reject(new Error(err))); 13 | }); 14 | 15 | const readFromDisk = async input => { 16 | const filePath = path.resolve(process.cwd(), input); 17 | const fileExists = await exists(filePath); 18 | if (fileExists) { 19 | try { 20 | const content = await readFile(filePath, 'utf8'); 21 | return JSON.parse(content.toString()); 22 | } catch (e) { 23 | throw new Error(`Could not read file: "${filePath}"`); 24 | } 25 | } 26 | throw new Error(`Could not find file: "${filePath}"`); 27 | }; 28 | 29 | const readApiConfig = input => { 30 | return /https?:\/\//.test(input) 31 | ? readFromNetwork(input) 32 | : readFromDisk(input); 33 | }; 34 | 35 | const getApiSet = async (input, jsonType) => { 36 | const extension = path.extname(input).toLowerCase(); 37 | const isYaml = /.ya?ml$/.test(extension); 38 | const apiConfig = await readApiConfig(input); 39 | return parserMap[jsonType](isYaml ? yaml.load(apiConfig) : apiConfig); 40 | }; 41 | 42 | module.exports = getApiSet; 43 | -------------------------------------------------------------------------------- /lib/api2code/generateCrud/index.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const getApiSet = require('./getApiSet'); 3 | // const validateApiSet = require('./validateApiSet'); 4 | const genServiceCode = require('./genService'); 5 | const genModelCode = require('./genModel'); 6 | const emitCode = require('./emitCode'); 7 | const { formatCode } = require('../../../utils'); 8 | const spinner = require('./spinner'); 9 | const { mkdir } = require('../../utils/fileSystem'); 10 | const { languages } = require('../../utils/constants'); 11 | 12 | const generateCrud = async options => { 13 | const { input, output, jsonType, language } = options; 14 | const isTs = languages.Typescript === language; 15 | spinner.getApiSet(); 16 | const apiSet = await getApiSet(input, jsonType); 17 | 18 | // await validateApiSet(apiSet); 19 | 20 | spinner.genCode(); 21 | const modelsConfig = await genModelCode(apiSet.apis, isTs); 22 | const servicesConfig = await genServiceCode( 23 | apiSet.apis, 24 | language, 25 | modelsConfig, 26 | ); 27 | 28 | spinner.emitCode(); 29 | const outputBaseDir = path.resolve(process.cwd(), output); 30 | await mkdir(outputBaseDir, { recursive: true }); 31 | await Promise.all([ 32 | emitCode(servicesConfig, outputBaseDir, 'services'), 33 | isTs && emitCode(modelsConfig, outputBaseDir, 'models'), 34 | ]); 35 | 36 | spinner.formatCode(); 37 | formatCode(outputBaseDir); 38 | 39 | spinner.success(); 40 | }; 41 | 42 | module.exports = generateCrud; 43 | -------------------------------------------------------------------------------- /lib/api2code/generateCrud/spinner.js: -------------------------------------------------------------------------------- 1 | const ora = require('ora'); 2 | 3 | const spinner = { 4 | ora: ora(''), 5 | getApiSet() { 6 | this.ora.start('Start getting api configuration'); 7 | }, 8 | 9 | genCode() { 10 | this.ora.succeed('Get api configuration success'); 11 | this.ora.start('Start generating code'); 12 | }, 13 | 14 | emitCode() { 15 | this.ora.succeed('Code generation successful'); 16 | this.ora.start('Start emiting code'); 17 | }, 18 | 19 | formatCode() { 20 | this.ora.succeed('Code emiting successful'); 21 | this.ora.start('Start format code'); 22 | }, 23 | 24 | success() { 25 | this.ora.succeed('Format code successful'); 26 | }, 27 | }; 28 | 29 | module.exports = spinner; 30 | -------------------------------------------------------------------------------- /lib/api2code/generateCrud/validateApiSet.js: -------------------------------------------------------------------------------- 1 | // const tsj = require('ts-json-schema-generator'); 2 | const path = require('path'); 3 | const Ajv = require('ajv/dist/jtd'); 4 | const { readFile } = require('../../utils/fileSystem'); 5 | 6 | const ajv = new Ajv(); 7 | 8 | // const config = { 9 | // path: 'path/to/source/file', 10 | // tsconfig: 'path/to/tsconfig.json', 11 | // type: '*', // Or if you want to generate schema for that one type only 12 | // }; 13 | 14 | const validateApiSet = async apiSet => { 15 | // const schema = tsj.createGenerator(config).createSchema(config.type); 16 | // const schemaString = JSON.stringify(schema, null, 2); 17 | // console.log(schemaString); 18 | const schema = await readFile(path.join(__dirname, './schema.json'), 'utf8'); 19 | 20 | const validate = ajv.compile(JSON.parse(schema)); 21 | 22 | const res = validate(apiSet); 23 | console.log(res); 24 | }; 25 | 26 | module.exports = validateApiSet; 27 | -------------------------------------------------------------------------------- /lib/api2code/generateInterface/index.js: -------------------------------------------------------------------------------- 1 | const { resolve, basename } = require('path'); 2 | const ora = require('ora'); 3 | const chalk = require('chalk'); 4 | const request = require('../../utils/request'); 5 | const { removeEmpty, path2CamelCase } = require('../../utils'); 6 | const output2File = require('../../utils/output2File'); 7 | const quicktypeJSON = require('../../utils/quicktypeJSON'); 8 | 9 | const spinner = ora('transforming...'); 10 | 11 | const outputInterface = (data, name, output) => { 12 | quicktypeJSON('TypeScript', name, JSON.stringify(data)) 13 | .then(({ lines }) => { 14 | output2File(resolve(process.cwd(), output), lines.join('\n')).then(() => { 15 | spinner.stop(); 16 | // eslint-disable-next-line no-console 17 | console.log(chalk.green('successfully 🎉 🎉 🎉')); 18 | }); 19 | }) 20 | .catch(err => console.error(err)); 21 | }; 22 | 23 | const fromRequest = options => { 24 | const babyData = options.body ? require(options.body) : null; 25 | request( 26 | removeEmpty({ 27 | method: options.httpMethod, 28 | url: `${options.url}${options.path}`, 29 | data: babyData, 30 | }), 31 | ) 32 | .then(({ data }) => 33 | outputInterface(data, path2CamelCase(options.path), options.output), 34 | ) 35 | .catch(err => console.error(err)); 36 | }; 37 | 38 | const fromLocalJson = options => { 39 | const dataJson = require(options.input); 40 | const name = basename(options.input) 41 | .split('.')[0] 42 | .replace(/^(\w)/g, (all, letter) => letter.toUpperCase()); 43 | outputInterface(dataJson, name, options.output); 44 | }; 45 | 46 | const generateInterface = options => { 47 | spinner.start(); 48 | // 如果有 -i 则通过本地json生成interface 49 | if (options.input) { 50 | fromLocalJson(options); 51 | return; 52 | } 53 | fromRequest(options); 54 | }; 55 | 56 | module.exports = generateInterface; 57 | -------------------------------------------------------------------------------- /lib/api2code/parser/index.js: -------------------------------------------------------------------------------- 1 | const parseOpenapi = require('./openapi'); 2 | 3 | const parserMap = { 4 | Custom: apiConfig => apiConfig, // 内部自定义的数据结构 5 | OpenAPI: apiConfig => parseOpenapi(apiConfig), 6 | // ... 7 | }; 8 | 9 | module.exports = { parserMap }; 10 | -------------------------------------------------------------------------------- /lib/api2code/parser/openapi/index.js: -------------------------------------------------------------------------------- 1 | const getServices = require('./parser/getServices'); 2 | const parseRef = require('./parseRef'); 3 | 4 | const parseOpenapi = async openapi => { 5 | const refContent = await parseRef(openapi); 6 | return getServices(refContent); 7 | }; 8 | 9 | module.exports = parseOpenapi; 10 | -------------------------------------------------------------------------------- /lib/api2code/parser/openapi/oraState.js: -------------------------------------------------------------------------------- 1 | const ora = require('ora'); 2 | 3 | const oasGenStatus = { 4 | spinner: ora('Generate code based on OAS'), 5 | start() { 6 | this.spinner.start(); 7 | }, 8 | 9 | read() { 10 | this.spinner.text = 'Reading OpenApi spec file...'; 11 | }, 12 | 13 | parse() { 14 | this.spinner.text = 'Start parsing...'; 15 | this.spinner.color = 'yellow'; 16 | }, 17 | 18 | generate() { 19 | this.spinner.text = 'Start Generating...'; 20 | this.spinner.color = 'green'; 21 | }, 22 | 23 | success() { 24 | this.spinner.stop(); 25 | }, 26 | }; 27 | 28 | module.exports = { 29 | oasGenStatus, 30 | }; 31 | -------------------------------------------------------------------------------- /lib/api2code/parser/openapi/parseRef.js: -------------------------------------------------------------------------------- 1 | const RefParser = require('json-schema-ref-parser'); 2 | 3 | const parseRef = json => { 4 | return RefParser.bundle(json); 5 | }; 6 | 7 | module.exports = parseRef; 8 | -------------------------------------------------------------------------------- /lib/api2code/parser/openapi/parser/getModels.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lgwebdream/fe-code/4a49a289e953bfd99f37875c966dd53590eb1401/lib/api2code/parser/openapi/parser/getModels.js -------------------------------------------------------------------------------- /lib/api2code/parser/openapi/parser/getServer.js: -------------------------------------------------------------------------------- 1 | const getServer = openApi => { 2 | const server = openApi.servers?.[0]; 3 | const variables = server?.variables || {}; 4 | let url = server?.url || ''; 5 | for (const variable in variables) { 6 | if (variables.hasOwnProperty(variable)) { 7 | url = url.replace(`{${variable}}`, variables[variable].default); 8 | } 9 | } 10 | return url; 11 | }; 12 | 13 | module.exports = getServer; 14 | -------------------------------------------------------------------------------- /lib/api2code/parser/openapi/parser/getServices.js: -------------------------------------------------------------------------------- 1 | const camelCase = require('camelcase'); 2 | const parseParams = require('./parseParams'); 3 | 4 | const getServiceName = path => { 5 | return path.match(/^\/(\w+)/)[1]; 6 | }; 7 | 8 | const handleMethodsGroup = (path, methodsGroup) => { 9 | const servicePath = []; 10 | for (const method in methodsGroup) { 11 | if (methodsGroup.hasOwnProperty(method)) { 12 | const request = methodsGroup[method]; 13 | const functionName = camelCase(path.split('/'), { 14 | pascalCase: true, 15 | }); 16 | const eachRequest = { 17 | method: method.toLocaleUpperCase(), 18 | path, 19 | name: `${method}${functionName}`, // unsupport dynamic route now. 20 | requestParams: parseParams(method, request), 21 | description: request.description, 22 | }; 23 | servicePath.push(eachRequest); 24 | } 25 | } 26 | return servicePath; 27 | }; 28 | 29 | const expandEachPath = services => { 30 | const servicesObj = {}; 31 | services.forEach((serviceValue, serviceKey) => { 32 | const pathArray = []; 33 | serviceValue.forEach(pathValue => { 34 | pathArray.push(...pathValue); 35 | }); 36 | servicesObj[serviceKey] = pathArray; 37 | }); 38 | return servicesObj; 39 | }; 40 | 41 | const splitServices = paths => { 42 | const services = new Map(); // key: service name, value: service 43 | for (const path in paths) { 44 | if (paths.hasOwnProperty(path)) { 45 | const methodsGroup = paths[path]; 46 | const serviceName = getServiceName(path); 47 | const eachPath = [path, handleMethodsGroup(path, methodsGroup)]; 48 | services.get(serviceName) 49 | ? services.get(serviceName).set(...eachPath) 50 | : services.set(serviceName, new Map([eachPath])); 51 | } 52 | } 53 | return services; 54 | }; 55 | 56 | const getServices = openApi => { 57 | const splitedServices = splitServices(openApi.paths); 58 | const services = expandEachPath(splitedServices); 59 | return services; 60 | }; 61 | 62 | module.exports = getServices; 63 | -------------------------------------------------------------------------------- /lib/api2code/parser/openapi/parser/parseParams.js: -------------------------------------------------------------------------------- 1 | // const getBody = () => {}; 2 | 3 | const parseGet = ({ parameters }) => { 4 | const getSchema = {}; 5 | if (!parameters) return getSchema; 6 | for (const param of parameters) { 7 | if (param.in === 'query') { 8 | getSchema[param.name] = { 9 | required: param.required, 10 | }; 11 | } 12 | } 13 | return getSchema; 14 | }; 15 | 16 | const parsePost = ({ requestBody }) => { 17 | const { content } = requestBody; 18 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 19 | const reqBody = content['application/json']; 20 | return {}; 21 | }; 22 | 23 | const parseParams = (method, request) => { 24 | switch (method) { 25 | case 'get': 26 | return parseGet(request); 27 | case 'post': 28 | return parsePost(request); 29 | default: 30 | return {}; 31 | } 32 | }; 33 | 34 | module.exports = parseParams; 35 | -------------------------------------------------------------------------------- /lib/api2code/template/helpers.js: -------------------------------------------------------------------------------- 1 | const camelCase = require('camelcase'); 2 | 3 | const toLowerCase = str => str.toLowerCase(); 4 | 5 | const path2CamelCase = str => camelCase(str.split('/').filter(i => i)); 6 | 7 | const diff = (v1, v2) => v1 === v2; 8 | 9 | const getType = (currentType, currentPath, interfacesConfig) => { 10 | const interface = interfacesConfig.find( 11 | i => i.type === currentType && i.path === currentPath, 12 | ); 13 | return interface.interfaceName; 14 | }; 15 | 16 | module.exports = { 17 | toLowerCase, 18 | path2CamelCase, 19 | diff, 20 | getType, 21 | }; 22 | -------------------------------------------------------------------------------- /lib/api2code/template/model.hbs: -------------------------------------------------------------------------------- 1 | {{#each interfacesConfig}} 2 | export interface {{this.interfaceName}} { 3 | {{#each this.data}} 4 | {{@key}}{{#if this.required}}{{else}}?{{/if}}: {{this.type}}; 5 | {{/each}} 6 | } 7 | 8 | {{/each}} -------------------------------------------------------------------------------- /lib/api2code/template/registerTemplates.js: -------------------------------------------------------------------------------- 1 | const Handlebars = require('handlebars'); 2 | const path = require('path'); 3 | const { readFile } = require('../../utils/fileSystem'); 4 | const helpers = require('./helpers'); 5 | 6 | const compileTemplate = pathStr => async options => { 7 | const template = await readFile(path.join(__dirname, pathStr), 'utf8'); 8 | return Handlebars.compile(template)(options); 9 | }; 10 | 11 | Object.entries(helpers).forEach(([key, helper]) => { 12 | Handlebars.registerHelper(key, helper); 13 | }); 14 | 15 | module.exports = { 16 | service: options => compileTemplate('./service.hbs')(options), 17 | model: options => compileTemplate('./model.hbs')(options), 18 | core: {}, 19 | }; 20 | -------------------------------------------------------------------------------- /lib/api2code/template/service.hbs: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | {{#if modelConfig.interfacesConfig}} 3 | import { 4 | {{#each modelConfig.interfacesConfig}} 5 | {{this.interfaceName}}, 6 | {{/each}} 7 | } from '../models/{{modelConfig.filename}}' 8 | {{/if}} 9 | 10 | 11 | export default class {{serviceName}} { 12 | {{#each service}} 13 | /** 14 | * @desc {{this.description}} 15 | {{#if (diff this.method 'POST')}} 16 | * @param requestBody Update an existent user in the store 17 | {{else}} 18 | {{#each this.requestParams}} 19 | * @param {{@key}} {{this.description}} 20 | {{/each}} 21 | {{/if}} 22 | * @returns any successful operation 23 | */ 24 | static async {{path2CamelCase this.path}} 25 | ( 26 | {{#if (diff this.method 'POST')}} 27 | requestBody 28 | {{#if ../isTs}} 29 | : {{getType 'body' this.path ../modelConfig.interfacesConfig}} 30 | {{/if}} 31 | {{else}} 32 | {{#each this.requestParams}} 33 | {{@key}} {{#if ../../isTs}}: {{this.type}}{{/if}}, 34 | {{/each}} 35 | {{/if}} 36 | ) 37 | 38 | {{#if ../isTs}} 39 | :Promise< 40 | {{getType 'response' this.path ../modelConfig.interfacesConfig}} 41 | > 42 | {{/if}} 43 | 44 | { 45 | const response = await axios.{{toLowerCase this.method}}( 46 | '{{this.path}}', 47 | {{#if (diff this.method 'POST')}} 48 | requestBody 49 | {{else}} 50 | { 51 | params: { 52 | {{#each this.requestParams}} 53 | {{@key}}, 54 | {{/each}} 55 | } 56 | } 57 | {{/if}} 58 | ); 59 | return response.data; 60 | } 61 | 62 | {{/each}} 63 | } -------------------------------------------------------------------------------- /lib/defaultConfig.js: -------------------------------------------------------------------------------- 1 | const customConfig = { 2 | projectName: 'empty-project', 3 | buildTool: 'snowpack', // webpack-backup/vite/snowpack; rollup(for feature) 4 | mainFramework: { 5 | name: 'vue', 6 | version: 2, 7 | }, 8 | uiFramework: 'none', 9 | featureList: [], 10 | lint: true, 11 | }; 12 | const CONFIG_NAME = '.fecoderc.json'; 13 | const initConfig = { 14 | request: { 15 | url: 'http://localhost:3000', 16 | headers: {}, 17 | }, 18 | language: 'zh-CN', 19 | templatePath: 'src', 20 | }; 21 | const defaultConfig = { 22 | ...initConfig, 23 | ...customConfig, 24 | }; 25 | module.exports.CONFIG_NAME = CONFIG_NAME; 26 | module.exports.initConfig = initConfig; 27 | module.exports.defaultConfig = defaultConfig; 28 | -------------------------------------------------------------------------------- /lib/loadConfig.js: -------------------------------------------------------------------------------- 1 | const { cosmiconfigSync } = require('cosmiconfig'); 2 | const { defaultConfig } = require('./defaultConfig'); 3 | 4 | const searchPlaces = [ 5 | '.fecoderc', 6 | '.fecoderc.json', 7 | '.fecoderc.yaml', 8 | '.fecoderc.yml', 9 | '.fecoderc.js', 10 | '.fecoderc.cjs', 11 | // 'fe-code.config.js', 12 | // 'fe-code.config.cjs', 13 | ]; 14 | 15 | const loadConfig = () => { 16 | const explorer = cosmiconfigSync('fe-code', { searchPlaces }); 17 | 18 | if (explorer.search()) { 19 | return explorer.search().config; 20 | } 21 | return defaultConfig; 22 | }; 23 | 24 | module.exports = loadConfig; 25 | -------------------------------------------------------------------------------- /lib/react/README.md: -------------------------------------------------------------------------------- 1 | ## react2code 2 | 3 | - `components` 基础组件 4 | - `template` 基础模板 5 | -------------------------------------------------------------------------------- /lib/react/components/Container/Modal.tsx: -------------------------------------------------------------------------------- 1 | import { Form, Modal } from 'antd'; 2 | import React, { useEffect, useState } from 'react'; 3 | import FForm from '../Form'; 4 | import { ICrudModalProps } from './ModalTypes'; 5 | 6 | const FCrudModal = (props: ICrudModalProps): React.ReactElement => { 7 | const { data, columns, onOk, visible, onCancel } = props; 8 | const [form] = Form.useForm(); 9 | const [isInit, setInit] = useState(false); 10 | 11 | useEffect(() => { 12 | setInit(true); 13 | }, []); 14 | 15 | useEffect(() => { 16 | if (!isInit) return; 17 | 18 | if (visible) { 19 | form?.setFieldsValue(data); 20 | } else { 21 | form?.resetFields(); 22 | } 23 | }, [data, visible]); 24 | 25 | return ( 26 | { 31 | form.validateFields().then(() => { 32 | onOk && onOk({ ...form.getFieldsValue() }); 33 | }); 34 | }} 35 | onCancel={e => { 36 | onCancel && onCancel(e); 37 | }} 38 | > 39 | 45 | 46 | ); 47 | }; 48 | 49 | export default FCrudModal; 50 | -------------------------------------------------------------------------------- /lib/react/components/Container/ModalTypes.ts: -------------------------------------------------------------------------------- 1 | import { ModalProps } from 'antd'; 2 | import { ICrudColumn } from '../CrudTypes'; 3 | 4 | export interface ICrudModalProps extends ModalProps { 5 | /** 表单 */ 6 | data?: Record; 7 | /** 表单字段列表 */ 8 | columns?: ICrudColumn[]; 9 | } 10 | -------------------------------------------------------------------------------- /lib/react/components/Form/FormTypes.ts: -------------------------------------------------------------------------------- 1 | import { 2 | FormProps, 3 | FormItemProps, 4 | InputProps, 5 | InputNumberProps, 6 | SelectProps, 7 | DatePickerProps, 8 | TimePickerProps, 9 | TreeSelectProps, 10 | CascaderProps, 11 | } from 'antd'; 12 | import { IFormComTypeEnum } from './constant'; 13 | 14 | export { IFormComTypeEnum }; 15 | 16 | export interface IFormItemProps extends FormItemProps { 17 | // TODO 18 | isList?: boolean; // 是否是List 19 | // TODO 20 | visibleOn?: string; // 显示联动 21 | key?: string; // 唯一值 22 | } 23 | 24 | export type IFormComponentProps = 25 | | InputProps 26 | | InputNumberProps 27 | | SelectProps 28 | | DatePickerProps 29 | | TimePickerProps 30 | | TreeSelectProps 31 | | CascaderProps; 32 | 33 | export type IFormProps = FormProps; 34 | -------------------------------------------------------------------------------- /lib/react/components/Form/README.md: -------------------------------------------------------------------------------- 1 | ## form 支持组件 2 | 3 | ```js 4 | export enum IFormComTypeEnum { 5 | Input = 'Input', 6 | InputNumber = 'InputNumber', 7 | Select = 'Select', 8 | DatePicker = 'DatePicker', // 日期选择器 9 | TimePicker = 'TimePicker', //时间选择框 10 | List = 'FormList' 11 | } 12 | ``` 13 | 14 | ## FormRender 入参 15 | 16 | ```js 17 | { 18 | layout: '', 19 | initialValues: '', 20 | onValuesChange: '', 21 | labelCol: { span: 4 }, 22 | wrapperCol: { span: 14 }, 23 | } 24 | ``` 25 | 26 | ## form schema 数据格式 27 | 28 | ```js 29 | export const schema = [ 30 | { 31 | comType: IFormComTypeEnum.Input, 32 | comProps: {}, 33 | itemProps: { 34 | name: 'email', 35 | label: 'E-mail', 36 | rules: [ 37 | { 38 | type: 'email', 39 | message: 'The input is not valid E-mail!', 40 | }, 41 | { 42 | required: true, 43 | message: 'Please input your E-mail!', 44 | }, 45 | ], 46 | }, 47 | }, 48 | ]; 49 | ``` 50 | 51 | ## FSelect (TODO) 52 | 53 | - 支持动态 Options 54 | -------------------------------------------------------------------------------- /lib/react/components/Form/constant.ts: -------------------------------------------------------------------------------- 1 | // 支持的组件类型 2 | export enum IFormComTypeEnum { 3 | Input = 'Input', 4 | InputNumber = 'InputNumber', 5 | Select = 'Select', 6 | DatePicker = 'DatePicker', // 日期选择器 7 | TimePicker = 'TimePicker', // 时间选择框 8 | RadioGroup = 'RadioGroup', // 单选框 9 | TreeSelect = 'TreeSelect', 10 | Cascader = 'Cascader', 11 | Switch = 'Switch', 12 | CheckboxGroup = 'CheckboxGroup', 13 | Slider = 'Slider', 14 | Rate = 'Rate', 15 | Checkbox = 'Checkbox', 16 | } 17 | -------------------------------------------------------------------------------- /lib/react/components/Form/form-components/f-checkbox/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Checkbox } from 'antd'; 3 | 4 | interface IFCheckboxProps { 5 | onChange: (value: boolean) => void; 6 | value: boolean; 7 | [key: string]: any; 8 | } 9 | 10 | export default function FCheckbox(props: IFCheckboxProps) { 11 | const { value, onChange, ...rest } = props; 12 | 13 | const onCheckboxChange = (e: { target: { checked: boolean } }) => { 14 | onChange(e.target.checked); 15 | }; 16 | 17 | return ; 18 | } 19 | -------------------------------------------------------------------------------- /lib/react/components/Form/form-components/f-switch/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Switch } from 'antd'; 3 | 4 | interface IFSwitchProps { 5 | onChange: (value: boolean) => void; 6 | value: boolean; 7 | [key: string]: any; 8 | } 9 | 10 | export default function FSwitch(props: IFSwitchProps) { 11 | const { value, onChange, ...rest } = props; 12 | 13 | const onSwitchChange = (e: boolean) => { 14 | onChange(e); 15 | }; 16 | 17 | return ; 18 | } 19 | -------------------------------------------------------------------------------- /lib/react/components/Form/formUtils.ts: -------------------------------------------------------------------------------- 1 | import { IFormComTypeEnum } from './constant'; 2 | import Mapping from './mappting'; 3 | 4 | export function findComByName(name: IFormComTypeEnum): any { 5 | if (Mapping[name]) { 6 | return Mapping[name]; 7 | } 8 | console.error(`未注册${name}组件`); 9 | return null; 10 | } 11 | -------------------------------------------------------------------------------- /lib/react/components/Form/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Form } from 'antd'; 3 | import type { IFormProps } from './FormTypes'; 4 | import { findComByName } from './formUtils'; 5 | import { ICrudColumn } from '../CrudTypes'; 6 | 7 | const { Item } = Form; 8 | 9 | const formatColumn = (column: ICrudColumn) => { 10 | const item = { ...column }; 11 | delete item.dataIndex; 12 | delete item.itemProps; 13 | delete item.fieldProps; 14 | return item; 15 | }; 16 | export interface IFFormProps extends IFormProps { 17 | schema: ICrudColumn[]; 18 | children?: React.ReactNode; 19 | } 20 | 21 | const FForm: React.FC = (props: IFFormProps) => { 22 | const { schema, children } = props; 23 | return ( 24 |

25 | {schema?.map(item => { 26 | const { type, fieldProps, itemProps } = item; 27 | const FComponent = findComByName(type); 28 | 29 | if (!FComponent) return null; 30 | 31 | const temp = formatColumn(item); 32 | return ( 33 | 40 | {/* TODO 完善更多表单 */} 41 | 42 | 43 | ); 44 | })} 45 | {children} 46 |
47 | ); 48 | }; 49 | 50 | export default FForm; 51 | -------------------------------------------------------------------------------- /lib/react/components/Form/mappting.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Input, 3 | InputNumber, 4 | Select, 5 | DatePicker, 6 | TimePicker, 7 | Radio, 8 | TreeSelect, 9 | Cascader, 10 | Checkbox, 11 | Slider, 12 | Rate, 13 | } from 'antd'; 14 | import { IFormComTypeEnum } from './constant'; 15 | import FCheckbox from './form-components/f-checkbox'; 16 | import FSwitch from './form-components/f-switch'; 17 | 18 | const Mapping = { 19 | [IFormComTypeEnum.Input]: Input, 20 | [IFormComTypeEnum.InputNumber]: InputNumber, 21 | [IFormComTypeEnum.Select]: Select, 22 | [IFormComTypeEnum.DatePicker]: DatePicker, // 日期选择器 23 | [IFormComTypeEnum.TimePicker]: TimePicker, // 时间选择框 24 | [IFormComTypeEnum.RadioGroup]: Radio.Group, // 单选 25 | [IFormComTypeEnum.TreeSelect]: TreeSelect, 26 | [IFormComTypeEnum.Cascader]: Cascader, 27 | [IFormComTypeEnum.Switch]: FSwitch, 28 | [IFormComTypeEnum.CheckboxGroup]: Checkbox.Group, 29 | [IFormComTypeEnum.Slider]: Slider, 30 | [IFormComTypeEnum.Rate]: Rate, 31 | [IFormComTypeEnum.Checkbox]: FCheckbox, 32 | }; 33 | 34 | export default Mapping; 35 | -------------------------------------------------------------------------------- /lib/react/components/Table/hooks/useActionType.ts: -------------------------------------------------------------------------------- 1 | import { ActionType, UseFetchActions } from '../TableTypes'; 2 | /** 3 | * 获取用户的 action 信息 4 | * 5 | * @param actionRef 6 | * @param counter 7 | * @param onCleanSelected 8 | */ 9 | export function useActionType( 10 | ref: React.MutableRefObject, 11 | action: UseFetchActions, 12 | props: { 13 | onCleanSelected: () => void; 14 | }, 15 | ) { 16 | const userAction: ActionType = { 17 | reload: async (resetRowSelected?: boolean) => { 18 | // 如果为 true,清空选择状态 19 | if (resetRowSelected) { 20 | await props.onCleanSelected(); 21 | } 22 | action?.reload(); 23 | }, 24 | /** 刷新并且重置 */ 25 | reloadAndRest: async () => { 26 | props.onCleanSelected(); 27 | await action.setPageInfo({ 28 | current: 1, 29 | }); 30 | }, 31 | }; 32 | ref.current = userAction; 33 | } 34 | -------------------------------------------------------------------------------- /lib/react/components/Table/hooks/usePrevious.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useRef } from 'react'; 2 | 3 | const usePrevious = (state: T): T | undefined => { 4 | const ref = useRef(); 5 | 6 | useEffect(() => { 7 | ref.current = state; 8 | }); 9 | 10 | return ref.current; 11 | }; 12 | 13 | export default usePrevious; 14 | -------------------------------------------------------------------------------- /lib/react/components/Table/index.tsx: -------------------------------------------------------------------------------- 1 | import CrudTable from './Table'; 2 | 3 | export default CrudTable; 4 | -------------------------------------------------------------------------------- /lib/react/components/Table/table.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lgwebdream/fe-code/4a49a289e953bfd99f37875c966dd53590eb1401/lib/react/components/Table/table.md -------------------------------------------------------------------------------- /lib/react/components/Table/utils/index.ts: -------------------------------------------------------------------------------- 1 | import type { ColumnType, SortOrder } from 'antd/lib/table/interface'; 2 | 3 | export { default as useFetchData } from './useFetchData'; 4 | 5 | function parseDataIndex( 6 | dataIndex: ColumnType<'string'>['dataIndex'], 7 | ): string | undefined { 8 | if (Array.isArray(dataIndex)) { 9 | return dataIndex.join(','); 10 | } 11 | return dataIndex?.toString(); 12 | } 13 | 14 | export function parseDefaultColumnConfig(columns: ColumnType[]) { 15 | const filter: Record = {}; 16 | const sort: Record = {}; 17 | columns.forEach(column => { 18 | // 转换 dataIndex 19 | const dataIndex = parseDataIndex(column.dataIndex); 20 | if (!dataIndex) { 21 | return; 22 | } 23 | // 当 column 启用 filters 功能时,取出默认的筛选值 24 | if (column.filters) { 25 | const defaultFilteredValue = 26 | column.defaultFilteredValue as React.ReactText[]; 27 | if (defaultFilteredValue === undefined) { 28 | filter[dataIndex] = null; 29 | } else { 30 | filter[dataIndex] = column.defaultFilteredValue as React.ReactText[]; 31 | } 32 | } 33 | // 当 column 启用 sorter 功能时,取出默认的排序值 34 | if (column.sorter && column.defaultSortOrder) { 35 | sort[dataIndex] = column.defaultSortOrder!; 36 | } 37 | }); 38 | return { sort, filter }; 39 | } 40 | -------------------------------------------------------------------------------- /lib/react/components/ToolBar/BatchOperation.tsx: -------------------------------------------------------------------------------- 1 | import React, { useMemo } from 'react'; 2 | import { Button } from 'antd'; 3 | import type { ICrudToolbar } from '../CrudTypes'; 4 | 5 | const { Group } = Button; 6 | 7 | export interface BatchOperationProps { 8 | selectedRowKeys?: (string | number)[]; 9 | selectedRows?: T[]; 10 | options?: ICrudToolbar[]; 11 | } 12 | 13 | export function BatchButtonGroup({ 14 | options, 15 | args, 16 | }: { 17 | options: ICrudToolbar[]; 18 | args: { row: T[] | T; rowKey?: (string | number)[] | number }; 19 | }) { 20 | return ( 21 | 22 | {options?.map((it, idx) => { 23 | const { 24 | key, 25 | type, 26 | label, 27 | children, 28 | icon, 29 | danger, 30 | disabled, 31 | style, 32 | className, 33 | onClick, 34 | request, 35 | } = it; 36 | return it.render ? ( 37 | 38 | {it.render(args.row, args.rowKey)} 39 | 40 | ) : ( 41 | 55 | ); 56 | })} 57 | 58 | ); 59 | } 60 | 61 | const BatchOperation = (props: BatchOperationProps) => { 62 | const { selectedRows, selectedRowKeys, options } = props; 63 | 64 | const Buttons = useMemo(() => { 65 | return ( 66 | 70 | ); 71 | }, [options, selectedRows, selectedRowKeys]); 72 | 73 | return
{Buttons}
; 74 | }; 75 | 76 | export default BatchOperation; 77 | -------------------------------------------------------------------------------- /lib/react/components/ToolBar/FilterSearch.tsx: -------------------------------------------------------------------------------- 1 | import classnames from 'classnames'; 2 | import { Form, Button } from 'antd'; 3 | import React, { useMemo } from 'react'; 4 | import { getClassName } from './utils'; 5 | import type { SearchOptions } from './ToolbarTypes'; 6 | import FForm from '../Form/index'; 7 | 8 | const { useForm } = Form; 9 | 10 | const FilterSearch = (props: SearchOptions) => { 11 | const [formInstance] = useForm(); 12 | const { columns, style, className, prefixCls, render, onSearch, onReset } = 13 | props; 14 | 15 | const nextClassName = classnames( 16 | getClassName('filter-search', prefixCls), 17 | className, 18 | ); 19 | 20 | const nextStyle = useMemo( 21 | () => ({ 22 | marginBottom: 10, 23 | ...(style || {}), 24 | }), 25 | [], 26 | ); 27 | 28 | const onResetClick = () => { 29 | formInstance.resetFields(); 30 | onReset?.(); 31 | }; 32 | 33 | const onSearchClick = async () => { 34 | const flag = await formInstance.validateFields(); 35 | flag && onSearch?.(formInstance.getFieldsValue()); 36 | }; 37 | 38 | return ( 39 |
40 | {render ? ( 41 | render() 42 | ) : ( 43 | <> 44 | {columns.length ? ( 45 | 46 | 47 | 48 | 51 | 52 | 53 | ) : null} 54 | 55 | )} 56 |
57 | ); 58 | }; 59 | 60 | export default FilterSearch; 61 | -------------------------------------------------------------------------------- /lib/react/components/ToolBar/ToolbarTypes.ts: -------------------------------------------------------------------------------- 1 | import React, { CSSProperties, ReactElement } from 'react'; 2 | import type { LabelTooltipType } from 'antd/lib/form/FormItemLabel'; 3 | import { ICrudColumn, ICrudToolbar } from '../CrudTypes'; 4 | 5 | export interface ToolBarOptions { 6 | headerTitle?: React.ReactNode; 7 | tooltip?: string | LabelTooltipType; 8 | tip?: string; 9 | className?: string; 10 | style?: CSSProperties; 11 | prefixCls?: string; 12 | render?: (rows: { 13 | selectedRowKeys?: (string | number)[]; 14 | selectedRows?: T[]; 15 | }) => ReactElement; 16 | } 17 | 18 | export interface SearchOptions { 19 | prefixCls?: string; 20 | className?: string; 21 | style?: CSSProperties; 22 | columns?: ICrudColumn[]; 23 | onSearch?: (values: T) => void; 24 | onReset?: () => void; 25 | render?: () => ReactElement; 26 | } 27 | 28 | export interface ToolBarProps { 29 | selectedRowKeys?: (string | number)[]; 30 | selectedRows?: T[]; 31 | toolbarOptions?: ToolBarOptions; 32 | batchOptions?: ICrudToolbar[]; 33 | searchOptions?: SearchOptions; 34 | } 35 | -------------------------------------------------------------------------------- /lib/react/components/ToolBar/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useMemo } from 'react'; 2 | import classnames from 'classnames'; 3 | import BatchOperation from './BatchOperation'; 4 | import FilberSearch from './FilterSearch'; 5 | import { getClassName } from './utils'; 6 | import { ToolBarProps } from './ToolbarTypes'; 7 | 8 | const ToolBar = (props: ToolBarProps) => { 9 | const { 10 | selectedRowKeys, 11 | selectedRows, 12 | searchOptions, 13 | toolbarOptions, 14 | batchOptions, 15 | } = props; 16 | 17 | const { prefixCls, style, className, render } = toolbarOptions || {}; 18 | 19 | const nextClassName = classnames( 20 | getClassName('toolbar', prefixCls), 21 | className, 22 | ); 23 | 24 | const nextStyle = useMemo( 25 | () => ({ 26 | padding: 10, 27 | ...style, 28 | }), 29 | [style], 30 | ); 31 | 32 | const dynamicRender = render?.({ selectedRowKeys, selectedRows }); 33 | 34 | return ( 35 |
36 | {dynamicRender || ( 37 | <> 38 | 39 | 44 | 45 | )} 46 |
47 | ); 48 | }; 49 | 50 | ToolBar.BatchOperation = BatchOperation; 51 | ToolBar.FilberSearch = FilberSearch; 52 | 53 | export default ToolBar; 54 | -------------------------------------------------------------------------------- /lib/react/components/ToolBar/utils.ts: -------------------------------------------------------------------------------- 1 | function getClassName(suffixCls: string, prefixCls?: string) { 2 | return prefixCls ? `${prefixCls}-${suffixCls}` : suffixCls; 3 | } 4 | 5 | export { getClassName }; 6 | -------------------------------------------------------------------------------- /lib/react/components/index.ts: -------------------------------------------------------------------------------- 1 | export * from './Form/FormTypes'; 2 | export * from './CrudTypes'; 3 | 4 | export { default as FCrud } from './Crud'; 5 | -------------------------------------------------------------------------------- /lib/react/components/service.ts: -------------------------------------------------------------------------------- 1 | export interface FetcherResult { 2 | data?: { 3 | data: T[] | undefined; 4 | }; 5 | code: number; 6 | msg: string; 7 | msgTimeout?: number; 8 | errors?: { 9 | [propName: string]: string; 10 | }; 11 | [propName: string]: any; // 为了兼容其他返回格式 12 | } 13 | 14 | export interface FetchOptions { 15 | method?: 'get' | 'post' | 'put' | 'patch' | 'delete'; 16 | successMessage?: string; 17 | errorMessage?: string; 18 | autoAppend?: boolean; 19 | beforeSend?: (data: any) => any; 20 | onSuccess?: (json: Payload) => any; 21 | onFailed?: (json: Payload) => any; 22 | silent?: boolean; 23 | [propName: string]: any; 24 | } 25 | 26 | export interface Payload { 27 | ok: boolean; 28 | msg: string; 29 | msgTimeout?: number; 30 | data: any; 31 | status: number; 32 | errors?: { 33 | [propName: string]: string; 34 | }; 35 | } 36 | -------------------------------------------------------------------------------- /lib/react/jsx/components/Container/Modal.jsx: -------------------------------------------------------------------------------- 1 | import { Form, Modal } from 'antd'; 2 | import React, { useEffect, useState } from 'react'; 3 | import FForm from '../Form'; 4 | const FCrudModal = (props) => { 5 | const { data, columns, onOk, visible, onCancel } = props; 6 | const [form] = Form.useForm(); 7 | const [isInit, setInit] = useState(false); 8 | useEffect(() => { 9 | setInit(true); 10 | }, []); 11 | useEffect(() => { 12 | if (!isInit) 13 | return; 14 | if (visible) { 15 | form?.setFieldsValue(data); 16 | } 17 | else { 18 | form?.resetFields(); 19 | } 20 | }, [data, visible]); 21 | return ( { 22 | form.validateFields().then(() => { 23 | onOk && onOk({ ...form.getFieldsValue() }); 24 | }); 25 | }} onCancel={e => { 26 | onCancel && onCancel(e); 27 | }}> 28 | 29 | ); 30 | }; 31 | export default FCrudModal; 32 | -------------------------------------------------------------------------------- /lib/react/jsx/components/Container/ModalTypes.js: -------------------------------------------------------------------------------- 1 | export {}; 2 | -------------------------------------------------------------------------------- /lib/react/jsx/components/CrudTypes.js: -------------------------------------------------------------------------------- 1 | /** 操作容器定义 */ 2 | export var ICurdContainerTypeEnum; 3 | (function (ICurdContainerTypeEnum) { 4 | /** modal 弹框模式 */ 5 | ICurdContainerTypeEnum["Modal"] = "modal"; 6 | /** panel 窗体模式 */ 7 | ICurdContainerTypeEnum["Panel"] = "panel"; 8 | })(ICurdContainerTypeEnum || (ICurdContainerTypeEnum = {})); 9 | /** 内置功能类型 */ 10 | export var ICrudToolbarTypeEnum; 11 | (function (ICrudToolbarTypeEnum) { 12 | /** 添加 */ 13 | ICrudToolbarTypeEnum["Add"] = "add"; 14 | /** 编辑 */ 15 | ICrudToolbarTypeEnum["Edit"] = "edit"; 16 | /** 删除 */ 17 | ICrudToolbarTypeEnum["Delete"] = "delete"; 18 | /** 批量删除 */ 19 | ICrudToolbarTypeEnum["DeleteBatch"] = "deleteBatch"; 20 | })(ICrudToolbarTypeEnum || (ICrudToolbarTypeEnum = {})); 21 | -------------------------------------------------------------------------------- /lib/react/jsx/components/Form/FormTypes.js: -------------------------------------------------------------------------------- 1 | import { IFormComTypeEnum } from './constant'; 2 | export { IFormComTypeEnum }; 3 | -------------------------------------------------------------------------------- /lib/react/jsx/components/Form/constant.js: -------------------------------------------------------------------------------- 1 | // 支持的组件类型 2 | export var IFormComTypeEnum; 3 | (function (IFormComTypeEnum) { 4 | IFormComTypeEnum["Input"] = "Input"; 5 | IFormComTypeEnum["InputNumber"] = "InputNumber"; 6 | IFormComTypeEnum["Select"] = "Select"; 7 | IFormComTypeEnum["DatePicker"] = "DatePicker"; 8 | IFormComTypeEnum["TimePicker"] = "TimePicker"; 9 | IFormComTypeEnum["RadioGroup"] = "RadioGroup"; 10 | IFormComTypeEnum["TreeSelect"] = "TreeSelect"; 11 | IFormComTypeEnum["Cascader"] = "Cascader"; 12 | IFormComTypeEnum["Switch"] = "Switch"; 13 | IFormComTypeEnum["CheckboxGroup"] = "CheckboxGroup"; 14 | IFormComTypeEnum["Slider"] = "Slider"; 15 | IFormComTypeEnum["Rate"] = "Rate"; 16 | IFormComTypeEnum["Checkbox"] = "Checkbox"; 17 | })(IFormComTypeEnum || (IFormComTypeEnum = {})); 18 | -------------------------------------------------------------------------------- /lib/react/jsx/components/Form/form-components/f-checkbox/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Checkbox } from 'antd'; 3 | export default function FCheckbox(props) { 4 | const { value, onChange, ...rest } = props; 5 | const onCheckboxChange = (e) => { 6 | onChange(e.target.checked); 7 | }; 8 | return ; 9 | } 10 | -------------------------------------------------------------------------------- /lib/react/jsx/components/Form/form-components/f-switch/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Switch } from 'antd'; 3 | export default function FSwitch(props) { 4 | const { value, onChange, ...rest } = props; 5 | const onSwitchChange = (e) => { 6 | onChange(e); 7 | }; 8 | return ; 9 | } 10 | -------------------------------------------------------------------------------- /lib/react/jsx/components/Form/formUtils.js: -------------------------------------------------------------------------------- 1 | import Mapping from './mappting'; 2 | export function findComByName(name) { 3 | if (Mapping[name]) { 4 | return Mapping[name]; 5 | } 6 | console.error(`未注册${name}组件`); 7 | return null; 8 | } 9 | -------------------------------------------------------------------------------- /lib/react/jsx/components/Form/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Form } from 'antd'; 3 | import { findComByName } from './formUtils'; 4 | const { Item } = Form; 5 | const formatColumn = (column) => { 6 | const item = { ...column }; 7 | delete item.dataIndex; 8 | delete item.itemProps; 9 | delete item.fieldProps; 10 | return item; 11 | }; 12 | const FForm = (props) => { 13 | const { schema, children } = props; 14 | return (
15 | {schema?.map(item => { 16 | const { type, fieldProps, itemProps } = item; 17 | const FComponent = findComByName(type); 18 | if (!FComponent) 19 | return null; 20 | const temp = formatColumn(item); 21 | return ( 22 | {/* TODO 完善更多表单 */} 23 | 24 | ); 25 | })} 26 | {children} 27 |
); 28 | }; 29 | export default FForm; 30 | -------------------------------------------------------------------------------- /lib/react/jsx/components/Form/mappting.js: -------------------------------------------------------------------------------- 1 | import { Input, InputNumber, Select, DatePicker, TimePicker, Radio, TreeSelect, Cascader, Checkbox, Slider, Rate, } from 'antd'; 2 | import { IFormComTypeEnum } from './constant'; 3 | import FCheckbox from './form-components/f-checkbox'; 4 | import FSwitch from './form-components/f-switch'; 5 | const Mapping = { 6 | [IFormComTypeEnum.Input]: Input, 7 | [IFormComTypeEnum.InputNumber]: InputNumber, 8 | [IFormComTypeEnum.Select]: Select, 9 | [IFormComTypeEnum.DatePicker]: DatePicker, 10 | [IFormComTypeEnum.TimePicker]: TimePicker, 11 | [IFormComTypeEnum.RadioGroup]: Radio.Group, 12 | [IFormComTypeEnum.TreeSelect]: TreeSelect, 13 | [IFormComTypeEnum.Cascader]: Cascader, 14 | [IFormComTypeEnum.Switch]: FSwitch, 15 | [IFormComTypeEnum.CheckboxGroup]: Checkbox.Group, 16 | [IFormComTypeEnum.Slider]: Slider, 17 | [IFormComTypeEnum.Rate]: Rate, 18 | [IFormComTypeEnum.Checkbox]: FCheckbox, 19 | }; 20 | export default Mapping; 21 | -------------------------------------------------------------------------------- /lib/react/jsx/components/Table/TableTypes.js: -------------------------------------------------------------------------------- 1 | export {}; 2 | -------------------------------------------------------------------------------- /lib/react/jsx/components/Table/hooks/useActionType.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 获取用户的 action 信息 3 | * 4 | * @param actionRef 5 | * @param counter 6 | * @param onCleanSelected 7 | */ 8 | export function useActionType(ref, action, props) { 9 | const userAction = { 10 | reload: async (resetRowSelected) => { 11 | // 如果为 true,清空选择状态 12 | if (resetRowSelected) { 13 | await props.onCleanSelected(); 14 | } 15 | action?.reload(); 16 | }, 17 | /** 刷新并且重置 */ 18 | reloadAndRest: async () => { 19 | props.onCleanSelected(); 20 | await action.setPageInfo({ 21 | current: 1, 22 | }); 23 | }, 24 | }; 25 | ref.current = userAction; 26 | } 27 | -------------------------------------------------------------------------------- /lib/react/jsx/components/Table/hooks/usePrevious.js: -------------------------------------------------------------------------------- 1 | import { useEffect, useRef } from 'react'; 2 | const usePrevious = (state) => { 3 | const ref = useRef(); 4 | useEffect(() => { 5 | ref.current = state; 6 | }); 7 | return ref.current; 8 | }; 9 | export default usePrevious; 10 | -------------------------------------------------------------------------------- /lib/react/jsx/components/Table/index.jsx: -------------------------------------------------------------------------------- 1 | import CrudTable from './Table'; 2 | export default CrudTable; 3 | -------------------------------------------------------------------------------- /lib/react/jsx/components/Table/utils/index.js: -------------------------------------------------------------------------------- 1 | export { default as useFetchData } from './useFetchData'; 2 | function parseDataIndex(dataIndex) { 3 | if (Array.isArray(dataIndex)) { 4 | return dataIndex.join(','); 5 | } 6 | return dataIndex?.toString(); 7 | } 8 | export function parseDefaultColumnConfig(columns) { 9 | const filter = {}; 10 | const sort = {}; 11 | columns.forEach(column => { 12 | // 转换 dataIndex 13 | const dataIndex = parseDataIndex(column.dataIndex); 14 | if (!dataIndex) { 15 | return; 16 | } 17 | // 当 column 启用 filters 功能时,取出默认的筛选值 18 | if (column.filters) { 19 | const defaultFilteredValue = column.defaultFilteredValue; 20 | if (defaultFilteredValue === undefined) { 21 | filter[dataIndex] = null; 22 | } 23 | else { 24 | filter[dataIndex] = column.defaultFilteredValue; 25 | } 26 | } 27 | // 当 column 启用 sorter 功能时,取出默认的排序值 28 | if (column.sorter && column.defaultSortOrder) { 29 | sort[dataIndex] = column.defaultSortOrder; 30 | } 31 | }); 32 | return { sort, filter }; 33 | } 34 | -------------------------------------------------------------------------------- /lib/react/jsx/components/ToolBar/BatchOperation.jsx: -------------------------------------------------------------------------------- 1 | import React, { useMemo } from 'react'; 2 | import { Button } from 'antd'; 3 | const { Group } = Button; 4 | export function BatchButtonGroup({ options, args, }) { 5 | return ( 6 | {options?.map((it, idx) => { 7 | const { key, type, label, children, icon, danger, disabled, style, className, onClick, request, } = it; 8 | return it.render ? ( 9 | {it.render(args.row, args.rowKey)} 10 | ) : (); 13 | })} 14 | ); 15 | } 16 | const BatchOperation = (props) => { 17 | const { selectedRows, selectedRowKeys, options } = props; 18 | const Buttons = useMemo(() => { 19 | return (); 20 | }, [options, selectedRows, selectedRowKeys]); 21 | return
{Buttons}
; 22 | }; 23 | export default BatchOperation; 24 | -------------------------------------------------------------------------------- /lib/react/jsx/components/ToolBar/FilterSearch.jsx: -------------------------------------------------------------------------------- 1 | import classnames from 'classnames'; 2 | import { Form, Button } from 'antd'; 3 | import React, { useMemo } from 'react'; 4 | import { getClassName } from './utils'; 5 | import FForm from '../Form/index'; 6 | const { useForm } = Form; 7 | const FilterSearch = (props) => { 8 | const [formInstance] = useForm(); 9 | const { columns, style, className, prefixCls, render, onSearch, onReset } = props; 10 | const nextClassName = classnames(getClassName('filter-search', prefixCls), className); 11 | const nextStyle = useMemo(() => ({ 12 | marginBottom: 10, 13 | ...(style || {}), 14 | }), []); 15 | const onResetClick = () => { 16 | formInstance.resetFields(); 17 | onReset?.(); 18 | }; 19 | const onSearchClick = async () => { 20 | const flag = await formInstance.validateFields(); 21 | flag && onSearch?.(formInstance.getFieldsValue()); 22 | }; 23 | return (
24 | {render ? (render()) : (<> 25 | {columns.length ? ( 26 | 27 | 28 | 31 | 32 | ) : null} 33 | )} 34 |
); 35 | }; 36 | export default FilterSearch; 37 | -------------------------------------------------------------------------------- /lib/react/jsx/components/ToolBar/ToolbarTypes.js: -------------------------------------------------------------------------------- 1 | export {}; 2 | -------------------------------------------------------------------------------- /lib/react/jsx/components/ToolBar/index.jsx: -------------------------------------------------------------------------------- 1 | import React, { useMemo } from 'react'; 2 | import classnames from 'classnames'; 3 | import BatchOperation from './BatchOperation'; 4 | import FilberSearch from './FilterSearch'; 5 | import { getClassName } from './utils'; 6 | const ToolBar = (props) => { 7 | const { selectedRowKeys, selectedRows, searchOptions, toolbarOptions, batchOptions, } = props; 8 | const { prefixCls, style, className, render } = toolbarOptions || {}; 9 | const nextClassName = classnames(getClassName('toolbar', prefixCls), className); 10 | const nextStyle = useMemo(() => ({ 11 | padding: 10, 12 | ...style, 13 | }), [style]); 14 | const dynamicRender = render?.({ selectedRowKeys, selectedRows }); 15 | return (
16 | {dynamicRender || (<> 17 | 18 | 19 | )} 20 |
); 21 | }; 22 | ToolBar.BatchOperation = BatchOperation; 23 | ToolBar.FilberSearch = FilberSearch; 24 | export default ToolBar; 25 | -------------------------------------------------------------------------------- /lib/react/jsx/components/ToolBar/utils.js: -------------------------------------------------------------------------------- 1 | function getClassName(suffixCls, prefixCls) { 2 | return prefixCls ? `${prefixCls}-${suffixCls}` : suffixCls; 3 | } 4 | export { getClassName }; 5 | -------------------------------------------------------------------------------- /lib/react/jsx/components/index.js: -------------------------------------------------------------------------------- 1 | export * from './Form/FormTypes'; 2 | export * from './CrudTypes'; 3 | export { default as FCrud } from './Crud'; 4 | -------------------------------------------------------------------------------- /lib/react/jsx/components/service.js: -------------------------------------------------------------------------------- 1 | export {}; 2 | -------------------------------------------------------------------------------- /lib/react/mock/delete.json: -------------------------------------------------------------------------------- 1 | {"result":3,"success":true} -------------------------------------------------------------------------------- /lib/react/mock/modify.json: -------------------------------------------------------------------------------- 1 | {"result":{"id":3,"name":"张三","age":18,"title":"cto"},"success":true} -------------------------------------------------------------------------------- /lib/react/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@fe-code/react", 3 | "version": "1.0.0", 4 | "description": "react crud template", 5 | "files": [ 6 | "components" 7 | ], 8 | "main": "components/index.ts", 9 | "scripts": { 10 | "clean": "rimraf jsx/*", 11 | "build": "npm run clean && tsc --project tsconfig.json --outDir jsx/ --module ES2015", 12 | "prepare": "npm run build" 13 | }, 14 | "keywords": [ 15 | "react" 16 | ], 17 | "author": "水逆", 18 | "license": "ISC", 19 | "dependencies": { 20 | "@ant-design/icons": "^4.6.2", 21 | "antd": "^4.16.7", 22 | "classnames": "^2.3.1", 23 | "react": "^17.0.2" 24 | }, 25 | "devDependencies": { 26 | "@types/react": "^17.0.14", 27 | "rimraf": "^3.0.2", 28 | "typescript": "^4.3.5" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /lib/react/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.eslint.json", 3 | "compilerOptions": { 4 | "types": ["react"], 5 | "jsx": "preserve", 6 | "declaration": false, 7 | "esModuleInterop": true, 8 | "strict": true, 9 | "importHelpers": true, 10 | "sourceMap": false, 11 | "module": "esnext", 12 | "skipLibCheck": true, 13 | "importsNotUsedAsValues": "remove", 14 | "allowSyntheticDefaultImports": true 15 | }, 16 | "include": ["components/*", "template/*"], 17 | "exclude": ["node_modules"] 18 | } -------------------------------------------------------------------------------- /lib/utils/constants.js: -------------------------------------------------------------------------------- 1 | const extMap = { 2 | JavaScript: '.js', 3 | Typescript: '.ts', 4 | }; 5 | 6 | const languages = { 7 | Typescript: 'Typescript', 8 | JavaScript: 'JavaScript', 9 | }; 10 | 11 | module.exports = { extMap, languages }; 12 | -------------------------------------------------------------------------------- /lib/utils/file.js: -------------------------------------------------------------------------------- 1 | const { join } = require('path'); 2 | 3 | const { 4 | readdirSync, 5 | readFile, 6 | writeFile, 7 | createReadStream, 8 | createWriteStream, 9 | existsSync, 10 | mkdirSync, 11 | } = require('fs'); 12 | 13 | const handleError = err => err && console.error(err) && process.exit(-1); 14 | 15 | /** 16 | * 迁移文件到固定目录 17 | * 18 | * @param {*} fromPath 需要迁移的文件夹路径 19 | * @param {*} toPath 目的文件夹路径 20 | * @param {*} ignoreFiles 需要忽略的文件名集合 21 | * @returns 22 | */ 23 | const transferDir = (fromPath, toPath, ignoreFiles = []) => { 24 | if (!existsSync(fromPath)) return; 25 | 26 | // not exist and mkdir 27 | !existsSync(toPath) && mkdirSync(toPath); 28 | 29 | const files = readdirSync(fromPath, { withFileTypes: true }); 30 | 31 | for (const file of files) { 32 | if (!ignoreFiles.includes(file.name)) { 33 | const fromFilePath = join(fromPath, file.name); 34 | const toFilePath = join(toPath, file.name); 35 | 36 | if (file.isFile()) { 37 | // copy file 38 | const reader = createReadStream(fromFilePath); 39 | const writer = createWriteStream(toFilePath); 40 | reader.pipe(writer); 41 | } else { 42 | transferDir(fromFilePath, toFilePath); 43 | } 44 | } 45 | } 46 | }; 47 | 48 | /** 49 | * 重写文件内容 50 | * 51 | * @param {*} fromFile 52 | * @param {*} toFile 53 | * @param {*} formatter 格式化回调 data => string 54 | */ 55 | const transferFile = (fromFile, toFile, formatter = null) => { 56 | return new Promise((resolve, reject) => { 57 | if (!existsSync(fromFile)) { 58 | reject(`${fromFile} not exist`); 59 | return; 60 | } 61 | 62 | readFile(fromFile, 'utf8', (err, data) => { 63 | if (err) { 64 | handleError(err); 65 | reject(err); 66 | return; 67 | } 68 | 69 | // formatter for data 70 | const replaceData = 71 | formatter && typeof formatter === 'function' ? formatter(data) : data; 72 | 73 | writeFile(toFile, replaceData, 'utf8', err => { 74 | if (err) { 75 | handleError(err); 76 | reject(err); 77 | return; 78 | } 79 | resolve(true); 80 | }); 81 | }); 82 | }); 83 | }; 84 | 85 | module.exports = { 86 | transferDir, 87 | transferFile, 88 | }; 89 | -------------------------------------------------------------------------------- /lib/utils/fileSystem.js: -------------------------------------------------------------------------------- 1 | const { 2 | copyFile: copyFileCore, 3 | exists: existsCore, 4 | readFile: readFileCore, 5 | writeFile: writeFileCore, 6 | mkdir: mkdirCore, 7 | } = require('fs'); 8 | const { promisify } = require('util'); 9 | 10 | const readFile = promisify(readFileCore); 11 | const writeFile = promisify(writeFileCore); 12 | const copyFile = promisify(copyFileCore); 13 | const exists = promisify(existsCore); 14 | const mkdir = promisify(mkdirCore); 15 | 16 | module.exports = { 17 | readFile, 18 | writeFile, 19 | copyFile, 20 | exists, 21 | mkdir, 22 | }; 23 | -------------------------------------------------------------------------------- /lib/utils/index.js: -------------------------------------------------------------------------------- 1 | const { resolve } = require('path'); 2 | 3 | const removeEmpty = obj => { 4 | const newObj = {}; 5 | Object.keys(obj).forEach(key => { 6 | if (obj[key] === Object(obj[key])) newObj[key] = removeEmpty(obj[key]); 7 | else if (obj[key] !== undefined) newObj[key] = obj[key]; 8 | }); 9 | return newObj; 10 | }; 11 | 12 | const path2CamelCase = name => 13 | name.replace(/\/(\w)/g, (all, letter) => letter.toUpperCase()); 14 | 15 | const getCwdPath = path => { 16 | if (typeof path === 'string') { 17 | return resolve(process.cwd(), path); 18 | } 19 | return path; 20 | }; 21 | 22 | module.exports = { 23 | removeEmpty, 24 | path2CamelCase, 25 | getCwdPath, 26 | }; 27 | -------------------------------------------------------------------------------- /lib/utils/log.js: -------------------------------------------------------------------------------- 1 | const logSymbols = require('log-symbols'); 2 | const chalk = require('chalk'); 3 | 4 | const logSuccess = message => 5 | console.log(logSymbols.success, chalk.green(message)); 6 | const logError = message => console.log(logSymbols.error, chalk.red(message)); 7 | const logInfo = message => console.log(logSymbols.info, chalk.blue(message)); 8 | const logWarning = message => 9 | console.log(logSymbols.warning, chalk.yellow(message)); 10 | 11 | module.exports = { 12 | logSuccess, 13 | logError, 14 | logInfo, 15 | logWarning, 16 | }; 17 | -------------------------------------------------------------------------------- /lib/utils/output2File.js: -------------------------------------------------------------------------------- 1 | const fs = require('promise-fs'); 2 | const path = require('path'); 3 | 4 | const output2File = (dir, filename = '', data) => 5 | new Promise((resolve, reject) => { 6 | const handleError = err => { 7 | reject(err); 8 | console.error(err); 9 | }; 10 | 11 | const completeDir = path.join(dir, filename); 12 | console.log(completeDir); 13 | fs.readFile(completeDir, { 14 | encoding: 'utf8', 15 | }) 16 | .then(res => { 17 | fs.writeFile(completeDir, `${res}\n${data}`) 18 | .then(() => resolve()) 19 | .catch(handleError); 20 | }) 21 | .catch(err => { 22 | const { errno } = err; 23 | // no directory or filename 24 | if (errno === -2 || errno === -4058) { 25 | fs.mkdirSync(dir, { recursive: true }); 26 | fs.writeFile(completeDir, data) 27 | .then(() => resolve()) 28 | .catch(handleError); 29 | } 30 | }); 31 | }); 32 | 33 | module.exports = output2File; 34 | -------------------------------------------------------------------------------- /lib/utils/quicktypeJSON.js: -------------------------------------------------------------------------------- 1 | const { 2 | quicktype, 3 | InputData, 4 | jsonInputForTargetLanguage, 5 | } = require('quicktype-core'); 6 | 7 | async function quicktypeJSON(targetLanguage, typeName, jsonString) { 8 | const jsonInput = jsonInputForTargetLanguage(targetLanguage); 9 | 10 | await jsonInput.addSource({ 11 | name: typeName, 12 | samples: [jsonString], 13 | }); 14 | 15 | const inputData = new InputData(); 16 | inputData.addInput(jsonInput); 17 | 18 | return quicktype({ 19 | inputData, 20 | lang: targetLanguage, 21 | rendererOptions: { 22 | 'just-types': true, 23 | }, 24 | }); 25 | } 26 | 27 | module.exports = quicktypeJSON; 28 | -------------------------------------------------------------------------------- /lib/utils/request.js: -------------------------------------------------------------------------------- 1 | const axios = require('axios'); 2 | const HttpsProxyAgent = require('https-proxy-agent'); 3 | const loadConfig = require('../loadConfig'); 4 | 5 | const { 6 | request: { url }, 7 | headers, 8 | } = loadConfig(); 9 | const httpsAgent = new HttpsProxyAgent(url); 10 | 11 | const request = axios.create({ 12 | httpsAgent, 13 | headers, 14 | }); 15 | 16 | module.exports = request; 17 | -------------------------------------------------------------------------------- /lib/vue2/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | 5 | 6 | # local env files 7 | .env.local 8 | .env.*.local 9 | 10 | # Log files 11 | npm-debug.log* 12 | yarn-debug.log* 13 | yarn-error.log* 14 | pnpm-debug.log* 15 | yarn.lock* 16 | # Editor directories and files 17 | .idea 18 | .vscode 19 | *.suo 20 | *.ntvs* 21 | *.njsproj 22 | *.sln 23 | *.sw? 24 | 25 | -------------------------------------------------------------------------------- /lib/vue2/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "semi": true, 4 | "bracketSpacing": true, 5 | "trailingComma": "all", 6 | "printWidth": 100, 7 | "proseWrap": "never", 8 | "arrowParens": "avoid", 9 | "overrides": [ 10 | { 11 | "files": ".prettierrc", 12 | "options": { 13 | "parser": "json" 14 | } 15 | } 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /lib/vue2/README.md: -------------------------------------------------------------------------------- 1 | # vue 2 | 3 | vue组件,如非开始正常安装依赖即可yarn install 4 | 5 | ## 开发步骤 6 | 7 | 1. 安装依赖 8 | 9 | ```bash 10 | yarn install 11 | npm link 12 | ``` 13 | -------------------------------------------------------------------------------- /lib/vue2/components/Form/config.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import { ICrud, ICurdFromItemTypeEnum } from '../CrudTypes'; 3 | import TablePropsBase from '../Table/config'; 4 | 5 | const apiConfig = { 6 | add: '/api/json/add', 7 | edit: '/api/json/edit', 8 | delete: '/api/json/delete', 9 | list: '/api/json/list', 10 | }; 11 | 12 | const TableProps: ICrud = { 13 | ...TablePropsBase, 14 | searchConfigs: [ 15 | { 16 | type: ICurdFromItemTypeEnum.Input, 17 | label: '审批人', 18 | value: '', 19 | prop: 'user', 20 | placeholder: '审批人', 21 | }, 22 | { 23 | type: ICurdFromItemTypeEnum.Select, 24 | label: '活动区域', 25 | value: '', 26 | prop: 'region', 27 | placeholder: '活动区域', 28 | data: [ 29 | { 30 | label: '上海', 31 | value: 'shanghai', 32 | }, 33 | { 34 | label: '北京', 35 | value: 'beijing', 36 | }, 37 | ], 38 | }, 39 | ], 40 | }; 41 | 42 | export default TableProps; 43 | -------------------------------------------------------------------------------- /lib/vue2/components/Form/constant.ts: -------------------------------------------------------------------------------- 1 | // 支持的组件类型 2 | export enum IFormComTypeEnum { 3 | Input = 'Input', 4 | InputNumber = 'InputNumber', 5 | Select = 'Select', 6 | DatePicker = 'DatePicker', // 日期选择器 7 | TimePicker = 'TimePicker', // 时间选择框 8 | RadioGroup = 'RadioGroup', // 单选框 9 | TreeSelect = 'TreeSelect', 10 | Cascader = 'Cascader', 11 | Switch = 'Switch', 12 | CheckboxGroup = 'CheckboxGroup', 13 | Slider = 'Slider', 14 | Rate = 'Rate', 15 | Checkbox = 'Checkbox', 16 | } 17 | 18 | /** 表单类型 */ 19 | export interface FormType { 20 | Input: IFormComTypeEnum.Input; 21 | InputNumber: IFormComTypeEnum.InputNumber; 22 | Select: IFormComTypeEnum.Select; 23 | DatePicker: IFormComTypeEnum.DatePicker; 24 | TimePicker: IFormComTypeEnum.TimePicker; 25 | RadioGroup: IFormComTypeEnum.RadioGroup; 26 | TreeSelect: IFormComTypeEnum.TreeSelect; 27 | Cascader: IFormComTypeEnum.Cascader; 28 | Switch: IFormComTypeEnum.Switch; 29 | CheckboxGroup: IFormComTypeEnum.CheckboxGroup; 30 | Slider: IFormComTypeEnum.Slider; 31 | Rate: IFormComTypeEnum.Rate; 32 | Checkbox: IFormComTypeEnum.Checkbox; 33 | } 34 | 35 | export interface IFormData { 36 | [key: string]: any; 37 | } 38 | -------------------------------------------------------------------------------- /lib/vue2/components/Table/ToolBar/filterSearch.vue: -------------------------------------------------------------------------------- 1 | 26 | 27 | 72 | 73 | 78 | -------------------------------------------------------------------------------- /lib/vue2/components/Table/config.ts: -------------------------------------------------------------------------------- 1 | 2 | import { 3 | ICrud, 4 | ICurdFromItemTypeEnum, 5 | ICrudToolbarTypeEnum, 6 | } from '../CrudTypes'; 7 | 8 | import axios from 'axios'; 9 | 10 | 11 | const apiConfig = {'list':'/api/json/list','add':'/api/json/add','edit':'/api/json/edit','delete':'/api/json/delete','batchDelete':'/api/json/delete'} 12 | 13 | 14 | const TableProps: ICrud = { 15 | title:'标题', 16 | columns: [ 17 | { dataIndex: 'date', title: '日期', readonly: true }, 18 | { dataIndex: 'name', title: '姓名' }, 19 | { dataIndex: 'address', title: '地址' }, 20 | ], 21 | 22 | request: async params => { return axios.get(apiConfig.list, { params })}, 23 | 24 | batchToolbar: [{ 25 | label: '添加', 26 | type: 'primary', 27 | toolbarType: ICrudToolbarTypeEnum.Add, 28 | request: (row) => 29 | axios(apiConfig.add, { method: 'post', data: row }).then(() => { 30 | 31 | }), 32 | },{ 33 | label: '删除', 34 | type: 'link', 35 | toolbarType: ICrudToolbarTypeEnum.Delete, 36 | request: (row) => 37 | axios(apiConfig.delete, { method: 'post', data: row }).then(() => { 38 | 39 | }), 40 | },{ 41 | label: '批量删除', 42 | type: 'dashed', 43 | toolbarType: ICrudToolbarTypeEnum.DeleteBatch, 44 | request: (row) => 45 | axios(apiConfig.delete, { method: 'post', data: row }).then(() => { 46 | 47 | }), 48 | }], 49 | searchConfigs: [ 50 | { 51 | type: ICurdFromItemTypeEnum.Input, 52 | label: '审批人', 53 | value: '', 54 | prop: 'user', 55 | placeholder: '审批人', 56 | }, 57 | { 58 | type: ICurdFromItemTypeEnum.Select, 59 | label: '活动区域', 60 | value: '', 61 | prop: 'region', 62 | placeholder: '活动区域', 63 | data: [ 64 | { 65 | label: '上海', 66 | value: 'shanghai', 67 | }, 68 | { 69 | label: '北京', 70 | value: 'beijing', 71 | }, 72 | ], 73 | }, 74 | ], 75 | }; 76 | 77 | 78 | 79 | export default TableProps 80 | -------------------------------------------------------------------------------- /lib/vue2/components/crud.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | -------------------------------------------------------------------------------- /lib/vue2/components/index.ts: -------------------------------------------------------------------------------- 1 | 2 | import Vue,{VueConstructor} from 'vue'; 3 | import Table from './Table/index.vue'; 4 | import ElementUI from 'element-ui'; 5 | import 'element-ui/lib/theme-chalk/index.css'; 6 | 7 | Vue.use(ElementUI) 8 | // 组件列表 9 | const components = [Table]; 10 | 11 | const install: any = function (Vue:VueConstructor) { 12 | if (install.installed) return; 13 | // @ts-ignore 14 | components.map(component => Vue.component(new component().$options.name, component)); 15 | }; 16 | 17 | if (typeof window !== 'undefined' && window.Vue) { 18 | install(window.Vue); 19 | } 20 | 21 | export default { 22 | // 导出的对象必须具有 install,才能被 Vue.use() 方法安装 23 | install, 24 | // 以下是具体的组件列表 25 | Table 26 | }; 27 | 28 | 29 | -------------------------------------------------------------------------------- /lib/vue2/components/shims-vue.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.vue' { 2 | import Vue from 'vue' 3 | export default Vue 4 | } 5 | declare module '*.ts' { 6 | import Vue from 'vue' 7 | export default Vue 8 | } 9 | declare module 'vue/types/vue' { 10 | // 3. 声明为 Vue 补充的东西 11 | interface Vue { 12 | install: string 13 | } 14 | } 15 | declare interface Window { 16 | Vue: any; 17 | } 18 | 19 | declare module '*.css'; 20 | declare module '*.less'; 21 | declare module '*.png'; 22 | declare module '*.svg'; 23 | -------------------------------------------------------------------------------- /lib/vue2/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@fe-code/vue2", 3 | "version": "0.1.0", 4 | "description": "", 5 | "license": "ISC", 6 | "main": "./components", 7 | "scripts": {}, 8 | "dependencies": { 9 | "element-ui": "^2.15.3", 10 | "vue": "^2.6.11", 11 | "vue-router": "^3.5.2" 12 | }, 13 | "devDependencies": { 14 | "babel-eslint": "^10.1.0", 15 | "typescript": "^4.3.5" 16 | }, 17 | "eslintConfig": { 18 | "root": true, 19 | "env": { 20 | "node": true 21 | }, 22 | "extends": [ 23 | "plugin:vue/vue3-essential", 24 | "eslint:recommended" 25 | ], 26 | "parserOptions": { 27 | "parser": "babel-eslint" 28 | }, 29 | "rules": {} 30 | }, 31 | "browserslist": [ 32 | "> 1%", 33 | "last 2 versions", 34 | "not dead" 35 | ] 36 | } 37 | -------------------------------------------------------------------------------- /lib/vue2/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "module": "esnext", 5 | "strict": true, 6 | "jsx": "preserve", 7 | "importHelpers": true, 8 | "moduleResolution": "node", 9 | "experimentalDecorators": true, 10 | "esModuleInterop": true, 11 | "allowSyntheticDefaultImports": true, 12 | "sourceMap": true, 13 | "baseUrl": ".", 14 | "types": ["webpack-env"], 15 | "paths": { 16 | "@/*": ["src/*"] 17 | }, 18 | "lib": ["esnext", "dom", "dom.iterable"] 19 | }, 20 | "include": [ 21 | "components/**/*.ts", 22 | "components/**/*.tsx", 23 | "components/**/*.vue", 24 | "examples/**/*.ts", 25 | "examples/**/*.tsx", 26 | "examples/**/*.vue", 27 | "shims-vue.d.ts" 28 | ], 29 | "exclude": ["node_modules"] 30 | } 31 | -------------------------------------------------------------------------------- /lib/vue3/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | 5 | 6 | # local env files 7 | .env.local 8 | .env.*.local 9 | 10 | # Log files 11 | npm-debug.log* 12 | yarn-debug.log* 13 | yarn-error.log* 14 | pnpm-debug.log* 15 | yarn.lock* 16 | # Editor directories and files 17 | .idea 18 | .vscode 19 | *.suo 20 | *.ntvs* 21 | *.njsproj 22 | *.sln 23 | *.sw? 24 | 25 | -------------------------------------------------------------------------------- /lib/vue3/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "semi": true, 4 | "bracketSpacing": true, 5 | "trailingComma": "all", 6 | "printWidth": 100, 7 | "proseWrap": "never", 8 | "arrowParens": "avoid", 9 | "overrides": [ 10 | { 11 | "files": ".prettierrc", 12 | "options": { 13 | "parser": "json" 14 | } 15 | } 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /lib/vue3/README.md: -------------------------------------------------------------------------------- 1 | # vue 2 | 3 | ## 安装依赖 4 | 5 | ```bash 6 | yarn install 7 | npm link 8 | ``` 9 | 10 | ## vue-demo安装 11 | 12 | 安装依赖,并link当前vue目录。关联组件 13 | 14 | ```bash 15 | yarn install 16 | yarn start 17 | ``` 18 | -------------------------------------------------------------------------------- /lib/vue3/components/Table/Form/constant.ts: -------------------------------------------------------------------------------- 1 | // 支持的组件类型 2 | export enum IFormComTypeEnum { 3 | Input = 'Input', 4 | InputNumber = 'InputNumber', 5 | Select = 'Select', 6 | DatePicker = 'DatePicker', // 日期选择器 7 | TimePicker = 'TimePicker', // 时间选择框 8 | RadioGroup = 'RadioGroup', // 单选框 9 | TreeSelect = 'TreeSelect', 10 | Cascader = 'Cascader', 11 | Switch = 'Switch', 12 | CheckboxGroup = 'CheckboxGroup', 13 | Slider = 'Slider', 14 | Rate = 'Rate', 15 | Checkbox = 'Checkbox', 16 | } 17 | 18 | /** 表单类型 */ 19 | export interface FormType { 20 | Input: IFormComTypeEnum.Input; 21 | InputNumber: IFormComTypeEnum.InputNumber; 22 | Select: IFormComTypeEnum.Select; 23 | DatePicker: IFormComTypeEnum.DatePicker; 24 | TimePicker: IFormComTypeEnum.TimePicker; 25 | RadioGroup: IFormComTypeEnum.RadioGroup; 26 | TreeSelect: IFormComTypeEnum.TreeSelect; 27 | Cascader: IFormComTypeEnum.Cascader; 28 | Switch: IFormComTypeEnum.Switch; 29 | CheckboxGroup: IFormComTypeEnum.CheckboxGroup; 30 | Slider: IFormComTypeEnum.Slider; 31 | Rate: IFormComTypeEnum.Rate; 32 | Checkbox: IFormComTypeEnum.Checkbox; 33 | } 34 | 35 | export interface IFormData { 36 | [key: string]: any; 37 | } 38 | -------------------------------------------------------------------------------- /lib/vue3/components/Table/ToolBar/filterSearch.vue: -------------------------------------------------------------------------------- 1 | 26 | 27 | 72 | 73 | 78 | -------------------------------------------------------------------------------- /lib/vue3/components/crud.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | -------------------------------------------------------------------------------- /lib/vue3/components/index.ts: -------------------------------------------------------------------------------- 1 | import { ComponentOptions } from 'vue'; 2 | import Table from './Table/index.vue'; 3 | 4 | // 组件列表 5 | const components = [Table]; 6 | 7 | const install: any = function (Vue: ComponentOptions) { 8 | if (install.installed) return; 9 | components.map(component => Vue.component(component.name, component)); 10 | }; 11 | 12 | if (typeof window !== 'undefined' && window.Vue) { 13 | install(window.Vue); 14 | } 15 | 16 | export default { 17 | // 导出的对象必须具有 install,才能被 Vue.use() 方法安装 18 | install, 19 | Table, 20 | }; 21 | -------------------------------------------------------------------------------- /lib/vue3/components/shims-vue.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.vue' { 2 | import { ComponentOptions } from 'vue'; 3 | const componentOptions: ComponentOptions; 4 | export default componentOptions; 5 | } 6 | 7 | declare interface Window { 8 | Vue: any; 9 | } 10 | 11 | declare module '*.css'; 12 | declare module '*.less'; 13 | declare module '*.png'; 14 | declare module '*.svg'; 15 | -------------------------------------------------------------------------------- /lib/vue3/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@fe-code/vue3", 3 | "version": "0.1.0", 4 | "description": "", 5 | "license": "ISC", 6 | "main": "./components", 7 | "scripts": {}, 8 | "dependencies": { 9 | "element-plus": "^1.0.2-beta.70", 10 | "vue": "3.1.5", 11 | "vue-router": "^3.5.2" 12 | }, 13 | "devDependencies": { 14 | "babel-eslint": "^10.1.0", 15 | "typescript": "^4.3.5" 16 | }, 17 | "eslintConfig": { 18 | "root": true, 19 | "env": { 20 | "node": true 21 | }, 22 | "extends": [ 23 | "plugin:vue/vue3-essential", 24 | "eslint:recommended" 25 | ], 26 | "parserOptions": { 27 | "parser": "babel-eslint" 28 | }, 29 | "rules": {} 30 | }, 31 | "browserslist": [ 32 | "> 1%", 33 | "last 2 versions", 34 | "not dead" 35 | ] 36 | } 37 | -------------------------------------------------------------------------------- /lib/vue3/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "module": "esnext", 5 | "strict": true, 6 | "jsx": "preserve", 7 | "importHelpers": true, 8 | "moduleResolution": "node", 9 | "experimentalDecorators": true, 10 | "esModuleInterop": true, 11 | "allowSyntheticDefaultImports": true, 12 | "sourceMap": true, 13 | "baseUrl": ".", 14 | "types": ["webpack-env"], 15 | "paths": { 16 | "@/*": ["src/*"] 17 | }, 18 | "lib": ["esnext", "dom", "dom.iterable"] 19 | }, 20 | "include": [ 21 | "components/**/*.ts", 22 | "components/**/*.tsx", 23 | "components/**/*.vue", 24 | "examples/**/*.ts", 25 | "examples/**/*.tsx", 26 | "examples/**/*.vue", 27 | "shims-vue.d.ts" 28 | ], 29 | "exclude": ["node_modules"] 30 | } 31 | -------------------------------------------------------------------------------- /mock/delete.json: -------------------------------------------------------------------------------- 1 | {"result":3,"success":true} -------------------------------------------------------------------------------- /mock/modify.json: -------------------------------------------------------------------------------- 1 | {"result":{"id":3,"name":"张三","age":18,"title":"cto"},"success":true} -------------------------------------------------------------------------------- /mocks/allApi.json: -------------------------------------------------------------------------------- 1 | { 2 | "apis": { 3 | "user": [ 4 | { 5 | "method": "GET", 6 | "domain": "http://x100.com", 7 | "path": "/order/v1/getlist", 8 | "name": "getList", 9 | "description": "查询订单列表", 10 | "requestParams": { 11 | "page_no": 0, 12 | "count": 10 13 | } 14 | }, 15 | { 16 | "method": "POST", 17 | "path": "/order/v1/submit", 18 | "name": "create", 19 | "description": "提交新的订单" 20 | } 21 | ] 22 | } 23 | } -------------------------------------------------------------------------------- /mocks/api.json: -------------------------------------------------------------------------------- 1 | { 2 | "getList": [ 3 | { 4 | "id": 1, 5 | "title": "test get request", 6 | "author": "menndy" 7 | }, 8 | { 9 | "id": 2, 10 | "title": "test get request1", 11 | "author": "menndy1" 12 | } 13 | ], 14 | "getDetails": { 15 | "title": "fe-code", 16 | "comments": 4343 17 | }, 18 | "login": [ 19 | { 20 | "username": "menndy", 21 | "currentPage": 123456, 22 | "id": 1 23 | } 24 | ] 25 | } -------------------------------------------------------------------------------- /mocks/apiConfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.0.0", 3 | "apis": { 4 | "userinfo": [ 5 | { 6 | "method": "GET", 7 | "path": "/userinfo/get", 8 | "description": "获取用户信息", 9 | "requestParams": { 10 | "uid": { 11 | "required": true, 12 | "type": "string", 13 | "description": "用户id", 14 | "example": "u37as29sJDsjakkJDJdd2", 15 | "in": "query" 16 | } 17 | }, 18 | "response": { 19 | "successful": { 20 | "required": true, 21 | "type": "boolean" 22 | } 23 | } 24 | }, 25 | { 26 | "method": "POST", 27 | "path": "/userinfo/create", 28 | "description": "创建用户信息", 29 | "requestParams": { 30 | "name": { 31 | "required": true, 32 | "type": "string", 33 | "description": "用户名字", 34 | "example": "menndy", 35 | "in": "body" 36 | }, 37 | "age": { 38 | "required": true, 39 | "type": "number", 40 | "description": "用户年龄", 41 | "example": 18, 42 | "in": "body" 43 | } 44 | }, 45 | "response": { 46 | "successful": { 47 | "required": true, 48 | "type": "boolean" 49 | } 50 | } 51 | } 52 | ], 53 | "store": [ 54 | { 55 | "method": "GET", 56 | "path": "/store/get", 57 | "description": "获取商品信息", 58 | "requestParams": { 59 | "id": { 60 | "required": true, 61 | "type": "string", 62 | "description": "商品id", 63 | "example": "u37as29sJDsjakkJDJdd2", 64 | "in": "query" 65 | }, 66 | "date": { 67 | "required": true, 68 | "type": "string", 69 | "description": "时间", 70 | "example": "2021.8.17", 71 | "in": "query" 72 | } 73 | }, 74 | "response": { 75 | "successful": { 76 | "required": true, 77 | "type": "boolean" 78 | } 79 | } 80 | } 81 | ] 82 | } 83 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fe-code", 3 | "repository": { 4 | "type": "git", 5 | "url": "https://github.com/lgwebdream/fe-code.git" 6 | }, 7 | "publishConfig": { 8 | "registry":"https://registry.npmjs.org" 9 | }, 10 | "version": "1.0.1", 11 | "description": "", 12 | "main": "index.js", 13 | "bin": { 14 | "fe-code": "bin/fe" 15 | }, 16 | "scripts": { 17 | "prepare": "husky install", 18 | "mock": "json-server --watch ./mocks/api.json", 19 | "eslint": "lint-staged", 20 | "fix": "eslint . --fix --ext .js,.jsx,.ts,.tsx", 21 | "test": "jest" 22 | }, 23 | "keywords": [], 24 | "author": "", 25 | "license": "ISC", 26 | "dependencies": { 27 | "@darkobits/lolcatjs": "^3.1.5", 28 | "ajv": "^8.6.2", 29 | "antd": "^4.16.7", 30 | "axios": "^1.6.4", 31 | "camelcase": "^6.2.0", 32 | "chalk": "^4.1.1", 33 | "commander": "^5.1.0", 34 | "cosmiconfig": "^7.0.0", 35 | "download-git-repo": "^3.0.2", 36 | "eslint-plugin-vue": "^7.14.0", 37 | "figlet": "^1.4.0", 38 | "fs-extra": "^10.0.0", 39 | "handlebars": "^4.7.7", 40 | "https-proxy-agent": "^5.0.0", 41 | "inquirer": "^7.1.0", 42 | "jest": "^28.0.0", 43 | "js-yaml": "^4.1.0", 44 | "json-schema-ref-parser": "^9.0.9", 45 | "json-server": "^0.17.1", 46 | "ora": "^4.0.4", 47 | "promise-fs": "^2.1.1", 48 | "quicktype-core": "^6.0.70", 49 | "shelljs": "^0.8.4" 50 | }, 51 | "devDependencies": { 52 | "@commitlint/cli": "^12.1.4", 53 | "@commitlint/config-conventional": "^12.1.4", 54 | "@types/react": "^17.0.14", 55 | "@typescript-eslint/eslint-plugin": "^4.28.3", 56 | "babel-eslint": "^10.1.0", 57 | "eslint": "^7.30.0", 58 | "eslint-config-airbnb-base": "^14.2.1", 59 | "eslint-config-airbnb-typescript": "^12.3.1", 60 | "eslint-config-prettier": "^8.3.0", 61 | "eslint-plugin-import": "^2.23.4", 62 | "eslint-plugin-jsx-a11y": "^6.4.1", 63 | "eslint-plugin-prettier": "^3.4.0", 64 | "eslint-plugin-react": "^7.24.0", 65 | "eslint-plugin-react-hooks": "^4.2.0", 66 | "husky": "^7.0.0", 67 | "lint-staged": "^11.0.0", 68 | "prettier": "^2.3.2", 69 | "typescript": "^4.3.5" 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /scripts/api2code.js: -------------------------------------------------------------------------------- 1 | const inquirer = require('inquirer'); 2 | const generateInterface = require('../lib/api2code/generateInterface'); 3 | const generateCrud = require('../lib/api2code/generateCrud'); 4 | const { parserMap } = require('../lib/api2code/parser'); 5 | const { removeEmpty } = require('../lib/utils'); 6 | const { languages } = require('../lib/utils/constants'); 7 | 8 | const handleTargetMap = { 9 | interface: generateInterface, 10 | crud: generateCrud, 11 | }; 12 | 13 | // main questions 14 | const promptList = [ 15 | { 16 | type: 'list', 17 | name: 'target', 18 | message: 'Please select the type of generation', 19 | choices: Object.keys(handleTargetMap), 20 | }, 21 | // { 22 | // type: 'confirm', 23 | // name: 'skip', 24 | // message: 'skip all the files generated before ? ', 25 | // }, 26 | { 27 | type: 'list', 28 | name: 'jsonType', 29 | message: 'please select the type of json', 30 | choices: Object.keys(parserMap), 31 | when: ({ target }) => target === 'crud', 32 | }, 33 | { 34 | type: 'list', 35 | name: 'language', 36 | message: 'Please select the coding language you used', 37 | choices: Object.keys(languages), 38 | when: ({ target }) => target === 'crud', 39 | }, 40 | // { 41 | // type: 'list', 42 | // name: 'requestLib', 43 | // message: 'Please select the request library you used', 44 | // choices: ['axios', 'fetch' /** graphQL */], 45 | // when: ({ target }) => target === 'crud', 46 | // }, 47 | // { 48 | // type: 'list', 49 | // name: 'codeStyle', 50 | // message: 'Please select the style for code', 51 | // choices: ['code-snippets', 'service'], 52 | // when: ({ target }) => target === 'crud', 53 | // }, 54 | ]; 55 | 56 | // main program 57 | const api2code = program => { 58 | program 59 | .command('api2code') 60 | .alias('a2c') 61 | .description('🌽 api translation typescript') 62 | // .option('-u, --url ', 'api addres(domain or ip)', config.request.url) 63 | // .option('-p, --path ', 'api path') 64 | .requiredOption('-i, --input ', 'input json file') 65 | .requiredOption('-o, --output ', 'output code file') 66 | .action(options => { 67 | const { output, input } = options; 68 | 69 | inquirer.prompt(promptList).then(({ target, ...props }) => { 70 | handleTargetMap[target]( 71 | removeEmpty({ 72 | // url, 73 | // path, 74 | output, 75 | input, 76 | ...props, 77 | }), 78 | ); 79 | }); 80 | }); 81 | }; 82 | module.exports = api2code; 83 | -------------------------------------------------------------------------------- /tsconfig.eslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "lib": ["esnext", "dom"], 5 | "allowJs": true, 6 | "module": "esnext", 7 | "strict": true, 8 | "moduleResolution": "node", 9 | "isolatedModules": false, 10 | "removeComments": false, 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "forceConsistentCasingInFileNames": false, 14 | "experimentalDecorators": true, 15 | "noImplicitAny": false, 16 | "noUnusedParameters": false, 17 | "noImplicitReturns": true, 18 | "noImplicitThis": true, 19 | "noUnusedLocals": false, 20 | "skipLibCheck": false, 21 | "strictNullChecks": false, 22 | "suppressImplicitAnyIndexErrors": true, 23 | "downlevelIteration": true, 24 | "importHelpers": true, 25 | "sourceMap": false, 26 | "checkJs": false, 27 | }, 28 | "exclude": ["node_modules"], 29 | "compileOnSave": false, 30 | "include": ["**/**.ts", "**/**.tsx", "**/**.js", "**/**/**/*.ts","**/**/**/*.jsx"], 31 | } -------------------------------------------------------------------------------- /utils.js: -------------------------------------------------------------------------------- 1 | const { writeJsonSync } = require('fs-extra'); 2 | const shell = require('shelljs'); 3 | const { componentDependencies } = require('./dependencies.config'); 4 | /** 5 | * patch dependencies to package.json 6 | * @param packagesPath{string} package path 7 | * @param main{string} react/vue 8 | * @return {void} 9 | */ 10 | module.exports.patchPackageJson = ({ packagesPath, main }) => { 11 | const packages = require(packagesPath); 12 | const ds = componentDependencies[main]; 13 | ds && 14 | Reflect.ownKeys(ds).forEach(item => { 15 | packages[item] = componentDependencies[item]; 16 | }) && 17 | writeJsonSync(packagesPath, packages); 18 | }; 19 | /** 20 | * transform array data to object with truly value 21 | * @param arr{Array} 22 | * @return {Object} 23 | */ 24 | module.exports.transformArr2TrueObj = arr => 25 | arr.reduce((pre, cur) => ({ ...pre, ...{ [cur]: true } }), {}); 26 | 27 | /** 28 | * format code 29 | * @param src{string} absolute path 30 | * @return {number} 31 | */ 32 | module.exports.formatCode = src => { 33 | return shell.exec(`npx prettier --loglevel error --write ${src} `).code; 34 | }; 35 | --------------------------------------------------------------------------------