openxiangda-cli 2.0.0-alpha.59 → 2.0.0-alpha.61

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.
@@ -24,5 +24,6 @@ export declare function prepareWorkspace(directory: string, options?: PrepareWor
24
24
  output: string;
25
25
  changed: boolean;
26
26
  contractDigest: string;
27
+ aiCatalogDigest: string;
27
28
  } | undefined>;
28
29
  //# sourceMappingURL=create-workspace.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"create-workspace.d.ts","sourceRoot":"","sources":["../src/create-workspace.ts"],"names":[],"mappings":"AAeA,MAAM,WAAW,oBAAoB;IACnC,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,aAAa,CAAC,EAAE,sBAAsB,CAAC;IACvC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,MAAM,sBAAsB,GAAG,SAAS,GAAG,SAAS,CAAC;AAE3D,MAAM,WAAW,uBAAuB;IACtC,aAAa,CAAC,EAAE,sBAAsB,CAAC;CACxC;AAED,wBAAsB,eAAe,CAAC,KAAK,EAAE,oBAAoB;;;;;;;;GAkDhE;AAED,wBAAsB,gBAAgB,CACpC,SAAS,EAAE,MAAM,EACjB,OAAO,GAAE,uBAA4B;;;;eAoBtC"}
1
+ {"version":3,"file":"create-workspace.d.ts","sourceRoot":"","sources":["../src/create-workspace.ts"],"names":[],"mappings":"AAeA,MAAM,WAAW,oBAAoB;IACnC,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,aAAa,CAAC,EAAE,sBAAsB,CAAC;IACvC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,MAAM,sBAAsB,GAAG,SAAS,GAAG,SAAS,CAAC;AAE3D,MAAM,WAAW,uBAAuB;IACtC,aAAa,CAAC,EAAE,sBAAsB,CAAC;CACxC;AAED,wBAAsB,eAAe,CAAC,KAAK,EAAE,oBAAoB;;;;;;;;GAkDhE;AAED,wBAAsB,gBAAgB,CACpC,SAAS,EAAE,MAAM,EACjB,OAAO,GAAE,uBAA4B;;;;;eAoBtC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openxiangda-cli",
3
- "version": "2.0.0-alpha.59",
3
+ "version": "2.0.0-alpha.61",
4
4
  "description": "Thin application-level CLI for OpenXiangda 2.0.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -23,9 +23,9 @@
23
23
  ],
24
24
  "dependencies": {
25
25
  "@oclif/core": "4.13.3",
26
- "openxiangda-contracts": "2.0.0-alpha.28",
27
- "openxiangda-devkit-core": "2.0.0-alpha.35",
28
- "openxiangda-mcp": "2.0.0-alpha.35"
26
+ "openxiangda-contracts": "2.0.0-alpha.29",
27
+ "openxiangda-devkit-core": "2.0.0-alpha.37",
28
+ "openxiangda-mcp": "2.0.0-alpha.37"
29
29
  },
30
30
  "devDependencies": {
31
31
  "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-nest": "2.0.0-alpha.33",
18
+ "openxiangda-nest": "2.0.0-alpha.35",
19
19
  "reflect-metadata": "0.2.2",
20
20
  "rxjs": "7.8.2"
21
21
  },
@@ -441,7 +441,7 @@ test('continues create, list, detail, update, and delete with declared query fie
441
441
  .locator('.ant-table-column-sorter')
442
442
  ).toHaveCount(0);
443
443
  const queriesBeforeSort = queryBodies.length;
444
- await page.getByRole('columnheader', { name: /仪器名称/ }).click();
444
+ await page.getByRole('columnheader', { name: /仪器中文名/ }).click();
445
445
  await expect.poll(() => queryBodies.length).toBeGreaterThan(queriesBeforeSort);
