From 365b409cdb629396cd2cbb2635ee10309cd3cd96 Mon Sep 17 00:00:00 2001
From: wj <2932352213@qq.com>
Date: 星期三, 06 五月 2026 13:35:37 +0800
Subject: [PATCH] feat:添加批量放置货架功能,添加导出弹窗,可导出筛选的数据

---
 zy-acs-manager/src/main/resources/application.yml                                      |    6 
 zy-acs-flow/src/i18n/en.js                                                             |   18 +
 zy-acs-manager/src/main/java/com/zy/acs/manager/manager/controller/CodeController.java |   48 ++++
 zy-acs-flow/src/map/BatchShelfDialog.jsx                                               |  133 +++++++++++++
 zy-acs-flow/src/map/tool.js                                                            |    5 
 zy-acs-flow/src/i18n/zh.js                                                             |   17 +
 zy-acs-flow/src/map/MapPage.jsx                                                        |  103 ++++++++++
 zy-acs-flow/src/page/code/CodeList.jsx                                                 |  217 ++++++++++++--------
 8 files changed, 452 insertions(+), 95 deletions(-)

diff --git a/zy-acs-flow/src/i18n/en.js b/zy-acs-flow/src/i18n/en.js
index ca33718..f2ed3c7 100644
--- a/zy-acs-flow/src/i18n/en.js
+++ b/zy-acs-flow/src/i18n/en.js
@@ -566,7 +566,11 @@
                 endTime: "end time",
                 errTime: "err time",
                 errDesc: "error",
+                batchshelving: "Batch Shelving",
+
+
             },
+
             mission: {
                 backpack: 'Backpack',
                 code: 'Code',
@@ -663,6 +667,7 @@
                 code: 'Send Code',
             },
         },
+
         settings: {
             resetPwd: {
                 currPwd: 'Current Password',
@@ -895,6 +900,17 @@
                     },
                 },
             },
+            batch: {
+                            title: 'Batch Shelving Settings',
+                            startX: 'Start X (mm)',
+                            startY: 'Start Y (mm)',
+                            autoOffset: 'Auto Offset',
+                            rows: 'Rows',
+                            cols: 'Columns',
+                            gapX: 'Row Spacing (mm)',
+                            gapY: 'Column Spacing (mm)',
+                            angle: 'Shelf Angle (deg)',
+                         },
             devices: {
                 title: 'Icons',
                 shelf: 'SHELF',
@@ -912,6 +928,7 @@
                 monitor: 'Console',
                 save: 'Save Map',
                 clear: 'Clear Map',
+                batchshelving: "Batch Shelving",
                 adapt: 'ADAPT',
                 rotate: 'ROTATE',
                 flip: 'FLIP',
@@ -1074,6 +1091,7 @@
             },
         },
     }
+
 };
 
 export default customEnglishMessages;
diff --git a/zy-acs-flow/src/i18n/zh.js b/zy-acs-flow/src/i18n/zh.js
index 5342c7d..61b42a7 100644
--- a/zy-acs-flow/src/i18n/zh.js
+++ b/zy-acs-flow/src/i18n/zh.js
@@ -546,6 +546,7 @@
                 endTime: "缁撴潫鏃堕棿",
                 duration: "鎸佺画鏃堕暱",
                 state: "鐘舵��",
+
             },
             action: {
                 uuid: "缂栧彿",
@@ -566,6 +567,7 @@
                 endTime: "瀹屾垚鏃堕棿",
                 errTime: "寮傚父鏃堕棿",
                 errDesc: "寮傚父",
+                batchshelving: "鎵归噺涓婃灦",
             },
             mission: {
                 backpack: '鑳岀瘬',
@@ -663,6 +665,7 @@
                 code: '鑾峰彇楠岃瘉鐮�',
             },
         },
+
         settings: {
             resetPwd: {
                 currPwd: '褰撳墠瀵嗙爜',
@@ -895,6 +898,17 @@
                     },
                 },
             },
