├── src ├── assets │ ├── theme │ │ ├── theme-default │ │ │ ├── index.lazy.less │ │ │ └── index.lazy.scss │ │ └── theme-red │ │ │ ├── index.lazy.less │ │ │ └── index.lazy.scss │ ├── logo.png │ ├── scss │ │ ├── variables.scss │ │ ├── basic.scss │ │ ├── _form-design-pc.scss │ │ ├── task-list.scss │ │ ├── _system.scss │ │ └── _task-form.scss │ └── less │ │ ├── variables.less │ │ └── basic.less ├── lang │ ├── README.md │ ├── zh-CN.ts │ └── en.ts ├── lib │ ├── README.md │ ├── haku-css │ │ ├── img │ │ │ └── nodata.png │ │ └── less │ │ │ ├── theme_x.less │ │ │ ├── theme_blue.less │ │ │ ├── main.less │ │ │ └── system.less │ ├── scss-loader │ │ ├── file-size.json │ │ ├── versions.json │ │ ├── sass.node.js │ │ └── sass.js │ ├── monaco-language │ │ └── sql │ │ │ ├── sql.suggestions.js │ │ │ └── sql.contribution.js │ ├── less-loader │ │ └── index.js │ ├── lifecycle │ │ ├── lifecycle.native.mjs │ │ ├── lifecycle.mjs │ │ └── lifecycle.es5.js │ └── haku-debug │ │ └── index.js ├── @types │ ├── form-design │ │ ├── index.d.ts │ │ ├── location.d.ts │ │ ├── form-theme.d.ts │ │ ├── property-group.d.ts │ │ ├── form-component-library.d.ts │ │ ├── form-panel.d.ts │ │ ├── form-footer-config.d.ts │ │ ├── form-control-group.d.ts │ │ ├── api.d.ts │ │ ├── remote-device.d.ts │ │ ├── form-function.d.ts │ │ ├── property-editor.d.ts │ │ ├── form-script.d.ts │ │ ├── form-variable.d.ts │ │ ├── service-config.d.ts │ │ ├── basic-control.d.ts │ │ ├── drag-config.d.ts │ │ ├── form-template.d.ts │ │ ├── form-config.d.ts │ │ ├── form-control-property.d.ts │ │ ├── form-control.d.ts │ │ └── main.d.ts │ ├── shims-vue.d.ts │ ├── antd-vue.d.ts │ ├── model.d.ts │ ├── shims-tsx.d.ts │ ├── basic.d.ts │ └── vue.d.ts ├── iconEditor_antd.ts ├── tools │ ├── README.md │ ├── registerComponentHooks.ts │ ├── filters.ts │ ├── decorator.ts │ ├── registerGlobalComponents.ts │ ├── directives.js │ └── createChart.js ├── formLibrarys.ts ├── store │ └── index.ts ├── components │ ├── taskform │ │ ├── BlankControl.vue │ │ └── TaskDetailTable.vue │ ├── control_vant │ │ ├── VantFormDesignControlCellGroup.vue │ │ ├── VantFormDesignControlGrid.vue │ │ ├── VantFormDesignControlRadioGroup.vue │ │ ├── VantFormDesignControlCheckboxGroup.vue │ │ ├── VantFormDesignControlRow.vue │ │ ├── VantFormDesignControlCollapse.vue │ │ ├── VantFormDesignControlTabs.vue │ │ └── VantFormDesignControl.vue │ ├── control_antd │ │ ├── AntdFormDesignStaticChildControl.vue │ │ ├── AntdFormDesignControlCard.vue │ │ ├── AntdFormDesignControlTabs.vue │ │ ├── AntdFormDesignControlCollapse.vue │ │ ├── AntdFormDesignControlFlex.vue │ │ ├── AntdFormDesignControlRow.vue │ │ ├── AntdFormDesignControlDatePicker.vue │ │ ├── AntdFormDesignChildform.vue │ │ ├── AntdFormDesignControlCustom.vue │ │ ├── AntdFormDesignControlCheckboxGroup.vue │ │ ├── AntdFormDesignControlSelect.vue │ │ ├── AntdFormDesignControlRadioGroup.vue │ │ ├── AntdFormDesignControlUpload.vue │ │ ├── AntdFormDesignComplexChildform.vue │ │ └── AntdFormDesignControl.vue │ ├── editor │ │ ├── simple-editor │ │ │ └── SimpleEditor.vue │ │ ├── function-picker │ │ │ └── FunctionPicker.vue │ │ ├── color-picker │ │ │ └── ColorPickerSlider.vue │ │ ├── api-editor │ │ │ └── ApiEditor.vue │ │ ├── box-editor │ │ │ └── BoxEditor.vue │ │ └── variable-picker │ │ │ └── VariablePicker.vue │ ├── FormDesignBlankControl.vue │ └── control_uni │ │ └── UniFormDesignControl.vue ├── views │ └── BasicFormDesign.vue ├── App.vue ├── router │ └── index.ts ├── formControlGroups.ts ├── config │ ├── theme.ts │ ├── router.ts │ ├── components.ts │ ├── service.ts │ ├── store.ts │ └── enum.ts ├── formControls_uni.ts └── formDevices.ts ├── public ├── favicon.png ├── printscreen.png ├── haku-formdesign.png ├── img │ ├── avatar │ │ └── geji.png │ ├── devices │ │ ├── iphonex.png │ │ ├── iphone678.png │ │ └── iphonex_backup.png │ └── icons │ │ ├── favicon-16x16.png │ │ ├── favicon-32x32.png │ │ ├── favicon-32x32_0.png │ │ ├── mstile-150x150.png │ │ ├── apple-touch-icon.png │ │ ├── android-chrome-192x192.png │ │ ├── android-chrome-512x512.png │ │ ├── apple-touch-icon-60x60.png │ │ ├── apple-touch-icon-76x76.png │ │ ├── apple-touch-icon-120x120.png │ │ ├── apple-touch-icon-152x152.png │ │ ├── apple-touch-icon-180x180.png │ │ └── msapplication-icon-144x144.png ├── index.html └── haku-formdesign.svg ├── tests ├── unit │ ├── .eslintrc.js │ └── example.spec.ts └── e2e │ ├── .eslintrc.js │ ├── specs │ ├── test.js │ └── test-with-pageobjects.js │ ├── custom-commands │ ├── openHomepageClass.js │ ├── openHomepage.js │ └── customExecute.js │ ├── custom-assertions │ └── elementCount.js │ ├── page-objects │ └── homepage.js │ └── globals.js ├── .editorconfig ├── .babelrc ├── .env ├── .gitignore ├── README.md ├── .prettierrc.js ├── tsconfig.json ├── LICENSE ├── vue.config.js └── package.json /src/assets/theme/theme-default/index.lazy.less: -------------------------------------------------------------------------------- 1 | @import '../../less/variables.less'; -------------------------------------------------------------------------------- /src/lang/README.md: -------------------------------------------------------------------------------- 1 | # 多语言文件夹 2 | 3 | > 存放静态多语言文本数据,目前仅有中文和英文两种语言,在编写配置文本时推荐分模块编写。 -------------------------------------------------------------------------------- /src/lib/README.md: -------------------------------------------------------------------------------- 1 | # js功能包文件夹 2 | 3 | > 加入了一些分散的、无需编译的js/css库,但在有条件的情况下还是尽可能引入npm库。 -------------------------------------------------------------------------------- /public/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hakubox/haku-form-design/HEAD/public/favicon.png -------------------------------------------------------------------------------- /src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hakubox/haku-form-design/HEAD/src/assets/logo.png -------------------------------------------------------------------------------- /tests/unit/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | mocha: true 4 | } 5 | }; 6 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 4 -------------------------------------------------------------------------------- /src/@types/form-design/index.d.ts: -------------------------------------------------------------------------------- 1 | import * as FormDesign from './main' 2 | export default FormDesign; -------------------------------------------------------------------------------- /public/printscreen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hakubox/haku-form-design/HEAD/public/printscreen.png -------------------------------------------------------------------------------- /public/haku-formdesign.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hakubox/haku-form-design/HEAD/public/haku-formdesign.png -------------------------------------------------------------------------------- /public/img/avatar/geji.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hakubox/haku-form-design/HEAD/public/img/avatar/geji.png -------------------------------------------------------------------------------- /src/@types/shims-vue.d.ts: -------------------------------------------------------------------------------- 1 | declare module "*.vue" { 2 | import Vue from "vue"; 3 | export default Vue; 4 | } -------------------------------------------------------------------------------- /src/assets/theme/theme-red/index.lazy.less: -------------------------------------------------------------------------------- 1 | @import '../../less/variables.less'; 2 | 3 | @primary-color: #ce1818; -------------------------------------------------------------------------------- /src/iconEditor_antd.ts: -------------------------------------------------------------------------------- 1 | export let icons_antd = [ 2 | { label: "", code: "F0C8", value: "success" } 3 | ]; -------------------------------------------------------------------------------- /tests/e2e/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | rules: { 3 | "no-unused-expressions": "off" 4 | } 5 | }; 6 | -------------------------------------------------------------------------------- /public/img/devices/iphonex.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hakubox/haku-form-design/HEAD/public/img/devices/iphonex.png -------------------------------------------------------------------------------- /src/lib/haku-css/img/nodata.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hakubox/haku-form-design/HEAD/src/lib/haku-css/img/nodata.png -------------------------------------------------------------------------------- /src/tools/README.md: -------------------------------------------------------------------------------- 1 | # 工具js库文件夹 2 | 3 | > 放置了部分公用js工具类,不要和lib库文件夹弄混。 4 | 5 | ### 目前的工具类: 6 | 7 | 1. common.js __公用函数库__ -------------------------------------------------------------------------------- /public/img/devices/iphone678.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hakubox/haku-form-design/HEAD/public/img/devices/iphone678.png -------------------------------------------------------------------------------- /public/img/icons/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hakubox/haku-form-design/HEAD/public/img/icons/favicon-16x16.png -------------------------------------------------------------------------------- /public/img/icons/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hakubox/haku-form-design/HEAD/public/img/icons/favicon-32x32.png -------------------------------------------------------------------------------- /public/img/icons/favicon-32x32_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hakubox/haku-form-design/HEAD/public/img/icons/favicon-32x32_0.png -------------------------------------------------------------------------------- /public/img/icons/mstile-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hakubox/haku-form-design/HEAD/public/img/icons/mstile-150x150.png -------------------------------------------------------------------------------- /src/assets/scss/variables.scss: -------------------------------------------------------------------------------- 1 | $-primary-color: #337AB7; //#1D87EA; 2 | $-error-color: #f5222d; 3 | $-bg-color: #F0F2F5; 4 | 5 | -------------------------------------------------------------------------------- /public/img/devices/iphonex_backup.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hakubox/haku-form-design/HEAD/public/img/devices/iphonex_backup.png -------------------------------------------------------------------------------- /public/img/icons/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hakubox/haku-form-design/HEAD/public/img/icons/apple-touch-icon.png -------------------------------------------------------------------------------- /public/img/icons/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hakubox/haku-form-design/HEAD/public/img/icons/android-chrome-192x192.png -------------------------------------------------------------------------------- /public/img/icons/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hakubox/haku-form-design/HEAD/public/img/icons/android-chrome-512x512.png -------------------------------------------------------------------------------- /public/img/icons/apple-touch-icon-60x60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hakubox/haku-form-design/HEAD/public/img/icons/apple-touch-icon-60x60.png -------------------------------------------------------------------------------- /public/img/icons/apple-touch-icon-76x76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hakubox/haku-form-design/HEAD/public/img/icons/apple-touch-icon-76x76.png -------------------------------------------------------------------------------- /public/img/icons/apple-touch-icon-120x120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hakubox/haku-form-design/HEAD/public/img/icons/apple-touch-icon-120x120.png -------------------------------------------------------------------------------- /public/img/icons/apple-touch-icon-152x152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hakubox/haku-form-design/HEAD/public/img/icons/apple-touch-icon-152x152.png -------------------------------------------------------------------------------- /public/img/icons/apple-touch-icon-180x180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hakubox/haku-form-design/HEAD/public/img/icons/apple-touch-icon-180x180.png -------------------------------------------------------------------------------- /src/assets/scss/basic.scss: -------------------------------------------------------------------------------- 1 | @import 'variables'; 2 | @import 'system'; 3 | @import 'form-design'; 4 | @import 'form-design-pc'; 5 | @import 'task-form'; -------------------------------------------------------------------------------- /public/img/icons/msapplication-icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hakubox/haku-form-design/HEAD/public/img/icons/msapplication-icon-144x144.png -------------------------------------------------------------------------------- /src/@types/form-design/location.d.ts: -------------------------------------------------------------------------------- 1 | /** 简单坐标类 */ 2 | export declare interface Location { 3 | /** X坐标 */ 4 | x: number; 5 | /** Y坐标 */ 6 | y: number; 7 | } -------------------------------------------------------------------------------- /src/@types/form-design/form-theme.d.ts: -------------------------------------------------------------------------------- 1 | 2 | /** 表单主题 */ 3 | export declare class FormTheme { 4 | /** 主题Code */ 5 | code: string; 6 | /** 标题 */ 7 | title: string; 8 | } -------------------------------------------------------------------------------- /src/assets/theme/theme-default/index.lazy.scss: -------------------------------------------------------------------------------- 1 | @import '../../scss/variables.scss'; 2 | @import '../../scss/system'; 3 | @import '../../scss/form-design'; 4 | @import '../../scss/form-design-pc'; -------------------------------------------------------------------------------- /src/lib/haku-css/less/theme_x.less: -------------------------------------------------------------------------------- 1 | // out: false 2 | 3 | .themeInit("x") { 4 | @page-bg: linear-gradient(135deg, #fdfcfb 0%, #e2d1c3 100%); 5 | 6 | @primary-color: rgba(0,0,0,0.3); 7 | } -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["@vue/cli-plugin-babel/preset"], 3 | "plugins": [ 4 | "@babel/plugin-proposal-nullish-coalescing-operator", 5 | "@babel/plugin-proposal-optional-chaining" 6 | ] 7 | } -------------------------------------------------------------------------------- /src/tools/registerComponentHooks.ts: -------------------------------------------------------------------------------- 1 | //class-component-hooks.js 2 | import Component from 'vue-class-component' 3 | 4 | Component.registerHooks([ 5 | 'beforeRouteEnter', 6 | 'beforeRouteLeave', 7 | 'beforeRouteUpdate' 8 | ]) -------------------------------------------------------------------------------- /src/lang/zh-CN.ts: -------------------------------------------------------------------------------- 1 | import { LocaleMessageObject } from 'vue-i18n'; 2 | 3 | const lang: LocaleMessageObject = { 4 | alert: { 5 | confirm: '确定', 6 | clear: '清空' 7 | } 8 | }; 9 | 10 | export default lang; -------------------------------------------------------------------------------- /src/lang/en.ts: -------------------------------------------------------------------------------- 1 | import { LocaleMessageObject } from 'vue-i18n'; 2 | 3 | const lang: LocaleMessageObject = { 4 | alert: { 5 | confirm: 'Confirm', 6 | clear: 'Clear' 7 | } 8 | }; 9 | 10 | export default lang; -------------------------------------------------------------------------------- /src/@types/form-design/property-group.d.ts: -------------------------------------------------------------------------------- 1 | import { FormControlProperty } from "./main"; 2 | 3 | /** 属性组 */ 4 | export declare class PropertyGroup { 5 | /** 属性名 */ 6 | title: string; 7 | /** 属性列表 */ 8 | propertys: Array; 9 | } -------------------------------------------------------------------------------- /src/assets/less/variables.less: -------------------------------------------------------------------------------- 1 | @import '~ant-design-vue/dist/antd.less'; 2 | @import '../../lib/haku-css/less/main.less'; 3 | 4 | @primary-1: #E2EFFE; 5 | @primary-color: #337AB7; 6 | @error-color: #f5222d; 7 | @table-row-hover-bg: #DCDCDC; 8 | 9 | @import 'basic.less'; -------------------------------------------------------------------------------- /src/assets/theme/theme-red/index.lazy.scss: -------------------------------------------------------------------------------- 1 | 2 | $-primary-color: #ce1818; 3 | // $-primary-color: #1D87EA; 4 | $-error-color: #f5222d; 5 | $-bg-color: #F0F2F5; 6 | 7 | @import '../../scss/system'; 8 | @import '../../scss/form-design'; 9 | @import '../../scss/form-design-pc'; -------------------------------------------------------------------------------- /src/@types/form-design/form-component-library.d.ts: -------------------------------------------------------------------------------- 1 | import { Enum } from "@/config/enum"; 2 | 3 | /** 表单组件库 */ 4 | export declare class ComponentLibrary { 5 | /** 组件库代码 */ 6 | code: string; 7 | /** 组件库名称 */ 8 | name: string; 9 | /** 组件库类型 */ 10 | type: Enum.FormType; 11 | } -------------------------------------------------------------------------------- /src/@types/form-design/form-panel.d.ts: -------------------------------------------------------------------------------- 1 | import { FormControl } from "./main"; 2 | 3 | /** 包含一个控件列表的表单区域 */ 4 | export declare class FormPanel { 5 | /** 区域名称 */ 6 | name?: string; 7 | /** 控件堆叠方向 */ 8 | direction: string; 9 | /** 控件列表 */ 10 | children: Array; 11 | } -------------------------------------------------------------------------------- /src/assets/less/basic.less: -------------------------------------------------------------------------------- 1 | 2 | 3 | body { 4 | .themeInit(@themeName); 5 | } 6 | 7 | form textarea.ant-input { 8 | margin-top: 4px; 9 | } 10 | 11 | .ant-notification-notice { 12 | float: right; 13 | 14 | .ant-notification-notice-message { 15 | word-break: keep-all; 16 | } 17 | } -------------------------------------------------------------------------------- /src/@types/form-design/form-footer-config.d.ts: -------------------------------------------------------------------------------- 1 | /** 表单底部配置 */ 2 | export declare class FormFooterConfig { 3 | /** 是否显示 */ 4 | isShow: boolean; 5 | /** 提交按钮文本 */ 6 | submitButtonText: string; 7 | /** 是否显示取消按钮 */ 8 | cancelButton: boolean; 9 | /** 取消按钮文字 */ 10 | cancelButtonText: string; 11 | } -------------------------------------------------------------------------------- /src/lib/scss-loader/file-size.json: -------------------------------------------------------------------------------- 1 | { 2 | "dist/sass.js": { 3 | "normal": "5 KB", 4 | "compressed": "2 KB" 5 | }, 6 | "dist/sass.sync.js": { 7 | "normal": "4497 KB", 8 | "compressed": "827 KB" 9 | }, 10 | "dist/sass.worker.js": { 11 | "normal": "4497 KB", 12 | "compressed": "827 KB" 13 | } 14 | } -------------------------------------------------------------------------------- /src/lib/scss-loader/versions.json: -------------------------------------------------------------------------------- 1 | { 2 | "emscripten": { 3 | "version": "1.38.31", 4 | "commit": "040e49a" 5 | }, 6 | "libsass": { 7 | "version": "3.6.2", 8 | "commit": "4da7c4bd" 9 | }, 10 | "sassjs": { 11 | "version": "0.11.1", 12 | "commit": "f286436", 13 | "branch": "libsass/3.6.2" 14 | } 15 | } -------------------------------------------------------------------------------- /src/@types/form-design/form-control-group.d.ts: -------------------------------------------------------------------------------- 1 | import { FormControl } from "./main"; 2 | 3 | /** 组件类型分组 */ 4 | export declare class FormControlGroup { 5 | /** 分组名称 */ 6 | name: string; 7 | /** 分组标题 */ 8 | title: string; 9 | /** Antd图标名 */ 10 | icon?: string; 11 | /** 组件清单 */ 12 | controls: Array; 13 | } -------------------------------------------------------------------------------- /src/@types/form-design/api.d.ts: -------------------------------------------------------------------------------- 1 | /** API接口 */ 2 | export class Api { 3 | /** 接口Id */ 4 | id: string; 5 | /** 名称/别名 */ 6 | name: string; 7 | /** 接口类型 */ 8 | type: 'get' | 'post'; 9 | /** 接口地址 */ 10 | address: string; 11 | /** 描述 */ 12 | remark: string; 13 | /** 参数 */ 14 | params?: Record; 15 | } -------------------------------------------------------------------------------- /src/tools/filters.ts: -------------------------------------------------------------------------------- 1 | import { Vue } from 'vue-property-decorator'; 2 | import { thousandNum, dateFormat } from './common'; 3 | 4 | // 数字转换为金额 5 | Vue.filter('money', function (money) { 6 | return thousandNum(money); 7 | }); 8 | 9 | // 日期格式化 10 | Vue.filter('format', function (date: string, format: string) { 11 | return dateFormat(new Date(date), format); 12 | }); -------------------------------------------------------------------------------- /src/formLibrarys.ts: -------------------------------------------------------------------------------- 1 | import { Enum } from './config/enum'; 2 | import FormDesign from './@types/form-design'; 3 | 4 | export const componentLibrarys: Array = [ 5 | { code: 'vant', name: 'vant', type: Enum.FormType.mobile }, 6 | { code: 'antd', name: 'ant-design', type: Enum.FormType.pc }, 7 | { code: 'uni', name: 'uni', type: Enum.FormType.mobile } 8 | ]; -------------------------------------------------------------------------------- /src/tools/decorator.ts: -------------------------------------------------------------------------------- 1 | import { createDecorator } from'vue-class-component' 2 | 3 | //Vue装饰器 4 | 5 | export const NoCache = createDecorator((options, key) => { 6 | //@ts-ignore 7 | options.computed[key].cache = false; 8 | }); 9 | 10 | /** 调用时打印相关日志 */ 11 | export const Log = createDecorator((component, key) => { 12 | console.log("#Component", component); 13 | console.log("#Key", key); 14 | }); -------------------------------------------------------------------------------- /src/@types/form-design/remote-device.d.ts: -------------------------------------------------------------------------------- 1 | import { Enum } from "@/config/enum"; 2 | 3 | /** 设备类型 */ 4 | export declare class RemoteDevice { 5 | /** 设备编号 */ 6 | code: string; 7 | /** 设备表单类型 */ 8 | type: Enum.FormType; 9 | /** 设备名称 */ 10 | name: string; 11 | /** 宽度 */ 12 | width: number; 13 | /** 高度 */ 14 | height: number; 15 | /** 设备像素比 */ 16 | pixelRatio: number; 17 | } -------------------------------------------------------------------------------- /src/store/index.ts: -------------------------------------------------------------------------------- 1 | import Vue from "vue"; 2 | import Vuex from "vuex"; 3 | 4 | Vue.use(Vuex); 5 | 6 | export default new Vuex.Store({ 7 | state: { 8 | formVariables: [] 9 | }, 10 | mutations: {}, 11 | getters: { 12 | getFormVariables: state => (value): any => { 13 | return state.formVariables; 14 | } 15 | }, 16 | actions: {}, 17 | modules: {} 18 | }); 19 | -------------------------------------------------------------------------------- /src/lib/haku-css/less/theme_blue.less: -------------------------------------------------------------------------------- 1 | // out: false 2 | 3 | .themeInit("blue") { 4 | @page-bg: linear-gradient(to top, #fff1eb 0%, #ace0f9 100%); 5 | 6 | @primary-color: #12B7F5; 7 | 8 | @warning-color: #FF9137; 9 | 10 | @error-color: #FB6155; 11 | 12 | @success-color: #6AC63D; 13 | 14 | @color-link: #00A5E0; 15 | 16 | @border-radius-sm: 0px; 17 | @border-radius-base: 0px; 18 | } -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | # 标题 2 | VUE_APP_TITLE = 表单设计器 3 | 4 | # 业务后端接口(默认) 5 | VUE_APP_INTERFACE = http://bpm.gejinet.com/api/ 6 | # 编辑器后端接口 7 | VUE_APP_DESIGNER_INTERFACE = http://bpmformdesigner.gejinet.com/api/ 8 | # 客户系统后端接口 9 | VUE_APP_CLIENT_INTERFACE = http://bpm.gejinet.com/api/ 10 | 11 | # 默认账号密码 12 | VUE_APP_DEFAULT_USERNAME = admin 13 | VUE_APP_DEFAULT_PASSWORD = 123456 14 | 15 | # 权限获取接口 16 | VUE_APP_PERMISSION_INTERFACE = /permission/getPermission/ -------------------------------------------------------------------------------- /src/@types/form-design/form-function.d.ts: -------------------------------------------------------------------------------- 1 | import { FormControl } from "./main"; 2 | import { Enum } from "@/config/enum"; 3 | 4 | /** 事件 */ 5 | export declare class FormFunction { 6 | /** 事件名称 */ 7 | name: string; 8 | /** 标题 */ 9 | title?: string; 10 | /** 备注 */ 11 | remark?: string; 12 | /** 字符串参数 */ 13 | params?: string; 14 | /** 函数字符串 */ 15 | body?: string; 16 | /** 函数声明 */ 17 | declare?: string; 18 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | temp 4 | todolist.todo 5 | /dist 6 | 7 | /tests/e2e/reports/ 8 | selenium-debug.log 9 | 10 | cli/appsettings.json 11 | 12 | public/assembly 13 | 14 | # local env files 15 | .env.local 16 | .env.*.local 17 | 18 | # Log files 19 | npm-debug.log* 20 | yarn-debug.log* 21 | yarn-error.log* 22 | 23 | # Editor directories and files 24 | .idea 25 | .vscode 26 | *.suo 27 | *.ntvs* 28 | *.njsproj 29 | *.sln 30 | *.sw? 31 | -------------------------------------------------------------------------------- /tests/unit/example.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from "chai"; 2 | import { shallowMount } from "@vue/test-utils"; 3 | import HelloWorld from "@/components/HelloWorld.vue"; 4 | 5 | describe("HelloWorld.vue", () => { 6 | it("renders props.msg when passed", () => { 7 | const msg = "new message"; 8 | const wrapper = shallowMount(HelloWorld, { 9 | propsData: { msg } 10 | }); 11 | expect(wrapper.text()).to.include(msg); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /src/assets/scss/_form-design-pc.scss: -------------------------------------------------------------------------------- 1 | // PC端表单样式 2 | .form-design-canvas { 3 | 4 | &.pc { 5 | text-align: initial; 6 | padding: 30px; 7 | 8 | // 表单大标题 9 | > .form-title { 10 | text-align: center; 11 | color: #333; 12 | font-size: 30px; 13 | margin-bottom: 30px; 14 | // font-weight: normal; 15 | letter-spacing: 2px; 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /src/@types/form-design/property-editor.d.ts: -------------------------------------------------------------------------------- 1 | import { Enum } from "@/config/enum"; 2 | import { BasicControl } from "./main"; 3 | 4 | /** 属性编辑器 */ 5 | export declare class PropertyEditor { 6 | /** 编辑器名称 */ 7 | name: string; 8 | /** 编辑器描述 */ 9 | description: string; 10 | /** 编辑器组件 */ 11 | control: Array; 12 | /** 编辑器 */ 13 | editor: Enum.FormControlPropertyEditor; 14 | /** 值转换器 */ 15 | format?(val: any): any; 16 | } -------------------------------------------------------------------------------- /src/@types/form-design/form-script.d.ts: -------------------------------------------------------------------------------- 1 | import { FormFooterConfig } from "./form-footer-config"; 2 | 3 | /** 表单变量 */ 4 | export declare class FormScript { 5 | 6 | /** Vue变量 */ 7 | data: Record; 8 | 9 | /** Vue函数 */ 10 | methods: Record; 11 | 12 | /** 变量监控 */ 13 | watch: Record; 14 | 15 | /** Created钩子 */ 16 | created(): void; 17 | 18 | /** Mounted钩子 */ 19 | mounted(): void; 20 | } -------------------------------------------------------------------------------- /src/components/taskform/BlankControl.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 16 | 17 | 20 | -------------------------------------------------------------------------------- /src/@types/form-design/form-variable.d.ts: -------------------------------------------------------------------------------- 1 | import { FormFooterConfig } from "./form-footer-config"; 2 | 3 | /** 表单变量 */ 4 | export declare class FormVariable { 5 | 6 | /** 变量前缀关键字 */ 7 | keyword?: 'const' | 'let' | 'var'; 8 | 9 | /** 子级变量列表 */ 10 | children?: Array; 11 | 12 | /** 变量名 */ 13 | name: string; 14 | 15 | /** 变量类型 */ 16 | type?: string; 17 | 18 | /** 变量默认值 */ 19 | default?: any; 20 | 21 | /** 注释 */ 22 | remark?: string; 23 | } -------------------------------------------------------------------------------- /src/tools/registerGlobalComponents.ts: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | 3 | const components = require.context('../components/', true, /\.(vue|ts|js)$/); 4 | 5 | const install = function():void { 6 | components.keys().forEach(name => { 7 | let componentName = name.substr(name.lastIndexOf('/') + 1).replace(/\.\/|\.(js|ts|vue)/g, ''); 8 | Vue.component(componentName, components(name).default); 9 | }); 10 | }; 11 | 12 | export default { 13 | install, 14 | ...components 15 | }; 16 | -------------------------------------------------------------------------------- /src/@types/antd-vue.d.ts: -------------------------------------------------------------------------------- 1 | 2 | //Antd-Vue Bug修复 3 | 4 | import { WrappedFormUtils } from 'ant-design-vue/types/form/form' 5 | import { Component, Prop, Vue } from 'vue-property-decorator'; 6 | 7 | declare module 'ant-design-vue/types/form/form' { 8 | 9 | 10 | interface WrappedFormUtils { 11 | clearField(name?: string): void; 12 | } 13 | 14 | interface Form { 15 | /** 创建表单(于created事件) */ 16 | createForm(context: Vue, options?: IformCreateOption): WrappedFormUtils; 17 | } 18 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 | 5 | # haku-form-design 6 | 7 | > 白箱表单设计器 8 | 9 | ## 示例图 10 | 11 |
12 | 13 |
14 | 15 | ## 说明 16 | 17 | :smiley: 使用VueCLI4.0+及TypeScript1.7+开发,目前仅对chrome浏览器提供完整支持。 18 | 19 | ## 安装步骤 20 | 21 | ``` 22 | // 安装 23 | npm install 24 | 25 | // 运行 26 | npm run serve 27 | 28 | // 打包 29 | npm run build 30 | ``` 31 | -------------------------------------------------------------------------------- /src/@types/form-design/service-config.d.ts: -------------------------------------------------------------------------------- 1 | 2 | /** 服务配置 */ 3 | export declare class ServiceConfig { 4 | /** 基础服务域名 */ 5 | baseUrl: string; 6 | 7 | /** 基础数据 */ 8 | baseData?: any[]; 9 | /** 基础数据API地址 */ 10 | baseDataUrl?: string; 11 | /** 基础数据标题字段 */ 12 | baseDataTitleField?: string; 13 | /** 基础数据值字段 */ 14 | baseDataValueField?: string; 15 | 16 | viewData?: any[]; 17 | /** 视图数据API地址 */ 18 | viewDataUrl?: string; 19 | /** 视图数据标题字段 */ 20 | viewDataTitleField?: string; 21 | /** 视图数据值字段 */ 22 | viewDataValueField?: string; 23 | } -------------------------------------------------------------------------------- /src/lib/haku-css/less/main.less: -------------------------------------------------------------------------------- 1 | // autoprefixer: > 5%; last 10 Chrome versions; not ie 6-8, out: ../css/main.css, relativeUrls: true, sourceMap: true, relativeUrls: true 2 | 3 | //1、全局变量/系统样式 4 | @import "system"; 5 | //2、基础框架 6 | @import "basic"; 7 | //3、页面/表单控件 8 | // @import "control"; 9 | //5、页面模块 10 | // @import "module"; 11 | 12 | //5、[风格/主题] 13 | //科技蓝主题(测试) 14 | // @import "theme_blue"; 15 | //测试主题(测试) 16 | // @import "theme_x"; 17 | 18 | @themeName: ""; 19 | 20 | .themeInit("") {} 21 | 22 | body { 23 | .themeInit(@themeName); 24 | 25 | } 26 | 27 | //.buildThemes(@themeName); -------------------------------------------------------------------------------- /src/views/BasicFormDesign.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 19 | 20 | -------------------------------------------------------------------------------- /src/@types/form-design/basic-control.d.ts: -------------------------------------------------------------------------------- 1 | /** 基础组件 */ 2 | export declare class BasicControl { 3 | /** 组件Id,即为Ref */ 4 | id?: string; 5 | /** 编辑器组件 */ 6 | control: string; 7 | /** 组件属性 */ 8 | attrs: Record; 9 | /** 组件属性 */ 10 | events: Record; 11 | /** 修改前的组件属性 */ 12 | propAttrs: Record; 13 | /** 组件默认属性 */ 14 | defaultAttrs?: Record; 15 | /** 编辑器插槽 */ 16 | slot: Record>; 17 | /** 包含的HTML */ 18 | html?: string; 19 | /** 是否为主控件 */ 20 | isMain?: boolean; 21 | } -------------------------------------------------------------------------------- /tests/e2e/specs/test.js: -------------------------------------------------------------------------------- 1 | // For authoring Nightwatch tests, see 2 | // https://nightwatchjs.org/guide 3 | 4 | module.exports = { 5 | "default e2e tests": browser => { 6 | browser 7 | .init() 8 | .waitForElementVisible("#app") 9 | .assert.elementPresent(".hello") 10 | .assert.containsText("h1", "Welcome to Your Vue.js + TypeScript App") 11 | .assert.elementCount("img", 1) 12 | .end(); 13 | }, 14 | 15 | "example e2e test using a custom command": browser => { 16 | browser 17 | .openHomepage() 18 | .assert.elementPresent(".hello") 19 | .end(); 20 | } 21 | }; 22 | -------------------------------------------------------------------------------- /src/@types/form-design/drag-config.d.ts: -------------------------------------------------------------------------------- 1 | import { Location } from '@/@types/form-design/location' 2 | 3 | /** 拖拽组件配置 */ 4 | export declare class DragConfig { 5 | /** 是否开始预拖拽 */ 6 | isPreDrag: boolean; 7 | /** 是否开始正式拖拽 */ 8 | isDrag: boolean; 9 | /** 是否进入放置范围 */ 10 | isDragArea: boolean; 11 | /** 正在拖拽的组件 */ 12 | control: any; 13 | /** 要插入的组件Id(非父组件的情况默认插入组件前) */ 14 | insertControlId: string; 15 | /** 要插入的父控件Slot索引 */ 16 | insertControlSlotIndex?: number; 17 | /** 用于移动的组件Id */ 18 | targetFormControlId: string; 19 | /** 开始坐标 */ 20 | startLoc: Location; 21 | /** 结束坐标 */ 22 | endLoc: Location; 23 | } -------------------------------------------------------------------------------- /src/@types/form-design/form-template.d.ts: -------------------------------------------------------------------------------- 1 | import { Form } from "ant-design-vue"; 2 | import { FormControl, FormVariable, FormFunction } from "./main"; 3 | 4 | /** 表单模板 */ 5 | export declare class FormTemplate { 6 | /** Code */ 7 | code: string; 8 | /** 标题 */ 9 | title: string; 10 | /** 描述 */ 11 | description?: string; 12 | /** 设备类型 */ 13 | deviceType: 'pc' | 'mobile'; 14 | /** 组件库code */ 15 | library: string; 16 | /** 控件列表 */ 17 | controls: Array; 18 | /** Vue初始化代码 */ 19 | script?: string; 20 | /** 初始样式(SCSS) */ 21 | style?: string; 22 | /** 初始API */ 23 | api?: Record[]; 24 | } -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | <%= VUE_APP_TITLE %> 10 | 11 | 12 | 15 |
16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | // .prettierrc.yaml 2 | module.exports = { 3 | printWidth: 120, //每行代码长度(默认120) 4 | tabWidth: 4, //每个tab相当于多少个空格(默认2) 5 | useTabs: false, //是否使用tab进行缩进(默认false) 6 | singleQuote: true, //使用单引号(默认false) 7 | semi: true, //声明结尾使用分号(默认true) 8 | trailingComma: 'none', //多行使用拖尾逗号(默认none) 9 | bracketSpacing: true, //对象字面量的大括号间使用空格(默认true) 10 | jsxBracketSameLine: false, //多行JSX中的>放置在最后一行的结尾,而不是另起一行(默认false) 11 | arrowParens: 'avoid', //只有一个参数的箭头函数的参数是否带圆括号(默认avoid) 12 | htmlWhitespaceSensitivity: 'ignore', 13 | jsxSingleQuote: true, 14 | } -------------------------------------------------------------------------------- /src/@types/form-design/form-config.d.ts: -------------------------------------------------------------------------------- 1 | import { FormFooterConfig } from "./form-footer-config"; 2 | 3 | /** 表单基本配置 */ 4 | export declare class FormConfig { 5 | /** 表单唯一编号 */ 6 | id: string; 7 | /** 表单组件库 */ 8 | formComponentLib: string; 9 | /** 面板标题 */ 10 | canvasTitle: string; 11 | /** 表单标题 */ 12 | formTitle: string; 13 | /** 表单名称 */ 14 | formName: string; 15 | /** 表单宽度 */ 16 | width: number; 17 | /** 表单高度 */ 18 | height: number; 19 | /** 表单头部高度 */ 20 | headerHeight: number; 21 | /** 设备类型 */ 22 | deviceId: string; 23 | /** 底部按钮配置 */ 24 | footer: FormFooterConfig; 25 | /** 表单当前控件索引(自增) */ 26 | controlIndex: number; 27 | /** 表单主题 */ 28 | formTheme: string; 29 | } -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 21 | 22 | 30 | -------------------------------------------------------------------------------- /src/router/index.ts: -------------------------------------------------------------------------------- 1 | import Vue from "vue"; 2 | import VueRouter from "vue-router"; 3 | import Home from "../views/Home.vue"; 4 | 5 | Vue.use(VueRouter); 6 | 7 | const routes = [ 8 | { 9 | path: "/", 10 | name: "home", 11 | component: Home 12 | }, 13 | { 14 | path: "/about", 15 | name: "about", 16 | // route level code-splitting 17 | // this generates a separate chunk (about.[hash].js) for this route 18 | // which is lazy-loaded when the route is visited. 19 | component: () => 20 | import(/* webpackChunkName: "about" */ "../views/About.vue") 21 | } 22 | ]; 23 | 24 | const router = new VueRouter({ 25 | mode: "history", 26 | base: process.env.BASE_URL, 27 | routes 28 | }); 29 | 30 | export default router; 31 | -------------------------------------------------------------------------------- /src/components/control_vant/VantFormDesignControlCellGroup.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 24 | 25 | -------------------------------------------------------------------------------- /src/components/control_antd/AntdFormDesignStaticChildControl.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 23 | 24 | -------------------------------------------------------------------------------- /src/components/control_vant/VantFormDesignControlGrid.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 25 | 26 | -------------------------------------------------------------------------------- /src/components/control_antd/AntdFormDesignControlCard.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 24 | 25 | -------------------------------------------------------------------------------- /tests/e2e/custom-commands/openHomepageClass.js: -------------------------------------------------------------------------------- 1 | /** 2 | * A class-based Nightwatch custom command which is a variation of the openHomepage.js command. 3 | * The command name is the filename and class needs to contain a "command" method. 4 | * 5 | * Example usage: 6 | * browser.openHomepageClass(); 7 | * 8 | * For more information on writing custom commands see: 9 | * https://nightwatchjs.org/guide/extending-nightwatch/#writing-custom-commands 10 | * 11 | */ 12 | 13 | const assert = require("assert"); 14 | 15 | module.exports = class { 16 | async command() { 17 | // Other Nightwatch commands are available via "this.api" 18 | this.api.init(); 19 | this.api.waitForElementVisible("#app"); 20 | 21 | const result = await this.api.elements("css selector", "#app ul"); 22 | assert.strictEqual(result.value.length, 3); 23 | } 24 | }; 25 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "module": "esnext", 5 | "strict": true, 6 | "allowJs": false, 7 | "checkJs": false, 8 | "noImplicitAny": false, 9 | "jsx": "preserve", 10 | "importHelpers": true, 11 | "moduleResolution": "node", 12 | "experimentalDecorators": true, 13 | "esModuleInterop": true, 14 | "allowSyntheticDefaultImports": true, 15 | "sourceMap": true, 16 | "baseUrl": ".", 17 | "types": ["webpack-env", "mocha", "chai"], 18 | "paths": { 19 | "@/*": ["src/*"] 20 | }, 21 | "lib": ["esnext", "dom", "dom.iterable", "scripthost"] 22 | }, 23 | "include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue", "tests/**/*.ts", "tests/**/*.tsx", "src/tools/directives.js"], 24 | "exclude": ["node_modules"] 25 | } 26 | -------------------------------------------------------------------------------- /tests/e2e/custom-commands/openHomepage.js: -------------------------------------------------------------------------------- 1 | /** 2 | * A basic Nightwatch custom command 3 | * which demonstrates usage of ES6 async/await instead of using callbacks. 4 | * The command name is the filename and the exported "command" function is the command. 5 | * 6 | * Example usage: 7 | * browser.openHomepage(); 8 | * 9 | * For more information on writing custom commands see: 10 | * https://nightwatchjs.org/guide/extending-nightwatch/#writing-custom-commands 11 | * 12 | */ 13 | module.exports = { 14 | command: async function() { 15 | // Other Nightwatch commands are available via "this" 16 | // .init() simply calls .url() command with the value of the "launch_url" setting 17 | this.init(); 18 | this.waitForElementVisible("#app"); 19 | 20 | const result = await this.elements("css selector", "#app ul"); 21 | this.assert.strictEqual(result.value.length, 3); 22 | } 23 | }; 24 | -------------------------------------------------------------------------------- /src/formControlGroups.ts: -------------------------------------------------------------------------------- 1 | import FormDesign from './@types/form-design'; 2 | 3 | /** 组件分组列表 */ 4 | let _controlGroups: Array = [ 5 | { 6 | name: 'layout', 7 | title: '布局控件', 8 | icon: 'layout', 9 | controls: [] 10 | }, { 11 | name: 'input', 12 | title: '输入控件', 13 | icon: 'edit', 14 | controls: [] 15 | }, { 16 | name: 'select', 17 | title: '选择控件', 18 | icon: 'select', 19 | controls: [] 20 | }, { 21 | name: 'upload', 22 | title: '上传控件', 23 | icon: 'cloud-upload', 24 | controls: [] 25 | }, { 26 | name: 'hidden', 27 | title: '隐藏控件', 28 | icon: 'eye-invisible', 29 | controls: [] 30 | }, { 31 | name: 'else', 32 | title: '其他控件', 33 | icon: 'inbox', 34 | controls: [] 35 | } 36 | ]; 37 | 38 | export function initFormControlGroups(): Array { 39 | return _controlGroups; 40 | } -------------------------------------------------------------------------------- /src/@types/model.d.ts: -------------------------------------------------------------------------------- 1 | 2 | 3 | declare module 'model' { 4 | 5 | /** 用户Model */ 6 | interface User { 7 | /** Id */ 8 | id: string, 9 | /** 账号 */ 10 | account: string, 11 | /** 姓名 */ 12 | name?: string, 13 | /** 创建日期 */ 14 | createtime?: string, 15 | /** 邮箱 */ 16 | email?: string, 17 | nickname?: string, 18 | phone?: string, 19 | roleid?: string, 20 | rolename?: string, 21 | } 22 | 23 | /** 组织Model */ 24 | interface Group { 25 | /** Id */ 26 | key: string, 27 | /** 组织名称 */ 28 | title: string, 29 | /** 组织英文名称 */ 30 | enTitle: string, 31 | /** 子组织 */ 32 | children: Array, 33 | /** 父组织Id */ 34 | parentId: string, 35 | /** 可选插槽 */ 36 | scopedSlots?: object 37 | } 38 | 39 | /**角色 */ 40 | interface Role{ 41 | /** Id */ 42 | key: string, 43 | /** 角色名称 */ 44 | name: string, 45 | } 46 | 47 | } -------------------------------------------------------------------------------- /src/components/control_antd/AntdFormDesignControlTabs.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 28 | 29 | -------------------------------------------------------------------------------- /src/components/control_antd/AntdFormDesignControlCollapse.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 27 | 28 | -------------------------------------------------------------------------------- /src/components/control_antd/AntdFormDesignControlFlex.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 23 | 24 | -------------------------------------------------------------------------------- /src/@types/shims-tsx.d.ts: -------------------------------------------------------------------------------- 1 | import Vue, { VNode } from 'vue'; 2 | import lodash from 'lodash'; 3 | import { VueCookies } from 'vue-cookies'; 4 | 5 | declare global { 6 | 7 | /** cookie操作类 */ 8 | const $cookies: VueCookies; 9 | 10 | interface Sass { 11 | /** 编译 */ 12 | compile(scssTxt: string, cb: (result: { text: string }) => any); 13 | } 14 | 15 | const _: typeof lodash; 16 | const Sass: Sass; 17 | namespace JSX { 18 | // tslint:disable no-empty-interface 19 | interface Element extends VNode {} 20 | // tslint:disable no-empty-interface 21 | interface ElementClass extends Vue {} 22 | interface IntrinsicElements { 23 | [elem: string]: any; 24 | } 25 | } 26 | 27 | interface Date { 28 | /** 29 | * 日期格式化 30 | * @param {string} [fmt='yyyy-MM-dd'] 格式化参数 31 | * @returns 格式化后的字符串 32 | */ 33 | format(fmt: string): string; 34 | } 35 | } 36 | 37 | declare module 'axios' { 38 | interface AxiosRequestConfig { 39 | /** 用于撤销请求的函数 */ 40 | cancel?: Function; 41 | } 42 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 hakubox 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/components/control_vant/VantFormDesignControlRadioGroup.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 33 | 34 | -------------------------------------------------------------------------------- /src/components/control_vant/VantFormDesignControlCheckboxGroup.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 33 | 34 | -------------------------------------------------------------------------------- /public/haku-formdesign.svg: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 9 | 10 | 11 | 13 | 14 | 15 | 17 | 18 | 19 | 21 | 22 | 23 | 25 | 26 | -------------------------------------------------------------------------------- /src/components/control_vant/VantFormDesignControlRow.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 30 | 31 | -------------------------------------------------------------------------------- /src/config/theme.ts: -------------------------------------------------------------------------------- 1 | const themeCache: Record> = {}; 2 | 3 | const themeAction = { 4 | /** 默认主题 */ 5 | default() { 6 | if (!themeCache.default) { 7 | themeCache.default = [ 8 | require('@/assets/theme/theme-default/index.lazy.less'), 9 | require('@/assets/theme/theme-default/index.lazy.scss') 10 | ]; 11 | } 12 | return themeCache.default; 13 | }, 14 | /** [test]红色主题 */ 15 | red() { 16 | if (!themeCache.red) { 17 | themeCache.red = [ 18 | require('@/assets/theme/theme-red/index.lazy.less'), 19 | require('@/assets/theme/theme-red/index.lazy.scss') 20 | ]; 21 | } 22 | return themeCache.red; 23 | } 24 | }; 25 | 26 | /** 当前主题 */ 27 | let currentTheme: Array = []; 28 | 29 | /** 设置主题 */ 30 | async function setTheme(theme) { 31 | if (themeAction[theme]) { 32 | const style = await themeAction[theme](); 33 | currentTheme.forEach(i => i.unuse()); 34 | style.forEach(i => i.use()); 35 | currentTheme = style; 36 | } 37 | } 38 | 39 | export default setTheme; -------------------------------------------------------------------------------- /tests/e2e/custom-assertions/elementCount.js: -------------------------------------------------------------------------------- 1 | /** 2 | * A custom Nightwatch assertion. The assertion name is the filename. 3 | * 4 | * Example usage: 5 | * browser.assert.elementCount(selector, count) 6 | * 7 | * For more information on custom assertions see: 8 | * https://nightwatchjs.org/guide/extending-nightwatch/#writing-custom-assertions 9 | * 10 | * 11 | * @param {string|object} selectorOrObject 12 | * @param {number} count 13 | */ 14 | 15 | exports.assertion = function elementCount(selectorOrObject, count) { 16 | let selector; 17 | 18 | // when called from a page object element or section 19 | if (typeof selectorOrObject === "object" && selectorOrObject.selector) { 20 | // eslint-disable-next-line prefer-destructuring 21 | selector = selectorOrObject.selector; 22 | } else { 23 | selector = selectorOrObject; 24 | } 25 | 26 | this.message = `Testing if element <${selector}> has count: ${count}`; 27 | this.expected = count; 28 | this.pass = val => val === count; 29 | this.value = res => res.value; 30 | function evaluator(_selector) { 31 | return document.querySelectorAll(_selector).length; 32 | } 33 | this.command = cb => this.api.execute(evaluator, [selector], cb); 34 | }; 35 | -------------------------------------------------------------------------------- /tests/e2e/specs/test-with-pageobjects.js: -------------------------------------------------------------------------------- 1 | //////////////////////////////////////////////////////////////// 2 | // For authoring Nightwatch tests, see 3 | // https://nightwatchjs.org/guide 4 | // 5 | // For more information on working with page objects see: 6 | // https://nightwatchjs.org/guide/working-with-page-objects/ 7 | //////////////////////////////////////////////////////////////// 8 | 9 | module.exports = { 10 | beforeEach: browser => browser.init(), 11 | 12 | "e2e tests using page objects": browser => { 13 | const homepage = browser.page.homepage(); 14 | homepage.waitForElementVisible("@appContainer"); 15 | 16 | const app = homepage.section.app; 17 | app.assert.elementCount("@logo", 1); 18 | app.expect.section("@welcome").to.be.visible; 19 | app.expect 20 | .section("@headline") 21 | .text.to.match(/^Welcome to Your Vue\.js (.*)App$/); 22 | 23 | browser.end(); 24 | }, 25 | 26 | 'verify if string "e2e-nightwatch" is within the cli plugin links': browser => { 27 | const homepage = browser.page.homepage(); 28 | const welcomeSection = homepage.section.app.section.welcome; 29 | 30 | welcomeSection.expect 31 | .element("@cliPluginLinks") 32 | .text.to.contain("e2e-nightwatch"); 33 | } 34 | }; 35 | -------------------------------------------------------------------------------- /src/@types/form-design/form-control-property.d.ts: -------------------------------------------------------------------------------- 1 | import { FormControl } from "./main"; 2 | import { Enum } from "@/config/enum"; 3 | 4 | /** 属性 */ 5 | export declare class FormControlProperty { 6 | /** 属性名 */ 7 | name: string; 8 | /** 属性标题 */ 9 | title: string; 10 | /** 属性类型(目前仅为model属性使用) */ 11 | type?: string | Function; 12 | /** 备注 */ 13 | remark?: string; 14 | /** 默认值 */ 15 | default?: any; 16 | /** 是否必填 */ 17 | require?: boolean; 18 | /** 是否显示 */ 19 | visible?: boolean; 20 | /** 是否子级 */ 21 | leaf?: boolean; 22 | /** 是否为可变属性 */ 23 | isSync?: boolean; 24 | /** 属性分组 */ 25 | group: Enum.FormControlPropertyGroup; 26 | /** 属性描述 */ 27 | description?: string; 28 | /** 编辑器 */ 29 | editor: Enum.FormControlPropertyEditor; 30 | /** 修改属性 */ 31 | change?(prop: FormControlProperty, propList: Record, control: Array, value: any, refs: Record): void; 32 | 33 | /** 附加属性 */ 34 | attrs?: Record; 35 | /** 附加选项 */ 36 | attach?: Array; 37 | /** 布局方式,默认为行内布局 */ 38 | layout?: Enum.PropertyLayout; 39 | 40 | /** 属性当前的编辑器 */ 41 | // currentEditor?: Enum.FormControlPropertyEditor; 42 | } -------------------------------------------------------------------------------- /src/components/control_antd/AntdFormDesignControlRow.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 35 | 36 | -------------------------------------------------------------------------------- /tests/e2e/page-objects/homepage.js: -------------------------------------------------------------------------------- 1 | /** 2 | * A Nightwatch page object. The page object name is the filename. 3 | * 4 | * Example usage: 5 | * browser.page.homepage.navigate() 6 | * 7 | * For more information on working with page objects see: 8 | * https://nightwatchjs.org/guide/working-with-page-objects/ 9 | * 10 | */ 11 | 12 | module.exports = { 13 | url: "/", 14 | commands: [], 15 | 16 | // A page object can have elements 17 | elements: { 18 | appContainer: "#app" 19 | }, 20 | 21 | // Or a page objects can also have sections 22 | sections: { 23 | app: { 24 | selector: "#app", 25 | 26 | elements: { 27 | logo: "img" 28 | }, 29 | 30 | // - a page object section can also have sub-sections 31 | // - elements or sub-sections located here are retrieved using the "app" section as the base 32 | sections: { 33 | headline: { 34 | selector: "h1" 35 | }, 36 | 37 | welcome: { 38 | // the equivalent css selector for the "welcome" sub-section would be: 39 | // '#app div.hello' 40 | selector: "div.hello", 41 | 42 | elements: { 43 | cliPluginLinks: { 44 | selector: "ul", 45 | index: 0 46 | } 47 | } 48 | } 49 | } 50 | } 51 | } 52 | }; 53 | -------------------------------------------------------------------------------- /src/@types/form-design/form-control.d.ts: -------------------------------------------------------------------------------- 1 | import { BasicControl, FormControlProperty, FormFunction } from "./main"; 2 | import { Enum } from "@/config/enum"; 3 | 4 | /** 表单控件 */ 5 | export declare class FormControl { 6 | /** 控件ID(全局唯一) */ 7 | id: string; 8 | /** 控件名称 */ 9 | name: string; 10 | /** 控件 */ 11 | control: BasicControl; 12 | /** 控件图标 */ 13 | icon?: string; 14 | /** 控件标题 */ 15 | title?: string; 16 | /** 控件简要说明 */ 17 | description?: string; 18 | /** 控件类型 */ 19 | type?: Enum.FormControlType | string; 20 | /** 控件属性 */ 21 | propertys: Array; 22 | /** 默认控件属性编辑器 */ 23 | propertyEditors?: Record; 24 | 25 | /** 控件高度 */ 26 | height?: number; 27 | /** 子组件区域选择器(与插槽无关) */ 28 | childrenSlot?: string; 29 | /** 子控件 */ 30 | children?: Array>; 31 | /** 备注名 */ 32 | remark?: string; 33 | /** 自动命名前缀 */ 34 | autoPrefix?: string; 35 | /** 是否为表单项 */ 36 | isFormItem?: boolean; 37 | /** 控件事件 */ 38 | events: Array; 39 | /** 构建Template */ 40 | render?: (control: FormControl) => string; 41 | /** 控件Ref */ 42 | ref?: string; 43 | /** 是否为隐藏控件 */ 44 | isHide?: boolean; 45 | /** 是否为原始控件(不需要form-control包装) */ 46 | isOriginal?: boolean; 47 | } -------------------------------------------------------------------------------- /tests/e2e/custom-commands/customExecute.js: -------------------------------------------------------------------------------- 1 | /** 2 | * A very basic Nightwatch custom command. The command name is the filename and the 3 | * exported "command" function is the command. 4 | * 5 | * Example usage: 6 | * browser.customExecute(function() { 7 | * console.log('Hello from the browser window') 8 | * }); 9 | * 10 | * For more information on writing custom commands see: 11 | * https://nightwatchjs.org/guide/extending-nightwatch/#writing-custom-commands 12 | * 13 | * @param {*} data 14 | */ 15 | exports.command = function command(data) { 16 | // Other Nightwatch commands are available via "this" 17 | 18 | // .execute() inject a snippet of JavaScript into the page for execution. 19 | // the executed script is assumed to be synchronous. 20 | // 21 | // See https://nightwatchjs.org/api/execute.html for more info. 22 | // 23 | this.execute( 24 | // The function argument is converted to a string and sent to the browser 25 | function(argData) { 26 | return argData; 27 | }, 28 | 29 | // The arguments for the function to be sent to the browser are specified in this array 30 | [data], 31 | 32 | function(result) { 33 | // The "result" object contains the result of what we have sent back from the browser window 34 | console.log("custom execute result:", result.value); 35 | } 36 | ); 37 | 38 | return this; 39 | }; 40 | -------------------------------------------------------------------------------- /src/components/control_vant/VantFormDesignControlCollapse.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 31 | 32 | -------------------------------------------------------------------------------- /src/lib/monaco-language/sql/sql.suggestions.js: -------------------------------------------------------------------------------- 1 | import * as monaco from 'monaco-editor/esm/vs/editor/editor.api'; 2 | 3 | // { 4 | // label: 'ifelse', 5 | // kind: monaco.languages.CompletionItemKind.Snippet, 6 | // insertText: [ 7 | // 'if (${1:condition}) {', 8 | // '\t$0', 9 | // '} else {', 10 | // '\t', 11 | // '}' 12 | // ].join('\n'), 13 | // insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet, 14 | // documentation: 'If-Else Statement' 15 | // } 16 | 17 | export let suggestions = [ 18 | { 19 | label: 'ssf', 20 | kind: monaco.languages.CompletionItemKind.Snippet, 21 | insertText: 'SELECT $1 FROM $0', 22 | insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet, 23 | documentation: 'SELECT $1 FROM $0' 24 | }, { 25 | label: 'orderby', 26 | kind: monaco.languages.CompletionItemKind.Snippet, 27 | insertText: 'ORDER BY', 28 | insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet, 29 | documentation: 'ORDER BY' 30 | }, { 31 | label: 'groupby', 32 | kind: monaco.languages.CompletionItemKind.Snippet, 33 | insertText: 'GROUP BY', 34 | insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet, 35 | documentation: 'GROUP BY' 36 | } 37 | ] -------------------------------------------------------------------------------- /src/components/control_vant/VantFormDesignControlTabs.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 35 | 36 | -------------------------------------------------------------------------------- /src/@types/basic.d.ts: -------------------------------------------------------------------------------- 1 | import { Pagination } from 'ant-design-vue'; 2 | 3 | /** 分页参数 */ 4 | export interface pageFilter { 5 | /** 每页数据条数 */ 6 | pageSize: number; 7 | /** 当前页数 */ 8 | pageNum: number; 9 | } 10 | 11 | /** 面包屑 */ 12 | export interface Breadcrumb { 13 | /** 标签文本 */ 14 | title: string; 15 | /** 跳转地址 */ 16 | url?: string; 17 | /** 图标 */ 18 | icon?: string; 19 | /** 点击事件 */ 20 | click?: Function 21 | } 22 | 23 | /** 用户信息 */ 24 | export interface UserInfo { 25 | id?: string, 26 | name?: string, 27 | rolename?: string, 28 | nickname?: string, 29 | phone?: string, 30 | email?: string, 31 | createtime?: Date | number 32 | } 33 | 34 | /** 页面用非可空分页类 */ 35 | export declare class PagePagination extends Pagination { 36 | total: number; 37 | defaultCurrent: number; 38 | current: number; 39 | defaultPageSize: number; 40 | pageSize: number; 41 | size: string; 42 | orderBy: string; 43 | sort: string; 44 | filters: object; 45 | } 46 | 47 | /** 弹窗查询Model */ 48 | export interface QueryDto { 49 | /** 数据源编号 */ 50 | code: string, 51 | /** 分页 */ 52 | inputPage: PagePagination, 53 | /** 过滤键值 */ 54 | filters?: object 55 | } 56 | /** 弹窗列 */ 57 | export interface Column { 58 | /** 列明 */ 59 | name: string, 60 | /** 字段名 */ 61 | colname: string, 62 | /** 是否显示 */ 63 | isdisplay: boolean, 64 | /** 对齐方式 */ 65 | alignment: string, 66 | } -------------------------------------------------------------------------------- /src/config/router.ts: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import Router from 'vue-router'; 3 | 4 | Vue.use(Router); 5 | 6 | export default new Router({ 7 | mode: 'history', 8 | base: process.env.BASE_URL, 9 | routes: [ 10 | { 11 | path: '/', 12 | redirect: '/formdesign', 13 | }, { 14 | path: '/taskapproval', 15 | name: 'taskapproval', 16 | meta: { title: '待审批' }, 17 | component: () => import('@/views/task-handle/TaskApproval.vue') 18 | }, { 19 | path: '/myraised', 20 | name: 'myraised', 21 | meta: { title: '我的申请' }, 22 | component: () => import('@/views/task-handle/MyRaised.vue') 23 | }, { 24 | path: '/myhandled', 25 | name: 'myhandled', 26 | meta: { title: '已办任务' }, 27 | component: () => import('@/views/task-handle/MyHandled.vue') 28 | }, { 29 | path: '/taskform', 30 | name: 'taskform', 31 | meta: { title: '表单' }, 32 | component: () => import('@/views/TaskForm.vue') 33 | }, { 34 | path: '/formdesign', 35 | name: 'formdesign', 36 | meta: { title: '表单编辑器' }, 37 | component: () => import('@/views/BasicFormDesign.vue') 38 | }, { 39 | path: '/taskconfig', 40 | name: 'taskconfig', 41 | meta: { title: '流程配置' }, 42 | component: () => import('@/views/task-handle/TaskConfig.vue') 43 | }, 44 | ] 45 | }); 46 | -------------------------------------------------------------------------------- /src/tools/directives.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import store from '@/config/store'; 3 | 4 | function insertAfter(newElement, targentElement) { 5 | let parent = targentElement.parentNode; 6 | if (parent.lastChild == targentElement) { 7 | parent.appendChild(newElement); 8 | } else { 9 | parent.insertBefore(newElement, targentElement.nextSibling) 10 | } 11 | } 12 | 13 | /** 14 | * Action 权限指令 (不建议使用) 15 | * 指令用法: 16 | * - 在需要控制 action 级别权限的组件上使用 v-action:[method] , 如下: 17 | * 添加用户 18 | * 删除用户 19 | * 修改 20 | * 21 | * - 当前用户没有权限时,组件上使用了该指令则会被隐藏 22 | */ 23 | const action = Vue.directive('action', { 24 | inserted: function(el, binding, vnode) { 25 | let permission = binding.arg || binding.value; 26 | if(permission) { 27 | if(!store.getters.checkPermissions(permission)) { 28 | let comment = document.createComment(` ${permission} `); 29 | if(el.parentNode) { 30 | insertAfter(comment, el); 31 | vnode.context.comment = comment; 32 | } 33 | (el.parentNode && el.parentNode.removeChild(el)) || (el.style.display = 'none'); 34 | } else if(vnode.context.comment) { 35 | insertAfter(el, vnode.context.comment); 36 | vnode.context.comment.parentNode.removeChild(vnode.context.comment); 37 | } 38 | } 39 | } 40 | }); 41 | 42 | export default action; 43 | -------------------------------------------------------------------------------- /src/lib/less-loader/index.js: -------------------------------------------------------------------------------- 1 | import lessRoot from 'less/lib/less'; 2 | import PluginLoader from 'less/lib/less-browser/plugin-loader'; 3 | import LogListener from 'less/lib/less-browser/log-listener'; 4 | import ImageSize from 'less/lib/less-browser/image-size'; 5 | 6 | export default (options = {}) => { 7 | const less = lessRoot(); 8 | 9 | less.options = options; 10 | less.PluginLoader = PluginLoader; 11 | 12 | LogListener(less, options); 13 | ImageSize(less.environment); 14 | 15 | // Setup user functions - Deprecate? 16 | if (options.functions) { 17 | less.functions.functionRegistry.addMultiple(options.functions); 18 | } 19 | 20 | function clone(obj) { 21 | const cloned = {}; 22 | for (const prop in obj) { 23 | if (obj.hasOwnProperty(prop)) { 24 | cloned[prop] = obj[prop]; 25 | } 26 | } 27 | return cloned; 28 | } 29 | 30 | function loadStyles(lessText, modifyVars) { 31 | return new Promise((resolve, reject) => { 32 | const instanceOptions = clone(options); 33 | instanceOptions.modifyVars = modifyVars; 34 | instanceOptions.filename = document.location.href.replace(/#.*$/, ''); 35 | 36 | less.render(lessText, instanceOptions, (e, result) => { 37 | if (result?.css) { 38 | resolve(result.css); 39 | } else { 40 | reject('less解析失败'); 41 | } 42 | }); 43 | }); 44 | } 45 | 46 | less.refreshStyles = loadStyles; 47 | return loadStyles; 48 | }; -------------------------------------------------------------------------------- /src/components/editor/simple-editor/SimpleEditor.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 44 | 45 | -------------------------------------------------------------------------------- /src/components/control_antd/AntdFormDesignControlDatePicker.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 53 | 54 | 66 | -------------------------------------------------------------------------------- /src/@types/form-design/main.d.ts: -------------------------------------------------------------------------------- 1 | import { Location } from "./location"; 2 | import { DragConfig } from "./drag-config"; 3 | import { FormConfig } from "./form-config"; 4 | import { ComponentLibrary } from "./form-component-library"; 5 | import { FormFooterConfig } from "./form-footer-config"; 6 | import { FormControlGroup } from "./form-control-group"; 7 | import { RemoteDevice } from "./remote-device"; 8 | import { FormPanel } from "./form-panel"; 9 | import { FormControlProperty } from "./form-control-property"; 10 | import { FormFunction } from "./form-function"; 11 | import { PropertyEditor } from "./property-editor"; 12 | import { BasicControl } from "./basic-control"; 13 | import { PropertyGroup } from "./property-group"; 14 | import { FormControl } from "./form-control"; 15 | import { FormVariable } from "./form-variable"; 16 | import { FormTheme } from "./form-theme"; 17 | import { FormTemplate } from "./form-template"; 18 | import { FormScript } from './form-script'; 19 | import { Api } from './api'; 20 | 21 | 22 | export { 23 | /** 简单坐标类 */ 24 | Location, 25 | /** 拖拽组件配置 */ 26 | DragConfig, 27 | /** 表单基本配置 */ 28 | FormConfig, 29 | /** 表单组件库 */ 30 | ComponentLibrary, 31 | /** 表单底部配置 */ 32 | FormFooterConfig, 33 | /** 设备类型 */ 34 | RemoteDevice, 35 | /** 组件类型分组 */ 36 | FormControlGroup, 37 | /** 包含一个控件列表的表单区域 */ 38 | FormPanel, 39 | /** 属性 */ 40 | FormControlProperty, 41 | /** 基础组件 */ 42 | BasicControl, 43 | /** 属性编辑器 */ 44 | PropertyEditor, 45 | /** 属性组 */ 46 | PropertyGroup, 47 | /** 表单控件 */ 48 | FormControl, 49 | /** 表单变量 */ 50 | FormVariable, 51 | /** 函数 */ 52 | FormFunction, 53 | /** 表单主题 */ 54 | FormTheme, 55 | /** 表单模板 */ 56 | FormTemplate, 57 | /** Vue代码 */ 58 | FormScript, 59 | /** 接口 */ 60 | Api 61 | }; -------------------------------------------------------------------------------- /src/components/control_antd/AntdFormDesignChildform.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 61 | 62 | -------------------------------------------------------------------------------- /src/components/FormDesignBlankControl.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 19 | 20 | -------------------------------------------------------------------------------- /src/components/editor/function-picker/FunctionPicker.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 72 | 73 | -------------------------------------------------------------------------------- /src/@types/vue.d.ts: -------------------------------------------------------------------------------- 1 | //公共函数库 2 | import * as common from '@/tools/common'; 3 | import { pageFilter, Breadcrumb, UserInfo, PagePagination } from "@/@types/basic.d"; 4 | //全局枚举 5 | // import '@/config/enum'; 6 | import { Vue } from 'vue-property-decorator'; 7 | import { Pagination } from 'ant-design-vue'; 8 | import Axios, { AxiosInstance } from 'axios'; 9 | import { Component } from 'vue'; 10 | import { VueCookies } from 'vue-cookies'; 11 | 12 | // Vue实例类型添加 13 | declare module 'vue/types/vue' { 14 | interface VueConstructor { 15 | /** 当前组件权限 */ 16 | permission: string | Array | Function; 17 | } 18 | 19 | /** 系统级配置 */ 20 | interface SysConfig { 21 | /** 系统标题 */ 22 | TITLE: string, 23 | /** 接口 */ 24 | INTERFACE: string, 25 | /** 编辑器后端接口 */ 26 | DESIGNER_INTERFACE: string, 27 | /** 客户系统后端接口 */ 28 | CLIENT_INTERFACE: string, 29 | /** 默认用户名 */ 30 | Default_username: string, 31 | /** 默认密码 */ 32 | Default_password: string, 33 | /** 权限接口 */ 34 | Permission_interface: string 35 | } 36 | 37 | interface Vue { 38 | _self: typeof Vue; 39 | /** 默认Axios实例接口(业务) */ 40 | $cookie: VueCookies; 41 | /** 公共函数库 */ 42 | $common: typeof common; 43 | /** 默认Axios实例接口(业务) */ 44 | $axios: AxiosInstance; 45 | /** 表单设计器后端接口 */ 46 | $api: AxiosInstance; 47 | /** 全局枚举 */ 48 | // $emum: typeof sysEnum; 49 | /** EventBus */ 50 | $bus: Vue; 51 | /** 设置主题 */ 52 | $setTheme(code: string): void; 53 | /** 系统级配置 */ 54 | $config: SysConfig; 55 | /** 当前组件权限 */ 56 | permission: string | Array | Function; 57 | 58 | /** 日期格式化 */ 59 | dateFormat(date: string | Date, format?: string): string; 60 | /** [root]获取用户信息 */ 61 | login(): void; 62 | /** [root]面包屑 */ 63 | breadcrumbSource: Array; 64 | /** [root]设置面包屑 */ 65 | setBreadcrumb(arr: Array): void; 66 | /** [root]获取分页器默认参数 */ 67 | getPagination(config?: object): PagePagination; 68 | } 69 | } -------------------------------------------------------------------------------- /src/config/components.ts: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import { 3 | Alert, 4 | Button, 5 | Breadcrumb, 6 | Pagination, 7 | DatePicker, 8 | Divider, 9 | Dropdown, 10 | Form, 11 | Icon, 12 | Input, 13 | InputNumber, 14 | Layout, 15 | message, 16 | Popconfirm, 17 | Radio, 18 | Table, 19 | Row, 20 | Col, 21 | Select, 22 | Slider, 23 | Tabs, 24 | Tag, 25 | Tooltip, 26 | Drawer, 27 | Upload, 28 | notification, 29 | LocaleProvider, 30 | Modal, 31 | Switch, 32 | List, 33 | Tree, 34 | Card, 35 | Collapse, 36 | Spin, 37 | Avatar, 38 | Menu, 39 | TreeSelect, 40 | Checkbox, 41 | Rate, 42 | AutoComplete, 43 | Popover, 44 | Anchor, 45 | } from 'ant-design-vue'; 46 | 47 | import InputSearch from 'ant-design-vue/lib/input'; 48 | 49 | import Empty from 'ant-design-vue/lib/empty'; 50 | 51 | Vue.prototype.$message = message; 52 | Vue.prototype.$notification = notification; 53 | Vue.prototype.$info = Modal.info; 54 | Vue.prototype.$success = Modal.success; 55 | Vue.prototype.$error = Modal.error; 56 | Vue.prototype.$warning = Modal.warning; 57 | Vue.prototype.$confirm = Modal.confirm; 58 | 59 | Vue.use(Alert); 60 | Vue.use(Button); 61 | Vue.use(Breadcrumb); 62 | Vue.use(Pagination); 63 | Vue.use(Divider); 64 | Vue.use(Dropdown); 65 | Vue.use(Form); 66 | Vue.use(Icon); 67 | Vue.use(Input); 68 | Vue.use(InputNumber); 69 | Vue.use(Layout); 70 | Vue.use(Popconfirm); 71 | Vue.use(Radio); 72 | Vue.use(Table); 73 | Vue.use(Row); 74 | Vue.use(Col); 75 | Vue.use(Select); 76 | Vue.use(Slider); 77 | Vue.use(Tabs); 78 | Vue.use(Tag); 79 | Vue.use(Tooltip); 80 | Vue.use(Drawer); 81 | Vue.use(Upload); 82 | Vue.use(LocaleProvider); 83 | Vue.use(Modal); 84 | Vue.use(DatePicker); 85 | Vue.use(Switch); 86 | Vue.use(List); 87 | Vue.use(Tree); 88 | Vue.use(Collapse); 89 | Vue.use(Empty); 90 | Vue.use(Card); 91 | Vue.use(Spin); 92 | Vue.use(Avatar); 93 | Vue.use(Menu); 94 | Vue.use(TreeSelect); 95 | Vue.use(Checkbox); 96 | Vue.use(Rate); 97 | Vue.use(AutoComplete); 98 | Vue.use(Popover); 99 | Vue.use(Anchor); 100 | 101 | Vue.component(InputSearch.name, InputSearch); 102 | Vue.component(Empty.name, Empty); -------------------------------------------------------------------------------- /src/formControls_uni.ts: -------------------------------------------------------------------------------- 1 | import FormDesign from '@/@types/form-design'; 2 | import { Enum } from '@/config/enum'; 3 | 4 | 5 | let formControls: Array = [ 6 | 7 | /** 8 | * 分页器 9 | */ 10 | { 11 | id: '', 12 | control: { 13 | control: 'uni-pagination', 14 | attrs: {}, 15 | events: {}, 16 | propAttrs: {}, 17 | slot: {}, 18 | defaultAttrs: {} 19 | }, 20 | name: 'pagination', 21 | icon: '', 22 | title: '分页器', 23 | type: Enum.FormControlType.else, 24 | childrenSlot: '.van-col', 25 | propertys: [ 26 | { 27 | name: 'show-icon', title: 'icon形式', default: '', 28 | group: Enum.FormControlPropertyGroup.style, editor: Enum.FormControlPropertyEditor.boolean 29 | }, { 30 | name: 'prev-text', title: '左侧文字', default: '', 31 | group: Enum.FormControlPropertyGroup.style, editor: Enum.FormControlPropertyEditor.singerLine 32 | }, { 33 | name: 'prev-text', title: '右侧文字', default: '', 34 | group: Enum.FormControlPropertyGroup.style, editor: Enum.FormControlPropertyEditor.singerLine 35 | }, { 36 | name: 'remark', title: '备注名', default: '', 37 | group: Enum.FormControlPropertyGroup.data, editor: Enum.FormControlPropertyEditor.singerLine 38 | } 39 | ], 40 | events: [ 41 | 42 | ] 43 | }, 44 | ] 45 | 46 | export function initUniControls() { 47 | return formControls.map(i => ({ 48 | ...i, 49 | control: { 50 | ...i.control, 51 | propertys: i.propertys.concat([ 52 | 53 | ]), 54 | // @ts-ignore 55 | attrs: Object.assign.apply({}, Object.entries(i.control.defaultAttrs) 56 | .map(([key, value]) => ({[key]:value})) 57 | .concat( 58 | i.propertys 59 | .filter(o => o.default !== undefined) 60 | .map(o => ({[o.name]: o.default})) 61 | ) 62 | .concat( 63 | Object.entries(i.control.attrs) 64 | .map(([key, value]) => ({[key]:value})) 65 | ) 66 | ) 67 | } 68 | })) 69 | } -------------------------------------------------------------------------------- /src/config/service.ts: -------------------------------------------------------------------------------- 1 | import { ServiceConfig } from '@/@types/form-design/service-config' 2 | import common, { post } from '@/tools/common'; 3 | import axios, { AxiosInstance } from 'axios'; 4 | import store from '@/config/store'; 5 | 6 | /** 基础数据URL */ 7 | const baseDataUrl: string = 'ExtenalApi/GetAllBasicData'; 8 | /** 视图数据URL */ 9 | const viewDataUrl: string = 'ExtenalApi/GetAllTablesAndViews'; 10 | 11 | /** 获取基础数据 */ 12 | function getBaseData(baseUrl?: string): Promise>> { 13 | return new Promise((resolve, reject) => { 14 | common.post((baseUrl || '') + baseDataUrl, { 15 | EnterpriseId: store.getters.getEnterpriseId, 16 | LanguageCulture: "zh-CN" 17 | }).then(d => { 18 | resolve(d?.map(i => ({ 19 | label: i.chName, 20 | value: i.id, 21 | })) || []); 22 | }).catch(err => { 23 | reject(err); 24 | }); 25 | }); 26 | } 27 | 28 | /** 获取视图数据 */ 29 | function getViewData(baseUrl?: string): Promise>> { 30 | return new Promise((resolve, reject) => { 31 | common.post((baseUrl || '') + viewDataUrl, { 32 | EnterpriseId: store.getters.getEnterpriseId, 33 | LanguageCulture: "zh-CN" 34 | }).then(d => { 35 | resolve(d?.bpmViewTables?.map(i => ({ 36 | label: `${i.name} [${i.schemaName}]`, 37 | value: i.schemaName, 38 | })) || []); 39 | }).catch(err => { 40 | reject(err); 41 | }); 42 | }); 43 | } 44 | 45 | /** 服务列表 */ 46 | class Service { 47 | constructor(baseUrl?: string) { 48 | if (baseUrl) { 49 | this._baseUrl = baseUrl; 50 | } 51 | 52 | setTimeout(() => { 53 | getBaseData(this._baseUrl).then(d => { 54 | this.baseData = d; 55 | }) 56 | getViewData(this._baseUrl).then(d => { 57 | this.viewData = d; 58 | }) 59 | }, 2000); 60 | } 61 | 62 | private _baseUrl: string = ''; 63 | /** 获取基础URL */ 64 | get baseUrl() { 65 | return this._baseUrl || axios.defaults.baseURL; 66 | } 67 | 68 | /** 基础数据 */ 69 | baseData: Array> = []; 70 | /** 视图数据 */ 71 | viewData: Array> = []; 72 | } 73 | 74 | const _service = new Service(); 75 | 76 | export default _service; -------------------------------------------------------------------------------- /src/components/control_antd/AntdFormDesignControlCustom.vue: -------------------------------------------------------------------------------- 1 | 62 | 63 | -------------------------------------------------------------------------------- /src/lib/monaco-language/sql/sql.contribution.js: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 'use strict'; 6 | import * as monaco from 'monaco-editor/esm/vs/editor/editor.api'; 7 | import { registerLanguage } from 'monaco-editor/esm/vs/basic-languages/_.contribution.js'; 8 | 9 | registerLanguage({ 10 | id: 'sql', 11 | extensions: ['.sql'], 12 | aliases: ['SQL'], 13 | loader: () => import('./sql.js') 14 | }); 15 | 16 | let sqlLanguage = require('./sql.js').language; 17 | 18 | monaco.languages.registerCompletionItemProvider('sql', { 19 | provideCompletionItems(model, position) { 20 | let textUntilPosition = model.getValueInRange({ 21 | startLineNumber: position.lineNumber, 22 | startColumn: 1, 23 | endLineNumber: position.lineNumber, 24 | endColumn: position.column 25 | }); 26 | let match = textUntilPosition.match(/(\S+)$/); 27 | if (!match) return []; 28 | match = match[0].toUpperCase(); 29 | let suggestions = []; 30 | sqlLanguage.keywords.forEach(item => { 31 | if (item.indexOf(match) !== -1) { 32 | suggestions.push({ 33 | label: item, 34 | kind: monaco.languages.CompletionItemKind.Keyword, 35 | insertText: item 36 | }); 37 | } 38 | }); 39 | sqlLanguage.operators.forEach(item => { 40 | if (item.indexOf(match) !== -1) { 41 | suggestions.push({ 42 | label: item, 43 | kind: monaco.languages.CompletionItemKind.Operator, 44 | insertText: item 45 | }); 46 | } 47 | }); 48 | sqlLanguage.builtinFunctions.forEach(item => { 49 | if (item.indexOf(match) !== -1) { 50 | suggestions.push({ 51 | label: item, 52 | kind: monaco.languages.CompletionItemKind.Function, 53 | insertText: item 54 | }); 55 | } 56 | }); 57 | return { 58 | suggestions: suggestions.concat(require('./sql.suggestions.js').suggestions), 59 | }; 60 | } 61 | }); -------------------------------------------------------------------------------- /src/lib/lifecycle/lifecycle.native.mjs: -------------------------------------------------------------------------------- 1 | /*! 2 | Copyright 2018 Google Inc. All Rights Reserved. 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | */ 15 | /*! lifecycle.native.mjs v0.1.1 */ 16 | class e extends Event{constructor(e,t){super(e),this.newState=t.newState,this.oldState=t.oldState,this.originalEvent=t.originalEvent}}const t="active",s="passive",i="hidden",a="frozen",n="terminated",r="object"==typeof safari&&safari.pushNotification,h="onpageshow"in self,o=["focus","blur","visibilitychange","freeze","resume","pageshow",h?"pagehide":"unload"],c=e=>(e.preventDefault(),e.returnValue="Are you sure?"),d=e=>e.reduce((e,t,s)=>(e[t]=s,e),{}),u=[[t,s,i,n],[t,s,i,a],[i,s,t],[a,i],[a,t],[a,s]].map(d),l=(e,t)=>{for(let s,i=0;s=u[i];++i){const i=s[e],a=s[t];if(i>=0&&a>=0&&a>i)return Object.keys(s).slice(i,a+1)}return[]},f=()=>document.visibilityState===i?i:document.hasFocus()?t:s;class v extends EventTarget{constructor(){super();const e=f();this.e=e,this.t=[],this.s=this.s.bind(this),o.forEach(e=>addEventListener(e,this.s,!0)),r&&addEventListener("beforeunload",e=>{this.i=setTimeout(()=>{e.defaultPrevented||e.returnValue.length>0||this.a(e,i)},0)})}get state(){return this.e}get pageWasDiscarded(){return document.wasDiscarded||!1}addUnsavedChanges(e){!this.t.indexOf(e)>-1&&(0===this.t.length&&addEventListener("beforeunload",c),this.t.push(e))}removeUnsavedChanges(e){const t=this.t.indexOf(e);t>-1&&(this.t.splice(t,1),0===this.t.length&&removeEventListener("beforeunload",c))}a(t,s){if(s!==this.e){const i=this.e,a=l(i,s);for(let s=0;s 2 |
3 | 4 | {{getText}} 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 87 | 88 | 90 | -------------------------------------------------------------------------------- /src/components/control_antd/AntdFormDesignControlSelect.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 80 | 81 | 93 | -------------------------------------------------------------------------------- /src/lib/scss-loader/sass.node.js: -------------------------------------------------------------------------------- 1 | /*! sass.js - v0.11.1 (f286436) - built 2019-10-20 2 | providing libsass 3.6.2 (4da7c4bd) 3 | via emscripten 1.38.31 (040e49a) 4 | */ 5 | var Sass = require('./sass.sync.js'); 6 | var fs = require('fs'); 7 | var path = require('path'); 8 | 9 | function fileExists(path) { 10 | var stat = fs.statSync(path); 11 | return stat && stat.isFile(); 12 | } 13 | 14 | function removeFileExtension(path) { 15 | return path.slice(0, path.lastIndexOf('.')); 16 | } 17 | 18 | function importFileToSass(path, done) { 19 | // any path must be relative to CWD to work in both environments (real FS, and emscripten FS) 20 | var requestedPath = './' + path; 21 | // figure out the *actual* path of the file 22 | var filesystemPath = Sass.findPathVariation(fileExists, requestedPath); 23 | if (!filesystemPath) { 24 | done({ 25 | error: 'File "' + requestedPath + '" not found', 26 | }); 27 | 28 | return; 29 | } 30 | 31 | // Make sure to omit the ".css" file extension when it was omitted in requestedPath. 32 | // This allow raw css imports. 33 | // see https://github.com/sass/libsass/pull/754 34 | var isRawCss = !requestedPath.endsWith('.css') && filesystemPath.endsWith('.css'); 35 | var targetPath = isRawCss ? removeFileExtension(filesystemPath) : filesystemPath; 36 | 37 | // write the file to emscripten FS so libsass internal FS handling 38 | // can engage the scss/sass switch, which apparently does not happen 39 | // for content provided through the importer callback directly 40 | var content = fs.readFileSync(filesystemPath, {encoding: 'utf8'}); 41 | Sass.writeFile(filesystemPath, content, function() { 42 | done({ 43 | path: targetPath, 44 | }); 45 | }); 46 | } 47 | 48 | function importerCallback(request, done) { 49 | importFileToSass(resolve(request), done); 50 | } 51 | 52 | function compileFile(path, options, callback) { 53 | if (!callback) { 54 | callback = options; 55 | options = {}; 56 | } 57 | 58 | Sass.importer(importerCallback); 59 | importFileToSass(path, function() { 60 | Sass.compileFile(path, options, callback); 61 | }); 62 | } 63 | 64 | function resolve(request) { 65 | // the request will not have the correct "resolved" path on Windows 66 | // see https://github.com/medialize/sass.js/issues/69 67 | // see https://github.com/medialize/sass.js/issues/86 68 | return path.normalize( 69 | path.join( 70 | // sass.js works in the "/sass/" directory, make that relative to CWD 71 | path.dirname(request.previous.replace(/^\/sass\//, '')), 72 | request.current 73 | ) 74 | ).replace(/\\/g, '/'); 75 | } 76 | 77 | compileFile.importFileToSass = importFileToSass; 78 | compileFile.Sass = Sass; 79 | 80 | module.exports = compileFile; 81 | -------------------------------------------------------------------------------- /src/components/control_antd/AntdFormDesignControlRadioGroup.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 85 | 86 | 98 | -------------------------------------------------------------------------------- /src/lib/lifecycle/lifecycle.mjs: -------------------------------------------------------------------------------- 1 | /*! 2 | Copyright 2018 Google Inc. All Rights Reserved. 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | */ 15 | /*! lifecycle.mjs v0.1.1 */ 16 | let e;try{new EventTarget,e=!0}catch(t){e=!1}class t{constructor(){this.e={}}addEventListener(e,t,s=!1){this.t(e).push(t)}removeEventListener(e,t,s=!1){const i=this.t(e),a=i.indexOf(t);a>-1&&i.splice(a,1)}dispatchEvent(e){return e.target=this,Object.freeze(e),this.t(e.type).forEach(t=>t(e)),!0}t(e){return this.e[e]=this.e[e]||[]}}var s=e?EventTarget:t;class i{constructor(e){this.type=e}}var a=e?Event:i;class n extends a{constructor(e,t){super(e),this.newState=t.newState,this.oldState=t.oldState,this.originalEvent=t.originalEvent}}const r="active",h="passive",c="hidden",o="frozen",d="terminated",u="object"==typeof safari&&safari.pushNotification,v="onpageshow"in self,l=["focus","blur","visibilitychange","freeze","resume","pageshow",v?"pagehide":"unload"],g=e=>(e.preventDefault(),e.returnValue="Are you sure?"),f=e=>e.reduce((e,t,s)=>(e[t]=s,e),{}),b=[[r,h,c,d],[r,h,c,o],[c,h,r],[o,c],[o,r],[o,h]].map(f),p=(e,t)=>{for(let s,i=0;s=b[i];++i){const i=s[e],a=s[t];if(i>=0&&a>=0&&a>i)return Object.keys(s).slice(i,a+1)}return[]},E=()=>document.visibilityState===c?c:document.hasFocus()?r:h;class m extends s{constructor(){super();const e=E();this.s=e,this.i=[],this.a=this.a.bind(this),l.forEach(e=>addEventListener(e,this.a,!0)),u&&addEventListener("beforeunload",e=>{this.n=setTimeout(()=>{e.defaultPrevented||e.returnValue.length>0||this.r(e,c)},0)})}get state(){return this.s}get pageWasDiscarded(){return document.wasDiscarded||!1}addUnsavedChanges(e){!this.i.indexOf(e)>-1&&(0===this.i.length&&addEventListener("beforeunload",g),this.i.push(e))}removeUnsavedChanges(e){const t=this.i.indexOf(e);t>-1&&(this.i.splice(t,1),0===this.i.length&&removeEventListener("beforeunload",g))}r(e,t){if(t!==this.s){const s=this.s,i=p(s,t);for(let t=0;t 2 |
3 | 10 | 11 | 12 | {{$attrs['btn-text']}} 13 | 14 |

{{$attrs['upload-tip']}}

15 |
16 |
17 | 18 | 19 | 98 | 99 | 106 | -------------------------------------------------------------------------------- /src/tools/createChart.js: -------------------------------------------------------------------------------- 1 | import G2 from '@antv/g2'; 2 | 3 | // 创建唯一的 ID 4 | let uniqueId = 0; 5 | function generateUniqueId() { 6 | return `el-g2-${uniqueId++}`; 7 | } 8 | 9 | export default function createG2(__operation) { 10 | return { 11 | data() { 12 | return { 13 | chart: null, 14 | chartId: generateUniqueId() 15 | }; 16 | }, 17 | props: { 18 | width: Number, 19 | height: { 20 | type: Number, 21 | default: 300 22 | }, 23 | data: { 24 | type: Array | Object, 25 | default: function() { 26 | return {}; 27 | } 28 | }, 29 | padding: { 30 | type: Array, 31 | default: () => [ 40, 30, 60, 60 ] 32 | }, 33 | plotCfg: { 34 | type: Object 35 | }, 36 | forceFit: { 37 | type: Boolean, 38 | default: () => true 39 | }, 40 | config: { 41 | type: Object, 42 | default: () => ({}) 43 | } 44 | }, 45 | watch: { 46 | data: function(newData, oldData) { 47 | if (newData !== oldData) { 48 | this.chart.changeData(newData); 49 | } 50 | }, 51 | width: function(val, oldVal) { 52 | if (val !== oldVal) { 53 | this.chart.changeSize(val, this.height); 54 | } 55 | }, 56 | height: function(val, oldVal) { 57 | if (val !== oldVal) { 58 | this.chart.changeSize(this.width, val); 59 | } 60 | } 61 | }, 62 | mounted() { 63 | this.$nextTick(() => { 64 | this.initChart(); 65 | }); 66 | }, 67 | beforeDestory() { 68 | this.chart.destroy(); 69 | this.chart = null; 70 | this.chartId = null; 71 | }, 72 | methods: { 73 | initChart() { 74 | const { width, height, data, plotCfg, forceFit, config } = this; 75 | 76 | const chart = new G2.Chart({ 77 | id: this.chartId, 78 | padding: this.padding, 79 | width, 80 | height, 81 | plotCfg, 82 | forceFit 83 | }); 84 | 85 | chart.source(data, config); 86 | __operation(chart); 87 | this.chart = chart; 88 | } 89 | }, 90 | render(createElement) { 91 | return createElement('div', { 92 | attrs: { 93 | id: this.chartId 94 | } 95 | }); 96 | } 97 | }; 98 | } 99 | -------------------------------------------------------------------------------- /tests/e2e/globals.js: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////////////////// 2 | // Refer to the entire list of global config settings here: 3 | // https://github.com/nightwatchjs/nightwatch/blob/master/lib/settings/defaults.js#L16 4 | // 5 | // More info on test globals: 6 | // https://nightwatchjs.org/gettingstarted/configuration/#test-globals 7 | // 8 | /////////////////////////////////////////////////////////////////////////////////// 9 | 10 | module.exports = { 11 | // this controls whether to abort the test execution when an assertion failed and skip the rest 12 | // it's being used in waitFor commands and expect assertions 13 | abortOnAssertionFailure: true, 14 | 15 | // this will overwrite the default polling interval (currently 500ms) for waitFor commands 16 | // and expect assertions that use retry 17 | waitForConditionPollInterval: 500, 18 | 19 | // default timeout value in milliseconds for waitFor commands and implicit waitFor value for 20 | // expect assertions 21 | waitForConditionTimeout: 5000, 22 | 23 | default: { 24 | /* 25 | The globals defined here are available everywhere in any test env 26 | */ 27 | /* 28 | myGlobal: function() { 29 | return 'I\'m a method'; 30 | } 31 | */ 32 | }, 33 | 34 | firefox: { 35 | /* 36 | The globals defined here are available only when the chrome testing env is being used 37 | i.e. when running with --env firefox 38 | */ 39 | /* 40 | * myGlobal: function() { 41 | * return 'Firefox specific global'; 42 | * } 43 | */ 44 | } 45 | 46 | ///////////////////////////////////////////////////////////////// 47 | // Global hooks 48 | // - simple functions which are executed as part of the test run 49 | // - take a callback argument which can be called when an async 50 | // async operation is finished 51 | ///////////////////////////////////////////////////////////////// 52 | /** 53 | * executed before the test run has started, so before a session is created 54 | */ 55 | /* 56 | before(cb) { 57 | //console.log('global before') 58 | cb(); 59 | }, 60 | */ 61 | 62 | /** 63 | * executed before every test suite has started 64 | */ 65 | /* 66 | beforeEach(browser, cb) { 67 | //console.log('global beforeEach') 68 | cb(); 69 | }, 70 | */ 71 | 72 | /** 73 | * executed after every test suite has ended 74 | */ 75 | /* 76 | afterEach(browser, cb) { 77 | browser.perform(function() { 78 | //console.log('global afterEach') 79 | cb(); 80 | }); 81 | }, 82 | */ 83 | 84 | /** 85 | * executed after the test run has finished 86 | */ 87 | /* 88 | after(cb) { 89 | //console.log('global after') 90 | cb(); 91 | }, 92 | */ 93 | 94 | ///////////////////////////////////////////////////////////////// 95 | // Global reporter 96 | // - define your own custom reporter 97 | ///////////////////////////////////////////////////////////////// 98 | /* 99 | reporter(results, cb) { 100 | cb(); 101 | } 102 | */ 103 | }; 104 | -------------------------------------------------------------------------------- /src/formDevices.ts: -------------------------------------------------------------------------------- 1 | import FormDesign from './@types/form-design'; 2 | import { Enum } from './config/enum'; 3 | 4 | /** 设备型号列表 */ 5 | export const remoteDevices: Array = [ 6 | /** 移动端设备 */ 7 | { 8 | code: 'galaxys5', 9 | type: Enum.FormType.mobile, 10 | name: 'Galaxy S5', 11 | width: 360, 12 | height: 640, 13 | pixelRatio: 3 14 | }, { 15 | code: 'pixel2', 16 | type: Enum.FormType.mobile, 17 | name: 'Pixel 2', 18 | width: 411, 19 | height: 731, 20 | pixelRatio: 2.625 21 | }, { 22 | code: 'pixel2xl', 23 | type: Enum.FormType.mobile, 24 | name: 'Pixel 2 XL', 25 | width: 411, 26 | height: 823, 27 | pixelRatio: 3.5 28 | }, { 29 | code: 'iphone5se', 30 | type: Enum.FormType.mobile, 31 | name: 'iPhone 5/SE', 32 | width: 320, 33 | height: 568, 34 | pixelRatio: 2 35 | }, { 36 | code: 'iphone678', 37 | type: Enum.FormType.mobile, 38 | name: 'iPhone 6/7/8', 39 | width: 375, 40 | height: 667, 41 | pixelRatio: 2 42 | }, { 43 | code: 'iphone678plus', 44 | type: Enum.FormType.mobile, 45 | name: 'iPhone 6/7/8 Plus', 46 | width: 414, 47 | height: 736, 48 | pixelRatio: 3 49 | }, { 50 | code: 'iphonex', 51 | type: Enum.FormType.mobile, 52 | name: 'iPhone X', 53 | width: 375, 54 | height: 812, 55 | pixelRatio: 3 56 | }, { 57 | code: 'ipad', 58 | type: Enum.FormType.mobile, 59 | name: 'iPad', 60 | width: 768, 61 | height: 1024, 62 | pixelRatio: 2 63 | }, { 64 | code: 'ipadpro', 65 | type: Enum.FormType.mobile, 66 | name: 'iPad Pro', 67 | width: 1024, 68 | height: 1366, 69 | pixelRatio: 2 70 | }, 71 | 72 | /** PC端设备 */ 73 | { 74 | code: 'xsmallpc', 75 | type: Enum.FormType.pc, 76 | name: '4:3小分辨率屏', 77 | width: 1024, 78 | height: 768, 79 | pixelRatio: 1 80 | }, { 81 | code: 'smallpc', 82 | type: Enum.FormType.pc, 83 | name: '小分辨率屏', 84 | width: 1366, 85 | height: 768, 86 | pixelRatio: 1 87 | }, { 88 | code: 'middlepc', 89 | type: Enum.FormType.pc, 90 | name: '中分辨率屏', 91 | width: 1600, 92 | height: 900, 93 | pixelRatio: 1 94 | }, { 95 | code: 'largepc', 96 | type: Enum.FormType.pc, 97 | name: '1080P屏', 98 | width: 1920, 99 | height: 1080, 100 | pixelRatio: 1 101 | }, { 102 | code: '2kpc', 103 | type: Enum.FormType.pc, 104 | name: '2K屏', 105 | width: 2560, 106 | height: 1440, 107 | pixelRatio: 1 108 | }, { 109 | code: '4kpc', 110 | type: Enum.FormType.pc, 111 | name: '4K屏', 112 | width: 3840, 113 | height: 2160, 114 | pixelRatio: 1 115 | } 116 | ]; 117 | 118 | export function initRemoteDevices(): Record { 119 | // @ts-ignore 120 | return Object.assign.apply({}, remoteDevices.map(i => ({ 121 | [i.code]: i 122 | }))); 123 | } -------------------------------------------------------------------------------- /src/components/editor/color-picker/ColorPickerSlider.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 74 | 75 | -------------------------------------------------------------------------------- /src/assets/scss/task-list.scss: -------------------------------------------------------------------------------- 1 | @import 'variables.scss'; 2 | 3 | .task-list { 4 | background: #EEEEEE; 5 | 6 | > .task-list-tools { 7 | padding: 6px; 8 | } 9 | } 10 | 11 | .ant-table-wrapper { 12 | 13 | .ant-pagination { 14 | display: block; 15 | clear: both; 16 | float: initial; 17 | text-align: right; 18 | padding: 4px 10px; 19 | margin: 4px 0px; 20 | background: white; 21 | 22 | > .ant-pagination-options { 23 | float: left; 24 | } 25 | 26 | .ant-pagination-item, 27 | .ant-pagination-prev, 28 | .ant-pagination-next { 29 | border: 1px solid #EEE !important; 30 | margin-right: 6px; 31 | line-height: 24px; 32 | border-radius: 0px; 33 | 34 | &:not(.ant-pagination-disabled):hover { 35 | background-color: #EEE; 36 | } 37 | 38 | > a { 39 | margin: 0 10px; 40 | } 41 | 42 | &.ant-pagination-item-active { 43 | border: none !important; 44 | background-color: $-primary-color; 45 | 46 | &:hover { 47 | background-color: $-primary-color; 48 | } 49 | 50 | > a { 51 | color: white !important; 52 | } 53 | } 54 | } 55 | } 56 | } 57 | 58 | .ant-table { 59 | background-color: white; 60 | 61 | .task-table-header { 62 | 63 | > th { 64 | background: #FAFAFA; 65 | line-height: 12px; 66 | font-size: 12px; 67 | text-align: center; 68 | padding: 7px !important; 69 | } 70 | } 71 | 72 | .task-table-row { 73 | 74 | > td { 75 | line-height: 12px; 76 | font-size: 12px; 77 | padding: 5px !important; 78 | } 79 | 80 | &:nth-child(2n) { 81 | 82 | > td { 83 | background-color: #fbfcfd; 84 | } 85 | } 86 | } 87 | 88 | /** 整个单元格的选中效果 */ 89 | .ant-table-selection-column { 90 | position: relative; 91 | 92 | .ant-radio-wrapper, 93 | .ant-checkbox-wrapper { 94 | 95 | &:before { 96 | content: ''; 97 | position: absolute; 98 | top: 0px; 99 | left: 0px; 100 | width: 100%; 101 | height: 100%; 102 | } 103 | } 104 | } 105 | } 106 | 107 | .task-btn { 108 | cursor: pointer; 109 | user-select: none; 110 | display: inline-block; 111 | color: white; 112 | background: linear-gradient(#426C9B, #5E89B5) !important; 113 | box-shadow: 0 1px 3px rgba(0,0,0,.1), 0 1px 2px rgba(0,0,0,.18); 114 | margin-bottom: 0; 115 | font-weight: 400; 116 | line-height: 1.44; 117 | touch-action: manipulation; 118 | text-align: center; 119 | vertical-align: middle; 120 | transition: 0.15s box-shadow; 121 | font-size: 12px; 122 | border: none; 123 | border-radius: 0px; 124 | 125 | > span { 126 | font-weight: 400; 127 | line-height: 1.44; 128 | font-size: 12px; 129 | } 130 | 131 | &:hover { 132 | box-shadow: 0 3px 6px rgba(0,0,0,.2), 0 3px 6px rgba(0,0,0,.26); 133 | } 134 | 135 | + .task-btn { 136 | margin-left: 6px; 137 | } 138 | } -------------------------------------------------------------------------------- /src/components/control_antd/AntdFormDesignComplexChildform.vue: -------------------------------------------------------------------------------- 1 | 26 | 27 | 94 | 95 | -------------------------------------------------------------------------------- /src/components/control_uni/UniFormDesignControl.vue: -------------------------------------------------------------------------------- 1 | 57 | 58 | 85 | 86 | -------------------------------------------------------------------------------- /src/components/control_vant/VantFormDesignControl.vue: -------------------------------------------------------------------------------- 1 | 57 | 58 | 85 | 86 | -------------------------------------------------------------------------------- /src/components/editor/api-editor/ApiEditor.vue: -------------------------------------------------------------------------------- 1 | 30 | 31 | 120 | 121 | -------------------------------------------------------------------------------- /src/assets/scss/_system.scss: -------------------------------------------------------------------------------- 1 | html { 2 | overflow-y: auto; 3 | } 4 | 5 | .main-tool-btns { 6 | position: absolute; 7 | top: -44px; 8 | right: 0px; 9 | } 10 | 11 | .main-content { 12 | background: #fff; 13 | padding: 24px; 14 | margin: 0; 15 | min-height: 280px; 16 | } 17 | 18 | 19 | .ant-form-item { 20 | margin-bottom: 10px !important; 21 | 22 | /** 较小下边距表单 */ 23 | &.ant-form-item-mini { 24 | margin-bottom: 10px !important; 25 | } 26 | } 27 | 28 | .bottom-btn-list { 29 | position: absolute; 30 | left: 0; 31 | bottom: 0; 32 | width: 100%; 33 | border-top: 1px solid #e9e9e9; 34 | padding: 10px 16px; 35 | background: #fff; 36 | text-align: right; 37 | 38 | > .ant-btn { 39 | margin-left: 15px; 40 | } 41 | } 42 | 43 | // 头部按钮列表 44 | .header-tools { 45 | float: right; 46 | 47 | > button:last-child { 48 | margin-right: 0px; 49 | } 50 | } 51 | 52 | // 分割按钮 53 | .split-icon { 54 | text-align: center; 55 | background-image: linear-gradient(#FFF 0px, #b6dcf6 1px); 56 | background-size: 20% 1px; 57 | background-repeat: no-repeat; 58 | background-position: 50%; 59 | 60 | > i { 61 | cursor: pointer; 62 | margin-left: 8px; 63 | font-size: 20px; 64 | background: white; 65 | padding: 10px; 66 | color: $-primary-color; 67 | } 68 | } 69 | 70 | .van-col { 71 | position: relative; 72 | } 73 | 74 | .hidden { 75 | display: none !important; 76 | } 77 | 78 | ::-webkit-scrollbar { 79 | width: 10px; 80 | height: 10px; 81 | } 82 | ::-webkit-scrollbar-button { 83 | width: 0; 84 | height: 0; 85 | } 86 | ::-webkit-scrollbar-button:start:decrement, 87 | ::-webkit-scrollbar-button:end:increment { 88 | display: block; 89 | } 90 | ::-webkit-scrollbar-button:vertical:start:increment, 91 | ::-webkit-scrollbar-button:vertical:end:decrement { 92 | display: none; 93 | } 94 | ::-webkit-scrollbar-track:vertical, 95 | ::-webkit-scrollbar-track:horizontal, 96 | ::-webkit-scrollbar-thumb:vertical, 97 | ::-webkit-scrollbar-thumb:horizontal, 98 | ::-webkit-scrollbar-track:vertical, 99 | ::-webkit-scrollbar-track:horizontal, 100 | ::-webkit-scrollbar-thumb:vertical, 101 | ::-webkit-scrollbar-thumb:horizontal { 102 | border: transparent; 103 | border-style: solid; 104 | } 105 | ::-webkit-scrollbar-track:vertical::-webkit-scrollbar-track:horizontal { 106 | background: #fff; 107 | background-clip: padding-box; 108 | } 109 | ::-webkit-scrollbar-thumb { 110 | min-height: 28px; 111 | padding-top: 100; 112 | background: rgba(0, 0, 0, .2); 113 | background-clip: padding-box; 114 | border-radius: 5px; 115 | box-shadow: inset 1px 1px 0 rgba(0, 0, 0, .1), inset 0 -1px 0 rgba(0, 0, 0, .07); 116 | -webkit-box-shadow: inset 1px 1px 0 rgba(0, 0, 0, .1), inset 0 -1px 0 rgba(0, 0, 0, .07); 117 | } 118 | ::-webkit-scrollbar-thumb:hover { 119 | background: rgba(0, 0, 0, .4); 120 | box-shadow: inset 1px 1px 1px rgba(0, 0, 0, .25); 121 | -webkit-box-shadow: inset 1px 1px 1px rgba(0, 0, 0, .25); 122 | } 123 | ::-webkit-scrollbar-thumb:active { 124 | background: rgba(0, 0, 0, .5); 125 | box-shadow: inset 1px 1px 3px rgba(0, 0, 0, .35); 126 | -webkit-box-shadow: inset 1px 1px 3px rgba(0, 0, 0, .35); 127 | } 128 | ::-webkit-scrollbar-track:vertical, 129 | ::-webkit-scrollbar-track:horizontal, 130 | ::-webkit-scrollbar-thumb:vertical, 131 | ::-webkit-scrollbar-thumb:horizontal { 132 | border-width: 0; 133 | } 134 | ::-webkit-scrollbar-track:hover { 135 | background: rgba(0, 0, 0, .05); 136 | box-shadow: inset 1px 0 0 rgba(0, 0, 0, .1); 137 | -webkit-box-shadow: inset 1px 0 0 rgba(0, 0, 0, .1); 138 | } 139 | ::-webkit-scrollbar-track:active { 140 | background: rgba(0, 0, 0, .05); 141 | box-shadow: inset 1px 0 0 rgba(0, 0, 0, .14), inset -1px -1px 0 rgba(0, 0, 0, .07); 142 | -webkit-box-shadow: inset 1px 0 0 rgba(0, 0, 0, .14), inset -1px -1px 0 rgba(0, 0, 0, .07); 143 | } 144 | 145 | .ant-input-group-wrapper { 146 | vertical-align: middle; 147 | } -------------------------------------------------------------------------------- /src/config/store.ts: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import Vuex from 'vuex'; 3 | import { UserInfo } from '@/@types/basic.d'; 4 | import { User } from 'model'; 5 | import FormDesign from '@/@types/form-design'; 6 | 7 | Vue.use(Vuex); 8 | 9 | export default new Vuex.Store({ 10 | state: { 11 | permissions: [ 12 | { 13 | id: -1, 14 | code: '', 15 | name: '', 16 | remark: '' 17 | } 18 | ], 19 | /** 用户信息 */ 20 | userInfo: { 21 | /** 用户Id */ 22 | userid: '', 23 | /** 用户账号 */ 24 | useraccount: '', 25 | /** 企业Id */ 26 | enterpriseid: '' 27 | }, 28 | formVariables: [] as Array, 29 | formFunctions: [] as Array, 30 | formScript: `return { 31 | data: { 32 | 33 | }, 34 | methods: { 35 | test() { 36 | 37 | } 38 | }, 39 | created() { 40 | 41 | } 42 | }`, 43 | formScriptComment: {} as Record, 44 | apiList: [ 45 | { 46 | name: '', 47 | type: 'post', 48 | address: '', 49 | remark: '' 50 | } 51 | ] as FormDesign.Api[] 52 | }, 53 | getters: { 54 | /** 获取用户信息 */ 55 | getUserInfo(state) { 56 | return state.userInfo || {}; 57 | }, 58 | /** 获取用户所有权限 */ 59 | allPermissions(state): Array { 60 | return state.permissions.map(i => i.code); 61 | }, 62 | /** 权限校验 */ 63 | checkPermissions: state => (value: undefined | string | Array): boolean => { 64 | let re = false; 65 | if(!value) { 66 | re = true; 67 | } else if(typeof(value) == 'string') { 68 | re = !!state.permissions.find(i => i && i.code === value); 69 | } else if(value instanceof Array) { 70 | re = value.every(val => !!state.permissions.find(i => i && i.code === val)); 71 | } 72 | return re; 73 | }, 74 | getFormScriptCode(state): string { 75 | return state.formScript; 76 | }, 77 | getFormScript(state): FormDesign.FormScript { 78 | return Function(state.formScript)() as FormDesign.FormScript; 79 | }, 80 | getFormScriptComment(state): Record { 81 | let _reg = /(\/\*\*\s*(?.*?)\s*\*\/)?\s*(?[a-zA-Z0-9_]+)(:|\()/gs; 82 | let _item: RegExpExecArray | null; 83 | let _list: Record = {}; 84 | while(_item = _reg.exec(state.formScript)) { 85 | if (_item !== null) { 86 | _list[_item.groups?.name || ''] = _item.groups?.remark || ''; 87 | } 88 | } 89 | return _list; 90 | }, 91 | getApiList(state) { 92 | return state.apiList; 93 | }, 94 | /** 获取用户Id */ 95 | getUserId(state) { 96 | return state.userInfo?.userid ?? ''; 97 | }, 98 | /** 获取用户账号 */ 99 | getUserAccount(state) { 100 | return state.userInfo?.useraccount ?? ''; 101 | }, 102 | /** 获取企业Id */ 103 | getEnterpriseId(state) { 104 | return state.userInfo?.enterpriseid ?? ''; 105 | }, 106 | /** 获取SysCode,暂时写死为SRM */ 107 | getSysCode(state) { 108 | return 'SRM'; 109 | } 110 | }, 111 | mutations: { 112 | setPermissions(state, permissions) { 113 | state.permissions = permissions; 114 | }, 115 | setUserInfo(state, userInfo) { 116 | state.userInfo = userInfo; 117 | }, 118 | setFormScript(state, script) { 119 | state.formScript = script; 120 | }, 121 | setApiList(state, apiList) { 122 | state.apiList = apiList; 123 | } 124 | }, 125 | actions: {} 126 | }); 127 | -------------------------------------------------------------------------------- /vue.config.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack'); 2 | const path = require('path'); 3 | const MonacoWebpackPlugin = require('monaco-editor-webpack-plugin'); 4 | // const ExtractTextPlugin = require('extract-text-webpack-plugin'); 5 | 6 | module.exports = { 7 | devServer: { 8 | disableHostCheck: true 9 | }, 10 | runtimeCompiler: true, 11 | chainWebpack(config) { 12 | // 换肤loader[scss] 13 | const scss = config.module.rule('scss').toConfig(); 14 | const useable_scss = { ...scss.oneOf[3], test: /\.lazy\.scss$/ }; 15 | useable_scss.use = [...useable_scss.use]; 16 | useable_scss.use[0] = { loader: 'style-loader', options: { injectType: 'lazySingletonStyleTag' } }; 17 | config.module.rule('scss').merge({ 18 | oneOf: [useable_scss] 19 | }); 20 | 21 | // 换肤loader[less] 22 | const less = config.module.rule('less').toConfig(); 23 | const useable_less = { ...less.oneOf[3], test: /\.lazy\.less$/ }; 24 | useable_less.use = [...useable_less.use]; 25 | useable_less.use[0] = { loader: 'style-loader', options: { injectType: 'lazySingletonStyleTag' } }; 26 | config.module.rule('less').merge({ 27 | oneOf: [useable_less] 28 | }); 29 | }, 30 | configureWebpack: { 31 | module: { 32 | rules: [ 33 | { 34 | test: /\.ts?$/, 35 | loader: 'assemblyscript-typescript-loader', 36 | include: /assembly/, //to avoid a conflict with other ts file who use 'ts-load',so you can division them with prop 'include' 37 | options: { 38 | limit: 1000, 39 | name: `static/assembly/[name].[hash:8].wasm` 40 | } 41 | } 42 | ] 43 | }, 44 | resolve: { 45 | alias: { 46 | '@ant-design/icons/lib/dist$': path.resolve(__dirname, './src/config/icons.ts') 47 | } 48 | }, 49 | plugins: [ 50 | new webpack.ContextReplacementPlugin(/moment[/\\]locale$/, /zh-cn|jp/), 51 | new MonacoWebpackPlugin({ 52 | languages: ['javascript', 'typescript', 'css', 'scss', 'html', 'json', 'csharp'] 53 | }) 54 | // new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/) 55 | ], 56 | optimization: { 57 | splitChunks: { 58 | chunks: 'all', 59 | cacheGroups: { 60 | libs: { 61 | name: 'chunk-libs', 62 | test: /[\\/]node_modules[\\/]/, 63 | priority: 10, 64 | chunks: 'initial' // only package third parties that are initially dependent 65 | }, 66 | elementUI: { 67 | name: 'chunk-antDesign', 68 | priority: 20, // the weight needs to be larger than libs and app or it will be packaged into libs or app 69 | test: /[\\/]node_modules[\\/]_?ant-design-vue(.*)/ // in order to adapt to cnpm 70 | }, 71 | commons: { 72 | name: 'chunk-commons', 73 | test: path.resolve(__dirname, './src/components'), 74 | minChunks: 3, 75 | priority: 5, 76 | reuseExistingChunk: true 77 | } 78 | } 79 | } 80 | } 81 | }, 82 | css: { 83 | sourceMap: process.env.NODE_ENV !== 'production', 84 | requireModuleExtension: true, 85 | loaderOptions: { 86 | less: { 87 | javascriptEnabled: true 88 | }, 89 | scss: { 90 | prependData: `@import "~@/assets/scss/variables.scss";` 91 | } 92 | } 93 | }, 94 | 95 | pwa: { 96 | name: 'demo', 97 | msTileColor: '#42B983' 98 | }, 99 | 100 | pluginOptions: { 101 | webpackBundleAnalyzer: { 102 | openAnalyzer: false 103 | }, 104 | apollo: { 105 | enableMocks: true, 106 | enableEngine: true 107 | } 108 | } 109 | }; 110 | -------------------------------------------------------------------------------- /src/config/enum.ts: -------------------------------------------------------------------------------- 1 | export namespace Enum { 2 | /** 用户权限类型 */ 3 | export enum userRole { 4 | /** 管理员 */ 5 | manager, 6 | /** 用户 */ 7 | user, 8 | /** 游客 */ 9 | visitor 10 | } 11 | 12 | /** 表单类型 */ 13 | export enum FormType { 14 | /** 移动端 */ 15 | mobile = 'mobile', 16 | /** PC端 */ 17 | pc = 'pc' 18 | } 19 | 20 | /** 表单组件库 */ 21 | export enum formComponentLib { 22 | vant = 'vant', 23 | antDesignVue = 'ant-design-vue' 24 | } 25 | 26 | /** 控件类型 */ 27 | export enum FormControlType { 28 | /** 子表单控件 */ 29 | childform = 'childform', 30 | /** 布局控件 */ 31 | layout = 'layout', 32 | /** 输入控件 */ 33 | input = 'input', 34 | /** 选择控件 */ 35 | select = 'select', 36 | /** 上传控件 */ 37 | upload = 'upload', 38 | /** 隐藏控件 */ 39 | hidden = 'hidden', 40 | /** 其他控件 */ 41 | else = 'else' 42 | } 43 | 44 | /** 控件属性组 */ 45 | export enum FormControlPropertyGroup { 46 | /** Flex子项属性 */ 47 | flex = 'Flex子项属性', 48 | /** 表单分组 */ 49 | childform = '子表单属性', 50 | /** 表单分组 */ 51 | form = '表单属性', 52 | /** 外观分组 */ 53 | style = '外观', 54 | /** 行为分组 */ 55 | action = '行为', 56 | /** 数据源分组 */ 57 | data = '数据', 58 | /** 其他分组 */ 59 | else = '其他', 60 | /** 函数分组 */ 61 | function = '函数', 62 | } 63 | 64 | /** 属性布局方式 */ 65 | export enum PropertyLayout { 66 | /** 行内布局(默认) */ 67 | inline = 'inline', 68 | /** 整块布局 */ 69 | block = 'block' 70 | } 71 | 72 | /** 属性附加选项 */ 73 | export enum FormControlPropertyAttach { 74 | /** 计算表达式 */ 75 | expression = 'expression', 76 | /** 绑定变量 */ 77 | variable = 'variable', 78 | /** 系统-视图 Array */ 79 | view = 'view', 80 | /** 系统-基础数据 Record */ 81 | basicData = 'basic-data', 82 | /** 远端请求 */ 83 | request = 'request' 84 | } 85 | 86 | /** 表单类型 */ 87 | export enum FormControlRuleType { 88 | /** 文本填写框 */ 89 | text = 'text', 90 | /** 数字填写框 */ 91 | number = 'number', 92 | /** 选择框 */ 93 | select = 'select', 94 | /** 日期框 */ 95 | date = 'date', 96 | /** 文件上传框 */ 97 | upload = 'upload' 98 | } 99 | 100 | /** 属性编辑器 */ 101 | export enum FormControlPropertyEditor { 102 | /** 任意值 */ 103 | any = 'any', 104 | /** 单行文本 */ 105 | singerLine = 'singer-line', 106 | /** 多行文本 */ 107 | multiLine = 'multi-line', 108 | /** 布尔类型 */ 109 | boolean = 'boolean', 110 | /** 整数 */ 111 | int = 'int', 112 | /** 浮点数 */ 113 | float = 'float', 114 | /** 像素长度 */ 115 | pixel = 'pixel', 116 | /** 颜色 */ 117 | color = 'color', 118 | /** 文件大小 */ 119 | byte = 'byte', 120 | /** Json类型 */ 121 | json = 'json', 122 | /** Javascript类型 */ 123 | javascript = 'javascript', 124 | /** 图标类型(vant) */ 125 | icon = 'icon', 126 | /** 图标类型(antd) */ 127 | icon_antd = 'icon_antd', 128 | /** 列表型 */ 129 | list = 'list', 130 | /** 函数类型 */ 131 | function = 'function', 132 | /** 对象列表类型 */ 133 | modelList = 'model-list', 134 | /** 校验规则 */ 135 | rules = 'rules', 136 | /** 盒模型类型 */ 137 | box = 'box', 138 | /** API类型 */ 139 | api = 'api', 140 | 141 | /** 计算表达式 */ 142 | expression = 'expression', 143 | /** 绑定变量 */ 144 | variable = 'variable', 145 | /** 系统-视图 Array */ 146 | viewData = 'view-data', 147 | /** 系统-基础数据 Record */ 148 | basicData = 'basic-data', 149 | /** 远端请求 */ 150 | request = 'request' 151 | } 152 | 153 | /** 表单区域中的控件堆叠方向 */ 154 | export enum FormPanelDirection { 155 | /** 行堆叠 */ 156 | row = 'row', 157 | /** 反向行堆叠 */ 158 | rowReverse = 'row-reverse', 159 | /** 列堆叠 */ 160 | column = 'column', 161 | /** 反向列堆叠 */ 162 | columnReverse = 'column-reverse' 163 | } 164 | } -------------------------------------------------------------------------------- /src/components/editor/box-editor/BoxEditor.vue: -------------------------------------------------------------------------------- 1 | 25 | 26 | 66 | 67 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "haku-form-design", 3 | "description": "白箱表单设计器", 4 | "version": "0.1.0", 5 | "private": true, 6 | "scripts": { 7 | "serve": "vue-cli-service serve", 8 | "build": "vue-cli-service build --modern", 9 | "report": "vue-cli-service build --report", 10 | "test:unit": "vue-cli-service test:unit", 11 | "test:e2e": "vue-cli-service test:e2e", 12 | "lint": "vue-cli-service lint" 13 | }, 14 | "dependencies": { 15 | "@vue/babel-helper-vue-jsx-merge-props": "^1.0.0", 16 | "@vue/babel-preset-jsx": "^1.1.2", 17 | "ant-design-vue": "^1.4.10", 18 | "core-js": "^3.3.2", 19 | "hls.js": "^0.13.2-canary.4480", 20 | "js-base64": "^2.5.2", 21 | "qs": "^6.9.1", 22 | "vant": "^2.5.5", 23 | "vue": "^2.6.10", 24 | "vue-class-component": "^7.0.2", 25 | "vue-cookies": "^1.7.0", 26 | "vue-draggable-resizable": "^2.1.0", 27 | "vue-property-decorator": "^8.3.0", 28 | "vue-router": "^3.1.3", 29 | "vuex": "^3.0.1", 30 | "vuex-class": "^0.3.2" 31 | }, 32 | "devDependencies": { 33 | "@antv/g2": "^3.5.9", 34 | "@babel/plugin-proposal-nullish-coalescing-operator": "^7.4.4", 35 | "@babel/plugin-proposal-optional-chaining": "^7.6.0", 36 | "@types/chai": "^4.1.0", 37 | "@types/mocha": "^5.2.4", 38 | "@vue/cli-plugin-babel": "^4.0.0", 39 | "@vue/cli-plugin-e2e-nightwatch": "^4.0.0", 40 | "@vue/cli-plugin-eslint": "^4.0.0", 41 | "@vue/cli-plugin-router": "^4.0.0", 42 | "@vue/cli-plugin-typescript": "^4.0.0", 43 | "@vue/cli-plugin-unit-mocha": "^4.0.0", 44 | "@vue/cli-plugin-vuex": "^4.0.0", 45 | "@vue/cli-service": "^4.0.0", 46 | "@vue/eslint-config-prettier": "^5.0.0", 47 | "@vue/eslint-config-typescript": "^4.0.0", 48 | "@vue/test-utils": "1.0.0-beta.29", 49 | "antd-scss-theme-plugin": "^1.0.8", 50 | "axios": "^0.19.0", 51 | "chai": "^4.1.2", 52 | "chromedriver": "78", 53 | "dayjs": "^1.8.16", 54 | "eslint": "^5.16.0", 55 | "eslint-plugin-prettier": "^3.1.1", 56 | "eslint-plugin-vue": "^5.0.0", 57 | "geckodriver": "^1.19.0", 58 | "less": "^3.10.3", 59 | "less-loader": "^5.0.0", 60 | "monaco-editor": "^0.18.1", 61 | "monaco-editor-webpack-plugin": "^1.7.0", 62 | "prettier": "^1.18.2", 63 | "sass": "^1.19.0", 64 | "sass-loader": "^8.0.0", 65 | "style-loader": "^1.0.1", 66 | "typescript": "^3.7.2", 67 | "vue-i18n": "^8.15.0", 68 | "vue-template-compiler": "^2.6.10" 69 | }, 70 | "eslintConfig": { 71 | "root": true, 72 | "env": { 73 | "node": true 74 | }, 75 | "rules": { 76 | "no-multi-spaces": 0, 77 | "object-shorthand": 0, 78 | "no-console": 0, 79 | "no-debugger": 0, 80 | "quotes": 0, 81 | "quote-props": 0, 82 | "vue/script-indent": 0, 83 | "no-script-url": 1, 84 | "no-var": 1, 85 | "new-cap": 0, 86 | "no-magic-numbers": 0, 87 | "space-before-blocks": [ 88 | 0, 89 | "always" 90 | ], 91 | "space-before-function-paren": [ 92 | 0, 93 | "always" 94 | ], 95 | "no-param-reassign": 0, 96 | "newline-per-chained-call": 0, 97 | "vue/html-quotes": 1, 98 | "vue/no-multi-spaces": 0, 99 | "vue/no-confusing-v-for-v-if": 1, 100 | "vue/no-v-html": 1, 101 | "vue/no-unused-vars": 0, 102 | "no-unused-vars": 0 103 | }, 104 | "parserOptions": { 105 | "parser": "@typescript-eslint/parser", 106 | "ecmaFeatures": { 107 | "legacyDecorators": true 108 | } 109 | }, 110 | "extends": [ 111 | "plugin:vue/essential", 112 | "@vue/typescript" 113 | ], 114 | "globals": { 115 | "_": true, 116 | "$cookies": true 117 | }, 118 | "overrides": [ 119 | { 120 | "files": [ 121 | "**/__tests__/*.{j,t}s?(x)" 122 | ], 123 | "env": { 124 | "mocha": true 125 | } 126 | } 127 | ] 128 | }, 129 | "postcss": { 130 | "plugins": { 131 | "autoprefixer": {} 132 | } 133 | }, 134 | "browserslist": [ 135 | "> 1%", 136 | "last 2 versions" 137 | ] 138 | } 139 | -------------------------------------------------------------------------------- /src/lib/lifecycle/lifecycle.es5.js: -------------------------------------------------------------------------------- 1 | /*! 2 | Copyright 2018 Google Inc. All Rights Reserved. 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | */ 15 | /*! lifecycle.es5.js v0.1.1 */ 16 | !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):e.lifecycle=t()}(this,function(){"use strict";var e=void 0;try{new EventTarget,e=!1}catch(t){e=!1}var t="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},n=function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")},i=function(){function e(e,t){for(var n=0;n-1&&n.splice(i,1)}},{key:"dispatchEvent",value:function(e){return e.target=this,Object.freeze(e),this._getRegistry(e.type).forEach(function(t){return t(e)}),!0}},{key:"_getRegistry",value:function(e){return this._registry[e]=this._registry[e]||[]}}]),e}(),o=e?EventTarget:s,u=e?Event:function e(t){n(this,e),this.type=t},f=function(e){function t(e,i){n(this,t);var r=a(this,(t.__proto__||Object.getPrototypeOf(t)).call(this,e));return r.newState=i.newState,r.oldState=i.oldState,r.originalEvent=i.originalEvent,r}return r(t,u),t}(),c="active",h="passive",d="hidden",l="frozen",p="terminated",v="object"===("undefined"==typeof safari?"undefined":t(safari))&&safari.pushNotification,y=["focus","blur","visibilitychange","freeze","resume","pageshow","onpageshow"in self?"pagehide":"unload"],g=function(e){return e.preventDefault(),e.returnValue="Are you sure?"},_=[[c,h,d,p],[c,h,d,l],[d,h,c],[l,d],[l,c],[l,h]].map(function(e){return e.reduce(function(e,t,n){return e[t]=n,e},{})}),b=function(){return document.visibilityState===d?d:document.hasFocus()?c:h};return new(function(e){function t(){n(this,t);var e=a(this,(t.__proto__||Object.getPrototypeOf(t)).call(this)),i=b();return e._state=i,e._unsavedChanges=[],e._handleEvents=e._handleEvents.bind(e),y.forEach(function(t){return addEventListener(t,e._handleEvents,!0)}),v&&addEventListener("beforeunload",function(t){e._safariBeforeUnloadTimeout=setTimeout(function(){t.defaultPrevented||t.returnValue.length>0||e._dispatchChangesIfNeeded(t,d)},0)}),e}return r(t,o),i(t,[{key:"addUnsavedChanges",value:function(e){!this._unsavedChanges.indexOf(e)>-1&&(0===this._unsavedChanges.length&&addEventListener("beforeunload",g),this._unsavedChanges.push(e))}},{key:"removeUnsavedChanges",value:function(e){var t=this._unsavedChanges.indexOf(e);t>-1&&(this._unsavedChanges.splice(t,1),0===this._unsavedChanges.length&&removeEventListener("beforeunload",g))}},{key:"_dispatchChangesIfNeeded",value:function(e,t){if(t!==this._state)for(var n=function(e,t){for(var n,i=0;n=_[i];++i){var r=n[e],a=n[t];if(r>=0&&a>=0&&a>r)return Object.keys(n).slice(r,a+1)}return[]}(this._state,t),i=0;i 2 |
13 |
14 | {{control.control.attrs.title}} 15 |
16 | 24 | 25 |
26 |
27 | 28 | 33 | 37 | 38 | 39 | 40 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 |
56 | 57 | 58 | 126 | 127 | -------------------------------------------------------------------------------- /src/assets/scss/_task-form.scss: -------------------------------------------------------------------------------- 1 | .task-form { 2 | position: relative; 3 | background-color: white; 4 | margin: 0px 250px 0px 250px; 5 | box-sizing: border-box; 6 | min-height: 100%; 7 | box-shadow: 0 1px 5px #ddd, 0px 0px 0px 1000px #F7F7F7; 8 | 9 | > .task-form-body { 10 | position: relative; 11 | background-color: white; 12 | min-height: 100%; 13 | // border-radius: 4px; 14 | 15 | } 16 | 17 | .form-title { 18 | width: 100%; 19 | text-align: center; 20 | color: #333; 21 | font-size: 30px; 22 | margin-bottom: 30px; 23 | letter-spacing: 2px; 24 | } 25 | } 26 | 27 | .task-form-title { 28 | width: 100%; 29 | text-align: center; 30 | color: #333; 31 | font-size: 30px; 32 | margin-bottom: 20px; 33 | // font-weight: normal; 34 | letter-spacing: 2px; 35 | } 36 | 37 | .control-custom { 38 | box-sizing: border-box; 39 | width: 100%; 40 | } 41 | 42 | .ant-form-text { 43 | display: inline-block; 44 | padding-right: 8px; 45 | } 46 | 47 | .ant-form-item { 48 | 49 | /** 较小下边距表单 */ 50 | &.ant-form-item-mini { 51 | margin-bottom: 10px !important; 52 | 53 | > .ant-form-item-label { 54 | line-height: 30px; 55 | } 56 | 57 | .ant-form-item-control { 58 | line-height: 30px; 59 | } 60 | } 61 | } 62 | 63 | .form-control { 64 | position: relative; 65 | 66 | &.form-control-loading{ 67 | 68 | .ant-form-item-children { 69 | 70 | &:after { 71 | content: ''; 72 | position: absolute; 73 | cursor: wait; 74 | top: 0px; 75 | left: 0px; 76 | width: 100%; 77 | height: 100%; 78 | transition: 0.2s; 79 | border-radius: 6px; 80 | background-color: rgba(0, 0, 0, 0.04); 81 | z-index: 998; 82 | } 83 | 84 | > .ant-spin { 85 | 86 | > .ant-spin-dot { 87 | position: absolute; 88 | top: 0; 89 | right: 0; 90 | left: 0; 91 | bottom: 0; 92 | z-index: 999; 93 | } 94 | } 95 | } 96 | } 97 | 98 | 99 | 100 | } 101 | 102 | // 表单动作按钮列表 103 | .action-btns { 104 | margin-top: 20px; 105 | margin-bottom: 20px; 106 | text-align: center; 107 | 108 | > button + button { 109 | margin-left: 20px; 110 | } 111 | } 112 | 113 | .ant-table-thead { 114 | > tr { 115 | 116 | > th { 117 | 118 | > div { 119 | text-align: center; 120 | } 121 | } 122 | } 123 | } 124 | 125 | .ant-form-item-children { 126 | position: static; 127 | } 128 | 129 | .ant-anchor-wrapper { 130 | position: fixed; 131 | top: 50px; 132 | right: 120px; 133 | background-color: transparent; 134 | } 135 | 136 | .ant-form-item-label { 137 | user-select: none; 138 | } 139 | 140 | .selection-block-remark { 141 | position: absolute; 142 | display: block; 143 | top: 0px; 144 | left: 0px; 145 | width: 100%; 146 | height: 100%; 147 | line-height: 1.5; 148 | color: transparent; 149 | border: 1px solid transparent; 150 | border-color: transparent; 151 | font-size: inherit; 152 | font-family: inherit; 153 | font-weight: inherit; 154 | font-style: inherit; 155 | 156 | .remark-selection { 157 | background-color: coral; 158 | } 159 | 160 | > span { 161 | position: absolute; 162 | top: 0px; 163 | left: 0px; 164 | width: 100%; 165 | height: 100%; 166 | padding: inherit; 167 | line-height: inherit; 168 | } 169 | 170 | &.ant-input-sm { 171 | height: 24px; 172 | padding: 1px 7px; 173 | line-height: 24px; 174 | } 175 | 176 | &.remark-ant-input { 177 | padding: 0px 11px; 178 | height: 32px; 179 | margin-top: 4px; 180 | line-height: 32px; 181 | } 182 | 183 | &.ant-textarea { 184 | margin-top: 4px; 185 | margin-bottom: 4px; 186 | padding: 4px 11px; 187 | } 188 | } 189 | 190 | .control-custom, 191 | .form-label { 192 | position: relative; 193 | display: inline-block; 194 | line-height: 25px; 195 | user-select: text; 196 | z-index: 0; 197 | 198 | .selection-block-remark { 199 | border-width: 0px; 200 | line-height: 25px; 201 | z-index: -1; 202 | } 203 | } 204 | 205 | .ant-table { 206 | 207 | table { 208 | 209 | > .ant-table-tbody { 210 | 211 | > .ant-table-row { 212 | 213 | > td { 214 | vertical-align: top; 215 | } 216 | } 217 | } 218 | } 219 | } 220 | 221 | button.wfull { 222 | width: auto !important; 223 | } -------------------------------------------------------------------------------- /src/components/editor/variable-picker/VariablePicker.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 148 | 149 | -------------------------------------------------------------------------------- /src/components/taskform/TaskDetailTable.vue: -------------------------------------------------------------------------------- 1 | 52 | 53 | 140 | 141 | 150 | -------------------------------------------------------------------------------- /src/lib/haku-css/less/system.less: -------------------------------------------------------------------------------- 1 | // autoprefixer: > 5%; last 10 Chrome versions; not ie 6-8, main: main.less 2 | 3 | //页面宽度 4 | @page-width: 1180px; 5 | 6 | //普通圆角 7 | @border-radius: 5px; 8 | 9 | //系统色变量 10 | 11 | //图片相对路径 12 | @imgurl: "/img"; 13 | //标准字体 14 | @font-family-base: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', 'Hiragino Sans GB', 15 | 'Microsoft YaHei', 'Helvetica Neue', Helvetica, Arial, sans-serif, 'Apple Color Emoji', 16 | 'Segoe UI Emoji', 'Segoe UI Symbol'; 17 | //代码字体 18 | @font-family-code: Consolas, 'Courier New', 'Microsoft YaHei'; 19 | //图标字体 20 | @font-family-icon: 'Font Awesome 5 Pro'; 21 | 22 | //bg 23 | @page-bg: linear-gradient(to top, #accbee 0%, #e7f0fd 100%); 24 | 25 | //主色调:文字、块标准色 26 | @primary-color: #B01D2D; 27 | //主色调:背景、失效 28 | @primary-color-bg: color(~`colorPalette('@{primary-color}', 1) `); 29 | 30 | //成功色:标准 31 | @success-color: #1D87EA; 32 | //成功色:浅色、边线 33 | @success-color-bg: color(~`colorPalette('@{success-color}', 1) `); 34 | 35 | //警告色:标准 36 | @warning-color: #FFA726; 37 | //警告色:浅色、边线 38 | @warning-color-bg: color(~`colorPalette('@{warning-color}', 1) `); 39 | 40 | //错误色:标准 41 | @error-color: #F4511E; 42 | //错误色:浅色、边线 43 | @error-color-bg: color(~`colorPalette('@{error-color}', 1) `); 44 | 45 | //灰度:遮罩(白色文字) 46 | @color-gray-shade: #000000; 47 | //灰度:正文(白色文字) 48 | @color-gray-text: #333333; 49 | //灰度:辅助文字、悬浮状态边线(白色文字) 50 | @color-gray-assist-text: #999999; 51 | //灰度:失效文字(白色文字) 52 | @color-gray-disabled-text: #CCCCCC; 53 | //灰度:线框(标准色文字) 54 | @color-gray-border: #DDDDDD; 55 | //灰度:失效背景、表头背景(标准色文字) 56 | @color-gray-disabled-bg: #EEEEEE; 57 | //灰度:大背景(标准色文字) 58 | @color-gray-bg: #F6F6F6; 59 | //灰度:表格隔行显示背景(标准色文字) 60 | @color-gray-bg-even: #FAFAFA; 61 | 62 | //链接色:标准(反白文字/链接色文字) 63 | @color-link: #3498DB; 64 | //反白色(标准色文字) 65 | @white: #FFF; 66 | 67 | //微小尺寸:按钮(行内) 68 | @size-xs: 24px; 69 | //标准尺寸:按钮(行内) 70 | @size-sm: 30px; 71 | //中尺寸:按钮(行内) 72 | @size-md: 40px; 73 | //大尺寸:按钮(行内) 74 | @size-lg: 50px; 75 | 76 | //基础字号 77 | @font-size-base: 14px; 78 | //小号字体 79 | @font-size-sm: @font-size-base - 2px; 80 | //大号字体 81 | @font-size-lg: @font-size-base + 2px; 82 | 83 | //圆角:中 84 | @border-radius-sm : 2px; 85 | //圆角:大 86 | @border-radius-base : 4px; 87 | 88 | .border-top-radius(@radius) { 89 | border-top-right-radius: @radius; 90 | border-top-left-radius: @radius; 91 | } 92 | .border-right-radius(@radius) { 93 | border-bottom-right-radius: @radius; 94 | border-top-right-radius: @radius; 95 | } 96 | .border-bottom-radius(@radius) { 97 | border-bottom-right-radius: @radius; 98 | border-bottom-left-radius: @radius; 99 | } 100 | .border-left-radius(@radius) { 101 | border-bottom-left-radius: @radius; 102 | border-top-left-radius: @radius; 103 | } 104 | 105 | .fl { float: left !important; } 106 | .fr { float: right !important; } 107 | .fi { float: inherit !important; } 108 | 109 | .clear() { 110 | &:before, 111 | &:after { 112 | content: " "; 113 | display: table; 114 | visibility: hidden; 115 | } 116 | &:after { 117 | clear: both; 118 | } 119 | } 120 | 121 | .clear { 122 | .clear(); 123 | } 124 | 125 | .block { 126 | display: block !important; 127 | } 128 | .inline-block { 129 | display: inline-block !important; 130 | } 131 | 132 | .tal { text-align: left !important; } 133 | .tar { text-align: right !important; } 134 | .tac { text-align: center !important; } 135 | .tai { text-align: inherit !important; } 136 | 137 | .vat { vertical-align: top !important; } 138 | .vatt { vertical-align: text-top !important; } 139 | .vam { vertical-align: middle !important; } 140 | .vatb { vertical-align: text-bottom !important; } 141 | .vab { vertical-align: bottom !important; } 142 | .vai { vertical-align: inherit !important; } 143 | .vainit { vertical-align: initial !important; } 144 | 145 | .loop( @count )when( @count > 0 ){ 146 | .pt@{count} { padding-top: ( 1px * @count) !important; } 147 | .pb@{count} { padding-bottom: ( 1px * @count) !important; } 148 | .pl@{count} { padding-left: ( 1px * @count) !important; } 149 | .pr@{count} { padding-right: ( 1px * @count) !important; } 150 | .p@{count} { padding: ( 1px * @count) !important; } 151 | 152 | .mt@{count} { margin-top: ( 1px * @count) !important; } 153 | .mb@{count} { margin-bottom: ( 1px * @count) !important; } 154 | .ml@{count} { margin-left: ( 1px * @count) !important; } 155 | .mr@{count} { margin-right: ( 1px * @count) !important; } 156 | .m@{count} { margin: ( 1px * @count) !important; } 157 | 158 | .lh@{count} { line-height: ( 1px * @count) !important; min-height: ( 1px * @count); } 159 | 160 | .loop((@count - 5)); 161 | } 162 | 163 | .loop(100); 164 | 165 | .wauto { width: auto !important; } 166 | .hauto { height: auto !important; } 167 | .wfull { width: 100% !important; } 168 | .hfull { height: 100% !important; } 169 | .whalf { width: 50% !important; } 170 | .hhalf { height: 50% !important; } 171 | 172 | // .loop2( @count )when( @count > 0 ){ 173 | // .h@{count} { height: ( 1px * @count) !important; } 174 | // .minh@{count} { min-height: ( 1px * @count) !important; } 175 | // .maxh@{count} { max-height: ( 1px * @count) !important; } 176 | // .w@{count} { width: ( 1px * @count) !important; } 177 | // .minw@{count} { min-width: ( 1px * @count) !important; } 178 | // .maxw@{count} { max-width: ( 1px * @count) !important; } 179 | 180 | // .loop2((@count - 10)); 181 | // } 182 | 183 | // .loop2(500); 184 | 185 | //块元素水平居中 186 | .center-block() { 187 | display: block; 188 | margin-left: auto; 189 | margin-right: auto; 190 | } 191 | 192 | //设置元素大小 193 | .size(@width; @height) { 194 | width: @width; 195 | height: @height; 196 | } 197 | //设置正方形大小 198 | .square(@size) { 199 | .size(@size; @size); 200 | } 201 | 202 | //设置行内控件大小 203 | .size-xs() { 204 | min-height: @size-xs; 205 | font-size: @font-size-sm; 206 | } 207 | 208 | .size-sm() { 209 | min-height: @size-sm; 210 | font-size: @font-size-base; 211 | } 212 | 213 | .size-md() { 214 | min-height: @size-md; 215 | font-size: @font-size-lg; 216 | } 217 | 218 | //光标:链接 219 | .pointer { 220 | cursor: pointer !important; 221 | } -------------------------------------------------------------------------------- /src/lib/scss-loader/sass.js: -------------------------------------------------------------------------------- 1 | /*! sass.js - v0.11.1 (f286436) - built 2019-10-20 2 | providing libsass 3.6.2 (4da7c4bd) 3 | via emscripten 1.38.31 (040e49a) 4 | */ 5 | 6 | (function (root, factory) { 7 | 'use strict'; 8 | if (typeof define === 'function' && define.amd) { 9 | define([], factory); 10 | } else if (typeof exports === 'object') { 11 | module.exports = factory(); 12 | } else { 13 | root.Sass = factory(); 14 | } 15 | }(this, function () {/*global document*/ 16 | // identify the path sass.js is located at in case we're loaded by a simple 17 | // 18 | // this path can be used to identify the location of 19 | // * sass.worker.js from sass.js 20 | // * libsass.js.mem from sass.sync.js 21 | // see https://github.com/medialize/sass.js/pull/32#issuecomment-103142214 22 | // see https://github.com/medialize/sass.js/issues/33 23 | var SASSJS_RELATIVE_PATH = (function() { 24 | 'use strict'; 25 | 26 | // in Node things are rather simple 27 | var hasDir = typeof __dirname !== 'undefined'; 28 | if (hasDir) { 29 | return __dirname; 30 | } 31 | 32 | // we can only run this test in the browser, 33 | // so make sure we actually have a DOM to work with. 34 | if (typeof document === 'undefined' || !document.getElementsByTagName) { 35 | return null; 36 | } 37 | 38 | // http://www.2ality.com/2014/05/current-script.html 39 | var currentScript = document.currentScript || (function() { 40 | var scripts = document.getElementsByTagName('script'); 41 | return scripts[scripts.length - 1]; 42 | })(); 43 | 44 | var path = currentScript && currentScript.src; 45 | if (!path) { 46 | return null; 47 | } 48 | 49 | // [worker] make sure we're not running in some concatenated thing 50 | if (path.slice(-8) === '/sass.js') { 51 | return path.slice(0, -8); 52 | } 53 | 54 | // [sync] make sure we're not running in some concatenated thing 55 | if (path.slice(-13) === '/sass.sync.js') { 56 | return path.slice(0, -13); 57 | } 58 | 59 | return null; 60 | })() || '.'; 61 | 62 | /*global Worker, SASSJS_RELATIVE_PATH*/ 63 | 'use strict'; 64 | 65 | var noop = function(){}; 66 | var slice = [].slice; 67 | // defined upon first Sass.initialize() call 68 | var globalWorkerUrl; 69 | 70 | function Sass(workerUrl) { 71 | if (!workerUrl && !globalWorkerUrl) { 72 | /*jshint laxbreak:true */ 73 | throw new Error( 74 | 'Sass needs to be initialized with the URL of sass.worker.js - ' 75 | + 'either via Sass.setWorkerUrl(url) or by new Sass(url)' 76 | ); 77 | /*jshint laxbreak:false */ 78 | } 79 | 80 | if (!globalWorkerUrl) { 81 | globalWorkerUrl = workerUrl; 82 | } 83 | 84 | // bind all functions 85 | // we're doing this because we used to have a single hard-wired instance that allowed 86 | // [].map(Sass.removeFile) and we need to maintain that for now (at least until 1.0.0) 87 | for (var key in this) { 88 | if (typeof this[key] === 'function') { 89 | this[key] = this[key].bind(this); 90 | } 91 | } 92 | 93 | this._callbacks = {}; 94 | this._worker = new Worker(workerUrl || globalWorkerUrl); 95 | this._worker.addEventListener('message', this._handleWorkerMessage, false); 96 | } 97 | 98 | // allow setting the workerUrl before the first Sass instance is initialized, 99 | // where registering the global workerUrl would've happened automatically 100 | Sass.setWorkerUrl = function(workerUrl) { 101 | globalWorkerUrl = workerUrl; 102 | }; 103 | 104 | Sass.style = { 105 | nested: 0, 106 | expanded: 1, 107 | compact: 2, 108 | compressed: 3 109 | }; 110 | 111 | Sass.comments = { 112 | 'none': 0, 113 | 'default': 1 114 | }; 115 | 116 | Sass.prototype = { 117 | style: Sass.style, 118 | comments: Sass.comments, 119 | 120 | destroy: function() { 121 | this._worker && this._worker.terminate(); 122 | this._worker = null; 123 | this._callbacks = {}; 124 | this._importer = null; 125 | }, 126 | 127 | _handleWorkerMessage: function(event) { 128 | if (event.data.command) { 129 | this[event.data.command](event.data.args); 130 | } 131 | 132 | this._callbacks[event.data.id] && this._callbacks[event.data.id](event.data.result); 133 | delete this._callbacks[event.data.id]; 134 | }, 135 | 136 | _dispatch: function(options, callback) { 137 | if (!this._worker) { 138 | throw new Error('Sass worker has been terminated'); 139 | } 140 | 141 | options.id = 'cb' + Date.now() + Math.random(); 142 | this._callbacks[options.id] = callback; 143 | this._worker.postMessage(options); 144 | }, 145 | 146 | _importerInit: function(args) { 147 | // importer API done callback pushing results 148 | // back to the worker 149 | var done = function done(result) { 150 | this._worker.postMessage({ 151 | command: '_importerFinish', 152 | args: [result] 153 | }); 154 | }.bind(this); 155 | 156 | try { 157 | this._importer(args[0], done); 158 | } catch(e) { 159 | done({ error: e.message }); 160 | throw e; 161 | } 162 | }, 163 | 164 | importer: function(importerCallback, callback) { 165 | if (typeof importerCallback !== 'function' && importerCallback !== null) { 166 | throw new Error('importer callback must either be a function or null'); 167 | } 168 | 169 | // callback is executed in the main EventLoop 170 | this._importer = importerCallback; 171 | // tell worker to activate importer callback 172 | this._worker.postMessage({ 173 | command: 'importer', 174 | args: [Boolean(importerCallback)] 175 | }); 176 | 177 | callback && callback(); 178 | }, 179 | }; 180 | 181 | var commands = 'writeFile readFile listFiles removeFile clearFiles lazyFiles preloadFiles options compile compileFile'; 182 | commands.split(' ').forEach(function(command) { 183 | Sass.prototype[command] = function() { 184 | var callback = slice.call(arguments, -1)[0]; 185 | var args = slice.call(arguments, 0, -1); 186 | if (typeof callback !== 'function') { 187 | args.push(callback); 188 | callback = noop; 189 | } 190 | 191 | this._dispatch({ 192 | command: command, 193 | args: args 194 | }, callback); 195 | }; 196 | }); 197 | 198 | // automatically set the workerUrl in case we're loaded by a simple 199 | // 200 | // see https://github.com/medialize/sass.js/pull/32#issuecomment-103142214 201 | Sass.setWorkerUrl(SASSJS_RELATIVE_PATH + '/sass.worker.js'); 202 | return Sass; 203 | })); -------------------------------------------------------------------------------- /src/lib/haku-debug/index.js: -------------------------------------------------------------------------------- 1 | import Vue from "vue"; 2 | import router from "@/router"; 3 | 4 | const hakuDebug = { 5 | XMLHttpRequest: window.XMLHttpRequest, 6 | targetTypes_mousedown: ["a", "input", "button", "textarea", "i"], 7 | targetTypes_keydown: ["a", "input", "button", "textarea"], 8 | message: { 9 | JSERR: { code: "01", msg: "JS异常" }, 10 | EVENTERR: { code: "02", msg: "静态资源加载异常" }, 11 | AJAXERR: { code: "03", msg: "AJAX请求异常" }, 12 | AJAXTIMEOUTERR: { code: "04", msg: "AJAX请求超时" } 13 | }, 14 | config: {} 15 | }; 16 | 17 | /** 18 | * @method 将异常发送给服务器 19 | */ 20 | hakuDebug.send = function() { 21 | navigator.sendBeacon("/log", {}); 22 | }; 23 | 24 | /** 25 | * @method 通用XMLHttpRequest事件 26 | */ 27 | hakuDebug.XMLTYPE = function(event) { 28 | let target = event.target; 29 | 30 | if ("readystatechange" === event.type) { 31 | // console.log('请求状态码改变') 32 | if (target.readyState == 4) { 33 | if (target.status == 404) { 34 | console.log({ 35 | errMsg: "错误码:" + event.target.status, 36 | errUrl: target.responseURL, 37 | errType: hakuDebug.message.AJAXERR 38 | }); 39 | } 40 | } 41 | } 42 | 43 | if ("error" === event.typ) { 44 | // console.log('请求出错') 45 | console.log({ 46 | errMsg: "错误码:" + event.target.status, 47 | errUrl: target.responseURL, 48 | errType: hakuDebug.message.AJAXERR 49 | }); 50 | } 51 | 52 | if ("timeout" === event.type) { 53 | // console.log('请求超时') 54 | console.log({ 55 | errMsg: "错误码:" + event.target.status, 56 | errUrl: target.responseURL, 57 | errType: hakuDebug.message.AJAXTIMEOUTERR 58 | }); 59 | } 60 | }; 61 | 62 | /** 63 | * 计算加载时间 64 | */ 65 | hakuDebug.getPerformanceTiming = function() { 66 | let performance = window.performance; 67 | 68 | if (!performance) { 69 | // 当前浏览器不支持 70 | console.log("你的浏览器不支持 performance 接口"); 71 | return; 72 | } 73 | 74 | let t = performance.timing; 75 | let times = {}; 76 | 77 | //【重要】页面加载完成的时间 78 | //【原因】这几乎代表了用户等待页面可用的时间 79 | times.loadPage = t.loadEventEnd - t.navigationStart; 80 | 81 | //【重要】解析 DOM 树结构的时间 82 | //【原因】反省下你的 DOM 树嵌套是不是太多了! 83 | times.domReady = t.domComplete - t.responseEnd; 84 | 85 | //【重要】重定向的时间 86 | //【原因】拒绝重定向!比如,http://example.com/ 就不该写成 http://example.com 87 | times.redirect = t.redirectEnd - t.redirectStart; 88 | 89 | //【重要】DNS 查询时间 90 | //【原因】DNS 预加载做了么?页面内是不是使用了太多不同的域名导致域名查询的时间太长? 91 | // 可使用 HTML5 Prefetch 预查询 DNS ,见:[HTML5 prefetch](http://segmentfault.com/a/1190000000633364) 92 | times.lookupDomain = t.domainLookupEnd - t.domainLookupStart; 93 | 94 | //【重要】读取页面第一个字节的时间 95 | //【原因】这可以理解为用户拿到你的资源占用的时间,加异地机房了么,加CDN 处理了么?加带宽了么?加 CPU 运算速度了么? 96 | // TTFB 即 Time To First Byte 的意思 97 | // 维基百科:https://en.wikipedia.org/wiki/Time_To_First_Byte 98 | times.ttfb = t.responseStart - t.navigationStart; 99 | 100 | //【重要】内容加载完成的时间 101 | //【原因】页面内容经过 gzip 压缩了么,静态资源 css/js 等压缩了么? 102 | times.request = t.responseEnd - t.requestStart; 103 | 104 | //【重要】执行 onload 回调函数的时间 105 | //【原因】是否太多不必要的操作都放到 onload 回调函数里执行了,考虑过延迟加载、按需加载的策略么? 106 | times.loadEvent = t.loadEventEnd - t.loadEventStart; 107 | 108 | // DNS 缓存时间 109 | times.appcache = t.domainLookupStart - t.fetchStart; 110 | 111 | // 卸载页面的时间 112 | times.unloadEvent = t.unloadEventEnd - t.unloadEventStart; 113 | 114 | // TCP 建立连接完成握手的时间 115 | times.connect = t.connectEnd - t.connectStart; 116 | 117 | return times; 118 | }; 119 | 120 | //全局点击事件捕获 121 | window.addEventListener("mousedown", function(e) { 122 | if ( 123 | hakuDebug.targetTypes_mousedown.indexOf( 124 | e.target.tagName.toLowerCase() 125 | ) >= 0 126 | ) { 127 | console.log("鼠标点击", e); 128 | } 129 | }); 130 | 131 | //全局按键事件捕获 132 | window.addEventListener("keydown", function(e) { 133 | if ( 134 | [27, 13, 116].indexOf(e.keyCode) >= 0 && 135 | hakuDebug.targetTypes_keydown.indexOf(e.target.tagName.toLowerCase()) >= 136 | 0 137 | ) { 138 | console.log("键盘按键", e); 139 | } 140 | }); 141 | 142 | //全局页面跳转 143 | window.addEventListener("popstate", function(event) { 144 | console.log("页面跳转", event); 145 | }); 146 | 147 | //全局Vue路由跳转 148 | router.afterEach((to, from) => { 149 | //Vue路由中跳转 150 | console.log("Vue路由跳转", from, to); 151 | }); 152 | 153 | /******************************/ 154 | 155 | //全局js错误 156 | window.addEventListener("error", function(msg, url, row, col, error) { 157 | console.error("全局错误", msg, url, row, col, error); 158 | }); 159 | 160 | //监听静态资源加载错误 161 | window.addEventListener( 162 | "error", 163 | function(event) { 164 | let errorTarget = event.target; 165 | 166 | if (errorTarget && errorTarget.baseURI) { 167 | let a = { 168 | errMsg: errorTarget.outerHTML, 169 | errUrl: errorTarget.baseURI, 170 | errType: "" 171 | }; 172 | console.log(a); 173 | } 174 | }, 175 | true 176 | ); 177 | 178 | /** 179 | * 全局异步错误 180 | */ 181 | window.addEventListener("unhandledrejection", function(event) { 182 | console.error("异步错误", event); 183 | }); 184 | 185 | /** 186 | * Vue内部错误监控 187 | */ 188 | Vue.config.errorHandler = function(err, vm, info) { 189 | console.error("Vue内部错误", err, vm, info); 190 | }; 191 | 192 | /** 193 | * 监听页面所有AJAX请求 194 | */ 195 | window.XMLHttpRequest = function() { 196 | let XML = new hakuDebug.XMLHttpRequest(); 197 | XML.addEventListener("readystatechange", hakuDebug.XMLTYPE); 198 | XML.addEventListener("error", hakuDebug.XMLTYPE); 199 | XML.addEventListener("timeout", hakuDebug.XMLTYPE); 200 | XML.addEventListener("loadstart", hakuDebug.XMLTYPE); 201 | XML.addEventListener("loadend", hakuDebug.XMLTYPE); 202 | 203 | return XML; 204 | }; 205 | 206 | /** 207 | * 页面初始加载后自动计算加载时间 208 | */ 209 | window.addEventListener("load", function() { 210 | setTimeout(() => { 211 | console.log(hakuDebug.getPerformanceTiming()); 212 | }, 10); 213 | }); 214 | 215 | export default function(cfg) { 216 | if (cfg && toString.call(cfg) === "[object Object]") { 217 | hakuDebug.config = { 218 | ...hakuDebug.config, 219 | ...cfg 220 | }; 221 | } 222 | } 223 | --------------------------------------------------------------------------------