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.
Files changed (233) hide show
  1. package/lib/0.js +1 -1
  2. package/lib/1.js +1 -1
  3. package/lib/10.js +1 -1
  4. package/lib/11.js +1 -1
  5. package/lib/12.js +1 -1
  6. package/lib/13.js +1 -1
  7. package/lib/14.js +1 -1
  8. package/lib/15.js +1 -1
  9. package/lib/16.js +1 -1
  10. package/lib/17.js +1 -1
  11. package/lib/18.js +1 -1
  12. package/lib/19.js +1 -1
  13. package/lib/20.js +1 -1
  14. package/lib/21.js +1 -1
  15. package/lib/22.js +1 -1
  16. package/lib/23.js +1 -1
  17. package/lib/24.js +1 -1
  18. package/lib/25.js +1 -1
  19. package/lib/26.js +1 -1
  20. package/lib/27.js +1 -1
  21. package/lib/28.js +1 -1
  22. package/lib/29.js +1 -1
  23. package/lib/3.js +1 -1
  24. package/lib/30.js +1 -1
  25. package/lib/31.js +1 -1
  26. package/lib/32.js +1 -1
  27. package/lib/33.js +1 -1
  28. package/lib/34.js +1 -1
  29. package/lib/35.js +1 -1
  30. package/lib/36.js +1 -1
  31. package/lib/37.js +1 -1
  32. package/lib/38.js +1 -1
  33. package/lib/39.js +1 -1
  34. package/lib/4.js +1 -1
  35. package/lib/40.js +1 -1
  36. package/lib/41.js +1 -1
  37. package/lib/42.js +1 -1
  38. package/lib/43.js +1 -1
  39. package/lib/44.js +1 -1
  40. package/lib/45.js +1 -1
  41. package/lib/46.js +1 -1
  42. package/lib/47.js +1 -1
  43. package/lib/48.js +1 -1
  44. package/lib/49.js +1 -1
  45. package/lib/5.js +1 -1
  46. package/lib/50.js +1 -1
  47. package/lib/51.js +1 -1
  48. package/lib/52.js +1 -1
  49. package/lib/53.js +1 -1
  50. package/lib/54.js +1 -1
  51. package/lib/55.js +1 -1
  52. package/lib/56.js +1 -1
  53. package/lib/57.js +1 -1
  54. package/lib/58.js +1 -1
  55. package/lib/59.js +1 -1
  56. package/lib/6.js +1 -1
  57. package/lib/60.js +1 -1
  58. package/lib/61.js +1 -1
  59. package/lib/62.js +1 -1
  60. package/lib/63.js +1 -1
  61. package/lib/64.js +1 -1
  62. package/lib/65.js +1 -1
  63. package/lib/66.js +1 -1
  64. package/lib/67.js +1 -1
  65. package/lib/68.js +1 -1
  66. package/lib/69.js +1 -1
  67. package/lib/7.js +1 -1
  68. package/lib/70.js +1 -1
  69. package/lib/71.js +1 -1
  70. package/lib/72.js +1 -1
  71. package/lib/73.js +1 -1
  72. package/lib/74.js +1 -1
  73. package/lib/75.js +1 -1
  74. package/lib/76.js +1 -1
  75. package/lib/77.js +1 -1
  76. package/lib/78.js +1 -1
  77. package/lib/79.js +1 -1
  78. package/lib/8.js +1 -1
  79. package/lib/80.js +1 -1
  80. package/lib/81.js +1 -1
  81. package/lib/9.js +1 -1
  82. package/lib/account-login.js +837 -862
  83. package/lib/alert.js +2 -2
  84. package/lib/aside.js +2 -2
  85. package/lib/audit-trajectory.js +4 -4
  86. package/lib/autocomplete.js +1813 -1988
  87. package/lib/avatar.js +2 -2
  88. package/lib/backtop.js +2 -2
  89. package/lib/badge.js +2 -2
  90. package/lib/breadcrumb-item.js +4 -4
  91. package/lib/breadcrumb.js +4 -4
  92. package/lib/button-group.js +4 -4
  93. package/lib/button.js +2 -2
  94. package/lib/ca-login.js +837 -862
  95. package/lib/calendar.js +839 -864
  96. package/lib/card.js +2 -2
  97. package/lib/carousel-item.js +846 -871
  98. package/lib/carousel.js +839 -864
  99. package/lib/cascader-panel.js +852 -877
  100. package/lib/cascader.js +2048 -2223
  101. package/lib/checkbox-button.js +4 -4
  102. package/lib/checkbox-group.js +859 -884
  103. package/lib/checkbox.js +4 -4
  104. package/lib/col.js +2 -2
  105. package/lib/collapse-item.js +1040 -1065
  106. package/lib/collapse.js +1040 -1065
  107. package/lib/color-picker.js +972 -1122
  108. package/lib/container.js +2 -2
  109. package/lib/cron.js +12 -12
  110. package/lib/data-table.js +843 -868
  111. package/lib/date-picker.js +10520 -10695
  112. package/lib/descriptions-item.js +4 -4
  113. package/lib/descriptions.js +4 -4
  114. package/lib/dialog.js +837 -862
  115. package/lib/divider.js +2 -2
  116. package/lib/drawer.js +2 -2
  117. package/lib/dropdown-item.js +4 -4
  118. package/lib/dropdown-menu.js +974 -1124
  119. package/lib/dropdown.js +841 -866
  120. package/lib/email-login.js +837 -862
  121. package/lib/empty.js +2 -2
  122. package/lib/eoss.common.js +4023 -2668
  123. package/lib/flip.js +2 -2
  124. package/lib/flow-group.js +853 -878
  125. package/lib/flow.js +868 -893
  126. package/lib/flows.js +854 -879
  127. package/lib/footer.js +2 -2
  128. package/lib/form-item.js +860 -885
  129. package/lib/form.js +933 -950
  130. package/lib/guide.js +2 -2
  131. package/lib/header.js +2 -2
  132. package/lib/icon.js +837 -862
  133. package/lib/icons.js +2 -2
  134. package/lib/image-viewer.js +837 -862
  135. package/lib/image.js +4 -4
  136. package/lib/index.js +1 -1
  137. package/lib/infinite-scroll.js +2 -2
  138. package/lib/input-number.js +6 -6
  139. package/lib/input.js +839 -864
  140. package/lib/link.js +2 -2
  141. package/lib/loading.js +2 -2
  142. package/lib/login.js +867 -891
  143. package/lib/lowcode-view.js +1843 -1185
  144. package/lib/lowcode.js +3050 -1569
  145. package/lib/main.js +970 -988
  146. package/lib/marquee.js +837 -862
  147. package/lib/menu-item-group.js +15 -15
  148. package/lib/menu-item.js +6 -6
  149. package/lib/menu.js +2227 -2368
  150. package/lib/message-box.js +843 -868
  151. package/lib/message.js +839 -864
  152. package/lib/notification.js +861 -886
  153. package/lib/option-group.js +4 -4
  154. package/lib/option.js +839 -864
  155. package/lib/page-header.js +2 -2
  156. package/lib/pagination.js +839 -864
  157. package/lib/phone-login.js +847 -866
  158. package/lib/popconfirm.js +4 -4
  159. package/lib/popover.js +1831 -2006
  160. package/lib/process-config.js +839 -864
  161. package/lib/process-module.js +846 -871
  162. package/lib/process-rule.js +905 -930
  163. package/lib/progress.js +2 -2
  164. package/lib/qr-code.js +4 -4
  165. package/lib/qrcode-login.js +839 -864
  166. package/lib/radio-button.js +4 -4
  167. package/lib/radio-group.js +850 -875
  168. package/lib/radio.js +4 -4
  169. package/lib/rate.js +837 -862
  170. package/lib/retrial-auth.js +837 -862
  171. package/lib/row.js +2 -2
  172. package/lib/scrollbar.js +839 -864
  173. package/lib/select.js +2139 -2314
  174. package/lib/selector-panel.js +940 -965
  175. package/lib/selector.js +852 -877
  176. package/lib/slider.js +4 -4
  177. package/lib/step.js +32 -32
  178. package/lib/steps.js +874 -899
  179. package/lib/submenu.js +1269 -1385
  180. package/lib/switch.js +839 -864
  181. package/lib/tab-pane.js +4 -4
  182. package/lib/table-column.js +840 -865
  183. package/lib/table.js +1825 -2000
  184. package/lib/tabs.js +846 -871
  185. package/lib/tag.js +2 -2
  186. package/lib/theme-chalk/fonts/iconfont.ttf +0 -0
  187. package/lib/theme-chalk/fonts/iconfont.woff +0 -0
  188. package/lib/theme-chalk/index.css +1 -1
  189. package/lib/theme-chalk/lowcode.css +1 -1
  190. package/lib/time-picker.js +6471 -6646
  191. package/lib/time-select.js +5039 -5214
  192. package/lib/timeline-item.js +2 -2
  193. package/lib/timeline.js +2 -2
  194. package/lib/tips.js +837 -862
  195. package/lib/toolbar.js +4 -4
  196. package/lib/tooltip.js +1813 -1988
  197. package/lib/transfer.js +837 -862
  198. package/lib/tree-group.js +853 -878
  199. package/lib/tree.js +1026 -1051
  200. package/lib/update-history.js +4 -4
  201. package/lib/upload.js +859 -884
  202. package/lib/wujie.js +841 -866
  203. package/package.json +1 -1
  204. package/packages/flow-group/src/main.vue +2 -2
  205. package/packages/form/src/form-item-render.vue +1 -0
  206. package/packages/form/src/form-item.vue +12 -6
  207. package/packages/form/src/form.vue +5 -5
  208. package/packages/login/src/login.vue +1 -0
  209. package/packages/lowcode/src/attribute/function.vue +28 -9
  210. package/packages/lowcode/src/attribute/props.vue +202 -0
  211. package/packages/lowcode/src/attribute/styles.vue +79 -5
  212. package/packages/lowcode/src/attribute.vue +61 -4
  213. package/packages/lowcode/src/canvas.vue +159 -2
  214. package/packages/lowcode/src/form-render.vue +305 -0
  215. package/packages/lowcode/src/lowcode.vue +33 -13
  216. package/packages/lowcode/src/modules.vue +53 -37
  217. package/packages/lowcode/src/render.vue +80 -25
  218. package/packages/lowcode/src/util/components.js +100 -0
  219. package/packages/lowcode/src/util/props-util.js +67 -0
  220. package/packages/lowcode/src/util/source-generator.js +69 -9
  221. package/packages/lowcode/src/util/source-parser.js +64 -10
  222. package/packages/lowcode-view/src/render.vue +48 -18
  223. package/packages/main/src/components/userinfo.vue +0 -1
  224. package/packages/menu/src/menu-item-group.vue +1 -1
  225. package/packages/menu/src/mixins.js +8 -4
  226. package/packages/menu/src/submenu.vue +35 -5
  227. package/packages/phone-login/src/phone-login.vue +5 -2
  228. package/packages/theme-chalk/dist/fonts/iconfont.ttf +0 -0
  229. package/packages/theme-chalk/dist/fonts/iconfont.woff +0 -0
  230. package/packages/theme-chalk/src/fonts/iconfont.ttf +0 -0
  231. package/packages/theme-chalk/src/fonts/iconfont.woff +0 -0
  232. package/packages/theme-chalk/src/lowcode.scss +21 -2
  233. 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
- const id = `${uuid()}`;
459
- const newNode = { ...droppedItem, id };
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 index = this.page.children.findIndex((c) => c.id === updates.id);
465
- if (index !== -1) {
466
- const newComponent = { ...this.page.children[index], ...updates };
467
- this.$set(this.page.children, index, newComponent);
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 ? this.urlMode.replace('{id}', page.id) : '';
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');