'开源首次提交代码'

pull/1/head
vdpAdmin 2021-09-23 17:35:06 +08:00
commit dae58f925b
97 changed files with 10983 additions and 0 deletions

26
.gitignore vendored Normal file
View File

@ -0,0 +1,26 @@
.DS_Store
node_modules
/dist
/dist0/
/dist2
# local env files
.env.local
.env.*.local
# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
/package-lock.json
/VariantForm.iml

37
README.md Normal file
View File

@ -0,0 +1,37 @@
# Variant Form
##### 一款高效的Vue表单可视化设计一键生成源码享受更多摸鱼时间。
文档官网http://www.vform666.com/
在线演示http://demo.vform666.com/
VS Code插件http://www.vform666.com/pages/plugin/
Github仓库https://github.com/vform666/variant-form
Gitee备份仓库https://gitee.com/vdpadmin/variant-form
技术交流群微信搜索“vformAdmin”或者扫如下二维码加群
![image](https://ks3-cn-beijing.ksyuncs.com/vform-static/img/vx-qrcode-242.png)
<br/>
#### 安装依赖
```
npm install
```
#### 开发调试
```
npm run serve
```
#### 生产打包
```
npm run build
```
#### 浏览器兼容性
```Chrome及同内核的浏览器如QQ浏览器、360浏览器等等FirefoxSafariIE 11```

5
babel.config.js Normal file
View File

@ -0,0 +1,5 @@
module.exports = {
presets: [
'@vue/cli-plugin-babel/preset'
]
}

View File

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

View File

@ -0,0 +1,28 @@
<!DOCTYPE html>
<html lang="">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="renderer" content="webkit">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<!-- 禁止浏览器缓存index.html begin -->
<meta http-equiv="Expires" content="0">
<meta http-equiv="Pragma" content="no-cache">
<meta http-equiv="Cache-control" content="no-cache">
<meta http-equiv="Cache" content="no-cache">
<!-- 禁止浏览器缓存index.html end -->
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title><%= htmlWebpackPlugin.options.title %></title>
<link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css">
</head>
<body>
<noscript>
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<script src="https://unpkg.com/element-ui/lib/index.js"></script>
</body>
</html>

31
install-render.js Normal file
View File

@ -0,0 +1,31 @@
import VFormRender from '@/components/form-render/index.vue'
import ContainerItem from "@/components/form-render/container-item";
import axios from "axios";
VFormRender.install = function (Vue) {
Vue.component(VFormRender.name, VFormRender)
}
const components = [
VFormRender
]
const install = (Vue) => {
/* 递归组件如需在递归组件的嵌套组件中使用,必须注册为全局组件,原因不明?? begin */
Vue.component('container-item', ContainerItem)
/* end */
components.forEach(component => {
Vue.component(component.name, component)
})
}
if (typeof window !== 'undefined' && window.Vue) { /* script方式引入时主动调用install方法 */
window.axios = axios
install(window.Vue);
}
export default {
install,
VFormRender
}

45
install.js Normal file
View File

@ -0,0 +1,45 @@
import axios from 'axios'
import VFormDesigner from '@/components/form-designer/index.vue'
import VFormRender from '@/components/form-render/index.vue'
import ContainerWidget from "@/components/form-designer/form-widget/container-widget";
import ContainerItem from "@/components/form-render/container-item";
import '@/utils/directive'
import '@/icons'
import '@/iconfont/iconfont.css'
VFormDesigner.install = function (Vue) {
Vue.component(VFormDesigner.name, VFormDesigner)
}
VFormRender.install = function (Vue) {
Vue.component(VFormRender.name, VFormRender)
}
const components = [
VFormDesigner,
VFormRender
]
const install = (Vue) => {
/* 递归组件如需在递归组件的嵌套组件中使用,必须注册为全局组件,原因不明?? begin */
Vue.component('container-widget', ContainerWidget)
Vue.component('container-item', ContainerItem)
/* end */
components.forEach(component => {
Vue.component(component.name, component)
})
}
if (typeof window !== 'undefined' && window.Vue) { /* script方式引入时主动调用install方法 */
window.axios = axios
install(window.Vue);
}
export default {
install,
VFormDesigner,
VFormRender
}

10
jsconfig.json Normal file
View File

@ -0,0 +1,10 @@
{
"######": "本文件用于解决IDEA无法识别Vue项目@符号的问题",
"compilerOptions": {
"baseUrl": "./",
"paths": {
"@/*": ["src/*"]
}
},
"exclude": ["node_modules", "dist"]
}

8
license.txt Normal file
View File

@ -0,0 +1,8 @@
# Variant Form 许可条款 1.0
1. 免责声明任何情况下根据任何法律本作者不对用户因使用VariantForm产生的侵权、数据损坏丢失、软硬件故障和违法犯罪等问题承担任何责任
2. 禁止任何用户对VariantForm进行简单包装后即声称为自己的产品
3. VariantForm为开源项目获取到源代码的用户可自由修改源码供自身开发使用可分发build构建后的库代码也可分发VariantForm源代码需保留文件头部的作者声明本作者保留VariantForm的原始著作权
4. 个人或公司用户均可将VariantForm项目应用于商业项目开发为支持本项目持续开发请尽量购买VariantForm Pro版源码订阅更新服务
5. 如果你不同意本许可条款请勿使用VariantForm任何情况下一旦实际使用VariantForm则代表你已确定完全同意本许可条款
6. 条款内容结束。

57
package.json Normal file
View File

@ -0,0 +1,57 @@
{
"name": "variant-form",
"version": "1.1.2",
"private": false,
"scripts": {
"serve": "vue-cli-service serve --open",
"build": "vue-cli-service build --report --dest dist0",
"lib": "vue-cli-service build --report --target lib --name VFormDesigner install.js",
"lib-render": "vue-cli-service build --report --target lib --dest dist2 --name VFormRender install-render.js",
"lint": "vue-cli-service lint"
},
"dependencies": {
"axios": "^0.21.1",
"clipboard": "^2.0.8",
"core-js": "^3.6.5",
"element-ui": "^2.15.1",
"file-saver": "^2.0.5",
"vue": "^2.6.11",
"vue-i18n": "^8.24.5",
"vue2-editor": "^2.10.2",
"vuedraggable": "^2.24.3"
},
"devDependencies": {
"@vue/cli-plugin-babel": "~4.5.0",
"@vue/cli-plugin-eslint": "~4.5.0",
"@vue/cli-service": "~4.5.0",
"ace-builds": "^1.4.12",
"babel-eslint": "^10.1.0",
"babel-polyfill": "^6.26.0",
"eslint": "^6.7.2",
"eslint-plugin-vue": "^6.2.2",
"mvdir": "^1.0.21",
"node-sass": "^4.12.0",
"sass-loader": "^8.0.2",
"svg-sprite-loader": "^5.2.1",
"vue-template-compiler": "^2.6.11"
},
"eslintConfig": {
"root": true,
"env": {
"node": true
},
"extends": [
"plugin:vue/essential",
"eslint:recommended"
],
"parserOptions": {
"parser": "babel-eslint"
},
"rules": {}
},
"browserslist": [
"> 1%",
"last 2 versions",
"not dead"
]
}

BIN
public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

28
public/index.html Normal file
View File

@ -0,0 +1,28 @@
<!DOCTYPE html>
<html lang="">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="renderer" content="webkit">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<!-- 禁止浏览器缓存index.html begin -->
<meta http-equiv="Expires" content="0">
<meta http-equiv="Pragma" content="no-cache">
<meta http-equiv="Cache-control" content="no-cache">
<meta http-equiv="Cache" content="no-cache">
<!-- 禁止浏览器缓存index.html end -->
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title><%= htmlWebpackPlugin.options.title %></title>
<link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css">
</head>
<body>
<noscript>
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<script src="https://unpkg.com/element-ui/lib/index.js"></script>
</body>
</html>

22
src/App.vue Normal file
View File

@ -0,0 +1,22 @@
<template>
<div id="app">
<VFormDesigner />
</div>
</template>
<script>
import VFormDesigner from './components/form-designer/index.vue'
export default {
name: 'App',
components: {
VFormDesigner,
}
}
</script>
<style lang="scss">
#app {
height: 100%;
}
</style>

BIN
src/assets/vform-logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 453 B

View File

@ -0,0 +1,130 @@
<template>
<div class="ace-container">
<!-- 官方文档中使用id这里禁止使用在后期打包后容易出现问题使用 ref 或者 DOM 就行 -->
<div class="ace-editor" ref="ace"></div>
</div>
</template>
<script>
import ace from 'ace-builds'
/* webpackjs便
特别提示禁用此行后需要调用ace.config.set('basePath', 'path...')指定动态js加载URL
*/
//import 'ace-builds/webpack-resolver'
//import 'ace-builds/src-min-noconflict/theme-monokai' //
import 'ace-builds/src-min-noconflict/theme-sqlserver' //
import 'ace-builds/src-min-noconflict/mode-javascript' //
import 'ace-builds/src-min-noconflict/mode-json' //
import 'ace-builds/src-min-noconflict/mode-css' //
import 'ace-builds/src-min-noconflict/ext-language_tools'
import {ACE_BASE_PATH} from "@/utils/config";
export default {
name: 'CodeEditor',
props: {
value: {
type: String,
required: true
},
readonly: {
type: Boolean,
default: false
},
mode: {
type: String,
default: 'javascript'
},
userWorker: { //
type: Boolean,
default: true
},
},
mounted() {
//ace.config.set('basePath', 'https://ks3-cn-beijing.ksyun.com/vform2021/ace')
ace.config.set('basePath', ACE_BASE_PATH)
this.addAutoCompletion(ace) //
this.aceEditor = ace.edit(this.$refs.ace, {
maxLines: 20, //
minLines: 5, //
fontSize: 12, //
theme: this.themePath, //
mode: this.modePath, //
tabSize: 2, // 2
readOnly: this.readonly,
highlightActiveLine: true,
value: this.codeValue
})
this.aceEditor.setOptions({
enableBasicAutocompletion: true,
enableSnippets: true, //
enableLiveAutocompletion: true, //
})
if (this.mode === 'json') {
this.setJsonMode()
} else if (this.mode === 'css') {
this.setCssMode()
}
if (!this.userWorker) {
this.aceEditor.getSession().setUseWorker(false)
}
//
this.aceEditor.getSession().on('change',(ev)=>{
//this.$emit('update:value', this.aceEditor.getValue()) // , .sync
this.$emit('input', this.aceEditor.getValue())
})
},
data() {
return {
aceEditor: null,
themePath: 'ace/theme/sqlserver', // webpack-resolver
modePath: 'ace/mode/javascript', //
codeValue: this.value
}
},
watch: {
//
},
methods: {
addAutoCompletion(ace) {
let acData = [
{meta: 'VForm API', caption: 'getWidgetRef', value: 'getWidgetRef()', score: 1},
{meta: 'VForm API', caption: 'getFormRef', value: 'getFormRef()', score: 1},
//TODO:
]
let langTools = ace.require('ace/ext/language_tools')
langTools.addCompleter({
getCompletions: function(editor, session, pos, prefix, callback) {
if (prefix.length === 0) {
return callback(null, []);
}else {
return callback(null, acData);
}
}
})
},
setJsonMode() {
this.aceEditor?.getSession().setMode('ace/mode/json')
},
setCssMode() {
this.aceEditor?.getSession().setMode('ace/mode/css')
},
}
}
</script>
<style lang="scss" scoped>
.ace-editor {
min-height: 300px;
}
</style>

View File

@ -0,0 +1,779 @@
/**
* author: vformAdmin
* email: vdpadmin@163.com
* website: https://www.yuque.com/variantdev/vform
* date: 2021.08.18
* remark: 如果要分发VForm源码需在本文件顶部保留此文件头信息
*/
import {deepClone, generateId, overwriteObj} from "@/utils/util"
import {advancedFields, basicFields, containers} from "@/components/form-designer/widget-panel/widgetsConfig.js";
import {VARIANT_FORM_VERSION} from "@/utils/config";
export function createDesigner(vueInstance) {
let defaultFormConfig = {
modelName: 'formData',
refName: 'vForm',
rulesName: 'rules',
labelWidth: 80,
labelPosition: 'left',
size: '',
labelAlign: 'label-left-align',
cssCode: '',
customClass: '',
functions: '',
layoutType: 'PC',
onFormCreated: '',
onFormMounted: '',
onFormDataChange: '',
}
return {
widgetList: [],
formConfig: {cssCode: ''},
selectedId: null,
selectedWidget: null,
selectedWidgetName: null, //选中组件名称(唯一)
vueInstance: vueInstance,
formWidget: null, //表单设计容器
historyData: {
index: -1, //index: 0,
maxStep: 20,
steps: [],
},
initDesigner() {
this.widgetList = []
this.formConfig = deepClone(defaultFormConfig)
//输出版本信息和语雀链接
console.info(`%cVariantForm %cVer${VARIANT_FORM_VERSION} %chttps://www.yuque.com/variantdev/vform`,
"color:#409EFF;font-size: 22px;font-weight:bolder",
"color:#999;font-size: 12px",
"color:#333"
)
this.initHistoryData()
},
clearDesigner() {
let emptyWidgetListFlag = (this.widgetList.length === 0)
this.widgetList = []
this.selectedId = null
this.selectedWidgetName = null
this.selectedWidget = {} //this.selectedWidget = null
overwriteObj(this.formConfig, defaultFormConfig) //
if (!emptyWidgetListFlag) {
this.emitHistoryChange()
} else {
this.saveCurrentHistoryStep()
}
},
getLayoutType() {
return this.formConfig.layoutType || 'PC'
},
changeLayoutType(newType) {
this.formConfig.layoutType = newType
},
getImportTemplate() {
return {
widgetList: [],
formConfig: deepClone(this.formConfig)
}
},
loadFormJson(formJson) {
let modifiedFlag = false
if (!!formJson && !!formJson.widgetList) {
this.widgetList = formJson.widgetList
modifiedFlag = true
}
if (!!formJson && !!formJson.formConfig) {
//this.formConfig = importObj.formConfig
overwriteObj(this.formConfig, formJson.formConfig) /* 用=赋值会导致inject依赖注入的formConfig属性变成非响应式 */
modifiedFlag = true
}
return modifiedFlag
},
setSelected(selected) {
if (!selected) {
this.clearSelected()
return
}
this.selectedWidget = selected
if (!!selected.id) {
this.selectedId = selected.id
this.selectedWidgetName = selected.options.name
}
},
updateSelectedWidgetNameAndRef(selectedWidget, newName) {
this.selectedWidgetName = newName
selectedWidget.options.name = newName
},
clearSelected() {
this.selectedId = null
this.selectedWidgetName = null
this.selectedWidget = {} //this.selectedWidget = null
},
checkWidgetMove(evt) { /* Only field widget can be dragged into sub-form */
if (!!evt.draggedContext && !!evt.draggedContext.element) {
let wgCategory = evt.draggedContext.element.category
if (!!evt.to) {
if ((evt.to.className === 'sub-form-table') && (wgCategory === 'container')) {
//this.$message.info(this.vueInstance.i18nt('designer.hint.onlyFieldWidgetAcceptable'))
return false
}
}
}
return true
},
insertTableRow(widget, insertPos, cloneRowIdx) {
let rowIdx = (insertPos === undefined) ? widget.rows.length : insertPos //确定插入列位置
let newRow = (cloneRowIdx === undefined) ? deepClone(widget.rows[widget.rows.length - 1]) : deepClone( widget.rows[cloneRowIdx] )
newRow.id = 'table-row-' + generateId()
newRow.merged = false
newRow.cols.forEach(col => {
col.id = 'table-cell-' + generateId()
col.options.name = col.id
col.merged = false
col.options.colspan = 1
col.options.rowspan = 1
})
widget.rows.splice(rowIdx, 0, newRow)
let colNo = 0
while ((rowIdx < widget.rows.length - 1) && (colNo < widget.rows[0].cols.length)) { //越界判断
let rowMerged = widget.rows[rowIdx + 1].cols[colNo].merged //确定插入位置的单元格是否为合并单元格
if (!!rowMerged) {
let rowArray = widget.rows
let unMergedCell = {}
let startRowIndex = null
for (let i = rowIdx; i >= 0; i--) { //查找该行已合并的主单元格
if (!rowArray[i].cols[colNo].merged && (rowArray[i].cols[colNo].options.rowspan > 1)) {
startRowIndex = i
unMergedCell = rowArray[i].cols[colNo]
break
}
}
let newRowspan = unMergedCell.options.rowspan + 1
this.setPropsOfMergedRows(widget.rows, startRowIndex, colNo, unMergedCell.options.colspan, newRowspan)
colNo += unMergedCell.options.colspan
} else {
colNo += 1
}
}
this.emitHistoryChange()
},
insertTableCol(widget, insertPos) {
let colIdx = (insertPos === undefined) ? widget.rows[0].cols.length : insertPos //确定插入列位置
widget.rows.forEach(row => {
let newCol = deepClone(this.getContainerByType('table-cell'))
newCol.id = 'table-cell-' + generateId()
newCol.options.name = newCol.id
newCol.merged = false
newCol.options.colspan = 1
newCol.options.rowspan = 1
row.cols.splice(colIdx, 0, newCol)
})
let rowNo = 0
while((colIdx < widget.rows[0].cols.length - 1) && (rowNo < widget.rows.length)) { //越界判断
let colMerged = widget.rows[rowNo].cols[colIdx + 1].merged //确定插入位置的单元格是否为合并单元格
if (!!colMerged) {
let colArray = widget.rows[rowNo].cols
let unMergedCell = {}
let startColIndex = null
for (let i = colIdx; i >= 0; i--) { //查找该行已合并的主单元格
if (!colArray[i].merged && (colArray[i].options.colspan > 1)) {
startColIndex = i
unMergedCell = colArray[i]
break
}
}
let newColspan = unMergedCell.options.colspan + 1
this.setPropsOfMergedCols(widget.rows, rowNo, startColIndex, newColspan, unMergedCell.options.rowspan)
rowNo += unMergedCell.options.rowspan
} else {
rowNo += 1
}
}
this.emitHistoryChange()
},
setPropsOfMergedCols(rowArray, startRowIndex, startColIndex, newColspan, rowspan) {
for (let i = startRowIndex; i < startRowIndex + rowspan; i++) {
for (let j = startColIndex; j < startColIndex + newColspan; j++) {
if ((i === startRowIndex) && (j === startColIndex)) {
rowArray[i].cols[j].options.colspan = newColspan //合并后的主单元格
continue
}
rowArray[i].cols[j].merged = true
rowArray[i].cols[j].options.colspan = newColspan
rowArray[i].cols[j].widgetList = []
}
}
},
setPropsOfMergedRows(rowArray, startRowIndex, startColIndex, colspan, newRowspan) {
for (let i = startRowIndex; i < startRowIndex + newRowspan; i++) {
for (let j = startColIndex; j < startColIndex + colspan; j++) {
if ((i === startRowIndex) && (j === startColIndex)) {
rowArray[i].cols[j].options.rowspan = newRowspan
continue
}
rowArray[i].cols[j].merged = true
rowArray[i].cols[j].options.rowspan = newRowspan
rowArray[i].cols[j].widgetList = []
}
}
},
setPropsOfSplitCol(rowArray, startRowIndex, startColIndex, colspan, rowspan) {
for (let i = startRowIndex; i < startRowIndex + rowspan; i++) {
for (let j = startColIndex; j < startColIndex + colspan; j++) {
if ((i === startRowIndex) && (j === startColIndex)) {
rowArray[i].cols[j].options.colspan = 1
continue
}
rowArray[i].cols[j].merged = false;
rowArray[i].cols[j].options.colspan = 1
}
}
},
setPropsOfSplitRow(rowArray, startRowIndex, startColIndex, colspan, rowspan) {
for (let i = startRowIndex; i < startRowIndex + rowspan; i++) {
for (let j = startColIndex; j < startColIndex + colspan; j++) {
if ((i === startRowIndex) && (j === startColIndex)) {
rowArray[i].cols[j].options.rowspan = 1 //合并后的主单元格
continue
}
rowArray[i].cols[j].merged = false;
rowArray[i].cols[j].options.rowspan = 1
}
}
},
mergeTableCol(rowArray, colArray, curRow, curCol, leftFlag, cellWidget) {
let mergedColIdx = !!leftFlag ? curCol : curCol + colArray[curCol].options.colspan
let remainedColIdx = !!leftFlag ? curCol - colArray[curCol - 1].options.colspan : curCol
if (!!colArray[mergedColIdx].widgetList && (colArray[mergedColIdx].widgetList.length > 0)) { //保留widgetList
if (!colArray[remainedColIdx].widgetList || (colArray[remainedColIdx].widgetList.length === 0)) {
colArray[remainedColIdx].widgetList = deepClone(colArray[mergedColIdx].widgetList)
}
}
let newColspan = colArray[mergedColIdx].options.colspan * 1 + colArray[remainedColIdx].options.colspan * 1
this.setPropsOfMergedCols(rowArray, curRow, remainedColIdx, newColspan, cellWidget.options.rowspan)
this.emitHistoryChange()
},
mergeTableWholeRow(rowArray, colArray, rowIndex, colIndex) { //需要考虑操作的行存在已合并的单元格!!
//整行所有单元格行高不一致不可合并!!
let startRowspan = rowArray[rowIndex].cols[0].options.rowspan
let unmatchedFlag = false
for (let i = 1; i < rowArray[rowIndex].cols.length; i++) {
if (rowArray[rowIndex].cols[i].options.rowspan !== startRowspan) {
unmatchedFlag = true
break;
}
}
if (unmatchedFlag) {
this.vueInstance.$message.info(this.vueInstance.i18nt('designer.hint.rowspanNotConsistentForMergeEntireRow'))
return
}
let widgetListCols = colArray.filter((colItem) => {
return !colItem.merged && !!colItem.widgetList && (colItem.widgetList.length > 0)
})
if (!!widgetListCols && (widgetListCols.length > 0)) { //保留widgetList
if ((widgetListCols[0].id !== colArray[0].id) && (!colArray[0].widgetList ||
colArray[0].widgetList.length <= 0)) {
colArray[0].widgetList = deepClone( widgetListCols[0].widgetList )
}
}
this.setPropsOfMergedCols(rowArray, rowIndex, 0, colArray.length, colArray[colIndex].options.rowspan)
this.emitHistoryChange()
},
mergeTableRow(rowArray, curRow, curCol, aboveFlag, cellWidget) {
let mergedRowIdx = !!aboveFlag ? curRow : curRow + cellWidget.options.rowspan
let remainedRowIdx = !!aboveFlag ? curRow - cellWidget.options.rowspan : curRow
if (!!rowArray[mergedRowIdx].cols[curCol].widgetList && (rowArray[mergedRowIdx].cols[curCol].widgetList.length > 0)) { //保留widgetList
if (!rowArray[remainedRowIdx].cols[curCol].widgetList || (rowArray[remainedRowIdx].cols[curCol].widgetList.length === 0)) {
rowArray[remainedRowIdx].cols[curCol].widgetList = deepClone(rowArray[mergedRowIdx].cols[curCol].widgetList)
}
}
let newRowspan = rowArray[mergedRowIdx].cols[curCol].options.rowspan * 1 + rowArray[remainedRowIdx].cols[curCol].options.rowspan * 1
this.setPropsOfMergedRows(rowArray, remainedRowIdx, curCol, cellWidget.options.colspan, newRowspan)
this.emitHistoryChange()
},
mergeTableWholeCol(rowArray, colArray, rowIndex, colIndex) { //需要考虑操作的列存在已合并的单元格!!
//整列所有单元格列宽不一致不可合并!!
let startColspan = rowArray[0].cols[colIndex].options.colspan
let unmatchedFlag = false
for (let i = 1; i < rowArray.length; i++) {
if (rowArray[i].cols[colIndex].options.colspan !== startColspan) {
unmatchedFlag = true
break;
}
}
if (unmatchedFlag) {
this.vueInstance.$message.info(this.vueInstance.i18nt('designer.hint.colspanNotConsistentForMergeEntireColumn'))
return
}
let widgetListCols = []
rowArray.forEach(rowItem => {
let tempCell = rowItem.cols[colIndex]
if (!tempCell.merged && !!tempCell.widgetList && (tempCell.widgetList.length > 0)) {
widgetListCols.push(tempCell)
}
})
let firstCellOfCol = rowArray[0].cols[colIndex]
if (!!widgetListCols && (widgetListCols.length > 0)) { //保留widgetList
if ((widgetListCols[0].id !== firstCellOfCol.id) && (!firstCellOfCol.widgetList ||
firstCellOfCol.widgetList.length <= 0)) {
firstCellOfCol.widgetList = deepClone( widgetListCols[0].widgetList )
}
}
this.setPropsOfMergedRows(rowArray, 0, colIndex, firstCellOfCol.options.colspan, rowArray.length)
this.emitHistoryChange()
},
undoMergeTableCol(rowArray, rowIndex, colIndex, colspan, rowspan) {
this.setPropsOfSplitCol(rowArray, rowIndex, colIndex, colspan, rowspan)
this.emitHistoryChange()
},
undoMergeTableRow(rowArray, rowIndex, colIndex, colspan, rowspan) {
this.setPropsOfSplitRow(rowArray, rowIndex, colIndex, colspan, rowspan)
this.emitHistoryChange()
},
deleteTableWholeCol(rowArray, colIndex) { //需考虑删除的是合并列!!
//仅剩一列则不可删除!!
if (rowArray[0].cols[0].options.colspan === rowArray[0].cols.length) {
return
}
//整列所有单元格列宽不一致不可删除!!
let startColspan = rowArray[0].cols[colIndex].options.colspan
let unmatchedFlag = false
for (let i = 1; i < rowArray.length; i++) {
if (rowArray[i].cols[colIndex].options.colspan !== startColspan) {
unmatchedFlag = true
break;
}
}
if (unmatchedFlag) {
this.vueInstance.$message.info(this.vueInstance.i18nt('designer.hint.colspanNotConsistentForDeleteEntireColumn'))
return
}
rowArray.forEach((rItem) => {
rItem.cols.splice(colIndex, startColspan)
})
this.emitHistoryChange()
},
deleteTableWholeRow(rowArray, rowIndex) { //需考虑删除的是合并行!!
//仅剩一行则不可删除!!
if (rowArray[0].cols[0].options.rowspan === rowArray.length) {
return
}
//整行所有单元格行高不一致不可删除!!
let startRowspan = rowArray[rowIndex].cols[0].options.rowspan
let unmatchedFlag = false
for (let i = 1; i < rowArray[rowIndex].cols.length; i++) {
if (rowArray[rowIndex].cols[i].options.rowspan !== startRowspan) {
unmatchedFlag = true
break;
}
}
if (unmatchedFlag) {
this.vueInstance.$message.info(this.vueInstance.i18nt('designer.hint.rowspanNotConsistentForDeleteEntireRow'))
return
}
rowArray.splice(rowIndex, startRowspan)
this.emitHistoryChange()
},
getContainerByType(typeName) {
let foundCon = null
containers.forEach(con => {
if (!!con.type && (con.type === typeName)) {
foundCon = con
}
})
return foundCon
},
getFieldWidgetByType(typeName) {
let foundWidget = null
basicFields.forEach(bf => {
if (!!bf.type && (bf.type === typeName)) {
foundWidget = bf
}
})
if (!!foundWidget) {
return foundWidget
}
advancedFields.forEach(af => {
if (!!af.type && (af.type === typeName)) {
foundWidget = af
}
})
return foundWidget
},
hasConfig(widget, configName) {
let originalWidget = null
if (!!widget.category) {
originalWidget = this.getContainerByType(widget.type)
} else {
originalWidget = this.getFieldWidgetByType(widget.type)
}
if (!originalWidget || !originalWidget.options) {
return false
}
return Object.keys(originalWidget.options).indexOf(configName) > -1
},
cloneGridCol(widget, parentWidget) {
let newGridCol = deepClone(this.getContainerByType('grid-col'))
newGridCol.options.span = widget.options.span
let tmpId = generateId()
newGridCol.id = 'grid-col-' + tmpId
newGridCol.options.name = 'gridCol' + tmpId
parentWidget.cols.push(newGridCol)
},
cloneContainer(containWidget) {
if (containWidget.type === 'grid') {
let newGrid = deepClone(this.getContainerByType('grid'))
newGrid.id = newGrid.type + generateId()
newGrid.options.name = newGrid.id
containWidget.cols.forEach(gridCol => {
let newGridCol = deepClone(this.getContainerByType('grid-col'))
let tmpId = generateId()
newGridCol.id = 'grid-col-' + tmpId
newGridCol.options.name = 'gridCol' + tmpId
newGridCol.options.span = gridCol.options.span
newGrid.cols.push(newGridCol)
})
return newGrid
} else if (containWidget.type === 'table') {
let newTable = deepClone(this.getContainerByType('table'))
newTable.id = newTable.type + generateId()
newTable.options.name = newTable.id
containWidget.rows.forEach(tRow => {
let newRow = deepClone(tRow)
newRow.id = 'table-row-' + generateId()
newRow.cols.forEach(col => {
col.id = 'table-cell-' + generateId()
col.options.name = col.id
col.widgetList = [] //清空组件列表
})
newTable.rows.push(newRow)
})
return newTable
} else {
return null
}
},
moveUpWidget(parentList, indexOfParentList) {
if (!!parentList) {
if (indexOfParentList === 0) {
this.vueInstance.$message(this.vueInstance.i18nt('designer.hint.moveUpFirstChildHint'))
return
}
let tempWidget = parentList[indexOfParentList]
parentList.splice(indexOfParentList, 1)
parentList.splice(indexOfParentList - 1, 0, tempWidget)
}
},
moveDownWidget(parentList, indexOfParentList) {
if (!!parentList) {
if (indexOfParentList === parentList.length - 1) {
this.vueInstance.$message(this.vueInstance.i18nt('designer.hint.moveDownLastChildHint'))
return
}
let tempWidget = parentList[indexOfParentList]
parentList.splice(indexOfParentList, 1)
parentList.splice(indexOfParentList + 1, 0, tempWidget)
}
},
copyNewFieldWidget(origin) {
let newWidget = deepClone(origin)
let tempId = generateId()
newWidget.id = newWidget.type.replace(/-/g, '') + tempId
newWidget.options.name = newWidget.id
newWidget.options.label = newWidget.type.toLowerCase()
delete newWidget.displayName
return newWidget
},
copyNewContainerWidget(origin) {
let newCon = deepClone(origin)
newCon.id = newCon.type.replace(/-/g, '') + generateId()
newCon.options.name = newCon.id
if (newCon.type === 'grid') {
let newCol = deepClone( this.getContainerByType('grid-col') )
let tmpId = generateId()
newCol.id = 'grid-col-' + tmpId
newCol.options.name = 'gridCol' + tmpId
newCon.cols.push(newCol)
//
newCol = deepClone(newCol)
tmpId = generateId()
newCol.id = 'grid-col-' + tmpId
newCol.options.name = 'gridCol' + tmpId
newCon.cols.push(newCol)
} else if (newCon.type === 'table') {
let newRow = {cols: []}
newRow.id = 'table-row-' + generateId()
newRow.merged = false
let newCell = deepClone( this.getContainerByType('table-cell') )
newCell.id = 'table-cell-' + generateId()
newCell.options.name = newCell.id
newCell.merged = false
newCell.options.colspan = 1
newCell.options.rowspan = 1
newRow.cols.push(newCell)
newCon.rows.push(newRow)
} else if (newCon.type === 'tab') {
let newTabPane = deepClone( this.getContainerByType('tab-pane') )
newTabPane.id = 'tab-pane-' + generateId()
newTabPane.options.name = 'tab1'
newTabPane.options.label = 'tab 1'
newCon.tabs.push(newTabPane)
}
//newCon.options.customClass = []
delete newCon.displayName
return newCon
},
addContainerByDbClick(container) {
let newCon = this.copyNewContainerWidget(container)
this.widgetList.push(newCon)
this.setSelected(newCon)
},
addFieldByDbClick(widget) {
let newWidget = this.copyNewFieldWidget(widget)
if (!!this.selectedWidget && this.selectedWidget.type === 'tab') {
//获取当前激活的tabPane
//TODO:
} else if (!!this.selectedWidget && !!this.selectedWidget.widgetList) {
this.selectedWidget.widgetList.push(newWidget)
} else {
this.widgetList.push(newWidget)
}
this.setSelected(newWidget)
},
deleteColOfGrid(gridWidget, colIdx) {
if (!!gridWidget && !!gridWidget.cols) {
gridWidget.cols.splice(colIdx, 1)
}
},
addNewColOfGrid(gridWidget) {
const cols = gridWidget.cols
let newGridCol = deepClone(this.getContainerByType('grid-col'))
let tmpId = generateId()
newGridCol.id = 'grid-col-' + tmpId
newGridCol.options.name = 'gridCol' + tmpId
if ((!!cols) && (cols.length > 0)) {
let spanSum = 0
cols.forEach((col) => {
spanSum += col.options.span
})
if (spanSum >= 24) {
//this.$message.info('列栅格之和超出24')
console.log('列栅格之和超出24')
gridWidget.cols.push(newGridCol)
} else {
newGridCol.options.span = (24 - spanSum) > 12 ? 12 : (24 - spanSum)
gridWidget.cols.push(newGridCol)
}
} else {
gridWidget.cols = [newGridCol]
}
},
addTabPaneOfTabs(tabsWidget) {
const tabPanes = tabsWidget.tabs
let newTabPane = deepClone( this.getContainerByType('tab-pane') )
newTabPane.id = 'tab-pane-' + generateId()
newTabPane.options.name = newTabPane.id
newTabPane.options.label = 'tab ' + (tabPanes.length + 1)
tabPanes.push(newTabPane)
},
deleteTabPaneOfTabs(tabsWidget, tpIdx) {
tabsWidget.tabs.splice(tpIdx, 1)
},
emitEvent(evtName, evtData) { //用于兄弟组件发射事件
this.vueInstance.$emit(evtName, evtData)
},
handleEvent(evtName, callback) { //用于兄弟组件接收事件
this.vueInstance.$on(evtName, (data) => callback(data))
},
registerFormWidget(formWidget) {
this.formWidget = formWidget
},
initHistoryData() {
this.loadFormContentFromStorage()
this.historyData.index++
this.historyData.steps[this.historyData.index] = ({
widgetList: deepClone(this.widgetList),
formConfig: deepClone(this.formConfig)
})
},
emitHistoryChange() {
//console.log('------------', 'Form history changed!')
if (this.historyData.index === this.historyData.maxStep - 1) {
this.historyData.steps.shift()
} else {
this.historyData.index++
}
this.historyData.steps[this.historyData.index] = ({
widgetList: deepClone(this.widgetList),
formConfig: deepClone(this.formConfig)
})
this.saveFormContentToStorage()
if (this.historyData.index < this.historyData.steps.length - 1) {
this.historyData.steps = this.historyData.steps.slice(0, this.historyData.index + 1)
}
console.log('history', this.historyData.index)
},
saveCurrentHistoryStep() {
this.historyData.steps[this.historyData.index] = deepClone({
widgetList: this.widgetList,
formConfig: this.formConfig
})
this.saveFormContentToStorage()
},
undoHistoryStep() {
if (this.historyData.index !== 0) {
this.historyData.index--
}
console.log('undo', this.historyData.index)
this.widgetList = deepClone(this.historyData.steps[this.historyData.index].widgetList)
this.formConfig = deepClone(this.historyData.steps[this.historyData.index].formConfig)
},
redoHistoryStep() {
if (this.historyData.index !== (this.historyData.steps.length - 1)) {
this.historyData.index++
}
console.log('redo', this.historyData.index)
this.widgetList = deepClone(this.historyData.steps[this.historyData.index].widgetList)
this.formConfig = deepClone(this.historyData.steps[this.historyData.index].formConfig)
},
undoEnabled() {
return (this.historyData.index > 0) && (this.historyData.steps.length > 0)
},
redoEnabled() {
return this.historyData.index < (this.historyData.steps.length - 1)
},
saveFormContentToStorage() {
window.localStorage.setItem('widget__list__backup', JSON.stringify(this.widgetList))
window.localStorage.setItem('form__config__backup', JSON.stringify(this.formConfig))
},
loadFormContentFromStorage() {
let widgetListBackup = window.localStorage.getItem('widget__list__backup')
if (!!widgetListBackup) {
this.widgetList = JSON.parse(widgetListBackup)
}
let formConfigBackup = window.localStorage.getItem('form__config__backup')
if (!!formConfigBackup) {
//this.formConfig = JSON.parse(formConfigBackup)
overwriteObj(this.formConfig, JSON.parse(formConfigBackup)) /* 用=赋值会导致inject依赖注入的formConfig属性变成非响应式 */
}
},
}
}

View File

