1个文件已添加
13个文件已修改
663 ■■■■ 已修改文件
rsf-admin/src/i18n/en.js 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-admin/src/i18n/zh.js 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-admin/src/layout/TabsBar.jsx 219 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-admin/src/page/task/FlowStepInstanceModal.jsx 243 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-admin/src/page/task/TaskList.jsx 37 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-admin/src/page/taskPathTemplate/TaskPathTemplateList.jsx 25 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-admin/src/page/taskPathTemplate/TaskTemplateFlowViewer.jsx 10 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-admin/src/page/taskPathTemplateMerge/TaskPathTemplateMergeList.jsx 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/manager/controller/BasStationAreaController.java 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/manager/entity/BasStationArea.java 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/manager/schedules/TaskMissionSchedules.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/manager/service/impl/TaskServiceImpl.java 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/manager/utils/LocManageUtil.java 10 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/system/controller/FlowStepInstanceController.java 18 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-admin/src/i18n/en.js
@@ -226,6 +226,7 @@
        subsystemFlowTemplate: 'SubsystemFlowTemplate',
        flowStepTemplate: 'FlowStepTemplate',
        taskPathTemplateMerge: 'TaskPathTemplateMerge',
        missionFlowStepInstance: 'Mission Flow Steps',
    },
    table: {
        field: {
@@ -280,6 +281,28 @@
                retryEnabled: "retryEnabled",
                retryConfig: "retryConfig",
                timeoutSeconds: "timeoutSeconds",
            },
            flowStepInstance: {
                flowInstanceId: "Flow Instance ID",
                flowInstanceNo: "Flow Instance No",
                stepTemplateId: "Step Template ID",
                errorCode: "Error Code",
                errorMessage: "Error Message",
                startTime: "Start Time",
                endTime: "End Time",
                durationSeconds: "Duration(s)",
                inputData: "Input Data",
                outputData: "Output Data",
                retryTimes: "Retry Times",
                id: "ID",
                stepOrder: "Step Order",
                stepCode: "Step Code",
                stepName: "Step Name",
                stepType: "Step Type",
                status: "Status",
                executeResult: "Execute Result",
                taskNo: "Task No",
                createTime: "Create Time",
            },
            subsystemFlowTemplate: {
                flowCode: "flowCode",
@@ -1505,6 +1528,7 @@
        order: 'Orders',
        modiftySite: 'Modify SiteNo',
        selectWave: 'Select Wave Rule',
        flowStep: "Flow Step",
    },
    placeholder: {
rsf-admin/src/i18n/zh.js
@@ -242,9 +242,32 @@
        subsystemFlowTemplate: '子系统流程模板',
        flowStepTemplate: '流程步骤模板',
        taskPathTemplateMerge: '任务路径模板合并',
        missionFlowStepInstance: '任务流程步骤',
    },
    table: {
        field: {
        field: {
            flowStepInstance: {
                flowInstanceId: "流程实例ID",
                flowInstanceNo: "流程实例编号",
                stepTemplateId: "步骤模板ID",
                errorCode: "错误编码",
                errorMessage: "错误消息",
                startTime: "开始时间",
                endTime: "结束时间",
                durationSeconds: "持续秒数",
                inputData: "输入数据",
                outputData: "输出数据",
                retryTimes: "重试次数",
                id: "ID",
                stepOrder: "步骤顺序",
                stepCode: "步骤编码",
                stepName: "步骤名称",
                stepType: "步骤类型",
                status: "状态",
                executeResult: "执行结果",
                taskNo: "任务号",
                createTime: "创建时间",
            },
            basStationArea: {
                type: "类型",
                stationAreaName: "站点区域名称",
@@ -1573,6 +1596,7 @@
        modiftySite: '修改库口',
        selectWave: '波次规则',
        transformation: "转换",
        flowStep: "流程步骤",
    },
    placeholder: {
        warehouseAreasCode: "用于库位编码前缀占位符",
rsf-admin/src/layout/TabsBar.jsx
@@ -1,7 +1,7 @@
import React, { useState, useEffect, useCallback, useRef } from 'react';
import { useLocation, useNavigate } from 'react-router-dom';
import { useTranslate, usePermissions } from 'react-admin';
import { Box, Tab, Tabs, IconButton, Tooltip, Divider } from '@mui/material';
import { Box, Tab, Tabs, IconButton, Tooltip, Divider, Menu, MenuItem } from '@mui/material';
import CloseIcon from '@mui/icons-material/Close';
import HomeIcon from '@mui/icons-material/Home';
import SettingsIcon from '@mui/icons-material/Settings';
@@ -86,6 +86,36 @@
    const { permissions } = usePermissions();
    const [tabs, setTabs] = useState(getSavedTabs);
    const pendingNavigationRef = useRef(null);
    const tabsBarRef = useRef(null);
    const [contextMenu, setContextMenu] = useState(null);
    const [contextMenuTab, setContextMenuTab] = useState(null);
    const contextMenuOpenRef = useRef(false);
    contextMenuOpenRef.current = contextMenu !== null;
    // 在标签页右键,阻止浏览器默认菜单
    useEffect(() => {
        const onDocContextMenu = (e) => {
            const inTabsBarByTarget = tabsBarRef.current && tabsBarRef.current.contains(e.target);
            let inTabsBarByPos = false;
            if (tabsBarRef.current) {
                const rect = tabsBarRef.current.getBoundingClientRect();
                inTabsBarByPos = e.clientX >= rect.left && e.clientX <= rect.right && e.clientY >= rect.top && e.clientY <= rect.bottom;
            }
            const inTabsBar = inTabsBarByTarget || inTabsBarByPos;
            const menuOpen = contextMenuOpenRef.current;
            if (inTabsBar || menuOpen) {
                e.preventDefault();
            }
        };
        document.addEventListener('contextmenu', onDocContextMenu, true);
        return () => document.removeEventListener('contextmenu', onDocContextMenu, true);
    }, []); // 用 ref 读最新状态,避免快速点击闭包滞后
    // 翻译辅助,无翻译 key 时返回默认值
    const t = useCallback((key, defaultValue) => {
        const translated = translate(key);
        return translated === key ? defaultValue : translated;
    }, [translate]);
    // 处理待执行的导航
    useEffect(() => {
@@ -240,6 +270,126 @@
        }
    };
    // 关闭其他标签
    const handleCloseOthers = (keepTabPath) => {
        const keepTab = tabs.find(tab => tab.path === keepTabPath);
        if (!keepTab) return;
        const dashboardTab = tabs.find(tab => tab.path === '/dashboard');
        const newTabs = [];
        if (keepTab.path !== '/dashboard' && dashboardTab) {
            newTabs.push(dashboardTab);
        }
        if (!newTabs.some(tab => tab.path === keepTab.path)) {
            newTabs.push(keepTab);
        }
        saveTabs(newTabs);
        setTabs(newTabs);
        setContextMenu(null);
        setContextMenuTab(null);
    };
    // 关闭左侧标签
    const handleCloseLeft = (keepTabPath) => {
        const keepTabIndex = tabs.findIndex(tab => tab.path === keepTabPath);
        if (keepTabIndex < 0) return;
        let newTabs = tabs.slice(keepTabIndex);
        const dashboardTab = newTabs.find(tab => tab.path === '/dashboard');
        if (dashboardTab && newTabs[0].path !== '/dashboard') {
            const dashboardIndex = newTabs.findIndex(tab => tab.path === '/dashboard');
            if (dashboardIndex > 0) {
                newTabs.splice(dashboardIndex, 1);
                newTabs.unshift(dashboardTab);
            }
        }
        saveTabs(newTabs);
        setTabs(newTabs);
        setContextMenu(null);
        setContextMenuTab(null);
    };
    // 关闭右侧标签
    const handleCloseRight = (keepTabPath) => {
        const keepTabIndex = tabs.findIndex(tab => tab.path === keepTabPath);
        if (keepTabIndex < 0) return;
        const newTabs = tabs.slice(0, keepTabIndex + 1);
        saveTabs(newTabs);
        setTabs(newTabs);
        setContextMenu(null);
        setContextMenuTab(null);
    };
    // 右键菜单:打开
    const handleContextMenu = (event, tab) => {
        event.preventDefault();
        event.stopPropagation();
        if (!tab) return;
        const tabObj = tabs.find(t => t.path === tab.path || isSameResource(t.path, tab.path));
        if (!tabObj) return;
        const hasItems = tabObj.closable ||
            canCloseLeftForTab(tab.path) ||
            canCloseRightForTab(tab.path) ||
            canCloseOthersForTab(tab.path);
        if (!hasItems) return;
        setContextMenu({ mouseX: event.clientX + 2, mouseY: event.clientY - 6 });
        setContextMenuTab(tab);
    };
    const handleCloseContextMenu = () => {
        setContextMenu(null);
        setContextMenuTab(null);
    };
    // 右键菜单:关闭当前标签
    const handleCloseCurrentTab = () => {
        if (!contextMenuTab) {
            handleCloseContextMenu();
            return;
        }
        const tabPath = contextMenuTab.path;
        const tabIndex = tabs.findIndex(tab => tab.path === tabPath);
        const newTabs = tabs.filter(tab => tab.path !== tabPath);
        if (location.pathname === tabPath || isSameResource(location.pathname, tabPath)) {
            const newIndex = Math.min(tabIndex, newTabs.length - 1);
            if (newIndex >= 0 && newTabs[newIndex]) {
                pendingNavigationRef.current = newTabs[newIndex].path;
            } else {
                pendingNavigationRef.current = '/dashboard';
            }
        }
        saveTabs(newTabs);
        setTabs(newTabs);
        handleCloseContextMenu();
    };
    // 右键菜单:关闭其他/左侧/右侧
    const handleCloseOtherTabs = () => {
        if (contextMenuTab) handleCloseOthers(contextMenuTab.path);
    };
    const handleCloseLeftTabs = () => {
        if (contextMenuTab) handleCloseLeft(contextMenuTab.path);
    };
    const handleCloseRightTabs = () => {
        if (contextMenuTab) handleCloseRight(contextMenuTab.path);
    };
    // 是否可关闭其他/左侧/右侧
    const canCloseOthersForTab = (tabPath) => {
        return tabs.filter(tab => {
            const isTarget = tab.path === tabPath || isSameResource(tab.path, tabPath);
            return !isTarget && tab.closable;
        }).length > 0;
    };
    const canCloseLeftForTab = (tabPath) => {
        const tabIndex = tabs.findIndex(tab => tab.path === tabPath || isSameResource(tab.path, tabPath));
        if (tabIndex <= 0) return false;
        return tabs.slice(0, tabIndex).some(tab => tab.closable);
    };
    const canCloseRightForTab = (tabPath) => {
        const tabIndex = tabs.findIndex(tab => tab.path === tabPath || isSameResource(tab.path, tabPath));
        if (tabIndex < 0 || tabIndex >= tabs.length - 1) return false;
        return tabs.slice(tabIndex + 1).some(tab => tab.closable);
    };
    // 获取当前标签页索引
    const currentTabIndex = tabs.findIndex(tab =>
        tab.path === location.pathname || isSameResource(tab.path, location.pathname)
@@ -249,8 +399,25 @@
    // 判断是否有可关闭的标签
    const hasClosableTabs = tabs.some(tab => tab.closable);
    // 右键打开时遮罩处理
    const menuPaperRef = useRef(null);
    useEffect(() => {
        if (contextMenu === null) return;
        const onDocClick = (e) => {
            const inMenu = menuPaperRef.current && menuPaperRef.current.contains(e.target);
            const inTabsBar = tabsBarRef.current && tabsBarRef.current.contains(e.target);
            if (!inMenu && !inTabsBar) {
                setContextMenu(null);
                setContextMenuTab(null);
            }
        };
        document.addEventListener('mousedown', onDocClick, true);
        return () => document.removeEventListener('mousedown', onDocClick, true);
    }, [contextMenu]);
    return (
        <Box
            ref={tabsBarRef}
            sx={{
                width: '100%',
                backgroundColor: '#fff',
@@ -282,10 +449,12 @@
                        key={tab.path}
                        label={
                            <Box
                                onContextMenu={(e) => handleContextMenu(e, tab)}
                                sx={{
                                    display: 'flex',
                                    alignItems: 'center',
                                    gap: 0.5,
                                    width: '100%',
                                }}
                            >
                                {tab.path === '/dashboard' && (
@@ -362,6 +531,54 @@
                    </Tooltip>
                </Box>
            )}
            {/* 右键菜单 */}
            <Menu
                open={contextMenu !== null}
                onClose={handleCloseContextMenu}
                anchorReference="anchorPosition"
                anchorPosition={
                    contextMenu !== null
                        ? { top: contextMenu.mouseY, left: contextMenu.mouseX }
                        : undefined
                }
                disableScrollLock
                ModalProps={{
                    disablePortal: true,
                    BackdropProps: { sx: { pointerEvents: 'none' } }, // 不拦右键,便于再右键另一个标签
                }}
                PaperProps={{
                    ref: menuPaperRef,
                    sx: {
                        minWidth: 120,
                        borderRadius: '8px',
                        boxShadow: '0 4px 20px rgba(0,0,0,0.08)',
                        mt: 0.5,
                        py: 0.5,
                    },
                }}
                MenuListProps={{ sx: { py: 0 } }}
            >
                {/*{contextMenuTab && contextMenuTab.closable && (*/}
                {/*    <MenuItem onClick={handleCloseCurrentTab} sx={{ fontSize: '0.8125rem', py: 0.75, px: 1.5, minHeight: 'auto' }}>*/}
                {/*        {t('ra.action.close', '关闭当前标签')}*/}
                {/*    </MenuItem>*/}
                {/*)}*/}
                {contextMenuTab && canCloseLeftForTab(contextMenuTab.path) && (
                    <MenuItem onClick={handleCloseLeftTabs} sx={{ fontSize: '0.8125rem', py: 0.75, px: 1.5, minHeight: 'auto' }}>
                        {t('ra.action.closeLeft', '关闭左侧标签')}
                    </MenuItem>
                )}
                {contextMenuTab && canCloseRightForTab(contextMenuTab.path) && (
                    <MenuItem onClick={handleCloseRightTabs} sx={{ fontSize: '0.8125rem', py: 0.75, px: 1.5, minHeight: 'auto' }}>
                        {t('ra.action.closeRight', '关闭右侧标签')}
                    </MenuItem>
                )}
                {contextMenuTab && canCloseOthersForTab(contextMenuTab.path) && (
                    <MenuItem onClick={handleCloseOtherTabs} sx={{ fontSize: '0.8125rem', py: 0.75, px: 1.5, minHeight: 'auto' }}>
                        {t('ra.action.closeOthers', '关闭其他标签')}
                    </MenuItem>
                )}
            </Menu>
        </Box>
    );
};
rsf-admin/src/page/task/FlowStepInstanceModal.jsx
New file
@@ -0,0 +1,243 @@
import React, { useState, useEffect } from "react";
import {
    useTranslate,
    useNotify,
    useRefresh,
    Button,
    DatagridConfigurable,
    TextField,
    NumberField,
    DateField,
    EditButton,
    Toolbar,
    useRecordContext,
    SimpleForm,
    TextInput,
    NumberInput,
    SelectInput,
    WrapperField
} from 'react-admin';
import { Box, DialogContent, DialogTitle, DialogActions, Dialog, IconButton, Tooltip } from '@mui/material';
import { styled } from '@mui/material/styles';
import DialogCloseButton from "../components/DialogCloseButton";
import request from '@/utils/request';
import AddIcon from '@mui/icons-material/Add';
import DeleteIcon from '@mui/icons-material/Delete';
import EditIcon from '@mui/icons-material/Edit';
const StyledDatagrid = styled(DatagridConfigurable)(({ theme }) => ({
    '& .css-1vooibu-MuiSvgIcon-root': {
        height: '.9em'
    },
    '& .RaDatagrid-row': {
        cursor: 'auto'
    },
    '& .opt': {
        width: 150
    },
}));
const FlowStepInstanceModal = (props) => {
    const { open, setOpen, record } = props;
    const translate = useTranslate();
    const notify = useNotify();
    const [data, setData] = useState([]);
    const [loading, setLoading] = useState(false);
    // Form states
    const [formOpen, setFormOpen] = useState(false);
    const [isEdit, setIsEdit] = useState(false);
    const [formData, setFormData] = useState({});
    const handleClose = (event, reason) => {
        if (reason !== "backdropClick") {
            setOpen(false);
        }
    };
    const fetchData = async () => {
        if (!record?.taskCode) return;
        setLoading(true);
        try {
            const res = await request.post(`/flowStepInstance/page`, {
                taskNo: record.taskCode,
                current: 1,
                pageSize: 999
            });
            if (res.data.code === 200) {
                setData(res.data.data.records || []);
            } else {
                notify(res.data.msg, { type: 'error' });
            }
        } catch (error) {
            notify('Failed to fetch data', { type: 'error' });
        } finally {
            setLoading(false);
        }
    };
    useEffect(() => {
        if (open && record) {
            fetchData();
        }
    }, [open, record]);
    const handleCreate = () => {
        setFormData({ taskNo: record?.taskCode, status: 0 }); // Default values
        setIsEdit(false);
        setFormOpen(true);
    };
    const handleEdit = (item) => {
        setFormData(item);
        setIsEdit(true);
        setFormOpen(true);
    };
    const handleDelete = async (item) => {
        try {
            const res = await request.post(`/flowStepInstance/remove/${item.id}`);
            if (res.data.code === 200) {
                notify('Deleted successfully', { type: 'success' });
                fetchData();
            } else {
                notify(res.data.msg, { type: 'error' });
            }
        } catch (error) {
            notify('Delete failed', { type: 'error' });
        }
    };
    const handleFormClose = () => {
        setFormOpen(false);
        setFormData({});
    };
    const handleSave = async (dataToSave) => {
        try {
            let res;
            if (isEdit) {
                res = await request.post(`/flowStepInstance/update`, dataToSave);
            } else {
                res = await request.post(`/flowStepInstance/save`, dataToSave);
            }
            if (res.data.code === 200) {
                notify(isEdit ? 'Updated successfully' : 'Created successfully', { type: 'success' });
                setFormOpen(false);
                fetchData();
            } else {
                notify(res.data.msg, { type: 'error' });
            }
        } catch (error) {
            notify('Save failed', { type: 'error' });
        }
    };
    return (
        <React.Fragment>
            <Dialog
                open={open}
                onClose={handleClose}
                aria-labelledby="form-dialog-title"
                fullWidth
                maxWidth="lg"
            >
                <DialogTitle id="form-dialog-title" sx={{
                    position: 'sticky',
                    top: 0,
                    backgroundColor: 'background.paper',
                    zIndex: 1000
                }}>
                    {translate('menu.missionFlowStepInstance')}
                    <Box sx={{ position: 'absolute', top: 8, right: 8, zIndex: 1001 }}>
                        <DialogCloseButton onClose={handleClose} />
                    </Box>
                </DialogTitle>
                <DialogContent sx={{ minHeight: 400 }}>
                    <Box sx={{ mb: 2, display: 'flex', justifyContent: 'flex-end' }}>
                        <Button variant="contained" label="ra.action.create" onClick={handleCreate}>
                            <AddIcon />
                        </Button>
                    </Box>
                    <StyledDatagrid
                        data={data}
                        isLoading={loading}
                        bulkActionButtons={false}
                        rowClick={false}
                    >
                        <NumberField source="id" label={translate("table.field.flowStepInstance.id")} />
                        <NumberField source="stepOrder" label={translate("table.field.flowStepInstance.stepOrder")} />
                        <TextField source="stepCode" label={translate("table.field.flowStepInstance.stepCode")} />
                        <TextField source="stepName" label={translate("table.field.flowStepInstance.stepName")} />
                        <TextField source="stepType" label={translate("table.field.flowStepInstance.stepType")} />
                        <NumberField source="status" label={translate("table.field.flowStepInstance.status")} />
                        <TextField source="executeResult" label={translate("table.field.flowStepInstance.executeResult")} />
                        <TextField source="taskNo" label={translate("table.field.flowStepInstance.taskNo")} />
                        <DateField source="createTime" label={translate("table.field.flowStepInstance.createTime")} showTime />
                        <WrapperField cellClassName="opt" label="common.field.opt" onClick={(e) => e.stopPropagation()} >
                            <RowActions onEdit={handleEdit} onDelete={handleDelete} />
                        </WrapperField>
                    </StyledDatagrid>
                </DialogContent>
                <DialogActions>
                    <Button label="ra.action.close" onClick={handleClose} />
                </DialogActions>
            </Dialog>
            {/* Nested Form Dialog */}
            <Dialog open={formOpen} onClose={handleFormClose} fullWidth maxWidth="sm">
                <DialogTitle>
                    {isEdit ? translate("ra.action.edit") : translate("ra.action.create")}
                </DialogTitle>
                <DialogContent>
                    <SimpleForm record={formData} onSubmit={handleSave} toolbar={<CustomToolbar />}>
                        <TextInput source="taskNo" disabled fullWidth label={translate("table.field.flowStepInstance.taskNo")} />
                        <NumberInput source="stepOrder" required fullWidth label={translate("table.field.flowStepInstance.stepOrder")} />
                        <TextInput source="stepCode" required fullWidth label={translate("table.field.flowStepInstance.stepCode")} />
                        <TextInput source="stepName" required fullWidth label={translate("table.field.flowStepInstance.stepName")} />
                        <TextInput source="stepType" required fullWidth label={translate("table.field.flowStepInstance.stepType")} />
                        <SelectInput source="status" label={translate("table.field.flowStepInstance.status")} choices={[
                            { id: 0, name: '排队中' },
                            { id: 1, name: '待执行' },
                            { id: 2, name: '执行中' },
                            { id: 3, name: '执行成功' },
                            { id: 4, name: '执行失败' },
                            { id: 5, name: '已跳过' },
                            { id: 6, name: '已取消' }
                        ]} required fullWidth />
                        <TextInput source="executeResult" fullWidth label={translate("table.field.flowStepInstance.executeResult")} />
                    </SimpleForm>
                </DialogContent>
            </Dialog>
        </React.Fragment>
    );
};
const RowActions = ({ onEdit, onDelete }) => {
    const record = useRecordContext();
    return (
        <Box display="flex">
            <Tooltip title="Edit">
                <IconButton onClick={() => onEdit(record)} size="small" color="primary">
                    <EditIcon fontSize="small" />
                </IconButton>
            </Tooltip>
            {/* If there's an issue with event propagation you might need to handle it in onClick */}
            <Tooltip title="Delete">
                <IconButton onClick={() => onDelete(record)} size="small" color="error">
                    <DeleteIcon fontSize="small" />
                </IconButton>
            </Tooltip>
        </Box>
    );
};
const CustomToolbar = props => (
    <Toolbar {...props}>
        <Button label="ra.action.save" type="submit" variant="contained" />
    </Toolbar>
);
export default FlowStepInstanceModal;
rsf-admin/src/page/task/TaskList.jsx
@@ -46,6 +46,8 @@
import GradingOutlinedIcon from '@mui/icons-material/GradingOutlined';
import StickyDataTable from "@/page/components/StickyDataTable";
import useTableLayout from '@/utils/useTableLayout';
import AccountTreeOutlinedIcon from '@mui/icons-material/AccountTreeOutlined';
import FlowStepInstanceModal from './FlowStepInstanceModal';
const TaskList = (props) => {
    const translate = useTranslate();
@@ -53,6 +55,10 @@
    const [drawerVal, setDrawerVal] = useState(false);
    const [autoExce, setAutoExce] = useState(false);
    const dict = JSON.parse(localStorage.getItem('sys_dicts'))?.filter(dict => (dict.dictTypeCode == 'sys_warehouse_type')) || [];
    // state for FlowStepInstanceModal
    const [flowStepModalOpen, setFlowStepModalOpen] = useState(false);
    const [currentTaskRecord, setCurrentTaskRecord] = useState(null);
    useEffect(() => {
        getConfig()
@@ -136,7 +142,13 @@
                )}
                perPage={DEFAULT_PAGE_SIZE}
            >
                <TableItems drawerVal={drawerVal} />
                <TableItems
                    drawerVal={drawerVal}
                    onOpenFlowStep={(record) => {
                        setCurrentTaskRecord(record);
                        setFlowStepModalOpen(true);
                    }}
                />
            </List>
            <PageDrawer
                title='Task Detail'
@@ -144,6 +156,11 @@
                setDrawerVal={setDrawerVal}
            >
            </PageDrawer>
            <FlowStepInstanceModal
                open={flowStepModalOpen}
                setOpen={setFlowStepModalOpen}
                record={currentTaskRecord}
            />
        </Box>
    )
}
@@ -151,7 +168,7 @@
export default TaskList;
const TableItems = ({ drawerVal }) => {
const TableItems = ({ drawerVal, onOpenFlowStep }) => {
    const omittedFields = ['id', 'createTime', 'createBy$', 'memo', 'robotCode', 'exceStatus', 'expDesc', 'expCode', 'status', 'warehType$', 'orderType', 'order_type', 'orderType$'];
    const { boxMaxWidth, boxMaxHeight } = useTableLayout(drawerVal);
    return (
@@ -201,6 +218,7 @@
                    <CheckButton />
                    <PickButton />
                    <SetTopButton />
                    <FlowStepButton onClick={onOpenFlowStep} />
                </WrapperField>
            </StickyDataTable>
        </Box>
@@ -208,6 +226,21 @@
}
const FlowStepButton = ({ onClick }) => {
    const record = useRecordContext();
    return (
        <Button
            label="toolbar.flowStep"
            onClick={(e) => {
                e.stopPropagation();
                onClick(record);
            }}
        >
            <AccountTreeOutlinedIcon />
        </Button>
    )
}
/**
 * 盘点
 * @returns te
rsf-admin/src/page/taskPathTemplate/TaskPathTemplateList.jsx
@@ -225,26 +225,15 @@
                >
                    {baseColumns
                        .map((column) => (
                            <DataTable.Col
                                key={column.key || column.props.source}
                                source={column.props.source}
                                label={column.props.label}
                                sx={column.props.sx}
                            >
                                {column}
                            </DataTable.Col>
                            column
                        ))
                    }
                    <DataTable.Col
                        source="opt"
                        label="common.field.opt"
                    >
                        <WrapperField source="opt" cellClassName="opt" label="common.field.opt">
                            <ViewFlowButton onClick={onOpenFlow} />
                            <EditButton sx={{ padding: '1px', fontSize: '.75rem' }} />
                            <DeleteButton sx={{ padding: '1px', fontSize: '.75rem' }} mutationMode={OPERATE_MODE} />
                        </WrapperField>
                    </DataTable.Col>
                    <WrapperField source="opt" cellClassName="opt" label="common.field.opt">
                        <ViewFlowButton onClick={onOpenFlow} />
                        <EditButton sx={{ padding: '1px', fontSize: '.75rem' }} />
                        <DeleteButton sx={{ padding: '1px', fontSize: '.75rem' }} mutationMode={OPERATE_MODE} />
                    </WrapperField>
                </StickyDataTable>}
        </Box>
    )
rsf-admin/src/page/taskPathTemplate/TaskTemplateFlowViewer.jsx
@@ -75,15 +75,7 @@
                        }}
                    // Manually handle selection style if needed, or rely on StickyDataTable support
                    >
                        {columns.map((col, index) => (
                            <DataTable.Col
                                key={col.props.source}
                                source={col.props.source}
                                label={col.props.label}
                            >
                                {col}
                            </DataTable.Col>
                        ))}
                        {columns.map((col, index) => col)}
                    </StickyDataTable>
                </Box>
                <Pagination rowsPerPageOptions={[10, 25, 50, 100]} />
rsf-admin/src/page/taskPathTemplateMerge/TaskPathTemplateMergeList.jsx
@@ -187,18 +187,7 @@
                    rowClick={false}
                    hiddenColumns={['id', 'createTime', 'createBy', 'memo', 'statusBool']}
                >
                    {columns
                        .map((column) => (
                            <DataTable.Col
                                key={column.key || column.props.source}
                                source={column.props.source}
                                label={column.props.label}
                                sx={column.props.sx}
                            >
                                {column}
                            </DataTable.Col>
                        ))
                    }
                    {columns}
                </StickyDataTable>}
        </Box>
    )
rsf-server/src/main/java/com/vincent/rsf/server/manager/controller/BasStationAreaController.java
@@ -9,8 +9,10 @@
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.manager.entity.BasStation;
import com.vincent.rsf.server.manager.entity.BasStationArea;
import com.vincent.rsf.server.manager.service.BasStationAreaService;
import com.vincent.rsf.server.manager.service.impl.BasStationServiceImpl;
import com.vincent.rsf.server.system.controller.BaseController;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
@@ -24,6 +26,8 @@
    @Autowired
    private BasStationAreaService basStationAreaService;
    @Autowired
    private BasStationServiceImpl basStationService;
    @PreAuthorize("hasAuthority('manager:basStationArea:list')")
    @PostMapping("/basStationArea/page")
@@ -59,6 +63,13 @@
        basStationArea.setCreateTime(new Date());
        basStationArea.setUpdateBy(getLoginUserId());
        basStationArea.setUpdateTime(new Date());
        List<String> objects = new ArrayList<>();
        for (String station: basStationArea.getStationAlias()) {
            long parseLong = Long.parseLong(station);
            BasStation byId = basStationService.getById(parseLong);
            objects.add(byId.getStationName());
        }
        basStationArea.setStationAliasStaNo(objects);
        if (!basStationAreaService.save(basStationArea)) {
            return R.error("Save Fail");
        }
rsf-server/src/main/java/com/vincent/rsf/server/manager/entity/BasStationArea.java
@@ -177,6 +177,13 @@
    @TableField(typeHandler = JacksonTypeHandler.class)
    private List<String> stationAlias;
    /**
     * 区域包含站点编号集
     */
    @ApiModelProperty(value = "区域包含站点编号集")
    @TableField(typeHandler = JacksonTypeHandler.class)
    private List<String> stationAliasStaNo;
    public BasStationArea() {
    }
rsf-server/src/main/java/com/vincent/rsf/server/manager/schedules/TaskMissionSchedules.java
@@ -173,10 +173,10 @@
                            if (targSiteArea != null && !targSiteArea.isEmpty()) {
                                for (String areaId : targSiteArea) {
                                    BasStationArea basStationArea = basStationAreaService.getById(Long.parseLong(areaId));
                                    if (basStationArea == null || basStationArea.getStationAlias() == null || basStationArea.getStationAlias().isEmpty()) {
                                    if (basStationArea == null || basStationArea.getStationAliasStaNo() == null || basStationArea.getStationAliasStaNo().isEmpty()) {
                                        continue;
                                    }
                                    siteList.put(basStationArea.getStationAreaId(), basStationArea.getStationAlias());
                                    siteList.put(basStationArea.getStationAreaId(), basStationArea.getStationAliasStaNo());
//                                    siteList.addAll(basStationArea.getStationAlias());
                                }
                                if (!siteList.isEmpty()) {
rsf-server/src/main/java/com/vincent/rsf/server/manager/service/impl/TaskServiceImpl.java
@@ -8,7 +8,6 @@
import com.fasterxml.jackson.databind.cfg.CoercionAction;
import com.fasterxml.jackson.databind.cfg.CoercionInputShape;
import com.vincent.rsf.framework.common.Cools;
import com.vincent.rsf.framework.common.DateUtils;
import com.vincent.rsf.server.api.config.RemotesInfoProperties;
import com.vincent.rsf.server.api.controller.erp.params.TaskInParam;
import com.vincent.rsf.server.api.entity.CommonResponse;
@@ -34,7 +33,6 @@
import com.vincent.rsf.server.manager.enums.LocStsType;
import com.vincent.rsf.server.system.entity.Config;
import com.vincent.rsf.server.system.service.ConfigService;
import com.vincent.rsf.server.system.service.impl.ConfigServiceImpl;
import com.vincent.rsf.server.system.utils.SerialRuleUtils;
import com.vincent.rsf.server.system.utils.SystemAuthUtils;
import lombok.Synchronized;
@@ -995,14 +993,14 @@
                } else if (task.getTaskType().equals(TaskType.TASK_TYPE_CROSS_DOCKING_OUT.type)) {
                    //109.备货
                    complateOutStockDocking(task, loginUserId);
                } else if (task.getTaskType().equals(TaskType.TASK_TYPE_CHECK_OUT.type)) {
                } else if (task.getTaskType().equals(TaskType.TASK_TYPE_CHECK_OUT.type) || task.getTaskType().equals(TaskType.TASK_TYPE_PICK_AGAIN_OUT.type)) {
                    //107.盘
//                    pickOrCheckTask(task.getId(), Constants.TASK_TYPE_OUT_CHECK);
                    complateOutStock2(task, loginUserId);
                } else if (task.getTaskType().equals(TaskType.TASK_TYPE_PICK_AGAIN_OUT.type)) {
                    //103.拣选
//                    pickOrCheckTask(task.getId(), Constants.TASK_TYPE_OUT_PICK);
                    complateOutStock2(task, loginUserId);
//                } else if () {
//                    //103.拣选
////                    pickOrCheckTask(task.getId(), Constants.TASK_TYPE_OUT_PICK);
//                    complateOutStock2(task, loginUserId);
                } else {
                    complateOutStock(task, loginUserId);
                }
@@ -1380,7 +1378,7 @@
        Loc loc = locService.getOne(new LambdaQueryWrapper<Loc>()
                .eq(Loc::getCode, task.getOrgLoc()));
        if (Objects.isNull(loc)) {
            throw new CoolException("没有空库位!!");
            throw new CoolException("源库位不存在!!");
        }
        String ruleCode = SerialRuleUtils.generateRuleCode(SerialRuleCode.SYS_TASK_CODE, task);
@@ -1426,7 +1424,7 @@
                if (taskItem.getFieldsIndex().equals(working.getFieldsIndex())) {
                    Double minQty = taskItem.getAnfme();
                    if (!task.getTaskType().equals(TaskType.TASK_TYPE_CHECK_IN.type)) {
                        minQty = Math.round((working.getAnfme() - taskItem.getQty()) * 1000000) / 1000000.0;
                        minQty = Math.round((working.getAnfme() - taskItem.getAnfme()) * 1000000) / 1000000.0;
                    }
                    if (minQty.compareTo(0.0) >= 0) {
                        taskItem.setAnfme(minQty);
rsf-server/src/main/java/com/vincent/rsf/server/manager/utils/LocManageUtil.java
@@ -248,7 +248,7 @@
                        }
                        List<String> targSiteAreaList = new ArrayList<>();
                        for (BasStationArea basStationArea : basStationAreas) {
                            if (basStationArea.getStationAlias().isEmpty()){
                            if (basStationArea.getStationAliasStaNo().isEmpty()){
                                continue;
                            }
                            targSiteAreaList.add(basStationArea.getId().toString());
@@ -259,9 +259,9 @@
                        int count = 0;
                        for (BasStationArea basStationArea : basStationAreas) {
                            count++;
                            if (!basStationArea.getStationAlias().isEmpty()) {
                            if (!basStationArea.getStationAliasStaNo().isEmpty()) {
                                List<OrderOutItemDto.staListDto> maps = new ArrayList<>();
                                for (String site : basStationArea.getStationAlias()) {
                                for (String site : basStationArea.getStationAliasStaNo()) {
                                    OrderOutItemDto.staListDto staListDto = new OrderOutItemDto.staListDto();
                                    staListDto.setStaNo(site);
                                    staListDto.setStaName(site);
@@ -269,7 +269,7 @@
                                }
                                orderOutItemDto.setStaNos(maps);
                                //获取满足条件站点
                                Set<String> stationSet = new HashSet<>(basStationArea.getStationAlias());
                                Set<String> stationSet = new HashSet<>(basStationArea.getStationAliasStaNo());
//                                Set<String> stationSet = basStationArea.getStationAlias().stream().collect(Collectors.toSet());
//                                Set<String> stationSet = deviceSites.stream().map(DeviceSite::getSite).collect(Collectors.toSet());
                                //已使用站点
@@ -295,7 +295,7 @@
//                                        throw new CoolException("站點不存在!!");
                                    } else if (!stas.isEmpty()) {
                                        orderOutItemDto.setTargSiteAreaNow(basStationArea.getStationAreaId());
                                        orderOutItemDto.setSiteNo(basStationArea.getStationAlias().get(0));
                                        orderOutItemDto.setSiteNo(basStationArea.getStationAliasStaNo().get(0));
                                    } else {
                                        throw new CoolException("未找到符合条件站点!!!请检查库区或者路径配置!!!");
                                    }
rsf-server/src/main/java/com/vincent/rsf/server/system/controller/FlowStepInstanceController.java
@@ -25,7 +25,7 @@
    @Autowired
    private FlowStepInstanceService flowStepInstanceService;
    @PreAuthorize("hasAuthority('system:flowStepInstance:list')")
    @PreAuthorize("hasAuthority('manager:task:list')")
    @PostMapping("/flowStepInstance/page")
    public R page(@RequestBody Map<String, Object> map) {
        BaseParam baseParam = buildParam(map, BaseParam.class);
@@ -33,25 +33,25 @@
        return R.ok().add(flowStepInstanceService.page(pageParam, pageParam.buildWrapper(true)));
    }
    @PreAuthorize("hasAuthority('system:flowStepInstance:list')")
    @PreAuthorize("hasAuthority('manager:task:list')")
    @PostMapping("/flowStepInstance/list")
    public R list(@RequestBody Map<String, Object> map) {
        return R.ok().add(flowStepInstanceService.list());
    }
    @PreAuthorize("hasAuthority('system:flowStepInstance:list')")
    @PreAuthorize("hasAuthority('manager:task:list')")
    @PostMapping({"/flowStepInstance/many/{ids}", "/flowStepInstances/many/{ids}"})
    public R many(@PathVariable Long[] ids) {
        return R.ok().add(flowStepInstanceService.listByIds(Arrays.asList(ids)));
    }
    @PreAuthorize("hasAuthority('system:flowStepInstance:list')")
    @PreAuthorize("hasAuthority('manager:task:list')")
    @GetMapping("/flowStepInstance/{id}")
    public R get(@PathVariable("id") Long id) {
        return R.ok().add(flowStepInstanceService.getById(id));
    }
    @PreAuthorize("hasAuthority('system:flowStepInstance:save')")
    @PreAuthorize("hasAuthority('manager:task:save')")
    @OperationLog("Create 子流程步骤实例")
    @PostMapping("/flowStepInstance/save")
    public R save(@RequestBody FlowStepInstance flowStepInstance) {
@@ -63,7 +63,7 @@
        return R.ok("Save Success").add(flowStepInstance);
    }
    @PreAuthorize("hasAuthority('system:flowStepInstance:update')")
    @PreAuthorize("hasAuthority('manager:task:update')")
    @OperationLog("Update 子流程步骤实例")
    @PostMapping("/flowStepInstance/update")
    public R update(@RequestBody FlowStepInstance flowStepInstance) {
@@ -74,7 +74,7 @@
        return R.ok("Update Success").add(flowStepInstance);
    }
    @PreAuthorize("hasAuthority('system:flowStepInstance:remove')")
    @PreAuthorize("hasAuthority('manager:task:remove')")
    @OperationLog("Delete 子流程步骤实例")
    @PostMapping("/flowStepInstance/remove/{ids}")
    public R remove(@PathVariable Long[] ids) {
@@ -84,7 +84,7 @@
        return R.ok("Delete Success").add(ids);
    }
    @PreAuthorize("hasAuthority('system:flowStepInstance:list')")
    @PreAuthorize("hasAuthority('manager:task:list')")
    @PostMapping("/flowStepInstance/query")
    public R query(@RequestParam(required = false) String condition) {
        List<KeyValVo> vos = new ArrayList<>();
@@ -98,7 +98,7 @@
        return R.ok().add(vos);
    }
    @PreAuthorize("hasAuthority('system:flowStepInstance:list')")
    @PreAuthorize("hasAuthority('manager:task:list')")
    @PostMapping("/flowStepInstance/export")
    public void export(@RequestBody Map<String, Object> map, HttpServletResponse response) throws Exception {
        ExcelUtil.build(ExcelUtil.create(flowStepInstanceService.list(), FlowStepInstance.class), response);