├── .browserslistrc ├── .eslintrc.js ├── .gitignore ├── README.md ├── babel.config.js ├── dist ├── demo.html ├── es-form.common.js ├── es-form.umd.js └── es-form.umd.min.js ├── docs ├── .vuepress │ ├── components │ │ ├── demo-block.vue │ │ ├── demo-custom.vue │ │ ├── entry.js │ │ ├── slots │ │ │ └── scoped.vue │ │ └── version │ │ │ ├── entry.js │ │ │ └── select.vue │ ├── config.js │ └── styles │ │ └── index.styl ├── README.md ├── base │ ├── README.md │ ├── array.md │ ├── auto-match.md │ ├── com-format.md │ ├── component.md │ ├── console.md │ ├── d-col-group.md │ ├── desc.md │ ├── explain.md │ ├── form.md │ ├── format.md │ ├── help.md │ ├── hidden.md │ ├── install.md │ ├── label.md │ ├── parse.md │ ├── properties.md │ ├── quickstart.md │ ├── rules.md │ ├── scopedSlots.md │ ├── settings.md │ ├── tabs.md │ ├── title.md │ ├── ui.md │ ├── unit.md │ └── value.md ├── donate.md ├── images │ ├── alipay.png │ └── wechat.png └── version │ └── overview.md ├── package.json ├── postcss.config.js ├── public ├── favicon.ico └── index.html ├── src ├── App.vue ├── assets │ └── logo.png ├── components │ ├── common │ │ ├── header.vue │ │ ├── main-footer.vue │ │ └── nav.vue │ ├── demo-frame │ │ ├── index.vue │ │ ├── mixins │ │ │ └── sys-mixin.js │ │ └── schemas │ │ │ └── operation-schema.js │ ├── help │ │ └── index.vue │ ├── register.js │ ├── tags │ │ └── index.vue │ └── units │ │ ├── README.md │ │ ├── align-desc.vue │ │ ├── checkbox-group.vue │ │ ├── desc.vue │ │ ├── label.vue │ │ ├── radio-group.vue │ │ ├── select.vue │ │ ├── slot.vue │ │ ├── title.vue │ │ └── unit.vue ├── libs │ ├── constant.js │ ├── listeners.js │ ├── storage.js │ ├── uri.js │ └── utils.js ├── main.js ├── package │ ├── README.md │ ├── base.js │ ├── components │ │ ├── btn.vue │ │ ├── console.vue │ │ ├── edit-abbr-btns.vue │ │ ├── edit-bottom-btns.vue │ │ ├── edit-btns.vue │ │ ├── help.vue │ │ ├── tabs-btn.vue │ │ ├── tabs-nav-item.vue │ │ └── tabs-nav.vue │ ├── form-item.vue │ ├── index.js │ ├── index.vue │ ├── layout │ │ ├── array-card.vue │ │ ├── array-legend.vue │ │ ├── array-row.vue │ │ ├── array-table.vue │ │ ├── array-tabs.vue │ │ ├── object-table.vue │ │ ├── object.vue │ │ └── tabs.vue │ ├── libs │ │ ├── component-utils.js │ │ ├── constant.js │ │ ├── data-cache.js │ │ ├── drag.js │ │ ├── form-utils.js │ │ ├── global.js │ │ ├── parse.js │ │ ├── pop-utils.js │ │ ├── rules.js │ │ ├── schema-rules.js │ │ ├── schema-utils.js │ │ ├── submit.js │ │ ├── tabs-observer.js │ │ └── utils.js │ ├── mixins │ │ ├── array-del-pop-mixin.js │ │ ├── array-edit-item-mixin.js │ │ ├── array-mixin.js │ │ └── item-mixin.js │ └── static │ │ └── css │ │ ├── index.scss │ │ └── mixins.scss ├── router │ ├── index.js │ └── nav-route.js ├── static │ └── css │ │ ├── index.scss │ │ └── mixins.scss └── views │ ├── README.md │ ├── demo │ ├── array-card.vue │ ├── array-legend.vue │ ├── array-row.vue │ ├── array-table.vue │ ├── array-tabs.vue │ ├── component.vue │ ├── desc.vue │ ├── example.vue │ ├── help.vue │ ├── label.vue │ ├── properties.vue │ ├── scopedSlots.vue │ ├── simple.vue │ ├── standard.vue │ ├── tabs.vue │ ├── title.vue │ └── unit.vue │ ├── home │ ├── components │ │ ├── README.md │ │ └── native.vue │ └── index.vue │ └── notfound │ ├── components │ └── README.md │ └── index.vue ├── tests └── unit │ ├── .eslintrc.js │ └── libs │ └── utils.spec.js └── vue.config.js /.browserslistrc: -------------------------------------------------------------------------------- 1 | > 1% 2 | last 2 versions 3 | not ie <= 8 4 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { 4 | node: true 5 | }, 6 | extends: ["plugin:vue/essential", "@vue/prettier"], 7 | rules: { 8 | "no-console": process.env.NODE_ENV === "production" ? "error" : "off", 9 | "no-debugger": process.env.NODE_ENV === "production" ? "error" : "off" 10 | }, 11 | parserOptions: { 12 | parser: "babel-eslint" 13 | } 14 | }; 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | # /dist 4 | package-lock.json 5 | 6 | # local env files 7 | .env.local 8 | .env.*.local 9 | 10 | # Log files 11 | npm-debug.log* 12 | yarn-debug.log* 13 | yarn-error.log* 14 | debug.log 15 | 16 | # Editor directories and files 17 | .idea 18 | .vscode 19 | *.suo 20 | *.ntvs* 21 | *.njsproj 22 | *.sln 23 | *.sw* 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vue-easy-form 2 | 3 |

4 | vue 5 | version 6 |

