openxiangda-cli 2.0.0-alpha.62 → 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 +4 -4
- package/template/apps/server/Dockerfile +2 -1
- package/template/apps/server/package.json +1 -1
- package/template/apps/web/package.json +1 -1
- package/template/apps/web/src/AuthoritativeSelector.tsx +262 -14
- package/template/apps/web/src/components/resource/GeneratedResourceCrud.tsx +220 -21
- package/template/apps/web/src/components/resource/SurfaceFields.tsx +112 -3
- package/template/apps/web/src/main.tsx +13 -1
- package/template/apps/web/src/platform-client.ts +55 -17
- package/template/apps/web/src/styles.css +282 -0
- package/template/apps/web/test/contracts.test.ts +17 -0
- package/template/package.json +3 -3
|
@@ -12,30 +12,62 @@ import {
|
|
|
12
12
|
type DirectoryResolveRequest,
|
|
13
13
|
} from 'openxiangda-contracts/browser';
|
|
14
14
|
import { applicationCode, runtimeMount } from './runtime-meta';
|
|
15
|
+
import { useSyncExternalStore } from 'react';
|
|
15
16
|
|
|
16
17
|
interface PlatformEnvelope<T> {
|
|
17
18
|
code: number;
|
|
18
19
|
message?: string;
|
|
19
20
|
errorCode?: string;
|
|
21
|
+
requestId?: string;
|
|
20
22
|
data: T;
|
|
21
23
|
}
|
|
22
24
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
25
|
+
let globalRequestCount = 0;
|
|
26
|
+
const globalRequestListeners = new Set<() => void>();
|
|
27
|
+
|
|
28
|
+
function beginGlobalRequest() {
|
|
29
|
+
globalRequestCount += 1;
|
|
30
|
+
globalRequestListeners.forEach(listener => listener());
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function endGlobalRequest() {
|
|
34
|
+
globalRequestCount = Math.max(0, globalRequestCount - 1);
|
|
35
|
+
globalRequestListeners.forEach(listener => listener());
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function useGlobalRequestLoading() {
|
|
39
|
+
return useSyncExternalStore(
|
|
40
|
+
listener => {
|
|
41
|
+
globalRequestListeners.add(listener);
|
|
42
|
+
return () => globalRequestListeners.delete(listener);
|
|
31
43
|
},
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
44
|
+
() => globalRequestCount > 0,
|
|
45
|
+
() => false,
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async function request<T>(path: string, init?: RequestInit): Promise<T> {
|
|
50
|
+
beginGlobalRequest();
|
|
51
|
+
try {
|
|
52
|
+
const response = await fetch(path, {
|
|
53
|
+
credentials: 'include',
|
|
54
|
+
...init,
|
|
55
|
+
headers: {
|
|
56
|
+
accept: 'application/json',
|
|
57
|
+
...(init?.body ? { 'content-type': 'application/json' } : {}),
|
|
58
|
+
...init?.headers,
|
|
59
|
+
},
|
|
60
|
+
});
|
|
61
|
+
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;
|
|
63
|
+
if (!response.ok || (envelope && envelope.code !== 200)) {
|
|
64
|
+
const requestId = envelope?.requestId ? ` (requestId: ${envelope.requestId})` : '';
|
|
65
|
+
throw new Error(`${envelope?.errorCode || `HTTP_${response.status}`}: ${envelope?.message || '平台请求失败'}${requestId}`);
|
|
66
|
+
}
|
|
67
|
+
return envelope ? envelope.data : payload as T;
|
|
68
|
+
} finally {
|
|
69
|
+
endGlobalRequest();
|
|
37
70
|
}
|
|
38
|
-
return envelope ? envelope.data : payload as T;
|
|
39
71
|
}
|
|
40
72
|
|
|
41
73
|
export interface RuntimeIdentity {
|
|
@@ -169,7 +201,11 @@ export function createNativeResourceClient(code: string, surface: DataResourceSu
|
|
|
169
201
|
}
|
|
170
202
|
for (const [fieldCode, value] of Object.entries(query.filters || {})) {
|
|
171
203
|
if (value === undefined || value === null || value === '') continue;
|
|
172
|
-
|
|
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];
|
|
173
209
|
if (Array.isArray(value) && value.length === 2 && ['date', 'datetime', 'number', 'money', 'percent'].includes(field?.widget || '')) {
|
|
174
210
|
if (value[0] !== undefined && value[0] !== null && value[0] !== '') filters.push({ field: fieldCode, operator: 'gte', value: value[0] });
|
|
175
211
|
if (value[1] !== undefined && value[1] !== null && value[1] !== '') filters.push({ field: fieldCode, operator: 'lte', value: value[1] });
|
|
@@ -183,8 +219,10 @@ export function createNativeResourceClient(code: string, surface: DataResourceSu
|
|
|
183
219
|
};
|
|
184
220
|
return {
|
|
185
221
|
async list(query: GenericResourceQuery) {
|
|
186
|
-
const
|
|
187
|
-
const
|
|
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 };
|
|
188
226
|
const page = await request<DataPage<Record<string, unknown>>>(`${base}/query`, { method: 'POST', body: JSON.stringify({ ...body, environmentKey: currentEnvironmentKey() }) });
|
|
189
227
|
return { rows: page.items, total: page.total };
|
|
190
228
|
},
|
|
@@ -24,6 +24,25 @@ body {
|
|
|
24
24
|
place-items: center;
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
+
.oxa-global-request-loading {
|
|
28
|
+
position: fixed;
|
|
29
|
+
z-index: 1200;
|
|
30
|
+
top: 14px;
|
|
31
|
+
left: 50%;
|
|
32
|
+
display: inline-flex;
|
|
33
|
+
align-items: center;
|
|
34
|
+
gap: 8px;
|
|
35
|
+
padding: 7px 13px;
|
|
36
|
+
border: 1px solid #dbe7f7;
|
|
37
|
+
border-radius: 999px;
|
|
38
|
+
background: rgba(255, 255, 255, 0.96);
|
|
39
|
+
box-shadow: 0 8px 24px rgba(31, 59, 102, 0.15);
|
|
40
|
+
color: #355070;
|
|
41
|
+
font-size: 13px;
|
|
42
|
+
pointer-events: none;
|
|
43
|
+
transform: translateX(-50%);
|
|
44
|
+
}
|
|
45
|
+
|
|
27
46
|
.oxa-app-layout {
|
|
28
47
|
min-height: 100vh;
|
|
29
48
|
background: #f3f7fd;
|
|
@@ -1075,6 +1094,61 @@ body {
|
|
|
1075
1094
|
.oxa-audit-entry {
|
|
1076
1095
|
display: grid;
|
|
1077
1096
|
gap: 4px;
|
|
1097
|
+
padding: 10px 0;
|
|
1098
|
+
border-bottom: 1px solid #edf0f5;
|
|
1099
|
+
}
|
|
1100
|
+
|
|
1101
|
+
.oxa-audit-entry:last-child {
|
|
1102
|
+
border-bottom: 0;
|
|
1103
|
+
}
|
|
1104
|
+
|
|
1105
|
+
.oxa-audit-heading,
|
|
1106
|
+
.oxa-audit-meta,
|
|
1107
|
+
.oxa-audit-change-values {
|
|
1108
|
+
display: flex;
|
|
1109
|
+
align-items: center;
|
|
1110
|
+
gap: 8px;
|
|
1111
|
+
}
|
|
1112
|
+
|
|
1113
|
+
.oxa-audit-heading {
|
|
1114
|
+
justify-content: space-between;
|
|
1115
|
+
}
|
|
1116
|
+
|
|
1117
|
+
.oxa-audit-meta {
|
|
1118
|
+
flex-wrap: wrap;
|
|
1119
|
+
color: #7b879b;
|
|
1120
|
+
font-size: 12px;
|
|
1121
|
+
}
|
|
1122
|
+
|
|
1123
|
+
.oxa-audit-meta .ant-typography {
|
|
1124
|
+
font-size: 12px;
|
|
1125
|
+
}
|
|
1126
|
+
|
|
1127
|
+
.oxa-audit-changes {
|
|
1128
|
+
display: grid;
|
|
1129
|
+
gap: 7px;
|
|
1130
|
+
margin-top: 4px;
|
|
1131
|
+
}
|
|
1132
|
+
|
|
1133
|
+
.oxa-audit-change {
|
|
1134
|
+
display: grid;
|
|
1135
|
+
gap: 3px;
|
|
1136
|
+
padding: 7px 8px;
|
|
1137
|
+
border-radius: 6px;
|
|
1138
|
+
background: #f7f9fc;
|
|
1139
|
+
}
|
|
1140
|
+
|
|
1141
|
+
.oxa-audit-change-values {
|
|
1142
|
+
min-width: 0;
|
|
1143
|
+
color: #47566f;
|
|
1144
|
+
font-size: 12px;
|
|
1145
|
+
}
|
|
1146
|
+
|
|
1147
|
+
.oxa-audit-change-values > :not(svg) {
|
|
1148
|
+
min-width: 0;
|
|
1149
|
+
overflow: hidden;
|
|
1150
|
+
text-overflow: ellipsis;
|
|
1151
|
+
white-space: nowrap;
|
|
1078
1152
|
}
|
|
1079
1153
|
|
|
1080
1154
|
.oxa-audit-entry > span {
|
|
@@ -1082,6 +1156,214 @@ body {
|
|
|
1082
1156
|
font-size: 12px;
|
|
1083
1157
|
}
|
|
1084
1158
|
|
|
1159
|
+
.oxa-mobile-reference-trigger {
|
|
1160
|
+
display: flex;
|
|
1161
|
+
width: 100%;
|
|
1162
|
+
min-height: 42px;
|
|
1163
|
+
align-items: center;
|
|
1164
|
+
justify-content: space-between;
|
|
1165
|
+
gap: 8px;
|
|
1166
|
+
padding: 9px 12px;
|
|
1167
|
+
border: 1px solid #d9dfe9;
|
|
1168
|
+
border-radius: 8px;
|
|
1169
|
+
background: #fff;
|
|
1170
|
+
color: #1f2b3d;
|
|
1171
|
+
font: inherit;
|
|
1172
|
+
text-align: left;
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1175
|
+
.oxa-mobile-reference-trigger:disabled {
|
|
1176
|
+
background: #f5f7fa;
|
|
1177
|
+
color: #9aa5b5;
|
|
1178
|
+
}
|
|
1179
|
+
|
|
1180
|
+
.oxa-mobile-reference-placeholder {
|
|
1181
|
+
color: #9aa5b5;
|
|
1182
|
+
}
|
|
1183
|
+
|
|
1184
|
+
.oxa-mobile-reference-backdrop {
|
|
1185
|
+
position: fixed;
|
|
1186
|
+
z-index: 1200;
|
|
1187
|
+
inset: 0;
|
|
1188
|
+
display: flex;
|
|
1189
|
+
align-items: flex-end;
|
|
1190
|
+
background: rgb(15 23 42 / 42%);
|
|
1191
|
+
}
|
|
1192
|
+
|
|
1193
|
+
.oxa-mobile-reference-sheet {
|
|
1194
|
+
display: grid;
|
|
1195
|
+
width: 100%;
|
|
1196
|
+
max-height: min(78vh, 640px);
|
|
1197
|
+
gap: 12px;
|
|
1198
|
+
padding: 16px 16px max(16px, env(safe-area-inset-bottom));
|
|
1199
|
+
overflow: auto;
|
|
1200
|
+
border-radius: 16px 16px 0 0;
|
|
1201
|
+
background: #fff;
|
|
1202
|
+
box-shadow: 0 -8px 32px rgb(15 23 42 / 18%);
|
|
1203
|
+
}
|
|
1204
|
+
|
|
1205
|
+
.oxa-mobile-reference-header {
|
|
1206
|
+
display: flex;
|
|
1207
|
+
align-items: center;
|
|
1208
|
+
justify-content: space-between;
|
|
1209
|
+
color: #1f2b3d;
|
|
1210
|
+
}
|
|
1211
|
+
|
|
1212
|
+
.oxa-mobile-reference-header button,
|
|
1213
|
+
.oxa-mobile-reference-more,
|
|
1214
|
+
.oxa-mobile-reference-confirm {
|
|
1215
|
+
border: 0;
|
|
1216
|
+
background: transparent;
|
|
1217
|
+
color: #2d6cdf;
|
|
1218
|
+
font: inherit;
|
|
1219
|
+
}
|
|
1220
|
+
|
|
1221
|
+
.oxa-mobile-reference-search {
|
|
1222
|
+
width: 100%;
|
|
1223
|
+
}
|
|
1224
|
+
|
|
1225
|
+
.oxa-mobile-reference-options {
|
|
1226
|
+
display: grid;
|
|
1227
|
+
gap: 4px;
|
|
1228
|
+
}
|
|
1229
|
+
|
|
1230
|
+
.oxa-mobile-reference-option {
|
|
1231
|
+
display: flex;
|
|
1232
|
+
min-height: 48px;
|
|
1233
|
+
align-items: center;
|
|
1234
|
+
gap: 10px;
|
|
1235
|
+
padding: 8px 4px;
|
|
1236
|
+
border: 0;
|
|
1237
|
+
border-radius: 8px;
|
|
1238
|
+
background: transparent;
|
|
1239
|
+
color: #1f2b3d;
|
|
1240
|
+
font: inherit;
|
|
1241
|
+
text-align: left;
|
|
1242
|
+
}
|
|
1243
|
+
|
|
1244
|
+
.oxa-mobile-reference-option.is-selected {
|
|
1245
|
+
background: #edf4ff;
|
|
1246
|
+
}
|
|
1247
|
+
|
|
1248
|
+
.oxa-mobile-reference-option:disabled {
|
|
1249
|
+
color: #aab3c0;
|
|
1250
|
+
opacity: 0.7;
|
|
1251
|
+
}
|
|
1252
|
+
|
|
1253
|
+
.oxa-mobile-reference-option-copy {
|
|
1254
|
+
display: grid;
|
|
1255
|
+
min-width: 0;
|
|
1256
|
+
flex: 1;
|
|
1257
|
+
gap: 2px;
|
|
1258
|
+
}
|
|
1259
|
+
|
|
1260
|
+
.oxa-mobile-reference-option-copy small {
|
|
1261
|
+
overflow: hidden;
|
|
1262
|
+
color: #7b879b;
|
|
1263
|
+
font-size: 12px;
|
|
1264
|
+
text-overflow: ellipsis;
|
|
1265
|
+
white-space: nowrap;
|
|
1266
|
+
}
|
|
1267
|
+
|
|
1268
|
+
.oxa-mobile-reference-loading,
|
|
1269
|
+
.oxa-mobile-reference-empty,
|
|
1270
|
+
.oxa-mobile-reference-error {
|
|
1271
|
+
padding: 12px 4px;
|
|
1272
|
+
color: #7b879b;
|
|
1273
|
+
font-size: 13px;
|
|
1274
|
+
text-align: center;
|
|
1275
|
+
}
|
|
1276
|
+
|
|
1277
|
+
.oxa-mobile-reference-error {
|
|
1278
|
+
color: #cf3c46;
|
|
1279
|
+
}
|
|
1280
|
+
|
|
1281
|
+
.oxa-mobile-reference-more,
|
|
1282
|
+
.oxa-mobile-reference-confirm {
|
|
1283
|
+
min-height: 42px;
|
|
1284
|
+
border-radius: 8px;
|
|
1285
|
+
background: #f0f5ff;
|
|
1286
|
+
font-weight: 600;
|
|
1287
|
+
text-align: center;
|
|
1288
|
+
}
|
|
1289
|
+
|
|
1290
|
+
.oxa-mobile-reference-confirm {
|
|
1291
|
+
background: #2d6cdf;
|
|
1292
|
+
color: #fff;
|
|
1293
|
+
}
|
|
1294
|
+
|
|
1295
|
+
.oxa-mobile-file-field {
|
|
1296
|
+
display: grid;
|
|
1297
|
+
gap: 8px;
|
|
1298
|
+
}
|
|
1299
|
+
|
|
1300
|
+
.oxa-mobile-file-button {
|
|
1301
|
+
display: inline-flex !important;
|
|
1302
|
+
width: 100%;
|
|
1303
|
+
min-height: 42px;
|
|
1304
|
+
align-items: center;
|
|
1305
|
+
justify-content: center;
|
|
1306
|
+
border: 1px solid #d9dfe9 !important;
|
|
1307
|
+
border-radius: 8px;
|
|
1308
|
+
background: #fff !important;
|
|
1309
|
+
color: #2d6cdf;
|
|
1310
|
+
font-weight: 600;
|
|
1311
|
+
}
|
|
1312
|
+
|
|
1313
|
+
.oxa-mobile-file-button.ant-btn:disabled {
|
|
1314
|
+
background: #f5f7fa;
|
|
1315
|
+
color: #9aa5b5;
|
|
1316
|
+
}
|
|
1317
|
+
|
|
1318
|
+
.oxa-mobile-pending-files {
|
|
1319
|
+
display: grid;
|
|
1320
|
+
gap: 8px;
|
|
1321
|
+
}
|
|
1322
|
+
|
|
1323
|
+
.oxa-mobile-pending-file {
|
|
1324
|
+
display: flex;
|
|
1325
|
+
min-height: 42px;
|
|
1326
|
+
align-items: center;
|
|
1327
|
+
gap: 8px;
|
|
1328
|
+
padding: 7px 9px;
|
|
1329
|
+
border: 1px solid #dbe3ee;
|
|
1330
|
+
border-radius: 8px;
|
|
1331
|
+
background: #fbfcfe;
|
|
1332
|
+
font-size: 13px;
|
|
1333
|
+
}
|
|
1334
|
+
|
|
1335
|
+
.oxa-mobile-pending-file-name {
|
|
1336
|
+
min-width: 0;
|
|
1337
|
+
overflow: hidden;
|
|
1338
|
+
text-overflow: ellipsis;
|
|
1339
|
+
white-space: nowrap;
|
|
1340
|
+
}
|
|
1341
|
+
|
|
1342
|
+
.oxa-mobile-pending-file-copy {
|
|
1343
|
+
display: grid;
|
|
1344
|
+
min-width: 0;
|
|
1345
|
+
flex: 1;
|
|
1346
|
+
gap: 2px;
|
|
1347
|
+
}
|
|
1348
|
+
|
|
1349
|
+
.oxa-mobile-pending-file-copy small {
|
|
1350
|
+
color: #7b879b;
|
|
1351
|
+
font-size: 12px;
|
|
1352
|
+
}
|
|
1353
|
+
|
|
1354
|
+
.oxa-mobile-pending-status {
|
|
1355
|
+
color: #2d6cdf;
|
|
1356
|
+
white-space: nowrap;
|
|
1357
|
+
}
|
|
1358
|
+
|
|
1359
|
+
.oxa-mobile-pending-error {
|
|
1360
|
+
max-width: 42%;
|
|
1361
|
+
overflow: hidden;
|
|
1362
|
+
color: #cf3c46;
|
|
1363
|
+
text-overflow: ellipsis;
|
|
1364
|
+
white-space: nowrap;
|
|
1365
|
+
}
|
|
1366
|
+
|
|
1085
1367
|
@media (max-width: 1280px) {
|
|
1086
1368
|
.oxa-grid,
|
|
1087
1369
|
.oxa-filter-grid {
|
|
@@ -20,9 +20,26 @@ test('standard surfaces keep authoritative directory/resource selectors and mobi
|
|
|
20
20
|
const crud = readFileSync(new URL('../src/components/resource/GeneratedResourceCrud.tsx', import.meta.url), 'utf8');
|
|
21
21
|
assert.match(surface, /PlatformDirectoryPicker/);
|
|
22
22
|
assert.match(surface, /source="resource"/);
|
|
23
|
+
assert.match(surface, /MobileAuthoritativeSelector/);
|
|
24
|
+
assert.match(surface, /oxa-mobile-file-field/);
|
|
25
|
+
assert.match(surface, /mobilePending/);
|
|
26
|
+
assert.match(surface, /上传中/);
|
|
27
|
+
assert.match(surface, /重试/);
|
|
28
|
+
assert.match(surface, /AttachmentFileList files=\{refs\}/);
|
|
23
29
|
assert.match(selector, /searchResource|resolveResource/);
|
|
30
|
+
assert.match(selector, /role="dialog"/);
|
|
31
|
+
assert.match(selector, /oxa-mobile-reference-sheet/);
|
|
24
32
|
assert.match(crud, /MobileResourceListPage/);
|
|
25
33
|
assert.match(crud, /fieldWritable/);
|
|
34
|
+
assert.match(crud, /exportResourceRows/);
|
|
35
|
+
assert.match(crud, /text\/csv;charset=utf-8/);
|
|
36
|
+
assert.match(crud, /数据读取失败/);
|
|
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/);
|
|
26
43
|
});
|
|
27
44
|
|
|
28
45
|
test('runtime metadata stays mount-scoped', () => {
|
package/template/package.json
CHANGED
|
@@ -21,9 +21,9 @@
|
|
|
21
21
|
"build": "pnpm --recursive build"
|
|
22
22
|
},
|
|
23
23
|
"devDependencies": {
|
|
24
|
-
"openxiangda-cli": "2.0.0-alpha.
|
|
25
|
-
"openxiangda-contracts": "2.0.0-alpha.
|
|
26
|
-
"openxiangda-devkit-core": "2.0.0-alpha.
|
|
24
|
+
"openxiangda-cli": "2.0.0-alpha.67",
|
|
25
|
+
"openxiangda-contracts": "2.0.0-alpha.31",
|
|
26
|
+
"openxiangda-devkit-core": "2.0.0-alpha.41",
|
|
27
27
|
"typescript": "5.9.3"
|
|
28
28
|
}
|
|
29
29
|
}
|