openxiangda-cli 2.0.0-alpha.57 → 2.0.0-alpha.59
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/commands/create.d.ts.map +1 -1
- package/dist/commands/create.js +13 -5
- package/dist/commands/create.js.map +1 -1
- package/dist/create-workspace.d.ts +6 -1
- package/dist/create-workspace.d.ts.map +1 -1
- package/dist/create-workspace.js +12 -9
- package/dist/create-workspace.js.map +1 -1
- package/package.json +4 -4
- package/template/README.md +3 -1
- package/template/apps/server/package.json +1 -1
- package/template/apps/web/e2e/instruments.spec.ts +1197 -9
- package/template/apps/web/package.json +4 -2
- package/template/apps/web/scripts/check.mjs +2 -2
- package/template/apps/web/scripts/verify-build.mjs +14 -2
- package/template/apps/web/src/AuthoritativeSelector.tsx +371 -0
- package/template/apps/web/src/CollegePage.tsx +181 -0
- package/template/apps/web/src/FilePreviewPage.tsx +303 -0
- package/template/apps/web/src/InstrumentDetailPage.tsx +94 -20
- package/template/apps/web/src/InstrumentForm.tsx +347 -94
- package/template/apps/web/src/InstrumentFormPage.tsx +49 -9
- package/template/apps/web/src/InstrumentListPage.tsx +30 -35
- package/template/apps/web/src/Shell.tsx +229 -41
- package/template/apps/web/src/components/platform-fields/AttachmentFileList.tsx +226 -0
- package/template/apps/web/src/components/platform-fields/PlatformDirectoryPicker.tsx +698 -0
- package/template/apps/web/src/data-provider.ts +1 -1
- package/template/apps/web/src/fields.ts +17 -6
- package/template/apps/web/src/instrument.ts +1 -1
- package/template/apps/web/src/main.tsx +21 -1
- package/template/apps/web/src/platform-client.ts +248 -2
- package/template/apps/web/src/runtime-meta.ts +15 -0
- package/template/apps/web/src/runtime.tsx +4 -2
- package/template/apps/web/src/styles.css +1007 -28
- package/template/apps/web/test/contracts.test.ts +473 -10
- package/template/openxiangda.config.ts +24 -5
- package/template/package.json +3 -3
- package/template/packages/contracts/src/generated.ts +22 -0
- package/template/platform/data/colleges.ts +21 -0
- package/template/platform/data/instruments.ts +1 -1
- package/template/scripts/verify-template-budget.mjs +2 -2
|
@@ -8,7 +8,10 @@ export type FieldKind =
|
|
|
8
8
|
| 'multi'
|
|
9
9
|
| 'email'
|
|
10
10
|
| 'phone'
|
|
11
|
-
| 'file'
|
|
11
|
+
| 'file'
|
|
12
|
+
| 'scope'
|
|
13
|
+
| 'directory-user'
|
|
14
|
+
| 'directory-department';
|
|
12
15
|
|
|
13
16
|
export interface InstrumentField {
|
|
14
17
|
key: string;
|
|
@@ -58,11 +61,11 @@ export const instrumentFields: InstrumentField[] = [
|
|
|
58
61
|
{ key: 'openToOutside', label: '是否对外开放', kind: 'boolean', section: 'usage', required: true },
|
|
59
62
|
{ key: 'subjectAreas', label: '学科领域', kind: 'multi', section: 'usage', options: options([['材料', 'material'], ['生命科学', 'life'], ['化学', 'chemistry'], ['物理', 'physics']]) },
|
|
60
63
|
{ key: 'platformProperty', label: '平台属性', kind: 'select', section: 'usage', options: options([['共享平台设备', 'shared'], ['课题组设备', 'research']]) },
|
|
61
|
-
{ key: 'collegeId', label: '所属学院', kind: '
|
|
62
|
-
{ key: 'manageDepartmentId', label: '管理部门', kind: '
|
|
63
|
-
{ key: 'useDepartmentId', label: '使用部门', kind: '
|
|
64
|
-
{ key: 'instrumentAdminIds', label: '仪器管理员', kind: '
|
|
65
|
-
{ key: 'contactUserIds', label: '联系人', kind: '
|
|
64
|
+
{ key: 'collegeId', label: '所属学院', kind: 'scope', section: 'usage', required: true, createCapability: 'app:instrument-center:instrument:field:college-id:create', updateCapability: 'app:instrument-center:instrument:field:college-id:update' },
|
|
65
|
+
{ key: 'manageDepartmentId', label: '管理部门', kind: 'directory-department', section: 'management', required: true },
|
|
66
|
+
{ key: 'useDepartmentId', label: '使用部门', kind: 'directory-department', section: 'management', required: true },
|
|
67
|
+
{ key: 'instrumentAdminIds', label: '仪器管理员', kind: 'directory-user', section: 'management', required: true, multiple: true, createCapability: 'app:instrument-center:instrument:field:instrument-admin-ids:create', updateCapability: 'app:instrument-center:instrument:field:instrument-admin-ids:update' },
|
|
68
|
+
{ key: 'contactUserIds', label: '联系人', kind: 'directory-user', section: 'management', required: true, multiple: true },
|
|
66
69
|
{ key: 'contactPhone', label: '联系电话', kind: 'phone', section: 'management', required: true },
|
|
67
70
|
{ key: 'contactEmail', label: '联系邮箱', kind: 'email', section: 'management' },
|
|
68
71
|
{ key: 'locationId', label: '存放点', kind: 'select', section: 'materials', options: options([['A 区 101', 'A-101'], ['B 区 203', 'B-203']]) },
|
|
@@ -70,3 +73,11 @@ export const instrumentFields: InstrumentField[] = [
|
|
|
70
73
|
{ key: 'serviceContent', label: '服务内容与主要功能', kind: 'textarea', section: 'materials' },
|
|
71
74
|
{ key: 'attachments', label: '仪器资料', kind: 'file', section: 'materials', multiple: true, maxCount: 8 },
|
|
72
75
|
];
|
|
76
|
+
|
|
77
|
+
const instrumentResourceFieldCodes = new Set(
|
|
78
|
+
instrumentFields.filter(field => !field.system).map(field => field.key)
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
export function isInstrumentResourceField(field: string) {
|
|
82
|
+
return instrumentResourceFieldCodes.has(field);
|
|
83
|
+
}
|
|
@@ -5,6 +5,8 @@ import React from 'react';
|
|
|
5
5
|
import ReactDOM from 'react-dom/client';
|
|
6
6
|
import { BrowserRouter, Navigate, Route, Routes } from 'react-router-dom';
|
|
7
7
|
import { instrumentProvider } from './data-provider';
|
|
8
|
+
import { CollegePage } from './CollegePage';
|
|
9
|
+
import { FilePreviewPage } from './FilePreviewPage';
|
|
8
10
|
import { InstrumentDetailPage } from './InstrumentDetailPage';
|
|
9
11
|
import { InstrumentFormPage } from './InstrumentFormPage';
|
|
10
12
|
import { InstrumentListPage } from './InstrumentListPage';
|
|
@@ -16,7 +18,20 @@ ReactDOM.createRoot(document.getElementById('root')!).render(
|
|
|
16
18
|
<React.StrictMode>
|
|
17
19
|
<ConfigProvider
|
|
18
20
|
locale={zhCN}
|
|
19
|
-
theme={{
|
|
21
|
+
theme={{
|
|
22
|
+
token: {
|
|
23
|
+
borderRadius: 8,
|
|
24
|
+
colorBgLayout: '#f3f7fd',
|
|
25
|
+
colorPrimary: '#1677ff',
|
|
26
|
+
colorText: '#172033',
|
|
27
|
+
fontSize: 14,
|
|
28
|
+
},
|
|
29
|
+
components: {
|
|
30
|
+
Card: { headerFontSize: 16 },
|
|
31
|
+
Layout: { bodyBg: '#f3f7fd', headerBg: '#ffffff', siderBg: '#ffffff' },
|
|
32
|
+
Menu: { itemBorderRadius: 8, itemSelectedBg: '#eaf3ff' },
|
|
33
|
+
},
|
|
34
|
+
}}
|
|
20
35
|
>
|
|
21
36
|
<AntdApp>
|
|
22
37
|
<RuntimeBoundary>
|
|
@@ -36,6 +51,11 @@ ReactDOM.createRoot(document.getElementById('root')!).render(
|
|
|
36
51
|
<Routes>
|
|
37
52
|
<Route path="/" element={<Navigate replace to="/instruments" />} />
|
|
38
53
|
<Route path="/instruments" element={<InstrumentListPage />} />
|
|
54
|
+
<Route path="/colleges" element={<CollegePage />} />
|
|
55
|
+
<Route
|
|
56
|
+
path="/files/:resourceCode/:fileId/preview"
|
|
57
|
+
element={<FilePreviewPage />}
|
|
58
|
+
/>
|
|
39
59
|
<Route
|
|
40
60
|
path="/instruments/new"
|
|
41
61
|
element={<InstrumentFormPage mode="create" />}
|
|
@@ -1,18 +1,25 @@
|
|
|
1
1
|
import {
|
|
2
2
|
SCHEMA_VERSIONS,
|
|
3
|
+
type DataFilePreview,
|
|
3
4
|
type DataFileRef,
|
|
4
5
|
type DataFileUploadPlan,
|
|
6
|
+
type DataAuditPage,
|
|
5
7
|
type DataPage,
|
|
6
8
|
type DataQuery,
|
|
7
9
|
type DataRecord,
|
|
10
|
+
type DirectoryEntryPage,
|
|
11
|
+
type DirectoryResolveRequest,
|
|
12
|
+
type NativeScopeValuePage,
|
|
13
|
+
type NativeScopeValueResolveRequest,
|
|
8
14
|
} from 'openxiangda-contracts/browser';
|
|
9
15
|
import type { InstrumentListQuery, InstrumentRecord } from './instrument';
|
|
16
|
+
import { isInstrumentResourceField } from './fields';
|
|
10
17
|
import { applicationCode, runtimeMount } from './runtime-meta';
|
|
11
18
|
|
|
12
19
|
const resourceCode = 'instruments';
|
|
13
20
|
const nativeBase = () =>
|
|
14
21
|
`/service/openxiangda-api/v2/applications/${applicationCode()}/native`;
|
|
15
|
-
const dataBase = () => `${nativeBase()}/data/${
|
|
22
|
+
const dataBase = (code = resourceCode) => `${nativeBase()}/data/${code}`;
|
|
16
23
|
|
|
17
24
|
interface PlatformEnvelope<T> {
|
|
18
25
|
code: number;
|
|
@@ -135,9 +142,125 @@ function currentEnvironmentKey() {
|
|
|
135
142
|
return key;
|
|
136
143
|
}
|
|
137
144
|
|
|
145
|
+
export type DirectoryKind = 'user' | 'department';
|
|
146
|
+
|
|
147
|
+
export async function searchDirectory(
|
|
148
|
+
kind: DirectoryKind,
|
|
149
|
+
options: { keyword: string; cursor?: string }
|
|
150
|
+
) {
|
|
151
|
+
const params = new URLSearchParams({
|
|
152
|
+
environmentKey: currentEnvironmentKey(),
|
|
153
|
+
keyword: options.keyword.trim(),
|
|
154
|
+
limit: '20',
|
|
155
|
+
});
|
|
156
|
+
if (options.cursor) params.set('cursor', options.cursor);
|
|
157
|
+
return request<DirectoryEntryPage>(
|
|
158
|
+
`${nativeBase().replace(/\/native$/, '')}/directory/${kind}s?${params}`
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export async function browseDepartmentTree(options: {
|
|
163
|
+
parentId?: string;
|
|
164
|
+
cursor?: string;
|
|
165
|
+
}) {
|
|
166
|
+
const params = new URLSearchParams({
|
|
167
|
+
environmentKey: currentEnvironmentKey(),
|
|
168
|
+
limit: '100',
|
|
169
|
+
});
|
|
170
|
+
if (options.parentId) params.set('parentId', options.parentId);
|
|
171
|
+
if (options.cursor) params.set('offset', options.cursor);
|
|
172
|
+
return request<DirectoryEntryPage>(
|
|
173
|
+
`${nativeBase().replace(/\/native$/, '')}/directory/departments/tree?${params}`
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export async function browseDepartmentUsers(
|
|
178
|
+
departmentId: string,
|
|
179
|
+
page = 1
|
|
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
|
+
);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
export async function resolveDirectory(
|
|
194
|
+
kind: DirectoryKind,
|
|
195
|
+
ids: string[]
|
|
196
|
+
) {
|
|
197
|
+
const uniqueIds = [...new Set(ids)];
|
|
198
|
+
const body: DirectoryResolveRequest = {
|
|
199
|
+
schemaVersion: SCHEMA_VERSIONS.directoryResolveRequest,
|
|
200
|
+
kind,
|
|
201
|
+
ids: uniqueIds,
|
|
202
|
+
};
|
|
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
|
+
}
|
|
234
|
+
|
|
235
|
+
export async function resolveCollegeScope(
|
|
236
|
+
operation: 'create' | 'update',
|
|
237
|
+
values: string[]
|
|
238
|
+
) {
|
|
239
|
+
const uniqueValues = [...new Set(values)];
|
|
240
|
+
const body: NativeScopeValueResolveRequest = {
|
|
241
|
+
schemaVersion: SCHEMA_VERSIONS.nativeScopeValueResolveRequest,
|
|
242
|
+
dimensionCode: 'college',
|
|
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
|
+
);
|
|
258
|
+
}
|
|
259
|
+
|
|
138
260
|
export interface InstrumentDataApiAdapter {
|
|
139
261
|
list(query: InstrumentListQuery): Promise<{ rows: InstrumentRecord[]; total: number }>;
|
|
140
262
|
get(id: string): Promise<InstrumentRecord>;
|
|
263
|
+
audit(id: string): Promise<DataAuditPage>;
|
|
141
264
|
create(data: Partial<InstrumentRecord>): Promise<InstrumentRecord>;
|
|
142
265
|
update(id: string, expectedRevision: number, data: Partial<InstrumentRecord>): Promise<InstrumentRecord>;
|
|
143
266
|
remove(id: string, expectedRevision: number): Promise<InstrumentRecord>;
|
|
@@ -165,10 +288,11 @@ export const instrumentDataApi: InstrumentDataApiAdapter = {
|
|
|
165
288
|
const body: DataQuery = {
|
|
166
289
|
schemaVersion: SCHEMA_VERSIONS.dataQuery,
|
|
167
290
|
filters,
|
|
168
|
-
order: [{ field: String(query.sort?.field || '
|
|
291
|
+
order: [{ field: String(query.sort?.field || 'instrumentCode'), direction: query.sort?.order || 'asc' }],
|
|
169
292
|
limit: query.pageSize,
|
|
170
293
|
offset: (query.page - 1) * query.pageSize,
|
|
171
294
|
};
|
|
295
|
+
assertInstrumentQueryFields(body);
|
|
172
296
|
const page = await request<DataPage<InstrumentRecord>>(`${dataBase()}/query`, {
|
|
173
297
|
method: 'POST',
|
|
174
298
|
body: JSON.stringify({ ...body, environmentKey: currentEnvironmentKey() }),
|
|
@@ -183,6 +307,15 @@ export const instrumentDataApi: InstrumentDataApiAdapter = {
|
|
|
183
307
|
);
|
|
184
308
|
return record.data;
|
|
185
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
|
+
},
|
|
186
319
|
async create(data) {
|
|
187
320
|
const record = await request<DataRecord<InstrumentRecord>>(`${dataBase()}/records`, {
|
|
188
321
|
method: 'POST',
|
|
@@ -244,3 +377,116 @@ export const instrumentDataApi: InstrumentDataApiAdapter = {
|
|
|
244
377
|
);
|
|
245
378
|
},
|
|
246
379
|
};
|
|
380
|
+
|
|
381
|
+
export function dataFileContentUrl(
|
|
382
|
+
resource: string,
|
|
383
|
+
fileId: string,
|
|
384
|
+
disposition: 'attachment' | 'inline' = 'attachment'
|
|
385
|
+
) {
|
|
386
|
+
const query = new URLSearchParams({
|
|
387
|
+
environmentKey: currentEnvironmentKey(),
|
|
388
|
+
disposition,
|
|
389
|
+
});
|
|
390
|
+
return `${dataBase(resource)}/files/${encodeURIComponent(
|
|
391
|
+
fileId
|
|
392
|
+
)}/content?${query.toString()}`;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
export async function loadDataFilePreview(
|
|
396
|
+
resource: string,
|
|
397
|
+
fileId: string
|
|
398
|
+
) {
|
|
399
|
+
return await request<DataFilePreview>(
|
|
400
|
+
`${dataBase(resource)}/files/${encodeURIComponent(
|
|
401
|
+
fileId
|
|
402
|
+
)}/preview?environmentKey=${encodeURIComponent(currentEnvironmentKey())}`
|
|
403
|
+
);
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
export async function fetchDataFileBlob(resource: string, fileId: string) {
|
|
407
|
+
const response = await fetch(dataFileContentUrl(resource, fileId, 'inline'), {
|
|
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();
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
export interface CollegeRecord extends Record<string, unknown> {
|
|
422
|
+
id: string;
|
|
423
|
+
revision: number;
|
|
424
|
+
name: string;
|
|
425
|
+
enabled: boolean;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
export const collegeDataApi = {
|
|
429
|
+
async list(page = 1, pageSize = 20) {
|
|
430
|
+
const body: DataQuery = {
|
|
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
|
+
};
|
|
480
|
+
|
|
481
|
+
function assertInstrumentQueryFields(query: DataQuery) {
|
|
482
|
+
for (const field of [
|
|
483
|
+
...(query.filters || []).map(filter => filter.field),
|
|
484
|
+
...(query.order || []).map(order => order.field),
|
|
485
|
+
]) {
|
|
486
|
+
if (!isInstrumentResourceField(field)) {
|
|
487
|
+
throw new Error(
|
|
488
|
+
`OPENXIANGDA_INSTRUMENT_QUERY_FIELD_NOT_DECLARED:${field}`
|
|
489
|
+
);
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
}
|
|
@@ -45,6 +45,21 @@ export function applicationBasename() {
|
|
|
45
45
|
return resolveApplicationBasename(runtimeMount());
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
+
export function attachmentPreviewPath(resourceCode: string, fileId: string) {
|
|
49
|
+
return resolveAttachmentPreviewPath(runtimeMount(), resourceCode, fileId);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function resolveAttachmentPreviewPath(
|
|
53
|
+
mount: RuntimeMount | null,
|
|
54
|
+
resourceCode: string,
|
|
55
|
+
fileId: string
|
|
56
|
+
) {
|
|
57
|
+
const base = resolveApplicationBasename(mount)?.replace(/\/+$/, '') || '';
|
|
58
|
+
return `${base}/files/${encodeURIComponent(resourceCode)}/${encodeURIComponent(
|
|
59
|
+
fileId
|
|
60
|
+
)}/preview`;
|
|
61
|
+
}
|
|
62
|
+
|
|
48
63
|
export function resolveApplicationApiPath(
|
|
49
64
|
mount: RuntimeMount | null,
|
|
50
65
|
path: string
|
|
@@ -47,7 +47,7 @@ export function RuntimeBoundary({ children }: { children: React.ReactNode }) {
|
|
|
47
47
|
showIcon
|
|
48
48
|
type="error"
|
|
49
49
|
data-testid="production-data-warning"
|
|
50
|
-
|
|
50
|
+
title="正在使用正式数据:所有新增、编辑和删除都会影响生产环境"
|
|
51
51
|
/>
|
|
52
52
|
)}
|
|
53
53
|
{error ? (
|
|
@@ -60,7 +60,9 @@ export function RuntimeBoundary({ children }: { children: React.ReactNode }) {
|
|
|
60
60
|
) : value ? (
|
|
61
61
|
<RuntimeContext.Provider value={value}>{children}</RuntimeContext.Provider>
|
|
62
62
|
) : (
|
|
63
|
-
<div className="oxa-loading"
|
|
63
|
+
<div className="oxa-loading">
|
|
64
|
+
<Spin description="正在读取平台身份与权限并集" />
|
|
65
|
+
</div>
|
|
64
66
|
)}
|
|
65
67
|
</>
|
|
66
68
|
);
|