From d6fcca25f2979710b008502e26aeceb8993f86ae Mon Sep 17 00:00:00 2001
From: zhou zhou <3272660260@qq.com>
Date: 星期二, 06 一月 2026 20:00:28 +0800
Subject: [PATCH] #

---
 rsf-admin/src/page/menuPda/MenuPdaList.jsx |  456 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 456 insertions(+), 0 deletions(-)

diff --git a/rsf-admin/src/page/menuPda/MenuPdaList.jsx b/rsf-admin/src/page/menuPda/MenuPdaList.jsx
new file mode 100644
index 0000000..6bde7a7
--- /dev/null
+++ b/rsf-admin/src/page/menuPda/MenuPdaList.jsx
@@ -0,0 +1,456 @@
+import React from 'react';
+import {
+    Title,
+    useTranslate,
+    useNotify,
+    useRedirect,
+    useRefresh,
+    useDelete,
+} from 'react-admin';
+import { styled } from '@mui/material/styles';
+import {
+    Box,
+    Collapse,
+    IconButton,
+    Table,
+    TableBody,
+    TableCell,
+    TableContainer,
+    TableHead,
+    TableRow,
+    Paper,
+    Card,
+    Typography,
+    TextField,
+    Tooltip,
+    Button,
+    Chip,
+    LinearProgress,
+} from '@mui/material';
+import { Add, Edit, Delete } from '@mui/icons-material';
+import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown';
+import KeyboardArrowRightIcon from '@mui/icons-material/KeyboardArrowRight';
+import RefreshIcon from '@mui/icons-material/Refresh';
+import request from '@/utils/request';
+import DeptEdit from './MenuPdaEdit';
+import * as Icons from '@mui/icons-material';
+
+const RESOURCE = 'menuPda';
+const TITLE = 'menu.menuPda';
+
+const columns = [
+    {
+        id: 'name',
+        label: 'table.field.menuPda.name',
+        minWidth: 200,
+    },
+    {
+        id: 'icon',
+        label: 'table.field.menuPda.icon',
+        minWidth: 80,
+        format: (iconStr) => {
+            if (iconStr) {
+                const IconComponent = getIconComponent(iconStr);
+                if (IconComponent) {
+                    return <IconComponent />;
+                }
+            }
+        }
+    },
+    {
+        id: 'route',
+        label: 'table.field.menuPda.route',
+        minWidth: 80,
+    },
+    {
+        id: 'component',
+        label: 'table.field.menuPda.component',
+        minWidth: 100,
+    },
+    {
+        id: 'authority',
+        label: 'table.field.menuPda.authority',
+        minWidth: 100,
+    },
+    {
+        id: 'type',
+        label: 'table.field.menuPda.type',
+        minWidth: 100,
+        format: (val) => {
+            if (Number(val) === 0) {
+                return <Chip variant='outlined' label="Menu" color="primary" size='small' />;
+            } else {
+                return <Chip variant='outlined' label="Button" color="default" size='small' />;
+            }
+        }
+    },
+    {
+        id: 'sort',
+        label: 'table.field.menuPda.sort',
+        minWidth: 80,
+        format: (val) => {
+            return <Typography variant='body2' sx={{ fontWeight: 'bold' }}>{val}</Typography>
+        }
+    },
+    // {
+    //     id: 'updateTime',
+    //     label: 'common.field.updateTime',
+    //     minWidth: 120,
+    //     format: (val) => {
+    //         return new Date(val).toLocaleString();
+    //     }
+    // },
+    {
+        id: 'id',
+        label: 'common.field.id',
+        minWidth: 80,
+    },
+    {
+        id: 'actions',
+        label: 'common.field.opt',
+        minWidth: 100,
+    },
+];
+
+const getIconComponent = (iconStr) => {
+    return Icons[iconStr] || null;
+};
+
+const StyledTableRow = styled(TableRow)(({ theme }) => ({
+    '& .MuiButtonBase-root': {
+        padding: '0px 8px'
+    }
+}));
+
+const StyledTableCell = styled(TableCell)(({ theme }) => ({
+    overflow: 'hidden',
+    textOverflow: 'ellipsis',
+    whiteSpace: 'nowrap',
+    maxWidth: 600,
+    // 纭繚鎵�鏈夊崟鍏冩牸鏈夊熀鏈殑鍐呰竟璺�
+    padding: '8px 16px',
+}));
+
+const TreeTableRow = (props) => {
+    const { row, depth = 0, openNodes, setOpenNodes, onEdit, onDelete } = props;
+    const translate = useTranslate();
+
+    const toggleNode = (id) => {
+        setOpenNodes(prevState => ({ ...prevState, [id]: !prevState[id] }));
+    };
+
+    const isOpen = openNodes[row.id] || false;
+
+    // 鏇存槑鏄剧殑閫忔槑搴︽笎鍙�
+    const getOpacity = (currentDepth) => {
+        // 绗竴绾э細100%锛岀浜岀骇锛�75%锛岀涓夌骇锛�50%锛岀鍥涚骇锛�40%
+        const opacities = [1, 0.9, 0.8, 0.7];
+        return opacities[currentDepth] || 0.4;
+    };
+
+    const opacity = getOpacity(depth);
+
+    return (
+        <React.Fragment>
+            <StyledTableRow
+                hover
+                tabIndex={-1}
+                key={row.id}
+                sx={{
+                    opacity: depth > 0 ? opacity : 1,
+                    // 娣诲姞鑳屾櫙鑹叉笎鍙樺寮烘晥鏋�
+                    backgroundColor: depth > 0 ? `rgba(0, 0, 0, ${0.02 * (3 - depth)})` : 'inherit',
+                }}
+            >
+                <StyledTableCell sx={{
+                    padding: 0,
+                    width: 60,
+                    // 杩涗竴姝ュ噺灏忕缉杩涜窛绂诲埌12px
+                    paddingLeft: depth * 4
+                }}>
+                    {row.children && row.children.length > 0 && (
+                        <IconButton
+                            aria-label="expand row"
+                            size="small"
+                            onClick={() => toggleNode(row.id)}
+                            sx={{
+                                opacity: depth > 0 ? opacity : 1,
+                            }}
+                        >
+                            {isOpen ? <KeyboardArrowDownIcon /> : <KeyboardArrowRightIcon />}
+                        </IconButton>
+                    )}
+                </StyledTableCell>
+                {columns.map((column, idx) => {
+                    if (column.id !== 'actions') {
+                        let value = row[column.id];
+                        if (column.id === 'name' && row['type'] === 0) {
+                            value = translate(value);
+                        }
+                        return (
+                            <StyledTableCell
+                                key={column.id}
+                                align={column.align || 'left'}
+                                style={{
+                                    // 鍚嶇О鍒椾篃浣跨敤12px缂╄繘
+                                    paddingLeft: idx === 0 ? (depth * 24 + 16) : 16,
+                                    // opacity: column.id === 'icon' && .6
+                                }}
+                                onClick={() => column.id === 'name' && toggleNode(row.id)}
+                                sx={{
+                                    opacity: column.id === 'icon' ? 0.6 : (depth > 0 ? opacity : 1),
+                                    fontWeight: 400,
+                                    // 浣跨敤瀛椾綋澶у皬鎴栭鑹叉潵鍖哄垎灞傜骇
+                                    fontSize: depth === 0 ? '0.95rem' : '0.9rem',
+                                    // 鎴栬�呬娇鐢ㄤ笉鍚岀殑棰滆壊
+                                    color: depth === 0 ? 'text.primary' : `rgba(0, 0, 0, ${opacity})`,
+                                }}
+                            >
+                                {column.format ? column.format(value) : value}
+                            </StyledTableCell>
+                        )
+                    }
+                })}
+                <StyledTableCell>
+                    <Tooltip title="Edit">
+                        <IconButton
+                            onClick={() => onEdit(row)}
+                            sx={{
+                                opacity: depth > 0 ? opacity : 1,
+                            }}
+                            size="small"
+                        >
+                            <Edit />
+                        </IconButton>
+                    </Tooltip>
+                    <Tooltip title="Delete">
+                        <IconButton
+                            onClick={() => onDelete(row)}
+                            sx={{
+                                opacity: depth > 0 ? opacity : 1,
+                            }}
+                            size="small"
+                        >
+                            <Delete />
+                        </IconButton>
+                    </Tooltip>
+                </StyledTableCell>
+            </StyledTableRow>
+            {row.children && row.children.length > 0 && isOpen && (
+                row.children.map((child) => (
+                    <TreeTableRow
+                        key={child.id}
+                        row={child}
+                        depth={depth + 1}
+                        onEdit={onEdit}
+                        onDelete={onDelete}
+                        openNodes={openNodes}
+                        setOpenNodes={setOpenNodes}
+                    />
+                ))
+            )}
+        </React.Fragment>
+    );
+};
+
+const MenuPdaList = () => {
+    const translate = useTranslate();
+    const notify = useNotify();
+    const redirect = useRedirect();
+    const refresh = useRefresh();
+    const [deleteOne] = useDelete();
+
+    const [treeData, setTreeData] = React.useState(null);
+    const [filter, setFilter] = React.useState("");
+    const [createDialog, setCreateDialog] = React.useState(false);
+    const [editRecord, setEditRecord] = React.useState(null);
+    const [openNodes, setOpenNodes] = React.useState({});
+    const [expandAll, setExpandAll] = React.useState(false);
+    const notifyState = React.useRef({ last: '', at: 0 });
+    const pushNotify = React.useCallback((type, msg) => {
+        const text = typeof msg === 'string' ? msg : (msg || '');
+        const now = Date.now();
+        if (notifyState.current.last === text && now - notifyState.current.at < 1500) return;
+        notifyState.current = { last: text, at: now };
+        notify(text, { type, messageArgs: { _: text } });
+    }, [notify]);
+
+    const http = async () => {
+        const res = await request.post(RESOURCE + '/tree', {
+            condition: filter
+        });
+        if (res?.data?.code === 200) {
+            setTreeData(res.data.data);
+        } else {
+            const msg = translate('ra.notification.http_error', { _: res?.data?.msg || 'Request failed' });
+            pushNotify('warning', msg);
+        }
+    }
+
+    React.useEffect(() => {
+        http();
+    }, [filter]);
+
+    const handleRefresh = () => {
+        http();
+    };
+
+    const handleAdd = () => {
+        setEditRecord(null);
+        setCreateDialog(true);
+    };
+
+    const handleEdit = (node) => {
+        setEditRecord(node);
+        setCreateDialog(true);
+    };
+
+    const handleDelete = (node) => {
+        if (window.confirm(translate('ra.message.delete_content'))) {
+            deleteOne(
+                RESOURCE,
+                { id: node.id },
+                {
+                    onSuccess: () => {
+                        handleRefresh();
+                        const msg = translate('ra.message.delete_success', { _: 'Deleted successfully' });
+                        pushNotify('success', msg);
+                    },
+                    onError: (error) => {
+                        const msg = translate('ra.notification.http_error', { _: error?.message || 'Network error' });
+                        pushNotify('error', msg);
+                    },
+                }
+            );
+        }
+    };
+
+    const toggleExpandAll = () => {
+        setExpandAll(prevExpandAll => {
+            const newExpandAll = !prevExpandAll;
+            const newOpenNodes = {};
+            const updateOpenNodes = (nodes) => {
+                if (!nodes) return;
+                nodes.forEach(node => {
+                    newOpenNodes[node.id] = newExpandAll;
+                    if (node.children) {
+                        updateOpenNodes(node.children);
+                    }
+                });
+            };
+            updateOpenNodes(treeData);
+            setOpenNodes(newOpenNodes);
+            return newExpandAll;
+        });
+    };
+
+    return (
+        <div>
+            <DeptEdit
+                editRecord={editRecord}
+                open={createDialog}
+                setOpen={setCreateDialog}
+                callback={() => {
+                    handleRefresh();
+                }}
+                resource={RESOURCE}
+            />
+            <Title title={TITLE} />
+            <Box sx={{ mt: 2, mr: 3, mb: 1, display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
+                <Box width={300} >
+                    <Button
+                        variant="outlined"
+                        color="primary"
+                        startIcon={expandAll ? <KeyboardArrowDownIcon /> : <KeyboardArrowRightIcon />}
+                        onClick={toggleExpandAll}
+                        sx={{ ml: 1 }}
+                    >
+                        {expandAll ? translate('common.action.collapseAll') : translate('common.action.expandAll')}
+                    </Button>
+                    {/* <TextField
+                        label="Search"
+                        value={filter}
+                        onChange={({ target }) => {
+                            setFilter(target.value)
+                        }}
+                        variant="filled"
+                        size="small"
+                        margin="dense"
+                        fullWidth
+                    /> */}
+                </Box>
+                <Box>
+                    <Button
+                        variant="outlined"
+                        color="primary"
+                        startIcon={<RefreshIcon />}
+                        onClick={handleRefresh}
+                        sx={{ ml: 1 }}
+                    >
+                        {translate('ra.action.refresh')}
+                    </Button>
+                    <Button
+                        variant="outlined"
+                        color="primary"
+                        startIcon={<Add />}
+                        onClick={handleAdd}
+                        sx={{ ml: 1 }}
+                    >
+                        {translate('ra.action.add')}
+                    </Button>
+                </Box>
+            </Box>
+            <Card sx={{
+                position: 'relative',
+            }}>
+                {!treeData && (
+                    <LinearProgress
+                        sx={{
+                            height: "3px",
+                            position: 'absolute',
+                            top: 0,
+                            left: 0,
+                            right: 0,
+                        }}
+                    />
+                )}
+                <TableContainer component={Paper}>
+                    <Table size="small">
+                        <TableHead>
+                            <TableRow>
+                                <StyledTableCell sx={{ padding: 0, width: 60 }} />
+                                {columns.map((column, idx) => (
+                                    <StyledTableCell
+                                        key={idx}
+                                        align={column.align || 'left'}
+                                        style={{
+                                            minWidth: column.minWidth
+                                        }}
+                                    >
+                                        {translate(column.label)}
+                                    </StyledTableCell>
+                                ))}
+                            </TableRow>
+                        </TableHead>
+                        <TableBody>
+                            {treeData && treeData.length > 0 && (
+                                treeData.map((row) => (
+                                    <TreeTableRow
+                                        key={row.id}
+                                        row={row}
+                                        onEdit={handleEdit}
+                                        onDelete={handleDelete}
+                                        openNodes={openNodes}
+                                        setOpenNodes={setOpenNodes}
+                                    />
+                                ))
+                            )}
+                        </TableBody>
+                    </Table>
+                </TableContainer>
+            </Card>
+        </div>
+    );
+};
+
+export default MenuPdaList;

--
Gitblit v1.9.1