@ -0,0 +1,408 @@
<template>
<div class="container-wrapper">
<el-row v-if="widget.type === 'grid'" :key="widget.id" :gutter="widget.options.gutter" class="grid-container"
:class="[selected ? 'selected' : '', customClass]"
@click.native.stop="selectWidget(widget)">
<template v-for="(colWidget, colIdx) in widget.cols">
<grid-col-widget :widget="colWidget" :designer="designer" :key="colWidget.id" :parent-list="widget.cols"
:index-of-parent-list="colIdx" :parent-widget="widget"></grid-col-widget>
</template>
</el-row>
<div v-else-if="widget.type === 'table'" :key="widget.id" class="table-container"
:class="{'selected': selected}" @click.stop="selectWidget(widget)">
<table class="table-layout">
<tbody>
<tr v-for="(row, rowIdx) in widget.rows" :key="row.id">
<template v-for="(colWidget, colIdx) in row.cols">
<table-cell-widget v-if="!colWidget.merged" :widget="colWidget" :designer="designer"
:key="colWidget.id" :parent-list="widget.cols"
:row-index="rowIdx" :row-length="widget.rows.length"
:col-index="colIdx" :col-length="row.cols.length"
:col-array="row.cols" :row-array="widget.rows"
:parent-widget="widget"></table-cell-widget>
</template>
</tr>
</tbody>
</table>
</div>
<div v-else-if="widget.type === 'tab'" :key="widget.id" class="tab-container"
:class="{'selected': selected}" @click.stop="selectWidget(widget)">
<el-tabs :type="widget.displayType" v-model="activeTab" @tab-click="onTabClick">
<el-tab-pane v-for="(tab, index) in widget.tabs" :key="index" :label="tab.options.label" :name="tab.options.name"
@click.native.stop="selectWidget(widget)">
<draggable :list="tab.widgetList" v-bind="{group:'dragGroup', ghostClass: 'ghost',animation: 200}"
handle=".drag-handler"
@add="(evt) => onContainerDragAdd(evt, tab.widgetList)"
@update="onContainerDragUpdate" :move="checkContainerMove">
<transition-group name="fade" tag="div" class="form-widget-list">
<template v-for="(subWidget, swIdx) in tab.widgetList">
<template v-if="'container' === subWidget.category">
<container-widget :widget="subWidget" :designer="designer" :key="subWidget.id" :parent-list="tab.widgetList"
:index-of-parent-list="swIdx" :parent-widget="widget"></container-widget>
</template>
<template v-else>
<field-widget :field="subWidget" :designer="designer" :key="subWidget.id" :parent-list="tab.widgetList"
:index-of-parent-list="swIdx" :parent-widget="widget" :design-state="true"></field-widget>
</template>
</template>
</transition-group>
</draggable>
</el-tab-pane>
</el-tabs>
</div>
<div v-else-if="widget.type === 'section'" :key="widget.id" class="section-container"
:class="{'selected': selected}" @click.stop="selectWidget(widget)">
<draggable :list="widget.widgetList" v-bind="{group:'dragGroup', ghostClass: 'ghost', animation: 200}"
handle=".drag-handler"
@add="(evt) => onContainerDragAdd(evt, widget.widgetList)"
@update="onContainerDragUpdate" :move="checkContainerMove">
<transition-group name="fade" tag="div" class="form-widget-list">
<template v-for="(subWidget, swIdx) in widget.widgetList">
<template v-if="'container' === subWidget.category">
<container-widget :widget="subWidget" :designer="designer" :key="subWidget.id" :parent-list="widget.widgetList"
:index-of-parent-list="swIdx" :parent-widget="widget"></container-widget>
</template>
<template v-else>
<field-widget :field="subWidget" :designer="designer" :key="subWidget.id" :parent-list="widget.widgetList"
:index-of-parent-list="swIdx" :parent-widget="widget" :design-state="true"></field-widget>
</template>
</template>
</transition-group>
</draggable>
</div>
<div class="container-action" v-if="designer.selectedId === widget.id && !widget.internal">
<i class="el-icon-back" :title="i18nt('designer.hint.selectParentWidget')"
@click.stop="selectParentWidget(widget)"></i>
<i class="el-icon-top" v-if="!!parentList && (parentList.length > 1)" :title="i18nt('designer.hint.moveUpWidget')"
@click.stop="moveUpWidget()"></i>
<i class="el-icon-bottom" v-if="!!parentList && (parentList.length > 1)" :title="i18nt('designer.hint.moveDownWidget')"
@click.stop="moveDownWidget()"></i>
<i v-if="widget.type === 'table'" class="iconfont icon-insertrow" :title="i18nt('designer.hint.insertRow')"
@click.stop="insertTableRow(widget)"></i>
<i v-if="widget.type === 'table'" class="iconfont icon-insertcolumn" :title="i18nt('designer.hint.insertColumn')"
@click.stop="insertTableCol(widget)"></i>
<i class="el-icon-copy-document" v-if="(widget.type === 'grid') || (widget.type === 'table')"
:title="i18nt('designer.hint.cloneWidget')" @click.stop="cloneContainer(widget)"></i>
<i class="el-icon-delete" :title="i18nt('designer.hint.remove')" @click.stop="removeWidget"></i>
</div>
<div class="drag-handler" v-if="designer.selectedId === widget.id && !widget.internal">
<i class="el-icon-rank" :title="i18nt('designer.hint.dragHandler')"></i>
<i>{{i18nt('designer.widgetLabel.' + widget.type)}}</i>
</div>
</div>
</template>
<script>
import Draggable from 'vuedraggable'
import FieldWidget from "@/components/form-designer/form-widget/field-widget";
import GridColWidget from "@/components/form-designer/form-widget/grid-col-widget";
import TableCellWidget from "@/components/form-designer/form-widget/table-cell-widget";
import VTabPane from "@/components/form-designer/form-widget/tab-pane";
import i18n from "@/utils/i18n";
export default {
name: "container-widget",
componentName: 'ContainerWidget',
mixins: [i18n],
components: {
Draggable,
FieldWidget,
GridColWidget,
TableCellWidget,
VTabPane,
},
props: {
widget: Object,
parentWidget: Object,
parentList: Array,
indexOfParentList: Number,
designer: Object,
//
},
data() {
return {
activeTab: 'tab1',
//
}
},
computed: {
selected() {
return this.widget.id === this.designer.selectedId
},
customClass() {
return this.widget.options.customClass || ''
},
},
watch: {
//
},
mounted() {
//
},
methods: {
insertTableRow(widget) {
this.designer.insertTableRow(widget)
this.designer.emitHistoryChange()
},
insertTableCol(widget) {
this.designer.insertTableCol(widget)
this.designer.emitHistoryChange()
},
onContainerDragAdd(evt, subList) {
const newIndex = evt.newIndex
if (!!subList[newIndex]) {
this.designer.setSelected( subList[newIndex] )
}
this.designer.emitHistoryChange()
},
onContainerDragUpdate() {
this.designer.emitHistoryChange()
},
checkContainerMove(evt) {
return this.designer.checkWidgetMove(evt)
},
selectWidget(widget) {
this.designer.setSelected(widget)
},
selectParentWidget() {
if (this.parentWidget) {
this.designer.setSelected(this.parentWidget)
} else {
this.designer.clearSelected()
}
},
moveUpWidget() {
this.designer.moveUpWidget(this.parentList, this.indexOfParentList)
this.designer.emitHistoryChange()
},
moveDownWidget() {
this.designer.moveDownWidget(this.parentList, this.indexOfParentList)
this.designer.emitHistoryChange()
},
cloneContainer(widget) {
if (!!this.parentList) {
let newCon = this.designer.cloneContainer(widget)
this.parentList.splice(this.indexOfParentList + 1, 0, newCon)
this.designer.setSelected(newCon)
this.designer.emitHistoryChange()
}
},
removeWidget() {
if (!!this.parentList) {
let nextSelected = null
if (this.parentList.length === 1) {
if (!!this.parentWidget) {
nextSelected = this.parentWidget
}
} else if (this.parentList.length === (1 + this.indexOfParentList)) {
nextSelected = this.parentList[this.indexOfParentList - 1]
} else {
nextSelected = this.parentList[this.indexOfParentList + 1]
}
this.$nextTick(() => {
this.parentList.splice(this.indexOfParentList, 1)
//if (!!nextSelected) {
this.designer.setSelected(nextSelected)
//}
this.designer.emitHistoryChange()
})
}
},
onSubFormDragAdd(evt, subList) {
const newIndex = evt.newIndex
if (!!subList[newIndex]) {
this.designer.setSelected( subList[newIndex] )
}
this.designer.emitHistoryChange()
console.log('test', 'onSubFormDragAdd')
this.designer.emitEvent('field-selected', this.widget)
},
onSubFormDragEnd(evt) {
console.log('sub form drag end: ', evt)
},
onTabClick(evt) {
console.log('onTabClick', evt)
let paneName = evt.name
this.widget.tabs.forEach((tp) => {
tp.options.active = tp.options.name === paneName;
})
},
}
}
</script>
<style lang="scss" scoped>
.container-wrapper {
position: relative;
margin-bottom: 5px;
.container-action{
position: absolute;
//bottom: -30px;
bottom: 0;
right: -2px;
height: 28px;
line-height: 28px;
background: $--color-primary;
z-index: 999;
i {
font-size: 14px;
color: #fff;
margin: 0 5px;
cursor: pointer;
}
}
.drag-handler {
position: absolute;
top: -2px;
//bottom: -24px; /* */
left: -6px;
height: 22px;
line-height: 22px;
background: $--color-primary;
z-index: 9;
i {
font-size: 14px;
font-style: normal;
color: #fff;
margin: 4px;
cursor: move;
}
}
}
.el-row.grid-container {
min-height: 50px;
//line-height: 48px;
//padding: 6px;
outline: 1px dashed #336699;
.form-widget-list {
min-height: 28px;
}
}
div.table-container {
padding: 5px;
border: 1px dashed #336699;
box-sizing: border-box;
table.table-layout {
width: 100%;
text-align: center;
//border: 1px solid #c8ebfb;
border-collapse: collapse;
table-layout: fixed;
::v-deep td {
height: 48px;
border: 1px dashed #336699;
padding: 3px;
display: table-cell;
}
.form-widget-list {
border: 1px dashed #336699;
min-height: 36px;
}
}
}
.tab-container {
//padding: 5px;
margin: 2px;
.form-widget-list {
min-height: 28px;
}
}
.grid-container.selected, .table-container.selected, .tab-container.selected,
.sub-form-container.selected, .section-container.selected, .grid-cell.selected {
outline: 2px solid $--color-primary !important;
}
//.el-tabs.selected {
// outline: 2px solid $&#45;&#45;color-primary;
//}
.section-container {
border: 1px dashed #336699;
border-radius: 5px;
margin: 6px 0;
padding: 6px;
::v-deep .form-widget-list {
min-height: 28px;
}
}
.sub-form-container {
//width: 100%;
padding: 8px;
border: 1px dashed #336699;
::v-deep .sub-form-table {
min-height: 68px;
div.sub-form-table-column {
display: inline-block;
//width: 200px;
}
}
::v-deep .ghost{
content: '';
font-size: 0;
//height: 3px;
height: 74px;
width: 1px;
box-sizing: border-box;
display: inline-block;
background: $--color-primary;
border: 2px solid $--color-primary;
outline-width: 0;
padding: 0;
margin: 0;
overflow: hidden;
}
}
</style>

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,202 @@
<template>
<el-col v-else-if="widget.type === 'grid-col'" class="grid-cell" :span="widget.options.span || 12"
:class="[selected ? 'selected' : '', customClass]"
:key="widget.id" @click.native.stop="selectWidget(widget)">
<draggable :list="widget.widgetList" v-bind="{group:'dragGroup', ghostClass: 'ghost',animation: 200}"
handle=".drag-handler" @end="(evt) => onGridDragEnd(evt, widget.widgetList)"
@add="(evt) => onGridDragAdd(evt, widget.widgetList)"
@update="onGridDragUpdate" :move="checkContainerMove">
<transition-group name="fade" tag="div" class="form-widget-list">
<template v-for="(subWidget, swIdx) in widget.widgetList">
<template v-if="'container' === subWidget.category">
<container-widget :widget="subWidget" :designer="designer" :key="subWidget.id" :parent-list="widget.widgetList"
:index-of-parent-list="swIdx" :parent-widget="widget"></container-widget>
</template>
<template v-else>
<field-widget :field="subWidget" :designer="designer" :key="subWidget.id" :parent-list="widget.widgetList"
:index-of-parent-list="swIdx" :parent-widget="widget" :design-state="true"></field-widget>
</template>
</template>
</transition-group>
</draggable>
<div class="grid-col-action" v-if="designer.selectedId === widget.id && widget.type === 'grid-col'">
<i class="el-icon-back" :title="i18nt('designer.hint.selectParentWidget')"
@click.stop="selectParentWidget(widget)"></i>
<i class="el-icon-top" v-if="!!parentList && (parentList.length > 1)" :title="i18nt('designer.hint.moveUpWidget')"
@click.stop="moveUpWidget()"></i>
<i class="el-icon-bottom" v-if="!!parentList && (parentList.length > 1)" :title="i18nt('designer.hint.moveDownWidget')"
@click.stop="moveDownWidget()"></i>
<i class="el-icon-copy-document" :title="i18nt('designer.hint.cloneWidget')" @click.stop="cloneGridCol(widget)"></i>
<i class="el-icon-delete" :title="i18nt('designer.hint.remove')" @click.stop="removeWidget"></i>
</div>
<div class="grid-col-handler" v-if="designer.selectedId === widget.id && widget.type === 'grid-col'">
<i>{{i18nt('designer.widgetLabel.' + widget.type)}}</i>
</div>
</el-col>
</template>
<script>
import Draggable from 'vuedraggable'
//import ContainerWidget from "@/components/form-designer/form-widget/container-widget";
import FieldWidget from "@/components/form-designer/form-widget/field-widget";
import i18n from "@/utils/i18n";
export default {
name: "GridColWidget",
componentName: "GridColWidget",
mixins: [i18n],
components: {
Draggable,
//'container-widget': ContainerWidget, /* 使 */
FieldWidget,
},
props: {
widget: Object,
parentWidget: Object,
parentList: Array,
indexOfParentList: Number,
designer: Object,
},
computed: {
selected() {
return this.widget.id === this.designer.selectedId
},
customClass() {
return this.widget.options.customClass || ''
},
},
watch: {
//
},
methods: {
onGridDragEnd(evt, subList) {
//
},
onGridDragAdd(evt, subList) {
const newIndex = evt.newIndex
if (!!subList[newIndex]) {
this.designer.setSelected( subList[newIndex] )
}
this.designer.emitHistoryChange()
},
onGridDragUpdate() {
this.designer.emitHistoryChange()
},
selectWidget(widget) {
console.log('id: ' + widget.id)
this.designer.setSelected(widget)
},
checkContainerMove(evt) {
return this.designer.checkWidgetMove(evt)
},
selectParentWidget() {
if (this.parentWidget) {
this.designer.setSelected(this.parentWidget)
} else {
this.designer.clearSelected()
}
},
moveUpWidget() {
this.designer.moveUpWidget(this.parentList, this.indexOfParentList)
},
moveDownWidget() {
this.designer.moveDownWidget(this.parentList, this.indexOfParentList)
},
cloneGridCol(widget) {
this.designer.cloneGridCol(widget, this.parentWidget)
},
removeWidget() {
if (!!this.parentList) {
let nextSelected = null
if (this.parentList.length === 1) {
if (!!this.parentWidget) {
nextSelected = this.parentWidget
}
} else if (this.parentList.length === (1 + this.indexOfParentList)) {
nextSelected = this.parentList[this.indexOfParentList - 1]
} else {
nextSelected = this.parentList[this.indexOfParentList + 1]
}
this.$nextTick(() => {
this.parentList.splice(this.indexOfParentList, 1)
//if (!!nextSelected) {
this.designer.setSelected(nextSelected)
//}
this.designer.emitHistoryChange()
})
}
},
}
}
</script>
<style lang="scss" scoped>
.grid-cell {
min-height: 38px;
//line-height: 36px;
margin: 6px 0;
padding: 3px;
//min-height: 300px;
//border-right: 1px dotted #cccccc;
outline: 1px dashed #336699;
position: relative;
.form-widget-list {
min-height: 28px;
}
.grid-col-action{
position: absolute;
//bottom: -30px;
bottom: 0;
right: -2px;
height: 28px;
line-height: 28px;
background: $--color-primary;
z-index: 999;
i {
font-size: 14px;
color: #fff;
margin: 0 5px;
cursor: pointer;
}
}
.grid-col-handler {
position: absolute;
top: -2px;
//bottom: -24px; /* */
left: -2px;
height: 22px;
line-height: 22px;
background: $--color-primary;
z-index: 9;
i {
font-size: 14px;
font-style: normal;
color: #fff;
margin: 4px;
cursor: default; //cursor: move;
}
}
}
</style>

View File

@ -0,0 +1,242 @@
<template>
<div class="form-widget-container">
<el-form class="full-height-width widget-form" :label-position="labelPosition"
:class="[customClass, layoutType === 'H5' ? 'h5-layout' : '']" :size="size" :validate-on-rule-change="false">
<div v-if="designer.widgetList.length === 0" class="no-widget-hint">{{i18nt('designer.noWidgetHint')}}</div>
<draggable :list="designer.widgetList" v-bind="{group:'dragGroup', ghostClass: 'ghost',animation: 300}"
handle=".drag-handler" @end="onDragEnd" @add="onDragAdd" @update="onDragUpdate" :move="checkMove">
<transition-group name="fade" tag="div" class="form-widget-list">
<template v-for="(widget, index) in designer.widgetList">
<template v-if="'container' === widget.category">
<container-widget :widget="widget" :designer="designer" :key="widget.id" :parent-list="designer.widgetList"
:index-of-parent-list="index" :parent-widget="null"></container-widget>
</template>
<template v-else>
<field-widget :field="widget" :designer="designer" :key="widget.id" :parent-list="designer.widgetList"
:index-of-parent-list="index" :parent-widget="null" :design-state="true"></field-widget>
</template>
</template>
</transition-group>
</draggable>
</el-form>
</div>
</template>
<script>
import Draggable from 'vuedraggable'
import ContainerWidget from "@/components/form-designer/form-widget/container-widget";
import FieldWidget from "@/components/form-designer/form-widget/field-widget";
import i18n from "@/utils/i18n";
export default {
name: "VFormWidget",
componentName: "VFormWidget",
mixins: [i18n],
components: {
Draggable,
ContainerWidget,
FieldWidget,
},
props: {
designer: Object,
formConfig: Object,
optionData: { //prop
type: Object,
default: () => {}
},
},
provide() {
return {
refList: this.widgetRefList,
formConfig: this.formConfig,
globalOptionData: this.optionData,
globalModel: {
formModel: this.formModel,
}
}
},
data() {
return {
formModel: {},
widgetRefList: {},
}
},
computed: {
labelPosition() {
if (!!this.designer.formConfig && !!this.designer.formConfig.labelPosition) {
return this.designer.formConfig.labelPosition
}
return 'left'
},
size() {
if (!!this.designer.formConfig && !!this.designer.formConfig.size) {
return this.designer.formConfig.size
}
return 'medium'
},
customClass() {
return this.designer.formConfig.customClass || ''
},
layoutType() {
return this.designer.getLayoutType()
},
},
watch: {
'designer.widgetList': {
deep: true,
handler(val) {
//
}
},
'designer.formConfig': {
deep: true,
handler(val) {
//
}
},
},
created() {
this.designer.initDesigner();
},
mounted() {
this.disableFirefoxDefaultDrop() /* 禁用Firefox默认拖拽搜索功能!! */
this.designer.registerFormWidget(this)
},
methods: {
disableFirefoxDefaultDrop() {
let isFirefox = (navigator.userAgent.toLowerCase().indexOf("firefox") !== -1)
if (isFirefox) {
document.body.ondrop = function (event) {
event.stopPropagation();
event.preventDefault();
}
}
},
onDragEnd(evt) {
//console.log('drag end000', evt)
},
onDragAdd(evt) {
const newIndex = evt.newIndex
if (!!this.designer.widgetList[newIndex]) {
this.designer.setSelected( this.designer.widgetList[newIndex] )
}
this.designer.emitHistoryChange()
},
onDragUpdate() { /* 在VueDraggable内拖拽组件发生位置变化时会触发update未发生组件位置变化不会触发 */
this.designer.emitHistoryChange()
},
checkMove(evt) {
return this.designer.checkWidgetMove(evt)
},
getFormData() {
return this.formModel
},
getWidgetRef(widgetName, showError) {
let foundRef = this.widgetRefList[widgetName]
if (!foundRef && !!showError) {
this.$message.error(this.i18nt('designer.hint.refNotFound') + widgetName)
}
return foundRef
},
}
}
</script>
<style lang="scss" scoped>
.container-scroll-bar {
::v-deep .el-scrollbar__wrap, ::v-deep .el-scrollbar__view {
overflow-x: hidden;
}
}
.form-widget-container {
padding: 10px;
background: #f1f2f3;
overflow-x: hidden;
overflow-y: auto;
.el-form.full-height-width {
/*
margin: 0 auto;
width: 420px;
border-radius: 15px;
//border-width: 10px;
box-shadow: 0 0 1px 10px #495060;
*/
height: 100%;
padding: 3px;
background: #ffffff;
.no-widget-hint {
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
text-align: center;
font-size: 18px;
color: #999999;
}
.form-widget-list {
min-height: calc(100vh - 56px - 68px);
padding: 3px;
}
}
.el-form.h5-layout {
margin: 0 auto;
width: 420px;
border-radius: 15px;
//border-width: 10px;
box-shadow: 0 0 1px 10px #495060;
}
.el-form.widget-form ::v-deep .el-row {
padding: 2px;
border: 1px dashed rgba(170, 170, 170, 0.75);
}
}
.grid-cell {
min-height: 30px;
border-right: 1px dotted #cccccc;
}
.fade-enter-active, .fade-leave-active {
transition: opacity .5s;
}
.fade-enter, .fade-leave-to {
opacity: 0;
}
</style>

View File

@ -0,0 +1,90 @@
<template>
<el-tab-pane :name="'tab1'" :label="widget.label" @click.native.stop="selectWidget(widget)">
<draggable :list="widget.widgetList" v-bind="{group:'dragGroup', ghostClass: 'ghost',animation: 200}"
handle=".drag-handler" @end="(evt) => onTabDragEnd(evt, widget.widgetList)"
@add="(evt) => onTabDragAdd(evt, widget.widgetList)"
@update="onTabDragUpdate" :move="checkContainerMove">
<transition-group name="fade" tag="div" class="form-widget-list">
<template v-for="(subWidget, swIdx) in widget.widgetList">
<template v-if="'container' === subWidget.category">
<container-widget :widget="subWidget" :designer="designer" :key="subWidget.id" :parent-list="widget.widgetList"
:index-of-parent-list="swIdx" :parent-widget="widget"></container-widget>
</template>
<template v-else>
<field-widget :field="subWidget" :designer="designer" :key="subWidget.id" :parent-list="widget.widgetList"
:index-of-parent-list="swIdx" :parent-widget="widget" :design-state="true"></field-widget>
</template>
</template>
</transition-group>
</draggable>
</el-tab-pane>
</template>
<script>
/* 此组件未用上!! */
import Draggable from 'vuedraggable'
import ContainerWidget from "@/components/form-designer/form-widget/container-widget";
import FieldWidget from "@/components/form-designer/form-widget/field-widget";
import i18n from "@/utils/i18n";
export default {
name: "VTabPane",
mixins: [i18n],
components: {
Draggable,
ContainerWidget,
FieldWidget,
},
props: {
widget: Object,
parentWidget: Object,
parentList: Array,
indexOfParentList: Number,
designer: Object,
},
computed: {
selected() {
return this.widget.id === this.designer.selectedId
},
},
methods: {
selectWidget(widget) {
this.designer.setSelected(widget)
},
onTabDragEnd(obj, subList) {
//
},
onTabDragAdd(evt, subList) { //
const newIndex = evt.newIndex
console.log(newIndex)
if (!!subList[newIndex]) {
this.designer.setSelected( subList[newIndex] )
}
this.designer.emitHistoryChange()
},
onTabDragUpdate() {
this.designer.emitHistoryChange()
},
checkContainerMove(evt) {
return this.designer.checkWidgetMove(evt)
},
}
}
</script>
<style lang="scss" scoped>
.el-tab-pane {
::v-deep .form-widget-list {
min-height: 28px;
}
}
</style>

View File

@ -0,0 +1,339 @@
<template>
<td class="table-cell" :class="[selected ? 'selected' : '', customClass]"
:style="{width: widget.options.cellWidth + '!important' || '', height: widget.options.cellHeight + '!important' || ''}"
:colspan="widget.options.colspan || 1" :rowspan="widget.options.rowspan || 1"
@click.stop="selectWidget(widget)">
<draggable :list="widget.widgetList" class="draggable-div" v-bind="{group:'dragGroup', ghostClass: 'ghost',animation: 200}"
handle=".drag-handler" @end="(evt) => onTableDragEnd(evt, widget.widgetList)"
@add="(evt) => onTableDragAdd(evt, widget.widgetList)"
@update="onTableDragUpdate" :move="checkContainerMove">
<transition-group name="fade" tag="div" class="form-widget-list">
<template v-for="(subWidget, swIdx) in widget.widgetList">
<template v-if="'container' === subWidget.category">
<container-widget :widget="subWidget" :designer="designer" :key="subWidget.id" :parent-list="widget.widgetList"
:index-of-parent-list="swIdx" :parent-widget="widget"></container-widget>
</template>
<template v-else>
<field-widget :field="subWidget" :designer="designer" :key="subWidget.id" :parent-list="widget.widgetList"
:index-of-parent-list="swIdx" :parent-widget="widget" :design-state="true"></field-widget>
</template>
</template>
</transition-group>
</draggable>
<div class="table-cell-action" v-if="designer.selectedId === widget.id && widget.type === 'table-cell'">
<i class="el-icon-back" :title="i18nt('designer.hint.selectParentWidget')"
@click.stop="selectParentWidget()"></i>
<el-dropdown trigger="click" @command="handleTableCellCommand" size="small">
<i class="el-icon-menu" :title="i18nt('designer.hint.cellSetting')"></i>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item command="insertLeftCol">{{i18nt('designer.setting.insertColumnToLeft')}}</el-dropdown-item>
<el-dropdown-item command="insertRightCol">{{i18nt('designer.setting.insertColumnToRight')}}</el-dropdown-item>
<el-dropdown-item command="insertAboveRow">{{i18nt('designer.setting.insertRowAbove')}}</el-dropdown-item>
<el-dropdown-item command="insertBelowRow">{{i18nt('designer.setting.insertRowBelow')}}</el-dropdown-item>
<el-dropdown-item command="mergeLeftCol" :disabled="mergeLeftColDisabled" divided>{{i18nt('designer.setting.mergeLeftColumn')}}</el-dropdown-item>
<el-dropdown-item command="mergeRightCol" :disabled="mergeRightColDisabled">{{i18nt('designer.setting.mergeRightColumn')}}</el-dropdown-item>
<el-dropdown-item command="mergeWholeRow" :disabled="mergeWholeRowDisabled">{{i18nt('designer.setting.mergeEntireRow')}}</el-dropdown-item>
<el-dropdown-item command="mergeAboveRow" :disabled="mergeAboveRowDisabled" divided>{{i18nt('designer.setting.mergeRowAbove')}}</el-dropdown-item>
<el-dropdown-item command="mergeBelowRow" :disabled="mergeBelowRowDisabled">{{i18nt('designer.setting.mergeRowBelow')}}</el-dropdown-item>
<el-dropdown-item command="mergeWholeCol" :disabled="mergeWholeColDisabled">{{i18nt('designer.setting.mergeEntireColumn')}}</el-dropdown-item>
<el-dropdown-item command="undoMergeRow" :disabled="undoMergeRowDisabled" divided>{{i18nt('designer.setting.undoMergeRow')}}</el-dropdown-item>
<el-dropdown-item command="undoMergeCol" :disabled="undoMergeColDisabled">{{i18nt('designer.setting.undoMergeCol')}}</el-dropdown-item>
<el-dropdown-item command="deleteWholeCol" :disabled="deleteWholeColDisabled" divided>{{i18nt('designer.setting.deleteEntireCol')}}</el-dropdown-item>
<el-dropdown-item command="deleteWholeRow" :disabled="deleteWholeRowDisabled">{{i18nt('designer.setting.deleteEntireRow')}}</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</div>
<div class="table-cell-handler" v-if="designer.selectedId === widget.id && widget.type === 'table-cell'">
<i>{{i18nt('designer.widgetLabel.' + widget.type)}}</i>
</div>
</td>
</template>
<script>
import Draggable from 'vuedraggable'
import FieldWidget from "@/components/form-designer/form-widget/field-widget";
import i18n from "@/utils/i18n";
export default {
name: "TableCellWidget",
componentName: "TableCellWidget",
mixins: [i18n],
components: {
Draggable,
FieldWidget,
},
props: {
widget: Object,
parentWidget: Object,
parentList: Array,
rowIndex: Number,
colIndex: Number,
rowLength: Number,
colLength: Number,
colArray: Array,
rowArray: Array,
designer: Object,
},
computed: {
selected() {
return this.widget.id === this.designer.selectedId
},
customClass() {
return this.widget.options.customClass || ''
},
mergeLeftColDisabled() {
return (this.colIndex <= 0) || (this.colArray[this.colIndex - 1].options.rowspan !== this.widget.options.rowspan)
},
mergeRightColDisabled() {
let rightColIndex = this.colIndex + this.widget.options.colspan
return (this.colIndex >= this.colLength - 1) || (rightColIndex > this.colLength -1)
|| (this.colArray[rightColIndex].options.rowspan !== this.widget.options.rowspan)
},
mergeWholeRowDisabled() {
return (this.colLength <= 1) || (this.colLength === this.widget.options.colspan)
},
mergeAboveRowDisabled() {
return (this.rowIndex <= 0) || (this.rowArray[this.rowIndex - 1].cols[this.colIndex].options.colspan
!== this.widget.options.colspan)
//return this.rowIndex <= 0
//return (this.rowIndex <= 0) || (this.widget.options.colspan !== this.rowArray) //TODO
},
mergeBelowRowDisabled() {
let belowRowIndex = this.rowIndex + this.widget.options.rowspan
return (this.rowIndex >= this.rowLength - 1) || (belowRowIndex > this.rowLength -1)
|| (this.rowArray[belowRowIndex].cols[this.colIndex].options.colspan !== this.widget.options.colspan)
},
mergeWholeColDisabled() {
return (this.rowLength <= 1) || (this.rowLength === this.widget.options.rowspan)
},
undoMergeColDisabled() {
return this.widget.merged || (this.widget.options.colspan <= 1)
},
undoMergeRowDisabled() {
return this.widget.merged || (this.widget.options.rowspan <= 1)
},
deleteWholeColDisabled() {
//return this.colLength === 1
return (this.rowLength === 1) || (this.widget.options.colspan === this.colLength)
},
deleteWholeRowDisabled() {
return (this.rowLength === 1) || (this.widget.options.rowspan === this.rowLength)
},
},
watch: {
//
},
methods: {
selectWidget(widget) {
this.designer.setSelected(widget)
},
checkContainerMove(evt) {
return this.designer.checkWidgetMove(evt)
},
onTableDragEnd(obj, subList) {
//
},
onTableDragAdd(evt, subList) { //
const newIndex = evt.newIndex
if (!!subList[newIndex]) {
this.designer.setSelected( subList[newIndex] )
}
this.designer.emitHistoryChange()
},
onTableDragUpdate() {
this.designer.emitHistoryChange()
},
selectParentWidget() {
if (this.parentWidget) {
this.designer.setSelected(this.parentWidget)
} else {
this.designer.clearSelected()
}
},
handleTableCellCommand(command) {
if (command === 'insertLeftCol') {
this.insertLeftCol()
} else if (command === 'insertRightCol') {
this.insertRightCol()
} else if (command === 'insertAboveRow') {
this.insertAboveRow()
} else if (command === 'insertBelowRow') {
this.insertBelowRow()
} else if (command === 'mergeLeftCol') {
this.mergeLeftCol()
} else if (command === 'mergeRightCol') {
this.mergeRightCol()
} else if (command === 'mergeWholeCol') {
this.mergeWholeCol()
} else if (command === 'mergeAboveRow') {
this.mergeAboveRow()
} else if (command === 'mergeBelowRow') {
this.mergeBelowRow()
} else if (command === 'mergeWholeRow') {
this.mergeWholeRow()
} else if (command === 'undoMergeCol') {
this.undoMergeCol()
} else if (command === 'undoMergeRow') {
this.undoMergeRow()
} else if (command === 'deleteWholeCol') {
this.deleteWholeCol()
} else if (command === 'deleteWholeRow') {
this.deleteWholeRow()
}
},
insertLeftCol() {
this.designer.insertTableCol(this.parentWidget, this.colIndex)
},
insertRightCol() {
this.designer.insertTableCol(this.parentWidget, this.colIndex + 1)
},
insertAboveRow() {
this.designer.insertTableRow(this.parentWidget, this.rowIndex, this.rowIndex)
},
insertBelowRow() {
this.designer.insertTableRow(this.parentWidget, this.rowIndex + 1, this.rowIndex)
},
mergeLeftCol() {
//this.designer.mergeTableColumn(this.colArray, this.colIndex, true)
this.designer.mergeTableCol(this.rowArray, this.colArray, this.rowIndex, this.colIndex, true, this.widget)
},
mergeRightCol() {
//this.designer.mergeTableColumn(this.colArray, this.colIndex, false)
this.designer.mergeTableCol(this.rowArray, this.colArray, this.rowIndex, this.colIndex, false, this.widget)
},
mergeWholeRow() {
this.designer.mergeTableWholeRow(this.rowArray, this.colArray, this.rowIndex, this.colIndex)
},
mergeAboveRow() {
this.designer.mergeTableRow(this.rowArray, this.rowIndex, this.colIndex, true, this.widget)
},
mergeBelowRow() {
this.designer.mergeTableRow(this.rowArray, this.rowIndex, this.colIndex, false, this.widget)
},
mergeWholeCol() {
this.designer.mergeTableWholeCol(this.rowArray, this.colArray, this.rowIndex, this.colIndex)
},
undoMergeCol() {
this.designer.undoMergeTableCol(this.rowArray, this.rowIndex, this.colIndex,
this.widget.options.colspan, this.widget.options.rowspan)
},
undoMergeRow() {
this.designer.undoMergeTableRow(this.rowArray, this.rowIndex, this.colIndex,
this.widget.options.colspan, this.widget.options.rowspan)
},
deleteWholeCol() {
this.designer.deleteTableWholeCol(this.rowArray, this.colIndex)
},
deleteWholeRow() {
this.designer.deleteTableWholeRow(this.rowArray, this.rowIndex)
},
}
}
</script>
<style lang="scss" scoped>
.table-cell {
//padding: 3px;
border: 1px dashed #336699;
display: table-cell;
position: relative;
.draggable-div {
position: relative;
height: 100%;
}
.form-widget-list {
border: 1px dashed #336699;
margin: 3px;
//min-height: 36px;
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
}
.table-cell-action{
position: absolute;
//bottom: -30px;
bottom: 0;
right: -2px;
height: 28px;
line-height: 28px;
background: $--color-primary;
z-index: 999;
i {
font-size: 14px;
color: #fff;
margin: 0 5px;
cursor: pointer;
}
}
.table-cell-handler {
position: absolute;
top: -2px;
//bottom: -24px; /* */
left: -2px;
height: 22px;
line-height: 22px;
background: $--color-primary;
z-index: 9;
i {
font-size: 14px;
font-style: normal;
color: #fff;
margin: 4px;
cursor: default; //cursor: move;
}
}
}
.table-cell.selected {
outline: 2px solid $--color-primary !important;
}
</style>

View File

