From 44ac1c9a2a6ee6ac9f618f4a63510f8f94d1b1a9 Mon Sep 17 00:00:00 2001
From: zhou zhou <3272660260@qq.com>
Date: 星期三, 25 三月 2026 15:43:51 +0800
Subject: [PATCH] #打印+导出

---
 rsf-admin/src/page/warehouseAreas/WarehouseAreasList.jsx                                             |  313 +++++-----
 rsf-server/src/main/java/com/vincent/rsf/server/common/utils/ExcelUtil.java                          |  216 +++++++
 rsf-server/src/main/java/com/vincent/rsf/server/common/service/ListExportService.java                |  154 +++++
 rsf-server/src/main/java/com/vincent/rsf/server/manager/controller/WarehouseAreasItemController.java |   72 ++
 rsf-server/src/main/java/com/vincent/rsf/server/manager/controller/WarehouseAreasController.java     |   50 +
 rsf-admin/src/page/components/ListExportPrintButton.jsx                                              |  545 +++++++++++++++++++
 rsf-server/src/main/java/com/vincent/rsf/server/common/service/ListExportHandler.java                |   20 
 rsf-admin/src/page/warehouseAreasItem/WarehouseAreasItemList.jsx                                     |  216 ++++---
 rsf-admin/src/page/components/listExportPrintUtils.js                                                |   62 ++
 9 files changed, 1,402 insertions(+), 246 deletions(-)

