zhou zhou
4 天以前 73ec875e216f434d510a6b95c574fe80387b2bc2
#权限
13个文件已添加
6个文件已修改
1227 ■■■■■ 已修改文件
pom.xml 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-admin/src/page/components/PageDrawer.jsx 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-admin/src/page/matnrRoleMenu/MatnrRoleMenuCreate.jsx 125 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-admin/src/page/matnrRoleMenu/MatnrRoleMenuEdit.jsx 97 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-admin/src/page/matnrRoleMenu/MatnrRoleMenuList.jsx 154 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-admin/src/page/matnrRoleMenu/MatnrRoleMenuPanel.jsx 63 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-admin/src/page/matnrRoleMenu/index.jsx 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-admin/src/page/system/role/AssignPermissions_matnr.jsx 380 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-admin/src/page/system/role/RoleList.jsx 167 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/common/CodeBuilder.java 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/common/config/MybatisPlusConfig.java 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/manager/entity/MatnrGroup.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/system/controller/MatnrRoleMenuController.java 68 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/system/entity/MatnrRoleMenu.java 50 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/MatnrRoleMenuMapper.java 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/system/service/MatnrRoleMenuService.java 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/MatnrRoleMenuServiceImpl.java 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/matnrRoleMenu.sql 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/resources/mapper/system/MatnrRoleMenuMapper.xml 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pom.xml
@@ -38,6 +38,11 @@
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.2.21</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>${lombok.version}</version>
rsf-admin/src/page/components/PageDrawer.jsx
@@ -32,8 +32,7 @@
            sx={{
                zIndex: 100,
                '& .MuiDrawer-paper': {
                    top: '86px', // AppBar(50px) + TabsBar(36px)
                    height: 'calc(100% - 86px)',
                    top: '86px', // AppBar(50px) + TabsBar(36px)
                }
            }}
        >