@ -0,0 +1,267 @@
<!--
/**
* author: vformAdmin
* email: vdpadmin@163.com
* website: https://www.yuque.com/variantdev/vform
* date: 2021.08.18
* remark: 如果要分发VForm源码需在本文件顶部保留此文件头信息
*/
-->
<template>
<el-container class="full-height">
<el-header class="main-header">
<div class="float-left main-title">
<img src="../../assets/vform-logo.png" @click="openHome">
<span class="bold">VariantForm Pro</span> {{i18nt('application.productTitle')}} <span class="version-span">Ver {{vFormVersion}}</span></div>
<div class="float-right external-link">
<el-dropdown @command="handleLanguageChanged">
<span class="el-dropdown-link">{{curLangName}}<i class="el-icon-arrow-down el-icon--right"></i></span>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item command="zh-CN">{{i18nt('application.zh-CN')}}</el-dropdown-item>
<el-dropdown-item command="en-US">{{i18nt('application.en-US')}}</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
<a href="javascript:void(0)" @click="(ev) => openUrl(ev, gitUrl)" target="_blank"><svg-icon icon-class="github" />{{i18nt('application.github')}}</a>
<a href="javascript:void(0)" @click="(ev) => openUrl(ev, docUrl)" target="_blank"><svg-icon icon-class="document" />{{i18nt('application.document')}}</a>
<a href="javascript:void(0)" @click="(ev) => openUrl(ev, chatUrl)" target="_blank">{{i18nt('application.qqGroup')}}</a>
<a href="javascript:void(0)" @click="(ev) => openUrl(ev, subScribeUrl)" target="_blank">
{{i18nt('application.subscription')}}<i class="el-icon-top-right"></i></a>
</div>
</el-header>
<el-container>
<el-aside class="side-panel">
<widget-panel :designer="designer" />
</el-aside>
<el-container class="center-layout-container">
<el-header class="toolbar-header">
<toolbar-panel :designer="designer"></toolbar-panel>
</el-header>
<el-main class="form-widget-main">
<el-scrollbar class="container-scroll-bar" :style="{height: scrollerHeight}">
<v-form-widget :designer="designer" :form-config="designer.formConfig">
</v-form-widget>
</el-scrollbar>
</el-main>
</el-container>
<el-aside>
<setting-panel :designer="designer" :selected-widget="designer.selectedWidget" :form-config="designer.formConfig" />
</el-aside>
</el-container>
</el-container>
</template>
<script>
import WidgetPanel from './widget-panel/index'
import ToolbarPanel from './toolbar-panel/index'
import SettingPanel from './setting-panel/index'
import VFormWidget from './form-widget/index'
import {createDesigner} from "@/components/form-designer/designer";
import {addWindowResizeHandler, getQueryParam} from "@/utils/util";
import {VARIANT_FORM_VERSION} from "@/utils/config";
import i18n, { changeLocale } from "@/utils/i18n";
export default {
name: "VFormDesigner",
componentName: "VFormDesigner",
mixins: [i18n],
components: {
WidgetPanel,
ToolbarPanel,
SettingPanel,
VFormWidget,
},
data() {
return {
vFormVersion: VARIANT_FORM_VERSION,
curLangName: '',
vsCodeFlag: false,
docUrl: 'http://www.vform666.com/document.html',
gitUrl: 'https://github.com/vform666/variant-form',
chatUrl: 'http://www.vform666.com/chat-group.html',
subScribeUrl: 'http://www.vform666.com/subscribe.html',
scrollerHeight: 0,
designer: createDesigner(this),
}
},
provide() {
return {
//
}
},
created() {
this.vsCodeFlag = getQueryParam('vscode') == 1
},
mounted() {
this.initLocale()
this.scrollerHeight = window.innerHeight - 56 - 36 + 'px'
addWindowResizeHandler(() => {
this.$nextTick(() => {
this.scrollerHeight = window.innerHeight - 56 - 36 + 'px'
})
})
},
methods: {
openHome() {
if (!!this.vsCodeFlag) {
const msgObj = {
cmd: 'openUrl',
data: {
url: 'http://www.vform666.com/'
}
}
window.parent.postMessage(msgObj, '*')
}
},
openUrl(event, url) {
if (!!this.vsCodeFlag) {
const msgObj = {
cmd: 'openUrl',
data: {
url
}
}
window.parent.postMessage(msgObj, '*')
} else {
let aDom = event.currentTarget
aDom.href = url
//window.open(url, '_blank') //
}
},
initLocale() {
let curLocale = localStorage.getItem('v_form_locale')
if (!!this.vsCodeFlag) {
curLocale = curLocale || 'en-US'
} else {
curLocale = curLocale || 'zh-CN'
}
this.curLangName = this.i18nt('application.' + curLocale)
this.changeLanguage(curLocale)
},
handleLanguageChanged(command) {
this.changeLanguage(command)
this.curLangName = this.i18nt('application.' + command)
},
changeLanguage(langName) {
changeLocale(langName)
},
setFormJson(formJson) {
let modifiedFlag = false
if (!!formJson) {
if (typeof formJson === 'string') {
modifiedFlag = this.designer.loadFormJson( JSON.parse(formJson) )
} else if (formJson.constructor === Object) {
modifiedFlag = this.designer.loadFormJson(formJson)
}
if (modifiedFlag) {
this.designer.emitHistoryChange()
}
}
},
//TODO:
}
}
</script>
<style lang="scss" scoped>
.el-container.full-height {
height: 100%;
overflow-y: hidden;
}
.el-container.center-layout-container {
min-width: 680px;
border-left: 2px dotted #EBEEF5;
border-right: 2px dotted #EBEEF5;
}
.el-header.main-header {
border-bottom: 2px dotted #EBEEF5;
height: 48px !important;
line-height: 48px !important;
}
div.main-title {
font-size: 18px;
color: #242424;
display: flex;
align-items: center;
justify-items: center;
img {
cursor: pointer;
}
span.bold {
font-size: 20px;
font-weight: bold;
margin: 0 6px 0 6px;
}
span.version-span {
font-size: 14px;
color: #101F1C;
margin-left: 6px;
}
}
.float-left {
float: left;
}
.float-right {
float: right;
}
.el-dropdown-link {
margin-right: 12px;
cursor: pointer;
}
div.external-link a {
font-size: 13px;
text-decoration: none;
margin-right: 10px;
color: #606266;
}
.el-header.toolbar-header {
border-bottom: 1px dotted #CCCCCC;
height: 42px !important;
line-height: 42px !important;
}
.el-aside.side-panel {
width: 260px !important;
overflow-y: hidden;
}
.el-main.form-widget-main {
padding: 0;
position: relative;
overflow-x: hidden;
}
.container-scroll-bar {
::v-deep .el-scrollbar__wrap, ::v-deep .el-scrollbar__view {
overflow-x: hidden;
}
}
</style>

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,234 @@
<template>
<div class="option-items-pane">
<el-radio-group v-if="(selectedWidget.type === 'radio') || ((selectedWidget.type === 'select') && !selectedWidget.options.multiple)"
v-model="optionModel.defaultValue" @change="emitDefaultValueChange">
<draggable tag="ul" :list="optionModel.optionItems"
v-bind="{group:'optionsGroup', ghostClass: 'ghost', handle: '.drag-option'}">
<li v-for="(option, idx) in optionModel.optionItems" :key="idx">
<el-radio :label="option.value">
<el-input v-model="option.value" size="mini" style="width: 100px"></el-input>
<el-input v-model="option.label" size="mini" style="width: 100px"></el-input>
<i class="iconfont icon-drag drag-option"></i>
<el-button circle plain size="mini" type="danger" @click="deleteOption(option, idx)"
icon="el-icon-minus" class="col-delete-button"></el-button>
</el-radio>
</li>
</draggable>
</el-radio-group>
<el-checkbox-group v-else-if="(selectedWidget.type === 'checkbox') || ((selectedWidget.type === 'select') && selectedWidget.options.multiple)"
v-model="optionModel.defaultValue" @change="emitDefaultValueChange">
<draggable tag="ul" :list="optionModel.optionItems"
v-bind="{group:'optionsGroup', ghostClass: 'ghost', handle: '.drag-option'}">
<li v-for="(option, idx) in optionModel.optionItems" :key="idx">
<el-checkbox :label="option.value">
<el-input v-model="option.value" size="mini" style="width: 100px"></el-input>
<el-input v-model="option.label" size="mini" style="width: 100px"></el-input>
<i class="iconfont icon-drag drag-option"></i>
<el-button circle plain size="mini" type="danger" @click="deleteOption(option, idx)"
icon="el-icon-minus" class="col-delete-button"></el-button>
</el-checkbox>
</li>
</draggable>
</el-checkbox-group>
<el-cascader v-else-if="(selectedWidget.type === 'cascader')"
v-model="optionModel.defaultValue" :options="optionModel.optionItems"
@change="emitDefaultValueChange"
:placeholder="i18nt('render.hint.selectPlaceholder')" style="width: 100%">
</el-cascader>
<div v-if="(selectedWidget.type === 'cascader')">
<el-button type="text" @click="importCascaderOptions">{{i18nt('designer.setting.importOptions')}}</el-button>
<el-button type="text" @click="resetDefault">{{i18nt('designer.setting.resetDefault')}}</el-button>
</div>
<div v-if="(selectedWidget.type === 'radio') || (selectedWidget.type === 'checkbox') || (selectedWidget.type === 'select')">
<el-button type="text" @click="addOption">{{i18nt('designer.setting.addOption')}}</el-button>
<el-button type="text" @click="importOptions">{{i18nt('designer.setting.importOptions')}}</el-button>
<el-button type="text" @click="resetDefault">{{i18nt('designer.setting.resetDefault')}}</el-button>
</div>
<el-dialog :title="i18nt('designer.setting.importOptions')" :visible.sync="showImportDialogFlag"
v-if="showImportDialogFlag" :show-close="true" class="small-padding-dialog"
:close-on-click-modal="false" :close-on-press-escape="false" :destroy-on-close="true">
<el-form-item>
<el-input type="textarea" rows="10" v-model="optionLines"></el-input>
</el-form-item>
<div slot="footer" class="dialog-footer">
<el-button size="large" type="primary" @click="saveOptions">{{i18nt('designer.hint.confirm')}}</el-button>
<el-button size="large" type="" @click="showImportDialogFlag = false">{{i18nt('designer.hint.cancel')}}</el-button>
</div>
</el-dialog>
<el-dialog :title="i18nt('designer.setting.importOptions')" :visible.sync="showImportCascaderDialogFlag"
v-if="showImportCascaderDialogFlag" :show-close="true" class="small-padding-dialog"
:close-on-click-modal="false" :close-on-press-escape="false" :destroy-on-close="true">
<code-editor v-model="cascaderOptions" mode="json" :readonly="false"></code-editor>
<div slot="footer" class="dialog-footer">
<el-button size="large" type="primary" @click="saveCascaderOptions">{{i18nt('designer.hint.confirm')}}</el-button>
<el-button size="large" type="" @click="showImportCascaderDialogFlag = false">{{i18nt('designer.hint.cancel')}}</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import Draggable from 'vuedraggable'
import CodeEditor from '@/components/code-editor/index'
import i18n from "@/utils/i18n";
export default {
name: "OptionItemsSetting",
mixins: [i18n],
components: {
Draggable,
//CodeEditor: () => import('@/components/code-editor/index'),
CodeEditor,
},
props: {
designer: Object,
selectedWidget: Object,
},
data() {
return {
showImportDialogFlag: false,
optionLines: '',
cascaderOptions: '',
showImportCascaderDialogFlag: false,
//separator: '||',
separator: ',',
}
},
computed: {
optionModel() {
return this.selectedWidget.options
},
},
watch: {
'selectedWidget.options': {
deep: true,
handler(val) {
//console.log('888888', 'Options change!')
}
},
},
methods: {
emitDefaultValueChange() {
if (!!this.designer && !!this.designer.formWidget) {
let fieldWidget = this.designer.formWidget.getWidgetRef(this.selectedWidget.options.name)
if (!!fieldWidget && !!fieldWidget.refreshDefaultValue) {
fieldWidget.refreshDefaultValue()
}
}
},
deleteOption(option, index) {
this.optionModel.optionItems.splice(index, 1)
},
addOption() {
let newValue = this.optionModel.optionItems.length + 1
this.optionModel.optionItems.push({
value: newValue,
label: 'new option'
})
},
importOptions() {
this.optionLines = ''
if (this.optionModel.optionItems.length > 0) {
this.optionModel.optionItems.forEach((opt) => {
if (opt.value === opt.label) {
this.optionLines += opt.value + '\n'
} else {
this.optionLines += opt.value + this.separator + opt.label + '\n'
}
})
}
this.showImportDialogFlag = true
},
saveOptions() {
let lineArray = this.optionLines.split('\n')
//console.log('test', lineArray)
if (lineArray.length > 0) {
this.optionModel.optionItems = []
lineArray.forEach((optLine) => {
if (!!optLine && !!optLine.trim()) {
if (optLine.indexOf(this.separator) !== -1) {
this.optionModel.optionItems.push({
value: optLine.split(this.separator)[0],
label: optLine.split(this.separator)[1]
})
} else {
this.optionModel.optionItems.push({
value: optLine,
label: optLine
})
}
}
})
} else {
this.optionModel.optionItems = []
}
this.showImportDialogFlag = false
},
resetDefault() {
if ((this.selectedWidget.type === 'checkbox') ||
((this.selectedWidget.type === 'select') && this.selectedWidget.options.multiple)) {
this.optionModel.defaultValue = []
} else {
this.optionModel.defaultValue = ''
}
this.emitDefaultValueChange()
},
importCascaderOptions() {
this.cascaderOptions = JSON.stringify(this.optionModel.optionItems, null, ' ')
this.showImportCascaderDialogFlag = true
},
saveCascaderOptions() {
try {
let newOptions = JSON.parse(this.cascaderOptions)
this.optionModel.optionItems = newOptions
//TODO:
this.showImportCascaderDialogFlag = false
} catch (ex) {
this.$message.error(this.i18nt('designer.hint.invalidOptionsData') + ex.message)
}
},
}
}
</script>
<style lang="scss" scoped>
.option-items-pane ul {
padding-inline-start: 6px;
padding-left: 6px; /* 重置IE11默认样式 */
}
li.ghost{
background: #fff;
border: 2px dotted $--color-primary;
}
.drag-option {
cursor: move;
}
.small-padding-dialog ::v-deep .el-dialog__body {
padding: 10px 15px;
}
.dialog-footer .el-button {
width: 100px;
}
</style>

View File

@ -0,0 +1,494 @@
<template>
<div class="toolbar-container">
<div class="left-toolbar">
<el-button type="text" :disabled="undoDisabled" :title="i18nt('designer.toolbar.undoHint')" @click="undoHistory">
<svg-icon icon-class="undo" /></el-button>
<el-button type="text" :disabled="redoDisabled" :title="i18nt('designer.toolbar.redoHint')" @click="redoHistory">
<svg-icon icon-class="redo" /></el-button>
<el-button-group style="margin-left: 20px">
<el-button :type="layoutType === 'PC' ? 'primary': ''" @click="changeLayoutType('PC')">
{{i18nt('designer.toolbar.pcLayout')}}</el-button>
<el-button :type="layoutType === 'H5' ? 'primary': ''" @click="changeLayoutType('H5')">
{{i18nt('designer.toolbar.mobileLayout')}}</el-button>
</el-button-group>
</div>
<div class="right-toolbar">
<el-button type="text" @click="clearFormWidget"><i class="el-icon-delete" />{{i18nt('designer.toolbar.clear')}}</el-button>
<el-button type="text" @click="previewForm"><i class="el-icon-view" />{{i18nt('designer.toolbar.preview')}}</el-button>
<el-button type="text" @click="importJson">{{i18nt('designer.toolbar.importJson')}}</el-button>
<el-button type="text" @click="exportJson">{{i18nt('designer.toolbar.exportJson')}}</el-button>
<el-button type="text" @click="exportCode">{{i18nt('designer.toolbar.exportCode')}}</el-button>
<el-button type="text" @click="generateSFC"><svg-icon icon-class="vue-sfc" />{{i18nt('designer.toolbar.generateSFC')}}</el-button>
</div>
<el-dialog :title="i18nt('designer.toolbar.preview')" :visible.sync="showPreviewDialogFlag" v-if="showPreviewDialogFlag"
:show-close="true" :close-on-click-modal="false" :close-on-press-escape="false" center v-dialog-drag
:destroy-on-close="true" class="small-padding-dialog" width="75%" :fullscreen="layoutType === 'H5'">
<div>
<div class="form-render-wrapper" :class="[layoutType === 'H5' ? 'h5-layout' : '']">
<VFormRender ref="preForm" :form-json="formJson" :form-data="testFormData"
@appendButtonClick="testOnAppendButtonClick" @buttonClick="testOnButtonClick"
@formChange="handleFormChange"></VFormRender>
</div>
</div>
<code-editor v-model="testFunc" style="display: none"></code-editor>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="getFormData">{{i18nt('designer.hint.getFormData')}}</el-button>
<el-button type="primary" @click="resetForm">{{i18nt('designer.hint.resetForm')}}</el-button>
<el-button type="primary" @click="setFormDisabled">{{i18nt('designer.hint.disableForm')}}</el-button>
<el-button type="primary" @click="setFormEnabled">{{i18nt('designer.hint.enableForm')}}</el-button>
<el-button type="" @click="showPreviewDialogFlag = false">{{i18nt('designer.hint.closePreview')}}</el-button>
</div>
</el-dialog>
<el-dialog :title="i18nt('designer.toolbar.importJson')" :visible.sync="showImportJsonDialogFlag"
v-if="showImportJsonDialogFlag" :show-close="true" class="small-padding-dialog" center v-dialog-drag
:close-on-click-modal="false" :close-on-press-escape="false" :destroy-on-close="true">
<el-alert type="info" :title="i18nt('designer.hint.importJsonHint')" show-icon></el-alert>
<code-editor :mode="'json'" :readonly="false" v-model="importTemplate"></code-editor>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="doJsonImport">
{{i18nt('designer.hint.import')}}</el-button>
<el-button @click="showImportJsonDialogFlag = false">
{{i18nt('designer.hint.cancel')}}</el-button>
</div>
</el-dialog>
<el-dialog :title="i18nt('designer.toolbar.exportJson')" :visible.sync="showExportJsonDialogFlag"
v-if="showExportJsonDialogFlag" :show-close="true" class="small-padding-dialog" center v-dialog-drag
:close-on-click-modal="false" :close-on-press-escape="false" :destroy-on-close="true">
<code-editor :mode="'json'" :readonly="true" v-model="jsonContent"></code-editor>
<div slot="footer" class="dialog-footer">
<el-button type="primary" class="copy-json-btn" :data-clipboard-text="jsonRawContent" @click="copyFormJson">
{{i18nt('designer.hint.copyJson')}}</el-button>
<el-button @click="saveFormJson">{{i18nt('designer.hint.saveFormJson')}}</el-button>
<el-button type="" @click="showExportJsonDialogFlag = false">
{{i18nt('designer.hint.closePreview')}}</el-button>
</div>
</el-dialog>
<el-dialog :title="i18nt('designer.toolbar.exportCode')" :visible.sync="showExportCodeDialogFlag"
v-if="showExportCodeDialogFlag" :show-close="true" class="small-padding-dialog" center v-dialog-drag
width="65%" :close-on-click-modal="false" :close-on-press-escape="false" :destroy-on-close="true">
<el-tabs type="border-card" class="no-box-shadow no-padding" v-model="activeCodeTab">
<el-tab-pane label="Vue" name="vue">
<code-editor :mode="'html'" :readonly="true" v-model="vueCode" :user-worker="false"></code-editor>
</el-tab-pane>
<el-tab-pane label="HTML" name="html">
<code-editor :mode="'html'" :readonly="true" v-model="htmlCode" :user-worker="false"></code-editor>
</el-tab-pane>
</el-tabs>
<div slot="footer" class="dialog-footer">
<el-button type="primary" class="copy-vue-btn" :data-clipboard-text="vueCode" @click="copyVueCode">
{{i18nt('designer.hint.copyVueCode')}}</el-button>
<el-button type="primary" class="copy-html-btn" :data-clipboard-text="htmlCode" @click="copyHtmlCode">
{{i18nt('designer.hint.copyHtmlCode')}}</el-button>
<el-button @click="saveVueCode">{{i18nt('designer.hint.saveVueCode')}}</el-button>
<el-button @click="saveHtmlCode">{{i18nt('designer.hint.saveHtmlCode')}}</el-button>
<el-button type="" @click="showExportCodeDialogFlag = false">
{{i18nt('designer.hint.closePreview')}}</el-button>
</div>
</el-dialog>
<el-dialog :title="i18nt('designer.hint.exportFormData')" :visible.sync="showFormDataDialogFlag"
v-if="showFormDataDialogFlag" :show-close="true" class="dialog-title-light-bg" center v-dialog-drag
:close-on-click-modal="false" :close-on-press-escape="false" :destroy-on-close="true"
:append-to-body="true">
<div style="border: 1px solid #DCDFE6">
<code-editor :mode="'json'" :readonly="true" v-model="formDataJson"></code-editor>
</div>
<div slot="footer" class="dialog-footer">
<el-button type="primary" class="copy-form-data-json-btn" :data-clipboard-text="formDataRawJson" @click="copyFormDataJson">
{{i18nt('designer.hint.copyFormData')}}</el-button>
<el-button @click="saveFormData">{{i18nt('designer.hint.saveFormData')}}</el-button>
<el-button type="" @click="showFormDataDialogFlag = false">
{{i18nt('designer.hint.closePreview')}}</el-button>
</div>
</el-dialog>
<el-dialog :title="i18nt('designer.toolbar.generateSFC')" :visible.sync="showExportSFCDialogFlag"
v-if="showExportSFCDialogFlag" :show-close="true" class="small-padding-dialog" center v-dialog-drag
width="65%" :close-on-click-modal="false" :close-on-press-escape="false" :destroy-on-close="true">
<el-tabs type="border-card" class="no-box-shadow no-padding" v-model="activeSFCTab">
<el-tab-pane label="Vue2" name="vue2">
<code-editor :mode="'html'" :readonly="true" v-model="sfcCode" :user-worker="false"></code-editor>
</el-tab-pane>
<el-tab-pane label="Vue3" name="vue3">
<code-editor :mode="'html'" :readonly="true" v-model="sfcCodeV3" :user-worker="false"></code-editor>
</el-tab-pane>
</el-tabs>
<div slot="footer" class="dialog-footer">
<el-button type="primary" class="copy-vue2-sfc-btn" :data-clipboard-text="sfcCode" @click="copyV2SFC">
{{i18nt('designer.hint.copyVue2SFC')}}</el-button>
<el-button type="primary" class="copy-vue3-sfc-btn" :data-clipboard-text="sfcCodeV3" @click="copyV3SFC">
{{i18nt('designer.hint.copyVue3SFC')}}</el-button>
<el-button @click="saveV2SFC">{{i18nt('designer.hint.saveVue2SFC')}}</el-button>
<el-button @click="saveV3SFC">{{i18nt('designer.hint.saveVue3SFC')}}</el-button>
<el-button type="" @click="showExportSFCDialogFlag = false">
{{i18nt('designer.hint.closePreview')}}</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import VFormRender from '@/components/form-render/index'
import CodeEditor from '@/components/code-editor/index'
import Clipboard from 'clipboard'
import {deepClone, copyToClipboard, generateId, getQueryParam} from "@/utils/util";
import i18n from '@/utils/i18n'
import {generateCode} from "@/utils/code-generator";
import {genSFC} from "@/utils/sfc-generator";
import loadBeautifier from "@/utils/beautifierLoader";
import { saveAs } from 'file-saver'
export default {
name: "ToolbarPanel",
mixins: [i18n],
components: {
VFormRender,
CodeEditor,
Clipboard,
},
props: {
designer: Object
},
data() {
return {
showPreviewDialogFlag: false,
showImportJsonDialogFlag: false,
showExportJsonDialogFlag: false,
showExportCodeDialogFlag: false,
showFormDataDialogFlag: false,
showExportSFCDialogFlag: false,
testFunc: '',
importTemplate: '',
jsonContent: '',
jsonRawContent: '',
formDataJson: '',
formDataRawJson: '',
vueCode: '',
htmlCode: '',
sfcCode: '',
sfcCodeV3: '',
activeCodeTab: 'vue',
activeSFCTab: 'vue2',
testFormData: {
// 'userName': '666888',
// 'productItems': [
// {'pName': 'iPhone12', 'pNum': 10},
// {'pName': 'P50', 'pNum': 16},
// ]
},
}
},
computed: {
formJson() {
return {
widgetList: this.designer.widgetList,
formConfig: this.designer.formConfig
}
},
undoDisabled() {
return !this.designer.undoEnabled()
},
redoDisabled() {
return !this.designer.redoEnabled()
},
layoutType() {
return this.designer.getLayoutType()
},
},
methods: {
undoHistory() {
this.designer.undoHistoryStep()
},
redoHistory() {
this.designer.redoHistoryStep()
},
changeLayoutType(newType) {
this.designer.changeLayoutType(newType)
},
clearFormWidget() {
this.designer.clearDesigner()
},
previewForm() {
this.showPreviewDialogFlag = true
},
saveAsFile(fileContent, defaultFileName) {
this.$prompt(this.i18nt('designer.hint.fileNameForSave'), this.i18nt('designer.hint.saveFileTitle'), {
inputValue: defaultFileName,
closeOnClickModal: false,
inputPlaceholder: this.i18nt('designer.hint.fileNameInputPlaceholder')
}).then(({ value }) => {
if (!value) {
value = defaultFileName
}
if (getQueryParam('vscode') == 1) {
this.vsSaveFile(value, fileContent)
return
}
const fileBlob = new Blob([fileContent], { type: 'text/plain;charset=utf-8' })
saveAs(fileBlob ,value)
}).catch(() => {
//
})
},
vsSaveFile(fileName, fileContent) {
const msgObj = {
cmd: 'writeFile',
data: {
fileName,
code: fileContent
}
}
window.parent.postMessage(msgObj, '*')
},
importJson() {
this.importTemplate = JSON.stringify(this.designer.getImportTemplate(), null, ' ')
this.showImportJsonDialogFlag = true
},
doJsonImport() {
try {
let importObj = JSON.parse(this.importTemplate)
this.designer.loadFormJson(importObj)
this.showImportJsonDialogFlag = false
this.$message.success(this.i18nt('designer.hint.importJsonSuccess'))
this.designer.emitHistoryChange()
} catch(ex) {
this.$message.error(ex + '')
}
},
exportJson() {
let widgetList = deepClone(this.designer.widgetList)
let formConfig = deepClone(this.designer.formConfig)
this.jsonContent = JSON.stringify({widgetList, formConfig}, null, ' ')
this.jsonRawContent = JSON.stringify({widgetList, formConfig})
this.showExportJsonDialogFlag = true
},
copyFormJson(e) {
copyToClipboard(this.jsonRawContent, e,
this.$message,
this.i18nt('designer.hint.copyJsonSuccess'),
this.i18nt('designer.hint.copyJsonFail')
)
},
saveFormJson() {
this.saveAsFile(this.jsonContent, `vform${generateId()}.json`)
},
exportCode() {
this.vueCode = generateCode(this.formJson)
this.htmlCode = generateCode(this.formJson, 'html')
this.showExportCodeDialogFlag = true
},
copyVueCode(e) {
copyToClipboard(this.vueCode, e,
this.$message,
this.i18nt('designer.hint.copyVueCodeSuccess'),
this.i18nt('designer.hint.copyVueCodeFail')
)
},
copyHtmlCode(e) {
copyToClipboard(this.htmlCode, e,
this.$message,
this.i18nt('designer.hint.copyHtmlCodeSuccess'),
this.i18nt('designer.hint.copyHtmlCodeFail')
)
},
saveVueCode() {
this.saveAsFile(this.vueCode, `vform${generateId()}.vue`)
},
saveHtmlCode() {
this.saveAsFile(this.htmlCode, `vform${generateId()}.html`)
},
generateSFC() {
loadBeautifier(beautifier => {
this.sfcCode = genSFC(this.designer.formConfig, this.designer.widgetList, beautifier)
this.sfcCodeV3 = genSFC(this.designer.formConfig, this.designer.widgetList, beautifier, true)
this.showExportSFCDialogFlag = true
})
},
copyV2SFC(e) {
copyToClipboard(this.sfcCode, e,
this.$message,
this.i18nt('designer.hint.copySFCSuccess'),
this.i18nt('designer.hint.copySFCFail')
)
},
copyV3SFC(e) {
copyToClipboard(this.sfcCodeV3, e,
this.$message,
this.i18nt('designer.hint.copySFCSuccess'),
this.i18nt('designer.hint.copySFCFail')
)
},
saveV2SFC() {
this.saveAsFile(this.sfcCode, `vformV2-${generateId()}.vue`)
},
saveV3SFC() {
this.saveAsFile(this.sfcCodeV3, `vformV3-${generateId()}.vue`)
},
getFormData() {
this.$refs['preForm'].getFormData().then(formData => {
this.formDataJson = JSON.stringify(formData, null, ' ')
this.formDataRawJson = JSON.stringify(formData)
this.showFormDataDialogFlag = true
}).catch(error => {
this.$message.error(error)
})
},
copyFormDataJson(e) {
copyToClipboard(this.formDataRawJson, e,
this.$message,
this.i18nt('designer.hint.copyJsonSuccess'),
this.i18nt('designer.hint.copyJsonFail')
)
},
saveFormData() {
this.saveAsFile(this.htmlCode, `formData${generateId()}.json`)
},
resetForm() {
this.$refs['preForm'].resetForm()
},
setFormDisabled() {
this.$refs['preForm'].disableForm()
},
setFormEnabled() {
this.$refs['preForm'].enableForm()
},
handleFormChange(fieldName, newValue, oldValue, formModel) {
console.log('---formChange start---')
console.log('fieldName', fieldName)
console.log('newValue', newValue)
console.log('oldValue', oldValue)
console.log('formModel', formModel)
console.log('---formChange end---')
},
testOnAppendButtonClick(clickedWidget) {
console.log('test', clickedWidget)
},
testOnButtonClick(button) {
console.log('test', button)
},
}
}
</script>
<style lang="scss" scoped>
div.toolbar-container {
}
.left-toolbar {
float: left;
font-size: 16px;
}
.right-toolbar {
float: right;
::v-deep .el-button--text {
font-size: 14px !important;
}
}
.el-button i {
margin-right: 3px;
}
.small-padding-dialog {
::v-deep .el-dialog__header {
padding-top: 3px;
padding-bottom: 3px;
background: #f1f2f3;
}
::v-deep .el-dialog__body {
padding: 12px 15px 12px 15px;
.el-alert {
padding: 0 10px;
}
}
::v-deep .ace-container {
border: 1px solid #DCDFE6;
}
}
.dialog-title-light-bg {
::v-deep .el-dialog__header {
background: #f1f2f3;
}
}
.no-box-shadow {
box-shadow: none;
}
.no-padding.el-tabs--border-card {
::v-deep .el-tabs__content {
padding: 0;
}
}
.form-render-wrapper {
//height: calc(100vh - 142px);
}
.form-render-wrapper.h5-layout {
margin: 0 auto;
width: 420px;
border-radius: 15px;
//border-width: 10px;
box-shadow: 0 0 1px 10px #495060;
height: calc(100vh - 142px);
}
</style>

View File

@ -0,0 +1,244 @@
<template>
<el-scrollbar class="side-scroll-bar" :style="{height: scrollerHeight}">
<div class="panel-container">
<el-collapse v-model="activeNames" class="widget-collapse">
<el-collapse-item name="1" :title="i18nt('designer.containerTitle')">
<draggable tag="ul" :list="containers" :group="{name: 'dragGroup', pull: 'clone', put: false}"
:clone="handleContainerWidgetClone" ghost-class="ghost" :sort="false"
:move="checkContainerMove" @end="onContainerDragEnd">
<li v-for="(ctn, index) in containers" :key="index" class="container-widget-item" :title="ctn.displayName"
@dblclick="addContainerByDbClick(ctn)">
<span><svg-icon :icon-class="ctn.icon" />{{i18nt(`designer.widgetLabel.${ctn.type}`)}}</span>
</li>
</draggable>
</el-collapse-item>
<el-collapse-item name="2" :title="i18nt('designer.basicFieldTitle')">
<draggable tag="ul" :list="basicFields" :group="{name: 'dragGroup', pull: 'clone', put: false}"
:clone="handleFieldWidgetClone" ghost-class="ghost" :sort="false">
<li v-for="(fld, index) in basicFields" :key="index" class="field-widget-item" :title="fld.displayName"
@dblclick="addFieldByDbClick(fld)">
<span><svg-icon :icon-class="fld.icon" />{{i18nt(`designer.widgetLabel.${fld.type}`)}}</span>
</li>
</draggable>
</el-collapse-item>
<el-collapse-item name="3" :title="i18nt('designer.advancedFieldTitle')">
<draggable tag="ul" :list="advancedFields" :group="{name: 'dragGroup', pull: 'clone', put: false}"
:clone="handleFieldWidgetClone" ghost-class="ghost" :sort="false">
<li v-for="(fld, index) in advancedFields" :key="index" class="field-widget-item" :title="fld.displayName"
@dblclick="addFieldByDbClick(fld)">
<span><svg-icon :icon-class="fld.icon" />{{i18nt(`designer.widgetLabel.${fld.type}`)}}</span>
</li>
</draggable>
</el-collapse-item>
<!--
<el-collapse-item name="4" :title="i18nt('designer.customFieldTitle')">
<draggable tag="ul" :list="customFields" :group="{name: 'dragGroup', pull: 'clone', put: false}"
:clone="handleFieldWidgetClone" ghost-class="ghost" :sort="false">
<li v-for="(fld, index) in customFields" :key="index" class="field-widget-item" :title="fld.displayName"
@dblclick="addFieldByDbClick(fld)">
<span :title="fld.displayName"><svg-icon :icon-class="fld.icon" />{{i18nt(`designer.widgetLabel.${fld.type}`)}}</span>
</li>
</draggable>
</el-collapse-item>
-->
</el-collapse>
</div>
</el-scrollbar>
</template>
<script>
import Draggable from 'vuedraggable'
import {containers, basicFields, advancedFields, customFields} from "./widgetsConfig";
import {addWindowResizeHandler} from "@/utils/util";
import i18n from "@/utils/i18n";
export default {
name: "FieldPanel",
mixins: [i18n],
components: {
Draggable,
},
props: {
designer: Object,
},
data() {
return {
scrollerHeight: 0,
activeNames: ['1', '2', '3', '4'],
containers,
basicFields,
advancedFields,
customFields,
allContainers: [],
}
},
computed: {
//
},
mounted() {
this.loadWidgets()
this.scrollerHeight = window.innerHeight - 56 + 'px'
addWindowResizeHandler(() => {
this.$nextTick(() => {
this.scrollerHeight = window.innerHeight - 56 + 'px'
//console.log(this.scrollerHeight)
})
})
},
methods: {
loadWidgets() {
//this.allContainers = deepClone(this.containers)
this.containers = this.containers.map(con => {
return {
...con,
//category: 'container',
displayName: this.i18nt(`designer.widgetLabel.${con.type}`)
}
}).filter(con => {
return !con.internal
})
this.basicFields = this.basicFields.map(fld => {
return {
...fld,
displayName: this.i18nt(`designer.widgetLabel.${fld.type}`)
}
})
this.advancedFields = this.advancedFields.map(fld => {
return {
...fld,
displayName: this.i18nt(`designer.widgetLabel.${fld.type}`)
}
})
///*
this.customFields = this.customFields.map(fld => {
return {
...fld,
displayName: this.i18nt(`designer.widgetLabel.${fld.type}`)
}
})
//*/
},
handleContainerWidgetClone(origin) {
return this.designer.copyNewContainerWidget(origin)
},
handleFieldWidgetClone(origin) {
return this.designer.copyNewFieldWidget(origin)
},
checkContainerMove(evt) {
return this.designer.checkWidgetMove(evt)
},
onContainerDragEnd(evt) {
//console.log('Drag end of container: ')
//console.log(evt)
},
addContainerByDbClick(container) {
this.designer.addContainerByDbClick(container)
},
addFieldByDbClick(widget) {
//console.log('addWidgetByDbClick')
this.designer.addFieldByDbClick(widget)
},
}
}
</script>
<style lang="scss" scoped>
.side-scroll-bar {
//height: calc(100% - 56px);
//height: 100%;
::v-deep .el-scrollbar__wrap {
overflow-x: hidden;
}
}
div.panel-container {
//height: calc(100% - 48px);
//height: 100%;
//overflow-y: hidden;
padding-bottom: 10px;
}
.el-collapse-item ::v-deep ul > li {
list-style: none;
}
.widget-collapse {
::v-deep .el-collapse-item__header {
margin-left: 8px;
font-style: italic;
font-weight: bold;
}
::v-deep .el-collapse-item__content {
padding-bottom: 6px;
ul {
padding-left: 10px; /* 重置IE11默认样式 */
margin: 0; /* 重置IE11默认样式 */
margin-block-start: 0;
margin-block-end: 0.25em;
padding-inline-start: 10px;
&:after {
content: "";
display: block;
clear: both;
}
.container-widget-item, .field-widget-item {
display: inline-block;
height: 28px;
line-height: 28px;
width: 115px;
float: left;
margin: 2px 6px 6px 0;
cursor: move;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
background: #f1f2f3;
}
.container-widget-item:hover, .field-widget-item:hover {
background: #EBEEF5;
outline: 1px solid $--color-primary;
}
.drag-handler {
position: absolute;
top: 0;
left: 160px;
background-color: #dddddd;
border-radius: 5px;
padding-right: 5px;
font-size: 11px;
color: #666666;
}
}
}
}
</style>

View File

