openxiangda 1.0.18 → 1.0.20

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.
@@ -74,6 +74,8 @@ When the user provides a root domain such as `https://yida.wisejob.cn/`, use it
74
74
  - Before publishing to another platform, verify the workspace is bound for that profile. Resource IDs from one profile must not be reused for another profile.
75
75
  - Use `openxiangda app snapshot <APP_XXX> --profile <name> --json` for diagnosis before changing an existing app.
76
76
  - Run write commands that update `.openxiangda/state.json` sequentially within the same workspace. Read-only commands can run in parallel.
77
+ - JS_CODE is backend-executed workflow/automation logic, not frontend page code. Use it when logic must run after a backend trigger such as fixed cron schedules, form date-field schedules, form submit/update/delete/field-change events, or workflow approval/process events.
78
+ - Use JS_CODE for cross-form data queries, create/update/batch update operations, process termination, platform API calls, external HTTP calls, and complex orchestration that the frontend cannot handle reliably. Do not use it for simple UI interactions, ordinary form validation, or display-only page behavior.
77
79
  - For workflow/automation JS_CODE nodes, prefer V2 `runtimeMode: "trusted_node"`. AI-authored source must be TypeScript under `sy-lowcode-app-workspace/src/js-code-nodes/<scriptCode>/index.ts`. `pnpm build-js-code --script <scriptCode>` runs TypeScript validation before bundling, and `sourceFile.localPath` should point to the TS source; the CLI builds, uploads, and replaces it with snapshot metadata during validate/create.
78
80
 
79
81
  ## Subskills
@@ -7,6 +7,8 @@ Automations have two files:
7
7
 
8
8
  ## Trigger Config
9
9
 
10
+ Automation can run from form events, workflow events, fixed cron schedules, or a form date field reaching a configured time. Pair these triggers with JS_CODE when the action must execute on the backend and cannot be handled reliably by a frontend page.
11
+
10
12
  Form submit trigger:
11
13
 
12
14
  ```json
@@ -96,7 +98,7 @@ Supported node types:
96
98
 
97
99
  ## JS_CODE V2
98
100
 
99
- Use trusted Node JS_CODE nodes for AI/admin automation logic:
101
+ Use trusted Node JS_CODE nodes for AI/admin automation logic that runs after an automation trigger. Typical cases include scheduled data cleanup, date-field reminders, cross-form synchronization after submit/update/delete, workflow-completed follow-up writes, calling internal platform APIs, calling external HTTP services, and other backend-only orchestration.
100
102
 
101
103
  ```json