diff --git a/rsf-admin/src/page/components/ListExportPrintButton.jsx b/rsf-admin/src/page/components/ListExportPrintButton.jsx
new file mode 100644
index 0000000..8b7d770
--- /dev/null
+++ b/rsf-admin/src/page/components/ListExportPrintButton.jsx
@@ -0,0 +1,545 @@
+import React, { useMemo, useRef, useState } from "react";
+import DownloadIcon from "@mui/icons-material/GetApp";
+import PrintOutlinedIcon from "@mui/icons-material/PrintOutlined";
+import SaveIcon from '@mui/icons-material/Save';
+import {
+    Box,
+    Button as MuiButton,
+    CircularProgress,
+    Dialog,
+    DialogActions,
+    DialogContent,
+    DialogTitle,
+    Paper,
+    Table,
+    TableBody,
+    TableCell,
+    TableContainer,
+    TableHead,
+    TableRow,
+    Typography,
+} from '@mui/material';
+import {
+    Button,
+    useDataProvider,
+    useListContext,
+    useNotify,
+    useStore,
+    useTranslate,
+    useUnselectAll,
+} from "react-admin";
+import { useReactToPrint } from "react-to-print";
+import DialogCloseButton from "./DialogCloseButton";
+import { getValueBySource, resolveVisibleColumns } from "./listExportPrintUtils";
+
+const PREVIEW_ROW_LIMIT = 200;
+
+const formatColumnLabel = (label, translate) => {
+    if (!label) {
+        return '';
+    }
+
+    if (typeof label !== 'string') {
+        return String(label);
+    }
+
+    if (label.includes('.')) {
+        return translate(label, { _: label });
+    }
+
+    return label;
+};
+
+const formatValue = (value, fieldType) => {
+    if (value == null) {
+        return '';
+    }
+
+    if (fieldType === 'boolean') {
+        return value ? 'true' : 'false';
+    }
+
+    return String(value);
+};
+
+const padNumber = (value) => String(value).padStart(2, '0');
+
+const formatDate = (date = new Date()) => (
+    `${date.getFullYear()}骞�${padNumber(date.getMonth() + 1)}鏈�${padNumber(date.getDate())}鏃
+);
+
+const formatDateTime = (date = new Date()) => (
+    `${date.getFullYear()}/${padNumber(date.getMonth() + 1)}/${padNumber(date.getDate())} ${padNumber(date.getHours())}:${padNumber(date.getMinutes())}:${padNumber(date.getSeconds())}`
+);
+
+const getPrintLayoutConfig = (columnCount) => {
+    if (columnCount >= 16) {
+        return {
+            bodyFontSize: 8,
+            headerFontSize: 8,
+            cellPaddingX: 0.35,
+            cellPaddingY: 0.4,
+            sequenceWidth: 36,
+        };
+    }
+
+    if (columnCount >= 12) {
+        return {
+            bodyFontSize: 9,
+            headerFontSize: 9,
+            cellPaddingX: 0.45,
+            cellPaddingY: 0.5,
+            sequenceWidth: 42,
+        };
+    }
+
+    return {
+        bodyFontSize: 10,
+        headerFontSize: 10,
+        cellPaddingX: 0.6,
+        cellPaddingY: 0.65,
+        sequenceWidth: 52,
+    };
+};
+
+const getPrintOperator = () => {
+    try {
+        const user = JSON.parse(localStorage.getItem('user'));
+        return user?.fullName || user?.username || 'SYSTEM';
+    } catch (error) {
+        return 'SYSTEM';
+    }
+};
+
+const buildReportMeta = (reportTitle, count = 0) => ({
+    reportTitle,
+    reportDate: formatDate(),
+    printedAt: formatDateTime(),
+    operator: getPrintOperator(),
+    count,
+});
+
+const getRecordKey = (record, index) => (
+    record?.id ?? record?.barcode ?? record?.code ?? `row-${index}`
+);
+
+const PrintReportContent = ({
+    contentRef,
+    rows,
+    reportTitle,
+    printMeta,
+    translatedColumns,
+    printLayoutConfig,
+    sequenceColumnWidth,
+    dataColumnWidth,
+    previewNotice,
+}) => (
+    <Box
+        ref={contentRef}
+        sx={{
+            width: '277mm',
+            minHeight: '186mm',
+            mx: 'auto',
+            px: 1.5,
+            py: 2,
+            backgroundColor: '#fff',
+            color: '#111827',
+            boxSizing: 'border-box',
+        }}
+    >
+        <Typography
+            variant="h5"
+            sx={{
+                mb: 1,
+                textAlign: 'center',
+                fontWeight: 700,
+                letterSpacing: '0.08em',
+            }}
+        >
+            {reportTitle}
+        </Typography>
+        <Box
+            sx={{
+                mb: 1.5,
+                borderBottom: '2px solid #111827',
+            }}
+        />
+        <Box
+            sx={{
+                display: 'flex',
+                justifyContent: 'space-between',
+                gap: 2,
+                flexWrap: 'wrap',
+                mb: 2,
+                fontSize: 12,
+                color: '#374151',
+            }}
+        >
+            <Typography variant="body2">鎶ヨ〃鏃ユ湡: {printMeta.reportDate}</Typography>
+            <Typography variant="body2">鎵撳嵃浜�: {printMeta.operator}</Typography>
+            <Typography variant="body2">鎵撳嵃鏃堕棿: {printMeta.printedAt}</Typography>
+            <Typography variant="body2">璁板綍鏁�: {printMeta.count}</Typography>
+        </Box>
+        {previewNotice && (
+            <Typography variant="body2" sx={{ mb: 1.5, color: '#6b7280' }}>
+                {previewNotice}
+            </Typography>
+        )}
+        <TableContainer
+            component={Paper}
+            sx={{
+                boxShadow: 'none',
+                border: 'none',
+                backgroundColor: 'transparent',
+            }}
+        >
+            <Table
+                size="small"
+                sx={{
+                    width: '100%',
+                    tableLayout: 'fixed',
+                    '& .MuiTableCell-root': {
+                        borderBottom: 'none',
+                        fontSize: printLayoutConfig.bodyFontSize,
+                        px: printLayoutConfig.cellPaddingX,
+                        py: printLayoutConfig.cellPaddingY,
+                        whiteSpace: 'normal',
+                        wordBreak: 'break-all',
+                        overflowWrap: 'anywhere',
+                        lineHeight: 1.35,
+                        verticalAlign: 'top',
+                        boxSizing: 'border-box',
+                    },
+                    '& .MuiTableHead-root .MuiTableCell-root': {
+                        backgroundColor: '#f9fafb',
+                        fontWeight: 700,
+                        fontSize: printLayoutConfig.headerFontSize,
+                        textAlign: 'center',
+                    },
+                    '& .MuiTableRow-root': {
+                        pageBreakInside: 'avoid',
+                        breakInside: 'avoid',
+                    },
+                }}
+            >
+                <colgroup>
+                    <col style={{ width: sequenceColumnWidth }} />
+                    {translatedColumns.map((column) => (
+                        <col key={column.source} style={{ width: dataColumnWidth }} />
+                    ))}
+                </colgroup>
+                <TableHead>
+                    <TableRow>
+                        <TableCell align="center">
+                            搴忓彿
+                        </TableCell>
+                        {translatedColumns.map((column) => (
+                            <TableCell key={column.source}>
+                                {column.labelText}
+                            </TableCell>
+                        ))}
+                    </TableRow>
+                </TableHead>
+                <TableBody>
+                    {rows.map((record, index) => (
+                        <TableRow key={getRecordKey(record, index)}>
+                            <TableCell align="center">
+                                {padNumber(index + 1)}
+                            </TableCell>
+                            {translatedColumns.map((column) => (
+                                <TableCell key={column.source}>
+                                    {formatValue(getValueBySource(record, column.source), column.fieldType)}
+                                </TableCell>
+                            ))}
+                        </TableRow>
+                    ))}
+                </TableBody>
+            </Table>
+        </TableContainer>
+    </Box>
+);
+
+const ListExportPrintButton = ({
+    storeKey,
+    columns = [],
+    title,
+    fileName,
+    maxResults = 1000,
+}) => {
+    const {
+        filter,
+        selectedIds = [],
+        filterValues = {},
+        resource,
+        sort,
+        total = 0,
+    } = useListContext();
+    const [hiddenColumns] = useStore(
+        storeKey,
+        columns.filter((column) => column.hidden).map((column) => column.source)
+    );
+    const [columnRanks] = useStore(`${storeKey}_columnRanks`, []);
+    const dataProvider = useDataProvider();
+    const notify = useNotify();
+    const translate = useTranslate();
+    const unselectAll = useUnselectAll(resource);
+    const previewContentRef = useRef(null);
+    const fullPrintContentRef = useRef(null);
+    const [printOpen, setPrintOpen] = useState(false);
+    const [printLoading, setPrintLoading] = useState(false);
+    const [printRows, setPrintRows] = useState([]);
+    const [renderFullPrintContent, setRenderFullPrintContent] = useState(false);
+
+    const defaultHiddenColumns = useMemo(
+        () => columns.filter((column) => column.hidden).map((column) => column.source),
+        [columns]
+    );
+
+    const visibleColumns = useMemo(
+        () => resolveVisibleColumns(storeKey, defaultHiddenColumns, columns, hiddenColumns, columnRanks),
+        [storeKey, defaultHiddenColumns, columns, hiddenColumns, columnRanks]
+    );
+
+    const mergedFilter = useMemo(
+        () => (filter ? { ...filterValues, ...filter } : filterValues),
+        [filter, filterValues]
+    );
+
+    const translatedTitle = useMemo(
+        () => formatColumnLabel(title || resource, translate),
+        [title, resource, translate]
+    );
+
+    const reportTitle = useMemo(() => `${translatedTitle}鎶ヨ〃`, [translatedTitle]);
+    const [printMeta, setPrintMeta] = useState(() => buildReportMeta(reportTitle, 0));
+
+    const translatedColumns = useMemo(
+        () => visibleColumns.map((column) => ({
+            ...column,
+            labelText: formatColumnLabel(column.label, translate),
+        })),
+        [visibleColumns, translate]
+    );
+    const previewRows = useMemo(
+        () => printRows.slice(0, PREVIEW_ROW_LIMIT),
+        [printRows]
+    );
+    const previewNotice = printRows.length > PREVIEW_ROW_LIMIT
+        ? `褰撳墠浠呴瑙堝墠 ${PREVIEW_ROW_LIMIT} 鏉★紝鎵撳嵃灏嗗寘鍚叏閮� ${printRows.length} 鏉¤褰昤
+        : '';
+    const printLayoutConfig = useMemo(
+        () => getPrintLayoutConfig(translatedColumns.length),
+        [translatedColumns.length]
+    );
+    const sequenceColumnWidth = `${printLayoutConfig.sequenceWidth}px`;
+    const dataColumnWidth = translatedColumns.length > 0
+        ? `calc((100% - ${sequenceColumnWidth}) / ${translatedColumns.length})`
+        : 'auto';
+
+    const isDisabled = total === 0 || translatedColumns.length === 0;
+
+    const getPerPage = () => {
+        if (selectedIds.length > 0) {
+            return Math.min(selectedIds.length, maxResults);
+        }
+
+        if (total > 0) {
+            return Math.min(total, maxResults);
+        }
+
+        return maxResults;
+    };
+
+    const downloadBlob = (response) => {
+        const url = window.URL.createObjectURL(
+            new Blob([response.data], { type: response.headers["content-type"] }),
+        );
+        const link = document.createElement("a");
+        link.href = url;
+        link.setAttribute("download", `${fileName || resource}.xlsx`);
+        document.body.appendChild(link);
+        link.click();
+        link.remove();
+        window.URL.revokeObjectURL(url);
+    };
+
+    const handleExport = (event) => {
+        if (translatedColumns.length === 0) {
+            notify('鏆傛棤鍙鍑虹殑鍒�', { type: 'warning' });
+            return;
+        }
+
+        dataProvider
+            .export(resource, {
+                sort,
+                ids: selectedIds,
+                filter: mergedFilter,
+                pagination: { page: 1, perPage: getPerPage() },
+                meta: {
+                    ...buildReportMeta(reportTitle, selectedIds.length > 0 ? selectedIds.length : total),
+                    columns: translatedColumns.map((column) => ({
+                        source: column.source,
+                        label: column.labelText,
+                    })),
+                },
+            })
+            .then((response) => {
+                downloadBlob(response);
+                unselectAll();
+            })
+            .catch((error) => {
+                console.error(error);
+                notify("ra.notification.http_error", { type: "error" });
+            });
+
+        if (typeof event?.stopPropagation === 'function') {
+            event.stopPropagation();
+        }
+    };
+
+    const loadPrintRows = async () => {
+        if (translatedColumns.length === 0) {
+            notify('鏆傛棤鍙墦鍗扮殑鍒�', { type: 'warning' });
+            return;
+        }
+
+        setPrintOpen(true);
+        setPrintLoading(true);
+        try {
+            let rows = [];
+            if (selectedIds.length > 0) {
+                const response = await dataProvider.getMany(resource, { ids: selectedIds });
+                rows = response.data || [];
+            } else {
+                const response = await dataProvider.getList(resource, {
+                    sort,
+                    filter: mergedFilter,
+                    pagination: { page: 1, perPage: getPerPage() },
+                });
+                rows = response.data || [];
+            }
+            setPrintRows(rows);
+            setPrintMeta(buildReportMeta(reportTitle, rows.length));
+        } catch (error) {
+            console.error(error);
+            notify("ra.notification.http_error", { type: "error" });
+            setPrintOpen(false);
+        } finally {
+            setPrintLoading(false);
+        }
+    };
+
+    const handlePrintDialogClose = () => {
+        setPrintOpen(false);
+        setPrintRows([]);
+    };
+
+    const triggerPrint = useReactToPrint({
+        content: () => fullPrintContentRef.current,
+        documentTitle: translatedTitle,
+        onAfterPrint: () => {
+            setRenderFullPrintContent(false);
+        },
+        pageStyle: `
+            @page {
+                size: A4 landscape;
+                margin: 12mm 10mm;
+            }
+            html, body {
+                width: 297mm;
+                min-height: 210mm;
+            }
+            body {
+                -webkit-print-color-adjust: exact;
+                print-color-adjust: exact;
+                margin: 0;
+            }
+        `,
+    });
+
+    const handlePrint = async () => {
+        if (printLoading || printRows.length === 0) {
+            return;
+        }
+
+        setRenderFullPrintContent(true);
+        await new Promise((resolve) => setTimeout(resolve, 0));
+        if (typeof triggerPrint === 'function') {
+            triggerPrint();
+        } else {
+            setRenderFullPrintContent(false);
+        }
+    };
+
+    return (
+        <>
+            <Button
+                onClick={handleExport}
+                label="ra.action.export"
+                disabled={isDisabled}
+            >
+                <DownloadIcon />
+            </Button>
+            <Button
+                onClick={loadPrintRows}
+                label="toolbar.print"
+                disabled={isDisabled}
+            >
+                <PrintOutlinedIcon />
+            </Button>
+            <Dialog open={printOpen} maxWidth="xl" fullWidth onClose={handlePrintDialogClose}>
+                <DialogCloseButton onClose={handlePrintDialogClose} />
+                <DialogTitle>{translatedTitle}</DialogTitle>
+                <DialogContent dividers>
+                    {printLoading ? (
+                        <Box sx={{ py: 8, display: 'flex', justifyContent: 'center' }}>
+                            <CircularProgress size={28} />
+                        </Box>
+                    ) : printRows.length === 0 ? (
+                        <Box sx={{ py: 8, textAlign: 'center' }}>
+                            <Typography color="text.secondary">鏆傛棤鍙墦鍗版暟鎹�</Typography>
+                        </Box>
+                    ) : (
+                        <PrintReportContent
+                            contentRef={previewContentRef}
+                            rows={previewRows}
+                            reportTitle={reportTitle}
+                            printMeta={printMeta}
+                            translatedColumns={translatedColumns}
+                            printLayoutConfig={printLayoutConfig}
+                            sequenceColumnWidth={sequenceColumnWidth}
+                            dataColumnWidth={dataColumnWidth}
+                            previewNotice={previewNotice}
+                        />
+                    )}
+                </DialogContent>
+                <DialogActions sx={{ position: 'sticky', bottom: 0, backgroundColor: 'background.paper' }}>
+                    <MuiButton
+                        onClick={handlePrint}
+                        disabled={printLoading || printRows.length === 0}
+                        variant="contained"
+                        startIcon={<SaveIcon />}
+                    >
+                        {translate('toolbar.confirm')}
+                    </MuiButton>
+                </DialogActions>
+            </Dialog>
+            {renderFullPrintContent && (
+                <Box sx={{ position: 'fixed', left: '-10000px', top: 0, zIndex: -1 }}>
+                    <PrintReportContent
+                        contentRef={fullPrintContentRef}
+                        rows={printRows}
+                        reportTitle={reportTitle}
+                        printMeta={printMeta}
+                        translatedColumns={translatedColumns}
+                        printLayoutConfig={printLayoutConfig}
+                        sequenceColumnWidth={sequenceColumnWidth}
+                        dataColumnWidth={dataColumnWidth}
+                    />
+                </Box>
+            )}
+        </>
+    );
+};
+
+export default ListExportPrintButton;
diff --git a/rsf-admin/src/page/components/listExportPrintUtils.js b/rsf-admin/src/page/components/listExportPrintUtils.js
new file mode 100644
index 0000000..d7b8326
--- /dev/null
+++ b/rsf-admin/src/page/components/listExportPrintUtils.js
@@ -0,0 +1,62 @@
+const EXTEND_FIELD_SOURCE_REGEXP = /^extendFields\.\[(.+)\]$/;
+
+const getExtendFieldKey = (source) => {
+    const match = EXTEND_FIELD_SOURCE_REGEXP.exec(source || '');
+    return match ? match[1] : null;
+};
+
+const reorderColumns = (columns = [], columnRanks = []) => {
+    if (!Array.isArray(columnRanks) || columnRanks.length === 0) {
+        return columns;
+    }
+
+    return columns.reduce((accumulator, column, index) => {
+        const rank = columnRanks.indexOf(index);
+        if (rank === -1) {
+            accumulator[index] = column;
+        } else {
+            accumulator[rank] = column;
+        }
+        return accumulator;
+    }, []).filter(Boolean);
+};
+
+export const resolveVisibleColumns = (
+    storeKey,
+    defaultHiddenColumns = [],
+    columns = [],
+    hiddenColumns = defaultHiddenColumns,
+    columnRanks = []
+) => {
+    if (!storeKey || !Array.isArray(columns)) {
+        return [];
+    }
+
+    const visibleColumns = reorderColumns(columns, columnRanks);
+    const effectiveHiddenColumns = Array.isArray(hiddenColumns) ? hiddenColumns : defaultHiddenColumns;
+
+    return visibleColumns.filter((column) => {
+        if (!column?.source) {
+            return false;
+        }
+        return !effectiveHiddenColumns.includes(column.source);
+    });
+};
+
+export const getValueBySource = (record, source) => {
+    if (!record || !source) {
+        return '';
+    }
+
+    const extendFieldKey = getExtendFieldKey(source);
+    if (extendFieldKey) {
+        return record.extendFields?.[extendFieldKey] ?? '';
+    }
+
+    return source.split('.').reduce((value, key) => {
+        if (value == null) {
+            return undefined;
+        }
+        return value[key];
+    }, record) ?? '';
+};
diff --git a/rsf-admin/src/page/warehouseAreas/WarehouseAreasList.jsx b/rsf-admin/src/page/warehouseAreas/WarehouseAreasList.jsx
index a271012..663a76e 100644
--- a/rsf-admin/src/page/warehouseAreas/WarehouseAreasList.jsx
+++ b/rsf-admin/src/page/warehouseAreas/WarehouseAreasList.jsx
@@ -1,75 +1,42 @@
-import React, { useState, useRef, useEffect, useMemo, useCallback } from "react";
-import { useNavigate } from 'react-router-dom';
+import React, { useState } from "react";
 import {
     List,
-    DatagridConfigurable,
     SearchInput,
     TopToolbar,
     ColumnsButton,
     EditButton,
     FilterButton,
-    CreateButton,
-    ExportButton,
     BulkDeleteButton,
     WrapperField,
     useRecordContext,
-    useTranslate,
     useNotify,
-    useListContext,
-    FunctionField,
     TextField,
     NumberField,
     DateField,
-    BooleanField,
     ReferenceField,
     TextInput,
-    DateTimeInput,
-    DateInput,
-    SelectInput,
     NumberInput,
     ReferenceInput,
-    ReferenceArrayInput,
     AutocompleteInput,
     DeleteButton,
     useRefresh,
     Button
 } from 'react-admin';
-import { Box, Typography, Card, Stack } from '@mui/material';
-import { styled } from '@mui/material/styles';
+import { Box } from '@mui/material';
 import WarehouseAreasCreate from "./WarehouseAreasCreate";
-import WarehouseAreasPanel from "./WarehouseAreasPanel";
 import EmptyData from "../components/EmptyData";
 import MyCreateButton from "../components/MyCreateButton";
-import MyExportButton from '../components/MyExportButton';
 import PageDrawer from "../components/PageDrawer";
 import BatchModal from "./BatchModal";
 import { PAGE_DRAWER_WIDTH, OPERATE_MODE, DEFAULT_PAGE_SIZE } from '@/config/setting';
-import * as Common from '@/utils/common';
 import EditIcon from '@mui/icons-material/Edit';
 import StickyDataTable from "@/page/components/StickyDataTable";
-
-const StyledDatagrid = styled(DatagridConfigurable)(({ theme }) => ({
-    '& .css-1vooibu-MuiSvgIcon-root': {
-        height: '.9em'
-    },
-    '& .RaDatagrid-row': {
-        cursor: 'auto'
-    },
-    '& .column-name': {
-    },
-    '& .opt': {
-        width: 200
-    },
-    '& .MuiTableCell-root': {
-        whiteSpace: 'nowrap',
-        overflow: 'visible',
-        textOverflow: 'unset'
-    }
-}));
+import ListExportPrintButton from "../components/ListExportPrintButton";
 
 const filters = [
-    <SearchInput source="condition" placeholder="鎼滅储搴撳尯鍚嶇О" alwaysOn />,
+    <SearchInput key="condition" source="condition" placeholder="鎼滅储搴撳尯鍚嶇О" alwaysOn />,
     <ReferenceInput
+        key="warehouseId"
         source="warehouseId"
         label="table.field.loc.warehouseId"
         reference="warehouse"
@@ -80,45 +47,140 @@
             filterToQuery={(val) => ({ name: val })}
         />
     </ReferenceInput>,
-    // <TextInput source="uuid" label="table.field.warehouseAreas.uuid" />,
-    <TextInput source="code" label="table.field.warehouseAreas.code" />,
-    <TextInput source="name" label="table.field.warehouseAreas.name" />,
-    <ReferenceInput source="shipperId" label="table.field.warehouseAreas.shipperId" reference="shipper">
-        <AutocompleteInput label="table.field.warehouseAreas.shipperId" optionText="name" filterToQuery={(val) => ({ name: val })} />
+    <TextInput key="code" source="code" label="table.field.warehouseAreas.code" />,
+    <TextInput key="name" source="name" label="table.field.warehouseAreas.name" />,
+    <ReferenceInput
+        key="shipperId"
+        source="shipperId"
+        label="table.field.warehouseAreas.shipperId"
+        reference="shipper"
+    >
+        <AutocompleteInput
+            label="table.field.warehouseAreas.shipperId"
+            optionText="name"
+            filterToQuery={(val) => ({ name: val })}
+        />
     </ReferenceInput>,
-    <NumberInput source="supplierId" label="table.field.warehouseAreas.supplierId" />,
-    // <SelectInput source="flagMinus" label="table.field.warehouseAreas.flagMinus"
-    //     choices={[
-    //         { id: 0, name: '鍚�' },
-    //         { id: 1, name: '鏄�' },
-    //     ]}
-    // />,
-    // <SelectInput source="flagLabelMange" label="table.field.warehouseAreas.flagLabelMange"
-    //     choices={[
-    //         { id: 0, name: ' 鍚�' },
-    //         { id: 1, name: ' 鏄�' },
-    //     ]}
-    // />,
-    // <SelectInput source="flagMix" label="table.field.warehouseAreas.flagMix"
-    //     choices={[
-    //         { id: 0, name: '鍚�' },
-    //         { id: 1, name: '鏄�' },
-    //     ]}
-    // />,
-    // <TextInput label="common.field.memo" source="memo" />,
-    // <SelectInput
-    //     label="common.field.status"
-    //     source="status"
-    //     choices={[
-    //         { id: '1', name: 'common.enums.statusTrue' },
-    //         { id: '0', name: 'common.enums.statusFalse' },
-    //     ]}
-    //     resettable
-    // />,
-]
+    <NumberInput key="supplierId" source="supplierId" label="table.field.warehouseAreas.supplierId" />,
+];
+
+const HIDDEN_COLUMN_SOURCES = [
+    'id',
+    'updateTime',
+    'updateBy',
+    'createTime',
+    'createBy',
+    'longitude',
+    'latgitude',
+    'length',
+    'width',
+    'height',
+    'shipperId$',
+    'supplierId',
+    'sort',
+];
+
+const createColumnDef = (source, label, fieldType = 'text', extra = {}) => ({
+    source,
+    label,
+    fieldType,
+    hidden: HIDDEN_COLUMN_SOURCES.includes(source),
+    ...extra,
+});
+
+const COLUMN_DEFS = [
+    createColumnDef('id', 'id', 'number'),
+    createColumnDef('sort', 'table.field.warehouseAreas.sort', 'number'),
+    createColumnDef('warehouseId$', 'table.field.warehouseAreas.wareId'),
+    createColumnDef('code', 'table.field.warehouseAreas.code'),
+    createColumnDef('name', 'table.field.warehouseAreas.name'),
+    createColumnDef('type$', 'table.field.warehouseAreas.type'),
+    createColumnDef('shipperId$', 'table.field.warehouseAreas.shipperId'),
+    createColumnDef('supplierId', 'table.field.warehouseAreas.supplierId', 'number'),
+    createColumnDef('flagMix$', 'table.field.warehouseAreas.flagMix'),
+    createColumnDef('flagMinus$', 'table.field.warehouseAreas.flagMinus'),
+    createColumnDef('updateBy', 'common.field.updateBy', 'text', {
+        component: 'reference',
+        reference: 'user',
+        childSource: 'nickname',
+        sortable: false,
+    }),
+    createColumnDef('updateTime', 'common.field.updateTime', 'date', {
+        component: 'date',
+        showTime: true,
+    }),
+    createColumnDef('createBy', 'common.field.createBy', 'text', {
+        component: 'reference',
+        reference: 'user',
+        childSource: 'nickname',
+        sortable: false,
+    }),
+    createColumnDef('createTime', 'common.field.createTime', 'date', {
+        component: 'date',
+        showTime: true,
+    }),
+    createColumnDef('memo', 'common.field.memo', 'text', {
+        sortable: false,
+    }),
+];
+
+const renderColumnField = (column) => {
+    if (column.component === 'reference') {
+        return (
+            <ReferenceField
+                key={column.source}
+                source={column.source}
+                label={column.label}
+                reference={column.reference}
+                link={false}
+                sortable={column.sortable}
+            >
+                <TextField source={column.childSource} />
+            </ReferenceField>
+        );
+    }
+
+    if (column.component === 'date' || column.fieldType === 'date') {
+        return (
+            <DateField
+                key={column.source}
+                source={column.source}
+                label={column.label}
+                showTime={column.showTime}
+            />
+        );
+    }
+
+    if (column.fieldType === 'number') {
+        return (
+            <NumberField
+                key={column.source}
+                source={column.source}
+                label={column.label}
+            />
+        );
+    }
+
+    return (
+        <TextField
+            key={column.source}
+            source={column.source}
+            label={column.label}
+            sortable={column.sortable}
+        />
+    );
+};
+
+const exportPrintButton = (
+    <ListExportPrintButton
+        storeKey="warehouseAreas"
+        columns={COLUMN_DEFS}
+        title="menu.warehouseAreas"
+        fileName="warehouseAreas"
+    />
+);
 
 const WarehouseAreasList = () => {
-    const translate = useTranslate();
     const [createDialog, setCreateDialog] = useState(false);
     const [drawerVal, setDrawerVal] = useState(false);
 
@@ -133,145 +195,104 @@
                         }),
                     marginRight: drawerVal ? `${PAGE_DRAWER_WIDTH}px` : 0,
                 }}
