├── .editorconfig
├── .fatherrc.ts
├── .gitignore
├── .prettierignore
├── .prettierrc
├── CONTRIBUTING.md
├── README.md
├── example
├── .editorconfig
├── .env
├── .eslintignore
├── .eslintrc.js
├── .gitignore
├── .prettierignore
├── .prettierrc.js
├── config
│ ├── config.ts
│ └── routeConfig.ts
├── mock
│ └── route.ts
├── package.json
├── src
│ ├── constant.ts
│ ├── layouts
│ │ ├── BlankLayout
│ │ │ └── index.tsx
│ │ └── LoginLayout
│ │ │ └── index.tsx
│ ├── pages
│ │ ├── 404.tsx
│ │ ├── index.css
│ │ ├── index.tsx
│ │ └── login
│ │ │ └── index.tsx
│ └── services
│ │ └── api-lock.json
├── tsconfig.json
└── typing.d.ts
├── interfaces
├── api.ts
├── common.ts
└── screen.ts
├── package.json
├── src
├── api
│ └── index.tsx
├── constantConfig.ts
├── index.ts
├── manage
│ ├── index.ts
│ └── templates
│ │ ├── formActionMethods.ts
│ │ ├── formActionMethodsModal.ts
│ │ ├── index.ts
│ │ ├── longDetail.ts
│ │ ├── longDetailModal.ts
│ │ ├── longForm.ts
│ │ ├── longFormModal.ts
│ │ ├── shortDetail.ts
│ │ ├── shortDetailModal.ts
│ │ ├── shortForm.ts
│ │ ├── shortFormModal.ts
│ │ ├── table.ts
│ │ └── util.ts
├── screen
│ ├── index.ts
│ └── templates
│ │ ├── generateCol.ts
│ │ ├── generateLayout.ts
│ │ ├── generateRow.ts
│ │ └── generateScreen.ts
└── utils
│ ├── index.ts
│ ├── removeUnusedImport.ts
│ ├── writeNewMenu.ts
│ └── writeNewRoute.ts
├── tsconfig.json
├── ui
├── components
│ ├── ConfigActions
│ │ └── index.tsx
│ ├── FormItemConfig
│ │ ├── index.module.less
│ │ └── index.tsx
│ ├── FormItemConfigDrawer
│ │ ├── index.tsx
│ │ └── props.ts
│ ├── FormItemsDrawer
│ │ └── index.tsx
│ └── Title
│ │ ├── index.module.less
│ │ └── index.tsx
├── hooks
│ ├── useCard.ts
│ ├── useConfig.ts
│ ├── useConfigVisible.ts
│ ├── useFormItem.ts
│ ├── useScreen.ts
│ └── useTable.ts
├── index.tsx
├── pages
│ ├── manage
│ │ ├── Context.ts
│ │ ├── components
│ │ │ ├── ConstantConfigAction
│ │ │ │ ├── index.module.less
│ │ │ │ └── index.tsx
│ │ │ ├── Dashboard
│ │ │ │ ├── index.module.less
│ │ │ │ └── index.tsx
│ │ │ ├── ExportActions
│ │ │ │ ├── index.module.less
│ │ │ │ └── index.tsx
│ │ │ ├── ImportAction
│ │ │ │ ├── index.module.less
│ │ │ │ └── index.tsx
│ │ │ ├── PathMenuAction
│ │ │ │ ├── index.module.less
│ │ │ │ └── index.tsx
│ │ │ ├── TemplateList
│ │ │ │ ├── index.module.less
│ │ │ │ ├── index.tsx
│ │ │ │ └── template.json
│ │ │ ├── content
│ │ │ │ ├── LongDetailContent
│ │ │ │ │ ├── index.module.less
│ │ │ │ │ └── index.tsx
│ │ │ │ ├── LongDetailModalContent
│ │ │ │ │ ├── index.module.less
│ │ │ │ │ └── index.tsx
│ │ │ │ ├── LongFormContent
│ │ │ │ │ └── index.tsx
│ │ │ │ ├── LongFormModalContent
│ │ │ │ │ └── index.tsx
│ │ │ │ ├── ShortDetailContent
│ │ │ │ │ ├── index.module.less
│ │ │ │ │ └── index.tsx
│ │ │ │ ├── ShortDetailModalContent
│ │ │ │ │ ├── index.module.less
│ │ │ │ │ └── index.tsx
│ │ │ │ ├── ShortFormContent
│ │ │ │ │ └── index.tsx
│ │ │ │ ├── ShortFormModalContent
│ │ │ │ │ └── index.tsx
│ │ │ │ └── TableContent
│ │ │ │ │ ├── TitleWithActions
│ │ │ │ │ └── index.tsx
│ │ │ │ │ └── index.tsx
│ │ │ └── drawers
│ │ │ │ ├── ApiConfigDrawer
│ │ │ │ └── index.tsx
│ │ │ │ ├── CardConfigDrawer
│ │ │ │ └── index.tsx
│ │ │ │ ├── ShortFormConfigDrawer
│ │ │ │ └── index.tsx
│ │ │ │ ├── TableColumnConfigDrawer
│ │ │ │ └── index.tsx
│ │ │ │ └── TableConfigDrawer
│ │ │ │ └── index.tsx
│ │ ├── index.module.less
│ │ └── index.tsx
│ ├── mobile
│ │ ├── Context.ts
│ │ └── index.tsx
│ └── screen
│ │ ├── ColConfigDrawer
│ │ ├── index.tsx
│ │ └── props.ts
│ │ ├── Context.ts
│ │ ├── Dashboard
│ │ ├── index.module.less
│ │ └── index.tsx
│ │ ├── ScreenConfigDrawer
│ │ └── index.tsx
│ │ ├── helper.tsx
│ │ ├── index.tsx
│ │ ├── preview.ts
│ │ └── structure.ts
└── utils
│ └── index.ts
└── yarn.lock
/.editorconfig:
--------------------------------------------------------------------------------
1 | # http://editorconfig.org
2 | root = true
3 |
4 | [*]
5 | indent_style = space
6 | indent_size = 2
7 | end_of_line = lf
8 | charset = utf-8
9 | trim_trailing_whitespace = true
10 | insert_final_newline = true
11 |
12 | [*.md]
13 | trim_trailing_whitespace = false
14 |
15 | [Makefile]
16 | indent_style = tab
17 |
--------------------------------------------------------------------------------
/.fatherrc.ts:
--------------------------------------------------------------------------------
1 | const isProd = process.env.NODE_ENV === 'prod';
2 |
3 | export default [
4 | {
5 | target: 'node',
6 | cjs: { type: 'babel', lazy: true },
7 | disableTypeCheck: true,
8 | extraBabelPlugins: [
9 | [
10 | 'babel-plugin-import',
11 | { libraryName: 'antd', libraryDirectory: 'es', style: true },
12 | 'antd',
13 | ],
14 | ],
15 | },
16 | {
17 | entry: 'ui/index.tsx',
18 | umd: {
19 | name: 'tasks',
20 | minFile: isProd,
21 | sourcemap: !isProd,
22 | },
23 | extraExternals: ['antd', 'react', 'react-dom', 'xterm'],
24 | typescriptOpts: {
25 | check: false,
26 | globals: {
27 | antd: 'window.antd',
28 | react: 'window.React',
29 | 'react-dom': 'window.ReactDOM',
30 | },
31 | },
32 | },
33 | ];
34 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .changelog
3 | .umi
4 | .umi-test
5 | .umi-production
6 | .DS_Store
7 | /lib
8 | dist
9 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | **/node_modules/**
2 | package.json
3 |
4 | # fixtures
5 | **/fixtures/**
6 |
7 | # templates
8 | **/templates/**
9 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "printWidth": 100,
3 | "singleQuote": true,
4 | "trailingComma": "all",
5 | "proseWrap": "never"
6 | }
7 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing to plugin
2 |
3 | ## Set up
4 |
5 | Install dev deps after git clone the repo.
6 |
7 | ```bash
8 | # npm is not allowed.
9 | $ yarn
10 | ```
11 |
12 | ## Build
13 |
14 | Transform with babel and rollup.
15 |
16 | ```bash
17 | $ yarn build
18 |
19 | # Build and monitor file changes
20 | $ yarn build --watch
21 |
22 | ```
23 |
24 | ## Dev Plugin
25 |
26 | ```bash
27 | # This Step must only be executed in Build
28 | $ yarn dev
29 | ```
30 |
31 | ## Debug
32 |
33 | TODO
34 |
35 | ## Test
36 |
37 | ```bash
38 | $ yarn test
39 | ```
40 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # umi-plugin-page-creator
2 |
3 | [](https://npmjs.org/package/umi-plugin-page-creator) [](https://npmjs.org/package/umi-plugin-page-creator)
4 |
5 | ## Install
6 |
7 | ```bash
8 | # or npm install
9 | $ yarn add umi-plugin-page-creator -D
10 | ```
11 |
12 | ## Development UI
13 |
14 | UI mini start:
15 |
16 | ```bash
17 | $ yarn watch
18 | $ yarn start
19 | ```
20 |
21 |
22 |
23 | ## Usage
24 |
25 | Umi会自动识别以umi-plugin-*开头的插件,并自动加载。不需要额外的配置
26 |
27 | ## LICENSE
28 |
29 | MIT
30 |
--------------------------------------------------------------------------------
/example/.editorconfig:
--------------------------------------------------------------------------------
1 | # http://editorconfig.org
2 | root = true
3 |
4 | [*]
5 | indent_style = space
6 | indent_size = 2
7 | end_of_line = lf
8 | charset = utf-8
9 | trim_trailing_whitespace = true
10 | insert_final_newline = true
11 |
12 | [*.md]
13 | trim_trailing_whitespace = false
14 |
15 | [Makefile]
16 | indent_style = tab
17 |
--------------------------------------------------------------------------------
/example/.env:
--------------------------------------------------------------------------------
1 | BROWSER=none
2 | ESLINT=1
3 |
--------------------------------------------------------------------------------
/example/.eslintignore:
--------------------------------------------------------------------------------
1 | /scripts
2 | **/node_modules/**
3 | _scripts
4 | /lambda/mock/**
5 | _test_
6 |
--------------------------------------------------------------------------------
/example/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | parser: "@typescript-eslint/parser", // Specifies the ESLint parser
3 | parserOptions: {
4 | ecmaVersion: 2020, // Allows for the parsing of modern ECMAScript features
5 | sourceType: "module", // Allows for the use of imports
6 | ecmaFeatures: {
7 | jsx: true // Allows for the parsing of JSX
8 | }
9 | },
10 | settings: {
11 | react: {
12 | version: "detect" // Tells eslint-plugin-react to automatically detect the version of React to use
13 | }
14 | },
15 | extends: [
16 | "plugin:react/recommended", // Uses the recommended rules from @eslint-plugin-react
17 | "plugin:@typescript-eslint/recommended", // Uses the recommended rules from the @typescript-eslint/eslint-plugin
18 | "prettier/@typescript-eslint", // Uses eslint-config-prettier to disable ESLint rules from @typescript-eslint/eslint-plugin that would conflict with prettier
19 | "plugin:prettier/recommended" // Enables eslint-plugin-prettier and eslint-config-prettier. This will display prettier errors as ESLint errors. Make sure this is always the last configuration in the extends array.
20 | ],
21 | plugins: ['import'],
22 | rules: {
23 | 'prettier/prettier': 'off',
24 | // Place to specify ESLint rules. Can be used to overwrite rules specified from the extended configs
25 | // e.g. "@typescript-eslint/explicit-function-return-type": "off",
26 | 'import/newline-after-import': 'error',
27 | 'react/display-name': 'off',
28 | '@typescript-eslint/explicit-module-boundary-types': 'off',
29 | 'react/prop-types': 'off',
30 | },
31 | };
32 |
--------------------------------------------------------------------------------
/example/.gitignore:
--------------------------------------------------------------------------------
1 | /.umi
2 | /.umi-production
3 | /dist
4 | /node_modules
5 | /yarn.lock
6 |
--------------------------------------------------------------------------------
/example/.prettierignore:
--------------------------------------------------------------------------------
1 | **/*.svg
2 | package.json
3 | .umi
4 | .umi-production
5 | /dist
6 | .dockerignore
7 | .DS_Store
8 | .eslintignore
9 | *.png
10 | *.toml
11 | docker
12 | .editorconfig
13 | Dockerfile*
14 | .gitignore
15 | .prettierignore
16 | LICENSE
17 | .eslintcache
18 | *.lock
19 | yarn-error.log
20 | .history
21 | CNAME
22 | /build
23 | umi-block.json
24 |
--------------------------------------------------------------------------------
/example/.prettierrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | semi: true,
3 | trailingComma: "all",
4 | singleQuote: true,
5 | printWidth: 120,
6 | tabWidth: 2
7 | };
8 |
--------------------------------------------------------------------------------
/example/config/config.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * @文件描述:
3 | * @公司: thundersdata
4 | * @作者: 陈杰
5 | * @Date: 2020-05-07 11:07:13
6 | * @LastEditors: 陈杰
7 | * @LastEditTime: 2020-05-07 11:22:12
8 | */
9 | import { defineConfig } from 'umi';
10 | import routeConfig from './routeConfig';
11 |
12 | export default defineConfig({
13 | presets: [require.resolve('@umijs/preset-ui')],
14 | plugins: [require.resolve('../../lib')],
15 | routes: routeConfig,
16 | });
17 |
--------------------------------------------------------------------------------
/example/config/routeConfig.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * @文件描述:
3 | * @公司: thundersdata
4 | * @作者: 陈杰
5 | * @Date: 2020-05-07 11:41:44
6 | * @LastEditors: 陈杰
7 | * @LastEditTime: 2020-05-20 12:04:03
8 | */
9 | export default [
10 | {
11 | path: '/user',
12 | component: '../layouts/LoginLayout',
13 | routes: [
14 | {
15 | path: '/user/login',
16 | component: './login',
17 | title: '登录',
18 | },
19 | ],
20 | },
21 | {
22 | path: '/',
23 | component: '../layouts/BlankLayout',
24 | routes: [
25 | {
26 | path: '/homepage',
27 | component: './index',
28 | },
29 | {
30 | path: '/',
31 | redirect: '/homepage',
32 | },
33 | {
34 | path: '*',
35 | component: './404',
36 | },
37 | ],
38 | },
39 | ];
40 |
--------------------------------------------------------------------------------
/example/mock/route.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * @文件描述:
3 | * @公司: thundersdata
4 | * @作者: 陈杰
5 | * @Date: 2019-10-27 16:26:45
6 | * @LastEditors: 陈杰
7 | * @LastEditTime: 2020-05-20 11:10:41
8 | */
9 | export default {
10 | '/resource': {
11 | success: true,
12 | data: [],
13 | code: 20000,
14 | message: '成功',
15 | },
16 | };
17 |
--------------------------------------------------------------------------------
/example/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "example",
3 | "version": "1.0.0",
4 | "description": "",
5 | "license": "MIT",
6 | "scripts": {
7 | "start": "umi dev",
8 | "eslint:fix": "eslint --fix --ext .ts,.tsx src/"
9 | },
10 | "dependencies": {
11 | "react": "^16.13.1",
12 | "react-dom": "^16.13.1"
13 | },
14 | "devDependencies": {
15 | "@types/react": "^16.9.35",
16 | "@types/react-dom": "^16.9.8",
17 | "@typescript-eslint/eslint-plugin": "^3.1.0",
18 | "@typescript-eslint/parser": "^3.1.0",
19 | "eslint": "^7.1.0",
20 | "eslint-config-prettier": "^6.11.0",
21 | "eslint-plugin-import": "^2.20.2",
22 | "eslint-plugin-prettier": "^3.1.3",
23 | "eslint-plugin-react": "^7.20.0",
24 | "prettier": "^2.0.5",
25 | "typescript": "^3.8.3"
26 | },
27 | "engines": {
28 | "node": ">=8.0.0"
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/example/src/constant.ts:
--------------------------------------------------------------------------------
1 | import test from 'ttt';
2 |
3 | const a = 123;
4 | const b = 555;
5 |
--------------------------------------------------------------------------------
/example/src/layouts/BlankLayout/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const BlankLayout: React.FC = (props) => (
4 |
5 |
layout header
6 | {props.children}
7 |
8 | );
9 |
10 | export default BlankLayout;
11 |
--------------------------------------------------------------------------------
/example/src/layouts/LoginLayout/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const LoginLayout: React.FC = (props) => (
4 |
5 |
login header
6 | {props.children}
7 |
8 | );
9 |
10 | export default LoginLayout;
11 |
--------------------------------------------------------------------------------
/example/src/pages/404.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export default () => 404
;
4 |
--------------------------------------------------------------------------------
/example/src/pages/index.css:
--------------------------------------------------------------------------------
1 | body,
2 | html,
3 | #root {
4 | height: 100%;
5 | margin: 0;
6 | }
7 |
8 | a {
9 | color: #1890ff;
10 | cursor: pointer;
11 | }
12 |
13 | .normal {
14 | background: #ddd;
15 | height: 100vh;
16 | padding: 48px;
17 | }
18 |
--------------------------------------------------------------------------------
/example/src/pages/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import styles from './index.css';
3 |
4 | export default () => Hello Umi!
;
5 |
--------------------------------------------------------------------------------
/example/src/pages/login/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export default () => login page
;
4 |
--------------------------------------------------------------------------------
/example/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "esnext",
4 | "module": "esnext",
5 | "moduleResolution": "node",
6 | "importHelpers": true,
7 | "jsx": "react",
8 | "esModuleInterop": true,
9 | "sourceMap": true,
10 | "baseUrl": "./",
11 | "allowJs": true,
12 | "lib": ["esnext", "dom"],
13 | "noEmit": true,
14 | "strict": true,
15 | "experimentalDecorators": true,
16 | "allowSyntheticDefaultImports": true,
17 | "isolatedModules": false,
18 | "forceConsistentCasingInFileNames": true,
19 | "noImplicitReturns": true,
20 | "noImplicitThis": true,
21 | "noImplicitAny": true,
22 | "noUnusedLocals": true,
23 | "noUnusedParameters": true,
24 | "strictNullChecks": true,
25 | "suppressImplicitAnyIndexErrors": true,
26 | "downlevelIteration": true,
27 | "resolveJsonModule": true,
28 | "strictPropertyInitialization": true,
29 | "skipLibCheck": true,
30 | "paths": {
31 | "@/*": ["src/*"],
32 | "@@/*": ["src/.umi/*"]
33 | }
34 | },
35 | "include": ["./src/**/*", "./typings.d.ts"],
36 | }
37 |
--------------------------------------------------------------------------------
/example/typing.d.ts:
--------------------------------------------------------------------------------
1 | declare module '*.css';
2 | declare module '*.less';
3 |
--------------------------------------------------------------------------------
/interfaces/api.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * @文件描述:
3 | * @公司: thundersdata
4 | * @作者: 陈杰
5 | * @Date: 2020-05-18 21:18:11
6 | * @LastEditors: 黄姗姗
7 | * @LastEditTime: 2020-05-22 15:29:38
8 | */
9 |
10 | /** api-lock.json的结构 */
11 | export interface ApiJSON {
12 | name: string;
13 | mods: ApiMod[];
14 | baseClasses: ApiClass[];
15 | }
16 |
17 | /** 模块 */
18 | export interface ApiMod {
19 | name: string;
20 | description: string;
21 | interfaces: ApiInterface[];
22 | }
23 |
24 | /** 模块下的接口 */
25 | export interface ApiInterface {
26 | consumes: string[];
27 | description: string;
28 | name: string;
29 | method: string;
30 | path: string;
31 | response: DataType;
32 | parameters: ApiProperty[];
33 | }
34 |
35 | export interface DataType {
36 | typeArgs: DataType[];
37 | typeName: string;
38 | isDefsType: boolean;
39 | }
40 |
41 | export interface ApiProperty {
42 | in?: string;
43 | description: string;
44 | name: string;
45 | required: boolean;
46 | dataType: DataType;
47 | }
48 |
49 | export interface ApiClass {
50 | name: string;
51 | properties: ApiProperty[];
52 | description?: string;
53 | required: boolean;
54 | }
55 |
56 | export interface BaseClass {
57 | name: string;
58 | dbId: string;
59 | description: string;
60 | properties: {
61 | label: string;
62 | value: string;
63 | required: boolean;
64 | }[];
65 | }
66 |
--------------------------------------------------------------------------------
/interfaces/common.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * @文件描述:
3 | * @公司: thundersdata
4 | * @作者: 陈杰
5 | * @Date: 2020-04-29 21:58:39
6 | * @LastEditors: 廖军
7 | * @LastEditTime: 2020-10-09 17:03:40
8 | */
9 | import { Store } from 'antd/lib/form/interface';
10 | import { ColumnType } from 'antd/lib/table';
11 |
12 | export type FormItemType =
13 | | 'input'
14 | | 'cascader'
15 | | 'date'
16 | | 'range'
17 | | 'time'
18 | | 'number'
19 | | 'radio'
20 | | 'checkbox'
21 | | 'switch'
22 | | 'slider'
23 | | 'select'
24 | | 'treeselect'
25 | | 'upload'
26 | | 'rate'
27 | | 'password'
28 | | 'textarea';
29 |
30 | /** 适用于input/date/time/number */
31 | export type FormItemProps = {
32 | type: FormItemType;
33 | name: string;
34 | label: string;
35 | placeholder?: string | [string, string];
36 | [key: string]: unknown;
37 | };
38 |
39 | export type TemplateType =
40 | | 'short-form'
41 | | 'long-form'
42 | | 'one-column-form-modal'
43 | | 'two-columns-form-modal'
44 | | 'short-detail'
45 | | 'long-detail'
46 | | 'one-column-detail-modal'
47 | | 'two-columns-detail-modal'
48 | | 'table';
49 |
50 | export type ShortFormConfig = {
51 | title: string;
52 | };
53 |
54 | export type Template = {
55 | name: string;
56 | type: TemplateType;
57 | image: string;
58 | };
59 |
60 | export type AjaxResponse = {
61 | success: boolean;
62 | data?: T;
63 | message: string;
64 | };
65 |
66 | export type CardItemProps = {
67 | title: string;
68 | formItems: FormItemProps[];
69 | };
70 |
71 | /** 导出/导入的json格式 */
72 | export type ConfigProps = {
73 | formConfig?: Store;
74 | formItems?: FormItemProps[];
75 | initialFetch?: string[];
76 | submitFetch?: string[];
77 | cards?: CardItemProps[];
78 | tableConfig?: Store;
79 | columns?: ColumnType[];
80 | };
81 |
82 | /**
83 | * 表字段验证配置
84 | */
85 | export interface TableVerificationRuleList {
86 | appVerificationRuleCode: string;
87 | tableCode: string;
88 | tableName: string;
89 | tableFieldCode: string;
90 | isRequired: boolean;
91 | requiredMaxLength: number;
92 | requiredMinLength: number;
93 | fieldName: string;
94 | fieldType: string;
95 | pattern: string;
96 | }
97 |
--------------------------------------------------------------------------------
/interfaces/screen.ts:
--------------------------------------------------------------------------------
1 | import { ColSize } from 'antd/lib/col';
2 |
3 | export interface ScreenConfig {
4 | title: string;
5 | titleStyle: string;
6 | gutter: number;
7 | left: {
8 | xs: ColSize;
9 | sm: ColSize;
10 | md: ColSize;
11 | lg: ColSize;
12 | xl: ColSize;
13 | xxl: ColSize;
14 | rows: ScreenRow[];
15 | };
16 | center: {
17 | xs: ColSize;
18 | sm: ColSize;
19 | md: ColSize;
20 | lg: ColSize;
21 | xl: ColSize;
22 | xxl: ColSize;
23 | rows: ScreenRow[];
24 | };
25 | right: {
26 | xs: ColSize;
27 | sm: ColSize;
28 | md: ColSize;
29 | lg: ColSize;
30 | xl: ColSize;
31 | xxl: ColSize;
32 | rows: ScreenRow[];
33 | };
34 | }
35 |
36 | export interface ScreenRow {
37 | height: number;
38 | cols: ScreenCol[];
39 | }
40 |
41 | export interface ScreenCol {
42 | xs?: ColSize;
43 | sm?: ColSize;
44 | md?: ColSize;
45 | lg?: ColSize;
46 | xl?: ColSize;
47 | xxl?: ColSize;
48 | type: ColType;
49 | chartConfig: ChartConfig;
50 | }
51 |
52 | export type LayoutType = 'left' | 'center' | 'right';
53 | export type ColType =
54 | | 'custom'
55 | | 'bar'
56 | | 'groupBar'
57 | | 'rangeBar'
58 | | 'barLine'
59 | | 'groupBarLine'
60 | | 'column'
61 | | 'groupColumn'
62 | | 'rangeColumn'
63 | | 'circle'
64 | | 'rose'
65 | | 'line'
66 | | 'wave'
67 | | 'radar'
68 | | 'circleStackBar'
69 | | 'scatter'
70 | | 'stackArea'
71 | | 'stackBar'
72 | | 'stackRose'
73 | | 'waterfall'
74 | | 'map'
75 | | 'table'
76 | | 'rank';
77 |
78 | export interface ChartConfig {
79 | [key: string]: unknown;
80 | }
81 |
82 | export interface ScreenColConfig {
83 | layoutType: LayoutType;
84 | rowIndex: number;
85 | colIndex: number;
86 | config: Partial;
87 | }
88 |
89 | export interface ScreenConfigPayload {
90 | title: string;
91 | titleStyle: object;
92 | gutter: number;
93 | layout: ScreenConfigPayloadLayout[];
94 | }
95 |
96 | export interface ScreenConfigPayloadLayout {
97 | name: string;
98 | xs?: ColSize;
99 | sm?: ColSize;
100 | md?: ColSize;
101 | lg?: ColSize;
102 | xl?: ColSize;
103 | xxl?: ColSize;
104 | rows: ScreenConfigPayloadRow[];
105 | }
106 |
107 | export interface ScreenConfigPayloadRow {
108 | name: string;
109 | height: number;
110 | cols: ScreenConfigPayloadCol[];
111 | }
112 |
113 | export interface ScreenConfigPayloadCol {
114 | name: string;
115 | xs?: ColSize;
116 | sm?: ColSize;
117 | md?: ColSize;
118 | lg?: ColSize;
119 | xl?: ColSize;
120 | xxl?: ColSize;
121 | type: ColType;
122 | chartConfig: ChartConfig;
123 | }
124 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "umi-plugin-page-creator",
3 | "version": "1.7.3",
4 | "description": "",
5 | "authors": {
6 | "name": "chenjie",
7 | "email": "chenjie@thundersdata.com"
8 | },
9 | "repository": "/umi-plugin-page-creator",
10 | "main": "lib/index.js",
11 | "scripts": {
12 | "start": "cross-env UMI_UI=1 APP_ROOT=example umi dev",
13 | "watch": "father-build --watch",
14 | "build": "rm -rf dist lib && cross-env NODE_ENV=prod father-build",
15 | "ui:build": "ui-build",
16 | "prettier": "prettier --parser=typescript --write '{src,ui}/**/*.ts?(x)'",
17 | "test": "umi-test",
18 | "test:coverage": "umi-test --coverage",
19 | "test:update": "umi-test --updateSnapshot"
20 | },
21 | "lint-staged": {
22 | "*.ts?(x)": [
23 | "git add"
24 | ]
25 | },
26 | "devDependencies": {
27 | "@testing-library/react": "^9.4.0",
28 | "@testing-library/react-hooks": "^3.2.1",
29 | "@types/classnames": "^2.2.10",
30 | "@types/codemirror": "^0.0.95",
31 | "@types/faker": "^4.1.11",
32 | "@types/jest": "^25.1.3",
33 | "@types/node": "^13.7.7",
34 | "@umijs/fabric": "^2.0.8",
35 | "@umijs/preset-ui": "^2.1.13",
36 | "@umijs/test": "^3.0.10",
37 | "@umijs/test-utils": "^1.0.0",
38 | "@umijs/ui-builder": "^0.0.5",
39 | "@umijs/ui-types": "^2.0.0-beta.2",
40 | "antd": "^4.2.0",
41 | "body-parser": "^1.18.2",
42 | "classnames": "^2.2.6",
43 | "codemirror": "^5.54.0",
44 | "copy-to-clipboard": "^3.3.1",
45 | "cross-env": "^6.0.3",
46 | "express": "^4.15.3",
47 | "faker": "^4.1.0",
48 | "father-build": "^1.17.2",
49 | "immer": "^6.0.5",
50 | "lerna": "^3.20.2",
51 | "lint-staged": "^10.0.8",
52 | "npm-run-all": "^4.1.5",
53 | "pify": "^5.0.0",
54 | "puppeteer": "^1.20.0",
55 | "query-string": "^6.11.1",
56 | "react": "^16.12.0",
57 | "react-codemirror2": "^7.2.0",
58 | "react-dom": "^16.12.0",
59 | "react-test-renderer": "^16.9.0",
60 | "test-umi-plugin": "^0.1.0",
61 | "typescript": "^3.9.2",
62 | "umi": "^3.1.0",
63 | "use-immer": "^0.4.0",
64 | "yorkie": "^2.0.0"
65 | },
66 | "gitHooks": {
67 | "pre-commit": "lint-staged"
68 | },
69 | "files": [
70 | "dist",
71 | "lib"
72 | ]
73 | }
74 |
--------------------------------------------------------------------------------
/src/api/index.tsx:
--------------------------------------------------------------------------------
1 | import { IApi } from 'umi';
2 | import { existsSync } from 'fs';
3 | import { ApiJSON } from '../../interfaces/api';
4 |
5 | /**
6 | * 根据项目根目录下的services/api-lock.json,生成一个接口和声明的关联文件。
7 | * 在UI侧选择某个接口,
8 | */
9 | export default function(api: IApi) {
10 | const jsonPath = api.paths.absSrcPath + '/services/api-lock.json';
11 | if (!existsSync(jsonPath)) {
12 | return {
13 | databases: null,
14 | // mods: null,
15 | baseClasses: null,
16 | };
17 | }
18 | const apiJson: ApiJSON[] = require(jsonPath);
19 |
20 | const databases = apiJson.map(db => ({
21 | label: db.name,
22 | value: db.name,
23 | children: db.mods.map(mod => ({
24 | label: mod.description,
25 | value: mod.name,
26 | children: mod.interfaces.map(({ name, response, description, method, parameters }) => {
27 | // 提交表单数据时的DTO
28 | const paramsName = parameters.find(param => param.in === 'body')?.dataType.typeName;
29 | // 获取数据时的DTO
30 | let responseName;
31 | if (response.typeArgs.length > 0) {
32 | responseName = response.typeArgs.find(arg => arg.isDefsType)?.typeName;
33 | } else {
34 | if (response.isDefsType) {
35 | responseName = response.typeName;
36 | }
37 | }
38 | const value = `${name}-${paramsName}-${responseName}`;
39 | return {
40 | label: `${description}(${method})`,
41 | value,
42 | };
43 | }),
44 | })),
45 | }));
46 |
47 | const mods = apiJson.reduce(
48 | (accu, curr) =>
49 | accu.concat(
50 | curr.mods.map(mod => ({
51 | name: mod.name,
52 | description: mod.description,
53 | dbId: curr.name,
54 | })) as [],
55 | ),
56 | [],
57 | );
58 |
59 | const baseClasses = apiJson.reduce(
60 | (accu, curr) =>
61 | accu.concat(
62 | curr.baseClasses.map(mod => ({
63 | name: mod.name,
64 | dbId: curr.name,
65 | description: mod.description || '',
66 | properties: mod.properties.map(prop => ({
67 | label: prop.description,
68 | value: prop.name,
69 | required: prop.required,
70 | })),
71 | })) as [],
72 | ),
73 | [],
74 | );
75 |
76 | return {
77 | databases,
78 | // mods,
79 | baseClasses,
80 | };
81 | }
82 |
--------------------------------------------------------------------------------
/src/constantConfig.ts:
--------------------------------------------------------------------------------
1 | import { IApi } from 'umi';
2 | import { existsSync, writeFileSync, readFileSync } from 'fs';
3 | import * as types from '@babel/types';
4 | import * as parser from '@babel/parser';
5 | import generate from '@babel/generator';
6 |
7 | /**
8 | * 加载常量文件的内容
9 | * @param api
10 | */
11 | export function getConstantConfig(api: IApi) {
12 | const constantFilePath = api.paths.absSrcPath + '/constant.ts';
13 | if (!existsSync(constantFilePath)) {
14 | writeFileSync(constantFilePath, '', 'utf-8');
15 | return '';
16 | } else {
17 | const ast: types.File = parser.parse(readFileSync(constantFilePath, 'utf-8'), {
18 | sourceType: 'module',
19 | plugins: ['typescript'],
20 | });
21 | return generate(ast, {}).code;
22 | }
23 | }
24 |
25 | /**
26 | * 保存修改之后的常量配置
27 | * @param api
28 | * @param code
29 | */
30 | export function saveConstantConfig(api: IApi, code: string) {
31 | const constantFilePath = api.paths.absSrcPath + '/constant.ts';
32 | writeFileSync(constantFilePath, code, 'utf-8');
33 | }
34 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * @文件描述:
3 | * @公司: thundersdata
4 | * @作者: 陈杰
5 | * @Date: 2020-04-29 10:38:23
6 | * @LastEditors: 黄姗姗
7 | * @LastEditTime: 2020-05-22 17:21:04
8 | */
9 | // ref:
10 | // - https://umijs.org/plugin/develop.html
11 | import { IApi } from '@umijs/types';
12 | import { join } from 'path';
13 |
14 | import generatePage from './manage';
15 | import generateApi from './api';
16 | import generateScreen from './screen';
17 | import {getConstantConfig, saveConstantConfig} from './constantConfig'
18 |
19 | export default function(api: IApi) {
20 | let mods = [];
21 |
22 | // @ts-ignore
23 | api.addUIPlugin(() => join(__dirname, '../dist/index.umd.js'));
24 |
25 | // @ts-ignore
26 | api.onUISocket(({ action, failure, success }) => {
27 | const { type, payload = {} } = action;
28 | if (type.includes('screen')) {
29 | const result = generateScreen(payload, api);
30 | if (result) {
31 | success({ success: true, message: '恭喜你,文件创建成功' });
32 | } else {
33 | failure({ success: false, message: '对不起,文件创建失败' });
34 | }
35 | } else if (type.includes('apiGenerator')) {
36 | const { databases, baseClasses } = generateApi(api);
37 | if (databases === null) {
38 | failure({
39 | success: false,
40 | databases: null,
41 | });
42 | } else {
43 | success({
44 | success: true,
45 | databases,
46 | baseClasses,
47 | });
48 | }
49 | } else if (type.includes('constant')) {
50 | if (type === 'org.umi-plugin-page-creator.constantLoad') {
51 | const code = getConstantConfig(api);
52 | success({
53 | success: true,
54 | data: code,
55 | })
56 | } else if (type === 'org.umi-plugin-page-creator.constantSave') {
57 | saveConstantConfig(api, payload.code);
58 | success({
59 | success: true,
60 | message: '常量配置保存成功'
61 | });
62 | }
63 | } else {
64 | const result = generatePage(payload, type, api);
65 | if (result) {
66 | success({ success: true, message: '恭喜你,文件创建成功' });
67 | } else {
68 | failure({ success: false, message: '对不起,目录已存在' });
69 | }
70 | }
71 | });
72 | }
73 |
--------------------------------------------------------------------------------
/src/manage/templates/formActionMethods.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * @文件描述: 生成table与form连接的一些方法组件(非modal类型)
3 | * @公司: thundersdata
4 | * @作者: 廖军
5 | * @Date: 2020-10-10 16:15:04
6 | * @LastEditors: 廖军
7 | * @LastEditTime: 2020-10-27 14:08:34
8 | */
9 |
10 | export interface Payload {
11 | initialFetch?: string[];
12 | pageName: string;
13 | generateDetail: boolean;
14 | }
15 |
16 | export default ({ pageName, initialFetch = [], generateDetail }: Payload) => {
17 | const apiStr = initialFetch.length === 3 ?
18 | `API.${initialFetch[0]}.${initialFetch[1]}` : 'API.recruitment.person';
19 | return `
20 | import React, { forwardRef } from 'react';
21 | import { Store } from 'antd/es/form/interface';
22 | import { history } from 'umi';
23 | import { useRequest } from 'ahooks';
24 | import { message } from 'antd';
25 |
26 | export interface FormActionMethodsInstance {
27 | onAdd?: () => void;
28 | onDelete?: (row: Store) => void;
29 | onEdit?: (row: Store) => void;
30 | onPreview?: (row: Store) => void;
31 | onDeleteBatch?: (ids: number[]) => void;
32 | }
33 |
34 | export default forwardRef void }>(({ reload }, ref) => {
35 | const formActionRef = ref as React.MutableRefObject;
36 | const { run: handleDelete } = useRequest(${apiStr}.remove.fetch, {
37 | manual: true,
38 | onSuccess: () => {
39 | message.success('删除成功');
40 | reload && reload();
41 | },
42 | });
43 | const { run: handleDeleteBatch } = useRequest(${apiStr}.deleteBatch, {
44 | manual: true,
45 | onSuccess: () => {
46 | message.success('批量删除成功');
47 | reload && reload();
48 | },
49 | });
50 |
51 | /** 新增 */
52 | formActionRef.current.onAdd = () => history.push('/${pageName}/edit');
53 |
54 | /** 删除 */
55 | formActionRef.current.onDelete = (row: Store) => handleDelete(row.id);
56 |
57 | /** 编辑 */
58 | formActionRef.current.onEdit = (row: Store) => history.push(\`/${pageName}/edit?id=\${row.id}\`);
59 |
60 | ${generateDetail ? `/** 查看 */
61 | formActionRef.current.onPreview = (row: Store) => history.push(\`/${pageName}/detail?id=\${row.id}\`);` : ''}
62 |
63 | /** 批量删除 */
64 | formActionRef.current.onDeleteBatch = (ids: number[]) => handleDeleteBatch(ids);
65 |
66 | return <>>;
67 | });
68 | `;
69 | }
--------------------------------------------------------------------------------
/src/manage/templates/formActionMethodsModal.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * @文件描述: 生成table与form连接的一些方法组件(modal类型)
3 | * @公司: thundersdata
4 | * @作者: 廖军
5 | * @Date: 2020-10-10 16:26:10
6 | * @LastEditors: 廖军
7 | * @LastEditTime: 2020-10-27 14:09:49
8 | */
9 |
10 | export interface Payload {
11 | initialFetch?: string[];
12 | generateDetail: boolean;
13 | }
14 |
15 | export default ({ initialFetch = [], generateDetail }: Payload) => {
16 | const apiStr = initialFetch.length === 3 ?
17 | `API.${initialFetch[0]}.${initialFetch[1]}` : 'API.recruitment.person';
18 | return `
19 | import React, { forwardRef } from 'react';
20 | import { Store } from 'antd/es/form/interface';
21 | import { useRequest } from 'ahooks';
22 | import { message } from 'antd';
23 | import { useImmer } from 'use-immer';
24 | import Edit from '../Edit';
25 | ${generateDetail ? `import Detail from '../Detail';` : ''}
26 |
27 | export interface FormActionMethodsInstance {
28 | onAdd?: () => void;
29 | onDelete?: (row: Store) => void;
30 | onEdit?: (row: Store) => void;
31 | onPreview?: (row: Store) => void;
32 | onDeleteBatch?: (ids: number[]) => void;
33 | }
34 |
35 | export default forwardRef void }>(({ reload }, ref) => {
36 | const formActionRef = ref as React.MutableRefObject;
37 | const [editModalConfig, setEditModalConfig] = useImmer<{
38 | visible: boolean;
39 | formData: Store;
40 | loading: boolean;
41 | }>({
42 | visible: false,
43 | formData: {},
44 | loading: false,
45 | });
46 | ${generateDetail ? `const [detailModalConfig, setDetailModalConfig] = useImmer<{
47 | visible: boolean;
48 | formData: Store;
49 | loading: boolean;
50 | }>({
51 | visible: false,
52 | formData: {},
53 | loading: false,
54 | });` : ''}
55 |
56 | const { run: handleDelete } = useRequest(${apiStr}.remove.fetch, {
57 | manual: true,
58 | onSuccess: () => {
59 | message.success('删除成功');
60 | reload && reload();
61 | },
62 | });
63 |
64 | const { run: handleDeleteBatch } = useRequest(${apiStr}.deleteBatch, {
65 | manual: true,
66 | onSuccess: () => {
67 | message.success('批量删除成功');
68 | reload && reload();
69 | },
70 | });
71 |
72 | /** 新增 */
73 | formActionRef.current.onAdd = () =>
74 | setEditModalConfig(config => {
75 | config.visible = true;
76 | config.formData = {};
77 | });
78 |
79 | /** 删除 */
80 | formActionRef.current.onDelete = (row: Store) => handleDelete(row.id);
81 |
82 | /** 编辑 */
83 | formActionRef.current.onEdit = (row: Store) =>
84 | setEditModalConfig(config => {
85 | config.visible = true;
86 | config.formData = row;
87 | });
88 |
89 | ${generateDetail ? `/** 查看 */
90 | formActionRef.current.onPreview = (row: Store) =>
91 | setDetailModalConfig(config => {
92 | config.visible = true;
93 | config.formData = row;
94 | });` : ''}
95 |
96 | /** 批量删除 */
97 | formActionRef.current.onDeleteBatch = (ids: number[]) => handleDeleteBatch(ids);
98 |
99 | return (
100 | <>
101 |
106 | setEditModalConfig(config => {
107 | config.visible = false;
108 | config.loading = false;
109 | config.formData = {};
110 | })
111 | }
112 | reload={reload}
113 | />
114 | ${generateDetail ? `
119 | setDetailModalConfig(config => {
120 | config.visible = false;
121 | config.loading = false;
122 | config.formData = {};
123 | })
124 | }
125 | />` : ''}
126 | >
127 | );
128 | });
129 | `;
130 | }
--------------------------------------------------------------------------------
/src/manage/templates/index.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * @文件描述:
3 | * @公司: thundersdata
4 | * @作者: 陈杰
5 | * @Date: 2020-05-07 14:06:48
6 | * @LastEditors: 廖军
7 | * @LastEditTime: 2020-10-10 16:50:07
8 | */
9 | import generateShortFormCode from './shortForm';
10 | import generateLongFormCode from './longForm';
11 | import generateShortDetailCode from './shortDetail';
12 | import generateLongDetailCode from './longDetail';
13 | import generateTableCode from './table';
14 | import generateShortFormModalCode from './shortFormModal';
15 | import generateLongFormModalCode from './longFormModal';
16 | import generateShortDetailModalCode from './shortDetailModal';
17 | import generateLongDetailModalCode from './longDetailModal';
18 | import generateFormActionMethodsCode from './formActionMethods';
19 | import generateFormActionMethodsModalCode from './formActionMethodsModal';
20 |
21 | export {
22 | generateShortFormCode,
23 | generateLongFormCode,
24 | generateShortDetailCode,
25 | generateLongDetailCode,
26 | generateTableCode,
27 | generateShortFormModalCode,
28 | generateLongFormModalCode,
29 | generateShortDetailModalCode,
30 | generateLongDetailModalCode,
31 | generateFormActionMethodsCode,
32 | generateFormActionMethodsModalCode,
33 | };
34 |
--------------------------------------------------------------------------------
/src/manage/templates/longDetail.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * @文件描述: 生成长详情页面
3 | * @公司: thundersdata
4 | * @作者: 陈杰
5 | * @Date: 2020-05-08 16:05:30
6 | * @LastEditors: 廖军
7 | * @LastEditTime: 2020-10-09 15:38:34
8 | */
9 | import { transformFormItemLines, generateBreadcrumbs } from './util';
10 | import { CardItemProps } from '../../../interfaces/common';
11 |
12 | export interface Payload {
13 | cards: CardItemProps[];
14 | initialFetch?: string[];
15 | menu: string;
16 | }
17 |
18 | export default function generateLongFormCode(payload: Payload): string {
19 | if (payload && payload.cards) {
20 | const { cards = [], initialFetch, menu } = payload;
21 |
22 | const breadcrumbs = generateBreadcrumbs(menu);
23 |
24 | const code = `
25 | import React from 'react';
26 | import {
27 | Form,
28 | Card,
29 | Row,
30 | Col,
31 | Spin,
32 | } from 'antd';
33 | import { history } from 'umi';
34 | import { useRequest } from 'ahooks';
35 | import Title from '@/components/Title';
36 | import DetailValue from '@/components/DetailValue';
37 | ${breadcrumbs.length > 1 && `import CustomBreadcrumb from '@/components/CustomBreadcrumb';`}
38 | console.log('emptyline');
39 | const colLayout = {
40 | lg: {
41 | span: 8
42 | },
43 | md: {
44 | span: 12
45 | },
46 | sm: {
47 | span: 24
48 | }
49 | }
50 | console.log('emptyline');
51 | export default () => {
52 | const [form] = Form.useForm();
53 | const { id } = history.location.query;
54 | console.log('emptyline');
55 | const { loading } = useRequest(API.${initialFetch && initialFetch.length === 3 ? `${initialFetch[0]}.${initialFetch[1]}.${
56 | initialFetch[2].split('-')[0]
57 | }.fetch({ id })` : `recruitment.person.getPerson.fetch(
58 | { personCode: id },
59 | )`}, {
60 | ready: !!id,
61 | onSuccess: data => {
62 | const values = {
63 | ...data
64 | };
65 | form.setFieldsValue(values);
66 | }
67 | });
68 | console.log('emptyline');
69 | return (
70 |
71 |
72 |
111 |
112 | );
113 | };
114 | `;
115 | return code;
116 | }
117 | return '';
118 | }
119 |
--------------------------------------------------------------------------------
/src/manage/templates/longDetailModal.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * @文件描述: 生成大表单页面
3 | * @公司: thundersdata
4 | * @作者: 陈杰
5 | * @Date: 2020-05-08 16:05:30
6 | * @LastEditors: 陈杰
7 | * @LastEditTime: 2020-05-15 19:33:41
8 | */
9 | import { transformFormItemLines } from './util';
10 | import { FormItemProps } from '../../../interfaces/common';
11 | import { Store } from 'antd/lib/form/interface';
12 |
13 | export interface Payload {
14 | formConfig: Store;
15 | formItems: FormItemProps[];
16 | }
17 |
18 | export default function generateLongDetailModalCode(payload: Payload): string {
19 | if (payload && payload.formConfig && payload.formItems) {
20 | const { formConfig, formItems = [] } = payload;
21 |
22 | const cols = 2;
23 | // 把formItems分成2列
24 | const formItemLines = transformFormItemLines(formItems, cols);
25 |
26 | const code = `
27 | import React, { useEffect } from 'react';
28 | import {
29 | Modal,
30 | Form,
31 | Row,
32 | Col,
33 | Spin,
34 | Input,
35 | DatePicker,
36 | TimePicker,
37 | Cascader,
38 | InputNumber,
39 | Radio,
40 | Checkbox,
41 | Switch,
42 | Slider,
43 | Select,
44 | TreeSelect,
45 | Upload,
46 | Rate,
47 | } from 'antd';
48 | import { FormInstance } from 'antd/lib/form';
49 | import { Store } from 'antd/es/form/interface';
50 | import { isEmpty } from 'lodash-es';
51 | import DetailValue from '@/components/DetailValue';
52 | console.log('emptyline');
53 | const layout = {
54 | labelCol: { span: 8 },
55 | wrapperCol: { span: 16 },
56 | };
57 | console.log('emptyline');
58 | export default ({
59 | visible,
60 | toggleVisible,
61 | formData,
62 | loading,
63 | }: {
64 | visible: boolean;
65 | toggleVisible: () => void;
66 | formData: Store;
67 | loading: boolean;
68 | }) => {
69 | const [form] = Form.useForm();
70 | console.log('emptyline');
71 | useEffect(() => {
72 | if (!isEmpty(formData)) {
73 | form.setFieldsValue(formData);
74 | }
75 | }, [formData]);
76 | console.log('emptyline');
77 | return (
78 |
87 |
88 |
112 |
113 |
114 | );
115 | };
116 | `;
117 | return code;
118 | }
119 | return '';
120 | }
121 |
--------------------------------------------------------------------------------
/src/manage/templates/longForm.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * @文件描述: 生成大表单页面
3 | * @公司: thundersdata
4 | * @作者: 陈杰
5 | * @Date: 2020-05-08 16:05:30
6 | * @LastEditors: 廖军
7 | * @LastEditTime: 2020-10-10 10:15:36
8 | */
9 | import { createFormComponentsByType, transformFormItemLines, generateRules, generateBreadcrumbs } from './util';
10 | import { CardItemProps, FormItemProps } from '../../../interfaces/common';
11 | import { getPageNameByPath } from '..';
12 |
13 | export interface Payload {
14 | cards: CardItemProps[];
15 | initialFetch?: string[];
16 | submitFetch?: string[];
17 | menu: string;
18 | path: string;
19 | }
20 |
21 | export default function generateLongFormCode(payload: Payload): string {
22 | if (payload && payload.cards) {
23 | const { cards = [], initialFetch, submitFetch, menu } = payload;
24 | const formItems = cards.reduce((acc, curr) => acc.concat(curr.formItems), [] as FormItemProps[]);
25 | const hasUploadItem = formItems.find(item => item.type === 'upload');
26 |
27 | const breadcrumbs = generateBreadcrumbs(menu);
28 |
29 | const code = `
30 | import React ${hasUploadItem ? ', { useState }' : ''} from 'react';
31 | import {
32 | Form,
33 | Button,
34 | Card,
35 | Row,
36 | Col,
37 | Input,
38 | DatePicker,
39 | TimePicker,
40 | Cascader,
41 | InputNumber,
42 | Radio,
43 | Checkbox,
44 | Switch,
45 | Slider,
46 | Select,
47 | TreeSelect,
48 | Upload,
49 | Rate,
50 | message,
51 | Spin,
52 | } from 'antd';
53 | import { history } from 'umi';
54 | import { useRequest } from 'ahooks';
55 | import { Store } from 'antd/es/form/interface';
56 | import Title from '@/components/Title';
57 | import useSpinning from '@/hooks/useSpinning';
58 | import FooterToolbar from '@/components/FooterToolbar';
59 | ${breadcrumbs.length > 1 && `import CustomBreadcrumb from '@/components/CustomBreadcrumb';`}
60 | import { getVerificationRules } from '@/pages/${getPageNameByPath(payload.path)}/validators';
61 | console.log('emptyline');
62 | const colLayout = {
63 | lg: {
64 | span: 8
65 | },
66 | md: {
67 | span: 12
68 | },
69 | sm: {
70 | span: 24
71 | }
72 | }
73 | console.log('emptyline');
74 | export default () => {
75 | const [form] = Form.useForm();
76 | const { tip, setTip } = useSpinning();
77 | ${hasUploadItem ? `const [submitBtnDisabled, setSubmitBtnDisabled] = useState(false);` : ''}
78 | console.log('emptyline');
79 | const { id } = history.location.query;
80 | console.log('emptyline');
81 | const fetchDetail = () => {
82 | if (id) {
83 | setTip('加载详情中,请稍候...');
84 | return API.${initialFetch && initialFetch.length === 3 ? `${initialFetch[0]}.${initialFetch[1]}.${
85 | initialFetch[2].split('-')[0]
86 | }.fetch({ id })` : `recruitment.person.getPerson.fetch(
87 | { personCode: id },
88 | )`};
89 | }
90 | return Promise.resolve(false);
91 | };
92 | console.log('emptyline');
93 | const { loading } = useRequest(fetchDetail, {
94 | refreshDeps: [id],
95 | onSuccess: data => {
96 | const values = {
97 | ...data
98 | };
99 | form.setFieldsValue(values);
100 | }
101 | });
102 | console.log('emptyline');
103 | const submit = (values: Store) => {
104 | setSpinning(true);
105 | setTip('数据保存中,请稍候...');
106 | console.log('emptyline');
107 | const payload = {
108 | ...values,
109 | };
110 | console.log('emptyline');
111 | return API.${submitFetch && submitFetch.length === 3 ? `${submitFetch[0]}.${submitFetch[1]}.${
112 | submitFetch[2].split('-')[0]
113 | }` : 'recruitment.person.addPerson'}.fetch(payload);
114 | };
115 | console.log('emptyline');
116 | const { run: handleFinish, loading: submitting } = useRequest(submit, {
117 | manual: true,
118 | onSuccess: () => {
119 | message.success('保存成功');
120 | },
121 | });
122 | console.log('emptyline');
123 | return (
124 |
125 |
126 |
192 |
193 | );
194 | };
195 | `;
196 | return code;
197 | }
198 | return '';
199 | }
200 |
--------------------------------------------------------------------------------
/src/manage/templates/longFormModal.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * @文件描述: 生成大表单页面
3 | * @公司: thundersdata
4 | * @作者: 陈杰
5 | * @Date: 2020-05-08 16:05:30
6 | * @LastEditors: 廖军
7 | * @LastEditTime: 2020-10-10 17:40:45
8 | */
9 | import { createFormComponentsByType, transformFormItemLines, generateRules } from './util';
10 | import { FormItemProps } from '../../../interfaces/common';
11 | import { Store } from 'antd/lib/form/interface';
12 | import { getPageNameByPath } from '..';
13 |
14 | export interface Payload {
15 | formConfig: Store;
16 | formItems: FormItemProps[];
17 | submitFetch?: string[];
18 | fromTable: boolean;
19 | path: string;
20 | }
21 |
22 | export default function generateLongFormModalCode(payload: Payload): string {
23 | if (payload && payload.formConfig && payload.formItems) {
24 | const { formConfig, formItems = [], submitFetch, fromTable } = payload;
25 |
26 | const cols = 2;
27 | // 把formItems分成2列
28 | const formItemLines = transformFormItemLines(formItems, cols);
29 | const item = formItems.find(item => item.type === 'upload');
30 |
31 | const code = `
32 | import React, { useEffect ${item ? ', useState' : ''} } from 'react';
33 | import {
34 | Modal,
35 | Button,
36 | Form,
37 | Row,
38 | Col,
39 | Spin,
40 | Input,
41 | DatePicker,
42 | TimePicker,
43 | Cascader,
44 | InputNumber,
45 | Radio,
46 | Checkbox,
47 | Switch,
48 | Slider,
49 | Select,
50 | TreeSelect,
51 | Upload,
52 | Rate,
53 | message,
54 | } from 'antd';
55 | import { FormInstance } from 'antd/lib/form';
56 | import { Store } from 'antd/es/form/interface';
57 | import { isEmpty } from 'lodash-es';
58 | import { useRequest } from 'ahooks';
59 | import useSpinning from '@/hooks/useSpinning';
60 | import { getVerificationRules } from '@/pages/${getPageNameByPath(payload.path)}/validators';
61 | console.log('emptyline');
62 | const twoColumnsLayout = {
63 | labelCol: { span: 8 },
64 | wrapperCol: { span: 16 },
65 | };
66 | console.log('emptyline');
67 | export default ({
68 | visible,
69 | toggleVisible,
70 | formData,
71 | loading,
72 | ${fromTable ? `reload,` : ''}
73 | }: {
74 | visible: boolean;
75 | toggleVisible: () => void;
76 | formData: Store;
77 | loading: boolean;
78 | ${fromTable ? `reload?: () => void;` : ''}
79 | }) => {
80 | const [form] = Form.useForm();
81 | const { tip, setTip } = useSpinning();
82 | ${item ? `const [submitBtnDisabled, setSubmitBtnDisabled] = useState(false);` : ''}
83 | console.log('emptyline');
84 | useEffect(() => {
85 | if (!isEmpty(formData)) {
86 | form.setFieldsValue(formData);
87 | }
88 | }, [formData]);
89 | console.log('emptyline');
90 | const handleCancel = () => {
91 | toggleVisible();
92 | form.resetFields();
93 | };
94 | console.log('emptyline');
95 | const submit = (values: Store) => {
96 | setTip('数据保存中,请稍候...');
97 | console.log('emptyline');
98 | const payload = {
99 | ...values,
100 | };
101 | console.log('emptyline');
102 | return API.${submitFetch && submitFetch.length === 3 ? `${submitFetch[0]}.${submitFetch[1]}.${
103 | submitFetch[2].split('-')[0]
104 | }` : 'recruitment.person.addPerson'}.fetch(payload);
105 | };
106 | console.log('emptyline');
107 | const { run: handleFinish, loading: submitting } = useRequest(submit, {
108 | manual: true,
109 | onSuccess: () => {
110 | message.success('保存成功');
111 | form.resetFields();
112 | ${fromTable ? `reload && reload();` : ''}
113 | }
114 | });
115 | console.log('emptyline');
116 | return (
117 | form.submit()}
126 | onCancel={handleCancel}
127 | confirmLoading={submitting}
128 | >
129 |
130 |
173 |
174 |
175 | );
176 | };
177 | `;
178 | return code;
179 | }
180 | return '';
181 | }
182 |
--------------------------------------------------------------------------------
/src/manage/templates/shortDetail.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * @文件描述: 生成短详情页面
3 | * @公司: thundersdata
4 | * @作者: 陈杰
5 | * @Date: 2020-05-07 14:04:41
6 | * @LastEditors: 廖军
7 | * @LastEditTime: 2020-10-09 15:39:08
8 | */
9 | import { Store } from 'antd/lib/form/interface';
10 | import { FormItemProps } from '../../../interfaces/common';
11 | import { generateBreadcrumbs } from './util';
12 |
13 | export interface Payload {
14 | formConfig: Store;
15 | formItems: FormItemProps[];
16 | initialFetch?: string[];
17 | menu: string;
18 | }
19 |
20 | export default function generateShortDetail(payload: Payload): string {
21 | if (payload && payload.formConfig && payload.formItems) {
22 | const { formConfig, formItems, initialFetch, menu } = payload;
23 |
24 | const breadcrumbs = generateBreadcrumbs(menu);
25 |
26 | const code = `
27 | import React from 'react';
28 | import { Card, Form, Spin } from 'antd';
29 | import { history } from 'umi';
30 | import { useRequest } from 'ahooks';
31 | import Title from '@/components/Title';
32 | import DetailValue from '@/components/DetailValue';
33 | ${breadcrumbs.length > 1 && `import CustomBreadcrumb from '@/components/CustomBreadcrumb';`}
34 | console.log('emptyline');
35 | const formItemLayout = {
36 | labelCol: {
37 | xs: { span: 24 },
38 | sm: { span: 7 },
39 | },
40 | wrapperCol: {
41 | xs: { span: 24 },
42 | sm: { span: 12 },
43 | md: { span: 10 },
44 | },
45 | };
46 | console.log('emptyline');
47 | export default () => {
48 | const [form] = Form.useForm();
49 | const { id } = history.location.query;
50 | console.log('emptyline');
51 | const { loading } = useRequest(API.${initialFetch && initialFetch.length === 3 ? `${initialFetch[0]}.${initialFetch[1]}.${
52 | initialFetch[2].split('-')[0]
53 | }.fetch({ id })` : `recruitment.person.getPerson.fetch(
54 | { personCode: id },
55 | )`}, {
56 | ready: !!id,
57 | onSuccess: data => {
58 | const values = {
59 | ...data
60 | }
61 | form.setFieldsValue(values);
62 | }
63 | });
64 | console.log('emptyline');
65 | return (
66 |
67 |
68 |
78 |
79 | `;
80 | })
81 | .join('')}
82 |
83 |
84 |
85 | );
86 | };
87 | `;
88 | return code;
89 | }
90 | return '';
91 | }
92 |
--------------------------------------------------------------------------------
/src/manage/templates/shortDetailModal.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * @文件描述: 生成单列详情弹窗
3 | * @公司: thundersdata
4 | * @作者: 陈杰
5 | * @Date: 2020-05-07 14:04:41
6 | * @LastEditors: 陈杰
7 | * @LastEditTime: 2020-05-19 11:39:50
8 | */
9 | import { Store } from 'antd/lib/form/interface';
10 | import { FormItemProps } from '../../../interfaces/common';
11 |
12 | export interface Payload {
13 | formConfig: Store;
14 | formItems: FormItemProps[];
15 | }
16 |
17 | export default function generateShortDetailModalCode(payload: Payload): string {
18 | if (payload && payload.formConfig && payload.formItems) {
19 | const { formConfig, formItems } = payload;
20 |
21 | const code = `
22 | import React, { useEffect } from 'react';
23 | import {
24 | Button,
25 | Modal,
26 | Form,
27 | Spin,
28 | Input,
29 | DatePicker,
30 | TimePicker,
31 | Cascader,
32 | InputNumber,
33 | Radio,
34 | Checkbox,
35 | Switch,
36 | Slider,
37 | Select,
38 | TreeSelect,
39 | Upload,
40 | Rate,
41 | } from 'antd';
42 | import { isEmpty } from 'lodash-es';
43 | import { FormInstance } from 'antd/lib/form';
44 | import { Store } from 'antd/es/form/interface';
45 | import DetailValue from '@/components/DetailValue';
46 | console.log('emptyline');
47 | const layout = {
48 | labelCol: { span: 5 },
49 | wrapperCol: { span: 18 },
50 | };
51 | console.log('emptyline');
52 | export default ({
53 | visible,
54 | toggleVisible,
55 | formData,
56 | loading,
57 | }: {
58 | visible: boolean;
59 | toggleVisible: () => void;
60 | formData: Store;
61 | loading: boolean;
62 | }) => {
63 | const [form] = Form.useForm();
64 | console.log('emptyline');
65 | useEffect(() => {
66 | if (!isEmpty(formData)) {
67 | form.setFieldsValue(formData);
68 | }
69 | }, [formData]);
70 | console.log('emptyline');
71 | return (
72 |
80 |
81 |
93 |
94 | `;
95 | })
96 | .join('')}
97 |
98 |
99 |
100 | );
101 | };
102 | `;
103 | return code;
104 | }
105 | return '';
106 | }
107 |
--------------------------------------------------------------------------------
/src/manage/templates/shortForm.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * @文件描述: 生成短表单
3 | * @公司: thundersdata
4 | * @作者: 陈杰
5 | * @Date: 2020-05-07 14:04:41
6 | * @LastEditors: 廖军
7 | * @LastEditTime: 2020-10-10 10:10:28
8 | */
9 | import { Store } from 'antd/lib/form/interface';
10 | import { createFormComponentsByType, generateRules, generateBreadcrumbs } from './util';
11 | import { FormItemProps } from '../../../interfaces/common';
12 | import { getPageNameByPath } from '..';
13 |
14 | export interface Payload {
15 | formConfig: Store;
16 | formItems: FormItemProps[];
17 | initialFetch?: string[];
18 | submitFetch?: string[];
19 | menu: string;
20 | path: string;
21 | }
22 |
23 | export default function generateShortFormCode(payload: Payload): string {
24 | if (payload && payload.formConfig && payload.formItems) {
25 | const { formConfig, formItems, initialFetch, submitFetch, menu, } = payload;
26 | const hasUploadItem = formItems.find(item => item.type === 'upload');
27 |
28 | const breadcrumbs = generateBreadcrumbs(menu);
29 |
30 | const code = `
31 | import React ${hasUploadItem ? ', { useState }' : ''} from 'react';
32 | import {
33 | Form,
34 | Button,
35 | Card,
36 | Input,
37 | DatePicker,
38 | TimePicker,
39 | Cascader,
40 | InputNumber,
41 | Radio,
42 | Checkbox,
43 | Switch,
44 | Slider,
45 | Select,
46 | TreeSelect,
47 | Upload,
48 | Rate,
49 | message,
50 | Spin,
51 | } from 'antd';
52 | import { Store } from 'antd/es/form/interface';
53 | import { history } from 'umi';
54 | import { useRequest } from 'ahooks';
55 | import Title from '@/components/Title';
56 | import useSpinning from '@/hooks/useSpinning';
57 | ${breadcrumbs.length > 1 && `import CustomBreadcrumb from '@/components/CustomBreadcrumb';`}
58 | import { getVerificationRules } from '@/pages/${getPageNameByPath(payload.path)}/validators';
59 | console.log('emptyline');
60 | const formItemLayout = {
61 | labelCol: {
62 | xs: { span: 24 },
63 | sm: { span: 7 },
64 | },
65 | wrapperCol: {
66 | xs: { span: 24 },
67 | sm: { span: 12 },
68 | md: { span: 10 },
69 | },
70 | };
71 | console.log('emptyline');
72 | const submitFormLayout = {
73 | wrapperCol: {
74 | xs: { span: 24, offset: 0 },
75 | sm: { span: 10, offset: 7 },
76 | },
77 | };
78 | console.log('emptyline');
79 | export default () => {
80 | const [form] = Form.useForm();
81 | const { tip, setTip } = useSpinning();
82 | ${hasUploadItem ? `const [submitBtnDisabled, setSubmitBtnDisabled] = useState(false);` : ''}
83 | console.log('emptyline');
84 | const { id } = history.location.query;
85 | console.log('emptyline');
86 | const fetchDetail = () => {
87 | if (id) {
88 | setTip('加载详情中,请稍候...');
89 | return API.${initialFetch && initialFetch.length === 3 ? `${initialFetch[0]}.${initialFetch[1]}.${
90 | initialFetch[2].split('-')[0]
91 | }.fetch({ id })` : `recruitment.person.getPerson.fetch(
92 | { personCode: id },
93 | )`};
94 | }
95 | return Promise.resolve(false);
96 | };
97 | console.log('emptyline');
98 | const { loading } = useRequest(fetchDetail, {
99 | refreshDeps: [id],
100 | onSuccess: data => {
101 | const values = {
102 | ...data
103 | };
104 | form.setFieldsValue(values);
105 | },
106 | });
107 | console.log('emptyline');
108 | const submit = (values: Store) => {
109 | setTip('数据保存中,请稍候...');
110 | console.log('emptyline');
111 | const payload = {
112 | ...values,
113 | };
114 | console.log('emptyline');
115 | return API.${submitFetch && submitFetch.length === 3 ? `${submitFetch[0]}.${submitFetch[1]}.${
116 | submitFetch[2].split('-')[0]
117 | }` : 'recruitment.person.addPerson'}.fetch(payload);
118 | };
119 | console.log('emptyline');
120 | const { run: handleFinish, loading: submitting } = useRequest(submit, {
121 | manual: true,
122 | onSuccess: () => {
123 | message.success('保存成功');
124 | },
125 | });
126 | console.log('emptyline');
127 | return (
128 |
129 |
130 | }>
131 | {
152 | if (Array.isArray(e)) {
153 | return e;
154 | }
155 | return e && e.fileList;
156 | }}` : ''}
157 | >
158 | ${createFormComponentsByType(type, restProps)}
159 | `;
160 | })
161 | .join('')}
162 |
163 |
166 |
167 |
168 |
169 |
170 | );
171 | };
172 | `;
173 | return code;
174 | }
175 | return '';
176 | }
177 |
--------------------------------------------------------------------------------
/src/manage/templates/shortFormModal.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * @文件描述: 生成单列表单弹窗
3 | * @公司: thundersdata
4 | * @作者: 陈杰
5 | * @Date: 2020-05-07 14:04:41
6 | * @LastEditors: 廖军
7 | * @LastEditTime: 2020-10-10 17:41:18
8 | */
9 | import { Store } from 'antd/lib/form/interface';
10 | import { createFormComponentsByType, generateRules } from './util';
11 | import { FormItemProps } from '../../../interfaces/common';
12 | import { getPageNameByPath } from '..';
13 |
14 | export interface Payload {
15 | formConfig: Store;
16 | formItems: FormItemProps[];
17 | submitFetch?: string[];
18 | fromTable: boolean;
19 | path: string;
20 | }
21 |
22 | export default function generateShortFormModalCode(payload: Payload): string {
23 | if (payload && payload.formConfig && payload.formItems) {
24 | const { formConfig, formItems, submitFetch, fromTable } = payload;
25 | const item = formItems.find(item => item.type === 'upload');
26 |
27 | const code = `
28 | import React, { useEffect ${item ? ', useState' : ''} } from 'react';
29 | import {
30 | Button,
31 | Modal,
32 | Form,
33 | Spin,
34 | Input,
35 | DatePicker,
36 | TimePicker,
37 | Cascader,
38 | InputNumber,
39 | Radio,
40 | Checkbox,
41 | Switch,
42 | Slider,
43 | Select,
44 | TreeSelect,
45 | Upload,
46 | Rate,
47 | message,
48 | } from 'antd';
49 | import { isEmpty } from 'lodash-es';
50 | import { FormInstance } from 'antd/lib/form';
51 | import { Store } from 'antd/es/form/interface';
52 | import { useRequest } from 'ahooks';
53 | import useSpinning from '@/hooks/useSpinning';
54 | import { getVerificationRules } from '@/pages/${getPageNameByPath(payload.path)}/validators';
55 | console.log('emptyline');
56 | const formLayout = {
57 | labelCol: { span: 6 },
58 | wrapperCol: { span: 17 },
59 | };
60 | console.log('emptyline');
61 | export default ({
62 | visible,
63 | toggleVisible,
64 | formData,
65 | loading,
66 | ${fromTable ? `reload,` : ''}
67 | }: {
68 | visible: boolean;
69 | toggleVisible: () => void;
70 | formData: Store;
71 | loading: boolean;
72 | ${fromTable ? `reload?: () => void;` : ''}
73 | }) => {
74 | const [form] = Form.useForm();
75 | const { tip, setTip } = useSpinning();
76 | ${item ? `const [submitBtnDisabled, setSubmitBtnDisabled] = useState(false);` : ''}
77 | console.log('emptyline');
78 | useEffect(() => {
79 | if (!isEmpty(formData)) {
80 | form.setFieldsValue(formData);
81 | }
82 | }, [formData]);
83 | console.log('emptyline');
84 | const handleCancel = () => {
85 | toggleVisible();
86 | form.resetFields();
87 | }
88 | console.log('emptyline');
89 | const submit = (values: Store) => {
90 | setTip('数据保存中,请稍候...');
91 | console.log('emptyline');
92 | const payload = {
93 | ...values,
94 | };
95 | console.log('emptyline');
96 | return API.${submitFetch && submitFetch.length === 3 ? `${submitFetch[0]}.${submitFetch[1]}.${
97 | submitFetch[2].split('-')[0]
98 | }` : 'recruitment.person.addPerson'}.fetch(payload);
99 | };
100 | console.log('emptyline');
101 | const { run: handleFinish, loading: submitting } = useRequest(submit, {
102 | manual: true,
103 | onSuccess: () => {
104 | message.success('保存成功');
105 | form.resetFields();
106 | ${fromTable ? `reload && reload();` : ''}
107 | },
108 | });
109 | console.log('emptyline');
110 | return (
111 | form.submit()}
119 | onCancel={handleCancel}
120 | confirmLoading={submitting}
121 | >
122 |
123 | {
143 | if (Array.isArray(e)) {
144 | return e;
145 | }
146 | return e && e.fileList;
147 | }}` : ''}
148 | >
149 | ${createFormComponentsByType(type, restProps)}
150 | `;
151 | })
152 | .join('')}
153 |
154 |
155 |
156 | );
157 | };
158 | `;
159 | return code;
160 | }
161 | return '';
162 | }
163 |
--------------------------------------------------------------------------------
/src/manage/templates/table.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * @文件描述: 生成表格页面
3 | * @公司: thundersdata
4 | * @作者: 陈杰
5 | * @Date: 2020-05-08 16:14:11
6 | * @LastEditors: 廖军
7 | * @LastEditTime: 2020-10-27 14:07:26
8 | */
9 | import { Store } from 'antd/lib/form/interface';
10 | import { ColumnType } from 'antd/lib/table';
11 | import { generateBreadcrumbs } from './util';
12 |
13 | export interface Payload {
14 | tableConfig: Store;
15 | columns: ColumnType[];
16 | initialFetch?: string[];
17 | menu: string;
18 | pageName: string;
19 | }
20 |
21 | export default function generateTable(payload: Payload): string {
22 | if (payload && payload.tableConfig && payload.columns) {
23 | const { tableConfig, columns, initialFetch, menu, pageName } = payload;
24 |
25 | const breadcrumbs = generateBreadcrumbs(menu);
26 | const columnsStrings = columns.map(config => JSON.stringify(config));
27 |
28 | const code = `
29 | import React, { useRef } from 'react';
30 | import { message, Popconfirm, Button } from 'antd';
31 | import ProTable, { ProColumns, ActionType } from '@ant-design/pro-table';
32 | import json from '@/utils/json';
33 | import { initialPagination } from '@/constant';
34 | ${breadcrumbs.length > 1 && `import CustomBreadcrumb from '@/components/CustomBreadcrumb';`}
35 | import LinkButtons from '@/components/LinkButtons';
36 | import { Store } from 'antd/es/form/interface';
37 | import FormActionMethods, { FormActionMethodsInstance } from '@/pages/${pageName}/components/FormActionMethods';
38 | console.log('emptyline');
39 | export default () => {
40 | const actionRef = useRef();
41 | const formActionRef = useRef({});
42 | const columns: ProColumns<${initialFetch && initialFetch.length === 3 ? `defs.${initialFetch[0]}.${initialFetch[2].split('-')[2]}` : 'defs.recruitment.PersonResultDTO'}>[] = [${[...columnsStrings, `
43 | {
44 | title: '操作',
45 | dataIndex: 'id',
46 | align: 'left',
47 | copyable: false,
48 | valueType: 'text',
49 | hideInSearch: true,
50 | render: (_, row) => (
51 | formActionRef.current?.onPreview!(row),
57 | },
58 | {
59 | name: '编辑',
60 | key: 'edit',
61 | onClick: () => formActionRef.current?.onEdit!(row),
62 | },
63 | {
64 | name: '删除',
65 | key: 'delete',
66 | onClick: () => formActionRef.current?.onDelete!(row),
67 | },
68 | ]}
69 | />
70 | ),
71 | }
72 | `].join(',')}];
73 | console.log('emptyline');
74 | return (
75 | <>
76 |
77 | {
80 | delete rest._timestamp;
81 | const {
82 | list,
83 | page,
84 | total,
85 | } = await ${initialFetch && initialFetch.length === 3 ? `API.${initialFetch[0]}.${initialFetch[1]}.${initialFetch[2].split('-')[0]}` : 'API.recruitment.person.queryPerson'}.fetch(
86 | json.removeEmpty({
87 | ...rest,
88 | page: current || initialPagination.page,
89 | pageSize: rest?.pageSize || initialPagination.pageSize,
90 | })
91 | );
92 | return {
93 | success: true,
94 | data: list || [],
95 | page,
96 | total,
97 | };
98 | }}
99 | columns={columns}
100 | rowKey="${tableConfig.rowKey}"
101 | headerTitle="${tableConfig.headerTitle}"
102 | rowSelection={{}}
103 | toolBarRender={(_, { selectedRowKeys = [] }) => [
104 | ...(selectedRowKeys?.length > 0
105 | ? [
106 | formActionRef.current?.onDeleteBatch!(selectedRowKeys.map(id => +id))}
111 | okText="是"
112 | cancelText="否"
113 | >
114 |
115 | ,
116 | ]
117 | : []),
118 | ,
125 | ]}
126 | />
127 |
128 | >
129 | );
130 | }
131 | `;
132 | return code;
133 | }
134 | return '';
135 | }
136 |
--------------------------------------------------------------------------------
/src/manage/templates/util.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * @文件描述:
3 | * @公司: thundersdata
4 | * @作者: 陈杰
5 | * @Date: 2020-05-08 13:56:59
6 | * @LastEditors: 陈杰
7 | * @LastEditTime: 2020-05-19 23:31:55
8 | */
9 | import { LabeledValue } from 'antd/lib/select';
10 | import { FormItemType, FormItemProps } from '../../../interfaces/common';
11 | import { Rule } from 'antd/lib/form';
12 |
13 | export function createFormComponentsByType(
14 | type: FormItemType,
15 | props: Omit,
16 | ) {
17 | const propsStr = generatePropsStr(props);
18 | switch (type) {
19 | case 'input':
20 | default:
21 | return `
22 |
23 | `;
24 | case 'cascader':
25 | return ``;
26 |
27 | case 'checkbox':
28 | const { options: checkboxOptions, ...checkboxProps } = props;
29 | return ``;
37 |
38 | case 'date':
39 | return ``;
40 |
41 | case 'number':
42 | return ``;
43 |
44 | case 'password':
45 | return ``;
46 |
47 | case 'radio':
48 | const { options: radioOptions, ...radioProps } = props;
49 | return ``;
55 |
56 | case 'range':
57 | return ``;
58 |
59 | case 'rate':
60 | return ``;
61 |
62 | case 'select':
63 | const { options: selectOptions, ...selectProps } = props;
64 | return ``;
72 |
73 | case 'slider':
74 | return ``;
75 |
76 | case 'switch':
77 | return ``;
78 |
79 | case 'textarea':
80 | return ``;
81 |
82 | case 'time':
83 | return ``;
84 |
85 | case 'treeselect':
86 | return ``;
87 |
88 | case 'upload':
89 | return ` {
90 | if (info.file.status === 'uploading') {
91 | setSubmitBtnDisabled(true);
92 | } else {
93 | setSubmitBtnDisabled(false);
94 | }
95 | }} ${propsStr}>`;
96 | }
97 | }
98 |
99 | /**
100 | * 把属性打平成字符串
101 | */
102 | export function generatePropsStr(props: object): string {
103 | const result = `${Object.entries(props)
104 | .map(item => {
105 | const [key, value] = item;
106 | if (typeof value === 'number' || typeof value === 'bigint') {
107 | return `${key}={${value}}`;
108 | } else if (typeof value === 'boolean') {
109 | return value ? `${key}` : ``;
110 | } else if (typeof value === 'string') {
111 | return `${key}="${value}"`;
112 | } else if (typeof value === 'function') {
113 | return `${key}={${value.toString()}}`;
114 | } else if (typeof value === 'object') {
115 | return `${key}={${JSON.stringify(value)}}`;
116 | }
117 |
118 | return '';
119 | })
120 | .join(' ')}`;
121 |
122 | return result;
123 | }
124 |
125 | /**
126 | * 将一个数组按照指定的列拆分成N个二维数组
127 | * @param formItems
128 | * @param cols
129 | */
130 | export function transformFormItemLines(formItems: FormItemProps[], cols = 3) {
131 | let lineNum =
132 | formItems.length % cols === 0
133 | ? formItems.length / cols
134 | : Math.floor(formItems.length / cols + 1);
135 | let res = [];
136 | for (let i = 0; i < lineNum; i++) {
137 | let temp = formItems.slice(i * cols, i * cols + cols);
138 | res.push(temp);
139 | }
140 | return res;
141 | }
142 |
143 | export function generateRules(customRules?: string, required?: boolean) {
144 | const rules: Rule[] = [{
145 | whitespace: true,
146 | }];
147 | try {
148 | if (customRules) {
149 | const _rules = eval((customRules as string).replace(/\u21b5/g, ''));
150 | if (Array.isArray(_rules)) {
151 | rules.push(..._rules);
152 | }
153 | }
154 | } catch (error) {
155 | console.error(error.message);
156 | }
157 |
158 | if (required) {
159 | const requiredObj = rules.find(item => Object.keys(item).includes('required'));
160 | if (!requiredObj || requiredObj['required'] === false) {
161 | rules.push({
162 | required: true,
163 | });
164 | }
165 | }
166 |
167 | return JSON.stringify(rules);
168 | }
169 |
170 | /**
171 | * 生成面包屑
172 | * @param menu
173 | * @param path
174 | */
175 | export function generateBreadcrumbs(menu: string) {
176 | const menus = menu ? menu.split('/') : [];
177 | const breadcrumbs = [];
178 |
179 | for (let _menu of menus) {
180 | breadcrumbs.push({
181 | breadcrumbName: _menu,
182 | });
183 | }
184 |
185 | return JSON.stringify(breadcrumbs);
186 | }
187 |
--------------------------------------------------------------------------------
/src/screen/index.ts:
--------------------------------------------------------------------------------
1 | import { IApi } from 'umi';
2 | import { existsSync, mkdirSync } from 'fs';
3 | import { ScreenConfigPayload } from '../../interfaces/screen';
4 | import generateScreen from './templates/generateScreen';
5 | import generateLayout from './templates/generateLayout';
6 | import generateRow from './templates/generateRow';
7 | import generateCol from './templates/generateCol';
8 |
9 | /**
10 | * 生成大屏的一系列文件
11 | * @param payload
12 | * @param api
13 | */
14 | export default function(payload: ScreenConfigPayload, api: IApi) {
15 | const componentsPath = api.paths.absPagesPath + '/components';
16 | if (!existsSync(componentsPath)) {
17 | mkdirSync(componentsPath);
18 | }
19 |
20 | // 生成大屏的配置
21 | generateScreen(api.paths.absPagesPath!, payload);
22 |
23 | const { gutter, layout } = payload;
24 |
25 | // 生成布局组件(Left/Center/Right)
26 | layout.forEach((item) => {
27 | const {
28 | name: layoutName,
29 | rows,
30 | } = item;
31 |
32 | const layoutPath = componentsPath + '/' + layoutName;
33 | if (!existsSync(layoutPath)) {
34 | mkdirSync(layoutPath); // <- 创建布局的文件夹
35 | }
36 | generateLayout(layoutPath, item);
37 |
38 | rows.forEach(row => {
39 | const { name: rowName, cols } = row;
40 |
41 | const rowPath = layoutPath + '/' + rowName;
42 | if (!existsSync(rowPath)) {
43 | mkdirSync(rowPath); // <- 创建行的文件夹
44 | }
45 | generateRow(rowPath, gutter, row);
46 |
47 | cols.forEach(col => {
48 | const { name: colName } = col;
49 |
50 | const colPath = rowPath + '/' + colName;
51 | if (!existsSync(colPath)) {
52 | mkdirSync(colPath);
53 | }
54 | generateCol(colPath, col);
55 | });
56 | });
57 | });
58 |
59 | return true;
60 | }
61 |
--------------------------------------------------------------------------------
/src/screen/templates/generateLayout.ts:
--------------------------------------------------------------------------------
1 | import { writeFileSync } from "fs";
2 | import { ScreenConfigPayloadLayout } from "../../../interfaces/screen";
3 | import { removeUnusedImport } from "../../utils/removeUnusedImport";
4 |
5 | /**
6 | * 生成布局组件
7 | * @param payload
8 | */
9 | export default function(layoutPath: string, layout: ScreenConfigPayloadLayout) {
10 | const code = `
11 | import React from 'react';
12 | import { Row, Col } from 'antd';
13 | ${layout.rows.map(item => `import ${item.name} from './${item.name}';`).join('')}
14 |
15 | export default () => {
16 | return (
17 |
18 |
24 | ${layout.rows.map(row => `<${row.name} />`).join('')}
25 |
26 |
27 | );
28 | }
29 | `;
30 | const removeUnusedImportCode = removeUnusedImport(code);
31 | if (removeUnusedImportCode) {
32 | writeFileSync(`${layoutPath}/index.tsx`, removeUnusedImportCode, 'utf-8');
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/screen/templates/generateRow.ts:
--------------------------------------------------------------------------------
1 | import { writeFileSync } from "fs";
2 | import { ScreenConfigPayloadRow } from "../../../interfaces/screen";
3 | import { removeUnusedImport } from "../../utils/removeUnusedImport";
4 |
5 | export default function(rowPath: string, gutter: number, row: ScreenConfigPayloadRow) {
6 | const code = `
7 | import React from 'react';
8 | import { Row, Col } from 'antd';
9 | ${row.cols.map(item => `import ${item.name} from './${item.name}';`).join('')}
10 |
11 | export default () => {
12 | return (
13 |
14 | ${row.cols.map(col => `<${col.name} />`).join('')}
15 |
16 | );
17 | }
18 | `;
19 | const removeUnusedImportCode = removeUnusedImport(code);
20 | if (removeUnusedImportCode) {
21 | writeFileSync(`${rowPath}/index.tsx`, removeUnusedImportCode, 'utf-8');
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/screen/templates/generateScreen.ts:
--------------------------------------------------------------------------------
1 | import { ScreenConfigPayload } from "../../../interfaces/screen";
2 | import { removeUnusedImport } from "../../utils/removeUnusedImport";
3 | import { writeFileSync } from "fs";
4 |
5 | /**
6 | * 生成大屏页面
7 | * @param payload
8 | */
9 | export default function(screenPath: string, payload: ScreenConfigPayload) {
10 | const { title, titleStyle, gutter, layout } = payload;
11 |
12 | const code = `
13 | import React from 'react';
14 | import { Row, Col } from 'antd';
15 | ${layout.map(item => `import ${item.name} from './components/${item.name}';`).join('')}
16 |
17 | export default () => {
18 | return (
19 |
20 |
21 |
22 | ${title}
23 |
24 |
25 |
26 | ${layout.map(item => `
27 | <${item.name} />
28 | `).join('')}
29 |
30 |
31 | );
32 | }
33 | `;
34 | const removeUnusedImportCode = removeUnusedImport(code);
35 | if (removeUnusedImportCode) {
36 | writeFileSync(`${screenPath}/index.tsx`, removeUnusedImportCode, 'utf-8');
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/utils/index.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * @文件描述:
3 | * @公司: thundersdata
4 | * @作者: 陈杰
5 | * @Date: 2020-05-01 18:11:49
6 | * @LastEditors: 陈杰
7 | * @LastEditTime: 2020-05-06 16:34:22
8 | */
9 | import fs from 'fs';
10 | import { join } from 'path';
11 | import { utils } from 'umi';
12 |
13 | const { winPath } = utils;
14 |
15 | export interface TreeData {
16 | title: string;
17 | value: string;
18 | key: string;
19 | children?: TreeData[];
20 | }
21 |
22 | /**
23 | * 遍历文件地址
24 | * @param path
25 | */
26 | export const getFolderTreeData = (
27 | path: string,
28 | parentPath: string = '/',
29 | depth: number = 0,
30 | ): TreeData[] => {
31 | const files = fs.readdirSync(winPath(path));
32 | return files
33 | .map((fileName: string) => {
34 | const status = fs.statSync(join(path, fileName));
35 | // 是文件夹 并且不已 . 开头, 且最深三层
36 | if (status.isDirectory() && fileName.indexOf('.') !== 0 && depth < 3) {
37 | const absPath = winPath(join(path, fileName));
38 | const absPagePath = winPath(join(parentPath, fileName));
39 | const children = getFolderTreeData(absPath, absPagePath, depth + 1);
40 | if (children && children.length > 0) {
41 | return {
42 | key: absPagePath,
43 | title: fileName,
44 | value: absPagePath,
45 | children,
46 | } as TreeData;
47 | }
48 | return {
49 | title: fileName,
50 | value: absPagePath,
51 | key: absPagePath,
52 | } as TreeData;
53 | }
54 | return undefined;
55 | })
56 | .filter(item => item !== undefined) as TreeData[];
57 | };
58 |
59 | /**
60 | * 遍历文件地址
61 | * 包含文件
62 | * @param path
63 | */
64 | export const getFilesTreeData = (
65 | path: string,
66 | parentPath: string = '/',
67 | depth: number = 0,
68 | ): TreeData[] => {
69 | const files = fs.readdirSync(winPath(path));
70 | return files
71 | .map((fileName: string) => {
72 | const status = fs.statSync(join(path, fileName));
73 | const isDirectory = status.isDirectory();
74 | // 是文件夹 并且不已 . 开头, 且最深五层
75 | if (fileName.indexOf('.') !== 0 && depth < 5) {
76 | if (
77 | !isDirectory &&
78 | !fileName.includes('.tsx') &&
79 | !fileName.includes('.jsx') &&
80 | !fileName.includes('.js') &&
81 | !fileName.includes('.ts')
82 | ) {
83 | return undefined;
84 | }
85 | const absPath = winPath(join(path, fileName));
86 | const absPagePath = winPath(join(parentPath, fileName));
87 | const children = isDirectory ? getFilesTreeData(absPath, absPagePath, depth + 1) : [];
88 | return {
89 | selectable: !isDirectory,
90 | key: absPagePath,
91 | title: fileName,
92 | value: absPagePath,
93 | children,
94 | };
95 | }
96 | return undefined;
97 | })
98 | .filter(obj => obj) as TreeData[];
99 | };
100 |
--------------------------------------------------------------------------------
/src/utils/removeUnusedImport.ts:
--------------------------------------------------------------------------------
1 | import * as types from '@babel/types';
2 | import * as parser from '@babel/parser';
3 | import traverse, { NodePath } from '@babel/traverse';
4 | import generate from '@babel/generator';
5 |
6 | export function removeUnusedImport(code: string) {
7 | // 解析代码,构建ast
8 | const ast: types.File = parser.parse(code, {
9 | sourceType: 'module',
10 | plugins: ['jsx', 'typescript'],
11 | });
12 |
13 | const opts = {
14 | ignore: ['react'],
15 | };
16 |
17 | traverse(ast, {
18 | Program: {
19 | enter: path => {
20 | for (const [, binding] of Object.entries(path.scope.bindings)) {
21 | if (binding.kind === 'module') {
22 | if (!binding.referenced) {
23 | const source = binding.path.parentPath.get('source');
24 | if (
25 | types.isStringLiteral(source) &&
26 | (!opts.ignore || !match(opts.ignore, (source as NodePath).node.value))
27 | ) {
28 | if (binding.path.node.type === 'ImportSpecifier') {
29 | binding.path.remove();
30 | } else if (binding.path.parentPath) {
31 | binding.path.parentPath.remove();
32 | }
33 | }
34 | }
35 | }
36 | }
37 | },
38 | },
39 | });
40 | return generate(ast, {}).code;
41 | }
42 |
43 | function match(rule: any, value: any): boolean {
44 | if (typeof rule === 'string') {
45 | return rule === value;
46 | }
47 | if (rule instanceof RegExp) {
48 | return rule.test(value);
49 | }
50 | if (typeof rule === 'function') {
51 | return rule(value);
52 | }
53 | if (Array.isArray(rule)) {
54 | return rule.some(r => match(r, value));
55 | }
56 | return false;
57 | }
58 |
--------------------------------------------------------------------------------
/src/utils/writeNewMenu.ts:
--------------------------------------------------------------------------------
1 | import { writeFileSync, existsSync, readFileSync } from 'fs';
2 | import * as types from '@babel/types';
3 | import * as parser from '@babel/parser';
4 | import traverse from '@babel/traverse';
5 | import generate from '@babel/generator';
6 | import prettier from 'prettier';
7 |
8 | interface Resource {
9 | menu: string;
10 | path: string;
11 | }
12 |
13 | export function writeNewMenu(resource: Resource, mockPath: string) {
14 | const code = getNewMenuCode(resource, mockPath);
15 | writeFileSync(mockPath, code, 'utf-8');
16 | }
17 |
18 | /**
19 | * 生成权限资源的mock数据,方便系统构建菜单
20 | * @param resource
21 | * @param mockPath
22 | */
23 | function getNewMenuCode(resource: Resource, mockPath: string) {
24 | const { menu, path } = resource;
25 | if (existsSync(mockPath)) {
26 | // 解析文件内容,构建ast
27 | const ast: types.File = parser.parse(readFileSync(mockPath, 'utf-8'), {
28 | sourceType: 'module',
29 | plugins: ['typescript'],
30 | });
31 |
32 | // 对ast的节点进行处理,在指定的位置插入新节点
33 | traverse(ast, {
34 | ObjectExpression({ node, parent }) {
35 | if (types.isArrayExpression(parent)) {
36 | return;
37 | }
38 | const { properties } = node;
39 | const property = properties.find(
40 | p => types.isObjectProperty(p) && types.isIdentifier(p.key) && p.key.name === 'data',
41 | );
42 | if (property) {
43 | // data: []
44 | const { elements = [] } = (property as types.ObjectProperty)
45 | .value as types.ArrayExpression;
46 |
47 | if (!menu.includes('/') && !path.substr(1).includes('/') && path.startsWith('/')) {
48 | // 只是一级目录
49 | const newNode = (parser.parse(
50 | `(${JSON.stringify({
51 | key: path,
52 | apiUrl: path,
53 | description: menu,
54 | children: [],
55 | })})`,
56 | ).program.body[0] as any).expression;
57 | elements.push(newNode);
58 | } else {
59 | // 有二级目录
60 | const menus = menu.split('/').filter(item => item);
61 | const _paths = path.split('/').filter(item => item);
62 | const paths = ['/' + _paths[0], !path.startsWith('/') ? '/' + path : path];
63 |
64 | /**
65 | * 这里的逻辑比较复杂,注释一下:
66 | * 1. 首先判断一级目录是否存在:
67 | * a). 如果不存在,则直接保存整个对象
68 | * b). 如果存在,进行第二步判断
69 | * 2. 二级目录是否存在:
70 | * a). 如果不存在,在children最后插入
71 | */
72 | const submenu = {
73 | key: paths[0],
74 | apiUrl: paths[0],
75 | description: menus[0],
76 | children: [
77 | {
78 | key: paths[1],
79 | apiUrl: paths[1],
80 | description: menus.length === 2 ? menus[1] : menus[0],
81 | children: [],
82 | },
83 | ],
84 | };
85 | const menuItem = {
86 | key: paths[1],
87 | apiUrl: paths[1],
88 | description: menus.length === 2 ? menus[1] : menus[0],
89 | children: [],
90 | };
91 |
92 | /**
93 | * 遍历data的elements(也就是data下的对象(key/apiUrl/description/children)),
94 | * elements里面有properties属性,是一个对象。这个对象就是element的键值对
95 | * 在这个键值对里面找到key.name === submenu.key
96 | * 如果找到了,说明一级目录存在,如果没有找到,说明以及目录不存在
97 | * 一级目录存在的情况下,再从这个element的键值对里面去找key.name === 'children',它也是一个elements,再重复上面的步骤判断二级目录是否存在
98 | */
99 | let node = null;
100 | let childrenNode = null;
101 | for (const ele of elements) {
102 | const { properties } = ele as types.ObjectExpression;
103 | node = properties.find(
104 | p =>
105 | ((p as types.ObjectProperty).value as types.StringLiteral).value === submenu.key,
106 | );
107 | if (node) {
108 | childrenNode = properties.find(
109 | p => (p as types.ObjectProperty).key.name === 'children',
110 | );
111 | }
112 | }
113 | if (!node) {
114 | // 父菜单也不存在的话,直接把整个结构添加进去
115 | elements.push(
116 | (parser.parse(`(${JSON.stringify(submenu)})`).program.body[0] as any).expression,
117 | );
118 | } else if (childrenNode) {
119 | const { elements: childrenElements } = (childrenNode as types.ObjectProperty)
120 | .value as types.ArrayExpression;
121 | if (childrenElements.length === 0) {
122 | childrenElements.push(
123 | (parser.parse(`(${JSON.stringify(menuItem)})`).program.body[0] as any).expression,
124 | );
125 | } else {
126 | // 判断要添加的菜单是否已经存在,已经存在的话就不添加
127 | let existed = false;
128 | for (const ele of childrenElements) {
129 | const { properties } = ele as types.ObjectExpression;
130 | node = properties.find(
131 | p =>
132 | ((p as types.ObjectProperty).value as types.StringLiteral).value ===
133 | menuItem.key,
134 | );
135 | if (node) {
136 | existed = true;
137 | break;
138 | }
139 | }
140 | !existed &&
141 | childrenElements.unshift(
142 | (parser.parse(`(${JSON.stringify(menuItem)})`).program.body[0] as any)
143 | .expression,
144 | );
145 | }
146 | }
147 | }
148 | }
149 | },
150 | });
151 |
152 | // 根据处理之后的ast生成最终的代码
153 | const code = generate(ast, {}).code;
154 | return prettier.format(code, {
155 | singleQuote: true,
156 | trailingComma: 'es5',
157 | printWidth: 100,
158 | parser: 'typescript',
159 | });
160 | }
161 | return '';
162 | }
163 |
--------------------------------------------------------------------------------
/src/utils/writeNewRoute.ts:
--------------------------------------------------------------------------------
1 | import { readFileSync, writeFileSync, existsSync } from 'fs';
2 | import { join, dirname } from 'path';
3 | import * as parser from '@babel/parser';
4 | import traverse from '@babel/traverse';
5 | import generate from '@babel/generator';
6 | import * as t from '@babel/types';
7 | import { winPath, createDebug } from '@umijs/utils';
8 | import prettier from 'prettier';
9 |
10 | const debug = createDebug('umi-build-dev:writeNewRoute');
11 |
12 | type Route = {
13 | path: string;
14 | component: string;
15 | exact?: boolean;
16 | title?: string;
17 | };
18 |
19 | /**
20 | * 将路由写入路由文件
21 | * @param {*} newRoute 新的路由配置: { path, component, ... }
22 | * @param {*} configPath 配置路径
23 | * @param {*} absSrcPath 代码路径
24 | */
25 | export function writeNewRoute(newRoute: Route, configPath: string, absSrcPath: string) {
26 | const { code, routesPath } = getNewRouteCode(configPath, newRoute, absSrcPath);
27 | writeFileSync(routesPath, code, 'utf-8');
28 | }
29 |
30 | /**
31 | * 获取目标
32 | * @param {*} configPath
33 | * @param {*} newRoute
34 | */
35 | export function getNewRouteCode(configPath: string, newRoute: Route, absSrcPath: string) {
36 | debug(`find routes in configPath: ${configPath}`);
37 | const ast: any = parser.parse(readFileSync(configPath, 'utf-8'), {
38 | sourceType: 'module',
39 | plugins: ['typescript'],
40 | });
41 | let routesNode = null;
42 | const importModules = [];
43 | // 查询当前配置文件是否导出 routes 属性
44 | traverse(ast, {
45 | Program({ node }) {
46 | // find import
47 | const { body } = node;
48 | body.forEach(item => {
49 | if (t.isImportDeclaration(item)) {
50 | const { specifiers } = item;
51 | const defaultSpecifier = specifiers.find(
52 | s => t.isImportDefaultSpecifier(s) && t.isIdentifier(s.local),
53 | );
54 | if (defaultSpecifier && t.isStringLiteral(item.source)) {
55 | importModules.push({
56 | identifierName: defaultSpecifier.local.name,
57 | modulePath: item.source.value,
58 | });
59 | }
60 | }
61 | });
62 | },
63 | AssignmentExpression({ node }) {
64 | // for exports.routes
65 | const { left, operator, right } = node;
66 | if (
67 | operator === '=' &&
68 | t.isMemberExpression(left) &&
69 | t.isIdentifier(left.object) &&
70 | left.object.name === 'exports' &&
71 | t.isIdentifier(left.property) &&
72 | left.property.name === 'routes'
73 | ) {
74 | routesNode = right;
75 | }
76 | },
77 | ExportDefaultDeclaration({ node }) {
78 | // export default []
79 | const { declaration } = node;
80 | if (t.isArrayExpression(declaration)) {
81 | routesNode = declaration;
82 | }
83 | },
84 | ObjectExpression({ node, parent }) {
85 | // find routes on object, like { routes: [] }
86 | if (t.isArrayExpression(parent)) {
87 | // children routes
88 | return;
89 | }
90 | const { properties } = node;
91 | properties.forEach((p: any) => {
92 | const { key, value } = p;
93 | if (t.isObjectProperty(p) && t.isIdentifier(key) && key.name === 'routes') {
94 | routesNode = value;
95 | }
96 | });
97 | },
98 | });
99 | if (routesNode) {
100 | // routes 配置不在当前文件, 需要 load 对应的文件 export default { routes: pageRoutes } case 1
101 | if (!t.isArrayExpression(routesNode)) {
102 | const source = importModules.find(m => m.identifierName === routesNode.name);
103 | if (source) {
104 | const newConfigPath = getModulePath(configPath, source.modulePath, absSrcPath);
105 | return getNewRouteCode(newConfigPath, newRoute, absSrcPath);
106 | }
107 | throw new Error(`can not find import of ${routesNode.name}`);
108 | } else {
109 | // 配置在当前文件 // export default { routes: [] } case 2
110 | writeRouteNode(routesNode, newRoute);
111 | }
112 | } else {
113 | throw new Error('route array config not found.');
114 | }
115 | const code = generateCode(ast);
116 | debug(code, configPath);
117 | return { code, routesPath: configPath };
118 | }
119 |
120 | function getNewRouteNode(newRoute: any) {
121 | return (parser.parse(`(${JSON.stringify(newRoute)})`).program.body[0] as any).expression;
122 | }
123 |
124 | /**
125 | * 写入节点
126 | * @param {*} node 找到的节点
127 | * @param {*} newRoute 新的路由配置
128 | */
129 | export function writeRouteNode(targetNode, newRoute: Route, currentPath = '/') {
130 | debug(`writeRouteNode currentPath newRoute.path: ${newRoute.path} currentPath: ${currentPath}`);
131 | const { elements } = targetNode;
132 | const paths: string[] = elements.map(ele => {
133 | if (!t.isObjectExpression(ele)) {
134 | return false;
135 | }
136 | const { properties } = ele;
137 | const redirect = properties.find((p: any) => p.key.name === 'redirect');
138 | if (redirect) {
139 | return false;
140 | }
141 | const pathProp: any = properties.find((p: any) => p.key.name === 'path');
142 | if (!pathProp) {
143 | return currentPath;
144 | }
145 | let fullPath: string = pathProp.value.value;
146 | if (fullPath.indexOf('/') !== 0) {
147 | fullPath = join(currentPath, fullPath);
148 | }
149 | return fullPath;
150 | });
151 | debug('paths', paths);
152 | const matchedIndex = paths.findIndex(p => p && newRoute.path.indexOf(winPath(p)) === 0);
153 | const newNode = getNewRouteNode(newRoute);
154 | if (matchedIndex === -1) {
155 | elements.unshift(newNode);
156 | // return container for test
157 | return targetNode;
158 | }
159 | // matched, insert to children routes
160 | const matchedEle = elements[matchedIndex];
161 | const routesProp = matchedEle.properties.find(p => p.key.name === 'routes');
162 | if (!routesProp) {
163 | // not find children routes, insert before the matched element
164 | elements.splice(matchedIndex, 0, newNode);
165 | return targetNode;
166 | }
167 | return writeRouteNode(routesProp.value, newRoute, paths[matchedIndex]);
168 | }
169 |
170 | /**
171 | * 生成代码
172 | * @param {*} ast
173 | */
174 | function generateCode(ast) {
175 | const newCode = generate(ast, {}).code;
176 | return prettier.format(newCode, {
177 | singleQuote: true,
178 | trailingComma: 'es5',
179 | printWidth: 100,
180 | parser: 'typescript',
181 | });
182 | }
183 |
184 | /**
185 | * 获取路由配置的真实路径
186 | * @param {*} modulePath
187 | */
188 | function getModulePath(configPath: string, modulePath: string, absSrcPath: string) {
189 | // like @/route.config
190 | if (/^@\//.test(modulePath)) {
191 | modulePath = join(absSrcPath, modulePath.replace(/^@\//, ''));
192 | } else {
193 | modulePath = join(dirname(configPath), modulePath);
194 | }
195 | if (!/\.[j|t]s$/.test(modulePath)) {
196 | if (existsSync(`${modulePath}.js`)) {
197 | modulePath = `${modulePath}.js`;
198 | } else {
199 | modulePath = `${modulePath}.ts`;
200 | }
201 | }
202 | return modulePath;
203 | }
204 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "esnext",
4 | "moduleResolution": "node",
5 | "jsx": "preserve",
6 | "allowJs": true,
7 | "esModuleInterop": true,
8 | "experimentalDecorators": true,
9 | "strict": true,
10 | "forceConsistentCasingInFileNames": true,
11 | "noImplicitReturns": true,
12 | "suppressImplicitAnyIndexErrors": true,
13 | "declaration": true,
14 | "resolveJsonModule": true
15 | },
16 | "exclude": ["node_modules", "dist", "**/*.spec.ts", "lib", "fixtures", "examples"]
17 | }
18 |
--------------------------------------------------------------------------------
/ui/components/ConfigActions/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { CSSProperties } from 'react';
2 | import { Button, Tooltip } from 'antd';
3 | import {
4 | ArrowUpOutlined,
5 | ArrowDownOutlined,
6 | ArrowLeftOutlined,
7 | ArrowRightOutlined,
8 | SettingOutlined,
9 | DeleteOutlined,
10 | CopyOutlined,
11 | } from '@ant-design/icons';
12 |
13 | export default ({
14 | moveUp,
15 | moveDown,
16 | configItem,
17 | deleteItem,
18 | copyItem,
19 | position = 'left',
20 | style,
21 | }: {
22 | position?: 'left' | 'top';
23 | moveUp?: () => void;
24 | moveDown?: () => void;
25 | configItem?: () => void;
26 | deleteItem?: () => void;
27 | copyItem?: () => void;
28 | style?: CSSProperties;
29 | }) => {
30 | return (
31 |
39 |
40 |
43 |
44 |
45 |
48 |
49 |
50 |
53 |
54 |
55 |
58 |
59 |
60 |
63 |
64 |
65 | );
66 | };
67 |
--------------------------------------------------------------------------------
/ui/components/FormItemConfig/index.module.less:
--------------------------------------------------------------------------------
1 | .formItemConfig {
2 | display: flex;
3 | align-items: flex-start;
4 |
5 | :global {
6 | .ant-form-item {
7 | flex: 1;
8 | width: 100%;
9 | }
10 | }
11 | }
12 | .top {
13 | flex-direction: column;
14 | }
15 |
--------------------------------------------------------------------------------
/ui/components/FormItemConfigDrawer/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useContext, useState, useMemo } from 'react';
2 | import { Drawer, Form, Input, Button, Radio, Tooltip, Select } from 'antd';
3 | import { QuestionCircleOutlined } from '@ant-design/icons';
4 | import { FormItemProps, FormItemType } from '../../../interfaces/common';
5 | import { Store } from 'antd/lib/form/interface';
6 | import * as itemProps from './props';
7 | import renderFormItem from '../FormItemConfig';
8 | import { filterEmpty } from '../../utils';
9 | import Context from '../../pages/manage/Context';
10 |
11 | const { Option } = Select;
12 |
13 | export default ({
14 | visible,
15 | onVisible,
16 | index,
17 | formItem,
18 | onConfirm,
19 | submitFetch,
20 | initialFetch,
21 | from = 'form',
22 | }: {
23 | visible: boolean;
24 | onVisible: (visible: boolean) => void;
25 | index?: number;
26 | formItem: FormItemProps;
27 | onConfirm: (index: number, formItem: FormItemProps) => void;
28 | submitFetch?: string[];
29 | initialFetch?: string[];
30 | from?: 'form' | 'detail';
31 | }) => {
32 | const { baseClasses = [] } = useContext(Context);
33 |
34 | const [form] = Form.useForm();
35 | const { name, type, label, placeholder, ...restProps } = formItem;
36 |
37 | const [paramsName, setParamsName] = useState();
38 | const [responseName, setResponseName] = useState();
39 |
40 | useEffect(() => {
41 | form.setFieldsValue(formItem);
42 | }, [formItem]);
43 |
44 | /** submitFetch中第三个值为value-paramsName-responseName,提交表单数据选用paramsName作为DTO */
45 | useEffect(() => {
46 | if (submitFetch && submitFetch.length === 3) {
47 | const paramsName = submitFetch[2].split('-')[1];
48 | setParamsName(paramsName);
49 | }
50 | }, [submitFetch]);
51 |
52 | /** initialFetch中第三个值为value-paramsName-responseName,获取初始数据选用responseName作为DTO */
53 | useEffect(() => {
54 | if (initialFetch && initialFetch.length === 3) {
55 | const responseName = initialFetch[2].split('-')[2];
56 | setResponseName(responseName);
57 | }
58 | }, [initialFetch]);
59 |
60 | const handleFinish = (values: Store) => {
61 | if (index !== undefined) {
62 | const { prop, ...restValues } = values;
63 | onConfirm(index, { ...formItem, ...filterEmpty(restValues) });
64 | onVisible(false);
65 | form.resetFields();
66 | }
67 | };
68 |
69 | const properties = useMemo(() => {
70 | if (from === 'form') {
71 | return baseClasses.find(item => item.name === paramsName)?.properties || [];
72 | }
73 | return baseClasses.find(item => item.name === responseName)?.properties || [];
74 | }, [baseClasses, paramsName, from, responseName]);
75 |
76 | const handleChange = (value: string) => {
77 | const matchClass = properties.find(item => item.value === value);
78 | form.setFieldsValue({
79 | label: matchClass?.label,
80 | name: value,
81 | required: matchClass?.required,
82 | });
83 | };
84 |
85 | const propVisible =
86 | (from === 'form' && submitFetch && submitFetch.length > 0) ||
87 | (from === 'detail' && initialFetch && initialFetch.length > 0);
88 |
89 | return (
90 | onVisible(false)}
94 | width={400}
95 | >
96 |
105 |
113 |
114 | )}
115 |
121 |
122 |
123 |
129 |
130 |
131 | {from === 'form' ? (
132 | <>
133 |
139 |
145 |
146 |
149 | 自定义规则
150 |
161 |
162 |
163 |
164 | }
165 | name="customRules"
166 | >
167 |
168 |
169 |
170 |
171 |
172 | {renderOtherProps(type)}
173 | >
174 | ) : (
175 |
176 |
180 |
181 | )}
182 |
183 |
186 |
187 |
188 |
189 | );
190 | };
191 |
192 | /**
193 | * 渲染表单元素本身的属性
194 | * @param type
195 | * @param props
196 | */
197 | function renderOtherProps(type: FormItemType) {
198 | let props: FormItemProps[] = itemProps[`${type}Props`];
199 | return props.map(item => renderFormItem({ formItem: item }));
200 | }
201 |
--------------------------------------------------------------------------------
/ui/components/FormItemsDrawer/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import {
3 | Drawer,
4 | Form,
5 | Cascader,
6 | Checkbox,
7 | Input,
8 | DatePicker,
9 | InputNumber,
10 | Radio,
11 | Switch,
12 | Slider,
13 | Select,
14 | TreeSelect,
15 | Upload,
16 | TimePicker,
17 | Rate,
18 | Button,
19 | } from 'antd';
20 | import { UploadOutlined } from '@ant-design/icons';
21 | import { FormItemType } from '../../../interfaces/common';
22 | import { CheckboxChangeEvent } from 'antd/lib/checkbox';
23 |
24 | const { RangePicker } = DatePicker;
25 | const { Option } = Select;
26 |
27 | export default ({
28 | setVisible,
29 | visible,
30 | onSubmit,
31 | }: {
32 | visible: boolean;
33 | setVisible: (visible: boolean) => void;
34 | onSubmit: (checkedComponents: FormItemType[]) => void;
35 | }) => {
36 | const [checkedComponents, setCheckedComponents] = useState([]);
37 |
38 | /** 复选框是否选中 */
39 | const handleChange = (type: FormItemType) => (e: CheckboxChangeEvent) => {
40 | if (e.target.checked) {
41 | // 加到checkedComponents里面
42 | setCheckedComponents(checked => [...checked, type]);
43 | } else {
44 | // 从checkedComponents移出
45 | const checked = checkedComponents.slice();
46 | const index = checked.findIndex(item => item === type);
47 | if (index > -1) {
48 | checked.splice(index, 1);
49 | }
50 | setCheckedComponents(checked);
51 | }
52 | };
53 |
54 | /** 将选中的这些表单元素传出去 */
55 | const handleSubmit = () => {
56 | onSubmit(checkedComponents);
57 | setCheckedComponents([]);
58 | };
59 |
60 | const handleClose = () => {
61 | setVisible(false);
62 | setCheckedComponents([]);
63 | };
64 |
65 | return (
66 |
67 | 文本输入框}>
69 |
70 |
71 | 密码输入框}>
72 |
73 |
74 | 文本域输入框}>
75 |
76 |
77 | 级联选择}>
78 |
114 |
115 | 日期选择器}>
116 |
117 |
118 | 日期范围选择器}>
119 |
120 |
121 | 时间选择器}>
122 |
123 |
124 | 数字输入框}>
125 |
126 |
127 | 单选框}>
128 |
129 | 男
130 | 女
131 |
132 |
133 | 复选框}>
134 |
135 |
136 | 开关}>
137 |
138 |
139 | 滑动输入条}>
140 |
141 |
142 | 下拉选择}>
143 |
148 |
149 | 树形选择}>
150 |
173 |
174 | 上传}>
175 |
176 |
179 |
180 |
181 | 评分}>
182 |
183 |
184 |
187 |
188 |
189 | );
190 | };
191 |
--------------------------------------------------------------------------------
/ui/components/Title/index.module.less:
--------------------------------------------------------------------------------
1 | .title {
2 | height: 32px;
3 | line-height: 32px;
4 |
5 | ::before {
6 | content: '';
7 | display: inline-block;
8 | height: 16px;
9 | width: 4px;
10 | background: #0189fb;
11 | margin-right: 10px;
12 | transform: translateY(2px);
13 | }
14 |
15 | span {
16 | display: inline-block;
17 | flex: 1;
18 | overflow: hidden;
19 | white-space: nowrap;
20 | text-overflow: ellipsis;
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/ui/components/Title/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import styles from './index.module.less';
3 |
4 | export default ({ text = '' }: { text: string }) => (
5 |
6 | {text}
7 |
8 | );
9 |
--------------------------------------------------------------------------------
/ui/hooks/useCard.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * @文件描述: 对表单或者详情页的Card的操作的封装。包含上移、下移、复制
3 | * @公司: thundersdata
4 | * @作者: 陈杰
5 | * @Date: 2020-05-11 11:07:24
6 | * @LastEditors: 陈杰
7 | * @LastEditTime: 2020-05-14 18:28:44
8 | */
9 | import { useState } from 'react';
10 | import { CardItemProps, FormItemProps, FormItemType } from '../../interfaces/common';
11 | import { Store } from 'antd/lib/form/interface';
12 | import produce from 'immer';
13 | import faker from 'faker';
14 |
15 | export default function useCard() {
16 | const [cards, setCards] = useState([{ title: '自定义Card0', formItems: [] }]);
17 | const [cardIndex, setCardIndex] = useState(0);
18 | const [currentCard, setCurrentCard] = useState();
19 | const [itemIndex, setItemIndex] = useState();
20 | const [currentItem, setCurrentItem] = useState();
21 |
22 | /**
23 | * 上移Card
24 | * @param index
25 | */
26 | const moveCardUp = (index: number) => () => {
27 | if (index === 0) return;
28 |
29 | setCards(
30 | produce(cards, draft => {
31 | const card = draft.splice(index, 1);
32 | draft.splice(index - 1, 0, ...card);
33 | }),
34 | );
35 | };
36 |
37 | /**
38 | * 下移Card
39 | * @param index
40 | */
41 | const moveCardDown = (index: number) => () => {
42 | if (index === cards.length - 1) return;
43 |
44 | setCards(
45 | produce(cards, draft => {
46 | const card = draft.splice(index, 1);
47 | draft.splice(index + 1, 0, ...card);
48 | }),
49 | );
50 | };
51 |
52 | /**
53 | * 配置Card
54 | * @param values
55 | */
56 | const configCard = (values: Store) => {
57 | setCards(
58 | produce(cards, draft => {
59 | draft[cardIndex].title = values.title;
60 | }),
61 | );
62 | };
63 |
64 | /** 往Card中添加表单项 */
65 | const addFormItemsToCard = (checkedComponents: FormItemType[]) => {
66 | const newFormItems = checkedComponents.map(type => ({
67 | type,
68 | name: faker.name.lastName(),
69 | label: faker.name.title(),
70 | }));
71 | setCards(
72 | produce(cards, draft => {
73 | draft[cardIndex].formItems.push(...newFormItems);
74 | }),
75 | );
76 | };
77 |
78 | /**
79 | * 删除Card
80 | * @param index
81 | */
82 | const deleteCard = (index: number) => () => {
83 | setCards(
84 | produce(cards, draft => {
85 | draft.splice(index, 1);
86 | }),
87 | );
88 | };
89 |
90 | /**
91 | * 复制card
92 | * @param index
93 | */
94 | const copyCard = (index: number) => () => {
95 | setCards(
96 | produce(cards, draft => {
97 | draft.splice(index + 1, 0, draft[index]);
98 | }),
99 | );
100 | };
101 |
102 | /**
103 | * card里面的某个form配置项上移
104 | * @param cardIndex
105 | * @param itemIndex
106 | */
107 | const moveItemUp = (itemIndex: number, cardIndex: number) => () => {
108 | setCards(
109 | produce(cards, draft => {
110 | const card = draft[cardIndex];
111 | const { formItems = [] } = card;
112 | if (itemIndex === 0) return;
113 | const items = formItems.splice(itemIndex, 1);
114 | formItems.splice(itemIndex - 1, 0, ...items);
115 | }),
116 | );
117 | };
118 |
119 | /**
120 | * card里面某个form配置项下移
121 | * @param cardIndex
122 | * @param itemIndex
123 | */
124 | const moveItemDown = (itemIndex: number, cardIndex: number) => () => {
125 | setCards(
126 | produce(cards, draft => {
127 | const card = draft[cardIndex];
128 | const { formItems = [] } = card;
129 | if (itemIndex === formItems.length - 1) return;
130 | const items = formItems.splice(itemIndex, 1);
131 | formItems.splice(itemIndex + 1, 0, ...items);
132 | }),
133 | );
134 | };
135 |
136 | /**
137 | * 配置某个Card的某个表单项
138 | * @param itemIndex
139 | * @param formItem
140 | */
141 | const configItem = (itemIndex: number, formItem: FormItemProps) => {
142 | setCards(
143 | produce(cards, draft => {
144 | const { formItems = [] } = draft[cardIndex];
145 | formItems[itemIndex] = formItem;
146 | }),
147 | );
148 | };
149 |
150 | /**
151 | * 删除某个Card的某个表单项
152 | * @param cardIndex
153 | * @param itemIndex
154 | */
155 | const deleteItem = (itemIndex: number, cardIndex: number) => () => {
156 | setCards(
157 | produce(cards, draft => {
158 | const card = draft[cardIndex];
159 | const { formItems = [] } = card;
160 | formItems.splice(itemIndex, 1);
161 | }),
162 | );
163 | };
164 |
165 | /**
166 | * 复制某个card的某个表单项
167 | * @param cardIndex
168 | * @param itemIndex
169 | */
170 | const copyItem = (itemIndex: number, cardIndex: number) => () => {
171 | setCards(
172 | produce(cards, draft => {
173 | const card = draft[cardIndex];
174 | const { formItems = [] } = card;
175 | const formItem = {
176 | ...formItems[itemIndex],
177 | name: faker.name.lastName(),
178 | };
179 | formItems.splice(itemIndex + 1, 0, formItem);
180 | }),
181 | );
182 | };
183 |
184 | return {
185 | cards,
186 | setCards,
187 | moveCardUp,
188 | moveCardDown,
189 | configCard,
190 | deleteCard,
191 | copyCard,
192 | moveItemUp,
193 | moveItemDown,
194 | configItem,
195 | deleteItem,
196 | copyItem,
197 | cardIndex,
198 | setCardIndex,
199 | currentCard,
200 | addFormItemsToCard,
201 | currentItem,
202 | setCurrentItem,
203 | itemIndex,
204 | setItemIndex,
205 | };
206 | }
207 |
--------------------------------------------------------------------------------
/ui/hooks/useConfig.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * @文件描述:
3 | * @公司: thundersdata
4 | * @作者: 黄姗姗
5 | * @Date: 2020-05-28 17:35:45
6 | * @LastEditors: 黄姗姗
7 | * @LastEditTime: 2020-05-28 17:44:33
8 | */
9 | import { useState } from 'react';
10 |
11 | export default function useConfig() {
12 | const [initialFetch, setInitialFetch] = useState();
13 | const [submitFetch, setSubmitFetch] = useState();
14 |
15 | return {
16 | initialFetch,
17 | setInitialFetch,
18 | submitFetch,
19 | setSubmitFetch,
20 | }
21 | }
--------------------------------------------------------------------------------
/ui/hooks/useConfigVisible.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * @文件描述:
3 | * @公司: thundersdata
4 | * @作者: 陈杰
5 | * @Date: 2020-05-11 15:23:37
6 | * @LastEditors: 黄姗姗
7 | * @LastEditTime: 2020-05-29 10:41:43
8 | */
9 | import { useState } from 'react';
10 |
11 | export default function useConfigVisible() {
12 | const [formItemsDrawerVisible, setFormItemsDrawerVisible] = useState(false);
13 | const [pathModalVisible, setPathModalVisible] = useState(false);
14 | const [formConfigDrawerVisible, setFormConfigDrawerVisible] = useState(false);
15 | const [cardDrawerVisible, setCardDrawerVisible] = useState(false);
16 | const [formItemConfigDrawerVisible, setFormItemConfigDrawerVisible] = useState(false);
17 | const [tableConfigDrawerVisible, setTableConfigDrawerVisible] = useState(false);
18 | const [columnConfigDrawerVisible, setColumnConfigDrawerVisible] = useState(false);
19 | const [apiConfigDrawerVisible, setApiConfigDrawerVisible] = useState(false);
20 |
21 | return {
22 | formItemsDrawerVisible,
23 | pathModalVisible,
24 | formConfigDrawerVisible,
25 | cardDrawerVisible,
26 | formItemConfigDrawerVisible,
27 | tableConfigDrawerVisible,
28 | columnConfigDrawerVisible,
29 | apiConfigDrawerVisible,
30 | setFormItemsDrawerVisible,
31 | setPathModalVisible,
32 | setFormConfigDrawerVisible,
33 | setCardDrawerVisible,
34 | setFormItemConfigDrawerVisible,
35 | setTableConfigDrawerVisible,
36 | setColumnConfigDrawerVisible,
37 | setApiConfigDrawerVisible,
38 | };
39 | }
40 |
--------------------------------------------------------------------------------
/ui/hooks/useFormItem.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * @文件描述: 对表单元素操作的封装,包含上移、下移、配置
3 | * @公司: thundersdata
4 | * @作者: 陈杰
5 | * @Date: 2020-04-30 11:31:49
6 | * @LastEditors: 陈杰
7 | * @LastEditTime: 2020-05-13 10:32:59
8 | */
9 | import { useState } from 'react';
10 | import { FormItemProps } from '../../interfaces/common';
11 |
12 | export default function useFormItem() {
13 | const [formItems, setFormItems] = useState([]);
14 | const [index, setIndex] = useState(); // 当前选中的表单元素的index
15 | const [currentItem, setCurrentItem] = useState(); // 当前选中的表单元素
16 |
17 | /**
18 | * 上移表单项
19 | * @param index
20 | */
21 | const moveUp = (index: number) => () => {
22 | if (index === 0) return;
23 | const _formItems = formItems.slice();
24 |
25 | // 拿到这个index对应的item
26 | const formItem = _formItems.splice(index, 1);
27 | // 插入回去
28 | _formItems.splice(index - 1, 0, ...formItem);
29 |
30 | setFormItems(_formItems);
31 | };
32 | /**
33 | * 下移表单项
34 | * @param index
35 | */
36 | const moveDown = (index: number) => () => {
37 | if (index === formItems.length - 1) return;
38 | const _formItems = formItems.slice();
39 |
40 | // 拿到这个index对应的item
41 | const formItem = _formItems.splice(index, 1);
42 | // 插入回去
43 | _formItems.splice(index + 1, 0, ...formItem);
44 |
45 | setFormItems(_formItems);
46 | };
47 | /**
48 | * 配置表单项
49 | * @param formItem
50 | * @param index
51 | */
52 | const configItem = (formItem: FormItemProps, index: number) => {
53 | setIndex(index);
54 | setCurrentItem(formItem);
55 | };
56 |
57 | /** 表单元素配置项配置完成 */
58 | const handleConfirm = (index: number, formItem: FormItemProps) => {
59 | const _formItems = formItems.slice();
60 | const item = _formItems.splice(index, 1);
61 | const mergedItem = {
62 | ...item[0],
63 | ...formItem,
64 | };
65 | _formItems.splice(index, 0, mergedItem);
66 | setFormItems(_formItems);
67 | };
68 |
69 | /**
70 | * 删除配置项
71 | * @param index
72 | */
73 | const deleteItem = (index: number) => () => {
74 | const _formItems = formItems.slice();
75 | _formItems.splice(index, 1);
76 | setFormItems(_formItems);
77 | };
78 |
79 | /**
80 | * 复制配置项
81 | * @param index
82 | */
83 | const copyItem = (index: number) => () => {
84 | const _formItems = formItems.slice();
85 | const formItem = _formItems[index];
86 | _formItems.splice(index + 1, 0, formItem);
87 |
88 | setFormItems(_formItems);
89 | };
90 |
91 | return {
92 | formItems,
93 | setFormItems,
94 | moveUp,
95 | moveDown,
96 | configItem,
97 | deleteItem,
98 | copyItem,
99 | index,
100 | currentItem,
101 | onConfirm: handleConfirm,
102 | };
103 | }
104 |
--------------------------------------------------------------------------------
/ui/hooks/useTable.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * @文件描述: 对表格的列的操作
3 | * @公司: thundersdata
4 | * @作者: 陈杰
5 | * @Date: 2020-04-30 11:31:49
6 | * @LastEditors: 陈杰
7 | * @LastEditTime: 2020-05-14 11:44:43
8 | */
9 | import { useState } from 'react';
10 | import { ColumnType } from 'antd/lib/table';
11 | import { useImmer } from 'use-immer';
12 |
13 | export default function useTable() {
14 | const [columns, setColumns] = useImmer[]>([]);
15 | const [index, setIndex] = useState(0); // 当前选中的表单元素的index
16 | const [currentColumn, setCurrentColumn] = useState>(); // 当前选中的表单元素
17 |
18 | /**
19 | * 上移表单项
20 | * @param index
21 | */
22 | const moveUp = (index: number) => () => {
23 | if (index === 0) return;
24 |
25 | setColumns(draft => {
26 | // 拿到这个index对应的item
27 | const column = draft.splice(index, 1);
28 | // 插入回去
29 | draft.splice(index - 1, 0, ...column);
30 | });
31 | };
32 | /**
33 | * 下移表单项
34 | * @param index
35 | */
36 | const moveDown = (index: number) => () => {
37 | if (index === columns.length - 1) return;
38 |
39 | setColumns(draft => {
40 | // 拿到这个index对应的item
41 | const column = draft.splice(index, 1);
42 | // 插入回去
43 | draft.splice(index + 1, 0, ...column);
44 | });
45 | };
46 | /**
47 | * 配置表格列
48 | * @param formItem
49 | * @param index
50 | */
51 | const configColumn = (column: ColumnType, index: number) => {
52 | setIndex(index);
53 | setCurrentColumn(column);
54 | };
55 |
56 | /** 配置完成 */
57 | const handleConfirm = (columnProps: ColumnType) => {
58 | setColumns(draft => {
59 | const column = draft.find(item => item.dataIndex === columnProps.dataIndex);
60 | if (!currentColumn && !column) {
61 | draft.push(columnProps);
62 | } else {
63 | draft[index] = columnProps;
64 | }
65 | });
66 | };
67 |
68 | /**
69 | * 删除配置项
70 | * @param index
71 | */
72 | const deleteColumn = (index: number) => () => {
73 | setColumns(draft => {
74 | draft.splice(index, 1);
75 | });
76 | };
77 |
78 | /**
79 | * 复制配置项
80 | * @param index
81 | */
82 | const copyColumn = (index: number) => () => {
83 | setColumns(draft => {
84 | const column = draft[index];
85 | draft.splice(index + 1, 0, column);
86 | });
87 | };
88 |
89 | return {
90 | columns,
91 | setColumns,
92 | moveUp,
93 | moveDown,
94 | configColumn,
95 | deleteColumn,
96 | copyColumn,
97 | index,
98 | setIndex,
99 | currentColumn,
100 | setCurrentColumn,
101 | onConfirm: handleConfirm,
102 | };
103 | }
104 |
--------------------------------------------------------------------------------
/ui/index.tsx:
--------------------------------------------------------------------------------
1 | import { IUiApi } from '@umijs/ui-types';
2 | import { DashboardFilled } from '@ant-design/icons';
3 | import ManageConfigPanel from './pages/manage';
4 | import ScreenConfigPanel from './pages/screen';
5 | import AppConfigPanel from './pages/mobile';
6 |
7 | export default (api: IUiApi) => {
8 | api.addPanel({
9 | title: '中后台',
10 | path: '/manageConfig',
11 | icon: ,
12 | component: () => ,
13 | });
14 | api.addPanel({
15 | title: '大屏',
16 | path: '/screenConfig',
17 | icon: ,
18 | component: () => ,
19 | });
20 | api.addPanel({
21 | title: '移动端',
22 | path: '/appConfig',
23 | icon: ,
24 | component: () => ,
25 | });
26 | };
27 |
--------------------------------------------------------------------------------
/ui/pages/manage/Context.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * @文件描述:
3 | * @公司: thundersdata
4 | * @作者: 陈杰
5 | * @Date: 2020-04-29 11:29:07
6 | * @LastEditors: 黄姗姗
7 | * @LastEditTime: 2020-05-29 14:12:04
8 | */
9 | import { createContext } from 'react';
10 | import { IUiApi } from '@umijs/ui-types';
11 | import { TemplateType } from '../../../interfaces/common';
12 | import { CascaderOptionType } from 'antd/lib/cascader';
13 | import { BaseClass } from '../../../interfaces/api';
14 |
15 | const MainContext = createContext({} as {
16 | /** 主体 */
17 | api: IUiApi;
18 | templateType?: TemplateType;
19 | addTemplate: (templateType: TemplateType) => void;
20 | databases: CascaderOptionType[];
21 | baseClasses: BaseClass[];
22 | impConfigJson: string;
23 | setImpConfigJson: (configJson: string) => void;
24 | constantConfig: string;
25 | });
26 |
27 | export default MainContext;
28 |
--------------------------------------------------------------------------------
/ui/pages/manage/components/ConstantConfigAction/index.module.less:
--------------------------------------------------------------------------------
1 | .bubble {
2 | width: 60px;
3 | height: 60px;
4 | border-radius: 60px;
5 | display: flex;
6 | justify-content: center;
7 | align-items: center;
8 | }
9 | .fixed {
10 | position: fixed;
11 | bottom: 40px;
12 | margin-left: 20px;
13 | }
14 | .space {
15 | margin-bottom: 10px;
16 | }
17 |
--------------------------------------------------------------------------------
/ui/pages/manage/components/ConstantConfigAction/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { useContext, useState } from 'react';
2 | import { Button, Modal } from 'antd';
3 | import { UnControlled as CodeMirror } from 'react-codemirror2';
4 | import 'codemirror/mode/javascript/javascript';
5 | import 'codemirror/lib/codemirror.css';
6 | import 'codemirror/theme/material-palenight.css';
7 | import classNames from 'classnames';
8 | import styles from './index.module.less';
9 | import Context from '../../Context';
10 |
11 | export default ({
12 | visible,
13 | setVisible,
14 | onSubmit,
15 | }: {
16 | visible: boolean;
17 | setVisible: (visible: boolean) => void;
18 | onSubmit: (code: string) => void;
19 | }) => {
20 | const {constantConfig} = useContext(Context);
21 | const [editorValue, setEditorValue] = useState('');
22 |
23 | return (
24 | <>
25 |
32 | onSubmit(editorValue)}
38 | onCancel={() => setVisible(false)}
39 | bodyStyle={{ maxHeight: 768, overflowY: 'auto' }}
40 | >
41 | {
50 | editor.setSize('100%', 600);
51 | }}
52 | onChange={(editor, data, value) => {
53 | setEditorValue(value);
54 | }}
55 | />
56 |
57 | >
58 | );
59 | };
60 |
--------------------------------------------------------------------------------
/ui/pages/manage/components/Dashboard/index.module.less:
--------------------------------------------------------------------------------
1 | .dashboard {
2 | position: relative;
3 | }
4 |
--------------------------------------------------------------------------------
/ui/pages/manage/components/Dashboard/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { useContext } from 'react';
2 | import Context from '../../Context';
3 |
4 | import styles from './index.module.less';
5 | import ShortFormContent from '../content/ShortFormContent';
6 | import LongFormContent from '../content/LongFormContent';
7 | import ShortDetailContent from '../content/ShortDetailContent';
8 | import LongDetailContent from '../content/LongDetailContent';
9 | import TableContent from '../content/TableContent';
10 | import ShortFormModalContent from '../content/ShortFormModalContent';
11 | import LongFormModalContent from '../content/LongFormModalContent';
12 | import ShortDetailModalContent from '../content/ShortDetailModalContent';
13 | import LongDetailModalContent from '../content/LongDetailModalContent';
14 |
15 | export default () => {
16 | const { templateType } = useContext(Context);
17 |
18 | /** 根据模板映射不同的显示内容 */
19 | const createContentByType = () => {
20 | switch (templateType) {
21 | case 'short-form':
22 | return ;
23 | case 'long-form':
24 | return ;
25 | case 'one-column-form-modal':
26 | return ;
27 | case 'two-columns-form-modal':
28 | return ;
29 | case 'short-detail':
30 | return ;
31 | case 'long-detail':
32 | return ;
33 | case 'one-column-detail-modal':
34 | return ;
35 | case 'two-columns-detail-modal':
36 | return ;
37 | case 'table':
38 | return ;
39 | default:
40 | return null;
41 | }
42 | };
43 |
44 | return {createContentByType()}
;
45 | };
46 |
--------------------------------------------------------------------------------
/ui/pages/manage/components/ExportActions/index.module.less:
--------------------------------------------------------------------------------
1 | .bubble {
2 | width: 60px;
3 | height: 60px;
4 | border-radius: 60px;
5 | display: flex;
6 | justify-content: center;
7 | align-items: center;
8 | }
9 | .fixed {
10 | position: fixed;
11 | bottom: 40px;
12 | right: 150px;
13 | }
14 | .space {
15 | margin-bottom: 10px;
16 | }
17 |
--------------------------------------------------------------------------------
/ui/pages/manage/components/ExportActions/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { useContext } from 'react';
2 | import { Button, message } from 'antd';
3 | import classNames from 'classnames';
4 | import styles from './index.module.less';
5 | import Context from '../../Context';
6 |
7 | export default ({ onClick }: { onClick: () => void }) => {
8 | const { templateType } = useContext(Context);
9 |
10 | return (
11 |
24 | )
25 | }
--------------------------------------------------------------------------------
/ui/pages/manage/components/ImportAction/index.module.less:
--------------------------------------------------------------------------------
1 | .bubble {
2 | width: 60px;
3 | height: 60px;
4 | border-radius: 60px;
5 | display: flex;
6 | justify-content: center;
7 | align-items: center;
8 | }
9 | .fixed {
10 | position: fixed;
11 | bottom: 40px;
12 | right: 80px;
13 | }
14 | .space {
15 | margin-bottom: 10px;
16 | }
17 |
--------------------------------------------------------------------------------
/ui/pages/manage/components/ImportAction/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { useContext } from 'react';
2 | import { Button, Modal, Form, Input, message } from 'antd';
3 | import classNames from 'classnames';
4 | import styles from './index.module.less';
5 | import { Store } from 'antd/lib/form/interface';
6 | import Context from '../../Context';
7 |
8 | const formLayout = {
9 | labelCol: { span: 4 },
10 | wrapperCol: { span: 20 },
11 | };
12 |
13 | export default ({
14 | modalVisible,
15 | setModalVisible,
16 | onSubmit,
17 | }: {
18 | modalVisible: boolean;
19 | setModalVisible: (visible: boolean) => void;
20 | onSubmit: (values: Store) => void;
21 | }) => {
22 | const [form] = Form.useForm();
23 |
24 | const { templateType } = useContext(Context);
25 |
26 | const handleFinish = (values: Store) => {
27 | form.resetFields();
28 | onSubmit(values);
29 | }
30 | return (
31 | <>
32 |
45 | form.submit()}
51 | onCancel={() => setModalVisible(false)}
52 | bodyStyle={{ maxHeight: 650, overflowY: 'auto' }}
53 | >
54 |
61 |
62 |
63 |
64 |
65 | >
66 | )
67 | }
68 |
--------------------------------------------------------------------------------
/ui/pages/manage/components/PathMenuAction/index.module.less:
--------------------------------------------------------------------------------
1 | .bubble {
2 | width: 60px;
3 | height: 60px;
4 | border-radius: 60px;
5 | display: flex;
6 | justify-content: center;
7 | align-items: center;
8 | }
9 | .fixed {
10 | position: fixed;
11 | bottom: 40px;
12 | right: 10px;
13 | }
14 | .space {
15 | margin-bottom: 10px;
16 | }
17 |
--------------------------------------------------------------------------------
/ui/pages/manage/components/TemplateList/index.module.less:
--------------------------------------------------------------------------------
1 | .row {
2 | display: flex;
3 | height: 152px;
4 | overflow-x: auto;
5 | }
6 | .col {
7 | height: 100px;
8 | margin-right: 15px;
9 | position: relative;
10 |
11 | .hover {
12 | width: 100%;
13 | height: 100%;
14 | position: absolute;
15 | top: 0;
16 | left: 0;
17 | display: none;
18 | }
19 | &:hover {
20 | .hover {
21 | display: flex;
22 | justify-content: center;
23 | align-items: center;
24 | background-color: rgba(0, 0, 0, 0.65);
25 | }
26 | }
27 |
28 | img {
29 | height: 100%;
30 | display: block;
31 | }
32 | .name {
33 | text-align: center;
34 | }
35 | }
36 | .selected {
37 | border: 2px solid #0189fb;
38 | padding: 2px;
39 | }
40 |
--------------------------------------------------------------------------------
/ui/pages/manage/components/TemplateList/index.tsx:
--------------------------------------------------------------------------------
1 | /*
2 | * @文件描述: 中后台管理系统的模板以及缩略图
3 | * @公司: thundersdata
4 | * @作者: 陈杰
5 | * @Date: 2020-04-29 11:32:45
6 | * @LastEditors: 陈杰
7 | * @LastEditTime: 2020-04-29 22:00:14
8 | */
9 | import React, { useState, useContext } from 'react';
10 | import { Button, Tooltip, Modal } from 'antd';
11 | import { EyeOutlined, PlusOutlined } from '@ant-design/icons';
12 | import classnames from 'classnames';
13 | import Context from '../../Context';
14 | import templateList from './template.json';
15 | import styles from './index.module.less';
16 | import { Template } from '../../../../../interfaces/common';
17 |
18 | export default () => {
19 | const [visible, setVisible] = useState(false);
20 | const [image, setImage] = useState();
21 | const [title, setTitle] = useState();
22 | const { addTemplate, templateType } = useContext(Context);
23 |
24 | const previewImage = (image: string, title: string) => {
25 | setImage(image);
26 | setTitle(title);
27 | setVisible(true);
28 | };
29 |
30 | return (
31 |
32 | {(templateList as Template[]).map(template => (
33 |
40 |
41 |
42 |
45 |
46 |
47 |
48 |
51 |
52 |
53 |
54 |

55 |
{template.name}
56 |
57 |
58 | ))}
59 |
setVisible(false)}
63 | width="80vw"
64 | centered
65 | >
66 | {image &&
}
67 |
68 |
69 | );
70 | };
71 |
--------------------------------------------------------------------------------
/ui/pages/manage/components/content/LongDetailContent/index.module.less:
--------------------------------------------------------------------------------
1 | .formItemConfig {
2 | display: flex;
3 | align-items: flex-start;
4 | flex-direction: column;
5 |
6 | :global {
7 | .ant-form-item {
8 | flex: 1;
9 | width: 100%;
10 | }
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/ui/pages/manage/components/content/LongDetailModalContent/index.module.less:
--------------------------------------------------------------------------------
1 | .formItemConfig {
2 | display: flex;
3 | align-items: flex-start;
4 |
5 | :global {
6 | .ant-form-item {
7 | flex: 1;
8 | width: 100%;
9 | }
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/ui/pages/manage/components/content/LongDetailModalContent/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { useContext, useState, useEffect } from 'react';
2 | import { Form, Button, Card, message, Input, Row, Col } from 'antd';
3 | import Title from '../../../../../components/Title';
4 | import { AjaxResponse } from '../../../../../../interfaces/common';
5 | import FormItemConfigDrawer from '../../../../../components/FormItemConfigDrawer';
6 | import Context from '../../../Context';
7 | import PathMenuAction from '../../PathMenuAction';
8 | import { Store } from 'antd/lib/form/interface';
9 | import ShortFormConfigDrawer from '../../drawers/ShortFormConfigDrawer';
10 | import useFormItem from '../../../../../hooks/useFormItem';
11 | import produce from 'immer';
12 | import faker from 'faker';
13 | import styles from './index.module.less';
14 | import useConfigVisible from '../../../../../hooks/useConfigVisible';
15 | import ConfigActions from '../../../../../components/ConfigActions';
16 | import { transformFormItemLines } from '../../../../../utils';
17 | import ApiConfigDrawer from '../../drawers/ApiConfigDrawer';
18 | import useConfig from '../../../../../hooks/useConfig';
19 | import copy from 'copy-to-clipboard';
20 | import ExportActions from '../../ExportActions';
21 |
22 | const formItemLayout = {
23 | labelCol: {
24 | xs: { span: 24 },
25 | sm: { span: 7 },
26 | md: { span: 10 },
27 | },
28 | wrapperCol: {
29 | xs: { span: 24 },
30 | sm: { span: 12 },
31 | md: { span: 14 },
32 | },
33 | };
34 |
35 | export default () => {
36 | const { api, impConfigJson } = useContext(Context);
37 | const [formConfig, setFormConfig] = useState({
38 | title: '两列详情',
39 | });
40 |
41 | const {
42 | initialFetch,
43 | setInitialFetch,
44 | submitFetch,
45 | setSubmitFetch,
46 | } = useConfig();
47 |
48 | const {
49 | pathModalVisible,
50 | setPathModalVisible,
51 | formConfigDrawerVisible,
52 | setFormConfigDrawerVisible,
53 | formItemConfigDrawerVisible,
54 | setFormItemConfigDrawerVisible,
55 | apiConfigDrawerVisible,
56 | setApiConfigDrawerVisible,
57 | } = useConfigVisible();
58 |
59 | const {
60 | formItems,
61 | setFormItems,
62 | moveUp,
63 | moveDown,
64 | configItem,
65 | deleteItem,
66 | copyItem,
67 | currentItem,
68 | index,
69 | onConfirm,
70 | } = useFormItem();
71 |
72 | /**
73 | * 添加详情展示项
74 | */
75 | const addDetailItem = () => {
76 | setFormItems(
77 | produce(formItems, draft => {
78 | draft.push({
79 | label: faker.name.title(),
80 | name: faker.name.lastName(),
81 | type: 'input',
82 | });
83 | }),
84 | );
85 | };
86 |
87 | const handleApiSubmit = (initialFetch?: string[], submitFetch?: string[]) => {
88 | setInitialFetch(initialFetch);
89 | setSubmitFetch(submitFetch);
90 | };
91 |
92 | /**
93 | * 把配置的表单信息和添加的表单项配置传到服务端
94 | */
95 | const remoteCall = async ({ path, dirName }: { path?: string; dirName?: string }) => {
96 | // 对formItems进行遍历,如果其中有任一项没有配置label/name,则不允许提交
97 | if (formItems.length === 0) {
98 | message.error('您还没有添加详情展示项,不能提交!');
99 | return;
100 | }
101 | const key = 'message';
102 | try {
103 | message.loading({ content: '正在生成文件,请稍候...', key });
104 | const result = await api.callRemote({
105 | type: 'org.umi-plugin-page-creator.longDetailModal',
106 | payload: {
107 | formConfig,
108 | formItems,
109 | path,
110 | dirName,
111 | initialFetch,
112 | submitFetch,
113 | },
114 | });
115 | message.success({ content: (result as AjaxResponse).message , key });
116 | setPathModalVisible(false);
117 | } catch (error) {
118 | message.error({ content: error.message, key });
119 | }
120 | };
121 |
122 | /** 把导入的配置信息进行解析 */
123 | useEffect(() => {
124 | if (impConfigJson) {
125 | const { formConfig = { title: '两列详情', }, formItems = [], initialFetch = [], submitFetch = [] } = JSON.parse(impConfigJson);
126 | setFormConfig(formConfig);
127 | setFormItems(formItems);
128 | setInitialFetch(initialFetch);
129 | setSubmitFetch(submitFetch);
130 | }
131 | }, [impConfigJson]);
132 |
133 | /** 导出 */
134 | const handleExport = () => {
135 | copy(JSON.stringify({
136 | formConfig,
137 | formItems,
138 | initialFetch,
139 | submitFetch
140 | }, null, 2));
141 | message.success('配置已复制到剪贴板');
142 | };
143 |
144 | const cols = 2;
145 | // 把formItems分成2列
146 | const formItemLines = transformFormItemLines(formItems, cols);
147 |
148 | return (
149 | <>
150 | }
152 | extra={
153 |
156 | }
157 | >
158 |
190 |
191 |
192 | {/**页面接口配置 */}
193 |
200 |
201 | {/**表单配置 */}
202 |
208 |
209 | {/**配置单个表单项 */}
210 | {currentItem && (
211 |
220 | )}
221 |
222 | {/**提交时候弹出的输入文件路径 */}
223 |
230 |
231 |
232 | >
233 | );
234 | };
235 |
--------------------------------------------------------------------------------
/ui/pages/manage/components/content/ShortDetailContent/index.module.less:
--------------------------------------------------------------------------------
1 | .formItemConfig {
2 | display: flex;
3 | align-items: flex-start;
4 |
5 | :global {
6 | .ant-form-item {
7 | flex: 1;
8 | width: 100%;
9 | }
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/ui/pages/manage/components/content/ShortDetailContent/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { useContext, useState, useEffect } from 'react';
2 | import { Form, Button, Card, message, Input } from 'antd';
3 | import Title from '../../../../../components/Title';
4 | import { AjaxResponse } from '../../../../../../interfaces/common';
5 | import FormItemConfigDrawer from '../../../../../components/FormItemConfigDrawer';
6 | import Context from '../../../Context';
7 | import PathMenuAction from '../../PathMenuAction';
8 | import { Store } from 'antd/lib/form/interface';
9 | import ShortFormConfigDrawer from '../../drawers/ShortFormConfigDrawer';
10 | import useFormItem from '../../../../../hooks/useFormItem';
11 | import produce from 'immer';
12 | import faker from 'faker';
13 | import styles from './index.module.less';
14 | import useConfigVisible from '../../../../../hooks/useConfigVisible';
15 | import ConfigActions from '../../../../../components/ConfigActions';
16 | import ApiConfigDrawer from '../../drawers/ApiConfigDrawer';
17 | import useConfig from '../../../../../hooks/useConfig';
18 | import copy from 'copy-to-clipboard';
19 | import ExportActions from '../../ExportActions';
20 |
21 | const formItemLayout = {
22 | labelCol: {
23 | xs: { span: 24 },
24 | sm: { span: 7 },
25 | md: { span: 10 },
26 | },
27 | wrapperCol: {
28 | xs: { span: 24 },
29 | sm: { span: 12 },
30 | md: { span: 14 },
31 | },
32 | };
33 |
34 | export default () => {
35 | const { api, impConfigJson } = useContext(Context);
36 | const [formConfig, setFormConfig] = useState({
37 | title: '单列详情',
38 | });
39 |
40 | const {
41 | initialFetch,
42 | setInitialFetch,
43 | submitFetch,
44 | setSubmitFetch,
45 | } = useConfig();
46 |
47 | const {
48 | pathModalVisible,
49 | setPathModalVisible,
50 | formConfigDrawerVisible,
51 | setFormConfigDrawerVisible,
52 | formItemConfigDrawerVisible,
53 | setFormItemConfigDrawerVisible,
54 | apiConfigDrawerVisible,
55 | setApiConfigDrawerVisible,
56 | } = useConfigVisible();
57 |
58 | const {
59 | formItems,
60 | setFormItems,
61 | moveUp,
62 | moveDown,
63 | configItem,
64 | deleteItem,
65 | copyItem,
66 | currentItem,
67 | index,
68 | onConfirm,
69 | } = useFormItem();
70 |
71 | /**
72 | * 添加详情展示项
73 | */
74 | const addDetailItem = () => {
75 | setFormItems(
76 | produce(formItems, draft => {
77 | draft.push({
78 | label: faker.name.title(),
79 | name: faker.name.lastName(),
80 | type: 'input',
81 | });
82 | }),
83 | );
84 | };
85 |
86 | const handleApiSubmit = (initialFetch?: string[], submitFetch?: string[]) => {
87 | setInitialFetch(initialFetch);
88 | setSubmitFetch(submitFetch);
89 | };
90 |
91 | /**
92 | * 把配置的表单信息和添加的表单项配置传到服务端
93 | */
94 | const remoteCall = async ({ path, menu }: { path?: string; menu?: string }) => {
95 | // 对formItems进行遍历,如果其中有任一项没有配置label/name,则不允许提交
96 | if (formItems.length === 0) {
97 | message.error('您还没有添加详情展示项,不能提交!');
98 | return;
99 | }
100 | const key = 'message';
101 | try {
102 | message.loading({ content: '正在生成文件,请稍候...', key });
103 | const result = await api.callRemote({
104 | type: 'org.umi-plugin-page-creator.shortDetail',
105 | payload: {
106 | formConfig,
107 | formItems,
108 | path,
109 | menu,
110 | initialFetch,
111 | submitFetch,
112 | },
113 | });
114 | message.success({ content: (result as AjaxResponse).message , key });
115 | setPathModalVisible(false);
116 | } catch (error) {
117 | message.error({ content: error.message, key });
118 | }
119 | };
120 |
121 | /** 把导入的配置信息进行解析 */
122 | useEffect(() => {
123 | if (impConfigJson) {
124 | const { formConfig = { title: '单列详情', }, formItems = [], initialFetch = [], submitFetch = [] } = JSON.parse(impConfigJson);
125 | setFormConfig(formConfig);
126 | setFormItems(formItems);
127 | setInitialFetch(initialFetch);
128 | setSubmitFetch(submitFetch);
129 | }
130 | }, [impConfigJson]);
131 |
132 | /** 导出 */
133 | const handleExport = () => {
134 | copy(JSON.stringify({
135 | formConfig,
136 | formItems,
137 | initialFetch,
138 | submitFetch
139 | }, null, 2));
140 | message.success('配置已复制到剪贴板');
141 | };
142 |
143 | return (
144 | <>
145 | }
147 | extra={
148 |
151 | }
152 | >
153 |
178 |
179 |
180 | {/**页面接口配置 */}
181 |
188 |
189 | {/**表单配置 */}
190 |
196 |
197 | {/**配置单个表单项 */}
198 | {currentItem && (
199 |
208 | )}
209 |
210 | {/**提交时候弹出的输入文件路径 */}
211 |
218 |
219 |
220 | >
221 | );
222 | };
223 |
--------------------------------------------------------------------------------
/ui/pages/manage/components/content/ShortDetailModalContent/index.module.less:
--------------------------------------------------------------------------------
1 | .formItemConfig {
2 | display: flex;
3 | align-items: flex-start;
4 |
5 | :global {
6 | .ant-form-item {
7 | flex: 1;
8 | width: 100%;
9 | }
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/ui/pages/manage/components/content/ShortDetailModalContent/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { useContext, useState, useEffect } from 'react';
2 | import { Form, Button, Card, message, Input } from 'antd';
3 | import Title from '../../../../../components/Title';
4 | import { AjaxResponse } from '../../../../../../interfaces/common';
5 | import FormItemConfigDrawer from '../../../../../components/FormItemConfigDrawer';
6 | import Context from '../../../Context';
7 | import PathMenuAction from '../../PathMenuAction';
8 | import { Store } from 'antd/lib/form/interface';
9 | import ShortFormConfigDrawer from '../../drawers/ShortFormConfigDrawer';
10 | import useFormItem from '../../../../../hooks/useFormItem';
11 | import produce from 'immer';
12 | import faker from 'faker';
13 | import styles from './index.module.less';
14 | import useConfigVisible from '../../../../../hooks/useConfigVisible';
15 | import ConfigActions from '../../../../../components/ConfigActions';
16 | import ApiConfigDrawer from '../../drawers/ApiConfigDrawer';
17 | import useConfig from '../../../../../hooks/useConfig';
18 | import copy from 'copy-to-clipboard';
19 | import ExportActions from '../../ExportActions';
20 |
21 | const formItemLayout = {
22 | labelCol: {
23 | xs: { span: 24 },
24 | sm: { span: 7 },
25 | md: { span: 10 },
26 | },
27 | wrapperCol: {
28 | xs: { span: 24 },
29 | sm: { span: 12 },
30 | md: { span: 14 },
31 | },
32 | };
33 |
34 | export default () => {
35 | const { api, impConfigJson } = useContext(Context);
36 | const [formConfig, setFormConfig] = useState({
37 | title: '单列详情',
38 | });
39 |
40 | const {
41 | initialFetch,
42 | setInitialFetch,
43 | submitFetch,
44 | setSubmitFetch,
45 | } = useConfig();
46 |
47 | const {
48 | pathModalVisible,
49 | setPathModalVisible,
50 | formConfigDrawerVisible,
51 | setFormConfigDrawerVisible,
52 | formItemConfigDrawerVisible,
53 | setFormItemConfigDrawerVisible,
54 | apiConfigDrawerVisible,
55 | setApiConfigDrawerVisible,
56 | } = useConfigVisible();
57 |
58 | const {
59 | formItems,
60 | setFormItems,
61 | moveUp,
62 | moveDown,
63 | configItem,
64 | deleteItem,
65 | copyItem,
66 | currentItem,
67 | index,
68 | onConfirm,
69 | } = useFormItem();
70 |
71 | /**
72 | * 添加详情展示项
73 | */
74 | const addDetailItem = () => {
75 | setFormItems(
76 | produce(formItems, draft => {
77 | draft.push({
78 | label: faker.name.title(),
79 | name: faker.name.lastName(),
80 | type: 'input',
81 | });
82 | }),
83 | );
84 | };
85 |
86 | const handleApiSubmit = (initialFetch?: string[], submitFetch?: string[]) => {
87 | setInitialFetch(initialFetch);
88 | setSubmitFetch(submitFetch);
89 | };
90 |
91 | /**
92 | * 把配置的表单信息和添加的表单项配置传到服务端
93 | */
94 | const remoteCall = async ({ path, dirName }: { path?: string; dirName?: string }) => {
95 | // 对formItems进行遍历,如果其中有任一项没有配置label/name,则不允许提交
96 | if (formItems.length === 0) {
97 | message.error('您还没有添加详情展示项,不能提交!');
98 | return;
99 | }
100 | const key = 'message';
101 | try {
102 | message.loading({ content: '正在生成文件,请稍候...', key });
103 | const result = await api.callRemote({
104 | type: 'org.umi-plugin-page-creator.shortDetailModal',
105 | payload: {
106 | formConfig,
107 | formItems,
108 | path,
109 | dirName,
110 | initialFetch,
111 | submitFetch,
112 | },
113 | });
114 | message.success({ content: (result as AjaxResponse).message , key });
115 | setPathModalVisible(false);
116 | } catch (error) {
117 | message.error({ content: error.message, key });
118 | }
119 | };
120 |
121 | /** 把导入的配置信息进行解析 */
122 | useEffect(() => {
123 | if (impConfigJson) {
124 | const { formConfig = { title: '单列详情', }, formItems = [], initialFetch = [], submitFetch = [] } = JSON.parse(impConfigJson);
125 | setFormConfig(formConfig);
126 | setFormItems(formItems);
127 | setInitialFetch(initialFetch);
128 | setSubmitFetch(submitFetch);
129 | }
130 | }, [impConfigJson]);
131 |
132 | /** 导出 */
133 | const handleExport = () => {
134 | copy(JSON.stringify({
135 | formConfig,
136 | formItems,
137 | initialFetch,
138 | submitFetch
139 | }, null, 2));
140 | message.success('配置已复制到剪贴板');
141 | };
142 |
143 | return (
144 | <>
145 | }
147 | extra={
148 |
151 | }
152 | >
153 |
178 |
179 |
180 | {/**页面接口配置 */}
181 |
188 |
189 | {/**表单配置 */}
190 |
196 |
197 | {/**配置单个表单项 */}
198 | {currentItem && (
199 |
208 | )}
209 |
210 | {/**提交时候弹出的输入文件路径 */}
211 |
218 |
219 |
220 | >
221 | );
222 | };
223 |
--------------------------------------------------------------------------------
/ui/pages/manage/components/content/TableContent/TitleWithActions/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ConfigActions from '../../../../../../components/ConfigActions';
3 | import { ColumnTitle } from 'antd/lib/table/interface';
4 | import { Tooltip } from 'antd';
5 |
6 | export default function({
7 | title,
8 | moveUp,
9 | moveDown,
10 | configItem,
11 | deleteItem,
12 | copyItem,
13 | }: {
14 | title?: ColumnTitle;
15 | moveUp?: () => void;
16 | moveDown?: () => void;
17 | configItem?: () => void;
18 | deleteItem?: () => void;
19 | copyItem?: () => void;
20 | }) {
21 | return (
22 |
23 |
34 | }
35 | >
36 |
{title}
37 |
38 |
39 | );
40 | }
41 |
--------------------------------------------------------------------------------
/ui/pages/manage/components/content/TableContent/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { useContext, useState, useEffect } from 'react';
2 | import { Button, Card, message, Table } from 'antd';
3 | import Title from '../../../../../components/Title';
4 | import { AjaxResponse } from '../../../../../../interfaces/common';
5 | import Context from '../../../Context';
6 | import PathMenuAction from '../../PathMenuAction';
7 | import { Store } from 'antd/lib/form/interface';
8 | import TableConfigDrawer from '../../drawers/TableConfigDrawer';
9 | import TableColumnConfigDrawer from '../../drawers/TableColumnConfigDrawer';
10 | import TitleWithActions from './TitleWithActions';
11 | import useConfigVisible from '../../../../../hooks/useConfigVisible';
12 | import useTable from '../../../../../hooks/useTable';
13 | import { filterEmpty } from '../../../../../utils';
14 | import ApiConfigDrawer from '../../drawers/ApiConfigDrawer';
15 | import useConfig from '../../../../../hooks/useConfig';
16 | import { ColumnType } from 'antd/lib/table/interface';
17 | import copy from 'copy-to-clipboard';
18 | import ExportActions from '../../ExportActions';
19 |
20 | export default () => {
21 | const { api, impConfigJson } = useContext(Context);
22 | const [tableConfig, setTableConfig] = useState({
23 | headerTitle: '表格配置',
24 | rowKey: 'id',
25 | bordered: false,
26 | });
27 |
28 | const { initialFetch, setInitialFetch, submitFetch, setSubmitFetch } = useConfig();
29 |
30 | const {
31 | pathModalVisible,
32 | setPathModalVisible,
33 | tableConfigDrawerVisible,
34 | setTableConfigDrawerVisible,
35 | columnConfigDrawerVisible,
36 | setColumnConfigDrawerVisible,
37 | apiConfigDrawerVisible,
38 | setApiConfigDrawerVisible,
39 | } = useConfigVisible();
40 |
41 | const {
42 | columns,
43 | index,
44 | moveUp,
45 | moveDown,
46 | copyColumn,
47 | configColumn,
48 | deleteColumn,
49 | currentColumn,
50 | setIndex,
51 | setCurrentColumn,
52 | onConfirm,
53 | } = useTable();
54 |
55 | const handleApiSubmit = (initialFetch?: string[], submitFetch?: string[]) => {
56 | setInitialFetch(initialFetch);
57 | setSubmitFetch(submitFetch);
58 | };
59 |
60 | /**
61 | * 把配置的表单信息和添加的表单项配置传到服务端
62 | */
63 | const remoteCall = async ({ path, menu }: { path?: string; menu?: string }) => {
64 | const key = 'message';
65 | try {
66 | if (columns.length === 0) {
67 | message.error('你还没有配置表格列');
68 | return;
69 | }
70 | message.loading({ content: '正在生成文件,请稍候...', key });
71 | const result = await api.callRemote({
72 | type: 'org.umi-plugin-page-creator.table',
73 | payload: {
74 | tableConfig,
75 | columns,
76 | path,
77 | menu,
78 | initialFetch,
79 | submitFetch,
80 | },
81 | });
82 | message.success({ content: (result as AjaxResponse).message, key });
83 | setPathModalVisible(false);
84 | } catch (error) {
85 | message.error({ content: error.message, key });
86 | }
87 | };
88 |
89 | /** 把导入的配置信息进行解析 */
90 | useEffect(() => {
91 | if (impConfigJson) {
92 | const {
93 | tableConfig = {
94 | headerTitle: '表格配置',
95 | bordered: true,
96 | },
97 | columns = [],
98 | initialFetch = [],
99 | submitFetch = [],
100 | } = JSON.parse(impConfigJson);
101 | setTableConfig(tableConfig);
102 | (columns as ColumnType[]).map(item => onConfirm(item));
103 | setInitialFetch(initialFetch);
104 | setSubmitFetch(submitFetch);
105 | }
106 | }, [impConfigJson]);
107 |
108 | /** 导出 */
109 | const handleExport = () => {
110 | copy(
111 | JSON.stringify(
112 | {
113 | tableConfig,
114 | columns,
115 | initialFetch,
116 | submitFetch,
117 | },
118 | null,
119 | 2,
120 | ),
121 | );
122 | message.success('配置已复制到剪贴板');
123 | };
124 |
125 | return (
126 | <>
127 | }
129 | extra={
130 |
133 | }
134 | >
135 | ({
144 | ...column,
145 | title: (
146 | {
153 | configColumn(column, index);
154 | setColumnConfigDrawerVisible(true);
155 | }}
156 | />
157 | ),
158 | }))}
159 | dataSource={[]}
160 | />
161 |
168 |
178 |
179 |
180 | {/**页面接口配置 */}
181 |
188 |
189 | {
194 | setTableConfig(values);
195 | setTableConfigDrawerVisible(false);
196 | }}
197 | />
198 |
199 | {
203 | const findIndex = columns.findIndex(item => item.dataIndex === values.dataIndex);
204 | // 如果index不存在,或者findIndex和index相同,表示新增或者修改没有改到dataIndex
205 | if ((!index && findIndex > -1) || (index && index === findIndex)) {
206 | message.error('这个dataIndex已存在,请修改后重新提交');
207 | return;
208 | }
209 | onConfirm(filterEmpty(values));
210 | setColumnConfigDrawerVisible(false);
211 | }}
212 | current={currentColumn}
213 | initialFetch={initialFetch}
214 | />
215 |
216 | {/**提交时候弹出的输入文件路径 */}
217 |
223 |
224 |
225 | >
226 | );
227 | };
228 |
--------------------------------------------------------------------------------
/ui/pages/manage/components/drawers/ApiConfigDrawer/index.tsx:
--------------------------------------------------------------------------------
1 | /*
2 | * @文件描述: 接口API的配置内容
3 | * @公司: thundersdata
4 | * @作者: 陈杰
5 | * @Date: 2020-04-29 17:56:31
6 | * @LastEditors: 黄姗姗
7 | * @LastEditTime: 2020-05-28 18:24:49
8 | */
9 | import React, { useContext, useEffect } from 'react';
10 | import { Form, Button, Drawer, Cascader } from 'antd';
11 | import { Store } from 'antd/lib/form/interface';
12 | import Context from '../../../Context';
13 |
14 | export default ({
15 | visible,
16 | setVisible,
17 | onSubmit,
18 | submitFetch,
19 | initialFetch,
20 | }: {
21 | visible: boolean;
22 | setVisible: (visible: boolean) => void;
23 | onSubmit: (initialFetch?: string[], submitFetch?: string[]) => void;
24 | submitFetch?: string[];
25 | initialFetch?: string[];
26 | }) => {
27 | const { databases = [] } = useContext(Context);
28 | const [form] = Form.useForm();
29 |
30 | useEffect(() => {
31 | form.setFieldsValue({
32 | initialFetch
33 | })
34 | }, [initialFetch]);
35 |
36 | useEffect(() => {
37 | form.setFieldsValue({
38 | submitFetch
39 | })
40 | }, [submitFetch]);
41 |
42 | const handleFinish = (values: Store) => {
43 | setVisible(false);
44 | const { initialFetch, submitFetch } = values;
45 | onSubmit(initialFetch, submitFetch);
46 | };
47 |
48 | return (
49 | setVisible(false)}>
50 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
61 |
62 |
63 |
64 | );
65 | };
66 |
--------------------------------------------------------------------------------
/ui/pages/manage/components/drawers/CardConfigDrawer/index.tsx:
--------------------------------------------------------------------------------
1 | /*
2 | * @文件描述: 短表单的配置内容
3 | * @公司: thundersdata
4 | * @作者: 陈杰
5 | * @Date: 2020-04-29 17:56:31
6 | * @LastEditors: 陈杰
7 | * @LastEditTime: 2020-05-11 15:21:35
8 | */
9 | import React from 'react';
10 | import { Form, Button, Input, Drawer } from 'antd';
11 | import { Store } from 'antd/lib/form/interface';
12 |
13 | export default ({
14 | visible,
15 | setVisible,
16 | onFinish,
17 | }: {
18 | visible: boolean;
19 | setVisible: (visible: boolean) => void;
20 | onFinish: (values: Store) => void;
21 | }) => {
22 | const [form] = Form.useForm();
23 |
24 | const handleFinish = (values: Store) => {
25 | onFinish(values);
26 | setVisible(false);
27 | };
28 |
29 | return (
30 | setVisible(false)}>
31 |
38 |
39 |
40 |
41 |
44 |
45 |
46 |
47 | );
48 | };
49 |
--------------------------------------------------------------------------------
/ui/pages/manage/components/drawers/ShortFormConfigDrawer/index.tsx:
--------------------------------------------------------------------------------
1 | /*
2 | * @文件描述: 短表单的配置内容
3 | * @公司: thundersdata
4 | * @作者: 陈杰
5 | * @Date: 2020-04-29 17:56:31
6 | * @LastEditors: 黄姗姗
7 | * @LastEditTime: 2020-05-28 18:21:40
8 | */
9 | import React, { useContext, useEffect } from 'react';
10 | import { Form, Button, Input, Drawer } from 'antd';
11 | import { Store } from 'antd/lib/form/interface';
12 |
13 | export default ({
14 | visible,
15 | setVisible,
16 | onFinish,
17 | formConfig,
18 | }: {
19 | visible: boolean;
20 | setVisible: (visible: boolean) => void;
21 | onFinish: (values: Store) => void;
22 | formConfig: Store;
23 | }) => {
24 | const [form] = Form.useForm();
25 |
26 | useEffect(() => {
27 | form.setFieldsValue(formConfig);
28 | }, [formConfig]);
29 |
30 | const handleFinish = (values: Store) => {
31 | setVisible(false);
32 | onFinish(values);
33 | };
34 |
35 | return (
36 | setVisible(false)}>
37 |
44 |
45 |
46 |
47 |
50 |
51 |
52 |
53 | );
54 | };
55 |
--------------------------------------------------------------------------------
/ui/pages/manage/components/drawers/TableColumnConfigDrawer/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { useContext, useMemo, useEffect, useState } from 'react';
2 | import { Drawer, Form, Input, Button, Radio, Select, InputNumber, Tooltip, message } from 'antd';
3 | import { QuestionCircleOutlined } from '@ant-design/icons';
4 | import { Store } from 'antd/lib/form/interface';
5 | import { ColumnType } from 'antd/lib/table';
6 | import Context from '../../../Context';
7 |
8 | const { Option } = Select;
9 |
10 | export default function ({
11 | setVisible,
12 | visible,
13 | onSubmit,
14 | current,
15 | initialFetch,
16 | }: {
17 | visible: boolean;
18 | setVisible: (visible: boolean) => void;
19 | onSubmit: (values: Store) => void;
20 | current?: ColumnType;
21 | initialFetch?: string[];
22 | }) {
23 | const [form] = Form.useForm();
24 | const { baseClasses = [] } = useContext(Context);
25 | const [responseName, setResponseName] = useState();
26 |
27 | const initialValues = {
28 | title: '',
29 | dataIndex: '',
30 | align: 'left',
31 | ellipsis: false,
32 | copyable: false,
33 | valueType: 'text',
34 | hideInSearch: false,
35 | hideInTable: false,
36 | order: undefined,
37 | };
38 | useEffect(() => {
39 | if (current) {
40 | form.setFieldsValue(current);
41 | } else {
42 | form.setFieldsValue(initialValues);
43 | }
44 | }, [current]);
45 |
46 | /** initialFetch中第三个值为value-paramsName-responseName,获取初始数据选用responseName作为DTO */
47 | useEffect(() => {
48 | if (initialFetch && initialFetch.length === 3) {
49 | const responseName = initialFetch[2].split('-')[2];
50 | setResponseName(responseName);
51 | }
52 | }, [initialFetch]);
53 |
54 | const properties = useMemo(
55 | () => baseClasses.find(item => item.name === responseName)?.properties || [],
56 | [baseClasses, responseName],
57 | );
58 |
59 | const handleChange = (value: string) => {
60 | const matchClass = properties.find(item => item.value === value);
61 | form.setFieldsValue({
62 | title: matchClass?.label,
63 | dataIndex: value,
64 | });
65 | };
66 |
67 | return (
68 | {
73 | form.setFieldsValue(initialValues);
74 | setVisible(false);
75 | }}
76 | >
77 |
87 |
95 |
96 | )}
97 |
103 |
104 |
105 |
111 |
112 |
113 |
114 |
121 |
122 |
123 |
129 |
130 |
131 |
137 |
138 |
141 | 枚举值
142 |
154 |
155 |
156 |
157 | }
158 | name="valueEnum"
159 | >
160 |
161 |
162 |
163 |
188 |
189 |
190 |
196 |
197 |
198 |
204 |
205 |
206 |
207 |
208 |
211 |
212 |
213 | );
214 | }
215 |
--------------------------------------------------------------------------------
/ui/pages/manage/components/drawers/TableConfigDrawer/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Drawer, Form, Input, Button, Radio } from 'antd';
3 | import { filterEmpty } from '../../../../../utils';
4 | import { Store } from 'antd/lib/form/interface';
5 |
6 | export default ({
7 | setVisible,
8 | visible,
9 | onSubmit,
10 | tableConfig,
11 | }: {
12 | visible: boolean;
13 | setVisible: (visible: boolean) => void;
14 | onSubmit: (values: Store) => void;
15 | tableConfig: Store;
16 | }) => {
17 | return (
18 | setVisible(false)}
23 | >
24 |
32 |
33 |
34 |
35 |
36 |
37 |
40 |
41 |
42 | );
43 | };
44 |
--------------------------------------------------------------------------------
/ui/pages/manage/index.module.less:
--------------------------------------------------------------------------------
1 | :global {
2 | .umi-ui-layouts-dashboard_content {
3 | .ant-layout {
4 | height: 100%;
5 |
6 | &-header {
7 | height: 170px;
8 | }
9 | }
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/ui/pages/manage/index.tsx:
--------------------------------------------------------------------------------
1 | /*
2 | * @文件描述:
3 | * @公司: thundersdata
4 | * @作者: 陈杰
5 | * @Date: 2020-04-29 11:06:58
6 | * @LastEditors: 黄姗姗
7 | * @LastEditTime: 2020-05-29 14:27:52
8 | */
9 | import React, { useState, useEffect } from 'react'; // -> 暂时先解决报错,后期全部删掉
10 | import { Layout, message } from 'antd';
11 | import { IUiApi } from '@umijs/ui-types';
12 | import Context from './Context';
13 | import './index.module.less';
14 | import { CascaderOptionType } from 'antd/lib/cascader';
15 | import { Store } from 'antd/lib/form/interface';
16 | import { TemplateType } from '../../../interfaces/common';
17 | import { BaseClass } from '../../../interfaces/api';
18 | import TemplateList from './components/TemplateList';
19 | import Dashboard from './components/Dashboard';
20 | import ImportAction from './components/ImportAction';
21 | import ConstantConfigAction from './components/ConstantConfigAction';
22 |
23 | const { Header, Content } = Layout;
24 |
25 | export default ({ api }: { api: IUiApi }) => {
26 | const [databases, setDatabases] = useState([]);
27 | const [baseClasses, setBaseClasses] = useState([]);
28 | const [templateType, setTemplate] = useState();
29 | const [importModalVisible, setImportModalVisible] = useState(false);
30 | const [impConfigJson, setImpConfigJson] = useState(''); // 导入的json
31 | const [constantModalVisible, setConstantModalVisible] = useState(false);
32 | const [constantConfig, setConstantConfig] = useState('');
33 |
34 | /** 页面加载时调用后端接口,后端从services/api-lock.json读取数据,生成对应的接口以及类型 */
35 | useEffect(() => {
36 | (async () => {
37 | const result = (await api.callRemote({
38 | type: 'org.umi-plugin-page-creator.apiGenerator',
39 | payload: {
40 | fetchApiJson: true,
41 | },
42 | })) as { databases: CascaderOptionType[]; success: boolean; baseClasses: BaseClass[] };
43 |
44 | if (!result.success) {
45 | message.warning('你的项目没有集成pont');
46 | } else {
47 | setDatabases(result.databases);
48 | setBaseClasses(result.baseClasses);
49 | }
50 | })();
51 | }, []);
52 |
53 | /**
54 | * 页面初始化之后,通过服务端读取项目下的constant.ts文件,把文件内容返回回来
55 | */
56 | useEffect(() => {
57 | (async () => {
58 | const result = (await api.callRemote({
59 | type: 'org.umi-plugin-page-creator.constantLoad',
60 | })) as { success: boolean; data: string };
61 | if (result.success) {
62 | setConstantConfig(result.data);
63 | }
64 | })();
65 | }, []);
66 |
67 | const addTemplate = (templateType: TemplateType) => {
68 | setTemplate(templateType);
69 | setImpConfigJson('');
70 | message.success('模板添加成功,你可以开始配置了');
71 | };
72 |
73 | /** 导入 */
74 | const handleImportSubmit = (values: Store) => {
75 | setImportModalVisible(false);
76 | const { importConfig } = values;
77 | setImpConfigJson(importConfig);
78 | };
79 |
80 | /**
81 | * 保存常量的配置,调用服务端接口写回数据
82 | * @param code
83 | */
84 | const saveConstantConfig = async (code: string) => {
85 | const key = 'message';
86 | message.loading({ content: '正在保存,请稍候...', key });
87 | await api.callRemote({
88 | type: 'org.umi-plugin-page-creator.constantSave',
89 | payload: {
90 | code,
91 | }
92 | });
93 | message.success({ content: '常量配置保存成功', key });
94 | };
95 |
96 | return (
97 |
109 |
110 |
113 |
114 | {/* 导入 */}
115 |
120 |
125 |
126 |
127 |
128 |
129 | );
130 | };
131 |
--------------------------------------------------------------------------------
/ui/pages/mobile/Context.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * @文件描述:
3 | * @公司: thundersdata
4 | * @作者: 陈杰
5 | * @Date: 2020-04-29 11:29:07
6 | * @LastEditors: 陈杰
7 | * @LastEditTime: 2020-04-29 11:29:42
8 | */
9 | import { createContext } from 'react';
10 | import { IUiApi } from '@umijs/ui-types';
11 |
12 | const UIContext = createContext({} as { api: IUiApi });
13 |
14 | export default UIContext;
15 |
--------------------------------------------------------------------------------
/ui/pages/mobile/index.tsx:
--------------------------------------------------------------------------------
1 | import { Button } from 'antd';
2 | import { IUiApi } from '@umijs/ui-types';
3 |
4 | export default ({ api }: { api: IUiApi }) => {
5 | const { callRemote } = api;
6 | return (
7 |
18 | );
19 | };
20 |
--------------------------------------------------------------------------------
/ui/pages/screen/ColConfigDrawer/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react';
2 | import { Drawer, Form, InputNumber, Divider, Button, Select, Row, Col, Input } from 'antd';
3 | import { Store } from 'antd/lib/form/interface';
4 | import * as props from './props';
5 | import renderFormItem from '../../../components/FormItemConfig';
6 | import { FormItemProps } from '../../../../interfaces/common';
7 | import { renderPreviewImage } from '../helper';
8 | import { ScreenColConfig } from '../../../../interfaces/screen';
9 |
10 | const formLayout = {
11 | labelCol: {
12 | span: 8,
13 | },
14 | wrapperCol: {
15 | span: 16,
16 | },
17 | };
18 | const initialValues = {
19 | xs: 1,
20 | sm: 1,
21 | md: 1,
22 | lg: 1,
23 | xl: 1,
24 | xxl: 1,
25 | type: 'custom',
26 | };
27 |
28 | export default ({
29 | visible,
30 | toggleVisible,
31 | onFinish,
32 | col,
33 | }: {
34 | visible: boolean;
35 | toggleVisible: () => void;
36 | onFinish: (values: Store) => void;
37 | col: ScreenColConfig;
38 | }) => {
39 | const [form] = Form.useForm();
40 | const [type, setType] = useState('custom');
41 |
42 | useEffect(() => {
43 | if (col) {
44 | const {config: { type, chartConfig, xs, sm, md, lg, xl, xxl }} = col;
45 | form.setFieldsValue({
46 | type,
47 | ...chartConfig,
48 | xs: xs?.span,
49 | sm: sm?.span,
50 | md: md?.span,
51 | lg: lg?.span,
52 | xl: xl?.span,
53 | xxl: xxl?.span,
54 | });
55 | setType(type!);
56 | } else {
57 | form.setFieldsValue(initialValues);
58 | setType('custom');
59 | }
60 | }, [col])
61 |
62 | const renderOtherProps = () => {
63 | const otherProps = props[`${type}Props`];
64 | if (otherProps) {
65 | return otherProps.map((prop: FormItemProps) => renderFormItem({ formItem: prop }));
66 | }
67 | return null;
68 | };
69 |
70 | const handleFinish = (values: Store) => {
71 | onFinish(values);
72 | toggleVisible();
73 | };
74 |
75 | return (
76 |
77 |
78 |
79 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 | 图表
100 |
101 |
102 |
103 |
104 |
129 |
130 | {renderOtherProps()}
131 |
132 |
135 |
136 |
137 |
138 |
139 |
140 | {renderPreviewImage(type)}
141 |
142 |
143 |
144 | );
145 | };
146 |
--------------------------------------------------------------------------------
/ui/pages/screen/Context.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * @文件描述:
3 | * @公司: thundersdata
4 | * @作者: 陈杰
5 | * @Date: 2020-04-29 11:29:07
6 | * @LastEditors: 陈杰
7 | * @LastEditTime: 2020-05-06 16:25:51
8 | */
9 | import { createContext } from 'react';
10 | import { IUiApi } from '@umijs/ui-types';
11 |
12 | const UIContext = createContext({} as { api: IUiApi });
13 |
14 | export default UIContext;
15 |
--------------------------------------------------------------------------------
/ui/pages/screen/Dashboard/index.module.less:
--------------------------------------------------------------------------------
1 | .floatBtn {
2 | width: 60px;
3 | height: 60px;
4 | border-radius: 60px;
5 | display: flex;
6 | justify-content: center;
7 | align-items: center;
8 | position: fixed;
9 | bottom: 40px;
10 | right: 10px;
11 | }
12 | .submitBtn {
13 | right: 80px;
14 | }
15 | .layout {
16 | height: 100%;
17 | display: flex;
18 | flex-direction: column;
19 | justify-content: space-between;
20 | }
21 | .col {
22 | height: 100%;
23 | min-height: 225px;
24 | border: 1px solid blue;
25 | position: relative;
26 | flex: 1;
27 | display: flex;
28 | justify-content: center;
29 | align-items: center;
30 |
31 | :global {
32 | .ant-btn {
33 | position: absolute;
34 | right: 0;
35 | top: -30px;
36 | }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/ui/pages/screen/helper.tsx:
--------------------------------------------------------------------------------
1 | import preview from './preview';
2 |
3 | export function renderPreviewImage(type: string) {
4 | const item = preview.find(item => item.type === type && item.image);
5 | if (item) {
6 | return (
7 |
item.type === type)!.image}
9 | alt={type as string}
10 | style={{ width: '100%' }}
11 | />
12 | );
13 | }
14 | return (
15 | 对不起,暂无预览图
16 | );
17 | }
18 |
--------------------------------------------------------------------------------
/ui/pages/screen/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { IUiApi } from '@umijs/ui-types';
3 | import Context from './Context';
4 | import Dashboard from './Dashboard';
5 |
6 | export default ({ api }: { api: IUiApi }) => {
7 | return (
8 |
9 |
10 |
11 | );
12 | };
13 |
--------------------------------------------------------------------------------
/ui/pages/screen/structure.ts:
--------------------------------------------------------------------------------
1 | export default {
2 | title: '', // <- 大屏的名称
3 | titleStyle: {}, // <- 大屏名称的样式
4 | gutter: 16, // <- 大屏每个组件之间的间距
5 | layout: [{
6 | name: 'Left', // <- 要渲染的组件名称
7 | span: 7, // <- 左/中/右 布局占比
8 | rows: [{
9 | name: 'Row1', // <- 要渲染的组件名称
10 | height: 1, // <- 每行高度占比
11 | cols: [{
12 | name: 'Row1Col1', // <- 要渲染的组件名称
13 | span: 24, // <- 每列宽度占比
14 | type: 'bar', // <- 列中元素的类型(图表、自定义、其他)
15 | chartConfig: {},
16 | }]
17 | }, {
18 | name: 'Row2', // <- 要渲染的组件名称
19 | height: 1,
20 | cols: [{
21 | name: 'Row2Col1', // <- 要渲染的组件名称
22 | span: 24,
23 | type: 'bar',
24 | chartConfig: {},
25 | }]
26 | }, {
27 | name: 'Row3', // <- 要渲染的组件名称
28 | height: 1,
29 | cols: [{
30 | name: 'Row3Col1', // <- 要渲染的组件名称
31 | span: 24,
32 | type: 'custom', // 自定义时,用一个div占位
33 | chartConfig: {},
34 | }]
35 | }]
36 | }],
37 | }
38 |
--------------------------------------------------------------------------------
/ui/utils/index.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * @文件描述:
3 | * @公司: thundersdata
4 | * @作者: 陈杰
5 | * @Date: 2020-05-11 17:20:18
6 | * @LastEditors: 陈杰
7 | * @LastEditTime: 2020-05-14 11:17:27
8 | */
9 | import { Store } from 'antd/lib/form/interface';
10 | import { FormItemProps } from '../../interfaces/common';
11 | import { ScreenConfig, ScreenConfigPayload, LayoutType } from '../../interfaces/screen';
12 |
13 | /**
14 | * 将一个数组按照指定的列拆分成N个二维数组
15 | * @param formItems
16 | * @param cols
17 | */
18 | export function transformFormItemLines(formItems: FormItemProps[], cols = 3) {
19 | let lineNum =
20 | formItems.length % cols === 0
21 | ? formItems.length / cols
22 | : Math.floor(formItems.length / cols + 1);
23 | let res = [];
24 | for (let i = 0; i < lineNum; i++) {
25 | let temp = formItems.slice(i * cols, i * cols + cols);
26 | res.push(temp);
27 | }
28 | return res;
29 | }
30 |
31 | /**
32 | * 过滤掉空数据
33 | * @param values
34 | */
35 | export function filterEmpty(values: Store) {
36 | const filteredValues = {};
37 |
38 | Object.entries(values).forEach(([key, value]) => {
39 | if (value !== '' && value !== undefined && value !== null) {
40 | filteredValues[key] = value;
41 | }
42 | });
43 | return filteredValues;
44 | }
45 |
46 | /**
47 | * 将大屏配置参数转换成后端需要的数据结构
48 | * @param screenConfig
49 | */
50 | type ScreenConfigPayloadPart = Omit;
51 | export function transformConfig(screenConfig: ScreenConfig): ScreenConfigPayloadPart {
52 | const payload: ScreenConfigPayloadPart = {
53 | layout: [],
54 | };
55 |
56 | function generateConfig(type: LayoutType, config: ScreenConfig) {
57 | const layout = config[type];
58 | const { rows, ...restProps } = layout;
59 | return {
60 | ...restProps,
61 | name: type.charAt(0).toUpperCase() + type.substr(1, type.length),
62 | rows: rows.map((row, rowIndex) => ({
63 | name: `Row${rowIndex}`,
64 | height: row.height,
65 | cols: row.cols.map((col, colIndex) => ({
66 | name: `Row${rowIndex}Col${colIndex}`,
67 | ...col,
68 | })),
69 | })),
70 | };
71 | }
72 |
73 | const leftConfig = generateConfig('left', screenConfig);
74 | const centerConfig = generateConfig('center', screenConfig);
75 | const rightConfig = generateConfig('right', screenConfig);
76 |
77 | payload.layout.push(leftConfig);
78 | payload.layout.push(centerConfig);
79 | payload.layout.push(rightConfig);
80 |
81 | return payload;
82 | }
83 |
--------------------------------------------------------------------------------