@wordrhyme/auto-crud 1.0.0 → 1.0.2

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/dist/index.cjs CHANGED
@@ -1450,7 +1450,12 @@ function DataTableFilterList({ table, debounceMs = DEBOUNCE_MS$2, throttleMs = T
1450
1450
  const [open, setOpen] = react.useState(false);
1451
1451
  const addButtonRef = react.useRef(null);
1452
1452
  const columns = react.useMemo(() => {
1453
- return table.getAllColumns().filter((column) => column.columnDef.enableColumnFilter);
1453
+ return table.getAllColumns().filter((column) => {
1454
+ if (!column.getCanFilter()) return false;
1455
+ const modes = column.columnDef.meta?.modes;
1456
+ if (modes && !modes.includes("advanced")) return false;
1457
+ return true;
1458
+ });
1454
1459
  }, [table]);
1455
1460
  const [filters, setFilters] = useReadableFilters(columns, { debounceMs });
1456
1461
  const debouncedSetFilters = useDebouncedCallback(setFilters, debounceMs);
@@ -1932,7 +1937,12 @@ const REMOVE_FILTER_SHORTCUTS = ["backspace", "delete"];
1932
1937
  function DataTableFilterMenu({ table, debounceMs = DEBOUNCE_MS$1, throttleMs = THROTTLE_MS$1, shallow = true, disabled,...props }) {
1933
1938
  const id = react.useId();
1934
1939
  const columns = react.useMemo(() => {
1935
- return table.getAllColumns().filter((column) => column.columnDef.enableColumnFilter);
1940
+ return table.getAllColumns().filter((column) => {
1941
+ if (!column.columnDef.enableColumnFilter) return false;
1942
+ const modes = column.columnDef.meta?.modes;
1943
+ if (modes && !modes.includes("command")) return false;
1944
+ return true;
1945
+ });
1936
1946
  }, [table]);
1937
1947
  const [open, setOpen] = react.useState(false);
1938
1948
  const [selectedColumn, setSelectedColumn] = react.useState(null);
@@ -2689,7 +2699,7 @@ function DataTableViewOptions({ table, disabled,...props }) {
2689
2699
  //#endregion
2690
2700
  //#region src/components/auto-crud/auto-table-simple-filters.tsx
2691
2701
  function AutoTableSimpleFilters({ table, shallow = false, filters: externalFilters, onFiltersChange }) {
2692
- const columns = react.useMemo(() => table.getAllColumns().filter((col) => col.columnDef.enableColumnFilter), [table]);
2702
+ const columns = react.useMemo(() => table.getAllColumns().filter((col) => col.getCanFilter()), [table]);
2693
2703
  const queryStateOptions = table.options.meta?.queryStateOptions;
2694
2704
  const [queryFilters, setQueryFilters] = useReadableFilters(columns, {
2695
2705
  ...queryStateOptions,
@@ -2709,9 +2719,10 @@ function AutoTableSimpleFilters({ table, shallow = false, filters: externalFilte
2709
2719
  react.useEffect(() => {
2710
2720
  setMounted(true);
2711
2721
  }, []);
2712
- const getFilterValue = (columnId) => {
2713
- return filters.find((f) => f.id === columnId)?.value;
2714
- };
2722
+ const filterMap = react.useMemo(() => new Map(filters.map((f) => [f.id, f])), [filters]);
2723
+ const getFilterValue = react.useCallback((columnId) => {
2724
+ return filterMap.get(columnId)?.value;
2725
+ }, [filterMap]);
2715
2726
  const updateFilter = react.useCallback((columnId, value) => {
2716
2727
  const column = columns.find((c) => c.id === columnId);
2717
2728
  if (!column) return;
@@ -2734,7 +2745,14 @@ function AutoTableSimpleFilters({ table, shallow = false, filters: externalFilte
2734
2745
  setFilters
2735
2746
  ]);
2736
2747
  const hasFilters = filters.length > 0;
2737
- const filterColumns = react.useMemo(() => columns.filter((col) => col.columnDef.meta?.variant && col.columnDef.enableColumnFilter !== false), [columns]);
2748
+ const filterColumns = react.useMemo(() => columns.filter((col) => {
2749
+ const meta = col.columnDef.meta;
2750
+ const enableFilter = col.columnDef.enableColumnFilter !== false;
2751
+ if (!meta?.variant || !enableFilter) return false;
2752
+ const modes = meta.modes;
2753
+ if (modes && !modes.includes("simple")) return false;
2754
+ return true;
2755
+ }), [columns]);
2738
2756
  const [expanded, setExpanded] = react.useState(false);
2739
2757
  const [visibleCount, setVisibleCount] = react.useState(null);
2740
2758
  const containerRef = react.useRef(null);
@@ -2874,7 +2892,7 @@ function AutoTableSimpleFilters({ table, shallow = false, filters: externalFilte
2874
2892
  }
2875
2893
  function SimpleFacetedFilter({ title, options, multiple, value, onChange }) {
2876
2894
  const [open, setOpen] = react.useState(false);
2877
- const selectedValues = new Set(value);
2895
+ const selectedValues = react.useMemo(() => new Set(value), [value]);
2878
2896
  const onItemSelect = (option, isSelected) => {
2879
2897
  if (multiple) {
2880
2898
  const newValues = new Set(selectedValues);
@@ -2905,6 +2923,12 @@ function SimpleFacetedFilter({ title, options, multiple, value, onChange }) {
2905
2923
  tabIndex: 0,
2906
2924
  className: "rounded-sm opacity-70 hover:opacity-100",
2907
2925
  onClick: onReset,
2926
+ onKeyDown: (e) => {
2927
+ if (e.key === "Enter" || e.key === " ") {
2928
+ e.preventDefault();
2929
+ onReset(e);
2930
+ }
2931
+ },
2908
2932
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.XCircle, {})
2909
2933
  }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.PlusCircle, {}),
2910
2934
  title,
@@ -3012,6 +3036,12 @@ function SimpleSliderFilter({ title, range, unit, value, onChange }) {
3012
3036
  tabIndex: 0,
3013
3037
  className: "rounded-sm opacity-70 hover:opacity-100",
3014
3038
  onClick: onReset,
3039
+ onKeyDown: (e) => {
3040
+ if (e.key === "Enter" || e.key === " ") {
3041
+ e.preventDefault();
3042
+ onReset(e);
3043
+ }
3044
+ },
3015
3045
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.XCircle, {})
3016
3046
  }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.PlusCircle, {}),
3017
3047
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: title }),
@@ -3135,6 +3165,12 @@ function SimpleDateFilter({ title, multiple, value, onChange }) {
3135
3165
  tabIndex: 0,
3136
3166
  className: "rounded-sm opacity-70 hover:opacity-100",
3137
3167
  onClick: onReset,
3168
+ onKeyDown: (e) => {
3169
+ if (e.key === "Enter" || e.key === " ") {
3170
+ e.preventDefault();
3171
+ onReset(e);
3172
+ }
3173
+ },
3138
3174
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.XCircle, {})
3139
3175
  }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.CalendarIcon, {}),
3140
3176
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: title }),
@@ -3747,33 +3783,47 @@ function inferFilterVariantByFieldName(fieldName) {
3747
3783
 
3748
3784
  //#endregion
3749
3785
  //#region src/lib/schema-bridge/zod-to-columns.tsx
3786
+ const parsedFieldCache = /* @__PURE__ */ new WeakMap();
3787
+ const baseTableSchemaCache = /* @__PURE__ */ new WeakMap();
3750
3788
  /**
3751
3789
  * 解析 Zod 字段类型
3752
3790
  */
3753
3791
  function parseZodField(schema) {
3792
+ const cached = parsedFieldCache.get(schema);
3793
+ if (cached) return cached;
3794
+ const finalize = (result) => {
3795
+ parsedFieldCache.set(schema, result);
3796
+ return result;
3797
+ };
3754
3798
  const def = schema._zod?.def ?? schema._def;
3755
- const testNull = schema.safeParse(null);
3756
- const required = !schema.safeParse(void 0).success && !testNull.success;
3757
3799
  const typeName = def?.typeName;
3758
- if (def?.type === "nullable" || def?.type === "optional" || typeName === "ZodNullable" || typeName === "ZodOptional") {
3800
+ const isOptional = def?.type === "optional" || typeName === "ZodOptional";
3801
+ const isNullable = def?.type === "nullable" || typeName === "ZodNullable";
3802
+ if (isOptional || isNullable) {
3759
3803
  const innerType = def?.innerType;
3760
- if (innerType) return {
3804
+ if (innerType) return finalize({
3761
3805
  ...parseZodField(innerType),
3762
3806
  required: false
3763
- };
3807
+ });
3764
3808
  }
3765
- if (def?.type === "boolean") return {
3809
+ let required;
3810
+ if (isOptional || isNullable) required = false;
3811
+ else {
3812
+ const testNull = schema.safeParse(null);
3813
+ required = !schema.safeParse(void 0).success && !testNull.success;
3814
+ }
3815
+ if (def?.type === "boolean") return finalize({
3766
3816
  type: "boolean",
3767
3817
  required
3768
- };
3769
- if (def?.type === "number") return {
3818
+ });
3819
+ if (def?.type === "number") return finalize({
3770
3820
  type: "number",
3771
3821
  required
3772
- };
3773
- if (def?.type === "date") return {
3822
+ });
3823
+ if (def?.type === "date") return finalize({
3774
3824
  type: "date",
3775
3825
  required
3776
- };
3826
+ });
3777
3827
  if (def?.type === "enum") {
3778
3828
  let enumValues$1;
3779
3829
  if (def?.entries) {
@@ -3784,20 +3834,20 @@ function parseZodField(schema) {
3784
3834
  const values = def.values;
3785
3835
  if (Array.isArray(values)) enumValues$1 = values.map(String);
3786
3836
  }
3787
- if (enumValues$1 && enumValues$1.length > 0) return {
3837
+ if (enumValues$1 && enumValues$1.length > 0) return finalize({
3788
3838
  type: "enum",
3789
3839
  required,
3790
3840
  enumValues: enumValues$1
3791
- };
3841
+ });
3792
3842
  }
3793
- if (def?.type === "string") return {
3843
+ if (def?.type === "string") return finalize({
3794
3844
  type: "string",
3795
3845
  required
3796
- };
3797
- if (typeName === "ZodBoolean") return {
3846
+ });
3847
+ if (typeName === "ZodBoolean") return finalize({
3798
3848
  type: "boolean",
3799
3849
  required
3800
- };
3850
+ });
3801
3851
  let enumValues;
3802
3852
  if (def?.entries) {
3803
3853
  const entries = def.entries;
@@ -3807,51 +3857,51 @@ function parseZodField(schema) {
3807
3857
  const values = def.values;
3808
3858
  if (Array.isArray(values)) enumValues = values.map(String);
3809
3859
  }
3810
- if (enumValues && Array.isArray(enumValues) && enumValues.length > 0) return {
3860
+ if (enumValues && Array.isArray(enumValues) && enumValues.length > 0) return finalize({
3811
3861
  type: "enum",
3812
3862
  required,
3813
3863
  enumValues
3814
- };
3815
- if (typeName === "ZodNumber") return {
3864
+ });
3865
+ if (typeName === "ZodNumber") return finalize({
3816
3866
  type: "number",
3817
3867
  required
3818
- };
3819
- if (typeName === "ZodDate") return {
3868
+ });
3869
+ if (typeName === "ZodDate") return finalize({
3820
3870
  type: "date",
3821
3871
  required
3822
- };
3823
- if (typeName === "ZodArray") return {
3872
+ });
3873
+ if (typeName === "ZodArray") return finalize({
3824
3874
  type: "array",
3825
3875
  required
3826
- };
3827
- if (typeName === "ZodObject") return {
3876
+ });
3877
+ if (typeName === "ZodObject") return finalize({
3828
3878
  type: "object",
3829
3879
  required
3830
- };
3880
+ });
3831
3881
  const testString = schema.safeParse("test");
3832
3882
  const testNumber = schema.safeParse(123);
3833
3883
  const testBoolean = schema.safeParse(true);
3834
3884
  const testDate = schema.safeParse(17040672e5);
3835
- if (testBoolean.success && !testString.success && !testNumber.success) return {
3885
+ if (testBoolean.success && !testString.success && !testNumber.success) return finalize({
3836
3886
  type: "boolean",
3837
3887
  required
3838
- };
3839
- if (testDate.success && !testString.success) return {
3888
+ });
3889
+ if (testDate.success && !testString.success) return finalize({
3840
3890
  type: "date",
3841
3891
  required
3842
- };
3843
- if (testNumber.success && !testString.success) return {
3892
+ });
3893
+ if (testNumber.success && !testString.success) return finalize({
3844
3894
  type: "number",
3845
3895
  required
3846
- };
3847
- if (schema.safeParse([]).success && !testString.success) return {
3896
+ });
3897
+ if (schema.safeParse([]).success && !testString.success) return finalize({
3848
3898
  type: "array",
3849
3899
  required
3850
- };
3851
- return {
3900
+ });
3901
+ return finalize({
3852
3902
  type: "string",
3853
3903
  required
3854
- };
3904
+ });
3855
3905
  }
3856
3906
  /**
3857
3907
  * 渲染单元格内容
@@ -3893,9 +3943,14 @@ function renderCell(value, type) {
3893
3943
  * 从 Zod Schema 创建 Table Schema (ColumnDef 数组)
3894
3944
  */
3895
3945
  function createTableSchema(schema, options) {
3946
+ const { overrides, exclude = [] } = options ?? {};
3947
+ const canCache = !(!!overrides && Object.keys(overrides).length > 0) && exclude.length === 0;
3948
+ if (canCache) {
3949
+ const cached = baseTableSchemaCache.get(schema);
3950
+ if (cached) return [...cached];
3951
+ }
3896
3952
  const shape = schema.shape;
3897
3953
  const columns = [];
3898
- const { overrides, exclude = [] } = options ?? {};
3899
3954
  for (const [key, fieldSchema] of Object.entries(shape)) {
3900
3955
  if (exclude.includes(key)) continue;
3901
3956
  const override = overrides?.[key];
@@ -3930,6 +3985,7 @@ function createTableSchema(schema, options) {
3930
3985
  ...restOverride
3931
3986
  });
3932
3987
  }
3988
+ if (canCache) baseTableSchemaCache.set(schema, columns);
3933
3989
  return columns;
3934
3990
  }
3935
3991
  /**
@@ -3958,8 +4014,7 @@ function createSelectColumn() {
3958
4014
  /**
3959
4015
  * 创建操作列
3960
4016
  */
3961
- function createActionsColumn(config) {
3962
- const { onEdit, onDelete, onView, onCopy } = config;
4017
+ function createActionsColumn(items) {
3963
4018
  return {
3964
4019
  id: "actions",
3965
4020
  cell: ({ row }) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(__pixpilot_shadcn.DropdownMenu, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(__pixpilot_shadcn.DropdownMenuTrigger, {
@@ -3970,29 +4025,14 @@ function createActionsColumn(config) {
3970
4025
  className: "flex size-8 p-0 data-[state=open]:bg-muted",
3971
4026
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Ellipsis, { className: "size-4" })
3972
4027
  })
3973
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(__pixpilot_shadcn.DropdownMenuContent, {
4028
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__pixpilot_shadcn.DropdownMenuContent, {
3974
4029
  align: "end",
3975
4030
  className: "w-40",
3976
- children: [
3977
- onView && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__pixpilot_shadcn.DropdownMenuItem, {
3978
- onClick: () => onView(row.original),
3979
- children: "查看"
3980
- }),
3981
- onEdit && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__pixpilot_shadcn.DropdownMenuItem, {
3982
- onClick: () => onEdit(row.original),
3983
- children: "编辑"
3984
- }),
3985
- onCopy && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__pixpilot_shadcn.DropdownMenuItem, {
3986
- onClick: () => onCopy(row.original),
3987
- children: "复制"
3988
- }),
3989
- (onView || onEdit || onCopy) && onDelete && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__pixpilot_shadcn.DropdownMenuSeparator, {}),
3990
- onDelete && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__pixpilot_shadcn.DropdownMenuItem, {
3991
- className: "text-destructive",
3992
- onClick: () => onDelete(row.original),
3993
- children: "删除"
3994
- })
3995
- ]
4031
+ children: items.map((item, i) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react.Fragment, { children: [item.separator && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__pixpilot_shadcn.DropdownMenuSeparator, {}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__pixpilot_shadcn.DropdownMenuItem, {
4032
+ className: item.variant === "destructive" ? "text-destructive" : "",
4033
+ onClick: () => item.onClick(row.original),
4034
+ children: item.label
4035
+ })] }, i))
3996
4036
  })] }),
3997
4037
  size: 40
3998
4038
  };
@@ -4446,6 +4486,13 @@ function useDataTable(props) {
4446
4486
  filterableColumns,
4447
4487
  enableAdvancedFilter
4448
4488
  ]);
4489
+ const getCoreRowModelFn = react.useMemo(() => (0, __tanstack_react_table.getCoreRowModel)(), []);
4490
+ const getFilteredRowModelFn = react.useMemo(() => (0, __tanstack_react_table.getFilteredRowModel)(), []);
4491
+ const getPaginationRowModelFn = react.useMemo(() => (0, __tanstack_react_table.getPaginationRowModel)(), []);
4492
+ const getSortedRowModelFn = react.useMemo(() => (0, __tanstack_react_table.getSortedRowModel)(), []);
4493
+ const getFacetedRowModelFn = react.useMemo(() => (0, __tanstack_react_table.getFacetedRowModel)(), []);
4494
+ const getFacetedUniqueValuesFn = react.useMemo(() => (0, __tanstack_react_table.getFacetedUniqueValues)(), []);
4495
+ const getFacetedMinMaxValuesFn = react.useMemo(() => (0, __tanstack_react_table.getFacetedMinMaxValues)(), []);
4449
4496
  const table = (0, __tanstack_react_table.useReactTable)({
4450
4497
  ...tableProps,
4451
4498
  columns,
@@ -4468,13 +4515,13 @@ function useDataTable(props) {
4468
4515
  onSortingChange,
4469
4516
  onColumnFiltersChange,
4470
4517
  onColumnVisibilityChange: setColumnVisibility,
4471
- getCoreRowModel: (0, __tanstack_react_table.getCoreRowModel)(),
4472
- getFilteredRowModel: (0, __tanstack_react_table.getFilteredRowModel)(),
4473
- getPaginationRowModel: (0, __tanstack_react_table.getPaginationRowModel)(),
4474
- getSortedRowModel: (0, __tanstack_react_table.getSortedRowModel)(),
4475
- getFacetedRowModel: (0, __tanstack_react_table.getFacetedRowModel)(),
4476
- getFacetedUniqueValues: (0, __tanstack_react_table.getFacetedUniqueValues)(),
4477
- getFacetedMinMaxValues: (0, __tanstack_react_table.getFacetedMinMaxValues)(),
4518
+ getCoreRowModel: getCoreRowModelFn,
4519
+ getFilteredRowModel: getFilteredRowModelFn,
4520
+ getPaginationRowModel: getPaginationRowModelFn,
4521
+ getSortedRowModel: getSortedRowModelFn,
4522
+ getFacetedRowModel: getFacetedRowModelFn,
4523
+ getFacetedUniqueValues: getFacetedUniqueValuesFn,
4524
+ getFacetedMinMaxValues: getFacetedMinMaxValuesFn,
4478
4525
  manualPagination: true,
4479
4526
  manualSorting: true,
4480
4527
  manualFiltering: true,
@@ -4505,6 +4552,11 @@ function useDataTable(props) {
4505
4552
 
4506
4553
  //#endregion
4507
4554
  //#region src/components/auto-crud/auto-table.tsx
4555
+ const DEFAULT_MODES = [
4556
+ "simple",
4557
+ "advanced",
4558
+ "command"
4559
+ ];
4508
4560
  /** 过滤模式配置 */
4509
4561
  const filterModeConfig = {
4510
4562
  simple: {
@@ -4524,11 +4576,7 @@ const filterModeConfig = {
4524
4576
  }
4525
4577
  };
4526
4578
  function AutoTable({ schema, data, pageCount = 1, overrides, enableRowSelection = true, exclude, actions, pinnedColumns, filterMode = "simple", onDeleteSelected, onUpdateSelected, batchUpdateFields, actionBarExtra, initialSorting, enableExport = true, onSelectedCountChange, getSelectedRows }) {
4527
- const modes = filterMode ? Array.isArray(filterMode) ? filterMode : [filterMode] : [
4528
- "simple",
4529
- "advanced",
4530
- "command"
4531
- ];
4579
+ const modes = filterMode ? Array.isArray(filterMode) ? filterMode : [filterMode] : DEFAULT_MODES;
4532
4580
  const [currentMode, setCurrentMode] = useQueryState("filterMode", parseAsStringEnum(modes).withDefault(modes[0] ?? "simple"));
4533
4581
  const showToggle = modes.length > 1;
4534
4582
  const { table, shallow, debounceMs, throttleMs } = useDataTable({
@@ -4550,13 +4598,18 @@ function AutoTable({ schema, data, pageCount = 1, overrides, enableRowSelection
4550
4598
  ]),
4551
4599
  pageCount,
4552
4600
  enableAdvancedFilter: true,
4553
- initialState: {
4601
+ initialState: (0, react.useMemo)(() => ({
4554
4602
  sorting: initialSorting,
4555
4603
  columnPinning: pinnedColumns ?? {
4556
4604
  left: enableRowSelection ? ["select"] : [],
4557
4605
  right: actions ? ["actions"] : []
4558
4606
  }
4559
- },
4607
+ }), [
4608
+ initialSorting,
4609
+ pinnedColumns,
4610
+ enableRowSelection,
4611
+ actions
4612
+ ]),
4560
4613
  shallow: false,
4561
4614
  clearOnDefault: true
4562
4615
  });
@@ -4597,7 +4650,7 @@ function AutoTable({ schema, data, pageCount = 1, overrides, enableRowSelection
4597
4650
  })
4598
4651
  })
4599
4652
  })] }) : null;
