| | |
| | | subsystemFlowTemplate: 'SubsystemFlowTemplate', |
| | | flowStepTemplate: 'FlowStepTemplate', |
| | | taskPathTemplateMerge: 'TaskPathTemplateMerge', |
| | | missionFlowStepInstance: 'Mission Flow Steps', |
| | | }, |
| | | table: { |
| | | field: { |
| | |
| | | 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", |
| | |
| | | order: 'Orders', |
| | | modiftySite: 'Modify SiteNo', |
| | | selectWave: 'Select Wave Rule', |
| | | flowStep: "Flow Step", |
| | | |
| | | }, |
| | | placeholder: { |
| | |
| | | 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: "站点区域名称", |
| | |
| | | modiftySite: '修改库口', |
| | | selectWave: '波次规则', |
| | | transformation: "转换", |
| | | flowStep: "流程步骤", |
| | | }, |
| | | placeholder: { |
| | | warehouseAreasCode: "用于库位编码前缀占位符", |
| | |
| | | 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'; |
| | |
| | | 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(() => { |
| | |
| | | } |
| | | }; |
| | | |
| | | // 关闭其他标签 |
| | | 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) |
| | |
| | | // 判断是否有可关闭的标签 |
| | | 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', |
| | |
| | | key={tab.path} |
| | | label={ |
| | | <Box |
| | | onContextMenu={(e) => handleContextMenu(e, tab)} |
| | | sx={{ |
| | | display: 'flex', |
| | | alignItems: 'center', |
| | | gap: 0.5, |
| | | width: '100%', |
| | | }} |
| | | > |
| | | {tab.path === '/dashboard' && ( |
| | |
| | | </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> |
| | | ); |
| | | }; |
| New file |
| | |
| | | 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; |
| | |
| | | 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(); |
| | |
| | | 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() |
| | |
| | | )} |
| | | perPage={DEFAULT_PAGE_SIZE} |
| | | > |
| | | <TableItems drawerVal={drawerVal} /> |
| | | <TableItems |
| | | drawerVal={drawerVal} |
| | | onOpenFlowStep={(record) => { |
| | | setCurrentTaskRecord(record); |
| | | setFlowStepModalOpen(true); |
| | | }} |
| | | /> |
| | | </List> |
| | | <PageDrawer |
| | | title='Task Detail' |
| | |
| | | setDrawerVal={setDrawerVal} |
| | | > |
| | | </PageDrawer> |
| | | <FlowStepInstanceModal |
| | | open={flowStepModalOpen} |
| | | setOpen={setFlowStepModalOpen} |
| | | record={currentTaskRecord} |
| | | /> |
| | | </Box> |
| | | ) |
| | | } |
| | |
| | | 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 ( |
| | |
| | | <CheckButton /> |
| | | <PickButton /> |
| | | <SetTopButton /> |
| | | <FlowStepButton onClick={onOpenFlowStep} /> |
| | | </WrapperField> |
| | | </StickyDataTable> |
| | | </Box> |
| | |
| | | } |
| | | |
| | | |
| | | const FlowStepButton = ({ onClick }) => { |
| | | const record = useRecordContext(); |
| | | return ( |
| | | <Button |
| | | label="toolbar.flowStep" |
| | | onClick={(e) => { |
| | | e.stopPropagation(); |
| | | onClick(record); |
| | | }} |
| | | > |
| | | <AccountTreeOutlinedIcon /> |
| | | </Button> |
| | | ) |
| | | } |
| | | |
| | | /** |
| | | * 盘点 |
| | | * @returns te |
| | |
| | | > |
| | | {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> |
| | | ) |
| | |
| | | }} |
| | | // 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]} /> |
| | |
| | | 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> |
| | | ) |
| | |
| | | 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; |
| | |
| | | |
| | | @Autowired |
| | | private BasStationAreaService basStationAreaService; |
| | | @Autowired |
| | | private BasStationServiceImpl basStationService; |
| | | |
| | | @PreAuthorize("hasAuthority('manager:basStationArea:list')") |
| | | @PostMapping("/basStationArea/page") |
| | |
| | | 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"); |
| | | } |
| | |
| | | @TableField(typeHandler = JacksonTypeHandler.class) |
| | | private List<String> stationAlias; |
| | | |
| | | /** |
| | | * 区域包含站点编号集 |
| | | */ |
| | | @ApiModelProperty(value = "区域包含站点编号集") |
| | | @TableField(typeHandler = JacksonTypeHandler.class) |
| | | private List<String> stationAliasStaNo; |
| | | |
| | | public BasStationArea() { |
| | | } |
| | | |
| | |
| | | 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()) { |
| | |
| | | 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; |
| | |
| | | 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; |
| | |
| | | } 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); |
| | | } |
| | |
| | | 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); |
| | |
| | | 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); |
| | |
| | | } |
| | | List<String> targSiteAreaList = new ArrayList<>(); |
| | | for (BasStationArea basStationArea : basStationAreas) { |
| | | if (basStationArea.getStationAlias().isEmpty()){ |
| | | if (basStationArea.getStationAliasStaNo().isEmpty()){ |
| | | continue; |
| | | } |
| | | targSiteAreaList.add(basStationArea.getId().toString()); |
| | |
| | | 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); |
| | |
| | | } |
| | | 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()); |
| | | //已使用站点 |
| | |
| | | // 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("未找到符合条件站点!!!请检查库区或者路径配置!!!"); |
| | | } |
| | |
| | | @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); |
| | |
| | | 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) { |
| | |
| | | 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) { |
| | |
| | | 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) { |
| | |
| | | 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<>(); |
| | |
| | | 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); |