openxiangda 1.0.21 → 1.0.24

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 (100) hide show
  1. package/README.md +28 -10
  2. package/lib/cli.js +723 -11
  3. package/lib/workspace-init.js +13 -0
  4. package/openxiangda-skills/SKILL.md +26 -10
  5. package/openxiangda-skills/references/architecture-patterns.md +44 -22
  6. package/openxiangda-skills/references/automation-v3.md +2 -0
  7. package/openxiangda-skills/references/best-practices.md +163 -0
  8. package/openxiangda-skills/references/connector-resources.md +3 -0
  9. package/openxiangda-skills/references/notifications.md +80 -0
  10. package/openxiangda-skills/references/openxiangda-api.md +45 -0
  11. package/openxiangda-skills/references/pages/page-sdk.md +1 -0
  12. package/openxiangda-skills/references/pages/workspace-structure.md +5 -3
  13. package/openxiangda-skills/references/workspace-state.md +6 -0
  14. package/openxiangda-skills/skills/openxiangda-app/SKILL.md +11 -7
  15. package/openxiangda-skills/skills/openxiangda-core/SKILL.md +22 -4
  16. package/openxiangda-skills/skills/openxiangda-form/SKILL.md +6 -1
  17. package/openxiangda-skills/skills/openxiangda-page/SKILL.md +9 -1
  18. package/openxiangda-skills/skills/openxiangda-permission-settings/SKILL.md +3 -0
  19. package/openxiangda-skills/skills/openxiangda-workflow-automation/SKILL.md +9 -0
  20. package/package.json +1 -1
  21. package/packages/sdk/dist/runtime/index.cjs +34 -2
  22. package/packages/sdk/dist/runtime/index.cjs.map +1 -1
  23. package/packages/sdk/dist/runtime/index.d.mts +66 -1
  24. package/packages/sdk/dist/runtime/index.d.ts +66 -1
  25. package/packages/sdk/dist/runtime/index.mjs +34 -2
  26. package/packages/sdk/dist/runtime/index.mjs.map +1 -1
  27. package/templates/sy-lowcode-app-workspace/examples/best-practices/README.md +32 -0
  28. package/templates/sy-lowcode-app-workspace/examples/best-practices/catalog.json +61 -0
  29. package/templates/sy-lowcode-app-workspace/examples/best-practices/decision-guide.md +44 -0
  30. package/templates/sy-lowcode-app-workspace/examples/best-practices/design-style.md +30 -0
  31. package/templates/sy-lowcode-app-workspace/examples/best-practices/module-structure.md +48 -0
  32. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/domain/role-governance/index.ts +2 -0
  33. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/domain/role-governance/permissions.test.ts +35 -0
  34. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/domain/role-governance/permissions.ts +24 -0
  35. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/domain/role-governance/types.ts +17 -0
  36. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/domain/service-ticket/index.ts +4 -0
  37. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/domain/service-ticket/permissions.test.ts +42 -0
  38. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/domain/service-ticket/permissions.ts +23 -0
  39. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/domain/service-ticket/state-machine.test.ts +63 -0
  40. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/domain/service-ticket/state-machine.ts +73 -0
  41. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/domain/service-ticket/ticket-query.test.ts +34 -0
  42. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/domain/service-ticket/ticket-query.ts +73 -0
  43. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/domain/service-ticket/types.ts +64 -0
  44. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/forms/app-role/page.tsx +1 -0
  45. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/forms/app-role/schema.ts +57 -0
  46. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/forms/customer-profile/page.tsx +1 -0
  47. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/forms/customer-profile/schema.ts +83 -0
  48. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/forms/service-ticket/page.tsx +1 -0
  49. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/forms/service-ticket/schema.ts +97 -0
  50. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/forms/ticket-action-log/page.tsx +1 -0
  51. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/forms/ticket-action-log/schema.ts +65 -0
  52. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/js-code-nodes/daily_ticket_digest/index.ts +44 -0
  53. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/js-code-nodes/sync_roles_to_platform/index.ts +33 -0
  54. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/pages/interactive-workbench/App.tsx +7 -0
  55. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/pages/interactive-workbench/WorkbenchPage.tsx +36 -0
  56. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/pages/interactive-workbench/components/ConfigPanel.tsx +34 -0
  57. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/pages/interactive-workbench/components/PreviewPanel.tsx +17 -0
  58. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/pages/interactive-workbench/index.tsx +10 -0
  59. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/pages/interactive-workbench/page.config.ts +9 -0
  60. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/pages/interactive-workbench/reducer.ts +29 -0
  61. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/pages/interactive-workbench/styles.css +24 -0
  62. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/pages/mobile-portal-shell/App.tsx +7 -0
  63. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/pages/mobile-portal-shell/MobilePortalShell.tsx +31 -0
  64. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/pages/mobile-portal-shell/index.tsx +10 -0
  65. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/pages/mobile-portal-shell/modules/MobileHome.tsx +13 -0
  66. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/pages/mobile-portal-shell/page.config.ts +14 -0
  67. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/pages/mobile-portal-shell/routes.ts +13 -0
  68. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/pages/mobile-portal-shell/styles.css +11 -0
  69. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/pages/pc-portal-shell/App.tsx +7 -0
  70. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/pages/pc-portal-shell/PcPortalShell.tsx +35 -0
  71. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/pages/pc-portal-shell/components/PortalMetric.tsx +11 -0
  72. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/pages/pc-portal-shell/index.tsx +10 -0
  73. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/pages/pc-portal-shell/modules/HomeModule.tsx +25 -0
  74. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/pages/pc-portal-shell/modules/TicketsModule.tsx +14 -0
  75. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/pages/pc-portal-shell/page.config.ts +14 -0
  76. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/pages/pc-portal-shell/routes.ts +19 -0
  77. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/pages/pc-portal-shell/styles.css +35 -0
  78. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/pages/service-ticket-ops/App.tsx +7 -0
  79. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/pages/service-ticket-ops/TicketOpsPage.tsx +105 -0
  80. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/pages/service-ticket-ops/components/TicketActionTimeline.tsx +22 -0
  81. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/pages/service-ticket-ops/components/TicketDetailDrawer.tsx +41 -0
  82. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/pages/service-ticket-ops/components/TicketTableActions.tsx +55 -0
  83. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/pages/service-ticket-ops/index.tsx +10 -0
  84. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/pages/service-ticket-ops/page.config.ts +9 -0
  85. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/pages/service-ticket-ops/styles.css +35 -0
  86. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/resources/automations/daily-ticket-digest/automation.json +25 -0
  87. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/resources/automations/daily-ticket-digest/trigger.json +9 -0
  88. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/resources/notifications/daily-ticket-digest.json +24 -0
  89. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/resources/permissions/form-groups/service-ticket-college.json +21 -0
  90. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/resources/permissions/roles.json +17 -0
  91. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/resources/workflows/expense-approval-workflow.json +48 -0
  92. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/shared/components/ConfirmAction.tsx +22 -0
  93. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/shared/components/QueryState.tsx +37 -0
  94. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/shared/components/StatusTag.tsx +20 -0
  95. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/shared/hooks/useTicketOps.ts +96 -0
  96. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/shared/services/role-governance.ts +48 -0
  97. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/shared/services/service-ticket.ts +113 -0
  98. package/templates/sy-lowcode-app-workspace/package.json +1 -0
  99. package/templates/sy-lowcode-app-workspace/src/dev/App.tsx +11 -1
  100. package/templates/sy-lowcode-app-workspace/tsconfig.examples.json +24 -0
