openxiangda 1.0.50 → 1.0.52

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 (50) hide show
  1. package/lib/cli.js +339 -9
  2. package/lib/workspace-init.js +20 -8
  3. package/openxiangda-skills/SKILL.md +4 -3
  4. package/openxiangda-skills/skills/openxiangda-app/SKILL.md +28 -0
  5. package/openxiangda-skills/skills/openxiangda-core/SKILL.md +45 -1
  6. package/openxiangda-skills/skills/openxiangda-permission-settings/SKILL.md +31 -0
  7. package/package.json +7 -1
  8. package/packages/sdk/dist/runtime/index.cjs +3590 -3376
  9. package/packages/sdk/dist/runtime/index.cjs.map +1 -1
  10. package/packages/sdk/dist/runtime/index.d.mts +1 -0
  11. package/packages/sdk/dist/runtime/index.d.ts +1 -0
  12. package/packages/sdk/dist/runtime/index.mjs +3079 -2859
  13. package/packages/sdk/dist/runtime/index.mjs.map +1 -1
  14. package/packages/sdk/dist/runtime/react.cjs +236 -0
  15. package/packages/sdk/dist/runtime/react.cjs.map +1 -0
  16. package/packages/sdk/dist/runtime/react.d.mts +109 -0
  17. package/packages/sdk/dist/runtime/react.d.ts +109 -0
  18. package/packages/sdk/dist/runtime/react.mjs +222 -0
  19. package/packages/sdk/dist/runtime/react.mjs.map +1 -0
  20. package/packages/sdk/src/build-source/scripts/sync-schema.mjs +31 -0
  21. package/templates/openxiangda-react-spa/.env.example +4 -0
  22. package/templates/openxiangda-react-spa/AGENTS.md +69 -0
  23. package/templates/openxiangda-react-spa/app-workspace.config.ts +32 -0
  24. package/templates/openxiangda-react-spa/index.html +12 -0
  25. package/templates/openxiangda-react-spa/package.json +39 -0
  26. package/templates/openxiangda-react-spa/postcss.config.cjs +6 -0
  27. package/templates/openxiangda-react-spa/src/app/router.tsx +97 -0
  28. package/templates/openxiangda-react-spa/src/forms/.gitkeep +1 -0
  29. package/templates/openxiangda-react-spa/src/layouts/AdminShell.tsx +102 -0
  30. package/templates/openxiangda-react-spa/src/layouts/PublicShell.tsx +11 -0
  31. package/templates/openxiangda-react-spa/src/layouts/UserShell.tsx +22 -0
  32. package/templates/openxiangda-react-spa/src/main.tsx +12 -0
  33. package/templates/openxiangda-react-spa/src/pages/admin/RuntimeWorkspacePage.tsx +57 -0
  34. package/templates/openxiangda-react-spa/src/pages/defaults/DataRoutePage.tsx +17 -0
  35. package/templates/openxiangda-react-spa/src/pages/defaults/FilePreviewRoutePage.tsx +14 -0
  36. package/templates/openxiangda-react-spa/src/pages/defaults/FormRoutePage.tsx +62 -0
  37. package/templates/openxiangda-react-spa/src/pages/portal/UserPortalPage.tsx +27 -0
  38. package/templates/openxiangda-react-spa/src/pages/public/PublicHomePage.tsx +10 -0
  39. package/templates/openxiangda-react-spa/src/pages/states/NotFoundPage.tsx +16 -0
  40. package/templates/openxiangda-react-spa/src/resources/menus/menus.json +31 -0
  41. package/templates/openxiangda-react-spa/src/resources/permissions/page-groups/app-admin.json +8 -0
  42. package/templates/openxiangda-react-spa/src/resources/permissions/page-groups/app-user.json +8 -0
  43. package/templates/openxiangda-react-spa/src/resources/roles/roles.json +14 -0
  44. package/templates/openxiangda-react-spa/src/shared/form-schema.ts +135 -0
  45. package/templates/openxiangda-react-spa/src/styles/index.css +23 -0
  46. package/templates/openxiangda-react-spa/tailwind.config.cjs +29 -0
  47. package/templates/openxiangda-react-spa/tsconfig.app.json +36 -0
  48. package/templates/openxiangda-react-spa/tsconfig.json +7 -0
  49. package/templates/openxiangda-react-spa/tsconfig.node.json +10 -0
  50. package/templates/openxiangda-react-spa/vite.config.ts +73 -0
