From 18c0ffb02bbc0aa7eeea1d79a210260880b964f8 Mon Sep 17 00:00:00 2001
From: lbq <1065079612@qq.com>
Date: 星期三, 07 一月 2026 11:07:21 +0800
Subject: [PATCH] Merge remote-tracking branch 'origin/devlop-phyz' into devlop-phyz

---
 rsf-admin/src/page/menuPda/index.jsx                                                            |   15 
 rsf-server/src/main/java/com/vincent/rsf/server/manager/schedules/TaskSchedules.java            |    2 
 rsf-server/src/main/java/com/vincent/rsf/server/manager/mapper/MenuPdaMapper.java               |   12 
 rsf-admin/src/page/pdaRoleMenu/index.jsx                                                        |   18 
 rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/PdaRoleMenuServiceImpl.java |   24 
 rsf-server/src/main/java/com/vincent/rsf/server/api/controller/pda/InBoundController.java       |   34 
 rsf-server/src/main/java/com/vincent/rsf/server/common/CodeBuilder.java                         |    8 
 rsf-admin/src/page/pdaRoleMenu/PdaRoleMenuEdit.jsx                                              |   97 ++
 rsf-server/src/main/java/com/vincent/rsf/server/manager/service/MenuPdaService.java             |    8 
 rsf-admin/src/page/menuPda/MenuPdaList.jsx                                                      |  456 ++++++++++
 rsf-server/src/main/java/com/vincent/rsf/server/common/config/MybatisPlusConfig.java            |    2 
 rsf-server/src/main/java/com/vincent/rsf/server/api/entity/params/PdaGeneralParam.java          |   15 
 rsf-admin/src/i18n/zh.js                                                                        |   21 
 rsf-admin/src/page/menuPda/MenuPdaEdit.jsx                                                      |  225 +++++
 rsf-server/src/main/java/com/vincent/rsf/server/manager/controller/MenuPdaController.java       |  157 +++
 rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/PdaRoleMenuMapper.java            |   22 
 rsf-admin/src/i18n/en.js                                                                        |   21 
 rsf-admin/src/page/ResourceContent.js                                                           |    9 
 rsf-server/src/main/java/com/vincent/rsf/server/api/service/impl/InBoundServiceImpl.java        |   14 
 rsf-admin/src/page/pdaRoleMenu/PdaRoleMenuPanel.jsx                                             |   63 +
 rsf-server/src/main/resources/application-dev.yml                                               |    2 
 rsf-admin/src/page/pdaRoleMenu/PdaRoleMenuCreate.jsx                                            |  125 ++
 rsf-server/src/main/java/com/vincent/rsf/server/manager/service/impl/MenuPdaServiceImpl.java    |   12 
 rsf-server/src/main/java/com/vincent/rsf/server/manager/entity/MenuPda.java                     |  258 ++++++
 rsf-server/src/main/java/com/vincent/rsf/server/api/service/InBoundService.java                 |   10 
 rsf-open-api/src/main/resources/application-dev.yml                                             |    4 
 rsf-server/src/main/resources/mapper/manager/MenuPdaMapper.xml                                  |    5 
 rsf-admin/src/i18n/core/chineseMessages.js                                                      |    1 
 rsf-server/src/main/java/com/vincent/rsf/server/system/service/PdaRoleMenuService.java          |   14 
 rsf-server/src/main/resources/mapper/system/PdaRoleMenuMapper.xml                               |   37 
 rsf-server/src/main/java/pdaRoleMenu.sql                                                        |   23 
 rsf-admin/src/page/pdaRoleMenu/PdaRoleMenuList.jsx                                              |  154 +++
 rsf-admin/src/page/system/role/RoleList.jsx                                                     |   69 +
 rsf-server/src/main/java/com/vincent/rsf/server/manager/schedules/CheckOrderSchedules.java      |    8 
 rsf-server/src/main/java/com/vincent/rsf/server/system/controller/PdaRoleMenuController.java    |   58 +
 rsf-admin/src/page/system/role/AssignPermissions_pda.jsx                                        |  380 +++++++++
 rsf-server/src/main/java/com/vincent/rsf/server/system/entity/PdaRoleMenu.java                  |   50 +
 rsf-server/src/main/java/menuPda.sql                                                            |   35 
 38 files changed, 2,448 insertions(+), 20 deletions(-)

diff --git a/rsf-admin/src/i18n/core/chineseMessages.js b/rsf-admin/src/i18n/core/chineseMessages.js
index 59a0d7d..1565801 100644
--- a/rsf-admin/src/i18n/core/chineseMessages.js
+++ b/rsf-admin/src/i18n/core/chineseMessages.js
@@ -25,6 +25,7 @@
       save: "淇濆瓨",
       search: "鎼滅储",
       select_all_button: "鍏ㄩ儴閫変腑",
+      select_all: "鍏ㄩ儴閫変腑",
       select_row: "閫変腑涓�琛�",
       show: "鏄剧ず",
       sort: "鎺掑簭",
diff --git a/rsf-admin/src/i18n/en.js b/rsf-admin/src/i18n/en.js
index 2f09aff..d466567 100644
--- a/rsf-admin/src/i18n/en.js
+++ b/rsf-admin/src/i18n/en.js
@@ -219,6 +219,7 @@
         stockStatistic: 'Stock Statistic',
         statisticCount: 'Statistic Count',
         preparation: "澶囨枡鍗�",
+        menuPda: 'MenuPda',
     },
     table: {
         field: {
@@ -311,6 +312,26 @@
                     button: 'Button',
                 }
             },
+            menuPda: {
+                name: "name",
+                parentId: "parent id",
+                parentName: "higher",
+                path: "path",
+                pathName: "pathName",
+                route: "route",
+                component: "component",
+                brief: "brief",
+                code: "code",
+                type: "type",
+                authority: "authority",
+                icon: "icon",
+                sort: "sort",
+                meta: "meta",
+                enums: {
+                    menu: 'Menu',
+                    button: 'Button',
+                }
+            },
             user: {
                 username: "username",
                 password: "password",
diff --git a/rsf-admin/src/i18n/zh.js b/rsf-admin/src/i18n/zh.js
index 6563ae3..0256cf5 100644
--- a/rsf-admin/src/i18n/zh.js
+++ b/rsf-admin/src/i18n/zh.js
@@ -235,6 +235,7 @@
         platform: '骞冲彴绠$悊',
         freeze: '搴撳瓨鍐荤粨',
         transferPoces: '璋冩嫧绠$悊',
+        menuPda: 'PDA鑿滃崟',
     },
     table: {
         field: {
@@ -340,6 +341,26 @@
                     button: '鎸夐挳',
                 }
             },