-                title={"menu.warehouseAreas"}
-                empty={<EmptyData onClick={() => { setCreateDialog(true) }} />}
+                title="menu.warehouseAreas"
+                empty={<EmptyData onClick={() => { setCreateDialog(true); }} />}
                 filters={filters}
                 sort={{ field: "warehouseId", order: "desc" }}
                 actions={(
                     <TopToolbar>
                         <FilterButton />
-                        <MyCreateButton onClick={() => { setCreateDialog(true) }} />
-                        <ColumnsButton storeKey='warehouseAreas' />
-                        <MyExportButton />
+                        <MyCreateButton onClick={() => { setCreateDialog(true); }} />
+                        <ColumnsButton storeKey="warehouseAreas" />
+                        {exportPrintButton}
                     </TopToolbar>
                 )}
                 perPage={DEFAULT_PAGE_SIZE}
             >
                 <StickyDataTable
-                    storeKey='warehouseAreas'
-                    bulkActionButtons={() => <BulkDeleteButton mutationMode={OPERATE_MODE} />}
-                    rowClick={(id, resource, record) => false}
+                    storeKey="warehouseAreas"
+                    bulkActionButtons={
+                        <>
+                            {exportPrintButton}
+                            <BulkDeleteButton mutationMode={OPERATE_MODE} />
+                        </>
+                    }
+                    rowClick={() => false}
                     stickyRight={['opt']}
-                    hiddenColumns={['id', 'updateTime', 'updateBy', 'createTime', 'createBy', 'longitude', 'latgitude', 'length', 'width', 'height', 'shipperId$', 'supplierId', 'sort']}
+                    hiddenColumns={HIDDEN_COLUMN_SOURCES}
                 >
-                    <NumberField source="id" />
-                    <NumberField source="sort" label="table.field.warehouseAreas.sort" />
-                    <TextField source="warehouseId$" label="table.field.warehouseAreas.wareId" />
-                    <TextField source="code" label="table.field.warehouseAreas.code" />
-                    <TextField source="name" label="table.field.warehouseAreas.name" />
-                    <TextField source="type$" label="table.field.warehouseAreas.type"/>
-                    <TextField source="shipperId$" label="table.field.warehouseAreas.shipperId" />
-                    <NumberField source="supplierId" label="table.field.warehouseAreas.supplierId" />
-                    <TextField source="flagMix$" label="table.field.warehouseAreas.flagMix" sortable={false} />
-                    <TextField source="flagMinus$" label="table.field.warehouseAreas.flagMinus" sortable={false} />
-                    <ReferenceField source="updateBy" label="common.field.updateBy" reference="user" link={false} sortable={false}>
-                        <TextField source="nickname" />
-                    </ReferenceField>
-                    <DateField source="updateTime" label="common.field.updateTime" showTime />
-                    <ReferenceField source="createBy" label="common.field.createBy" reference="user" link={false} sortable={false}>
-                        <TextField source="nickname" />
-                    </ReferenceField>
-                    <DateField source="createTime" label="common.field.createTime" showTime />
-                    {/* <BooleanField source="statusBool" label="common.field.status" sortable={false} /> */}
-                    <TextField source="memo" label="common.field.memo" sortable={false} />
+                    {COLUMN_DEFS.map((column) => renderColumnField(column))}
                     <WrapperField cellClassName="opt" label="common.field.opt">
                         <EditButton sx={{ padding: '1px', fontSize: '.75rem' }} />
                         <DeleteButton sx={{ padding: '1px', fontSize: '.75rem' }} mutationMode={OPERATE_MODE} />
                     </WrapperField>
-
-                    {/* <TextField source="flagLabelMange$" label="table.field.warehouseAreas.flagLabelMange" sortable={false} /> */}
-                    {/* <TextField source="uuid" label="table.field.warehouseAreas.uuid" /> */}
-                    {/* <ReferenceField source="shipperId" label="table.field.warehouseAreas.shipperId" reference="shipper" link={false} sortable={false}>
-                        <TextField source="name" />
-                    </ReferenceField> */}
                 </StickyDataTable>
-
             </List>
             <WarehouseAreasCreate
                 open={createDialog}
                 setOpen={setCreateDialog}
             />
             <PageDrawer
-                title='WarehouseAreas Detail'
+                title="WarehouseAreas Detail"
                 drawerVal={drawerVal}
                 setDrawerVal={setDrawerVal}
-            >
-            </PageDrawer>
+            />
         </Box>
-    )
-}
+    );
+};
 
 export default WarehouseAreasList;
 
