@wordrhyme/auto-crud 1.3.8 → 1.4.0
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/README.md +29 -5
- package/dist/index.cjs +294 -52
- package/dist/index.d.cts +47 -19
- package/dist/index.d.ts +47 -19
- package/dist/index.js +294 -52
- package/package.json +5 -3
package/README.md
CHANGED
|
@@ -546,11 +546,11 @@ table={{
|
|
|
546
546
|
|
|
547
547
|
`toolbar` 数组控制页面顶部右侧工具栏的按钮,设计思路完全对齐行操作 `actions`。旧 prop `toolbarActions` 仍兼容读取,但新代码请使用 `toolbar`。
|
|
548
548
|
|
|
549
|
-
内置操作类型:`create`(新建)、`import`(导入)、`export`(导出)。
|
|
549
|
+
内置操作类型:`refresh`(刷新)、`create`(新建)、`import`(导入)、`export`(导出)。
|
|
550
550
|
|
|
551
551
|
### 只添加自定义按钮(内置保持默认)
|
|
552
552
|
|
|
553
|
-
只传 `type: "custom"`
|
|
553
|
+
只传 `type: "custom"` 项时,所有内置按钮(刷新、导入、导出、新建)保持原样,custom 项按 `position` 插入首部或尾部。
|
|
554
554
|
|
|
555
555
|
```tsx
|
|
556
556
|
<AutoCrudTable
|
|
@@ -563,7 +563,7 @@ table={{
|
|
|
563
563
|
{ type: 'custom', component: <Button variant="outline">批量标签</Button> }, // 默认 position: "end"
|
|
564
564
|
]}
|
|
565
565
|
/>
|
|
566
|
-
// 渲染顺序: 分类管理 · [导入] · [导出] · [新建] · 批量标签
|
|
566
|
+
// 渲染顺序: 分类管理 · [刷新] · [导入] · [导出] · [新建] · 批量标签
|
|
567
567
|
```
|
|
568
568
|
|
|
569
569
|
### 调整内置按钮顺序
|
|
@@ -669,7 +669,7 @@ table={{
|
|
|
669
669
|
|
|
670
670
|
```tsx
|
|
671
671
|
<AutoCrudTable
|
|
672
|
-
// defaults
|
|
672
|
+
// defaults 即内置按钮的信息,在此数组里你可以随意 map、过滤或插值
|
|
673
673
|
toolbar={(defaults) =>
|
|
674
674
|
defaults.map((btn) =>
|
|
675
675
|
btn.type === 'create'
|
|
@@ -697,7 +697,7 @@ type ToolbarActionConfig =
|
|
|
697
697
|
| ((defaults: ToolbarBuiltinActionItem[]) => ToolbarActionItem[]);
|
|
698
698
|
|
|
699
699
|
type ToolbarBuiltinActionItem = ActionMeta & {
|
|
700
|
-
type: 'create' | 'import' | 'export';
|
|
700
|
+
type: 'refresh' | 'create' | 'import' | 'export';
|
|
701
701
|
onClick?: () => void; // 覆盖默认行为
|
|
702
702
|
label?: string; // 覆盖默认文案
|
|
703
703
|
component?: React.ReactNode | ((context: AutoCrudToolbarContext) => React.ReactNode);
|
|
@@ -715,10 +715,34 @@ interface AutoCrudToolbarContext {
|
|
|
715
715
|
rowIds: string[];
|
|
716
716
|
selectedRowIds: string[];
|
|
717
717
|
selectedCount: number;
|
|
718
|
+
refresh?: () => Promise<unknown>;
|
|
719
|
+
openImport?: () => void;
|
|
720
|
+
exportData?: () => Promise<void>;
|
|
718
721
|
openCreate?: () => void;
|
|
722
|
+
isRefreshing: boolean;
|
|
723
|
+
isExporting: boolean;
|
|
719
724
|
}
|
|
720
725
|
```
|
|
721
726
|
|
|
727
|
+
`AutoCrudToolbarContext` 是工具栏自定义组件的正式 command contract。自定义按钮需要复用 AutoCrud 内置能力时,直接调用 context 中的命令,不要绕过内部状态重新实现。
|
|
728
|
+
|
|
729
|
+
例如业务侧希望把默认“新建”按钮替换成“手动创建”,可以替换 `type: "create"` 的组件并调用 `openCreate`:
|
|
730
|
+
|
|
731
|
+
```tsx
|
|
732
|
+
<AutoCrudTable
|
|
733
|
+
toolbar={[
|
|
734
|
+
{
|
|
735
|
+
type: 'create',
|
|
736
|
+
component: ({ openCreate }) => (
|
|
737
|
+
<Button onClick={() => openCreate?.()}>手动创建</Button>
|
|
738
|
+
),
|
|
739
|
+
},
|
|
740
|
+
]}
|
|
741
|
+
/>
|
|
742
|
+
```
|
|
743
|
+
|
|
744
|
+
这里“手动创建”只是业务按钮文案,不是新的 AutoCrud 流程;它仍然打开 AutoCrud 原生的新建弹窗。`openCreate`、`openImport`、`exportData` 会按对应权限和能力可选暴露,扩展方应使用 `?.()` 或自行控制 disabled 状态。
|
|
745
|
+
|
|
722
746
|
### 工具栏扩展 Resolver
|
|
723
747
|
|
|
724
748
|
宿主应用可以通过 `setToolbarResolver` 为指定 CRUD 注入额外工具栏动作。resolver 是普通纯函数,不是 React Hook;不要在 resolver 内调用 `useRouter`、`useMemo` 等 React Hooks。
|
package/dist/index.cjs
CHANGED
|
@@ -4365,6 +4365,7 @@ function normalizeDataSourceRegistration(registration) {
|
|
|
4365
4365
|
debounceMs: typeof registration.debounceMs === "number" && registration.debounceMs >= 0 ? registration.debounceMs : 300,
|
|
4366
4366
|
loadMore: registration.loadMore === true,
|
|
4367
4367
|
pageSize: typeof registration.pageSize === "number" && registration.pageSize > 0 ? registration.pageSize : 20,
|
|
4368
|
+
resolveFields: registration.resolveFields,
|
|
4368
4369
|
load: registration.load
|
|
4369
4370
|
};
|
|
4370
4371
|
}
|
|
@@ -4599,8 +4600,10 @@ function loadAutoCrudDataSource({ entry, field, registryVersion, search, sourceK
|
|
|
4599
4600
|
}
|
|
4600
4601
|
Promise.resolve(entry.load({
|
|
4601
4602
|
field: field.path.toString(),
|
|
4603
|
+
type: "form",
|
|
4602
4604
|
page,
|
|
4603
4605
|
pageSize: entry.loadMore ? entry.pageSize : void 0,
|
|
4606
|
+
query: searchValue,
|
|
4604
4607
|
search: searchValue,
|
|
4605
4608
|
values,
|
|
4606
4609
|
signal: controller?.signal
|
|
@@ -5889,8 +5892,8 @@ function haveSameRowSelection(left, right, idKey) {
|
|
|
5889
5892
|
if (leftIds.length === left.length && rightIds.length === right.length) return areStringArraysEqual(leftIds, rightIds);
|
|
5890
5893
|
return left.every((row, index$1) => row === right[index$1]);
|
|
5891
5894
|
}
|
|
5892
|
-
function
|
|
5893
|
-
return actions.find((action$1) => action$1.type ===
|
|
5895
|
+
function readToolbarAction(actions, type) {
|
|
5896
|
+
return actions.find((action$1) => action$1.type === type);
|
|
5894
5897
|
}
|
|
5895
5898
|
function resolveToolbarActionsWithResolver(targetId, ownerActions, context) {
|
|
5896
5899
|
const resolver = autoCrudToolbarResolver;
|
|
@@ -5964,6 +5967,47 @@ function shouldLoadDynamicFilterOptions(config) {
|
|
|
5964
5967
|
}
|
|
5965
5968
|
return normalizeDataSourceConfig(getFilterDataSource(config)) !== void 0;
|
|
5966
5969
|
}
|
|
5970
|
+
function isResolveValuePresent(value) {
|
|
5971
|
+
return value !== null && value !== void 0 && String(value).length > 0;
|
|
5972
|
+
}
|
|
5973
|
+
function getResolveValues(rows, field) {
|
|
5974
|
+
const values = /* @__PURE__ */ new Set();
|
|
5975
|
+
for (const row of rows) {
|
|
5976
|
+
const value = row[field];
|
|
5977
|
+
const items = Array.isArray(value) ? value : [value];
|
|
5978
|
+
for (const item of items) if (isResolveValuePresent(item)) values.add(String(item));
|
|
5979
|
+
}
|
|
5980
|
+
return Array.from(values);
|
|
5981
|
+
}
|
|
5982
|
+
function normalizeResolveCacheValue(value) {
|
|
5983
|
+
if (value instanceof Date) return value.toISOString();
|
|
5984
|
+
if (Array.isArray(value)) return value.map(normalizeResolveCacheValue);
|
|
5985
|
+
if (value && typeof value === "object") return Object.fromEntries(Object.keys(value).sort().map((key) => [key, normalizeResolveCacheValue(value[key])]));
|
|
5986
|
+
return value === void 0 ? { __autoCrudUndefined: true } : value;
|
|
5987
|
+
}
|
|
5988
|
+
function resolveRecordSignature(record) {
|
|
5989
|
+
return JSON.stringify(Object.keys(record).sort().map((key) => [key, normalizeResolveCacheValue(record[key])]));
|
|
5990
|
+
}
|
|
5991
|
+
function resolveRecordCacheKey(sourceKey, field, record) {
|
|
5992
|
+
return `${sourceKey}\u0000${field}\u0000${String(record[field])}\u0000${resolveRecordSignature(record)}`;
|
|
5993
|
+
}
|
|
5994
|
+
function mergeFieldOptions(current, incoming) {
|
|
5995
|
+
if ((!current || current.length === 0) && (!incoming || incoming.length === 0)) return;
|
|
5996
|
+
return mergeFilterOptions(current, incoming ?? []);
|
|
5997
|
+
}
|
|
5998
|
+
function mergeOptionsByField(left, right) {
|
|
5999
|
+
const keys = new Set([...Object.keys(left ?? {}), ...Object.keys(right ?? {})]);
|
|
6000
|
+
return Object.fromEntries(Array.from(keys).flatMap((key) => {
|
|
6001
|
+
const options = mergeFieldOptions(left?.[key], right?.[key]);
|
|
6002
|
+
return options ? [[key, options]] : [];
|
|
6003
|
+
}));
|
|
6004
|
+
}
|
|
6005
|
+
function shouldResolveOptions(field, config, hiddenColumns) {
|
|
6006
|
+
if (!config) return false;
|
|
6007
|
+
if (hiddenColumns.has(field)) return false;
|
|
6008
|
+
if (normalizeFieldOptions(config.enum)) return false;
|
|
6009
|
+
return normalizeDataSourceConfig(config.dataSource) !== void 0;
|
|
6010
|
+
}
|
|
5967
6011
|
function useRegistryVersion(subscribe) {
|
|
5968
6012
|
const [version$1, setVersion] = react.useState(0);
|
|
5969
6013
|
react.useEffect(() => subscribe(() => setVersion((current) => current + 1)), [subscribe]);
|
|
@@ -6085,8 +6129,10 @@ function useDynamicFilterOptions(fields) {
|
|
|
6085
6129
|
const timer = setTimeout(() => {
|
|
6086
6130
|
Promise.resolve(entry.load({
|
|
6087
6131
|
field,
|
|
6132
|
+
type: "filter",
|
|
6088
6133
|
page,
|
|
6089
6134
|
pageSize: entry.loadMore ? entry.pageSize : void 0,
|
|
6135
|
+
query: search,
|
|
6090
6136
|
search,
|
|
6091
6137
|
values: {},
|
|
6092
6138
|
signal: controller?.signal
|
|
@@ -6179,6 +6225,117 @@ function useDynamicFilterOptions(fields) {
|
|
|
6179
6225
|
setSearchValue
|
|
6180
6226
|
};
|
|
6181
6227
|
}
|
|
6228
|
+
function rowHasResolveValue(row, field, value) {
|
|
6229
|
+
const rowValue = row[field];
|
|
6230
|
+
return (Array.isArray(rowValue) ? rowValue : [rowValue]).some((item) => item !== null && item !== void 0 && String(item) === value);
|
|
6231
|
+
}
|
|
6232
|
+
function resolveExtraFields(entry, field) {
|
|
6233
|
+
const configured = typeof entry.resolveFields === "function" ? entry.resolveFields({ field }) : entry.resolveFields;
|
|
6234
|
+
return Array.from(new Set((configured ?? []).filter((key) => typeof key === "string" && key.length > 0)));
|
|
6235
|
+
}
|
|
6236
|
+
function buildResolveRecords(rows, field, values, extraFields) {
|
|
6237
|
+
return values.map((value) => {
|
|
6238
|
+
const row = rows.find((candidate) => rowHasResolveValue(candidate, field, value));
|
|
6239
|
+
const record = { [field]: value };
|
|
6240
|
+
for (const extraField of extraFields) if (row && Object.prototype.hasOwnProperty.call(row, extraField)) record[extraField] = row[extraField];
|
|
6241
|
+
return record;
|
|
6242
|
+
});
|
|
6243
|
+
}
|
|
6244
|
+
function useDynamicResolveOptions(fields, rows, hiddenColumns) {
|
|
6245
|
+
const registryVersion = useRegistryVersion(dataSources.subscribe);
|
|
6246
|
+
const [optionsByField, setOptionsByField] = react.useState({});
|
|
6247
|
+
const cacheRef = react.useRef(/* @__PURE__ */ new Map());
|
|
6248
|
+
const requestVersionRef = react.useRef(0);
|
|
6249
|
+
const sourceEntries = react.useMemo(() => {
|
|
6250
|
+
if (!fields || rows.length === 0) return [];
|
|
6251
|
+
const hiddenSet = new Set(hiddenColumns);
|
|
6252
|
+
return Object.entries(fields).flatMap(([field, config]) => {
|
|
6253
|
+
if (!shouldResolveOptions(field, config, hiddenSet)) return [];
|
|
6254
|
+
const source = normalizeDataSourceConfig(config.dataSource);
|
|
6255
|
+
const values = getResolveValues(rows, field);
|
|
6256
|
+
return source && values.length > 0 ? [{
|
|
6257
|
+
field,
|
|
6258
|
+
values,
|
|
6259
|
+
source
|
|
6260
|
+
}] : [];
|
|
6261
|
+
});
|
|
6262
|
+
}, [
|
|
6263
|
+
fields,
|
|
6264
|
+
hiddenColumns,
|
|
6265
|
+
rows
|
|
6266
|
+
]);
|
|
6267
|
+
react.useEffect(() => {
|
|
6268
|
+
const requestVersion = requestVersionRef.current + 1;
|
|
6269
|
+
requestVersionRef.current = requestVersion;
|
|
6270
|
+
if (sourceEntries.length === 0) {
|
|
6271
|
+
setOptionsByField({});
|
|
6272
|
+
return;
|
|
6273
|
+
}
|
|
6274
|
+
let cancelled = false;
|
|
6275
|
+
const activeFields = new Set(sourceEntries.map(({ field }) => field));
|
|
6276
|
+
const readCachedOptions = (entry, records) => mergeFilterOptions(void 0, records.flatMap((record) => {
|
|
6277
|
+
const option = cacheRef.current.get(resolveRecordCacheKey(entry.source.key, entry.field, record));
|
|
6278
|
+
return option ? [option] : [];
|
|
6279
|
+
}));
|
|
6280
|
+
const recordsByField = new Map(sourceEntries.flatMap((sourceEntry) => {
|
|
6281
|
+
const entry = dataSources.get(sourceEntry.source.key);
|
|
6282
|
+
if (!entry) return [];
|
|
6283
|
+
return [[sourceEntry.field, buildResolveRecords(rows, sourceEntry.field, sourceEntry.values, resolveExtraFields(entry, sourceEntry.field))]];
|
|
6284
|
+
}));
|
|
6285
|
+
const readSourceEntryCachedOptions = (entry) => {
|
|
6286
|
+
return readCachedOptions(entry, recordsByField.get(entry.field) ?? []);
|
|
6287
|
+
};
|
|
6288
|
+
const findOptionForRecord = (options, field, record) => {
|
|
6289
|
+
const value = String(record[field]);
|
|
6290
|
+
return options.find((option) => option.value === value);
|
|
6291
|
+
};
|
|
6292
|
+
const missingRecordsFor = (entry, records) => records.filter((record) => !cacheRef.current.has(resolveRecordCacheKey(entry.source.key, entry.field, record)));
|
|
6293
|
+
setOptionsByField((current) => {
|
|
6294
|
+
const next = {};
|
|
6295
|
+
for (const entry of sourceEntries) {
|
|
6296
|
+
const cached = readSourceEntryCachedOptions(entry);
|
|
6297
|
+
if (cached.length > 0) next[entry.field] = cached;
|
|
6298
|
+
}
|
|
6299
|
+
for (const [field, options] of Object.entries(current)) if (activeFields.has(field) && !next[field]) next[field] = options;
|
|
6300
|
+
return next;
|
|
6301
|
+
});
|
|
6302
|
+
for (const sourceEntry of sourceEntries) {
|
|
6303
|
+
const entry = dataSources.get(sourceEntry.source.key);
|
|
6304
|
+
if (!entry) continue;
|
|
6305
|
+
const missingRecords = missingRecordsFor(sourceEntry, recordsByField.get(sourceEntry.field) ?? []);
|
|
6306
|
+
if (missingRecords.length === 0) continue;
|
|
6307
|
+
Promise.resolve(entry.load({
|
|
6308
|
+
field: sourceEntry.field,
|
|
6309
|
+
type: "resolve",
|
|
6310
|
+
values: missingRecords
|
|
6311
|
+
})).then((result) => {
|
|
6312
|
+
if (cancelled || requestVersionRef.current !== requestVersion) return;
|
|
6313
|
+
const options = normalizeOptions(result);
|
|
6314
|
+
for (const record of missingRecords) {
|
|
6315
|
+
const option = findOptionForRecord(options, sourceEntry.field, record);
|
|
6316
|
+
if (!option) continue;
|
|
6317
|
+
cacheRef.current.set(resolveRecordCacheKey(sourceEntry.source.key, sourceEntry.field, record), option);
|
|
6318
|
+
}
|
|
6319
|
+
const cached = readSourceEntryCachedOptions(sourceEntry);
|
|
6320
|
+
setOptionsByField((current) => ({
|
|
6321
|
+
...current,
|
|
6322
|
+
[sourceEntry.field]: cached
|
|
6323
|
+
}));
|
|
6324
|
+
}, (error) => {
|
|
6325
|
+
if (cancelled || requestVersionRef.current !== requestVersion) return;
|
|
6326
|
+
console.warn(`[AutoCrud] Failed to resolve data source "${sourceEntry.source.key}".`, error);
|
|
6327
|
+
});
|
|
6328
|
+
}
|
|
6329
|
+
return () => {
|
|
6330
|
+
cancelled = true;
|
|
6331
|
+
};
|
|
6332
|
+
}, [
|
|
6333
|
+
registryVersion,
|
|
6334
|
+
rows,
|
|
6335
|
+
sourceEntries
|
|
6336
|
+
]);
|
|
6337
|
+
return { optionsByField };
|
|
6338
|
+
}
|
|
6182
6339
|
function getOptionLabel(value, options) {
|
|
6183
6340
|
const stringValue = String(value);
|
|
6184
6341
|
return options?.find((option) => option.value === stringValue)?.label ?? stringValue;
|
|
@@ -6187,7 +6344,7 @@ function getOptionLabel(value, options) {
|
|
|
6187
6344
|
* 从统一配置生成表格 overrides
|
|
6188
6345
|
* 当 filter 配置存在时,合并到 meta 中(不影响列隐藏)
|
|
6189
6346
|
*/
|
|
6190
|
-
function buildTableOverrides(fields, legacyOverrides, dynamicFilterState) {
|
|
6347
|
+
function buildTableOverrides(fields, legacyOverrides, dynamicFilterState, dynamicResolveState) {
|
|
6191
6348
|
const result = { ...legacyOverrides };
|
|
6192
6349
|
if (fields) for (const [key, config] of Object.entries(fields)) {
|
|
6193
6350
|
const fieldOptions = normalizeFieldOptions(config.enum);
|
|
@@ -6195,7 +6352,8 @@ function buildTableOverrides(fields, legacyOverrides, dynamicFilterState) {
|
|
|
6195
6352
|
const filterDataSourceRegistered = dynamicFilterState?.registeredByField[key];
|
|
6196
6353
|
const filterDataSourceSearchable = dynamicFilterState?.searchableByField[key];
|
|
6197
6354
|
const currentDynamicOptions = !fieldOptions && filterDataSourceRegistered ? toTableOptions(dynamicFilterState?.optionsByField[key] ?? []) : void 0;
|
|
6198
|
-
const
|
|
6355
|
+
const mergedDynamicOptions = mergeFieldOptions(!fieldOptions && filterDataSourceRegistered ? dynamicFilterState?.labelOptionsByField[key] ?? dynamicFilterState?.optionsByField[key] ?? [] : void 0, !fieldOptions ? dynamicResolveState?.optionsByField[key] : void 0);
|
|
6356
|
+
const dynamicOptions = !fieldOptions && mergedDynamicOptions ? toTableOptions(mergedDynamicOptions) : void 0;
|
|
6199
6357
|
const tableMeta = config.table !== false && typeof config.table === "object" ? config.table.meta : void 0;
|
|
6200
6358
|
const fieldEnumMeta = tableOptions ? {
|
|
6201
6359
|
options: tableOptions,
|
|
@@ -6203,18 +6361,20 @@ function buildTableOverrides(fields, legacyOverrides, dynamicFilterState) {
|
|
|
6203
6361
|
} : void 0;
|
|
6204
6362
|
const fieldDataSourceMeta = dynamicOptions ? {
|
|
6205
6363
|
options: dynamicOptions,
|
|
6206
|
-
|
|
6207
|
-
|
|
6208
|
-
|
|
6209
|
-
|
|
6210
|
-
|
|
6211
|
-
|
|
6212
|
-
|
|
6213
|
-
|
|
6214
|
-
|
|
6215
|
-
|
|
6216
|
-
|
|
6217
|
-
|
|
6364
|
+
...filterDataSourceRegistered ? {
|
|
6365
|
+
autoCrudFilterOptions: currentDynamicOptions ?? dynamicOptions,
|
|
6366
|
+
autoCrudFilterHasMore: dynamicFilterState?.hasMoreByField[key] ?? false,
|
|
6367
|
+
autoCrudFilterLoading: dynamicFilterState?.loadingByField[key] ?? false,
|
|
6368
|
+
autoCrudFilterOnPopupScroll: (event) => {
|
|
6369
|
+
if (!isNearPopupScrollBottom(event.currentTarget)) return;
|
|
6370
|
+
dynamicFilterState?.loadMore(key);
|
|
6371
|
+
},
|
|
6372
|
+
variant: "multiSelect",
|
|
6373
|
+
...filterDataSourceSearchable ? {
|
|
6374
|
+
autoCrudFilterSearchValue: dynamicFilterState?.searchValues[key] ?? "",
|
|
6375
|
+
autoCrudFilterOnSearch: (value) => dynamicFilterState?.setSearchValue(key, value),
|
|
6376
|
+
autoCrudFilterShouldFilter: false
|
|
6377
|
+
} : void 0
|
|
6218
6378
|
} : void 0
|
|
6219
6379
|
} : void 0;
|
|
6220
6380
|
let filterMeta;
|
|
@@ -6266,6 +6426,35 @@ function buildTableOverrides(fields, legacyOverrides, dynamicFilterState) {
|
|
|
6266
6426
|
}
|
|
6267
6427
|
return result;
|
|
6268
6428
|
}
|
|
6429
|
+
function buildCapabilityTableOverrides(schema, capabilities) {
|
|
6430
|
+
if (!capabilities) return {};
|
|
6431
|
+
const result = {};
|
|
6432
|
+
const filterFields = capabilities.filters?.fields;
|
|
6433
|
+
const sortFields = capabilities.sort?.fields;
|
|
6434
|
+
const filterSet = Array.isArray(filterFields) ? new Set(filterFields) : null;
|
|
6435
|
+
const sortSet = Array.isArray(sortFields) ? new Set(sortFields) : null;
|
|
6436
|
+
for (const key of Object.keys(schema.shape)) {
|
|
6437
|
+
const override = {};
|
|
6438
|
+
if (capabilities.filters) {
|
|
6439
|
+
if (!capabilities.filters.enabled) override.enableColumnFilter = false;
|
|
6440
|
+
else if (filterSet) override.enableColumnFilter = filterSet.has(key);
|
|
6441
|
+
}
|
|
6442
|
+
if (capabilities.sort) {
|
|
6443
|
+
if (!capabilities.sort.enabled) override.enableSorting = false;
|
|
6444
|
+
else if (sortSet) override.enableSorting = sortSet.has(key);
|
|
6445
|
+
}
|
|
6446
|
+
if (Object.keys(override).length > 0) result[key] = override;
|
|
6447
|
+
}
|
|
6448
|
+
return result;
|
|
6449
|
+
}
|
|
6450
|
+
function mergeTableOverrides(base, enforced) {
|
|
6451
|
+
const result = { ...base ?? {} };
|
|
6452
|
+
for (const [key, override] of Object.entries(enforced)) result[key] = {
|
|
6453
|
+
...result[key] ?? {},
|
|
6454
|
+
...override
|
|
6455
|
+
};
|
|
6456
|
+
return result;
|
|
6457
|
+
}
|
|
6269
6458
|
/**
|
|
6270
6459
|
* 从统一配置生成隐藏列列表
|
|
6271
6460
|
*/
|
|
@@ -6543,6 +6732,14 @@ function AutoCrudTable({ id, title, description, schema, resource, fields, table
|
|
|
6543
6732
|
const [exporting, setExporting] = react.useState(false);
|
|
6544
6733
|
const getSelectedRowsRef = react.useRef(null);
|
|
6545
6734
|
const dynamicFilterOptions = useDynamicFilterOptions(resolvedFields);
|
|
6735
|
+
const hiddenColumns = react.useMemo(() => buildHiddenColumns(resolvedFields, tableConfig?.hidden, denyFields, tableConfig?.overrides), [
|
|
6736
|
+
resolvedFields,
|
|
6737
|
+
tableConfig?.hidden,
|
|
6738
|
+
denyFields,
|
|
6739
|
+
tableConfig?.overrides
|
|
6740
|
+
]);
|
|
6741
|
+
const dynamicResolveOptions = useDynamicResolveOptions(resolvedFields, resource.tableData.data, hiddenColumns);
|
|
6742
|
+
const dynamicOptionsByField = react.useMemo(() => mergeOptionsByField(dynamicFilterOptions.labelOptionsByField, dynamicResolveOptions.optionsByField), [dynamicFilterOptions.labelOptionsByField, dynamicResolveOptions.optionsByField]);
|
|
6546
6743
|
const resourceIdKey = resource.idKey ?? "id";
|
|
6547
6744
|
const actionRegistryVersion = useCrudActionsVersion();
|
|
6548
6745
|
const unifiedActions = react.useMemo(() => {
|
|
@@ -6576,28 +6773,10 @@ function AutoCrudTable({ id, title, description, schema, resource, fields, table
|
|
|
6576
6773
|
const handleSelectedRowsChange = react.useCallback((rows) => {
|
|
6577
6774
|
setSelectedRows((currentRows) => haveSameRowSelection(currentRows, rows, resourceIdKey) ? currentRows : rows);
|
|
6578
6775
|
}, [resourceIdKey]);
|
|
6579
|
-
const
|
|
6580
|
-
const
|
|
6581
|
-
const
|
|
6582
|
-
|
|
6583
|
-
idKey: resourceIdKey,
|
|
6584
|
-
rowIds,
|
|
6585
|
-
selectedRowIds,
|
|
6586
|
-
selectedCount,
|
|
6587
|
-
isRefreshing: resource.tableData.isFetching,
|
|
6588
|
-
...resource.handlers.refresh ? { refresh: resource.handlers.refresh } : {},
|
|
6589
|
-
...toolbarOpenCreate ? { openCreate: toolbarOpenCreate } : {}
|
|
6590
|
-
}), [
|
|
6591
|
-
id,
|
|
6592
|
-
resourceIdKey,
|
|
6593
|
-
rowIds,
|
|
6594
|
-
selectedRowIds,
|
|
6595
|
-
selectedCount,
|
|
6596
|
-
resource.handlers.refresh,
|
|
6597
|
-
resource.tableData.isFetching,
|
|
6598
|
-
toolbarOpenCreate
|
|
6599
|
-
]);
|
|
6600
|
-
const resolvedToolbarActions = resolveToolbarActionsWithResolver(id, registryToolbarActions, toolbarContext);
|
|
6776
|
+
const ownerRefreshAction = react.useMemo(() => readToolbarAction(ownerToolbarActions, "refresh"), [ownerToolbarActions]);
|
|
6777
|
+
const ownerImportAction = react.useMemo(() => readToolbarAction(ownerToolbarActions, "import"), [ownerToolbarActions]);
|
|
6778
|
+
const ownerExportAction = react.useMemo(() => readToolbarAction(ownerToolbarActions, "export"), [ownerToolbarActions]);
|
|
6779
|
+
const ownerCreateAction = react.useMemo(() => readToolbarAction(ownerToolbarActions, "create"), [ownerToolbarActions]);
|
|
6601
6780
|
const importColumns = react.useMemo(() => {
|
|
6602
6781
|
const shape = resolvedSchema.shape;
|
|
6603
6782
|
const denySet = new Set([...DEFAULT_IMPORT_MANAGED_FIELDS, ...denyFields ?? []]);
|
|
@@ -6634,10 +6813,66 @@ function AutoCrudTable({ id, title, description, schema, resource, fields, table
|
|
|
6634
6813
|
setExporting(false);
|
|
6635
6814
|
}
|
|
6636
6815
|
}, [selectedCount, handleExport]);
|
|
6637
|
-
const
|
|
6816
|
+
const toolbarRefresh = react.useMemo(() => {
|
|
6817
|
+
const refresh = ownerRefreshAction?.onClick ?? resource.handlers.refresh;
|
|
6818
|
+
if (!refresh) return void 0;
|
|
6819
|
+
return async () => refresh();
|
|
6820
|
+
}, [ownerRefreshAction?.onClick, resource.handlers.refresh]);
|
|
6821
|
+
const toolbarOpenImport = react.useMemo(() => {
|
|
6822
|
+
if (!canImport) return void 0;
|
|
6823
|
+
return ownerImportAction?.onClick ?? (() => setImportOpen(true));
|
|
6824
|
+
}, [canImport, ownerImportAction?.onClick]);
|
|
6825
|
+
const toolbarExportData = react.useMemo(() => {
|
|
6826
|
+
if (!canExport) return void 0;
|
|
6827
|
+
const exportData = ownerExportAction?.onClick ?? handleExportClick;
|
|
6828
|
+
return async () => exportData();
|
|
6829
|
+
}, [
|
|
6830
|
+
canExport,
|
|
6831
|
+
handleExportClick,
|
|
6832
|
+
ownerExportAction?.onClick
|
|
6833
|
+
]);
|
|
6834
|
+
const toolbarOpenCreate = react.useMemo(() => {
|
|
6835
|
+
if (!can.create) return void 0;
|
|
6836
|
+
return ownerCreateAction?.onClick ?? onCreate ?? resource.handlers.openCreate;
|
|
6837
|
+
}, [
|
|
6838
|
+
can.create,
|
|
6839
|
+
onCreate,
|
|
6840
|
+
ownerCreateAction?.onClick,
|
|
6841
|
+
resource.handlers.openCreate
|
|
6842
|
+
]);
|
|
6843
|
+
const toolbarContext = react.useMemo(() => ({
|
|
6844
|
+
crudId: id ?? "",
|
|
6845
|
+
idKey: resourceIdKey,
|
|
6846
|
+
rowIds,
|
|
6847
|
+
selectedRowIds,
|
|
6848
|
+
selectedCount,
|
|
6849
|
+
isRefreshing: resource.tableData.isFetching,
|
|
6850
|
+
isExporting: exporting,
|
|
6851
|
+
...toolbarRefresh ? { refresh: toolbarRefresh } : {},
|
|
6852
|
+
...toolbarOpenImport ? { openImport: toolbarOpenImport } : {},
|
|
6853
|
+
...toolbarExportData ? { exportData: toolbarExportData } : {},
|
|
6854
|
+
...toolbarOpenCreate ? { openCreate: toolbarOpenCreate } : {}
|
|
6855
|
+
}), [
|
|
6856
|
+
id,
|
|
6857
|
+
resourceIdKey,
|
|
6858
|
+
rowIds,
|
|
6859
|
+
selectedRowIds,
|
|
6860
|
+
selectedCount,
|
|
6861
|
+
resource.tableData.isFetching,
|
|
6862
|
+
exporting,
|
|
6863
|
+
toolbarRefresh,
|
|
6864
|
+
toolbarOpenImport,
|
|
6865
|
+
toolbarExportData,
|
|
6866
|
+
toolbarOpenCreate
|
|
6867
|
+
]);
|
|
6868
|
+
const resolvedToolbarActions = resolveToolbarActionsWithResolver(id, registryToolbarActions, toolbarContext);
|
|
6869
|
+
const tableOverrides = react.useMemo(() => mergeTableOverrides(buildTableOverrides(resolvedFields, tableConfig?.overrides, dynamicFilterOptions, dynamicResolveOptions), buildCapabilityTableOverrides(resolvedSchema, resource.capabilities)), [
|
|
6638
6870
|
resolvedFields,
|
|
6639
6871
|
tableConfig?.overrides,
|
|
6640
|
-
dynamicFilterOptions
|
|
6872
|
+
dynamicFilterOptions,
|
|
6873
|
+
dynamicResolveOptions,
|
|
6874
|
+
resolvedSchema,
|
|
6875
|
+
resource.capabilities
|
|
6641
6876
|
]);
|
|
6642
6877
|
const defaultSort = react.useMemo(() => resource.defaultSort ?? tableConfig?.defaultSort ?? getDefaultSortingForSchema(resolvedSchema), [
|
|
6643
6878
|
resource.defaultSort,
|
|
@@ -6649,18 +6884,23 @@ function AutoCrudTable({ id, title, description, schema, resource, fields, table
|
|
|
6649
6884
|
formConfig?.overrides,
|
|
6650
6885
|
denyFields
|
|
6651
6886
|
]);
|
|
6652
|
-
const hiddenColumns = react.useMemo(() => buildHiddenColumns(resolvedFields, tableConfig?.hidden, denyFields, tableConfig?.overrides), [
|
|
6653
|
-
resolvedFields,
|
|
6654
|
-
tableConfig?.hidden,
|
|
6655
|
-
denyFields,
|
|
6656
|
-
tableConfig?.overrides
|
|
6657
|
-
]);
|
|
6658
6887
|
const searchConfig = react.useMemo(() => {
|
|
6659
6888
|
const tableSearch = tableConfig?.search;
|
|
6889
|
+
const searchCapability = resource.capabilities?.search;
|
|
6660
6890
|
if (tableSearch === false) return false;
|
|
6891
|
+
if (searchCapability) {
|
|
6892
|
+
if (!searchCapability.enabled) return false;
|
|
6893
|
+
if (tableSearch && typeof tableSearch === "object") return tableSearch;
|
|
6894
|
+
return true;
|
|
6895
|
+
}
|
|
6661
6896
|
if (tableSearch && typeof tableSearch === "object") return tableSearch;
|
|
6897
|
+
if (tableSearch === true) return true;
|
|
6662
6898
|
return Object.values(resolvedFields).some((field) => field?.search === true) ? true : false;
|
|
6663
|
-
}, [
|
|
6899
|
+
}, [
|
|
6900
|
+
resolvedFields,
|
|
6901
|
+
resource.capabilities?.search,
|
|
6902
|
+
tableConfig?.search
|
|
6903
|
+
]);
|
|
6664
6904
|
const batchFields = react.useMemo(() => buildBatchUpdateFields(resolvedSchema, tableConfig?.batchFields, resolvedFields), [
|
|
6665
6905
|
resolvedSchema,
|
|
6666
6906
|
tableConfig?.batchFields,
|
|
@@ -6715,7 +6955,7 @@ function AutoCrudTable({ id, title, description, schema, resource, fields, table
|
|
|
6715
6955
|
children: (() => {
|
|
6716
6956
|
const renderBuiltinButton = (type, overrides) => {
|
|
6717
6957
|
if (type === "refresh") {
|
|
6718
|
-
const onRefresh = overrides?.onClick ??
|
|
6958
|
+
const onRefresh = overrides?.onClick ?? toolbarRefresh;
|
|
6719
6959
|
const label = overrides?.label ?? locale.toolbar.refresh;
|
|
6720
6960
|
if (!onRefresh) return null;
|
|
6721
6961
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__wordrhyme_shadcn.Button, {
|
|
@@ -6731,18 +6971,18 @@ function AutoCrudTable({ id, title, description, schema, resource, fields, table
|
|
|
6731
6971
|
if (type === "import" && canImport) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(__wordrhyme_shadcn.Button, {
|
|
6732
6972
|
variant: "outline",
|
|
6733
6973
|
size: "sm",
|
|
6734
|
-
onClick: overrides?.onClick ??
|
|
6974
|
+
onClick: overrides?.onClick ?? toolbarOpenImport,
|
|
6735
6975
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Download, { className: "mr-2 h-4 w-4" }), overrides?.label ?? locale.toolbar.import]
|
|
6736
6976
|
}, "import");
|
|
6737
6977
|
if (type === "export" && canExport) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(__wordrhyme_shadcn.Button, {
|
|
6738
6978
|
variant: "outline",
|
|
6739
6979
|
size: "sm",
|
|
6740
|
-
onClick: overrides?.onClick ??
|
|
6980
|
+
onClick: overrides?.onClick ?? toolbarExportData,
|
|
6741
6981
|
disabled: exporting || selectedCount === 0 && !resource.handlers.export,
|
|
6742
6982
|
children: [exporting ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Loader2, { className: "mr-2 h-4 w-4 animate-spin" }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Upload, { className: "mr-2 h-4 w-4" }), overrides?.label ?? (selectedCount > 0 ? locale.toolbar.exportSelected(selectedCount) : locale.toolbar.export)]
|
|
6743
6983
|
}, "export");
|
|
6744
6984
|
if (type === "create" && can.create) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__wordrhyme_shadcn.Button, {
|
|
6745
|
-
onClick: overrides?.onClick ??
|
|
6985
|
+
onClick: overrides?.onClick ?? toolbarOpenCreate,
|
|
6746
6986
|
children: overrides?.label ?? locale.toolbar.create
|
|
6747
6987
|
}, "create");
|
|
6748
6988
|
return null;
|
|
@@ -6830,7 +7070,7 @@ function AutoCrudTable({ id, title, description, schema, resource, fields, table
|
|
|
6830
7070
|
data: resource.modal.selected,
|
|
6831
7071
|
schema: resolvedSchema,
|
|
6832
7072
|
fields: resolvedFields,
|
|
6833
|
-
dynamicOptions:
|
|
7073
|
+
dynamicOptions: dynamicOptionsByField,
|
|
6834
7074
|
denyFields,
|
|
6835
7075
|
locale
|
|
6836
7076
|
}),
|
|
@@ -7613,6 +7853,7 @@ function useAutoCrudResource({ router, schema, query: queryTransform, options =
|
|
|
7613
7853
|
const resolvedSchema = (0, react.useMemo)(() => mergeMetadataSchema(schema, metadata?.schema), [schema, metadata?.schema]);
|
|
7614
7854
|
const defaultSorting = (0, react.useMemo)(() => defaultSortOption === false ? [] : defaultSortOption ?? getDefaultSortingForSchema(resolvedSchema), [defaultSortOption, resolvedSchema]);
|
|
7615
7855
|
const metadataFields = (0, react.useMemo)(() => readMetadataFields(metadata?.fields), [metadata?.fields]);
|
|
7856
|
+
const capabilities = metadata?.capabilities;
|
|
7616
7857
|
const columns = (0, react.useMemo)(() => createTableSchema(resolvedSchema), [resolvedSchema]);
|
|
7617
7858
|
const [urlParams] = useQueryStates({
|
|
7618
7859
|
page: parseAsInteger.withDefault(1),
|
|
@@ -7905,6 +8146,7 @@ function useAutoCrudResource({ router, schema, query: queryTransform, options =
|
|
|
7905
8146
|
},
|
|
7906
8147
|
schema: resolvedSchema,
|
|
7907
8148
|
fields: metadataFields,
|
|
8149
|
+
capabilities,
|
|
7908
8150
|
defaultSort: defaultSorting,
|
|
7909
8151
|
modal,
|
|
7910
8152
|
mutations: {
|