4600
- const renderFilters = () => {
4653
+ const filtersContent = (0, react.useMemo)(() => {
4601
4654
  switch (currentMode) {
4602
4655
  case "simple": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AutoTableSimpleFilters, {
4603
4656
  table,
@@ -4617,7 +4670,13 @@ function AutoTable({ schema, data, pageCount = 1, overrides, enableRowSelection
4617
4670
  align: "start"
4618
4671
  });
4619
4672
  }
4620
- };
4673
+ }, [
4674
+ currentMode,
4675
+ table,
4676
+ shallow,
4677
+ debounceMs,
4678
+ throttleMs
4679
+ ]);
4621
4680
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4622
4681
  className: "space-y-4",
4623
4682
  children: [
@@ -4629,7 +4688,7 @@ function AutoTable({ schema, data, pageCount = 1, overrides, enableRowSelection
4629
4688
  children: [currentMode !== "simple" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DataTableSortList, {
4630
4689
  table,
4631
4690
  align: "start"
4632
- }), renderFilters()]
4691
+ }), filtersContent]
4633
4692
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4634
4693
  className: "flex items-center gap-2",
4635
4694
  children: [
@@ -4861,10 +4920,462 @@ function AutoFormInner({ schema: zodSchema, initialValues, onSubmit, overrides,
4861
4920
  }
4862
4921
  const AutoForm = (0, react.forwardRef)(AutoFormInner);
4863
4922
 
4923
+ //#endregion
4924
+ //#region src/i18n/locale.ts
4925
+ const zhCN = {
4926
+ toolbar: {
4927
+ create: "新建",
4928
+ import: "导入",
4929
+ export: "导出",
4930
+ exportSelected: (count) => `导出(${count})`
4931
+ },
4932
+ rowActions: {
4933
+ view: "查看",
4934
+ edit: "编辑",
4935
+ copy: "复制",
4936
+ delete: "删除"
4937
+ },
4938
+ viewModal: { title: "详情" },
4939
+ boolean: {
4940
+ true: "是",
4941
+ false: "否"
4942
+ },
4943
+ formModal: {
4944
+ createTitle: "新建",
4945
+ editTitle: "编辑",
4946
+ cancel: "取消",
4947
+ create: "创建",
4948
+ save: "保存",
4949
+ submitting: "处理中..."
4950
+ },
4951
+ deleteModal: {
4952
+ title: "确认删除",
4953
+ description: "此操作无法撤销。确定要删除这条记录吗?",
4954
+ cancel: "取消",
4955
+ confirm: "确认删除",
4956
+ confirming: "删除中..."
4957
+ },
4958
+ importDialog: {
4959
+ title: "导入数据",
4960
+ uploadDescription: "上传 CSV 或 JSON 文件以批量导入数据",
4961
+ previewDescription: (count) => `已解析 ${count} 条数据,确认后开始导入`,
4962
+ importingDescription: "正在导入数据...",
4963
+ doneDescription: "导入完成",
4964
+ dragHint: "拖拽文件到此处,或点击选择文件",
4965
+ formatHint: "支持 CSV、JSON 格式",
4966
+ downloadTemplate: "下载 CSV 模板",
4967
+ moreColumns: (count) => `+${count} 列`,
4968
+ previewSummary: (rows, fields, format, previewLimit) => `共 ${rows} 条数据${previewLimit ? `(预览前 ${previewLimit} 条)` : ""},${fields} 个字段,格式: ${format.toUpperCase()}`,
4969
+ reselect: "重新选择",
4970
+ confirmImport: "确认导入",
4971
+ importingRows: (count) => `正在导入 ${count} 条数据...`,
4972
+ resultCreated: "新建",
4973
+ resultUpdated: "更新",
4974
+ resultSkipped: "跳过",
4975
+ resultFailed: "验证失败",
4976
+ failedRowHeader: "行号",
4977
+ failedErrorHeader: "错误",
4978
+ close: "关闭",
4979
+ continueImport: "继续导入",
4980
+ errorNoData: "文件中没有数据行",
4981
+ errorParseFailed: "文件解析失败",
4982
+ errorImportFailed: "导入失败"
4983
+ }
4984
+ };
4985
+ const enUS = {
4986
+ toolbar: {
4987
+ create: "New",
4988
+ import: "Import",
4989
+ export: "Export",
4990
+ exportSelected: (count) => `Export (${count})`
4991
+ },
4992
+ rowActions: {
4993
+ view: "View",
4994
+ edit: "Edit",
4995
+ copy: "Copy",
4996
+ delete: "Delete"
4997
+ },
4998
+ viewModal: { title: "Details" },
4999
+ boolean: {
5000
+ true: "Yes",
5001
+ false: "No"
5002
+ },
5003
+ formModal: {
5004
+ createTitle: "New",
5005
+ editTitle: "Edit",
5006
+ cancel: "Cancel",
5007
+ create: "Create",
5008
+ save: "Save",
5009
+ submitting: "Processing..."
5010
+ },
5011
+ deleteModal: {
5012
+ title: "Confirm Delete",
5013
+ description: "This action cannot be undone. Are you sure you want to delete this record?",
5014
+ cancel: "Cancel",
5015
+ confirm: "Delete",
5016
+ confirming: "Deleting..."
5017
+ },
5018
+ importDialog: {
5019
+ title: "Import Data",
5020
+ uploadDescription: "Upload a CSV or JSON file to bulk import data",
5021
+ previewDescription: (count) => `Parsed ${count} records, confirm to start importing`,
5022
+ importingDescription: "Importing data...",
5023
+ doneDescription: "Import complete",
5024
+ dragHint: "Drag file here, or click to select",
5025
+ formatHint: "Supports CSV, JSON formats",
5026
+ downloadTemplate: "Download CSV Template",
5027
+ moreColumns: (count) => `+${count} more`,
5028
+ previewSummary: (rows, fields, format, previewLimit) => `${rows} records${previewLimit ? ` (preview first ${previewLimit})` : ""}, ${fields} fields, format: ${format.toUpperCase()}`,
5029
+ reselect: "Reselect",
5030
+ confirmImport: "Confirm Import",
5031
+ importingRows: (count) => `Importing ${count} records...`,
5032
+ resultCreated: "Created",
5033
+ resultUpdated: "Updated",
5034
+ resultSkipped: "Skipped",
5035
+ resultFailed: "Failed",
5036
+ failedRowHeader: "Row",
5037
+ failedErrorHeader: "Error",
5038
+ close: "Close",
5039
+ continueImport: "Import More",
5040
+ errorNoData: "No data rows found in file",
5041
+ errorParseFailed: "Failed to parse file",
5042
+ errorImportFailed: "Import failed"
5043
+ }
5044
+ };
5045
+ const jaJP = {
5046
+ toolbar: {
5047
+ create: "新規作成",
5048
+ import: "インポート",
5049
+ export: "エクスポート",
5050
+ exportSelected: (count) => `エクスポート(${count})`
5051
+ },
5052
+ rowActions: {
5053
+ view: "詳細",
5054
+ edit: "編集",
5055
+ copy: "コピー",
5056
+ delete: "削除"
5057
+ },
5058
+ viewModal: { title: "詳細" },
5059
+ boolean: {
5060
+ true: "はい",
5061
+ false: "いいえ"
5062
+ },
5063
+ formModal: {
5064
+ createTitle: "新規作成",
5065
+ editTitle: "編集",
5066
+ cancel: "キャンセル",
5067
+ create: "作成",
5068
+ save: "保存",
5069
+ submitting: "処理中..."
5070
+ },
5071
+ deleteModal: {
5072
+ title: "削除の確認",
5073
+ description: "この操作は元に戻せません。このレコードを削除してもよろしいですか?",
5074
+ cancel: "キャンセル",
5075
+ confirm: "削除する",
5076
+ confirming: "削除中..."
5077
+ },
5078
+ importDialog: {
5079
+ title: "データのインポート",
5080
+ uploadDescription: "CSVまたはJSONファイルをアップロードして一括インポート",
5081
+ previewDescription: (count) => `${count}件のデータを解析しました。確認してインポートを開始します`,
5082
+ importingDescription: "データをインポート中...",
5083
+ doneDescription: "インポート完了",
5084
+ dragHint: "ファイルをここにドラッグするか、クリックして選択",
5085
+ formatHint: "CSV・JSON形式に対応",
5086
+ downloadTemplate: "CSVテンプレートをダウンロード",
5087
+ moreColumns: (count) => `+${count}列`,
5088
+ previewSummary: (rows, fields, format, previewLimit) => `${rows}件${previewLimit ? `(上位${previewLimit}件プレビュー)` : ""}、${fields}フィールド、形式: ${format.toUpperCase()}`,
5089
+ reselect: "再選択",
5090
+ confirmImport: "インポートを確認",
5091
+ importingRows: (count) => `${count}件をインポート中...`,
5092
+ resultCreated: "新規作成",
5093
+ resultUpdated: "更新",
5094
+ resultSkipped: "スキップ",
5095
+ resultFailed: "失敗",
5096
+ failedRowHeader: "行番号",
5097
+ failedErrorHeader: "エラー",
5098
+ close: "閉じる",
5099
+ continueImport: "続けてインポート",
5100
+ errorNoData: "ファイルにデータ行がありません",
5101
+ errorParseFailed: "ファイルの解析に失敗しました",
5102
+ errorImportFailed: "インポートに失敗しました"
5103
+ }
5104
+ };
5105
+ const koKR = {
5106
+ toolbar: {
5107
+ create: "새로 만들기",
5108
+ import: "가져오기",
5109
+ export: "내보내기",
5110
+ exportSelected: (count) => `내보내기(${count})`
5111
+ },
5112
+ rowActions: {
5113
+ view: "보기",
5114
+ edit: "편집",
5115
+ copy: "복사",
5116
+ delete: "삭제"
5117
+ },
5118
+ viewModal: { title: "상세 정보" },
5119
+ boolean: {
5120
+ true: "예",
5121
+ false: "아니요"
5122
+ },
5123
+ formModal: {
5124
+ createTitle: "새로 만들기",
5125
+ editTitle: "편집",
5126
+ cancel: "취소",
5127
+ create: "만들기",
5128
+ save: "저장",
5129
+ submitting: "처리 중..."
5130
+ },
5131
+ deleteModal: {
5132
+ title: "삭제 확인",
5133
+ description: "이 작업은 취소할 수 없습니다. 이 레코드를 삭제하시겠습니까?",
5134
+ cancel: "취소",
5135
+ confirm: "삭제",
5136
+ confirming: "삭제 중..."
5137
+ },
5138
+ importDialog: {
5139
+ title: "데이터 가져오기",
5140
+ uploadDescription: "CSV 또는 JSON 파일을 업로드하여 일괄 가져오기",
5141
+ previewDescription: (count) => `${count}개의 데이터를 분석했습니다. 확인 후 가져오기를 시작합니다`,
5142
+ importingDescription: "데이터를 가져오는 중...",
5143
+ doneDescription: "가져오기 완료",
5144
+ dragHint: "파일을 여기에 드래그하거나 클릭하여 선택",
5145
+ formatHint: "CSV, JSON 형식 지원",
5146
+ downloadTemplate: "CSV 템플릿 다운로드",
5147
+ moreColumns: (count) => `+${count}열 더`,
5148
+ previewSummary: (rows, fields, format, previewLimit) => `${rows}개 레코드${previewLimit ? ` (상위 ${previewLimit}개 미리보기)` : ""}, ${fields}개 필드, 형식: ${format.toUpperCase()}`,
5149
+ reselect: "다시 선택",
5150
+ confirmImport: "가져오기 확인",
5151
+ importingRows: (count) => `${count}개의 레코드를 가져오는 중...`,
5152
+ resultCreated: "생성됨",
5153
+ resultUpdated: "업데이트됨",
5154
+ resultSkipped: "건너뜀",
5155
+ resultFailed: "실패",
5156
+ failedRowHeader: "행 번호",
5157
+ failedErrorHeader: "오류",
5158
+ close: "닫기",
5159
+ continueImport: "계속 가져오기",
5160
+ errorNoData: "파일에 데이터 행이 없습니다",
5161
+ errorParseFailed: "파일 파싱에 실패했습니다",
5162
+ errorImportFailed: "가져오기에 실패했습니다"
5163
+ }
5164
+ };
5165
+ const frFR = {
5166
+ toolbar: {
5167
+ create: "Nouveau",
5168
+ import: "Importer",
5169
+ export: "Exporter",
5170
+ exportSelected: (count) => `Exporter (${count})`
5171
+ },
5172
+ rowActions: {
5173
+ view: "Voir",
5174
+ edit: "Modifier",
5175
+ copy: "Copier",
5176
+ delete: "Supprimer"
5177
+ },
5178
+ viewModal: { title: "Détails" },
5179
+ boolean: {
5180
+ true: "Oui",
5181
+ false: "Non"
5182
+ },
5183
+ formModal: {
5184
+ createTitle: "Nouveau",
5185
+ editTitle: "Modifier",
5186
+ cancel: "Annuler",
5187
+ create: "Créer",
5188
+ save: "Enregistrer",
5189
+ submitting: "En cours..."
5190
+ },
5191
+ deleteModal: {
5192
+ title: "Confirmer la suppression",
5193
+ description: "Cette action est irréversible. Voulez-vous vraiment supprimer cet enregistrement ?",
5194
+ cancel: "Annuler",
5195
+ confirm: "Supprimer",
5196
+ confirming: "Suppression..."
5197
+ },
5198
+ importDialog: {
5199
+ title: "Importer des données",
5200
+ uploadDescription: "Téléchargez un fichier CSV ou JSON pour importer en masse",
5201
+ previewDescription: (count) => `${count} enregistrements analysés, confirmez pour démarrer l'import`,
5202
+ importingDescription: "Importation en cours...",
5203
+ doneDescription: "Import terminé",
5204
+ dragHint: "Glissez un fichier ici ou cliquez pour sélectionner",
5205
+ formatHint: "Formats CSV et JSON acceptés",
5206
+ downloadTemplate: "Télécharger le modèle CSV",
5207
+ moreColumns: (count) => `+${count} colonnes`,
5208
+ previewSummary: (rows, fields, format, previewLimit) => `${rows} enregistrements${previewLimit ? ` (aperçu des ${previewLimit} premiers)` : ""}, ${fields} champs, format : ${format.toUpperCase()}`,
5209
+ reselect: "Resélectionner",
5210
+ confirmImport: "Confirmer l'import",
5211
+ importingRows: (count) => `Importation de ${count} enregistrements...`,
5212
+ resultCreated: "Créés",
5213
+ resultUpdated: "Mis à jour",
5214
+ resultSkipped: "Ignorés",
5215
+ resultFailed: "Échoués",
5216
+ failedRowHeader: "Ligne",
5217
+ failedErrorHeader: "Erreur",
5218
+ close: "Fermer",
5219
+ continueImport: "Continuer l'import",
5220
+ errorNoData: "Aucune ligne de données dans le fichier",
5221
+ errorParseFailed: "Échec de l'analyse du fichier",
5222
+ errorImportFailed: "Échec de l'importation"
5223
+ }
5224
+ };
5225
+ const deDE = {
5226
+ toolbar: {
5227
+ create: "Neu",
5228
+ import: "Importieren",
5229
+ export: "Exportieren",
5230
+ exportSelected: (count) => `Exportieren (${count})`
5231
+ },
5232
+ rowActions: {
5233
+ view: "Anzeigen",
5234
+ edit: "Bearbeiten",
5235
+ copy: "Kopieren",
5236
+ delete: "Löschen"
5237
+ },
5238
+ viewModal: { title: "Details" },
5239
+ boolean: {
5240
+ true: "Ja",
5241
+ false: "Nein"
5242
+ },
5243
+ formModal: {
5244
+ createTitle: "Neu",
5245
+ editTitle: "Bearbeiten",
5246
+ cancel: "Abbrechen",
5247
+ create: "Erstellen",
5248
+ save: "Speichern",
5249
+ submitting: "Wird verarbeitet..."
5250
+ },
5251
+ deleteModal: {
5252
+ title: "Löschen bestätigen",
5253
+ description: "Diese Aktion kann nicht rückgängig gemacht werden. Möchten Sie diesen Datensatz wirklich löschen?",
5254
+ cancel: "Abbrechen",
5255
+ confirm: "Löschen",
5256
+ confirming: "Wird gelöscht..."
5257
+ },
5258
+ importDialog: {
5259
+ title: "Daten importieren",
5260
+ uploadDescription: "CSV- oder JSON-Datei hochladen, um Daten massenweise zu importieren",
5261
+ previewDescription: (count) => `${count} Datensätze analysiert, bestätigen Sie den Import`,
5262
+ importingDescription: "Daten werden importiert...",
5263
+ doneDescription: "Import abgeschlossen",
5264
+ dragHint: "Datei hierher ziehen oder klicken zum Auswählen",
5265
+ formatHint: "CSV- und JSON-Formate werden unterstützt",
5266
+ downloadTemplate: "CSV-Vorlage herunterladen",
5267
+ moreColumns: (count) => `+${count} Spalten`,
5268
+ previewSummary: (rows, fields, format, previewLimit) => `${rows} Datensätze${previewLimit ? ` (Vorschau der ersten ${previewLimit})` : ""}, ${fields} Felder, Format: ${format.toUpperCase()}`,
5269
+ reselect: "Neu auswählen",
5270
+ confirmImport: "Import bestätigen",
5271
+ importingRows: (count) => `${count} Datensätze werden importiert...`,
5272
+ resultCreated: "Erstellt",
5273
+ resultUpdated: "Aktualisiert",
5274
+ resultSkipped: "Übersprungen",
5275
+ resultFailed: "Fehlgeschlagen",
5276
+ failedRowHeader: "Zeile",
5277
+ failedErrorHeader: "Fehler",
5278
+ close: "Schließen",
5279
+ continueImport: "Weiteren Import starten",
5280
+ errorNoData: "Keine Datenzeilen in der Datei",
5281
+ errorParseFailed: "Datei konnte nicht analysiert werden",
5282
+ errorImportFailed: "Import fehlgeschlagen"
5283
+ }
5284
+ };
5285
+ const esES = {
5286
+ toolbar: {
5287
+ create: "Nuevo",
5288
+ import: "Importar",
5289
+ export: "Exportar",
5290
+ exportSelected: (count) => `Exportar (${count})`
5291
+ },
5292
+ rowActions: {
5293
+ view: "Ver",
5294
+ edit: "Editar",
5295
+ copy: "Copiar",
5296
+ delete: "Eliminar"
5297
+ },
5298
+ viewModal: { title: "Detalles" },
5299
+ boolean: {
5300
+ true: "Sí",
5301
+ false: "No"
5302
+ },
5303
+ formModal: {
5304
+ createTitle: "Nuevo",
5305
+ editTitle: "Editar",
5306
+ cancel: "Cancelar",
5307
+ create: "Crear",
5308
+ save: "Guardar",
5309
+ submitting: "Procesando..."
5310
+ },
5311
+ deleteModal: {
5312
+ title: "Confirmar eliminación",
5313
+ description: "Esta acción no se puede deshacer. ¿Está seguro de que desea eliminar este registro?",
5314
+ cancel: "Cancelar",
5315
+ confirm: "Eliminar",
5316
+ confirming: "Eliminando..."
5317
+ },
5318
+ importDialog: {
5319
+ title: "Importar datos",
5320
+ uploadDescription: "Suba un archivo CSV o JSON para importar datos de forma masiva",
5321
+ previewDescription: (count) => `Se analizaron ${count} registros, confirme para iniciar la importación`,
5322
+ importingDescription: "Importando datos...",
5323
+ doneDescription: "Importación completada",
5324
+ dragHint: "Arrastre el archivo aquí o haga clic para seleccionar",
5325
+ formatHint: "Formatos CSV y JSON compatibles",
5326
+ downloadTemplate: "Descargar plantilla CSV",
5327
+ moreColumns: (count) => `+${count} columnas`,
5328
+ previewSummary: (rows, fields, format, previewLimit) => `${rows} registros${previewLimit ? ` (vista previa de los primeros ${previewLimit})` : ""}, ${fields} campos, formato: ${format.toUpperCase()}`,
5329
+ reselect: "Volver a seleccionar",
5330
+ confirmImport: "Confirmar importación",
5331
+ importingRows: (count) => `Importando ${count} registros...`,
5332
+ resultCreated: "Creados",
5333
+ resultUpdated: "Actualizados",
5334
+ resultSkipped: "Omitidos",
5335
+ resultFailed: "Fallidos",
5336
+ failedRowHeader: "Fila",
5337
+ failedErrorHeader: "Error",
5338
+ close: "Cerrar",
5339
+ continueImport: "Continuar importando",
5340
+ errorNoData: "No se encontraron filas de datos en el archivo",
5341
+ errorParseFailed: "Error al analizar el archivo",
5342
+ errorImportFailed: "Error en la importación"
5343
+ }
5344
+ };
5345
+ const BUILTIN_LOCALES = {
5346
+ "zh-CN": zhCN,
5347
+ "en-US": enUS,
5348
+ "ja-JP": jaJP,
5349
+ "ko-KR": koKR,
5350
+ "fr-FR": frFR,
5351
+ "de-DE": deDE,
5352
+ "es-ES": esES
5353
+ };
5354
+ function deepMerge(target, source) {
5355
+ const result = { ...target };
5356
+ for (const key of Object.keys(source)) {
5357
+ const sv = source[key];
5358
+ const tv = target[key];
5359
+ if (sv !== void 0 && sv !== null && typeof sv === "object" && !Array.isArray(sv) && typeof tv === "object" && tv !== null && !Array.isArray(tv)) result[key] = deepMerge(tv, sv);
5360
+ else if (sv !== void 0) result[key] = sv;
5361
+ }
5362
+ return result;
5363
+ }
5364
+ /**
5365
+ * 解析 locale prop 为完整的 AutoCrudLocale
5366
+ * - string → 内置语言(fallback: zh-CN)
5367
+ * - object → 以 zh-CN 为基础深合并
5368
+ */
5369
+ function resolveLocale(localeProp) {
5370
+ if (!localeProp) return zhCN;
5371
+ if (typeof localeProp === "string") return BUILTIN_LOCALES[localeProp] ?? zhCN;
5372
+ return deepMerge(zhCN, localeProp);
5373
+ }
5374
+
4864
5375
  //#endregion
4865
5376
  //#region src/components/auto-crud/crud-form-modal.tsx
4866
- function CrudFormModal({ open, onOpenChange, mode, schema, initialValues, onSubmit, loading = false, variant = "dialog", overrides, title, labelAlign, labelWidth }) {
4867
- const defaultTitle = mode === "create" ? "新建" : "编辑";
5377
+ function CrudFormModal({ open, onOpenChange, mode, schema, initialValues, onSubmit, loading = false, variant = "dialog", overrides, title, locale = zhCN.formModal, labelAlign, labelWidth }) {
5378
+ const defaultTitle = mode === "create" ? locale.createTitle : locale.editTitle;
4868
5379
  const formRef = (0, react.useRef)(null);
4869
5380
  const handleSubmit = async () => {
4870
5381
  await formRef.current?.submit();
@@ -4880,12 +5391,12 @@ function CrudFormModal({ open, onOpenChange, mode, schema, initialValues, onSubm
4880
5391
  type: "button",
4881
5392
  variant: "outline",
4882
5393
  onClick: () => onOpenChange(false),
4883
- children: "取消"
5394
+ children: locale.cancel
4884
5395
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__pixpilot_shadcn.Button, {
4885
5396
  type: "button",
4886
5397
  onClick: handleSubmit,
4887
5398
  disabled: loading,
4888
- children: loading ? "处理中..." : mode === "create" ? "创建" : "保存"
5399
+ children: loading ? locale.submitting : mode === "create" ? locale.create : locale.save
4889
5400
  })]
4890
5401
  }),
4891
5402
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AutoForm, {
@@ -4905,7 +5416,7 @@ function CrudFormModal({ open, onOpenChange, mode, schema, initialValues, onSubm
4905
5416
 
4906
5417
  //#endregion
4907
5418
  //#region src/components/auto-crud/import-dialog.tsx
4908
- function ImportDialog({ open, onOpenChange, onImport, columns = [], title = "导入数据" }) {
5419
+ function ImportDialog({ open, onOpenChange, onImport, columns = [], title, locale = zhCN.importDialog }) {
4909
5420
  const [step, setStep] = react.useState("upload");
4910
5421
  const [parsedData, setParsedData] = react.useState(null);
4911
5422
  const [error, setError] = react.useState(null);
@@ -4928,13 +5439,13 @@ function ImportDialog({ open, onOpenChange, onImport, columns = [], title = "导
4928
5439
  try {
4929
5440
  const data = await parseImportFile(file);
4930
5441
  if (data.rows.length === 0) {
4931
- setError("文件中没有数据行");
5442
+ setError(locale.errorNoData);
4932
5443
  return;
4933
5444
  }
4934
5445
  setParsedData(data);
4935
5446
  setStep("preview");
4936
5447
  } catch (e) {
4937
- setError(e instanceof Error ? e.message : "文件解析失败");
5448
+ setError(e instanceof Error ? e.message : locale.errorParseFailed);
4938
5449
  }
4939
5450
  }, []);
4940
5451
  const handleDrop = react.useCallback((e) => {
@@ -4954,7 +5465,7 @@ function ImportDialog({ open, onOpenChange, onImport, columns = [], title = "导
4954
5465
  setResult(await onImport(parsedData.rows));
4955
5466
  setStep("result");
4956
5467
  } catch (e) {
4957
- setError(e instanceof Error ? e.message : "导入失败");
5468
+ setError(e instanceof Error ? e.message : locale.errorImportFailed);
4958
5469
  setStep("preview");
4959
5470
  }
4960
5471
  }, [parsedData, onImport]);
@@ -4974,11 +5485,11 @@ function ImportDialog({ open, onOpenChange, onImport, columns = [], title = "导
4974
5485
  onOpenChange: handleOpenChange,
4975
5486
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(__pixpilot_shadcn.DialogContent, {
4976
5487
  className: "sm:max-w-[600px]",
4977
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(__pixpilot_shadcn.DialogHeader, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(__pixpilot_shadcn.DialogTitle, { children: title }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(__pixpilot_shadcn.DialogDescription, { children: [
4978
- step === "upload" && "上传 CSV 或 JSON 文件以批量导入数据",
4979
- step === "preview" && `已解析 ${parsedData?.rows.length ?? 0} 条数据,确认后开始导入`,
4980
- step === "importing" && "正在导入数据...",
4981
- step === "result" && "导入完成"
5488
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(__pixpilot_shadcn.DialogHeader, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(__pixpilot_shadcn.DialogTitle, { children: title ?? locale.title }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(__pixpilot_shadcn.DialogDescription, { children: [
5489
+ step === "upload" && locale.uploadDescription,
5490
+ step === "preview" && locale.previewDescription(parsedData?.rows.length ?? 0),
5491
+ step === "importing" && locale.importingDescription,
5492
+ step === "result" && locale.doneDescription
4982
5493
  ] })] }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4983
5494
  className: "space-y-4",
4984
5495
  children: [
@@ -5009,11 +5520,11 @@ function ImportDialog({ open, onOpenChange, onImport, columns = [], title = "导
5009
5520
  }),
5010
5521
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
5011
5522
  className: "text-sm text-muted-foreground",
5012
- children: "拖拽文件到此处,或点击选择文件"
5523
+ children: locale.dragHint
5013
5524
  }),
5014
5525
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
5015
5526
  className: "text-xs text-muted-foreground/60",
5016
- children: "支持 CSV、JSON 格式"
5527
+ children: locale.formatHint
5017
5528
  })
5018
5529
  ]
5019
5530
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
@@ -5028,7 +5539,7 @@ function ImportDialog({ open, onOpenChange, onImport, columns = [], title = "导
5028
5539
  size: "sm",
5029
5540
  className: "w-full",
5030
5541
  onClick: handleDownloadTemplate,
5031
- children: "下载 CSV 模板"
5542
+ children: locale.downloadTemplate
5032
5543
  })] }),
5033
5544
  step === "preview" && parsedData && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
5034
5545
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
@@ -5048,13 +5559,9 @@ function ImportDialog({ open, onOpenChange, onImport, columns = [], title = "导
5048
5559
  className: "px-3 py-2 text-left font-medium text-muted-foreground",
5049
5560
  children: h
5050
5561
  }, h)),
5051
- parsedData.headers.length > 6 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("th", {
5562
+ parsedData.headers.length > 6 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", {
5052
5563
  className: "px-3 py-2 text-left font-medium text-muted-foreground",
5053
- children: [
5054
- "+",
5055
- parsedData.headers.length - 6,
5056
- " 列"
5057
- ]
5564
+ children: locale.moreColumns(parsedData.headers.length - 6)
5058
5565
  })
5059
5566
  ] })
5060
5567
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("tbody", { children: parsedData.rows.slice(0, 10).map((row, i) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("tr", {
@@ -5077,40 +5584,27 @@ function ImportDialog({ open, onOpenChange, onImport, columns = [], title = "导
5077
5584
  })
5078
5585
  })
5079
5586
  }),
5080
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
5587
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
5081
5588
  className: "text-sm text-muted-foreground",
5082
- children: [
5083
- "共 ",
5084
- parsedData.rows.length,
5085
- " 条数据",
5086
- parsedData.rows.length > 10 && "(预览前 10 条)",
5087
- ",",
5088
- parsedData.headers.length,
5089
- " 个字段 ,格式: ",
5090
- parsedData.format.toUpperCase()
5091
- ]
5589
+ children: locale.previewSummary(parsedData.rows.length, parsedData.headers.length, parsedData.format, parsedData.rows.length > 10 ? 10 : void 0)
5092
5590
  }),
5093
5591
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5094
5592
  className: "flex gap-2 justify-end",
5095
5593
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(__pixpilot_shadcn.Button, {
5096
5594
  variant: "outline",
5097
5595
  onClick: reset,
5098
- children: "重新选择"
5596
+ children: locale.reselect
5099
5597
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__pixpilot_shadcn.Button, {
5100
5598
  onClick: handleImport,
5101
- children: "确认导入"
5599
+ children: locale.confirmImport
5102
5600
  })]
5103
5601
  })
5104
5602
  ] }),
5105
5603
  step === "importing" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5106
5604
  className: "flex flex-col items-center gap-4 py-8",
5107
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "h-8 w-8 animate-spin rounded-full border-4 border-primary border-t-transparent" }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
5605
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "h-8 w-8 animate-spin rounded-full border-4 border-primary border-t-transparent" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
5108
5606
  className: "text-sm text-muted-foreground",