102
104
  {
@@ -117,7 +119,45 @@ Use trusted Node JS_CODE nodes for AI/admin automation logic:
117
119
 
118
120
  Author source in `sy-lowcode-app-workspace/src/js-code-nodes/<scriptCode>/index.ts`. AI-authored source must be TypeScript. Build with `pnpm build-js-code --script <scriptCode>`; the command runs TypeScript validation before bundling. During validate/create, the CLI uploads the generated bundle, replaces `sourceFile.localPath` with `{ bucketName, objectName, sha256, ... }`, and the backend verifies sha256 before execution.
119
121
 
120
- Scripts may use `module.exports = async (ctx) => {}`, `require`, `process`, `Buffer`, legacy `methods.*`, arbitrary HTTP, and `platform.api` for `/openxiangda-api/v1`.
122
+ The backend runs the snapshot in the trusted Node runtime, applies the node timeout (`30000` ms by default), stores execution logs, and writes the returned value to the node output and `variables.node_<nodeId>`. Scripts may use `export default async function (ctx) {}`, `module.exports = async (ctx) => {}`, `require`, `process`, `Buffer`, arbitrary HTTP, and `platform.api` for `/openxiangda-api/v1`.
123
+
124
+ Runtime context includes `ctx.triggerEvent`, `ctx.formData`, `ctx.workflowData`, `ctx.operator`, `ctx.app`, `ctx.variables`, and `ctx.node`. Data/process bridge methods include `ctx.methods.queryOneData`, `queryManyData`, `updateOneData`, `updateDataByFormInstanceId`, `updateManyData`, `createOneData`, `terminateProcess`, and `getAllParentDepartments`.
125
+
126
+ Example `src/js-code-nodes/scheduled_reconcile/index.ts`:
127
+
128
+ ```ts
129
+ export default async function scheduledReconcile(ctx) {
130
+ const appType = ctx.app.appType;
131
+ const current = ctx.formData?.current || {};
132
+ const formInstanceId =
133
+ ctx.triggerEvent?.data?.formInstanceId ||
134
+ current.formInstanceId ||
135
+ current.formInstId;
136
+
137
+ const pendingRows = await ctx.methods.queryManyData(appType, "FORM_ORDER", {
138
+ status: "pending",
139
+ });
140
+
141
+ if (formInstanceId) {
142
+ await ctx.methods.updateDataByFormInstanceId(
143
+ appType,
144
+ "FORM_ORDER",
145
+ formInstanceId,
146
+ { last_checked_at: new Date().toISOString() }
147
+ );
148
+ }
149
+
150
+ const log = await ctx.methods.createOneData(appType, "FORM_JOB_LOG", {
151
+ trigger_type: ctx.triggerEvent?.type || "scheduled",
152
+ processed_count: Array.isArray(pendingRows) ? pendingRows.length : 0,
153
+ });
154
+
155
+ return {
156
+ processedCount: pendingRows?.length || 0,
157
+ logId: log?.formInstanceId || log?.id,
158
+ };
159
+ }
160
+ ```
121
161
 
122
162
  Validation rules:
123
163
 
@@ -52,6 +52,8 @@ Supported node types:
52
52
 
53
53
  ## JS_CODE V2
54
54
 
55
+ Workflow JS_CODE runs on the backend when the process reaches the `js_code` node. Use it before or after approval nodes, in branches, or before ending a process when the workflow needs server-side side effects such as cross-form synchronization, audit record creation, external API calls, or terminating a related process. Do not use it for frontend-only behavior.
56
+
55
57
  Inline:
56
58
 
57
59
  ```json
@@ -87,7 +89,43 @@ File snapshot:
87
89
  }
88
90
  ```
89
91
 
90
- AI-authored JS_CODE source must be TypeScript under `sy-lowcode-app-workspace/src/js-code-nodes/<scriptCode>/index.ts`. When validating or creating, the CLI runs `pnpm build-js-code --script <scriptCode>`, which runs TypeScript validation first, bundles to `dist/js-code-nodes/<scriptCode>/index.cjs`, uploads the bundle, and replaces `sourceFile.localPath` with immutable snapshot metadata. Scripts can export `export default async function (ctx) {}` or `module.exports = async (ctx) => {}` and call `ctx.platform.api` or global `platform.api`; the default API namespace is `/openxiangda-api/v1`.
92
+ AI-authored JS_CODE source must be TypeScript under `sy-lowcode-app-workspace/src/js-code-nodes/<scriptCode>/index.ts`. When validating or creating, the CLI runs `pnpm build-js-code --script <scriptCode>`, which runs TypeScript validation first, bundles to `dist/js-code-nodes/<scriptCode>/index.cjs`, uploads the bundle, and replaces `sourceFile.localPath` with immutable snapshot metadata. The backend verifies snapshot `sha256`, runs it in the trusted Node runtime, applies the node timeout (`30000` ms by default), stores execution logs, and writes the returned value to the node output and `variables.node_<nodeId>`.
93
+
94
+ Scripts can export `export default async function (ctx) {}` or `module.exports = async (ctx) => {}`. The runtime exposes `ctx.triggerEvent`, `ctx.formData`, `ctx.workflowData`, `ctx.operator`, `ctx.app`, `ctx.variables`, `ctx.methods.*`, `ctx.platform.api.*`, `ctx.utils`, `require`, `process`, and `Buffer`. Use `ctx.methods.queryOneData/queryManyData/updateOneData/updateDataByFormInstanceId/updateManyData/createOneData/terminateProcess/getAllParentDepartments` for platform-side data and process operations.
95
+
96
+ Example `src/js-code-nodes/sync_customer/index.ts`:
97
+
98
+ ```ts
99
+ export default async function syncCustomer(ctx) {
100
+ const current = ctx.formData?.current || {};
101
+ const appType = ctx.app.appType;
102
+ const customerNo = current.customer_no;
103
+
104
+ const customer = await ctx.methods.queryOneData(appType, "FORM_CUSTOMER", {
105
+ customer_no: customerNo,
106
+ });
107
+
108
+ if (customer) {
109
+ await ctx.methods.updateOneData(
110
+ appType,
111
+ "FORM_CUSTOMER",
112
+ { customer_no: customerNo },
113
+ { last_approved_at: new Date().toISOString() }
114
+ );
115
+ }
116
+
117
+ const log = await ctx.methods.createOneData(appType, "FORM_SYNC_LOG", {
118
+ source: "workflow",
119
+ customer_no: customerNo,
120
+ result: customer ? "updated" : "missing",
121
+ });
122
+
123
+ return {
124
+ customerFound: Boolean(customer),
125
+ logId: log?.formInstanceId || log?.id,
126
+ };
127
+ }
128
+ ```
91
129
 
92
130
  Field permission config lives in `flowConfig` by node ID:
93
131
 
@@ -44,7 +44,9 @@ Use `workflow pull` to inspect the live definition. Use logical workflow codes l
44
44
 
45
45
  ## JS_CODE V2
46
46
 
47
- For non-trivial logic, prefer JS_CODE V2 trusted Node scripts over large inline snippets. AI-authored JS_CODE source must be TypeScript:
47
+ JS_CODE is the backend execution escape hatch for workflow and automation. Use it when the logic must run on the server after a backend trigger, such as a fixed cron schedule, a form date-field schedule, a form submit/update/delete/field-change event, or a workflow approval/process event. It is appropriate for cross-form data queries, create/update/batch update operations, process termination, platform API calls, external HTTP calls, and complex orchestration that the frontend cannot handle reliably.
48
+
49
+ Do not use JS_CODE for simple UI interactions, ordinary form validation, display-only page behavior, or logic that belongs in a normal React code page. For non-trivial backend logic, prefer JS_CODE V2 trusted Node scripts over large inline snippets. AI-authored JS_CODE source must be TypeScript:
48
50
 
49
51
  1. Put source in `sy-lowcode-app-workspace/src/js-code-nodes/<scriptCode>/index.ts`.
50
52
  2. Run `pnpm build-js-code --script <scriptCode>`. This command runs TypeScript validation first and only bundles after `tsc` passes.
@@ -68,7 +70,14 @@ For non-trivial logic, prefer JS_CODE V2 trusted Node scripts over large inline
68
70
 
69
71
  The CLI requires `sourceFile.localPath` to point to `src/js-code-nodes/<scriptCode>/index.ts`. During validate/create it runs `pnpm build-js-code --script <scriptCode>`, uploads the generated `dist/js-code-nodes/<scriptCode>/index.cjs` to `/file/js-code-snapshot/upload`, verifies the server snapshot metadata, and replaces it with `{ bucketName, objectName, sha256, ... }`.
70
72
 
71
- Inside the TypeScript script, prefer `export default async function (ctx) {}` or `module.exports = async (ctx) => {}`. The runtime exposes Node `require`, `process`, `Buffer`, installed dependencies, arbitrary HTTP, legacy `methods.*`, and `platform.api` for `/openxiangda-api/v1`.
73
+ The backend verifies the uploaded snapshot sha256 before execution, runs it in the trusted Node runtime, applies the node timeout (`30000` ms by default), stores console/runtime logs in the execution record, and writes the returned value to the node output and `variables.node_<nodeId>`.
74
+
75
+ Inside the TypeScript script, prefer `export default async function (ctx) {}` or `module.exports = async (ctx) => {}`. The runtime exposes:
76
+
77
+ - Context: `ctx.triggerEvent`, `ctx.formData`, `ctx.workflowData`, `ctx.operator`, `ctx.app`, `ctx.variables`, and `ctx.node`.
78
+ - Data/process methods: `ctx.methods.queryOneData`, `queryManyData`, `updateOneData`, `updateDataByFormInstanceId`, `updateManyData`, `createOneData`, `terminateProcess`, and `getAllParentDepartments`.
79
+ - Platform API bridge: `ctx.platform.api.get/post/put/patch/delete/request` for `/openxiangda-api/v1`.
80
+ - Node runtime helpers: `require`, `process`, `Buffer`, `ctx.utils`, `ctx.utils.http`, and `ctx.console`.
72
81
 
73
82
  ## Automation Flow
74
83
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openxiangda",
3
- "version": "1.0.18",
3
+ "version": "1.0.20",
4
4
  "description": "OpenXiangda CLI, workspace build tools, runtime SDK, and form components.",
5
5
  "private": false,
6
6
  "bin": {
@@ -46195,6 +46195,17 @@ var StandardFormPage = ({
46195
46195
 
46196
46196
  // packages/sdk/src/components/modules/DataManagementList.tsx
46197
46197
  var import_jsx_runtime101 = require("react/jsx-runtime");
46198
+ var DEFAULT_MAX_VISIBLE_ROW_ACTIONS = 4;
46199
+ var ACTION_COLUMN_MIN_WIDTH = 96;
46200
+ var ACTION_BUTTON_ESTIMATED_WIDTH = 56;
46201
+ var ACTION_MORE_BUTTON_WIDTH = 36;
46202
+ var ACTION_COLUMN_HORIZONTAL_PADDING = 24;
46203
+ function normalizeMaxVisibleRowActions(maxVisibleRowActions) {
46204
+ if (typeof maxVisibleRowActions !== "number" || !Number.isFinite(maxVisibleRowActions)) {
46205
+ return DEFAULT_MAX_VISIBLE_ROW_ACTIONS;
46206
+ }
46207
+ return Math.max(0, Math.floor(maxVisibleRowActions));
46208
+ }
46198
46209
  var createGroup = () => ({
46199
46210
  id: `group_${Date.now()}_${Math.random().toString(36).slice(2)}`,
46200
46211
  logic: "AND",
@@ -47127,7 +47138,8 @@ var DataManagementList = ({
47127
47138
  detailPageUrlBuilder,
47128
47139
  submitRenderer,
47129
47140
  requestOverride,
47130
- rowActions = []
47141
+ rowActions = [],
47142
+ maxVisibleRowActions
47131
47143
  }) => {
47132
47144
  const rootRef = (0, import_react279.useRef)(null);
47133
47145
  const api = (0, import_react279.useMemo)(() => {
@@ -47557,6 +47569,12 @@ var DataManagementList = ({
47557
47569
  setImportBase64("");
47558
47570
  await loadData({ current: 1, pageSize });
47559
47571
  };
47572
+ const visibleRowActionLimit = normalizeMaxVisibleRowActions(maxVisibleRowActions);
47573
+ const rowActionCount = 1 + rowActions.length + (readonly ? 0 : 1) + (isProcessForm ? 1 : 0);
47574
+ const actionColumnWidth = Math.max(
47575
+ ACTION_COLUMN_MIN_WIDTH,
47576
+ Math.min(rowActionCount, visibleRowActionLimit) * ACTION_BUTTON_ESTIMATED_WIDTH + (rowActionCount > visibleRowActionLimit ? ACTION_MORE_BUTTON_WIDTH : 0) + ACTION_COLUMN_HORIZONTAL_PADDING
47577
+ );
47560
47578
  const columns = (0, import_react279.useMemo)(() => {
47561
47579
  const baseColumns = visibleFields.map((field) => {
47562
47580
  const columnWidth = widths[field.fieldId] || field.width || 160;
@@ -47583,53 +47601,88 @@ var DataManagementList = ({
47583
47601
  {
47584
47602
  title: "\u64CD\u4F5C",
47585
47603
  key: "__actions",
47586
- width: 136,
47604
+ width: actionColumnWidth,
47587
47605
  fixed: "right",
47588
- render: (_, record2) => /* @__PURE__ */ (0, import_jsx_runtime101.jsxs)(import_antd39.Space, { size: 4, children: [
47589
- /* @__PURE__ */ (0, import_jsx_runtime101.jsx)(import_antd39.Button, { type: "link", size: "small", onClick: () => handleDetail(record2), children: "\u8BE6\u60C5" }),
47590
- /* @__PURE__ */ (0, import_jsx_runtime101.jsx)(
47591
- import_antd39.Dropdown,
47606
+ render: (_, record2) => {
47607
+ const actions = [
47592
47608
  {
47593
- trigger: ["click"],
47594
- getPopupContainer,
47595
- menu: {
47596
- items: [
47597
- ...!readonly ? [
47598
- {
47599
- key: "delete",
47600
- label: "\u5220\u9664",
47601
- danger: true,
47602
- icon: /* @__PURE__ */ (0, import_jsx_runtime101.jsx)(import_icons16.DeleteOutlined, {}),
47603
- onClick: () => confirmDanger(
47604
- "\u786E\u8BA4\u5220\u9664",
47605
- "\u5220\u9664\u540E\u4E0D\u53EF\u6062\u590D\uFF0C\u786E\u8BA4\u7EE7\u7EED\u5417\uFF1F",
47606
- () => handleDelete([String(getRecordId(record2))])
47607
- )
47608
- }
47609
- ] : [],
47610
- ...rowActions.map((action) => ({
47609
+ key: "detail",
47610
+ label: "\u8BE6\u60C5",
47611
+ onClick: () => handleDetail(record2)
47612
+ },
47613
+ ...rowActions.map((action) => ({
47614
+ key: action.key,
47615
+ label: action.label,
47616
+ danger: action.danger,
47617
+ onClick: () => action.onClick(record2)
47618
+ })),
47619
+ ...!readonly ? [
47620
+ {
47621
+ key: "delete",
47622
+ label: "\u5220\u9664",
47623
+ danger: true,
47624
+ icon: /* @__PURE__ */ (0, import_jsx_runtime101.jsx)(import_icons16.DeleteOutlined, {}),
47625
+ onClick: () => confirmDanger(
47626
+ "\u786E\u8BA4\u5220\u9664",
47627
+ "\u5220\u9664\u540E\u4E0D\u53EF\u6062\u590D\uFF0C\u786E\u8BA4\u7EE7\u7EED\u5417\uFF1F",
47628
+ () => handleDelete([String(getRecordId(record2))])
47629
+ )
47630
+ }
47631
+ ] : [],
47632
+ ...isProcessForm ? [
47633
+ {
47634
+ key: "workflow",
47635
+ label: "\u6D41\u7A0B\u65E5\u5FD7",
47636
+ icon: /* @__PURE__ */ (0, import_jsx_runtime101.jsx)(import_icons16.HistoryOutlined, {}),
47637
+ onClick: () => import_antd39.message.info("\u8BF7\u901A\u8FC7 detailRenderer \u63A5\u5165\u6D41\u7A0B\u65E5\u5FD7\u6216\u6D41\u7A0B\u56FE\u5165\u53E3")
47638
+ }
47639
+ ] : []
47640
+ ];
47641
+ const visibleActions = actions.slice(0, visibleRowActionLimit);
47642
+ const moreActions = actions.slice(visibleRowActionLimit);
47643
+ return /* @__PURE__ */ (0, import_jsx_runtime101.jsxs)(import_antd39.Space, { size: 4, children: [
47644
+ visibleActions.map((action) => /* @__PURE__ */ (0, import_jsx_runtime101.jsx)(
47645
+ import_antd39.Button,
47646
+ {
47647
+ type: "link",
47648
+ size: "small",
47649
+ danger: action.danger,
47650
+ onClick: action.onClick,
47651
+ children: action.label
47652
+ },
47653
+ action.key
47654
+ )),
47655
+ moreActions.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime101.jsx)(
47656
+ import_antd39.Dropdown,
47657
+ {
47658
+ trigger: ["click"],
47659
+ getPopupContainer,
47660
+ menu: {
47661
+ items: moreActions.map((action) => ({
47611
47662
  key: action.key,
47612
47663
  label: action.label,
47613
47664
  danger: action.danger,
47614
- onClick: () => action.onClick(record2)
47615
- })),
47616
- ...isProcessForm ? [
47617
- {
47618
- key: "workflow",
47619
- label: "\u6D41\u7A0B\u65E5\u5FD7",
47620
- icon: /* @__PURE__ */ (0, import_jsx_runtime101.jsx)(import_icons16.HistoryOutlined, {}),
47621
- onClick: () => import_antd39.message.info("\u8BF7\u901A\u8FC7 detailRenderer \u63A5\u5165\u6D41\u7A0B\u65E5\u5FD7\u6216\u6D41\u7A0B\u56FE\u5165\u53E3")
47622
- }
47623
- ] : []
47624
- ]
47625
- },
47626
- children: /* @__PURE__ */ (0, import_jsx_runtime101.jsx)(import_antd39.Button, { type: "text", size: "small", icon: /* @__PURE__ */ (0, import_jsx_runtime101.jsx)(import_icons16.MoreOutlined, {}) })
47627
- }
47628
- )
47629
- ] })
47665
+ icon: action.icon,
47666
+ onClick: action.onClick
47667
+ }))
47668
+ },
47669
+ children: /* @__PURE__ */ (0, import_jsx_runtime101.jsx)(
47670
+ import_antd39.Button,
47671
+ {
47672
+ type: "text",
47673
+ size: "small",
47674
+ icon: /* @__PURE__ */ (0, import_jsx_runtime101.jsx)(import_icons16.MoreOutlined, {}),
47675
+ "aria-label": "\u66F4\u591A\u64CD\u4F5C"
47676
+ }
47677
+ )
47678
+ }
47679
+ )
47680
+ ] });
47681
+ }
47630
47682
  }
47631
47683
  ];
47632
47684
  }, [
47685
+ actionColumnWidth,
47633
47686
  confirmDanger,
47634
47687
  commitColumnWidth,
47635
47688
  getPopupContainer,
@@ -47640,13 +47693,17 @@ var DataManagementList = ({
47640
47693
  readonly,
47641
47694
  rowActions,
47642
47695
  updateColumnWidth,
47696
+ visibleRowActionLimit,
47643
47697
  visibleFields,
47644
47698
  widths
47645
47699
  ]);
47646
47700
  const tableSize = density === "compact" ? "small" : density === "loose" ? "large" : "middle";
47647
47701
  const tableScrollX = Math.max(
47648
47702
  900,
47649
- visibleFields.reduce((sum, field) => sum + (widths[field.fieldId] || field.width || 160), 136)
47703
+ visibleFields.reduce(
47704
+ (sum, field) => sum + (widths[field.fieldId] || field.width || 160),
47705
+ actionColumnWidth
47706
+ )
47650
47707
  );
47651
47708
  const importPreviewColumns = (0, import_react279.useMemo)(() => {
47652
47709
  const keys2 = Array.from(
@@ -48298,6 +48355,7 @@ function LowcodePageRenderer({ schema, context }) {
48298
48355
  configScope: "personal",
48299
48356
  forcedConfig: props.forcedConfig,
48300
48357
  showForcedConfig: props.showForcedConfig,
48358
+ maxVisibleRowActions: props.maxVisibleRowActions,
48301
48359
  requestOverride
48302
48360
  },
48303
48361
  node.id