+             batch: {
+                        title: '璐ф灦闃靛垪璁剧疆',
+                        startX: '璧风偣 X (姣背)',
+                        startY: '璧风偣 Y (姣背)',
+                        autoOffset: '鑷姩鍋忕Щ',
+                        rows: '琛屾暟',
+                        cols: '鍒楁暟',
+                        gapX: '琛岄棿璺� (姣背)',
+                        gapY: '鍒楅棿璺� (姣背)',
+                        angle: '璐ф灦瑙掑害 (搴�)',
+                    },
             devices: {
                 title: '鍥炬爣搴�',
                 shelf: '璐ф灦',
@@ -912,6 +926,7 @@
                 monitor: '鏃ュ織鐩戞帶',
                 save: '淇濆瓨鍦板浘',
                 clear: '娓呯┖鍦板浘',
+                batchshelving: "鎵归噺涓婃灦",
                 adapt: '閫傞厤',
                 rotate: '鏃嬭浆',
                 flip: '缈昏浆',
@@ -931,6 +946,7 @@
                 addArea: '娣诲姞鍖哄煙',
                 cancelAddArea: '鍙栨秷娣诲姞',
                 areaList: '鍖哄煙鍒楄〃',
+
             },
             mode: {
                 observer: '瑙傚療妯″紡',
@@ -1074,6 +1090,7 @@
             },
         },
     }
+
 };
 
 export default customChineseMessages;
diff --git a/zy-acs-flow/src/map/BatchShelfDialog.jsx b/zy-acs-flow/src/map/BatchShelfDialog.jsx
new file mode 100644
index 0000000..2d30d5b
--- /dev/null
+++ b/zy-acs-flow/src/map/BatchShelfDialog.jsx
@@ -0,0 +1,133 @@
+import React, { useState, useEffect } from 'react';
+import { Dialog, DialogTitle, DialogContent, DialogActions, TextField, Button, Stack } from '@mui/material';
+import { useTranslate } from 'react-admin';
+
+const BatchShelfDialog = ({ open, onClose, onConfirm, mapContainer }) => {
+    const translate = useTranslate();
+    const [startX, setStartX] = useState(0);
+    const [startY, setStartY] = useState(0);
+    const [rows, setRows] = useState(20);
+    const [cols, setCols] = useState(15);
+    const [gapX, setGapX] = useState(1000);
+    const [gapY, setGapY] = useState(1000);
+    const [angle, setAngle] = useState(0);
+
+    // 鑷姩鍋忕Щ锛氭壘鍒板凡鏈夎揣鏋剁殑鏈�澶� X 鍜� Y锛屽苟鍔犱笂瀵瑰簲闂磋窛浣滀负鏂拌捣鐐�
+    const handleAutoOffset = () => {
+        if (!mapContainer) {
+            console.warn('鍦板浘瀹瑰櫒鏈紶鍏ワ紝鏃犳硶鑷姩鍋忕Щ');
+            return;
+        }
+        let maxX = -Infinity;
+        let maxY = -Infinity;
+        mapContainer.children.forEach(child => {
+            if (child.data && (child.data.type === 'shelf' || child.data.type === 'SHELF')) {
+                maxX = Math.max(maxX, child.x);
+                maxY = Math.max(maxY, child.y);
+            }
+        });
+        setStartX(isFinite(maxX) ? maxX + gapX : 0);
+        setStartY(isFinite(maxY) ? maxY + gapY : 0);
+    };
+
+    const handleSubmit = () => {
+        onConfirm({
+            startX: parseFloat(startX),
+            startY: parseFloat(startY),
+            rows: parseInt(rows, 10),
+            cols: parseInt(cols, 10),
+            gapX: parseFloat(gapX),
+            gapY: parseFloat(gapY),
+            angle: parseFloat(angle),
+        });
+    };
+
+    // 杈呭姪鍑芥暟锛氬鐞嗘墜鍔ㄨ緭鍏ワ紝鍏佽绌哄�硷紙缃负0锛�
+    const handleNumberInput = (setter) => (e) => {
+        const val = e.target.value;
+        if (val === '') {
+            setter(0);
+        } else {
+            const num = parseFloat(val);
+            if (!isNaN(num)) setter(num);
+        }
+    };
+
+    return (
+        <Dialog open={open} onClose={onClose} maxWidth="xs" fullWidth>
+            <DialogTitle>{translate('page.map.batch.title', { _: '璐ф灦闃靛垪璁剧疆' })}</DialogTitle>
+            <DialogContent>
+                <Stack spacing={2} sx={{ mt: 1 }}>
+                    <Stack direction="row" spacing={1} alignItems="center">
+                        <TextField
+                            label={translate('page.map.batch.startX', { _: '璧风偣 X (姣背)' })}
+                            type="text"
+                            value={startX}
+                            onChange={handleNumberInput(setStartX)}
+                            fullWidth
+                        />
+                        <TextField
+                            label={translate('page.map.batch.startY', { _: '璧风偣 Y (姣背)' })}
+                            type="text"
+                            value={startY}
+                            onChange={handleNumberInput(setStartY)}
+                            fullWidth
+                        />
+                        <Button variant="outlined" onClick={handleAutoOffset}>
+                            {translate('page.map.batch.autoOffset', { _: '鑷姩鍋忕Щ' })}
+                        </Button>
+                    </Stack>
+
+                    <TextField
+                        label={translate('page.map.batch.rows', { _: '琛屾暟' })}
+                        type="number"
+                        value={rows}
+                        onChange={e => setRows(parseInt(e.target.value, 10))}
+                        inputProps={{ min: 1 }}
+                        fullWidth
+                    />
+                    <TextField
+                        label={translate('page.map.batch.cols', { _: '鍒楁暟' })}
+                        type="number"
+                        value={cols}
+                        onChange={e => setCols(parseInt(e.target.value, 10))}
+                        inputProps={{ min: 1 }}
+                        fullWidth
+                    />
+                    <TextField
+                        label={translate('page.map.batch.gapX', { _: '琛岄棿璺� (姣背)' })}
+                        type="number"
+                        value={gapX}
+                        onChange={e => setGapX(parseFloat(e.target.value))}
+                        inputProps={{ step: 100, min: 0 }}
+                        fullWidth
+                    />
+                    <TextField
+                        label={translate('page.map.batch.gapY', { _: '鍒楅棿璺� (姣背)' })}
+                        type="number"
+                        value={gapY}
+                        onChange={e => setGapY(parseFloat(e.target.value))}
+                        inputProps={{ step: 100, min: 0 }}
+                        fullWidth
+                    />
+                    <TextField
+                        label={translate('page.map.batch.angle', { _: '璐ф灦瑙掑害 (搴�)' })}
+                        type="number"
+                        value={angle}
+                        onChange={e => setAngle(parseFloat(e.target.value))}
+                        inputProps={{ step: 1 }}
+                        fullWidth
+                    />
+                </Stack>
+            </DialogContent>
+            <DialogActions>
+                <Button onClick={onClose}>{translate('ra.action.cancel')}</Button>
+                <Button onClick={handleSubmit} variant="contained" color="primary">
+                    {translate('ra.action.confirm')}
+                </Button>
+            </DialogActions>
+        </Dialog>
+    );
+};
+
+export default BatchShelfDialog;
\ No newline at end of file
diff --git a/zy-acs-flow/src/map/MapPage.jsx b/zy-acs-flow/src/map/MapPage.jsx
index 552d4bd..3f7e37c 100644
--- a/zy-acs-flow/src/map/MapPage.jsx
+++ b/zy-acs-flow/src/map/MapPage.jsx
@@ -30,14 +30,22 @@
 import AreaFab from "./header/AreaFab";
 import MoreOperate from "./header/MoreOperate";
 import NewsLogDialog from "./NewsLogDialog";
