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