@wordrhyme/auto-crud 1.2.1 → 1.2.4
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 +44 -12
- package/dist/index.cjs +131 -42
- package/dist/index.d.cts +58 -28
- package/dist/index.d.ts +74 -44
- package/dist/index.js +130 -43
- package/package.json +6 -6
package/README.md
CHANGED
|
@@ -277,7 +277,9 @@ interface AutoCrudTableProps<TSchema> {
|
|
|
277
277
|
* - 只传 custom 项:内置保持默认,custom 追加到首/尾
|
|
278
278
|
* - 包含任意内置 type:数组完全接管,未列出的内置项自动隐藏
|
|
279
279
|
*/
|
|
280
|
-
|
|
280
|
+
toolbar?: ToolbarActionConfig;
|
|
281
|
+
/** @deprecated 新代码请使用 toolbar */
|
|
282
|
+
toolbarActions?: ToolbarActionConfig;
|
|
281
283
|
/**
|
|
282
284
|
* 行操作配置
|
|
283
285
|
* - 只传 custom 项:内置保持默认,custom 追加到首/尾
|
|
@@ -304,6 +306,9 @@ interface UseAutoCrudResourceConfig<TData> {
|
|
|
304
306
|
|
|
305
307
|
```typescript
|
|
306
308
|
interface UseAutoCrudResourceReturn<TSchema, TData> {
|
|
309
|
+
// 主键字段名;未提供时 AutoCrudTable 默认按 "id" 读取
|
|
310
|
+
idKey?: keyof TData & string;
|
|
311
|
+
|
|
307
312
|
// 表格数据
|
|
308
313
|
tableData: {
|
|
309
314
|
data: TData[];
|
|
@@ -529,7 +534,7 @@ table={{
|
|
|
529
534
|
|
|
530
535
|
## 🔧 工具栏操作配置
|
|
531
536
|
|
|
532
|
-
`
|
|
537
|
+
`toolbar` 数组控制页面顶部右侧工具栏的按钮,设计思路完全对齐行操作 `actions`。旧 prop `toolbarActions` 仍兼容读取,但新代码请使用 `toolbar`。
|
|
533
538
|
|
|
534
539
|
内置操作类型:`create`(新建)、`import`(导入)、`export`(导出)。
|
|
535
540
|
|
|
@@ -539,7 +544,7 @@ table={{
|
|
|
539
544
|
|
|
540
545
|
```tsx
|
|
541
546
|
<AutoCrudTable
|
|
542
|
-
|
|
547
|
+
toolbar={[
|
|
543
548
|
{
|
|
544
549
|
type: 'custom',
|
|
545
550
|
component: <Button variant="outline">分类管理</Button>,
|
|
@@ -557,7 +562,7 @@ table={{
|
|
|
557
562
|
|
|
558
563
|
```tsx
|
|
559
564
|
<AutoCrudTable
|
|
560
|
-
|
|
565
|
+
toolbar={[
|
|
561
566
|
{ type: 'create' }, // 新建移到最前
|
|
562
567
|
{ type: 'export', label: '导出 CSV' }, // 覆盖文案
|
|
563
568
|
// import 未列出 → 隐藏
|
|
@@ -570,7 +575,7 @@ table={{
|
|
|
570
575
|
|
|
571
576
|
```tsx
|
|
572
577
|
<AutoCrudTable
|
|
573
|
-
|
|
578
|
+
toolbar={[
|
|
574
579
|
{ type: 'import' },
|
|
575
580
|
{ type: 'export' },
|
|
576
581
|
{
|
|
@@ -586,7 +591,7 @@ table={{
|
|
|
586
591
|
|
|
587
592
|
```tsx
|
|
588
593
|
<AutoCrudTable
|
|
589
|
-
|
|
594
|
+
toolbar={[
|
|
590
595
|
{ type: 'export' },
|
|
591
596
|
{
|
|
592
597
|
type: 'create',
|
|
@@ -614,7 +619,7 @@ table={{
|
|
|
614
619
|
|
|
615
620
|
```tsx
|
|
616
621
|
<AutoCrudTable
|
|
617
|
-
|
|
622
|
+
toolbar={[
|
|
618
623
|
{ type: 'custom', component: <CategoryFilter onChange={setCategory} /> },
|
|
619
624
|
{ type: 'export' },
|
|
620
625
|
{ type: 'create', label: '发布', onClick: () => router.push('/products/new') },
|
|
@@ -625,7 +630,7 @@ table={{
|
|
|
625
630
|
|
|
626
631
|
### 与 `permissions` 的交互
|
|
627
632
|
|
|
628
|
-
`
|
|
633
|
+
`toolbar` 与 `permissions` 的交互规则如下:
|
|
629
634
|
|
|
630
635
|
- `permissions.can.create = false` → 即使配置了 `{ type: "create" }`,新建按钮仍不渲染
|
|
631
636
|
- `permissions.can.export = false` → 即使配置了 `{ type: "export" }`,导出按钮仍不渲染
|
|
@@ -637,7 +642,7 @@ table={{
|
|
|
637
642
|
permissions={{
|
|
638
643
|
can: { create: isAdmin, export: true, delete: isAdmin },
|
|
639
644
|
}}
|
|
640
|
-
|
|
645
|
+
toolbar={[
|
|
641
646
|
{ type: 'export' }, // ✅ 始终渲染
|
|
642
647
|
{ type: 'create', label: '发布商品' }, // 🔒 仅 isAdmin 时渲染
|
|
643
648
|
{
|
|
@@ -648,14 +653,14 @@ table={{
|
|
|
648
653
|
/>
|
|
649
654
|
```
|
|
650
655
|
|
|
651
|
-
### `
|
|
656
|
+
### `toolbar` 传参高级语法(函数式)
|
|
652
657
|
|
|
653
658
|
如果你的目标是拦截并修改基于原顺序的所有内容,你还可以传递一个函数:
|
|
654
659
|
|
|
655
660
|
```tsx
|
|
656
661
|
<AutoCrudTable
|
|
657
662
|
// defaults 即内置三个按钮的信息,在此数组里你可以随意 map、过滤或插值
|
|
658
|
-
|
|
663
|
+
toolbar={(defaults) =>
|
|
659
664
|
defaults.map((btn) =>
|
|
660
665
|
btn.type === 'create'
|
|
661
666
|
? { ...btn, label: '发布商品', onClick: () => router.push('/products/new') }
|
|
@@ -671,6 +676,10 @@ table={{
|
|
|
671
676
|
```typescript
|
|
672
677
|
type ToolbarActionItem = ToolbarBuiltinActionItem | ToolbarCustomActionItem;
|
|
673
678
|
|
|
679
|
+
type ToolbarActionConfig =
|
|
680
|
+
| ToolbarActionItem[]
|
|
681
|
+
| ((defaults: ToolbarBuiltinActionItem[]) => ToolbarActionItem[]);
|
|
682
|
+
|
|
674
683
|
type ToolbarBuiltinActionItem = {
|
|
675
684
|
type: 'create' | 'import' | 'export';
|
|
676
685
|
onClick?: () => void; // 覆盖默认行为
|
|
@@ -685,6 +694,29 @@ type ToolbarCustomActionItem = {
|
|
|
685
694
|
};
|
|
686
695
|
```
|
|
687
696
|
|
|
697
|
+
### 工具栏扩展 Resolver
|
|
698
|
+
|
|
699
|
+
宿主应用可以通过 `setAutoCrudToolbarResolver` 为指定 CRUD 注入额外工具栏动作。resolver 是普通纯函数,不是 React Hook;不要在 resolver 内调用 `useRouter`、`useMemo` 等 React Hooks。
|
|
700
|
+
|
|
701
|
+
```tsx
|
|
702
|
+
import { AutoCrudTable, setAutoCrudToolbarResolver } from '@wordrhyme/auto-crud';
|
|
703
|
+
import type { AutoCrudToolbarResolver } from '@wordrhyme/auto-crud';
|
|
704
|
+
|
|
705
|
+
const toolbarResolver: AutoCrudToolbarResolver = (targetId, ownerActions, context) => {
|
|
706
|
+
if (targetId !== 'com.wordrhyme.shop.products') return [...ownerActions];
|
|
707
|
+
|
|
708
|
+
return [
|
|
709
|
+
...ownerActions,
|
|
710
|
+
{
|
|
711
|
+
type: 'custom',
|
|
712
|
+
component: <Button disabled={context.selectedCount === 0}>批量发布</Button>,
|
|
713
|
+
},
|
|
714
|
+
];
|
|
715
|
+
};
|
|
716
|
+
|
|
717
|
+
setAutoCrudToolbarResolver(toolbarResolver);
|
|
718
|
+
```
|
|
719
|
+
|
|
688
720
|
---
|
|
689
721
|
|
|
690
722
|
## 🎬 行操作配置
|
|
@@ -790,7 +822,7 @@ type ActionItem<T> =
|
|
|
790
822
|
|
|
791
823
|
### 批量悬浮栏操作顺序
|
|
792
824
|
|
|
793
|
-
`table.batchActions` 控制多选后悬浮操作栏,设计思路对齐行操作 `actions` 和顶部 `
|
|
825
|
+
`table.batchActions` 控制多选后悬浮操作栏,设计思路对齐行操作 `actions` 和顶部 `toolbar`。
|
|
794
826
|
|
|
795
827
|
内置操作类型:`batchUpdate`(批量更新字段下拉)、`export`(导出选中行)、`delete`(删除选中行)。
|
|
796
828
|
|
package/dist/index.cjs
CHANGED
|
@@ -4907,7 +4907,7 @@ const filterModeConfig = {
|
|
|
4907
4907
|
tooltip: "Linear-style command filters"
|
|
4908
4908
|
}
|
|
4909
4909
|
};
|
|
4910
|
-
function AutoTable({ schema, data, pageCount = 1, overrides, enableRowSelection = true, exclude, actions, pinnedColumns, filterMode = "simple", search = false, onDeleteSelected, onUpdateSelected, batchUpdateFields, actionBarExtra, actionBarActions, initialSorting, enableExport = true, showDefaultExport = true, onSelectedCountChange, getSelectedRows }) {
|
|
4910
|
+
function AutoTable({ schema, data, pageCount = 1, overrides, enableRowSelection = true, exclude, actions, pinnedColumns, filterMode = "simple", search = false, onDeleteSelected, onUpdateSelected, batchUpdateFields, actionBarExtra, actionBarActions, initialSorting, enableExport = true, showDefaultExport = true, onSelectedCountChange, onSelectedRowsChange, getSelectedRows }) {
|
|
4911
4911
|
const modes = filterMode ? Array.isArray(filterMode) ? filterMode : [filterMode] : DEFAULT_MODES;
|
|
4912
4912
|
const [currentMode, setCurrentMode] = useQueryState("filterMode", parseAsStringEnum(modes).withDefault(modes[0] ?? "simple"));
|
|
4913
4913
|
const showToggle = modes.length > 1;
|
|
@@ -4956,17 +4956,28 @@ function AutoTable({ schema, data, pageCount = 1, overrides, enableRowSelection
|
|
|
4956
4956
|
shallow: false,
|
|
4957
4957
|
clearOnDefault: true
|
|
4958
4958
|
});
|
|
4959
|
-
const
|
|
4959
|
+
const rowSelection = table.getState().rowSelection;
|
|
4960
|
+
const columnFilters = table.getState().columnFilters;
|
|
4961
|
+
const selectedRows = (0, react.useMemo)(() => table.getFilteredSelectedRowModel().rows.map((row) => row.original), [
|
|
4962
|
+
table,
|
|
4963
|
+
rowSelection,
|
|
4964
|
+
columnFilters,
|
|
4965
|
+
data
|
|
4966
|
+
]);
|
|
4967
|
+
const getSelectedRowsFn = (0, react.useCallback)(() => selectedRows, [selectedRows]);
|
|
4960
4968
|
(0, react.useEffect)(() => {
|
|
4961
4969
|
if (getSelectedRows) getSelectedRows.current = getSelectedRowsFn;
|
|
4962
4970
|
return () => {
|
|
4963
4971
|
if (getSelectedRows) getSelectedRows.current = null;
|
|
4964
4972
|
};
|
|
4965
4973
|
}, [getSelectedRows, getSelectedRowsFn]);
|
|
4966
|
-
const selectedRowCount =
|
|
4974
|
+
const selectedRowCount = selectedRows.length;
|
|
4967
4975
|
(0, react.useEffect)(() => {
|
|
4968
4976
|
onSelectedCountChange?.(selectedRowCount);
|
|
4969
4977
|
}, [selectedRowCount, onSelectedCountChange]);
|
|
4978
|
+
(0, react.useEffect)(() => {
|
|
4979
|
+
onSelectedRowsChange?.(selectedRows);
|
|
4980
|
+
}, [selectedRows, onSelectedRowsChange]);
|
|
4970
4981
|
const FilterModeSelect = showToggle ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(__pixpilot_shadcn.DropdownMenu, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(__pixpilot_shadcn.DropdownMenuTrigger, {
|
|
4971
4982
|
asChild: true,
|
|
4972
4983
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__pixpilot_shadcn.Button, {
|
|
@@ -5235,8 +5246,12 @@ function createEditFormSchema(schema, options) {
|
|
|
5235
5246
|
function createRegistry(label) {
|
|
5236
5247
|
const entries = /* @__PURE__ */ new Map();
|
|
5237
5248
|
const listeners = /* @__PURE__ */ new Set();
|
|
5249
|
+
let notifyScheduled = false;
|
|
5238
5250
|
const notify = () => {
|
|
5251
|
+
if (notifyScheduled) return;
|
|
5252
|
+
notifyScheduled = true;
|
|
5239
5253
|
queueMicrotask(() => {
|
|
5254
|
+
notifyScheduled = false;
|
|
5240
5255
|
for (const listener of listeners) listener();
|
|
5241
5256
|
});
|
|
5242
5257
|
};
|
|
@@ -5266,20 +5281,32 @@ function createRegistry(label) {
|
|
|
5266
5281
|
};
|
|
5267
5282
|
}
|
|
5268
5283
|
const formComponents = createRegistry("form component");
|
|
5269
|
-
const
|
|
5270
|
-
function
|
|
5271
|
-
if (typeof
|
|
5272
|
-
|
|
5273
|
-
|
|
5274
|
-
|
|
5275
|
-
}
|
|
5276
|
-
if (!config?.key) return void 0;
|
|
5284
|
+
const components = formComponents;
|
|
5285
|
+
function normalizeDataSourceRegistration(registration) {
|
|
5286
|
+
if (typeof registration === "function") return {
|
|
5287
|
+
dependencies: [],
|
|
5288
|
+
reset: false,
|
|
5289
|
+
load: registration
|
|
5290
|
+
};
|
|
5277
5291
|
return {
|
|
5278
|
-
|
|
5279
|
-
|
|
5280
|
-
|
|
5292
|
+
dependencies: Array.isArray(registration.dependencies) ? registration.dependencies : [],
|
|
5293
|
+
reset: registration.reset === true,
|
|
5294
|
+
load: registration.load
|
|
5281
5295
|
};
|
|
5282
5296
|
}
|
|
5297
|
+
const dataSourceRegistry = createRegistry("data source");
|
|
5298
|
+
const dataSources = {
|
|
5299
|
+
register(name, registration) {
|
|
5300
|
+
dataSourceRegistry.register(name, normalizeDataSourceRegistration(registration));
|
|
5301
|
+
},
|
|
5302
|
+
unregister: dataSourceRegistry.unregister,
|
|
5303
|
+
get: dataSourceRegistry.get,
|
|
5304
|
+
all: dataSourceRegistry.all,
|
|
5305
|
+
subscribe: dataSourceRegistry.subscribe
|
|
5306
|
+
};
|
|
5307
|
+
function normalizeDataSourceConfig(config) {
|
|
5308
|
+
if (typeof config === "string") return config.length > 0 ? { key: config } : void 0;
|
|
5309
|
+
}
|
|
5283
5310
|
function normalizeOptions(result) {
|
|
5284
5311
|
const options = Array.isArray(result) ? result : result?.options;
|
|
5285
5312
|
if (!Array.isArray(options)) return [];
|
|
@@ -5345,9 +5372,7 @@ function useRegistryVersion$1(subscribe) {
|
|
|
5345
5372
|
return version;
|
|
5346
5373
|
}
|
|
5347
5374
|
function isDataSourceConfig(value) {
|
|
5348
|
-
|
|
5349
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
5350
|
-
return typeof value.key === "string";
|
|
5375
|
+
return typeof value === "string" && value.length > 0;
|
|
5351
5376
|
}
|
|
5352
5377
|
function collectDataSourceConfigs(schema, result = {}) {
|
|
5353
5378
|
const properties = schema.properties;
|
|
@@ -5377,17 +5402,17 @@ function useFormDataSources(form, schema) {
|
|
|
5377
5402
|
const loadFieldOptions = async (fieldName, source) => {
|
|
5378
5403
|
const version = (loadVersions.get(fieldName) ?? 0) + 1;
|
|
5379
5404
|
loadVersions.set(fieldName, version);
|
|
5380
|
-
const
|
|
5381
|
-
if (!
|
|
5405
|
+
const entry = dataSources.get(source.key);
|
|
5406
|
+
if (!entry) {
|
|
5382
5407
|
form.setFieldState(fieldName, (state) => {
|
|
5383
5408
|
state.dataSource = [];
|
|
5384
5409
|
});
|
|
5385
5410
|
return;
|
|
5386
5411
|
}
|
|
5387
5412
|
try {
|
|
5388
|
-
const options = normalizeOptions(await
|
|
5413
|
+
const options = normalizeOptions(await entry.load({
|
|
5389
5414
|
field: fieldName,
|
|
5390
|
-
values: Object.fromEntries(
|
|
5415
|
+
values: Object.fromEntries(entry.dependencies.map((dependency) => [dependency, form.getValuesIn(dependency)])),
|
|
5391
5416
|
signal: controller?.signal
|
|
5392
5417
|
}));
|
|
5393
5418
|
if (!active || loadVersions.get(fieldName) !== version) return;
|
|
@@ -5404,10 +5429,11 @@ function useFormDataSources(form, schema) {
|
|
|
5404
5429
|
};
|
|
5405
5430
|
for (const [fieldName, source] of sourceEntries) loadFieldOptions(fieldName, source);
|
|
5406
5431
|
form.addEffects(effectId, () => {
|
|
5407
|
-
for (const dependency of new Set(sourceEntries.flatMap(([, source]) => source.
|
|
5432
|
+
for (const dependency of new Set(sourceEntries.flatMap(([, source]) => dataSources.get(source.key)?.dependencies ?? []))) (0, __formily_core.onFieldValueChange)(dependency, () => {
|
|
5408
5433
|
for (const [fieldName, source] of sourceEntries) {
|
|
5409
|
-
|
|
5410
|
-
if (
|
|
5434
|
+
const entry = dataSources.get(source.key);
|
|
5435
|
+
if (!entry?.dependencies.includes(dependency)) continue;
|
|
5436
|
+
if (entry.reset) form.setValuesIn(fieldName, void 0);
|
|
5411
5437
|
loadFieldOptions(fieldName, source);
|
|
5412
5438
|
}
|
|
5413
5439
|
});
|
|
@@ -5446,8 +5472,8 @@ function AutoFormInner({ schema: zodSchema, initialValues, onSubmit, overrides,
|
|
|
5446
5472
|
}, [labelAlign, labelWidth]);
|
|
5447
5473
|
const fieldComponents = (0, react.useMemo)(() => ({ fields: {
|
|
5448
5474
|
...defaultFieldComponents,
|
|
5449
|
-
...
|
|
5450
|
-
} }), [useRegistryVersion$1(
|
|
5475
|
+
...components.all()
|
|
5476
|
+
} }), [useRegistryVersion$1(components.subscribe)]);
|
|
5451
5477
|
useFormDataSources(form, formSchema);
|
|
5452
5478
|
const handleSubmit = async () => {
|
|
5453
5479
|
await form.validate();
|
|
@@ -6260,6 +6286,44 @@ function ImportDialog({ open, onOpenChange, onImport, columns = [], title, local
|
|
|
6260
6286
|
|
|
6261
6287
|
//#endregion
|
|
6262
6288
|
//#region src/components/auto-crud/auto-crud-table.tsx
|
|
6289
|
+
let autoCrudToolbarResolver = null;
|
|
6290
|
+
function setAutoCrudToolbarResolver(resolver) {
|
|
6291
|
+
autoCrudToolbarResolver = resolver;
|
|
6292
|
+
}
|
|
6293
|
+
function getDefaultToolbarActions() {
|
|
6294
|
+
return [
|
|
6295
|
+
{ type: "import" },
|
|
6296
|
+
{ type: "export" },
|
|
6297
|
+
{ type: "create" }
|
|
6298
|
+
];
|
|
6299
|
+
}
|
|
6300
|
+
function resolveOwnerToolbarActions(toolbar) {
|
|
6301
|
+
const resolved = typeof toolbar === "function" ? toolbar(getDefaultToolbarActions()) : toolbar;
|
|
6302
|
+
return resolved !== void 0 && resolved.length > 0 ? [...resolved] : getDefaultToolbarActions();
|
|
6303
|
+
}
|
|
6304
|
+
function readRowIds(rows, idKey) {
|
|
6305
|
+
return rows.flatMap((row) => {
|
|
6306
|
+
const value = row[idKey];
|
|
6307
|
+
return value === null || value === void 0 ? [] : [String(value)];
|
|
6308
|
+
});
|
|
6309
|
+
}
|
|
6310
|
+
function areStringArraysEqual(left, right) {
|
|
6311
|
+
return left.length === right.length && left.every((value, index) => value === right[index]);
|
|
6312
|
+
}
|
|
6313
|
+
function haveSameRowSelection(left, right, idKey) {
|
|
6314
|
+
if (left.length !== right.length) return false;
|
|
6315
|
+
const leftIds = readRowIds(left, idKey);
|
|
6316
|
+
const rightIds = readRowIds(right, idKey);
|
|
6317
|
+
if (leftIds.length === left.length && rightIds.length === right.length) return areStringArraysEqual(leftIds, rightIds);
|
|
6318
|
+
return left.every((row, index) => row === right[index]);
|
|
6319
|
+
}
|
|
6320
|
+
function readCreateAction(actions) {
|
|
6321
|
+
return actions.find((action) => action.type === "create");
|
|
6322
|
+
}
|
|
6323
|
+
function resolveToolbarActionsWithResolver(targetId, ownerActions, context) {
|
|
6324
|
+
const resolver = autoCrudToolbarResolver;
|
|
6325
|
+
return targetId !== void 0 && targetId.length > 0 && resolver !== null ? resolver(targetId, ownerActions, context) : ownerActions;
|
|
6326
|
+
}
|
|
6263
6327
|
function mergeFieldPart(base, override) {
|
|
6264
6328
|
if (override === void 0) return base;
|
|
6265
6329
|
if (override === false || base === false || typeof base !== "object" || typeof override !== "object" || base === null || override === null || Array.isArray(base) || Array.isArray(override)) return override;
|
|
@@ -6346,10 +6410,10 @@ function useDynamicFilterOptions(fields) {
|
|
|
6346
6410
|
let active = true;
|
|
6347
6411
|
const controller = typeof AbortController === "undefined" ? void 0 : new AbortController();
|
|
6348
6412
|
Promise.all(sourceEntries.map(async ({ field, source }) => {
|
|
6349
|
-
const
|
|
6350
|
-
if (!
|
|
6413
|
+
const entry = dataSources.get(source.key);
|
|
6414
|
+
if (!entry) return [field, []];
|
|
6351
6415
|
try {
|
|
6352
|
-
return [field, normalizeOptions(await
|
|
6416
|
+
return [field, normalizeOptions(await entry.load({
|
|
6353
6417
|
field,
|
|
6354
6418
|
values: {},
|
|
6355
6419
|
signal: controller?.signal
|
|
@@ -6583,7 +6647,7 @@ function renderFieldValue(value, type, booleanLocale, options) {
|
|
|
6583
6647
|
/**
|
|
6584
6648
|
* ViewModal 组件 - 详情查看弹窗
|
|
6585
6649
|
*/
|
|
6586
|
-
function ViewModal({ open, onOpenChange, variant, data, schema, fields: fieldConfig, denyFields, locale }) {
|
|
6650
|
+
function ViewModal({ open, onOpenChange, variant, data, schema, fields: fieldConfig, dynamicOptions, denyFields, locale }) {
|
|
6587
6651
|
if (!data) return null;
|
|
6588
6652
|
const shape = schema.shape;
|
|
6589
6653
|
const denySet = new Set(denyFields ?? []);
|
|
@@ -6596,7 +6660,7 @@ function ViewModal({ open, onOpenChange, variant, data, schema, fields: fieldCon
|
|
|
6596
6660
|
const parsed = parseZodField(fieldSchema);
|
|
6597
6661
|
const label = fieldConfig?.[key]?.label ?? humanize(key);
|
|
6598
6662
|
const value = data[key];
|
|
6599
|
-
const options = normalizeFieldOptions(fieldConfig?.[key]?.enum);
|
|
6663
|
+
const options = normalizeFieldOptions(fieldConfig?.[key]?.enum) ?? normalizeFieldOptions(dynamicOptions?.[key]);
|
|
6600
6664
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
6601
6665
|
className: "grid grid-cols-3 items-start gap-4",
|
|
6602
6666
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("dt", {
|
|
@@ -6618,6 +6682,7 @@ function ViewModal({ open, onOpenChange, variant, data, schema, fields: fieldCon
|
|
|
6618
6682
|
open,
|
|
6619
6683
|
onOpenChange,
|
|
6620
6684
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(__pixpilot_shadcn.DialogContent, {
|
|
6685
|
+
"aria-describedby": void 0,
|
|
6621
6686
|
className: "max-w-2xl max-h-[80vh] overflow-y-auto",
|
|
6622
6687
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(__pixpilot_shadcn.DialogHeader, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__pixpilot_shadcn.DialogTitle, { children: locale.viewModal.title }) }), content]
|
|
6623
6688
|
})
|
|
@@ -6709,7 +6774,7 @@ function resolveActions(actionsOrFn, defaults, rowActionsLocale) {
|
|
|
6709
6774
|
*
|
|
6710
6775
|
* 高级 CRUD 表格组件,封装了完整的增删改查流程
|
|
6711
6776
|
*/
|
|
6712
|
-
function AutoCrudTable({ title, description, schema, resource, fields, table: tableConfig, form: formConfig, permissions, actions: actionItems, toolbarActions, locale: localeProp, onCreate }) {
|
|
6777
|
+
function AutoCrudTable({ id, title, description, schema, resource, fields, table: tableConfig, form: formConfig, permissions, actions: actionItems, toolbar, toolbarActions, locale: localeProp, onCreate }) {
|
|
6713
6778
|
const locale = resolveLocale(localeProp);
|
|
6714
6779
|
const resolvedSchema = resource.schema ?? schema;
|
|
6715
6780
|
const resolvedFields = react.useMemo(() => mergeFields(resource.fields, fields) ?? {}, [fields, resource.fields]);
|
|
@@ -6724,10 +6789,35 @@ function AutoCrudTable({ title, description, schema, resource, fields, table: ta
|
|
|
6724
6789
|
const canImport = can.import && !!resource.handlers.import;
|
|
6725
6790
|
const canExport = can.export;
|
|
6726
6791
|
const [importOpen, setImportOpen] = react.useState(false);
|
|
6727
|
-
const [
|
|
6792
|
+
const [selectedRows, setSelectedRows] = react.useState([]);
|
|
6728
6793
|
const [exporting, setExporting] = react.useState(false);
|
|
6729
6794
|
const getSelectedRowsRef = react.useRef(null);
|
|
6730
6795
|
const dynamicFilterOptions = useDynamicFilterOptions(resolvedFields);
|
|
6796
|
+
const resourceIdKey = resource.idKey ?? "id";
|
|
6797
|
+
const ownerToolbarActions = react.useMemo(() => resolveOwnerToolbarActions(toolbar ?? toolbarActions), [toolbar, toolbarActions]);
|
|
6798
|
+
const selectedCount = selectedRows.length;
|
|
6799
|
+
const selectedRowIds = react.useMemo(() => readRowIds(selectedRows, resourceIdKey), [selectedRows, resourceIdKey]);
|
|
6800
|
+
const rowIds = react.useMemo(() => readRowIds(resource.tableData.data, resourceIdKey), [resource.tableData.data, resourceIdKey]);
|
|
6801
|
+
const handleSelectedRowsChange = react.useCallback((rows) => {
|
|
6802
|
+
setSelectedRows((currentRows) => haveSameRowSelection(currentRows, rows, resourceIdKey) ? currentRows : rows);
|
|
6803
|
+
}, [resourceIdKey]);
|
|
6804
|
+
const ownerCreateAction = react.useMemo(() => readCreateAction(ownerToolbarActions), [ownerToolbarActions]);
|
|
6805
|
+
const toolbarOpenCreate = can.create ? ownerCreateAction?.onClick ?? onCreate ?? resource.handlers.openCreate : void 0;
|
|
6806
|
+
const resolvedToolbarActions = resolveToolbarActionsWithResolver(id, ownerToolbarActions, react.useMemo(() => ({
|
|
6807
|
+
crudId: id ?? "",
|
|
6808
|
+
idKey: resourceIdKey,
|
|
6809
|
+
rowIds,
|
|
6810
|
+
selectedRowIds,
|
|
6811
|
+
selectedCount,
|
|
6812
|
+
...toolbarOpenCreate ? { openCreate: toolbarOpenCreate } : {}
|
|
6813
|
+
}), [
|
|
6814
|
+
id,
|
|
6815
|
+
resourceIdKey,
|
|
6816
|
+
rowIds,
|
|
6817
|
+
selectedRowIds,
|
|
6818
|
+
selectedCount,
|
|
6819
|
+
toolbarOpenCreate
|
|
6820
|
+
]));
|
|
6731
6821
|
const importColumns = react.useMemo(() => {
|
|
6732
6822
|
const shape = resolvedSchema.shape;
|
|
6733
6823
|
const denySet = new Set(denyFields ?? []);
|
|
@@ -6737,9 +6827,9 @@ function AutoCrudTable({ title, description, schema, resource, fields, table: ta
|
|
|
6737
6827
|
const filename = title ?? "export";
|
|
6738
6828
|
const excludeColumns = denyFields;
|
|
6739
6829
|
if (mode === "selected") {
|
|
6740
|
-
const selectedRows = getSelectedRowsRef.current?.();
|
|
6741
|
-
if (!selectedRows || selectedRows.length === 0) return;
|
|
6742
|
-
exportAllToCSV(selectedRows, {
|
|
6830
|
+
const selectedRows$1 = getSelectedRowsRef.current?.();
|
|
6831
|
+
if (!selectedRows$1 || selectedRows$1.length === 0) return;
|
|
6832
|
+
exportAllToCSV(selectedRows$1, {
|
|
6743
6833
|
filename,
|
|
6744
6834
|
excludeColumns
|
|
6745
6835
|
});
|
|
@@ -6833,11 +6923,6 @@ function AutoCrudTable({ title, description, schema, resource, fields, table: ta
|
|
|
6833
6923
|
}, "create");
|
|
6834
6924
|
return null;
|
|
6835
6925
|
};
|
|
6836
|
-
const resolvedToolbarActions = typeof toolbarActions === "function" ? toolbarActions([
|
|
6837
|
-
{ type: "import" },
|
|
6838
|
-
{ type: "export" },
|
|
6839
|
-
{ type: "create" }
|
|
6840
|
-
]) : toolbarActions;
|
|
6841
6926
|
if (!resolvedToolbarActions || resolvedToolbarActions.length === 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
6842
6927
|
className: "flex items-center gap-2",
|
|
6843
6928
|
children: [
|
|
@@ -6895,7 +6980,7 @@ function AutoCrudTable({ title, description, schema, resource, fields, table: ta
|
|
|
6895
6980
|
enableExport: canExport,
|
|
6896
6981
|
showDefaultExport: false,
|
|
6897
6982
|
initialSorting: tableConfig?.defaultSort,
|
|
6898
|
-
|
|
6983
|
+
onSelectedRowsChange: handleSelectedRowsChange,
|
|
6899
6984
|
getSelectedRows: getSelectedRowsRef
|
|
6900
6985
|
}),
|
|
6901
6986
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(CrudFormModal, {
|
|
@@ -6922,6 +7007,7 @@ function AutoCrudTable({ title, description, schema, resource, fields, table: ta
|
|
|
6922
7007
|
data: resource.modal.selected,
|
|
6923
7008
|
schema: resolvedSchema,
|
|
6924
7009
|
fields: resolvedFields,
|
|
7010
|
+
dynamicOptions: dynamicFilterOptions,
|
|
6925
7011
|
denyFields,
|
|
6926
7012
|
locale
|
|
6927
7013
|
}),
|
|
@@ -7969,6 +8055,7 @@ function useAutoCrudResource({ router, schema, query: queryTransform, options =
|
|
|
7969
8055
|
]);
|
|
7970
8056
|
const canExport = !!(exportFetcher || exportQuery);
|
|
7971
8057
|
return {
|
|
8058
|
+
idKey,
|
|
7972
8059
|
tableData: {
|
|
7973
8060
|
data: tableRows,
|
|
7974
8061
|
pageCount: listQuery.data?.pageCount ?? 0,
|
|
@@ -8327,6 +8414,7 @@ exports.MultiCombobox = MultiCombobox;
|
|
|
8327
8414
|
exports.SchemaAdapter = SchemaAdapter;
|
|
8328
8415
|
exports.cn = cn;
|
|
8329
8416
|
exports.coerceRowValues = coerceRowValues;
|
|
8417
|
+
exports.components = components;
|
|
8330
8418
|
exports.createActionsColumn = createActionsColumn;
|
|
8331
8419
|
exports.createEditFormSchema = createEditFormSchema;
|
|
8332
8420
|
exports.createFormSchema = createFormSchema;
|
|
@@ -8362,6 +8450,7 @@ exports.parseImportFile = parseImportFile;
|
|
|
8362
8450
|
exports.parseJSON = parseJSON;
|
|
8363
8451
|
exports.parseZodField = parseZodField;
|
|
8364
8452
|
exports.resolveLocale = resolveLocale;
|
|
8453
|
+
exports.setAutoCrudToolbarResolver = setAutoCrudToolbarResolver;
|
|
8365
8454
|
exports.setSearchParams = setSearchParams;
|
|
8366
8455
|
exports.useAutoCrudResource = useAutoCrudResource;
|
|
8367
8456
|
exports.useDataTable = useDataTable;
|