openxiangda-cli 2.0.0-alpha.67 → 2.0.0-alpha.69

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.
@@ -1,11 +1,10 @@
1
1
  import { Refine } from '@refinedev/core';
2
- import { App as AntdApp, ConfigProvider, Spin } from 'antd';
3
- import zhCN from 'antd/locale/zh_CN';
4
2
  import React from 'react';
5
3
  import ReactDOM from 'react-dom/client';
6
4
  import { BrowserRouter, Navigate, Route, Routes } from 'react-router-dom';
7
5
  import { resourceDefinitions } from '../../../packages/contracts/src/generated.js';
8
6
  import { applicationProvider } from './data-provider';
7
+ import { AppearanceProvider } from './appearance';
9
8
  import { GeneratedResourcePage } from './components/resource/GeneratedResourceCrud';
10
9
  import { FilePreviewPage } from './FilePreviewPage';
11
10
  import { EmptyApplicationPage } from './Shell';
@@ -20,9 +19,13 @@ const firstPath = codes[0] ? `/${codes[0]}` : '/';
20
19
  function GlobalRequestLoading() {
21
20
  const loading = useGlobalRequestLoading();
22
21
  return loading ? (
23
- <div aria-live="polite" className="oxa-global-request-loading">
24
- <Spin size="small" />
25
- <span>正在加载…</span>
22
+ <div
23
+ aria-label="页面请求进行中"
24
+ aria-valuetext="正在加载"
25
+ className="oxa-global-request-loading"
26
+ role="progressbar"
27
+ >
28
+ <span />
26
29
  </div>
27
30
  ) : null;
28
31
  }