-
 const MixButton = () => {
-    const record = useRecordContext();
-    const notify = useNotify();
-    const refresh = useRefresh();
-
-
     const [createDialog, setCreateDialog] = useState(false);
 
     return (
         <>
-            <Button onClick={() => setCreateDialog(true)} label={"toolbar.batchMix"}>
+            <Button onClick={() => setCreateDialog(true)} label="toolbar.batchMix">
                 <EditIcon />
             </Button>
 
             <BatchModal
                 open={createDialog}
                 setOpen={setCreateDialog}
-                fieldType={'flagMix'}
+                fieldType="flagMix"
             />
         </>
-
-    )
-}
+    );
+};
 
 const WareButton = () => {
-    const record = useRecordContext();
-    const notify = useNotify();
-    const refresh = useRefresh();
-
-
     const [createDialog, setCreateDialog] = useState(false);
 
     return (
         <>
-            <Button onClick={() => setCreateDialog(true)} label={"toolbar.batchWarehouse"}>
+            <Button onClick={() => setCreateDialog(true)} label="toolbar.batchWarehouse">
                 <EditIcon />
             </Button>
 
             <BatchModal
                 open={createDialog}
                 setOpen={setCreateDialog}
-                fieldType={'wareId'}
+                fieldType="wareId"
             />
         </>
-
-    )
-}
+    );
+};
 
 const StatusButton = () => {
-    const record = useRecordContext();
-    const notify = useNotify();
-    const refresh = useRefresh();
-
-
     const [createDialog, setCreateDialog] = useState(false);
 
     return (
         <>
-            <Button onClick={() => setCreateDialog(true)} label={"toolbar.batchStatus"}>
+            <Button onClick={() => setCreateDialog(true)} label="toolbar.batchStatus">
                 <EditIcon />
             </Button>
 
             <BatchModal
                 open={createDialog}
                 setOpen={setCreateDialog}
-                fieldType={'status'}
+                fieldType="status"
             />
         </>
-
-    )
-}
+    );
+};
diff --git a/rsf-admin/src/page/warehouseAreasItem/WarehouseAreasItemList.jsx b/rsf-admin/src/page/warehouseAreasItem/WarehouseAreasItemList.jsx
index a2f385e..ba41a6a 100644
--- a/rsf-admin/src/page/warehouseAreasItem/WarehouseAreasItemList.jsx
+++ b/rsf-admin/src/page/warehouseAreasItem/WarehouseAreasItemList.jsx
@@ -1,4 +1,4 @@
-import React, { useState, useEffect } from "react";
+import React, { useEffect, useState } from "react";
 import {
     List,
     SearchInput,
@@ -20,11 +20,11 @@
 import { Box, LinearProgress } from '@mui/material';
 import WarehouseAreasItemCreate from "./WarehouseAreasItemCreate";
 import request from '@/utils/request';
-import MyExportButton from '../components/MyExportButton';
 import { DEFAULT_PAGE_SIZE, PAGE_DRAWER_WIDTH } from '@/config/setting';
 import StickyDataTable from "../components/StickyDataTable";
 import WarehouseIsptResult from "./WarehouseIsptResult";
 import useTableLayout from '@/utils/useTableLayout';
+import ListExportPrintButton from "../components/ListExportPrintButton";
 
 const baseFilters = [
     <SearchInput key="condition" source="condition" alwaysOn />,
@@ -50,7 +50,6 @@
     <NumberInput key="weight" source="weight" label="table.field.warehouseAreasItem.weight" />,
     <TextInput key="prodTime" source="prodTime" label="table.field.warehouseAreasItem.prodTime" />,
     <TextInput key="splrBtch" source="splrBtch" label="table.field.warehouseAreasItem.splrBtch" />,
-
     <TextInput key="memo" label="common.field.memo" source="memo" />,
     <SelectInput
         key="status"
@@ -66,61 +65,132 @@
 
 const baseFilterSources = new Set(baseFilters.map((item) => item.props.source).filter(Boolean));
 
-const hiddenColumns = [
+const hiddenColumnSources = [
     'prodTime', 'platOrderCode', 'id', 'createTime', 'memo', 'areaId', 'brand',
     'weight', 'splrId', 'projectCode', 'statusBool', 'extendFields.[priceUnitId]', 'isptResult$',
     'extendFields.[inStockType]', 'matnrId', 'trackCode', 'workQty', 'batch', 'shipperId',
     'isptResult', 'createBy$', 'extendFields.[baseUnitId]'
 ];
 
-const baseColumns = [
-    <NumberField key="id" source="id" />,
-    <TextField key="areaName" source="areaName" label="鏀惰揣鍖哄悕绉�" />,
-    <TextField key="asnCode" source="asnCode" label="table.field.warehouseAreasItem.asnCode" />,
-    <TextField key="platWorkCode" source="platWorkCode" label="table.field.asnOrderItem.platWorkCode" />,
-    <TextField key="platItemId" source="platItemId" label="table.field.deliveryItem.platItemId" />,
-    <NumberField key="matnrId" source="matnrId" label="table.field.warehouseAreasItem.matnrId" />,
-    <TextField key="matnrCode" source="matnrCode" label="table.field.warehouseAreasItem.matnrCode" />,
-    <TextField key="maktx" source="maktx" label="table.field.warehouseAreasItem.matnrName" />,
-    <TextField key="splrBatch" source="splrBatch" label="table.field.warehouseAreasItem.splrBtch" />,
-    <TextField key="batch" source="batch" label="table.field.warehouseAreasItem.batch" />,
-    <TextField key="trackCode" source="trackCode" label="table.field.warehouseAreasItem.barcode" />,
-    <TextField key="unit" source="unit" label="table.field.warehouseAreasItem.unit" />,
-    <NumberField key="anfme" source="anfme" label="table.field.warehouseAreasItem.anfme" />,
-    <NumberField key="workQty" source="workQty" label="table.field.warehouseAreasItem.workQty" />,
-    <NumberField key="ableQty" source="ableQty" label="table.field.warehouseAreasItem.qty" />,
-    <TextField key="platOrderCode" source="platOrderCode" label="table.field.asnOrderItem.platOrderCode" />,
-    <TextField key="projectCode" source="projectCode" label="table.field.asnOrderItem.projectCode" />,
-    <TextField key="brand" source="brand" label="table.field.warehouseAreasItem.brand" />,
-    <TextField key="shipperId" source="shipperId" label="table.field.warehouseAreasItem.shipperId" />,
-    <TextField key="splrId" source="splrId" label="table.field.warehouseAreasItem.splrId" />,
-    <TextField key="isptResult" source="isptResult$" label="table.field.warehouseAreasItem.isptResult" sortable={false} />,
-    <NumberField key="weight" source="weight" label="table.field.warehouseAreasItem.weight" />,
-    <TextField key="prodTime" source="prodTime" label="table.field.warehouseAreasItem.prodTime" />,
+const createColumnDef = (source, label, fieldType = 'text', options = {}) => ({
+    source,
+    label,
+    fieldType,
+    hidden: hiddenColumnSources.includes(source),
+    sortable: options.sortable,
+});
+
+const baseColumnDefs = [
+    createColumnDef('id', 'table.field.warehouseAreasItem.id', 'number'),
+    createColumnDef('areaName', 'table.field.warehouseAreasItem.areaName'),
+    createColumnDef('asnCode', 'table.field.warehouseAreasItem.asnCode'),
+    createColumnDef('platWorkCode', 'table.field.asnOrderItem.platWorkCode'),
+    createColumnDef('platItemId', 'table.field.deliveryItem.platItemId'),
+    createColumnDef('matnrId', 'table.field.warehouseAreasItem.matnrId', 'number'),
+    createColumnDef('matnrCode', 'table.field.warehouseAreasItem.matnrCode'),
+    createColumnDef('maktx', 'table.field.warehouseAreasItem.matnrName'),
+    createColumnDef('splrBatch', 'table.field.warehouseAreasItem.splrBtch'),
+    createColumnDef('batch', 'table.field.warehouseAreasItem.batch'),
+    createColumnDef('trackCode', 'table.field.warehouseAreasItem.barcode'),
+    createColumnDef('unit', 'table.field.warehouseAreasItem.unit'),
+    createColumnDef('anfme', 'table.field.warehouseAreasItem.anfme', 'number'),
+    createColumnDef('workQty', 'table.field.warehouseAreasItem.workQty', 'number'),
+    createColumnDef('ableQty', 'table.field.warehouseAreasItem.qty', 'number'),
+    createColumnDef('platOrderCode', 'table.field.asnOrderItem.platOrderCode'),
+    createColumnDef('projectCode', 'table.field.asnOrderItem.projectCode'),
+    createColumnDef('brand', 'table.field.warehouseAreasItem.brand'),
+    createColumnDef('shipperId', 'table.field.warehouseAreasItem.shipperId'),
+    createColumnDef('splrId', 'table.field.warehouseAreasItem.splrId'),
+    createColumnDef('isptResult$', 'table.field.warehouseAreasItem.isptResult', 'text', { sortable: false }),
+    createColumnDef('weight', 'table.field.warehouseAreasItem.weight', 'number'),
+    createColumnDef('prodTime', 'table.field.warehouseAreasItem.prodTime'),
 ];
 
-const trailingColumns = [
-    <TextField key="updateBy" source="updateBy$" label="common.field.updateBy" />,
-    <DateField key="updateTime" source="updateTime" label="common.field.updateTime" showTime />,
-    <TextField key="createBy" source="createBy$" label="common.field.createBy" />,
-    <DateField key="createTime" source="createTime" label="common.field.createTime" showTime />,
-    <BooleanField key="statusBool" source="statusBool" label="common.field.status" sortable={false} />,
-    <TextField key="memo" source="memo" label="common.field.memo" sortable={false} />,
+const trailingColumnDefs = [
+    createColumnDef('updateBy$', 'common.field.updateBy'),
+    createColumnDef('updateTime', 'common.field.updateTime', 'date'),
+    createColumnDef('createBy$', 'common.field.createBy'),
+    createColumnDef('createTime', 'common.field.createTime', 'date'),
+    createColumnDef('statusBool', 'common.field.status', 'boolean', { sortable: false }),
+    createColumnDef('memo', 'common.field.memo', 'text', { sortable: false }),
 ];
 
 const buildDynamicFilter = (field) => (
     <TextInput key={field.fields} source={field.fields} label={field.fieldsAlise} />
 );
 
-const buildDynamicColumn = (field) => (
-    <TextField key={field.fields} source={`extendFields.[${field.fields}]`} label={field.fieldsAlise} />
+const buildDynamicColumnDef = (field) => createColumnDef(
+    `extendFields.[${field.fields}]`,
+    field.fieldsAlise
 );
 
+const buildFieldElement = (column) => {
+    const commonProps = {
+        key: column.source,
+        source: column.source,
+        label: column.label,
+    };
+
+    if (column.sortable === false) {
+        commonProps.sortable = false;
+    }
+
+    switch (column.fieldType) {
+        case 'number':
+            return <NumberField {...commonProps} />;
+        case 'date':
+            return <DateField {...commonProps} showTime />;
+        case 'boolean':
+            return <BooleanField {...commonProps} />;
+        default:
+            return <TextField {...commonProps} />;
+    }
+};
+
 const WarehouseAreasItemList = () => {
-    const [itemInfo, setItemInfo] = useState({})
+    const notify = useNotify();
+    const [itemInfo, setItemInfo] = useState({});
     const [createDialog, setCreateDialog] = useState(false);
     const [drawerVal, setDrawerVal] = useState(false);
     const [dynamicFilters, setDynamicFilters] = useState([]);
+    const [columnDefs, setColumnDefs] = useState([]);
+
+    useEffect(() => {
+        let active = true;
+
+        const getDynamicFields = async () => {
+            try {
+                const { data: { code, data, msg } } = await request.get("/fields/enable/list");
+                if (!active) {
+                    return;
+                }
+                if (code === 200) {
+                    const dynamicColumnDefs = data.map(buildDynamicColumnDef);
+                    const nextDynamicFilters = data
+                        .filter((field) => !baseFilterSources.has(field.fields))
+                        .map(buildDynamicFilter);
+
+                    setColumnDefs([...baseColumnDefs, ...dynamicColumnDefs, ...trailingColumnDefs]);
+                    setDynamicFilters(nextDynamicFilters);
+                } else {
+                    setColumnDefs([...baseColumnDefs, ...trailingColumnDefs]);
+                    notify(msg);
+                }
+            } catch (error) {
+                if (active) {
+                    setColumnDefs([...baseColumnDefs, ...trailingColumnDefs]);
+                    notify('璇锋眰鍑洪敊');
+                }
+            }
+        };
+
+        getDynamicFields();
+
+        return () => {
+            active = false;
+            setDynamicFilters([]);
+        };
+    }, [notify]);
 
     return (
         <Box display="flex">
@@ -139,8 +209,16 @@
                 actions={(
                     <TopToolbar>
                         <FilterButton />
-                        <ColumnsButton storeKey='warehouseAreasItem' />
-                        <MyExportButton />
+                        {columnDefs.length > 0 && (
+                            <ColumnsButton storeKey='warehouseAreasItem' />
+                        )}
+                        {columnDefs.length > 0 && (
+                            <ListExportPrintButton
+                                storeKey='warehouseAreasItem'
+                                columns={columnDefs}
+                                title='menu.warehouseAreasItem'
+                            />
+                        )}
                     </TopToolbar>
                 )}
                 filters={[...baseFilters, ...dynamicFilters]}
@@ -148,7 +226,7 @@
             >
                 <DynamicFields
                     drawerOpen={!!drawerVal}
-                    onDynamicFiltersChange={setDynamicFilters}
+                    columnDefs={columnDefs}
                 />
             </List>
             <WarehouseAreasItemCreate
@@ -162,12 +240,6 @@
                 setDrawerVal={setDrawerVal}
             >
             </WarehouseIsptResult>
-            {/* <PageDrawer
-                title='WarehouseAreasItem Detail'
-                drawerVal={drawerVal}
-                setDrawerVal={setDrawerVal}
-            >
-            </PageDrawer> */}
         </Box>
     )
 }
@@ -175,46 +247,10 @@
 export default WarehouseAreasItemList;
 
 
-const DynamicFields = ({ drawerOpen, onDynamicFiltersChange }) => {
-    const notify = useNotify();
-    const [columns, setColumns] = useState([]);
+const DynamicFields = ({ drawerOpen, columnDefs }) => {
     const { isLoading } = useListContext();
     const { boxMaxWidth, boxMaxHeight } = useTableLayout(drawerOpen);
-
-    useEffect(() => {
-        let active = true;
-
-        const getDynamicFields = async () => {
-            try {
-                const { data: { code, data, msg }, } = await request.get("/fields/enable/list");
-                if (!active) {
-                    return;
-                }
-                if (code === 200) {
-                    const dynamicColumns = data.map(buildDynamicColumn);
-                    const nextDynamicFilters = data
-                        .filter((field) => !baseFilterSources.has(field.fields))
-                        .map(buildDynamicFilter);
-
-                    setColumns([...baseColumns, ...dynamicColumns, ...trailingColumns]);
-                    onDynamicFiltersChange(nextDynamicFilters);
-                } else {
-                    notify(msg);
-                }
-            } catch (error) {
-                if (active) {
-                    notify('璇锋眰鍑洪敊');
-                }
-            }
-        };
-
-        getDynamicFields();
-
-        return () => {
-            active = false;
-            onDynamicFiltersChange([]);
-        };
-    }, [notify, onDynamicFiltersChange]);
+    const columns = columnDefs.map(buildFieldElement);
 
     return (
         <Box sx={{
@@ -241,9 +277,15 @@
             {columns.length > 0 &&
                 <StickyDataTable
                     storeKey='warehouseAreasItem'
-                    bulkActionButtons={false}
+                    bulkActionButtons={(
+                        <ListExportPrintButton
+                            storeKey='warehouseAreasItem'
+                            columns={columnDefs}
+                            title='menu.warehouseAreasItem'
+                        />
+                    )}
                     rowClick={false}
-                    hiddenColumns={hiddenColumns}
+                    hiddenColumns={hiddenColumnSources}
                 >
                     {columns}
                 </StickyDataTable>}
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/common/service/ListExportHandler.java b/rsf-server/src/main/java/com/vincent/rsf/server/common/service/ListExportHandler.java
new file mode 100644
index 0000000..da4a548
--- /dev/null
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/common/service/ListExportHandler.java
@@ -0,0 +1,20 @@
+package com.vincent.rsf.server.common.service;
+
+import com.vincent.rsf.server.common.domain.BaseParam;
+import com.vincent.rsf.server.common.utils.ExcelUtil;
+
+import java.util.List;
+import java.util.Map;
+
+public interface ListExportHandler<T, P extends BaseParam> {
+    List<T> listByIds(List<Long> ids);
+
+    List<T> listByFilter(Map<String, Object> sanitizedMap, P baseParam);
+
+    default void fillExportFields(List<T> records) {
+    }
+
+    Map<String, Object> toExportRow(T record, List<ExcelUtil.ExportColumn> columns);
+
+    String defaultReportTitle();
+}
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/common/service/ListExportService.java b/rsf-server/src/main/java/com/vincent/rsf/server/common/service/ListExportService.java
new file mode 100644
index 0000000..b661c90
--- /dev/null
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/common/service/ListExportService.java
@@ -0,0 +1,154 @@
+package com.vincent.rsf.server.common.service;
+
+import com.vincent.rsf.framework.exception.CoolException;
+import com.vincent.rsf.server.common.domain.BaseParam;
+import com.vincent.rsf.server.common.utils.ExcelUtil;
+import org.springframework.stereotype.Service;
+
+import jakarta.servlet.http.HttpServletResponse;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.function.Function;
+
+@Service
+public class ListExportService {
+    private static final Set<String> EXPORT_ONLY_KEYS = Set.of(
+            "columns",
+            "reportTitle",
+            "reportDate",
+            "printedAt",
+            "operator",
+            "count",
+            "report_title",
+            "report_date",
+            "printed_at"
+    );
+
+    public <T, P extends BaseParam> void export(
+            Map<String, Object> map,
+            Function<Map<String, Object>, P> paramBuilder,
+            ListExportHandler<T, P> exportHandler,
+            HttpServletResponse response
+    ) throws Exception {
+        Map<String, Object> sanitizedMap = sanitizeExportMap(map);
+        P baseParam = paramBuilder.apply(sanitizedMap);
+        List<ExcelUtil.ExportColumn> columns = buildExportColumns(map);
+        if (columns.isEmpty()) {
+            throw new CoolException("瀵煎嚭鍒椾笉鑳戒负绌�");
+        }
+
+        List<T> records = getExportRecords(sanitizedMap, baseParam, exportHandler);
+        exportHandler.fillExportFields(records);
+
+        List<Map<String, Object>> rows = records.stream()
+                .map(record -> exportHandler.toExportRow(record, columns))
+                .toList();
+
+        ExcelUtil.ExportMeta exportMeta = buildExportMeta(map, rows.size(), exportHandler.defaultReportTitle());
+        ExcelUtil.build(ExcelUtil.create(rows, columns, exportMeta), response);
+    }
+
+    private Map<String, Object> sanitizeExportMap(Map<String, Object> map) {
+        Map<String, Object> exportMap = new HashMap<>(map);
+        EXPORT_ONLY_KEYS.forEach(exportMap::remove);
+        exportMap.remove("meta");
+
+        sanitizeNestedMap(exportMap, "filter");
+        sanitizeNestedMap(exportMap, "filterValues");
+        return exportMap;
+    }
+
+    private void sanitizeNestedMap(Map<String, Object> exportMap, String key) {
+        Object nestedObject = exportMap.get(key);
+        if (nestedObject instanceof Map<?, ?> nestedMap) {
+            Map<String, Object> sanitizedMap = new HashMap<>();
+            for (Map.Entry<?, ?> entry : nestedMap.entrySet()) {
+                String nestedKey = String.valueOf(entry.getKey());
+                if (!EXPORT_ONLY_KEYS.contains(nestedKey)) {
+                    sanitizedMap.put(nestedKey, entry.getValue());
+                }
+            }
+            exportMap.put(key, sanitizedMap);
+        }
+    }
+
+    private List<ExcelUtil.ExportColumn> buildExportColumns(Map<String, Object> map) {
+        Object columnsObject = map.get("columns");
+        if (Objects.isNull(columnsObject)) {
+            Object metaObject = map.get("meta");
+            if (metaObject instanceof Map<?, ?> metaMap) {
+                columnsObject = metaMap.get("columns");
+            }
+        }
+
+        if (!(columnsObject instanceof List<?> rawColumns)) {
+            return Collections.emptyList();
+        }
+
+        List<ExcelUtil.ExportColumn> columns = new ArrayList<>();
+        for (Object rawColumn : rawColumns) {
+            if (!(rawColumn instanceof Map<?, ?> columnMap)) {
+                continue;
+            }
+            Object source = columnMap.get("source");
+            Object label = columnMap.get("label");
+            if (Objects.isNull(source) || Objects.isNull(label)) {
+                continue;
+            }
+            columns.add(new ExcelUtil.ExportColumn(String.valueOf(source), String.valueOf(label)));
+        }
+        return columns;
+    }
+
+    private <T, P extends BaseParam> List<T> getExportRecords(
+            Map<String, Object> sanitizedMap,
+            P baseParam,
+            ListExportHandler<T, P> exportHandler
+    ) {
+        List<Long> ids = parseExportIds(sanitizedMap.get("ids"));
+        if (!ids.isEmpty()) {
+            return exportHandler.listByIds(ids);
+        }
+        return exportHandler.listByFilter(sanitizedMap, baseParam);
+    }
+
+    private List<Long> parseExportIds(Object idsObject) {
+        if (idsObject instanceof List<?> ids && !ids.isEmpty()) {
+            return ids.stream()
+                    .map(String::valueOf)
+                    .map(Long::valueOf)
+                    .toList();
+        }
+        if (idsObject instanceof Object[] ids && ids.length > 0) {
+            List<Long> exportIds = new ArrayList<>();
+            for (Object id : ids) {
+                exportIds.add(Long.valueOf(String.valueOf(id)));
+            }
+            return exportIds;
+        }
+        return Collections.emptyList();
+    }
+
+    private ExcelUtil.ExportMeta buildExportMeta(Map<String, Object> map, int count, String defaultReportTitle) {
+        Object metaObject = map.get("meta");
+        if (!(metaObject instanceof Map<?, ?> metaMap)) {
+            return new ExcelUtil.ExportMeta(defaultReportTitle, "", "", "", count);
+        }
+
+        String reportTitle = getMetaValue(metaMap, "reportTitle", defaultReportTitle);
+        String reportDate = getMetaValue(metaMap, "reportDate", "");
+        String printedAt = getMetaValue(metaMap, "printedAt", "");
+        String operator = getMetaValue(metaMap, "operator", "");
+        return new ExcelUtil.ExportMeta(reportTitle, reportDate, printedAt, operator, count);
+    }
+
+    private String getMetaValue(Map<?, ?> metaMap, String key, String defaultValue) {
+        Object value = metaMap.get(key);
+        return Objects.isNull(value) ? defaultValue : String.valueOf(value);
+    }
+}
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/common/utils/ExcelUtil.java b/rsf-server/src/main/java/com/vincent/rsf/server/common/utils/ExcelUtil.java
index 8d81944..4b3649a 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/common/utils/ExcelUtil.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/common/utils/ExcelUtil.java
@@ -15,6 +15,7 @@
 import org.apache.poi.hssf.usermodel.HSSFWorkbook;
 import org.apache.poi.ss.usermodel.*;
 import org.apache.poi.xssf.usermodel.XSSFWorkbook;
+import org.apache.poi.ss.util.CellRangeAddress;
 
 import jakarta.servlet.http.HttpServletResponse;
 import java.io.IOException;
@@ -23,12 +24,16 @@
 import java.net.URLEncoder;
 import java.text.SimpleDateFormat;
 import java.util.*;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 
 /**
  * Created by vincent on 2/17/2024
  */
 @Slf4j
 public class ExcelUtil {
+    private static final Pattern EXTEND_FIELD_SOURCE_PATTERN = Pattern.compile("^extendFields\\.\\[(.+)]$");
+
     public static void build(Workbook workbook, HttpServletResponse response) {
         response.reset();
         Http.cors(response);
@@ -112,14 +117,7 @@
                     } catch (IllegalAccessException e) {
                         e.printStackTrace();
                     }
-                    if (value != null) {
-                        if (value instanceof Date) {
-                            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
-                            row.createCell(cellIndex).setCellValue(sdf.format((Date) value));
-                        } else {
-                            row.createCell(cellIndex).setCellValue(value.toString());
-                        }
-                    }
+                    writeCellValue(row, cellIndex, value);
                     cellIndex++;
                 }
             }
@@ -131,6 +129,208 @@
         return workbook;
     }
 
+    public static Workbook create(List<Map<String, Object>> rows, List<ExportColumn> columns) {
+        return create(rows, columns, null);
+    }
+
+    public static Workbook create(List<Map<String, Object>> rows, List<ExportColumn> columns, ExportMeta exportMeta) {
+        XSSFWorkbook workbook = new XSSFWorkbook();
+        Sheet sheet = workbook.createSheet("export");
+        int titleColumnCount = Math.max(columns.size(), 4);
+        int currentRowIndex = 0;
+
+        CellStyle titleStyle = createTitleStyle(workbook);
+        CellStyle subHeaderStyle = createSubHeaderStyle(workbook);
+        CellStyle headerStyle = createHeaderStyle(workbook);
+        CellStyle bodyStyle = createBodyStyle(workbook);
+
+        if (exportMeta != null && StringUtils.isNotBlank(exportMeta.getReportTitle())) {
+            Row titleRow = sheet.createRow(currentRowIndex++);
+            titleRow.setHeightInPoints(24);
+            Cell titleCell = titleRow.createCell(0);
+            titleCell.setCellValue(exportMeta.getReportTitle());
+            titleCell.setCellStyle(titleStyle);
+            sheet.addMergedRegion(new CellRangeAddress(0, 0, 0, titleColumnCount - 1));
+
+            Row subHeaderRow = sheet.createRow(currentRowIndex++);
+            writeSubHeaderCell(subHeaderRow, 0, getSubHeaderText("鎶ヨ〃鏃ユ湡", exportMeta.getReportDate()), subHeaderStyle);
+            writeSubHeaderCell(subHeaderRow, 1, getSubHeaderText("鎵撳嵃浜�", exportMeta.getOperator()), subHeaderStyle);
+            writeSubHeaderCell(subHeaderRow, 2, getSubHeaderText("鎵撳嵃鏃堕棿", exportMeta.getPrintedAt()), subHeaderStyle);
+            writeSubHeaderCell(subHeaderRow, 3, getSubHeaderText("璁板綍鏁�", String.valueOf(exportMeta.getCount())), subHeaderStyle);
+
+            currentRowIndex++;
+        }
+
+        Row header = sheet.createRow(currentRowIndex++);
+        for (int index = 0; index < columns.size(); index++) {
+            Cell headerCell = header.createCell(index);
+            headerCell.setCellValue(columns.get(index).getLabel());
+            headerCell.setCellStyle(headerStyle);
+        }
+
+        int rowIndex = currentRowIndex;
+        for (Map<String, Object> rowData : rows) {
+            Row row = sheet.createRow(rowIndex++);
+            for (int columnIndex = 0; columnIndex < columns.size(); columnIndex++) {
+                Object value = getRowValue(rowData, columns.get(columnIndex).getSource());
+                writeCellValue(row, columnIndex, value, bodyStyle);
+            }
+        }
+
+        for (int columnIndex = 0; columnIndex < columns.size(); columnIndex++) {
+            sheet.autoSizeColumn(columnIndex);
+        }
+
+        return workbook;
+    }
+
+    private static Object getRowValue(Map<String, Object> rowData, String source) {
+        if (rowData == null || StringUtils.isBlank(source)) {
+            return null;
+        }
+
+        if (rowData.containsKey(source)) {
+            return rowData.get(source);
+        }
+
+        Matcher matcher = EXTEND_FIELD_SOURCE_PATTERN.matcher(source);
+        if (matcher.matches()) {
+            Object extendFields = rowData.get("extendFields");
+            if (extendFields instanceof Map<?, ?> extendFieldMap) {
+                return extendFieldMap.get(matcher.group(1));
+            }
+        }
+
+        return rowData.get(source);
+    }
+
+    private static void writeCellValue(Row row, int cellIndex, Object value) {
+        writeCellValue(row, cellIndex, value, null);
+    }
+
+    private static void writeCellValue(Row row, int cellIndex, Object value, CellStyle cellStyle) {
+        Cell cell = row.createCell(cellIndex);
+        if (cellStyle != null) {
+            cell.setCellStyle(cellStyle);
+        }
+        if (value == null) {
+            return;
+        }
+        if (value instanceof Date) {
+            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+            cell.setCellValue(sdf.format((Date) value));
+            return;
+        }
+        cell.setCellValue(value.toString());
+    }
+
+    private static void writeSubHeaderCell(Row row, int cellIndex, String value, CellStyle cellStyle) {
+        Cell cell = row.createCell(cellIndex);
+        cell.setCellValue(value);
+        cell.setCellStyle(cellStyle);
+    }
+
+    private static String getSubHeaderText(String label, String value) {
+        return label + ": " + StringUtils.defaultString(value);
+    }
+
+    private static CellStyle createTitleStyle(Workbook workbook) {
+        CellStyle cellStyle = workbook.createCellStyle();
+        cellStyle.setAlignment(HorizontalAlignment.CENTER);
+        cellStyle.setVerticalAlignment(VerticalAlignment.CENTER);
+
+        Font font = workbook.createFont();
+        font.setBold(true);
+        font.setFontHeightInPoints((short) 14);
+        cellStyle.setFont(font);
+        return cellStyle;
+    }
+
+    private static CellStyle createSubHeaderStyle(Workbook workbook) {
+        CellStyle cellStyle = workbook.createCellStyle();
+        cellStyle.setAlignment(HorizontalAlignment.LEFT);
+        cellStyle.setVerticalAlignment(VerticalAlignment.CENTER);
+
+        Font font = workbook.createFont();
+        font.setFontHeightInPoints((short) 10);
+        cellStyle.setFont(font);
+        return cellStyle;
+    }
+
+    private static CellStyle createHeaderStyle(Workbook workbook) {
+        CellStyle cellStyle = workbook.createCellStyle();
+        cellStyle.setAlignment(HorizontalAlignment.CENTER);
+        cellStyle.setVerticalAlignment(VerticalAlignment.CENTER);
+
+        Font font = workbook.createFont();
+        font.setBold(true);
+        font.setFontHeightInPoints((short) 10);
+        cellStyle.setFont(font);
+        return cellStyle;
+    }
+
+    private static CellStyle createBodyStyle(Workbook workbook) {
+        CellStyle cellStyle = workbook.createCellStyle();
+        cellStyle.setAlignment(HorizontalAlignment.LEFT);
+        cellStyle.setVerticalAlignment(VerticalAlignment.TOP);
+        cellStyle.setWrapText(true);
+        return cellStyle;
+    }
+
+    public static class ExportColumn {
+        private final String source;
+        private final String label;
+
+        public ExportColumn(String source, String label) {
+            this.source = source;
+            this.label = label;
+        }
+
+        public String getSource() {
+            return source;
+        }
+
+        public String getLabel() {
+            return label;
+        }
+    }
+
+    public static class ExportMeta {
+        private final String reportTitle;
+        private final String reportDate;
+        private final String printedAt;
+        private final String operator;
+        private final int count;
+
+        public ExportMeta(String reportTitle, String reportDate, String printedAt, String operator, int count) {
+            this.reportTitle = reportTitle;
+            this.reportDate = reportDate;
+            this.printedAt = printedAt;
+            this.operator = operator;
+            this.count = count;
+        }
+
+        public String getReportTitle() {
+            return reportTitle;
+        }
+
+        public String getReportDate() {
+            return reportDate;
+        }
+
+        public String getPrintedAt() {
+            return printedAt;
+        }
+
+        public String getOperator() {
+            return operator;
+        }
+
+        public int getCount() {
+            return count;
+        }
+    }
+
     /**
      * 娣诲姞瀵煎叆excel閰嶇疆鍙傛暟
      * 娉細榛樿閰嶇疆鍙弧瓒冲綋鍓嶉渶姹�
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/manager/controller/WarehouseAreasController.java b/rsf-server/src/main/java/com/vincent/rsf/server/manager/controller/WarehouseAreasController.java
index 0f18db4..85efc65 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/manager/controller/WarehouseAreasController.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/manager/controller/WarehouseAreasController.java
@@ -1,15 +1,18 @@
 package com.vincent.rsf.server.manager.controller;
 
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.vincent.rsf.framework.common.Cools;
 import com.vincent.rsf.framework.common.R;
 import com.vincent.rsf.framework.exception.CoolException;
-import com.vincent.rsf.server.common.utils.ExcelUtil;
 import com.vincent.rsf.server.common.annotation.OperationLog;
 import com.vincent.rsf.server.common.domain.BaseParam;
 import com.vincent.rsf.server.common.domain.KeyValVo;
 import com.vincent.rsf.server.common.domain.PageParam;
+import com.vincent.rsf.server.common.service.ListExportHandler;
+import com.vincent.rsf.server.common.service.ListExportService;
+import com.vincent.rsf.server.common.utils.ExcelUtil;
 import com.vincent.rsf.server.manager.controller.params.WarehouseAreaParam;
 import com.vincent.rsf.server.manager.entity.Loc;
 import com.vincent.rsf.server.manager.entity.WarehouseAreas;
@@ -18,6 +21,8 @@
 import com.vincent.rsf.server.system.controller.BaseController;
 import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiOperation;
+import org.springframework.beans.BeanWrapper;
+import org.springframework.beans.BeanWrapperImpl;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.web.bind.annotation.*;
@@ -34,6 +39,42 @@
 
     @Autowired
     private LocService locService;
+
+    @Autowired
+    private ListExportService listExportService;
+
+    private final ListExportHandler<WarehouseAreas, BaseParam> warehouseAreasExportHandler = new ListExportHandler<>() {
+        @Override
+        public List<WarehouseAreas> listByIds(List<Long> ids) {
+            return warehouseAreasService.listByIds(ids);
+        }
+
+        @Override
+        public List<WarehouseAreas> listByFilter(Map<String, Object> sanitizedMap, BaseParam baseParam) {
+            PageParam<WarehouseAreas, BaseParam> pageParam = new PageParam<>(baseParam, WarehouseAreas.class);
+            QueryWrapper<WarehouseAreas> queryWrapper = pageParam.buildWrapper(true);
+            return warehouseAreasService.list(queryWrapper);
+        }
+
+        @Override
+        public Map<String, Object> toExportRow(WarehouseAreas record, List<ExcelUtil.ExportColumn> columns) {
+            BeanWrapper beanWrapper = new BeanWrapperImpl(record);
+            Map<String, Object> row = new LinkedHashMap<>();
+
+            for (ExcelUtil.ExportColumn column : columns) {
+                if (beanWrapper.isReadableProperty(column.getSource())) {
+                    row.put(column.getSource(), beanWrapper.getPropertyValue(column.getSource()));
+                }
+            }
+
+            return row;
+        }
+
+        @Override
+        public String defaultReportTitle() {
+            return "浠撳簱搴撳尯鎶ヨ〃";
+        }
+    };
 
     @PreAuthorize("hasAuthority('manager:warehouseAreas:list')")
     @PostMapping("/warehouseAreas/page")
@@ -170,7 +211,12 @@
     @PreAuthorize("hasAuthority('manager:warehouseAreas:list')")
     @PostMapping("/warehouseAreas/export")
     public void export(@RequestBody Map<String, Object> map, HttpServletResponse response) throws Exception {
-        ExcelUtil.build(ExcelUtil.create(warehouseAreasService.list(), WarehouseAreas.class), response);
+        listExportService.export(
+                map,
+                exportMap -> buildParam(exportMap, BaseParam.class),
+                warehouseAreasExportHandler,
+                response
+        );
     }
 
 }
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/manager/controller/WarehouseAreasItemController.java b/rsf-server/src/main/java/com/vincent/rsf/server/manager/controller/WarehouseAreasItemController.java
index f468a2d..b022be8 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/manager/controller/WarehouseAreasItemController.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/manager/controller/WarehouseAreasItemController.java
@@ -6,16 +6,20 @@
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.vincent.rsf.framework.common.Cools;
 import com.vincent.rsf.framework.common.R;
-import com.vincent.rsf.server.common.utils.ExcelUtil;
 import com.vincent.rsf.server.common.annotation.OperationLog;
 import com.vincent.rsf.server.common.domain.BaseParam;
 import com.vincent.rsf.server.common.domain.KeyValVo;
 import com.vincent.rsf.server.common.domain.PageParam;
+import com.vincent.rsf.server.common.service.ListExportHandler;
+import com.vincent.rsf.server.common.service.ListExportService;
+import com.vincent.rsf.server.common.utils.ExcelUtil;
 import com.vincent.rsf.server.common.utils.FieldsUtils;
 import com.vincent.rsf.server.manager.entity.WarehouseAreasItem;
 import com.vincent.rsf.server.manager.service.WarehouseAreasItemService;
 import com.vincent.rsf.server.system.controller.BaseController;
 import io.swagger.annotations.Api;
+import org.springframework.beans.BeanWrapper;
+import org.springframework.beans.BeanWrapperImpl;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.web.bind.annotation.*;
@@ -26,9 +30,41 @@
 @Api(tags = "搴撳尯搴撳瓨鏄庣粏")
 @RestController
 public class WarehouseAreasItemController extends BaseController {
-
     @Autowired
     private WarehouseAreasItemService warehouseAreasItemService;
+
+    @Autowired
+    private ListExportService listExportService;
+
+    private final ListExportHandler<WarehouseAreasItem, BaseParam> warehouseAreasItemExportHandler = new ListExportHandler<>() {
+        @Override
+        public List<WarehouseAreasItem> listByIds(List<Long> ids) {
+            return warehouseAreasItemService.listByIds(ids);
+        }
+
+        @Override
+        public List<WarehouseAreasItem> listByFilter(Map<String, Object> sanitizedMap, BaseParam baseParam) {
+            PageParam<WarehouseAreasItem, BaseParam> pageParam = new PageParam<>(baseParam, WarehouseAreasItem.class);
+            QueryWrapper<WarehouseAreasItem> queryWrapper = pageParam.buildWrapper(true);
+            FieldsUtils.setFieldsFilters(queryWrapper, pageParam, WarehouseAreasItem.class);
+            return warehouseAreasItemService.list(queryWrapper);
+        }
+
+        @Override
+        public void fillExportFields(List<WarehouseAreasItem> records) {
+            fillExtendFields(records);
+        }
+
+        @Override
+        public Map<String, Object> toExportRow(WarehouseAreasItem record, List<ExcelUtil.ExportColumn> columns) {
+            return buildExportRow(record, columns);
+        }
+
+        @Override
+        public String defaultReportTitle() {
+            return "鏀惰揣搴撳瓨鎶ヨ〃";
+        }
+    };
 
     @PreAuthorize("hasAuthority('manager:warehouseAreasItem:list')")
     @PostMapping("/warehouseAreasItem/page")
@@ -141,7 +177,37 @@
     @PreAuthorize("hasAuthority('manager:warehouseAreasItem:list')")
     @PostMapping("/warehouseAreasItem/export")
     public void export(@RequestBody Map<String, Object> map, HttpServletResponse response) throws Exception {
-        ExcelUtil.build(ExcelUtil.create(warehouseAreasItemService.list(), WarehouseAreasItem.class), response);
+        listExportService.export(
+                map,
+                exportMap -> buildParam(exportMap, BaseParam.class),
+                warehouseAreasItemExportHandler,
+                response
+        );
+    }
+
+    private void fillExtendFields(List<WarehouseAreasItem> records) {
+        for (WarehouseAreasItem record : records) {
+            if (!Objects.isNull(record.getFieldsIndex())) {
+                Map<String, String> fields = FieldsUtils.getFields(record.getFieldsIndex());
+                record.setExtendFields(fields);
+            }
+        }
+    }
+    private Map<String, Object> buildExportRow(WarehouseAreasItem record, List<ExcelUtil.ExportColumn> columns) {
+        BeanWrapper beanWrapper = new BeanWrapperImpl(record);
+        Map<String, Object> row = new LinkedHashMap<>();
+        row.put("extendFields", record.getExtendFields());
+
+        for (ExcelUtil.ExportColumn column : columns) {
+            if (column.getSource().startsWith("extendFields.[")) {
+                continue;
+            }
+            if (beanWrapper.isReadableProperty(column.getSource())) {
+                row.put(column.getSource(), beanWrapper.getPropertyValue(column.getSource()));
+            }
+        }
+
+        return row;
     }
 
 }

--
Gitblit v1.9.1