446
446
  expect(
447
447
  (queryBodies.at(-1) as { order: Array<{ field: string; direction: string }> })
@@ -16,7 +16,7 @@
16
16
  "antd": "6.4.3",
17
17
  "dayjs": "1.11.18",
18
18
  "docx-preview": "0.3.7",
19
- "openxiangda-contracts": "2.0.0-alpha.28",
19
+ "openxiangda-contracts": "2.0.0-alpha.29",
20
20
  "react": "19.2.8",
21
21
  "react-dom": "19.2.8",
22
22
  "react-router-dom": "7.8.2",
@@ -19,8 +19,11 @@ const lines = files.reduce(
19
19
  (total, file) => total + readFileSync(file, 'utf8').split(/\r?\n/).length,
20
20
  0
21
21
  );
22
- if (files.length > 18) throw new Error(`WEB_SOURCE_FILE_BUDGET_EXCEEDED:${files.length}>18`);
23
- if (lines > 4_300) throw new Error(`WEB_BUSINESS_LOC_BUDGET_EXCEEDED:${lines}>4300`);
22
+ // The shared Surface renderer is deliberately counted as application source;
23
+ // keep its small budget explicit instead of hiding it in generated output.
24
+ if (files.length > 20) throw new Error(`WEB_SOURCE_FILE_BUDGET_EXCEEDED:${files.length}>20`);
25
+ // Mobile shells, Surface filters, and generated resource CRUD are shared application infrastructure.
26
+ if (lines > 5_600) throw new Error(`WEB_BUSINESS_LOC_BUDGET_EXCEEDED:${lines}>5600`);
24
27
  const source = files.map(file => readFileSync(file, 'utf8')).join('\n');
25
28
  for (const marker of ['@umijs/', 'openxiangda-' + 'admin', 'openxiangda-' + 'user', 'instrument_query', 'instrument_save', 'function.invoke', 'role' + '-session', 'college-' + 'am', 'user-' + 'wang', 'dept-' + 'am-test']) {
26
29
  if (source.includes(marker)) throw new Error(`WEB_FORBIDDEN_MARKER:${marker}`);
@@ -1,23 +1,24 @@
1
- import {
2
- ArrowLeftOutlined,
3
- EditOutlined,
4
- HistoryOutlined,
5
- } from '@ant-design/icons';
6
- import { Button, Card, Result, Space, Spin, Tag, Typography } from 'antd';
1
+ import { Space, Tag, Typography } from 'antd';
7
2
  import { useEffect, useState } from 'react';
8
3
  import type { DataAuditEntry } from 'openxiangda-contracts/browser';
9
4
  import { useNavigate, useParams } from 'react-router-dom';
5
+ import { useOne } from '@refinedev/core';
6
+ import {
7
+ MobileResourceDetailPage,
8
+ ResourceDetailPage,
9
+ } from './components/resource/StandardResourcePages';
10
10
  import { InstrumentForm } from './InstrumentForm';
11
- import { Shell } from './Shell';
12
11
  import { INSTRUMENT_CAPABILITIES, type InstrumentRecord } from './instrument';
12
+ import { instrumentSurface } from '../../../platform/data/instrument-surface.js';
13
13
  import { instrumentDataApi } from './platform-client';
14
- import { useRuntime } from './runtime';
15
- import { useOne } from '@refinedev/core';
16
14
 
17
- export function InstrumentDetailPage() {
15
+ export function InstrumentDetailPage({ variant = 'desktop' }: { variant?: 'desktop' | 'mobile' }) {
16
+ return variant === 'mobile' ? <MobileInstrumentDetailPage /> : <DesktopInstrumentDetailPage />;
17
+ }
18
+
19
+ function DesktopInstrumentDetailPage() {
18
20
  const { id = '' } = useParams();
19
21
  const navigate = useNavigate();
20
- const { hasCapability } = useRuntime();
21
22
  const [auditEntries, setAuditEntries] = useState<DataAuditEntry[]>([]);
22
23
  const [auditError, setAuditError] = useState('');
23
24
  const query = useOne<InstrumentRecord>({
@@ -43,61 +44,19 @@ export function InstrumentDetailPage() {
43
44
  active = false;
44
45
  };
45
46
  }, [record?.id, record?.revision]);
46
- if (query.query.isLoading) {
47
- return (
48
- <Shell>
49
- <div className="oxa-page-loading">
50
- <Spin />
51
- </div>
52
- </Shell>
53
- );
54
- }
55
- if (query.query.isError || !record) {
56
- return (
57
- <Shell>
58
- <Result
59
- status="403"
60
- subTitle={query.query.error?.message || '记录不存在'}
61
- title="无法访问"
62
- />
63
- </Shell>
64
- );
65
- }
66
47
  return (
67
- <Shell>
68
- <Card className="oxa-detail-hero">
69
- <div className="oxa-detail-hero-top">
70
- <Button
71
- aria-label="返回列表"
72
- icon={<ArrowLeftOutlined />}
73
- onClick={() => navigate('/instruments')}
74
- type="link"
75
- >
76
- 返回仪器资源
77
- </Button>
78
- <Space>
79
- <Button
80
- icon={<HistoryOutlined />}
81
- onClick={() =>
82
- document
83
- .getElementById('instrument-audit')
84
- ?.scrollIntoView({ behavior: 'smooth', block: 'center' })
85
- }
86
- >
87
- 操作记录
88
- </Button>
89
- {hasCapability(INSTRUMENT_CAPABILITIES.update) && (
90
- <Button
91
- icon={<EditOutlined />}
92
- onClick={() => navigate(`/instruments/${id}/edit`)}
93
- type="primary"
94
- >
95
- 编辑
96
- </Button>
97
- )}
98
- </Space>
99
- </div>
100
- <div className="oxa-detail-hero-main">
48
+ <ResourceDetailPage
49
+ backLabel="返回仪器资源"
50
+ backPath="/instruments"
51
+ editCapability={INSTRUMENT_CAPABILITIES.update}
52
+ editPath={`/instruments/${id}/edit`}
53
+ error={
54
+ query.query.isError || (!query.query.isLoading && !record)
55
+ ? query.query.error?.message || '记录不存在'
56
+ : undefined
57
+ }
58
+ hero={
59
+ record ? (
101
60
  <div>
102
61
  <Space align="center" size={10}>
103
62
  <Typography.Title level={2}>仪器详情</Typography.Title>
@@ -112,20 +71,103 @@ export function InstrumentDetailPage() {
112
71
  {record.instrumentCode} · {record.specModel}
113
72
  </Typography.Text>
114
73
  </div>
115
- <Space className="oxa-record-meta" separator={<span>·</span>}>
74
+ ) : null
75
+ }
76
+ loading={query.query.isLoading}
77
+ meta={
78
+ record ? (
79
+ <>
116
80
  <span>最近更新 {formatDateTime(record.updated_at)}</span>
117
81
  <span>版本 {record.revision}</span>
118
- </Space>
119
- </div>
120
- </Card>
121
- <InstrumentForm
122
- auditEntries={auditEntries}
123
- auditError={auditError}
124
- mode="detail"
125
- onCancel={() => navigate('/instruments')}
126
- record={record}
127
- />
128
- </Shell>
82
+ </>
83
+ ) : null
84
+ }
85
+ readCapability={INSTRUMENT_CAPABILITIES.read}
86
+ resource="instruments"
87
+ surface={instrumentSurface}
88
+ title="仪器"
89
+ auditTargetId="instrument-audit"
90
+ >
91
+ {record && (
92
+ <InstrumentForm
93
+ auditEntries={auditEntries}
94
+ auditError={auditError}
95
+ mode="detail"
96
+ onCancel={() => navigate('/instruments')}
97
+ record={record}
98
+ />
99
+ )}
100
+ </ResourceDetailPage>
101
+ );
102
+ }
103
+
104
+ function MobileInstrumentDetailPage() {
105
+ const { id = '' } = useParams();
106
+ const navigate = useNavigate();
107
+ const [auditEntries, setAuditEntries] = useState<DataAuditEntry[]>([]);
108
+ const [auditError, setAuditError] = useState('');
109
+ const query = useOne<InstrumentRecord>({
110
+ resource: 'instruments',
111
+ id,
112
+ queryOptions: { retry: false },
113
+ });
114
+ const record = query.result;
115
+ useEffect(() => {
116
+ if (!record?.id) return;
117
+ let active = true;
118
+ setAuditError('');
119
+ void instrumentDataApi.audit(record.id).then(
120
+ page => {
121
+ if (active) setAuditEntries(page.items || []);
122
+ },
123
+ error => {
124
+ if (active) setAuditError(error instanceof Error ? error.message : String(error));
125
+ }
126
+ );
127
+ return () => {
128
+ active = false;
129
+ };
130
+ }, [record?.id, record?.revision]);
131
+ return (
132
+ <MobileResourceDetailPage
133
+ backLabel="返回仪器资源"
134
+ backPath="/m/instruments"
135
+ editCapability={INSTRUMENT_CAPABILITIES.update}
136
+ editPath={`/m/instruments/${id}/edit`}
137
+ error={query.query.isError || (!query.query.isLoading && !record) ? query.query.error?.message || '记录不存在' : undefined}
138
+ hero={
139
+ record ? (
140
+ <div>
141
+ <Space align="center" size={8}>
142
+ <Typography.Title level={3}>仪器详情</Typography.Title>
143
+ <Tag color={record.instrumentStatus === 'normal' ? 'green' : 'default'}>
144
+ {record.instrumentStatus === 'normal' ? '启用' : '非正常'}
145
+ </Tag>
146
+ </Space>
147
+ <Typography.Title className="oxa-instrument-name" level={4}>{record.chineseName}</Typography.Title>
148
+ <Typography.Text type="secondary">{record.instrumentCode} · {record.specModel}</Typography.Text>
149
+ </div>
150
+ ) : null
151
+ }
152
+ loading={query.query.isLoading}
153
+ meta={record ? <><span>最近更新 {formatDateTime(record.updated_at)}</span><span>版本 {record.revision}</span></> : null}
154
+ readCapability={INSTRUMENT_CAPABILITIES.read}
155
+ resource="instruments"
156
+ surface={instrumentSurface}
157
+ title="仪器"
158
+ auditTargetId="instrument-audit"
159
+ >
160
+ {record && (
161
+ <InstrumentForm
162
+ auditEntries={auditEntries}
163
+ auditError={auditError}
164
+ mode="detail"
165
+ onCancel={() => navigate('/m/instruments')}
166
+ record={record}
167
+ variant="mobile"
168
+ />
169
+ )}
170
+ </MobileResourceDetailPage>
129
171
  );
130
172
  }
131
173