aldehyde 0.2.481 → 0.2.482

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 (136) hide show
  1. package/lib/controls/direct-file-view/index.d.ts.map +1 -1
  2. package/lib/controls/direct-file-view/index.js +4 -4
  3. package/lib/controls/direct-file-view/index.js.map +1 -1
  4. package/lib/controls/direct-upload/index.d.ts.map +1 -1
  5. package/lib/controls/direct-upload/index.js +3 -3
  6. package/lib/controls/direct-upload/index.js.map +1 -1
  7. package/lib/controls/entry-control.d.ts +2 -1
  8. package/lib/controls/entry-control.d.ts.map +1 -1
  9. package/lib/controls/entry-control.js +10 -0
  10. package/lib/controls/entry-control.js.map +1 -1
  11. package/lib/controls/select/index.d.ts +8 -2
  12. package/lib/controls/select/index.d.ts.map +1 -1
  13. package/lib/controls/select/index.js +35 -13
  14. package/lib/controls/select/index.js.map +1 -1
  15. package/lib/controls/upload-read-excel/index.d.ts +10 -0
  16. package/lib/controls/upload-read-excel/index.d.ts.map +1 -0
  17. package/lib/controls/upload-read-excel/index.js +30 -0
  18. package/lib/controls/upload-read-excel/index.js.map +1 -0
  19. package/lib/detail/dtmpl.css +5 -0
  20. package/lib/detail/view/dtmpl-view.d.ts.map +1 -1
  21. package/lib/detail/view/dtmpl-view.js +12 -2
  22. package/lib/detail/view/dtmpl-view.js.map +1 -1
  23. package/lib/draw-canvas-edit/components/asset-bar/index.less +2 -2
  24. package/lib/draw-canvas-edit/components/main-header/index.d.ts.map +1 -1
  25. package/lib/draw-canvas-edit/components/main-header/index.js +32 -2
  26. package/lib/draw-canvas-edit/components/main-header/index.js.map +1 -1
  27. package/lib/draw-canvas-edit/components/render/components/index.less +61 -0
  28. package/lib/draw-canvas-edit/components/render/components/workstation-component.d.ts +7 -0
  29. package/lib/draw-canvas-edit/components/render/components/workstation-component.d.ts.map +1 -1
  30. package/lib/draw-canvas-edit/components/render/components/workstation-component.js +122 -15
  31. package/lib/draw-canvas-edit/components/render/components/workstation-component.js.map +1 -1
  32. package/lib/draw-canvas-edit/components/render/handlers/selection-handlers.d.ts.map +1 -1
  33. package/lib/draw-canvas-edit/components/render/handlers/selection-handlers.js +19 -5
  34. package/lib/draw-canvas-edit/components/render/handlers/selection-handlers.js.map +1 -1
  35. package/lib/draw-canvas-edit/components/render/index.d.ts.map +1 -1
  36. package/lib/draw-canvas-edit/components/render/index.js +15 -4
  37. package/lib/draw-canvas-edit/components/render/index.js.map +1 -1
  38. package/lib/draw-canvas-edit/components/render/tools/asset-tool.d.ts +2 -0
  39. package/lib/draw-canvas-edit/components/render/tools/asset-tool.d.ts.map +1 -1
  40. package/lib/draw-canvas-edit/components/render/tools/asset-tool.js +22 -11
  41. package/lib/draw-canvas-edit/components/render/tools/asset-tool.js.map +1 -1
  42. package/lib/draw-canvas-edit/components/render/tools/component-factory.js.map +1 -1
  43. package/lib/draw-canvas-edit/components/render/tools/import-export-tool.d.ts +1 -0
  44. package/lib/draw-canvas-edit/components/render/tools/import-export-tool.d.ts.map +1 -1
  45. package/lib/draw-canvas-edit/components/render/tools/import-export-tool.js +35 -22
  46. package/lib/draw-canvas-edit/components/render/tools/import-export-tool.js.map +1 -1
  47. package/lib/draw-canvas-edit/components/render/types.d.ts +3 -0
  48. package/lib/draw-canvas-edit/components/render/types.d.ts.map +1 -1
  49. package/lib/draw-canvas-edit/components/render/types.js.map +1 -1
  50. package/lib/draw-canvas-edit/components/setting-form/index.d.ts.map +1 -1
  51. package/lib/draw-canvas-edit/components/setting-form/index.js +10 -0
  52. package/lib/draw-canvas-edit/components/setting-form/index.js.map +1 -1
  53. package/lib/draw-canvas-edit/index.d.ts.map +1 -1
  54. package/lib/draw-canvas-edit/index.js +5 -2
  55. package/lib/draw-canvas-edit/index.js.map +1 -1
  56. package/lib/draw-canvas-view/index.less +7 -0
  57. package/lib/draw-canvas-view/view.d.ts.map +1 -1
  58. package/lib/draw-canvas-view/view.js +16 -21
  59. package/lib/draw-canvas-view/view.js.map +1 -1
  60. package/lib/form/dtmpl-form.d.ts.map +1 -1
  61. package/lib/form/dtmpl-form.js +11 -2
  62. package/lib/form/dtmpl-form.js.map +1 -1
  63. package/lib/form/field-group-form.d.ts.map +1 -1
  64. package/lib/form/field-group-form.js +1 -1
  65. package/lib/form/field-group-form.js.map +1 -1
  66. package/lib/form/form-Item-group.d.ts +2 -1
  67. package/lib/form/form-Item-group.d.ts.map +1 -1
  68. package/lib/form/form-Item-group.js +2 -2
  69. package/lib/form/form-Item-group.js.map +1 -1
  70. package/lib/list/card-list/card-list-item.d.ts.map +1 -1
  71. package/lib/list/card-list/card-list-item.js +3 -2
  72. package/lib/list/card-list/card-list-item.js.map +1 -1
  73. package/lib/list/card-list/index.less +1 -1
  74. package/lib/lowcode-components/carousel/index.js +3 -3
  75. package/lib/lowcode-components/carousel/index.js.map +1 -1
  76. package/lib/module/ltmpl-table.d.ts +1 -0
  77. package/lib/module/ltmpl-table.d.ts.map +1 -1
  78. package/lib/module/ltmpl-table.js +2 -2
  79. package/lib/module/ltmpl-table.js.map +1 -1
  80. package/lib/table/act-table.d.ts.map +1 -1
  81. package/lib/table/act-table.js +21 -20
  82. package/lib/table/act-table.js.map +1 -1
  83. package/lib/table/column/column-builder.d.ts.map +1 -1
  84. package/lib/table/column/column-builder.js +16 -15
  85. package/lib/table/column/column-builder.js.map +1 -1
  86. package/lib/table/column/index.less +22 -0
  87. package/lib/table/query-table.js +2 -1
  88. package/lib/table/query-table.js.map +1 -1
  89. package/lib/table/relation-table.d.ts +1 -1
  90. package/lib/table/relation-table.d.ts.map +1 -1
  91. package/lib/table/relation-table.js +5 -24
  92. package/lib/table/relation-table.js.map +1 -1
  93. package/lib/tmpl/hc-data-source.d.ts +1 -0
  94. package/lib/tmpl/hc-data-source.d.ts.map +1 -1
  95. package/lib/tmpl/hc-data-source.js +9 -0
  96. package/lib/tmpl/hc-data-source.js.map +1 -1
  97. package/lib/tmpl/interface.d.ts +5 -0
  98. package/lib/tmpl/interface.d.ts.map +1 -1
  99. package/lib/tmpl/interface.js.map +1 -1
  100. package/package.json +2 -1
  101. package/src/aldehyde/controls/direct-file-view/index.tsx +5 -4
  102. package/src/aldehyde/controls/direct-upload/index.tsx +4 -3
  103. package/src/aldehyde/controls/entry-control.tsx +11 -1
  104. package/src/aldehyde/controls/select/index.tsx +25 -14
  105. package/src/aldehyde/controls/upload-read-excel/index.tsx +44 -0
  106. package/src/aldehyde/detail/dtmpl.css +5 -0
  107. package/src/aldehyde/detail/view/dtmpl-view.tsx +22 -2
  108. package/src/aldehyde/draw-canvas-edit/components/asset-bar/index.less +2 -2
  109. package/src/aldehyde/draw-canvas-edit/components/main-header/index.tsx +33 -2
  110. package/src/aldehyde/draw-canvas-edit/components/render/components/index.less +61 -0
  111. package/src/aldehyde/draw-canvas-edit/components/render/components/workstation-component.tsx +131 -15
  112. package/src/aldehyde/draw-canvas-edit/components/render/handlers/selection-handlers.ts +17 -5
  113. package/src/aldehyde/draw-canvas-edit/components/render/index.ts +16 -3
  114. package/src/aldehyde/draw-canvas-edit/components/render/tools/asset-tool.ts +21 -7
  115. package/src/aldehyde/draw-canvas-edit/components/render/tools/component-factory.ts +1 -1
  116. package/src/aldehyde/draw-canvas-edit/components/render/tools/import-export-tool.ts +34 -22
  117. package/src/aldehyde/draw-canvas-edit/components/render/types.ts +3 -0
  118. package/src/aldehyde/draw-canvas-edit/components/setting-form/index.tsx +12 -0
  119. package/src/aldehyde/draw-canvas-edit/index.tsx +5 -2
  120. package/src/aldehyde/draw-canvas-view/index.less +7 -0
  121. package/src/aldehyde/draw-canvas-view/view.tsx +18 -21
  122. package/src/aldehyde/form/dtmpl-form.tsx +20 -1
  123. package/src/aldehyde/form/field-group-form.tsx +6 -5
  124. package/src/aldehyde/form/form-Item-group.tsx +5 -4
  125. package/src/aldehyde/list/card-list/card-list-item.tsx +3 -2
  126. package/src/aldehyde/list/card-list/index.less +1 -1
  127. package/src/aldehyde/lowcode-components/carousel/index.tsx +3 -3
  128. package/src/aldehyde/lowcode-components/data.d.ts +1 -0
  129. package/src/aldehyde/module/ltmpl-table.tsx +3 -2
  130. package/src/aldehyde/table/act-table.tsx +13 -14
  131. package/src/aldehyde/table/column/column-builder.tsx +18 -17
  132. package/src/aldehyde/table/column/index.less +22 -0
  133. package/src/aldehyde/table/query-table.tsx +1 -1
  134. package/src/aldehyde/table/relation-table.tsx +6 -21
  135. package/src/aldehyde/tmpl/hc-data-source.tsx +8 -0
  136. package/src/aldehyde/tmpl/interface.tsx +5 -0
