Commit b54804b5 by zhaochengxiang

模型打标签

parent cba055b8
...@@ -54,7 +54,7 @@ module.exports = { ...@@ -54,7 +54,7 @@ module.exports = {
changeOrigin: true, changeOrigin: true,
}, },
'/data-quality': { '/data-quality': {
target: 'http://localhost:3000', target: 'http://192.168.0.111:8189',
changeOrigin: true, changeOrigin: true,
}, },
}, },
......
import * as service from '../service/tag'; import * as service from '../service/tag';
import { call } from 'redux-saga/effects'; import { call } from 'redux-saga/effects';
export function* batchAddTagResource(payload) {
return yield call(service.batchAddTagResource, payload);
}
export function* deleteTagResource(payload) {
return yield call(service.deleteTagResource, payload);
}
export function* getResourceTagIn(payload) { export function* getResourceTagIn(payload) {
return yield call(service.getResourceTagIn, payload); return yield call(service.getResourceTagIn, payload);
} }
export function* batchAddTagResourceByTagList(payload) { export function* deleteTags(payload) {
return yield call(service.batchAddTagResourceByTagList, payload); return yield call(service.deleteTags, payload);
}
export function* getTagByKeywordAndCreator(payload) {
return yield call(service.getTagByKeywordAndCreator, payload);
}
export function* getSupportTagForm(payload) {
return yield call(service.getSupportTagForm, payload);
}
export function* saveTag(payload) {
return yield call(service.saveTag, payload);
} }
export function* getTagIdListByNameList(payload) { export function* addTags(payload) {
return yield call(service.getTagIdListByNameList, payload); return yield call(service.addTags, payload);
} }
\ No newline at end of file
import { GetJSON, PostJSON, Delete } from "../util/axios"; import { PostJSON, Delete, callFetchRaw } from "../util/axios";
export function batchAddTagResource(payload) {
return PostJSON("/tagger/tagResource/batchAddTagResource", payload);
}
export function deleteTagResource(payload) {
return Delete("/tagger/tagResource/deleteTagResource", payload);
}
export function getResourceTagIn(payload) { export function getResourceTagIn(payload) {
return PostJSON("/tagger/tagResource/getResourceTagIn", payload); return callFetchRaw('post', '/tagger/manager/tag/resource/getResourceTagIn', payload)
}
export function batchAddTagResourceByTagList(payload) {
return PostJSON("/tagger/tagResource/batchAddTagResourceByTagList", payload)
} }
export function getTagByKeywordAndCreator(payload) { export function deleteTags(payload) {
return GetJSON("/tagger/tag/getTagByKeywordAndCreator", payload); return Delete("/tagger/manager/tag/resource/deleteTagInResource", payload);
} }
export function getSupportTagForm(payload) { export function addTags(payload) {
return GetJSON("/tagger/tag/getSupportTagForm", payload); return PostJSON("/tagger/manager/tag/resource/batchAddTagResourceByTagList", payload);
} }
\ No newline at end of file
export function saveTag(payload) {
return PostJSON("/tagger/tag/saveTag", payload);
}
export function getTagIdListByNameList(payload) {
return PostJSON("/tagger/tag/getTagIdListByNameList", payload);
}
...@@ -162,4 +162,28 @@ export function PostFile(url, payload) { ...@@ -162,4 +162,28 @@ export function PostFile(url, payload) {
return fileplain.post(url, formData, { params } ).then( return fileplain.post(url, formData, { params } ).then(
callback callback
) )
} }
\ No newline at end of file
const config = {
baseURL,
headers: {
'Content-Type': 'multipart/form-data',
},
cache: 'no-cache',
}
export const callFetchRaw = (method, url, options) => {
const { params, ...reqConfig } = options;
var bodyFormData = new FormData();
Object.keys(params||[]).forEach(key => {
bodyFormData.append(key, params[key]);
});
return axios.request({
method,
url,
data: bodyFormData,
...config,
...reqConfig
})
}
...@@ -6,9 +6,7 @@ import MetadataInfo from './MetadataInfo'; ...@@ -6,9 +6,7 @@ import MetadataInfo from './MetadataInfo';
import { dispatch } from '../../../../model'; import { dispatch } from '../../../../model';
import { highlightSearchContentByTerms, showMessage, getAssetRange, checkMenuAdmit } from '../../../../util'; import { highlightSearchContentByTerms, showMessage, getAssetRange, checkMenuAdmit } from '../../../../util';
import { AppContext } from '../../../../App'; import { AppContext } from '../../../../App';
import Tag from '../../Tag';
import Separate from './Separate'; import Separate from './Separate';
import AssetTagModal from './AssetTagModal';
import { AnchorId, AnchorDirId, AssetManageReference } from '../../../../util/constant'; import { AnchorId, AnchorDirId, AssetManageReference } from '../../../../util/constant';
import PermissionButton from '../../../../util/Component/PermissionButton'; import PermissionButton from '../../../../util/Component/PermissionButton';
import DataQuality, { DataQualityFeignTagList } from '../../../QianKun/data-quality' import DataQuality, { DataQualityFeignTagList } from '../../../QianKun/data-quality'
...@@ -28,8 +26,6 @@ const AssetAction = (props) => { ...@@ -28,8 +26,6 @@ const AssetAction = (props) => {
const [ assetPaths, setAssetPaths ] = useState([]); const [ assetPaths, setAssetPaths ] = useState([]);
const [ resourceRelations, setResourceRelations ] = useState([]); const [ resourceRelations, setResourceRelations ] = useState([]);
const { assets, attributes, attributesFoldMap } = assetParams; const { assets, attributes, attributesFoldMap } = assetParams;
const [ assetTagModalVisible, setAssetTagModalVisible ] = useState(false);
const [ selectTag, setSelectTag ] = useState({});
useEffect(() => { useEffect(() => {
if (action === 'add') { if (action === 'add') {
...@@ -346,23 +342,6 @@ const AssetAction = (props) => { ...@@ -346,23 +342,6 @@ const AssetAction = (props) => {
}) })
} }
const onAssetTag = (value) => {
setSelectTag(value);
setAssetTagModalVisible(true);
}
const onAssetTagModalCancel = (refresh = false) => {
setAssetTagModalVisible(false);
if (refresh) {
getResourceRelations();
let event = new Event('storage');
event.key = 'tagChange';
window?.dispatchEvent(event);
}
}
const formItemLayout = { const formItemLayout = {
labelCol: { labelCol: {
xs: { span: 24 }, xs: { span: 24 },
...@@ -608,21 +587,6 @@ const AssetAction = (props) => { ...@@ -608,21 +587,6 @@ const AssetAction = (props) => {
</div> </div>
</React.Fragment> </React.Fragment>
} }
<AppContext.Consumer>
{
value => {
return (
<AssetTagModal
visible={assetTagModalVisible}
id={id}
tag={selectTag}
creator={value?.user?.userName||''}
onCancel={onAssetTagModalCancel}
/>
);
}
}
</AppContext.Consumer>
</div> </div>
) )
} }
......
import React, { useEffect, useState } from 'react';
import { Modal, Form, Input, Space, Button, Select } from 'antd';
import { dispatch } from '../../../../model';
import { AppContext } from '../../../../App';
import MetadataInfo from './MetadataInfo';
import { showMessage, getAssetRange } from '../../../../util';
import { AssetManageReference } from '../../../../util/constant';
const { Option } = Select;
const DirsSelect = ({ value = [], data = [], onChange, ...restProps }) => {
const onDirChange = (value, option) => {
if (option.length > 0) {
const recentItem = option[option.length-1];
const recentItemTheme = recentItem.children.split('/')[0];
const newValue = [];
option.forEach(item => {
if (item.children.split('/')[0] !== recentItemTheme) {
newValue.push(item.value);
}
})
newValue.push(recentItem.value);
onChange(newValue);
} else {
onChange(value);
}
}
return (
<Select
mode="multiple"
value={value||[]}
onChange={onDirChange}
allowClear
style={{ width: '100%' }}
>
{
(data||[]).map((dir, index) => {
return (
<Option key={index} value={dir.id||''}>{dir.path||''}</Option>
);
})
}
</Select>
)
}
const AssetTagModal = (props) => {
const { onCancel, visible, id, tag, creator } = props;
const [ form ] = Form.useForm();
const [ confirmLoading, setConfirmLoading ] = useState(false);
const [ elements, setElements ] = useState([]);
const [ asset, setAsset ] = useState('');
const [ metadataId, setMetadataId ] = useState('');
const [ dirs, setDirs ] = useState([]);
useEffect(() => {
if (visible && (id||'')!=='') {
getAsset();
} else {
setMetadataId('');
}
//eslint-disable-next-line react-hooks/exhaustive-deps
}, [ visible, id ])
useEffect(() => {
if (visible && (tag?.name||'')!=='') {
getDirectoryByName(tag?.name);
}
//eslint-disable-next-line react-hooks/exhaustive-deps
}, [ visible ])
const getAsset = () => {
dispatch({
type: 'assetmanage.getDataAssetDetail',
payload: {
dataAssetId: id,
range: getAssetRange(AssetManageReference),
},
callback: data => {
setAsset(data);
setElements(data?.elements||[]);
setMetadataId(data.mid||'');
let _fieldsValue = {};
(data.elements||[]).forEach(element => {
_fieldsValue[element.name] = element.value;
})
form.setFieldsValue(_fieldsValue);
}
})
}
const getDirectoryByName = (keyword) => {
dispatch({
type: 'assetmanage.listDirectoryByName',
payload: {
name: keyword
},
callback: data => {
setDirs(data||[]);
}
});
}
//选中元数据后 内容回填.
const fillElementValueBeforeCreate = (mid = metadataId) => {
dispatch({
type: 'assetmanage.fillElementValueBeforeCreate',
payload: {
params: {
metadataIds: mid
}
},
callback: data => {
let _fieldsValue = {};
(data||[]).forEach(element => {
_fieldsValue[element.name] = element.value;
})
form.setFieldsValue(_fieldsValue);
}
})
}
const onOk = async() => {
try {
const row = await form.validateFields();
const newElements = [...elements];
(newElements||[]).forEach(element => {
if (row.hasOwnProperty(element.name)) {
element.value = row[element.name];
}
});
const params = {
dirId: (row.dirs||[]).join(','),
}
if ((metadataId||'')!=='') {
params.metadataId = metadataId;
}
setConfirmLoading(true);
dispatch({
type: 'assetmanage.checkCodeIsExist',
payload: {
data: { ...asset, elements: newElements }
},
callback: isExist => {
if (isExist === 'true') {
setConfirmLoading(false);
showMessage('warn', '已存在相同的资产编号,请重新输入');
} else {
dispatch({
type: 'assetmanage.addOrUpdateDataAsset',
payload: {
params,
data: { ...asset, elements: newElements }
},
callback: data => {
const ids = [id];
if (data && ((data?.id||'')!=='') && (data?.id!==id)) {
ids.push(data?.id);
}
dispatch({
type: 'tag.batchAddTagResourceByTagList',
payload: {
params: {
tagIds: tag?.tagId,
resourceIds: ids.join(','),
type: 'dataAsset',
creator
}
},
callback: () => {
reset();
onCancel && onCancel(true);
},
error: () => {
setConfirmLoading(false);
}
})
},
error: () => {
setConfirmLoading(false);
}
})
}
},
error: () => {
setConfirmLoading(false);
}
})
} catch (errInfo) {
console.log('Validate Failed:', errInfo);
setConfirmLoading(false);
}
}
const reset = () => {
setConfirmLoading(false);
form?.resetFields();
}
const formItemLayout = {
labelCol: {
xs: { span: 24 },
sm: { span: 5 },
},
wrapperCol: {
xs: { span: 24 },
sm: { span: 17 },
},
};
return (
<Modal
forceRender
className='asset-add'
title='资产打标签'
visible={ visible }
width={ 600 }
onCancel={() => {
reset();
onCancel && onCancel();
} }
footer={
<Space>
<Button onClick={() => onCancel && onCancel() }>取消</Button>
<Button type="primary" onClick={ onOk } loading={ confirmLoading }>确定</Button>
</Space>
}
>
{
visible && <Form {...formItemLayout} form={form}>
<AppContext.Consumer>
{
value => {
value?.onGlobalStateChange&&value?.onGlobalStateChange((state, prev) => {
if (state.message === 'data-govern-show-metadata-list-callback-message') {
setMetadataId(state.data?.metadataId||'');
form?.setFieldsValue({ '资产项': state.data?.metadataInfoJson||'' });
if ((state.data?.metadataId||'') !== '') {
fillElementValueBeforeCreate(state.data?.metadataId||'');
}
}
});
return (
<>
<Form.Item
label='资产标签'
name='tag'
>
<span>{tag?.name||''}</span>
</Form.Item>
<Form.Item
label='挂载目录'
name='dirs'
required={true}
>
<DirsSelect data={dirs||[]} />
</Form.Item>
{ (elements||[]).map((element, index) => {
return (
<Form.Item
label={element.name}
name={element.name}
key={index}
>
{ (element.name==='资产项') ? <MetadataInfo /> : <Input disabled={element.manualMaintain==='否'} /> }
</Form.Item>
)
})}
</>
);
}}
</AppContext.Consumer>
</Form>
}
</Modal>
);
}
export default AssetTagModal;
...@@ -7,9 +7,9 @@ import { dispatch } from '../../../../model'; ...@@ -7,9 +7,9 @@ import { dispatch } from '../../../../model';
import { showMessage, getQueryParam, isSzseEnv, formatDate } from '../../../../util'; import { showMessage, getQueryParam, isSzseEnv, formatDate } from '../../../../util';
import { AnchorId, AnchorTimestamp, Action, CatalogId, ModelerId } from '../../../../util/constant'; import { AnchorId, AnchorTimestamp, Action, CatalogId, ModelerId } from '../../../../util/constant';
import ExpandedModelTable from "./ExpandedModelTable"; import ExpandedModelTable from "./ExpandedModelTable";
// import Tag from "../../Tag";
import { useContextMenu, Menu as RcMenu, Item as RcItem } from "react-contexify"; import { useContextMenu, Menu as RcMenu, Item as RcItem } from "react-contexify";
import PermissionRcItem from '../../../../util/Component/PermissionRcItem'; import PermissionRcItem from '../../../../util/Component/PermissionRcItem';
import TagCell from './tag-help';
import './ModelTable.less'; import './ModelTable.less';
import 'react-contexify/dist/ReactContexify.css'; import 'react-contexify/dist/ReactContexify.css';
...@@ -140,6 +140,7 @@ const ModelTable = (props) => { ...@@ -140,6 +140,7 @@ const ModelTable = (props) => {
const [ expandedSelectedRowKeys, setExpandedSelectedRowKeys ] = useState([]); const [ expandedSelectedRowKeys, setExpandedSelectedRowKeys ] = useState([]);
const [ currentItem, setCurrentItem ] = useState(null); const [ currentItem, setCurrentItem ] = useState(null);
const [ scrollRowIndex, setScrollRowIndex ] = useState(); const [ scrollRowIndex, setScrollRowIndex ] = useState();
const [resoureTagMap, setResourceTagMap] = useState();
const expandedDataMapRef = useRef(new Map()); const expandedDataMapRef = useRef(new Map());
const shouldScrollRef = useRef(false); const shouldScrollRef = useRef(false);
...@@ -255,12 +256,27 @@ const ModelTable = (props) => { ...@@ -255,12 +256,27 @@ const ModelTable = (props) => {
) )
} }
}, },
{
name:'标签',
key:'tag',
width:360,
className: 'table-tag-cell',
formatter(props) {
return (
props.row?.state?.id === '4' ? <TagCell
id={props.row?.id}
type='model'
tags={resoureTagMap?.[`${props.row?.id}`]}
/> : null
)
}
}
]; ];
const columns = useMemo(() => { const columns = useMemo(() => {
let newCols = [...cols]; let newCols = [...cols];
if ((visibleColNames||[]).length > 0) { if ((visibleColNames||[]).length > 0) {
newCols = newCols.filter(col => visibleColNames.indexOf(col.name)!==-1 || col.name==='序号'); newCols = newCols.filter(col => visibleColNames.indexOf(col.name)!==-1 || col.name==='序号' || col.name==='标签');
if (visibleColNames.indexOf('模型描述') === -1) { if (visibleColNames.indexOf('模型描述') === -1) {
newCols[newCols.length-1].width = null; newCols[newCols.length-1].width = null;
...@@ -269,7 +285,7 @@ const ModelTable = (props) => { ...@@ -269,7 +285,7 @@ const ModelTable = (props) => {
return newCols; return newCols;
//eslint-disable-next-line react-hooks/exhaustive-deps //eslint-disable-next-line react-hooks/exhaustive-deps
}, [visibleColNames]) }, [visibleColNames, resoureTagMap])
const summarySelectedCount = useMemo(() => { const summarySelectedCount = useMemo(() => {
let newSelectedRowKeys = Array.from(new Set([...selectedRowKeys, ...expandedSelectedRowKeys])); let newSelectedRowKeys = Array.from(new Set([...selectedRowKeys, ...expandedSelectedRowKeys]));
...@@ -288,7 +304,9 @@ const ModelTable = (props) => { ...@@ -288,7 +304,9 @@ const ModelTable = (props) => {
}, [selectedRowKeys, expandedSelectedRowKeys, data]) }, [selectedRowKeys, expandedSelectedRowKeys, data])
useEffect(() => { useEffect(() => {
if (data && gridRef.current) { if (data) {
getResourceTag();
setTimeout(() => { setTimeout(() => {
try { try {
gridRef.current?.scrollToRow(0); gridRef.current?.scrollToRow(0);
...@@ -342,6 +360,27 @@ const ModelTable = (props) => { ...@@ -342,6 +360,27 @@ const ModelTable = (props) => {
} }
}) })
const getResourceTag = () => {
const ids = (data??[]).map(item => item.id);
if (ids.length > 0) {
dispatch({
type: 'tag.getResourceTagIn',
payload: {
params: {
resourceIds: ids,
includeAll: true,
includePrivate: true
}
},
callback: data => {
setResourceTagMap(data?.data)
}
});
} else {
setResourceTagMap()
}
}
const modelEventChange = (e) => { const modelEventChange = (e) => {
if (e.key === 'modelChange') { if (e.key === 'modelChange') {
expandedDataMapRef.current.delete(LocalStorage.get('modelId')); expandedDataMapRef.current.delete(LocalStorage.get('modelId'));
...@@ -622,8 +661,9 @@ const ModelTable = (props) => { ...@@ -622,8 +661,9 @@ const ModelTable = (props) => {
<PermissionRcItem <PermissionRcItem
id={`action-${index}`} id={`action-${index}`}
onClick={handleItemClick} onClick={handleItemClick}
permissionKey={item.cnName} defaultPermission={true}
permissions={currentItem?.optionList?.filter(item => item.enabled).map(item => item.name)} // permissionKey={item.cnName}
// permissions={currentItem?.optionList?.filter(item => item.enabled).map(item => item.name)}
> >
{item.cnName||''} {item.cnName||''}
</PermissionRcItem> </PermissionRcItem>
......
import React, { useEffect } from 'react'
import { Tooltip, Tag, Typography, Space, Button, Modal, Spin } from 'antd'
import {
EllipsisOutlined,
PlusOutlined,
} from '@ant-design/icons'
import DataQuality, { DataQualityFeighTagSelect } from '../../../QianKun/data-quality'
import { showMessage } from '../../../../util'
import { dispatch } from '../../../../model'
import { AppContext } from '../../../../App'
const FC = ({ type, id, tags }) => {
const [data, setData] = React.useState([])
const [tagSelectPopupParams, setTagSelectPopupParams] = React.useState({
visible: false,
type: undefined,
items: undefined,
})
useEffect(() => {
setData(tags)
}, [tags])
const getTags = () => {
dispatch({
type: 'tag.getResourceTagIn',
payload: {
params: {
resourceIds: id,
includeAll: true,
includePrivate: true
}
},
callback: data => {
setData(data?.data?.[`${id}`]);
}
});
}
return (
<React.Fragment>
<TagCell
value={data}
onChange={(val) => {
getTags()
}}
onAdd={(val) => {
setTagSelectPopupParams({
visible: true,
type,
items: [{ resourceId: id }]
})
}}
/>
<TagSelectPopup
onCancel={() => {
setTagSelectPopupParams({
visible: false,
type: undefined,
items: undefined
})
}}
onChange={(val) => {
getTags()
}}
{...tagSelectPopupParams}
/>
</React.Fragment>
)
}
export default FC
export const TagRender = (props) => {
const { data, closable, onClose, style, ...restProps } = props;
const color = React.useMemo(() => {
if (data) {
return (data.type==='public') ? '#196AD2' : '#4C7813'
}
return ''
}, [data])
const onPreventMouseDown = (event) => {
event.preventDefault();
event.stopPropagation();
};
return (
<Tooltip title={data?.name}>
<Tag
onMouseDown={onPreventMouseDown}
closable={closable}
onClose={onClose}
style={{
marginRight: 3,
padding: '2px 7px',
textAlign: 'center',
borderColor: color,
color,
backgroundColor: '#fff',
...style
}}
{...restProps}
>
<Typography.Text ellipsis={true} style={{ width: 56 }}>
<span style={{ color }}>{data?.name}</span>
</Typography.Text>
</Tag>
</Tooltip>
);
};
export function TagCell({ value, readonly = false, onChange, onAdd }) {
const maxShowCount = 3
const [haveMore, _value] = React.useMemo(() => {
return [(value??[]).length>maxShowCount, (value??[]).slice(0, maxShowCount)]
}, [value])
const onClose = (e, tag) => {
e.preventDefault()
if (tag?.id) {
dispatch({
type: 'tag.deleteTags',
payload: {
tagInResourceIds: tag?.id
},
callback: () => {
showMessage('success', '删除成功');
onChange?.()
}
})
}
}
return (
<div style={{
display: 'flex',
marginRight: '12px',
justifyContent: 'space-between',
alignItems: 'center',
height: '100%',
}}>
{
_value?.map((item, index) => <TagRender
key={index}
data={item}
closable={!readonly&&item?.deleteAble}
onClose={(e) => {
onClose(e, item)
}}
style={{ marginRight: 0 }}
/>)
}
{
haveMore && <Tooltip title={
<Space wrap={true}>
{
value?.map((item, index) => <TagRender
key={index}
data={item}
closable={!readonly&&item?.deleteAble}
onClose={(e) => {
onClose(e, item)
}}
/>)
}
</Space>
}>
<Button size='small' icon={<EllipsisOutlined />}></Button>
</Tooltip>
}
{
!readonly && <Button size='small' icon={<PlusOutlined />} onClick={(e) => {
e.stopPropagation()
onAdd?.()
}}></Button>
}
<React.Fragment>
{
(_value??[]).length < maxShowCount && Array.from({ length: maxShowCount-_value.length }, (_, key) => <div key={key} style={{ width: 80 }} />)
}
</React.Fragment>
{
!haveMore && <div style={{ width: 24 }} />
}
</div>
)
}
export function TagSelectPopup({ visible, type, items, onCancel, onChange }) {
const [waiting, setWaiting] = React.useState(false)
const [selectedRows, setSelectedRows] = React.useState([])
const [showDataQuality, setShowDataQuality] = React.useState(false)
const app = React.useContext(AppContext)
React.useEffect(() => {
if (visible) {
setTimeout(() => {
setShowDataQuality(true)
}, 300)
}
}, [visible])
const close = () => {
setWaiting(false)
setShowDataQuality(false)
setSelectedRows([])
onCancel?.()
}
const save = async () => {
if (items) {
if ((selectedRows??[]).length === 0) {
showMessage('warn', '请先选择标签')
return
}
setWaiting(true)
dispatch({
type: 'tag.addTags',
payload: {
params: {
tagIds: (selectedRows??[]).map(item => item.id).toString(),
resourceIds: (items??[]).map(item => item.resourceId).toString(),
type,
catalogId: (items??[]).length>0?items[0].resourceCatalogId:'',
creator: app?.user?.userName,
}
},
callback: () => {
setWaiting(false)
onChange?.(selectedRows)
close()
},
error: () => {
setWaiting(false)
}
})
} else {
onChange?.(selectedRows)
close()
}
}
const footer = React.useMemo(() => {
return [
<Button key={'cancel'}
onClick={() => close()}
>取消</Button>,
<Button key={'save'} type='primary'
onClick={() => save()}
>确定</Button>
]
}, [close, save])
return (
<Modal
visible={visible}
footer={footer}
width='90%'
bodyStyle={{ padding: '15px 15px 0px 15px', overflowX: 'auto', minHeight: '60vh' }}
title='选择标签'
centered destroyOnClose
onCancel={() => { close() }}
>
<Spin spinning={waiting} >
{
showDataQuality ? <DataQuality type={DataQualityFeighTagSelect} onChange={(val) => {
setSelectedRows(val)
}} /> : null
}
</Spin>
</Modal>
)
}
\ No newline at end of file
import React, { useState, useEffect } from 'react';
import { Modal, Button, AutoComplete, Input, Avatar, TreeSelect, Form, Select, Checkbox } from 'antd';
import { dispatch } from '../../../../model';
import { showMessage } from '../../../../util';
import './AddTagModal.less';
const { Option } = Select;
const ClassifySelect = ({ value = '', data = [], onChange, ...restProps }) => {
const onClassifyChange = (value) => {
onChange((value||[]).join(','));
}
return (
<Select
mode="multiple"
value={value?value.split(','):[]}
onChange={onClassifyChange}
allowClear
style={{ width: '100%' }}
>
{
(data?.tagClassifyNodes||[]).map((item, index) => {
return (
<Option key={item.id}>{item.cnName||''}</Option>
);
})
}
</Select>
)
}
const AddTagModal = (props) => {
const { visible, onCancel, id, type, creator, onAssetTag } = props;
const [ step, setStep ] = useState(0);
const [ keyword, setKeyword ] = useState('');
const [ selectTag, setSelectTag ] = useState(null);
const [ matchTags, setMatchTags ] = useState([]);
const [ options, setOptions ] = useState([]);
const [ confirmLoading, setConfirmLoading ] = useState(false);
const [ tagForm, setTagForm ] = useState({});
const [ onlyTag, setOnlyTag ] = useState(true);
const [form] = Form.useForm();
useEffect(() => {
if (visible) {
getTagByKeywordAndCreator();
getSupportTagForm();
}
//eslint-disable-next-line react-hooks/exhaustive-deps
}, [ visible ])
const getTagByKeywordAndCreator = (value=keyword) => {
dispatch({
type: 'tag.getTagByKeywordAndCreator',
payload: {
keyword: value,
creator,
},
callback: data => {
setMatchTags(data||[]);
let tagExsit = false, _selectTag = null;
const _options = [];
(data||[]).forEach(item => {
_options.push({
value: item.tagId,
label: (
<div
style={{
display: 'flex',
justifyContent: 'space-between',
}}
>
{item.name||''}
<Avatar
shape="square"
size="small"
title={item.type==='public'?'公共标签':'个人标签'}
>
{item.type==='public'?'公':'个'}
</Avatar>
</div>
)
});
if (value==='' || item.name===value) {
tagExsit = true;
}
if (item.name===value && selectTag===null) {
_selectTag = item;
}
})
if (!tagExsit) {
_options.push({
value: -1,
label: (
<a >{`创建 ${value||''}`}</a>
)
})
}
setSelectTag(_selectTag);
setOptions(_options);
}
})
}
const getSupportTagForm = () => {
dispatch({
type: 'tag.getSupportTagForm',
payload: {
isDefault: false,
type: 'private',
creator
},
callback: data => {
setTagForm(data||{});
}
})
}
const onOk = () => {
if (keyword === '') {
showMessage('warn', '请先填写标签');
return;
}
if (onlyTag) {
if (selectTag) {
setConfirmLoading(true);
dispatch({
type: 'tag.batchAddTagResource',
payload: {
params: {
tagId: selectTag.tagId,
resourceIds: id,
type,
creator
}
},
callback: () => {
reset();
onCancel && onCancel(true);
},
error: () => {
setConfirmLoading(false);
}
})
} else {
form.resetFields();
setStep(1);
}
} else {
if (selectTag) {
if (selectTag.type === 'public') {
setConfirmLoading(true);
dispatch({
type: 'assetmanage.listDirectoryByName',
payload: {
name: keyword
},
callback: data => {
setConfirmLoading(false);
if ((data||[]).length===0) {
setConfirmLoading(true);
dispatch({
type: 'tag.batchAddTagResource',
payload: {
params: {
tagId: selectTag.tagId,
resourceIds: id,
type,
creator
}
},
callback: () => {
reset();
onCancel && onCancel(true);
},
error: () => {
setConfirmLoading(false);
}
})
} else {
reset();
onAssetTag && onAssetTag(selectTag);
}
},
error: () => {
setConfirmLoading(false);
}
})
} else {
setConfirmLoading(true);
dispatch({
type: 'tag.batchAddTagResource',
payload: {
params: {
tagId: selectTag.tagId,
resourceIds: id,
type,
creator
}
},
callback: () => {
reset();
onCancel && onCancel(true);
},
error: () => {
setConfirmLoading(false);
}
})
}
} else {
form.resetFields();
setStep(1);
}
}
}
const cancel = () => {
reset();
onCancel && onCancel();
}
const onBack = () => {
setStep(0);
}
const onCreate = async () => {
try {
const row = await form?.validateFields();
const newTagForm = JSON.parse(JSON.stringify(tagForm));
(newTagForm?.targetParameters||[]).forEach(param => {
param.value = row[param.name];
})
setConfirmLoading(true);
dispatch({
type: 'tag.saveTag',
payload: {
params: {
isDefault: false
},
data: newTagForm
},
callback: data => {
dispatch({
type: 'tag.batchAddTagResource',
payload: {
params: {
tagId: data.id,
resourceIds: id,
type,
creator
}
},
callback: () => {
reset();
onCancel && onCancel(true);
},
error: () => {
setConfirmLoading(false);
}
})
},
error: () => {
setConfirmLoading(false);
}
})
} catch (errInfo) {
console.log('Validate Failed:', errInfo);
}
}
const onKeywordChange = (value) => {
//匹配行被选中时 跳过
if (value!==undefined && value!==null && typeof(value)!=='string') return;
setSelectTag(null);
setOptions([]);
setKeyword(value||'');
getTagByKeywordAndCreator(value||'');
}
const onSelect = (value, option) => {
if (value!==-1) {
let _selectTag = null;
matchTags.forEach(item => {
if (item.tagId === value) {
_selectTag = item;
}
})
setSelectTag(_selectTag);
setKeyword(_selectTag.name||'');
} else {
setSelectTag(null);
setStep(1);
}
}
const reset = () => {
setConfirmLoading(false);
form.resetFields();
setStep(0);
setSelectTag(null);
setKeyword('');
setOnlyTag(true);
}
const onOnlyTagChange = (e) => {
setOnlyTag(e.target.checked);
}
const loop = (data) =>
data.map(item => {
if ((item.children||[]).length>0) {
return { ...item, ...{title: item.name, value: item.idString, children: loop(item.children)} };
}
return { ...item, ...{ title: item.name, value: item.idString }};
});
const formItemLayout = {
labelCol: {
xs: { span: 24 },
sm: { span: 4 },
},
wrapperCol: {
xs: { span: 24 },
sm: { span: 20 },
},
};
let footer = [];
if (step === 0) {
footer = [
<Button
key="0"
onClick={cancel}
>
取消
</Button>,
<Button
key="1"
type="primary"
loading={confirmLoading}
onClick={onOk}
disabled={keyword===''||options.length===0}
>
确定
</Button>
];
} else if (step === 1) {
footer = [
<Button
key="0"
onClick={onBack}
>
返回
</Button>,
<Button
key="1"
type="primary"
loading={confirmLoading}
onClick={onCreate}
>
创建
</Button>
];
}
return (
<Modal
className='add-tag-modal'
forceRender
visible={visible}
title={(step===0)?'新建标签':`创建${keyword}`}
width={520}
onCancel={cancel}
footer={footer}
>
{
step === 0 && <span>
<AutoComplete
style={{ width: '80%', marginRight: 5 }}
allowClear
value={keyword}
onChange={onKeywordChange}
options={options}
onSelect={onSelect}
/>
<Checkbox onChange={onOnlyTagChange} checked={onlyTag}>仅打标签</Checkbox>
</span>
}
{
step === 1 && <Form
{...formItemLayout}
form={form}
initialValues={{ name: keyword }}
>
{
(tagForm?.targetParameters||[]).map((param, index) => {
if (param.name === 'usable') return null;
let itemComponent = (
<Input disabled={param.name==='name'} />
);
if (param.selectMode==='singleSelect') {
itemComponent = (
<TreeSelect
style={{ width: '100%' }}
dropdownStyle={{ maxHeight: 400, overflow: 'auto' }}
treeData={loop(param?.tagCatalogNode?.children||[])}
treeDefaultExpandAll
allowClear
/>
);
} else if (param.selectMode==='multiSelect') {
itemComponent = (
<ClassifySelect
data={param}
/>
);
}
return (
<Form.Item
key={index}
label={param.cnName||''}
name={param.name||''}
rules={[{ required: param.required, message: `请选择${param.cnName}!` }]}
>
{ itemComponent }
</Form.Item>
);
})
}
</Form>
}
</Modal>
)
}
export default AddTagModal;
\ No newline at end of file
.add-tag-modal {
.yy-select-item-group {
min-height: 0 !important;
height: 0 !important;
}
}
\ No newline at end of file
import React, { useEffect, useState } from 'react';
import { Button, Tag, Modal } from 'antd';
import { PlusOutlined } from '@ant-design/icons';
import AddTagModal from './Component/AddTagModal';
import { dispatch } from '../../../model';
const TagColumn = (props) => {
const { id, type = 'dataAsset', creator = '', styleType = 'simple', onAssetTag } = props;
const [ ownTags, setOwnTags ] = useState([]);
const [ addTagModalVisible, setAddTagModalVisible ] = useState(false);
const [modal, contextHolder] = Modal.useModal();
useEffect(() => {
if ((id||'') !== '') {
getTags();
} else {
setOwnTags([]);
}
window?.addEventListener("storage", tagEventChange);
return () => {
window?.removeEventListener("storage", tagEventChange);
}
//eslint-disable-next-line react-hooks/exhaustive-deps
}, [id])
const tagEventChange = (e) => {
if (e.key === 'tagChange' && (id||'') !== '') {
getTags();
}
}
const getTags = () => {
dispatch({
type: 'tag.getResourceTagIn',
payload: {
params: {
resourceIds: id,
type,
creator,
includeAll: true
}
},
callback: data => {
if (data) {
setOwnTags(data[id]||[]);
}
}
})
}
const onAddBtnClick = () => {
setAddTagModalVisible(true);
}
const onAddTagModalCancel = (refresh=false) => {
setAddTagModalVisible(false);
refresh && getTags();
}
const onAssetTagCancel = (selectTag) => {
setAddTagModalVisible(false);
onAssetTag && onAssetTag(selectTag);
}
const handleClose = (index) => {
const _tag = ownTags[index];
modal.confirm({
title: '提示!',
content: '您确定要删除该标签吗?',
onOk: () => {
dispatch({
type: 'tag.deleteTagResource',
payload: {
esIds: _tag.esId
},
callback: () => {
getTags();
}
})
}
});
}
// let tagsContent = '';
// (ownTags||[]).forEach((item, index) => {
// if (index === 0) {
// tagsContent = (item.name||'');
// } else {
// tagsContent += ',' + (item.name||'');
// }
// })
return (
<React.Fragment>
{
(styleType==='simple') && <div>
{
(ownTags||[]).map((item, index) => {
return (
<Tag
key={index}
style={{ marginBottom: 5 }}
color={item.type==='public'?'#2db7f5':'volcano'}
>
{ item.name||'' }
</Tag>
);
})
}
</div>
}
{
(styleType==='complex') && <div>
<div>
{
(ownTags||[]).map((item, index) => {
return (
<Tag
key={index}
closable
onClose={e => {
e.preventDefault();
handleClose(index);
}}
style={{ marginBottom: 5 }}
color={item.type==='public'?'#2db7f5':'volcano'}
>
{ item.name||'' }
</Tag>
);
})
}
</div>
<div>
<Button
size='small'
type='text'
icon={<PlusOutlined />}
onClick={onAddBtnClick}
>
新增标签
</Button>
</div>
</div>
}
<AddTagModal
visible={addTagModalVisible}
id={id}
type={type}
creator={creator}
onCancel={onAddTagModalCancel}
onAssetTag={onAssetTagCancel}
/>
{contextHolder}
</React.Fragment>
);
}
export default TagColumn;
\ No newline at end of file
...@@ -2,10 +2,10 @@ import React from 'react' ...@@ -2,10 +2,10 @@ import React from 'react'
import { loadMicroApp } from 'qiankun' import { loadMicroApp } from 'qiankun'
import { AppContext } from '../../App' import { AppContext } from '../../App'
export const DataQualityFeignTagCell = 'data-quality-feign-tag-cell' export const DataQualityFeighTagSelect = 'data-quality-feign-tag-select'
export const DataQualityFeignTagList = 'data-quality-feign-tag-list' export const DataQualityFeignTagList = 'data-quality-feign-tag-list'
const FC = ({ type, data }) => { const FC = ({ type, data, onChange }) => {
const containerRef = React.useRef() const containerRef = React.useRef()
const microApp = React.useRef() const microApp = React.useRef()
const app = React.useContext(AppContext) const app = React.useContext(AppContext)
...@@ -20,7 +20,8 @@ const FC = ({ type, data }) => { ...@@ -20,7 +20,8 @@ const FC = ({ type, data }) => {
props: { props: {
type, type,
data, data,
user: app?.user user: app?.user,
onChange
}, },
}, },
); );
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment