vincentlu
1 天以前 9480117a82283efc252063814391e2ab5e653e91
Merge remote-tracking branch 'zoneyung/rcs_master_hk' into rcs_master_hk
1个文件已添加
7个文件已修改
625 ■■■■ 已修改文件
zy-acs-flow/src/i18n/en.js 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zy-acs-flow/src/i18n/zh.js 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zy-acs-flow/src/map/BatchShelfDialog.jsx 133 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zy-acs-flow/src/map/MapPage.jsx 103 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zy-acs-flow/src/map/tool.js 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zy-acs-flow/src/page/code/CodeList.jsx 295 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zy-acs-manager/src/main/java/com/zy/acs/manager/manager/controller/CodeController.java 48 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zy-acs-manager/src/main/resources/application.yml 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
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;
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;
zy-acs-flow/src/map/BatchShelfDialog.jsx
New file
@@ -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;
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>
zy-acs-flow/src/map/tool.js
@@ -107,9 +107,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:
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,248 @@
    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 , Typography} 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 [loading, setLoading] = useState(false);
    const notify = useNotify();
    const translate = useTranslate();
    // 直接从浏览器地址栏解析 filter 参数
    const getFilterFromUrl = () => {
        const hash = window.location.hash;
        const match = hash.match(/[?&]filter=([^&]+)/);
        if (match) {
            try {
                const filterJson = decodeURIComponent(match[1]);
                return JSON.parse(filterJson);
            } catch (e) {
                console.error('解析 filter 失败', e);
                return {};
            }
        }
        return {};
    };
    const handleExport = async (useCurrentFilter) => {
        setLoading(true);
        try {
            let filter = {};
            if (useCurrentFilter) {
                filter = getFilterFromUrl();
            }
            console.log('最终导出使用的 filter:', filter);
            const token = localStorage.getItem('Authorization');
            const queryParams = new URLSearchParams(filter).toString();
            const url = `/api/code/export${queryParams ? '?' + queryParams : ''}`;
            console.log('请求 URL:', url);
            const response = await fetch(url, {
                method: 'POST',
                headers: {
                    'Authorization': `Bearer ${token}`,
                    'Content-Type': 'application/json',
                },
                body: JSON.stringify(filter),
            });
            if (!response.ok) throw new Error(`导出失败 ${response.status}`);
            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' });
        } finally {
            setLoading(false);
            setOpen(false);
        }
    };
   return (
       <>
           <Button
               color="primary"
               onClick={() => setOpen(true)}
               startIcon={<DownloadIcon />}
               size="small"
               sx={{ textTransform: 'none' }}
           >
               {translate('ra.action.export') || '导出'}
           </Button>
           <Dialog
               open={open}
               onClose={() => setOpen(false)}
               fullWidth
               maxWidth="sm"
               PaperProps={{
                   sx: {
                       borderRadius: 3,
                       boxShadow: 24,
                       p: 1,
                   }
               }}
           >
               <DialogTitle sx={{ pb: 1, fontWeight: 'bold', fontSize: '1.25rem' }}>
                   {translate('code.export.title', { _: '选择导出范围' })}
               </DialogTitle>
               <DialogContent sx={{ pt: 0 }}>
                   <Typography variant="body2" color="text.secondary">
                       {translate('code.export.message', { _: '请选择要导出的数据范围' })}
                   </Typography>
               </DialogContent>
               <DialogActions sx={{ px: 3, pb: 2, gap: 1 }}>
                   <Button
                       onClick={() => handleExport(true)}
                       disabled={loading}
                       size="medium"
                       sx={{
                           backgroundColor: '#fff',
                           color: '#1e293b',
                           border: '1px solid #cbd5e1',
                           '&:hover': {
                               backgroundColor: '#1976d2',
                               borderColor: '#1976d2',
                               color: '#fff',
                           },
                           '&:disabled': {
                               backgroundColor: '#f1f5f9',
                               color: '#94a3b8',
                               borderColor: '#e2e8f0',
                           }
                       }}
                   >
                       {translate('code.export.current', { _: '导出当前筛选结果' })}
                   </Button>
                   <Button
                       onClick={() => handleExport(false)}
                       disabled={loading}
                       size="medium"
                       sx={{
                           backgroundColor: '#fff',
                           color: '#1e293b',
                           border: '1px solid #cbd5e1',
                           '&:hover': {
                               backgroundColor: '#1976d2',
                               borderColor: '#1976d2',
                               color: '#fff',
                           },
                           '&:disabled': {
                               backgroundColor: '#f1f5f9',
                               color: '#94a3b8',
                               borderColor: '#e2e8f0',
                           }
                       }}
                   >
                       {translate('code.export.all', { _: '导出全部数据' })}
                   </Button>
                   <Button
                       onClick={() => setOpen(false)}
                       disabled={loading}
                       size="medium"
                       sx={{
                           backgroundColor: '#fff',
                           color: '#1e293b',
                           border: '1px solid #cbd5e1',
                           '&:hover': {
                               backgroundColor: '#1976d2',
                               borderColor: '#1976d2',
                               color: '#fff',
                           },
                           '&:disabled': {
                               backgroundColor: '#f1f5f9',
                               color: '#94a3b8',
                               borderColor: '#e2e8f0',
                           }
                       }}
                   >
                       {translate('ra.action.cancel') || '取消'}
                   </Button>
               </DialogActions>
           </Dialog>
       </>
   );
   };
// =======================================================
// =======================================================
const CodeList = () => {
    const translate = useTranslate();
    const [createDialog, setCreateDialog] = useState(false);
    const [drawerVal, setDrawerVal] = useState(false);
@@ -119,17 +263,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 +292,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 +308,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;
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. 构建查询条件(MyBatis-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')")
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