@@ -0,0 +1,96 @@
1
+ import { useCallback, useEffect, useMemo, useState } from "react";
2
+ import type { PageSdk } from "openxiangda/runtime";
3
+
4
+ import {
5
+ getTicketUiPermissions,
6
+ type TicketAction,
7
+ type TicketOperator,
8
+ type TicketRecord,
9
+ type TicketSearchState,
10
+ } from "@/domain/service-ticket";
11
+ import {
12
+ queryTickets,
13
+ transitionTicket,
14
+ type TicketQueryResult,
15
+ } from "@/shared/services/service-ticket";
16
+
17
+ const initialResult: TicketQueryResult = { records: [], total: 0 };
18
+
19
+ export function useTicketOps(sdk: PageSdk, operator: TicketOperator) {
20
+ const [search, setSearch] = useState<TicketSearchState>({});
21
+ const [page, setPage] = useState(1);
22
+ const [pageSize, setPageSize] = useState(20);
23
+ const [result, setResult] = useState<TicketQueryResult>(initialResult);
24
+ const [loading, setLoading] = useState(false);
25
+ const [refreshing, setRefreshing] = useState(false);
26
+ const [submittingAction, setSubmittingAction] = useState<TicketAction | null>(null);
27
+ const [error, setError] = useState<string | null>(null);
28
+
29
+ const load = useCallback(
30
+ async (mode: "loading" | "refreshing" = "loading") => {
31
+ if (mode === "loading") setLoading(true);
32
+ else setRefreshing(true);
33
+ setError(null);
34
+ try {
35
+ const next = await queryTickets(sdk, {
36
+ currentPage: page,
37
+ pageSize,
38
+ search,
39
+ });
40
+ setResult(next);
41
+ } catch (nextError) {
42
+ setError(nextError instanceof Error ? nextError.message : "加载失败");
43
+ } finally {
44
+ setLoading(false);
45
+ setRefreshing(false);
46
+ }
47
+ },
48
+ [page, pageSize, sdk, search],
49
+ );
50
+
51
+ useEffect(() => {
52
+ void load("loading");
53
+ }, [load]);
54
+
55
+ const runAction = useCallback(
56
+ async (ticket: TicketRecord, action: TicketAction, comment?: string) => {
57
+ setSubmittingAction(action);
58
+ setError(null);
59
+ try {
60
+ await transitionTicket(sdk, ticket, action, operator, comment);
61
+ await load("refreshing");
62
+ } catch (nextError) {
63
+ setError(nextError instanceof Error ? nextError.message : "操作失败");
64
+ } finally {
65
+ setSubmittingAction(null);
66
+ }
67
+ },
68
+ [load, operator, sdk],
69
+ );
70
+
71
+ const recordsWithPermissions = useMemo(
72
+ () =>
73
+ result.records.map((ticket) => ({
74
+ ticket,
75
+ permissions: getTicketUiPermissions(ticket, operator),
76
+ })),
77
+ [operator, result.records],
78
+ );
79
+
80
+ return {
81
+ search,
82
+ setSearch,
83
+ page,
84
+ setPage,
85
+ pageSize,
86
+ setPageSize,
87
+ total: result.total,
88
+ recordsWithPermissions,
89
+ loading,
90
+ refreshing,
91
+ submittingAction,
92
+ error,
93
+ reload: () => load("refreshing"),
94
+ runAction,
95
+ };
96
+ }
@@ -0,0 +1,48 @@
1
+ import type { PageSdk } from "openxiangda/runtime";
2
+
3
+ import { buildRoleCode, type AppRoleRecord } from "@/domain/role-governance";
4
+
5
+ export const APP_ROLE_FORM_UUID = "FORM_APP_ROLE";
6
+
7
+ export async function queryEnabledBusinessRoles(sdk: PageSdk) {
8
+ const response = await sdk.form.advancedSearch<AppRoleRecord>({
9
+ formUuid: APP_ROLE_FORM_UUID,
10
+ currentPage: 1,
11
+ pageSize: 100,
12
+ filters: {
13
+ id: "enabled_roles",
14
+ logic: "AND",
15
+ rules: [
16
+ {
17
+ id: "enabled_EQ",
18
+ key: "enabled",
19
+ componentName: "RadioField",
20
+ operator: "EQ",
21
+ value: "enabled",
22
+ },
23
+ ],
24
+ conditions: [],
25
+ } as never,
26
+ });
27
+ return response.result?.data || [];
28
+ }
29
+
30
+ export async function syncBusinessRoleToPlatform(sdk: PageSdk, role: AppRoleRecord) {
31
+ const roleCode = buildRoleCode(role);
32
+ const platformRole = await sdk.role.create({
33
+ code: roleCode,
34
+ name: role.roleName,
35
+ scope: "app",
36
+ description: "由业务角色维护表同步",
37
+ });
38
+ const roleId = platformRole.result?.id;
39
+ if (!roleId) return platformRole;
40
+
41
+ for (const member of role.members || []) {
42
+ await sdk.role.assignRoles({
43
+ userId: member.value,
44
+ roleIds: [roleId],
45
+ });
46
+ }
47
+ return platformRole;
48
+ }
@@ -0,0 +1,113 @@
1
+ import type { PageSdk } from "openxiangda/runtime";
2
+
3
+ import {
4
+ assertTicketTransition,
5
+ buildTicketFilterGroup,
6
+ type TicketAction,
7
+ type TicketActionLog,
8
+ type TicketOperator,
9
+ type TicketRecord,
10
+ type TicketSearchState,
11
+ } from "@/domain/service-ticket";
12
+
13
+ export const SERVICE_TICKET_FORM_UUID = "FORM_SERVICE_TICKET";
14
+ export const TICKET_ACTION_LOG_FORM_UUID = "FORM_TICKET_ACTION_LOG";
15
+
16
+ export interface TicketQueryInput {
17
+ currentPage: number;
18
+ pageSize: number;
19
+ search: TicketSearchState;
20
+ }
21
+
22
+ export interface TicketQueryResult {
23
+ records: TicketRecord[];
24
+ total: number;
25
+ }
26
+
27
+ export async function queryTickets(
28
+ sdk: PageSdk,
29
+ input: TicketQueryInput,
30
+ ): Promise<TicketQueryResult> {
31
+ const response = await sdk.form.advancedSearch<TicketRecord>({
32
+ formUuid: SERVICE_TICKET_FORM_UUID,
33
+ currentPage: input.currentPage,
34
+ pageSize: input.pageSize,
35
+ filters: buildTicketFilterGroup(input.search) as never,
36
+ order: [{ id: "modifiedTime", isAsc: "n" }],
37
+ });
38
+
39
+ return {
40
+ records: response.result?.data || [],
41
+ total: response.result?.totalCount || 0,
42
+ };
43
+ }
44
+
45
+ export async function transitionTicket(
46
+ sdk: PageSdk,
47
+ ticket: TicketRecord,
48
+ action: TicketAction,
49
+ operator: TicketOperator,
50
+ comment?: string,
51
+ ) {
52
+ const nextStatus = assertTicketTransition(ticket, action, operator);
53
+ const operatedAt = new Date().toISOString();
54
+
55
+ await sdk.form.update({
56
+ formUuid: SERVICE_TICKET_FORM_UUID,
57
+ formInstanceId: ticket.formInstanceId,
58
+ data: {
59
+ status: nextStatus,
60
+ lastActionAt: operatedAt,
61
+ ownerUserId: ticket.ownerUserId,
62
+ ownerDeptId: ticket.ownerDeptId,
63
+ collegeId: ticket.collegeId,
64
+ classId: ticket.classId,
65
+ },
66
+ } as never);
67
+
68
+ const log: TicketActionLog = {
69
+ ticketId: ticket.formInstanceId,
70
+ action,
71
+ fromStatus: ticket.status,
72
+ toStatus: nextStatus,
73
+ operatorId: operator.userId,
74
+ operatorName: operator.userName,
75
+ comment,
76
+ operatedAt,
77
+ };
78
+
79
+ await sdk.form.create({
80
+ formUuid: TICKET_ACTION_LOG_FORM_UUID,
81
+ data: log,
82
+ } as never);
83
+
84
+ return { nextStatus, operatedAt };
85
+ }
86
+
87
+ export async function queryTicketActionLogs(
88
+ sdk: PageSdk,
89
+ ticketId: string,
90
+ ): Promise<TicketActionLog[]> {
91
+ const response = await sdk.form.advancedSearch<TicketActionLog>({
92
+ formUuid: TICKET_ACTION_LOG_FORM_UUID,
93
+ currentPage: 1,
94
+ pageSize: 50,
95
+ filters: {
96
+ id: "ticket_log_filters",
97
+ logic: "AND",
98
+ rules: [
99
+ {
100
+ id: "ticketId_EQ",
101
+ key: "ticketId",
102
+ componentName: "TextField",
103
+ operator: "EQ",
104
+ value: ticketId,
105
+ },
106
+ ],
107
+ conditions: [],
108
+ } as never,
109
+ order: [{ id: "createdAt", isAsc: "n" }],
110
+ });
111
+
112
+ return response.result?.data || [];
113
+ }
@@ -12,6 +12,7 @@
12
12
  "build-js-code": "node scripts/build-js-code.mjs",
