Commit d2420212a68106da906d7d820cc59d61327a76cc

Authored by YunaiV
1 parent 00f96865

增加 Form 编辑框

yudao-admin-ui/src/router/index.js
... ... @@ -149,7 +149,20 @@ export const constantRoutes = [
149 149 meta: { title: '请假表单-审批', icon: 'form' }
150 150 }
151 151 ]
152   - }
  152 + },
  153 + {
  154 + path: '/bpm',
  155 + component: Layout,
  156 + hidden: true,
  157 + children: [
  158 + {
  159 + path: 'manager/form/edit',
  160 + component: (resolve) => require(['@/views/bpm/form/build/index'], resolve),
  161 + name: '流程表单-编辑',
  162 + meta: { title: '流程表单-编辑' }
  163 + }
  164 + ]
  165 + },
153 166 ]
154 167  
155 168 export default new Router({
... ...
yudao-admin-ui/src/views/bpm/form/build/CodeTypeDialog.vue 0 → 100644
  1 +<template>
  2 + <div>
  3 + <el-dialog
  4 + v-bind="$attrs"
  5 + width="500px"
  6 + :close-on-click-modal="false"
  7 + :modal-append-to-body="false"
  8 + v-on="$listeners"
  9 + @open="onOpen"
  10 + @close="onClose"
  11 + >
  12 + <el-row :gutter="15">
  13 + <el-form
  14 + ref="elForm"
  15 + :model="formData"
  16 + :rules="rules"
  17 + size="medium"
  18 + label-width="100px"
  19 + >
  20 + <el-col :span="24">
  21 + <el-form-item label="生成类型" prop="type">
  22 + <el-radio-group v-model="formData.type">
  23 + <el-radio-button
  24 + v-for="(item, index) in typeOptions"
  25 + :key="index"
  26 + :label="item.value"
  27 + :disabled="item.disabled"
  28 + >
  29 + {{ item.label }}
  30 + </el-radio-button>
  31 + </el-radio-group>
  32 + </el-form-item>
  33 + <el-form-item v-if="showFileName" label="文件名" prop="fileName">
  34 + <el-input v-model="formData.fileName" placeholder="请输入文件名" clearable />
  35 + </el-form-item>
  36 + </el-col>
  37 + </el-form>
  38 + </el-row>
  39 +
  40 + <div slot="footer">
  41 + <el-button @click="close">
  42 + 取消
  43 + </el-button>
  44 + <el-button type="primary" @click="handelConfirm">
  45 + 确定
  46 + </el-button>
  47 + </div>
  48 + </el-dialog>
  49 + </div>
  50 +</template>
  51 +<script>
  52 +export default {
  53 + inheritAttrs: false,
  54 + props: ['showFileName'],
  55 + data() {
  56 + return {
  57 + formData: {
  58 + fileName: undefined,
  59 + type: 'file'
  60 + },
  61 + rules: {
  62 + fileName: [{
  63 + required: true,
  64 + message: '请输入文件名',
  65 + trigger: 'blur'
  66 + }],
  67 + type: [{
  68 + required: true,
  69 + message: '生成类型不能为空',
  70 + trigger: 'change'
  71 + }]
  72 + },
  73 + typeOptions: [{
  74 + label: '页面',
  75 + value: 'file'
  76 + }, {
  77 + label: '弹窗',
  78 + value: 'dialog'
  79 + }]
  80 + }
  81 + },
  82 + computed: {
  83 + },
  84 + watch: {},
  85 + mounted() {},
  86 + methods: {
  87 + onOpen() {
  88 + if (this.showFileName) {
  89 + this.formData.fileName = `${+new Date()}.vue`
  90 + }
  91 + },
  92 + onClose() {
  93 + },
  94 + close(e) {
  95 + this.$emit('update:visible', false)
  96 + },
  97 + handelConfirm() {
  98 + this.$refs.elForm.validate(valid => {
  99 + if (!valid) return
  100 + this.$emit('confirm', { ...this.formData })
  101 + this.close()
  102 + })
  103 + }
  104 + }
  105 +}
  106 +</script>
... ...
yudao-admin-ui/src/views/bpm/form/build/DraggableItem.vue 0 → 100644
  1 +<script>
  2 +import draggable from 'vuedraggable'
  3 +import render from '@/utils/generator/render'
  4 +
  5 +const components = {
  6 + itemBtns(h, element, index, parent) {
  7 + const { copyItem, deleteItem } = this.$listeners
  8 + return [
  9 + <span class="drawing-item-copy" title="复制" onClick={event => {
  10 + copyItem(element, parent); event.stopPropagation()
  11 + }}>
  12 + <i class="el-icon-copy-document" />
  13 + </span>,
  14 + <span class="drawing-item-delete" title="删除" onClick={event => {
  15 + deleteItem(index, parent); event.stopPropagation()
  16 + }}>
  17 + <i class="el-icon-delete" />
  18 + </span>
  19 + ]
  20 + }
  21 +}
  22 +const layouts = {
  23 + colFormItem(h, element, index, parent) {
  24 + const { activeItem } = this.$listeners
  25 + let className = this.activeId === element.formId ? 'drawing-item active-from-item' : 'drawing-item'
  26 + if (this.formConf.unFocusedComponentBorder) className += ' unfocus-bordered'
  27 + return (
  28 + <el-col span={element.span} class={className}
  29 + nativeOnClick={event => { activeItem(element); event.stopPropagation() }}>
  30 + <el-form-item label-width={element.labelWidth ? `${element.labelWidth}px` : null}
  31 + label={element.label} required={element.required}>
  32 + <render key={element.renderKey} conf={element} onInput={ event => {
  33 + this.$set(element, 'defaultValue', event)
  34 + }} />
  35 + </el-form-item>
  36 + {components.itemBtns.apply(this, arguments)}
  37 + </el-col>
  38 + )
  39 + },
  40 + rowFormItem(h, element, index, parent) {
  41 + const { activeItem } = this.$listeners
  42 + const className = this.activeId === element.formId ? 'drawing-row-item active-from-item' : 'drawing-row-item'
  43 + let child = renderChildren.apply(this, arguments)
  44 + if (element.type === 'flex') {
  45 + child = <el-row type={element.type} justify={element.justify} align={element.align}>
  46 + {child}
  47 + </el-row>
  48 + }
  49 + return (
  50 + <el-col span={element.span}>
  51 + <el-row gutter={element.gutter} class={className}
  52 + nativeOnClick={event => { activeItem(element); event.stopPropagation() }}>
  53 + <span class="component-name">{element.componentName}</span>
  54 + <draggable list={element.children} animation={340} group="componentsGroup" class="drag-wrapper">
  55 + {child}
  56 + </draggable>
  57 + {components.itemBtns.apply(this, arguments)}
  58 + </el-row>
  59 + </el-col>
  60 + )
  61 + }
  62 +}
  63 +
  64 +function renderChildren(h, element, index, parent) {
  65 + if (!Array.isArray(element.children)) return null
  66 + return element.children.map((el, i) => {
  67 + const layout = layouts[el.layout]
  68 + if (layout) {
  69 + return layout.call(this, h, el, i, element.children)
  70 + }
  71 + return layoutIsNotFound()
  72 + })
  73 +}
  74 +
  75 +function layoutIsNotFound() {
  76 + throw new Error(`没有与${this.element.layout}匹配的layout`)
  77 +}
  78 +
  79 +export default {
  80 + components: {
  81 + render,
  82 + draggable
  83 + },
  84 + props: [
  85 + 'element',
  86 + 'index',
  87 + 'drawingList',
  88 + 'activeId',
  89 + 'formConf'
  90 + ],
  91 + render(h) {
  92 + const layout = layouts[this.element.layout]
  93 +
  94 + if (layout) {
  95 + return layout.call(this, h, this.element, this.index, this.drawingList)
  96 + }
  97 + return layoutIsNotFound()
  98 + }
  99 +}
  100 +</script>
... ...
yudao-admin-ui/src/views/bpm/form/build/IconsDialog.vue 0 → 100644
  1 +<template>
  2 + <div class="icon-dialog">
  3 + <el-dialog
  4 + v-bind="$attrs"
  5 + width="980px"
  6 + :modal-append-to-body="false"
  7 + v-on="$listeners"
  8 + @open="onOpen"
  9 + @close="onClose"
  10 + >
  11 + <div slot="title">
  12 + 选择图标
  13 + <el-input
  14 + v-model="key"
  15 + size="mini"
  16 + :style="{width: '260px'}"
  17 + placeholder="请输入图标名称"
  18 + prefix-icon="el-icon-search"
  19 + clearable
  20 + />
  21 + </div>
  22 + <ul class="icon-ul">
  23 + <li
  24 + v-for="icon in iconList"
  25 + :key="icon"
  26 + :class="active===icon?'active-item':''"
  27 + @click="onSelect(icon)"
  28 + >
  29 + <i :class="icon" />
  30 + <div>{{ icon }}</div>
  31 + </li>
  32 + </ul>
  33 + </el-dialog>
  34 + </div>
  35 +</template>
  36 +<script>
  37 +import iconList from '@/utils/generator/icon.json'
  38 +
  39 +const originList = iconList.map(name => `el-icon-${name}`)
  40 +
  41 +export default {
  42 + inheritAttrs: false,
  43 + props: ['current'],
  44 + data() {
  45 + return {
  46 + iconList: originList,
  47 + active: null,
  48 + key: ''
  49 + }
  50 + },
  51 + watch: {
  52 + key(val) {
  53 + if (val) {
  54 + this.iconList = originList.filter(name => name.indexOf(val) > -1)
  55 + } else {
  56 + this.iconList = originList
  57 + }
  58 + }
  59 + },
  60 + methods: {
  61 + onOpen() {
  62 + this.active = this.current
  63 + this.key = ''
  64 + },
  65 + onClose() {},
  66 + onSelect(icon) {
  67 + this.active = icon
  68 + this.$emit('select', icon)
  69 + this.$emit('update:visible', false)
  70 + }
  71 + }
  72 +}
  73 +</script>
  74 +<style lang="scss" scoped>
  75 +.icon-ul {
  76 + margin: 0;
  77 + padding: 0;
  78 + font-size: 0;
  79 + li {
  80 + list-style-type: none;
  81 + text-align: center;
  82 + font-size: 14px;
  83 + display: inline-block;
  84 + width: 16.66%;
  85 + box-sizing: border-box;
  86 + height: 108px;
  87 + padding: 15px 6px 6px 6px;
  88 + cursor: pointer;
  89 + overflow: hidden;
  90 + &:hover {
  91 + background: #f2f2f2;
  92 + }
  93 + &.active-item{
  94 + background: #e1f3fb;
  95 + color: #7a6df0
  96 + }
  97 + > i {
  98 + font-size: 30px;
  99 + line-height: 50px;
  100 + }
  101 + }
  102 +}
  103 +.icon-dialog {
  104 + ::v-deep .el-dialog {
  105 + border-radius: 8px;
  106 + margin-bottom: 0;
  107 + margin-top: 4vh !important;
  108 + display: flex;
  109 + flex-direction: column;
  110 + max-height: 92vh;
  111 + overflow: hidden;
  112 + box-sizing: border-box;
  113 + .el-dialog__header {
  114 + padding-top: 14px;
  115 + }
  116 + .el-dialog__body {
  117 + margin: 0 20px 20px 20px;
  118 + padding: 0;
  119 + overflow: auto;
  120 + }
  121 + }
  122 +}
  123 +</style>
... ...
yudao-admin-ui/src/views/bpm/form/build/RightPanel.vue 0 → 100644
  1 +<template>
  2 + <div class="right-board">
  3 + <el-tabs v-model="currentTab" class="center-tabs">
  4 + <el-tab-pane label="组件属性" name="field" />
  5 + <el-tab-pane label="表单属性" name="form" />
  6 + </el-tabs>
  7 + <div class="field-box">
  8 + <a class="document-link" target="_blank" :href="documentLink" title="查看组件文档">
  9 + <i class="el-icon-link" />
  10 + </a>
  11 + <el-scrollbar class="right-scrollbar">
  12 + <!-- 组件属性 -->
  13 + <el-form v-show="currentTab==='field' && showField" size="small" label-width="90px">
  14 + <el-form-item v-if="activeData.changeTag" label="组件类型">
  15 + <el-select
  16 + v-model="activeData.tagIcon"
  17 + placeholder="请选择组件类型"
  18 + :style="{width: '100%'}"
  19 + @change="tagChange"
  20 + >
  21 + <el-option-group v-for="group in tagList" :key="group.label" :label="group.label">
  22 + <el-option
  23 + v-for="item in group.options"
  24 + :key="item.label"
  25 + :label="item.label"
  26 + :value="item.tagIcon"
  27 + >
  28 + <svg-icon class="node-icon" :icon-class="item.tagIcon" />
  29 + <span> {{ item.label }}</span>
  30 + </el-option>
  31 + </el-option-group>
  32 + </el-select>
  33 + </el-form-item>
  34 + <el-form-item v-if="activeData.vModel!==undefined" label="字段名">
  35 + <el-input v-model="activeData.vModel" placeholder="请输入字段名(v-model)" />
  36 + </el-form-item>
  37 + <el-form-item v-if="activeData.componentName!==undefined" label="组件名">
  38 + {{ activeData.componentName }}
  39 + </el-form-item>
  40 + <el-form-item v-if="activeData.label!==undefined" label="标题">
  41 + <el-input v-model="activeData.label" placeholder="请输入标题" />
  42 + </el-form-item>
  43 + <el-form-item v-if="activeData.placeholder!==undefined" label="占位提示">
  44 + <el-input v-model="activeData.placeholder" placeholder="请输入占位提示" />
  45 + </el-form-item>
  46 + <el-form-item v-if="activeData['start-placeholder']!==undefined" label="开始占位">
  47 + <el-input v-model="activeData['start-placeholder']" placeholder="请输入占位提示" />
  48 + </el-form-item>
  49 + <el-form-item v-if="activeData['end-placeholder']!==undefined" label="结束占位">
  50 + <el-input v-model="activeData['end-placeholder']" placeholder="请输入占位提示" />
  51 + </el-form-item>
  52 + <el-form-item v-if="activeData.span!==undefined" label="表单栅格">
  53 + <el-slider v-model="activeData.span" :max="24" :min="1" :marks="{12:''}" @change="spanChange" />
  54 + </el-form-item>
  55 + <el-form-item v-if="activeData.layout==='rowFormItem'" label="栅格间隔">
  56 + <el-input-number v-model="activeData.gutter" :min="0" placeholder="栅格间隔" />
  57 + </el-form-item>
  58 + <el-form-item v-if="activeData.layout==='rowFormItem'" label="布局模式">
  59 + <el-radio-group v-model="activeData.type">
  60 + <el-radio-button label="default" />
  61 + <el-radio-button label="flex" />
  62 + </el-radio-group>
  63 + </el-form-item>
  64 + <el-form-item v-if="activeData.justify!==undefined&&activeData.type==='flex'" label="水平排列">
  65 + <el-select v-model="activeData.justify" placeholder="请选择水平排列" :style="{width: '100%'}">
  66 + <el-option
  67 + v-for="(item, index) in justifyOptions"
  68 + :key="index"
  69 + :label="item.label"
  70 + :value="item.value"
  71 + />
  72 + </el-select>
  73 + </el-form-item>
  74 + <el-form-item v-if="activeData.align!==undefined&&activeData.type==='flex'" label="垂直排列">
  75 + <el-radio-group v-model="activeData.align">
  76 + <el-radio-button label="top" />
  77 + <el-radio-button label="middle" />
  78 + <el-radio-button label="bottom" />
  79 + </el-radio-group>
  80 + </el-form-item>
  81 + <el-form-item v-if="activeData.labelWidth!==undefined" label="标签宽度">
  82 + <el-input v-model.number="activeData.labelWidth" type="number" placeholder="请输入标签宽度" />
  83 + </el-form-item>
  84 + <el-form-item v-if="activeData.style&&activeData.style.width!==undefined" label="组件宽度">
  85 + <el-input v-model="activeData.style.width" placeholder="请输入组件宽度" clearable />
  86 + </el-form-item>
  87 + <el-form-item v-if="activeData.vModel!==undefined" label="默认值">
  88 + <el-input
  89 + :value="setDefaultValue(activeData.defaultValue)"
  90 + placeholder="请输入默认值"
  91 + @input="onDefaultValueInput"
  92 + />
  93 + </el-form-item>
  94 + <el-form-item v-if="activeData.tag==='el-checkbox-group'" label="至少应选">
  95 + <el-input-number
  96 + :value="activeData.min"
  97 + :min="0"
  98 + placeholder="至少应选"
  99 + @input="$set(activeData, 'min', $event?$event:undefined)"
  100 + />
  101 + </el-form-item>
  102 + <el-form-item v-if="activeData.tag==='el-checkbox-group'" label="最多可选">
  103 + <el-input-number
  104 + :value="activeData.max"
  105 + :min="0"
  106 + placeholder="最多可选"
  107 + @input="$set(activeData, 'max', $event?$event:undefined)"
  108 + />
  109 + </el-form-item>
  110 + <el-form-item v-if="activeData.prepend!==undefined" label="前缀">
  111 + <el-input v-model="activeData.prepend" placeholder="请输入前缀" />
  112 + </el-form-item>
  113 + <el-form-item v-if="activeData.append!==undefined" label="后缀">
  114 + <el-input v-model="activeData.append" placeholder="请输入后缀" />
  115 + </el-form-item>
  116 + <el-form-item v-if="activeData['prefix-icon']!==undefined" label="前图标">
  117 + <el-input v-model="activeData['prefix-icon']" placeholder="请输入前图标名称">
  118 + <el-button slot="append" icon="el-icon-thumb" @click="openIconsDialog('prefix-icon')">
  119 + 选择
  120 + </el-button>
  121 + </el-input>
  122 + </el-form-item>
  123 + <el-form-item v-if="activeData['suffix-icon'] !== undefined" label="后图标">
  124 + <el-input v-model="activeData['suffix-icon']" placeholder="请输入后图标名称">
  125 + <el-button slot="append" icon="el-icon-thumb" @click="openIconsDialog('suffix-icon')">
  126 + 选择
  127 + </el-button>
  128 + </el-input>
  129 + </el-form-item>
  130 + <el-form-item v-if="activeData.tag === 'el-cascader'" label="选项分隔符">
  131 + <el-input v-model="activeData.separator" placeholder="请输入选项分隔符" />
  132 + </el-form-item>
  133 + <el-form-item v-if="activeData.autosize !== undefined" label="最小行数">
  134 + <el-input-number v-model="activeData.autosize.minRows" :min="1" placeholder="最小行数" />
  135 + </el-form-item>
  136 + <el-form-item v-if="activeData.autosize !== undefined" label="最大行数">
  137 + <el-input-number v-model="activeData.autosize.maxRows" :min="1" placeholder="最大行数" />
  138 + </el-form-item>
  139 + <el-form-item v-if="activeData.min !== undefined" label="最小值">
  140 + <el-input-number v-model="activeData.min" placeholder="最小值" />
  141 + </el-form-item>
  142 + <el-form-item v-if="activeData.max !== undefined" label="最大值">
  143 + <el-input-number v-model="activeData.max" placeholder="最大值" />
  144 + </el-form-item>
  145 + <el-form-item v-if="activeData.step !== undefined" label="步长">
  146 + <el-input-number v-model="activeData.step" placeholder="步数" />
  147 + </el-form-item>
  148 + <el-form-item v-if="activeData.tag === 'el-input-number'" label="精度">
  149 + <el-input-number v-model="activeData.precision" :min="0" placeholder="精度" />
  150 + </el-form-item>
  151 + <el-form-item v-if="activeData.tag === 'el-input-number'" label="按钮位置">
  152 + <el-radio-group v-model="activeData['controls-position']">
  153 + <el-radio-button label="">
  154 + 默认
  155 + </el-radio-button>
  156 + <el-radio-button label="right">
  157 + 右侧
  158 + </el-radio-button>
  159 + </el-radio-group>
  160 + </el-form-item>
  161 + <el-form-item v-if="activeData.maxlength !== undefined" label="最多输入">
  162 + <el-input v-model="activeData.maxlength" placeholder="请输入字符长度">
  163 + <template slot="append">
  164 + 个字符
  165 + </template>
  166 + </el-input>
  167 + </el-form-item>
  168 + <el-form-item v-if="activeData['active-text'] !== undefined" label="开启提示">
  169 + <el-input v-model="activeData['active-text']" placeholder="请输入开启提示" />
  170 + </el-form-item>
  171 + <el-form-item v-if="activeData['inactive-text'] !== undefined" label="关闭提示">
  172 + <el-input v-model="activeData['inactive-text']" placeholder="请输入关闭提示" />
  173 + </el-form-item>
  174 + <el-form-item v-if="activeData['active-value'] !== undefined" label="开启值">
  175 + <el-input
  176 + :value="setDefaultValue(activeData['active-value'])"
  177 + placeholder="请输入开启值"
  178 + @input="onSwitchValueInput($event, 'active-value')"
  179 + />
  180 + </el-form-item>
  181 + <el-form-item v-if="activeData['inactive-value'] !== undefined" label="关闭值">
  182 + <el-input
  183 + :value="setDefaultValue(activeData['inactive-value'])"
  184 + placeholder="请输入关闭值"
  185 + @input="onSwitchValueInput($event, 'inactive-value')"
  186 + />
  187 + </el-form-item>
  188 + <el-form-item
  189 + v-if="activeData.type !== undefined && 'el-date-picker' === activeData.tag"
  190 + label="时间类型"
  191 + >
  192 + <el-select
  193 + v-model="activeData.type"
  194 + placeholder="请选择时间类型"
  195 + :style="{ width: '100%' }"
  196 + @change="dateTypeChange"
  197 + >
  198 + <el-option
  199 + v-for="(item, index) in dateOptions"
  200 + :key="index"
  201 + :label="item.label"
  202 + :value="item.value"
  203 + />
  204 + </el-select>
  205 + </el-form-item>
  206 + <el-form-item v-if="activeData.name !== undefined" label="文件字段名">
  207 + <el-input v-model="activeData.name" placeholder="请输入上传文件字段名" />
  208 + </el-form-item>
  209 + <el-form-item v-if="activeData.accept !== undefined" label="文件类型">
  210 + <el-select
  211 + v-model="activeData.accept"
  212 + placeholder="请选择文件类型"
  213 + :style="{ width: '100%' }"
  214 + clearable
  215 + >
  216 + <el-option label="图片" value="image/*" />
  217 + <el-option label="视频" value="video/*" />
  218 + <el-option label="音频" value="audio/*" />
  219 + <el-option label="excel" value=".xls,.xlsx" />
  220 + <el-option label="word" value=".doc,.docx" />
  221 + <el-option label="pdf" value=".pdf" />
  222 + <el-option label="txt" value=".txt" />
  223 + </el-select>
  224 + </el-form-item>
  225 + <el-form-item v-if="activeData.fileSize !== undefined" label="文件大小">
  226 + <el-input v-model.number="activeData.fileSize" placeholder="请输入文件大小">
  227 + <el-select slot="append" v-model="activeData.sizeUnit" :style="{ width: '66px' }">
  228 + <el-option label="KB" value="KB" />
  229 + <el-option label="MB" value="MB" />
  230 + <el-option label="GB" value="GB" />
  231 + </el-select>
  232 + </el-input>
  233 + </el-form-item>
  234 + <el-form-item v-if="activeData.action !== undefined" label="上传地址">
  235 + <el-input v-model="activeData.action" placeholder="请输入上传地址" clearable />
  236 + </el-form-item>
  237 + <el-form-item v-if="activeData['list-type'] !== undefined" label="列表类型">
  238 + <el-radio-group v-model="activeData['list-type']" size="small">
  239 + <el-radio-button label="text">
  240 + text
  241 + </el-radio-button>
  242 + <el-radio-button label="picture">
  243 + picture
  244 + </el-radio-button>
  245 + <el-radio-button label="picture-card">
  246 + picture-card
  247 + </el-radio-button>
  248 + </el-radio-group>
  249 + </el-form-item>
  250 + <el-form-item
  251 + v-if="activeData.buttonText !== undefined"
  252 + v-show="'picture-card' !== activeData['list-type']"
  253 + label="按钮文字"
  254 + >
  255 + <el-input v-model="activeData.buttonText" placeholder="请输入按钮文字" />
  256 + </el-form-item>
  257 + <el-form-item v-if="activeData['range-separator'] !== undefined" label="分隔符">
  258 + <el-input v-model="activeData['range-separator']" placeholder="请输入分隔符" />
  259 + </el-form-item>
  260 + <el-form-item v-if="activeData['picker-options'] !== undefined" label="时间段">
  261 + <el-input
  262 + v-model="activeData['picker-options'].selectableRange"
  263 + placeholder="请输入时间段"
  264 + />
  265 + </el-form-item>
  266 + <el-form-item v-if="activeData.format !== undefined" label="时间格式">
  267 + <el-input
  268 + :value="activeData.format"
  269 + placeholder="请输入时间格式"
  270 + @input="setTimeValue($event)"
  271 + />
  272 + </el-form-item>
  273 + <template v-if="['el-checkbox-group', 'el-radio-group', 'el-select'].indexOf(activeData.tag) > -1">
  274 + <el-divider>选项</el-divider>
  275 + <draggable
  276 + :list="activeData.options"
  277 + :animation="340"
  278 + group="selectItem"
  279 + handle=".option-drag"
  280 + >
  281 + <div v-for="(item, index) in activeData.options" :key="index" class="select-item">
  282 + <div class="select-line-icon option-drag">
  283 + <i class="el-icon-s-operation" />
  284 + </div>
  285 + <el-input v-model="item.label" placeholder="选项名" size="small" />
  286 + <el-input
  287 + placeholder="选项值"
  288 + size="small"
  289 + :value="item.value"
  290 + @input="setOptionValue(item, $event)"
  291 + />
  292 + <div class="close-btn select-line-icon" @click="activeData.options.splice(index, 1)">
  293 + <i class="el-icon-remove-outline" />
  294 + </div>
  295 + </div>
  296 + </draggable>
  297 + <div style="margin-left: 20px;">
  298 + <el-button
  299 + style="padding-bottom: 0"
  300 + icon="el-icon-circle-plus-outline"
  301 + type="text"
  302 + @click="addSelectItem"
  303 + >
  304 + 添加选项
  305 + </el-button>
  306 + </div>
  307 + <el-divider />
  308 + </template>
  309 +
  310 + <template v-if="['el-cascader'].indexOf(activeData.tag) > -1">
  311 + <el-divider>选项</el-divider>
  312 + <el-form-item label="数据类型">
  313 + <el-radio-group v-model="activeData.dataType" size="small">
  314 + <el-radio-button label="dynamic">
  315 + 动态数据
  316 + </el-radio-button>
  317 + <el-radio-button label="static">
  318 + 静态数据
  319 + </el-radio-button>
  320 + </el-radio-group>
  321 + </el-form-item>
  322 +
  323 + <template v-if="activeData.dataType === 'dynamic'">
  324 + <el-form-item label="标签键名">
  325 + <el-input v-model="activeData.labelKey" placeholder="请输入标签键名" />
  326 + </el-form-item>
  327 + <el-form-item label="值键名">
  328 + <el-input v-model="activeData.valueKey" placeholder="请输入值键名" />
  329 + </el-form-item>
  330 + <el-form-item label="子级键名">
  331 + <el-input v-model="activeData.childrenKey" placeholder="请输入子级键名" />
  332 + </el-form-item>
  333 + </template>
  334 +
  335 + <el-tree
  336 + v-if="activeData.dataType === 'static'"
  337 + draggable
  338 + :data="activeData.options"
  339 + node-key="id"
  340 + :expand-on-click-node="false"
  341 + :render-content="renderContent"
  342 + />
  343 + <div v-if="activeData.dataType === 'static'" style="margin-left: 20px">
  344 + <el-button
  345 + style="padding-bottom: 0"
  346 + icon="el-icon-circle-plus-outline"
  347 + type="text"
  348 + @click="addTreeItem"
  349 + >
  350 + 添加父级
  351 + </el-button>
  352 + </div>
  353 + <el-divider />
  354 + </template>
  355 +
  356 + <el-form-item v-if="activeData.optionType !== undefined" label="选项样式">
  357 + <el-radio-group v-model="activeData.optionType">
  358 + <el-radio-button label="default">
  359 + 默认
  360 + </el-radio-button>
  361 + <el-radio-button label="button">
  362 + 按钮
  363 + </el-radio-button>
  364 + </el-radio-group>
  365 + </el-form-item>
  366 + <el-form-item v-if="activeData['active-color'] !== undefined" label="开启颜色">
  367 + <el-color-picker v-model="activeData['active-color']" />
  368 + </el-form-item>
  369 + <el-form-item v-if="activeData['inactive-color'] !== undefined" label="关闭颜色">
  370 + <el-color-picker v-model="activeData['inactive-color']" />
  371 + </el-form-item>
  372 +
  373 + <el-form-item v-if="activeData['allow-half'] !== undefined" label="允许半选">
  374 + <el-switch v-model="activeData['allow-half']" />
  375 + </el-form-item>
  376 + <el-form-item v-if="activeData['show-text'] !== undefined" label="辅助文字">
  377 + <el-switch v-model="activeData['show-text']" @change="rateTextChange" />
  378 + </el-form-item>
  379 + <el-form-item v-if="activeData['show-score'] !== undefined" label="显示分数">
  380 + <el-switch v-model="activeData['show-score']" @change="rateScoreChange" />
  381 + </el-form-item>
  382 + <el-form-item v-if="activeData['show-stops'] !== undefined" label="显示间断点">
  383 + <el-switch v-model="activeData['show-stops']" />
  384 + </el-form-item>
  385 + <el-form-item v-if="activeData.range !== undefined" label="范围选择">
  386 + <el-switch v-model="activeData.range" @change="rangeChange" />
  387 + </el-form-item>
  388 + <el-form-item
  389 + v-if="activeData.border !== undefined && activeData.optionType === 'default'"
  390 + label="是否带边框"
  391 + >
  392 + <el-switch v-model="activeData.border" />
  393 + </el-form-item>
  394 + <el-form-item v-if="activeData.tag === 'el-color-picker'" label="颜色格式">
  395 + <el-select
  396 + v-model="activeData['color-format']"
  397 + placeholder="请选择颜色格式"
  398 + :style="{ width: '100%' }"
  399 + @change="colorFormatChange"
  400 + >
  401 + <el-option
  402 + v-for="(item, index) in colorFormatOptions"
  403 + :key="index"
  404 + :label="item.label"
  405 + :value="item.value"
  406 + />
  407 + </el-select>
  408 + </el-form-item>
  409 + <el-form-item
  410 + v-if="activeData.size !== undefined &&
  411 + (activeData.optionType === 'button' ||
  412 + activeData.border ||
  413 + activeData.tag === 'el-color-picker')"
  414 + label="选项尺寸"
  415 + >
  416 + <el-radio-group v-model="activeData.size">
  417 + <el-radio-button label="medium">
  418 + 中等
  419 + </el-radio-button>
  420 + <el-radio-button label="small">
  421 + 较小
  422 + </el-radio-button>
  423 + <el-radio-button label="mini">
  424 + 迷你
  425 + </el-radio-button>
  426 + </el-radio-group>
  427 + </el-form-item>
  428 + <el-form-item v-if="activeData['show-word-limit'] !== undefined" label="输入统计">
  429 + <el-switch v-model="activeData['show-word-limit']" />
  430 + </el-form-item>
  431 + <el-form-item v-if="activeData.tag === 'el-input-number'" label="严格步数">
  432 + <el-switch v-model="activeData['step-strictly']" />
  433 + </el-form-item>
  434 + <el-form-item v-if="activeData.tag === 'el-cascader'" label="是否多选">
  435 + <el-switch v-model="activeData.props.props.multiple" />
  436 + </el-form-item>
  437 + <el-form-item v-if="activeData.tag === 'el-cascader'" label="展示全路径">
  438 + <el-switch v-model="activeData['show-all-levels']" />
  439 + </el-form-item>
  440 + <el-form-item v-if="activeData.tag === 'el-cascader'" label="可否筛选">
  441 + <el-switch v-model="activeData.filterable" />
  442 + </el-form-item>
  443 + <el-form-item v-if="activeData.clearable !== undefined" label="能否清空">
  444 + <el-switch v-model="activeData.clearable" />
  445 + </el-form-item>
  446 + <el-form-item v-if="activeData.showTip !== undefined" label="显示提示">
  447 + <el-switch v-model="activeData.showTip" />
  448 + </el-form-item>
  449 + <el-form-item v-if="activeData.multiple !== undefined" label="多选文件">
  450 + <el-switch v-model="activeData.multiple" />
  451 + </el-form-item>
  452 + <el-form-item v-if="activeData['auto-upload'] !== undefined" label="自动上传">
  453 + <el-switch v-model="activeData['auto-upload']" />
  454 + </el-form-item>
  455 + <el-form-item v-if="activeData.readonly !== undefined" label="是否只读">
  456 + <el-switch v-model="activeData.readonly" />
  457 + </el-form-item>
  458 + <el-form-item v-if="activeData.disabled !== undefined" label="是否禁用">
  459 + <el-switch v-model="activeData.disabled" />
  460 + </el-form-item>
  461 + <el-form-item v-if="activeData.tag === 'el-select'" label="是否可搜索">
  462 + <el-switch v-model="activeData.filterable" />
  463 + </el-form-item>
  464 + <el-form-item v-if="activeData.tag === 'el-select'" label="是否多选">
  465 + <el-switch v-model="activeData.multiple" @change="multipleChange" />
  466 + </el-form-item>
  467 + <el-form-item v-if="activeData.required !== undefined" label="是否必填">
  468 + <el-switch v-model="activeData.required" />
  469 + </el-form-item>
  470 +
  471 + <template v-if="activeData.layoutTree">
  472 + <el-divider>布局结构树</el-divider>
  473 + <el-tree
  474 + :data="[activeData]"
  475 + :props="layoutTreeProps"
  476 + node-key="renderKey"
  477 + default-expand-all
  478 + draggable
  479 + >
  480 + <span slot-scope="{ node, data }">
  481 + <span class="node-label">
  482 + <svg-icon class="node-icon" :icon-class="data.tagIcon" />
  483 + {{ node.label }}
  484 + </span>
  485 + </span>
  486 + </el-tree>
  487 + </template>
  488 +
  489 + <template v-if="activeData.layout === 'colFormItem'">
  490 + <el-divider>正则校验</el-divider>
  491 + <div
  492 + v-for="(item, index) in activeData.regList"
  493 + :key="index"
  494 + class="reg-item"
  495 + >
  496 + <span class="close-btn" @click="activeData.regList.splice(index, 1)">
  497 + <i class="el-icon-close" />
  498 + </span>
  499 + <el-form-item label="表达式">
  500 + <el-input v-model="item.pattern" placeholder="请输入正则" />
  501 + </el-form-item>
  502 + <el-form-item label="错误提示" style="margin-bottom:0">
  503 + <el-input v-model="item.message" placeholder="请输入错误提示" />
  504 + </el-form-item>
  505 + </div>
  506 + <div style="margin-left: 20px">
  507 + <el-button icon="el-icon-circle-plus-outline" type="text" @click="addReg">
  508 + 添加规则
  509 + </el-button>
  510 + </div>
  511 + </template>
  512 + </el-form>
  513 + <!-- 表单属性 -->
  514 + <el-form v-show="currentTab === 'form'" size="small" label-width="90px">
  515 + <el-form-item label="表单名">
  516 + <el-input v-model="formConf.formRef" placeholder="请输入表单名(ref)" />
  517 + </el-form-item>
  518 + <el-form-item label="表单模型">
  519 + <el-input v-model="formConf.formModel" placeholder="请输入数据模型" />
  520 + </el-form-item>
  521 + <el-form-item label="校验模型">
  522 + <el-input v-model="formConf.formRules" placeholder="请输入校验模型" />
  523 + </el-form-item>
  524 + <el-form-item label="表单尺寸">
  525 + <el-radio-group v-model="formConf.size">
  526 + <el-radio-button label="medium">
  527 + 中等
  528 + </el-radio-button>
  529 + <el-radio-button label="small">
  530 + 较小
  531 + </el-radio-button>
  532 + <el-radio-button label="mini">
  533 + 迷你
  534 + </el-radio-button>
  535 + </el-radio-group>
  536 + </el-form-item>
  537 + <el-form-item label="标签对齐">
  538 + <el-radio-group v-model="formConf.labelPosition">
  539 + <el-radio-button label="left">
  540 + 左对齐
  541 + </el-radio-button>
  542 + <el-radio-button label="right">
  543 + 右对齐
  544 + </el-radio-button>
  545 + <el-radio-button label="top">
  546 + 顶部对齐
  547 + </el-radio-button>
  548 + </el-radio-group>
  549 + </el-form-item>
  550 + <el-form-item label="标签宽度">
  551 + <el-input-number v-model="formConf.labelWidth" placeholder="标签宽度" />
  552 + </el-form-item>
  553 + <el-form-item label="栅格间隔">
  554 + <el-input-number v-model="formConf.gutter" :min="0" placeholder="栅格间隔" />
  555 + </el-form-item>
  556 + <el-form-item label="禁用表单">
  557 + <el-switch v-model="formConf.disabled" />
  558 + </el-form-item>
  559 + <el-form-item label="表单按钮">
  560 + <el-switch v-model="formConf.formBtns" />
  561 + </el-form-item>
  562 + <el-form-item label="显示未选中组件边框">
  563 + <el-switch v-model="formConf.unFocusedComponentBorder" />
  564 + </el-form-item>
  565 + </el-form>
  566 + </el-scrollbar>
  567 + </div>
  568 +
  569 + <treeNode-dialog :visible.sync="dialogVisible" title="添加选项" @commit="addNode" />
  570 + <icons-dialog :visible.sync="iconsVisible" :current="activeData[currentIconModel]" @select="setIcon" />
  571 + </div>
  572 +</template>
  573 +
  574 +<script>
  575 +import { isArray } from 'util'
  576 +import TreeNodeDialog from './TreeNodeDialog'
  577 +import { isNumberStr } from '@/utils/index'
  578 +import IconsDialog from './IconsDialog'
  579 +import {
  580 + inputComponents,
  581 + selectComponents,
  582 + layoutComponents
  583 +} from '@/utils/generator/config'
  584 +
  585 +const dateTimeFormat = {
  586 + date: 'yyyy-MM-dd',
  587 + week: 'yyyy 第 WW 周',
  588 + month: 'yyyy-MM',
  589 + year: 'yyyy',
  590 + datetime: 'yyyy-MM-dd HH:mm:ss',
  591 + daterange: 'yyyy-MM-dd',
  592 + monthrange: 'yyyy-MM',
  593 + datetimerange: 'yyyy-MM-dd HH:mm:ss'
  594 +}
  595 +
  596 +export default {
  597 + components: {
  598 + TreeNodeDialog,
  599 + IconsDialog
  600 + },
  601 + props: ['showField', 'activeData', 'formConf'],
  602 + data() {
  603 + return {
  604 + currentTab: 'field',
  605 + currentNode: null,
  606 + dialogVisible: false,
  607 + iconsVisible: false,
  608 + currentIconModel: null,
  609 + dateTypeOptions: [
  610 + {
  611 + label: '日(date)',
  612 + value: 'date'
  613 + },
  614 + {
  615 + label: '周(week)',
  616 + value: 'week'
  617 + },
  618 + {
  619 + label: '月(month)',
  620 + value: 'month'
  621 + },
  622 + {
  623 + label: '年(year)',
  624 + value: 'year'
  625 + },
  626 + {
  627 + label: '日期时间(datetime)',
  628 + value: 'datetime'
  629 + }
  630 + ],
  631 + dateRangeTypeOptions: [
  632 + {
  633 + label: '日期范围(daterange)',
  634 + value: 'daterange'
  635 + },
  636 + {
  637 + label: '月范围(monthrange)',
  638 + value: 'monthrange'
  639 + },
  640 + {
  641 + label: '日期时间范围(datetimerange)',
  642 + value: 'datetimerange'
  643 + }
  644 + ],
  645 + colorFormatOptions: [
  646 + {
  647 + label: 'hex',
  648 + value: 'hex'
  649 + },
  650 + {
  651 + label: 'rgb',
  652 + value: 'rgb'
  653 + },
  654 + {
  655 + label: 'rgba',
  656 + value: 'rgba'
  657 + },
  658 + {
  659 + label: 'hsv',
  660 + value: 'hsv'
  661 + },
  662 + {
  663 + label: 'hsl',
  664 + value: 'hsl'
  665 + }
  666 + ],
  667 + justifyOptions: [
  668 + {
  669 + label: 'start',
  670 + value: 'start'
  671 + },
  672 + {
  673 + label: 'end',
  674 + value: 'end'
  675 + },
  676 + {
  677 + label: 'center',
  678 + value: 'center'
  679 + },
  680 + {
  681 + label: 'space-around',
  682 + value: 'space-around'
  683 + },
  684 + {
  685 + label: 'space-between',
  686 + value: 'space-between'
  687 + }
  688 + ],
  689 + layoutTreeProps: {
  690 + label(data, node) {
  691 + return data.componentName || `${data.label}: ${data.vModel}`
  692 + }
  693 + }
  694 + }
  695 + },
  696 + computed: {
  697 + documentLink() {
  698 + return (
  699 + this.activeData.document
  700 + || 'https://element.eleme.cn/#/zh-CN/component/installation'
  701 + )
  702 + },
  703 + dateOptions() {
  704 + if (
  705 + this.activeData.type !== undefined
  706 + && this.activeData.tag === 'el-date-picker'
  707 + ) {
  708 + if (this.activeData['start-placeholder'] === undefined) {
  709 + return this.dateTypeOptions
  710 + }
  711 + return this.dateRangeTypeOptions
  712 + }
  713 + return []
  714 + },
  715 + tagList() {
  716 + return [
  717 + {
  718 + label: '输入型组件',
  719 + options: inputComponents
  720 + },
  721 + {
  722 + label: '选择型组件',
  723 + options: selectComponents
  724 + }
  725 + ]
  726 + }
  727 + },
  728 + methods: {
  729 + addReg() {
  730 + this.activeData.regList.push({
  731 + pattern: '',
  732 + message: ''
  733 + })
  734 + },
  735 + addSelectItem() {
  736 + this.activeData.options.push({
  737 + label: '',
  738 + value: ''
  739 + })
  740 + },
  741 + addTreeItem() {
  742 + ++this.idGlobal
  743 + this.dialogVisible = true
  744 + this.currentNode = this.activeData.options
  745 + },
  746 + renderContent(h, { node, data, store }) {
  747 + return (
  748 + <div class="custom-tree-node">
  749 + <span>{node.label}</span>
  750 + <span class="node-operation">
  751 + <i on-click={() => this.append(data)}
  752 + class="el-icon-plus"
  753 + title="添加"
  754 + ></i>
  755 + <i on-click={() => this.remove(node, data)}
  756 + class="el-icon-delete"
  757 + title="删除"
  758 + ></i>
  759 + </span>
  760 + </div>
  761 + )
  762 + },
  763 + append(data) {
  764 + if (!data.children) {
  765 + this.$set(data, 'children', [])
  766 + }
  767 + this.dialogVisible = true
  768 + this.currentNode = data.children
  769 + },
  770 + remove(node, data) {
  771 + const { parent } = node
  772 + const children = parent.data.children || parent.data
  773 + const index = children.findIndex(d => d.id === data.id)
  774 + children.splice(index, 1)
  775 + },
  776 + addNode(data) {
  777 + this.currentNode.push(data)
  778 + },
  779 + setOptionValue(item, val) {
  780 + item.value = isNumberStr(val) ? +val : val
  781 + },
  782 + setDefaultValue(val) {
  783 + if (Array.isArray(val)) {
  784 + return val.join(',')
  785 + }
  786 + if (['string', 'number'].indexOf(val) > -1) {
  787 + return val
  788 + }
  789 + if (typeof val === 'boolean') {
  790 + return `${val}`
  791 + }
  792 + return val
  793 + },
  794 + onDefaultValueInput(str) {
  795 + if (isArray(this.activeData.defaultValue)) {
  796 + // 数组
  797 + this.$set(
  798 + this.activeData,
  799 + 'defaultValue',
  800 + str.split(',').map(val => (isNumberStr(val) ? +val : val))
  801 + )
  802 + } else if (['true', 'false'].indexOf(str) > -1) {
  803 + // 布尔
  804 + this.$set(this.activeData, 'defaultValue', JSON.parse(str))
  805 + } else {
  806 + // 字符串和数字
  807 + this.$set(
  808 + this.activeData,
  809 + 'defaultValue',
  810 + isNumberStr(str) ? +str : str
  811 + )
  812 + }
  813 + },
  814 + onSwitchValueInput(val, name) {
  815 + if (['true', 'false'].indexOf(val) > -1) {
  816 + this.$set(this.activeData, name, JSON.parse(val))
  817 + } else {
  818 + this.$set(this.activeData, name, isNumberStr(val) ? +val : val)
  819 + }
  820 + },
  821 + setTimeValue(val, type) {
  822 + const valueFormat = type === 'week' ? dateTimeFormat.date : val
  823 + this.$set(this.activeData, 'defaultValue', null)
  824 + this.$set(this.activeData, 'value-format', valueFormat)
  825 + this.$set(this.activeData, 'format', val)
  826 + },
  827 + spanChange(val) {
  828 + this.formConf.span = val
  829 + },
  830 + multipleChange(val) {
  831 + this.$set(this.activeData, 'defaultValue', val ? [] : '')
  832 + },
  833 + dateTypeChange(val) {
  834 + this.setTimeValue(dateTimeFormat[val], val)
  835 + },
  836 + rangeChange(val) {
  837 + this.$set(
  838 + this.activeData,
  839 + 'defaultValue',
  840 + val ? [this.activeData.min, this.activeData.max] : this.activeData.min
  841 + )
  842 + },
  843 + rateTextChange(val) {
  844 + if (val) this.activeData['show-score'] = false
  845 + },
  846 + rateScoreChange(val) {
  847 + if (val) this.activeData['show-text'] = false
  848 + },
  849 + colorFormatChange(val) {
  850 + this.activeData.defaultValue = null
  851 + this.activeData['show-alpha'] = val.indexOf('a') > -1
  852 + this.activeData.renderKey = +new Date() // 更新renderKey,重新渲染该组件
  853 + },
  854 + openIconsDialog(model) {
  855 + this.iconsVisible = true
  856 + this.currentIconModel = model
  857 + },
  858 + setIcon(val) {
  859 + this.activeData[this.currentIconModel] = val
  860 + },
  861 + tagChange(tagIcon) {
  862 + let target = inputComponents.find(item => item.tagIcon === tagIcon)
  863 + if (!target) target = selectComponents.find(item => item.tagIcon === tagIcon)
  864 + this.$emit('tag-change', target)
  865 + }
  866 + }
  867 +}
  868 +</script>
  869 +
  870 +<style lang="scss" scoped>
  871 +.right-board {
  872 + width: 350px;
  873 + position: absolute;
  874 + right: 0;
  875 + top: 0;
  876 + padding-top: 3px;
  877 + .field-box {
  878 + position: relative;
  879 + height: calc(100vh - 42px);
  880 + box-sizing: border-box;
  881 + overflow: hidden;
  882 + }
  883 + .el-scrollbar {
  884 + height: 100%;
  885 + }
  886 +}
  887 +.select-item {
  888 + display: flex;
  889 + border: 1px dashed #fff;
  890 + box-sizing: border-box;
  891 + & .close-btn {
  892 + cursor: pointer;
  893 + color: #f56c6c;
  894 + }
  895 + & .el-input + .el-input {
  896 + margin-left: 4px;
  897 + }
  898 +}
  899 +.select-item + .select-item {
  900 + margin-top: 4px;
  901 +}
  902 +.select-item.sortable-chosen {
  903 + border: 1px dashed #409eff;
  904 +}
  905 +.select-line-icon {
  906 + line-height: 32px;
  907 + font-size: 22px;
  908 + padding: 0 4px;
  909 + color: #777;
  910 +}
  911 +.option-drag {
  912 + cursor: move;
  913 +}
  914 +.time-range {
  915 + .el-date-editor {
  916 + width: 227px;
  917 + }
  918 + ::v-deep .el-icon-time {
  919 + display: none;
  920 + }
  921 +}
  922 +.document-link {
  923 + position: absolute;
  924 + display: block;
  925 + width: 26px;
  926 + height: 26px;
  927 + top: 0;
  928 + left: 0;
  929 + cursor: pointer;
  930 + background: #409eff;
  931 + z-index: 1;
  932 + border-radius: 0 0 6px 0;
  933 + text-align: center;
  934 + line-height: 26px;
  935 + color: #fff;
  936 + font-size: 18px;
  937 +}
  938 +.node-label{
  939 + font-size: 14px;
  940 +}
  941 +.node-icon{
  942 + color: #bebfc3;
  943 +}
  944 +</style>
... ...
yudao-admin-ui/src/views/bpm/form/build/TreeNodeDialog.vue 0 → 100644
  1 +<template>
  2 + <div>
  3 + <el-dialog
  4 + v-bind="$attrs"
  5 + :close-on-click-modal="false"
  6 + :modal-append-to-body="false"
  7 + v-on="$listeners"
  8 + @open="onOpen"
  9 + @close="onClose"
  10 + >
  11 + <el-row :gutter="0">
  12 + <el-form
  13 + ref="elForm"
  14 + :model="formData"
  15 + :rules="rules"
  16 + size="small"
  17 + label-width="100px"
  18 + >
  19 + <el-col :span="24">
  20 + <el-form-item
  21 + label="选项名"
  22 + prop="label"
  23 + >
  24 + <el-input
  25 + v-model="formData.label"
  26 + placeholder="请输入选项名"
  27 + clearable
  28 + />
  29 + </el-form-item>
  30 + </el-col>
  31 + <el-col :span="24">
  32 + <el-form-item
  33 + label="选项值"
  34 + prop="value"
  35 + >
  36 + <el-input
  37 + v-model="formData.value"
  38 + placeholder="请输入选项值"
  39 + clearable
  40 + >
  41 + <el-select
  42 + slot="append"
  43 + v-model="dataType"
  44 + :style="{width: '100px'}"
  45 + >
  46 + <el-option
  47 + v-for="(item, index) in dataTypeOptions"
  48 + :key="index"
  49 + :label="item.label"
  50 + :value="item.value"
  51 + :disabled="item.disabled"
  52 + />
  53 + </el-select>
  54 + </el-input>
  55 + </el-form-item>
  56 + </el-col>
  57 + </el-form>
  58 + </el-row>
  59 + <div slot="footer">
  60 + <el-button
  61 + type="primary"
  62 + @click="handelConfirm"
  63 + >
  64 + 确定
  65 + </el-button>
  66 + <el-button @click="close">
  67 + 取消
  68 + </el-button>
  69 + </div>
  70 + </el-dialog>
  71 + </div>
  72 +</template>
  73 +<script>
  74 +import { isNumberStr } from '@/utils/index'
  75 +
  76 +export default {
  77 + components: {},
  78 + inheritAttrs: false,
  79 + props: [],
  80 + data() {
  81 + return {
  82 + id: 100,
  83 + formData: {
  84 + label: undefined,
  85 + value: undefined
  86 + },
  87 + rules: {
  88 + label: [
  89 + {
  90 + required: true,
  91 + message: '请输入选项名',
  92 + trigger: 'blur'
  93 + }
  94 + ],
  95 + value: [
  96 + {
  97 + required: true,
  98 + message: '请输入选项值',
  99 + trigger: 'blur'
  100 + }
  101 + ]
  102 + },
  103 + dataType: 'string',
  104 + dataTypeOptions: [
  105 + {
  106 + label: '字符串',
  107 + value: 'string'
  108 + },
  109 + {
  110 + label: '数字',
  111 + value: 'number'
  112 + }
  113 + ]
  114 + }
  115 + },
  116 + computed: {},
  117 + watch: {
  118 + // eslint-disable-next-line func-names
  119 + 'formData.value': function (val) {
  120 + this.dataType = isNumberStr(val) ? 'number' : 'string'
  121 + }
  122 + },
  123 + created() {},
  124 + mounted() {},
  125 + methods: {
  126 + onOpen() {
  127 + this.formData = {
  128 + label: undefined,
  129 + value: undefined
  130 + }
  131 + },
  132 + onClose() {},
  133 + close() {
  134 + this.$emit('update:visible', false)
  135 + },
  136 + handelConfirm() {
  137 + this.$refs.elForm.validate(valid => {
  138 + if (!valid) return
  139 + if (this.dataType === 'number') {
  140 + this.formData.value = parseFloat(this.formData.value)
  141 + }
  142 + this.formData.id = this.id++
  143 + this.$emit('commit', this.formData)
  144 + this.close()
  145 + })
  146 + }
  147 + }
  148 +}
  149 +</script>
... ...
yudao-admin-ui/src/views/bpm/form/build/index.vue 0 → 100644
  1 +<template>
  2 + <div class="container">
  3 + <div class="left-board">
  4 + <div class="logo-wrapper">
  5 + <div class="logo">
  6 + <img :src="logo" alt="logo"> Form Generator
  7 + </div>
  8 + </div>
  9 + <el-scrollbar class="left-scrollbar">
  10 + <div class="components-list">
  11 + <div class="components-title">
  12 + <svg-icon icon-class="component" />输入型组件
  13 + </div>
  14 + <draggable
  15 + class="components-draggable"
  16 + :list="inputComponents"
  17 + :group="{ name: 'componentsGroup', pull: 'clone', put: false }"
  18 + :clone="cloneComponent"
  19 + draggable=".components-item"
  20 + :sort="false"
  21 + @end="onEnd"
  22 + >
  23 + <div
  24 + v-for="(element, index) in inputComponents" :key="index" class="components-item"
  25 + @click="addComponent(element)"
  26 + >
  27 + <div class="components-body">
  28 + <svg-icon :icon-class="element.tagIcon" />
  29 + {{ element.label }}
  30 + </div>
  31 + </div>
  32 + </draggable>
  33 + <div class="components-title">
  34 + <svg-icon icon-class="component" />选择型组件
  35 + </div>
  36 + <draggable
  37 + class="components-draggable"
  38 + :list="selectComponents"
  39 + :group="{ name: 'componentsGroup', pull: 'clone', put: false }"
  40 + :clone="cloneComponent"
  41 + draggable=".components-item"
  42 + :sort="false"
  43 + @end="onEnd"
  44 + >
  45 + <div
  46 + v-for="(element, index) in selectComponents"
  47 + :key="index"
  48 + class="components-item"
  49 + @click="addComponent(element)"
  50 + >
  51 + <div class="components-body">
  52 + <svg-icon :icon-class="element.tagIcon" />
  53 + {{ element.label }}
  54 + </div>
  55 + </div>
  56 + </draggable>
  57 + <div class="components-title">
  58 + <svg-icon icon-class="component" /> 布局型组件
  59 + </div>
  60 + <draggable
  61 + class="components-draggable" :list="layoutComponents"
  62 + :group="{ name: 'componentsGroup', pull: 'clone', put: false }" :clone="cloneComponent"
  63 + draggable=".components-item" :sort="false" @end="onEnd"
  64 + >
  65 + <div
  66 + v-for="(element, index) in layoutComponents" :key="index" class="components-item"
  67 + @click="addComponent(element)"
  68 + >
  69 + <div class="components-body">
  70 + <svg-icon :icon-class="element.tagIcon" />
  71 + {{ element.label }}
  72 + </div>
  73 + </div>
  74 + </draggable>
  75 + </div>
  76 + </el-scrollbar>
  77 + </div>
  78 +
  79 + <div class="center-board">
  80 + <div class="action-bar">
  81 + <el-button icon="el-icon-download" type="text" @click="download">
  82 + 导出vue文件
  83 + </el-button>
  84 + <el-button class="copy-btn-main" icon="el-icon-document-copy" type="text" @click="copy">
  85 + 复制代码
  86 + </el-button>
  87 + <el-button class="delete-btn" icon="el-icon-delete" type="text" @click="empty">
  88 + 清空
  89 + </el-button>
  90 + </div>
  91 + <el-scrollbar class="center-scrollbar">
  92 + <el-row class="center-board-row" :gutter="formConf.gutter">
  93 + <el-form
  94 + :size="formConf.size"
  95 + :label-position="formConf.labelPosition"
  96 + :disabled="formConf.disabled"
  97 + :label-width="formConf.labelWidth + 'px'"
  98 + >
  99 + <draggable class="drawing-board" :list="drawingList" :animation="340" group="componentsGroup">
  100 + <draggable-item
  101 + v-for="(element, index) in drawingList"
  102 + :key="element.renderKey"
  103 + :drawing-list="drawingList"
  104 + :element="element"
  105 + :index="index"
  106 + :active-id="activeId"
  107 + :form-conf="formConf"
  108 + @activeItem="activeFormItem"
  109 + @copyItem="drawingItemCopy"
  110 + @deleteItem="drawingItemDelete"
  111 + />
  112 + </draggable>
  113 + <div v-show="!drawingList.length" class="empty-info">
  114 + 从左侧拖入或点选组件进行表单设计
  115 + </div>
  116 + </el-form>
  117 + </el-row>
  118 + </el-scrollbar>
  119 + </div>
  120 +
  121 + <right-panel
  122 + :active-data="activeData"
  123 + :form-conf="formConf"
  124 + :show-field="!!drawingList.length"
  125 + @tag-change="tagChange"
  126 + />
  127 +
  128 + <code-type-dialog
  129 + :visible.sync="dialogVisible"
  130 + title="选择生成类型"
  131 + :show-file-name="showFileName"
  132 + @confirm="generate"
  133 + />
  134 + <input id="copyNode" type="hidden">
  135 + </div>
  136 +</template>
  137 +
  138 +<script>
  139 +import draggable from 'vuedraggable'
  140 +import { saveAs } from 'file-saver'
  141 +import beautifier from 'js-beautify'
  142 +import ClipboardJS from 'clipboard'
  143 +import render from '@/utils/generator/render'
  144 +import RightPanel from './RightPanel'
  145 +import {
  146 + inputComponents,
  147 + selectComponents,
  148 + layoutComponents,
  149 + formConf
  150 +} from '@/utils/generator/config'
  151 +import {
  152 + exportDefault, beautifierConf, isNumberStr, titleCase
  153 +} from '@/utils/index'
  154 +import {
  155 + makeUpHtml, vueTemplate, vueScript, cssStyle
  156 +} from '@/utils/generator/html'
  157 +import { makeUpJs } from '@/utils/generator/js'
  158 +import { makeUpCss } from '@/utils/generator/css'
  159 +import drawingDefalut from '@/utils/generator/drawingDefalut'
  160 +import logo from '@/assets/logo/logo.png'
  161 +import CodeTypeDialog from './CodeTypeDialog'
  162 +import DraggableItem from './DraggableItem'
  163 +
  164 +const emptyActiveData = { style: {}, autosize: {} }
  165 +let oldActiveId
  166 +let tempActiveData
  167 +
  168 +export default {
  169 + components: {
  170 + draggable,
  171 + render,
  172 + RightPanel,
  173 + CodeTypeDialog,
  174 + DraggableItem
  175 + },
  176 + data() {
  177 + return {
  178 + logo,
  179 + idGlobal: 100,
  180 + formConf,
  181 + inputComponents,
  182 + selectComponents,
  183 + layoutComponents,
  184 + labelWidth: 100,
  185 + drawingList: drawingDefalut,
  186 + drawingData: {},
  187 + activeId: drawingDefalut[0].formId,
  188 + drawerVisible: false,
  189 + formData: {},
  190 + dialogVisible: false,
  191 + generateConf: null,
  192 + showFileName: false,
  193 + activeData: drawingDefalut[0]
  194 + }
  195 + },
  196 + computed: {
  197 + },
  198 + watch: {
  199 + // eslint-disable-next-line func-names
  200 + 'activeData.label': function (val, oldVal) {
  201 + if (
  202 + this.activeData.placeholder === undefined
  203 + || !this.activeData.tag
  204 + || oldActiveId !== this.activeId
  205 + ) {
  206 + return
  207 + }
  208 + this.activeData.placeholder = this.activeData.placeholder.replace(oldVal, '') + val
  209 + },
  210 + activeId: {
  211 + handler(val) {
  212 + oldActiveId = val
  213 + },
  214 + immediate: true
  215 + }
  216 + },
  217 + mounted() {
  218 + const clipboard = new ClipboardJS('#copyNode', {
  219 + text: trigger => {
  220 + const codeStr = this.generateCode()
  221 + this.$notify({
  222 + title: '成功',
  223 + message: '代码已复制到剪切板,可粘贴。',
  224 + type: 'success'
  225 + })
  226 + return codeStr
  227 + }
  228 + })
  229 + clipboard.on('error', e => {
  230 + this.$message.error('代码复制失败')
  231 + })
  232 + },
  233 + methods: {
  234 + activeFormItem(element) {
  235 + this.activeData = element
  236 + this.activeId = element.formId
  237 + },
  238 + onEnd(obj, a) {
  239 + if (obj.from !== obj.to) {
  240 + this.activeData = tempActiveData
  241 + this.activeId = this.idGlobal
  242 + }
  243 + },
  244 + addComponent(item) {
  245 + const clone = this.cloneComponent(item)
  246 + this.drawingList.push(clone)
  247 + this.activeFormItem(clone)
  248 + },
  249 + cloneComponent(origin) {
  250 + const clone = JSON.parse(JSON.stringify(origin))
  251 + clone.formId = ++this.idGlobal
  252 + clone.span = formConf.span
  253 + clone.renderKey = +new Date() // 改变renderKey后可以实现强制更新组件
  254 + if (!clone.layout) clone.layout = 'colFormItem'
  255 + if (clone.layout === 'colFormItem') {
  256 + clone.vModel = `field${this.idGlobal}`
  257 + clone.placeholder !== undefined && (clone.placeholder += clone.label)
  258 + tempActiveData = clone
  259 + } else if (clone.layout === 'rowFormItem') {
  260 + delete clone.label
  261 + clone.componentName = `row${this.idGlobal}`
  262 + clone.gutter = this.formConf.gutter
  263 + tempActiveData = clone
  264 + }
  265 + return tempActiveData
  266 + },
  267 + AssembleFormData() {
  268 + this.formData = {
  269 + fields: JSON.parse(JSON.stringify(this.drawingList)),
  270 + ...this.formConf
  271 + }
  272 + },
  273 + generate(data) {
  274 + const func = this[`exec${titleCase(this.operationType)}`]
  275 + this.generateConf = data
  276 + func && func(data)
  277 + },
  278 + execRun(data) {
  279 + this.AssembleFormData()
  280 + this.drawerVisible = true
  281 + },
  282 + execDownload(data) {
  283 + const codeStr = this.generateCode()
  284 + const blob = new Blob([codeStr], { type: 'text/plain;charset=utf-8' })
  285 + saveAs(blob, data.fileName)
  286 + },
  287 + execCopy(data) {
  288 + document.getElementById('copyNode').click()
  289 + },
  290 + empty() {
  291 + this.$confirm('确定要清空所有组件吗?', '提示', { type: 'warning' }).then(
  292 + () => {
  293 + this.drawingList = []
  294 + }
  295 + )
  296 + },
  297 + drawingItemCopy(item, parent) {
  298 + let clone = JSON.parse(JSON.stringify(item))
  299 + clone = this.createIdAndKey(clone)
  300 + parent.push(clone)
  301 + this.activeFormItem(clone)
  302 + },
  303 + createIdAndKey(item) {
  304 + item.formId = ++this.idGlobal
  305 + item.renderKey = +new Date()
  306 + if (item.layout === 'colFormItem') {
  307 + item.vModel = `field${this.idGlobal}`
  308 + } else if (item.layout === 'rowFormItem') {
  309 + item.componentName = `row${this.idGlobal}`
  310 + }
  311 + if (Array.isArray(item.children)) {
  312 + item.children = item.children.map(childItem => this.createIdAndKey(childItem))
  313 + }
  314 + return item
  315 + },
  316 + drawingItemDelete(index, parent) {
  317 + parent.splice(index, 1)
  318 + this.$nextTick(() => {
  319 + const len = this.drawingList.length
  320 + if (len) {
  321 + this.activeFormItem(this.drawingList[len - 1])
  322 + }
  323 + })
  324 + },
  325 + generateCode() {
  326 + const { type } = this.generateConf
  327 + this.AssembleFormData()
  328 + const script = vueScript(makeUpJs(this.formData, type))
  329 + const html = vueTemplate(makeUpHtml(this.formData, type))
  330 + const css = cssStyle(makeUpCss(this.formData))
  331 + return beautifier.html(html + script + css, beautifierConf.html)
  332 + },
  333 + download() {
  334 + this.dialogVisible = true
  335 + this.showFileName = true
  336 + this.operationType = 'download'
  337 + },
  338 + run() {
  339 + this.dialogVisible = true
  340 + this.showFileName = false
  341 + this.operationType = 'run'
  342 + },
  343 + copy() {
  344 + this.dialogVisible = true
  345 + this.showFileName = false
  346 + this.operationType = 'copy'
  347 + },
  348 + tagChange(newTag) {
  349 + newTag = this.cloneComponent(newTag)
  350 + newTag.vModel = this.activeData.vModel
  351 + newTag.formId = this.activeId
  352 + newTag.span = this.activeData.span
  353 + delete this.activeData.tag
  354 + delete this.activeData.tagIcon
  355 + delete this.activeData.document
  356 + Object.keys(newTag).forEach(key => {
  357 + if (this.activeData[key] !== undefined
  358 + && typeof this.activeData[key] === typeof newTag[key]) {
  359 + newTag[key] = this.activeData[key]
  360 + }
  361 + })
  362 + this.activeData = newTag
  363 + this.updateDrawingList(newTag, this.drawingList)
  364 + },
  365 + updateDrawingList(newTag, list) {
  366 + const index = list.findIndex(item => item.formId === this.activeId)
  367 + if (index > -1) {
  368 + list.splice(index, 1, newTag)
  369 + } else {
  370 + list.forEach(item => {
  371 + if (Array.isArray(item.children)) this.updateDrawingList(newTag, item.children)
  372 + })
  373 + }
  374 + }
  375 + }
  376 +}
  377 +</script>
  378 +
  379 +<style lang='scss'>
  380 +body, html{
  381 + margin: 0;
  382 + padding: 0;
  383 + background: #fff;
  384 + -moz-osx-font-smoothing: grayscale;
  385 + -webkit-font-smoothing: antialiased;
  386 + text-rendering: optimizeLegibility;
  387 + font-family: -apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji;
  388 +}
  389 +
  390 +input, textarea{
  391 + font-family: -apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji;
  392 +}
  393 +
  394 +.editor-tabs{
  395 + background: #121315;
  396 + .el-tabs__header{
  397 + margin: 0;
  398 + border-bottom-color: #121315;
  399 + .el-tabs__nav{
  400 + border-color: #121315;
  401 + }
  402 + }
  403 + .el-tabs__item{
  404 + height: 32px;
  405 + line-height: 32px;
  406 + color: #888a8e;
  407 + border-left: 1px solid #121315 !important;
  408 + background: #363636;
  409 + margin-right: 5px;
  410 + user-select: none;
  411 + }
  412 + .el-tabs__item.is-active{
  413 + background: #1e1e1e;
  414 + border-bottom-color: #1e1e1e!important;
  415 + color: #fff;
  416 + }
  417 + .el-icon-edit{
  418 + color: #f1fa8c;
  419 + }
  420 + .el-icon-document{
  421 + color: #a95812;
  422 + }
  423 +}
  424 +
  425 +// home
  426 +.right-scrollbar {
  427 + .el-scrollbar__view {
  428 + padding: 12px 18px 15px 15px;
  429 + }
  430 +}
  431 +.left-scrollbar .el-scrollbar__wrap {
  432 + box-sizing: border-box;
  433 + overflow-x: hidden !important;
  434 + margin-bottom: 0 !important;
  435 +}
  436 +.center-tabs{
  437 + .el-tabs__header{
  438 + margin-bottom: 0!important;
  439 + }
  440 + .el-tabs__item{
  441 + width: 50%;
  442 + text-align: center;
  443 + }
  444 + .el-tabs__nav{
  445 + width: 100%;
  446 + }
  447 +}
  448 +.reg-item{
  449 + padding: 12px 6px;
  450 + background: #f8f8f8;
  451 + position: relative;
  452 + border-radius: 4px;
  453 + .close-btn{
  454 + position: absolute;
  455 + right: -6px;
  456 + top: -6px;
  457 + display: block;
  458 + width: 16px;
  459 + height: 16px;
  460 + line-height: 16px;
  461 + background: rgba(0, 0, 0, 0.2);
  462 + border-radius: 50%;
  463 + color: #fff;
  464 + text-align: center;
  465 + z-index: 1;
  466 + cursor: pointer;
  467 + font-size: 12px;
  468 + &:hover{
  469 + background: rgba(210, 23, 23, 0.5)
  470 + }
  471 + }
  472 + & + .reg-item{
  473 + margin-top: 18px;
  474 + }
  475 +}
  476 +.action-bar{
  477 + & .el-button+.el-button {
  478 + margin-left: 15px;
  479 + }
  480 + & i {
  481 + font-size: 20px;
  482 + vertical-align: middle;
  483 + position: relative;
  484 + top: -1px;
  485 + }
  486 +}
  487 +
  488 +.custom-tree-node{
  489 + width: 100%;
  490 + font-size: 14px;
  491 + .node-operation{
  492 + float: right;
  493 + }
  494 + i[class*="el-icon"] + i[class*="el-icon"]{
  495 + margin-left: 6px;
  496 + }
  497 + .el-icon-plus{
  498 + color: #409EFF;
  499 + }
  500 + .el-icon-delete{
  501 + color: #157a0c;
  502 + }
  503 +}
  504 +
  505 +.left-scrollbar .el-scrollbar__view{
  506 + overflow-x: hidden;
  507 +}
  508 +
  509 +.el-rate{
  510 + display: inline-block;
  511 + vertical-align: text-top;
  512 +}
  513 +.el-upload__tip{
  514 + line-height: 1.2;
  515 +}
  516 +
  517 +$selectedColor: #f6f7ff;
  518 +$lighterBlue: #409EFF;
  519 +
  520 +.container {
  521 + position: relative;
  522 + width: 100%;
  523 + height: 100%;
  524 +}
  525 +
  526 +.components-list {
  527 + padding: 8px;
  528 + box-sizing: border-box;
  529 + height: 100%;
  530 + .components-item {
  531 + display: inline-block;
  532 + width: 48%;
  533 + margin: 1%;
  534 + transition: transform 0ms !important;
  535 + }
  536 +}
  537 +.components-draggable{
  538 + padding-bottom: 20px;
  539 +}
  540 +.components-title{
  541 + font-size: 14px;
  542 + color: #222;
  543 + margin: 6px 2px;
  544 + .svg-icon{
  545 + color: #666;
  546 + font-size: 18px;
  547 + }
  548 +}
  549 +
  550 +.components-body {
  551 + padding: 8px 10px;
  552 + background: $selectedColor;
  553 + font-size: 12px;
  554 + cursor: move;
  555 + border: 1px dashed $selectedColor;
  556 + border-radius: 3px;
  557 + .svg-icon{
  558 + color: #777;
  559 + font-size: 15px;
  560 + }
  561 + &:hover {
  562 + border: 1px dashed #787be8;
  563 + color: #787be8;
  564 + .svg-icon {
  565 + color: #787be8;
  566 + }
  567 + }
  568 +}
  569 +
  570 +.left-board {
  571 + width: 260px;
  572 + position: absolute;
  573 + left: 0;
  574 + top: 0;
  575 + height: 100vh;
  576 +}
  577 +.left-scrollbar{
  578 + height: calc(100vh - 42px);
  579 + overflow: hidden;
  580 +}
  581 +.center-scrollbar {
  582 + height: calc(100vh - 42px);
  583 + overflow: hidden;
  584 + border-left: 1px solid #f1e8e8;
  585 + border-right: 1px solid #f1e8e8;
  586 + box-sizing: border-box;
  587 +}
  588 +.center-board {
  589 + height: 100vh;
  590 + width: auto;
  591 + margin: 0 350px 0 260px;
  592 + box-sizing: border-box;
  593 +}
  594 +.empty-info{
  595 + position: absolute;
  596 + top: 46%;
  597 + left: 0;
  598 + right: 0;
  599 + text-align: center;
  600 + font-size: 18px;
  601 + color: #ccb1ea;
  602 + letter-spacing: 4px;
  603 +}
  604 +.action-bar{
  605 + position: relative;
  606 + height: 42px;
  607 + text-align: right;
  608 + padding: 0 15px;
  609 + box-sizing: border-box;;
  610 + border: 1px solid #f1e8e8;
  611 + border-top: none;
  612 + border-left: none;
  613 + .delete-btn{
  614 + color: #F56C6C;
  615 + }
  616 +}
  617 +.logo-wrapper{
  618 + position: relative;
  619 + height: 42px;
  620 + background: #fff;
  621 + border-bottom: 1px solid #f1e8e8;
  622 + box-sizing: border-box;
  623 +}
  624 +.logo{
  625 + position: absolute;
  626 + left: 12px;
  627 + top: 6px;
  628 + line-height: 30px;
  629 + color: #00afff;
  630 + font-weight: 600;
  631 + font-size: 17px;
  632 + white-space: nowrap;
  633 + > img{
  634 + width: 30px;
  635 + height: 30px;
  636 + vertical-align: top;
  637 + }
  638 + .github{
  639 + display: inline-block;
  640 + vertical-align: sub;
  641 + margin-left: 15px;
  642 + > img{
  643 + height: 22px;
  644 + }
  645 + }
  646 +}
  647 +
  648 +.center-board-row {
  649 + padding: 12px 12px 15px 12px;
  650 + box-sizing: border-box;
  651 + & > .el-form {
  652 + // 69 = 12+15+42
  653 + height: calc(100vh - 69px);
  654 + }
  655 +}
  656 +.drawing-board {
  657 + height: 100%;
  658 + position: relative;
  659 + .components-body {
  660 + padding: 0;
  661 + margin: 0;
  662 + font-size: 0;
  663 + }
  664 + .sortable-ghost {
  665 + position: relative;
  666 + display: block;
  667 + overflow: hidden;
  668 + &::before {
  669 + content: " ";
  670 + position: absolute;
  671 + left: 0;
  672 + right: 0;
  673 + top: 0;
  674 + height: 3px;
  675 + background: rgb(89, 89, 223);
  676 + z-index: 2;
  677 + }
  678 + }
  679 + .components-item.sortable-ghost {
  680 + width: 100%;
  681 + height: 60px;
  682 + background-color: $selectedColor;
  683 + }
  684 + .active-from-item {
  685 + & > .el-form-item{
  686 + background: $selectedColor;
  687 + border-radius: 6px;
  688 + }
  689 + & > .drawing-item-copy, & > .drawing-item-delete{
  690 + display: initial;
  691 + }
  692 + & > .component-name{
  693 + color: $lighterBlue;
  694 + }
  695 + }
  696 + .el-form-item{
  697 + margin-bottom: 15px;
  698 + }
  699 +}
  700 +.drawing-item{
  701 + position: relative;
  702 + cursor: move;
  703 + &.unfocus-bordered:not(.activeFromItem) > div:first-child {
  704 + border: 1px dashed #ccc;
  705 + }
  706 + .el-form-item{
  707 + padding: 12px 10px;
  708 + }
  709 +}
  710 +.drawing-row-item{
  711 + position: relative;
  712 + cursor: move;
  713 + box-sizing: border-box;
  714 + border: 1px dashed #ccc;
  715 + border-radius: 3px;
  716 + padding: 0 2px;
  717 + margin-bottom: 15px;
  718 + .drawing-row-item {
  719 + margin-bottom: 2px;
  720 + }
  721 + .el-col{
  722 + margin-top: 22px;
  723 + }
  724 + .el-form-item{
  725 + margin-bottom: 0;
  726 + }
  727 + .drag-wrapper{
  728 + min-height: 80px;
  729 + }
  730 + &.active-from-item{
  731 + border: 1px dashed $lighterBlue;
  732 + }
  733 + .component-name{
  734 + position: absolute;
  735 + top: 0;
  736 + left: 0;
  737 + font-size: 12px;
  738 + color: #bbb;
  739 + display: inline-block;
  740 + padding: 0 6px;
  741 + }
  742 +}
  743 +.drawing-item, .drawing-row-item{
  744 + &:hover {
  745 + & > .el-form-item{
  746 + background: $selectedColor;
  747 + border-radius: 6px;
  748 + }
  749 + & > .drawing-item-copy, & > .drawing-item-delete{
  750 + display: initial;
  751 + }
  752 + }
  753 + & > .drawing-item-copy, & > .drawing-item-delete{
  754 + display: none;
  755 + position: absolute;
  756 + top: -10px;
  757 + width: 22px;
  758 + height: 22px;
  759 + line-height: 22px;
  760 + text-align: center;
  761 + border-radius: 50%;
  762 + font-size: 12px;
  763 + border: 1px solid;
  764 + cursor: pointer;
  765 + z-index: 1;
  766 + }
  767 + & > .drawing-item-copy{
  768 + right: 56px;
  769 + border-color: $lighterBlue;
  770 + color: $lighterBlue;
  771 + background: #fff;
  772 + &:hover{
  773 + background: $lighterBlue;
  774 + color: #fff;
  775 + }
  776 + }
  777 + & > .drawing-item-delete{
  778 + right: 24px;
  779 + border-color: #F56C6C;
  780 + color: #F56C6C;
  781 + background: #fff;
  782 + &:hover{
  783 + background: #F56C6C;
  784 + color: #fff;
  785 + }
  786 + }
  787 +}
  788 +
  789 +</style>
... ...