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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openxiangda-cli",
3
- "version": "2.0.0-alpha.66",
3
+ "version": "2.0.0-alpha.67",
4
4
  "description": "Thin application-level CLI for OpenXiangda 2.0.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -247,15 +247,21 @@ function AuditEntry({ entry, surface }: { entry: DataAuditEntry; surface: DataRe
247
247
 
248
248
  function useGeneratedList(definition: GeneratedDefinition) {
249
249
  const { code, surface } = definition;
250
- const defaultSort = surface.list?.defaultSort || {
251
- field: Object.keys(surface.fields)[0] || 'id',
252
- order: 'asc' as const,
253
- };
250
+ const defaultSortField = surface.list?.defaultSort?.field || Object.keys(surface.fields)[0] || 'id';
251
+ const defaultSortOrder = surface.list?.defaultSort?.order || 'asc';
252
+ const defaultPageSize = surface.list?.defaultPageSize || 20;
254
253
  const [page, setPage] = useState(1);
255
- const [pageSize, setPageSize] = useState(surface.list?.defaultPageSize || 20);
254
+ const [pageSize, setPageSize] = useState(defaultPageSize);
256
255
  const [draft, setDraft] = useState<Partial<GenericResourceQuery>>({});
257
256
  const [query, setQuery] = useState<Partial<GenericResourceQuery>>({});
258
- const [sort, setSort] = useState({ field: defaultSort.field, order: defaultSort.order || 'asc' as const });
257
+ const [sort, setSort] = useState({ field: defaultSortField, order: defaultSortOrder as 'asc' | 'desc' });
258
+ useEffect(() => {
259
+ setPage(1);
260
+ setPageSize(defaultPageSize);
261
+ setDraft({});
262
+ setQuery({});
263
+ setSort({ field: defaultSortField, order: defaultSortOrder as 'asc' | 'desc' });
264
+ }, [code, defaultPageSize, defaultSortField, defaultSortOrder]);
259
265
  const list = useList<ResourceRecord>({
260
266
  resource: code,
261
267
  pagination: { currentPage: page, pageSize },
@@ -278,9 +284,10 @@ export function GeneratedResourcePage({
278
284
  if (!definition || definition.surface.generated === false) {
279
285
  return <Result status="404" title="未声明可生成的资源页面" />;
280
286
  }
281
- if (mode === 'list') return <GeneratedResourceListPage definition={definition} variant={variant} />;
282
- if (mode === 'detail') return <GeneratedResourceDetailPage definition={definition} variant={variant} />;
283
- return <GeneratedResourceFormPage definition={definition} mode={mode} variant={variant} />;
287
+ const pageKey = `${variant}:${mode}:${resourceCode}`;
288
+ if (mode === 'list') return <GeneratedResourceListPage key={pageKey} definition={definition} variant={variant} />;
289
+ if (mode === 'detail') return <GeneratedResourceDetailPage key={pageKey} definition={definition} variant={variant} />;
290
+ return <GeneratedResourceFormPage key={pageKey} definition={definition} mode={mode} variant={variant} />;
284
291
  }
285
292
 
286
293
  function GeneratedResourceListPage({ definition, variant }: { definition: GeneratedDefinition; variant: 'desktop' | 'mobile' }) {
@@ -201,7 +201,11 @@ export function createNativeResourceClient(code: string, surface: DataResourceSu
201
201
  }
202
202
  for (const [fieldCode, value] of Object.entries(query.filters || {})) {
203
203
  if (value === undefined || value === null || value === '') continue;
204
- const field = surface.fields[assertField(fieldCode)];
204
+ // A list state can outlive a resource route during client-side navigation.
205
+ // Ignore filters from the previous resource instead of constructing an
206
+ // invalid Native Data query for the current resource.
207
+ if (!declaredFields.has(fieldCode)) continue;
208
+ const field = surface.fields[fieldCode];
205
209
  if (Array.isArray(value) && value.length === 2 && ['date', 'datetime', 'number', 'money', 'percent'].includes(field?.widget || '')) {
206
210
  if (value[0] !== undefined && value[0] !== null && value[0] !== '') filters.push({ field: fieldCode, operator: 'gte', value: value[0] });
207
211
  if (value[1] !== undefined && value[1] !== null && value[1] !== '') filters.push({ field: fieldCode, operator: 'lte', value: value[1] });
@@ -215,8 +219,10 @@ export function createNativeResourceClient(code: string, surface: DataResourceSu
215
219
  };
216
220
  return {
217
221
  async list(query: GenericResourceQuery) {
218
- const sort = query.sort || surface.list?.defaultSort || { field: Object.keys(surface.fields)[0] || 'id', order: 'asc' as const };
219
- 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 };
222
+ const fallbackSort = surface.list?.defaultSort || { field: Object.keys(surface.fields)[0] || 'id', order: 'asc' as const };
223
+ const requestedSort = query.sort || fallbackSort;
224
+ 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 };
220
226
  const page = await request<DataPage<Record<string, unknown>>>(`${base}/query`, { method: 'POST', body: JSON.stringify({ ...body, environmentKey: currentEnvironmentKey() }) });
221
227
  return { rows: page.items, total: page.total };
222
228
  },
@@ -35,6 +35,11 @@ test('standard surfaces keep authoritative directory/resource selectors and mobi
35
35
  assert.match(crud, /text\/csv;charset=utf-8/);
36
36
  assert.match(crud, /数据读取失败/);
37
37
  assert.match(crud, /state\.list\.query\.refetch/);
38
+ assert.match(crud, /key=\{pageKey\}/);
39
+ assert.match(crud, /setQuery\(\{\}\)/);
40
+ const client = readFileSync(new URL('../src/platform-client.ts', import.meta.url), 'utf8');
41
+ assert.match(client, /declaredFields\.has\(requestedSort\.field\)/);
42
+ assert.match(client, /if \(!declaredFields\.has\(fieldCode\)\) continue/);
38
43
  });
39
44
 
40
45
  test('runtime metadata stays mount-scoped', () => {
@@ -21,7 +21,7 @@
21
21
  "build": "pnpm --recursive build"
22
22
  },
23
23
  "devDependencies": {
24
- "openxiangda-cli": "2.0.0-alpha.66",
24
+ "openxiangda-cli": "2.0.0-alpha.67",
25
25
  "openxiangda-contracts": "2.0.0-alpha.31",
26
26
  "openxiangda-devkit-core": "2.0.0-alpha.41",
27
27
  "typescript": "5.9.3"