From 6e5ff559023efd2d24fdca2adcb7268d06420e46 Mon Sep 17 00:00:00 2001
From: zhou zhou <3272660260@qq.com>
Date: 星期二, 24 三月 2026 15:38:34 +0800
Subject: [PATCH] #打印+导出
---
rsf-admin/src/page/components/MyExportButton.jsx | 87 +
rsf-admin/src/page/components/listReport/ListReportPreviewDialog.jsx | 607 +++++++++++
rsf-admin/src/page/components/listReport/useListReportOutput.js | 292 +++++
rsf-admin/src/page/warehouseAreasItem/WarehouseAreasItemPrintPreview.jsx | 1
rsf-admin/src/page/warehouseAreasItem/warehouseAreasItemOutputUtils.jsx | 493 +++++++++
rsf-server/src/main/java/com/vincent/rsf/server/manager/service/impl/WarehouseAreasItemServiceImpl.java | 35
rsf-admin/src/page/warehouseAreasItem/WarehouseAreasItemList.jsx | 385 +++----
rsf-server/src/main/java/com/vincent/rsf/server/common/domain/report/ReportMeta.java | 49
rsf-server/src/main/java/com/vincent/rsf/server/common/utils/ExcelUtil.java | 354 ++++++
rsf-server/src/main/java/com/vincent/rsf/server/manager/service/WarehouseAreasItemService.java | 7
rsf-server/src/main/java/com/vincent/rsf/server/common/domain/report/ReportQueryResponse.java | 52 +
rsf-server/src/main/java/com/vincent/rsf/server/manager/controller/WarehouseAreasItemController.java | 97 +
rsf-server/src/main/java/com/vincent/rsf/server/common/domain/report/ReportColumnMeta.java | 40
rsf-server/src/main/java/com/vincent/rsf/server/common/domain/report/ReportQueryRequest.java | 165 +++
rsf-admin/src/page/components/listReport/listReportUtils.js | 162 +++
rsf-admin/src/page/components/listReport/MyPrintButton.jsx | 38
rsf-server/src/main/java/com/vincent/rsf/server/common/support/report/ListReportSupport.java | 107 ++
rsf-admin/src/page/components/listReport/ListReportActions.jsx | 58 +
18 files changed, 2,749 insertions(+), 280 deletions(-)
diff --git a/rsf-admin/src/page/components/MyExportButton.jsx b/rsf-admin/src/page/components/MyExportButton.jsx
index a684e47..1e132f1 100644
--- a/rsf-admin/src/page/components/MyExportButton.jsx
+++ b/rsf-admin/src/page/components/MyExportButton.jsx
@@ -1,5 +1,4 @@
import * as React from "react";
-import { useCallback } from "react";
import DownloadIcon from "@mui/icons-material/GetApp";
import {
Button,
@@ -8,6 +7,11 @@
useListContext,
useUnselectAll,
} from "react-admin";
+import { useListReportActionParams } from "./listReport/useListReportOutput";
+import {
+ downloadBlobFile,
+ resolveReportMeta,
+} from "./listReport/listReportUtils";
const MyExportButton = (props) => {
const {
@@ -18,62 +22,73 @@
icon = defaultIcon,
exporter: customExporter,
meta,
+ reportConfig,
+ onExport,
+ loading = false,
+ filename,
...rest
} = props;
const { filter, selectedIds, filterValues, resource, sort, total } = useListContext();
+ const { visibleColumns, params } = useListReportActionParams(reportConfig, { ids });
const unSelect = useUnselectAll(resource);
const dataProvider = useDataProvider();
const notify = useNotify();
- const handleClick =
- // useCallback(
- (event) => {
- dataProvider
- .export(resource, {
+ const handleClick = async (event) => {
+ try {
+ const hasReportConfig = Boolean(reportConfig?.resource && Array.isArray(reportConfig?.columns));
+ if (hasReportConfig) {
+ const actionParams = {
+ ...params,
+ columns: visibleColumns,
+ };
+ if (typeof onExport === "function") {
+ await onExport(actionParams, event);
+ } else {
+ const resolvedResource = reportConfig.resource || params.resource;
+ const response = await dataProvider.export(resolvedResource, {
+ sort: actionParams.sort,
+ ids: actionParams.ids,
+ filter: actionParams.filter
+ ? { ...actionParams.filterValues, ...actionParams.filter }
+ : actionParams.filterValues,
+ pagination: { page: 1, perPage: maxResults },
+ meta,
+ columns: visibleColumns,
+ reportMeta: resolveReportMeta(reportConfig, actionParams),
+ });
+ downloadBlobFile(
+ response,
+ filename || reportConfig.exportFileName || `${resolvedResource}.xlsx`,
+ );
+ }
+ } else {
+ const response = await dataProvider.export(resource, {
sort,
- ids: selectedIds,
+ ids: ids ?? selectedIds,
filter: filter ? { ...filterValues, ...filter } : filterValues,
pagination: { page: 1, perPage: maxResults },
meta,
- })
- .then((res) => {
- const url = window.URL.createObjectURL(
- new Blob([res.data], { type: res.headers["content-type"] }),
- );
- const link = document.createElement("a");
- link.href = url;
- link.setAttribute("download", `${resource}.xlsx`);
- document.body.appendChild(link);
- link.click();
- link.remove();
- unSelect();
- })
- .catch((error) => {
- console.error(error);
- notify("ra.notification.http_error", { type: "error" });
});
+ downloadBlobFile(response, filename || `${resource}.xlsx`);
+ }
+ unSelect();
if (typeof onClick === "function") {
onClick(event);
}
+ } catch (error) {
+ console.error(error);
+ notify("ra.notification.http_error", { type: "error" });
}
- // [
- // dataProvider,
- // filter,
- // filterValues,
- // maxResults,
- // notify,
- // onClick,
- // resource,
- // sort,
- // meta,
- // ],
- // );
+ };
+
+ const disabled = total === 0 || loading || (reportConfig?.columns && visibleColumns.length === 0);
return (
<Button
onClick={handleClick}
label={label}
- disabled={total === 0}
+ disabled={disabled}
{...sanitizeRestProps(rest)}
>
{icon}
diff --git a/rsf-admin/src/page/components/listReport/ListReportActions.jsx b/rsf-admin/src/page/components/listReport/ListReportActions.jsx
new file mode 100644
index 0000000..c87b95f
--- /dev/null
+++ b/rsf-admin/src/page/components/listReport/ListReportActions.jsx
@@ -0,0 +1,58 @@
+import React from "react";
+import { FilterButton, SelectColumnsButton, TopToolbar } from "react-admin";
+import MyExportButton from "@/page/components/MyExportButton";
+import MyPrintButton from "./MyPrintButton";
+
+export const ListReportActions = ({
+ reportConfig,
+ loading = false,
+ onExport,
+ onPrintPreview,
+ showFilterButton = true,
+ showSelectColumnsButton = true,
+ children,
+}) => (
+ <TopToolbar>
+ {showFilterButton && <FilterButton />}
+ {showSelectColumnsButton && reportConfig?.preferenceKey && (
+ <SelectColumnsButton preferenceKey={reportConfig.preferenceKey} />
+ )}
+ {reportConfig?.enablePrint && (
+ <MyPrintButton
+ reportConfig={reportConfig}
+ onPrintPreview={onPrintPreview}
+ loading={loading}
+ />
+ )}
+ <MyExportButton
+ reportConfig={reportConfig}
+ onExport={onExport}
+ loading={loading}
+ />
+ {children}
+ </TopToolbar>
+);
+
+export const ListReportBulkActions = ({
+ reportConfig,
+ loading = false,
+ onExport,
+ onPrintPreview,
+}) => (
+ <>
+ {reportConfig?.enablePrint && (
+ <MyPrintButton
+ reportConfig={reportConfig}
+ onPrintPreview={onPrintPreview}
+ loading={loading}
+ />
+ )}
+ <MyExportButton
+ reportConfig={reportConfig}
+ onExport={onExport}
+ loading={loading}
+ />
+ </>
+);
+
+export default ListReportActions;
diff --git a/rsf-admin/src/page/components/listReport/ListReportPreviewDialog.jsx b/rsf-admin/src/page/components/listReport/ListReportPreviewDialog.jsx
new file mode 100644
index 0000000..1c95531
--- /dev/null
+++ b/rsf-admin/src/page/components/listReport/ListReportPreviewDialog.jsx
@@ -0,0 +1,607 @@
+import React, { useEffect, useMemo, useRef, useState } from "react";
+import { useTranslate } from "react-admin";
+import {
+ Box,
+ Button,
+ CircularProgress,
+ Dialog,
+ DialogActions,
+ DialogContent,
+ DialogTitle,
+ ToggleButton,
+ ToggleButtonGroup,
+ Typography,
+} from "@mui/material";
+import PrintOutlinedIcon from "@mui/icons-material/PrintOutlined";
+import { useReactToPrint } from "react-to-print";
+import DialogCloseButton from "@/page/components/DialogCloseButton";
+import { getListReportCellValue } from "./listReportUtils";
+
+const PREVIEW_LAYOUT = {
+ landscape: {
+ width: "297mm",
+ height: "210mm",
+ },
+ portrait: {
+ width: "210mm",
+ height: "297mm",
+ },
+};
+
+const PRINT_PAGE_MARGIN = "12mm";
+
+const buildPrintPageStyle = orientation => `
+@page {
+ size: A4 ${orientation};
+ margin: 0;
+}
+@media print {
+ html, body {
+ width: auto;
+ min-height: auto;
+ height: auto;
+ margin: 0 !important;
+ padding: 0 !important;
+ -webkit-print-color-adjust: exact;
+ print-color-adjust: exact;
+ background: #ffffff;
+ }
+ table {
+ width: 100%;
+ border-collapse: collapse;
+ }
+ thead {
+ display: table-header-group;
+ }
+ .list-report-print-shell {
+ display: block !important;
+ padding: 0 !important;
+ width: 100% !important;
+ max-width: none !important;
+ margin: 0 auto !important;
+ }
+ .list-report-print-live-root {
+ position: static !important;
+ left: auto !important;
+ top: auto !important;
+ visibility: visible !important;
+ background: #ffffff !important;
+ }
+ .list-report-print-page {
+ box-shadow: none !important;
+ border: none !important;
+ margin: 0 auto !important;
+ break-after: page;
+ page-break-after: always;
+ overflow: hidden !important;
+ box-sizing: border-box !important;
+ }
+ .list-report-print-page:last-child {
+ break-after: auto;
+ page-break-after: auto;
+ }
+ .list-report-print-header {
+ page-break-inside: avoid !important;
+ break-inside: avoid !important;
+ }
+ .list-report-print-table-wrap {
+ overflow: visible !important;
+ }
+ .list-report-print-table {
+ width: 100% !important;
+ table-layout: fixed !important;
+ }
+ .list-report-print-table thead {
+ display: table-header-group !important;
+ }
+ .list-report-print-table tr {
+ page-break-inside: avoid !important;
+ break-inside: avoid !important;
+ }
+}
+`;
+
+const getPageBoxSx = (paperSize, printable = false) => ({
+ display: "flex",
+ flexDirection: "column",
+ width: paperSize.width,
+ height: paperSize.height,
+ maxWidth: "100%",
+ mx: "auto",
+ p: PRINT_PAGE_MARGIN,
+ backgroundColor: "#fff",
+ boxShadow: printable ? "none" : "0 8px 24px rgba(15, 23, 42, 0.08)",
+ border: printable ? "none" : "1px solid #d7dce2",
+ boxSizing: "border-box",
+ overflow: "hidden",
+});
+
+const ReportHeader = ({ printedAt, reportMeta, totalCount }) => (
+ <>
+ <Box className="list-report-print-header" sx={{ borderBottom: "2px solid #111", pb: 1.25 }}>
+ <Typography
+ variant="h5"
+ sx={{
+ textAlign: "center",
+ fontWeight: 700,
+ letterSpacing: ".02em",
+ color: "#111827",
+ }}
+ >
+ {reportMeta?.title || "鎶ヨ〃"}
+ </Typography>
+ </Box>
+
+ <Box
+ className="list-report-print-header"
+ sx={{
+ display: "flex",
+ justifyContent: "space-between",
+ gap: 2,
+ flexWrap: "wrap",
+ borderBottom: "1px solid #111",
+ pb: 1,
+ color: "#111827",
+ fontSize: 14,
+ }}
+ >
+ <Typography variant="body2">鎶ヨ〃鏃ユ湡锛歿reportMeta?.reportDate || printedAt}</Typography>
+ <Typography variant="body2">鎵撳嵃浜猴細{reportMeta?.printedBy || "-"}</Typography>
+ <Typography variant="body2">鎵撳嵃鏃堕棿锛歿printedAt}</Typography>
+ <Typography variant="body2">璁板綍鏁帮細{totalCount}</Typography>
+ </Box>
+ </>
+);
+
+const ReportTable = ({ displayColumns, pageRows, renderRow, tableWrapRef, theadRef, tbodyRef }) => (
+ <Box ref={tableWrapRef} className="list-report-print-table-wrap" sx={{ overflow: "hidden", flex: 1 }}>
+ <table
+ className="list-report-print-table"
+ style={{
+ width: "100%",
+ borderCollapse: "collapse",
+ tableLayout: "fixed",
+ fontSize: "12px",
+ }}
+ >
+ <thead ref={theadRef}>
+ <tr>
+ {displayColumns.map(column => (
+ <th
+ key={column.key}
+ style={{
+ padding: "8px 8px",
+ backgroundColor: "#f1f3f5",
+ textAlign: "center",
+ whiteSpace: "normal",
+ fontWeight: 700,
+ color: "#111827",
+ }}
+ >
+ {column.label}
+ </th>
+ ))}
+ </tr>
+ </thead>
+ <tbody ref={tbodyRef}>
+ {pageRows.map(renderRow)}
+ </tbody>
+ </table>
+ </Box>
+);
+
+const ReportPageFrame = ({ children, paperSize, printable = false }) => (
+ <Box className="list-report-print-page" sx={getPageBoxSx(paperSize, printable)}>
+ <Box sx={{ display: "flex", flexDirection: "column", gap: 2, width: "100%", height: "100%" }}>
+ {children}
+ </Box>
+ </Box>
+);
+
+const ListReportPreviewDialog = ({
+ open,
+ onClose,
+ rows = [],
+ columns = [],
+ reportMeta,
+ totalRows = 0,
+ loading = false,
+ allRowsLoaded = false,
+ loadedTransportPages = 0,
+ totalTransportPages = 0,
+ prefetching = false,
+ dialogTitle = "鎵撳嵃棰勮",
+ defaultOrientation = "landscape",
+ getCellValue = getListReportCellValue,
+}) => {
+ const translate = useTranslate();
+ const printContentRef = useRef(null);
+ const measurePageRef = useRef(null);
+ const measureContentRef = useRef(null);
+ const measureTableWrapRef = useRef(null);
+ const measureTheadRef = useRef(null);
+ const measureBodyRef = useRef(null);
+ const measureFooterRef = useRef(null);
+ const [orientation, setOrientation] = useState(defaultOrientation);
+ const [currentPage, setCurrentPage] = useState(1);
+ const [isPreparingPrint, setIsPreparingPrint] = useState(false);
+ const [measuring, setMeasuring] = useState(false);
+ const [paginatedRows, setPaginatedRows] = useState([]);
+
+ const printedAt = useMemo(
+ () =>
+ new Date().toLocaleString("zh-CN", {
+ hour12: false,
+ }),
+ [open]
+ );
+
+ const previewPaperSize = useMemo(
+ () => PREVIEW_LAYOUT[orientation] || PREVIEW_LAYOUT.landscape,
+ [orientation]
+ );
+
+ const displayColumns = useMemo(
+ () => [{ key: "__serialNo", label: "搴忓彿", source: "__serialNo" }, ...columns],
+ [columns]
+ );
+
+ const totalCount = totalRows || rows.length;
+ const currentPageRows = paginatedRows[Math.max(currentPage - 1, 0)] || [];
+
+ useEffect(() => {
+ if (!open) {
+ setCurrentPage(1);
+ setPaginatedRows([]);
+ setIsPreparingPrint(false);
+ setOrientation(defaultOrientation);
+ }
+ }, [defaultOrientation, open]);
+
+ useEffect(() => {
+ if (currentPage > paginatedRows.length) {
+ setCurrentPage(Math.max(paginatedRows.length, 1));
+ }
+ }, [currentPage, paginatedRows.length]);
+
+ useEffect(() => {
+ if (!open) {
+ return undefined;
+ }
+ if (rows.length === 0 || columns.length === 0) {
+ setPaginatedRows([]);
+ setMeasuring(false);
+ return undefined;
+ }
+
+ setMeasuring(true);
+ const frameId = requestAnimationFrame(() => {
+ const pageElement = measurePageRef.current;
+ const contentElement = measureContentRef.current;
+ const tableWrapElement = measureTableWrapRef.current;
+ const tableHeadElement = measureTheadRef.current;
+ const tableBodyElement = measureBodyRef.current;
+ const footerElement = measureFooterRef.current;
+
+ if (!pageElement || !contentElement || !tableWrapElement || !tableHeadElement || !tableBodyElement || !footerElement) {
+ setPaginatedRows([rows]);
+ setMeasuring(false);
+ return;
+ }
+
+ const availableBodyHeight = Math.max(
+ contentElement.clientHeight -
+ tableWrapElement.offsetTop -
+ tableHeadElement.offsetHeight -
+ footerElement.offsetHeight,
+ 80
+ );
+
+ const rowHeights = Array.from(tableBodyElement.querySelectorAll("tr")).map(
+ row => row.getBoundingClientRect().height || row.offsetHeight || 24
+ );
+
+ const nextPages = [];
+ let pageRows = [];
+ let usedHeight = 0;
+
+ rows.forEach((record, index) => {
+ const rowHeight = rowHeights[index] || 24;
+ const exceedsCurrentPage =
+ pageRows.length > 0 && usedHeight + rowHeight > availableBodyHeight;
+
+ if (exceedsCurrentPage) {
+ nextPages.push(pageRows);
+ pageRows = [record];
+ usedHeight = rowHeight;
+ return;
+ }
+
+ pageRows.push(record);
+ usedHeight += rowHeight;
+ });
+
+ if (pageRows.length > 0) {
+ nextPages.push(pageRows);
+ }
+
+ setPaginatedRows(nextPages);
+ setMeasuring(false);
+ });
+
+ return () => cancelAnimationFrame(frameId);
+ }, [columns.length, defaultOrientation, open, orientation, reportMeta?.printedBy, reportMeta?.reportDate, reportMeta?.title, rows]);
+
+ const handlePrint = useReactToPrint({
+ content: () => printContentRef.current,
+ documentTitle: reportMeta?.title || dialogTitle,
+ pageStyle: buildPrintPageStyle(orientation),
+ onAfterPrint: () => {
+ setIsPreparingPrint(false);
+ },
+ });
+
+ useEffect(() => {
+ if (!isPreparingPrint || paginatedRows.length === 0) {
+ return undefined;
+ }
+
+ const firstFrame = requestAnimationFrame(() => {
+ const secondFrame = requestAnimationFrame(() => {
+ handlePrint();
+ });
+ return () => cancelAnimationFrame(secondFrame);
+ });
+
+ return () => cancelAnimationFrame(firstFrame);
+ }, [handlePrint, isPreparingPrint, paginatedRows]);
+
+ const renderDataRow = (record, rowIndex, serialStart, keyPrefix) => (
+ <tr key={record.id ?? `${keyPrefix}-row-${rowIndex}`}>
+ {displayColumns.map(column => (
+ <td
+ key={`${record.id ?? keyPrefix}-${column.key}-${rowIndex}`}
+ style={{
+ padding: "6px 8px",
+ verticalAlign: "top",
+ whiteSpace: "normal",
+ wordBreak: "break-word",
+ textAlign: column.key === "__serialNo" ? "center" : "left",
+ color: "#111827",
+ }}
+ >
+ {column.key === "__serialNo"
+ ? String(serialStart + rowIndex + 1).padStart(3, "0")
+ : String(getCellValue(record, column.source))}
+ </td>
+ ))}
+ </tr>
+ );
+
+ const renderPage = (pageRows, pageIndex, pageCount, printable = false) => {
+ const serialStart = paginatedRows.slice(0, pageIndex).reduce((sum, page) => sum + page.length, 0);
+ return (
+ <ReportPageFrame key={`preview-page-${pageIndex + 1}`} paperSize={previewPaperSize} printable={printable}>
+ <ReportHeader printedAt={printedAt} reportMeta={reportMeta} totalCount={totalCount} />
+ <ReportTable
+ displayColumns={displayColumns}
+ pageRows={pageRows}
+ renderRow={(record, rowIndex) =>
+ renderDataRow(record, rowIndex, serialStart, `page-${pageIndex + 1}`)
+ }
+ />
+ <Box
+ sx={{
+ display: "flex",
+ justifyContent: "flex-end",
+ color: "#6b7280",
+ fontSize: 12,
+ }}
+ >
+ 绗� {pageIndex + 1} / {pageCount} 椤�
+ </Box>
+ </ReportPageFrame>
+ );
+ };
+
+ const printDisabled =
+ loading ||
+ prefetching ||
+ measuring ||
+ isPreparingPrint ||
+ !allRowsLoaded ||
+ paginatedRows.length === 0 ||
+ columns.length === 0;
+
+ const printButtonLabel = (() => {
+ if (isPreparingPrint) {
+ return "鍑嗗鎵撳嵃涓�...";
+ }
+ if (prefetching || !allRowsLoaded) {
+ return `鍔犺浇鎵撳嵃鏁版嵁涓�... ${loadedTransportPages}/${Math.max(totalTransportPages, 1)}`;
+ }
+ if (measuring) {
+ return "鍒嗛〉璁$畻涓�...";
+ }
+ return translate("toolbar.print");
+ })();
+
+ return (
+ <Dialog open={open} onClose={onClose} fullWidth maxWidth="xl">
+ <DialogTitle
+ sx={{
+ position: "sticky",
+ top: 0,
+ backgroundColor: "background.paper",
+ zIndex: 1,
+ textAlign: "center",
+ }}
+ >
+ {dialogTitle}
+ <Box sx={{ position: "absolute", right: 8, top: 8 }}>
+ <DialogCloseButton onClose={onClose} />
+ </Box>
+ </DialogTitle>
+ <DialogContent dividers>
+ {loading ? (
+ <Box
+ sx={{
+ minHeight: 280,
+ display: "flex",
+ alignItems: "center",
+ justifyContent: "center",
+ }}
+ >
+ <CircularProgress size={28} />
+ </Box>
+ ) : (
+ <Box
+ className="list-report-print-shell"
+ sx={{
+ display: "flex",
+ flexDirection: "column",
+ gap: 2,
+ alignItems: "center",
+ py: 3,
+ }}
+ >
+ {paginatedRows.length > 0
+ ? renderPage(
+ currentPageRows,
+ Math.max(currentPage - 1, 0),
+ Math.max(paginatedRows.length, 1)
+ )
+ : (
+ <Box
+ sx={{
+ minHeight: 280,
+ display: "flex",
+ alignItems: "center",
+ justifyContent: "center",
+ color: "#6b7280",
+ }}
+ >
+ {prefetching || measuring ? "姝e湪鐢熸垚棰勮..." : "鏆傛棤鍙墦鍗版暟鎹�"}
+ </Box>
+ )}
+ </Box>
+ )}
+ </DialogContent>
+ <DialogActions sx={{ px: 3, py: 2, justifyContent: "space-between", gap: 2, flexWrap: "wrap" }}>
+ <Box sx={{ display: "flex", gap: 2, alignItems: "center", flexWrap: "wrap" }}>
+ <ToggleButtonGroup
+ value={orientation}
+ exclusive
+ size="small"
+ onChange={(_, value) => {
+ if (value) {
+ setCurrentPage(1);
+ setOrientation(value);
+ }
+ }}
+ >
+ <ToggleButton value="landscape">妯増</ToggleButton>
+ <ToggleButton value="portrait">绔栫増</ToggleButton>
+ </ToggleButtonGroup>
+ <Button
+ size="small"
+ onClick={() => setCurrentPage(page => Math.max(1, page - 1))}
+ disabled={loading || currentPage <= 1}
+ >
+ 涓婁竴椤�
+ </Button>
+ <Typography variant="body2" sx={{ minWidth: 104, textAlign: "center" }}>
+ 绗� {paginatedRows.length === 0 ? 0 : currentPage} / {paginatedRows.length} 椤�
+ </Typography>
+ <Button
+ size="small"
+ onClick={() => setCurrentPage(page => Math.min(paginatedRows.length, page + 1))}
+ disabled={loading || paginatedRows.length === 0 || currentPage >= paginatedRows.length}
+ >
+ 涓嬩竴椤�
+ </Button>
+ {(prefetching || measuring) && (
+ <Typography variant="body2" sx={{ color: "#6b7280" }}>
+ {prefetching
+ ? `姝e湪鍔犺浇瀹屾暣鎵撳嵃鏁版嵁 ${loadedTransportPages}/${Math.max(totalTransportPages, 1)}`
+ : "姝e湪鎸夌焊寮犲昂瀵搁噸鏂板垎椤�"}
+ </Typography>
+ )}
+ </Box>
+ <Button
+ variant="contained"
+ startIcon={
+ isPreparingPrint || prefetching || measuring
+ ? <CircularProgress size={16} color="inherit" />
+ : <PrintOutlinedIcon />
+ }
+ onClick={() => setIsPreparingPrint(true)}
+ disabled={printDisabled}
+ >
+ {printButtonLabel}
+ </Button>
+ </DialogActions>
+
+ {open && rows.length > 0 && columns.length > 0 && (
+ <Box
+ sx={{
+ position: "fixed",
+ left: "-20000px",
+ top: 0,
+ visibility: "hidden",
+ pointerEvents: "none",
+ backgroundColor: "#fff",
+ }}
+ >
+ <ReportPageFrame paperSize={previewPaperSize} printable>
+ <Box ref={measurePageRef} sx={{ width: "100%", height: "100%" }}>
+ <Box ref={measureContentRef} sx={{ display: "flex", flexDirection: "column", gap: 2, width: "100%", height: "100%" }}>
+ <ReportHeader printedAt={printedAt} reportMeta={reportMeta} totalCount={totalCount} />
+ <ReportTable
+ displayColumns={displayColumns}
+ pageRows={rows}
+ renderRow={(record, rowIndex) => renderDataRow(record, rowIndex, 0, "measure")}
+ tableWrapRef={measureTableWrapRef}
+ theadRef={measureTheadRef}
+ tbodyRef={measureBodyRef}
+ />
+ <Box
+ ref={measureFooterRef}
+ sx={{
+ display: "flex",
+ justifyContent: "flex-end",
+ color: "#6b7280",
+ fontSize: 12,
+ }}
+ >
+ 绗� 1 / 1 椤�
+ </Box>
+ </Box>
+ </Box>
+ </ReportPageFrame>
+ </Box>
+ )}
+
+ {isPreparingPrint && (
+ <Box
+ ref={printContentRef}
+ className="list-report-print-shell list-report-print-live-root"
+ sx={{
+ position: "fixed",
+ left: "-10000px",
+ top: 0,
+ backgroundColor: "#fff",
+ px: 3,
+ py: 3,
+ }}
+ >
+ {paginatedRows.map((pageRows, pageIndex) =>
+ renderPage(pageRows, pageIndex, paginatedRows.length, true)
+ )}
+ </Box>
+ )}
+ </Dialog>
+ );
+};
+
+export default ListReportPreviewDialog;
diff --git a/rsf-admin/src/page/components/listReport/MyPrintButton.jsx b/rsf-admin/src/page/components/listReport/MyPrintButton.jsx
new file mode 100644
index 0000000..038b48f
--- /dev/null
+++ b/rsf-admin/src/page/components/listReport/MyPrintButton.jsx
@@ -0,0 +1,38 @@
+import React from "react";
+import PrintOutlinedIcon from "@mui/icons-material/PrintOutlined";
+import { Button } from "react-admin";
+import { useListReportActionParams } from "./useListReportOutput";
+
+const MyPrintButton = ({
+ reportConfig,
+ onPrintPreview,
+ label = "toolbar.print",
+ icon = <PrintOutlinedIcon />,
+ loading = false,
+ disabled,
+ ...rest
+}) => {
+ const { visibleColumns, params } = useListReportActionParams(reportConfig);
+ const resolvedDisabled = disabled ?? (
+ params.total === 0 ||
+ loading ||
+ visibleColumns.length === 0
+ );
+
+ if (!reportConfig?.enablePrint) {
+ return null;
+ }
+
+ return (
+ <Button
+ onClick={() => onPrintPreview({ ...params, columns: visibleColumns })}
+ label={label}
+ disabled={resolvedDisabled}
+ {...rest}
+ >
+ {icon}
+ </Button>
+ );
+};
+
+export default MyPrintButton;
diff --git a/rsf-admin/src/page/components/listReport/listReportUtils.js b/rsf-admin/src/page/components/listReport/listReportUtils.js
new file mode 100644
index 0000000..e938b5e
--- /dev/null
+++ b/rsf-admin/src/page/components/listReport/listReportUtils.js
@@ -0,0 +1,162 @@
+import * as Common from "@/utils/common";
+
+export const DEFAULT_PRINT_PREVIEW_FETCH_PAGE_SIZE = 100;
+
+export const downloadBlobFile = (response, filename) => {
+ const blob = new Blob([response.data], { type: response.headers["content-type"] });
+ const url = window.URL.createObjectURL(blob);
+ const link = document.createElement("a");
+ link.href = url;
+ link.setAttribute("download", filename);
+ document.body.appendChild(link);
+ link.click();
+ link.remove();
+ window.URL.revokeObjectURL(url);
+};
+
+export const buildDefaultReportMeta = title => {
+ let user = null;
+ try {
+ const persistedUser = localStorage.getItem("user");
+ user = persistedUser ? JSON.parse(persistedUser) : null;
+ } catch (error) {
+ console.warn("Failed to parse persisted user", error);
+ }
+
+ const tenant = user?.tenant || {};
+ const now = new Date();
+ const year = now.getFullYear();
+ const month = `${now.getMonth() + 1}`.padStart(2, "0");
+ const day = `${now.getDate()}`.padStart(2, "0");
+
+ return {
+ companyName:
+ tenant?.name ||
+ tenant?.tenantName ||
+ tenant?.label ||
+ user?.fullName ||
+ "RSF",
+ printedBy: user?.fullName || user?.username || "绯荤粺鐢ㄦ埛",
+ reportDate: `${year}骞�${month}鏈�${day}鏃,
+ reportDateValue: `${year}-${month}-${day}`,
+ title,
+ };
+};
+
+export const buildListReportRequestPayload = ({
+ filter,
+ filterValues,
+ sort,
+ ids,
+ columns,
+ reportMeta,
+ extraPayload = {},
+}) => ({
+ ...Common.integrateParams({
+ sort,
+ filter: filter ? { ...filterValues, ...filter } : filterValues,
+ }),
+ ids: ids?.length ? ids : undefined,
+ columns,
+ reportMeta,
+ ...extraPayload,
+});
+
+const getColumnToken = column => column?.key || column?.source;
+
+const resolveColumnByToken = (columnDefs, token) => {
+ if (typeof token === "number" || /^\d+$/.test(String(token))) {
+ return columnDefs[Number(token)];
+ }
+
+ if (typeof token === "string") {
+ return columnDefs.find(column => column.key === token || column.source === token);
+ }
+
+ if (token && typeof token === "object") {
+ if ("index" in token) {
+ return columnDefs[Number(token.index)];
+ }
+ if ("source" in token) {
+ return columnDefs.find(column => column.source === token.source);
+ }
+ if ("key" in token) {
+ return columnDefs.find(column => column.key === token.key);
+ }
+ if ("id" in token) {
+ return columnDefs.find(column => column.key === token.id || column.source === token.id);
+ }
+ }
+
+ return undefined;
+};
+
+const buildDefaultVisibleColumns = (columnDefs, omitSet) =>
+ columnDefs.filter(column => !omitSet.has(column.source) && !omitSet.has(column.key));
+
+export const resolveVisibleColumns = ({
+ columnDefs = [],
+ storedColumns = [],
+ availableColumns = [],
+ storedOmit = [],
+ omit = [],
+}) => {
+ if (!Array.isArray(columnDefs) || columnDefs.length === 0) {
+ return [];
+ }
+
+ const omitSet = new Set([...(omit || []), ...(storedOmit || [])]);
+ const fallbackColumns = buildDefaultVisibleColumns(columnDefs, omitSet);
+ const storedOrder = Array.isArray(storedColumns) && storedColumns.length > 0
+ ? storedColumns
+ : Array.isArray(availableColumns) && availableColumns.length > 0
+ ? availableColumns
+ : [];
+
+ const resolvedColumns = storedOrder
+ .map(token => resolveColumnByToken(columnDefs, token))
+ .filter(Boolean)
+ .filter(column => !omitSet.has(column.source) && !omitSet.has(column.key));
+
+ const visibleColumns = resolvedColumns.length > 0 ? resolvedColumns : fallbackColumns;
+
+ return visibleColumns.map(column => ({
+ key: column.key,
+ source: column.source,
+ label: column.label,
+ isExtendField: Boolean(column.isExtendField),
+ }));
+};
+
+const getNestedValue = (record, source) =>
+ source.split(".").reduce((accumulator, segment) => {
+ if (accumulator == null) {
+ return undefined;
+ }
+ return accumulator[segment];
+ }, record);
+
+export const getListReportCellValue = (record, source) => {
+ if (!record || !source) {
+ return "";
+ }
+
+ const extendFieldMatch = source.match(/^extendFields\.\[(.+)\]$/);
+ if (extendFieldMatch) {
+ return record?.extendFields?.[extendFieldMatch[1]] ?? "";
+ }
+
+ if (Object.prototype.hasOwnProperty.call(record, source)) {
+ return record[source] ?? "";
+ }
+
+ const value = source.includes(".") ? getNestedValue(record, source) : record[source];
+ return value ?? "";
+};
+
+export const resolveReportMeta = (reportConfig, params) => {
+ if (typeof reportConfig?.buildReportMeta === "function") {
+ return reportConfig.buildReportMeta(params);
+ }
+ return reportConfig?.reportMeta || null;
+};
diff --git a/rsf-admin/src/page/components/listReport/useListReportOutput.js b/rsf-admin/src/page/components/listReport/useListReportOutput.js
new file mode 100644
index 0000000..6f9950b
--- /dev/null
+++ b/rsf-admin/src/page/components/listReport/useListReportOutput.js
@@ -0,0 +1,292 @@
+import { useCallback, useMemo, useRef, useState } from "react";
+import { useListContext, useStore } from "react-admin";
+import request from "@/utils/request";
+import {
+ DEFAULT_PRINT_PREVIEW_FETCH_PAGE_SIZE,
+ downloadBlobFile,
+ resolveReportMeta,
+ resolveVisibleColumns,
+} from "./listReportUtils";
+
+const getPreferencePath = (preferenceKey, field) =>
+ `preferences.${preferenceKey || "__listReport__"}.${field}`;
+
+export const useListReportActionParams = (reportConfig, options = {}) => {
+ const { ids: explicitIds } = options;
+ const { filter, filterValues, resource, selectedIds, sort, total } = useListContext();
+ const [storedColumns] = useStore(
+ getPreferencePath(reportConfig?.preferenceKey, "columns"),
+ []
+ );
+ const [availableColumns] = useStore(
+ getPreferencePath(reportConfig?.preferenceKey, "availableColumns"),
+ []
+ );
+ const [storedOmit] = useStore(
+ getPreferencePath(reportConfig?.preferenceKey, "omit"),
+ []
+ );
+
+ const visibleColumns = useMemo(
+ () =>
+ resolveVisibleColumns({
+ columnDefs: reportConfig?.columns,
+ storedColumns,
+ availableColumns,
+ storedOmit,
+ omit: reportConfig?.omit,
+ }),
+ [availableColumns, reportConfig?.columns, reportConfig?.omit, storedColumns, storedOmit]
+ );
+
+ return {
+ visibleColumns,
+ params: {
+ filter,
+ filterValues,
+ ids: explicitIds ?? selectedIds,
+ resource: reportConfig?.resource || resource,
+ sort,
+ total,
+ },
+ };
+};
+
+export const useListReportOutput = ({
+ reportConfig,
+ buildRequestPayload,
+ notify,
+}) => {
+ const [exportLoading, setExportLoading] = useState(false);
+ const [previewOpen, setPreviewOpen] = useState(false);
+ const [previewRows, setPreviewRows] = useState([]);
+ const [previewColumns, setPreviewColumns] = useState([]);
+ const [previewMeta, setPreviewMeta] = useState(null);
+ const [previewDataset, setPreviewDataset] = useState({
+ loadedPages: 0,
+ transportPages: 0,
+ pageSize: reportConfig?.previewPageSize || DEFAULT_PRINT_PREVIEW_FETCH_PAGE_SIZE,
+ total: 0,
+ fullyLoaded: false,
+ });
+ const [previewLoading, setPreviewLoading] = useState(false);
+ const [previewPrefetching, setPreviewPrefetching] = useState(false);
+ const requestIdRef = useRef(0);
+ const cacheRef = useRef(new Map());
+
+ const previewPageSize = reportConfig?.previewPageSize || DEFAULT_PRINT_PREVIEW_FETCH_PAGE_SIZE;
+
+ const buildPayload = useCallback(
+ params => {
+ const payload = typeof buildRequestPayload === "function"
+ ? buildRequestPayload(params)
+ : {};
+ if (payload.reportMeta) {
+ return payload;
+ }
+ return {
+ ...payload,
+ reportMeta: resolveReportMeta(reportConfig, params),
+ };
+ },
+ [buildRequestPayload, reportConfig]
+ );
+
+ const resetPreviewCache = useCallback(() => {
+ cacheRef.current = new Map();
+ setPreviewRows([]);
+ setPreviewColumns([]);
+ setPreviewMeta(null);
+ setPreviewDataset({
+ loadedPages: 0,
+ transportPages: 0,
+ pageSize: previewPageSize,
+ total: 0,
+ fullyLoaded: false,
+ });
+ }, [previewPageSize]);
+
+ const fetchPreviewPage = useCallback(
+ async ({ payload, current }) => {
+ const { data: { code, data, msg } } = await request.post(
+ `/${reportConfig.resource}/print/query`,
+ {
+ ...payload,
+ current,
+ pageSize: previewPageSize,
+ }
+ );
+
+ if (code !== 200) {
+ throw new Error(msg || "Print preview query failed");
+ }
+
+ return data || {};
+ },
+ [previewPageSize, reportConfig.resource]
+ );
+
+ const syncPreviewCache = useCallback(
+ ({ totalPages, totalRows }) => {
+ const nextRows = [];
+ for (let pageIndex = 1; pageIndex <= totalPages; pageIndex += 1) {
+ const pageRows = cacheRef.current.get(pageIndex);
+ if (Array.isArray(pageRows)) {
+ nextRows.push(...pageRows);
+ }
+ }
+
+ setPreviewRows(nextRows);
+ setPreviewDataset({
+ loadedPages: cacheRef.current.size,
+ transportPages: totalPages,
+ pageSize: previewPageSize,
+ total: totalRows,
+ fullyLoaded: cacheRef.current.size >= totalPages,
+ });
+ },
+ [previewPageSize]
+ );
+
+ const applyPreviewPage = useCallback(
+ ({ data, payload, openPreview = false }) => {
+ const current = Number(data.current) || 1;
+ const totalPages = Number(data.pages) || 0;
+ const totalRows = Number(data.total) || 0;
+
+ setPreviewColumns(payload.columns || []);
+ setPreviewMeta(payload.reportMeta || null);
+ cacheRef.current.set(current, Array.isArray(data.records) ? data.records : []);
+ syncPreviewCache({ totalPages, totalRows });
+
+ if (openPreview) {
+ setPreviewOpen(true);
+ }
+ },
+ [syncPreviewCache]
+ );
+
+ const prefetchPreviewPages = useCallback(
+ async ({ payload, totalPages, totalRows, requestId }) => {
+ if (totalPages <= 1) {
+ setPreviewPrefetching(false);
+ return;
+ }
+
+ setPreviewPrefetching(true);
+ const pendingPages = Array.from({ length: totalPages - 1 }, (_, index) => index + 2);
+ const workerCount = Math.min(3, pendingPages.length);
+ let hasErrored = false;
+
+ const worker = async () => {
+ while (pendingPages.length > 0 && requestIdRef.current === requestId) {
+ const nextPage = pendingPages.shift();
+ if (!nextPage) {
+ return;
+ }
+
+ try {
+ const data = await fetchPreviewPage({ payload, current: nextPage });
+ if (requestIdRef.current !== requestId) {
+ return;
+ }
+ cacheRef.current.set(nextPage, Array.isArray(data.records) ? data.records : []);
+ syncPreviewCache({ totalPages, totalRows });
+ } catch (error) {
+ if (!hasErrored && requestIdRef.current === requestId) {
+ hasErrored = true;
+ console.error(error);
+ notify(error.message || "ra.notification.http_error", { type: "error" });
+ }
+ return;
+ }
+ }
+ };
+
+ await Promise.all(Array.from({ length: workerCount }, () => worker()));
+ if (requestIdRef.current === requestId) {
+ setPreviewPrefetching(false);
+ }
+ },
+ [fetchPreviewPage, notify, syncPreviewCache]
+ );
+
+ const openPrintPreview = useCallback(
+ async params => {
+ if (!reportConfig?.enablePrint) {
+ return;
+ }
+ setPreviewLoading(true);
+ try {
+ requestIdRef.current += 1;
+ const requestId = requestIdRef.current;
+ resetPreviewCache();
+ const payload = buildPayload(params);
+ const data = await fetchPreviewPage({ payload, current: 1 });
+ applyPreviewPage({ data, payload, openPreview: true });
+ prefetchPreviewPages({
+ payload,
+ totalPages: Number(data.pages) || 0,
+ totalRows: Number(data.total) || 0,
+ requestId,
+ });
+ } catch (error) {
+ console.error(error);
+ notify(error.message || "ra.notification.http_error", { type: "error" });
+ } finally {
+ setPreviewLoading(false);
+ }
+ },
+ [
+ applyPreviewPage,
+ buildPayload,
+ fetchPreviewPage,
+ notify,
+ prefetchPreviewPages,
+ reportConfig?.enablePrint,
+ resetPreviewCache,
+ ]
+ );
+
+ const closePrintPreview = useCallback(() => {
+ requestIdRef.current += 1;
+ setPreviewPrefetching(false);
+ setPreviewOpen(false);
+ }, []);
+
+ const exportReport = useCallback(
+ async params => {
+ setExportLoading(true);
+ try {
+ const payload = buildPayload(params);
+ const response = await request.post(`/${reportConfig.resource}/export`, payload, {
+ responseType: "blob",
+ });
+ downloadBlobFile(
+ response,
+ reportConfig.exportFileName || `${reportConfig.resource}.xlsx`
+ );
+ } catch (error) {
+ console.error(error);
+ notify("ra.notification.http_error", { type: "error" });
+ } finally {
+ setExportLoading(false);
+ }
+ },
+ [buildPayload, notify, reportConfig.exportFileName, reportConfig.resource]
+ );
+
+ return {
+ exportLoading,
+ previewOpen,
+ previewRows,
+ previewColumns,
+ previewMeta,
+ previewDataset,
+ previewLoading,
+ previewPrefetching,
+ openPrintPreview,
+ closePrintPreview,
+ exportReport,
+ };
+};
diff --git a/rsf-admin/src/page/warehouseAreasItem/WarehouseAreasItemList.jsx b/rsf-admin/src/page/warehouseAreasItem/WarehouseAreasItemList.jsx
index 3e6af78..b12e6e7 100644
--- a/rsf-admin/src/page/warehouseAreasItem/WarehouseAreasItemList.jsx
+++ b/rsf-admin/src/page/warehouseAreasItem/WarehouseAreasItemList.jsx
@@ -1,267 +1,228 @@
-import React, { useState, useRef, useEffect, useMemo, useCallback } from "react";
-import { useNavigate } from 'react-router-dom';
+import React, { useEffect, useMemo, useState } from "react";
import {
- List,
DatagridConfigurable,
- SearchInput,
- TopToolbar,
- SelectColumnsButton,
- EditButton,
- FilterButton,
- CreateButton,
- ExportButton,
- BulkDeleteButton,
- WrapperField,
- useRecordContext,
- useTranslate,
- useNotify,
+ List,
useListContext,
- FunctionField,
- TextField,
- NumberField,
- DateField,
- BooleanField,
- ReferenceField,
- TextInput,
- DateTimeInput,
- DateInput,
- SelectInput,
- NumberInput,
- ReferenceInput,
- ReferenceArrayInput,
- useRefresh,
- AutocompleteInput,
- DeleteButton,
-} from 'react-admin';
-import { Box, Typography, Card, Stack, LinearProgress } from '@mui/material';
-import { styled } from '@mui/material/styles';
-import WarehouseAreasItemCreate from "./WarehouseAreasItemCreate";
-import WarehouseAreasItemPanel from "./WarehouseAreasItemPanel";
-import EmptyData from "../components/EmptyData";
-import request from '@/utils/request';
-import MyCreateButton from "../components/MyCreateButton";
-import MyExportButton from '../components/MyExportButton';
-import PageDrawer from "../components/PageDrawer";
-import MyField from "../components/MyField";
-import { PAGE_DRAWER_WIDTH, OPERATE_MODE, DEFAULT_PAGE_SIZE } from '@/config/setting';
-import * as Common from '@/utils/common';
-import WarehouseIsptResult from "./WarehouseIsptResult"
+ useNotify,
+ useTranslate,
+} from "react-admin";
+import { Box, LinearProgress } from "@mui/material";
+import { styled } from "@mui/material/styles";
+import { DEFAULT_PAGE_SIZE } from "@/config/setting";
+import ListReportActions, {
+ ListReportBulkActions,
+} from "@/page/components/listReport/ListReportActions";
+import ListReportPreviewDialog from "@/page/components/listReport/ListReportPreviewDialog";
+import { useListReportOutput } from "@/page/components/listReport/useListReportOutput";
+import { buildListReportRequestPayload } from "@/page/components/listReport/listReportUtils";
+import request from "@/utils/request";
+import WarehouseIsptResult from "./WarehouseIsptResult";
+import {
+ buildWarehouseAreasItemBaseFilters,
+ buildWarehouseAreasItemDynamicFilters,
+ buildWarehouseAreasItemReportConfig,
+} from "./warehouseAreasItemOutputUtils.jsx";
-
-const StyledDatagrid = styled(DatagridConfigurable)(({ theme }) => ({
- '& .css-1vooibu-MuiSvgIcon-root': {
- height: '.9em'
+const StyledDatagrid = styled(DatagridConfigurable)(() => ({
+ "& .css-1vooibu-MuiSvgIcon-root": {
+ height: ".9em",
},
- '& .RaDatagrid-row': {
- cursor: 'auto'
+ "& .RaDatagrid-row": {
+ cursor: "auto",
},
- '& .column-name': {
+ "& .opt": {
+ width: 200,
},
- '& .opt': {
- width: 200
+ "& .MuiTableCell-root": {
+ whiteSpace: "nowrap",
+ overflow: "visible",
+ textOverflow: "unset",
},
- '& .MuiTableCell-root': {
- whiteSpace: 'nowrap',
- overflow: 'visible',
- textOverflow: 'unset'
- }
}));
-
-const filters = [
- <SearchInput source="condition" alwaysOn />,
- <NumberInput source="areaId" label="table.field.warehouseAreasItem.areaId" />,
- <TextInput source="asnCode" label="table.field.warehouseAreasItem.asnCode" />,
- <TextInput source="areaName" label="table.field.warehouseAreasItem.areaName" />,
- <NumberInput source="matnrId" label="table.field.warehouseAreasItem.matnrId" />,
- <TextInput source="matnrName" label="table.field.warehouseAreasItem.matnrName" />,
- <TextInput source="matnrCode" label="table.field.warehouseAreasItem.matnrCode" />,
- <TextInput source="barcode" label="table.field.warehouseAreasItem.barcode" />,
- <NumberInput source="anfme" label="table.field.warehouseAreasItem.anfme" />,
- <TextInput source="batch" label="table.field.warehouseAreasItem.batch" />,
- <TextInput source="platOrderCode" label="table.field.asnOrderItem.platOrderCode" />,
- <TextInput source="platWorkCode" label="table.field.asnOrderItem.platWorkCode" />,
- <TextInput source="projectCode" label="table.field.asnOrderItem.projectCode" />,
- <TextInput source="unit" label="table.field.warehouseAreasItem.unit" />,
- <TextInput source="stockUnit" label="table.field.warehouseAreasItem.stockUnit" />,
- <TextInput source="brand" label="table.field.warehouseAreasItem.brand" />,
- <ReferenceInput source="shipperId" label="table.field.warehouseAreasItem.shipperId" reference="companys">
- <AutocompleteInput label="table.field.warehouseAreasItem.shipperId" optionText="name" filterToQuery={(val) => ({ name: val })} />
- </ReferenceInput>,
- <TextInput source="splrId" label="table.field.warehouseAreasItem.splrId" />,
- <NumberInput source="weight" label="table.field.warehouseAreasItem.weight" />,
- <TextInput source="prodTime" label="table.field.warehouseAreasItem.prodTime" />,
- <TextInput source="splrBtch" label="table.field.warehouseAreasItem.splrBtch" />,
-
- <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
- />,
-]
const WarehouseAreasItemList = () => {
const translate = useTranslate();
- const [itemInfo, setItemInfo] = useState({})
- const [createDialog, setCreateDialog] = useState(false);
+ const notify = useNotify();
+ const [itemInfo] = useState({});
const [drawerVal, setDrawerVal] = useState(false);
+ const { dynamicFields, dynamicFieldsLoading } = useWarehouseAreasItemDynamicFields(notify);
+
+ const filters = useMemo(
+ () => [
+ ...buildWarehouseAreasItemBaseFilters(),
+ ...buildWarehouseAreasItemDynamicFilters(dynamicFields),
+ ],
+ [dynamicFields]
+ );
+
+ const reportConfig = useMemo(
+ () => buildWarehouseAreasItemReportConfig(translate, dynamicFields),
+ [dynamicFields, translate]
+ );
+
+ const buildOutputPayload = params => buildListReportRequestPayload({
+ ...params,
+ reportMeta: reportConfig.buildReportMeta?.(params),
+ });
+
+ const {
+ exportLoading,
+ previewOpen,
+ previewRows,
+ previewColumns,
+ previewMeta,
+ previewDataset,
+ previewLoading,
+ previewPrefetching,
+ openPrintPreview,
+ closePrintPreview,
+ exportReport,
+ } = useListReportOutput({
+ reportConfig,
+ buildRequestPayload: buildOutputPayload,
+ notify,
+ });
+
+ const reportLoading = dynamicFieldsLoading || exportLoading || previewLoading;
return (
<Box display="flex">
<List
- title={"menu.warehouseAreasItem"}
+ title="menu.warehouseAreasItem"
empty={false}
filters={filters}
sort={{ field: "create_time", order: "desc" }}
sx={{
flexGrow: 1,
- transition: (theme) =>
- theme.transitions.create(['all'], {
+ transition: theme =>
+ theme.transitions.create(["all"], {
duration: theme.transitions.duration.enteringScreen,
}),
}}
actions={(
- <TopToolbar>
- <FilterButton />
- <SelectColumnsButton preferenceKey='warehouseAreasItem' />
- <MyExportButton />
- </TopToolbar>
+ <ListReportActions
+ reportConfig={reportConfig}
+ loading={reportLoading}
+ onExport={exportReport}
+ onPrintPreview={openPrintPreview}
+ />
)}
perPage={DEFAULT_PAGE_SIZE}
>
<DynamicFields
- drawerVal={drawerVal}
- setDrawerVal={setDrawerVal}
- itemInfo={itemInfo}
- setItemInfo={setItemInfo} />
+ reportConfig={reportConfig}
+ loading={dynamicFieldsLoading}
+ onExport={exportReport}
+ onPrintPreview={openPrintPreview}
+ />
</List>
- <WarehouseAreasItemCreate
- open={createDialog}
- setOpen={setCreateDialog}
- />
+
<WarehouseIsptResult
record={itemInfo}
drawerVal={drawerVal}
from="warehosueItem"
setDrawerVal={setDrawerVal}
- >
- </WarehouseIsptResult>
- {/* <PageDrawer
- title='WarehouseAreasItem Detail'
- drawerVal={drawerVal}
- setDrawerVal={setDrawerVal}
- >
- </PageDrawer> */}
+ />
+
+ <ListReportPreviewDialog
+ open={previewOpen}
+ onClose={closePrintPreview}
+ rows={previewRows}
+ columns={previewColumns}
+ reportMeta={previewMeta}
+ totalRows={previewDataset.total}
+ loading={previewLoading}
+ allRowsLoaded={previewDataset.fullyLoaded}
+ loadedTransportPages={previewDataset.loadedPages}
+ totalTransportPages={previewDataset.transportPages}
+ prefetching={previewPrefetching}
+ dialogTitle="鏀惰揣搴撳瓨鎵撳嵃棰勮"
+ defaultOrientation={reportConfig.defaultOrientation}
+ />
</Box>
- )
-}
+ );
+};
export default WarehouseAreasItemList;
-
-const DynamicFields = (props) => {
- const { drawerVal, setDrawerVal, itemInfo, setItemInfo } = props
- const translate = useTranslate();
- const notify = useNotify();
- const [columns, setColumns] = useState([]);
+const DynamicFields = ({ reportConfig, loading, onExport, onPrintPreview }) => {
const { isLoading } = useListContext();
- const refresh = useRefresh();
- useEffect(() => {
- getDynamicFields();
- }, []);
-
- const getDynamicFields = async () => {
- const { data: { code, data, msg }, } = await request.get("/fields/enable/list");
- if (code == 200) {
- const arr = [
- <NumberField key="id" source="id" />,
- // <NumberField key="areaId" source="areaId" label="table.field.warehouseAreasItem.areaId" />,
- <TextField key="areaName" source="areaName" label="鏀惰揣鍖哄悕绉�" />, //table.field.warehouseAreasItem.areaName
- <TextField key="asnCode" source="asnCode" label="table.field.warehouseAreasItem.asnCode" />,
- <TextField source="platWorkCode" label="table.field.asnOrderItem.platWorkCode" />,
- <TextField 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 source="platOrderCode" label="table.field.asnOrderItem.platOrderCode" />,
- <TextField source="projectCode" label="table.field.asnOrderItem.projectCode" />,
- // <MyField source="isptQty" label="table.field.qlyIsptItem.anfme"
- // onClick={(event, record, val) => {
- // event.stopPropagation();
- // setItemInfo(record)
- // setDrawerVal(!!drawerVal && drawerVal === val ? null : val);
- // }}
- // />,
-
-
- // <TextField key="stockUnit" source="stockUnit" label="table.field.warehouseAreasItem.stockUnit" />,
- <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 fields = data.map(el => <TextField key={el.fields} source={`extendFields.[${el.fields}]`} label={el.fieldsAlise} />)
- const lastArr = [
- <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} />,
- ]
- setColumns([...arr, ...fields, ...lastArr]);
- //filters娣诲姞杩囨护瀛楁
- data.map(el => {
- var i = 0;
- filters.map((item) => {
- if (item.key === el.fields) {
- i = 1;
- }
- })
- i === 0 && filters.push(<TextInput key={el.fields} source={el.fields} label={el.fieldsAlise} />)
- })
- } else {
- notify(msg);
- }
- }
return (
- <Box sx={{ position: 'relative', minHeight: "82vh", }}>
- {isLoading && (
+ <Box sx={{ position: "relative", minHeight: "82vh" }}>
+ {(isLoading || loading) && (
<LinearProgress
sx={{
height: "2px",
- position: 'absolute',
+ position: "absolute",
top: 0,
left: 0,
right: 0,
}}
/>
)}
- {columns.length > 0 &&
+
+ {reportConfig.columns.length > 0 && (
<StyledDatagrid
- preferenceKey='warehouseAreasItem'
- bulkActionButtons={false}
- rowClick={(id, resource, record) => false}
- omit={['prodTime','platOrderCode','id', 'createTime', 'memo', 'areaId', 'brand',
- 'weight', 'splrId', 'projectCode','statusBool', 'extendFields.[priceUnitId]', 'isptResult$', 'extendFields.[inStockType]',
- 'matnrId', 'trackCode', 'workQty', 'batch', 'shipperId', 'isptResult', 'createBy$', 'createTime', 'extendFields.[baseUnitId]']}
+ preferenceKey={reportConfig.preferenceKey}
+ bulkActionButtons={(
+ <ListReportBulkActions
+ reportConfig={reportConfig}
+ loading={loading}
+ onExport={onExport}
+ onPrintPreview={onPrintPreview}
+ />
+ )}
+ rowClick={false}
+ omit={reportConfig.omit}
>
- {columns.map((column) => column)}
- </StyledDatagrid>}
+ {reportConfig.columns.map(column => column.element)}
+ </StyledDatagrid>
+ )}
</Box>
- )
-}
+ );
+};
+
+const useWarehouseAreasItemDynamicFields = notify => {
+ const [dynamicFields, setDynamicFields] = useState([]);
+ const [dynamicFieldsLoading, setDynamicFieldsLoading] = useState(true);
+
+ useEffect(() => {
+ let active = true;
+
+ const loadDynamicFields = async () => {
+ setDynamicFieldsLoading(true);
+ try {
+ const { data: { code, data, msg } } = await request.get("/fields/enable/list");
+ if (!active) {
+ return;
+ }
+ if (code === 200) {
+ setDynamicFields(Array.isArray(data) ? data : []);
+ return;
+ }
+ notify(msg, { type: "warning" });
+ } catch (error) {
+ if (!active) {
+ return;
+ }
+ console.error(error);
+ notify("ra.notification.http_error", { type: "error" });
+ } finally {
+ if (active) {
+ setDynamicFieldsLoading(false);
+ }
+ }
+ };
+
+ loadDynamicFields();
+ return () => {
+ active = false;
+ };
+ }, [notify]);
+
+ return {
+ dynamicFields,
+ dynamicFieldsLoading,
+ };
+};
diff --git a/rsf-admin/src/page/warehouseAreasItem/WarehouseAreasItemPrintPreview.jsx b/rsf-admin/src/page/warehouseAreasItem/WarehouseAreasItemPrintPreview.jsx
new file mode 100644
index 0000000..e44be32
--- /dev/null
+++ b/rsf-admin/src/page/warehouseAreasItem/WarehouseAreasItemPrintPreview.jsx
@@ -0,0 +1 @@
+export { default } from "@/page/components/listReport/ListReportPreviewDialog";
diff --git a/rsf-admin/src/page/warehouseAreasItem/warehouseAreasItemOutputUtils.jsx b/rsf-admin/src/page/warehouseAreasItem/warehouseAreasItemOutputUtils.jsx
new file mode 100644
index 0000000..325533e
--- /dev/null
+++ b/rsf-admin/src/page/warehouseAreasItem/warehouseAreasItemOutputUtils.jsx
@@ -0,0 +1,493 @@
+import React from "react";
+import {
+ BooleanField,
+ DateField,
+ NumberField,
+ SearchInput,
+ SelectInput,
+ TextField,
+ TextInput,
+ NumberInput,
+ ReferenceInput,
+ AutocompleteInput,
+} from "react-admin";
+import { buildDefaultReportMeta } from "@/page/components/listReport/listReportUtils";
+
+export const WAREHOUSE_AREAS_ITEM_PREFERENCE_KEY = "warehouseAreasItem";
+
+export const WAREHOUSE_AREAS_ITEM_OMIT = [
+ "prodTime",
+ "platOrderCode",
+ "id",
+ "createTime",
+ "memo",
+ "areaId",
+ "brand",
+ "weight",
+ "splrId",
+ "projectCode",
+ "statusBool",
+ "extendFields.[priceUnitId]",
+ "isptResult$",
+ "extendFields.[inStockType]",
+ "matnrId",
+ "trackCode",
+ "workQty",
+ "batch",
+ "shipperId",
+ "isptResult",
+ "createBy$",
+ "createTime",
+ "extendFields.[baseUnitId]",
+];
+
+const resolveLabel = (translate, label, fallback = "") => {
+ if (!label) {
+ return fallback;
+ }
+ const translated = translate(label, { _: label });
+ return translated || fallback || label;
+};
+
+export const buildWarehouseAreasItemReportMeta = () => {
+ return {
+ ...buildDefaultReportMeta("鏀惰揣搴撳瓨鎶ヨ〃"),
+ };
+};
+
+export const buildWarehouseAreasItemBaseFilters = () => ([
+ <SearchInput key="condition" source="condition" alwaysOn />,
+ <NumberInput key="areaId" source="areaId" label="table.field.warehouseAreasItem.areaId" />,
+ <TextInput key="asnCode" source="asnCode" label="table.field.warehouseAreasItem.asnCode" />,
+ <TextInput key="areaName" source="areaName" label="table.field.warehouseAreasItem.areaName" />,
+ <NumberInput key="matnrId" source="matnrId" label="table.field.warehouseAreasItem.matnrId" />,
+ <TextInput key="matnrName" source="matnrName" label="table.field.warehouseAreasItem.matnrName" />,
+ <TextInput key="matnrCode" source="matnrCode" label="table.field.warehouseAreasItem.matnrCode" />,
+ <TextInput key="barcode" source="barcode" label="table.field.warehouseAreasItem.barcode" />,
+ <NumberInput key="anfme" source="anfme" label="table.field.warehouseAreasItem.anfme" />,
+ <TextInput key="batch" source="batch" label="table.field.warehouseAreasItem.batch" />,
+ <TextInput key="platOrderCode" source="platOrderCode" label="table.field.asnOrderItem.platOrderCode" />,
+ <TextInput key="platWorkCode" source="platWorkCode" label="table.field.asnOrderItem.platWorkCode" />,
+ <TextInput key="projectCode" source="projectCode" label="table.field.asnOrderItem.projectCode" />,
+ <TextInput key="unit" source="unit" label="table.field.warehouseAreasItem.unit" />,
+ <TextInput key="stockUnit" source="stockUnit" label="table.field.warehouseAreasItem.stockUnit" />,
+ <TextInput key="brand" source="brand" label="table.field.warehouseAreasItem.brand" />,
+ <ReferenceInput
+ key="shipperId"
+ source="shipperId"
+ label="table.field.warehouseAreasItem.shipperId"
+ reference="companys"
+ >
+ <AutocompleteInput
+ label="table.field.warehouseAreasItem.shipperId"
+ optionText="name"
+ filterToQuery={val => ({ name: val })}
+ />
+ </ReferenceInput>,
+ <TextInput key="splrId" source="splrId" label="table.field.warehouseAreasItem.splrId" />,
+ <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"
+ label="common.field.status"
+ source="status"
+ choices={[
+ { id: "1", name: "common.enums.statusTrue" },
+ { id: "0", name: "common.enums.statusFalse" },
+ ]}
+ resettable
+ />,
+]);
+
+const createColumnDef = ({ key, source, label, element, isExtendField = false }) => ({
+ key,
+ source,
+ label,
+ isExtendField,
+ element,
+});
+
+export const buildWarehouseAreasItemColumnDefs = (translate, dynamicFields = []) => {
+ const baseDefs = [
+ createColumnDef({
+ key: "id",
+ source: "id",
+ label: "ID",
+ element: <NumberField key="id" source="id" label="ID" />,
+ }),
+ createColumnDef({
+ key: "areaName",
+ source: "areaName",
+ label: "鏀惰揣鍖哄悕绉�",
+ element: <TextField key="areaName" source="areaName" label="鏀惰揣鍖哄悕绉�" />,
+ }),
+ createColumnDef({
+ key: "asnCode",
+ source: "asnCode",
+ label: resolveLabel(translate, "table.field.warehouseAreasItem.asnCode"),
+ element: (
+ <TextField
+ key="asnCode"
+ source="asnCode"
+ label={resolveLabel(translate, "table.field.warehouseAreasItem.asnCode")}
+ />
+ ),
+ }),
+ createColumnDef({
+ key: "platWorkCode",
+ source: "platWorkCode",
+ label: resolveLabel(translate, "table.field.asnOrderItem.platWorkCode"),
+ element: (
+ <TextField
+ key="platWorkCode"
+ source="platWorkCode"
+ label={resolveLabel(translate, "table.field.asnOrderItem.platWorkCode")}
+ />
+ ),
+ }),
+ createColumnDef({
+ key: "platItemId",
+ source: "platItemId",
+ label: resolveLabel(translate, "table.field.deliveryItem.platItemId"),
+ element: (
+ <TextField
+ key="platItemId"
+ source="platItemId"
+ label={resolveLabel(translate, "table.field.deliveryItem.platItemId")}
+ />
+ ),
+ }),
+ createColumnDef({
+ key: "matnrId",
+ source: "matnrId",
+ label: resolveLabel(translate, "table.field.warehouseAreasItem.matnrId"),
+ element: (
+ <NumberField
+ key="matnrId"
+ source="matnrId"
+ label={resolveLabel(translate, "table.field.warehouseAreasItem.matnrId")}
+ />
+ ),
+ }),
+ createColumnDef({
+ key: "matnrCode",
+ source: "matnrCode",
+ label: resolveLabel(translate, "table.field.warehouseAreasItem.matnrCode"),
+ element: (
+ <TextField
+ key="matnrCode"
+ source="matnrCode"
+ label={resolveLabel(translate, "table.field.warehouseAreasItem.matnrCode")}
+ />
+ ),
+ }),
+ createColumnDef({
+ key: "maktx",
+ source: "maktx",
+ label: resolveLabel(translate, "table.field.warehouseAreasItem.matnrName"),
+ element: (
+ <TextField
+ key="maktx"
+ source="maktx"
+ label={resolveLabel(translate, "table.field.warehouseAreasItem.matnrName")}
+ />
+ ),
+ }),
+ createColumnDef({
+ key: "splrBatch",
+ source: "splrBatch",
+ label: resolveLabel(translate, "table.field.warehouseAreasItem.splrBtch"),
+ element: (
+ <TextField
+ key="splrBatch"
+ source="splrBatch"
+ label={resolveLabel(translate, "table.field.warehouseAreasItem.splrBtch")}
+ />
+ ),
+ }),
+ createColumnDef({
+ key: "batch",
+ source: "batch",
+ label: resolveLabel(translate, "table.field.warehouseAreasItem.batch"),
+ element: (
+ <TextField
+ key="batch"
+ source="batch"
+ label={resolveLabel(translate, "table.field.warehouseAreasItem.batch")}
+ />
+ ),
+ }),
+ createColumnDef({
+ key: "trackCode",
+ source: "trackCode",
+ label: resolveLabel(translate, "table.field.warehouseAreasItem.barcode"),
+ element: (
+ <TextField
+ key="trackCode"
+ source="trackCode"
+ label={resolveLabel(translate, "table.field.warehouseAreasItem.barcode")}
+ />
+ ),
+ }),
+ createColumnDef({
+ key: "unit",
+ source: "unit",
+ label: resolveLabel(translate, "table.field.warehouseAreasItem.unit"),
+ element: (
+ <TextField
+ key="unit"
+ source="unit"
+ label={resolveLabel(translate, "table.field.warehouseAreasItem.unit")}
+ />
+ ),
+ }),
+ createColumnDef({
+ key: "anfme",
+ source: "anfme",
+ label: resolveLabel(translate, "table.field.warehouseAreasItem.anfme"),
+ element: (
+ <NumberField
+ key="anfme"
+ source="anfme"
+ label={resolveLabel(translate, "table.field.warehouseAreasItem.anfme")}
+ />
+ ),
+ }),
+ createColumnDef({
+ key: "workQty",
+ source: "workQty",
+ label: resolveLabel(translate, "table.field.warehouseAreasItem.workQty"),
+ element: (
+ <NumberField
+ key="workQty"
+ source="workQty"
+ label={resolveLabel(translate, "table.field.warehouseAreasItem.workQty")}
+ />
+ ),
+ }),
+ createColumnDef({
+ key: "ableQty",
+ source: "ableQty",
+ label: resolveLabel(translate, "table.field.warehouseAreasItem.qty"),
+ element: (
+ <NumberField
+ key="ableQty"
+ source="ableQty"
+ label={resolveLabel(translate, "table.field.warehouseAreasItem.qty")}
+ />
+ ),
+ }),
+ createColumnDef({
+ key: "platOrderCode",
+ source: "platOrderCode",
+ label: resolveLabel(translate, "table.field.asnOrderItem.platOrderCode"),
+ element: (
+ <TextField
+ key="platOrderCode"
+ source="platOrderCode"
+ label={resolveLabel(translate, "table.field.asnOrderItem.platOrderCode")}
+ />
+ ),
+ }),
+ createColumnDef({
+ key: "projectCode",
+ source: "projectCode",
+ label: resolveLabel(translate, "table.field.asnOrderItem.projectCode"),
+ element: (
+ <TextField
+ key="projectCode"
+ source="projectCode"
+ label={resolveLabel(translate, "table.field.asnOrderItem.projectCode")}
+ />
+ ),
+ }),
+ createColumnDef({
+ key: "brand",
+ source: "brand",
+ label: resolveLabel(translate, "table.field.warehouseAreasItem.brand"),
+ element: (
+ <TextField
+ key="brand"
+ source="brand"
+ label={resolveLabel(translate, "table.field.warehouseAreasItem.brand")}
+ />
+ ),
+ }),
+ createColumnDef({
+ key: "shipperId",
+ source: "shipperId",
+ label: resolveLabel(translate, "table.field.warehouseAreasItem.shipperId"),
+ element: (
+ <TextField
+ key="shipperId"
+ source="shipperId"
+ label={resolveLabel(translate, "table.field.warehouseAreasItem.shipperId")}
+ />
+ ),
+ }),
+ createColumnDef({
+ key: "splrId",
+ source: "splrId",
+ label: resolveLabel(translate, "table.field.warehouseAreasItem.splrId"),
+ element: (
+ <TextField
+ key="splrId"
+ source="splrId"
+ label={resolveLabel(translate, "table.field.warehouseAreasItem.splrId")}
+ />
+ ),
+ }),
+ createColumnDef({
+ key: "isptResult$",
+ source: "isptResult$",
+ label: resolveLabel(translate, "table.field.warehouseAreasItem.isptResult"),
+ element: (
+ <TextField
+ key="isptResult$"
+ source="isptResult$"
+ label={resolveLabel(translate, "table.field.warehouseAreasItem.isptResult")}
+ sortable={false}
+ />
+ ),
+ }),
+ createColumnDef({
+ key: "weight",
+ source: "weight",
+ label: resolveLabel(translate, "table.field.warehouseAreasItem.weight"),
+ element: (
+ <NumberField
+ key="weight"
+ source="weight"
+ label={resolveLabel(translate, "table.field.warehouseAreasItem.weight")}
+ />
+ ),
+ }),
+ createColumnDef({
+ key: "prodTime",
+ source: "prodTime",
+ label: resolveLabel(translate, "table.field.warehouseAreasItem.prodTime"),
+ element: (
+ <TextField
+ key="prodTime"
+ source="prodTime"
+ label={resolveLabel(translate, "table.field.warehouseAreasItem.prodTime")}
+ />
+ ),
+ }),
+ ];
+
+ const extendDefs = dynamicFields.map(field => {
+ const source = `extendFields.[${field.fields}]`;
+ return createColumnDef({
+ key: source,
+ source,
+ label: field.fieldsAlise || field.fields,
+ isExtendField: true,
+ element: <TextField key={source} source={source} label={field.fieldsAlise || field.fields} />,
+ });
+ });
+
+ const tailDefs = [
+ createColumnDef({
+ key: "updateBy$",
+ source: "updateBy$",
+ label: resolveLabel(translate, "common.field.updateBy"),
+ element: (
+ <TextField
+ key="updateBy$"
+ source="updateBy$"
+ label={resolveLabel(translate, "common.field.updateBy")}
+ />
+ ),
+ }),
+ createColumnDef({
+ key: "updateTime",
+ source: "updateTime",
+ label: resolveLabel(translate, "common.field.updateTime"),
+ element: (
+ <DateField
+ key="updateTime"
+ source="updateTime"
+ label={resolveLabel(translate, "common.field.updateTime")}
+ showTime
+ />
+ ),
+ }),
+ createColumnDef({
+ key: "createBy$",
+ source: "createBy$",
+ label: resolveLabel(translate, "common.field.createBy"),
+ element: (
+ <TextField
+ key="createBy$"
+ source="createBy$"
+ label={resolveLabel(translate, "common.field.createBy")}
+ />
+ ),
+ }),
+ createColumnDef({
+ key: "createTime",
+ source: "createTime",
+ label: resolveLabel(translate, "common.field.createTime"),
+ element: (
+ <DateField
+ key="createTime"
+ source="createTime"
+ label={resolveLabel(translate, "common.field.createTime")}
+ showTime
+ />
+ ),
+ }),
+ createColumnDef({
+ key: "statusBool",
+ source: "statusBool",
+ label: resolveLabel(translate, "common.field.status"),
+ element: (
+ <BooleanField
+ key="statusBool"
+ source="statusBool"
+ label={resolveLabel(translate, "common.field.status")}
+ sortable={false}
+ />
+ ),
+ }),
+ createColumnDef({
+ key: "memo",
+ source: "memo",
+ label: resolveLabel(translate, "common.field.memo"),
+ element: (
+ <TextField
+ key="memo"
+ source="memo"
+ label={resolveLabel(translate, "common.field.memo")}
+ sortable={false}
+ />
+ ),
+ }),
+ ];
+
+ return [...baseDefs, ...extendDefs, ...tailDefs];
+};
+
+export const buildWarehouseAreasItemDynamicFilters = dynamicFields =>
+ dynamicFields.map(field => (
+ <TextInput
+ key={`filter-${field.fields}`}
+ source={field.fields}
+ label={field.fieldsAlise || field.fields}
+ />
+ ));
+
+export const buildWarehouseAreasItemReportConfig = (translate, dynamicFields = []) => ({
+ resource: "warehouseAreasItem",
+ preferenceKey: WAREHOUSE_AREAS_ITEM_PREFERENCE_KEY,
+ title: "鏀惰揣搴撳瓨鎶ヨ〃",
+ columns: buildWarehouseAreasItemColumnDefs(translate, dynamicFields),
+ omit: WAREHOUSE_AREAS_ITEM_OMIT,
+ buildReportMeta: buildWarehouseAreasItemReportMeta,
+ enablePrint: true,
+ defaultOrientation: "landscape",
+ exportFileName: "warehouseAreasItem.xlsx",
+});
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/common/domain/report/ReportColumnMeta.java b/rsf-server/src/main/java/com/vincent/rsf/server/common/domain/report/ReportColumnMeta.java
new file mode 100644
index 0000000..3f12565
--- /dev/null
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/common/domain/report/ReportColumnMeta.java
@@ -0,0 +1,40 @@
+package com.vincent.rsf.server.common.domain.report;
+
+public class ReportColumnMeta {
+ private String key;
+ private String source;
+ private String label;
+ private Boolean isExtendField;
+
+ public String getKey() {
+ return key;
+ }
+
+ public void setKey(String key) {
+ this.key = key;
+ }
+
+ public String getSource() {
+ return source;
+ }
+
+ public void setSource(String source) {
+ this.source = source;
+ }
+
+ public String getLabel() {
+ return label;
+ }
+
+ public void setLabel(String label) {
+ this.label = label;
+ }
+
+ public Boolean getIsExtendField() {
+ return isExtendField;
+ }
+
+ public void setIsExtendField(Boolean extendField) {
+ isExtendField = extendField;
+ }
+}
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/common/domain/report/ReportMeta.java b/rsf-server/src/main/java/com/vincent/rsf/server/common/domain/report/ReportMeta.java
new file mode 100644
index 0000000..c88b389
--- /dev/null
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/common/domain/report/ReportMeta.java
@@ -0,0 +1,49 @@
+package com.vincent.rsf.server.common.domain.report;
+
+public class ReportMeta {
+ private String title;
+ private String companyName;
+ private String printedBy;
+ private String reportDate;
+ private String reportDateValue;
+
+ public String getTitle() {
+ return title;
+ }
+
+ public void setTitle(String title) {
+ this.title = title;
+ }
+
+ public String getCompanyName() {
+ return companyName;
+ }
+
+ public void setCompanyName(String companyName) {
+ this.companyName = companyName;
+ }
+
+ public String getPrintedBy() {
+ return printedBy;
+ }
+
+ public void setPrintedBy(String printedBy) {
+ this.printedBy = printedBy;
+ }
+
+ public String getReportDate() {
+ return reportDate;
+ }
+
+ public void setReportDate(String reportDate) {
+ this.reportDate = reportDate;
+ }
+
+ public String getReportDateValue() {
+ return reportDateValue;
+ }
+
+ public void setReportDateValue(String reportDateValue) {
+ this.reportDateValue = reportDateValue;
+ }
+}
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/common/domain/report/ReportQueryRequest.java b/rsf-server/src/main/java/com/vincent/rsf/server/common/domain/report/ReportQueryRequest.java
new file mode 100644
index 0000000..6bbf09a
--- /dev/null
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/common/domain/report/ReportQueryRequest.java
@@ -0,0 +1,165 @@
+package com.vincent.rsf.server.common.domain.report;
+
+import java.util.*;
+
+public class ReportQueryRequest {
+ private Long current = 1L;
+ private Long pageSize = 12L;
+ private String orderBy;
+ private List<Long> ids = new ArrayList<>();
+ private List<ReportColumnMeta> columns = new ArrayList<>();
+ private ReportMeta reportMeta;
+ private Map<String, Object> rawParams = new HashMap<>();
+
+ public static ReportQueryRequest fromMap(Map<String, Object> map) {
+ ReportQueryRequest request = new ReportQueryRequest();
+ Map<String, Object> safeMap = map == null ? new HashMap<>() : new HashMap<>(map);
+ request.setRawParams(safeMap);
+ request.setCurrent(parseLong(safeMap.get("current"), 1L));
+ request.setPageSize(parseLong(safeMap.get("pageSize"), 12L));
+ request.setOrderBy(Objects.isNull(safeMap.get("orderBy")) ? null : String.valueOf(safeMap.get("orderBy")));
+ request.setIds(parseIds(safeMap.get("ids")));
+ request.setColumns(parseColumns(safeMap.get("columns")));
+ request.setReportMeta(parseReportMeta(safeMap.get("reportMeta")));
+ return request;
+ }
+
+ public Map<String, Object> toPageParamMap(boolean includeFilters) {
+ Map<String, Object> queryMap = new HashMap<>(rawParams);
+ queryMap.remove("ids");
+ queryMap.remove("columns");
+ queryMap.remove("reportMeta");
+
+ if (!includeFilters) {
+ Object resolvedOrderBy = queryMap.get("orderBy");
+ queryMap.clear();
+ if (!Objects.isNull(resolvedOrderBy)) {
+ queryMap.put("orderBy", resolvedOrderBy);
+ }
+ }
+
+ return queryMap;
+ }
+
+ public Long getCurrent() {
+ return current;
+ }
+
+ public void setCurrent(Long current) {
+ this.current = current;
+ }
+
+ public Long getPageSize() {
+ return pageSize;
+ }
+
+ public void setPageSize(Long pageSize) {
+ this.pageSize = pageSize;
+ }
+
+ public String getOrderBy() {
+ return orderBy;
+ }
+
+ public void setOrderBy(String orderBy) {
+ this.orderBy = orderBy;
+ }
+
+ public List<Long> getIds() {
+ return ids;
+ }
+
+ public void setIds(List<Long> ids) {
+ this.ids = ids;
+ }
+
+ public List<ReportColumnMeta> getColumns() {
+ return columns;
+ }
+
+ public void setColumns(List<ReportColumnMeta> columns) {
+ this.columns = columns;
+ }
+
+ public ReportMeta getReportMeta() {
+ return reportMeta;
+ }
+
+ public void setReportMeta(ReportMeta reportMeta) {
+ this.reportMeta = reportMeta;
+ }
+
+ public Map<String, Object> getRawParams() {
+ return rawParams;
+ }
+
+ public void setRawParams(Map<String, Object> rawParams) {
+ this.rawParams = rawParams;
+ }
+
+ private static long parseLong(Object value, long defaultValue) {
+ if (Objects.isNull(value)) {
+ return defaultValue;
+ }
+ try {
+ return Long.parseLong(String.valueOf(value));
+ } catch (NumberFormatException ignore) {
+ return defaultValue;
+ }
+ }
+
+ private static List<Long> parseIds(Object idsObj) {
+ List<Long> ids = new ArrayList<>();
+ if (!(idsObj instanceof Collection<?> collection)) {
+ return ids;
+ }
+ for (Object value : collection) {
+ if (Objects.isNull(value)) {
+ continue;
+ }
+ ids.add(Long.parseLong(String.valueOf(value)));
+ }
+ return ids;
+ }
+
+ private static List<ReportColumnMeta> parseColumns(Object columnsObj) {
+ List<ReportColumnMeta> columns = new ArrayList<>();
+ if (!(columnsObj instanceof Collection<?> collection)) {
+ return columns;
+ }
+ for (Object item : collection) {
+ if (!(item instanceof Map<?, ?> columnMap)) {
+ continue;
+ }
+ Object source = columnMap.get("source");
+ if (Objects.isNull(source) || String.valueOf(source).trim().isEmpty()) {
+ continue;
+ }
+ ReportColumnMeta columnMeta = new ReportColumnMeta();
+ columnMeta.setKey(Objects.isNull(columnMap.get("key")) ? null : String.valueOf(columnMap.get("key")));
+ columnMeta.setSource(String.valueOf(source));
+ columnMeta.setLabel(Objects.isNull(columnMap.get("label")) ? String.valueOf(source) : String.valueOf(columnMap.get("label")));
+ columnMeta.setIsExtendField(Boolean.parseBoolean(String.valueOf(columnMap.get("isExtendField"))));
+ columns.add(columnMeta);
+ }
+ return columns;
+ }
+
+ private static ReportMeta parseReportMeta(Object reportMetaObj) {
+ if (!(reportMetaObj instanceof Map<?, ?> reportMetaMap)) {
+ return null;
+ }
+ ReportMeta reportMeta = new ReportMeta();
+ reportMeta.setTitle(getMapString(reportMetaMap, "title"));
+ reportMeta.setCompanyName(getMapString(reportMetaMap, "companyName"));
+ reportMeta.setPrintedBy(getMapString(reportMetaMap, "printedBy"));
+ reportMeta.setReportDate(getMapString(reportMetaMap, "reportDate"));
+ reportMeta.setReportDateValue(getMapString(reportMetaMap, "reportDateValue"));
+ return reportMeta;
+ }
+
+ private static String getMapString(Map<?, ?> map, String key) {
+ Object value = map.get(key);
+ return Objects.isNull(value) ? null : String.valueOf(value);
+ }
+}
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/common/domain/report/ReportQueryResponse.java b/rsf-server/src/main/java/com/vincent/rsf/server/common/domain/report/ReportQueryResponse.java
new file mode 100644
index 0000000..62f7a55
--- /dev/null
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/common/domain/report/ReportQueryResponse.java
@@ -0,0 +1,52 @@
+package com.vincent.rsf.server.common.domain.report;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class ReportQueryResponse<T> {
+ private List<T> records = new ArrayList<>();
+ private Long total = 0L;
+ private Long current = 1L;
+ private Long pageSize = 0L;
+ private Long pages = 0L;
+
+ public List<T> getRecords() {
+ return records;
+ }
+
+ public void setRecords(List<T> records) {
+ this.records = records;
+ }
+
+ public Long getTotal() {
+ return total;
+ }
+
+ public void setTotal(Long total) {
+ this.total = total;
+ }
+
+ public Long getCurrent() {
+ return current;
+ }
+
+ public void setCurrent(Long current) {
+ this.current = current;
+ }
+
+ public Long getPageSize() {
+ return pageSize;
+ }
+
+ public void setPageSize(Long pageSize) {
+ this.pageSize = pageSize;
+ }
+
+ public Long getPages() {
+ return pages;
+ }
+
+ public void setPages(Long pages) {
+ this.pages = pages;
+ }
+}
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/common/support/report/ListReportSupport.java b/rsf-server/src/main/java/com/vincent/rsf/server/common/support/report/ListReportSupport.java
new file mode 100644
index 0000000..93aa553
--- /dev/null
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/common/support/report/ListReportSupport.java
@@ -0,0 +1,107 @@
+package com.vincent.rsf.server.common.support.report;
+
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.vincent.rsf.framework.common.Cools;
+import com.vincent.rsf.server.common.domain.report.ReportColumnMeta;
+import com.vincent.rsf.server.common.domain.report.ReportMeta;
+import com.vincent.rsf.server.common.domain.report.ReportQueryRequest;
+import com.vincent.rsf.server.common.domain.report.ReportQueryResponse;
+import com.vincent.rsf.server.common.utils.ExcelUtil;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+public class ListReportSupport<T> {
+
+ public interface QueryWrapperBuilder<T> {
+ QueryWrapper<T> build(ReportQueryRequest request);
+ }
+
+ public interface RecordLoader<T> {
+ List<T> list(QueryWrapper<T> queryWrapper);
+
+ IPage<T> page(Page<T> page, QueryWrapper<T> queryWrapper);
+
+ default void afterLoad(List<T> records) {
+ }
+ }
+
+ private final QueryWrapperBuilder<T> queryWrapperBuilder;
+ private final RecordLoader<T> recordLoader;
+
+ public ListReportSupport(QueryWrapperBuilder<T> queryWrapperBuilder, RecordLoader<T> recordLoader) {
+ this.queryWrapperBuilder = queryWrapperBuilder;
+ this.recordLoader = recordLoader;
+ }
+
+ public List<T> queryRecords(ReportQueryRequest request) {
+ List<T> records = recordLoader.list(queryWrapperBuilder.build(request));
+ recordLoader.afterLoad(records);
+ return records;
+ }
+
+ public ReportQueryResponse<T> queryPage(ReportQueryRequest request) {
+ Page<T> page = new Page<>(
+ Math.max(request.getCurrent(), 1L),
+ Math.max(request.getPageSize(), 1L)
+ );
+ IPage<T> result = recordLoader.page(page, queryWrapperBuilder.build(request));
+ recordLoader.afterLoad(result.getRecords());
+
+ ReportQueryResponse<T> response = new ReportQueryResponse<>();
+ response.setRecords(result.getRecords());
+ response.setTotal(result.getTotal());
+ response.setCurrent(result.getCurrent());
+ response.setPageSize(result.getSize());
+ response.setPages(result.getPages());
+ return response;
+ }
+
+ public static List<ExcelUtil.ColumnMeta> toExcelColumns(List<ReportColumnMeta> columns) {
+ if (columns == null || columns.isEmpty()) {
+ return Collections.emptyList();
+ }
+
+ List<ExcelUtil.ColumnMeta> excelColumns = new ArrayList<>();
+ for (ReportColumnMeta column : columns) {
+ if (column == null || Cools.isEmpty(column.getSource())) {
+ continue;
+ }
+ excelColumns.add(new ExcelUtil.ColumnMeta()
+ .setKey(column.getKey())
+ .setSource(column.getSource())
+ .setLabel(column.getLabel())
+ .setExtendField(Boolean.TRUE.equals(column.getIsExtendField())));
+ }
+ return excelColumns;
+ }
+
+ public static ExcelUtil.ReportMeta toExcelReportMeta(ReportMeta reportMeta) {
+ if (reportMeta == null) {
+ return null;
+ }
+ return new ExcelUtil.ReportMeta()
+ .setTitle(reportMeta.getTitle())
+ .setCompanyName(reportMeta.getCompanyName())
+ .setPrintedBy(reportMeta.getPrintedBy())
+ .setReportDate(reportMeta.getReportDate())
+ .setReportDateValue(reportMeta.getReportDateValue());
+ }
+
+ public static void applyOrderBy(QueryWrapper<?> queryWrapper, String orderBy) {
+ if (Cools.isEmpty(orderBy)) {
+ return;
+ }
+ for (String item : orderBy.split(",")) {
+ String[] temp = item.trim().split(" ");
+ if (temp.length == 0 || Cools.isEmpty(temp[0])) {
+ continue;
+ }
+ boolean asc = temp.length == 1 || !"desc".equalsIgnoreCase(temp[temp.length - 1]);
+ queryWrapper.orderBy(true, asc, temp[0]);
+ }
+ }
+}
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..1c12cc5 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,10 +15,12 @@
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;
import java.lang.reflect.Field;
+import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.net.URLEncoder;
import java.text.SimpleDateFormat;
@@ -131,6 +133,262 @@
return workbook;
}
+ public static Workbook create(List<?> list, List<ColumnMeta> columns) {
+ return create(list, columns, null);
+ }
+
+ public static Workbook create(List<?> list, List<ColumnMeta> columns, ReportMeta reportMeta) {
+ XSSFWorkbook workbook = new XSSFWorkbook();
+ Sheet sheet = workbook.createSheet("export");
+ List<ColumnMeta> safeColumns = columns == null ? Collections.emptyList() : columns;
+ int sheetColumnCount = safeColumns.size() + 1;
+ configureA4PrintLayout(sheet);
+
+ CellStyle titleStyle = createTitleStyle(workbook);
+ CellStyle metaLabelStyle = createMetaLabelStyle(workbook);
+ CellStyle metaValueStyle = createMetaValueStyle(workbook);
+ CellStyle headerStyle = createHeaderStyle(workbook);
+ CellStyle bodyStyle = createBodyStyle(workbook);
+ CellStyle serialStyle = createCenteredBodyStyle(workbook);
+
+ int rowIndex = 0;
+ if (reportMeta != null) {
+ Row titleRow = sheet.createRow(rowIndex++);
+ titleRow.setHeightInPoints(28);
+ Cell titleCell = titleRow.createCell(0);
+ titleCell.setCellValue(StringUtils.defaultIfBlank(reportMeta.getTitle(), "鎶ヨ〃"));
+ titleCell.setCellStyle(titleStyle);
+ sheet.addMergedRegion(new CellRangeAddress(0, 0, 0, Math.max(0, sheetColumnCount - 1)));
+
+ Row metaRow = sheet.createRow(rowIndex++);
+ int metaCol = 0;
+ metaCol = writeMetaPair(metaRow, metaCol, "鎶ヨ〃鏃ユ湡", reportMeta.getReportDate(), metaLabelStyle, metaValueStyle);
+ writeMetaPair(metaRow, metaCol, "鎵撳嵃浜�", reportMeta.getPrintedBy(), metaLabelStyle, metaValueStyle);
+
+ rowIndex++;
+ }
+
+ int headerRowIndex = rowIndex;
+ Row header = sheet.createRow(rowIndex++);
+ Cell serialHeaderCell = header.createCell(0);
+ serialHeaderCell.setCellValue("搴忓彿");
+ serialHeaderCell.setCellStyle(headerStyle);
+
+ for (int i = 0; i < safeColumns.size(); i++) {
+ ColumnMeta column = safeColumns.get(i);
+ Cell headerCell = header.createCell(i + 1);
+ headerCell.setCellValue(
+ StringUtils.isBlank(column.getLabel()) ? column.getSource() : column.getLabel()
+ );
+ headerCell.setCellStyle(headerStyle);
+ }
+
+ if (list != null) {
+ int serialNo = 1;
+ for (Object rowObj : list) {
+ Row row = sheet.createRow(rowIndex++);
+ Cell serialCell = row.createCell(0);
+ serialCell.setCellValue(String.format("%03d", serialNo++));
+ serialCell.setCellStyle(serialStyle);
+ for (int i = 0; i < safeColumns.size(); i++) {
+ Object value = getColumnValue(rowObj, safeColumns.get(i).getSource());
+ Cell cell = row.createCell(i + 1);
+ cell.setCellStyle(bodyStyle);
+ if (value != null) {
+ if (value instanceof Date) {
+ SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+ cell.setCellValue(sdf.format((Date) value));
+ } else {
+ cell.setCellValue(value.toString());
+ }
+ }
+ }
+ }
+ }
+
+ for (int i = 0; i <= safeColumns.size(); i++) {
+ sheet.autoSizeColumn(i);
+ sheet.setColumnWidth(i, Math.min(sheet.getColumnWidth(i) + 1024, 12000));
+ }
+
+ sheet.setRepeatingRows(CellRangeAddress.valueOf((headerRowIndex + 1) + ":" + (headerRowIndex + 1)));
+
+ return workbook;
+ }
+
+ private static void configureA4PrintLayout(Sheet sheet) {
+ sheet.setAutobreaks(true);
+ sheet.setFitToPage(true);
+ sheet.setHorizontallyCenter(true);
+ sheet.setDisplayGridlines(false);
+
+ PrintSetup printSetup = sheet.getPrintSetup();
+ printSetup.setPaperSize(PrintSetup.A4_PAPERSIZE);
+ printSetup.setLandscape(true);
+ printSetup.setFitWidth((short) 1);
+ printSetup.setFitHeight((short) 0);
+
+ sheet.setMargin(Sheet.LeftMargin, 0.3);
+ sheet.setMargin(Sheet.RightMargin, 0.3);
+ sheet.setMargin(Sheet.TopMargin, 0.4);
+ sheet.setMargin(Sheet.BottomMargin, 0.4);
+ }
+
+ private static int writeMetaPair(Row row, int startCol, String label, String value, CellStyle labelStyle, CellStyle valueStyle) {
+ Cell labelCell = row.createCell(startCol);
+ labelCell.setCellValue(label + "锛�");
+ labelCell.setCellStyle(labelStyle);
+
+ Cell valueCell = row.createCell(startCol + 1);
+ valueCell.setCellValue(StringUtils.defaultString(value));
+ valueCell.setCellStyle(valueStyle);
+ return startCol + 2;
+ }
+
+ private static CellStyle createTitleStyle(Workbook workbook) {
+ CellStyle style = workbook.createCellStyle();
+ style.setAlignment(HorizontalAlignment.CENTER);
+ style.setVerticalAlignment(VerticalAlignment.CENTER);
+ style.setBorderBottom(BorderStyle.THICK);
+ Font font = workbook.createFont();
+ font.setBold(true);
+ font.setFontHeightInPoints((short) 16);
+ style.setFont(font);
+ return style;
+ }
+
+ private static CellStyle createMetaLabelStyle(Workbook workbook) {
+ CellStyle style = workbook.createCellStyle();
+ style.setAlignment(HorizontalAlignment.LEFT);
+ style.setVerticalAlignment(VerticalAlignment.CENTER);
+ style.setBorderBottom(BorderStyle.THIN);
+ Font font = workbook.createFont();
+ font.setBold(true);
+ style.setFont(font);
+ return style;
+ }
+
+ private static CellStyle createMetaValueStyle(Workbook workbook) {
+ CellStyle style = workbook.createCellStyle();
+ style.setAlignment(HorizontalAlignment.LEFT);
+ style.setVerticalAlignment(VerticalAlignment.CENTER);
+ style.setBorderBottom(BorderStyle.THIN);
+ return style;
+ }
+
+ private static CellStyle createHeaderStyle(Workbook workbook) {
+ CellStyle style = workbook.createCellStyle();
+ style.setAlignment(HorizontalAlignment.CENTER);
+ style.setVerticalAlignment(VerticalAlignment.CENTER);
+ style.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex());
+ style.setFillPattern(FillPatternType.SOLID_FOREGROUND);
+ Font font = workbook.createFont();
+ font.setBold(true);
+ style.setFont(font);
+ return style;
+ }
+
+ private static CellStyle createBodyStyle(Workbook workbook) {
+ CellStyle style = workbook.createCellStyle();
+ style.setAlignment(HorizontalAlignment.LEFT);
+ style.setVerticalAlignment(VerticalAlignment.CENTER);
+ return style;
+ }
+
+ private static CellStyle createCenteredBodyStyle(Workbook workbook) {
+ CellStyle style = workbook.createCellStyle();
+ style.setAlignment(HorizontalAlignment.CENTER);
+ style.setVerticalAlignment(VerticalAlignment.CENTER);
+ return style;
+ }
+
+ private static Object getColumnValue(Object rowObj, String source) {
+ if (rowObj == null || StringUtils.isBlank(source)) {
+ return null;
+ }
+
+ if (rowObj instanceof Map) {
+ return getValueFromMap((Map<?, ?>) rowObj, source);
+ }
+
+ String extendFieldKey = extractExtendFieldKey(source);
+ if (extendFieldKey != null) {
+ Object extendFields = getBeanValue(rowObj, "extendFields");
+ if (extendFields instanceof Map) {
+ return ((Map<?, ?>) extendFields).get(extendFieldKey);
+ }
+ return null;
+ }
+
+ return getBeanValue(rowObj, source);
+ }
+
+ private static Object getValueFromMap(Map<?, ?> rowObj, String source) {
+ String extendFieldKey = extractExtendFieldKey(source);
+ if (extendFieldKey != null) {
+ Object extendFields = rowObj.get("extendFields");
+ if (extendFields instanceof Map) {
+ return ((Map<?, ?>) extendFields).get(extendFieldKey);
+ }
+ return null;
+ }
+ return rowObj.get(source);
+ }
+
+ private static String extractExtendFieldKey(String source) {
+ if (source == null || !source.startsWith("extendFields.[")) {
+ return null;
+ }
+ int startIndex = source.indexOf('[');
+ int endIndex = source.indexOf(']');
+ if (startIndex < 0 || endIndex <= startIndex) {
+ return null;
+ }
+ return source.substring(startIndex + 1, endIndex);
+ }
+
+ private static Object getBeanValue(Object rowObj, String source) {
+ Object value = invokeGetter(rowObj, source);
+ if (value != null) {
+ return value;
+ }
+ Field field = findField(rowObj.getClass(), source);
+ if (field == null) {
+ return null;
+ }
+ try {
+ field.setAccessible(true);
+ return field.get(rowObj);
+ } catch (IllegalAccessException ignore) {
+ return null;
+ }
+ }
+
+ private static Object invokeGetter(Object target, String source) {
+ String suffix = Character.toUpperCase(source.charAt(0)) + source.substring(1);
+ String[] methodNames = new String[] { "get" + suffix, "is" + suffix };
+ for (String methodName : methodNames) {
+ try {
+ Method method = target.getClass().getMethod(methodName);
+ return method.invoke(target);
+ } catch (Exception ignore) {
+ }
+ }
+ return null;
+ }
+
+ private static Field findField(Class<?> clazz, String source) {
+ Class<?> current = clazz;
+ while (current != null && current != Object.class) {
+ try {
+ return current.getDeclaredField(source);
+ } catch (NoSuchFieldException ignore) {
+ current = current.getSuperclass();
+ }
+ }
+ return null;
+ }
+
/**
* 娣诲姞瀵煎叆excel閰嶇疆鍙傛暟
* 娉細榛樿閰嶇疆鍙弧瓒冲綋鍓嶉渶姹�
@@ -223,6 +481,102 @@
return false;
}
+ public static class ColumnMeta {
+ private String key;
+ private String source;
+ private String label;
+ private Boolean extendField;
+
+ public String getKey() {
+ return key;
+ }
+
+ public ColumnMeta setKey(String key) {
+ this.key = key;
+ return this;
+ }
+
+ public String getSource() {
+ return source;
+ }
+
+ public ColumnMeta setSource(String source) {
+ this.source = source;
+ return this;
+ }
+
+ public String getLabel() {
+ return label;
+ }
+
+ public ColumnMeta setLabel(String label) {
+ this.label = label;
+ return this;
+ }
+
+ public Boolean getExtendField() {
+ return extendField;
+ }
+
+ public ColumnMeta setExtendField(Boolean extendField) {
+ this.extendField = extendField;
+ return this;
+ }
+ }
+
+ public static class ReportMeta {
+ private String title;
+ private String companyName;
+ private String printedBy;
+ private String reportDate;
+ private String reportDateValue;
+
+ public String getTitle() {
+ return title;
+ }
+
+ public ReportMeta setTitle(String title) {
+ this.title = title;
+ return this;
+ }
+
+ public String getCompanyName() {
+ return companyName;
+ }
+
+ public ReportMeta setCompanyName(String companyName) {
+ this.companyName = companyName;
+ return this;
+ }
+
+ public String getReportDate() {
+ return reportDate;
+ }
+
+ public ReportMeta setReportDate(String reportDate) {
+ this.reportDate = reportDate;
+ return this;
+ }
+
+ public String getReportDateValue() {
+ return reportDateValue;
+ }
+
+ public ReportMeta setReportDateValue(String reportDateValue) {
+ this.reportDateValue = reportDateValue;
+ return this;
+ }
+
+ public String getPrintedBy() {
+ return printedBy;
+ }
+
+ public ReportMeta setPrintedBy(String printedBy) {
+ this.printedBy = printedBy;
+ return this;
+ }
+ }
+
}
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..94d2207 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
@@ -11,6 +11,9 @@
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.domain.report.ReportQueryRequest;
+import com.vincent.rsf.server.common.domain.report.ReportQueryResponse;
+import com.vincent.rsf.server.common.support.report.ListReportSupport;
import com.vincent.rsf.server.common.utils.FieldsUtils;
import com.vincent.rsf.server.manager.entity.WarehouseAreasItem;
import com.vincent.rsf.server.manager.service.WarehouseAreasItemService;
@@ -33,21 +36,10 @@
@PreAuthorize("hasAuthority('manager:warehouseAreasItem:list')")
@PostMapping("/warehouseAreasItem/page")
public R page(@RequestBody Map<String, Object> map) {
- BaseParam baseParam = buildParam(map, BaseParam.class);
- PageParam<WarehouseAreasItem, BaseParam> pageParam = new PageParam<>(baseParam, WarehouseAreasItem.class);
- QueryWrapper<WarehouseAreasItem> queryWrapper = pageParam.buildWrapper(true);
- /**鎷兼帴鎵╁睍瀛楁杩囨护*/
- FieldsUtils.setFieldsFilters(queryWrapper,pageParam, WarehouseAreasItem.class);
- /**鎷兼帴鎵╁睍瀛楁*/
+ PageParam<WarehouseAreasItem, BaseParam> pageParam = buildPageParam(map, true);
+ QueryWrapper<WarehouseAreasItem> queryWrapper = buildFilterQueryWrapper(pageParam);
PageParam<WarehouseAreasItem, BaseParam> page = warehouseAreasItemService.page(pageParam, queryWrapper);
- List<WarehouseAreasItem> records = page.getRecords();
- for (WarehouseAreasItem record : records) {
- if (!Objects.isNull(record.getFieldsIndex())) {
- Map<String, String> fields = FieldsUtils.getFields(record.getFieldsIndex());
- record.setExtendFields(fields);
- }
- }
- page.setRecords(records);
+ warehouseAreasItemService.fillExtendFields(page.getRecords());
return R.ok().add(page);
}
@@ -55,19 +47,10 @@
@PreAuthorize("hasAuthority('manager:warehouseAreasItem:list')")
@PostMapping("/warehouseAreasItem/ispts/page")
public R getIsptPage(@RequestBody Map<String, Object> map) {
- BaseParam baseParam = buildParam(map, BaseParam.class);
- PageParam<WarehouseAreasItem, BaseParam> pageParam = new PageParam<>(baseParam, WarehouseAreasItem.class);
+ PageParam<WarehouseAreasItem, BaseParam> pageParam = buildPageParam(map, true);
QueryWrapper<WarehouseAreasItem> queryWrapper = pageParam.buildWrapper(true);
- /**鎷兼帴鎵╁睍瀛楁*/
IPage<WarehouseAreasItem> page = warehouseAreasItemService.pageByItemId(pageParam, queryWrapper);
- List<WarehouseAreasItem> records = page.getRecords();
- for (WarehouseAreasItem record : records) {
- if (!Objects.isNull(record.getFieldsIndex())) {
- Map<String, String> fields = FieldsUtils.getFields(record.getFieldsIndex());
- record.setExtendFields(fields);
- }
- }
- page.setRecords(records);
+ warehouseAreasItemService.fillExtendFields(page.getRecords());
return R.ok().add(page);
}
@@ -141,7 +124,69 @@
@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);
+ ReportQueryRequest request = ReportQueryRequest.fromMap(map);
+ List<WarehouseAreasItem> records = createListReportSupport().queryRecords(request);
+ List<ExcelUtil.ColumnMeta> columns = ListReportSupport.toExcelColumns(request.getColumns());
+ ExcelUtil.ReportMeta reportMeta = ListReportSupport.toExcelReportMeta(request.getReportMeta());
+ if (columns.isEmpty()) {
+ ExcelUtil.build(ExcelUtil.create(records, WarehouseAreasItem.class), response);
+ return;
+ }
+ ExcelUtil.build(ExcelUtil.create(records, columns, reportMeta), response);
+ }
+
+ @PreAuthorize("hasAuthority('manager:warehouseAreasItem:list')")
+ @PostMapping("/warehouseAreasItem/print/query")
+ public R printQuery(@RequestBody Map<String, Object> map) {
+ ReportQueryResponse<WarehouseAreasItem> result = createListReportSupport()
+ .queryPage(ReportQueryRequest.fromMap(map));
+ return R.ok().add(result);
+ }
+
+ private PageParam<WarehouseAreasItem, BaseParam> buildPageParam(Map<String, Object> map, boolean includeFilters) {
+ return buildPageParam(ReportQueryRequest.fromMap(map), includeFilters);
+ }
+
+ private PageParam<WarehouseAreasItem, BaseParam> buildPageParam(ReportQueryRequest request, boolean includeFilters) {
+ BaseParam baseParam = buildParam(request.toPageParamMap(includeFilters), BaseParam.class);
+ return new PageParam<>(baseParam, WarehouseAreasItem.class);
+ }
+
+ private QueryWrapper<WarehouseAreasItem> buildFilterQueryWrapper(PageParam<WarehouseAreasItem, BaseParam> pageParam) {
+ QueryWrapper<WarehouseAreasItem> queryWrapper = pageParam.buildWrapper(true);
+ FieldsUtils.setFieldsFilters(queryWrapper, pageParam, WarehouseAreasItem.class);
+ return queryWrapper;
+ }
+
+ private QueryWrapper<WarehouseAreasItem> buildOutputQueryWrapper(ReportQueryRequest request) {
+ List<Long> ids = request.getIds();
+ PageParam<WarehouseAreasItem, BaseParam> pageParam = buildPageParam(request, ids.isEmpty());
+ QueryWrapper<WarehouseAreasItem> queryWrapper = ids.isEmpty()
+ ? buildFilterQueryWrapper(pageParam)
+ : new QueryWrapper<>();
+
+ if (!ids.isEmpty()) {
+ queryWrapper.in("id", ids);
+ }
+ ListReportSupport.applyOrderBy(queryWrapper, pageParam.getWhere().getOrderBy());
+ return queryWrapper;
+ }
+
+ private ListReportSupport<WarehouseAreasItem> createListReportSupport() {
+ return new ListReportSupport<>(
+ this::buildOutputQueryWrapper,
+ new ListReportSupport.RecordLoader<>() {
+ @Override
+ public List<WarehouseAreasItem> list(QueryWrapper<WarehouseAreasItem> queryWrapper) {
+ return warehouseAreasItemService.listForOutput(queryWrapper);
+ }
+
+ @Override
+ public IPage<WarehouseAreasItem> page(Page<WarehouseAreasItem> page, QueryWrapper<WarehouseAreasItem> queryWrapper) {
+ return warehouseAreasItemService.pageForOutput(page, queryWrapper);
+ }
+ }
+ );
}
}
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/manager/service/WarehouseAreasItemService.java b/rsf-server/src/main/java/com/vincent/rsf/server/manager/service/WarehouseAreasItemService.java
index d2a4283..8d86f79 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/manager/service/WarehouseAreasItemService.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/manager/service/WarehouseAreasItemService.java
@@ -3,6 +3,7 @@
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Constants;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.IService;
import com.vincent.rsf.server.common.domain.BaseParam;
import com.vincent.rsf.server.common.domain.PageParam;
@@ -16,4 +17,10 @@
List<WarehouseAreasItem> getList();
IPage<WarehouseAreasItem> pageByItemId(PageParam<WarehouseAreasItem, BaseParam> pageParam, QueryWrapper<WarehouseAreasItem> queryWrapper);
+
+ List<WarehouseAreasItem> listForOutput(QueryWrapper<WarehouseAreasItem> queryWrapper);
+
+ IPage<WarehouseAreasItem> pageForOutput(Page<WarehouseAreasItem> page, QueryWrapper<WarehouseAreasItem> queryWrapper);
+
+ void fillExtendFields(List<WarehouseAreasItem> records);
}
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/manager/service/impl/WarehouseAreasItemServiceImpl.java b/rsf-server/src/main/java/com/vincent/rsf/server/manager/service/impl/WarehouseAreasItemServiceImpl.java
index 3f9a887..7e6c220 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/manager/service/impl/WarehouseAreasItemServiceImpl.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/manager/service/impl/WarehouseAreasItemServiceImpl.java
@@ -2,6 +2,7 @@
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.vincent.rsf.server.common.domain.BaseParam;
import com.vincent.rsf.server.common.domain.PageParam;
import com.vincent.rsf.server.common.utils.FieldsUtils;
@@ -20,12 +21,7 @@
@Override
public List<WarehouseAreasItem> getList() {
List<WarehouseAreasItem> areasItems = this.list();
- for (WarehouseAreasItem areasItem : areasItems) {
- if (Objects.isNull(areasItem.getFieldsIndex())) {
- continue;
- }
- areasItem.setExtendFields(FieldsUtils.getFields(areasItem.getFieldsIndex()));
- }
+ fillExtendFields(areasItems);
return areasItems;
}
@@ -34,4 +30,31 @@
IPage<WarehouseAreasItem> itemIPage = this.baseMapper.pageByItemId(pageParam, queryWrapper);
return itemIPage;
}
+
+ @Override
+ public List<WarehouseAreasItem> listForOutput(QueryWrapper<WarehouseAreasItem> queryWrapper) {
+ List<WarehouseAreasItem> records = this.list(queryWrapper);
+ fillExtendFields(records);
+ return records;
+ }
+
+ @Override
+ public IPage<WarehouseAreasItem> pageForOutput(Page<WarehouseAreasItem> page, QueryWrapper<WarehouseAreasItem> queryWrapper) {
+ IPage<WarehouseAreasItem> outputPage = this.page(page, queryWrapper);
+ fillExtendFields(outputPage.getRecords());
+ return outputPage;
+ }
+
+ @Override
+ public void fillExtendFields(List<WarehouseAreasItem> records) {
+ if (records == null || records.isEmpty()) {
+ return;
+ }
+ for (WarehouseAreasItem record : records) {
+ if (Objects.isNull(record.getFieldsIndex())) {
+ continue;
+ }
+ record.setExtendFields(FieldsUtils.getFields(record.getFieldsIndex()));
+ }
+ }
}
--
Gitblit v1.9.1