7 | 8 | vue-easy-form:简称esForm,是一个独立、不依赖第三方类库的vue表单组件。通过一份json配置,动态输出用户所需要的表单。组件布局丰富、逻辑控制简洁、事件联动灵活、`无缝对接第三方类库`, 极大地提高用户开发效率。 9 | 10 | ## 项目安装 11 | 12 | ### Node 版本要求 13 | [Node.js](https://nodejs.org/en/) >= 8.11; 若需要升级;可使用[nvm](https://github.com/nvm-sh/nvm) 或 [nvm-windows](https://github.com/coreybutler/nvm-windows) 进行多个 Node 版本管理。 14 | 15 | 16 | ### 项目信息 17 | [esForm文档](https://chengaohe45.github.io/vue-easy-form-docs/dist/) 18 | [esForm源码](https://github.com/chengaohe45/vue-easy-form) 19 | 20 | ### Install 21 | ``` 22 | npm install 23 | ``` 24 | 25 | ### Compiles and hot-reloads for development 26 | ``` 27 | npm run serve 28 | npm run dev 29 | ``` 30 | 31 | ### Compiles and minifies for production 32 | ``` 33 | npm run build 34 | ``` 35 | 36 | ### Lints and fixes files 37 | ``` 38 | npm run lint 39 | ``` 40 | 41 | ### Run your unit tests 42 | ``` 43 | npm run test:unit 44 | ``` 45 | 46 | ## 目录结构 47 | 本项目包含`esForm框架源码`和`实例源码`,其中`esForm框架源码`独立存在于`/src/package`文件夹中。整个项目具体结构如下: 48 | 49 | ```js 50 | ├── dist # 构建相关 51 | ├── public # 编译资源 52 | │ │── favicon.ico # favicon图标 53 | │ └── index.html # html模板 54 | ├── src # 源代码 55 | │ ├── assets # 静态资源 56 | │ ├── components # 全局公用组件 57 | │ ├── libs # 工具类 58 | │ ├── package # 独立的esform源码,不依赖其它文件夹 59 | │ ├── router # 菜单配置和路由实现 60 | │ ├── static # 全局样式 61 | │ ├── views # 页面视图,demo就存在于此 62 | │ ├── App.vue # vue入口组件 63 | │ ├── main.js # 入口文件 64 | ├── tests # 测试模块 65 | ├── .eslintrc.js # eslint配置项 66 | ├── .gitignore # git忽略文件 67 | ├── vue.config.js # vue-cli3脚手架配置 68 | ├── postcss.config.js # postcss配置 69 | ├── README.md # readme文件 70 | └── package.json # package.json安装依赖 71 | ``` -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: ["@vue/app"] 3 | }; 4 | -------------------------------------------------------------------------------- /dist/demo.html: -------------------------------------------------------------------------------- 1 | 2 | es-form demo 3 | 4 | 5 | 6 | 9 | -------------------------------------------------------------------------------- /docs/.vuepress/components/entry.js: -------------------------------------------------------------------------------- 1 | import Vue from "vue"; 2 | // import App from "./App.vue"; 3 | // import router from "./router/index"; 4 | 5 | // 引入UI样式,各模块按需引入ui组件 6 | import "element-ui/lib/theme-chalk/index.css"; 7 | 8 | // import "https://shadow.elemecdn.com/npm/highlight.js@9.3.0/styles/color-brewer.css"; 9 | //引入ui组件 10 | import elementUI from "element-ui"; 11 | 12 | // 引入基本样式 13 | // import esForm from "../../../../vue-easy-form/src/package/index.js"; 14 | 15 | import esForm from "@/package/index.js"; 16 | 17 | import gComponent from "@/components/register.js"; 18 | 19 | // 注册全局组件 20 | gComponent.register(); 21 | Vue.use(elementUI); 22 | Vue.use(esForm, { 23 | rowHeight: 40, 24 | rowSpace: 12, 25 | labelWidth: 100, 26 | offsetLeft: 0, 27 | offsetRight: 0, 28 | colon: false, 29 | direction: "h", 30 | defaultCom: "el-input", // 如:若用element-ui, 改为el-input 31 | defaultVal: "", // 对defaultCom这个组件的value设置默认值 32 | trimDoms: ["input", "textarea", "el-input"], // 数组,空数组会全部清空 33 | hasConsole: true // 推荐写成动态,编译时不用修改 34 | }); 35 | 36 | /* 37 | 打印类库的版本 38 | 当处于生产环境时:hash中存在libs_version=就行了,不宜写过多的代码来做查询判断;因为只是个调试信息,不影响功能 39 | */ 40 | // if ( 41 | // process.env.NODE_ENV != "production" || 42 | // (location.hash && location.hash.indexOf("libs_version=") >= 1) 43 | // ) { 44 | // console.log("esForm's version: " + esForm.version); 45 | // } 46 | 47 | Vue.config.productionTip = false; 48 | 49 | // window.vm = new Vue({ 50 | // router, 51 | // render: h => h(App) 52 | // }).$mount("#app"); -------------------------------------------------------------------------------- /docs/.vuepress/components/slots/scoped.vue: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 113 | -------------------------------------------------------------------------------- /docs/.vuepress/components/version/entry.js: -------------------------------------------------------------------------------- 1 | import Vue from "vue"; 2 | // 引入UI样式,各模块按需引入ui组件 3 | import "element-ui/lib/theme-chalk/index.css"; 4 | import elementUI from "element-ui"; 5 | Vue.use(elementUI); 6 | Vue.config.productionTip = false; -------------------------------------------------------------------------------- /docs/.vuepress/components/version/select.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 110 | 111 | 116 | -------------------------------------------------------------------------------- /docs/.vuepress/config.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | const webpack = require("webpack"); 3 | let version = require("../../package.json").version; 4 | 5 | let nums = version.split("."); 6 | nums = nums.slice(0, nums.length - 1); // 两个数字,中间版本 7 | let urlVersion = nums.join("."); // 打开就是编译最新版本 8 | 9 | let isLatest = true; 10 | let argvs = process.argv; 11 | if (argvs && argvs.length > 0) { 12 | let paramIndex = argvs.indexOf("version"); 13 | if (paramIndex > 0) { 14 | isLatest = false; 15 | } 16 | } 17 | 18 | const MAIN_DIR_PATH = "vue-easy-form-docs"; // 也可“vue-easy-form-docs/xx”;左右不要有"/" 19 | // 这几个主要用于版本控制 20 | var latestBaseUrl = "/" + MAIN_DIR_PATH + "/dist"; // 最新版本的baseUrl 21 | var baseUrl = isLatest ? (latestBaseUrl + "/") : ("/" + MAIN_DIR_PATH + "/" + urlVersion + "/"); // 最右边一定要有“/”否则编译有问题 22 | var prefixVerBaseUrl = "/" + MAIN_DIR_PATH; // 当选择不是最新版本时,此时组合的前缀 23 | var suffixVersion = ".x"; // 点后面不能是数字 24 | 25 | module.exports = { 26 | title: "vue-easy-form(当前版本:" + urlVersion + suffixVersion + ")", 27 | description: '后台自动化平台', 28 | base: baseUrl, 29 | dest: "../vue-easy-form-docs/" + (isLatest ? "dist/" : (urlVersion + "/")), 30 | themeConfig: { 31 | // lastUpdated: '最后更新', 32 | nav: [ 33 | { text: '指南', link: '/' }, 34 | { text: '查看版本', link: '/version/overview' }, 35 | // { 36 | // text: '版本', 37 | // items: [ 38 | // { 39 | // text: '版本总览', 40 | // link: '/version/overview' 41 | // }, 42 | // { 43 | // text: '1.6.*', 44 | // link: '/version/1.6.all' 45 | // }, 46 | // { 47 | // text: '1.5.*', 48 | // link: '/version/1.5.all' 49 | // }, 50 | // { 51 | // text: '1.4.*', 52 | // link: '/version/1.4.all' 53 | // } 54 | // ] 55 | // }, 56 | { text: '捐赠', link: '/donate' }, 57 | { text: 'GitHub', link: 'https://github.com/chengaohe45/vue-easy-form' }, 58 | { text: 'esForm例子', link: 'https://chengaohe45.github.io/vue-easy-form-docs/demo' } 59 | ], 60 | sidebar: { 61 | '/': [ 62 | { 63 | title: '指南', 64 | collapsable: false, 65 | children: [ 66 | '', 67 | 'base/install', 68 | 'base/quickstart', 69 | 'base/settings', 70 | 'base/explain', 71 | 'base/com-format', 72 | 'base/scopedSlots', 73 | 'base/parse', 74 | 'base/console' 75 | ] 76 | }, 77 | { 78 | title: '基础属性详解', 79 | collapsable: false, 80 | children: [ 81 | 'base/d-col-group', 82 | 'base/ui', 83 | 'base/hidden', 84 | 'base/properties', 85 | 'base/tabs', 86 | 'base/rules', 87 | 'base/array', 88 | 'base/value', 89 | 'base/format', 90 | 'base/auto-match', 91 | 'base/form' 92 | ] 93 | }, 94 | { 95 | title: '可组件化属性详解', 96 | collapsable: false, 97 | children: [ 98 | 'base/component', 99 | 'base/label', 100 | 'base/title', 101 | 'base/help', 102 | 'base/unit', 103 | 'base/desc' 104 | ] 105 | } 106 | ], 107 | '/version/': ['overview', '1.4.all'] 108 | } 109 | }, 110 | 111 | configureWebpack: { 112 | resolve: { 113 | alias: { 114 | // "$src": path.resolve( __dirname, "../../src"), 115 | "@": path.resolve( __dirname, "../../src") 116 | } 117 | }, 118 | 119 | plugins: [ 120 | new webpack.DefinePlugin({ 121 | "process.env": { 122 | VUE_APP_VERSION: JSON.stringify(version), // 与库的写法同步 123 | // 文档中将要用到 124 | VUE_DOCS_VERSION: JSON.stringify(urlVersion), 125 | URL_SEPARATOR: JSON.stringify(baseUrl.replace(/\/+$/g, "")), // 去年右边的“/”才好split 126 | LATEST_BASE_URL: JSON.stringify(latestBaseUrl), 127 | PREFIX_VERSION: JSON.stringify(prefixVerBaseUrl), 128 | SUFFIX_VERSION: JSON.stringify(suffixVersion) 129 | } 130 | }) 131 | ] 132 | } 133 | }; 134 | -------------------------------------------------------------------------------- /docs/.vuepress/styles/index.styl: -------------------------------------------------------------------------------- 1 | 2 | 3 | .page .content:not(.custom),.page .content__default:not(.custom),.page .page-edit, .page .page-nav{ 4 | max-width: 880px; 5 | } 6 | 7 | .divider-line { 8 | margin: 0; 9 | padding: 0; 10 | border-top: 1px solid #47494c; 11 | } 12 | 13 | .divider-line + .extra-class { 14 | border-radius: 0; 15 | } 16 | 17 | .divider-line + .extra-class > pre{ 18 | border-radius: 0; 19 | } 20 | 21 | // 覆盖一下样式,因为被vuepress>table影响了 22 | .el-year-table { 23 | display: table; 24 | 25 | tr { 26 | border-top: none; 27 | } 28 | td, th { 29 | border: none; 30 | } 31 | 32 | } 33 | 34 | .el-date-table { 35 | margin: 0; 36 | display: table; 37 | border-collapse: separate; 38 | border-spacing: 0; 39 | border: none; 40 | 41 | td { 42 | border: none; 43 | } 44 | 45 | th { 46 | border: none; 47 | border-bottom-width: 1px; 48 | } 49 | 50 | tr:nth-child(2n) { 51 | background-color: transparent; 52 | } 53 | } 54 | 55 | .es-form-array-table { 56 | 57 | table { 58 | margin: 0; 59 | display: table; 60 | border-collapse: separate; 61 | border-spacing: 0; 62 | border: none; 63 | } 64 | 65 | td { 66 | border: none; 67 | border-top-width: 1px; 68 | border-right-width: 1px; 69 | } 70 | 71 | th { 72 | border: none; 73 | border-top-width: 1px; 74 | border-right-width: 1px; 75 | } 76 | 77 | tr:nth-child(2n) { 78 | background-color: transparent; 79 | } 80 | } -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # vue-easy-form介绍 2 | 3 | > vue-easy-form:简称esForm,是一个独立、不依赖第三方类库的vue表单组件。通过一份json配置动态输出用户所需要的表单。组件布局丰富、逻辑控制简洁、事件联动灵活、`无缝对接第三方类库`, 极大地提高用户开发效率。 4 | 5 | ## 为什么选择esForm? 6 | - 只需要一个json配置,几乎无需html, css; 7 | - 布局丰富,包含弹性布局(分为24栏)和tabs布局; 8 | - 无缝对接第三方组件,无需为框架定制化组件(可自由选择`element-ui`、`fish-ui`等类库); 9 | - 表单功能齐全,包含标题、验证、事件、单位、帮助、描述、`数组`等; 10 | - 逻辑控制灵活方便(支持[动态解析](./base/parse.md)) 11 | 12 | ## 目录结构 13 | 本项目包含`esForm框架源码`和`实例源码`,其中`esForm框架源码`独立存在于`/src/package`文件夹中。整个项目具体结构如下: 14 | ```js 15 | ├── dist # 构建相关 16 | ├── public # 编译资源 17 | │ │── favicon.ico # favicon图标 18 | │ └── index.html # html模板 19 | ├── src # 源代码 20 | │ ├── assets # 静态资源 21 | │ ├── components # 全局公用组件 22 | │ ├── libs # 工具类 23 | │ ├── package # 独立的esform源码,不依赖其它文件夹 24 | │ ├── router # 菜单配置和路由实现 25 | │ ├── static # 全局样式 26 | │ ├── views # 页面视图,demo就存在于此 27 | │ ├── App.vue # vue入口组件 28 | │ ├── main.js # 入口文件 29 | ├── tests # 测试模块 30 | ├── .eslintrc.js # eslint配置项 31 | ├── .gitignore # git忽略文件 32 | ├── vue.config.js # vue-cli3脚手架配置 33 | ├── postcss.config.js # postcss配置 34 | ├── README.md # readme文件 35 | └── package.json # package.json安装依赖 36 | ``` -------------------------------------------------------------------------------- /docs/base/README.md: -------------------------------------------------------------------------------- 1 | # vue-esay-form 2 | 3 | > vue-esay-form:简称esForm,是一个独立、不依赖第三方类库的vue表单组件。通过一份json配置动态输出用户所需要的表单。组件布局丰富、逻辑控制简洁、事件联动灵活、`无缝对接第三方类库`, 极大地提高用户开发效率。 4 | 5 | -------------------------------------------------------------------------------- /docs/base/auto-match.md: -------------------------------------------------------------------------------- 1 | # 自动匹配 2 | `自动匹配就是把所有的二级的项在输出时提升为一级。`
3 | `应用场景`:一般用于表单结构和接口字段不一致时匹配。比如:现需要保存一个页面信息,后台需要保存接口的字段只需要一级{pageName, fontSize, fontColor, backgroundImage, backgroundColor), 但对产品设计来说,可能需要把字体和背景按各自模块区分,页面结构才更加清晰。
4 | 字段(见`行高亮`): 5 | ```js {2} 6 | schema: { 7 | autoMatch: true, // 设置autoMatch为true; autoMatch只有在要节点中有效 8 | properties: { 9 | name: { 10 | label: "名称", 11 | component: "el-input", 12 | value: "天天" 13 | } 14 | // ... 其它项 15 | } 16 | } 17 | ``` 18 | 19 | ### 实例 20 | 可打开`调试面版`查看输出结果,`formValue`全部都平级了 21 | 22 | 23 | 24 | 25 | ```html 26 | 27 | 28 | 79 | ``` 80 | 81 | 82 | 83 | ::: warning 注意 84 | - autoMatch设置只能在根节点中设置,其它块是无效的; 85 | - 自动匹配只影响一级和二级的表单值输出,对根值rootData没有影响; 86 | - 因为输出表单值时,二级要提升为一级,为了保持key的唯一性,编写schema时,一级和二级的所有的key(如:pageName、font、background、fontSize、backgroundImage)不能相同。 87 | ::: 88 | 89 | ### 设值 90 | 91 | 在自动匹配中,因为key是唯一的,下面的值的设置效果是一样的:
92 | 如: 93 | ```js 94 | form.setValue({ // 正常结构 95 | font: { 96 | fontSize: 12 97 | } 98 | }); 99 | 100 | form.setValue({ // 简化结构 101 | fontSize: 12 102 | }); 103 | 104 | form.setValue("font.fontSize", 12); // 正常结构 105 | form.setValue("fontSize", 12); // 简化结构 106 | ``` 107 | -------------------------------------------------------------------------------- /docs/base/com-format.md: -------------------------------------------------------------------------------- 1 | # 组件格式 2 | 从一个Object开始,按一定的格式配置引入相应的Vue组件;目前支持组件化的属性有:[项标签label](./label.md)、[描述desc](./desc.md)、[帮助help](./help.md)、[单元unit](./unit.md)、[标题title](./title.md)、[项组件component](./component.md),当然,这些属性都有自己特殊的写法,具体的写法见`可组件化属性详解`。 3 | ## 配置格式 4 | ``` js 5 | // attrName为某个属性的索引,如: label, title, component 6 | `attrName`: { // 若是一个对象,可写成一个Object 7 | name: "el-button", // 组件名,组件名不支持动态解析 8 | style: { color: "#000" }, // 内联样式,一个对象;支持动态解析 9 | class: "box1 box2", // 样式类;支持动态解析 10 | props: { // 属性: 以下全都支持动态解析 11 | disabled: "es: $root.status ? true : false", // 动态解析:es语法 12 | type: function(options) { // 动态解析:函数写法 13 | return options.rootData.status ? "primary" : "success"; 14 | }, 15 | size: "mini", // 固定值 16 | }, 17 | text: "新建", // 组件的文本,如新建;支持动态解析 18 | value: "", // 组件的值,这个不支持动态解析。因为其会与v-model实行双向绑定 19 | 20 | actions: { // 组件的行为,多个时可写数组 21 | trigger: "click", 22 | handler: function() {} 23 | }, 24 | 25 | directives: { // 指令;directives可简写为v;单值时也可直接写上指令名;多个值时也可写成数组 26 | name: "loading", // 指令名 27 | value: 2, // 指令的绑定值;支持动态解析;注:只有指令绑定值value支持动态解析 28 | expression: "1 + 1", // 指令表达式 29 | arg: "foo", // 传给指令的参数 30 | modifiers: { // 一个包含修饰符的对象 31 | bar: true 32 | } 33 | }, 34 | 35 | scopedSlots: { // 插槽:键值对写法:具体见“组件插槽” 36 | default: { // 自定义组件 37 | name: "el-tag", 38 | text: "123" 39 | }, 40 | otherSlot: "123" // 字符串,数值,布尔,函数等 41 | } 42 | } 43 | ``` 44 | - `name`: 组件名;`类型`:字符串/组件对象;`必填` 45 | - `style`: 内联样式;`类型`:对象;`非必填` 46 | - `class`: 样式类;`类型`:字符串/对象/数组;要符合[Vue Class](https://cn.vuejs.org/v2/guide/class-and-style.html)写法;`非必填` 47 | - `props`: 组件属性;`类型`:对象;`非必填` 48 | - `text`: 组件文本;`类型`:字符串、数值、布尔;`非必填` 49 | - `value`: [组件的值](#value配置)(与v-model实行双向绑定);`类型`:任何;`非必填` 50 | - `actions`: [组件事件](#组件事件);`类型`:对象/数组;`非必填` 51 | - `directives`: 指令;`类型`:字符串/对象/数组;`非必填`;索引`directives`可简写为`v` 52 | - `scopedSlots`: [组件插槽](./scopedSlots.md);`类型`:字符串/布尔/数值/对象 53 | 54 | > 动态解析是指支持[es写法](./parse.md#es写法)或[函数写法](./parse.md#函数写法) 55 | 56 | ::: warning 注意 57 | `没有this指针` 当props里的的属性(如:disabled)写成一个函数时,this并不指向表单的。 58 | 为什么? 因为当执行这些函数时,表单内的组件正处于未构造或正在重复构造,不建议调用表单内的函数(如:form.getValue等);这个与验证[rules.checks](rules.md)、数组[array.insertValue](array.md#数组默认插入值)、项组件[component.actions](component.md#组件事件)里面的函数不同,后者们的this是指向表单的 59 | ::: 60 | 61 | ## Value配置 62 | 平时写value时一般有两种写法,不同的写法代表的意义是不同的。 63 | 1. 写在`v-model`进行双向绑定,也是表单组件常见的写法 64 | ``` html 65 | 66 | 75 | ``` 76 | 等价于`json配置型`写法,如下: 77 | ``` js 78 | `attrName`: { 79 | name: "el-input", 80 | props: { 81 | placeholder: "请输入内容" 82 | }, 83 | value: "" // 与name同级,说明是绑定v-model(与组件内的value进行双向绑定),所以不支持动态解析 84 | } 85 | ``` 86 | 87 | 2. 直接配置value属值 88 | ``` html 89 | 90 | 99 | ``` 100 | 等价于`json配置`型写法,如下: 101 | ``` js 102 | `attrName`: { 103 | name: "el-input", 104 | props: { 105 | placeholder: "请输入内容", 106 | value: "" // 当成一个普通属性,不会与组件内的value进行双向绑写,所以动态解析 107 | } 108 | } 109 | ``` 110 | 111 | ## 组件事件 112 | 字段:actions 113 | ```js 114 | 标准写法 115 | actions: { 116 | // 默认为click, 多个事件写法: ["change", true]或"change input" 117 | // 注: true会转换为input事件(v1.8.1增加) 118 | trigger: "change", 119 | // options => {value, event, pathKey, index, idxChain,target} 120 | handler: function(options){...} 121 | } 122 | 123 | 简写 (直接写成一个函数),trigger默认为click 124 | actions: function(options){...} 125 | 126 | 多个事件 127 | actions: [标准写法或简写组成的数组] 128 | ``` 129 | 函数handler返回的参数options包含的信息有: 130 | - `value`: 当前项组件的值,表单的值可以通过this取出 131 | - `args`: 事件本身所携带的信息(就是函数的局部变量`arguments`);如`keyup.native`,可以从这里提取键值; 132 | - `event`: 事件本身所携带的第一个信息, 也就是`args[0]` 133 | - `target`: 当前项组件(若是`数组事件`,这个为`null`) 134 | - `pathKey`: 需要检查的组件的路径 135 | - `idxChain`: 需要检查的组件所要数组所组成的id 如: 1,2 136 | - `index`:组件处于数组的子节点(非孙子)时的索引,其余的返回-1 137 | - `instance`:表单实例(当handler为箭头函数,若需要可用instance取出表单实例使用) 138 | > handler函数的this指针指向当前的表单实例(`instance`也是当前的表单实例),这样可以方便取出其它组件(如`this.getRef("xxxx")`),从而做联动等功能 139 | 140 | ### 当props里面的某属性是的类型是函数怎么办? 141 | 142 | 例子:`showText`是函数类型;若要表达其值是一个函数,则在其名称前加上`s:`前缀,系统将不会对此函数进行动态解析 143 | ``` js {3} 144 | `attrName`: { 145 | name: "自定义组件", 146 | props: { // 注意:`s:`是对props里面的属性进行有效补充,对其它的属性(如`text`)是没有作用的 147 | "s:showText": function(names) { // 注意空格,因为空格也可以当作key,所以不要留空格 148 | return names.join(","); 149 | }, 150 | color: "#fff000" 151 | }, 152 | text: "hello" 153 | } 154 | ``` 155 | ::: tip 为什么这样写? 156 | 因为在应用过程中,`表单组件`一般都不会用一个函数作为一个属性类型(虽理论上是可以的,但至今笔者都极少极少碰到此类型的`表单组件`),所以为了保持简洁且和其它属性保持一致的写法,当前面不加`s:`前缀,则认为是可动态解析的属性。 157 | ::: -------------------------------------------------------------------------------- /docs/base/console.md: -------------------------------------------------------------------------------- 1 | # 调试面板 2 | 若设置了调试面板为打开时,表单的左上角有一个`C`按钮;在面版里面可以`实时`看到表单的[根值](.explain.md#根值)和[表单值](.explain.md#表单值);常用于开发调试用。 3 | ### 实例 4 | 以下用的是`全局引入设置`,设置了hasConsole为true 5 | 6 | 7 | 8 | 9 | ```html 10 | 11 | 12 | 31 | ``` 32 | 33 | 34 | 35 | ### 全局引入设置 36 | 37 | 在引入时直接设置; 全局`hasConsole`的默认值为`false` 38 | 39 | ```js 40 | Vue.use(esForm, 41 | { 42 | rowSpace: 10, 43 | hasConsole: process.env.NODE_ENV != "production" // 推荐写成动态,这样编译时不用修改; 44 | }); 45 | ``` 46 | 47 | ### 表单属性设置 48 | 49 | 对表单的属性hasConsole进行设置;表单`hasConsole`的优先级高于全局`hasConsole`的优先级;当不设置表单`hasConsole`时,则会自动取全局设置 50 | 51 | ```html 52 | 57 | 58 | ``` 59 | -------------------------------------------------------------------------------- /docs/base/desc.md: -------------------------------------------------------------------------------- 1 | # 描述/desc 2 | 3 | 字段:`desc`
4 | 值类型有: 5 | - `string`: 描述的内容;支持[动态解析](./parse.md) 6 | - `object`: 一个对象,见[组件格式](./com-format.md) 7 | 8 | ### 实例 9 | 10 | 11 | 12 | ```html 13 | 14 | 15 | 65 | ``` 66 | 67 | 68 | 69 | ### 组件详解 70 | 71 | | 属性名 | 说明 | 类型 | 可选值| 默认值 72 | | -- | -- | -- | -- | -- 73 | | hidden | 控制组件是否隐藏, 支持[动态解析](./parse.md) | boolean | -- | false 74 | | 其它 | 跟[组件格式](./com-format.md)一样 | -- | -- | -- -------------------------------------------------------------------------------- /docs/base/explain.md: -------------------------------------------------------------------------------- 1 | # 表单相关概念 2 | 3 | ## 根值 4 | 5 | rootData: 表单中所有的值,包含隐藏的;也就是说有什么值就取什么值出来;用于操作`表单项组件`的逻辑控制 6 | 7 | ## 表单值 8 | 9 | formValue: 表单中用户所需要的值(一般不包含隐藏的值、临时值;场景如下);用于数据提交;可认为formValue是rootData的一个子集。 10 | 11 | 影响formValue的场景: 12 | - 在表单逻辑控制中,一般隐藏的`表单项组件`不需要提交的(若需要提交, 可设置`hdValue`); 13 | - 在表单逻辑控制中,隐藏的`表单项组件`需要提交的指定的值(可设置`hdValue`); 14 | - 在`表单项组件`件显示中,有的值只是显示,但并不需要提交(可设置`isTmp`) 15 | 16 | ## 索引链 17 | idxChain: 指出`表单项组件`所处于数组的位置
18 | 如:base.student[0].courses[1].name, 如索引链为"0,1"; 当父节点或祖先节点不是数组时,则`表单项组件`为`空字符` 19 | > 应用场景:[项组件事件](./component.md#组件事件)、[项组件验证](./rules.md)、[数组事件](./array.md#数组事件)所携带的信息会返回此值 20 | 21 | 22 | ## 项组件路径 23 | pathKey: `表单项组件`位置的具体路径. 如: 24 | ```js 25 | base.student[0].courses[1].name // 推荐的写法 26 | base.target.name // 推荐的写法 27 | base.target["name"] 28 | ``` 29 | 生成形式见[表单值/value](./form-value.md) 30 | > 应用场景:[项组件事件](./component.md#组件事件)、[项组件验证](./rules.md)、[数组事件](./array.md#数组事件)所携带的信息会返回此值; [form.setValue](./form.md#表单方法)等需要此值设置 31 | 32 | 33 | ## es语法 34 | es语法就一条`es:`为前缀的字符串,再按照一定的规则解析出来的js语句。
35 | 如:
`es: $root.isJson ? "JSON格式" : "XML格式"`
36 | 说明:字符串以es:开头,用表单根数据代替$root, 最终变为
37 | `rootData.isJson ? "JSON格式" : "XML格式"`
38 | 39 | 支持es有4个数据源,这4个值共同影响整个es的解析: 40 | 1. `rootData`: 整个表单的`根值/rootData`. root在es语法中的写法是`$root` 41 | 2. `global`: 从表单中传入,用于外部对表单影响, 不设置则默认为`空对象`; global在es语法中的写法是`$global` 42 | 3. `index`:数组中孩子节点(非孙子节点))项所在的索引,其它节点(非孩子)节点此值是-1. index在es语法中的写法是`$index` 43 | 4. `hidden`: 用于判断某一项是否隐藏. hidden在es语法中的写法是`$hidden("base.target")`; 括号中是路径 44 | 45 | [es写法](./parse.md#es写法) 46 | 47 | 48 | -------------------------------------------------------------------------------- /docs/base/form.md: -------------------------------------------------------------------------------- 1 | # 表单属性/事件/方法 2 | 3 | ### 实例 4 | ```html 5 | 6 | 15 | 16 | 17 | 37 | ``` 38 | ## 表单属性 39 | 40 | | 事件名称 | 类型 | 默认值 | 说明 41 | | -- | -- | -- | -- 42 | | value/v-model | Object | {} | 绑定值(表单值) 43 | | schema | Object | {} | 具体的表单配置 44 | | global | Object | {} | 外部值;和rootData共同影响项隐藏等 45 | | hasConsole | boolean | -- | 是否有调试控制台;当设置true或false时,优先级会高于全局的hasConsole,不设置则取全局的hasConsole 46 | 47 | ## 表单事件 48 | 49 | | 事件名称 | 说明 | 回调参数 | 备注 50 | | -- | -- | -- | -- 51 | | inited | 表单初始化完成时触发 | (formValue) | schema改变时([非深度监听](https://cn.vuejs.org/v2/api/#watch)),表单会重新初始化,inited会再次调用(`inited未完成之前,用户行为事件(change、submit)暂不会触发,其它事件(input)不受影响`) 52 | | input | 表单的值有改变时触发 | (formValue, keyPath) | -- 53 | | change | 表单组件改变时触发 | (formValue, keyPath, eventData) | setValue不会触发 54 | | submit | 提交表单 | (formValue) | form.submit(); 组件事件(@enterSubmit; @submit)会触发 55 | ::: warning 注意 56 | 1. 表单事件写法有两种,最好根据自已的应用场景,两者选其一: 57 | - 写法一:在`元素中直接配置` 58 | - 写法二:在schema的`actions中配置` 59 | 2. 若写法上两者都存在,两者都会触发:先触发`actions中配置`的事件,再触发`元素中直接配置`的事件; 60 | 3. 两种写法的所返回的参数是一样(见上面),跟[组件事件的参数](../base/component.md#actions组件事件)不同,但写法跟[组件事件的写法](../base/com-format.md#组件事件)一样; 61 | 4. actions函数的this指针是指向此表单。 62 | ::: 63 | 64 | ## 表单方法 65 | 66 | | 属性名 | 说明 | 参数 | 备注 67 | | -- | -- | -- | -- 68 | | getRef | 取元素或组件 | (name, hasEmpty, idxChain) | 类似于ref;若项组件在表单数组中,则返回来的是一个数组[见下面详解](#getref);
`注意:`隐藏的项是不会取出的
`hasEmpty`为添加 69 | | checkAll | 检查表单是否有错 | 空 | 返回true/false 70 | | submit | 触发submit事件 | 空 | -- 71 | | isHidden | 判断某项是否隐藏 | ([pathKey](./explain.md#项组件路径)) | -- 72 | | getGlobal | 取表单的全局数据 | 空 | 应用表单传入来的global 73 | | getRootData | 取表单根值 | 空 | 实时取值,表单存在的值,包括隐藏的或临时的 74 | | getValue | 取表单值 | 空 | 实时取值,表单存在的值;也是getRootData的别名 75 | | setValue | 设置表单值 | ([pathKey](./explain.md#项组件路径), value) | 当pathKey是Object时,值自动匹配设置;当pathKey为字符串时,则是设置某个值 76 | | getFormValue | 取表单值 | 空 | 实时取值,用户提交所需要的值,不包括隐藏的或临时的;也就是v-model 77 | | getTabsIndex | 取某一个tabs的索引 | ([pathKey](./explain.md#项组件路径)) | 返回当前tabs的索引,不是tabs返回false;(支持普通或数组tabs) 78 | | setTabsIndex | 设某一个tabs的索引 | ([pathKey](./explain.md#项组件路径), index) | 设置当前tabs的索引;(支持普通或数组tabs) 79 | | clearErrMsg | 清除错误信息 | ([pathKey](./explain.md#项组件路径), clearNext) | 1. 当pathKey没有值时则清除所有,clearNext无效;
2. 当pathKey有值时,则是清除某一项,clearNext是对pathKey的补充,是否一起清除此项的后代 80 | | reset | 重置表单值 | (onlySchema) | `onlySchema`为添加;默认为`false`
`false`:重置的值为`schema配置`和`formValue`初始化时结合的值;
`true`:重置的值为仅为`schema配置`初始化时的值 81 | 82 | 83 | ### getRef 84 | 写法:form.getRef(name, hasEmpty, idxChain)
85 | 参数: 86 | - `name` 必填;在[项组件](./component.html)中设置的ref名称 87 | - `hasEmpty` 选填;用于`数组`,默认为`false`; 当返回值是数组时,数组中是否可返回空值 88 | - `idxChain` 选填;用于`数组`; 项组件所在的[索引链](./explain.md#索引链); 具体作用是当取出是数组的时候,可以用idxChain指出来出是哪一个 89 | ::: warning 注意 90 | 注意:隐藏的项(也就是属性hidden为true, 包括自身或父类隐藏) 91 | 1. 返回值为非数组情况下:`隐藏的项`不会取出来; 92 | 2. 返回值为数组情况下:当`hasEmpty`为`false`时,则`null`(`隐藏的项`)值不会返回;当`hasEmpty`为`true`,则`null`会返回; 93 | ::: 94 | [项组件actions的具体写法](./component.html#组件事件) 95 | -------------------------------------------------------------------------------- /docs/base/format.md: -------------------------------------------------------------------------------- 1 | # 值转换 2 | 3 | 控制入值和输出值的转换
4 | > `场景`:当外部值不是表单所需要的数据或类型时(比如:el-switch的值是一个boolean值,但是后台保存的是0/1),此时就需要做转换 5 | 6 | `值转换`有两种形式写法: 7 | - `枚举`: (推荐) 8 | - `函数`: --- 9 | 10 | 字段(见`行高亮`): 11 | ```js {8,23} 12 | propName: { 13 | 14 | label: "label名称", 15 | properties: { 16 | status: { 17 | label: "名称", 18 | component: "el-switch", 19 | format: [ // 枚举写法:写成一个数组[{outer和inner一一对应}] 20 | { 21 | outer: 0, // 表单输出的值或设置时用的值 22 | inner: false // 转换为表单内部的值 23 | }, 24 | { 25 | outer: 1, 26 | inner: true 27 | } 28 | ], 29 | value: false 30 | }, 31 | isOpen: { 32 | label: "状态", 33 | component: "el-switch", 34 | format: { // 函数写法:写成一个对象{outer函数, inner函数} 35 | outer: value => { // 从外部值转化为内部值 36 | if (value == 0) { 37 | return false; 38 | } else { 39 | return true; 40 | } 41 | }, 42 | inner: value => { // 从内部值转化为外部值 43 | if (value == true) { 44 | return 1; 45 | } else { 46 | return 0; 47 | } 48 | } 49 | }, 50 | value: 1 51 | } 52 | // ... 其它项 53 | } 54 | } 55 | ``` 56 | 57 | 58 | 59 | ### 实例 60 | 61 | 62 | 63 | ```html 64 | 65 | 66 | 126 | ``` 127 | 128 | 129 | 130 | ### 枚举写法 131 | 132 | 写成一个数组,每一项是一个对象,对象属性包含`outer`和`inner`,它们是一一对应的。 133 | - `outer`: 表单输出的值或设置时用的值 134 | - `inner`: 转换为表单内部的值 135 | 136 | ### 函数写法 137 | 138 | 写成一个对象,format中包含两个属性`outer`和`inner`,这两个属性是一个函数,至少写一个。 139 | - `outer`: 外部数据转换为表单内部数据的函数,参数value就是外部值 140 | - `inner`: 表单内部数据转换为外部数据的函数,参数value就是内部值 141 | 142 | ::: warning 注意 143 | - 1. 只有在组件项(叶子节点)中有效; 144 | - 2. 当是函数写法时,`inner` 所值入的参数value就是表单组件的值,这个值要进行健壮性判断。比如: 表单组件有值的情况返回一个数组,没有值的情况返回一个空数组,也有可能返回null值,主要是看组件怎么实现。 145 | ::: 146 | 147 | -------------------------------------------------------------------------------- /docs/base/help.md: -------------------------------------------------------------------------------- 1 | 2 | # 帮助/help 3 | 4 | 字段:`help`
5 | 值类型有: 6 | - `string`: 提示的内容,支持html;会直接调用系统默认的help组件;支持[动态解析](./parse.md) 7 | - `object`: 一个对象,见[组件格式](./com-format.md) 8 | 9 | ### 实例:写法/位置 10 | 11 | 12 | 13 | ```html 14 | 15 | 16 | 104 | ``` 105 | 106 | 107 | 108 | ### 组件详解 109 | 110 | | 属性名 | 说明 | 类型 | 可选值| 默认值 111 | | -- | -- | -- | -- | -- 112 | | hidden | 控制组件是否隐藏, 支持[动态解析](./parse.md) | boolean | -- | false 113 | | 其它 | 跟[组件格式](./com-format.md)一样 | -- | -- | -- 114 | 115 | ### `系统帮助组件`属性详解 116 | 也就是help.props, 支持[动态解析](./parse.md) 117 | | 属性名 | 说明 | 类型 | 可选值| 默认值 118 | | -- | -- | -- | -- | -- 119 | | content | tooltip提示的内容,支持html | string | -- | "" 120 | | maxWidth | tooltip提示框的最大宽度 | number | -- | 300 121 | | href | 是否有超链接 | string | -- | "" 122 | | placement | tooltip的方向 | -- | top right bottom left | "top" 123 | -------------------------------------------------------------------------------- /docs/base/hidden.md: -------------------------------------------------------------------------------- 1 | # 隐藏控制/隐藏值/临时值 2 | 3 | 字段(见`行高亮`): 4 | ```js {2,3,4,8,9,10} 5 | propName: { 6 | hidden: false, // 是否隐藏此项; 支持动态解析;在根节点(最外级)中无效 7 | hdValue: undefined, // 当hidden为true时,根据此设置取出隐藏时的值 8 | isTmp: false, // 临时值; 在根节点(最外级)中无效 9 | label: "label值", 10 | properties: { 11 | name: { 12 | hidden: false, // 是否隐藏此项; 在根节点(最外级)中无效 13 | hdValue: undefined, // 当hidden为true时,根据此设置取出隐藏时的值 14 | isTmp: false, // 临时值; 在根节点(最外级)中无效 15 | label: "名称", 16 | component: "el-input", 17 | value: "天天" 18 | } 19 | // ... 其它项 20 | } 21 | } 22 | ``` 23 | 24 | ### 实例 25 | 26 | 27 | 28 | ```html 29 | 30 | 31 | 142 | ``` 143 | 144 | 145 | 146 | ### 临时值 147 | 临时值是指此项在页面中是存在的,但表单值并不会输出。
`应用场景`:当编辑用户信息,界面显示从后台接口取出`姓名、性别、生日`等信息,但`姓名、性别`不能提交到后端修改,此时就可以把`姓名、性别`设置为临时值,表单并不会取出此两项值 148 | 149 | 150 | ### 隐藏值 151 | 隐藏值只有当当前的hidden为true时有效;当hidden为true时,hdValue的设置对此项值的影响: 152 | - `不设置/undefined`: 不取出此项的值; 153 | - `null`: 此项的值是什么照样取出;若是properties,则其子节点也要设置为null; 154 | - `其它值`: 取出此值,如值为hdValue为5,则取出为5 155 | 156 | ::: warning 注意 157 | 1. `隐藏`包括本节点的hidden为true或祖先节点存在hidden为true; 158 | 2. `临时值`的优先级比`hdValue`大,当同时符合条件时(为临时值、hidden为true且hdValue非undefined),也不会取出此项值。 159 | 3. `临时值`只是值不输出而已,它和`hdValue`一样,并不影响[根值](./explain.md#根值)的动态解析。 160 | ::: 161 | 162 | 163 | -------------------------------------------------------------------------------- /docs/base/install.md: -------------------------------------------------------------------------------- 1 | # 安装/全局配置 2 | 3 | ## npm安装 4 | ```js 5 | // 安装 6 | npm install --save vue-easy-form 7 | 8 | // 引用 9 | import esForm from "vue-easy-form"; 10 | ``` 11 | 12 | ## cdn安装 13 | 可以通过[unpkg.com/vue-easy-form](https://unpkg.com/browse/vue-easy-form/) 获取到最新版本的资源,在页面上引入`js`文件即可 14 | ```html 15 | 16 | 17 | 21 | 22 | // 引用 23 | var esForm = window["esForm"]; 24 | ``` 25 | 26 | ## 注册 27 | 注册的作用主要是:声明一个全局的`es-form`组件和表单框的基本配置。
28 | `语法`:Vue.use(esForm, options); 29 | 30 | ```js 31 | Vue.use(esForm); 32 | 或 33 | Vue.use(esForm, 34 | { 35 | rowHeight: 40, 36 | rowSpace: 20, 37 | labelWidth: 100, 38 | offsetLeft: 0, 39 | offsetRight: 0, 40 | colon: false, 41 | direction: "h", 42 | defaultCom: "input", // 如:若用element-ui, 改为el-input 43 | defaultVal: "", // 对defaultCom这个组件的value设置默认值 44 | trimDoms: ["input", "textarea", "el-input"], // 数组,空数组会全部清空 45 | hasConsole: process.env.NODE_ENV != "production" // 推荐写成动态,编译时不用修改 46 | }); 47 | ``` 48 | 参数(get): 49 | 50 | - esForm: 必填,所引入的esForm组件 51 | - options:非必填,全局设置 52 | 53 | ## 全局设置 54 | 55 | 表单全局配置有如下 56 | | 属性名 | 默认值 | 说明 57 | | -- | -- | -- 58 | | rowHeight | 40 | 设置每一项(行)的高度;主要用于项label和项组件横向对齐 59 | | rowSpace | 20 | 整数(px) 设置项(行)与项(行)的距离 60 | | labelWidth | 100 | 整数(px) 设置项(行)的label的宽度 61 | | offsetLeft | 0 | 整数(px) 项的左偏移量 62 | | offsetRight | 0 | 整数(px) 项的右偏移量 63 | | colon | false | 是否有冒号 64 | | direction | "h" | 竖排还是横排 65 | | defaultCom | "input" | 当配置时,不写component.name时用这个 66 | | defaultVal | "" | 对defaultCom的补充,当组件为defaultCom时且没有设置默认值,则取此值;
`注:此值对其它组件不补充` 67 | | trimDoms | ["input", "textarea", "el-input"] | 指出哪些表单元素需要去掉左右两边空格 68 | | hasConsole | false | 所有的表单是否有调试控制台; 若想设置`单个表单`,可在`对应的表单`中设置hasConsole -------------------------------------------------------------------------------- /docs/base/properties.md: -------------------------------------------------------------------------------- 1 | # 块布局/标题 2 | 3 | 字段(见`行高亮`): 4 | ```js {4,5,6,7,8,14,15} 5 | propName: { 6 | ui: { // 块(properties)的ui配置 7 | 8 | showBody: true, // 隐藏/打开切换按钮;不设置则没有切换按钮 9 | toggleTexts: ["打开", "隐藏"], // 切换按钮不同状态显示的文字 10 | type: "", // 整个块的布局类型;值:"bg","block","bg-block"或空值 11 | hasBorder: false, // 内容区是否有边框 12 | padding: undefined, // 内容区的内边距;若没有设置,则根据type和hasBorder的值进行自动取值 13 | 14 | colon: false // label中是否有冒号 15 | // ... 16 | }, 17 | label: false, // 块properties的label一般都设置为false 18 | title: "标题", 19 | properties: { 20 | name: { 21 | label: "名称", 22 | component: "el-input", 23 | value: "天天" 24 | } 25 | // ... 其它项 26 | } 27 | } 28 | ``` 29 | 30 | ### 实例 31 | 32 | 33 | 34 | ```html 35 | 36 | 37 | 148 | ``` 149 | 150 | 151 | 152 | ### 子属性 153 | 当properties的子属性(如name)设置为`null`、`undefined`、`false`时,说明此项是不显示的; 这样写的目的是为了提高代码的可读性。 154 | > `场景`:新增和编辑可能用到同一个页面,编辑时可能某些项是不需要的,但是新增是需要填写的;写法如:name: this.$route.params.id ? false : {正常的组件} 155 | 156 | ::: warning 注意 157 | 当`properties`和`component`同时存在, `component`将失效。 158 | ::: 159 | 160 | -------------------------------------------------------------------------------- /docs/base/quickstart.md: -------------------------------------------------------------------------------- 1 | # 快速上手 2 | 3 | ### 实例 4 | ```html 5 | 6 | ``` 7 | 8 | ## 标准写法 9 | 10 | 标准的表单配置从一个对象开始,里面包含一个properties,可在每一层properties这一相同的层级中设置一些影响此表单的基本配置(如ui)。 11 | 12 | 13 | 14 | 15 | ```html 16 | 65 | ``` 66 | 67 | 68 | 69 | ## 简单写法 70 | 71 | 当表单的基础配置已经完成,不需要个性化设置,可以写一个简单的写法,省略掉properties这一层,直接进入表单属性 72 | 73 | 74 | 75 | 76 | ```html 77 | 111 | ``` 112 | 113 | 114 | 115 | 116 | 117 | -------------------------------------------------------------------------------- /docs/base/scopedSlots.md: -------------------------------------------------------------------------------- 1 | # 组件插槽 2 | 3 | 字段:`scopedSlots` 4 | 5 | ## 配置格式 6 | ``` js 7 | // attrName为某个属性的索引,如: label, title, component 8 | `attrName`: { // 若是一个对象,可写成一个Object 9 | name: "el-select", // 组件名,组件名不支持动态解析 10 | 11 | //... 组件的其它配置:如 style class props 12 | 13 | // 写法一:若是字符串,数值,布尔,函数,则可简写 14 | scopedSlots: "123", // 则默认的slotName为default 15 | 16 | // 写法二:数组 17 | scopedSlots: []; // 内容可为字符串,数值,布尔,组件对象,但不能是函数(可以用函数返回数组) 18 | 19 | // 写法三:对象(键值对):指出匹配哪个slotName 20 | scopedSlots: { // 插槽:键值对写法:具体见“组件插槽” 21 | 22 | default: { // 自定义组件,支持动态解析:scopedSlots必须指定slotName 23 | // hidden: false, // 控制是否隐藏 24 | name: "el-tag", 25 | text: "测试slot" 26 | }, 27 | 28 | slotName1: "slotName1测试", 29 | 30 | slotName2: ["slotName1测试"], // 内容可为字符串,数值,布尔,组件对象,但不能是函数(可以用函数返回数组) 31 | 32 | // data => {global, rootData, root, pathKey, idxChain, index, $hidden} 见动态解析函数写法 33 | // scoped => 作用域插槽所携带的值 34 | slotName3: function(data, scoped) { 35 | 36 | // 返回值可返回:字符串,数值,布尔,组件,虚拟节点 37 | // 组件: 这里不支持动态了,用户可用data和scoped改造组件 38 | return { 39 | name: "el-tag", 40 | text: "测试slot" 41 | }; 42 | 43 | // jsx写法:返回虚拟节点 44 | // 为了兼容低版本的vue,无论是否使用scoped,虚拟节点都必须在函数里面返回 45 | return {scoped.color}; 46 | 47 | // 返回数组 48 | return []; 49 | 50 | // 返回null,undefined 51 | return undefined; // 说明此插槽为空 52 | } 53 | } 54 | 55 | } 56 | ``` 57 | [动态解析 > 函数写法](./parse.md#函数写法) 58 | 59 | ### 实例 60 | 61 | 62 | 63 | 64 | <<< @/docs/.vuepress/components/slots/scoped.vue 65 |
66 | 67 | <<< @/docs/../src/components/units/slot.vue 68 |
69 |
70 | 71 | -------------------------------------------------------------------------------- /docs/base/settings.md: -------------------------------------------------------------------------------- 1 | # schema配置 2 | 3 | ## 配置列表 4 | | 属性名 | 说明 | 类型 | 可选值| 默认值 | 备注 5 | | -- | -- | -- | -- | -- | -- 6 | | [title](./title.md) | 表单块(properties)的名称 | string
object | -- | -- | properties中有效;
支持[动态解析](./parse.md)和[组件化](./com-format.md) 7 | | [ui](./ui.md) | 影响块(properties)的布局 | string
object | -- | -- | properties中有效 8 | | [rowHeight](./ui.md) | 项的行高 | number | -- | 上一级 | 不设置时,
继承上一级的ui.rowHeight 9 | | [rowSpace](./ui.md) | 与上一次项(行)之间的间隔 | number | -- | 上一级 | 不设置时,
继承上一级的ui.rowSpace 10 | | [labelWidth](./ui.md) | 项label的宽度 | number | -- | 上一级 | 不设置时,
继承上一级的ui.labelWidth 11 | | [offsetLeft](./ui.md) | 项的左边偏移量 | number | >=0 | 上一级 | 不设置时,
继承上一级的ui.offsetLeft 12 | | [offsetRight](./ui.md) | 项的右边偏移量 | number | >=0 | 上一级 | 不设置时,
继承上一级的ui.offsetRight 13 | | [colon](./ui.md) | label中是否有冒号 | boolean | -- | 上一级 | 不设置时,
继承上一级的ui.colon 14 | | [direction](./ui.md) | 项的排版方向 | string | "h" "v" | 上一级 | 不设置时,
继承上一级的ui.direction 15 | | [hidden](./hidden.md) | 是否隐藏此项 | boolean | -- | false | 支持[动态解析](./parse.md) 16 | | [hdValue](./hidden.md) | `hidden`或`祖先hidden`为true时有效 | -- | -- | -- | 值为`undefined`时:相应的字段不会取出
值为`null`时: 为正常遍历节点
`其余`: 取此值 17 | | [isTmp](./hidden.md) | 临时值 | boolean | -- | false | 表单不输出此项值,但可作为表单内部使用 18 | | [col](./d-col-group.md) | 列数(宽度) | number | 1到24 | 24 | 一行分24列 19 | | [group](./d-col-group.md) | 项与项进行分组 | string | -- | -- | 设置为分组,是`相邻`的;
只对`component`有效,对`properties`是无效的 20 | | [label](./label.md) | label设置 | strin
object | -- | false | false代表隐藏label
支持[动态解析](./parse.md)和[组件化](./com-format.md) 21 | | [component](./component.md) | 配置组件 | string
object | -- | 全局 | 22 | | [unit](./unit.md) | 对项进行补充 如:px| string | -- | -- | 支持[动态解析](./parse.md)和[组件化](./com-format.md) 23 | | [desc](./desc.md) | 对项进行描述 | string | -- | 全局 | 支持[动态解析](./parse.md)和[组件化](./com-format.md) 24 | | [help](./help.md) | 对项设置帮助 | string
object | -- | -- | 支持[动态解析](./parse.md)和[组件化](./com-format.md) 25 | | [value](./value.md) | 组件的值 | -- | -- | -- | 26 | | [isTrim](./value.md) | 是否去掉两边的空格 | boolean | -- | true | 27 | | [format](./format.md) | 数值转换 | array
object | -- | -- | 组件内有效 28 | | [array](./array.md) | 此项为数组 | string
object | -- | -- | 根节点无效 29 | | [rules](./rules.md) | 规则设置 | object | -- | -- | 30 | | layout | 布局 | object
string | "space"
"tabs"
Object | false | `区分大小写`
`space`: 是一个[占位空间](./d-col-group.md)
`tabs`:下一级为[tabs布局](./tabs.md) 31 | | [properties](./properties.md) | 下一级 | object | -- | -- | 下一级,重复上面的配置 32 | | [autoMatch](./auto-match.md) | 一二级的数据自动匹配 | boolean | -- | false | 只在根节点中有效 33 | ### [ui属性](./ui.md) 34 | 35 | 只有在properties中有效;主要是影响头部的样式和body的边距 36 | 37 | | 属性名 | 说明 | 类型 | 可选值| 默认值 | 备注 38 | | -- | -- | -- | -- | -- | -- 39 | | showBody | 隐藏/打开切换按钮 | boolean | -- | -- | `不设置`代表没有切换按钮;
`设置`说明有切换按钮,值代表最先是否显示body 40 | | toggleTexts | 切换按钮不同状态显示的文字 | array | -- | ["打开", "隐藏"] | 数组长度必须是2,且内容是字符串 41 | | type | 整个块的布局类型 | string | "bg"
"block"
"bg-block"
""| -- | `block`就是前面有一竖 42 | | hasBorder | 内容区是否有边框 | boolean | -- | false | -- 43 | | padding | 内容区的内边距 | number
string
array | -- | -- | `不设置`:根据实际情况调整
`整数`:20
`字符串`:"20px 10px", `数组`:[20, 10] 44 | | rowHeight | 项的行高 | number | -- | 上一级 | 会影响下一级 45 | | rowSpace | 与上一次项(行)之间的间隔 | number | -- | 上一级 | 会影响下一级 46 | | labelWidth | 项label的宽度 | number | -- | 上一级 | 会影响下一级 47 | | offsetLeft | 项的左边偏移量 | number | >=0 | 上一级 | 会影响下一级 48 | | offsetRight | 项的右边偏移量 | number | >=0 | 上一级 | 会影响下一级 49 | | colon | label中是否有冒号 | boolean | -- | 上一级 | 会影响下一级 50 | | direction | 项的排版方向 | string | "h" "v" | 上一级 | 会影响下一级 51 | 52 | 53 | ### layout属性 54 | layout有两种作用: 55 | - [占位空间](./d-col-group.md):此项什么都不显示,类似visibility: hidden; 56 | - [tabs布局](./tabs.md):对properties下的属性进行tabs布局 57 | 58 | 写法: 59 | - `String`: 字符串,值有:`space` | `tabs` 60 | - `Object`: 对象,其详细配置如下: 61 | 62 | | 属性名 | 说明 | 类型 | 可选值| 默认值 | 备注 63 | | -- | -- | -- | -- | -- | -- 64 | | name | 类型 | string | space
tabs | -- | -- 65 | | type | 布局类型 | string | bg
card
line | card | -- 66 | | hasBorder | 内容区是否有边框 | boolean | -- | false | -- 67 | | padding | 内容区的内边距 | number/string | -- | -- | `不设置`:根据实际情况调整
`整数`:20
`字符串`:"20px 10px"
`数组`:[20, 10] 68 | 69 | 注:`当name为space时,其余的属性是无效的,不用设置也可` 70 | -------------------------------------------------------------------------------- /docs/base/title.md: -------------------------------------------------------------------------------- 1 | # 标题/title 2 | 3 | 字段:`title`,就是块(properties)的标题, 它在properties(同级)才有效 4 | 5 | 值类型有: 6 | - `false`: 默认值,没有标题 7 | - `string`: 标题;支持[动态解析](./parse.md) 8 | - `object`: 一个对象,见[组件格式](./com-format.md) 9 | 10 | ### 实例 11 | 12 | 13 | 14 | ```html 15 | 16 | 17 | 94 | ``` 95 | 96 | 97 | 98 | ### 组件详解 99 | 100 | | 属性名 | 说明 | 类型 | 可选值| 默认值 101 | | -- | -- | -- | -- | -- 102 | | hidden | 控制组件是否隐藏, 支持[动态解析](./parse.md) | boolean | -- | false 103 | | 其它 | 跟[组件格式](./com-format.md)一样 | -- | -- | -- 104 | 105 | title和[ui属性](./settings.md#ui属性)都只有在properties中有效 106 | 107 | -------------------------------------------------------------------------------- /docs/base/unit.md: -------------------------------------------------------------------------------- 1 | # 单位/unit 2 | 3 | 字段:`unit`
4 | 值类型有: 5 | - `string`: 补充的内容;支持[动态解析](./parse.md) 6 | - `object`: 一个对象,见[组件格式](./com-format.md) 7 | 8 | 20 | 21 | ### 实例 22 | 23 | 24 | 25 | 26 | ```html 27 | 28 | 29 | 94 | ``` 95 | 96 | 97 | 98 | ### 组件详解 99 | 100 | | 属性名 | 说明 | 类型 | 可选值| 默认值 101 | | -- | -- | -- | -- | -- 102 | | hidden | 控制组件是否隐藏, 支持[动态解析](./parse.md) | boolean | -- | false 103 | | 其它 | 跟[组件格式](./com-format.md)一样 | -- | -- | -- 104 | -------------------------------------------------------------------------------- /docs/base/value.md: -------------------------------------------------------------------------------- 1 | # 表单值 2 | 3 | 字段(见`行高亮`): 4 | ```js {7,8} 5 | propName: { 6 | label: "label值", 7 | properties: { 8 | name: { 9 | label: "名称", 10 | component: "el-input", 11 | isTrim: true, // 是否去掉值两边空间 12 | value: "天天" // 默认值。重置时也用到此值 13 | } 14 | // ... 其它项 15 | } 16 | } 17 | ``` 18 | ### 实例 19 | 20 | 21 | 22 | ```html 23 | 24 | 25 | 90 | ``` 91 | 92 | 93 | 94 | >[pathKey:项组件路径](./explain.md#项组件路径)的组成形式:各个块(properties)中的属性(如name, base)连接起来形式唯一的字符路径 95 | 96 | ### 设值 97 | 有两种方式设置: 98 | - `formValue`: 表单非深度监听此值,若要通过此值设置表单值,必须重新改变此变量的地址(`不推荐`) 99 | - `form.setValue(key, value)`: 表单设置值(`推荐`) 100 | 101 | 如: 102 | ```js 103 | // setValue参数只有一个对象,则一次性设置多个值 104 | // 设置多个值:只设置所要设置的key, 如endTime是不会改变的 105 | form.setValue({ // 多值设置 106 | name: "广告名称", 107 | base: { 108 | backgroundColor: "#f00" 109 | } 110 | }); 111 | 112 | // setValue参数有两个,单设置某项的值 113 | form.setValue("base.backgroundColor", "#f00"); // 单值设置 114 | ``` 115 | 116 | ### 取值 117 | 取出的值为一个对象,有两种方式取出: 118 | 119 | - `formValue`: v-model双向绑定(Vue机制,非实时) 120 | - [`form.getValue()`](./form.md#表单方法): 表单输出的值([setValue](./form.md#表单方法)过后,可即时取出,实时)
121 | 注:若想取`某个项`的值,可从上值中解析 122 |
123 |
124 | 表单的[根值](./explain.md#根值) 125 | - [`form.getRootData()`](./form.md#表单方法): 表单的根(原始)值([setValue](./form.md#表单方法)过后,可即时取出,实时) 126 | 127 | ### 去掉两边的空格/isTrim 128 | 1. 当isTrim不设置时,系统会根据全局设置的[trimDoms](./install.md#全局设置)来判断组件是否去掉空格; 129 | 2. isTrim触发的时机为change.native或者change,也就是`输入框改变,光标离开输入框时`才会触发; 130 | 3. 直接设置值([setValue](./form.md#表单方法))是不会触发change.native或者change。 -------------------------------------------------------------------------------- /docs/donate.md: -------------------------------------------------------------------------------- 1 | ::: tip 赏一杯 2 | 如果你觉得这个项目对你有所帮助,你可以帮作者买一杯咖啡以表示鼓励 3 | ::: 4 | 5 |
6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 16 | 19 | 20 | 21 |
WechatAlipay
14 | 15 | 17 | 18 |
22 |
23 | 24 | 25 | -------------------------------------------------------------------------------- /docs/images/alipay.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chengaohe45/vue-easy-form/8cee81eea170316a51ae0d77e3dbb84d6f3076f1/docs/images/alipay.png -------------------------------------------------------------------------------- /docs/images/wechat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chengaohe45/vue-easy-form/8cee81eea170316a51ae0d77e3dbb84d6f3076f1/docs/images/wechat.png -------------------------------------------------------------------------------- /docs/version/overview.md: -------------------------------------------------------------------------------- 1 | # 版本总览 2 | 3 |
4 | 系统的最新版本:version 5 |
6 | 7 | ## 文档版本选择 8 |
9 | 10 | 11 | 12 |
13 | 14 | ## 当前版本记录 15 | ### 1.8.4 16 | - 优化底层的实现方法:修复`纯$attrs、$listeners实现的组件`无法双向绑定的问题 17 | ### 1.8.2 18 | - [长度](../base/d-col-group.md)增加`最大/最小`写法. 19 | - 优化[tabs](../base/tabs.md#实例)布局. 20 | 21 | ### 1.8.1 22 | - [组件事件](../base/com-format.md#组件事件)增加`true`写法. 23 | 24 | ### 1.8.0 25 | - 修复[table数组](../base/array.md)的组件不可隐藏的问题; 26 | - 优化表单[getRef](../base/form.md#表单方法)方法. 27 | 28 | 29 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-easy-form", 3 | "version": "1.8.6", 4 | "private": false, 5 | "description": "", 6 | "homepage": "", 7 | "author": { 8 | "name": "peter.chen", 9 | "email": "", 10 | "url": "" 11 | }, 12 | "main": "dist/es-form.umd.min.js", 13 | "scripts": { 14 | "dev": "vue-cli-service serve", 15 | "serve": "vue-cli-service serve", 16 | "build": "vue-cli-service build", 17 | "release": "vue-cli-service build --target lib --name es-form ./src/package/index.js", 18 | "lint": "vue-cli-service lint", 19 | "test:unit": "vue-cli-service test:unit", 20 | "docs:dev": "vuepress dev docs", 21 | "docs:build": "vuepress build docs", 22 | "v:dev": "vuepress dev docs version", 23 | "v:build": "vuepress build docs version" 24 | }, 25 | "license": "MIT", 26 | "keywords": [ 27 | "vue", 28 | "easy-form", 29 | "es-form", 30 | "vue-easy-form" 31 | ], 32 | "files": [ 33 | "dist", 34 | "README.md" 35 | ], 36 | "dependencies": { 37 | "vue": "^2.6.11" 38 | }, 39 | "devDependencies": { 40 | "@vue/cli-plugin-babel": "^3.0.1", 41 | "@vue/cli-plugin-eslint": "^3.0.1", 42 | "@vue/cli-plugin-unit-mocha": "^3.0.1", 43 | "@vue/cli-service": "^3.0.1", 44 | "@vue/eslint-config-prettier": "^3.0.1", 45 | "@vue/test-utils": "^1.0.0-beta.20", 46 | "axios": "^0.19.2", 47 | "chai": "^4.1.2", 48 | "element-ui": "^2.13.0", 49 | "node-sass": "^4.9.0", 50 | "sass-loader": "^7.0.1", 51 | "vue-router": "^3.0.1", 52 | "vue-template-compiler": "^2.6.11", 53 | "vuepress": "^1.2.0", 54 | "webpack-api-mocker": "^1.5.15" 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | autoprefixer: {} 4 | } 5 | }; 6 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chengaohe45/vue-easy-form/8cee81eea170316a51ae0d77e3dbb84d6f3076f1/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | easy form 9 | 10 | 11 | 14 |
15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 52 | 53 | 120 | -------------------------------------------------------------------------------- /src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chengaohe45/vue-easy-form/8cee81eea170316a51ae0d77e3dbb84d6f3076f1/src/assets/logo.png -------------------------------------------------------------------------------- /src/components/common/header.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 34 | 35 | 74 | -------------------------------------------------------------------------------- /src/components/common/main-footer.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 36 | 37 | 51 | -------------------------------------------------------------------------------- /src/components/common/nav.vue: -------------------------------------------------------------------------------- 1 | 40 | 41 | 76 | 77 | 97 | -------------------------------------------------------------------------------- /src/components/demo-frame/schemas/operation-schema.js: -------------------------------------------------------------------------------- 1 | export default { 2 | ui: { 3 | colon: true, 4 | rowSpace: 10, 5 | labelWidth: 72 6 | }, 7 | properties: { 8 | key: { 9 | label: "Key", 10 | component: { 11 | name: "el-input", 12 | props: { 13 | placeholder: "项组件路径/pathKey" 14 | } 15 | }, 16 | value: "", 17 | help: { 18 | props: { 19 | content: "点击查看pathKey", 20 | href: 21 | "https://chengaohe45.github.io/vue-easy-form-docs/dist/base/explain.html#项组件路径" 22 | } 23 | } 24 | // desc: "pathKey可以为空;为空则\"值\"要写成一个Object" 25 | }, 26 | value: { 27 | label: "Value", 28 | component: { 29 | name: "el-input", 30 | props: { 31 | placeholder: 32 | "es: {{$root.key}} ? '格式:整数(123),字符串(\"123\")': '格式:对象({...})'", 33 | type: "textarea", 34 | rows: 4 35 | } 36 | }, 37 | value: "", 38 | rules: { 39 | required: true, 40 | emptyMsg: "值不能为空" 41 | }, 42 | desc: 43 | "es: {{$root.key}} ? '对应的值(字符串两边记得加上双引号,如:\"你好\")': (!{{$global}}.fromDoc ? '必须是一个Object(可复制下面的值来试试)' : '必须是一个Object')", 44 | help: { 45 | props: { 46 | content: 47 | "es: !{{$global}}.fromDoc ? '此值会用eval解析, 所以输入要符合格式。
点击可查看值设值' : '此值会用eval解析, 所以输入要符合格式。'", 48 | href: 49 | "es: !{{$global}}.fromDoc ? 'https://chengaohe45.github.io/vue-easy-form-docs/dist/base/value.html#设值' : ''" 50 | } 51 | } 52 | } 53 | } 54 | }; 55 | -------------------------------------------------------------------------------- /src/components/help/index.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 63 | 64 | 96 | -------------------------------------------------------------------------------- /src/components/register.js: -------------------------------------------------------------------------------- 1 | import Vue from "vue"; 2 | import help from "./help/index"; 3 | import unit from "./units/unit"; 4 | import desc from "./units/desc"; 5 | import label from "./units/label"; 6 | import title from "./units/title"; 7 | import select from "./units/select"; 8 | 9 | import slot from "./units/slot"; 10 | let all = { 11 | register() { 12 | Vue.component("g-help", help); 13 | Vue.component("g-unit", unit); 14 | Vue.component("g-desc", desc); 15 | Vue.component("g-label", label); 16 | Vue.component("g-title", title); 17 | Vue.component("g-select", select); 18 | Vue.component("g-slot", slot); 19 | } 20 | }; 21 | 22 | export default all; 23 | -------------------------------------------------------------------------------- /src/components/tags/index.vue: -------------------------------------------------------------------------------- 1 | 31 | 32 | 120 | 121 | 148 | -------------------------------------------------------------------------------- /src/components/units/README.md: -------------------------------------------------------------------------------- 1 | # units 2 | 3 | 这个文件夹主要放一些独立而通用的小组件,具体功能见每个小组件 4 | 5 | -------------------------------------------------------------------------------- /src/components/units/align-desc.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 36 | 37 | 43 | -------------------------------------------------------------------------------- /src/components/units/checkbox-group.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 15 | 16 | 68 | -------------------------------------------------------------------------------- /src/components/units/desc.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 26 | 27 | 45 | -------------------------------------------------------------------------------- /src/components/units/label.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 31 | 32 | 46 | -------------------------------------------------------------------------------- /src/components/units/radio-group.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 15 | 16 | 68 | -------------------------------------------------------------------------------- /src/components/units/select.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 14 | 15 | 61 | -------------------------------------------------------------------------------- /src/components/units/slot.vue: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 16 | -------------------------------------------------------------------------------- /src/components/units/title.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 21 | 22 | 50 | -------------------------------------------------------------------------------- /src/components/units/unit.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 32 | 33 | 47 | -------------------------------------------------------------------------------- /src/libs/constant.js: -------------------------------------------------------------------------------- 1 | const SYSTEM_CHECK_LOGIN = "SYSTEM_CHECK_LOGIN"; 2 | const SYSTEM_PATH_RECORDS = "SYSTEM_PATH_RECORDS"; 3 | const SYSTEM_LAST_LIST_STAT = "SYSTEM_LAST_LIST_STAT"; 4 | const constant = { 5 | SYSTEM_CHECK_LOGIN, 6 | SYSTEM_PATH_RECORDS, 7 | SYSTEM_LAST_LIST_STAT 8 | }; 9 | 10 | export default constant; 11 | -------------------------------------------------------------------------------- /src/libs/storage.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | /* -----------------------本地储存模块----------------------------- */ 4 | /* 判断是否过期,如果过期则先删除 */ 5 | var expiresKey = "__expires__"; 6 | 7 | var d = new Date().getTime(); 8 | for (var key in localStorage) { 9 | var v = localStorage.getItem(key); 10 | if (v) { 11 | try { 12 | v = window.JSON.parse(v); 13 | } catch (e) { 14 | // console.warn(v); 15 | } 16 | if ( 17 | Object.prototype.toString 18 | .call(v) 19 | .toLowerCase() 20 | .indexOf("array") > 0 21 | ) { 22 | var expires = v[0] && v[0][expiresKey]; 23 | if (expires && /^\d{13,}$/.test(expires) && expires <= d) 24 | localStorage.removeItem(key); 25 | } 26 | } 27 | } 28 | 29 | var storage = { 30 | /** 31 | * 保存值到本地存储 32 | * @method set 33 | * @param {String} key 需要保存的键名 34 | * @param {Object|String|Array|Boolean} value 需要保存的值 35 | * @param {Number} expires 存储的过期时间 单位:秒 36 | */ 37 | set: function(key, value, expires) { 38 | var v = []; /* 这个数据将记录两个值:第一个值为过期时间,其中0为永久性保存;第二个值为用户需要保存的值 */ 39 | var expiresObj = {}; 40 | if (expires && typeof expires === "number") { 41 | var d = new Date().getTime(); 42 | expiresObj[expiresKey] = d + expires * 1000; 43 | } else { 44 | expiresObj[expiresKey] = 0; /* 0 代表永久性保存 */ 45 | } 46 | v.push(expiresObj); 47 | v.push(value); 48 | localStorage.setItem(key, window.JSON.stringify(v)); 49 | }, 50 | /** 51 | * 需要获取的本地存储 52 | * @method get 53 | * @param {String} key 对应的key 54 | * @return {Object|String|Array|Boolean} 返回值 55 | */ 56 | get: function(key, defaultValue = "") { 57 | var value = localStorage.getItem(key); 58 | if (value === null || value === undefined) { 59 | value = defaultValue; 60 | return value; 61 | } 62 | 63 | try { 64 | value = window.JSON.parse(value); 65 | } catch (e) { 66 | return value; /* 防止以前的写法:没有经过window.JSON.stringify对setItem的值进行处理 */ 67 | } 68 | 69 | if (typeof value !== "object") { 70 | return value; 71 | } 72 | 73 | var expires = value[0] && value[0][expiresKey]; 74 | if (typeof expires === "number") { 75 | /* 现在的写法 */ 76 | if (expires && /^\d{13,}$/.test(expires)) { 77 | var d = new Date().getTime(); 78 | if (expires <= d) { 79 | localStorage.removeItem(key); 80 | return defaultValue; 81 | } 82 | } 83 | return value[1]; 84 | } else { 85 | /* 为了兼容之前的业务,这样写不是现在的格式所以直接返回以前的值 */ 86 | return value; 87 | } 88 | }, 89 | /** 90 | * 删除一个本地存储 91 | * @method remove 92 | * @param {String} key 需要删除的key 93 | */ 94 | remove: function(key) { 95 | localStorage.removeItem(key); 96 | } 97 | }; 98 | 99 | export default storage; 100 | -------------------------------------------------------------------------------- /src/libs/uri.js: -------------------------------------------------------------------------------- 1 | var uri = { 2 | query(name, fromHash) { 3 | var search = fromHash ? window.location.hash : location.search; 4 | if (search) { 5 | var regStr = "(^|\\?|&)" + name + "=([^&]*)(\\s|&|$)"; 6 | var reg = new RegExp(regStr, "i"); 7 | if (reg.test(search)) { 8 | return window.decodeURIComponent(RegExp.$2.replace(/\+/g, " ")); 9 | } 10 | } 11 | return ""; 12 | }, 13 | 14 | get(name) { 15 | var val = this.query(name); 16 | if (!val) { 17 | val = this.query(name, true); 18 | } 19 | return val; 20 | }, 21 | 22 | has(name, fromHash) { 23 | var search = fromHash ? window.location.hash : location.search; 24 | if (search) { 25 | var regStr = "(^|\\?|&)" + name + "=([^&]*)(\\s|&|$)"; 26 | var reg = new RegExp(regStr, "i"); 27 | if (reg.test(search)) { 28 | return true; 29 | } 30 | } 31 | return false; 32 | } 33 | }; 34 | 35 | export default uri; 36 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from "vue"; 2 | import App from "./App.vue"; 3 | import router from "./router/index"; 4 | 5 | // 引入UI样式,各模块按需引入ui组件 6 | import "element-ui/lib/theme-chalk/index.css"; 7 | //引入ui组件 8 | import elementUI from "element-ui"; 9 | // 引入基本样式 10 | import "@/static/css/index.scss"; 11 | 12 | // import esForm from "@/package/index.js"; 13 | import esForm from "vue-easy-form"; // 见vue.config.js别名 14 | 15 | import gComponent from "@/components/register.js"; 16 | 17 | // 注册全局组件 18 | gComponent.register(); 19 | Vue.use(elementUI); 20 | Vue.use(esForm, { 21 | rowHeight: 40, 22 | rowSpace: 20, 23 | labelWidth: 100, 24 | offsetLeft: 0, 25 | offsetRight: 0, 26 | colon: false, 27 | direction: "h", 28 | defaultCom: "el-input", // 如:若用element-ui, 改为el-input 29 | defaultVal: "", // 对defaultCom这个组件的value设置默认值 30 | trimDoms: ["input", "textarea", "el-input"], // 数组,空数组会全部清空 31 | hasConsole: process.env.NODE_ENV != "production" // 推荐写成动态,编译时不用修改 32 | }); 33 | 34 | /* 35 | 打印类库的版本 36 | 当处于生产环境时:hash中存在libs_version=就行了,不宜写过多的代码来做查询判断;因为只是个调试信息,不影响功能 37 | */ 38 | if ( 39 | process.env.NODE_ENV != "production" || 40 | (location.hash && location.hash.indexOf("libs_version=") >= 1) 41 | ) { 42 | console.log("esForm's version: " + esForm.version); 43 | } 44 | 45 | Vue.config.productionTip = false; 46 | 47 | window.vm = new Vue({ 48 | router, 49 | render: h => h(App) 50 | }).$mount("#app"); 51 | 52 | // window.onerror = function() { 53 | // alert(1); 54 | // }; 55 | -------------------------------------------------------------------------------- /src/package/README.md: -------------------------------------------------------------------------------- 1 | # vue-easy-form简介 2 | 3 | ## vue-easy-form是一个表单工具,能过配置实现自己所需要的表单效果,集布局、验证、逻辑、取值于一身 4 | 5 | ### 全局配置 6 | 7 | -------------------------------------------------------------------------------- /src/package/components/btn.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 9 | 31 | -------------------------------------------------------------------------------- /src/package/components/edit-bottom-btns.vue: -------------------------------------------------------------------------------- 1 | 53 | 54 | 64 | 65 | 115 | -------------------------------------------------------------------------------- /src/package/components/edit-btns.vue: -------------------------------------------------------------------------------- 1 | 67 | 68 | 73 | 74 | 151 | -------------------------------------------------------------------------------- /src/package/components/tabs-btn.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 9 | 33 | -------------------------------------------------------------------------------- /src/package/components/tabs-nav-item.vue: -------------------------------------------------------------------------------- 1 | 79 | 80 | 81 | 82 | 158 | -------------------------------------------------------------------------------- /src/package/index.js: -------------------------------------------------------------------------------- 1 | // import esBase from "./base"; 2 | import esForm from "./index.vue"; 3 | import schemaUtils from "./libs/schema-utils"; 4 | 5 | // import esRules from "./libs/rules.js"; 6 | import esUtils from "./libs/utils"; 7 | import esGlobal from "./libs/global.js"; 8 | // import esConstant from "./libs/constant"; 9 | 10 | const install = function(Vue, globalOpts = {}) { 11 | /* istanbul ignore if */ 12 | if (install.installed) return; 13 | 14 | // 保存外部传入来的Vue(如用来判断vnode) 15 | esUtils.initVue(Vue); 16 | 17 | if (Object.keys(globalOpts).length > 0) { 18 | esUtils.mergeGlobal(esGlobal, globalOpts); 19 | // console.log(esGlobal); 20 | } 21 | 22 | // Vue.component(esConstant.HELP_NAME, esHelp); 23 | // Vue.component("es-base", esBase); 24 | Vue.component("es-form", esForm); 25 | }; 26 | 27 | const check = function(schema) { 28 | return schemaUtils.check(schema); 29 | }; 30 | 31 | /* istanbul ignore if */ 32 | if (typeof window !== "undefined" && window.Vue) { 33 | install(window.Vue); 34 | } 35 | 36 | export default { 37 | version: typeof process != "undefined" ? process.env.VUE_APP_VERSION : "??", 38 | install, 39 | esForm, 40 | check 41 | }; 42 | -------------------------------------------------------------------------------- /src/package/layout/object-table.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 41 | 42 | 56 | -------------------------------------------------------------------------------- /src/package/layout/tabs.vue: -------------------------------------------------------------------------------- 1 | 82 | 83 | 112 | 113 | 164 | -------------------------------------------------------------------------------- /src/package/libs/constant.js: -------------------------------------------------------------------------------- 1 | let constant = { 2 | // HELP_NAME: "es-help", //注册时用的全局名字 3 | UI_MAX_COL: 24, //整修个布局分为多少列,这个值不要随便改, 要跟object.vue的UI_MAX_COL对应 4 | WIDTH_AUTO: "auto", 5 | ARRAY_TABLE: "array-table", // 只支持properities(非叶子) 6 | ARRAY_TABS: "array-tabs", 7 | ARRAY_LEGEND: "array-legend", 8 | ARRAY_ROW: "array", 9 | ARRAY_CARD: "array-card", // 只支持组件(叶子) 10 | 11 | POINT_CENTER_CENTER: "center-center", // tip框与源icon可以居中指向 12 | POINT_ARROW_CENTER: "arrow-center", // tip框偏移,但指向源icon中间 13 | POINT_ARROW_OFFSET: "arrow-offset", // tip框偏移,也不指向源icon中间 14 | 15 | ADJ_NATIVE: "native", // 将原生事件绑定到组件; 本身是区分大小写的;keyup.native为例:原生input.text监听不到,但组件(就只有一个input.text)可以监听到事件, 16 | 17 | INPUT_EVENT: "input", 18 | CHANGE_EVENT: "change", // 应用于数组改变 19 | CLICK_EVENT: "click", 20 | 21 | TAG_INPUT: "input", 22 | TYPE_RADIO: "radio", 23 | TYPE_CHECKBOX: "checkbox", 24 | 25 | KEYUP_NATIVE: "keyup.native", 26 | ENTER_SUBMIT: "@enterSubmit", 27 | ONLY_SUBMIT: "@submit", 28 | 29 | // TYPE_TMP: "tmp", // 表单的临时值,跟组件没有什么区别;只是不会取出;使用场景:快速做标题或编辑时,某些项需要显示但又不需要提交给后台 30 | LAYOUT_SPACE: "space", // 占位符; space不可以乱改,因为其它地方(.vue)有用到 31 | LAYOUT_TABS: "tabs", // properties的子属性是tabs布局; space不可以乱改,因为其它地方(.vue)有用到 32 | 33 | UI_FORM: "_es_form_qwerty_", // 说明界面属于哪种类型 34 | UI_ITEM: "_es_item_qwerrf_", 35 | UI_ARRAY: "_es_array_aadfsd_", 36 | 37 | ES_FUNC_NAME: "__E0S1_2F3U4NC_N4AM5E__", 38 | ES_OPTIONS: "__es__Options__", // 此值要是正规的命名 39 | 40 | PREFIX_STATIC_FUNC: ["s:"], // 对于props里面的属性,以PRE_STATIC_FUNC开头的且是函数,则说明是静态,不解析 41 | 42 | // 原生的表单组件,主要是用来过滤空格 43 | FORM_INPUTS: ["input", "textarea"], 44 | INPUT_CHANGE: "change", 45 | 46 | IDX_CHAIN_KEY: "[i]", // 数组链的代替字符,不可随便改 47 | 48 | COM_TARGET_REF: "__comTarget__" 49 | }; 50 | 51 | export default constant; 52 | -------------------------------------------------------------------------------- /src/package/libs/data-cache.js: -------------------------------------------------------------------------------- 1 | /** 2 | * data-cache.js 3 | * 4 | * Copyright (c) 2020 chengaohe All rights reserved. 5 | * 6 | * 用于记录表单数据 7 | * 8 | */ 9 | 10 | // import utils from "./utils"; 11 | // import constant from "./constant"; 12 | 13 | var formDataMap = {}; 14 | // window.formDataMap = formDataMap; 15 | let dataCache = { 16 | __getData(formId) { 17 | if (!formDataMap[formId]) { 18 | formDataMap[formId] = {}; 19 | } 20 | return formDataMap[formId]; 21 | }, 22 | 23 | getRoot(formId, defaultValue) { 24 | var data = this.__getData(formId); 25 | return data.root ? data.root : defaultValue; 26 | }, 27 | 28 | setRoot(formId, value) { 29 | var data = this.__getData(formId); 30 | data.root = value; 31 | }, 32 | 33 | getGlobal(formId, defaultValue) { 34 | var data = this.__getData(formId); 35 | return data.global ? data.global : defaultValue; 36 | }, 37 | 38 | setGlobal(formId, value) { 39 | var data = this.__getData(formId); 40 | data.global = value; 41 | }, 42 | 43 | getHiddenFunc(formId) { 44 | var data = this.__getData(formId); 45 | return data.hiddenFunc; 46 | }, 47 | 48 | setHiddenFunc(formId, value) { 49 | var data = this.__getData(formId); 50 | data.hiddenFunc = value; 51 | }, 52 | 53 | remove(formId) { 54 | if (formDataMap.hasOwnProperty(formId)) { 55 | formDataMap[formId].hiddenFunc = null; 56 | delete formDataMap[formId]; 57 | } 58 | } 59 | }; 60 | // window.dataCache = dataCache; 61 | 62 | export default dataCache; 63 | -------------------------------------------------------------------------------- /src/package/libs/drag.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 点击拖拽 3 | * @param {*} event mousedown事件 4 | * @param {*} callback 回调函数。mousemove返回偏移量,mouseup返回false 5 | */ 6 | function esDrag(event, callback) { 7 | // The mouse position (in window coordinates) 8 | // at which the drag begins 9 | 10 | var cb = callback; 11 | 12 | var startX = event.clientX, 13 | startY = event.clientY; 14 | // var deltaX = startX - origX, deltaY = startY - origY; 15 | 16 | document.addEventListener("mousemove", moveHandler, true); 17 | document.addEventListener("mouseup", upHandler, true); 18 | window.addEventListener("blur", upHandler, true); 19 | 20 | // We've handled this event. Don't let anybody else see it. 21 | if (event.stopPropagation) event.stopPropagation(); 22 | // DOM Level 2 23 | else event.cancelBubble = true; // IE 24 | 25 | // Now prevent any default action. 26 | if (event.preventDefault) event.preventDefault(); 27 | // DOM Level 2 28 | else event.returnValue = false; // IE 29 | 30 | /** 31 | * This is the handler that captures mousemove events when an element 32 | * is being dragged. It is responsible for moving the element. 33 | **/ 34 | function moveHandler(e) { 35 | if (!e) e = window.event; // IE Event Model 36 | 37 | // Move the element to the current mouse position, adjusted as 38 | // necessary by the offset of the initial mouse-click. 39 | // elementToDrag.style.left = (e.clientX - deltaX) + "px"; 40 | // elementToDrag.style.top = (e.clientY - deltaY) + "px"; 41 | 42 | var offsetX = e.clientX - startX; 43 | var offsetY = e.clientY - startY; 44 | 45 | cb({ offsetX, offsetY }); 46 | 47 | // And don't let anyone else see this event. 48 | if (e.stopPropagation) e.stopPropagation(); 49 | // DOM Level 2 50 | else e.cancelBubble = true; // IE 51 | } 52 | 53 | /** 54 | * This is the handler that captures the final mouseup event that 55 | * occurs at the end of a drag. 56 | **/ 57 | function upHandler(e) { 58 | if (!e) e = window.event; // IE Event Model 59 | 60 | document.removeEventListener("mousemove", moveHandler, true); 61 | document.removeEventListener("mouseup", upHandler, true); 62 | window.removeEventListener("blur", upHandler, true); 63 | 64 | cb(false); 65 | 66 | cb = null; 67 | 68 | // And don't let the event propagate any further. 69 | if (e.stopPropagation) e.stopPropagation(); 70 | // DOM Level 2 71 | else e.cancelBubble = true; // IE 72 | } 73 | 74 | event = null; 75 | callback = null; 76 | } 77 | 78 | export default esDrag; 79 | -------------------------------------------------------------------------------- /src/package/libs/global.js: -------------------------------------------------------------------------------- 1 | // import constant from "./constant"; 2 | // import esHelp from "../components/help.vue"; 3 | 4 | let global = { 5 | boxRowHeight: 40, 6 | boxRowSpace: 20, 7 | boxLabelWidth: 100, 8 | 9 | rowHeight: 40, 10 | rowSpace: 20, 11 | labelWidth: 100, 12 | colon: false, 13 | direction: "h", 14 | offsetLeft: 0, // 项的左边偏移 15 | offsetRight: 0, // // 项的右边偏移 16 | 17 | defaultCom: "input", 18 | defaultVal: "", // 这个是对defaultCom的补充,当组件为defaultCom时且没有设置默认值,则取此值;注:此值对其它组件不补充 19 | // help: { name: esHelp, props: {} }, 20 | trimDoms: ["input", "textarea", "el-input"], 21 | // 经测试:在el-input里,change比change.native先触发 22 | hasConsole: false, // 是否有控制台,默认为false 23 | trimEvent: 24 | "change.native" /* 这个是form的表单项改变时,此事件触发后会去掉左右两边空格;一般都是change, 因为有些类库可以喜欢写changed */ 25 | }; 26 | 27 | export default global; 28 | -------------------------------------------------------------------------------- /src/package/libs/schema-rules.js: -------------------------------------------------------------------------------- 1 | import utils from "./utils"; 2 | import parse from "./parse"; 3 | 4 | let schemaRules = { 5 | isFunc: function(value) { 6 | return utils.isFunc(value); 7 | }, 8 | 9 | isEs: function(value) { 10 | return parse.isEsScript(value); 11 | }, 12 | 13 | isStr: function(value) { 14 | return utils.isStr(value); 15 | }, 16 | 17 | /** 18 | * [数值范围] 19 | * @param {[String]} value [description] 20 | * @return {Boolean} [description] 21 | */ 22 | // range: function(value, min, max, isInt) { 23 | // if (utils.isNum(value)) { 24 | // if (isInt) { 25 | // return schemaRules.isInt(value, min, max); 26 | // } else { 27 | // var tmpMin = min; 28 | // var tmpMax = max; 29 | // if (utils.isNum(min) && utils.isNum(min) && min > max) { 30 | // tmpMin = max; 31 | // tmpMax = min; 32 | // } 33 | 34 | // if (utils.isNum(tmpMin) && value < tmpMin) { 35 | // return false; 36 | // } else if (utils.isNum(tmpMax) && value > tmpMax) { 37 | // return false; 38 | // } else { 39 | // return true; 40 | // } 41 | // } 42 | // } else { 43 | // return false; 44 | // } 45 | // }, 46 | 47 | /** 48 | * [isInt 是否整数] 49 | * @param {[Number or String]} value [description] 50 | * @param {[Number]} min [最小值(包含此值),因为这个很常用, 不写就不比较] 51 | * @return {Boolean} [description] 52 | */ 53 | isInt: function(value, min, max) { 54 | var tmpMin = min; 55 | var tmpMax = max; 56 | if (utils.isNum(min) && utils.isNum(min) && min > max) { 57 | tmpMin = max; 58 | tmpMax = min; 59 | } 60 | if (utils.isNum(value) && /^\d+$/.test(value + "")) { 61 | if (utils.isNum(tmpMin) && value < tmpMin) { 62 | return false; 63 | } else if (utils.isNum(tmpMax) && value > tmpMax) { 64 | return false; 65 | } else { 66 | return true; 67 | } 68 | } else { 69 | return false; 70 | } 71 | } 72 | }; 73 | 74 | export default schemaRules; 75 | -------------------------------------------------------------------------------- /src/package/libs/submit.js: -------------------------------------------------------------------------------- 1 | /* 应用于表单组件函数,不能作为其它用途:因为this代表的是表单 */ 2 | let onlySubmit = function() { 3 | this.submit(); 4 | }; 5 | 6 | /** 7 | * 应用于表单组件函数,不能作为其它用途:因为this代表的是表单, event代表的是keyup事件 8 | * 参数就是组件事件所对就的参数 9 | * @param {*} value 当前组件的值 10 | * @param {*} key 组件的源key 11 | * @param {*} event 事件所携带的事件 12 | */ 13 | let enterSubmit = function(options) { 14 | // console.log("value: ", value); 15 | // console.log("key: ", key); 16 | if (options.event && options.event.keyCode === 13) { 17 | this.submit(); 18 | } 19 | }; 20 | 21 | export { enterSubmit, onlySubmit }; 22 | -------------------------------------------------------------------------------- /src/package/libs/tabs-observer.js: -------------------------------------------------------------------------------- 1 | /** 2 | * tabs-observer.js 3 | * 4 | * Copyright (c) 2019 chengaohe All rights reserved. 5 | * 6 | * 用于监听tabs的变化: 这样做的目标是使所有的tabs都来自同上个监听 7 | * 8 | */ 9 | 10 | import utils from "./utils"; 11 | 12 | let tabsObserver = { 13 | __INTERVAL: 50, 14 | __listeners: [], 15 | __observer: null, 16 | __resizeWinHandler: null, 17 | __throttleTimer: null, 18 | 19 | /** 20 | * 移除某个监听 21 | * @param {*} callback 22 | */ 23 | add(callback) { 24 | if (utils.isFunc(callback)) { 25 | if (this.__listeners.length <= 0) { 26 | this.__listeners.push(callback); 27 | this.__start(); 28 | } else if (!this.__listeners.includes(callback)) { 29 | this.__listeners.push(callback); 30 | } 31 | } else { 32 | console.warn("tabsObserver.add(callback)中callback不是一个函数"); 33 | } 34 | }, 35 | 36 | /** 37 | * 移除某个监听 38 | * @param {*} callback 39 | */ 40 | remove(callback) { 41 | var index = this.__listeners.indexOf(callback); 42 | if (index >= 0) { 43 | this.__listeners.splice(index, 1); 44 | if (this.__listeners.length <= 0) { 45 | this.__stop(); 46 | } 47 | } 48 | }, 49 | 50 | /** 51 | * 窗体改变时执行回调:当是resize时entries为undefined 52 | */ 53 | __observerHandler(entries) { 54 | this.__listeners.forEach(cbItem => { 55 | cbItem(entries); 56 | }); 57 | }, 58 | 59 | /** 60 | * 启动监听 61 | */ 62 | __start() { 63 | if (this.__observer) { 64 | return true; 65 | // 已经存在,不用重构 66 | } 67 | 68 | // console.log("start..."); 69 | 70 | this.__resizeWinHandler = () => { 71 | if (!this.__throttleTimer) 72 | this.__throttleTimer = setTimeout(() => { 73 | this.__throttleTimer = null; 74 | this.__observerHandler(); 75 | }, this.__INTERVAL); 76 | }; 77 | 78 | window.addEventListener("resize", this.__resizeWinHandler, true); 79 | 80 | let MutationObserver = 81 | window.MutationObserver || 82 | window.WebKitMutationObserver || 83 | window.MozMutationObserver; 84 | 85 | this.__observer = new MutationObserver(entries => { 86 | this.__observerHandler(entries); 87 | }); 88 | 89 | this.__observer.observe(document.body, { 90 | childList: true, 91 | subtree: true, 92 | attributes: true, 93 | characterData: true 94 | }); 95 | }, 96 | 97 | /** 98 | * 停止监听 99 | */ 100 | __stop() { 101 | // console.log("stop..."); 102 | if (this.__observer) { 103 | this.__observer.disconnect(); 104 | this.__observer.takeRecords(); 105 | this.__observer = null; 106 | } 107 | 108 | if (this.__resizeWinHandler) { 109 | window.removeEventListener("resize", this.__resizeWinHandler, true); 110 | this.__resizeWinHandler = null; 111 | } 112 | 113 | if (this.__throttleTimer) { 114 | clearTimeout(this.__throttleTimer); 115 | this.__throttleTimer = null; 116 | } 117 | } 118 | }; 119 | 120 | export default tabsObserver; 121 | -------------------------------------------------------------------------------- /src/package/mixins/array-del-pop-mixin.js: -------------------------------------------------------------------------------- 1 | // import utils from "../libs/utils"; 2 | import popUtils from "../libs/pop-utils"; 3 | export default { 4 | data() { 5 | return { 6 | showPop: false, 7 | popDom: null, 8 | clickDocHandler: null, 9 | scrollWinHandler: null, 10 | placement: "bottom", 11 | 12 | popPosition: { 13 | left: 0, 14 | top: 0, 15 | zindex: 1 16 | }, 17 | 18 | popArrow: { 19 | direction: "", 20 | left: 0, 21 | top: 0 22 | }, 23 | 24 | popInfo: { 25 | popBorderRadius: 4, 26 | viewSpace: 2, // 距离判断边预留的空间 27 | popArrowWH: 6, // 箭头的宽度和高度 28 | betweenSpace: 3 // pop与icon的空间 29 | } 30 | }; 31 | }, 32 | 33 | props: { 34 | hasDelete: { 35 | type: Boolean, 36 | required: false, 37 | default: false 38 | }, 39 | hasDelWarn: { 40 | type: Boolean, 41 | required: false, 42 | default: true 43 | }, 44 | index: { 45 | type: [Number, String], 46 | required: false, 47 | default: -1 // 设置为-1;低于索引值 48 | }, 49 | delMsg: { 50 | type: Object, 51 | required: false, 52 | default: () => { 53 | return {}; 54 | } 55 | }, 56 | delWarnBtns: { 57 | type: Array, 58 | required: false, 59 | default: () => { 60 | return []; 61 | } 62 | }, 63 | info: { 64 | type: Object, 65 | required: true, 66 | default: () => { 67 | return {}; 68 | } 69 | } 70 | }, 71 | 72 | computed: { 73 | canPop() { 74 | return this.hasDelWarn; 75 | } 76 | }, 77 | 78 | created() { 79 | if (this.canPop) { 80 | this.$data.clickDocHandler = event => { 81 | if (!this.isFromDelBtn(event.target)) { 82 | this.$data.showPop = false; 83 | this.cancelDocListen(); 84 | } else { 85 | //... 86 | } 87 | }; 88 | 89 | this.$data.scrollWinHandler = () => { 90 | this.adjustPop(); 91 | }; 92 | } 93 | }, 94 | 95 | methods: { 96 | showPopHandler() { 97 | // 有警告 98 | this.$data.showPop = !this.$data.showPop; 99 | if (this.$data.showPop) { 100 | this.addPopDom(); 101 | this.setDocListen(); 102 | this.$data.popPosition.zindex = popUtils.getMaxZIndex() + 1; 103 | // console.log("this.$data.popPosition.zindex = ", this.$data.popPosition.zindex); 104 | this.$nextTick(() => { 105 | this.adjustPop(); 106 | }); 107 | } else { 108 | this.cancelDocListen(); 109 | } 110 | }, 111 | 112 | clickDeletBtn() { 113 | if ( 114 | this.canPop && 115 | this.hasDelWarn && 116 | (!this.delMsg.hidden && (this.delMsg.name || this.delMsg.text)) 117 | ) { 118 | this.showPopHandler(); 119 | } else { 120 | // 没有警告 121 | this.$emit("delItem", this.index); 122 | } 123 | }, 124 | 125 | clickPopConfirm() { 126 | this.$data.showPop = false; 127 | this.$emit("delItem", this.index); 128 | this.cancelDocListen(); 129 | }, 130 | 131 | closePop() { 132 | this.$data.showPop = false; 133 | this.cancelDocListen(); 134 | }, 135 | 136 | setDocListen() { 137 | window.addEventListener("scroll", this.$data.scrollWinHandler, true); 138 | document.addEventListener("click", this.$data.clickDocHandler); 139 | }, 140 | 141 | cancelDocListen() { 142 | document.removeEventListener("click", this.$data.clickDocHandler); 143 | window.removeEventListener("scroll", this.$data.scrollWinHandler, true); 144 | }, 145 | 146 | isFromDelBtn(target) { 147 | var myDelBtn = this.$refs["delBtn"]; 148 | if (!this.$refs["delBtn"]) { 149 | return false; 150 | } 151 | myDelBtn = myDelBtn.$el ? myDelBtn.$el : myDelBtn; 152 | while (target) { 153 | if (target == myDelBtn) { 154 | return true; 155 | } 156 | target = target.parentNode; 157 | } 158 | return false; 159 | }, 160 | 161 | adjustPop() { 162 | var pop = this.$refs["pop"]; 163 | var icon = this.$refs["delBtn"].$el 164 | ? this.$refs["delBtn"].$el 165 | : this.$refs["delBtn"]; 166 | var popRect = pop.getBoundingClientRect(); 167 | var iconRect = icon.getBoundingClientRect(); 168 | 169 | var uiInfo = popUtils.getPopUiInfo( 170 | popRect, 171 | iconRect, 172 | this.$data.popInfo, 173 | this.$data.placement 174 | ); 175 | if (uiInfo) { 176 | this.$data.popPosition = { left: uiInfo.popLeft, top: uiInfo.popTop }; 177 | this.$data.popArrow = { 178 | direction: uiInfo.arrowDirection, 179 | left: uiInfo.arrowLeft, 180 | top: uiInfo.arrowTop 181 | }; 182 | } else { 183 | this.closePop(); 184 | } 185 | }, 186 | 187 | addPopDom() { 188 | if (!this.$data.popDom) { 189 | this.$data.popDom = this.$refs.pop; 190 | document.body.appendChild(this.$data.popDom); 191 | } 192 | }, 193 | 194 | removePopDom() { 195 | if (this.$data.popDom) { 196 | document.body.removeChild(this.$data.popDom); 197 | this.$data.popDom = null; 198 | } 199 | } 200 | }, 201 | 202 | destroyed() { 203 | if (this.$data.scrollWinHandler || this.$data.clickDocHandler) { 204 | this.$data.showPop = false; 205 | this.cancelDocListen(); 206 | 207 | this.$data.clickDocHandler = null; 208 | this.$data.scrollWinHandler = null; 209 | } 210 | this.removePopDom(); 211 | } 212 | }; 213 | -------------------------------------------------------------------------------- /src/package/mixins/array-edit-item-mixin.js: -------------------------------------------------------------------------------- 1 | export default { 2 | data() { 3 | return {}; 4 | }, 5 | 6 | props: { 7 | canDelete: { 8 | type: Boolean, 9 | required: false, 10 | default: true 11 | }, 12 | hasSort: { 13 | type: Boolean, 14 | required: false, 15 | default: false 16 | }, 17 | isFirst: { 18 | type: Boolean, 19 | required: false, 20 | default: false 21 | }, 22 | isLast: { 23 | type: Boolean, 24 | required: false, 25 | default: false 26 | }, 27 | fixed: { 28 | // 固定行(前几) 29 | type: Number, 30 | required: false, 31 | default: 0 32 | } 33 | }, 34 | 35 | methods: { 36 | upItem() { 37 | if (!this.isFirst) { 38 | this.$emit("upItem", this.index); 39 | } 40 | }, 41 | 42 | downItem() { 43 | if (!this.isLast) { 44 | this.$emit("downItem", this.index); 45 | } 46 | } 47 | } 48 | }; 49 | -------------------------------------------------------------------------------- /src/package/mixins/item-mixin.js: -------------------------------------------------------------------------------- 1 | export default { 2 | props: { 3 | schema: { 4 | type: Object, 5 | default() { 6 | return {}; 7 | } 8 | } 9 | } 10 | }; 11 | -------------------------------------------------------------------------------- /src/package/static/css/index.scss: -------------------------------------------------------------------------------- 1 | 2 | @import "mixins.scss"; 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/package/static/css/mixins.scss: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | @mixin display-flex { 5 | display: -webkit-box; 6 | display: -ms-flexbox; 7 | display: flex; 8 | } 9 | 10 | @mixin inline-flex { 11 | display: -webkit-inline-box; 12 | display: -ms-inline-flexbox; 13 | display: inline-flex; 14 | } 15 | 16 | @mixin direction-h { 17 | -webkit-box-orient: horizontal; 18 | -webkit-box-direction: normal; 19 | -ms-flex-direction: row; 20 | flex-direction: row; 21 | } 22 | 23 | @mixin direction-v { 24 | -webkit-box-orient: vertical; 25 | -webkit-box-direction: normal; 26 | -ms-flex-direction: column; 27 | flex-direction: column; 28 | } 29 | 30 | @mixin display-center { 31 | @include display-flex; 32 | align-items: center; 33 | justify-content: center; 34 | } 35 | 36 | @mixin inline-center { 37 | @include inline-flex; 38 | align-items: center; 39 | justify-content: center; 40 | } 41 | 42 | 43 | @mixin flex-fixed { 44 | flex-grow: 0; 45 | flex-shrink: 0; 46 | flex-basis: auto; 47 | } 48 | 49 | @mixin flex-full { 50 | -webkit-box-flex: 1; 51 | -ms-flex: 1; 52 | flex: 1; 53 | -ms-flex-preferred-size: auto; 54 | flex-basis: auto; 55 | } 56 | 57 | @mixin border-box { 58 | -wekit-box-sizing: border-box; 59 | box-sizing: border-box; 60 | } 61 | 62 | @mixin clear { 63 | &:before, 64 | &:after{ content: '.'; display: block; overflow: hidden; visibility: hidden; font-size: 0; line-height: 0; width: 0; height: 0;} 65 | &:after{ clear: both; } 66 | } 67 | 68 | @mixin triangle($borderColor, $borderWidth, $direction, $test: 1) { 69 | margin: 0; 70 | padding: 0; 71 | width: 0; 72 | height: 0; 73 | font-size: 0; 74 | border-style: solid; 75 | @if $direction == "top" { 76 | border-width: 0 $borderWidth $borderWidth $borderWidth; 77 | border-color: transparent transparent $borderColor transparent; 78 | } @else if $direction == "right" { 79 | border-width: $borderWidth 0 $borderWidth $borderWidth; 80 | border-color: transparent transparent transparent $borderColor; 81 | } @else if $direction == "bottom" { 82 | border-width: $borderWidth $borderWidth 0 $borderWidth; 83 | border-color: $borderColor transparent transparent transparent; 84 | } @else { /* left */ 85 | border-width: $borderWidth $borderWidth $borderWidth 0; 86 | border-color: transparent $borderColor transparent transparent; 87 | } 88 | } 89 | 90 | @mixin arrow($borderColor, $borderWidth, $direction, $bgColor) { 91 | @include triangle($borderColor, $borderWidth, $direction); 92 | position: relative; 93 | &:before { 94 | content: ""; 95 | display: block; 96 | @include triangle($bgColor, $borderWidth, $direction); 97 | margin: 0; 98 | // padding: 0; 99 | position: absolute; 100 | @if $direction == "top" { 101 | top: 1px; 102 | left: -$borderWidth; 103 | } @else if $direction == "right" { 104 | top: -$borderWidth; 105 | left: -$borderWidth - 1; 106 | } @else if $direction == "bottom" { 107 | top: -$borderWidth - 1; 108 | left: -$borderWidth; 109 | } @else { /* left */ 110 | top: -$borderWidth; 111 | left: 1px; 112 | } 113 | } 114 | } 115 | 116 | @mixin border-top-radius($radius) { 117 | border-top-right-radius: $radius; 118 | border-top-left-radius: $radius; 119 | } 120 | @mixin border-right-radius($radius) { 121 | border-bottom-right-radius: $radius; 122 | border-top-right-radius: $radius; 123 | } 124 | @mixin border-bottom-radius($radius) { 125 | border-bottom-right-radius: $radius; 126 | border-bottom-left-radius: $radius; 127 | } 128 | @mixin border-left-radius($radius) { 129 | border-bottom-left-radius: $radius; 130 | border-top-left-radius: $radius; 131 | } 132 | 133 | $g_borderColor: #dcdfe6; 134 | $g_bgColor: #f5f7fa; 135 | $g_bgColor2: #d6d7da; 136 | $g_borderRadius: 4px; 137 | $g_errorColor: #f64a4a; 138 | -------------------------------------------------------------------------------- /src/router/index.js: -------------------------------------------------------------------------------- 1 | import Vue from "vue"; 2 | import Router from "vue-router"; 3 | 4 | import navRoute from "./nav-route"; 5 | 6 | // 路由配置从方法调用,变成了对象传递 7 | const routes = navRoute.getPages(); 8 | 9 | Vue.use(Router); 10 | 11 | export default new Router({ 12 | routes 13 | }); 14 | -------------------------------------------------------------------------------- /src/static/css/index.scss: -------------------------------------------------------------------------------- 1 | 2 | @import "mixins.scss"; 3 | 4 | 5 | button, input, select, textarea { 6 | font-family: inherit; 7 | font-size: inherit; 8 | line-height: inherit; 9 | color: inherit; 10 | } 11 | 12 | ol, li { 13 | margin: 0; 14 | padding: 0; 15 | list-style: none; 16 | } 17 | 18 | html, body { 19 | /*font-family: "Microsoft YaHei", "Helvetica Neue",Helvetica,"PingFang SC","Hiragino Sans GB","\5FAE\8F6F\96C5\9ED1",Arial,sans-serif;*/ 20 | /*font-family: Helvetica Neue,Helvetica,PingFang SC,Hiragino Sans GB,Microsoft YaHei,SimSun,sans-serif;*/ 21 | font-family: "Helvetica Neue",Helvetica,"PingFang SC","Hiragino Sans GB","Microsoft YaHei","微软雅黑",Arial,sans-serif; 22 | margin: 0; 23 | padding: 0; 24 | height: 100%; 25 | // overflow: hidden; 26 | background: #fff; 27 | min-width: 900px; 28 | color: #333; 29 | } 30 | 31 | .g-full-height { 32 | height: 100%; 33 | } 34 | 35 | .g-center { 36 | @include display-center; 37 | } 38 | 39 | .g-custom-loading .el-loading-spinner { 40 | width: 100px; 41 | height: 100px; 42 | margin-top: -40px; 43 | margin-left: -40px; 44 | top: 50%; 45 | left: 50%; 46 | border-radius: 4px; 47 | background: rgba(0, 0, 0, 0.3); 48 | @include display-center; 49 | @include direction-v; 50 | 51 | } 52 | 53 | .g-hidden-box { 54 | width: 0; 55 | height: 0; 56 | overflow: hidden; 57 | } 58 | 59 | .g-header { 60 | margin: 0; 61 | padding: 0; 62 | display: block; 63 | /*height: 60px;*/ 64 | background-color: rgb(64, 158, 255); 65 | color: #fff; 66 | overflow: hidden; 67 | position: relative; 68 | } 69 | 70 | .g-title { 71 | margin: 0; 72 | padding: 0 0 0 10px; 73 | line-height: 60px; 74 | text-align: left; 75 | font-size: 25px; 76 | } 77 | 78 | 79 | .g-nav-aside { 80 | margin: 0; 81 | padding: 0; 82 | display: block; 83 | background-color: #fff; 84 | text-align: left; 85 | /*width: $g_navAsideWidth;*/ 86 | @include border-box; 87 | border-right: 1px solid #e6e6e6; 88 | } 89 | 90 | .g-full-main { 91 | margin: 0; 92 | padding: 0; 93 | } 94 | 95 | .g-main-container { 96 | height: 100%; 97 | overflow: hidden; 98 | } 99 | 100 | 101 | .g-main-box { 102 | margin: 0; 103 | padding: 0; 104 | height: 100%; 105 | overflow: auto; 106 | text-align: left; 107 | } 108 | 109 | .g-form-box { 110 | padding-top: 30px; 111 | max-width: 700px; 112 | margin-right: auto; 113 | } 114 | 115 | .g-main-box .g-body-box { 116 | margin: 0; 117 | padding: 0px $g_bodyPaddingLeft 0px $g_bodyPaddingLeft; 118 | } 119 | 120 | .g-single { 121 | white-space: nowrap; 122 | line-height: 20px; 123 | } 124 | 125 | .g-interval-row { 126 | background-color: #fcfcfc; 127 | 128 | &:hover { 129 | background-color: #eff2f7; 130 | } 131 | } 132 | 133 | .g-footer-page-wrap { 134 | padding-top: 10px; 135 | } 136 | 137 | .g-footer-btn-wrap { 138 | padding-top: 10px; 139 | } 140 | 141 | .g-main-table { 142 | width: 100%; 143 | height: 100%; 144 | } 145 | 146 | .g-table-header { 147 | background-color: #f5f7fa !important; 148 | } 149 | 150 | .g-table-header th { 151 | padding-top: 8px !important; 152 | padding-bottom: 8px !important; 153 | background: #f5f7fa; 154 | } 155 | 156 | .g-table-cell { 157 | padding-top: 5px !important; 158 | padding-bottom: 5px !important; 159 | } 160 | 161 | .g-edit-dialog .el-dialog { 162 | max-width: 800px; 163 | min-width: 600px; 164 | } 165 | 166 | -------------------------------------------------------------------------------- /src/static/css/mixins.scss: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | @mixin display-flex { 5 | display: -webkit-box; 6 | display: -ms-flexbox; 7 | display: flex; 8 | } 9 | 10 | @mixin direction-h { 11 | -webkit-box-orient: horizontal; 12 | -webkit-box-direction: normal; 13 | -ms-flex-direction: row; 14 | flex-direction: row; 15 | } 16 | 17 | @mixin direction-v { 18 | -webkit-box-orient: vertical; 19 | -webkit-box-direction: normal; 20 | -ms-flex-direction: column; 21 | flex-direction: column; 22 | } 23 | 24 | @mixin display-center { 25 | @include display-flex; 26 | align-items:center; 27 | justify-content: center; 28 | } 29 | 30 | @mixin flex-fixed { 31 | flex-grow: 0; 32 | flex-shrink: 0; 33 | flex-basis: auto; 34 | } 35 | 36 | @mixin flex-full { 37 | -webkit-box-flex: 1; 38 | -ms-flex: 1; 39 | flex: 1; 40 | -ms-flex-preferred-size: auto; 41 | flex-basis: auto; 42 | } 43 | 44 | @mixin border-box { 45 | -wekit-box-sizing: border-box; 46 | box-sizing: border-box; 47 | } 48 | 49 | @mixin clear { 50 | &:before, 51 | &:after{ content: '.'; display: block; overflow: hidden; visibility: hidden; font-size: 0; line-height: 0; width: 0; height: 0;} 52 | &:after{ clear: both; } 53 | } 54 | 55 | @mixin triangle($borderColor, $borderWidth, $direction, $test: 1) { 56 | margin: 0; 57 | padding: 0; 58 | width: 0; 59 | height: 0; 60 | font-size: 0; 61 | border-style: solid; 62 | @if $direction == "top" { 63 | border-width: 0 $borderWidth $borderWidth $borderWidth; 64 | border-color: transparent transparent $borderColor transparent; 65 | } @else if $direction == "right" { 66 | border-width: $borderWidth 0 $borderWidth $borderWidth; 67 | border-color: transparent transparent transparent $borderColor; 68 | } @else if $direction == "bottom" { 69 | border-width: $borderWidth $borderWidth 0 $borderWidth; 70 | border-color: $borderColor transparent transparent transparent; 71 | } @else { /* left */ 72 | border-width: $borderWidth $borderWidth $borderWidth 0; 73 | border-color: transparent $borderColor transparent transparent; 74 | } 75 | } 76 | 77 | @mixin arrow($borderColor, $borderWidth, $direction, $bgColor) { 78 | @include triangle($borderColor, $borderWidth, $direction); 79 | position: relative; 80 | &:before { 81 | content: ""; 82 | display: block; 83 | @include triangle($bgColor, $borderWidth, $direction); 84 | margin: 0; 85 | // padding: 0; 86 | position: absolute; 87 | @if $direction == "top" { 88 | top: 1px; 89 | left: -$borderWidth; 90 | } @else if $direction == "right" { 91 | top: -$borderWidth; 92 | left: -$borderWidth - 1; 93 | } @else if $direction == "bottom" { 94 | top: -$borderWidth - 1; 95 | left: -$borderWidth; 96 | } @else { /* left */ 97 | top: -$borderWidth; 98 | left: 1px; 99 | } 100 | } 101 | } 102 | 103 | $g_bodyPaddingLeft: 20px; 104 | 105 | $g_navAsideWidth: 220px; 106 | 107 | -------------------------------------------------------------------------------- /src/views/README.md: -------------------------------------------------------------------------------- 1 | # 页面文件的命名规则和存在路径,虽不是固定,但力求统一 2 | 3 | ## 所有的模块都存放在views文件夹下,views下面的每一个文件夹代表一种模块(如news, home等),模块文件夹再分页面, modals, components 4 | 5 | ### 页面 6 | ``` 7 | list.vue rule-list.vue maker.vue rule-maker.vue 8 | ``` 9 | 10 | ### modal编辑内容(某个模块下),存放在modals文件夹 11 | ``` 12 | maker.vue rule-maker.vue(原理,同上) 13 | ``` 14 | 15 | ### 组件(某个模块下),存放在components文件夹 16 | ``` 17 | 无要求,最好根据功能自由命名 18 | ``` 19 | -------------------------------------------------------------------------------- /src/views/demo/array-card.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 87 | 88 | 89 | -------------------------------------------------------------------------------- /src/views/demo/array-legend.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 147 | 148 | 149 | -------------------------------------------------------------------------------- /src/views/demo/array-row.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 134 | 135 | 136 | -------------------------------------------------------------------------------- /src/views/demo/array-table.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 149 | 150 | 151 | -------------------------------------------------------------------------------- /src/views/demo/array-tabs.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 147 | 148 | 149 | -------------------------------------------------------------------------------- /src/views/demo/component.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 101 | 102 | 115 | -------------------------------------------------------------------------------- /src/views/demo/desc.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 74 | 75 | 76 | -------------------------------------------------------------------------------- /src/views/demo/help.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 111 | 112 | 113 | -------------------------------------------------------------------------------- /src/views/demo/properties.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 135 | 136 | 137 | -------------------------------------------------------------------------------- /src/views/demo/scopedSlots.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 141 | 142 | 155 | -------------------------------------------------------------------------------- /src/views/demo/simple.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /src/views/demo/standard.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /src/views/demo/tabs.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 182 | 183 | 184 | -------------------------------------------------------------------------------- /src/views/demo/title.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 103 | 104 | 105 | -------------------------------------------------------------------------------- /src/views/demo/unit.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 93 | 94 | 102 | -------------------------------------------------------------------------------- /src/views/home/components/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chengaohe45/vue-easy-form/8cee81eea170316a51ae0d77e3dbb84d6f3076f1/src/views/home/components/README.md -------------------------------------------------------------------------------- /src/views/home/components/native.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 40 | -------------------------------------------------------------------------------- /src/views/notfound/components/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chengaohe45/vue-easy-form/8cee81eea170316a51ae0d77e3dbb84d6f3076f1/src/views/notfound/components/README.md -------------------------------------------------------------------------------- /src/views/notfound/index.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /tests/unit/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | mocha: true 4 | }, 5 | rules: { 6 | "import/no-extraneous-dependencies": "off" 7 | } 8 | }; 9 | -------------------------------------------------------------------------------- /tests/unit/libs/utils.spec.js: -------------------------------------------------------------------------------- 1 | import { expect } from "chai"; 2 | import Vue from "vue"; 3 | 4 | import utils from "@/package/libs/utils.js"; 5 | 6 | // 初始化工具组件,传这个Vue:用注册时的Vue,这样虚拟节点才能判断正确 7 | utils.initVue(Vue); 8 | 9 | describe("utils.js", () => { 10 | it("base type check", () => { 11 | expect(utils.isBool(true)).to.equal(true); 12 | expect(utils.isBool(null)).to.equal(false); 13 | 14 | expect(utils.isNum(1.1)).to.equal(true); 15 | expect(utils.isNum("1.1")).to.equal(false); 16 | 17 | expect(utils.isStr("true")).to.equal(true); 18 | expect(utils.isStr(true)).to.equal(false); 19 | 20 | expect(utils.isFunc(() => {})).to.equal(true); 21 | expect(utils.isArr(true)).to.equal(false); 22 | 23 | expect(utils.isDate(new Date())).to.equal(true); 24 | expect(utils.isDate(123)).to.equal(false); 25 | 26 | expect(utils.isObj({})).to.equal(true); 27 | expect(utils.isObj("{}")).to.equal(false); 28 | }); 29 | 30 | it("deepCopy", () => { 31 | var obj = { 32 | a: 1, 33 | b: { 34 | b1: 21, 35 | b2: 23 36 | } 37 | }; 38 | obj.c = obj.b; // 同一个地址 39 | 40 | obj.d = { d1: obj.b, d2: 42 }; // 进入循环 41 | obj.b.b1 = obj.d; 42 | expect(utils.deepCopy(obj)).to.have.property("c"); 43 | var tmpObj = utils.deepCopy(obj); 44 | expect(tmpObj.b).to.equal(tmpObj.c); 45 | expect(tmpObj.b).to.equal(tmpObj.d.d1); 46 | }); 47 | }); 48 | -------------------------------------------------------------------------------- /vue.config.js: -------------------------------------------------------------------------------- 1 | // const apiMocker = require("webpack-api-mocker"); 2 | const path = require("path"); 3 | // const mocker = path.resolve(__dirname, "./mock/data.js"); 4 | 5 | // vue.config.js 配置说明 (这个file是peter我加的) 6 | // 这里只列一部分,具体配置惨考文档啊 7 | 8 | process.env.VUE_APP_VERSION = require("./package.json").version; 9 | 10 | const prodEnv = "production"; 11 | 12 | let isRelease = false; 13 | if (process.env.NODE_ENV === prodEnv) { 14 | let argvs = process.argv; 15 | if (argvs && argvs.length > 0) { 16 | let paramIndex = argvs.indexOf("--target"); 17 | let paramValIndex = argvs.indexOf("lib"); 18 | console.log(paramIndex, paramValIndex); 19 | if (paramIndex + 1 == paramValIndex) { 20 | isRelease = true; 21 | } 22 | } 23 | } 24 | 25 | module.exports = { 26 | // baseUrl type:{string} default:'/' 27 | // 将部署应用程序的基本URL 28 | // 将部署应用程序的基本URL 29 | // 默认情况下,Vue CLI假设您的应用程序将部署在域的根目录下。 30 | // https://www.my-app.com/。如果应用程序部署在子路径上,则需要使用此选项指定子路径。例如,如果您的应用程序部署在https://www.foobar.com/my-app/,集baseUrl到'/my-app/'. 31 | 32 | // baseUrl: process.env.NODE_ENV === 'production' ? '/online/' : '/test/', 33 | 34 | // baseUrl: "", 35 | publicPath: "", 36 | 37 | // outputDir: 在npm run build时 生成文件的目录 type:string, default:'dist' 38 | 39 | // outputDir: 'dist', 40 | 41 | outputDir: isRelease ? "dist" : "../vue-easy-form-docs/demo", 42 | 43 | // pages:{ type:Object,Default:undfind } 44 | /* 45 | 构建多页面模式的应用程序.每个“页面”都应该有一个相应的JavaScript条目文件。该值应该是一 46 | 个对象,其中键是条目的名称,而该值要么是指定其条目、模板和文件名的对象,要么是指定其条目 47 | 的字符串, 48 | 注意:请保证pages里配置的路径和文件名 在你的文档目录都存在 否则启动服务会报错的 49 | */ 50 | 51 | configureWebpack: config => { 52 | if (isRelease) { 53 | if (!config.output) { 54 | config.output = {}; 55 | } 56 | config.output.library = "esForm"; 57 | config.output.libraryExport = "default"; 58 | config.externals = { 59 | ...config.externals, 60 | vue: { 61 | root: "Vue", 62 | commonjs2: "vue", 63 | commonjs: "vue", 64 | amd: "vue" 65 | }, 66 | "element-ui": { 67 | root: "ELEMENT", 68 | commonjs2: "ELEMENT", 69 | commonjs: "ELEMENT", 70 | amd: "ELEMENT" 71 | }, 72 | axios: { 73 | root: "axios", 74 | commonjs2: "axios", 75 | commonjs: "axios", 76 | amd: "axios" 77 | } 78 | }; 79 | } 80 | 81 | var otherAlias = {}; 82 | otherAlias["vue-easy-form"] = path.resolve( 83 | __dirname, 84 | "./src/package/index.js" 85 | ); 86 | if (!config.resolve.alias) { 87 | config.resolve.alias = {}; 88 | } 89 | Object.assign(config.resolve.alias, otherAlias); 90 | }, 91 | css: { 92 | extract: false // 是否内联css 93 | }, 94 | // lintOnSave:{ type:Boolean default:true } 问你是否使用eslint 95 | lintOnSave: process.env.NODE_ENV !== prodEnv ? true : false, // eslint-loader 是否在保存的时候检查 96 | // productionSourceMap:{ type:Bollean,default:true } 生产源映射 97 | // 如果您不需要生产时的源映射,那么将此设置为false可以加速生产构建 98 | productionSourceMap: false, 99 | // devServer:{type:Object} 3个属性host,port,https 100 | // 它支持webPack-dev-server的所有选项 101 | 102 | devServer: { 103 | port: 8086, // 端口号 104 | // // host: 'localhost', //不要写这个东西,要写就写127.0.0.1 105 | // // https: false, // https:{type:Boolean} 106 | open: true //配置自动启动浏览器 107 | // before: (app) => { 108 | // apiMocker(app, mocker, { 109 | // proxy: { 110 | // // '/api/*': 'http://domain.com.com' //当没有mock数据,只符合此规则,则转换 111 | // }, 112 | // changeHost: true 113 | // }); 114 | // }, 115 | // proxy: 'http://localhost:4000' // 配置跨域处理,只有一个代理 116 | // proxy: { 117 | // '/api': { 118 | // target: '/mock/data.js', 119 | // ws: true, 120 | // changeOrigin: true 121 | // }, 122 | // '/foo': { 123 | // target: '' 124 | // } 125 | // }, // 配置多个代理 126 | } 127 | }; 128 | --------------------------------------------------------------------------------