@@ -0,0 +1,27 @@
1
+ import { FileText, Inbox, Search } from "lucide-react";
2
+
3
+ const entries = [
4
+ { title: "发起申请", desc: "打开常用表单和流程", icon: FileText },
5
+ { title: "我的待办", desc: "处理审批与办理任务", icon: Inbox },
6
+ { title: "数据查询", desc: "查看有权限的数据列表", icon: Search },
7
+ ];
8
+
9
+ export function UserPortalPage() {
10
+ return (
11
+ <div className="space-y-5">
12
+ <section className="ox-panel p-5">
13
+ <h1 className="text-xl font-semibold text-slate-950">用户门户</h1>
14
+ <p className="mt-1 text-sm text-slate-500">面向普通用户的入口页,和后台共享同一套权限与 SDK。</p>
15
+ </section>
16
+ <section className="grid gap-4 md:grid-cols-3">
17
+ {entries.map(entry => (
18
+ <div className="ox-panel p-5" key={entry.title}>
19
+ <entry.icon className="text-blue-600" size={20} />
20
+ <div className="mt-4 text-sm font-semibold text-slate-950">{entry.title}</div>
21
+ <div className="mt-1 text-sm text-slate-500">{entry.desc}</div>
22
+ </div>
23
+ ))}
24
+ </section>
25
+ </div>
26
+ );
27
+ }
@@ -0,0 +1,10 @@
1
+ export function PublicHomePage() {
2
+ return (
3
+ <section className="ox-panel p-6">
4
+ <h1 className="text-xl font-semibold text-slate-950">公开访问</h1>
5
+ <p className="mt-2 text-sm text-slate-500">
6
+ 公开表单、公开查询和 ticket 状态页都应在 PublicShell 下实现,不依赖旧平台导航参数。
7
+ </p>
8
+ </section>
9
+ );
10
+ }
@@ -0,0 +1,16 @@
1
+ import { Link } from "react-router-dom";
2
+
3
+ export function NotFoundPage() {
4
+ return (
5
+ <main className="grid min-h-screen place-items-center bg-[#f6f7f9] p-5">
6
+ <section className="ox-panel max-w-md p-8 text-center">
7
+ <div className="text-sm font-semibold text-blue-700">404</div>
8
+ <h1 className="mt-2 text-xl font-semibold text-slate-950">页面不存在</h1>
9
+ <p className="mt-2 text-sm text-slate-500">请检查当前 React Router 路由或平台 routeCode 配置。</p>
10
+ <Link className="mt-5 inline-flex rounded-md bg-blue-600 px-4 py-2 text-sm font-medium text-white" to="/">
11
+ 返回入口
12
+ </Link>
13
+ </section>
14
+ </main>
15
+ );
16
+ }
@@ -0,0 +1,31 @@
1
+ {
2
+ "menus": [
3
+ {
4
+ "code": "runtime_workspace",
5
+ "name": "运行时工作台",
6
+ "type": "nav",
7
+ "routeCode": "admin.dashboard",
8
+ "path": "/view/:appType/admin",
9
+ "icon": "LayoutDashboard",
10
+ "sortOrder": 10
11
+ },
12
+ {
13
+ "code": "user_portal",
14
+ "name": "用户门户",
15
+ "type": "nav",
16
+ "routeCode": "portal.home",
17
+ "path": "/view/:appType/portal",
18
+ "icon": "Home",
19
+ "sortOrder": 20
20
+ },
21
+ {
22
+ "code": "public_home",
23
+ "name": "公开访问",
24
+ "type": "nav",
25
+ "routeCode": "public.home",
26
+ "path": "/view/:appType/public",
27
+ "icon": "Globe",
28
+ "sortOrder": 30
29
+ }
30
+ ]
31
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "code": "app_admin_spa_pages",
3
+ "name": "应用管理员 SPA 页面",
4
+ "roles": ["app_admin"],
5
+ "menuCodes": ["runtime_workspace", "user_portal", "public_home"],
6
+ "routeCodes": ["admin.dashboard", "portal.home", "public.home"],
7
+ "pathPatterns": ["/view/:appType/admin/*", "/view/:appType/portal/*", "/view/:appType/public/*"]
8
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "code": "app_user_spa_pages",
3
+ "name": "普通用户 SPA 页面",
4
+ "roles": ["app_user"],
5
+ "menuCodes": ["user_portal", "public_home"],
6
+ "routeCodes": ["portal.home", "public.home"],
7
+ "pathPatterns": ["/view/:appType/portal/*", "/view/:appType/public/*"]
8
+ }
@@ -0,0 +1,14 @@
1
+ {
2
+ "roles": [
3
+ {
4
+ "code": "app_admin",
5
+ "name": "应用管理员",
6
+ "description": "可访问 mac-admin 后台与全部 React SPA 路由"
7
+ },
8
+ {
9
+ "code": "app_user",
10
+ "name": "普通用户",
11
+ "description": "可访问用户门户和被授权的业务页面"
12
+ }
13
+ ]
14
+ }
@@ -0,0 +1,135 @@
1
+ import { defineFormSchema } from "openxiangda";
2
+
3
+ export type FormComponentName =
4
+ | "TextField"
5
+ | "TextAreaField"
6
+ | "NumberField"
7
+ | "DateField"
8
+ | "CascadeDateField"
9
+ | "SelectField"
10
+ | "MultiSelectField"
11
+ | "RadioField"
12
+ | "CheckboxField"
13
+ | "UserSelectField"
14
+ | "DepartmentSelectField"
15
+ | "AttachmentField"
16
+ | "ImageField"
17
+ | "AddressField"
18
+ | "CascadeSelectField"
19
+ | "LocationField"
20
+ | "EditorField"
21
+ | "JSONField"
22
+ | "SubFormField";
23
+
24
+ export type FormOption = {
25
+ label: string;
26
+ value: string;
27
+ color?: string;
28
+ disabled?: boolean;
29
+ };
30
+
31
+ export type FieldDefinition = {
32
+ fieldId: string;
33
+ componentName: FormComponentName;
34
+ label: string;
35
+ required?: boolean;
36
+ behavior?: "NORMAL" | "READONLY" | "DISABLED" | "HIDDEN";
37
+ rules?: Array<{
38
+ required?: boolean;
39
+ message?: string;
40
+ min?: number;
41
+ max?: number;
42
+ minLength?: number;
43
+ maxLength?: number;
44
+ preset?: "phone" | "idCard" | "email" | "url" | "bankCard";
45
+ pattern?: RegExp | string;
46
+ }>;
47
+ placeholder?: string;
48
+ tips?: string;
49
+ options?: FormOption[];
50
+ defaultValue?: unknown;
51
+ columns?: FieldDefinition[];
52
+ [key: string]: unknown;
53
+ };
54
+
55
+ export type OpenXiangdaFormSchemaInput = {
56
+ formMeta: {
57
+ formUuid: string;
58
+ appType: string;
59
+ title: string;
60
+ formType?: "receipt" | "process";
61
+ };
62
+ fields: FieldDefinition[];
63
+ template?: Record<string, unknown>;
64
+ };
65
+
66
+ export function option(value: string, label = value): FormOption {
67
+ return { label, value };
68
+ }
69
+
70
+ export function options(values: string[]): FormOption[] {
71
+ return values.map((value) => option(value));
72
+ }
73
+
74
+ export function createFormSchema(input: OpenXiangdaFormSchemaInput) {
75
+ return defineFormSchema({
76
+ formMeta: input.formMeta,
77
+ template: {
78
+ ...(input.template || {}),
79
+ ...(input.formMeta.formType === "process" ? { formType: "process" } : {}),
80
+ },
81
+ fields: input.fields.map(normalizeField),
82
+ layout: input.fields.map((field) => ({
83
+ id: `layout_${field.fieldId}`,
84
+ type: "field" as const,
85
+ fieldId: field.fieldId,
86
+ })),
87
+ });
88
+ }
89
+
90
+ function normalizeField(field: FieldDefinition): FieldDefinition {
91
+ return {
92
+ ...field,
93
+ options:
94
+ needsOptionsArray(field.componentName) && !Array.isArray(field.options)
95
+ ? []
96
+ : field.options,
97
+ columns: field.columns?.map(normalizeField),
98
+ rules:
99
+ field.rules ||
100
+ (field.required
101
+ ? [
102
+ {
103
+ required: true,
104
+ message: `${needsSelectVerb(field.componentName) ? "请选择" : "请填写"}${field.label}`,
105
+ },
106
+ ]
107
+ : undefined),
108
+ };
109
+ }
110
+
111
+ function needsOptionsArray(componentName: FormComponentName): boolean {
112
+ return [
113
+ "SelectField",
114
+ "MultiSelectField",
115
+ "RadioField",
116
+ "CheckboxField",
117
+ "CascadeSelectField",
118
+ ].includes(componentName);
119
+ }
120
+
121
+ function needsSelectVerb(componentName: FormComponentName): boolean {
122
+ return [
123
+ "SelectField",
124
+ "MultiSelectField",
125
+ "RadioField",
126
+ "CheckboxField",
127
+ "UserSelectField",
128
+ "DepartmentSelectField",
129
+ "DateField",
130
+ "CascadeDateField",
131
+ "CascadeSelectField",
132
+ "ImageField",
133
+ "AttachmentField",
134
+ ].includes(componentName);
135
+ }
@@ -0,0 +1,23 @@
1
+ @tailwind base;
2
+ @tailwind components;
3
+ @tailwind utilities;
4
+
5
+ html,
6
+ body,
7
+ #root {
8
+ min-height: 100%;
9
+ }
10
+
11
+ body {
12
+ margin: 0;
13
+ font-family:
14
+ Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI",
15
+ sans-serif;
16
+ background: #f6f7f9;
17
+ }
18
+
19
+ .ox-panel {
20
+ border: 1px solid rgb(226 232 240);
21
+ border-radius: 8px;
22
+ background: rgb(255 255 255 / 0.92);
23
+ }
@@ -0,0 +1,29 @@
1
+ const path = require("node:path");
2
+ const openxiangdaPresetModule = require("openxiangda/tailwind-preset");
3
+ const openxiangdaPreset =
4
+ openxiangdaPresetModule.default ?? openxiangdaPresetModule;
5
+
6
+ function resolveOpenXiangdaContent() {
7
+ try {
8
+ const packagePath = require.resolve("openxiangda");
9
+ const distDir = path.dirname(packagePath);
10
+ return [path.join(distDir, "..", "**/*.{js,mjs,cjs}")];
11
+ } catch {
12
+ return [];
13
+ }
14
+ }
15
+
16
+ /** @type {import('tailwindcss').Config} */
17
+ module.exports = {
18
+ content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}", ...resolveOpenXiangdaContent()],
19
+ blocklist: ["[-:T]", "[-:TZ.]"],
20
+ presets: [openxiangdaPreset],
21
+ theme: {
22
+ extend: {
23
+ colors: {
24
+ graphite: "#1f2937",
25
+ },
26
+ },
27
+ },
28
+ plugins: [],
29
+ };
@@ -0,0 +1,36 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "useDefineForClassFields": true,
5
+ "lib": ["ES2022", "DOM", "DOM.Iterable"],
6
+ "allowJs": false,
7
+ "skipLibCheck": true,
8
+ "esModuleInterop": true,
9
+ "allowSyntheticDefaultImports": true,
10
+ "strict": true,
11
+ "forceConsistentCasingInFileNames": true,
12
+ "module": "ESNext",
13
+ "moduleResolution": "Bundler",
14
+ "resolveJsonModule": true,
15
+ "isolatedModules": true,
16
+ "noEmit": true,
17
+ "jsx": "react-jsx",
18
+ "baseUrl": ".",
19
+ "paths": {
20
+ "@/*": ["src/*"],
21
+ "openxiangda": [
22
+ "../../packages/sdk/src/components/index.ts",
23
+ "node_modules/openxiangda"
24
+ ],
25
+ "openxiangda/runtime": [
26
+ "../../packages/sdk/src/runtime/index.ts",
27
+ "node_modules/openxiangda/runtime"
28
+ ],
29
+ "openxiangda/runtime/react": [
30
+ "../../packages/sdk/src/runtime/react/openxiangdaProvider.tsx",
31
+ "node_modules/openxiangda/runtime/react"
32
+ ]
33
+ }
34
+ },
35
+ "include": ["src", "vite.config.ts"]
36
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "files": [],
3
+ "references": [
4
+ { "path": "./tsconfig.app.json" },
5
+ { "path": "./tsconfig.node.json" }
6
+ ]
7
+ }
@@ -0,0 +1,10 @@
1
+ {
2
+ "compilerOptions": {
3
+ "composite": true,
4
+ "skipLibCheck": true,
5
+ "module": "ESNext",
6
+ "moduleResolution": "Bundler",
7
+ "allowSyntheticDefaultImports": true
8
+ },
9
+ "include": ["vite.config.ts"]
10
+ }
@@ -0,0 +1,73 @@
1
+ import react from "@vitejs/plugin-react";
2
+ import { fileURLToPath } from "node:url";
3
+ import { defineConfig, loadEnv } from "vite";
4
+
5
+ const resolveProxyTarget = (value?: string) => {
6
+ const normalized = String(value || "").trim();
7
+ if (!normalized) return "";
8
+ try {
9
+ return new URL(normalized).origin;
10
+ } catch {
11
+ return "";
12
+ }
13
+ };
14
+
15
+ const toPort = (value?: string) => {
16
+ const port = Number(value || 5174);
17
+ return Number.isFinite(port) && port > 0 ? port : 5174;
18
+ };
19
+
20
+ export default defineConfig(({ mode }) => {
21
+ const env = loadEnv(mode, process.cwd(), "");
22
+ const serviceTarget = resolveProxyTarget(
23
+ env.OPENXIANGDA_BASE_URL || env.APP_PLATFORM_URL,
24
+ );
25
+ const servicePrefix = env.APP_SERVICE_PREFIX || "/service";
26
+ const appType = env.OPENXIANGDA_APP_TYPE || env.APP_TYPE || "";
27
+ const buildId = env.OPENXIANGDA_BUILD_ID || "";
28
+ const runtimeAssetBase =
29
+ env.OPENXIANGDA_RUNTIME_ASSET_BASE ||
30
+ (appType && buildId
31
+ ? `${servicePrefix.replace(/\/+$/, "")}/openxiangda-api/v1/apps/${encodeURIComponent(appType)}/runtime/releases/by-build/${encodeURIComponent(buildId)}/files/`
32
+ : "/");
33
+
34
+ return {
35
+ base: runtimeAssetBase,
36
+ define: {
37
+ "process.env.OPENXIANGDA_APP_TYPE": JSON.stringify(appType),
38
+ "process.env.APP_TYPE": JSON.stringify(appType),
39
+ "process.env.OPENXIANGDA_BASE_URL": JSON.stringify(
40
+ env.OPENXIANGDA_BASE_URL || env.APP_PLATFORM_URL || "",
41
+ ),
42
+ "process.env.APP_SERVICE_PREFIX": JSON.stringify(servicePrefix),
43
+ },
44
+ plugins: [react()],
45
+ resolve: {
46
+ alias: [
47
+ {
48
+ find: "@",
49
+ replacement: fileURLToPath(new URL("./src", import.meta.url)),
50
+ },
51
+ ],
52
+ dedupe: ["react", "react-dom"],
53
+ },
54
+ server: {
55
+ host: env.OPENXIANGDA_DEV_HOST || "127.0.0.1",
56
+ port: toPort(env.OPENXIANGDA_DEV_PORT),
57
+ proxy: serviceTarget
58
+ ? {
59
+ [servicePrefix]: {
60
+ target: serviceTarget,
61
+ changeOrigin: true,
62
+ secure: false,
63
+ cookieDomainRewrite: "",
64
+ cookiePathRewrite: "/",
65
+ headers: {
66
+ "x-openxiangda-dev-proxy": "1",
67
+ },
68
+ },
69
+ }
70
+ : undefined,
71
+ },
72
+ };
73
+ });