@@ -40,21 +43,19 @@ const resourceRoutes = codes.flatMap(resourceCode => [
40
43
 
41
44
  ReactDOM.createRoot(document.getElementById('root')!).render(
42
45
  <React.StrictMode>
43
- <ConfigProvider locale={zhCN} theme={{ token: { borderRadius: 8, colorBgLayout: '#f3f7fd', colorPrimary: '#1677ff', colorText: '#172033', fontSize: 14 }, components: { Card: { headerFontSize: 16 }, Layout: { bodyBg: '#f3f7fd', headerBg: '#ffffff', siderBg: '#ffffff' }, Menu: { itemBorderRadius: 8, itemSelectedBg: '#eaf3ff' } } }}>
44
- <AntdApp>
45
- <GlobalRequestLoading />
46
- <RuntimeBoundary>
47
- <BrowserRouter basename={applicationBasename()}>
48
- <Refine dataProvider={applicationProvider} resources={codes.map(code => ({ name: code, list: `/${code}`, create: `/${code}/new`, edit: `/${code}/:id/edit`, show: `/${code}/:id` }))}>
49
- <Routes>
50
- <Route path="/" element={codes.length ? <Navigate replace to={firstPath} /> : <EmptyApplicationPage />} />
51
- <Route path="/files/:resourceCode/:fileId/preview" element={<FilePreviewPage />} />
52
- {resourceRoutes}
53
- </Routes>
54
- </Refine>
55
- </BrowserRouter>
56
- </RuntimeBoundary>
57
- </AntdApp>
58
- </ConfigProvider>
46
+ <AppearanceProvider>
47
+ <GlobalRequestLoading />
48
+ <RuntimeBoundary>
49
+ <BrowserRouter basename={applicationBasename()}>
50
+ <Refine dataProvider={applicationProvider} resources={codes.map(code => ({ name: code, list: `/${code}`, create: `/${code}/new`, edit: `/${code}/:id/edit`, show: `/${code}/:id` }))}>
51
+ <Routes>
52
+ <Route path="/" element={codes.length ? <Navigate replace to={firstPath} /> : <EmptyApplicationPage />} />
53
+ <Route path="/files/:resourceCode/:fileId/preview" element={<FilePreviewPage />} />
54
+ {resourceRoutes}
55
+ </Routes>
56
+ </Refine>
57
+ </BrowserRouter>
58
+ </RuntimeBoundary>
59
+ </AppearanceProvider>
59
60
  </React.StrictMode>,
60
61
  );
@@ -8,6 +8,9 @@ import {
8
8
  type DataQuery,
9
9
  type DataRecord,
10
10
  type DataResourceSurface,
11
+ type DataTransactionOperation,
12
+ type DataTransactionRequest,
13
+ type DataTransactionResult,
11
14
  type DirectoryEntryPage,
12
15
  type DirectoryResolveRequest,
13
16
  } from 'openxiangda-contracts/browser';
@@ -15,7 +18,7 @@ import { applicationCode, runtimeMount } from './runtime-meta';
15
18
  import { useSyncExternalStore } from 'react';
16
19
 
17
20
  interface PlatformEnvelope<T> {
18
- code: number;
21
+ code: number | string;
19
22
  message?: string;
20
23
  errorCode?: string;
21
24
  requestId?: string;
@@ -27,22 +30,22 @@ const globalRequestListeners = new Set<() => void>();
27
30
 
28
31
  function beginGlobalRequest() {
29
32
  globalRequestCount += 1;
30
- globalRequestListeners.forEach(listener => listener());
33
+ globalRequestListeners.forEach((listener) => listener());
31
34
  }
32
35
 
33
36
  function endGlobalRequest() {
34
37
  globalRequestCount = Math.max(0, globalRequestCount - 1);
35
- globalRequestListeners.forEach(listener => listener());
38
+ globalRequestListeners.forEach((listener) => listener());
36
39
  }
37
40
 
38
41
  export function useGlobalRequestLoading() {
39
42
  return useSyncExternalStore(
40
- listener => {
43
+ (listener) => {
41
44
  globalRequestListeners.add(listener);
42
45
  return () => globalRequestListeners.delete(listener);
43
46
  },
44
47
  () => globalRequestCount > 0,
45
- () => false,
48
+ () => false
46
49
  );
47
50
  }
48
51
 
@@ -59,12 +62,19 @@ async function request<T>(path: string, init?: RequestInit): Promise<T> {
59
62
  },
60
63
  });
61
64
  const payload = (await response.json().catch(() => null)) as PlatformEnvelope<T> | T | null;
62
- const envelope = payload && typeof payload === 'object' && 'code' in payload ? payload as PlatformEnvelope<T> : null;
65
+ const envelope = payload && typeof payload === 'object' && 'code' in payload ? (payload as PlatformEnvelope<T>) : null;
63
66
  if (!response.ok || (envelope && envelope.code !== 200)) {
64
67
  const requestId = envelope?.requestId ? ` (requestId: ${envelope.requestId})` : '';
65
- throw new Error(`${envelope?.errorCode || `HTTP_${response.status}`}: ${envelope?.message || '平台请求失败'}${requestId}`);
68
+ throw Object.assign(
69
+ new Error(`${envelope?.errorCode || `HTTP_${response.status}`}: ${envelope?.message || '平台请求失败'}${requestId}`),
70
+ {
71
+ code: envelope?.errorCode || `HTTP_${response.status}`,
72
+ status: response.status,
73
+ data: envelope?.data ?? null,
74
+ }
75
+ );
66
76
  }
67
- return envelope ? envelope.data : payload as T;
77
+ return envelope ? envelope.data : (payload as T);
68
78
  } finally {
69
79
  endGlobalRequest();
70
80
  }
@@ -103,8 +113,15 @@ let activeIdentity: RuntimeIdentity | undefined;
103
113
  export async function loadCurrentIdentity(): Promise<RuntimeIdentity> {
104
114
  const mount = runtimeMount();
105
115
  if (mount) {
106
- const current = await request<DeployedCurrent>(`${nativeBase()}/current-user?environmentKey=${encodeURIComponent(mount.environmentKey)}`);
107
- if (current.schemaVersion !== 'openxiangda.current-user/v2' || current.appCode !== mount.appCode || current.environment.key !== mount.environmentKey || current.principal.type !== 'user') {
116
+ const current = await request<DeployedCurrent>(
117
+ `${nativeBase()}/current-user?environmentKey=${encodeURIComponent(mount.environmentKey)}`
118
+ );
119
+ if (
120
+ current.schemaVersion !== 'openxiangda.current-user/v2' ||
121
+ current.appCode !== mount.appCode ||
122
+ current.environment.key !== mount.environmentKey ||
123
+ current.principal.type !== 'user'
124
+ ) {
108
125
  throw new Error('OPENXIANGDA_DEPLOYED_CURRENT_USER_INVALID');
109
126
  }
110
127
  activeIdentity = { ...current.principal, environment: current.environment };
@@ -115,6 +132,13 @@ export async function loadCurrentIdentity(): Promise<RuntimeIdentity> {
115
132
  return activeIdentity;
116
133
  }
117
134
 
135
+ export async function logoutCurrentUser() {
136
+ await request<null>('/api/auth/logout', {
137
+ method: 'POST',
138
+ });
139
+ activeIdentity = undefined;
140
+ }
141
+
118
142
  function currentEnvironmentKey() {
119
143
  const key = activeIdentity?.environment.key || runtimeMount()?.environmentKey;
120
144
  if (!key) throw new Error('OPENXIANGDA_CURRENT_ENVIRONMENT_REQUIRED');
@@ -124,29 +148,49 @@ function currentEnvironmentKey() {
124
148
  export type DirectoryKind = 'user' | 'department';
125
149
 
126
150
  export async function searchDirectory(kind: DirectoryKind, options: { keyword: string; cursor?: string }) {
127
- const params = new URLSearchParams({ environmentKey: currentEnvironmentKey(), keyword: options.keyword.trim(), limit: '20' });
151
+ const params = new URLSearchParams({
152
+ environmentKey: currentEnvironmentKey(),
153
+ keyword: options.keyword.trim(),
154
+ limit: '20',
155
+ });
128
156
  if (options.cursor) params.set('cursor', options.cursor);
129
157
  return request<DirectoryEntryPage>(`${nativeBase().replace(/\/native$/, '')}/directory/${kind}s?${params}`);
130
158
  }
131
159
 
132
160
  export async function browseDepartmentTree(options: { parentId?: string; cursor?: string }) {
133
- const params = new URLSearchParams({ environmentKey: currentEnvironmentKey(), limit: '100' });
161
+ const params = new URLSearchParams({
162
+ environmentKey: currentEnvironmentKey(),
163
+ limit: '100',
164
+ });
134
165
  if (options.parentId) params.set('parentId', options.parentId);
135
166
  if (options.cursor) params.set('offset', options.cursor);
136
167
  return request<DirectoryEntryPage>(`${nativeBase().replace(/\/native$/, '')}/directory/departments/tree?${params}`);
137
168
  }
138
169
 
139
170
  export async function browseDepartmentUsers(departmentId: string, page = 1) {
140
- const params = new URLSearchParams({ environmentKey: currentEnvironmentKey(), page: String(page), limit: '20' });
141
- return request<DirectoryEntryPage>(`${nativeBase().replace(/\/native$/, '')}/directory/departments/${encodeURIComponent(departmentId)}/users?${params}`);
171
+ const params = new URLSearchParams({
172
+ environmentKey: currentEnvironmentKey(),
173
+ page: String(page),
174
+ limit: '20',
175
+ });
176
+ return request<DirectoryEntryPage>(
177
+ `${nativeBase().replace(/\/native$/, '')}/directory/departments/${encodeURIComponent(departmentId)}/users?${params}`
178
+ );
142
179
  }
143
180
 
144
181
  export async function resolveDirectory(kind: DirectoryKind, ids: string[]) {
145
182
  const uniqueIds = [...new Set(ids)];
146
183
  if (uniqueIds.length === 0) throw new Error('OPENXIANGDA_DIRECTORY_RESOLVE_IDS_REQUIRED');
147
184
  if (uniqueIds.length > 50) throw new Error('OPENXIANGDA_DIRECTORY_RESOLVE_IDS_EXCEEDED');
148
- const body: DirectoryResolveRequest = { schemaVersion: SCHEMA_VERSIONS.directoryResolveRequest, kind, ids: uniqueIds };
149
- return request<DirectoryEntryPage>(`${nativeBase().replace(/\/native$/, '')}/directory/resolve?environmentKey=${encodeURIComponent(currentEnvironmentKey())}`, { method: 'POST', body: JSON.stringify(body) });
185
+ const body: DirectoryResolveRequest = {
186
+ schemaVersion: SCHEMA_VERSIONS.directoryResolveRequest,
187
+ kind,
188
+ ids: uniqueIds,
189
+ };
190
+ return request<DirectoryEntryPage>(
191
+ `${nativeBase().replace(/\/native$/, '')}/directory/resolve?environmentKey=${encodeURIComponent(currentEnvironmentKey())}`,
192
+ { method: 'POST', body: JSON.stringify(body) }
193
+ );
150
194
  }
151
195
 
152
196
  export async function searchResource(resourceCode: string, labelField: string, options: { keyword: string; cursor?: string }) {
@@ -154,14 +198,32 @@ export async function searchResource(resourceCode: string, labelField: string, o
154
198
  const offset = options.cursor ? Number(options.cursor) : 0;
155
199
  const query: DataQuery = {
156
200
  schemaVersion: SCHEMA_VERSIONS.dataQuery,
157
- filters: options.keyword.trim() ? [{ field: labelField, operator: 'ilike', value: `%${options.keyword.trim()}%` }] : [],
201
+ filters: options.keyword.trim()
202
+ ? [
203
+ {
204
+ field: labelField,
205
+ operator: 'ilike',
206
+ value: `%${options.keyword.trim()}%`,
207
+ },
208
+ ]
209
+ : [],
158
210
  order: [{ field: labelField, direction: 'asc' }],
159
211
  limit: pageSize,
160
212
  offset: Number.isSafeInteger(offset) && offset >= 0 ? offset : 0,
161
213
  };
162
- const page = await request<DataPage<Record<string, unknown>>>(`${dataBase(resourceCode)}/query`, { method: 'POST', body: JSON.stringify({ ...query, environmentKey: currentEnvironmentKey() }) });
214
+ const page = await request<DataPage<Record<string, unknown>>>(`${dataBase(resourceCode)}/query`, {
215
+ method: 'POST',
216
+ body: JSON.stringify({
217
+ ...query,
218
+ environmentKey: currentEnvironmentKey(),
219
+ }),
220
+ });
163
221
  return {
164
- items: page.items.map(item => ({ id: String(item.id), label: String(item[labelField] ?? item.id), selectable: true })),
222
+ items: page.items.map((item) => ({
223
+ id: String(item.id),
224
+ label: String(item[labelField] ?? item.id),
225
+ selectable: true,
226
+ })),
165
227
  nextCursor: page.items.length >= pageSize ? String(page.offset + page.items.length) : null,
166
228
  };
167
229
  }
@@ -169,10 +231,18 @@ export async function searchResource(resourceCode: string, labelField: string, o
169
231
  export async function resolveResource(resourceCode: string, ids: string[], labelField: string) {
170
232
  if (ids.length === 0) throw new Error('OPENXIANGDA_RESOURCE_RESOLVE_IDS_REQUIRED');
171
233
  if (ids.length > 50) throw new Error('OPENXIANGDA_RESOURCE_RESOLVE_IDS_EXCEEDED');
172
- const items = await Promise.all(ids.map(async id => {
173
- const record = await request<DataRecord<Record<string, unknown>>>(`${dataBase(resourceCode)}/records/${encodeURIComponent(id)}?environmentKey=${encodeURIComponent(currentEnvironmentKey())}`);
174
- return { id, label: String(record.data[labelField] ?? id), selectable: true };
175
- }));
234
+ const items = await Promise.all(
235
+ ids.map(async (id) => {
236
+ const record = await request<DataRecord<Record<string, unknown>>>(
237
+ `${dataBase(resourceCode)}/records/${encodeURIComponent(id)}?environmentKey=${encodeURIComponent(currentEnvironmentKey())}`
238
+ );
239
+ return {
240
+ id,
241
+ label: String(record.data[labelField] ?? id),
242
+ selectable: true,
243
+ };
244
+ })
245
+ );
176
246
  return { items, nextCursor: null };
177
247
  }
178
248
 
@@ -197,7 +267,12 @@ export function createNativeResourceClient(code: string, surface: DataResourceSu
197
267
  if (query.keyword?.trim()) {
198
268
  const searchable = surface.list?.searchableFields || [];
199
269
  const field = searchable.includes(query.searchField || '') ? query.searchField! : searchable[0];
200
- if (field) filters.push({ field: assertField(field), operator: 'ilike', value: `%${query.keyword.trim()}%` });
270
+ if (field)
271
+ filters.push({
272
+ field: assertField(field),
273
+ operator: 'ilike',
274
+ value: `%${query.keyword.trim()}%`,
275
+ });
201
276
  }
202
277
  for (const [fieldCode, value] of Object.entries(query.filters || {})) {
203
278
  if (value === undefined || value === null || value === '') continue;
@@ -207,8 +282,10 @@ export function createNativeResourceClient(code: string, surface: DataResourceSu
207
282
  if (!declaredFields.has(fieldCode)) continue;
208
283
  const field = surface.fields[fieldCode];
209
284
  if (Array.isArray(value) && value.length === 2 && ['date', 'datetime', 'number', 'money', 'percent'].includes(field?.widget || '')) {
210
- if (value[0] !== undefined && value[0] !== null && value[0] !== '') filters.push({ field: fieldCode, operator: 'gte', value: value[0] });
211
- if (value[1] !== undefined && value[1] !== null && value[1] !== '') filters.push({ field: fieldCode, operator: 'lte', value: value[1] });
285
+ if (value[0] !== undefined && value[0] !== null && value[0] !== '')
286
+ filters.push({ field: fieldCode, operator: 'gte', value: value[0] });
287
+ if (value[1] !== undefined && value[1] !== null && value[1] !== '')
288
+ filters.push({ field: fieldCode, operator: 'lte', value: value[1] });
212
289
  } else if (Array.isArray(value) && ['multi', 'directory-user', 'directory-department', 'resource'].includes(field?.widget || '')) {
213
290
  filters.push({ field: fieldCode, operator: 'cs', value });
214
291
  } else {
@@ -219,42 +296,112 @@ export function createNativeResourceClient(code: string, surface: DataResourceSu
219
296
  };
220
297
  return {
221
298
  async list(query: GenericResourceQuery) {
222
- const fallbackSort = surface.list?.defaultSort || { field: Object.keys(surface.fields)[0] || 'id', order: 'asc' as const };
299
+ const fallbackSort = surface.list?.defaultSort || {
300
+ field: Object.keys(surface.fields)[0] || 'id',
301
+ order: 'asc' as const,
302
+ };
223
303
  const requestedSort = query.sort || fallbackSort;
224
304
  const sort = declaredFields.has(requestedSort.field) ? requestedSort : fallbackSort;
225
- const body: DataQuery = { schemaVersion: SCHEMA_VERSIONS.dataQuery, filters: filtersFor(query), order: [{ field: sort.field, direction: sort.order || 'asc' }], limit: query.pageSize, offset: (query.page - 1) * query.pageSize };
226
- const page = await request<DataPage<Record<string, unknown>>>(`${base}/query`, { method: 'POST', body: JSON.stringify({ ...body, environmentKey: currentEnvironmentKey() }) });
305
+ const body: DataQuery = {
306
+ schemaVersion: SCHEMA_VERSIONS.dataQuery,
307
+ filters: filtersFor(query),
308
+ order: [{ field: sort.field, direction: sort.order || 'asc' }],
309
+ limit: query.pageSize,
310
+ offset: (query.page - 1) * query.pageSize,
311
+ };
312
+ const page = await request<DataPage<Record<string, unknown>>>(`${base}/query`, {
313
+ method: 'POST',
314
+ body: JSON.stringify({
315
+ ...body,
316
+ environmentKey: currentEnvironmentKey(),
317
+ }),
318
+ });
227
319
  return { rows: page.items, total: page.total };
228
320
  },
229
321
  async get(id: string) {
230
- const record = await request<DataRecord<Record<string, unknown>>>(`${base}/records/${encodeURIComponent(id)}?environmentKey=${encodeURIComponent(currentEnvironmentKey())}`);
322
+ const record = await request<DataRecord<Record<string, unknown>>>(
323
+ `${base}/records/${encodeURIComponent(id)}?environmentKey=${encodeURIComponent(currentEnvironmentKey())}`
324
+ );
231
325
  return record.data;
232
326
  },
233
327
  async audit(id: string) {
234
- return request<DataAuditPage>(`${base}/records/${encodeURIComponent(id)}/audit?limit=10&offset=0&environmentKey=${encodeURIComponent(currentEnvironmentKey())}`);
328
+ return request<DataAuditPage>(
329
+ `${base}/records/${encodeURIComponent(id)}/audit?limit=10&offset=0&environmentKey=${encodeURIComponent(currentEnvironmentKey())}`
330
+ );
235
331
  },
236
332
  async create(data: Record<string, unknown>) {
237
- const record = await request<DataRecord<Record<string, unknown>>>(`${base}/records`, { method: 'POST', body: JSON.stringify({ environmentKey: currentEnvironmentKey(), data }) });
333
+ const record = await request<DataRecord<Record<string, unknown>>>(`${base}/records`, {
334
+ method: 'POST',
335
+ body: JSON.stringify({
336
+ environmentKey: currentEnvironmentKey(),
337
+ data,
338
+ }),
339
+ });
238
340
  return record.data;
239
341
  },
240
342
  async update(id: string, expectedRevision: number, data: Record<string, unknown>) {
241
- const record = await request<DataRecord<Record<string, unknown>>>(`${base}/records/${encodeURIComponent(id)}/update`, { method: 'POST', body: JSON.stringify({ environmentKey: currentEnvironmentKey(), expectedRevision, data }) });
343
+ const record = await request<DataRecord<Record<string, unknown>>>(`${base}/records/${encodeURIComponent(id)}/update`, {
344
+ method: 'POST',
345
+ body: JSON.stringify({
346
+ environmentKey: currentEnvironmentKey(),
347
+ expectedRevision,
348
+ data,
349
+ }),
350
+ });
242
351
  return record.data;
243
352
  },
244
353
  async remove(id: string, expectedRevision: number) {
245
- const record = await request<DataRecord<Record<string, unknown>>>(`${base}/records/${encodeURIComponent(id)}/delete`, { method: 'POST', body: JSON.stringify({ environmentKey: currentEnvironmentKey(), expectedRevision }) });
354
+ const record = await request<DataRecord<Record<string, unknown>>>(`${base}/records/${encodeURIComponent(id)}/delete`, {
355
+ method: 'POST',
356
+ body: JSON.stringify({
357
+ environmentKey: currentEnvironmentKey(),
358
+ expectedRevision,
359
+ }),
360
+ });
246
361
  return record.data;
247
362
  },
248
363
  async upload(fieldCode: string, file: File, recordId?: string) {
249
364
  assertField(fieldCode);
250
- 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 } : {}) }) });
251
- const uploaded = await fetch(plan.uploadUrl, { method: plan.uploadMethod, headers: plan.headers, body: file });
365
+ const plan = await request<DataFileUploadPlan>(`${base}/files/uploads/initiate`, {
366
+ method: 'POST',
367
+ body: JSON.stringify({
368
+ environmentKey: currentEnvironmentKey(),
369
+ fieldCode,
370
+ fileName: file.name,
371
+ fileSize: file.size,
372
+ contentType: file.type || 'application/octet-stream',
373
+ ...(recordId ? { recordId } : {}),
374
+ }),
375
+ });
376
+ const uploaded = await fetch(plan.uploadUrl, {
377
+ method: plan.uploadMethod,
378
+ headers: plan.headers,
379
+ body: file,
380
+ });
252
381
  if (!uploaded.ok) throw new Error(`FILE_UPLOAD_FAILED: HTTP_${uploaded.status}`);
253
- return request<DataFileRef>(`${base}/files/${encodeURIComponent(plan.file.id)}/complete`, { method: 'POST', body: JSON.stringify({ environmentKey: currentEnvironmentKey() }) });
382
+ return request<DataFileRef>(`${base}/files/${encodeURIComponent(plan.file.id)}/complete`, {
383
+ method: 'POST',
384
+ body: JSON.stringify({ environmentKey: currentEnvironmentKey() }),
385
+ });
254
386
  },
255
387
  };
256
388
  }
257
389
 
390
+ export async function transactNativeData(operations: DataTransactionOperation[], idempotencyKey: string = crypto.randomUUID()) {
391
+ const transaction: DataTransactionRequest = {
392
+ schemaVersion: SCHEMA_VERSIONS.dataTransactionRequest,
393
+ idempotencyKey,
394
+ operations,
395
+ };
396
+ return request<DataTransactionResult>(`${nativeBase()}/data/transactions`, {
397
+ method: 'POST',
398
+ body: JSON.stringify({
399
+ ...transaction,
400
+ environmentKey: currentEnvironmentKey(),
401
+ }),
402
+ });
403
+ }
404
+
258
405
  function nativeBase() {
259
406
  return `/service/openxiangda-api/v2/applications/${applicationCode()}/native`;
260
407
  }
@@ -264,16 +411,24 @@ function dataBase(code: string) {
264
411
  }
265
412
 
266
413
  export function dataFileContentUrl(resource: string, fileId: string, disposition: 'attachment' | 'inline' = 'attachment') {
267
- const query = new URLSearchParams({ environmentKey: currentEnvironmentKey(), disposition });
414
+ const query = new URLSearchParams({
415
+ environmentKey: currentEnvironmentKey(),
416
+ disposition,
417
+ });
268
418
  return `${dataBase(resource)}/files/${encodeURIComponent(fileId)}/content?${query}`;
269
419
  }
270
420
 
271
421
  export async function loadDataFilePreview(resource: string, fileId: string) {
272
- return request<DataFilePreview>(`${dataBase(resource)}/files/${encodeURIComponent(fileId)}/preview?environmentKey=${encodeURIComponent(currentEnvironmentKey())}`);
422
+ return request<DataFilePreview>(
423
+ `${dataBase(resource)}/files/${encodeURIComponent(fileId)}/preview?environmentKey=${encodeURIComponent(currentEnvironmentKey())}`
424
+ );
273
425
  }
274
426
 
275
427
  export async function fetchDataFileBlob(resource: string, fileId: string) {
276
- const response = await fetch(dataFileContentUrl(resource, fileId, 'inline'), { credentials: 'include', headers: { accept: 'application/octet-stream,*/*' } });
428
+ const response = await fetch(dataFileContentUrl(resource, fileId, 'inline'), {
429
+ credentials: 'include',
430
+ headers: { accept: 'application/octet-stream,*/*' },
431
+ });
277
432
  if (!response.ok) throw new Error(`FILE_CONTENT_READ_FAILED: HTTP_${response.status}`);
278
433
  return response.blob();
279
434
  }