aldehyde 0.2.481 → 0.2.483

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 (138) 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 +26 -16
  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.d.ts +5 -0
  88. package/lib/table/query-table.d.ts.map +1 -1
  89. package/lib/table/query-table.js +17 -5
  90. package/lib/table/query-table.js.map +1 -1
  91. package/lib/table/relation-table.d.ts +1 -1
  92. package/lib/table/relation-table.d.ts.map +1 -1
  93. package/lib/table/relation-table.js +5 -24
  94. package/lib/table/relation-table.js.map +1 -1
  95. package/lib/tmpl/hc-data-source.d.ts +1 -0
  96. package/lib/tmpl/hc-data-source.d.ts.map +1 -1
  97. package/lib/tmpl/hc-data-source.js +9 -0
  98. package/lib/tmpl/hc-data-source.js.map +1 -1
  99. package/lib/tmpl/interface.d.ts +5 -0
  100. package/lib/tmpl/interface.d.ts.map +1 -1
  101. package/lib/tmpl/interface.js.map +1 -1
  102. package/package.json +2 -1
  103. package/src/aldehyde/controls/direct-file-view/index.tsx +5 -4
  104. package/src/aldehyde/controls/direct-upload/index.tsx +4 -3
  105. package/src/aldehyde/controls/entry-control.tsx +11 -1
  106. package/src/aldehyde/controls/select/index.tsx +25 -14
  107. package/src/aldehyde/controls/upload-read-excel/index.tsx +44 -0
  108. package/src/aldehyde/detail/dtmpl.css +5 -0
  109. package/src/aldehyde/detail/view/dtmpl-view.tsx +22 -2
  110. package/src/aldehyde/draw-canvas-edit/components/asset-bar/index.less +2 -2
  111. package/src/aldehyde/draw-canvas-edit/components/main-header/index.tsx +33 -2
  112. package/src/aldehyde/draw-canvas-edit/components/render/components/index.less +61 -0
  113. package/src/aldehyde/draw-canvas-edit/components/render/components/workstation-component.tsx +131 -15
  114. package/src/aldehyde/draw-canvas-edit/components/render/handlers/selection-handlers.ts +17 -5
  115. package/src/aldehyde/draw-canvas-edit/components/render/index.ts +16 -3
  116. package/src/aldehyde/draw-canvas-edit/components/render/tools/asset-tool.ts +21 -7
  117. package/src/aldehyde/draw-canvas-edit/components/render/tools/component-factory.ts +1 -1
  118. package/src/aldehyde/draw-canvas-edit/components/render/tools/import-export-tool.ts +34 -22
  119. package/src/aldehyde/draw-canvas-edit/components/render/types.ts +3 -0
  120. package/src/aldehyde/draw-canvas-edit/components/setting-form/index.tsx +12 -0
  121. package/src/aldehyde/draw-canvas-edit/index.tsx +5 -2
  122. package/src/aldehyde/draw-canvas-view/index.less +7 -0
  123. package/src/aldehyde/draw-canvas-view/view.tsx +18 -21
  124. package/src/aldehyde/form/dtmpl-form.tsx +20 -1
  125. package/src/aldehyde/form/field-group-form.tsx +6 -5
  126. package/src/aldehyde/form/form-Item-group.tsx +5 -4
  127. package/src/aldehyde/list/card-list/card-list-item.tsx +3 -2
  128. package/src/aldehyde/list/card-list/index.less +1 -1
  129. package/src/aldehyde/lowcode-components/carousel/index.tsx +3 -3
  130. package/src/aldehyde/lowcode-components/data.d.ts +1 -0
  131. package/src/aldehyde/module/ltmpl-table.tsx +3 -2
  132. package/src/aldehyde/table/act-table.tsx +13 -14
  133. package/src/aldehyde/table/column/column-builder.tsx +29 -17
  134. package/src/aldehyde/table/column/index.less +22 -0
  135. package/src/aldehyde/table/query-table.tsx +15 -5
  136. package/src/aldehyde/table/relation-table.tsx +6 -21
  137. package/src/aldehyde/tmpl/hc-data-source.tsx +8 -0
  138. package/src/aldehyde/tmpl/interface.tsx +5 -0
