openxiangda-cli 2.0.0-alpha.60 → 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 +1 -0
- 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/scripts/check.mjs +5 -2
- 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 +306 -0
- package/template/apps/web/src/components/resource/StandardResourcePages.tsx +431 -0
- package/template/apps/web/src/components/resource/SurfaceFields.tsx +500 -0
- package/template/apps/web/src/data-provider.ts +36 -31
- package/template/apps/web/src/main.tsx +24 -54
- package/template/apps/web/src/platform-client.ts +131 -382
- package/template/apps/web/src/runtime-meta.ts +1 -1
- package/template/apps/web/src/styles.css +100 -3
- package/template/apps/web/test/contracts.test.ts +31 -693
- package/template/openxiangda.config.ts +14 -81
- package/template/package.json +3 -3
- package/template/packages/contracts/src/generated.ts +7 -130
- 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 -134
- package/template/apps/web/src/InstrumentForm.tsx +0 -575
- package/template/apps/web/src/InstrumentFormPage.tsx +0 -117
- package/template/apps/web/src/InstrumentListPage.tsx +0 -385
- package/template/apps/web/src/fields.ts +0 -83
- package/template/apps/web/src/instrument.ts +0 -55
- package/template/platform/data/colleges.ts +0 -21
- package/template/platform/data/instruments.ts +0 -81
|
@@ -1,26 +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
13
|
} from 'openxiangda-contracts/browser';
|
|
15
|
-
import type { InstrumentListQuery, InstrumentRecord } from './instrument';
|
|
16
|
-
import { isInstrumentResourceField } from './fields';
|
|
17
14
|
import { applicationCode, runtimeMount } from './runtime-meta';
|
|
18
15
|
|
|
19
|
-
const resourceCode = 'instruments';
|
|
20
|
-
const nativeBase = () =>
|
|
21
|
-
`/service/openxiangda-api/v2/applications/${applicationCode()}/native`;
|
|
22
|
-
const dataBase = (code = resourceCode) => `${nativeBase()}/data/${code}`;
|
|
23
|
-
|
|
24
16
|
interface PlatformEnvelope<T> {
|
|
25
17
|
code: number;
|
|
26
18
|
message?: string;
|
|
@@ -38,19 +30,12 @@ async function request<T>(path: string, init?: RequestInit): Promise<T> {
|
|
|
38
30
|
...init?.headers,
|
|
39
31
|
},
|
|
40
32
|
});
|
|
41
|
-
const payload = (await response.json().catch(() => null)) as
|
|
42
|
-
|
|
43
|
-
| T
|
|
44
|
-
| null;
|
|
45
|
-
const envelope =
|
|
46
|
-
payload && typeof payload === 'object' && 'code' in payload
|
|
47
|
-
? (payload as PlatformEnvelope<T>)
|
|
48
|
-
: 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;
|
|
49
35
|
if (!response.ok || (envelope && envelope.code !== 200)) {
|
|
50
|
-
|
|
51
|
-
throw new Error(`${code}: ${envelope?.message || '平台请求失败'}`);
|
|
36
|
+
throw new Error(`${envelope?.errorCode || `HTTP_${response.status}`}: ${envelope?.message || '平台请求失败'}`);
|
|
52
37
|
}
|
|
53
|
-
return envelope ? envelope.data :
|
|
38
|
+
return envelope ? envelope.data : payload as T;
|
|
54
39
|
}
|
|
55
40
|
|
|
56
41
|
export interface RuntimeIdentity {
|
|
@@ -71,33 +56,14 @@ export interface RuntimeIdentity {
|
|
|
71
56
|
|
|
72
57
|
interface ConnectedCurrent {
|
|
73
58
|
environment: RuntimeIdentity['environment'];
|
|
74
|
-
principal:
|
|
75
|
-
userId: string;
|
|
76
|
-
roleCodes: string[];
|
|
77
|
-
capabilityCodes: string[];
|
|
78
|
-
isAppSuperAdmin: boolean;
|
|
79
|
-
};
|
|
59
|
+
principal: Pick<RuntimeIdentity, 'userId' | 'roleCodes' | 'capabilityCodes' | 'isAppSuperAdmin'>;
|
|
80
60
|
}
|
|
81
61
|
|
|
82
62
|
interface DeployedCurrent {
|
|
83
63
|
schemaVersion: 'openxiangda.current-user/v2';
|
|
84
64
|
appCode: string;
|
|
85
|
-
environment:
|
|
86
|
-
|
|
87
|
-
key: 'preproduction' | 'production';
|
|
88
|
-
activeAppVersionId: string;
|
|
89
|
-
headRevision: number;
|
|
90
|
-
authzRevisionId: string;
|
|
91
|
-
authzVersion: number;
|
|
92
|
-
scopeDataVersion: string;
|
|
93
|
-
};
|
|
94
|
-
principal: {
|
|
95
|
-
type: 'user';
|
|
96
|
-
userId: string;
|
|
97
|
-
roleCodes: string[];
|
|
98
|
-
capabilityCodes: string[];
|
|
99
|
-
isAppSuperAdmin: boolean;
|
|
100
|
-
};
|
|
65
|
+
environment: RuntimeIdentity['environment'];
|
|
66
|
+
principal: { type: 'user' } & Pick<RuntimeIdentity, 'userId' | 'roleCodes' | 'capabilityCodes' | 'isAppSuperAdmin'>;
|
|
101
67
|
}
|
|
102
68
|
|
|
103
69
|
let activeIdentity: RuntimeIdentity | undefined;
|
|
@@ -105,35 +71,16 @@ let activeIdentity: RuntimeIdentity | undefined;
|
|
|
105
71
|
export async function loadCurrentIdentity(): Promise<RuntimeIdentity> {
|
|
106
72
|
const mount = runtimeMount();
|
|
107
73
|
if (mount) {
|
|
108
|
-
const current = await request<DeployedCurrent>(
|
|
109
|
-
|
|
110
|
-
mount.environmentKey
|
|
111
|
-
)}`
|
|
112
|
-
);
|
|
113
|
-
if (
|
|
114
|
-
current.schemaVersion !== 'openxiangda.current-user/v2' ||
|
|
115
|
-
current.appCode !== mount.appCode ||
|
|
116
|
-
current.environment.key !== mount.environmentKey ||
|
|
117
|
-
current.principal.type !== 'user' ||
|
|
118
|
-
!Array.isArray(current.principal.roleCodes) ||
|
|
119
|
-
!Array.isArray(current.principal.capabilityCodes)
|
|
120
|
-
) {
|
|
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') {
|
|
121
76
|
throw new Error('OPENXIANGDA_DEPLOYED_CURRENT_USER_INVALID');
|
|
122
77
|
}
|
|
123
78
|
activeIdentity = { ...current.principal, environment: current.environment };
|
|
124
79
|
return activeIdentity;
|
|
125
80
|
}
|
|
126
|
-
const
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
const current = await request<ConnectedCurrent>(connectedPath);
|
|
130
|
-
activeIdentity = { ...current.principal, environment: current.environment };
|
|
131
|
-
return activeIdentity;
|
|
132
|
-
} catch (error) {
|
|
133
|
-
throw new Error(
|
|
134
|
-
`OPENXIANGDA_CONNECTED_CURRENT_USER_FAILED: ${error instanceof Error ? error.message : String(error)}`
|
|
135
|
-
);
|
|
136
|
-
}
|
|
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;
|
|
137
84
|
}
|
|
138
85
|
|
|
139
86
|
function currentEnvironmentKey() {
|
|
@@ -144,349 +91,151 @@ function currentEnvironmentKey() {
|
|
|
144
91
|
|
|
145
92
|
export type DirectoryKind = 'user' | 'department';
|
|
146
93
|
|
|
147
|
-
export async function searchDirectory(
|
|
148
|
-
|
|
149
|
-
options: { keyword: string; cursor?: string }
|
|
150
|
-
) {
|
|
151
|
-
const params = new URLSearchParams({
|
|
152
|
-
environmentKey: currentEnvironmentKey(),
|
|
153
|
-
keyword: options.keyword.trim(),
|
|
154
|
-
limit: '20',
|
|
155
|
-
});
|
|
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' });
|
|
156
96
|
if (options.cursor) params.set('cursor', options.cursor);
|
|
157
|
-
return request<DirectoryEntryPage>(
|
|
158
|
-
`${nativeBase().replace(/\/native$/, '')}/directory/${kind}s?${params}`
|
|
159
|
-
);
|
|
97
|
+
return request<DirectoryEntryPage>(`${nativeBase().replace(/\/native$/, '')}/directory/${kind}s?${params}`);
|
|
160
98
|
}
|
|
161
99
|
|
|
162
|
-
export async function browseDepartmentTree(options: {
|
|
163
|
-
|
|
164
|
-
cursor?: string;
|
|
165
|
-
}) {
|
|
166
|
-
const params = new URLSearchParams({
|
|
167
|
-
environmentKey: currentEnvironmentKey(),
|
|
168
|
-
limit: '100',
|
|
169
|
-
});
|
|
100
|
+
export async function browseDepartmentTree(options: { parentId?: string; cursor?: string }) {
|
|
101
|
+
const params = new URLSearchParams({ environmentKey: currentEnvironmentKey(), limit: '100' });
|
|
170
102
|
if (options.parentId) params.set('parentId', options.parentId);
|
|
171
103
|
if (options.cursor) params.set('offset', options.cursor);
|
|
172
|
-
return request<DirectoryEntryPage>(
|
|
173
|
-
`${nativeBase().replace(/\/native$/, '')}/directory/departments/tree?${params}`
|
|
174
|
-
);
|
|
104
|
+
return request<DirectoryEntryPage>(`${nativeBase().replace(/\/native$/, '')}/directory/departments/tree?${params}`);
|
|
175
105
|
}
|
|
176
106
|
|
|
177
|
-
export async function browseDepartmentUsers(
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
) {
|
|
181
|
-
const params = new URLSearchParams({
|
|
182
|
-
environmentKey: currentEnvironmentKey(),
|
|
183
|
-
page: String(page),
|
|
184
|
-
limit: '20',
|
|
185
|
-
});
|
|
186
|
-
return request<DirectoryEntryPage>(
|
|
187
|
-
`${nativeBase().replace(/\/native$/, '')}/directory/departments/${encodeURIComponent(
|
|
188
|
-
departmentId
|
|
189
|
-
)}/users?${params}`
|
|
190
|
-
);
|
|
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}`);
|
|
191
110
|
}
|
|
192
111
|
|
|
193
|
-
export async function resolveDirectory(
|
|
194
|
-
kind: DirectoryKind,
|
|
195
|
-
ids: string[]
|
|
196
|
-
) {
|
|
112
|
+
export async function resolveDirectory(kind: DirectoryKind, ids: string[]) {
|
|
197
113
|
const uniqueIds = [...new Set(ids)];
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
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,
|
|
129
|
+
};
|
|
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,
|
|
202
134
|
};
|
|
203
|
-
if (body.ids.length === 0) {
|
|
204
|
-
throw new Error('OPENXIANGDA_DIRECTORY_RESOLVE_IDS_REQUIRED');
|
|
205
|
-
}
|
|
206
|
-
if (body.ids.length > 50) {
|
|
207
|
-
throw new Error('OPENXIANGDA_DIRECTORY_RESOLVE_IDS_EXCEEDED');
|
|
208
|
-
}
|
|
209
|
-
return request<DirectoryEntryPage>(
|
|
210
|
-
`${nativeBase().replace(/\/native$/, '')}/directory/resolve?environmentKey=${encodeURIComponent(
|
|
211
|
-
currentEnvironmentKey()
|
|
212
|
-
)}`,
|
|
213
|
-
{ method: 'POST', body: JSON.stringify(body) }
|
|
214
|
-
);
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
const scopeValuesBase = () =>
|
|
218
|
-
`${nativeBase()}/data-resources/${resourceCode}/fields/collegeId/scope-values`;
|
|
219
|
-
|
|
220
|
-
export async function searchCollegeScope(
|
|
221
|
-
operation: 'create' | 'update',
|
|
222
|
-
options: { keyword: string; cursor?: string }
|
|
223
|
-
) {
|
|
224
|
-
const params = new URLSearchParams({
|
|
225
|
-
environmentKey: currentEnvironmentKey(),
|
|
226
|
-
dimensionCode: 'college',
|
|
227
|
-
operation,
|
|
228
|
-
keyword: options.keyword.trim(),
|
|
229
|
-
limit: '20',
|
|
230
|
-
});
|
|
231
|
-
if (options.cursor) params.set('cursor', options.cursor);
|
|
232
|
-
return request<NativeScopeValuePage>(`${scopeValuesBase()}?${params}`);
|
|
233
135
|
}
|
|
234
136
|
|
|
235
|
-
export async function
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
operation,
|
|
244
|
-
values: uniqueValues,
|
|
245
|
-
};
|
|
246
|
-
if (body.values.length === 0) {
|
|
247
|
-
throw new Error('OPENXIANGDA_SCOPE_RESOLVE_VALUES_REQUIRED');
|
|
248
|
-
}
|
|
249
|
-
if (body.values.length > 50) {
|
|
250
|
-
throw new Error('OPENXIANGDA_SCOPE_RESOLVE_VALUES_EXCEEDED');
|
|
251
|
-
}
|
|
252
|
-
return request<NativeScopeValuePage>(
|
|
253
|
-
`${scopeValuesBase()}/resolve?environmentKey=${encodeURIComponent(
|
|
254
|
-
currentEnvironmentKey()
|
|
255
|
-
)}`,
|
|
256
|
-
{ method: 'POST', body: JSON.stringify(body) }
|
|
257
|
-
);
|
|
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 };
|
|
258
145
|
}
|
|
259
146
|
|
|
260
|
-
export interface
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
upload(fieldCode: 'image' | 'attachments', file: File, recordId?: string): Promise<DataFileRef>;
|
|
147
|
+
export interface GenericResourceQuery {
|
|
148
|
+
page: number;
|
|
149
|
+
pageSize: number;
|
|
150
|
+
keyword?: string;
|
|
151
|
+
searchField?: string;
|
|
152
|
+
filters?: Record<string, unknown>;
|
|
153
|
+
sort?: { field: string; order: 'asc' | 'desc' };
|
|
268
154
|
}
|
|
269
155
|
|
|
270
|
-
export
|
|
271
|
-
|
|
156
|
+
export function createNativeResourceClient(code: string, surface: DataResourceSurface) {
|
|
157
|
+
const base = dataBase(code);
|
|
158
|
+
const declaredFields = new Set(Object.keys(surface.fields));
|
|
159
|
+
const assertField = (field: string) => {
|
|
160
|
+
if (!declaredFields.has(field)) throw new Error(`OPENXIANGDA_RESOURCE_QUERY_FIELD_NOT_DECLARED:${code}:${field}`);
|
|
161
|
+
return field;
|
|
162
|
+
};
|
|
163
|
+
const filtersFor = (query: GenericResourceQuery) => {
|
|
272
164
|
const filters: NonNullable<DataQuery['filters']> = [];
|
|
273
|
-
// The current DataQuery contract has AND filters but no grouped OR. Keep
|
|
274
|
-
// this honest single-field search until the platform adds a native OR AST.
|
|
275
165
|
if (query.keyword?.trim()) {
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
if (query.instrumentAdminId) filters.push({ field: 'instrumentAdminIds', operator: 'cs', value: [query.instrumentAdminId] });
|
|
280
|
-
if (query.instrumentStatus) filters.push({ field: 'instrumentStatus', operator: 'eq', value: query.instrumentStatus });
|
|
281
|
-
if (query.openToOutside !== undefined) filters.push({ field: 'openToOutside', operator: 'eq', value: query.openToOutside });
|
|
282
|
-
if (query.enabledDate) {
|
|
283
|
-
filters.push({ field: 'enabledDate', operator: 'gte', value: query.enabledDate[0] });
|
|
284
|
-
filters.push({ field: 'enabledDate', operator: 'lte', value: query.enabledDate[1] });
|
|
166
|
+
const searchable = surface.list?.searchableFields || [];
|
|
167
|
+
const field = searchable.includes(query.searchField || '') ? query.searchField! : searchable[0];
|
|
168
|
+
if (field) filters.push({ field: assertField(field), operator: 'ilike', value: `%${query.keyword.trim()}%` });
|
|
285
169
|
}
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
const page = await request<DataPage<InstrumentRecord>>(`${dataBase()}/query`, {
|
|
297
|
-
method: 'POST',
|
|
298
|
-
body: JSON.stringify({ ...body, environmentKey: currentEnvironmentKey() }),
|
|
299
|
-
});
|
|
300
|
-
return { rows: page.items, total: page.total };
|
|
301
|
-
},
|
|
302
|
-
async get(id) {
|
|
303
|
-
const record = await request<DataRecord<InstrumentRecord>>(
|
|
304
|
-
`${dataBase()}/records/${encodeURIComponent(
|
|
305
|
-
id
|
|
306
|
-
)}?environmentKey=${encodeURIComponent(currentEnvironmentKey())}`
|
|
307
|
-
);
|
|
308
|
-
return record.data;
|
|
309
|
-
},
|
|
310
|
-
async audit(id) {
|
|
311
|
-
return await request<DataAuditPage>(
|
|
312
|
-
`${dataBase()}/records/${encodeURIComponent(
|
|
313
|
-
id
|
|
314
|
-
)}/audit?limit=10&offset=0&environmentKey=${encodeURIComponent(
|
|
315
|
-
currentEnvironmentKey()
|
|
316
|
-
)}`
|
|
317
|
-
);
|
|
318
|
-
},
|
|
319
|
-
async create(data) {
|
|
320
|
-
const record = await request<DataRecord<InstrumentRecord>>(`${dataBase()}/records`, {
|
|
321
|
-
method: 'POST',
|
|
322
|
-
body: JSON.stringify({ environmentKey: currentEnvironmentKey(), data }),
|
|
323
|
-
});
|
|
324
|
-
return record.data;
|
|
325
|
-
},
|
|
326
|
-
async update(id, expectedRevision, data) {
|
|
327
|
-
const record = await request<DataRecord<InstrumentRecord>>(
|
|
328
|
-
`${dataBase()}/records/${encodeURIComponent(id)}/update`,
|
|
329
|
-
{
|
|
330
|
-
method: 'POST',
|
|
331
|
-
body: JSON.stringify({
|
|
332
|
-
environmentKey: currentEnvironmentKey(),
|
|
333
|
-
expectedRevision,
|
|
334
|
-
data,
|
|
335
|
-
}),
|
|
336
|
-
}
|
|
337
|
-
);
|
|
338
|
-
return record.data;
|
|
339
|
-
},
|
|
340
|
-
async remove(id, expectedRevision) {
|
|
341
|
-
const record = await request<DataRecord<InstrumentRecord>>(
|
|
342
|
-
`${dataBase()}/records/${encodeURIComponent(id)}/delete`,
|
|
343
|
-
{
|
|
344
|
-
method: 'POST',
|
|
345
|
-
body: JSON.stringify({
|
|
346
|
-
environmentKey: currentEnvironmentKey(),
|
|
347
|
-
expectedRevision,
|
|
348
|
-
}),
|
|
349
|
-
}
|
|
350
|
-
);
|
|
351
|
-
return record.data;
|
|
352
|
-
},
|
|
353
|
-
async upload(fieldCode, file, recordId) {
|
|
354
|
-
const plan = await request<DataFileUploadPlan>(`${dataBase()}/files/uploads/initiate`, {
|
|
355
|
-
method: 'POST',
|
|
356
|
-
body: JSON.stringify({
|
|
357
|
-
environmentKey: currentEnvironmentKey(),
|
|
358
|
-
fieldCode,
|
|
359
|
-
fileName: file.name,
|
|
360
|
-
fileSize: file.size,
|
|
361
|
-
contentType: file.type || 'application/octet-stream',
|
|
362
|
-
...(recordId ? { recordId } : {}),
|
|
363
|
-
}),
|
|
364
|
-
});
|
|
365
|
-
const uploaded = await fetch(plan.uploadUrl, {
|
|
366
|
-
method: plan.uploadMethod,
|
|
367
|
-
headers: plan.headers,
|
|
368
|
-
body: file,
|
|
369
|
-
});
|
|
370
|
-
if (!uploaded.ok) throw new Error(`FILE_UPLOAD_FAILED: HTTP_${uploaded.status}`);
|
|
371
|
-
return await request<DataFileRef>(
|
|
372
|
-
`${dataBase()}/files/${encodeURIComponent(plan.file.id)}/complete`,
|
|
373
|
-
{
|
|
374
|
-
method: 'POST',
|
|
375
|
-
body: JSON.stringify({ environmentKey: currentEnvironmentKey() }),
|
|
170
|
+
for (const [fieldCode, value] of Object.entries(query.filters || {})) {
|
|
171
|
+
if (value === undefined || value === null || value === '') continue;
|
|
172
|
+
const field = surface.fields[assertField(fieldCode)];
|
|
173
|
+
if (Array.isArray(value) && value.length === 2 && ['date', 'datetime', 'number', 'money', 'percent'].includes(field?.widget || '')) {
|
|
174
|
+
if (value[0] !== undefined && value[0] !== null && value[0] !== '') filters.push({ field: fieldCode, operator: 'gte', value: value[0] });
|
|
175
|
+
if (value[1] !== undefined && value[1] !== null && value[1] !== '') filters.push({ field: fieldCode, operator: 'lte', value: value[1] });
|
|
176
|
+
} else if (Array.isArray(value) && ['multi', 'directory-user', 'directory-department', 'resource'].includes(field?.widget || '')) {
|
|
177
|
+
filters.push({ field: fieldCode, operator: 'cs', value });
|
|
178
|
+
} else {
|
|
179
|
+
filters.push({ field: fieldCode, operator: 'eq', value });
|
|
376
180
|
}
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
};
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
181
|
+
}
|
|
182
|
+
return filters;
|
|
183
|
+
};
|
|
184
|
+
return {
|
|
185
|
+
async list(query: GenericResourceQuery) {
|
|
186
|
+
const sort = query.sort || surface.list?.defaultSort || { field: Object.keys(surface.fields)[0] || 'id', order: 'asc' as const };
|
|
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() }) });
|
|
189
|
+
return { rows: page.items, total: page.total };
|
|
190
|
+
},
|
|
191
|
+
async get(id: string) {
|
|
192
|
+
const record = await request<DataRecord<Record<string, unknown>>>(`${base}/records/${encodeURIComponent(id)}?environmentKey=${encodeURIComponent(currentEnvironmentKey())}`);
|
|
193
|
+
return record.data;
|
|
194
|
+
},
|
|
195
|
+
async audit(id: string) {
|
|
196
|
+
return request<DataAuditPage>(`${base}/records/${encodeURIComponent(id)}/audit?limit=10&offset=0&environmentKey=${encodeURIComponent(currentEnvironmentKey())}`);
|
|
197
|
+
},
|
|
198
|
+
async create(data: Record<string, unknown>) {
|
|
199
|
+
const record = await request<DataRecord<Record<string, unknown>>>(`${base}/records`, { method: 'POST', body: JSON.stringify({ environmentKey: currentEnvironmentKey(), data }) });
|
|
200
|
+
return record.data;
|
|
201
|
+
},
|
|
202
|
+
async update(id: string, expectedRevision: number, data: Record<string, unknown>) {
|
|
203
|
+
const record = await request<DataRecord<Record<string, unknown>>>(`${base}/records/${encodeURIComponent(id)}/update`, { method: 'POST', body: JSON.stringify({ environmentKey: currentEnvironmentKey(), expectedRevision, data }) });
|
|
204
|
+
return record.data;
|
|
205
|
+
},
|
|
206
|
+
async remove(id: string, expectedRevision: number) {
|
|
207
|
+
const record = await request<DataRecord<Record<string, unknown>>>(`${base}/records/${encodeURIComponent(id)}/delete`, { method: 'POST', body: JSON.stringify({ environmentKey: currentEnvironmentKey(), expectedRevision }) });
|
|
208
|
+
return record.data;
|
|
209
|
+
},
|
|
210
|
+
async upload(fieldCode: string, file: File, recordId?: string) {
|
|
211
|
+
assertField(fieldCode);
|
|
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 } : {}) }) });
|
|
213
|
+
const uploaded = await fetch(plan.uploadUrl, { method: plan.uploadMethod, headers: plan.headers, body: file });
|
|
214
|
+
if (!uploaded.ok) throw new Error(`FILE_UPLOAD_FAILED: HTTP_${uploaded.status}`);
|
|
215
|
+
return request<DataFileRef>(`${base}/files/${encodeURIComponent(plan.file.id)}/complete`, { method: 'POST', body: JSON.stringify({ environmentKey: currentEnvironmentKey() }) });
|
|
216
|
+
},
|
|
217
|
+
};
|
|
393
218
|
}
|
|
394
219
|
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
fileId: string
|
|
398
|
-
) {
|
|
399
|
-
return await request<DataFilePreview>(
|
|
400
|
-
`${dataBase(resource)}/files/${encodeURIComponent(
|
|
401
|
-
fileId
|
|
402
|
-
)}/preview?environmentKey=${encodeURIComponent(currentEnvironmentKey())}`
|
|
403
|
-
);
|
|
220
|
+
function nativeBase() {
|
|
221
|
+
return `/service/openxiangda-api/v2/applications/${applicationCode()}/native`;
|
|
404
222
|
}
|
|
405
223
|
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
credentials: 'include',
|
|
409
|
-
headers: { accept: 'application/octet-stream,*/*' },
|
|
410
|
-
});
|
|
411
|
-
if (!response.ok) {
|
|
412
|
-
const payload = (await response.json().catch(() => null)) as
|
|
413
|
-
| PlatformEnvelope<null>
|
|
414
|
-
| null;
|
|
415
|
-
const code = payload?.errorCode || `HTTP_${response.status}`;
|
|
416
|
-
throw new Error(`${code}: ${payload?.message || '文件内容读取失败'}`);
|
|
417
|
-
}
|
|
418
|
-
return await response.blob();
|
|
224
|
+
function dataBase(code: string) {
|
|
225
|
+
return `${nativeBase()}/data/${encodeURIComponent(code)}`;
|
|
419
226
|
}
|
|
420
227
|
|
|
421
|
-
export
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
name: string;
|
|
425
|
-
enabled: boolean;
|
|
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}`;
|
|
426
231
|
}
|
|
427
232
|
|
|
428
|
-
export
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
schemaVersion: SCHEMA_VERSIONS.dataQuery,
|
|
432
|
-
order: [{ field: 'name', direction: 'asc' }],
|
|
433
|
-
limit: pageSize,
|
|
434
|
-
offset: (page - 1) * pageSize,
|
|
435
|
-
};
|
|
436
|
-
const result = await request<DataPage<CollegeRecord>>(
|
|
437
|
-
`${dataBase('colleges')}/query`,
|
|
438
|
-
{
|
|
439
|
-
method: 'POST',
|
|
440
|
-
body: JSON.stringify({
|
|
441
|
-
...body,
|
|
442
|
-
environmentKey: currentEnvironmentKey(),
|
|
443
|
-
}),
|
|
444
|
-
}
|
|
445
|
-
);
|
|
446
|
-
return { rows: result.items, total: result.total };
|
|
447
|
-
},
|
|
448
|
-
async create(data: Pick<CollegeRecord, 'name' | 'enabled'>) {
|
|
449
|
-
const record = await request<DataRecord<CollegeRecord>>(
|
|
450
|
-
`${dataBase('colleges')}/records`,
|
|
451
|
-
{
|
|
452
|
-
method: 'POST',
|
|
453
|
-
body: JSON.stringify({
|
|
454
|
-
environmentKey: currentEnvironmentKey(),
|
|
455
|
-
data,
|
|
456
|
-
}),
|
|
457
|
-
}
|
|
458
|
-
);
|
|
459
|
-
return record.data;
|
|
460
|
-
},
|
|
461
|
-
async update(
|
|
462
|
-
id: string,
|
|
463
|
-
expectedRevision: number,
|
|
464
|
-
data: Pick<CollegeRecord, 'name' | 'enabled'>
|
|
465
|
-
) {
|
|
466
|
-
const record = await request<DataRecord<CollegeRecord>>(
|
|
467
|
-
`${dataBase('colleges')}/records/${encodeURIComponent(id)}/update`,
|
|
468
|
-
{
|
|
469
|
-
method: 'POST',
|
|
470
|
-
body: JSON.stringify({
|
|
471
|
-
environmentKey: currentEnvironmentKey(),
|
|
472
|
-
expectedRevision,
|
|
473
|
-
data,
|
|
474
|
-
}),
|
|
475
|
-
}
|
|
476
|
-
);
|
|
477
|
-
return record.data;
|
|
478
|
-
},
|
|
479
|
-
};
|
|
233
|
+
export async function loadDataFilePreview(resource: string, fileId: string) {
|
|
234
|
+
return request<DataFilePreview>(`${dataBase(resource)}/files/${encodeURIComponent(fileId)}/preview?environmentKey=${encodeURIComponent(currentEnvironmentKey())}`);
|
|
235
|
+
}
|
|
480
236
|
|
|
481
|
-
function
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
]) {
|
|
486
|
-
if (!isInstrumentResourceField(field)) {
|
|
487
|
-
throw new Error(
|
|
488
|
-
`OPENXIANGDA_INSTRUMENT_QUERY_FIELD_NOT_DECLARED:${field}`
|
|
489
|
-
);
|
|
490
|
-
}
|
|
491
|
-
}
|
|
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();
|
|
492
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) {
|