├── docs
├── index.md
├── .vitepress
│ └── config.ts
└── introduction.md
├── .eslintrc
├── src
├── index.d.ts
├── assets
│ └── logo.png
├── vueCustomProperties.d.ts
├── env.d.ts
├── components
│ ├── Content
│ │ ├── components
│ │ │ ├── DraggableItem.vue
│ │ │ └── Drop.vue
│ │ └── Content.vue
│ ├── LeftSider
│ │ └── Sider.vue
│ ├── RightSider
│ │ ├── components
│ │ │ ├── FormItemConfig
│ │ │ │ ├── SwitchFormItemConfig.vue
│ │ │ │ ├── TreeSelectFormItemConfig.vue
│ │ │ │ ├── DividerConfig.vue
│ │ │ │ ├── RateFormItemConfig.vue
│ │ │ │ ├── InputFormItemConfig.vue
│ │ │ │ ├── InputNumberFormItemConfig.vue
│ │ │ │ ├── ColorPickerFormItemConfig.vue
│ │ │ │ ├── CheckBoxFormItemConfig.vue
│ │ │ │ ├── TimePickerFormItemConfig.vue
│ │ │ │ ├── SliderFormItemConfig.vue
│ │ │ │ ├── RadioFormItemConfig.vue
│ │ │ │ ├── SelectFormItemConfig.vue
│ │ │ │ ├── DatePickerFormItemConfig.vue
│ │ │ │ └── UploadFormItemConfig.vue
│ │ │ └── FormConfig
│ │ │ │ └── FormConfig.vue
│ │ └── Sider.vue
│ └── Layout
│ │ ├── components
│ │ └── SetGenerateCodeModal
│ │ │ └── SetGenerateCodeModal.vue
│ │ └── Layout.vue
├── main.ts
├── utils
│ ├── hook
│ │ └── useConfig.ts
│ ├── i18n
│ │ ├── index.ts
│ │ └── const.ts
│ └── index.ts
├── App.vue
├── const
│ └── const.ts
└── store
│ └── index.ts
├── .gitattributes
├── .vscode
└── extensions.json
├── .husky
└── pre-commit
├── lib
├── index.ts
└── components
│ └── proForm
│ ├── types
│ └── props.ts
│ └── index.tsx
├── .gitignore
├── public
└── favicon.ico
├── vite.config.ts
├── tsconfig.node.json
├── index.html
├── README.zh-CN.md
├── tsconfig.json
├── vite.config.lib.ts
├── README.md
└── package.json
/docs/index.md:
--------------------------------------------------------------------------------
1 | 111
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "@antfu"
3 | }
--------------------------------------------------------------------------------
/src/index.d.ts:
--------------------------------------------------------------------------------
1 | declare interface Window {
2 | $message: any;
3 | }
4 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 |
--------------------------------------------------------------------------------
/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | "recommendations": ["johnsoncodehk.volar"]
3 | }
4 |
--------------------------------------------------------------------------------
/.husky/pre-commit:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | . "$(dirname "$0")/_/husky.sh"
3 |
4 | npx lint-staged
5 |
--------------------------------------------------------------------------------
/lib/index.ts:
--------------------------------------------------------------------------------
1 | import ProFrom from './components/proForm/index'
2 |
3 | export { ProFrom }
4 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .DS_Store
3 | dist
4 | dist-ssr
5 | *.local
6 | yarn.lock
7 | es
8 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/doom-9-zz/naive-ui-form-creator/HEAD/public/favicon.ico
--------------------------------------------------------------------------------
/src/assets/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/doom-9-zz/naive-ui-form-creator/HEAD/src/assets/logo.png
--------------------------------------------------------------------------------
/src/vueCustomProperties.d.ts:
--------------------------------------------------------------------------------
1 | declare module '@vue/runtime-core' {
2 | export interface ComponentCustomProperties {
3 | $t: (target: string) => string;
4 | }
5 | }
6 |
7 | export {};
8 |
--------------------------------------------------------------------------------
/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vite'
2 | import vue from '@vitejs/plugin-vue'
3 |
4 | // https://vitejs.dev/config/
5 | export default defineConfig({
6 | plugins: [vue()],
7 | })
8 |
--------------------------------------------------------------------------------
/tsconfig.node.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "composite": true,
4 | "module": "esnext",
5 | "moduleResolution": "node"
6 | },
7 | "include": ["vite.config.ts","vite.config.lib.ts"]
8 | }
9 |
--------------------------------------------------------------------------------
/src/env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | declare module '*.vue' {
4 | import type { DefineComponent } from 'vue'
5 | // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types
6 | const component: DefineComponent<{}, {}, any>
7 | export default component
8 | }
9 |
--------------------------------------------------------------------------------
/src/components/Content/components/DraggableItem.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/docs/.vitepress/config.ts:
--------------------------------------------------------------------------------
1 | export default {
2 | title: 'VitePress111',
3 | description: 'Just playing around.',
4 | themeConfig: {
5 | sidebar: [
6 | {
7 | text: 'Guide',
8 | items: [
9 | { text: 'Introduction', link: '/introduction' },
10 | ],
11 | },
12 | ],
13 | },
14 | }
15 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | naive-ui-form-creator
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/src/main.ts:
--------------------------------------------------------------------------------
1 | import { createApp } from 'vue'
2 | import naive from 'naive-ui'
3 | import i18n from 'vue3-i18n-plugin'
4 | import { store } from './store/index'
5 | import App from './App.vue'
6 | import i18nConfig from './utils/i18n/const'
7 |
8 | createApp(App)
9 | .use(store)
10 | .use(naive)
11 | .use(i18n, {
12 | initial: 'zh',
13 | translationConfiguration: i18nConfig,
14 | })
15 | .mount('#app')
16 |
--------------------------------------------------------------------------------
/src/utils/hook/useConfig.ts:
--------------------------------------------------------------------------------
1 | import type { Ref } from 'vue'
2 | import { ref, watch } from 'vue'
3 |
4 | import { store } from '../../store/index'
5 |
6 | export const useConfig = >(option: { sync?: boolean }): Ref => {
7 | const Config = ref({ ...store.getters.formItemConfig })
8 |
9 | if (option.sync === undefined || option.sync) {
10 | watch(
11 | Config,
12 | () => {
13 | store.commit('changeSelectedFormItemConfig', Config.value)
14 | },
15 | {
16 | deep: true,
17 | },
18 | )
19 | }
20 |
21 | return Config
22 | }
23 |
--------------------------------------------------------------------------------
/README.zh-CN.md:
--------------------------------------------------------------------------------
1 | [English](https://github.com/doom-9/naive-ui-form-creator/blob/main/README.md)
2 |
3 | ## 简介
4 |
5 | `naive-ui-form-creator`是一个基于 `naive-ui` 组件库的**可视化表单生成器**,如果你有经常**制作`naive-ui`表单**的需求,可以使用此工具。`naive-ui-form-creator`是**可视化的**,**支持拖拽排序的**,它能够及时呈现和反馈表单的内容。
6 |
7 | 在线预览:[地址](https://naive-create-form.vercel.app/)
8 |
9 | ## 如何使用
10 |
11 | 你可以 Fock 项目,自己部署访问,或者直接访问上面已经部署好的地址。
12 |
13 | 当你制作完成表单后,点击右上角的**生成组件代码**,你就能得到一份 Vue3 的模版代码,然后拷贝到你的项目中使用。
14 |
15 | ## 提示
16 |
17 | 使用过程中如果遇到问题或者 bug,欢迎提交 issue,会及时修复的。
18 |
19 | ## Todo
20 |
21 | - [ ] 增加更多组件。
22 | - [x] 支持校验规则。
23 | - [ ] 优化拖拽动画。
24 | - [ ] 优化操作逻辑。
25 | - [x] 国际化
26 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "esnext",
4 | "useDefineForClassFields": true,
5 | "module": "esnext",
6 | "moduleResolution": "node",
7 | "strict": true,
8 | "jsx": "preserve",
9 | "jsxFactory": "h",
10 | "jsxFragmentFactory": "Fragment",
11 | "sourceMap": true,
12 | "resolveJsonModule": true,
13 | "isolatedModules": true,
14 | "esModuleInterop": true,
15 | "lib": ["esnext", "dom"],
16 | "skipLibCheck": true
17 | },
18 | "include": [
19 | "src/**/*.ts",
20 | "src/**/*.d.ts",
21 | "src/**/*.tsx",
22 | "src/**/*.vue",
23 | "lib/**/*.tsx",
24 | "lib/**/*.ts"
25 | ],
26 | "references": [{ "path": "./tsconfig.node.json" }]
27 | }
28 |
--------------------------------------------------------------------------------
/vite.config.lib.ts:
--------------------------------------------------------------------------------
1 | import { resolve } from 'path'
2 | import { defineConfig } from 'vite'
3 | import vue from '@vitejs/plugin-vue'
4 | import vueJsx from '@vitejs/plugin-vue-jsx'
5 | import dts from 'vite-plugin-dts'
6 |
7 | // https://vitejs.dev/config/
8 | export default defineConfig({
9 | plugins: [
10 | vue(),
11 | vueJsx({
12 | // options are passed on to @vue/babel-plugin-jsx
13 | }),
14 | dts({
15 | include: ['lib/**/*.tsx', 'lib/**/*.ts'],
16 | insertTypesEntry: true,
17 | }),
18 | ],
19 | build: {
20 | lib: {
21 | entry: resolve(__dirname, 'lib/index.ts'),
22 | formats: ['es', 'cjs'],
23 | fileName: 'index',
24 | },
25 | rollupOptions: {
26 | // 确保外部化处理那些你不想打包进库的依赖
27 | external: ['vue', 'naive-ui'],
28 | output: {
29 | // 在 UMD 构建模式下为这些外部化的依赖提供一个全局变量
30 | globals: {
31 | vue: 'Vue',
32 | },
33 | preserveModules: true,
34 | },
35 | },
36 | outDir: 'dist',
37 | },
38 | })
39 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [中文](https://github.com/doom-9/naive-ui-form-creator/blob/main/README.zh-CN.md)
2 |
3 | ## Introduce
4 |
5 | `Naive UI form creator` is a **visual form generator** based on the `naive UI` component library. You can use this tool if you often need to **create `naive UI` forms**. Naive UI form creator is **Visual** and **Support drag-and-drop sorting**,which can present and feed back the contents of the form in time.
6 |
7 | Online preview: [Address](https://naive-create-form.vercel.app/)
8 |
9 | ## How to use
10 |
11 | You can either deploy your own access to the Fork project or directly access the address already deployed above.
12 |
13 | When you have finished making the form, click **Generate Component Code** in the upper right corner, and you will get a template code for Vue3 and copy it to use in your project.
14 |
15 | ## Tips
16 |
17 | If you encounter problems or bugs during use, you are welcome to submit issues.
18 |
19 | ## Todo
20 |
21 | - [ ] Add more components.
22 | - [x] Supports checking rules.
23 | - [ ] Optimize drag animation.
24 | - [ ] Optimize operation logic.
25 | - [x] Internationalization
26 |
--------------------------------------------------------------------------------
/src/utils/i18n/index.ts:
--------------------------------------------------------------------------------
1 | import type { App } from 'vue'
2 | import { ref } from 'vue'
3 |
4 | interface I18nOptions {
5 | initial: string
6 | i18nConfig: Record
7 | }
8 |
9 | const local = ref(undefined)
10 |
11 | const getOptionsValue = (target: string, options: Record): string => {
12 | const targetSplitArray = target.split('.')
13 | if (local.value === undefined)
14 | return ''
15 | targetSplitArray.unshift(local.value)
16 | let current = options
17 | while (targetSplitArray.length > 0) {
18 | current = current[targetSplitArray[0]]
19 | if (current === undefined)
20 | return ''
21 |
22 | targetSplitArray.shift()
23 | }
24 | if (typeof current === 'string')
25 | return current
26 |
27 | return ''
28 | }
29 |
30 | export const changeLocal = (value: string): void => {
31 | local.value = value
32 | }
33 |
34 | export default {
35 | install: (app: App, options: I18nOptions) => {
36 | local.value = options.initial
37 | app.config.globalProperties.$t = (target: string): string => {
38 | return getOptionsValue(target, options.i18nConfig)
39 | }
40 | },
41 | }
42 |
--------------------------------------------------------------------------------
/src/components/LeftSider/Sider.vue:
--------------------------------------------------------------------------------
1 |
19 |
20 |
21 |
22 |
23 | {{ $t('formComponent') }}
24 |
25 |
32 | {{ $t(item.formItemConfig.label) }}
33 |
34 |
35 | {{ $t('layoutComponent') }}
36 |
37 |
44 | {{ $t(item.formItemConfig.label) }}
45 |
46 |
47 |
48 |
49 |
50 |
--------------------------------------------------------------------------------
/src/components/RightSider/components/FormItemConfig/SwitchFormItemConfig.vue:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 | small
24 |
25 |
26 | medium
27 |
28 |
29 | large
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 | {{ $t('yes') }}
39 |
40 |
41 | {{ $t('no') }}
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
--------------------------------------------------------------------------------
/src/components/RightSider/components/FormItemConfig/TreeSelectFormItemConfig.vue:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 | {{ $t('yes') }}
24 |
25 |
26 | {{ $t('no') }}
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 | small
36 |
37 |
38 | medium
39 |
40 |
41 | large
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
--------------------------------------------------------------------------------
/src/components/RightSider/components/FormItemConfig/DividerConfig.vue:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 | {{ $t('yes') }}
17 |
18 |
19 | {{ $t('no') }}
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 | {{ $t('yes') }}
29 |
30 |
31 | {{ $t('no') }}
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 | left
41 |
42 |
43 | center
44 |
45 |
46 | right
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
--------------------------------------------------------------------------------
/src/components/Layout/components/SetGenerateCodeModal/SetGenerateCodeModal.vue:
--------------------------------------------------------------------------------
1 |
38 |
39 |
40 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
--------------------------------------------------------------------------------
/src/components/RightSider/components/FormItemConfig/RateFormItemConfig.vue:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 | small
24 |
25 |
26 | medium
27 |
28 |
29 | large
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 | {{ $t('yes') }}
42 |
43 |
44 | {{ $t('no') }}
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "naive-create-form",
3 | "version": "0.0.0",
4 | "scripts": {
5 | "dev": "vite",
6 | "build": "vue-tsc --noEmit && vite build",
7 | "lib-build": "vite build -c vite.config.lib.ts",
8 | "serve": "vite preview",
9 | "lint": "eslint .",
10 | "check": "npx taze -w",
11 | "docs:dev": "vitepress dev docs",
12 | "docs:build": "vitepress build docs",
13 | "docs:serve": "vitepress serve docs",
14 | "prepare": "husky install"
15 | },
16 | "dependencies": {
17 | "@types/uuid": "^8.3.4",
18 | "@vicons/antd": "^0.11.0",
19 | "highlight.js": "^11.6.0",
20 | "naive-ui": "^2.31.0",
21 | "uuid": "^8.3.2",
22 | "vue": "^3.0.0",
23 | "vue3-i18n-plugin": "^1.0.9",
24 | "vuex": "^4.0.2"
25 | },
26 | "devDependencies": {
27 | "@antfu/eslint-config": "^0.25.2",
28 | "@types/node": "^18.0.4",
29 | "@vicons/fluent": "^0.12.0",
30 | "@vitejs/plugin-vue": "^2.3.3",
31 | "@vitejs/plugin-vue-jsx": "^1.3.10",
32 | "eslint": "^8.19.0",
33 | "husky": "^7.0.4",
34 | "lint-staged": "^13.0.3",
35 | "typescript": "^4.7.4",
36 | "vite": "^3.0.0",
37 | "vite-plugin-dts": "^1.3.0",
38 | "vitepress": "^1.0.0-draft.8",
39 | "vue-tsc": "^0.3.0"
40 | },
41 | "peerDependencies": {
42 | "naive-ui": "^2.31.0",
43 | "vue": "^3.0.0"
44 | },
45 | "lint-staged": {
46 | "*.ts": [
47 | "prettier --write",
48 | "eslint --fix"
49 | ],
50 | "*.vue": [
51 | "prettier --write",
52 | "eslint --fix"
53 | ],
54 | "*.tsx": [
55 | "prettier --write",
56 | "eslint --fix"
57 | ]
58 | },
59 | "license": "MIT",
60 | "files": [
61 | "es",
62 | "README.md"
63 | ],
64 | "types": "dist/index.d.ts",
65 | "main": "dist/index.js",
66 | "module": "dist/index.mjs"
67 | }
68 |
--------------------------------------------------------------------------------
/src/App.vue:
--------------------------------------------------------------------------------
1 |
40 |
41 |
42 |
48 |
49 |
55 |
56 |
57 |
58 |
59 |
64 |
--------------------------------------------------------------------------------
/src/components/RightSider/components/FormItemConfig/InputFormItemConfig.vue:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 | {{ $t('yes') }}
24 |
25 |
26 | {{ $t('no') }}
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 | text
42 |
43 |
44 | password
45 |
46 |
47 | textarea
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 | small
57 |
58 |
59 | medium
60 |
61 |
62 | large
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
--------------------------------------------------------------------------------
/src/components/RightSider/components/FormItemConfig/InputNumberFormItemConfig.vue:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 | {{ $t('yes') }}
24 |
25 |
26 | {{ $t('no') }}
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 | {{ $t('yes') }}
45 |
46 |
47 | {{ $t('no') }}
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 | small
57 |
58 |
59 | medium
60 |
61 |
62 | large
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
--------------------------------------------------------------------------------
/src/components/RightSider/components/FormItemConfig/ColorPickerFormItemConfig.vue:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 | small
24 |
25 |
26 | medium
27 |
28 |
29 | large
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 | {{ $t('yes') }}
39 |
40 |
41 | {{ $t('no') }}
42 |
43 |
44 |
45 |
46 |
47 |
69 |
70 |
71 |
81 |
82 |
83 |
84 |
85 |
86 |
--------------------------------------------------------------------------------
/src/components/RightSider/components/FormItemConfig/CheckBoxFormItemConfig.vue:
--------------------------------------------------------------------------------
1 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
55 |
56 |
57 |
62 |
63 |
64 | -
65 |
66 |
67 |
72 |
73 |
74 |
75 |
76 |
77 | {{ $t('apply') }}
78 |
79 |
80 |
81 |
82 |
83 |
--------------------------------------------------------------------------------
/src/components/RightSider/components/FormItemConfig/TimePickerFormItemConfig.vue:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
37 |
38 |
39 |
40 |
41 |
42 | {{ $t('yes') }}
43 |
44 |
45 | {{ $t('no') }}
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 | small
55 |
56 |
57 | medium
58 |
59 |
60 | large
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 | {{ $t('yes') }}
73 |
74 |
75 | {{ $t('no') }}
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
--------------------------------------------------------------------------------
/src/components/RightSider/components/FormItemConfig/SliderFormItemConfig.vue:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 | {{ $t('yes') }}
24 |
25 |
26 | {{ $t('no') }}
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 | {{ $t('yes') }}
36 |
37 |
38 | {{ $t('no') }}
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 | {{ $t('yes') }}
48 |
49 |
50 | {{ $t('no') }}
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 | {{ $t('yes') }}
60 |
61 |
62 | {{ $t('no') }}
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
--------------------------------------------------------------------------------
/src/components/RightSider/components/FormItemConfig/RadioFormItemConfig.vue:
--------------------------------------------------------------------------------
1 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 | small
46 |
47 |
48 | medium
49 |
50 |
51 | large
52 |
53 |
54 |
55 |
56 |
57 |
63 |
64 |
65 |
70 |
71 |
72 | -
73 |
74 |
75 |
80 |
81 |
82 |
83 |
84 |
85 | {{ $t('apply') }}
86 |
87 |
88 |
89 |
90 |
91 |
--------------------------------------------------------------------------------
/lib/components/proForm/types/props.ts:
--------------------------------------------------------------------------------
1 | import type {
2 | CheckboxGroupProps,
3 | CheckboxProps,
4 | ColorPickerProps,
5 | DatePickerProps,
6 | FormItemProps,
7 | InputNumberProps,
8 | InputProps,
9 | RadioGroupProps,
10 | RadioProps,
11 | RateProps,
12 | SelectProps,
13 | SliderProps,
14 | SwitchProps,
15 | TimePickerProps,
16 | UploadProps,
17 | } from 'naive-ui'
18 | // import type { FileInfo } from 'naive-ui/es/upload/src/interface'
19 |
20 | type addCommonProps> = T & {
21 | formItemProps?: Omit
22 | label: string
23 | key: string
24 | show?: boolean
25 | tooltipConfig?: {
26 | show: boolean
27 | text: string
28 | }
29 | }
30 |
31 | export type ProFormItem =
32 | | addCommonProps<{
33 | type: 'input'
34 | props?: Omit
35 | }>
36 | | addCommonProps<{
37 | type: 'inputNumber'
38 | props?: Omit
39 | }>
40 | | addCommonProps<{
41 | type: 'radio'
42 | props?:
43 | | Omit
44 | | Omit
45 | valueEnum: {
46 | label: string
47 | value: string | number
48 | }[]
49 | group?: boolean
50 | }>
51 | | addCommonProps<{
52 | type: 'select'
53 | props?: Omit
54 | valueEnum: {
55 | label: string
56 | value: string | number
57 | disabled: boolean
58 | }[]
59 | }>
60 | | addCommonProps<{
61 | type: 'rate'
62 | props?: Omit
63 | }>
64 | | addCommonProps<{
65 | type: 'switch'
66 | props?: Omit
67 | }>
68 | | addCommonProps<{
69 | type: 'timePicker'
70 | props?: Omit
71 | }>
72 | | addCommonProps<{
73 | type: 'datePicker'
74 | props?: Omit
75 | }>
76 | | addCommonProps<{
77 | type: 'colorPicker'
78 | props?: Omit
79 | }>
80 | | addCommonProps<{
81 | type: 'checkbox'
82 | props?:
83 | | Omit<
84 | CheckboxProps,
85 | 'onUpdateChecked' | 'value' | 'label' | 'disabled'
86 | >
87 | | Omit
88 | valueEnum: {
89 | label: string
90 | value: string | number
91 | disabled: boolean
92 | }[]
93 | }>
94 | | addCommonProps<{
95 | type: 'upload'
96 | props?: Omit
97 | buttonText?: string
98 | }>
99 | | addCommonProps<{
100 | type: 'slider'
101 | props?: Omit
102 | }>
103 | | {
104 | type: 'divider'
105 | dashed?: boolean
106 | titlePlacement?: 'left' | 'right' | 'center'
107 | vertical?: boolean
108 | text: string
109 | }
110 |
111 | export type ProFormItemType = 'input' | 'radio'
112 |
--------------------------------------------------------------------------------
/src/components/RightSider/components/FormConfig/FormConfig.vue:
--------------------------------------------------------------------------------
1 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 | small
38 |
39 |
40 | medium
41 |
42 |
43 | large
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 | left
56 |
57 |
58 | top
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 | left
71 |
72 |
73 | right
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 | left
92 |
93 |
94 | right
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 | {{ $t('reset') }}
103 |
104 |
105 |
106 |
107 |
108 |
--------------------------------------------------------------------------------
/docs/introduction.md:
--------------------------------------------------------------------------------
1 |
138 |
139 | # Docs
140 |
141 | This is a .md using a custom component
142 |
143 |
144 |
145 | ## More docs
146 |
147 | ...
148 |
--------------------------------------------------------------------------------
/src/components/RightSider/components/FormItemConfig/SelectFormItemConfig.vue:
--------------------------------------------------------------------------------
1 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 | {{ $t('yes') }}
50 |
51 |
52 | {{ $t('no') }}
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 | small
65 |
66 |
67 | medium
68 |
69 |
70 | large
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 | {{ $t('yes') }}
80 |
81 |
82 | {{ $t('no') }}
83 |
84 |
85 |
86 |
87 |
88 |
94 |
95 |
96 |
101 |
102 |
103 | -
104 |
105 |
106 |
111 |
112 |
113 |
114 |
115 |
116 | {{ $t('apply') }}
117 |
118 |
119 |
120 |
121 |
122 |
--------------------------------------------------------------------------------
/src/components/RightSider/Sider.vue:
--------------------------------------------------------------------------------
1 |
77 |
78 |
79 |
80 | {{ $t('setUp') }}
81 |
82 |
83 |
84 |
88 |
89 |
90 | {{ $t('addFormItemAndSelect') }}
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
--------------------------------------------------------------------------------
/src/components/Layout/Layout.vue:
--------------------------------------------------------------------------------
1 |
39 |
40 |
41 |
42 |
43 |
53 |
54 | naive-ui-form-creator
55 |
56 |
57 |
65 | {{ $t('generateCodeSettings') }}
66 |
67 |
68 |
69 |
70 |
71 | {{ $t('generateComponentCode') }}
72 |
73 |
74 |
75 |
76 |
77 | {{ $props.isDark ? $t('light') : $t('dark') }}
78 |
79 |
80 | {{ $props.isEnglish ? '中文' : 'English' }}
81 |
82 |
83 | Github
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
99 |
100 |
101 |
102 |
103 | {{ $t('tip') }}
104 |
105 |
106 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
--------------------------------------------------------------------------------
/src/components/RightSider/components/FormItemConfig/DatePickerFormItemConfig.vue:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 | date
24 |
25 |
26 | datetime
27 |
28 |
29 | daterange
30 |
31 |
32 | datetimerange
33 |
34 |
35 | month
36 |
37 |
38 | year
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
91 |
92 |
93 |
94 |
95 |
96 | {{ $t('yes') }}
97 |
98 |
99 | {{ $t('no') }}
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 | small
109 |
110 |
111 | medium
112 |
113 |
114 | large
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
--------------------------------------------------------------------------------
/src/components/Content/components/Drop.vue:
--------------------------------------------------------------------------------
1 |
128 |
129 |
130 |
136 |
137 |
138 |
139 |
140 |
141 |
--------------------------------------------------------------------------------
/src/const/const.ts:
--------------------------------------------------------------------------------
1 | import type { State } from '../store'
2 |
3 | type selectItem = Array<{
4 | value: string
5 | formItemConfig: {
6 | [key: string]: any
7 | }
8 | }>
9 |
10 | export const options: selectItem = [
11 | {
12 | value: '0',
13 | formItemConfig: {
14 | label: 'input',
15 | name: undefined,
16 | rules: [],
17 | clearable: false,
18 | maxlength: undefined,
19 | minlength: undefined,
20 | type: 'text',
21 | size: 'medium',
22 | },
23 | },
24 | {
25 | value: '1',
26 | formItemConfig: {
27 | label: 'inputNumber',
28 | name: undefined,
29 | rules: [],
30 | clearable: false,
31 | max: undefined,
32 | min: undefined,
33 | type: 'text',
34 | size: 'medium',
35 | step: 1,
36 | showButton: true,
37 | },
38 | },
39 | {
40 | value: '2',
41 | formItemConfig: {
42 | label: 'radio',
43 | name: undefined,
44 | rules: [],
45 | size: 'medium',
46 | options: [],
47 | },
48 | },
49 | {
50 | value: '3',
51 | formItemConfig: {
52 | label: 'rate',
53 | name: undefined,
54 | rules: [],
55 | size: 'medium',
56 | count: 5,
57 | allowHalf: false,
58 | },
59 | },
60 | {
61 | value: '4',
62 | formItemConfig: {
63 | label: 'select',
64 | name: undefined,
65 | rules: [],
66 | size: 'medium',
67 | multiple: false,
68 | placeholder: '请选择',
69 | clearable: false,
70 | options: [],
71 | },
72 | },
73 | {
74 | value: '5',
75 | formItemConfig: {
76 | label: 'slider',
77 | name: undefined,
78 | rules: [],
79 | max: 100,
80 | min: 0,
81 | step: 1,
82 | range: false,
83 | reverse: false,
84 | vertical: false,
85 | tooltip: true,
86 | },
87 | },
88 | {
89 | value: '6',
90 | formItemConfig: {
91 | label: 'switch',
92 | name: undefined,
93 | rules: [],
94 | round: true,
95 | size: 'medium',
96 | },
97 | },
98 | {
99 | value: '7',
100 | formItemConfig: {
101 | label: 'timePicker',
102 | name: undefined,
103 | rules: [],
104 | actions: [],
105 | clearable: false,
106 | format: 'HH:mm:ss',
107 | size: 'medium',
108 | placeholder: undefined,
109 | use12Hours: false,
110 | },
111 | },
112 | {
113 | value: '12',
114 | formItemConfig: {
115 | label: 'datePicker',
116 | name: undefined,
117 | rules: [],
118 | actions: [],
119 | clearable: false,
120 | format: '',
121 | size: 'medium',
122 | placeholder: undefined,
123 | type: 'date',
124 | startPlaceholder: undefined,
125 | endPlaceholder: undefined,
126 | separator: undefined,
127 | },
128 | },
129 | {
130 | value: '8',
131 | formItemConfig: {
132 | label: 'treeSelect',
133 | name: undefined,
134 | rules: [],
135 | size: 'medium',
136 | clearable: false,
137 | },
138 | },
139 | {
140 | value: '9',
141 | formItemConfig: {
142 | label: 'upload',
143 | name: undefined,
144 | rules: [],
145 | accept: undefined,
146 | action: undefined,
147 | defaultUpload: true,
148 | data: [],
149 | headers: [],
150 | listType: 'text',
151 | max: undefined,
152 | method: 'POST',
153 | multiple: false,
154 | fileName: 'file',
155 | withCredentials: false,
156 | showCancelButton: true,
157 | showDownloadButton: false,
158 | showRemoveButton: true,
159 | showRetryButton: true,
160 | showFileList: true,
161 | },
162 | },
163 | {
164 | value: '10',
165 | formItemConfig: {
166 | label: 'colorPicker',
167 | name: undefined,
168 | rules: [],
169 | size: 'medium',
170 | modes: [],
171 | showAlpha: true,
172 | actions: [],
173 | },
174 | },
175 | {
176 | value: '11',
177 | formItemConfig: {
178 | label: 'checkbox',
179 | name: undefined,
180 | rules: [],
181 | max: undefined,
182 | min: undefined,
183 | options: [],
184 | },
185 | },
186 | ]
187 |
188 | export const LayoutOptions: selectItem = [
189 | {
190 | value: '13',
191 | formItemConfig: {
192 | label: 'divider',
193 | name: 'divider',
194 | dashed: false,
195 | vertical: false,
196 | titlePlacement: 'center',
197 | },
198 | },
199 | ]
200 |
201 | export const initialFormState: State['formConfig'] = {
202 | labelPlacement: 'left',
203 | labelWidth: '80',
204 | labelAlign: 'left',
205 | showRequireMark: false,
206 | size: 'medium',
207 | inline: false,
208 | showFeedback: true,
209 | showLabel: true,
210 | requireMarkPlacement: 'right',
211 | model: 'formValue',
212 | }
213 |
214 | export const UI_NAME = 'naive-ui'
215 | export const PREFIX = 'n'
216 |
217 | export const getItemConfig = (
218 | value: string,
219 | ): {
220 | [key: string]: any
221 | } => {
222 | const mergeOptions = [...options, ...LayoutOptions]
223 | const config = mergeOptions.find(item => item.value === value)
224 | if (config !== undefined)
225 | return config.formItemConfig
226 |
227 | return {}
228 | }
229 |
230 | export const ruleOptions: Array<{
231 | value: string
232 | label: string
233 | }> = [
234 | {
235 | label: 'Required verification',
236 | value: '0',
237 | },
238 | {
239 | label: 'Mobile phone number verification',
240 | value: '1',
241 | },
242 | {
243 | label: 'ID card verification',
244 | value: '2',
245 | },
246 | ]
247 |
--------------------------------------------------------------------------------
/src/utils/i18n/const.ts:
--------------------------------------------------------------------------------
1 | export default {
2 | zh: {
3 | input: '文本输入',
4 | inputNumber: '数字输入',
5 | radio: '单选',
6 | rate: '评分',
7 | select: '选择器',
8 | slider: '滑动选择',
9 | switch: '开关',
10 | timePicker: '时间选择器',
11 | datePicker: '日期选择器',
12 | treeSelect: '树形选择',
13 | upload: '上传',
14 | colorPicker: '颜色选择器',
15 | checkbox: '复选框',
16 | divider: '分割线',
17 | formComponent: '表单组件',
18 | layoutComponent: '布局组件',
19 | dark: '暗色',
20 | light: '亮色',
21 | preview: '预览',
22 | generateCodeSettings: '生成代码设置',
23 | generateComponentCode: '生成组件代码',
24 | tip: '使用过程中如果遇到问题或者 bug,欢迎提交 issue。',
25 | setUp: '设置',
26 | formItemConfiguration: '表单项配置',
27 | formConfiguration: '表单配置',
28 | add: '试着添加一个',
29 | addFormItem: '从左侧添加表单项',
30 | addFormItemAndSelect: '试着添加一个并且选中它',
31 | model: '收集到的值的对象标识',
32 | size: '尺寸',
33 | inline: '行内',
34 | labelPlacement: '标签位置',
35 | labelWidth: '标签宽度',
36 | labelAlign: '标签对齐',
37 | showFeedback: '显示反馈',
38 | showLabel: '显示标签',
39 | showRequireMark: '显示必填标记',
40 | requireMarkPlacement: '必填标记位置',
41 | reset: '重置',
42 | id: '标识',
43 | name: '名称',
44 | rules: '校验规则',
45 | maxSelect: '可被勾选的 checkbox 的最大数量',
46 | minSelect: '可被勾选的 checkbox 的最小数量',
47 | addOptions: '添加选项',
48 | apply: '应用',
49 | showAlpha: '显示透明度',
50 | modes: '模式',
51 | actions: '操作',
52 | type: '类型',
53 | format: '格式',
54 | clearable: '可清空',
55 | startPlaceholder: '开始时的占位符',
56 | endPlaceholder: '结束时的占位符',
57 | separator: '分隔符',
58 | placeholder: '占位符',
59 | maxlength: '最大长度',
60 | minlength: '最小长度',
61 | max: '最大值',
62 | min: '最小值',
63 | step: '步长',
64 | showButton: '显示按钮',
65 | count: '数量',
66 | allowHalf: '允许半选',
67 | multiple: '多选',
68 | range: '范围',
69 | reverse: '反向',
70 | vertical: '垂直',
71 | tooltip: '提示',
72 | round: '圆角',
73 | use12Hours: '12 小时制',
74 | accept: '接受文件类型',
75 | action: '请求提交的地址',
76 | uploadData: '需要附加的数据',
77 | headers: '请求头',
78 | defaultUpload: '默认上传',
79 | listType: '列表类型',
80 | uploadMax: '最大上传数量',
81 | method: '请求方法',
82 | fileName: '文件名',
83 | withCredentials: '携带cookie',
84 | showCancelButton: '显示取消按钮',
85 | showDownloadButton: '显示下载按钮',
86 | showRemoveButton: '显示移除按钮',
87 | showRetryButton: '显示重试按钮',
88 | showFileList: '显示文件列表',
89 | dashed: '虚线',
90 | titlePlacement: '标题位置',
91 | yes: '是',
92 | no: '否',
93 | import: '自动添加import',
94 | confirmAndCancelBtn: '确认和取消按钮',
95 | confirm: '确认',
96 | cancel: '取消',
97 | modalTitle: '选项',
98 | copy: '复制',
99 | redo: '重做',
100 | },
101 | en: {
102 | input: 'Input',
103 | inputNumber: 'InputNumber',
104 | radio: 'Radio',
105 | rate: 'Rate',
106 | select: 'Select',
107 | slider: 'Slider',
108 | switch: 'Switch',
109 | timePicker: 'TimePicker',
110 | datePicker: 'DatePicker',
111 | treeSelect: 'TreeSelect',
112 | upload: 'Upload',
113 | colorPicker: 'ColorPicker',
114 | checkbox: 'Checkbox',
115 | divider: 'Divider',
116 | formComponent: 'Form Component',
117 | layoutComponent: 'Layout Component',
118 | dark: 'Dark',
119 | light: 'Light',
120 | preview: 'Preview',
121 | generateCodeSettings: 'Generate Code Settings',
122 | generateComponentCode: 'Generate Component Code',
123 | tip: 'If you encounter problems or bugs during use, you are welcome to submit issue.',
124 | setUp: 'Set Up',
125 | formItemConfiguration: 'Form Item Configuration',
126 | formConfiguration: 'Form Configuration',
127 | add: 'Try adding one',
128 | addFormItem: 'Add form items from left',
129 | addFormItemAndSelect: 'Try adding one and select it',
130 | model: 'Model',
131 | size: 'Size',
132 | inline: 'Inline',
133 | labelPlacement: 'Label Placement',
134 | labelWidth: 'Label Width',
135 | labelAlign: 'Label Align',
136 | showFeedback: 'Show Feedback',
137 | showLabel: 'Show Label',
138 | showRequireMark: 'Show Require Mark',
139 | requireMarkPlacement: 'Require Mark Placement',
140 | reset: 'Reset',
141 | id: 'ID',
142 | name: 'Name',
143 | rules: 'Rules',
144 | maxSelect: 'Maximum number of checkboxes that can be checked',
145 | minSelect: 'Minimum number of checkboxes that can be checked',
146 | addOptions: 'Add Options',
147 | apply: 'Apply',
148 | showAlpha: 'Show Alpha',
149 | modes: 'Modes',
150 | actions: 'Actions',
151 | type: 'Type',
152 | format: 'Format',
153 | clearable: 'Clearable',
154 | startPlaceholder: 'Start Placeholder',
155 | endPlaceholder: 'End Placeholder',
156 | separator: 'Separator',
157 | placeholder: 'Placeholder',
158 | maxlength: 'Maximum Length',
159 | minlength: 'Minimum Length',
160 | max: 'Maximum',
161 | min: 'Minimum',
162 | step: 'Step',
163 | showButton: 'Show Button',
164 | count: 'Count',
165 | allowHalf: 'Allow Half',
166 | multiple: 'Multiple',
167 | range: 'Range',
168 | reverse: 'Reverse',
169 | vertical: 'Vertical',
170 | tooltip: 'Tooltip',
171 | round: 'Round',
172 | use12Hours: 'Use 12 Hours',
173 | accept: 'Accept File Type',
174 | action: 'Action',
175 | uploadData: 'Upload Data',
176 | headers: 'Headers',
177 | defaultUpload: 'Default Upload',
178 | listType: 'List Type',
179 | uploadMax: 'Maximum Upload',
180 | method: 'Method',
181 | fileName: 'File Name',
182 | withCredentials: 'With Credentials',
183 | showCancelButton: 'Show Cancel Button',
184 | showDownloadButton: 'Show Download Button',
185 | showRemoveButton: 'Show Remove Button',
186 | showRetryButton: 'Show Retry Button',
187 | showFileList: 'Show File List',
188 | dashed: 'Dashed',
189 | titlePlacement: 'Title Placement',
190 | yes: 'Yes',
191 | no: 'No',
192 | import: 'Auto add import',
193 | confirmAndCancelBtn: 'Confirm and Cancel Button',
194 | confirm: 'Confirm',
195 | cancel: 'Cancel',
196 | modalTitle: 'Options',
197 | copy: 'Copy',
198 | redo: 'Redo',
199 | },
200 | }
201 |
--------------------------------------------------------------------------------
/src/store/index.ts:
--------------------------------------------------------------------------------
1 | import { createStore } from 'vuex'
2 | import { v4 as uuidv4 } from 'uuid'
3 | import { getItemConfig, initialFormState } from '../const/const'
4 |
5 | export interface formItemType {
6 | value: string
7 | id: string
8 | formItemConfig: {
9 | [key: string]: any
10 | }
11 | }
12 |
13 | export interface State {
14 | formItemArray: formItemType[]
15 | selectedFormItem: string
16 | selectedFormItemType: string
17 | autoAddImport: boolean
18 | confirmAndCancelBtn: boolean
19 | formConfig: {
20 | labelPlacement: 'left' | 'top'
21 | labelWidth: string
22 | labelAlign: 'left' | 'right'
23 | size: 'small' | 'medium' | 'large'
24 | inline: boolean
25 | showFeedback: boolean
26 | showLabel: boolean
27 | showRequireMark: boolean
28 | requireMarkPlacement: 'left' | 'right'
29 | model: string | undefined
30 | }
31 | tabsValue: 'form' | 'formItem'
32 | local: string
33 | }
34 |
35 | export const store = createStore({
36 | strict: true,
37 | state() {
38 | return {
39 | formItemArray: [],
40 | autoAddImport: false,
41 | confirmAndCancelBtn: false,
42 | formConfig: initialFormState,
43 | selectedFormItem: '',
44 | selectedFormItemType: '',
45 | tabsValue: 'formItem',
46 | local: 'zh',
47 | }
48 | },
49 | mutations: {
50 | add(state: State, payload: Omit): void {
51 | const id = uuidv4()
52 | state.formItemArray.push({
53 | id,
54 | formItemConfig: getItemConfig(payload.value),
55 | ...payload,
56 | })
57 | window.$message.success(state.local === 'zh' ? '操作成功' : 'Success')
58 | },
59 | addAndSelect(state: State, payload: Omit): void {
60 | const id = uuidv4()
61 | state.formItemArray.push({
62 | id,
63 | formItemConfig: getItemConfig(payload.value),
64 | ...payload,
65 | })
66 | store.commit('changeSelectedFormItem', id)
67 | window.$message.success(state.local === 'zh' ? '操作成功' : 'Success')
68 | },
69 | remove(state: State, payload: string): void {
70 | state.formItemArray.splice(
71 | state.formItemArray.findIndex(item => item.id === payload),
72 | 1,
73 | )
74 | window.$message.success(state.local === 'zh' ? '操作成功' : 'Success')
75 | },
76 | clear(state: State): void {
77 | state.formItemArray = []
78 | },
79 | copy(state: State, payload: string): void {
80 | const index = state.formItemArray.findIndex(item => item.id === payload)
81 | if (index !== -1) {
82 | const newItem = { ...state.formItemArray[index] }
83 | newItem.id = uuidv4()
84 | state.formItemArray.splice(index + 1, 0, newItem)
85 | }
86 | window.$message.success(state.local === 'zh' ? '操作成功' : 'Success')
87 | },
88 | up(state: State, payload: string) {
89 | const index = state.formItemArray.findIndex(item => item.id === payload)
90 | if (index > 0) {
91 | const item = state.formItemArray[index]
92 | state.formItemArray.splice(index, 1)
93 | state.formItemArray.splice(index - 1, 0, item)
94 | }
95 | else {
96 | window.$message.warning(state.local === 'zh' ? '已经是第一个了' : 'Already the first')
97 | }
98 | },
99 | down(state: State, payload: string) {
100 | const index = state.formItemArray.findIndex(item => item.id === payload)
101 | if (index < state.formItemArray.length - 1) {
102 | const item = state.formItemArray[index]
103 | state.formItemArray.splice(index, 1)
104 | state.formItemArray.splice(index + 1, 0, item)
105 | }
106 | else {
107 | window.$message.warning(state.local === 'zh' ? '已经是最后一个了' : 'Already the last')
108 | }
109 | },
110 | exchange(state: State, payload: { id1: string; id2: string }) {
111 | const index1 = state.formItemArray.findIndex(item => item.id === payload.id1)
112 | const index2 = state.formItemArray.findIndex(item => item.id === payload.id2)
113 | if (index1 !== -1 && index2 !== -1) {
114 | const item1 = state.formItemArray[index1]
115 | const item2 = state.formItemArray[index2]
116 | state.formItemArray.splice(index1, 1, item2)
117 | state.formItemArray.splice(index2, 1, item1)
118 | }
119 | },
120 | insertDrop(state: State, payload: { from: number; to: number }) {
121 | if (payload.from !== -1 && payload.to !== -1) {
122 | if (payload.from < payload.to) {
123 | const item1 = state.formItemArray[payload.from]
124 | state.formItemArray.splice(payload.from, 1)
125 | state.formItemArray.splice(payload.to, 0, item1)
126 | }
127 | if (payload.from > payload.to) {
128 | const item1 = state.formItemArray[payload.from]
129 | state.formItemArray.splice(payload.from, 1)
130 | state.formItemArray.splice(payload.to, 0, item1)
131 | }
132 |
133 | // dom diff
134 | state.formItemArray = state.formItemArray.map((item) => {
135 | item.id = uuidv4()
136 | return item
137 | })
138 | }
139 | },
140 | changeAutoAddImport(state: State, payload: boolean) {
141 | state.autoAddImport = payload
142 | window.$message.success(state.local === 'zh' ? '操作成功' : 'Success')
143 | },
144 | changeFormConfig(state: State, payload: Partial) {
145 | state.formConfig = {
146 | ...state.formConfig,
147 | ...payload,
148 | }
149 | window.$message.success(state.local === 'zh' ? '操作成功' : 'Success')
150 | },
151 | changeSelectedFormItem(state: State, payload: string) {
152 | state.selectedFormItem = payload
153 | const index = state.formItemArray.findIndex(item => item.id === payload)
154 | if (index !== -1)
155 | state.selectedFormItemType = state.formItemArray[index].value
156 | },
157 | changeSelectedFormItemConfig(state: State, payload: formItemType['formItemConfig']) {
158 | const index = state.formItemArray.findIndex(item => item.id === state.selectedFormItem)
159 | if (index !== -1) {
160 | state.formItemArray[index].formItemConfig = {
161 | ...state.formItemArray[index].formItemConfig,
162 | ...payload,
163 | }
164 | }
165 | else {
166 | window.$message.warning(
167 | state.local === 'zh' ? '请先选择一个表单项' : 'Please select a form item',
168 | )
169 | }
170 | },
171 | changeTabsValue(state: State, payload: State['tabsValue']) {
172 | state.tabsValue = payload
173 | },
174 | changeConfirmAndCancelBtn(state: State, payload: boolean) {
175 | state.confirmAndCancelBtn = payload
176 | },
177 | changeLocal(state: State, payload: State['local']) {
178 | state.local = payload
179 | },
180 | },
181 | getters: {
182 | formItemArrayLength(state) {
183 | return state.formItemArray.length
184 | },
185 | formItemConfig(state) {
186 | return state.formItemArray.find(item => item.id === state.selectedFormItem)?.formItemConfig
187 | },
188 | selectedFormItem(state) {
189 | const index = state.formItemArray.findIndex(item => item.id === state.selectedFormItem)
190 | if (index !== -1)
191 | return state.formItemArray[index]
192 |
193 | return null
194 | },
195 | },
196 | })
197 |
--------------------------------------------------------------------------------
/src/components/RightSider/components/FormItemConfig/UploadFormItemConfig.vue:
--------------------------------------------------------------------------------
1 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
72 |
73 |
74 |
79 |
80 |
81 | -
82 |
83 |
84 |
89 |
90 |
91 |
92 |
93 |
94 |
100 |
101 |
102 |
107 |
108 |
109 | -
110 |
111 |
112 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 | {{ $t('yes') }}
126 |
127 |
128 | {{ $t('no') }}
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 | text
138 |
139 |
140 | image
141 |
142 |
143 | image-card
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 | {{ $t('yes') }}
159 |
160 |
161 | {{ $t('no') }}
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 | {{ $t('yes') }}
174 |
175 |
176 | {{ $t('no') }}
177 |
178 |
179 |
180 |
181 |
182 |
183 |
184 |
185 | {{ $t('yes') }}
186 |
187 |
188 | {{ $t('no') }}
189 |
190 |
191 |
192 |
193 |
194 |
195 |
196 |
197 | {{ $t('yes') }}
198 |
199 |
200 | {{ $t('no') }}
201 |
202 |
203 |
204 |
205 |
206 |
207 |
208 |
209 | {{ $t('yes') }}
210 |
211 |
212 | {{ $t('no') }}
213 |
214 |
215 |
216 |
217 |
218 |
219 |
220 |
221 | {{ $t('yes') }}
222 |
223 |
224 | {{ $t('no') }}
225 |
226 |
227 |
228 |
229 |
230 |
231 |
232 |
233 | {{ $t('yes') }}
234 |
235 |
236 | {{ $t('no') }}
237 |
238 |
239 |
240 |
241 |
242 | {{ $t('apply') }}
243 |
244 |
245 |
246 |
247 |
248 |
--------------------------------------------------------------------------------
/src/components/Content/Content.vue:
--------------------------------------------------------------------------------
1 |
35 |
36 |
37 |
38 | {{ $t('preview') }}
39 |
40 |
41 |
42 |
43 | {{ $t('add') }}
44 |
45 |
46 |
47 |
58 |
59 |
67 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
95 |
104 |
105 |
106 |
116 | {{ item2.label }}
117 |
118 |
119 |
120 |
126 |
141 |
152 |
157 |
166 |
183 |
200 | 上传文件
201 |
202 |
209 |
214 |
215 |
221 |
222 |
223 |
235 |
241 | {{ item.formItemConfig.name }}
242 |
243 |
244 |
245 |
246 |
247 |
248 |
249 |
250 |
258 |
--------------------------------------------------------------------------------
/lib/components/proForm/index.tsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable react/no-string-refs */
2 | import type { ComputedRef, PropType } from 'vue'
3 | // eslint-disable-next-line @typescript-eslint/no-unused-vars
4 | import { Fragment, computed, defineComponent, h, ref, watchEffect } from 'vue'
5 | import type {
6 | CheckboxGroupProps,
7 | FormInst,
8 | FormProps,
9 | RadioGroupProps,
10 | } from 'naive-ui'
11 | import {
12 | NButton,
13 | NCheckbox,
14 | NCheckboxGroup,
15 | NColorPicker,
16 | NDatePicker,
17 | NDivider,
18 | NForm,
19 | NFormItem,
20 | NIcon,
21 | NInput,
22 | NInputNumber,
23 | NRadio,
24 | NRadioGroup,
25 | NRate,
26 | NSelect,
27 | NSlider,
28 | NSpace,
29 | NSwitch,
30 | NTimePicker,
31 | NTooltip,
32 | NUpload,
33 | } from 'naive-ui'
34 | import type { FormValidateCallback } from 'naive-ui/es/form/src/interface'
35 | import type { FileInfo } from 'naive-ui/es/upload/src/interface'
36 | import { QuestionCircle48Regular } from '@vicons/fluent'
37 | import type { ProFormItem } from './types/props'
38 |
39 | const ProFormProps = {
40 | formProps: Object as PropType>,
41 | formItems: Array as PropType,
42 | resetButton: Boolean,
43 | validateButton: Boolean,
44 | submitButton: {
45 | type: Boolean,
46 | default: true,
47 | },
48 | onReset: Function as PropType<() => void>,
49 | onFinish: Function as PropType<() => void>,
50 | onError: Function as PropType,
51 | onValidate: Function as PropType<() => void>,
52 | title: String,
53 | isKeyPressSubmit: Boolean,
54 | }
55 |
56 | export default defineComponent({
57 | name: 'ProForm',
58 | props: ProFormProps,
59 | setup(props) {
60 | const getObj = (data: ProFormItem[] | undefined) => {
61 | if (!data)
62 | return {}
63 |
64 | const obj: Record = {}
65 |
66 | data.forEach((item) => {
67 | if (item.type === 'divider')
68 | return
69 | obj[item.key] = null
70 | })
71 |
72 | return obj
73 | }
74 |
75 | const modalData = ref>(getObj(props.formItems))
76 |
77 | const formRef = ref(null)
78 |
79 | const handleValidateClick = () => {
80 | formRef.value?.validate((errors) => {
81 | if (!errors)
82 | props?.onValidate && props.onValidate()
83 | else props?.onError && props.onError(errors)
84 | })
85 | }
86 |
87 | const handleResetClick = () => {
88 | for (const key in modalData.value) {
89 | if (Object.prototype.hasOwnProperty.call(modalData.value, key))
90 | modalData.value[key] = null
91 | }
92 | props?.onReset && props.onReset()
93 | }
94 |
95 | const handleSubmitClick = () => {
96 | formRef.value?.validate((errors) => {
97 | if (!errors) {
98 | props?.onFinish && props.onFinish()
99 | console.log(modalData.value)
100 | }
101 | else {
102 | props?.onError && props.onError(errors)
103 | }
104 | })
105 | }
106 |
107 | const handleInputUpdateValue = (
108 | val: string | number | null | (string | number)[] | Required[],
109 | key: string,
110 | ) => {
111 | modalData.value[key] = val
112 | }
113 |
114 | const handleRadioUpdateChecked = (
115 | val: boolean,
116 | key: string,
117 | flag: string | number,
118 | ) => {
119 | if (val)
120 | modalData.value[key] = flag
121 | }
122 |
123 | const keyDownHandler = (e: KeyboardEvent) => {
124 | if (e.key === 'Enter')
125 | handleSubmitClick()
126 | }
127 |
128 | watchEffect(() => {
129 | if (props.isKeyPressSubmit)
130 | window.addEventListener('keydown', keyDownHandler)
131 | else window.removeEventListener('keydown', keyDownHandler)
132 | })
133 |
134 | const getNTooltipVnode = (item: ProFormItem) => {
135 | if (item.type === 'divider')
136 | return
137 | return item.tooltipConfig?.show
138 | ? (
139 |
140 | {{
141 | trigger: () => (
142 |
143 |
144 |
145 | ),
146 | default: () => item.tooltipConfig?.text,
147 | }}
148 |
149 | )
150 | : null
151 | }
152 |
153 | const getNFormItemVnode: (
154 | item: ProFormItem
155 | ) => JSX.Element | JSX.Element[] | undefined = (item) => {
156 | switch (item.type) {
157 | case 'input':
158 | return (
159 | {
163 | handleInputUpdateValue(value, item.key)
164 | }}
165 | />
166 | )
167 | case 'inputNumber':
168 | return (
169 | {
173 | handleInputUpdateValue(value, item.key)
174 | }}
175 | />
176 | )
177 |
178 | case 'radio':
179 | return item.valueEnum.length > 1
180 | ? (
181 | {
185 | handleInputUpdateValue(value, item.key)
186 | }}
187 | >
188 | {item.valueEnum.map(valueItem => (
189 |
190 | ))}
191 |
192 | )
193 | : (
194 | item.valueEnum.map(valueItem => (
195 | {
201 | handleRadioUpdateChecked(value, item.key, valueItem.value)
202 | }}
203 | />
204 | ))
205 | )
206 |
207 | case 'select':
208 | return (
209 | {
214 | handleInputUpdateValue(value, item.key)
215 | }}
216 | />
217 | )
218 |
219 | case 'rate':
220 | return (
221 | {
225 | handleInputUpdateValue(value, item.key)
226 | }}
227 | />
228 | )
229 |
230 | case 'switch':
231 | return (
232 | {
236 | handleInputUpdateValue(value, item.key)
237 | }}
238 | />
239 | )
240 |
241 | case 'timePicker':
242 | return (
243 | {
247 | handleInputUpdateValue(value, item.key)
248 | }}
249 | />
250 | )
251 |
252 | case 'datePicker':
253 | return (
254 | {
257 | handleInputUpdateValue(value, item.key)
258 | }}
259 | value={modalData.value[item.key]}
260 | />
261 | )
262 |
263 | case 'colorPicker':
264 | return (
265 | {
268 | handleInputUpdateValue(value, item.key)
269 | }}
270 | value={modalData.value[item.key]}
271 | />
272 | )
273 |
274 | case 'checkbox':
275 | return item.valueEnum.length > 1
276 | ? (
277 | )}
282 | onUpdateValue={(value) => {
283 | handleInputUpdateValue(value, item.key)
284 | }}
285 | value={modalData.value[item.key]}
286 | >
287 | {item.valueEnum.map(valueItem => (
288 |
289 | ))}
290 |
291 | )
292 | : (
293 | item.valueEnum.map(valueItem => (
294 | {
299 | handleInputUpdateValue(value, item.key)
300 | }}
301 | value={modalData.value[item.key]}
302 | />
303 | ))
304 | )
305 |
306 | case 'upload':
307 | return (
308 | {
312 | handleInputUpdateValue(value, item.key)
313 | }}
314 | >
315 | {item.buttonText}
316 |
317 | )
318 |
319 | case 'slider':
320 | return (
321 | {
323 | handleInputUpdateValue(value, item.key)
324 | }}
325 | value={modalData.value[item.key]}
326 | />
327 | )
328 |
329 | default:
330 | return undefined
331 | }
332 | }
333 |
334 | const Vnode: ComputedRef = computed(() => {
335 | const { formItems } = props
336 | return formItems?.map((item) => {
337 | if (item.type === 'divider') {
338 | return (
339 |
344 | {item.text}
345 |
346 | )
347 | }
348 | else {
349 | return (
350 |
356 | {getNFormItemVnode(item)}
357 | {getNTooltipVnode(item)}
358 |
359 | )
360 | }
361 | })
362 | })
363 |
364 | return {
365 | modalData,
366 | formRef,
367 | handleValidateClick,
368 | handleInputUpdateValue,
369 | handleRadioUpdateChecked,
370 | handleResetClick,
371 | handleSubmitClick,
372 | Vnode,
373 | }
374 | },
375 | render() {
376 | const {
377 | formProps,
378 | modalData,
379 | handleValidateClick,
380 | handleResetClick,
381 | handleSubmitClick,
382 | resetButton,
383 | validateButton,
384 | submitButton,
385 | title,
386 | Vnode,
387 | } = this
388 |
389 | return (
390 |
391 | {title ? {title} : null}
392 |
393 | {Vnode}
394 |
395 |
396 | {validateButton === true
397 | ? (
398 |
399 | 验证
400 |
401 | )
402 | : null}
403 | {resetButton === true
404 | ? (
405 |
406 | 重置
407 |
408 | )
409 | : null}
410 | {submitButton === true
411 | ? (
412 |
413 | 提交
414 |
415 | )
416 | : null}
417 |
418 |
419 | )
420 | },
421 | })
422 |
--------------------------------------------------------------------------------
/src/utils/index.ts:
--------------------------------------------------------------------------------
1 | import { PREFIX, UI_NAME } from '../const/const'
2 | import type { formItemType } from '../store'
3 | import { store } from '../store'
4 |
5 | interface bindConfig {
6 | name: string
7 | val: any
8 | }
9 |
10 | const typeToImport: Record = {
11 | 0: ['Input'],
12 | 1: ['InputNumber'],
13 | 2: ['Radio', 'RadioGroup', 'Space'],
14 | 3: ['Rate'],
15 | 4: ['Select'],
16 | 5: ['Slider'],
17 | 6: ['Switch'],
18 | 7: ['TimePicker'],
19 | 8: ['TreeSelect'],
20 | 9: ['Upload'],
21 | 10: ['ColorPicker'],
22 | 11: ['Checkbox', 'CheckboxGroup', 'Space'],
23 | 12: ['DatePicker'],
24 | 13: ['Divider'],
25 | }
26 |
27 | // bind
28 |
29 | const combineNameAndValue = (
30 | name: string,
31 | val: any,
32 | ): {
33 | name: string
34 | val: any
35 | } => {
36 | return {
37 | name,
38 | val,
39 | }
40 | }
41 |
42 | const bindBooleanAndNumberConfig = (config: bindConfig): string => {
43 | return `${
44 | config.val !== undefined
45 | ? `:${String(config.name)}="${String(config.val)}"`
46 | : ''
47 | }`
48 | }
49 |
50 | const bindStringConfig = (config: bindConfig): string => {
51 | return `${
52 | config.val !== undefined
53 | ? `${String(config.name)}="${String(config.val)}"`
54 | : ''
55 | }`
56 | }
57 |
58 | const bindValueConfig = (config: bindConfig): string => {
59 | return `${
60 | config.val !== undefined
61 | ? `v-model="${
62 | store.state.formConfig.model !== undefined
63 | ? `${store.state.formConfig.model}.`
64 | : ''
65 | }${String(config.val)}"`
66 | : ''
67 | }`
68 | }
69 |
70 | const bindFileListConfig = (config: bindConfig): string => {
71 | return `${
72 | config.val !== undefined ? `v-model:file-list="${String(config.val)}"` : ''
73 | }`
74 | }
75 |
76 | const replaceOptions = (str: string): string => {
77 | return str.replace(/"label"/g, 'label').replace(/"value"/g, 'value')
78 | }
79 |
80 | // formItemConfig
81 |
82 | const getInputFormItemContentConfig = (item: {
83 | [key: string]: any
84 | }): string => {
85 | const { name, clearable, maxlength, minlength, type, size } = item
86 | return `${bindValueConfig(
87 | combineNameAndValue('name', name),
88 | )} ${bindBooleanAndNumberConfig(
89 | combineNameAndValue('clearable', clearable),
90 | )} ${bindBooleanAndNumberConfig(
91 | combineNameAndValue('maxlength', maxlength),
92 | )} ${bindBooleanAndNumberConfig(
93 | combineNameAndValue('minlength', minlength),
94 | )} ${bindStringConfig(combineNameAndValue('type', type))} ${bindStringConfig(
95 | combineNameAndValue('size', size),
96 | )}`
97 | }
98 |
99 | const getInputNumberFormItemContentConfig = (item: {
100 | [key: string]: any
101 | }): string => {
102 | const { name, clearable, max, min, size, step, showButton } = item
103 | return `${bindValueConfig(
104 | combineNameAndValue('name', name),
105 | )} ${bindBooleanAndNumberConfig(
106 | combineNameAndValue('clearable', clearable),
107 | )} ${bindBooleanAndNumberConfig(
108 | combineNameAndValue('max', max),
109 | )} ${bindBooleanAndNumberConfig(
110 | combineNameAndValue('min', min),
111 | )} ${bindBooleanAndNumberConfig(
112 | combineNameAndValue('step', step),
113 | )} ${bindStringConfig(
114 | combineNameAndValue('size', size),
115 | )} ${bindBooleanAndNumberConfig(
116 | combineNameAndValue('show-button', showButton),
117 | )}`
118 | }
119 |
120 | const getRadioFormItemContentConfig = (item: {
121 | [key: string]: any
122 | }): string => {
123 | const { name, size } = item
124 | return `${bindValueConfig(
125 | combineNameAndValue('name', name),
126 | )} ${bindStringConfig(combineNameAndValue('size', size))}`
127 | }
128 |
129 | const getRateFormItemContentConfig = (item: { [key: string]: any }): string => {
130 | const { name, size, count, allowHalf } = item
131 | return `${bindValueConfig(
132 | combineNameAndValue('name', name),
133 | )} ${bindStringConfig(
134 | combineNameAndValue('size', size),
135 | )} ${bindBooleanAndNumberConfig(
136 | combineNameAndValue('count', count),
137 | )} ${bindBooleanAndNumberConfig(
138 | combineNameAndValue('allow-half', allowHalf),
139 | )}`
140 | }
141 |
142 | const getSelectFormItemContentConfig = (item: {
143 | [key: string]: any
144 | }): string => {
145 | const { name, size, multiple, placeholder, clearable, options } = item
146 | return `${bindValueConfig(
147 | combineNameAndValue('name', name),
148 | )} ${bindStringConfig(combineNameAndValue('size', size))} ${bindStringConfig(
149 | combineNameAndValue('placeholder', placeholder),
150 | )} ${bindBooleanAndNumberConfig(
151 | combineNameAndValue('multiple', multiple),
152 | )} ${bindBooleanAndNumberConfig(
153 | combineNameAndValue('clearable', clearable),
154 | )} ${bindBooleanAndNumberConfig(
155 | combineNameAndValue('options', replaceOptions(JSON.stringify(options))),
156 | )}`
157 | }
158 |
159 | const getSliderFormItemContentConfig = (item: {
160 | [key: string]: any
161 | }): string => {
162 | const { name, max, min, step, range, reverse, vertical, tooltip } = item
163 | return `${bindValueConfig(
164 | combineNameAndValue('name', name),
165 | )} ${bindBooleanAndNumberConfig(
166 | combineNameAndValue('max', max),
167 | )} ${bindBooleanAndNumberConfig(
168 | combineNameAndValue('min', min),
169 | )} ${bindBooleanAndNumberConfig(
170 | combineNameAndValue('step', step),
171 | )} ${bindBooleanAndNumberConfig(
172 | combineNameAndValue('range', range),
173 | )} ${bindBooleanAndNumberConfig(
174 | combineNameAndValue('reverse', reverse),
175 | )} ${bindBooleanAndNumberConfig(
176 | combineNameAndValue('vertical', vertical),
177 | )} ${bindBooleanAndNumberConfig(combineNameAndValue('tooltip', tooltip))}`
178 | }
179 |
180 | const getSwitchFormItemContentConfig = (item: {
181 | [key: string]: any
182 | }): string => {
183 | const { name, round, size } = item
184 | return `${bindValueConfig(
185 | combineNameAndValue('name', name),
186 | )} ${bindBooleanAndNumberConfig(
187 | combineNameAndValue('round', round),
188 | )} ${bindStringConfig(combineNameAndValue('size', size))}`
189 | }
190 |
191 | const getTimePickerFormItemContentConfig = (item: {
192 | [key: string]: any
193 | }): string => {
194 | const { name, size, actions, clearable, format, placeholder, use12Hours }
195 | = item
196 | return `${bindValueConfig(
197 | combineNameAndValue('name', name),
198 | )} ${bindBooleanAndNumberConfig(
199 | combineNameAndValue('clearable', clearable),
200 | )} ${bindBooleanAndNumberConfig(
201 | combineNameAndValue('use-12-hours', use12Hours),
202 | )} ${bindBooleanAndNumberConfig(
203 | combineNameAndValue('actions', JSON.stringify(actions)),
204 | )} ${bindStringConfig(
205 | combineNameAndValue('format', format),
206 | )} ${bindStringConfig(
207 | combineNameAndValue('placeholder', placeholder),
208 | )} ${bindStringConfig(combineNameAndValue('size', size))}`
209 | }
210 |
211 | const getTreeSelectFormItemContentConfig = (item: {
212 | [key: string]: any
213 | }): string => {
214 | const { name, size, clearable } = item
215 | return `${bindValueConfig(
216 | combineNameAndValue('name', name),
217 | )} ${bindBooleanAndNumberConfig(
218 | combineNameAndValue('clearable', clearable),
219 | )} ${bindStringConfig(combineNameAndValue('size', size))}`
220 | }
221 |
222 | const getUploadFormItemContentConfig = (item: {
223 | [key: string]: any
224 | }): string => {
225 | const {
226 | name,
227 | accept,
228 | action,
229 | defaultUpload,
230 | data,
231 | headers,
232 | listType,
233 | max,
234 | method,
235 | multiple,
236 | fileName,
237 | withCredentials,
238 | showCancelButton,
239 | showDownloadButton,
240 | showRemoveButton,
241 | showRetryButton,
242 | showFileList,
243 | } = item
244 | const handledDate: Record = {}
245 | const handledHeaders: Record = {};
246 | (
247 | data as Array<{
248 | key: string
249 | value: string
250 | }>
251 | ).map((item) => {
252 | return (handledDate[item.key] = item.value)
253 | });
254 | (
255 | headers as Array<{
256 | key: string
257 | value: string
258 | }>
259 | ).map((item) => {
260 | return (handledHeaders[item.key] = item.value)
261 | })
262 | return `${bindFileListConfig(
263 | combineNameAndValue('name', name),
264 | )} ${bindBooleanAndNumberConfig(
265 | combineNameAndValue('default-upload', defaultUpload),
266 | )} ${bindBooleanAndNumberConfig(
267 | combineNameAndValue('data', JSON.stringify(handledDate)),
268 | )} ${bindBooleanAndNumberConfig(
269 | combineNameAndValue('headers', JSON.stringify(handledHeaders)),
270 | )} ${bindBooleanAndNumberConfig(
271 | combineNameAndValue('show-cancel-button', showCancelButton),
272 | )} ${bindBooleanAndNumberConfig(
273 | combineNameAndValue('show-download-button', showDownloadButton),
274 | )} ${bindBooleanAndNumberConfig(
275 | combineNameAndValue('show-remove-button', showRemoveButton),
276 | )} ${bindBooleanAndNumberConfig(
277 | combineNameAndValue('show-retry-button', showRetryButton),
278 | )} ${bindBooleanAndNumberConfig(
279 | combineNameAndValue('show-file-list', showFileList),
280 | )} ${bindStringConfig(
281 | combineNameAndValue('accept', accept),
282 | )} ${bindStringConfig(
283 | combineNameAndValue('action', action),
284 | )} ${bindStringConfig(
285 | combineNameAndValue('list-type', listType),
286 | )} ${bindBooleanAndNumberConfig(
287 | combineNameAndValue('max', max),
288 | )} ${bindStringConfig(
289 | combineNameAndValue('method', method),
290 | )} ${bindBooleanAndNumberConfig(
291 | combineNameAndValue('multiple', multiple),
292 | )} ${bindStringConfig(
293 | combineNameAndValue('name', fileName),
294 | )} ${bindBooleanAndNumberConfig(
295 | combineNameAndValue('with-credentials', withCredentials),
296 | )}`
297 | }
298 |
299 | const getCheckBoxFormItemContentConfig = (item: {
300 | [key: string]: any
301 | }): string => {
302 | const { name, max, min } = item
303 | return `${bindValueConfig(
304 | combineNameAndValue('name', name),
305 | )} ${bindBooleanAndNumberConfig(
306 | combineNameAndValue('max', max),
307 | )} ${bindBooleanAndNumberConfig(combineNameAndValue('min', min))}`
308 | }
309 |
310 | const getColorPickerFormItemContentConfig = (item: {
311 | [key: string]: any
312 | }): string => {
313 | const { name, size, modes, showAlpha, actions } = item
314 | return `${bindValueConfig(
315 | combineNameAndValue('name', name),
316 | )} ${bindBooleanAndNumberConfig(
317 | combineNameAndValue('modes', JSON.stringify(modes)),
318 | )} ${bindBooleanAndNumberConfig(
319 | combineNameAndValue('showAlpha', showAlpha),
320 | )} ${bindBooleanAndNumberConfig(
321 | combineNameAndValue('actions', JSON.stringify(actions)),
322 | )} ${bindStringConfig(combineNameAndValue('size', size))}`
323 | }
324 |
325 | const getDividerContentConfig = (item: { [key: string]: any }): string => {
326 | const { dashed, vertical, titlePlacement } = item
327 | return `${bindBooleanAndNumberConfig(
328 | combineNameAndValue('dashed', dashed),
329 | )} ${bindBooleanAndNumberConfig(
330 | combineNameAndValue('vertical', vertical),
331 | )} ${bindStringConfig(
332 | combineNameAndValue('title-placement', titlePlacement),
333 | )}`
334 | }
335 |
336 | const getDatePickerFormItemContentConfig = (item: {
337 | [key: string]: any
338 | }): string => {
339 | const {
340 | name,
341 | clearable,
342 | format,
343 | size,
344 | actions,
345 | placeholder,
346 | type,
347 | startPlaceholder,
348 | endPlaceholder,
349 | separator,
350 | } = item
351 | return `${bindValueConfig(
352 | combineNameAndValue('name', name),
353 | )} ${bindBooleanAndNumberConfig(
354 | combineNameAndValue('actions', JSON.stringify(actions)),
355 | )} ${bindBooleanAndNumberConfig(
356 | combineNameAndValue('clearable', clearable),
357 | )} ${bindStringConfig(
358 | combineNameAndValue('format', format),
359 | )} ${bindStringConfig(combineNameAndValue('size', size))} ${bindStringConfig(
360 | combineNameAndValue('placeholder', placeholder),
361 | )} ${bindStringConfig(combineNameAndValue('type', type))} ${bindStringConfig(
362 | combineNameAndValue('start-placeholder', startPlaceholder),
363 | )} ${bindStringConfig(
364 | combineNameAndValue('end-placeholder', endPlaceholder),
365 | )} ${bindStringConfig(combineNameAndValue('separator', separator))}`
366 | }
367 |
368 | const getFormItemConfig = (item: formItemType): string => {
369 | return `label="${item.formItemConfig.label as string}" path="${
370 | item.formItemConfig.name as string
371 | }"`
372 | }
373 |
374 | const getFormItemContentConfig = (
375 | item: { [key: string]: any },
376 | type: string,
377 | ): string => {
378 | if (Object.keys(item).length === 0)
379 | return ''
380 |
381 | switch (type) {
382 | case '0':
383 | return getInputFormItemContentConfig(item)
384 | case '1':
385 | return getInputNumberFormItemContentConfig(item)
386 | case '2':
387 | return getRadioFormItemContentConfig(item)
388 | case '3':
389 | return getRateFormItemContentConfig(item)
390 | case '4':
391 | return getSelectFormItemContentConfig(item)
392 | case '5':
393 | return getSliderFormItemContentConfig(item)
394 | case '6':
395 | return getSwitchFormItemContentConfig(item)
396 | case '7':
397 | return getTimePickerFormItemContentConfig(item)
398 | case '8':
399 | return getTreeSelectFormItemContentConfig(item)
400 | case '9':
401 | return getUploadFormItemContentConfig(item)
402 | case '10':
403 | return getColorPickerFormItemContentConfig(item)
404 | case '11':
405 | return getCheckBoxFormItemContentConfig(item)
406 | case '12':
407 | return getDatePickerFormItemContentConfig(item)
408 | case '13':
409 | return getDividerContentConfig(item)
410 | default:
411 | return ''
412 | }
413 | }
414 |
415 | const getTypeToFormItem = (item: formItemType): string => {
416 | const type = item.value
417 | const formItemConfig = getFormItemConfig(item)
418 | const formItemContentConfig = getFormItemContentConfig(
419 | item.formItemConfig,
420 | type,
421 | )
422 | switch (type) {
423 | case '0':
424 | return `
425 | <${PREFIX}-form-item ${formItemConfig}>
426 | <${PREFIX}-input ${formItemContentConfig}/>
427 | ${PREFIX}-form-item>`
428 | case '1':
429 | return `
430 | <${PREFIX}-form-item ${formItemConfig}>
431 | <${PREFIX}-input-number ${formItemContentConfig}/>
432 | ${PREFIX}-form-item>`
433 | case '2':
434 | return `
435 | <${PREFIX}-form-item ${formItemConfig}>
436 | <${PREFIX}-radio-group ${formItemContentConfig}>
437 | <${PREFIX}-space>
438 | ${
439 | item.formItemConfig.options !== undefined
440 | ? (
441 | item.formItemConfig.options as Array<{
442 | label: string
443 | value: string
444 | }>
445 | )
446 | .map(
447 | (option: { value: string; label: string }) =>
448 | `<${PREFIX}-radio value="${option.value}">${option.label}${PREFIX}-radio>`,
449 | )
450 | .join('')
451 | : ''
452 | }
453 | ${PREFIX}-space>
454 | ${PREFIX}-radio-group>
455 | ${PREFIX}-form-item>`
456 | case '3':
457 | return `
458 | <${PREFIX}-form-item ${formItemConfig}>
459 | <${PREFIX}-rate ${formItemContentConfig}/>
460 | ${PREFIX}-form-item>`
461 | case '4':
462 | return `
463 | <${PREFIX}-form-item ${formItemConfig}>
464 | <${PREFIX}-select ${formItemContentConfig} />
465 | ${PREFIX}-form-item>`
466 | case '5':
467 | return `
468 | <${PREFIX}-form-item ${formItemConfig}>
469 | <${PREFIX}-slider ${formItemContentConfig}/>
470 | ${PREFIX}-form-item>`
471 | case '6':
472 | return `
473 | <${PREFIX}-form-item ${formItemConfig}>
474 | <${PREFIX}-switch ${formItemContentConfig}/>
475 | ${PREFIX}-form-item>`
476 | case '7':
477 | return `
478 | <${PREFIX}-form-item ${formItemConfig}>
479 | <${PREFIX}-time-picker ${formItemContentConfig}/>
480 | ${PREFIX}-form-item>`
481 | case '8':
482 | return `
483 | <${PREFIX}-form-item ${formItemConfig}>
484 | <${PREFIX}-tree-select ${formItemContentConfig}/>
485 | ${PREFIX}-form-item>`
486 | case '9':
487 | return `
488 | <${PREFIX}-form-item ${formItemConfig}>
489 | <${PREFIX}-upload ${formItemContentConfig}>
490 | <${PREFIX}-button>上传文件${PREFIX}-button>
491 | ${PREFIX}-upload>
492 | ${PREFIX}-form-item>`
493 | case '10':
494 | return `
495 | <${PREFIX}-form-item ${formItemConfig}>
496 | <${PREFIX}-color-picker ${formItemContentConfig}/>
497 | ${PREFIX}-form-item>`
498 | case '11':
499 | return `
500 | <${PREFIX}-form-item ${formItemConfig}>
501 | <${PREFIX}-checkbox-group ${formItemContentConfig}>
502 | <${PREFIX}-space item-style="display: flex;">
503 | ${
504 | item.formItemConfig.options !== undefined
505 | ? (
506 | item.formItemConfig.options as Array<{
507 | label: string
508 | value: string
509 | }>
510 | )
511 | .map(
512 | (option: { value: string; label: string }) =>
513 | `<${PREFIX}-checkbox value="${option.value}" label="${option.label}"/>`,
514 | )
515 | .join('')
516 | : ''
517 | }
518 | ${PREFIX}-space>
519 | ${PREFIX}-checkbox-group>
520 | ${PREFIX}-form-item>`
521 | case '13':
522 | return `
523 | <${PREFIX}-divider ${formItemContentConfig}>${
524 | (item.formItemConfig.name as string) ?? ''
525 | }${PREFIX}-divider>`
526 | case '12':
527 | return `
528 | <${PREFIX}-form-item ${formItemConfig}>
529 | <${PREFIX}-date-picker ${formItemContentConfig}/>
530 | ${PREFIX}-form-item>`
531 | default:
532 | return ''
533 | }
534 | }
535 |
536 | const getFormItemImport = (data: formItemType[]): string => {
537 | const prefix = PREFIX.toUpperCase()
538 | const { confirmAndCancelBtn } = store.state
539 | const array: string[] = []
540 | data.forEach((item) => {
541 | return typeToImport[item.value].forEach((importItem) => {
542 | array.push(`${prefix}${importItem} ,`)
543 | })
544 | })
545 | // button
546 | if (confirmAndCancelBtn) {
547 | ['Button', 'Space'].forEach((item) => {
548 | array.push(`${prefix}${item} ,`)
549 | })
550 | }
551 | return `${Array.from(new Set(array)).join('')}`
552 | }
553 |
554 | // formConfig
555 |
556 | const getFormConfig = (): string => {
557 | const {
558 | size,
559 | inline,
560 | labelWidth,
561 | labelAlign,
562 | labelPlacement,
563 | showFeedback,
564 | showLabel,
565 | showRequireMark,
566 | requireMarkPlacement,
567 | model,
568 | } = store.state.formConfig
569 |
570 | return `${bindBooleanAndNumberConfig(
571 | combineNameAndValue('model', model),
572 | )} ${bindStringConfig(
573 | combineNameAndValue('size', size),
574 | )} ${bindBooleanAndNumberConfig(
575 | combineNameAndValue('inline', inline),
576 | )} ${bindBooleanAndNumberConfig(
577 | combineNameAndValue('label-width', labelWidth),
578 | )} ${bindStringConfig(
579 | combineNameAndValue('label-align', labelAlign),
580 | )} ${bindStringConfig(
581 | combineNameAndValue('label-placement', labelPlacement),
582 | )} ${bindBooleanAndNumberConfig(
583 | combineNameAndValue('show-feedback', showFeedback),
584 | )} ${bindBooleanAndNumberConfig(
585 | combineNameAndValue('show-label', showLabel),
586 | )} ${bindBooleanAndNumberConfig(
587 | combineNameAndValue('show-require-mark', showRequireMark),
588 | )} ${bindBooleanAndNumberConfig(
589 | combineNameAndValue('rules', 'rules'),
590 | )} ${bindStringConfig(
591 | combineNameAndValue('require-mark-placement', requireMarkPlacement),
592 | )}`
593 | }
594 |
595 | // import
596 |
597 | const getImport = (data: formItemType[]): string => {
598 | const prefix = PREFIX.toUpperCase()
599 | if (store.state.autoAddImport) {
600 | const importStr = `
601 |
606 | `
607 | return importStr
608 | }
609 |
610 | return ''
611 | }
612 |
613 | // confirmAndCancelButton
614 |
615 | const getConfirmAndCancelButton = (): string => {
616 | const { confirmAndCancelBtn } = store.state
617 | if (confirmAndCancelBtn) {
618 | return `
619 | <${PREFIX}-form-item>
620 | <${PREFIX}-space justify="center">
621 | <${PREFIX}-button @click="handleValidateClick">确认${PREFIX}-button>
622 | <${PREFIX}-button>取消${PREFIX}-button>
623 | ${PREFIX}-space>
624 | ${PREFIX}-form-item>`
625 | }
626 | return ''
627 | }
628 |
629 | // rules
630 |
631 | const getRulesObject = (data: formItemType[]): string => {
632 | const rulesArray: string[] = []
633 |
634 | for (let i = 0; i < data.length; i++) {
635 | const item = data[i]
636 | if (item.formItemConfig.name === undefined)
637 | continue
638 | const rules = data[i].formItemConfig.rules as string[]
639 | const itemRulesArray: string[] = []
640 |
641 | for (let i = 0; i < rules.length; i++) {
642 | const element = rules[i]
643 | switch (element) {
644 | case '0':
645 | itemRulesArray.push(
646 | `{ required: true, message: '请输入${
647 | item.formItemConfig.label as string
648 | }', trigger: 'blur' },`,
649 | )
650 | break
651 | case '1':
652 | itemRulesArray.push(
653 | `{ validator: (rule,value)=>{
654 | let reg = /^(13[0-9]|14[01456879]|15[0-35-9]|16[2567]|17[0-8]|18[0-9]|19[0-35-9])\d{8}$/;
655 | if(!reg.test(value)){
656 | return new Error('请输入正确的${
657 | item.formItemConfig.label as string
658 | }');
659 | }
660 | return true;
661 | }, message: '请输入正确的${
662 | item.formItemConfig.label as string
663 | }', trigger: 'blur' },`,
664 | )
665 | break
666 | case '2':
667 | itemRulesArray.push(
668 | `{ validator: (rule,value)=>{
669 | let reg = /(^\d{15}$)|(^\d{18}$)|(^\d{17}(\d|X|x)$)/;
670 | if(!reg.test(value)){
671 | return new Error('请输入正确的${
672 | item.formItemConfig.label as string
673 | }');
674 | }
675 | return true;
676 | }, message: '请输入正确的${
677 | item.formItemConfig.label as string
678 | }', trigger: 'blur' },`,
679 | )
680 | break
681 | default:
682 | break
683 | }
684 | }
685 |
686 | rulesArray.push(`${item.formItemConfig.name as string}: [
687 | ${itemRulesArray.join('')}
688 | ],`)
689 | }
690 |
691 | if (rulesArray.length === 0)
692 | return ''
693 |
694 | return `
695 | const rules = {
696 | ${rulesArray.join('')}
697 | }
698 | `
699 | }
700 |
701 | // entry
702 |
703 | export const generateCode = (data: formItemType[]): string => {
704 | const Code = `
705 |
706 | <${PREFIX}-form ${getFormConfig()}>
707 | ${data.map(item => getTypeToFormItem(item)).join('')}
708 | ${getConfirmAndCancelButton()}
709 | ${PREFIX}-form>
710 |
711 |
715 | `
716 |
717 | return Code
718 | }
719 |
720 | // normal
721 |
722 | export const copy = (value: string): void => {
723 | const textarea = document.createElement('textarea')
724 | textarea.value = value
725 | document.body.appendChild(textarea)
726 | textarea.select()
727 | // 暂时没有可用的替代方法。
728 | // 确切的说这个 API 本来也不是标准 API,而是一个 IE 的私有 API,在 IE9 时被引入,后续的若干年里陆续被 Chrome / Firefix / Opera 等浏览器也做了兼容支持,但始终没有形成标准。
729 | // 这个 API 被废弃的主要原因第一个就是安全问题,在用户未经授权的情况下就可以执行一些敏感操作,这就很恐怖了;第二个问题是因为这是一个同步方法,而且操作了 DOM 对象,会阻塞页面渲染和脚本执行,因当初还没 Promise,所以没设计成异步,挖坑了。新设计的 API 肯定是要解决这两个问题。
730 | // 不过 W3C 也正在拟草案,大概率以后会引入一个叫 Clipboard 的类型(Chrome 66.0 开始已经有这个类型了,不过还不能用,相关 API 仅存在于文档中),用来处理跟剪贴版相关的操作,不过之后肯定会是像现在获取地理位置啊、麦克风啊什么的,浏览器先会弹出一个对话框让用户授权,你才能读写剪贴板了。
731 | document.execCommand('copy')
732 | document.body.removeChild(textarea)
733 | }
734 |
735 | export function copyPropertyValue(
736 | obj: T,
737 | target: T,
738 | key: K,
739 | ): void {
740 | obj[key] = target[key]
741 | }
742 |
743 | export function getParentElement(
744 | element: HTMLElement | null,
745 | ): HTMLElement | null {
746 | if (element === null)
747 | return null
748 |
749 | let currElement: HTMLElement | null = element
750 | while (currElement != null) {
751 | if (currElement.dataset.drag === 'doom')
752 | return currElement
753 |
754 | currElement = currElement.parentElement
755 | }
756 | return null
757 | }
758 |
--------------------------------------------------------------------------------