+import BatchShelfDialog from './BatchShelfDialog';
+import { DEVICE_TYPE } from './constants';   // 纭璺緞
 
 let player;
 let websocket;
 
 const Map = () => {
+
     const notify = useNotification();
     const [sidebarOpen] = useSidebarState();
     const translate = useTranslate();
+
+
+
+    // 璋冭瘯杈撳嚭
+            console.log('batchshelving translation:', translate('batchshelving'));
     const theme = useTheme();
     const themeMode = theme.palette.mode;
 
@@ -59,6 +67,80 @@
 
     const [curSprite, setCurSprite] = useState(null);
     const [batchSprites, setBatchSprites] = useState([]);
+
+    const [batchDialogOpen, setBatchDialogOpen] = useState(false);
+    const [history, setHistory] = useState([]);
+    const [historyIndex, setHistoryIndex] = useState(-1);
+    const startBatchPlace = () => {
+        setBatchDialogOpen(true);
+    };
+const recordAction = (action) => {
+    const newHistory = history.slice(0, historyIndex + 1);
+    newHistory.push(action);
+    setHistory(newHistory);
+    setHistoryIndex(newHistory.length - 1);
+};
+
+const undo = () => {
+    if (historyIndex < 0) return;
+    const action = history[historyIndex];
+    if (action.type === 'ADD_SHELVES') {
+        action.sprites.forEach(sprite => {
+            if (sprite.parent) mapContainer.removeChild(sprite);
+            sprite.destroy();
+        });
+        if (batchSprites.some(s => action.sprites.includes(s))) {
+            setBatchSprites([]);
+            setCurSprite(null);
+        }
+    }
+    setHistoryIndex(historyIndex - 1);
+};
+const handleBatchConfirm = async ({ startX, startY, rows, cols, gapX, gapY, angle }) => {
+    const start = { x: startX, y: startY };
+    const angleRad = angle * Math.PI / 180;
+    console.log(`浠� (${startX}, ${startY}) 鐢熸垚 ${rows} 琛� ${cols} 鍒楋紝闂磋窛(${gapX},${gapY})锛岃搴�${angle}掳`);
+
+    const existingPositions = new Set();
+    if (mapContainer) {
+        mapContainer.children.forEach(child => {
+            if (child.data?.type === DEVICE_TYPE.SHELF) {
+                const key = `${Math.round(child.x * 10)}_${Math.round(child.y * 10)}`;
+                existingPositions.add(key);
+            }
+        });
+    }
+
+    let created = 0;
+    const newSprites = [];
+    for (let i = 0; i < rows; i++) {
+        for (let j = 0; j < cols; j++) {
+            const posX = start.x + j * gapX;
+            const posY = start.y + i * gapY;
+            const key = `${Math.round(posX * 10)}_${Math.round(posY * 10)}`;
+            if (existingPositions.has(key)) continue;
+
+            const sprite = Tool.generateSprite(DEVICE_TYPE.SHELF);
+            if (!sprite) continue;
+            Tool.initSprite(sprite, DEVICE_TYPE.SHELF);
+            sprite.x = posX;
+            sprite.y = posY;
+            sprite.rotation = angleRad;
+            mapContainer.addChild(sprite);
+            Tool.beMovable(sprite);
+            created++;
+            newSprites.push(sprite);
+        }
+    }
+
+    if (newSprites.length) {
+        recordAction({ type: 'ADD_SHELVES', sprites: newSprites });
+    }
+
+    notify.success(`鎴愬姛鐢熸垚 ${created} 涓揣鏋讹紝璇疯寰椾繚瀛樺湴鍥綻);
+    setBatchDialogOpen(false);
+};
+
 
     const [rcsStatus, setRcsStatus] = useState(null);
     const [showRoutes, setShowRoutes] = useState(false);
@@ -375,6 +457,18 @@
                                 Http.saveMapData(curZone);
                             }}
                         />
+                     <Button
+                         variant="contained"
+                         color="secondary"
+                         onClick={startBatchPlace}
+                         sx={{ textTransform: 'none' }}
+                     >
+                         {translate('batchshelving',{_: '鎵归噺璐ф灦'})}
+                     </Button>
+
+                        <Button onClick={undo} disabled={historyIndex < 0}>
+                            {translate('ra.action.undo', { _: '鎾ら攢' })}
+                        </Button>
                     </>
                 )}
 
@@ -602,12 +696,21 @@
                 open={logDialogOpen}
                 onClose={() => setLogDialogOpen(false)}
             />
+            <BatchShelfDialog
+                open={batchDialogOpen}
+                onClose={() => setBatchDialogOpen(false)}
+                onConfirm={handleBatchConfirm}
+                mapContainer={mapContainer}
+            />
         </Box>
     );
 }
 
 const MapPage = () => {
+
+
     return (
+
         <NotificationProvider>
             <Map />
         </NotificationProvider>
diff --git a/zy-acs-flow/src/map/tool.js b/zy-acs-flow/src/map/tool.js
index 2ffcb82..da7247e 100644
--- a/zy-acs-flow/src/map/tool.js
+++ b/zy-acs-flow/src/map/tool.js
@@ -90,9 +90,8 @@
     let sprite;
     switch (deviceType) {
         case DEVICE_TYPE.SHELF:
-            sprite = new PIXI.Sprite(PIXI.Texture.from(shelf, { resourceOptions: { scale: 1 } }));
-            // sprite.width = 50;
-            // sprite.height = 50;
+            sprite = new PIXI.Sprite(PIXI.Texture.from(shelf));
+            sprite.scale.set(2.0, 2.0);   // 璋冩暣鍚堥�傜殑澶у皬
             sprite.zIndex = DEVICE_Z_INDEX.SHELF;
             break;
         case DEVICE_TYPE.CHARGE:
diff --git a/zy-acs-flow/src/page/code/CodeList.jsx b/zy-acs-flow/src/page/code/CodeList.jsx
index 1f45bad..e1ca3d9 100644
--- a/zy-acs-flow/src/page/code/CodeList.jsx
+++ b/zy-acs-flow/src/page/code/CodeList.jsx
@@ -1,5 +1,4 @@
-import React, { useState, useRef, useEffect, useMemo, useCallback } from "react";
-import { useNavigate } from 'react-router-dom';
+import React, { useState } from "react";
 import {
     List,
     DatagridConfigurable,
@@ -8,103 +7,170 @@
     SelectColumnsButton,
     EditButton,
     FilterButton,
-    CreateButton,
-    ExportButton,
     BulkDeleteButton,
     WrapperField,
-    useRecordContext,
     useTranslate,
     useListContext,
-    useCreatePath,
     TextField,
     NumberField,
     DateField,
     BooleanField,
     ReferenceField,
     TextInput,
-    DateTimeInput,
     DateInput,
     SelectInput,
     NumberInput,
-    ReferenceInput,
-    ReferenceArrayInput,
-    AutocompleteInput,
     DeleteButton,
-    FunctionField,
+    useNotify,
+    downloadCSV
 } from 'react-admin';
-import { Box, Typography, Card, Stack } from '@mui/material';
+import { Box, Button, Dialog, DialogTitle, DialogContent, DialogActions } from '@mui/material';
 import { styled } from '@mui/material/styles';
 import CodeCreate from "./CodeCreate";
 import CodePanel from "./CodePanel";
 import EmptyData from "../components/EmptyData";
 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 ImportButton from '../components/ImportButton'
+import ImportButton from '../components/ImportButton';
 import { useCodeImport } from './useCodeImport';
-
-// import * as importTemp from './importTemp.csv?raw';
-// const IMPORT_TEMP_URL = `data:text/csv;name=crm_contacts_sample.csv;charset=utf-8,${encodeURIComponent(importTemp.default)}`;
+import PageDrawer from "../components/PageDrawer";
+import { PAGE_DRAWER_WIDTH, OPERATE_MODE, DEFAULT_PAGE_SIZE } from '@/config/setting';
+import { useLocation } from 'react-router-dom';
+import DownloadIcon from '@mui/icons-material/Download';
+// 鎴栬�� import ArrowDownwardIcon from '@mui/icons-material/ArrowDownward';
 
 const IMPORT_TEMP_URL = '/imports/code_import_template.xlsx';
 
 const StyledDatagrid = styled(DatagridConfigurable)(({ theme }) => ({
-    '& .css-1vooibu-MuiSvgIcon-root': {
-        height: '.9em'
-    },
-    '& .RaDatagrid-row': {
-        cursor: 'auto'
-    },
-    '& .column-name': {
-    },
-    '& .opt': {
-        width: 200
-    },
+    '& .css-1vooibu-MuiSvgIcon-root': { height: '.9em' },
+    '& .RaDatagrid-row': { cursor: 'auto' },
+    '& .opt': { width: 200 },
 }));
 
 const filters = [
     <SearchInput source="condition" alwaysOn />,
     <DateInput label='common.time.after' source="timeStart" alwaysOn />,
     <DateInput label='common.time.before' source="timeEnd" alwaysOn />,
-
     <TextInput source="uuid" label="table.field.code.uuid" />,
     <TextInput source="data" label="table.field.code.data" alwaysOn />,
     <NumberInput source="x" label="table.field.code.x" />,
     <NumberInput source="y" label="table.field.code.y" />,
-    <SelectInput source="corner" label="table.field.code.corner"
-        choices={[
-            { id: '1', name: 'common.enums.true' },
-            { id: '0', name: 'common.enums.false' },
-        ]}
+    <SelectInput
+        source="corner"
+        label="table.field.code.corner"
+        choices={[{ id: '1', name: 'common.enums.true' }, { id: '0', name: 'common.enums.false' }]}
         alwaysOn
     />,
-    // <TextInput source="scale" label="table.field.code.scale" />,
-    // <SelectInput source="spin" label="table.field.code.spin"
-    //     choices={[
-    //         { id: 0, name: 'page.code.enums.spin.na' },
-    //         { id: 1, name: 'page.code.enums.spin.cw' },
-    //         { id: 2, name: 'page.code.enums.spin.ccw' },
-    //     ]}
-    //     alwaysOn
-    //     emptyText={false}
-    // />,
     <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' },
-        ]}
+        choices={[{ id: '1', name: 'common.enums.statusTrue' }, { id: '0', name: 'common.enums.statusFalse' }]}
     />,
-]
+];
 
-const CodeList = () => {
+// ===================== 鑷畾涔夊鍑烘寜閽� =====================
+const CustomExportButton = () => {
+    const [open, setOpen] = useState(false);
+    const notify = useNotify();
     const translate = useTranslate();
 
+    // 鐩存帴浠庢祻瑙堝櫒鍦板潃鏍忚В鏋� filter 鍙傛暟
+    const getFilterFromUrl = () => {
+        const hash = window.location.hash; // 渚嬪 "#/code?filter=%7B%22corner%22%3A%220%22%7D"
+        const match = hash.match(/[?&]filter=([^&]+)/);
+        if (match) {
+            try {
+                const filterJson = decodeURIComponent(match[1]);
+                return JSON.parse(filterJson);
+            } catch (e) {
+                console.error('瑙f瀽 filter 澶辫触', e);
+                return {};
+            }
+        }
+        return {};
+    };
+
+    const handleExport = async (useCurrentFilter) => {
+        try {
+            let filter = {};
+            if (useCurrentFilter) {
+                filter = getFilterFromUrl();
+            }
+            console.log('鏈�缁堝鍑轰娇鐢ㄧ殑 filter:', filter);
+
+            const token = localStorage.getItem('Authorization');
+            // 灏� filter 杞崲涓烘煡璇㈠瓧绗︿覆锛屼緥濡� corner=0
+            const queryParams = new URLSearchParams(filter).toString();
+            const url = `/api/code/export${queryParams ? '?' + queryParams : ''}`;
+            console.log('璇锋眰 URL:', url);
+
+            const response = await fetch(url, {
+                method: 'POST',  // 鏀瑰洖 POST锛堝洜涓轰箣鍓� POST 鑳借繑鍥� Excel锛�
+                headers: {
+                    'Authorization': `Bearer ${token}`,
+                    'Content-Type': 'application/json',
+                },
+                // 灏濊瘯鍚屾椂灏� filter 浠� JSON 鏍煎紡鏀惧湪 body 涓紙鐪嬪悗绔槸鍚﹁鍙栵級
+                body: JSON.stringify(filter),
+            });
+
+            if (!response.ok) throw new Error(`瀵煎嚭澶辫触 ${response.status}`);
+
+            // 妫�鏌ュ搷搴旂被鍨嬶紝濡傛灉鏄� JSON 璇存槑鍚庣鎶ラ敊浜�
+            const contentType = response.headers.get('content-type');
+            if (contentType && contentType.includes('application/json')) {
+                const errorJson = await response.json();
+                console.error('鍚庣杩斿洖閿欒:', errorJson);
+                notify(`瀵煎嚭澶辫触: ${errorJson.msg || '鏈煡閿欒'}`, { type: 'error' });
+                return;
+            }
+
+            const blob = await response.blob();
+            const link = document.createElement('a');
+            link.href = URL.createObjectURL(blob);
+            link.download = `code_export_${new Date().toISOString().slice(0,19)}.xlsx`;
+            link.click();
+            URL.revokeObjectURL(link.href);
+            notify('瀵煎嚭鎴愬姛', { type: 'success' });
+        } catch (error) {
+            console.error(error);
+            notify('瀵煎嚭澶辫触', { type: 'error' });
+        }
+        setOpen(false);
+    };
+
+    return (
+        <>
+           <Button
+               color="primary"
+               onClick={() => setOpen(true)}
+               startIcon={<DownloadIcon />}   // 娣诲姞鍚戜笅绠ご鍥炬爣
+               size="small"                   // 涓� ImportButton 淇濇寔涓�鑷寸殑灏忓昂瀵�
+               sx={{ textTransform: 'none' }} // 鍙�夛細姝e父澶у皬鍐欙紝鍖归厤瀵煎叆鎸夐挳
+           >
+               {translate('ra.action.export') || '瀵煎嚭'}
+           </Button>
+            <Dialog open={open} onClose={() => setOpen(false)}>
+                <DialogTitle>{translate('code.export.title',{_: '閫夋嫨瀵煎嚭鑼冨洿'})}</DialogTitle>
+                <DialogContent>{translate('code.export.message',{_: '璇烽�夋嫨瑕佸鍑虹殑鏁版嵁鑼冨洿'}) }</DialogContent>
+                <DialogActions>
+                    <Button onClick={() => handleExport(true)}>
+                        {translate('code.export.current',{_: '瀵煎嚭褰撳墠绛涢�夌粨鏋�'}) }
+                    </Button>
+                    <Button onClick={() => handleExport(false)}>
+                        {translate('code.export.all',{_: '瀵煎嚭鍏ㄩ儴鏁版嵁' } )}
+                    </Button>
+                    <Button onClick={() => setOpen(false)}>
+                        {translate('ra.action.cancel') || '鍙栨秷'}
+                    </Button>
+                </DialogActions>
+            </Dialog>
+        </>
+    );
+};
+// =======================================================
+const CodeList = () => {
+    const translate = useTranslate();
     const [createDialog, setCreateDialog] = useState(false);
     const [drawerVal, setDrawerVal] = useState(false);
 
@@ -119,17 +185,17 @@
                         }),
                     marginRight: drawerVal ? `${PAGE_DRAWER_WIDTH}px` : 0,
                 }}
-                title={"menu.code"}
-                empty={<EmptyData onClick={() => { setCreateDialog(true) }} />}
+                title="menu.code"
+                empty={<EmptyData onClick={() => setCreateDialog(true)} />}
                 filters={filters}
                 sort={{ field: "id", order: "asc" }}
                 actions={(
                     <TopToolbar>
                         <FilterButton />
-                        <MyCreateButton onClick={() => { setCreateDialog(true) }} />
+                        <MyCreateButton onClick={() => setCreateDialog(true)} />
                         <SelectColumnsButton preferenceKey='code' />
                         <ImportButton type="xlsx" importTemp={IMPORT_TEMP_URL} useImport={useCodeImport} onceBatch={10} />
-                        <MyExportButton />
+                        <CustomExportButton />  {/* 鏇挎崲鍘熸湁鐨勫鍑烘寜閽� */}
                     </TopToolbar>
                 )}
                 perPage={DEFAULT_PAGE_SIZE}
@@ -148,23 +214,6 @@
                     <NumberField source="x" label="table.field.code.x" />
                     <NumberField source="y" label="table.field.code.y" />
                     <BooleanField source="cornerBool" label="table.field.code.corner" sortable={false} />
-                    {/* <TextField source="scale" label="table.field.code.scale" /> */}
-                    {/* <FunctionField
-                        label="table.field.code.spin"
-                        sortable={false}
-                        render={(record) => {
-                            switch (record.spin) {
-                                case 0:
-                                    return translate('page.code.enums.spin.na');
-                                case 1:
-                                    return translate('page.code.enums.spin.cw');
-                                case 2:
-                                    return translate('page.code.enums.spin.ccw');
-                                default:
-                                    return 'N/A';
-                            }
-                        }}
-                    /> */}
                     <ReferenceField source="updateBy" label="common.field.updateBy" reference="user" link={false} sortable={false}>
                         <TextField source="nickname" />
                     </ReferenceField>
@@ -181,18 +230,10 @@
                     </WrapperField>
                 </StyledDatagrid>
             </List>
-            <CodeCreate
-                open={createDialog}
-                setOpen={setCreateDialog}
-            />
-            <PageDrawer
-                title='Code Detail'
-                drawerVal={drawerVal}
-                setDrawerVal={setDrawerVal}
-            >
-            </PageDrawer>
+            <CodeCreate open={createDialog} setOpen={setCreateDialog} />
+            <PageDrawer title='Code Detail' drawerVal={drawerVal} setDrawerVal={setDrawerVal} />
         </Box>
-    )
-}
+    );
+};
 
-export default CodeList;
+export default CodeList;
\ No newline at end of file
diff --git a/zy-acs-manager/src/main/java/com/zy/acs/manager/manager/controller/CodeController.java b/zy-acs-manager/src/main/java/com/zy/acs/manager/manager/controller/CodeController.java
index 4ccf9a6..d8ebe5e 100644
--- a/zy-acs-manager/src/main/java/com/zy/acs/manager/manager/controller/CodeController.java
+++ b/zy-acs-manager/src/main/java/com/zy/acs/manager/manager/controller/CodeController.java
@@ -2,6 +2,7 @@
 
 import com.alibaba.fastjson.JSON;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.zy.acs.common.utils.GsonUtils;
 import com.zy.acs.common.utils.QrCodeCodecSupport;
@@ -202,7 +203,52 @@
     @PreAuthorize("hasAuthority('manager:code:list')")
     @PostMapping("/code/export")
     public void export(@RequestBody Map<String, Object> map, HttpServletResponse response) throws Exception {
-        ExcelUtil.build(ExcelUtil.create(codeService.list(), Code.class), response);
+        // 1. 鎻愬彇绛涢�夋潯浠讹紙鍏煎鍓嶇 { filter: {...} } 鏍煎紡锛�
+        Map<String, Object> filter = map;
+        if (map != null && map.containsKey("filter")) {
+            Object filterObj = map.get("filter");
+            if (filterObj instanceof Map) {
+                filter = (Map<String, Object>) filterObj;
+            }
+        }
+
+        // 2. 鏋勫缓鏌ヨ鏉′欢锛圡yBatis-Plus锛�
+        QueryWrapper<Code> wrapper = new QueryWrapper<>();
+        if (filter != null && !filter.isEmpty()) {
+            // 鏍规嵁鍓嶇鍙兘浼犻�掔殑瀛楁娣诲姞鏉′欢锛堝瓧娈靛悕闇�涓庢暟鎹簱鍒楁垨瀹炰綋灞炴�у搴旓級
+            if (filter.containsKey("corner")) {
+                wrapper.eq("corner", filter.get("corner"));
+            }
+            if (filter.containsKey("condition")) {
+                String condition = (String) filter.get("condition");
+                wrapper.and(w -> w.like("data", condition).or().like("uuid", condition));
+            }
+            if (filter.containsKey("timeStart")) {
+                wrapper.ge("create_time", filter.get("timeStart"));
+            }
+            if (filter.containsKey("timeEnd")) {
+                wrapper.le("create_time", filter.get("timeEnd"));
+            }
+            if (filter.containsKey("x")) {
+                wrapper.eq("x", filter.get("x"));
+            }
+            if (filter.containsKey("y")) {
+                wrapper.eq("y", filter.get("y"));
+            }
+            if (filter.containsKey("memo")) {
+                wrapper.like("memo", filter.get("memo"));
+            }
+            if (filter.containsKey("status")) {
+                wrapper.eq("status", filter.get("status"));
+            }
+            // 杩樺彲浠ユ坊鍔犲叾浠栧瓧娈靛 data, uuid 绛�
+        }
+
+        // 3. 鏌ヨ绗﹀悎鏉′欢鐨勬暟鎹�
+        List<Code> list = codeService.list(wrapper);
+
+        // 4. 瀵煎嚭 Excel
+        ExcelUtil.build(ExcelUtil.create(list, Code.class), response);
     }
 
     @PreAuthorize("hasAuthority('manager:code:save')")
diff --git a/zy-acs-manager/src/main/resources/application.yml b/zy-acs-manager/src/main/resources/application.yml
index 1d2e04a..dc52dd7 100644
--- a/zy-acs-manager/src/main/resources/application.yml
+++ b/zy-acs-manager/src/main/resources/application.yml
@@ -7,9 +7,9 @@
     static-path-pattern: /**
   datasource:
     driver-class-name: com.mysql.jdbc.Driver
-    url: jdbc:mysql://localhost:3306/rcs_ctu_stable_hik?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai
+    url: jdbc:mysql://localhost:3306/rcs_ctu_stable_hik?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
     username: root
-    password: xltys1995
+    password: Mysql20260414
     type: com.alibaba.druid.pool.DruidDataSource
     druid:
       initial-size: 5
@@ -43,7 +43,7 @@
 
 redis:
   host: localhost
-  password: xltys1995
+  password: Redis20260417
   port: 6379
   max: 30
   min: 10

--
Gitblit v1.9.1