赵冬冬 пре 3 година
комит
0539485b2c
100 измењених фајлова са 8586 додато и 0 уклоњено
  1. 45 0
      .all-contributorsrc
  2. 2 0
      .browserslistrc
  3. 2 0
      .env.lib
  4. 22 0
      .eslintrc.js
  5. 21 0
      .gitignore
  6. 6 0
      .grenrc.js
  7. 1 0
      .npmignore
  8. 175 0
      CHANGELOG.md
  9. 21 0
      LICENSE
  10. 158 0
      README.md
  11. 4 0
      babel.config.js
  12. 104 0
      package.json
  13. 1 0
      prettier.config.js
  14. BIN
      public/favicon.ico
  15. 21 0
      public/index.html
  16. 8 0
      release.sh
  17. 49 0
      src/App.vue
  18. 74 0
      src/components/app-header.vue
  19. 182 0
      src/components/app-main/components/components/batchDialog.vue
  20. 56 0
      src/components/app-main/components/components/exportDialog.vue
  21. 106 0
      src/components/app-main/components/components/htmlDialog.vue
  22. 177 0
      src/components/app-main/components/components/importDialog.vue
  23. 61 0
      src/components/app-main/components/components/previewDialog.vue
  24. 121 0
      src/components/app-main/components/components/remoteConfig.vue
  25. 29 0
      src/components/app-main/components/components/tpl.ejs
  26. 223 0
      src/components/app-main/components/main-center.vue
  27. 209 0
      src/components/app-main/components/main-header.vue
  28. 110 0
      src/components/app-main/components/main-left-components.vue
  29. 464 0
      src/components/app-main/components/main-left-projects.vue
  30. 50 0
      src/components/app-main/components/main-right/components/components/attrs-header.vue
  31. 213 0
      src/components/app-main/components/main-right/components/components/form-item-rules.vue
  32. 35 0
      src/components/app-main/components/main-right/components/components/searchMixin.ts
  33. 187 0
      src/components/app-main/components/main-right/components/form-config.vue
  34. 59 0
      src/components/app-main/components/main-right/components/form-item-attrs.vue
  35. 180 0
      src/components/app-main/components/main-right/components/form-item-config.vue
  36. 73 0
      src/components/app-main/components/main-right/index.vue
  37. 159 0
      src/components/app-main/index.vue
  38. 12 0
      src/config/README.md
  39. 95 0
      src/config/comps/autocomplete.ts
  40. 64 0
      src/config/comps/bmap.ts
  41. 74 0
      src/config/comps/button.ts
  42. 359 0
      src/config/comps/cascader-panel.ts
  43. 411 0
      src/config/comps/cascader.ts
  44. 68 0
      src/config/comps/checkbox-button.ts
  45. 61 0
      src/config/comps/checkbox.ts
  46. 25 0
      src/config/comps/codemirror.ts
  47. 46 0
      src/config/comps/color.ts
  48. 69 0
      src/config/comps/date.ts
  49. 102 0
      src/config/comps/daterange.ts
  50. 63 0
      src/config/comps/dates.ts
  51. 68 0
      src/config/comps/datetime.ts
  52. 101 0
      src/config/comps/datetimerange.ts
  53. 40 0
      src/config/comps/dynamic.ts
  54. 77 0
      src/config/comps/gallery.ts
  55. 123 0
      src/config/comps/image-uploader.ts
  56. 58 0
      src/config/comps/image.ts
  57. 120 0
      src/config/comps/input.ts
  58. 35 0
      src/config/comps/json-editor.ts
  59. 142 0
      src/config/comps/markdown-editor.ts
  60. 63 0
      src/config/comps/month.ts
  61. 96 0
      src/config/comps/monthrange.ts
  62. 73 0
      src/config/comps/number.ts
  63. 88 0
      src/config/comps/password.ts
  64. 70 0
      src/config/comps/quill-editor.ts
  65. 50 0
      src/config/comps/radio-button.ts
  66. 40 0
      src/config/comps/radio.ts
  67. 107 0
      src/config/comps/rate.ts
  68. 134 0
      src/config/comps/select.ts
  69. 107 0
      src/config/comps/slider.ts
  70. 60 0
      src/config/comps/switch.ts
  71. 138 0
      src/config/comps/table-editor.ts
  72. 87 0
      src/config/comps/tag.ts
  73. 28 0
      src/config/comps/text.ts
  74. 83 0
      src/config/comps/textarea.ts
  75. 59 0
      src/config/comps/time.ts
  76. 86 0
      src/config/comps/timerange.ts
  77. 80 0
      src/config/comps/transfer.ts
  78. 120 0
      src/config/comps/tree-select.ts
  79. 96 0
      src/config/comps/upload-file.ts
  80. 79 0
      src/config/comps/video-uploader.ts
  81. 63 0
      src/config/comps/week.ts
  82. 63 0
      src/config/comps/year.ts
  83. 43 0
      src/config/comps/yesno.ts
  84. 16 0
      src/config/index.ts
  85. 62 0
      src/extend/codemirror.ts
  86. 27 0
      src/extend/index.ts
  87. 63 0
      src/helpers/api.ts
  88. 243 0
      src/helpers/comps.ts
  89. 29 0
      src/helpers/remoteConfig.ts
  90. 95 0
      src/helpers/tool.ts
  91. 84 0
      src/helpers/utils.ts
  92. 55 0
      src/main.ts
  93. 13 0
      src/shims-tsx.d.ts
  94. 9 0
      src/shims-vue.d.ts
  95. 22 0
      src/store/formAttrDefault.ts
  96. 275 0
      src/store/index.ts
  97. 32 0
      src/store/listDefault.ts
  98. 11 0
      src/store/persistedstate.ts
  99. 10 0
      src/types/common.d.ts
  100. 6 0
      src/types/comp.ts

+ 45 - 0
.all-contributorsrc

@@ -0,0 +1,45 @@
+{
+  "files": [
+    "README.md"
+  ],
+  "imageSize": 100,
+  "commit": false,
+  "contributors": [
+    {
+      "login": "dream2023",
+      "name": "张超杰",
+      "avatar_url": "https://avatars0.githubusercontent.com/u/19297757?v=4",
+      "profile": "http://dream2023.github.io",
+      "contributions": [
+        "doc",
+        "code",
+        "ideas"
+      ]
+    },
+    {
+      "login": "Wisenl",
+      "name": "Wisen",
+      "avatar_url": "https://avatars0.githubusercontent.com/u/17942052?v=4",
+      "profile": "https://github.com/Wisenl",
+      "contributions": [
+        "code"
+      ]
+    },
+    {
+      "login": "IWANABETHATGUY",
+      "name": "IWANABETHATGUY",
+      "avatar_url": "https://avatars1.githubusercontent.com/u/17974631?v=4",
+      "profile": "https://github.com/IWANABETHATGUY",
+      "contributions": [
+        "code"
+      ]
+    }
+  ],
+  "contributorsPerLine": 7,
+  "projectName": "vue-ele-form-generator",
+  "projectOwner": "dream2023",
+  "repoType": "github",
+  "repoHost": "https://github.com",
+  "skipCi": true,
+  "commitConvention": "none"
+}

+ 2 - 0
.browserslistrc

@@ -0,0 +1,2 @@
+> 1%
+last 2 versions

+ 2 - 0
.env.lib

@@ -0,0 +1,2 @@
+VUE_APP_IS_LAB=true
+IS_LAB=true

+ 22 - 0
.eslintrc.js

@@ -0,0 +1,22 @@
+module.exports = {
+  root: true,
+  env: {
+    node: true
+  },
+  extends: [
+    "plugin:vue/essential",
+    "eslint:recommended",
+    "@vue/typescript/recommended",
+    "@vue/prettier",
+    "@vue/prettier/@typescript-eslint"
+  ],
+  parserOptions: {
+    ecmaVersion: 2020
+  },
+  rules: {
+    "no-eval": "off",
+    "@typescript-eslint/no-explicit-any": "off",
+    "no-console": process.env.NODE_ENV === "production" ? "error" : "off",
+    "no-debugger": process.env.NODE_ENV === "production" ? "error" : "off"
+  }
+};

+ 21 - 0
.gitignore

@@ -0,0 +1,21 @@
+.DS_Store
+node_modules
+/dist
+lib
+# local env files
+.env.local
+.env.*.local
+
+# Log files
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+
+# Editor directories and files
+.idea
+.vscode
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw?

+ 6 - 0
.grenrc.js

@@ -0,0 +1,6 @@
+module.exports = {
+  dataSource: "commits",
+  includeMessages: "commits",
+  ignoreCommitsWith: ["changelog", "release", "docs"],
+  changelogFilename: "CHANGELOG.md"
+};

+ 1 - 0
.npmignore

@@ -0,0 +1 @@
+src

+ 175 - 0
CHANGELOG.md

