Merge pull request '4262' (#37) from dev into master

This commit is contained in:
fanshuai 2024-04-26 14:49:43 +08:00
commit 12f6524480
36 changed files with 460 additions and 115 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

View File

@ -224,8 +224,8 @@ const Dataset = () => {
<span style={{ color: '#1d1d20', fontSize: '20px' }}>{datasetDetailObj.name}</span> <span style={{ color: '#1d1d20', fontSize: '20px' }}>{datasetDetailObj.name}</span>
<div className={Styles.smallTagBox}> <div className={Styles.smallTagBox}>
<div className={Styles.tagItem}>数据集 id{datasetDetailObj.id}</div> <div className={Styles.tagItem}>数据集 id{datasetDetailObj.id}</div>
<div className={Styles.tagItem}>{datasetDetailObj.data_tag || '...'}</div> <div className={Styles.tagItem}>{datasetDetailObj.dataset_type_name || '...'}</div>
<div className={Styles.tagItem}>{datasetDetailObj.data_type}</div> <div className={Styles.tagItem}>{datasetDetailObj.dataset_tag_name || '...'}</div>
</div> </div>
</div> </div>
<div className={Styles.datasetIntroCneterBox}> <div className={Styles.datasetIntroCneterBox}>

View File

@ -229,6 +229,11 @@
border-color: #eaeaea; border-color: #eaeaea;
border-radius: 4px; border-radius: 4px;
cursor: pointer; cursor: pointer;
.dropdown{
position: absolute;
right: 20px;
top: 15px;
}
.itemText { .itemText {
position: absolute; position: absolute;
top: 20px; top: 20px;

View File

@ -1,14 +1,22 @@
import { getAccessToken } from '@/access'; import { getAccessToken } from '@/access';
import clock from '@/assets/img/clock.png'; import clock from '@/assets/img/clock.png';
import creatByImg from '@/assets/img/creatBy.png'; import creatByImg from '@/assets/img/creatBy.png';
import deleteIcon from '@/assets/img/delete-icon.png';
import KFIcon from '@/components/KFIcon'; import KFIcon from '@/components/KFIcon';
import { addDatesetAndVesion, getAssetIcon, getDatasetList } from '@/services/dataset/index.js'; import {
addDatesetAndVesion,
deleteDataset,
getAssetIcon,
getDatasetList,
} from '@/services/dataset/index.js';
import { getDictSelectOption } from '@/services/system/dict'; import { getDictSelectOption } from '@/services/system/dict';
import { modalConfirm } from '@/utils/ui';
import { UploadOutlined } from '@ant-design/icons'; import { UploadOutlined } from '@ant-design/icons';
import { Button, Form, Input, Modal, Pagination, Radio, Select, Upload } from 'antd'; import { Button, Form, Input, Modal, Pagination, Radio, Select, Upload } from 'antd';
import moment from 'moment'; import moment from 'moment';
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import './index.less'; import './index.less';
import Styles from './index.less'; import Styles from './index.less';
const { Search } = Input; const { Search } = Input;
@ -277,7 +285,30 @@ const PublicData = (React.FC = () => {
return ( return (
<div className={Styles.dataItem} onClick={(e) => routeToIntro(e, item)}> <div className={Styles.dataItem} onClick={(e) => routeToIntro(e, item)}>
<span className={Styles.itemText}>{item.name}</span> <span className={Styles.itemText}>{item.name}</span>
<img
onClick={(e) => {
e.stopPropagation();
modalConfirm({
title: '确定删除该条数据集实例吗?',
onOk: () => {
deleteDataset(item.id).then((ret) => {
if (ret.code === 200) {
message.success('删除成功');
getModelLists(queryFlow);
} else {
message.error(ret.msg);
}
});
},
});
}}
className={Styles.dropdown}
style={{ width: '17px', marginRight: '6px' }}
src={deleteIcon}
alt=""
/>
<div className={Styles.itemDescripition}>{item.description}</div> <div className={Styles.itemDescripition}>{item.description}</div>
<div className={Styles.itemTime}> <div className={Styles.itemTime}>
<img <img
style={{ width: '17px', marginRight: '6px' }} style={{ width: '17px', marginRight: '6px' }}

View File

@ -1,6 +1,8 @@
import clock from '@/assets/img/clock.png'; import clock from '@/assets/img/clock.png';
import creatByImg from '@/assets/img/creatBy.png'; import creatByImg from '@/assets/img/creatBy.png';
import { getAssetIcon, getDatasetList } from '@/services/dataset/index.js'; import deleteIcon from '@/assets/img/delete-icon.png';
import { deleteDataset, getAssetIcon, getDatasetList } from '@/services/dataset/index.js';
import { modalConfirm } from '@/utils/ui';
import { Form, Input, Pagination } from 'antd'; import { Form, Input, Pagination } from 'antd';
import moment from 'moment'; import moment from 'moment';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
@ -225,6 +227,28 @@ const PublicData = () => {
return ( return (
<div className={Styles.dataItem} onClick={(e) => routeToIntro(e, item)}> <div className={Styles.dataItem} onClick={(e) => routeToIntro(e, item)}>
<span className={Styles.itemText}>{item.name}</span> <span className={Styles.itemText}>{item.name}</span>
<img
onClick={(e) => {
e.stopPropagation();
modalConfirm({
title: '确定删除该条数据集实例吗?',
onOk: () => {
deleteDataset(item.id).then((ret) => {
if (ret.code === 200) {
message.success('删除成功');
getModelLists(queryFlow);
} else {
message.error(ret.msg);
}
});
},
});
}}
className={Styles.dropdown}
style={{ width: '17px', marginRight: '6px' }}
src={deleteIcon}
alt=""
/>
<div className={Styles.itemDescripition}>{item.description}</div> <div className={Styles.itemDescripition}>{item.description}</div>
<div className={Styles.itemTime}> <div className={Styles.itemTime}>
<img <img

View File

@ -68,19 +68,9 @@ function ExperimentText() {
}; };
const formChange = (val) => {}; const formChange = (val) => {};
const handlerClick = (e) => { const handlerClick = (e) => {
console.log(propsRef, graph, messageRef.current); if (e.target.get('name') !== 'anchor-point' && e.item) {
// let cache = []; propsRef.current.showDrawer(e, locationParams.id, messageRef.current);
// let json_str = JSON.stringify(graph, function(key, value) { }
// if (typeof value === 'object' && value !== null) {
// if (cache.indexOf(value) !== -1) {
// return;
// }
// cache.push(value);
// }
// return value;
// });
// console.log(json_str);
propsRef.current.showDrawer(e, locationParams.id, messageRef.current);
}; };
const getGraphData = (data) => { const getGraphData = (data) => {
if (graph) { if (graph) {
@ -387,8 +377,7 @@ function ExperimentText() {
fitView: true, fitView: true,
fitViewPadding: [320, 320, 220, 320], fitViewPadding: [320, 320, 220, 320],
}); });
graph.on('node:click', handlerClick);
graph.on('dblclick', handlerClick);
window.onresize = () => { window.onresize = () => {
if (!graph || graph.get('destroyed')) return; if (!graph || graph.get('destroyed')) return;
if (!graphRef.current || !graphRef.current.scrollWidth || !graphRef.current.scrollHeight) if (!graphRef.current || !graphRef.current.scrollWidth || !graphRef.current.scrollHeight)

View File

@ -29,12 +29,37 @@
background: #ffffff; background: #ffffff;
box-shadow: 0px 3px 6px rgba(146, 146, 146, 0.09); box-shadow: 0px 3px 6px rgba(146, 146, 146, 0.09);
} }
.drawBox{
:global{
.ant-drawer .ant-drawer-body{
overflow: hidden;
}
}
}
.experimentDrawer{
:global{
.ant-tabs >.ant-tabs-nav .ant-tabs-nav-list{
margin-left: 24px;
}
.ant-drawer .ant-drawer-body{
overflow-y: hidden;
}
.ant-tabs {
height: calc(100% - 160px);
overflow-y: auto;
}
}
}
.detailBox { .detailBox {
display: flex; display: flex;
align-items: center; align-items: center;
margin-bottom: 15px; margin-bottom: 15px;
color: #1d1d20; color: #1d1d20;
font-size: 15px; font-size: 15px;
padding-left: 24px;
} }
.allMessageItem { .allMessageItem {
display: flex; display: flex;

View File

@ -12,7 +12,7 @@ import styles from './paramsModal.less';
type ParamsModalProps = { type ParamsModalProps = {
open: boolean; open: boolean;
onCancel: () => void; onCancel: () => void;
globalParam?: PipelineGlobalParam[]; globalParam?: PipelineGlobalParam[] | null;
}; };
function ParamsModal({ open, onCancel, globalParam = [] }: ParamsModalProps) { function ParamsModal({ open, onCancel, globalParam = [] }: ParamsModalProps) {
@ -26,7 +26,7 @@ function ParamsModal({ open, onCancel, globalParam = [] }: ParamsModalProps) {
cancelButtonProps={{ style: { display: 'none' } }} cancelButtonProps={{ style: { display: 'none' } }}
> >
<div className={styles.params_container}> <div className={styles.params_container}>
{globalParam.map((item) => ( {globalParam?.map((item) => (
<div key={item.param_name} className={styles.params_container_line}> <div key={item.param_name} className={styles.params_container_line}>
<span className={styles.params_container_line_label}>{getParamType(item)}</span> <span className={styles.params_container_line_label}>{getParamType(item)}</span>
<span className={styles.params_container_line_value}>{item.param_value}</span> <span className={styles.params_container_line_value}>{item.param_value}</span>

View File

@ -386,7 +386,7 @@ const Props = forwardRef(({ onParentChange }, ref) => {
}, },
})); }));
return ( return (
<> <div className={Styles.drawBox}>
<Drawer <Drawer
title="任务执行详情" title="任务执行详情"
placement="right" placement="right"
@ -397,9 +397,12 @@ const Props = forwardRef(({ onParentChange }, ref) => {
afterOpenChange={afterOpenChange} afterOpenChange={afterOpenChange}
open={open} open={open}
width={420} width={420}
className={Styles.experimentDrawer}
destroyOnClose={true} destroyOnClose={true}
> >
<div className={Styles.detailBox}>任务名称{stagingItem.label}</div> <div className={Styles.detailBox} style={{ marginTop: '15px' }}>
任务名称{stagingItem.label}
</div>
<div className={Styles.detailBox}> <div className={Styles.detailBox}>
执行状态 执行状态
<div <div
@ -429,7 +432,7 @@ const Props = forwardRef(({ onParentChange }, ref) => {
</div> </div>
<Tabs defaultActiveKey="1" items={items} /> <Tabs defaultActiveKey="1" items={items} />
</Drawer> </Drawer>
</> </div>
); );
}); });

View File

@ -69,6 +69,7 @@ function Experiment() {
return { ...item, key: item.id }; return { ...item, key: item.id };
}), }),
); );
setTotal(res.data.totalElements); setTotal(res.data.totalElements);
} }
}; };
@ -82,6 +83,7 @@ function Experiment() {
// //
const getQueryByExperiment = (val) => { const getQueryByExperiment = (val) => {
getQueryByExperimentId(val).then((ret) => { getQueryByExperimentId(val).then((ret) => {
console.log(val);
setExpandedRowKeys(val); setExpandedRowKeys(val);
if (ret && ret.data && ret.data.length > 0) { if (ret && ret.data && ret.data.length > 0) {
try { try {
@ -159,6 +161,7 @@ function Experiment() {
}; };
const expandChange = (e, record) => { const expandChange = (e, record) => {
clearExperimentInTimers(); clearExperimentInTimers();
console.log(e, record);
if (record.id === expandedRowKeys) { if (record.id === expandedRowKeys) {
setExpandedRowKeys(null); setExpandedRowKeys(null);
} else { } else {
@ -517,6 +520,7 @@ function Experiment() {
: ''} : ''}
</div> </div>
), ),
onExpand: (e, a) => { onExpand: (e, a) => {
expandChange(e, a); expandChange(e, a);
}, },

View File

@ -219,6 +219,11 @@
border-color: #eaeaea; border-color: #eaeaea;
border-radius: 4px; border-radius: 4px;
cursor: pointer; cursor: pointer;
.dropdown{
position: absolute;
right: 20px;
top: 15px;
}
.itemText { .itemText {
position: absolute; position: absolute;
top: 20px; top: 20px;

View File

@ -222,8 +222,8 @@ const Dataset = () => {
<span style={{ color: '#1d1d20', fontSize: '20px' }}>{datasetDetailObj.name}</span> <span style={{ color: '#1d1d20', fontSize: '20px' }}>{datasetDetailObj.name}</span>
<div className={Styles.smallTagBox}> <div className={Styles.smallTagBox}>
<div className={Styles.tagItem}>模型 id{datasetDetailObj.id}</div> <div className={Styles.tagItem}>模型 id{datasetDetailObj.id}</div>
<div className={Styles.tagItem}>{datasetDetailObj.data_tag || '...'}</div> <div className={Styles.tagItem}>{datasetDetailObj.model_type_name || '...'}</div>
<div className={Styles.tagItem}>{datasetDetailObj.data_type}</div> <div className={Styles.tagItem}>{datasetDetailObj.model_tag_name || '...'}</div>
</div> </div>
</div> </div>
<div className={Styles.datasetIntroCneterBox}> <div className={Styles.datasetIntroCneterBox}>

View File

@ -1,10 +1,12 @@
import { getAccessToken } from '@/access'; import { getAccessToken } from '@/access';
import clock from '@/assets/img/clock.png'; import clock from '@/assets/img/clock.png';
import creatByImg from '@/assets/img/creatBy.png'; import creatByImg from '@/assets/img/creatBy.png';
import deleteIcon from '@/assets/img/delete-icon.png';
import KFIcon from '@/components/KFIcon'; import KFIcon from '@/components/KFIcon';
import { addModel, getAssetIcon, getModelList } from '@/services/dataset/index.js'; import { addModel, deleteModel, getAssetIcon, getModelList } from '@/services/dataset/index.js';
import { modalConfirm } from '@/utils/ui';
import { UploadOutlined } from '@ant-design/icons'; import { UploadOutlined } from '@ant-design/icons';
import { Button, Form, Input, Modal, Pagination, Select, Upload } from 'antd'; import { Button, Form, Input, Modal, Pagination, Select, Upload, message } from 'antd';
import moment from 'moment'; import moment from 'moment';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
@ -54,8 +56,8 @@ const PublicData = () => {
}); });
const [activeType, setActiveType] = useState(null); const [activeType, setActiveType] = useState(null);
const [activeTag, setActiveTag] = useState(null); const [activeTag, setActiveTag] = useState(null);
const [datasetTypeList, setDatasetTypeList] = useState([]); const [modelTypeList, setmodelTypeList] = useState([]);
const [datasetDirectionList, setDatasetDirectionList] = useState([]); const [modelDirectionList, setmodelDirectionList] = useState([]);
const [isModalOpen, setIsModalOpen] = useState(false); const [isModalOpen, setIsModalOpen] = useState(false);
const [datasetList, setDatasetList] = useState([]); const [datasetList, setDatasetList] = useState([]);
const [total, setTotal] = useState(0); const [total, setTotal] = useState(0);
@ -82,11 +84,11 @@ const PublicData = () => {
getAssetIcon(params).then((ret) => { getAssetIcon(params).then((ret) => {
console.log(ret); console.log(ret);
if (ret.code == 200 && ret.data.content && ret.data.content.length > 0) { if (ret.code == 200 && ret.data.content && ret.data.content.length > 0) {
setDatasetTypeList(ret.data.content.filter((item) => item.category_id == 3)); setmodelTypeList(ret.data.content.filter((item) => item.category_id == 3));
setDatasetDirectionList(ret.data.content.filter((item) => item.category_id == 4)); setmodelDirectionList(ret.data.content.filter((item) => item.category_id == 4));
} else { } else {
setDatasetTypeList([]); setmodelTypeList([]);
setDatasetDirectionList([]); setmodelDirectionList([]);
} }
}); });
}; };
@ -181,8 +183,8 @@ const PublicData = () => {
/> />
<div className={Styles.itemTitle}>模型框架</div> <div className={Styles.itemTitle}>模型框架</div>
<div className={Styles.itemBox}> <div className={Styles.itemBox}>
{datasetTypeList && datasetTypeList.length > 0 {modelTypeList && modelTypeList.length > 0
? datasetTypeList.map((item) => { ? modelTypeList.map((item) => {
return ( return (
<div> <div>
<div <div
@ -222,8 +224,8 @@ const PublicData = () => {
</div> </div>
<div className={Styles.itemTitle}>模型能力</div> <div className={Styles.itemTitle}>模型能力</div>
<div className={Styles.itemBox}> <div className={Styles.itemBox}>
{datasetDirectionList && datasetDirectionList.length > 0 {modelDirectionList && modelDirectionList.length > 0
? datasetDirectionList.map((item) => { ? modelDirectionList.map((item) => {
return ( return (
<div> <div>
<div <div
@ -282,9 +284,80 @@ const PublicData = () => {
{datasetList && datasetList.length > 0 {datasetList && datasetList.length > 0
? datasetList.map((item) => { ? datasetList.map((item) => {
return ( return (
<div className={Styles.dataItem} onClick={(e) => routeToIntro(e, item)}> <div
className={Styles.dataItem}
onClick={(e) => {
routeToIntro(e, item);
}}
>
<span className={Styles.itemText}>{item.name}</span> <span className={Styles.itemText}>{item.name}</span>
<div className={Styles.itemDescripition}>{item.description}</div> <img
onClick={(e) => {
e.stopPropagation();
modalConfirm({
title: '确定删除该条模型实例吗?',
onOk: () => {
deleteModel(item.id).then((ret) => {
if (ret.code === 200) {
message.success('删除成功');
getModelLists(queryFlow);
} else {
message.error(ret.msg);
}
});
},
});
}}
className={Styles.dropdown}
style={{ width: '17px', marginRight: '6px' }}
src={deleteIcon}
alt=""
/>
{/* <Dropdown
className={Styles.dropdown}
key={item.name}
menu={{
items: [
{
label: '详情',
key: 'detail',
},
{
label: '删除',
key: 'delete',
},
],
onClick: (e) => {
console.log(e);
if (e.key === 'detail') {
routeToIntro(e, item);
} else if (e.key === 'delete') {
modalConfirm({
title: '确定删除该条模型实例吗?',
onOk: () => {
deleteModel(item.id).then((ret) => {
if (ret.code === 200) {
message.success('删除成功');
getModelLists(queryFlow);
} else {
message.error(ret.msg);
}
});
},
});
}
},
}}
>
<div>
<img
style={{ width: '17px', marginRight: '6px' }}
src={moreBack}
alt=""
/>
</div>
</Dropdown> */}
,<div className={Styles.itemDescripition}>{item.description}</div>
<div className={Styles.itemTime}> <div className={Styles.itemTime}>
<img <img
style={{ width: '17px', marginRight: '6px' }} style={{ width: '17px', marginRight: '6px' }}
@ -401,7 +474,13 @@ const PublicData = () => {
] ]
} }
> >
<Select allowClear placeholder="请选择模型类型" options={[]} /> <Select
allowClear
placeholder="请选择模型类型"
options={modelTypeList.map((item) => {
return { value: item.id, label: item.name };
})}
/>
</Form.Item> </Form.Item>
<Form.Item <Form.Item
label="模型能力" label="模型能力"
@ -415,7 +494,13 @@ const PublicData = () => {
] ]
} }
> >
<Select allowClear placeholder="请选择模型标签" options={[]} /> <Select
allowClear
placeholder="请选择模型标签"
options={modelDirectionList.map((item) => {
return { value: item.id, label: item.name };
})}
/>
</Form.Item> </Form.Item>
<Form.Item label="模型文件" name="models_version_vos"> <Form.Item label="模型文件" name="models_version_vos">
<Upload {...props} data={{ uuid: uuid }} accept=".zip,.tgz"> <Upload {...props} data={{ uuid: uuid }} accept=".zip,.tgz">

View File

@ -1,7 +1,9 @@
import clock from '@/assets/img/clock.png'; import clock from '@/assets/img/clock.png';
import creatByImg from '@/assets/img/creatBy.png'; import creatByImg from '@/assets/img/creatBy.png';
import { getAssetIcon, getModelList } from '@/services/dataset/index.js'; import deleteIcon from '@/assets/img/delete-icon.png';
import { Form, Input, Modal, Pagination, Radio } from 'antd'; import { deleteModel, getAssetIcon, getModelList } from '@/services/dataset/index.js';
import { modalConfirm } from '@/utils/ui';
import { Form, Input, Modal, Pagination, Radio, message } from 'antd';
import moment from 'moment'; import moment from 'moment';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
@ -224,8 +226,35 @@ const PublicData = () => {
{datasetList && datasetList.length > 0 {datasetList && datasetList.length > 0
? datasetList.map((item) => { ? datasetList.map((item) => {
return ( return (
<div className={Styles.dataItem} onClick={(e) => routeToIntro(e, item)}> <div
className={Styles.dataItem}
onClick={(e) => {
routeToIntro(e, item);
}}
>
<span className={Styles.itemText}>{item.name}</span> <span className={Styles.itemText}>{item.name}</span>
<img
onClick={(e) => {
e.stopPropagation();
modalConfirm({
title: '确定删除该条模型实例吗?',
onOk: () => {
deleteModel(item.id).then((ret) => {
if (ret.code === 200) {
message.success('删除成功');
getModelLists(queryFlow);
} else {
message.error(ret.msg);
}
});
},
});
}}
className={Styles.dropdown}
style={{ width: '17px', marginRight: '6px' }}
src={deleteIcon}
alt=""
/>
<div className={Styles.itemDescripition}>{item.description}</div> <div className={Styles.itemDescripition}>{item.description}</div>
<div className={Styles.itemTime}> <div className={Styles.itemTime}>
<img <img

View File

@ -14,7 +14,7 @@ import styles from './globalParamsDrawer.less';
type GlobalParamsDrawerProps = { type GlobalParamsDrawerProps = {
open: boolean; open: boolean;
onClose: () => void; onClose: () => void;
globalParam: PipelineGlobalParam[]; globalParam: PipelineGlobalParam[] | null;
}; };
const GlobalParamsDrawer = forwardRef( const GlobalParamsDrawer = forwardRef(

View File

@ -128,18 +128,7 @@ const EditPipeline = () => {
}; };
const handlerClick = (e) => { const handlerClick = (e) => {
e.stopPropagation(); e.stopPropagation();
console.log(propsRef, graph); // console.log(propsRef, graph);
// let cache = [];
// let json_str = JSON.stringify(graph, function(key, value) {
// if (typeof value === 'object' && value !== null) {
// if (cache.indexOf(value) !== -1) {
// return;
// }
// cache.push(value);
// }
// return value;
// });
// console.log(json_str);
propsRef.current.showDrawer(e); propsRef.current.showDrawer(e);
}; };
const getGraphData = (data) => { const getGraphData = (data) => {
@ -281,7 +270,7 @@ const EditPipeline = () => {
const getFirstWorkflow = (val) => { const getFirstWorkflow = (val) => {
getWorkflowById(val).then((ret) => { getWorkflowById(val).then((ret) => {
if (ret && ret.data) { if (ret && ret.data) {
setGlobalParam(ret.data.global_param); setGlobalParam(ret.data.global_param || []);
} }
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));
@ -353,7 +342,7 @@ const EditPipeline = () => {
// this.graph.setItemState(e.item, 'showAnchors', false); // this.graph.setItemState(e.item, 'showAnchors', false);
graph.setItemState(e.item, 'nodeSelected', false); graph.setItemState(e.item, 'nodeSelected', false);
}); });
graph.off('dblclick', handlerClick); // graph.off('dblclick', handlerClick);
}; };
}, []); }, []);
const initGraph = () => { const initGraph = () => {
@ -390,30 +379,15 @@ const EditPipeline = () => {
getAnchorPoints(cfg) { getAnchorPoints(cfg) {
return ( return (
cfg.anchorPoints || [ cfg.anchorPoints || [
// 31 //
[0.5, 0], // [0.5, 0],
[0.5, 1], // [0.5, 1],
[0, 0.5],
[1, 0.5],
] ]
); );
}, },
// draw(cfg, group) {
// let rect=group.addShape('text', {
// attrs: {
// text: fittingString(cfg.label, 110, 15),
// x: 90 - getTextSize(cfg.label, 110, 15),
// y: 0,
// fontSize: 10,
// textAlign: 'center',
// textBaseline: 'middle',
// fill:'#000'
// },
// name: 'text-shape',
// });
// return rect;
// },
afterDraw(cfg, group) { afterDraw(cfg, group) {
// console.log(group, cfg, 12312);
const image = group.addShape('image', { const image = group.addShape('image', {
attrs: { attrs: {
x: -45, x: -45,
@ -442,7 +416,6 @@ const EditPipeline = () => {
} }
const bbox = group.getBBox(); const bbox = group.getBBox();
const anchorPoints = this.getAnchorPoints(cfg); const anchorPoints = this.getAnchorPoints(cfg);
// console.log(anchorPoints);
anchorPoints.forEach((anchorPos, i) => { anchorPoints.forEach((anchorPos, i) => {
group.addShape('circle', { group.addShape('circle', {
attrs: { attrs: {
@ -456,6 +429,7 @@ const EditPipeline = () => {
anchorPointIdx: i, // flag the idx of the anchor-point circle anchorPointIdx: i, // flag the idx of the anchor-point circle
links: 0, // cache the number of edges connected to this shape links: 0, // cache the number of edges connected to this shape
visible: false, // invisible by default, shows up when links > 1 or the node is in showAnchors state visible: false, // invisible by default, shows up when links > 1 or the node is in showAnchors state
draggable: true,
}); });
}); });
return image; return image;
@ -503,7 +477,7 @@ const EditPipeline = () => {
// config the shouldBegin and shouldEnd to make sure the create-edge is began and ended at anchor-point circles // config the shouldBegin and shouldEnd to make sure the create-edge is began and ended at anchor-point circles
{ {
type: 'create-edge', type: 'create-edge',
// trigger: 'drag', trigger: 'drag',
shouldBegin: (e) => { shouldBegin: (e) => {
// avoid beginning at other shapes on the node // avoid beginning at other shapes on the node
if (e.target && e.target.get('name') !== 'anchor-point') return false; if (e.target && e.target.get('name') !== 'anchor-point') return false;
@ -575,7 +549,7 @@ const EditPipeline = () => {
}, },
defaultEdge: { defaultEdge: {
// type: 'quadratic', // type: 'quadratic',
type: 'cubic-vertical', // type: 'cubic-vertical',
style: { style: {
endArrow: { endArrow: {
@ -619,16 +593,20 @@ const EditPipeline = () => {
fitView: true, fitView: true,
fitViewPadding: [320, 320, 220, 320], fitViewPadding: [320, 320, 220, 320],
}); });
graph.on('dblclick', (e) => { // graph.on('dblclick', (e) => {
console.log(e.item); // console.log(e.item);
if (e.item) { // if (e.item) {
// graph.setItemState(e.item, 'nodeClicked', true);
// 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); graph.setItemState(e.item, 'nodeClicked', true);
handlerClick(e); handlerClick(e);
} }
}); });
graph.on('click', (e) => {
console.log(e.item);
});
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, {
@ -666,6 +644,39 @@ const EditPipeline = () => {
}, },
}); });
}); });
graph.on('node:dragenter', (e) => {
console.log(e.target.get('name'));
console.log('node:dragenter');
graph.setItemState(e.item, 'nodeSelected', true);
graph.updateItem(e.item, {
//
style: {
stroke: '#1664ff',
},
});
});
graph.on('node:dragleave', (e) => {
console.log(e.target.get('name'));
console.log('node:dragleave');
graph.setItemState(e.item, 'nodeSelected', false);
graph.updateItem(e.item, {
//
style: {
stroke: 'transparent',
},
});
});
graph.on('node:dragstart', (e) => {
console.log('node:dragstart');
graph.setItemState(e.item, 'nodeSelected', true);
graph.updateItem(e.item, {
//
style: {
stroke: '#1664ff',
},
});
});
graph.on('afterremoveitem', (e) => { graph.on('afterremoveitem', (e) => {
if (e.item && e.item.source && e.item.target) { if (e.item && e.item.source && e.item.target) {
const sourceNode = graph.findById(e.item.source); const sourceNode = graph.findById(e.item.source);

View File

@ -118,3 +118,15 @@ export function exportDataset(id) {
method: 'GET', method: 'GET',
}); });
} }
// 删除模型集
export function deleteModel(id) {
return request(`/api/mmp/models/${id}`, {
method: 'DELETE',
});
}
// 删除数据集
export function deleteDataset(id) {
return request(`/api/mmp/dataset/${id}`, {
method: 'DELETE',
});
}

View File

@ -17,7 +17,9 @@ import org.springframework.scheduling.annotation.EnableScheduling;
@EnableRyFeignClients @EnableRyFeignClients
@SpringBootApplication @SpringBootApplication
@EnableScheduling @EnableScheduling
public class RuoYiManagementPlatformApplication { public class
RuoYiManagementPlatformApplication {
public static void main(String[] args) { public static void main(String[] args) {
SpringApplication.run(RuoYiManagementPlatformApplication.class, args); SpringApplication.run(RuoYiManagementPlatformApplication.class, args);
System.out.println("(♥◠‿◠)ノ゙ 复杂智能软件管理平台启动成功 ლ(´ڡ`ლ)゙ \n" + System.out.println("(♥◠‿◠)ノ゙ 复杂智能软件管理平台启动成功 ლ(´ڡ`ლ)゙ \n" +

View File

@ -158,7 +158,7 @@ public class DatasetController {
*/ */
@DeleteMapping({"{id}"}) @DeleteMapping({"{id}"})
@ApiOperation("根据id删除数据集") @ApiOperation("根据id删除数据集")
public AjaxResult deleteById(@PathVariable("id") Integer id) { public AjaxResult deleteById(@PathVariable("id") Integer id) throws Exception {
return AjaxResult.success(this.datasetService.removeById(id)); return AjaxResult.success(this.datasetService.removeById(id));
} }

View File

@ -161,7 +161,7 @@ public class ModelsController extends BaseController {
*/ */
@DeleteMapping("{id}") @DeleteMapping("{id}")
@ApiOperation("删除模型") @ApiOperation("删除模型")
public GenericsAjaxResult<String> deleteById(@PathVariable("id") Integer id) { public GenericsAjaxResult<String> deleteById(@PathVariable("id") Integer id) throws Exception {
return genericsSuccess(this.modelsService.removeById(id)); return genericsSuccess(this.modelsService.removeById(id));
} }

View File

@ -19,8 +19,8 @@ public class AssetIcon implements Serializable {
/** /**
* 主键 * 主键
*/ */
@ApiModelProperty(value = "资产ID") @ApiModelProperty(value = "资产ID")
private Integer id; private Integer id;
@ApiModelProperty(value = "资产图标名称") @ApiModelProperty(value = "资产图标名称")
private String name; private String name;

View File

@ -76,6 +76,13 @@ public class Dataset implements Serializable {
@ApiModelProperty(value = "状态0失效1生效") @ApiModelProperty(value = "状态0失效1生效")
private Integer state; private Integer state;
@ApiModelProperty(value = "数据集类型名字")
private String datasetTypeName;
@ApiModelProperty(value = "数据集tag名字")
private String datasetTagName;
public Integer getId() { public Integer getId() {
return id; return id;
@ -165,5 +172,21 @@ public class Dataset implements Serializable {
this.state = state; this.state = state;
} }
public String getDatasetTypeName() {
return datasetTypeName;
}
public String getDatasetTagName() {
return datasetTagName;
}
public void setDatasetTagName(String datasetTagName) {
this.datasetTagName = datasetTagName;
}
public void setDatasetTypeName(String datasetTypeName) {
this.datasetTypeName = datasetTypeName;
}
} }

View File

@ -118,7 +118,7 @@ public class Image implements Serializable {
this.updateTime = updateTime; this.updateTime = updateTime;
} }
// Getter Setter
public Integer getVersionCount() { public Integer getVersionCount() {
return versionCount; return versionCount;
} }

View File

@ -52,6 +52,16 @@ public class Models implements Serializable {
private Integer state; private Integer state;
@ApiModelProperty(value = "模型类型名")
private String modelTypeName;
@ApiModelProperty(value = "模型tag名")
private String modelTagName;
public Integer getId() { public Integer getId() {
return id; return id;
} }
@ -158,5 +168,21 @@ public class Models implements Serializable {
this.state = state; this.state = state;
} }
public String getModelTagName() {
return modelTagName;
}
public void setModelTagName(String modelTagName) {
this.modelTagName = modelTagName;
}
public String getModelTypeName() {
return modelTypeName;
}
public void setModelTypeName(String modelTypeName) {
this.modelTypeName = modelTypeName;
}
} }

View File

@ -84,5 +84,6 @@ public interface AssetIconDao {
List<AssetIcon> queryByCategoryId(Integer categoryId); List<AssetIcon> queryByCategoryId(Integer categoryId);
AssetIcon queryByPath(String path);
} }

View File

@ -22,6 +22,15 @@ public interface AssetIconService {
*/ */
AssetIcon queryById(Integer id); AssetIcon queryById(Integer id);
/**
* 通过path查询单条数据
*
* @param path 路径
* @return 实例对象
*/
AssetIcon queryByPath(String path);
/** /**
* 分页查询 * 分页查询
* *

View File

@ -68,7 +68,7 @@ DatasetService {
*/ */
boolean deleteById(Integer id); boolean deleteById(Integer id);
String removeById(Integer id); String removeById(Integer id) throws Exception;
ResponseEntity<InputStreamResource> downloadDataset(Integer id) throws Exception; ResponseEntity<InputStreamResource> downloadDataset(Integer id) throws Exception;

View File

@ -64,7 +64,7 @@ public interface ModelsService {
*/ */
boolean deleteById(Integer id); boolean deleteById(Integer id);
String removeById(Integer id); String removeById(Integer id) throws Exception;
ResponseEntity<InputStreamResource> downloadModels(Integer id) throws Exception; ResponseEntity<InputStreamResource> downloadModels(Integer id) throws Exception;

View File

@ -2,7 +2,6 @@ package com.ruoyi.platform.service.impl;
import com.ruoyi.common.security.utils.SecurityUtils; import com.ruoyi.common.security.utils.SecurityUtils;
import com.ruoyi.platform.domain.AssetIcon; import com.ruoyi.platform.domain.AssetIcon;
import com.ruoyi.platform.domain.Models;
import com.ruoyi.platform.mapper.AssetIconDao; import com.ruoyi.platform.mapper.AssetIconDao;
import com.ruoyi.platform.service.AssetIconService; import com.ruoyi.platform.service.AssetIconService;
import com.ruoyi.system.api.model.LoginUser; import com.ruoyi.system.api.model.LoginUser;
@ -38,6 +37,12 @@ public class AssetIconServiceImpl implements AssetIconService {
return this.assetIconDao.queryById(id); return this.assetIconDao.queryById(id);
} }
@Override
public AssetIcon queryByPath(String path) {
return this.assetIconDao.queryByPath(path);
}
/** /**
* 分页查询 * 分页查询
* *
@ -125,4 +130,6 @@ public class AssetIconServiceImpl implements AssetIconService {
public List<AssetIcon> queryByCategoryId(Integer categoryId) { public List<AssetIcon> queryByCategoryId(Integer categoryId) {
return this.assetIconDao.queryByCategoryId(categoryId); return this.assetIconDao.queryByCategoryId(categoryId);
} }
} }

View File

@ -3,10 +3,12 @@ package com.ruoyi.platform.service.impl;
import com.ruoyi.common.security.utils.SecurityUtils; import com.ruoyi.common.security.utils.SecurityUtils;
import com.ruoyi.platform.annotations.CheckDuplicate; import com.ruoyi.platform.annotations.CheckDuplicate;
import com.ruoyi.platform.domain.AssetIcon;
import com.ruoyi.platform.domain.Dataset; import com.ruoyi.platform.domain.Dataset;
import com.ruoyi.platform.domain.DatasetVersion; import com.ruoyi.platform.domain.DatasetVersion;
import com.ruoyi.platform.mapper.DatasetDao; import com.ruoyi.platform.mapper.DatasetDao;
import com.ruoyi.platform.mapper.DatasetVersionDao; import com.ruoyi.platform.mapper.DatasetVersionDao;
import com.ruoyi.platform.service.AssetIconService;
import com.ruoyi.platform.service.DatasetService; import com.ruoyi.platform.service.DatasetService;
import com.ruoyi.platform.service.DatasetVersionService; import com.ruoyi.platform.service.DatasetVersionService;
import com.ruoyi.platform.service.MinioService; import com.ruoyi.platform.service.MinioService;
@ -58,6 +60,9 @@ public class DatasetServiceImpl implements DatasetService {
@Resource @Resource
private DatasetVersionService datasetVersionService; private DatasetVersionService datasetVersionService;
@Resource
private AssetIconService assetIconService;
@Resource @Resource
private MinioService minioService; private MinioService minioService;
@ -77,7 +82,27 @@ public class DatasetServiceImpl implements DatasetService {
*/ */
@Override @Override
public Dataset queryById(Integer id) { public Dataset queryById(Integer id) {
return this.datasetDao.queryById(id); Dataset dataset = this.datasetDao.queryById(id);
if (dataset != null) {
String dataType = dataset.getDataType();
String dataTag = dataset.getDataTag();
// 判空逻辑只有当dataType和dataTag不为空时才进行查询
if (dataType != null && !dataType.isEmpty()) {
AssetIcon dataTypeAssetIcon = assetIconService.queryById(Integer.valueOf(dataType));
if (dataTypeAssetIcon != null) {
dataset.setDatasetTypeName(dataTypeAssetIcon.getName());
}
}
if (dataTag != null && !dataTag.isEmpty()) {
AssetIcon dataTagAssetIcon = assetIconService.queryById(Integer.valueOf(dataTag));
if (dataTagAssetIcon != null) {
dataset.setDatasetTagName(dataTagAssetIcon.getName());
}
}
}
return dataset;
} }
/** /**
@ -146,10 +171,10 @@ public class DatasetServiceImpl implements DatasetService {
} }
@Override @Override
public String removeById(Integer id) { public String removeById(Integer id) throws Exception {
Dataset dataset = this.datasetDao.queryById(id); Dataset dataset = this.datasetDao.queryById(id);
if (dataset == null){ if (dataset == null){
return "数据集不存在"; throw new Exception("数据集不存在");
} }
//判断权限只有admin和创建者本身可以删除该数据集 //判断权限只有admin和创建者本身可以删除该数据集
@ -157,10 +182,10 @@ public class DatasetServiceImpl implements DatasetService {
String username = loginUser.getUsername(); String username = loginUser.getUsername();
String createdBy = dataset.getCreateBy(); String createdBy = dataset.getCreateBy();
if (!(StringUtils.equals(username,"admin") || StringUtils.equals(username,createdBy))){ if (!(StringUtils.equals(username,"admin") || StringUtils.equals(username,createdBy))){
return "无权限删除该数据集版本"; throw new Exception("无权限删除该数据集");
} }
if (datasetVersionService.queryByDatasetId(id).size()>0){ if (datasetVersionService.queryByDatasetId(id).size()>0){
return "请先删除该数据集的版本文件"; throw new Exception("请先删除该数据集下的版本文件");
} }
dataset.setState(0); dataset.setState(0);

View File

@ -316,6 +316,7 @@ public class ImageServiceImpl implements ImageService {
String filePath = "/data/argo-workflow/" + bucketName + "/" +path; String filePath = "/data/argo-workflow/" + bucketName + "/" +path;
String logs2 = k8sClientUtil.executeCommand(pod,"docker load -i "+filePath); String logs2 = k8sClientUtil.executeCommand(pod,"docker load -i "+filePath);
// 在容器里执行 docker tag name:tag nexus3.kube-system.svc:8083/imageName:imageTag // 在容器里执行 docker tag name:tag nexus3.kube-system.svc:8083/imageName:imageTag
if (StringUtils.isNoneBlank(logs2)){ if (StringUtils.isNoneBlank(logs2)){
String substring = logs2.substring(logs2.indexOf(":")+1).trim(); String substring = logs2.substring(logs2.indexOf(":")+1).trim();
String tagCmd = "docker tag " + substring + " " + harborUrl + "/" + repository + "/" + username + "/" + imageName + ":" + imageTag; String tagCmd = "docker tag " + substring + " " + harborUrl + "/" + repository + "/" + username + "/" + imageName + ":" + imageTag;

View File

@ -1,10 +1,12 @@
package com.ruoyi.platform.service.impl; package com.ruoyi.platform.service.impl;
import com.ruoyi.common.security.utils.SecurityUtils; import com.ruoyi.common.security.utils.SecurityUtils;
import com.ruoyi.platform.domain.AssetIcon;
import com.ruoyi.platform.domain.Models; import com.ruoyi.platform.domain.Models;
import com.ruoyi.platform.domain.ModelsVersion; import com.ruoyi.platform.domain.ModelsVersion;
import com.ruoyi.platform.mapper.ModelsDao; import com.ruoyi.platform.mapper.ModelsDao;
import com.ruoyi.platform.mapper.ModelsVersionDao; import com.ruoyi.platform.mapper.ModelsVersionDao;
import com.ruoyi.platform.service.AssetIconService;
import com.ruoyi.platform.service.MinioService; import com.ruoyi.platform.service.MinioService;
import com.ruoyi.platform.service.ModelsService; import com.ruoyi.platform.service.ModelsService;
import com.ruoyi.platform.service.ModelsVersionService; import com.ruoyi.platform.service.ModelsVersionService;
@ -57,6 +59,9 @@ public class ModelsServiceImpl implements ModelsService {
@Resource @Resource
private MinioService minioService; private MinioService minioService;
@Resource
private AssetIconService assetIconService;
// 固定存储桶名 // 固定存储桶名
private final String bucketName = "platform-data"; private final String bucketName = "platform-data";
@ -72,7 +77,23 @@ public class ModelsServiceImpl implements ModelsService {
*/ */
@Override @Override
public Models queryById(Integer id) { public Models queryById(Integer id) {
return this.modelsDao.queryById(id); Models models = this.modelsDao.queryById(id);
String modelType = models.getModelType();
String modelTag = models.getModelTag();
//去资产管理表中查询对应的图标名,注意判空逻辑只有当dataType和dataTag不为空时才进行查询
if(modelType != null && !modelType.isEmpty()){
AssetIcon modelTypeAssetIcon = assetIconService.queryById(Integer.valueOf(modelType));
if (modelTypeAssetIcon != null){
models.setModelTypeName(modelTypeAssetIcon.getName());
}
}
if(modelTag != null && !modelTag.isEmpty()){
AssetIcon modelTagAssetIcon = assetIconService.queryById(Integer.valueOf(modelTag));
if (modelTagAssetIcon != null){
models.setModelTagName(modelTagAssetIcon.getName());
}
}
return models;
} }
/** /**
@ -137,23 +158,23 @@ public class ModelsServiceImpl implements ModelsService {
} }
@Override @Override
public String removeById(Integer id) { public String removeById(Integer id) throws Exception {
Models models = this.modelsDao.queryById(id); Models models = this.modelsDao.queryById(id);
if (models == null){ if (models == null){
return "模型不存在"; throw new Exception("模型不存在");
} }
//判断权限只有admin和创建者本身可以删除该数据集 //判断权限只有admin和创建者本身可以删除该模型
LoginUser loginUser = SecurityUtils.getLoginUser(); LoginUser loginUser = SecurityUtils.getLoginUser();
String username = loginUser.getUsername(); String username = loginUser.getUsername();
String createdBy = models.getCreateBy(); String createdBy = models.getCreateBy();
if (!(StringUtils.equals(username,"admin") || StringUtils.equals(username,createdBy))){ if (!(StringUtils.equals(username,"admin") || StringUtils.equals(username,createdBy))){
return "无权限删除该模型"; throw new Exception("无权限删除该模型");
} }
if (modelsVersionService.queryByModelsId(id).size()>0){ if (modelsVersionService.queryByModelsId(id).size()>0){
return "请先删除该模型的版本文件"; throw new Exception("请先删除该镜像下的版本文件");
} }
models.setState(0); models.setState(0);
return this.modelsDao.update(models)>0?"删除成功":"删除失败"; return this.modelsDao.update(models)>0?"删除成功":"删除失败";
@ -207,7 +228,6 @@ public class ModelsServiceImpl implements ModelsService {
*/ */
@Override @Override
public List<Map<String, String>> uploadModels(MultipartFile[] files, String uuid) throws Exception { public List<Map<String, String>> uploadModels(MultipartFile[] files, String uuid) throws Exception {
List<Map<String, String>> results = new ArrayList<>(); List<Map<String, String>> results = new ArrayList<>();
for (MultipartFile file:files) { for (MultipartFile file:files) {
// 构建objectName // 构建objectName
@ -232,7 +252,6 @@ public class ModelsServiceImpl implements ModelsService {
ModelsVersion version = modelsVersionService.queryByModelsVersion(modelsVersion); ModelsVersion version = modelsVersionService.queryByModelsVersion(modelsVersion);
String url = ""; String url = "";
if (version == null) { if (version == null) {
//插表,因为这里是一次直接插表所以这里定掉date然后用DAO插入 //插表,因为这里是一次直接插表所以这里定掉date然后用DAO插入

View File

@ -43,6 +43,13 @@
</where> </where>
</select> </select>
<select id="queryByPath" resultMap="AssetIconMap" >
select
id, name,category_id, path, description, create_by, create_time, update_by, update_time, state
from asset_icon
where path = #{path} and state = 1
</select>
<select id="queryAllByLimit" resultMap="AssetIconMap"> <select id="queryAllByLimit" resultMap="AssetIconMap">
select select
id, name, category_id, path, description, create_by, create_time, update_by, update_time, state id, name, category_id, path, description, create_by, create_time, update_by, update_time, state
@ -123,6 +130,7 @@
</where> </where>
</select> </select>
<insert id="insert" keyProperty="id" useGeneratedKeys="true"> <insert id="insert" keyProperty="id" useGeneratedKeys="true">
insert into asset_icon(name, category_id, path, description, create_by, create_time, update_by, update_time, state) insert into asset_icon(name, category_id, path, description, create_by, create_time, update_by, update_time, state)
values (#{assetIcon.name}, #{assetIcon.categoryId}, #{assetIcon.path}, #{assetIcon.description}, #{assetIcon.createBy}, #{assetIcon.createTime}, #{assetIcon.updateBy}, #{assetIcon.updateTime}, #{assetIcon.state}) values (#{assetIcon.name}, #{assetIcon.categoryId}, #{assetIcon.path}, #{assetIcon.description}, #{assetIcon.createBy}, #{assetIcon.createTime}, #{assetIcon.updateBy}, #{assetIcon.updateTime}, #{assetIcon.state})

View File

@ -152,13 +152,13 @@
description = #{dataset.description}, description = #{dataset.description},
</if> </if>
<if test="dataset.availableRange != null and dataset.availableRange != ''"> <if test="dataset.availableRange != null and dataset.availableRange != ''">
available_range = #{dataset.availableRange} available_range = #{dataset.availableRange},
</if>, </if>
<if test="dataset.dataType != null"> <if test="dataset.dataType != null">
data_type = #{dataset.dataType}, data_type = #{dataset.dataType},
</if> </if>
<if test="dataset.dataTag != null"> <if test="dataset.dataTag != null">
data_tag = #{dataset.dataTag} data_tag = #{dataset.dataTag},
</if> </if>
<if test="dataset.createBy != null and dataset.createBy != ''"> <if test="dataset.createBy != null and dataset.createBy != ''">
create_by = #{dataset.createBy}, create_by = #{dataset.createBy},
@ -179,6 +179,7 @@
where id = #{dataset.id} where id = #{dataset.id}
</update> </update>
<!--通过主键删除--> <!--通过主键删除-->
<delete id="deleteById"> <delete id="deleteById">
delete from dataset where id = #{id} delete from dataset where id = #{id}

View File

@ -154,7 +154,7 @@
description = #{models.description}, description = #{models.description},
</if> </if>
<if test="models.availableRange != null and models.availableRange != ''"> <if test="models.availableRange != null and models.availableRange != ''">
available_range = #{models.availableRange} available_range = #{models.availableRange},
</if> </if>
<if test="models.modelType != null"> <if test="models.modelType != null">
model_type = #{models.modelType}, model_type = #{models.modelType},