@@ -0,0 +1,61 @@
1
+ // 工位卡片
2
+ .workstation-card {
3
+ overflow: hidden;
4
+
5
+ .card-list,
6
+ .ant-card {
7
+ margin: 0;
8
+ }
9
+
10
+ .ant-card {
11
+ height: 100%;
12
+
13
+ .ant-card-body {
14
+ padding: 0 !important;
15
+ }
16
+ }
17
+
18
+ .card-list-item-head {
19
+ margin: 12px 0 8px;
20
+ }
21
+
22
+ .card-list-item-head-title {
23
+ .card-list-item-head-primary {
24
+ min-height: 0px !important;
25
+
26
+ .primary-column {
27
+ font-size: 16px !important;
28
+ }
29
+ }
30
+
31
+ .card-list-item-head-second {
32
+ min-height: 0px !important;
33
+ margin-top: 0px !important;
34
+
35
+ .second-column {
36
+ font-size: 12px !important;
37
+ opacity: 0.8;
38
+ }
39
+ }
40
+ }
41
+
42
+ .card-list-item-content {
43
+ padding: 0 12px;
44
+
45
+ .card-list-item-main {
46
+ background-color: transparent;
47
+ margin-bottom: 8px;
48
+ }
49
+
50
+ .card-show-cols {
51
+ padding: 0;
52
+ background-color: transparent;
53
+ }
54
+ }
55
+
56
+ .card-list-item-footer {
57
+ text-align: right;
58
+ padding: 8px;
59
+ border-top: 1px solid #e8e8e8;
60
+ }
61
+ }
@@ -1,32 +1,147 @@
1
1
  import React from "react";