@@ -0,0 +1,44 @@
1
+ import React, { useContext } from "react";
2
+ import { Upload, Button, message } from 'antd';
3
+ import { UploadOutlined } from '@ant-design/icons';
4
+ import * as XLSX from 'xlsx';
5
+ import { LocaleContext } from "../../locale/LocaleProvider";
6
+
7
+ // 读取Excel文件
8
+
9
+ interface Props {
10
+ onChangeData: (data: { [key: string]: any }[]) => void; // 数据更新
11
+ children?: React.JSX.Element;
12
+ }
13
+
14
+ const Index = (props: Props) => {
15
+ const { onChangeData, children } = props;
16
+ const { translate } = useContext(LocaleContext);
17
+
18
+ const handleUpload = (file) => {
19
+ const isXlsx = file.name.toLowerCase().endsWith('.xlsx');
20
+ if (!isXlsx) {
21
+ message.warning(translate('${仅支持上传.xlsx格式}'));
22
+ return false;
23
+ }
24
+ const reader = new FileReader();
25
+ reader.readAsArrayBuffer(file);
26
+ reader.onload = e => {
27
+ const buffer = new Uint8Array(e.target.result as ArrayBuffer);
28
+ const wb = XLSX.read(buffer, { type: 'array' });
29
+ const sheet = wb.Sheets[wb.SheetNames[0]];
30
+ const data = XLSX.utils.sheet_to_json(sheet, { defval: '' });
31
+ onChangeData(data);
32
+ };
33
+ // 阻止自动上传
34
+ return false;
35
+ };
36
+
37
+ return (
38
+ <Upload accept=".xlsx" beforeUpload={handleUpload} showUploadList={false}>
39
+ {children || <Button icon={<UploadOutlined />}>上传Excel</Button>}
40
+ </Upload>
41
+ );
42
+ };
43
+
44
+ export default Index;
@@ -44,6 +44,11 @@
44
44
  margin: 0;
45
45
  }
46
46
 