5109
- children: [
5110
- "正在导入 ",
5111
- parsedData?.rows.length ?? 0,
5112
- " 条数据..."
5113
- ]
5607
+ children: locale.importingRows(parsedData?.rows.length ?? 0)
5114
5608
  })]
5115
5609
  }),
5116
5610
  step === "result" && result && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
@@ -5125,7 +5619,7 @@ function ImportDialog({ open, onOpenChange, onImport, columns = [], title = "导
5125
5619
  children: result.success
5126
5620
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
5127
5621
  className: "text-xs text-green-600/80",
5128
- children: "新建"
5622
+ children: locale.resultCreated
5129
5623
  })]
5130
5624
  }),
5131
5625
  (result.updated ?? 0) > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
@@ -5135,7 +5629,7 @@ function ImportDialog({ open, onOpenChange, onImport, columns = [], title = "导
5135
5629
  children: result.updated
5136
5630
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
5137
5631
  className: "text-xs text-blue-600/80",
5138
- children: "更新"
5632
+ children: locale.resultUpdated
5139
5633
  })]
5140
5634
  }),
5141
5635
  result.skipped > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
@@ -5145,7 +5639,7 @@ function ImportDialog({ open, onOpenChange, onImport, columns = [], title = "导
5145
5639
  children: result.skipped
5146
5640
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
5147
5641
  className: "text-xs text-yellow-600/80",
5148
- children: "跳过"
5642
+ children: locale.resultSkipped
5149
5643
  })]
