This commit is contained in:
robin
2022-12-01 21:48:35 +08:00
commit 879cd36baf
69 changed files with 5265 additions and 0 deletions
+32
View File
@@ -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
}
Executable
+68
View File
@@ -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
+21
View File
@@ -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.
+141
View File
@@ -0,0 +1,141 @@
<p align="center">
<a href="http://www.form-create.com">
<img width="200" src="http://file.lotkk.com/form-create.png">
</a>
</p>
# 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
<link href="https://unpkg.com/element-plus@2.0.1/dist/index.css"></link>
<script src="https://unpkg.com/vue@next"></script>
<script src="https://unpkg.com/element-plus@2.0.1/dist/index.full.js"></script>
<script src="https://unpkg.com/@form-create/element-ui@next/dist/form-create.min.js"></script>
<script src="https://unpkg.com/@form-create/designer@next/dist/index.umd.js"></script>
```
**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
<fc-designer ref="designer"/>
```
## 组件`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
+4
View File
@@ -0,0 +1,4 @@
module.exports = {
'presets': [['@vue/cli-plugin-babel/preset', {'useBuiltIns': false}]],
'plugins': ['@vue/babel-plugin-jsx']
}
+303
View File
@@ -0,0 +1,303 @@
<template>
<div id="app">
<div class="_fc-t-header">
<img class="_fc-t-logo" src="http://form-create.com/logo.png">
<div class="_fc-t-name">form-create-designer</div>
<div class="_fc-t-menu">
<el-button size="small" @click="setJson"> 导入JSON</el-button>
<el-button size="small" @click="setOption"> 导入Options</el-button>
<el-button size="small" type="primary" @click="showJson">生成JSON</el-button>
<el-button size="small" type="success" @click="showOption">生成Options</el-button>
<el-button size="small" type="danger" @click="showTemplate">生成组件</el-button>
</div>
</div>
<fc-designer ref="designer"/>
<ElFooter class="_fc-copyright" height="30px">
<div class="_fc-b-item">
<el-popover
placement="top"
width="400"
trigger="hover">
<el-image src="http://form-create.com/img/donation.jpg"></el-image>
<template #reference>
<span>赞助</span>
</template>
</el-popover>
</div>
<span style="margin: 0 5px;">|</span>
<div class="_fc-b-item">
<iframe
src="https://ghbtns.com/github-btn.html?user=xaboy&amp;repo=form-create-designer&amp;type=star&amp;count=true&amp;size=mini"
frameborder="0" scrolling="0" width="90" height="21" title="GitHub"></iframe>
</div>
<span style="margin: 0 5px;">|</span>
<div class="_fc-b-item">
<a href='https://gitee.com/xaboy/form-create-designer/stargazers' style="display: inline-flex;"><img
src='https://gitee.com/xaboy/form-create-designer/badge/star.svg?theme=dark' alt='gitee'/></a>
</div>
</ElFooter>
<el-dialog :title="title[type]" v-model="state" class="_fc-t-dialog">
<div ref="editor" v-if="state"></div>
<span style="color: red;" v-if="err">输入内容格式有误!</span>
<template #footer v-if="type > 2">
<span slot="footer" class="dialog-footer">
<el-button @click="state = false" size="small"> </el-button>
<el-button type="primary" @click="onOk" size="small"> </el-button>
</span>
</template>
</el-dialog>
</div>
</template>
<script>
import jsonlint from 'jsonlint-mod';
import 'codemirror/lib/codemirror.css';
import 'codemirror/addon/lint/lint.css';
import CodeMirror from 'codemirror/lib/codemirror';
import 'codemirror/addon/lint/lint';
import 'codemirror/addon/lint/json-lint';
import 'codemirror/mode/javascript/javascript';
import 'codemirror/mode/vue/vue';
import 'codemirror/mode/xml/xml';
import 'codemirror/mode/css/css';
import 'codemirror/addon/mode/overlay';
import 'codemirror/addon/mode/simple';
import 'codemirror/addon/selection/selection-pointer';
import 'codemirror/mode/handlebars/handlebars';
import 'codemirror/mode/htmlmixed/htmlmixed';
import 'codemirror/mode/pug/pug';
import is from '@form-create/utils/lib/type';
import formCreate from '@form-create/element-ui';
// import image from '../src/components/custom/image.ts';
const TITLE = ['生成规则', '表单规则', '生成组件', '设置生成规则', '设置表单规则'];
export default {
name: 'app',
data() {
return {
state: false,
value: null,
title: TITLE,
editor: null,
err: false,
type: -1,
};
},
watch: {
state(n) {
if (!n) {
this.value = null;
this.err = false;
}
},
value() {
this.load();
}
},
methods: {
load() {
let val;
if (this.type === 2) {
val = this.value;
} else if (this.type === 0) {
val = formCreate.toJson(this.value, 2);
} else {
val = JSON.stringify(this.value, null, 2);
}
this.$nextTick(() => {
this.editor = CodeMirror(this.$refs.editor, {
lineNumbers: true,
mode: this.type === 2 ? {name: 'vue'} : 'application/json',
gutters: ['CodeMirror-lint-markers'],
// lint: true,
line: true,
tabSize: 2,
lineWrapping: true,
value: val || ''
});
});
},
onValidationError(e) {
this.err = e.length !== 0;
},
showJson() {
this.state = true;
this.type = 0;
this.value = this.$refs.designer.getRule();
},
showOption() {
this.state = true;
this.type = 1;
this.value = this.$refs.designer.getOption();
},
showTemplate() {
this.state = true;
this.type = 2;
this.value = this.makeTemplate();
},
setJson() {
this.state = true;
this.type = 3;
this.value = [];
},
setOption() {
this.state = true;
this.type = 4;
this.value = {form: {}};
},
onOk() {
if (this.err) return;
const json = this.editor.getValue();
let val = JSON.parse(json);
if (this.type === 3) {
if (!Array.isArray(val)) {
this.err = true;
return;
}
this.$refs.designer.setRule(formCreate.parseJson(json));
} else {
if (!is.Object(val) || !val.form) {
this.err = true;
return;
}
this.$refs.designer.setOption(val);
}
this.state = false;
},
makeTemplate() {
const rule = this.$refs.designer.getRule();
const opt = this.$refs.designer.getOption();
return `<template>
<form-create
v-model="fapi"
:rule="rule"
:option="option"
@submit="onSubmit"
></form-create>
</template>
<script>
import formCreate from "@form-create/element-ui";
export default {
data () {
return {
fapi: null,
rule: formCreate.parseJson('${formCreate.toJson(rule).replaceAll('\\', '\\\\')}'),
option: formCreate.parseJson('${JSON.stringify(opt)}')
}
},
methods: {
onSubmit (formData) {
//todo 提交表单
}
}
}
<\/script>`;
}
},
beforeCreate() {
window.jsonlint = jsonlint;
},
created() {
// this.$nextTick(() => {
// console.log("插入组件规则",this.$refs.designer);
// //插入组件规则
// this.$refs.designer.addComponent(image);
// //插入拖拽按钮到`main`分类下
// this.$refs.designer.appendMenuItem("main", {
// icon : image.icon,
// name : image.name,
// label: image.label
// });
// });
}
};
</script>
<style>
._fc-t-header {
height: 60px;
margin: 0 20px;
position: relative;
display: flex;
align-items: center;
cursor: default;
}
._fc-t-logo {
height: 26px;
}
._fc-t-name {
display: inline-block;
color: rgba(0, 0, 0, 0.8);
font-size: 20px;
font-weight: 600;
margin-left: 5px;
}
._fc-t-menu {
position: absolute;
right: 0;
}
._fc-t-menu i {
font-size: 12px;
}
body {
min-height: 100vh;
padding: 0;
margin: 0;
display: flex !important;
flex-direction: column !important;
}
#app {
display: flex;
flex-direction: column;
flex: 1;
}
._fc-copyright {
display: flex;
justify-content: flex-end;
align-items: center;
padding: 0 20px;
font-size: 16px;
border-top: 1px solid #ECECEC;
background-color: #fff;
cursor: pointer;
}
._fc-t-dialog .CodeMirror {
height: 450px;
}
._fc-t-dialog .CodeMirror-line {
line-height: 16px !important;
font-size: 13px !important;
}
.CodeMirror-lint-tooltip {
z-index: 2021 !important;
}
._fc-t-dialog .el-dialog__body {
padding: 0px 20px;
}
._fc-b-item {
display: flex;
}
</style>
+11
View File
@@ -0,0 +1,11 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>form-create-designer 示例</title>
</head>
<body>
<div id="app">
</div>
</body>
</html>
+18
View File
@@ -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')
+135
View File
@@ -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"
}
}
+33
View File
@@ -0,0 +1,33 @@
<script>
import {h, resolveComponent, defineComponent} from 'vue';
export default defineComponent({
name: 'DragBox',
props: ['rule', 'tag', 'formCreateInject'],
render(ctx) {
const subRule = {...ctx.$props.rule.props, ...ctx.$attrs};
let _class = subRule.tag + '-drag drag-box';
if (!Object.keys(ctx.$slots).length) {
_class += ' ' + subRule.tag + '-holder';
}
subRule.class = _class;
subRule.modelValue = [...this.$props.formCreateInject.children];
const keys = {};
if (ctx.$slots.default) {
const children = ctx.$slots.default();
children.forEach(v => {
if (v.key) {
keys[v.key] = v;
}
})
}
return h(resolveComponent('draggable'), subRule, {
item: ({element}) => {
return h('div', {}, keys[element.__fc__.key + 'fc'])
}
});
}
});
</script>
+136
View File
@@ -0,0 +1,136 @@
<template>
<div class="drag-tool" @click.stop="active" :class="{active: state.active === id}">
<div class="drag-mask" v-if="mask"></div>
<div class="drag-l">
<div class="drag-btn _fc-drag-btn" v-if="state.active === id && dragBtn !== false" style="cursor: move;">
<i class="fc-icon icon-move"></i>
</div>
</div>
<div class="drag-r">
<div class="drag-btn" @click="$emit('create')">
<i class="fc-icon icon-add"></i>
</div>
<div class="drag-btn" @click="$emit('copy')">
<i class="fc-icon icon-copy"></i>
</div>
<div class="drag-btn" v-if="children" @click="$emit('addChild')">
<i class="fc-icon icon-add-child"></i>
</div>
<div class="drag-btn drag-btn-danger" @click="$emit('delete')">
<i class="fc-icon icon-delete"></i>
</div>
</div>
<slot name="default"></slot>
</div>
</template>
<script>
import {computed, inject, toRefs, defineComponent} from 'vue';
let uni = 1;
export default defineComponent({
name: 'DragTool',
props: ['dragBtn', 'children', 'unique', 'mask'],
setup(props) {
const {unique} = toRefs(props);
const id = computed(() => unique.value || uni++);
const state = inject('fcx');
return {
id,
state
};
},
methods: {
active() {
if (this.state.active === this.id) return;
this.state.active = this.id;
this.$emit('active');
}
},
beforeDestroy() {
this.state = {};
}
});
</script>
<style>
.drag-tool {
position: relative;
min-height: 20px;
box-sizing: border-box;
padding: 2px;
outline: 1px dashed #2E73FF;
overflow: hidden;
word-wrap: break-word;
word-break: break-all;
}
.drag-tool .drag-tool {
margin: 5px;
}
.drag-tool + .drag-tool {
margin-top: 5px;
}
.drag-tool.active {
outline: 2px solid #2E73FF;
}
.drag-tool.active > div > .drag-btn {
display: flex;
}
.drag-tool .drag-btn {
display: none;
}
.drag-r {
position: absolute;
right: 2px;
bottom: 2px;
z-index: 1904;
}
.drag-l {
position: absolute;
top: 0;
left: 0;
z-index: 1904
}
.drag-btn {
height: 18px;
width: 18px;
color: #fff;
background-color: #2E73FF;
text-align: center;
line-height: 20px;
padding-bottom: 1px;
float: left;
cursor: pointer;
justify-content: center;
}
.drag-btn + .drag-btn {
margin-left: 2px;
}
.drag-btn-danger {
background-color: #FF2E2E;
}
.drag-btn i {
font-size: 13px;
}
.drag-mask {
z-index: 1900;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;;
}
</style>
+861
View File
@@ -0,0 +1,861 @@
<style>
._fc-designer {
height: 100%;
min-height: 500px;
overflow: hidden;
cursor: default;
position: relative;
}
._fc-designer > .el-main {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
padding: 0px;
}
._fc-m .form-create ._fc-l-item {
background: #2E73FF;
width: 100%;
height: 10px;
overflow: hidden;
transition: all .3s ease;
}
._fc-l, ._fc-m, ._fc-r {
border-top: 1px solid #ECECEC;
box-sizing: border-box;
}
._fc-l-group {
padding: 0 12px;
}
._fc-l-title {
font-weight: 600;
font-size: 14px;
margin: 18px 0px 5px;
}
._fc-l-item {
display: inline-block;
background: #FFF;
color: #000;
min-width: 70px;
width: 33.33%;
height: 70px;
line-height: 1;
text-align: center;
transition: all .2s ease;
cursor: pointer;
}
._fc-l-item i {
font-size: 21px;
display: inline-block;
}
._fc-l-item ._fc-l-name {
font-size: 12px;
}
._fc-l-item ._fc-l-icon {
padding: 10px 5px 12px;
}
._fc-l-item:hover {
background: #2E73FF;
color: #fff;
}
._fc-m-tools {
height: 40px;
align-items: center;
display: flex;
justify-content: flex-end;
border: 1px solid #ECECEC;
border-top: 0 none;
}
._fc-m-tools button.el-button {
padding: 5px 14px;
display: flex;
align-items: center;
}
._fc-m-tools .fc-icon {
font-size: 14px;
margin-right: 2px;
}
._fc-r .el-tabs__nav-wrap::after {
height: 1px;
background-color: #ECECEC;
}
._fc-r ._fc-r-tabs {
display: flex;
padding: 0;
border-bottom: 1px solid #ECECEC;
}
._fc-r ._fc-r-tab {
height: 40px;
box-sizing: border-box;
line-height: 40px;
display: inline-block;
list-style: none;
font-size: 14px;
font-weight: 600;
color: #303133;
position: relative;
flex: 1;
text-align: center;
}
._fc-r ._fc-r-tab.active {
color: #409EFF;
border-bottom: 2px solid #409EFF;
}
.drag-box {
min-height: 60px;
width: 100%;
}
._fc-m-drag {
overflow: auto;
padding: 2px;
box-sizing: border-box;
}
._fc-m-drag, .draggable-drag {
background: #fff;
height: 100%;
position: relative;
}
._fc-m-drag > form, ._fc-m-drag > form > .el-row {
height: 100%;
}
</style>
<template>
<ElContainer class="_fc-designer" :style="'height:'+dragHeight">
<ElMain>
<ElContainer style="height: 100%;">
<el-aside class="_fc-l" width="266px">
<template v-for="(item, index) in menuList" :key="index">
<div class="_fc-l-group">
<h4 class="_fc-l-title">{{ item.title }}</h4>
<draggable :group="{name:'default', pull:'clone',put:false}" :sort="false"
itemKey="name"
:list="item.list">
<template #item="{element}">
<div class="_fc-l-item">
<div class="_fc-l-icon">
<i class="fc-icon" :class="element.icon || 'icon-input'"></i>
</div>
<span class="_fc-l-name">{{ element.label }}</span>
</div>
</template>
</draggable>
</div>
</template>
</el-aside>
<ElContainer class="_fc-m">
<el-header class="_fc-m-tools" height="45">
<slot name="handle"></slot>
<el-button type="primary" plain round size="small"
@click="previewFc"> <i class="fc-icon icon-preview"></i>
</el-button>
<el-popconfirm
title="清空后将不能恢复,确定要清空吗?"
width="200px"
confirm-button-text="清空"
cancel-button-text="取消"
@confirm="clearDragRule">
<template #reference>
<el-button type="danger" plain round size="small"> <i class="fc-icon icon-delete"></i>
</el-button>
</template>
</el-popconfirm>
</el-header>
<ElMain style="background: #F5F5F5;padding: 20px;">
<div class="_fc-m-drag">
<DragForm :rule="dragForm.rule" :option="form.value"
v-model:api="dragForm.api"></DragForm>
</div>
</ElMain>
</ElContainer>
<ElAside class="_fc-r" width="320px">
<ElContainer style="height: 100%;">
<el-header height="40px" class="_fc-r-tabs">
<div class="_fc-r-tab" :class="{active: activeTab==='props'}" v-if="!!activeRule"
@click="activeTab='props'">组件配置
</div>
<div class="_fc-r-tab" :class="{active: activeTab==='form' && !!activeRule}"
@click="activeTab='form'">表单配置
</div>
</el-header>
<ElMain v-show="activeTab==='form'">
<DragForm :rule="form.rule" :option="form.option"
v-model="form.value.form"></DragForm>
</ElMain>
<ElMain v-show="activeTab==='props'" style="padding: 0 20px;"
:key="activeRule ? activeRule._id: ''">
<div>
<ElDivider v-if="showBaseRule">基础配置</ElDivider>
<DragForm v-show="showBaseRule" v-model:api="baseForm.api"
:rule="baseForm.rule"
:option="baseForm.options"
@change="baseChange"></DragForm>
<ElDivider>属性配置</ElDivider>
<DragForm v-model:api="propsForm.api" :rule="propsForm.rule"
:option="propsForm.options"
@change="propChange" @removeField="propRemoveField"></DragForm>
<ElDivider v-if="showBaseRule">验证规则</ElDivider>
<DragForm v-show="showBaseRule" v-model:api="validateForm.api"
:rule="validateForm.rule"
:option="validateForm.options"
@update:modelValue="validateChange"></DragForm>
</div>
</ElMain>
</ElContainer>
</ElAside>
<ElDialog v-model="preview.state" width="800px" append-to-body>
<ViewForm :rule="preview.rule" :option="preview.option" v-if="preview.state"></ViewForm>
</ElDialog>
</ElContainer>
</ElMain>
</ElContainer>
</template>
<style>
</style>
<script>
import form from '../config/base/form';
import field from '../config/base/field';
import validate from '../config/base/validate';
import {deepCopy} from '@form-create/utils/lib/deepextend';
import is, {hasProperty} from '@form-create/utils/lib/type';
import {lower} from '@form-create/utils/lib/tocase';
import ruleList from '../config/rule';
import draggable from 'vuedraggable/src/vuedraggable';
import createMenu from '../config/menu';
import {upper} from '../utils/index';
import {designerForm} from '../utils/form';
import viewForm from '../utils/form';
import {computed, reactive, toRefs, ref, getCurrentInstance, provide, nextTick, watch, defineComponent} from 'vue';
export default defineComponent({
name: 'FcDesigner',
components: {
draggable,
DragForm: designerForm.$form(),
ViewForm: viewForm.$form(),
},
props: ['menu', 'height', 'config', 'mask'],
setup(props) {
const {menu, height, config, mask} = toRefs(props);
const vm = getCurrentInstance();
provide('fcx', ref({active: null}));
provide('designer', vm.ctx);
const dragHeight = computed(() => {
const h = height.value;
if (!h) return '100%';
return is.Number(h) ? `${h}px` : h;
})
const data = reactive({
cacheProps: {},
moveRule: null,
addRule: null,
added: null,
activeTab: 'form',
activeRule: null,
children: ref([]),
menuList: menu.value || createMenu(),
showBaseRule: false,
visible: {
preview: false
},
preview: {
state: false,
rule: [],
option: {}
},
dragForm: ref({
rule: [],
api: {},
}),
form: {
rule: form(),
option: {
form: {
labelPosition: 'top',
size: 'small'
},
submitBtn: false
},
value: {
form: {
inline: false,
hideRequiredAsterisk: false,
labelPosition: 'right',
size: 'small',
labelWidth: '125px',
formCreateSubmitBtn: true,
formCreateResetBtn: false
},
submitBtn: false
}
},
baseForm: {
rule: field(),
api: {},
options: {
form: {
labelPosition: 'top',
size: 'small'
},
submitBtn: false,
mounted: (fapi) => {
fapi.activeRule = data.activeRule;
fapi.setValue(fapi.options.formData || {});
}
}
},
validateForm: {
rule: validate(),
api: {},
options: {
form: {
labelPosition: 'top',
size: 'small'
},
submitBtn: false,
mounted: (fapi) => {
fapi.activeRule = data.activeRule;
fapi.setValue(fapi.options.formData || {});
}
}
},
propsForm: {
rule: [],
api: {},
options: {
form: {
labelPosition: 'top',
size: 'small'
},
submitBtn: false,
mounted: (fapi) => {
fapi.activeRule = data.activeRule;
fapi.setValue(fapi.options.formData || {});
}
}
}
});
watch(() => data.preview.state, function (n) {
if (!n) {
nextTick(() => {
data.preview.rule = data.preview.option = null;
});
}
})
const methods = {
makeChildren(children) {
return reactive({children}).children;
},
addMenu(config) {
if (!config.name || !config.list) return;
let flag = true;
data.menuList.forEach((v, i) => {
if (v.name === config.name) {
data.menuList[i] = config
flag = false;
}
});
if (flag) {
data.menuList.push(config);
}
},
removeMenu(name) {
[...data.menuList].forEach((v, i) => {
if (v.name === name) {
data.menuList.splice(i, 1);
}
});
},
setMenuItem(name, list) {
data.menuList.forEach(v => {
if (v.name === name) {
v.list = list;
}
});
},
appendMenuItem(name, item) {
data.menuList.forEach(v => {
if (v.name === name) {
v.list.push(item);
}
});
},
removeMenuItem(item) {
data.menuList.forEach(v => {
let idx;
if (is.String(item)) {
[...v.list].forEach((menu, idx) => {
if (menu.name === item) {
v.list.splice(idx, 1);
}
});
} else {
if ((idx = v.list.indexOf(item)) > -1) {
v.list.splice(idx, 1);
}
}
});
},
addComponent(component) {
if (Array.isArray(component)) {
component.forEach(v => {
ruleList[v.name] = v;
});
} else {
ruleList[component.name] = component;
}
},
getParent(rule) {
let parent = rule.__fc__.parent.rule;
const config = parent.config;
if (config && config.config.inside) {
rule = parent;
parent = parent.__fc__.parent.rule;
}
return {root: parent, parent: rule};
},
makeDrag(group, tag, children, on) {
return {
type: 'DragBox',
wrap: {
show: false
},
col: {
show: false
},
inject: true,
props: {
rule: {
props: {
tag: 'el-col',
group: group === true ? 'default' : group,
ghostClass: 'ghost',
animation: 150,
handle: '._fc-drag-btn',
emptyInsertThreshold: 0,
direction: 'vertical',
itemKey: 'type',
}
},
tag,
},
children,
on
};
},
clearDragRule() {
methods.setRule([]);
},
makeDragRule(children) {
return methods.makeChildren([methods.makeDrag(true, 'draggable', children, {
add: (inject, evt) => methods.dragAdd(children, evt),
end: (inject, evt) => methods.dragEnd(children, evt),
start: (inject, evt) => methods.dragStart(children, evt),
unchoose: (inject, evt) => methods.dragUnchoose(children, evt),
})]);
},
previewFc() {
data.preview.state = true;
data.preview.rule = methods.getRule();
data.preview.option = methods.getOption();
},
getRule() {
return methods.parseRule(deepCopy(data.dragForm.api.rule[0].children));
},
getJson() {
return designerForm.toJson(methods.getRule());
},
getOption() {
const option = deepCopy(data.form.value);
option.submitBtn = option.form.formCreateSubmitBtn;
option.resetBtn = option.form.formCreateResetBtn;
delete option.form.formCreateSubmitBtn;
delete option.form.formCreateResetBtn;
return option;
},
setRule(rules) {
data.children = methods.makeChildren(methods.loadRule(is.String(rules) ? designerForm.parseJson(rules) : rules));
methods.clearActiveRule();
data.dragForm.rule = methods.makeDragRule(data.children);
},
clearActiveRule() {
data.activeRule = null;
data.activeTab = 'form';
},
setOption(opt) {
let option = {...opt};
option.form.formCreateSubmitBtn = !!option.submitBtn;
option.form.formCreateResetBtn = !!option.resetBtn;
option.submitBtn = false;
delete option.resetBtn;
data.form.value = option;
},
loadRule(rules) {
const loadRule = [];
rules.forEach(rule => {
if (is.String(rule)) {
return loadRule.push(rule);
}
const config = ruleList[rule._fc_drag_tag] || ruleList[rule.type];
const _children = rule.children;
rule.children = [];
if (rule.control) {
rule._control = rule.control;
delete rule.control;
}
if (config) {
rule = methods.makeRule(config, rule);
if (_children) {
let children = rule.children[0].children;
if (config.drag) {
children = children[0].children;
}
children.push(...methods.loadRule(_children));
}
} else if (_children) {
rule.children = methods.loadRule(_children);
}
loadRule.push(rule);
});
return loadRule;
},
parseRule(children) {
return [...children].reduce((initial, rule) => {
if (is.String(rule)) {
initial.push(rule);
return initial;
} else if (rule.type === 'DragBox') {
initial.push(...methods.parseRule(rule.children));
return initial;
} else if (rule.type === 'DragTool') {
rule = rule.children[0];
if (rule.type === 'DragBox') {
initial.push(...methods.parseRule(rule.children));
return initial;
}
}
if (!rule) return initial;
rule = {...rule};
if (rule.children.length) {
rule.children = methods.parseRule(rule.children);
}
delete rule._id;
if (rule.config) {
delete rule.config.config;
}
if (rule.effect) {
delete rule.effect._fc;
delete rule.effect._fc_tool;
}
if (rule._control) {
rule.control = rule._control;
delete rule._control;
}
Object.keys(rule).filter(k => (Array.isArray(rule[k]) && rule[k].length === 0) || (is.Object(rule[k]) && Object.keys(rule[k]).length === 0)).forEach(k => {
delete rule[k];
});
initial.push(rule);
return initial;
}, []);
},
baseChange(field, value, _, fapi) {
if (data.activeRule && fapi[data.activeRule._id] === data.activeRule) {
data.activeRule[field] = value;
}
},
propRemoveField(field, _, fapi) {
if (data.activeRule && fapi[data.activeRule._id] === data.activeRule) {
data.dragForm.api.sync(data.activeRule);
if (field.indexOf('formCreate') === 0) {
field = field.replace('formCreate', '');
if (!field) return;
field = lower(field);
if (field.indexOf('effect') === 0 && field.indexOf('>') > -1) {
delete data.activeRule.effect[field.split('>')[1]];
} else if (field.indexOf('props') === 0 && field.indexOf('>') > -1) {
delete data.activeRule.props[field.split('>')[1]];
} else if (field === 'child') {
delete data.activeRule.children[0];
} else if (field) {
data.activeRule[field] = undefined;
}
} else {
delete data.activeRule.props[field];
}
}
},
propChange(field, value, _, fapi) {
if (data.activeRule && fapi[data.activeRule._id] === data.activeRule) {
if (field.indexOf('formCreate') === 0) {
field = field.replace('formCreate', '');
if (!field) return;
field = lower(field);
if (field.indexOf('effect') === 0 && field.indexOf('>') > -1) {
data.activeRule.effect[field.split('>')[1]] = value;
} else if (field.indexOf('props') === 0 && field.indexOf('>') > -1) {
data.activeRule.props[field.split('>')[1]] = value;
} else if (field === 'child') {
data.activeRule.children[0] = value;
} else {
data.activeRule[field] = value;
}
} else {
data.activeRule.props[field] = value;
}
}
},
validateChange(formData) {
if (!data.activeRule || data.validateForm.api[data.activeRule._id] !== data.activeRule) return;
data.activeRule.validate = formData.validate || [];
data.dragForm.api.refreshValidate();
data.dragForm.api.nextTick(() => {
data.dragForm.api.clearValidateState(data.activeRule.field);
});
},
toolActive(rule) {
if (data.activeRule) {
delete data.propsForm.api[data.activeRule._id];
delete data.baseForm.api[data.activeRule._id];
delete data.validateForm.api[data.activeRule._id];
}
data.activeRule = rule;
nextTick(() => {
data.activeTab = 'props';
nextTick(() => {
data.propsForm.api[data.activeRule._id] = data.activeRule;
data.baseForm.api[data.activeRule._id] = data.activeRule;
data.validateForm.api[data.activeRule._id] = data.activeRule;
});
});
if (!data.cacheProps[rule._id]) {
data.cacheProps[rule._id] = rule.config.config.props(rule);
}
data.propsForm.rule = data.cacheProps[rule._id];
const formData = {...rule.props, formCreateChild: rule.children[0]};
Object.keys(rule).forEach(k => {
if (['effect', 'config', 'payload', 'id', 'type'].indexOf(k) < 0)
formData['formCreate' + upper(k)] = rule[k];
});
['props', 'effect'].forEach(name => {
rule[name] && Object.keys(rule[name]).forEach(k => {
formData['formCreate' + upper(name) + '>' + k] = rule[name][k];
});
});
data.propsForm.options.formData = formData;
data.showBaseRule = hasProperty(rule, 'field') && rule.input !== false && (!config.value || config.value.showBaseForm !== false);
if (data.showBaseRule) {
data.baseForm.options.formData = {
field: rule.field,
title: rule.title || '',
info: rule.info,
_control: rule._control,
};
data.validateForm.options.formData = {validate: rule.validate ? [...rule.validate] : []};
}
},
dragStart(children) {
console.log('dragStart');
data.moveRule = children;
data.added = false;
},
dragUnchoose(children, evt) {
console.log('dragUnchoose');
data.addRule = {
children,
oldIndex: evt.oldIndex
};
},
dragAdd(children, evt) {
console.log('dragAdd');
const newIndex = evt.newIndex;
const menu = evt.item._underlying_vm_;
if (!menu || menu.__fc__) {
if (data.addRule) {
const rule = data.addRule.children.splice(data.addRule.oldIndex, 1);
children.splice(newIndex, 0, rule[0]);
}
} else {
const rule = methods.makeRule(ruleList[menu.name]);
children.splice(newIndex, 0, rule);
}
data.added = true;
// data.dragForm.api.refresh();
},
dragEnd(children, {newIndex, oldIndex}) {
console.log('dragEnd');
if (!data.added && !(data.moveRule === children && newIndex === oldIndex)) {
const rule = data.moveRule.splice(oldIndex, 1);
children.splice(newIndex, 0, rule[0]);
}
data.moveRule = null;
data.addRule = null;
data.added = false;
// data.dragForm.api.refresh();
},
makeRule(config, _rule) {
const rule = _rule || config.rule();
rule.config = {config};
if (!rule.effect) rule.effect = {};
rule.effect._fc = true;
rule._fc_drag_tag = config.name;
let drag;
if (config.drag) {
rule.children.push(drag = methods.makeDrag(config.drag, rule.type, methods.makeChildren([]), {
end: (inject, evt) => methods.dragEnd(inject.self.children, evt),
add: (inject, evt) => methods.dragAdd(inject.self.children, evt),
start: (inject, evt) => methods.dragStart(inject.self.children, evt),
unchoose: (inject, evt) => methods.dragUnchoose(inject.self.children, evt),
}));
}
if (config.children && !_rule) {
const child = methods.makeRule(ruleList[config.children]);
(drag || rule).children.push(child);
}
const dragMask = mask.value !== undefined ? mask.value !== false : config.mask !== false;
if (config.inside) {
rule.children = methods.makeChildren([{
type: 'DragTool',
props: {
dragBtn: config.dragBtn !== false,
children: config.children,
mask: dragMask,
},
effect: {
_fc_tool: true
},
inject: true,
on: {
delete: ({self}) => {
const parent = methods.getParent(self).parent;
parent.__fc__.rm();
vm.emit('delete', parent);
methods.clearActiveRule();
},
create: ({self}) => {
const top = methods.getParent(self);
vm.emit('create', top.parent);
top.root.children.splice(top.root.children.indexOf(top.parent) + 1, 0, methods.makeRule(top.parent.config.config));
},
addChild: ({self}) => {
const top = methods.getParent(self);
const config = top.parent.config.config;
const item = ruleList[config.children];
if (!item) return;
(!config.drag ? top.parent : top.parent.children[0]).children[0].children.push(methods.makeRule(item));
},
copy: ({self}) => {
const top = methods.getParent(self);
vm.emit('copy', top.parent);
top.root.children.splice(top.root.children.indexOf(top.parent) + 1, 0, designerForm.copyRule(top.parent));
},
active: ({self}) => {
const top = methods.getParent(self);
vm.emit('active', top.parent);
methods.toolActive(top.parent);
}
},
children: rule.children
}]);
return rule;
} else {
return {
type: 'DragTool',
props: {
dragBtn: config.dragBtn !== false,
children: config.children,
mask: dragMask,
},
effect: {
_fc_tool: true
},
inject: true,
on: {
delete: ({self}) => {
vm.emit('delete', self.children[0]);
self.__fc__.rm();
methods.clearActiveRule();
},
create: ({self}) => {
vm.emit('create', self.children[0]);
const top = methods.getParent(self);
top.root.children.splice(top.root.children.indexOf(top.parent) + 1, 0, methods.makeRule(self.children[0].config.config));
},
addChild: ({self}) => {
const config = self.children[0].config.config;
const item = ruleList[config.children];
if (!item) return;
(!config.drag ? self : self.children[0]).children[0].children.push(methods.makeRule(item));
},
copy: ({self}) => {
vm.emit('copy', self.children[0]);
const top = methods.getParent(self);
top.root.children.splice(top.root.children.indexOf(top.parent) + 1, 0, designerForm.copyRule(top.parent));
},
active: ({self}) => {
vm.emit('active', self.children[0]);
methods.toolActive(self.children[0]);
}
},
children: methods.makeChildren([rule])
};
}
},
}
data.dragForm.rule = methods.makeDragRule(methods.makeChildren(data.children));
return {
...toRefs(data), ...methods, dragHeight
}
},
created() {
document.body.ondrop = e => {
e.preventDefault();
e.stopPropagation();
};
}
});
</script>
+162
View File
@@ -0,0 +1,162 @@
<template>
<div class="_fc_fetch">
<DragForm v-model:api="api" :modelValue="formValue" :rule="rule" :option="option" @change="input"/>
</div>
</template>
<script>
import debounce from '@form-create/utils/lib/debounce';
import is from '@form-create/utils/lib/type';
import {designerForm} from '../utils/form';
import {defineComponent} from 'vue';
export default defineComponent({
name: 'Fetch',
props: {
modelValue: [Object, String],
to: String,
},
components: {
DragForm: designerForm.$form(),
},
computed: {
formValue() {
const val = this.modelValue;
if (!val) return {};
if (is.String(val)) {
return {
action: val
};
}
if (!val._parse && val.parse) {
return {...val, _parse: '' + val.parse};
} else if (is.Function(val._parse)) {
return {...val, _parse: '' + val._parse};
}
return val;
}
},
data() {
return {
api: {},
fetch: {},
option: {
form: {
labelPosition: 'right',
size: 'small',
labelWidth: '90px'
},
submitBtn: false,
},
rule: [
{
type: 'input',
field: 'action',
title: '接口: ',
validate: [{required: true, message: '请数据接口'}]
},
{
type: 'select',
field: 'method',
title: '请求方式: ',
value: 'GET',
options: [
{label: 'GET', value: 'GET'},
{label: 'POST', value: 'POST'},
],
control: [
{
value: 'POST',
rule: [
{
type: 'select',
field: 'dataType',
title: '提交方式: ',
value: 'FormData',
options: [
{label: 'FormData', value: 'FormData'},
{label: 'JSON', value: 'JSON'},
]
},
]
}
]
},
{
type: 'Struct',
field: 'data',
title: '附带数据: ',
value: {},
props: {
defaultValue: {},
}
},
{
type: 'Struct',
field: 'headers',
title: 'header信息: ',
value: {},
props: {
defaultValue: {},
}
},
{
type: 'input',
field: '_parse',
title: '解析函数',
info: '解析接口数据,返回组件所需的数据结构',
value: 'function (res){\n return res.data;\n}',
props: {
type: 'textarea',
rows: 8,
},
validate: [{
validator: (_, v, cb) => {
if (!v) return cb();
try {
this.parseFn(v);
} catch (e) {
return cb(false);
}
cb();
}, message: '请输入正确的解析函数'
}]
},
]
};
},
methods: {
parseFn(v) {
return eval(`(function () {
return ${v}
})()`);
},
_input() {
this.api.submit((formData) => {
formData.to = this.to || 'options';
if (formData._parse) formData.parse = this.parseFn(formData._parse);
this.$emit('update:modelValue', formData);
});
},
input: debounce(function () {
this._input();
}, 1000),
},
mounted() {
this._input();
}
});
</script>
<style>
._fc_fetch .el-form-item__label {
float: left;
display: inline-block;
text-align: right;
padding-right: 5px;
}
._fc_fetch {
background-color: #bfdaf7;
padding: 10px;
}
</style>
+14
View File
@@ -0,0 +1,14 @@
<template>
<svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" data-v-029747aa="">
<path fill="currentColor"
d="M771.776 794.88A384 384 0 0 1 128 512h64a320 320 0 0 0 555.712 216.448H654.72a32 32 0 1 1 0-64h149.056a32 32 0 0 1 32 32v148.928a32 32 0 1 1-64 0v-50.56zM276.288 295.616h92.992a32 32 0 0 1 0 64H220.16a32 32 0 0 1-32-32V178.56a32 32 0 0 1 64 0v50.56A384 384 0 0 1 896.128 512h-64a320 320 0 0 0-555.776-216.384z"></path>
</svg>
</template>
<script>
import {defineComponent} from 'vue';
export default defineComponent({
name: 'IconRefresh'
});
</script>
+64
View File
@@ -0,0 +1,64 @@
<template>
<div class="_fc-required">
<ElSwitch v-model="required"></ElSwitch>
<ElInput v-model="requiredMsg" v-if="required" placeholder="请输入提示语"></ElInput>
</div>
</template>
<script>
import is from '@form-create/utils/lib/type';
import {defineComponent} from 'vue';
export default defineComponent({
name: 'Required',
props: {
modelValue: {}
},
watch: {
required() {
this.update();
},
requiredMsg() {
this.update();
},
modelValue(n) {
const flag = is.String(n);
this.required = n === undefined ? false : (flag ? true : !!n);
this.requiredMsg = flag ? n : '';
},
},
data() {
const flag = is.String(this.modelValue);
return {
required: this.modelValue === undefined ? false : (flag ? true : !!this.modelValue),
requiredMsg: flag ? this.modelValue : ''
};
},
methods: {
update() {
let val;
if (this.required === false) {
val = false;
} else {
val = this.requiredMsg;
}
this.$emit('update:modelValue', val);
},
}
});
</script>
<style>
._fc-required {
display: flex;
align-items: center;
}
._fc-required .el-input {
margin-left: 15px;
}
._fc-required .el-switch {
height: 28px;
}
</style>
+127
View File
@@ -0,0 +1,127 @@
<template>
<div class="_fc_struct">
<ElButton @click="visible=true" style="width: 100%;">{{ title }}</ElButton>
<ElDialog :title="title" v-model="visible" :close-on-click-modal="false" append-to-body>
<div ref="editor" v-if="visible"></div>
<template #footer>
<span class="dialog-footer">
<span class="_fc_err" v-if="err">
输入内容格式有误{{ err !== true ? err : '' }}</span>
<ElButton @click="visible = false" size="small"> </ElButton>
<ElButton type="primary" @click="onOk" size="small"> </ElButton>
</span>
</template>
</ElDialog>
</div>
</template>
<script>
import 'codemirror/lib/codemirror.css';
import CodeMirror from 'codemirror/lib/codemirror';
import 'codemirror/mode/javascript/javascript';
import {deepParseFn, toJSON} from '../utils/index';
import {deepCopy} from '@form-create/utils/lib/deepextend';
import {defineComponent} from 'vue';
export default defineComponent({
name: 'Struct',
props: {
modelValue: [Object, Array],
title: {
type: String,
default: '编辑数据'
},
defaultValue: {
require: false
},
validate: Function,
},
data() {
return {
editor: null,
visible: false,
err: false,
oldVal: null,
};
},
watch: {
modelValue() {
this.load();
},
visible(n) {
if (n) {
this.load();
} else {
this.err = false;
}
}
},
methods: {
load() {
const val = toJSON(this.modelValue ? deepParseFn(deepCopy(this.modelValue)) : this.defaultValue);
this.oldVal = val;
this.$nextTick(() => {
this.editor = CodeMirror(this.$refs.editor, {
lineNumbers: true,
mode: 'javascript',
gutters: ['CodeMirror-lint-markers'],
lint: true,
line: true,
tabSize: 2,
lineWrapping: true,
value: val || ''
});
});
},
onOk() {
if (this.err) return;
const str = this.editor.getValue();
let val;
try {
val = eval('(function (){return ' + str + '}())');
} catch (e) {
this.err = ` (${e})`;
return;
}
if (this.validate && false === this.validate(val)) {
this.err = true;
return;
}
this.visible = false;
if (toJSON(val, null, 2) !== this.oldVal) {
this.$emit('update:modelValue', val);
}
},
}
});
</script>
<style>
._fc_struct{
width: 100%;
}
._fc_struct .CodeMirror {
height: 450px;
}
._fc_struct .CodeMirror-line {
line-height: 16px !important;
font-size: 13px !important;
}
.CodeMirror-lint-tooltip {
z-index: 2021 !important;
}
._fc_struct .el-dialog__body {
padding: 0px 20px;
}
._fc_err {
color: red;
float: left;
text-align: left;
width: 65%;
}
</style>
+75
View File
@@ -0,0 +1,75 @@
<template>
<div class="_fc_table_opt">
<el-table
:data="modelValue"
border
size="small"
style="width: 100%">
<template v-for="(col,idx) in column" :key="col.label + idx">
<el-table-column :label="col.label">
<template #default="scope">
<el-input size="small" :modelValue="scope.row[col.key] || ''"
@Update:modelValue="(n)=>(scope.row[col.key] = n, onInput(scope.row))"></el-input>
</template>
</el-table-column>
</template>
<el-table-column min-width="35" align="center" fixed="right" label="操作">
<template #default="scope">
<i class="fc-icon icon-delete" @click="del(scope.$index)"></i>
</template>
</el-table-column>
</el-table>
<el-button link type="primary" @click="add">
<i class="fc-icon icon-add"></i> 添加
</el-button>
</div>
</template>
<script>
import {defineComponent} from 'vue';
export default defineComponent({
name: 'TableOptions',
inheritAttrs: false,
props: {
modelValue: [Object, Array, String]
},
data() {
return {
column: [{label: 'label', key: 'label'}, {label: 'value', key: 'value'}]
};
},
created() {
if (!Array.isArray(this.modelValue)) {
this.$emit('input', []);
}
},
methods: {
onInput(item) {
if (item.label && item.value) {
this.input();
}
},
input() {
this.$emit('update:modelValue', this.modelValue);
},
add() {
this.modelValue.push(this.column.reduce((initial, v) => {
initial[v.key] = '';
return initial;
}, {}));
},
del(idx) {
this.modelValue.splice(idx, 1);
this.input(this.modelValue);
}
}
});
</script>
<style scoped>
._fc_table_opt {
width: 100%;
}
</style>
+229
View File
@@ -0,0 +1,229 @@
<template>
<DragForm class="_fc-validate" :rule="rule" :option="option" :modelValue="formValue"
@update:modelValue="onInput"></DragForm>
</template>
<script>
import {designerForm} from '../utils/form';
import {defineComponent} from 'vue';
export default defineComponent({
name: 'Validate',
inject: ['designer'],
props: {
modelValue: Array
},
components: {
DragForm: designerForm.$form(),
},
watch: {
modelValue(n) {
this.formValue = this.parseValue(n);
}
},
data() {
return {
formValue: {},
option: {
form: {
labelPosition: 'top',
size: 'small',
labelWidth: '90px'
},
submitBtn: false,
formData: this.parseValue(this.modelValue)
},
rule: [
{
type: 'select',
field: 'type',
value: '',
title: '字段类型',
options: [
{value: '', label: '请选择'},
{value: 'string', label: 'String'},
{value: 'array', label: 'Array'},
{value: 'number', label: 'Number'},
{value: 'integer', label: 'Integer'},
{value: 'float', label: 'Float'},
{value: 'object', label: 'Object'},
{value: 'date', label: 'Date'},
{value: 'url', label: 'url'},
{value: 'hex', label: 'hex'},
{value: 'email', label: 'email'},
],
control: [
{
handle: v => {
return !!v;
},
rule: [
{
type: 'group',
field: 'validate',
props: {
expand: 1,
rule: [
{
type: 'select',
title: '触发方式',
field: 'trigger',
value: 'change',
options: [
{label: 'change', value: 'change'},
{label: 'submit', value: 'submit'},
{label: 'blur', value: 'blur'},
]
},
{
type: 'select',
title: '验证方式',
field: 'mode',
options: [
{value: 'required', label: '必填'},
{value: 'pattern', label: '正则表达式'},
{value: 'min', label: '最小值'},
{value: 'max', label: '最大值'},
{value: 'len', label: '长度'},
],
value: 'required',
control: [
{
value: 'required',
rule: [
{
type: 'hidden',
field: 'required',
value: true
}
]
},
{
value: 'pattern',
rule: [
{
type: 'input',
field: 'pattern',
title: '正则表达式'
}
]
},
{
value: 'min',
rule: [
{
type: 'inputNumber',
field: 'min',
title: '最小值'
}
]
},
{
value: 'max',
rule: [
{
type: 'inputNumber',
field: 'max',
title: '最大值'
}
]
},
{
value: 'len',
rule: [
{
type: 'inputNumber',
field: 'len',
title: '长度'
}
]
},
]
},
{
type: 'input',
title: '错误信息',
field: 'message',
value: '',
children: [
{
type: 'span',
slot: 'append',
inject: true,
class: 'append-msg',
on: {
click: (inject) => {
if (this.designer.activeRule) {
let msg = '请输入';
if (inject.api.form.mode !== 'required') {
msg += '正确的';
}
msg += this.designer.activeRule.title;
inject.api.setValue('message', msg);
}
}
},
children: ['自动获取']
}
]
}
]
},
value: []
}
]
}
]
},
]
};
},
methods: {
onInput: function (formData) {
let val = [];
const {validate, type} = formData;
if (type && (!validate || !validate.length)) {
return;
} else if (type) {
validate.forEach(v => {
v.type = type;
});
val = [...validate];
}
this.$emit('update:modelValue', val);
},
parseValue(n) {
let val = {
validate: n ? [...n] : [],
type: n.length ? n[0].type : undefined
};
val.validate.forEach(v => {
if (!v.mode) {
Object.keys(v).forEach(k => {
if (['message', 'type', 'trigger', 'mode'].indexOf(k) < 0) {
v.mode = k;
}
});
}
});
return val;
}
}
});
</script>
<style>
._fc-validate .form-create .el-form-item {
margin-bottom: 22px !important;
}
._fc-validate .append-msg {
cursor: pointer;
}
._fc-validate .el-input-group__append {
padding: 0 10px;
}
</style>
+84
View File
@@ -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: ['刷新']
},
]
}
];
}
+55
View File
@@ -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: '是否显示表单重置按钮',
},
];
}
+9
View File
@@ -0,0 +1,9 @@
export default function validate() {
return [
{
type: 'validate',
field: 'validate',
value: []
},
];
}
+51
View File
@@ -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
]
},
];
}
+45
View File
@@ -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'}]
}];
}
};
+54
View File
@@ -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: '图标类名'
}];
}
};
+180
View File
@@ -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: '选项分隔符'
}];
}
};
+45
View File
@@ -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 激活时的填充色和边框色'}];
}
};
+23
View File
@@ -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}}];
}
};
+36
View File
@@ -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'}]
}];
}
};
+75
View File
@@ -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: '自定义清空图标的类名'
}];
}
};
+37
View File
@@ -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'
}]
}];
}
};
+27
View File
@@ -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: "宽度" },
];
}
};
+23
View File
@@ -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: '是否禁用'}];
}
};
+57
View File
@@ -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: "图像描述"},
];
}
};
+61
View File
@@ -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;
+65
View File
@@ -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: '自动获取焦点'}];
}
};
+41
View File
@@ -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'}];
}
};
+42
View File
@@ -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 激活时的填充色和边框色'
}];
}
};
+44
View File
@@ -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: '分数显示模板'
}];
}
};
+41
View File
@@ -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'
}]
}];
}
};
+62
View File
@@ -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,是否在输入框获得焦点后自动弹出选项菜单'}];
}
};
+44
View File
@@ -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 高度,竖向模式时必填'
}];
}
};
+41
View File
@@ -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',
},
]
}
}
];
}
};
+33
View File
@@ -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'
}
}
];
}
};
+39
View File
@@ -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 关闭时的背景色'
}];
}
};
+36
View File
@@ -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: '标签的宽度是否自撑开'}];
}
};
+29
View File
@@ -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: '标签是否延迟渲染'
}];
}
};
+57
View File
@@ -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: '自定义清空图标的类名'
}];
}
};
+84
View File
@@ -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: []}
}];
}
};
+100
View File
@@ -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: '每个树节点用来作为唯一标识的属性,整棵树应该是唯一的'
}];
}
};
+64
View File
@@ -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: '最大允许上传个数'
}];
}
};
+56
View File
@@ -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};
Binary file not shown.
+17
View File
@@ -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";
}
Binary file not shown.
+138
View File
@@ -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";
}
+9
View File
@@ -0,0 +1,9 @@
import formCreate from '@form-create/element-ui';
const viewForm = formCreate;
const designerForm = formCreate.factory();
export default viewForm;
export {designerForm};
+132
View File
@@ -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;
};
+61
View File
@@ -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 <ui-compnents...>', 'Build @form-create/component-<ui-package> package or packages array') // 打单独的组件
.option('-p, --packages <ui-package...>', 'Build @form-create/<ui-package> 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
}
+163
View File
@@ -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)
}
+73
View File
@@ -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
+88
View File
@@ -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<any>) => {
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
+14
View File
@@ -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');
+140
View File
@@ -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}`
}
})
}
+69
View File
@@ -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
}
+21
View File
@@ -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"
]
}
+31
View File
@@ -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<Menu> {
}
export interface DragRule {
name: string;
rule(): Rule;
props(): Rule[];
children?: string;
inside?: true;
drag?: true | String;
dragBtn?: false;
mask?: false;
}
+85
View File
@@ -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]
})
+22
View File
@@ -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()]
})
+28
View File
@@ -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'
},
]
}
}
}