├── 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 |
2 |
3 |
4 |
5 |
6 |
7 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/src/apps/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
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 |
2 |
3 |
4 |
5 |
6 |
7 |
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 | 
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 |
58 |
62 |
63 | ```
64 |
65 |
66 |
--------------------------------------------------------------------------------
/src/assets/icons/svg/rate.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/components/form/SvgIcon/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
8 |
11 |
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 |
2 |
3 |
4 |
5 | {{ group.title }}
6 |
15 |
21 |
22 |
23 | {{ element.name }}
24 |
25 |
26 |
27 |
28 |
29 |
30 |
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 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
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 = /', '')
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 |
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 |
2 |
3 |
10 |
11 | 选择图标
12 |
20 |
21 |
22 | -
28 |
29 |
{{ icon }}
30 |
31 |
32 |
33 |
34 |
35 |
83 |
140 |
--------------------------------------------------------------------------------
/src/assets/icons/svg/color.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/components/form/InputIcon.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 选择图标
5 |
13 |
14 |
15 |
16 | -
22 |
23 |
{{ icon }}
24 |
25 |
26 |
27 |
28 | {{ dialogTableVisible }}
30 |
31 | 选择
32 |
33 |
34 |
35 |
91 |
141 |
--------------------------------------------------------------------------------
/src/components/form/OptionInput.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
18 |
19 |
20 |
21 |
22 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 | 添加
42 |
43 |
44 |
45 |
46 |
47 |
48 |
53 |
54 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
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 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 | 拖拽或者 点击上传
14 |
15 |
16 | {{ tip }}
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 | Click to upload
25 |
26 |
27 | {{ tip }}
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
127 |
--------------------------------------------------------------------------------
/src/components/form/PageSetting.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 | {{ title }}
12 |
13 |
17 |
18 |
19 |
20 |
21 |
22 | 验证规则
23 |
29 |
30 | 删除
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 | {
47 | modelValue.formConf.attrs[index]['__val__'] = e;
48 | }
49 | "
50 | v-bind="eleRenderFormat(item)"
51 | >
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
123 |
--------------------------------------------------------------------------------
/src/components/form/ElementRender.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
10 |
11 |
12 |
17 |
18 |
19 |
20 |
21 |
28 |
29 |
30 |
31 |
32 | {{ val }}
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
47 |
48 |
49 |
50 | {{ val }}
51 |
52 |
53 |
54 |
55 |
56 |
150 |
--------------------------------------------------------------------------------
/src/components/form/PageDrawer.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
17 |
21 |
22 |
29 |
30 |
31 |
35 |
36 |
37 |
38 | 立即创建
39 | 取消
40 |
41 |
42 |
43 |
44 |
45 |
46 |
123 |
172 |
--------------------------------------------------------------------------------
/src/components/form/RulesInput.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | 添加规则
9 |
10 |
12 |
13 |
14 |
20 |
21 | {{ item.title }}
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
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 |
2 |
3 |
4 |
5 |
6 | {{files}}
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
32 |
33 |
34 |
35 |
40 |
41 |
42 |
47 |
48 |
49 |
50 |
55 |
56 |
57 | 生成
58 | 下载
59 |
60 |
61 |
62 |
63 |
64 |
69 |
70 |
71 |
72 |
73 |
74 |
195 |
196 |
197 |
--------------------------------------------------------------------------------
/src/components/form/PageGenerator.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
16 |
17 |
18 |
19 | {
22 | settings.drawingList.push(el);
23 | }
24 | "
25 | >
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 | 生成
40 |
41 |
42 |
43 |
44 | 清空
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 | 下载代码
73 |
74 |
75 |
76 |
77 |
78 | 复制代码
79 |
80 |
81 |
82 |
83 |
84 | {{ generate(settings) }}
85 |
86 |
87 |
88 |
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 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 | 选项一
13 |
14 |
15 | 选项二
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 | 选项一
35 |
36 |
37 | 选项二
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 | 选项一
47 |
48 |
49 | 选项二
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 | 选项一
101 |
102 |
103 | 选项二
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 | 选项一
139 |
140 |
141 | 选项二
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
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(`${slots[name]}`)
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), "", tagName, ">\n"]
432 | if (ele.formItem) {
433 | node = ["<", "el-form-item", " ", attrFormat(ele.formItem, {
434 | prop: ele.attrs.fieldName
435 | }), " ", ">", node.join(""), "", "el-form-item", ">"];
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 ["", html_beautify(html), '', ""].join("\n")
454 |
455 | }
456 | export {
457 | generate
458 | }
--------------------------------------------------------------------------------