openxiangda-cli 2.0.0-alpha.61 → 2.0.0-alpha.62
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/dist/create-workspace.d.ts.map +1 -1
- package/dist/create-workspace.js +1 -0
- package/dist/create-workspace.js.map +1 -1
- package/package.json +4 -4
- package/template/AGENTS.md +1 -1
- package/template/README.md +7 -7
- package/template/apps/server/package.json +1 -1
- package/template/apps/server/src/app.module.ts +1 -3
- package/template/apps/server/test/smoke.test.ts +6 -23
- package/template/apps/web/e2e/resources.spec.ts +20 -0
- package/template/apps/web/index.html +1 -1
- package/template/apps/web/package.json +1 -1
- package/template/apps/web/src/AuthoritativeSelector.tsx +38 -29
- package/template/apps/web/src/Shell.tsx +39 -211
- package/template/apps/web/src/components/platform-fields/AttachmentFileList.tsx +1 -1
- package/template/apps/web/src/components/resource/GeneratedResourceCrud.tsx +26 -5
- package/template/apps/web/src/components/resource/SurfaceFields.tsx +32 -1
- package/template/apps/web/src/data-provider.ts +23 -102
- package/template/apps/web/src/main.tsx +22 -82
- package/template/apps/web/src/platform-client.ts +71 -415
- package/template/apps/web/src/runtime-meta.ts +1 -1
- package/template/apps/web/src/styles.css +3 -3
- package/template/apps/web/test/contracts.test.ts +31 -769
- package/template/openxiangda.config.ts +14 -81
- package/template/package.json +3 -3
- package/template/packages/contracts/src/generated.ts +7 -804
- package/template/apps/server/src/instrument-context.controller.ts +0 -25
- package/template/apps/web/e2e/instruments.spec.ts +0 -1338
- package/template/apps/web/src/CollegePage.tsx +0 -181
- package/template/apps/web/src/InstrumentDetailPage.tsx +0 -176
- package/template/apps/web/src/InstrumentForm.tsx +0 -380
- package/template/apps/web/src/InstrumentFormPage.tsx +0 -82
- package/template/apps/web/src/InstrumentListPage.tsx +0 -457
- package/template/apps/web/src/fields.ts +0 -18
- package/template/apps/web/src/instrument.ts +0 -56
- package/template/platform/data/colleges.ts +0 -21
- package/template/platform/data/instrument-surface.ts +0 -125
- package/template/platform/data/instruments.ts +0 -83
|
@@ -1,27 +1,18 @@
|
|
|
1
1
|
import {
|
|
2
2
|
SCHEMA_VERSIONS,
|
|
3
|
+
type DataAuditPage,
|
|
3
4
|
type DataFilePreview,
|
|
4
5
|
type DataFileRef,
|
|
5
6
|
type DataFileUploadPlan,
|
|
6
|
-
type DataAuditPage,
|
|
7
7
|
type DataPage,
|
|
8
8
|
type DataQuery,
|
|
9
9
|
type DataRecord,
|
|
10
|
+
type DataResourceSurface,
|
|
10
11
|
type DirectoryEntryPage,
|
|
11
12
|
type DirectoryResolveRequest,
|
|
12
|
-
type NativeScopeValuePage,
|
|
13
|
-
type NativeScopeValueResolveRequest,
|
|
14
|
-
type DataResourceSurface,
|
|
15
13
|
} from 'openxiangda-contracts/browser';
|
|
16
|
-
import type { InstrumentListQuery, InstrumentRecord } from './instrument';
|
|
17
|
-
import { instrumentSurface, isInstrumentResourceField } from './fields';
|
|
18
14
|
import { applicationCode, runtimeMount } from './runtime-meta';
|
|
19
15
|
|
|
20
|
-
const resourceCode = 'instruments';
|
|
21
|
-
const nativeBase = () =>
|
|
22
|
-
`/service/openxiangda-api/v2/applications/${applicationCode()}/native`;
|
|
23
|
-
const dataBase = (code = resourceCode) => `${nativeBase()}/data/${code}`;
|
|
24
|
-
|
|
25
16
|
interface PlatformEnvelope<T> {
|
|
26
17
|
code: number;
|
|
27
18
|
message?: string;
|
|
@@ -39,19 +30,12 @@ async function request<T>(path: string, init?: RequestInit): Promise<T> {
|
|
|
39
30
|
...init?.headers,
|
|
40
31
|
},
|
|
41
32
|
});
|
|
42
|
-
const payload = (await response.json().catch(() => null)) as
|
|
43
|
-
|
|
44
|
-
| T
|
|
45
|
-
| null;
|
|
46
|
-
const envelope =
|
|
47
|
-
payload && typeof payload === 'object' && 'code' in payload
|
|
48
|
-
? (payload as PlatformEnvelope<T>)
|
|
49
|
-
: null;
|
|
33
|
+
const payload = (await response.json().catch(() => null)) as PlatformEnvelope<T> | T | null;
|
|
34
|
+
const envelope = payload && typeof payload === 'object' && 'code' in payload ? payload as PlatformEnvelope<T> : null;
|
|
50
35
|
if (!response.ok || (envelope && envelope.code !== 200)) {
|
|
51
|
-
|
|
52
|
-
throw new Error(`${code}: ${envelope?.message || '平台请求失败'}`);
|
|
36
|
+
throw new Error(`${envelope?.errorCode || `HTTP_${response.status}`}: ${envelope?.message || '平台请求失败'}`);
|
|
53
37
|
}
|
|
54
|
-
return envelope ? envelope.data :
|
|
38
|
+
return envelope ? envelope.data : payload as T;
|
|
55
39
|
}
|
|
56
40
|
|
|
57
41
|
export interface RuntimeIdentity {
|
|
@@ -72,33 +56,14 @@ export interface RuntimeIdentity {
|
|
|
72
56
|
|
|
73
57
|
interface ConnectedCurrent {
|
|
74
58
|
environment: RuntimeIdentity['environment'];
|
|
75
|
-
principal:
|
|
76
|
-
userId: string;
|
|
77
|
-
roleCodes: string[];
|
|
78
|
-
capabilityCodes: string[];
|
|
79
|
-
isAppSuperAdmin: boolean;
|
|
80
|
-
};
|
|
59
|
+
principal: Pick<RuntimeIdentity, 'userId' | 'roleCodes' | 'capabilityCodes' | 'isAppSuperAdmin'>;
|
|
81
60
|
}
|
|
82
61
|
|
|
83
62
|
interface DeployedCurrent {
|
|
84
63
|
schemaVersion: 'openxiangda.current-user/v2';
|
|
85
64
|
appCode: string;
|
|
86
|
-
environment:
|
|
87
|
-
|
|
88
|
-
key: 'preproduction' | 'production';
|
|
89
|
-
activeAppVersionId: string;
|
|
90
|
-
headRevision: number;
|
|
91
|
-
authzRevisionId: string;
|
|
92
|
-
authzVersion: number;
|
|
93
|
-
scopeDataVersion: string;
|
|
94
|
-
};
|
|
95
|
-
principal: {
|
|
96
|
-
type: 'user';
|
|
97
|
-
userId: string;
|
|
98
|
-
roleCodes: string[];
|
|
99
|
-
capabilityCodes: string[];
|
|
100
|
-
isAppSuperAdmin: boolean;
|
|
101
|
-
};
|
|
65
|
+
environment: RuntimeIdentity['environment'];
|
|
66
|
+
principal: { type: 'user' } & Pick<RuntimeIdentity, 'userId' | 'roleCodes' | 'capabilityCodes' | 'isAppSuperAdmin'>;
|
|
102
67
|
}
|
|
103
68
|
|
|
104
69
|
let activeIdentity: RuntimeIdentity | undefined;
|
|
@@ -106,35 +71,16 @@ let activeIdentity: RuntimeIdentity | undefined;
|
|
|
106
71
|
export async function loadCurrentIdentity(): Promise<RuntimeIdentity> {
|
|
107
72
|
const mount = runtimeMount();
|
|
108
73
|
if (mount) {
|
|
109
|
-
const current = await request<DeployedCurrent>(
|
|
110
|
-
|
|
111
|
-
mount.environmentKey
|
|
112
|
-
)}`
|
|
113
|
-
);
|
|
114
|
-
if (
|
|
115
|
-
current.schemaVersion !== 'openxiangda.current-user/v2' ||
|
|
116
|
-
current.appCode !== mount.appCode ||
|
|
117
|
-
current.environment.key !== mount.environmentKey ||
|
|
118
|
-
current.principal.type !== 'user' ||
|
|
119
|
-
!Array.isArray(current.principal.roleCodes) ||
|
|
120
|
-
!Array.isArray(current.principal.capabilityCodes)
|
|
121
|
-
) {
|
|
74
|
+
const current = await request<DeployedCurrent>(`${nativeBase()}/current-user?environmentKey=${encodeURIComponent(mount.environmentKey)}`);
|
|
75
|
+
if (current.schemaVersion !== 'openxiangda.current-user/v2' || current.appCode !== mount.appCode || current.environment.key !== mount.environmentKey || current.principal.type !== 'user') {
|
|
122
76
|
throw new Error('OPENXIANGDA_DEPLOYED_CURRENT_USER_INVALID');
|
|
123
77
|
}
|
|
124
78
|
activeIdentity = { ...current.principal, environment: current.environment };
|
|
125
79
|
return activeIdentity;
|
|
126
80
|
}
|
|
127
|
-
const
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
const current = await request<ConnectedCurrent>(connectedPath);
|
|
131
|
-
activeIdentity = { ...current.principal, environment: current.environment };
|
|
132
|
-
return activeIdentity;
|
|
133
|
-
} catch (error) {
|
|
134
|
-
throw new Error(
|
|
135
|
-
`OPENXIANGDA_CONNECTED_CURRENT_USER_FAILED: ${error instanceof Error ? error.message : String(error)}`
|
|
136
|
-
);
|
|
137
|
-
}
|
|
81
|
+
const current = await request<ConnectedCurrent>(`/service/openxiangda-api/v2/applications/${applicationCode()}/dev-sessions/current`);
|
|
82
|
+
activeIdentity = { ...current.principal, environment: current.environment };
|
|
83
|
+
return activeIdentity;
|
|
138
84
|
}
|
|
139
85
|
|
|
140
86
|
function currentEnvironmentKey() {
|
|
@@ -145,244 +91,59 @@ function currentEnvironmentKey() {
|
|
|
145
91
|
|
|
146
92
|
export type DirectoryKind = 'user' | 'department';
|
|
147
93
|
|
|
148
|
-
export async function searchDirectory(
|
|
149
|
-
|
|
150
|
-
options: { keyword: string; cursor?: string }
|
|
151
|
-
) {
|
|
152
|
-
const params = new URLSearchParams({
|
|
153
|
-
environmentKey: currentEnvironmentKey(),
|
|
154
|
-
keyword: options.keyword.trim(),
|
|
155
|
-
limit: '20',
|
|
156
|
-
});
|
|
94
|
+
export async function searchDirectory(kind: DirectoryKind, options: { keyword: string; cursor?: string }) {
|
|
95
|
+
const params = new URLSearchParams({ environmentKey: currentEnvironmentKey(), keyword: options.keyword.trim(), limit: '20' });
|
|
157
96
|
if (options.cursor) params.set('cursor', options.cursor);
|
|
158
|
-
return request<DirectoryEntryPage>(
|
|
159
|
-
`${nativeBase().replace(/\/native$/, '')}/directory/${kind}s?${params}`
|
|
160
|
-
);
|
|
97
|
+
return request<DirectoryEntryPage>(`${nativeBase().replace(/\/native$/, '')}/directory/${kind}s?${params}`);
|
|
161
98
|
}
|
|
162
99
|
|
|
163
|
-
export async function browseDepartmentTree(options: {
|
|
164
|
-
|
|
165
|
-
cursor?: string;
|
|
166
|
-
}) {
|
|
167
|
-
const params = new URLSearchParams({
|
|
168
|
-
environmentKey: currentEnvironmentKey(),
|
|
169
|
-
limit: '100',
|
|
170
|
-
});
|
|
100
|
+
export async function browseDepartmentTree(options: { parentId?: string; cursor?: string }) {
|
|
101
|
+
const params = new URLSearchParams({ environmentKey: currentEnvironmentKey(), limit: '100' });
|
|
171
102
|
if (options.parentId) params.set('parentId', options.parentId);
|
|
172
103
|
if (options.cursor) params.set('offset', options.cursor);
|
|
173
|
-
return request<DirectoryEntryPage>(
|
|
174
|
-
`${nativeBase().replace(/\/native$/, '')}/directory/departments/tree?${params}`
|
|
175
|
-
);
|
|
104
|
+
return request<DirectoryEntryPage>(`${nativeBase().replace(/\/native$/, '')}/directory/departments/tree?${params}`);
|
|
176
105
|
}
|
|
177
106
|
|
|
178
|
-
export async function browseDepartmentUsers(
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
) {
|
|
182
|
-
const params = new URLSearchParams({
|
|
183
|
-
environmentKey: currentEnvironmentKey(),
|
|
184
|
-
page: String(page),
|
|
185
|
-
limit: '20',
|
|
186
|
-
});
|
|
187
|
-
return request<DirectoryEntryPage>(
|
|
188
|
-
`${nativeBase().replace(/\/native$/, '')}/directory/departments/${encodeURIComponent(
|
|
189
|
-
departmentId
|
|
190
|
-
)}/users?${params}`
|
|
191
|
-
);
|
|
107
|
+
export async function browseDepartmentUsers(departmentId: string, page = 1) {
|
|
108
|
+
const params = new URLSearchParams({ environmentKey: currentEnvironmentKey(), page: String(page), limit: '20' });
|
|
109
|
+
return request<DirectoryEntryPage>(`${nativeBase().replace(/\/native$/, '')}/directory/departments/${encodeURIComponent(departmentId)}/users?${params}`);
|
|
192
110
|
}
|
|
193
111
|
|
|
194
|
-
export async function resolveDirectory(
|
|
195
|
-
kind: DirectoryKind,
|
|
196
|
-
ids: string[]
|
|
197
|
-
) {
|
|
112
|
+
export async function resolveDirectory(kind: DirectoryKind, ids: string[]) {
|
|
198
113
|
const uniqueIds = [...new Set(ids)];
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
114
|
+
if (uniqueIds.length === 0) throw new Error('OPENXIANGDA_DIRECTORY_RESOLVE_IDS_REQUIRED');
|
|
115
|
+
if (uniqueIds.length > 50) throw new Error('OPENXIANGDA_DIRECTORY_RESOLVE_IDS_EXCEEDED');
|
|
116
|
+
const body: DirectoryResolveRequest = { schemaVersion: SCHEMA_VERSIONS.directoryResolveRequest, kind, ids: uniqueIds };
|
|
117
|
+
return request<DirectoryEntryPage>(`${nativeBase().replace(/\/native$/, '')}/directory/resolve?environmentKey=${encodeURIComponent(currentEnvironmentKey())}`, { method: 'POST', body: JSON.stringify(body) });
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export async function searchResource(resourceCode: string, labelField: string, options: { keyword: string; cursor?: string }) {
|
|
121
|
+
const pageSize = 20;
|
|
122
|
+
const offset = options.cursor ? Number(options.cursor) : 0;
|
|
123
|
+
const query: DataQuery = {
|
|
124
|
+
schemaVersion: SCHEMA_VERSIONS.dataQuery,
|
|
125
|
+
filters: options.keyword.trim() ? [{ field: labelField, operator: 'ilike', value: `%${options.keyword.trim()}%` }] : [],
|
|
126
|
+
order: [{ field: labelField, direction: 'asc' }],
|
|
127
|
+
limit: pageSize,
|
|
128
|
+
offset: Number.isSafeInteger(offset) && offset >= 0 ? offset : 0,
|
|
203
129
|
};
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
throw new Error('OPENXIANGDA_DIRECTORY_RESOLVE_IDS_EXCEEDED');
|
|
209
|
-
}
|
|
210
|
-
return request<DirectoryEntryPage>(
|
|
211
|
-
`${nativeBase().replace(/\/native$/, '')}/directory/resolve?environmentKey=${encodeURIComponent(
|
|
212
|
-
currentEnvironmentKey()
|
|
213
|
-
)}`,
|
|
214
|
-
{ method: 'POST', body: JSON.stringify(body) }
|
|
215
|
-
);
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
const scopeValuesBase = () =>
|
|
219
|
-
`${nativeBase()}/data-resources/${resourceCode}/fields/collegeId/scope-values`;
|
|
220
|
-
|
|
221
|
-
export async function searchCollegeScope(
|
|
222
|
-
operation: 'create' | 'update',
|
|
223
|
-
options: { keyword: string; cursor?: string }
|
|
224
|
-
) {
|
|
225
|
-
const params = new URLSearchParams({
|
|
226
|
-
environmentKey: currentEnvironmentKey(),
|
|
227
|
-
dimensionCode: 'college',
|
|
228
|
-
operation,
|
|
229
|
-
keyword: options.keyword.trim(),
|
|
230
|
-
limit: '20',
|
|
231
|
-
});
|
|
232
|
-
if (options.cursor) params.set('cursor', options.cursor);
|
|
233
|
-
return request<NativeScopeValuePage>(`${scopeValuesBase()}?${params}`);
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
export async function resolveCollegeScope(
|
|
237
|
-
operation: 'create' | 'update',
|
|
238
|
-
values: string[]
|
|
239
|
-
) {
|
|
240
|
-
const uniqueValues = [...new Set(values)];
|
|
241
|
-
const body: NativeScopeValueResolveRequest = {
|
|
242
|
-
schemaVersion: SCHEMA_VERSIONS.nativeScopeValueResolveRequest,
|
|
243
|
-
dimensionCode: 'college',
|
|
244
|
-
operation,
|
|
245
|
-
values: uniqueValues,
|
|
130
|
+
const page = await request<DataPage<Record<string, unknown>>>(`${dataBase(resourceCode)}/query`, { method: 'POST', body: JSON.stringify({ ...query, environmentKey: currentEnvironmentKey() }) });
|
|
131
|
+
return {
|
|
132
|
+
items: page.items.map(item => ({ id: String(item.id), label: String(item[labelField] ?? item.id), selectable: true })),
|
|
133
|
+
nextCursor: page.items.length >= pageSize ? String(page.offset + page.items.length) : null,
|
|
246
134
|
};
|
|
247
|
-
if (body.values.length === 0) {
|
|
248
|
-
throw new Error('OPENXIANGDA_SCOPE_RESOLVE_VALUES_REQUIRED');
|
|
249
|
-
}
|
|
250
|
-
if (body.values.length > 50) {
|
|
251
|
-
throw new Error('OPENXIANGDA_SCOPE_RESOLVE_VALUES_EXCEEDED');
|
|
252
|
-
}
|
|
253
|
-
return request<NativeScopeValuePage>(
|
|
254
|
-
`${scopeValuesBase()}/resolve?environmentKey=${encodeURIComponent(
|
|
255
|
-
currentEnvironmentKey()
|
|
256
|
-
)}`,
|
|
257
|
-
{ method: 'POST', body: JSON.stringify(body) }
|
|
258
|
-
);
|
|
259
135
|
}
|
|
260
136
|
|
|
261
|
-
export
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
137
|
+
export async function resolveResource(resourceCode: string, ids: string[], labelField: string) {
|
|
138
|
+
if (ids.length === 0) throw new Error('OPENXIANGDA_RESOURCE_RESOLVE_IDS_REQUIRED');
|
|
139
|
+
if (ids.length > 50) throw new Error('OPENXIANGDA_RESOURCE_RESOLVE_IDS_EXCEEDED');
|
|
140
|
+
const items = await Promise.all(ids.map(async id => {
|
|
141
|
+
const record = await request<DataRecord<Record<string, unknown>>>(`${dataBase(resourceCode)}/records/${encodeURIComponent(id)}?environmentKey=${encodeURIComponent(currentEnvironmentKey())}`);
|
|
142
|
+
return { id, label: String(record.data[labelField] ?? id), selectable: true };
|
|
143
|
+
}));
|
|
144
|
+
return { items, nextCursor: null };
|
|
269
145
|
}
|
|
270
146
|
|
|
271
|
-
export const instrumentDataApi: InstrumentDataApiAdapter = {
|
|
272
|
-
async list(query) {
|
|
273
|
-
const filters: NonNullable<DataQuery['filters']> = [];
|
|
274
|
-
// The current DataQuery contract has AND filters but no grouped OR. Keep
|
|
275
|
-
// this honest single-field search until the platform adds a native OR AST.
|
|
276
|
-
if (query.keyword?.trim()) {
|
|
277
|
-
const searchableFields = instrumentSurface.list?.searchableFields || [];
|
|
278
|
-
const field = searchableFields.includes(query.searchField || '')
|
|
279
|
-
? query.searchField!
|
|
280
|
-
: searchableFields[0] || 'chineseName';
|
|
281
|
-
filters.push({ field, operator: 'ilike', value: `%${query.keyword.trim()}%` });
|
|
282
|
-
}
|
|
283
|
-
if (query.collegeId) filters.push({ field: 'collegeId', operator: 'eq', value: query.collegeId });
|
|
284
|
-
if (query.instrumentAdminId) filters.push({ field: 'instrumentAdminIds', operator: 'cs', value: [query.instrumentAdminId] });
|
|
285
|
-
if (query.instrumentStatus) filters.push({ field: 'instrumentStatus', operator: 'eq', value: query.instrumentStatus });
|
|
286
|
-
if (query.openToOutside !== undefined) filters.push({ field: 'openToOutside', operator: 'eq', value: query.openToOutside });
|
|
287
|
-
if (query.enabledDate) {
|
|
288
|
-
filters.push({ field: 'enabledDate', operator: 'gte', value: query.enabledDate[0] });
|
|
289
|
-
filters.push({ field: 'enabledDate', operator: 'lte', value: query.enabledDate[1] });
|
|
290
|
-
}
|
|
291
|
-
if (query.assetValue?.[0] !== undefined) filters.push({ field: 'assetValue', operator: 'gte', value: query.assetValue[0] });
|
|
292
|
-
if (query.assetValue?.[1] !== undefined) filters.push({ field: 'assetValue', operator: 'lte', value: query.assetValue[1] });
|
|
293
|
-
const body: DataQuery = {
|
|
294
|
-
schemaVersion: SCHEMA_VERSIONS.dataQuery,
|
|
295
|
-
filters,
|
|
296
|
-
order: [{ field: String(query.sort?.field || 'instrumentCode'), direction: query.sort?.order || 'asc' }],
|
|
297
|
-
limit: query.pageSize,
|
|
298
|
-
offset: (query.page - 1) * query.pageSize,
|
|
299
|
-
};
|
|
300
|
-
assertInstrumentQueryFields(body);
|
|
301
|
-
const page = await request<DataPage<InstrumentRecord>>(`${dataBase()}/query`, {
|
|
302
|
-
method: 'POST',
|
|
303
|
-
body: JSON.stringify({ ...body, environmentKey: currentEnvironmentKey() }),
|
|
304
|
-
});
|
|
305
|
-
return { rows: page.items, total: page.total };
|
|
306
|
-
},
|
|
307
|
-
async get(id) {
|
|
308
|
-
const record = await request<DataRecord<InstrumentRecord>>(
|
|
309
|
-
`${dataBase()}/records/${encodeURIComponent(
|
|
310
|
-
id
|
|
311
|
-
)}?environmentKey=${encodeURIComponent(currentEnvironmentKey())}`
|
|
312
|
-
);
|
|
313
|
-
return record.data;
|
|
314
|
-
},
|
|
315
|
-
async audit(id) {
|
|
316
|
-
return await request<DataAuditPage>(
|
|
317
|
-
`${dataBase()}/records/${encodeURIComponent(
|
|
318
|
-
id
|
|
319
|
-
)}/audit?limit=10&offset=0&environmentKey=${encodeURIComponent(
|
|
320
|
-
currentEnvironmentKey()
|
|
321
|
-
)}`
|
|
322
|
-
);
|
|
323
|
-
},
|
|
324
|
-
async create(data) {
|
|
325
|
-
const record = await request<DataRecord<InstrumentRecord>>(`${dataBase()}/records`, {
|
|
326
|
-
method: 'POST',
|
|
327
|
-
body: JSON.stringify({ environmentKey: currentEnvironmentKey(), data }),
|
|
328
|
-
});
|
|
329
|
-
return record.data;
|
|
330
|
-
},
|
|
331
|
-
async update(id, expectedRevision, data) {
|
|
332
|
-
const record = await request<DataRecord<InstrumentRecord>>(
|
|
333
|
-
`${dataBase()}/records/${encodeURIComponent(id)}/update`,
|
|
334
|
-
{
|
|
335
|
-
method: 'POST',
|
|
336
|
-
body: JSON.stringify({
|
|
337
|
-
environmentKey: currentEnvironmentKey(),
|
|
338
|
-
expectedRevision,
|
|
339
|
-
data,
|
|
340
|
-
}),
|
|
341
|
-
}
|
|
342
|
-
);
|
|
343
|
-
return record.data;
|
|
344
|
-
},
|
|
345
|
-
async remove(id, expectedRevision) {
|
|
346
|
-
const record = await request<DataRecord<InstrumentRecord>>(
|
|
347
|
-
`${dataBase()}/records/${encodeURIComponent(id)}/delete`,
|
|
348
|
-
{
|
|
349
|
-
method: 'POST',
|
|
350
|
-
body: JSON.stringify({
|
|
351
|
-
environmentKey: currentEnvironmentKey(),
|
|
352
|
-
expectedRevision,
|
|
353
|
-
}),
|
|
354
|
-
}
|
|
355
|
-
);
|
|
356
|
-
return record.data;
|
|
357
|
-
},
|
|
358
|
-
async upload(fieldCode, file, recordId) {
|
|
359
|
-
const plan = await request<DataFileUploadPlan>(`${dataBase()}/files/uploads/initiate`, {
|
|
360
|
-
method: 'POST',
|
|
361
|
-
body: JSON.stringify({
|
|
362
|
-
environmentKey: currentEnvironmentKey(),
|
|
363
|
-
fieldCode,
|
|
364
|
-
fileName: file.name,
|
|
365
|
-
fileSize: file.size,
|
|
366
|
-
contentType: file.type || 'application/octet-stream',
|
|
367
|
-
...(recordId ? { recordId } : {}),
|
|
368
|
-
}),
|
|
369
|
-
});
|
|
370
|
-
const uploaded = await fetch(plan.uploadUrl, {
|
|
371
|
-
method: plan.uploadMethod,
|
|
372
|
-
headers: plan.headers,
|
|
373
|
-
body: file,
|
|
374
|
-
});
|
|
375
|
-
if (!uploaded.ok) throw new Error(`FILE_UPLOAD_FAILED: HTTP_${uploaded.status}`);
|
|
376
|
-
return await request<DataFileRef>(
|
|
377
|
-
`${dataBase()}/files/${encodeURIComponent(plan.file.id)}/complete`,
|
|
378
|
-
{
|
|
379
|
-
method: 'POST',
|
|
380
|
-
body: JSON.stringify({ environmentKey: currentEnvironmentKey() }),
|
|
381
|
-
}
|
|
382
|
-
);
|
|
383
|
-
},
|
|
384
|
-
};
|
|
385
|
-
|
|
386
147
|
export interface GenericResourceQuery {
|
|
387
148
|
page: number;
|
|
388
149
|
pageSize: number;
|
|
@@ -392,17 +153,11 @@ export interface GenericResourceQuery {
|
|
|
392
153
|
sort?: { field: string; order: 'asc' | 'desc' };
|
|
393
154
|
}
|
|
394
155
|
|
|
395
|
-
|
|
396
|
-
export function createNativeResourceClient(
|
|
397
|
-
code: string,
|
|
398
|
-
surface: DataResourceSurface
|
|
399
|
-
) {
|
|
156
|
+
export function createNativeResourceClient(code: string, surface: DataResourceSurface) {
|
|
400
157
|
const base = dataBase(code);
|
|
401
158
|
const declaredFields = new Set(Object.keys(surface.fields));
|
|
402
159
|
const assertField = (field: string) => {
|
|
403
|
-
if (!declaredFields.has(field)) {
|
|
404
|
-
throw new Error(`OPENXIANGDA_RESOURCE_QUERY_FIELD_NOT_DECLARED:${code}:${field}`);
|
|
405
|
-
}
|
|
160
|
+
if (!declaredFields.has(field)) throw new Error(`OPENXIANGDA_RESOURCE_QUERY_FIELD_NOT_DECLARED:${code}:${field}`);
|
|
406
161
|
return field;
|
|
407
162
|
};
|
|
408
163
|
const filtersFor = (query: GenericResourceQuery) => {
|
|
@@ -418,7 +173,7 @@ export function createNativeResourceClient(
|
|
|
418
173
|
if (Array.isArray(value) && value.length === 2 && ['date', 'datetime', 'number', 'money', 'percent'].includes(field?.widget || '')) {
|
|
419
174
|
if (value[0] !== undefined && value[0] !== null && value[0] !== '') filters.push({ field: fieldCode, operator: 'gte', value: value[0] });
|
|
420
175
|
if (value[1] !== undefined && value[1] !== null && value[1] !== '') filters.push({ field: fieldCode, operator: 'lte', value: value[1] });
|
|
421
|
-
} else if (Array.isArray(value) && ['multi', 'directory-user', 'directory-department'].includes(field?.widget || '')) {
|
|
176
|
+
} else if (Array.isArray(value) && ['multi', 'directory-user', 'directory-department', 'resource'].includes(field?.widget || '')) {
|
|
422
177
|
filters.push({ field: fieldCode, operator: 'cs', value });
|
|
423
178
|
} else {
|
|
424
179
|
filters.push({ field: fieldCode, operator: 'eq', value });
|
|
@@ -429,17 +184,8 @@ export function createNativeResourceClient(
|
|
|
429
184
|
return {
|
|
430
185
|
async list(query: GenericResourceQuery) {
|
|
431
186
|
const sort = query.sort || surface.list?.defaultSort || { field: Object.keys(surface.fields)[0] || 'id', order: 'asc' as const };
|
|
432
|
-
const body: DataQuery = {
|
|
433
|
-
|
|
434
|
-
filters: filtersFor(query),
|
|
435
|
-
order: [{ field: assertField(sort.field), direction: sort.order || 'asc' }],
|
|
436
|
-
limit: query.pageSize,
|
|
437
|
-
offset: (query.page - 1) * query.pageSize,
|
|
438
|
-
};
|
|
439
|
-
const page = await request<DataPage<Record<string, unknown>>>(`${base}/query`, {
|
|
440
|
-
method: 'POST',
|
|
441
|
-
body: JSON.stringify({ ...body, environmentKey: currentEnvironmentKey() }),
|
|
442
|
-
});
|
|
187
|
+
const body: DataQuery = { schemaVersion: SCHEMA_VERSIONS.dataQuery, filters: filtersFor(query), order: [{ field: assertField(sort.field), direction: sort.order || 'asc' }], limit: query.pageSize, offset: (query.page - 1) * query.pageSize };
|
|
188
|
+
const page = await request<DataPage<Record<string, unknown>>>(`${base}/query`, { method: 'POST', body: JSON.stringify({ ...body, environmentKey: currentEnvironmentKey() }) });
|
|
443
189
|
return { rows: page.items, total: page.total };
|
|
444
190
|
},
|
|
445
191
|
async get(id: string) {
|
|
@@ -447,7 +193,7 @@ export function createNativeResourceClient(
|
|
|
447
193
|
return record.data;
|
|
448
194
|
},
|
|
449
195
|
async audit(id: string) {
|
|
450
|
-
return
|
|
196
|
+
return request<DataAuditPage>(`${base}/records/${encodeURIComponent(id)}/audit?limit=10&offset=0&environmentKey=${encodeURIComponent(currentEnvironmentKey())}`);
|
|
451
197
|
},
|
|
452
198
|
async create(data: Record<string, unknown>) {
|
|
453
199
|
const record = await request<DataRecord<Record<string, unknown>>>(`${base}/records`, { method: 'POST', body: JSON.stringify({ environmentKey: currentEnvironmentKey(), data }) });
|
|
@@ -466,120 +212,30 @@ export function createNativeResourceClient(
|
|
|
466
212
|
const plan = await request<DataFileUploadPlan>(`${base}/files/uploads/initiate`, { method: 'POST', body: JSON.stringify({ environmentKey: currentEnvironmentKey(), fieldCode, fileName: file.name, fileSize: file.size, contentType: file.type || 'application/octet-stream', ...(recordId ? { recordId } : {}) }) });
|
|
467
213
|
const uploaded = await fetch(plan.uploadUrl, { method: plan.uploadMethod, headers: plan.headers, body: file });
|
|
468
214
|
if (!uploaded.ok) throw new Error(`FILE_UPLOAD_FAILED: HTTP_${uploaded.status}`);
|
|
469
|
-
return
|
|
215
|
+
return request<DataFileRef>(`${base}/files/${encodeURIComponent(plan.file.id)}/complete`, { method: 'POST', body: JSON.stringify({ environmentKey: currentEnvironmentKey() }) });
|
|
470
216
|
},
|
|
471
217
|
};
|
|
472
218
|
}
|
|
473
219
|
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
fileId: string,
|
|
477
|
-
disposition: 'attachment' | 'inline' = 'attachment'
|
|
478
|
-
) {
|
|
479
|
-
const query = new URLSearchParams({
|
|
480
|
-
environmentKey: currentEnvironmentKey(),
|
|
481
|
-
disposition,
|
|
482
|
-
});
|
|
483
|
-
return `${dataBase(resource)}/files/${encodeURIComponent(
|
|
484
|
-
fileId
|
|
485
|
-
)}/content?${query.toString()}`;
|
|
220
|
+
function nativeBase() {
|
|
221
|
+
return `/service/openxiangda-api/v2/applications/${applicationCode()}/native`;
|
|
486
222
|
}
|
|
487
223
|
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
fileId: string
|
|
491
|
-
) {
|
|
492
|
-
return await request<DataFilePreview>(
|
|
493
|
-
`${dataBase(resource)}/files/${encodeURIComponent(
|
|
494
|
-
fileId
|
|
495
|
-
)}/preview?environmentKey=${encodeURIComponent(currentEnvironmentKey())}`
|
|
496
|
-
);
|
|
224
|
+
function dataBase(code: string) {
|
|
225
|
+
return `${nativeBase()}/data/${encodeURIComponent(code)}`;
|
|
497
226
|
}
|
|
498
227
|
|
|
499
|
-
export
|
|
500
|
-
const
|
|
501
|
-
|
|
502
|
-
headers: { accept: 'application/octet-stream,*/*' },
|
|
503
|
-
});
|
|
504
|
-
if (!response.ok) {
|
|
505
|
-
const payload = (await response.json().catch(() => null)) as
|
|
506
|
-
| PlatformEnvelope<null>
|
|
507
|
-
| null;
|
|
508
|
-
const code = payload?.errorCode || `HTTP_${response.status}`;
|
|
509
|
-
throw new Error(`${code}: ${payload?.message || '文件内容读取失败'}`);
|
|
510
|
-
}
|
|
511
|
-
return await response.blob();
|
|
228
|
+
export function dataFileContentUrl(resource: string, fileId: string, disposition: 'attachment' | 'inline' = 'attachment') {
|
|
229
|
+
const query = new URLSearchParams({ environmentKey: currentEnvironmentKey(), disposition });
|
|
230
|
+
return `${dataBase(resource)}/files/${encodeURIComponent(fileId)}/content?${query}`;
|
|
512
231
|
}
|
|
513
232
|
|
|
514
|
-
export
|
|
515
|
-
|
|
516
|
-
revision: number;
|
|
517
|
-
name: string;
|
|
518
|
-
enabled: boolean;
|
|
233
|
+
export async function loadDataFilePreview(resource: string, fileId: string) {
|
|
234
|
+
return request<DataFilePreview>(`${dataBase(resource)}/files/${encodeURIComponent(fileId)}/preview?environmentKey=${encodeURIComponent(currentEnvironmentKey())}`);
|
|
519
235
|
}
|
|
520
236
|
|
|
521
|
-
export
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
order: [{ field: 'name', direction: 'asc' }],
|
|
526
|
-
limit: pageSize,
|
|
527
|
-
offset: (page - 1) * pageSize,
|
|
528
|
-
};
|
|
529
|
-
const result = await request<DataPage<CollegeRecord>>(
|
|
530
|
-
`${dataBase('colleges')}/query`,
|
|
531
|
-
{
|
|
532
|
-
method: 'POST',
|
|
533
|
-
body: JSON.stringify({
|
|
534
|
-
...body,
|
|
535
|
-
environmentKey: currentEnvironmentKey(),
|
|
536
|
-
}),
|
|
537
|
-
}
|
|
538
|
-
);
|
|
539
|
-
return { rows: result.items, total: result.total };
|
|
540
|
-
},
|
|
541
|
-
async create(data: Pick<CollegeRecord, 'name' | 'enabled'>) {
|
|
542
|
-
const record = await request<DataRecord<CollegeRecord>>(
|
|
543
|
-
`${dataBase('colleges')}/records`,
|
|
544
|
-
{
|
|
545
|
-
method: 'POST',
|
|
546
|
-
body: JSON.stringify({
|
|
547
|
-
environmentKey: currentEnvironmentKey(),
|
|
548
|
-
data,
|
|
549
|
-
}),
|
|
550
|
-
}
|
|
551
|
-
);
|
|
552
|
-
return record.data;
|
|
553
|
-
},
|
|
554
|
-
async update(
|
|
555
|
-
id: string,
|
|
556
|
-
expectedRevision: number,
|
|
557
|
-
data: Pick<CollegeRecord, 'name' | 'enabled'>
|
|
558
|
-
) {
|
|
559
|
-
const record = await request<DataRecord<CollegeRecord>>(
|
|
560
|
-
`${dataBase('colleges')}/records/${encodeURIComponent(id)}/update`,
|
|
561
|
-
{
|
|
562
|
-
method: 'POST',
|
|
563
|
-
body: JSON.stringify({
|
|
564
|
-
environmentKey: currentEnvironmentKey(),
|
|
565
|
-
expectedRevision,
|
|
566
|
-
data,
|
|
567
|
-
}),
|
|
568
|
-
}
|
|
569
|
-
);
|
|
570
|
-
return record.data;
|
|
571
|
-
},
|
|
572
|
-
};
|
|
573
|
-
|
|
574
|
-
function assertInstrumentQueryFields(query: DataQuery) {
|
|
575
|
-
for (const field of [
|
|
576
|
-
...(query.filters || []).map(filter => filter.field),
|
|
577
|
-
...(query.order || []).map(order => order.field),
|
|
578
|
-
]) {
|
|
579
|
-
if (!isInstrumentResourceField(field)) {
|
|
580
|
-
throw new Error(
|
|
581
|
-
`OPENXIANGDA_INSTRUMENT_QUERY_FIELD_NOT_DECLARED:${field}`
|
|
582
|
-
);
|
|
583
|
-
}
|
|
584
|
-
}
|
|
237
|
+
export async function fetchDataFileBlob(resource: string, fileId: string) {
|
|
238
|
+
const response = await fetch(dataFileContentUrl(resource, fileId, 'inline'), { credentials: 'include', headers: { accept: 'application/octet-stream,*/*' } });
|
|
239
|
+
if (!response.ok) throw new Error(`FILE_CONTENT_READ_FAILED: HTTP_${response.status}`);
|
|
240
|
+
return response.blob();
|
|
585
241
|
}
|
|
@@ -31,7 +31,7 @@ export function runtimeMount() {
|
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
export function applicationCode() {
|
|
34
|
-
return runtimeMount()?.appCode || '
|
|
34
|
+
return runtimeMount()?.appCode || 'openxiangda-application';
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
export function resolveApplicationBasename(mount: RuntimeMount | null) {
|
|
@@ -391,7 +391,7 @@ body {
|
|
|
391
391
|
gap: 16px;
|
|
392
392
|
}
|
|
393
393
|
|
|
394
|
-
.oxa-
|
|
394
|
+
.oxa-resource-form {
|
|
395
395
|
display: grid;
|
|
396
396
|
gap: 16px;
|
|
397
397
|
}
|
|
@@ -522,7 +522,7 @@ body {
|
|
|
522
522
|
gap: 1px;
|
|
523
523
|
}
|
|
524
524
|
|
|
525
|
-
.oxa-resolved-
|
|
525
|
+
.oxa-resolved-resource .oxa-resolved-item {
|
|
526
526
|
border-color: #cfe1ff;
|
|
527
527
|
background: #eff6ff;
|
|
528
528
|
color: #155fc7;
|
|
@@ -1029,7 +1029,7 @@ body {
|
|
|
1029
1029
|
font-size: 22px;
|
|
1030
1030
|
}
|
|
1031
1031
|
|
|
1032
|
-
.oxa-
|
|
1032
|
+
.oxa-resource-name.ant-typography {
|
|
1033
1033
|
margin-bottom: 2px;
|
|
1034
1034
|
font-size: 18px;
|
|
1035
1035
|
}
|