@@ -0,0 +1,175 @@
+# Changelog
+
+All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
+
+### [2.2.2](https://github.com/dream2023/vue-ele-form-generator/compare/v2.2.1...v2.2.2) (2020-04-17)
+
+
+### Bug Fixes
+
+* 修复导入数据问题 ([52abbc0](https://github.com/dream2023/vue-ele-form-generator/commit/52abbc0bce2cfde6124b28b798b0ad2ce54823de))
+
+### [2.2.1](https://github.com/dream2023/vue-ele-form-generator/compare/v2.2.0...v2.2.1) (2020-04-13)
+
+## [2.2.0](https://github.com/dream2023/vue-ele-form-generator/compare/v2.1.0...v2.2.0) (2020-03-25)
+
+
+### Features
+
+* 增加下载文件 ([1f8484b](https://github.com/dream2023/vue-ele-form-generator/commit/1f8484b2b9c7dee6a54d7970e1db568aab8aeee5))
+* 增加组件使用排序 ([8ad993f](https://github.com/dream2023/vue-ele-form-generator/commit/8ad993f51f89694552a41953d807ec56a47de75f))
+
+## [2.1.0](https://github.com/dream2023/vue-ele-form-generator/compare/v2.0.2...v2.1.0) (2020-03-17)
+
+
+### Features
+
+* fuzzySearch ([7447009](https://github.com/dream2023/vue-ele-form-generator/commit/7447009c62637f87e9ae8e9640fbed056470980b))
+* 右侧搜索 ([242d34e](https://github.com/dream2023/vue-ele-form-generator/commit/242d34edb53cec250906c24c3b54f07c64303716))
+* 完成多页面 & 保存到服务器 ([#26](https://github.com/dream2023/vue-ele-form-generator/issues/26)) ([4e22b26](https://github.com/dream2023/vue-ele-form-generator/commit/4e22b26f549e4ca793bc9dabafb05f6ebf975802))
+* 完成深度对象的搜索和设置label ([367acb8](https://github.com/dream2023/vue-ele-form-generator/commit/367acb847617a023f9e9f272497f7ecc448a0678))
+* 扩展新增函数 ([41a78b2](https://github.com/dream2023/vue-ele-form-generator/commit/41a78b2896f055a4e4b298e5d43676e839422cce))
+
+
+### Bug Fixes
+
+* 修复若干组件属性 ([c5e4765](https://github.com/dream2023/vue-ele-form-generator/commit/c5e47650e4f880f38c4f9d3faf73bcae307ab53f))
+* 修改store默认值 ([57c2af7](https://github.com/dream2023/vue-ele-form-generator/commit/57c2af7f4cf42989378b6fef4b41a4837c7817d9))
+* 升级依赖 & 解决BUG ([259a8d1](https://github.com/dream2023/vue-ele-form-generator/commit/259a8d1f85374cf83a7e0044dabb295f62eb2bf7))
+* 解决弹窗预览BUG ([c34dfc8](https://github.com/dream2023/vue-ele-form-generator/commit/c34dfc877bd0718e46759898668bd2a8f37d5046))
+
+### [2.0.2](https://github.com/dream2023/vue-ele-form-generator/compare/v2.0.1...v2.0.2) (2020-03-02)
+
+
+### Bug Fixes
+
+* 修复若干组件属性 ([eb42623](https://github.com/dream2023/vue-ele-form-generator/commit/eb4262319dab6eda98ea7123cf68d8b47e565545))
+
+### [2.0.1](https://github.com/dream2023/vue-ele-form-generator/compare/v2.0.0...v2.0.1) (2020-02-27)
+
+## [2.0.0](https://github.com/dream2023/vue-ele-form-generator/compare/v1.10.1...v2.0.0) (2020-02-27)
+
+
+### ⚠ BREAKING CHANGES
+
+* 用ts重构 & 修复若干问题
+
+### Bug Fixes
+
+* release 脚本错误 ([551d51d](https://github.com/dream2023/vue-ele-form-generator/commit/551d51d7eebda6663464616383af1df7b3ad28ae))
+
+
+* 用ts重构 & 修复若干问题 ([e8ca828](https://github.com/dream2023/vue-ele-form-generator/commit/e8ca828114ab6be693732b22454daf50048096c2))
+
+### [1.10.1](https://github.com/dream2023/vue-ele-form-generator/compare/v1.10.0...v1.10.1) (2020-02-14)
+
+### Bug Fixes
+
+- **#21:** 无 gui 界面报错 & node 版本检测 ([bf67acb](https://github.com/dream2023/vue-ele-form-generator/commit/bf67acb1f741cea534c5260f439f7247d56bc8fd)), closes [#21](https://github.com/dream2023/vue-ele-form-generator/issues/21)
+
+## [1.10.0](https://github.com/dream2023/vue-ele-form-generator/compare/v1.9.0...v1.10.0) (2020-02-10)
+
+### Features
+
+- 增加属性搜索 ([e2fb6bc](https://github.com/dream2023/vue-ele-form-generator/commit/e2fb6bc0b197842c9ede19bfdb047493e0ca8b1d))
+- 添加 组件查找 功能 ([25d5023](https://github.com/dream2023/vue-ele-form-generator/commit/25d50236ec9269e7ae7d88d99e9b6400675d3e62))
+
+## [1.9.0](https://github.com/dream2023/vue-ele-form-generator/compare/v1.8.0...v1.9.0) (2020-02-06)
+
+### Features
+
+- 增加批量添加表单项 ([e20defd](https://github.com/dream2023/vue-ele-form-generator/commit/e20defd44f141143a22edda3a174d8e97ec5add6))
+
+## [1.8.0](https://github.com/dream2023/vue-ele-form-generator/compare/v1.7.0...v1.8.0) (2020-02-02)
+
+### Features
+
+- 增加 cascader-panel 组件 ([6bc0994](https://github.com/dream2023/vue-ele-form-generator/commit/6bc09943687f3816439710e934adbe71651a6aea))
+- 增加文档链接 ([63a3d06](https://github.com/dream2023/vue-ele-form-generator/commit/63a3d06ffdd4cdfe165f56cec808786cc8f8c84c))
+
+### Bug Fixes
+
+- 删除多余属性 ([723d283](https://github.com/dream2023/vue-ele-form-generator/commit/723d2832e3c83e2a7876b2509d06ea9a60de0feb))
+
+## [1.7.0](https://github.com/dream2023/vue-ele-form-generator/compare/v1.6.1...v1.7.0) (2020-01-31)
+
+### Features
+
+- 增加可拖动左侧面板 ([1d94312](https://github.com/dream2023/vue-ele-form-generator/commit/1d94312bd5e809378371e56268e277435936a056))
+
+### [1.6.1](https://github.com/dream2023/vue-ele-form-generator/compare/v1.6.0...v1.6.1) (2020-01-30)
+
+### Bug Fixes
+
+- 生成唯一 id ([6a31d40](https://github.com/dream2023/vue-ele-form-generator/commit/6a31d40d0de7f6383331a456a4bc80ed62961a93))
+
+## [1.6.0](https://github.com/dream2023/vue-ele-form-generator/compare/v1.5.0...v1.6.0) (2020-01-30)
+
+### Features
+
+- 增加 rules 校检规则编辑 ([eb884a1](https://github.com/dream2023/vue-ele-form-generator/commit/eb884a1d3c5c845390335ce37526a5fa00cb2baf))
+- 增加校检 & 重构代码 ([8d8236c](https://github.com/dream2023/vue-ele-form-generator/commit/8d8236ca3bc0934f99ce96dbadcba2c92afa972b))
+
+### Bug Fixes
+
+- cli 脚本更新提示信息 ([4d3fe6a](https://github.com/dream2023/vue-ele-form-generator/commit/4d3fe6a5c369925c98186173872256eb88f55b50))
+
+## [1.5.0](https://github.com/dream2023/vue-ele-form-generator/compare/v1.4.0...v1.5.0) (2020-01-20)
+
+### Features
+
+- default 默认值类型完善 ([c8abec9](https://github.com/dream2023/vue-ele-form-generator/commit/c8abec90d4d4db2f8435ede872ccceed08e34f03))
+
+## [1.4.0](https://github.com/dream2023/vue-ele-form-generator/compare/v1.3.2...v1.4.0) (2020-01-19)
+
+### Features
+
+- cli 优化提示 ([6c0727b](https://github.com/dream2023/vue-ele-form-generator/commit/6c0727bc013bff133b47d3628d93f57b0c07eb0a))
+
+### [1.3.2](https://github.com/dream2023/vue-ele-form-generator/compare/v1.3.0...v1.3.2) (2020-01-19)
+
+### Bug Fixes
+
+- 兼容 vscode ([7529d06](https://github.com/dream2023/vue-ele-form-generator/commit/7529d064a0054c8fb3469ff2985a9993594a9f02))
+
+## [1.3.0](https://github.com/dream2023/vue-ele-form-generator/compare/v1.2.0...v1.3.0) (2020-01-19)
+
+### Features
+
+- 兼容 vscode 插件 ([3bf52c6](https://github.com/dream2023/vue-ele-form-generator/commit/3bf52c6996b9c031aacff5a44ac9aacb0702f65b))
+
+## [1.2.0](https://github.com/dream2023/vue-ele-form-generator/compare/v1.1.0...v1.2.0) (2020-01-15)
+
+### Features
+
+- 增加属性链接 ([4a9bb06](https://github.com/dream2023/vue-ele-form-generator/commit/4a9bb065bba2a2e53bd7b12cdb63c36af7f84261))
+
+## 1.1.0 (2020-01-14)
+
+### Features
+
+- 优化显示代码 ([459b957](https://github.com/dream2023/vue-ele-form-generator/commit/459b957d734eab16a2469983583d37ad08a280f6))
+- 使用 cdn 和 使用动态组件加快访问 ([c11b76c](https://github.com/dream2023/vue-ele-form-generator/commit/c11b76c4a6e1274742b2c0d79ae1af25f4205908))
+- 修改 label 并 增加辅助属性功能 ([89ea798](https://github.com/dream2023/vue-ele-form-generator/commit/89ea7987f04c2b48f22789ee112a2813a8ce18bc))
+- 升级依赖, 支持动态 type ([9be5b22](https://github.com/dream2023/vue-ele-form-generator/commit/9be5b2293f9855212f551a2f0f64c4f672fa0a4d))
+- 增加 cli 工具 ([d447566](https://github.com/dream2023/vue-ele-form-generator/commit/d44756653c9fe6ba897a4974bd247d9289fc58cc))
+- 增加 release 规范 ([6ee5bfb](https://github.com/dream2023/vue-ele-form-generator/commit/6ee5bfb447de5d45bc4ca668fcc44086ced3b13f))
+- 增加 tree-select 组件 ([63f6636](https://github.com/dream2023/vue-ele-form-generator/commit/63f6636fc1ca930ad9aad7a4bd516058156d1f24))
+- 增加初始化示例 ([a8bfa70](https://github.com/dream2023/vue-ele-form-generator/commit/a8bfa70b2741893c3fb30633134e02811c43e1dc))
+- 增加取消按钮选项 ([3f4bf6b](https://github.com/dream2023/vue-ele-form-generator/commit/3f4bf6bbbb807b284f1186e0ad48f1acd38402bf))
+- 增加属性面板的提示 ([47c6174](https://github.com/dream2023/vue-ele-form-generator/commit/47c617484914d0d1b9516b9f678d28c173b3aba2))
+- 增加数据持久化, 刷新不会丢数据 ([1c9efcf](https://github.com/dream2023/vue-ele-form-generator/commit/1c9efcf1da5fb7c1c0d2c4207a5e349a4c1434df))
+- 增加清空列表功能 ([5beac99](https://github.com/dream2023/vue-ele-form-generator/commit/5beac9926c751254c074a3ce948e458123d7f457))
+- 完善扩展组件属性 ([1f9c2ff](https://github.com/dream2023/vue-ele-form-generator/commit/1f9c2ff058988848ee1ce3bf481dc7c92ca3217a))
+- 完成基本功能待完善属性 ([d6e7052](https://github.com/dream2023/vue-ele-form-generator/commit/d6e70520d0564e026543c68042b0c4abbbdc782f))
+- 完成基础组件的属性添加 ([94a3f37](https://github.com/dream2023/vue-ele-form-generator/commit/94a3f3716cbbc01ac8febd1f10b02842030fd02a))
+- 完成大部分内置组件属性 ([f5afdfa](https://github.com/dream2023/vue-ele-form-generator/commit/f5afdfaa733a49a09ff58193eba2107aadff8822))
+- 属性优化 ([0bbe25a](https://github.com/dream2023/vue-ele-form-generator/commit/0bbe25aee811cb9bbe6cf197191f67104f07fb4a))
+- 用 vuex 代替多个组件相互传值 ([1f0bfeb](https://github.com/dream2023/vue-ele-form-generator/commit/1f0bfeb0c46ecf95bba67689ef705ab35d632a61))
+- 重构配置属性方式 ([53e708d](https://github.com/dream2023/vue-ele-form-generator/commit/53e708d5ad9e0e1f54824e31b9258b7fe7b3dc9f))
+
+### Bug Fixes
+
+- windows 显示不正常 ([014851d](https://github.com/dream2023/vue-ele-form-generator/commit/014851d3234275a7d358e940fbb5b31ecf2996c5))
+- 修复样式问题 ([448fbf1](https://github.com/dream2023/vue-ele-form-generator/commit/448fbf1007f514175bdcbe7418d4a6e84ae01bfe))
+- 生成代码不正常 ([baf405b](https://github.com/dream2023/vue-ele-form-generator/commit/baf405b35c71a5eff07ee4a3f430e904089a7e2e))

+ 21 - 0
LICENSE

@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2019 二当家的
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.m

+ 158 - 0
README.md

@@ -0,0 +1,158 @@
+# vue-ele-form-generator | vue-ele-form 的表单设计器
+
+[![MIT](https://img.shields.io/github/license/dream2023/vue-ele-form-generator)](https://github.com/dream2023/vue-ele-form-generator)
+![npm](https://img.shields.io/npm/dt/vue-ele-form-generator)
+[![Netlify Status](https://api.netlify.com/api/v1/badges/4c2ddffb-26b2-4e64-8b22-25678db57483/deploy-status)](https://app.netlify.com/sites/vue-ele-form-generator/deploys)
+![gitub pages](https://github.com/dream2023/vue-ele-form-generator/workflows/gitub%20pages/badge.svg)
+[![Star on GitHub](https://img.shields.io/github/stars/dream2023/vue-ele-form-generator.svg?style=social)](https://github.com/dream2023/vue-ele-form-generator/stargazers)
+
+## 介绍
+
+vue-ele-form-generator 是专为 [vue-ele-form](https://github.com/dream2023/vue-ele-form) 开发的可视化表单设计工具, 并且支持[vscode 插件](https://marketplace.visualstudio.com/items?itemName=dream2023.fgen-for-vscode)、[cli 本地启动](https://github.com/dream2023/fgen-cli)、[在线设计](https://dream2023.gitee.io/vue-ele-form-generator/)多种方式, 让表单开发的效率更上一层楼!
+
+[![vue-ele-form-generator 演示图](https://s1.ax1x.com/2020/03/17/8UJqhT.gif)](https://dream2023.gitee.io/vue-ele-form-generator/)
+
+## 特性
+
+- 提供[vscode 插件](https://marketplace.visualstudio.com/items?itemName=dream2023.fgen-for-vscode)更贴近日常开发
+- 可视化配置页面
+- 提供 `vue-ele-form` 全部基础组件 和 全部扩展组件
+- 支持组件属性点选配置
+- 支持本地启动, 告别被墙的痛苦
+- 服务器保存, 应用到项目
+- 基于项目的多表单管理, 省去二次开发
+- 数据持久化(刷新依然存在)
+- 一键生成配置 json 数据
+- 一键生成.vue 格式内容
+
+## 帮助文章 Wiki
+
+- [二次开发简单指导](https://github.com/dream2023/vue-ele-form-generator/wiki/%E4%BA%8C%E6%AC%A1%E5%BC%80%E5%8F%91%E7%AE%80%E5%8D%95%E6%8C%87%E5%AF%BC)
+
+- [如何将 vue form generator 集成到已有项目](https://github.com/dream2023/vue-ele-form-generator/wiki/%E5%A6%82%E4%BD%95%E5%B0%86vue-form-generator%E9%9B%86%E6%88%90%E5%88%B0%E5%B7%B2%E6%9C%89%E9%A1%B9%E7%9B%AE)
+
+- [如何将数据存到服务器](https://github.com/dream2023/vue-ele-form-generator/wiki/%E5%B0%86%E6%95%B0%E6%8D%AE%E5%AD%98%E5%88%B0%E6%9C%8D%E5%8A%A1%E5%99%A8)
+
+- [如何以守护进程的方式启动 cli 工具](https://github.com/dream2023/vue-ele-form-generator/wiki/%E5%A6%82%E4%BD%95%E4%BB%A5%E5%AE%88%E6%8A%A4%E8%BF%9B%E7%A8%8B%E6%96%B9%E5%BC%8F%E5%90%AF%E5%8A%A8fgen-cli)
+
+## 安装 和 使用
+
+### 第一步: 项目安装 vue-ele-form
+
+本可视化项目是专为 vue-ele-form 组件开发的表单设计器, 如果想要在项目中使用生成的代码, 必须[安装](https://www.yuque.com/chaojie-vjiel/vbwzgu/xl46cd) `vue-ele-form` 组件, 点击[查看](https://www.yuque.com/chaojie-vjiel/vbwzgu/xl46cd);
+
+### 第二步: 使用可视化设计表单
+
+#### 第一种方式: 在线设计地址(有点慢, 请耐心)
+
+- 中国: [https://dream2023.gitee.io/vue-ele-form-generator/](https://dream2023.gitee.io/vue-ele-form-generator/)
+- 其它地区(online): [https://vue-ele-form-generator.netlify.com/](https://vue-ele-form-generator.netlify.com/)
+
+#### 第二种方式: 本地启动
+
+```bash
+# 安装
+yarn global add fgen-cli # 或 npm install -g fgen-cli
+```
+
+```bash
+# 基本使用
+fgen
+```
+
+```bash
+# 指定端口
+fgen -p 8080
+```
+
+```bash
+# 更新
+yarn global add fgen-cli # 或 npm update -g fgen-cli
+```
+
+#### 第三种方式: vscode 插件
+
+插件市场搜索: `fgen-for-vscode`, 或者点击[链接](https://marketplace.visualstudio.com/items?itemName=dream2023.fgen-for-vscode&ssr=false#review-details)
+
+## 生态
+
+| Project                                                                                          | Status                                                                                                                         | Description                                 |
+| ------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------- |
+| [vue-ele-form](https://github.com/dream2023/vue-ele-form)                                        | [![npm](https://img.shields.io/npm/v/vue-ele-form)](https://github.com/dream2023/vue-ele-form)                                 | 接基于 ElementUI 的数据驱动表单             |
+| [vue-ele-form-generator](https://github.com/dream2023/vue-ele-form-generator)                    | [![npm](https://img.shields.io/npm/v/vue-ele-form-generator)](https://github.com/dream2023/vue-ele-form-generator)             | 专为 vue-ele-form 开发的可视化表单设计工具  |
+| [fgen-cli](https://github.com/dream2023/fgen-cli)                                                | [![npm](https://img.shields.io/npm/v/fgen-cli)](https://github.com/dream2023/fgen-cli)                                         | 本地启动 vue-ele-form-generator 的 cli 工具 |
+| [fgen-for-vscode](https://marketplace.visualstudio.com/items?itemName=dream2023.fgen-for-vscode) | ![Visual Studio Marketplace Version](https://img.shields.io/visual-studio-marketplace/v/dream2023.fgen-for-vscode)             | vue-ele-form-generator 的 vscode 扩展       |
+| [vue-ele-form-json-editor](https://github.com/dream2023/vue-ele-form-json-editor)                | [![npm](https://img.shields.io/npm/v/vue-ele-form-json-editor)](https://github.com/dream2023/vue-ele-form-json-editor)         | JSON 编辑器(vue-ele-form 扩展)              |
+| [vue-ele-form-upload-file](https://github.com/dream2023/vue-ele-form-upload-file)                | [![npm](https://img.shields.io/npm/v/vue-ele-form-upload-file)](https://github.com/dream2023/vue-ele-form-upload-file)         | upload 文件上传组件(vue-ele-form 扩展)      |
+| [vue-ele-form-image-uploader](https://github.com/dream2023/vue-ele-form-image-uploader)          | [![npm](https://img.shields.io/npm/v/vue-ele-form-image-uploader)](https://github.com/dream2023/vue-ele-form-image-uploader)   | 上传图片增强组件(vue-ele-form 扩展)         |
+| [vue-ele-form-tree-select](https://github.com/dream2023/vue-ele-form-tree-select)                | [![npm](https://img.shields.io/npm/v/vue-ele-form-tree-select)](https://github.com/dream2023/vue-ele-form-tree-select)         | 树形选择框组件(vue-ele-form 扩展)           |
+| [vue-ele-form-table-editor](https://github.com/dream2023/vue-ele-form-table-editor)              | [![npm](https://img.shields.io/npm/v/vue-ele-form-table-editor)](https://github.com/dream2023/vue-ele-form-table-editor)       | 表格编辑器(vue-ele-form 扩展)               |
+| [vue-ele-form-dynamic](https://github.com/dream2023/vue-ele-form-dynamic)                        | [![npm](https://img.shields.io/npm/v/vue-ele-form-dynamic)](https://github.com/dream2023/vue-ele-form-dynamic)                 | 动态表单(vue-ele-form 扩展)                 |
+| [vue-ele-form-video-uploader](https://github.com/dream2023/vue-ele-form-video-uploader)          | [![npm](https://img.shields.io/npm/v/vue-ele-form-video-uploader)](https://github.com/dream2023/vue-ele-form-video-uploader)   | 上传视频增强组件(vue-ele-form 扩展)         |
+| [vue-ele-form-quill-editor](https://github.com/dream2023/vue-ele-form-quill-editor)              | [![npm](https://img.shields.io/npm/v/vue-ele-form-quill-editor)](https://github.com/dream2023/vue-ele-form-quill-editor)       | 富文本编辑器(vue-ele-form 扩展)             |
+| [vue-ele-form-markdown-editor](https://github.com/dream2023/vue-ele-form-markdown-editor)        | [![npm](https://img.shields.io/npm/v/vue-ele-form-markdown-editor)](https://github.com/dream2023/vue-ele-form-markdown-editor) | markdown 编辑器(vue-ele-form 扩展)          |
+| [vue-ele-form-bmap](https://github.com/dream2023/vue-ele-form-bmap)                              | [![npm](https://img.shields.io/npm/v/vue-ele-form-bmap)](https://github.com/dream2023/vue-ele-form-bmap)                       | 百度地图组件(vue-ele-form 扩展)             |
+| [vue-ele-form-codemirror](https://github.com/dream2023/vue-ele-form-codemirror)                  | [![npm](https://img.shields.io/npm/v/vue-ele-form-codemirror)](https://github.com/dream2023/vue-ele-form-codemirror)           | 代码编辑器(vue-ele-form-gallery 扩展)       |
+| [vue-ele-form-gallery](https://github.com/dream2023/vue-ele-form-gallery)                        | [![npm](https://img.shields.io/npm/v/vue-ele-form-gallery)](https://github.com/dream2023/vue-ele-form-gallery)                 | 图片/视频展示组件(vue-ele-form 扩展)        |
+
+<h2 align="center">特别感谢赞助者</h2>
+<!--platinum start-->
+<table>
+  <tbody>
+    <tr>
+      <td align="center" valign="middle">
+        <a href="http://www.youkefu.cn" target="_blank">
+          <img width="200px" src="https://portrait.gitee.com/uploads/avatars/user/400/1200081_ukewo_admin_1578945969.png">
+          <div>优客服</div>
+        </a>
+      </td>
+      <td align="center" valign="middle">
+        <a href="http://www.sagedoit.com/" target="_blank">
+          <img width="200px" src="https://i.loli.net/2020/02/10/capiUTAPgCWvLkM.png">
+          <div>圣捷远创</div>
+        </a>
+      </td>
+      <td align="center" valign="middle">
+        <a href="https://github.com/DamonNie" target="_blank">
+          <img width="200px" src="https://avatars2.githubusercontent.com/u/16314117?s=460&v=4">
+          <div>damonnie</div>
+        </a>
+      </td>
+      <td align="center" valign="middle">
+        <a href="https://github.com/xzusoft" target="_blank">
+          <img width="200px" src="https://avatars3.githubusercontent.com/u/12249515?s=460&v=4">
+          <div>xzusoft</div>
+        </a>
+      </td>
+    </tr><tr></tr>
+  </tbody>
+</table>
+<!--platinum end-->
+
+> 如果您觉得对您有所帮助, 可以请作者喝一杯咖啡, 让开源走的更远, 点击进入[码云赞赏](https://gitee.com/dream2023/vue-ele-form-generator)一下, 并加入下面交流群, 将链接发送给我
+
+## 交流群
+
+![交流群](https://i.loli.net/2020/02/07/MmY1u7f4wR3igcB.jpg)
+
+## Contributors ✨
+
+Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):
+
+<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->
+<!-- prettier-ignore-start -->
+<!-- markdownlint-disable -->
+<table>
+  <tr>
+    <td align="center"><a href="http://dream2023.github.io"><img src="https://avatars0.githubusercontent.com/u/19297757?v=4" width="100px;" alt=""/><br /><sub><b>张超杰</b></sub></a><br /><a href="https://github.com/dream2023/vue-ele-form-generator/commits?author=dream2023" title="Documentation">📖</a> <a href="https://github.com/dream2023/vue-ele-form-generator/commits?author=dream2023" title="Code">💻</a> <a href="#ideas-dream2023" title="Ideas, Planning, & Feedback">🤔</a></td>
+    <td align="center"><a href="https://github.com/Wisenl"><img src="https://avatars0.githubusercontent.com/u/17942052?v=4" width="100px;" alt=""/><br /><sub><b>Wisen</b></sub></a><br /><a href="https://github.com/dream2023/vue-ele-form-generator/commits?author=Wisenl" title="Code">💻</a></td>
+    <td align="center"><a href="https://github.com/IWANABETHATGUY"><img src="https://avatars1.githubusercontent.com/u/17974631?v=4" width="100px;" alt=""/><br /><sub><b>IWANABETHATGUY</b></sub></a><br /><a href="https://github.com/dream2023/vue-ele-form-generator/commits?author=IWANABETHATGUY" title="Code">💻</a></td>
+  </tr>
+</table>
+
+<!-- markdownlint-enable -->
+<!-- prettier-ignore-end -->
+
+<!-- ALL-CONTRIBUTORS-LIST:END -->
+
+This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!

+ 4 - 0
babel.config.js

@@ -0,0 +1,4 @@
+module.exports = {
+  plugins: ["lodash"],
+  presets: ["@vue/cli-plugin-babel/preset"]
+};

+ 104 - 0
package.json

@@ -0,0 +1,104 @@
+{
+  "name": "vue-ele-form-generator",
+  "version": "2.2.2",
+  "description": "vue-ele-form-generator是专为vue-ele-form开发的可视化表单设计工具, 让表单开发的效率更上一层楼",
+  "private": false,
+  "license": "MIT",
+  "scripts": {
+    "bootstrap": "yarn || npm i",
+    "serve": "vue-cli-service serve",
+    "build": "vue-cli-service build",
+    "build:lib": "vue-cli-service build --mode=lib --target lib --name vue-ele-form-generator --dest ./lib/ ./src/main.ts",
+    "lint": "vue-cli-service lint src/* --mode production",
+    "release": "./release.sh"
+  },
+  "repository": {
+    "type": "git",
+    "url": "https://github.com/dream2023/vue-ele-form-generator"
+  },
+  "keywords": [
+    "vue-ele-form",
+    "vue-ele-form-generator",
+    "form-schema",
+    "vue-form",
+    "form-designer",
+    "form-design",
+    "element-ui",
+    "form-generator",
+    "for-generate",
+    "vue-form-generator",
+    "form schema"
+  ],
+  "homepage": "https://vue-ele-form-generator.netlify.com/",
+  "dependencies": {},
+  "devDependencies": {
+    "@types/crypto-js": "^3.1.43",
+    "@types/ejs": "^3.0.2",
+    "@types/lodash-es": "^4.17.3",
+    "@types/lz-string": "^1.3.33",
+    "@types/serialize-javascript": "^1.5.0",
+    "@typescript-eslint/eslint-plugin": "^2.24.0",
+    "@typescript-eslint/parser": "^2.24.0",
+    "@vue/cli-plugin-babel": "^4.2.3",
+    "@vue/cli-plugin-eslint": "^4.2.3",
+    "@vue/cli-plugin-typescript": "^4.2.3",
+    "@vue/cli-service": "^4.2.3",
+    "@vue/composition-api": "^0.4.0",
+    "@vue/eslint-config-prettier": "^6.0.0",
+    "@vue/eslint-config-typescript": "^5.0.2",
+    "babel-plugin-lodash": "^3.3.4",
+    "clipboard-copy": "^3.1.0",
+    "commitizen": "^4.0.3",
+    "core-js": "^3.6.4",
+    "cz-conventional-changelog": "^3.0.2",
+    "ejs": "^3.0.2",
+    "element-ui": "^2.13.0",
+    "eslint": "^6.7.2",
+    "eslint-plugin-prettier": "^3.1.1",
+    "eslint-plugin-vue": "^6.2.2",
+    "github-release-notes": "^0.17.1",
+    "lint-staged": "^9.5.0",
+    "lodash-es": "^4.17.15",
+    "normalize.css": "^8.0.1",
+    "prettier": "^1.19.1",
+    "raw-loader": "^4.0.0",
+    "sass": "^1.26.3",
+    "sass-loader": "^8.0.2",
+    "serialize-javascript": "^2.1.2",
+    "standard-version": "^7.0.1",
+    "typescript": "~3.7.5",
+    "vue": "^2.6.11",
+    "vue-ele-form": "^0.8.24",
+    "vue-ele-form-bmap": "^0.1.0",
+    "vue-ele-form-codemirror": "^0.1.1",
+    "vue-ele-form-dynamic": "^0.4.0",
+    "vue-ele-form-gallery": "^0.1.1",
+    "vue-ele-form-image-uploader": "^0.1.5",
+    "vue-ele-form-json-editor": "^0.1.2",
+    "vue-ele-form-markdown-editor": "^0.1.2",
+    "vue-ele-form-quill-editor": "^0.1.1",
+    "vue-ele-form-table-editor": "^0.1.3",
+    "vue-ele-form-tree-select": "^0.1.1",
+    "vue-ele-form-upload-file": "0.0.5",
+    "vue-ele-form-video-uploader": "^0.1.3",
+    "vue-multipane": "^0.9.5",
+    "vue-template-compiler": "^2.6.11",
+    "vuedraggable": "^2.23.0",
+    "vuex": "^3.1.3",
+    "vuex-persistedstate": "^2.7.1"
+  },
+  "config": {
+    "commitizen": {
+      "path": "node_modules/cz-conventional-changelog"
+    }
+  },
+  "gitHooks": {
+    "pre-commit": "lint-staged"
+  },
+  "lint-staged": {
+    "*.{js,jsx,vue,ts,tsx}": [
+      "vue-cli-service lint src/* --mode production",
+      "git add"
+    ]
+  }
+}

+ 1 - 0
prettier.config.js

@@ -0,0 +1 @@
+module.exports = {};

BIN
public/favicon.ico


+ 21 - 0
public/index.html

@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html lang="en">
+  <head>
+    <meta charset="utf-8" />
+    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
+    <meta name="viewport" content="width=device-width,initial-scale=1.0" />
+    <link rel="icon" href="<%= BASE_URL %>favicon.ico" />
+    <title><%= htmlWebpackPlugin.options.title %></title>
+  </head>
+  <body>
+    <noscript>
+      <strong
+        >We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work
+        properly without JavaScript enabled. Please enable it to
+        continue.</strong
+      >
+    </noscript>
+    <div id="app"></div>
+    <!-- built files will be auto injected -->
+  </body>
+</html>

+ 8 - 0
release.sh

@@ -0,0 +1,8 @@
+#!/bin/bash
+npm run lint
+npm run build
+npx standard-version
+npm publish --registry http://registry.npmjs.org
+git push --follow-tags origin master
+git push gitee
+npx gren release --override

+ 49 - 0
src/App.vue

@@ -0,0 +1,49 @@
+<template>
+  <div class="app">
+    <app-header />
+    <app-main />
+  </div>
+</template>
+
+<script lang="ts">
+import store from "@/store";
+import AppHeader from "./components/app-header.vue";
+import AppMain from "./components/app-main/index.vue";
+import { defineComponent, toRefs, watch } from "@vue/composition-api";
+import { preventReloadWindow } from "@/helpers/utils";
+
+export default defineComponent({
+  name: "App",
+  components: {
+    AppHeader,
+    AppMain
+  },
+  setup() {
+    // 阻止页面刷新
+    preventReloadWindow();
+
+    // 设置 title
+    const appName = "表单生成器";
+    const { currentForm } = toRefs(store.getters);
+    watch(currentForm, () => {
+      if (currentForm.value) {
+        document.title = currentForm.value.name + " | " + appName;
+      } else {
+        document.title = appName;
+      }
+    });
+  }
+});
+</script>
+
+<style>
+/* vscode 需要重置背景和样式 */
+body {
+  background: white;
+  padding: 0;
+}
+
+.app {
+  min-width: 1350px;
+}
+</style>

+ 74 - 0
src/components/app-header.vue

@@ -0,0 +1,74 @@
+<template>
+  <div class="app-header">
+    <el-link
+      :underline="false"
+      href="https://github.com/dream2023/vue-ele-form-generator"
+      target="_blank"
+      type="primary"
+    >
+      <h1 class="app-header-title">VUE-ELE-FORM 表单生成器</h1>
+    </el-link>
+    <div class="app-header-right">
+      <el-link
+        :href="item.url"
+        target="_blank"
+        type="primary"
+        v-for="item of links"
+        :key="item.title"
+        >{{ item.title }}</el-link
+      >
+    </div>
+  </div>
+</template>
+
+<script lang="ts">
+import { defineComponent } from "@vue/composition-api";
+
+export default defineComponent({
+  name: "AppHeader",
+  setup() {
+    return {
+      links: [
+        {
+          url: "https://github.com/dream2023/vue-ele-form",
+          title: "vue-ele-form"
+        },
+        {
+          url: "https://www.yuque.com/chaojie-vjiel/vbwzgu",
+          title: "Docs"
+        },
+        {
+          url: "https://github.com/dream2023/vue-ele-form-generator",
+          title: "GitHub"
+        }
+      ]
+    };
+  }
+});
+</script>
+
+<style lang="scss">
+.app-header {
+  box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08);
+  z-index: 1;
+  width: 100%;
+  padding: 0 20px;
+  display: flex;
+  justify-content: space-between;
+  box-sizing: border-box;
+
+  .app-header-title {
+    font-size: 24px;
+    color: #409eff;
+  }
+
+  .app-header-right {
+    display: flex;
+    align-items: center;
+
+    .el-link {
+      margin-right: 20px;
+    }
+  }
+}
+</style>

+ 182 - 0
src/components/app-main/components/components/batchDialog.vue

@@ -0,0 +1,182 @@
+<template>
+  <ele-form-dialog
+    v-model="formData"
+    :formDesc="batchFormDesc"
+    labelPosition="left"
+    width="800px"
+    :dialogAttrs="{
+      'close-on-click-modal': false,
+      'append-to-body': true
+    }"
+    :request-fn="handleAdd"
+    :visible="visible"
+    @update:visible="$emit('update:visible', $event)"
+    title="批量添加表单信息"
+  ></ele-form-dialog>
+</template>
+
+<script lang="ts">
+import _ from "lodash-es";
+import store from "@/store";
+import { Message } from "element-ui";
+import { addFormItem } from "@/helpers/tool";
+import {
+  toRefs,
+  ref,
+  createElement,
+  defineComponent,
+  computed
+} from "@vue/composition-api";
+import { FormItemList, FormDesc } from "@/types/project";
+import { Comp } from "../../../../types/comp";
+
+export default defineComponent({
+  name: "batchDialog",
+  props: {
+    visible: {
+      type: Boolean,
+      default: false
+    }
+  },
+  setup(props, context) {
+    const {
+      currentFormDesc: allFormDesc,
+      currentFormItemList: list,
+      sortedComps: comps
+    } = toRefs(store.getters);
+    const formData = ref({
+      type: "dynamic",
+      data: []
+    });
+    // 数量统计, 用于判断是否重复
+    const countObj = computed(() =>
+      _.countBy(formData.value.data, (o: any) => o.field)
+    );
+    const batchFormDesc: FormDesc = {
+      type: {
+        type: "radio",
+        label: "添加方式",
+        default: "dynamic",
+        options: [
+          { text: "逐个新增", value: "dynamic" },
+          { text: "编辑对象", value: "json-editor" },
+          {
+            text: "数据库导入(待开发)",
+            value: "mysql",
+            attrs: { disabled: true }
+          }
+        ]
+      },
+      data: {
+        type: data => data.type,
+        label: "表单项",
+        isTypeChangeReloadValue: false,
+        attrs: {
+          columns: [
+            {
+              type: "el-input",
+              valueKey: "field",
+              attrs: {
+                placeholder: "请输入 field"
+              }
+            },
+            {
+              type: "el-input",
+              valueKey: "label",
+              attrs: {
+                placeholder: "请输入 label"
+              }
+            },
+            {
+              type: "el-select",
+              valueKey: "type",
+              slots: {
+                default(h: typeof createElement) {
+                  return comps.value.map((item: Comp) =>
+                    h("el-option", {
+                      attrs: {
+                        label: `${item.type} - ${item.label}`,
+                        value: item.type
+                      }
+                    })
+                  );
+                }
+              },
+              attrs: {
+                filterable: true,
+                placeholder: "请输入选择 type"
+              }
+            }
+          ]
+        },
+        valueFormatter: arr =>
+          arr.map((item: AnyObj) => _.pick(item, ["field", "label", "type"])),
+        rules: {
+          type: "array",
+          validator: (rule: any, value: any, callback: AnyFunction) => {
+            let errorArr: any[] = [];
+            if (Array.isArray(value) && value.length) {
+              // 字段不可重复
+              errorArr = value
+                .map((item, index) =>
+                  allFormDesc.value[item.field] ||
+                  countObj.value[item.field] > 1
+                    ? `第${index + 1}行的 field 重复`
+                    : null
+                )
+                .filter(Boolean);
+
+              // 字段不可为空
+              errorArr = errorArr.concat(
+                value
+                  .map((item, index) => {
+                    const errMsg = [];
+                    if (!item.field) errMsg.push("field");
+                    if (!item.label) errMsg.push("label");
+                    if (!item.type) errMsg.push("type");
+
+                    return errMsg.length
+                      ? `第${index + 1}行的 ${errMsg.join("、")} 未填写`
+                      : null;
+                  })
+                  .filter(Boolean)
+              );
+            }
+            errorArr.length
+              ? callback(new Error(errorArr.join(" & ")))
+              : callback();
+          }
+        }
+      }
+    };
+    const updateList = (data: FormItemList) =>
+      store.commit("updateCurrentFormItemList", data);
+
+    function handleAdd({ data }: { data: AnyObj }) {
+      if (Array.isArray(data) && data.length) {
+        const newFormItems = data.map(item =>
+          addFormItem(item.type, { field: item.field, label: item.label })
+        );
+        updateList(list.value.concat(newFormItems));
+        Message.success("添加成功");
+        formData.value = {
+          type: "dynamic",
+          data: []
+        };
+
+        // 更新type使用次数
+        newFormItems.forEach(item =>
+          store.commit("updateCompCount", item.type)
+        );
+
+        context.emit("update:visible", false);
+      }
+    }
+    return {
+      handleAdd,
+      batchFormDesc,
+      formData
+    };
+  }
+});
+</script>

+ 56 - 0
src/components/app-main/components/components/exportDialog.vue

@@ -0,0 +1,56 @@
+<template>
+  <el-dialog
+    :visible="visible"
+    append-to-body
+    title="数据"
+    @update:visible="$emit('update:visible', $event)"
+    v-if="visible"
+    width="600px"
+  >
+    <json-editor :value="codeData"></json-editor>
+    <div style="text-align: center;margin-top: 20px">
+      <el-button @click="handleCopyData" type="primary">复制数据</el-button>
+    </div>
+  </el-dialog>
+</template>
+
+<script lang="ts">
+import copy from "clipboard-copy";
+import { Message } from "element-ui";
+import serialize from "serialize-javascript";
+import { defineComponent, computed, toRefs } from "@vue/composition-api";
+
+export default defineComponent({
+  name: "exportDialog",
+  props: {
+    visible: {
+      type: Boolean,
+      default: false
+    },
+    formDesc: {
+      type: Object,
+      default: () => ({})
+    },
+    formAttr: {
+      type: Object,
+      default: () => ({})
+    }
+  },
+  setup(props) {
+    const { formAttr, formDesc } = toRefs(props);
+    const codeData = computed(() => {
+      return Object.assign({}, formAttr.value, {
+        formDesc: formDesc.value
+      });
+    });
+    const handleCopyData = () => {
+      copy(serialize(codeData.value, { space: 2 }));
+      Message.success("复制成功!");
+    };
+    return {
+      codeData,
+      handleCopyData
+    };
+  }
+});
+</script>

+ 106 - 0
src/components/app-main/components/components/htmlDialog.vue

@@ -0,0 +1,106 @@
+<template>
+  <el-dialog
+    :visible="visible"
+    append-to-body
+    title="代码"
+    v-if="visible"
+    @update:visible="$emit('update:visible', $event)"
+    width="600px"
+  >
+    <codemirror :value="codeHtml"></codemirror>
+    <div style="text-align: center;margin-top: 20px">
+      <el-button @click="handleCopyHtml" type="primary">复制代码</el-button>
+      <el-button type="primary"
+        ><el-link
+          :href="fileURL"
+          style="color: white;"
+          :underline="false"
+          :download="fileName + '.vue'"
+          >下载文件</el-link
+        ></el-button
+      >
+    </div>
+  </el-dialog>
+</template>
+
+<script lang="ts">
+import tpl from "./tpl.ejs";
+import copy from "clipboard-copy";
+import ejs from "ejs";
+import { Message } from "element-ui";
+import _ from "lodash-es";
+import serialize from "serialize-javascript";
+import { defineComponent, computed, toRefs } from "@vue/composition-api";
+import store from "../../../../store";
+
+export default defineComponent({
+  name: "htmlDialog",
+  props: {
+    visible: {
+      type: Boolean,
+      default: false
+    },
+    formDesc: {
+      type: Object,
+      default: () => ({})
+    },
+    formAttr: {
+      type: Object,
+      default: () => ({})
+    }
+  },
+  setup(props) {
+    const { formDesc, formAttr } = toRefs(props);
+    const { currentForm } = toRefs(store.getters);
+
+    // 渲染模板
+    const codeHtml = computed(() => {
+      // 将  formDesc 转为 字符串
+      const getFormDescStr = (formDesc: any) => {
+        if (_.isEmpty(formDesc)) return "{}";
+
+        return (
+          serialize(formDesc, { space: 2 })
+            // 增加空格, {\n   'name': 'zhang'} -> {\n\t  'name': 'zhang'} 保持缩进好看
+            .replace(/\n/g, "\n\t  ")
+            // 将 key 的引号去掉: '{"name": "zhang"}' -> '{name: "zhang"}'
+            .replace(/"(\w+)":/g, "$1:")
+        );
+      };
+
+      const getFormAttrObj = (formAttr: any) => {
+        return Object.entries(formAttr).map(([key, value]) => {
+          // 将 [['name', 'zhang'], ['age', 10]] => [{name: 'zhang', ':age': 10}]
+          // 因为 vue 模板 非字符串前需要加 : 表示变量
+          key = typeof value === "string" ? key : `:${key}`;
+          return {
+            key,
+            value
+          };
+        });
+      };
+
+      return ejs.render(tpl, {
+        formAttr: getFormAttrObj(formAttr.value),
+        formDesc: getFormDescStr(formDesc.value)
+      });
+    });
+
+    const fileURL = computed(() => {
+      const blob = new Blob([codeHtml.value]);
+      return URL.createObjectURL(blob);
+    });
+    const handleCopyHtml = () => {
+      copy(codeHtml.value);
+      Message.success("复制成功!");
+    };
+
+    return {
+      fileName: currentForm.value.name,
+      fileURL,
+      codeHtml,
+      handleCopyHtml
+    };
+  }
+});
+</script>

+ 177 - 0
src/components/app-main/components/components/importDialog.vue

@@ -0,0 +1,177 @@
+<template>
+  <el-dialog
+    :visible="visible"
+    @update:visible="$emit('update:visible', $event)"
+    append-to-body
+    title="导入数据"
+    v-if="visible"
+    width="600px"
+  >
+    <!-- 预览弹窗 -->
+    <el-dialog width="90%" :visible.sync="isShowPreview" append-to-body>
+      <el-card header="表单预览" shadow="hover" class="box-card">
+        <ele-form
+          :form-desc="formDesc"
+          v-model="formData"
+          :request-fn="handleRequest"
+          @request-success="handleRequestSuccess"
+          v-bind="formAttr"
+        ></ele-form>
+      </el-card>
+      <div slot="footer">
+        <el-button @click="isShowPreview = false">返回编辑</el-button>
+        <el-button type="primary" @click="confirmGen">生成表单</el-button>
+      </div>
+    </el-dialog>
+
+    <el-alert
+      title="必须是JSON对象"
+      style="margin-bottom: 20px"
+      type="warning"
+      show-icon
+    >
+    </el-alert>
+
+    <!-- 编辑器 -->
+    <codemirror @input="handleChange" v-model="jsonStr"></codemirror>
+    <!-- 按钮操作 -->
+    <div style="text-align: center;margin-top: 20px">
+      <el-button @click="handleImport" :disabled="importDisabled" type="primary"
+        >导入数据</el-button
+      >
+    </div>
+  </el-dialog>
+</template>
+
+<script lang="ts">
+import _ from "lodash-es";
+import store from "@/store";
+import { Message } from "element-ui";
+import formAttrDefault from "@/store/formAttrDefault";
+import {
+  defineComponent,
+  ref,
+  computed,
+  watch,
+  toRefs
+} from "@vue/composition-api";
+
+export default defineComponent({
+  name: "importDialog",
+  props: {
+    visible: {
+      type: Boolean,
+      default: false
+    }
+  },
+  setup(props, context) {
+    const isShowPreview = ref(false);
+    const formDesc = ref({});
+    const formAttr = ref({});
+    const jsonData = ref({} as AnyObj);
+    const jsonStr = ref("{}");
+    const isError = ref(false);
+
+    // 无数据时, 禁用按钮
+    const importDisabled = computed(() => {
+      return !jsonData.value || Object.keys(jsonData.value).length === 0;
+    });
+
+    // 当隐藏时, 清空数据
+    const { visible } = toRefs(props);
+    watch(visible, () => {
+      isShowPreview.value = false;
+      jsonData.value = {};
+      formDesc.value = {};
+      jsonStr.value = "{}";
+    });
+
+    // 代码输入事件
+    const handleChange = _.debounce(json => {
+      try {
+        json = eval("(" + json + ")");
+        if (typeof json === "object") {
+          jsonData.value = _.cloneDeep(json);
+          formDesc.value = jsonData.value.formDesc;
+          isError.value = false;
+        } else {
+          isError.value = true;
+        }
+      } catch (err) {
+        isError.value = true;
+      }
+    }, 200);
+
+    // 处理粘贴的json
+    const handleImport = () => {
+      if (typeof jsonData.value !== "object" || isError.value) {
+        Message.error("请输入正确的 JSON 格式!");
+        return false;
+      }
+
+      if (jsonData.value && jsonData.value.formDesc) {
+        const keys = Object.keys(formAttrDefault);
+        formAttr.value = Object.assign(
+          {},
+          formAttrDefault,
+          _.pick(jsonData.value, keys)
+        );
+
+        // 临时预览的 formAttr
+        isShowPreview.value = true;
+      } else {
+        Message.error('数据必须有 "formDesc" 属性!');
+        return false;
+      }
+    };
+
+    // 处理json数据,将json 映射到操作面板 (list)
+    function json2Form() {
+      // 清空原有list (待优化)
+      store.commit("clearCurrentForm");
+      // 1.分离 formAttr (表单配置数据)
+      store.commit("updateCurrentFormAttr", formAttr.value);
+      // 2.更新 formDesc(组件通用配置数据)
+      // 3.处理 formDesc.attr(组件属性配置数据)
+      const list = Object.entries(formDesc.value).map(([key, val]) => ({
+        ...(val as object),
+        attrs: (val as { attrs: AnyObj }).attrs || {},
+        field: key
+      }));
+      // 更新 list
+      store.commit("updateCurrentFormItemList", list);
+      // 更新 selectIndex
+      store.commit("updateFormItemIndex", 0);
+    }
+
+    // 确认生成表单
+    function confirmGen() {
+      json2Form();
+      context.emit("update:visible", false);
+    }
+    return {
+      formData: ref({}),
+      confirmGen,
+      handleImport,
+      handleChange,
+      importDisabled,
+      isShowPreview,
+      formDesc,
+      formAttr,
+      jsonData,
+      jsonStr,
+      isError
+    };
+  },
+  methods: {
+    handleRequest(data) {
+      // eslint-disable-next-line no-console
+      console.log(data);
+      return Promise.resolve();
+    },
+    handleRequestSuccess() {
+      this.$message.success("发送成功");
+    }
+  }
+});
+</script>

+ 61 - 0
src/components/app-main/components/components/previewDialog.vue

@@ -0,0 +1,61 @@
+<template>
+  <el-dialog
+    append-to-body
+    :visible="visible"
+    @update:visible="$emit('update:visible', $event)"
+    title="预览"
+    width="90%"
+  >
+    <ele-form
+      :formDesc="computedFormDesc"
+      v-model="formData"
+      :visible="visible"
+      @update:visible="$emit('update:visible', $event)"
+      :request-fn="handleRequest"
+      @request-success="handleRequestSuccess"
+      v-bind="formAttr"
+    ></ele-form>
+  </el-dialog>
+</template>
+
+<script lang="ts">
+import _ from "lodash-es";
+import { defineComponent, toRefs, computed, ref } from "@vue/composition-api";
+
+export default defineComponent({
+  name: "previewDialog",
+  props: {
+    formDesc: {
+      type: Object,
+      default: () => ({})
+    },
+    formAttr: {
+      type: Object,
+      default: () => ({})
+    },
+    visible: {
+      type: Boolean,
+      default: false
+    }
+  },
+  setup(props) {
+    const formData = ref({});
+    const { formDesc } = toRefs(props);
+    return {
+      formData,
+      // 需要加一层 clone, 因为 ele-form会修改内部属性
+      computedFormDesc: computed(() => _.cloneDeep(formDesc.value))
+    };
+  },
+  methods: {
+    handleRequest(data) {
+      // eslint-disable-next-line no-console
+      console.log(data);
+      return Promise.resolve(data);
+    },
+    handleRequestSuccess() {
+      this.$message.success("发送成功");
+    }
+  }
+});
+</script>

+ 121 - 0
src/components/app-main/components/components/remoteConfig.vue

@@ -0,0 +1,121 @@
+<template>
+  <ele-form-dialog
+    v-model="formData"
+    :formDesc="formDesc"
+    width="800px"
+    :isShowLabel="false"
+    :dialogAttrs="{
+      'append-to-body': true
+    }"
+    :request-fn="handleAdd"
+    :visible="visible"
+    @update:visible="$emit('update:visible', $event)"
+    title="保存至服务器接口设置"
+  >
+    <el-alert
+      title="警告"
+      type="warning"
+      v-if="formData.type === 'remote'"
+      show-icon
+      style="margin-bottom: 40px"
+    >
+      浏览器会存在跨域的情况, 请做好跨域处理, 接口等具体请参考
+      <a
+        target="_blank"
+        href="https://github.com/dream2023/vue-ele-form-generator/wiki/%E5%B0%86%E6%95%B0%E6%8D%AE%E5%AD%98%E5%88%B0%E6%9C%8D%E5%8A%A1%E5%99%A8"
+        >文章</a
+      >
+    </el-alert>
+  </ele-form-dialog>
+</template>
+
+<script>
+import { defineComponent, ref, watch, toRefs } from "@vue/composition-api";
+import { Message } from "element-ui";
+import {
+  getRemoteConfig,
+  setRemoteConfig,
+  removeRemoteConfig
+} from "@/helpers/remoteConfig";
+import { isVscode } from "../../../../helpers/tool";
+import store from "../../../../store";
+
+export default defineComponent({
+  name: "remoteConfig",
+  props: {
+    visible: {
+      type: Boolean,
+      default: false
+    }
+  },
+  setup(props, context) {
+    const methods = ["GET", "POST", "PUT"];
+    const formData = ref({});
+    const { visible } = toRefs(props);
+    watch(visible, val => {
+      if (val && getRemoteConfig()) {
+        formData.value = getRemoteConfig();
+      } else {
+        formData.value = {};
+      }
+    });
+    const handleAdd = data => {
+      if (data.type === "remote") {
+        setRemoteConfig(data);
+      } else {
+        removeRemoteConfig();
+      }
+      store.commit("updateSaveType", data.type);
+      context.emit("update:visible", false);
+      Message.success("设置保存成功");
+    };
+    return {
+      formData,
+      formDesc: {
+        type: {
+          type: "radio",
+          label: "保存方式",
+          default: isVscode ? null : "local",
+          isShowLabel: true,
+          options: [
+            { text: "本地", value: "local", disabled: isVscode },
+            { text: "服务器", value: "remote" }
+          ]
+        },
+        getMethod: {
+          type: "select",
+          label: "获取方法",
+          default: "GET",
+          options: methods,
+          layout: 6,
+          vif: data => data.type === "remote",
+          required: true
+        },
+        getUrl: {
+          type: "input",
+          label: "获取表单数据的URL",
+          vif: data => data.type === "remote",
+          required: true,
+          layout: 18
+        },
+        updateMethod: {
+          type: "select",
+          label: "更新方法",
+          vif: data => data.type === "remote",
+          layout: 6,
+          options: methods,
+          required: true
+        },
+        updateUrl: {
+          type: "input",
+          layout: 18,
+          vif: data => data.type === "remote",
+          label: "更新表单数据的URL",
+          required: true
+        }
+      },
+      handleAdd
+    };
+  }
+});
+</script>

+ 29 - 0
src/components/app-main/components/components/tpl.ejs

@@ -0,0 +1,29 @@
+<template>
+  <ele-form<% for(let item of formAttr) { %>
+    <%- item.key %>="<%= item.value %>"<% } %>
+    :formDesc="formDesc"
+    v-model="formData"
+    :request-fn="handleRequest"
+    @request-success="handleRequestSuccess"
+  />
+</template>
+
+<script>
+export default {
+  data() {
+    return {
+      formData: {},
+      formDesc: <%- formDesc %>
+    };
+  },
+  methods: {
+    handleRequest(data) {
+      console.log(data);
+      return Promise.resolve();
+    },
+    handleRequestSuccess() {
+      this.$message.success("发送成功");
+    }
+  }
+};
+</script>

+ 223 - 0
src/components/app-main/components/main-center.vue

@@ -0,0 +1,223 @@
+<template>
+  <div class="app-main-center">
+    <ele-form
+      v-model="formData"
+      :form-desc="currentFormDesc"
+      :request-fn="handleSubmit"
+      @request-success="handleSuccess"
+      ref="ele-form"
+      v-bind="currentFormAttr"
+    >
+      <template
+        v-slot:form-content="{ props, formData, formDesc, formErrorObj }"
+      >
+        <draggable
+          :animation="200"
+          v-if="isRenderFinish"
+          :disabled="false"
+          :list="list"
+          @add="handleAdd"
+          @end="handleMoveEnd"
+          @start="handleMoveStart"
+          group="form"
+          tag="el-row"
+          style="padding-bottom: 80px;"
+        >
+          <!-- 当为空时 -->
+          <div class="form-area-placeholder" v-if="list.length === 0">
+            从左侧拖拽来添加表单项
+          </div>
+          <template v-else>
+            <template v-for="(formItem, field, index) of formDesc">
+              <el-col
+                :key="field"
+                v-bind="formItem._colAttrs"
+                @click.native="handleFormItemClick(index)"
+                v-if="formItem._vif"
+                class="form-item"
+                :class="{ 'form-item-active': currentFormItemIndex === index }"
+              >
+                <el-form-item
+                  :error="formErrorObj ? formErrorObj[field] : null"
+                  :prop="field"
+                  :label="
+                    props.isShowLabel && formItem.isShowLabel !== false
+                      ? formItem.label
+                      : null
+                  "
+                  :label-width="formItem.labelWidth || null"
+                >
+                  <component
+                    :disabled="props.disabled || formItem._disabled"
+                    :desc="formItem"
+                    :is="formItem._type"
+                    :options="formItem._options"
+                    :ref="field"
+                    :field="field"
+                    v-model="formItem.default"
+                  />
+                  <div
+                    class="ele-form-tip"
+                    v-if="formItem.tip"
+                    v-html="formItem.tip"
+                  ></div>
+                </el-form-item>
+
+                <!-- 删除按钮 -->
+                <i
+                  @click.stop="handleDelete(index)"
+                  class="el-icon-delete form-item-delete-btn"
+                  v-if="currentFormItemIndex === index"
+                ></i>
+              </el-col>
+            </template>
+          </template>
+        </draggable>
+      </template>
+    </ele-form>
+  </div>
+</template>
+
+<script lang="ts">
+import Vue from "vue";
+import store from "@/store";
+import draggable from "vuedraggable";
+import { defineComponent, toRefs, ref, onMounted } from "@vue/composition-api";
+
+export default defineComponent({
+  name: "AppMainCenter",
+  components: {
+    draggable
+  },
+  setup() {
+    const {
+      currentFormAttr,
+      currentFormDesc,
+      currentFormItemList: list
+    } = toRefs(store.getters);
+    const { currentFormItemIndex } = toRefs(store.state);
+
+    // 确保渲染结束
+    const isRenderFinish = ref(false);
+    onMounted(() => {
+      Vue.nextTick(() => {
+        isRenderFinish.value = true;
+      });
+    });
+
+    // 通过index删除
+    const deleteItemByIndex = (index: number) =>
+      store.commit("deleteFormItemByIndex", index);
+
+    // 通过index更新
+    const updateSelectIndex = (index: number) =>
+      store.commit("updateFormItemIndex", index);
+
+    // 删除
+    function handleDelete(index: number) {
+      deleteItemByIndex(index);
+
+      // 因为如果删除最后一个, 界面则无选中效果
+      // 所以这里删除最后一个后, 重新选择最后一个
+      if (index >= list.value.length) {
+        updateSelectIndex(list.value.length - 1);
+      }
+    }
+    // 新增
+    function handleAdd(res: AnyObj) {
+      updateSelectIndex(res.newIndex);
+      store.commit("updateCompCount", list.value[res.newIndex].type);
+    }
+    // 移动开始
+    function handleMoveStart(res: AnyObj) {
+      updateSelectIndex(res.oldIndex);
+    }
+    // 移动结束
+    function handleMoveEnd(res: AnyObj) {
+      updateSelectIndex(res.newIndex);
+    }
+    // 点击选中
+    function handleFormItemClick(index: number) {
+      updateSelectIndex(index);
+    }
+    return {
+      formData: ref({}),
+      list,
+      handleDelete,
+      handleMoveStart,
+      handleFormItemClick,
+      handleMoveEnd,
+      handleAdd,
+      currentFormAttr,
+      currentFormDesc,
+      currentFormItemIndex,
+      isRenderFinish
+    };
+  },
+  methods: {
+    // 表单提交
+    handleSubmit(data: any) {
+      // eslint-disable-next-line no-console
+      console.log(data);
+      return Promise.resolve();
+    },
+    // 请求成功
+    handleSuccess() {
+      this.$message.success("创建成功");
+    }
+  }
+});
+</script>
+
+<style lang="scss">
+.app-main-center {
+  padding: 20px;
+
+  /* 当无表单时的占位 */
+  .form-area-placeholder {
+    width: 100%;
+    height: 300px;
+    line-height: 300px;
+    background-color: white;
+    color: #909399;
+    text-align: center;
+  }
+
+  /* 表单项 */
+  .form-item {
+    background: white;
+    cursor: move;
+    position: relative;
+    z-index: 1;
+    padding: 0 20px;
+    border: 1px dashed rgba(0, 0, 0, 0);
+
+    &-active {
+      border: 1px dashed #409eff;
+    }
+
+    /* 遮挡区(遮挡住) */
+    &::after {
+      content: " ";
+      display: block;
+      left: 0;
+      top: 0;
+      right: 0;
+      bottom: 0;
+      position: absolute;
+      z-index: 2;
+    }
+
+    .form-item-delete-btn {
+      position: absolute;
+      right: 0;
+      bottom: 0;
+      z-index: 3;
+      cursor: pointer;
+      padding: 7px 15px;
+      color: white;
+      background: #409eff;
+    }
+  }
+}
+</style>

+ 209 - 0
src/components/app-main/components/main-header.vue

@@ -0,0 +1,209 @@
+<template>
+  <div class="app-main-header">
+    <div class="app-main-header-buttons">
+      <div>
+        <!-- 顶部按钮 -->
+        <el-button @click="isPreview = true" icon="el-icon-view" type="text"
+          >预览</el-button
+        >
+        <el-button
+          @click="handleSaveData"
+          icon="el-icon-upload"
+          type="text"
+          v-if="saveType === 'remote'"
+          >保存到服务器</el-button
+        >
+        <el-button
+          @click="isShowExportData = true"
+          icon="el-icon-upload2"
+          type="text"
+          >生成数据</el-button
+        >
+        <el-button
+          @click="isShowHtmlCode = true"
+          icon="el-icon-tickets"
+          type="text"
+          >生成代码</el-button
+        >
+        <el-button
+          @click="isShowImportDialog = true"
+          icon="el-icon-download"
+          type="text"
+          >导入数据</el-button
+        >
+        <el-button
+          @click="isShowBatchDialog = true"
+          icon="el-icon-plus"
+          type="text"
+          >批量添加</el-button
+        >
+        <el-button @click="clearForm" icon="el-icon-delete" type="text"
+          >清空表单</el-button
+        >
+      </div>
+      <div>
+        <el-button
+          @click="isShowremoteConfig = true"
+          icon="el-icon-setting"
+          type="text"
+          >保存设置</el-button
+        >
+      </div>
+    </div>
+
+    <!-- 预览弹窗 -->
+    <preview-dialog
+      :formDesc="formDesc"
+      :formAttr="filterFormAttr"
+      :visible.sync="isPreview"
+    />
+
+    <!-- 导入数据弹框 -->
+    <import-dialog :visible.sync="isShowImportDialog" />
+
+    <!-- 导出数据弹框 -->
+    <export-dialog
+      :formDesc="formDesc"
+      :formAttr="filterFormAttr"
+      :visible.sync="isShowExportData"
+    />
+
+    <!-- 生成代码弹框 -->
+    <html-dialog
+      :formDesc="formDesc"
+      :formAttr="filterFormAttr"
+      :visible.sync="isShowHtmlCode"
+    />
+
+    <!-- 批量添加 -->
+    <batch-dialog :visible.sync="isShowBatchDialog" />
+
+    <!-- 保存配置 -->
+    <remote-config :visible.sync="isShowremoteConfig" />
+  </div>
+</template>
+
+<script lang="ts">
+import _ from "lodash-es";
+import store from "@/store";
+import configList from "@/config";
+import { filterObjByDefault } from "@/helpers/utils";
+import remoteConfig from "./components/remoteConfig.vue";
+import htmlDialog from "./components/htmlDialog.vue";
+import batchDialog from "./components/batchDialog.vue";
+import importDialog from "./components/importDialog.vue";
+import exportDialog from "./components/exportDialog.vue";
+import previewDialog from "./components/previewDialog.vue";
+import { defineComponent, toRefs, computed, ref } from "@vue/composition-api";
+import { FormItemList } from "@/types/project";
+import { saveFormToServer } from "@/helpers/api";
+import { Message } from "element-ui";
+
+export default defineComponent({
+  name: "AppMainHeader",
+  components: {
+    remoteConfig,
+    htmlDialog,
+    batchDialog,
+    importDialog,
+    exportDialog,
+    previewDialog
+  },
+  setup() {
+    const { saveType } = toRefs(store.state);
+    const {
+      currentFormDesc: originFormDesc,
+      currentFormAttr: filterFormAttr
+    } = toRefs(store.getters);
+
+    // 处理数据, 主要是删除默认值和对值进行 formatter
+    const processData = (
+      obj: AnyObj,
+      defaultObj: AnyObj = {},
+      assistProperty: string[] = [],
+      formatterObj: AnyObj = {}
+    ): AnyObj => {
+      // 删除默认值
+      obj = filterObjByDefault(obj, defaultObj);
+      // 无值的 & 辅助属性 & 私有属性
+      const isShouldDelete = (val: any, key: string) =>
+        _.isNil(val) || assistProperty.includes(key) || key.startsWith("_");
+      obj = _.omitBy(_.cloneDeep(obj), isShouldDelete);
+
+      // 对数据做自定义处理
+      const formatterValue = (val: any, key: string) =>
+        formatterObj[key]?.valueFormatter
+          ? formatterObj[key].valueFormatter(val)
+          : val;
+      obj = _.mapValues(obj, formatterValue);
+
+      return obj;
+    };
+
+    const formDesc = computed(() => {
+      // 对formDesc每一项进一步处理:
+      // 1.删除无用属性 2.自定义formatter函数
+      return _.mapValues(_.cloneDeep(originFormDesc.value), (formItem: any) => {
+        const { commonDefaultData, attrsDefaultData, assistProperty, attrs } =
+          configList[formItem.type as string] || {};
+
+        formItem = processData(formItem, commonDefaultData as FormItemList);
+
+        // 组件自身属性
+        if (formItem.attrs) {
+          formItem.attrs = processData(
+            formItem.attrs,
+            attrsDefaultData,
+            assistProperty,
+            attrs
+          );
+        }
+        if (_.isEmpty(formItem.attrs)) {
+          delete formItem.attrs;
+        }
+
+        return formItem;
+      });
+    });
+
+    // 保存数据
+    const handleSaveData = async () => {
+      const res = await saveFormToServer(store.state);
+      if (res) {
+        if (res.code === 0) {
+          Message.success("保存成功");
+        } else {
+          Message.error("保存失败, 失败原因: " + res.msg);
+        }
+      }
+    };
+
+    return {
+      formDesc,
+      filterFormAttr,
+      handleSaveData,
+      clearForm: () => store.commit("clearCurrentForm"),
+      isShowExportData: false,
+      isShowHtmlCode: false,
+      isPreview: ref(false),
+      isShowBatchDialog: false,
+      isShowImportDialog: false,
+      isShowremoteConfig: false,
+      saveType
+    };
+  }
+});
+</script>
+
+<style lang="scss">
+.app-main-header {
+  height: 60px;
+  line-height: 60px;
+  padding: 0 15px;
+  border-bottom: 1px solid #ebeef5;
+  .app-main-header-buttons {
+    display: flex;
+    justify-content: space-between;
+  }
+}
+</style>

+ 110 - 0
src/components/app-main/components/main-left-components.vue

@@ -0,0 +1,110 @@
+<template>
+  <div class="app-main-left-components">
+    <div class="search-comps">
+      <el-input
+        clearable
+        placeholder="请输入关键字查找组件"
+        v-model.trim="searchValue"
+      ></el-input>
+    </div>
+    <div class="app-main-content" style="padding-top: 10px;">
+      <draggable
+        :clone="handleAddFormItem"
+        :group="{ name: 'form', pull: 'clone', put: false }"
+        :list="filteredComps"
+        :sort="false"
+      >
+        <template v-for="item of filteredComps">
+          <div :key="item.type" class="comp-item">
+            <div class="comp-item-title">{{ item.type }}</div>
+            <div>{{ item.label }}</div>
+          </div>
+        </template>
+      </draggable>
+    </div>
+  </div>
+</template>
+
+<script lang="ts">
+import draggable from "vuedraggable";
+import { addFormItem } from "@/helpers/tool";
+import { defineComponent, ref, computed, toRefs } from "@vue/composition-api";
+import { Comp } from "@/types/comp";
+import { fuzzySearch } from "@/helpers/utils";
+import store from "../../../store";
+
+export default defineComponent({
+  name: "AppMainLeftComponents",
+  components: {
+    draggable
+  },
+  setup() {
+    const { sortedComps: comps } = toRefs(store.getters);
+
+    // 搜索
+    const search = () => {
+      const searchValue = ref("");
+      const filteredComps = computed(() => {
+        const keyword = searchValue.value.toLowerCase();
+        if (!keyword) {
+          return comps.value;
+        } else {
+          return comps.value.filter(
+            (item: Comp) =>
+              fuzzySearch(item.type, keyword) ||
+              fuzzySearch(item.label, keyword)
+          );
+        }
+      });
+      return {
+        searchValue,
+        filteredComps
+      };
+    };
+
+    const { searchValue, filteredComps } = search();
+    return {
+      searchValue,
+      filteredComps
+    };
+  },
+  methods: {
+    handleAddFormItem({ label, type }: Comp) {
+      return addFormItem(type, { label });
+    }
+  }
+});
+</script>
+
+<style lang="scss">
+.app-main-left-components {
+  line-height: 1.5em;
+  height: 100%;
+
+  .search-comps {
+    padding: 10px;
+    border-bottom: 1px solid #eee;
+  }
+
+  .comp-item {
+    width: 120px;
+    color: #606266;
+    cursor: move;
+    border: 1px solid #ebeef5;
+    overflow: hidden;
+    white-space: nowrap;
+    display: inline-block;
+    text-overflow: ellipsis;
+    border-radius: 3px;
+    padding: 6px 8px;
+    box-sizing: border-box;
+    margin: 5px;
+    font-size: 12px;
+
+    .comp-item-title {
+      font-weight: bold;
+      color: #222;
+    }
+  }
+}
+</style>

+ 464 - 0
src/components/app-main/components/main-left-projects.vue

@@ -0,0 +1,464 @@
+<template>
+  <div class="app-main-left-projects">
+    <div class="search-comps">
+      <el-input placeholder="输入关键字进行过滤" v-model="keyword"> </el-input>
+    </div>
+    <el-button
+      @click="beforeCreateProject"
+      class="create-project-btn"
+      type="primary"
+      size="mini"
+      >新建工程</el-button
+    >
+    <el-tree
+      :data="projectList"
+      default-expand-all
+      ref="tree"
+      highlight-current
+      :current-node-key="currentNodeKey"
+      node-key="key"
+      :filter-node-method="filterNode"
+      :props="{ children: 'formList', label: 'name' }"
+    >
+      <span class="custom-tree-node" slot-scope="{ node }">
+        <span
+          class="custom-tree-node-label"
+          @click="handleCommand('select', node)"
+          >{{ node.label }}</span
+        >
+        <el-dropdown @command="handleCommand($event, node)">
+          <span class="el-dropdown-link">
+            <i class="el-icon-more"></i>
+          </span>
+          <el-dropdown-menu slot="dropdown">
+            <el-dropdown-item
+              v-if="node.level === TreeLevel.PROJECT"
+              command="create"
+              >新建表单</el-dropdown-item
+            >
+            <el-dropdown-item command="update">编辑名称</el-dropdown-item>
+            <el-dropdown-item command="delete" style="color: #F56C6C"
+              >删除{{
+                node.level === TreeLevel.PROJECT ? "工程" : "表单"
+              }}</el-dropdown-item
+            >
+          </el-dropdown-menu>
+        </el-dropdown>
+      </span>
+    </el-tree>
+
+    <ele-form-dialog
+      :title="dialogTitle"
+      v-model="formData"
+      :formDesc="formDesc"
+      node-key="key"
+      :dialogAttrs="{
+        'append-to-body': true
+      }"
+      @request="handleEdit"
+      :visible.sync="isShowDialog"
+    />
+  </div>
+</template>
+
+<script lang="ts">
+import store from "@/store";
+import {
+  ref,
+  toRefs,
+  defineComponent,
+  watch,
+  computed
+} from "@vue/composition-api";
+import { Message, MessageBox } from "element-ui";
+import _ from "lodash-es";
+
+export default defineComponent({
+  name: "AppMainLeftProjects",
+  setup() {
+    // tree的level
+    enum TreeLevel {
+      PROJECT = 1,
+      FORM = 2
+    }
+    enum EditType {
+      PROJECT = 1,
+      FORM = 2
+    }
+
+    const { projectList, currentFormIndex, currentProjectIndex } = toRefs(
+      store.state
+    );
+
+    // 翻转, 后创建的放到前面
+    // 表单名字加上层级
+    const computedProjectList = computed(() =>
+      _.cloneDeep(projectList.value).map(project => {
+        project.key = project.name;
+        project.formList = project.formList.map(form => {
+          form.key = project.name + "-" + form.name;
+          return form;
+        });
+        return project;
+      })
+    );
+
+    // 基础表单描述
+    const baseFormDesc = (nameArr: string[], exclude?: string) => ({
+      name: {
+        type: "input",
+        label: "名称",
+        required: true,
+        rules: {
+          validator(rule: any, value: string, callback: AnyFunction) {
+            if (nameArr.includes(value)) {
+              return exclude === value
+                ? callback()
+                : callback(new Error("名称重复"));
+            } else {
+              callback();
+            }
+          }
+        }
+      }
+    });
+
+    // 表单描述
+    const formDesc = ref({});
+    // 表单数据
+    const formData = ref({});
+    // 是否显示探弹窗
+    const isShowDialog = ref(false);
+    // 用于判断是新增还是更新
+    const isUpdate = ref(false);
+    // 用于判断当前正在编辑的是表单还是工程
+    const editingType = ref(EditType.PROJECT);
+
+    // 当前正在编辑的工程索引
+    const currentEditProjectIndex = ref(0);
+
+    // 通过名称获取工程索引
+    function getProjectIndexByName(name: string): number {
+      return projectList.value.findIndex(project => project.name === name);
+    }
+
+    // 创建工程前
+    const beforeCreateProject = () => {
+      editingType.value = EditType.PROJECT;
+
+      formDesc.value = baseFormDesc(
+        computedProjectList.value.map(project => project.name)
+      );
+      formData.value = {};
+      isUpdate.value = false;
+      isShowDialog.value = true;
+    };
+
+    // 创建工程
+    const createProject = (data: any) => {
+      store.commit("createProject", data);
+      Message.success("新增成功");
+    };
+
+    // 更新工程前
+    const beforeUpdateProject = (node: any) => {
+      editingType.value = EditType.PROJECT;
+
+      formDesc.value = baseFormDesc(
+        computedProjectList.value.map(project => project.name),
+        node.data.name
+      );
+      formData.value = computedProjectList.value[currentEditProjectIndex.value];
+      isShowDialog.value = true;
+    };
+
+    // 更新工程
+    const updateProject = (data: any) => {
+      store.commit("updateProject", {
+        projectIndex: currentEditProjectIndex.value,
+        project: data
+      });
+      Message.success("更新成功");
+    };
+
+    // 编辑工程 ( 更新 / 创建 )
+    const handleEditProject = (data: any) => {
+      isUpdate.value ? updateProject(data) : createProject(data);
+      isShowDialog.value = false;
+      isUpdate.value = false;
+    };
+
+    // 删除工程
+    const deleteProject = async (node: any) => {
+      if (node.childNodes.length) {
+        try {
+          await MessageBox.confirm(
+            "此工程下存在表单, 您确定要删除吗?!",
+            "提示",
+            {
+              type: "warning"
+            }
+          );
+          store.commit("deleteProjectByIndex", currentEditProjectIndex.value);
+        } catch {
+          // DO NOTHING
+        }
+      } else {
+        store.commit("deleteProjectByIndex", currentEditProjectIndex.value);
+      }
+    };
+
+    // 当前正在编辑的表单的index
+    const currentEditFormIndex = ref(0);
+
+    // 通过名称获取当前编辑表单索引
+    function getFormIndexByName(projectIndex: number, name: string) {
+      const formIndex = projectList.value[projectIndex].formList.findIndex(
+        form => form.name === name
+      );
+      return formIndex;
+    }
+
+    // 创建表单前
+    const beforeCreateForm = () => {
+      editingType.value = EditType.FORM;
+
+      formDesc.value = baseFormDesc(
+        computedProjectList.value[currentEditProjectIndex.value].formList.map(
+          form => form.name
+        )
+      );
+      formData.value = {};
+      isShowDialog.value = true;
+    };
+    // 创建表单
+    const createForm = (data: any) => {
+      store.commit("createForm", {
+        projectIndex: currentEditProjectIndex.value,
+        form: data
+      });
+      Message.success("新增成功");
+    };
+
+    // 更新表单前
+    const beforeUpdateForm = (node: any) => {
+      editingType.value = EditType.FORM;
+
+      formDesc.value = baseFormDesc(
+        computedProjectList.value[currentEditProjectIndex.value].formList.map(
+          form => form.name
+        ),
+        node.data.name
+      );
+      formData.value =
+        computedProjectList.value[currentEditProjectIndex.value].formList[
+          currentEditFormIndex.value
+        ];
+      isShowDialog.value = true;
+    };
+
+    // 更新表单
+    const updateForm = (data: any) => {
+      store.commit("updateForm", {
+        projectIndex: currentEditProjectIndex.value,
+        formIndex: currentEditFormIndex.value,
+        form: data
+      });
+      Message.success("更新成功");
+    };
+
+    // 编辑表单 ( 更新 / 创建 )
+    const handleEditForm = (data: any) => {
+      isUpdate.value ? updateForm(data) : createForm(data);
+      isShowDialog.value = false;
+      isUpdate.value = false;
+    };
+
+    // 删除表单
+    const deleteForm = async () => {
+      try {
+        await MessageBox.confirm("您确定要删除此表单吗?!", "提示", {
+          type: "warning"
+        });
+        store.commit("deleteFormByIndex", {
+          projectIndex: currentEditProjectIndex.value,
+          formIndex: currentEditFormIndex.value
+        });
+      } catch {
+        // DO NOTHING
+      }
+    };
+
+    // 选择表单
+    const selectForm = async () => {
+      const updateSelect = () => {
+        store.commit("updateProjectIndex", currentEditProjectIndex.value);
+        store.commit("updateFormIndex", currentEditFormIndex.value);
+      };
+
+      if (currentFormIndex.value === null) {
+        updateSelect();
+      } else {
+        try {
+          await MessageBox.confirm("您确定要切换表单吗?!", "提示", {
+            type: "warning"
+          });
+          updateSelect();
+        } catch {
+          // DO NOTHING
+        }
+      }
+    };
+
+    const handleCommand = (command: string, node: any) => {
+      if (node.level === TreeLevel.PROJECT) {
+        currentEditProjectIndex.value = getProjectIndexByName(node.key);
+
+        switch (command) {
+          case "delete":
+            deleteProject(node);
+            break;
+          case "update":
+            isUpdate.value = true;
+            beforeUpdateProject(node);
+            break;
+          case "create":
+            beforeCreateForm();
+            break;
+        }
+      } else if (node.level === TreeLevel.FORM) {
+        currentEditProjectIndex.value = getProjectIndexByName(
+          node.parent.data.name
+        );
+        currentEditFormIndex.value = getFormIndexByName(
+          currentEditProjectIndex.value,
+          node.data.name
+        );
+
+        switch (command) {
+          case "delete":
+            deleteForm();
+            break;
+          case "update":
+            isUpdate.value = true;
+            beforeUpdateForm(node);
+            break;
+          case "select":
+            // 当前即选中的, 则无需选择
+            if (
+              currentEditProjectIndex.value === currentProjectIndex.value &&
+              currentEditFormIndex.value === currentFormIndex.value
+            ) {
+              return;
+            }
+            selectForm();
+            break;
+        }
+      }
+    };
+
+    const handleEdit = (data: any) => {
+      if (editingType.value === EditType.PROJECT) {
+        handleEditProject(data);
+      } else {
+        handleEditForm(data);
+      }
+    };
+
+    // 搜索相关
+    function search() {
+      const keyword = ref("");
+      const tree = ref(null);
+      const filterNode = (keyword: string, data: { name: string }) => {
+        if (!keyword) return true;
+        return data.name.indexOf(keyword) !== -1;
+      };
+
+      watch(
+        keyword,
+        val => {
+          (tree.value as any).filter(val);
+        },
+        { lazy: true }
+      );
+
+      return {
+        tree,
+        filterNode,
+        keyword
+      };
+    }
+
+    const currentNodeKey = computed(() => {
+      if (
+        currentFormIndex.value !== null &&
+        currentProjectIndex.value !== null
+      ) {
+        return computedProjectList.value[currentProjectIndex.value].formList[
+          currentFormIndex.value
+        ].key;
+      } else {
+        return null;
+      }
+    });
+    return {
+      ...search(),
+      currentNodeKey,
+      beforeCreateProject,
+      projectList: computedProjectList,
+      handleCommand,
+      isShowDialog,
+      formData,
+      formDesc,
+      isUpdate,
+      handleEdit,
+      dialogTitle: computed(
+        () =>
+          `${isUpdate.value ? "更新" : "新建"}${
+            editingType.value === EditType.PROJECT ? "工程" : "表单"
+          }`
+      ),
+      TreeLevel
+    };
+  }
+});
+</script>
+
+<style lang="scss">
+.app-main-left-projects {
+  line-height: 1.5em;
+  height: 100%;
+
+  .search-comps {
+    padding: 10px;
+    border-bottom: 1px solid #eee;
+  }
+
+  .create-project-btn {
+    margin: 8px 10px;
+  }
+
+  .custom-tree-node {
+    display: flex;
+    justify-content: space-between;
+    padding-right: 20px;
+    width: 100%;
+    align-items: center;
+    font-size: 14px;
+
+    .custom-tree-node-label {
+      flex: 1;
+    }
+
+    .operation-btns {
+      display: none;
+    }
+
+    &:hover {
+      .operation-btns {
+        display: block;
+      }
+    }
+  }
+}
+</style>

+ 50 - 0
src/components/app-main/components/main-right/components/components/attrs-header.vue

@@ -0,0 +1,50 @@
+<template>
+  <div>
+    <div class="app-main-right-link">
+      <el-link type="primary" target="_blank" :href="url"
+        >点击查看{{ title }}</el-link
+      >&nbsp;
+      <span style="vertical-align: middle;">属性详细解释</span>
+    </div>
+    <div class="app-main-right-search">
+      <el-input
+        v-model.trim="keyword"
+        clearable
+        @input="$emit('input', $event)"
+        placeholder="输入关键字搜索属性"
+      ></el-input>
+    </div>
+  </div>
+</template>
+
+<script lang="ts">
+import { defineComponent } from "@vue/composition-api";
+
+export default defineComponent({
+  name: "attrs-header",
+  props: {
+    url: String,
+    title: String
+  },
+  setup() {
+    return {
+      keyword: ""
+    };
+  }
+});
+</script>
+
+<style scoped>
+.app-main-right-link {
+  color: #666;
+  font-size: 14px;
+  margin-left: 24px;
+  padding-left: 10px;
+  margin-bottom: 10px;
+  border-left: 3px solid #eee;
+}
+.app-main-right-search {
+  padding: 20px;
+  box-sizing: border-box;
+}
+</style>

+ 213 - 0
src/components/app-main/components/main-right/components/components/form-item-rules.vue

@@ -0,0 +1,213 @@
+<template>
+  <!-- 新增校检 -->
+  <ele-form-dialog
+    v-model="formData"
+    :formDesc="computedFormDesc"
+    :request-fn="handleAddRule"
+    @request-success="handleSuccess"
+    :visible="visible"
+    :dialogAttrs="{
+      'append-to-body': true
+    }"
+    :formBtns="formBtns"
+    @update:visible="toggleDialog"
+    title="新增校检规则"
+  />
+</template>
+
+<script lang="ts">
+import _ from "lodash-es";
+import store from "@/store";
+import { changeFormLabel } from "@/helpers/tool";
+import { Message } from "element-ui";
+import {
+  defineComponent,
+  computed,
+  toRefs,
+  ref,
+  watch
+} from "@vue/composition-api";
+import { FormItemList, FormDesc } from "@/types/project";
+import { CreateElement } from "vue";
+
+export default defineComponent({
+  name: "FormItemRules",
+  props: {
+    visible: {
+      type: Boolean,
+      default: false
+    }
+  },
+  setup(props, context) {
+    const rangeLinkageFn = (data: AnyObj) =>
+      ["any", "string", "number", "array", "float", "integer", "hex"].includes(
+        data.type
+      );
+    const formDesc: FormDesc = {
+      type: {
+        type: "select",
+        label: "类型",
+        default: null,
+        layout: 12,
+        attrs: {
+          clearable: true
+        },
+        options: [
+          { value: "string", desc: "必须是 string" },
+          { value: "number", desc: "必须是 number" },
+          { value: "boolean", desc: "必须是 boolean" },
+          {
+            value: "regexp",
+            desc: "必须是正则或者是在调用 new RegExp 时不报错的字符串"
+          },
+          { value: "integer", desc: "整数" },
+          { value: "float", desc: "浮点数" },
+          { value: "enum", desc: "值必须出现在 enmu 枚举值中" },
+          { value: "date", desc: "合法的日期,使用 Date 判断" },
+          { value: "url", desc: "URL" },
+          { value: "email", desc: "邮箱地址" },
+          { value: "array", desc: "必须是数组,通过 Array.isArray 判断" },
+          { value: "object", desc: "是对象且不为数组" },
+          { value: "method", desc: "必须是 function" },
+          { value: "hex", desc: "16进制" }
+        ],
+        slots: {
+          default(h: CreateElement, { options }: AnyObj) {
+            const res = options.map((option: AnyObj) =>
+              h("el-option", { attrs: { value: option.value } }, [
+                h("span", { style: { float: "left" } }, option.value),
+                h(
+                  "span",
+                  {
+                    style: {
+                      float: "right",
+                      color: "#8492a6",
+                      fontSize: "13px"
+                    }
+                  },
+                  option.desc
+                )
+              ])
+            );
+            return res;
+          }
+        }
+      },
+      trigger: {
+        type: "select",
+        label: "触发方式",
+        layout: 12,
+        attrs: {
+          clearable: true
+        },
+        options: ["change", "blur"]
+      },
+      pattern: {
+        type: "input",
+        layout: 12,
+        label: "正则表达式文本",
+        slots: {
+          prepend: (h: CreateElement) => h("span", "/"),
+          append: (h: CreateElement) => h("span", "/")
+        },
+        valueFormatter(val: any) {
+          return val ? new RegExp(val) : val;
+        },
+        vif: (data: AnyObj) =>
+          ["any", "regexp", "string", "url"].includes(data.type)
+      },
+      min: {
+        type: "number",
+        label: "范围最小值",
+        vif: rangeLinkageFn,
+        layout: 12
+      },
+      max: {
+        type: "number",
+        label: "范围最大值",
+        layout: 12,
+        vif: rangeLinkageFn
+      },
+      len: {
+        type: "number",
+        label: "val.length(字符串 OR 数组) / 数值)",
+        layout: 12,
+        vif: rangeLinkageFn
+      },
+      enum: {
+        type: "dynamic",
+        label: "枚举值",
+        default: [],
+        layout: 12,
+        vif: (data: AnyObj) => data.type === "enum"
+      },
+      message: {
+        type: "input",
+        label: "提示信息",
+        layout: 12,
+        attrs: {
+          clearable: true
+        }
+      }
+    };
+    const { currentFormItem } = toRefs(store.getters);
+    const updateCurrentItem = (data: FormItemList) =>
+      store.commit("updateCurrentItem", data);
+    const formData = ref({});
+    const computedFormDesc = computed(() => changeFormLabel(formDesc));
+
+    function handleAddRule(data: AnyObj) {
+      // 过滤掉空值
+      const filteredData = _.cloneDeep(data);
+      for (const key in filteredData) {
+        if (!data[key]) {
+          delete filteredData[key];
+        }
+      }
+      return filteredData;
+    }
+
+    function toggleDialog(visible = false) {
+      context.emit("update:visible", visible);
+    }
+    function handleSuccess(data: AnyObj, isClose = true) {
+      if (Object.keys(data).length === 0) return;
+
+      const formItem = currentFormItem.value;
+      if (!formItem.rules) formItem.rules = [];
+      formItem.rules.push(data);
+      updateCurrentItem(formItem);
+      formData.value = {};
+      Message.success("创建成功");
+
+      if (isClose) {
+        toggleDialog();
+      }
+    }
+    const formBtns = [
+      {
+        text: "提交并新增",
+        type: "primary",
+        click: () => {
+          const data = handleAddRule(formData.value);
+          handleSuccess(data, false);
+        }
+      }
+    ];
+    const { visible } = toRefs(props);
+    watch(visible, value => {
+      if (!value) {
+        formData.value = {};
+      }
+    });
+    return {
+      handleAddRule,
+      formBtns,
+      handleSuccess,
+      toggleDialog,
+      computedFormDesc,
+      formData
+    };
+  }
+});
+</script>

+ 35 - 0
src/components/app-main/components/main-right/components/components/searchMixin.ts

@@ -0,0 +1,35 @@
+import _ from "lodash-es";
+import { ref, computed, Ref } from "@vue/composition-api";
+import { FormDesc } from "@/types/project";
+import { filterObjBy, fuzzySearch } from "@/helpers/utils";
+
+export default function(formDesc: Ref<FormDesc>) {
+  const keyword = ref("");
+  const filterFormDesc = computed(() => {
+    if (keyword.value) {
+      // 通过 label 进行过滤
+      const filterFn = (label: string) => fuzzySearch(label, keyword.value);
+
+      // 深度过滤, 当存在 children 时
+      const deepFilter = (obj: FormDesc) => {
+        return filterObjBy(obj, (obj: AnyObj) => {
+          if (obj.children) {
+            obj.children = deepFilter(obj.children);
+            return true;
+          } else {
+            return filterFn(obj.label);
+          }
+        });
+      };
+
+      return deepFilter(_.cloneDeep(formDesc.value));
+    } else {
+      return formDesc.value;
+    }
+  });
+
+  return {
+    filterFormDesc,
+    keyword
+  };
+}

+ 187 - 0
src/components/app-main/components/main-right/components/form-config.vue

@@ -0,0 +1,187 @@
+<template>
+  <div>
+    <attrs-header
+      url="https://www.yuque.com/chaojie-vjiel/vbwzgu/dyw8a7"
+      title="表单配置"
+      v-model="keyword"
+    />
+    <ele-form
+      :formData="formAttr"
+      @input="updateFormAttr"
+      :form-desc="filterFormDesc"
+      :isShowBackBtn="false"
+      :isShowSubmitBtn="false"
+      :span="20"
+      ref="ele-form"
+      labelPosition="top"
+    ></ele-form>
+  </div>
+</template>
+
+<script lang="ts">
+import _ from "lodash-es";
+import store from "@/store";
+import { changeFormLabel } from "@/helpers/tool";
+import searchMixin from "./components/searchMixin";
+import AttrsHeader from "./components/attrs-header.vue";
+import { defineComponent, toRefs, computed } from "@vue/composition-api";
+import { FormDesc } from "@/types/project";
+
+export default defineComponent({
+  name: "AppFormConfig",
+  components: { AttrsHeader },
+  setup() {
+    const { currerntOriginFormAttr } = toRefs(store.getters);
+    const formAttr = computed(() => _.cloneDeep(currerntOriginFormAttr.value));
+    const updateFormAttr = (data: AnyObj) =>
+      store.commit("updateCurrentFormAttr", data);
+    const originDesc: FormDesc = {
+      inline: {
+        type: "radio",
+        default: false,
+        label: "inline模式 / layout模式",
+        options: [
+          { text: "layout模式", value: false },
+          { text: "inline模式", value: true }
+        ],
+        on: {
+          change: (val: any) => {
+            if (!val) {
+              updateFormAttr(
+                Object.assign({}, formAttr.value, { isResponsive: true })
+              );
+            }
+          }
+        }
+      },
+      isResponsive: {
+        type: "switch",
+        label: "是否响应式",
+        vif: (data: AnyObj) => !data.inline,
+        options: [
+          { text: "是", value: true },
+          { text: "否", value: false }
+        ]
+      },
+      labelPosition: {
+        type: "select",
+        label: "标签位置",
+        options(data: AnyObj) {
+          const options: any[] = ["left", "right", "top"];
+          if (data.isResponsive && !data.inline) {
+            options.unshift({ text: "响应式", value: null });
+          }
+          return options;
+        }
+      },
+      span: {
+        type: "select",
+        label: "表单宽度",
+        vif: (data: AnyObj) => !data.inline,
+        options(data: AnyObj) {
+          const options: any[] = Array.from({ length: 24 }, (v, i) => {
+            return { text: `${24 - i} / 24`, value: 24 - i };
+          });
+          if (data.isResponsive) {
+            options.unshift({ text: "响应式", value: null });
+          }
+          return options;
+        },
+        style: {
+          width: "100%"
+        }
+      },
+      isDialog: {
+        type: "switch",
+        label: "是否为弹窗"
+      },
+      isShowLabel: {
+        type: "switch",
+        label: "是否显示标签"
+      },
+      labelWidth: {
+        type: "input",
+        label: "标签宽度",
+        attrs: {
+          type: "number",
+          min: 0,
+          step: 10
+        },
+        tip: "默认值为auto"
+      },
+      disabled: {
+        type: "switch",
+        label: "全局禁用表单"
+      },
+      readonly: {
+        type: "switch",
+        label: "全局只读表单"
+      },
+      isShowSubmitBtn: {
+        type: "radio",
+        label: "提交按钮",
+        options: [
+          { text: "显示", value: true },
+          { text: "隐藏", value: false }
+        ]
+      },
+      isShowResetBtn: {
+        type: "radio",
+        label: "重置按钮",
+        options: [
+          { text: "显示", value: true },
+          { text: "隐藏", value: false }
+        ]
+      },
+      isShowBackBtn: {
+        type: "radio",
+        label: "返回按钮",
+        options: [
+          { text: "默认", value: null },
+          { text: "显示", value: true },
+          { text: "隐藏", value: false }
+        ]
+      },
+      isShowCancelBtn: {
+        type: "radio",
+        label: "取消按钮",
+        options: [
+          { text: "默认", value: null },
+          { text: "显示", value: true },
+          { text: "隐藏", value: false }
+        ]
+      },
+      formBtnSize: {
+        type: "select",
+        label: "表单按钮大小",
+        options: [{ text: "默认", value: null }, "medium", "small", "mini"]
+      },
+      submitBtnText: {
+        type: "input",
+        label: "提交按钮文字"
+      },
+      backBtnText: {
+        type: "input",
+        label: "返回按钮文字"
+      },
+      cancelBtnText: {
+        type: "input",
+        label: "取消按钮文字"
+      },
+      resetBtnText: {
+        type: "input",
+        label: "返回按钮文字"
+      }
+    };
+
+    const formDesc = computed(() => changeFormLabel(originDesc));
+    const { filterFormDesc, keyword } = searchMixin(formDesc);
+    return {
+      updateFormAttr,
+      formAttr,
+      filterFormDesc,
+      keyword
+    };
+  }
+});
+</script>

+ 59 - 0
src/components/app-main/components/main-right/components/form-item-attrs.vue

@@ -0,0 +1,59 @@
+<template>
+  <div>
+    <template v-if="isShow">
+      <attrs-header
+        :url="attrLink"
+        :title="currentFormItem.type + '组件'"
+        v-model="keyword"
+      />
+      <ele-form
+        :formData="currentFormItem.attrs"
+        :formDesc="filterFormDesc"
+        @input="updateFormAttrs"
+        :isShowBackBtn="false"
+        :isShowSubmitBtn="false"
+        :span="20"
+        labelPosition="top"
+      />
+    </template>
+    <div class="form-item-placeholder" v-else>
+      从左侧拖拽添加表单项并点选
+    </div>
+  </div>
+</template>
+
+<script lang="ts">
+import store from "@/store";
+import configList from "@/config";
+import { changeFormLabel } from "@/helpers/tool";
+import searchMixin from "./components/searchMixin";
+import AttrsHeader from "./components/attrs-header.vue";
+import { defineComponent, computed, toRefs } from "@vue/composition-api";
+
+export default defineComponent({
+  name: "AppFormItemAttrs",
+  components: { AttrsHeader },
+  setup() {
+    const { currentFormItem } = toRefs(store.getters);
+    const isShow = computed(
+      () => currentFormItem.value && currentFormItem.value.attrs
+    );
+    const config = computed(() => configList[currentFormItem.value.type]);
+    const formDesc = computed(() => {
+      const formDesc = config.value.attrs || {};
+      return changeFormLabel(formDesc, config.value.assistProperty);
+    });
+
+    const updateFormAttrs = (data: any) =>
+      store.commit("updateCurrentItemAttrs", data);
+
+    return {
+      updateFormAttrs,
+      currentFormItem,
+      ...searchMixin(formDesc),
+      isShow,
+      attrLink: computed(() => config.value.url)
+    };
+  }
+});
+</script>

+ 180 - 0
src/components/app-main/components/main-right/components/form-item-config.vue

@@ -0,0 +1,180 @@
+<template>
+  <div>
+    <template v-if="currentFormItem">
+      <attrs-header
+        url="https://www.yuque.com/chaojie-vjiel/vbwzgu/iw5dzf"
+        title="通用配置"
+        v-model="keyword"
+      />
+      <ele-form
+        :formData="currentFormItem"
+        @input="updateCurrentItem"
+        :formDesc="filterFormDesc"
+        :isShowBackBtn="false"
+        :isShowSubmitBtn="false"
+        :rules="rules"
+        :span="20"
+        labelPosition="top"
+      >
+        <template v-slot:rules="{ data, desc, field, type }">
+          <div style="margin-bottom: 20px">
+            <el-button @click="isShowRuleDialog = true" type="danger"
+              >新增校检规则</el-button
+            >
+          </div>
+          <component
+            :desc="desc"
+            :is="type"
+            :field="field"
+            :value="currentFormItem[field]"
+            @input="handleChangeRules"
+          />
+          <form-item-rules :visible.sync="isShowRuleDialog" />
+        </template>
+      </ele-form>
+    </template>
+    <div class="form-item-placeholder" v-else>
+      从左侧拖拽添加表单项并点选
+    </div>
+  </div>
+</template>
+
+<script lang="ts">
+import _ from "lodash-es";
+import store from "@/store";
+import configList from "@/config";
+import { changeFormLabel } from "@/helpers/tool";
+import serialize from "serialize-javascript";
+import searchMixin from "./components/searchMixin";
+import AttrsHeader from "./components/attrs-header.vue";
+import FormItemRules from "./components/form-item-rules.vue";
+import { defineComponent, toRefs, computed, ref } from "@vue/composition-api";
+import { FormDesc, FormItemList } from "@/types/project";
+
+export default defineComponent({
+  name: "AppFormItemConfig",
+  components: {
+    AttrsHeader,
+    FormItemRules
+  },
+  setup() {
+    const { currentFormItemList: list } = toRefs(store.getters);
+
+    const { currentFormItem } = toRefs(store.getters);
+    const updateCurrentItem = (data: FormItemList) =>
+      store.commit("updateCurrentItem", data);
+    const countObj = computed(() => _.countBy(list.value, (o: any) => o.field));
+    const config: FormDesc = {
+      field: {
+        type: "input",
+        label: "字段名",
+        tip: "字段名不可重复",
+        rules: {
+          type: "string",
+          validator(rule: any, value: any, callback: AnyFunction) {
+            if (countObj.value[value] > 1) {
+              callback("字段名重复");
+            } else {
+              callback();
+            }
+          }
+        }
+      },
+      label: {
+        type: "input",
+        label: "标签"
+      },
+      layout: {
+        type: "slider",
+        label: "宽度",
+        attrs: {
+          min: 1,
+          max: 24,
+          "format-tooltip"(val: string) {
+            return `${val} / 24`;
+          }
+        },
+        on: {
+          input: (val: any) => {
+            // slider组件, 如果传递的value为null或者undefined, 会赋值为 1, 无法利用到默认值, 所以去掉
+            if (val !== 1) {
+              updateCurrentItem({ ...currentFormItem.value, layout: val });
+            }
+          }
+        }
+      },
+      default: {
+        type: "input",
+        label: "默认值"
+      },
+      required: {
+        type: "yesno",
+        label: "校检",
+        title: "是否必填"
+      },
+      rules: {
+        type: "textarea",
+        label: "校检规则",
+        title: "新增校检规则",
+        displayFormatter: (val: any) =>
+          val ? serialize(val, { space: 2 }) : "",
+        tip:
+          '校检规则文档, 请<a target="_blank" href="https://www.yuque.com/chaojie-vjiel/vbwzgu/qzzkpd" class="el-link el-link--primary">点击查看</a>'
+      },
+      tip: {
+        type: "input",
+        label: "表单项提示"
+      },
+      isShowLabel: {
+        label: "否显示标签",
+        type: "switch",
+        tip: "与全局isShowLabel作用相同"
+      },
+      labelWidth: {
+        label: "标签宽度",
+        type: "input",
+        tip: "需要以`px`作为单位, 例如`100px`, 默认为全局设置的labelWidth值"
+      }
+    };
+    const rules = {
+      field: {
+        required: true,
+        message: "字段必填"
+      },
+      label: {
+        required: true,
+        message: "标签不能为空"
+      }
+    };
+    const formDesc = computed(() => {
+      const customConfig = configList[currentFormItem.value.type].common || {};
+      const formDesc = Object.assign({}, config, customConfig);
+      return changeFormLabel(formDesc);
+    });
+    const handleChangeRules = (rules: AnyObj) => {
+      try {
+        if (rules) {
+          rules = eval("(" + rules + ")");
+          if (typeof rules !== "object") return;
+        } else {
+          rules = [];
+        }
+        const data = Object.assign({}, currentFormItem.value, { rules: rules });
+        updateCurrentItem(data);
+      } catch (error) {
+        // eslint-disable-next-line no-console
+        console.error(error);
+      }
+    };
+
+    return {
+      updateCurrentItem,
+      currentFormItem,
+      ...searchMixin(formDesc),
+      handleChangeRules,
+      rules: rules,
+      isShowRuleDialog: ref(false)
+    };
+  }
+});
+</script>

+ 73 - 0
src/components/app-main/components/main-right/index.vue

@@ -0,0 +1,73 @@
+<template>
+  <div class="app-main-right">
+    <el-tabs :stretch="true" v-model="activeTab">
+      <el-tab-pane label="组件通用配置" name="item-config"></el-tab-pane>
+      <el-tab-pane label="组件属性配置" name="item-attrs"></el-tab-pane>
+      <el-tab-pane label="表单配置" name="form-config"> </el-tab-pane>
+    </el-tabs>
+    <div class="app-main-content">
+      <app-form-item-config v-show="activeTab === 'item-config'" />
+      <app-form-item-attrs v-show="activeTab === 'item-attrs'" />
+      <app-form-config v-show="activeTab === 'form-config'" />
+    </div>
+  </div>
+</template>
+
+<script lang="ts">
+import AppFormConfig from "./components/form-config.vue";
+import AppFormItemAttrs from "./components/form-item-attrs.vue";
+import AppFormItemConfig from "./components/form-item-config.vue";
+import { defineComponent } from "@vue/composition-api";
+export default defineComponent({
+  name: "AppMainRight",
+  components: {
+    AppFormConfig,
+    AppFormItemAttrs,
+    AppFormItemConfig
+  },
+  setup() {
+    return {
+      activeTab: "item-config"
+    };
+  }
+});
+</script>
+
+<style lang="scss">
+.app-main-right {
+  width: 315px;
+  margin-top: 21px;
+  height: 100%;
+
+  .form-item-placeholder {
+    height: 300px;
+    line-height: 300px;
+    color: #909399;
+    text-align: center;
+  }
+
+  .el-tabs__nav-wrap {
+    padding: 0 15px;
+  }
+  .el-tabs__item {
+    padding: 0 10px;
+  }
+  .el-tabs__nav-wrap::after {
+    height: 1px !important;
+  }
+
+  .el-tabs__active-bar {
+    height: 1px !important;
+  }
+  .jsoneditor-container.min-box {
+    height: 200px !important;
+  }
+  div.jsoneditor-menu {
+    display: none;
+  }
+  div.jsoneditor-outer.has-main-menu-bar {
+    margin-top: 0 !important;
+    padding-top: 0 !important;
+  }
+}
+</style>

+ 159 - 0
src/components/app-main/index.vue

@@ -0,0 +1,159 @@
+<template>
+  <multipane @paneResizeStop="handlePaneresize" class="app-main">
+    <el-menu
+      @select="handleChangeMenu"
+      v-once
+      :default-active="activeComponent"
+    >
+      <el-menu-item
+        v-for="item of menus"
+        :index="item.componentName"
+        :key="item.componentName"
+      >
+        <i :class="item.icon"></i>
+      </el-menu-item>
+    </el-menu>
+    <component
+      :is="'app-main-left-' + activeComponent"
+      :style="{ width: leftWidth }"
+    />
+    <multipane-resizer></multipane-resizer>
+
+    <template v-if="isShowRight">
+      <div class="app-main-container">
+        <app-main-header />
+        <app-main-center class="app-main-content" />
+      </div>
+      <multipane-resizer></multipane-resizer>
+      <app-main-right />
+    </template>
+    <template v-else>
+      <div class="app-main-container not-selected">
+        请先选择表单
+      </div>
+    </template>
+  </multipane>
+</template>
+
+<script lang="ts">
+import store from "@/store";
+import { isVscode } from "@/helpers/tool";
+import AppMainHeader from "./components/main-header.vue";
+import AppMainCenter from "./components/main-center.vue";
+import { Multipane, MultipaneResizer } from "vue-multipane";
+import { defineComponent, ref, toRefs, computed } from "@vue/composition-api";
+import AppMainRight from "./components/main-right/index.vue";
+import AppMainLeftProjects from "./components/main-left-projects.vue";
+import AppMainLeftComponents from "./components/main-left-components.vue";
+
+export default defineComponent({
+  name: "AppMain",
+  components: {
+    AppMainCenter,
+    AppMainHeader,
+    AppMainRight,
+    Multipane,
+    MultipaneResizer,
+    AppMainLeftComponents,
+    AppMainLeftProjects
+  },
+  setup() {
+    const defaultWidth = "260px";
+    let leftWidth = defaultWidth;
+    if (!isVscode) {
+      leftWidth = localStorage.getItem("app-main-left") || defaultWidth;
+    }
+    const { currentProjectIndex, currentFormIndex } = toRefs(store.state);
+
+    // panel 拖拉变化
+    function handlePaneresize(el: HTMLElement) {
+      if (!isVscode && el.className === "app-main-left") {
+        localStorage.setItem("app-main-left", el.style.width);
+      }
+    }
+
+    // 改变菜单
+    const isShowRight = computed(
+      () =>
+        currentProjectIndex.value !== null && currentFormIndex.value !== null
+    );
+    const activeComponent = ref(isShowRight.value ? "components" : "projects");
+    function handleChangeMenu(componentName: string) {
+      activeComponent.value = componentName;
+    }
+
+    const menus = [
+      {
+        icon: "el-icon-document",
+        componentName: "components"
+      },
+      {
+        icon: "el-icon-menu",
+        componentName: "projects"
+      }
+    ];
+    return {
+      isShowRight,
+      leftWidth,
+      menus,
+      activeComponent,
+      handleChangeMenu,
+      handlePaneresize
+    };
+  }
+});
+</script>
+
+<style lang="scss">
+.app-main {
+  height: calc(100vh - 60px);
+  overflow: hidden;
+  margin-top: 2px;
+  .el-menu-item {
+    padding: 0 15px;
+  }
+  .app-main-content {
+    height: calc(100% - 60px);
+    overflow: scroll;
+    padding-bottom: 20px;
+    box-sizing: border-box;
+  }
+
+  .app-main-container {
+    flex-grow: 1;
+    flex: 1;
+    min-width: 730px;
+    &.not-selected {
+      text-align: center;
+      color: #666;
+      font-size: 14px;
+      margin-top: 15%;
+    }
+  }
+
+  & > .multipane-resizer {
+    margin: 0;
+    left: 0;
+    position: relative;
+    border-left: 1px solid #ebeef5;
+    border-right: 1px solid #ebeef5;
+    width: 7px;
+    &::before {
+      display: block;
+      content: "";
+      width: 1px;
+      height: 40px;
+      position: absolute;
+      top: 50%;
+      left: 50%;
+      margin-top: -20px;
+      margin-left: -1.5px;
+      border-left: 1px solid #dcdfe6;
+      border-right: 1px solid #dcdfe6;
+      &::before {
+        border-color: #999;
+      }
+    }
+  }
+}
+</style>

+ 12 - 0
src/config/README.md

@@ -0,0 +1,12 @@
+# 说明
+
+此文件夹为表单组件配置文件
+
+- url: 属性的说明地址
+- common: 代表通用配置, 例如 `default`、`options`等, 具体参考: [formDesc 表单描述](https://www.yuque.com/chaojie-vjiel/vbwzgu/iw5dzf)
+- attrs: 组件自身属性, 例如 `input` 组件有`maxlength`、`clearable`、`prefix-icon`等, 具体各个属性, 请参考[type 类型列表 第三列属性说明](https://www.yuque.com/chaojie-vjiel/vbwzgu/kz163g)
+- attrsDefaultData: 组件自身属性的默认值
+- commonDefaultData: 通用配置的默认值
+- assistProperty: 辅助属性, 用于判断类型, 展示数据时, 当删除此属性
+
+> tip: `attrs` 默认值, 有两个`attrsData`和`attrsDefaultData`, 原因是当默认值未发生变化时, 有的无需向用户展示, 需要删除, 有的必须有, 所有才区分两者, 同理 `common` 也是

+ 95 - 0
src/config/comps/autocomplete.ts

@@ -0,0 +1,95 @@
+import { Config } from "@/types/config";
+
+const config: Config = {
+  url: "https://element.eleme.cn/#/zh-CN/component/input#dai-shu-ru-jian-yi",
+  attrs: {
+    placeholder: {
+      type: "input",
+      label: "输入框占位文本",
+      attrs: {
+        clearable: true
+      }
+    },
+    valueKey: {
+      type: "input",
+      label: "输入建议对象中用于显示的键名",
+      attrs: {
+        clearable: true
+      }
+    },
+    prefixIcon: {
+      type: "input",
+      label: "输入框头部图标",
+      attrs: {
+        clearable: true
+      }
+    },
+    suffixIcon: {
+      type: "input",
+      label: "输入框尾部图标",
+      attrs: {
+        clearable: true
+      }
+    },
+    debounce: {
+      type: "number",
+      label: "获取输入建议的去抖延时",
+      attrs: {
+        min: 0,
+        step: 100
+      }
+    },
+    placement: {
+      type: "select",
+      label: "菜单弹出位置",
+      options: [
+        "top",
+        "top-start",
+        "top-end",
+        "bottom",
+        "bottom-start",
+        "bottom-end"
+      ]
+    },
+    popperClass: {
+      type: "input",
+      label: "Autocomplete 下拉列表的类名"
+    },
+    triggerOnFocus: {
+      type: "switch",
+      label: "是否在输入框 focus 时显示建议列表"
+    },
+    selectWhenUnmatched: {
+      type: "switch",
+      label: "在输入没有任何匹配建议的情况下,按下回车是否触发 select 事件"
+    },
+    hideLoading: {
+      type: "switch",
+      label: "是否隐藏远程加载时的加载图标"
+    },
+    popperAppendToBody: {
+      type: "switch",
+      label:
+        "是否将下拉列表插入至 body 元素。在下拉列表的定位出现问题时,可将该属性设置为 false"
+    },
+    highlightFirstItem: {
+      type: "switch",
+      label: "是否默认突出显示远程搜索建议中的第一项"
+    }
+  },
+  attrsData: {},
+  attrsDefaultData: {
+    valueKey: "value",
+    debounce: 300,
+    placement: "bottom-end",
+    triggerOnFocus: false,
+    selectWhenUnmatched: false,
+    hideLoading: false,
+    popperAppendToBody: true,
+    highlightFirstItem: false
+  },
+  common: {},
+  commonData: {},
+  commonDefaultData: {}
+};
+export default config;

+ 64 - 0
src/config/comps/bmap.ts

@@ -0,0 +1,64 @@
+import { Config } from "@/types/config";
+
+const config: Config = {
+  url: "https://github.com/dream2023/vue-ele-form-bmap",
+  attrs: {
+    ak: {
+      type: "input",
+      label: "密钥",
+      required: true
+    },
+    zoom: {
+      type: "number",
+      label: "缩放比",
+      attrs: {
+        min: 1
+      }
+    },
+    isScrollWheelZoom: {
+      type: "switch",
+      label: "滚轮缩放大小"
+    },
+    mapHeight: {
+      type: "number",
+      label: "地图高度",
+      attrs: {
+        min: 0,
+        step: 50
+      }
+    },
+    isShowNavigation: {
+      type: "switch",
+      label: "是否显示缩略控件"
+    },
+    isShowGeolocation: {
+      type: "switch",
+      label: "是否显示自动定位控件"
+    },
+    placeholder: {
+      type: "input",
+      label: "搜索占位符"
+    }
+  },
+  attrsData: {
+    ak: "9YLHZRPvUNLhi34Oh2ojqeGSpzCf1rVG"
+  },
+  attrsDefaultData: {
+    zoom: 12,
+    isScrollWheelZoom: false,
+    mapHeight: 400,
+    isShowGeolocation: true
+  },
+  common: {
+    default: {
+      type: "json-editor",
+      label: "默认值"
+    }
+  },
+  commonData: {},
+  commonDefaultData: {
+    default: null
+  }
+};
+
+export default config;

+ 74 - 0
src/config/comps/button.ts

@@ -0,0 +1,74 @@
+import { Config } from "@/types/config";
+
+const config: Config = {
+  url: "https://element.eleme.cn/#/zh-CN/component/button",
+  attrs: {
+    size: {
+      type: "select",
+      label: "尺寸",
+      options: [{ text: "默认", value: null }, "medium", "small", "mini"]
+    },
+    type: {
+      type: "select",
+      label: "类型",
+      options: [
+        { text: "默认", value: null },
+        "primary",
+        "success",
+        "warning",
+        "danger",
+        "info",
+        "text"
+      ]
+    },
+    plain: {
+      type: "switch",
+      label: "是否朴素按钮"
+    },
+    round: {
+      type: "switch",
+      label: "是否圆角按钮"
+    },
+    circle: {
+      type: "switch",
+      label: "是否圆形按钮"
+    },
+    icon: {
+      type: "input",
+      label: "图标类名",
+      attrs: {
+        clearable: true
+      }
+    },
+    autofocus: {
+      type: "switch",
+      label: "是否默认聚焦"
+    },
+    nativeType: {
+      type: "radio",
+      label: "原生 type 属性",
+      options: ["button", "submit", "reset"]
+    }
+  },
+  attrsData: {},
+  attrsDefaultData: {
+    size: null,
+    type: null,
+    plain: false,
+    round: false,
+    circle: false,
+    autofocus: false,
+    nativeType: "button"
+  },
+  common: {
+    default: {
+      type: "input",
+      label: "按钮文本"
+    }
+  },
+  commonData: {
+    default: "按钮文本"
+  },
+  commonDefaultData: {}
+};
+export default config;

+ 359 - 0
src/config/comps/cascader-panel.ts

@@ -0,0 +1,359 @@
+import { Config } from "@/types/config";
+
+const config: Config = {
+  url: "https://element.eleme.cn/#/zh-CN/component/cascader#ji-lian-mian-ban",
+  attrs: {
+    props: {
+      children: {
+        expandTrigger: {
+          options: ["click", "hover"],
+          label: "次级菜单的展开方式",
+          type: "radio",
+          default: "click"
+        },
+        multiple: {
+          label: "是否多选",
+          type: "switch"
+        },
+        checkStrictly: {
+          label: "是否严格的遵守父子节点不互相关联",
+          type: "switch"
+        },
+        emitPath: {
+          label:
+            "在选中节点改变时,是否返回由该节点所在的各级菜单的值所组成的数组,若设置 false,则只返回该节点的值",
+          type: "switch",
+          default: true
+        },
+        lazy: {
+          label: "是否动态加载子节点,需与 lazyLoad 方法结合使用",
+          type: "switch"
+        },
+        value: {
+          label: "指定选项的值为选项对象的某个属性值",
+          type: "input",
+          default: "value"
+        },
+        label: {
+          label: "指定选项标签为选项对象的某个属性值",
+          type: "input",
+          default: "label"
+        },
+        children: {
+          label: "指定选项的子选项为选项对象的某个属性值",
+          type: "input",
+          default: "children"
+        },
+        disabled: {
+          label: "指定选项的禁用为选项对象的某个属性值",
+          type: "input",
+          default: "disabled"
+        },
+        leaf: {
+          label: "指定选项的叶子节点的标志位为选项对象的某个属性值",
+          type: "input",
+          default: "leaf"
+        }
+      }
+    }
+  },
+  attrsData: {},
+  attrsDefaultData: {
+    props: {
+      expandTrigger: "click",
+      emitPath: true,
+      value: "value",
+      label: "label",
+      children: "children",
+      disabled: "disabled",
+      leaf: "leaf"
+    }
+  },
+  common: {
+    default: {
+      type: "cascader",
+      label: "默认值",
+      isReloadOptions: true,
+      options: data => data.options
+    },
+    options: {
+      type: "json-editor",
+      label: "选项",
+      tip:
+        'options支持`API接口`、`数组`、`函数`、`Promise`等, 具体看<a target="_blank" href="https://www.yuque.com/chaojie-vjiel/vbwzgu/rgenav" class="el-link el-link--primary">文档</a>'
+    }
+  },
+  commonData: {
+    options: [
+      {
+        value: "zhinan",
+        label: "指南",
+        children: [
+          {
+            value: "shejiyuanze",
+            label: "设计原则",
+            children: [
+              {
+                value: "yizhi",
+                label: "一致"
+              },
+              {
+                value: "fankui",
+                label: "反馈"
+              },
+              {
+                value: "xiaolv",
+                label: "效率"
+              },
+              {
+                value: "kekong",
+                label: "可控"
+              }
+            ]
+          },
+          {
+            value: "daohang",
+            label: "导航",
+            children: [
+              {
+                value: "cexiangdaohang",
+                label: "侧向导航"
+              },
+              {
+                value: "dingbudaohang",
+                label: "顶部导航"
+              }
+            ]
+          }
+        ]
+      },
+      {
+        value: "zujian",
+        label: "组件",
+        children: [
+          {
+            value: "basic",
+            label: "Basic",
+            children: [
+              {
+                value: "layout",
+                label: "Layout 布局"
+              },
+              {
+                value: "color",
+                label: "Color 色彩"
+              },
+              {
+                value: "typography",
+                label: "Typography 字体"
+              },
+              {
+                value: "icon",
+                label: "Icon 图标"
+              },
+              {
+                value: "button",
+                label: "Button 按钮"
+              }
+            ]
+          },
+          {
+            value: "form",
+            label: "Form",
+            children: [
+              {
+                value: "radio",
+                label: "Radio 单选框"
+              },
+              {
+                value: "checkbox",
+                label: "Checkbox 多选框"
+              },
+              {
+                value: "input",
+                label: "Input 输入框"
+              },
+              {
+                value: "input-number",
+                label: "InputNumber 计数器"
+              },
+              {
+                value: "select",
+                label: "Select 选择器"
+              },
+              {
+                value: "cascader",
+                label: "Cascader 级联选择器"
+              },
+              {
+                value: "switch",
+                label: "Switch 开关"
+              },
+              {
+                value: "slider",
+                label: "Slider 滑块"
+              },
+              {
+                value: "time-picker",
+                label: "TimePicker 时间选择器"
+              },
+              {
+                value: "date-picker",
+                label: "DatePicker 日期选择器"
+              },
+              {
+                value: "datetime-picker",
+                label: "DateTimePicker 日期时间选择器"
+              },
+              {
+                value: "upload",
+                label: "Upload 上传"
+              },
+              {
+                value: "rate",
+                label: "Rate 评分"
+              },
+              {
+                value: "form",
+                label: "Form 表单"
+              }
+            ]
+          },
+          {
+            value: "data",
+            label: "Data",
+            children: [
+              {
+                value: "table",
+                label: "Table 表格"
+              },
+              {
+                value: "tag",
+                label: "Tag 标签"
+              },
+              {
+                value: "progress",
+                label: "Progress 进度条"
+              },
+              {
+                value: "tree",
+                label: "Tree 树形控件"
+              },
+              {
+                value: "pagination",
+                label: "Pagination 分页"
+              },
+              {
+                value: "badge",
+                label: "Badge 标记"
+              }
+            ]
+          },
+          {
+            value: "notice",
+            label: "Notice",
+            children: [
+              {
+                value: "alert",
+                label: "Alert 警告"
+              },
+              {
+                value: "loading",
+                label: "Loading 加载"
+              },
+              {
+                value: "message",
+                label: "Message 消息提示"
+              },
+              {
+                value: "message-box",
+                label: "MessageBox 弹框"
+              },
+              {
+                value: "notification",
+                label: "Notification 通知"
+              }
+            ]
+          },
+          {
+            value: "navigation",
+            label: "Navigation",
+            children: [
+              {
+                value: "menu",
+                label: "NavMenu 导航菜单"
+              },
+              {
+                value: "tabs",
+                label: "Tabs 标签页"
+              },
+              {
+                value: "breadcrumb",
+                label: "Breadcrumb 面包屑"
+              },
+              {
+                value: "dropdown",
+                label: "Dropdown 下拉菜单"
+              },
+              {
+                value: "steps",
+                label: "Steps 步骤条"
+              }
+            ]
+          },
+          {
+            value: "others",
+            label: "Others",
+            children: [
+              {
+                value: "dialog",
+                label: "Dialog 对话框"
+              },
+              {
+                value: "tooltip",
+                label: "Tooltip 文字提示"
+              },
+              {
+                value: "popover",
+                label: "Popover 弹出框"
+              },
+              {
+                value: "card",
+                label: "Card 卡片"
+              },
+              {
+                value: "carousel",
+                label: "Carousel 走马灯"
+              },
+              {
+                value: "collapse",
+                label: "Collapse 折叠面板"
+              }
+            ]
+          }
+        ]
+      },
+      {
+        value: "ziyuan",
+        label: "资源",
+        children: [
+          {
+            value: "axure",
+            label: "Axure Components"
+          },
+          {
+            value: "sketch",
+            label: "Sketch Templates"
+          },
+          {
+            value: "jiaohu",
+            label: "组件交互文档"
+          }
+        ]
+      }
+    ]
+  },
+  commonDefaultData: {}
+};
+
+export default config;

+ 411 - 0
src/config/comps/cascader.ts

@@ -0,0 +1,411 @@
+import { Config } from "@/types/config";
+
+const config: Config = {
+  url: "https://element.eleme.cn/#/zh-CN/component/cascader",
+  attrs: {
+    showAllLevels: {
+      type: "switch",
+      label: "输入框中是否显示选中值的完整路径"
+    },
+    collapseTags: {
+      type: "switch",
+      label: "多选模式下是否折叠Tag"
+    },
+    clearable: {
+      type: "switch",
+      label: "是否可清空"
+    },
+    placeholder: {
+      type: "input",
+      label: "输入框占位文本",
+      attrs: {
+        clearable: true
+      }
+    },
+    size: {
+      type: "select",
+      label: "输入框尺寸",
+      options: [{ text: "默认", value: null }, "medium", "small", "mini"]
+    },
+    separator: {
+      type: "input",
+      label: "选项分隔符",
+      vif: data => data.showAllLevels
+    },
+    filterable: {
+      type: "switch",
+      label: "是否可搜索选项"
+    },
+    debounce: {
+      type: "number",
+      label: "搜索关键词输入的去抖延迟,毫秒",
+      attrs: {
+        min: 0,
+        step: 100
+      }
+    },
+    props: {
+      children: {
+        expandTrigger: {
+          options: ["click", "hover"],
+          label: "次级菜单的展开方式",
+          type: "radio",
+          default: "click"
+        },
+        multiple: {
+          label: "是否多选",
+          type: "switch"
+        },
+        checkStrictly: {
+          label: "是否严格的遵守父子节点不互相关联",
+          type: "switch"
+        },
+        emitPath: {
+          label:
+            "在选中节点改变时,是否返回由该节点所在的各级菜单的值所组成的数组,若设置 false,则只返回该节点的值",
+          type: "switch",
+          default: true
+        },
+        lazy: {
+          label: "是否动态加载子节点,需与 lazyLoad 方法结合使用",
+          type: "switch"
+        },
+        value: {
+          label: "指定选项的值为选项对象的某个属性值",
+          type: "input",
+          default: "value"
+        },
+        label: {
+          label: "指定选项标签为选项对象的某个属性值",
+          type: "input",
+          default: "label"
+        },
+        children: {
+          label: "指定选项的子选项为选项对象的某个属性值",
+          type: "input",
+          default: "children"
+        },
+        disabled: {
+          label: "指定选项的禁用为选项对象的某个属性值",
+          type: "input",
+          default: "disabled"
+        },
+        leaf: {
+          label: "指定选项的叶子节点的标志位为选项对象的某个属性值",
+          type: "input",
+          default: "leaf"
+        }
+      }
+    },
+    popperClass: {
+      type: "input",
+      label: "自定义浮层类名"
+    }
+  },
+  attrsData: {},
+  attrsDefaultData: {
+    size: null,
+    clearable: false,
+    showAllLevels: true,
+    collapseTags: false,
+    separator: "/",
+    filterable: false,
+    debounce: 300,
+    props: {
+      expandTrigger: "click",
+      emitPath: true,
+      value: "value",
+      label: "label",
+      children: "children",
+      disabled: "disabled",
+      leaf: "leaf"
+    }
+  },
+  common: {
+    default: {
+      type: "cascader",
+      label: "默认值",
+      isReloadOptions: true,
+      options: data => data.options
+    },
+    options: {
+      type: "json-editor",
+      label: "选项",
+      tip:
+        'options支持`API接口`、`数组`、`函数`、`Promise`等, 具体看<a target="_blank" href="https://www.yuque.com/chaojie-vjiel/vbwzgu/rgenav" class="el-link el-link--primary">文档</a>'
+    }
+  },
+  commonData: {
+    options: [
+      {
+        value: "zhinan",
+        label: "指南",
+        children: [
+          {
+            value: "shejiyuanze",
+            label: "设计原则",
+            children: [
+              {
+                value: "yizhi",
+                label: "一致"
+              },
+              {
+                value: "fankui",
+                label: "反馈"
+              },
+              {
+                value: "xiaolv",
+                label: "效率"
+              },
+              {
+                value: "kekong",
+                label: "可控"
+              }
+            ]
+          },
+          {
+            value: "daohang",
+            label: "导航",
+            children: [
+              {
+                value: "cexiangdaohang",
+                label: "侧向导航"
+              },
+              {
+                value: "dingbudaohang",
+                label: "顶部导航"
+              }
+            ]
+          }
+        ]
+      },
+      {
+        value: "zujian",
+        label: "组件",
+        children: [
+          {
+            value: "basic",
+            label: "Basic",
+            children: [
+              {
+                value: "layout",
+                label: "Layout 布局"
+              },
+              {
+                value: "color",
+                label: "Color 色彩"
+              },
+              {
+                value: "typography",
+                label: "Typography 字体"
+              },
+              {
+                value: "icon",
+                label: "Icon 图标"
+              },
+              {
+                value: "button",
+                label: "Button 按钮"
+              }
+            ]
+          },
+          {
+            value: "form",
+            label: "Form",
+            children: [
+              {
+                value: "radio",
+                label: "Radio 单选框"
+              },
+              {
+                value: "checkbox",
+                label: "Checkbox 多选框"
+              },
+              {
+                value: "input",
+                label: "Input 输入框"
+              },
+              {
+                value: "input-number",
+                label: "InputNumber 计数器"
+              },
+              {
+                value: "select",
+                label: "Select 选择器"
+              },
+              {
+                value: "cascader",
+                label: "Cascader 级联选择器"
+              },
+              {
+                value: "switch",
+                label: "Switch 开关"
+              },
+              {
+                value: "slider",
+                label: "Slider 滑块"
+              },
+              {
+                value: "time-picker",
+                label: "TimePicker 时间选择器"
+              },
+              {
+                value: "date-picker",
+                label: "DatePicker 日期选择器"
+              },
+              {
+                value: "datetime-picker",
+                label: "DateTimePicker 日期时间选择器"
+              },
+              {
+                value: "upload",
+                label: "Upload 上传"
+              },
+              {
+                value: "rate",
+                label: "Rate 评分"
+              },
+              {
+                value: "form",
+                label: "Form 表单"
+              }
+            ]
+          },
+          {
+            value: "data",
+            label: "Data",
+            children: [
+              {
+                value: "table",
+                label: "Table 表格"
+              },
+              {
+                value: "tag",
+                label: "Tag 标签"
+              },
+              {
+                value: "progress",
+                label: "Progress 进度条"
+              },
+              {
+                value: "tree",
+                label: "Tree 树形控件"
+              },
+              {
+                value: "pagination",
+                label: "Pagination 分页"
+              },
+              {
+                value: "badge",
+                label: "Badge 标记"
+              }
+            ]
+          },
+          {
+            value: "notice",
+            label: "Notice",
+            children: [
+              {
+                value: "alert",
+                label: "Alert 警告"
+              },
+              {
+                value: "loading",
+                label: "Loading 加载"
+              },
+              {
+                value: "message",
+                label: "Message 消息提示"
+              },
+              {
+                value: "message-box",
+                label: "MessageBox 弹框"
+              },
+              {
+                value: "notification",
+                label: "Notification 通知"
+              }
+            ]
+          },
+          {
+            value: "navigation",
+            label: "Navigation",
+            children: [
+              {
+                value: "menu",
+                label: "NavMenu 导航菜单"
+              },
+              {
+                value: "tabs",
+                label: "Tabs 标签页"
+              },
+              {
+                value: "breadcrumb",
+                label: "Breadcrumb 面包屑"
+              },
+              {
+                value: "dropdown",
+                label: "Dropdown 下拉菜单"
+              },
+              {
+                value: "steps",
+                label: "Steps 步骤条"
+              }
+            ]
+          },
+          {
+            value: "others",
+            label: "Others",
+            children: [
+              {
+                value: "dialog",
+                label: "Dialog 对话框"
+              },
+              {
+                value: "tooltip",
+                label: "Tooltip 文字提示"
+              },
+              {
+                value: "popover",
+                label: "Popover 弹出框"
+              },
+              {
+                value: "card",
+                label: "Card 卡片"
+              },
+              {
+                value: "carousel",
+                label: "Carousel 走马灯"
+              },
+              {
+                value: "collapse",
+                label: "Collapse 折叠面板"
+              }
+            ]
+          }
+        ]
+      },
+      {
+        value: "ziyuan",
+        label: "资源",
+        children: [
+          {
+            value: "axure",
+            label: "Axure Components"
+          },
+          {
+            value: "sketch",
+            label: "Sketch Templates"
+          },
+          {
+            value: "jiaohu",
+            label: "组件交互文档"
+          }
+        ]
+      }
+    ]
+  },
+  commonDefaultData: {}
+};
+
+export default config;

+ 68 - 0
src/config/comps/checkbox-button.ts

@@ -0,0 +1,68 @@
+import { Config } from "@/types/config";
+
+const config: Config = {
+  url: "https://element.eleme.cn/#/zh-CN/component/checkbox#an-niu-yang-shi",
+  attrs: {
+    textColor: {
+      type: "color",
+      label: "Checkbox 激活时的文本颜色"
+    },
+    fill: {
+      type: "color",
+      label: "Checkbox 激活时的填充色和边框色"
+    },
+    size: {
+      type: "select",
+      label: "多选框组尺寸",
+      options: [{ text: "默认", value: null }, "medium", "small", "mini"]
+    },
+    min: {
+      type: "input",
+      label: "可被勾选的 Checkbox 的最小数量",
+      attrs: {
+        type: "number",
+        min: 0
+      }
+    },
+    max: {
+      type: "input",
+      label: "可被勾选的 Checkbox 的最大数量",
+      attrs: {
+        type: "number",
+        min: 0
+      }
+    }
+  },
+  attrsData: {},
+  attrsDefaultData: {
+    size: null,
+    min: 0,
+    max: 0,
+    textColor: "#ffffff",
+    fill: "#409EFF"
+  },
+  common: {
+    default: {
+      type: "checkbox",
+      label: "默认值",
+      isReloadOptions: true,
+      options: data => data.options
+    },
+    options: {
+      type: "json-editor",
+      label: "选项",
+      tip:
+        'options支持`API接口`、`数组`、`函数`、`Promise`等, 具体看<a target="_blank" href="https://www.yuque.com/chaojie-vjiel/vbwzgu/rgenav" class="el-link el-link--primary">文档</a>'
+    }
+  },
+  commonData: {
+    options: [
+      { text: "选项1", value: 1 },
+      { text: "选项2", value: 2 },
+      { text: "选项3", value: 3 }
+    ]
+  },
+  commonDefaultData: {}
+};
+
+export default config;

+ 61 - 0
src/config/comps/checkbox.ts

@@ -0,0 +1,61 @@
+import { Config } from "@/types/config";
+
+const config: Config = {
+  url: "https://element.eleme.cn/#/zh-CN/component/checkbox",
+  attrs: {
+    size: {
+      type: "select",
+      label: "多选框组尺寸",
+      options: [{ text: "默认", value: null }, "medium", "small", "mini"]
+    },
+    min: {
+      type: "number",
+      label: "可被勾选的 Checkbox 的最小数量",
+      attrs: {
+        min: 0
+      }
+    },
+    max: {
+      type: "number",
+      label: "可被勾选的 Checkbox 的最大数量",
+      attrs: {
+        min: 0
+      }
+    }
+  },
+  attrsData: {},
+  attrsDefaultData: {
+    size: null,
+    min: 0,
+    max: 0
+  },
+  common: {
+    default: {
+      type: "checkbox",
+      label: "默认值",
+      isReloadOptions: true,
+      options: data => data.options
+    },
+    options: {
+      type: "json-editor",
+      label: "选项",
+      tip:
+        'options支持`API接口`、`数组`、`函数`、`Promise`等, 具体看<a target="_blank" href="https://www.yuque.com/chaojie-vjiel/vbwzgu/rgenav" class="el-link el-link--primary">文档</a>'
+    }
+  },
+  commonData: {
+    options: [
+      { text: "选项1", value: 1 },
+      { text: "选项2", value: 2 },
+      { text: "选项3", value: 3 }
+    ]
+  },
+  commonDefaultData: {
+    prop: {
+      text: "text",
+      value: "value"
+    }
+  }
+};
+
+export default config;

+ 25 - 0
src/config/comps/codemirror.ts

@@ -0,0 +1,25 @@
+import { Config } from "@/types/config";
+
+const config: Config = {
+  url: "https://github.com/dream2023/vue-ele-form-codemirror",
+  attrs: {
+    options: {
+      type: "json-editor",
+      label: "配置"
+    }
+  },
+  attrsData: {},
+  attrsDefaultData: {
+    options: {}
+  },
+  common: {
+    default: {
+      type: "textarea",
+      label: "默认值"
+    }
+  },
+  commonData: {},
+  commonDefaultData: {}
+};
+
+export default config;

+ 46 - 0
src/config/comps/color.ts

@@ -0,0 +1,46 @@
+import { Config } from "@/types/config";
+
+const config: Config = {
+  url: "https://element.eleme.cn/#/zh-CN/component/color-picker",
+  attrs: {
+    popperClass: {
+      type: "input",
+      label: "ColorPicker 下拉框的类名"
+    },
+    size: {
+      type: "select",
+      label: "输入框尺寸",
+      options: [{ text: "默认", value: null }, "medium", "small", "mini"]
+    },
+    showAlpha: {
+      type: "switch",
+      label: "是否支持透明度选择"
+    },
+    colorFormat: {
+      type: "select",
+      label: "写入 v-model 的颜色的格式",
+      options: ["hsl", "hsv", "hex", "rgb"]
+    },
+    predefine: {
+      type: "json-editor",
+      label: "预定义颜色"
+    }
+  },
+  attrsData: {},
+  attrsDefaultData: {
+    size: null,
+    showAlpha: false,
+    colorFormat: "hex",
+    predefine: []
+  },
+  common: {
+    default: {
+      type: "color",
+      label: "默认值"
+    }
+  },
+  commonData: {},
+  commonDefaultData: {}
+};
+
+export default config;

+ 69 - 0
src/config/comps/date.ts

@@ -0,0 +1,69 @@
+import { Config } from "@/types/config";
+
+const config: Config = {
+  url: "https://element.eleme.cn/#/zh-CN/component/date-picker",
+  attrs: {
+    type: {
+      type: "select",
+      label: "显示类型",
+      options: ["year", "month", "date", "week", "datetime"]
+    },
+    placeholder: {
+      type: "input",
+      label: "占位内容"
+    },
+    format: {
+      type: "input",
+      label: "显示在输入框中的格式"
+    },
+    clearable: {
+      type: "switch",
+      label: "是否显示清除按钮"
+    },
+    editable: {
+      type: "switch",
+      label: "文本框可输入"
+    },
+    size: {
+      type: "select",
+      label: "输入框尺寸",
+      options: [{ text: "默认", value: null }, "medium", "small", "mini"]
+    },
+    popperClass: {
+      type: "input",
+      label: "DatePicker 下拉框的类名"
+    },
+    prefixIcon: {
+      type: "input",
+      label: "自定义头部图标的类名"
+    },
+    clearIcon: {
+      type: "input",
+      label: "自定义清空图标的类名"
+    },
+    readonly: {
+      type: "switch",
+      label: "完全只读"
+    }
+  },
+  attrsData: {},
+  attrsDefaultData: {
+    type: "date",
+    readonly: false,
+    editable: true,
+    clearable: true,
+    size: null,
+    prefixIcon: "el-icon-date",
+    clearIcon: "el-icon-circle-close"
+  },
+  common: {
+    default: {
+      type: "date",
+      label: "默认值"
+    }
+  },
+  commonData: {},
+  commonDefaultData: {}
+};
+
+export default config;

+ 102 - 0
src/config/comps/daterange.ts

@@ -0,0 +1,102 @@
+import { Config } from "@/types/config";
+
+const config: Config = {
+  url:
+    "https://element.eleme.cn/#/zh-CN/component/date-picker#xuan-ze-ri-qi-fan-wei",
+  attrs: {
+    type: {
+      type: "select",
+      label: "显示类型",
+      options: ["datetimerange", "daterange", "monthrange"]
+    },
+    startPlaceholder: {
+      type: "input",
+      label: "范围选择时开始日期的占位内容"
+    },
+    endPlaceholder: {
+      type: "input",
+      label: "范围选择时结束日期的占位内容"
+    },
+    rangeSeparator: {
+      type: "input",
+      label: "选择范围时的分隔符"
+    },
+    format: {
+      type: "input",
+      label: "显示在输入框中的格式"
+    },
+    valueFormat: {
+      type: "input",
+      label: "绑定值的格式, 不指定则绑定值为 Date 对象",
+      attrs: {
+        clearable: true
+      }
+    },
+    clearable: {
+      type: "switch",
+      label: "是否显示清除按钮"
+    },
+    editable: {
+      type: "switch",
+      label: "文本框可输入"
+    },
+    size: {
+      type: "select",
+      label: "输入框尺寸",
+      options: [{ text: "默认", value: null }, "medium", "small", "mini"]
+    },
+    unlinkPanels: {
+      type: "switch",
+      label: "在范围选择器里取消两个日期面板之间的联动"
+    },
+    align: {
+      type: "select",
+      label: "对齐方式",
+      options: ["left", "center", "right"]
+    },
+    popperClass: {
+      type: "input",
+      label: "DatePicker 下拉框的类名"
+    },
+    prefixIcon: {
+      type: "input",
+      label: "自定义头部图标的类名"
+    },
+    clearIcon: {
+      type: "input",
+      label: "自定义清空图标的类名"
+    },
+    readonly: {
+      type: "switch",
+      label: "完全只读"
+    },
+    validateEvent: {
+      type: "switch",
+      label: "输入时是否触发表单的校验"
+    }
+  },
+  attrsData: {},
+  attrsDefaultData: {
+    type: "daterange",
+    validateEvent: false,
+    unlinkPanels: false,
+    readonly: false,
+    editable: true,
+    clearable: true,
+    align: "left",
+    size: null,
+    rangeSeparator: "-",
+    prefixIcon: "el-icon-date",
+    clearIcon: "el-icon-circle-close"
+  },
+  common: {
+    default: {
+      type: "daterange",
+      label: "默认值"
+    }
+  },
+  commonData: {},
+  commonDefaultData: {}
+};
+
+export default config;

+ 63 - 0
src/config/comps/dates.ts

@@ -0,0 +1,63 @@
+import { Config } from "@/types/config";
+
+const config: Config = {
+  url: "https://element.eleme.cn/#/zh-CN/component/date-picker",
+  attrs: {
+    placeholder: {
+      type: "input",
+      label: "占位内容"
+    },
+    format: {
+      type: "input",
+      label: "显示在输入框中的格式"
+    },
+    clearable: {
+      type: "switch",
+      label: "是否显示清除按钮"
+    },
+    editable: {
+      type: "switch",
+      label: "文本框可输入"
+    },
+    size: {
+      type: "select",
+      label: "输入框尺寸",
+      options: [{ text: "默认", value: null }, "medium", "small", "mini"]
+    },
+    popperClass: {
+      type: "input",
+      label: "DatePicker 下拉框的类名"
+    },
+    prefixIcon: {
+      type: "input",
+      label: "自定义头部图标的类名"
+    },
+    clearIcon: {
+      type: "input",
+      label: "自定义清空图标的类名"
+    },
+    readonly: {
+      type: "switch",
+      label: "完全只读"
+    }
+  },
+  attrsData: {},
+  attrsDefaultData: {
+    readonly: false,
+    editable: true,
+    clearable: true,
+    size: null,
+    prefixIcon: "el-icon-date",
+    clearIcon: "el-icon-circle-close"
+  },
+  common: {
+    default: {
+      type: "dates",
+      label: "默认值"
+    }
+  },
+  commonData: {},
+  commonDefaultData: {}
+};
+
+export default config;

+ 68 - 0
src/config/comps/datetime.ts

@@ -0,0 +1,68 @@
+import { Config } from "@/types/config";
+
+const config: Config = {
+  url: "https://element.eleme.cn/#/zh-CN/component/datetime-picker",
+  attrs: {
+    placeholder: {
+      type: "input",
+      label: "占位内容"
+    },
+    format: {
+      type: "input",
+      label: "显示在输入框中的格式"
+    },
+    clearable: {
+      type: "switch",
+      label: "是否显示清除按钮"
+    },
+    timeArrowControl: {
+      type: "switch",
+      label: "是否使用箭头进行时间选择"
+    },
+    editable: {
+      type: "switch",
+      label: "文本框可输入"
+    },
+    size: {
+      type: "select",
+      label: "输入框尺寸",
+      options: [{ text: "默认", value: null }, "medium", "small", "mini"]
+    },
+    popperClass: {
+      type: "input",
+      label: "DateTimePicker 下拉框的类名"
+    },
+    prefixIcon: {
+      type: "input",
+      label: "自定义头部图标的类名"
+    },
+    clearIcon: {
+      type: "input",
+      label: "自定义清空图标的类名"
+    },
+    readonly: {
+      type: "switch",
+      label: "完全只读"
+    }
+  },
+  attrsData: {},
+  attrsDefaultData: {
+    timeArrowControl: false,
+    readonly: false,
+    editable: true,
+    clearable: true,
+    size: null,
+    prefixIcon: "el-icon-date",
+    clearIcon: "el-icon-circle-close"
+  },
+  common: {
+    default: {
+      type: "datetime",
+      label: "默认值"
+    }
+  },
+  commonData: {},
+  commonDefaultData: {}
+};
+
+export default config;

+ 101 - 0
src/config/comps/datetimerange.ts

@@ -0,0 +1,101 @@
+import { Config } from "@/types/config";
+
+const config: Config = {
+  url:
+    "https://element.eleme.cn/#/zh-CN/component/datetime-picker#ri-qi-he-shi-jian-fan-wei",
+  attrs: {
+    startPlaceholder: {
+      type: "input",
+      label: "范围选择时开始日期的占位内容"
+    },
+    endPlaceholder: {
+      type: "input",
+      label: "范围选择时结束日期的占位内容"
+    },
+    rangeSeparator: {
+      type: "input",
+      label: "选择范围时的分隔符"
+    },
+    timeArrowControl: {
+      type: "switch",
+      label: "是否使用箭头进行时间选择"
+    },
+    format: {
+      type: "input",
+      label: "显示在输入框中的格式"
+    },
+    valueFormat: {
+      type: "input",
+      label: "绑定值的格式, 不指定则绑定值为 Date 对象",
+      attrs: {
+        clearable: true
+      }
+    },
+    clearable: {
+      type: "switch",
+      label: "是否显示清除按钮"
+    },
+    editable: {
+      type: "switch",
+      label: "文本框可输入"
+    },
+    size: {
+      type: "select",
+      label: "输入框尺寸",
+      options: [{ text: "默认", value: null }, "medium", "small", "mini"]
+    },
+    unlinkPanels: {
+      type: "switch",
+      label: "在范围选择器里取消两个日期面板之间的联动"
+    },
+    align: {
+      type: "select",
+      label: "对齐方式",
+      options: ["left", "center", "right"]
+    },
+    popperClass: {
+      type: "input",
+      label: "DateTimePicker 下拉框的类名"
+    },
+    prefixIcon: {
+      type: "input",
+      label: "自定义头部图标的类名"
+    },
+    clearIcon: {
+      type: "input",
+      label: "自定义清空图标的类名"
+    },
+    readonly: {
+      type: "switch",
+      label: "完全只读"
+    },
+    validateEvent: {
+      type: "switch",
+      label: "输入时是否触发表单的校验"
+    }
+  },
+  attrsData: {},
+  attrsDefaultData: {
+    timeArrowControl: false,
+    validateEvent: false,
+    unlinkPanels: false,
+    readonly: false,
+    editable: true,
+    clearable: true,
+    align: "left",
+    size: null,
+    rangeSeparator: "-",
+    prefixIcon: "el-icon-date",
+    clearIcon: "el-icon-circle-close"
+  },
+  common: {
+    default: {
+      type: "datetimerange",
+      label: "默认值"
+    }
+  },
+  commonData: {},
+  commonDefaultData: {}
+};
+
+export default config;

+ 40 - 0
src/config/comps/dynamic.ts

@@ -0,0 +1,40 @@
+import { Config } from "@/types/config";
+
+const config: Config = {
+  url: "https://github.com/dream2023/vue-ele-form-dynamic",
+  attrs: {
+    columns: {
+      type: "json-editor",
+      label: "列",
+      required: true,
+      tip: "可以是数组或者对象"
+    },
+    rules: {
+      type: "json-editor",
+      label: "校检规则",
+      tip:
+        "当columns为数组时, 则rules必须为对象类型, 指定校检字段, 当columns为对象时, 则rules为数组类型"
+    },
+    delimiter: {
+      type: "input",
+      label: "分割符"
+    }
+  },
+  attrsData: {},
+  attrsDefaultData: {
+    rules: [],
+    delimiter: "-"
+  },
+  common: {
+    default: {
+      type: "json-editor",
+      label: "默认值"
+    }
+  },
+  commonData: {
+    default: []
+  },
+  commonDefaultData: {}
+};
+
+export default config;

+ 77 - 0
src/config/comps/gallery.ts

@@ -0,0 +1,77 @@
+import { Config } from "@/types/config";
+
+const config: Config = {
+  url: "https://github.com/dream2023/vue-ele-form-gallery",
+  attrs: {
+    type: {
+      type: "select",
+      label: "类型",
+      options: ["image", "video", "iframe"]
+    },
+    width: {
+      type: "number",
+      label: "缩略图宽度",
+      attrs: {
+        min: 0,
+        step: 10
+      }
+    },
+    height: {
+      type: "number",
+      label: "缩略图高度",
+      attrs: {
+        min: 0,
+        step: 10
+      }
+    },
+    lazy: {
+      type: "switch",
+      label: "缩略图是否懒加载"
+    },
+    thumbSuffix: {
+      type: "input",
+      label: "缩略图后缀"
+    },
+    thumbStyle: {
+      type: "json-editor",
+      label: "缩略图样式"
+    },
+    carouselAttrs: {
+      type: "json-editor",
+      label: "轮播图属性"
+    }
+  },
+  attrsData: {},
+  attrsDefaultData: {
+    type: "image",
+    size: 150,
+    width: 150,
+    height: 150,
+    lazy: false,
+    thumbStyle: {},
+    carouselAttrs: {}
+  },
+  common: {
+    default: {
+      type: "dynamic",
+      label: "默认值",
+      attrs: {
+        columns: {
+          type: "el-input",
+          attrs: {
+            placeholder: "图片链接"
+          }
+        }
+      }
+    }
+  },
+  commonData: {
+    default: [
+      "https://cube.elemecdn.com/6/94/4d3ea53c084bad6931a56d5158a48jpeg.jpeg",
+      "https://fuss10.elemecdn.com/e/5d/4a731a90594a4af544c0c25941171jpeg.jpeg"
+    ]
+  },
+  commonDefaultData: {}
+};
+
+export default config;

+ 123 - 0
src/config/comps/image-uploader.ts

@@ -0,0 +1,123 @@
+import { Config } from "@/types/config";
+
+const config: Config = {
+  url: "https://github.com/dream2023/vue-ele-form-image-uploader",
+  attrs: {
+    action: {
+      type: "input",
+      label: "上传地址",
+      required: true
+    },
+    name: {
+      type: "input",
+      label: "上传的文件字段名",
+      required: true
+    },
+    corp: {
+      type: "switch",
+      label: "是否剪裁"
+    },
+    cropHeight: {
+      type: "number",
+      label: "裁剪高度",
+      vif: data => data.corp,
+      attrs: {
+        min: 0
+      }
+    },
+    cropWidth: {
+      type: "number",
+      label: "裁剪宽度",
+      vif: data => data.corp,
+      attrs: {
+        min: 0
+      }
+    },
+    multiple: {
+      type: "switch",
+      label: "是否支持多选文件"
+    },
+    limit: {
+      type: "input",
+      label: "文件个数显示",
+      vif: data => data.multiple,
+      attrs: {
+        min: 0,
+        type: "number"
+      }
+    },
+    size: {
+      type: "number",
+      label: "图片显示大小",
+      attrs: {
+        min: 0,
+        step: 10
+      }
+    },
+    fileSize: {
+      type: "number",
+      label: "文件大小限制(MB)",
+      attrs: {
+        min: 0
+      }
+    },
+    lazy: {
+      type: "switch",
+      label: "图片懒加载"
+    },
+    drag: {
+      type: "switch",
+      label: "是否启用拖拽上传"
+    },
+    withCredentials: {
+      type: "switch",
+      label: "支持发送 cookie 凭证信息"
+    },
+    isShowTip: {
+      type: "switch",
+      label: "是否显示提示"
+    },
+    title: {
+      type: "input",
+      label: "弹窗标题"
+    },
+    thumbSuffix: {
+      type: "input",
+      label: "略图后缀, 例如七牛云缩略图样式 (?imageView2/1/w/20/h/20)"
+    },
+    fileType: {
+      type: "json-editor",
+      label: '文件类型, 例如["png", "jpg", "jpeg"]',
+      tip: "因为这是JSON编辑器, 所以要用`双引号`"
+    },
+    data: {
+      type: "json-editor",
+      label: "上传时附带的额外参数"
+    },
+    headers: {
+      type: "json-editor",
+      label: "设置上传的请求头部"
+    }
+  },
+  attrsData: {
+    action: "https://www.mocky.io/v2/5cc8019d300000980a055e76"
+  },
+  attrsDefaultData: {
+    fileSize: 0,
+    name: "file",
+    crop: false,
+    size: 150,
+    isShowTip: false,
+    drag: false,
+    data: {},
+    headers: {},
+    fileType: [],
+    withCredentials: false,
+    multiple: false
+  },
+  common: {},
+  commonData: {},
+  commonDefaultData: {}
+};
+
+export default config;

+ 58 - 0
src/config/comps/image.ts

@@ -0,0 +1,58 @@
+import { Config } from "@/types/config";
+
+const config: Config = {
+  url: "https://www.yuque.com/chaojie-vjiel/vbwzgu/kz163g#ld3lx",
+  attrs: {
+    fit: {
+      type: "select",
+      label: "确定图片如何适应容器框",
+      options: ["fill", "contain", "cover", "none", "scale-down"]
+    },
+    lazy: {
+      type: "switch",
+      label: "是否开启懒加载"
+    },
+    scrollContainer: {
+      type: "input",
+      label: "开启懒加载后,监听 scroll 事件的容器",
+      vif: data => data.lazy
+    },
+    previewSrcList: {
+      type: "json-editor",
+      label: "图片预览列表"
+    },
+    zIndex: {
+      type: "number",
+      label: "设置图片预览的 z-index"
+    }
+  },
+  attrsData: {},
+  attrsDefaultData: {
+    fit: "cover",
+    lazy: false,
+    previewSrcList: [],
+    zIndex: 2000
+  },
+  common: {
+    default: {
+      type: "dynamic",
+      label: "默认值",
+      attrs: {
+        columns: {
+          type: "el-input",
+          attrs: {
+            placeholder: "图片链接"
+          }
+        }
+      }
+    }
+  },
+  commonData: {
+    default: [
+      "https://cube.elemecdn.com/6/94/4d3ea53c084bad6931a56d5158a48jpeg.jpeg"
+    ]
+  },
+  commonDefaultData: {}
+};
+
+export default config;

+ 120 - 0
src/config/comps/input.ts

@@ -0,0 +1,120 @@
+import { Config } from "@/types/config";
+
+const config: Config = {
+  url: "https://element.eleme.cn/#/zh-CN/component/input",
+  attrs: {
+    type: {
+      type: "input",
+      label: "类型",
+      attrs: {
+        clearable: true
+      }
+    },
+    placeholder: {
+      type: "input",
+      label: "输入框占位文本",
+      attrs: {
+        clearable: true
+      }
+    },
+    prefixIcon: {
+      type: "input",
+      label: "输入框头部图标",
+      attrs: {
+        clearable: true
+      }
+    },
+    suffixIcon: {
+      type: "input",
+      label: "输入框尾部图标",
+      attrs: {
+        clearable: true
+      }
+    },
+    clearable: {
+      type: "switch",
+      label: "是否可清空"
+    },
+    minlength: {
+      type: "number",
+      label: "最小输入长度",
+      attrs: {
+        min: 0
+      }
+    },
+    maxlength: {
+      type: "number",
+      label: "最大输入长度",
+      attrs: {
+        min: 0
+      }
+    },
+    showWordLimit: {
+      type: "switch",
+      vif: data => data.minlength || data.maxlength,
+      label: "是否显示输入字数统计"
+    },
+    size: {
+      type: "select",
+      label: "输入框尺寸",
+      options: [{ text: "默认", value: null }, "medium", "small", "mini"]
+    },
+    max: {
+      type: "input",
+      label: "原生属性,设置最大值",
+      attrs: {
+        min: 0,
+        type: "number",
+        clearable: true
+      }
+    },
+    min: {
+      type: "input",
+      label: "原生属性,设置最小值",
+      attrs: {
+        min: 0,
+        type: "number",
+        clearable: true
+      }
+    },
+    resize: {
+      type: "select",
+      label: "控制是否能被用户缩放",
+      options: ["both", "horizontal", "vertical"],
+      attrs: {
+        clearable: true
+      }
+    },
+    autofocus: {
+      type: "switch",
+      label: "原生属性,自动获取焦点"
+    },
+    tabindex: {
+      type: "input",
+      label: "输入框的tabindex",
+      attrs: {
+        clearable: true
+      }
+    },
+    validateEvent: {
+      type: "switch",
+      label: "输入时是否触发表单的校验"
+    }
+  },
+  attrsData: {},
+  attrsDefaultData: {
+    size: null,
+    type: "text",
+    minlength: 0,
+    maxlength: 0,
+    showWordLimit: false,
+    clearable: false,
+    autofocus: false,
+    validateEvent: true
+  },
+  common: {},
+  commonData: {},
+  commonDefaultData: {}
+};
+
+export default config;

+ 35 - 0
src/config/comps/json-editor.ts

@@ -0,0 +1,35 @@
+import { Config } from "@/types/config";
+
+const config: Config = {
+  url: "https://github.com/dream2023/vue-ele-form-upload-file",
+  attrs: {
+    height: {
+      type: "input",
+      label: "高度"
+    },
+    plus: {
+      type: "switch",
+      label: "是否显示全屏按钮"
+    },
+    options: {
+      type: "json-editor",
+      label: "配置"
+    }
+  },
+  attrsData: {},
+  attrsDefaultData: {
+    height: "300px",
+    plus: false,
+    options: {}
+  },
+  common: {
+    default: {
+      type: "json-editor",
+      label: "默认值"
+    }
+  },
+  commonData: {},
+  commonDefaultData: {}
+};
+
+export default config;

+ 142 - 0
src/config/comps/markdown-editor.ts

@@ -0,0 +1,142 @@
+import { Config } from "@/types/config";
+
+const config: Config = {
+  url: "https://github.com/dream2023/vue-ele-form-markdown-editor",
+  attrs: {
+    fontSize: {
+      type: "input",
+      label: "编辑区域文字大小"
+    },
+    scrollStyle: {
+      type: "switch",
+      label: "开启滚动条样式(暂时仅支持chrome)"
+    },
+    boxShadow: {
+      type: "switch",
+      label: "开启边框阴影"
+    },
+    boxShadowStyle: {
+      type: "input",
+      label: "边框阴影样式"
+    },
+    transition: {
+      type: "switch",
+      label: "是否开启过渡动画"
+    },
+    toolbarsBackground: {
+      type: "color",
+      label: "工具栏背景颜色"
+    },
+    previewBackground: {
+      type: "color",
+      label: "预览框背景颜色"
+    },
+    subfield: {
+      type: "switch",
+      label: "true: 双栏(编辑预览同屏), false: 单栏(编辑预览分屏)"
+    },
+    defaultOpen: {
+      type: "radio",
+      label: "默认展示区域",
+      options: ["edit", "preview"]
+    },
+    placeholder: {
+      type: "input",
+      label: "输入框为空时默认提示文本"
+    },
+    editable: {
+      type: "switch",
+      label: "是否允许编辑"
+    },
+    toolbarsFlag: {
+      type: "switch",
+      label: "工具栏是否显示"
+    },
+    navigation: {
+      type: "switch",
+      label: "默认展示目录"
+    },
+    shortCut: {
+      type: "switch",
+      label: "是否启用快捷键"
+    },
+    autofocus: {
+      type: "switch",
+      label: "自动聚焦到文本框"
+    },
+    ishljs: {
+      type: "switch",
+      label: "代码高亮"
+    },
+    toolbars: {
+      type: "json-editor",
+      label: "工具栏"
+    },
+    action: {
+      type: "input",
+      label: "上传地址",
+      required: true
+    },
+    fileSize: {
+      type: "input",
+      label: "文件大小限制(MB)",
+      attrs: {
+        type: "number",
+        min: 0
+      }
+    },
+    name: {
+      type: "input",
+      label: "上传的文件字段名",
+      required: true
+    },
+    withCredentials: {
+      type: "switch",
+      label: "支持发送 cookie 凭证信息"
+    },
+    data: {
+      type: "json-editor",
+      label: "上传时附带的额外参数"
+    },
+    headers: {
+      type: "json-editor",
+      label: "设置上传的请求头部"
+    }
+  },
+  attrsData: {
+    action: "https://www.mocky.io/v2/5cc8019d300000980a055e76"
+  },
+  attrsDefaultData: {
+    fontSize: "15px",
+    scrollStyle: true,
+    boxShadow: true,
+    boxShadowStyle: "0 2px 12px 0 rgba(0, 0, 0, 0.1)",
+    transition: true,
+    toolbarsBackground: "#ffffff",
+    previewBackground: "#fbfbfb",
+    subfield: true,
+    defaultOpen: "edit",
+    placeholder: "开始编辑...",
+    editable: true,
+    toolbarsFlag: true,
+    navigation: false,
+    shortCut: true,
+    autofocus: true,
+    ishljs: true,
+    name: "file",
+    data: {},
+    headers: {},
+    withCredentials: false
+  },
+  common: {
+    default: {
+      type: "textarea",
+      label: "默认值",
+      default: ""
+    }
+  },
+  commonData: {},
+  commonDefaultData: {}
+};
+
+export default config;

+ 63 - 0
src/config/comps/month.ts

@@ -0,0 +1,63 @@
+import { Config } from "@/types/config";
+
+const config: Config = {
+  url: "https://element.eleme.cn/#/zh-CN/component/date-picker",
+  attrs: {
+    placeholder: {
+      type: "input",
+      label: "占位内容"
+    },
+    format: {
+      type: "input",
+      label: "显示在输入框中的格式"
+    },
+    clearable: {
+      type: "switch",
+      label: "是否显示清除按钮"
+    },
+    editable: {
+      type: "switch",
+      label: "文本框可输入"
+    },
+    size: {
+      type: "select",
+      label: "输入框尺寸",
+      options: [{ text: "默认", value: null }, "medium", "small", "mini"]
+    },
+    popperClass: {
+      type: "input",
+      label: "DatePicker 下拉框的类名"
+    },
+    prefixIcon: {
+      type: "input",
+      label: "自定义头部图标的类名"
+    },
+    clearIcon: {
+      type: "input",
+      label: "自定义清空图标的类名"
+    },
+    readonly: {
+      type: "switch",
+      label: "完全只读"
+    }
+  },
+  attrsData: {},
+  attrsDefaultData: {
+    readonly: false,
+    editable: true,
+    clearable: true,
+    size: null,
+    prefixIcon: "el-icon-date",
+    clearIcon: "el-icon-circle-close"
+  },
+  common: {
+    default: {
+      type: "month",
+      label: "默认值"
+    }
+  },
+  commonData: {},
+  commonDefaultData: {}
+};
+
+export default config;

+ 96 - 0
src/config/comps/monthrange.ts

@@ -0,0 +1,96 @@
+import { Config } from "@/types/config";
+
+const config: Config = {
+  url:
+    "https://element.eleme.cn/#/zh-CN/component/date-picker#xuan-ze-yue-fen-fan-wei",
+  attrs: {
+    startPlaceholder: {
+      type: "input",
+      label: "范围选择时开始日期的占位内容"
+    },
+    endPlaceholder: {
+      type: "input",
+      label: "范围选择时结束日期的占位内容"
+    },
+    rangeSeparator: {
+      type: "input",
+      label: "选择范围时的分隔符"
+    },
+    format: {
+      type: "input",
+      label: "显示在输入框中的格式"
+    },
+    valueFormat: {
+      type: "input",
+      label: "绑定值的格式, 不指定则绑定值为 Date 对象",
+      attrs: {
+        clearable: true
+      }
+    },
+    clearable: {
+      type: "switch",
+      label: "是否显示清除按钮"
+    },
+    editable: {
+      type: "switch",
+      label: "文本框可输入"
+    },
+    size: {
+      type: "select",
+      label: "输入框尺寸",
+      options: [{ text: "默认", value: null }, "medium", "small", "mini"]
+    },
+    unlinkPanels: {
+      type: "switch",
+      label: "在范围选择器里取消两个日期面板之间的联动"
+    },
+    align: {
+      type: "select",
+      label: "对齐方式",
+      options: ["left", "center", "right"]
+    },
+    popperClass: {
+      type: "input",
+      label: "DatePicker 下拉框的类名"
+    },
+    prefixIcon: {
+      type: "input",
+      label: "自定义头部图标的类名"
+    },
+    clearIcon: {
+      type: "input",
+      label: "自定义清空图标的类名"
+    },
+    readonly: {
+      type: "switch",
+      label: "完全只读"
+    },
+    validateEvent: {
+      type: "switch",
+      label: "输入时是否触发表单的校验"
+    }
+  },
+  attrsData: {},
+  attrsDefaultData: {
+    validateEvent: false,
+    unlinkPanels: false,
+    readonly: false,
+    editable: true,
+    clearable: true,
+    align: "left",
+    size: null,
+    rangeSeparator: "-",
+    prefixIcon: "el-icon-date",
+    clearIcon: "el-icon-circle-close"
+  },
+  common: {
+    default: {
+      type: "monthrange",
+      label: "默认值"
+    }
+  },
+  commonData: {},
+  commonDefaultData: {}
+};
+
+export default config;

+ 73 - 0
src/config/comps/number.ts

@@ -0,0 +1,73 @@
+import { Config } from "@/types/config";
+
+const config: Config = {
+  url: "https://element.eleme.cn/#/zh-CN/component/input-number",
+  attrs: {
+    placeholder: {
+      type: "input",
+      label: "输入框默认 placeholder"
+    },
+    min: {
+      type: "number",
+      label: "最小值"
+    },
+    max: {
+      type: "number",
+      label: "最大值"
+    },
+    step: {
+      type: "number",
+      label: "步长",
+      attrs: {
+        min: 0
+      }
+    },
+    stepStrictly: {
+      type: "switch",
+      label: "是否只能输入 step 的倍数"
+    },
+    precision: {
+      type: "number",
+      label: "数值精度",
+      attrs: {
+        type: "number",
+        min: 0
+      }
+    },
+    size: {
+      type: "radio",
+      label: "计数器尺寸",
+      options: [{ text: "默认", value: null }, "large", "small"]
+    },
+    controls: {
+      type: "switch",
+      label: "是否使用控制按钮"
+    },
+    controlsPosition: {
+      type: "radio",
+      label: "控制按钮位置",
+      options: [{ text: "默认", value: null }, "right"]
+    }
+  },
+  attrsData: {},
+  attrsDefaultData: {
+    size: null,
+    min: -Infinity,
+    max: Infinity,
+    step: 1,
+    controlsPosition: null,
+    precision: 0,
+    stepStrictly: false,
+    controls: true
+  },
+  common: {
+    default: {
+      type: "number",
+      label: "默认值"
+    }
+  },
+  commonData: {},
+  commonDefaultData: {}
+};
+
+export default config;

+ 88 - 0
src/config/comps/password.ts

@@ -0,0 +1,88 @@
+import { Config } from "@/types/config";
+
+const config: Config = {
+  url: "https://element.eleme.cn/#/zh-CN/component/input#mi-ma-kuang",
+  attrs: {
+    placeholder: {
+      type: "input",
+      label: "输入框占位文本",
+      attrs: {
+        clearable: true
+      }
+    },
+    prefixIcon: {
+      type: "input",
+      label: "输入框头部图标",
+      attrs: {
+        clearable: true
+      }
+    },
+    suffixIcon: {
+      type: "input",
+      label: "输入框尾部图标",
+      attrs: {
+        clearable: true
+      }
+    },
+    clearable: {
+      type: "switch",
+      label: "是否可清空"
+    },
+    minlength: {
+      type: "number",
+      label: "最小输入长度",
+      attrs: {
+        min: 0
+      }
+    },
+    maxlength: {
+      type: "number",
+      label: "最大输入长度",
+      attrs: {
+        min: 0
+      }
+    },
+    showWordLimit: {
+      type: "switch",
+      label: "是否显示输入字数统计",
+      vif: data => data.minlength || data.maxlength
+    },
+    size: {
+      type: "select",
+      label: "输入框尺寸",
+      options: [{ text: "默认", value: null }, "medium", "small", "mini"]
+    },
+    autofocus: {
+      type: "switch",
+      label: "原生属性,自动获取焦点"
+    },
+    tabindex: {
+      type: "input",
+      label: "输入框的tabindex",
+      attrs: {
+        clearable: true
+      }
+    },
+    validateEvent: {
+      type: "switch",
+      label: "输入时是否触发表单的校验"
+    }
+  },
+  attrsData: {},
+  attrsDefaultData: {
+    size: null,
+    showPassword: false,
+    type: "text",
+    showWordLimit: false,
+    clearable: false,
+    autofocus: false,
+    validateEvent: true,
+    maxlength: 0,
+    minlength: 0
+  },
+  common: {},
+  commonData: {},
+  commonDefaultData: {}
+};
+
+export default config;

+ 70 - 0
src/config/comps/quill-editor.ts

@@ -0,0 +1,70 @@
+import { Config } from "@/types/config";
+
+const config: Config = {
+  url: "https://github.com/dream2023/vue-ele-form-quill-editor",
+  attrs: {
+    action: {
+      type: "input",
+      label: "上传地址",
+      required: true
+    },
+    editorOptions: {
+      type: "json-editor",
+      label: "编辑器设置",
+      tip: "请参考: https://github.com/davidroyer/vue2-editor"
+    },
+    placeholder: {
+      type: "input",
+      label: "占位符"
+    },
+    fileSize: {
+      type: "input",
+      label: "文件大小限制(MB)",
+      attrs: {
+        type: "number",
+        min: 0
+      }
+    },
+    name: {
+      type: "input",
+      label: "上传的文件字段名",
+      required: true
+    },
+    withCredentials: {
+      type: "switch",
+      label: "支持发送 cookie 凭证信息"
+    },
+    data: {
+      type: "json-editor",
+      label: "上传时附带的额外参数"
+    },
+    headers: {
+      type: "json-editor",
+      label: "设置上传的请求头部"
+    },
+    editorToolbar: {
+      type: "json-editor",
+      label: "自定义toolbar"
+    }
+  },
+  attrsData: {
+    action: "https://www.mocky.io/v2/5cc8019d300000980a055e76"
+  },
+  attrsDefaultData: {
+    name: "file",
+    data: {},
+    headers: {},
+    editorToolbar: [],
+    withCredentials: false
+  },
+  common: {
+    default: {
+      type: "textarea",
+      label: "默认值"
+    }
+  },
+  commonData: {},
+  commonDefaultData: {}
+};
+
+export default config;

+ 50 - 0
src/config/comps/radio-button.ts

@@ -0,0 +1,50 @@
+import { Config } from "@/types/config";
+
+const config: Config = {
+  url: "https://element.eleme.cn/#/zh-CN/component/radio#an-niu-yang-shi",
+  attrs: {
+    size: {
+      type: "select",
+      label: "单选框组尺寸",
+      options: [{ text: "默认", value: null }, "medium", "small", "mini"]
+    },
+    textColor: {
+      type: "color",
+      label: "Radio 激活时的文本颜色"
+    },
+    fill: {
+      type: "color",
+      label: "Radio 激活时的填充色和边框色"
+    }
+  },
+  attrsData: {},
+  attrsDefaultData: {
+    size: null,
+    textColor: "#ffffff",
+    fill: "#409EFF"
+  },
+  common: {
+    default: {
+      type: "radio",
+      label: "默认值",
+      isReloadOptions: true,
+      options: data => data.options
+    },
+    options: {
+      type: "json-editor",
+      label: "选项",
+      tip:
+        'options支持`API接口`、`数组`、`函数`、`Promise`等, 具体看<a target="_blank" href="https://www.yuque.com/chaojie-vjiel/vbwzgu/rgenav" class="el-link el-link--primary">文档</a>'
+    }
+  },
+  commonData: {
+    options: [
+      { text: "选项1", value: 1 },
+      { text: "选项2", value: 2 },
+      { text: "选项3", value: 3 }
+    ]
+  },
+  commonDefaultData: {}
+};
+
+export default config;

+ 40 - 0
src/config/comps/radio.ts

@@ -0,0 +1,40 @@
+import { Config } from "@/types/config";
+
+const config: Config = {
+  url: "https://element.eleme.cn/#/zh-CN/component/radio",
+  attrs: {
+    size: {
+      type: "select",
+      label: "单选框组尺寸",
+      options: [{ text: "默认", value: null }, "medium", "small", "mini"]
+    }
+  },
+  attrsData: {},
+  attrsDefaultData: {
+    size: null
+  },
+  common: {
+    default: {
+      type: "radio",
+      label: "默认值",
+      isReloadOptions: true,
+      options: data => data.options
+    },
+    options: {
+      type: "json-editor",
+      label: "选项",
+      tip:
+        'options支持`API接口`、`数组`、`函数`、`Promise`等, 具体看<a target="_blank" href="https://www.yuque.com/chaojie-vjiel/vbwzgu/rgenav" class="el-link el-link--primary">文档</a>'
+    }
+  },
+  commonData: {
+    options: [
+      { text: "选项1", value: 1 },
+      { text: "选项2", value: 2 },
+      { text: "选项3", value: 3 }
+    ]
+  },
+  commonDefaultData: {}
+};
+
+export default config;

+ 107 - 0
src/config/comps/rate.ts

@@ -0,0 +1,107 @@
+import { Config } from "@/types/config";
+
+const config: Config = {
+  url: "https://element.eleme.cn/#/zh-CN/component/rate",
+  attrs: {
+    max: {
+      type: "number",
+      label: "最大分值",
+      attrs: {
+        min: 0
+      }
+    },
+    allowHalf: {
+      type: "switch",
+      label: "是否允许半选"
+    },
+    lowThreshold: {
+      type: "number",
+      label: "低分和中等分数的界限值,值本身被划分在低分中",
+      attrs: {
+        min: 0
+      }
+    },
+    highThreshold: {
+      type: "number",
+      label: "高分和中等分数的界限值,值本身被划分在高分中",
+      attrs: {
+        min: 0
+      }
+    },
+    colors: {
+      type: "json-editor",
+      label: "icon 的颜色(3个元素)"
+    },
+    voidColor: {
+      type: "color",
+      label: "未选中 icon 的颜色"
+    },
+    disabledVoidColor: {
+      type: "color",
+      label: "只读时未选中 icon 的颜色"
+    },
+    iconClasses: {
+      type: "json-editor",
+      label: "icon 的类名"
+    },
+    voidIconClass: {
+      type: "input",
+      label: "未选中 icon 的类名"
+    },
+    disabledVoidIconClass: {
+      type: "input",
+      label: "只读时未选中 icon 的类名"
+    },
+    showText: {
+      type: "switch",
+      label:
+        "是否显示辅助文字,若为真,则会从 texts 数组中选取当前分数对应的文字内容"
+    },
+    showScore: {
+      type: "switch",
+      label: "是否显示当前分数,show-score 和 show-text 不能同时为真"
+    },
+    textColor: {
+      type: "color",
+      label: "辅助文字的颜色"
+    },
+    texts: {
+      type: "json-editor",
+      label: "辅助文字数组"
+    },
+    scoreTemplate: {
+      type: "input",
+      label: "分数显示模板"
+    }
+  },
+  attrsData: {},
+  attrsDefaultData: {
+    max: 5,
+    allowHalf: false,
+    lowThreshold: 2,
+    highThreshold: 4,
+    colors: ["#F7BA2A", "#F7BA2A", "#F7BA2A"],
+    voidColor: "#C6D1DE",
+    disabledVoidColor: "#EFF2F7",
+    iconClasses: ["el-icon-star-on", "el-icon-star-on", "el-icon-star-on"],
+    voidIconClass: "el-icon-star-off",
+    disabledVoidIconClass: "el-icon-star-on",
+    showText: false,
+    showScore: false,
+    textColor: "#1F2D3D",
+    texts: ["极差", "失望", "一般", "满意", "惊喜"],
+    scoreTemplate: "{value}"
+  },
+  common: {
+    default: {
+      type: "number",
+      label: "默认值"
+    }
+  },
+  commonData: {},
+  commonDefaultData: {
+    default: 0
+  }
+};
+
+export default config;

+ 134 - 0
src/config/comps/select.ts

@@ -0,0 +1,134 @@
+import { Config } from "@/types/config";
+
+const config: Config = {
+  url: "https://element.eleme.cn/#/zh-CN/component/select",
+  attrs: {
+    placeholder: {
+      type: "input",
+      label: "占位符"
+    },
+    popperAppendToBody: {
+      type: "switch",
+      label:
+        "是否将弹出框插入至 body 元素。在弹出框的定位出现问题时,可将该属性设置为 false"
+    },
+    multiple: {
+      type: "switch",
+      label: "是否多选"
+    },
+    valueKey: {
+      type: "input",
+      label: "作为 value 唯一标识的键名,绑定值为对象类型时必填"
+    },
+    size: {
+      type: "select",
+      label: "输入框尺寸",
+      options: [{ text: "默认", value: null }, "medium", "small", "mini"]
+    },
+    clearable: {
+      type: "switch",
+      label: "是否可以清空选项"
+    },
+    collapseTags: {
+      type: "switch",
+      label: "多选时是否将选中值按文字的形式展示"
+    },
+    multipleLimit: {
+      type: "number",
+      label: "多选时用户最多可以选择的项目数,为 0 则不限制",
+      attrs: {
+        min: 0
+      }
+    },
+    autocomplete: {
+      type: "input",
+      label: "select input 的 autocomplete 属性"
+    },
+    filterable: {
+      type: "switch",
+      label: "是否可搜索"
+    },
+    allowCreate: {
+      type: "switch",
+      label: "是否允许用户创建新条目,需配合 filterable 使用"
+    },
+    remote: {
+      type: "switch",
+      label: "是否为远程搜索"
+    },
+    loadingText: {
+      type: "input",
+      label: "远程加载时显示的文字"
+    },
+    noMatchText: {
+      type: "input",
+      label: '搜索条件无匹配时显示的文字,也可以使用slot="empty"设置'
+    },
+    noDataText: {
+      type: "input",
+      label: '选项为空时显示的文字,也可以使用slot="empty"设置'
+    },
+    popperClass: {
+      type: "input",
+      label: "Select 下拉框的类名"
+    },
+    reserveKeyword: {
+      type: "switch",
+      label: "多选且可搜索时,是否在选中一个选项后保留当前的搜索关键词"
+    },
+    defaultFirstOption: {
+      type: "switch",
+      label:
+        "在输入框按下回车,选择第一个匹配项。需配合 filterable 或 remote 使用"
+    },
+    automaticDropdown: {
+      type: "switch",
+      label: "对于不可搜索的 Select,是否在输入框获得焦点后自动弹出选项菜单"
+    }
+  },
+  attrsData: {},
+  attrsDefaultData: {
+    size: null,
+    multiple: false,
+    valueKey: "value",
+    clearable: false,
+    collapseTags: false,
+    multipleLimit: 0,
+    autocomplete: "off",
+    filterable: false,
+    allowCreate: false,
+    remote: false,
+    loading: false,
+    loadingText: "加载中",
+    noMatchText: "无匹配数据",
+    noDataText: "无数据",
+    reserveKeyword: false,
+    defaultFirstOption: false,
+    popperAppendToBody: true,
+    automaticDropdown: false
+  },
+  common: {
+    default: {
+      type: "select",
+      label: "默认值",
+      isReloadOptions: true,
+      options: data => data.options
+    },
+    options: {
+      type: "json-editor",
+      label: "选项",
+      tip:
+        'options支持`API接口`、`数组`、`函数`、`Promise`等, 具体看<a target="_blank" href="https://www.yuque.com/chaojie-vjiel/vbwzgu/rgenav" class="el-link el-link--primary">文档</a>'
+    }
+  },
+  commonData: {
+    options: [
+      { text: "选项1", value: 1 },
+      { text: "选项2", value: 2 },
+      { text: "选项3", value: 3 }
+    ]
+  },
+  commonDefaultData: {}
+};
+
+export default config;

+ 107 - 0
src/config/comps/slider.ts

@@ -0,0 +1,107 @@
+import { Config } from "@/types/config";
+
+const config: Config = {
+  url: "https://element.eleme.cn/#/zh-CN/component/slider",
+  attrs: {
+    min: {
+      type: "number",
+      label: "最小值",
+      attrs: {
+        min: 0
+      }
+    },
+    max: {
+      type: "number",
+      label: "最大值",
+      attrs: {
+        min: 0
+      }
+    },
+    step: {
+      type: "number",
+      label: "步长",
+      attrs: {
+        min: 0
+      }
+    },
+    showInput: {
+      type: "switch",
+      label: "是否显示输入框,仅在非范围选择时有效"
+    },
+    showInputControls: {
+      type: "switch",
+      label: "在显示输入框的情况下,是否显示输入框的控制按钮"
+    },
+    inputSize: {
+      type: "select",
+      label: "输入框的尺寸",
+      options: ["large", "medium", "small", "mini"]
+    },
+    showStops: {
+      type: "switch",
+      label: "是否显示间断点"
+    },
+    showTooltip: {
+      type: "switch",
+      label: "是否显示 tooltip"
+    },
+    range: {
+      type: "switch",
+      label: "是否为范围选择"
+    },
+    vertical: {
+      type: "switch",
+      label: "是否竖向模式"
+    },
+    height: {
+      type: "input",
+      label: "Slider 高度,竖向模式时必填"
+    },
+    label: {
+      type: "input",
+      label: "屏幕阅读器标签"
+    },
+    debounce: {
+      type: "number",
+      label: "输入时的去抖延迟,毫秒,仅在show-input等于true时有效",
+      attrs: {
+        min: 0,
+        step: 100
+      }
+    },
+    tooltipClass: {
+      type: "input",
+      label: "tooltip 的自定义类名"
+    },
+    marks: {
+      type: "json-editor",
+      label:
+        "标记,key 的类型必须为 number 且取值在闭区间 [min, max] 内,每个标记可以单独设置样式"
+    }
+  },
+  attrsData: {},
+  attrsDefaultData: {
+    min: 0,
+    max: 100,
+    step: 1,
+    showInput: false,
+    showInputControls: true,
+    inputSize: "small",
+    showStops: false,
+    showTooltip: true,
+    range: false,
+    vertical: false,
+    debounce: 300,
+    marks: {}
+  },
+  common: {
+    default: {
+      type: "number",
+      label: "默认值"
+    }
+  },
+  commonData: {},
+  commonDefaultData: {}
+};
+
+export default config;

+ 60 - 0
src/config/comps/switch.ts

@@ -0,0 +1,60 @@
+import { Config } from "@/types/config";
+
+const config: Config = {
+  url: "https://element.eleme.cn/#/zh-CN/component/switch",
+  attrs: {
+    activeColor: {
+      type: "color",
+      label: "switch 打开时的背景色"
+    },
+    inactiveColor: {
+      type: "color",
+      label: "switch 关闭时的背景色"
+    },
+    width: {
+      type: "number",
+      label: "switch 的宽度(像素)",
+      attrs: {
+        min: 1,
+        step: 10
+      }
+    },
+    activeIconClass: {
+      type: "input",
+      label: "switch 打开时所显示图标的类名,设置此项会忽略 active-text"
+    },
+    inactiveIconClass: {
+      type: "input",
+      label: "switch 关闭时所显示图标的类名,设置此项会忽略 inactive-text"
+    },
+    activeText: {
+      type: "input",
+      label: "switch 打开时的文字描述"
+    },
+    inactiveText: {
+      type: "input",
+      label: "switch 关闭时的文字描述"
+    },
+    validateEvent: {
+      type: "switch",
+      label: "改变 switch 状态时是否触发表单的校验"
+    }
+  },
+  attrsData: {},
+  attrsDefaultData: {
+    width: 40,
+    activeColor: "#409EFF",
+    inactiveColor: "#C0CCDA",
+    validateEvent: true
+  },
+  common: {
+    default: {
+      type: "switch",
+      label: "默认值"
+    }
+  },
+  commonData: {},
+  commonDefaultData: {}
+};
+
+export default config;

+ 138 - 0
src/config/comps/table-editor.ts

@@ -0,0 +1,138 @@
+import { Config } from "@/types/config";
+
+const config: Config = {
+  url: "https://github.com/dream2023/vue-ele-form-table-editor",
+  attrs: {
+    columns: {
+      type: "json-editor",
+      label: "table 列",
+      required: true
+    },
+    isShowDelete: {
+      type: "switch",
+      label: "是否显示删除"
+    },
+    isShowAdd: {
+      type: "switch",
+      label: "是否显示新增按钮"
+    },
+    addBtnText: {
+      type: "input",
+      label: "新增按钮文本"
+    },
+    newColumnValue: {
+      type: "json-editor",
+      label: "新增列的值"
+    },
+    rules: {
+      type: "json-editor",
+      label: "校检规则"
+    },
+    extraBtns: {
+      type: "json-editor",
+      label: "右侧其它按钮"
+    },
+    deleteBtnAttr: {
+      type: "json-editor",
+      label: "删除按钮属性"
+    },
+    tableAttrs: {
+      type: "json-editor",
+      label: "表格属性"
+    }
+  },
+  attrsData: {
+    columns: [
+      {
+        // el-table-column 的属性
+        type: "index",
+        width: 50
+      },
+      {
+        // el-table-column 的属性
+        prop: "grade",
+        label: "年级"
+      },
+      {
+        prop: "name",
+        label: "姓名",
+        content: {
+          type: "el-input",
+          attrs: {
+            placeholder: "学员姓名"
+          }
+        }
+      },
+      {
+        label: "缴费",
+        width: 340,
+        content: [
+          "已缴纳: ",
+          {
+            type: "el-input",
+            valueKey: "tuition",
+            style: {
+              width: "100px",
+              marginRight: "10px"
+            }
+          },
+          "未缴纳: ",
+          {
+            type: "el-input",
+            valueKey: "unPay",
+            style: {
+              width: "100px"
+            }
+          }
+        ]
+      },
+      {
+        prop: "dream",
+        label: "梦想",
+        content: {
+          type: "el-select",
+          options: [
+            { text: "科学家", value: "scientist" },
+            { text: "警察", value: "policeman" },
+            "程序员"
+          ]
+        }
+      }
+    ],
+    newColumnValue: {
+      grade: "三年级二班"
+    }
+  },
+  attrsDefaultData: {
+    isShowDelete: true,
+    deleteBtnAttr: {
+      type: "text"
+    },
+    isShowAdd: true,
+    addBtnText: "新增",
+    tableAttrs: {
+      border: true
+    }
+  },
+  common: {
+    default: {
+      type: "json-editor",
+      label: "默认值"
+    }
+  },
+  commonData: {
+    default: [
+      {
+        grade: "三年级二班",
+        name: "小张",
+        sex: 1,
+        tuition: 2000,
+        unPay: 100,
+        dream: ""
+      }
+    ]
+  },
+  commonDefaultData: {}
+};
+
+export default config;

+ 87 - 0
src/config/comps/tag.ts

@@ -0,0 +1,87 @@
+import { Config } from "@/types/config";
+
+const config: Config = {
+  url: "https://www.yuque.com/chaojie-vjiel/vbwzgu/kz163g#ZFYvW",
+  attrs: {
+    placeholder: {
+      type: "input",
+      label: "占位符"
+    },
+    popperAppendToBody: {
+      type: "switch",
+      label:
+        "是否将弹出框插入至 body 元素。在弹出框的定位出现问题时,可将该属性设置为 false"
+    },
+    size: {
+      type: "select",
+      label: "输入框尺寸",
+      options: [{ text: "默认", value: null }, "medium", "small", "mini"]
+    },
+    clearable: {
+      type: "switch",
+      label: "是否可以清空选项"
+    },
+    collapseTags: {
+      type: "switch",
+      label: "多选时是否将选中值按文字的形式展示"
+    },
+    multipleLimit: {
+      type: "number",
+      label: "多选时用户最多可以选择的项目数,为 0 则不限制",
+      attrs: {
+        min: 0
+      }
+    },
+    popperClass: {
+      type: "input",
+      label: "Select 下拉框的类名"
+    },
+    defaultFirstOption: {
+      type: "switch",
+      label: "在输入框按下回车,选择第一个匹配项"
+    },
+    automaticDropdown: {
+      type: "switch",
+      label: "对于不可搜索的 Select,是否在输入框获得焦点后自动弹出选项菜单"
+    }
+  },
+  attrsData: {},
+  attrsDefaultData: {
+    size: null,
+    multiple: false,
+    valueKey: "value",
+    clearable: false,
+    collapseTags: false,
+    multipleLimit: 0,
+    autocomplete: "off",
+    filterable: false,
+    allowCreate: false,
+    remote: false,
+    loading: false,
+    loadingText: "加载中",
+    noMatchText: "无匹配数据",
+    noDataText: "无数据",
+    reserveKeyword: false,
+    defaultFirstOption: false,
+    popperAppendToBody: true,
+    automaticDropdown: false
+  },
+  common: {
+    default: {
+      type: "dynamic",
+      label: "默认值",
+      attrs: {
+        columns: {
+          type: "el-input",
+          attrs: {
+            placeholder: "默认tag"
+          }
+        }
+      }
+    }
+  },
+  commonData: {},
+  commonDefaultData: {}
+};
+
+export default config;

+ 28 - 0
src/config/comps/text.ts

@@ -0,0 +1,28 @@
+import { Config } from "@/types/config";
+
+const config: Config = {
+  url: "https://www.yuque.com/chaojie-vjiel/vbwzgu/kz163g#IGR5Q",
+  attrs: {},
+  attrsData: {},
+  attrsDefaultData: {},
+  common: {
+    default: {
+      type: "input",
+      label: "默认值"
+    },
+    options: {
+      type: "json-editor",
+      label: "选项",
+      tip:
+        'options支持`API接口`、`数组`、`函数`、`Promise`等, 具体看<a target="_blank" href="https://www.yuque.com/chaojie-vjiel/vbwzgu/rgenav" class="el-link el-link--primary">文档</a>'
+    }
+  },
+  commonData: {
+    default: "我是一段静态文本"
+  },
+  commonDefaultData: {
+    options: []
+  }
+};
+
+export default config;

+ 83 - 0
src/config/comps/textarea.ts

@@ -0,0 +1,83 @@
+import { Config } from "@/types/config";
+
+const config: Config = {
+  url: "https://element.eleme.cn/#/zh-CN/component/input#wen-ben-yu",
+  attrs: {
+    placeholder: {
+      type: "input",
+      label: "输入框占位文本",
+      attrs: {
+        clearable: true
+      }
+    },
+    rows: {
+      type: "number",
+      label: "输入框行",
+      attrs: {
+        min: 1
+      }
+    },
+    autosizeType: {
+      type: "radio",
+      label: "自适应内容高度值类型",
+      options: [
+        { text: "自适应", value: "switch" },
+        { text: "最大最小值", value: "json-editor" }
+      ]
+    },
+    autosize: {
+      type: data => data.autosizeType,
+      label: "自适应内容高度配置",
+      attrs: {
+        height: "200px"
+      }
+    },
+    resize: {
+      type: "select",
+      label: "控制是否能被用户缩放",
+      options: ["both", "horizontal", "vertical"],
+      attrs: {
+        clearable: true
+      }
+    },
+    showWordLimit: {
+      type: "switch",
+      label: "是否显示输入字数统计",
+      vif: data => data.minlength || data.maxlength
+    },
+    clearable: {
+      type: "switch",
+      label: "是否可清空"
+    },
+
+    autofocus: {
+      type: "switch",
+      label: "原生属性,自动获取焦点"
+    },
+    tabindex: {
+      type: "input",
+      label: "输入框的tabindex",
+      attrs: {
+        clearable: true
+      }
+    },
+    validateEvent: {
+      type: "switch",
+      label: "输入时是否触发表单的校验"
+    }
+  },
+  attrsData: {},
+  assistProperty: ["autosizeType"],
+  attrsDefaultData: {
+    rows: 2,
+    autosizeType: "switch",
+    autosize: false,
+    showWordLimit: false,
+    clearable: false
+  },
+  common: {},
+  commonData: {},
+  commonDefaultData: {}
+};
+
+export default config;

+ 59 - 0
src/config/comps/time.ts

@@ -0,0 +1,59 @@
+import { Config } from "@/types/config";
+
+const config: Config = {
+  url: "https://element.eleme.cn/#/zh-CN/component/time-picker",
+  attrs: {
+    placeholder: {
+      type: "input",
+      label: "占位内容"
+    },
+    clearable: {
+      type: "switch",
+      label: "是否显示清除按钮"
+    },
+    editable: {
+      type: "switch",
+      label: "文本框可输入"
+    },
+    size: {
+      type: "select",
+      label: "输入框尺寸",
+      options: [{ text: "默认", value: null }, "medium", "small", "mini"]
+    },
+    popperClass: {
+      type: "input",
+      label: "TimePicker 下拉框的类名"
+    },
+    prefixIcon: {
+      type: "input",
+      label: "自定义头部图标的类名"
+    },
+    clearIcon: {
+      type: "input",
+      label: "自定义清空图标的类名"
+    },
+    readonly: {
+      type: "switch",
+      label: "完全只读"
+    }
+  },
+  attrsData: {},
+  attrsDefaultData: {
+    readonly: false,
+    editable: true,
+    clearable: true,
+    size: null,
+    prefixIcon: "el-icon-time",
+    clearIcon: "el-icon-circle-close"
+  },
+  common: {
+    default: {
+      type: "time",
+      label: "默认值"
+    }
+  },
+  commonData: {},
+  commonDefaultData: {}
+};
+
+export default config;

+ 86 - 0
src/config/comps/timerange.ts

@@ -0,0 +1,86 @@
+import { Config } from "@/types/config";
+
+const config: Config = {
+  url:
+    "https://element.eleme.cn/#/zh-CN/component/time-picker#ren-yi-shi-jian-fan-wei",
+  attrs: {
+    startPlaceholder: {
+      type: "input",
+      label: "范围选择时开始日期的占位内容"
+    },
+    endPlaceholder: {
+      type: "input",
+      label: "范围选择时结束日期的占位内容"
+    },
+    rangeSeparator: {
+      type: "input",
+      label: "选择范围时的分隔符"
+    },
+    arrowControl: {
+      type: "switch",
+      label: "是否使用箭头进行时间选择"
+    },
+    valueFormat: {
+      type: "input",
+      label: "绑定值的格式, 不指定则绑定值为 Date 对象",
+      attrs: {
+        clearable: true
+      }
+    },
+    clearable: {
+      type: "switch",
+      label: "是否显示清除按钮"
+    },
+    editable: {
+      type: "switch",
+      label: "文本框可输入"
+    },
+    size: {
+      type: "select",
+      label: "输入框尺寸",
+      options: [{ text: "默认", value: null }, "medium", "small", "mini"]
+    },
+    align: {
+      type: "select",
+      label: "对齐方式",
+      options: ["left", "center", "right"]
+    },
+    popperClass: {
+      type: "input",
+      label: "TimePicker 下拉框的类名"
+    },
+    prefixIcon: {
+      type: "input",
+      label: "自定义头部图标的类名"
+    },
+    clearIcon: {
+      type: "input",
+      label: "自定义清空图标的类名"
+    },
+    readonly: {
+      type: "switch",
+      label: "完全只读"
+    }
+  },
+  attrsData: {},
+  attrsDefaultData: {
+    readonly: false,
+    editable: true,
+    clearable: true,
+    align: "left",
+    size: null,
+    rangeSeparator: "-",
+    prefixIcon: "el-icon-time",
+    clearIcon: "el-icon-circle-close"
+  },
+  common: {
+    default: {
+      type: "timerange",
+      label: "默认值"
+    }
+  },
+  commonData: {},
+  commonDefaultData: {}
+};
+
+export default config;

+ 80 - 0
src/config/comps/transfer.ts

@@ -0,0 +1,80 @@
+import { Config } from "@/types/config";
+
+const config: Config = {
+  url: "https://element.eleme.cn/#/zh-CN/component/transfer",
+  attrs: {
+    filterable: {
+      type: "switch",
+      label: "是否可搜索选项"
+    },
+    filterPlaceholder: {
+      type: "input",
+      label: "搜索框占位符",
+      vif: data => data.filterable
+    },
+    targetOrder: {
+      type: "select",
+      label: "右侧列表元素的排序策略",
+      options: ["original", "push", "unshift"]
+    },
+    titles: {
+      type: "json-editor",
+      label: "自定义列表标题"
+    },
+    buttonTexts: {
+      type: "json-editor",
+      label: "自定义按钮文案"
+    },
+    format: {
+      type: "json-editor",
+      label: "列表顶部勾选状态文案"
+    },
+    props: {
+      type: "json-editor",
+      label: "数据源的字段别名"
+    }
+  },
+  attrsData: {},
+  attrsDefaultData: {
+    format: {
+      noChecked: "",
+      hasChecked: ""
+    },
+    props: {},
+    titles: ["列表 1", "列表 2"],
+    targetOrder: "original",
+    filterable: false,
+    filterPlaceholder: "请输入搜索内容"
+  },
+  common: {
+    default: {
+      type: "select",
+      label: "默认值",
+      options: data =>
+        data.options.map((item: AnyObj) => ({
+          text: item.label,
+          value: item.key
+        })),
+      attrs: {
+        multiple: true
+      }
+    },
+    options: {
+      type: "json-editor",
+      label: "选项",
+      tip:
+        'options支持`API接口`、`数组`、`函数`、`Promise`等, 具体看<a target="_blank" href="https://www.yuque.com/chaojie-vjiel/vbwzgu/rgenav" class="el-link el-link--primary">文档</a>'
+    }
+  },
+  commonData: {
+    options: [
+      { key: 1, label: "选项1" },
+      { key: 2, label: "选项3" },
+      { key: 3, label: "选项3" },
+      { key: 4, label: "选项4" }
+    ]
+  },
+  commonDefaultData: {}
+};
+
+export default config;

+ 120 - 0
src/config/comps/tree-select.ts

@@ -0,0 +1,120 @@
+import { Config } from "@/types/config";
+
+const config: Config = {
+  url: "https://github.com/dream2023/vue-ele-form-tree-select",
+  attrs: {
+    placeholder: {
+      type: "input",
+      label: "占位符"
+    },
+    appendToBody: {
+      type: "switch",
+      label: "弹窗插入body"
+    },
+    multiple: {
+      type: "switch",
+      label: "是否开启多选模式"
+    },
+    searchable: {
+      type: "switch",
+      label: "是否开启搜索功能"
+    },
+    async: {
+      type: "switch",
+      label: "是否开启异步搜索"
+    },
+    autoFocus: {
+      type: "switch",
+      label: "是否自动聚焦"
+    },
+    clearable: {
+      type: "switch",
+      label: "是否显示清除图标"
+    },
+    limit: {
+      type: "number",
+      label: "多选数量显示",
+      vif: data => data.multiple,
+      attrs: {
+        min: 1
+      }
+    },
+    maxHeight: {
+      type: "number",
+      label: "弹出菜单高度",
+      attrs: {
+        min: 0,
+        step: 10
+      }
+    },
+    alwaysOpen: {
+      type: "switch",
+      label: "一直打开选项菜单"
+    },
+    flattenSearchResults: {
+      type: "switch",
+      label: "是否展平搜索结果",
+      vif: data => data.async
+    },
+    autoLoadRootOptions: {
+      type: "switch",
+      label: "是否自定加载根节点选项"
+    }
+  },
+  attrsData: {},
+  attrsDefaultData: {
+    limit: Infinity,
+    searchable: true,
+    clearable: true,
+    multiple: false,
+    flattenSearchResults: false,
+    autoLoadRootOptions: true,
+    autoFocus: false,
+    async: false,
+    maxHeight: 300,
+    alwaysOpen: false,
+    appendToBody: false
+  },
+  common: {
+    default: {
+      type: "tree-select",
+      label: "默认值",
+      options: data => data.options
+    },
+    options: {
+      type: "json-editor",
+      label: "选项",
+      tip:
+        'options支持`API接口`、`数组`、`函数`、`Promise`等, 具体看<a target="_blank" href="https://www.yuque.com/chaojie-vjiel/vbwzgu/rgenav" class="el-link el-link--primary">文档</a>'
+    }
+  },
+  commonData: {
+    options: [
+      {
+        id: "a",
+        label: "a",
+        children: [
+          {
+            id: "aa",
+            label: "aa"
+          },
+          {
+            id: "ab",
+            label: "ab"
+          }
+        ]
+      },
+      {
+        id: "b",
+        label: "b"
+      },
+      {
+        id: "c",
+        label: "c"
+      }
+    ]
+  },
+  commonDefaultData: {}
+};
+
+export default config;

+ 96 - 0
src/config/comps/upload-file.ts

@@ -0,0 +1,96 @@
+import { Config } from "@/types/config";
+
+const config: Config = {
+  url: "https://github.com/dream2023/vue-ele-form-upload-file",
+  attrs: {
+    action: {
+      type: "input",
+      label: "上传地址",
+      required: true
+    },
+    name: {
+      type: "input",
+      label: "上传的文件字段名",
+      required: true
+    },
+    placeholder: {
+      type: "input",
+      label: "上传按钮文本"
+    },
+    fileSize: {
+      type: "input",
+      label: "文件大小限制(MB)",
+      attrs: {
+        type: "number",
+        min: 0
+      }
+    },
+    fileType: {
+      type: "json-editor",
+      label: '文件类型, 例如["png", "jpg", "jpeg"]',
+      tip: "因为这是JSON编辑器, 所以要用`双引号`"
+    },
+    multiple: {
+      type: "switch",
+      label: "是否支持多选文件"
+    },
+    limit: {
+      type: "input",
+      label: "文件个数显示",
+      vif: data => data.multiple,
+      attrs: {
+        type: "number",
+        min: 0
+      }
+    },
+    isCanDownload: {
+      type: "switch",
+      label: "是否显示下载"
+    },
+    isCanDelete: {
+      type: "switch",
+      label: "是否可删除"
+    },
+    isCanUploadSame: {
+      type: "switch",
+      label: "是否可上传相同文件"
+    },
+    isShowTip: {
+      type: "switch",
+      label: "是否显示提示"
+    },
+    withCredentials: {
+      type: "switch",
+      label: "支持发送 cookie 凭证信息"
+    },
+    data: {
+      type: "json-editor",
+      label: "上传时附带的额外参数"
+    },
+    headers: {
+      type: "json-editor",
+      label: "设置上传的请求头部"
+    }
+  },
+  attrsData: {
+    action: "https://www.mocky.io/v2/5cc8019d300000980a055e76"
+  },
+  attrsDefaultData: {
+    name: "file",
+    limit: 0,
+    isShowSize: true,
+    isCanDelete: true,
+    isCanDownload: true,
+    isCanUploadSame: true,
+    data: {},
+    headers: {},
+    fileType: [],
+    withCredentials: false,
+    multiple: true
+  },
+  common: {},
+  commonData: {},
+  commonDefaultData: {}
+};
+
+export default config;

+ 79 - 0
src/config/comps/video-uploader.ts

@@ -0,0 +1,79 @@
+import { Config } from "@/types/config";
+
+const config: Config = {
+  url: "https://github.com/dream2023/vue-ele-form-video-uploader",
+  attrs: {
+    action: {
+      type: "input",
+      label: "上传地址",
+      required: true
+    },
+    name: {
+      type: "input",
+      label: "上传的文件字段名",
+      required: true
+    },
+    fileSize: {
+      type: "input",
+      label: "文件大小限制(MB)",
+      attrs: {
+        type: "number",
+        min: 0
+      }
+    },
+    width: {
+      type: "number",
+      label: "显示宽度",
+      attrs: {
+        step: 10,
+        min: 0
+      }
+    },
+    height: {
+      type: "number",
+      label: "显示高度(默认auto)",
+      attrs: {
+        step: 10,
+        min: 0
+      }
+    },
+    fileType: {
+      type: "json-editor",
+      label: "文件类型"
+    },
+    isShowTip: {
+      type: "switch",
+      label: "是否显示提示"
+    },
+    withCredentials: {
+      type: "switch",
+      label: "支持发送 cookie 凭证信息"
+    },
+    data: {
+      type: "json-editor",
+      label: "上传时附带的额外参数"
+    },
+    headers: {
+      type: "json-editor",
+      label: "设置上传的请求头部"
+    }
+  },
+  attrsData: {
+    action: "https://www.mocky.io/v2/5cc8019d300000980a055e76"
+  },
+  attrsDefaultData: {
+    width: 360,
+    height: 0,
+    name: "file",
+    isShowTip: true,
+    data: {},
+    headers: {},
+    fileType: [],
+    withCredentials: false
+  },
+  common: {},
+  commonData: {},
+  commonDefaultData: {}
+};
+
+export default config;

+ 63 - 0
src/config/comps/week.ts

@@ -0,0 +1,63 @@
+import { Config } from "@/types/config";
+
+const config: Config = {
+  url: "https://element.eleme.cn/#/zh-CN/component/date-picker",
+  attrs: {
+    placeholder: {
+      type: "input",
+      label: "占位内容"
+    },
+    format: {
+      type: "input",
+      label: "显示在输入框中的格式"
+    },
+    clearable: {
+      type: "switch",
+      label: "是否显示清除按钮"
+    },
+    editable: {
+      type: "switch",
+      label: "文本框可输入"
+    },
+    size: {
+      type: "select",
+      label: "输入框尺寸",
+      options: [{ text: "默认", value: null }, "medium", "small", "mini"]
+    },
+    popperClass: {
+      type: "input",
+      label: "DatePicker 下拉框的类名"
+    },
+    prefixIcon: {
+      type: "input",
+      label: "自定义头部图标的类名"
+    },
+    clearIcon: {
+      type: "input",
+      label: "自定义清空图标的类名"
+    },
+    readonly: {
+      type: "switch",
+      label: "完全只读"
+    }
+  },
+  attrsData: {},
+  attrsDefaultData: {
+    readonly: false,
+    editable: true,
+    clearable: true,
+    size: null,
+    prefixIcon: "el-icon-date",
+    clearIcon: "el-icon-circle-close"
+  },
+  common: {
+    default: {
+      type: "week",
+      label: "默认值"
+    }
+  },
+  commonData: {},
+  commonDefaultData: {}
+};
+
+export default config;

+ 63 - 0
src/config/comps/year.ts

@@ -0,0 +1,63 @@
+import { Config } from "@/types/config";
+
+const config: Config = {
+  url: "https://element.eleme.cn/#/zh-CN/component/date-picker",
+  attrs: {
+    placeholder: {
+      type: "input",
+      label: "占位内容"
+    },
+    format: {
+      type: "input",
+      label: "显示在输入框中的格式"
+    },
+    clearable: {
+      type: "switch",
+      label: "是否显示清除按钮"
+    },
+    editable: {
+      type: "switch",
+      label: "文本框可输入"
+    },
+    size: {
+      type: "select",
+      label: "输入框尺寸",
+      options: [{ text: "默认", value: null }, "medium", "small", "mini"]
+    },
+    popperClass: {
+      type: "input",
+      label: "DatePicker 下拉框的类名"
+    },
+    prefixIcon: {
+      type: "input",
+      label: "自定义头部图标的类名"
+    },
+    clearIcon: {
+      type: "input",
+      label: "自定义清空图标的类名"
+    },
+    readonly: {
+      type: "switch",
+      label: "完全只读"
+    }
+  },
+  attrsData: {},
+  attrsDefaultData: {
+    readonly: false,
+    editable: true,
+    clearable: true,
+    size: null,
+    prefixIcon: "el-icon-date",
+    clearIcon: "el-icon-circle-close"
+  },
+  common: {
+    default: {
+      type: "year",
+      label: "默认值"
+    }
+  },
+  commonData: {},
+  commonDefaultData: {}
+};
+
+export default config;

+ 43 - 0
src/config/comps/yesno.ts

@@ -0,0 +1,43 @@
+import { Config } from "@/types/config";
+
+const config: Config = {
+  url: "https://www.yuque.com/chaojie-vjiel/vbwzgu/kz163g#y1sNr",
+  attrs: {
+    border: {
+      type: "switch",
+      label: "是否显示边框"
+    },
+    size: {
+      type: "select",
+      label: "Checkbox 的尺寸",
+      options: [{ text: "默认", value: null }, "medium", "small", "mini"],
+      vif: (data: AnyObj) => data.border
+    },
+    indeterminate: {
+      type: "switch",
+      label: "设置 indeterminate 状态,只负责样式控制"
+    }
+  },
+  attrsData: {},
+  attrsDefaultData: {
+    border: false,
+    size: null,
+    indeterminate: false
+  },
+  common: {
+    title: {
+      type: "input",
+      label: "说明文本"
+    },
+    default: {
+      type: "switch",
+      label: "默认值"
+    }
+  },
+  commonData: {
+    title: "是否选择"
+  },
+  commonDefaultData: {}
+};
+
+export default config;

+ 16 - 0
src/config/index.ts

@@ -0,0 +1,16 @@
+import { Config } from "@/types/config";
+import { getFileName } from "@/helpers/utils";
+
+// 获取comps目录下所有的文件
+const requireConfig = require.context("./comps", false, /\.ts$/);
+
+// 获取文件内容
+type ConfigList = { [key: string]: Config };
+const configList: ConfigList = requireConfig
+  .keys()
+  .reduce((acc: ConfigList, file: string) => {
+    acc[getFileName(file)] = requireConfig(file).default;
+    return acc;
+  }, {});
+
+export default configList;

+ 62 - 0
src/extend/codemirror.ts

@@ -0,0 +1,62 @@
+// language
+import "codemirror/mode/javascript/javascript.js";
+import "codemirror/mode/vue/vue.js";
+// theme css
+import "codemirror/theme/monokai.css";
+// require active-line.js
+import "codemirror/addon/selection/active-line.js";
+// styleSelectedText
+import "codemirror/addon/selection/mark-selection.js";
+import "codemirror/addon/search/searchcursor.js";
+// hint
+import "codemirror/addon/hint/show-hint.js";
+import "codemirror/addon/hint/show-hint.css";
+import "codemirror/addon/hint/javascript-hint.js";
+// highlightSelectionMatches
+import "codemirror/addon/scroll/annotatescrollbar.js";
+import "codemirror/addon/search/matchesonscrollbar.js";
+
+import "codemirror/addon/search/match-highlighter.js";
+// keyMap
+import "codemirror/mode/clike/clike.js";
+import "codemirror/addon/edit/matchbrackets.js";
+import "codemirror/addon/comment/comment.js";
+import "codemirror/addon/dialog/dialog.js";
+import "codemirror/addon/dialog/dialog.css";
+
+import "codemirror/addon/search/search.js";
+import "codemirror/keymap/sublime.js";
+// foldGutter
+import "codemirror/addon/fold/foldgutter.css";
+import "codemirror/addon/fold/brace-fold.js";
+import "codemirror/addon/fold/comment-fold.js";
+import "codemirror/addon/fold/foldcode.js";
+import "codemirror/addon/fold/foldgutter.js";
+import "codemirror/addon/fold/indent-fold.js";
+import "codemirror/addon/fold/markdown-fold.js";
+import "codemirror/addon/fold/xml-fold.js";
+
+export default {
+  options: {
+    tabSize: 4,
+    styleActiveLine: false,
+    lineNumbers: true,
+    styleSelectedText: false,
+    line: true,
+    foldGutter: true,
+    gutters: ["CodeMirror-linenumbers", "CodeMirror-foldgutter"],
+    highlightSelectionMatches: { showToken: /\w/, annotateScrollbar: true },
+    mode: "text/javascript",
+    // hint.js options
+    hintOptions: {
+      // 当匹配只有一项的时候是否自动补全
+      completeSingle: false
+    },
+    // 快捷键 可提供三种模式 sublime、emacs、vim
+    keyMap: "sublime",
+    matchBrackets: true,
+    showCursorWhenSelecting: true,
+    theme: "monokai",
+    extraKeys: { Ctrl: "autocomplete" }
+  }
+};

+ 27 - 0
src/extend/index.ts

@@ -0,0 +1,27 @@
+import Vue from "vue";
+
+import uploaderFile from "vue-ele-form-upload-file";
+import codemirror from "vue-ele-form-codemirror";
+import jsonEditor from "vue-ele-form-json-editor";
+import imageUploader from "vue-ele-form-image-uploader";
+import videoUploader from "vue-ele-form-video-uploader";
+import quillEditor from "vue-ele-form-quill-editor";
+import markdownEditor from "vue-ele-form-markdown-editor";
+import bmap from "vue-ele-form-bmap";
+import gallery from "vue-ele-form-gallery";
+import treeSelect from "vue-ele-form-tree-select";
+import dynamic from "vue-ele-form-dynamic";
+import tableEditor from "vue-ele-form-table-editor";
+
+Vue.component("upload-file", uploaderFile);
+Vue.component("codemirror", codemirror);
+Vue.component("json-editor", jsonEditor);
+Vue.component("image-uploader", imageUploader);
+Vue.component("video-uploader", videoUploader);
+Vue.component("quill-editor", quillEditor);
+Vue.component("markdown-editor", markdownEditor);
+Vue.component("bmap", bmap);
+Vue.component("gallery", gallery);
+Vue.component("tree-select", treeSelect);
+Vue.component("dynamic", dynamic);
+Vue.component("table-editor", tableEditor);

+ 63 - 0
src/helpers/api.ts

@@ -0,0 +1,63 @@
+import { getRemoteConfig } from "@/helpers/remoteConfig";
+import serialize from "serialize-javascript";
+import { Message } from "element-ui";
+
+interface ResponseData {
+  code: number;
+  msg: string;
+  data?: object;
+}
+// 将数据保存到服务器
+export const saveFormToServer = (
+  data: object
+): undefined | Promise<ResponseData> => {
+  const remoteConfig = getRemoteConfig();
+  if (remoteConfig) {
+    return fetch(
+      new Request(remoteConfig.updateUrl, {
+        method: remoteConfig.updateMethod,
+        body: serialize(data)
+      })
+    )
+      .then(res => {
+        if (res.ok) {
+          return res.json();
+        } else {
+          throw new Error(res.statusText);
+        }
+      })
+      .catch(err => {
+        Message.error("保存数据失败, 失败原因: " + err.message);
+      });
+  }
+};
+
+// 从服务器获取数据
+export const getFormFromServer = (): undefined | Promise<ResponseData> => {
+  const remoteConfig = getRemoteConfig();
+  if (remoteConfig) {
+    return fetch(
+      new Request(remoteConfig.getUrl, {
+        method: remoteConfig.getMethod
+      })
+    )
+      .then(res => {
+        if (res.ok) {
+          return res.json();
+        } else {
+          throw new Error(res.statusText);
+        }
+      })
+      .then(res => {
+        try {
+          res.data = eval("(" + res.data + ")");
+          return res;
+        } catch {
+          throw new TypeError("返回数据格式不正确: " + res);
+        }
+      })
+      .catch(err => {
+        Message.error("获取数据失败, 失败原因: " + err.message);
+      });
+  }
+};

+ 243 - 0
src/helpers/comps.ts

@@ -0,0 +1,243 @@
+import { Comp } from "@/types/comp";
+
+const comps: Comp[] = [
+  {
+    type: "input",
+    label: "单行输入框",
+    count: 4
+  },
+  {
+    type: "textarea",
+    label: "多行输入框",
+    count: 4
+  },
+  {
+    type: "quill-editor",
+    label: "富文本编辑器",
+    isExtend: true,
+    count: 4
+  },
+  {
+    type: "select",
+    label: "选择器",
+    count: 4
+  },
+  {
+    type: "number",
+    label: "数字",
+    count: 3
+  },
+  {
+    type: "checkbox",
+    label: "复选",
+    count: 3
+  },
+  {
+    type: "radio",
+    label: "单选",
+    count: 3
+  },
+  {
+    type: "switch",
+    label: "开关",
+    count: 2
+  },
+  {
+    type: "image-uploader",
+    label: "上传图片",
+    isExtend: true,
+    count: 2
+  },
+  {
+    type: "date",
+    label: "日期",
+    count: 2
+  },
+  {
+    type: "daterange",
+    label: "日期范围",
+    count: 2
+  },
+  {
+    type: "datetime",
+    label: "时间和日期",
+    count: 2
+  },
+  {
+    type: "datetimerange",
+    label: "日期和时间范围",
+    count: 2
+  },
+  {
+    type: "time",
+    label: "时间",
+    count: 2
+  },
+  {
+    type: "timerange",
+    label: "时间范围",
+    count: 2
+  },
+  {
+    type: "dates",
+    label: "多日期",
+    count: 2
+  },
+  {
+    type: "week",
+    label: "周",
+    count: 2
+  },
+  {
+    type: "month",
+    label: "月",
+    count: 2
+  },
+  {
+    type: "monthrange",
+    label: "月范围",
+    count: 2
+  },
+  {
+    type: "year",
+    label: "年",
+    count: 2
+  },
+  {
+    type: "color",
+    label: "颜色选择器",
+    count: 1
+  },
+  {
+    type: "cascader",
+    label: "级联选择器",
+    count: 1
+  },
+  {
+    type: "transfer",
+    label: "穿梭框",
+    count: 1
+  },
+  {
+    type: "cascader-panel",
+    label: "级联面板",
+    count: 1
+  },
+  {
+    type: "password",
+    label: "密码框",
+    count: 1
+  },
+  {
+    type: "upload-file",
+    label: "文件上传",
+    isExtend: true,
+    count: 1
+  },
+  {
+    type: "yesno",
+    label: "是否",
+    count: 1
+  },
+  {
+    type: "slider",
+    label: "滑块",
+    count: 0
+  },
+  {
+    type: "radio-button",
+    label: "单选按钮",
+    count: 0
+  },
+  {
+    type: "checkbox-button",
+    label: "复选按钮",
+    count: 0
+  },
+  {
+    type: "autocomplete",
+    label: "带建议的输入框",
+    count: 0
+  },
+  {
+    type: "rate",
+    label: "评分组件",
+    count: 0
+  },
+  {
+    type: "image",
+    label: "图片展示",
+    count: 0
+  },
+  {
+    type: "tag",
+    label: "标签",
+    count: 0
+  },
+  {
+    type: "text",
+    label: "静态文本",
+    count: 0
+  },
+  {
+    type: "button",
+    label: "按钮",
+    count: 0
+  },
+  {
+    type: "video-uploader",
+    label: "上传视频",
+    isExtend: true,
+    count: 0
+  },
+  {
+    type: "tree-select",
+    label: "树形下拉选择器",
+    isExtend: true,
+    count: 0
+  },
+  {
+    type: "dynamic",
+    label: "动态组件",
+    isExtend: true,
+    count: 0
+  },
+  {
+    type: "table-editor",
+    label: "表格内容编辑组件",
+    isExtend: true,
+    count: 0
+  },
+  {
+    type: "codemirror",
+    label: "代码编辑器",
+    isExtend: true,
+    count: 0
+  },
+  {
+    type: "json-editor",
+    label: "JSON编辑器",
+    isExtend: true,
+    count: 0
+  },
+  {
+    type: "markdown-editor",
+    label: "markdown编辑器",
+    isExtend: true,
+    count: 0
+  },
+  {
+    type: "bmap",
+    label: "地图",
+    isExtend: true,
+    count: 0
+  },
+  {
+    type: "gallery",
+    label: "图片及视频展示",
+    isExtend: true,
+    count: 0
+  }
+];
+
+export default comps;

+ 29 - 0
src/helpers/remoteConfig.ts

@@ -0,0 +1,29 @@
+type Methods = "GET" | "POST" | "PUT";
+
+interface RemoteConfig {
+  getMethod: Methods;
+  getUrl: string;
+  updateMethod: Methods;
+  updateUrl: string;
+}
+
+const saveKey = "remote-config";
+
+// 获取remoteConfig
+export function getRemoteConfig(): RemoteConfig | null {
+  if (localStorage.getItem(saveKey)) {
+    return JSON.parse(localStorage.getItem(saveKey) || "{}");
+  } else {
+    return null;
+  }
+}
+
+// 设置remoteConfig
+export function setRemoteConfig(data: RemoteConfig): void {
+  localStorage.setItem(saveKey, JSON.stringify(data));
+}
+
+// 移除远程
+export function removeRemoteConfig() {
+  localStorage.removeItem(saveKey);
+}

+ 95 - 0
src/helpers/tool.ts

@@ -0,0 +1,95 @@
+/******************/
+// 业务相关的工具函数
+/******************/
+
+import _ from "lodash-es";
+import comps from "./comps";
+import configList from "@/config";
+import { FormDescData, FormDesc } from "@/types/project";
+
+/**
+ * 修改label => key + label, 同时修改 attrs: { placeholder:  key + label } 更明确告知用户属性名
+ * @param obj 要更改的 formDesc
+ * @param exceptFields 需要排除的字段
+ */
+export function changeFormLabel(
+  obj: FormDesc,
+  exceptFields: string[] = []
+): FormDesc {
+  function deepChangeLabel(obj: FormDesc, parentKey?: string) {
+    // eslint-disable-next-line @typescript-eslint/no-use-before-define
+    return _.mapValues(obj, (o, key) => changeLabel(o, key, parentKey));
+  }
+
+  function changeLabel(obj: FormDescData, key: string, parentKey?: string) {
+    if (!exceptFields.includes(key)) {
+      if (obj.children) {
+        obj.children = deepChangeLabel(obj.children, key);
+      } else {
+        obj.attrs = {
+          ...obj.attrs,
+          placeholder: obj.label
+        };
+        obj.label = `${parentKey ? parentKey + "." : ""}${key}: ${obj.label}`;
+      }
+    }
+    return obj;
+  }
+  return deepChangeLabel(_.cloneDeep(obj));
+}
+
+/**
+ * 判断是否为 vscode
+ * vscode无法使用 localStorage
+ */
+export const isVscode: boolean = (() => {
+  try {
+    localStorage.getItem("test");
+    return false;
+  } catch {
+    return true;
+  }
+})();
+
+type formItem = {
+  type: string;
+  label: string;
+  field: string;
+};
+/**
+ * 新增表单项
+ * @param formItem 表单项
+ */
+import { FormItem } from "@/types/project";
+
+export function addFormItem(
+  type: string,
+  common: AnyObj = {},
+  attrs?: AnyObj
+): FormItem {
+  const {
+    attrsData = {},
+    attrsDefaultData = {},
+    commonData = {},
+    commonDefaultData = {}
+  } = configList[type] || {};
+
+  common.field = common.field || "key_" + Date.now();
+  common.label = common.label || _.find(comps, { type: "input" })?.label;
+
+  return Object.assign(
+    {},
+    _.cloneDeep(commonDefaultData),
+    _.cloneDeep(commonData),
+    {
+      ...(common as { field: string }),
+      type,
+      // 组件属性
+      attrs: {
+        ...attrs,
+        ..._.cloneDeep(attrsDefaultData),
+        ..._.cloneDeep(attrsData)
+      }
+    }
+  );
+}

+ 84 - 0
src/helpers/utils.ts

@@ -0,0 +1,84 @@
+/******************/
+// 与业务无关的工具函数
+/******************/
+import _ from "lodash-es";
+import { onMounted, onUnmounted } from "@vue/composition-api";
+
+/**
+ * 获取文件名
+ * @param str 文件路径
+ */
+export function getFileName(str: string) {
+  const pos1 = str.lastIndexOf("/");
+  const pos2 = str.lastIndexOf(".");
+  return str.substring(pos1 + 1, pos2);
+}
+
+// 将数组转为对象, 并删除原字段
+export function keyBy(list: any[], key: string): object {
+  // 1.数组转以 key值 的对象
+  const obj = _.keyBy(_.cloneDeep(list), key);
+  // 2.删除原 key
+  const deleteKey = (obj: object) => _.omit(obj, key);
+  return _.mapValues(obj, deleteKey);
+}
+
+// 过滤对象属性
+export function filterObjBy<T extends AnyObj>(
+  obj: T,
+  fn: AnyFunction
+): Partial<T> {
+  return Object.keys(obj).reduce((acc: AnyObj, key: string) => {
+    if (fn(obj[key], key)) {
+      acc[key] = obj[key];
+    }
+    return acc;
+  }, {});
+}
+
+// 深度去除默认值
+export function filterObjByDefault(
+  obj: AnyObj = {},
+  defaultObj: AnyObj = {}
+): AnyObj {
+  const filterDefault = (val: any, key: string) => defaultObj[key] !== val;
+  return filterObjBy(obj, (v, k) => {
+    if (_.isPlainObject(v)) {
+      obj[k] = filterObjByDefault(v, defaultObj[k]);
+      return Object.keys(obj[k]).length;
+    } else {
+      return filterDefault(v, k);
+    }
+  });
+}
+
+// 阻止页面刷新
+export function preventReloadWindow() {
+  const handler = (e: BeforeUnloadEvent) => {
+    e.returnValue = "重新加载此网站?";
+    return e;
+  };
+  onMounted(() => {
+    window.addEventListener("beforeunload", handler);
+  });
+  onUnmounted(() => {
+    window.removeEventListener("beforeunload", handler);
+  });
+}
+
+// 模糊搜索
+export function fuzzySearch(searcher: string, target: string): boolean {
+  if (target.length > searcher.length) return false;
+  let i = 0;
+  let j = 0;
+  outer: while (i < target.length) {
+    while (j < searcher.length && searcher[j] !== target[i]) {
+      j++;
+    }
+    if (j === searcher.length) {
+      break outer;
+    }
+    i++;
+  }
+  return i === target.length;
+}

+ 55 - 0
src/main.ts

@@ -0,0 +1,55 @@
+import "./extend";
+import Vue, { CreateElement } from "vue";
+import App from "./App.vue";
+import store from "./store";
+import ElementUI from "element-ui";
+import EleForm from "vue-ele-form";
+import codemirrorConfig from "./extend/codemirror";
+import VueCompositionAPI from "@vue/composition-api";
+import "@/helpers/api";
+// css样式
+import "normalize.css";
+import "element-ui/lib/theme-chalk/index.css";
+
+// 插件
+Vue.use(EleForm, {
+  upload: {
+    action: "https://www.mocky.io/v2/5cc8019d300000980a055e76",
+    responseFn(response: any) {
+      // 因为是 mock 地址, 所以, 总是返回同一张图片的URL, 正常使用的时候不会
+      return response.url;
+    }
+  },
+  "upload-file": {
+    responseFn(response: any, file: AnyObj) {
+      return {
+        name: file.name,
+        url: URL.createObjectURL(file.raw),
+        size: file.size
+      };
+    }
+  },
+  codemirror: codemirrorConfig,
+  bmap: {
+    ak: "9YLHZRPvUNLhi34Oh2ojqeGSpzCf1rVG"
+  }
+});
+Vue.use(ElementUI);
+Vue.use(VueCompositionAPI);
+
+// 配置
+Vue.config.productionTip = false;
+
+// 实例化
+const appVueOptions = {
+  store,
+  render: (h: CreateElement) => h(App)
+};
+
+const isLab = process.env.VUE_APP_IS_LAB;
+if (!isLab) {
+  new Vue(appVueOptions).$mount("#app");
+}
+
+// 支持Vue组件形式导出整个应用
+export default appVueOptions;

+ 13 - 0
src/shims-tsx.d.ts

@@ -0,0 +1,13 @@
+import Vue, { VNode } from "vue";
+
+declare global {
+  namespace JSX {
+    // tslint:disable no-empty-interface
+    interface Element extends VNode {}
+    // tslint:disable no-empty-interface
+    interface ElementClass extends Vue {}
+    interface IntrinsicElements {
+      [elem: string]: any;
+    }
+  }
+}

+ 9 - 0
src/shims-vue.d.ts

@@ -0,0 +1,9 @@
+declare module "*.vue" {
+  import Vue from "vue";
+  export default Vue;
+}
+
+declare module "*.ejs" {
+  let str: string;
+  export default str;
+}

+ 22 - 0
src/store/formAttrDefault.ts

@@ -0,0 +1,22 @@
+const formAttrDefault: AnyObj = {
+  inline: false,
+  disabled: false,
+  readonly: false,
+  isShowLabel: true,
+  isDialog: false,
+  isShowSubmitBtn: true,
+  isShowBackBtn: null,
+  isShowResetBtn: false,
+  isShowCancelBtn: null,
+  isResponsive: true,
+  submitBtnText: "提交",
+  cancelBtnText: "取消",
+  backBtnText: "返回",
+  resetBtnText: "重置",
+  labelWidth: null,
+  labelPosition: null,
+  span: null,
+  formBtnSize: null
+};
+
+export default formAttrDefault;

+ 275 - 0
src/store/index.ts

@@ -0,0 +1,275 @@
+import Vue from "vue";
+import Vuex from "vuex";
+import _ from "lodash-es";
+import comps from "@/helpers/comps";
+import { Message } from "element-ui";
+import { keyBy } from "@/helpers/utils";
+import listDefault from "./listDefault";
+import { Project } from "@/types/project";
+import persistedstate from "./persistedstate";
+import formAttrDefault from "./formAttrDefault";
+import { getFormFromServer } from "@/helpers/api";
+import { getRemoteConfig } from "@/helpers/remoteConfig";
+import { Comp } from "@/types/comp";
+
+Vue.use(Vuex);
+
+const remoteConfig = getRemoteConfig();
+
+interface StateData {
+  [key: string]: any;
+  projectList: Project[];
+  saveType: "local" | "remote";
+  currentProjectIndex: number | null;
+  currentFormIndex: number | null;
+  currentFormItemIndex: number | null;
+  comps: Comp[];
+}
+
+const store = new Vuex.Store<StateData>({
+  state: {
+    // 工程列表
+    projectList: [
+      {
+        // 工程名称
+        name: "默认工程",
+        // 表单列表
+        formList: [
+          {
+            // 表单名称
+            name: "示例表单",
+            // 表单属性
+            formAttr: _.cloneDeep(formAttrDefault),
+            // 表单项列表
+            formItemList: listDefault
+          }
+        ]
+      }
+    ],
+    // 当前工程索引
+    currentProjectIndex: 0,
+    // 当前表单索引
+    currentFormIndex: 0,
+    // 当前表单项索引
+    currentFormItemIndex: null,
+    // 保存数据方式
+    saveType: "local",
+    // 右侧组件列表
+    comps: comps
+  },
+  getters: {
+    // 根据使用的次数排序
+    sortedComps(state) {
+      return _.cloneDeep(_.sortBy(state.comps, "count")).reverse();
+    },
+    // 当前 project
+    currentProject(state) {
+      return state.currentProjectIndex !== null
+        ? state.projectList[state.currentProjectIndex]
+        : null;
+    },
+    // 当前表单
+    currentForm(state, getters) {
+      return getters.currentProject && state.currentFormIndex !== null
+        ? getters.currentProject.formList[state.currentFormIndex]
+        : null;
+    },
+    // 当前表单项
+    currentFormItem(state, getters) {
+      return getters.currentForm && state.currentFormItemIndex !== null
+        ? getters.currentForm.formItemList[state.currentFormItemIndex]
+        : null;
+    },
+    // 为经过过滤的 formAttr
+    currerntOriginFormAttr(state, getters) {
+      if (getters.currentForm) {
+        return getters.currentForm.formAttr;
+      } else {
+        return {};
+      }
+    },
+    // 过滤掉空值和默认值后的 formAttr
+    currentFormAttr(state, getters) {
+      if (getters.currentForm) {
+        // 判断是否为空或者是否和默认值相同
+        const isEmptyOrDefaultValue = (val: any, key: string) =>
+          _.isNil(val) || val === formAttrDefault[key];
+        return _.omitBy(getters.currentForm.formAttr, isEmptyOrDefaultValue);
+      } else {
+        return null;
+      }
+    },
+    // 当前 formItemList
+    currentFormItemList(state, getters) {
+      return getters.currentForm ? getters.currentForm.formItemList : null;
+    },
+    // 将数组转为对象
+    currentFormDesc(state, getters) {
+      return getters.currentFormItemList
+        ? keyBy(getters.currentFormItemList, "field")
+        : null;
+    }
+  },
+  mutations: {
+    // 更新组件使用次数
+    updateCompCount(state, compType) {
+      const compIndex = state.comps.findIndex(item => item.type === compType);
+      if (compIndex > -1) {
+        Vue.set(
+          state.comps[compIndex],
+          "count",
+          state.comps[compIndex].count + 1
+        );
+      }
+    },
+    // 更新所有
+    updateAll(state, data) {
+      Object.assign(state, data);
+    },
+    // 更新 project 索引
+    updateProjectIndex(state, index) {
+      state.currentProjectIndex = index;
+    },
+    // 更新 form 索引
+    updateFormIndex(state, index) {
+      state.currentFormIndex = index;
+    },
+    // 更新 formItem 索引
+    updateFormItemIndex(state, index) {
+      state.currentFormItemIndex = index;
+    },
+    // 新增 project
+    createProject(state, project) {
+      project.formList = [];
+      state.projectList.push(project);
+    },
+    // 新增 form
+    createForm(state, { projectIndex, form }) {
+      form.formItemList = [];
+      form.formAttr = _.cloneDeep(formAttrDefault);
+      state.projectList[projectIndex].formList.push(form);
+    },
+    // 新增 formItem
+    createFormItem(state, formItem) {
+      const formItemList = _.cloneDeep(store.getters.formItemList);
+      formItemList.push(formItem);
+      store.commit("updateCurrentForm", { formItemList });
+    },
+    // 通过索引删除 project
+    deleteProjectByIndex(state, index) {
+      // 置空索引
+      if (state.currentProjectIndex === index) {
+        state.currentProjectIndex = null;
+        state.currentFormIndex = null;
+        state.currentFormItemIndex = null;
+      }
+
+      // 删除
+      state.projectList.splice(index, 1);
+    },
+    // 通过索引删除 form
+    deleteFormByIndex(state, { projectIndex, formIndex }) {
+      // 置空索引
+      if (state.currentFormIndex == formIndex) {
+        state.currentFormIndex = null;
+        state.currentFormItemIndex = null;
+      }
+
+      // 删除
+      state.projectList[projectIndex].formList.splice(formIndex, 1);
+    },
+    // 通过索引删除 formItem
+    deleteFormItemByIndex(state, index) {
+      const formItemList = _.cloneDeep(store.getters.currentFormItemList);
+      formItemList.splice(index, 1);
+      store.commit("updateCurrentForm", { formItemList });
+    },
+    // 更新 project
+    updateProject(state, { projectIndex, project }) {
+      Object.assign(state.projectList[projectIndex], project);
+    },
+    // 更新 form
+    updateForm(state, { projectIndex, formIndex, form }) {
+      Object.assign(state.projectList[projectIndex].formList[formIndex], form);
+    },
+    // 更新表单项
+    updateCurrentItem(state, item) {
+      const { currentFormItemIndex } = state;
+      if (currentFormItemIndex !== null) {
+        const formItemList = _.cloneDeep(store.getters.currentFormItemList);
+        formItemList.splice(currentFormItemIndex, 1, item);
+        store.commit("updateCurrentForm", { formItemList });
+      }
+    },
+    // 更新表单项数据
+    updateCurrentItemAttrs(state, attrs) {
+      const {
+        currentFormIndex,
+        currentFormItemIndex,
+        currentProjectIndex
+      } = state;
+      if (
+        currentFormIndex !== null &&
+        currentFormItemIndex !== null &&
+        currentProjectIndex !== null
+      ) {
+        state.projectList[currentProjectIndex].formList[
+          currentFormIndex
+        ].formItemList[currentFormItemIndex].attrs = attrs;
+      }
+    },
+    // 更新当前表单
+    updateCurrentForm(state, form) {
+      if (
+        state.currentProjectIndex !== null &&
+        state.currentFormIndex !== null
+      ) {
+        Object.assign(
+          state.projectList[state.currentProjectIndex].formList[
+            state.currentFormIndex
+          ],
+          form
+        );
+      }
+    },
+    // 清空列表
+    clearCurrentForm(state) {
+      store.commit("updateCurrentForm", {
+        formItemList: [],
+        formAttr: _.cloneDeep(formAttrDefault)
+      });
+      state.currentFormItemIndex = null;
+    },
+    // 更新表单属性
+    updateCurrentFormAttr(state, formAttr) {
+      store.commit("updateCurrentForm", { formAttr });
+    },
+    // 修改列表
+    updateCurrentFormItemList(state, formItemList) {
+      store.commit("updateCurrentForm", { formItemList });
+    },
+    // 更新 saveType
+    updateSaveType(state, type) {
+      state.saveType = type;
+    }
+  },
+  actions: {
+    async updateStateFromRemote({ commit }) {
+      const res = await getFormFromServer();
+      if (res) {
+        if (res.code === 0) {
+          commit("updateAll", res.data);
+        } else {
+          Message.error("获取数据失败, 失败原因: " + res.msg);
+        }
+      }
+    }
+  },
+  plugins: persistedstate()
+});
+
+if (remoteConfig) {
+  store.dispatch("updateStateFromRemote");
+}
+
+export default store;

+ 32 - 0
src/store/listDefault.ts

@@ -0,0 +1,32 @@
+import { FormItemList } from "@/types/project";
+
+const list: FormItemList = [
+  {
+    field: "name",
+    type: "input",
+    label: "姓名(示例)",
+    layout: 12,
+    required: true,
+    attrs: {}
+  },
+  {
+    field: "age",
+    type: "input",
+    label: "年龄(示例)",
+    layout: 12,
+    required: true,
+    attrs: {
+      type: "number",
+      min: 10
+    }
+  },
+  {
+    field: "description",
+    type: "quill-editor",
+    label: "介绍",
+    layout: 24,
+    attrs: {}
+  }
+];
+
+export default list;

+ 11 - 0
src/store/persistedstate.ts

@@ -0,0 +1,11 @@
+import { isVscode } from "@/helpers/tool";
+import createPersistedState from "vuex-persistedstate";
+
+export default () => {
+  if (isVscode) {
+    // 在vscode插件下, 不支持localStorage
+    return [];
+  } else {
+    return [createPersistedState({ key: "vue-ele-form-generator" })];
+  }
+};

+ 10 - 0
src/types/common.d.ts

@@ -0,0 +1,10 @@
+// 任意对象
+interface AnyObj {
+  [key: string]: any;
+  [key: number]: any;
+}
+
+// 任意函数
+interface AnyFunction {
+  (...args: any[]): any;
+}

+ 6 - 0
src/types/comp.ts

@@ -0,0 +1,6 @@
+export interface Comp {
+  type: string;
+  label: string;
+  isExtend?: boolean;
+  count: number;
+}

Неке датотеке нису приказане због велике количине промена