5150
5644
  }),
5151
5645
  result.failed.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
@@ -5155,7 +5649,7 @@ function ImportDialog({ open, onOpenChange, onImport, columns = [], title = "导
5155
5649
  children: result.failed.length
5156
5650
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
5157
5651
  className: "text-xs text-red-600/80",
5158
- children: "验证失败"
5652
+ children: locale.resultFailed
5159
5653
  })]
5160
5654
  })
5161
5655
  ]
@@ -5169,10 +5663,10 @@ function ImportDialog({ open, onOpenChange, onImport, columns = [], title = "导
5169
5663
  className: "sticky top-0 bg-red-50 dark:bg-red-950/30",
5170
5664
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("tr", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", {
5171
5665
  className: "px-3 py-2 text-left font-medium text-red-600",
5172
- children: "行号"
5666
+ children: locale.failedRowHeader
5173
5667
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", {
5174
5668
  className: "px-3 py-2 text-left font-medium text-red-600",
5175
- children: "错误"
5669
+ children: locale.failedErrorHeader
5176
5670
  })] })
5177
5671
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("tbody", { children: result.failed.map((f) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("tr", {
5178
5672
  className: "border-t border-red-100 dark:border-red-900",
@@ -5192,11 +5686,11 @@ function ImportDialog({ open, onOpenChange, onImport, columns = [], title = "导
5192
5686
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(__pixpilot_shadcn.Button, {
5193
5687
  variant: "outline",
5194
5688
  onClick: () => handleOpenChange(false),
5195
- children: "关闭"
5689
+ children: locale.close
5196
5690
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__pixpilot_shadcn.Button, {
5197
5691
  variant: "outline",
5198
5692
  onClick: reset,
5199
- children: "继续导入"
5693
+ children: locale.continueImport
5200
5694
  })]
5201
5695
  })] }),
5202
5696
  error && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
@@ -5218,26 +5712,51 @@ function ImportDialog({ open, onOpenChange, onImport, columns = [], title = "导
5218
5712
  function buildTableOverrides(fields, legacyOverrides) {
5219
5713
  const result = { ...legacyOverrides };
5220
5714
  if (fields) for (const [key, config] of Object.entries(fields)) {
5715
+ const tableMeta = config.table !== false && typeof config.table === "object" ? config.table.meta : void 0;
5221
5716
  let filterMeta;
5222
- if (config.filter && config.filter.enabled !== false && !config.filter.hidden) {
5223
- const { enabled: _, hidden: __,...filterProps } = config.filter;
5224
- if (Object.keys(filterProps).length > 0) filterMeta = filterProps;
5717
+ if (config.filter && typeof config.filter === "object") {
5718
+ if (config.filter.enabled !== false && config.filter.hidden !== true) {
5719
+ const { enabled: _, hidden: __,...filterProps } = config.filter;
5720
+ if (Object.keys(filterProps).length > 0) filterMeta = filterProps;
5721
+ }
5722
+ }
5723
+ if (config.filter === false) result[key] = {
5724
+ ...result[key],
5725
+ enableColumnFilter: false
5726
+ };
5727
+ else if (config.filter && typeof config.filter === "object") {
5728
+ if (config.filter.enabled === false || config.filter.hidden === true) result[key] = {
5729
+ ...result[key],
5730
+ enableColumnFilter: false
5731
+ };
5225
5732
  }
5226
- result[key] = {
5733
+ if (tableMeta || filterMeta) result[key] = {
5227
5734
  ...result[key],
5228
- ...config.label && { label: config.label },
5229
- ...config.hidden && { hidden: true },
5230
- ...config.table,
5231
- ...filterMeta && { meta: {
5735
+ meta: {
5232
5736
  ...result[key]?.meta ?? {},
5233
- ...config.table?.meta ?? {},
5234
- ...filterMeta
5235
- } }
5737
+ ...tableMeta ?? {},
5738
+ ...filterMeta ?? {}
5739
+ }
5236
5740
  };
5237
- if (config.filter?.enabled === false || config.filter?.hidden === true) result[key] = {
5741
+ if (config.label) result[key] = {
5238
5742
  ...result[key],
5239
- enableColumnFilter: false
5743
+ label: config.label
5240
5744
  };
5745
+ if (config.hidden) result[key] = {
5746
+ ...result[key],
5747
+ hidden: true
5748
+ };
5749
+ if (config.table === false) result[key] = {
5750
+ ...result[key],
5751
+ hidden: true
5752
+ };
5753
+ else if (config.table && typeof config.table === "object") {
5754
+ const { meta,...tableProps } = config.table;
5755
+ result[key] = {
5756
+ ...result[key],
5757
+ ...tableProps
5758
+ };
5759
+ }
5241
5760
  }
5242
5761
  return result;
5243
5762
  }
@@ -5251,12 +5770,24 @@ function buildFormOverrides(fields, legacyOverrides, denyFields) {
5251
5770
  ...result[field],
5252
5771
  "x-hidden": true
5253
5772
  };
5254
- if (fields) for (const [key, config] of Object.entries(fields)) result[key] = {
5255
- ...result[key],
5256
- ...config.label && { title: config.label },
5257
- ...config.hidden && { "x-hidden": true },
5258
- ...config.form
5259
- };
5773
+ if (fields) for (const [key, config] of Object.entries(fields)) {
5774
+ if (config.label) result[key] = {
5775
+ ...result[key],
5776
+ title: config.label
5777
+ };
5778
+ if (config.hidden) result[key] = {
5779
+ ...result[key],
5780
+ "x-hidden": true
5781
+ };
5782
+ if (config.form === false) result[key] = {
5783
+ ...result[key],
5784
+ "x-hidden": true
5785
+ };
5786
+ else if (config.form && typeof config.form === "object") result[key] = {
5787
+ ...result[key],
5788
+ ...config.form
5789
+ };
5790
+ }
5260
5791
  return result;
5261
5792
  }
5262
5793
  /**
@@ -5265,7 +5796,9 @@ function buildFormOverrides(fields, legacyOverrides, denyFields) {
5265
5796
  function buildHiddenColumns(fields, legacyHidden, denyFields) {
5266
5797
  const hidden = new Set([...legacyHidden ?? [], ...denyFields ?? []]);
5267
5798
  if (fields) {
5268
- for (const [key, config] of Object.entries(fields)) if (config.hidden || config.table?.hidden) hidden.add(key);
5799
+ for (const [key, config] of Object.entries(fields)) if (config.hidden) hidden.add(key);
5800
+ else if (config.table === false) hidden.add(key);
5801
+ else if (config.table && typeof config.table === "object" && config.table.hidden) hidden.add(key);
5269
5802
  }
5270
5803
  return Array.from(hidden);
5271
5804
  }
@@ -5307,13 +5840,13 @@ function buildBatchUpdateFields(schema, batchFields, fields) {
5307
5840
  /**
5308
5841
  * 渲染字段值
5309
5842
  */
5310
- function renderFieldValue(value, type) {
5843
+ function renderFieldValue(value, type, booleanLocale) {
5311
5844
  if (value === null || value === void 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5312
5845
  className: "text-muted-foreground",
5313
5846
  children: "-"
5314
5847
  });
5315
5848
  switch (type) {
5316
- case "boolean": return value ? "是" : "否";
5849
+ case "boolean": return value ? booleanLocale.true : booleanLocale.false;
5317
5850
  case "date": return formatDate(value);
5318
5851
  case "enum": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__pixpilot_shadcn.Badge, {
5319
5852
  variant: "outline",
@@ -5336,7 +5869,7 @@ function renderFieldValue(value, type) {
5336
5869
  /**
5337
5870
  * ViewModal 组件 - 详情查看弹窗
5338
5871
  */
5339
- function ViewModal({ open, onOpenChange, variant, data, schema, fields: fieldConfig, denyFields }) {
5872
+ function ViewModal({ open, onOpenChange, variant, data, schema, fields: fieldConfig, denyFields, locale }) {
5340
5873
  if (!data) return null;
5341
5874
  const shape = schema.shape;
5342
5875
  const denySet = new Set(denyFields ?? []);
@@ -5356,7 +5889,7 @@ function ViewModal({ open, onOpenChange, variant, data, schema, fields: fieldCon
5356
5889
  children: label
5357
5890
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("dd", {
5358
5891
  className: "col-span-2 text-sm",
5359
- children: renderFieldValue(value, parsed.type)
5892
+ children: renderFieldValue(value, parsed.type, locale.boolean)
5360
5893
  })]
5361
5894
  }, key);
5362
5895
  })
@@ -5364,23 +5897,89 @@ function ViewModal({ open, onOpenChange, variant, data, schema, fields: fieldCon
5364
5897
  if (variant === "sheet") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__pixpilot_shadcn.Sheet, {
5365
5898
  open,
5366
5899
  onOpenChange,
5367
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(__pixpilot_shadcn.SheetContent, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(__pixpilot_shadcn.SheetHeader, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__pixpilot_shadcn.SheetTitle, { children: "详情" }) }), content] })
5900
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(__pixpilot_shadcn.SheetContent, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(__pixpilot_shadcn.SheetHeader, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__pixpilot_shadcn.SheetTitle, { children: locale.viewModal.title }) }), content] })
5368
5901
  });
5369
5902
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__pixpilot_shadcn.Dialog, {
5370
5903
  open,
5371
5904
  onOpenChange,
5372
5905
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(__pixpilot_shadcn.DialogContent, {
5373
5906
  className: "max-w-2xl max-h-[80vh] overflow-y-auto",
5374
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(__pixpilot_shadcn.DialogHeader, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__pixpilot_shadcn.DialogTitle, { children: "详情" }) }), content]
5907
+ 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]
5375
5908
  })
5376
5909
  });
5377
5910
  }
5911
+ function resolveActions(items, defaults, rowActionsLocale) {
5912
+ const defaultItems = [
5913
+ {
5914
+ label: rowActionsLocale.view,
5915
+ onClick: defaults.openView
5916
+ },
5917
+ ...defaults.openEdit ? [{
5918
+ label: rowActionsLocale.edit,
5919
+ onClick: defaults.openEdit
5920
+ }] : [],
5921
+ ...defaults.copyRow ? [{
5922
+ label: rowActionsLocale.copy,
5923
+ onClick: defaults.copyRow
5924
+ }] : [],
5925
+ ...defaults.openDelete ? [{
5926
+ label: rowActionsLocale.delete,
5927
+ onClick: defaults.openDelete,
5928
+ separator: true,
5929
+ variant: "destructive"
5930
+ }] : []
5931
+ ];
5932
+ if (!items || items.length === 0) return defaultItems;
5933
+ if (!items.some((i) => i.type !== "custom")) {
5934
+ const startItems = items.filter((i) => i.type === "custom" && i.position === "start").map(({ label, onClick, separator, variant }) => ({
5935
+ label,
5936
+ onClick,
5937
+ separator,
5938
+ variant
5939
+ }));
5940
+ const endItems = items.filter((i) => i.type === "custom" && i.position !== "start").map(({ label, onClick, separator, variant }) => ({
5941
+ label,
5942
+ onClick,
5943
+ separator,
5944
+ variant
5945
+ }));
5946
+ return [
5947
+ ...startItems,
5948
+ ...defaultItems,
5949
+ ...endItems
5950
+ ];
5951
+ }
5952
+ const handlerMap = {
5953
+ view: defaults.openView,
5954
+ edit: defaults.openEdit,
5955
+ copy: defaults.copyRow,
5956
+ delete: defaults.openDelete
5957
+ };
5958
+ const variantMap = { delete: "destructive" };
5959
+ return items.flatMap((item) => {
5960
+ if (item.type === "custom") return [{
5961
+ label: item.label,
5962
+ onClick: item.onClick,
5963
+ separator: item.separator,
5964
+ variant: item.variant
5965
+ }];
5966
+ const handler = item.onClick ?? handlerMap[item.type];
5967
+ if (!handler) return [];
5968
+ return [{
5969
+ label: item.label ?? rowActionsLocale[item.type],
5970
+ onClick: handler,
5971
+ separator: item.separator,
5972
+ variant: variantMap[item.type]
5973
+ }];
5974
+ });
5975
+ }
5378
5976
  /**
5379
5977
  * AutoCrudTable 组件
5380
5978
  *
5381
5979
  * 高级 CRUD 表格组件,封装了完整的增删改查流程
5382
5980
  */