+            menuPda: {
+                name: "鑿滃崟鍚嶇О",
+                parentId: "涓婄骇鑿滃崟ID",
+                parentName: "涓婄骇鑿滃崟",
+                path: "璺緞",
+                pathName: "璺緞鍚�",
+                route: "璺敱鍦板潃",
+                component: "缁勪欢",
+                brief: "绠�杩�",
+                code: "鏍囪瘑",
+                type: "绫诲瀷",
+                authority: "閴存潈",
+                icon: "鍥炬爣",
+                sort: "鎺掑簭",
+                meta: "鍏�",
+                enums: {
+                    menu: '鑿滃崟',
+                    button: '鎸夐挳',
+                }
+            },
             user: {
                 username: "璐﹀彿",
                 password: "瀵嗙爜",
diff --git a/rsf-admin/src/page/ResourceContent.js b/rsf-admin/src/page/ResourceContent.js
index 07d6451..505e0bf 100644
--- a/rsf-admin/src/page/ResourceContent.js
+++ b/rsf-admin/src/page/ResourceContent.js
@@ -64,6 +64,7 @@
 import inStatisticItem from './statistics/inStockItem';
 import statisticCount from './statistics/stockStatisticNum';
 import preparation from "./orders/preparation";
+import menuPda from './menuPda';
 // import locItem from "./basicInfo/locItem";
 
 const ResourceContent = (node) => {
@@ -174,13 +175,13 @@
       return transfer;
     case "locRevise":
       return locRevise;
-    case "locDeadReport": 
+    case "locDeadReport":
       return locDeadReport;
     case "inStatistic":
       return inStatistic;
-    case "outStatistic": 
+    case "outStatistic":
       return outStatistic;
-    case "outStatisticItem": 
+    case "outStatisticItem":
       return outStatisticItem;
     case "inStatisticItem":
       return inStatisticItem;
@@ -188,6 +189,8 @@
       return statisticCount;
     case "preparation":
       return preparation;
+    case 'menuPda':
+      return menuPda;
     // case "locItem":
     //   return locItem;
     default:
diff --git a/rsf-admin/src/page/menuPda/MenuPdaEdit.jsx b/rsf-admin/src/page/menuPda/MenuPdaEdit.jsx
new file mode 100644
index 0000000..3560161
--- /dev/null
+++ b/rsf-admin/src/page/menuPda/MenuPdaEdit.jsx
@@ -0,0 +1,225 @@
+import React, { useState, useRef, useEffect, useMemo } from "react";
+import {
+    CreateBase,
+    useTranslate,
+    TextInput,
+    NumberInput,
+    BooleanInput,
+    DateInput,
+    SaveButton,
+    SelectInput,
+    ReferenceInput,
+    ReferenceArrayInput,
+    AutocompleteInput,
+    Toolbar,
+    required,
+    useNotify,
+    Form,
+    useUpdate,
+    useCreate,
+    useCreateContext,
+} from 'react-admin';
+import {
+    Dialog,
+    DialogActions,
+    DialogContent,
+    DialogTitle,
+    Stack,
+    Grid,
+    Box,
+} from '@mui/material';
+import DialogCloseButton from "@/page/components/DialogCloseButton";
+import StatusSelectInput from "@/page/components/StatusSelectInput";
+import MemoInput from "@/page/components/MemoInput";
+import TreeSelectInput from "@/page/components/TreeSelectInput";
+
+const EditContent = ({ editRecord }) => {
+    const { resource } = useCreateContext();
+    return (
+        <Grid container rowSpacing={2} columnSpacing={2}>
+            <Grid item xs={6} display="flex" gap={1}>
+                <TreeSelectInput
+                    label="table.field.menu.parentName"
+                    value={editRecord?.parentId}
+                    isTranslate
+                    resource={resource}
+                />
+            </Grid>
+            <Grid item xs={6} display="flex" gap={1}>
+                <TextInput
+                    label="table.field.menu.name"
+                    source="name"
+                    parse={v => v}
+                    validate={required()}
+                />
+            </Grid>
+            <Grid item xs={6} display="flex" gap={1}>
+                <TextInput
+                    label="table.field.menu.route"
+                    source="route"
+                    validate={required()}
+                    parse={v => v}
+                />
+            </Grid>
+            <Grid item xs={6} display="flex" gap={1}>
+                <TextInput
+                    label="table.field.menu.component"
+                    source="component"
+                    parse={v => v}
+                />
+            </Grid>
+            <Grid item xs={6} display="flex" gap={1}>
+                <SelectInput
+                    label="table.field.menu.type"
+                    source="type"
+                    validate={required()}
+                    choices={[
+                        { id: 0, name: 'table.field.menu.enums.menu' },
+                        { id: 1, name: 'table.field.menu.enums.button' },
+                    ]}
+                />
+            </Grid>
+            <Grid item xs={6} display="flex" gap={1}>
+                <TextInput
+                    label="table.field.menu.authority"
+                    source="authority"
+                    parse={v => v}
+                />
+            </Grid>
+            <Grid item xs={6} display="flex" gap={1}>
+                <TextInput
+                    label="table.field.menu.icon"
+                    source="icon"
+                    parse={v => v}
+                />
+            </Grid>
+            <Grid item xs={6} display="flex" gap={1}>
+                <NumberInput
+                    label="table.field.menu.sort"
+                    source="sort"
+                />
+            </Grid>
+
+            <Grid item xs={6} display="flex" gap={1}>
+                <StatusSelectInput />
+            </Grid>
+            <Grid item xs={6} display="flex" gap={1}>
+                <TextInput
+                    label="common.field.memo"
+                    source="memo"
+                    parse={v => v}
+                    fullWidth
+                    multiline
+                    minRows={2}
+                    autoFocus
+                />
+            </Grid>
+        </Grid>
+    )
+}
+
+const MenuPdaEdit = (props) => {
+    const { editRecord, open, setOpen, callback, resource } = props;
+
+    const translate = useTranslate();
+    const notify = useNotify();
+
+    const [update] = useUpdate();
+    const [create] = useCreate();
+
+    const handleClose = (event, reason) => {
+        if (reason !== "backdropClick") {
+            setOpen(false);
+        }
+    };
+
+    const handleSuccess = async (data) => {
+        setOpen(false);
+        callback();
+        notify('common.response.success', { type: 'info' });
+    };
+
+    const handleError = async (data) => {
+        notify('common.response.fail', { type: 'error' });
+    };
+
+    const onSubmit = (data) => {
+        const _params = { ...data };
+        if (editRecord) {
+            if (_params.parentId === editRecord.id) {
+                notify('common.response.dataError', { type: 'error' });
+                return;
+            }
+            update(
+                resource,
+                {
+                    id: editRecord.id,
+                    data: _params,
+                },
+                {
+                    onSuccess: () => {
+                        handleSuccess();
+                    },
+                    onError: (error) => {
+                        handleError();
+                    },
+                }
+            );
+        } else {
+            create(
+                resource,
+                { data: _params },
+                {
+                    onSuccess: () => {
+                        handleSuccess();
+                    },
+                    onError: (error) => {
+                        handleError();
+                    },
+                }
+            );
+        }
+    };
+
+    return (
+        <>
+            <CreateBase>
+                <Dialog
+                    open={open}
+                    onClose={handleClose}
+                    aria-labelledby="form-dialog-title"
+                    fullWidth
+                    disableRestoreFocus
+                    maxWidth="md"   // 'xs' | 'sm' | 'md' | 'lg' | 'xl'
+                >
+                    <Form record={editRecord} onSubmit={onSubmit}>
+                        <DialogTitle id="form-dialog-title" sx={{
+                            position: 'sticky',
+                            top: 0,
+                            backgroundColor: 'background.paper',
+                            zIndex: 1000
+                        }}
+                        >
+                            {editRecord ? translate('update.title') : translate('create.title')}
+                            <Box sx={{ position: 'absolute', top: 8, right: 8, zIndex: 1001 }}>
+                                <DialogCloseButton onClose={handleClose} />
+                            </Box>
+                        </DialogTitle>
+                        <DialogContent sx={{ mt: 2 }}>
+                            <EditContent
+                                editRecord={editRecord}
+                            />
+                        </DialogContent>
+                        <DialogActions sx={{ position: 'sticky', bottom: 0, backgroundColor: 'background.paper', zIndex: 1000 }}>
+                            <Toolbar sx={{ width: '100%', justifyContent: 'space-between' }}  >
+                                <SaveButton />
+                            </Toolbar>
+                        </DialogActions>
+                    </Form>
+                </Dialog>
+            </CreateBase>
+        </>
+    )
+}
+
+export default MenuPdaEdit;
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;
diff --git a/rsf-admin/src/page/menuPda/index.jsx b/rsf-admin/src/page/menuPda/index.jsx
new file mode 100644
index 0000000..7e791c4
--- /dev/null
+++ b/rsf-admin/src/page/menuPda/index.jsx
@@ -0,0 +1,15 @@
+import React, { useState, useRef, useEffect, useMemo } from "react";
+import {
+    ListGuesser,
+    EditGuesser,
+    ShowGuesser,
+} from "react-admin";
+
+import MenuPdaList from "./MenuPdaList";
+
+export default {
+    list: MenuPdaList,
+    recordRepresentation: (record) => {
+        return `${record.name}`
+    }
+};
\ No newline at end of file
diff --git a/rsf-admin/src/page/pdaRoleMenu/PdaRoleMenuCreate.jsx b/rsf-admin/src/page/pdaRoleMenu/PdaRoleMenuCreate.jsx
new file mode 100644
index 0000000..1b9e664
--- /dev/null
+++ b/rsf-admin/src/page/pdaRoleMenu/PdaRoleMenuCreate.jsx
@@ -0,0 +1,125 @@
+import React, { useState, useRef, useEffect, useMemo } from "react";
+import {
+    CreateBase,
+    useTranslate,
+    TextInput,
+    NumberInput,
+    BooleanInput,
+    DateInput,
+    SaveButton,
+    SelectInput,
+    ReferenceInput,
+    ReferenceArrayInput,
+    AutocompleteInput,
+    Toolbar,
+    required,
+    useDataProvider,
+    useNotify,
+    Form,
+    useCreateController,
+} from 'react-admin';
+import {
+    Dialog,
+    DialogActions,
+    DialogContent,
+    DialogTitle,
+    Stack,
+    Grid,
+    Box,
+} from '@mui/material';
+import DialogCloseButton from "../components/DialogCloseButton";
+import StatusSelectInput from "../components/StatusSelectInput";
+import MemoInput from "../components/MemoInput";
+
+const PdaRoleMenuCreate = (props) => {
+    const { open, setOpen } = props;
+
+    const translate = useTranslate();
+    const notify = useNotify();
+
+    const handleClose = (event, reason) => {
+        if (reason !== "backdropClick") {
+            setOpen(false);
+        }
+    };
+
+    const handleSuccess = async (data) => {
+        setOpen(false);
+        notify('common.response.success');
+    };
+
+    const handleError = async (error) => {
+        notify(error.message || 'common.response.fail', { type: 'error', messageArgs: { _: error.message } });
+    };
+
+    return (
+        <>
+            <CreateBase
+                record={{}}
+                transform={(data) => {
+                    return data;
+                }}
+                mutationOptions={{ onSuccess: handleSuccess, onError: handleError }}
+            >
+                <Dialog
+                    open={open}
+                    onClose={handleClose}
+                    aria-labelledby="form-dialog-title"
+                    fullWidth
+                    disableRestoreFocus
+                    maxWidth="md"   // 'xs' | 'sm' | 'md' | 'lg' | 'xl'
+                >
+                    <Form>
+                        <DialogTitle id="form-dialog-title" sx={{
+                            position: 'sticky',
+                            top: 0,
+                            backgroundColor: 'background.paper',
+                            zIndex: 1000
+                        }}
+                        >
+                            {translate('create.title')}
+                            <Box sx={{ position: 'absolute', top: 8, right: 8, zIndex: 1001 }}>
+                                <DialogCloseButton onClose={handleClose} />
+                            </Box>
+                        </DialogTitle>
+                        <DialogContent sx={{ mt: 2 }}>
+                            <Grid container rowSpacing={2} columnSpacing={2}>
+                                <Grid item xs={6} display="flex" gap={1}>
+                                    <NumberInput
+                                        label="table.field.pdaRoleMenu.roleId"
+                                        source="roleId"
+                                        autoFocus
+                                        validate={required()}
+                                    />
+                                </Grid>
+                                <Grid item xs={6} display="flex" gap={1}>
+                                    <NumberInput
+                                        label="table.field.pdaRoleMenu.menuId"
+                                        source="menuId"
+                                        validate={required()}
+                                    />
+                                </Grid>
+
+                                <Grid item xs={6} display="flex" gap={1}>
+                                    <StatusSelectInput />
+                                </Grid>
+                                <Grid item xs={12} display="flex" gap={1}>
+                                    <Stack direction="column" spacing={1} width={'100%'}>
+                                        <MemoInput />
+                                    </Stack>
+                                </Grid>
+                            </Grid>
+                        </DialogContent>
+                        <DialogActions sx={{ position: 'sticky', bottom: 0, backgroundColor: 'background.paper', zIndex: 1000 }}>
+                            <Toolbar sx={{ width: '100%', justifyContent: 'space-between' }}  >
+                                <SaveButton />
+                            </Toolbar>
+                        </DialogActions>
+                    </Form>
+                </Dialog>
+            </CreateBase>
+        </>
+    )
+}
+
+export default PdaRoleMenuCreate;
diff --git a/rsf-admin/src/page/pdaRoleMenu/PdaRoleMenuEdit.jsx b/rsf-admin/src/page/pdaRoleMenu/PdaRoleMenuEdit.jsx
new file mode 100644
index 0000000..7e7d4d8
--- /dev/null
+++ b/rsf-admin/src/page/pdaRoleMenu/PdaRoleMenuEdit.jsx
@@ -0,0 +1,97 @@
+import React, { useState, useRef, useEffect, useMemo } from "react";
+import {
+    Edit,
+    SimpleForm,
+    FormDataConsumer,
+    useTranslate,
+    TextInput,
+    NumberInput,
+    BooleanInput,
+    DateInput,
+    SelectInput,
+    ReferenceInput,
+    ReferenceArrayInput,
+    AutocompleteInput,
+    SaveButton,
+    Toolbar,
+    Labeled,
+    NumberField,
+    required,
+    useRecordContext,
+    DeleteButton,
+} from 'react-admin';
+import { useWatch, useFormContext } from "react-hook-form";
+import { Stack, Grid, Box, Typography } from '@mui/material';
+import * as Common from '@/utils/common';
+import { EDIT_MODE, REFERENCE_INPUT_PAGESIZE } from '@/config/setting';
+import EditBaseAside from "../components/EditBaseAside";
+import CustomerTopToolBar from "../components/EditTopToolBar";
+import MemoInput from "../components/MemoInput";
+import StatusSelectInput from "../components/StatusSelectInput";
+
+const FormToolbar = () => {
+    const { getValues } = useFormContext();
+
+    return (
+        <Toolbar sx={{ justifyContent: 'space-between' }}>
+            <SaveButton />
+            <DeleteButton mutationMode="optimistic" />
+        </Toolbar>
+    )
+}
+
+const PdaRoleMenuEdit = () => {
+    const translate = useTranslate();
+
+    return (
+        <Edit
+            redirect="list"
+            mutationMode={EDIT_MODE}
+            actions={<CustomerTopToolBar />}
+            aside={<EditBaseAside />}
+        >
+            <SimpleForm
+                shouldUnregister
+                warnWhenUnsavedChanges
+                toolbar={<FormToolbar />}
+                mode="onTouched"
+                defaultValues={{}}
+            // validate={(values) => { }}
+            >
+                <Grid container width={{ xs: '100%', xl: '80%' }} rowSpacing={3} columnSpacing={3}>
+                    <Grid item xs={12} md={8}>
+                        <Typography variant="h6" gutterBottom>
+                            {translate('common.edit.title.main')}
+                        </Typography>
+                        <Stack direction='row' gap={2}>
+                            <NumberInput
+                                label="table.field.pdaRoleMenu.roleId"
+                                source="roleId"
+                                autoFocus
+                                validate={required()}
+                            />
+                        </Stack>
+                        <Stack direction='row' gap={2}>
+                            <NumberInput
+                                label="table.field.pdaRoleMenu.menuId"
+                                source="menuId"
+                                validate={required()}
+                            />
+                        </Stack>
+
+                    </Grid>
+                    <Grid item xs={12} md={4}>
+                        <Typography variant="h6" gutterBottom>
+                            {translate('common.edit.title.common')}
+                        </Typography>
+                        <StatusSelectInput />
+                        <Box mt="2em" />
+                        <MemoInput />
+                    </Grid>
+                </Grid>
+            </SimpleForm>
+        </Edit >
+    )
+}
+
+export default PdaRoleMenuEdit;
diff --git a/rsf-admin/src/page/pdaRoleMenu/PdaRoleMenuList.jsx b/rsf-admin/src/page/pdaRoleMenu/PdaRoleMenuList.jsx
new file mode 100644
index 0000000..4271ecd
--- /dev/null
+++ b/rsf-admin/src/page/pdaRoleMenu/PdaRoleMenuList.jsx
@@ -0,0 +1,154 @@
+import React, { useState, useRef, useEffect, useMemo, useCallback } from "react";
+import { useNavigate } from 'react-router-dom';
+import {
+    List,
+    DatagridConfigurable,
+    SearchInput,
+    TopToolbar,
+    SelectColumnsButton,
+    EditButton,
+    FilterButton,
+    CreateButton,
+    ExportButton,
+    BulkDeleteButton,
+    WrapperField,
+    useRecordContext,
+    useTranslate,
+    useNotify,
+    useListContext,
+    FunctionField,
+    TextField,
+    NumberField,
+    DateField,
+    BooleanField,
+    ReferenceField,
+    TextInput,
+    DateTimeInput,
+    DateInput,
+    SelectInput,
+    NumberInput,
+    ReferenceInput,
+    ReferenceArrayInput,
+    AutocompleteInput,
+    DeleteButton,
+} from 'react-admin';
+import { Box, Typography, Card, Stack } from '@mui/material';
+import { styled } from '@mui/material/styles';
+import PdaRoleMenuCreate from "./PdaRoleMenuCreate";
+import PdaRoleMenuPanel from "./PdaRoleMenuPanel";
+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';
+
+const StyledDatagrid = styled(DatagridConfigurable)(({ theme }) => ({
+    '& .css-1vooibu-MuiSvgIcon-root': {
+        height: '.9em'
+    },
+    '& .RaDatagrid-row': {
+        cursor: 'auto'
+    },
+    '& .column-name': {
+    },
+    '& .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 />,
+
+    <NumberInput source="roleId" label="table.field.pdaRoleMenu.roleId" />,
+    <NumberInput source="menuId" label="table.field.pdaRoleMenu.menuId" />,
+
+    <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 PdaRoleMenuList = () => {
+    const translate = useTranslate();
+
+    const [createDialog, setCreateDialog] = useState(false);
+    const [drawerVal, setDrawerVal] = useState(false);
+
+    return (
+        <Box display="flex">
+            <List
+                sx={{
+                    flexGrow: 1,
+                    transition: (theme) =>
+                        theme.transitions.create(['all'], {
+                            duration: theme.transitions.duration.enteringScreen,
+                        }),
+                    marginRight: !!drawerVal ? `${PAGE_DRAWER_WIDTH}px` : 0,
+                }}
+                title={"menu.pdaRoleMenu"}
+                empty={<EmptyData onClick={() => { setCreateDialog(true) }} />}
+                filters={filters}
+                sort={{ field: "create_time", order: "desc" }}
+                actions={(
+                    <TopToolbar>
+                        <FilterButton />
+                        <MyCreateButton onClick={() => { setCreateDialog(true) }} />
+                        <SelectColumnsButton preferenceKey='pdaRoleMenu' />
+                        <MyExportButton />
+                    </TopToolbar>
+                )}
+                perPage={DEFAULT_PAGE_SIZE}
+            >
+                <StyledDatagrid
+                    preferenceKey='pdaRoleMenu'
+                    bulkActionButtons={() => <BulkDeleteButton mutationMode={OPERATE_MODE} />}
+                    rowClick={(id, resource, record) => false}
+                    expand={() => <PdaRoleMenuPanel />}
+                    expandSingle={true}
+                    omit={['id', 'createTime', 'createBy', 'memo']}
+                >
+                    <NumberField source="id" />
+                    <NumberField source="roleId" label="table.field.pdaRoleMenu.roleId" />
+                    <NumberField source="menuId" label="table.field.pdaRoleMenu.menuId" />
+
+                    <ReferenceField source="updateBy" label="common.field.updateBy" reference="user" link={false} sortable={false}>
+                        <TextField source="nickname" />
+                    </ReferenceField>
+                    <DateField source="updateTime" label="common.field.updateTime" showTime />
+                    <ReferenceField source="createBy" label="common.field.createBy" reference="user" link={false} sortable={false}>
+                        <TextField source="nickname" />
+                    </ReferenceField>
+                    <DateField source="createTime" label="common.field.createTime" showTime />
+                    <BooleanField source="statusBool" label="common.field.status" sortable={false} />
+                    <TextField source="memo" label="common.field.memo" sortable={false} />
+                    <WrapperField cellClassName="opt" label="common.field.opt">
+                        <EditButton sx={{ padding: '1px', fontSize: '.75rem' }} />
+                        <DeleteButton sx={{ padding: '1px', fontSize: '.75rem' }} mutationMode={OPERATE_MODE} />
+                    </WrapperField>
+                </StyledDatagrid>
+            </List>
+            <PdaRoleMenuCreate
+                open={createDialog}
+                setOpen={setCreateDialog}
+            />
+            <PageDrawer
+                title='PdaRoleMenu Detail'
+                drawerVal={drawerVal}
+                setDrawerVal={setDrawerVal}
+            >
+            </PageDrawer>
+        </Box>
+    )
+}
+
+export default PdaRoleMenuList;
diff --git a/rsf-admin/src/page/pdaRoleMenu/PdaRoleMenuPanel.jsx b/rsf-admin/src/page/pdaRoleMenu/PdaRoleMenuPanel.jsx
new file mode 100644
index 0000000..525ab23
--- /dev/null
+++ b/rsf-admin/src/page/pdaRoleMenu/PdaRoleMenuPanel.jsx
@@ -0,0 +1,63 @@
+import React, { useState, useRef, useEffect, useMemo } from "react";
+import { Box, Card, CardContent, Grid, Typography, Tooltip } from '@mui/material';
+import {
+    useTranslate,
+    useRecordContext,
+} from 'react-admin';
+import PanelTypography from "../components/PanelTypography";
+import * as Common from '@/utils/common'
+
+const PdaRoleMenuPanel = () => {
+    const record = useRecordContext();
+    if (!record) return null;
+    const translate = useTranslate();
+    return (
+        <>
+            <Card sx={{ width: { xs: 300, sm: 500, md: 600, lg: 800 }, margin: 'auto' }}>
+                <CardContent>
+                    <Grid container spacing={2}>
+                        <Grid item xs={12} sx={{ display: 'flex', justifyContent: 'space-between' }}>
+                            <Typography variant="h6" gutterBottom align="left" sx={{
+                                maxWidth: { xs: '100px', sm: '180px', md: '260px', lg: '360px' },
+                                whiteSpace: 'nowrap',
+                                overflow: 'hidden',
+                                textOverflow: 'ellipsis',
+                            }}>
+                                {Common.camelToPascalWithSpaces(translate('table.field.pdaRoleMenu.id'))}: {record.id}
+                            </Typography>
+                            {/*  inherit, primary, secondary, textPrimary, textSecondary, error */}
+                            <Typography variant="h6" gutterBottom align="right" >
+                                ID: {record.id}
+                            </Typography>
+                        </Grid>
+                    </Grid>
+                    <Grid container spacing={2}>
+                        <Grid item xs={12} container alignContent="flex-end">
+                            <Typography variant="caption" color="textSecondary" sx={{ wordWrap: 'break-word', wordBreak: 'break-all' }}>
+                                {Common.camelToPascalWithSpaces(translate('common.field.memo'))}:{record.memo}
+                            </Typography>
+                        </Grid>
+                    </Grid>
+                    <Box height={20}>&nbsp;</Box>
+                    <Grid container spacing={2}>
+                        <Grid item xs={6}>
+                            <PanelTypography
+                                title="table.field.pdaRoleMenu.roleId" 
+                                property={record.roleId}
+                            />
+                        </Grid>
+                        <Grid item xs={6}>
+                            <PanelTypography
+                                title="table.field.pdaRoleMenu.menuId" 
+                                property={record.menuId}
+                            />
+                        </Grid>
+
+                    </Grid>
+                </CardContent>
+            </Card >
+        </>
+    );
+};
+
+export default PdaRoleMenuPanel;
diff --git a/rsf-admin/src/page/pdaRoleMenu/index.jsx b/rsf-admin/src/page/pdaRoleMenu/index.jsx
new file mode 100644
index 0000000..af3c1fc
--- /dev/null
+++ b/rsf-admin/src/page/pdaRoleMenu/index.jsx
@@ -0,0 +1,18 @@
+import React, { useState, useRef, useEffect, useMemo } from "react";
+import {
+    ListGuesser,
+    EditGuesser,
+    ShowGuesser,
+} from "react-admin";
+
+import PdaRoleMenuList from "./PdaRoleMenuList";
+import PdaRoleMenuEdit from "./PdaRoleMenuEdit";
+
+export default {
+    list: PdaRoleMenuList,
+    edit: PdaRoleMenuEdit,
+    show: ShowGuesser,
+    recordRepresentation: (record) => {
+        return `${record.id}`
+    }
+};
diff --git a/rsf-admin/src/page/system/role/AssignPermissions_pda.jsx b/rsf-admin/src/page/system/role/AssignPermissions_pda.jsx
new file mode 100644
index 0000000..7d6989c
--- /dev/null
+++ b/rsf-admin/src/page/system/role/AssignPermissions_pda.jsx
@@ -0,0 +1,380 @@
+import React, { useState, useRef, useEffect, useMemo, useCallback } from "react";
+import {
+    useTranslate,
+    useNotify,
+    TextInput
+} from 'react-admin';
+import { Box, Button, Card, Stack, CardContent, Skeleton, TextField } from '@mui/material';
+import { SimpleTreeView, TreeItem, RichTreeView, useTreeViewApiRef } from '@mui/x-tree-view';
+import SaveIcon from '@mui/icons-material/Save';
+import request from '@/utils/request'
+
+const DEFAULT_EXPAND_ALL = true;
+
+const AssignPermissionsPda = (props) => {
+    const { role, originMenuIds, setDrawerVal, closeCallback, authType } = props;
+    const translate = useTranslate();
+    const notify = useNotify();
+
+    const [loading, setLoading] = useState(false);
+    const [treeData, setTreeData] = useState([]);
+    const [selectedItems, setSelectedItems] = useState([]);
+    const [expandedItems, setExpandedItems] = useState([]);
+    const [parmas, setParmas] = useState({ condition: '', authType: authType });
+    const toggledItemRef = useRef({});
+    const apiRef = useTreeViewApiRef();
+
+    useEffect(() => {
+        reload()
+    }, [role, originMenuIds])
+
+    const reload = () => {
+        setSelectedItems(originMenuIds.map(item => item + ""));
+
+        const transformTree = (treeData) => {
+            return treeData.map(data => {
+                return {
+                    id: data.id + '',
+                    label: data.type === 0 ? translate(data.name || data.code) : data.name || data.code,
+                    type: data.type,
+                    children: (data.children && data.children.length > 0 ? transformTree(data.children) : null)
+                }
+            })
+        }
+        const http = async () => {
+            const res = await request.post('/menuPda/tree', parmas);
+            if (res?.data?.code === 200) {
+                const transformData = transformTree(res.data.data);
+                setTreeData(transformData);
+                if (DEFAULT_EXPAND_ALL) {
+                    setExpandedItems(getAllItemsWithChildrenItemIds(transformData));
+                }
+            } else {
+                notify(res.data.msg, { type: 'error' });
+            }
+            setLoading(false);
+        }
+        setLoading(true);
+        setTimeout(() => {
+            http();
+        }, 200);
+    }
+
+
+    const getAllItemItemIds = () => {
+        const ids = [];
+        const registerItemId = (item) => {
+            ids.push(item.id);
+            item.children?.forEach(registerItemId);
+        };
+        treeData.forEach(registerItemId);
+        return ids;
+    };
+
+    const getAllItemsWithChildrenItemIds = (treeDataHandle) => {
+        const itemIds = [];
+        const registerItemId = (item) => {
+            if (item.children?.length) {
+                itemIds.push(item.id);
+                item.children.forEach(registerItemId);
+            }
+        };
+        if (treeDataHandle) {
+            treeDataHandle.forEach(registerItemId);
+        } else {
+            treeData.forEach(registerItemId);
+        }
+        return itemIds;
+    };
+
+    const handleSelectedItemsChange = (event, newSelectedItems) => {
+        const itemsToSelect = [];
+        const itemsToUnSelect = {};
+        Object.entries(toggledItemRef.current).forEach(([itemId, isSelected]) => {
+            const item = apiRef.current.getItem(itemId);
+            if (isSelected) {
+                itemsToSelect.push(...getItemDescendantsIds(item));
+
+                const parentIds = getParentIds(treeData, itemId);
+                itemsToSelect.push(...parentIds);
+            } else {
+                // 鍙栨秷瀛愯妭鐐�
+                const treeNode = checkoutTreeNode(treeData, itemId);
+                if (treeNode?.children && treeNode?.children.length > 0) {
+                    const allChildren = getItemDescendantsIds(treeNode);
+                    const childrenSet = new Set(allChildren);
+                    newSelectedItems = newSelectedItems.filter(item => !childrenSet.has(item));
+                }
+
+                // 鍙栨秷鐖惰妭鐐�
+                const removeParentIfAllSiblingsDeselected = (itemId, newSelectedItems, treeData) => {
+                    let updatedSelectedItems = [...newSelectedItems];
+                    let currentId = itemId;
+                    while (true) {
+                        const parentId = getParentId(treeData, currentId);
+                        if (!parentId) break;
+                        const siblings = getChildrenIds(treeData, parentId);
+                        const allSiblingsDeselected = siblings.every(siblingId => !updatedSelectedItems.includes(siblingId));
+                        if (allSiblingsDeselected) {
+                            updatedSelectedItems = updatedSelectedItems.filter(id => id !== parentId);
+                            currentId = parentId;
+                        } else {
+                            break;
+                        }
+                    }
+                    return updatedSelectedItems;
+                };
+                newSelectedItems = removeParentIfAllSiblingsDeselected(itemId, newSelectedItems, treeData);
+            }
+        });
+
+        const newSelectedItemsWithChildren = Array.from(
+            new Set(
+                [...newSelectedItems, ...itemsToSelect].filter(
+                    (itemId) => !itemsToUnSelect[itemId],
+                ),
+            ),
+        );
+
+        setSelectedItems(newSelectedItemsWithChildren);
+
+        toggledItemRef.current = {};
+    };
+
+    const handleSave = (event) => {
+        request.post('/rolePda/scope/update', {
+            id: role.id,
+            menuIds: {
+                checked: selectedItems,
+                halfChecked: []
+            }
+        }).then(res => {
+            if (res?.data.code === 200) {
+                setDrawerVal(null);
+                if (closeCallback) {
+                    closeCallback();
+                }
+                notify(res.data.msg, { type: 'info', messageArgs: { _: res.data.msg } })
+            } else {
+                notify(res.data.msg, { type: 'error' })
+            }
+
+        })
+    }
+
+    const search = (e) => {
+        const value = e.target.value;
+        setParmas({
+            ...parmas,
+            condition: value
+        })
+        reload()
+
+    }
+
+    return (
+        <>
+            <Card sx={{
+                ml: 1,
+                mr: 1,
+                height: 'calc(100vh - 140px)',
+                overflowY: 'auto'
+            }}>
+                <CardContent sx={{
+                    overflow: 'auto',
+                    display: 'flex',
+                    flexDirection: 'column',
+                    justifyContent: 'space-between'
+                }}>
+                    <Box>
+                        <Box mb={1} sx={{
+                            display: 'flex',
+                            justifyContent: 'space-between'
+                        }}>
+                            <Button onClick={() => {
+                                setSelectedItems((oldSelected) =>
+                                    oldSelected.length === 0 ? getAllItemItemIds() : [],
+                                );
+                            }}>
+                                {selectedItems.length === 0 ? translate('ra.action.select_all') : translate('ra.action.unselect')}
+                            </Button>
+                            <Button onClick={() => {
+                                setExpandedItems((oldExpanded) =>
+                                    oldExpanded.length === 0 ? getAllItemsWithChildrenItemIds() : [],
+                                );
+                            }}>
+                                {expandedItems.length === 0 ? translate('common.action.expandAll') : translate('common.action.collapseAll')}
+                            </Button>
+                        </Box>
+                        <Box sx={{
+                            display: 'flex',
+                            justifyContent: 'space-between',
+                            alignItems: 'center'
+                        }}>
+                            <TextField sx={{ width: '200px' }} label="鎼滅储鑿滃崟" variant="outlined" value={parmas.condition} onChange={(e) => search(e)} />
+                            <Button startIcon={<SaveIcon />} size="small" variant="contained" onClick={handleSave} sx={{ height: '40px' }}>
+                                {translate('ra.action.save')}
+                            </Button>
+                        </Box>
+
+                        <Box sx={{
+                            minWidth: 290,
+                            overflow: 'auto',
+                            marginTop: '10px',
+                            padding: 1,
+                            borderBottom: '1px solid background.paper',
+                            borderRadius: '4px',
+                            boxShadow: '0 1px 2px rgba(0, 0, 0, 0.2)',
+                            backgroundColor: 'background.paper',
+                        }}>
+                            {loading ? (
+                                <SkeletonBox />
+                            ) : (
+                                <RichTreeView
+                                    multiSelect
+                                    checkboxSelection
+                                    apiRef={apiRef}
+                                    items={treeData}
+                                    selectedItems={selectedItems}
+                                    onSelectedItemsChange={handleSelectedItemsChange}
+                                    onItemSelectionToggle={(event, itemId, isSelected) => {
+                                        toggledItemRef.current[itemId] = isSelected;
+                                    }}
+
+                                    expandedItems={expandedItems}
+                                    onExpandedItemsChange={(event, itemIds) => {
+                                        setExpandedItems(itemIds);
+                                    }}
+                                />
+                            )}
+
+                        </Box>
+                    </Box>
+
+                </CardContent>
+            </Card>
+        </>
+    )
+}
+
+const checkoutTreeNode = (treeData, targetId) => {
+    let result = null;
+    const checkout = (node) => {
+        if (node.id === targetId) {
+            result = node;
+            return true;
+        } else {
+            if (node.children) {
+                for (const child of node.children) {
+                    if (checkout(child)) {
+                        return true;
+                    }
+                }
+            }
+        }
+        return false;
+    };
+    treeData.forEach(item => {
+        if (checkout(item)) {
+            return;
+        }
+    });
+    return result;
+};
+
+const getItemDescendantsIds = (item) => {
+    const ids = [];
+    item.children?.forEach((child) => {
+        ids.push(child.id);
+        ids.push(...getItemDescendantsIds(child));
+    });
+    return ids;
+}
+
+const getParentIds = (tree, targetId) => {
+    let parentIds = [];
+    const searchTree = (node, path = []) => {
+        if (node.id === targetId) {
+            parentIds = [...path];
+            return true;
+        }
+        if (node.children) {
+            for (const child of node.children) {
+                if (searchTree(child, [...path, node.id])) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    };
+    tree.forEach(item => {
+        searchTree(item);
+    })
+    return parentIds;
+};
+
+const getParentId = (tree, targetId) => {
+    let parentId = null;
+    const searchTree = (node) => {
+        if (node.children) {
+            for (const child of node.children) {
+                if (child.id === targetId) {
+                    parentId = node.id;
+                    return true;
+                }
+                if (searchTree(child)) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    };
+    tree.forEach(item => {
+        if (searchTree(item)) {
+            return parentId;
+        }
+    });
+    return parentId;
+};
+
+const getChildrenIds = (tree, targetId) => {
+    let childrenIds = [];
+    const searchTree = (node) => {
+        if (node.id === targetId && node.children) {
+            childrenIds = node.children.map(child => child.id);
+        } else if (node.children) {
+            for (const child of node.children) {
+                searchTree(child);
+            }
+        }
+    };
+    tree.forEach(item => {
+        searchTree(item);
+    });
+    return childrenIds;
+};
+
+const SkeletonBox = () => {
+    return (
+        <Stack spacing={1}>
+            <Skeleton variant="rounded" width={200} height={20} />
+            <Skeleton variant="rounded" width={200} height={20} style={{ marginLeft: '50px' }} />
+            <Skeleton variant="rounded" width={200} height={20} style={{ marginLeft: '50px' }} />
+            <Skeleton variant="rounded" width={200} height={20} style={{ marginLeft: '50px' }} />
+            <Skeleton variant="rounded" width={200} height={20} />
+            <Skeleton variant="rounded" width={200} height={20} style={{ marginLeft: '50px' }} />
+            <Skeleton variant="rounded" width={200} height={20} style={{ marginLeft: '50px' }} />
+            <Skeleton variant="rounded" width={200} height={20} style={{ marginLeft: '50px' }} />
+            <Skeleton variant="rounded" width={200} height={20} />
+            <Skeleton variant="rounded" width={200} height={20} style={{ marginLeft: '50px' }} />
+            <Skeleton variant="rounded" width={200} height={20} style={{ marginLeft: '50px' }} />
+            <Skeleton variant="rounded" width={200} height={20} style={{ marginLeft: '50px' }} />
+            <Skeleton variant="rounded" width={200} height={20} />
+            <Skeleton variant="rounded" width={200} height={20} style={{ marginLeft: '50px' }} />
+            <Skeleton variant="rounded" width={200} height={20} style={{ marginLeft: '50px' }} />
+            <Skeleton variant="rounded" width={200} height={20} style={{ marginLeft: '50px' }} />
+        </Stack>
+    )
+}
+
+export default AssignPermissionsPda;
\ No newline at end of file
diff --git a/rsf-admin/src/page/system/role/RoleList.jsx b/rsf-admin/src/page/system/role/RoleList.jsx
index 1973c7d..6ef91f5 100644
--- a/rsf-admin/src/page/system/role/RoleList.jsx
+++ b/rsf-admin/src/page/system/role/RoleList.jsx
@@ -42,8 +42,10 @@
 import { PAGE_DRAWER_WIDTH, OPERATE_MODE, DEFAULT_PAGE_SIZE } from '@/config/setting';
 import * as Common from '@/utils/common';
 import AssignPermissions from "./AssignPermissions";
+import AssignPermissionsPda from "./AssignPermissions_pda";
 import request from '@/utils/request';
 import AssignmentIndIcon from '@mui/icons-material/AssignmentInd';
+import AdUnitsIcon from '@mui/icons-material/AdUnits';
 
 const StyledDatagrid = styled(DatagridConfigurable)(({ theme }) => ({
     '& .css-1vooibu-MuiSvgIcon-root': {
@@ -80,10 +82,11 @@
 
     const [createDialog, setCreateDialog] = useState(false);
     const [drawerVal, setDrawerVal] = useState(false);
+    const [drawerValPda, setDrawerValPda] = useState(false);
 
     const [menuIds, setMenuIds] = useState([]);
 
-    const [authType,setAuthType] = useState(0)
+    const [authType, setAuthType] = useState(0)
 
     const assign = (record) => {
         request('/role/scope/list', {
@@ -102,6 +105,23 @@
         });
     }
 
+    const assignPda = (record) => {
+        request('/rolePda/scope/list', {
+            method: 'GET',
+            params: {
+                roleId: record.id
+            }
+        }).then((res) => {
+            if (res?.data?.code === 200) {
+                const { data: menuIds } = res.data;
+                setMenuIds(menuIds || []);
+                setDrawerValPda(!!drawerValPda && drawerValPda === record ? null : record);
+            } else {
+                notify(res.data.msg, { type: 'error' });
+            }
+        });
+    }
+
     return (
         <Box display="flex">
             <List
@@ -111,7 +131,7 @@
                         theme.transitions.create(['all'], {
                             duration: theme.transitions.duration.enteringScreen,
                         }),
-                    marginRight: !!drawerVal ? `${PAGE_DRAWER_WIDTH}px` : 0,
+                    marginRight: (!!drawerVal || !!drawerValPda) ? `${PAGE_DRAWER_WIDTH}px` : 0,
                 }}
                 title={"menu.role"}
                 empty={<EmptyData onClick={() => { setCreateDialog(true) }} />}
@@ -131,7 +151,7 @@
                     preferenceKey='role'
                     bulkActionButtons={() => <BulkDeleteButton mutationMode={OPERATE_MODE} />}
                     rowClick={(id, resource, record) => false}
-                    omit={['id', 'createTime', 'memo','statusBool']}
+                    omit={['id', 'createTime', 'memo', 'statusBool']}
                 >
                     <NumberField source="id" />
                     <MyField source="name" label="table.field.role.name"
@@ -148,7 +168,7 @@
                     <TextField source="memo" label="common.field.memo" sortable={false} />
                     <WrapperField cellClassName="opt" label="common.field.opt">
                         <ScopeButton sx={{ padding: '1px', fontSize: '.75rem' }} assign={assign} auType={0} setAuthType={setAuthType} label="缃戦〉鏉冮檺&nbsp;&nbsp;&nbsp;" />
-                        <ScopeButton sx={{ padding: '1px', fontSize: '.75rem' }} assign={assign} auType={1} setAuthType={setAuthType} label="PDA鏉冮檺&nbsp;&nbsp;&nbsp;" />
+                        <PdaScopeButton sx={{ padding: '1px', fontSize: '.75rem' }} assignPda={assignPda} auType={1} setAuthType={setAuthType} label="PDA鏉冮檺&nbsp;&nbsp;&nbsp;" />
                         <ScopeButton sx={{ padding: '1px', fontSize: '.75rem' }} assign={assign} auType={2} setAuthType={setAuthType} label="浠撳簱鏉冮檺&nbsp;" />
                         <EditButton sx={{ padding: '1px', fontSize: '.75rem' }} />
                         <DeleteButton sx={{ padding: '1px', fontSize: '.75rem' }} mutationMode={OPERATE_MODE} />
@@ -174,7 +194,25 @@
                     closeCallback={() => {
                         setMenuIds([]);
                     }}
-                    authType = {authType}
+                    authType={authType}
+                />
+            </PageDrawer>
+            <PageDrawer
+                drawerVal={drawerValPda}
+                setDrawerVal={setDrawerValPda}
+                title={!!drawerValPda ? `Scope by ${drawerValPda.code || drawerValPda.name}` : 'Role Detail'}
+                closeCallback={() => {
+                    setMenuIds([]);
+                }}
+            >
+                <AssignPermissionsPda
+                    role={drawerValPda}
+                    originMenuIds={menuIds}
+                    setDrawerVal={setDrawerValPda}
+                    closeCallback={() => {
+                        setMenuIds([]);
+                    }}
+                    authType={authType}
                 />
             </PageDrawer>
         </Box>
@@ -183,7 +221,7 @@
 
 const ScopeButton = (props) => {
     const record = useRecordContext();
-    const { assign, auType, setAuthType, label, ...rest } = props;    
+    const { assign, auType, setAuthType, label, ...rest } = props;
     return (
         <Button
             variant="text"
@@ -200,4 +238,23 @@
     )
 }
 
+const PdaScopeButton = (props) => {
+    const record = useRecordContext();
+    const { assignPda, auType, setAuthType, label, ...rest } = props;
+    return (
+        <Button
+            variant="text"
+            color="primary"
+            startIcon={<AdUnitsIcon />}
+            label={label}
+            onClick={(event) => {
+                setAuthType(auType);
+                event.stopPropagation();
+                assignPda(record);
+            }}
+            {...rest}
+        />
+    )
+}
+
 export default RoleList;
diff --git a/rsf-open-api/src/main/resources/application-dev.yml b/rsf-open-api/src/main/resources/application-dev.yml
index f7df34c..c4ba7ad 100644
--- a/rsf-open-api/src/main/resources/application-dev.yml
+++ b/rsf-open-api/src/main/resources/application-dev.yml
@@ -14,8 +14,8 @@
 #    url: jdbc:mysql://47.76.147.249:3306/rsf?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai
 #    username: rsf
     username: root
-    url: jdbc:mysql://127.0.0.1:3306/rsf?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai
-    password: 34821015
+    url: jdbc:mysql://192.168.4.15:3306/rsf_20250106?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai
+    password: 1234
 #    url: jdbc:mysql://10.10.10.200:3306/rsf?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai
 #    password: xltys1995
     type: com.alibaba.druid.pool.DruidDataSource
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/pda/InBoundController.java b/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/pda/InBoundController.java
new file mode 100644
index 0000000..d3a69e6
--- /dev/null
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/api/controller/pda/InBoundController.java
@@ -0,0 +1,34 @@
+package com.vincent.rsf.server.api.controller.pda;
+
+import com.vincent.rsf.framework.common.R;
+import com.vincent.rsf.server.api.entity.params.PdaGeneralParam;
+import com.vincent.rsf.server.api.service.InBoundService;
+import com.vincent.rsf.server.system.controller.BaseController;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.Map;
+
+@Api(tags = "PDA鍏ュ簱鎿嶄綔鎺ュ彛")
+@RequestMapping("/pda")
+@RestController
+public class InBoundController extends BaseController {
+
+    @Autowired
+    private InBoundService inBoundService;
+
+    @PreAuthorize("hasAuthority('manager:task:list')")
+    @PostMapping("/in/emptyContainer/warehousing")
+    @ApiOperation("绌哄鍣ㄥ叆搴�")
+    public R emptyContainerWarehousing(@RequestBody PdaGeneralParam param) {
+
+        return R.ok();
+    }
+
+}
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/api/entity/params/PdaGeneralParam.java b/rsf-server/src/main/java/com/vincent/rsf/server/api/entity/params/PdaGeneralParam.java
new file mode 100644
index 0000000..0ad33e4
--- /dev/null
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/api/entity/params/PdaGeneralParam.java
@@ -0,0 +1,15 @@
+package com.vincent.rsf.server.api.entity.params;
+
+import lombok.Data;
+import lombok.experimental.Accessors;
+
+@Data
+@Accessors(chain = true)
+public class PdaGeneralParam {
+
+    // 瀹瑰櫒鍙�
+    private String containerNo;
+    // 鎺ラ┏绔欏彿
+    private String transferStationNo;
+
+}
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/api/service/InBoundService.java b/rsf-server/src/main/java/com/vincent/rsf/server/api/service/InBoundService.java
new file mode 100644
index 0000000..927631a
--- /dev/null
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/api/service/InBoundService.java
@@ -0,0 +1,10 @@
+package com.vincent.rsf.server.api.service;
+
+import com.vincent.rsf.framework.common.R;
+
+/**
+ * PDA鍏ュ簱鎿嶄綔Service鎺ュ彛
+ */
+public interface InBoundService {
+
+}
\ No newline at end of file
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/api/service/impl/InBoundServiceImpl.java b/rsf-server/src/main/java/com/vincent/rsf/server/api/service/impl/InBoundServiceImpl.java
new file mode 100644
index 0000000..4ce52c2
--- /dev/null
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/api/service/impl/InBoundServiceImpl.java
@@ -0,0 +1,14 @@
+package com.vincent.rsf.server.api.service.impl;
+
+import com.vincent.rsf.server.api.service.InBoundService;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+
+/**
+ * PDA鍏ュ簱鎿嶄綔Service瀹炵幇绫�
+ */
+@Slf4j
+@Service
+public class InBoundServiceImpl implements InBoundService {
+
+}
\ No newline at end of file
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/common/CodeBuilder.java b/rsf-server/src/main/java/com/vincent/rsf/server/common/CodeBuilder.java
index e8bbef4..cd880a0 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/common/CodeBuilder.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/common/CodeBuilder.java
@@ -15,15 +15,15 @@
         generator.frontendPrefixPath = "rsf-admin/";
 
         generator.sqlOsType = SqlOsType.MYSQL;
-        generator.url = "192.168.4.151:3306/rsf";
+        generator.url = "192.168.4.15:3306/rsf_20250106";
         generator.username = "root";
-        generator.password = "34821015";
+        generator.password = "1234";
 //        generator.url="47.97.1.152:51433;databasename=jkasrs";
 //        generator.username="sa";
 //        generator.password="Zoneyung@zy56$";
 
-        generator.table = "view_stock_statistic";
-        generator.tableDesc = "鏃ュ簱瀛樼粺璁�";
+        generator.table = "sys_pda_role_menu";
+        generator.tableDesc = "PDA鏉冮檺";
         generator.packagePath = "com.vincent.rsf.server.manager";
 
         generator.build();
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/common/config/MybatisPlusConfig.java b/rsf-server/src/main/java/com/vincent/rsf/server/common/config/MybatisPlusConfig.java
index 70da919..056b4a7 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/common/config/MybatisPlusConfig.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/common/config/MybatisPlusConfig.java
@@ -52,6 +52,8 @@
                         "sys_user_role",
                         "sys_role_menu",
                         "sys_menu",
+                        "sys_pda_role_menu",
+                        "sys_menu_pda",
                         "man_loc_type_rela",
                         "man_qly_inspect_result",
                         "view_stock_manage",
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/manager/controller/MenuPdaController.java b/rsf-server/src/main/java/com/vincent/rsf/server/manager/controller/MenuPdaController.java
new file mode 100644
index 0000000..e46fcd6
--- /dev/null
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/manager/controller/MenuPdaController.java
@@ -0,0 +1,157 @@
+package com.vincent.rsf.server.manager.controller;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.vincent.rsf.common.utils.Utils;
+import com.vincent.rsf.framework.common.Cools;
+import com.vincent.rsf.framework.common.R;
+import com.vincent.rsf.server.common.utils.ExcelUtil;
+import com.vincent.rsf.server.common.annotation.OperationLog;
+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.utils.NodeUtils;
+import com.vincent.rsf.server.manager.entity.MenuPda;
+import com.vincent.rsf.server.manager.service.MenuPdaService;
+import com.vincent.rsf.server.system.controller.BaseController;
+import com.vincent.rsf.server.system.entity.Menu;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+import javax.servlet.http.HttpServletResponse;
+import java.util.*;
+
+@RestController
+public class MenuPdaController extends BaseController {
+
+    @Autowired
+    private MenuPdaService menuPdaService;
+
+    @PreAuthorize("hasAuthority('manager:menuPda:list')")
+    @PostMapping("/menuPda/page")
+    public R page(@RequestBody Map<String, Object> map) {
+        BaseParam baseParam = buildParam(map, BaseParam.class);
+        PageParam<MenuPda, BaseParam> pageParam = new PageParam<>(baseParam, MenuPda.class);
+        return R.ok().add(menuPdaService.page(pageParam, pageParam.buildWrapper(true)));
+    }
+
+    @PreAuthorize("hasAuthority('manager:menuPda:list')")
+    @PostMapping("/menuPda/list")
+    public R list(@RequestBody Map<String, Object> map) {
+        return R.ok().add(menuPdaService.list());
+    }
+
+    @PreAuthorize("hasAuthority('manager:menuPda:list')")
+    @PostMapping({"/menuPda/many/{ids}", "/menuPdas/many/{ids}"})
+    public R many(@PathVariable Long[] ids) {
+        return R.ok().add(menuPdaService.listByIds(Arrays.asList(ids)));
+    }
+
+    @PreAuthorize("hasAuthority('manager:menuPda:list')")
+    @GetMapping("/menuPda/{id}")
+    public R get(@PathVariable("id") Long id) {
+        return R.ok().add(menuPdaService.getById(id));
+    }
+
+//    @PreAuthorize("hasAuthority('system:menu:list')")
+    @PostMapping("/menuPda/tree")
+    public R tree(@RequestBody Map<String, Object> map) {
+//        PageParam<Menu, BaseParam> param = new PageParam<>(buildParam(map, BaseParam.class), Menu.class);
+//        QueryWrapper<Menu> wrapper = param.buildWrapper(true, queryWrapper -> queryWrapper.orderByAsc("sort"));
+//        List<Menu> menus = menuService.list(wrapper);
+//        return R.ok().add(Utils.toTreeData(menus, 0L, Menu::getParentId, Menu::getId, Menu::setChildren));
+        List<MenuPda> menuList = menuPdaService.list(new LambdaQueryWrapper<MenuPda>().orderByAsc(MenuPda::getSort));
+        List<MenuPda> treeData = Utils.toTreeData(menuList, 0L, MenuPda::getParentId, MenuPda::getId, MenuPda::setChildren);
+        if (!Cools.isEmpty(map.get("condition"))) {
+            Utils.treeRemove(treeData, String.valueOf(map.get("condition")), MenuPda::getName, MenuPda::getChildren);
+            Utils.treeRemove(treeData, String.valueOf(map.get("condition")), MenuPda::getName, MenuPda::getChildren);
+        }
+        return R.ok().add(treeData);
+    }
+
+    @PreAuthorize("hasAuthority('system:menu:save')")
+    @OperationLog("Save Menu")
+    @PostMapping("/menuPda/save")
+    public R save(@RequestBody MenuPda menu) {
+        if (menu.getParentId() != null && menu.getParentId() > 0) {
+            MenuPda parent = menuPdaService.getById(menu.getParentId());
+            if (parent != null) {
+                menu.setParentName(parent.getName());
+            }
+        } else {
+            menu.setParentId(0L);
+        }
+
+        NodeUtils nodeUtils = new NodeUtils();
+        nodeUtils.generatePath0(item -> menuPdaService.getById(item.getParentId()), menu, MenuPda::getId, MenuPda::getName, MenuPda::getParentId);
+        menu.setPath(nodeUtils.path.toString());
+        menu.setPathName(nodeUtils.pathName.toString());
+
+        menu.setCreateBy(getLoginUserId());
+        menu.setCreateTime(new Date());
+        menu.setUpdateBy(getLoginUserId());
+        menu.setUpdateTime(new Date());
+        if (!menuPdaService.save(menu)) {
+            return R.error("Save Fail");
+        }
+        return R.ok("Save Success").add(menu);
+    }
+
+    @PreAuthorize("hasAuthority('system:menu:update')")
+    @OperationLog("Update Menu")
+    @PostMapping("/menuPda/update")
+    public R update(@RequestBody MenuPda menu) {
+        if (menu.getParentId() != null && menu.getParentId() > 0) {
+            MenuPda parent = menuPdaService.getById(menu.getParentId());
+            if (parent != null) {
+                menu.setParentName(parent.getName());
+            }
+        } else {
+            menu.setParentId(0L);
+        }
+
+        NodeUtils nodeUtils = new NodeUtils();
+        nodeUtils.generatePath0(item -> menuPdaService.getById(item.getParentId()), menu, MenuPda::getId, MenuPda::getName, MenuPda::getParentId);
+        menu.setPath(nodeUtils.path.toString());
+        menu.setPathName(nodeUtils.pathName.toString());
+
+        menu.setUpdateBy(getLoginUserId());
+        menu.setUpdateTime(new Date());
+        if (!menuPdaService.updateById(menu)) {
+            return R.error("Update Fail");
+        }
+        return R.ok("Update Success").add(menu);
+    }
+
+    @PreAuthorize("hasAuthority('manager:menuPda:remove')")
+    @OperationLog("Delete PDA鏉冮檺")
+    @PostMapping("/menuPda/remove/{ids}")
+    public R remove(@PathVariable Long[] ids) {
+        if (!menuPdaService.removeByIds(Arrays.asList(ids))) {
+            return R.error("Delete Fail");
+        }
+        return R.ok("Delete Success").add(ids);
+    }
+
+    @PreAuthorize("hasAuthority('manager:menuPda:list')")
+    @PostMapping("/menuPda/query")
+    public R query(@RequestParam(required = false) String condition) {
+        List<KeyValVo> vos = new ArrayList<>();
+        LambdaQueryWrapper<MenuPda> wrapper = new LambdaQueryWrapper<>();
+        if (!Cools.isEmpty(condition)) {
+            wrapper.like(MenuPda::getName, condition);
+        }
+        menuPdaService.page(new Page<>(1, 30), wrapper).getRecords().forEach(
+                item -> vos.add(new KeyValVo(item.getId(), item.getName()))
+        );
+        return R.ok().add(vos);
+    }
+
+    @PreAuthorize("hasAuthority('manager:menuPda:list')")
+    @PostMapping("/menuPda/export")
+    public void export(@RequestBody Map<String, Object> map, HttpServletResponse response) throws Exception {
+        ExcelUtil.build(ExcelUtil.create(menuPdaService.list(), MenuPda.class), response);
+    }
+
+}
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/manager/entity/MenuPda.java b/rsf-server/src/main/java/com/vincent/rsf/server/manager/entity/MenuPda.java
new file mode 100644
index 0000000..e16825f
--- /dev/null
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/manager/entity/MenuPda.java
@@ -0,0 +1,258 @@
+package com.vincent.rsf.server.manager.entity;
+
+import com.baomidou.mybatisplus.annotation.*;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.vincent.rsf.server.system.entity.Menu;
+import com.vincent.rsf.server.system.entity.Tenant;
+import com.vincent.rsf.server.system.service.TenantService;
+import org.springframework.format.annotation.DateTimeFormat;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+import com.baomidou.mybatisplus.annotation.TableLogic;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import com.vincent.rsf.framework.common.Cools;
+import com.vincent.rsf.framework.common.SpringUtils;
+import com.vincent.rsf.server.system.service.UserService;
+import com.vincent.rsf.server.system.entity.User;
+import org.springframework.security.core.GrantedAuthority;
+
+import java.io.Serializable;
+import java.util.Date;
+import java.util.List;
+
+@Data
+@TableName("sys_menu_pda")
+public class MenuPda implements Serializable, GrantedAuthority {
+
+    private static final long serialVersionUID = 1L;
+
+    public static final int TYPE_MENU = 0; // 鑿滃崟绫诲瀷
+    public static final int TYPE_BTN = 1; // 鎸夐挳绫诲瀷
+
+    /**
+     * ID
+     */
+    @ApiModelProperty(value = "ID")
+    @TableId(value = "id", type = IdType.AUTO)
+    private Long id;
+
+    /**
+     * 鍚嶇О
+     */
+    @ApiModelProperty(value = "鍚嶇О")
+    private String name;
+
+    /**
+     * 涓婄骇鑿滃崟
+     */
+    @ApiModelProperty(value = "涓婄骇鑿滃崟")
+    private Long parentId;
+
+    /**
+     * 涓婄骇鑿滃崟鍚�
+     */
+    @ApiModelProperty(value = "涓婄骇鑿滃崟鍚�")
+    private String parentName;
+
+    /**
+     * 鍏宠仈璺緞
+     */
+    @ApiModelProperty(value = "鍏宠仈璺緞")
+    private String path;
+
+    /**
+     * 鍏宠仈璺緞鍚�
+     */
+    @ApiModelProperty(value = "鍏宠仈璺緞鍚�")
+    private String pathName;
+
+    /**
+     * 璺敱鍦板潃
+     */
+    @ApiModelProperty(value = "璺敱鍦板潃")
+    private String route;
+
+    /**
+     * 椤甸潰缁勪欢
+     */
+    @ApiModelProperty(value = "椤甸潰缁勪欢")
+    private String component;
+
+    /**
+     * 绠�杩�
+     */
+    @ApiModelProperty(value = "绠�杩�")
+    private String brief;
+
+    /**
+     * 鏍囪瘑
+     */
+    @ApiModelProperty(value = "鏍囪瘑")
+    private String code;
+
+    /**
+     * 绫诲瀷 0: 鑿滃崟 1: 鎸夐挳
+     */
+    @ApiModelProperty(value = "绫诲瀷 0: 鑿滃崟  1: 鎸夐挳  ")
+    private Integer type;
+
+    /**
+     * 鏉冮檺鏍囪瘑
+     */
+    @ApiModelProperty(value = "鏉冮檺鏍囪瘑")
+    private String authority;
+
+    /**
+     * 鑿滃崟鍥炬爣
+     */
+    @ApiModelProperty(value = "鑿滃崟鍥炬爣")
+    private String icon;
+
+    /**
+     * 鎺掑簭
+     */
+    @ApiModelProperty(value = "鎺掑簭")
+    private Integer sort;
+
+    /**
+     * 鍏冧俊鎭�
+     */
+    @ApiModelProperty(value = "鍏冧俊鎭�")
+    private String meta;
+
+    /**
+     * 鎵�灞炴満鏋�
+     */
+    @ApiModelProperty(value = "鎵�灞炴満鏋�")
+    private Long tenantId;
+
+    /**
+     * 鐘舵�� 1: 姝e父 0: 绂佺敤
+     */
+    @ApiModelProperty(value = "鐘舵�� 1: 姝e父  0: 绂佺敤  ")
+    private Integer status;
+
+    /**
+     * 鏄惁鍒犻櫎 1: 鏄� 0: 鍚�
+     */
+    @ApiModelProperty(value = "鏄惁鍒犻櫎 1: 鏄�  0: 鍚�  ")
+    @TableLogic
+    private Integer deleted;
+
+    /**
+     * 娣诲姞鏃堕棿
+     */
+    @ApiModelProperty(value = "娣诲姞鏃堕棿")
+    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date createTime;
+
+    /**
+     * 娣诲姞浜哄憳
+     */
+    @ApiModelProperty(value = "娣诲姞浜哄憳")
+    private Long createBy;
+
+    /**
+     * 淇敼鏃堕棿
+     */
+    @ApiModelProperty(value = "淇敼鏃堕棿")
+    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date updateTime;
+
+    /**
+     * 淇敼浜哄憳
+     */
+    @ApiModelProperty(value = "淇敼浜哄憳")
+    private Long updateBy;
+
+    /**
+     * 澶囨敞
+     */
+    @ApiModelProperty(value = "澶囨敞")
+    private String memo;
+
+    @TableField(exist = false)
+    private List<MenuPda> children;
+
+    public MenuPda() {
+    }
+
+    public String getType$() {
+        if (null == this.type) {
+            return null;
+        }
+        switch (this.type) {
+            case 0:
+                return "鑿滃崟";
+            case 1:
+                return "鎸夐挳";
+            default:
+                return String.valueOf(this.type);
+        }
+    }
+
+    public String getTenantId$() {
+        TenantService service = SpringUtils.getBean(TenantService.class);
+        Tenant tenant = service.getById(this.tenantId);
+        if (!Cools.isEmpty(tenant)) {
+            return String.valueOf(tenant.getName());
+        }
+        return null;
+    }
+
+    public String getCreateTime$() {
+        if (Cools.isEmpty(this.createTime)) {
+            return "";
+        }
+        return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(this.createTime);
+    }
+
+    public String getCreateBy$() {
+        UserService service = SpringUtils.getBean(UserService.class);
+        User user = service.getById(this.createBy);
+        if (!Cools.isEmpty(user)) {
+            return String.valueOf(user.getNickname());
+        }
+        return null;
+    }
+
+    public String getUpdateTime$() {
+        if (Cools.isEmpty(this.updateTime)) {
+            return "";
+        }
+        return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(this.updateTime);
+    }
+
+    public String getUpdateBy$() {
+        UserService service = SpringUtils.getBean(UserService.class);
+        User user = service.getById(this.updateBy);
+        if (!Cools.isEmpty(user)) {
+            return String.valueOf(user.getNickname());
+        }
+        return null;
+    }
+
+    public Boolean getStatusBool() {
+        if (null == this.status) {
+            return null;
+        }
+        switch (this.status) {
+            case 1:
+                return true;
+            case 0:
+                return false;
+            default:
+                return null;
+        }
+    }
+
+}
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/manager/mapper/MenuPdaMapper.java b/rsf-server/src/main/java/com/vincent/rsf/server/manager/mapper/MenuPdaMapper.java
new file mode 100644
index 0000000..464428c
--- /dev/null
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/manager/mapper/MenuPdaMapper.java
@@ -0,0 +1,12 @@
+package com.vincent.rsf.server.manager.mapper;
+
+import com.vincent.rsf.server.manager.entity.MenuPda;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.apache.ibatis.annotations.Mapper;
+import org.springframework.stereotype.Repository;
+
+@Mapper
+@Repository
+public interface MenuPdaMapper extends BaseMapper<MenuPda> {
+
+}
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/manager/schedules/CheckOrderSchedules.java b/rsf-server/src/main/java/com/vincent/rsf/server/manager/schedules/CheckOrderSchedules.java
index 12938da..18b8ada 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/manager/schedules/CheckOrderSchedules.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/manager/schedules/CheckOrderSchedules.java
@@ -1,6 +1,5 @@
 package com.vincent.rsf.server.manager.schedules;
 
-
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.vincent.rsf.framework.exception.CoolException;
 import com.vincent.rsf.server.manager.entity.*;
@@ -70,13 +69,16 @@
             return;
         }
         Long loginUserId = SystemAuthUtils.getLoginUserId();
-        Map<Long, List<TaskItem>> taskMps = taskItems.stream().collect(Collectors.groupingBy(TaskItem::getOrderId));
+        Map<Long, List<TaskItem>> taskMps = taskItems.stream()
+                .filter(item -> Objects.nonNull(item.getOrderId()))
+                .collect(Collectors.groupingBy(TaskItem::getOrderId));
         taskMps.keySet().forEach(orderId -> {
             WkOrder order = checkOrderService.getById(orderId);
             if (Objects.isNull(order)) {
                 throw new CoolException("鐩樼偣鍗曟嵁涓嶅瓨鍦紒锛�");
             }
-            CheckDiff checkDiff = checkDiffService.getOne(new LambdaQueryWrapper<CheckDiff>().eq(CheckDiff::getOrderId, orderId));
+            CheckDiff checkDiff = checkDiffService
+                    .getOne(new LambdaQueryWrapper<CheckDiff>().eq(CheckDiff::getOrderId, orderId));
             if (Objects.isNull(checkDiff)) {
                 checkDiff = new CheckDiff();
                 checkDiff.setAnfme(order.getAnfme())
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/manager/schedules/TaskSchedules.java b/rsf-server/src/main/java/com/vincent/rsf/server/manager/schedules/TaskSchedules.java
index eb5e75e..061473a 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/manager/schedules/TaskSchedules.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/manager/schedules/TaskSchedules.java
@@ -237,7 +237,7 @@
     /**
      * 闈炲厜鐢电珯鐐逛换鍔′笅鍙�
      */
-    @Scheduled(cron = "0/5 * * * * ?  ")
+//    @Scheduled(cron = "0/5 * * * * ?  ")
     @Transactional(rollbackFor = Exception.class)
     public void pubTaskToWcs() {
         Long loginUserId = SystemAuthUtils.getLoginUserId();
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/manager/service/MenuPdaService.java b/rsf-server/src/main/java/com/vincent/rsf/server/manager/service/MenuPdaService.java
new file mode 100644
index 0000000..ea61403
--- /dev/null
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/manager/service/MenuPdaService.java
@@ -0,0 +1,8 @@
+package com.vincent.rsf.server.manager.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.vincent.rsf.server.manager.entity.MenuPda;
+
+public interface MenuPdaService extends IService<MenuPda> {
+
+}
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/manager/service/impl/MenuPdaServiceImpl.java b/rsf-server/src/main/java/com/vincent/rsf/server/manager/service/impl/MenuPdaServiceImpl.java
new file mode 100644
index 0000000..b184894
--- /dev/null
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/manager/service/impl/MenuPdaServiceImpl.java
@@ -0,0 +1,12 @@
+package com.vincent.rsf.server.manager.service.impl;
+
+import com.vincent.rsf.server.manager.mapper.MenuPdaMapper;
+import com.vincent.rsf.server.manager.entity.MenuPda;
+import com.vincent.rsf.server.manager.service.MenuPdaService;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import org.springframework.stereotype.Service;
+
+@Service("menuPdaService")
+public class MenuPdaServiceImpl extends ServiceImpl<MenuPdaMapper, MenuPda> implements MenuPdaService {
+
+}
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/PdaRoleMenuController.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/PdaRoleMenuController.java
new file mode 100644
index 0000000..325b1b4
--- /dev/null
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/controller/PdaRoleMenuController.java
@@ -0,0 +1,58 @@
+package com.vincent.rsf.server.system.controller;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.vincent.rsf.framework.common.Cools;
+import com.vincent.rsf.framework.common.R;
+import com.vincent.rsf.framework.exception.CoolException;
+import com.vincent.rsf.server.common.utils.ExcelUtil;
+import com.vincent.rsf.server.common.annotation.OperationLog;
+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.system.controller.param.RoleScopeParam;
+import com.vincent.rsf.server.system.entity.PdaRoleMenu;
+import com.vincent.rsf.server.system.entity.Role;
+import com.vincent.rsf.server.system.entity.RoleMenu;
+import com.vincent.rsf.server.system.service.PdaRoleMenuService;
+import com.vincent.rsf.server.system.controller.BaseController;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.web.bind.annotation.*;
+
+import javax.servlet.http.HttpServletResponse;
+import java.util.*;
+
+@RestController
+public class PdaRoleMenuController extends BaseController {
+
+    @Autowired
+    private PdaRoleMenuService pdaRoleMenuService;
+
+//    @PreAuthorize("hasAuthority('system:role:list')")
+    @GetMapping("/rolePda/scope/list")
+    public R scopeList(@RequestParam Long roleId) {
+        return R.ok().add(pdaRoleMenuService.listStrictlyMenuByRoleId(roleId));
+    }
+
+    @PreAuthorize("hasAuthority('system:role:update')")
+    @OperationLog("Assign Permissions")
+    @PostMapping("/rolePda/scope/update")
+    @Transactional
+    public R scopeUpdate(@RequestBody RoleScopeParam param) {
+        Long roleId = param.getId();
+        List<Long> menuIds = new ArrayList<>(param.getMenuIds().getChecked());
+        menuIds.addAll(param.getMenuIds().getHalfChecked());
+        pdaRoleMenuService.remove(new LambdaQueryWrapper<PdaRoleMenu>().eq(PdaRoleMenu::getRoleId, roleId));
+        for (Long menuId : menuIds) {
+            if (!pdaRoleMenuService.save(new PdaRoleMenu(roleId, menuId))) {
+                throw new CoolException("Internal Server Error!");
+            }
+        }
+        return R.ok("Assign Success");
+    }
+
+
+
+}
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/entity/PdaRoleMenu.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/entity/PdaRoleMenu.java
new file mode 100644
index 0000000..5b10d4e
--- /dev/null
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/entity/PdaRoleMenu.java
@@ -0,0 +1,50 @@
+package com.vincent.rsf.server.system.entity;
+
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableLogic;
+import com.baomidou.mybatisplus.annotation.TableName;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import com.vincent.rsf.framework.common.Cools;
+import com.vincent.rsf.framework.common.SpringUtils;
+import com.vincent.rsf.server.system.service.UserService;
+import com.vincent.rsf.server.system.entity.User;
+import java.io.Serializable;
+import java.util.Date;
+
+@Data
+@TableName("sys_pda_role_menu")
+public class PdaRoleMenu implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @ApiModelProperty(value= "")
+    @TableId(value = "id", type = IdType.AUTO)
+    private Long id;
+
+    @ApiModelProperty(value= "")
+    private Long roleId;
+
+    @ApiModelProperty(value= "")
+    private Long menuId;
+
+    public PdaRoleMenu() {}
+
+    public PdaRoleMenu(Long roleId,Long menuId) {
+        this.roleId = roleId;
+        this.menuId = menuId;
+    }
+
+//    PdaRoleMenu pdaRoleMenu = new PdaRoleMenu(
+//            null,    // [闈炵┖]
+//            null    // [闈炵┖]
+//    );
+
+
+
+
+
+}
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/PdaRoleMenuMapper.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/PdaRoleMenuMapper.java
new file mode 100644
index 0000000..d3b21a3
--- /dev/null
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/PdaRoleMenuMapper.java
@@ -0,0 +1,22 @@
+package com.vincent.rsf.server.system.mapper;
+
+import com.baomidou.mybatisplus.annotation.InterceptorIgnore;
+import com.vincent.rsf.server.manager.entity.MenuPda;
+import com.vincent.rsf.server.system.entity.Menu;
+import com.vincent.rsf.server.system.entity.PdaRoleMenu;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+import org.springframework.stereotype.Repository;
+
+import java.util.List;
+
+@Mapper
+@Repository
+public interface PdaRoleMenuMapper extends BaseMapper<PdaRoleMenu> {
+
+    @InterceptorIgnore(tenantLine = "true")
+    List<MenuPda> listMenuByUserId(@Param("userId") Long userId, @Param("type") Integer menuType);
+
+    List<Long> listStrictlyMenuByRoleId(@Param("roleId") Long roleId);
+}
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/service/PdaRoleMenuService.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/service/PdaRoleMenuService.java
new file mode 100644
index 0000000..c5b43bd
--- /dev/null
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/service/PdaRoleMenuService.java
@@ -0,0 +1,14 @@
+package com.vincent.rsf.server.system.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.vincent.rsf.server.manager.entity.MenuPda;
+import com.vincent.rsf.server.system.entity.Menu;
+import com.vincent.rsf.server.system.entity.PdaRoleMenu;
+
+import java.util.List;
+
+public interface PdaRoleMenuService extends IService<PdaRoleMenu> {
+    List<MenuPda> listMenuByUserId(Long userId, Integer menuType);
+
+    List<Long> listStrictlyMenuByRoleId(Long roleId);
+}
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/PdaRoleMenuServiceImpl.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/PdaRoleMenuServiceImpl.java
new file mode 100644
index 0000000..6168ed1
--- /dev/null
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/PdaRoleMenuServiceImpl.java
@@ -0,0 +1,24 @@
+package com.vincent.rsf.server.system.service.impl;
+
+import com.vincent.rsf.server.manager.entity.MenuPda;
+import com.vincent.rsf.server.system.entity.Menu;
+import com.vincent.rsf.server.system.mapper.PdaRoleMenuMapper;
+import com.vincent.rsf.server.system.entity.PdaRoleMenu;
+import com.vincent.rsf.server.system.service.PdaRoleMenuService;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+
+@Service("pdaRoleMenuService")
+public class PdaRoleMenuServiceImpl extends ServiceImpl<PdaRoleMenuMapper, PdaRoleMenu> implements PdaRoleMenuService {
+    @Override
+    public List<MenuPda> listMenuByUserId(Long userId, Integer menuType) {
+        return baseMapper.listMenuByUserId(userId, menuType);
+    }
+
+    @Override
+    public List<Long> listStrictlyMenuByRoleId(Long roleId) {
+        return baseMapper.listStrictlyMenuByRoleId(roleId);
+    }
+}
diff --git a/rsf-server/src/main/java/menuPda.sql b/rsf-server/src/main/java/menuPda.sql
new file mode 100644
index 0000000..d381644
--- /dev/null
+++ b/rsf-server/src/main/java/menuPda.sql
@@ -0,0 +1,35 @@
+-- save menuPda record
+-- mysql
+insert into `sys_menu` ( `name`, `parent_id`, `route`, `component`, `type`, `sort`, `tenant_id`, `status`) values ( 'menu.menuPda', '0', '/manager/menuPda', 'menuPda', '0' , '0', '1' , '1');
+
+insert into `sys_menu` ( `name`, `parent_id`, `type`, `authority`, `sort`, `tenant_id`, `status`) values ( 'Query PDA鏉冮檺', '', '1', 'manager:menuPda:list', '0', '1', '1');
+insert into `sys_menu` ( `name`, `parent_id`, `type`, `authority`, `sort`, `tenant_id`, `status`) values ( 'Create PDA鏉冮檺', '', '1', 'manager:menuPda:save', '1', '1', '1');
+insert into `sys_menu` ( `name`, `parent_id`, `type`, `authority`, `sort`, `tenant_id`, `status`) values ( 'Update PDA鏉冮檺', '', '1', 'manager:menuPda:update', '2', '1', '1');
+insert into `sys_menu` ( `name`, `parent_id`, `type`, `authority`, `sort`, `tenant_id`, `status`) values ( 'Delete PDA鏉冮檺', '', '1', 'manager:menuPda:remove', '3', '1', '1');
+
+-- locale menu name
+menuPda: 'MenuPda',
+
+-- locale field
+menuPda: {
+    name: "name",
+    parentId: "parentId",
+    parentName: "parentName",
+    path: "path",
+    pathName: "pathName",
+    route: "route",
+    component: "component",
+    brief: "brief",
+    code: "code",
+    type: "type",
+    authority: "authority",
+    icon: "icon",
+    sort: "sort",
+    meta: "meta",
+},
+
+-- ResourceContent
+import menuPda from './menuPda';
+
+case 'menuPda':
+    return menuPda;
diff --git a/rsf-server/src/main/java/pdaRoleMenu.sql b/rsf-server/src/main/java/pdaRoleMenu.sql
new file mode 100644
index 0000000..c977da7
--- /dev/null
+++ b/rsf-server/src/main/java/pdaRoleMenu.sql
@@ -0,0 +1,23 @@
+-- save pdaRoleMenu record
+-- mysql
+insert into `sys_menu` ( `name`, `parent_id`, `route`, `component`, `type`, `sort`, `tenant_id`, `status`) values ( 'menu.pdaRoleMenu', '0', '/system/pdaRoleMenu', 'pdaRoleMenu', '0' , '0', '1' , '1');
+
+insert into `sys_menu` ( `name`, `parent_id`, `type`, `authority`, `sort`, `tenant_id`, `status`) values ( 'Query PDA鏉冮檺', '', '1', 'system:pdaRoleMenu:list', '0', '1', '1');
+insert into `sys_menu` ( `name`, `parent_id`, `type`, `authority`, `sort`, `tenant_id`, `status`) values ( 'Create PDA鏉冮檺', '', '1', 'system:pdaRoleMenu:save', '1', '1', '1');
+insert into `sys_menu` ( `name`, `parent_id`, `type`, `authority`, `sort`, `tenant_id`, `status`) values ( 'Update PDA鏉冮檺', '', '1', 'system:pdaRoleMenu:update', '2', '1', '1');
+insert into `sys_menu` ( `name`, `parent_id`, `type`, `authority`, `sort`, `tenant_id`, `status`) values ( 'Delete PDA鏉冮檺', '', '1', 'system:pdaRoleMenu:remove', '3', '1', '1');
+
+-- locale menu name
+pdaRoleMenu: 'PdaRoleMenu',
+
+-- locale field
+pdaRoleMenu: {
+    roleId: "roleId",
+    menuId: "menuId",
+},
+
+-- ResourceContent
+import pdaRoleMenu from './pdaRoleMenu';
+
+case 'pdaRoleMenu':
+    return pdaRoleMenu;
diff --git a/rsf-server/src/main/resources/application-dev.yml b/rsf-server/src/main/resources/application-dev.yml
index b4140cf..b3bcadf 100644
--- a/rsf-server/src/main/resources/application-dev.yml
+++ b/rsf-server/src/main/resources/application-dev.yml
@@ -16,7 +16,7 @@
     username: root
 #    url: jdbc:mysql://10.10.10.200:3306/rsf?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai
 #    password: xltys1995
-    url: jdbc:mysql://192.168.4.15:3306/rsf?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai
+    url: jdbc:mysql://192.168.4.15:3306/rsf_20250106?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai
     password: 1234
     type: com.alibaba.druid.pool.DruidDataSource
     druid:
diff --git a/rsf-server/src/main/resources/mapper/manager/MenuPdaMapper.xml b/rsf-server/src/main/resources/mapper/manager/MenuPdaMapper.xml
new file mode 100644
index 0000000..157aacc
--- /dev/null
+++ b/rsf-server/src/main/resources/mapper/manager/MenuPdaMapper.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.vincent.rsf.server.manager.mapper.MenuPdaMapper">
+
+</mapper>
diff --git a/rsf-server/src/main/resources/mapper/system/PdaRoleMenuMapper.xml b/rsf-server/src/main/resources/mapper/system/PdaRoleMenuMapper.xml
new file mode 100644
index 0000000..5ab2813
--- /dev/null
+++ b/rsf-server/src/main/resources/mapper/system/PdaRoleMenuMapper.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.vincent.rsf.server.system.mapper.PdaRoleMenuMapper">
+    <select id="listMenuByUserId" resultType="com.vincent.rsf.server.manager.entity.MenuPda">
+        SELECT DISTINCT sm.*
+        FROM sys_menu_pda sm
+        JOIN sys_pda_role_menu srm ON sm.id = srm.menu_id
+        JOIN sys_user_role sur ON srm.role_id = sur.role_id
+        JOIN sys_role sr ON sur.role_id = sr.id
+        WHERE 1=1
+        <if test="type != null">
+            AND sm.type = #{type}
+        </if>
+        AND sur.user_id = #{userId}
+        AND sr.deleted = 0
+        AND sm.deleted = 0
+        ORDER BY sm.sort
+    </select>
+
+    <select id="listStrictlyMenuByRoleId" resultType="java.lang.Long">
+        select sm.id
+        from sys_menu_pda sm
+        left join sys_pda_role_menu srm on sm.id = srm.menu_id
+        where 1=1
+        and sm.deleted = 0
+        and srm.role_id = #{roleId}
+        <!--
+        and sm.id not in (
+            select sm.parent_id
+            from sys_menu sm
+            inner join sys_role_menu srm on sm.id = srm.menu_id
+            and srm.role_id = #{roleId}
+        )
+        -->
+        order by sm.sort
+    </select>
+</mapper>

--
Gitblit v1.9.1