openxiangda-cli 2.0.0-alpha.120 → 2.0.0-alpha.122

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.120",
3
+ "version": "2.0.0-alpha.122",
4
4
  "description": "Thin application-level CLI for OpenXiangda 2.0.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -24,7 +24,7 @@
24
24
  "openxiangda-contracts": "2.0.0-alpha.58",
25
25
  "openxiangda-devkit-core": "2.0.0-alpha.76",
26
26
  "openxiangda-mcp": "2.0.0-alpha.76",
27
- "openxiangda-skill-kit": "2.0.0-alpha.95"
27
+ "openxiangda-skill-kit": "2.0.0-alpha.97"
28
28
  },
29
29
  "devDependencies": {
30
30
  "tsx": "4.23.12",
@@ -15,7 +15,7 @@
15
15
  "@nestjs/common": "11.1.29",
16
16
  "@nestjs/core": "11.1.29",
17
17
  "@nestjs/platform-fastify": "11.1.29",
18
- "openxiangda": "2.0.0-alpha.34",
18
+ "openxiangda": "2.0.0-alpha.36",
19
19
  "reflect-metadata": "0.2.2",
20
20
  "rxjs": "7.8.2"
21
21
  },
@@ -24,6 +24,36 @@ const field = (
24
24
  });
25
25
 
26
26
  const fields: DataResourceSurface['fields'] = {
27
+ departmentMultiple: field('构成部门', 'department.multiple', 'directory-department', {
28
+ list: true,
29
+ section: '组织信息',
30
+ }),
31
+ departmentSingle: field('牵头部门', 'department.single', 'directory-department', {
32
+ list: true,
33
+ section: '组织信息',
34
+ }),
35
+ userMultiple: field('小组成员', 'user.multiple', 'directory-user', {
36
+ list: true,
37
+ section: '组织信息',
38
+ }),
39
+ resourceMultiple: field('关联资源', 'resource-ref.multiple', 'resource', {
40
+ list: true,
41
+ section: '组织信息',
42
+ source: {
43
+ kind: 'resource',
44
+ resourceCode: 'resource-01',
45
+ labelField: 'name',
46
+ searchFields: ['name'],
47
+ },
48
+ }),
49
+ enabled: field('启用状态', 'boolean', 'switch', {
50
+ list: true,
51
+ section: '组织信息',
52
+ }),
53
+ displayOrder: field('显示顺序', 'number.integer', 'number', {
54
+ list: true,
55
+ section: '组织信息',
56
+ }),
27
57
  description: field('栏目说明', 'text.long', 'textarea', {
28
58
  section: '栏目设置',
29
59
  }),
@@ -107,6 +137,11 @@ const surface: DataResourceSurface = {
107
137
  mobile: { enabled: true },
108
138
  };
109
139
 
140
+ const { enabled, displayOrder, ...remainingFields } = fields;
141
+ const acceptanceSurface = new URLSearchParams(location.search).get('primary') === 'falsy'
142
+ ? { ...surface, fields: { enabled, displayOrder, ...remainingFields } }
143
+ : surface;
144
+
110
145
  const names = [
111
146
  '内容栏目',
112
147
  '内容文章',
@@ -135,7 +170,7 @@ const resourceDefinitions = Object.fromEntries(
135
170
  update: `app:openxiangda-application:data:${code}:update`,
136
171
  delete: `app:openxiangda-application:data:${code}:delete`,
137
172
  },
138
- surface,
173
+ surface: acceptanceSurface,
139
174
  },
140
175
  ];
141
176
  })
@@ -21,6 +21,27 @@ const definitions = resourceDefinitions as Record<string, DeclaredResource>;
21
21
  const allCapabilities = [...capabilities] as string[];
22
22
  const runtimeBase = `/view/${appCode}`;
23
23
 
