[];
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 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 | 查询
13 | 重置
14 |
15 |
16 |
17 |
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 |
2 |
3 |
4 |
5 |
6 |
7 |
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 |
2 |
3 | 2222
4 | button
5 |
6 |
7 |
8 |
16 |
17 |
19 |
--------------------------------------------------------------------------------
/examples/vue3-demo/src/views/TableDemo.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
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 |
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 ();
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 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 | 查询
21 | 重置
22 |
23 |
24 |
25 |
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 |
2 |
3 |
4 |
5 |
6 |
7 |
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 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 | 查询
21 | 重置
22 |
23 |
24 |
25 |
26 |
27 |
72 |
73 |
78 |
--------------------------------------------------------------------------------
/lib/vue3/components/crud.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
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