rsf-admin/src/page/matnrRoleMenu/MatnrRoleMenuCreate.jsx
New file
@@ -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 MatnrRoleMenuCreate = (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.matnrRoleMenu.roleId"
                                        source="roleId"
                                        autoFocus
                                        validate={required()}
                                    />
                                </Grid>
                                <Grid item xs={6} display="flex" gap={1}>
                                    <NumberInput
                                        label="table.field.matnrRoleMenu.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 MatnrRoleMenuCreate;
rsf-admin/src/page/matnrRoleMenu/MatnrRoleMenuEdit.jsx
New file
@@ -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 MatnrRoleMenuEdit = () => {
    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.matnrRoleMenu.roleId"
                                source="roleId"
                                autoFocus
                                validate={required()}
                            />
                        </Stack>
                        <Stack direction='row' gap={2}>
                            <NumberInput
                                label="table.field.matnrRoleMenu.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 MatnrRoleMenuEdit;
rsf-admin/src/page/matnrRoleMenu/MatnrRoleMenuList.jsx
New file
@@ -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 MatnrRoleMenuCreate from "./MatnrRoleMenuCreate";
import MatnrRoleMenuPanel from "./MatnrRoleMenuPanel";
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.matnrRoleMenu.roleId" />,
    <NumberInput source="menuId" label="table.field.matnrRoleMenu.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 MatnrRoleMenuList = () => {
    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.matnrRoleMenu"}
                empty={<EmptyData onClick={() => { setCreateDialog(true) }} />}
                filters={filters}
                sort={{ field: "create_time", order: "desc" }}
                actions={(
                    <TopToolbar>
                        <FilterButton />
                        <MyCreateButton onClick={() => { setCreateDialog(true) }} />
                        <SelectColumnsButton preferenceKey='matnrRoleMenu' />
                        <MyExportButton />
                    </TopToolbar>
                )}
                perPage={DEFAULT_PAGE_SIZE}
            >
                <StyledDatagrid
                    preferenceKey='matnrRoleMenu'
                    bulkActionButtons={() => <BulkDeleteButton mutationMode={OPERATE_MODE} />}
                    rowClick={(id, resource, record) => false}
                    expand={() => <MatnrRoleMenuPanel />}
                    expandSingle={true}
                    omit={['id', 'createTime', 'createBy', 'memo']}
                >
                    <NumberField source="id" />
                    <NumberField source="roleId" label="table.field.matnrRoleMenu.roleId" />
                    <NumberField source="menuId" label="table.field.matnrRoleMenu.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>
            <MatnrRoleMenuCreate
                open={createDialog}
                setOpen={setCreateDialog}
            />
            <PageDrawer
                title='MatnrRoleMenu Detail'
                drawerVal={drawerVal}
                setDrawerVal={setDrawerVal}
            >
            </PageDrawer>
        </Box>
    )
}
export default MatnrRoleMenuList;
rsf-admin/src/page/matnrRoleMenu/MatnrRoleMenuPanel.jsx
New file
@@ -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 MatnrRoleMenuPanel = () => {
    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.matnrRoleMenu.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.matnrRoleMenu.roleId"
                                property={record.roleId}
                            />
                        </Grid>
                        <Grid item xs={6}>
                            <PanelTypography
                                title="table.field.matnrRoleMenu.menuId"
                                property={record.menuId}
                            />
                        </Grid>
                    </Grid>
                </CardContent>
            </Card >
        </>
    );
};
export default MatnrRoleMenuPanel;
rsf-admin/src/page/matnrRoleMenu/index.jsx
New file
@@ -0,0 +1,18 @@
import React, { useState, useRef, useEffect, useMemo } from "react";
import {
    ListGuesser,
    EditGuesser,
    ShowGuesser,
} from "react-admin";
import MatnrRoleMenuList from "./MatnrRoleMenuList";
import MatnrRoleMenuEdit from "./MatnrRoleMenuEdit";
export default {
    list: MatnrRoleMenuList,
    edit: MatnrRoleMenuEdit,
    show: ShowGuesser,
    recordRepresentation: (record) => {
        return `${record.id}`
    }
};
rsf-admin/src/page/system/role/AssignPermissions_matnr.jsx
New file
@@ -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 AssignPermissionsMatnr = (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('/menuMatnrGroup/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('/roleMatnr/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 AssignPermissionsMatnr;
rsf-admin/src/page/system/role/RoleList.jsx
@@ -30,7 +30,8 @@
    useNotify,
    Button,
} from 'react-admin';
import { Box, Card, Stack } from '@mui/material';
import { Box, Card, Stack, Menu, MenuItem, ListItemIcon, ListItemText } from '@mui/material';
import SecurityIcon from '@mui/icons-material/Security';
import { styled } from '@mui/material/styles';
import RoleCreate from "./RoleCreate";
import RolePanel from "./RolePanel";
@@ -43,9 +44,12 @@
import * as Common from '@/utils/common';
import AssignPermissions from "./AssignPermissions";
import AssignPermissionsPda from "./AssignPermissions_pda";
import AssignPermissionsMatnr from "./AssignPermissions_matnr";
import request from '@/utils/request';
import AssignmentIndIcon from '@mui/icons-material/AssignmentInd';
import AdUnitsIcon from '@mui/icons-material/AdUnits';
import ArticleIcon from '@mui/icons-material/Article';
import { margin } from "@mui/system";
const StyledDatagrid = styled(DatagridConfigurable)(({ theme }) => ({
    '& .css-1vooibu-MuiSvgIcon-root': {
@@ -83,12 +87,14 @@
    const [createDialog, setCreateDialog] = useState(false);
    const [drawerVal, setDrawerVal] = useState(false);
    const [drawerValPda, setDrawerValPda] = useState(false);
    const [drawerValMatnr, setDrawerValMatnr] = useState(false);
    const [menuIds, setMenuIds] = useState([]);
    const [authType, setAuthType] = useState(0)
    const assign = (record) => {
        setDrawerValPda(false);
        request('/role/scope/list', {
            method: 'GET',
            params: {
@@ -106,6 +112,7 @@
    }
    const assignPda = (record) => {
        setDrawerVal(false);
        request('/rolePda/scope/list', {
            method: 'GET',
            params: {
@@ -122,6 +129,24 @@
        });
    }
    const assignMatnr = (record) => {
        setDrawerVal(false);
        request('/roleMatnr/scope/list', {
            method: 'GET',
            params: {
                roleId: record.id
            }
        }).then((res) => {
            if (res?.data?.code === 200) {
                const { data: menuIds } = res.data;
                setMenuIds(menuIds || []);
                setDrawerValMatnr(!!drawerValMatnr && drawerValMatnr === record ? null : record);
            } else {
                notify(res.data.msg, { type: 'error' });
            }
        });
    }
    return (
        <Box display="flex">
            <List
@@ -131,7 +156,7 @@
                        theme.transitions.create(['all'], {
                            duration: theme.transitions.duration.enteringScreen,
                        }),
                    marginRight: (!!drawerVal || !!drawerValPda) ? `${PAGE_DRAWER_WIDTH}px` : 0,
                    marginRight: (!!drawerVal || !!drawerValPda || !!drawerValMatnr) ? `${PAGE_DRAWER_WIDTH}px` : 0,
                }}
                title={"menu.role"}
                empty={<EmptyData onClick={() => { setCreateDialog(true) }} />}
@@ -167,9 +192,12 @@
                    <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">
                        <ScopeButton sx={{ padding: '1px', fontSize: '.75rem' }} assign={assign} auType={0} setAuthType={setAuthType} label="网页权限&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;" />
                        <PermissionMenuButton
                            assign={assign}
                            assignPda={assignPda}
                            assignMatnr={assignMatnr}
                            setAuthType={setAuthType}
                        />
                        <EditButton sx={{ padding: '1px', fontSize: '.75rem' }} />
                        <DeleteButton sx={{ padding: '1px', fontSize: '.75rem' }} mutationMode={OPERATE_MODE} />
                    </WrapperField>
@@ -215,46 +243,109 @@
                    authType={authType}
                />
            </PageDrawer>
            <PageDrawer
                drawerVal={drawerValMatnr}
                setDrawerVal={setDrawerValMatnr}
                title={!!drawerValMatnr ? `Scope by ${drawerValMatnr.code || drawerValMatnr.name}` : 'Role Detail'}
                closeCallback={() => {
                    setMenuIds([]);
                }}
            >
                <AssignPermissionsMatnr
                    role={drawerValMatnr}
                    originMenuIds={menuIds}
                    setDrawerVal={setDrawerValMatnr}
                    closeCallback={() => {
                        setMenuIds([]);
                    }}
                    authType={authType}
                />
            </PageDrawer>
        </Box>
    )
}
const ScopeButton = (props) => {
const PermissionMenuButton = ({ assign, assignPda, assignMatnr, setAuthType }) => {
    const record = useRecordContext();
    const { assign, auType, setAuthType, label, ...rest } = props;
    return (
        <Button
            variant="text"
            color="primary"
            startIcon={<AssignmentIndIcon />}
            label={label}
            onClick={(event) => {
                setAuthType(auType);
                event.stopPropagation();
                assign(record);
            }}
            {...rest}
        />
    )
}
    const [anchorEl, setAnchorEl] = useState(null);
    const open = Boolean(anchorEl);
const PdaScopeButton = (props) => {
    const record = useRecordContext();
    const { assignPda, auType, setAuthType, label, ...rest } = props;
    const handleClick = (event) => {
        event.stopPropagation();
        setAnchorEl(event.currentTarget);
    };
    const handleClose = (event) => {
        if (event) event.stopPropagation();
        setAnchorEl(null);
    };
    const handleWebPermission = (event) => {
        event.stopPropagation();
        setAuthType(0);
        assign(record);
        handleClose();
    };
    const handlePdaPermission = (event) => {
        event.stopPropagation();
        setAuthType(1);
        assignPda(record);
        handleClose();
    };
    const handleMatnrPermission = (event) => {
        event.stopPropagation();
        setAuthType(2);
        assignMatnr(record);
        handleClose();
    };
    return (
        <Button
            variant="text"
            color="primary"
            startIcon={<AdUnitsIcon />}
            label={label}
            onClick={(event) => {
                setAuthType(auType);
                event.stopPropagation();
                assignPda(record);
            }}
            {...rest}
        />
    )
        <>
            <Button
                variant="text"
                color="primary"
                startIcon={<SecurityIcon />}
                label="权限"
                onClick={handleClick}
                sx={{ marginLeft: '2px', padding: '1px', fontSize: '.75rem' }}
            />
            <Menu
                anchorEl={anchorEl}
                open={open}
                onClose={handleClose}
                onClick={(e) => e.stopPropagation()}
                anchorOrigin={{
                    vertical: 'bottom',
                    horizontal: 'left',
                }}
                transformOrigin={{
                    vertical: 'top',
                    horizontal: 'left',
                }}
            >
                <MenuItem onClick={handleWebPermission}>
                    <ListItemIcon>
                        <AssignmentIndIcon fontSize="small" />
                    </ListItemIcon>
                    <ListItemText>网页权限</ListItemText>
                </MenuItem>
                <MenuItem onClick={handlePdaPermission}>
                    <ListItemIcon>
                        <AdUnitsIcon fontSize="small" />
                    </ListItemIcon>
                    <ListItemText>PDA权限</ListItemText>
                </MenuItem>
                <MenuItem onClick={handleMatnrPermission}>
                    <ListItemIcon>
                        <ArticleIcon fontSize="small" />
                    </ListItemIcon>
                    <ListItemText>物料权限</ListItemText>
                </MenuItem>
            </Menu>
        </>
    );
}
export default RoleList;
rsf-server/src/main/java/com/vincent/rsf/server/common/CodeBuilder.java
@@ -22,9 +22,9 @@
//        generator.username="sa";
//        generator.password="Zoneyung@zy56$";
        generator.table = "sys_pda_role_menu";
        generator.tableDesc = "PDA权限";
        generator.packagePath = "com.vincent.rsf.server.manager";
        generator.table = "sys_matnr_role_menu";
        generator.tableDesc = "物料权限";
        generator.packagePath = "com.vincent.rsf.server.system";
        generator.build();
    }
rsf-server/src/main/java/com/vincent/rsf/server/common/config/MybatisPlusConfig.java
@@ -54,6 +54,7 @@
                        "sys_menu",
                        "sys_pda_role_menu",
                        "sys_menu_pda",
                        "sys_matnr_role_menu",
                        "man_loc_type_rela",
                        "man_qly_inspect_result",
                        "view_stock_manage",
rsf-server/src/main/java/com/vincent/rsf/server/manager/entity/MatnrGroup.java
@@ -60,6 +60,8 @@
    @ApiModelProperty(value= "上级分类ID")
    private Long parentId;
    private Integer sort;
    /**
     * 状态 1: 正常  0: 冻结  
     */
rsf-server/src/main/java/com/vincent/rsf/server/system/controller/MatnrRoleMenuController.java
New file
@@ -0,0 +1,68 @@
package com.vincent.rsf.server.system.controller;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
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.framework.exception.CoolException;
import com.vincent.rsf.server.common.annotation.OperationLog;
import com.vincent.rsf.server.manager.entity.MatnrGroup;
import com.vincent.rsf.server.manager.entity.MenuPda;
import com.vincent.rsf.server.manager.service.MatnrGroupService;
import com.vincent.rsf.server.system.controller.param.RoleScopeParam;
import com.vincent.rsf.server.system.entity.MatnrRoleMenu;
import com.vincent.rsf.server.system.entity.PdaRoleMenu;
import com.vincent.rsf.server.system.service.MatnrRoleMenuService;
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 java.util.*;
@RestController
public class MatnrRoleMenuController extends BaseController {
    @Autowired
    private MatnrRoleMenuService matnrRoleMenuService;
    @Autowired
    private MatnrGroupService matnrGroupService;
    @GetMapping("/roleMatnr/scope/list")
    public R scopeList(@RequestParam Long roleId) {
        return R.ok().add(matnrRoleMenuService.listStrictlyMenuByRoleId(roleId));
    }
    @PostMapping("/menuMatnrGroup/tree")
    public R tree(@RequestBody Map<String, Object> map) {
        List<MatnrGroup> menuList = matnrGroupService.list(new LambdaQueryWrapper<MatnrGroup>().orderByAsc(MatnrGroup::getSort));
        List<MatnrGroup> treeData = Utils.toTreeData(menuList, 0L, MatnrGroup::getParentId, MatnrGroup::getId,
                MatnrGroup::setChildren);
        if (!Cools.isEmpty(map.get("condition"))) {
            Utils.treeRemove(treeData, String.valueOf(map.get("condition")), MatnrGroup::getName, MatnrGroup::getChildren);
            Utils.treeRemove(treeData, String.valueOf(map.get("condition")), MatnrGroup::getName, MatnrGroup::getChildren);
        }
        return R.ok().add(treeData);
    }
    @PreAuthorize("hasAuthority('system:role:update')")
    @OperationLog("Assign Permissions")
    @PostMapping("/roleMatnr/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());
        matnrRoleMenuService.remove(new LambdaQueryWrapper<MatnrRoleMenu>().eq(MatnrRoleMenu::getRoleId, roleId));
        for (Long menuId : menuIds) {
            if (!matnrRoleMenuService.save(new MatnrRoleMenu(roleId, menuId))) {
                throw new CoolException("Internal Server Error!");
            }
        }
        return R.ok("Assign Success");
    }
}
rsf-server/src/main/java/com/vincent/rsf/server/system/entity/MatnrRoleMenu.java
New file
@@ -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_matnr_role_menu")
public class MatnrRoleMenu 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 MatnrRoleMenu() {}
    public MatnrRoleMenu(Long roleId,Long menuId) {
        this.roleId = roleId;
        this.menuId = menuId;
    }
//    MatnrRoleMenu matnrRoleMenu = new MatnrRoleMenu(
//            null,    // [非空]
//            null    // [非空]
//    );
}
rsf-server/src/main/java/com/vincent/rsf/server/system/mapper/MatnrRoleMenuMapper.java
New file
@@ -0,0 +1,15 @@
package com.vincent.rsf.server.system.mapper;
import com.vincent.rsf.server.system.entity.MatnrRoleMenu;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.stereotype.Repository;
import java.util.List;
@Mapper
@Repository
public interface MatnrRoleMenuMapper extends BaseMapper<MatnrRoleMenu> {
    List<Long> listStrictlyMenuByRoleId(Long roleId);
}
rsf-server/src/main/java/com/vincent/rsf/server/system/service/MatnrRoleMenuService.java
New file
@@ -0,0 +1,11 @@
package com.vincent.rsf.server.system.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.vincent.rsf.server.system.entity.MatnrRoleMenu;
import java.util.List;
public interface MatnrRoleMenuService extends IService<MatnrRoleMenu> {
    List<Long> listStrictlyMenuByRoleId(Long roleId);
}
rsf-server/src/main/java/com/vincent/rsf/server/system/service/impl/MatnrRoleMenuServiceImpl.java
New file
@@ -0,0 +1,17 @@
package com.vincent.rsf.server.system.service.impl;
import com.vincent.rsf.server.system.mapper.MatnrRoleMenuMapper;
import com.vincent.rsf.server.system.entity.MatnrRoleMenu;
import com.vincent.rsf.server.system.service.MatnrRoleMenuService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
import java.util.List;
@Service("matnrRoleMenuService")
public class MatnrRoleMenuServiceImpl extends ServiceImpl<MatnrRoleMenuMapper, MatnrRoleMenu> implements MatnrRoleMenuService {
    @Override
    public List<Long> listStrictlyMenuByRoleId(Long roleId) {
        return baseMapper.listStrictlyMenuByRoleId(roleId);
    }
}
rsf-server/src/main/java/matnrRoleMenu.sql
New file
@@ -0,0 +1,23 @@
-- save matnrRoleMenu record
-- mysql
insert into `sys_menu` ( `name`, `parent_id`, `route`, `component`, `type`, `sort`, `tenant_id`, `status`) values ( 'menu.matnrRoleMenu', '0', '/system/matnrRoleMenu', 'matnrRoleMenu', '0' , '0', '1' , '1');
insert into `sys_menu` ( `name`, `parent_id`, `type`, `authority`, `sort`, `tenant_id`, `status`) values ( 'Query 物料权限', '', '1', 'system:matnrRoleMenu:list', '0', '1', '1');
insert into `sys_menu` ( `name`, `parent_id`, `type`, `authority`, `sort`, `tenant_id`, `status`) values ( 'Create 物料权限', '', '1', 'system:matnrRoleMenu:save', '1', '1', '1');
insert into `sys_menu` ( `name`, `parent_id`, `type`, `authority`, `sort`, `tenant_id`, `status`) values ( 'Update 物料权限', '', '1', 'system:matnrRoleMenu:update', '2', '1', '1');
insert into `sys_menu` ( `name`, `parent_id`, `type`, `authority`, `sort`, `tenant_id`, `status`) values ( 'Delete 物料权限', '', '1', 'system:matnrRoleMenu:remove', '3', '1', '1');
-- locale menu name
matnrRoleMenu: 'MatnrRoleMenu',
-- locale field
matnrRoleMenu: {
    roleId: "roleId",
    menuId: "menuId",
},
-- ResourceContent
import matnrRoleMenu from './matnrRoleMenu';
case 'matnrRoleMenu':
    return matnrRoleMenu;
rsf-server/src/main/resources/mapper/system/MatnrRoleMenuMapper.xml
New file
@@ -0,0 +1,22 @@
<?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.MatnrRoleMenuMapper">
    <select id="listStrictlyMenuByRoleId" resultType="java.lang.Long">
        select sm.id
        from man_matnr_group sm
        left join sys_matnr_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>