| | |
| | | locInit: 'loc init', |
| | | init: "Init", |
| | | siteInit: 'site init', |
| | | pathInit: 'Path init', |
| | | batch: 'batch', |
| | | pick: 'Pick', |
| | | check: 'Check', |
| | |
| | | unenable: '禁用', |
| | | locInit: '库位初始化', |
| | | siteInit: '站点初始化', |
| | | pathInit: '路径初始化', |
| | | continue: '继续收货', |
| | | batch: '批量操作', |
| | | confirm: '确认', |
| | |
| | | return normalizePath(path1) === normalizePath(path2); |
| | | }; |
| | | |
| | | const LONG_PRESS_MS = 300; |
| | | |
| | | const TabsBar = () => { |
| | | const location = useLocation(); |
| | | const navigate = useNavigate(); |
| | |
| | | const [contextMenuTab, setContextMenuTab] = useState(null); |
| | | const contextMenuOpenRef = useRef(false); |
| | | contextMenuOpenRef.current = contextMenu !== null; |
| | | |
| | | const [draggingIndex, setDraggingIndex] = useState(null); |
| | | const [dropIndicatorIndex, setDropIndicatorIndex] = useState(null); |
| | | const longPressTimerRef = useRef(null); |
| | | const longPressIndexRef = useRef(null); |
| | | const justFinishedDragRef = useRef(false); |
| | | |
| | | // 在标签页右键,阻止浏览器默认菜单 |
| | | useEffect(() => { |
| | |
| | | |
| | | // 切换标签页 |
| | | const handleTabChange = (event, newValue) => { |
| | | if (justFinishedDragRef.current) { |
| | | justFinishedDragRef.current = false; |
| | | return; |
| | | } |
| | | const targetTab = tabs[newValue]; |
| | | if (targetTab && targetTab.path !== location.pathname) { |
| | | navigate(targetTab.path); |
| | |
| | | return () => document.removeEventListener('mousedown', onDocClick, true); |
| | | }, [contextMenu]); |
| | | |
| | | const clearLongPressTimer = useCallback(() => { |
| | | if (longPressTimerRef.current) { |
| | | clearTimeout(longPressTimerRef.current); |
| | | longPressTimerRef.current = null; |
| | | } |
| | | longPressIndexRef.current = null; |
| | | }, []); |
| | | |
| | | const handleTabPointerDown = useCallback((e, index) => { |
| | | if (index < 0) return; |
| | | longPressIndexRef.current = index; |
| | | longPressTimerRef.current = setTimeout(() => { |
| | | longPressTimerRef.current = null; |
| | | setDraggingIndex(index); |
| | | setDropIndicatorIndex(index); |
| | | }, LONG_PRESS_MS); |
| | | }, []); |
| | | |
| | | const handleTabPointerUp = useCallback(() => { |
| | | clearLongPressTimer(); |
| | | }, [clearLongPressTimer]); |
| | | |
| | | useEffect(() => { |
| | | if (draggingIndex === null) return; |
| | | const getDropIndex = (clientX) => { |
| | | const nodes = tabsBarRef.current?.querySelectorAll('[data-tab-index]'); |
| | | if (!nodes?.length) return draggingIndex; |
| | | for (let i = 0; i < nodes.length; i++) { |
| | | const rect = nodes[i].getBoundingClientRect(); |
| | | const mid = rect.left + rect.width / 2; |
| | | if (clientX <= mid) { |
| | | const drop = i; |
| | | if (draggingIndex === 0) return drop <= 0 ? 0 : draggingIndex; |
| | | return drop <= 0 ? 1 : drop; |
| | | } |
| | | } |
| | | const drop = nodes.length; |
| | | return draggingIndex === 0 ? 0 : drop; |
| | | }; |
| | | const clientXFromEvent = (e) => e.clientX ?? e.touches?.[0]?.clientX; |
| | | const onMove = (e) => { |
| | | const x = clientXFromEvent(e); |
| | | if (x != null) setDropIndicatorIndex(getDropIndex(x)); |
| | | }; |
| | | const onTouchMove = (e) => { |
| | | e.preventDefault(); |
| | | onMove(e); |
| | | }; |
| | | const onUp = () => { |
| | | justFinishedDragRef.current = true; |
| | | setDraggingIndex((di) => { |
| | | setDropIndicatorIndex((dropIdx) => { |
| | | if (di !== null && dropIdx !== null && di !== dropIdx) { |
| | | const newTabs = [...tabs]; |
| | | const [item] = newTabs.splice(di, 1); |
| | | const insertAt = dropIdx > di ? dropIdx - 1 : dropIdx; |
| | | newTabs.splice(insertAt, 0, item); |
| | | const dashboard = newTabs.find((t) => t.path === '/dashboard'); |
| | | if (dashboard && newTabs[0].path !== '/dashboard') { |
| | | const idx = newTabs.indexOf(dashboard); |
| | | newTabs.splice(idx, 1); |
| | | newTabs.unshift(dashboard); |
| | | } |
| | | saveTabs(newTabs); |
| | | setTabs(newTabs); |
| | | } |
| | | return null; |
| | | }); |
| | | return null; |
| | | }); |
| | | }; |
| | | document.addEventListener('mousemove', onMove, true); |
| | | document.addEventListener('mouseup', onUp, true); |
| | | document.addEventListener('touchmove', onTouchMove, { passive: false, capture: true }); |
| | | document.addEventListener('touchend', onUp, true); |
| | | document.addEventListener('touchcancel', onUp, true); |
| | | return () => { |
| | | document.removeEventListener('mousemove', onMove, true); |
| | | document.removeEventListener('mouseup', onUp, true); |
| | | document.removeEventListener('touchmove', onTouchMove, true); |
| | | document.removeEventListener('touchend', onUp, true); |
| | | document.removeEventListener('touchcancel', onUp, true); |
| | | }; |
| | | }, [draggingIndex, tabs]); |
| | | |
| | | return ( |
| | | <Box |
| | | ref={tabsBarRef} |
| | |
| | | key={tab.path} |
| | | label={ |
| | | <Box |
| | | data-tab-index={index} |
| | | onContextMenu={(e) => handleContextMenu(e, tab)} |
| | | onMouseDown={(e) => handleTabPointerDown(e, index)} |
| | | onMouseUp={handleTabPointerUp} |
| | | onMouseLeave={handleTabPointerUp} |
| | | onTouchStart={(e) => handleTabPointerDown(e, index)} |
| | | onTouchEnd={handleTabPointerUp} |
| | | onTouchCancel={handleTabPointerUp} |
| | | sx={{ |
| | | display: 'flex', |
| | | alignItems: 'center', |
| | | gap: 0.5, |
| | | width: '100%', |
| | | ...(draggingIndex === index && { opacity: 0.7 }), |
| | | }} |
| | | > |
| | | {tab.path === '/dashboard' && ( |
| | |
| | | /> |
| | | </Stack> |
| | | <Stack direction='row' gap={2}> |
| | | <TextInput |
| | | label="table.field.deviceSite.target" |
| | | source="target" |
| | | parse={v => v} |
| | | /> |
| | | </Stack> |
| | | <Stack direction='row' gap={2}> |
| | | <NumberInput |
| | | label="table.field.deviceSite.channel" |
| | | source="channel" |
| | | /> |
| | | </Stack> |
| | | <Stack direction='row' gap={2}> |
| | | <WarehouseSelect |
| | | label={translate("table.field.deviceSite.areaIdStart")} |
| | | name="areaIdStart" |
| | |
| | | DeleteButton, |
| | | Button |
| | | } from 'react-admin'; |
| | | import { Box, Typography, Card, Stack } from '@mui/material'; |
| | | import { Box, Typography, Card, Stack, Button as MuiButton } from '@mui/material'; |
| | | import { styled } from '@mui/material/styles'; |
| | | import DeviceSiteCreate from "./DeviceSiteCreate"; |
| | | import DeviceSitePanel from "./DeviceSitePanel"; |
| | |
| | | import * as Common from '@/utils/common'; |
| | | import InitModal from "./InitModal"; |
| | | import CabinIcon from '@mui/icons-material/Cabin'; |
| | | import ContentCopyIcon from '@mui/icons-material/ContentCopy'; |
| | | |
| | | const StyledDatagrid = styled(DatagridConfigurable)(({ theme }) => ({ |
| | | '& .css-1vooibu-MuiSvgIcon-root': { |
| | |
| | | '& .column-name': { |
| | | }, |
| | | '& .opt': { |
| | | width: 200 |
| | | width: 260, |
| | | minWidth: 260, |
| | | overflow: 'visible', |
| | | '& > *': { flexShrink: 0 } |
| | | }, |
| | | })); |
| | | |
| | |
| | | />, |
| | | ] |
| | | |
| | | const CopyButton = ({ setInitCopyData, setInitDialogOpen }) => { |
| | | const record = useRecordContext(); |
| | | const translate = useTranslate(); |
| | | if (!record) return null; |
| | | const label = translate('toolbar.copy') || '复制'; |
| | | return ( |
| | | <MuiButton |
| | | size="small" |
| | | color="primary" |
| | | sx={{ |
| | | padding: '2px 8px', |
| | | fontSize: '.75rem', |
| | | minWidth: 'auto', |
| | | flexShrink: 0, |
| | | '& .MuiButton-startIcon': { marginRight: 0.5 }, |
| | | }} |
| | | startIcon={<ContentCopyIcon fontSize="small" />} |
| | | onClick={(e) => { |
| | | e.stopPropagation(); |
| | | setInitCopyData({ |
| | | rows: [{ |
| | | deviceSiteName: record.deviceSite ?? '', |
| | | siteName: record.site ?? '', |
| | | target: record.target ?? '', |
| | | }], |
| | | channel: record.channel != null ? String(record.channel) : '', |
| | | type: record.type, |
| | | typeIds: record.type != null ? [record.type] : [], |
| | | deviceType: record.device ?? '', |
| | | deviceCode: record.deviceCode ?? '', |
| | | areaIdStart: record.areaIdStart, |
| | | areaIdEnd: record.areaIdEnd, |
| | | name: record.name ?? '', |
| | | wcsCode: record.target ?? '', |
| | | label: record.label ?? '', |
| | | }); |
| | | setInitDialogOpen(true); |
| | | }} |
| | | > |
| | | <span style={{ whiteSpace: 'nowrap' }}>{label}</span> |
| | | </MuiButton> |
| | | ); |
| | | }; |
| | | |
| | | const DeviceSiteList = () => { |
| | | const translate = useTranslate(); |
| | | |
| | | const [createDialog, setCreateDialog] = useState(false); |
| | | const [drawerVal, setDrawerVal] = useState(false); |
| | | const [initDialog, setInitDialog] = useState(false); |
| | | const [initCopyData, setInitCopyData] = useState(null); |
| | | |
| | | return ( |
| | | <Box display="flex"> |
| | |
| | | mt: 2 |
| | | }} |
| | | onClick={() => { setInitDialog(true) }}> |
| | | {translate('toolbar.siteInit')} |
| | | {translate('toolbar.pathInit')} |
| | | </Button> |
| | | </Box> |
| | | } |
| | |
| | | preferenceKey='deviceSite' |
| | | bulkActionButtons={() => <BulkDeleteButton mutationMode={OPERATE_MODE} />} |
| | | rowClick={(id, resource, record) => false} |
| | | omit={['id', 'createTime', 'createBy', 'memo', 'label','name','target','statusBool','updateBy']} |
| | | omit={['id', 'createTime', 'createBy', 'memo', 'statusBool', 'updateBy']} |
| | | > |
| | | <NumberField source="id" /> |
| | | <TextField source="site" label="table.field.deviceSite.site" /> |
| | |
| | | <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"> |
| | | <CopyButton setInitCopyData={setInitCopyData} setInitDialogOpen={setInitDialog} /> |
| | | <EditButton sx={{ padding: '1px', fontSize: '.75rem' }} /> |
| | | <DeleteButton sx={{ padding: '1px', fontSize: '.75rem' }} mutationMode={OPERATE_MODE} /> |
| | | </WrapperField> |
| | |
| | | <InitModal |
| | | open={initDialog} |
| | | setOpen={setInitDialog} |
| | | initialData={initCopyData} |
| | | onClose={() => setInitCopyData(null)} |
| | | /> |
| | | </Box> |
| | | ) |
| | |
| | | |
| | | return ( |
| | | <> |
| | | <Button onClick={() => setCreateDialog(true)} label={"toolbar.siteInit"}> |
| | | <Button onClick={() => setCreateDialog(true)} label={"toolbar.pathInit"}> |
| | | <CabinIcon /> |
| | | </Button> |
| | | <InitModal |
| | |
| | | import React, { useState, useRef, useEffect, useMemo } from "react"; |
| | | import React, { useState, useEffect } from "react"; |
| | | import { |
| | | CreateBase, |
| | | useTranslate, |
| | | TextInput, |
| | | NumberInput, |
| | | BooleanInput, |
| | | DateInput, |
| | | SaveButton, |
| | | SelectInput, |
| | | ReferenceInput, |
| | | ReferenceArrayInput, |
| | | AutocompleteInput, |
| | | Toolbar, |
| | | required, |
| | | useDataProvider, |
| | | useNotify, |
| | | Form, |
| | | useCreateController, |
| | | useListContext, |
| | | useNotify, |
| | | useRefresh, |
| | | SelectArrayInput |
| | | TextInput, |
| | | SelectInput, |
| | | } from 'react-admin'; |
| | | import { |
| | | Dialog, |
| | |
| | | DialogContent, |
| | | DialogTitle, |
| | | Grid, |
| | | TextField, |
| | | Box, |
| | | Button, |
| | | Paper, |
| | |
| | | TableBody, |
| | | TableRow, |
| | | TableCell, |
| | | Tooltip, |
| | | IconButton, |
| | | styled |
| | | |
| | | |
| | | MenuItem, |
| | | Select, |
| | | FormControl, |
| | | TextField, |
| | | } from '@mui/material'; |
| | | import DialogCloseButton from "../../components/DialogCloseButton"; |
| | | import DictionarySelect from "../../components/DictionarySelect"; |
| | | import { useForm, Controller, useWatch, FormProvider, useFormContext } from "react-hook-form"; |
| | | import SaveIcon from '@mui/icons-material/Save'; |
| | | import request from '@/utils/request'; |
| | | import { Add, Edit, Delete } from '@mui/icons-material'; |
| | | import _ from 'lodash'; |
| | | import { DataGrid } from '@mui/x-data-grid'; |
| | | import { Add, Delete } from '@mui/icons-material'; |
| | | import { ReferenceInput, AutocompleteInput } from 'react-admin'; |
| | | |
| | | const defaultRow = () => ({ deviceSite: '', site: '', target: '' }); |
| | | |
| | | |
| | | |
| | | const InitModal = ({ open, setOpen }) => { |
| | | const InitModal = ({ open, setOpen, initialData = null, onClose }) => { |
| | | const refresh = useRefresh(); |
| | | const translate = useTranslate(); |
| | | |
| | | |
| | | const notify = useNotify(); |
| | | const [disabled, setDisabled] = useState(false) |
| | | const [disabled, setDisabled] = useState(false); |
| | | const [rows, setRows] = useState([defaultRow()]); |
| | | const [stationOptions, setStationOptions] = useState([]); |
| | | |
| | | useEffect(() => { |
| | | if (!open) return; |
| | | request.post('/basStation/list', {}) |
| | | .then((res) => { |
| | | if (res?.data?.code === 200 && res?.data?.data) { |
| | | const list = Array.isArray(res.data.data) ? res.data.data : (res.data.data?.records || []); |
| | | const opts = list.map((item) => ({ id: item.id, stationName: item.stationName ?? item.name ?? item.id })); |
| | | setStationOptions(opts); |
| | | if (initialData?.rows?.length && opts.length) { |
| | | const resolved = initialData.rows.map((r) => { |
| | | const deviceSiteId = r.deviceSite || (r.deviceSiteName && opts.find((o) => o.stationName === r.deviceSiteName)?.id); |
| | | const siteId = r.site || (r.siteName && opts.find((o) => o.stationName === r.siteName)?.id); |
| | | return { |
| | | deviceSite: deviceSiteId != null ? String(deviceSiteId) : '', |
| | | site: siteId != null ? String(siteId) : '', |
| | | target: r.target ?? '', |
| | | }; |
| | | }); |
| | | setRows(resolved.length ? resolved : [defaultRow()]); |
| | | } |
| | | } |
| | | }) |
| | | .catch(() => {}); |
| | | }, [open, initialData]); |
| | | |
| | | useEffect(() => { |
| | | if (open && !initialData?.rows?.length) { |
| | | setRows([defaultRow()]); |
| | | } |
| | | }, [open, initialData]); |
| | | |
| | | const handleClose = (event, reason) => { |
| | | if (reason !== "backdropClick") { |
| | | setOpen(false); |
| | | if (typeof onClose === 'function') onClose(); |
| | | } |
| | | }; |
| | | |
| | | const handleReset = (e) => { |
| | | e.preventDefault(); |
| | | const addRow = () => setRows((prev) => [...prev, defaultRow()]); |
| | | const removeRow = (index) => { |
| | | if (rows.length <= 1) return; |
| | | setRows((prev) => prev.filter((_, i) => i !== index)); |
| | | }; |
| | | |
| | | const handleChange = (value, name) => { |
| | | setFormData((prevData) => ({ |
| | | ...prevData, |
| | | [name]: value |
| | | })); |
| | | const changeRow = (index, field, value) => { |
| | | setRows((prev) => prev.map((r, i) => (i === index ? { ...r, [field]: value } : r))); |
| | | }; |
| | | |
| | | const handleSubmit = async (value) => { |
| | | setDisabled(true) |
| | | const res = await request.post(`/deviceSite/init`, value); |
| | | const validRows = rows.filter( |
| | | (r) => (r.deviceSite !== '' && r.deviceSite != null) && (r.site !== '' && r.site != null) && (r.target !== '' && (r.target || '').trim() !== '') |
| | | ); |
| | | if (validRows.length === 0) { |
| | | notify('请至少填写一行完整的设备站点、作业站点、目标站点', { type: 'error' }); |
| | | return; |
| | | } |
| | | if (!(value.channel != null && String(value.channel).trim() !== '')) { |
| | | notify('巷道不能为空,多个请用英文逗号分隔,如 1,2,3', { type: 'error' }); |
| | | return; |
| | | } |
| | | setDisabled(true); |
| | | const payload = { |
| | | ...value, |
| | | // 名称、wcs编号、站点标签 已从界面注释,不再提交 |
| | | name: null, |
| | | wcsCode: null, |
| | | label: null, |
| | | rows: validRows.map((r) => ({ |
| | | deviceSite: String(r.deviceSite), |
| | | site: String(r.site), |
| | | target: String(r.target || '').trim(), |
| | | })), |
| | | }; |
| | | const res = await request.post('/deviceSite/init', payload); |
| | | if (res?.data?.code === 200) { |
| | | setOpen(false); |
| | | refresh(); |
| | | } else { |
| | | notify(res.data.msg); |
| | | notify(res?.data?.msg || '初始化失败'); |
| | | } |
| | | setDisabled(false) |
| | | } |
| | | setDisabled(false); |
| | | }; |
| | | |
| | | const formDefaultValues = initialData ? { |
| | | channel: initialData.channel != null ? String(initialData.channel) : '', |
| | | deviceType: initialData.deviceType ?? '', |
| | | // deviceCode 接驳位已注释,不默认填入 |
| | | // deviceCode: initialData.deviceCode ?? '', |
| | | areaIdStart: initialData.areaIdStart ?? undefined, |
| | | areaIdEnd: initialData.areaIdEnd ?? undefined, |
| | | flagInit: 0, |
| | | // name、wcsCode、label 已注释,不默认填入 |
| | | // name: initialData.name ?? '', |
| | | // wcsCode: initialData.wcsCode ?? '', |
| | | // label: initialData.label ?? '', |
| | | typeIds: Array.isArray(initialData.typeIds) ? initialData.typeIds : (initialData.type != null ? [initialData.type] : undefined), |
| | | } : undefined; |
| | | |
| | | return ( |
| | | <Dialog open={open} maxWidth="lg" fullWidth> |
| | | <Form onSubmit={handleSubmit}> |
| | | <Dialog open={open} maxWidth="lg" fullWidth onClose={handleClose}> |
| | | <Form onSubmit={handleSubmit} defaultValues={formDefaultValues} key={open ? (initialData ? 'copy' : 'new') : 'closed'}> |
| | | <DialogCloseButton onClose={handleClose} /> |
| | | <DialogTitle>{translate('toolbar.siteInit')}</DialogTitle> |
| | | <DialogTitle>{translate('toolbar.pathInit')}</DialogTitle> |
| | | <DialogContent sx={{ mt: 2 }}> |
| | | <Box sx={{ display: 'flex', flexDirection: 'column', gap: 3 }}> |
| | | <Grid container spacing={2}> |
| | | <Grid item xs={12}> |
| | | <TableContainer component={Paper} variant="outlined"> |
| | | <Table size="small"> |
| | | <TableHead> |
| | | <TableRow> |
| | | <TableCell>{translate('table.field.deviceSite.deviceSite')}</TableCell> |
| | | <TableCell>{translate('table.field.deviceSite.site')}</TableCell> |
| | | <TableCell>{translate('table.field.deviceSite.target')}</TableCell> |
| | | <TableCell width={80}>操作</TableCell> |
| | | </TableRow> |
| | | </TableHead> |
| | | <TableBody> |
| | | {rows.map((r, index) => ( |
| | | <TableRow key={index}> |
| | | <TableCell> |
| | | <FormControl fullWidth size="small"> |
| | | <Select |
| | | displayEmpty |
| | | value={r.deviceSite ?? ''} |
| | | onChange={(e) => changeRow(index, 'deviceSite', e.target.value)} |
| | | renderValue={(v) => { |
| | | const o = stationOptions.find((s) => String(s.id) === String(v)); |
| | | return o ? o.stationName : (v ? String(v) : ''); |
| | | }} |
| | | > |
| | | <MenuItem value="">请选择</MenuItem> |
| | | {stationOptions.map((opt) => ( |
| | | <MenuItem key={opt.id} value={opt.id}> |
| | | {opt.stationName} |
| | | </MenuItem> |
| | | ))} |
| | | </Select> |
| | | </FormControl> |
| | | </TableCell> |
| | | <TableCell> |
| | | <FormControl fullWidth size="small"> |
| | | <Select |
| | | displayEmpty |
| | | value={r.site ?? ''} |
| | | onChange={(e) => changeRow(index, 'site', e.target.value)} |
| | | renderValue={(v) => { |
| | | const o = stationOptions.find((s) => String(s.id) === String(v)); |
| | | return o ? o.stationName : (v ? String(v) : ''); |
| | | }} |
| | | > |
| | | <MenuItem value="">请选择</MenuItem> |
| | | {stationOptions.map((opt) => ( |
| | | <MenuItem key={opt.id} value={opt.id}> |
| | | {opt.stationName} |
| | | </MenuItem> |
| | | ))} |
| | | </Select> |
| | | </FormControl> |
| | | </TableCell> |
| | | <TableCell> |
| | | <TextField |
| | | size="small" |
| | | fullWidth |
| | | placeholder={translate('table.field.deviceSite.target')} |
| | | value={r.target ?? ''} |
| | | onChange={(e) => changeRow(index, 'target', e.target.value)} |
| | | /> |
| | | </TableCell> |
| | | <TableCell> |
| | | <IconButton |
| | | size="small" |
| | | onClick={() => removeRow(index)} |
| | | disabled={rows.length <= 1} |
| | | > |
| | | <Delete fontSize="small" /> |
| | | </IconButton> |
| | | </TableCell> |
| | | </TableRow> |
| | | ))} |
| | | </TableBody> |
| | | </Table> |
| | | </TableContainer> |
| | | <Box sx={{ display: 'flex', justifyContent: 'flex-end', mt: 1 }}> |
| | | <Button size="small" startIcon={<Add />} onClick={addRow}> |
| | | 新增一行 |
| | | </Button> |
| | | </Box> |
| | | </Grid> |
| | | |
| | | {/* 名称、wcs编号、站点标签 已注释,不显示也不默认填入 */} |
| | | {/* <Grid item xs={4}> |
| | | <TextInput |
| | | source="name" |
| | | label="table.field.deviceSite.name" |
| | | size="small" |
| | | fullWidth |
| | | /> |
| | | </Grid> |
| | | <Grid item xs={4}> |
| | | <TextInput |
| | | source="wcsCode" |
| | | label="table.field.deviceSite.wcsCode" |
| | | size="small" |
| | | fullWidth |
| | | /> |
| | | </Grid> |
| | | <Grid item xs={4}> |
| | | <TextInput |
| | | source="label" |
| | | label="table.field.deviceSite.label" |
| | | size="small" |
| | | fullWidth |
| | | /> |
| | | </Grid> */} |
| | | |
| | | <Grid item xs={4}> |
| | | <DictionarySelect |
| | | label={translate("table.field.deviceSite.type")} |
| | |
| | | dictTypeCode="sys_task_type" |
| | | multiple |
| | | /> |
| | | |
| | | </Grid> |
| | | |
| | | <Grid item xs={4}> |
| | | <DictionarySelect |
| | | label={translate("table.field.deviceSite.device")} |
| | |
| | | dictTypeCode="sys_device_type" |
| | | /> |
| | | </Grid> |
| | | {/* 接驳位 deviceCode 已注释 */} |
| | | {/* <Grid item xs={4}> |
| | | <TextInput |
| | | source="deviceCode" |
| | | label="table.field.deviceSite.deviceCode" |
| | | size="small" |
| | | fullWidth |
| | | /> |
| | | </Grid> */} |
| | | <Grid item xs={4}> |
| | | <TextInput |
| | | label={translate("table.field.deviceSite.channel")} |
| | | name="channel" |
| | | source="channel" |
| | | label="table.field.deviceSite.channel" |
| | | size="small" |
| | | type="number" |
| | | fullWidth |
| | | placeholder="英文逗号分隔多个,如 1,2,3" |
| | | /> |
| | | </Grid> |
| | | <Grid item xs={4}> |
| | | <ReferenceInput |
| | | source="deviceSites" |
| | | reference="basStation" |
| | | > |
| | | <SelectInput |
| | | label="table.field.deviceSite.deviceSite" |
| | | optionText="stationName" |
| | | /> |
| | | </ReferenceInput> |
| | | </Grid> |
| | | <Grid item xs={4}> |
| | | <ReferenceInput |
| | | source="site" |
| | | reference="basStation" |
| | | > |
| | | <SelectInput |
| | | label="table.field.deviceSite.site" |
| | | optionText="stationName" |
| | | /> |
| | | </ReferenceInput> |
| | | |
| | | </Grid> |
| | | <Grid item xs={4}> |
| | | <TextInput |
| | | label={translate("table.field.deviceSite.target")} |
| | | name="target" |
| | | placeholder={translate('common.action.inputPlaceholder')} |
| | | size="small" |
| | | // type="number" |
| | | source="flagInit" |
| | | label="table.field.deviceSite.flagInit" |
| | | choices={[ |
| | | { id: 0, name: '否' }, |
| | | { id: 1, name: '是' }, |
| | | ]} |
| | | /> |
| | | </Grid> |
| | | <Grid item xs={6} display="flex" gap={1}> |
| | |
| | | <AutocompleteInput optionValue="id" optionText="name" label={translate('table.field.deviceSite.areaIdEnd')} /> |
| | | </ReferenceInput> |
| | | </Grid> |
| | | <Grid item xs={4}> |
| | | <SelectInput |
| | | label="table.field.deviceSite.flagInit" |
| | | source="flagInit" |
| | | choices={[ |
| | | { id: 0, name: '否' }, |
| | | { id: 1, name: '是' }, |
| | | ]} |
| | | /> |
| | | </Grid> |
| | | |
| | | |
| | | </Grid> |
| | | |
| | | </Box> |
| | | </DialogContent> |
| | | <DialogActions sx={{ position: 'sticky', bottom: 0, backgroundColor: 'background.paper', zIndex: 1000 }}> |
| | | <Box sx={{ width: '100%', display: 'flex', justifyContent: 'space-between' }}> |
| | | <Button disabled={disabled} type="submit" variant="contained" startIcon={<SaveIcon />} > |
| | | <Button disabled={disabled} type="submit" variant="contained" startIcon={<SaveIcon />}> |
| | | {translate('toolbar.confirm')} |
| | | </Button> |
| | | </Box> |
| | | |
| | | </DialogActions> |
| | | </Form> |
| | | </Dialog> |
| | | ); |
| | | } |
| | | }; |
| | | |
| | | export default InitModal; |
| | | export default InitModal; |
| | |
| | | import com.vincent.rsf.server.system.controller.BaseController; |
| | | import org.springframework.beans.factory.annotation.Autowired; |
| | | import org.springframework.security.access.prepost.PreAuthorize; |
| | | import io.swagger.annotations.Api; |
| | | import org.springframework.web.bind.annotation.*; |
| | | |
| | | import javax.servlet.http.HttpServletResponse; |
| | |
| | | import java.util.stream.Collectors; |
| | | |
| | | @RestController |
| | | @Api(tags = "站点管理") |
| | | public class BasStationController extends BaseController { |
| | | |
| | | @Autowired |
| | |
| | | import com.vincent.rsf.server.system.controller.BaseController; |
| | | import io.swagger.annotations.Api; |
| | | import io.swagger.annotations.ApiOperation; |
| | | import org.slf4j.Logger; |
| | | import org.slf4j.LoggerFactory; |
| | | import org.springframework.beans.factory.annotation.Autowired; |
| | | import org.springframework.security.access.prepost.PreAuthorize; |
| | | import org.springframework.web.bind.annotation.*; |
| | |
| | | import java.util.*; |
| | | |
| | | @RestController |
| | | @Api(tags = "站点管理") |
| | | @Api(tags = "路径管理") |
| | | public class DeviceSiteController extends BaseController { |
| | | |
| | | private static final Logger log = LoggerFactory.getLogger(DeviceSiteController.class); |
| | | |
| | | @Autowired |
| | | private DeviceSiteService deviceSiteService; |
| | |
| | | } |
| | | |
| | | @PreAuthorize("hasAuthority('manager:deviceSite:save')") |
| | | @ApiOperation("站点初始化") |
| | | @ApiOperation("路径初始化") |
| | | @PostMapping("/deviceSite/init") |
| | | public R initDeviceSite(@RequestBody DeviceSiteParame param) { |
| | | if (Objects.isNull(param)) { |
| | |
| | | package com.vincent.rsf.server.manager.controller.params; |
| | | |
| | | import io.swagger.annotations.ApiModelProperty; |
| | | import lombok.AccessLevel; |
| | | import lombok.Data; |
| | | import lombok.Setter; |
| | | import lombok.experimental.Accessors; |
| | | |
| | | import java.util.ArrayList; |
| | | import java.util.List; |
| | | |
| | | @Data |
| | |
| | | @ApiModelProperty("设备类型") |
| | | private String deviceType; |
| | | |
| | | /** 作业类型,前端可能传字符串数组如 ["109"],通过 setter 统一转为 Long */ |
| | | @Setter(AccessLevel.NONE) |
| | | @ApiModelProperty("作业类型") |
| | | private List<Long> typeIds; |
| | | |
| | | /** 兼容前端传 ["109"] 等字符串数组 */ |
| | | public void setTypeIds(List<?> typeIds) { |
| | | if (typeIds == null) { |
| | | this.typeIds = null; |
| | | return; |
| | | } |
| | | this.typeIds = new ArrayList<>(); |
| | | for (Object o : typeIds) { |
| | | if (o == null) continue; |
| | | if (o instanceof Number) { |
| | | this.typeIds.add(((Number) o).longValue()); |
| | | } else { |
| | | this.typeIds.add(Long.parseLong(o.toString().trim())); |
| | | } |
| | | } |
| | | } |
| | | |
| | | @ApiModelProperty("作业站点") |
| | | private String site; |
| | |
| | | @ApiModelProperty("目标站点") |
| | | private String target; |
| | | |
| | | @ApiModelProperty("巷道") |
| | | private Integer channel; |
| | | /** 多行:每行一组 设备站点、作业站点、目标站点,每行对应一条记录(再按巷道、作业类型展开) */ |
| | | @ApiModelProperty("路径行列表:设备站点、作业站点、目标站点为一组,每行一条") |
| | | private List<DeviceSiteRowParam> rows; |
| | | |
| | | /** 巷道,英文逗号分隔多个,如 "1,2,3" */ |
| | | @ApiModelProperty("巷道,英文逗号分隔多个") |
| | | private String channel; |
| | | |
| | | @ApiModelProperty("源库区") |
| | | private Long areaIdStart; |
| | |
| | | @ApiModelProperty("目标库区") |
| | | private Long areaIdEnd; |
| | | |
| | | @ApiModelProperty("名称(公共,用于本批生成的所有路径)") |
| | | private String name; |
| | | |
| | | @ApiModelProperty("WCS编号(公共)") |
| | | private String wcsCode; |
| | | |
| | | @ApiModelProperty("站点标签(公共)") |
| | | private String label; |
| | | |
| | | } |
| New file |
| | |
| | | package com.vincent.rsf.server.manager.controller.params; |
| | | |
| | | import io.swagger.annotations.ApiModelProperty; |
| | | import lombok.Data; |
| | | import lombok.experimental.Accessors; |
| | | |
| | | /** |
| | | * 路径管理初始化:单行(设备站点、作业站点、目标站点一组) |
| | | */ |
| | | @Data |
| | | @Accessors(chain = true) |
| | | public class DeviceSiteRowParam { |
| | | |
| | | @ApiModelProperty("设备站点(站点ID)") |
| | | private String deviceSite; |
| | | |
| | | @ApiModelProperty("作业站点(站点ID)") |
| | | private String site; |
| | | |
| | | @ApiModelProperty("目标站点") |
| | | private String target; |
| | | } |
| | |
| | | import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; |
| | | import com.vincent.rsf.framework.exception.CoolException; |
| | | import com.vincent.rsf.server.manager.controller.params.DeviceSiteParame; |
| | | import com.vincent.rsf.server.manager.controller.params.DeviceSiteRowParam; |
| | | import com.vincent.rsf.server.manager.entity.BasStation; |
| | | import com.vincent.rsf.server.manager.mapper.DeviceSiteMapper; |
| | | import com.vincent.rsf.server.manager.entity.DeviceSite; |
| | |
| | | import org.springframework.transaction.annotation.Transactional; |
| | | |
| | | import java.util.ArrayList; |
| | | import java.util.Arrays; |
| | | import java.util.Collections; |
| | | import java.util.List; |
| | | import java.util.Objects; |
| | | |
| | | @Service("deviceSiteService") |
| | | public class DeviceSiteServiceImpl extends ServiceImpl<DeviceSiteMapper, DeviceSite> implements DeviceSiteService { |
| | | |
| | | /** 与表 man_device_site.target 列长度一致,超长截断避免 Data too long */ |
| | | private static final int TARGET_MAX_LENGTH = 255; |
| | | |
| | | @Autowired |
| | | private BasStationService basStationService; |
| | | |
| | | |
| | | /** |
| | | * 初始化站点 |
| | | * @param param |
| | | * @return |
| | | * 初始化站点:多行(设备站点、作业站点、目标站点为一组),巷道英文逗号分隔,每组×巷道×作业类型生成多条记录 |
| | | */ |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public boolean initSites(DeviceSiteParame param) { |
| | | if (param.getFlagInit() == 1) { |
| | | if (param.getFlagInit() != null && param.getFlagInit() == 1) { |
| | | List<DeviceSite> list = this.list(new LambdaQueryWrapper<DeviceSite>().select(DeviceSite::getId).last("limit 1")); |
| | | if (!list.isEmpty()) { |
| | | if (!this.remove(new LambdaQueryWrapper<>())) { |
| | |
| | | } |
| | | } |
| | | } |
| | | if (Objects.isNull(param.getDeviceSites()) || StringUtils.isBlank(param.getDeviceSites())) { |
| | | throw new CoolException("初始化失败: 设备作业站点不能为空!!"); |
| | | } |
| | | if (Objects.isNull(param.getSite()) || StringUtils.isBlank(param.getSite())) { |
| | | throw new CoolException("初始化失败: 作业站点不能为空!!"); |
| | | } |
| | | if (Objects.isNull(param.getTypeIds()) || param.getTypeIds().isEmpty()) { |
| | | throw new CoolException("初始化失败: 作业类型不能为空!!"); |
| | | } |
| | | if (Objects.isNull(param.getTarget()) || param.getTarget().isEmpty()) { |
| | | throw new CoolException("初始化失败: 目标站点不能为空!!"); |
| | | if (StringUtils.isBlank(param.getChannel())) { |
| | | throw new CoolException("初始化失败: 巷道不能为空!!"); |
| | | } |
| | | List<String> sites = Arrays.asList(StringUtils.split(param.getSite(), ",")); |
| | | List<String> dvSites = Arrays.asList(StringUtils.split(param.getDeviceSites(), ",")); |
| | | List<String> targets = Arrays.asList(StringUtils.split(param.getTarget(), ",")); |
| | | List<DeviceSite> deviceSites = new ArrayList<>(); |
| | | for (String site : sites) { |
| | | BasStation basStation = basStationService.getById(site); |
| | | if (null == basStation) { |
| | | throw new CoolException("初始化失败: 站点未找到!!"); |
| | | List<Integer> channels = parseChannels(param.getChannel()); |
| | | if (channels.isEmpty()) { |
| | | throw new CoolException("初始化失败: 巷道格式错误,请用英文逗号分隔,如 1,2,3!!"); |
| | | } |
| | | |
| | | List<DeviceSiteRowParam> rows = param.getRows(); |
| | | if (Objects.isNull(rows) || rows.isEmpty()) { |
| | | throw new CoolException("初始化失败: 请至少添加一行(设备站点、作业站点、目标站点)!!"); |
| | | } |
| | | |
| | | List<DeviceSite> deviceSites = new ArrayList<>(); |
| | | for (DeviceSiteRowParam row : rows) { |
| | | if (StringUtils.isBlank(row.getDeviceSite()) || StringUtils.isBlank(row.getSite()) || StringUtils.isBlank(row.getTarget())) { |
| | | throw new CoolException("初始化失败: 每行的设备站点、作业站点、目标站点均不能为空!!"); |
| | | } |
| | | for (String deviceSite : dvSites) { |
| | | BasStation basStation2 = basStationService.getById(deviceSite); |
| | | if (null == basStation2) { |
| | | throw new CoolException("初始化失败: 作业站点未找到!!"); |
| | | } |
| | | for (Long id : param.getTypeIds()) { |
| | | for (String target : targets) { |
| | | DeviceSite site1 = new DeviceSite(); |
| | | site1.setType(id + "") |
| | | .setSite(basStation.getStationName()) |
| | | .setDevice(param.getDeviceType()) |
| | | .setDeviceSite(basStation2.getStationName()) |
| | | .setTarget(target) |
| | | .setDeviceCode(param.getDeviceCode()) |
| | | .setAreaIdStart(param.getAreaIdStart()) |
| | | .setAreaIdEnd(param.getAreaIdEnd()) |
| | | .setChannel(param.getChannel()) |
| | | ; |
| | | deviceSites.add(site1); |
| | | } |
| | | BasStation siteStation = basStationService.getById(Long.parseLong(row.getSite().trim())); |
| | | if (siteStation == null) { |
| | | throw new CoolException("初始化失败: 作业站点未找到!!"); |
| | | } |
| | | BasStation deviceStation = basStationService.getById(Long.parseLong(row.getDeviceSite().trim())); |
| | | if (deviceStation == null) { |
| | | throw new CoolException("初始化失败: 设备站点未找到!!"); |
| | | } |
| | | for (Long typeId : param.getTypeIds()) { |
| | | for (Integer ch : channels) { |
| | | DeviceSite ds = new DeviceSite(); |
| | | String siteName = siteStation.getStationName(); |
| | | String deviceSiteName = deviceStation.getStationName(); |
| | | String targetVal = truncate(row.getTarget().trim(), TARGET_MAX_LENGTH); |
| | | String commonName = StringUtils.isNotBlank(param.getName()) ? param.getName().trim() : null; |
| | | String commonLabel = StringUtils.isNotBlank(param.getLabel()) ? param.getLabel().trim() : null; |
| | | ds.setType(String.valueOf(typeId)) |
| | | .setSite(siteName) |
| | | .setDevice(param.getDeviceType()) |
| | | .setDeviceSite(deviceSiteName) |
| | | .setTarget(targetVal) |
| | | .setDeviceCode(param.getDeviceCode()) |
| | | .setAreaIdStart(param.getAreaIdStart()) |
| | | .setAreaIdEnd(param.getAreaIdEnd()) |
| | | .setChannel(ch) |
| | | .setName(commonName != null ? commonName : (deviceSiteName + "-" + siteName + "-" + targetVal + "-" + ch)) |
| | | .setLabel(commonLabel); |
| | | deviceSites.add(ds); |
| | | } |
| | | } |
| | | } |
| | |
| | | } |
| | | return true; |
| | | } |
| | | |
| | | private static String truncate(String s, int maxLen) { |
| | | if (s == null) return null; |
| | | return s.length() <= maxLen ? s : s.substring(0, maxLen); |
| | | } |
| | | |
| | | /** 巷道英文逗号分割,解析为整数列表 */ |
| | | private List<Integer> parseChannels(String channelStr) { |
| | | if (StringUtils.isBlank(channelStr)) { |
| | | return Collections.emptyList(); |
| | | } |
| | | String[] parts = channelStr.split(","); |
| | | List<Integer> list = new ArrayList<>(); |
| | | for (String p : parts) { |
| | | String t = (p == null) ? "" : p.trim(); |
| | | if (t.isEmpty()) continue; |
| | | try { |
| | | list.add(Integer.parseInt(t)); |
| | | } catch (NumberFormatException e) { |
| | | return Collections.emptyList(); |
| | | } |
| | | } |
| | | return list; |
| | | } |
| | | } |