24
+ const semanticResourceItem = {
25
+ id: 'group-1',
26
+ revision: 1,
27
+ updated_at: '2026-08-28T01:00:00.000Z',
28
+ departmentMultiple: [
29
+ { label: '信息技术部', value: 'department-it' },
30
+ { label: '人文艺术部', value: 'department-humanities' },
31
+ ],
32
+ departmentSingle: { label: '校工会', value: 'department-union' },
33
+ userMultiple: [
34
+ { label: '周明远', value: 'user-zhou', employeeNo: 'T1001' },
35
+ { label: '沈书雅', value: 'user-shen', employeeNo: 'T1002' },
36
+ ],
37
+ resourceMultiple: [
38
+ { label: '教职工之家', value: 'resource-home', resourceCode: 'resource-01' },
39
+ { label: '文体活动室', value: 'resource-activity', resourceCode: 'resource-01' },
40
+ ],
41
+ enabled: false,
42
+ displayOrder: 0,
43
+ };
44
+
24
45
  function runtimeAuthorization(authorized: boolean, superAdmin = false) {
25
46
  return {
26
47
  schemaVersion: 'openxiangda.runtime-authorization/v2',
@@ -62,6 +83,7 @@ async function mockPlatform(
62
83
  authorized = true,
63
84
  targetRuntimeBase = runtimeBase,
64
85
  superAdmin = false,
86
+ queryItemsByResource: Record<string, Array<Record<string, unknown>>> = {},
65
87
  ) {
66
88
  let contextReads = 0;
67
89
  let avatarCompletes = 0;
@@ -162,6 +184,8 @@ async function mockPlatform(
162
184
  }
163
185
  const query = url.pathname.match(/\/native\/data\/([^/]+)\/query$/);
164
186
  if (query) {
187
+ const resourceCode = decodeURIComponent(query[1]);
188
+ const items = queryItemsByResource[resourceCode] || [];
165
189
  observed.push({ path: url.pathname, perspectiveCode });
166
190
  return route.fulfill({
167
191
  contentType: 'application/json',
@@ -169,11 +193,11 @@ async function mockPlatform(
169
193
  code: 200,
170
194
  data: {
171
195
  schemaVersion: 'openxiangda.native-data-page/v2',
172
- resourceCode: decodeURIComponent(query[1]),
196
+ resourceCode,
173
197
  appVersionId: 'app-version-1',
174
198
  environmentHeadRevision: 1,
175
- items: [],
176
- total: 0,
199
+ items,
200
+ total: items.length,
177
201
  limit: 500,
178
202
  offset: 0,
179
203
  },
@@ -379,6 +403,82 @@ test('keeps the user portal and namespaced generated admin resource in one route
379
403
  await expect(page.locator('.oxa-sider')).toHaveCount(1);
380
404
  });
381
405
 
406
+ test('renders canonical semantic labels in the real generated resource list', async ({ page }) => {
407
+ const resourceCode = 'resource-01';
408
+ await mockPlatform(page, true, runtimeBase, true, {
409
+ [resourceCode]: [semanticResourceItem],
410
+ });
411
+ await page.goto('/resource-experience.e2e.html');
412
+ await expect(page).toHaveURL(`/admin/resources/${resourceCode}`);
413
+ const row = page.getByRole('row').filter({ hasText: '信息技术部' });
414
+ await expect(row).toHaveCount(1);
415
+ for (const label of [
416
+ '信息技术部',
417
+ '人文艺术部',
418
+ '校工会',
419
+ '周明远',
420
+ '沈书雅',
421
+ '教职工之家',
422
+ '文体活动室',
423
+ ]) {
424
+ await expect(row).toContainText(label);
425
+ }
426
+ await expect(row).not.toContainText('[object Object]');
427
+ await expect(row.getByRole('cell').nth(5)).toHaveText('否');
428
+ await expect(row.getByRole('cell').nth(6)).toHaveText('0');
429
+ await page.screenshot({
430
+ fullPage: true,
431
+ path: 'test-results/generated-resource-semantic-values-desktop.png',
432
+ });
433
+ await row.getByText('信息技术部', { exact: true }).click();
434
+ await expect(page).toHaveURL(`/admin/resources/${resourceCode}/group-1`);
435
+ });
436
+
437
+ test('renders the canonical first field on the mobile generated list', async ({ page }) => {
438
+ const resourceCode = 'resource-01';
439
+ await mockPlatform(page, true, runtimeBase, true, {
440
+ [resourceCode]: [semanticResourceItem],
441
+ });
442
+ await page.setViewportSize({ width: 390, height: 844 });
443
+ await page.goto('/resource-experience.e2e.html');
444
+ await page.evaluate(resource => {
445
+ history.pushState({}, '', `/m/admin/resources/${resource}`);
446
+ window.dispatchEvent(new PopStateEvent('popstate'));
447
+ }, resourceCode);
448
+ const card = page.locator('.oxa-mobile-record-card').filter({ hasText: '信息技术部' });
449
+ await expect(card).toHaveCount(1);
450
+ await expect(card).toContainText('人文艺术部');
451
+ await expect(card).toContainText('校工会');
452
+ await expect(card).toContainText('周明远');
453
+ await expect(card).toContainText('教职工之家');
454
+ await expect(card).not.toContainText('[object Object]');
455
+ await page.screenshot({
456
+ fullPage: true,
457
+ path: 'test-results/generated-resource-semantic-values-mobile.png',
458
+ });
459
+ await card.getByText('信息技术部', { exact: true }).click();
460
+ await expect(page).toHaveURL(`/m/admin/resources/${resourceCode}/group-1`);
461
+ });
462
+
463
+ test('preserves false and zero when they lead the mobile generated list', async ({ page }) => {
464
+ const resourceCode = 'resource-01';
465
+ await mockPlatform(page, true, runtimeBase, true, {
466
+ [resourceCode]: [semanticResourceItem],
467
+ });
468
+ await page.setViewportSize({ width: 390, height: 844 });
469
+ await page.goto('/resource-experience.e2e.html?primary=falsy');
470
+ await page.evaluate(resource => {
471
+ history.pushState({}, '', `/m/admin/resources/${resource}`);
472
+ window.dispatchEvent(new PopStateEvent('popstate'));
473
+ }, resourceCode);
474
+ const card = page.locator('.oxa-mobile-record-card');
475
+ await expect(card).toHaveCount(1);
476
+ await expect(card.locator('.oxa-mobile-record-heading')).toHaveText('否');
477
+ await expect(card.locator('.oxa-mobile-record-fields > div').filter({ hasText: '显示顺序' })).toContainText('0');
478
+ await card.locator('.oxa-mobile-record-heading').getByText('否', { exact: true }).click();
479
+ await expect(page).toHaveURL(`/m/admin/resources/${resourceCode}/group-1`);
480
+ });
481
+
382
482
  test('fails closed on a direct operation route without its capability', async ({ page }) => {
383
483
  await mockPlatform(page, false, runtimeBase, false);
384
484
  await page.goto('/resource-experience.e2e.html');
@@ -14,7 +14,7 @@
14
14
  "@ant-design/icons": "6.2.3",
15
15
  "@refinedev/core": "5.0.12",
16
16
  "antd": "6.4.3",
17
- "openxiangda": "2.0.0-alpha.34",
17
+ "openxiangda": "2.0.0-alpha.36",
18
18
  "react": "19.2.8",
19
19
  "react-dom": "19.2.8",
20
20
  "react-router-dom": "7.8.2"
@@ -22,7 +22,7 @@
22
22
  "build": "pnpm --recursive build"
23
23
  },
24
24
  "devDependencies": {
25
- "openxiangda": "2.0.0-alpha.34",
25
+ "openxiangda": "2.0.0-alpha.36",
26
26
  "typescript": "5.9.3"
27
27
  }
28
28
  }