5383
- function AutoCrudTable({ title, description, schema, resource, fields, table: tableConfig, form: formConfig, slots, permissions }) {
5981
+ function AutoCrudTable({ title, description, schema, resource, fields, table: tableConfig, form: formConfig, slots, permissions, actions: actionItems, locale: localeProp }) {
5982
+ const locale = resolveLocale(localeProp);
5384
5983
  const can = {
5385
5984
  create: permissions?.can?.create ?? true,
5386
5985
  update: permissions?.can?.update ?? true,
@@ -5431,10 +6030,22 @@ function AutoCrudTable({ title, description, schema, resource, fields, table: ta
5431
6030
  setExporting(false);
5432
6031
  }
5433
6032
  }, [selectedCount, handleExport]);
5434
- const tableOverrides = buildTableOverrides(fields, tableConfig?.overrides);
5435
- const formOverrides = buildFormOverrides(fields, formConfig?.overrides, denyFields);
5436
- const hiddenColumns = buildHiddenColumns(fields, tableConfig?.hidden, denyFields);
5437
- const batchFields = buildBatchUpdateFields(schema, tableConfig?.batchFields, fields);
6033
+ const tableOverrides = react.useMemo(() => buildTableOverrides(fields, tableConfig?.overrides), [fields, tableConfig?.overrides]);
6034
+ const formOverrides = react.useMemo(() => buildFormOverrides(fields, formConfig?.overrides, denyFields), [
6035
+ fields,
6036
+ formConfig?.overrides,
6037
+ denyFields
6038
+ ]);
6039
+ const hiddenColumns = react.useMemo(() => buildHiddenColumns(fields, tableConfig?.hidden, denyFields), [
6040
+ fields,
6041
+ tableConfig?.hidden,
6042
+ denyFields
6043
+ ]);
6044
+ const batchFields = react.useMemo(() => buildBatchUpdateFields(schema, tableConfig?.batchFields, fields), [
6045
+ schema,
6046
+ tableConfig?.batchFields,
6047
+ fields
6048
+ ]);
5438
6049
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5439
6050
  className: "space-y-4",
5440
6051
  children: [
@@ -5461,18 +6072,18 @@ function AutoCrudTable({ title, description, schema, resource, fields, table: ta
5461
6072
  variant: "outline",
5462
6073
  size: "sm",
5463
6074
  onClick: () => setImportOpen(true),
5464
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Download, { className: "mr-2 h-4 w-4" }), "导入"]
6075
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Download, { className: "mr-2 h-4 w-4" }), locale.toolbar.import]
5465
6076
  }),
