commit 879cd36bafaf108225a79f0455cb54767d962fd8 Author: robin <1032740078@qq.com> Date: Thu Dec 1 21:48:35 2022 +0800 1 diff --git a/.cz-config.js b/.cz-config.js new file mode 100644 index 0000000..a8d7c28 --- /dev/null +++ b/.cz-config.js @@ -0,0 +1,32 @@ +module.exports = { + types: [ + {value: 'feat', name: 'feat: 新功能'}, + {value: 'fix', name: 'fix: 修复'}, + {value: 'docs', name: 'docs: 文档变更'}, + {value: 'style', name: 'style: 代码格式(不影响代码运行的变动)'}, + {value: 'cli', name: 'cli: 脚手架优化(不影响代码运行的变动)'}, + {value: 'refactor', name: 'refactor: 重构(既不是增加feature,也不是修复bug)'}, + {value: 'perf', name: 'perf: 性能优化'}, + {value: 'test', name: 'test: 增加测试'}, + {value: 'chore', name: 'chore: 构建过程或辅助工具的变动'}, + {value: 'revert', name: 'revert: 回退'}, + {value: 'build', name: 'build: 打包'} + ], + // override the messages, defaults are as follows + messages: { + type: '请选择提交类型:', + scope: '请输入文件修改范围(可选):', + // used if allowCustomScopes is true + customScope: '请输入修改范围(可选):', + subject: '请简要描述提交(必填):', + body: '请输入详细描述(可选,待优化去除,跳过即可):', + // breaking: 'List any BREAKING CHANGES (optional):\n', + footer: '请输入要关闭的issue(待优化去除,跳过即可):', + confirmCommit: '确认使用以上信息提交?(y/n/e/h)' + }, + allowCustomScopes: true, + // allowBreakingChanges: ['feat', 'fix'], + skipQuestions: ['body', 'footer'], + // limit subject length, commitlint默认是72 + subjectLimit: 72 +} \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100755 index 0000000..10e41f2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,68 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +stats.html + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# TypeScript v1 declaration files +typings/ + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variables file +.env + +# next.js build output +.next +.idea + +# custom +yarn.lock +/index.html +dist diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..210009a --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 xaboy + +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. diff --git a/README.md b/README.md new file mode 100644 index 0000000..7c0a77f --- /dev/null +++ b/README.md @@ -0,0 +1,141 @@ +

+ + + +

+ +# form-create-designer v3 + +**这个是 Vue3 版本** + +[![MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://github.com/xaboy/form-create-designer) +[![github](https://img.shields.io/badge/Author-xaboy-blue.svg)](https://github.com/xaboy) + +**form-create-designer 是基于 [@form-create/element-ui](https://github.com/xaboy/form-create) vue3版本实现的表单设计器组件。可以通过拖拽的方式快速创建表单,提高开发者对表单的开发效率,节省开发者的时间。** + +**[文档](http://designer.form-create.com/guide/) | [在线演示](http://form-create.com/v3/designer?fr=github) | [form-create 文档](http://form-create.com/v3/guide/)** + +> 如果对您有帮助,您可以点右上角 "Star" 支持一下 谢谢!本项目还在不断开发完善中,如有任何建议或问题[请在这里提出](https://github.com/xaboy/form-create-designer/issues/new) + +> 本项目QQ讨论群[629709230](https://jq.qq.com/?_wv=1027&k=F1FlEFIV) + + + +![demo1](http://form-create.com/img/designer-review.png) + +## 引入 + +**CDN:** + +```html + + + + + +``` + +**NodeJs:** + +```shell +npm install @form-create/designer@next +``` + +请自行导入`ElementPlus`并挂载 + +```js +import formCreate from '@form-create/element-ui' +import FcDesigner from '@form-create/designer' + +app.use(formCreate) +app.use(FcDesigner) +``` + +## 使用 + +```html + +``` + +## 组件`props` + +- **menu**`MenuList` 重新配置拖拽的组件 + +- **height**`int|string` 设计器组件高度, 默认`100%` + +## 组件方法 + +- 获取当前生成表单的生成规则 + + ```ts + type getRule = () => Rule[] + ``` + **示例: `this.$refs.designer.getRule()`** + +- 获取当前表单的全局配置 + + ```ts + type getOption = () => Object + ``` + +- 设置当前生成表单的规则 + + ```ts + type setRule = (rules: Rule[]) => void; + ``` + +- 设置当前表单的全局配置 + + ```ts + type setOption = (option: Object) => void; + ``` + +- 增加一组拖拽组件 + + ```ts + type addMenu = (menu: Menu) => void; + ``` +- 删除一组拖拽组件 + + ```ts + type removeMenu = (name: string) => void; + ``` + +- 批量覆盖插入拖拽组件 + + ```ts + type setMenuItem = (name: string, items: MenuItem[]) => void; + ``` + +- 插入一个拖拽组件到分组 + + ```ts + type appendMenuItem = (name:string, item: MenuItem) => void; + ``` + +- 删除一个拖拽组件 + + ```ts + type removeMenuItem = (item: string | MenuItem) => void; + ``` + +- 新增一个拖拽组件的生成规则 + + ```ts + type addComponent = (item: DragRule) => void; + ``` +> **提示! 内置的三个组件分组`name`分别为: `main`,`aide`,`layout`** + +## 捐赠 + +![donation.jpg](http://form-create.com/img/donation.jpg) + +## 联系 + +##### email : xaboy2005@qq.com + +## License + +[MIT](http://opensource.org/licenses/MIT) + +Copyright (c) 2021-present xaboy diff --git a/babel.config.js b/babel.config.js new file mode 100644 index 0000000..1a3e147 --- /dev/null +++ b/babel.config.js @@ -0,0 +1,4 @@ +module.exports = { + 'presets': [['@vue/cli-plugin-babel/preset', {'useBuiltIns': false}]], + 'plugins': ['@vue/babel-plugin-jsx'] +} diff --git a/examples/App.vue b/examples/App.vue new file mode 100644 index 0000000..f165a3c --- /dev/null +++ b/examples/App.vue @@ -0,0 +1,303 @@ + + + + + diff --git a/examples/index.html b/examples/index.html new file mode 100644 index 0000000..f145d99 --- /dev/null +++ b/examples/index.html @@ -0,0 +1,11 @@ + + + + + form-create-designer 示例 + + +
+
+ + diff --git a/examples/main.js b/examples/main.js new file mode 100644 index 0000000..8cfd809 --- /dev/null +++ b/examples/main.js @@ -0,0 +1,18 @@ +import {createApp} from 'vue'; +import ELEMENT from 'element-plus'; +import 'element-plus/dist/index.css'; +import formCreate from '@form-create/element-ui'; +import Antd from 'ant-design-vue'; +import App from './App'; +import 'ant-design-vue/dist/antd.css'; +import FcDesigner from '../src/index'; + +const app = createApp(App); + +app.use(Antd); +app.use(ELEMENT); +app.use(formCreate); +app.use(FcDesigner); + + +app.mount('#app') diff --git a/package.json b/package.json new file mode 100644 index 0000000..8cbb1d0 --- /dev/null +++ b/package.json @@ -0,0 +1,135 @@ +{ + "name": "@form-create/designer", + "version": "3.0.2", + "description": "好用的vue可视化表单设计器组件", + "unpkg": "./dist/index.umd.js", + "jsdelivr": "./dist/index.umd.js", + "typings": "./types/index.d.ts", + "main": "./dist/index.umd.js", + "module": "./dist/index.es.js", + "exports": { + ".": { + "import": "./dist/index.es.js", + "require": "./dist/index.umd.js" + } + }, + "scripts": { + "clean": "rimraf dist/", + "dev": "vue-cli-service serve", + "rollup": "rollup -c ./rollup.config.ts", + "build": "vite build --config ./vite.config.build.js", + "build:preview": "vite build --config ./vite.config.preview.js" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/xaboy/form-create-designer.git" + }, + "keywords": [ + "表单设计器", + "@form-create", + "form-builder", + "form-designer", + "draggable", + "form", + "components", + "vue3", + "element-ui", + "json-form", + "dynamic-form" + ], + "files": [ + "README.md", + "package.json", + "LICENSE", + "src", + "types", + "dist" + ], + "author": "xaboy", + "license": "MIT", + "bugs": { + "url": "https://github.com/xaboy/form-create-designer/issues" + }, + "homepage": "http://designer.form-create.com", + "publishConfig": { + "access": "public" + }, + "devDependencies": { + "@element-plus/icons-vue": "^0.2.6", + "@sixian/css-url": "^1.0.3", + "@types/chalk": "^2.2.0", + "@types/shelljs": "^0.8.9", + "@vitejs/plugin-vue": "^3.1.2", + "@vitejs/plugin-vue-jsx": "^2.0.1", + "@vue/babel-plugin-jsx": "^1.0.7", + "@vue/cli-plugin-babel": "^4.5.13", + "@vue/cli-service": "^4.5.3", + "@vue/compiler-sfc": "^3.0.11", + "babel-eslint": "^10.1.0", + "chalk": "^4.1.2", + "codemirror": "^5.60.0", + "commander": "^6.0.0", + "commitizen": "^4.1.2", + "cross-env": "^7.0.2", + "css-loader": "^4.2.1", + "cssnano": "^5.1.13", + "cssnano-preset-advanced": "^5.3.8", + "cz-conventional-changelog": "^3.2.0", + "cz-customizable": "^6.3.0", + "dayjs": "^1.10.7", + "element-plus": "^2.0.1", + "eslint": "^7.7.0", + "eslint-plugin-vue": "^6.2.2", + "esno": "^0.9.1", + "execa": "^5.1.1", + "fast-glob": "^3.2.7", + "figlet": "^1.5.0", + "fs-extra": "^10.0.0", + "html-webpack-plugin": "^4.3.0", + "humps": "^2.0.1", + "husky": "^4.2.5", + "jsonlint-mod": "^1.7.6", + "lint-staged": "^10.2.11", + "npm-run-all": "^4.1.5", + "ora": "^5.0.0", + "postcss": "^8.4.17", + "rimraf": "^3.0.2", + "rollup-plugin-visualizer": "^5.8.2", + "shelljs": "^0.8.4", + "stringify-author": "^0.1.3", + "tslib": "^2.3.1", + "typescript": "^4.4.3", + "vite": "^3.1.4", + "vite-plugin-banner": "^0.5.0", + "vite-plugin-css-injected-by-js": "^2.1.0", + "vue": "^3.1.5", + "vue-loader": "^15.9.3", + "vue-style-loader": "^4.1.2", + "vue-template-compiler": "^2.6.11" + }, + "config": { + "commitizen": { + "path": "./node_modules/cz-customizable" + } + }, + "husky": { + "hooks": { + "pre-commit": "lint-staged" + } + }, + "lint-staged": { + "*.{js,jsx,vue}": [ + "eslint --fix", + "git add" + ] + }, + "dependencies": { + "@form-create/ant-design-vue": "next", + "@form-create/component-wangeditor": "^3.1", + "@form-create/designer": "^1.0.8", + "@form-create/element-ui": "^3.1.16", + "@form-create/utils": "^3.1.15", + "ant-design-vue": "^3.2.15", + "vuedraggable": "4.1.0" + } +} diff --git a/src/components/DragBox.vue b/src/components/DragBox.vue new file mode 100644 index 0000000..0733977 --- /dev/null +++ b/src/components/DragBox.vue @@ -0,0 +1,33 @@ + diff --git a/src/components/DragTool.vue b/src/components/DragTool.vue new file mode 100644 index 0000000..1a89124 --- /dev/null +++ b/src/components/DragTool.vue @@ -0,0 +1,136 @@ + + + + + diff --git a/src/components/FcDesigner.vue b/src/components/FcDesigner.vue new file mode 100644 index 0000000..9c44256 --- /dev/null +++ b/src/components/FcDesigner.vue @@ -0,0 +1,861 @@ + + + + + + + diff --git a/src/components/Fetch.vue b/src/components/Fetch.vue new file mode 100644 index 0000000..96a69fe --- /dev/null +++ b/src/components/Fetch.vue @@ -0,0 +1,162 @@ + + + + diff --git a/src/components/IconRefresh.vue b/src/components/IconRefresh.vue new file mode 100644 index 0000000..2796dfe --- /dev/null +++ b/src/components/IconRefresh.vue @@ -0,0 +1,14 @@ + + + diff --git a/src/components/Required.vue b/src/components/Required.vue new file mode 100644 index 0000000..7fcac58 --- /dev/null +++ b/src/components/Required.vue @@ -0,0 +1,64 @@ + + + + + diff --git a/src/components/Struct.vue b/src/components/Struct.vue new file mode 100644 index 0000000..59ecfac --- /dev/null +++ b/src/components/Struct.vue @@ -0,0 +1,127 @@ + + + + + diff --git a/src/components/TableOptions.vue b/src/components/TableOptions.vue new file mode 100644 index 0000000..15ba384 --- /dev/null +++ b/src/components/TableOptions.vue @@ -0,0 +1,75 @@ + + + + + diff --git a/src/components/Validate.vue b/src/components/Validate.vue new file mode 100644 index 0000000..edfd987 --- /dev/null +++ b/src/components/Validate.vue @@ -0,0 +1,229 @@ + + + + + diff --git a/src/config/base/field.js b/src/config/base/field.js new file mode 100644 index 0000000..20485e2 --- /dev/null +++ b/src/config/base/field.js @@ -0,0 +1,84 @@ +import IconRefresh from '../../components/IconRefresh.vue'; +import {markRaw} from 'vue'; + +export default function field() { + return [ + { + type: 'input', + field: 'field', + value: '', + title: '字段 ID', + }, { + type: 'input', + field: 'title', + value: '', + title: '字段名称', + }, { + type: 'input', + field: 'info', + value: '', + title: '提示信息', + }, { + type: 'Struct', + field: '_control', + value: [], + title: '联动数据', + props: { + defaultValue: [], + validate(val) { + if (!Array.isArray(val)) return false; + if (!val.length) return true; + return !val.some(({rule}) => { + return !Array.isArray(rule); + }); + } + } + }, { + type: 'col', + props: { + span: 24 + }, + children: [ + { + type: 'el-button', + props: { + type: 'primary', + size: 'small', + }, + inject: true, + on: { + click({$f}) { + const rule = $f.activeRule; + if (rule) { + rule.__fc__.updateKey(); + rule.value = undefined; + rule.__fc__.$api.sync(rule); + } + }, + }, + native: true, + children: [{type: 'i', class: 'fc-icon icon-delete'}, '清空值'] + }, { + type: 'el-button', + props: { + type: 'success', + size: 'small', + icon: markRaw(IconRefresh), + }, + inject: true, + on: { + click({$f}) { + const rule = $f.activeRule; + if (rule) { + rule.__fc__.updateKey(true); + rule.__fc__.$api.sync(rule); + } + }, + }, + native: true, + children: ['刷新'] + }, + ] + } + ]; +} diff --git a/src/config/base/form.js b/src/config/base/form.js new file mode 100644 index 0000000..4cee918 --- /dev/null +++ b/src/config/base/form.js @@ -0,0 +1,55 @@ +export default function form() { + return [ + { + type: 'radio', + field: 'labelPosition', + value: 'left', + title: '标签位置', + options: [ + {value: 'right', label: 'right'}, + {value: 'left', label: 'left'}, + {value: 'top', label: 'top'}, + ] + }, { + type: 'radio', + field: 'size', + value: 'small', + title: '表单尺寸', + options: [ + {value: 'large', label: 'large'}, + {value: 'default', label: 'default'}, + {value: 'small', label: 'small'}, + ] + }, { + type: 'input', + field: 'labelWidth', + value: '125px', + title: '标签宽度', + }, { + type: 'switch', + field: 'hideRequiredAsterisk', + value: false, + title: '隐藏必填字段的标签旁边的红色星号', + }, { + type: 'switch', + field: 'showMessage', + value: true, + title: '显示校验错误信息', + }, { + type: 'switch', + field: 'inlineMessage', + value: false, + title: '以行内形式展示校验信息', + }, { + type: 'switch', + field: 'formCreateSubmitBtn', + value: true, + title: '是否显示表单提交按钮', + }, { + type: 'switch', + field: 'formCreateResetBtn', + value: false, + title: '是否显示表单重置按钮', + }, + ]; +} diff --git a/src/config/base/validate.js b/src/config/base/validate.js new file mode 100644 index 0000000..91b42ab --- /dev/null +++ b/src/config/base/validate.js @@ -0,0 +1,9 @@ +export default function validate() { + return [ + { + type: 'validate', + field: 'validate', + value: [] + }, + ]; +} \ No newline at end of file diff --git a/src/config/menu.js b/src/config/menu.js new file mode 100644 index 0000000..e71a4bc --- /dev/null +++ b/src/config/menu.js @@ -0,0 +1,51 @@ +import radio from './rule/radio'; +import checkbox from './rule/checkbox'; +import input from './rule/input'; +import number from './rule/number'; +import select from './rule/select'; +import _switch from './rule/switch'; +import slider from './rule/slider'; +import time from './rule/time'; +import date from './rule/date'; +import rate from './rule/rate'; +import color from './rule/color'; +import row from './rule/row'; +import divider from './rule/divider'; +import cascader from './rule/cascader'; +import upload from './rule/upload'; +import transfer from './rule/transfer'; +import tree from './rule/tree'; +import alert from './rule/alert'; +import span from './rule/span'; +import space from './rule/space'; +import button from './rule/button'; +import editor from './rule/editor'; +import tab from './rule/tab'; +import drawer from './rule/drawer'; +import image from './rule/image.ts'; +// 还需要在./rule/index.js文件里注册 +export default function createMenu() { + return [ + { + name: 'main', + title: '表单组件', + list: [ + input, number, radio, checkbox, select, _switch, time, date, slider, rate, color, cascader, upload, transfer, tree, editor + ] + }, + { + name: 'aide', + title: '辅助组件', + list: [ + alert, button, span, divider,image + ] + }, + { + name: 'layout', + title: '布局组件', + list: [ + row, tab, space,drawer + ] + }, + ]; +} diff --git a/src/config/rule/alert.js b/src/config/rule/alert.js new file mode 100644 index 0000000..4e6f5d2 --- /dev/null +++ b/src/config/rule/alert.js @@ -0,0 +1,45 @@ +const label = '提示'; +const name = 'el-alert'; + +export default { + icon: 'icon-alert', + label, + name, + rule() { + return { + type: name, + props: { + title: '提示', + description: 'form-create', + type: 'success', + effect: 'dark', + }, + children: [] + }; + }, + props() { + return [{type: 'input', field: 'title', title: '标题'}, { + type: 'select', + field: 'type', + title: '主题', + options: [{label: 'success', value: 'success'}, {label: 'warning', value: 'warning'}, { + label: 'info', + value: 'info' + }, {label: 'error', value: 'error'}] + }, {type: 'input', field: 'description', title: '辅助性文字'}, { + type: 'switch', + field: 'closable', + title: '是否可关闭', + value: true + }, {type: 'switch', field: 'center', title: '文字是否居中', value: true}, { + type: 'input', + field: 'closeText', + title: '关闭按钮自定义文本' + }, {type: 'switch', field: 'showIcon', title: '是否显示图标'}, { + type: 'select', + field: 'effect', + title: '选择提供的主题', + options: [{label: 'light', value: 'light'}, {label: 'dark', value: 'dark'}] + }]; + } +}; \ No newline at end of file diff --git a/src/config/rule/button.js b/src/config/rule/button.js new file mode 100644 index 0000000..06b2a0a --- /dev/null +++ b/src/config/rule/button.js @@ -0,0 +1,54 @@ +const label = '按钮'; +const name = 'el-button'; + +export default { + icon: 'icon-button', + label, + name, + mask: false, + rule() { + return { + type: name, + props: {}, + children: ['按钮'], + }; + }, + props() { + return [{ + type: 'input', + field: 'formCreateChild', + title: '内容', + }, { + type: 'select', + field: 'size', + title: '尺寸', + options: [{label: 'large', value: 'large'}, {label: 'default', value: 'default'}, { + label: 'small', + value: 'small' + }] + }, { + type: 'select', + field: 'type', + title: '类型', + options: [{label: 'primary', value: 'primary'}, { + label: 'success', + value: 'success' + }, {label: 'warning', value: 'warning'}, {label: 'danger', value: 'danger'}, { + label: 'info', + value: 'info' + }] + }, {type: 'switch', field: 'plain', title: '是否朴素按钮'}, { + type: 'switch', + field: 'round', + title: '是否圆角按钮' + }, {type: 'switch', field: 'circle', title: '是否圆形按钮'}, { + type: 'switch', + field: 'loading', + title: '是否加载中状态' + }, {type: 'switch', field: 'disabled', title: '是否禁用状态'}, { + type: 'input', + field: 'icon', + title: '图标类名' + }]; + } +}; diff --git a/src/config/rule/cascader.js b/src/config/rule/cascader.js new file mode 100644 index 0000000..ce1fa21 --- /dev/null +++ b/src/config/rule/cascader.js @@ -0,0 +1,180 @@ +import uniqueId from '@form-create/utils/lib/unique'; +import {makeOptionsRule, makeRequiredRule} from '../../utils/index'; + +const label = '级联选择器'; +const name = 'cascader'; + +export default { + icon: 'icon-cascader', + label, + name, + rule() { + return { + type: name, + field: uniqueId(), + title: label, + info: '', + effect: { + fetch: '' + }, + props: { + 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 表单' + }] + }] + }] + } + }; + }, + props() { + return [ + makeRequiredRule(), + makeOptionsRule('props.options', false), + { + type: 'Object', + field: 'props', + title: '配置选项', + props: { + rule: [{ + type: 'select', + field: 'expandTrigger', + title: '次级菜单的展开方式', + options: [{label: 'click', value: 'click'}, {label: 'hover', value: 'hover'}] + }, {type: 'switch', field: 'multiple', title: '是否多选'}, { + type: 'switch', + field: 'checkStrictly', + title: '是否严格的遵守父子节点不互相关联' + }, { + type: 'switch', + field: 'emitPath', + title: '在选中节点改变时,是否返回由该节点所在的各级菜单的值所组成的数组,若设置 false,则只返回该节点的值', + value: true + }, {type: 'input', field: 'value', title: '指定选项的值为选项对象的某个属性值'}, { + type: 'input', + field: 'label', + title: '指定选项标签为选项对象的某个属性值' + }, {type: 'input', field: 'children', title: '指定选项的子选项为选项对象的某个属性值'}, { + type: 'input', + field: 'disabled', + title: '指定选项的禁用为选项对象的某个属性值' + }, {type: 'input', field: 'leaf', title: '指定选项的叶子节点的标志位为选项对象的某个属性值'}] + } + }, { + type: 'select', + field: 'size', + title: '尺寸', + options: [{label: 'large', value: 'large'}, {label: 'default', value: 'default'}, { + label: 'small', + value: 'small' + }] + }, {type: 'input', field: 'placeholder', title: '输入框占位文本'}, { + type: 'switch', + field: 'disabled', + title: '是否禁用' + }, {type: 'switch', field: 'clearable', title: '是否支持清空选项'}, { + type: 'switch', + field: 'showAllLevels', + title: '输入框中是否显示选中值的完整路径', + value: true + }, {type: 'switch', field: 'collapseTags', title: '多选模式下是否折叠Tag'}, { + type: 'input', + field: 'separator', + title: '选项分隔符' + }]; + } +}; diff --git a/src/config/rule/checkbox.js b/src/config/rule/checkbox.js new file mode 100644 index 0000000..06a7691 --- /dev/null +++ b/src/config/rule/checkbox.js @@ -0,0 +1,45 @@ +import uniqueId from '@form-create/utils/lib/unique'; +import {makeOptionsRule, makeRequiredRule} from '../../utils/index'; + +const label = '多选框'; +const name = 'checkbox'; + +export default { + icon: 'icon-checkbox', + label, + name, + rule() { + return { + type: name, + field: uniqueId(), + title: label, + info: '', + effect: { + fetch: '' + }, + props: {}, + options: [ + {value: '1', label: '选项1'}, + {value: '2', label: '选项2'}, + ] + }; + }, + props() { + return [ + makeRequiredRule(), makeOptionsRule('options'), + { + type: 'switch', + field: 'type', + title: '按钮类型', + props: {activeValue: 'button', inactiveValue: 'default'} + }, {type: 'switch', field: 'disabled', title: '是否禁用'}, { + type: 'inputNumber', + field: 'min', + title: '可被勾选的 checkbox 的最小数量' + }, {type: 'inputNumber', field: 'max', title: '可被勾选的 checkbox 的最大数量'}, { + type: 'input', + field: 'textColor', + title: '按钮形式的 Checkbox 激活时的文本颜色' + }, {type: 'input', field: 'fill', title: '按钮形式的 Checkbox 激活时的填充色和边框色'}]; + } +}; diff --git a/src/config/rule/col.js b/src/config/rule/col.js new file mode 100644 index 0000000..8a79fa5 --- /dev/null +++ b/src/config/rule/col.js @@ -0,0 +1,23 @@ +const name = 'col'; + +export default { + name, + drag: true, + dragBtn: false, + inside: true, + mask: false, + rule() { + return { + type: name, + props: {span: 12}, + children: [] + }; + }, + props() { + return [ + {type: 'slider', field: 'span', title: '栅格占据的列数', value: 12, props: {min: 0, max: 24}}, + {type: 'slider', field: 'offset', title: '栅格左侧的间隔格数', props: {min: 0, max: 24}}, + {type: 'slider', field: 'push', title: '栅格向右移动格数', props: {min: 0, max: 24}}, + {type: 'slider', field: 'pull', title: '栅格向左移动格数', props: {min: 0, max: 24}}]; + } +}; diff --git a/src/config/rule/color.js b/src/config/rule/color.js new file mode 100644 index 0000000..f3a8e4c --- /dev/null +++ b/src/config/rule/color.js @@ -0,0 +1,36 @@ +import uniqueId from '@form-create/utils/lib/unique'; +import {makeRequiredRule} from '../../utils'; + +const label = '颜色选择器'; +const name = 'colorPicker'; + +export default { + icon: 'icon-color', + label, + name, + rule() { + return { + type: name, + field: uniqueId(), + title: label, + info: '', + props: {}, + }; + }, + props() { + return [ + makeRequiredRule(), {type: 'switch', field: 'disabled', title: '是否禁用'}, { + type: 'switch', + field: 'showAlpha', + title: '是否支持透明度选择' + }, { + type: 'select', + field: 'colorFormat', + title: '颜色的格式', + options: [{label: 'hsl', value: 'hsl'}, {label: 'hsv', value: 'hsv'}, { + label: 'hex', + value: 'hex' + }, {label: 'rgb', value: 'rgb'}] + }]; + } +}; diff --git a/src/config/rule/date.js b/src/config/rule/date.js new file mode 100644 index 0000000..80a2104 --- /dev/null +++ b/src/config/rule/date.js @@ -0,0 +1,75 @@ +import uniqueId from '@form-create/utils/lib/unique'; +import {makeRequiredRule} from '../../utils'; + +const label = '日期选择器'; +const name = 'datePicker'; + +export default { + icon: 'icon-date', + label, + name, + rule() { + return { + type: name, + field: uniqueId(), + title: label, + info: '', + props: {}, + }; + }, + props() { + return [makeRequiredRule(), { + type: 'Struct', + field: 'pickerOptions', + title: '当前时间日期选择器特有的选项', + props: {defaultValue: {}} + }, {type: 'switch', field: 'readonly', title: '完全只读'}, { + type: 'switch', + field: 'disabled', + title: '禁用' + }, { + type: 'select', + field: 'type', + title: '显示类型', + options: [{label: 'year', value: 'year'}, {label: 'month', value: 'month'}, { + label: 'date', + value: 'date' + }, {label: 'dates', value: 'dates'}, {label: 'week', value: 'week'}, { + label: 'datetime', + value: 'datetime' + }, {label: 'datetimerange', value: 'datetimerange'}, { + label: 'daterange', + value: 'daterange' + }, {label: 'monthrange', value: 'monthrange'}] + }, {type: 'switch', field: 'editable', title: '文本框可输入', value: true}, { + type: 'switch', + field: 'clearable', + title: '是否显示清除按钮', + value: true + }, {type: 'input', field: 'placeholder', title: '非范围选择时的占位内容'}, { + type: 'input', + field: 'startPlaceholder', + title: '范围选择时开始日期的占位内容' + }, {type: 'input', field: 'endPlaceholder', title: '范围选择时结束日期的占位内容'}, { + type: 'input', + field: 'format', + title: '显示在输入框中的格式' + }, { + type: 'select', + field: 'align', + title: '对齐方式', + options: [{label: 'left', value: 'left'}, {label: 'center', value: 'center'}, { + label: 'right', + value: 'right' + }, {label: 'left', value: 'left'}] + }, {type: 'input', field: 'rangeSeparator', title: '选择范围时的分隔符'}, { + type: 'switch', + field: 'unlinkPanels', + title: '在范围选择器里取消两个日期面板之间的联动' + }, {type: 'input', field: 'prefixIcon', title: '自定义头部图标的类名'}, { + type: 'input', + field: 'clearIcon', + title: '自定义清空图标的类名' + }]; + } +}; diff --git a/src/config/rule/divider.js b/src/config/rule/divider.js new file mode 100644 index 0000000..6b39933 --- /dev/null +++ b/src/config/rule/divider.js @@ -0,0 +1,37 @@ +const label = '分割线'; +const name = 'el-divider'; + +export default { + icon: 'icon-divider', + label, + name, + rule() { + return { + type: name, + props: {}, + wrap: {show: false}, + native: false, + children: [''], + }; + }, + props() { + return [{ + type: 'select', + field: 'direction', + title: '设置分割线方向', + options: [{label: 'horizontal', value: 'horizontal'}, {label: 'vertical', value: 'vertical'}] + }, { + type: 'input', + field: 'formCreateChild', + title: '设置分割线文案', + }, { + type: 'select', + field: 'contentPosition', + title: '设置分割线文案的位置', + options: [{label: 'left', value: 'left'}, {label: 'right', value: 'right'}, { + label: 'center', + value: 'center' + }] + }]; + } +}; \ No newline at end of file diff --git a/src/config/rule/drawer.js b/src/config/rule/drawer.js new file mode 100644 index 0000000..fdcc967 --- /dev/null +++ b/src/config/rule/drawer.js @@ -0,0 +1,27 @@ +const label = "浮层面板"; +const name = "a-drawer"; + +export default { + icon: "icon-row", + label, + name, + drag: true, + dragBtn: false, + mask: false, + rule() { + return { + type : name, + props : { + title:"Create a new account", + width:"720", + visible:false, + }, + children: [] + }; + }, + props() { + return [ + { type: "input", field: "width", title: "宽度" }, + ]; + } +}; diff --git a/src/config/rule/editor.js b/src/config/rule/editor.js new file mode 100644 index 0000000..e41b676 --- /dev/null +++ b/src/config/rule/editor.js @@ -0,0 +1,23 @@ +import uniqueId from '@form-create/utils/lib/unique'; +import {makeRequiredRule} from '../../utils'; + +const label = '富文本框'; +const name = 'fc-editor'; + +export default { + icon: 'icon-editor', + label, + name, + rule() { + return { + type: name, + field: uniqueId(), + title: label, + info: '', + props: {}, + }; + }, + props() { + return [makeRequiredRule(), {type: 'switch', field: 'disabled', title: '是否禁用'}]; + } +}; diff --git a/src/config/rule/image.ts b/src/config/rule/image.ts new file mode 100644 index 0000000..e4a929a --- /dev/null +++ b/src/config/rule/image.ts @@ -0,0 +1,57 @@ +// @ts-ignore +// import FcDesigner from "@form-create/ant-design-vue"; +import {makeRequiredRule,makeOptionsRule} from '../../utils'; +const label = "图片"; +const name = "a-image"; +const icon = "icon-xingzhuang-tupian"; +let i = 1; +const uniqueId = () => `uni${i++}`; + +export default { + //拖拽组件的图标 + icon, + //拖拽组件的名称 + label, + //拖拽组件的 key + name, + //拖拽组件的生成规则 + rule() { + //如果在 props 方法中需要修改 rule 的属性,需要提前在 rule 上定义对应的属性 + return { + //生成组件的名称 + type: name, + //field 自定不能重复,所以这里每次都会生成一个新的 + field: uniqueId(), + title: label, + info: "", + effect: { + fetch: "" + }, + //这里设置组件的默认props配置项, 在下面的 props 方法里面设置无效 + props: { + src: "https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png", + width: 200, + placeholder: true, + }, + options: [ + + ], + }; + }, + + //拖拽组件配置项(props)的生成规则 + props() { + return [ + //生成`checkbox`组件的`options`配置规则 + // makeRequiredRule(), + // makeOptionsRule("props"), + { type: "input", field: "src", title: "图片地址(支持http/base64)" }, + { type: "input", field: "fallback", title: "加载失败容错地址" }, + { type: "input", field: "width", title: "图像宽度" }, + { type: "input", field: "height", title: "图像高度" }, + { type: "switch", field: "preview", title: "预览参数,为 false 时禁用" }, + { type: "switch", field: "placeholder", title: "加载占位, 为 true 时使用默认占位"}, + { type: "input", field: "alt", title: "图像描述"}, + ]; + } +}; diff --git a/src/config/rule/index.js b/src/config/rule/index.js new file mode 100644 index 0000000..aa9b56b --- /dev/null +++ b/src/config/rule/index.js @@ -0,0 +1,61 @@ +import radio from './radio'; +import checkbox from './checkbox'; +import input from './input'; +import number from './number'; +import select from './select'; +import _switch from './switch'; +import slider from './slider'; +import time from './time'; +import date from './date'; +import rate from './rate'; +import color from './color'; +import row from './row'; +import col from './col'; +import tabPane from './tabPane'; +import divider from './divider'; +import cascader from './cascader'; +import upload from './upload'; +import transfer from './transfer'; +import tree from './tree'; +import alert from './alert'; +import span from './span'; +import space from './space'; +import tab from './tab'; +import button from './button'; +import editor from './editor'; +import drawer from './drawer'; +import image from './image.ts'; + +// 还需要在../menu.js文件里注册 + +const ruleList = { + [drawer.name]: drawer, + [radio.name]: radio, + [checkbox.name]: checkbox, + [input.name]: input, + [number.name]: number, + [select.name]: select, + [_switch.name]: _switch, + [slider.name]: slider, + [time.name]: time, + [date.name]: date, + [rate.name]: rate, + [color.name]: color, + [row.name]: row, + [col.name]: col, + [tab.name]: tab, + [tabPane.name]: tabPane, + [divider.name]: divider, + [cascader.name]: cascader, + [upload.name]: upload, + [transfer.name]: transfer, + [tree.name]: tree, + [alert.name]: alert, + [span.name]: span, + [space.name]: space, + [button.name]: button, + [editor.name]: editor, + [image.name]: image, +}; + +export default ruleList; diff --git a/src/config/rule/input.js b/src/config/rule/input.js new file mode 100644 index 0000000..6c935fd --- /dev/null +++ b/src/config/rule/input.js @@ -0,0 +1,65 @@ +import uniqueId from '@form-create/utils/lib/unique'; +import {makeRequiredRule} from '../../utils'; + +const label = '输入框'; +const name = 'input'; + +export default { + icon: 'icon-input', + label, + name, + rule() { + return { + type: name, + field: uniqueId(), + title: label, + info: '', + props: {} + }; + }, + props() { + return [makeRequiredRule(), { + type: 'select', + field: 'type', + title: '类型', + options: [{label: 'text', value: 'text'}, { + label: 'textarea', + value: 'textarea' + }, {label: 'number', value: 'number'}, {label: 'password', value: 'password'}] + }, {type: 'inputNumber', field: 'maxlength', title: '最大输入长度'}, { + type: 'inputNumber', + field: 'minlength', + title: '最小输入长度' + }, {type: 'switch', field: 'showWordLimit', title: '是否显示输入字数统计'}, { + type: 'input', + field: 'placeholder', + title: '输入框占位文本' + }, {type: 'switch', field: 'clearable', title: '是否可清空'}, { + type: 'switch', + field: 'showPassword', + title: '是否显示切换密码图标' + }, {type: 'switch', field: 'disabled', title: '禁用'}, { + type: 'input', + field: 'prefixIcon', + title: '输入框头部图标' + }, {type: 'input', field: 'suffixIcon', title: '输入框尾部图标'}, { + type: 'inputNumber', + field: 'rows', + info: '只对 type="textarea" 有效', + title: '输入框行数' + }, { + type: 'select', + field: 'autocomplete', + title: '自动补全', + options: [{label: 'on', value: 'on'}, {label: 'off', value: 'off'}] + }, {type: 'switch', field: 'readonly', title: '是否只读'}, { + type: 'select', + field: 'resize', + title: '控制是否能被用户缩放', + options: [{label: 'none', value: 'none'}, {label: 'both', value: 'both'}, { + label: 'horizontal', + value: 'horizontal' + }, {label: 'vertical', value: 'vertical'}] + }, {type: 'switch', field: 'autofocus', title: '自动获取焦点'}]; + } +}; diff --git a/src/config/rule/number.js b/src/config/rule/number.js new file mode 100644 index 0000000..55937bf --- /dev/null +++ b/src/config/rule/number.js @@ -0,0 +1,41 @@ +import uniqueId from '@form-create/utils/lib/unique'; +import {makeRequiredRule} from '../../utils'; + +const label = '计数器'; +const name = 'inputNumber'; + +export default { + icon: 'icon-number', + label, + name, + rule() { + return { + type: name, + field: uniqueId(), + title: label, + info: '', + props: {} + }; + }, + props() { + return [makeRequiredRule(), {type: 'inputNumber', field: 'min', title: '设置计数器允许的最小值'}, { + type: 'inputNumber', + field: 'max', + title: '设置计数器允许的最大值' + }, {type: 'inputNumber', field: 'step', title: '计数器步长'}, { + type: 'switch', + field: 'stepStrictly', + title: '是否只能输入 step 的倍数' + }, {type: 'switch', field: 'disabled', title: '是否禁用计数器'}, { + type: 'switch', + field: 'controls', + title: '是否使用控制按钮', + value: true + }, { + type: 'select', + field: 'controlsPosition', + title: '控制按钮位置', + options: [{label: 'default', value: ''}, {label: 'right', value: 'right'}] + }, {type: 'input', field: 'placeholder', title: '输入框默认 placeholder'}]; + } +}; diff --git a/src/config/rule/radio.js b/src/config/rule/radio.js new file mode 100644 index 0000000..85551f0 --- /dev/null +++ b/src/config/rule/radio.js @@ -0,0 +1,42 @@ +import uniqueId from '@form-create/utils/lib/unique'; +import {makeOptionsRule, makeRequiredRule} from '../../utils/index'; + +const label = '单选框'; +const name = 'radio'; + +export default { + icon: 'icon-radio', + label, + name, + rule() { + return { + type: name, + field: uniqueId(), + title: label, + info: '', + effect: { + fetch: '' + }, + props: {}, + options: [ + {value: '1', label: '选项1'}, + {value: '2', label: '选项2'}, + ] + }; + }, + props() { + return [ + makeRequiredRule(), + makeOptionsRule('options'), + {type: 'switch', field: 'disabled', title: '是否禁用'}, { + type: 'switch', + field: 'type', + title: '按钮形式', + props: {activeValue: 'button', inactiveValue: 'default'} + }, {type: 'input', field: 'textColor', title: '按钮形式的 Radio 激活时的文本颜色'}, { + type: 'input', + field: 'fill', + title: '按钮形式的 Radio 激活时的填充色和边框色' + }]; + } +}; diff --git a/src/config/rule/rate.js b/src/config/rule/rate.js new file mode 100644 index 0000000..463fee8 --- /dev/null +++ b/src/config/rule/rate.js @@ -0,0 +1,44 @@ +import uniqueId from '@form-create/utils/lib/unique'; +import {makeRequiredRule} from '../../utils'; + +const label = '评分'; +const name = 'rate'; + +export default { + icon: 'icon-rate', + label, + name, + rule() { + return { + type: name, + field: uniqueId(), + title: label, + info: '', + props: {}, + }; + }, + props() { + return [ + makeRequiredRule(), {type: 'inputNumber', field: 'max', title: '最大分值'}, { + type: 'switch', + field: 'disabled', + title: '是否为只读' + }, {type: 'switch', field: 'allowHalf', title: '是否允许半选'}, { + type: 'input', + field: 'voidColor', + title: '未选中 icon 的颜色' + }, {type: 'input', field: 'disabledVoidColor', title: '只读时未选中 icon 的颜色'}, { + type: 'input', + field: 'voidIconClass', + title: '未选中 icon 的类名' + }, {type: 'input', field: 'disabledVoidIconClass', title: '只读时未选中 icon 的类名'}, { + type: 'switch', + field: 'showScore', + title: '是否显示当前分数,show-score 和 show-text 不能同时为真' + }, {type: 'input', field: 'textColor', title: '辅助文字的颜色'}, { + type: 'input', + field: 'scoreTemplate', + title: '分数显示模板' + }]; + } +}; diff --git a/src/config/rule/row.js b/src/config/rule/row.js new file mode 100644 index 0000000..eca88d8 --- /dev/null +++ b/src/config/rule/row.js @@ -0,0 +1,41 @@ +const label = '栅格布局'; +const name = 'row'; + +export default { + icon: 'icon-row', + label, + name, + mask: false, + rule() { + return { + type: 'FcRow', + props: {}, + children: [] + }; + }, + children: 'col', + props() { + return [{type: 'inputNumber', field: 'gutter', title: '栅格间隔'}, { + type: 'switch', + field: 'type', + title: 'flex布局模式', + props: {activeValue: 'flex', inactiveValue: 'default'} + }, { + type: 'select', + field: 'justify', + title: 'flex 布局下的水平排列方式', + options: [{label: 'start', value: 'start'}, {label: 'end', value: 'end'}, { + label: 'center', + value: 'center' + }, {label: 'space-around', value: 'space-around'}, {label: 'space-between', value: 'space-between'}] + }, { + type: 'select', + field: 'align', + title: 'flex 布局下的垂直排列方式', + options: [{label: 'top', value: 'top'}, {label: 'middle', value: 'middle'}, { + label: 'bottom', + value: 'bottom' + }] + }]; + } +}; diff --git a/src/config/rule/select.js b/src/config/rule/select.js new file mode 100644 index 0000000..acfc1af --- /dev/null +++ b/src/config/rule/select.js @@ -0,0 +1,62 @@ +import uniqueId from '@form-create/utils/lib/unique'; +import {makeOptionsRule, makeRequiredRule} from '../../utils/index'; + +const label = '选择器'; +const name = 'select'; + +export default { + icon: 'icon-select', + label, + name, + rule() { + return { + type: name, + field: uniqueId(), + title: label, + info: '', + effect: { + fetch: '' + }, + props: {}, + options: [ + {value: '1', label: '选项1'}, + {value: '2', label: '选项2'}, + ] + }; + }, + props() { + return [ + makeRequiredRule(), + makeOptionsRule('options'), + {type: 'switch', field: 'multiple', title: '是否多选'}, { + type: 'switch', + field: 'disabled', + title: '是否禁用' + }, {type: 'switch', field: 'clearable', title: '是否可以清空选项'}, { + type: 'switch', + field: 'collapseTags', + title: '多选时是否将选中值按文字的形式展示' + }, {type: 'inputNumber', field: 'multipleLimit', title: '多选时用户最多可以选择的项目数,为 0 则不限制'}, { + type: 'input', + field: 'autocomplete', + title: 'autocomplete 属性' + }, {type: 'input', field: 'placeholder', title: '占位符'}, { + type: 'switch', + field: 'filterable', + title: '是否可搜索' + }, {type: 'switch', field: 'allowCreate', title: '是否允许用户创建新条目'}, { + type: 'input', + field: 'noMatchText', + title: '搜索条件无匹配时显示的文字' + }, {type: 'input', field: 'noDataText', title: '选项为空时显示的文字'}, { + type: 'switch', + field: 'reserveKeyword', + title: '多选且可搜索时,是否在选中一个选项后保留当前的搜索关键词' + }, {type: 'switch', field: 'defaultFirstOption', title: '在输入框按下回车,选择第一个匹配项'}, { + type: 'switch', + field: 'popperAppendToBody', + title: '是否将弹出框插入至 body 元素', + value: true + }, {type: 'switch', field: 'automaticDropdown', title: '对于不可搜索的 Select,是否在输入框获得焦点后自动弹出选项菜单'}]; + } +}; diff --git a/src/config/rule/slider.js b/src/config/rule/slider.js new file mode 100644 index 0000000..c3cf4ca --- /dev/null +++ b/src/config/rule/slider.js @@ -0,0 +1,44 @@ +import uniqueId from '@form-create/utils/lib/unique'; +import {makeRequiredRule} from '../../utils'; + +const label = '滑块'; +const name = 'slider'; + +export default { + icon: 'icon-slider', + label, + name, + rule() { + return { + type: name, + field: uniqueId(), + title: label, + info: '', + props: {}, + }; + }, + props() { + return [makeRequiredRule(), {type: 'inputNumber', field: 'min', title: '最小值'}, { + type: 'inputNumber', + field: 'max', + title: '最大值' + }, {type: 'switch', field: 'disabled', title: '是否禁用'}, { + type: 'inputNumber', + field: 'step', + title: '步长' + }, {type: 'switch', field: 'showInput', title: '是否显示输入框,仅在非范围选择时有效'}, { + type: 'switch', + field: 'showInputControls', + title: '在显示输入框的情况下,是否显示输入框的控制按钮', + value: true + }, {type: 'switch', field: 'showStops', title: '是否显示间断点'}, { + type: 'switch', + field: 'range', + title: '是否为范围选择' + }, {type: 'switch', field: 'vertical', title: '是否竖向模式'}, { + type: 'input', + field: 'height', + title: 'Slider 高度,竖向模式时必填' + }]; + } +}; diff --git a/src/config/rule/space.js b/src/config/rule/space.js new file mode 100644 index 0000000..86c1f63 --- /dev/null +++ b/src/config/rule/space.js @@ -0,0 +1,41 @@ +const label = '间距'; +const name = 'div'; + +export default { + icon: 'icon-space', + label, + name, + rule() { + return { + type: name, + wrap: { + show: false + }, + native: false, + style: { + width: '100%', + height: '20px', + }, + children: [] + }; + }, + props() { + return [ + { + type: 'object', + field: 'formCreateStyle', + native: true, + props: { + rule: [ + { + type: 'input', + field: 'height', + title: 'height', + }, + ] + } + } + + ]; + } +}; \ No newline at end of file diff --git a/src/config/rule/span.js b/src/config/rule/span.js new file mode 100644 index 0000000..6d983e0 --- /dev/null +++ b/src/config/rule/span.js @@ -0,0 +1,33 @@ +const label = '文字'; +const name = 'span'; + +export default { + icon: 'icon-span', + label, + name, + rule() { + return { + type: name, + title: '文字', + native: false, + children: ['这是一段文字'], + }; + }, + props() { + return [ + { + type: 'input', + field: 'formCreateTitle', + title: 'title', + }, + { + type: 'input', + field: 'formCreateChild', + title: '内容', + props: { + type: 'textarea' + } + } + ]; + } +}; \ No newline at end of file diff --git a/src/config/rule/switch.js b/src/config/rule/switch.js new file mode 100644 index 0000000..ca65ba3 --- /dev/null +++ b/src/config/rule/switch.js @@ -0,0 +1,39 @@ +import uniqueId from '@form-create/utils/lib/unique'; +import {makeRequiredRule} from '../../utils'; + +const label = '开关'; +const name = 'switch'; + +export default { + icon: 'icon-switch', + label, + name, + rule() { + return { + type: name, + field: uniqueId(), + title: label, + info: '', + props: {}, + }; + }, + props() { + return [makeRequiredRule(), {type: 'switch', field: 'disabled', title: '是否禁用'}, { + type: 'inputNumber', + field: 'width', + title: '宽度(px)' + }, {type: 'input', field: 'activeText', title: 'switch 打开时的文字描述'}, { + type: 'input', + field: 'inactiveText', + title: 'switch 关闭时的文字描述' + }, {type: 'input', field: 'activeValue', title: 'switch 打开时的值'}, { + type: 'input', + field: 'inactiveValue', + title: 'switch 关闭时的值' + }, {type: 'input', field: 'activeColor', title: 'switch 打开时的背景色'}, { + type: 'input', + field: 'inactiveColor', + title: 'switch 关闭时的背景色' + }]; + } +}; diff --git a/src/config/rule/tab.js b/src/config/rule/tab.js new file mode 100644 index 0000000..95a7b8a --- /dev/null +++ b/src/config/rule/tab.js @@ -0,0 +1,36 @@ +const label = '标签页'; +const name = 'tab'; + +export default { + icon: 'icon-tab', + label, + name, + children: 'tab-pane', + mask: false, + rule() { + return { + type: 'el-tabs', + style:'width:100%;', + children: [] + }; + }, + props() { + return [{ + type: 'select', + field: 'type', + title: '风格类型', + options: [{ + label: 'card', + value: 'card' + }, {label: 'border-card', value: 'border-card'}] + }, {type: 'switch', field: 'closable', title: '标签是否可关闭'}, { + type: 'select', + field: 'tabPosition', + title: '选项卡所在位置', + options: [{label: 'top', value: 'top'}, {label: 'right', value: 'right'}, { + label: 'left', + value: 'left' + }] + }, {type: 'switch', field: 'stretch', title: '标签的宽度是否自撑开'}]; + } +}; diff --git a/src/config/rule/tabPane.js b/src/config/rule/tabPane.js new file mode 100644 index 0000000..6656e58 --- /dev/null +++ b/src/config/rule/tabPane.js @@ -0,0 +1,29 @@ +const label = '标签页'; +const name = 'tab-pane'; + +export default { + label, + name, + inside: true, + drag: true, + dragBtn: false, + mask: false, + rule() { + return { + type: 'el-tab-pane', + props: {label: '新标签页'}, + children: [] + }; + }, + props() { + return [{type: 'input', field: 'label', title: '选项卡标题'}, { + type: 'switch', + field: 'disabled', + title: '是否禁用' + }, {type: 'input', field: 'name', title: '与选项卡绑定值 value 对应的标识符,表示选项卡别名'}, { + type: 'switch', + field: 'lazy', + title: '标签是否延迟渲染' + }]; + } +}; diff --git a/src/config/rule/time.js b/src/config/rule/time.js new file mode 100644 index 0000000..cda6b48 --- /dev/null +++ b/src/config/rule/time.js @@ -0,0 +1,57 @@ +import uniqueId from '@form-create/utils/lib/unique'; +import {makeRequiredRule} from '../../utils'; + +const label = '时间选择器'; +const name = 'timePicker'; + +export default { + icon: 'icon-time', + label, + name, + rule() { + return { + type: name, + field: uniqueId(), + title: label, + info: '', + props: {}, + }; + }, + props() { + return [makeRequiredRule(), { + type: 'Struct', + field: 'pickerOptions', + title: '当前时间日期选择器特有的选项', + props: {defaultValue: {}} + }, {type: 'switch', field: 'readonly', title: '完全只读'}, { + type: 'switch', + field: 'disabled', + title: '禁用' + }, {type: 'switch', field: 'editable', title: '文本框可输入', value: true}, { + type: 'switch', + field: 'clearable', + title: '是否显示清除按钮', + value: true + }, {type: 'input', field: 'placeholder', title: '非范围选择时的占位内容'}, { + type: 'input', + field: 'startPlaceholder', + title: '范围选择时开始日期的占位内容' + }, {type: 'input', field: 'endPlaceholder', title: '范围选择时开始日期的占位内容'}, { + type: 'switch', + field: 'isRange', + title: '是否为时间范围选择' + }, {type: 'switch', field: 'arrowControl', title: '是否使用箭头进行时间选择'}, { + type: 'select', + field: 'align', + title: '对齐方式', + options: [{label: 'left', value: 'left'}, {label: 'center', value: 'center'}, { + label: 'right', + value: 'right' + }] + }, {type: 'input', field: 'prefixIcon', title: '自定义头部图标的类名'}, { + type: 'input', + field: 'clearIcon', + title: '自定义清空图标的类名' + }]; + } +}; diff --git a/src/config/rule/transfer.js b/src/config/rule/transfer.js new file mode 100644 index 0000000..0a99bc8 --- /dev/null +++ b/src/config/rule/transfer.js @@ -0,0 +1,84 @@ +import uniqueId from '@form-create/utils/lib/unique'; + +const label = '穿梭框'; +const name = 'el-transfer'; + +const generateData = _ => { + const data = []; + for (let i = 1; i <= 15; i++) { + data.push({ + key: i, + label: `备选项 ${i}`, + disabled: i % 4 === 0 + }); + } + return data; +}; + +export default { + icon: 'icon-transfer', + label, + name, + rule() { + return { + type: name, + field: uniqueId(), + title: label, + info: '', + props: { + data: generateData() + } + }; + }, + props() { + return [{ + type: 'Struct', + field: 'data', + title: 'Transfer 的数据源', + props: {defaultValue: []} + }, {type: 'switch', field: 'filterable', title: '是否可搜索'}, { + type: 'input', + field: 'filterPlaceholder', + title: '搜索框占位符' + }, { + type: 'select', + field: 'targetOrder', + title: '右侧列表元素的排序策略', + info: '若为 original,则保持与数据源相同的顺序;若为 push,则新加入的元素排在最后;若为 unshift,则新加入的元素排在最前', + options: [{label: 'original', value: 'original'}, { + label: 'push', + value: 'push' + }, {label: 'unshift', value: 'unshift'}] + }, { + type: 'Struct', + field: 'titles', + title: '自定义列表标题', + props: {defaultValue: []} + }, { + type: 'Struct', + field: 'buttonTexts', + title: '自定义按钮文案', + props: {defaultValue: []} + }, { + type: 'Struct', + field: 'format', + title: '列表顶部勾选状态文案', + props: {defaultValue: {}} + }, { + type: 'Struct', + field: 'props', + title: '数据源的字段别名', + props: {defaultValue: {}} + }, { + type: 'Struct', + field: 'leftDefaultChecked', + title: '初始状态下左侧列表的已勾选项的 key 数组', + props: {defaultValue: []} + }, { + type: 'Struct', + field: 'rightDefaultChecked', + title: '初始状态下右侧列表的已勾选项的 key 数组', + props: {defaultValue: []} + }]; + } +}; \ No newline at end of file diff --git a/src/config/rule/tree.js b/src/config/rule/tree.js new file mode 100644 index 0000000..5184b7e --- /dev/null +++ b/src/config/rule/tree.js @@ -0,0 +1,100 @@ +import uniqueId from '@form-create/utils/lib/unique'; +import {makeOptionsRule, makeRequiredRule} from '../../utils/index'; + +const label = '树形控件'; +const name = 'tree'; + +export default { + icon: 'icon-tree', + label, + name, + rule() { + return { + type: name, + field: uniqueId(), + title: label, + info: '', + effect: { + fetch: '' + }, + props: { + props: { + label: 'label', + }, + showCheckbox: true, + nodeKey: 'id', + data: [{ + id: 1, + label: '一级 1', + children: [{ + id: 4, + label: '二级 1-1', + children: [{ + id: 9, + label: '三级 1-1-1' + }, { + id: 10, + label: '三级 1-1-2' + }] + }] + }, { + id: 2, + label: '一级 2', + children: [{ + id: 5, + label: '二级 2-1' + }, { + id: 6, + label: '二级 2-2' + }] + }, { + id: 3, + label: '一级 3', + children: [{ + id: 7, + label: '二级 3-1' + }, { + id: 8, + label: '二级 3-2' + }] + }] + }, + }; + }, + props() { + return [ + makeRequiredRule(), + makeOptionsRule('props.data', false), + {type: 'input', field: 'emptyText', title: '内容为空的时候展示的文本'}, { + type: 'Struct', + field: 'props', + title: '配置选项,具体看下表', + props: {defaultValue: {}} + }, {type: 'switch', field: 'renderAfterExpand', title: '是否在第一次展开某个树节点后才渲染其子节点', value: true}, { + type: 'switch', + field: 'defaultExpandAll', + title: '是否默认展开所有节点', + }, { + type: 'switch', + field: 'expandOnClickNode', + title: '是否在点击节点的时候展开或者收缩节点, 默认值为 true,如果为 false,则只有点箭头图标的时候才会展开或者收缩节点。', + value: true + }, { + type: 'switch', + field: 'checkOnClickNode', + title: '是否在点击节点的时候选中节点,默认值为 false,即只有在点击复选框时才会选中节点。' + }, {type: 'switch', field: 'autoExpandParent', title: '展开子节点的时候是否自动展开父节点', value: true}, { + type: 'switch', + field: 'checkStrictly', + title: '在显示复选框的情况下,是否严格的遵循父子不互相关联的做法,默认为 false' + }, {type: 'switch', field: 'accordion', title: '是否每次只打开一个同级树节点展开'}, { + type: 'inputNumber', + field: 'indent', + title: '相邻级节点间的水平缩进,单位为像素' + }, {type: 'input', field: 'iconClass', title: '自定义树节点的图标'}, { + type: 'input', + field: 'nodeKey', + title: '每个树节点用来作为唯一标识的属性,整棵树应该是唯一的' + }]; + } +}; diff --git a/src/config/rule/upload.js b/src/config/rule/upload.js new file mode 100644 index 0000000..cb49be4 --- /dev/null +++ b/src/config/rule/upload.js @@ -0,0 +1,64 @@ +import uniqueId from '@form-create/utils/lib/unique'; +import {makeRequiredRule} from '../../utils'; + +const label = '上传'; +const name = 'upload'; + +export default { + icon: 'icon-upload', + label, + name, + rule() { + return { + type: name, + field: uniqueId(), + title: label, + info: '', + props: { + action: '', + onSuccess(res, file) { + file.url = res.data.url; + } + } + }; + }, + props() { + return [makeRequiredRule(), { + type: 'select', + field: 'uploadType', + title: '上传类型', + value: 'image', + options: [{label: '图片', value: 'image'}, { + label: '文件', + value: 'file' + }] + }, {type: 'input', field: 'action', title: '上传的地址(必填)'}, { + type: 'Struct', + field: 'headers', + title: '设置上传的请求头部', + props: {defaultValue: {}} + }, {type: 'switch', field: 'multiple', title: '是否支持多选文件'}, { + type: 'Struct', + field: 'data', + title: '上传时附带的额外参数', + props: {defaultValue: {}} + }, {type: 'input', field: 'name', title: '上传的文件字段名'}, { + type: 'switch', + field: 'withCredentials', + title: '支持发送 cookie 凭证信息' + }, {type: 'input', field: 'accept', title: '接受上传的文件类型(thumbnail-mode 模式下此参数无效)'}, { + type: 'switch', + field: 'autoUpload', + title: '是否在选取文件后立即进行上传', + value: true + }, { + type: 'switch', + field: 'disabled', + title: '是否禁用' + }, { + type: 'inputNumber', + field: 'limit', + title: '最大允许上传个数' + }]; + } +}; diff --git a/src/index.js b/src/index.js new file mode 100644 index 0000000..823bc7a --- /dev/null +++ b/src/index.js @@ -0,0 +1,56 @@ +import FcDesigner from './components/FcDesigner.vue'; +import DragTool from './components/DragTool.vue'; +import Struct from './components/Struct.vue'; +import Fetch from './components/Fetch.vue'; +import Validate from './components/Validate.vue'; +import DragBox from './components/DragBox.vue'; +import Required from './components/Required.vue'; +import TableOptions from './components/TableOptions.vue'; +import {designerForm} from './utils/form'; +import FcEditor from '@form-create/component-wangeditor'; +import './style/index.css'; +import draggable from 'vuedraggable/src/vuedraggable'; +import unique from '@form-create/utils/lib/unique'; +import {makeOptionsRule} from './utils/index'; +import formCreate from './utils/form'; + +designerForm.component('draggable', draggable); +designerForm.component('DragTool', DragTool); +designerForm.component('DragBox', DragBox); +designerForm.component('Validate', Validate); +designerForm.component('Struct', Struct); +designerForm.component('Fetch', Fetch); +designerForm.component('Required', Required); +designerForm.component('TableOptions', TableOptions); +designerForm.component('FcEditor', FcEditor); +formCreate.component('FcEditor', FcEditor); + +designerForm.register('_fc', { + init(fc, rule) { + rule._id = unique(); + if (fc.repeat) + rule.field = unique(); + if (fc.value) { + rule.effect._fc = false; + } + } +}); + +designerForm.register('_fc_tool', { + init(_, rule) { + rule.props.unique = unique(); + } +}); + +const install = function (Vue) { + Vue.component('FcDesigner', FcDesigner); +}; + +FcDesigner.install = install; +FcDesigner.makeOptionsRule = makeOptionsRule; +FcDesigner.formCreate = formCreate; +FcDesigner.designerForm = designerForm; + +export default FcDesigner; + +export {formCreate, designerForm, install}; diff --git a/src/style/fonts/fc-icons.woff b/src/style/fonts/fc-icons.woff new file mode 100644 index 0000000..830e21f Binary files /dev/null and b/src/style/fonts/fc-icons.woff differ diff --git a/src/style/fonts/iconfont.css b/src/style/fonts/iconfont.css new file mode 100644 index 0000000..11a2a19 --- /dev/null +++ b/src/style/fonts/iconfont.css @@ -0,0 +1,17 @@ +@font-face { + font-family: "iconfont"; /* Project id */ + src: url('iconfont.ttf?t=1669574713695') format('truetype'); +} + +.iconfont { + font-family: "iconfont" !important; + font-size: 16px; + font-style: normal; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +.icon-xingzhuang-tupian:before { + content: "\eb98"; +} + diff --git a/src/style/fonts/iconfont.ttf b/src/style/fonts/iconfont.ttf new file mode 100644 index 0000000..35886cf Binary files /dev/null and b/src/style/fonts/iconfont.ttf differ diff --git a/src/style/index.css b/src/style/index.css new file mode 100644 index 0000000..270412e --- /dev/null +++ b/src/style/index.css @@ -0,0 +1,138 @@ +@font-face { + font-family: "fc-icon"; + src: url(fonts/fc-icons.woff) format('woff'); +} +@font-face { + font-family: "fc-icon"; /* Project id */ + src: url('fonts/iconfont.ttf') format('truetype'); +} +.fc-icon { + font-family: "fc-icon" !important; + font-size: 16px; + font-style: normal; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} +.icon-xingzhuang-tupian:before { + content: "\eb98"; +} +.icon-add-child:before { + content: "\e789"; +} + +.icon-switch:before { + content: "\e77c"; +} + +.icon-tab:before { + content: "\e77b"; +} + +.icon-button:before { + content: "\e77e"; +} + +.icon-input:before { + content: "\e77f"; +} + +.icon-checkbox:before { + content: "\e780"; +} + +.icon-radio:before { + content: "\e781"; +} + +.icon-rate:before { + content: "\e782"; +} + +.icon-number:before { + content: "\e783"; +} + +.icon-upload:before { + content: "\e784"; +} + +.icon-cascader:before { + content: "\e785"; +} + +.icon-space:before { + content: "\e786"; +} + +.icon-color:before { + content: "\e787"; +} + +.icon-span:before { + content: "\e788"; +} + +.icon-alert:before { + content: "\e78a"; +} + +.icon-row:before { + content: "\e78b"; +} + +.icon-divider:before { + content: "\e78d"; +} + +.icon-select:before { + content: "\e78e"; +} + +.icon-transfer:before { + content: "\e78f"; +} + +.icon-editor:before { + content: "\e790"; +} + +.icon-slider:before { + content: "\e791"; +} + +.icon-tree:before { + content: "\e792"; +} + +.icon-date:before { + content: "\e793"; +} + +.icon-time:before { + content: "\e794"; +} + +.icon-delete:before { + content: "\e770"; +} + +.icon-copy:before { + content: "\e771"; +} + +.icon-import:before { + content: "\e773"; +} + +.icon-add:before { + content: "\e774"; +} + +.icon-preview:before { + content: "\e776"; +} + +.icon-move:before { + content: "\e777"; +} + diff --git a/src/utils/form.js b/src/utils/form.js new file mode 100644 index 0000000..8a181c1 --- /dev/null +++ b/src/utils/form.js @@ -0,0 +1,9 @@ +import formCreate from '@form-create/element-ui'; + +const viewForm = formCreate; + +const designerForm = formCreate.factory(); + +export default viewForm; + +export {designerForm}; diff --git a/src/utils/index.js b/src/utils/index.js new file mode 100644 index 0000000..c6bfea2 --- /dev/null +++ b/src/utils/index.js @@ -0,0 +1,132 @@ +import is, {hasProperty} from '@form-create/utils/lib/type'; +import {parseFn} from '@form-create/utils/lib/json'; + +export function makeRequiredRule() { + return { + type: 'Required', field: 'formCreate$required', title: '是否必填' + }; +} + +export function makeOptionsRule(to, flag) { + const options = [ + {'label': 'JSON数据', 'value': 0}, + {'label': '接口数据', 'value': 1}, + ]; + + const control = [ + { + value: 0, + rule: [ + { + type: 'Struct', + field: 'formCreate' + upper(to).replace('.', '>'), + props: {defaultValue: []} + }, + ], + }, + { + value: 1, + rule: [ + { + type: 'Fetch', + field: 'formCreateEffect>fetch', + props: { + to + } + } + ] + } + ]; + + if (flag !== false) { + options.splice(0, 0, {'label': '静态数据', 'value': 2}); + control.push({ + value: 2, + rule: [ + { + type: 'TableOptions', + field: 'formCreate' + upper(to).replace('.', '>'), + props: {defaultValue: []} + }, + ], + }); + } + + return { + type: 'radio', + title: '选项数据', + field: '_optionType', + value: flag !== false ? 2 : 0, + options, + props: { + type: 'button' + }, + control + }; +} + +export function upper(str) { + return str.replace(str[0], str[0].toLocaleUpperCase()); +} + + +export const toJSON = function (val) { + const type = /object ([a-zA-Z]*)/.exec(Object.prototype.toString.call(val)); + if (type && _toJSON[type[1].toLowerCase()]) { + return _toJSON[type[1].toLowerCase()](val); + } else { + return val; + } +}; + +const _toJSON = { + object: function (val) { + var json = []; + for (var i in val) { + if (!hasProperty(val, i)) continue; + json.push( + toJSON(i) + ': ' + + ((val[i] != null) ? toJSON(val[i]) : 'null') + ); + } + return '{\n ' + json.join(',\n ') + '\n}'; + }, + array: function (val) { + for (var i = 0, json = []; i < val.length; i++) + json[i] = (val[i] != null) ? toJSON(val[i]) : 'null'; + return '[' + json.join(', ') + ']'; + }, + string: function (val) { + var tmp = val.split(''); + for (var i = 0; i < tmp.length; i++) { + var c = tmp[i]; + (c >= ' ') ? + (c === '\\') ? (tmp[i] = '\\\\') : + (c === '"') ? (tmp[i] = '\\"') : 0 : + (tmp[i] = + (c === '\n') ? '\\n' : + (c === '\r') ? '\\r' : + (c === '\t') ? '\\t' : + (c === '\b') ? '\\b' : + (c === '\f') ? '\\f' : + (c = c.charCodeAt(), ('\\u00' + ((c > 15) ? 1 : 0) + (c % 16))) + ); + } + return '"' + tmp.join('') + '"'; + } +}; + +export const deepParseFn = function (target) { + for (let key in target) { + if (Object.prototype.hasOwnProperty.call(target, key)) { + let data = target[key]; + if (Array.isArray(data) || is.Object(data)) { + deepParseFn(data); + } + if (is.String(data)) { + target[key] = parseFn(data); + } + } + } + return target; +}; diff --git a/tools/cli/run.ts b/tools/cli/run.ts new file mode 100644 index 0000000..89122da --- /dev/null +++ b/tools/cli/run.ts @@ -0,0 +1,61 @@ +/* + * @Author : djkloop + * @Date : 2021-09-15 15:13:17 + * @LastEditors : djkloop + * @LastEditTime : 2021-09-19 16:10:22 + * @Description : 头部注释 + * @FilePath : /form-create2/tools/cli/run.ts + */ +import {program} from 'commander'; +import path from 'path'; +import chalk from 'chalk'; +import build from '../lib/build'; +// pkgUrl +const log = console.log +const commandExecUrl = process.cwd() +log() +log(' ' + chalk.green('command run path with: ', commandExecUrl)) +log() +const pkgUrl = path.join(commandExecUrl, '/lerna.json'); + +program.on('--help', () => { + log(); + log(chalk.blue.bold(' Usage:'), chalk.cyan.bold('tools build tools with Node 👍 ~')); + log(); +}) + +/// tools 版本 +program + .version(`@form-create/tools v${require(pkgUrl).version}`, '-v, --version', 'tools versions') + +program + .command('build [flag]') + .description('build components && packages || build components || build packages') + .option('-a,--all', 'Build @form-create/[all]packages') /// 默认打包components和packages + .option('-c, --components ', 'Build @form-create/component- package or packages array') // 打单独的组件 + .option('-p, --packages ', 'Build @form-create/ package or packages array') // 打单独的包 + .action((_: any, cmd: any) => build(_, cleanArgs(cmd))); + +program.parse(process.argv); + + + +// code with vue-cli: https://github.com/vuejs/vue-cli/blob/dev/packages/%40vue/cli/bin/vue.js#L275 +function camelize(str: string) { + return str.replace(/-(\w)/g, (_, c) => c ? c.toUpperCase() : '') +} + +// commander passes the Command object itself as options, +// extract only actual options into a fresh object. +function cleanArgs(cmd: any) { + const args: any = {} + cmd.options.forEach((o: any) => { + const key = camelize(o.long.replace(/^--/, '')) + // if an option is not present and Command has a method with the same name + // it should not be copied + if (typeof cmd[key] !== 'function' && typeof cmd[key] !== 'undefined') { + args[key] = cmd[key] + } + }) + return args +} \ No newline at end of file diff --git a/tools/lib/build.ts b/tools/lib/build.ts new file mode 100644 index 0000000..450cbad --- /dev/null +++ b/tools/lib/build.ts @@ -0,0 +1,163 @@ +/* + * @Author : djkloop + * @Date : 2020-08-15 17:21:22 + * @LastEditors : djkloop + * @LastEditTime : 2021-10-04 19:33:38 + * @Description : 头部注释 + * @FilePath : /form-create2/tools/lib/build.ts + */ +import chalk from 'chalk'; +import ora from 'ora'; +import dayjs from 'dayjs'; +import fs from 'fs'; +import { exit } from 'process'; +import shell from 'shelljs'; +import createBuildComponents from './components'; +import createBuildPackages from './packages'; +import { blue, getFolderNames, getSingleComponentPaths, getSinglePackagePaths, red, targets as getAllTargetsPath, yellow } from './utils' + + +let spinner +const log = console.log +const context = process.cwd() + + +async function buildAllComponents(paths: string[] = []) { + let componentsPathsObject = Object.create(null) + paths.forEach((comPath: string) => { + const [name, cname] = comPath.split('/') + const componentsPaths = getSingleComponentPaths('components', name, cname) + + if (!componentsPathsObject[name]) { + componentsPathsObject[name] = componentsPaths[name] + } else { + componentsPathsObject[name] = [...componentsPathsObject[name], ...componentsPaths[name]] + } + }); + await createBuildComponents(componentsPathsObject) + await exit(); +} + + +async function createBuildTask(args: any) { + const isAll = args.all || Object.keys(args).length === 0 + const isComponentsAll = args?.components?.includes('all') + const isPackagesAll = args?.packages?.includes('all') + let tips = ''; + if (isAll) { + tips = 'build all components and all packages' + } else if (isComponentsAll) { + tips = 'build all components' + } else if (isPackagesAll) { + tips = 'build all packages' + } else { + tips = ' build task time' + ' ' + dayjs().format('YYYY-MM-DD HH:mm:ss') + '\n' + } + yellow(tips, true) + blue(' ---------------------- start ----------------------\n', true) + /// not all components + if (!isComponentsAll && args?.components?.length) { + const libs = args?.components + if (libs.length === 1) { + /// 先切包名 + const lib = libs[0]; + /// 单包下存在多个包 + if (lib.includes('*') || !lib.includes('/')) { + /// xxx/* + const [name] = !lib.includes('/') ? [lib] : lib.split('/') + const _p = getFolderNames('components', name) + buildAllComponents(_p) + } else { + const [name, cname] = lib.split('/') + const componentsPaths = getSingleComponentPaths('components', name, cname) + await createBuildComponents(componentsPaths) + await exit(); + } + } else { + const compsPaths = args?.components; + const allPaths = compsPaths.filter(cp => cp.includes('*') || !cp.includes('/')); + const fixPaths = compsPaths.filter(cp => !cp.includes('*') && cp.includes('/')); + let _all_ = [...fixPaths] + if (allPaths.length) { + allPaths.forEach(allp => { + const [name] = !allp.includes('/') ? [allp] : allp.split('/') + const _p = getFolderNames('components', name) + _all_ = [..._all_, ..._p] + }); + } + buildAllComponents(_all_) + } + } + + if (!isPackagesAll && args?.packages?.length) { + const libs = args?.packages + if (libs.length === 1) { + /// 先切包名 + const lib = libs[0]; + const [name] = lib.split('/') + const packagesAllPaths = getSinglePackagePaths('packages', name) + await createBuildPackages(packagesAllPaths) + await exit(); + } else { + let componentsPathsObject = Object.create(null) + libs.forEach(async lib => { + const componentsPaths = getSinglePackagePaths('packages', lib) + componentsPathsObject[lib] = componentsPaths[lib] + }); + await createBuildPackages(componentsPathsObject) + await exit(); + } + } + + /// 如果是打包全部 + if (isAll) { + /// 先打对应的依赖components + const componentsAllPaths = getAllTargetsPath('components') + const packagesAllPaths = getAllTargetsPath('packages') + if (!Object.keys(componentsAllPaths).length && !Object.keys(packagesAllPaths).length) { + yellow('\n no build components and packages task! please check buildFormCreateOptions or private with components/*/*/package.json \n') + exit(1); + } + if (!Object.keys(componentsAllPaths).length) { + yellow('\n no build components task! please check buildFormCreateOptions or private with components/*/*/package.json \n') + } + if (!Object.keys(packagesAllPaths).length) { + yellow('\n no build packages task! please check buildFormCreateOptions or private with packages/*/package.json \n') + } + /// 打印对应的组件 + await createBuildComponents(componentsAllPaths) + /// 再打对应的包 + await createBuildPackages(packagesAllPaths) + /// 退出 + await exit(); + } else { + + /// 单独打所有的component组件 + if(isComponentsAll) { + const componentsAllPaths = getAllTargetsPath('components') + if (!Object.keys(componentsAllPaths).length) { + yellow('\n no build components task! please check buildFormCreateOptions or private with components/*/*/package.json \n') + await exit(1) + } + + await createBuildComponents(componentsAllPaths) + await exit(); + } + + /// 单独打所有的package包 + if (isPackagesAll) { + const packagesAllPaths = getAllTargetsPath('packages') + if (!Object.keys(packagesAllPaths).length) { + yellow('\n no build packages task! please check buildFormCreateOptions or private with packages/*/package.json \n') + await exit(1) + } + + await createBuildPackages(packagesAllPaths) + await exit(); + } + } +} + +export default (_: any, args: any) => { + return createBuildTask(args) +} \ No newline at end of file diff --git a/tools/lib/components.ts b/tools/lib/components.ts new file mode 100644 index 0000000..5b1a884 --- /dev/null +++ b/tools/lib/components.ts @@ -0,0 +1,73 @@ +/** + * 打包components文件 + */ +import type { Ora } from 'ora'; +import chalk from 'chalk'; +import ora from 'ora'; +import os from 'os'; +import execa from 'execa'; +import dayjs from 'dayjs'; + +function sleep(ms) { + return new Promise((resolve) => { + setTimeout(resolve, ms); + }); +} + + +let spinner: Ora; +const build = async (target: string, comp: string, targetName: string) => { + dayjs().startOf('millisecond'); + /// env 先写死 + const env = 'production' + await execa( + 'rollup', + [ + `-c`, + '--environment', + [ + `NODE_ENV:${env}`, + `BUILD_TARGET:${targetName}`, + `BUILD_TARGET_COMP:${comp}`, + `BUILD_TYPE:component`, + `BUILD_TARGET_PATH:${target}` + ].filter(Boolean).join(',') + ], + { stdio: 'inherit' } + ); + console.log('build task time' + ' ' + dayjs().format('YYYY-MM-DD HH:mm:ss')) +} + + +const runParallel = async (maxConcurrency: number, source: string[], buildName: string, iteratorFn: Function) => { + const ret = [] + const executing = [] + for (const item of source) { + const comp = item.split('/').pop() + const p = Promise.resolve().then(() => iteratorFn(item, comp, buildName)) + ret.push(p) + + if (maxConcurrency <= source.length) { + const e = p.then(() => executing.splice(executing.indexOf(e), 1)) + executing.push(e) + if (executing.length >= maxConcurrency) { + await Promise.race(executing) + } + } + } + return Promise.all(ret) +} + +const buildAll = async (comAllTargets) => { + await runParallel(os.cpus().length, comAllTargets[1], comAllTargets[0], build) +} + +const createBuildComponents = async (cpaths: { [k: string]: any }) => { + /// 根据每个不同的ui库去生成每个ui库下面的不同的组件打包 + const cps = Object.entries(cpaths) + for (const cpath of cps) { + await buildAll(cpath) + } +} + +export default createBuildComponents \ No newline at end of file diff --git a/tools/lib/packages.ts b/tools/lib/packages.ts new file mode 100644 index 0000000..08f35f1 --- /dev/null +++ b/tools/lib/packages.ts @@ -0,0 +1,88 @@ +/** + * 打包packages文件 + */ +import type {Ora} from 'ora'; +import chalk from 'chalk'; +import ora from 'ora'; +import os from 'os'; +import execa from 'execa'; +import dayjs from 'dayjs'; + +let spinner: Ora; +const build = async (target: string, comp: string, targetName: string) => { + dayjs().startOf('millisecond'); + spinner.text = chalk.bold.yellow(`start build ${targetName} ui \n`); + /// env 先写死 + const env = 'production' + await execa( + 'rollup', + [ + '-c', + '--environment', + [ + `NODE_ENV:${env}`, + `BUILD_TARGET:${targetName}`, + `BUILD_TARGET_COMP:${comp}`, + 'BUILD_TYPE:packages', + `BUILD_TARGET_PATH:${target}` + ].filter(Boolean).join(',') + ], + {stdio: 'inherit'} + ); + spinner.text = chalk.bold.green(`finished build ${targetName} ui time: ${dayjs().startOf('millisecond').format('SSS')}ms. \n `); +} + + +const runParallel = async (maxConcurrency: number, source: string[], buildName: string, iteratorFn: Function) => { + const ret = [] + const executing = [] + for (const item of source) { + const comp = item.split('/').pop() + const p = Promise.resolve().then(() => iteratorFn(item, comp, buildName)) + ret.push(p) + + if (maxConcurrency <= source.length) { + const e = p.then(() => executing.splice(executing.indexOf(e), 1)) + executing.push(e) + if (executing.length >= maxConcurrency) { + await Promise.race(executing) + } + } + } + return Promise.all(ret) +} + +const buildAll = async (comAllTargets) => { + await runParallel(os.cpus().length, comAllTargets[1], comAllTargets[0], build) +} + +const _filter_current_url_ = (urls: Array) => { + const arr = [] + urls.map((url) => { + const itemUrlLibName = url[0]; + let itemUrlLibsUrl = ''; + url[1].forEach((item, idx) => { + const suffix = item.split('/').pop() + if (suffix === itemUrlLibName) { + itemUrlLibsUrl = item + } + }) + const item = [itemUrlLibName, [itemUrlLibsUrl]] + arr.push(item) + }) + return arr +} + +const createBuildPackages = async (cpaths: { [k: string]: any }) => { + const tips = chalk.redBright.bold('\n start build all packages \n') + spinner = ora(tips).start() + /// 根据每个不同的ui库去生成每个ui库下面的不同的组件打包 + const cps = Object.entries(cpaths) + /// 处理下当前的url + const fCps = _filter_current_url_(cps) + for (const cpath of fCps) { + await buildAll(cpath) + } +} + +export default createBuildPackages diff --git a/tools/lib/paths.ts b/tools/lib/paths.ts new file mode 100644 index 0000000..9fb7daa --- /dev/null +++ b/tools/lib/paths.ts @@ -0,0 +1,14 @@ +import path from 'path'; + +/// 项目目录 +export const projRoot = path.resolve(__dirname, '../../'); +/// 单独component目录 +export const compRoot = path.resolve(projRoot, './components'); +/// packages目录 +export const pkgRoot = path.resolve(projRoot, './packages'); +/// file - packages.json \\\ +export const pkgFileRoot = path.resolve(projRoot, './package.json'); +/// file - lerna.json \\\ +export const lernaFileRoot = path.resolve(projRoot, './lerna.json'); +/// file - rollup.config.js \\\ +export const rollupFileRoot = path.resolve(projRoot, './rollup.config.ts'); \ No newline at end of file diff --git a/tools/lib/utils.ts b/tools/lib/utils.ts new file mode 100644 index 0000000..a544066 --- /dev/null +++ b/tools/lib/utils.ts @@ -0,0 +1,140 @@ +import chalk from "chalk"; +import fs from "fs"; +import { projRoot } from "./paths"; + + +export function yellow(str: string, isBold: boolean = false) { + isBold ? console.log(chalk.bold.yellow(str)) : console.log(chalk.yellow(str)) +} + +export function green(str: string, isBold: boolean = false) { + isBold ? console.log(chalk.bold.green(str)) : console.log(chalk.green(str)) +} + +export function blue(str: string, isBold: boolean = false) { + isBold ? console.log(chalk.bold.blue(str)) : console.log(chalk.blue(str)) +} + +export function red(str: string, isBold: boolean = false) { + isBold ? console.log(chalk.bold.red(str)) : console.log(chalk.red(str)) +} + +export function errorAndExit(e) { + red(e.message) + process.exit(1) +} + + +export const targets = (dir: 'packages' | 'components' = 'packages') => { + const componentsAllPaths = Object.create(null); + const uiFoldersPath = fs.readdirSync(dir).filter(uiFolderPath => { + if (fs.statSync(`${projRoot}/${dir}/${uiFolderPath}`).isDirectory()) { + return true + } + }) + + // uiFoldersPath.forEach(uiPath => { + // const pkg = require(`${projRoot}/${dir}/${uiPath}/package.json`) + // if (pkg.private || !pkg.buildFormCreateOptions) { + // blue(`\n info: ${projRoot}/${dir}/${uiPath}/package.json private is true or buildFormCreateOptions is not exists!`) + // } + // }) + + if (dir === 'packages') { + const packagesFolderPath = [] + for (let index = 0; index < uiFoldersPath.length; index++) { + const uiPath = uiFoldersPath[index]; + if (!fs.statSync(`${projRoot}/${dir}/${uiPath}`).isDirectory()) { + continue; + } + const pkg = require(`${projRoot}/${dir}/${uiPath}/package.json`) + fs.rmdirSync(`${projRoot}/${dir}/${uiPath}/dist`, { recursive: true }); + if (pkg.private || !pkg.buildFormCreateOptions) { + red(`\n info: ${projRoot}/${dir}/${uiPath}/package.json private is true or buildFormCreateOptions is not exists!`) + continue; + } + packagesFolderPath.push(`${projRoot}/${dir}/${uiPath}`) + if (packagesFolderPath.length) { + componentsAllPaths[uiPath] = packagesFolderPath + } + } + } + + if (dir === 'components') { + uiFoldersPath.forEach(uiPath => { + const componentFolderPath = [] + const alen = fs.readdirSync(`${projRoot}/${dir}/${uiPath}`).length + for (let index = 0; index < alen; index++) { + const comPath = fs.readdirSync(`${projRoot}/${dir}/${uiPath}`)[index]; + if (!fs.statSync(`${projRoot}/${dir}/${uiPath}/${comPath}`).isDirectory()) { + continue; + } + const pkg = require(`${projRoot}/${dir}/${uiPath}/${comPath}/package.json`) + fs.rmdirSync(`${projRoot}/${dir}/${uiPath}/${comPath}/dist`, { recursive: true }); + if (pkg.private || !pkg.buildFormCreateOptions) { + red(`\n info: ${projRoot}/${dir}/${uiPath}/${comPath}/package.json private is true or buildFormCreateOptions is not exists!`) + continue; + } + componentFolderPath.push(`${projRoot}/${dir}/${uiPath}/${comPath}`) + } + if (componentFolderPath.length) { + componentsAllPaths[uiPath] = componentFolderPath + } + }) + } + + + return componentsAllPaths +} + + +export const getSingleComponentPaths = (dir: string = 'components', libname: string = '', comname: string = '') => { + const fpath = Object.create(null) + const _rootPath = dir + const _libPath = libname + const _compPath = comname + + if (!fs.statSync(`${projRoot}/${_rootPath}/${_libPath}/${_compPath}`).isDirectory()) { + return + } + + const pkg = require(`${projRoot}/${_rootPath}/${_libPath}/${_compPath}/package.json`) + fs.rmdirSync(`${projRoot}/${_rootPath}/${_libPath}/${_compPath}/dist`, { recursive: true }); + if (pkg.private || !pkg.buildFormCreateOptions) { + red(`\n info: ${projRoot}/${_rootPath}/${_libPath}/${_compPath}/package.json private is true or buildFormCreateOptions is not exists!`) + return + } + fpath[_libPath] = [`${projRoot}/${_rootPath}/${_libPath}/${_compPath}`] + + return fpath +} + +export const getSinglePackagePaths = (dir: string = 'packages', libname: string = '') => { + const fpath = Object.create(null) + const _rootPath = dir + const _libPath = libname + + if (!fs.statSync(`${projRoot}/${_rootPath}/${_libPath}`).isDirectory()) { + return + } + + const pkg = require(`${projRoot}/${_rootPath}/${_libPath}/package.json`) + fs.rmdirSync(`${projRoot}/${_rootPath}/${_libPath}/dist`, { recursive: true }); + if (pkg.private || !pkg.buildFormCreateOptions) { + red(`\n info: ${projRoot}/${_rootPath}/${_libPath}/package.json private is true or buildFormCreateOptions is not exists!`) + return + } + fpath[_libPath] = [`${projRoot}/${_rootPath}/${_libPath}`] + + return fpath +} + +/// Get all the folder names under the current folder +export const getFolderNames = (folder: string, uiFolder: string) => { + return fs.readdirSync(`${folder}/${uiFolder}`).map(uiFolderPath => { + if (fs.statSync(`${projRoot}/${folder}/${uiFolder}`).isDirectory()) { + fs.rmdirSync(`${projRoot}/${folder}/${uiFolder}/dist`, { recursive: true }); + return `${uiFolder}/${uiFolderPath}` + } + }) +} \ No newline at end of file diff --git a/tools/rollup/plugin/index.ts b/tools/rollup/plugin/index.ts new file mode 100644 index 0000000..71c0eb4 --- /dev/null +++ b/tools/rollup/plugin/index.ts @@ -0,0 +1,69 @@ +/* + * @Author : djkloop + * @Date : 2021-09-19 13:53:31 + * @LastEditors : djkloop + * @LastEditTime : 2021-09-19 14:11:52 + * @Description : 头部注释 + * @FilePath : /form-create2/tools/rollup/plugin/index.ts + */ +import vue from 'rollup-plugin-vue'; +import postcss from 'rollup-plugin-postcss'; +import { cssUrl } from '@sixian/css-url'; +import externals from 'rollup-plugin-node-externals'; +import nodeResolve from '@rollup/plugin-node-resolve'; +import commonjs from '@rollup/plugin-commonjs'; +import babel from '@rollup/plugin-babel'; + +export const createRollupPlugins = () => { + + const rollupPlugins = [ + vue({ + preprocessStyles: true, + }) + ]; + + /// css settings + rollupPlugins.push(postcss({ + minimize: true, + extract: false, + plugins: [ + cssUrl({ + imgExtensions: /\.(png|jpg|jpeg|gif|svg)$/, + fontExtensions: /\.(ttf|woff|woff2|eot)$/, + limit: 8192, + hash: false, + slash: false + }) + ] + })); + + + /// devDependencies + rollupPlugins.push(externals({ + devDeps: true + })); + + /// j + rollupPlugins.push(nodeResolve({ + extensions: ['.js', '.json', '.jsx', '.ts', '.tsx'], + preferBuiltins: true, + browser: true + })); + + /// commonjs + rollupPlugins.push(commonjs()); + + rollupPlugins.push(babel({ + babelHelpers: 'bundled', + exclude: 'node_modules/**', + extensions: ['.js', '.jsx', '.mjs', '.ts', '.tsx', '.vue'], + })); + + + // if (visualizer) { + + // } + + + return rollupPlugins +} \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..116a8d9 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,21 @@ + +{ + "compilerOptions": { + "target": "ESNext", + "sourceMap": true, + "allowUnreachableCode": true, + "allowSyntheticDefaultImports": true, + "allowJs": true, + "experimentalDecorators": true, + "moduleResolution": "node", + "noImplicitThis": true, + "noImplicitAny": false, + "importHelpers": true, + "jsx": "preserve" + }, + "include": [ + "*.vue", + "*", + "types/typing.d.ts" + ] +} \ No newline at end of file diff --git a/types/index.d.ts b/types/index.d.ts new file mode 100644 index 0000000..337be3b --- /dev/null +++ b/types/index.d.ts @@ -0,0 +1,31 @@ +import {Rule} from "@form-create/element-ui"; + +export interface MenuItem { + label: string, + name: string, + icon: string; +} + +export interface Menu { + title: string; + name: string; + list: MenuItem[] +} + +export interface MenuList extends Array { + +} + +export interface DragRule { + name: string; + + rule(): Rule; + + props(): Rule[]; + + children?: string; + inside?: true; + drag?: true | String; + dragBtn?: false; + mask?: false; +} diff --git a/vite.config.build.js b/vite.config.build.js new file mode 100644 index 0000000..42b96e8 --- /dev/null +++ b/vite.config.build.js @@ -0,0 +1,85 @@ +import {defineConfig} from 'vite' +import vue from '@vitejs/plugin-vue' +import vueJSX from '@vitejs/plugin-vue-jsx' +import banner from 'vite-plugin-banner' +import cssnano from 'cssnano' +import visualizer from 'rollup-plugin-visualizer'; +import cssInjectedByJsPlugin from 'vite-plugin-css-injected-by-js' +import {author, license, name, version} from './package.json' + +const extnedsPlugins = []; + +function getBanner(banner, pkg) { + if (!banner || typeof banner === 'string') { + return banner || ''; + } + + banner = {...pkg, ...(banner === true ? {} : banner)}; + + const author =banner.author + + const license = banner.license || ''; + return ( + '/*!\n' + + ' * form-create 可视化表单设计器\n' + + ` * ${banner.name} v${banner.version}\n` + + ` * (c) ${author || ''}\n` + + (license && ` * Released under the ${license} License.\n`) + + ' */' + ); +} + +const __banner__ = { + author: `2021-${new Date().getFullYear()} ${author}\n * Github https://github.com/xaboy/form-create-designer`, + license, + name, + version +} + +// 打包生产环境才引入的插件 +if (process.env.NODE_ENV === 'production') { + // 打包依赖展示 + extnedsPlugins.push( + visualizer({ + open: true, + gzipSize: true, + brotliSize: true, + }) + ); +} + +// https://vitejs.dev/config/ +export default defineConfig({ + build: { + lib: { + entry: 'src/index.js', + name: 'FcDesigner', + fileName: format => `index.${format}.js`, + }, + rollupOptions: { + output: { + exports: 'named', + globals: { + vue: 'Vue' + } + }, + external: [ + 'vue', + 'element-plus', + '@form-create/element-ui' + ], + + }, + brotliSize: true + }, + css: { + postcss: { + plugins: [ + cssnano({ + preset: 'advanced' + }) + ] + } + }, + plugins: [vue(), vueJSX(), banner(getBanner(__banner__)),cssInjectedByJsPlugin(), ...extnedsPlugins] +}) diff --git a/vite.config.preview.js b/vite.config.preview.js new file mode 100644 index 0000000..9905287 --- /dev/null +++ b/vite.config.preview.js @@ -0,0 +1,22 @@ +import {defineConfig} from 'vite' +import vue from '@vitejs/plugin-vue' +import vueJSX from '@vitejs/plugin-vue-jsx' +// https://vitejs.dev/config/ +export default defineConfig({ + build: { + cssCodeSplit: true, + rollupOptions: { + output: { + manualChunks: { + vue: ['vue'], + 'element-plus': ['element-plus'], + }, + globals: { + vue: 'Vue', + } + }, + }, + chunkSizeWarningLimit: 2000, + }, + plugins: [vue(), vueJSX()] +}) diff --git a/vue.config.js b/vue.config.js new file mode 100644 index 0000000..8457860 --- /dev/null +++ b/vue.config.js @@ -0,0 +1,28 @@ +/* + * @Author : djkloop + * @Date : 2020-08-15 21:16:03 + * @LastEditors : djkloop + * @LastEditTime : 2021-09-21 17:18:46 + * @Description : 头部注释 + * @FilePath : /form-create2/packages/element-ui/vue.config.js + */ +module.exports = { + pages: { + app: { + entry: 'examples/main.js', + template: 'examples/index.html', + filename: 'index.html' + } + }, + configureWebpack: { + module: { + rules: [ + { + test: /\.mjs$/, + include: /node_modules/, + type: 'javascript/auto' + }, + ] + } + } +}