openxiangda 1.0.51 → 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.
- package/package.json +1 -1
- package/packages/sdk/src/build-source/scripts/sync-schema.mjs +31 -0
- package/templates/openxiangda-react-spa/AGENTS.md +4 -0
- package/templates/openxiangda-react-spa/app-workspace.config.ts +32 -0
- package/templates/openxiangda-react-spa/package.json +4 -0
- package/templates/openxiangda-react-spa/src/forms/.gitkeep +1 -0
- package/templates/openxiangda-react-spa/src/shared/form-schema.ts +135 -0
package/package.json
CHANGED
|
@@ -79,6 +79,36 @@ sync-schema - 将表单 Schema 同步到后端 API
|
|
|
79
79
|
`);
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
+
function resolveWorkspaceAlias(importPath) {
|
|
83
|
+
if (!importPath.startsWith("@/")) return null;
|
|
84
|
+
const basePath = path.join(rootDir, "src", importPath.slice(2));
|
|
85
|
+
const candidates = [
|
|
86
|
+
basePath,
|
|
87
|
+
`${basePath}.ts`,
|
|
88
|
+
`${basePath}.tsx`,
|
|
89
|
+
`${basePath}.js`,
|
|
90
|
+
`${basePath}.jsx`,
|
|
91
|
+
`${basePath}.mjs`,
|
|
92
|
+
path.join(basePath, "index.ts"),
|
|
93
|
+
path.join(basePath, "index.tsx"),
|
|
94
|
+
path.join(basePath, "index.js"),
|
|
95
|
+
path.join(basePath, "index.jsx"),
|
|
96
|
+
path.join(basePath, "index.mjs"),
|
|
97
|
+
];
|
|
98
|
+
return candidates.find((candidate) => fs.existsSync(candidate)) || basePath;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function workspaceAliasPlugin() {
|
|
102
|
+
return {
|
|
103
|
+
name: "openxiangda-workspace-alias",
|
|
104
|
+
setup(build) {
|
|
105
|
+
build.onResolve({ filter: /^@\// }, (args) => ({
|
|
106
|
+
path: resolveWorkspaceAlias(args.path),
|
|
107
|
+
}));
|
|
108
|
+
},
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
82
112
|
// ---------- 表单发现 ----------
|
|
83
113
|
|
|
84
114
|
function discoverForms(filterName) {
|
|
@@ -117,6 +147,7 @@ async function loadSchema(schemaPath) {
|
|
|
117
147
|
format: "esm",
|
|
118
148
|
platform: "node",
|
|
119
149
|
target: "node18",
|
|
150
|
+
plugins: [workspaceAliasPlugin()],
|
|
120
151
|
write: false,
|
|
121
152
|
outfile: "out.mjs",
|
|
122
153
|
});
|
|
@@ -18,6 +18,7 @@ pnpm install
|
|
|
18
18
|
pnpm dev
|
|
19
19
|
pnpm typecheck
|
|
20
20
|
pnpm build
|
|
21
|
+
openxiangda workspace publish --profile <name> --form <formCode>
|
|
21
22
|
openxiangda resource plan --profile <name>
|
|
22
23
|
openxiangda resource publish --profile <name>
|
|
23
24
|
openxiangda runtime deploy --profile <name>
|
|
@@ -28,10 +29,13 @@ openxiangda runtime deploy --profile <name>
|
|
|
28
29
|
完整发布顺序:
|
|
29
30
|
|
|
30
31
|
```bash
|
|
32
|
+
openxiangda workspace publish --profile <name> --form <formCode>
|
|
31
33
|
openxiangda resource publish --profile <name>
|
|
32
34
|
openxiangda runtime deploy --profile <name>
|
|
33
35
|
```
|
|
34
36
|
|
|
37
|
+
如果应用需要平台表单、流程表单、详情页或数据列表页,在 `src/forms/<formCode>/schema.ts` 定义表单 schema,并用 `openxiangda workspace publish --form <formCode>` 发布。React SPA 自身继续用 `runtime deploy` 发布。
|
|
38
|
+
|
|
35
39
|
## 权限资源
|
|
36
40
|
|
|
37
41
|
React SPA 页面需要声明菜单 code、route code 和 path pattern:
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { defineAppWorkspaceConfig } from "openxiangda/build";
|
|
2
|
+
|
|
3
|
+
export default defineAppWorkspaceConfig({
|
|
4
|
+
appType: process.env.APP_TYPE || process.env.OPENXIANGDA_APP_TYPE || "APP_XXXXXXXXXXXXXXXX",
|
|
5
|
+
appName: process.env.APP_NAME || "OpenXiangda React SPA",
|
|
6
|
+
platformUrl:
|
|
7
|
+
process.env.APP_PLATFORM_URL ||
|
|
8
|
+
process.env.OPENXIANGDA_BASE_URL ||
|
|
9
|
+
"https://yida.wisejob.cn/service",
|
|
10
|
+
servicePrefix: process.env.APP_SERVICE_PREFIX || "/service",
|
|
11
|
+
appKey: process.env.APP_KEY || "",
|
|
12
|
+
appSecret: process.env.APP_SECRET || "",
|
|
13
|
+
userId: process.env.APP_USER_ID || "",
|
|
14
|
+
version: process.env.APP_VERSION || "0.1.0",
|
|
15
|
+
buildId: process.env.APP_BUILD_ID || "",
|
|
16
|
+
oss: {
|
|
17
|
+
region: process.env.APP_OSS_REGION || "oss-cn-hangzhou",
|
|
18
|
+
bucket: process.env.APP_OSS_BUCKET || "sy-app-workspace-dev",
|
|
19
|
+
accessKeyId: process.env.APP_OSS_ACCESS_KEY_ID || "",
|
|
20
|
+
accessKeySecret: process.env.APP_OSS_ACCESS_KEY_SECRET || "",
|
|
21
|
+
pathPrefix: process.env.APP_OSS_PATH_PREFIX || "app-workspace",
|
|
22
|
+
},
|
|
23
|
+
defaults: {
|
|
24
|
+
protocolVersion: process.env.APP_PAGE_PROTOCOL_VERSION || "1.0",
|
|
25
|
+
frameworkVersion: process.env.APP_FRAMEWORK_VERSION || "18.3.1",
|
|
26
|
+
cssIsolation: "none",
|
|
27
|
+
formMenuParentId: process.env.APP_FORM_MENU_PARENT_ID || "",
|
|
28
|
+
formMenuIcon: process.env.APP_FORM_MENU_ICON || "",
|
|
29
|
+
pageMenuParentId: process.env.APP_PAGE_MENU_PARENT_ID || "",
|
|
30
|
+
pageMenuIcon: process.env.APP_PAGE_MENU_ICON || "",
|
|
31
|
+
},
|
|
32
|
+
});
|
|
@@ -6,6 +6,10 @@
|
|
|
6
6
|
"scripts": {
|
|
7
7
|
"dev": "vite",
|
|
8
8
|
"build": "vite build",
|
|
9
|
+
"build:forms": "lowcode-workspace build-forms",
|
|
10
|
+
"sync-schema": "lowcode-workspace sync-schema",
|
|
11
|
+
"publish:all": "lowcode-workspace publish-all",
|
|
12
|
+
"openxiangda:publish": "lowcode-workspace publish-all",
|
|
9
13
|
"typecheck": "tsc -p tsconfig.app.json --noEmit",
|
|
10
14
|
"check": "pnpm typecheck && pnpm build",
|
|
11
15
|
"resources:plan": "openxiangda resource plan",
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -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
|
+
}
|