5466
6077
  canExport && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(__pixpilot_shadcn.Button, {
5467
6078
  variant: "outline",
5468
6079
  size: "sm",
5469
6080
  onClick: handleExportClick,
5470
6081
  disabled: exporting || selectedCount === 0 && !resource.handlers.export,
5471
- 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" }), selectedCount > 0 ? `导出(${selectedCount})` : "导出"]
6082
+ 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" }), selectedCount > 0 ? locale.toolbar.exportSelected(selectedCount) : locale.toolbar.export]
5472
6083
  }),
5473
6084
  can.create && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__pixpilot_shadcn.Button, {
5474
6085
  onClick: resource.handlers.openCreate,
5475
- children: "新建"
6086
+ children: locale.toolbar.create
5476
6087
  })
5477
6088
  ]
5478
6089
  })]
@@ -5484,12 +6095,12 @@ function AutoCrudTable({ title, description, schema, resource, fields, table: ta
5484
6095
  overrides: tableOverrides,
5485
6096
  exclude: hiddenColumns,
5486
6097
  filterMode: tableConfig?.filterModes,
5487
- actions: {
5488
- onView: resource.handlers.openView,
5489
- onEdit: can.update ? resource.handlers.openEdit : void 0,
5490
- onCopy: can.create ? resource.handlers.copyRow : void 0,
5491
- onDelete: can.delete ? resource.handlers.openDelete : void 0
5492
- },
6098
+ actions: resolveActions(actionItems, {
6099
+ openView: resource.handlers.openView,
6100
+ openEdit: can.update ? resource.handlers.openEdit : void 0,
6101
+ copyRow: can.create ? resource.handlers.copyRow : void 0,
6102
+ openDelete: can.delete ? resource.handlers.openDelete : void 0
6103
+ }, locale.rowActions),
5493
6104
  onDeleteSelected: can.delete ? resource.handlers.deleteMany : void 0,
5494
6105
  onUpdateSelected: can.update ? resource.handlers.updateMany : void 0,
5495
6106
  batchUpdateFields: can.update ? batchFields : void 0,
@@ -5510,7 +6121,8 @@ function AutoCrudTable({ title, description, schema, resource, fields, table: ta
5510
6121
  },
5511
6122
  loading: resource.mutations.isCreating || resource.mutations.isUpdating,
5512
6123
  variant: resource.modal.variant,
5513
- overrides: formOverrides
6124
+ overrides: formOverrides,
6125
+ locale: locale.formModal
5514
6126
  }),
5515
6127
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ViewModal, {
5516
6128
  open: resource.modal.viewOpen,
@@ -5519,22 +6131,24 @@ function AutoCrudTable({ title, description, schema, resource, fields, table: ta
5519
6131
  data: resource.modal.selected,
5520
6132
  schema,
5521
6133
  fields,
5522
- denyFields
6134
+ denyFields,
6135
+ locale
5523
6136
  }),
5524
6137
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__pixpilot_shadcn.AlertDialog, {
5525
6138
  open: resource.modal.deleteOpen,
5526
6139
  onOpenChange: (open) => !open && resource.handlers.closeModals(),
5527
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(__pixpilot_shadcn.AlertDialogContent, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(__pixpilot_shadcn.AlertDialogHeader, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(__pixpilot_shadcn.AlertDialogTitle, { children: "确认删除" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__pixpilot_shadcn.AlertDialogDescription, { children: "此操作无法撤销。确定要删除这条记录吗?" })] }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(__pixpilot_shadcn.AlertDialogFooter, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(__pixpilot_shadcn.AlertDialogCancel, { children: "取消" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__pixpilot_shadcn.AlertDialogAction, {
6140
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(__pixpilot_shadcn.AlertDialogContent, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(__pixpilot_shadcn.AlertDialogHeader, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(__pixpilot_shadcn.AlertDialogTitle, { children: locale.deleteModal.title }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__pixpilot_shadcn.AlertDialogDescription, { children: locale.deleteModal.description })] }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(__pixpilot_shadcn.AlertDialogFooter, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(__pixpilot_shadcn.AlertDialogCancel, { children: locale.deleteModal.cancel }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__pixpilot_shadcn.AlertDialogAction, {
5528
6141
  onClick: resource.handlers.confirmDelete,
5529
6142
  disabled: resource.mutations.isDeleting,
5530
- children: resource.mutations.isDeleting ? "删除中..." : "确认删除"
6143
+ children: resource.mutations.isDeleting ? locale.deleteModal.confirming : locale.deleteModal.confirm
5531
6144
  })] })] })
5532
6145
  }),
5533
6146
  canImport && resource.handlers.import && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ImportDialog, {
5534
6147
  open: importOpen,
5535
6148
  onOpenChange: setImportOpen,
5536
6149
  onImport: resource.handlers.import,
5537
- columns: importColumns
6150
+ columns: importColumns,
6151
+ locale: locale.importDialog
5538
6152
  })
5539
6153
  ]
5540
6154
  });
