openxiangda 1.0.17 → 1.0.19
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/openxiangda-skills/skills/openxiangda-page/SKILL.md +3 -0
- package/package.json +1 -1
- package/packages/sdk/dist/components/index.cjs +140 -62
- package/packages/sdk/dist/components/index.cjs.map +1 -1
- package/packages/sdk/dist/components/index.d.mts +2 -0
- package/packages/sdk/dist/components/index.d.ts +2 -0
- package/packages/sdk/dist/components/index.mjs +140 -62
- package/packages/sdk/dist/components/index.mjs.map +1 -1
- package/packages/sdk/dist/runtime/index.cjs +14 -13
- package/packages/sdk/dist/runtime/index.cjs.map +1 -1
- package/packages/sdk/dist/runtime/index.mjs +14 -13
- package/packages/sdk/dist/runtime/index.mjs.map +1 -1
- package/packages/sdk/dist/styles/antd-theme.cjs +14 -13
- package/packages/sdk/dist/styles/antd-theme.cjs.map +1 -1
- package/packages/sdk/dist/styles/antd-theme.mjs +14 -13
- package/packages/sdk/dist/styles/antd-theme.mjs.map +1 -1
|
@@ -60,3 +60,6 @@ Read these references only when editing page code:
|
|
|
60
60
|
- For list / detail / CRUD pages, follow `../../references/architecture-patterns.md` (e.g. `DataManagementList`) before writing custom data-fetching loops.
|
|
61
61
|
- Pick components per `../../references/component-guide.md`: prefer the platform component, fall back to Ant Design, and only build a custom component when neither fits.
|
|
62
62
|
- When a page misbehaves (lost styles, `options is undefined`, option labels showing raw values, etc.), consult `../../references/troubleshooting.md` before patching symptoms.
|
|
63
|
+
- For custom portal pages and business modals, do not temporarily embed a single `FormProvider` field component from the standard form runtime. If a standard component is required, navigate to a full standard form page or render a complete `StandardFormPage` inside a dedicated carrier route.
|
|
64
|
+
- Keep PC and mobile portal styles separate. Do not share one form UI implementation across both viewports unless the surrounding shell, modal layer, date picker, and bottom-sheet behavior have been tested in both contexts.
|
|
65
|
+
- After introducing Ant Design overlays, date/datetime fields, or mobile bottom-sheet selectors, verify that global button, modal, picker, and sheet styles do not leak into the host business page.
|
package/package.json
CHANGED
|
@@ -31713,26 +31713,35 @@ var secondOptions = Array.from({ length: 60 }, (_, second) => ({
|
|
|
31713
31713
|
label: `${second}\u79D2`,
|
|
31714
31714
|
value: pad2(second)
|
|
31715
31715
|
}));
|
|
31716
|
-
function
|
|
31717
|
-
return
|
|
31716
|
+
function resolveTimePickerPrecision(dateFormat) {
|
|
31717
|
+
return dateFormat && /s/i.test(dateFormat) ? "second" : "minute";
|
|
31718
31718
|
}
|
|
31719
|
-
function
|
|
31720
|
-
return
|
|
31719
|
+
function formatMobileTime(date4, dateFormat) {
|
|
31720
|
+
return (0, import_dayjs12.default)(date4).format(resolveTimePickerPrecision(dateFormat) === "second" ? "HH:mm:ss" : "HH:mm");
|
|
31721
31721
|
}
|
|
31722
|
-
function
|
|
31722
|
+
function getTimePickerColumns(precision = "second") {
|
|
31723
|
+
return precision === "second" ? [hourOptions, minuteOptions, secondOptions] : [hourOptions, minuteOptions];
|
|
31724
|
+
}
|
|
31725
|
+
function getTimePickerValue(date4, precision = "second") {
|
|
31726
|
+
const base = [(0, import_dayjs12.default)(date4).format("HH"), (0, import_dayjs12.default)(date4).format("mm")];
|
|
31727
|
+
return precision === "second" ? [...base, (0, import_dayjs12.default)(date4).format("ss")] : base;
|
|
31728
|
+
}
|
|
31729
|
+
function applyTimePickerValue(base, value, precision = "second") {
|
|
31723
31730
|
const [hour, minute, second] = value;
|
|
31724
|
-
return (0, import_dayjs12.default)(base).hour(Number(hour ?? 0)).minute(Number(minute ?? 0)).second(Number(second ?? 0)).millisecond(0).toDate();
|
|
31731
|
+
return (0, import_dayjs12.default)(base).hour(Number(hour ?? 0)).minute(Number(minute ?? 0)).second(precision === "second" ? Number(second ?? 0) : 0).millisecond(0).toDate();
|
|
31725
31732
|
}
|
|
31726
31733
|
function MobileTimePickerView({
|
|
31727
31734
|
value,
|
|
31735
|
+
dateFormat,
|
|
31728
31736
|
onChange
|
|
31729
31737
|
}) {
|
|
31738
|
+
const precision = resolveTimePickerPrecision(dateFormat);
|
|
31730
31739
|
return /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
|
|
31731
31740
|
picker_view_default,
|
|
31732
31741
|
{
|
|
31733
|
-
columns:
|
|
31734
|
-
value: getTimePickerValue(value),
|
|
31735
|
-
onChange: (nextValue) => onChange(applyTimePickerValue(value, nextValue)),
|
|
31742
|
+
columns: getTimePickerColumns(precision),
|
|
31743
|
+
value: getTimePickerValue(value, precision),
|
|
31744
|
+
onChange: (nextValue) => onChange(applyTimePickerValue(value, nextValue, precision)),
|
|
31736
31745
|
className: "sy-mobile-time-picker",
|
|
31737
31746
|
renderLabel: (item) => item.label
|
|
31738
31747
|
}
|
|
@@ -31769,30 +31778,32 @@ function getDateOptions(center, min2, max2) {
|
|
|
31769
31778
|
};
|
|
31770
31779
|
});
|
|
31771
31780
|
}
|
|
31772
|
-
function getDateTimePickerValue(date4) {
|
|
31773
|
-
return [(0, import_dayjs12.default)(date4).format("YYYY-MM-DD"), ...getTimePickerValue(date4)];
|
|
31781
|
+
function getDateTimePickerValue(date4, precision = "second") {
|
|
31782
|
+
return [(0, import_dayjs12.default)(date4).format("YYYY-MM-DD"), ...getTimePickerValue(date4, precision)];
|
|
31774
31783
|
}
|
|
31775
|
-
function applyDateTimePickerValue(base, value) {
|
|
31784
|
+
function applyDateTimePickerValue(base, value, precision = "second") {
|
|
31776
31785
|
const [dateValue, hour, minute, second] = value;
|
|
31777
31786
|
const date4 = (0, import_dayjs12.default)(String(dateValue || (0, import_dayjs12.default)(base).format("YYYY-MM-DD")));
|
|
31778
|
-
return date4.hour(Number(hour ?? 0)).minute(Number(minute ?? 0)).second(Number(second ?? 0)).millisecond(0).toDate();
|
|
31787
|
+
return date4.hour(Number(hour ?? 0)).minute(Number(minute ?? 0)).second(precision === "second" ? Number(second ?? 0) : 0).millisecond(0).toDate();
|
|
31779
31788
|
}
|
|
31780
31789
|
function MobileDateTimePickerView({
|
|
31781
31790
|
value,
|
|
31791
|
+
dateFormat,
|
|
31782
31792
|
min: min2,
|
|
31783
31793
|
max: max2,
|
|
31784
31794
|
onChange
|
|
31785
31795
|
}) {
|
|
31796
|
+
const precision = resolveTimePickerPrecision(dateFormat);
|
|
31786
31797
|
const columns = import_react216.default.useMemo(
|
|
31787
|
-
() => [getDateOptions(value, min2, max2),
|
|
31788
|
-
[value, min2, max2]
|
|
31798
|
+
() => [getDateOptions(value, min2, max2), ...getTimePickerColumns(precision)],
|
|
31799
|
+
[value, min2, max2, precision]
|
|
31789
31800
|
);
|
|
31790
31801
|
return /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
|
|
31791
31802
|
picker_view_default,
|
|
31792
31803
|
{
|
|
31793
31804
|
columns,
|
|
31794
|
-
value: getDateTimePickerValue(value),
|
|
31795
|
-
onChange: (nextValue) => onChange(applyDateTimePickerValue(value, nextValue)),
|
|
31805
|
+
value: getDateTimePickerValue(value, precision),
|
|
31806
|
+
onChange: (nextValue) => onChange(applyDateTimePickerValue(value, nextValue, precision)),
|
|
31796
31807
|
className: "sy-mobile-time-picker sy-mobile-date-time-picker",
|
|
31797
31808
|
renderLabel: (item) => item.label
|
|
31798
31809
|
}
|
|
@@ -31873,10 +31884,10 @@ function DateFieldMobile({
|
|
|
31873
31884
|
}
|
|
31874
31885
|
),
|
|
31875
31886
|
mode === "time" ? /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)(import_jsx_runtime35.Fragment, { children: [
|
|
31876
|
-
/* @__PURE__ */ (0, import_jsx_runtime35.jsx)(MobileTimePickerView, { value: tempDate, onChange: setTempDate }),
|
|
31887
|
+
/* @__PURE__ */ (0, import_jsx_runtime35.jsx)(MobileTimePickerView, { value: tempDate, dateFormat: format2, onChange: setTempDate }),
|
|
31877
31888
|
/* @__PURE__ */ (0, import_jsx_runtime35.jsxs)("div", { className: "sy-mobile-date-time-footer", children: [
|
|
31878
31889
|
/* @__PURE__ */ (0, import_jsx_runtime35.jsx)("span", { children: "\u9009\u62E9\u65F6\u95F4" }),
|
|
31879
|
-
/* @__PURE__ */ (0, import_jsx_runtime35.jsx)("button", { type: "button", className: "sy-mobile-time-pill is-active", children: formatMobileTime(tempDate) })
|
|
31890
|
+
/* @__PURE__ */ (0, import_jsx_runtime35.jsx)("button", { type: "button", className: "sy-mobile-time-pill is-active", children: formatMobileTime(tempDate, format2) })
|
|
31880
31891
|
] })
|
|
31881
31892
|
] }) : /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)(import_jsx_runtime35.Fragment, { children: [
|
|
31882
31893
|
/* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
|
|
@@ -31907,7 +31918,7 @@ function DateFieldMobile({
|
|
|
31907
31918
|
type: "button",
|
|
31908
31919
|
className: "sy-mobile-time-pill",
|
|
31909
31920
|
onClick: () => setMode("time"),
|
|
31910
|
-
children: formatMobileTime(tempDate)
|
|
31921
|
+
children: formatMobileTime(tempDate, format2)
|
|
31911
31922
|
}
|
|
31912
31923
|
)
|
|
31913
31924
|
] }) : null
|
|
@@ -32167,6 +32178,7 @@ function CascadeDateFieldMobile({
|
|
|
32167
32178
|
MobileDateTimePickerView,
|
|
32168
32179
|
{
|
|
32169
32180
|
value: timeStep === "start" ? tempStart : tempEnd,
|
|
32181
|
+
dateFormat: format2,
|
|
32170
32182
|
min: timeStep === "start" ? min2 : endMin,
|
|
32171
32183
|
max: max2,
|
|
32172
32184
|
onChange: timeStep === "start" ? updateTempStart : updateTempEnd
|
|
@@ -46094,6 +46106,7 @@ var StandardFormPage = ({
|
|
|
46094
46106
|
mode,
|
|
46095
46107
|
initialValues,
|
|
46096
46108
|
permissions,
|
|
46109
|
+
api: externalApi,
|
|
46097
46110
|
formUuid,
|
|
46098
46111
|
appType,
|
|
46099
46112
|
formInstanceId,
|
|
@@ -46107,7 +46120,7 @@ var StandardFormPage = ({
|
|
|
46107
46120
|
const formType = normalizeStandardFormType(
|
|
46108
46121
|
schema.template?.formType || (resolvedMode === "process" ? "process" : "form")
|
|
46109
46122
|
);
|
|
46110
|
-
const
|
|
46123
|
+
const submitApi = (0, import_react278.useMemo)(() => {
|
|
46111
46124
|
if (!onSubmit) return void 0;
|
|
46112
46125
|
return {
|
|
46113
46126
|
submitFormData: async (payload) => onSubmit(
|
|
@@ -46120,6 +46133,13 @@ var StandardFormPage = ({
|
|
|
46120
46133
|
}
|
|
46121
46134
|
};
|
|
46122
46135
|
}, [formType, onSubmit]);
|
|
46136
|
+
const api = (0, import_react278.useMemo)(() => {
|
|
46137
|
+
if (!externalApi && !submitApi) return void 0;
|
|
46138
|
+
return {
|
|
46139
|
+
...externalApi ?? {},
|
|
46140
|
+
...submitApi ?? {}
|
|
46141
|
+
};
|
|
46142
|
+
}, [externalApi, submitApi]);
|
|
46123
46143
|
if (resolvedMode === "process") {
|
|
46124
46144
|
return /* @__PURE__ */ (0, import_jsx_runtime100.jsx)(
|
|
46125
46145
|
ProcessDetailTemplate,
|
|
@@ -46175,6 +46195,17 @@ var StandardFormPage = ({
|
|
|
46175
46195
|
|
|
46176
46196
|
// packages/sdk/src/components/modules/DataManagementList.tsx
|
|
46177
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
|
+
}
|
|
46178
46209
|
var createGroup = () => ({
|
|
46179
46210
|
id: `group_${Date.now()}_${Math.random().toString(36).slice(2)}`,
|
|
46180
46211
|
logic: "AND",
|
|
@@ -47107,7 +47138,8 @@ var DataManagementList = ({
|
|
|
47107
47138
|
detailPageUrlBuilder,
|
|
47108
47139
|
submitRenderer,
|
|
47109
47140
|
requestOverride,
|
|
47110
|
-
rowActions = []
|
|
47141
|
+
rowActions = [],
|
|
47142
|
+
maxVisibleRowActions
|
|
47111
47143
|
}) => {
|
|
47112
47144
|
const rootRef = (0, import_react279.useRef)(null);
|
|
47113
47145
|
const api = (0, import_react279.useMemo)(() => {
|
|
@@ -47537,6 +47569,12 @@ var DataManagementList = ({
|
|
|
47537
47569
|
setImportBase64("");
|
|
47538
47570
|
await loadData({ current: 1, pageSize });
|
|
47539
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
|
+
);
|
|
47540
47578
|
const columns = (0, import_react279.useMemo)(() => {
|
|
47541
47579
|
const baseColumns = visibleFields.map((field) => {
|
|
47542
47580
|
const columnWidth = widths[field.fieldId] || field.width || 160;
|
|
@@ -47563,53 +47601,88 @@ var DataManagementList = ({
|
|
|
47563
47601
|
{
|
|
47564
47602
|
title: "\u64CD\u4F5C",
|
|
47565
47603
|
key: "__actions",
|
|
47566
|
-
width:
|
|
47604
|
+
width: actionColumnWidth,
|
|
47567
47605
|
fixed: "right",
|
|
47568
|
-
render: (_, record2) =>
|
|
47569
|
-
|
|
47570
|
-
/* @__PURE__ */ (0, import_jsx_runtime101.jsx)(
|
|
47571
|
-
import_antd39.Dropdown,
|
|
47606
|
+
render: (_, record2) => {
|
|
47607
|
+
const actions = [
|
|
47572
47608
|
{
|
|
47573
|
-
|
|
47574
|
-
|
|
47575
|
-
|
|
47576
|
-
|
|
47577
|
-
|
|
47578
|
-
|
|
47579
|
-
|
|
47580
|
-
|
|
47581
|
-
|
|
47582
|
-
|
|
47583
|
-
|
|
47584
|
-
|
|
47585
|
-
|
|
47586
|
-
|
|
47587
|
-
|
|
47588
|
-
|
|
47589
|
-
|
|
47590
|
-
|
|
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) => ({
|
|
47591
47662
|
key: action.key,
|
|
47592
47663
|
label: action.label,
|
|
47593
47664
|
danger: action.danger,
|
|
47594
|
-
|
|
47595
|
-
|
|
47596
|
-
|
|
47597
|
-
|
|
47598
|
-
|
|
47599
|
-
|
|
47600
|
-
|
|
47601
|
-
|
|
47602
|
-
|
|
47603
|
-
|
|
47604
|
-
|
|
47605
|
-
|
|
47606
|
-
|
|
47607
|
-
|
|
47608
|
-
|
|
47609
|
-
|
|
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
|
+
}
|
|
47610
47682
|
}
|
|
47611
47683
|
];
|
|
47612
47684
|
}, [
|
|
47685
|
+
actionColumnWidth,
|
|
47613
47686
|
confirmDanger,
|
|
47614
47687
|
commitColumnWidth,
|
|
47615
47688
|
getPopupContainer,
|
|
@@ -47620,13 +47693,17 @@ var DataManagementList = ({
|
|
|
47620
47693
|
readonly,
|
|
47621
47694
|
rowActions,
|
|
47622
47695
|
updateColumnWidth,
|
|
47696
|
+
visibleRowActionLimit,
|
|
47623
47697
|
visibleFields,
|
|
47624
47698
|
widths
|
|
47625
47699
|
]);
|
|
47626
47700
|
const tableSize = density === "compact" ? "small" : density === "loose" ? "large" : "middle";
|
|
47627
47701
|
const tableScrollX = Math.max(
|
|
47628
47702
|
900,
|
|
47629
|
-
visibleFields.reduce(
|
|
47703
|
+
visibleFields.reduce(
|
|
47704
|
+
(sum, field) => sum + (widths[field.fieldId] || field.width || 160),
|
|
47705
|
+
actionColumnWidth
|
|
47706
|
+
)
|
|
47630
47707
|
);
|
|
47631
47708
|
const importPreviewColumns = (0, import_react279.useMemo)(() => {
|
|
47632
47709
|
const keys2 = Array.from(
|
|
@@ -48278,6 +48355,7 @@ function LowcodePageRenderer({ schema, context }) {
|
|
|
48278
48355
|
configScope: "personal",
|
|
48279
48356
|
forcedConfig: props.forcedConfig,
|
|
48280
48357
|
showForcedConfig: props.showForcedConfig,
|
|
48358
|
+
maxVisibleRowActions: props.maxVisibleRowActions,
|
|
48281
48359
|
requestOverride
|
|
48282
48360
|
},
|
|
48283
48361
|
node.id
|