2
+ import { createRoot } from 'react-dom/client';
2
3
  import Konva from 'konva';
4
+ import { Card, ConfigProvider } from "antd";
3
5
  import { Render } from '../index';
4
6
  import { AssetSettings } from '../types';
5
- import HcserviceV3 from "../../../../tmpl/hcservice-v3";
7
+ import LtmplTable from "../../../../module/ltmpl-table";
8
+ import { antdLangMp } from "../../../../index";
9
+ import "./index.less";
6
10
 
7
- export interface WorkstationSettings extends AssetSettings {
8
- workstationNumber: string; // 工位编号
11
+ // 详情卡片
12
+ const DetailCard = (props: { workstationNumber: string, pageId: string, locale: string }) => {
13
+ const { pageId, workstationNumber, locale } = props;
14
+
15
+ return <ConfigProvider locale={antdLangMp[locale]}>
16
+ <Card
17
+ className="workstation-card"
18
+ styles={{
19
+ body: { padding: 0 },
20
+ root: { width: 200, boxShadow: '0 2px 8px rgba(0,0,0,0.2)', border: "none" }
21
+ }}
22
+ >
23
+ <LtmplTable
24
+ sourceId={pageId}
25
+ funcMode="simple"
26
+ defShowType="cardList"
27
+ defaultCriteriaValue={{ thirdCriterion: workstationNumber }}
28
+ showPagination={false}
29
+ showAggregates={false}
30
+ defRowCardCount={1}
31
+ />
32
+ </Card >
33
+ </ConfigProvider >;
9
34
  }
10
35
 
11
- // 渲染工位详情卡片
12
- const renderCard = async (props: { node: Konva.Group, settings: WorkstationSettings, pointerPos: { x: number, y: number } }) => {
13
- const { node, settings, pointerPos } = props;
14
- const { pageId, pageConfig, workstationNumber } = settings;
15
- if (!node || !pageId || !workstationNumber || !pointerPos) return;
16
- const pageInfo = { pageNo: 1, pageSize: 1, thirdCriterion: workstationNumber };
17
- const queryKey = await HcserviceV3.requestLtmplQueryKey(null, pageId, pageInfo);
18
- const { entities } = await HcserviceV3.requestQueryData(null, queryKey.key, pageInfo);
19
- console.log("entitiesentities", entities);
20
- const renderCard = <div></div>;
36
+ export interface WorkstationSettings extends AssetSettings {
37
+ workstationNumber: string; // 工位编号
21
38
  }
22
39
 
23
40
  export class WorkstationComponent {
24
41
  private render: Render;
42
+ // 卡片挂载管理
43
+ private cardRoot: ReturnType<typeof createRoot> | null = null;
44
+ private cardWrapper: HTMLDivElement | null = null;
45
+ private currentNodeId: string | null = null; // 当前打开的卡片对应的工位标识
25
46
 
26
47
  constructor(render: Render) {
27
48
  this.render = render;
28
49
  }
29
50
 
51
+ // 关闭并销毁卡片,清理所有监听器
52
+ private closeCard = () => {
53
+ const stage = this.render?.stage;
54
+ if (stage) { // 清理 stage 事件监听
55
+ stage.off('click tap');
56
+ }
57
+ if (this.cardRoot) { // 销毁 React 根节点
58
+ this.cardRoot.unmount();
59
+ this.cardRoot = null;
60
+ }
61
+ if (this.cardWrapper) { // 移除 DOM 节点
62
+ this.cardWrapper.remove();
63
+ this.cardWrapper = null;
64
+ }
65
+ this.currentNodeId = null;
66
+ };
67
+
68
+ // 根据容器边界重新计算卡片位置,避免被遮挡
69
+ private adjustCardPosition = (pointerPos: { x: number; y: number }) => {
70
+ if (!this.cardWrapper || !this.render.stage) return;
71
+ const container = this.render.stage.getContent();
72
+ const containerWidth = container.clientWidth;
73
+ const containerHeight = container.clientHeight;
74
+ const cardWidth = this.cardWrapper.offsetWidth;
75
+ const cardHeight = this.cardWrapper.offsetHeight;
76
+ if (!cardWidth || !cardHeight) return;
77
+ const padding = 8; // 距离容器边缘的最小间距
78
+ let translateX = -(cardWidth / 2); // 默认水平居中于指针
79
+ let translateY = -(cardHeight + padding); // 默认显示在指针上方
80
+ // 水平边界处理:卡片左侧超出
81
+ const cardLeft = pointerPos.x + translateX;
82
+ if (cardLeft < padding) {
83
+ translateX = padding - pointerPos.x;
84
+ }
85
+ // 水平边界处理:卡片右侧超出
86
+ const cardRight = pointerPos.x + translateX + cardWidth;
87
+ if (cardRight > containerWidth - padding) {
88
+ translateX = containerWidth - padding - cardWidth - pointerPos.x;
89
+ }
90
+ // 垂直边界处理:卡片上方超出 → 尝试翻转到指针下方
91
+ const cardTop = pointerPos.y + translateY;
92
+ if (cardTop < padding) {
93
+ // 检查下方空间是否足够
94
+ const cardBottomWhenBelow = pointerPos.y + padding + cardHeight;
95
+ if (cardBottomWhenBelow <= containerHeight - padding) {
96
+ translateY = padding; // 翻转到指针下方
97
+ } else {
98
+ // 上下空间都不足,强制贴住容器上边缘
99
+ translateY = padding - pointerPos.y;
100
+ }
101
+ } else {
102
+ // 即使上方不超出,也检查下方是否被容器底部裁切
103
+ const cardBottom = cardTop + cardHeight;
104
+ if (cardBottom > containerHeight - padding) {
105
+ translateY = padding; // 翻转到下方
106
+ }
107
+ }
108
+ this.cardWrapper.style.left = `${pointerPos.x}px`;
109
+ this.cardWrapper.style.top = `${pointerPos.y}px`;
110
+ this.cardWrapper.style.transform = `translate(${translateX}px, ${translateY}px)`;
111
+ };
112
+
113
+ // 渲染工位详情卡片
114
+ private renderCard = async (props: { node: Konva.Group, settings: WorkstationSettings, pointerPos: { x: number, y: number } }) => {
115
+ const { node, settings, pointerPos } = props;
116
+ const { pageId, workstationNumber } = settings;
117
+ if (!node || !pageId || !workstationNumber || !pointerPos) return;
118
+ // 如果同一个工位的卡片已经渲染,不再重复渲染
119
+ const nodeId = node.id();
120
+ if (this.currentNodeId === nodeId) return;
121
+ this.closeCard(); // 先关闭已有卡片(不同工位时)
122
+ const container = this.render.stage.getContent();
123
+ this.cardWrapper = document.createElement('div');
124
+ this.cardWrapper.style.cssText = `
125
+ position:absolute;
126
+ left:${pointerPos.x}px;
127
+ top:${pointerPos.y}px;
128
+ transform:translate(-50%, calc(-100% - 8px));
129
+ z-index:1000;
130
+ `;
131
+ // 用原生监听器在 wrapper 层截断事件,阻止冒泡到 container(Konva 监听层)
132
+ // this.cardWrapper.addEventListener('click', (e) => e.stopImmediatePropagation());
133
+ this.cardWrapper.addEventListener('mousedown', (e) => e.stopImmediatePropagation());
134
+ container.appendChild(this.cardWrapper); // 挂载到 container 下面
135
+ this.cardRoot = createRoot(this.cardWrapper);
136
+ this.cardRoot.render(<DetailCard workstationNumber={workstationNumber} pageId={pageId} locale={this.render?.config?.locale} />);
137
+ this.currentNodeId = nodeId; // 记录当前打开的工位
138
+ this.render.stage.on('click tap', this.closeCard); // 点击画布外关闭卡片
139
+ this.render.stage.on('wheel', this.closeCard); // 缩放画布关闭卡片
140
+ this.render.stage.on('dragmove', this.closeCard); // 平移画布关闭卡片
141
+ // 渲染完成后根据容器边界重新校准卡片位置,避免被遮挡
142
+ requestAnimationFrame(() => this.adjustCardPosition(pointerPos));
143
+ };
144
+
30
145
  /**
31
146
  * 创建工位组件
32
147
  */
@@ -97,8 +212,9 @@ export class WorkstationComponent {
97
212
  componentOnClick(node: Konva.Group, settings: WorkstationSettings) {
98
213
  node.off("click");
99
214
  node.on('click', (e) => {
100
- const pointerPos = e.target.getStage().getPointerPosition();
101
- renderCard({ node, settings, pointerPos })
215
+ e.cancelBubble = true; // 阻止事件向上冒泡到 Stage
216
+ const pointerPos = e.target.getStage()?.getPointerPosition();
217
+ this.renderCard({ node, settings, pointerPos });
102
218
  });
103
219
  }
104
220
  }
@@ -141,16 +141,28 @@ export class SelectionHandlers implements Handler {
141
141
  } else if (parent instanceof Konva.Transformer) {
142
142
  // transformer 点击事件交给 transformer 自己的 handler
143
143
  } else if (parent instanceof Konva.Group) {
144
- // (判断 ctrlKey 为了排查 mac 拖动快捷键)
145
- if (e.evt.button === MouseButton["左键"] && !e.evt.ctrlKey) {
144
+ if (e.evt.button === MouseButton["左键"]) {
146
145
  if (!this.render.ignore(parent) && !this.render.ignoreDraw(e.target)) {
147
146
  // 锁定节点:在 select 之前先禁用 Konva 内置拖拽,防止 race condition
148
147
  if (parent.attrs.locked) {
149
148
  parent.draggable(false);
150
149
  }
151
- if (e.evt.ctrlKey || e.evt.metaKey) { // 新增多选
152
- this.render.selectionTool.select([...this.render.selectionTool.selectingNodes, parent]);
153
- } else { // 单选
150
+ if (e.evt.ctrlKey || e.evt.metaKey) { // Ctrl+点选:切换选择
151
+ const idx = this.render.selectionTool.selectingNodes.findIndex((n) => n.id() === parent.id());
152
+ if (idx >= 0) {
153
+ // 已选中则移除
154
+ const nodes = [...this.render.selectionTool.selectingNodes];
155
+ nodes.splice(idx, 1);
156
+ if (nodes.length > 0) {
157
+ this.render.selectionTool.select(nodes);
158
+ } else {
159
+ this.render.selectionTool.selectingClear();
160
+ }
161
+ } else {
162
+ // 未选中则添加
163
+ this.render.selectionTool.select([...this.render.selectionTool.selectingNodes, parent]);
164
+ }
165
+ } else { // 普通点击:单选
154
166
  this.render.selectionTool.select([parent]);
155
167
  // 单选时无法通过 transformer 获取拖动初始位置 transformerMousedownPos
156
168
  // 此时直接取目标的 getClientRect 位置
@@ -540,7 +540,11 @@ export class Render {
540
540
 
541
541
  // 获取素材设置
542
542
  getAssetSettings(asset?: Konva.Node): AssetSettings {
543
- const base = asset?.attrs.assetSettings ?? { ...Render.AssetSettingsDefault };
543
+ if (!asset) {
544
+ return { ...Render.AssetSettingsDefault };
545
+ }
546
+ const attrs = asset.attrs;
547
+ const base = attrs.assetSettings ?? { ...Render.AssetSettingsDefault };
544
548
  return {
545
549
  // 特定
546
550
  ...base,
@@ -554,9 +558,11 @@ export class Render {
554
558
  x: parseFloat((asset?.position().x ?? 0).toFixed(1)),
555
559
  y: parseFloat((asset?.position().y ?? 0).toFixed(1)),
556
560
  rotation: parseFloat((asset?.rotation() ?? 0).toFixed(1)),
561
+ width: parseFloat(((attrs.width ?? 0) * (asset?.scaleX?.() ?? 1)).toFixed(1)),
562
+ height: parseFloat(((attrs.height ?? 0) * (asset?.scaleY?.() ?? 1)).toFixed(1)),
557
563
  tension:
558
- asset?.attrs.assetType === AssetType.Graph &&
559
- asset?.attrs.graphType === "Curve"
564
+ attrs.assetType === AssetType.Graph &&
565
+ attrs.graphType === "Curve"
560
566
  ? base.tension
561
567
  : undefined
562
568
  };
@@ -702,6 +708,13 @@ export class Render {
702
708
  }
703
709
  }
704
710
  }
711
+ // 通过 scale 实现视觉尺寸变更(设置 Group 的 width/height 只影响裁剪,不会缩放内部子元素)
712
+ if (settings.width || settings.height) {
713
+ const baseWidth = asset.width() || 1;
714
+ const baseHeight = asset.height() || 1;
715
+ asset.scaleX(settings.width / baseWidth);
716
+ asset.scaleY(settings.height / baseHeight);
717
+ }
705
718
  asset.setAttr('assetSettings', settings);
706
719
  if (update) {
707
720
  // 更新历史
@@ -6,14 +6,23 @@ const gifler = (window as any).gifler;
6
6
 
7
7
  export class AssetTool {
8
8
  private render: Render;
9
+ // 同一个 svgXML 内容只转一次 Blob,避免重复创建 Blob URL
10
+ private svgBlobCache: Map<string, string> = new Map();
11
+ // 同地址 svg 只 fetch 一次,避免重复网络请求
12
+ private svgRawCache: Map<string, string> = new Map();
13
+
9
14
  constructor(render: Render) {
10
15
  this.render = render;
11
16
  }
12
17
 
13
- // 加载 svg xml
14
- async loadSvgXML(svgXML: string) {
15
- const blob = new Blob([svgXML], { type: 'image/svg+xml' });
16
- const url = URL.createObjectURL(blob);
18
+ // 加载 svg xml(带 Blob 缓存)
19
+ loadSvgXML(svgXML: string) {
20
+ let url = this.svgBlobCache.get(svgXML);
21
+ if (!url) {
22
+ const blob = new Blob([svgXML], { type: 'image/svg+xml' });
23
+ url = URL.createObjectURL(blob);
24
+ this.svgBlobCache.set(svgXML, url);
25
+ }
17
26
  return new Promise<Konva.Image>((resolve) => {
18
27
  Konva.Image.fromURL(url, (imageNode) => {
19
28
  imageNode.setAttrs({ svgXML } as any);
@@ -22,10 +31,15 @@ export class AssetTool {
22
31
  });
23
32
  }
24
33
 
25
- // 加载 svg
34
+ // 加载 svg(带 fetch 缓存 + Blob 缓存)
26
35
  async loadSvg(src: string) {
27
- const svgXML = await (await fetch(src)).text();
28
- return this.loadSvgXML(this.render.setSvgXMLSettings(svgXML, this.render.getAssetSettings()));
36
+ let rawSvg = this.svgRawCache.get(src);
37
+ if (!rawSvg) {
38
+ rawSvg = await (await fetch(src)).text();
39
+ this.svgRawCache.set(src, rawSvg);
40
+ }
41
+ const svgXML = this.render.setSvgXMLSettings(rawSvg, this.render.getAssetSettings());
42
+ return this.loadSvgXML(svgXML);
29
43
  }
30
44
 
31
45
  // 加载 gif
@@ -31,7 +31,7 @@ export class ComponentFactory {
31
31
  */
32
32
  async createImageComponent(options: CreateImageComponentOptions) {
33
33
  const { src, type, componentType } = options;
34
- const { pageHeight, pageWidth } = this.render.getPageSettings()
34
+ const { pageHeight, pageWidth } = this.render.getPageSettings();
35
35
  const target = await this.render.assetTool[{ svg: "loadSvg", gif: "loadGif" }[type] || "loadImg"](handleImgUrl(src));
36
36
  let width = target.width();
37
37
  let height = target.height();
@@ -99,34 +99,46 @@ export class ImportExportTool {
99
99
  });
100
100
  }
101
101
 
102
- // 恢复图片(用于导入)
103
- async restoreImage(nodes: Konva.Node[] = []) {
104
- for (const node of nodes) {
102
+ // 汇总图片组件
103
+ getImgNodes(nodes: Konva.Node[] = []) {
104
+ const temNodes = nodes.map(node => {
105
105
  if (node instanceof Konva.Group) {
106
- // 递归
107
- await this.restoreImage(node.getChildren());
106
+ return this.getImgNodes(node.getChildren());
108
107
  } else if (node instanceof Konva.Image) {
108
+ return node;
109
+ }
110
+ }).flat(Infinity).filter(Boolean);
111
+ return temNodes;
112
+ }
113
+
114
+ // 恢复图片(用于导入)
115
+ async restoreImage(nodes: Konva.Node[] = []) {
116
+ const imgNodes = this.getImgNodes(nodes);
117
+ const tasks = imgNodes.map(node => {
118
+ // 处理图片
119
+ if (node.attrs.svgXML) { // svg 素材
120
+ return this.render.assetTool.loadSvgXML(node.attrs.svgXML);
121
+ } else if (node.attrs.gif) { // gif 素材
122
+ return this.render.assetTool.loadGif(node.attrs.gif);
123
+ } else if (node.attrs.src) { // 其他图片素材
124
+ return this.loadImage(node.attrs.src);
125
+ }
126
+ });
127
+ Promise.all(tasks).then(res => {
128
+ res.forEach((imageNode, index) => {
129
+ const node = imgNodes[index];
109
130
  // 处理图片
110
- if (node.attrs.svgXML) { // svg 素材
111
- const blob = new Blob([node.attrs.svgXML], { type: 'image/svg+xml' });
112
- const url = URL.createObjectURL(blob);
113
- const image = await this.loadImage(url);
114
- if (image) { // 设置图片
115
- node.image(image);
116
- }
117
- } else if (node.attrs.gif) { // gif 素材
118
- const imageNode = await this.render.assetTool.loadGif(node.attrs.gif);
119
- if (imageNode) {
131
+ if (imageNode && node) {
132
+ if (node.attrs.svgXML) { // svg 素材
120
133
  node.image(imageNode.image());
121
- }
122
- } else if (node.attrs.src) { // 其他图片素材
123
- const image = await this.loadImage(node.attrs.src);
124
- if (image) {
125
- node.image(image);
134
+ } else if (node.attrs.gif) { // gif 素材
135
+ node.image(imageNode.image());
136
+ } else if (node.attrs.src) { // 其他图片素材
137
+ node.image(imageNode);
126
138
  }
127
139
  }
128
- }
129
- }
140
+ })
141
+ });
130
142
  }
131
143
 
132
144
  // 恢复图形
@@ -15,6 +15,7 @@ export interface RenderConfig {
15
15
  attractNode?: boolean;
16
16
  readonly?: boolean;
17
17
  themeToken?: { [key: string]: any };
18
+ locale?: string; // 语言
18
19
  }
19
20
 
20
21
  export type RenderEvents = {
@@ -274,6 +275,8 @@ export interface AssetSettings {
274
275
  text: string;
275
276
  x: number;
276
277
  y: number;
278
+ width?: number;
279
+ height?: number;
277
280
  rotation: number;
278
281
  tension: number;
279
282
  dataSource?: string;
@@ -27,6 +27,17 @@ const positionSetting = {
27
27
  ]
28
28
  };
29
29
 
30
+ // 尺寸配置
31
+ const sizeSetting = {
32
+ title: "宽高",
33
+ isGroup: true,
34
+ fields: [
35
+ { title: "宽", noStyle: true, id: "width", extControlType: "float", colSpan: 1, min: 0, style: { width: "100%" } },
36
+ { title: "高", noStyle: true, id: "height", extControlType: "float", colSpan: 1, min: 0, style: { width: "100%" } }
37
+ ]
38
+ };
39
+
40
+
30
41
  // 旋转角度配置
31
42
  const rotationSetting = { fields: [{ title: "旋转角度", id: "rotation", extControlType: "int", min: -3600, max: 3600, style: { width: "100%" } }] };
32
43
 
@@ -39,6 +50,7 @@ const baseGraphSetting = [
39
50
  { title: "填充颜色", id: "fill", extControlType: "color", style: { width: "100%" } },
40
51
  ]
41
52
  },
53
+ sizeSetting,
42
54
  positionSetting,
43
55
  rotationSetting
44
56
  ];
@@ -9,6 +9,7 @@ import MainHeader from './components/main-header';
9
9
  import AssetBar from './components/asset-bar';
10
10
  import SettingForm from "./components/setting-form";
11
11
  import type Konva from 'konva';
12
+ import { useLocale } from "../index";
12
13
  import './index.less';
13
14
 
14
15
  interface Props {
@@ -33,6 +34,7 @@ const Index = (props: Props) => {
33
34
  const assetCurrentRef = useRef<Konva.Node>(null); // 当前选中组件Ref
34
35
  const [loading, setLoading] = useState<boolean>(false);
35
36
  const { token: themeToken } = theme.useToken();
37
+ const { getLocale } = useLocale();
36
38
 
37
39
  // 获取视图数据
38
40
  const getViewData = async () => {
@@ -48,10 +50,10 @@ const Index = (props: Props) => {
48
50
  const decompressedBuffer = gunzipSync(temByteArray);
49
51
  // 2. 将 Uint8Array 转换回普通字符串
50
52
  const originalData = strFromU8(decompressedBuffer);
51
- setLoading(false);
52
53
  // 重置画面数据
53
54
  await renderRef.current.importExportTool.restore(originalData as string);
54
55
  await renderRef.current.positionTool.positionFit(); // 画布默认自适应居中
56
+ setLoading(false);
55
57
  }
56
58
 
57
59
  useEffect(() => {
@@ -63,12 +65,13 @@ const Index = (props: Props) => {
63
65
  // 初始化渲染器
64
66
  const initRender = useCallback((width: number, height: number) => {
65
67
  if (renderRef.current || !stageRef.current) return;
68
+ const locale = getLocale();
66
69
  const render = new Render(stageRef.current, {
67
70
  width, height,
68
71
  showBg: true, showRuler: true, showRefLine: true,
69
72
  showPreview: false, showContextmenu: true,
70
73
  attractResize: true, attractBg: true, attractNode: true,
71
- themeToken
74
+ themeToken, locale
72
75
  });
73
76
  renderRef.current = render;
74
77
  // 初始化设置
@@ -33,9 +33,16 @@
33
33
  }
34
34
  }
35
35
 
36
+ .ant-spin,
37
+ .ant-spin-container {
38
+ width: 100%;
39
+ height: 100%;
40
+ }
41
+
36
42
  // 画布区域
37
43
  .draw-viewer-content {
38
44
  width: 100%;
45
+ height: 100%;
39
46
  flex-grow: 1;
40
47
  background-color: var(--ant-layout-body-bg);
41
48
  position: relative;
@@ -1,11 +1,11 @@
1
- import React, { useEffect, useRef, useCallback } from 'react';
2
- import { Space, Button } from "antd";
1
+ import React, { useEffect, useRef, useCallback, useState } from 'react';
2
+ import { Space, Button, Spin } from "antd";
3
3
  import { ExpandOutlined } from '@ant-design/icons';
4
4
  import chunk from "lodash/chunk";
5
5
  import HcserviceV3 from "../tmpl/hcservice-v3";
6
6
  import WebSocket from "../tmpl/web-socket";
7
7
  import { Render } from '../draw-canvas-edit/components/render';
8
- import { LtmplConfig, LtmplConfigRes } from "../tmpl/interface";
8
+ import { useLocale } from "../index";
9
9
  import './index.less';
10
10
 
11
11
  // 数据源请求结果
@@ -35,12 +35,14 @@ const Index = (props: ViewerProps) => {
35
35
  const observerRef = useRef<ResizeObserver | null>(null);
36
36
  const wsRef = useRef(undefined); // 实时数据websocket连接
37
37
  const subscribeRef = useRef(undefined); // 实时数据订阅
38
- const dataPageConfigRef = useRef<{ [key: string]: LtmplConfig }>({}); // 数据页面配置
38
+ const [loading, setLoading] = useState<boolean>(false);
39
+ const { getLocale } = useLocale();
39
40
 
40
41
  // 初始化渲染器(只读模式)
41
42
  const initRender = useCallback(
42
43
  (width: number, height: number) => {
43
44
  if (renderRef.current || !stageRef.current) return;
45
+ const locale = getLocale();
44
46
  const render = new Render(stageRef.current, {
45
47
  width,
46
48
  height,
@@ -49,7 +51,8 @@ const Index = (props: ViewerProps) => {
49
51
  showPreview: false, // 不需要预览框
50
52
  showContextmenu: false, // 不需要右键菜单
51
53
  showBg: true,
52
- readonly: true // 只读模式
54
+ readonly: true, // 只读模式
55
+ locale
53
56
  });
54
57
  renderRef.current = render;
55
58
  // 如果有数据,恢复画布
@@ -61,7 +64,7 @@ const Index = (props: ViewerProps) => {
61
64
  );
62
65
 
63
66
  // 更新指定组件设置
64
- const updateComponentSetting = (node: { [key: string]: any }, data: DataSource, pageId: string, pageConfig: LtmplConfig) => {
67
+ const updateComponentSetting = (node: { [key: string]: any }, data: DataSource, pageId: string) => {
65
68
  const { componentType, id, settings } = node;
66
69
  let temSetting;
67
70
  switch (componentType) {
@@ -73,23 +76,13 @@ const Index = (props: ViewerProps) => {
73
76
  default:
74
77
  break;
75
78
  }
76
- temSetting && renderRef.current.updateAssetById(id, { ...temSetting, pageId, pageConfig });
79
+ temSetting && renderRef.current.updateAssetById(id, { ...temSetting, pageId });
77
80
  }
78
81
 
79
- // 查询关联页面配置
80
- const getDataPageConfig = async (sourceId: string) => {
81
- if (!sourceId) return null;
82
- if (dataPageConfigRef.current[sourceId]) return dataPageConfigRef.current[sourceId];
83
- let ltmplConfigRes: LtmplConfigRes = await HcserviceV3.requestLtmplConfig(null, sourceId);
84
- dataPageConfigRef.current = { ...dataPageConfigRef.current, [sourceId]: ltmplConfigRes.ltmplConfig };
85
- return ltmplConfigRes.ltmplConfig;
86
- };
87
-
88
82
  // 更新组件数据->更新画面
89
83
  const updateAssetSettings = async (nodes: { [key: string]: any }[], data: DataSource) => {
90
84
  if (!renderRef.current || !nodes?.length || !data) return;
91
- const pageConfig = await getDataPageConfig(data.config?.pageId);
92
- nodes.forEach(node => updateComponentSetting(node, data, data.config?.pageId, pageConfig));
85
+ nodes.forEach(node => updateComponentSetting(node, data, data.config?.pageId));
93
86
  };
94
87
 
95
88
  // 整理组件数据源
@@ -163,6 +156,7 @@ const Index = (props: ViewerProps) => {
163
156
 
164
157
  // 加载画布数据
165
158
  const loadData = useCallback(async (render: Render, jsonData: string) => {
159
+ setLoading(true);
166
160
  try {
167
161
  await render.importExportTool.restore(jsonData);
168
162
  // 数据加载完成后自适应居中
@@ -171,6 +165,7 @@ const Index = (props: ViewerProps) => {
171
165
  } catch (e) {
172
166
  console.error('Failed to load canvas data:', e);
173
167
  }
168
+ setLoading(false);
174
169
  }, []);
175
170
 
176
171
  // 监听容器尺寸变化
@@ -230,9 +225,11 @@ const Index = (props: ViewerProps) => {
230
225
  </Space>
231
226
  </div>
232
227
  {/* 画布区域 */}
233
- <div className="draw-viewer-content" ref={boardRef}>
234
- <div ref={stageRef} />
235
- </div>
228
+ <Spin spinning={loading}>
229
+ <div className="draw-viewer-content" ref={boardRef}>
230
+ <div ref={stageRef} />
231
+ </div>
232
+ </Spin>
236
233
  </div>
237
234
  );
238
235
  };