@@ -6166,6 +6780,8 @@ function DataTableToolbarFilter({ column }) {
6166
6780
  const columnMeta = column.columnDef.meta;
6167
6781
  return react.useCallback(() => {
6168
6782
  if (!columnMeta?.variant) return null;
6783
+ const modes = columnMeta.modes;
6784
+ if (modes && !modes.includes("advanced")) return null;
6169
6785
  switch (columnMeta.variant) {
6170
6786
  case "text": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__pixpilot_shadcn.Input, {
6171
6787
  placeholder: columnMeta.placeholder ?? columnMeta.label,
@@ -6287,6 +6903,10 @@ function modalReducer(state, action) {
6287
6903
  function useAutoCrudResource({ router, schema, query: queryTransform, options = {} }) {
6288
6904
  const { idKey = "id", defaultVariant = "dialog", hooks, toast: toastAdapter, exportFetcher } = options;
6289
6905
  const toast = (0, react.useMemo)(() => toastAdapter === false ? noopToastAdapter : toastAdapter ?? defaultToastAdapter, [toastAdapter]);
6906
+ const hooksRef = (0, react.useRef)(hooks);
6907
+ (0, react.useEffect)(() => {
6908
+ hooksRef.current = hooks;
6909
+ }, [hooks]);
6290
6910
  const columns = (0, react.useMemo)(() => createTableSchema(schema), [schema]);
6291
6911
  const [urlParams] = useQueryStates({
6292
6912
  page: parseAsInteger.withDefault(1),
@@ -6350,15 +6970,16 @@ function useAutoCrudResource({ router, schema, query: queryTransform, options =
6350
6970
  payload: variant
6351
6971
  }), []);
6352
6972
  const submitCreate = (0, react.useCallback)(async (values) => {
6973
+ const currentHooks = hooksRef.current;
6353
6974
  try {
6354
- if (hooks?.beforeCreate) {
6355
- const result = await hooks.beforeCreate(values);
6975
+ if (currentHooks?.beforeCreate) {
6976
+ const result = await currentHooks.beforeCreate(values);
6356
6977
  if (typeof result === "boolean") {
6357
6978
  if (result) {
6358
6979
  toast.success("创建成功");
6359
6980
  closeModals();
6360
6981
  listQuery.refetch();
6361
- hooks?.onSuccess?.("create", values);
6982
+ currentHooks?.onSuccess?.("create", values);
6362
6983
  }
6363
6984
  return;
6364
6985
  }
@@ -6369,36 +6990,36 @@ function useAutoCrudResource({ router, schema, query: queryTransform, options =
6369
6990
  toast.success("创建成功");
6370
6991
  closeModals();
6371
6992
  listQuery.refetch();
6372
- hooks?.onSuccess?.("create", data);
6993
+ currentHooks?.onSuccess?.("create", data);
6373
6994
  },
6374
6995
  onError: (error) => {
6375
6996
  const err = error;
6376
6997
  toast.error(`创建失败: ${err.message}`);
6377
- hooks?.onError?.("create", err);
6998
+ currentHooks?.onError?.("create", err);
6378
6999
  }
6379
7000
  });
6380
7001
  } catch (error) {
6381
7002
  const err = error;
6382
7003
  toast.error(`创建失败: ${err.message}`);
6383
- hooks?.onError?.("create", err);
7004
+ currentHooks?.onError?.("create", err);
6384
7005
  }
6385
7006
  }, [
6386
7007
  createMutation,
6387
7008
  closeModals,
6388
- listQuery,
6389
- hooks
7009
+ listQuery
6390
7010
  ]);
6391
7011
  const submitUpdate = (0, react.useCallback)(async (values) => {
7012
+ const currentHooks = hooksRef.current;
6392
7013
  if (!modal.selected) return;
6393
7014
  try {
6394
- if (hooks?.beforeUpdate) {
6395
- const result = await hooks.beforeUpdate(values, modal.selected);
7015
+ if (currentHooks?.beforeUpdate) {
7016
+ const result = await currentHooks.beforeUpdate(values, modal.selected);
6396
7017
  if (typeof result === "boolean") {
6397
7018
  if (result) {
6398
7019
  toast.success("更新成功");
6399
7020
  closeModals();
6400
7021
  listQuery.refetch();
6401
- hooks?.onSuccess?.("update", values);
7022
+ currentHooks?.onSuccess?.("update", values);
6402
7023
  }
6403
7024
  return;
6404
7025
  }
@@ -6413,38 +7034,38 @@ function useAutoCrudResource({ router, schema, query: queryTransform, options =
6413
7034
  toast.success("更新成功");
6414
7035
  closeModals();
6415
7036
  listQuery.refetch();
6416
- hooks?.onSuccess?.("update", data);
7037
+ currentHooks?.onSuccess?.("update", data);
6417
7038
  },
6418
7039
  onError: (error) => {
6419
7040
  const err = error;
6420
7041
  toast.error(`更新失败: ${err.message}`);
6421
- hooks?.onError?.("update", err);
7042
+ currentHooks?.onError?.("update", err);
6422
7043
  }
6423
7044
  });
6424
7045
  } catch (error) {
6425
7046
  const err = error;
6426
7047
  toast.error(`更新失败: ${err.message}`);
6427
- hooks?.onError?.("update", err);
7048
+ currentHooks?.onError?.("update", err);
6428
7049
  }
6429
7050
  }, [
6430
7051
  modal.selected,
6431
7052
  idKey,
6432
7053
  updateMutation,
6433
7054
  closeModals,
6434
- listQuery,
6435
- hooks
7055
+ listQuery
6436
7056
  ]);
6437
7057
  const confirmDelete = (0, react.useCallback)(async () => {
7058
+ const currentHooks = hooksRef.current;
6438
7059
  if (!modal.selected) return;
6439
7060
  try {
6440
- if (hooks?.beforeDelete) {
6441
- const result = await hooks.beforeDelete(modal.selected);
7061
+ if (currentHooks?.beforeDelete) {
7062
+ const result = await currentHooks.beforeDelete(modal.selected);
6442
7063
  if (typeof result === "boolean") {
6443
7064
  if (result) {
6444
7065
  toast.success("删除成功");
6445
7066
  closeModals();
6446
7067
  listQuery.refetch();
6447
- hooks?.onSuccess?.("delete", modal.selected);
7068
+ currentHooks?.onSuccess?.("delete", modal.selected);
6448
7069
  }
6449
7070
  return;
6450
7071
  }
@@ -6455,28 +7076,28 @@ function useAutoCrudResource({ router, schema, query: queryTransform, options =
6455
7076
  toast.success("删除成功");
6456
7077
  closeModals();
6457
7078
  listQuery.refetch();
6458
- hooks?.onSuccess?.("delete", data);
7079
+ currentHooks?.onSuccess?.("delete", data);
6459
7080
  },
6460
7081
  onError: (error) => {
6461
7082
  const err = error;
6462
7083
  toast.error(`删除失败: ${err.message}`);
6463
- hooks?.onError?.("delete", err);
7084
+ currentHooks?.onError?.("delete", err);
6464
7085
  }
6465
7086
  });
6466
7087
  } catch (error) {
6467
7088
  const err = error;
6468
7089
  toast.error(`删除失败: ${err.message}`);
6469
- hooks?.onError?.("delete", err);
7090
+ currentHooks?.onError?.("delete", err);
6470
7091
  }
6471
7092
  }, [
6472
7093
  modal.selected,
6473
7094
  idKey,
6474
7095
  deleteMutation,
6475
7096
  closeModals,
6476
- listQuery,
6477
- hooks
7097
+ listQuery
6478
7098
  ]);
6479
7099
  const deleteMany = (0, react.useCallback)((rows) => {
7100
+ const currentHooks = hooksRef.current;
6480
7101
  if (!deleteManyMutation) {
6481
7102
  toast.error("批量删除功能未启用");
6482
7103
  return;
@@ -6486,21 +7107,21 @@ function useAutoCrudResource({ router, schema, query: queryTransform, options =
6486
7107
  onSuccess: (data) => {
6487
7108
  toast.success(`成功删除 ${ids.length} 条记录`);
6488
7109
  listQuery.refetch();
6489
- hooks?.onSuccess?.("delete", data);
7110
+ currentHooks?.onSuccess?.("delete", data);
6490
7111
  },
6491
7112
  onError: (error) => {
6492
7113
  const err = error;
6493
7114
  toast.error(`批量删除失败: ${err.message}`);
6494
- hooks?.onError?.("delete", err);
7115
+ currentHooks?.onError?.("delete", err);
6495
7116
  }
6496
7117
  });
6497
7118
  }, [
6498
7119
  deleteManyMutation,
6499
7120
  idKey,
6500
- listQuery,
6501
- hooks
7121
+ listQuery
6502
7122
  ]);
6503
7123
  const updateMany = (0, react.useCallback)((rows, data) => {
7124
+ const currentHooks = hooksRef.current;
6504
7125
  if (!updateManyMutation) {
6505
7126
  toast.error("批量更新功能未启用");
6506
7127
  return;
@@ -6513,19 +7134,18 @@ function useAutoCrudResource({ router, schema, query: queryTransform, options =
6513
7134
  onSuccess: (result) => {
6514
7135
  toast.success(`成功更新 ${ids.length} 条记录`);
6515
7136
  listQuery.refetch();
6516
- hooks?.onSuccess?.("update", result);
7137
+ currentHooks?.onSuccess?.("update", result);
6517
7138
  },
6518
7139
  onError: (error) => {
6519
7140
  const err = error;
6520
7141
  toast.error(`批量更新失败: ${err.message}`);
6521
- hooks?.onError?.("update", err);
7142
+ currentHooks?.onError?.("update", err);
6522
7143
  }
6523
7144
  });
6524
7145
  }, [
6525
7146
  updateManyMutation,
6526
7147
  idKey,
6527
- listQuery,
6528
- hooks
7148
+ listQuery
6529
7149
  ]);
6530
7150
  const copyRow = (0, react.useCallback)((row) => {
6531
7151
  dispatch({
@@ -6924,13 +7544,19 @@ exports.createSelectColumn = createSelectColumn;
6924
7544
  exports.createTRPCDataSource = createTRPCDataSource;
6925
7545
  exports.createTableSchema = createTableSchema;
6926
7546
  exports.dataToCSV = dataToCSV;
7547
+ exports.deDE = deDE;
6927
7548
  exports.downloadCSVTemplate = downloadCSVTemplate;
7549
+ exports.enUS = enUS;
7550
+ exports.esES = esES;
6928
7551
  exports.exportAllToCSV = exportAllToCSV;
6929
7552
  exports.exportTableToCSV = exportTableToCSV;
6930
7553
  exports.formatDate = formatDate;
7554
+ exports.frFR = frFR;
6931
7555
  exports.generateCSVTemplate = generateCSVTemplate;
6932
7556
  exports.getUrlParams = getUrlParams;
6933
7557
  exports.humanize = humanize;
7558
+ exports.jaJP = jaJP;
7559
+ exports.koKR = koKR;
6934
7560
  exports.noopToastAdapter = noopToastAdapter;
6935
7561
  exports.parseAsArrayOf = parseAsArrayOf;
6936
7562
  exports.parseAsInteger = parseAsInteger;
@@ -6940,6 +7566,7 @@ exports.parseCSV = parseCSV;
6940
7566
  exports.parseImportFile = parseImportFile;
6941
7567
  exports.parseJSON = parseJSON;
6942
7568
  exports.parseZodField = parseZodField;
7569
+ exports.resolveLocale = resolveLocale;
6943
7570
  exports.setSearchParams = setSearchParams;
6944
7571
  exports.useAutoCrudResource = useAutoCrudResource;
6945
7572
  exports.useDataTable = useDataTable;
@@ -6947,4 +7574,5 @@ exports.useQueryState = useQueryState;
6947
7574
  exports.useQueryStates = useQueryStates;
6948
7575
  exports.useReadableFilters = useReadableFilters;
6949
7576
  exports.useUrlState = useUrlState;
6950
- exports.useUrlStates = useUrlStates;
7577
+ exports.useUrlStates = useUrlStates;
7578
+ exports.zhCN = zhCN;