| @@ -19,7 +19,7 @@ const GlobalParamsDrawer = forwardRef( | |||||
| const [form] = Form.useForm(); | const [form] = Form.useForm(); | ||||
| useImperativeHandle(ref, () => ({ | useImperativeHandle(ref, () => ({ | ||||
| getFieldsValue: async () => { | |||||
| validateFields: async () => { | |||||
| const [values, error] = await to(form.validateFields()); | const [values, error] = await to(form.validateFields()); | ||||
| if (!error && values) { | if (!error && values) { | ||||
| return values; | return values; | ||||
| @@ -27,6 +27,9 @@ const GlobalParamsDrawer = forwardRef( | |||||
| return Promise.reject(error); | return Promise.reject(error); | ||||
| } | } | ||||
| }, | }, | ||||
| getFieldsValue: () => { | |||||
| return form.getFieldsValue(); | |||||
| }, | |||||
| })); | })); | ||||
| // 处理参数类型变化 | // 处理参数类型变化 | ||||
| @@ -0,0 +1,6 @@ | |||||
| .props-label { | |||||
| display: flex; | |||||
| align-items: center; | |||||
| justify-content: space-between; | |||||
| width: 100%; | |||||
| } | |||||
| @@ -0,0 +1,44 @@ | |||||
| import { Button, Dropdown, type MenuProps } from 'antd'; | |||||
| import { useEffect } from 'react'; | |||||
| import styles from './index.less'; | |||||
| type PropsLabelProps = { | |||||
| title: string; | |||||
| menuItems: MenuProps['items']; | |||||
| onClick?: (key: string) => void; | |||||
| }; | |||||
| function PropsLabel({ title, menuItems, onClick }: PropsLabelProps) { | |||||
| useEffect(() => {}, []); | |||||
| const handleItemClick: MenuProps['onClick'] = (e) => { | |||||
| const keyPath = e.keyPath.reverse(); | |||||
| if (keyPath[0] === 'global') { | |||||
| onClick?.(`\${${e.key}}`); | |||||
| } else { | |||||
| onClick?.(`{{${keyPath.join('.')}}}`); | |||||
| } | |||||
| }; | |||||
| return ( | |||||
| <div className={styles['props-label']}> | |||||
| <span>{title}</span> | |||||
| <Dropdown | |||||
| menu={{ | |||||
| items: menuItems, | |||||
| onClick: handleItemClick, | |||||
| triggerSubMenuAction: 'click', | |||||
| }} | |||||
| trigger={['click']} | |||||
| placement="topRight" | |||||
| arrow | |||||
| > | |||||
| <Button size="small" type="link"> | |||||
| 参数 | |||||
| </Button> | |||||
| </Dropdown> | |||||
| </div> | |||||
| ); | |||||
| } | |||||
| export default PropsLabel; | |||||
| @@ -3,6 +3,17 @@ | |||||
| width: 100%; | width: 100%; | ||||
| height: 100%; | height: 100%; | ||||
| } | } | ||||
| .editPipelineProps { | |||||
| :global { | |||||
| label { | |||||
| width: 100%; | |||||
| &::after { | |||||
| display: none; | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| .editPipelinePropsContent { | .editPipelinePropsContent { | ||||
| display: flex; | display: flex; | ||||
| align-items: center; | align-items: center; | ||||
| @@ -1,27 +1,26 @@ | |||||
| import KFIcon from '@/components/KFIcon'; | import KFIcon from '@/components/KFIcon'; | ||||
| import { useVisible } from '@/hooks'; | |||||
| import { useStateRef, useVisible } from '@/hooks'; | |||||
| import { getWorkflowById, saveWorkflow } from '@/services/pipeline/index.js'; | import { getWorkflowById, saveWorkflow } from '@/services/pipeline/index.js'; | ||||
| import { to } from '@/utils/promise'; | import { to } from '@/utils/promise'; | ||||
| import { useEmotionCss } from '@ant-design/use-emotion-css'; | import { useEmotionCss } from '@ant-design/use-emotion-css'; | ||||
| import G6 from '@antv/g6'; | import G6 from '@antv/g6'; | ||||
| import { Button, message } from 'antd'; | import { Button, message } from 'antd'; | ||||
| import { useEffect, useRef, useState } from 'react'; | |||||
| import { useEffect, useRef } from 'react'; | |||||
| import { useNavigate, useParams } from 'react-router-dom'; | import { useNavigate, useParams } from 'react-router-dom'; | ||||
| import { s8 } from '../../../utils'; | import { s8 } from '../../../utils'; | ||||
| import GlobalParamsDrawer from '../components/GlobalParamsDrawer'; | import GlobalParamsDrawer from '../components/GlobalParamsDrawer'; | ||||
| import Styles from './editPipeline.less'; | import Styles from './editPipeline.less'; | ||||
| import ModelMenus from './modelMenus'; | import ModelMenus from './modelMenus'; | ||||
| import Props from './props'; | import Props from './props'; | ||||
| import { findAllParentNodes, findFirstDuplicate } from './utils'; | |||||
| let graph = null; | let graph = null; | ||||
| const EditPipeline = () => { | const EditPipeline = () => { | ||||
| const propsRef = useRef(); | |||||
| const navgite = useNavigate(); | const navgite = useNavigate(); | ||||
| // const [contextMenu,setContextMenu]=useState({}) | |||||
| let contextMenu = {}; | let contextMenu = {}; | ||||
| const locationParams = useParams(); //新版本获取路由参数接口 | const locationParams = useParams(); //新版本获取路由参数接口 | ||||
| let sourceAnchorIdx, targetAnchorIdx; | |||||
| const pipelineContainer = useEmotionCss(() => { | const pipelineContainer = useEmotionCss(() => { | ||||
| return { | return { | ||||
| display: 'flex', | display: 'flex', | ||||
| @@ -59,8 +58,11 @@ const EditPipeline = () => { | |||||
| }); | }); | ||||
| const graphRef = useRef(); | const graphRef = useRef(); | ||||
| const paramsDrawerRef = useRef(); | const paramsDrawerRef = useRef(); | ||||
| const propsRef = useRef(); | |||||
| const [paramsDrawerOpen, openParamsDrawer, closeParamsDrawer] = useVisible(false); | const [paramsDrawerOpen, openParamsDrawer, closeParamsDrawer] = useVisible(false); | ||||
| const [globalParam, setGlobalParam] = useState([]); | |||||
| const [globalParam, setGlobalParam, globalParamRef] = useStateRef([]); | |||||
| let sourceAnchorIdx, targetAnchorIdx; | |||||
| const onDragEnd = (val) => { | const onDragEnd = (val) => { | ||||
| console.log(val, 'eee'); | console.log(val, 'eee'); | ||||
| @@ -93,17 +95,24 @@ const EditPipeline = () => { | |||||
| } | } | ||||
| }; | }; | ||||
| const savePipeline = async (val) => { | const savePipeline = async (val) => { | ||||
| const [res, error] = await to(paramsDrawerRef.current.getFieldsValue()); | |||||
| const [res, error] = await to(paramsDrawerRef.current.validateFields()); | |||||
| if (error) { | if (error) { | ||||
| message.error('全局参数配置有误'); | message.error('全局参数配置有误'); | ||||
| openParamsDrawer(); | openParamsDrawer(); | ||||
| return; | return; | ||||
| } | } | ||||
| const duplicateName = findFirstDuplicate(res.global_param || []); | |||||
| if (duplicateName) { | |||||
| message.error('全局参数配置有重复的参数名称:' + duplicateName); | |||||
| openParamsDrawer(); | |||||
| return; | |||||
| } | |||||
| const [propsRes, propsError] = await to(propsRef.current.getFieldsValue()); | const [propsRes, propsError] = await to(propsRef.current.getFieldsValue()); | ||||
| console.log(await to(propsRef.current.getFieldsValue())); | console.log(await to(propsRef.current.getFieldsValue())); | ||||
| if (propsError) { | if (propsError) { | ||||
| message.error('基本信息必填项需配置'); | message.error('基本信息必填项需配置'); | ||||
| // handlerClick(); | |||||
| return; | return; | ||||
| } | } | ||||
| propsRef.current.propClose(); | propsRef.current.propClose(); | ||||
| @@ -128,12 +137,19 @@ const EditPipeline = () => { | |||||
| }; | }; | ||||
| const handlerClick = (e) => { | const handlerClick = (e) => { | ||||
| e.stopPropagation(); | e.stopPropagation(); | ||||
| // console.log(propsRef, graph); | |||||
| propsRef.current.showDrawer(e); | |||||
| if (e.target.get('name') !== 'anchor-point' && e.item) { | |||||
| graph.setItemState(e.item, 'nodeClicked', true); | |||||
| const parentNodes = findAllParentNodes(graph, e.item); | |||||
| // 如果没有打开过全局参数抽屉,获取不到全局参数 | |||||
| const globalParams = | |||||
| paramsDrawerRef.current.getFieldsValue().global_param || globalParamRef.current; | |||||
| propsRef.current.showDrawer(e, globalParams, parentNodes); | |||||
| } | |||||
| }; | }; | ||||
| const getGraphData = (data) => { | const getGraphData = (data) => { | ||||
| console.log('graph', graph); | |||||
| if (graph) { | if (graph) { | ||||
| console.log(graph); | |||||
| console.log(data); | |||||
| graph.data(data); | graph.data(data); | ||||
| graph.render(); | graph.render(); | ||||
| } else { | } else { | ||||
| @@ -275,14 +291,13 @@ const EditPipeline = () => { | |||||
| if (graph && ret.data && ret.data.dag) { | if (graph && ret.data && ret.data.dag) { | ||||
| getGraphData(JSON.parse(ret.data.dag)); | getGraphData(JSON.parse(ret.data.dag)); | ||||
| } | } | ||||
| // graph&&graph.data(JSON.parse(ret.dag)) | |||||
| // graph.render() | |||||
| }); | }); | ||||
| }; | }; | ||||
| const handlerContextMenu = (e) => { | const handlerContextMenu = (e) => { | ||||
| e.stopPropagation(); | e.stopPropagation(); | ||||
| // this.menuType = e.item._cfg.type; | // this.menuType = e.item._cfg.type; | ||||
| }; | }; | ||||
| // 上下文菜单 | |||||
| const initMenu = () => { | const initMenu = () => { | ||||
| // const selectedNodes = this.selectedNodes; | // const selectedNodes = this.selectedNodes; | ||||
| contextMenu = new G6.Menu({ | contextMenu = new G6.Menu({ | ||||
| @@ -330,8 +345,8 @@ const EditPipeline = () => { | |||||
| initGraph(); | initGraph(); | ||||
| }; | }; | ||||
| useEffect(() => { | useEffect(() => { | ||||
| getFirstWorkflow(locationParams.id); | |||||
| initMenu(); | initMenu(); | ||||
| getFirstWorkflow(locationParams.id); | |||||
| return () => { | return () => { | ||||
| graph.off('node:mouseenter', (e) => { | graph.off('node:mouseenter', (e) => { | ||||
| @@ -449,7 +464,7 @@ const EditPipeline = () => { | |||||
| }, | }, | ||||
| 'rect', | 'rect', | ||||
| ); | ); | ||||
| console.log(graphRef, 'graphRef'); | |||||
| graph = new G6.Graph({ | graph = new G6.Graph({ | ||||
| container: graphRef.current, | container: graphRef.current, | ||||
| grid: true, | grid: true, | ||||
| @@ -600,13 +615,7 @@ const EditPipeline = () => { | |||||
| // handlerClick(e); | // handlerClick(e); | ||||
| // } | // } | ||||
| // }); | // }); | ||||
| graph.on('node:click', (e) => { | |||||
| console.log(e.target.get('name')); | |||||
| if (e.target.get('name') !== 'anchor-point' && e.item) { | |||||
| graph.setItemState(e.item, 'nodeClicked', true); | |||||
| handlerClick(e); | |||||
| } | |||||
| }); | |||||
| graph.on('node:click', handlerClick); | |||||
| graph.on('aftercreateedge', (e) => { | graph.on('aftercreateedge', (e) => { | ||||
| // update the sourceAnchor and targetAnchor for the newly added edge | // update the sourceAnchor and targetAnchor for the newly added edge | ||||
| graph.updateItem(e.edge, { | graph.updateItem(e.edge, { | ||||
| @@ -2,23 +2,6 @@ import { getComponentAll } from '@/services/pipeline/index.js'; | |||||
| import { Collapse } from 'antd'; | import { Collapse } from 'antd'; | ||||
| import { useEffect, useState } from 'react'; | import { useEffect, useState } from 'react'; | ||||
| import Styles from './modelMenus.less'; | import Styles from './modelMenus.less'; | ||||
| const items = [ | |||||
| { | |||||
| key: '1', | |||||
| label: 'This is panel header 1', | |||||
| children: [1, 2, 3, 4, 5], | |||||
| }, | |||||
| { | |||||
| key: '2', | |||||
| label: 'This is panel header 2', | |||||
| children: [1, 2, 3, 4, 5], | |||||
| }, | |||||
| { | |||||
| key: '3', | |||||
| label: 'This is panel header 3', | |||||
| children: [1, 2, 3, 4, 5], | |||||
| }, | |||||
| ]; | |||||
| const ModelMenus = ({ onParDragEnd }) => { | const ModelMenus = ({ onParDragEnd }) => { | ||||
| const [modelMenusList, setModelMenusList] = useState([]); | const [modelMenusList, setModelMenusList] = useState([]); | ||||
| useEffect(() => { | useEffect(() => { | ||||
| @@ -36,7 +19,7 @@ const ModelMenus = ({ onParDragEnd }) => { | |||||
| x: e.clientX, | x: e.clientX, | ||||
| y: e.clientY, | y: e.clientY, | ||||
| label: data.component_label, | label: data.component_label, | ||||
| img: `/assets/images/${data.icon_path}.png`, | |||||
| img: `/assets/images/pipeline/${data.icon_path}.png`, | |||||
| }); | }); | ||||
| }; | }; | ||||
| const { Panel } = Collapse; | const { Panel } = Collapse; | ||||
| @@ -64,7 +47,7 @@ const ModelMenus = ({ onParDragEnd }) => { | |||||
| > | > | ||||
| <img | <img | ||||
| style={{ height: '16px', marginRight: '15px' }} | style={{ height: '16px', marginRight: '15px' }} | ||||
| src={`/assets/images/${ele.icon_path}.png`} | |||||
| src={`/assets/images/pipeline/${ele.icon_path}.png`} | |||||
| alt="" | alt="" | ||||
| /> | /> | ||||
| {ele.component_label} | {ele.component_label} | ||||
| @@ -5,22 +5,25 @@ import { to } from '@/utils/promise'; | |||||
| import { Button, Drawer, Form, Input, Select } from 'antd'; | import { Button, Drawer, Form, Input, Select } from 'antd'; | ||||
| import { pick } from 'lodash'; | import { pick } from 'lodash'; | ||||
| import { forwardRef, useEffect, useImperativeHandle, useState } from 'react'; | import { forwardRef, useEffect, useImperativeHandle, useState } from 'react'; | ||||
| import PropsLabel from '../components/PropsLabel'; | |||||
| import ResourceSelectorModal, { ResourceSelectorType } from '../components/ResourceSelectorModal'; | import ResourceSelectorModal, { ResourceSelectorType } from '../components/ResourceSelectorModal'; | ||||
| import Styles from './editPipeline.less'; | import Styles from './editPipeline.less'; | ||||
| import { createMenuItems } from './utils'; | |||||
| const { TextArea } = Input; | const { TextArea } = Input; | ||||
| const Props = forwardRef(({ onParentChange }, ref) => { | const Props = forwardRef(({ onParentChange }, ref) => { | ||||
| const [form] = Form.useForm(); | const [form] = Form.useForm(); | ||||
| const [stagingItem, setStagingItem] = useState({}); | const [stagingItem, setStagingItem] = useState({}); | ||||
| const [open, setOpen] = useState(false); | const [open, setOpen] = useState(false); | ||||
| const [selectedModel, setSelectedModel] = useState(undefined); | |||||
| const [selectedDataset, setSelectedDataset] = useState(undefined); | |||||
| const [resourceStandardList, setResourceStandardList] = useState([]); | |||||
| const [selectedModel, setSelectedModel] = useState(undefined); // 选择的模型,为了再次打开时恢复原来的选择 | |||||
| const [selectedDataset, setSelectedDataset] = useState(undefined); // 选择的数据集,为了再次打开时恢复原来的选择 | |||||
| const [resourceStandardList, setResourceStandardList] = useState([]); // 资源规模列表 | |||||
| const [items, setItems] = useState([]); | |||||
| useEffect(() => { | useEffect(() => { | ||||
| getComputingResource(); | getComputingResource(); | ||||
| }, []); | }, []); | ||||
| // 获取资源规格列表数据 | |||||
| const getComputingResource = async () => { | const getComputingResource = async () => { | ||||
| const params = { | const params = { | ||||
| page: 0, | page: 0, | ||||
| @@ -35,49 +38,22 @@ const Props = forwardRef(({ onParentChange }, ref) => { | |||||
| const afterOpenChange = () => { | const afterOpenChange = () => { | ||||
| if (!open) { | if (!open) { | ||||
| console.log(stagingItem, form.getFieldsValue()); | |||||
| // 禁止校验 guard-for-in | |||||
| /* eslint-disable */ | |||||
| for (let i in form.getFieldsValue()) { | |||||
| for (let j in stagingItem.in_parameters) { | |||||
| if (i == j) { | |||||
| console.log(j, i); | |||||
| stagingItem.in_parameters[j].value = form.getFieldsValue()[i]; | |||||
| } | |||||
| } | |||||
| for (let p in stagingItem.out_parameters) { | |||||
| if (i == p) { | |||||
| stagingItem.out_parameters[p].value = form.getFieldsValue()[i]; | |||||
| } | |||||
| } | |||||
| for (let k in stagingItem.control_strategy) { | |||||
| if (i == k) { | |||||
| stagingItem.control_strategy[k].value = form.getFieldsValue()[i]; | |||||
| } | |||||
| } | |||||
| } | |||||
| /* eslint-enable */ | |||||
| // setStagingItem({...stagingItem,}) | |||||
| console.log(stagingItem.control_strategy); | |||||
| // console.log('zzzz', stagingItem, form.getFieldsValue()); | |||||
| const control_strategy = form.getFieldValue('control_strategy'); | |||||
| const in_parameters = form.getFieldValue('in_parameters'); | |||||
| const out_parameters = form.getFieldValue('out_parameters'); | |||||
| onParentChange({ | onParentChange({ | ||||
| ...stagingItem, | ...stagingItem, | ||||
| control_strategy: JSON.stringify(stagingItem.control_strategy), | |||||
| in_parameters: JSON.stringify(stagingItem.in_parameters), | |||||
| out_parameters: JSON.stringify(stagingItem.out_parameters), | |||||
| ...form.getFieldsValue(), | ...form.getFieldsValue(), | ||||
| control_strategy: JSON.stringify(control_strategy), | |||||
| in_parameters: JSON.stringify(in_parameters), | |||||
| out_parameters: JSON.stringify(out_parameters), | |||||
| }); | }); | ||||
| // onParentChange({...stagingItem,...form.getFieldsValue()}) | |||||
| } | } | ||||
| }; | }; | ||||
| const onClose = () => { | const onClose = () => { | ||||
| setOpen(false); | setOpen(false); | ||||
| }; | }; | ||||
| const onFinish = (values) => { | |||||
| console.log('Success:', values); | |||||
| }; | |||||
| const onFinishFailed = (errorInfo) => { | |||||
| console.log('Failed:', errorInfo); | |||||
| }; | |||||
| useImperativeHandle(ref, () => ({ | useImperativeHandle(ref, () => ({ | ||||
| getFieldsValue: async () => { | getFieldsValue: async () => { | ||||
| const [propsRes, propsError] = await to(form.validateFields()); | const [propsRes, propsError] = await to(form.validateFields()); | ||||
| @@ -88,30 +64,27 @@ const Props = forwardRef(({ onParentChange }, ref) => { | |||||
| return Promise.reject(propsError); | return Promise.reject(propsError); | ||||
| } | } | ||||
| }, | }, | ||||
| showDrawer(e) { | |||||
| showDrawer(e, params, parentNodes) { | |||||
| if (e.item && e.item.getModel()) { | if (e.item && e.item.getModel()) { | ||||
| // console.log(e.item.getModel().in_parameters); | |||||
| form.resetFields(); | form.resetFields(); | ||||
| form.setFieldsValue({ | |||||
| ...e.item.getModel(), | |||||
| in_parameters: JSON.parse(e.item.getModel().in_parameters), | |||||
| out_parameters: JSON.parse(e.item.getModel().out_parameters), | |||||
| control_strategy: JSON.parse(e.item.getModel().control_strategy), | |||||
| }); | |||||
| const model = e.item.getModel(); | |||||
| const nodeData = { | |||||
| ...model, | |||||
| in_parameters: JSON.parse(model.in_parameters), | |||||
| out_parameters: JSON.parse(model.out_parameters), | |||||
| control_strategy: JSON.parse(model.control_strategy), | |||||
| }; | |||||
| setStagingItem({ | setStagingItem({ | ||||
| ...e.item.getModel(), | |||||
| in_parameters: JSON.parse(e.item.getModel().in_parameters), | |||||
| out_parameters: JSON.parse(e.item.getModel().out_parameters), | |||||
| control_strategy: JSON.parse(e.item.getModel().control_strategy), | |||||
| ...nodeData, | |||||
| }); | |||||
| form.setFieldsValue({ | |||||
| ...nodeData, | |||||
| }); | }); | ||||
| // form.setFieldsValue({...e.item.getModel(),in_parameters:JSON.parse(e.item.getModel().in_parameters),out_parameters:JSON.parse(e.item.getModel().out_parameters)}) | |||||
| // setStagingItem({...e.item.getModel(),in_parameters:JSON.parse(e.item.getModel().in_parameters),out_parameters:JSON.parse(e.item.getModel().out_parameters)}) | |||||
| // setTimeout(() => { | |||||
| // console.log(stagingItem); | |||||
| // }, (500)); | |||||
| setSelectedModel(undefined); | setSelectedModel(undefined); | ||||
| setSelectedDataset(undefined); | setSelectedDataset(undefined); | ||||
| setOpen(true); | setOpen(true); | ||||
| setItems(createMenuItems(params, parentNodes)); | |||||
| } | } | ||||
| }, | }, | ||||
| propClose: async () => { | propClose: async () => { | ||||
| @@ -119,16 +92,12 @@ const Props = forwardRef(({ onParentChange }, ref) => { | |||||
| const [openRes, propsError] = await to(setOpen(false)); | const [openRes, propsError] = await to(setOpen(false)); | ||||
| console.log(setOpen(false)); | console.log(setOpen(false)); | ||||
| }, | }, | ||||
| // propClose() { | |||||
| // setOpen(false); | |||||
| // }, | |||||
| })); | })); | ||||
| // 选择数据集、模型 | // 选择数据集、模型 | ||||
| const selectResource = (name, item) => { | const selectResource = (name, item) => { | ||||
| let type; | let type; | ||||
| let resource = undefined; | |||||
| let resource; | |||||
| switch (item.item_type) { | switch (item.item_type) { | ||||
| case 'dataset': | case 'dataset': | ||||
| type = ResourceSelectorType.Dataset; | type = ResourceSelectorType.Dataset; | ||||
| @@ -154,7 +123,7 @@ const Props = forwardRef(({ onParentChange }, ref) => { | |||||
| } else { | } else { | ||||
| const jsonObj = pick(res, ['id', 'version', 'path']); | const jsonObj = pick(res, ['id', 'version', 'path']); | ||||
| const value = JSON.stringify(jsonObj); | const value = JSON.stringify(jsonObj); | ||||
| form.setFieldValue(name, value); | |||||
| form.setFieldValue(name, { ...item, value }); | |||||
| } | } | ||||
| if (type === ResourceSelectorType.Dataset) { | if (type === ResourceSelectorType.Dataset) { | ||||
| @@ -187,16 +156,26 @@ const Props = forwardRef(({ onParentChange }, ref) => { | |||||
| } | } | ||||
| }; | }; | ||||
| // 筛选资源规格 | |||||
| const filterResourceStandard = (input, { computing_resource = '' }) => { | const filterResourceStandard = (input, { computing_resource = '' }) => { | ||||
| return computing_resource.toLocaleLowerCase().includes(input.toLocaleLowerCase()); | return computing_resource.toLocaleLowerCase().includes(input.toLocaleLowerCase()); | ||||
| }; | }; | ||||
| // 控制策略 | // 控制策略 | ||||
| const controlStrategy = stagingItem.control_strategy; | |||||
| const controlStrategyList = Object.entries(stagingItem.control_strategy ?? {}).map( | |||||
| ([key, value]) => ({ key, value }), | |||||
| ); | |||||
| // 输入参数 | // 输入参数 | ||||
| const inParameters = stagingItem.in_parameters; | |||||
| const inParametersList = Object.entries(stagingItem.in_parameters ?? {}).map(([key, value]) => ({ | |||||
| key, | |||||
| value, | |||||
| })); | |||||
| // 输出参数 | // 输出参数 | ||||
| const outParameters = stagingItem.out_parameters; | |||||
| const outParametersList = Object.entries(stagingItem.out_parameters ?? {}).map( | |||||
| ([key, value]) => ({ key, value }), | |||||
| ); | |||||
| return ( | return ( | ||||
| <> | <> | ||||
| @@ -209,15 +188,15 @@ const Props = forwardRef(({ onParentChange }, ref) => { | |||||
| onClose={onClose} | onClose={onClose} | ||||
| afterOpenChange={afterOpenChange} | afterOpenChange={afterOpenChange} | ||||
| open={open} | open={open} | ||||
| width={420} | |||||
| width={520} | |||||
| className={Styles.editPipelineProps} | |||||
| > | > | ||||
| <Form | <Form | ||||
| name="form" | name="form" | ||||
| form={form} | form={form} | ||||
| // layout="vertical" | |||||
| layout="vertical" | layout="vertical" | ||||
| labelCol={{ | labelCol={{ | ||||
| span: 16, | |||||
| span: 24, | |||||
| }} | }} | ||||
| wrapperCol={{ | wrapperCol={{ | ||||
| span: 24, | span: 24, | ||||
| @@ -225,11 +204,6 @@ const Props = forwardRef(({ onParentChange }, ref) => { | |||||
| style={{ | style={{ | ||||
| maxWidth: 600, | maxWidth: 600, | ||||
| }} | }} | ||||
| initialValues={{ | |||||
| remember: true, | |||||
| }} | |||||
| onFinish={onFinish} | |||||
| onFinishFailed={onFinishFailed} | |||||
| autoComplete="off" | autoComplete="off" | ||||
| > | > | ||||
| <div className={Styles.editPipelinePropsContent}> | <div className={Styles.editPipelinePropsContent}> | ||||
| @@ -250,7 +224,7 @@ const Props = forwardRef(({ onParentChange }, ref) => { | |||||
| }, | }, | ||||
| ]} | ]} | ||||
| > | > | ||||
| <Input /> | |||||
| <Input placeholder="请输入任务名称" allowClear /> | |||||
| </Form.Item> | </Form.Item> | ||||
| <Form.Item | <Form.Item | ||||
| label="任务ID" | label="任务ID" | ||||
| @@ -275,7 +249,7 @@ const Props = forwardRef(({ onParentChange }, ref) => { | |||||
| <Form.Item label="镜像" required> | <Form.Item label="镜像" required> | ||||
| <div className={Styles['ref-row']}> | <div className={Styles['ref-row']}> | ||||
| <Form.Item name="image" noStyle rules={[{ required: true, message: '请输入镜像' }]}> | <Form.Item name="image" noStyle rules={[{ required: true, message: '请输入镜像' }]}> | ||||
| <Input placeholder="请输入镜像" allowClear /> | |||||
| <Input placeholder="请输入或选择镜像" allowClear /> | |||||
| </Form.Item> | </Form.Item> | ||||
| <Form.Item noStyle> | <Form.Item noStyle> | ||||
| <Button | <Button | ||||
| @@ -289,12 +263,33 @@ const Props = forwardRef(({ onParentChange }, ref) => { | |||||
| </Form.Item> | </Form.Item> | ||||
| </div> | </div> | ||||
| </Form.Item> | </Form.Item> | ||||
| <Form.Item label="工作目录" name="working_directory"> | |||||
| <Input /> | |||||
| <Form.Item | |||||
| name="working_directory" | |||||
| label={ | |||||
| <PropsLabel | |||||
| menuItems={items} | |||||
| title="工作目录" | |||||
| onClick={(value) => { | |||||
| form.setFieldValue('working_directory', value); | |||||
| }} | |||||
| /> | |||||
| } | |||||
| > | |||||
| <Input placeholder="请输入工作目录" allowClear /> | |||||
| </Form.Item> | </Form.Item> | ||||
| <Form.Item label="启动命令" name="command"> | |||||
| <TextArea /> | |||||
| <Form.Item | |||||
| name="command" | |||||
| label={ | |||||
| <PropsLabel | |||||
| menuItems={items} | |||||
| title="工作目录" | |||||
| onClick={(value) => { | |||||
| form.setFieldValue('command', value); | |||||
| }} | |||||
| /> | |||||
| } | |||||
| > | |||||
| <TextArea placeholder="请输入启动命令" allowClear /> | |||||
| </Form.Item> | </Form.Item> | ||||
| <Form.Item | <Form.Item | ||||
| label="资源规格" | label="资源规格" | ||||
| @@ -317,19 +312,63 @@ const Props = forwardRef(({ onParentChange }, ref) => { | |||||
| }} | }} | ||||
| /> | /> | ||||
| </Form.Item> | </Form.Item> | ||||
| <Form.Item label="挂载路径" name="mount_path"> | |||||
| <Input /> | |||||
| <Form.Item | |||||
| name="mount_path" | |||||
| label={ | |||||
| <PropsLabel | |||||
| menuItems={items} | |||||
| title="挂载路径" | |||||
| onClick={(value) => { | |||||
| form.setFieldValue('mount_path', value); | |||||
| }} | |||||
| /> | |||||
| } | |||||
| > | |||||
| <Input placeholder="请输入挂载路径" allowClear /> | |||||
| </Form.Item> | </Form.Item> | ||||
| <Form.Item label="环境变量" name="env_variables"> | |||||
| <TextArea /> | |||||
| <Form.Item | |||||
| name="env_variables" | |||||
| label={ | |||||
| <PropsLabel | |||||
| menuItems={items} | |||||
| title="环境变量" | |||||
| onClick={(value) => { | |||||
| form.setFieldValue('env_variables', value); | |||||
| }} | |||||
| /> | |||||
| } | |||||
| > | |||||
| <TextArea placeholder="请输入环境变量" allowClear /> | |||||
| </Form.Item> | </Form.Item> | ||||
| {controlStrategy && Object.keys(controlStrategy).length > 0 | |||||
| ? Object.keys(controlStrategy).map((item) => ( | |||||
| <Form.Item key={item} label={controlStrategy[item].label} name={item}> | |||||
| <Input /> | |||||
| </Form.Item> | |||||
| )) | |||||
| : ''} | |||||
| {controlStrategyList.map((item) => ( | |||||
| <Form.Item | |||||
| key={item.key} | |||||
| name={['control_strategy', item.key]} | |||||
| label={ | |||||
| <PropsLabel | |||||
| menuItems={items} | |||||
| title={item.value.label} | |||||
| onClick={(value) => { | |||||
| form.setFieldValue(['control_strategy', item.key], { | |||||
| ...item.value, | |||||
| value, | |||||
| }); | |||||
| }} | |||||
| /> | |||||
| } | |||||
| getValueProps={(e) => { | |||||
| return { value: e.value }; | |||||
| }} | |||||
| getValueFromEvent={(e) => { | |||||
| return { | |||||
| ...item.value, | |||||
| value: e.target.value, | |||||
| }; | |||||
| }} | |||||
| > | |||||
| <Input placeholder={item.value.label} allowClear /> | |||||
| </Form.Item> | |||||
| ))} | |||||
| <div className={Styles.editPipelinePropsContent}> | <div className={Styles.editPipelinePropsContent}> | ||||
| <img | <img | ||||
| style={{ width: '15px', marginRight: '10px' }} | style={{ width: '15px', marginRight: '10px' }} | ||||
| @@ -338,37 +377,55 @@ const Props = forwardRef(({ onParentChange }, ref) => { | |||||
| /> | /> | ||||
| 输入参数 | 输入参数 | ||||
| </div> | </div> | ||||
| {inParameters && Object.keys(inParameters).length > 0 | |||||
| ? Object.keys(inParameters).map((item) => ( | |||||
| {inParametersList.map((item) => ( | |||||
| <Form.Item | |||||
| key={item.key} | |||||
| label={ | |||||
| <PropsLabel | |||||
| menuItems={items} | |||||
| title={item.value.label + '(' + item.key + ')'} | |||||
| onClick={(value) => { | |||||
| form.setFieldValue(['in_parameters', item.key], { | |||||
| ...item.value, | |||||
| value, | |||||
| }); | |||||
| }} | |||||
| /> | |||||
| } | |||||
| required={item.value.require ? true : false} | |||||
| > | |||||
| <div className={Styles['ref-row']}> | |||||
| <Form.Item | <Form.Item | ||||
| key={item} | |||||
| label={inParameters[item].label + '(' + item + ')'} | |||||
| required={inParameters[item].require ? true : false} | |||||
| name={['in_parameters', item.key]} | |||||
| noStyle | |||||
| rules={[{ required: item.value.require ? true : false }]} | |||||
| getValueProps={(e) => { | |||||
| return { value: e.value }; | |||||
| }} | |||||
| getValueFromEvent={(e) => { | |||||
| return { | |||||
| ...item.value, | |||||
| value: e.target.value, | |||||
| }; | |||||
| }} | |||||
| > | > | ||||
| <div className={Styles['ref-row']}> | |||||
| <Form.Item | |||||
| name={item} | |||||
| noStyle | |||||
| rules={[{ required: inParameters[item].require ? true : false }]} | |||||
| > | |||||
| <Input /> | |||||
| </Form.Item> | |||||
| {inParameters[item].type === 'ref' && ( | |||||
| <Form.Item noStyle> | |||||
| <Button | |||||
| type="link" | |||||
| icon={getSelectBtnIcon(inParameters[item])} | |||||
| onClick={() => selectResource(item, inParameters[item])} | |||||
| className={Styles['select-button']} | |||||
| > | |||||
| {inParameters[item].label} | |||||
| </Button> | |||||
| </Form.Item> | |||||
| )} | |||||
| </div> | |||||
| <Input placeholder={item.value.label} allowClear /> | |||||
| </Form.Item> | </Form.Item> | ||||
| )) | |||||
| : ''} | |||||
| {item.value.type === 'ref' && ( | |||||
| <Form.Item noStyle> | |||||
| <Button | |||||
| type="link" | |||||
| icon={getSelectBtnIcon(item.value)} | |||||
| onClick={() => selectResource(['in_parameters', item.key], item.value)} | |||||
| className={Styles['select-button']} | |||||
| > | |||||
| {item.value.label} | |||||
| </Button> | |||||
| </Form.Item> | |||||
| )} | |||||
| </div> | |||||
| </Form.Item> | |||||
| ))} | |||||
| <div className={Styles.editPipelinePropsContent}> | <div className={Styles.editPipelinePropsContent}> | ||||
| <img | <img | ||||
| style={{ width: '15px', marginRight: '10px' }} | style={{ width: '15px', marginRight: '10px' }} | ||||
| @@ -377,18 +434,36 @@ const Props = forwardRef(({ onParentChange }, ref) => { | |||||
| /> | /> | ||||
| 输出参数 | 输出参数 | ||||
| </div> | </div> | ||||
| {outParameters && Object.keys(outParameters).length > 0 | |||||
| ? Object.keys(outParameters).map((item) => ( | |||||
| <Form.Item | |||||
| key={item} | |||||
| label={outParameters[item].label + '(' + item + ')'} | |||||
| rules={[{ required: outParameters[item].require ? true : false }]} | |||||
| name={item} | |||||
| > | |||||
| <Input /> | |||||
| </Form.Item> | |||||
| )) | |||||
| : ''} | |||||
| {outParametersList.map((item) => ( | |||||
| <Form.Item | |||||
| key={item.key} | |||||
| name={['out_parameters', item.key]} | |||||
| label={ | |||||
| <PropsLabel | |||||
| menuItems={items} | |||||
| title={item.value.label + '(' + item.key + ')'} | |||||
| onClick={(value) => { | |||||
| form.setFieldValue(['out_parameters', item.key], { | |||||
| ...item.value, | |||||
| value, | |||||
| }); | |||||
| }} | |||||
| /> | |||||
| } | |||||
| rules={[{ required: item.value.require ? true : false }]} | |||||
| getValueProps={(e) => { | |||||
| return { value: e.value }; | |||||
| }} | |||||
| getValueFromEvent={(e) => { | |||||
| return { | |||||
| ...item.value, | |||||
| value: e.target.value, | |||||
| }; | |||||
| }} | |||||
| > | |||||
| <Input placeholder={item.value.label} allowClear /> | |||||
| </Form.Item> | |||||
| ))} | |||||
| </Form> | </Form> | ||||
| </Drawer> | </Drawer> | ||||
| </> | </> | ||||
| @@ -0,0 +1,79 @@ | |||||
| import { PipelineGlobalParam } from '@/types'; | |||||
| import { Graph, INode } from '@antv/g6'; | |||||
| import { type MenuProps } from 'antd'; | |||||
| // 找到节点所以的上游节点 | |||||
| export const findAllParentNodes = (graph: Graph, node: INode) => { | |||||
| const parentNodes: INode[] = []; | |||||
| let index = -1; | |||||
| let targetNode = node; | |||||
| while (targetNode) { | |||||
| const neighbors: INode[] = graph.getNeighbors(targetNode, 'source'); | |||||
| for (const sourceNode of neighbors) { | |||||
| // 避免重复,也避免循环 | |||||
| const idx = parentNodes.findIndex((item) => sourceNode.getID() === item.getID()); | |||||
| if (idx === -1 && sourceNode.getID() !== node.getID()) { | |||||
| parentNodes.push(sourceNode); | |||||
| } | |||||
| } | |||||
| targetNode = parentNodes[++index]; | |||||
| } | |||||
| return parentNodes; | |||||
| }; | |||||
| // 判断并找到全局参数第一个重复项,有重复项时,全局参数不允许保存 | |||||
| export function findFirstDuplicate(params: PipelineGlobalParam[]): string | null { | |||||
| const seen = new Set(); | |||||
| for (const item of params) { | |||||
| if (seen.has(item.param_name)) { | |||||
| return item.param_name; | |||||
| } | |||||
| seen.add(item.param_name); | |||||
| } | |||||
| return null; | |||||
| } | |||||
| // 创建参数下拉菜单 | |||||
| export function createMenuItems( | |||||
| params: PipelineGlobalParam[], | |||||
| parentNodes: INode[], | |||||
| ): MenuProps['items'] { | |||||
| const nodes: MenuProps['items'] = parentNodes.map((item) => { | |||||
| const model = item.getModel(); | |||||
| const out_parameters = model.out_parameters as string | undefined | null; | |||||
| const out_parametersObj = parseJsonText(out_parameters); | |||||
| const outParametersList = Object.keys(out_parametersObj ?? {}); | |||||
| return { | |||||
| key: model.id as string, | |||||
| label: model.label as string, | |||||
| children: outParametersList.map((key: string) => ({ | |||||
| key: key as string, | |||||
| label: out_parametersObj[key].label, | |||||
| })), | |||||
| }; | |||||
| }); | |||||
| return [ | |||||
| { | |||||
| key: 'global', | |||||
| label: '全局参数', | |||||
| children: params.map((item) => ({ | |||||
| key: item.param_name, | |||||
| label: item.param_name, | |||||
| })), | |||||
| }, | |||||
| ...nodes, | |||||
| ]; | |||||
| } | |||||
| function parseJsonText(text?: string | null): any | null { | |||||
| if (!text) { | |||||
| return null; | |||||
| } | |||||
| try { | |||||
| return JSON.parse(text); | |||||
| } catch (error) { | |||||
| return null; | |||||
| } | |||||
| } | |||||
| @@ -222,12 +222,7 @@ const Login: React.FC = () => { | |||||
| getCaptchaCode(); | getCaptchaCode(); | ||||
| } | } | ||||
| } catch (error) { | } catch (error) { | ||||
| const defaultLoginFailureMessage = intl.formatMessage({ | |||||
| id: 'pages.login.failure', | |||||
| defaultMessage: '登录失败,请重试!', | |||||
| }); | |||||
| console.log(error); | |||||
| message.error(defaultLoginFailureMessage); | |||||
| getCaptchaCode(); | |||||
| } | } | ||||
| }; | }; | ||||
| const { code } = userLoginState; | const { code } = userLoginState; | ||||
| @@ -502,267 +497,6 @@ const Login: React.FC = () => { | |||||
| </div> | </div> | ||||
| </div> | </div> | ||||
| </div> | </div> | ||||
| {/* <Helmet> | |||||
| <title> | |||||
| {intl.formatMessage({ | |||||
| id: 'menu.login', | |||||
| defaultMessage: '登录页', | |||||
| })} | |||||
| - {Settings.title} | |||||
| </title> | |||||
| </Helmet> | |||||
| <Lang /> | |||||
| <div | |||||
| style={{ | |||||
| flex: '1', | |||||
| padding: '32px 0', | |||||
| }} | |||||
| > | |||||
| <LoginForm | |||||
| contentStyle={{ | |||||
| minWidth: 280, | |||||
| maxWidth: '75vw', | |||||
| }} | |||||
| logo={<img alt="logo" src="/logo.svg" />} | |||||
| title="Ant Design" | |||||
| subTitle={intl.formatMessage({ id: 'pages.layouts.userLayout.title' })} | |||||
| initialValues={{ | |||||
| autoLogin: true, | |||||
| }} | |||||
| actions={[ | |||||
| <FormattedMessage | |||||
| key="loginWith" | |||||
| id="pages.login.loginWith" | |||||
| defaultMessage="其他登录方式" | |||||
| />, | |||||
| <ActionIcons key="icons" />, | |||||
| ]} | |||||
| onFinish={async (values) => { | |||||
| await handleSubmit(values as API.LoginParams); | |||||
| }} | |||||
| > | |||||
| <Tabs | |||||
| activeKey={type} | |||||
| onChange={setType} | |||||
| centered | |||||
| items={[ | |||||
| { | |||||
| key: 'account', | |||||
| label: intl.formatMessage({ | |||||
| id: 'pages.login.accountLogin.tab', | |||||
| defaultMessage: '账户密码登录', | |||||
| }), | |||||
| }, | |||||
| { | |||||
| key: 'mobile', | |||||
| label: intl.formatMessage({ | |||||
| id: 'pages.login.phoneLogin.tab', | |||||
| defaultMessage: '手机号登录', | |||||
| }), | |||||
| }, | |||||
| ]} | |||||
| /> | |||||
| {code !== 200 && loginType === 'account' && ( | |||||
| <LoginMessage | |||||
| content={intl.formatMessage({ | |||||
| id: 'pages.login.accountLogin.errorMessage', | |||||
| defaultMessage: '账户或密码错误(admin/admin123)', | |||||
| })} | |||||
| /> | |||||
| )} | |||||
| {type === 'account' && ( | |||||
| <> | |||||
| <ProFormText | |||||
| name="username" | |||||
| initialValue="admin" | |||||
| fieldProps={{ | |||||
| size: 'large', | |||||
| prefix: <UserOutlined />, | |||||
| }} | |||||
| placeholder={intl.formatMessage({ | |||||
| id: 'pages.login.username.placeholder', | |||||
| defaultMessage: '用户名: admin', | |||||
| })} | |||||
| rules={[ | |||||
| { | |||||
| required: true, | |||||
| message: ( | |||||
| <FormattedMessage | |||||
| id="pages.login.username.required" | |||||
| defaultMessage="请输入用户名!" | |||||
| /> | |||||
| ), | |||||
| }, | |||||
| ]} | |||||
| /> | |||||
| <ProFormText.Password | |||||
| name="password" | |||||
| initialValue="admin123" | |||||
| fieldProps={{ | |||||
| size: 'large', | |||||
| prefix: <LockOutlined />, | |||||
| }} | |||||
| placeholder={intl.formatMessage({ | |||||
| id: 'pages.login.password.placeholder', | |||||
| defaultMessage: '密码: admin123', | |||||
| })} | |||||
| rules={[ | |||||
| { | |||||
| required: true, | |||||
| message: ( | |||||
| <FormattedMessage | |||||
| id="pages.login.password.required" | |||||
| defaultMessage="请输入密码!" | |||||
| /> | |||||
| ), | |||||
| }, | |||||
| ]} | |||||
| /> | |||||
| <Row> | |||||
| <Col flex={3}> | |||||
| <ProFormText | |||||
| style={{ | |||||
| float: 'right', | |||||
| }} | |||||
| name="code" | |||||
| placeholder={intl.formatMessage({ | |||||
| id: 'pages.login.captcha.placeholder', | |||||
| defaultMessage: '请输入验证', | |||||
| })} | |||||
| rules={[ | |||||
| { | |||||
| required: true, | |||||
| message: ( | |||||
| <FormattedMessage | |||||
| id="pages.searchTable.updateForm.ruleName.nameRules" | |||||
| defaultMessage="请输入验证啊" | |||||
| /> | |||||
| ), | |||||
| }, | |||||
| ]} | |||||
| /> | |||||
| </Col> | |||||
| <Col flex={2}> | |||||
| <Image | |||||
| src={captchaCode} | |||||
| alt="验证码" | |||||
| style={{ | |||||
| display: 'inline-block', | |||||
| verticalAlign: 'top', | |||||
| cursor: 'pointer', | |||||
| paddingLeft: '10px', | |||||
| width: '100px', | |||||
| }} | |||||
| preview={false} | |||||
| onClick={() => getCaptchaCode()} | |||||
| /> | |||||
| </Col> | |||||
| </Row> | |||||
| </> | |||||
| )} | |||||
| {code !== 200 && loginType === 'mobile' && <LoginMessage content="验证码错误" />} | |||||
| {type === 'mobile' && ( | |||||
| <> | |||||
| <ProFormText | |||||
| fieldProps={{ | |||||
| size: 'large', | |||||
| prefix: <MobileOutlined />, | |||||
| }} | |||||
| name="mobile" | |||||
| placeholder={intl.formatMessage({ | |||||
| id: 'pages.login.phoneNumber.placeholder', | |||||
| defaultMessage: '手机号', | |||||
| })} | |||||
| rules={[ | |||||
| { | |||||
| required: true, | |||||
| message: ( | |||||
| <FormattedMessage | |||||
| id="pages.login.phoneNumber.required" | |||||
| defaultMessage="请输入手机号!" | |||||
| /> | |||||
| ), | |||||
| }, | |||||
| { | |||||
| pattern: /^1\d{10}$/, | |||||
| message: ( | |||||
| <FormattedMessage | |||||
| id="pages.login.phoneNumber.invalid" | |||||
| defaultMessage="手机号格式错误!" | |||||
| /> | |||||
| ), | |||||
| }, | |||||
| ]} | |||||
| /> | |||||
| <ProFormCaptcha | |||||
| fieldProps={{ | |||||
| size: 'large', | |||||
| prefix: <LockOutlined />, | |||||
| }} | |||||
| captchaProps={{ | |||||
| size: 'large', | |||||
| }} | |||||
| placeholder={intl.formatMessage({ | |||||
| id: 'pages.login.captcha.placeholder', | |||||
| defaultMessage: '请输入验证码', | |||||
| })} | |||||
| captchaTextRender={(timing, count) => { | |||||
| if (timing) { | |||||
| return `${count} ${intl.formatMessage({ | |||||
| id: 'pages.getCaptchaSecondText', | |||||
| defaultMessage: '获取验证码', | |||||
| })}`; | |||||
| } | |||||
| return intl.formatMessage({ | |||||
| id: 'pages.login.phoneLogin.getVerificationCode', | |||||
| defaultMessage: '获取验证码', | |||||
| }); | |||||
| }} | |||||
| name="captcha" | |||||
| rules={[ | |||||
| { | |||||
| required: true, | |||||
| message: ( | |||||
| <FormattedMessage | |||||
| id="pages.login.captcha.required" | |||||
| defaultMessage="请输入验证码!" | |||||
| /> | |||||
| ), | |||||
| }, | |||||
| ]} | |||||
| onGetCaptcha={async (phone) => { | |||||
| const result = await getFakeCaptcha({ | |||||
| phone, | |||||
| }); | |||||
| if (!result) { | |||||
| return; | |||||
| } | |||||
| message.success('获取验证码成功!验证码为:1234'); | |||||
| }} | |||||
| /> | |||||
| </> | |||||
| )} | |||||
| <div | |||||
| style={{ | |||||
| marginBottom: 24, | |||||
| }} | |||||
| > | |||||
| <ProFormCheckbox noStyle name="autoLogin"> | |||||
| <FormattedMessage id="pages.login.rememberMe" defaultMessage="自动登录" /> | |||||
| </ProFormCheckbox> | |||||
| <a | |||||
| style={{ | |||||
| float: 'right', | |||||
| }} | |||||
| > | |||||
| <FormattedMessage id="pages.login.forgotPassword" defaultMessage="忘记密码" /> | |||||
| </a> | |||||
| </div> | |||||
| </LoginForm> | |||||
| </div> | |||||
| <Footer /> */} | |||||
| </div> | </div> | ||||
| ); | ); | ||||
| }; | }; | ||||
| @@ -1,7 +1,7 @@ | |||||
| /* | /* | ||||
| * @Author: 赵伟 | * @Author: 赵伟 | ||||
| * @Date: 2024-03-25 13:52:54 | * @Date: 2024-03-25 13:52:54 | ||||
| * @Description: | |||||
| * @Description: 工具类 | |||||
| */ | */ | ||||
| export function s8() { | export function s8() { | ||||
| return (((1 + Math.random()) * 0x100000000) | 0).toString(16).substring(1); | return (((1 + Math.random()) * 0x100000000) | 0).toString(16).substring(1); | ||||
| @@ -1,3 +1,9 @@ | |||||
| /* | |||||
| * @Author: 赵伟 | |||||
| * @Date: 2024-05-11 15:40:58 | |||||
| * @Description: 自定义菜单项 | |||||
| */ | |||||
| import KFIcon from '@/components/KFIcon'; | import KFIcon from '@/components/KFIcon'; | ||||
| import { MenuDataItem } from '@ant-design/pro-components'; | import { MenuDataItem } from '@ant-design/pro-components'; | ||||
| import { Link } from '@umijs/max'; | import { Link } from '@umijs/max'; | ||||