@ -0,0 +1,892 @@
export const containers = [
{
type: 'grid',
category: 'container',
icon: 'grid',
cols: [],
options: {
name: '',
hidden: false,
gutter: 12,
}
},
{
type: 'table',
category: 'container',
icon: 'table',
rows: [],
options: {
name: '',
hidden: false,
}
},
{
type: 'tab',
category: 'container',
icon: 'tab',
displayType: 'border-card',
tabs: [],
options: {
name: '',
hidden: false,
}
},
/*
{
type: 'section',
category: 'container',
icon: 'section',
widgetList: [],
options: {
name: '',
hidden: false,
}
},
*/
{
type: 'grid-col',
category: 'container',
icon: 'grid-col',
internal: true,
widgetList: [],
options: {
name: '',
hidden: false,
span: 12,
}
},
{
type: 'table-cell',
category: 'container',
icon: 'table-cell',
internal: true,
widgetList: [],
merged: false,
options: {
name: '',
cellWidth: '',
cellHeight: '',
colspan: 1,
rowspan: 1,
}
},
{
type: 'tab-pane',
category: 'container',
icon: 'tab-pane',
internal: true,
widgetList: [],
options: {
name: '',
label: '',
hidden: false,
active: false,
disabled: false,
}
},
]
export const basicFields = [
{
type: 'input',
icon: 'text-field',
formItemFlag: true,
options: {
name: '',
label: '',
labelAlign: '',
type: 'text',
defaultValue: '',
placeholder: '',
columnWidth: '200px',
size: '',
labelWidth: null,
labelHidden: false,
readonly: false,
disabled: false,
hidden: false,
clearable: true,
showPassword: false,
required: false,
validation: '',
validationHint: '',
//-------------------
customClass: '', //自定义css类名
labelIconClass: null,
labelIconPosition: 'rear',
labelTooltip: null,
minLength: null,
maxLength: null,
showWordLimit: false,
prefixIcon: '',
suffixIcon: '',
appendButton: false,
appendButtonDisabled: false,
buttonIcon: 'el-icon-search',
//-------------------
onCreated: '',
onMounted: '',
onInput: '',
onChange: '',
onFocus: '',
onBlur: '',
onValidate: '',
},
},
{
type: 'textarea',
icon: 'textarea-field',
formItemFlag: true,
options: {
name: '',
label: '',
labelAlign: '',
rows: 3,
defaultValue: '',
placeholder: '',
columnWidth: '200px',
size: '',
labelWidth: null,
labelHidden: false,
readonly: false,
disabled: false,
hidden: false,
required: false,
validation: '',
validationHint: '',
//-------------------
customClass: '', //自定义css类名
labelIconClass: null,
labelIconPosition: 'rear',
labelTooltip: null,
minLength: null,
maxLength: null,
showWordLimit: false,
//-------------------
onCreated: '',
onMounted: '',
onInput: '',
onChange: '',
onFocus: '',
onBlur: '',
onValidate: '',
},
},
{
type: 'number',
icon: 'number-field',
formItemFlag: true,
options: {
name: '',
label: '',
labelAlign: '',
defaultValue: 0,
placeholder: '',
columnWidth: '200px',
size: '',
labelWidth: null,
labelHidden: false,
disabled: false,
hidden: false,
required: false,
validation: '',
validationHint: '',
//-------------------
customClass: '', //自定义css类名
labelIconClass: null,
labelIconPosition: 'rear',
labelTooltip: null,
min: -100000000000,
max: 100000000000,
precision: 0,
step: 1,
controlsPosition: 'right',
//-------------------
onCreated: '',
onMounted: '',
onChange: '',
onFocus: '',
onBlur: '',
onValidate: '',
},
},
{
type: 'radio',
icon: 'radio-field',
formItemFlag: true,
options: {
name: '',
label: '',
labelAlign: '',
defaultValue: null,
columnWidth: '200px',
size: '',
displayStyle: 'inline',
buttonStyle: false,
border: false,
labelWidth: null,
labelHidden: false,
disabled: false,
hidden: false,
optionItems: [
{label: 'radio 1', value: 1},
{label: 'radio 2', value: 2},
{label: 'radio 3', value: 3},
],
required: false,
validation: '',
validationHint: '',
//-------------------
customClass: '', //自定义css类名
labelIconClass: null,
labelIconPosition: 'rear',
labelTooltip: null,
//-------------------
onCreated: '',
onMounted: '',
onChange: '',
onValidate: '',
},
},
{
type: 'checkbox',
icon: 'checkbox-field',
formItemFlag: true,
options: {
name: '',
label: '',
labelAlign: '',
defaultValue: [],
columnWidth: '200px',
size: '',
displayStyle: 'inline',
buttonStyle: false,
border: false,
labelWidth: null,
labelHidden: false,
disabled: false,
hidden: false,
optionItems: [
{label: 'check 1', value: 1},
{label: 'check 2', value: 2},
{label: 'check 3', value: 3},
],
required: false,
validation: '',
validationHint: '',
//-------------------
customClass: '', //自定义css类名
labelIconClass: null,
labelIconPosition: 'rear',
labelTooltip: null,
//-------------------
onCreated: '',
onMounted: '',
onChange: '',
onValidate: '',
},
},
{
type: 'select',
icon: 'select-field',
formItemFlag: true,
options: {
name: '',
label: '',
labelAlign: '',
defaultValue: '',
placeholder: '',
columnWidth: '200px',
size: '',
labelWidth: null,
labelHidden: false,
disabled: false,
hidden: false,
clearable: true,
filterable: false,
allowCreate: false,
remote: false,
automaticDropdown: false, //自动下拉
multiple: false,
multipleLimit: 0,
optionItems: [
{label: 'select 1', value: 1},
{label: 'select 2', value: 2},
{label: 'select 3', value: 3},
],
required: false,
validation: '',
validationHint: '',
//-------------------
customClass: '', //自定义css类名
labelIconClass: null,
labelIconPosition: 'rear',
labelTooltip: null,
//-------------------
onCreated: '',
onMounted: '',
onRemoteQuery: '',
onChange: '',
onFocus: '',
onBlur: '',
onValidate: '',
},
},
{
type: 'time',
icon: 'time-field',
formItemFlag: true,
options: {
name: '',
label: '',
labelAlign: '',
defaultValue: null,
placeholder: '',
columnWidth: '200px',
size: '',
labelWidth: null,
labelHidden: false,
readonly: false,
disabled: false,
hidden: false,
clearable: true,
editable: false,
format: 'HH:mm:ss', //时间格式
required: false,
validation: '',
validationHint: '',
//-------------------
customClass: '', //自定义css类名
labelIconClass: null,
labelIconPosition: 'rear',
labelTooltip: null,
//-------------------
onCreated: '',
onMounted: '',
onChange: '',
onFocus: '',
onBlur: '',
onValidate: '',
},
},
{
type: 'time-range',
icon: 'time-range-field',
formItemFlag: true,
options: {
name: '',
label: '',
labelAlign: '',
defaultValue: null,
startPlaceholder: '',
endPlaceholder: '',
columnWidth: '200px',
size: '',
labelWidth: null,
labelHidden: false,
readonly: false,
disabled: false,
hidden: false,
clearable: true,
editable: false,
format: 'HH:mm:ss', //时间格式
required: false,
validation: '',
validationHint: '',
//-------------------
customClass: '', //自定义css类名
labelIconClass: null,
labelIconPosition: 'rear',
labelTooltip: null,
//-------------------
onCreated: '',
onMounted: '',
onChange: '',
onFocus: '',
onBlur: '',
onValidate: '',
},
},
{
type: 'date',
icon: 'date-field',
formItemFlag: true,
options: {
name: '',
label: '',
labelAlign: '',
type: 'date',
defaultValue: null,
placeholder: '',
columnWidth: '200px',
size: '',
labelWidth: null,
labelHidden: false,
readonly: false,
disabled: false,
hidden: false,
clearable: true,
editable: false,
format: 'yyyy-MM-dd', //日期显示格式
valueFormat: 'yyyy-MM-dd', //日期对象格式
required: false,
validation: '',
validationHint: '',
//-------------------
customClass: '', //自定义css类名
labelIconClass: null,
labelIconPosition: 'rear',
labelTooltip: null,
//-------------------
onCreated: '',
onMounted: '',
onChange: '',
onFocus: '',
onBlur: '',
onValidate: '',
},
},
{
type: 'date-range',
icon: 'date-range-field',
formItemFlag: true,
options: {
name: '',
label: '',
labelAlign: '',
type: 'daterange',
defaultValue: null,
startPlaceholder: '',
endPlaceholder: '',
columnWidth: '200px',
size: '',
labelWidth: null,
labelHidden: false,
readonly: false,
disabled: false,
hidden: false,
clearable: true,
editable: false,
format: 'yyyy-MM-dd', //日期显示格式
valueFormat: 'yyyy-MM-dd', //日期对象格式
required: false,
validation: '',
validationHint: '',
//-------------------
customClass: '', //自定义css类名
labelIconClass: null,
labelIconPosition: 'rear',
labelTooltip: null,
//-------------------
onCreated: '',
onMounted: '',
onChange: '',
onFocus: '',
onBlur: '',
onValidate: '',
},
},
{
type: 'switch',
icon: 'switch-field',
formItemFlag: true,
options: {
name: '',
label: '',
labelAlign: '',
defaultValue: null,
columnWidth: '200px',
labelWidth: null,
labelHidden: false,
disabled: false,
hidden: false,
//-------------------
customClass: '', //自定义css类名
labelIconClass: null,
labelIconPosition: 'rear',
labelTooltip: null,
switchWidth: 40,
activeText: '',
inactiveText: '',
activeColor: null,
inactiveColor: null,
//-------------------
onCreated: '',
onMounted: '',
onChange: '',
onValidate: '',
},
},
{
type: 'rate',
icon: 'rate-field',
formItemFlag: true,
options: {
name: '',
label: '',
labelAlign: '',
defaultValue: null,
columnWidth: '200px',
labelWidth: null,
labelHidden: false,
disabled: false,
hidden: false,
required: false,
validation: '',
validationHint: '',
//-------------------
customClass: '', //自定义css类名
labelIconClass: null,
labelIconPosition: 'rear',
labelTooltip: null,
max: 5,
lowThreshold: 2,
highThreshold: 4,
allowHalf: false,
showText: false,
showScore: false,
//-------------------
onCreated: '',
onMounted: '',
onChange: '',
onValidate: '',
},
},
{
type: 'color',
icon: 'color-field',
formItemFlag: true,
options: {
name: '',
label: '',
labelAlign: '',
defaultValue: null,
columnWidth: '200px',
size: '',
labelWidth: null,
labelHidden: false,
disabled: false,
hidden: false,
required: false,
validation: '',
validationHint: '',
//-------------------
customClass: '', //自定义css类名
labelIconClass: null,
labelIconPosition: 'rear',
labelTooltip: null,
//-------------------
onCreated: '',
onMounted: '',
onChange: '',
onValidate: '',
},
},
{
type: 'slider',
icon: 'slider-field',
formItemFlag: true,
options: {
name: '',
label: '',
labelAlign: '',
columnWidth: '200px',
size: '',
labelWidth: null,
labelHidden: false,
disabled: false,
hidden: false,
required: false,
validation: '',
validationHint: '',
//-------------------
customClass: '', //自定义css类名
labelIconClass: null,
labelIconPosition: 'rear',
labelTooltip: null,
min: 0,
max: 100,
step: 1,
range: false,
//vertical: false,
height: null,
//-------------------
onCreated: '',
onMounted: '',
onChange: '',
onValidate: '',
},
},
{
type: 'static-text',
icon: 'static-text',
formItemFlag: false,
options: {
name: '',
columnWidth: '200px',
hidden: false,
textContent: 'static text',
//-------------------
customClass: '', //自定义css类名
//-------------------
onCreated: '',
onMounted: '',
},
},
{
type: 'html-text',
icon: 'html-text',
formItemFlag: false,
options: {
name: '',
columnWidth: '200px',
hidden: false,
htmlContent: '<b>html text</b>',
//-------------------
customClass: '', //自定义css类名
//-------------------
onCreated: '',
onMounted: '',
},
},
{
type: 'button',
icon: 'button',
formItemFlag: false,
options: {
name: '',
label: '',
columnWidth: '200px',
size: '',
disabled: false,
hidden: false,
type: '',
plain: false,
round: false,
circle: false,
icon: null,
//-------------------
customClass: '', //自定义css类名
//-------------------
onCreated: '',
onMounted: '',
onClick: '',
},
},
{
type: 'divider',
icon: 'divider',
formItemFlag: false,
options: {
name: '',
label: '',
columnWidth: '200px',
direction: 'horizontal',
contentPosition: 'center',
hidden: false,
//-------------------
customClass: '', //自定义css类名
//-------------------
onCreated: '',
onMounted: '',
},
},
//
]
export const advancedFields = [
{
type: 'picture-upload',
icon: 'picture-upload-field',
formItemFlag: true,
options: {
name: '',
label: '',
labelWidth: null,
labelHidden: false,
disabled: false,
hidden: false,
required: false,
requiredHint: '',
customRule: '',
customRuleHint: '',
//-------------------
uploadURL: '',
uploadTip: '',
withCredentials: false,
multipleSelect: false,
showFileList: true,
limit: 3,
fileMaxSize: 5, //MB
fileTypes: ['jpeg', 'png'],
//headers: [],
//-------------------
customClass: '', //自定义css类名
labelIconClass: null,
labelIconPosition: 'rear',
labelTooltip: null,
//-------------------
onCreated: '',
onMounted: '',
onBeforeUpload: '',
onUploadSuccess: '',
onUploadError: '',
onValidate: '',
//onFileChange: '',
},
},
{
type: 'file-upload',
icon: 'file-upload-field',
formItemFlag: true,
options: {
name: '',
label: '',
labelWidth: null,
labelHidden: false,
disabled: false,
hidden: false,
required: false,
requiredHint: '',
customRule: '',
customRuleHint: '',
//-------------------
uploadURL: '',
uploadTip: '',
withCredentials: false,
multipleSelect: false,
showFileList: true,
limit: 3,
fileMaxSize: 5, //MB
fileTypes: ['doc', 'docx', 'xls', 'xlsx'],
//headers: [],
//-------------------
customClass: '', //自定义css类名
labelIconClass: null,
labelIconPosition: 'rear',
labelTooltip: null,
//-------------------
onCreated: '',
onMounted: '',
onBeforeUpload: '',
onUploadSuccess: '',
onUploadError: '',
onValidate: '',
//onFileChange: '',
},
},
{
type: 'rich-editor',
icon: 'rich-editor-field',
formItemFlag: true,
options: {
name: '',
label: '',
placeholder: '',
labelWidth: null,
labelHidden: false,
disabled: false,
hidden: false,
required: false,
requiredHint: '',
customRule: '',
customRuleHint: '',
//-------------------
customClass: '', //自定义css类名
labelIconClass: null,
labelIconPosition: 'rear',
labelTooltip: null,
minLength: null,
maxLength: null,
showWordLimit: false,
//-------------------
onCreated: '',
onMounted: '',
onValidate: '',
},
},
{
type: 'cascader',
icon: 'cascader-field',
formItemFlag: true,
options: {
name: '',
label: '',
defaultValue: '',
placeholder: '',
size: '',
labelWidth: null,
labelHidden: false,
disabled: false,
hidden: false,
clearable: true,
filterable: false,
optionItems: [
{label: 'select 1', value: 1, children: [{label: 'child 1', value: 11}]},
{label: 'select 2', value: 2},
{label: 'select 3', value: 3},
],
required: false,
requiredHint: '',
customRule: '',
customRuleHint: '',
//-------------------
customClass: '', //自定义css类名
labelIconClass: null,
labelIconPosition: 'rear',
labelTooltip: null,
//-------------------
onCreated: '',
onMounted: '',
onChange: '',
onFocus: '',
onBlur: '',
onValidate: '',
},
},
]
export const customFields = [
{
type: 'custom',
icon: 'custom-component',
},
{
type: 'slot',
icon: 'slot-component',
},
]

View File

@ -0,0 +1,599 @@
<template>
<div class="container-wrapper">
<el-row v-if="widget.type === 'grid'" :key="widget.id" :gutter="widget.options.gutter" class="grid-container"
:class="[customClass]"
:ref="widget.id" v-show="!widget.options.hidden">
<template v-for="(colWidget, colIdx) in widget.cols">
<grid-col-item :widget="colWidget" :key="colIdx" :parent-list="widget.cols"
:index-of-parent-list="colIdx" :parent-widget="widget"></grid-col-item>
</template>
</el-row>
<div v-else-if="widget.type === 'table'" :key="widget.id" class="table-container"
v-show="!widget.options.hidden">
<table :ref="widget.id" class="table-layout" :class="[customClass]">
<tbody>
<tr v-for="(row, rowIdx) in widget.rows" :key="row.id">
<template v-for="(colWidget, colIdx) in row.cols">
<table-cell-item v-if="!colWidget.merged" :widget="colWidget" :key="colIdx" :parent-list="widget.cols"
:row-index="rowIdx" :col-index="colIdx" :parent-widget="widget"></table-cell-item>
</template>
</tr>
</tbody>
</table>
</div>
<div v-else-if="widget.type === 'tab'" :key="widget.id" class="tab-container"
v-show="!widget.options.hidden">
<el-tabs v-model="activeTabName" :type="widget.displayType" :ref="widget.id" :class="[customClass]">
<el-tab-pane v-for="(tab, index) in visibleTabs" :key="index" :label="tab.options.label"
:disabled="tab.options.disabled" :name="tab.options.name">
<template v-for="(subWidget, swIdx) in tab.widgetList">
<template v-if="'container' === subWidget.category">
<container-item :widget="subWidget" :key="swIdx" :parent-list="tab.widgetList"
:index-of-parent-list="swIdx" :parent-widget="widget"></container-item>
</template>
<template v-else>
<field-widget :field="subWidget" :key="swIdx" :parent-list="tab.widgetList"
:index-of-parent-list="swIdx" :parent-widget="widget"></field-widget>
</template>
</template>
</el-tab-pane>
</el-tabs>
</div>
<div v-else-if="widget.type === 'sub-form'" :key="widget.id" class="sub-form-container"
v-show="!widget.options.hidden">
<!-- <el-form :ref="widget.id" :model="formModel" label-position="top">-->
<el-row class="header-row">
<div class="action-header-column">
<span class="action-label">{{i18nt('render.hint.subFormAction')}}</span>
<el-button round type="primary" size="mini" class="action-button" @click="addSubFormRow"
:title="i18nt('render.hint.subFormAddActionHint')">
{{i18nt('render.hint.subFormAddAction')}}<i class="el-icon-plus el-icon-right"></i></el-button>
</div>
<template v-for="(subWidget) in widget.widgetList">
<div :key="subWidget.id + 'thc'" class="field-header-column"
:class="[getLabelAlign(widget, subWidget), !!subWidget.options.required ? 'is-required' : '']"
:style="{width: subWidget.options.columnWidth}">
<span v-if="!!subWidget.options.labelIconClass" class="custom-label">
<template v-if="subWidget.options.labelIconPosition === 'front'">
<template v-if="!!subWidget.options.labelTooltip">
<el-tooltip :content="subWidget.options.labelTooltip" effect="light">
<i :class="subWidget.options.labelIconClass"></i></el-tooltip>{{subWidget.options.label}}</template>
<template v-else>
<i :class="subWidget.options.labelIconClass"></i>{{subWidget.options.label}}</template>
</template>
<template v-else-if="subWidget.options.labelIconPosition === 'rear'">
<template v-if="!!subWidget.options.labelTooltip">
{{subWidget.options.label}}<el-tooltip :content="subWidget.options.labelTooltip" effect="light">
<i :class="subWidget.options.labelIconClass"></i></el-tooltip></template>
<template v-else>
{{subWidget.options.label}}<i :class="subWidget.options.labelIconClass"></i></template>
</template>
</span>
<template v-else>
<span :title="subWidget.options.labelTooltip">{{subWidget.options.label}}</span></template>
</div>
</template>
</el-row>
<el-row v-for="(subFormRowId, sfrIdx) in rowIdData" class="sub-form-row" :key="subFormRowId">
<div class="sub-form-action-column hide-label">
<div class="action-button-column">
<el-button circle type="" icon="el-icon-circle-plus-outline" @click="insertSubFormRow(sfrIdx)"
:title="i18nt('render.hint.insertSubFormRow')"></el-button>
<el-button circle type="" icon="el-icon-delete" @click="deleteSubFormRow(sfrIdx)"
:title="i18nt('render.hint.deleteSubFormRow')"></el-button>
<span v-if="widget.options.showRowNumber" class="row-number-span">#{{sfrIdx+1}}</span>
</div>
</div>
<template v-for="(subWidget, swIdx) in widget.widgetList">
<div class="sub-form-table-column hide-label" :key="subWidget.id + 'tc' + subFormRowId"
:style="{width: subWidget.options.columnWidth}">
<field-widget :field="fieldSchemaData[sfrIdx][swIdx]"
:key="fieldSchemaData[sfrIdx][swIdx].id" :parent-list="widget.widgetList"
:index-of-parent-list="swIdx" :parent-widget="widget"
:sub-form-row-id="subFormRowId"
:sub-form-row-index="sfrIdx" :sub-form-col-index="swIdx"></field-widget>
</div>
</template>
</el-row>
<!-- </el-form>-->
</div>
</div>
</template>
<script>
import emitter from 'element-ui/lib/mixins/emitter'
import FieldWidget from "../../components/form-designer/form-widget/field-widget";
import GridColItem from "./grid-col-item";
import TableCellItem from "./table-cell-item"
import {deepClone, generateId} from "../../utils/util";
import i18n from "../../utils/i18n";
import refMixin from "../../components/form-render/refMixin";
export default {
name: "ContainerItem",
componentName: 'ContainerItem',
mixins: [emitter, i18n, refMixin],
components: {
GridColItem,
TableCellItem,
FieldWidget,
},
props: {
widget: Object,
},
inject: ['refList', 'sfRefList', 'globalModel'],
data() {
return {
activeTabName: '',
rowIdData: [],
fieldSchemaData: [],
}
},
computed: {
customClass() {
return this.widget.options.customClass || ''
},
visibleTabs() {
return this.widget.tabs.filter(tp => {
return !tp.options.hidden
})
},
formModel: {
cache: false,
get() {
return this.globalModel.formModel
}
},
},
created() {
this.initRefList()
this.registerSubFormToRefList()
this.initRowIdData(true)
this.initFieldSchemaData()
this.initEventHandler()
},
mounted() {
this.initActiveTab()
this.handleSubFormFirstRowAdd() //
},
methods: {
getLabelAlign(widget, subWidget) {
return subWidget.options.labelAlign || widget.options.labelAlign
},
initActiveTab() {
if ((this.widget.type === 'tab') && (this.widget.tabs.length > 0)) {
let activePanes = this.widget.tabs.filter((tp) => {
return tp.options.active === true
})
if (activePanes.length > 0) {
this.activeTabName = activePanes[0].options.name
} else {
this.activeTabName = this.widget.tabs[0].options.name
}
}
},
registerSubFormToRefList() {
if (this.widget.type === 'sub-form') {
this.sfRefList[this.widget.options.name] = this
}
},
initRowIdData(initFlag) {
if (this.widget.type === 'sub-form') {
this.rowIdData.splice(0, this.rowIdData.length) //splicelength=0
let subFormModel = this.formModel[this.widget.options.name]
if (!!subFormModel && (subFormModel.length > 0)) {
subFormModel.forEach(() => {
this.rowIdData.push('r' + generateId())
})
if (!!initFlag) {
//SumFormDataChange
setTimeout(() => {
this.handleSubFormRowChange(subFormModel)
}, 800)
}
}
}
},
addToRowIdData() {
this.rowIdData.push('rowId' + generateId())
},
insertToRowIdData(rowIndex) {
this.rowIdData.splice(rowIndex, 0, 'rowId' + generateId())
},
deleteFromRowIdData(rowIndex) {
this.rowIdData.splice(rowIndex, 1)
},
initFieldSchemaData() { //fieldSchemaData
if (this.widget.type !== 'sub-form') {
return
}
let rowLength = this.rowIdData.length
this.fieldSchemaData.splice(0, this.fieldSchemaData.length) //splicelength=0
if (rowLength > 0) {
for (let i = 0; i < rowLength; i++) {
let fieldSchemas = []
this.widget.widgetList.forEach(swItem => {
fieldSchemas.push( this.cloneFieldSchema(swItem) )
})
this.fieldSchemaData.push(fieldSchemas)
}
}
},
addToFieldSchemaData(rowIndex) {
let fieldSchemas = []
this.widget.widgetList.forEach(swItem => {
fieldSchemas.push( this.cloneFieldSchema(swItem) )
})
if (rowIndex === undefined) {
this.fieldSchemaData.push(fieldSchemas)
} else {
this.fieldSchemaData.splice(rowIndex, 0, fieldSchemas)
}
},
deleteFromFieldSchemaData(rowIndex) {
this.fieldSchemaData.splice(rowIndex, 1)
},
cloneFieldSchema(fieldWidget) {
let newFieldSchema = deepClone(fieldWidget)
newFieldSchema.id = fieldWidget.type + generateId()
return newFieldSchema
},
initEventHandler() {
if (this.widget.type !== 'sub-form') {
return
}
this.$on('setFormData', function (newFormData) {
this.initRowIdData(false)
this.initFieldSchemaData()
let subFormData = newFormData[this.widget.options.name] || []
setTimeout(() => { //SubFormRowChange, 便
this.handleSubFormRowChange(subFormData)
}, 800)
})
},
handleSubFormFirstRowAdd() {
if (this.widget.type !== 'sub-form') {
return
}
if (!!this.widget.options.showBlankRow && (this.rowIdData.length === 1)) {
let oldSubFormData = this.formModel[this.widget.options.name] || []
this.handleSubFormRowAdd(oldSubFormData, this.rowIdData[0])
this.handleSubFormRowChange(oldSubFormData)
}
},
addSubFormRow() {
let newSubFormDataRow = {}
this.widget.widgetList.forEach(subFormItem => {
if (!!subFormItem.formItemFlag) {
newSubFormDataRow[subFormItem.options.name] = subFormItem.options.defaultValue
}
})
let oldSubFormData = this.formModel[this.widget.options.name] || []
oldSubFormData.push(newSubFormDataRow)
this.addToRowIdData()
this.addToFieldSchemaData()
this.handleSubFormRowAdd(oldSubFormData, this.rowIdData[oldSubFormData.length - 1])
this.handleSubFormRowChange(oldSubFormData)
},
insertSubFormRow(beforeFormRowIndex) {
let newSubFormDataRow = {}
this.widget.widgetList.forEach(subFormItem => {
if (!!subFormItem.formItemFlag) {
newSubFormDataRow[subFormItem.options.name] = subFormItem.options.defaultValue
}
})
let oldSubFormData = this.formModel[this.widget.options.name] || []
oldSubFormData.splice(beforeFormRowIndex, 0, newSubFormDataRow)
this.insertToRowIdData(beforeFormRowIndex)
this.addToFieldSchemaData(beforeFormRowIndex)
this.handleSubFormRowInsert(oldSubFormData, this.rowIdData[beforeFormRowIndex])
this.handleSubFormRowChange(oldSubFormData)
},
deleteSubFormRow(formRowIndex) {
this.$confirm(this.i18nt('render.hint.deleteSubFormRow') + '?', this.i18nt('render.hint.prompt'), {
confirmButtonText: this.i18nt('render.hint.confirm'),
cancelButtonText: this.i18nt('render.hint.cancel')
}).then(() => {
let oldSubFormData = this.formModel[this.widget.options.name] || []
let deletedDataRow = deepClone(oldSubFormData[formRowIndex])
oldSubFormData.splice(formRowIndex, 1)
this.deleteFromRowIdData(formRowIndex)
this.deleteFromFieldSchemaData(formRowIndex)
this.handelSubFormRowDelete(oldSubFormData, deletedDataRow)
this.handleSubFormRowChange(oldSubFormData)
}).catch(() => {
//
})
},
handleSubFormRowChange(subFormData) {
if (!!this.widget.options.onSubFormRowChange) {
let customFunc = new Function('subFormData', this.widget.options.onSubFormRowChange)
customFunc.call(this, subFormData)
}
},
handleSubFormRowAdd(subFormData, newRowId) {
if (!!this.widget.options.onSubFormRowAdd) {
let customFunc = new Function('subFormData', 'newRowId', this.widget.options.onSubFormRowAdd)
customFunc.call(this, subFormData, newRowId)
}
},
handleSubFormRowInsert(subFormData, newRowId) {
if (!!this.widget.options.onSubFormRowInsert) {
let customFunc = new Function('subFormData', 'newRowId', this.widget.options.onSubFormRowInsert)
customFunc.call(this, subFormData, newRowId)
}
},
handelSubFormRowDelete(subFormData, deletedDataRow) {
if (!!this.widget.options.onSubFormRowDelete) {
let customFunc = new Function('subFormData', 'deletedDataRow', this.widget.options.onSubFormRowDelete)
customFunc.call(this, subFormData, deletedDataRow)
}
},
//--------------------- API begin ------------------//
/* 提示:用户可自行扩充这些方法!!! */
setHidden(flag) {
this.widget.options.hidden = flag
},
activeTab(tabIndex) { //tabIndex0
if ((tabIndex >= 0) && (tabIndex < this.widget.tabs.length)) {
this.widget.tabs.forEach((tp, idx) => {
tp.options.active = idx === tabIndex
if (idx === tabIndex) {
this.activeTabName = tp.options.name
}
})
}
},
disableTab(tabIndex) {
if ((tabIndex >= 0) && (tabIndex < this.widget.tabs.length)) {
this.widget.tabs[tabIndex].options.disabled = true
}
},
enableTab(tabIndex) {
if ((tabIndex >= 0) && (tabIndex < this.widget.tabs.length)) {
this.widget.tabs[tabIndex].options.disabled = false
}
},
hideTab(tabIndex) {
if ((tabIndex >= 0) && (tabIndex < this.widget.tabs.length)) {
this.widget.tabs[tabIndex].options.hidden = true
}
},
showTab(tabIndex) {
if ((tabIndex >= 0) && (tabIndex < this.widget.tabs.length)) {
this.widget.tabs[tabIndex].options.hidden = false
}
},
disableSubFormRow(rowIndex) {
this.widget.widgetList.forEach(subWidget => {
let swRefName = subWidget.options.name + '@row' + this.rowIdData[rowIndex]
let foundSW = this.getWidgetRef(swRefName)
if (!!foundSW) {
foundSW.setDisabled(true)
}
})
},
enableSubFormRow(rowIndex) {
this.widget.widgetList.forEach(subWidget => {
let swRefName = subWidget.options.name + '@row' + this.rowIdData[rowIndex]
let foundSW = this.getWidgetRef(swRefName)
if (!!foundSW) {
foundSW.setDisabled(false)
}
})
},
disableSubForm() {
if (this.rowIdData.length > 0) {
this.rowIdData.forEach((dataRow, rIdx) => {
this.disableSubFormRow(rIdx)
})
}
},
enableSubForm() {
if (this.rowIdData.length > 0) {
this.rowIdData.forEach((dataRow, rIdx) => {
this.enableSubFormRow(rIdx)
})
}
},
resetSubForm() { //subForm
if (this.widget.type === 'sub-form') {
let subFormModel = this.formModel[this.widget.options.name]
if (!!subFormModel) {
subFormModel.splice(0, subFormModel.length)
this.rowIdData.splice(0, this.rowIdData.length)
}
}
},
getSubFormValues(needValidation = true) {
if (this.widget.type === 'sub-form') {
//TODO:
return this.formModel[this.widget.options.name]
} else {
this.$message.error(this.i18nt('render.hint.nonSubFormType'))
}
},
// validateField(fieldName) { //
// //TODO:
// },
//
// validateSubForm() { //
// //TODO:
// },
//--------------------- API end ------------------//
},
}
</script>
<style lang="scss" scoped>
div.table-container {
table.table-layout {
width: 100%;
table-layout: fixed;
border-collapse: collapse;
}
}
.sub-form-container {
margin-bottom: 8px;
text-align: left; //IE
::v-deep .el-row.header-row {
padding-bottom: 0;
}
::v-deep .el-row.sub-form-row {
padding-top: 3px;
padding-bottom: 3px;
.row-number-span {
margin-left: 16px;
}
}
}
div.action-header-column {
display: inline-block;
width: 120px;
.action-label {
margin-right: 12px;
}
.action-button {
padding-left: 8px;
padding-right: 8px;
}
}
div.field-header-column {
display: inline-block;
//overflow: hidden;
//white-space: nowrap; //
//text-overflow: ellipsis; //
span.custom-label i {
margin: 0 3px;
}
}
div.field-header-column.is-required:before {
content: '*';
color: #F56C6C;
margin-right: 4px;
}
div.label-center-left {
text-align: left;
}
div.label-center-align {
text-align: center;
}
div.label-right-align {
text-align: right;
}
div.sub-form-action-column {
display: inline-block;
width: 120px;
::v-deep .el-form-item {
margin-bottom: 0;
}
::v-deep .el-button {
font-size: 18px;
padding: 0;
background: #DCDFE6;
border: 4px solid #DCDFE6;
}
}
div.sub-form-action-column.hide-label {
::v-deep .el-form-item__label {
display: none;
}
}
div.sub-form-table-column {
display: inline-block;
//width: 200px;
::v-deep .el-form-item {
margin-left: 4px;
margin-right: 4px;
margin-bottom: 0;
}
::v-deep .el-form-item__content {
margin-left: 0 !important;
}
}
div.sub-form-table-column.hide-label {
::v-deep .el-form-item__label {
display: none;
}
}
</style>

View File

@ -0,0 +1,66 @@
<template>
<el-col class="grid-cell" :span="widget.options.span" :class="[customClass]"
:key="widget.id" v-show="!widget.options.hidden">
<template v-if="!!widget.widgetList && (widget.widgetList.length > 0)">
<template v-for="(subWidget, swIdx) in widget.widgetList">
<template v-if="'container' === subWidget.category">
<container-item :widget="subWidget" :key="swIdx" :parent-list="widget.widgetList"
:index-of-parent-list="swIdx" :parent-widget="widget"></container-item>
</template>
<template v-else>
<field-widget :field="subWidget" :designer="null" :key="swIdx" :parent-list="widget.widgetList"
:index-of-parent-list="swIdx" :parent-widget="widget"></field-widget>
</template>
</template>
</template>
<template v-else>
<el-col>
<div class="blank-cell"><span class="invisible-content">{{i18nt('render.hint.blankCellContent')}}</span></div>
</el-col>
</template>
</el-col>
</template>
<script>
//import ContainerItem from "./container-item";
import FieldWidget from "../../components/form-designer/form-widget/field-widget";
import i18n from "../../utils/i18n";
import refMixin from "../../components/form-render/refMixin";
export default {
name: "GridColItem",
componentName: 'GridColItem',
mixins: [i18n, refMixin],
components: {
FieldWidget,
//'container-item': ContainerItem, /* 使 */
},
props: {
widget: Object,
parentWidget: Object,
parentList: Array,
indexOfParentList: Number,
},
inject: ['refList', 'globalModel'],
computed: {
customClass() {
return this.widget.options.customClass || ''
},
},
created() {
this.initRefList()
},
}
</script>
<style lang="scss" scoped>
.blank-cell {
font-style: italic;
color: #cccccc;
span.invisible-content {
opacity: 0;
}
}
</style>

View File