13
13
  "build:js-code": "node scripts/build-js-code.mjs",
14
14
  "typecheck:js-code": "tsc -p tsconfig.js-code-nodes.json --noEmit",
15
+ "examples:check": "tsc -p tsconfig.examples.json --noEmit",
15
16
  "sync-schema": "lowcode-workspace sync-schema",
16
17
  "publish:oss": "lowcode-workspace publish-oss",
17
18
  "register": "lowcode-workspace register",
@@ -1,4 +1,4 @@
1
- import { Card, Space, Typography } from "antd";
1
+ import { Card, Divider, Space, Typography } from "antd";
2
2
 
3
3
  const { Paragraph, Text, Title } = Typography;
4
4
 
@@ -19,6 +19,16 @@ export default function App() {
19
19
  Publish from this workspace with{" "}
20
20
  <Text code>openxiangda workspace publish --profile &lt;name&gt;</Text>.
21
21
  </Paragraph>
22
+ <Divider style={{ margin: "4px 0" }} />
23
+ <Paragraph style={{ margin: 0 }}>
24
+ Best-practice templates live under{" "}
25
+ <Text code>examples/best-practices</Text>. They are not published by
26
+ default. Copy selected modules into <Text code>src</Text> when you
27
+ want to use them.
28
+ </Paragraph>
29
+ <Paragraph style={{ margin: 0 }}>
30
+ Validate the cookbook with <Text code>pnpm examples:check</Text>.
31
+ </Paragraph>
22
32
  </Space>
23
33
  </Card>
24
34
  </main>
@@ -0,0 +1,24 @@
1
+ {
2
+ "extends": "./tsconfig.app.json",
3
+ "compilerOptions": {
4
+ "baseUrl": ".",
5
+ "paths": {
6
+ "@/*": ["examples/best-practices/src/*", "src/*"],
7
+ "openxiangda": [
8
+ "../../packages/sdk/src/components/index.ts",
9
+ "node_modules/openxiangda"
10
+ ],
11
+ "openxiangda/runtime": [
12
+ "../../packages/sdk/src/runtime/index.ts",
13
+ "node_modules/openxiangda/runtime"
14
+ ]
15
+ }
16
+ },
17
+ "include": [
18
+ "examples/best-practices/src/**/*.ts",
19
+ "examples/best-practices/src/**/*.tsx",
20
+ "examples/best-practices/**/*.json",
21
+ "src/types/**/*.ts",
22
+ "src/shared/form-schema.ts"
23
+ ]
24
+ }