eoss 0.1.0-alpha.39 → 0.1.0-alpha.40
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/0.js +1 -1
- package/lib/1.js +1 -1
- package/lib/10.js +1 -1
- package/lib/11.js +1 -1
- package/lib/12.js +1 -1
- package/lib/13.js +1 -1
- package/lib/14.js +1 -1
- package/lib/15.js +1 -1
- package/lib/16.js +1 -1
- package/lib/17.js +1 -1
- package/lib/18.js +1 -1
- package/lib/19.js +1 -1
- package/lib/20.js +1 -1
- package/lib/21.js +1 -1
- package/lib/22.js +1 -1
- package/lib/23.js +1 -1
- package/lib/24.js +1 -1
- package/lib/25.js +1 -1
- package/lib/26.js +1 -1
- package/lib/27.js +1 -1
- package/lib/28.js +1 -1
- package/lib/29.js +1 -1
- package/lib/3.js +1 -1
- package/lib/30.js +1 -1
- package/lib/31.js +1 -1
- package/lib/32.js +1 -1
- package/lib/33.js +1 -1
- package/lib/34.js +1 -1
- package/lib/35.js +1 -1
- package/lib/36.js +1 -1
- package/lib/37.js +1 -1
- package/lib/38.js +1 -1
- package/lib/39.js +1 -1
- package/lib/4.js +1 -1
- package/lib/40.js +1 -1
- package/lib/41.js +1 -1
- package/lib/42.js +1 -1
- package/lib/43.js +1 -1
- package/lib/44.js +1 -1
- package/lib/45.js +1 -1
- package/lib/46.js +1 -1
- package/lib/47.js +1 -1
- package/lib/48.js +1 -1
- package/lib/49.js +1 -1
- package/lib/5.js +1 -1
- package/lib/50.js +1 -1
- package/lib/51.js +1 -1
- package/lib/52.js +1 -1
- package/lib/53.js +1 -1
- package/lib/54.js +1 -1
- package/lib/55.js +1 -1
- package/lib/56.js +1 -1
- package/lib/57.js +1 -1
- package/lib/58.js +1 -1
- package/lib/59.js +1 -1
- package/lib/6.js +1 -1
- package/lib/60.js +1 -1
- package/lib/61.js +1 -1
- package/lib/62.js +1 -1
- package/lib/63.js +1 -1
- package/lib/64.js +1 -1
- package/lib/65.js +1 -1
- package/lib/66.js +1 -1
- package/lib/67.js +1 -1
- package/lib/68.js +1 -1
- package/lib/69.js +1 -1
- package/lib/7.js +1 -1
- package/lib/70.js +1 -1
- package/lib/71.js +1 -1
- package/lib/72.js +1 -1
- package/lib/73.js +1 -1
- package/lib/74.js +1 -1
- package/lib/75.js +1 -1
- package/lib/76.js +1 -1
- package/lib/77.js +1 -1
- package/lib/78.js +1 -1
- package/lib/79.js +1 -1
- package/lib/8.js +1 -1
- package/lib/80.js +1 -1
- package/lib/81.js +1 -1
- package/lib/9.js +1 -1
- package/lib/account-login.js +837 -862
- package/lib/alert.js +2 -2
- package/lib/aside.js +2 -2
- package/lib/audit-trajectory.js +4 -4
- package/lib/autocomplete.js +1813 -1988
- package/lib/avatar.js +2 -2
- package/lib/backtop.js +2 -2
- package/lib/badge.js +2 -2
- package/lib/breadcrumb-item.js +4 -4
- package/lib/breadcrumb.js +4 -4
- package/lib/button-group.js +4 -4
- package/lib/button.js +2 -2
- package/lib/ca-login.js +837 -862
- package/lib/calendar.js +839 -864
- package/lib/card.js +2 -2
- package/lib/carousel-item.js +846 -871
- package/lib/carousel.js +839 -864
- package/lib/cascader-panel.js +852 -877
- package/lib/cascader.js +2048 -2223
- package/lib/checkbox-button.js +4 -4
- package/lib/checkbox-group.js +859 -884
- package/lib/checkbox.js +4 -4
- package/lib/col.js +2 -2
- package/lib/collapse-item.js +1040 -1065
- package/lib/collapse.js +1040 -1065
- package/lib/color-picker.js +972 -1122
- package/lib/container.js +2 -2
- package/lib/cron.js +12 -12
- package/lib/data-table.js +843 -868
- package/lib/date-picker.js +10520 -10695
- package/lib/descriptions-item.js +4 -4
- package/lib/descriptions.js +4 -4
- package/lib/dialog.js +837 -862
- package/lib/divider.js +2 -2
- package/lib/drawer.js +2 -2
- package/lib/dropdown-item.js +4 -4
- package/lib/dropdown-menu.js +974 -1124
- package/lib/dropdown.js +841 -866
- package/lib/email-login.js +837 -862
- package/lib/empty.js +2 -2
- package/lib/eoss.common.js +4023 -2668
- package/lib/flip.js +2 -2
- package/lib/flow-group.js +853 -878
- package/lib/flow.js +868 -893
- package/lib/flows.js +854 -879
- package/lib/footer.js +2 -2
- package/lib/form-item.js +860 -885
- package/lib/form.js +933 -950
- package/lib/guide.js +2 -2
- package/lib/header.js +2 -2
- package/lib/icon.js +837 -862
- package/lib/icons.js +2 -2
- package/lib/image-viewer.js +837 -862
- package/lib/image.js +4 -4
- package/lib/index.js +1 -1
- package/lib/infinite-scroll.js +2 -2
- package/lib/input-number.js +6 -6
- package/lib/input.js +839 -864
- package/lib/link.js +2 -2
- package/lib/loading.js +2 -2
- package/lib/login.js +867 -891
- package/lib/lowcode-view.js +1843 -1185
- package/lib/lowcode.js +3050 -1569
- package/lib/main.js +970 -988
- package/lib/marquee.js +837 -862
- package/lib/menu-item-group.js +15 -15
- package/lib/menu-item.js +6 -6
- package/lib/menu.js +2227 -2368
- package/lib/message-box.js +843 -868
- package/lib/message.js +839 -864
- package/lib/notification.js +861 -886
- package/lib/option-group.js +4 -4
- package/lib/option.js +839 -864
- package/lib/page-header.js +2 -2
- package/lib/pagination.js +839 -864
- package/lib/phone-login.js +847 -866
- package/lib/popconfirm.js +4 -4
- package/lib/popover.js +1831 -2006
- package/lib/process-config.js +839 -864
- package/lib/process-module.js +846 -871
- package/lib/process-rule.js +905 -930
- package/lib/progress.js +2 -2
- package/lib/qr-code.js +4 -4
- package/lib/qrcode-login.js +839 -864
- package/lib/radio-button.js +4 -4
- package/lib/radio-group.js +850 -875
- package/lib/radio.js +4 -4
- package/lib/rate.js +837 -862
- package/lib/retrial-auth.js +837 -862
- package/lib/row.js +2 -2
- package/lib/scrollbar.js +839 -864
- package/lib/select.js +2139 -2314
- package/lib/selector-panel.js +940 -965
- package/lib/selector.js +852 -877
- package/lib/slider.js +4 -4
- package/lib/step.js +32 -32
- package/lib/steps.js +874 -899
- package/lib/submenu.js +1269 -1385
- package/lib/switch.js +839 -864
- package/lib/tab-pane.js +4 -4
- package/lib/table-column.js +840 -865
- package/lib/table.js +1825 -2000
- package/lib/tabs.js +846 -871
- package/lib/tag.js +2 -2
- package/lib/theme-chalk/fonts/iconfont.ttf +0 -0
- package/lib/theme-chalk/fonts/iconfont.woff +0 -0
- package/lib/theme-chalk/index.css +1 -1
- package/lib/theme-chalk/lowcode.css +1 -1
- package/lib/time-picker.js +6471 -6646
- package/lib/time-select.js +5039 -5214
- package/lib/timeline-item.js +2 -2
- package/lib/timeline.js +2 -2
- package/lib/tips.js +837 -862
- package/lib/toolbar.js +4 -4
- package/lib/tooltip.js +1813 -1988
- package/lib/transfer.js +837 -862
- package/lib/tree-group.js +853 -878
- package/lib/tree.js +1026 -1051
- package/lib/update-history.js +4 -4
- package/lib/upload.js +859 -884
- package/lib/wujie.js +841 -866
- package/package.json +1 -1
- package/packages/flow-group/src/main.vue +2 -2
- package/packages/form/src/form-item-render.vue +1 -0
- package/packages/form/src/form-item.vue +12 -6
- package/packages/form/src/form.vue +5 -5
- package/packages/login/src/login.vue +1 -0
- package/packages/lowcode/src/attribute/function.vue +28 -9
- package/packages/lowcode/src/attribute/props.vue +202 -0
- package/packages/lowcode/src/attribute/styles.vue +79 -5
- package/packages/lowcode/src/attribute.vue +61 -4
- package/packages/lowcode/src/canvas.vue +159 -2
- package/packages/lowcode/src/form-render.vue +305 -0
- package/packages/lowcode/src/lowcode.vue +33 -13
- package/packages/lowcode/src/modules.vue +53 -37
- package/packages/lowcode/src/render.vue +80 -25
- package/packages/lowcode/src/util/components.js +100 -0
- package/packages/lowcode/src/util/props-util.js +67 -0
- package/packages/lowcode/src/util/source-generator.js +69 -9
- package/packages/lowcode/src/util/source-parser.js +64 -10
- package/packages/lowcode-view/src/render.vue +48 -18
- package/packages/main/src/components/userinfo.vue +0 -1
- package/packages/menu/src/menu-item-group.vue +1 -1
- package/packages/menu/src/mixins.js +8 -4
- package/packages/menu/src/submenu.vue +35 -5
- package/packages/phone-login/src/phone-login.vue +5 -2
- package/packages/theme-chalk/dist/fonts/iconfont.ttf +0 -0
- package/packages/theme-chalk/dist/fonts/iconfont.woff +0 -0
- package/packages/theme-chalk/src/fonts/iconfont.ttf +0 -0
- package/packages/theme-chalk/src/fonts/iconfont.woff +0 -0
- package/packages/theme-chalk/src/lowcode.scss +21 -2
- package/src/index.js +336 -336
|
@@ -182,6 +182,60 @@ const HTML_TAGS = new Set([
|
|
|
182
182
|
'slot'
|
|
183
183
|
]);
|
|
184
184
|
|
|
185
|
+
// kebab/camel 组件名 → Pascal 形式,便于统一比较
|
|
186
|
+
function toPascal(name) {
|
|
187
|
+
return String(name).replace(/(^|-)(\w)/g, (m, sep, c) => c.toUpperCase());
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// 表单容器:其内拖入的表单控件需包一层 es-form-item 节点(与源码结构一一对应)
|
|
191
|
+
const FORM_CONTAINERS = ['EsForm'];
|
|
192
|
+
|
|
193
|
+
// 需被 es-form-item 包裹的表单控件(Pascal 名)
|
|
194
|
+
const FORM_CONTROLS = [
|
|
195
|
+
'EsIcons',
|
|
196
|
+
'EsRadio',
|
|
197
|
+
'EsRadioGroup',
|
|
198
|
+
'EsCheckbox',
|
|
199
|
+
'EsCheckboxGroup',
|
|
200
|
+
'EsInput',
|
|
201
|
+
'EsInputNumber',
|
|
202
|
+
'EsSelect',
|
|
203
|
+
'EsSelector',
|
|
204
|
+
'EsCascader',
|
|
205
|
+
'EsSwitch',
|
|
206
|
+
'EsSlider',
|
|
207
|
+
'EsTimePicker',
|
|
208
|
+
'EsDatePicker',
|
|
209
|
+
'EsUpload',
|
|
210
|
+
'EsRate',
|
|
211
|
+
'EsColorPicker',
|
|
212
|
+
'EsTransfer',
|
|
213
|
+
'EsCron',
|
|
214
|
+
'EsAutocomplete'
|
|
215
|
+
];
|
|
216
|
+
|
|
217
|
+
// 组件名 → 字段键前缀(对齐 field-render 的 COMPONENT_MAP)
|
|
218
|
+
const COMP_TO_TYPE = {
|
|
219
|
+
EsInput: 'input',
|
|
220
|
+
EsInputNumber: 'number',
|
|
221
|
+
EsSelect: 'select',
|
|
222
|
+
EsSelector: 'selector',
|
|
223
|
+
EsCascader: 'cascader',
|
|
224
|
+
EsSwitch: 'switch',
|
|
225
|
+
EsDatePicker: 'date',
|
|
226
|
+
EsTimePicker: 'time',
|
|
227
|
+
EsUpload: 'upload',
|
|
228
|
+
EsRate: 'rate',
|
|
229
|
+
EsSlider: 'slider',
|
|
230
|
+
EsColorPicker: 'color',
|
|
231
|
+
EsTransfer: 'transfer',
|
|
232
|
+
EsRadio: 'radio',
|
|
233
|
+
EsRadioGroup: 'radio',
|
|
234
|
+
EsCheckbox: 'checkbox',
|
|
235
|
+
EsCheckboxGroup: 'checkbox',
|
|
236
|
+
EsTree: 'tree'
|
|
237
|
+
};
|
|
238
|
+
|
|
185
239
|
export default {
|
|
186
240
|
name: 'Canvas',
|
|
187
241
|
mixins: [busListener],
|
|
@@ -390,6 +444,65 @@ export default {
|
|
|
390
444
|
this.notifyAllChange();
|
|
391
445
|
}
|
|
392
446
|
});
|
|
447
|
+
|
|
448
|
+
// 表单内字段排序(es-form 插槽直接子节点即 form-render 根)
|
|
449
|
+
this.initFormSortables();
|
|
450
|
+
},
|
|
451
|
+
/**
|
|
452
|
+
* 初始化表单内字段排序:每个 es-form 根元素(插槽直接子节点为
|
|
453
|
+
* form-render 的 es-form-item / 普通 Render 根,均带 data-id)挂一个
|
|
454
|
+
* 独立 Sortable 实例;单独 group 限制只能在表单内部排序,
|
|
455
|
+
* 拖拽结束后按 DOM 真实顺序写回所属 form 节点的 children
|
|
456
|
+
*/
|
|
457
|
+
initFormSortables() {
|
|
458
|
+
const container = this.$refs.container;
|
|
459
|
+
if (!container) return;
|
|
460
|
+
const forms = Array.from(container.querySelectorAll('.es-form'));
|
|
461
|
+
// 先销毁旧实例,避免节点增删后残留
|
|
462
|
+
forms.forEach((el) => {
|
|
463
|
+
const existing = Sortable.get(el);
|
|
464
|
+
if (existing) {
|
|
465
|
+
existing.destroy();
|
|
466
|
+
}
|
|
467
|
+
});
|
|
468
|
+
forms.forEach((el) => {
|
|
469
|
+
new Sortable(el, {
|
|
470
|
+
animation: 150,
|
|
471
|
+
fallbackOnBody: true,
|
|
472
|
+
ghostClass: 'es-lowcode__canvas-ghost',
|
|
473
|
+
chosenClass: 'es-lowcode__canvas-chosen',
|
|
474
|
+
dragClass: 'es-lowcode__canvas-drag',
|
|
475
|
+
group: {
|
|
476
|
+
name: 'form-fields',
|
|
477
|
+
pull: false,
|
|
478
|
+
put: false
|
|
479
|
+
},
|
|
480
|
+
onEnd: () => {
|
|
481
|
+
// 表单节点 id 在 render.vue 包裹层的 data-id 上
|
|
482
|
+
const wrapper = el.closest('[data-id]');
|
|
483
|
+
const formId = wrapper && wrapper.getAttribute('data-id');
|
|
484
|
+
const node = formId ? this.findNodeById(formId) : null;
|
|
485
|
+
if (!node) return;
|
|
486
|
+
|
|
487
|
+
const domIds = Array.from(el.children)
|
|
488
|
+
.map((child) => child.getAttribute('data-id'))
|
|
489
|
+
.filter(Boolean);
|
|
490
|
+
const children = node.children || [];
|
|
491
|
+
const curIds = children.map((c) => c.id);
|
|
492
|
+
// 顺序未变化则不处理
|
|
493
|
+
if (curIds.join(',') === domIds.join(',')) return;
|
|
494
|
+
|
|
495
|
+
const byId = {};
|
|
496
|
+
children.forEach((c) => {
|
|
497
|
+
byId[c.id] = c;
|
|
498
|
+
});
|
|
499
|
+
const reordered = domIds.map((id) => byId[id]).filter(Boolean);
|
|
500
|
+
this.$set(node, 'children', reordered);
|
|
501
|
+
// 嵌套节点写回走 bus updateNode(按 id 定位 + 存历史)
|
|
502
|
+
bus.$emit('updateNode', node);
|
|
503
|
+
}
|
|
504
|
+
});
|
|
505
|
+
});
|
|
393
506
|
},
|
|
394
507
|
handleClickPage(page) {
|
|
395
508
|
this.handleClick(page);
|
|
@@ -455,8 +568,21 @@ export default {
|
|
|
455
568
|
*/
|
|
456
569
|
handleNodeDrop(data) {
|
|
457
570
|
const { targetNode, droppedItem } = data;
|
|
458
|
-
|
|
459
|
-
|
|
571
|
+
let newNode = { ...droppedItem, id: `${uuid()}` };
|
|
572
|
+
// 组件库条目上的 props 是定义数组,深拷贝到节点;
|
|
573
|
+
// 取值由 props 面板改动后写入条目的 value 字段
|
|
574
|
+
if (Array.isArray(newNode.props)) {
|
|
575
|
+
newNode.props = newNode.props.map((p) => ({ ...p }));
|
|
576
|
+
}
|
|
577
|
+
// 表单容器内拖入表单控件:控件节点直接存 children(JSON 不存 es-form-item
|
|
578
|
+
// 包裹节点,包裹层由 form-render 渲染时生成),label/字段键记在 formItem 上
|
|
579
|
+
if (this.isFormContainer(targetNode) && this.isFormControl(droppedItem)) {
|
|
580
|
+
const type = COMP_TO_TYPE[toPascal(droppedItem.name)] || 'input';
|
|
581
|
+
newNode.formItem = {
|
|
582
|
+
label: droppedItem.description || '',
|
|
583
|
+
prop: this.buildFieldProp(targetNode, type)
|
|
584
|
+
};
|
|
585
|
+
}
|
|
460
586
|
// 确保目标节点有 children 数组
|
|
461
587
|
if (!targetNode.children) {
|
|
462
588
|
this.$set(targetNode, 'children', []);
|
|
@@ -465,6 +591,33 @@ export default {
|
|
|
465
591
|
// 通知 lowcode 更新整个页面数据
|
|
466
592
|
bus.$emit('updateNode', targetNode);
|
|
467
593
|
},
|
|
594
|
+
/**
|
|
595
|
+
* 生成表单内唯一字段键(type 前缀 + 序号,冲突自增)
|
|
596
|
+
*/
|
|
597
|
+
buildFieldProp(targetNode, type) {
|
|
598
|
+
const children = targetNode.children || [];
|
|
599
|
+
let idx = children.length + 1;
|
|
600
|
+
let fieldName = type + idx;
|
|
601
|
+
while (
|
|
602
|
+
children.some((c) => c.formItem && c.formItem.prop === fieldName)
|
|
603
|
+
) {
|
|
604
|
+
idx += 1;
|
|
605
|
+
fieldName = type + idx;
|
|
606
|
+
}
|
|
607
|
+
return fieldName;
|
|
608
|
+
},
|
|
609
|
+
/**
|
|
610
|
+
* 判断节点是否为表单容器(es-form)
|
|
611
|
+
*/
|
|
612
|
+
isFormContainer(node) {
|
|
613
|
+
return !!node && FORM_CONTAINERS.includes(toPascal(node.name));
|
|
614
|
+
},
|
|
615
|
+
/**
|
|
616
|
+
* 判断拖入项是否为表单控件
|
|
617
|
+
*/
|
|
618
|
+
isFormControl(item) {
|
|
619
|
+
return !!item && FORM_CONTROLS.includes(toPascal(item.name));
|
|
620
|
+
},
|
|
468
621
|
/**
|
|
469
622
|
* 处理 Render 子组件的数据变更(如调整大小等)
|
|
470
623
|
* @param {Object} node 变更后的组件数据
|
|
@@ -592,6 +745,10 @@ export default {
|
|
|
592
745
|
const component = JSON.parse(componentData);
|
|
593
746
|
const id = `${uuid()}`;
|
|
594
747
|
const newNode = { ...component, id };
|
|
748
|
+
// 组件库条目上的 props 是定义数组,深拷贝到节点,取值改动后写入条目 value
|
|
749
|
+
if (Array.isArray(newNode.props)) {
|
|
750
|
+
newNode.props = newNode.props.map((p) => ({ ...p }));
|
|
751
|
+
}
|
|
595
752
|
// 元素与组件统一放入 children,用 type 区分
|
|
596
753
|
this.children.push(newNode);
|
|
597
754
|
// 通知 lowcode 同步更新
|
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<!-- 单个表单元素 render:表单控件节点(EsInput/EsSelect 等)→
|
|
3
|
+
es-form-item 包裹层(渲染时生成,JSON 不存包裹节点)+ 控件本体;
|
|
4
|
+
内部不接收拖入元素;仅支持宽度拖拽(字段可能需拖宽占多列),不提供高度手柄 -->
|
|
5
|
+
<es-form-item
|
|
6
|
+
class="es-lowcode__canvas--component"
|
|
7
|
+
:class="{ 'is-active': newNode.id === active }"
|
|
8
|
+
:data-id="newNode.id"
|
|
9
|
+
:style="newNode.style"
|
|
10
|
+
:label="formItem.label"
|
|
11
|
+
:prop="formItem.prop"
|
|
12
|
+
@click.native.stop="handleClick"
|
|
13
|
+
>
|
|
14
|
+
<component
|
|
15
|
+
:style="controlStyle"
|
|
16
|
+
:is="newNode.name"
|
|
17
|
+
v-bind="cleanProps"
|
|
18
|
+
v-on="newNode.events"
|
|
19
|
+
:key="instanceKey"
|
|
20
|
+
v-show="
|
|
21
|
+
newNode.directives['show'] !== undefined
|
|
22
|
+
? newNode.directives['show']
|
|
23
|
+
: true
|
|
24
|
+
"
|
|
25
|
+
></component>
|
|
26
|
+
<template v-if="newNode.id === active">
|
|
27
|
+
<div class="es-lowcode__handles">
|
|
28
|
+
<es-button
|
|
29
|
+
v-if="isRemove()"
|
|
30
|
+
type="text"
|
|
31
|
+
size="mini"
|
|
32
|
+
icon="es-icon-shanchu"
|
|
33
|
+
@click.stop="handleRemove"
|
|
34
|
+
></es-button>
|
|
35
|
+
</div>
|
|
36
|
+
<!-- 仅多列表单提供宽度拖拽:单列时字段本就独占一行,拖宽无意义 -->
|
|
37
|
+
<template v-if="canResizeWidth">
|
|
38
|
+
<div
|
|
39
|
+
class="es-lowcode__resize-handle is-left es-icon-stretch"
|
|
40
|
+
@pointerdown="handleResizeStart($event, 'left')"
|
|
41
|
+
@dragstart="$event.preventDefault()"
|
|
42
|
+
>
|
|
43
|
+
<span class="es-lowcode__resize-size" v-show="direction === 'left'">{{
|
|
44
|
+
width
|
|
45
|
+
}}</span>
|
|
46
|
+
</div>
|
|
47
|
+
<div
|
|
48
|
+
class="es-lowcode__resize-handle is-right es-icon-stretch"
|
|
49
|
+
@pointerdown="handleResizeStart($event, 'right')"
|
|
50
|
+
@dragstart="$event.preventDefault()"
|
|
51
|
+
>
|
|
52
|
+
<span
|
|
53
|
+
class="es-lowcode__resize-size"
|
|
54
|
+
v-show="direction === 'right'"
|
|
55
|
+
>{{ width }}</span
|
|
56
|
+
>
|
|
57
|
+
</div>
|
|
58
|
+
</template>
|
|
59
|
+
</template>
|
|
60
|
+
</es-form-item>
|
|
61
|
+
</template>
|
|
62
|
+
|
|
63
|
+
<script>
|
|
64
|
+
import EsButton from 'eoss/packages/button';
|
|
65
|
+
import bus from 'eoss/src/utils/bus.js';
|
|
66
|
+
import { propsToObject, getPropRaw } from './util/props-util.js';
|
|
67
|
+
|
|
68
|
+
export default {
|
|
69
|
+
name: 'FormRender',
|
|
70
|
+
// lowcode.vue provide 了自身实例,notRemoves(禁止删除名单)从其读取
|
|
71
|
+
inject: {
|
|
72
|
+
lowcode: {
|
|
73
|
+
default: null
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
components: {
|
|
77
|
+
EsButton
|
|
78
|
+
},
|
|
79
|
+
props: {
|
|
80
|
+
node: {
|
|
81
|
+
type: Object,
|
|
82
|
+
default: null
|
|
83
|
+
},
|
|
84
|
+
page: {
|
|
85
|
+
type: Object,
|
|
86
|
+
default: null
|
|
87
|
+
},
|
|
88
|
+
active: {
|
|
89
|
+
type: String,
|
|
90
|
+
default: ''
|
|
91
|
+
},
|
|
92
|
+
// 声明仅为吸收父级统一绑定的 :drag(手柄显隐由 active 控制,预览端无 active 自然不显示)
|
|
93
|
+
drag: {
|
|
94
|
+
type: Boolean,
|
|
95
|
+
default: true
|
|
96
|
+
}
|
|
97
|
+
},
|
|
98
|
+
data() {
|
|
99
|
+
const newNode = JSON.parse(JSON.stringify(this.node)) || {};
|
|
100
|
+
// directives 在页面结构上已定义,这里兼容子节点未携带的情况
|
|
101
|
+
newNode.directives = newNode.directives || {};
|
|
102
|
+
newNode.style = newNode.style || {};
|
|
103
|
+
return {
|
|
104
|
+
newNode,
|
|
105
|
+
resizingComponent: null,
|
|
106
|
+
initialMouseX: 0,
|
|
107
|
+
initialWidth: 0,
|
|
108
|
+
isResizing: false,
|
|
109
|
+
direction: ''
|
|
110
|
+
};
|
|
111
|
+
},
|
|
112
|
+
watch: {
|
|
113
|
+
node: {
|
|
114
|
+
handler(newVal) {
|
|
115
|
+
const node = JSON.parse(JSON.stringify(newVal)) || {};
|
|
116
|
+
node.directives = node.directives || {};
|
|
117
|
+
node.style = node.style || {};
|
|
118
|
+
this.newNode = node;
|
|
119
|
+
},
|
|
120
|
+
deep: true
|
|
121
|
+
}
|
|
122
|
+
},
|
|
123
|
+
computed: {
|
|
124
|
+
/**
|
|
125
|
+
* 表单元信息(label/字段键):canvas 拖入时写入 node.formItem,
|
|
126
|
+
* 解析源码时由 es-form-item 包裹层拆包还原
|
|
127
|
+
*/
|
|
128
|
+
formItem() {
|
|
129
|
+
return (this.newNode && this.newNode.formItem) || {};
|
|
130
|
+
},
|
|
131
|
+
/**
|
|
132
|
+
* 所属 es-form 节点:在页面树中反查包含当前字段的父节点
|
|
133
|
+
*/
|
|
134
|
+
parentForm() {
|
|
135
|
+
const find = (nodes) => {
|
|
136
|
+
if (!nodes) return null;
|
|
137
|
+
for (const node of nodes) {
|
|
138
|
+
const children = node.children || [];
|
|
139
|
+
if (children.some((c) => c.id === this.newNode.id)) return node;
|
|
140
|
+
const found = find(children);
|
|
141
|
+
if (found) return found;
|
|
142
|
+
}
|
|
143
|
+
return null;
|
|
144
|
+
};
|
|
145
|
+
return this.page ? find([this.page]) : null;
|
|
146
|
+
},
|
|
147
|
+
/**
|
|
148
|
+
* 是否允许宽度拖拽:单列(columns 缺省或 <=1)表单不显示宽度手柄,
|
|
149
|
+
* 多列时字段可拖宽占多列
|
|
150
|
+
*/
|
|
151
|
+
canResizeWidth() {
|
|
152
|
+
const columns = getPropRaw(
|
|
153
|
+
this.parentForm && this.parentForm.props,
|
|
154
|
+
'columns'
|
|
155
|
+
);
|
|
156
|
+
return (parseInt(columns, 10) || 1) > 1;
|
|
157
|
+
},
|
|
158
|
+
/**
|
|
159
|
+
* 手柄旁 span 展示的宽度:拖拽写入 style.width 后自动跟随,
|
|
160
|
+
* 无显式宽度(columns 列宽百分比布局)时从 DOM 取实际宽度
|
|
161
|
+
*/
|
|
162
|
+
width() {
|
|
163
|
+
const w = parseInt(this.newNode.style && this.newNode.style.width, 10);
|
|
164
|
+
if (!isNaN(w)) return w;
|
|
165
|
+
return (this.$el && this.$el.offsetWidth) || 0;
|
|
166
|
+
},
|
|
167
|
+
/**
|
|
168
|
+
* 控件样式:表单字段默认撑满 form-item 内容区(宽度由 es-form-item 根的 style.width 控制)
|
|
169
|
+
*/
|
|
170
|
+
controlStyle() {
|
|
171
|
+
const style = { ...((this.newNode && this.newNode.style) || {}) };
|
|
172
|
+
delete style.width;
|
|
173
|
+
delete style.height;
|
|
174
|
+
return { width: '100%', ...style };
|
|
175
|
+
},
|
|
176
|
+
/**
|
|
177
|
+
* 页面 data 变量:供 props 中 {{var}} 引用解析
|
|
178
|
+
*/
|
|
179
|
+
pageData() {
|
|
180
|
+
return (this.page && this.page.data) || {};
|
|
181
|
+
},
|
|
182
|
+
/**
|
|
183
|
+
* 过滤掉 Vue 指令(v-xxx)的 props,避免它们作为普通属性传递给组件
|
|
184
|
+
*/
|
|
185
|
+
cleanProps() {
|
|
186
|
+
if (!this.newNode) return {};
|
|
187
|
+
const result = {};
|
|
188
|
+
// 合并 HTML 属性
|
|
189
|
+
if (this.newNode.attrs) {
|
|
190
|
+
Object.assign(result, this.newNode.attrs);
|
|
191
|
+
}
|
|
192
|
+
// 合并组件 props:定义数组经 propsToObject 转取值对象(含 {{var}} 解析)
|
|
193
|
+
Object.assign(result, propsToObject(this.newNode.props, this.pageData));
|
|
194
|
+
return result;
|
|
195
|
+
},
|
|
196
|
+
/**
|
|
197
|
+
* 组件实例 key:节点 id + props 签名。
|
|
198
|
+
* 业务组件在 created 里按 props 拉数据、不 watch prop 变化,
|
|
199
|
+
* 源码视图改完 props 后必须换 key 强制重挂载,画布才会刷新
|
|
200
|
+
*/
|
|
201
|
+
instanceKey() {
|
|
202
|
+
let sig = '';
|
|
203
|
+
try {
|
|
204
|
+
sig = JSON.stringify(this.cleanProps);
|
|
205
|
+
} catch (e) {
|
|
206
|
+
sig = '';
|
|
207
|
+
}
|
|
208
|
+
return (this.newNode ? this.newNode.id : '') + ':' + sig;
|
|
209
|
+
}
|
|
210
|
+
},
|
|
211
|
+
methods: {
|
|
212
|
+
/**
|
|
213
|
+
* 是否展示删除按钮:节点 canRemove 未显式禁止,
|
|
214
|
+
* 且 id 不在 lowcode 的 notRemoves(禁止删除名单)中
|
|
215
|
+
*/
|
|
216
|
+
isRemove() {
|
|
217
|
+
if (this.newNode.canRemove === false) return false;
|
|
218
|
+
const notRemoves = (this.lowcode && this.lowcode.notRemoves) || [];
|
|
219
|
+
return !notRemoves.includes(this.newNode.id);
|
|
220
|
+
},
|
|
221
|
+
/**
|
|
222
|
+
* 处理字段点击事件(选中);拖拽中不响应,避免误选
|
|
223
|
+
*/
|
|
224
|
+
handleClick(event) {
|
|
225
|
+
if (this.isResizing) {
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
this.$emit('click', this.newNode, event);
|
|
229
|
+
},
|
|
230
|
+
/**
|
|
231
|
+
* 宽度拖拽开始(仅 left/right,字段不提供高度拖拽)
|
|
232
|
+
* @param event 指针事件对象
|
|
233
|
+
* @param direction 拖拽方向 left/right
|
|
234
|
+
*/
|
|
235
|
+
handleResizeStart(event, direction) {
|
|
236
|
+
event.preventDefault();
|
|
237
|
+
event.stopPropagation(); // 阻止事件冒泡,防止触发字段选中
|
|
238
|
+
this.direction = direction;
|
|
239
|
+
// 只响应鼠标左键或触摸
|
|
240
|
+
if (event.pointerType === 'mouse' && event.button !== 0) return;
|
|
241
|
+
this.resizingComponent = { ...this.newNode, direction };
|
|
242
|
+
this.initialMouseX = event.clientX;
|
|
243
|
+
this.initialWidth = this.width || 0;
|
|
244
|
+
document.addEventListener('pointermove', this.handleResizeMove);
|
|
245
|
+
document.addEventListener('pointerup', this.handleResizeEnd);
|
|
246
|
+
},
|
|
247
|
+
/**
|
|
248
|
+
* 宽度拖拽移动:实时写入 style.width(作用于 es-form-item 根),
|
|
249
|
+
* 上限钉到所属表单内容区宽度,下限保 40px 防止拖没
|
|
250
|
+
* @param event 指针事件对象
|
|
251
|
+
*/
|
|
252
|
+
handleResizeMove(event) {
|
|
253
|
+
event.preventDefault();
|
|
254
|
+
event.stopPropagation();
|
|
255
|
+
this.isResizing = true;
|
|
256
|
+
if (!this.resizingComponent) return;
|
|
257
|
+
const deltaX = event.clientX - this.initialMouseX;
|
|
258
|
+
let newWidth =
|
|
259
|
+
this.direction === 'right'
|
|
260
|
+
? this.initialWidth + deltaX
|
|
261
|
+
: this.initialWidth - deltaX;
|
|
262
|
+
const formContent =
|
|
263
|
+
this.$el && this.$el.parentElement ? this.$el.parentElement : null;
|
|
264
|
+
const maxWidth = formContent ? formContent.clientWidth : 0;
|
|
265
|
+
newWidth = Math.max(40, newWidth);
|
|
266
|
+
if (maxWidth > 0) {
|
|
267
|
+
newWidth = Math.min(newWidth, maxWidth);
|
|
268
|
+
}
|
|
269
|
+
this.$set(this.newNode.style, 'width', `${newWidth}px`);
|
|
270
|
+
},
|
|
271
|
+
/**
|
|
272
|
+
* 宽度拖拽结束:经 bus updateNode 持久化到页面 JSON(lowcode.vue 按 id
|
|
273
|
+
* 写回页面树并存档历史),嵌套字段不走 canvas 顶层 index 式 handleChange
|
|
274
|
+
* @param event 指针事件对象
|
|
275
|
+
*/
|
|
276
|
+
handleResizeEnd(event) {
|
|
277
|
+
event.preventDefault();
|
|
278
|
+
event.stopPropagation();
|
|
279
|
+
bus.$emit('updateNode', this.newNode);
|
|
280
|
+
this.resizingComponent = null;
|
|
281
|
+
document.removeEventListener('pointermove', this.handleResizeMove);
|
|
282
|
+
document.removeEventListener('pointerup', this.handleResizeEnd);
|
|
283
|
+
// 释放鼠标时浏览器会在指针位置派发 click,若此时鼠标不在被拖元素上,
|
|
284
|
+
// 会误选中底下的其它元素;在捕获阶段吞掉紧随其后的这一次 click
|
|
285
|
+
const swallowClick = (e) => {
|
|
286
|
+
e.stopPropagation();
|
|
287
|
+
e.preventDefault();
|
|
288
|
+
document.removeEventListener('click', swallowClick, true);
|
|
289
|
+
};
|
|
290
|
+
document.addEventListener('click', swallowClick, true);
|
|
291
|
+
// 延迟重置调整状态,避免触发点击事件
|
|
292
|
+
setTimeout(() => {
|
|
293
|
+
this.isResizing = false;
|
|
294
|
+
document.removeEventListener('click', swallowClick, true);
|
|
295
|
+
}, 200);
|
|
296
|
+
},
|
|
297
|
+
/**
|
|
298
|
+
* 处理字段删除事件
|
|
299
|
+
*/
|
|
300
|
+
handleRemove() {
|
|
301
|
+
this.$emit('remove', this.newNode);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
};
|
|
305
|
+
</script>
|
|
@@ -195,6 +195,8 @@ import EsRadio from 'eoss/packages/radio';
|
|
|
195
195
|
import EsRadioGroup from 'eoss/packages/radio-group';
|
|
196
196
|
import EsSelect from 'eoss/packages/select';
|
|
197
197
|
import EsSelector from 'eoss/packages/selector';
|
|
198
|
+
import librarys from './util/components';
|
|
199
|
+
|
|
198
200
|
export default {
|
|
199
201
|
name: 'EsLowcode',
|
|
200
202
|
mixins: [historyMixin, busListener],
|
|
@@ -224,10 +226,7 @@ export default {
|
|
|
224
226
|
|
|
225
227
|
props: {
|
|
226
228
|
// url模式
|
|
227
|
-
urlMode:
|
|
228
|
-
type: String,
|
|
229
|
-
required: true
|
|
230
|
-
},
|
|
229
|
+
urlMode: String,
|
|
231
230
|
// 授权类型
|
|
232
231
|
authorizeType: {
|
|
233
232
|
type: Array,
|
|
@@ -246,7 +245,14 @@ export default {
|
|
|
246
245
|
// 组件库数据
|
|
247
246
|
library: {
|
|
248
247
|
type: Array,
|
|
249
|
-
default: () => [
|
|
248
|
+
default: () => [
|
|
249
|
+
{
|
|
250
|
+
name: 'component',
|
|
251
|
+
label: 'Eoss',
|
|
252
|
+
collapse: true,
|
|
253
|
+
children: librarys
|
|
254
|
+
}
|
|
255
|
+
]
|
|
250
256
|
},
|
|
251
257
|
// 分页信息
|
|
252
258
|
pagination: {
|
|
@@ -271,7 +277,11 @@ export default {
|
|
|
271
277
|
// 保存方法
|
|
272
278
|
save: Function,
|
|
273
279
|
// 发布方法
|
|
274
|
-
publish: Function
|
|
280
|
+
publish: Function,
|
|
281
|
+
notRemoves: {
|
|
282
|
+
type: Array,
|
|
283
|
+
default: () => []
|
|
284
|
+
}
|
|
275
285
|
},
|
|
276
286
|
|
|
277
287
|
data() {
|
|
@@ -460,12 +470,20 @@ export default {
|
|
|
460
470
|
}
|
|
461
471
|
});
|
|
462
472
|
} else if (this.page.children) {
|
|
463
|
-
//
|
|
464
|
-
const
|
|
465
|
-
|
|
466
|
-
const
|
|
467
|
-
|
|
468
|
-
|
|
473
|
+
// 修改子组件属性:递归全树定位,嵌套组件(如表单内字段)也要能写回
|
|
474
|
+
const updateInTree = (nodes) => {
|
|
475
|
+
if (!nodes) return false;
|
|
476
|
+
const index = nodes.findIndex((c) => c.id === updates.id);
|
|
477
|
+
if (index !== -1) {
|
|
478
|
+
this.$set(nodes, index, { ...nodes[index], ...updates });
|
|
479
|
+
return true;
|
|
480
|
+
}
|
|
481
|
+
for (const node of nodes) {
|
|
482
|
+
if (node.children && updateInTree(node.children)) return true;
|
|
483
|
+
}
|
|
484
|
+
return false;
|
|
485
|
+
};
|
|
486
|
+
updateInTree(this.page.children);
|
|
469
487
|
}
|
|
470
488
|
// 保存历史记录
|
|
471
489
|
this.saveHistory();
|
|
@@ -532,7 +550,9 @@ export default {
|
|
|
532
550
|
return;
|
|
533
551
|
}
|
|
534
552
|
this.publishTitle = page.status === 1 ? '查看' : '发布';
|
|
535
|
-
const url = this.urlMode
|
|
553
|
+
const url = this.urlMode
|
|
554
|
+
? this.urlMode.replace('{id}', page.id)
|
|
555
|
+
: page.id;
|
|
536
556
|
this.readonly = page.status === 1;
|
|
537
557
|
this.publishForm = { name: page.name, pid: page.pid || '', url: url };
|
|
538
558
|
this.parentData = this.pagesData.filter((item) => item.type === 'page');
|