@ -0,0 +1,460 @@
<!--
/**
* author: vformAdmin
* email: vdpadmin@163.com
* website: https://www.yuque.com/variantdev/vform
* date: 2021.08.18
* remark: 如果要分发VForm源码需在本文件顶部保留此文件头信息
*/
-->
<template>
<el-form :label-position="labelPosition" :size="size" :class="[customClass]" class="render-form"
:label-width="formConfig.labelWidth + 'px'" :validate-on-rule-change="false"
:model="formDataModel" ref="renderForm"
@submit.native.prevent>
<template v-for="(widget, index) in widgetList">
<template v-if="'container' === widget.category">
<container-item :widget="widget" :key="widget.id" :parent-list="widgetList"
:index-of-parent-list="index" :parent-widget="null"></container-item>
</template>
<template v-else>
<field-widget :field="widget" :form-model="formDataModel" :designer="null" :key="widget.id" :parent-list="widgetList"
:index-of-parent-list="index" :parent-widget="null"></field-widget>
</template>
</template>
</el-form>
</template>
<script>
//import ElForm from 'element-ui/packages/form/src/form.vue' /* Element UI */
import emitter from 'element-ui/lib/mixins/emitter'
import FieldWidget from "../form-designer/form-widget/field-widget";
import ContainerItem from "./container-item";
import {deepClone, insertCustomCssToHead, insertGlobalFunctionsToHtml} from "../../utils/util";
import i18n, { changeLocale } from "../../utils/i18n";
export default {
name: "VFormRender",
componentName: 'VFormRender',
mixins: [emitter, i18n],
components: {
FieldWidget,
ContainerItem,
//ElForm,
},
props: {
formJson: Object, //propJSON
formData: { //prop
Object,
default: () => {}
},
optionData: { //prop
type: Object,
default: () => {}
},
},
provide() {
return {
refList: this.widgetRefList,
sfRefList: this.subFormRefList, //SubForm
formConfig: this.formConfig,
globalOptionData: this.optionData,
globalModel: {
formModel: this.formDataModel,
}
}
},
data() {
return {
formDataModel: {
//
},
widgetRefList: {},
subFormRefList: {},
}
},
computed: {
formConfig() {
return this.formJson.formConfig
},
widgetList() {
return this.formJson.widgetList
},
labelPosition() {
if (!!this.formConfig && !!this.formConfig.labelPosition) {
return this.formConfig.labelPosition
}
return 'left'
},
size() {
if (!!this.formConfig && !!this.formConfig.size) {
return this.formConfig.size
}
return 'medium'
},
customClass() {
return this.formConfig.customClass || ''
},
},
watch: {
//
},
created() {
this.insertCustomStyleAndScriptNode()
this.buildFormModel()
this.addFieldChangeEventHandler()
this.registerFormToRefList()
this.handleOnCreated()
},
mounted() {
this.initLocale()
this.handleOnMounted()
},
methods: {
initLocale() {
let curLocale = localStorage.getItem('v_form_locale') || 'zh-CN'
this.changeLanguage(curLocale)
},
insertCustomStyleAndScriptNode() {
if (!!this.formConfig && !!this.formConfig.cssCode) {
insertCustomCssToHead(this.formConfig.cssCode)
}
if (!!this.formConfig && !!this.formConfig.functions) {
insertGlobalFunctionsToHtml(this.formConfig.functions)
}
},
buildFormModel() {
this.widgetList.forEach((wItem) => {
this.buildDataFromWidget(wItem, null)
})
},
buildDataFromWidget(wItem) {
if (wItem.category === 'container') {
if (wItem.type === 'grid') {
if (!!wItem.cols && (wItem.cols.length > 0)) {
wItem.cols.forEach((childItem) => {
this.buildDataFromWidget(childItem, wItem)
})
}
} else if (wItem.type === 'table') {
if (!!wItem.rows && (wItem.rows.length > 0)) {
wItem.rows.forEach((rowItem) => {
if (!!rowItem.cols && (rowItem.cols.length > 0)) {
rowItem.cols.forEach((colItem) => {
this.buildDataFromWidget(colItem, wItem)
})
}
})
}
} else if (wItem.type === 'tab') {
if (!!wItem.tabs && (wItem.tabs.length > 0)) {
wItem.tabs.forEach((tabItem) => {
if (!!tabItem.widgetList && (tabItem.widgetList.length > 0)) {
tabItem.widgetList.forEach((childItem) => {
this.buildDataFromWidget(childItem, wItem)
})
}
})
}
} else if (wItem.type === 'sub-form') {
let subFormName = wItem.options.name
if (!this.formData.hasOwnProperty(subFormName)) {
let subFormDataRow = {}
if (wItem.options.showBlankRow) {
wItem.widgetList.forEach(subFormItem => {
if (!!subFormItem.formItemFlag) {
subFormDataRow[subFormItem.options.name] = subFormItem.options.defaultValue
}
})
this.$set(this.formDataModel, subFormName, [subFormDataRow]) //
} else {
this.$set(this.formDataModel, subFormName, []) //
}
} else {
let initialValue = this.formData[subFormName]
this.$set(this.formDataModel, subFormName, deepClone(initialValue))
}
} else if ((wItem.type === 'grid-col') || (wItem.type === 'table-cell')) {
if (!!wItem.widgetList && (wItem.widgetList.length > 0)) {
wItem.widgetList.forEach((childItem) => {
this.buildDataFromWidget(childItem, wItem)
})
}
} else {
if (!!wItem.widgetList && (wItem.widgetList.length > 0)) {
wItem.widgetList.forEach((childItem) => {
this.buildDataFromWidget(childItem, wItem)
})
}
}
} else if (!!wItem.formItemFlag) {
if (!this.formData.hasOwnProperty(wItem.options.name)) {
//this.formDataModel[wItem.options.name] = '' //$set
this.$set(this.formDataModel, wItem.options.name, wItem.options.defaultValue) //
} else {
let initialValue = this.formData[wItem.options.name]
this.$set(this.formDataModel, wItem.options.name, deepClone(initialValue))
}
}
},
addFieldChangeEventHandler() {
this.$on('fieldChange', function (fieldName, newValue, oldValue, subFormName, subFormRowIndex) {
this.handleFieldDataChange(fieldName, newValue, oldValue, subFormName, subFormRowIndex)
this.$emit('formChange', fieldName, newValue, oldValue, this.formDataModel, subFormName, subFormRowIndex)
})
},
registerFormToRefList() {
this.widgetRefList['v_form_ref'] = this
},
handleFieldDataChange(fieldName, newValue, oldValue, subFormName, subFormRowIndex) {
if (!!this.formConfig.onFormDataChange) {
let customFunc = new Function('fieldName', 'newValue', 'oldValue', 'formModel', 'subFormName', 'subFormRowIndex',
this.formConfig.onFormDataChange)
customFunc.call(this, fieldName, newValue, oldValue, this.formDataModel, subFormName, subFormRowIndex)
}
},
handleOnCreated() {
if (!!this.formConfig.onFormCreated) {
let customFunc = new Function(this.formConfig.onFormCreated)
customFunc.call(this)
}
},
handleOnMounted() {
if (!!this.formConfig.onFormMounted) {
let customFunc = new Function(this.formConfig.onFormMounted)
customFunc.call(this)
}
},
findWidgetAndSetDisabled(widgetName, disabledFlag) {
let foundW = this.getWidgetRef(widgetName)
if (!!foundW) {
foundW.setDisabled(disabledFlag)
}
},
findWidgetAndSetHidden(widgetName, hiddenFlag) {
let foundW = this.getWidgetRef(widgetName)
if (!!foundW) {
foundW.setHidden(hiddenFlag)
}
},
//--------------------- API begin ------------------//
/* 提示:用户可自行扩充这些方法!!! */
changeLanguage(langName) {
changeLocale(langName)
},
getNativeForm() { //form
return this.$refs['renderForm']
},
getWidgetRef(widgetName, showError = false) {
let foundRef = this.widgetRefList[widgetName]
if (!foundRef && !!showError) {
this.$message.error(this.i18nt('render.hint.refNotFound') + widgetName)
}
return foundRef
},
getFormData(needValidation = true) {
if (!needValidation) {
return this.formDataModel
}
let callback = function nullFunc() {}
let promise = new window.Promise(function (resolve, reject) {
callback = function callback(formData, error) {
!error ? resolve(formData) : reject(error);
};
});
this.$refs['renderForm'].validate((valid) => {
if (valid) {
callback(this.formDataModel)
} else {
callback(this.formDataModel, this.i18nt('render.hint.validationFailed'))
}
})
return promise
},
setFormData(formData) { //
//this.formDataModel = formData //injectformModel
Object.keys(this.formDataModel).forEach(propName => {
if (!!formData && formData.hasOwnProperty(propName)) {
this.formDataModel[propName] = deepClone( formData[propName] )
}
})
//this.formDataModel = formData
//this._provided.globalModel.formModel = formData /* 使inject */
//
// SubForm
//this.broadcast('ContainerItem', 'setFormData', formData)
this.broadcast('ContainerItem', 'setFormData', this.formDataModel)
// FieldWidget
//this.broadcast('FieldWidget', 'setFormData', formData)
this.broadcast('FieldWidget', 'setFormData', this.formDataModel)
},
getFieldValue(fieldName) { //
let fieldRef = this.getWidgetRef(fieldName)
if (!!fieldRef && !!fieldRef.getValue) {
fieldRef.getValue()
}
},
setFieldValue(fieldName, fieldValue) { //
let fieldRef = this.getWidgetRef(fieldName)
if (!!fieldRef && !!fieldRef.setValue) {
fieldRef.setValue(fieldValue)
}
},
getSubFormValues(subFormName, needValidation = true) {
let foundSFRef = this.subFormRefList[subFormName]
// if (!foundSFRef) {
// return this.formDataModel[subFormName]
// }
return foundSFRef.getSubFormValues(needValidation)
},
disableForm() {
let wNameList = Object.keys(this.widgetRefList)
wNameList.forEach(wName => {
let foundW = this.getWidgetRef(wName)
if (!!foundW) {
// if (!!foundW.setDisabled) {
// foundW.setDisabled(true)
// }
!!foundW.setDisabled && foundW.setDisabled(true)
}
})
},
enableForm() {
let wNameList = Object.keys(this.widgetRefList)
wNameList.forEach(wName => {
let foundW = this.getWidgetRef(wName)
if (!!foundW) {
// if (!!foundW.setDisabled) {
// foundW.setDisabled(false)
// }
!!foundW.setDisabled && foundW.setDisabled(false)
}
})
},
resetForm() { //
let subFormNames = Object.keys(this.subFormRefList)
subFormNames.forEach(sfName => {
if (!!this.subFormRefList[sfName].resetSubForm) {
this.subFormRefList[sfName].resetSubForm()
}
})
let wNameList = Object.keys(this.widgetRefList)
wNameList.forEach(wName => {
let foundW = this.getWidgetRef(wName)
if (!!foundW && !!foundW.resetField) {
foundW.resetField()
}
})
this.$refs.renderForm.clearValidate()
},
validateForm() {
//
},
validateFields() {
//
},
disableWidgets(widgetNames) {
if (!!widgetNames) {
if (typeof widgetNames === 'string') {
this.findWidgetAndSetDisabled(widgetNames, true)
} else if (Array.isArray(widgetNames)) {
widgetNames.forEach(wn => {
this.findWidgetAndSetDisabled(wn, true)
})
}
}
},
enableWidgets(widgetNames) {
if (!!widgetNames) {
if (typeof widgetNames === 'string') {
this.findWidgetAndSetDisabled(widgetNames, false)
} else if (Array.isArray(widgetNames)) {
widgetNames.forEach(wn => {
this.findWidgetAndSetDisabled(wn, false)
})
}
}
},
hideWidgets(widgetNames) {
if (!!widgetNames) {
if (typeof widgetNames === 'string') {
this.findWidgetAndSetHidden(widgetNames, true)
} else if (Array.isArray(widgetNames)) {
widgetNames.forEach(wn => {
this.findWidgetAndSetHidden(wn, true)
})
}
}
},
showWidgets(widgetNames) {
if (!!widgetNames) {
if (typeof widgetNames === 'string') {
this.findWidgetAndSetHidden(widgetNames, false)
} else if (Array.isArray(widgetNames)) {
widgetNames.forEach(wn => {
this.findWidgetAndSetHidden(wn, false)
})
}
}
}
//--------------------- API end ------------------//
},
}
</script>
<style lang="scss" scoped>
.el-form ::v-deep .el-row {
padding: 8px;
}
</style>

View File

@ -0,0 +1,22 @@
export default {
methods: {
initRefList() {
if ((this.refList !== null) && !!this.widget.options.name) {
this.refList[this.widget.options.name] = this
}
},
getWidgetRef(widgetName, showError) {
let foundRef = this.refList[widgetName]
if (!foundRef && !!showError) {
this.$message.error(this.i18nt('render.hint.refNotFound') + widgetName)
}
return foundRef
},
getFormRef() { /* 获取VFrom引用必须在VForm组件created之后方可调用 */
return this.refList['v_form_ref']
},
}
}

View File

@ -0,0 +1,61 @@
<template>
<td class="table-cell" :class="[customClass]"
:colspan="widget.options.colspan || 1" :rowspan="widget.options.rowspan || 1"
:style="{width: widget.options.cellWidth + ' !important' || '', height: widget.options.cellHeight + ' !important' || ''}">
<template v-for="(subWidget, swIdx) in widget.widgetList">
<template v-if="'container' === subWidget.category">
<container-item :widget="subWidget" :key="swIdx" :parent-list="widget.widgetList"
:index-of-parent-list="swIdx" :parent-widget="widget"></container-item>
</template>
<template v-else>
<field-widget :field="subWidget" :key="swIdx" :parent-list="widget.widgetList"
:index-of-parent-list="swIdx" :parent-widget="widget"></field-widget>
</template>
</template>
</td>
</template>
<script>
import FieldWidget from "../../components/form-designer/form-widget/field-widget";
import i18n from "../../utils/i18n";
import refMixin from "../../components/form-render/refMixin";
export default {
name: "TableCellItem",
componentName: "TableCellItem",
mixins: [i18n, refMixin],
components: {
FieldWidget,
//'container-item': ContainerItem, /* 使 */
},
props: {
widget: Object,
rowIndex: Number,
colIndex: Number,
},
inject: ['refList', 'globalModel'],
computed: {
customClass() {
return this.widget.options.customClass || ''
},
},
created() {
/* tableCell不生成组件引用 */
//this.initRefList()
},
methods: {
}
}
</script>
<style lang="scss" scoped>
td.table-cell {
display: table-cell;
height: 36px;
border: 1px dashed #336699;
}
</style>

View File

@ -0,0 +1,49 @@
<template>
<svg :class="svgClass" aria-hidden="true">
<use :xlink:href="iconName"></use>
<title v-if="!!title">{{title}}</title>
</svg>
</template>
<script>
export default {
name: 'SvgIcon',
props: {
iconClass: {
type: String,
required: true
},
className: {
type: String
},
title: {
type: String,
default: ''
}
},
computed: {
iconName() {
return `#icon-${this.iconClass}`
},
svgClass() {
if (this.className) {
return 'svg-icon ' + this.className
} else {
return 'svg-icon'
}
}
}
}
</script>
<style lang="scss" scoped>
.svg-icon {
width: 1.1em;
height: 1.1em;
margin-left: 0.35em;
margin-right: 0.35em;
vertical-align: -0.15em;
fill: currentColor;
overflow: hidden;
}
</style>

29
src/iconfont/iconfont.css Normal file
View File