47
+ .detailPage .actTable h3 {
48
+ padding: 0;
49
+ margin-bottom: 0.5em;
50
+ }
51
+
47
52
  .detailPage.showRightNav .ant-card {
48
53
  min-width: 740px;
49
54
  max-width: 1440px;
@@ -111,7 +111,7 @@ class DtmplView extends React.PureComponent<DtmplViewProps, DtmplViewStat> {
111
111
  serverKey,
112
112
  mainCode,
113
113
  showRightNav,
114
- parentId, maxColsOnRow,
114
+ parentId, maxColsOnRow, code
115
115
  } = this.props;
116
116
  const { dtmplData, loading, id } = this.state;
117
117
  const { pageLayout } = dtmplConfig;
@@ -120,7 +120,7 @@ class DtmplView extends React.PureComponent<DtmplViewProps, DtmplViewStat> {
120
120
  groups = dtmplConfig.groups;
121
121
  }
122
122
 
123
- return groups.map((item) => {
123
+ const groupElements = groups.map((item) => {
124
124
  if (item.type === "embed") {
125
125
  if (!dtmplData) {
126
126
  return null;
@@ -253,6 +253,26 @@ class DtmplView extends React.PureComponent<DtmplViewProps, DtmplViewStat> {
253
253
  );
254
254
  }
255
255
  });
256
+
257
+ if (dtmplConfig.viewRactions?.length) { // 关系查询
258
+ const viewRactions = dtmplConfig.viewRactions.map(item => {
259
+ const children = <LtmplTable funcMode="common" readOnly={true} serverKey={serverKey} sourceId={item.id} mainCode={code} />;
260
+ return pageLayout === "horizontalTabs" ? { key: item.id, label: item.title, children } :
261
+ <CollapseCard
262
+ title={item.title}
263
+ key={item.id}
264
+ id={id + item.id}
265
+ className="hoverable"
266
+ styles={{ header: { background: "#f2f4f5" } }}
267
+ loading={loading}
268
+ >
269
+ {children}
270
+ </CollapseCard>;
271
+ });
272
+ groupElements.push(...viewRactions);
273
+ }
274
+
275
+ return groupElements;
256
276
  };
257
277
 
258
278
  render() {
@@ -27,8 +27,8 @@
27
27
  top: 6px;
28
28
  left: 6px;
29
29
  object-fit: contain;
30
- width: calc(100% - 12px);
31
- height: calc(100% - 12px);
30
+ width: calc(~"100% - 12px");
31
+ height: calc(~"100% - 12px");
32
32
  pointer-events: none;
33
33
  }
34
34
  }
@@ -1,6 +1,6 @@
1
1
  import React, { useState, useEffect, useCallback, useMemo } from 'react';
2
- import { Dropdown, Modal, Space, Button, Divider, Tooltip } from 'antd';
3
- import { ImportOutlined, ExportOutlined, SaveOutlined, QuestionCircleOutlined, MergeCellsOutlined, NumberOutlined } from '@ant-design/icons';
2
+ import { Dropdown, Modal, Space, Button, Divider, Tooltip, message } from 'antd';
3
+ import { ImportOutlined, ExportOutlined, SaveOutlined, QuestionCircleOutlined, MergeCellsOutlined, NumberOutlined, SettingOutlined } from '@ant-design/icons';
4
4
  import { gzipSync, strToU8 } from 'fflate';
5
5
  import { uint8ArrayToHex } from 'uint8array-extras';
6
6
  import HcserviceV3 from "../../../tmpl/hcservice-v3";
@@ -8,6 +8,7 @@ import IconFont from "../../../icon/aliIcon";
8
8
  import { Render } from '../render';
9
9
  import { GraphType, LinkType, AlignType } from '../render/types';
10
10
  import * as Draws from '../render/draws';
11
+ import UploadReadExcel from "../../../controls/upload-read-excel";
11
12
  import './index.less';
12
13
 
13
14
  // 布局类型
@@ -78,6 +79,29 @@ const MainHeader = (props: MainHeaderProps) => {
78
79
  input.click();
79
80
  }, [render]);
80
81
 
82
+ // 批量设置组件属性
83
+ const handleBatchSetNodeConfig = useCallback((data: { [key: string]: any }[]) => {
84
+ if (!render || !data?.length) return;
85
+ const allAssets = render.getAllAssets();
86
+ data.forEach((item) => {
87
+ let nodeId, setting;
88
+ switch (item["组件类型"]) {
89
+ case "workstation":
90
+ const order = item["序号"];
91
+ const workstationNumber = item["工位编号"];
92
+ nodeId = allAssets.find((asset) => asset.componentType === "workstation" && asset.settings.order === order)?.id;
93
+ setting = { workstationNumber };
94
+ break;
95
+ default:
96
+ break;
97
+ }
98
+ if (nodeId) {
99
+ render.updateAssetById(nodeId, setting);
100
+ }
101
+ });
102
+ message.success("设置成功");
103
+ }, [render]);
104
+
81
105
  // 保存
82
106
  const handleSave = async () => {
83
107
  if (!recordCode) {
@@ -210,6 +234,13 @@ const MainHeader = (props: MainHeaderProps) => {
210
234
  <Tooltip title="快捷键">
211
235
  <Button type="text" onClick={() => setShowShortCut(true)} ><QuestionCircleOutlined /></Button>
212
236
  </Tooltip>
237
+ <Tooltip title="组件属性批量配置">
238
+ <>
239
+ <UploadReadExcel onChangeData={handleBatchSetNodeConfig} >
240
+ <Button type="text" ><SettingOutlined /></Button>
241
+ </UploadReadExcel>
242
+ </>
243
+ </Tooltip>
213
244
  <Tooltip title="导入">
214
245
  <Button type="text" onClick={handleRestore} ><ImportOutlined /></Button>
215
246
  </Tooltip>
@@ -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
  ];