├── src ├── styles │ ├── mixin.scss │ ├── index.scss │ └── home.scss ├── assets │ ├── logo.png │ └── icons │ │ └── svg │ │ ├── slider.svg │ │ ├── switch.svg │ │ ├── input.svg │ │ ├── textarea.svg │ │ ├── time.svg │ │ ├── row.svg │ │ ├── table.svg │ │ ├── checkbox.svg │ │ ├── line.svg │ │ ├── card.svg │ │ ├── radio.svg │ │ ├── select.svg │ │ ├── upload.svg │ │ ├── rate.svg │ │ ├── password.svg │ │ ├── rich-text.svg │ │ ├── date-range.svg │ │ ├── cascader.svg │ │ ├── button.svg │ │ ├── component.svg │ │ ├── time-range.svg │ │ ├── number.svg │ │ ├── date.svg │ │ └── color.svg ├── utils │ ├── common.js │ ├── cloneComponent.js │ ├── api.js │ ├── flow.js │ ├── func.js │ ├── utils.js │ ├── drawer.js │ ├── validate.js │ └── generate.js ├── apps │ ├── tool │ │ ├── dev.js │ │ └── Dev.vue │ ├── preview │ │ ├── main.js │ │ ├── App.vue │ │ ├── router.js │ │ ├── test.vue │ │ └── pages │ │ │ └── test.vue │ ├── App.vue │ └── main.js ├── components │ └── form │ │ ├── ui │ │ ├── element │ │ │ ├── base │ │ │ │ ├── elDivider.js │ │ │ │ ├── elScrollbar.js │ │ │ │ ├── elButton.js │ │ │ │ └── elRow.js │ │ │ └── form │ │ │ │ ├── elSwitch.js │ │ │ │ ├── elRate.js │ │ │ │ ├── elTimePicker.js │ │ │ │ ├── elColorPicker.js │ │ │ │ ├── elCheckboxGroup.js │ │ │ │ ├── elRadioGroup.js │ │ │ │ ├── elTimeSelect.js │ │ │ │ ├── elSlider.js │ │ │ │ ├── elInput.js │ │ │ │ ├── elInputNumber.js │ │ │ │ ├── elUpload.js │ │ │ │ └── elSelect.js │ │ ├── index.js │ │ └── helper.js │ │ ├── DraggableWarp.vue │ │ ├── SvgIcon │ │ └── index.vue │ │ ├── PagePanel.vue │ │ ├── icon.json │ │ ├── IconsDialog.vue │ │ ├── InputIcon.vue │ │ ├── OptionInput.vue │ │ ├── elementWarp │ │ └── UploadWarp.vue │ │ ├── PageSetting.vue │ │ ├── ElementRender.vue │ │ ├── PageDrawer.vue │ │ ├── RulesInput.vue │ │ └── PageGenerator.vue └── plugins │ └── svgBuilder.js ├── .gitignore ├── docs ├── favicon.ico └── assets │ ├── tool.c03c6ce8.css │ └── tool.7671f1aa.js ├── public └── favicon.ico ├── .idea ├── .gitignore ├── vcs.xml ├── modules.xml └── vue3-code-generator.iml ├── jsconfig.json ├── index.html ├── tool.html ├── preview.html ├── package.json ├── vite.config.js └── README.md /src/styles/mixin.scss: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | dist 4 | *.local 5 | package-lock.json -------------------------------------------------------------------------------- /docs/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yupk/vue3-code-generator/HEAD/docs/favicon.ico -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yupk/vue3-code-generator/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yupk/vue3-code-generator/HEAD/src/assets/logo.png -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # 默认忽略的文件 2 | /shelf/ 3 | /workspace.xml 4 | # 基于编辑器的 HTTP 客户端请求 5 | /httpRequests/ 6 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/utils/common.js: -------------------------------------------------------------------------------- 1 | export function debug (drawingList) { 2 | 3 | const log = function (event) { 4 | //console.log(drawingList) 5 | //console.log(event) 6 | } 7 | 8 | return { log } 9 | } 10 | 11 | -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es6", 4 | "baseUrl": ".", 5 | "paths": { 6 | "@/*": ["src/*"] 7 | } 8 | }, 9 | "exclude": ["node_modules", "dist"], 10 | "include": ["src/**/*"] 11 | } 12 | -------------------------------------------------------------------------------- /src/apps/tool/dev.js: -------------------------------------------------------------------------------- 1 | import { createApp } from 'vue' 2 | import Dev from './Dev.vue' 3 | import ElementPlus from 'element-plus' 4 | import 'element-plus/theme-chalk/index.css' 5 | const app = createApp(Dev); 6 | app.use(ElementPlus); 7 | app.mount('#app'); 8 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/apps/preview/main.js: -------------------------------------------------------------------------------- 1 | import { createApp } from 'vue' 2 | import App from './App.vue' 3 | import ElementPlus from 'element-plus' 4 | import 'element-plus/theme-chalk/index.css' 5 | import router from './router'; 6 | 7 | 8 | const app = createApp(App); 9 | app.use(ElementPlus); 10 | app.use(router) 11 | app.mount('#app'); 12 | -------------------------------------------------------------------------------- /src/apps/preview/App.vue: -------------------------------------------------------------------------------- 1 | 7 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/apps/App.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 页面编辑器 9 | 10 | 11 | 12 | 13 |
14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /tool.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Vite App 9 | 10 | 11 | 12 | 13 |
14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /preview.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Vite App 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /.idea/vue3-code-generator.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/assets/icons/svg/slider.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/utils/cloneComponent.js: -------------------------------------------------------------------------------- 1 | import {deepClone, randFieldId} from "@/utils/func.js"; 2 | 3 | export default function (props) { 4 | function copy(origin) { 5 | let new_element = deepClone(origin); 6 | console.log("fieldName", new_element); 7 | new_element.__ID = randFieldId(); 8 | 9 | if ("fieldName" in new_element.attrs) { 10 | new_element.attrs.fieldName.__val__ = new_element.__ID; 11 | } 12 | 13 | new_element.__formId = props.formId; 14 | 15 | return new_element; 16 | } 17 | 18 | const onEnd = function (obj) { 19 | //console.log(obj); 20 | }; 21 | 22 | return {copy, onEnd}; 23 | } 24 | -------------------------------------------------------------------------------- /src/assets/icons/svg/switch.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/assets/tool.c03c6ce8.css: -------------------------------------------------------------------------------- 1 | .el-container{display:flex;flex-direction:row;flex:1;flex-basis:auto;box-sizing:border-box;min-width:0}.el-container.is-vertical{flex-direction:column}.el-aside{--el-aside-width:300px;overflow:auto;box-sizing:border-box;flex-shrink:0;width:var(--el-aside-width)}.el-footer{--el-footer-padding:0 20px;--el-footer-height:60px;padding:var(--el-footer-padding);box-sizing:border-box;flex-shrink:0;height:var(--el-footer-height)}.el-header{--el-header-padding:0 20px;--el-header-height:60px;padding:var(--el-header-padding);box-sizing:border-box;flex-shrink:0;height:var(--el-header-height)}.el-main{--el-main-padding:20px;display:block;flex:1;flex-basis:auto;overflow:auto;box-sizing:border-box;padding:var(--el-main-padding)} 2 | -------------------------------------------------------------------------------- /src/assets/icons/svg/input.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/icons/svg/textarea.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/icons/svg/time.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/icons/svg/row.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/form/ui/element/base/elDivider.js: -------------------------------------------------------------------------------- 1 | import helper from "@/components/form/ui/helper.js"; 2 | 3 | export default { 4 | '__ID': '', 5 | tag: "el-divider", 6 | name: "分割线", 7 | __openRules: false, 8 | tagIcon: "line", 9 | __text: helper.input_text("文本内容", "分割线"), 10 | slots: { 11 | 12 | }, 13 | 14 | props: {}, 15 | ctrlBtn: true, 16 | attrs: { 17 | fieldName: helper.input_text("字段名", "字段名"), 18 | 'direction': helper.input_radio('方向', [{ "key": "horizontal", "value": "horizontal" }, { "key": "vertical", "value": "vertical" }], 'horizontal'), 19 | 'content-position': helper.input_radio('对齐', [{ "key": "left", "value": "left" }, { "key": "right", "value": "right" }, { "key": "center", "value": "center" }], 'center') 20 | } 21 | } -------------------------------------------------------------------------------- /src/components/form/ui/element/form/elSwitch.js: -------------------------------------------------------------------------------- 1 | import helper from "@/components/form/ui/helper.js"; 2 | 3 | export default { 4 | tag: "el-switch", 5 | name: "Switch 开关", 6 | __openRules: false, 7 | tagIcon: "switch", 8 | ctrlBtn: true, 9 | formItem: { 10 | showLabel: helper.input_boolean("显示 label", true), 11 | labelWidth: helper.input_number("label 宽", 100), 12 | label: helper.input_text("label", 'Switch 开关', ), 13 | }, 14 | attrs: { 15 | fieldName: helper.input_text("字段名", '字段名'), 16 | 'disabled': helper.input_boolean('是否禁用', false), 17 | 'loading': helper.input_boolean('是否显示加载中', false), 18 | 'validate-event': helper.input_boolean('改变 switch 状态时是否触发表单的校验', true) 19 | }, slots: { 20 | }, 21 | props: {}, 22 | childrens: [] 23 | } -------------------------------------------------------------------------------- /src/assets/icons/svg/table.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/form/DraggableWarp.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 40 | -------------------------------------------------------------------------------- /src/components/form/ui/element/form/elRate.js: -------------------------------------------------------------------------------- 1 | import helper from "@/components/form/ui/helper.js"; 2 | 3 | export default { 4 | tag: "el-rate", 5 | name: "Rate 评分", 6 | __openRules: true, 7 | tagIcon: "rate", 8 | defaultvalue: "", 9 | ctrlBtn: true, 10 | formItem: { 11 | showLabel: helper.input_boolean("显示 label", true), 12 | labelWidth: helper.input_number("label 宽", 100), 13 | label: helper.input_text("label", 'Rate 评分', ), 14 | }, 15 | attrs: { 16 | fieldName: helper.input_text("字段名", '字段名'), 17 | 'disabled': helper.input_boolean('是否为只读', false), 18 | 'allow-half': helper.input_boolean('是否允许半选', false), 19 | 'show-text': helper.input_boolean('是否显示辅助文字,若为真,则会从 texts 数组中选取当前分数对应的文字内容', false), 20 | 'show-score': helper.input_boolean('是否显示当前分数, show-score 和 show-text 不能同时为真', false) 21 | }, 22 | slots: { 23 | }, 24 | props: {}, 25 | childrens: [] 26 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "page-generator", 3 | "version": "0.0.1", 4 | "scripts": { 5 | "dev": "vite", 6 | "build": "vite build" 7 | }, 8 | "dependencies": { 9 | "@element-plus/icons-vue": "latest", 10 | "axios": "^0.21.1", 11 | "element-plus": "^1.2.0-beta.1", 12 | "element-theme-chalk": "^2.15.5", 13 | "file-saver": "^2.0.5", 14 | "highlight.js": "^11.3.1", 15 | "js-beautify": "^1.14.0", 16 | "jsplumb": "^2.15.6", 17 | "sass": "^1.3.2", 18 | "vue": "^3.2.0", 19 | "vue-clipboard3": "^1.0.1", 20 | "vue-draggable-next": "latest", 21 | "vue-router": "^4.0.12" 22 | }, 23 | "devDependencies": { 24 | "@vitejs/plugin-vue": "^1.0.4", 25 | "@vue/compiler-sfc": "^3.2.0", 26 | "node-sass": "^6.0.1", 27 | "sass": "^1.35.2", 28 | "sass-loader": "^12.1.0", 29 | "sass-resources-loader": "^2.2.3", 30 | "style-loader": "^3.1.0", 31 | "unplugin-element-plus": "^0.3.2", 32 | "unplugin-vue-components": "^0.18.0", 33 | "vite": "^2.0.0-beta.12", 34 | "vite-plugin-style-import": "^1.0.1" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/components/form/ui/element/form/elTimePicker.js: -------------------------------------------------------------------------------- 1 | import helper from "@/components/form/ui/helper.js"; 2 | 3 | export default { 4 | tag: "el-time-picker", 5 | name: "时间选择器", 6 | __openRules: false, 7 | tagIcon: "time", 8 | ctrlBtn: true, 9 | formItem: { 10 | showLabel: helper.input_boolean("显示 label", true), 11 | labelWidth: helper.input_number("label 宽", 100), 12 | label: helper.input_text("label", '时间选择器', ), 13 | }, 14 | attrs: { 15 | fieldName: helper.input_text("字段名", '字段名'), 16 | 'readonly': helper.input_boolean('完全只读', false), 17 | 'disabled': helper.input_boolean('禁用', false), 18 | 'editable': helper.input_boolean('文本框可输入', true), 19 | 'clearable': helper.input_boolean('是否显示清除按钮', true), 20 | 'is-range': helper.input_boolean('是否为时间范围选择', false), 21 | 'arrow-control': helper.input_boolean('是否使用箭头进行时间选择', false) 22 | }, slots: { 23 | }, 24 | props: {}, 25 | childrens: [] 26 | } -------------------------------------------------------------------------------- /src/components/form/ui/element/form/elColorPicker.js: -------------------------------------------------------------------------------- 1 | import helper from "@/components/form/ui/helper.js"; 2 | 3 | export default { 4 | tag: "el-color-picker", 5 | name: "颜色拾取", 6 | __openRules: false, 7 | tagIcon: "color", 8 | ctrlBtn: true, 9 | defaultvalue:"#000000", 10 | formItem: { 11 | showLabel: helper.input_boolean("显示 label", true), 12 | labelWidth: helper.input_number("label 宽", 100), 13 | label: helper.input_text("label", '颜色拾取',), 14 | }, 15 | attrs: { 16 | fieldName: helper.input_text("字段名", '字段名'), 17 | 'disabled': helper.input_boolean('是否禁用', false), 18 | 'size': helper.input_radio('尺寸', [{ "key": "medium", "value": "medium" }, { "key": "small", "value": "small" }, { "key": "mini", "value": "mini" }], ''), 19 | 'show-alpha': helper.input_boolean('是否支持透明度选择', false), 20 | 'color-format': helper.input_radio('写入 v-model 的颜色的格式', [{ "key": "hsl", "value": "hsl" }, { "key": "hsv", "value": "hsv" }, { "key": "hex", "value": "hex" }, { "key": "rgb", "value": "rgb" }], 'rgb') 21 | }, 22 | slots: { 23 | }, 24 | props: {}, 25 | childrens:[] 26 | } -------------------------------------------------------------------------------- /src/components/form/ui/element/form/elCheckboxGroup.js: -------------------------------------------------------------------------------- 1 | import helper from "@/components/form/ui/helper.js"; 2 | 3 | export default { 4 | tag: "el-checkbox-group", 5 | name: "复选框", 6 | __openRules: false, 7 | tagIcon: 'checkbox', 8 | __ID: '', 9 | defaultvalue: [1, 2], 10 | 11 | formItem: { 12 | showLabel: helper.input_boolean("显示 label", true), 13 | labelWidth: helper.input_number("label 宽", 100), 14 | label: helper.input_text("label", '单选框',), 15 | }, 16 | attrs: { 17 | fieldName: helper.input_text("字段名", '字段名'), 18 | disabled: helper.input_boolean("是否禁用", false), 19 | border: helper.input_boolean("占位字符", true), 20 | 21 | size: helper.input_radio("尺寸", [{ 22 | key: "medium", 23 | value: "中等" 24 | }, { 25 | key: "small", 26 | value: "较小" 27 | }, { 28 | key: "mini", 29 | value: "迷你" 30 | }], "medium"), 31 | 32 | }, 33 | 34 | __opt__: helper.input_opt("选择项", 'el-checkbox-button'), 35 | props: { 36 | 37 | 38 | }, 39 | childrens:[], 40 | ctrlBtn: true, 41 | 42 | } -------------------------------------------------------------------------------- /src/components/form/ui/element/form/elRadioGroup.js: -------------------------------------------------------------------------------- 1 | import helper from "@/components/form/ui/helper.js"; 2 | 3 | export default { 4 | tag: "el-radio-group", 5 | name: "单选框", 6 | __openRules: false, 7 | tagIcon: 'radio', 8 | __ID: '', 9 | 10 | defaultvalue: '', 11 | formItem: { 12 | showLabel: helper.input_boolean("显示 label", true), 13 | labelWidth: helper.input_number("label 宽", 100), 14 | label: helper.input_text("label", '单选框',), 15 | 16 | }, 17 | attrs: { 18 | fieldName: helper.input_text("字段名", '字段名'), 19 | disabled: helper.input_boolean("是否禁用", false), 20 | border: helper.input_boolean("占位字符", true), 21 | 22 | size: helper.input_radio("尺寸", [{ 23 | key: "medium", 24 | value: "中等" 25 | }, { 26 | key: "small", 27 | value: "较小" 28 | }, { 29 | key: "mini", 30 | value: "迷你" 31 | }], "medium"), 32 | }, 33 | __opt__: helper.input_opt("选择项", 'el-radio-button'), 34 | 35 | slots: { 36 | }, 37 | props:{}, 38 | childrens: [], 39 | ctrlBtn: true, 40 | 41 | rule: { 42 | 43 | } 44 | } -------------------------------------------------------------------------------- /vite.config.js: -------------------------------------------------------------------------------- 1 | import ElementPlus from 'unplugin-element-plus/vite' 2 | import Components from 'unplugin-vue-components/vite' 3 | import vue from '@vitejs/plugin-vue' 4 | 5 | import { ElementPlusResolver } from 'unplugin-vue-components/resolvers' 6 | import { 7 | defineConfig 8 | } from 'vite' 9 | import { 10 | svgBuilder 11 | } from './src/plugins/svgBuilder'; 12 | 13 | const { 14 | join, 15 | resolve 16 | } = require('path') 17 | 18 | const D = (s) => { 19 | 20 | return join(resolve("src"), s.substr(2)) 21 | } 22 | 23 | export default defineConfig({ 24 | 25 | build: { 26 | rollupOptions: { 27 | input: { 28 | main: "./index.html", 29 | tool: "./tool.html" 30 | 31 | }, 32 | }, 33 | }, 34 | 35 | resolve: { 36 | alias: [{ 37 | find: (new RegExp(/^@\/.+/)), 38 | replacement: D 39 | }], 40 | }, 41 | plugins: [ 42 | vue(), 43 | ElementPlus({ 44 | importStyle: 'sass', 45 | useSource: true 46 | }), 47 | Components({ 48 | resolvers: [ElementPlusResolver()] 49 | }), 50 | svgBuilder('./src/assets/icons/svg/') 51 | ] 52 | }) -------------------------------------------------------------------------------- /src/assets/icons/svg/checkbox.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/icons/svg/line.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/apps/main.js: -------------------------------------------------------------------------------- 1 | import { 2 | createApp 3 | } from 'vue'; 4 | import App from './App.vue'; 5 | import ElementPlus from 'element-plus'; 6 | import 'element-plus/theme-chalk/index.css'; 7 | import SvgIcon from '@/components/form/SvgIcon/index.vue'; 8 | import { 9 | VueDraggableNext 10 | } from "vue-draggable-next"; 11 | import DraggableWarp from "@/components/form/DraggableWarp.vue"; 12 | import OptionInput from "@/components/form/OptionInput.vue"; 13 | import UploadWarp from "@/components/form/elementWarp/UploadWarp.vue"; 14 | import zhCn from 'element-plus/es/locale/lang/zh-cn'; 15 | import hljs from 'highlight.js'; 16 | import 'highlight.js/styles/googlecode.css' //样式文件 17 | import { ElIcon } from 'element-plus'; 18 | 19 | const app = createApp(App); 20 | app.use(ElementPlus, { 21 | locale: zhCn, 22 | }); 23 | 24 | app.component("draggable", VueDraggableNext); 25 | app.component("draggable-warp", DraggableWarp); 26 | app.component("option-input", OptionInput); 27 | app.component("svg-icon", SvgIcon); 28 | app.component("upload-warp", UploadWarp); 29 | 30 | 31 | app.directive('highlight', function (el) { 32 | let blocks = el.querySelectorAll('pre code'); 33 | setTimeout(() => { 34 | blocks.forEach((block) => { 35 | hljs.highlightBlock(block) 36 | }) 37 | }, 200) 38 | }) 39 | 40 | app.mount('#app'); -------------------------------------------------------------------------------- /src/assets/icons/svg/card.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/icons/svg/radio.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/icons/svg/select.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/form/ui/element/form/elTimeSelect.js: -------------------------------------------------------------------------------- 1 | import helper from "@/components/form/ui/helper.js"; 2 | 3 | export default { 4 | tag: "el-time-select", 5 | name: "时间选择", 6 | __openRules: false, 7 | tagIcon: "time-range", 8 | defaultvalue: "", 9 | ctrlBtn: true, 10 | formItem: { 11 | showLabel: helper.input_boolean("显示 label", true), 12 | labelWidth: helper.input_number("label 宽", 100), 13 | label: helper.input_text("label", 'TimeSelect 时间选择',), 14 | }, 15 | attrs: { 16 | fieldName: helper.input_text("字段名", "字段名"), 17 | 'disabled': helper.input_boolean('禁用', false), 18 | 'editable': helper.input_boolean('文本框可输入', true), 19 | 'clearable': helper.input_boolean('是否显示清除按钮', true), 20 | 'size': helper.input_radio('输入框尺寸', [{ 21 | "key": "medium", 22 | "value": "medium" 23 | }, { 24 | "key": "small", 25 | "value": "small" 26 | }, { 27 | "key": "mini", 28 | "value": "mini" 29 | }], '') 30 | }, slots: { 31 | }, 32 | props: {}, 33 | childrens: [] 34 | } -------------------------------------------------------------------------------- /src/assets/icons/svg/upload.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/form/ui/element/form/elSlider.js: -------------------------------------------------------------------------------- 1 | import helper from "@/components/form/ui/helper.js"; 2 | 3 | export default { 4 | tag: "el-slider", 5 | name: "Slider 滑块", 6 | __openRules: false, 7 | tagIcon: "slider", 8 | ctrlBtn: true, 9 | formItem: { 10 | showLabel: helper.input_boolean("显示 label", true), 11 | labelWidth: helper.input_number("label 宽", 100), 12 | label: helper.input_text("label", "Slider 滑块"), 13 | }, 14 | attrs: { 15 | fieldName: helper.input_text("字段名", "字段名"), 16 | disabled: helper.input_boolean("是否禁用", false), 17 | "show-input": helper.input_boolean( 18 | "是否显示输入框,仅在非范围选择时有效", 19 | false 20 | ), 21 | "show-input-controls": helper.input_boolean( 22 | "在显示输入框的情况下,是否显示输入框的控制按钮", 23 | true 24 | ), 25 | "show-stops": helper.input_boolean("是否显示间断点", false), 26 | "show-tooltip": helper.input_boolean("是否显示 tooltip", true), 27 | range: helper.input_boolean("是否为范围选择", false), 28 | vertical: helper.input_boolean("是否竖向模式", false), 29 | }, 30 | slots: {}, 31 | props: {}, 32 | childrens: [], 33 | }; 34 | -------------------------------------------------------------------------------- /src/components/form/ui/element/form/elInput.js: -------------------------------------------------------------------------------- 1 | import helper from "@/components/form/ui/helper.js"; 2 | 3 | export default { 4 | tag: "el-input", 5 | name: "单行文本", 6 | __openRules: true, 7 | tagIcon: 'input', 8 | __ID: '', 9 | defaultvalue: '', 10 | 11 | formItem: { 12 | showLabel: helper.input_boolean("显示 label", true), 13 | labelWidth: helper.input_number("label 宽", 100), 14 | label: helper.input_text("label", '单行文本',), 15 | 16 | }, 17 | attrs: { 18 | fieldName: helper.input_text("字段名", '字段名'), 19 | placeholder: helper.input_text("占位字符", '请输入'), 20 | "prefix-icon": helper.input_icon("框头部图标", ''), 21 | "suffix-icon": helper.input_icon("框头部图标", ''), 22 | type: helper.input_select("类型", [{ 23 | key: "text", 24 | value: "文本" 25 | }, { 26 | key: "textarea", 27 | value: "多行文本" 28 | }, { 29 | key: "password", 30 | value: "密码" 31 | }, { 32 | key: "number", 33 | value: "数字" 34 | }], "text") 35 | 36 | 37 | }, 38 | 39 | props: { 40 | 41 | }, 42 | 43 | childrens: [], 44 | ctrlBtn: true, 45 | slots: { 46 | 47 | "prefix": helper.input_text("框头部内容", ''), 48 | "suffix": helper.input_text("框尾部内容", ''), 49 | "prepend": helper.input_text("框前置内容", ''), 50 | "append": helper.input_text("框后置内容", ''), 51 | }, 52 | 53 | rule: { 54 | required: helper.input_boolean("必填", false), 55 | 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /src/components/form/ui/element/base/elScrollbar.js: -------------------------------------------------------------------------------- 1 | import helper from "@/components/form/ui/helper.js"; 2 | 3 | export default { 4 | tag: "draggable", 5 | defaultvalue: [], 6 | props: { 7 | component: "el-scrollbar", 8 | group: "componentsGroup", 9 | class: "drag-wrapper box", 10 | style: { 11 | "margin-left": "0px", 12 | "margin-right": "0px" 13 | }, 14 | animation: 340, 15 | }, 16 | slots: { 17 | }, 18 | 19 | ctrlBtn: false, 20 | name: "滚动条", 21 | __openRules: false, 22 | tagIcon: 'input', 23 | __ID: '', 24 | 25 | attrs: { 26 | 'height': helper.input_text('滚动条高度', '100'), 27 | 'max-height': helper.input_text('滚动条最大高度', ''), 28 | 'native': helper.input_boolean('是否使用原生滚动条样式', false), 29 | 'wrap-style': helper.input_text('包裹容器的自定义样式', ''), 30 | 'wrap-class': helper.input_text('包裹容器的自定义类名', ''), 31 | 'view-style': helper.input_text('视图的自定义样式', ''), 32 | 'view-class': helper.input_text('视图的自定义类名', ''), 33 | 'noresize': helper.input_boolean('不响应容器尺寸变化,如果容器尺寸不会发生变化,最好设置它可以优化性能', false), 34 | 'tag': helper.input_text('视图的元素标签', 'div'), 35 | 'always': helper.input_boolean('滚动条总是显示', false), 36 | 'min-size': helper.input_text('滚动条最小尺寸', '20') 37 | }, 38 | childrens: [ 39 | 40 | 41 | ], 42 | } -------------------------------------------------------------------------------- /src/components/form/ui/element/form/elInputNumber.js: -------------------------------------------------------------------------------- 1 | import helper from "@/components/form/ui/helper.js"; 2 | 3 | export default { 4 | tag: "el-input-number", 5 | name: "单行文本", 6 | __openRules: false, 7 | tagIcon: 'number', 8 | __ID: '', 9 | defaultvalue: 0, 10 | 11 | 12 | formItem: { 13 | showLabel: helper.input_boolean("显示 label", true), 14 | labelWidth: helper.input_number("label 宽", 100), 15 | label: helper.input_text("label", '单行文本',), 16 | }, 17 | attrs: { 18 | fieldName: helper.input_text("字段名", '字段名'), 19 | placeholder: helper.input_text("占位字符", '请输入'), 20 | "prefix-icon": helper.input_icon("框头部图标", ''), 21 | "suffix-icon": helper.input_icon("框头部图标", ''), 22 | type: helper.input_select("类型", [{ 23 | key: "text", 24 | value: "文本" 25 | }, { 26 | key: "textarea", 27 | value: "多行文本" 28 | }, { 29 | key: "password", 30 | value: "密码" 31 | }, { 32 | key: "number", 33 | value: "数字" 34 | }], "text") 35 | 36 | 37 | }, 38 | 39 | props: { 40 | 41 | }, 42 | childrens:[], 43 | ctrlBtn: true, 44 | slots: { 45 | 46 | "prefix": helper.input_text("框头部内容", ''), 47 | "suffix": helper.input_text("框尾部内容", ''), 48 | "prepend": helper.input_text("框前置内容", ''), 49 | "append": helper.input_text("框后置内容", ''), 50 | }, 51 | 52 | rule: { 53 | required: helper.input_boolean("必填", false), 54 | regList: { 55 | type: "reg" 56 | } 57 | } 58 | } -------------------------------------------------------------------------------- /src/utils/api.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | 3 | 4 | axios.defaults.timeout = 10000; //响应时间 5 | import { 6 | ElMessage 7 | } from 'element-plus'; 8 | 9 | axios.defaults.baseURL = ''; //配置接口地址 10 | // 请求拦截器(在发送请求之前做些什么) 11 | axios.interceptors.request.use(function (config) { 12 | let token = localStorage.getItem('token'); 13 | if (token) { 14 | config.headers['security-hash'] = token; 15 | } 16 | return config; 17 | }, function (error) { 18 | // 对请求错误做些什么 19 | ElMessage.error('请求错误,请重试'); 20 | return Promise.reject(error); 21 | }); 22 | 23 | 24 | export class Api { 25 | static Callback(res, resolve) { 26 | if (res.data.code !== 0) { 27 | ElMessage.error(res.data.data.msg); 28 | } else { 29 | resolve(res.data); 30 | } 31 | } 32 | 33 | static Get(url, param) { 34 | return new Promise((resolve, reject) => { 35 | axios.get(url, { 36 | params: param 37 | }) 38 | .then(res => { 39 | Api.Callback(res, resolve) 40 | }) 41 | .catch(error => { 42 | reject(error) 43 | }) 44 | }) 45 | } 46 | 47 | static Post(url, params, config) { 48 | return new Promise((resolve, reject) => { 49 | axios.post(url, params, config) 50 | .then(res => { 51 | Api.Callback(res, resolve); 52 | }) 53 | .catch(error => { 54 | reject(error) 55 | }) 56 | }) 57 | } 58 | 59 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![image](https://ae01.alicdn.com/kf/U51bfb661aba945b48a4c71774421d414C.gif) 2 | ## 简介 3 | 拖拽生成 vue3 表单 4 | ## 在线预览 5 | [地址](https://yupk.github.io/vue3-code-generator/) 6 | 7 | ## 使用方法 8 | 9 | main.js 10 | ``` 11 | 12 | import { 13 | createApp 14 | } from 'vue'; 15 | import App from './App.vue'; 16 | import ElementPlus from 'element-plus'; 17 | import 'element-plus/theme-chalk/index.css'; 18 | import SvgIcon from '@/components/form/SvgIcon/index.vue'; 19 | import { 20 | VueDraggableNext 21 | } from "vue-draggable-next"; 22 | import DraggableWarp from "@/components/form/DraggableWarp.vue"; 23 | import OptionInput from "@/components/form/OptionInput.vue"; 24 | import UploadWarp from "@/components/form/elementWarp/UploadWarp.vue"; 25 | import zhCn from 'element-plus/es/locale/lang/zh-cn'; 26 | import hljs from 'highlight.js'; 27 | import 'highlight.js/styles/googlecode.css' //样式文件 28 | 29 | 30 | const app = createApp(App); 31 | app.use(ElementPlus, { 32 | locale: zhCn, 33 | }); 34 | 35 | app.component("draggable", VueDraggableNext); 36 | app.component("draggable-warp", DraggableWarp); 37 | app.component("option-input", OptionInput); 38 | app.component("svg-icon", SvgIcon); 39 | app.component("upload-warp", UploadWarp); 40 | 41 | 42 | app.directive('highlight', function (el) { 43 | let blocks = el.querySelectorAll('pre code'); 44 | setTimeout(() => { 45 | blocks.forEach((block) => { 46 | hljs.highlightBlock(block) 47 | }) 48 | }, 200) 49 | }) 50 | 51 | app.mount('#app'); 52 | 53 | ``` 54 | 然后就可以在项目任意处调用表单设计器了 55 | 如: 56 | ``` 57 | 63 | ``` 64 | 65 | 66 | -------------------------------------------------------------------------------- /src/assets/icons/svg/rate.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/form/SvgIcon/index.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 55 | 56 | 71 | -------------------------------------------------------------------------------- /src/components/form/ui/index.js: -------------------------------------------------------------------------------- 1 | import helper from "@/components/form/ui/helper.js"; 2 | 3 | 4 | // 表单属性【右面板】 5 | const formConf = { 6 | tag: "el-form", 7 | props: {}, childrens: [], 8 | __rules: {}, 9 | attrs: { 10 | __formRef: helper.input_text("表单名", 'refForm'), 11 | __formModel: helper.input_text("表单模型", 'formData'), 12 | size: helper.input_radio("表单尺寸", [{ 13 | key: "medium", 14 | value: "中等" 15 | }, { 16 | key: "small", 17 | value: "较小" 18 | }, { 19 | key: "mini", 20 | value: "迷你" 21 | }], "medium"), 22 | labelPosition: helper.input_radio("标签对齐", [{ 23 | key: "right", 24 | value: "右对齐" 25 | }, { 26 | key: "left", 27 | value: "右对齐" 28 | }, { 29 | key: "top", 30 | value: "顶部对齐" 31 | }], "right"), 32 | labelWidth: helper.input_number("标签宽度", 100), 33 | disabled: helper.input_boolean("禁用", false), 34 | inline: helper.input_boolean("行内模式", false), 35 | hideRequiredAsterisk: helper.input_boolean("必填标星", false), 36 | __formBtns: helper.input_boolean("是否显示按钮", true), 37 | }, 38 | } 39 | 40 | 41 | const elements = { 42 | base: {eles: [], title: "基本组件"}, 43 | form: {eles: [], title: "表单组件"}, 44 | }; 45 | 46 | let files = 47 | import.meta.globEager('./element/form/*.js'); 48 | 49 | 50 | for (const key in files) { 51 | elements.form.eles.push(files[key].default) 52 | } 53 | files = import.meta.globEager('./element/base/*.js'); 54 | 55 | 56 | for (const key in files) { 57 | elements.base.eles.push(files[key].default) 58 | } 59 | 60 | 61 | export { 62 | elements, 63 | formConf 64 | }; -------------------------------------------------------------------------------- /src/apps/preview/router.js: -------------------------------------------------------------------------------- 1 | 2 | import { createRouter, createWebHashHistory } from "vue-router"; 3 | const home = () => import('./test.vue'); 4 | 5 | 6 | const pages = import.meta.globEager('./pages/*.vue'); 7 | 8 | console.log(pages) 9 | // const login = () => import('./pages/login.vue'); 10 | // const AddRule = () => import('./pages/AddRule.vue'); 11 | const routes = [ 12 | 13 | { 14 | path: '/', 15 | component: home, 16 | },] 17 | for (let path in pages){ 18 | 19 | console.log(path,pages[path]) 20 | 21 | routes.push({ path: path.substr(1), component: pages[path].default}) 22 | } 23 | // const routes = [ 24 | 25 | // { 26 | // path: '/', 27 | // component: home, 28 | // }, 29 | // { 30 | // path: '/home', 31 | // name: 'home', 32 | // component: home, 33 | // meta: { 34 | // name: '首页', 35 | // title: '首页- 后台系统', 36 | // needLogin: true, //需要登录 37 | // } 38 | 39 | 40 | // }, 41 | 42 | // { 43 | // path: '/login', 44 | // name: 'login', 45 | // component: login, 46 | // meta: { 47 | // name: '首页', 48 | // title: '首页- 后台系统', 49 | // needLogin: false, //需要登录 50 | // }, 51 | // props: route => { return route.query } 52 | // }, 53 | 54 | // { 55 | // path: '/addRule', 56 | // name: 'AddRule', 57 | // component: AddRule, 58 | // meta: { 59 | // name: '新建方案', 60 | // title: '新建方案', 61 | // needLogin: true, //需要登录 62 | // }, 63 | 64 | // }, 65 | 66 | 67 | 68 | // ]; 69 | 70 | 71 | const router = createRouter({ 72 | history: createWebHashHistory(), 73 | routes: routes 74 | }) 75 | 76 | export default router 77 | -------------------------------------------------------------------------------- /src/assets/icons/svg/password.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/form/ui/element/form/elUpload.js: -------------------------------------------------------------------------------- 1 | import helper from "@/components/form/ui/helper.js"; 2 | import elButton from "@/components/form/ui/element/base/elButton"; 3 | export default { 4 | '__ID': '', 5 | tag: "upload-warp", 6 | name: "上传文件", 7 | __openRules: false, 8 | tagIcon: "input", 9 | ctrlBtn: true, 10 | defaultvalue: "", 11 | formItem: { 12 | showLabel: helper.input_boolean("显示 label", true), 13 | labelWidth: helper.input_number("label 宽", 100), 14 | label: helper.input_text("label", '上传文件',), 15 | }, 16 | attrs: { 17 | fieldName: helper.input_text("字段名", "字段名"), 18 | 19 | 'uploadType': helper.input_radio('上传类型', [{ "value": "图片", "key": "image" }, { "value": "文件", "key": "file" }], 'image'), 20 | 21 | 'action': helper.input_text('上传接口', 'https://www.vkandian.cn/index.php/index/upload'), 22 | 'multiple': helper.input_boolean('多文件上传', ''), 23 | 'name': helper.input_text('文件字段名', 'file'), 24 | 'tip': helper.input_text('上传提示', ''), 25 | 26 | 'show-file-list': helper.input_boolean('是否显示文件列表', true), 27 | 'drag': helper.input_boolean('拖拽上传', false), 28 | 'accept': helper.input_text('文件类型', ''), 29 | 'thumbnail-mode': helper.input_boolean('显示缩略图', false), 30 | 'list-type': helper.input_radio('列表模式', [{ "key": "text", "value": "text" }, { "key": "picture", "value": "picture" }, { "key": "picture-card", "value": "picture-card" }], 'text'), 31 | 'auto-upload': helper.input_boolean('自动上传', true), 32 | 'disabled': helper.input_boolean('禁用', false), 33 | 'limit': helper.input_number('最多可上传文件', 1) 34 | }, 35 | slots: { 36 | }, 37 | props: {}, 38 | childrens: [] 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | } -------------------------------------------------------------------------------- /src/utils/flow.js: -------------------------------------------------------------------------------- 1 | import { 2 | jsPlumb 3 | } from "jsplumb"; 4 | 5 | const connectorStyle = { 6 | ConnectionsDetachable: false, 7 | MaxConnections: 100, 8 | //连线类型 9 | Anchors: ['Bottom', 'Top'], 10 | Connector: ["Flowchart"], 11 | 12 | Endpoints: [ 13 | ["Dot", { 14 | radius: 4 15 | }], 16 | ["Dot", { 17 | radius: 5 18 | }] 19 | ], 20 | EndpointStyles: [{ 21 | fill: "#3296fa" 22 | }, { 23 | fill: "#3296fa" 24 | }] 25 | 26 | 27 | }; 28 | 29 | const lines = { 30 | condition: [ 31 | 32 | ], 33 | default: [ 34 | 35 | ['Label', { 36 | label: '', 37 | cssClass: '', 38 | labelStyle: { 39 | color: 'red' 40 | }, 41 | 42 | events: { 43 | click: function (diamondOverlay, originalEvent) { 44 | console.log("double click on diamond overlay for : ", diamondOverlay.component); 45 | } 46 | } 47 | 48 | }], 49 | ] 50 | } 51 | 52 | 53 | export default { 54 | 55 | 56 | install: (app, options) => { 57 | const plumbIns = jsPlumb.getInstance(); 58 | 59 | plumbIns.importDefaults(connectorStyle); 60 | app.provide("plumbIns", plumbIns); 61 | 62 | app.config.globalProperties.$plumbIns = () => { 63 | console.log(1) 64 | }; 65 | app.directive('flow', function (el, binding) { 66 | 67 | let lineType = binding.arg; 68 | 69 | let ele = binding.value; 70 | 71 | if (ele && ele.father) { 72 | for (let f of ele.father) { 73 | plumbIns.connect({ 74 | target: el, 75 | source: f, 76 | overlays: lines[lineType], 77 | 78 | }); 79 | } 80 | 81 | } 82 | 83 | }) 84 | 85 | } 86 | } -------------------------------------------------------------------------------- /src/components/form/PagePanel.vue: -------------------------------------------------------------------------------- 1 | 31 | 32 | 70 | 71 | 76 | -------------------------------------------------------------------------------- /src/components/form/ui/element/base/elButton.js: -------------------------------------------------------------------------------- 1 | import helper from "@/components/form/ui/helper.js"; 2 | 3 | export default { 4 | '__ID': '', 5 | tag: "el-button", 6 | name: "按钮", 7 | __openRules: false, 8 | tagIcon: "button", 9 | ctrlBtn: true, 10 | __text: helper.input_text("按钮文本", "提交"), 11 | slots: { 12 | }, 13 | props:{}, 14 | attrs: { 15 | fieldName: helper.input_text("字段名", "字段名"), 16 | 'size': helper.input_radio('尺寸', [{ 17 | "key": "medium", 18 | "value": "medium" 19 | }, { 20 | "key": "small", 21 | "value": "small" 22 | }, { 23 | "key": "mini", 24 | "value": "mini" 25 | }], ''), 26 | 'type': helper.input_radio('类型', [{ 27 | "key": "primary", 28 | "value": "primary" 29 | }, { 30 | "key": "success", 31 | "value": "success" 32 | }, { 33 | "key": "warning", 34 | "value": "warning" 35 | }, { 36 | "key": "danger", 37 | "value": "danger" 38 | }, { 39 | "key": "info", 40 | "value": "info" 41 | }, { 42 | "key": "text", 43 | "value": "text" 44 | }], ''), 45 | 'plain': helper.input_boolean('是否朴素按钮', false), 46 | 'round': helper.input_boolean('是否圆角按钮', false), 47 | 'circle': helper.input_boolean('是否圆形按钮', false), 48 | 'loading': helper.input_boolean('是否加载中状态', false), 49 | 'disabled': helper.input_boolean('是否禁用状态', false), 50 | 'autofocus': helper.input_boolean('是否默认聚焦', false) 51 | } 52 | } -------------------------------------------------------------------------------- /src/assets/icons/svg/rich-text.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/icons/svg/date-range.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/icons/svg/cascader.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/icons/svg/button.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/icons/svg/component.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/apps/preview/test.vue: -------------------------------------------------------------------------------- 1 | 27 | -------------------------------------------------------------------------------- /src/components/form/ui/element/form/elSelect.js: -------------------------------------------------------------------------------- 1 | import helper from "@/components/form/ui/helper.js"; 2 | 3 | 4 | const elOption = { 5 | tag: "el-option", 6 | name: "eloption", 7 | __openRules: false, 8 | tagIcon: "input", 9 | ctrlBtn: true, 10 | attrs: { 11 | fieldName: helper.input_text("字段名", "字段名"), 12 | 'disabled': helper.input_boolean('是否禁用该选项', false) 13 | }, 14 | slots: { 15 | }, 16 | props: {}, 17 | childrens: [] 18 | } 19 | 20 | export default { 21 | tag: "el-select", 22 | name: "select选择", 23 | __openRules: false, 24 | tagIcon: "select", 25 | ctrlBtn: true, 26 | defaultvalue: "", 27 | 28 | __opt__: helper.input_opt("选择项", 'el-option'), 29 | 30 | formItem: { 31 | showLabel: helper.input_boolean("显示 label", true), 32 | labelWidth: helper.input_number("label 宽", 100), 33 | label: helper.input_text("label", 'select选择',), 34 | }, 35 | attrs: { 36 | fieldName: helper.input_text("字段名", "字段名"), 37 | 'multiple': helper.input_boolean('是否多选', false), 38 | 'disabled': helper.input_boolean('是否禁用', false), 39 | 'size': helper.input_radio('输入框尺寸', [{ "key": "medium", "value": "medium" }, { "key": "small", "value": "small" }, { "key": "mini", "value": "mini" }], 'large'), 40 | 'clearable': helper.input_boolean('是否可以清空选项', false), 41 | 'collapse-tags': helper.input_boolean('多选时是否将选中值按文字的形式展示', false), 42 | 'filterable': helper.input_boolean('是否可搜索', false), 43 | 'allow-create': helper.input_boolean('是否允许用户创建新条目, 注意此时filterable必须为真。', false), 44 | 'remote': helper.input_boolean('是否为远程搜索', false), 45 | 'loading': helper.input_boolean('是否正在从远程获取数据', false), 46 | 'reserve-keyword': helper.input_boolean('多选且可搜索时,是否在选中一个选项后保留当前的搜索关键词', false), 47 | 'default-first-option': helper.input_boolean('在输入框按下回车,选择第一个匹配项。 需配合 filterable 或 remote 使用', false), 48 | 'popper-append-to-body': helper.input_boolean('是否将弹出框插入至 body 元素。 在弹出框的定位出现问题时,可将该属性设置为 false', true), 49 | 'automatic-dropdown': helper.input_boolean('对于不可搜索的 Select,是否在输入框获得焦点后自动弹出选项菜单', false) 50 | }, slots: { 51 | }, 52 | props: {}, 53 | childrens: [] 54 | } -------------------------------------------------------------------------------- /src/components/form/ui/element/base/elRow.js: -------------------------------------------------------------------------------- 1 | import helper from "@/components/form/ui/helper.js"; 2 | 3 | const elCol = { 4 | tag: "draggable", 5 | name: "栅格", 6 | __openRules: false, 7 | tagIcon: 'row', 8 | __ID: '', 9 | 10 | defaultvalue: [], 11 | attrs: { 12 | span: helper.input_range("列数", 1, 24, 8), 13 | offset: helper.input_range("左侧间隔", 0, 24, 0), 14 | push: helper.input_range("右移动", 0, 24, 0), 15 | pull: helper.input_range("左移动", 0, 24, 0), 16 | }, 17 | props: { 18 | style: { 19 | "min-height": "60px" 20 | }, 21 | component: "el-col", 22 | group: "componentsGroup", 23 | class: "drag-wrapper box", 24 | animation: 340, 25 | }, 26 | childrens: [ 27 | ], 28 | ctrlBtn: true, 29 | slots: { 30 | } 31 | } 32 | 33 | export default { 34 | tag: "el-row", 35 | defaultvalue: [], 36 | props: { 37 | component: "el-row", 38 | group: "componentsGroup", 39 | class: "drag-wrapper box", 40 | style: { 41 | "margin-left": "0px", 42 | "margin-right": "0px" 43 | }, 44 | animation: 340, 45 | }, 46 | ctrlBtn: true, 47 | name: "布局组件", 48 | __openRules: false, 49 | tagIcon: 'input', 50 | __ID: '', 51 | attrs: { 52 | gutter: helper.input_range("栅格间隔", 1, 24, 24), 53 | justify: helper.input_radio("justify", [{ 54 | key: 'start', 55 | value: 'start' 56 | }, 57 | { 58 | key: 'end', 59 | value: 'end' 60 | }, 61 | { 62 | key: 'center', 63 | value: 'center' 64 | }, 65 | { 66 | key: 'space-around', 67 | value: 'space-around' 68 | }, 69 | { 70 | key: 'space-between', 71 | value: 'space-between' 72 | } 73 | ], "start"), 74 | 75 | align: helper.input_radio("align", [{ 76 | key: 'top', 77 | value: 'top' 78 | }, { 79 | key: 'middle', 80 | value: 'middle' 81 | }, { 82 | key: 'bottom', 83 | value: 'bottom' 84 | }]), 85 | }, 86 | 87 | actions: { 88 | "增加列": function (current) { 89 | current.childrens.push(helper.cloneItem(elCol)) 90 | } 91 | }, 92 | 93 | childrens: [ 94 | helper.cloneItem(elCol) 95 | ], 96 | slots: {} 97 | } -------------------------------------------------------------------------------- /src/plugins/svgBuilder.js: -------------------------------------------------------------------------------- 1 | import { readFileSync, readdirSync } from 'fs' 2 | 3 | let idPerfix = '' 4 | const svgTitle = /+].*?)>/ 5 | const clearHeightWidth = /(width|height)="([^>+].*?)"/g 6 | 7 | const hasViewBox = /(viewBox="[^>+].*?")/g 8 | 9 | const clearReturn = /(\r)|(\n)/g 10 | 11 | function findSvgFile(dir) { 12 | const svgRes = [] 13 | const dirents = readdirSync(dir, { 14 | withFileTypes: true 15 | }) 16 | for (const dirent of dirents) { 17 | if (dirent.isDirectory()) { 18 | svgRes.push(...findSvgFile(dir + dirent.name + '/')) 19 | } else { 20 | const svg = readFileSync(dir + dirent.name) 21 | .toString() 22 | .replace(clearReturn, '') 23 | .replace(svgTitle, ($1, $2) => { 24 | // //console.log(++i) 25 | // //console.log(dirent.name) 26 | let width = 0 27 | let height = 0 28 | let content = $2.replace( 29 | clearHeightWidth, 30 | (s1, s2, s3) => { 31 | if (s2 === 'width') { 32 | width = s3 33 | } else if (s2 === 'height') { 34 | height = s3 35 | } 36 | return '' 37 | } 38 | ) 39 | if (!hasViewBox.test($2)) { 40 | content += `viewBox="0 0 ${width} ${height}"` 41 | } 42 | return `` 46 | }) 47 | .replace('', '') 48 | svgRes.push(svg) 49 | } 50 | } 51 | return svgRes 52 | } 53 | 54 | export const svgBuilder = (path, perfix = 'icon') => { 55 | if (path === '') return 56 | idPerfix = perfix 57 | const res = findSvgFile(path) 58 | // //console.log(res.length) 59 | // const res = [] 60 | return { 61 | name: 'svg-transform', 62 | transformIndexHtml(html) { 63 | return html.replace( 64 | '', 65 | ` 66 | 67 | 68 | ${res.join('')} 69 | 70 | ` 71 | ) 72 | } 73 | } 74 | } -------------------------------------------------------------------------------- /src/utils/func.js: -------------------------------------------------------------------------------- 1 | function randomString(length) { 2 | var str = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; 3 | var result = ''; 4 | for (var i = length; i > 0; --i) 5 | result += str[Math.floor(Math.random() * str.length)]; 6 | return result; 7 | } 8 | 9 | export function toHump(name) { 10 | return name.replace(/\_?\-(\w)/g, function (all, letter) { 11 | return letter.toUpperCase(); 12 | }); 13 | } 14 | export function randFieldId() { 15 | 16 | return `field_${randomString(5)}`; 17 | } 18 | // 深拷贝对象 19 | export function deepClone(obj) { 20 | const _toString = Object.prototype.toString 21 | 22 | // null, undefined, non-object, function 23 | if (!obj || typeof obj !== 'object') { 24 | return obj 25 | } 26 | 27 | // DOM Node 28 | if (obj.nodeType && 'cloneNode' in obj) { 29 | return obj.cloneNode(true) 30 | } 31 | 32 | // Date 33 | if (_toString.call(obj) === '[object Date]') { 34 | return new Date(obj.getTime()) 35 | } 36 | 37 | // RegExp 38 | if (_toString.call(obj) === '[object RegExp]') { 39 | const flags = [] 40 | if (obj.global) { 41 | flags.push('g') 42 | } 43 | if (obj.multiline) { 44 | flags.push('m') 45 | } 46 | if (obj.ignoreCase) { 47 | flags.push('i') 48 | } 49 | 50 | return new RegExp(obj.source, flags.join('')) 51 | } 52 | 53 | const result = Array.isArray(obj) ? [] : obj.constructor ? new obj.constructor() : {} 54 | 55 | for (const key in obj) { 56 | result[key] = deepClone(obj[key]) 57 | } 58 | 59 | return result 60 | } 61 | 62 | const toStr = Function.prototype.call.bind(Object.prototype.toString) 63 | export function isObjectObject(t) { 64 | return toStr(t) === '[object Object]' 65 | } 66 | export function isObjectArray(t) { 67 | return toStr(t) === '[object Array]' 68 | } 69 | export function isObjectNull(t) { 70 | return toStr(t) === '[object Null]' 71 | } 72 | export function isObjectUnde(t) { 73 | return toStr(t) === '[object Undefined]' 74 | } 75 | 76 | export function isNumber(t) { 77 | return toStr(t) === '[object Number]' 78 | } 79 | export function isStr(t) { 80 | return toStr(t) === '[object String]' 81 | } 82 | 83 | export const findEle = function (items, _id) { 84 | for (let item of items) { 85 | if (item.__ID == _id) { 86 | return item; 87 | } else if (isObjectArray(item.childrens)) { 88 | let el = findEle(item.childrens, _id); 89 | if (el) { 90 | return el; 91 | } 92 | } 93 | } 94 | 95 | return false; 96 | }; -------------------------------------------------------------------------------- /src/assets/icons/svg/time-range.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/icons/svg/number.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/form/ui/helper.js: -------------------------------------------------------------------------------- 1 | import { 2 | randFieldId, 3 | deepClone 4 | } from "@/utils/func.js"; 5 | 6 | const cloneItem = function (item) { 7 | const newitem = deepClone(item); 8 | newitem.__ID = randFieldId() 9 | return newitem; 10 | } 11 | const input_text = function (name, val) { 12 | 13 | return { 14 | __val__: val, 15 | input_type: "input_text", 16 | label: name 17 | } 18 | 19 | } 20 | 21 | const input_slot_text = function (name, val) { 22 | let ini = input_text(name, val); 23 | ini.tag = "text"; 24 | return ini; 25 | } 26 | 27 | const input_number = function (name, val) { 28 | 29 | return { 30 | __val__: val, 31 | input_type: "input_number", 32 | label: name 33 | } 34 | 35 | } 36 | 37 | const input_boolean = function (name, val) { 38 | 39 | return { 40 | __val__: val, 41 | input_type: "input_boolean", 42 | label: name 43 | } 44 | 45 | } 46 | 47 | 48 | const input_icon = function (name, val) { 49 | 50 | return { 51 | __val__: val, 52 | input_type: "input_icon", 53 | label: name 54 | } 55 | 56 | } 57 | 58 | 59 | const input_range = function (label, min, max, val) { 60 | 61 | return { 62 | __val__: val, 63 | input_type: "input_range", 64 | label, 65 | min, 66 | max 67 | } 68 | 69 | } 70 | 71 | 72 | export const input_radio = function (label, opts, val) { 73 | const __child = { 74 | tag: "el-radio-button", 75 | keyValue: { 76 | "key": "label", 77 | "value": "__default__" 78 | } 79 | } 80 | 81 | return { 82 | __val__: val, 83 | input_type: "input_radio", 84 | label, 85 | opts, 86 | __child 87 | } 88 | 89 | } 90 | const input_select = function (label, opts, val) { 91 | const __child = { 92 | tag: "el-option", 93 | keyValue: { 94 | "key": "value", 95 | "value": "label" 96 | } 97 | } 98 | 99 | 100 | return { 101 | __val__: val, 102 | input_type: "input_select", 103 | label, 104 | opts, 105 | __child 106 | } 107 | 108 | } 109 | 110 | const optValue = function (tag) { 111 | 112 | return { 113 | type: 'static', 114 | tag: tag, 115 | staticData: [{ 116 | key: "选项一", 117 | value: 1 118 | }, { 119 | key: "选项二", 120 | value: 2 121 | }], 122 | dynamicData: { 123 | url: "http://", 124 | medth: "post", 125 | keyName: "keyName", 126 | valueName: "valueName", 127 | } 128 | } 129 | 130 | } 131 | const input_opt = function (label, tag) { 132 | 133 | return { 134 | __val__: optValue(tag), 135 | input_type: "input_opt", 136 | label 137 | 138 | } 139 | } 140 | 141 | export default { 142 | input_text, input_icon, input_boolean, input_number, input_opt, input_radio, input_range, 143 | input_select, input_slot_text, cloneItem, optValue 144 | } -------------------------------------------------------------------------------- /src/components/form/icon.json: -------------------------------------------------------------------------------- 1 | ["platform-eleme","eleme","delete-solid","delete","s-tools","setting","user-solid","user","phone","phone-outline","more","more-outline","star-on","star-off","s-goods","goods","warning","warning-outline","question","info","remove","circle-plus","success","error","zoom-in","zoom-out","remove-outline","circle-plus-outline","circle-check","circle-close","s-help","help","minus","plus","check","close","picture","picture-outline","picture-outline-round","upload","upload2","download","camera-solid","camera","video-camera-solid","video-camera","message-solid","bell","s-cooperation","s-order","s-platform","s-fold","s-unfold","s-operation","s-promotion","s-home","s-release","s-ticket","s-management","s-open","s-shop","s-marketing","s-flag","s-comment","s-finance","s-claim","s-custom","s-opportunity","s-data","s-check","s-grid","menu","share","d-caret","caret-left","caret-right","caret-bottom","caret-top","bottom-left","bottom-right","back","right","bottom","top","top-left","top-right","arrow-left","arrow-right","arrow-down","arrow-up","d-arrow-left","d-arrow-right","video-pause","video-play","refresh","refresh-right","refresh-left","finished","sort","sort-up","sort-down","rank","loading","view","c-scale-to-original","date","edit","edit-outline","folder","folder-opened","folder-add","folder-remove","folder-delete","folder-checked","tickets","document-remove","document-delete","document-copy","document-checked","document","document-add","printer","paperclip","takeaway-box","search","monitor","attract","mobile","scissors","umbrella","headset","brush","mouse","coordinate","magic-stick","reading","data-line","data-board","pie-chart","data-analysis","collection-tag","film","suitcase","suitcase-1","receiving","collection","files","notebook-1","notebook-2","toilet-paper","office-building","school","table-lamp","house","no-smoking","smoking","shopping-cart-full","shopping-cart-1","shopping-cart-2","shopping-bag-1","shopping-bag-2","sold-out","sell","present","box","bank-card","money","coin","wallet","discount","price-tag","news","guide","male","female","thumb","cpu","link","connection","open","turn-off","set-up","chat-round","chat-line-round","chat-square","chat-dot-round","chat-dot-square","chat-line-square","message","postcard","position","turn-off-microphone","microphone","close-notification","bangzhu","time","odometer","crop","aim","switch-button","full-screen","copy-document","mic","stopwatch","medal-1","medal","trophy","trophy-1","first-aid-kit","discover","place","location","location-outline","location-information","add-location","delete-location","map-location","alarm-clock","timer","watch-1","watch","lock","unlock","key","service","mobile-phone","bicycle","truck","ship","basketball","football","soccer","baseball","wind-power","light-rain","lightning","heavy-rain","sunrise","sunrise-1","sunset","sunny","cloudy","partly-cloudy","cloudy-and-sunny","moon","moon-night","dish","dish-1","food","chicken","fork-spoon","knife-fork","burger","tableware","sugar","dessert","ice-cream","hot-water","water-cup","coffee-cup","cold-drink","goblet","goblet-full","goblet-square","goblet-square-full","refrigerator","grape","watermelon","cherry","apple","pear","orange","coffee","ice-tea","ice-drink","milk-tea","potato-strips","lollipop","ice-cream-square","ice-cream-round"] -------------------------------------------------------------------------------- /src/assets/icons/svg/date.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/styles/index.scss: -------------------------------------------------------------------------------- 1 | $editorTabsborderColor: #121315; 2 | body, html{ 3 | margin: 0; 4 | padding: 0; 5 | background: #fff; 6 | -moz-osx-font-smoothing: grayscale; 7 | -webkit-font-smoothing: antialiased; 8 | text-rendering: optimizeLegibility; 9 | font-family: -apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji; 10 | } 11 | 12 | input, textarea{ 13 | font-family: -apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji; 14 | } 15 | 16 | .editor-tabs{ 17 | background: $editorTabsborderColor; 18 | .el-tabs__header{ 19 | margin: 0; 20 | border-bottom-color: $editorTabsborderColor; 21 | .el-tabs__nav{ 22 | border-color: $editorTabsborderColor; 23 | } 24 | } 25 | .el-tabs__item{ 26 | height: 32px; 27 | line-height: 32px; 28 | color: #888a8e; 29 | border-left: 1px solid $editorTabsborderColor!important; 30 | background: #363636; 31 | margin-right: 5px; 32 | user-select: none; 33 | } 34 | .el-tabs__item.is-active{ 35 | background: #1e1e1e; 36 | border-bottom-color: #1e1e1e!important; 37 | color: #fff; 38 | } 39 | .el-icon-edit{ 40 | color: #f1fa8c; 41 | } 42 | .el-icon-document{ 43 | color: #a95812; 44 | } 45 | :focus.is-active.is-focus:not(:active) { 46 | box-shadow: none; 47 | border-radius: 0; 48 | } 49 | } 50 | 51 | // home 52 | .right-scrollbar { 53 | .el-scrollbar__view { 54 | padding: 12px 18px 15px 15px; 55 | } 56 | } 57 | .el-scrollbar__wrap { 58 | box-sizing: border-box; 59 | overflow-x: hidden !important; 60 | margin-bottom: 0 !important; 61 | } 62 | 63 | 64 | .center-tabs{ 65 | padding-left: 10px;; 66 | .el-tabs__header{ 67 | margin-bottom: 0!important; 68 | } 69 | .el-tabs__item{ 70 | width: 50%; 71 | text-align: center; 72 | } 73 | .el-tabs__nav{ 74 | width: 100%; 75 | } 76 | } 77 | .reg-item{ 78 | padding: 12px 6px; 79 | background: #f8f8f8; 80 | position: relative; 81 | border-radius: 4px; 82 | .close-btn{ 83 | position: absolute; 84 | right: -6px; 85 | top: -6px; 86 | display: block; 87 | width: 16px; 88 | height: 16px; 89 | line-height: 16px; 90 | background: rgba(0, 0, 0, 0.2); 91 | border-radius: 50%; 92 | color: #fff; 93 | text-align: center; 94 | z-index: 1; 95 | cursor: pointer; 96 | font-size: 12px; 97 | &:hover{ 98 | background: rgba(210, 23, 23, 0.5) 99 | } 100 | } 101 | & + .reg-item{ 102 | margin-top: 18px; 103 | } 104 | } 105 | .action-bar{ 106 | & .el-button+.el-button { 107 | margin-left: 15px; 108 | } 109 | & i { 110 | font-size: 20px; 111 | vertical-align: middle; 112 | position: relative; 113 | top: -1px; 114 | } 115 | } 116 | 117 | .custom-tree-node{ 118 | width: 100%; 119 | font-size: 14px; 120 | .node-operation{ 121 | float: right; 122 | } 123 | i[class*="el-icon"] + i[class*="el-icon"]{ 124 | margin-left: 6px; 125 | } 126 | .el-icon-plus{ 127 | color: #409EFF; 128 | } 129 | .el-icon-delete{ 130 | color: #157a0c; 131 | } 132 | } 133 | 134 | .el-scrollbar__view{ 135 | overflow-x: hidden; 136 | } 137 | 138 | .el-rate{ 139 | display: inline-block; 140 | vertical-align: text-top; 141 | } 142 | .el-upload__tip{ 143 | line-height: 1.2; 144 | } -------------------------------------------------------------------------------- /src/components/form/IconsDialog.vue: -------------------------------------------------------------------------------- 1 | 35 | 83 | 140 | -------------------------------------------------------------------------------- /src/assets/icons/svg/color.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/form/InputIcon.vue: -------------------------------------------------------------------------------- 1 | 35 | 91 | 141 | -------------------------------------------------------------------------------- /src/components/form/OptionInput.vue: -------------------------------------------------------------------------------- 1 | 2 | 76 | 77 | 114 | 115 | 117 | -------------------------------------------------------------------------------- /src/utils/utils.js: -------------------------------------------------------------------------------- 1 | import { 2 | isObjectObject 3 | } from "@/utils/func.js"; 4 | const slotParser = { 5 | 6 | default(conf) { 7 | let childrens = [] 8 | for (let db of conf.opts) { 9 | let attr = {} 10 | let slots = {} 11 | for (let k in conf.__child.keyValue) { 12 | if ('__default__' == conf.__child.keyValue[k]) { 13 | slots['default'] = db[k]; 14 | } else { 15 | attr[conf.__child.keyValue[k]] = db[k]; 16 | } 17 | 18 | } 19 | let item = { 20 | tag: conf.__child.tag, 21 | attrs: attr, 22 | slots: slots 23 | 24 | } 25 | childrens.push(item) 26 | } 27 | return childrens 28 | 29 | } 30 | 31 | } 32 | export const eleRenderFormat = function (conf, eleName) { 33 | 34 | if (!isObjectObject(conf)) { 35 | return null 36 | } 37 | 38 | 39 | const types = { 40 | input_text: { 41 | childrens: [], 42 | tag: "el-input", 43 | attrs: { type: "text" }, 44 | formItem: {}, 45 | slots: {} 46 | }, 47 | 48 | input_icon: { 49 | childrens: [], 50 | tag: "input-icon", 51 | attrs: {}, 52 | formItem: {}, 53 | slots: {} 54 | }, 55 | 56 | input_number: { 57 | childrens: [], 58 | tag: "el-input-number", 59 | attrs: { type: "number" }, 60 | formItem: {}, 61 | slots: {} 62 | }, 63 | input_range: { 64 | childrens: [], 65 | tag: "el-slider", 66 | attrs: { type: "number" }, 67 | formItem: {}, 68 | slots: {} 69 | }, 70 | input_radio: { 71 | childrens: [], 72 | tag: "el-radio-group", 73 | attrs: {}, 74 | formItem: {}, 75 | slots: {} 76 | 77 | }, 78 | input_select: { 79 | childrens: [], 80 | tag: "el-select", 81 | attrs: {}, 82 | formItem: {}, 83 | slots: {} 84 | }, 85 | input_opt: { 86 | childrens: [], 87 | tag: "option-input", 88 | attrs: {}, 89 | slots: {} 90 | }, 91 | 92 | input_boolean: { 93 | childrens: [], 94 | tag: "el-switch", attrs: { "active-value": true, "inactive-value": false } 95 | , formItem: {}, slots: {} 96 | }, 97 | } 98 | 99 | let t = conf.input_type 100 | if (conf.input_type in types == false) { 101 | t = 'input_text' 102 | } 103 | let ini = types[t]; 104 | for (let k in conf) { 105 | if (['__val__', 'input_type', 'label', 'opts'].includes(k) == false) { 106 | ini['attrs'][k] = conf[k]; 107 | } 108 | } 109 | if ('__child' in conf) { 110 | ini.childrens = slotParser.default(conf); 111 | } else if (Object.keys(ini.slots).length === 0) { 112 | 113 | delete ini.slots 114 | } 115 | 116 | ini['formItem'] = { showLabel: true, label: conf.label, labelWidth: conf.labelWidth ? conf.labelWidth : undefined } 117 | ini['defaultvalue'] = conf['__val__'] 118 | ini['eleName'] = eleName; 119 | //console.log(ini) 120 | return ini 121 | 122 | } 123 | 124 | 125 | export const eleRenderSetFormat = function (conf) { 126 | const eles = []; 127 | eles.push({ tag: "el-divider", slots: { default: "form item" } }); 128 | for (let f in conf.formItem) { 129 | let item = conf.formItem[f]; 130 | eles.push(eleRenderFormat(item, f)) 131 | } 132 | eles.push({ tag: "el-divider", slots: { default: "属性" } }); 133 | 134 | if ('__text' in conf) { 135 | 136 | eles.push(eleRenderFormat(conf.__text, '__text')) 137 | } 138 | 139 | for (let f in conf.attrs) { 140 | let item = conf.attrs[f]; 141 | eles.push(eleRenderFormat(item, f)) 142 | } 143 | eles.push(eleRenderFormat(conf.__opt__, '__opt__')) 144 | return eles; 145 | 146 | } -------------------------------------------------------------------------------- /docs/assets/tool.7671f1aa.js: -------------------------------------------------------------------------------- 1 | import{b as U,ad as y,r as _,e as E,j as I,g as B,E as S,h as D,l as N,a4 as j,a8 as F,ae as R,c as A,s as t,n,af as T,$ as H,o as J,x as g,y as L,a1 as M,ag as O,a as W,ab as q,ac as z}from"./UploadWarp.d51e447e.js";const G={components:{UploadWarp:y},setup(k,a){const w=_([]),e=_(""),v=_(""),V=_(""),f=_(!1),d=E({tag:"",name:"",__openRules:!1,tagIcon:"input",ctrlBtn:!0});return{data:d,code:e,make:function(){const u=[];v.value.split(` 2 | `).map(o=>{let i=o.trim().split(" "),l={name:i[0].trim(),title:i[1].trim(),type:i[2].trim(),opt:i[3].trim().split("/").map(b=>b.trim()),default:i[4]?i[4].trim():""};u.push(l)});const m=[];f.value==!1&&m.push('fieldName: helper.input_text("\u5B57\u6BB5\u540D", "\u5B57\u6BB5\u540D")');for(let o of u){let i="";if(["string","number"].includes(o.type.toLowerCase()))if(o.opt.length>1){let r=JSON.stringify(o.opt.map(p=>({key:p,value:p})));i=`'${o.name}':helper.input_radio('${o.title}',${r},'${o.default}')`}else i=`'${o.name}':helper.input_text('${o.title}','${o.default}')`;else o.type=="boolean"&&(i=`'${o.name}':helper.input_boolean('${o.title}',${o.default})`);i&&m.push(i);let l=["'__ID':''"];for(let r in d)if(r=="formItem"){if(d[r]){let p=`{ 3 | showLabel: helper.input_boolean("\u663E\u793A label", true), 4 | labelWidth: helper.input_number("label \u5BBD", 100), 5 | label: helper.input_text("label", '${d.name}', ), 6 | }`;l.push(`${r}:${p}`)}}else if(r=="tag"&&f.value==!0)l.push(`${r}:draggable`);else{let p=typeof d[r],C=["boolean","number"].includes(p)?d[r]:JSON.stringify(d[r]);l.push(`${r}:${C}`)}l.push(`attrs:{${m.join(`, 7 | `)}}`);let b="export default {"+l.join(`, 8 | `)+"}";e.value=`import helper from "@/components/form/ui/helper.js"; 9 | 10 | ${b}`,V.value=T(d.tag)+".js"}},codeStr:v,execDownload:function(){const u=new Blob([e.value],{type:"application/javascript; charset=utf-8"});H.exports.saveAs(u,V.value)},isContainer:f,files:w}}},K={style:{width:"100%"}},P=g("\u751F\u6210"),Q=g("\u4E0B\u8F7D");function X(k,a,w,e,v,V){const f=y,d=I,s=B,c=S,u=D,x=N,m=j,o=F,i=R;return J(),A("div",K,[t(i,{direction:"vertical"},{default:n(()=>[t(o,null,{default:n(()=>[t(m,{span:12},{default:n(()=>[g(L(e.files)+" ",1),t(f,{uploadType:"image","auto-upload":!0,modelValue:e.files,"onUpdate:modelValue":a[0]||(a[0]=l=>e.files=l),action:"https://www.vkandian.cn/index.php/index/upload"},null,8,["modelValue"]),t(x,null,{default:n(()=>[t(s,{label:"\u5C5E\u6027\u5B57\u7B26\u4E32"},{default:n(()=>[t(d,{type:"textarea",modelValue:e.codeStr,"onUpdate:modelValue":a[1]||(a[1]=l=>e.codeStr=l),rows:"10"},null,8,["modelValue"])]),_:1}),t(s,{label:"tag"},{default:n(()=>[t(d,{modelValue:e.data.tag,"onUpdate:modelValue":a[2]||(a[2]=l=>e.data.tag=l)},null,8,["modelValue"])]),_:1}),t(s,{label:"name"},{default:n(()=>[t(d,{modelValue:e.data.name,"onUpdate:modelValue":a[3]||(a[3]=l=>e.data.name=l)},null,8,["modelValue"])]),_:1}),t(s,{label:"\u56FE\u6807"},{default:n(()=>[t(d,{modelValue:e.data.tagIcon,"onUpdate:modelValue":a[4]||(a[4]=l=>e.data.tagIcon=l)},null,8,["modelValue"])]),_:1}),t(s,{label:"\u5BB9\u5668"},{default:n(()=>[t(c,{modelValue:e.isContainer,"onUpdate:modelValue":a[5]||(a[5]=l=>e.isContainer=l),"active-value":!0,"inactive-value":!1},null,8,["modelValue"])]),_:1}),t(s,{label:"\u5F00\u542F\u81EA\u5B9A\u4E49\u9A8C\u8BC1"},{default:n(()=>[t(c,{modelValue:e.data.__openRules,"onUpdate:modelValue":a[6]||(a[6]=l=>e.data.__openRules=l),"active-value":!0,"inactive-value":!1},null,8,["modelValue"])]),_:1}),t(s,{label:"\u663E\u793A\u63A7\u5236\u6309\u94AE"},{default:n(()=>[t(c,{modelValue:e.data.ctrlBtn,"onUpdate:modelValue":a[7]||(a[7]=l=>e.data.ctrlBtn=l),"active-value":!0,"inactive-value":!1},null,8,["modelValue"])]),_:1}),t(s,{label:"\u5F00\u542FformItem\u6807\u7B7E"},{default:n(()=>[t(c,{modelValue:e.data.formItem,"onUpdate:modelValue":a[8]||(a[8]=l=>e.data.formItem=l),"active-value":!0,"inactive-value":!1},null,8,["modelValue"])]),_:1}),t(s,null,{default:n(()=>[t(u,{onClick:e.make,type:"primary"},{default:n(()=>[P]),_:1},8,["onClick"]),t(u,{onClick:e.execDownload,type:"default"},{default:n(()=>[Q]),_:1},8,["onClick"])]),_:1})]),_:1})]),_:1}),t(m,{span:12},{default:n(()=>[M(W("textarea",{"onUpdate:modelValue":a[9]||(a[9]=l=>e.code=l),style:{width:"100%",height:"100%",border:"none",padding:"20px"}},`\r 11 | `,512),[[O,e.code]])]),_:1})]),_:1})]),_:1})])}var Y=U(G,[["render",X]]);const h=q(Y);h.use(z);h.mount("#app"); 12 | -------------------------------------------------------------------------------- /src/utils/drawer.js: -------------------------------------------------------------------------------- 1 | import { reactive, ref, watch } from "vue"; 2 | 3 | import { 4 | isObjectObject, 5 | isObjectArray, 6 | isStr, 7 | isNumber, 8 | isObjectUnde, 9 | } from "@/utils/func.js"; 10 | 11 | import { Api } from "@/utils/api"; 12 | 13 | const optParseHandles = { 14 | default: function (_c, data) { 15 | for (let item of data) { 16 | let son = {}; 17 | son.tag = _c.__opt__.tag; 18 | son.attrs = { 19 | label: item.value, 20 | }; 21 | son.slots = { 22 | default: item.key, 23 | }; 24 | _c.childrens.value.push(son); 25 | } 26 | }, 27 | "el-option": function (_c, data) { 28 | for (let item of data) { 29 | let son = {}; 30 | son.tag = _c.__opt__.tag; 31 | son.attrs = { 32 | value: item.value, 33 | label: item.key, 34 | }; 35 | 36 | _c.childrens.value.push(son); 37 | } 38 | }, 39 | }; 40 | 41 | const toVal = function (obj) { 42 | const _c = {}; 43 | for (let a in obj) { 44 | if (typeof obj[a] == "object") { 45 | if ("__val__" in obj[a]) { 46 | _c[a] = obj[a]["__val__"]; 47 | } else { 48 | _c[a] = toVal(obj[a]); 49 | } 50 | } else { 51 | _c[a] = obj[a]; 52 | } 53 | } 54 | 55 | return _c; 56 | }; 57 | const _clone = function (obj) { 58 | if (isNumber(obj) || isStr(obj) || isObjectUnde(obj)) { 59 | return obj; 60 | } 61 | 62 | const _c = {}; 63 | for (let a in obj) { 64 | if (isObjectObject(obj[a])) { 65 | if ("__val__" in obj[a]) { 66 | _c[a] = obj[a]["__val__"]; 67 | } else { 68 | if (["name", "tagIcon", "__formId", "rule"].includes(a) == false) { 69 | _c[a] = _clone(obj[a]); 70 | } 71 | } 72 | } else if (isObjectArray(obj[a])) { 73 | _c[a] = obj[a].map((x) => { 74 | return _clone(x); 75 | }); 76 | } else { 77 | if (["name", "tagIcon", "__formId", "rule"].includes(a) == false) { 78 | _c[a] = obj[a]; 79 | } 80 | } 81 | } 82 | 83 | if (Object.keys(obj).indexOf("events") > -1 && Object.keys(obj.events)) { 84 | _c.events = obj.events; 85 | } 86 | 87 | if ("slots" in _c) { 88 | for (let name in _c.slots) { 89 | if (!_c.slots[name]) { 90 | delete _c.slots[name]; 91 | } 92 | } 93 | } 94 | 95 | if ("props" in _c) { 96 | _c.attrs = Object.assign(_c.attrs, _c.props); 97 | delete _c.props; 98 | } 99 | 100 | if (!isObjectArray(_c.childrens)) { 101 | ///api 接口为异步模式,此处定义为响应式 102 | _c.childrens = ref([]); 103 | } else { 104 | _c.childrens = ref(_c.childrens); 105 | } 106 | 107 | if ("__text" in _c) { 108 | _c["slots"]["default"] = _c.__text; 109 | } 110 | 111 | if ("__opt__" in _c) { 112 | let data = 113 | _c.__opt__.type == "static" 114 | ? _c.__opt__.staticData 115 | : _c.__opt__.dynamicData; 116 | let parseFunc = 117 | _c.__opt__.tag in optParseHandles 118 | ? optParseHandles[_c.__opt__.tag] 119 | : optParseHandles["default"]; 120 | 121 | if (_c.__opt__.type == "static") { 122 | parseFunc(_c, data); 123 | } else { 124 | Api.Get(data.url).then((res) => { 125 | parseFunc(_c, res.data); 126 | }); 127 | } 128 | } 129 | 130 | return _c; 131 | }; 132 | 133 | export function initRender(settings) { 134 | const conf = reactive({ 135 | formConf: settings.formConf, 136 | current: settings.current, 137 | drawingList: settings.drawingList.map((x) => { 138 | return _clone(x); 139 | }), 140 | }); 141 | 142 | watch(settings, () => { 143 | conf.drawingList = settings.drawingList.map((x) => { 144 | return _clone(x); 145 | }); 146 | }); 147 | 148 | return conf; 149 | } 150 | -------------------------------------------------------------------------------- /src/components/form/elementWarp/UploadWarp.vue: -------------------------------------------------------------------------------- 1 | 37 | 127 | -------------------------------------------------------------------------------- /src/components/form/PageSetting.vue: -------------------------------------------------------------------------------- 1 | 64 | 123 | -------------------------------------------------------------------------------- /src/components/form/ElementRender.vue: -------------------------------------------------------------------------------- 1 | 55 | 56 | 150 | -------------------------------------------------------------------------------- /src/components/form/PageDrawer.vue: -------------------------------------------------------------------------------- 1 | 45 | 46 | 123 | 172 | -------------------------------------------------------------------------------- /src/components/form/RulesInput.vue: -------------------------------------------------------------------------------- 1 | 49 | 188 | -------------------------------------------------------------------------------- /src/utils/validate.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 邮箱 3 | * @param {*} s 4 | */ 5 | export function isEmail (s) { 6 | return /^([a-zA-Z0-9_-])+@([a-zA-Z0-9_-])+((.[a-zA-Z0-9_-]{2,3}){1,2})$/.test(s) 7 | } 8 | 9 | /** 10 | * 手机号码 11 | * @param {*} s 12 | */ 13 | export function isMobile (s) { 14 | return /^1[0-9]{10}$/.test(s) 15 | } 16 | 17 | /** 18 | * 电话号码 19 | * @param {*} s 20 | */ 21 | export function isPhone (s) { 22 | return /^([0-9]{3,4}-)?[0-9]{7,8}$/.test(s) 23 | } 24 | 25 | /** 26 | * URL地址 27 | * @param {*} s 28 | */ 29 | export function isURL (s) { 30 | return /^http[s]?:\/\/.*/.test(s) 31 | } 32 | 33 | export function isvalidUsername (str) { 34 | const valid_map = ['admin', 'editor'] 35 | return valid_map.indexOf(str.trim()) >= 0 36 | } 37 | 38 | /* 合法uri */ 39 | export function validateURL (textval) { 40 | const urlregex = /^(http|https?|ftp):\/\/([a-zA-Z0-9.-]+(:[a-zA-Z0-9.&%$-]+)*@)*((25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9][0-9]?)(\.(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])){3}|([a-zA-Z0-9-]+\.)*[a-zA-Z0-9-]+\.(com|edu|gov|int|mil|net|org|biz|arpa|info|name|pro|aero|coop|museum|[a-zA-Z]{2}))(:[0-9]+)*(\/($|[a-zA-Z0-9.,?'\\+&%$#=~_-]+))*$/ 41 | return urlregex.test(textval) 42 | } 43 | 44 | /* 小写字母 */ 45 | export function validateLowerCase (str) { 46 | const reg = /^[a-z]+$/ 47 | return reg.test(str) 48 | } 49 | 50 | /* 大写字母 */ 51 | export function validateUpperCase (str) { 52 | const reg = /^[A-Z]+$/ 53 | return reg.test(str) 54 | } 55 | 56 | /* 大小写字母 */ 57 | export function validatAlphabets (str) { 58 | const reg = /^[A-Za-z]+$/ 59 | return reg.test(str) 60 | } 61 | 62 | /* 验证pad还是pc */ 63 | export const vaildatePc = function () { 64 | const userAgentInfo = navigator.userAgent 65 | const Agents = ['Android', 'iPhone', 66 | 'SymbianOS', 'Windows Phone', 67 | 'iPad', 'iPod' 68 | ] 69 | let flag = true 70 | for (var v = 0; v < Agents.length; v++) { 71 | if (userAgentInfo.indexOf(Agents[v]) > 0) { 72 | flag = false 73 | break 74 | } 75 | } 76 | return flag 77 | } 78 | 79 | /** 80 | * validate email 81 | * @param email 82 | * @returns {boolean} 83 | */ 84 | export function validateEmail (email) { 85 | const re = /^(([^<>()\\[\]\\.,;:\s@"]+(\.[^<>()\\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/ 86 | return re.test(email) 87 | } 88 | 89 | /** 90 | * 判断身份证号码 91 | */ 92 | export function cardid (code) { 93 | let list = [] 94 | let result = true 95 | let msg = '' 96 | var city = { 97 | 11: '北京', 98 | 12: '天津', 99 | 13: '河北', 100 | 14: '山西', 101 | 15: '内蒙古', 102 | 21: '辽宁', 103 | 22: '吉林', 104 | 23: '黑龙江 ', 105 | 31: '上海', 106 | 32: '江苏', 107 | 33: '浙江', 108 | 34: '安徽', 109 | 35: '福建', 110 | 36: '江西', 111 | 37: '山东', 112 | 41: '河南', 113 | 42: '湖北 ', 114 | 43: '湖南', 115 | 44: '广东', 116 | 45: '广西', 117 | 46: '海南', 118 | 50: '重庆', 119 | 51: '四川', 120 | 52: '贵州', 121 | 53: '云南', 122 | 54: '西藏 ', 123 | 61: '陕西', 124 | 62: '甘肃', 125 | 63: '青海', 126 | 64: '宁夏', 127 | 65: '新疆', 128 | 71: '台湾', 129 | 81: '香港', 130 | 82: '澳门', 131 | 91: '国外 ' 132 | } 133 | if (!validatenull(code)) { 134 | if (code.length == 18) { 135 | if (!code || !/(^\d{18}$)|(^\d{17}(\d|X|x)$)/.test(code)) { 136 | msg = '证件号码格式错误' 137 | } else if (!city[code.substr(0, 2)]) { 138 | msg = '地址编码错误' 139 | } else { 140 | // 18位身份证需要验证最后一位校验位 141 | code = code.split('') 142 | // ∑(ai×Wi)(mod 11) 143 | // 加权因子 144 | var factor = [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2] 145 | // 校验位 146 | var parity = [1, 0, 'X', 9, 8, 7, 6, 5, 4, 3, 2, 'x'] 147 | var sum = 0 148 | var ai = 0 149 | var wi = 0 150 | for (var i = 0; i < 17; i++) { 151 | ai = code[i] 152 | wi = factor[i] 153 | sum += ai * wi 154 | } 155 | if (parity[sum % 11] != code[17]) { 156 | msg = '证件号码校验位错误' 157 | } else { 158 | result = false 159 | } 160 | } 161 | } else { 162 | msg = '证件号码长度不为18位' 163 | } 164 | } else { 165 | msg = '证件号码不能为空' 166 | } 167 | list.push(result) 168 | list.push(msg) 169 | return list 170 | } 171 | 172 | /** 173 | * 判断手机号码是否正确 174 | */ 175 | export function isvalidatemobile (phone) { 176 | let list = [] 177 | let result = true 178 | let msg = '' 179 | var isPhone = /^0\d{2,3}-?\d{7,8}$/ 180 | // 增加134 减少|1349[0-9]{7},增加181,增加145,增加17[678] 181 | if (!validatenull(phone)) { 182 | if (phone.length == 11) { 183 | if (isPhone.test(phone)) { 184 | msg = '手机号码格式不正确' 185 | } else { 186 | result = false 187 | } 188 | } else { 189 | msg = '手机号码长度不为11位' 190 | } 191 | } else { 192 | msg = '手机号码不能为空' 193 | } 194 | list.push(result) 195 | list.push(msg) 196 | return list 197 | } 198 | 199 | /** 200 | * 判断姓名是否正确 201 | */ 202 | export function validatename (name) { 203 | var regName = /^[\u4e00-\u9fa5]{2,4}$/ 204 | if (!regName.test(name)) return false 205 | return true 206 | } 207 | 208 | /** 209 | * 判断是否为整数 210 | */ 211 | export function validatenum (num, type) { 212 | let regName = /[^\d.]/g 213 | if (type == 1) { 214 | if (!regName.test(num)) return false 215 | } else if (type == 2) { 216 | regName = /[^\d]/g 217 | if (!regName.test(num)) return false 218 | } 219 | return true 220 | } 221 | 222 | /** 223 | * 判断是否为小数 224 | */ 225 | export function validatenumord (num, type) { 226 | let regName = /[^\d.]/g 227 | if (type == 1) { 228 | if (!regName.test(num)) return false 229 | } else if (type == 2) { 230 | regName = /[^\d.]/g 231 | if (!regName.test(num)) return false 232 | } 233 | return true 234 | } 235 | 236 | /** 237 | * 判断是否为空 238 | */ 239 | export function validatenull (val) { 240 | if (typeof val === 'boolean') { 241 | return false 242 | } 243 | if (typeof val === 'number') { 244 | return false 245 | } 246 | if (val instanceof Array) { 247 | if (val.length == 0) return true 248 | } else if (val instanceof Object) { 249 | if (JSON.stringify(val) === '{}') return true 250 | } else { 251 | if (val == 'null' || val == null || val == 'undefined' || val == undefined || val == '') return true 252 | return false 253 | } 254 | return false 255 | } 256 | -------------------------------------------------------------------------------- /src/apps/tool/Dev.vue: -------------------------------------------------------------------------------- 1 | 74 | 195 | 196 | 197 | -------------------------------------------------------------------------------- /src/components/form/PageGenerator.vue: -------------------------------------------------------------------------------- 1 | 89 | 90 | 288 | 289 | 293 | -------------------------------------------------------------------------------- /src/styles/home.scss: -------------------------------------------------------------------------------- 1 | $selectedColor: #f6f7ff; 2 | $lighterBlue: #409eff; 3 | 4 | .preview .box{ 5 | border: none !important; 6 | margin-top: 0px !important; 7 | padding: 0px !important; 8 | } 9 | .container { 10 | position: relative; 11 | width: 100%; 12 | height: 100%; 13 | } 14 | 15 | .components-list { 16 | padding: 8px; 17 | box-sizing: border-box; 18 | // height: 100%; 19 | .components-item { 20 | display: inline-block; 21 | width: 48%; 22 | margin: 1%; 23 | transition: transform 0ms !important; 24 | } 25 | } 26 | .components-draggable { 27 | padding-bottom: 20px; 28 | } 29 | .components-title { 30 | font-size: 14px; 31 | color: #222; 32 | margin: 6px 2px; 33 | .svg-icon { 34 | color: #666; 35 | font-size: 18px; 36 | } 37 | } 38 | 39 | .components-body { 40 | padding: 8px 10px; 41 | background: $selectedColor; 42 | font-size: 12px; 43 | cursor: move; 44 | border: 1px dashed $selectedColor; 45 | border-radius: 3px; 46 | .svg-icon { 47 | color: #777; 48 | font-size: 15px; 49 | } 50 | &:hover { 51 | border: 1px dashed #787be8; 52 | color: #787be8; 53 | .svg-icon { 54 | color: #787be8; 55 | } 56 | } 57 | } 58 | 59 | .left-board { 60 | width: 260px; 61 | position: absolute; 62 | left: 0; 63 | top: 0; 64 | height: 100vh; 65 | } 66 | .left-scrollbar { 67 | height: calc(100vh - 42px) !important; 68 | overflow: hidden; 69 | } 70 | .center-scrollbar { 71 | height: calc(100vh - 42px) !important; 72 | overflow: hidden; 73 | border-left: 1px solid #f1e8e8; 74 | border-right: 1px solid #f1e8e8; 75 | box-sizing: border-box; 76 | } 77 | .center-board { 78 | height: calc(100vh - 42px) !important; 79 | width: auto; 80 | margin: 0 350px 0 260px; 81 | box-sizing: border-box; 82 | } 83 | .empty-info { 84 | position: absolute; 85 | top: 46%; 86 | left: 0; 87 | right: 0; 88 | text-align: center; 89 | font-size: 18px; 90 | color: #ccb1ea; 91 | letter-spacing: 4px; 92 | } 93 | .selected { 94 | background-color: #ff556614; 95 | } 96 | .preview-action-bar{ 97 | display: flex; 98 | flex-direction: row; 99 | justify-content: flex-start; 100 | 101 | .btn{ 102 | margin-right: 15px; 103 | color: rgba(0, 0, 0, 0.664); 104 | font-size: 14px; 105 | cursor: pointer; 106 | } 107 | .btn:hover,.btn.on{ 108 | color: #00afff; 109 | } 110 | 111 | 112 | } 113 | .action-bar { 114 | position: relative; 115 | height: 42px; 116 | text-align: right; 117 | padding: 0 15px; 118 | box-sizing: border-box; 119 | border: 1px solid #f1e8e8; 120 | border-top: none; 121 | border-left: none; 122 | display: flex; 123 | flex-direction: row-reverse; 124 | justify-content:right; 125 | align-items: center; 126 | .btn{ 127 | margin-right: 15px; 128 | color: rgba(0, 0, 0, 0.664); 129 | font-size: 14px; 130 | cursor: pointer; 131 | } 132 | .btn:hover,.btn.on{ 133 | color: #00afff; 134 | } 135 | .el-button--text{ 136 | color: #222; 137 | } 138 | .delete-btn { 139 | color: #f56c6c; 140 | } 141 | } 142 | .logo-wrapper { 143 | position: relative; 144 | height: 42px; 145 | background: #fff; 146 | border-bottom: 1px solid #f1e8e8; 147 | box-sizing: border-box; 148 | } 149 | .logo { 150 | position: absolute; 151 | left: 12px; 152 | top: 6px; 153 | line-height: 30px; 154 | color: #00afff; 155 | font-weight: 600; 156 | font-size: 17px; 157 | white-space: nowrap; 158 | > img { 159 | width: 30px; 160 | height: 30px; 161 | vertical-align: top; 162 | } 163 | .github { 164 | display: inline-block; 165 | vertical-align: sub; 166 | margin-left: 15px; 167 | > img { 168 | height: 22px; 169 | } 170 | } 171 | } 172 | 173 | .center-board-row { 174 | padding: 12px 12px 15px 12px; 175 | box-sizing: border-box; 176 | & > .el-form { 177 | // 69 = 12+15+42 178 | height: calc(100vh - 69px); 179 | } 180 | } 181 | .drawing-board { 182 | height: 100%; 183 | position: relative; 184 | 185 | .components-body { 186 | padding: 0; 187 | margin: 0; 188 | font-size: 0; 189 | } 190 | .sortable-ghost { 191 | position: relative; 192 | display: block; 193 | overflow: hidden; 194 | &::before { 195 | content: " "; 196 | position: absolute; 197 | left: 0; 198 | right: 0; 199 | top: 0; 200 | height: 3px; 201 | background: rgb(89, 89, 223); 202 | z-index: 2; 203 | } 204 | } 205 | .components-item.sortable-ghost { 206 | width: 100%; 207 | height: 60px; 208 | background-color: $selectedColor; 209 | } 210 | .active-from-item { 211 | & > .el-form-item { 212 | background: $selectedColor; 213 | border-radius: 6px; 214 | } 215 | & > .drawing-item-copy, 216 | & > .drawing-item-delete { 217 | display: initial; 218 | } 219 | & > .component-name { 220 | color: $lighterBlue; 221 | } 222 | } 223 | .el-form-item { 224 | margin-bottom: 15px; 225 | } 226 | } 227 | .drawing-item { 228 | position: relative; 229 | cursor: move; 230 | &.unfocus-bordered:not(.active-from-item) > div:first-child { 231 | border: 1px dashed #ccc; 232 | } 233 | .el-form-item { 234 | padding: 12px 10px; 235 | } 236 | } 237 | 238 | .drag-wrapper { 239 | width: 100%; 240 | } 241 | 242 | .drawing-row-item { 243 | position: relative; 244 | cursor: move; 245 | box-sizing: border-box; 246 | border: 1px dashed #ccc; 247 | border-radius: 3px; 248 | padding: 0 2px; 249 | margin-bottom: 15px; 250 | .drawing-row-item { 251 | margin-bottom: 2px; 252 | } 253 | .el-col { 254 | margin-top: 22px; 255 | } 256 | .el-form-item { 257 | margin-bottom: 0; 258 | } 259 | .drag-wrapper { 260 | min-height: 80px; 261 | width: 100%; 262 | } 263 | &.active-from-item { 264 | border: 1px dashed $lighterBlue; 265 | } 266 | .component-name { 267 | position: absolute; 268 | top: 0; 269 | left: 0; 270 | font-size: 12px; 271 | color: #bbb; 272 | display: inline-block; 273 | padding: 0 6px; 274 | } 275 | } 276 | .drawing-item, 277 | .drawing-row-item { 278 | &:hover { 279 | & > .el-form-item { 280 | background: $selectedColor; 281 | border-radius: 6px; 282 | } 283 | & > .drawing-item-copy, 284 | & > .drawing-item-delete { 285 | display: initial; 286 | } 287 | } 288 | & > .drawing-item-copy, 289 | & > .drawing-item-delete { 290 | display: none; 291 | position: absolute; 292 | top: -10px; 293 | width: 22px; 294 | height: 22px; 295 | line-height: 22px; 296 | text-align: center; 297 | border-radius: 50%; 298 | font-size: 12px; 299 | border: 1px solid; 300 | cursor: pointer; 301 | z-index: 1; 302 | } 303 | & > .drawing-item-copy { 304 | right: 56px; 305 | border-color: $lighterBlue; 306 | color: $lighterBlue; 307 | background: #fff; 308 | &:hover { 309 | background: $lighterBlue; 310 | color: #fff; 311 | } 312 | } 313 | & > .drawing-item-delete { 314 | right: 24px; 315 | border-color: #f56c6c; 316 | color: #f56c6c; 317 | background: #fff; 318 | &:hover { 319 | background: #f56c6c; 320 | color: #fff; 321 | } 322 | } 323 | } 324 | 325 | .right-board { 326 | width: 350px; 327 | position: absolute; 328 | right: 0; 329 | top: 0; 330 | padding-top: 3px; 331 | 332 | .field-box { 333 | position: relative; 334 | height: calc(100vh - 42px); 335 | box-sizing: border-box; 336 | overflow: hidden; 337 | } 338 | .el-scrollbar { 339 | height: 100%; 340 | } 341 | } 342 | .select-item { 343 | display: flex; 344 | border: 1px dashed #fff; 345 | box-sizing: border-box; 346 | & .close-btn { 347 | cursor: pointer; 348 | color: #f56c6c; 349 | } 350 | & .el-input + .el-input { 351 | margin-left: 4px; 352 | } 353 | } 354 | .select-item + .select-item { 355 | margin-top: 4px; 356 | } 357 | .select-item.sortable-chosen { 358 | border: 1px dashed #409eff; 359 | } 360 | .select-line-icon { 361 | line-height: 32px; 362 | font-size: 22px; 363 | padding: 0 4px; 364 | color: #777; 365 | } 366 | .option-drag { 367 | cursor: move; 368 | } 369 | .time-range { 370 | .el-date-editor { 371 | width: 227px; 372 | } 373 | ::v-deep(.el-icon-time) { 374 | display: none; 375 | } 376 | } 377 | .document-link { 378 | position: absolute; 379 | display: block; 380 | width: 26px; 381 | height: 26px; 382 | top: 0; 383 | left: 0; 384 | cursor: pointer; 385 | background: #409eff; 386 | z-index: 1; 387 | border-radius: 0 0 6px 0; 388 | text-align: center; 389 | line-height: 26px; 390 | color: #fff; 391 | font-size: 18px; 392 | } 393 | .node-label { 394 | font-size: 14px; 395 | } 396 | .node-icon { 397 | color: #bebfc3; 398 | } 399 | 400 | .el-row.box { 401 | border: 1px dashed #409eff; 402 | // margin-bottom: 15px; 403 | margin-top: 22px; 404 | padding: 25px; 405 | 406 | // position:relative; 407 | } 408 | 409 | .box { 410 | border: 1px dashed #ffbc40; 411 | // margin-bottom: 15px; 412 | margin-top: 22px; 413 | padding: 25px; 414 | // position:relative; 415 | // top: 25px; 416 | } 417 | 418 | .drag-wrapper, 419 | .el-form-item { 420 | cursor: move; 421 | } 422 | 423 | .el-form-item__content ._btn, 424 | .item-tool-box ._btn { 425 | display: none; 426 | } 427 | 428 | .el-form-item__content:hover ._btn, 429 | .item-tool-box:hover ._btn { 430 | display: block; 431 | } 432 | 433 | .mobile { 434 | width: 375px; 435 | } 436 | .mobile, .pad { 437 | margin: auto; 438 | background: #fafafa; 439 | -webkit-box-shadow: #ebedf0 0 4px 12px; 440 | box-shadow: 0 4px 12px #ebedf0; 441 | top: 10px; 442 | left: 10px; 443 | right: 10px; 444 | bottom: 10px; 445 | } -------------------------------------------------------------------------------- /src/apps/preview/pages/test.vue: -------------------------------------------------------------------------------- 1 | 159 | -------------------------------------------------------------------------------- /src/utils/generate.js: -------------------------------------------------------------------------------- 1 | import { 2 | deepClone, 3 | isObjectObject, 4 | isObjectArray, 5 | isStr, 6 | isNumber, 7 | isObjectUnde 8 | } from "@/utils/func.js"; 9 | 10 | import { 11 | js_beautify, 12 | 13 | html_beautify 14 | } from 'js-beautify' 15 | 16 | class Set { 17 | 18 | constructor() { 19 | this.__data = []; 20 | 21 | } 22 | 23 | add(s) { 24 | if (this.__data.indexOf(s) > -1) { 25 | return 26 | } 27 | this.__data.push(s); 28 | } 29 | 30 | data() { 31 | 32 | return this.__data; 33 | } 34 | } 35 | 36 | class Scripts { 37 | constructor(rules) { 38 | this.importFuncVue = new Set(); 39 | this.importString = new Set(); 40 | this.UIData = new Set(); 41 | this.vars = new Set(); 42 | this.formData = new Set(); 43 | this.APIData = new Set(); 44 | this.ActionData = new Set(); 45 | this.returnData = new Set(); 46 | this.rules = false; 47 | 48 | } 49 | 50 | definedVar(name,val){ 51 | this.vars.add(`const ${name}=ref(${val})`) 52 | } 53 | addRules(rules) { 54 | this.rules = rules; 55 | } 56 | 57 | addUiDataFromApi(url, medth, name) { 58 | this.UIData.add(name); 59 | this.importString.add('import {Api} from "@/api"; ') 60 | this.APIData.add(` 61 | Api.${medth}("${url}").then(res=>{ 62 | UIData.${name}=res.data.data; 63 | }); 64 | `); 65 | 66 | } 67 | 68 | addPostAction(apiUrl) { 69 | 70 | this.importString.add('import {Api} from "@/api"; '); 71 | this.ActionData.add(` 72 | const postData=function(formEl){ 73 | formEl.validate((valid) => { 74 | if(valid===false){ 75 | return false; 76 | } 77 | Api.Post("${apiUrl}",formData).then(res=>{ 78 | Elmessage.sucess("操作成功!"); 79 | }); 80 | }) 81 | } 82 | `); 83 | this.returnData.add("postData"); 84 | 85 | } 86 | 87 | 88 | renderImport(lines) { 89 | let vueFunc = this.importFuncVue.data().join(","); 90 | lines.push(`import {${vueFunc}} from "vue";`); 91 | return lines.concat(this.importString.data()); 92 | } 93 | 94 | red(lines) { 95 | this.importFuncVue.add('reactive'); 96 | let formData = {}; 97 | this.formData.data().forEach(element => { 98 | console.log(element) 99 | formData[element[0]] = element[1]; 100 | }); 101 | lines.push(`const formData=reactive(${JSON.stringify(formData)});`); 102 | this.returnData.add("formData"); 103 | return lines; 104 | } 105 | 106 | renderUIData(lines) { 107 | this.importFuncVue.add('reactive'); 108 | let uiData = {}; 109 | this.UIData.data().forEach(element => { 110 | uiData[element] = ''; 111 | }); 112 | lines.push(`const UIData=reactive(${JSON.stringify(uiData)});`); 113 | this.returnData.add("UIData"); 114 | return lines; 115 | } 116 | 117 | renderApiScript(lines) { 118 | 119 | return lines.concat(this.APIData.data().join("\n")); 120 | } 121 | renderVarsData(lines) { 122 | return lines.concat(this.vars.data().join("\n")); 123 | } 124 | renderActionData(lines) { 125 | return lines.concat(this.ActionData.data().join("\n")); 126 | } 127 | 128 | renderFormRules(lines) { 129 | 130 | if (this.rules === false) { 131 | 132 | return lines; 133 | } 134 | for (let p in this.rules) { 135 | 136 | this.rules[p] = Object.values(this.rules[p]) 137 | } 138 | this.returnData.add("rules"); 139 | let ruleStr = JSON.stringify(this.rules, (k, v) => { 140 | if (k === 'pattern') { 141 | return `--${v}--`; 142 | } 143 | return v; 144 | }); 145 | 146 | ruleStr = ruleStr.replace(/\-\-"/g, '/').replace(/"\-\-/g, '/') 147 | lines.push(`const rules=${ruleStr}`); 148 | return lines; 149 | } 150 | 151 | renderSetup(lines) { 152 | lines = this.red(lines); 153 | lines = this.renderUIData(lines); 154 | lines = this.renderApiScript(lines); 155 | lines = this.renderFormRules(lines); 156 | lines = this.renderVarsData(lines); 157 | lines = this.renderActionData(lines); 158 | return lines; 159 | } 160 | 161 | renderEnd(lines) { 162 | return lines; 163 | } 164 | 165 | render() { 166 | this.importFuncVue.add('ref'); 167 | let lines = [""]; 168 | lines = this.renderSetup(lines); 169 | lines = this.renderEnd(lines); 170 | lines[0] = this.renderImport([]).join("\n"); 171 | return lines.join("\n") 172 | } 173 | 174 | 175 | } 176 | 177 | const keyName = function (k) { 178 | let r = new RegExp('([A-Z]{1,1})'); 179 | return k.replace(r, '-$1').toLowerCase(); 180 | } 181 | 182 | const toVal = function (obj) { 183 | const _c = {} 184 | for (let a in obj) { 185 | if (typeof obj[a] == 'object') { 186 | if ('__val__' in obj[a]) { 187 | _c[a] = obj[a]['__val__']; 188 | } else { 189 | _c[a] = toVal(obj[a]); 190 | } 191 | } else { 192 | _c[a] = obj[a]; 193 | } 194 | } 195 | 196 | return _c; 197 | 198 | } 199 | 200 | 201 | const isBoolean = function (s) { 202 | if (typeof s == 'boolean') { 203 | return true; 204 | } 205 | return ['true', "false"].indexOf(s) > -1; 206 | } 207 | const attrFuns = { 208 | FormData: "formData", 209 | default(k, v) { 210 | if (k.substring(0, 2) === '__') { 211 | return ""; 212 | } 213 | if (v) { 214 | if (isBoolean(v)) { 215 | console.log(k, '=----') 216 | return `:${keyName(k)}="${v}"` 217 | } 218 | return `${keyName(k)}="${v}"` 219 | } 220 | return ""; 221 | }, 222 | __formRef(v) { 223 | 224 | // attrFuns.FormData = v; 225 | return `ref="${v}"`; 226 | 227 | }, 228 | __formModel(v) { 229 | attrFuns.FormData = v; 230 | return `:model="${v}"`; 231 | }, 232 | fieldName(v) { 233 | return `v-model="${attrFuns.FormData}.${v}"`; 234 | }, 235 | style(css) { 236 | let lines = []; 237 | for (let name in css) { 238 | lines.push(`${keyName(name)}:${css[name]}`); 239 | } 240 | return `style="${lines.join(";")}"` 241 | } 242 | } 243 | const attrFormat = function (attrs, props) { 244 | attrs = Object.assign({}, attrs, props); 245 | let attr = []; 246 | for (let k in attrs) { 247 | 248 | if (k in attrFuns) { 249 | attr.push(attrFuns[k](attrs[k])); 250 | } else { 251 | attr.push(attrFuns.default(k, attrs[k])); 252 | } 253 | 254 | } 255 | 256 | return attr.join(" "); 257 | 258 | } 259 | 260 | const slotFormat = function (slots) { 261 | 262 | let eles = []; 263 | for (let name in slots) { 264 | if (slots[name]) { 265 | eles.push(``) 266 | } 267 | 268 | } 269 | 270 | return eles.join(""); 271 | } 272 | 273 | const childrenFormat = function (childrens, js) { 274 | 275 | if (!childrens) { 276 | return '' 277 | } 278 | childrens = Object.values(childrens) 279 | let sons = childrens.map(function (ele) { 280 | 281 | return toHtml(ele, js); 282 | }) 283 | 284 | return sons.join(" "); 285 | } 286 | 287 | const optParseHandles = { 288 | 289 | dynamic: { 290 | default: function (name, opts, data) { 291 | let sons = []; 292 | 293 | let son = { 294 | props: {} 295 | } 296 | son.tag = opts.tag 297 | son.attrs = { 298 | ":label": "item.value", 299 | "v-for": `item in UIData.${name}`, 300 | ":key": "item.key" 301 | } 302 | son.slots = { 303 | default: "{{item.key}}" 304 | } 305 | sons.push(son); 306 | 307 | return sons; 308 | 309 | }, 310 | "el-option": function (name, opts, data) { 311 | let sons = []; 312 | 313 | let son = { 314 | props: {} 315 | } 316 | son.tag = opts.tag 317 | son.attrs = { 318 | ":value": "item.value", 319 | ":label": "item.key", 320 | "v-for": `item in UIData.${name}`, 321 | ":key": "item.key" 322 | } 323 | 324 | sons.push(son); 325 | 326 | 327 | return sons; 328 | 329 | } 330 | }, 331 | 332 | static: { 333 | default: function (name, opts, data) { 334 | let sons = []; 335 | for (let item of data) { 336 | let son = { 337 | props: {} 338 | } 339 | son.tag = opts.tag; 340 | son.attrs = { 341 | "label": item.value 342 | } 343 | son.slots = { 344 | default: item.key 345 | } 346 | sons.push(son); 347 | } 348 | return sons; 349 | 350 | }, 351 | "el-option": function (name, opts, data) { 352 | let sons = []; 353 | for (let item of data) { 354 | let son = { 355 | props: {} 356 | } 357 | son.tag = opts.tag 358 | son.attrs = { 359 | value: item.value, 360 | label: item.key 361 | } 362 | 363 | sons.push(son); 364 | 365 | } 366 | return sons; 367 | 368 | } 369 | }, 370 | 371 | } 372 | 373 | const opt = function (name, opts, js) { 374 | 375 | 376 | let data = opts.type === 'static' ? opts.staticData : opts.dynamicData; 377 | let parseFunc = opts.tag in optParseHandles[opts.type] ? optParseHandles[opts.type][opts.tag] : optParseHandles[opts.type]['default']; 378 | 379 | if (opts.type === "dynamic") { 380 | 381 | js.addUiDataFromApi(data.url, data.medth, name); 382 | 383 | 384 | } 385 | 386 | return parseFunc(name, opts, data) 387 | 388 | 389 | } 390 | 391 | const renderBtns = function (ele, js) { 392 | if (('__formBtns' in ele.attrs) && ele.attrs.__formBtns) { 393 | 394 | js.addPostAction(ele.api); 395 | return ` 396 | 397 | 立即创建 398 | 取消 399 | `; 400 | 401 | } 402 | } 403 | const toHtml = function (ele, js) { 404 | if (ele.attrs.fieldName) { 405 | js.formData.add([ele.attrs.fieldName, ele.defaultvalue]); 406 | } 407 | 408 | 409 | if (ele.__rules) { 410 | js.addRules(deepClone(ele.__rules)) 411 | ele.attrs[":rules"] = "rules"; 412 | } 413 | let tagName = 'component' in ele.props ? ele.props.component : ele.tag; 414 | 415 | if ('__text' in ele) { 416 | ele['slots']['default'] = ele.__text; 417 | } 418 | 419 | if ('__opt__' in ele) { 420 | 421 | if (isObjectArray(ele['childrens']) === false) { 422 | ele['childrens'] = []; 423 | } 424 | let ops = opt(ele.attrs.fieldName, ele.__opt__, js); 425 | 426 | ele['childrens'] = ele['childrens'].concat(ops); 427 | 428 | } 429 | 430 | 431 | let node = ["<", tagName, " ", attrFormat(ele.attrs, ele.props), " ", ">\n", childrenFormat(ele.childrens, js), slotFormat(ele.slots), renderBtns(ele, js), "\n"] 432 | if (ele.formItem) { 433 | node = ["<", "el-form-item", " ", attrFormat(ele.formItem, { 434 | prop: ele.attrs.fieldName 435 | }), " ", ">", node.join(""), ""]; 436 | 437 | } 438 | 439 | return node.join(""); 440 | 441 | } 442 | const generate = function (settings) { 443 | settings = toVal(settings); 444 | let element = settings.formConf; 445 | console.log(element); 446 | element.childrens = settings.drawingList; 447 | 448 | const js = new Scripts(); 449 | let html = toHtml(element, js); 450 | 451 | js.definedVar(element.attrs.__formRef,'null'); 452 | 453 | return ["', ""].join("\n") 454 | 455 | } 456 | export { 457 | generate 458 | } --------------------------------------------------------------------------------