@ -0,0 +1,29 @@
@font-face {
font-family: "iconfont"; /* Project id */
src: url('iconfont.ttf?t=1620643511304') format('truetype');
}
.iconfont {
font-family: "iconfont" !important;
font-size: 16px;
font-style: normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.icon-insertcolumn:before {
content: "\e753";
}
.icon-insertrow:before {
content: "\e754";
}
.icon-hide:before {
content: "\e76b";
}
.icon-drag:before {
content: "\e61d";
}

BIN
src/iconfont/iconfont.eot Normal file

Binary file not shown.

1
src/iconfont/iconfont.js Normal file
View File

@ -0,0 +1 @@
!function(e){var t,n,o,l,c,i,a='<svg><symbol id="icon-insertcolumn" viewBox="0 0 1024 1024"><path d="M653.184 713.6c12.864-12.864 33.6-12.864 46.528 0 6.4 6.4 3.776 14.72 3.776 23.232 0 8.384-3.264 16.768-9.6 23.104L569.92 886.4c-0.128 0.128-0.32 0.192-0.512 0.32-2.88 2.88-9.536 5.184-13.312 6.784-3.456 1.344-0.64 1.856-4.096 2.112C551.232 895.616 550.656 896 549.952 896c-0.512 0-0.896-0.256-1.344-0.256-3.84-0.192-5.76-0.896-9.344-2.24-3.264-1.344-6.016-3.52-8.64-5.76-0.64-0.512-1.472-0.768-2.048-1.344l-137.344-126.4c-12.864-12.736-6.976-33.6 5.888-46.4 12.8-12.864 33.6-12.864 46.464 0l105.472 100.352L653.184 713.6zM384 64l0 576 320 0L704 64 384 64zM128 704l0 256L64 960l0-256 0-64 0 0 64 0 128 0 64 0 0 64 0 256L257.024 960 256 704 128 704M832 704l0 256-64 0 0-256 0-64 0 0 64 0 128 0 64 0 0 64 0 256-62.976 0L960 704 832 704" ></path></symbol><symbol id="icon-insertrow" viewBox="0 0 1024 1024"><path d="M310.336 653.184c12.864 12.864 12.864 33.6 0 46.528-6.4 6.4-14.72 3.776-23.168 3.776s-16.832-3.264-23.168-9.6L137.6 569.92C137.472 569.792 137.408 569.6 137.344 569.408 134.464 566.592 132.096 559.872 130.496 556.096 129.152 552.704 128.64 555.52 128.384 552 128.384 551.232 128 550.656 128 549.952c0-0.512 0.256-0.896 0.256-1.344 0.192-3.84 0.896-5.76 2.24-9.344 1.344-3.264 3.52-6.016 5.76-8.64C136.768 529.92 137.024 529.088 137.6 528.512l126.336-137.344c12.8-12.864 33.6-6.976 46.4 5.888 12.864 12.8 12.864 33.6 0 46.464L210.048 548.992 310.336 653.184zM960 384 384 384l0 320 576 0L960 384zM320 128 64 128 64 64l256 0 64 0 0 0 0 64 0 128 0 64L320 320 64 320 64 257.024 320 256 320 128M320 832 64 832l0-64 256 0 64 0 0 0 0 64 0 128 0 64L320 1024 64 1024l0-62.976L320 960 320 832" ></path></symbol><symbol id="icon-hide" viewBox="0 0 1024 1024"><path d="M956.8 496c-41.6-70.4-99.2-147.2-176-204.8l105.6-105.6c12.8-12.8 12.8-32 0-44.8s-32-12.8-44.8 0l-115.2 115.2C665.6 214.4 592 192 512 192 297.6 192 153.6 358.4 67.2 496c-6.4 9.6-6.4 22.4 0 32 41.6 70.4 102.4 147.2 176 204.8l-108.8 108.8c-12.8 12.8-12.8 32 0 44.8C144 892.8 150.4 896 160 896s16-3.2 22.4-9.6l115.2-115.2c60.8 38.4 134.4 60.8 214.4 60.8 185.6 0 374.4-128 444.8-307.2C960 515.2 960 505.6 956.8 496zM134.4 512c76.8-121.6 201.6-256 377.6-256 60.8 0 118.4 16 166.4 44.8l-80 80C576 361.6 544 352 512 352c-89.6 0-160 70.4-160 160 0 32 9.6 64 25.6 89.6l-89.6 89.6C224 640 172.8 572.8 134.4 512zM608 512c0 54.4-41.6 96-96 96-16 0-28.8-3.2-41.6-9.6l128-128C604.8 483.2 608 496 608 512zM416 512c0-54.4 41.6-96 96-96 16 0 28.8 3.2 41.6 9.6l-128 128C419.2 540.8 416 528 416 512zM512 768c-60.8 0-118.4-16-166.4-44.8l80-80C448 662.4 480 672 512 672c89.6 0 160-70.4 160-160 0-32-9.6-64-25.6-89.6l89.6-89.6c67.2 51.2 118.4 118.4 156.8 179.2C825.6 659.2 665.6 768 512 768z" ></path></symbol><symbol id="icon-drag" viewBox="0 0 1024 1024"><path d="M288 192a96 96 0 1 1 0-192 96 96 0 0 1 0 192z m0 277.312a96 96 0 1 1 0-192 96 96 0 0 1 0 192z m0 277.376a96 96 0 1 1 0-192 96 96 0 0 1 0 192zM288 1024a96 96 0 1 1 0-192 96 96 0 0 1 0 192z m448-832a96 96 0 1 1 0-192 96 96 0 0 1 0 192z m0 277.312a96 96 0 1 1 0-192 96 96 0 0 1 0 192z m0 277.376a96 96 0 1 1 0-192 96 96 0 0 1 0 192zM736 1024a96 96 0 1 1 0-192 96 96 0 0 1 0 192z" fill="#595959" ></path></symbol></svg>',d=(d=document.getElementsByTagName("script"))[d.length-1].getAttribute("data-injectcss");if(d&&!e.__iconfont__svg__cssinject__){e.__iconfont__svg__cssinject__=!0;try{document.write("<style>.svgfont {display: inline-block;width: 1em;height: 1em;fill: currentColor;vertical-align: -0.1em;font-size:16px;}</style>")}catch(e){console&&console.log(e)}}function s(){c||(c=!0,o())}t=function(){var e,t,n,o;(o=document.createElement("div")).innerHTML=a,a=null,(n=o.getElementsByTagName("svg")[0])&&(n.setAttribute("aria-hidden","true"),n.style.position="absolute",n.style.width=0,n.style.height=0,n.style.overflow="hidden",e=n,(t=document.body).firstChild?(o=e,(n=t.firstChild).parentNode.insertBefore(o,n)):t.appendChild(e))},document.addEventListener?~["complete","loaded","interactive"].indexOf(document.readyState)?setTimeout(t,0):(n=function(){document.removeEventListener("DOMContentLoaded",n,!1),t()},document.addEventListener("DOMContentLoaded",n,!1)):document.attachEvent&&(o=t,l=e.document,c=!1,(i=function(){try{l.documentElement.doScroll("left")}catch(e){return void setTimeout(i,50)}s()})(),l.onreadystatechange=function(){"complete"==l.readyState&&(l.onreadystatechange=null,s())})}(window);

View File

@ -0,0 +1,37 @@
{
"id": "",
"name": "",
"font_family": "iconfont",
"css_prefix_text": "icon-",
"description": "",
"glyphs": [
{
"icon_id": "586931",
"name": "insert-column",
"font_class": "insertcolumn",
"unicode": "e753",
"unicode_decimal": 59219
},
{
"icon_id": "586932",
"name": "insert-row",
"font_class": "insertrow",
"unicode": "e754",
"unicode_decimal": 59220
},
{
"icon_id": "1030072",
"name": "hide",
"font_class": "hide",
"unicode": "e76b",
"unicode_decimal": 59243
},
{
"icon_id": "14772710",
"name": "drag",
"font_class": "drag",
"unicode": "e61d",
"unicode_decimal": 58909
}
]
}

35
src/iconfont/iconfont.svg Normal file
View File

@ -0,0 +1,35 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >
<!--
2013-9-30: Created.
-->
<svg>
<metadata>
Created by iconfont
</metadata>
<defs>
<font id="iconfont" horiz-adv-x="1024" >
<font-face
font-family="iconfont"
font-weight="500"
font-stretch="normal"
units-per-em="1024"
ascent="896"
descent="-128"
/>
<missing-glyph />
<glyph glyph-name="insertcolumn" unicode="&#59219;" d="M653.184 182.4c12.864 12.864 33.6 12.864 46.528 0 6.4-6.4 3.776-14.72 3.776-23.232 0-8.384-3.264-16.768-9.6-23.104L569.92 9.6c-0.128-0.128-0.32-0.192-0.512-0.32-2.88-2.88-9.536-5.184-13.312-6.784-3.456-1.344-0.64-1.856-4.096-2.112C551.232 0.384 550.656 0 549.952 0c-0.512 0-0.896 0.256-1.344 0.256-3.84 0.192-5.76 0.896-9.344 2.24-3.264 1.344-6.016 3.52-8.64 5.76-0.64 0.512-1.472 0.768-2.048 1.344l-137.344 126.4c-12.864 12.736-6.976 33.6 5.888 46.4 12.8 12.864 33.6 12.864 46.464 0l105.472-100.352L653.184 182.4zM384 832l0-576 320 0L704 832 384 832zM128 192l0-256L64-64l0 256 0 64 0 0 64 0 128 0 64 0 0-64 0-256L257.024-64 256 192 128 192M832 192l0-256-64 0 0 256 0 64 0 0 64 0 128 0 64 0 0-64 0-256-62.976 0L960 192 832 192" horiz-adv-x="1024" />
<glyph glyph-name="insertrow" unicode="&#59220;" d="M310.336 242.816c12.864-12.864 12.864-33.6 0-46.528-6.4-6.4-14.72-3.776-23.168-3.776s-16.832 3.264-23.168 9.6L137.6 326.08C137.472 326.208 137.408 326.4 137.344 326.592 134.464 329.408 132.096 336.128 130.496 339.904 129.152 343.296 128.64 340.48 128.384 344 128.384 344.768 128 345.344 128 346.048c0 0.512 0.256 0.896 0.256 1.344 0.192 3.84 0.896 5.76 2.24 9.344 1.344 3.264 3.52 6.016 5.76 8.64C136.768 366.08 137.024 366.912 137.6 367.488l126.336 137.344c12.8 12.864 33.6 6.976 46.4-5.888 12.864-12.8 12.864-33.6 0-46.464L210.048 347.008 310.336 242.816zM960 512 384 512l0-320 576 0L960 512zM320 768 64 768 64 832l256 0 64 0 0 0 0-64 0-128 0-64L320 576 64 576 64 638.976 320 640 320 768M320 64 64 64l0 64 256 0 64 0 0 0 0-64 0-128 0-64L320-128 64-128l0 62.976L320-64 320 64" horiz-adv-x="1024" />
<glyph glyph-name="hide" unicode="&#59243;" d="M956.8 400c-41.6 70.4-99.2 147.2-176 204.8l105.6 105.6c12.8 12.8 12.8 32 0 44.8s-32 12.8-44.8 0l-115.2-115.2C665.6 681.6 592 704 512 704 297.6 704 153.6 537.6 67.2 400c-6.4-9.6-6.4-22.4 0-32 41.6-70.4 102.4-147.2 176-204.8l-108.8-108.8c-12.8-12.8-12.8-32 0-44.8C144 3.2 150.4 0 160 0s16 3.2 22.4 9.6l115.2 115.2c60.8-38.4 134.4-60.8 214.4-60.8 185.6 0 374.4 128 444.8 307.2C960 380.8 960 390.4 956.8 400zM134.4 384c76.8 121.6 201.6 256 377.6 256 60.8 0 118.4-16 166.4-44.8l-80-80C576 534.4 544 544 512 544c-89.6 0-160-70.4-160-160 0-32 9.6-64 25.6-89.6l-89.6-89.6C224 256 172.8 323.2 134.4 384zM608 384c0-54.4-41.6-96-96-96-16 0-28.8 3.2-41.6 9.6l128 128C604.8 412.8 608 400 608 384zM416 384c0 54.4 41.6 96 96 96 16 0 28.8-3.2 41.6-9.6l-128-128C419.2 355.2 416 368 416 384zM512 128c-60.8 0-118.4 16-166.4 44.8l80 80C448 233.6 480 224 512 224c89.6 0 160 70.4 160 160 0 32-9.6 64-25.6 89.6l89.6 89.6c67.2-51.2 118.4-118.4 156.8-179.2C825.6 236.8 665.6 128 512 128z" horiz-adv-x="1024" />
</font>
</defs></svg>

After

Width:  |  Height:  |  Size: 3.1 KiB

BIN
src/iconfont/iconfont.ttf Normal file

Binary file not shown.

BIN
src/iconfont/iconfont.woff Normal file

Binary file not shown.

BIN
src/iconfont/iconfont.woff2 Normal file

Binary file not shown.

9
src/icons/index.js Normal file
View File

@ -0,0 +1,9 @@
import Vue from 'vue'
import SvgIcon from '@/components/svg-icon'// svg组件
// register globally
Vue.component('svg-icon', SvgIcon)
const requireAll = requireContext => requireContext.keys().map(requireContext)
const req = require.context('./svg', false, /\.svg$/)
requireAll(req)

1
src/icons/svg/button.svg Normal file
View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1615349620794" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="63464" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M912 176v416h-179.52v-32h147.52v-352h-736v352h175.488v32H112v-416z" fill="#646E7F" p-id="63465"></path><path d="M436.384 788.512l0.544 2.688a16 16 0 0 0 27.776 5.504l44.032-54.336 56.768 97.664a16 16 0 0 0 21.792 5.856l68.672-39.392 2.368-1.664a16 16 0 0 0 3.52-20.256l-55.904-96.16 68.8-12.064a16 16 0 0 0 6.464-28.8l-256-180.64a16 16 0 0 0-25.12 14.976l36.288 306.624z" fill="#00C296" p-id="63466"></path></svg>

After

Width:  |  Height:  |  Size: 791 B

View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1615359824945" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="66338" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M661.377376 411.069935V475.664516H314.175312v395.654882h557.144086V475.664516h-48.447312V411.069935h48.447312c35.674839 0 64.594581 28.919742 64.59458 64.594581v395.654882c0 35.674839-28.919742 64.594581-64.59458 64.59458h-557.144086c-35.674839 0-64.600086-28.919742-64.600086-64.59458V475.664516c0-35.674839 28.925247-64.594581 64.600086-64.594581h347.202064z m48.447312-322.983913c35.674839 0 64.600086 28.919742 64.600086 64.59458v403.731269c0 35.674839-28.925247 64.594581-64.600086 64.594581H362.622624v-64.594581h347.202064V152.680602h-557.144086v403.731269h48.447312V621.006452h-48.447312c-35.674839 0-64.594581-28.919742-64.59458-64.594581V152.680602C88.086022 117.005763 117.005763 88.086022 152.680602 88.086022h557.144086z" fill="#3A3A3A" p-id="66339"></path></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1615197394367" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="25735" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M897.940444 896.76927c6.258541-6.27696 10.256598-14.833847 10.256598-24.530696L908.197042 147.672294c0-9.118682-3.998057-18.235316-10.256598-24.533766l0 0c-6.27696-6.257517-14.815427-9.695826-24.511253-9.695826l0 0-723.784474 0 0 0c-9.68764 0-18.235316 3.437286-24.503067 9.695826l0 0c-6.26775 6.297426-9.686616 15.414061-9.686616 24.533766L115.455033 872.238574c0 9.69685 3.419889 18.253736 9.686616 24.530696 6.26775 6.277984 14.815427 10.276041 24.503067 10.276041l0 0 723.784474 0 0 0C883.126041 907.045311 891.663484 903.047254 897.940444 896.76927L897.940444 896.76927zM149.644717 61.521169l723.784474 0 0 0c23.933085 0 45.586245 9.69685 60.97984 25.110911 15.396665 15.97381 25.073048 37.665855 25.073048 61.039191L959.482079 872.238574c0 23.969924-9.676383 45.64355-25.073048 61.056588l0 0c-15.393595 15.395642-37.046754 25.092491-60.97984 25.092491l0 0-723.784474 0 0 0c-23.364127 0-45.016263-9.69685-60.971653-25.092491l0 0c-15.395642-15.414061-25.082258-37.086663-25.082258-61.056588L63.590805 147.672294c0-23.37436 9.686616-45.065382 25.082258-61.039191l0 0C104.628454 71.218018 126.28059 61.521169 149.644717 61.521169L149.644717 61.521169z" p-id="25736"></path><path d="M417.41939 698.269357c-6.025227 0-12.047384-2.301416-16.667611-6.89913L259.500731 550.119179c-9.172917-9.148357-9.172917-24.093744 0-33.290197 9.169847-9.147334 24.115234-9.147334 33.288151 0l124.583436 124.606972 312.89429-312.916802c9.194406-9.171893 24.139793-9.171893 33.288151 0 9.196453 9.171893 9.196453 24.116257 0 33.289174L433.991834 691.370227c-4.618181 4.644787-10.642384 6.89913-16.665565 6.89913L417.41939 698.269357z" p-id="25737"></path></svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1615349378669" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="52731" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M619.52 490.666667h-0.853333l-85.333334-85.333334h0.853334z" p-id="52732"></path><path d="M619.52 507.733333h-0.853333a17.066667 17.066667 0 1 1 0-34.133333c9.437867 0 17.476267 7.645867 17.476266 17.066667s-7.185067 17.066667-16.622933 17.066666z m-85.333333-85.333333c-9.437867 0-17.493333-7.645867-17.493334-17.066667s7.202133-17.066667 16.64-17.066666h0.853334a17.066667 17.066667 0 1 1 0 34.133333zM192 866.133333a34.133333 34.133333 0 0 1-24.132267-58.2656l42.666667-42.666666a34.133333 34.133333 0 1 1 48.264533 48.264533l-42.666666 42.666667a33.962667 33.962667 0 0 1-24.132267 10.001066z" p-id="52733"></path><path d="M619.52 490.666667l-0.426667-0.426667L534.186667 405.333333l212.48-213.333333h85.333333v85.333333z" p-id="52734"></path><path d="M662.186667 567.466667a33.9968 33.9968 0 0 1-24.132267-10.001067l-170.666667-170.666667a34.133333 34.133333 0 0 1 48.2816-48.2816l170.666667 170.666667A34.133333 34.133333 0 0 1 662.186667 567.466667z" p-id="52735"></path><path d="M320 806.4h-85.333333a17.066667 17.066667 0 0 1-17.066667-17.066667v-85.333333c0-4.539733 1.792-8.874667 5.000533-12.066133l299.093334-299.093334a16.520533 16.520533 0 0 1 3.566933-2.730666l209.3056-210.141867c3.208533-3.208533 7.560533-5.0176 12.100267-5.0176h85.333333a17.066667 17.066667 0 0 1 17.066667 17.066667v85.333333a17.066667 17.066667 0 0 1-4.9664 12.049067l-212.48 213.333333a17.954133 17.954133 0 0 1-3.618134 2.781867l-295.936 295.918933a17.134933 17.134933 0 0 1-12.066133 4.9664z m-68.266667-34.133333h61.201067l294.0928-294.0928a16.520533 16.520533 0 0 1 3.566933-2.730667L814.933333 270.2848V209.066667h-61.184L546.286933 417.3824a17.954133 17.954133 0 0 1-3.618133 2.781867L251.733333 711.0656v61.201067z" p-id="52736"></path></svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1615359878096" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="68659" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M811.008 335.872c-2.048-7.168-11.264-9.216-17.408-4.096L690.176 435.2c-8.192 8.192-20.48 8.192-28.672 0l-72.704-72.704c-8.192-8.192-8.192-20.48 0-28.672l104.448-104.448c5.12-5.12 3.072-14.336-4.096-17.408-17.408-4.096-35.84-7.168-54.272-7.168-108.544 0-195.584 94.208-183.296 204.8 2.048 17.408 6.144 32.768 12.288 48.128L225.28 697.344c-27.648 27.648-27.648 73.728 0 101.376 14.336 14.336 32.768 21.504 51.2 21.504s36.864-7.168 51.2-21.504l238.592-238.592c15.36 6.144 31.744 10.24 48.128 12.288 111.616 12.288 204.8-74.752 204.8-183.296 0-18.432-3.072-36.864-8.192-53.248z" p-id="68660"></path></svg>

After

Width:  |  Height:  |  Size: 979 B

View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1615349120101" class="icon" viewBox="0 0 1132 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="47755" xmlns:xlink="http://www.w3.org/1999/xlink" width="221.09375" height="200"><defs><style type="text/css"></style></defs><path d="M1023.99488 1023.99488C1023.99488 1023.99488 107.788935 1023.99488 107.788935 1023.99488 48.262496 1023.99488 0 975.732384 0 916.205945 0 916.205945 0 350.314037 0 350.314037 0 350.314037 0 296.419571 0 296.419571 0 296.419571 0 188.630636 0 188.630636 0 129.104196 48.262496 80.841701 107.788935 80.841701 107.788935 80.841701 188.630636 80.841701 188.630636 80.841701 188.630636 80.841701 188.630636 134.736168 188.630636 134.736168 188.630636 134.736168 107.788935 134.736168 107.788935 134.736168 78.012242 134.736168 53.894468 158.853943 53.894468 188.630636 53.894468 188.630636 53.894468 296.419571 53.894468 296.419571 53.894468 296.419571 1077.889348 296.419571 1077.889348 296.419571 1077.889348 296.419571 1077.889348 188.630636 1077.889348 188.630636 1077.889348 158.853943 1053.771573 134.736168 1023.99488 134.736168 1023.99488 134.736168 943.153179 134.736168 943.153179 134.736168 943.153179 134.736168 943.153179 80.841701 943.153179 80.841701 943.153179 80.841701 1023.99488 80.841701 1023.99488 80.841701 1083.521319 80.841701 1131.783815 129.104196 1131.783815 188.630636 1131.783815 188.630636 1131.783815 296.419571 1131.783815 296.419571 1131.783815 296.419571 1131.783815 350.314037 1131.783815 350.314037 1131.783815 350.314037 1131.783815 916.205945 1131.783815 916.205945 1131.783815 975.732384 1083.521319 1023.99488 1023.99488 1023.99488ZM1077.889348 350.314037C1077.889348 350.314037 53.894468 350.314037 53.894468 350.314037 53.894468 350.314037 53.894468 916.205945 53.894468 916.205945 53.894468 945.982638 78.012242 970.100412 107.788935 970.100412 107.788935 970.100412 1023.99488 970.100412 1023.99488 970.100412 1053.771573 970.100412 1077.889348 945.982638 1077.889348 916.205945 1077.889348 916.205945 1077.889348 350.314037 1077.889348 350.314037ZM794.943393 628.086123C794.943393 628.086123 791.359411 633.718094 791.359411 633.718094 788.044902 640.023747 784.27229 645.871297 780.122415 651.449375 780.122415 651.449375 646.733608 861.476114 646.733608 861.476114 646.733608 861.476114 589.120422 861.476114 589.120422 861.476114 589.120422 861.476114 687.477826 700.601129 687.477826 700.601129 687.370037 700.601129 687.262248 700.628076 687.154459 700.628076 620.190583 700.628076 565.891908 640.29322 565.891908 565.891908 565.891908 491.490596 620.190583 431.155739 687.154459 431.155739 754.118334 431.155739 808.417011 491.490596 808.417011 565.891908 808.417011 588.312005 803.027564 609.142217 794.296661 627.735808 794.296661 627.735808 794.943393 628.086123 794.943393 628.086123ZM687.154459 485.050207C649.940329 485.050207 619.786375 521.240341 619.786375 565.891908 619.786375 610.543473 649.940329 646.733608 687.154459 646.733608 706.206153 646.733608 723.317646 637.16734 735.578638 621.915206 735.578638 621.915206 745.360484 605.908549 745.360484 605.908549 751.019403 594.078714 754.522543 580.524255 754.522543 565.891908 754.522543 521.240341 724.368589 485.050207 687.154459 485.050207ZM376.937904 506.93136C376.937904 506.93136 296.203993 579.257736 296.203993 579.257736 296.203993 579.257736 296.203993 512.266913 296.203993 512.266913 296.203993 512.266913 378.608633 431.856367 378.608633 431.856367 378.608633 431.856367 430.670689 431.856367 430.670689 431.856367 430.670689 431.856367 430.670689 861.583903 430.670689 861.583903 430.670689 861.583903 376.937904 861.583903 376.937904 861.583903 376.937904 861.583903 376.937904 506.93136 376.937904 506.93136ZM889.258712 215.577869C874.383839 215.577869 862.311477 203.505508 862.311477 188.630636 862.311477 188.630636 862.311477 26.947233 862.311477 26.947233 862.311477 12.072361 874.383839 0 889.258712 0 904.133584 0 916.205945 12.072361 916.205945 26.947233 916.205945 26.947233 916.205945 188.630636 916.205945 188.630636 916.205945 203.505508 904.133584 215.577869 889.258712 215.577869ZM296.419571 80.841701C296.419571 80.841701 835.364244 80.841701 835.364244 80.841701 835.364244 80.841701 835.364244 134.736168 835.364244 134.736168 835.364244 134.736168 296.419571 134.736168 296.419571 134.736168 296.419571 134.736168 296.419571 80.841701 296.419571 80.841701ZM242.525103 215.577869C227.65023 215.577869 215.577869 203.505508 215.577869 188.630636 215.577869 188.630636 215.577869 26.947233 215.577869 26.947233 215.577869 12.072361 227.65023 0 242.525103 0 257.399976 0 269.472337 12.072361 269.472337 26.947233 269.472337 26.947233 269.472337 188.630636 269.472337 188.630636 269.472337 203.505508 257.399976 215.577869 242.525103 215.577869Z" p-id="47756"></path></svg>

After

Width:  |  Height:  |  Size: 4.8 KiB

View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1615349000433" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="46573" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M887.467 192.853H786.773v-73.386c0-10.24-6.826-17.067-17.066-17.067s-17.067 6.827-17.067 17.067v73.386H303.787v-73.386c0-10.24-6.827-17.067-17.067-17.067s-17.067 6.827-17.067 17.067v73.386H168.96c-46.08 0-85.333 37.547-85.333 85.334v558.08c0 46.08 37.546 85.333 85.333 85.333h718.507c46.08 0 85.333-37.547 85.333-85.333v-558.08c0-47.787-37.547-85.334-85.333-85.334zM168.96 226.987h100.693v66.56c0 10.24 6.827 17.066 17.067 17.066s17.067-6.826 17.067-17.066v-66.56h450.56v66.56c0 10.24 6.826 17.066 17.066 17.066s17.067-6.826 17.067-17.066v-66.56h98.987c27.306 0 51.2 22.186 51.2 51.2v88.746H117.76v-88.746c0-29.014 22.187-51.2 51.2-51.2z m718.507 660.48H168.96c-27.307 0-51.2-22.187-51.2-51.2v-435.2h820.907v435.2c0 27.306-22.187 51.2-51.2 51.2z" p-id="46574"></path><path d="M858.453 493.227H327.68c-10.24 0-17.067 6.826-17.067 17.066V624.64H194.56c-10.24 0-17.067 6.827-17.067 17.067v133.12c0 10.24 6.827 17.066 17.067 17.066H460.8c10.24 0 17.067-6.826 17.067-17.066V660.48h380.586c10.24 0 17.067-6.827 17.067-17.067v-133.12c0-10.24-6.827-17.066-17.067-17.066zM445.44 527.36v97.28h-98.987v-97.28h98.987z m-230.4 131.413h98.987v98.987H215.04v-98.987z m131.413 97.28v-97.28h98.987v97.28h-98.987z m133.12-228.693h97.28v98.987h-97.28V527.36z m131.414 0h98.986v98.987h-98.986V527.36z m230.4 97.28H742.4v-98.987h98.987v98.987z" p-id="46575"></path></svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1615349638162" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="63666" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M62.5 491.773h899v74.918h-899v-74.918z" p-id="63667"></path></svg>

After

Width:  |  Height:  |  Size: 444 B

View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1609379540538" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5454" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M979.478261 706.381913a44.521739 44.521739 0 0 1-11.842783 57.967304l-4.36313 2.849392-393.794783 227.862261-5.38713 4.763826a84.012522 84.012522 0 0 1-43.942957 17.808695l-8.102956 0.400696c-19.055304 0-37.309217-6.544696-52.045913-18.253913l-5.431652-4.719304-393.750261-227.862261a44.521739 44.521739 0 0 1-18.610087-56.186435l2.359652-4.630261a44.521739 44.521739 0 0 1 60.816696-16.250435l405.325913 234.540522 1.335652 1.513739 1.335652-1.513739 405.281391-234.540522a44.521739 44.521739 0 0 1 60.861218 16.250435z m0-222.608696a44.521739 44.521739 0 0 1-11.842783 57.967305l-4.36313 2.849391-393.794783 227.862261-5.38713 4.763826a84.012522 84.012522 0 0 1-43.942957 17.808696l-8.102956 0.400695c-19.055304 0-37.309217-6.544696-52.045913-18.253913l-5.431652-4.719304-393.750261-227.862261a44.521739 44.521739 0 0 1-18.610087-56.186435l2.359652-4.630261a44.521739 44.521739 0 0 1 60.816696-16.250434l405.325913 234.540521 1.335652 1.513739 1.335652-1.513739 405.281391-234.540521a44.521739 44.521739 0 0 1 60.861218 16.250434zM512 0c18.788174 0 36.864 6.099478 51.645217 17.185391l4.58574 3.739826 403.500521 199.68 5.609739 6.144c32.50087 35.439304 32.50087 89.889391 0 125.328696l-5.609739 6.144-403.500521 199.590957-4.541218 3.784347a86.238609 86.238609 0 0 1-43.675826 16.829218L512 578.782609c-18.788174 0-36.864-6.099478-51.645217-17.185392l-4.630261-3.784347L52.268522 358.221913l-5.609739-6.144a92.738783 92.738783 0 0 1 0-125.328696l5.609739-6.144L455.724522 20.925217l4.585739-3.739826c12.688696-9.48313 27.826087-15.315478 43.675826-16.829217z" fill="#008df0" p-id="5455"></path></svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

1
src/icons/svg/drag.svg Normal file
View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1615186378215" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="6349" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M574.958 267.016h-63.454 204.649L511.213 63.655 307.85 267.016h141.191V456.68H258.688v125.917H449.04V772.95h125.917V582.596h188.875V456.679H574.958V267.016z m-63.704 693.33l189.62-187.396H323.126l188.129 187.395zM71.292 518.891l187.395 189.62v-377.75L71.292 518.892z m692.54-188.13v377.75L952.708 518.89 763.833 330.762z" p-id="6350"></path></svg>

After

Width:  |  Height:  |  Size: 724 B

View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1615359776308" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="65360" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M676.48 72.96l209.92 211.2 9.6 23.04v620.8l-32 32h-704l-32-32v-832l32-32h494.08l22.4 8.96zM640 320h192l-192-192v192zM192 128v768h640V384H608L576 352V128H192z m512 320H320v64h384V448zM320 576h384v64H320V576z m384 128H320v64h384v-64z" p-id="65361"></path></svg>

After

Width:  |  Height:  |  Size: 637 B

1
src/icons/svg/github.svg Normal file
View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1609377351479" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2490" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M512 0C229.283787 0 0.142041 234.942803 0.142041 524.867683c0 231.829001 146.647305 428.553077 350.068189 497.952484 25.592898 4.819996 34.976961-11.38884 34.976961-25.294314 0-12.45521-0.469203-45.470049-0.725133-89.276559-142.381822 31.735193-172.453477-70.380469-172.453477-70.380469-23.246882-60.569859-56.816233-76.693384-56.816234-76.693385-46.493765-32.58829 3.540351-31.948468 3.540351-31.948467 51.356415 3.71097 78.356923 54.086324 78.356923 54.086324 45.683323 80.19108 119.817417 57.072162 148.993321 43.593236 4.649376-33.91059 17.915029-57.029508 32.50298-70.167195-113.675122-13.222997-233.151301-58.223843-233.1513-259.341366 0-57.285437 19.919806-104.163095 52.678715-140.846248-5.246544-13.265652-22.820334-66.626844 4.990615-138.884127 0 0 42.996069-14.076094 140.760939 53.787741 40.863327-11.644769 84.627183-17.445825 128.177764-17.6591 43.465272 0.213274 87.271782 6.014331 128.135109 17.6591 97.679561-67.906489 140.59032-53.787741 140.59032-53.787741 27.938914 72.257282 10.407779 125.618474 5.118579 138.884127 32.844219 36.683154 52.593405 83.560812 52.593405 140.846248 0 201.586726-119.646798 245.990404-233.663158 258.957473 18.341577 16.208835 34.721032 48.199958 34.721032 97.210357 0 70.167195-0.639822 126.7275-0.639823 143.960051 0 14.033439 9.213443 30.370239 35.190235 25.209005 203.250265-69.527373 349.769606-266.123484 349.769605-497.867175C1023.857959 234.942803 794.673558 0 512 0" fill="#3E75C3" p-id="2491"></path></svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

1
src/icons/svg/grid.svg Normal file
View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1615196195559" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="8472" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M819.3536 921.6h102.4v-102.4h-102.4v102.4z m102.4-204.8h-102.4a102.4 102.4 0 0 0-102.4 102.4v102.4a102.4 102.4 0 0 0 102.4 102.4h102.4a102.4 102.4 0 0 0 102.4-102.4v-102.4a102.4 102.4 0 0 0-102.4-102.4z m-460.8 204.8h102.4v-102.4h-102.4v102.4z m102.4-204.8h-102.4a102.4 102.4 0 0 0-102.4 102.4v102.4a102.4 102.4 0 0 0 102.4 102.4h102.4a102.4 102.4 0 0 0 102.4-102.4v-102.4a102.4 102.4 0 0 0-102.4-102.4z m-460.8 204.8h102.4v-102.4h-102.4v102.4z m102.4-204.8h-102.4a102.4 102.4 0 0 0-102.4 102.4v102.4a102.4 102.4 0 0 0 102.4 102.4h102.4a102.4 102.4 0 0 0 102.4-102.4v-102.4a102.4 102.4 0 0 0-102.4-102.4z m614.4-153.6h102.4V460.8h-102.4v102.4z m102.4-204.8h-102.4a102.4 102.4 0 0 0-102.4 102.4v102.4a102.4 102.4 0 0 0 102.4 102.4h102.4a102.4 102.4 0 0 0 102.4-102.4V460.8a102.4 102.4 0 0 0-102.4-102.4z m-460.8 204.8h102.4V460.8h-102.4v102.4z m102.4-204.8h-102.4a102.4 102.4 0 0 0-102.4 102.4v102.4a102.4 102.4 0 0 0 102.4 102.4h102.4a102.4 102.4 0 0 0 102.4-102.4V460.8a102.4 102.4 0 0 0-102.4-102.4z m-460.8 204.8h102.4V460.8h-102.4v102.4z m102.4-204.8h-102.4a102.4 102.4 0 0 0-102.4 102.4v102.4a102.4 102.4 0 0 0 102.4 102.4h102.4a102.4 102.4 0 0 0 102.4-102.4V460.8a102.4 102.4 0 0 0-102.4-102.4z m614.4-153.6h102.4V102.4h-102.4v102.4z m102.4-204.8h-102.4a102.4 102.4 0 0 0-102.4 102.4v102.4a102.4 102.4 0 0 0 102.4 102.4h102.4a102.4 102.4 0 0 0 102.4-102.4V102.4a102.4 102.4 0 0 0-102.4-102.4z m-460.8 204.8h102.4V102.4h-102.4v102.4z m102.4-204.8h-102.4a102.4 102.4 0 0 0-102.4 102.4v102.4a102.4 102.4 0 0 0 102.4 102.4h102.4a102.4 102.4 0 0 0 102.4-102.4V102.4a102.4 102.4 0 0 0-102.4-102.4z m-460.8 204.8h102.4V102.4h-102.4v102.4z m102.4-204.8h-102.4a102.4 102.4 0 0 0-102.4 102.4v102.4a102.4 102.4 0 0 0 102.4 102.4h102.4a102.4 102.4 0 0 0 102.4-102.4V102.4a102.4 102.4 0 0 0-102.4-102.4z" p-id="8473"></path></svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1615349493614" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="56263" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M137.6 512l204.8-204.8c12.8-12.8 12.8-32 0-44.8-12.8-12.8-32-12.8-44.8 0L70.4 489.6c-6.4 6.4-9.6 12.8-9.6 22.4 0 9.6 3.2 16 9.6 22.4l227.2 227.2c12.8 12.8 32 12.8 44.8 0 12.8-12.8 12.8-32 0-44.8L137.6 512z m464-339.2c-16-3.2-35.2 6.4-38.4 22.4L396.8 812.8c-3.2 16 6.4 35.2 22.4 38.4 16 3.2 35.2-6.4 38.4-22.4L624 211.2c6.4-16-3.2-35.2-22.4-38.4z m352 316.8L726.4 262.4c-12.8-12.8-32-12.8-44.8 0-12.8 12.8-12.8 32 0 44.8L886.4 512 681.6 716.8c-12.8 12.8-12.8 32 0 44.8 12.8 12.8 32 12.8 44.8 0l227.2-227.2c6.4-6.4 9.6-16 9.6-22.4 0-9.6-3.2-16-9.6-22.4z" fill="#333333" p-id="56264"></path></svg>

After

Width:  |  Height:  |  Size: 972 B

View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1615197023867" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="24161" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M960 1024H64a64 64 0 0 1-64-64V64a64 64 0 0 1 64-64h896a64 64 0 0 1 64 64v896a64 64 0 0 1-64 64z m0-896a64 64 0 0 0-64-64H128a64 64 0 0 0-64 64v768a64 64 0 0 0 64 64h768a64 64 0 0 0 64-64V128z m-128 640h-128a64 64 0 0 1 0-128h64V576h-64a64 64 0 0 1 0-128h64V384h-64a64 64 0 0 1 0-128h128a64 64 0 0 1 64 64v384a64 64 0 0 1-64 64z m-320-128a64 64 0 0 1 0 128H384a64 64 0 0 1-64-64V512a64 64 0 0 1 64-64h64V384H384a64 64 0 0 1 0-128h128a64 64 0 0 1 64 64v192a64 64 0 0 1-64 64H448v64h64z m-320 128a64 64 0 0 1-64-64V320a64 64 0 0 1 128 0v384a64 64 0 0 1-64 64z" p-id="24162"></path></svg>

After

Width:  |  Height:  |  Size: 963 B

View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1615359727968" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="64597" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M896 1024 128 1024c-70.688 0-128-57.312-128-128L0 128c0-70.688 57.312-128 128-128l768 0c70.688 0 128 57.312 128 128l0 768C1024 966.688 966.688 1024 896 1024zM896 960c35.328 0 64-28.672 64-64l0-256.032-192-192-273.184 273.152L730.624 960 896 960zM64 896c0 35.328 28.672 64 64 64l512.032 0L318.24 638.208 64 865.952 64 896zM960 128c0-35.328-28.672-64-64-64L128 64C92.672 64 64 92.672 64 128l0 650.752L320 544l129.856 131.552L768 352l192 196.096L960 128zM256 384c-70.688 0-128-57.312-128-128s57.312-128 128-128 128 57.312 128 128S326.688 384 256 384zM256 192C220.672 192 192 220.672 192 256s28.672 64 64 64 64-28.672 64-64S291.328 192 256 192z" p-id="64598"></path></svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1615197291815" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="24992" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M512 65.983389c-245.919634 0-446.016611 200.095256-446.016611 446.016611 0 245.952318 200.064292 446.016611 446.016611 446.016611S958.016611 757.952318 958.016611 512C958.016611 266.080366 757.952318 65.983389 512 65.983389zM512 894.016611c-210.655557 0-382.016611-171.392017-382.016611-382.016611 0-210.655557 171.359333-382.016611 382.016611-382.016611 210.624593 0 382.016611 171.359333 382.016611 382.016611C894.016611 722.624593 722.624593 894.016611 512 894.016611zM512 352.00086c-88.223841 0-160.00086 71.775299-160.00086 159.99914s71.775299 160.00086 160.00086 160.00086 160.00086-71.775299 160.00086-160.00086S600.223841 352.00086 512 352.00086z" p-id="24993"></path></svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1615349207874" class="icon" viewBox="0 0 1069 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="49626" xmlns:xlink="http://www.w3.org/1999/xlink" width="208.7890625" height="200"><defs><style type="text/css"></style></defs><path d="M633.73 378.02l9.498 18.688 20.78 2.798 206.616 27.332a11.465 11.465 0 0 1 6.61 19.473L729.966 593.665l-14.893 14.893 3.8 20.683 37.847 204.89a11.465 11.465 0 0 1-16.481 12.296l-185.55-94.58-18.687-9.493-18.487 9.992-183.24 99.35a11.465 11.465 0 0 1-16.784-11.867l32.543-205.796 3.297-20.786-15.192-14.492-151.033-143.484a11.465 11.465 0 0 1 6.1-19.64L399 402.998l20.786-3.296 9.092-18.98 89.713-188.078a11.465 11.465 0 0 1 20.569-0.263l94.568 185.635zM496.647 85.52L374.89 340.501l-279.126 44.26a34.395 34.395 0 0 0-18.303 58.908l204.873 194.663-44.169 279.115a34.395 34.395 0 0 0 50.366 35.616l248.4-134.679L788.776 946.66a34.395 34.395 0 0 0 49.437-36.894l-51.306-277.854 199.731-199.909a34.395 34.395 0 0 0-19.828-58.408l-280.118-37.032L558.33 84.713a34.395 34.395 0 0 0-61.682 0.802z" p-id="49627"></path></svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

1
src/icons/svg/redo.svg Normal file
View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1615176655087" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3521" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M412.081 346.3h443.415l-215.328-212.429c-18.973-18.973-18.973-46.064 0-65.038s44.325-19.884 63.381-0.83l291.385 284.591c18.973 18.973 18.973 44.159 0 63.132l-291.385 284.923c-18.973 18.973-44.325 18.973-63.381-0.083-18.973-18.973-18.973-43.91 0-62.883l215.328-208.534h-443.415c-177.3 0-314.335 138.359-314.335 309.364v44.325c0 25.354-16.074 44.325-41.425 44.325s-41.425-18.973-41.425-44.325v-44.325c0-221.709 169.181-392.213 397.185-392.213z" p-id="3522"></path></svg>

After

Width:  |  Height:  |  Size: 845 B

View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1615359803051" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="66102" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M313.359894 448.093505l319.271534 0 0 31.927153-319.271534 0 0-31.927153Z" p-id="66103"></path><path d="M313.359894 583.783907l223.490074 0 0 31.927153-223.490074 0 0-31.927153Z" p-id="66104"></path><path d="M313.359894 719.474308l127.708614 0 0 31.927153-127.708614 0 0-31.927153Z" p-id="66105"></path><path d="M889.411699 554.808992l-39.954991-39.971363-39.957037-39.940664c-7.357572-7.357572-19.284205-7.357572-26.641777 0L453.157847 804.590871c-2.113127 1.730411-3.749394 4.068665-4.591575 6.873548l-36.488029 121.394814c-1.544169 5.160533-0.016373 10.523681 3.539616 14.078647l0.989537 0.763386 0.77055 0.981351c3.554966 3.554966 8.902764 5.082762 14.062274 3.554966l121.394814-36.494169c2.844791-0.857531 5.214768-2.52552 6.938016-4.677533l329.638649-329.630463C896.761084 574.093197 896.761084 562.152237 889.411699 554.808992zM541.135574 889.75553l-95.197152 28.622898 28.622898-95.235015 255.020184-255.020184 66.598814 66.598814L541.135574 889.75553zM856.112292 574.779835l-46.611597 46.611597-66.590628-66.598814 46.605457-46.596248c3.677762-3.679809 9.641591-3.679809 13.319353-0.016373l26.892487 26.892487 26.383904 26.393113C859.791078 565.145407 859.791078 571.116399 856.112292 574.779835z" p-id="66106"></path><path d="M671.874197 224.898143l0-28.933983c0-22.004153-17.904789-39.908942-39.908942-39.908942L314.026066 156.055219c-22.004153 0-39.908942 17.904789-39.908942 39.908942l0 28.933983L169.687704 224.898143l0 643.563408c0 35.261085 28.591175 63.854307 63.854307 63.854307l127.708614 0 0-47.89073-111.745037 0c-17.631566 0-31.927153-14.297634-31.927153-31.927153L217.578434 272.788873l56.538691 0 0 10.974959c0 22.004153 17.904789 39.908942 39.908942 39.908942l317.938166 0c22.004153 0 39.908942-17.904789 39.908942-39.908942l0-10.974959 56.538691 0 0 164.662247 47.89073 0L776.302595 224.898143 671.874197 224.898143zM623.983467 275.782044 322.007855 275.782044l0-71.836095 301.974589 0L623.982444 275.782044z" p-id="66107"></path></svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1615196523892" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="11328" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M141.074286 906.496h741.851428c89.581714 0 134.582857-44.562286 134.582857-132.845714V250.331429c0-88.283429-45.001143-132.845714-134.582857-132.845715H141.074286C51.931429 117.504 6.491429 161.645714 6.491429 250.331429V773.668571c0 88.704 45.44 132.845714 134.582857 132.845715z m1.28-68.992c-42.861714 0-66.852571-22.710857-66.852572-67.291429V253.805714c0-44.580571 23.990857-67.291429 66.852572-67.291428h739.291428c42.422857 0 66.852571 22.710857 66.852572 67.291428V770.194286c0 44.580571-24.429714 67.291429-66.852572 67.291428z" p-id="11329"></path></svg>

After

Width:  |  Height:  |  Size: 942 B

View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1615197848232" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="40340" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M374.784 649.514667a32 32 0 0 1 3.072 41.685333l-3.114667 3.584L225.28 843.946667a32 32 0 0 1-37.845333 5.504l-3.968-2.56-85.333334-64a32 32 0 0 1 34.432-53.76l3.968 2.56 63.146667 47.36 129.834667-129.621334a32 32 0 0 1 45.269333 0.042667zM906.154667 725.333333a32 32 0 0 1 4.309333 63.701334l-4.309333 0.298666h-448a32 32 0 0 1-4.352-63.744l4.352-0.256h448z m0.512-256a32 32 0 0 1 4.352 63.701334l-4.352 0.298666h-448a32 32 0 0 1-4.352-63.701333L458.666667 469.333333h448zM374.826667 137.557333a32 32 0 0 1 2.986666 41.685334l-3.114666 3.584L224.853333 332.032a32 32 0 0 1-37.888 5.418667l-3.925333-2.56-84.906667-64a32 32 0 0 1 34.517334-53.674667l3.968 2.56 62.72 47.274667 130.261333-129.578667a32 32 0 0 1 45.226667 0.085333z m531.328 75.818667a32 32 0 0 1 4.309333 63.701333l-4.309333 0.298667H459.349333a32 32 0 0 1-4.352-63.744l4.352-0.256h446.805334z" p-id="40341"></path></svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1615349403933" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="53483" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M951.453 476.844H523.672a131.836 131.836 0 0 0-254.18 0H72.547v70.312h196.945a131.836 131.836 0 0 0 254.18 0h427.781z" p-id="53484"></path></svg>

After

Width:  |  Height:  |  Size: 523 B

View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1615359992022" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="71129" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M512 102.4c-212.48 0-384 171.52-384 384s171.52 384 384 384 384-171.52 384-384-171.52-384-384-384z m25.6 716.8v-128c0-15.36-10.24-25.6-25.6-25.6s-25.6 10.24-25.6 25.6v128C322.56 806.4 192 675.84 179.2 512h128c15.36 0 25.6-10.24 25.6-25.6s-10.24-25.6-25.6-25.6h-128C192 296.96 322.56 166.4 486.4 156.16V281.6c0 15.36 10.24 25.6 25.6 25.6s25.6-10.24 25.6-25.6V156.16C701.44 168.96 832 299.52 844.8 460.8h-128c-15.36 0-25.6 10.24-25.6 25.6s10.24 25.6 25.6 25.6h128C832 675.84 701.44 806.4 537.6 819.2z" fill="#333333" p-id="71130"></path></svg>

After

Width:  |  Height:  |  Size: 918 B

View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1615349457830" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="55446" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M213.333333 160c-4.821333 0-9.472 0.64-13.824 1.792a32 32 0 0 1-16.554666-61.824c9.728-2.56 19.925333-3.968 30.378666-3.968h33.194667a32 32 0 0 1 0 64H213.333333zM347.264 128a32 32 0 0 1 32-32h66.346667a32 32 0 1 1 0 64H379.306667a32 32 0 0 1-32-32z m199.125333 0a32 32 0 0 1 32-32h66.346667a32 32 0 0 1 0 64h-66.346667a32 32 0 0 1-32-32z m199.082667 0a32 32 0 0 1 32-32H810.666667c10.453333 0 20.650667 1.365333 30.378666 3.968a32 32 0 1 1-16.554666 61.866667A53.418667 53.418667 0 0 0 810.666667 160h-33.194667a32 32 0 0 1-32-32z m-606.293333 32.341333a32 32 0 0 1 22.613333 39.168A53.461333 53.461333 0 0 0 160 213.333333v33.194667a32 32 0 0 1-64 0V213.333333c0-10.453333 1.365333-20.650667 3.968-30.378666a32 32 0 0 1 39.168-22.613334z m745.685333 0a32 32 0 0 1 39.168 22.613334c2.56 9.728 3.968 19.925333 3.968 30.378666v33.194667a32 32 0 0 1-64 0V213.333333c0-4.821333-0.64-9.472-1.792-13.824a32 32 0 0 1 22.613333-39.168zM128 347.221333a32 32 0 0 1 32 32v66.389334a32 32 0 1 1-64 0V379.306667a32 32 0 0 1 32-32z m768 0a32 32 0 0 1 32 32v66.389334a32 32 0 1 1-64 0V379.306667a32 32 0 0 1 32-32zM128 546.432a32 32 0 0 1 32 32v66.346667a32 32 0 0 1-64 0v-66.346667a32 32 0 0 1 32-32z m768 0a32 32 0 0 1 32 32v66.346667a32 32 0 0 1-64 0v-66.346667a32 32 0 0 1 32-32z m0 199.082667a32 32 0 0 1 32 32V810.666667c0 10.453333-1.365333 20.650667-3.968 30.378666a32 32 0 1 1-61.866667-16.554666c1.194667-4.352 1.834667-8.96 1.834667-13.824v-33.194667a32 32 0 0 1 32-32z m-768 0a32 32 0 0 1 32 32V810.666667c0 4.821333 0.64 9.472 1.792 13.824a32 32 0 0 1-61.824 16.512A117.461333 117.461333 0 0 1 96 810.666667v-33.194667a32 32 0 0 1 32-32z m32.341333 139.392a32 32 0 0 1 39.168-22.656c4.352 1.152 8.96 1.792 13.824 1.792h33.194667a32 32 0 0 1 0 64H213.333333c-10.453333 0-20.650667-1.365333-30.378666-3.968a32 32 0 0 1-22.613334-39.168z m703.317334 0a32 32 0 0 1-22.613334 39.168c-9.728 2.56-19.925333 3.968-30.378666 3.968h-33.194667a32 32 0 0 1 0-64H810.666667c4.821333 0 9.472-0.64 13.824-1.792a32 32 0 0 1 39.168 22.613333zM347.306667 896a32 32 0 0 1 32-32h66.346666a32 32 0 1 1 0 64H379.306667a32 32 0 0 1-32-32z m199.125333 0a32 32 0 0 1 32-32h66.346667a32 32 0 0 1 0 64h-66.346667a32 32 0 0 1-32-32zM341.333333 352a32 32 0 0 0 0 64h138.666667V682.666667a32 32 0 0 0 64 0V416H682.666667a32 32 0 0 0 0-64H341.333333z" p-id="55447"></path></svg>

After

Width:  |  Height:  |  Size: 2.7 KiB

View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1616146134710" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="97354" xmlns:xlink="http://www.w3.org/1999/xlink" width="300" height="300"><defs><style type="text/css"></style></defs><path d="M512 106.666667H112a5.333333 5.333333 0 0 0-5.333333 5.333333v800a5.333333 5.333333 0 0 0 5.333333 5.333333h800a5.333333 5.333333 0 0 0 5.333333-5.333333V112a5.333333 5.333333 0 0 0-5.333333-5.333333z m0 74.666666h325.333333a5.333333 5.333333 0 0 1 5.333334 5.333334v160a5.333333 5.333333 0 0 1-5.333334 5.333333H186.666667a5.333333 5.333333 0 0 1-5.333334-5.333333V186.666667a5.333333 5.333333 0 0 1 5.333334-5.333334z m85.333333 250.666667v405.333333a5.333333 5.333333 0 0 1-5.333333 5.333334H432a5.333333 5.333333 0 0 1-5.333333-5.333334V432a5.333333 5.333333 0 0 1 5.333333-5.333333h160a5.333333 5.333333 0 0 1 5.333333 5.333333z m-410.666666-5.333333h160a5.333333 5.333333 0 0 1 5.333333 5.333333v405.333333a5.333333 5.333333 0 0 1-5.333333 5.333334H186.666667a5.333333 5.333333 0 0 1-5.333334-5.333334V432a5.333333 5.333333 0 0 1 5.333334-5.333333z m485.333333 410.666666V432a5.333333 5.333333 0 0 1 5.333333-5.333333h160a5.333333 5.333333 0 0 1 5.333334 5.333333v405.333333a5.333333 5.333333 0 0 1-5.333334 5.333334h-160a5.333333 5.333333 0 0 1-5.333333-5.333334z" p-id="97355"></path></svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1615349165848" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="48563" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M692 792H332C182 792 62 672 62 522s120-270 270-270h360c150 0 270 120 270 270 0 147-120 270-270 270zM332 312c-117 0-210 93-210 210s93 210 210 210h360c117 0 210-93 210-210s-93-210-210-210H332z" p-id="48564"></path><path d="M191 522a150 150 0 1 0 300 0 150 150 0 1 0-300 0z" p-id="48565"></path></svg>

After

Width:  |  Height:  |  Size: 676 B

1
src/icons/svg/tab.svg Normal file
View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1615196217184" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="9209" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M908.8 1005.44H115.2a101.76 101.76 0 0 1-101.12-101.76V110.72A101.76 101.76 0 0 1 115.2 8.96h296.96a32.64 32.64 0 0 1 32 32V262.4a32 32 0 0 1-32 32 32 32 0 0 1-32-32v-192H115.2a37.76 37.76 0 0 0-37.12 37.76v795.52a37.76 37.76 0 0 0 37.12 37.76h793.6a37.76 37.76 0 0 0 37.12-37.76V267.52a32 32 0 0 1 32-32 32 32 0 0 1 32 32v636.16a101.76 101.76 0 0 1-101.12 101.76z" fill="#323333" p-id="9210"></path><path d="M977.92 299.52a32.64 32.64 0 0 1-32-32V180.48a37.12 37.12 0 0 0-37.12-37.76H421.12a32 32 0 0 1-32-32 32 32 0 0 1 32-32h487.68a101.76 101.76 0 0 1 101.12 101.76v87.04a32 32 0 0 1-32 32z" fill="#323333" p-id="9211"></path><path d="M977.92 299.52H64a32 32 0 0 1-32-32 32 32 0 0 1 32-32h913.92a32 32 0 0 1 32 32 32 32 0 0 1-32 32z" fill="#323333" p-id="9212"></path><path d="M699.52 299.52a32 32 0 0 1-32-32V110.72a32 32 0 0 1 64 0v156.8a32 32 0 0 1-32 32z" fill="#323333" p-id="9213"></path></svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

1
src/icons/svg/table.svg Normal file
View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1615196178002" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="7726" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M925.586 0H101.369C69.885 0 42.24 28.924 42.24 62.328v902.8c0 33.403 27.645 58.872 59.129 58.872h824.217c31.484 0 56.057-25.469 56.057-58.873V62.328C981.643 28.924 957.198 0 925.586 0zM373.719 735.908V543.932h276.445v191.976z m276.445 42.235v203.494H373.719V778.143z m287.964-276.446h-244.45V298.203h244.45z m-287.964 0H373.719V298.203h276.445z m-319.96 0H85.754V298.203h244.45z m-244.45 42.235h244.45v191.976H85.754z m607.925 0h244.449v191.976h-244.45zM101.369 42.235h824.217c7.807 0 12.542 10.366 12.542 20.093v193.64H85.755V62.328c0-9.727 7.807-20.093 15.614-20.093zM85.755 964.999V778.143h244.449v203.494H101.369c-7.807 0-15.614-6.91-15.614-16.51z m839.83 16.638H693.68V778.143h244.449v186.856c0 9.727-4.607 16.638-12.542 16.638z" p-id="7727"></path></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1615196772316" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="12703" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M896 224H128c-35.2 0-64 28.8-64 64v448c0 35.2 28.8 64 64 64h768c35.2 0 64-28.8 64-64V288c0-35.2-28.8-64-64-64z m0 480c0 19.2-12.8 32-32 32H160c-19.2 0-32-12.8-32-32V320c0-19.2 12.8-32 32-32h704c19.2 0 32 12.8 32 32v384z" p-id="12704"></path><path d="M224 352c-19.2 0-32 12.8-32 32v256c0 16 12.8 32 32 32s32-12.8 32-32V384c0-16-12.8-32-32-32z" p-id="12705"></path></svg>

After

Width:  |  Height:  |  Size: 747 B

View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1615196859629" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="12939" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M896.4 173.1H128.9c-35.2 0-49 13.8-49 49v575.6c0 35.2 13.8 49 49 49h767.5c35.2 0 49-13.8 49-49V222.1c0-35.2-13.8-49-49-49z m0 592.6c0 16-12.8 32-32 32H160.9c-19.2 0-32-12.8-32-32V254.1c0-16 12.8-32 32-32h703.5c19.2 0 32 12.8 32 32v511.6z" p-id="12940"></path><path d="M710.2 766.7h141.5c8.1 0 14.7-6.6 14.7-14.7V610.4c0-1.3-1.6-2-2.6-1.1L709.1 764.1c-1 1-0.3 2.6 1.1 2.6zM207.2 594.3h-13.5c-10 0-18.2-8.2-18.2-18.2V291.8c0-10.2 8.4-18.6 18.6-18.6h12.8c10.2 0 18.6 8.4 18.6 18.6v284.3c-0.1 10-8.3 18.2-18.3 18.2z" p-id="12941"></path></svg>

After

Width:  |  Height:  |  Size: 917 B

View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1615348664389" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="45069" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M512 39.384615a472.615385 472.615385 0 1 0 472.615385 472.615385A472.615385 472.615385 0 0 0 512 39.384615z m0 866.461539a393.846154 393.846154 0 1 1 393.846154-393.846154 393.846154 393.846154 0 0 1-393.846154 393.846154z m75.854769-373.720616A77.154462 77.154462 0 0 0 590.769231 512a78.454154 78.454154 0 0 0-39.384616-67.859692V196.923077a39.384615 39.384615 0 0 0-78.76923 0v247.217231a78.336 78.336 0 0 0 59.549538 143.714461l70.144 70.144a39.384615 39.384615 0 0 0 55.689846-55.689846zM512 551.384615a39.384615 39.384615 0 1 1 39.384615-39.384615 39.384615 39.384615 0 0 1-39.384615 39.384615z m315.076923-78.76923a39.384615 39.384615 0 1 0 39.384615 39.384615 39.384615 39.384615 0 0 0-39.384615-39.384615zM196.923077 472.615385a39.384615 39.384615 0 1 0 39.384615 39.384615 39.384615 39.384615 0 0 0-39.384615-39.384615z m509.991385 234.299077a39.384615 39.384615 0 1 0 55.689846 0 39.384615 39.384615 0 0 0-55.689846 0z m-389.907693-389.907693a39.384615 39.384615 0 1 0-55.729231 0 39.384615 39.384615 0 0 0 55.808 0.039385zM512 787.692308a39.384615 39.384615 0 1 0 39.384615 39.384615 39.384615 39.384615 0 0 0-39.384615-39.384615z m-250.604308-80.777846a39.384615 39.384615 0 1 0 55.689846 0 39.384615 39.384615 0 0 0-55.689846-0.039385zM706.914462 261.395692a39.384615 39.384615 0 1 0 55.689846 0 39.384615 39.384615 0 0 0-55.689846-0.039384z" fill="#333333" p-id="45070"></path></svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1615348933903" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="46152" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M498.596 482.29H345.42v57.308h210.478V274.197h-57.301V482.29z m0 0M577.685 644.985h379.88v57.302h-379.88v-57.302z m0 0M577.685 773.765h379.88v57.307h-379.88v-57.307z m0 0M577.685 902.55h379.88v57.307h-379.88V902.55z m0 0" p-id="46153"></path><path d="M102.523 382.29a28.668 28.668 0 0 0 23.367 2.56l190.81-61.886c15.053-4.883 23.298-21.04 18.415-36.09-4.882-15.052-21.04-23.297-36.093-18.415l-123.346 40c15.994-26.117 35.17-50.538 57.37-72.745 73.768-73.767 171.847-114.388 276.169-114.388 104.32 0 202.395 40.622 276.161 114.388S899.77 407.56 899.77 511.882c0 26.428-2.616 52.45-7.71 77.78h58.303c4.465-25.499 6.709-51.47 6.709-77.78 0-60.45-11.846-119.102-35.205-174.336-22.56-53.335-54.85-101.227-95.969-142.35-41.122-41.122-89.017-73.408-142.348-95.968-55.233-23.361-113.89-35.207-174.334-35.207-60.45 0-119.107 11.846-174.337 35.208-53.335 22.56-101.23 54.846-142.35 95.969-23.98 23.98-44.933 50.278-62.727 78.6l-20.738-105.654c-3.043-15.528-18.105-25.642-33.632-22.6-15.528 3.048-25.643 18.105-22.6 33.637l36.103 183.932a28.666 28.666 0 0 0 13.588 19.178z m0 0M126.02 587.942H67.768c5.76 33.679 15.368 66.544 28.79 98.278 22.56 53.334 54.85 101.225 95.972 142.348 41.123 41.123 89.014 73.409 142.349 95.969 54.112 22.888 111.518 34.711 170.668 35.182v-57.324c-102.95-0.941-199.595-41.446-272.5-114.349-55.501-55.502-92.237-124.77-107.027-200.104z m0 0" p-id="46154"></path></svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

1
src/icons/svg/undo.svg Normal file
View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1615176644441" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3382" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M609.206 396.656h-415.702l201.87-199.152c17.787-17.787 17.787-43.185 0-60.973s-41.555-18.641-59.42-0.778l-273.097 266.804c-17.787 17.787-17.787 41.399 0 59.186l273.173 267.115c17.787 17.787 41.555 17.787 59.42-0.078 17.787-17.787 17.787-41.166 0-58.953l-201.948-195.501h415.702c166.219 0 311.155 129.712 311.155 290.029v41.555c0 23.769 15.069 41.555 38.836 41.555s38.836-17.787 38.836-41.555v-41.555c0-207.852-175.073-367.7-388.828-367.7z" p-id="3383"></path></svg>

After

Width:  |  Height:  |  Size: 842 B

View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1629968876879" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1210" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M454.137642 11.17578L54.066443 174.091899c-72.088591 29.490787-72.088591 120.523113 0 150.0139l400.275996 162.916119c36.45389 14.950191 78.846896 14.950191 115.505583 0l400.071199-162.813721c72.190989-29.593186 72.088591-120.830308-0.307196-150.116298L569.745624 11.278178A155.338625 155.338625 0 0 0 454.137642 11.073381z m469.190231 237.871869L532.370147 408.584616l-7.88469 2.457565a55.090429 55.090429 0 0 1-32.562744-2.457565L100.350595 249.047649 491.615517 89.817879a55.090429 55.090429 0 0 1 40.447434 0l391.162524 159.22977z" fill="#1890FF" p-id="1211"></path><path d="M498.681018 729.911317c-20.274916 0-40.652231-3.788747-59.391168-11.673436L53.759247 561.260878C20.479713 547.846666 0 519.891858 0 488.557896c0-31.43636 20.582112-59.391169 53.861646-72.702982l36.556288-15.052589c21.4013-8.806277 47.103341-1.023986 57.240799 17.407756 10.239857 18.226945 1.023986 40.140238-20.479714 48.946515l-36.453889 14.950191c-3.071957 1.228783-4.710334 3.58395-4.710334 6.451109 0 3.071957 1.535978 5.119928 4.710334 6.45111l385.325805 156.874604c14.335799 5.939117 30.924367 5.939117 45.362565 0l385.325806-156.874604c3.071957-1.228783 4.915131-3.58395 4.915131-6.45111 0-3.071957-1.535978-5.222327-4.607936-6.451109L862.195929 463.879842c-21.4013-8.806277-30.412374-30.71957-20.377314-48.946515 10.342255-18.329343 35.839498-26.214033 57.240798-17.407756l44.850572 18.431742c33.177136 13.516611 53.759247 41.266622 53.759248 72.702982 0 31.333961-20.479713 59.391169-53.759248 72.702982L558.481781 718.237881a161.584938 161.584938 0 0 1-59.800763 11.673436z" fill="#5DE1C8" p-id="1212"></path><path d="M498.681018 966.247209c-20.274916 0-40.652231-3.891146-59.391168-11.673437L53.759247 797.59677C20.479713 784.080159 0 756.227749 0 724.996186c0-31.43636 20.582112-59.493567 53.861646-72.805381l36.556288-14.95019c21.4013-8.806277 47.103341-1.023986 57.240799 17.407756 10.239857 18.226945 1.023986 40.140238-20.479714 48.946515l-36.453889 14.95019c-3.071957 1.126384-4.710334 3.58395-4.710334 6.45111 0 3.071957 1.535978 5.119928 4.710334 6.348711l385.325805 156.977002c14.335799 5.939117 30.924367 5.939117 45.362565 0L906.739306 731.1401c3.071957-1.126384 4.915131-3.58395 4.915131-6.348711 0-3.071957-1.535978-5.324725-4.607936-6.45111l-44.850572-18.329343c-21.4013-8.806277-30.412374-30.71957-20.377314-48.946515 10.342255-18.431742 35.839498-26.214033 57.240798-17.407756l44.850572 18.329343c33.177136 13.516611 53.759247 41.369021 53.759248 72.702982 0 31.43636-20.479713 59.493567-53.759248 72.805381L558.481781 954.573772a161.584938 161.584938 0 0 1-59.800763 11.673437z" fill="#FF7272" p-id="1213"></path></svg>

After

Width:  |  Height:  |  Size: 2.9 KiB

298
src/lang/en-US.js Normal file
View File

@ -0,0 +1,298 @@
export default {
application: {
'zh-CN': '简体中文',
'en-US': 'English',
productTitle: 'Online Form Designer',
github: 'GitHub',
document: 'Docs',
qqGroup: 'WeChat Group',
deployment: 'Deployment',
subscription: 'Subscription',
},
designer: {
containerTitle: 'Container',
dragHandlerHint: 'drag container or field to layout center',
dragAction: 'drag',
basicFieldTitle: 'Basic Field',
advancedFieldTitle: 'Advanced Field',
customFieldTitle: 'Customized Field',
noWidgetHint: 'Please select a widget from the left list, drag and drop to this container.',
widgetLabel: {
grid: 'Grid',
table: 'Table',
tab: 'Tab',
section: 'Section',
'sub-form': 'SubForm',
'grid-col': 'GridCol',
'table-cell': 'TableCell',
'tab-pane': 'TabPane',
input: 'Input',
textarea: 'Textarea',
number: 'InputNumber',
radio: 'Radio',
checkbox: 'Checkbox',
select: 'Select',
time: 'Time',
'time-range': 'Time range',
date: 'Date',
'date-range': 'Date range',
switch: 'Switch',
rate: 'Rate',
color: 'ColorPicker',
slider: 'Slider',
'static-text': 'Text',
'html-text': 'HTML',
button: 'Button',
divider: 'Divider',
'picture-upload': 'Picture',
'file-upload': 'File',
'rich-editor': 'Rich Editor',
cascader: 'Cascader',
custom: 'Custom Component',
slot: 'Slot'
},
hint: {
selectParentWidget: 'Select parent of this widget',
moveUpWidget: 'Move up this widget',
moveDownWidget: 'Move down this widget',
cloneWidget: 'Clone this widget',
insertRow: 'Insert new row',
insertColumn: 'Insert new column',
remove: 'Remove this widget',
cellSetting: 'Cell setting',
dragHandler: 'Drag handler',
copyField: 'Copy field widget',
onlyFieldWidgetAcceptable: 'Only field widget can be dragged into sub-form',
moveUpFirstChildHint: 'First child can not be move up',
moveDownLastChildHint: 'Last child can not be move down',
closePreview: 'Close',
copyJson: 'Copy',
saveFormJson: 'Save As File',
copyVueCode: 'Copy Vue Code',
copyHtmlCode: 'Copy HTML Code',
copyJsonSuccess: 'Copy succeed',
importJsonSuccess: 'Import succeed',
copyJsonFail: 'Copy failed',
copyVueCodeSuccess: 'Copy succeed',
copyVueCodeFail: 'Copy failed',
copyHtmlCodeSuccess: 'Copy succeed',
copyHtmlCodeFail: 'Copy failed',
saveVueCode: 'Save Vue File',
saveHtmlCode: 'Save Html File',
getFormData: 'Get Data',
resetForm: 'Reset',
disableForm: 'Disable',
enableForm: 'Enable',
exportFormData: 'Form Data',
copyFormData: 'Copy',
saveFormData: 'Save As File',
copyVue2SFC: 'Copy Vue2',
copyVue3SFC: 'Copy Vue3',
copySFCFail: 'Copy failed',
copySFCSuccess: 'Copy succeed',
saveVue2SFC: 'Save As Vue2',
saveVue3SFC: 'Save As Vue3',
fileNameForSave: 'File name:',
saveFileTitle: 'Save as File',
fileNameInputPlaceholder: 'Enter the file name',
widgetSetting: 'Widget Config',
formSetting: 'Form Config',
prompt: 'Prompt',
confirm: 'OK',
cancel: 'Cancel',
import: 'Import',
importJsonHint: 'The code to be imported should have the following JSON format.',
invalidOptionsData: 'Invalid data of options:',
lastPaneCannotBeDeleted: 'The last pane cannot be deleted.',
duplicateName: 'Duplicate name: ',
nameRequired: 'Name required.',
numberValidator: 'Number',
letterValidator: 'Letter',
letterAndNumberValidator: 'LetterAndNumber',
mobilePhoneValidator: 'MobilePhone',
emailValidator: 'Email',
urlValidator: 'URL',
noChineseValidator: 'Non-Chinese',
chineseValidator: 'Chinese',
rowspanNotConsistentForMergeEntireRow: 'Cells in this row don\'t have the same rowspan, operation failed.',
colspanNotConsistentForMergeEntireColumn: 'Cells in this column don\'t have the same colspan, operation failed.',
rowspanNotConsistentForDeleteEntireRow: 'Cells in this row don\'t have the same rowspan, operation failed.',
colspanNotConsistentForDeleteEntireColumn: 'Cells in this column don\'t have the same colspan, operation failed.',
},
toolbar: {
undoHint: 'Undo',
redoHint: 'Redo',
pcLayout: 'PC',
mobileLayout: 'H5',
clear: 'Clear',
preview: 'Preview',
importJson: 'Import JSON',
exportJson: 'Export JSON',
exportCode: 'Export Code',
generateCode: 'Generate Code',
generateSFC: 'Generate SFC',
},
setting: {
basicSetting: 'Basic Setting',
attributeSetting: 'Attribute Setting',
commonSetting: 'Common Setting',
advancedSetting: 'Advanced Setting',
eventSetting: 'Event Setting',
fieldName: 'Unique Name',
label: 'Label',
displayType: 'Type',
defaultValue: 'Default Value',
placeholder: 'Placeholder',
startPlaceholder: 'Start Placeholder',
endPlaceholder: 'End Placeholder',
widgetColumnWidth: 'Width',
widgetSize: 'Size',
displayStyle: 'Display Style',
inlineLayout: 'inline',
blockLayout: 'block',
buttonStyle: 'Show As Button',
border: 'Show Border',
labelWidth: 'Width Of Label',
rows: 'Rows',
labelHidden: 'Hide Label',
required: 'Required',
validation: 'Validation',
validationHelp: 'Regular expressions supported',
validationHint: 'Validation Hint',
readonly: 'Readonly',
disabled: 'Disabled',
hidden: 'Hidden',
textContent: 'Text',
htmlContent: 'HTML',
clearable: 'Clearable',
editable: 'Editable',
format: 'Format',
valueFormat: 'Value Format',
showPassword: 'Show Reveal',
filterable: 'Filterable',
allowCreate: 'Allow Create',
remote: 'Remote Query',
automaticDropdown: 'Automatic Dropdown',
multiple: 'Multiple',
multipleLimit: 'Multiple Limit',
contentPosition: 'Content Position',
plain: 'Plain',
round: 'Round',
circle: 'Circle',
icon: 'Icon',
optionsSetting: 'Options Setting',
addOption: 'Add Option',
importOptions: 'Import Options',
resetDefault: 'Reset Default',
uploadSetting: 'Upload Setting',
uploadURL: 'Upload URL',
uploadTip: 'Tip Content',
withCredentials: 'Send Cookie',
multipleSelect: 'File Multi-select',
showFileList: 'Show File List',
limit: 'Max Upload Number',
fileMaxSize: 'Max Size(MB)',
fileTypes: 'Upload File Types',
fileTypesHelp: 'Allows to add more file types',
headers: 'Request Headers',
cellWidth: 'width',
cellHeight: 'height',
gutter: 'Gutter(px)',
columnSetting: 'Cols Setting',
colsOfGrid: 'Cols Of Grid:',
colSpanTitle: 'Spans Of Col',
addColumn: 'Add Column',
tabPaneSetting: 'Tab Panes',
addTabPane: 'Add Tab Pane',
paneActive: 'Active',
customLabelIcon: 'Custom Label',
labelIconClass: 'Label Icon Class',
labelIconPosition: 'Label Icon Position',
labelTooltip: 'Label Tooltip',
minValue: 'Min Value',
maxValue: 'Max Value',
precision: 'Precision',
step: 'Step',
controlsPosition: 'Controls Position',
minLength: 'Min Length',
maxLength: 'Max Length',
showWordLimit: 'Show Word Limit',
prefixIcon: 'Prefix Icon',
suffixIcon: 'Suffix Icon',
inputButton: 'Input Button Setting',
appendButton: 'Append Button',
appendButtonDisabled: 'Button Disabled',
buttonIcon: 'Button Icon',
switchWidth: 'Width of Switch(px)',
activeText: 'Active Text',
inactiveText: 'Inactive Text',
activeColor: 'Active Color',
inactiveColor: 'Inactive Color',
maxStars: 'Stars Max Number',
lowThreshold: 'Low Threshold',
highThreshold: 'High Threshold',
allowHalf: 'Allow Half',
showText: 'Show Text',
showScore: 'Show Score',
range: 'Range',
vertical: 'Vertical',
showBlankRow: 'Show Blank Row',
showRowNumber: 'Show Row Number',
insertColumnToLeft: 'insert column to left',
insertColumnToRight: 'insert column to right',
insertRowAbove: 'insert row above',
insertRowBelow: 'insert row below',
mergeLeftColumn: 'merge left cell',
mergeRightColumn: 'merge right cell',
mergeEntireRow: 'merge entire row',
mergeRowAbove: 'merge cell above',
mergeRowBelow: 'merge cell below',
mergeEntireColumn: 'merge entire column',
undoMergeCol: 'undo merge column',
undoMergeRow: 'undo merge row',
deleteEntireCol: 'delete entire column',
deleteEntireRow: 'delete entire row',
widgetName: 'Unique Name',
formSize: 'Size',
labelPosition: 'Position Of Label',
topPosition: 'Top',
leftPosition: 'Left',
labelAlign: 'Label Align',
leftAlign: 'Left',
centerAlign: 'Center',
rightAlign: 'Right',
formCss: 'Form CSS',
addCss: 'Edit',
customClass: 'Custom Class',
globalFunctions: 'Global Functions',
addEventHandler: 'Edit',
editWidgetEventHandler: 'Edit Widget Event Handler',
editFormEventHandler: 'Edit Form Event Handler',
formSFCSetting: 'SFC Setting',
formModelName: 'Model Name',
formRefName: 'Ref Name',
formRulesName: 'Rules Name',
}
}
}

38
src/lang/en-US_render.js Normal file
View File

@ -0,0 +1,38 @@
export default {
render: {
hint: {
prompt: 'Prompt',
confirm: 'OK',
cancel: 'Cancel',
selectPlaceholder: 'Pick some item',
timePlaceholder: 'Select time',
startTimePlaceholder: 'Start time',
endTimePlaceholder: 'End time',
datePlaceholder: 'Select date',
startDatePlaceholder: 'Start date',
endDatePlaceholder: 'End date',
blankCellContent: '--',
uploadError: 'Upload error: ',
uploadExceed: 'The maximum number(${uploadLimit}) of file uploads has been exceeded.',
unsupportedFileType: 'Unsupported format: ',
fileSizeExceed: 'File size out of limit: ',
refNotFound: 'Ref not found: ',
fieldRequired: 'Input value should be not null.',
invalidNumber: 'Invalid number format',
selectFile: ' File...',
downloadFile: 'Download',
removeFile: 'Remove',
validationFailed: 'Form validation failed',
subFormAction: 'Action',
subFormAddAction: 'Add',
subFormAddActionHint: 'add new row',
insertSubFormRow: 'insert new row',
deleteSubFormRow: 'delete this row',
nonSubFormType: 'The type of widget don\'t match sub-form',
}
}
}

298
src/lang/zh-CN.js Normal file
View File

@ -0,0 +1,298 @@
export default {
application: {
'zh-CN': '简体中文',
'en-US': 'English',
productTitle: '表单设计器',
github: 'GitHub',
document: '文档',
qqGroup: '技术WX群',
deployment: '私有部署',
subscription: '订阅Pro',
},
designer: {
containerTitle: '容器',
dragHandlerHint: '鼠标拖拽容器组件或字段组件并放置于表单中',
dragAction: '拖动',
basicFieldTitle: '基础字段',
advancedFieldTitle: '高级字段',
customFieldTitle: '自定义扩展字段',
noWidgetHint: '请从左侧列表中选择一个组件, 然后用鼠标拖动组件放置于此处.',
widgetLabel: {
grid: '栅格',
table: '表格',
tab: '标签页',
section: '区块',
'sub-form': '子表单',
'grid-col': '栅格列',
'table-cell': '单元格',
'tab-pane': '选项卡页',
input: '单行输入',
textarea: '多行输入',
number: '计数器',
radio: '单选项',
checkbox: '多选项',
select: '下拉选项',
time: '时间',
'time-range': '时间范围',
date: '日期',
'date-range': '日期范围',
switch: '开关',
rate: '评分',
color: '颜色选择器',
slider: '滑块',
'static-text': '静态文字',
'html-text': 'HTML',
button: '按钮',
divider: '分隔线',
'picture-upload': '图片',
'file-upload': '文件',
'rich-editor': '富文本',
cascader: '级联选择',
custom: 'Custom Component',
slot: 'Slot'
},
hint: {
selectParentWidget: '选中父组件',
moveUpWidget: '上移组件',
moveDownWidget: '下移组件',
cloneWidget: '复制组件',
insertRow: '插入新行',
insertColumn: '插入新列',
remove: '移除组件',
cellSetting: '单元格操作',
dragHandler: '拖拽手柄',
copyField: '复制字段组件',
onlyFieldWidgetAcceptable: '子表单只能接收字段组件',
moveUpFirstChildHint: '已经移动到最上面',
moveDownLastChildHint: '已经移动到最下面',
closePreview: '关闭',
copyJson: '复制JSON',
saveFormJson: '保存为文件',
copyVueCode: '复制Vue代码',
copyHtmlCode: '复制HTML代码',
copyJsonSuccess: '复制JSON成功',
importJsonSuccess: '导入JSON成功',
copyJsonFail: '复制JSON失败',
copyVueCodeSuccess: '复制Vue代码成功',
copyVueCodeFail: '复制Vue代码失败',
copyHtmlCodeSuccess: '复制HTML代码成功',
copyHtmlCodeFail: '复制HTML代码失败',
saveVueCode: '保存Vue文件',
saveHtmlCode: '保存Html文件',
getFormData: '获取数据',
resetForm: '重置表单',
disableForm: '禁用编辑',
enableForm: '恢复编辑',
exportFormData: '表单数据',
copyFormData: '复制JSON',
saveFormData: '保存为文件',
copyVue2SFC: '复制Vue2代码',
copyVue3SFC: '复制Vue3代码',
copySFCFail: '复制SFC代码失败',
copySFCSuccess: '复制SFC代码成功',
saveVue2SFC: '保存为Vue2组件',
saveVue3SFC: '保存为Vue3组件',
fileNameForSave: '文件名:',
saveFileTitle: '保存为文件',
fileNameInputPlaceholder: '请输入文件名',
widgetSetting: '组件设置',
formSetting: '表单设置',
prompt: '提示',
confirm: '确定',
cancel: '取消',
import: '导入',
importJsonHint: '导入的JSON内容须符合下述格式以保证顺利导入.',
invalidOptionsData: '无效的选项数据:',
lastPaneCannotBeDeleted: '仅剩一个选项卡页不可删除.',
duplicateName: '组件名称已存在: ',
nameRequired: '组件名称不可为空',
numberValidator: '数字',
letterValidator: '字母',
letterAndNumberValidator: '数字字母',
mobilePhoneValidator: '手机号码',
emailValidator: '邮箱',
urlValidator: '网址',
noChineseValidator: '非中文字符',
chineseValidator: '仅中文字符',
rowspanNotConsistentForMergeEntireRow: '存在行高不一致的单元格, 无法合并整行.',
colspanNotConsistentForMergeEntireColumn: '存在列宽不一致的单元格, 无法合并整列.',
rowspanNotConsistentForDeleteEntireRow: '存在行高不一致的单元格, 不可删除整行.',
colspanNotConsistentForDeleteEntireColumn: '存在列宽不一致的单元格, 不可删除整列.',
},
toolbar: {
undoHint: '撤销',
redoHint: '重做',
pcLayout: 'PC',
mobileLayout: 'H5',
clear: '清空',
preview: '预览',
importJson: '导入JSON',
exportJson: '导出JSON',
exportCode: '导出代码',
generateCode: '生成代码',
generateSFC: '生成SFC',
},
setting: {
basicSetting: '基本属性',
attributeSetting: '属性设置',
commonSetting: '常见属性',
advancedSetting: '高级属性',
eventSetting: '事件属性',
fieldName: '字段唯一名称',
label: '字段标签',
displayType: '显示类型',
defaultValue: '默认值',
placeholder: '占位内容',
startPlaceholder: '起始占位内容',
endPlaceholder: '截止占位内容',
widgetColumnWidth: '组件列宽',
widgetSize: '组件大小',
displayStyle: '显示样式',
inlineLayout: '行内',
blockLayout: '块',
buttonStyle: '显示为按钮',
border: '带有边框',
labelWidth: '标签宽度',
rows: '行数',
labelHidden: '隐藏字段标签',
required: '必填字段',
validation: '字段校验',
validationHelp: '支持输入正则表达式',
validationHint: '校验失败提示',
readonly: '只读',
disabled: '禁用',
hidden: '隐藏',
textContent: '静态文字',
htmlContent: 'HTML',
clearable: '可清除',
editable: '可输入',
format: '显示格式',
valueFormat: '绑定值格式',
showPassword: '可显示密码',
filterable: '可搜索选项',
allowCreate: '允许创建选项',
remote: '可远程搜索',
automaticDropdown: '自动弹出选项',
multiple: '选项可多选',
multipleLimit: '多选数量限制',
contentPosition: '文字位置',
plain: '朴素按钮',
round: '圆角按钮',
circle: '圆形按钮',
icon: '图标',
optionsSetting: '选项设置',
addOption: '增加选项',
importOptions: '导入选项',
resetDefault: '重设选中项',
uploadSetting: '上传参数设置',
uploadURL: '上传地址',
uploadTip: '上传提示内容',
withCredentials: '发送cookie凭证',
multipleSelect: '文件可多选',
showFileList: '显示文件列表',
limit: '最大上传数量',
fileMaxSize: '文件大小限制(MB)',
fileTypes: '上传文件类型',
fileTypesHelp: '支持添加其他文件类型',
headers: '上传请求头',
cellWidth: '宽度',
cellHeight: '高度',
gutter: '栅格间隔(像素)',
columnSetting: '栅格属性设置',
colsOfGrid: '当前栅格列:',
colSpanTitle: '栅格列',
addColumn: '增加栅格',
tabPaneSetting: '选项卡设置',
addTabPane: '增加选项卡页',
paneActive: '激活',
customLabelIcon: '定制字段标签',
labelIconClass: '标签Icon样式',
labelIconPosition: '标签Icon位置',
labelTooltip: '标签文字提示',
minValue: '最小值',
maxValue: '最大值',
precision: '精度',
step: '增减步长',
controlsPosition: '控制按钮位置',
minLength: '最小长度',
maxLength: '最大长度',
showWordLimit: '显示字数统计',
prefixIcon: '头部Icon',
suffixIcon: '尾部Icon',
inputButton: '输入框按钮设置',
appendButton: '添加后置按钮',
appendButtonDisabled: '后置按钮禁用',
buttonIcon: '后置按钮Icon',
switchWidth: '开关宽度(像素)',
activeText: '开启时文字描述',
inactiveText: '关闭时文字描述',
activeColor: '开启时背景色',
inactiveColor: '关闭时背景色',
maxStars: '最大评分值',
lowThreshold: '低分界限值',
highThreshold: '高分界限值',
allowHalf: '允许半选',
showText: '显示辅助文字',
showScore: '显示当前分数',
range: '是否为范围选择',
vertical: '是否竖向显示',
showBlankRow: '默认显示新行',
showRowNumber: '显示行号',
insertColumnToLeft: '插入左侧列',
insertColumnToRight: '插入右侧列',
insertRowAbove: '插入上方行',
insertRowBelow: '插入下方行',
mergeLeftColumn: '合并左侧单元格',
mergeRightColumn: '合并右侧单元格',
mergeEntireRow: '合并整行',
mergeRowAbove: '合并上方单元格',
mergeRowBelow: '合并下方单元格',
mergeEntireColumn: '合并整列',
undoMergeCol: '撤销列合并',
undoMergeRow: '撤销行合并',
deleteEntireCol: '删除整列',
deleteEntireRow: '删除整行',
widgetName: '组件唯一名称',
formSize: '全局组件大小',
labelPosition: '字段标签位置',
topPosition: '顶部',
leftPosition: '左边',
labelAlign: '字段标签对齐',
leftAlign: '居左',
centerAlign: '居中',
rightAlign: '居右',
formCss: '表单全局CSS',
addCss: '编写CSS',
customClass: '自定义CSS样式',
globalFunctions: '表单全局函数',
addEventHandler: '编写代码',
editWidgetEventHandler: '组件事件处理',
editFormEventHandler: '表单事件处理',
formSFCSetting: '生产SFC设置',
formModelName: '数据对象名称',
formRefName: '引用名称',
formRulesName: '验证规则名称',
}
}
}

38
src/lang/zh-CN_render.js Normal file
View File

@ -0,0 +1,38 @@
export default {
render: {
hint: {
prompt: '提示',
confirm: '确定',
cancel: '取消',
selectPlaceholder: '请选择',
timePlaceholder: '选择时间',
startTimePlaceholder: '起始时间',
endTimePlaceholder: '截止时间',
datePlaceholder: '选择日期',
startDatePlaceholder: '起始日期',
endDatePlaceholder: '截止日期',
blankCellContent: '--',
uploadError: '上传错误: ',
uploadExceed: '最大上传数量(${uploadLimit})已超出.',
unsupportedFileType: '不支持格式: ',
fileSizeExceed: '文件大小已超出: ',
refNotFound: '组件未找到: ',
fieldRequired: '字段值不可为空',
invalidNumber: '数据格式错误',
selectFile: ' 选择文件',
downloadFile: '下载',
removeFile: '移除',
validationFailed: '表单数据校验失败',
subFormAction: '操作',
subFormAddAction: '新增',
subFormAddActionHint: '新增行',
insertSubFormRow: '插入行',
deleteSubFormRow: '删除行',
nonSubFormType: '组件类型不是子表单',
}
}
}

34
src/main.js Normal file
View File

@ -0,0 +1,34 @@
import 'babel-polyfill'
import './utils/debug-console'
import Vue from 'vue'
import axios from "axios";
import App from './App.vue'
import ElementUI from 'element-ui'
import './utils/directive'
import './icons'
import ContainerWidget from "@/components/form-designer/form-widget/container-widget";
import ContainerItem from "@/components/form-render/container-item";
import 'element-ui/lib/theme-chalk/index.css'
import '@/styles/index.scss'
import '@/iconfont/iconfont.css'
/* 递归组件如需在递归组件的嵌套组件中使用,必须注册为全局组件,原因不明?? begin */
Vue.component('container-widget', ContainerWidget)
Vue.component('container-item', ContainerItem)
/* end */
Vue.use(ElementUI, { size: 'small' })
if (typeof window !== 'undefined') {
window.axios = axios
}
Vue.config.productionTip = false
new Vue({
el: "#app",
render: h => h(App),
})

103
src/styles/global.scss Normal file
View File

@ -0,0 +1,103 @@
/* 全局css变量 */
$--color-primary: #409EFF;
.primary-color {
color: $--color-primary;
}
.background-opacity {
background: rgba(64, 158, 255, 0.6);
}
.form-widget-list {
.ghost{
content: '';
font-size: 0;
height: 3px;
box-sizing: border-box;
background: $--color-primary;
border: 2px solid $--color-primary;
outline-width: 0;
padding: 0;
overflow: hidden;
}
}
.el-form-item--medium {
.el-radio {
line-height: 36px !important;
}
.el-rate{
margin-top: 8px;
}
}
.el-form-item--small {
.el-radio {
line-height: 32px !important;
}
.el-rate{
margin-top: 6px;
}
}
.el-form-item--mini {
.el-radio {
line-height: 28px !important;
}
.el-rate{
margin-top: 4px;
}
}
input[type="password"]::-ms-reveal { /* 隐藏IE/Edge原生的密码查看按钮 */
display: none;
}
/* 滚动条样式 begin */
::-webkit-scrollbar {
width: 8px;
height: 8px;
}
::-webkit-scrollbar-track {
width: 8px;
background: rgba(#101F1C, 0.1);
-webkit-border-radius: 2em;
-moz-border-radius: 2em;
border-radius: 2em;
}
::-webkit-scrollbar-thumb {
background-color: rgba(#101F1C, 0.35);
background-clip: padding-box;
min-height: 28px;
-webkit-border-radius: 2em;
-moz-border-radius: 2em;
border-radius: 2em;
}
::-webkit-scrollbar-thumb:hover {
background-color: rgba(#101F1C, 0.85);
}
* {//Firefox
scrollbar-color: #e5e5e5 #f7f7f9; //
scrollbar-width: thin; //thin
}
/*
body {//IE
scrollbar-shadow-color: #e5e5e5;
scrollbar-face-color: #e5e5e5;
scrollbar-base-color: #ffffff;
scrollbar-arrow-color: #444040;
}
*/
/* 滚动条样式 end */

15
src/styles/index.scss Normal file
View File

@ -0,0 +1,15 @@
html {
height: 100%;
box-sizing: border-box;
}
body {
height: 100%;
margin: 0; /* */
background-color: #ffffff;
-moz-osx-font-smoothing: grayscale;
-webkit-font-smoothing: antialiased;
text-rendering: optimizeLegibility;
font-family: Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB, Microsoft YaHei, Arial, sans-serif;
}

View File

@ -0,0 +1,77 @@
import {loadRemoteScript} from "@/utils/util";
import {BEAUTIFIER_PATH} from "@/utils/config";
let beautifierObj
export const beautifierOpts = {
html: {
indent_size: '2',
indent_char: ' ',
max_preserve_newlines: '-1',
preserve_newlines: false,
keep_array_indentation: false,
break_chained_methods: false,
indent_scripts: 'separate',
brace_style: 'end-expand',
space_before_conditional: true,
unescape_strings: false,
jslint_happy: false,
end_with_newline: true,
wrap_line_length: '110',
indent_inner_html: true,
comma_first: false,
e4x: true,
indent_empty_lines: true
},
js: {
indent_size: '2',
indent_char: ' ',
max_preserve_newlines: '-1',
preserve_newlines: false,
keep_array_indentation: false,
break_chained_methods: false,
indent_scripts: 'normal',
brace_style: 'end-expand',
space_before_conditional: true,
unescape_strings: false,
jslint_happy: true,
end_with_newline: true,
wrap_line_length: '110',
indent_inner_html: true,
comma_first: false,
e4x: true,
indent_empty_lines: true
},
css: {
indent_size: '2',
indent_char: ' ',
max_preserve_newlines: '-1',
preserve_newlines: false,
keep_array_indentation: false,
break_chained_methods: false,
indent_scripts: 'normal',
brace_style: 'end-expand',
space_before_conditional: true,
unescape_strings: false,
jslint_happy: true,
end_with_newline: true,
wrap_line_length: '110',
indent_inner_html: true,
comma_first: false,
e4x: true,
indent_empty_lines: true
}
}
export default function loadBeautifier(callback) {
if (beautifierObj) {
callback(beautifierObj)
return
}
loadRemoteScript(BEAUTIFIER_PATH, () => {
// eslint-disable-next-line no-undef
beautifierObj = beautifier //beautifier为全局对象
callback(beautifierObj)
})
}

View File

@ -0,0 +1,91 @@
export const generateCode = function(formJson, codeType= 'vue') {
let formJsonStr = JSON.stringify(formJson)
if (codeType === 'html') {
return `<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no" />
<title>VForm Demo</title>
<link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css">
<link rel="stylesheet" href="https://ks3-cn-beijing.ksyun.com/vform2021/VFormRender.css?t=20210720">
<style type="text/css">
</style>
</head>
<body>
<div id="app">
<v-form-render :form-json="formJson" :form-data="formData" :option-data="optionData" ref="vFormRef">
</v-form-render>
<el-button type="primary" @click="submitForm">Submit</el-button>
</div>
<script type="text/javascript">
if (!!window.ActiveXObject || "ActiveXObject" in window) { //IE load polyfill.js for Promise
var scriptEle = document.createElement("script");
scriptEle.type = "text/javascript";
scriptEle.src = "https://cdn.bootcss.com/babel-polyfill/6.23.0/polyfill.min.js"
document.body.appendChild(scriptEle)
}
</script>
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<script src="https://unpkg.com/element-ui/lib/index.js"></script>
<script src="https://ks3-cn-beijing.ksyun.com/vform2021/VFormRender.umd.min.js?t=20210720"></script>
<script>
new Vue({
el: '#app',
data: {
formJson: ${formJsonStr},
formData: {},
optionData: {}
},
methods: {
submitForm: function() {
this.$refs.vFormRef.getFormData().then( function(formData) {
// Form Validation OK
alert( JSON.stringify(formData) )
}).catch( function(error) {
// Form Validation Failed
alert(error)
})
}
}
});
</script>
</body>
</html>`
} else {
return `<template>
<div>
<v-form-render :form-json="formJson" :form-data="formData" :option-data="optionData" ref="vFormRef">
</v-form-render>
<el-button type="primary" @click="submitForm">Submit</el-button>
</div>
</template>
<script>
export default {
data() {
return {
formJson: ${formJsonStr},
formData: {},
optionData: {}
}
},
methods: {
submitForm() {
this.$refs.vFormRef.getFormData().then(formData => {
// Form Validation OK
alert( JSON.stringify(formData) )
}).catch(error => {
// Form Validation failed
this.$message.error(error)
})
}
}
}
</script>`
}
}

16
src/utils/config.js Normal file
View File

@ -0,0 +1,16 @@
export const DESIGNER_OPTIONS = {
showLanguageMenu: true,
showGetSourceURL: true,
showGithubURL: true,
showDocumentURL: true,
showChatGroup: true,
//
}
export const VARIANT_FORM_VERSION = '1.1.2'
//export const ACE_BASE_PATH = 'public/lib/ace/src-min-noconflict'
export const ACE_BASE_PATH = 'https://ks3-cn-beijing.ksyun.com/vform2021/ace-mini'
export const BEAUTIFIER_PATH= 'https://ks3-cn-beijing.ksyun.com/vform2021/js-beautify/1.14.0/beautifier.min.js'

View File

@ -0,0 +1,7 @@
console.log = (function(logFunc) {
return function () {
if (process.env.NODE_ENV === 'development') {
logFunc.call(console, ...arguments);
}
}
})(console.log)

74
src/utils/directive.js Normal file
View File

@ -0,0 +1,74 @@
import Vue from 'vue'
// v-dialogDrag: 弹窗拖拽
Vue.directive('dialogDrag', {
bind(el, binding, vnode, oldVnode) {
const dialogHeaderEl = el.querySelector('.el-dialog__header')
const dragDom = el.querySelector('.el-dialog')
dialogHeaderEl.style.cursor = 'move'
// 获取原有属性 ie dom元素.currentStyle 火狐谷歌 window.getComputedStyle(dom元素, null);
const sty = dragDom.currentStyle || window.getComputedStyle(dragDom, null)
dialogHeaderEl.onmousedown = (e) => {
// 鼠标按下,计算当前元素距离可视区的距离
const disX = e.clientX - dialogHeaderEl.offsetLeft
const disY = e.clientY - dialogHeaderEl.offsetTop
// 获取到的值带px 正则匹配替换
let styL, styT
// 注意在ie中 第一次获取到的值为组件自带50% 移动之后赋值为px
if (sty.left.includes('%')) {
styL = +document.body.clientWidth * (+sty.left.replace(/%/g, '') / 100)
styT = +document.body.clientHeight * (+sty.top.replace(/%/g, '') / 100)
} else {
styL = +sty.left.replace(/px/g, '')
styT = +sty.top.replace(/px/g, '')
}
document.onmousemove = function(e) {
// 通过事件委托,计算移动的距离
const l = e.clientX - disX
const t = e.clientY - disY
// 移动当前元素
dragDom.style.left = `${l + styL}px`
dragDom.style.top = `${t + styT}px`
// 将此时的位置传出去
// binding.value({x:e.pageX,y:e.pageY})
}
document.onmouseup = function(e) {
document.onmousemove = null
document.onmouseup = null
}
}
}
})
// v-dialogDragWidth: 弹窗宽度拖大 拖小
Vue.directive('dialogDragWidth', {
bind(el, binding, vnode, oldVnode) {
const dragDom = binding.value.$el.querySelector('.el-dialog')
el.onmousedown = (e) => {
// 鼠标按下,计算当前元素距离可视区的距离
const disX = e.clientX - el.offsetLeft
document.onmousemove = function(e) {
e.preventDefault() // 移动时禁用默认事件
// 通过事件委托,计算移动的距离
const l = e.clientX - disX
dragDom.style.width = `${l}px`
}
document.onmouseup = function(e) {
document.onmousemove = null
document.onmouseup = null
}
}
}
})

59
src/utils/i18n.js Normal file
View File

@ -0,0 +1,59 @@
import Vue from 'vue'
import VueI18n from 'vue-i18n'
import enLocaleElement from "element-ui/lib/locale/lang/en";
import zhLocaleElement from "element-ui/lib/locale/lang/zh-CN";
import locale from "element-ui/lib/locale"
import enLocale from "@/lang/en-US";
import zhLocale from "@/lang/zh-CN";
import enLocale_render from "@/lang/en-US_render";
import zhLocale_render from "@/lang/zh-CN_render";
const langResources = {
'en-US': {
something: {
//...
},
...enLocaleElement,
...enLocale,
...enLocale_render
},
'zh-CN': {
something: {
//...
},
...zhLocaleElement,
...zhLocale,
...zhLocale_render
}
}
Vue.use(VueI18n)
const i18n = new VueI18n({
locale: localStorage.getItem('v_form_locale') || 'zh-CN',
//locale: 'en-US',
messages: langResources,
})
locale.i18n((key, value) => i18n.t(key, value))
export const changeLocale = function(langName) {
i18n.locale = langName
localStorage.setItem('v_form_locale', langName)
}
export const translate = function(key) {
return i18n._t(key, i18n.locale, i18n._getMessages())
}
export default {
methods: {
i18nt(key) {
return i18n._t(key, i18n.locale, i18n._getMessages())
}
}
}

541
src/utils/sfc-generator.js Normal file
View File

@ -0,0 +1,541 @@
import {isNotNull} from "@/utils/util";
import {genVue2JS} from "@/utils/vue2js-generator";
import {beautifierOpts} from "@/utils/beautifierLoader";
import {genVue3JS} from "@/utils/vue3js-generator";
function buildClassAttr(ctn, defaultClass) {
const cop = ctn.options
let gridClassArray = []
!!defaultClass && gridClassArray.push(defaultClass)
!!cop.customClass && (cop.customClass.length > 0) && gridClassArray.push(cop.customClass.join(' '))
return gridClassArray.length > 0 ? `class="${gridClassArray.join(' ')}"` : ''
}
const containerTemplates = { //容器组件属性
'grid': (ctn, formConfig) => {
const gridClassAttr = buildClassAttr(ctn)
const gridTemplate =
`<el-row ${gridClassAttr}>
${ctn.cols.map(col => {
const colOpt = col.options
const colClassAttr = buildClassAttr(col, 'grid-cell')
return `<el-col :span="${colOpt.span}" ${colClassAttr}>
${col.widgetList.map(cw => {
if (cw.category === 'container') {
return buildContainerWidget(cw, formConfig)
} else {
return buildFieldWidget(cw, formConfig)
}
}).join('')
}
</el-col>`
}).join('')
}
</el-row>`
return gridTemplate
},
'table': (ctn, formConfig) => {
const tableClassAttr = buildClassAttr(ctn, 'table-layout')
const tableTemplate =
`<div class="table-container">
<table ${tableClassAttr}><tbody>
${ctn.rows.map(tr => {
return `<tr>${
tr.cols.filter(td => !td.merged).map(td => {
const tdOpt = td.options
const tdClassAttr = buildClassAttr(td, 'table-cell')
const colspanAttr = (!isNaN(tdOpt.colspan) && (tdOpt.colspan !== 1)) ? `colspan="${tdOpt.colspan}"` : ''
const rowspanAttr = (!isNaN(tdOpt.rowspan) && (tdOpt.rowspan !== 1)) ? `rowspan="${tdOpt.rowspan}"` : ''
let tdStyleArray = []
!!tdOpt.cellWidth && tdStyleArray.push('width: ' + tdOpt.cellWidth + ' !important')
!!tdOpt.cellHeight && tdStyleArray.push('height: ' + tdOpt.cellHeight + ' !important')
let tdStyleAttr = (tdStyleArray.length > 0) ? `style="${tdStyleArray.join(';')}"` : ''
return `<td ${tdClassAttr} ${colspanAttr} ${rowspanAttr} ${tdStyleAttr}>${td.widgetList.map(tw => {
if (tw.category === 'container') {
return buildContainerWidget(tw, formConfig)
} else {
return buildFieldWidget(tw, formConfig)
}
}).join('')
}
</td>`
}).join('')
}</tr>`
}).join('')
}
</tbody></table>
</div>`
return tableTemplate
},
'tab': (ctn, formConfig) => {
const tabClassAttr = buildClassAttr(ctn)
const vModel = ctn.tabs && (ctn.tabs.length > 0) ? `v-model="${ctn.options.name}ActiveTab"` : ''
const tabTemplate =
`<div class="tab-container">
<el-tabs ${vModel} type="${ctn.displayType}" ${tabClassAttr}>
${ctn.tabs.map(tab => {
const tabOpt = tab.options
const disabledAttr = (tabOpt.disabled === true) ? `disabled` : ''
return `<el-tab-pane name="${tabOpt.name}" label="${tabOpt.label}" ${disabledAttr}>
${tab.widgetList.map(tw => {
if (tw.category === 'container') {
return buildContainerWidget(tw, formConfig)
} else {
return buildFieldWidget(tw, formConfig)
}
}).join('')
}</el-tab-pane>`
}).join('')}
</el-tabs>
</div>`
return tabTemplate
},
'sub-form': (ctn, formConfig) => {
//TODO:
},
}
function buildContainerWidget(widget, formConfig) {
return containerTemplates[widget.type] ? containerTemplates[widget.type](widget, formConfig) : null
}
function getElAttrs(widget, formConfig) { //获取El组件属性
let wop = widget.options
return {
vModel: `v-model="${formConfig.modelName}.${wop.name}"`,
readonly: wop.readonly ? `readonly="true"` : '',
disabled: wop.disabled ? `:disabled="true"` : '',
size: !!wop.size ? `size="${wop.size}"` : '',
type: !!wop.type ? `type="${wop.type === 'number' ? 'text' : wop.type}"` : '',
showPassword: !!wop.showPassword ? `:show-password="${wop.showPassword}"` : '',
placeholder: !!wop.placeholder ? `placeholder="${wop.placeholder}"` : '',
rows: (isNotNull(wop.rows) && !isNaN(wop.rows)) ? `rows="${wop.rows}"` : '',
clearable: !!wop.clearable ? 'clearable' : '',
minlength: (isNotNull(wop.minLength) && !isNaN(wop.minLength)) ? `:minlength="${wop.minLength}"` : '',
maxlength: (isNotNull(wop.maxLength) && !isNaN(wop.maxLength)) ? `:maxlength="${wop.maxLength}"` : '',
showWordLimit: !!wop.showWordLimit ? `:show-word-limit="true"`: '',
prefixIcon: !!wop.prefixIcon ? `prefix-icon="${wop.prefixIcon}"` : '',
suffixIcon: !!wop.suffixIcon ? `suffix-icon="${wop.suffixIcon}"` : '',
controlsPosition: wop.controlsPosition === 'right' ? `controls-position="right"` : '',
min: (isNotNull(wop.min) && !isNaN(wop.min)) ? `:min="${wop.min}"` : '',
max: (isNotNull(wop.max) && !isNaN(wop.max)) ? `:max="${wop.max}"` : '',
precision: (isNotNull(wop.precision) && !isNaN(wop.precision)) ? `:precision="${wop.precision}"` : '',
step: (isNotNull(wop.step) && !isNaN(wop.step)) ? `:step="${wop.step}"` : '',
filterable: !!wop.filterable ? `filterable` : '',
allowCreate: !!wop.allowCreate ? `allow-create` : '',
defaultFirstOption: (!!wop.filterable && !!wop.allowCreate) ? `default-first-option` : '',
multiple: !!wop.multiple ? `multiple` : '',
multipleLimit: (!isNaN(wop.multipleLimit) && (wop.multipleLimit > 0)) ? `:multiple-limit="${wop.multipleLimit}"` : '',
automaticDropdown: !!wop.automaticDropdown ? `automatic-dropdown` : '',
remote: !!wop.remote ? `remote` : '',
format: !!wop.format ? `format="${wop.format}"` : '',
valueFormat: !!wop.valueFormat ? `value-format="${wop.valueFormat}"` : '',
editable: !!wop.editable ? `:editable="${wop.editable}"` : '',
startPlaceholder: !!wop.startPlaceholder ? `start-placeholder="${wop.startPlaceholder}"` : '',
endPlaceholder: !!wop.endPlaceholder ? `end-placeholder="${wop.endPlaceholder}"` : '',
activeText: !!wop.activeText ? `active-text="${wop.activeText}"` : '',
inactiveText: !!wop.inactiveText ? `inactive-text="${wop.inactiveText}"` : '',
activeColor: !!wop.activeColor ? `active-color="${wop.activeColor}"` : '',
inactiveColor: !!wop.inactiveColor ? `inactive-color="${wop.inactiveColor}"` : '',
switchWidth: (!isNaN(wop.switchWidth) && (wop.switchWidth !== 40)) ? `:width="${wop.switchWidth}"` : '',
rateMax: (!isNaN(wop.max) && (wop.max !== 5)) ? `:max="${wop.max}"` : '',
lowThreshold: (!isNaN(wop.lowThreshold) && (wop.lowThreshold !== 2)) ? `:low-threshold="${wop.lowThreshold}"` : '',
highThreshold: (!isNaN(wop.highThreshold) && (wop.highThreshold !== 4)) ? `:high-threshold="${wop.highThreshold}"` : '',
allowHalf: !!wop.allowHalf ? `allow-half` : '',
showText: !!wop.showText ? `show-text` : '',
showScore: !!wop.showScore ? `show-score` : '',
sliderMin: (!isNaN(wop.min) && (wop.min !== 0)) ? `:min="${wop.min}"` : '',
sliderMax: (!isNaN(wop.max) && (wop.max !== 100)) ? `:max="${wop.max}"` : '',
sliderStep: (!isNaN(wop.step) && (wop.step !== 1)) ? `:step="${wop.step}"` : '',
sliderRange: !!wop.range ? `range` : '',
sliderVertical: !!wop.vertical ? `vertical` : '',
uploadAction: !!wop.uploadURL ? `action="${wop.uploadURL}"` : '',
withCredentials: !!wop.withCredentials ? `with-credentials` : '',
multipleSelect: !!wop.multipleSelect ? `multiple` : '',
showFileList: !!wop.showFileList ? `show-file-list` : '',
limit: !isNaN(wop.limit) ? `:limit="${wop.limit}"` : '',
uploadTipSlotChild: !!wop.uploadTip ? `<template #tip><div class="el-upload__tip">${wop.uploadTip}</div></template>` : '',
pictureUploadIconChild: `<template #default><i class="el-icon-plus"></i></template>`,
fileUploadIconChild: `<template #default><i class="el-icon-plus"></i></template>`,
buttonType: !!wop.type ? `type="${wop.type}` : '',
buttonPlain: !!wop.plain ? `plain` : '',
buttonRound: !!wop.round ? `round` : '',
buttonCircle: !!wop.circle ? `circle` : '',
buttonIcon: !!wop.icon ? `icon="${wop.icon}"` : '',
contentPosition: (!!wop.contentPosition && (wop.contentPosition !== 'center')) ? `content-position="${wop.contentPosition}"` : '',
appendButtonChild: !!wop.appendButton ? `<template #append><el-button class="${wop.buttonIcon}" ${!!wop.appendButtonDisabled ? 'disabled' : ''}></el-button></template>` : '',
}
}
function buildRadioChildren(widget, formConfig) {
let wop = widget.options
const childTag = !!wop.buttonStyle ? 'el-radio-button' : 'el-radio'
const borderAttr = !!wop.border ? `border` : ''
const styleAttr = `style="{display: ${wop.displayStyle}}"`
return `<${childTag} v-for="(item, index) in ${wop.name}Options" :key="index" :label="item.value"
:disabled="item.disabled" ${borderAttr} ${styleAttr}>{{item.label}}</${childTag}>`
}
function buildCheckboxChildren(widget, formConfig) {
let wop = widget.options
const childTag = !!wop.buttonStyle ? 'el-checkbox-button' : 'el-checkbox'
const borderAttr = !!wop.border ? `border` : ''
const styleAttr = `style="{display: ${wop.displayStyle}}"`
return `<${childTag} v-for="(item, index) in ${wop.name}Options" :key="index" :label="item.value"
:disabled="item.disabled" ${borderAttr} ${styleAttr}>{{item.label}}</${childTag}>`
}
function buildSelectChildren(widget, formConfig) {
let wop = widget.options
const childTag = 'el-option'
return `<${childTag} v-for="(item, index) in ${wop.name}Options" :key="index" :label="item.value"
:value="item.value" :disabled="item.disabled"></${childTag}>`
}
const elTemplates = { //字段组件属性
'input': (widget, formConfig) => {
const {vModel, readonly, disabled, size, type, showPassword, placeholder, clearable,
minlength, maxlength, showWordLimit, prefixIcon, suffixIcon, appendButtonChild} = getElAttrs(widget, formConfig)
return `<el-input ${vModel} ${readonly} ${disabled} ${size} ${type} ${showPassword} ${placeholder} ${clearable}
${minlength} ${maxlength} ${showWordLimit} ${prefixIcon} ${suffixIcon}>${appendButtonChild}</el-input>`
},
'textarea': (widget, formConfig) => {
const {vModel, readonly, disabled, size, type, showPassword, placeholder, rows, clearable,
minlength, maxlength, showWordLimit} = getElAttrs(widget, formConfig)
return `<el-input type="textarea" ${vModel} ${readonly} ${disabled} ${size} ${type} ${showPassword} ${placeholder}
${rows} ${clearable} ${minlength} ${maxlength} ${showWordLimit}></el-input>`
},
'number': (widget, formConfig) => {
const {vModel, disabled, size, type, showPassword, placeholder, controlsPosition, min, max, precision, step
} = getElAttrs(widget, formConfig)
return `<el-input-number ${vModel} class="full-width-input" ${disabled} ${size} ${type} ${showPassword}
${placeholder} ${controlsPosition} ${min} ${max} ${precision} ${step}></el-input-number>`
},
'radio': (widget, formConfig) => {
const {vModel, disabled, size} = getElAttrs(widget, formConfig)
const radioOptions = buildRadioChildren(widget, formConfig)
return `<el-radio-group ${vModel} ${disabled} ${size}>${radioOptions}</el-radio-group>`
},
'checkbox': (widget, formConfig) => {
const {vModel, disabled, size} = getElAttrs(widget, formConfig)
const checkboxOptions = buildCheckboxChildren(widget, formConfig)
return `<el-checkbox-group ${vModel} ${disabled} ${size}>${checkboxOptions}</el-checkbox-group>`
},
'select': (widget, formConfig) => {
const {vModel, disabled, size, clearable, filterable, allowCreate, defaultFirstOption, automaticDropdown,
multiple, multipleLimit, remote, placeholder} = getElAttrs(widget, formConfig)
const selectOptions = buildSelectChildren(widget, formConfig)
return `<el-select ${vModel} class="full-width-input" ${disabled} ${size} ${clearable} ${filterable}
${allowCreate} ${defaultFirstOption} ${automaticDropdown} ${multiple} ${multipleLimit} ${placeholder}
${remote}>${selectOptions}</el-select>`
},
'time': (widget, formConfig) => {
const {vModel, readonly, disabled, size, placeholder, clearable, format, editable
} = getElAttrs(widget, formConfig)
return `<el-input ${vModel} class="full-width-input" ${readonly} ${disabled} ${size} ${format}
value-format="HH:mm:ss" ${placeholder} ${clearable} ${editable}></el-input>`
},
'time-range': (widget, formConfig) => {
const {vModel, readonly, disabled, size, startPlaceholder, endPlaceholder, clearable, format, editable
} = getElAttrs(widget, formConfig)
return `<el-input is-range ${vModel} class="full-width-input" ${readonly} ${disabled} ${size} ${format}
value-format="HH:mm:ss" ${startPlaceholder} ${endPlaceholder} ${clearable} ${editable}></el-input>`
},
'date': (widget, formConfig) => {
const {vModel, readonly, disabled, size, type, placeholder, clearable, format, valueFormat, editable
} = getElAttrs(widget, formConfig)
return `<el-input ${vModel} ${type} class="full-width-input" ${readonly} ${disabled} ${size} ${format}
${valueFormat} ${placeholder} ${clearable} ${editable}></el-input>`
},
'date-range': (widget, formConfig) => {
const {vModel, readonly, disabled, size, type, startPlaceholder, endPlaceholder, clearable, format, valueFormat, editable
} = getElAttrs(widget, formConfig)
return `<el-input is-range ${vModel} ${type} class="full-width-input" ${readonly} ${disabled} ${size} ${format}
${valueFormat} ${startPlaceholder} ${endPlaceholder} ${clearable} ${editable}></el-input>`
},
'switch': (widget, formConfig) => {
const {vModel, disabled, activeText, inactiveText, activeColor, inactiveColor, switchWidth
} = getElAttrs(widget, formConfig)
return `<el-switch ${vModel} ${disabled} ${activeText} ${inactiveText} ${activeColor} ${inactiveColor}
${switchWidth}></el-switch>`
},
'rate': (widget, formConfig) => {
const {vModel, disabled, rateMax, lowThreshold, highThreshold, allowHalf, showText,
showScore} = getElAttrs(widget, formConfig)
return `<el-rate ${vModel} ${disabled} ${rateMax} ${lowThreshold} ${highThreshold} ${allowHalf}
${showText} ${showScore}></el-rate>`
},
'color': (widget, formConfig) => {
const {vModel, disabled, size
} = getElAttrs(widget, formConfig)
return `<el-color-picker ${vModel} ${disabled} ${size}></el-color-picker>`
},
'slider': (widget, formConfig) => {
const {vModel, disabled, sliderMin, sliderMax, sliderStep, sliderRange, sliderVertical
} = getElAttrs(widget, formConfig)
return `<el-slider ${vModel} ${disabled} ${sliderMin} ${sliderMax} ${sliderStep} ${sliderRange}
${sliderVertical}></el-slider>`
},
'picture-upload': (widget, formConfig) => {
const {vModel, disabled, uploadAction, withCredentials, multipleSelect, showFileList, limit,
uploadTipSlotChild, pictureUploadIconChild} = getElAttrs(widget, formConfig)
let wop = widget.options
return `<el-upload :file-list="${wop.name}FileList" :headers="${wop.name}UploadHeaders" :data="${wop.name}UploadData"
${disabled} ${uploadAction} list-type="picture-card" ${withCredentials} ${multipleSelect} ${showFileList}
${limit}>${uploadTipSlotChild} ${pictureUploadIconChild}</el-upload>`
},
'file-upload': (widget, formConfig) => {
const {vModel, disabled, uploadAction, withCredentials, multipleSelect, showFileList, limit,
uploadTipSlotChild, fileUploadIconChild} = getElAttrs(widget, formConfig)
let wop = widget.options
return `<el-upload :file-list="${wop.name}FileList" :headers="${wop.name}UploadHeaders" :data="${wop.name}UploadData"
${disabled} ${uploadAction} list-type="picture-card" ${withCredentials} ${multipleSelect} ${showFileList}
${limit}>${uploadTipSlotChild} ${fileUploadIconChild}</el-upload>`
},
'rich-editor': (widget, formConfig) => {
const {vModel, disabled, placeholder
} = getElAttrs(widget, formConfig)
return `<vue-editor ${vModel} ${disabled} ${placeholder}></vue-editor>`
},
'cascader': (widget, formConfig) => {
const {vModel, disabled, size, clearable, filterable, placeholder} = getElAttrs(widget, formConfig)
let wop = widget.options
const optionsAttr = `:options="${wop.name}Options"`
return `<el-cascader ${vModel} class="full-width-input" ${optionsAttr} ${disabled} ${size} ${clearable}
${filterable} ${placeholder}></el-cascader>`
},
'static-text': (widget, formConfig) => {
return `<div>${widget.options.textContent}</div>`
},
'html-text': (widget, formConfig) => {
return `<div v-html="${widget.options.htmlContent}"></div>`
},
'button': (widget, formConfig) => {
const {buttonType, buttonPlain, buttonRound, buttonCircle, buttonIcon, disabled} = getElAttrs(widget, formConfig)
return `<el-button ${buttonType} ${buttonPlain} ${buttonRound} ${buttonCircle} ${buttonIcon}
${disabled}>${widget.options.label}</el-button>`
},
'divider': (widget, formConfig) => {
const {contentPosition} = getElAttrs(widget, formConfig)
return `<el-divider direction="horizontal" ${contentPosition}></el-divider>`
},
}
function buildFieldWidget(widget, formConfig) {
let wop = widget.options
const label = wop.labelHidden ? '' : wop.label
const labelWidthAttr = wop.labelHidden ? `label-width="0"` : (!!wop.labelWidth ? `label-width="${wop.labelWidth}px"` : '')
const labelTooltipAttr = wop.labelTooltip ? `title="${wop.labelTooltip}"` : ''
const propAttr = `prop="${wop.name}"`
let classArray = []
!!wop.required && classArray.push('required')
!!wop.customClass && (wop.customClass.length > 0) && classArray.push(wop.customClass.join(' '))
if (!!wop.labelAlign) {
wop.labelAlign !== 'label-left-align' && classArray.push(wop.labelAlign)
} else {
//classArray.push(formConfig.labelAlign || 'label-left-align')
formConfig.labelAlign !== 'label-left-align' && classArray.push(formConfig.labelAlign)
}
const classAttr = (classArray.length > 0) ? `class="${classArray.join(' ')}"` : ''
let customLabelDom =
`<template #label><span class="custom-label">${wop.labelIconPosition === 'front' ?
(!!wop.labelTooltip ?
`<el-tooltip content="${wop.labelTooltip}" effect="light"><i class="${wop.labelIconClass}"></i></el-tooltip>${wop.label}` :
`<i class="${wop.labelIconClass}"></i>${wop.label}`
)
:
(!!wop.labelTooltip ?
`${wop.label}<el-tooltip content="${wop.labelTooltip}" effect="light"><i class="${wop.labelIconClass}"></i></el-tooltip>` :
`${wop.label}<i class="${wop.labelIconClass}"></i>`
)
}
</span></template>`
!wop.labelIconClass && (customLabelDom = '')
const fwDom = elTemplates[widget.type] ? elTemplates[widget.type](widget, formConfig) : null
const isFormItem = !!widget.formItemFlag
const vShowAttr = !!wop.hidden ? `v-show="false"` : ''
return isFormItem ?
`<el-form-item label="${label}" ${labelWidthAttr} ${labelTooltipAttr} ${propAttr} ${classAttr} >
${customLabelDom}
${fwDom}
</el-form-item>`
:
`<div class="static-content-item" ${vShowAttr}>${fwDom}</div>`
}
function genTemplate(formConfig, widgetList, vue3Flag = false) {
const submitAttr = !!vue3Flag ? `@submit.prevent` : `@submit.native.prevent`
let childrenList = []
widgetList.forEach(wgt => {
if (wgt.category === 'container') {
childrenList.push( buildContainerWidget(wgt, formConfig) )
} else {
childrenList.push( buildFieldWidget(wgt, formConfig) )
}
})
const formTemplate =
` <el-form :model="${formConfig.modelName}" ref="${formConfig.refName}" :rules="${formConfig.rulesName}"
label-position="${formConfig.labelPosition}" label-width="${formConfig.labelWidth}px" size="${formConfig.size || 'medium'}"
${submitAttr}>
${!!childrenList ? childrenList.join('\n') : ''}
</el-form>`
return formTemplate
}
const genGlobalCSS = function (formConfig) {
const globalCssTemplate =
` .el-input-number.full-width-input, .el-cascader.full-width-input {
width: 100% !important;
}
.el-form-item--medium {
.el-radio {
line-height: 36px !important;
}
.el-rate{
margin-top: 8px;
}
}
.el-form-item--small {
.el-radio {
line-height: 32px !important;
}
.el-rate{
margin-top: 6px;
}
}
.el-form-item--mini {
.el-radio {
line-height: 28px !important;
}
.el-rate{
margin-top: 4px;
}
}
${formConfig.cssCode}`
return globalCssTemplate
}
const genScopedCSS = function (formConfig, vue3Flag = false) {
//const vDeep = !!vue3Flag ? `::v-deep` : `:deep`
const cssTemplate =
` div.table-container {
table.table-layout {
width: 100%;
table-layout: fixed;
border-collapse: collapse;
td.table-cell {
display: table-cell;
height: 36px;
border: 1px solid #e1e2e3;
}
}
}
div.tab-container {
}
.label-left-align ${!!vue3Flag ? `:deep(.el-form-item__label)` : `::v-deep .el-form-item__label`} {
text-align: left;
}
.label-center-align ${!!vue3Flag ? `:deep(.el-form-item__label)` : `::v-deep .el-form-item__label`} {
text-align: center;
}
.label-right-align ${!!vue3Flag ? `:deep(.el-form-item__label)` : `::v-deep .el-form-item__label`} {
text-align: right;
}
.custom-label {
}
.static-content-item {
min-height: 20px;
display: flex;
align-items: center;
${!!vue3Flag ? `:deep(.el-divider--horizontal)` : `::v-deep .el-divider--horizontal`} {
margin: 0;
}
}`
return cssTemplate
}
export const genSFC = function (formConfig, widgetList, beautifier, vue3Flag = false) {
const html = beautifier.html(genTemplate(formConfig, widgetList, vue3Flag), beautifierOpts.html)
const js = beautifier.js(!!vue3Flag ? genVue3JS(formConfig, widgetList): genVue2JS(formConfig, widgetList), beautifierOpts.js)
const globalCss = beautifier.css(genGlobalCSS(formConfig), beautifierOpts.css)
const scopedCss = beautifier.css(genScopedCSS(formConfig, vue3Flag), beautifierOpts.css)
return `<!--
Codes Generated By VForm:
http://www.vform666.com
-->
<template>
${html}
</template>
<script>
${js}
</script>
<style lang="scss">
${globalCss}
</style>
<style lang="scss" scoped>
${scopedCss}
</style>`
}

197
src/utils/util.js Normal file
View File

@ -0,0 +1,197 @@
import Clipboard from 'clipboard'
export function isNull(value) {
return (value === null) || (value === undefined);
}
export function isNotNull(value) {
return (value !== null) && (value !== undefined);
}
export function isEmptyStr(str) {
//return (str === undefined) || (!str) || (!/[^\s]/.test(str));
return (str === undefined) || (!str && (str !== 0) && (str !== '0')) || (!/[^\s]/.test(str));
}
export const generateId = function() {
return Math.floor(Math.random() * 100000 + Math.random() * 20000 + Math.random() * 5000);
};
export const deepClone = function (origin) {
return JSON.parse(JSON.stringify(origin))
}
export const overwriteObj = function(obj1, obj2) { /* 浅拷贝对象属性obj2覆盖obj1 */
// for (let prop in obj2) {
// if (obj2.hasOwnProperty(prop)) {
// obj1[prop] = obj2[prop]
// }
// }
Object.keys(obj2).forEach(prop => {
obj1[prop] = obj2[prop]
})
}
export const addWindowResizeHandler = function (handler) {
let oldHandler = window.onresize
if (typeof window.onresize != 'function') {
window.onresize = handler
} else {
window.onresize = function () {
oldHandler()
handler()
}
}
}
const createStyleSheet = function() {
let head = document.head || document.getElementsByTagName('head')[0];
let style = document.createElement('style');
style.type = 'text/css';
head.appendChild(style);
return style.sheet;
}
export const insertCustomCssToHead = function (cssCode) {
let head = document.getElementsByTagName('head')[0]
let oldStyle = document.getElementById('vform-custom-css')
if (!!oldStyle) {
head.removeChild(oldStyle) //应该先清除后插入!!
}
let newStyle = document.createElement('style')
newStyle.type = 'text/css'
newStyle.rel = 'stylesheet'
newStyle.id = 'vform-custom-css'
try {
newStyle.appendChild(document.createTextNode(cssCode))
} catch(ex) {
newStyle.styleSheet.cssText = cssCode
}
head.appendChild(newStyle)
}
export const insertGlobalFunctionsToHtml = function (functionsCode) {
let bodyEle = document.getElementsByTagName('body')[0]
let oldScriptEle = document.getElementById('v_form_global_functions')
!!oldScriptEle && bodyEle.removeChild(oldScriptEle)
let newScriptEle = document.createElement('script')
newScriptEle.id = 'v_form_global_functions'
newScriptEle.type = 'text/javascript'
newScriptEle.innerHTML = functionsCode
bodyEle.appendChild(newScriptEle)
}
export const optionExists = function(optionsObj, optionName) {
if (!optionsObj) {
return false
}
return Object.keys(optionsObj).indexOf(optionName) > -1
}
export const loadRemoteScript = function(srcPath, callback) { /*记载远程js加载成功后执行回调函数*/
let sid = encodeURIComponent(srcPath)
let oldScriptEle = document.getElementById(sid)
if (!oldScriptEle) {
let s = document.createElement('script')
s.src = srcPath
s.id = sid
document.body.appendChild(s)
s.onload = s.onreadystatechange = function (_, isAbort) { /* 借鉴自ace.js */
if (isAbort || !s.readyState || s.readyState === "loaded" || s.readyState === "complete") {
s = s.onload = s.onreadystatechange = null
if (!isAbort) {
callback()
}
}
}
}
}
export function traverseFieldWidgets(widgetList, handler) {
widgetList.map(w => {
if (w.formItemFlag) {
handler(w)
} else if (w.type === 'grid') {
w.cols.map(col => {
traverseFieldWidgets(col.widgetList, handler)
})
} else if (w.type === 'table') {
w.rows.map(row => {
row.cols.map(cell => {
traverseFieldWidgets(cell.widgetList, handler)
})
})
} else if (w.type === 'tab') {
w.tabs.map(tab => {
traverseFieldWidgets(tab.widgetList, handler)
})
} else if (w.type === 'sbu-form') {
traverseFieldWidgets(w.widgetList, handler)
}
})
}
export function traverseContainWidgets(widgetList, handler) {
widgetList.map(w => {
if (w.category === 'container') {
handler(w)
}
if (w.type === 'grid') {
w.cols.map(col => {
traverseContainWidgets(col.widgetList, handler)
})
} else if (w.type === 'table') {
w.rows.map(row => {
row.cols.map(cell => {
traverseContainWidgets(cell.widgetList, handler)
})
})
} else if (w.type === 'tab') {
w.tabs.map(tab => {
traverseContainWidgets(tab.widgetList, handler)
})
} else if (w.type === 'sbu-form') {
traverseContainWidgets(w.widgetList, handler)
}
})
}
export function copyToClipboard(content, clickEvent, $message, successMsg, errorMsg) {
const clipboard = new Clipboard(clickEvent.target, {
text: () => content
})
clipboard.on('success', () => {
$message.success(successMsg)
clipboard.destroy()
})
clipboard.on('error', () => {
$message.error(errorMsg)
clipboard.destroy()
})
clipboard.onClick(clickEvent)
}
export function getQueryParam(variable)
{
let query = window.location.search.substring(1);
let vars = query.split("&")
for (let i=0; i<vars.length; i++) {
let pair = vars[i].split("=")
if(pair[0] == variable) {
return pair[1]
}
}
return undefined;
}

127
src/utils/validators.js Normal file
View File

@ -0,0 +1,127 @@
import {isEmptyStr, isNull} from "./util";
export const getRegExp = function (validatorName) {
const commonRegExp = {
number: '/^\\d+(\\.\\d+)?$/',
letter: '/^[A-Za-z]$/',
letterAndNumber: '/^[A-Za-z0-9]+$/',
mobilePhone: '/^[1][3-9][0-9]{9}$/',
letterStartNumberIncluded: '/^[A-Za-z]+[A-Za-z\\d]*$/',
noChinese: '/^[^\u4e00-\u9fa5]$/',
chinese: '/^[\u4e00-\u9fa5]$/',
email: '/^([-_A-Za-z0-9.]+)@([_A-Za-z0-9]+\\.)+[A-Za-z0-9]{2,3}$/',
url: '/^([hH][tT]{2}[pP]:\\/\\/|[hH][tT]{2}[pP][sS]:\\/\\/)(([A-Za-z0-9-~]+)\\.)+([A-Za-z0-9-~\\/])+$/',
}
return commonRegExp[validatorName]
}
const validateFn = function (validatorName, rule, value, callback, defaultErrorMsg) {
//空值不校验
if (isNull(value) || (value.length <= 0)) {
callback()
return
}
const reg = eval(getRegExp(validatorName))
if (!reg.test(value)) {
let errTxt = rule.errorMsg || defaultErrorMsg
callback(new Error(errTxt))
} else {
callback()
}
}
const FormValidators = {
/* 数字 */
number(rule, value, callback) {
validateFn('number', rule, value, callback, '[' + rule.label + ']包含非数字字符')
},
/* 字母 */
letter(rule, value, callback) {
validateFn('letter', rule, value, callback, '[' + rule.label + ']包含非字母字符')
},
/* 字母和数字 */
letterAndNumber(rule, value, callback) {
validateFn('letterAndNumber', rule, value, callback, '[' + rule.label + ']只能输入字母或数字')
},
/* 手机号码 */
mobilePhone(rule, value, callback) {
validateFn('mobilePhone', rule, value, callback, '[' + rule.label + ']手机号码格式有误')
},
/* 禁止空白字符开头 */
noBlankStart(rule, value, callback) {
//暂未实现
},
/* 禁止空白字符结尾 */
noBlankEnd(rule, value, callback) {
//暂未实现
},
/* 字母开头,仅可包含数字 */
letterStartNumberIncluded(rule, value, callback) {
validateFn('letterStartNumberIncluded', rule, value, callback, '[' + rule.label + ']必须以字母开头,可包含数字')
},
/* 禁止中文输入 */
noChinese(rule, value, callback) {
validateFn('noChinese', rule, value, callback, '[' + rule.label + ']不可输入中文字符')
},
/* 必须中文输入 */
chinese(rule, value, callback) {
validateFn('chinese', rule, value, callback, '[' + rule.label + ']只能输入中文字符')
},
/* 电子邮箱 */
email(rule, value, callback) {
validateFn('email', rule, value, callback, '[' + rule.label + ']邮箱格式有误')
},
/* URL网址 */
url(rule, value, callback) {
validateFn('url', rule, value, callback, '[' + rule.label + ']URL格式有误')
},
/*
test(rule, value, callback, errorMsg) {
//空值不校验
if (isNull(value) || (value.length <= 0)) {
callback()
return
}
if (value < 100) {
callback(new Error('[' + rule.label + ']不能小于100'))
} else {
callback()
}
},
*/
regExp(rule, value, callback) {
//空值不校验
if (isNull(value) || (value.length <= 0)) {
callback()
return
}
const pattern = eval(rule.regExp)
if (!pattern.test(value)) {
let errTxt = rule.errorMsg || '[' + rule.label + ']invalid value'
callback(new Error(errTxt))
} else {
callback()
}
},
}
export default FormValidators

View File

@ -0,0 +1,144 @@
import {isNotNull, traverseContainWidgets, traverseFieldWidgets} from "@/utils/util";
import {translate} from "@/utils/i18n";
import FormValidators, {getRegExp} from "@/utils/validators";
export function buildDefaultValueListFn(formConfig, widgetList, resultList) {
return function(fieldWidget) {
const fop = fieldWidget.options
const fd = fop.defaultValue
if (isNotNull(fd)) {
resultList.push(`${fop.name}: ${JSON.stringify(fd)},`)
} else {
resultList.push(`${fop.name}: null,`)
}
}
}
export function buildRulesListFn(formConfig, widgetList, resultList) {
return function(fieldWidget) {
const fop = fieldWidget.options
let fieldRules = []
if (!!fop.required) {
fieldRules.push(`{
required: true,
message: '${translate('render.hint.fieldRequired')}',
}`)
}
if (!!fop.validation) {
let vldName = fop.validation
if (!!FormValidators[vldName]) {
fieldRules.push(`{
pattern: ${eval( getRegExp(vldName) )},
trigger: ['blur', 'change'],
message: '${fop.validationHint}'
}`)
} else {
fieldRules.push(`{
pattern: '${eval(vldName)}',
trigger: ['blur', 'change'],
message: '${fop.validationHint}'
}`)
}
}
//TODO: 自定义校验函数
fieldRules.length > 0 && resultList.push(`${fop.name}: [${fieldRules.join(',')}],`)
}
}
export function buildFieldOptionsFn(formConfig, widgetList, resultList) {
return function(fieldWidget) {
const fop = fieldWidget.options
const ft = fieldWidget.type
if ((ft === 'radio') || (ft === 'checkbox') || (ft === 'select') || (ft === 'cascader')) {
resultList.push(`${fop.name}Options: ${JSON.stringify(fop.optionItems)},`)
}
}
}
export function buildUploadDataFn(formConfig, widgetList, resultList) {
return function(fieldWidget) {
const fop = fieldWidget.options
const ft = fieldWidget.type
if ((ft === 'picture-upload') || (ft === 'file-upload')) {
resultList.push(`${fop.name}FileList: [],`)
resultList.push(`${fop.name}UploadHeaders: {},`)
resultList.push(`${fop.name}UploadData: {},`)
}
}
}
export function buildActiveTabs(formConfig, widgetList) {
let resultList = []
const handlerFn = function (cw) {
const cop = cw.options
const ct = cw.type
if (ct === 'tab') {
cw.tabs.length > 0 && resultList.push(`'${cop.name}ActiveTab': '${cw.tabs[0].options.name}',`)
}
}
traverseContainWidgets(widgetList, handlerFn)
return resultList
}
export const genVue2JS = function (formConfig, widgetList) {
let defaultValueList = []
let rulesList = []
let fieldOptions = []
let uploadData = []
traverseFieldWidgets(widgetList, (widget) => {
buildDefaultValueListFn(formConfig, widgetList, defaultValueList)(widget)
buildRulesListFn(formConfig, widgetList, rulesList)(widget)
buildFieldOptionsFn(formConfig, widgetList, fieldOptions)(widget)
buildUploadDataFn(formConfig, widgetList, uploadData)(widget)
})
const activeTabs = buildActiveTabs(formConfig, widgetList)
const v2JSTemplate =
` export default {
components: {},
props: {},
data() {
return {
${formConfig.modelName}: {
${defaultValueList.join('\n')}
},
${formConfig.rulesName}: {
${rulesList.join('\n')}
},
${activeTabs.join('\n')}
${fieldOptions.join('\n')}
${uploadData.join('\n')}
}
},
computed: {},
watch: {},
created() {
},
mounted() {
},
methods: {
submitForm() {
this.$refs['vForm'].validate(valid => {
if (!valid) return
//TODO: 提交表单
})
},
resetForm() {
this.$refs['vForm'].resetFields()
}
}
}`
return v2JSTemplate
}

View File

@ -0,0 +1,70 @@
import {
buildActiveTabs,
buildDefaultValueListFn,
buildFieldOptionsFn,
buildRulesListFn, buildUploadDataFn
} from "@/utils/vue2js-generator";
import {traverseFieldWidgets} from "@/utils/util";
export const genVue3JS = function (formConfig, widgetList) {
let defaultValueList = []
let rulesList = []
let fieldOptions = []
let uploadData = []
traverseFieldWidgets(widgetList, (widget) => {
buildDefaultValueListFn(formConfig, widgetList, defaultValueList)(widget)
buildRulesListFn(formConfig, widgetList, rulesList)(widget)
buildFieldOptionsFn(formConfig, widgetList, fieldOptions)(widget)
buildUploadDataFn(formConfig, widgetList, uploadData)(widget)
})
const activeTabs = buildActiveTabs(formConfig, widgetList)
const v3JSTemplate =
` import { defineComponent, toRefs, reactive, getCurrentInstance } from 'vue'
export default defineComponent({
components: {},
props: {},
setup() {
const state = reactive({
${formConfig.modelName}: {
${defaultValueList.join('\n')}
},
${formConfig.rulesName}: {
${rulesList.join('\n')}
},
${activeTabs.join('\n')}
${fieldOptions.join('\n')}
${uploadData.join('\n')}
})
const instance = getCurrentInstance()
const submitForm = () => {
instance.ctx.$refs['vForm'].validate(valid => {
if (!valid) return
//TODO: 提交表单
})
}
const resetForm = () => {
instance.ctx.$refs['vForm'].resetFields()
}
return {
...toRefs(state),
submitForm,
resetForm
}
}
})`
return v3JSTemplate
}

90
vue.config.js Normal file
View File

@ -0,0 +1,90 @@
'use strict'
const path = require('path')
const IS_PROD = process.env.NODE_ENV === 'production'
function resolve (dir) {
return path.join(__dirname, dir)
}
const npmConfigArgv = JSON.parse(process.env.npm_config_argv)
/*
console.log('npm config: ', npmConfigArgv)
const procArgv = process.argv
console.log('npm config: ', procArgv)
*/
let buildProdFlag = false
npmConfigArgv.original.forEach(cItem => {
if (cItem === 'build') {
buildProdFlag = true
}
})
const mvdir = require('mvdir');
if (IS_PROD && buildProdFlag) {
mvdir('index_template/index_prod.html', 'public/index.html', { copy: true });
} else {
mvdir('index_template/index_dev.html', 'public/index.html', { copy: true });
}
module.exports = {
assetsDir: './',
/* 开启vue运行时模板编译功能 */
runtimeCompiler: true,
lintOnSave: false,
productionSourceMap: false,
/* 指定node_modules目录中需要做babel转译的依赖库 */
transpileDependencies: [
'element-ui', 'vuedraggable',
],
css: {
loaderOptions: {
scss: {
/* 自动引入全局scss文件 */
prependData: `
@import "./src/styles/global.scss";
`
}
}
},
configureWebpack: (config) => {
config.devtool = 'source-map'
config.output.libraryExport = 'default' /* 解决import UMD打包文件时, 组件install方法执行报错的问题 */
if (IS_PROD && buildProdFlag) { /* 仅生产环境使用 */
/* CDN打包需要修改index.html加入CDN资源 */
config.externals = {
'vue': 'Vue',
'element-ui': 'ELEMENT',
//'quill': 'Quill',
}
}
},
chainWebpack: config => {
/* 配置svg图标自动加载 begin */
config.module
.rule('svg')
.exclude.add(resolve('src/icons'))
.end()
config.module
.rule('icons')
.test(/\.svg$/)
.include.add(resolve('src/icons'))
.end()
.use('svg-sprite-loader')
.loader('svg-sprite-loader')
.options({
symbolId: 'icon-[name]'
})
/* 配置svg图标自动加载 end */
},
}