Commit 9618f576 by zhaochengxiang

合并资产一期优化代码

parents 4bc2512e e4002b20
...@@ -24,6 +24,7 @@ ...@@ -24,6 +24,7 @@
"crypto-js": "^4.0.0", "crypto-js": "^4.0.0",
"echarts": "^5.3.1", "echarts": "^5.3.1",
"eslint-config-react-app": "^7.0.1", "eslint-config-react-app": "^7.0.1",
"immer": "9.0.15",
"immutability-helper": "^3.1.1", "immutability-helper": "^3.1.1",
"insert-css": "^2.0.0", "insert-css": "^2.0.0",
"less": "^4.1.1", "less": "^4.1.1",
......
...@@ -8,7 +8,7 @@ import { $hostParams, ContextPath } from './util'; ...@@ -8,7 +8,7 @@ import { $hostParams, ContextPath } from './util';
import loadable from "./util/loadable"; import loadable from "./util/loadable";
import { AssetMountReference } from './util/constant'; import { AssetMountReference } from './util/constant';
import { Basic as AssetResourceTree } from './view/Manage/AssetResourceManage/change-catalog';
export const AppContext = React.createContext(); export const AppContext = React.createContext();
const Signin = loadable(()=> import('./view/Signin')); const Signin = loadable(()=> import('./view/Signin'));
...@@ -16,20 +16,23 @@ const Home = loadable(()=> import('./view/Home')); ...@@ -16,20 +16,23 @@ const Home = loadable(()=> import('./view/Home'));
const Manage = loadable(()=> import('./view/Manage')); const Manage = loadable(()=> import('./view/Manage'));
const Model = loadable(()=> import('./view/Manage/Model')); const Model = loadable(()=> import('./view/Manage/Model'));
const ModelConfig = loadable(()=> import('./view/Manage/ModelConfig')); const ModelConfig = loadable(()=> import('./view/Manage/ModelConfig'));
const AssetResourceManage = loadable(()=> import('./view/Manage/AssetResourceManage'));
const AssetManage = loadable(()=> import('./view/Manage/AssetManage')); const AssetManage = loadable(()=> import('./view/Manage/AssetManage'));
const AssetResourceBrowse = loadable(()=> import('./view/Manage/AssetResourceBrowse')); const AssetResourceBrowse = loadable(()=> import('./view/Manage/AssetResourceBrowse'));
const AssetBrowse = loadable(()=> import('./view/Manage/AssetBrowse')); const AssetBrowse = loadable(()=> import('./view/Manage/AssetBrowse'));
const TaskManage = loadable(()=> import('./view/Manage/AssetTask'));
const AssetRecycle = loadable(()=> import('./view/Manage/AssetRecycle')); const AssetRecycle = loadable(()=> import('./view/Manage/AssetRecycle'));
const AssetOperation = loadable(()=> import('./view/Manage/AssetOperation'));
const DatasourceManage = loadable(()=> import('./view/Manage/DatasourceManage')); const DatasourceManage = loadable(()=> import('./view/Manage/DatasourceManage'));
const AssetDetailPage = loadable(()=> import('./view/Manage/AssetManage/Component/AssetDetailPage')); const AssetDetailPage = loadable(()=> import('./view/Manage/AssetManage/Component/AssetDetailPage'));
const AssetDetail = loadable(()=> import('./view/Manage/AssetManage/Component/AssetDetail')); const AssetDetail = loadable(()=> import('./view/Manage/AssetManage/Component/AssetDetail'));
const ImportAction = loadable(()=> import('./view/Manage/Model/Component/ImportAction')); const ImportAction = loadable(()=> import('./view/Manage/Model/Component/ImportAction'));
const EditModel = loadable(()=> import('./view/Manage/Model/Component/EditModel')); const EditModel = loadable(()=> import('./view/Manage/Model/Component/EditModel'));
const EditTemplate = loadable(()=> import('./view/Manage/ModelConfig/Component/EditTemplate')); const EditTemplate = loadable(()=> import('./view/Manage/ModelConfig/Component/EditTemplate'));
const AssetTree = loadable(()=> import('./view/Manage/AssetManage/Component/AssetManageTree'));
const DataMasterDefine = loadable(()=> import('./view/Manage/DataMaster/Define')); const DataMasterDefine = loadable(()=> import('./view/Manage/DataMaster/Define'));
const DataMasterManage = loadable(()=> import('./view/Manage/DataMaster/Manage')); const DataMasterManage = loadable(()=> import('./view/Manage/DataMaster/Manage'));
const MetadataHarvester = loadable(() => import('./view/Manage/MetadataHarvester')); const MetadataHarvester = loadable(() => import('./view/Manage/MetadataHarvester'));
const EditAssets = loadable(()=> import('./view/Manage/AssetResourceManage/edit-assets'));
export class App extends React.Component { export class App extends React.Component {
constructor() { constructor() {
...@@ -100,10 +103,9 @@ export class App extends React.Component { ...@@ -100,10 +103,9 @@ export class App extends React.Component {
); );
} }
if (message === 'showAssetTree') { if (message === 'showAssetResourceTree') {
return ( return (
<AssetTree <AssetResourceTree
reference={AssetMountReference}
checkable={true} checkable={true}
onCheck={(values) => { onCheck={(values) => {
hostParams.callback&&hostParams.callback(values); hostParams.callback&&hostParams.callback(values);
...@@ -140,8 +142,8 @@ export class App extends React.Component { ...@@ -140,8 +142,8 @@ export class App extends React.Component {
<Route path={`${ContextPath}/manage`} component={Manage} /> <Route path={`${ContextPath}/manage`} component={Manage} />
<Route path={`${ContextPath}/data-model-action`} component={EditModel} exact /> <Route path={`${ContextPath}/data-model-action`} component={EditModel} exact />
<Route path={`${ContextPath}/model-template-action`} component={EditTemplate} exact /> <Route path={`${ContextPath}/model-template-action`} component={EditTemplate} exact />
<Route path={`${ContextPath}/asset-detail`} component={AssetDetailPage} exact /> <Route path={`${ContextPath}/asset-detail`} component={AssetDetailPage} exact />
<Route path={`${ContextPath}/edit-assets`} component={EditAssets} exact />
<Route path={'/center-home/view/datasource-manage'} component={DatasourceManage} exact /> <Route path={'/center-home/view/datasource-manage'} component={DatasourceManage} exact />
<Route path={'/center-home/view/data-model'} component={Model} exact /> <Route path={'/center-home/view/data-model'} component={Model} exact />
...@@ -154,10 +156,13 @@ export class App extends React.Component { ...@@ -154,10 +156,13 @@ export class App extends React.Component {
<Route path={'/center-home/menu/datasource-manage'} component={DatasourceManage} exact /> <Route path={'/center-home/menu/datasource-manage'} component={DatasourceManage} exact />
<Route path={'/center-home/menu/data-model'} component={Model} exact /> <Route path={'/center-home/menu/data-model'} component={Model} exact />
<Route path={'/center-home/menu/model-config'} component={ModelConfig} exact /> <Route path={'/center-home/menu/model-config'} component={ModelConfig} exact />
<Route path={'/center-home/menu/asset-resource-manage'} component={AssetResourceManage} exact />
<Route path={'/center-home/menu/asset-manage'} component={AssetManage} exact /> <Route path={'/center-home/menu/asset-manage'} component={AssetManage} exact />
<Route path={'/center-home/menu/asset-resource-browse'} component={AssetResourceBrowse} exact /> <Route path={'/center-home/menu/asset-resource-browse'} component={AssetResourceBrowse} exact />
<Route path={'/center-home/menu/asset-browse'} component={AssetBrowse} exact /> <Route path={'/center-home/menu/asset-browse'} component={AssetBrowse} exact />
<Route path={'/center-home/menu/asset-recycle'} component={AssetRecycle} exact /> <Route path={'/center-home/menu/task-manage'} component={TaskManage} exact />
<Route path={'/center-home/menu/asset-recycle'} component={AssetRecycle} exact />
<Route path={'/center-home/menu/asset-operation'} component={AssetOperation} exact />
<Route path={'/center-home/menu/msd-define'} component={DataMasterDefine} exact /> <Route path={'/center-home/menu/msd-define'} component={DataMasterDefine} exact />
<Route path={'/center-home/menu/msd-manage'} component={DataMasterManage} exact /> <Route path={'/center-home/menu/msd-manage'} component={DataMasterManage} exact />
<Route path={'/center-home/menu/metadata-harvester'} component={MetadataHarvester} exact /> <Route path={'/center-home/menu/metadata-harvester'} component={MetadataHarvester} exact />
......
...@@ -386,4 +386,8 @@ svg { ...@@ -386,4 +386,8 @@ svg {
.yy-tree .yy-tree-treenode { .yy-tree .yy-tree-treenode {
white-space: nowrap; white-space: nowrap;
}
tbody > tr > td.table-tag-cell {
padding: 12px 8px !important;
} }
\ No newline at end of file
import * as service from '../service/dataassetmanager'; import * as service from '../service/dataassetmanager';
import * as metadataService from '../service/metadata';
import { call } from 'redux-saga/effects'; import { call } from 'redux-saga/effects';
export function* importElement(payload) { export function* importElement(payload) {
...@@ -17,16 +18,16 @@ export function* listUserElements(payload) { ...@@ -17,16 +18,16 @@ export function* listUserElements(payload) {
return yield call(service.listUserElements, payload); return yield call(service.listUserElements, payload);
} }
export function* listCustomElements() { export function* listCustomElements(payload) {
return yield call(service.listCustomElements); return yield call(service.listCustomElements, payload);
} }
export function* listFilterElementIds() { export function* listFilterElementIds(payload) {
return yield call(service.listFilterElementIds); return yield call(service.listFilterElementIds, payload);
} }
export function* listFilterElements() { export function* listFilterElements(payload) {
return yield call(service.listFilterElements); return yield call(service.listFilterElements, payload);
} }
export function* listFilterElementsGroupByType(payload) { export function* listFilterElementsGroupByType(payload) {
...@@ -37,8 +38,8 @@ export function* setupFilterElementIds(payload) { ...@@ -37,8 +38,8 @@ export function* setupFilterElementIds(payload) {
return yield call(service.setupFilterElementIds, payload); return yield call(service.setupFilterElementIds, payload);
} }
export function* listFilterElementIdsConfig() { export function* listFilterElementIdsConfig(payload) {
return yield call(service.listFilterElementIdsConfig); return yield call(service.listFilterElementIdsConfig, payload);
} }
export function* setupFilterElementIdsConfig(payload) { export function* setupFilterElementIdsConfig(payload) {
...@@ -65,10 +66,22 @@ export function* getDataAssetDetail(payload) { ...@@ -65,10 +66,22 @@ export function* getDataAssetDetail(payload) {
return yield call(service.getDataAssetDetail, payload); return yield call(service.getDataAssetDetail, payload);
} }
export function* getResourceRelateDataAssetDetail(payload) {
return yield call(service.getResourceRelateDataAssetDetail, payload);
}
export function* listDataAssetsByPage(payload) { export function* listDataAssetsByPage(payload) {
return yield call(service.listDataAssetsByPage, payload); return yield call(service.listDataAssetsByPage, payload);
} }
export function* getAssetPublishStatus() {
return yield call(service.getAssetPublishStatus)
}
export function* listDataResourcesByPage(payload) {
return yield call(service.listDataResourcesByPage, payload);
}
export function* listDataAssetsByPersonalCustomType(payload) { export function* listDataAssetsByPersonalCustomType(payload) {
return yield call(service.listDataAssetsByPersonalCustomType, payload); return yield call(service.listDataAssetsByPersonalCustomType, payload);
} }
...@@ -91,6 +104,12 @@ export function* existDataAsset(payload) { ...@@ -91,6 +104,12 @@ export function* existDataAsset(payload) {
export function* queryAllDirectoryAsTree() { export function* queryAllDirectoryAsTree() {
return yield call(service.queryAllDirectoryAsTree); return yield call(service.queryAllDirectoryAsTree);
} }
export function* queryResourceManageTree() {
return yield call(service.queryResourceManageTree);
}
export function* queryDataAssetManageTree() {
return yield call(service.queryDataAssetManageTree);
}
export function* queryResourceDirectoryAsTree() { export function* queryResourceDirectoryAsTree() {
return yield call(service.queryResourceDirectoryAsTree); return yield call(service.queryResourceDirectoryAsTree);
} }
...@@ -157,6 +176,10 @@ export function* loadDataAssets(payload) { ...@@ -157,6 +176,10 @@ export function* loadDataAssets(payload) {
return yield call(service.loadDataAssets, payload); return yield call(service.loadDataAssets, payload);
} }
export function* dataAssetAddResources(payload) {
return yield call(service.dataAssetAddResources, payload);
}
export function* unloadDataAssets(payload) { export function* unloadDataAssets(payload) {
return yield call(service.unloadDataAssets, payload); return yield call(service.unloadDataAssets, payload);
} }
...@@ -189,8 +212,8 @@ export function* getAttributesByMetadataModel(payload) { ...@@ -189,8 +212,8 @@ export function* getAttributesByMetadataModel(payload) {
return yield call(service.getAttributesByMetadataModel, payload); return yield call(service.getAttributesByMetadataModel, payload);
} }
export function* loadElementWithoutCustom() { export function* loadElementWithoutCustom(payload) {
return yield call(service.loadElementWithoutCustom); return yield call(service.loadElementWithoutCustom, payload);
} }
export function* saveEleAndAttrRel(payload) { export function* saveEleAndAttrRel(payload) {
...@@ -213,10 +236,18 @@ export function* getAssetPaths(payload) { ...@@ -213,10 +236,18 @@ export function* getAssetPaths(payload) {
return yield call(service.getAssetPaths, payload); return yield call(service.getAssetPaths, payload);
} }
export function* getMetadataItems(payload) {
return yield call(service.getMetadataItems, payload);
}
export function* getResourceRelations(payload) { export function* getResourceRelations(payload) {
return yield call(service.getResourceRelations, payload); return yield call(service.getResourceRelations, payload);
} }
export function* getResourceRelateDataAssetsWhenChecking(payload) {
return yield call(service.getResourceRelateDataAssetsWhenChecking, payload);
}
export function* updateResourceState(payload) { export function* updateResourceState(payload) {
return yield call(service.updateResourceState, payload); return yield call(service.updateResourceState, payload);
} }
...@@ -231,4 +262,152 @@ export function* getPrivilegeByRangeAndDirId(payload) { ...@@ -231,4 +262,152 @@ export function* getPrivilegeByRangeAndDirId(payload) {
export function* getPreviewRangeByDirId(payload) { export function* getPreviewRangeByDirId(payload) {
return yield call(service.getPreviewRangeByDirId, payload); return yield call(service.getPreviewRangeByDirId, payload);
}
export function* resourceAddOrUpdateDirectory(payload) {
return yield call(service.resourceAddOrUpdateDirectory, payload)
}
export function* getSystems() {
return yield call(metadataService.getSystems)
}
export function* getDatasources(payload) {
return yield call(metadataService.getDatasources, payload)
}
export function* getDatabases(payload) {
return yield call(metadataService.getDatabases, payload)
}
export function* getSchemas(payload) {
return yield call(metadataService.getSchemas, payload)
}
export function* resourceTestSyncStrategy(payload) {
return yield call(service.resourceTestSyncStrategy, payload)
}
export function* getDirectoryWithSyncStrategy(payload) {
return yield call(service.getDirectoryWithSyncStrategy, payload)
}
export function* resourceSyncSchema(payload) {
return yield call(service.resourceSyncSchema, payload)
}
export function* getResourceSortingStatus() {
return yield call(service.getResourceSortingStatus)
}
export function* getResourceRelatedMetadataStatus() {
return yield call(service.getResourceRelatedMetadataStatus)
}
export function* getElementValues(payload) {
return yield call(service.getElementValues, payload)
}
export function* getResourceBatchEditInfo(payload) {
return yield call(service.getResourceBatchEditInfo, payload)
}
export function* resourceBatchEdit(payload) {
return yield call(service.resourceBatchEdit, payload)
}
export function* getResourceDraft(payload) {
return yield call(service.getResourceDraft, payload)
}
export function* syncResourceDraft(payload) {
return yield call(service.syncResourceDraft, payload)
}
export function* deleteResources(payload) {
return yield call(service.deleteResources, payload)
}
export function* checkResources(payload) {
return yield call(service.checkResources, payload)
}
export function* addOrUpdateResource(payload) {
return yield call(service.addOrUpdateResource, payload)
}
export function* resourceAddAsAsset(payload) {
return yield call(service.resourceAddAsAsset, payload)
}
export function* resourceFillElementValueBeforeAddAsAsset(payload) {
return yield call(service.resourceFillElementValueBeforeAddAsAsset, payload)
}
export function* resourceCombineDataAsset(payload) {
return yield call(service.resourceCombineDataAsset, payload)
}
export function* changeElementValue(payload) {
return yield call(service.changeElementValue, payload)
}
export function* getDistinctValuesByElementId(payload) {
return yield call(service.getDistinctValuesByElementId, payload)
}
export function* getTasks(payload) {
return yield call(service.getTasks, payload)
}
export function* distributeTask(payload) {
return yield call(service.distributeTask, payload)
}
export function* reDistributeTask(payload) {
return yield call(service.reDistributeTask, payload)
}
export function* autoDistributeTask(payload) {
return yield call(service.autoDistributeTask, payload)
}
export function* listAutoDistributeUserDepartments(payload) {
return yield call(service.listAutoDistributeUserDepartments, payload)
}
export function* getNoticeTypes(payload) {
return yield call(service.getNoticeTypes, payload)
}
export function* listDistributeAbleUsersByName(payload) {
return yield call(service.listDistributeAbleUsersByName, payload)
}
export function* getMetadataColumns(payload) {
return yield call(metadataService.getMetadataColumns, payload)
}
export function* operationOverview() {
return yield call(service.operationOverview)
}
export function* getOperationSupportDataAssetStatisticsObject() {
return yield call(service.getOperationSupportDataAssetStatisticsObject)
}
export function* getOperationSupportResourceStatisticsObject() {
return yield call(service.getOperationSupportResourceStatisticsObject)
}
export function* operationCountByStatisticsObject(payload) {
return yield call(service.operationCountByStatisticsObject, payload)
}
export function* getOperationPopularDataAssetRanking() {
return yield call(service.getOperationPopularDataAssetRanking)
}
export function* getOperationLatestDataAssetRanking() {
return yield call(service.getOperationLatestDataAssetRanking)
} }
\ No newline at end of file
...@@ -24,6 +24,10 @@ export const routes = [ ...@@ -24,6 +24,10 @@ export const routes = [
text: '模型配置', text: '模型配置',
}, },
{ {
name: 'asset-resource-manage',
text: '资源管理',
},
{
name: 'asset-manage', name: 'asset-manage',
text: '资产管理', text: '资产管理',
}, },
...@@ -36,10 +40,18 @@ export const routes = [ ...@@ -36,10 +40,18 @@ export const routes = [
text: '资产浏览' text: '资产浏览'
}, },
{ {
name: 'task-manage',
text: '任务管理',
},
{
name: 'asset-recycle', name: 'asset-recycle',
text: '未挂载资产', text: '未挂载资产',
}, },
{ {
name: 'asset-operation',
text: '资产运营',
},
{
name: 'msd-define', name: 'msd-define',
text: '主数据定义' text: '主数据定义'
}, },
......
...@@ -16,16 +16,16 @@ export function listUserElements(payload) { ...@@ -16,16 +16,16 @@ export function listUserElements(payload) {
return PostJSON("/dataassetmanager/elementApi/listSelectedRangeElements", payload); return PostJSON("/dataassetmanager/elementApi/listSelectedRangeElements", payload);
} }
export function listCustomElements() { export function listCustomElements(payload) {
return GetJSON("/dataassetmanager/elementApi/listCustomElements"); return GetJSON("/dataassetmanager/elementApi/listCustomElements", payload);
} }
export function listFilterElementIds() { export function listFilterElementIds(payload) {
return GetJSON("/dataassetmanager/elementApi/listFilterElementIds"); return GetJSON("/dataassetmanager/elementApi/listFilterElementIds", payload);
} }
export function listFilterElements() { export function listFilterElements(payload) {
return GetJSON("/dataassetmanager/elementApi/listFilterElements"); return GetJSON("/dataassetmanager/elementApi/listFilterElements", payload);
} }
export function listFilterElementsGroupByType(payload) { export function listFilterElementsGroupByType(payload) {
...@@ -36,8 +36,8 @@ export function setupFilterElementIds(payload) { ...@@ -36,8 +36,8 @@ export function setupFilterElementIds(payload) {
return PostJSON("/dataassetmanager/elementApi/setupFilterElementIds", payload); return PostJSON("/dataassetmanager/elementApi/setupFilterElementIds", payload);
} }
export function listFilterElementIdsConfig() { export function listFilterElementIdsConfig(payload) {
return PostJSON("/dataassetmanager/configApi/listFilterElementIds"); return PostJSON("/dataassetmanager/configApi/listFilterElementIds", payload);
} }
export function setupFilterElementIdsConfig(payload) { export function setupFilterElementIdsConfig(payload) {
...@@ -45,7 +45,7 @@ export function setupFilterElementIdsConfig(payload) { ...@@ -45,7 +45,7 @@ export function setupFilterElementIdsConfig(payload) {
} }
export function addOrUpdateDataAsset(payload) { export function addOrUpdateDataAsset(payload) {
return PostJSON("/dataassetmanager/dataAssetApi/addOrUpdateDataAsset", payload); return PostJSON("/dataassetmanager/dataAssetApi/addDataAsset", payload);
} }
export function checkCodeIsExist(payload) { export function checkCodeIsExist(payload) {
...@@ -64,8 +64,20 @@ export function getDataAssetDetail(payload) { ...@@ -64,8 +64,20 @@ export function getDataAssetDetail(payload) {
return GetJSON("/dataassetmanager/dataAssetApi/getDataAssetDetail", payload); return GetJSON("/dataassetmanager/dataAssetApi/getDataAssetDetail", payload);
} }
export function getResourceRelateDataAssetDetail(payload) {
return GetJSON("/dataassetmanager/resourceApi/getResourceRelateDataAssetDetail", payload)
}
export function listDataAssetsByPage(payload) { export function listDataAssetsByPage(payload) {
return GetJSON("/dataassetmanager/dataAssetApi/listDataAssetsByPage", payload); return PostJSON("/dataassetmanager/dataAssetApi/listDataAssetsByPage", payload);
}
export function getAssetPublishStatus() {
return GetJSON("/dataassetmanager/dataAssetApi/listPublishStatus");
}
export function listDataResourcesByPage(payload) {
return PostJSON("/dataassetmanager/resourceApi/listResourcesByPage", payload);
} }
export function listDataAssetsByPersonalCustomType(payload) { export function listDataAssetsByPersonalCustomType(payload) {
...@@ -80,6 +92,10 @@ export function loadDataAssets(payload) { ...@@ -80,6 +92,10 @@ export function loadDataAssets(payload) {
return PostJSON("/dataassetmanager/dataAssetApi/loadDataAssets", payload); return PostJSON("/dataassetmanager/dataAssetApi/loadDataAssets", payload);
} }
export function dataAssetAddResources(payload) {
return PostJSON("/dataassetmanager/dataAssetApi/addResources", payload);
}
export function unloadDataAssets(payload) { export function unloadDataAssets(payload) {
return PostJSON("/dataassetmanager/dataAssetApi/unloadDataAssetsByDataAssetIdAndDirId", payload); return PostJSON("/dataassetmanager/dataAssetApi/unloadDataAssetsByDataAssetIdAndDirId", payload);
} }
...@@ -108,6 +124,10 @@ export function getAssetPaths(payload) { ...@@ -108,6 +124,10 @@ export function getAssetPaths(payload) {
return GetJSON("/dataassetmanager/dataAssetApi/getMultiPath", payload); return GetJSON("/dataassetmanager/dataAssetApi/getMultiPath", payload);
} }
export function getMetadataItems(payload) {
return PostJSON("/dataassetmanager/dataAssetApi/getMetadataItems", payload);
}
export function addOrUpdateDirectory(payload) { export function addOrUpdateDirectory(payload) {
return PostJSON("/dataassetmanager/directoryApi/addOrUpdateDirectory", payload); return PostJSON("/dataassetmanager/directoryApi/addOrUpdateDirectory", payload);
} }
...@@ -128,8 +148,16 @@ export function queryAllDirectoryAsTree() { ...@@ -128,8 +148,16 @@ export function queryAllDirectoryAsTree() {
return GetJSON("/dataassetmanager/directoryApi/queryAllDirectoryAsTree"); return GetJSON("/dataassetmanager/directoryApi/queryAllDirectoryAsTree");
} }
export function queryResourceManageTree() {
return GetJSON("/dataassetmanager/resourceApi/queryResourceManageTree")
}
export function queryDataAssetManageTree() {
return GetJSON("/dataassetmanager/dataAssetApi/queryDataAssetManageTree");
}
export function queryResourceDirectoryAsTree() { export function queryResourceDirectoryAsTree() {
return PostJSON("/dataassetmanager/directoryApi/querySourceTypeAsTree"); return GetJSON("/dataassetmanager/directoryApi/querySourceTypeAsTree");
} }
export function queryAssetDirectoryAsTree() { export function queryAssetDirectoryAsTree() {
...@@ -200,8 +228,8 @@ export function getAttributesByMetadataModel(payload) { ...@@ -200,8 +228,8 @@ export function getAttributesByMetadataModel(payload) {
return GetJSON("/dataassetmanager/eleAndAttrApi/getModelAttributes", payload); return GetJSON("/dataassetmanager/eleAndAttrApi/getModelAttributes", payload);
} }
export function loadElementWithoutCustom() { export function loadElementWithoutCustom(payload) {
return GetJSON("/dataassetmanager/eleAndAttrApi/loadElementWithoutCustom"); return GetJSON("/dataassetmanager/eleAndAttrApi/loadElementWithoutCustom", payload);
} }
export function saveEleAndAttrRel(payload) { export function saveEleAndAttrRel(payload) {
...@@ -224,6 +252,10 @@ export function getResourceRelations(payload) { ...@@ -224,6 +252,10 @@ export function getResourceRelations(payload) {
return GetJSON("/dataassetmanager/resourceApi/getResourceRelations", payload); return GetJSON("/dataassetmanager/resourceApi/getResourceRelations", payload);
} }
export function getResourceRelateDataAssetsWhenChecking(payload) {
return GetJSON("/dataassetmanager/resourceApi/getResourceRelateDataAssetsWhenChecking", payload);
}
export function updateResourceState(payload) { export function updateResourceState(payload) {
return PostJSON("/dataassetmanager/resourceApi/updateResourceState", payload); return PostJSON("/dataassetmanager/resourceApi/updateResourceState", payload);
} }
...@@ -238,4 +270,132 @@ export function getPrivilegeByRangeAndDirId(payload) { ...@@ -238,4 +270,132 @@ export function getPrivilegeByRangeAndDirId(payload) {
export function getPreviewRangeByDirId(payload) { export function getPreviewRangeByDirId(payload) {
return Get("/dataassetmanager/dataAssetApi/getPreviewRangeByDirId", payload); return Get("/dataassetmanager/dataAssetApi/getPreviewRangeByDirId", payload);
}
export function resourceAddOrUpdateDirectory(payload) {
return PostJSON("/dataassetmanager/resourceApi/addOrUpdateDirectory", payload);
}
export function resourceTestSyncStrategy(payload) {
return Post("/dataassetmanager/resourceApi/testSyncStrategy", payload)
}
export function getDirectoryWithSyncStrategy(payload) {
return GetJSON("/dataassetmanager/resourceApi/getDirectoryWithSyncStrategy", payload)
}
export function resourceSyncSchema(payload) {
return Post("/dataassetmanager/resourceApi/syncSchema", payload);
}
export function getResourceSortingStatus() {
return GetJSON("/dataassetmanager/resourceApi/listSortingStatus");
}
export function getResourceRelatedMetadataStatus() {
return GetJSON("/dataassetmanager/resourceApi/listRelatedMetadataStatus");
}
export function getElementValues(payload) {
return GetJSON("/dataassetmanager/parameterApi/conf/getPropertiesByDataAssetType", payload);
}
export function getResourceBatchEditInfo(payload) {
return GetJSON("/dataassetmanager/resourceApi/getBatchEditInfo", payload)
}
export function resourceBatchEdit(payload) {
return PostJSON("/dataassetmanager/resourceApi/batchEdit", payload)
}
export function getResourceDraft(payload) {
return GetJSON("/dataassetmanager/resourceApi/getDraft", payload)
}
export function syncResourceDraft(payload) {
return PostJSON("/dataassetmanager/resourceApi/syncDraft", payload)
}
export function deleteResources(payload) {
return PostJSON("/dataassetmanager/resourceApi/deleteResources", payload)
}
export function checkResources(payload) {
return PostJSON("/dataassetmanager/resourceApi/check", payload)
}
export function addOrUpdateResource(payload) {
return PostJSON("/dataassetmanager/resourceApi/addResource", payload);
}
export function resourceAddAsAsset(payload) {
return PostJSON("/dataassetmanager/resourceApi/addAsAsset", payload);
}
export function resourceFillElementValueBeforeAddAsAsset(payload) {
return GetJSON("/dataassetmanager/resourceApi/fillElementValueBeforeAddAsAsset", payload);
}
export function resourceCombineDataAsset(payload) {
return PostJSON("/dataassetmanager/resourceApi/combineExistDataAsset", payload);
}
export function changeElementValue(payload) {
return PostJSON("/dataassetmanager/resourceApi/batchChangeElementValue", payload);
}
export function getDistinctValuesByElementId(payload) {
return GetJSON("/dataassetmanager/resourceApi/getDistinctValuesByElementId", payload);
}
export function getTasks(payload) {
return GetJSON("/dataassetmanager/resource/taskApi/listTasksByPage", payload)
}
export function distributeTask(payload) {
return PostJSON("/dataassetmanager/resource/taskApi/distributeTask", payload)
}
export function reDistributeTask(payload) {
return PostJSON("/dataassetmanager/resource/taskApi/reDistributeTask", payload)
}
export function autoDistributeTask(payload) {
return PostJSON("/dataassetmanager/resource/taskApi/autoDistributeTask", payload)
}
export function listAutoDistributeUserDepartments(payload) {
return PostJSON("/dataassetmanager/resource/taskApi/listAutoDistributeUserDepartments", payload)
}
export function getNoticeTypes() {
return GetJSON("/dataassetmanager/resource/taskApi/listNoticeTypes")
}
export function listDistributeAbleUsersByName(payload) {
return GetJSON("/dataassetmanager/userApi/listDistributeAbleUsersByName", payload)
}
export function operationOverview() {
return GetJSON("/dataassetmanager/operationApi/statisticalOverview")
}
export function getOperationSupportDataAssetStatisticsObject() {
return GetJSON("/dataassetmanager/operationApi/listSupportDataAssetStatisticsObject")
}
export function getOperationSupportResourceStatisticsObject() {
return GetJSON("/dataassetmanager/operationApi/listSupportResourceStatisticsObject")
}
export function operationCountByStatisticsObject(payload) {
return PostJSON("/dataassetmanager/operationApi/countByStatisticsObject", payload)
}
export function getOperationPopularDataAssetRanking() {
return GetJSON("/dataassetmanager/operationApi/getPopularDataAssetRanking")
}
export function getOperationLatestDataAssetRanking() {
return GetJSON("/dataassetmanager/operationApi/getLatestDataAssetRanking")
} }
\ No newline at end of file
import { GetJSON } from "../util/axios" import { GetJSON, PostJSON } from "../util/axios"
export function queryDatabase(payload) { export function queryDatabase(payload) {
return GetJSON("/metadatarepo/rest/tag/getDatabaseBySystemCode", payload); return GetJSON("/metadatarepo/rest/tag/getDatabaseBySystemCode", payload);
...@@ -18,4 +18,24 @@ export function queryAllFields(payload) { ...@@ -18,4 +18,24 @@ export function queryAllFields(payload) {
export function getSystemAllGraph(payload) { export function getSystemAllGraph(payload) {
return GetJSON("/metadatarelation/getSystemAllGraph", payload); return GetJSON("/metadatarelation/getSystemAllGraph", payload);
}
export function getSystems() {
return PostJSON('/metadatarepo/rest/system/listAllSystem')
}
export function getDatasources(payload) {
return GetJSON('/metadatarepo/rest/user/getUserSystem', payload)
}
export function getDatabases(payload) {
return PostJSON('/metadatarepo/rest/metadata/getChildBySysAndPath', payload)
}
export function getSchemas(payload) {
return GetJSON('/metadatarepo/rest/query/getChildBySys', payload)
}
export function getMetadataColumns(payload) {
return PostJSON('/metadatarepo/rest/metadata/getChild', payload)
} }
\ No newline at end of file
// @import (reference) "@/less" 只导入变量
@ant-prefix: foobar;
.ssetable {
.react-resizable{
position: relative;
background-clip: padding-box;
user-select: none;
}
.react-resizable-handle {
position: absolute;
width: 10px;
height: 100%;
bottom: 0;
right: -5px;
cursor: col-resize;
// background: red;
z-index: 999;
}
.@{ant-prefix}-table-thead > tr > th {
padding: 8px 8px !important;
white-space: nowrap;
text-overflow: ellipsis;
}
// .@{ant-prefix}-table-tbody > tr > td {
// padding: 12px 8px !important;
// }
.@{ant-prefix}-table-tbody > .@{ant-prefix}-table-measure-row > td {
padding: 0px !important;
}
.@{ant-prefix}-table-tbody > tr .@{ant-prefix}-table-row-selected > td {
background: #fff !important;
}
tr.@{ant-prefix}-table-expanded-row > td {
padding: 0 !important;
background: #fff !important;
}
tr.@{ant-prefix}-table-expanded-row {
.@{ant-prefix}-table {
margin: 0 !important;
}
}
.@{ant-prefix}-table-thead > tr > th {
background-color: #F2F5FC !important;
}
}
import React, { useEffect, useMemo, useRef, useState } from 'react'
import { PaginationProps, Table, TableColumnType, TableProps } from 'antd'
import type { SortOrder, ColumnGroupType, ColumnType } from 'antd/lib/table/interface'
import { Resizable } from 'react-resizable'
import ResizeObserver from 'rc-resize-observer'
import produce from 'immer'
import { nanoid } from 'nanoid'
import { getScrollbarWidth } from '..'
import { defaultPageSizeOptions } from '../hooks/page'
import ToolTip from './Tooltip'
import { Menu, useContextMenu } from 'react-contexify'
import { generateUUID } from ".."
import PermissionRcItem from './PermissionRcItem'
import 'react-contexify/dist/ReactContexify.css'
import './Table.less'
interface Props<RowType> {
width: number
maxHeight?: string | number
pageSize: number
pageNum: number
total: number
pageSizeOptions: string[] | number[]
onPaginate: PaginationProps["onChange"]
rowSelection?: TableProps<RowType>
bodyCell: any
extraColWidth?: number
menuData: string[]
menuPermissions: string[]
onMenuItemClick: Function
onRowClick: Function
shouldRowContextMenu: Function
}
const scrollbarWidth = getScrollbarWidth()
function FC<RowType extends object = any>({ width, maxHeight, pageSize, pageNum, total, pageSizeOptions, onPaginate, columns, bodyCell, extraColWidth = 0, menuData, menuPermissions, onMenuItemClick, onRowClick, shouldRowContextMenu, ...rest }: TableProps<RowType> & Props<RowType>) {
type Columns = typeof columns
const MENU_ID = generateUUID()
const [tableWidth, setTableWidth] = useState(0)
const { show } = useContextMenu({
id: MENU_ID,
})
const paddingCol = useRef<TableColumnType<any>>({
key: 'padding',
width: 0,
render: () => undefined
})
const handleResize = (index: number) => (e: any, { size }: any) => {
setCols((prevCols) => {
const nextColumns = [...(prevCols ?? [])];
nextColumns[index] = {
...nextColumns[index],
width: size.width,
};
return nextColumns;
});
};
const [cols, setCols] = useState<Columns>()
useEffect(() => {
if (!!columns && tableWidth > 0) {
const contentWidth = getWidth(width ?? tableWidth,extraColWidth)
setDefaultWidth(columns, contentWidth)
paddingCol.current.width = 0
const cols = columns
.map((col, index) => {
const render = getRender(col);
const colWidth = col.width ?? 100;
return {
...col, /* colRef: createRef(), */ width: colWidth, render, ellipsis: true,
onHeaderCell: (column: any) => ({
width: column.width,
// colRef: column.colRef,
onResize: handleResize(index),
}),
};
})
setCols(cols)
}
}, [columns, tableWidth, width, extraColWidth])
// add padding column
const cols1 = useMemo(() => !!cols ? [...cols, paddingCol.current] : undefined, [cols, pageSize, pageNum])
const scroll = useMemo(() => (maxHeight === undefined || total === 0 /* fix 暂无数据显示滚动条? */ ? { x: '100%' } : { y: maxHeight, x: '100%' /* 'max-content' 所有列显示全宽*/ }), [maxHeight, total])
const ref = useRef<HTMLDivElement>(null)
const [rightClickNode, setRightClickNode] = React.useState(undefined)
const handleContextMenu = (event, node) => {
show(event, {
position: {
x: event.clientX + 30,
y: event.clientY - 10
}
})
}
return (
<ResizeObserver
onResize={(size) => {
setTableWidth(size?.width)
}}
>
<Table
ref={ref}
className="ssetable"
size='middle'
rowKey="id" // could be overrided in rest.
showSorterTooltip={false}
columns={cols1}
pagination={{
pageSizeOptions: pageSizeOptions ?? defaultPageSizeOptions, showSizeChanger: true,
position: ['bottomCenter'], pageSize, current: pageNum, total, onChange: onPaginate,
showTotal: total => `共 ${total} 条`,
}}
components={{
body: {
cell: bodyCell ?? null
},
header: {
cell: ResizableTitle,
},
}}
onRow={(record, index) => {
return {
id: record?.id,
onClick: event => {
onRowClick?.(event, record)
},
onContextMenu: event => {
setRightClickNode(record)
if (shouldRowContextMenu?.(record)) {
handleContextMenu(event, record)
}
},
}
}}
scroll={scroll}
{...rest}
/>
<Menu id={MENU_ID}>
{
menuData?.map(item => <PermissionRcItem
key={item}
id={item}
disabled={rightClickNode?.[`${item}`]?.disabled}
tip={rightClickNode?.[`${item}`]?.tip}
permissions={rightClickNode?.permissions||menuPermissions}
permissionKey={item}
onClick={() => onMenuItemClick?.(item, rightClickNode)}>
{item}
</PermissionRcItem>)
}
</Menu>
</ResizeObserver>
)
}
// 可变列宽
const ResizableTitle = (props: any) => {
const { onResize, width, ...restProps } = props;
if (!width) {
return <th {...restProps} />;
}
return (
<Resizable
width={width}
height={0}
handle={
<span
className="react-resizable-handle"
onClick={e => {
e.stopPropagation();
}}
/>
}
onResize={onResize}
draggableOpts={{ enableUserSelectHack: false }}
>
<th {...restProps} />
</Resizable>
);
};
function setDefaultWidth(columns: any[], width: number) {
let rowWidth = 0, count = 0
for (const col of columns) {
if (typeof col.width === 'number') {
rowWidth += col.width
} else {
count++
}
}
if (count > 0) {
const defaultW = (rowWidth > width ? 0 : width - rowWidth) / count
for (const col of columns) {
if (typeof col.width !== 'number') {
col.width = (defaultW < 100) ? 100 : defaultW
}
}
}
}
function getWidth(tableWidth: number, extraColWidth: number): number {
return tableWidth - scrollbarWidth - extraColWidth // scrollbar width, checkbox column
}
function getRender<T>(col: ColumnGroupType<T> | ColumnType<T>) {
return (value: any, record: T, index: number) => {
// 用户自定render
if (col.render) {
const rs = col.render(value, record, index)
if (typeof rs === 'string') {
return (<ToolTip msg={rs} defaultStyle />)
}
return rs
} else {
return (<ToolTip msg={value} defaultStyle />)
}
}
}
export default FC
export function getStringSorter<T>(k: keyof T) {
return (a: T, b: T, sortOrder?: SortOrder) => {
const aVal = a[k], bVal = b[k]
if (typeof aVal === 'string' && typeof bVal === 'string')
return aVal.localeCompare(bVal);
return 0
}
}
export function getNumberSorter<T>(k: keyof T) {
return (a: T, b: T, sortOrder?: SortOrder) => {
const aVal = a[k], bVal = b[k]
if (typeof aVal === 'number' && typeof bVal === 'number')
return aVal - bVal;
return 0
}
}
export function generateId<T = any>(data: T[], id = 'id') {
return produce(data, (draft) => {
draft?.forEach((row: any) => {
row[id] = nanoid()
})
})
}
export function generateIdFromArrayRow<T extends unknown[]>(data?: T[], id = 'id'): unknown[] | undefined {
return data?.map((row, i) => {
const obj: { [key: string]: unknown } = { [id]: nanoid() }
row.forEach((cell, j) => {
obj[j] = cell
})
return obj
})
}
export function filterRows<RowType = unknown>(rows: RowType[] | undefined, filter: (row: RowType) => boolean) {
if (rows) {
const matched = rows.filter(filter)
return matched
}
return rows
}
\ No newline at end of file
import React, { CSSProperties, HTMLAttributes, useEffect, useRef, useState } from "react";
import { Tooltip as Tooltip_, TooltipProps } from "antd";
import type { TooltipPlacement } from "antd/es/tooltip";
const DefaultStyle: CSSProperties = {
overflow: "hidden",
whiteSpace: "nowrap",
textOverflow: "ellipsis",
// backgroundColor: "red"
}
export default function ({ msg, defaultStyle, style, placement, children, ...rest }: { msg: string | JSX.Element, defaultStyle?: boolean, placement?: TooltipPlacement } & HTMLAttributes<any>) {
const wrapper = useRef<HTMLDivElement>(null);
const text = useRef<HTMLSpanElement>(null);
const [toolTip, setToolTip] = useState(false);
useEffect(() => {
const wrapperRect = wrapper.current!.getBoundingClientRect(),
textRect = text.current!.getBoundingClientRect();
if (wrapperRect.width < textRect.width) {
setToolTip(true);
}
}, [msg]);
const _text = (
<div
ref={wrapper}
style={defaultStyle ? { ...DefaultStyle, ...style } : style}
{...rest}
>
<span ref={text}>{children ?? msg}</span>
{/* {msg} */}
</div>
);
if (toolTip) {
return <Tooltip_ title={msg} placement={placement} /* color="#fff" overlayInnerStyle={{ color: '#000' }} */ >{_text}</Tooltip_>;
} else {
return _text;
}
}
export const Tooltip = ({ children, color, ...props }: TooltipProps) => {
return <Tooltip_ /* color="#fff" overlayInnerStyle={{ color: '#000' }} */ {...props} >{children}</Tooltip_>
}
\ No newline at end of file
import React from "react"
import { Tree } from "antd"
import { Menu, Item, useContextMenu } from 'react-contexify'
import { generateUUID } from ".."
import 'react-contexify/dist/ReactContexify.css';
const FC = ({ shouldRowContextMenu, menuData, onMenuItemClick, ...restProps }) => {
const MENU_ID = generateUUID()
const { show } = useContextMenu({
id: MENU_ID,
})
const [rightClickNode, setRightClickNode] = React.useState(undefined)
const handleContextMenu = (event, node) => {
show(event, {
position: {
x: event.clientX + 30,
y: event.clientY - 10
}
})
}
return (
<div>
<Tree
onRightClick={({event, node}) => {
setRightClickNode(node)
if (shouldRowContextMenu?.(node)) {
handleContextMenu(event, node)
}
}}
{...restProps}
/>
<Menu id={MENU_ID}>
{
menuData?.map(item => <Item key={item.id} id={item.id} onClick={() => onMenuItemClick?.(item.id, rightClickNode)}>{item.title}</Item>)
}
</Menu>
</div>
)
}
export default FC
\ No newline at end of file
...@@ -23,6 +23,7 @@ export const DataModelerRoleUser = 'user'; ...@@ -23,6 +23,7 @@ export const DataModelerRoleUser = 'user';
export const DataModelerRoleReader = 'reader'; export const DataModelerRoleReader = 'reader';
//资产 //资产
export const ResourceManageReference = 'resource-manage';
export const AssetManageReference = 'asset-manage'; export const AssetManageReference = 'asset-manage';
export const AssetBrowseReference = 'asset-browse'; export const AssetBrowseReference = 'asset-browse';
export const ResourceBrowseReference = 'resource-browse'; export const ResourceBrowseReference = 'resource-browse';
......
import { useState } from "react"
export interface Page {
pageSize: number
pageNum: number
}
export const defaultPageSize = 20
export const defaultPageSizeOptions = [10, 20, 50, 100]
export type PageSizeOptions = typeof defaultPageSizeOptions[number]
export const defaultPage = {
pageSize: defaultPageSize,
pageNum: 1
}
export function usePage(pageSize?: number): [Page, React.Dispatch<React.SetStateAction<Page>>, (pageNum: number) => void] {
const [page, setPage] = useState<Page>(() => ({
pageSize: pageSize ?? defaultPageSize, pageNum: 1,
}))
const gotoPage = (pageNum: number) => {
setPage(prev => ({ ...prev, pageNum }))
}
return [page, setPage, gotoPage]
}
export type PageType = ReturnType<typeof usePage>[0]
export function paginate<T>(list: T[] | undefined, page: Page) {
if (list) {
const { pageSize, pageNum } = page
const start = (pageNum - 1) * pageSize
const end = start + pageSize
return list.slice(start, end)
}
return list
}
\ No newline at end of file
...@@ -8,7 +8,7 @@ import LocalStorage from 'local-storage'; ...@@ -8,7 +8,7 @@ import LocalStorage from 'local-storage';
import { dispatchLatest, action } from '../model'; import { dispatchLatest, action } from '../model';
import { set_sess_state } from "../model/reducer"; import { set_sess_state } from "../model/reducer";
import { DataModelerRoleAdmin, DataModelerRoleUser, DataModelerRoleReader, AssetManageReference, AssetBrowseReference, ResourceBrowseReference, AssetRecycleReference } from './constant'; import { DataModelerRoleAdmin, DataModelerRoleUser, DataModelerRoleReader, AssetManageReference, AssetBrowseReference, ResourceBrowseReference, AssetRecycleReference, ResourceManageReference } from './constant';
//内网深交所环境 isSzseEnv true //内网深交所环境 isSzseEnv true
//元曜公网环境 isSzseEnv false //元曜公网环境 isSzseEnv false
...@@ -410,8 +410,14 @@ export function getDataModelerRole(user) { ...@@ -410,8 +410,14 @@ export function getDataModelerRole(user) {
return DataModelerRoleAdmin; return DataModelerRoleAdmin;
} }
export function getAssetType(reference) {
return (reference===AssetManageReference || reference===AssetBrowseReference) ? 'dataAsset' : 'resource'
}
export function getAssetRange(menuName) { export function getAssetRange(menuName) {
if (menuName === AssetManageReference) { if (menuName === ResourceManageReference ) {
return 'dataAsset_resourceManage';
} else if (menuName === AssetManageReference) {
return 'dataAsset_dataAssetManage'; return 'dataAsset_dataAssetManage';
} else if (menuName === AssetBrowseReference) { } else if (menuName === AssetBrowseReference) {
return 'dataAsset_dataAssetBrowse'; return 'dataAsset_dataAssetBrowse';
...@@ -467,4 +473,35 @@ export function checkMenuAdmit(menuinfo) { ...@@ -467,4 +473,35 @@ export function checkMenuAdmit(menuinfo) {
} }
return false; return false;
}
export function getScrollbarWidth() {
// Creating invisible container
const outer = document.createElement('div');
outer.style.visibility = 'hidden';
outer.style.overflow = 'scroll'; // forcing scrollbar to appear
outer.style.msOverflowStyle = 'scrollbar'; // needed for WinJS apps
document.body.appendChild(outer);
// Creating inner element and placing it in the container
const inner = document.createElement('div');
outer.appendChild(inner);
// Calculating difference between container's full width and the child width
const scrollbarWidth = (outer.offsetWidth - inner.offsetWidth);
// Removing temporary elements from the DOM
outer.parentNode?.removeChild(outer);
return scrollbarWidth;
}
export function getValidString(strs) {
for (const str of strs) {
if (typeof str === 'string' && str.length > 0) {
return str
}
}
} }
\ No newline at end of file
...@@ -6,7 +6,7 @@ import { ResizableBox } from 'react-resizable'; ...@@ -6,7 +6,7 @@ import { ResizableBox } from 'react-resizable';
import AssetTree from '../AssetManage/Component/AssetTree'; import AssetTree from '../AssetManage/Component/AssetTree';
import AssetDirectory from '../AssetManage/Component/AssetDirectory'; import AssetDirectory from '../AssetManage/Component/AssetDirectory';
import RelationContainer from './Component/RelationContainer'; import RelationContainer from './Component/RelationContainer';
import AssetTable from "../AssetManage/Component/AssetTable"; import AssetTable from "./table";
import Separate from '../AssetManage/Component/Separate'; import Separate from '../AssetManage/Component/Separate';
import { AssetBrowseReference } from '../../../util/constant'; import { AssetBrowseReference } from '../../../util/constant';
...@@ -14,9 +14,6 @@ import { AssetBrowseReference } from '../../../util/constant'; ...@@ -14,9 +14,6 @@ import { AssetBrowseReference } from '../../../util/constant';
import './index.less'; import './index.less';
const AssetBrowse = (props) => { const AssetBrowse = (props) => {
const { reference = AssetBrowseReference } = props;
const [ nodeParams, setNodeParams ] = useState({ centerId: '', expandId: '', nodeType: '' }); const [ nodeParams, setNodeParams ] = useState({ centerId: '', expandId: '', nodeType: '' });
const [ expandTree, setExpandTree ] = useState(true); const [ expandTree, setExpandTree ] = useState(true);
const [ expandRelation, setExpandRelation ] = useState(true); const [ expandRelation, setExpandRelation ] = useState(true);
...@@ -77,25 +74,25 @@ const AssetBrowse = (props) => { ...@@ -77,25 +74,25 @@ const AssetBrowse = (props) => {
axis='x' axis='x'
minConstraints={[230, Infinity]} maxConstraints={[Infinity, Infinity]} minConstraints={[230, Infinity]} maxConstraints={[Infinity, Infinity]}
> >
<AssetTree centerId={centerId} onSelect={onTreeSelect} reference={reference} {...props} /> <AssetTree centerId={centerId} onSelect={onTreeSelect} reference={AssetBrowseReference} {...props} />
</ResizableBox> </ResizableBox>
{ {
expandTree && <Separate width={15} /> expandTree && <Separate width={15} />
} }
<div className={rightClasses}> <div className={rightClasses}>
<AssetDirectory id={nodeId} assetCount={assetCount} reference={reference} nodeType={nodeParams.nodeType} /> <AssetDirectory id={nodeId} assetCount={assetCount} reference={AssetBrowseReference} nodeType={nodeParams.nodeType} />
<Separate height={15} /> <Separate height={15} />
<div className='flex' style={{ flex: 1, height: '100%', overflow: 'hidden' }}> <div className='flex' style={{ flex: 1, height: '100%', overflow: 'hidden' }}>
{ {
expandRelation && <React.Fragment> expandRelation && <React.Fragment>
<div style={{ flex: 1, height: '100%', overflow: 'hidden' }}> <div style={{ flex: 1, height: '100%', overflow: 'hidden' }}>
<RelationContainer reference={reference} nodeParams={nodeParams} onChange={onRelationChange} resize={resizeRelation} /> <RelationContainer reference={AssetBrowseReference} nodeParams={nodeParams} onChange={onRelationChange} resize={resizeRelation} />
</div> </div>
<Separate width={15} /> <Separate width={15} />
</React.Fragment> </React.Fragment>
} }
<div style={{ flex: 1, overflow: 'hidden' }}> <div style={{ flex: 1, overflow: 'hidden' }}>
<AssetTable nodeId={nodeId} nodeType={nodeParams.nodeType} reference={reference} onCountChange={onAssetCountChange} onFullScreenChange={onFullScreenChange} {...props} /> <AssetTable node={{ nodeId, type: nodeParams.nodeType }} onFullScreenChange={onFullScreenChange} {...props} />
</div> </div>
</div> </div>
<div className='tree-toggle' onClick={treeToggleClick}> <div className='tree-toggle' onClick={treeToggleClick}>
......
import React from 'react'
import classNames from 'classnames'
import { Tooltip, Typography, Space, Dropdown, Button, Menu, Checkbox, Input, Select, Modal, Table as AntdTable } from 'antd'
import ResizeObserver from 'rc-resize-observer'
import { debounceTime, Subject } from 'rxjs'
import { DownOutlined, UpOutlined } from "@ant-design/icons"
import LocalStorage from 'local-storage'
import { defaultPage, usePage } from '../../../util/hooks/page'
import Table from '../../../util/Component/Table'
import { dispatch } from '../../../model'
import { getAssetRange, getAssetType, getQueryParam, IsArr, isSzseEnv, showMessage, showNotifaction } from '../../../util'
import { AssetBrowseReference } from '../../../util/constant'
import PermissionButton from '../../../util/Component/PermissionButton'
import PermissionMenuItem from '../../../util/Component/PermissionMenuItem'
import { FullScreenSvg, CancelFullScreenSvg } from '../AssetManage/Component/AssetSvg'
import FilterElement from '../AssetManage/Component/FilterElementModal'
import FilterElementValue from '../AssetResourceManage/filter-element-value'
import { AssetDirectorySubject } from '../AssetManage/Component/AssetDirectory'
import { AssetActionSubject } from '../AssetManage/Component/AssetAction'
import TagCell from '../Model/Component/tag-help'
import { MetadataColumn } from '../AssetResourceManage/table'
import AssetDetailDrawer from '../AssetManage/Component/AssetDetailDrawer'
import '../AssetManage/table.less'
const FC = (props) => {
const { node, onFullScreenChange } = props
const [args, setArgs] = React.useState(() => ({
params: {
page: defaultPage.pageNum,
size: defaultPage.pageSize,
catalogType: 'currentRecursive',
keyword: undefined,
elementValueFilters: []
},
}))
const [fullScreen, setFullScreen] = React.useState(false)
const [loading, setLoading] = React.useState(false)
const [loadingElements, setLoadingElements] = React.useState(false)
const [columns, setColumns] = React.useState()
const [elements, setElements] = React.useState()
const [data, setData] = React.useState()
const [total, setTotal] = React.useState()
const [row, setRow] = React.useState()
const $keyword = React.useMemo(() => new Subject(), [])
const [keyword, setKeyword] = React.useState()
const [searchType, setSearchType] = React.useState('keyword')
const [resoureTagMap, setResourceTagMap] = React.useState()
const [filterElementParams, setFilterElementParams] = React.useState({
visible: false,
type: undefined,
reference: undefined
})
const [filterElementValueParams, setFilterElementValueParams] = React.useState({
visible: false,
type: undefined,
defaultValue: undefined,
})
const [assetDetailParams, setAssetDetailParams] = React.useState({
visible: false,
id: undefined,
dirId: undefined,
reference: undefined,
})
const [page, setPage] = usePage()
const [modal, contextHolder] = Modal.useModal()
const locationIdRef = React.useRef(getQueryParam('id', props?.location?.search))
const locationDidRef = React.useRef(getQueryParam('did', props?.location?.search))
const setArgsAndPage = React.useCallback((params) => {
// 设置查询参数时将分页置为1
setPage(prevpg => {
setArgs((prev) => {
const newparams = params ? { ...prev.params, ...params } : undefined
return { params: { ...newparams, page: 1, size: prevpg.pageSize } }
})
return ({ ...prevpg, pageNum: 1 })
})
//eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
const setPageAndArgs = React.useCallback((page) => {
setPage(prev => ({ ...prev, ...page }))
setArgs((prev) => {
return { params: { ...prev.params, page: page.pageNum, size: page.pageSize } }
})
//eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
const storageChange = (e) => {
if (e.key === 'editAssetsChange') {
getAssets()
} else if (e.key === 'assetRelationOnClickEvent') {
locationIdRef.current = e.relation?.dataAssetId
locationDidRef.current = e.relation?.dirId
} else if (e.key === 'assetPathOnClickEvent') {
locationIdRef.current = e.id
locationDidRef.current = e.dirId
}
}
React.useEffect(() => {
getElements()
const $$keyword = $keyword.pipe(debounceTime(1000)).subscribe((keyword) => {
setArgsAndPage({ keyword })
})
window?.addEventListener("storage", storageChange)
return () => {
$$keyword.unsubscribe()
window?.removeEventListener("storage", storageChange)
}
//eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
React.useEffect(() => {
if (node) {
setRow()
if (locationIdRef.current && locationDidRef.current) {
getDataAssetLocation()
} else {
setPageAndArgs({ pageNum: 1, pageSize: page.pageSize })
}
}
}, [node])
React.useEffect(() => {
if (node?.nodeId) {
getAssets()
}
}, [args])
React.useEffect(() => {
if (data) {
getResourceTag()
}
}, [data])
const notElementCol = [
{
title: '标签',
dataIndex: 'tag',
width: 360,
className: 'table-tag-cell',
render: (_, record) => <div onClick={(e) => {e?.stopPropagation()}}>
<TagCell
id={record.id}
did={record.dirId}
type='dataAsset'
tags={resoureTagMap?.[`${record.id}`]}
/>
</div>
}
]
React.useEffect(() => {
const newColumns = []
let index = 0
for (const element of elements??[]) {
const col = {
title: element.name,
dataIndex: `element${++index}`,
ellipsis: true,
width: 120,
render: (text, record) => {
return (
<Tooltip title={text}>
<Typography.Text ellipsis={true}>
{text}
</Typography.Text>
</Tooltip>
);
}
}
if (element.name === '编号') {
col.width = 60
} else if (element.name === '中文名称') {
col.width = isSzseEnv ? 230 : 160
} else if (element.name === '英文名称') {
col.width = isSzseEnv ? 224 : 160
} else if (element.name === '资产路径') {
col.render = (text, record) => {
return (
<Tooltip title={text}>
<Typography.Text ellipsis={true}>
<a onClick={()=>{
let event = new Event('storage')
event.key = 'assetDirChangeEvent'
event.dirId = record.dirId
window?.dispatchEvent(event)
}}>
{text}
</a>
</Typography.Text>
</Tooltip>
)
}
} else if (element.metadataItem === '是') {
col.width = isSzseEnv ? 250 : 120
col.render = (text, record) => <MetadataColumn data={text} />
}
newColumns.push(col)
}
setColumns([...newColumns, ...notElementCol])
}, [elements, resoureTagMap])
const tableData = React.useMemo(() => {
const newTableData = []
for (const item of (data??[])) {
const newAsset = {...item}
let index = 0
for (const elementValue of (item.elementValues??[])) {
newAsset[`element${++index}`] = elementValue
}
newTableData.push(newAsset)
}
return newTableData
}, [data])
const tableMaxHeight = React.useMemo(() => {
return fullScreen ? 'calc(100vh - 209px - 72px)' : 'calc(100vh - 209px - 123px - 15px - 72px)'
}, [fullScreen])
const getElements = () => {
setLoadingElements(true)
dispatch({
type: 'assetmanage.listFilterElements',
payload: {
range: getAssetRange(AssetBrowseReference),
dataAssetType: getAssetType(AssetBrowseReference)
},
callback: data => {
setLoadingElements(false)
setElements(data)
},
error: () => {
setLoadingElements(false)
}
})
}
const getAssets = () => {
setLoading(true)
let [recursive, dirId] = [true, node?.nodeId]
if (args.params.catalogType === 'current') {
recursive = false
}
if (args.params.catalogType === 'fullSearch') {
dirId = ''
}
dispatch({
type: 'assetmanage.listDataAssetsByPage',
payload: {
data: args.params.elementValueFilters??[],
params: {
dirId,
pageNum: args.params.page,
pageSize: args.params.size,
keyword: args.params.keyword,
range: getAssetRange(AssetBrowseReference),
recursive,
isCustomDir: (node?.resourceType==='custom'||node?.type==='custom')
}
},
callback: data => {
setLoading(false)
setData(data?.data)
setTotal(data?.total)
if (locationIdRef.current) {
const index = (data?.data??[]).findIndex(item => item.id === locationIdRef.current)
if (index !== -1) {
setRow(data?.data[index])
}
setTimeout(() => {
var anchor = document.getElementById(`${locationIdRef.current}`)
anchor?.scrollIntoView()
locationIdRef.current = null
locationDidRef.current = null
}, 500)
}
},
error: () => {
setLoading(false)
locationIdRef.current = null
locationDidRef.current = null
}
})
}
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 getDataAssetLocation = () => {
setLoading(true)
dispatch({
type: 'assetmanage.getDataAssetLocation',
payload: {
dataAssetId: locationIdRef.current,
dirId: locationDidRef.current,
},
callback: data => {
const newPageNum = parseInt(data.offset/page.pageSize + ((data.offset%page.pageSize===0)?0:1))
setPageAndArgs({ ...page, pageNum: newPageNum })
},
error: () => {
setLoading(false)
locationIdRef.current = null
locationDidRef.current = null
setPageAndArgs({ ...page, pageNum: 1 })
}
})
}
const onFilterElementClick = () => {
setFilterElementParams({
visible: true,
type: 'user',
reference: AssetBrowseReference
})
}
const classes = classNames('asset-list', {
'asset-list-fullscreen': fullScreen,
});
return (
<div className={classes}>
<div
className='flex'
style={{
padding: '20px 15px 5px',
alignItems: 'center',
justifyContent: 'space-between',
}}
>
<Space className='mr-3' style={{ flex: 0, paddingBottom: 15 }}>
<Button onClick={onFilterElementClick}>可见列设置</Button>
</Space>
<div className='flex' style={{ flex: 1, overflow: 'auto', paddingBottom: 15 }}>
<div style={{ flex: 1 }}></div>
<Space style={{ flex: 0 }}>
<Select
value={args.params.catalogType}
onChange={(value) => {
setArgsAndPage({ catalogType: value })
}}
style={{ width: 170 }}
>
<Select.Option value='currentRecursive'>
当前目录(含子目录)
</Select.Option>
<Select.Option value='current'>
当前目录
</Select.Option>
<Select.Option value='fullSearch'>
全部数据
</Select.Option>
</Select>
<Input size="middle"
addonBefore={
<Select
value={searchType}
onChange={(value) => {
setSearchType(value)
if (value === 'attribute') {
setFilterElementValueParams({
visible: true,
type: AssetBrowseReference,
defaultValue: args.params.elementValueFilters
})
}
}}
style={{ width: 120 }}
>
<Select.Option value='keyword'>
关键字搜索
</Select.Option>
<Select.Option value='attribute'>
属性值过滤
</Select.Option>
</Select>
}
placeholder="资产要素值/任务"
value={keyword}
bordered={true} allowClear
onChange={(e) => {
const keyword = e.target.value
setKeyword(keyword)
$keyword.next((keyword??'').trim())
}}
style={{ width: 270 }}
/>
<Tooltip title={fullScreen?'取消全屏':'全屏'}>
<Button
onClick={() => {
setFullScreen(!fullScreen)
onFullScreenChange?.(!fullScreen)
}}
icon={fullScreen ?<CancelFullScreenSvg style={{ width: 20, height: 20, marginTop: 2 }} /> : <FullScreenSvg style={{ width: 20, height: 20, marginTop: 2 }} />}
type='text'
/>
</Tooltip>
</Space>
</div>
</div>
<div className='px-3'>
<Table
maxHeight={tableMaxHeight}
loading={loading||loadingElements}
columns={columns}
dataSource={tableData}
pageSize={page.pageSize} pageNum={page.pageNum} total={total??0}
onRowClick={(event, value) => {
setRow(value)
setAssetDetailParams({
visible: true,
id: value?.id,
dirId: value?.dirId,
reference: AssetBrowseReference
})
}}
rowClassName={(record, index) => (record?.id === row?.id) ? 'yy-table-select-row' : ''}
onPaginate={(page, pageSize) => {
setRow()
setPageAndArgs({ pageNum: page, pageSize })
}}
/>
</div>
<FilterElement
{...filterElementParams}
onCancel={(refresh = false) => {
setFilterElementParams({
visible: false,
type: undefined,
reference: undefined
})
if (refresh) {
getElements()
getAssets()
}
}}
/>
<FilterElementValue
{...filterElementValueParams}
onCancel={(refresh, val) => {
setFilterElementValueParams({
visible: false,
type: undefined,
defaultValue: undefined,
})
setSearchType('keyword')
if (refresh) {
setArgsAndPage({ elementValueFilters: val })
}
}}
/>
<AssetDetailDrawer
{...assetDetailParams}
onCancel={() => {
setAssetDetailParams({
visible: false,
id: undefined,
dirId: undefined,
reference: undefined
})
}}
/>
{contextHolder}
</div>
)
}
export default FC
\ No newline at end of file
...@@ -5,10 +5,11 @@ import { dispatch } from '../../../../model'; ...@@ -5,10 +5,11 @@ import { dispatch } from '../../../../model';
import AssetAction from './AssetAction'; import AssetAction from './AssetAction';
import './AddAssetModel.less'; import './AddAssetModel.less';
import { AssetManageReference, ResourceManageReference } from '../../../../util/constant';
const AddAssetModel = (props) => { const AddAssetModel = (props) => {
const { onCancel, visible, nodeId } = props; const { onCancel, visible, nodeId, reference = AssetManageReference } = props;
const [ confirmLoading, setConfirmLoading ] = useState(false); const [ confirmLoading, setConfirmLoading ] = useState(false);
const [ metadataId, setMetadataId ] = useState(''); const [ metadataId, setMetadataId ] = useState('');
const [ elements, setElements ] = useState([]); const [ elements, setElements ] = useState([]);
...@@ -34,12 +35,8 @@ const AddAssetModel = (props) => { ...@@ -34,12 +35,8 @@ const AddAssetModel = (props) => {
} }
}); });
const params = { let params = {
dirId: nodeId, dirIds: nodeId,
}
if ((metadataId||'')!=='') {
params.metadataId = metadataId;
} }
setConfirmLoading(true); setConfirmLoading(true);
...@@ -55,7 +52,7 @@ const AddAssetModel = (props) => { ...@@ -55,7 +52,7 @@ const AddAssetModel = (props) => {
showMessage('warn', '已存在相同的资产编号,请重新输入'); showMessage('warn', '已存在相同的资产编号,请重新输入');
} else { } else {
dispatch({ dispatch({
type: 'assetmanage.addOrUpdateDataAsset', type: (reference===ResourceManageReference)?'assetmanage.addOrUpdateResource':'assetmanage.addOrUpdateDataAsset',
payload: { payload: {
params, params,
data: { elements: newElements } data: { elements: newElements }
...@@ -103,6 +100,7 @@ const AddAssetModel = (props) => { ...@@ -103,6 +100,7 @@ const AddAssetModel = (props) => {
form={form} form={form}
dirId={nodeId} dirId={nodeId}
action='add' action='add'
reference={reference}
onMetadataChange={onMetadataChange} onMetadataChange={onMetadataChange}
onElementsChange={onElementsChange} onElementsChange={onElementsChange}
/> />
......
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { Form, Spin, Input, Descriptions, Space, Button, Tooltip } from 'antd'; import { Form, Spin, Input, Descriptions, Space, Button, Tooltip, Select } from 'antd';
import { DownOutlined, UpOutlined } from '@ant-design/icons'; import { DownOutlined, UpOutlined } from '@ant-design/icons';
import MetadataInfo from './MetadataInfo';
import { dispatch } from '../../../../model'; import { dispatch } from '../../../../model';
import { highlightSearchContentByTerms, showMessage, getAssetRange, checkMenuAdmit } from '../../../../util'; import { highlightSearchContentByTerms, showMessage, getAssetRange, checkMenuAdmit, getAssetType } from '../../../../util';
import { AppContext } from '../../../../App'; import { AppContext } from '../../../../App';
import Separate from './Separate'; import Separate from './Separate';
import { AnchorId, AnchorDirId, AssetManageReference } from '../../../../util/constant'; import { AnchorId, AnchorDirId, AssetManageReference, ResourceManageReference } 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'
import AssetItem from '../asset-item';
import ResourceItem from '../../AssetResourceManage/resource-item';
import { CancelSvg, EditSvg, SaveSvg, FullScreenSvg, CancelFullScreenSvg } from './AssetSvg'; import { CancelSvg, EditSvg, SaveSvg, FullScreenSvg, CancelFullScreenSvg } from './AssetSvg';
import { Subject } from 'rxjs';
export const AssetActionSubject = new Subject();
const AssetAction = (props) => { const AssetAction = (props) => {
const { id, dirId, action, terms, onChange, readOnly = false, form, onMetadataChange, onElementsChange, reference = AssetManageReference } = props; const { id, dirId, action, terms, onChange, readOnly = false, form, onMetadataChange, onElementsChange, reference = AssetManageReference } = props;
...@@ -81,7 +85,8 @@ const AssetAction = (props) => { ...@@ -81,7 +85,8 @@ const AssetAction = (props) => {
type: 'assetmanage.listElements', type: 'assetmanage.listElements',
payload: { payload: {
params: { params: {
range: getAssetRange(reference) range: getAssetRange(reference),
dataAssetType: getAssetType(reference),
} }
}, },
callback: data => { callback: data => {
...@@ -120,7 +125,8 @@ const AssetAction = (props) => { ...@@ -120,7 +125,8 @@ const AssetAction = (props) => {
type: 'assetmanage.listUserElements', type: 'assetmanage.listUserElements',
payload: { payload: {
params: { params: {
range: getAssetRange(reference) range: getAssetRange(reference),
dataAssetType: getAssetType(reference)
} }
}, },
callback: data => { callback: data => {
...@@ -228,25 +234,18 @@ const AssetAction = (props) => { ...@@ -228,25 +234,18 @@ const AssetAction = (props) => {
} }
const jumpToRelation = (relation) => { const jumpToRelation = (relation) => {
if (!readOnly) { const timestamp = new Date().getTime();
let event = new Event('storage');
event.key = 'assetRelationOnClickEvent';
event.relation = relation;
window?.dispatchEvent(event);
} else {
const timestamp = new Date().getTime();
if (relation.resourceType==='innerSource'||relation.resourceType==='outerSource') { if (relation.resourceType==='innerSource'||relation.resourceType==='outerSource') {
if (checkMenuAdmit('asset-resource-browse')) { if (checkMenuAdmit('asset-resource-browse')) {
window.open(`/center-home/menu/asset-resource-browse?${AnchorId}=${relation?.dataAssetId}&${AnchorDirId}=${relation?.dirId}&timestamp=${timestamp}`); window.open(`/center-home/menu/asset-resource-browse?${AnchorId}=${relation?.dataAssetId}&${AnchorDirId}=${relation?.dirId}&timestamp=${timestamp}`);
} }
} else if (relation.resourceType==='dataAsset') { } else if (relation.resourceType==='dataAsset') {
if (checkMenuAdmit('asset-browse')) { if (checkMenuAdmit('asset-browse')) {
window.open(`/center-home/menu/asset-browse?${AnchorId}=${relation?.dataAssetId}&${AnchorDirId}=${relation?.dirId}&timestamp=${timestamp}`); window.open(`/center-home/menu/asset-browse?${AnchorId}=${relation?.dataAssetId}&${AnchorDirId}=${relation?.dirId}&timestamp=${timestamp}`);
}
} else {
showMessage('warn', '资产类型不是资源也不是资产!');
} }
} else {
showMessage('warn', '资产类型不是资源也不是资产!');
} }
} }
...@@ -261,12 +260,8 @@ const AssetAction = (props) => { ...@@ -261,12 +260,8 @@ const AssetAction = (props) => {
} }
}); });
const params = { let params = {
dirId, dirIds: dirId,
}
if ((metadataId||'')!=='') {
params.metadataId = metadataId;
} }
setConfirmLoading(true); setConfirmLoading(true);
...@@ -282,7 +277,7 @@ const AssetAction = (props) => { ...@@ -282,7 +277,7 @@ const AssetAction = (props) => {
showMessage('warn', '已存在相同的资产编号,请重新输入'); showMessage('warn', '已存在相同的资产编号,请重新输入');
} else { } else {
dispatch({ dispatch({
type: 'assetmanage.addOrUpdateDataAsset', type: (reference===ResourceManageReference)?'assetmanage.addOrUpdateResource':'assetmanage.addOrUpdateDataAsset',
payload: { payload: {
params, params,
data: action==='add' ? { elements: newElements } : { ...assets, elements: newElements } data: action==='add' ? { elements: newElements } : { ...assets, elements: newElements }
...@@ -293,6 +288,9 @@ const AssetAction = (props) => { ...@@ -293,6 +288,9 @@ const AssetAction = (props) => {
getAsset(); getAsset();
showMessage("success",(action==='add')?"新增成功":"修改成功"); showMessage("success",(action==='add')?"新增成功":"修改成功");
onChange && onChange(); onChange && onChange();
if (action !== 'add') {
AssetActionSubject.next({ type: 'asset-change' })
}
}, },
error: () => { error: () => {
setConfirmLoading(false); setConfirmLoading(false);
...@@ -535,8 +533,6 @@ const AssetAction = (props) => { ...@@ -535,8 +533,6 @@ const AssetAction = (props) => {
setMetadataId(state.data?.metadataId||''); setMetadataId(state.data?.metadataId||'');
onMetadataChange && onMetadataChange(state.data?.metadataId||''); onMetadataChange && onMetadataChange(state.data?.metadataId||'');
form?.setFieldsValue({ '资产项': state.data?.metadataInfoJson||'' });
if ((state.data?.metadataId||'') !== '') { if ((state.data?.metadataId||'') !== '') {
fillElementValueBeforeCreate(state.data?.metadataId||''); fillElementValueBeforeCreate(state.data?.metadataId||'');
} }
...@@ -552,7 +548,7 @@ const AssetAction = (props) => { ...@@ -552,7 +548,7 @@ const AssetAction = (props) => {
key={_index} key={_index}
style={{ marginBottom: (_index===sameAttributeElements.length-1)? 0 : 15 }} style={{ marginBottom: (_index===sameAttributeElements.length-1)? 0 : 15 }}
> >
{ (element.name==='资产项') ? <MetadataInfo /> : <Input disabled={element.manualMaintain==='否'} /> } <ElementItem type='edit' element={element} reference={reference} />
</Form.Item> </Form.Item>
); );
}) })
...@@ -564,9 +560,7 @@ const AssetAction = (props) => { ...@@ -564,9 +560,7 @@ const AssetAction = (props) => {
(sameAttributeElements||[]).map((item, index) => { (sameAttributeElements||[]).map((item, index) => {
return ( return (
<Descriptions.Item label={<div className='title-common' style={{ textAlign: 'right', width: 60 }}>{item.name||''}</div>} key={index} style={{ paddingBottom: (index===sameAttributeElements.length-1)? 0 : 10 }}> <Descriptions.Item label={<div className='title-common' style={{ textAlign: 'right', width: 60 }}>{item.name||''}</div>} key={index} style={{ paddingBottom: (index===sameAttributeElements.length-1)? 0 : 10 }}>
{ <ElementItem element={item} value={item.value} terms={terms} />
item.name==='资产项' ? <MetadataInfo config={false} value={item.value||''} /> : <span className='text-color'>{highlightSearchContentByTerms(item.value||'', terms)}</span>
}
</Descriptions.Item> </Descriptions.Item>
); );
}) })
...@@ -590,4 +584,79 @@ const AssetAction = (props) => { ...@@ -590,4 +584,79 @@ const AssetAction = (props) => {
) )
} }
export default AssetAction; export default AssetAction;
\ No newline at end of file
export const MultipleItem = ({ value, onChange, element }) => {
return (
<Select
value={value?value.split(','):undefined}
mode="multiple"
disabled={element?.manualMaintain==='否'}
onChange={(val) => {
onChange?.((val??[]).length === 0 ? undefined : val.toString())
}}
dropdownRender={(originNode) => (
<div
onClick={e => {
e.stopPropagation()
}}
>
{originNode}
</div>
)}
>
{
(typeof(element?.optional) === 'string') && (element.optional??'').split(',').map((item, index) => <Select.Option key={index} value={item}>{item}</Select.Option>)
}
</Select>
)
}
export const ElementItem = ({ type = 'detail', element, reference = AssetManageReference, ...restProps }) => {
const { value, terms } = restProps
const inputItem = () => {
if (element?.metadataItem === '是') {
if (getAssetType(reference) === 'dataAsset') {
return <AssetItem readonly={(type==='detail')} {...restProps} />
} else if (getAssetType(reference) === 'resource') {
return <ResourceItem readonly={(type==='detail')} {...restProps} />
}
}
if (type === 'detail') {
return <span className='text-color'>{highlightSearchContentByTerms(value, terms)}</span>
} else {
if (element?.selectMode === '单选') {
return <Select
allowClear
disabled={element?.manualMaintain==='否'}
dropdownRender={(originNode) => (
<div
onClick={e => {
e.stopPropagation()
}}
>
{originNode}
</div>
)}
{...restProps}
>
{
(typeof(element?.optional) === 'string') && (element?.optional??'').split(',').map((item, index) => <Select.Option key={index} value={item}>{item}</Select.Option>)
}
</Select>
} else if (element?.selectMode === '多选') {
return <MultipleItem element={element} {...restProps}/>
}
return <Input disabled={element?.manualMaintain==='否'} {...restProps} />
}
}
return (
<React.Fragment>
{inputItem()}
</React.Fragment>
)
}
\ No newline at end of file
import React, { useEffect, useMemo, useState } from "react"; import React, { useEffect, useMemo, useState } from "react";
import { Spin, Descriptions, Divider } from "antd"; import { Spin, Descriptions, Divider } from "antd";
import MetadataInfo from './MetadataInfo'; import { getAssetRange, getAssetType, highlightSearchContentByTerms } from '../../../../util';
import { getAssetRange, highlightSearchContentByTerms } from '../../../../util';
import { dispatch } from '../../../../model'; import { dispatch } from '../../../../model';
import { ElementItem } from "./AssetAction";
import { AssetManageReference } from "../../../../util/constant";
const AssetDetail = (props)=>{ const AssetDetail = (props)=>{
...@@ -71,7 +72,8 @@ const AssetDetail = (props)=>{ ...@@ -71,7 +72,8 @@ const AssetDetail = (props)=>{
type: 'assetmanage.listUserElements', type: 'assetmanage.listUserElements',
payload: { payload: {
params: { params: {
range: getAssetRange(reference) range: getAssetRange(reference),
dataAssetType: getAssetType(reference)
} }
}, },
callback: data => { callback: data => {
...@@ -143,9 +145,7 @@ const AssetDetail = (props)=>{ ...@@ -143,9 +145,7 @@ const AssetDetail = (props)=>{
(_currentValues||[]).map((item, index) => { (_currentValues||[]).map((item, index) => {
return ( return (
<Descriptions.Item label={item.name||''} key={index}> <Descriptions.Item label={item.name||''} key={index}>
{ <ElementItem type={AssetManageReference} element={item} value={item.value} terms={terms} />
item.name==='资产项' ? <MetadataInfo config={false} value={item.value||''} terms={terms} /> : <span>{highlightSearchContentByTerms(item.value||'', terms)}</span>
}
</Descriptions.Item> </Descriptions.Item>
); );
}) })
......
...@@ -2,20 +2,24 @@ import React, { useEffect, useState } from 'react'; ...@@ -2,20 +2,24 @@ import React, { useEffect, useState } from 'react';
import { Spin, Tooltip, Typography, Dropdown, Menu } from 'antd'; import { Spin, Tooltip, Typography, Dropdown, Menu } from 'antd';
import { dispatch } from '../../../../model'; import { dispatch } from '../../../../model';
import { AssetBrowseReference, AssetManageReference, ResourceBrowseReference } from '../../../../util/constant'; import { AssetBrowseReference, AssetManageReference, ResourceBrowseReference, ResourceManageReference } from '../../../../util/constant';
import ImportElement from './ImportElement'; import ImportElement from './ImportElement';
import AttributeRelationModal from "./AttributeRelationModal"; import AttributeRelationModal from "./AttributeRelationModal";
import FilterElementModal from './FilterElementModal'; import FilterElementModal from './FilterElementModal';
import { showNotifaction, getAssetRange } from '../../../../util'; import ChangeElementValue from './change-element-value';
import { showNotifaction, getAssetRange, getAssetType } from '../../../../util';
import { MoreSvg } from './AssetSvg'; import { MoreSvg } from './AssetSvg';
import Separate from './Separate'; import Separate from './Separate';
import record from '../Assets/record.png'; import record from '../Assets/record.png';
import PermissionMenuItem from '../../../../util/Component/PermissionMenuItem'; import PermissionMenuItem from '../../../../util/Component/PermissionMenuItem';
import './AssetDirectory.less'; import './AssetDirectory.less';
import { Subject } from 'rxjs';
const { Paragraph, Text } = Typography; const { Paragraph, Text } = Typography;
export const AssetDirectorySubject = new Subject();
const AssetDirectory = (props) => { const AssetDirectory = (props) => {
const { id, directoryChanged, assetCount, reference = AssetManageReference, onElementsChange, nodeType } = props; const { id, directoryChanged, assetCount, reference = AssetManageReference, onElementsChange, nodeType } = props;
const [ dir, setDir ] = useState(null); const [ dir, setDir ] = useState(null);
...@@ -25,10 +29,16 @@ const AssetDirectory = (props) => { ...@@ -25,10 +29,16 @@ const AssetDirectory = (props) => {
const [ filterElementVisible, setFilterElementVisible ] = useState(false); const [ filterElementVisible, setFilterElementVisible ] = useState(false);
const [ resourceState, setResourceState ] = useState(null); const [ resourceState, setResourceState ] = useState(null);
const [ permissions, setPermissions ] = useState([]); const [ permissions, setPermissions ] = useState([]);
const [changeElementValueParams, setChangeElementValueParams] = useState({
visible: false,
type: undefined
})
useEffect(() => { useEffect(() => {
getPermissions(); getPermissions()
}, [])
useEffect(() => {
const storageChange = (e) => { const storageChange = (e) => {
if (e.key === 'assetResourceChange') { if (e.key === 'assetResourceChange') {
getResourceState(); getResourceState();
...@@ -108,7 +118,7 @@ const AssetDirectory = (props) => { ...@@ -108,7 +118,7 @@ const AssetDirectory = (props) => {
} }
const onExportElementBtnClick = () => { const onExportElementBtnClick = () => {
window.open('/api/dataassetmanager/elementApi/export'); window.open(`/api/dataassetmanager/elementApi/export?dataAssetType=${getAssetType(reference)}`);
} }
const onFilterElementClick = () => { const onFilterElementClick = () => {
...@@ -119,11 +129,19 @@ const AssetDirectory = (props) => { ...@@ -119,11 +129,19 @@ const AssetDirectory = (props) => {
setAttributeRelationModalVisible(true); setAttributeRelationModalVisible(true);
} }
const onChangeElementValueClick = () => {
setChangeElementValueParams({
visible: true,
type: reference
})
}
const onImportElementCancel = (visible = false, change = false, tip = '') => { const onImportElementCancel = (visible = false, change = false, tip = '') => {
setImportElementVisible(visible); setImportElementVisible(visible);
if (change) { if (change) {
onElementsChange && onElementsChange(); onElementsChange && onElementsChange();
AssetDirectorySubject.next({ type: 'element-change' })
} }
if (tip && tip!== '') { if (tip && tip!== '') {
...@@ -148,6 +166,8 @@ const AssetDirectory = (props) => { ...@@ -148,6 +166,8 @@ const AssetDirectory = (props) => {
onFilterElementClick(); onFilterElementClick();
} else if (key === 'attributeRelate') { } else if (key === 'attributeRelate') {
onAttributeRelationBtnClick(); onAttributeRelationBtnClick();
} else if (key === 'elementValueChangeManage') {
onChangeElementValueClick();
} }
} }
...@@ -189,6 +209,16 @@ const AssetDirectory = (props) => { ...@@ -189,6 +209,16 @@ const AssetDirectory = (props) => {
资产属性关联 资产属性关联
</div> </div>
</PermissionMenuItem> </PermissionMenuItem>
<PermissionMenuItem
defaultPermission='true'
key='elementValueChangeManage'
permissionKey='elementValueChangeManage'
permissions={permissions}
>
<div className='text-center'>
属性值变更管理
</div>
</PermissionMenuItem>
</Menu> </Menu>
); );
...@@ -224,7 +254,7 @@ const AssetDirectory = (props) => { ...@@ -224,7 +254,7 @@ const AssetDirectory = (props) => {
</Tooltip> </Tooltip>
</Paragraph> </Paragraph>
</div> </div>
<div className={(reference===AssetManageReference)?'mb-common':''}> <div className={(reference===ResourceManageReference||reference===AssetManageReference)?'mb-common':''}>
<Paragraph> <Paragraph>
<Tooltip title={dir?.desc||''}> <Tooltip title={dir?.desc||''}>
<Text className='title-color' ellipsis={true}> <Text className='title-color' ellipsis={true}>
...@@ -235,7 +265,7 @@ const AssetDirectory = (props) => { ...@@ -235,7 +265,7 @@ const AssetDirectory = (props) => {
</Paragraph> </Paragraph>
</div> </div>
{ {
(reference===AssetManageReference) && <div className='flex'> (reference===ResourceManageReference||reference===AssetManageReference) && <div className='flex'>
<Paragraph style={{ flex: 1, overflow: 'hidden' }}> <Paragraph style={{ flex: 1, overflow: 'hidden' }}>
<Tooltip title={dir?.remarks||''}> <Tooltip title={dir?.remarks||''}>
<Text className='title-color' ellipsis={true}> <Text className='title-color' ellipsis={true}>
...@@ -302,7 +332,7 @@ const AssetDirectory = (props) => { ...@@ -302,7 +332,7 @@ const AssetDirectory = (props) => {
</div> </div>
{ {
(reference===AssetManageReference) && <Dropdown overlay={elementManageMenu} placement="bottomCenter"> (reference===ResourceManageReference||reference===AssetManageReference) && <Dropdown overlay={elementManageMenu} placement="bottomCenter">
<div <div
className='flex more-container' className='flex more-container'
style={{ style={{
...@@ -322,6 +352,7 @@ const AssetDirectory = (props) => { ...@@ -322,6 +352,7 @@ const AssetDirectory = (props) => {
<ImportElement <ImportElement
visible={importElementVisible} visible={importElementVisible}
type={reference}
onCancel={onImportElementCancel} onCancel={onImportElementCancel}
/> />
...@@ -332,9 +363,21 @@ const AssetDirectory = (props) => { ...@@ -332,9 +363,21 @@ const AssetDirectory = (props) => {
/> />
<AttributeRelationModal <AttributeRelationModal
type={reference}
visible={ attributeRelationModalVisible } visible={ attributeRelationModalVisible }
onCancel={ onAttributeRelationModalCancel } onCancel={ onAttributeRelationModalCancel }
/> />
<ChangeElementValue
{...changeElementValueParams}
onCancel={(refresh) => {
setChangeElementValueParams({
visible: false,
type: undefined
})
refresh && AssetDirectorySubject.next({ type: 'element-value-change' })
}}
/>
</Spin> </Spin>
); );
} }
......
import React from 'react';
import { Row, Col, Typography } from 'antd';
import MetadataInfo from './MetadataInfo';
import './AssetItem.less';
const AssetItem = (props) => {
const { data } = props;
return (
<div className='asset-item'>
{
(data.elementsGroup||[]).map((elementGroup, index) => {
const _type = elementGroup.type||'';
return (
<div key={index}>
<div className='flex' style={{ alignItems: 'center', padding: '15px 0' }}>
<div style={{ width: 3, height: 14, backgroundColor: '#0069AC', marginRight: 5 }} />
<span style={{ fontWeight: 'bold', color: '#464646' }}>{_type||''}</span>
</div>
<Row>
{
elementGroup && elementGroup.elements && elementGroup.elements.map((element, _index) => {
return (
<Col className='mb-3' key={_index} md={8}>
<Typography.Paragraph title={ `${element.name||''}: ${element.value||''}` } style={{ color: '#464646' }} ellipsis>
{ `${element.name||''}: `}
{ element.name==='资产项'?<MetadataInfo config={false} value={element.value||''} />:`${element.value||''}` }
</Typography.Paragraph>
</Col>
);
})
}
</Row>
<div style={{ width: '100%', height: 2, backgroundColor: '#ededed' }} />
</div>
)
})
}
</div>
);
}
export default AssetItem;
\ No newline at end of file
.asset-item {
margin: 0px 0px 0px 30px;
.yy-divider-horizontal {
margin: 0 !important;
}
}
\ No newline at end of file
import React, { useEffect, useState, useRef, useMemo } from 'react';
import {Card, Spin, Tooltip, Tree, Dropdown, Menu, Modal, AutoComplete} from 'antd';
import { PlusOutlined, ImportOutlined,ExportOutlined,ReloadOutlined, SettingOutlined } from '@ant-design/icons';
import classNames from 'classnames';
import { useContextMenu, Menu as RcMenu, Item as RcItem } from "react-contexify";
import { dispatch } from '../../../../model';
import ImportDirectory from './ImportDirectory';
import UpdateDirectoryModal from './UpdateDirectoryModal';
import CustomDirectoryModal from './CustomDirectoryModal';
import { showMessage, getQueryParam, getAssetRange } from '../../../../util';
import { AnchorTimestamp, AnchorId, AssetManageReference, AssetBrowseReference, ResourceBrowseReference, AssetMountReference, AnchorDirId } from '../../../../util/constant';
import { highlightSearchContentByTerms } from '../../../../util';
import PermissionRcItem from '../../../../util/Component/PermissionRcItem';
import './AssetManageTree.less';
import 'react-contexify/dist/ReactContexify.css';
const { Option } = AutoComplete;
function updateTreeData(list, key, children) {
return list.map((node) => {
if (node.nodeId === key) {
return { ...node, children };
}
if (node.children) {
return {
...node,
children: updateTreeData(node.children, key, children),
};
}
return node;
});
}
const AssetManageTree = (props) => {
const MENU_ID = 'asset-manage-tree';
const { show } = useContextMenu({
id: MENU_ID,
});
const { checkable = false, onSelect, className, onCheck, tableId, reference=AssetManageReference, onDirectoryChange, centerId } = props;
const [ keyword, setKeyword ] = useState('');
const [ loading, setLoading ] = useState(false);
const [ treeData, setTreeData ] = useState([]);
const [ dataList, setDataList ] = useState([]);
const [ groupIds, setGroupIds ] = useState([]);
const [ expandedKeys, setExpandedKeys ] = useState([]);
const [ checkedKeys, setCheckedKeys ] = useState([]);
const [ autoExpandParent, setAutoExpandParent ] = useState(false);
const [ currentDirId, setCurrentDirId ] = useState('');
const [ currentDirType, setCurrentDirType ] = useState('');
const [ currentRightClickDir, setCurrentRightClickDir ] = useState({});
const [ importDirectoryVisible, setImportDirectoryVisible ] = useState(false);
const [ updateDirectoryModalVisible, setUpdateDirectoryModalVisible ] = useState(false);
const [ updateDirectoryAction, setUpdateDirectoryAction ] = useState('');
const [ customDirectoryModalVisible, setCustomDirectoryModalVisible ] = useState(false);
const [ customDirectoryAction, setCustomDirectoryAction ] = useState('');
const [options, setOptions] = useState([]);
const [ loadedKeys, setLoadedKeys ] = useState([]);
const [ permissions, setPermissions ] = useState([]);
const [modal, contextHolder] = Modal.useModal();
const timestamp = getQueryParam(AnchorTimestamp, props?.location?.search);
const id = getQueryParam(AnchorId, props?.location?.search);
const did = getQueryParam(AnchorDirId, props?.location?.search);
const treeDataRef = useRef([]);
const dataListRef = useRef([]);
useEffect(() => {
getPermissions();
window?.addEventListener("storage", storageChange);
return () => {
window?.removeEventListener("storage", storageChange);
}
//eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
useEffect(() => {
if ((did||'') !== '') {
getAllDirectoryAsTree(true, did);
} else if ((id||'') !== '') {
getDataAssetLocationThenGetTreeData();
} else {
getAllDirectoryAsTree(true);
}
//eslint-disable-next-line react-hooks/exhaustive-deps
}, [timestamp])
useEffect(() => {
if ((tableId||'') !== '') {
getTableDirIds();
}
//eslint-disable-next-line react-hooks/exhaustive-deps
}, [tableId])
useEffect(() => {
if ((centerId||'')!=='' && centerId!==currentDirId) {
treeDirectoryChanged(centerId);
}
//eslint-disable-next-line react-hooks/exhaustive-deps
}, [centerId])
const havePermission = useMemo(() => {
return (permissions||[]).findIndex(item => item === 'dirManage') !== -1
}, [permissions])
const storageChange = (e) => {
if (e.key === 'assetDirChangeEvent' || e.key === 'assetPathOnClickEvent') {
if ((e.dirId||'') !== '') {
treeDirectoryChanged(e.dirId);
}
} else if (e.key === 'assetRelationOnClickEvent') {
treeDirectoryChanged(e.relation?.dirId);
}
}
const getPermissions = () => {
if (!getAssetRange(reference)) return;
dispatch({
type: 'assetmanage.getPrivilegeByRange',
payload: {
range: getAssetRange(reference),
},
callback: data => {
setPermissions(data);
}
});
}
const getDataAssetLocationThenGetTreeData = () => {
setLoading(true);
dispatch({
type: 'assetmanage.getDataAssetLocation',
payload: {
dataAssetId: id
},
callback: data => {
getAllDirectoryAsTree(true, data.dirId||'');
},
error: () => {
setLoading(false);
getAllDirectoryAsTree(true);
}
});
}
const getTableDirIds = () => {
dispatch({
type: 'assetmanage.getDataAssetDetail',
payload: {
dataAssetId: tableId,
range: getAssetRange(AssetManageReference),
},
callback: data => {
setCheckedKeys(data.dirIds||[]);
setExpandedKeys(data.dirIds||[]);
setAutoExpandParent(true);
onCheck && onCheck(data.dirIds||[]);
},
})
}
const getAllDirectoryAsTree = (resetCurrentDirId=true, defaultSelectedId='') => {
setLoading(true);
if (resetCurrentDirId) {
onSelect && onSelect('', '');
}
let url = '';
if (reference === AssetManageReference || reference === AssetMountReference) {
url = 'assetmanage.queryAllDirectoryAsTree';
}
dispatch({
type: url,
callback: data => {
setLoading(false);
let newData = [...data];
if (reference === AssetMountReference) {
newData = newData.filter(item => item.resourceType!=='custom');
}
setLoadedKeys([]);
setTreeData(newData);
const _dataList = [], _groupIds = [];
generateList(newData, _dataList);
generateGroupIds(newData, _groupIds);
setDataList(_dataList);
setGroupIds(_groupIds);
treeDataRef.current = newData;
dataListRef.current = _dataList;
let defaultItem = null;
if ((defaultSelectedId||'') === '') {
_dataList.forEach(item => {
if ((defaultSelectedId||'')==='') {
defaultSelectedId = item.key;
}
})
}
function recursion(subCatalogs) {
if ((subCatalogs||[]).length===0) return;
(subCatalogs||[]).forEach(catalog=> {
if (catalog.nodeId === defaultSelectedId) {
defaultItem = catalog;
}
recursion(catalog.children);
})
}
if ((defaultSelectedId||'') !== '') {
recursion(newData);
}
if (resetCurrentDirId) {
if (defaultItem) {
const expandedKeys = _dataList
.map(item => {
if (item.key.indexOf(defaultSelectedId) > -1) {
return getParentKey(item.key, newData);
}
return null;
})
.filter((item, i, self) => item && self.indexOf(item) === i);
setExpandedKeys([...expandedKeys, defaultSelectedId]);
setAutoExpandParent(true);
setCurrentDirId(defaultItem.nodeId);
setCurrentDirType(defaultItem.type||'');
onSelect && onSelect(defaultItem.nodeId, defaultItem.type||'', defaultItem.level);
}
}
},
error: () => {
setLoading(false);
}
});
}
const treeDirectoryChanged = (did) => {
let defaultItem = null;
function recursion(subCatalogs) {
if ((subCatalogs||[]).length===0) return;
(subCatalogs||[]).forEach(catalog=> {
if (catalog.nodeId === did) {
defaultItem = catalog;
}
recursion(catalog.children);
})
}
if ((did||'') !== '') {
recursion(treeDataRef.current);
}
if (defaultItem) {
const expandedKeys = (dataListRef.current||[])
.map(item => {
if (item.key.indexOf(did) > -1) {
return getParentKey(item.key, treeDataRef.current);
}
return null;
})
.filter((item, i, self) => item && self.indexOf(item) === i);
setExpandedKeys([...expandedKeys, did]);
setAutoExpandParent(true);
setCurrentDirId(defaultItem.nodeId);
setCurrentDirType(defaultItem.type||'');
onSelect && onSelect(defaultItem.nodeId, defaultItem.type||'', defaultItem.level);
}
}
const generateList = (treeData, list, path = null) => {
for (let i = 0; i < treeData.length; i++) {
if (treeData[i].resourceType !== 'custom') {
const node = treeData[i];
const { nodeId, text } = node;
const currentPath = path ? `${path}/${text}` : text;
list.push({ key: nodeId , title: text, value: currentPath, level: node.level });
if (node.children) {
generateList(node.children, list, currentPath);
}
}
}
};
const generateGroupIds = (treeData, list) => {
function generateGroupItem(data, list) {
for (let i = 0; i < data.length; i++) {
const node = data[i];
list.push(node.nodeId);
if (node.children) {
generateGroupItem(node.children, list);
}
}
}
for (let i = 0; i < treeData.length; i++) {
const channelTreeData = treeData[i];
if (channelTreeData.children) {
for (let j =0; j < channelTreeData.children.length; j++) {
const node = channelTreeData.children[j];
const { nodeId } = node;
const groupItem = [nodeId];
if (node.children) {
generateGroupItem(node.children, groupItem);
}
list.push(groupItem);
}
}
}
};
const getParentKey = (key, tree) => {
let parentKey;
for (let i = 0; i < tree.length; i++) {
const node = tree[i];
if (node.children) {
if (node.children.some(item => item.nodeId === key)) {
parentKey = node.nodeId;
} else if (getParentKey(key, node.children)) {
parentKey = getParentKey(key, node.children);
}
}
}
return parentKey;
};
const getCurrentType = (key, tree) => {
let type = '';
(tree||[]).forEach(node => {
if (node.nodeId === key) {
type = node.type||'';
} else if (node.children) {
if (getCurrentType(key, node.children)) {
type = getCurrentType(key, node.children);
}
}
})
return type;
}
const addDir = () => {
if (!havePermission || currentDirType==='custom') return;
setUpdateDirectoryAction('add');
setUpdateDirectoryModalVisible(true);
}
const editDir = () => {
if ((currentDirType||'') === '') {
setUpdateDirectoryAction('edit');
setUpdateDirectoryModalVisible(true);
} else if (currentDirType === 'custom') {
setCustomDirectoryAction('edit');
setCustomDirectoryModalVisible(true);
}
}
const refreshTree = () => {
getAllDirectoryAsTree(false);
}
const importDir = () => {
if (!havePermission || currentDirType==='custom') return;
setImportDirectoryVisible(true);
}
const exportAllDir = () => {
window.open('/api/dataassetmanager/directoryApi/export');
}
const exportCurrentDir = () => {
if(currentDirId){
dispatch({
type: 'assetmanage.getDirectoryById',
payload: {
dirId: currentDirId
},
callback: data => {
window.open(`/api/dataassetmanager/directoryApi/export?parentPath=${data.path}`);
}
})
} else {
showMessage("warn","请选择目录")
}
}
const deleteDir = () => {
if (currentRightClickDir.nodeId) {
modal.confirm({
title: '提示',
content: '节点下可能包含资产信息,删除后将把资产从该目录上移除,确定继续吗?',
onOk: () => {
dispatch({
type: 'assetmanage.deleteDirectory',
payload: {data: [ currentRightClickDir.nodeId ]},
callback: () => {
showMessage("success","删除成功");
getAllDirectoryAsTree(true, (currentRightClickDir.nodeId===currentDirId)?'':currentDirId);
}
})
}
})
} else {
showMessage('info', '请先选择目录');
}
}
const moveNode = (steps) => {
if ((currentRightClickDir.nodeId||'') === '') {
showMessage('info', '请先选择目录');
return;
}
setLoading(true);
dispatch({
type: 'assetmanage.upDownDirectory',
payload: {
params: {
dirId: currentRightClickDir.nodeId,
steps
}
},
callback: () => {
showMessage('success', (steps===1)?'上移目录成功':'下移目录成功');
getAllDirectoryAsTree(false);
},
error: () => {
setLoading(false);
}
});
}
const customDir = () => {
if (!havePermission) return;
setCustomDirectoryAction('add');
setCustomDirectoryModalVisible(true);
}
// const onChange = (e) => {
// const { value } = e.target;
// if (value === '') {
// setExpandedKeys([]);
// setAutoExpandParent(false);
// setKeyword(value);
// return;
// }
// const expandedKeys = dataList
// .map(item => {
// if (item.title.indexOf(value) > -1) {
// return getParentKey(item.key, treeData);
// }
// return null;
// })
// .filter((item, i, self) => item && self.indexOf(item) === i);
// setExpandedKeys(expandedKeys);
// setAutoExpandParent(true);
// setKeyword(value);
// }
const onTreeSelect = (keys, { node }) => {
if ((keys||[]).length === 0) {
return;
}
setCurrentDirId(keys[0]);
const _currentDirType = getCurrentType(keys[0], treeData);
setCurrentDirType(_currentDirType);
onSelect && onSelect(keys[0], _currentDirType, node.level);
}
const onTreeCheck = (values, e) => {
//同一主题下只能挂载一个目录
if (e.node?.level === 1) {
showMessage('warn', '栏目不允许勾选');
return;
}
const _checkedKeysValue = [...(values.checked||[])];
if (e.checked) {
const _currentNodeId = e.node?.key;
let _groupItem = [];
groupIds.forEach(groupItem => {
groupItem.forEach(id => {
if (id === _currentNodeId) {
_groupItem = groupItem;
}
})
})
const _filterKeys = (_checkedKeysValue.filter(item => item===_currentNodeId || !(_groupItem.includes(item))));
setCheckedKeys(_filterKeys);
onCheck && onCheck(_filterKeys);
} else {
setCheckedKeys(_checkedKeysValue);
onCheck && onCheck(_checkedKeysValue);
}
}
const onExpand = (expandedKeys) => {
setExpandedKeys(expandedKeys);
setAutoExpandParent(false);
};
const onUpdateDirectoryCancel = (refresh=false, id) => {
setUpdateDirectoryModalVisible(false);
if (refresh) {
if (updateDirectoryAction === 'add') {
getAllDirectoryAsTree(true, id);
} else {
getAllDirectoryAsTree();
onDirectoryChange && onDirectoryChange();
}
}
}
const onImportDirectoryCancel = (refresh=false, resetCurrentDirId=false) => {
setImportDirectoryVisible(false);
refresh && getAllDirectoryAsTree(resetCurrentDirId);
}
const onCustomDirectoryCancel = (refresh=false, id='') => {
setCustomDirectoryModalVisible(false);
if (refresh) {
if (customDirectoryAction === 'add') {
getAllDirectoryAsTree(true, id);
} else {
getAllDirectoryAsTree(false);
}
}
}
const displayMenu = (e) => {
show(e, {
position: {
x: e.clientX + 30,
y: e.clientY - 10
}
});
}
const onAutoCompleteSearch = (searchText) => {
setKeyword(searchText);
setOptions(
!searchText ? [] : (dataList||[]).filter(item => item.value.indexOf(searchText)!==-1),
);
};
const onAutoCompleteSelect = (value, option) => {
const paths = value.split('/');
setKeyword(paths[paths.length-1]);
treeDirectoryChanged(option.key);
};
const onLoadData = ({ key, children }) =>
new Promise((resolve) => {
if (children) {
resolve();
return;
}
setLoadedKeys([...loadedKeys, key]);
dispatch({
type: 'assetmanage.getDirectoryChild',
payload: {
parentId: key,
},
callback: (data) => {
if (data && data.length>0) {
let newTreeData = updateTreeData(treeData, key, data);
setTreeData(newTreeData);
treeDataRef.current = newTreeData;
}
resolve();
},
error: () => {
resolve();
}
});
});
const exportMenu = (
<Menu>
<Menu.Item>
<div style={{ textAlign: 'center' }} onClick={() => exportAllDir()}>
导出所有
</div>
</Menu.Item>
<Menu.Item>
<div style={{ textAlign: 'center' }} onClick={() => exportCurrentDir()}>
导出选中目录
</div>
</Menu.Item>
</Menu>
);
const loop = (data, rootResourceType = null) =>
data.map(item => {
if (item.level === 1) {
rootResourceType = item.resourceType;
}
const title = (
<span
className={(item.level===1)?'title-color': 'text-color'}
>
{item.text}
{
//自定义类型栏目不统计资产数
(reference===AssetManageReference) && (item.level!==1||(item.level===1&&item.resourceType!=='custom')) && <span>{` (${item.dataAssetAndSubDirCount})`}</span>
}
</span>
);
if (item.children && item.children.length>0) {
return { ...item, ...{title, key: item.nodeId, children: loop(item.children, rootResourceType), className: (item.level===1)?'root':''} };
}
if (rootResourceType !== 'custom') {
return { ...item, ...{ title, key: item.nodeId, isLeaf: true, className: (item.level===1)?'root':'' }};
}
return { ...item, ...{ title, key: item.nodeId, children: null, className: (item.level===1)?'root':''}};
});
const classes = classNames('asset-manage-tree', className, {
'asset-manage-tree-read-only': (reference===AssetBrowseReference||reference===ResourceBrowseReference),
'asset-manage-tree-asset-mount-reference': reference===AssetMountReference,
});
return (
<Card
className={classes}
title={ (reference===AssetBrowseReference||reference===ResourceBrowseReference || reference===AssetMountReference) ? null : (
<div
className='flex px-2'
style={{
height: 40,
alignItems: 'center',
justifyContent: 'space-around',
}}
>
<Tooltip title={havePermission?"新增目录":"暂无权限"}>
<PlusOutlined className={(!havePermission||currentDirType==='custom')?'disable': 'default'} onClick={addDir} style={{ fontSize:16,cursor: (!havePermission||currentDirType==='custom')?'not-allowed':'pointer' }}/>
</Tooltip>
<Tooltip title="刷新目录">
<ReloadOutlined className='default' onClick={refreshTree} style={{ fontSize:16,cursor:'pointer' }} />
</Tooltip>
<Tooltip title={havePermission?"导入目录":"暂无权限"}>
<ImportOutlined className={(!havePermission||currentDirType==='custom')?'disable': 'default'} onClick={importDir} style={{ fontSize:16,cursor:(!havePermission||currentDirType==='custom')?'not-allowed':'pointer' }} />
</Tooltip>
<Dropdown overlay={havePermission?exportMenu:<></>} placement="bottomCenter" >
<Tooltip title={havePermission?"导出目录":"暂无权限"}>
<ExportOutlined className={(!havePermission)?'disable': 'default'} style={{ fontSize:16,cursor:'pointer',cursor:(!havePermission)?'not-allowed':'pointer' }} />
</Tooltip>
</Dropdown>
<Tooltip title={havePermission?"自定义目录":"暂无权限"}>
<SettingOutlined className={(!havePermission)?'disable': 'default'} onClick={customDir} style={{ fontSize:16,cursor:(!havePermission)?'not-allowed':'pointer' }} />
</Tooltip>
</div>
)}
bordered={false}
bodyStyle={{ padding: '10px 15px' }}
headStyle={{ padding: 0 }}
style={{ width: '100%' }}
>
<Spin spinning={loading}>
<AutoComplete
allowClear
value={keyword}
style={{ marginBottom: 10, width: '100%' }}
onSelect={onAutoCompleteSelect}
onSearch={onAutoCompleteSearch}
onClear={() => { setKeyword(''); }}
>
{
(options||[]).map((item, index) => {
return (
<Option key={item.key} value={item.value}>
<div style={{ whiteSpace: 'normal' }}>
{highlightSearchContentByTerms(item.value, [keyword])}
</div>
</Option>
);
})
}
</AutoComplete>
<Tree
checkable={checkable}
showLine={true}
showIcon={false}
onExpand={onExpand}
expandedKeys={expandedKeys}
autoExpandParent={autoExpandParent}
treeData={loop(treeData)}
loadData={onLoadData}
loadedKeys={loadedKeys}
selectedKeys={[currentDirId||'']}
onSelect={onTreeSelect}
onCheck={onTreeCheck}
checkedKeys={checkedKeys}
checkStrictly
onRightClick={({event, node}) => {
if (reference === AssetManageReference) {
setCurrentRightClickDir(node);
setCurrentDirType(node.type||'');
displayMenu(event);
}
}}
/>
</Spin>
<UpdateDirectoryModal
visible={ updateDirectoryModalVisible }
onCancel={ onUpdateDirectoryCancel }
action={ updateDirectoryAction }
dirId={ (updateDirectoryAction==='add')?currentDirId:currentRightClickDir.nodeId }
/>
<ImportDirectory
visible={ importDirectoryVisible }
onCancel={ onImportDirectoryCancel }
dirId={ currentDirId }
/>
<CustomDirectoryModal
visible={ customDirectoryModalVisible }
onCancel={ onCustomDirectoryCancel }
action={ customDirectoryAction }
dirId= { currentDirId }
/>
{
(reference!==AssetMountReference) && <RcMenu id={MENU_ID}>
{
currentRightClickDir && (currentRightClickDir.type!=='custom') && <PermissionRcItem
id="edit"
onClick={editDir}
defaultPermission={havePermission}
>
{ (currentRightClickDir.level===1)?'修改栏目':'修改目录' }
</PermissionRcItem>
}
<PermissionRcItem
id="up"
onClick={() => { moveNode(1); }}
defaultPermission={havePermission}
>
{ (currentRightClickDir.level===1)?'上移栏目':'上移目录' }
</PermissionRcItem>
<PermissionRcItem
id="down"
onClick={() => { moveNode(-1); }}
defaultPermission={havePermission}
>
{ (currentRightClickDir.level===1)?'下移栏目':'下移目录' }
</PermissionRcItem>
{
currentRightClickDir && (currentRightClickDir.type!=='custom'||(currentRightClickDir.type==='custom'&&currentRightClickDir.level===2)) && <PermissionRcItem
id="delete"
onClick={deleteDir}
defaultPermission={havePermission}
>
{ (currentRightClickDir.level===1)?'删除栏目':'删除目录' }
</PermissionRcItem>
}
</RcMenu>
}
{contextHolder}
</Card>
)
}
export default AssetManageTree;
\ No newline at end of file
@import '../../../../variables.less';
.asset-manage-tree {
.yy-card-head-title {
padding: 0;
}
.yy-tree{
height: calc(100vh - @header-height - @breadcrumb-height - 25px - 40px - 62px) !important;
overflow: auto !important;
}
// .root {
// display: flex;
// position: relative;
// width: 100%;
// background-color: #e7f2ff;
// margin-bottom: 3px;
// padding: 5px;
// align-items: center;
// .yy-tree-switcher {
// display: block;
// position: absolute;
// opacity: 0 !important;
// left: 0;
// top: 0;
// width: 100%;
// height: 100%;
// }
// .yy-tree-node-content-wrapper {
// margin-left: 20px;
// }
// }
// .yy-tree-indent .yy-tree-indent-unit:first-child {
// opacity: 0 !important;
// }
.site-tree-search-value {
color: #f50;
}
}
.asset-manage-tree-read-only {
.yy-tree {
height: calc(100vh - @header-height - @breadcrumb-height - 25px - 62px) !important;;
overflow: auto !important;
}
}
.asset-manage-tree-asset-mount-reference {
.yy-tree {
height: 400px !important;
overflow: auto !important;
}
}
\ No newline at end of file
import React,{ useState, useEffect, useRef, useMemo } from "react";
import { Button, Pagination, Space, Modal, Input, Table, Tooltip, Checkbox, Typography, Dropdown, Menu } from "antd";
import classNames from 'classnames';
import { Resizable } from 'react-resizable';
import ResizeObserver from 'rc-resize-observer';
import { useContextMenu, Menu as RcMenu, Item as RcItem } from "react-contexify";
import LocalStorage from 'local-storage';
import FilterElementModal from './FilterElementModal';
import AssetMount from '../../AssetRecycle/Component/AssetMount';
import ImportAssetDrawer from './ImportAssetDrawer';
import AssetEdit from './AddAssetModel';
import AssetDetailDrawer from "./AssetDetailDrawer";
import { dispatch, dispatchLatestHomepage } from '../../../../model';
import { showMessage, showNotifaction, getQueryParam, inputWidth, isSzseEnv, getAssetRange } from '../../../../util';
import { AnchorId, AnchorDirId, AnchorTimestamp, AssetBrowseReference, AssetManageReference, AssetRecycleReference, ResourceBrowseReference } from '../../../../util/constant';
import { FullScreenSvg, CancelFullScreenSvg } from './AssetSvg';
import AssetDeleteModal from './AssetDeleteModal';
import PermissionButton from '../../../../util/Component/PermissionButton';
import PermissionRcItem from '../../../../util/Component/PermissionRcItem';
import PermissionMenuItem from '../../../../util/Component/PermissionMenuItem';
import "./AssetTable.less";
import 'react-contexify/dist/ReactContexify.css';
const { Text } = Typography;
const { Search } = Input;
const { Column } = Table;
//资产项
const AssetItem = (props) => {
const { metadata } = props;
let content = '';
if (typeof metadata==='string') {
content = metadata;
} else {
(metadata?.columnItems||[]).forEach((item, index) => {
content += ((index===0)?'':',') + (((item.metadataColumnCnName||'')!=='')?(item.metadataColumnCnName||''):(item.metadataColumnName||''));
})
}
const cols = [
{
title: '序号',
dataIndex: 'key',
render: (text, record, index) => {
return (index+1).toString();
},
width: 60,
ellipsis: true,
},
{
title: '字段中文名称',
width: 160,
dataIndex: 'metadataColumnCnName',
editable: true,
ellipsis: true,
},
{
title: '字段英文名称',
width: 160,
dataIndex: 'metadataColumnName',
editable: true,
ellipsis: true,
},
];
return (
<Tooltip
overlayClassName='tooltip-common'
title={(typeof metadata==='string') ? (content||'') : <div style={{ width: 500 }}>
<Table
dataSource={metadata?.columnItems||[]}
columns={cols}
loading={false}
pagination={false}
size='small'
/>
</div>}
>
<Text ellipsis={true}>
{content||''}
</Text>
</Tooltip>
);
}
const ResizeableHeaderCell = props => {
const { onResize, width, onClick, ...restProps } = props;
if (!width) {
return <th {...restProps} />;
}
return (
<Resizable
width={width}
height={0}
handle={
<span
className="react-resizable-handle"
onClick={(e) => {
e.stopPropagation();
}}
/>
}
onResize={onResize}
draggableOpts={{ enableUserSelectHack: false }}
>
<th
onClick={onClick}
{...restProps}
/>
</Resizable>
);
};
const AssetTable = (props) => {
const { className, nodeId, nodeType, nodeLevel, elementsChanged, assetActionChanged, onSelect, onCountChange, reference = AssetManageReference, onFullScreenChange } = props;
const MENU_ID = 'asset-table';
const { show } = useContextMenu({
id: MENU_ID,
});
const [ loading, setLoading ] = useState(false);
const [ columns, setColumns ] = useState([]);
const [ realColumns, setRealColumns ] = useState([]);
const [ assets, setAssets ] = useState([]);
const [ total, setTotal ] = useState(0);
const [ selectItem, setSelectItem ] = useState({});
const [ contextMenuItem, setContextMenuItem ] = useState({});
const [ checkedKeys, setCheckedKeys ] = useState([]);
const [ importAssetVisible, setImportAssetVisible ] = useState(false);
const [ filterElementVisible, setFilterElementVisible ] = useState(false);
const [ assetEditVisible, setAssetEditVisible ] = useState(false);
const [ assetMountVisible, setAssetMountVisible ] = useState(false);
const [ assetDetailDrawerVisible, setAssetDetailDrawerVisible ] = useState(false);
// const [ currentAssetId, setCurrentAssetId ] = useState('');
const [ pagination, setPagination ] = useState( { pageNum: 1, pageSize: 20 } );
const { pageNum, pageSize } = pagination;
const [ keyword, setKeyword ] = useState('');
const [ batchCatalogChange, setBatchCatalogChange ] = useState(false);
const [ fullScreen, setFullScreen ] = useState(false);
const [ recursive, setRecursive ] = useState(true);
const [ fullSearch, setFullSearch ] = useState(false);
const [ TableWidth, setTableWidth ] = useState(0);
const [ compact, setCompact ] = useState(false);
const [ assetDeleteModalVisible, setAssetDeleteModalVisible ] = useState(false);
const [ permissions, setPermissions ] = useState([]);
const [ modal, contextHolder ] = Modal.useModal();
const anchorId = getQueryParam(AnchorId, props?.location?.search);
const timestamp = getQueryParam(AnchorTimestamp, props?.location?.search);
const anchorDid = getQueryParam(AnchorDirId, props?.location?.search);
const shouldScrollRef = useRef(false);
const metadataIndexRef = useRef('');
const remoteRelationRef = useRef(null);
useEffect(() => {
if (TableWidth>0 && columns.length>0) {
const currentWidth = (columns.reduce((preVal, col) => (col.width?col.width:0) + preVal, 0)) + 32.0;
if (currentWidth < TableWidth) {
columns.forEach(column => {
column.width = (TableWidth - 32.0)/columns.length;
})
}
setRealColumns([...columns, <Column key='auto' />]);
} else {
setRealColumns([]);
}
}, [columns, TableWidth])
useEffect(() => {
window?.addEventListener("storage", storageChange);
return () => {
window?.removeEventListener("storage", storageChange);
}
//eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
useEffect(() => {
if ((nodeId||'') !== '' ) {
setSelectItem({});
setCheckedKeys([]);
getPermissions();
if (shouldScrollRef.current === true) {
if (remoteRelationRef.current) {
getDataAssetLocationByRelation();
} else {
getDataAssetLocation();
}
} else {
setPagination({ ...pagination, pageNum: 1 });
}
} else if (reference === AssetRecycleReference) {
getRecyclePermissions();
}
//eslint-disable-next-line react-hooks/exhaustive-deps
}, [ nodeId, reference ])
useEffect(() => {
if ((anchorId||'') !== '') {
shouldScrollRef.current = true;
}
//eslint-disable-next-line react-hooks/exhaustive-deps
}, [timestamp])
useEffect(() => {
if ((nodeId||'') !== '' || reference===AssetRecycleReference) {
getFilterElementsGroupThenGetDataAssets();
}
//eslint-disable-next-line react-hooks/exhaustive-deps
}, [ keyword, pagination, elementsChanged, assetActionChanged, recursive, fullSearch ])
const canAdd = useMemo(() => {
const allowIndex = (permissions||[]).findIndex(item => item==='add');
return (allowIndex !== -1);
}, [permissions])
const canImport = useMemo(() => {
let _canImport = true;
if ((checkedKeys||[]).length === 0) {
const allowIndex = (permissions||[]).findIndex(item => item==='import');
_canImport = (allowIndex !== -1);
} else {
checkedKeys?.forEach(key => {
const index = (assets||[]).findIndex(item => item.id === key);
if (index !== -1) {
const allowIndex = (assets[index].allowButtons||[]).findIndex(item => item==='import');
_canImport = (allowIndex !== -1);
}
});
}
return _canImport;
}, [assets, checkedKeys, permissions])
const canExport = useMemo(() => {
let _canExport = true;
if ((checkedKeys||[]).length === 0) {
const allowIndex = (permissions||[]).findIndex(item => item==='export');
_canExport = (allowIndex !== -1);
} else {
checkedKeys?.forEach(key => {
const index = (assets||[]).findIndex(item => item.id === key);
if (index !== -1) {
const allowIndex = (assets[index].allowButtons||[]).findIndex(item => item==='export');
_canExport = (allowIndex !== -1);
}
});
}
return _canExport;
}, [assets, checkedKeys, permissions])
const canChangeDir = useMemo(() => {
let _canChangeDir = true;
checkedKeys?.forEach(key => {
const index = (assets||[]).findIndex(item => item.id === key);
if (index !== -1) {
const allowIndex = (assets[index].allowButtons||[]).findIndex(item => item==='changeDir');
_canChangeDir = (allowIndex !== -1);
}
})
return _canChangeDir;
}, [assets, checkedKeys])
const canDelete= useMemo(() => {
let _canDelete = true;
checkedKeys?.forEach(key => {
const index = (assets||[]).findIndex(item => item.id === key);
if (index !== -1) {
const allowIndex = (assets[index].allowButtons||[]).findIndex(item => item==='delete');
_canDelete = (allowIndex !== -1);
}
})
return _canDelete;
}, [assets, checkedKeys])
const storageChange = (e) => {
if (e.key === 'assetRelationOnClickEvent') {
remoteRelationRef.current = e.relation;
shouldScrollRef.current = true;
} else if (e.key === 'assetPathOnClickEvent') {
remoteRelationRef.current = {
dataAssetId: e.id,
dirId: e.dirId
};
shouldScrollRef.current = true;
}
}
const getPermissions = () => {
dispatch({
type: 'assetmanage.getPrivilegeByRangeAndDirId',
payload: {
range: getAssetRange(reference),
optionId: nodeId
},
callback: data => {
setPermissions(data);
}
});
}
const getRecyclePermissions = () => {
dispatch({
type: 'assetmanage.getPrivilegeByRange',
payload: {
range: getAssetRange(reference),
},
callback: data => {
setPermissions(data);
}
});
}
const getDataAssetLocationByRelation = () => {
setLoading(true);
dispatch({
type: 'assetmanage.getDataAssetLocation',
payload: {
dataAssetId: remoteRelationRef.current?.dataAssetId,
dirId: remoteRelationRef.current?.dirId,
},
callback: data => {
const anchorLocation = data.offset;
const _pageNum = parseInt(anchorLocation/pageSize + ((anchorLocation%pageSize===0)?0:1));
setPagination({ ...pagination, pageNum: _pageNum });
},
error: () => {
setLoading(false);
remoteRelationRef.current = null;
setPagination({ ...pagination, pageNum: 1 });
}
});
}
const getDataAssetLocation = () => {
setLoading(true);
dispatch({
type: 'assetmanage.getDataAssetLocation',
payload: {
dataAssetId: anchorId||'',
dirId: anchorDid||'',
},
callback: data => {
const anchorLocation = data.offset;
const _pageNum = parseInt(anchorLocation/pageSize + ((anchorLocation%pageSize===0)?0:1));
setPagination({ ...pagination, pageNum: _pageNum });
},
error: () => {
setLoading(false);
setPagination({ ...pagination, pageNum: 1 });
}
});
}
const changeCurrent = (page,size) => {
setCheckedKeys([]);
setPagination({ pageNum: page, pageSize: size });
}
const getFilterElementsGroupThenGetDataAssets = () => {
setLoading(true);
dispatch({
type: 'assetmanage.listFilterElementsGroupByType',
payload: {
range: getAssetRange(reference)
},
callback: data => {
let _columns = [];
let index = 0, _metadataIndex = '';
(data||[]).forEach(group => {
(group.names||[]).forEach((name, i) => {
index++;
const params = {
title: name,
dataIndex: `element${index}`,
ellipsis: true,
width: 120,
render: (text, record) => {
return (
<Tooltip title={text||''}>
<Text ellipsis={true}>
{text||''}
</Text>
</Tooltip>
);
}
};
if (name === '编号') {
params.width = 60;
// params.fixed = 'left';
} else if (name === '中文名称') {
params.width = isSzseEnv?230:160;
// params.fixed = 'left';
} else if (name === '英文名称') {
params.width = isSzseEnv?224:160;
// params.fixed = 'left';
// params.render = (text, record) => {
// return (
// <Tooltip title={text||''}>
// <a onClick={()=>{detailAsset(record);}}>
// {text||''}
// </a>
// </Tooltip>
// );
// }
} else if (name === '资产项') {
params.width = isSzseEnv?250:120;
params.render = (metadata, _) => {
return (
<AssetItem metadata={metadata} />
);
}
_metadataIndex = `element${index}`;
} else if (name === '资产路径') {
params.render = (text, record) => {
return (
<Tooltip title={text||''}>
<Text ellipsis={true}>
<a onClick={()=>{
let event = new Event('storage');
event.key = 'assetDirChangeEvent';
event.dirId = record.dirId||'';
window?.dispatchEvent(event);
}}>
{text||''}
</a>
</Text>
</Tooltip>
);
}
}
_columns.push(params);
})
})
setAssets([]);
setColumns(_columns);
metadataIndexRef.current = _metadataIndex;
getDataAssets(_metadataIndex);
},
error: () => {
setLoading(false)
}
})
}
const getDataAssets = (projectIndex = metadataIndexRef.current) => {
setLoading(true);
let params = {
dirId: nodeId,
pageNum,
pageSize,
keyword: keyword,
range: getAssetRange(reference),
};
if (reference !== AssetRecycleReference) {
params.recursive = recursive;
//全局搜索资产
if (fullSearch) {
params.dirId = '';
}
}
let url = 'assetmanage.listDataAssetsByPage';
if (reference===AssetRecycleReference) {
url = 'assetmanage.listRecycleBinDataAssetsByPage';
} else if ((reference===AssetBrowseReference|| reference===ResourceBrowseReference)) {
params.checkPermission = true;
if (nodeType === 'custom') {
url = 'assetmanage.listDataAssetsByPersonalCustomType';
if (reference === AssetBrowseReference) {
params.resourceType = 'dataAsset';
} else if (reference === ResourceBrowseReference) {
params.resourceType = 'innerSource,outerSource';
}
}
}
dispatchLatestHomepage({
type: url,
payload: params,
callback: data => {
const _assets = [];
(data.data||[]).forEach(asset => {
let _asset = {...asset}, index = 0;
(asset.elementValues||[]).forEach((elementValue) => {
(elementValue.values||[]).forEach((value, i) => {
index++;
if (projectIndex === `element${index}`) {
let metadata = {};
try {
metadata = JSON.parse(value);
_asset['metadata'] = metadata;
_asset[`element${index}`] = metadata;
} catch(error) {
metadata = value;
_asset['metadata'] = metadata;
_asset[`element${index}`] = metadata;
}
} else {
_asset[`element${index}`] = value;
}
});
})
_assets.push(_asset);
})
setAssets(_assets);
if (shouldScrollRef.current) {
let scrollId = null;
if (remoteRelationRef.current) {
scrollId = remoteRelationRef.current.dataAssetId;
} else {
scrollId = anchorId;
}
shouldScrollRef.current = false;
setTimeout(() => {
var anchor = document.querySelector(`#data-asset-${scrollId}`);
anchor?.scrollIntoView();
}, 500)
}
if (reference === AssetManageReference) {
if (remoteRelationRef.current) {
const index = _assets.findIndex((asset) => asset.id === remoteRelationRef.current.dataAssetId);
remoteRelationRef.current = null;
if (index === -1) {
setSelectItem(_assets.length>0?_assets[0]:{});
onSelect && onSelect(_assets.length>0?_assets[0].id:'', _assets.length>0?_assets[0].dirId:'');
} else {
setSelectItem(_assets[index]);
onSelect && onSelect(_assets[index].id, _assets[index].dirId);
}
} else if ((selectItem?.id||'') !=='') {
const index = _assets.findIndex((asset) => asset.id === selectItem?.id);
if (index === -1) {
setSelectItem(_assets.length>0?_assets[0]:{});
onSelect && onSelect(_assets.length>0?_assets[0].id:'', _assets.length>0?_assets[0].dirId:'');
}
} else {
setSelectItem(_assets.length>0?_assets[0]:{});
onSelect && onSelect(_assets.length>0?_assets[0].id:'', _assets.length>0?_assets[0].dirId:'');
}
}
setTotal(data.total||0);
onCountChange && onCountChange(data.total||0);
setLoading(false);
},
error: () => {
setLoading(false)
}
})
}
const onSearchInputChange = (value) => {
setKeyword(value||'');
setPagination({ ...pagination, pageNum: 1 });
}
const addAsset =()=>{
setAssetEditVisible(true);
}
const onAssetEditCancel=(refresh = false) => {
setAssetEditVisible(false);
refresh && getDataAssets();
}
const onAssetMountCancel = (refresh = false) => {
setAssetMountVisible(false);
if (refresh) {
setCheckedKeys([]);
getDataAssets();
}
}
const importAsset = () => {
setImportAssetVisible(true);
}
const onFilterElementClick = () => {
setFilterElementVisible(true);
}
const onRecursiveChange = (e) => {
setRecursive(!e.target.checked);
}
const onFullSearchChange = (e) => {
setFullSearch(e.target.checked);
}
const exportAsset = () => {
if ((checkedKeys||[]).length === 0) {
modal.confirm({
title: '提示',
content: '是否导出选中目录下的所有资产?',
onOk: () => {
window.open(`/api/dataassetmanager/dataAssetApi/exportByDataAssetIds?dirId=${nodeId}&recursive=${recursive}`);
}
})
} else {
window.open(`/api/dataassetmanager/dataAssetApi/exportByDataAssetIds?dataAssetIds=${checkedKeys.join(',')}`);
}
}
// const detailAsset = (item)=>{
// setCurrentAssetId(item.id);
// window.open(`/center-home/asset-detail?id=${item.id}&dirId=${nodeId}`);
// }
const deleteAssets = () => {
if ((checkedKeys||[]).length > 0) {
if (reference === AssetManageReference) {
setAssetDeleteModalVisible(true);
} else {
modal.confirm({
title: '提示',
content: '您确定要永久删除这些资产吗?',
onOk: () => {
let payload = {
data: checkedKeys
}
dispatch({
type: 'assetmanage.deleteDataAssets',
payload,
callback: () => {
showMessage("success","删除成功");
getDataAssets();
setCheckedKeys([]);
},
error: () => {
}
})
}
})
}
}else{
showMessage("warn","请先选择资产")
}
}
const onImportAssetCancel = () => {
setImportAssetVisible(false);
}
const onImportAssetSuccess = (tip = '') => {
getDataAssets();
if ((tip||'') !== '') {
showNotifaction('导入提示', tip, 5);
}
}
const onFilterElementModalCancel = (refresh = false) => {
setFilterElementVisible(false);
refresh && getFilterElementsGroupThenGetDataAssets();
}
const onAssetDetailDrawerCancel = () => {
setAssetDetailDrawerVisible(false);
}
const onBatchCatalogChangeBtnClick = () => {
setBatchCatalogChange(true);
setAssetMountVisible(true);
}
const onAssetDeleteModalCancel = () => {
setAssetDeleteModalVisible(false);
}
const onAssetDeleteModalDelete = () => {
setAssetDeleteModalVisible(false);
const asssetIdsAndDirIds = [];
(assets||[]).forEach(asset => {
if (checkedKeys.indexOf(asset.id) !== -1) {
asssetIdsAndDirIds.push({ dataAssetId: asset.id||'', dirId: asset.dirId||'' });
}
})
let payload = {
data: asssetIdsAndDirIds
};
dispatch({
type: 'assetmanage.unloadDataAssets',
payload,
callback: () => {
showMessage("success","删除成功");
getDataAssets();
setCheckedKeys([]);
}
})
}
const onAssetDeleteModalDeleteAll = () => {
setAssetDeleteModalVisible(false);
let payload = {
data: checkedKeys
};
dispatch({
type: 'assetmanage.unloadDataAssetsFromAllDirs',
payload,
callback: () => {
showMessage("success","删除成功");
getDataAssets();
setCheckedKeys([]);
}
})
}
const recoveryAssets = () => {
if ((checkedKeys||[]).length > 0) {
modal.confirm({
title: '提示',
content: '您确定要恢复这些资产吗?',
onOk: () => {
let payload = {
data: checkedKeys
}
dispatch({
type: 'assetmanage.recoveryFromRecycleBin',
payload,
callback: (data) => {
if (data?.message) {
showNotifaction('提示', data?.message, 5);
}
getDataAssets();
setCheckedKeys([]);
}
})
}
})
}else{
showMessage("warn","请选择资产");
}
}
const onSelectChange = keys => {
setCheckedKeys(keys);
};
const onFullScreenClick = () => {
setFullScreen(!fullScreen);
onFullScreenChange && onFullScreenChange(!fullScreen);
}
const handleResize = index => (e, { size }) => {
const nextColumns = [...realColumns];
nextColumns[index] = {
...nextColumns[index],
width: size.width,
};
setRealColumns(nextColumns);
};
const displayMenu = (e) => {
show(e);
}
const handleItemClick = ({ event, props, data, triggerEvent }) => {
const key = event.currentTarget.id;
if (key === 'uncombed') {
modal.confirm({
title: '提示',
content: '是否将该条非资产的资源转为未梳理状态?',
onOk: () => {
dispatch({
type: 'assetmanage.updateResourceState',
payload: {
params: {
dataAssetId: contextMenuItem.id,
resourceState: 'uncombed'
}
},
callback: () => {
LocalStorage.set('assetResourceChange', !(LocalStorage.get('assetResourceChange')||false));
let event = new Event('storage');
event.key = 'assetResourceChange';
window?.dispatchEvent(event);
getDataAssets();
}
});
}
});
} else if (key === 'notRelatedAsset') {
modal.confirm({
title: '提示',
content: '是否将该条未梳理的资源转为非资产?',
onOk: () => {
dispatch({
type: 'assetmanage.updateResourceState',
payload: {
params: {
dataAssetId: contextMenuItem.id,
resourceState: 'notRelatedAsset'
}
},
callback: () => {
LocalStorage.set('assetResourceChange', !(LocalStorage.get('assetResourceChange')||false));
let event = new Event('storage');
event.key = 'assetResourceChange';
window?.dispatchEvent(event);
getDataAssets();
}
});
}
});
}
}
const onMenuClick = ({ key }) => {
console.log('key', key);
if (key === 'import') {
importAsset();
} else if (key === 'export') {
exportAsset();
} else if (key === 'changeDir') {
onBatchCatalogChangeBtnClick();
} else if (key === 'delete') {
deleteAssets();
} else if (key === 'colConfig') {
onFilterElementClick();
}
}
const moreMenu = (
<Menu onClick={onMenuClick}>
{
(reference===AssetManageReference && nodeType!=='custom') && <PermissionMenuItem key='import' defaultPermission={canImport}>
<div className='text-center'>
导入
</div>
</PermissionMenuItem>
}
{
(reference===AssetManageReference || reference===AssetRecycleReference) && <React.Fragment>
<PermissionMenuItem key='export' defaultPermission={canExport}>
<div className='text-center'>
导出
</div>
</PermissionMenuItem>
<PermissionMenuItem
key='changeDir'
defaultPermission={canChangeDir}
disabled={(checkedKeys||[]).length===0}
tip={(checkedKeys||[]).length===0?'请先选择资产':''}
>
<div className='text-center'>
变更目录
</div>
</PermissionMenuItem>
{
//自定义目录下的资产不允许新增 删除
(nodeType!=='custom') && <PermissionMenuItem
key='delete'
defaultPermission={canDelete}
disabled={(checkedKeys||[]).length===0}
tip={(checkedKeys||[]).length===0?'请先选择资产':''}
>
<div className='text-center'>
删除
</div>
</PermissionMenuItem>
}
</React.Fragment>
}
<Menu.Item key='colConfig'>
<div className='text-center'>
可见列设置
</div>
</Menu.Item>
</Menu>
);
const rowSelection = {
selectedRowKeys: checkedKeys,
onChange: onSelectChange,
};
const classes = classNames('asset-list', className, {
'asset-list-fullscreen': fullScreen,
});
let scrollY = null;
if ((assets||[]).length>0) {
if (fullScreen) {
scrollY = 'calc(100vh - 209px - 72px)';
} else if (reference===AssetManageReference) {
scrollY = 'calc(100vh - 209px - 123px - 15px - 72px)';
} else if (reference===AssetBrowseReference ||reference===ResourceBrowseReference) {
scrollY = 'calc(100vh - 209px - 94px - 15px - 72px)';
} else if (reference===AssetRecycleReference) {
scrollY = 'calc(100vh - 209px - 72px)';
}
}
return(
<div className={classes}>
<div
className='flex p-common'
style={{
alignItems: 'center',
justifyContent: 'space-between',
}}
>
<Space size={15}>
{
//栏目和自定义目录 不允许新增资产
(reference===AssetManageReference && nodeLevel!==1 && nodeType!=='custom') && <PermissionButton
defaultPermission={canAdd}
onClick={addAsset}
>
新增
</PermissionButton>
}
{
(compact && reference===AssetManageReference) ? <Dropdown overlay={moreMenu} placement="bottomCenter">
<Button>其他操作</Button>
</Dropdown> : <React.Fragment>
{
(reference===AssetManageReference && nodeType!=='custom') && <PermissionButton defaultPermission={canImport} onClick={importAsset}>导入</PermissionButton>
}
{
(reference===AssetManageReference || reference===AssetRecycleReference) && <React.Fragment>
<PermissionButton defaultPermission={canExport} onClick={exportAsset} >导出</PermissionButton>
{
(reference === AssetManageReference) && <PermissionButton
defaultPermission={canChangeDir}
tip={(checkedKeys||[]).length===0?'请先选择资产':''}
onClick={onBatchCatalogChangeBtnClick}
disabled={(checkedKeys||[]).length===0}
>
变更目录
</PermissionButton>
}
{
(reference === AssetRecycleReference) && <PermissionButton
onClick={onBatchCatalogChangeBtnClick}
disabled={(checkedKeys||[]).length===0}
permissionKey='loadDataAsset'
permissions={permissions}
tip={(checkedKeys||[]).length===0?'请先选择资产':''}
>
挂载
</PermissionButton>
}
{
(reference===AssetRecycleReference) &&
<PermissionButton
onClick={recoveryAssets}
disabled={(checkedKeys||[]).length===0}
permissionKey='resumeDataAsset'
permissions={permissions}
tip={(checkedKeys||[]).length===0?'请先选择资产':''}
>
恢复
</PermissionButton>
}
{
//自定义目录下的资产不允许删除
(nodeType!=='custom') &&
<PermissionButton
defaultPermission={canDelete}
tip={(checkedKeys||[]).length===0?'请先选择资产':''}
onClick={deleteAssets}
disabled={(checkedKeys||[]).length===0}
>
删除
</PermissionButton>
}
</React.Fragment>
}
<Button onClick={onFilterElementClick}>可见列设置</Button>
</React.Fragment>
}
{
(reference!==AssetRecycleReference && isSzseEnv) && <Checkbox onChange={onRecursiveChange} checked={!recursive}>仅显示当前目录</Checkbox>
}
</Space>
<Space>
{
(reference!==AssetRecycleReference) && <Checkbox onChange={onFullSearchChange} checked={fullSearch}>全部数据</Checkbox>
}
<Search
placeholder="请输入资产要素值"
allowClear
onSearch={onSearchInputChange}
enterButton
style={{ width: inputWidth }}
/>
{
(reference!==AssetRecycleReference) && <Tooltip title={fullScreen?'取消全屏':'全屏'}>
<Button onClick={onFullScreenClick} icon={fullScreen?<CancelFullScreenSvg style={{ width: 20, height: 20 }} />:<FullScreenSvg style={{ width: 20, height: 20 }} />} type='text'></Button>
</Tooltip>
}
</Space>
</div>
<div
className='px-common'
>
<ResizeObserver
onResize={({ width }) => {
setTableWidth(width);
if (width < 1030 && !compact) {
setCompact(true);
} else if (width >= 1030 && compact) {
setCompact(false);
}
}}
>
<Table
rowSelection={rowSelection}
components={{
header: {
cell: ResizeableHeaderCell,
}
}}
onRow={(record) => {
return {
id: `data-asset-${record?.id}`,
onClick: (e) => {
setSelectItem(record);
onSelect && onSelect(record?.id, record?.dirId);
if (reference !== AssetManageReference) {
setAssetDetailDrawerVisible(true);
}
},
onContextMenu: event => {
if (reference===AssetManageReference && (record.resourceState==='uncombed'||record.resourceState==='notRelatedAsset')) {
setContextMenuItem(record);
displayMenu(event);
}
},
}
}}
rowClassName={(record, index) => {
if (record?.id===anchorId || record?.id===selectItem?.id) {
return 'yy-table-select-row';
}
return '';
}}
loading={loading}
columns={
(realColumns||[]).map((column, index) => {
return {
...column,
onHeaderCell: column => ({
width: column.width,
onResize: handleResize(index),
}),
};
})
}
rowKey='id'
dataSource={assets}
pagination={false}
size='default'
scroll={{ y: scrollY }}
/>
</ResizeObserver>
</div>
<Pagination
size="small"
className="text-center m-3"
showSizeChanger
showQuickJumper
onChange={changeCurrent}
onShowSizeChange={changeCurrent}
current={pageNum}
pageSize={pageSize}
defaultCurrent={1}
total={total}
showTotal={total => `共 ${total} 条`}
/>
<AssetEdit
visible={assetEditVisible}
nodeId={nodeId}
onCancel={onAssetEditCancel}
/>
<ImportAssetDrawer
visible={importAssetVisible}
nodeId={nodeId}
onCancel={onImportAssetCancel}
onSuccess={onImportAssetSuccess}
/>
<AssetMount
visible={ assetMountVisible }
reference={reference}
ids={ batchCatalogChange?checkedKeys:[] }
onCancel={ onAssetMountCancel }
{...props}
/>
<FilterElementModal
type={(reference===AssetManageReference)?'admin': 'user'}
reference={reference}
visible={ filterElementVisible }
onCancel={ onFilterElementModalCancel}
/>
<AssetDetailDrawer
id={selectItem?.id}
dirId={selectItem?.dirId}
reference={reference}
visible={assetDetailDrawerVisible}
onCancel={onAssetDetailDrawerCancel}
/>
<AssetDeleteModal
visible={assetDeleteModalVisible}
onCancel={onAssetDeleteModalCancel}
onDelete={onAssetDeleteModalDelete}
onDeleteAll={onAssetDeleteModalDeleteAll}
/>
<RcMenu id={MENU_ID}>
{
(contextMenuItem.resourceState==='notRelatedAsset') && <PermissionRcItem
id="uncombed"
permissionKey='changeToUncombed'
permissions={contextMenuItem?.allowButtons}
onClick={handleItemClick}
>
转为未梳理
</PermissionRcItem>
}
{
(contextMenuItem.resourceState==='uncombed') && <PermissionRcItem
id="notRelatedAsset"
permissionKey='changeToNotAsset'
permissions={contextMenuItem?.allowButtons}
onClick={handleItemClick}
>
转为非资产
</PermissionRcItem>
}
</RcMenu>
{contextHolder}
</div>
)
}
export default AssetTable;
\ No newline at end of file
...@@ -2,6 +2,8 @@ import React, { useEffect, useState } from 'react'; ...@@ -2,6 +2,8 @@ import React, { useEffect, useState } from 'react';
import { Modal, Form, TreeSelect, Select, Space, Button, Row, Col, } from 'antd'; import { Modal, Form, TreeSelect, Select, Space, Button, Row, Col, } from 'antd';
import { dispatch } from '../../../../model'; import { dispatch } from '../../../../model';
import { AssetManageReference, ResourceManageReference } from '../../../../util/constant';
import { getAssetType } from '../../../../util';
const MetaModelSelect = ({ value = {}, metaModelTreeData = [], onChange, ...restProps }) => { const MetaModelSelect = ({ value = {}, metaModelTreeData = [], onChange, ...restProps }) => {
const [ attributes, setAttributes ] = useState([]); const [ attributes, setAttributes ] = useState([]);
...@@ -99,7 +101,7 @@ const MetaModelSelect = ({ value = {}, metaModelTreeData = [], onChange, ...rest ...@@ -99,7 +101,7 @@ const MetaModelSelect = ({ value = {}, metaModelTreeData = [], onChange, ...rest
} }
const AttributeRelationModal = (props) => { const AttributeRelationModal = (props) => {
const { visible, onCancel } = props; const { visible, onCancel, type = AssetManageReference } = props;
const [ confirmLoading, setConfirmLoading ] = useState(false); const [ confirmLoading, setConfirmLoading ] = useState(false);
const [ metadataModelTreeData, setMetadataModelTreeData ] = useState([]); const [ metadataModelTreeData, setMetadataModelTreeData ] = useState([]);
...@@ -146,6 +148,9 @@ const AttributeRelationModal = (props) => { ...@@ -146,6 +148,9 @@ const AttributeRelationModal = (props) => {
const loadElementWithoutCustom = () => { const loadElementWithoutCustom = () => {
dispatch({ dispatch({
type: 'assetmanage.loadElementWithoutCustom', type: 'assetmanage.loadElementWithoutCustom',
payload: {
dataAssetType: getAssetType(type)
},
callback: data => { callback: data => {
setElements(data||[]); setElements(data||[]);
getRelAttrByModel(); getRelAttrByModel();
...@@ -159,6 +164,11 @@ const AttributeRelationModal = (props) => { ...@@ -159,6 +164,11 @@ const AttributeRelationModal = (props) => {
const getRelAttrByModel = () => { const getRelAttrByModel = () => {
dispatch({ dispatch({
type: 'assetmanage.getRelAttrByModel', type: 'assetmanage.getRelAttrByModel',
payload: {
params: {
dataAssetType: getAssetType(type),
}
},
callback: data => { callback: data => {
let _fieldsValue = {}; let _fieldsValue = {};
(data||[]).forEach(item => { (data||[]).forEach(item => {
...@@ -191,7 +201,10 @@ const AttributeRelationModal = (props) => { ...@@ -191,7 +201,10 @@ const AttributeRelationModal = (props) => {
dispatch({ dispatch({
type: 'assetmanage.saveEleAndAttrRel', type: 'assetmanage.saveEleAndAttrRel',
payload: { payload: {
data: newRels data: newRels,
params: {
dataAssetType: getAssetType(type)
}
}, },
callback: data => { callback: data => {
reset(); reset();
......
...@@ -7,14 +7,17 @@ import update from 'immutability-helper'; ...@@ -7,14 +7,17 @@ import update from 'immutability-helper';
import DragTag from './DragTag'; import DragTag from './DragTag';
import PreviewTree from './PreviewTree'; import PreviewTree from './PreviewTree';
import { dispatch, dispatchLatest } from '../../../../model'; import { dispatch, dispatchLatest } from '../../../../model';
import { AssetManageReference, AssetBrowseReference, ResourceBrowseReference } from '../../../../util/constant'; import { AssetManageReference, AssetBrowseReference, ResourceBrowseReference, ResourceManageReference } from '../../../../util/constant';
import { showMessage } from '../../../../util'; import { getAssetType, showMessage } from '../../../../util';
const resourceTypes = [ const resourceTypes = [
{ key: 'innerSource', name: '内部资源' }, { key: 'innerSource', name: '内部资源' },
{ key: 'outerSource', name: '外部资源' }, { key: 'outerSource', name: '外部资源' },
{ key: 'dataAsset', name: '资产' }, ]
const assetTypes = [
{ key: 'dataAsset', name: '资产' }
] ]
const CustomDirectoryModal = (props) => { const CustomDirectoryModal = (props) => {
...@@ -49,6 +52,9 @@ const CustomDirectoryModal = (props) => { ...@@ -49,6 +52,9 @@ const CustomDirectoryModal = (props) => {
const getAllElementsThenGetCurrentDirectory = () => { const getAllElementsThenGetCurrentDirectory = () => {
dispatch({ dispatch({
type: 'assetmanage.listCustomElements', type: 'assetmanage.listCustomElements',
payload: {
dataAssetType: getAssetType(reference)
},
callback: elements => { callback: elements => {
setData(elements||[]); setData(elements||[]);
setFilterData((elements||[]).filter(item => (item.name||'').indexOf(keyword)!==-1)); setFilterData((elements||[]).filter(item => (item.name||'').indexOf(keyword)!==-1));
...@@ -81,11 +87,15 @@ const CustomDirectoryModal = (props) => { ...@@ -81,11 +87,15 @@ const CustomDirectoryModal = (props) => {
let url = 'assetmanage.previewTreeByCustomElements'; let url = 'assetmanage.previewTreeByCustomElements';
let payload = { let payload = {
data: checkedValues data: checkedValues,
params: {
dataAssetType: getAssetType(reference)
}
} }
if (reference===AssetManageReference) { if (reference===ResourceManageReference || reference===AssetManageReference) {
payload.params = { payload.params = {
...payload.params,
resourceTypes: (form.getFieldValue('resourceTypes')||[]).join(',') resourceTypes: (form.getFieldValue('resourceTypes')||[]).join(',')
}; };
} }
...@@ -94,6 +104,7 @@ const CustomDirectoryModal = (props) => { ...@@ -94,6 +104,7 @@ const CustomDirectoryModal = (props) => {
url = 'assetmanage.previewTreeByCustomElementsAndResourceType'; url = 'assetmanage.previewTreeByCustomElementsAndResourceType';
payload.params = { payload.params = {
...payload.params,
resourceType: (reference===ResourceBrowseReference)?'resource':'dataAsset', resourceType: (reference===ResourceBrowseReference)?'resource':'dataAsset',
} }
} }
...@@ -155,7 +166,10 @@ const CustomDirectoryModal = (props) => { ...@@ -155,7 +166,10 @@ const CustomDirectoryModal = (props) => {
let payload = { let payload = {
data: checkedValues, data: checkedValues,
params: row params: {
...row,
dataAssetType: getAssetType(reference)
}
} }
if (action === 'edit') { if (action === 'edit') {
...@@ -164,10 +178,10 @@ const CustomDirectoryModal = (props) => { ...@@ -164,10 +178,10 @@ const CustomDirectoryModal = (props) => {
let url = 'assetmanage.saveTreeByCustomElements'; let url = 'assetmanage.saveTreeByCustomElements';
if (reference===AssetManageReference) { if (reference===ResourceManageReference || reference===AssetManageReference) {
payload.params = { payload.params = {
...payload.params, ...payload.params,
resourceTypes: (row.resourceTypes||[]).join(',') resourceTypes: (row.resourceTypes||[]).join(','),
} }
} }
...@@ -304,7 +318,7 @@ const CustomDirectoryModal = (props) => { ...@@ -304,7 +318,7 @@ const CustomDirectoryModal = (props) => {
<Input /> <Input />
</Form.Item> </Form.Item>
{ {
reference===AssetManageReference && <Form.Item (reference===ResourceManageReference) && <Form.Item
label='数据范围' label='数据范围'
name='resourceTypes' name='resourceTypes'
> >
...@@ -317,6 +331,20 @@ const CustomDirectoryModal = (props) => { ...@@ -317,6 +331,20 @@ const CustomDirectoryModal = (props) => {
</Select> </Select>
</Form.Item> </Form.Item>
} }
{
(reference===AssetManageReference) && <Form.Item
label='数据范围'
name='resourceTypes'
>
<Select mode="multiple" allowClear>
{
assetTypes.map((item,index) => {
return <Select.Option key={item.key}>{item.name}</Select.Option>
})
}
</Select>
</Form.Item>
}
<Form.Item <Form.Item
label='描述或原因' label='描述或原因'
name='desc' name='desc'
......
...@@ -3,7 +3,7 @@ import { Row, Col, Checkbox, Typography, Button, Switch, Modal } from 'antd'; ...@@ -3,7 +3,7 @@ import { Row, Col, Checkbox, Typography, Button, Switch, Modal } from 'antd';
import { dispatch } from '../../../../model'; import { dispatch } from '../../../../model';
import './FilterElementModal.less'; import './FilterElementModal.less';
import { getAssetRange } from '../../../../util'; import { getAssetRange, getAssetType } from '../../../../util';
//type //type
//global 全局设置 //global 全局设置
...@@ -27,21 +27,31 @@ const FilterElementModal = (props) => { ...@@ -27,21 +27,31 @@ const FilterElementModal = (props) => {
}, [visible]); }, [visible]);
const getAllFilterElementIdsThenGetAllElements = () => { const getAllFilterElementIdsThenGetAllElements = () => {
let url = '';
if (type === 'global') { if (type === 'global') {
url = 'assetmanage.listFilterElementIdsConfig'; dispatch({
type: 'assetmanage.listFilterElementIdsConfig',
payload: {
params: {
dataAssetType: getAssetType(reference)
}
},
callback: data => {
setSelectedKeys(data||[]);
getAllElements();
}
})
} else { } else {
url = 'assetmanage.listFilterElementIds'; dispatch({
type: 'assetmanage.listFilterElementIds',
payload: {
dataAssetType: getAssetType(reference)
},
callback: data => {
setSelectedKeys(data||[]);
getAllElements();
}
})
} }
dispatch({
type: url,
callback: data => {
setSelectedKeys(data||[]);
getAllElements();
}
})
} }
const getAllElements = () => { const getAllElements = () => {
...@@ -57,7 +67,8 @@ const FilterElementModal = (props) => { ...@@ -57,7 +67,8 @@ const FilterElementModal = (props) => {
type: url, type: url,
payload: { payload: {
params: { params: {
range: getAssetRange(reference) range: getAssetRange(reference),
dataAssetType: getAssetType(reference)
} }
}, },
callback: data => { callback: data => {
...@@ -129,7 +140,10 @@ const FilterElementModal = (props) => { ...@@ -129,7 +140,10 @@ const FilterElementModal = (props) => {
dispatch({ dispatch({
type: url, type: url,
payload: { payload: {
data: selectedKeys data: selectedKeys,
params: {
dataAssetType: getAssetType(reference)
}
}, },
callback: () => { callback: () => {
reset(); reset();
......
...@@ -3,10 +3,11 @@ import { Button, Upload, Drawer, Table, Pagination, Divider, Form } from 'antd'; ...@@ -3,10 +3,11 @@ import { Button, Upload, Drawer, Table, Pagination, Divider, Form } from 'antd';
import { UploadOutlined, DownloadOutlined } from '@ant-design/icons'; import { UploadOutlined, DownloadOutlined } from '@ant-design/icons';
import { dispatch } from '../../../../model'; import { dispatch } from '../../../../model';
import { showMessage, formatDate } from '../../../../util'; import { showMessage, formatDate, getAssetType } from '../../../../util';
import { AssetManageReference } from '../../../../util/constant';
const ImportAssetDrawer = (props) => { const ImportAssetDrawer = (props) => {
const { onCancel, onSuccess, visible, nodeId } = props; const { onCancel, onSuccess, visible, nodeId, reference = AssetManageReference } = props;
const [ fileList, setFileList ] = useState([]); const [ fileList, setFileList ] = useState([]);
const [ confirmLoading, setConfirmLoading ] = useState(false); const [ confirmLoading, setConfirmLoading ] = useState(false);
...@@ -76,7 +77,7 @@ const ImportAssetDrawer = (props) => { ...@@ -76,7 +77,7 @@ const ImportAssetDrawer = (props) => {
}, [visible]) }, [visible])
const downloadTemplate = () => { const downloadTemplate = () => {
window.open("/api/dataassetmanager/dataAssetApi/getImportTemplate"); window.open(`/api/dataassetmanager/dataAssetApi/getImportTemplate?dataAssetType=${getAssetType(reference)}`);
} }
const getLogs = (p = 1, s = 20) => { const getLogs = (p = 1, s = 20) => {
...@@ -84,6 +85,7 @@ const ImportAssetDrawer = (props) => { ...@@ -84,6 +85,7 @@ const ImportAssetDrawer = (props) => {
dispatch({ dispatch({
type: 'assetmanage.importLogs', type: 'assetmanage.importLogs',
payload: { payload: {
dataAssetType: getAssetType(reference),
page: p, page: p,
pageSize: s pageSize: s
}, },
...@@ -134,7 +136,8 @@ const ImportAssetDrawer = (props) => { ...@@ -134,7 +136,8 @@ const ImportAssetDrawer = (props) => {
dispatch({ dispatch({
type: 'assetmanage.getDirectoryById', type: 'assetmanage.getDirectoryById',
payload: { payload: {
dirId: nodeId dirId: nodeId,
dataAssetType: getAssetType(reference)
}, },
callback: data => { callback: data => {
console.log('path', data.path); console.log('path', data.path);
......
...@@ -3,10 +3,11 @@ import { Modal, Form, Upload, Button, Radio, Space } from 'antd'; ...@@ -3,10 +3,11 @@ import { Modal, Form, Upload, Button, Radio, Space } from 'antd';
import { UploadOutlined } from '@ant-design/icons'; import { UploadOutlined } from '@ant-design/icons';
import { dispatch } from '../../../../model'; import { dispatch } from '../../../../model';
import { showNotifaction } from '../../../../util'; import { getAssetType, showNotifaction } from '../../../../util';
import { AssetManageReference } from '../../../../util/constant';
const ImportDirectory = (props) => { const ImportDirectory = (props) => {
const { visible, onCancel, dirId } = props; const { visible, onCancel, dirId, reference = AssetManageReference } = props;
const [ uploading, setUploading ] = useState(false); const [ uploading, setUploading ] = useState(false);
const [ fileList, setFileList ] = useState([]); const [ fileList, setFileList ] = useState([]);
const [ form ] = Form.useForm(); const [ form ] = Form.useForm();
...@@ -32,7 +33,8 @@ const ImportDirectory = (props) => { ...@@ -32,7 +33,8 @@ const ImportDirectory = (props) => {
dispatch({ dispatch({
type: 'assetmanage.getDirectoryById', type: 'assetmanage.getDirectoryById',
payload: { payload: {
dirId dirId,
dataAssetType: getAssetType(reference)
}, },
callback: data => { callback: data => {
setDir(data); setDir(data);
...@@ -55,7 +57,8 @@ const ImportDirectory = (props) => { ...@@ -55,7 +57,8 @@ const ImportDirectory = (props) => {
if (row.type === 'root') { if (row.type === 'root') {
payload = { payload = {
params: { params: {
ignoreRepeatPath ignoreRepeatPath,
dataAssetType: getAssetType(reference)
}, },
fileList: fileList, fileList: fileList,
}; };
...@@ -63,7 +66,8 @@ const ImportDirectory = (props) => { ...@@ -63,7 +66,8 @@ const ImportDirectory = (props) => {
payload = { payload = {
params: { params: {
ignoreRepeatPath, ignoreRepeatPath,
parentPath: dir.path parentPath: dir.path,
dataAssetType: getAssetType(reference)
}, },
fileList: fileList fileList: fileList
}; };
......
...@@ -3,10 +3,11 @@ import { Button, Upload, Modal } from 'antd'; ...@@ -3,10 +3,11 @@ import { Button, Upload, Modal } from 'antd';
import { DownloadOutlined, UploadOutlined } from '@ant-design/icons'; import { DownloadOutlined, UploadOutlined } from '@ant-design/icons';
import { dispatchLatest } from '../../../../model'; import { dispatchLatest } from '../../../../model';
import { showMessage } from '../../../../util'; import { getAssetType, showMessage } from '../../../../util';
import { AssetManageReference } from '../../../../util/constant';
const ImportElement = (props) => { const ImportElement = (props) => {
const { onCancel, visible } = props; const { onCancel, visible, type = AssetManageReference } = props;
const [ fileList, setFileList ] = useState([]); const [ fileList, setFileList ] = useState([]);
const [ confirmLoading, setConfirmLoading ] = useState(false); const [ confirmLoading, setConfirmLoading ] = useState(false);
...@@ -42,7 +43,12 @@ const ImportElement = (props) => { ...@@ -42,7 +43,12 @@ const ImportElement = (props) => {
setConfirmLoading(true); setConfirmLoading(true);
dispatchLatest({ dispatchLatest({
type: 'assetmanage.importElement', type: 'assetmanage.importElement',
payload: { fileList }, payload: {
params: {
dataAssetType: getAssetType(type)
},
fileList,
},
callback: data => { callback: data => {
setConfirmLoading(false); setConfirmLoading(false);
reset(); reset();
......
import React from 'react';
import { Button } from 'antd';
import { SettingFilled } from '@ant-design/icons';
import { highlightSearchContentByTerms } from '../../../../util';
import { AppContext } from '../../../../App';
const MetadataInfo = ({ value = '', config = true, terms = [] }) => {
let metadata = {};
try {
metadata = JSON.parse(value);
} catch(error) {
metadata = value;
}
return (
<AppContext.Consumer>
{
value => <div className='flex'>
{
(typeof metadata==='string') ? <span style={{ marginRight: 5 }}>{highlightSearchContentByTerms(metadata||'', terms)}</span> : <div className='flex' style={{ flexDirection: 'column' }}>
<a onClick={() => {
value?.setGlobalState && value?.setGlobalState({
message: 'data-govern-show-metadata-message',
data: metadata
})
}} style={{ marginRight: 5, marginTop: config?5:0 }}>{highlightSearchContentByTerms(metadata?.tableName||'',terms)}
</a>
{
(metadata?.columnItems||[]).map((item, index) => {
let _content = '';
if ((item.metadataColumnCnName||'')!=='') {
_content = item.metadataColumnCnName + '/';
}
_content += item.metadataColumnName||'';
return <span key={index}>{highlightSearchContentByTerms(_content,terms)}</span>
})
}
</div>
}
{
config && <Button icon={<SettingFilled />} onClick={() => {
value?.setGlobalState && value?.setGlobalState({
message: 'data-govern-show-metadata-list-message',
data: (typeof metadata==='string') ? {} : metadata
})
}} />
}
</div>
}
</AppContext.Consumer>
);
}
export default MetadataInfo;
\ No newline at end of file
import React, { useEffect, useState } from 'react';
import { Modal, Form, Input, Space, Button, Radio, Select } from 'antd';
import { dispatch } from '../../../../model';
import { showMessage } from '../../../../util';
const resourceTypes = [
{ key: 'innerSource', name: '内部资源' },
{ key: 'outerSource', name: '外部资源' },
{ key: 'dataAsset', name: '资产' },
{ key: 'custom', name: '自定义' },
]
const UpdateDirectoryModal = (props) => {
const { visible, onCancel, dirId, action } = props;
const [ form ] = Form.useForm();
const [ dir, setDir ] = useState(null);
const [ confirmLoading, setConfirmLoading ] = useState(false);
const [ isThemeAdd, setIsThemeAdd ] = useState(false);
useEffect(() => {
if (visible) {
setDir(null);
form.resetFields();
if ((dirId||'')!=='') {
getDirectory();
}
}
//eslint-disable-next-line react-hooks/exhaustive-deps
}, [ visible ])
const getDirectory = () => {
setDir(null);
dispatch({
type: 'assetmanage.getDirectoryById',
payload: {
dirId
},
callback: data => {
setDir(data);
if (action !== 'add') {
form.setFieldsValue({ code: data?.code, name: data?.name||'', desc: data?.desc||'', remarks: data?.remarks||'', resourceType: data?.resourceType });
}
}
})
}
const onOk = async () => {
try {
const row = await form.validateFields();
setConfirmLoading(true);
let payload = {
data: {
code: row.code,
name: row.name,
desc: row.desc,
remarks: row.remarks,
resourceType: row.resourceType
}
};
if (action === 'add') {
if (row.type === 'directory') {
if (dir === null) {
showMessage('warn', '资产目录节点信息正在加载中...');
return;
}
payload = { ...payload, params: {
parentPath: dir.path||''
}};
} else {
payload.data.resourceType = row.resourceType;
}
} else {
if (dir === null) {
showMessage('warn', '资产目录节点信息正在加载中...');
return;
}
payload.data = { ...payload.data, ...{ order: dir.order, id: dirId } };
const parentPath = dir.path.substring(0, dir.path.lastIndexOf("/"));;
payload = { ...payload, params: {
parentPath
}};
}
dispatch({
type: 'assetmanage.addOrUpdateDirectory',
payload: payload,
callback: data => {
setConfirmLoading(false);
onCancel && onCancel(true, data?.id||'');
},
error: () => {
setConfirmLoading(false);
}
})
} catch (errInfo) {
console.log('Validate Failed:', errInfo);
}
}
const onReset = () => {
if(action === 'add') {
setIsThemeAdd(false);
form.resetFields();
} else {
if (dir === null) {
showMessage('warn', '资产目录节点信息正在加载中...');
return;
}
form.resetFields();
}
}
const onValuesChange = (changedValues, allValues) => {
if (action==='add') {
if (changedValues.type === 'theme') {
setIsThemeAdd(true);
} else if (changedValues.type === 'directory') {
setIsThemeAdd(false);
}
}
}
const formItemLayout = {
labelCol: {
xs: { span: 24 },
sm: { span: 5 },
},
wrapperCol: {
xs: { span: 24 },
sm: { span: 17 },
},
};
return (
<Modal
forceRender
title={'资产目录信息'}
visible={visible}
width={600}
onCancel={() => { onCancel && onCancel() }}
footer={
<Space>
<Button type="primary" onClick={onOk} loading={confirmLoading}>提交</Button>
<Button onClick={onReset} >重置</Button>
<Button onClick={() => onCancel && onCancel() }>返回</Button>
</Space>
}
>
<Form {...formItemLayout} form={form} onValuesChange={onValuesChange}>
{
action==='add' && <Form.Item
label="类型"
name="type"
rules={[{ required: true, message: '必填项' }]}
>
<Radio.Group>
<Radio value='theme'>栏目</Radio>
<Radio value='directory' disabled={ dirId===null }>目录</Radio>
</Radio.Group>
</Form.Item>
}
{
((action==='add'&&isThemeAdd) || action!=='add') && <Form.Item
label="资产类型"
name="resourceType"
rules={[{ required: false }]}
>
<Select allowClear>
{
resourceTypes.map((item,index) => {
return <Select.Option key={item.key}>{item.name}</Select.Option>
})
}
</Select>
</Form.Item>
}
<Form.Item
label="编号"
name="code"
rules={[{ required: true, message: '必填项' }]}
>
<Input placeholder="请输入编号" />
</Form.Item>
<Form.Item
label="名称"
name="name"
rules={[{ required: true, message: '必填项' }]}
>
<Input placeholder="请输入名称" />
</Form.Item>
{
action !== 'add' && (
<Form.Item
label="路径"
name="path"
>
<span>{ dir ? (dir.path||''):'' }</span>
</Form.Item>
)
}
<Form.Item
label="描述"
name="desc"
>
<Input.TextArea placeholder="请输入描述" autoSize={{ minRows: 4, maxRows: 4 }} />
</Form.Item>
<Form.Item
label="备注"
name="remarks"
>
<Input.TextArea placeholder="请输入备注" autoSize={{ minRows: 4, maxRows: 4 }} />
</Form.Item>
</Form>
</Modal>
);
}
export default UpdateDirectoryModal;
\ No newline at end of file
import React from "react"
import { Modal, Button, Spin, Form, Select } from "antd"
import { dispatch } from '../../../../model'
import { getAssetRange, getAssetType } from '../../../../util'
import { ElementItem } from "./AssetAction"
const FC = (props) => {
const { type, visible, onCancel } = props
const [loading, setLoading] = React.useState(false)
const [waiting, setWaiting] = React.useState(false)
const [elements, setElements] = React.useState()
const basicRef = React.useRef()
React.useEffect(() => {
if (visible) {
getElements()
}
}, [visible])
const getElements = () => {
setLoading(true)
dispatch({
type: 'assetmanage.listElements',
payload: {
params: {
range: getAssetRange(type),
dataAssetType: getAssetType(type)
}
},
callback: data => {
setLoading(false)
setElements((data??[]).filter(item => item.supportBatchEdit==='是'))
},
error: () => {
setLoading(false)
}
})
}
const close = (refresh = false) => {
setLoading(false)
setWaiting(false)
onCancel?.(refresh)
}
const save = async () => {
try {
const rows = await basicRef.current?.validate()
setWaiting(true)
dispatch({
type: 'assetmanage.changeElementValue',
payload: {
params: {
dataAssetType: getAssetType(type),
...rows
}
},
callback: data => {
setWaiting(false)
close(true)
},
error: () => {
setWaiting(false)
}
})
} catch(e) {
}
}
const footer = React.useMemo(() => {
return [
<Button key={'cancel'}
onClick={() => close()}
>取消</Button>,
<Button disabled={waiting} key={'save'} type='primary'
onClick={() => save()}
>确定</Button>
]
}, [close, save, waiting])
return (
<Modal
visible={visible}
footer={footer}
width={520}
bodyStyle={{ padding: '15px 15px 0px 15px', overflowX: 'auto', maxHeight: '80vh' }}
title='属性值变更管理'
centered destroyOnClose
onCancel={() => { close() }}
>
<Spin spinning={loading||waiting} >
<Basic ref={basicRef} type={type} elements={elements} />
</Spin>
</Modal>
)
}
export default FC
export const Basic = React.forwardRef(function ({ type, elements }, ref) {
const [form] = Form.useForm()
const [loadingSources, setLoadingSources] = React.useState(false)
const [sources, setSources] = React.useState()
const [currentElement, setCurrentElement] = React.useState()
React.useImperativeHandle(ref, () => ({
validate: async () => {
return await form.validateFields()
},
}), [form])
const marginBottom = React.useMemo(() => {
return 15
}, [])
const getSources = (elementId) => {
setLoadingSources(true)
dispatch({
type: 'assetmanage.getDistinctValuesByElementId',
payload: {
dataAssetType: getAssetType(type),
elementId
},
callback: data => {
setLoadingSources(false)
setSources(data)
},
error: () => {
setLoadingSources(false)
}
})
}
const onValuesChange = (changedValues, allValues) => {
if (changedValues.hasOwnProperty('elementId')) {
const index = (elements??[]).findIndex(item => item.id === changedValues.elementId)
if (index !== -1) {
setCurrentElement(elements[index])
}
getSources(changedValues.elementId)
}
}
return (
<Form
form={form}
labelCol={{ span: 5 }}
wrapperCol={{ span: 17 }}
autoComplete="off"
onValuesChange={onValuesChange}
>
<Form.Item
label="属性"
name="elementId"
rules={[{ required: true, message: '请选择属性!' }]}
style={{ marginBottom }}
>
<Select placeholder='请选择属性'>
{
(elements??[]).map((item,index) =>
<Select.Option key={item.id} value={item.id}>{item.name}</Select.Option>
)
}
</Select>
</Form.Item>
<Form.Item
label="原属性值"
name="source"
rules={[{ required: true, message: '请选择原属性值!' }]}
style={{ marginBottom }}
>
<Select loading={loadingSources} placeholder='请选择属性'>
{
(sources??[]).map((item,index) =>
<Select.Option key={item} value={item}>{item}</Select.Option>
)
}
</Select>
</Form.Item>
<Form.Item
label="替换属性值"
name="target"
rules={[{ required: true, message: '请选择替换属性值!' }]}
style={{ marginBottom }}
>
<ElementItem type='edit' element={currentElement} />
</Form.Item>
</Form>
)
})
\ No newline at end of file
import React from "react"
import { Modal, Button, Spin, Row, Col, Tree, Tooltip, Typography, Input } from "antd"
import { debounceTime, Subject } from 'rxjs'
import produce from 'immer'
import { dispatch } from '../../../model'
import { getAssetRange, getAssetType, inputWidth, isSzseEnv, showMessage } from "../../../util"
import { AssetManageReference, ResourceManageReference } from "../../../util/constant"
import Table from '../../../util/Component/Table'
import { defaultPage, usePage } from '../../../util/hooks/page'
import { MetadataColumn } from "../AssetResourceManage/table"
import './add-resources.less'
const FC = (props) => {
const { visible, item, onCancel, onOk } = props
const [waiting, setWaiting] = React.useState(false)
const basicRef = React.useRef()
const close = (refresh = false) => {
setWaiting(false)
onCancel?.(refresh)
}
const save = () => {
const selectedRows = basicRef.current?.selectedRows
if ((selectedRows??[]).length === 0) {
showMessage('warn', '请先选择资源')
return
}
onOk?.(selectedRows)
if (item) {
setWaiting(true)
dispatch({
type: 'assetmanage.dataAssetAddResources',
payload: {
params: {
dataAssetId: item?.id,
resourceIds: (selectedRows??[]).map(item => item.id).toString()
}
},
callback: data => {
setWaiting(false)
close(true)
},
error: () => {
setWaiting(false)
}
})
} else {
close()
}
}
const footer = React.useMemo(() => {
return [
<Button key={'cancel'}
onClick={() => close()}
>取消</Button>,
<Button key={'save'} type='primary'
onClick={() => save()}
>确定</Button>
]
}, [close, save])
return (
<Modal
className='add-resources'
visible={visible}
footer={footer}
width='90%'
bodyStyle={{ padding: '15px 15px 0px 15px', overflowX: 'auto', height: '80vh' }}
title='新增资源'
centered destroyOnClose
onCancel={() => { close() }}
>
<Spin spinning={waiting} >
<Basic ref={basicRef} />
</Spin>
</Modal>
)
}
export default FC
export const Basic = React.forwardRef(function ({ }, ref) {
const [args, setArgs] = React.useState(() => ({
params: {
page: defaultPage.pageNum,
size: defaultPage.pageSize,
keyword: undefined,
},
}))
const [loadingTreeData, setLoadingTreeData] = React.useState(false)
const [treeData, setTreeData] = React.useState()
const [node, setNode] = React.useState()
const [loading, setLoading] = React.useState(false)
const [loadingElements, setLoadingElements] = React.useState(false)
const [elements, setElements] = React.useState()
const [data, setData] = React.useState()
const [total, setTotal] = React.useState()
const [selectedRows, setSelectedRows] = React.useState([])
const $keyword = React.useMemo(() => new Subject(), [])
const [keyword, setKeyword] = React.useState()
const [page, setPage] = usePage()
const [expandedKeys, setExpandedKeys] = React.useState([])
const [autoExpandParent, setAutoExpandParent] = React.useState(false)
React.useImperativeHandle(ref, () => ({
selectedRows
}), [selectedRows])
React.useEffect(() => {
getTreeData()
}, [])
React.useEffect(() => {
if (node) {
setSelectedRows([])
setPageAndArgs({ pageNum: 1, pageSize: page.pageSize })
}
}, [node])
const treeData1 = React.useMemo(() => {
if (treeData) {
const newTreeData = produce(treeData, draft => {
const setNode = (g) => {
g.key = g.nodeId
g.title = g.text
g.children?.forEach((child) => {
setNode(child)
})
}
draft.forEach((child) => {
setNode(child)
})
})
return newTreeData
}
return undefined
}, [treeData])
const columns = React.useMemo(() => {
const newColumns = []
let index = 0
for (const element of elements??[]) {
const col = {
title: element.name,
dataIndex: `element${++index}`,
ellipsis: true,
width: 120,
render: (text, record) => {
return (
<Tooltip title={text}>
<Typography.Text ellipsis={true}>
{text}
</Typography.Text>
</Tooltip>
);
}
}
if (element.name === '编号') {
col.width = 60
} else if (element.name === '中文名称') {
col.width = isSzseEnv ? 230 : 160
} else if (element.name === '英文名称') {
col.width = isSzseEnv ? 224 : 160
} else if (element.name === '资源路径') {
col.render = (text, record) => {
return (
<Tooltip title={text}>
<Typography.Text ellipsis={true}>
<a onClick={()=>{
let event = new Event('storage')
event.key = 'assetDirChangeEvent'
event.dirId = record.dirId
window?.dispatchEvent(event)
}}>
{text}
</a>
</Typography.Text>
</Tooltip>
)
}
} else if (element.metadataItem === '是') {
col.width = isSzseEnv ? 250 : 120
col.render = (text, record) => <MetadataColumn data={text} />
}
newColumns.push(col)
}
return newColumns
}, [elements])
const tableData = React.useMemo(() => {
const newTableData = []
for (const item of (data??[])) {
const newAsset = {...item}
let index = 0
for (const elementValue of (item.elementValues??[])) {
newAsset[`element${++index}`] = elementValue
}
newTableData.push(newAsset)
}
return newTableData
}, [data])
const setArgsAndPage = React.useCallback((params) => {
// 设置查询参数时将分页置为1
setPage(prevpg => {
setArgs((prev) => {
const newparams = params ? { ...prev.params, ...params } : undefined
return { params: { ...newparams, page: 1, size: prevpg.pageSize } }
})
return ({ ...prevpg, pageNum: 1 })
})
//eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
const setPageAndArgs = React.useCallback((page) => {
setPage(prev => ({ ...prev, ...page }))
setArgs((prev) => {
return { params: { ...prev.params, page: page.pageNum, size: page.pageSize } }
})
//eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
React.useEffect(() => {
const $$keyword = $keyword.pipe(debounceTime(1000)).subscribe((keyword) => {
setArgsAndPage({ keyword })
})
return () => {
$$keyword.unsubscribe()
}
//eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
React.useEffect(() => {
if (node?.nodeId) {
getElements()
getAssets()
}
}, [args])
const getTreeData = () => {
setLoadingTreeData(true)
dispatch({
type: 'assetmanage.queryResourceManageTree',
callback: data => {
setLoadingTreeData(false)
setTreeData(data)
if ((data??[]).length > 0) {
const firstNode = data[0]
setNode(firstNode)
setExpandedKeys([firstNode.nodeId])
setAutoExpandParent(true)
}
},
error: () => {
setLoadingTreeData(false)
}
})
}
const getElements = () => {
setLoadingElements(true)
dispatch({
type: 'assetmanage.listFilterElements',
payload: {
range: getAssetRange(ResourceManageReference),
dataAssetType: getAssetType(ResourceManageReference)
},
callback: data => {
setLoadingElements(false)
setElements(data)
},
error: () => {
setLoadingElements(false)
}
})
}
const getAssets = () => {
setLoading(true)
dispatch({
type: 'assetmanage.listDataResourcesByPage',
payload: {
data: [],
params: {
dirId: node?.nodeId,
pageNum: args.params.page,
pageSize: args.params.size,
keyword: args.params.keyword,
range: getAssetRange(ResourceManageReference),
}
},
callback: data => {
setLoading(false)
setData(data?.data)
setTotal(data?.total)
},
error: () => {
setLoading(false)
}
})
}
const onTreeExpand = (expandedKeys) => {
setExpandedKeys(expandedKeys)
setAutoExpandParent(false)
}
const onTreeSelect = (selectedKeys, { selectedNodes }) => {
if (selectedKeys.length === 0 || selectedNodes.length === 0) {
return
}
setNode(selectedNodes[0])
}
return (
<Row>
<Col span={4}>
<Spin spinning={loadingTreeData}>
<Tree
className='tree'
showLine
showIcon={false}
autoExpandParent={autoExpandParent}
treeData={treeData1}
selectedKeys={node?[node.nodeId]:[]}
expandedKeys={expandedKeys}
onSelect={onTreeSelect}
onExpand={onTreeExpand}
/>
</Spin>
</Col>
<Col span={20}>
<div className='flex'
style={{
padding: '0px 0px 15px',
alignItems: 'center',
justifyContent: 'end',
}}>
<Input size="middle"
placeholder="资源要素值搜索"
value={keyword}
bordered={true} allowClear
onChange={(e) => {
const keyword = e.target.value
setKeyword(keyword)
$keyword.next((keyword??'').trim())
}}
style={{ width: inputWidth }}
/>
</div>
<Table
maxHeight='calc(80vh - 155px)'
loading={loading||loadingElements}
columns={columns}
dataSource={tableData}
pageSize={page.pageSize} pageNum={page.pageNum} total={total??0}
onPaginate={(page, pageSize) => {
setSelectedRows([])
setPageAndArgs({ pageNum: page, pageSize })
}}
rowSelection={{
selectedRowKeys: (selectedRows??[]).map(item => item.id),
onChange: (selectedRowKeys, selectedRows) => {
setSelectedRows(selectedRows)
},
}}
/>
</Col>
</Row>
)
})
\ No newline at end of file
.add-resources {
.tree {
height: calc(80vh - 30px);
overflow: auto;
}
}
\ No newline at end of file
//资产项
import React from 'react'
import { Button, Tooltip } from 'antd'
import { AppContext } from '../../../App'
import { highlightSearchContentByTerms, IsArr } from '../../../util'
import { MetadataColumnTooltipTitle } from '../AssetResourceManage/table'
import AddResources from './add-resources'
import { dispatch } from '../../../model'
const FC = ({ value, onChange, readonly = true, terms = [] }) => {
const [decodeData, setDecodeData] = React.useState()
const [addResourcesParams, setAddResourcesParams] = React.useState({
visible: false,
})
const app = React.useContext(AppContext)
React.useMemo(() => {
if (value) {
try {
setDecodeData(JSON.parse(value))
} catch(error) {
setDecodeData(value)
}
} else {
setDecodeData()
}
}, [value])
const onAddResourcesOk = (resources) => {
dispatch({
type: 'assetmanage.getMetadataItems',
payload: {
params: {
resourceIds: (resources??[]).map(item => item.id).toString()
}
},
callback: data => {
if (typeof decodeData === 'string') {
onChange?.(JSON.stringify(data??[]))
} else {
const newData = [...decodeData??[]]
for (const item of data??[]) {
const _index = (newData??[]).findIndex(_item => item.metadataId ===_item.metadataId)
if (_index === -1) {
newData.push(item)
}
}
onChange?.(JSON.stringify(newData))
}
},
})
}
return (
<div>
{
!readonly && <Button onClick={() => {
setAddResourcesParams({
visible: true
})
}}>添加资源</Button>
}
{
(typeof decodeData === 'string') && <div className='flex' style={{ alignItems: 'center', marginTop: readonly?0:5 }}>
<span style={{ marginRight: 5 }}>
{highlightSearchContentByTerms(decodeData, terms)}
</span>
{
!readonly && <Button size='small' onClick={() => {
onChange?.()
}}>删除</Button>
}
</div>
}
{
IsArr(decodeData) && <div>
{
//资源可能没有资源项
(decodeData??[]).filter(item => item.metadataId).map((item, index) => <div key={index} className='flex' style={{ alignItems: 'center', marginTop: readonly?0:5 }}>
<span>
<Tooltip
overlayClassName='tooltip-common'
title={<MetadataColumnTooltipTitle data={[item]} />}
>
<a onClick={() => {
app?.setGlobalState?.({
message: 'data-govern-show-metadata-message',
data: item
})
}}
style={{ marginRight: 5 }}
>
{highlightSearchContentByTerms(item.enName, terms)}
</a>
</Tooltip>
</span>
{
!readonly && <Button size='small' onClick={() => {
const newData = [...decodeData]
const _index = (newData??[]).findIndex(_item => item.metadataId ===_item.metadataId)
if (_index !== -1) {
newData.splice(_index, 1)
onChange?.(JSON.stringify(newData))
}
}}>删除</Button>
}
</div>
)
}
</div>
}
<AddResources
{...addResourcesParams}
onCancel={(refresh) => {
setAddResourcesParams({
visible: false
})
}}
onOk={onAddResourcesOk}
/>
</div>
)
}
export default FC
\ No newline at end of file
import React from 'react'
import { Button, Modal, Spin, Tree, AutoComplete } from 'antd'
import { dispatch } from '../../../model'
import produce from 'immer'
import { highlightSearchContentByTerms, showMessage, showNotifaction } from '../../../util'
import { generateList } from '../AssetResourceManage/tree'
const FC = (props) => {
const { visible, items, onCancel } = props
const [waiting, setWaiting] = React.useState(false)
const basicRef = React.useRef()
const close = (refresh = false) => {
setWaiting(false)
onCancel?.(refresh)
}
const save = () => {
const checkedKeys = basicRef.current?.getCheckedKeys()
if ((checkedKeys??[]).length === 0) {
showMessage('warn', '请先选择资源目录')
return
}
setWaiting(true)
dispatch({
type: 'assetmanage.loadDataAssets',
payload: {
params: {
dirId: checkedKeys.join(","),
},
data: (items??[]).map(item => item.id)
},
callback: data => {
setWaiting(false)
if (data?.message) {
showNotifaction('提示', data?.message, 5)
}
onCancel?.(true)
},
error: () => {
setWaiting(false)
}
})
}
const footer = React.useMemo(() => {
return [
<Button key={'cancel'}
onClick={() => close()}
>取消</Button>,
<Button key={'save'} type='primary'
disabled={waiting}
onClick={() => save()}
>保存</Button>
]
}, [close, save, waiting])
return (
<Modal
visible={visible}
footer={footer}
width='400px'
bodyStyle={{ padding: '15px 15px 0px 15px', overflowX: 'auto', maxHeight: '80vh', height: 500 }}
title='变更目录'
centered destroyOnClose
onCancel={() => { close() }}
>
<Spin spinning={waiting}>
<Basic ref={basicRef} items={items} />
</Spin>
</Modal>
)
}
export default FC
export const Basic = React.forwardRef(function ({ items, onCheck }, ref) {
const [data, setData] = React.useState()
const [dataList, setDataList] = React.useState()
const [loading, setLoading] = React.useState(false)
const [checkedKeys, setCheckedKeys] = React.useState()
const [expandedKeys, setExpandedKeys] = React.useState([])
const [autoExpandParent, setAutoExpandParent] = React.useState(false)
const [options, setOptions] = React.useState()
const [keyword, setKeyword] = React.useState()
React.useImperativeHandle(ref, () => ({
getCheckedKeys: () => checkedKeys
}), [checkedKeys])
React.useEffect(() => {
getTreeData()
getAssetPaths()
}, [])
const treeData = React.useMemo(() => {
if (data) {
const newTreeData = produce(data, draft => {
const setNode = (g) => {
g.key = g.nodeId
g.title = g.text
g.children?.forEach((child) => {
setNode(child)
})
}
draft.forEach((child) => {
setNode(child)
})
})
return newTreeData
}
return []
}, [data])
const getAssetPaths = () => {
if ((items??[]).length > 0) {
dispatch({
type: 'assetmanage.getAssetPaths',
payload: {
dataAssetId: items[0].id,
},
callback: data => {
setCheckedKeys((data??[]).map(item => item.dirId))
}
})
}
}
const getTreeData = () => {
setLoading(true)
dispatch({
type: 'assetmanage.queryDataAssetManageTree',
callback: data => {
setLoading(false)
const newData = (data??[]).filter(item => item.resourceType !== 'custom')
setData(newData)
if ((newData??[]).length > 0) {
const newDataList = []
generateList(newData, newDataList)
setDataList(newDataList)
const firstNode = newData[0]
setExpandedKeys([firstNode.nodeId])
setAutoExpandParent(true)
}
},
error: () => {
setLoading(false)
}
})
}
const onTreeExpand = (expandedKeys) => {
setExpandedKeys(expandedKeys)
setAutoExpandParent(false)
}
const onTreeCheck = (values, e) => {
//同一主题下只能挂载一个目录
if (e.node?.level === 1) {
showMessage('warn', '栏目不允许勾选')
return
}
const newCheckedKeys = values.checked??[]
if (e.checked) {
const index = (dataList??[]).findIndex(item => item.nodeId === e.node?.key)
if (index !== -1) {
const currentSubjectNodeId = dataList[index].subjectNodeId
const filterChecktedKeys = newCheckedKeys.filter(key => {
if (key !== e.node?.key) {
const index = (dataList??[]).findIndex(item => item.nodeId === key)
if (index !== -1) {
return (dataList[index].subjectNodeId !== currentSubjectNodeId)
} else {
return false
}
}
return true
})
setCheckedKeys(filterChecktedKeys)
onCheck?.(filterChecktedKeys)
}
} else {
setCheckedKeys(newCheckedKeys)
onCheck?.(newCheckedKeys)
}
}
const onAutoCompleteSearch = (searchText) => {
setKeyword(searchText)
setOptions(!searchText?[]:(dataList||[]).filter(item => item.value.indexOf(searchText)!==-1))
}
const onAutoCompleteSelect = (value, option) => {
const paths = value.split('/')
setKeyword(paths[paths.length-1])
const newExpandedKeys = [...expandedKeys, option.key]
setExpandedKeys(Array.from(new Set(newExpandedKeys)))
setAutoExpandParent(true)
}
return (
<Spin spinning={loading}>
<AutoComplete
allowClear
value={keyword}
style={{ marginBottom: 10, width: '100%' }}
onSelect={onAutoCompleteSelect}
onSearch={onAutoCompleteSearch}
onClear={() => {
setKeyword()
}}
>
{
(options||[]).map((item, index) => {
return (
<AutoComplete.Option key={item.key} value={item.value}>
<div style={{ whiteSpace: 'normal' }}>
{highlightSearchContentByTerms(item.value, [keyword])}
</div>
</AutoComplete.Option>
);
})
}
</AutoComplete>
<Tree
checkable
checkStrictly
showLine
showIcon={false}
treeData={treeData}
autoExpandParent={autoExpandParent}
expandedKeys={expandedKeys}
checkedKeys={checkedKeys}
onExpand={onTreeExpand}
onCheck={onTreeCheck}
/>
</Spin>
)
})
\ No newline at end of file
import React, { useState } from 'react'; import React from "react"
import classNames from 'classnames'; import classNames from 'classnames'
import { Form } from 'antd'; import { ResizableBox } from 'react-resizable'
import { CaretLeftOutlined, CaretRightOutlined } from '@ant-design/icons'; import { Form } from "antd"
import { ResizableBox } from 'react-resizable'; import { CaretLeftOutlined, CaretRightOutlined } from '@ant-design/icons'
import AssetTree from './Component/AssetManageTree'; import Tree from './tree'
import AssetDirectory from './Component/AssetDirectory'; import Separate from './Component/Separate'
import AssetTable from './Component/AssetTable'; import NodeDetail from './Component/AssetDirectory'
import AssetAction from './Component/AssetAction'; import AssetList from './table'
import Separate from './Component/Separate'; import AssetDetail from './Component/AssetAction'
import { AssetManageReference } from "../../../util/constant"
import { AssetManageReference } from '../../../util/constant';
import './index.less'
import './index.less';
const FC = (props) => {
const AssetManage = (props) => { const [collapseTree, setCollapseTree] = React.useState(false)
const [node, setNode] = React.useState()
const [ nodeId, setNodeId ] = useState(''); const [asset, setAsset] = React.useState()
const [ nodeType, setNodeType ] = useState(''); const [assetListFullScreen, setAssetListFullScreen] = React.useState(false)
const [ nodeLevel, setNodeLevel ] = useState(null); const [directoryChanged, setDirectoryChanged] = React.useState(false)
const [ assetParams, setAssetParams ] = useState({ assetId: '', assetDirId: '' })
const [ expandTree, setExpandTree ] = useState(true); const [form] = Form.useForm()
const [ assetFullScreen, setAssetFullScreen ] = useState(false);
const onTreeClick = (value) => {
const [ assetCount, setAssetCount ] = useState(0); setDirectoryChanged(!directoryChanged)
const [ directoryChanged, setDirectoryChanged ] = useState(false); setNode(value)
const [ elementsChanged, setElementsChanged ] = useState(false);
const [ assetActionChanged, setAssetActionChanged ] = useState(false);
const [ form ] = Form.useForm();
const { assetId, assetDirId } = assetParams;
const onTreeSelect = (value, type, level) => {
setNodeId(value||'');
setNodeType(type);
setNodeLevel(level);
} }
const onTableSelect = (id, did) => { const onAssetListClick = (value) => {
setAssetParams({ assetId: id, assetDirId: did }); setAsset(value)
} }
const treeToggleClick = () => { const onAssetListFullScreenChange = (value) => {
setExpandTree(!expandTree); setAssetListFullScreen(value)
} }
const onElementsChange = () => { const treeToggleClick = () => {
setElementsChanged(!elementsChanged); setCollapseTree(!collapseTree)
}
const onDirectoryChange = () => {
setDirectoryChanged(!directoryChanged);
}
const onAssetActionChange = () => {
setAssetActionChanged(!assetActionChanged);
}
const onAssetCountChange = (count) => {
setAssetCount(count);
}
const onFullScreenChange = (value) => {
setAssetFullScreen(value);
} }
const classes = classNames('asset-manage', { const rootClasses = classNames('asset-manage', {
'asset-manage-collapse': !expandTree 'asset-manage-collapse': collapseTree
}); })
const middleClasses = classNames('middle', { const middleClasses = classNames('middle', {
'middle-fullscreen': assetFullScreen 'middle-fullscreen': assetListFullScreen
}); })
return ( return (
<div className={classes}> <div className={rootClasses}>
<ResizableBox <ResizableBox
className='left' className='left'
width={230} width={230}
height={Infinity} height={Infinity}
axis='x' axis='x'
minConstraints={[230, Infinity]} maxConstraints={[Infinity, Infinity]} minConstraints={[230, Infinity]}
maxConstraints={[Infinity, Infinity]}
> >
<AssetTree onSelect={onTreeSelect} onDirectoryChange={onDirectoryChange} {...props} /> <Tree onClick={onTreeClick} {...props} />
</ResizableBox> </ResizableBox>
{ {
expandTree && <Separate width={15} /> !collapseTree && <Separate width={15} />
} }
<div className={middleClasses}> <div className={middleClasses}>
<AssetDirectory id={nodeId} assetCount={assetCount} directoryChanged={directoryChanged} onElementsChange={onElementsChange} /> <NodeDetail
reference={AssetManageReference}
id={node?.nodeId}
assetCount={node?.dataAssetAndSubDirCount}
directoryChanged={directoryChanged}
/>
<Separate height={15} /> <Separate height={15} />
<AssetTable nodeId={nodeId} nodeType={nodeType} nodeLevel={nodeLevel} reference={AssetManageReference} elementsChanged={elementsChanged} assetActionChanged={assetActionChanged} onSelect={onTableSelect} onCountChange={onAssetCountChange} onFullScreenChange={onFullScreenChange} {...props} /> <AssetList
node={node}
onClick={onAssetListClick}
onFullScreenChange={onAssetListFullScreenChange}
{...props}
/>
<div className='tree-toggle' onClick={treeToggleClick}> <div className='tree-toggle' onClick={treeToggleClick}>
{ expandTree ? <CaretLeftOutlined /> : <CaretRightOutlined /> } { !collapseTree ? <CaretLeftOutlined /> : <CaretRightOutlined /> }
</div> </div>
</div> </div>
<Separate width='15px' /> <Separate width='15px' />
<div className='right'> <div className='right'>
<AssetAction form={form} id={assetId} dirId={assetDirId} action='detail' reference={AssetManageReference} onChange={onAssetActionChange} /> <AssetDetail
form={form}
id={asset?.id}
dirId={asset?.dirId}
action='detail'
reference={AssetManageReference}
/>
</div> </div>
</div> </div>
) )
} }
export default AssetManage; export default FC
\ No newline at end of file \ No newline at end of file
import React from 'react'
import classNames from 'classnames'
import { Tooltip, Typography, Space, Dropdown, Button, Menu, Checkbox, Input, Select, Modal, Table as AntdTable } from 'antd'
import ResizeObserver from 'rc-resize-observer'
import { debounceTime, Subject } from 'rxjs'
import { DownOutlined, UpOutlined } from "@ant-design/icons"
import LocalStorage from 'local-storage'
import { defaultPage, usePage } from '../../../util/hooks/page'
import Table from '../../../util/Component/Table'
import { dispatch } from '../../../model'
import { getAssetRange, getAssetType, getQueryParam, IsArr, isSzseEnv, showMessage, showNotifaction } from '../../../util'
import { AssetManageReference } from '../../../util/constant'
import PermissionButton from '../../../util/Component/PermissionButton'
import PermissionMenuItem from '../../../util/Component/PermissionMenuItem'
import { FullScreenSvg, CancelFullScreenSvg } from './Component/AssetSvg'
import AddAsset from './Component/AddAssetModel'
import AddResources from './add-resources'
import ImportAsset from './Component/ImportAssetDrawer'
import ChangeCatalog from './change-catalog'
import AssetDelete from './Component/AssetDeleteModal'
import FilterElement from './Component/FilterElementModal'
import FilterElementValue from '../AssetResourceManage/filter-element-value'
import SelectBatchEditElements from '../AssetResourceManage/select-batch-edit-elements'
import { AssetDirectorySubject } from './Component/AssetDirectory'
import { AssetActionSubject } from './Component/AssetAction'
import TagCell from '../Model/Component/tag-help'
import './table.less'
import { MetadataColumn } from '../AssetResourceManage/table'
const operationMap = {
addResource: '添加资源',
publish: '发布',
}
const FC = (props) => {
const { node, onClick, onFullScreenChange } = props
const [args, setArgs] = React.useState(() => ({
params: {
page: defaultPage.pageNum,
size: defaultPage.pageSize,
catalogType: 'currentRecursive',
keyword: undefined,
publishStatus: undefined,
elementValueFilters: []
},
}))
const [fullScreen, setFullScreen] = React.useState(false)
const [compact, setCompact] = React.useState(false)
const [loading, setLoading] = React.useState(false)
const [loadingElements, setLoadingElements] = React.useState(false)
const [columns, setColumns] = React.useState()
const [elements, setElements] = React.useState()
const [data, setData] = React.useState()
const [total, setTotal] = React.useState()
const [selectedRows, setSelectedRows] = React.useState([])
const [row, setRow] = React.useState()
const [rightRow, setRightRow] = React.useState()
const [permissions, setPermissions] = React.useState([])
const [loadingPublishStatus, setLoadingPublishStatus] = React.useState(false)
const [publishStatus, setPublishStatus] = React.useState()
const $keyword = React.useMemo(() => new Subject(), [])
const [keyword, setKeyword] = React.useState()
const [searchType, setSearchType] = React.useState('keyword')
const [resoureTagMap, setResourceTagMap] = React.useState()
const [addAssetParams, setAddAssetParams] = React.useState({
visible: false,
reference: undefined,
nodeId: undefined
})
const [addResourcesParams, setAddResourcesParams] = React.useState({
visible: false,
item: undefined
})
const [importAssetParams, setImportAssetParams] = React.useState({
visible: false,
reference: undefined,
nodeId: undefined
})
const [changeCatalogParams, setChangeCatalogParams] = React.useState({
visible: false,
items: undefined
})
const [assetDeleteParams, setAssetDeleteParams] = React.useState({
visible: false
})
const [filterElementParams, setFilterElementParams] = React.useState({
visible: false,
type: undefined,
reference: undefined
})
const [filterElementValueParams, setFilterElementValueParams] = React.useState({
visible: false,
type: undefined,
defaultValue: undefined,
})
const [selectBatchEditElementsParams, setSelectBatchEditElementsParams] = React.useState({
visible: false,
type: undefined
})
const [page, setPage] = usePage()
const [modal, contextHolder] = Modal.useModal()
const locationIdRef = React.useRef(getQueryParam('id', props?.location?.search))
const locationDidRef = React.useRef(getQueryParam('did', props?.location?.search))
const setArgsAndPage = React.useCallback((params) => {
// 设置查询参数时将分页置为1
setPage(prevpg => {
setArgs((prev) => {
const newparams = params ? { ...prev.params, ...params } : undefined
return { params: { ...newparams, page: 1, size: prevpg.pageSize } }
})
return ({ ...prevpg, pageNum: 1 })
})
//eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
const setPageAndArgs = React.useCallback((page) => {
setPage(prev => ({ ...prev, ...page }))
setArgs((prev) => {
return { params: { ...prev.params, page: page.pageNum, size: page.pageSize } }
})
//eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
const storageChange = (e) => {
if (e.key === 'editAssetsChange') {
getAssets()
} else if (e.key === 'assetRelationOnClickEvent') {
locationIdRef.current = e.relation?.dataAssetId
locationDidRef.current = e.relation?.dirId
} else if (e.key === 'assetPathOnClickEvent') {
locationIdRef.current = e.id
locationDidRef.current = e.dirId
}
}
React.useEffect(() => {
getElements()
getPublishStatus()
const $$keyword = $keyword.pipe(debounceTime(1000)).subscribe((keyword) => {
setArgsAndPage({ keyword })
})
const $$assetDirectorySubject = AssetDirectorySubject.subscribe((props) => {
if (props.type === 'element-change') {
getElements()
getAssets()
} else if (props.type === 'element-value-change') {
getAssets()
}
})
const $$assetActionSubject = AssetActionSubject.subscribe((props) => {
if (props.type === 'asset-change') {
getAssets()
}
})
window?.addEventListener("storage", storageChange)
return () => {
$$keyword.unsubscribe()
$$assetDirectorySubject.unsubscribe()
$$assetActionSubject.unsubscribe()
window?.removeEventListener("storage", storageChange)
}
//eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
React.useEffect(() => {
if (node) {
getPermissions()
setRow()
setSelectedRows([])
if (locationIdRef.current && locationDidRef.current) {
getDataAssetLocation()
} else {
setPageAndArgs({ pageNum: 1, pageSize: page.pageSize })
}
}
}, [node])
React.useEffect(() => {
if (node?.nodeId) {
getAssets()
}
}, [args])
React.useEffect(() => {
if (data) {
getResourceTag()
}
}, [data])
const [addAble, batchEditAble, importAble, exportAble, changeDirectoryAble, deleteAble] = React.useMemo(() => {
let [_addAble, _batchEditAble, _importAble, _exportAble, _changeDiretoryAble, _deleteAble] = [false, false, false, false, false, false]
_addAble = (permissions??[]).findIndex(item => item==='add') !== -1
if ((selectedRows??[]).length === 0) {
_importAble = (permissions??[]).findIndex(item => item==='import') !== -1
_exportAble = (permissions??[]).findIndex(item => item==='export') !== -1
} else {
let [allowImport, allowExport] = [true, true]
for (const row of selectedRows??[]) {
const importIndex = (row.allowButtons??[]).findIndex(item => item==='import')
const exportIndex = (row.allowButtons??[]).findIndex(item => item==='export')
if (importIndex === -1) {
allowImport = false
}
if (exportIndex === -1) {
allowExport = false
}
}
_importAble = allowImport
_exportAble = allowExport
}
let [allowBatchEdit, allowChangeDirectory, allowDelete] = [true, true, true]
for (const row of selectedRows??[]) {
const batchEditIndex = (row.allowButtons??[]).findIndex(item => item==='batchEdit')
const changeDirecotoryIndex = (row.allowButtons??[]).findIndex(item => item==='changeDir')
const deleteIndex = (row.allowButtons??[]).findIndex(item => item==='delete')
if (batchEditIndex === -1) {
allowBatchEdit = false
}
if (changeDirecotoryIndex === -1) {
allowChangeDirectory = false
}
if (deleteIndex === -1) {
allowDelete = false
}
}
_batchEditAble = allowBatchEdit
_changeDiretoryAble = allowChangeDirectory
_deleteAble = allowDelete
return [_addAble, _batchEditAble, _importAble, _exportAble, _changeDiretoryAble, _deleteAble]
}, [permissions, selectedRows])
const menuData = React.useMemo(() => {
const newMenuData = []
for (const key of rightRow?.allowButtons??[]) {
if (operationMap[`${key}`]) {
newMenuData.push(operationMap[`${key}`])
}
}
return newMenuData
}, [rightRow])
const notElementCol = [
{
title: '资产状态',
dataIndex: 'publishStatus',
ellipsis: true,
width: 120,
render: (_, record) => record.assetExtraAttribute?.publishStatus
},
{
title: '标签',
dataIndex: 'tag',
width: 360,
className: 'table-tag-cell',
render: (_, record) => <TagCell
id={record.id}
did={record.dirId}
type='dataAsset'
tags={resoureTagMap?.[`${record.id}`]}
/>
}
]
React.useEffect(() => {
const newColumns = []
let index = 0
for (const element of elements??[]) {
const col = {
title: element.name,
dataIndex: `element${++index}`,
ellipsis: true,
width: 120,
render: (text, record) => {
return (
<Tooltip title={text}>
<Typography.Text ellipsis={true}>
{text}
</Typography.Text>
</Tooltip>
);
}
}
if (element.name === '编号') {
col.width = 60
} else if (element.name === '中文名称') {
col.width = isSzseEnv ? 230 : 160
} else if (element.name === '英文名称') {
col.width = isSzseEnv ? 224 : 160
} else if (element.name === '资产路径') {
col.render = (text, record) => {
return (
<Tooltip title={text}>
<Typography.Text ellipsis={true}>
<a onClick={()=>{
let event = new Event('storage')
event.key = 'assetDirChangeEvent'
event.dirId = record.dirId
window?.dispatchEvent(event)
}}>
{text}
</a>
</Typography.Text>
</Tooltip>
)
}
} else if (element.metadataItem === '是') {
col.width = isSzseEnv ? 250 : 120
col.render = (text, record) => <MetadataColumn data={text} />
}
newColumns.push(col)
}
setColumns([...newColumns, ...notElementCol])
}, [elements, resoureTagMap])
const tableData = React.useMemo(() => {
const newTableData = []
for (const item of (data??[])) {
const newAsset = {...item}
let index = 0
for (const elementValue of (item.elementValues??[])) {
newAsset[`element${++index}`] = elementValue
}
newTableData.push(newAsset)
}
return newTableData
}, [data])
const tableMaxHeight = React.useMemo(() => {
return fullScreen ? 'calc(100vh - 209px - 72px)' : 'calc(100vh - 209px - 123px - 15px - 72px)'
}, [fullScreen])
const getPermissions = () => {
dispatch({
type: 'assetmanage.getPrivilegeByRangeAndDirId',
payload: {
range: getAssetRange(AssetManageReference),
optionId: node?.nodeId
},
callback: data => {
setPermissions(data)
}
})
}
const getPublishStatus = () => {
setLoadingPublishStatus(true)
dispatch({
type: 'assetmanage.getAssetPublishStatus',
callback: data => {
setLoadingPublishStatus(false)
setPublishStatus(data)
},
error: () => {
setLoadingPublishStatus(false)
}
})
}
const getElements = () => {
setLoadingElements(true)
dispatch({
type: 'assetmanage.listFilterElements',
payload: {
range: getAssetRange(AssetManageReference),
dataAssetType: getAssetType(AssetManageReference)
},
callback: data => {
setLoadingElements(false)
setElements(data)
},
error: () => {
setLoadingElements(false)
}
})
}
const getAssets = () => {
setLoading(true)
let [recursive, dirId] = [true, node?.nodeId]
if (args.params.catalogType === 'current') {
recursive = false
}
if (args.params.catalogType === 'fullSearch') {
dirId = ''
}
dispatch({
type: 'assetmanage.listDataAssetsByPage',
payload: {
data: args.params.elementValueFilters??[],
params: {
dirId,
pageNum: args.params.page,
pageSize: args.params.size,
keyword: args.params.keyword,
range: getAssetRange(AssetManageReference),
publishStatus: args.params.publishStatus,
recursive,
isCustomDir: (node?.resourceType==='custom'||node?.type==='custom')
}
},
callback: data => {
setLoading(false)
setData(data?.data)
setTotal(data?.total)
if (locationIdRef.current) {
const index = (data?.data??[]).findIndex(item => item.id === locationIdRef.current)
if (index !== -1) {
onClick?.(data?.data[index])
setRow(data?.data[index])
} else {
if ((data?.data??[]).length > 0) {
onClick?.(data?.data[0])
setRow(data?.data[0])
} else {
onClick?.(undefined)
setRow()
}
}
setTimeout(() => {
var anchor = document.getElementById(`${locationIdRef.current}`)
anchor?.scrollIntoView()
locationIdRef.current = null
locationDidRef.current = null
}, 500)
} else {
setRow(prevRow => {
if (!prevRow || (data?.data??[]).findIndex(item => item.id === prevRow.id) === -1) {
if ((data?.data??[]).length > 0) {
onClick?.(data?.data[0])
return data?.data[0]
} else {
onClick?.(undefined)
return undefined
}
}
return prevRow
})
}
},
error: () => {
setLoading(false)
locationIdRef.current = null
locationDidRef.current = null
}
})
}
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 getDataAssetLocation = () => {
setLoading(true)
dispatch({
type: 'assetmanage.getDataAssetLocation',
payload: {
dataAssetId: locationIdRef.current,
dirId: locationDidRef.current,
},
callback: data => {
const newPageNum = parseInt(data.offset/page.pageSize + ((data.offset%page.pageSize===0)?0:1))
setPageAndArgs({ ...page, pageNum: newPageNum })
},
error: () => {
setLoading(false)
locationIdRef.current = null
locationDidRef.current = null
setPageAndArgs({ ...page, pageNum: 1 })
}
})
}
const onAddClick = () => {
setAddAssetParams({
visible: true,
reference: AssetManageReference,
nodeId: node?.nodeId
})
}
const onBatchEditClick = () => {
setSelectBatchEditElementsParams({
visible: true,
type: AssetManageReference
})
}
const onImportClick = () => {
setImportAssetParams({
visible: true,
reference: AssetManageReference,
nodeId: node?.nodeId
})
}
const onExportClick = () => {
if ((selectedRows??[]).length === 0) {
modal.confirm({
title: '提示',
content: '是否导出选中目录下的所有资产?',
onOk: () => {
window.open(`/api/dataassetmanager/dataAssetApi/exportByDataAssetIds?dirId=${node?.nodeId}&recursive=${args.params.catalogType === 'currentRecursive'}&dataAsset=${getAssetRange(AssetManageReference)}`);
}
})
} else {
window.open(`/api/dataassetmanager/dataAssetApi/exportByDataAssetIds?dataAssetIds=${selectedRows.map(item => item.id).join(',')}&dataAsset=${getAssetRange(AssetManageReference)}`);
}
}
const onChangeDirectoryClick = () => {
setChangeCatalogParams({
visible: true,
items: selectedRows
})
}
const onDeleteClick = () => {
setAssetDeleteParams({
visible: true
})
}
const onFilterElementClick = () => {
setFilterElementParams({
visible: true,
type: 'admin',
reference: AssetManageReference
})
}
const deleteAssets = () => {
setAssetDeleteParams({
visible: false
})
dispatch({
type: 'assetmanage.unloadDataAssets',
payload: {
data: (selectedRows??[]).map(item => {
return { dataAssetId: item.id, dirId: item.dirId }
})
},
callback: () => {
showMessage("success","删除成功")
getAssets()
setSelectedRows([])
}
})
}
const deleteAssetsFromAllDirs = () => {
setAssetDeleteParams({
visible: false
})
dispatch({
type: 'assetmanage.unloadDataAssetsFromAllDirs',
payload: {
data: (selectedRows??[]).map(item => item.id)
},
callback: () => {
showMessage("success","删除成功")
getAssets()
setSelectedRows([])
}
})
}
const onRightAddResourcesClick = () => {
setAddResourcesParams({
visible: true,
item: rightRow
})
}
const onRightPublishClick = () => {
modal.confirm({
title: '提示',
content: '确定发起资产的发布流程吗?',
onOk: () => {
}
})
}
const onMenuClick = ({ key }) => {
if (key === 'batchEdit') {
onBatchEditClick()
} else if (key === 'import') {
onImportClick()
} else if (key === 'export') {
onExportClick()
} else if (key === 'changeDir') {
onChangeDirectoryClick()
} else if (key === 'delete') {
onDeleteClick()
} else if (key === 'colConfig') {
onFilterElementClick()
}
}
const onRightMenuItemClick = (key, record) => {
if (key === '添加资源') {
onRightAddResourcesClick()
} else if (key === '发布') {
onRightPublishClick()
}
}
const moreMenu = (
<Menu onClick={onMenuClick}>
{
compact && <React.Fragment>
<PermissionMenuItem
key='batchEdit'
defaultPermission={batchEditAble}
disabled={(selectedRows??[]).length===0}
tip={(selectedRows??[]).length===0?'请先选择资产':''}
>
<div className='text-center'>
批量编辑
</div>
</PermissionMenuItem>
</React.Fragment>
}
{
(node?.resourceType !== 'custom' && node?.type !== 'custom') && <PermissionMenuItem key='import' defaultPermission={importAble}>
<div className='text-center'>
导入
</div>
</PermissionMenuItem>
}
<PermissionMenuItem key='export' defaultPermission={exportAble}>
<div className='text-center'>
导出
</div>
</PermissionMenuItem>
<PermissionMenuItem
key='changeDir'
defaultPermission={changeDirectoryAble}
disabled={(selectedRows??[]).length===0}
tip={(selectedRows??[]).length===0?'请先选择资产':''}
>
<div className='text-center'>
变更目录
</div>
</PermissionMenuItem>
{
(node?.resourceType !== 'custom' && node?.type !== 'custom') && <PermissionMenuItem
key='delete'
defaultPermission={deleteAble}
disabled={(selectedRows??[]).length===0}
tip={(selectedRows??[]).length===0?'请先选择资产':''}
>
<div className='text-center'>
删除
</div>
</PermissionMenuItem>
}
<Menu.Item key='colConfig'>
<div className='text-center'>
可见列设置
</div>
</Menu.Item>
</Menu>
)
const classes = classNames('asset-list', {
'asset-list-fullscreen': fullScreen,
});
return (
<div className={classes}>
<div
className='flex'
style={{
padding: '20px 15px 5px',
alignItems: 'center',
justifyContent: 'space-between',
}}
>
<Space className='mr-3' style={{ flex: 0, paddingBottom: 15 }}>
{
(node?.level !== 1 && node?.resourceType !== 'custom' && node?.type !== 'custom') && <PermissionButton
defaultPermission={addAble}
onClick={onAddClick}
>
新增
</PermissionButton>
}
{
compact ? <Dropdown overlay={moreMenu} placement="bottomCenter">
<Button>更多</Button>
</Dropdown> : <React.Fragment>
<PermissionButton
defaultPermission={batchEditAble}
onClick={onBatchEditClick}
disabled={(selectedRows??[]).length===0}
tip={(selectedRows??[]).length===0?'请先选择资产':''}
>
批量编辑
</PermissionButton>
<Dropdown overlay={moreMenu} placement="bottomCenter">
<Button>更多</Button>
</Dropdown>
</React.Fragment>
}
</Space>
<div className='flex' style={{ flex: 1, overflow: 'auto', paddingBottom: 15 }}>
<div style={{ flex: 1 }}></div>
<Space style={{ flex: 0 }}>
<Select
value={args.params.catalogType}
onChange={(value) => {
setArgsAndPage({ catalogType: value })
}}
style={{ width: 170 }}
>
<Select.Option value='currentRecursive'>
当前目录(含子目录)
</Select.Option>
<Select.Option value='current'>
当前目录
</Select.Option>
<Select.Option value='fullSearch'>
全部数据
</Select.Option>
</Select>
<Select
placeholder='资产状态'
onChange={(value) => {
setArgsAndPage({ publishStatus: value })
}}
style={{ width: 100 }}
allowClear
>
{
(publishStatus??[]).map((item, index) => {
return <Select.Option key={index} value={item.type}>
{item.desc}
</Select.Option>
})
}
</Select>
<Input size="middle"
addonBefore={
<Select
value={searchType}
onChange={(value) => {
setSearchType(value)
if (value === 'attribute') {
setFilterElementValueParams({
visible: true,
type: AssetManageReference,
defaultValue: args.params.elementValueFilters
})
}
}}
style={{ width: 120 }}
>
<Select.Option value='keyword'>
关键字搜索
</Select.Option>
<Select.Option value='attribute'>
属性值过滤
</Select.Option>
</Select>
}
placeholder="资产要素值/任务"
value={keyword}
bordered={true} allowClear
onChange={(e) => {
const keyword = e.target.value
setKeyword(keyword)
$keyword.next((keyword??'').trim())
}}
style={{ width: 270 }}
/>
<Tooltip title={fullScreen?'取消全屏':'全屏'}>
<Button
onClick={() => {
setFullScreen(!fullScreen)
onFullScreenChange?.(!fullScreen)
}}
icon={fullScreen ?<CancelFullScreenSvg style={{ width: 20, height: 20, marginTop: 2 }} /> : <FullScreenSvg style={{ width: 20, height: 20, marginTop: 2 }} />}
type='text'
/>
</Tooltip>
</Space>
</div>
</div>
<div className='px-3'>
<ResizeObserver
onResize={({ width }) => {
setCompact(width < 1030)
}}
>
<Table
maxHeight={tableMaxHeight}
loading={loading||loadingElements}
columns={columns}
dataSource={tableData}
pageSize={page.pageSize} pageNum={page.pageNum} total={total??0}
onRowClick={(event, value) => {
setRow(value)
onClick?.(value)
}}
rowClassName={(record, index) => (record?.id === row?.id) ? 'yy-table-select-row' : ''}
onPaginate={(page, pageSize) => {
setRow()
setSelectedRows([])
setPageAndArgs({ pageNum: page, pageSize })
}}
rowSelection={{
selectedRowKeys: (selectedRows??[]).map(item => item.id),
onChange: (selectedRowKeys, selectedRows) => {
setSelectedRows(selectedRows)
},
}}
shouldRowContextMenu={(record) => {
setRightRow(record)
let allowContextMenu = false
for (const key of (record.allowButtons??[])) {
if (operationMap[`${key}`]) {
allowContextMenu = true
}
}
return allowContextMenu
}}
menuData={menuData}
menuPermissions={menuData}
onMenuItemClick={onRightMenuItemClick}
/>
</ResizeObserver>
</div>
<AddAsset
{...addAssetParams}
onCancel={(refresh = false) => {
setAddAssetParams({
visible: false,
reference: undefined,
nodeId: undefined
})
refresh && getAssets()
}}
/>
<AddResources
{...addResourcesParams}
onCancel={(refresh) => {
setAddResourcesParams({
visible: false,
item: undefined
})
refresh && getAssets()
}}
/>
<ImportAsset
{...importAssetParams}
onCancel={() => {
setImportAssetParams({
visible: false,
reference: undefined,
nodeId: undefined
})
}}
onSuccess={(tip = '') => {
getAssets()
if (tip) {
showNotifaction('导入提示', tip, 5)
}
}}
/>
<AssetDelete
{...assetDeleteParams}
onCancel={() => {
setAssetDeleteParams({
visible: false
})
}}
onDelete={deleteAssets}
onDeleteAll={deleteAssetsFromAllDirs}
/>
<FilterElement
{...filterElementParams}
onCancel={(refresh = false) => {
setFilterElementParams({
visible: false,
type: undefined,
reference: undefined
})
if (refresh) {
getElements()
getAssets()
}
}}
/>
<FilterElementValue
{...filterElementValueParams}
onCancel={(refresh, val) => {
setFilterElementValueParams({
visible: false,
type: undefined,
defaultValue: undefined,
})
setSearchType('keyword')
if (refresh) {
setArgsAndPage({ elementValueFilters: val })
}
}}
/>
<SelectBatchEditElements
{...selectBatchEditElementsParams}
onCancel={(refresh, value) => {
setSelectBatchEditElementsParams({
visible: false,
type: undefined,
})
if (refresh) {
setTimeout(() => {
window.open(`/data-govern/edit-assets?ids=${(selectedRows??[]).map(item => item.id).toString()}&elementIds=${(value??[]).toString()}`)
}, 300)
}
}}
/>
<ChangeCatalog
{...changeCatalogParams}
onCancel={(refresh = false) => {
setChangeCatalogParams({
visible: false,
items: undefined
})
refresh && getAssets()
}}
/>
{contextHolder}
</div>
)
}
export default FC
\ No newline at end of file
@import '../../../../variables.less'; @import '../../../variables.less';
.asset-list { .asset-list {
background-color: #fff; background-color: #fff;
......
import React, { useEffect, useMemo, useState } from 'react';
import { Tooltip, Spin, Modal, Button, AutoComplete, Menu, Dropdown } from 'antd'
import { PlusOutlined, ReloadOutlined, ImportOutlined, ExportOutlined, SettingOutlined } from '@ant-design/icons'
import produce from 'immer'
import { dispatch } from '../../../model'
import { showMessage, highlightSearchContentByTerms, showNotifaction, getAssetType, getAssetRange } from '../../../util'
import Tree from '../../../util/Component/Tree'
import PermissionButton from '../../../util/Component/PermissionButton'
import ImportNode from './Component/ImportDirectory'
import CustomNode from './Component/CustomDirectoryModal'
import { AssetManageReference } from '../../../util/constant'
import { generateList, updateTreeData } from '../AssetResourceManage/tree'
import UpdateNode from './update-node'
import '../AssetResourceManage/tree.less'
const FC = (props) => {
const {onClick} = props
const [loading, setLoading] = useState(false)
const [data, setData] = useState(undefined)
const [dataList, setDataList] = useState()
const [keyword, setKeyword] = useState()
const [options, setOptions] = useState()
const [expandedKeys, setExpandedKeys] = useState([])
const [loadedKeys, setLoadedKeys] = useState([])
const [selectedKey, setSelectedKey] = useState('')
const [rightSelectedNode, setRightSelectedNode] = useState()
const [autoExpandParent, setAutoExpandParent] = useState(false)
const [updateNodeParam, setUpdateNodeParam] = useState({
visible: false,
action: undefined,
id: undefined,
})
const [importNodeParam, setImportNodeParam] = useState({
visible: false,
reference: undefined,
dirId: undefined
})
const [customNodeParam, setCustomNodeParam] = useState({
visible: false,
reference: undefined
})
const [permissions, setPermissions] = useState()
const [modal, contextHolder] = Modal.useModal()
const storageChange = (e) => {
if (e.key === 'assetDirChangeEvent' || e.key === 'assetPathOnClickEvent') {
if (e.dirId) {
setSelectedKey(e.dirId)
resetSelectedNodeLogic()
}
} else if (e.key === 'assetRelationOnClickEvent') {
setSelectedKey(e.relation?.dirId)
resetSelectedNodeLogic()
}
}
useEffect(() => {
getTreeData()
getPermissions()
window?.addEventListener("storage", storageChange)
return () => {
window?.removeEventListener("storage", storageChange)
}
//eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
const havePermission = useMemo(() => {
return (permissions??[]).findIndex(item => item === 'dirManage') !== -1
}, [permissions])
const treeData = useMemo(() => {
if (data) {
const newTreeData = produce(data, draft => {
const setNode = (g) => {
g.key = g.nodeId
g.title = (
<span className={(g.level===1)?'title-color':'text-color'}>
{g.text}
{
//自定义类型栏目不统计资产数
(g.level === 1 && (g.resourceType === 'custom') || g.type === 'custom') ? null : <span>{` (${g.dataAssetAndSubDirCount})`}</span>
}
</span>
)
g.children?.forEach((child) => {
setNode(child)
})
if (g.resourceType !== 'custom' && g.type !== 'custom' && (g.children??[]).length === 0) {
g.isLeaf = true
}
if ((g.resourceType === 'custom'||g.type === 'custom') && (g.children??[]).length === 0) {
g.children = null
}
}
draft.forEach((child) => {
setNode(child)
})
})
return newTreeData
}
return []
}, [data])
const menuData = useMemo(() => {
if (rightSelectedNode) {
if (rightSelectedNode.level === 1) {
return [
{ id: 'edit', title: '编辑栏目' },
{ id: 'up', title: '上移栏目' },
{ id: 'down', title: '下移栏目' },
{ id: 'delete', title: '删除栏目' },
]
} else if (rightSelectedNode.resourceType === 'custom' || rightSelectedNode.type === 'custom') {
return [
{ id: 'up', title: '上移目录' },
{ id: 'down', title: '下移目录' },
{ id: 'delete', title: '删除目录' },
]
} else {
return [
{ id: 'edit', title: '编辑目录' },
{ id: 'up', title: '上移目录' },
{ id: 'down', title: '下移目录' },
{ id: 'delete', title: '删除目录' },
]
}
}
return []
}, [rightSelectedNode])
const resetSelectedNodeLogic = () => {
setData(prevData => {
setSelectedKey(prevSelectedKey => {
if ((prevData??[]).length === 0) return;
const firstNode = prevData[0]
if (!prevSelectedKey) {
setExpandedKeys(Array.from(new Set([...expandedKeys, firstNode.nodeId])))
setAutoExpandParent(true)
onTreeSelect([firstNode.nodeId], { selectedNodes: [firstNode]})
} else {
setDataList(prevDataList => {
const filterNodes = (prevDataList??[]).filter(item => item.nodeId === prevSelectedKey)
if (filterNodes?.length > 0) {
const newExpandedKeys = [...expandedKeys, prevSelectedKey]
setExpandedKeys(Array.from(new Set(newExpandedKeys)))
setAutoExpandParent(true)
onTreeSelect([prevSelectedKey], {selectedNodes: filterNodes})
}
return prevDataList
})
}
return prevSelectedKey
})
return prevData
})
}
const getTreeData = (resetSelectedNode = true) => {
setLoading(true)
setSelectedKey(prevSelectedKey => {
dispatch({
type: 'assetmanage.queryDataAssetManageTree',
callback: data => {
setLoading(false)
setLoadedKeys([])
setData(data)
if ((data??[]).length > 0) {
const newDataList = []
generateList(data, newDataList)
setDataList(newDataList)
if (resetSelectedNode) {
resetSelectedNodeLogic()
}
}
},
error: () => {
setLoading(false)
}
})
return prevSelectedKey
})
}
const getPermissions = () => {
dispatch({
type: 'assetmanage.getPrivilegeByRange',
payload: {
range: getAssetRange(AssetManageReference),
},
callback: data => {
setPermissions(data);
}
})
}
const onAddClick = () => {
setUpdateNodeParam({
visible: true,
action: 'add',
id: selectedKey
})
}
const onImportClick = () => {
setImportNodeParam({
visible: true,
reference: AssetManageReference,
dirId: selectedKey
})
}
const onCustomClick = () => {
setCustomNodeParam({
visible: true,
reference: AssetManageReference,
})
}
const onDeleteNodeClick = (node) => {
modal.confirm({
title: '提示',
content: '节点下可能包含资产信息,删除后将把资产从该目录上移除,确定继续吗?',
onOk: () => {
dispatch({
type: 'assetmanage.deleteDirectory',
payload: {data: [ rightSelectedNode?.nodeId ]},
callback: () => {
showMessage("success","删除成功")
if (rightSelectedNode?.nodeId === selectedKey) {
setSelectedKey()
getTreeData()
} else {
getTreeData(false)
}
}
})
}
})
}
const onMoveNodeClick = (steps) => {
setLoading(true);
dispatch({
type: 'assetmanage.upDownDirectory',
payload: {
params: {
dirId: rightSelectedNode?.nodeId,
steps
}
},
callback: () => {
showMessage('success', (steps===1)?'上移目录成功':'下移目录成功');
getTreeData(false)
},
error: () => {
setLoading(false)
}
});
}
const onRefreshClick = () => {
getTreeData(false)
}
const onTreeExpand = (expandedKeys) => {
setExpandedKeys(expandedKeys)
setAutoExpandParent(false)
}
const onTreeSelect = (selectedKeys, { selectedNodes }) => {
if (selectedKeys.length === 0 || selectedNodes.length === 0) {
return
}
setSelectedKey(selectedKeys[0])
onClick && onClick(selectedNodes[0])
}
const onUpdateNodeCancel = (refresh = false, nodeId = undefined) => {
setUpdateNodeParam(prevParam => {
if (refresh) {
if (prevParam.action !== 'add' && nodeId) {
setSelectedKey(nodeId)
getTreeData()
} else {
getTreeData(false)
}
}
return {
visible: false,
action: undefined,
id: undefined
}
})
}
const onImportNodeCancel = (refresh = false, resetSelectedNode = false) => {
setImportNodeParam({
visible: false,
reference: undefined,
dirId: undefined
})
refresh && getTreeData(resetSelectedNode)
}
const onCustomNodeCancel = (refresh = false) => {
setCustomNodeParam({
visible: false,
reference: undefined,
})
if (refresh) {
getTreeData(false)
}
}
const onMenuItemClick = (key, node) => {
console.log('key', key)
if (key === 'delete') {
onDeleteNodeClick(node)
} else if (key === 'edit') {
setUpdateNodeParam({
visible: true,
action: 'edit',
id: rightSelectedNode?.nodeId
})
} else if (key === 'up') {
onMoveNodeClick(1)
} else if (key === 'down') {
onMoveNodeClick(-1)
}
}
const onLoadData = ({ key, children }) =>
new Promise((resolve) => {
if (children) {
resolve()
return
}
setLoadedKeys([...loadedKeys, key])
dispatch({
type: 'assetmanage.getDirectoryChild',
payload: {
parentId: key,
},
callback: (_data) => {
let newData = updateTreeData(data, key, _data??[])
setData(newData)
resolve()
},
error: () => {
resolve()
}
})
})
const onAutoCompleteSearch = (searchText) => {
setKeyword(searchText)
setOptions(!searchText?[]:(dataList||[]).filter(item => item.value.indexOf(searchText)!==-1))
}
const onAutoCompleteSelect = (value, option) => {
const paths = value.split('/')
setKeyword(paths[paths.length-1])
setSelectedKey(option.key)
resetSelectedNodeLogic()
}
const exportMenu = (
<Menu>
<Menu.Item>
<div style={{ textAlign: 'center' }} onClick={() => {
window.open(`/api/dataassetmanager/directoryApi/export?dataAssetType=${getAssetType(AssetManageReference)}`)
}}>
导出所有
</div>
</Menu.Item>
<Menu.Item>
<div style={{ textAlign: 'center' }} onClick={() => {
if(selectedKey){
dispatch({
type: 'assetmanage.getDirectoryById',
payload: {
dirId: selectedKey,
dataAssetType: getAssetType(AssetManageReference)
},
callback: data => {
window.open(`/api/dataassetmanager/directoryApi/export?parentPath=${data.path}`);
}
})
} else {
showMessage("warn","请选择目录")
}
}}>
导出选中目录
</div>
</Menu.Item>
</Menu>
)
return (
<div className='resource-manage-tree'>
<div className='header p-3'>
<PermissionButton
defaultPermission={havePermission}
tip="新增目录"
type="text"
icon={<PlusOutlined />}
onClick={onAddClick}
/>
<Tooltip title="刷新目录">
<Button type="text" icon={<ReloadOutlined />} onClick={onRefreshClick} />
</Tooltip>
<PermissionButton
defaultPermission={havePermission}
tip="导入目录"
type="text"
icon={<ImportOutlined />}
onClick={onImportClick}
/>
<Dropdown overlay={havePermission?exportMenu:<></>} placement="bottomCenter" >
<PermissionButton
defaultPermission={havePermission}
tip="导出目录"
type="text"
icon={<ExportOutlined />}
/>
</Dropdown>
<PermissionButton
defaultPermission={havePermission}
tip="自定义目录"
type="text"
icon={<SettingOutlined />}
onClick={onCustomClick}
/>
</div>
<div className='p-3'>
<Spin spinning={loading}>
<AutoComplete
allowClear
value={keyword}
style={{ marginBottom: 10, width: '100%' }}
onSelect={onAutoCompleteSelect}
onSearch={onAutoCompleteSearch}
onClear={() => {
setKeyword()
}}
>
{
(options||[]).map((item, index) => {
return (
<AutoComplete.Option key={item.key} value={item.value}>
<div style={{ whiteSpace: 'normal' }}>
{highlightSearchContentByTerms(item.value, [keyword])}
</div>
</AutoComplete.Option>
);
})
}
</AutoComplete>
<Tree
className='tree'
showLine
showIcon={false}
autoExpandParent={autoExpandParent}
treeData={treeData}
loadData={onLoadData}
loadedKeys={loadedKeys}
onExpand={onTreeExpand}
onSelect={onTreeSelect}
expandedKeys={expandedKeys}
selectedKeys={[selectedKey]}
shouldRowContextMenu={(node) => {
setRightSelectedNode(node)
return havePermission
}}
menuData={menuData}
onMenuItemClick={onMenuItemClick}
/>
</Spin>
</div>
<UpdateNode
{...updateNodeParam}
onCancel={onUpdateNodeCancel}
/>
<ImportNode
{...importNodeParam}
onCancel={onImportNodeCancel}
/>
<CustomNode
action='add'
{...customNodeParam}
onCancel={onCustomNodeCancel}
/>
{contextHolder}
</div>
)
}
export default FC
\ No newline at end of file
import React from 'react'
import { Modal, Button, Spin, Form, Input, Radio, Select, Space, TreeSelect, Row, Col, Checkbox } from 'antd'
import { QuestionCircleOutlined } from '@ant-design/icons'
import { dispatch } from '../../../model'
import { getAssetType, getValidString, showMessage } from '../../../util'
import { AssetManageReference } from '../../../util/constant'
const resourceTypes = [
{ key: 'dataAsset', name: '资产' },
{ key: 'custom', name: '自定义' },
]
const FC = (props) => {
const { id, action, visible, onCancel } = props
const [loading, setLoading] = React.useState(false)
const [waiting, setWaiting] = React.useState(false)
const [node, setNode] = React.useState()
const basicRef = React.useRef()
React.useEffect(() => {
if (visible && id) {
getDetail()
}
}, [visible, id])
const getDetail = () => {
setLoading(true)
dispatch({
type: 'assetmanage.getDirectoryById',
payload: {
dirId: id
},
callback: data => {
setLoading(false)
setNode(data)
},
error: () => {
setLoading(false)
}
})
}
const close = (refresh = false) => {
setLoading(false)
setWaiting(false)
setNode()
onCancel?.(refresh)
}
const save = async() => {
try {
const rows = await basicRef.current?.validate()
setWaiting(true)
let parentPath = ''
if (action === 'add') {
if (basicRef.current?.getType === 'child') {
parentPath = node?.path
rows.resourceType = node?.resourceType
}
} else {
parentPath = node?.path.substring(0, node?.path.lastIndexOf("/"))
}
dispatch({
type: 'assetmanage.addOrUpdateDirectory',
payload: {
data: (action === 'add')?rows:{...node??{}, ...rows},
params: {
parentPath,
dataAssetType: getAssetType(AssetManageReference)
}
},
callback: data => {
setWaiting(false)
onCancel?.(true, data?.id)
},
error: () => {
setWaiting(false)
}
})
} catch (e) {
}
}
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='600px'
bodyStyle={{ padding: '15px 15px 0px 15px', overflowX: 'auto', maxHeight: '80vh' }}
title='资产目录信息'
centered destroyOnClose
onCancel={() => { close() }}
>
<Spin spinning={loading||waiting} >
<Basic
ref={basicRef}
node={node}
action={action}
/>
</Spin>
</Modal>
)
}
export default FC
export const Basic = React.forwardRef(function ({ node, action }, ref) {
const [type, setType] = React.useState('root')
const [currentResourceType, setCurrentResourceType] = React.useState()
const [form] = Form.useForm()
React.useImperativeHandle(ref, () => ({
getType: type,
validate: async () => {
return await form.validateFields()
},
}), [type, form])
React.useEffect(() => {
if (node) {
const index = resourceTypes.findIndex(item => item.key === node?.resourceType)
if (index !== -1) {
setCurrentResourceType(resourceTypes[index].name)
}
if (action !== 'add') {
form.setFieldsValue(node)
}
}
}, [action, node])
const allowChangeResourceType = React.useMemo(() => {
return (action==='add' && type==='root')
}, [action, type])
const onValuesChange = (changedValues, allValues) => {
}
return (
<Form
form={form}
labelCol={{ span: 5 }}
wrapperCol={{ span: 17 }}
autoComplete="off"
onValuesChange={onValuesChange}
>
{
action==='add' && <Form.Item
label="类型"
rules={[{ required: true, message: '请选择类型!' }]}
>
<Radio.Group value={type} onChange={(e) => {
setType(e.target.value)
}}>
<Radio value='root'>栏目</Radio>
<Radio value='child' disabled={!node?.id}>目录</Radio>
</Radio.Group>
</Form.Item>
}
<Form.Item
label="资产类型"
name="resourceType"
rules={[{ required: allowChangeResourceType?true:false, message: '请选择资产类型!' }]}
>
{
allowChangeResourceType ? <Select allowClear>
{
resourceTypes.map((item,index) => {
return <Select.Option key={item.key} value={item.key}>{item.name}</Select.Option>
})
}
</Select> : <span>{currentResourceType}</span>
}
</Form.Item>
<Form.Item
label="编号"
name="code"
rules={[{ required: true, message: '必填项' }]}
>
<Input placeholder="请输入编号" />
</Form.Item>
<Form.Item
label="名称"
name="name"
rules={[{ required: true, message: '必填项' }]}
>
<Input placeholder="请输入名称" />
</Form.Item>
{
action !== 'add' && (
<Form.Item
label="路径"
name="path"
>
<span>{node?.path}</span>
</Form.Item>
)
}
<Form.Item
label="描述"
name="desc"
>
<Input placeholder="请输入描述" />
</Form.Item>
<Form.Item
label="备注"
name="remarks"
>
<Input placeholder="请输入备注" />
</Form.Item>
</Form>
)
})
import React from 'react'
import * as echarts from 'echarts'
import ResizeObserver from 'rc-resize-observer'
import { Row, Col, Card, Space, Divider, Badge, Select, DatePicker, Spin, Tabs } from 'antd'
import produce from 'immer'
import Table from '../../../util/Component/Table'
import { dispatch } from '../../../model'
import './index.less'
const overviewInformations = [
{ title: '内部资源', key: 'innerResourceCount'},
{ title: '外部资源', key: 'outerResourceCount'},
{ title: '已发布资产', key: 'publishedDataAssetCount'},
{ title: '未发布资产', key: 'unPublishedDataAssetCount'},
{ title: '梳理任务', key: 'taskCount'},
]
const FC = (props) => {
return (
<div className='asset-operation'>
<Overview />
<div className='my-3' style={{ overflow: 'hidden' }}>
<Row gutter={10}>
<Col span={12}>
<Statistic type='resource' />
</Col>
<Col span={12}>
<Statistic />
</Col>
</Row>
</div>
<div style={{ overflow: 'hidden' }}>
<Row gutter={10}>
<Col span={12}>
<PopularAssetRank />
</Col>
<Col span={12}>
<LatestAssetRank />
</Col>
</Row>
</div>
</div>
)
}
export default FC
export const Header = ({ title }) => (
<Space size='small' align='center'>
<Divider style={{ width: 2, height: 15, backgroundColor: '#196AD2', margin: '15px 0' }} />
<span style={{ fontWeight: 500, fontSize: 16 }}>{title}</span>
</Space>
)
const Overview = () => {
const [loading, setLoading] = React.useState(false)
const [data, setData] = React.useState()
const [contentWidth, setContentWidth] = React.useState()
React.useEffect(() => {
getOverview()
}, [])
const colWidth = React.useMemo(() => {
const len = (overviewInformations??[]).length
if (contentWidth && len>0) {
return (contentWidth - (len-1)*10)/len
}
return 0
}, [contentWidth])
const getOverview = () => {
setLoading(true)
dispatch({
type: 'assetmanage.operationOverview',
callback: data => {
setLoading(false)
setData(data)
},
error: () => {
setLoading(false)
}
})
}
return (
<Spin spinning={loading}>
<Card size='small'>
<Header title='资产数据概览' />
<ResizeObserver
onResize={({ width }) => {
setContentWidth(width)
}}
>
<div className='flex' style={{ justifyContent: 'space-between', alignItems: 'center' }}>
{
(overviewInformations??[]).map((item, index) => {
return (
<div key={item.key} style={{ width: colWidth }}>
<Card>
<div
className='flex'
style={{
justifyContent: 'space-between',
alignItems: 'center'
}}
>
<span style={{ color: 'rgba(0, 0, 0, 0.45)' }}>{item.title}</span>
<span style={{ color: 'rgba(0, 0, 0, 0.85)', fontSize: 24 }}>{data?.[item.key]}</span>
</div>
</Card>
</div>
);
})
}
</div>
</ResizeObserver>
</Card>
</Spin>
)
}
const Statistic = ({ type = 'dataAsset' }) => {
const [loading, setLoading] = React.useState(false)
const [loadingStatisticsObject, setLoadingStatisticsObject] = React.useState(false)
const [statisticsObject, setStatisticsObject] = React.useState()
const [currentStatisticObject, setCurrentStatisticObject] = React.useState()
const [startTime, setStartTime] = React.useState()
const [endTime, setEndTime] = React.useState()
const [operationType, setOperationType] = React.useState()
const [data, setData] = React.useState()
React.useEffect(() => {
getStatisticsObject()
}, [])
React.useEffect(() => {
if (currentStatisticObject
&& (
(currentStatisticObject.groupByOperateTypes??[]).length===0
|| ((currentStatisticObject.groupByOperateTypes??[]).length>0&&operationType)
)
) {
getCount()
}
}, [currentStatisticObject, operationType, startTime, endTime])
const getStatisticsObject = () => {
setLoadingStatisticsObject(true)
dispatch({
type: (type==='dataAsset')?'assetmanage.getOperationSupportDataAssetStatisticsObject':'assetmanage.getOperationSupportResourceStatisticsObject',
callback: data => {
setLoadingStatisticsObject(false)
setStatisticsObject(data)
if ((data??[]).length > 0) {
const newCurrentStatisticObject = data[0]
setCurrentStatisticObject(newCurrentStatisticObject)
if ((newCurrentStatisticObject?.groupByOperateTypes??[]).length > 0) {
setOperationType(newCurrentStatisticObject?.groupByOperateTypes[0].type)
} else {
setOperationType()
}
}
},
error: () => {
setLoadingStatisticsObject(false)
}
})
}
const getCount = () => {
const newCurrentStatisticObject = produce(currentStatisticObject, (draft) => {
draft.beingTime = startTime
draft.endTime = endTime
for (let item of (draft.groupByOperateTypes??[])) {
item.selected = (item.type===operationType)
}
})
setLoading(true)
dispatch({
type: 'assetmanage.operationCountByStatisticsObject',
payload: {
data: newCurrentStatisticObject
},
callback: data => {
setLoading(false)
setData(data)
},
error: () => {
setLoading(false)
}
})
}
return (
<Card size='small'>
<Header title={type==='dataAsset'?'数据资产统计':'数据资源统计'} />
<div className='flex' style={{ justifyContent: 'space-between', alignItems: 'center' }}>
<Select
loading={loadingStatisticsObject}
onChange={(value) => {
const index = (statisticsObject??[]).findIndex(item => item.statisticsById===value)
if (index !== -1) {
const newCurrentStatisticObject = statisticsObject[index]
setCurrentStatisticObject(newCurrentStatisticObject)
if ((newCurrentStatisticObject?.groupByOperateTypes??[]).length > 0) {
setOperationType(newCurrentStatisticObject?.groupByOperateTypes[0].type)
} else {
setOperationType()
}
}
}}
value={currentStatisticObject?.statisticsById}
style={{ width: 140 }}
>
{
(statisticsObject??[]).map((item, index) => <Select.Option key={item.statisticsById} value={item.statisticsById}>{item.statisticsByName}</Select.Option>)
}
</Select>
<DatePicker.RangePicker
format="YYYY-MM-DD"
onChange={(values) => {
setStartTime((values??[]).length>0?values[0].valueOf():'')
setEndTime((values??[]).length>1?values[1].valueOf():'')
}}
style={{ width: 240 }}
/>
</div>
<div className='mb-3' style={{ height: 38 }}>
{
(currentStatisticObject?.groupByOperateTypes??[]).length > 0 && <Tabs
size='small'
activeKey={operationType}
onChange={(val) => {
setOperationType(val)
}}
>
{
(currentStatisticObject?.groupByOperateTypes??[]).map((item, index) => {
return <Tabs.TabPane tab={item.name} key={item.type}></Tabs.TabPane>
})
}
</Tabs>
}
</div>
<Spin spinning={loading}>
<StackedLine data={data} />
</Spin>
</Card>
)
}
const PopularAssetRank = () => {
const [loading, setLoading] = React.useState(false)
const [data, setData] = React.useState()
const cols = [
{
title: '名次',
dataIndex: 'index',
render: (_, __, index) => {
let color = '#c7000b'
if (index === 0) {
color = '#FD6161'
} else if (index === 1){
color = '#F0864F'
} else if (index === 2){
color = '#F9C22E'
}
return (
<Badge style={{ marginBottom: 2, backgroundColor: color }} count={index+1} />
)
}
},
{
title: '英文名',
dataIndex: 'enName',
ellipsis: true,
},
{
title: '中文名',
dataIndex: 'cnName',
ellipsis: true,
},
{
title: '浏览次数',
dataIndex: 'visitCount',
ellipsis: true,
},
]
React.useEffect(() => {
getRanks()
}, [])
const getRanks = () => {
setLoading(true)
dispatch({
type: 'assetmanage.getOperationPopularDataAssetRanking',
callback: data => {
setLoading(false)
setData(data)
},
error: () => {
setLoading(false)
}
})
}
return (
<Card size='small'>
<Header title='资产浏览排行榜' />
<Table
loading={loading}
size='small'
columns={cols}
dataSource={data}
pagination={false}
/>
</Card>
)
}
const LatestAssetRank = () => {
const [loading, setLoading] = React.useState(false)
const [data, setData] = React.useState()
const cols = [
{
title: '名次',
dataIndex: 'index',
render: (_, __, index) => {
let color = '#c7000b'
if (index === 0) {
color = '#FD6161'
} else if (index === 1){
color = '#F0864F'
} else if (index === 2){
color = '#F9C22E'
}
return (
<Badge style={{ marginBottom: 2, backgroundColor: color }} count={index+1} />
)
}
},
{
title: '英文名',
dataIndex: 'enName',
ellipsis: true,
},
{
title: '中文名',
dataIndex: 'cnName',
ellipsis: true,
},
{
title: '发布时间',
dataIndex: 'createTime',
ellipsis: true,
},
]
React.useEffect(() => {
getRanks()
}, [])
const getRanks = () => {
setLoading(true)
dispatch({
type: 'assetmanage.getOperationLatestDataAssetRanking',
callback: data => {
setLoading(false)
setData(data)
},
error: () => {
setLoading(false)
}
})
}
return (
<Card size='small'>
<Header title='最新发布资产' />
<Table
loading={loading}
size='small'
columns={cols}
dataSource={data}
pagination={false}
/>
</Card>
)
}
const StackedLine = ({ data }) => {
const ref = React.useRef()
const echartsRef = React.useRef()
React.useEffect(() => {
echartsRef.current = echarts.init(ref.current)
}, [])
React.useEffect(() => {
setOption()
}, [data])
const setOption = () => {
let serieNames = []
if ((data??[]).length === 0) {
echartsRef.current?.clear()
return
}
if ((data??[]).length > 0) {
serieNames = Object.keys(data[0]).filter(item => item !== 'date')
}
let option = {
tooltip: {
trigger: 'axis'
},
legend: {
bottom: 0,
data: serieNames
},
grid: {
left: '3%',
right: '4%',
top: '3%',
bottom: '10%',
containLabel: true
},
xAxis: {
type: 'category',
boundaryGap: true,
data: (data??[]).map(item => item.date)
},
yAxis: {
type: 'value'
},
series: (serieNames??[]).map(name => {
return ({
name,
type: 'line',
stack: 'Total',
data: (data??[]).map(item => item[`${name}`])
})
})
}
echartsRef.current?.setOption(option)
}
return (
<div ref={ref} style={{ width: '100%', height: '300px' }}/>
)
}
\ No newline at end of file
.asset-operation {
height: 100%;
overflow: auto;
.yy-tabs-nav::before {
border: none;
}
.yy-tabs-nav-wrap {
justify-content: flex-end;
}
}
\ No newline at end of file
import React, { useState } from "react";
import { Modal } from "antd";
import { dispatch } from '../../../../model';
import AssetTree from '../../AssetManage/Component/AssetManageTree';
import { showMessage, showNotifaction } from '../../../../util';
import { AssetManageReference, AssetRecycleReference, AssetMountReference } from "../../../../util/constant";
const AssetMount = (props) => {
const { onCancel, visible, ids, reference = AssetManageReference } = props;
const [ dirIds, setDirIds ] = useState([]);
const [ confirmLoading, setConfirmLoading ] = useState(false);
const onCheck = (values) => {
setDirIds(values||[]);
}
const onOk = () => {
if ((dirIds||[]).length === 0) {
showMessage('warn', '请先选择资产目录');
return;
}
setConfirmLoading(true);
dispatch({
type: 'assetmanage.loadDataAssets',
payload: {
params: {
dirId: dirIds.join(","),
},
data: ids
},
callback: data => {
setConfirmLoading(false);
if (data?.message) {
showNotifaction('提示', data?.message, 5);
}
reset();
onCancel && onCancel(true);
},
error: () => {
setConfirmLoading(false);
}
})
}
const reset = () => {
setConfirmLoading(false);
}
return(
<Modal
title={(reference===AssetRecycleReference)?'挂载目录详情':'变更目录详情'}
visible={ visible }
width={ 400 }
confirmLoading={ confirmLoading }
onCancel={()=>{
reset();
onCancel && onCancel()
}}
onOk={ onOk }
>
<AssetTree
checkable={true}
onCheck={onCheck}
tableId={(reference===AssetManageReference&&(ids||[].length>0))?ids[0]:''}
reference={AssetMountReference}
/>
</Modal>
)
}
export default AssetMount;
\ No newline at end of file
import React from 'react'
import { Button, Modal, Spin, Tree, AutoComplete } from 'antd'
import { dispatch } from '../../../model'
import produce from 'immer'
import { highlightSearchContentByTerms, showMessage, showNotifaction } from '../../../util'
import { generateList } from '../AssetResourceManage/tree'
const FC = (props) => {
const { visible, items, onCancel } = props
const [waiting, setWaiting] = React.useState(false)
const basicRef = React.useRef()
const close = (refresh = false) => {
setWaiting(false)
onCancel?.(refresh)
}
const save = () => {
const checkedKeys = basicRef.current?.getCheckedKeys()
if ((checkedKeys??[]).length === 0) {
showMessage('warn', '请先选择资源目录')
return
}
setWaiting(true)
dispatch({
type: 'assetmanage.loadDataAssets',
payload: {
params: {
dirId: (checkedKeys??[]).toString(),
},
data: (items??[]).map(item => item.id)
},
callback: data => {
setWaiting(false)
if (data?.message) {
showNotifaction('提示', data?.message, 5)
}
onCancel?.(true)
},
error: () => {
setWaiting(false)
}
})
}
const footer = React.useMemo(() => {
return [
<Button key={'cancel'}
onClick={() => close()}
>取消</Button>,
<Button key={'save'} type='primary'
disabled={waiting}
onClick={() => save()}
>保存</Button>
]
}, [close, save, waiting])
return (
<Modal
visible={visible}
footer={footer}
width='400px'
bodyStyle={{ padding: '15px 15px 0px 15px', overflowX: 'auto', maxHeight: '80vh', height: 500 }}
title='变更目录'
centered destroyOnClose
onCancel={() => { close() }}
>
<Spin spinning={waiting}>
<Basic ref={basicRef} items={items} />
</Spin>
</Modal>
)
}
export default FC
export const Basic = React.forwardRef(function ({ items, onCheck }, ref) {
const [data, setData] = React.useState()
const [dataList, setDataList] = React.useState()
const [loading, setLoading] = React.useState(false)
const [checkedKeys, setCheckedKeys] = React.useState()
const [expandedKeys, setExpandedKeys] = React.useState([])
const [autoExpandParent, setAutoExpandParent] = React.useState(false)
const [options, setOptions] = React.useState()
const [keyword, setKeyword] = React.useState()
React.useImperativeHandle(ref, () => ({
getCheckedKeys: () => checkedKeys
}), [checkedKeys])
React.useEffect(() => {
getTreeData()
}, [])
const treeData = React.useMemo(() => {
if (data) {
const newTreeData = produce(data, draft => {
const setNode = (g) => {
g.key = g.nodeId
g.title = g.text
g.children?.forEach((child) => {
setNode(child)
})
}
draft.forEach((child) => {
setNode(child)
})
})
return newTreeData
}
return []
}, [data])
const getTreeData = () => {
setLoading(true)
dispatch({
type: 'assetmanage.queryDataAssetManageTree',
callback: data => {
setLoading(false)
const newData = (data??[]).filter(item => item.resourceType !== 'custom')
setData(newData)
if ((newData??[]).length > 0) {
const newDataList = []
generateList(newData, newDataList)
setDataList(newDataList)
const firstNode = newData[0]
setExpandedKeys([firstNode.nodeId])
setAutoExpandParent(true)
}
},
error: () => {
setLoading(false)
}
})
}
const onTreeExpand = (expandedKeys) => {
setExpandedKeys(expandedKeys)
setAutoExpandParent(false)
}
const onTreeCheck = (values, e) => {
//同一主题下只能挂载一个目录
if (e.node?.level === 1) {
showMessage('warn', '栏目不允许勾选')
return
}
const newCheckedKeys = values.checked??[]
if (e.checked) {
const index = (dataList??[]).findIndex(item => item.nodeId === e.node?.key)
if (index !== -1) {
const currentSubjectNodeId = dataList[index].subjectNodeId
const filterChecktedKeys = newCheckedKeys.filter(key => {
if (key !== e.node?.key) {
const index = (dataList??[]).findIndex(item => item.nodeId === key)
if (index !== -1) {
return (dataList[index].subjectNodeId !== currentSubjectNodeId)
} else {
return false
}
}
return true
})
setCheckedKeys(filterChecktedKeys)
onCheck?.(filterChecktedKeys)
}
} else {
setCheckedKeys(newCheckedKeys)
onCheck?.(newCheckedKeys)
}
}
const onAutoCompleteSearch = (searchText) => {
setKeyword(searchText)
setOptions(!searchText?[]:(dataList||[]).filter(item => item.value.indexOf(searchText)!==-1))
}
const onAutoCompleteSelect = (value, option) => {
const paths = value.split('/')
setKeyword(paths[paths.length-1])
const newExpandedKeys = [...expandedKeys, option.key]
setExpandedKeys(Array.from(new Set(newExpandedKeys)))
setAutoExpandParent(true)
}
return (
<Spin spinning={loading}>
<AutoComplete
allowClear
value={keyword}
style={{ marginBottom: 10, width: '100%' }}
onSelect={onAutoCompleteSelect}
onSearch={onAutoCompleteSearch}
onClear={() => {
setKeyword()
}}
>
{
(options||[]).map((item, index) => {
return (
<AutoComplete.Option key={item.key} value={item.value}>
<div style={{ whiteSpace: 'normal' }}>
{highlightSearchContentByTerms(item.value, [keyword])}
</div>
</AutoComplete.Option>
);
})
}
</AutoComplete>
<Tree
checkable
checkStrictly
showLine
showIcon={false}
treeData={treeData}
autoExpandParent={autoExpandParent}
expandedKeys={expandedKeys}
checkedKeys={checkedKeys}
onExpand={onTreeExpand}
onCheck={onTreeCheck}
/>
</Spin>
)
})
\ No newline at end of file
import React from 'react'; import React from 'react';
import AssetTable from '../AssetManage/Component/AssetTable'; import AssetTable from './table';
import { AssetRecycleReference } from '../../../util/constant'; import { AssetRecycleReference } from '../../../util/constant';
const AssetRecycle = (props) => { const AssetRecycle = (props) => {
......
import React from 'react'
import classNames from 'classnames'
import { Tooltip, Typography, Space, Dropdown, Button, Menu, Checkbox, Input, Select, Modal, Table as AntdTable } from 'antd'
import ResizeObserver from 'rc-resize-observer'
import { debounceTime, Subject } from 'rxjs'
import { DownOutlined, UpOutlined } from "@ant-design/icons"
import LocalStorage from 'local-storage'
import { defaultPage, usePage } from '../../../util/hooks/page'
import Table from '../../../util/Component/Table'
import { dispatch } from '../../../model'
import { getAssetRange, getAssetType, getQueryParam, IsArr, isSzseEnv, showMessage, showNotifaction } from '../../../util'
import { AssetBrowseReference, AssetRecycleReference } from '../../../util/constant'
import PermissionButton from '../../../util/Component/PermissionButton'
import PermissionMenuItem from '../../../util/Component/PermissionMenuItem'
import ChangeCatalog from './change-catalog'
import FilterElement from '../AssetManage/Component/FilterElementModal'
import TagCell from '../Model/Component/tag-help'
import { MetadataColumn } from '../AssetResourceManage/table'
import AssetDetailDrawer from '../AssetManage/Component/AssetDetailDrawer'
import '../AssetManage/table.less'
const FC = () => {
const [args, setArgs] = React.useState(() => ({
params: {
page: defaultPage.pageNum,
size: defaultPage.pageSize,
keyword: undefined,
},
}))
const [ permissions, setPermissions ] = React.useState()
const [loading, setLoading] = React.useState(false)
const [loadingElements, setLoadingElements] = React.useState(false)
const [columns, setColumns] = React.useState()
const [elements, setElements] = React.useState()
const [data, setData] = React.useState()
const [total, setTotal] = React.useState()
const [row, setRow] = React.useState()
const [selectedRows, setSelectedRows] = React.useState()
const $keyword = React.useMemo(() => new Subject(), [])
const [keyword, setKeyword] = React.useState()
const [searchType, setSearchType] = React.useState('keyword')
const [resoureTagMap, setResourceTagMap] = React.useState()
const [filterElementParams, setFilterElementParams] = React.useState({
visible: false,
type: undefined,
reference: undefined
})
const [changeCatalogParams, setChangeCatalogParams] = React.useState({
visible: false,
items: undefined
})
const [assetDetailParams, setAssetDetailParams] = React.useState({
visible: false,
id: undefined,
dirId: undefined,
reference: undefined,
})
const [page, setPage] = usePage()
const [modal, contextHolder] = Modal.useModal()
const setArgsAndPage = React.useCallback((params) => {
// 设置查询参数时将分页置为1
setPage(prevpg => {
setArgs((prev) => {
const newparams = params ? { ...prev.params, ...params } : undefined
return { params: { ...newparams, page: 1, size: prevpg.pageSize } }
})
return ({ ...prevpg, pageNum: 1 })
})
//eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
const setPageAndArgs = React.useCallback((page) => {
setPage(prev => ({ ...prev, ...page }))
setArgs((prev) => {
return { params: { ...prev.params, page: page.pageNum, size: page.pageSize } }
})
//eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
React.useEffect(() => {
getPermissions()
getElements()
const $$keyword = $keyword.pipe(debounceTime(1000)).subscribe((keyword) => {
setArgsAndPage({ keyword })
})
return () => {
$$keyword.unsubscribe()
}
//eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
React.useEffect(() => {
getAssets()
}, [args])
React.useEffect(() => {
if (data) {
getResourceTag()
}
}, [data])
const notElementCol = [
{
title: '标签',
dataIndex: 'tag',
width: 360,
className: 'table-tag-cell',
render: (_, record) => <div onClick={(e) => {e?.stopPropagation()}}>
<TagCell
id={record.id}
did={record.dirId}
type='dataAsset'
tags={resoureTagMap?.[`${record.id}`]}
/>
</div>
}
]
React.useEffect(() => {
const newColumns = []
let index = 0
for (const element of elements??[]) {
const col = {
title: element.name,
dataIndex: `element${++index}`,
ellipsis: true,
width: 120,
render: (text, record) => {
return (
<Tooltip title={text}>
<Typography.Text ellipsis={true}>
{text}
</Typography.Text>
</Tooltip>
);
}
}
if (element.name === '编号') {
col.width = 60
} else if (element.name === '中文名称') {
col.width = isSzseEnv ? 230 : 160
} else if (element.name === '英文名称') {
col.width = isSzseEnv ? 224 : 160
} else if (element.name === '资产路径') {
col.render = (text, record) => {
return (
<Tooltip title={text}>
<Typography.Text ellipsis={true}>
<a onClick={()=>{
let event = new Event('storage')
event.key = 'assetDirChangeEvent'
event.dirId = record.dirId
window?.dispatchEvent(event)
}}>
{text}
</a>
</Typography.Text>
</Tooltip>
)
}
} else if (element.metadataItem === '是') {
col.width = isSzseEnv ? 250 : 120
col.render = (text, record) => <MetadataColumn data={text} />
}
newColumns.push(col)
}
setColumns([...newColumns, ...notElementCol])
}, [elements, resoureTagMap])
const tableData = React.useMemo(() => {
const newTableData = []
for (const item of (data??[])) {
const newAsset = {...item}
let index = 0
for (const elementValue of (item.elementValues??[])) {
newAsset[`element${++index}`] = elementValue
}
newTableData.push(newAsset)
}
return newTableData
}, [data])
const tableMaxHeight = React.useMemo(() => {
return 'calc(100vh - 199px - 72px)'
}, [])
const getPermissions = () => {
dispatch({
type: 'assetmanage.getPrivilegeByRange',
payload: {
range: getAssetRange(AssetRecycleReference),
},
callback: data => {
setPermissions(data)
}
})
}
const getElements = () => {
setLoadingElements(true)
dispatch({
type: 'assetmanage.listFilterElements',
payload: {
range: getAssetRange(AssetRecycleReference),
dataAssetType: getAssetType(AssetRecycleReference)
},
callback: data => {
setLoadingElements(false)
setElements(data)
},
error: () => {
setLoadingElements(false)
}
})
}
const getAssets = () => {
setLoading(true)
dispatch({
type: 'assetmanage.listRecycleBinDataAssetsByPage',
payload: {
pageNum: args.params.page,
pageSize: args.params.size,
keyword: args.params.keyword,
},
callback: data => {
setLoading(false)
setData(data?.data)
setTotal(data?.total)
},
error: () => {
setLoading(false)
}
})
}
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 onExportClick = () => {
window.open(`/api/dataassetmanager/dataAssetApi/exportByDataAssetIds?dataAssetIds=${(selectedRows??[]).map(item => item.id).toString()}`);
}
const onMountClick = () => {
setChangeCatalogParams({
visible: true,
items: selectedRows
})
}
const onFilterElementClick = () => {
setFilterElementParams({
visible: true,
type: 'user',
reference: AssetBrowseReference
})
}
const onRecoveryClick = () => {
modal.confirm({
title: '提示',
content: '您确定要恢复这些资产吗?',
onOk: () => {
dispatch({
type: 'assetmanage.recoveryFromRecycleBin',
payload: {
data: (selectedRows??[]).map(item => item.id)
},
callback: (data) => {
if (data?.message) {
showNotifaction('提示', data?.message, 5);
}
getAssets()
setSelectedRows()
}
})
}
})
}
const onDeleteClick = () => {
modal.confirm({
title: '提示',
content: '您确定要永久删除这些资产吗?',
onOk: () => {
dispatch({
type: 'assetmanage.deleteDataAssets',
payload: {
data: (selectedRows??[]).map(item => item.id)
},
callback: () => {
showMessage("success","删除成功")
getAssets()
setSelectedRows()
},
error: () => {
}
})
}
})
}
const refreshPage = () => {
getElements()
getAssets()
}
return (
<div className='asset-list'>
<div
className='flex'
style={{
padding: '15px',
alignItems: 'center',
justifyContent: 'space-between',
}}
>
<Space>
<PermissionButton
permissionKey='export'
permissions={permissions}
disabled={(selectedRows??[]).length===0}
tip={(selectedRows??[]).length===0?'请先选择资产':''}
onClick={onExportClick}
>
导出
</PermissionButton>
<PermissionButton
permissionKey='loadDataAsset'
permissions={permissions}
disabled={(selectedRows??[]).length===0}
tip={(selectedRows??[]).length===0?'请先选择资产':''}
onClick={onMountClick}
>
挂载
</PermissionButton>
<PermissionButton
permissionKey='resumeDataAsset'
permissions={permissions}
disabled={(selectedRows??[]).length===0}
tip={(selectedRows??[]).length===0?'请先选择资产':''}
onClick={onRecoveryClick}
>
恢复
</PermissionButton>
<PermissionButton
permissionKey='delete'
permissions={permissions}
disabled={(selectedRows??[]).length===0}
tip={(selectedRows??[]).length===0?'请先选择资产':''}
onClick={onDeleteClick}
>
删除
</PermissionButton>
<Button onClick={onFilterElementClick}>可见列设置</Button>
</Space>
<Input size="middle"
placeholder="资产要素值"
value={keyword}
bordered={true} allowClear
onChange={(e) => {
const keyword = e.target.value
setKeyword(keyword)
$keyword.next((keyword??'').trim())
}}
style={{ width: 270 }}
/>
</div>
<div className='px-3'>
<Table
maxHeight={tableMaxHeight}
loading={loading||loadingElements}
columns={columns}
dataSource={tableData}
pageSize={page.pageSize} pageNum={page.pageNum} total={total??0}
onRowClick={(event, value) => {
setRow(value)
setAssetDetailParams({
visible: true,
id: value?.id,
dirId: value?.dirId,
reference: AssetBrowseReference
})
}}
rowClassName={(record, index) => (record?.id === row?.id) ? 'yy-table-select-row' : ''}
onPaginate={(page, pageSize) => {
setRow()
setPageAndArgs({ pageNum: page, pageSize })
}}
rowSelection={{
selectedRowKeys: (selectedRows??[]).map(item => item.id),
onChange: (selectedRowKeys, selectedRows) => {
setSelectedRows(selectedRows)
},
}}
/>
</div>
<FilterElement
{...filterElementParams}
onCancel={(refresh = false) => {
setFilterElementParams({
visible: false,
type: undefined,
reference: undefined
})
if (refresh) {
refreshPage()
}
}}
/>
<ChangeCatalog
{...changeCatalogParams}
onCancel={(refresh) => {
setChangeCatalogParams({
visible: false,
items: undefined
})
if (refresh) {
refreshPage()
}
}}
/>
<AssetDetailDrawer
{...assetDetailParams}
onCancel={() => {
setAssetDetailParams({
visible: false,
id: undefined,
dirId: undefined,
reference: undefined
})
}}
/>
{contextHolder}
</div>
)
}
export default FC
\ No newline at end of file
import React from 'react'; import React, { useState } from 'react';
import classNames from 'classnames';
import { CaretLeftOutlined, CaretRightOutlined } from '@ant-design/icons';
import { ResizableBox } from 'react-resizable';
import AssetBrowse from '../AssetBrowse'; import AssetTree from '../AssetManage/Component/AssetTree';
import AssetDirectory from '../AssetManage/Component/AssetDirectory';
import RelationContainer from '../AssetBrowse/Component/RelationContainer';
import AssetTable from "./table";
import Separate from '../AssetManage/Component/Separate';
import { ResourceBrowseReference } from '../../../util/constant'; import { ResourceBrowseReference } from '../../../util/constant';
const AssetResourceBrowse = (props) => { import '../AssetBrowse/index.less';
const FC = (props) => {
const [ nodeParams, setNodeParams ] = useState({ centerId: '', expandId: '', nodeType: '' });
const [ expandTree, setExpandTree ] = useState(true);
const [ expandRelation, setExpandRelation ] = useState(true);
const [ assetCount, setAssetCount ] = useState(0);
const [ resizeRelation, setResizeRelation ] = useState(false);
const [ assetFullScreen, setAssetFullScreen ] = useState(false);
const { centerId, expandId } = nodeParams;
const onTreeSelect = (value, type) => {
setNodeParams({ centerId: value||'', expandId: '', nodeType: type });
}
const treeToggleClick = () => {
setExpandTree(!expandTree);
setResizeRelation(!resizeRelation);
}
const relationToggleClick = () => {
setExpandRelation(!expandRelation);
setResizeRelation(!resizeRelation);
}
const onRelationChange = (data) => {
setNodeParams(data);
}
const onAssetCountChange = (count) => {
setAssetCount(count);
}
const onFullScreenChange = (value) => {
setAssetFullScreen(value);
}
let nodeId = '';
if ((expandId||'') !== '') {
nodeId = expandId;
} else {
nodeId = centerId;
}
const classes = classNames('asset-browse', {
'asset-browse-tree-collapse': !expandTree,
'asset-browse-relation-collapse': !expandRelation,
});
const rightClasses = classNames('right', {
'right-fullscreen': assetFullScreen
});
return ( return (
<AssetBrowse reference={ResourceBrowseReference} {...props} /> <div className={classes}>
<ResizableBox
className='left'
width={230}
height={Infinity}
axis='x'
minConstraints={[230, Infinity]} maxConstraints={[Infinity, Infinity]}
>
<AssetTree centerId={centerId} onSelect={onTreeSelect} reference={ResourceBrowseReference} {...props} />
</ResizableBox>
{
expandTree && <Separate width={15} />
}
<div className={rightClasses}>
<AssetDirectory id={nodeId} assetCount={assetCount} reference={ResourceBrowseReference} nodeType={nodeParams.nodeType} />
<Separate height={15} />
<div className='flex' style={{ flex: 1, height: '100%', overflow: 'hidden' }}>
{
expandRelation && <React.Fragment>
<div style={{ flex: 1, height: '100%', overflow: 'hidden' }}>
<RelationContainer reference={ResourceBrowseReference} nodeParams={nodeParams} onChange={onRelationChange} resize={resizeRelation} />
</div>
<Separate width={15} />
</React.Fragment>
}
<div style={{ flex: 1, overflow: 'hidden' }}>
<AssetTable node={{ nodeId, type: nodeParams.nodeType }} onFullScreenChange={onFullScreenChange} {...props} />
</div>
</div>
<div className='tree-toggle' onClick={treeToggleClick}>
{ expandTree ? <CaretLeftOutlined /> : <CaretRightOutlined /> }
</div>
<div className='relation-toggle' onClick={relationToggleClick}>
{ expandRelation ? <CaretLeftOutlined /> : <CaretRightOutlined /> }
</div>
</div>
</div>
) )
} }
export default AssetResourceBrowse; export default FC;
\ No newline at end of file \ No newline at end of file
import React from 'react'
import classNames from 'classnames'
import { Tooltip, Typography, Space, Dropdown, Button, Menu, Checkbox, Input, Select, Modal, Table as AntdTable } from 'antd'
import ResizeObserver from 'rc-resize-observer'
import { debounceTime, Subject } from 'rxjs'
import { DownOutlined, UpOutlined } from "@ant-design/icons"
import LocalStorage from 'local-storage'
import { defaultPage, usePage } from '../../../util/hooks/page'
import Table from '../../../util/Component/Table'
import { dispatch } from '../../../model'
import { getAssetRange, getAssetType, getQueryParam, IsArr, isSzseEnv, showMessage, showNotifaction } from '../../../util'
import { ResourceBrowseReference } from '../../../util/constant'
import PermissionButton from '../../../util/Component/PermissionButton'
import PermissionMenuItem from '../../../util/Component/PermissionMenuItem'
import { FullScreenSvg, CancelFullScreenSvg } from '../AssetManage/Component/AssetSvg'
import FilterElement from '../AssetManage/Component/FilterElementModal'
import FilterElementValue from '../AssetResourceManage/filter-element-value'
import { AssetDirectorySubject } from '../AssetManage/Component/AssetDirectory'
import { AssetActionSubject } from '../AssetManage/Component/AssetAction'
import TagCell from '../Model/Component/tag-help'
import { MetadataColumn } from '../AssetResourceManage/table'
import AssetDetailDrawer from '../AssetManage/Component/AssetDetailDrawer'
import '../AssetManage/table.less'
const FC = (props) => {
const { node, onFullScreenChange } = props
const [args, setArgs] = React.useState(() => ({
params: {
page: defaultPage.pageNum,
size: defaultPage.pageSize,
catalogType: 'currentRecursive',
keyword: undefined,
elementValueFilters: []
},
}))
const [fullScreen, setFullScreen] = React.useState(false)
const [loading, setLoading] = React.useState(false)
const [loadingElements, setLoadingElements] = React.useState(false)
const [columns, setColumns] = React.useState()
const [elements, setElements] = React.useState()
const [data, setData] = React.useState()
const [total, setTotal] = React.useState()
const [row, setRow] = React.useState()
const $keyword = React.useMemo(() => new Subject(), [])
const [keyword, setKeyword] = React.useState()
const [searchType, setSearchType] = React.useState('keyword')
const [resoureTagMap, setResourceTagMap] = React.useState()
const [filterElementParams, setFilterElementParams] = React.useState({
visible: false,
type: undefined,
reference: undefined
})
const [filterElementValueParams, setFilterElementValueParams] = React.useState({
visible: false,
type: undefined,
defaultValue: undefined,
})
const [assetDetailParams, setAssetDetailParams] = React.useState({
visible: false,
id: undefined,
dirId: undefined,
reference: undefined,
})
const [page, setPage] = usePage()
const [modal, contextHolder] = Modal.useModal()
const locationIdRef = React.useRef(getQueryParam('id', props?.location?.search))
const locationDidRef = React.useRef(getQueryParam('did', props?.location?.search))
const setArgsAndPage = React.useCallback((params) => {
// 设置查询参数时将分页置为1
setPage(prevpg => {
setArgs((prev) => {
const newparams = params ? { ...prev.params, ...params } : undefined
return { params: { ...newparams, page: 1, size: prevpg.pageSize } }
})
return ({ ...prevpg, pageNum: 1 })
})
//eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
const setPageAndArgs = React.useCallback((page) => {
setPage(prev => ({ ...prev, ...page }))
setArgs((prev) => {
return { params: { ...prev.params, page: page.pageNum, size: page.pageSize } }
})
//eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
const storageChange = (e) => {
if (e.key === 'editAssetsChange') {
getAssets()
} else if (e.key === 'assetRelationOnClickEvent') {
locationIdRef.current = e.relation?.dataAssetId
locationDidRef.current = e.relation?.dirId
} else if (e.key === 'assetPathOnClickEvent') {
locationIdRef.current = e.id
locationDidRef.current = e.dirId
}
}
React.useEffect(() => {
getElements()
const $$keyword = $keyword.pipe(debounceTime(1000)).subscribe((keyword) => {
setArgsAndPage({ keyword })
})
window?.addEventListener("storage", storageChange)
return () => {
$$keyword.unsubscribe()
window?.removeEventListener("storage", storageChange)
}
//eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
React.useEffect(() => {
if (node) {
setRow()
if (locationIdRef.current && locationDidRef.current) {
getDataAssetLocation()
} else {
setPageAndArgs({ pageNum: 1, pageSize: page.pageSize })
}
}
}, [node])
React.useEffect(() => {
if (node?.nodeId) {
getAssets()
}
}, [args])
React.useEffect(() => {
if (data) {
getResourceTag()
}
}, [data])
const notElementCol = [
{
title: '标签',
dataIndex: 'tag',
width: 360,
className: 'table-tag-cell',
render: (_, record) => <div onClick={(e) => {e?.stopPropagation()}}>
<TagCell
id={record.id}
did={record.dirId}
type='dataAsset'
tags={resoureTagMap?.[`${record.id}`]}
/>
</div>
}
]
React.useEffect(() => {
const newColumns = []
let index = 0
for (const element of elements??[]) {
const col = {
title: element.name,
dataIndex: `element${++index}`,
ellipsis: true,
width: 120,
render: (text, record) => {
return (
<Tooltip title={text}>
<Typography.Text ellipsis={true}>
{text}
</Typography.Text>
</Tooltip>
);
}
}
if (element.name === '编号') {
col.width = 60
} else if (element.name === '中文名称') {
col.width = isSzseEnv ? 230 : 160
} else if (element.name === '英文名称') {
col.width = isSzseEnv ? 224 : 160
} else if (element.name === '资产路径') {
col.render = (text, record) => {
return (
<Tooltip title={text}>
<Typography.Text ellipsis={true}>
<a onClick={()=>{
let event = new Event('storage')
event.key = 'assetDirChangeEvent'
event.dirId = record.dirId
window?.dispatchEvent(event)
}}>
{text}
</a>
</Typography.Text>
</Tooltip>
)
}
} else if (element.metadataItem === '是') {
col.width = isSzseEnv ? 250 : 120
col.render = (text, record) => <MetadataColumn data={text} />
}
newColumns.push(col)
}
setColumns([...newColumns, ...notElementCol])
}, [elements, resoureTagMap])
const tableData = React.useMemo(() => {
const newTableData = []
for (const item of (data??[])) {
const newAsset = {...item}
let index = 0
for (const elementValue of (item.elementValues??[])) {
newAsset[`element${++index}`] = elementValue
}
newTableData.push(newAsset)
}
return newTableData
}, [data])
const tableMaxHeight = React.useMemo(() => {
return fullScreen ? 'calc(100vh - 209px - 72px)' : 'calc(100vh - 209px - 123px - 15px - 72px)'
}, [fullScreen])
const getElements = () => {
setLoadingElements(true)
dispatch({
type: 'assetmanage.listFilterElements',
payload: {
range: getAssetRange(ResourceBrowseReference),
dataAssetType: getAssetType(ResourceBrowseReference)
},
callback: data => {
setLoadingElements(false)
setElements(data)
},
error: () => {
setLoadingElements(false)
}
})
}
const getAssets = () => {
setLoading(true)
let [recursive, dirId] = [true, node?.nodeId]
if (args.params.catalogType === 'current') {
recursive = false
}
if (args.params.catalogType === 'fullSearch') {
dirId = ''
}
dispatch({
type: 'assetmanage.listDataResourcesByPage',
payload: {
data: args.params.elementValueFilters??[],
params: {
dirId,
pageNum: args.params.page,
pageSize: args.params.size,
keyword: args.params.keyword,
range: getAssetRange(ResourceBrowseReference),
recursive,
isCustomDir: (node?.resourceType==='custom'||node?.type==='custom')
}
},
callback: data => {
setLoading(false)
setData(data?.data)
setTotal(data?.total)
if (locationIdRef.current) {
const index = (data?.data??[]).findIndex(item => item.id === locationIdRef.current)
if (index !== -1) {
setRow(data?.data[index])
}
setTimeout(() => {
var anchor = document.getElementById(`${locationIdRef.current}`)
anchor?.scrollIntoView()
locationIdRef.current = null
locationDidRef.current = null
}, 500)
}
},
error: () => {
setLoading(false)
locationIdRef.current = null
locationDidRef.current = null
}
})
}
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 getDataAssetLocation = () => {
setLoading(true)
dispatch({
type: 'assetmanage.getDataAssetLocation',
payload: {
dataAssetId: locationIdRef.current,
dirId: locationDidRef.current,
},
callback: data => {
const newPageNum = parseInt(data.offset/page.pageSize + ((data.offset%page.pageSize===0)?0:1))
setPageAndArgs({ ...page, pageNum: newPageNum })
},
error: () => {
setLoading(false)
locationIdRef.current = null
locationDidRef.current = null
setPageAndArgs({ ...page, pageNum: 1 })
}
})
}
const onFilterElementClick = () => {
setFilterElementParams({
visible: true,
type: 'user',
reference: ResourceBrowseReference
})
}
const classes = classNames('asset-list', {
'asset-list-fullscreen': fullScreen,
});
return (
<div className={classes}>
<div
className='flex'
style={{
padding: '20px 15px 5px',
alignItems: 'center',
justifyContent: 'space-between',
}}
>
<Space className='mr-3' style={{ flex: 0, paddingBottom: 15 }}>
<Button onClick={onFilterElementClick}>可见列设置</Button>
</Space>
<div className='flex' style={{ flex: 1, overflow: 'auto', paddingBottom: 15 }}>
<div style={{ flex: 1 }}></div>
<Space style={{ flex: 0 }}>
<Select
value={args.params.catalogType}
onChange={(value) => {
setArgsAndPage({ catalogType: value })
}}
style={{ width: 170 }}
>
<Select.Option value='currentRecursive'>
当前目录(含子目录)
</Select.Option>
<Select.Option value='current'>
当前目录
</Select.Option>
<Select.Option value='fullSearch'>
全部数据
</Select.Option>
</Select>
<Input size="middle"
addonBefore={
<Select
value={searchType}
onChange={(value) => {
setSearchType(value)
if (value === 'attribute') {
setFilterElementValueParams({
visible: true,
type: ResourceBrowseReference,
defaultValue: args.params.elementValueFilters
})
}
}}
style={{ width: 120 }}
>
<Select.Option value='keyword'>
关键字搜索
</Select.Option>
<Select.Option value='attribute'>
属性值过滤
</Select.Option>
</Select>
}
placeholder="资产要素值/任务"
value={keyword}
bordered={true} allowClear
onChange={(e) => {
const keyword = e.target.value
setKeyword(keyword)
$keyword.next((keyword??'').trim())
}}
style={{ width: 270 }}
/>
<Tooltip title={fullScreen?'取消全屏':'全屏'}>
<Button
onClick={() => {
setFullScreen(!fullScreen)
onFullScreenChange?.(!fullScreen)
}}
icon={fullScreen ?<CancelFullScreenSvg style={{ width: 20, height: 20, marginTop: 2 }} /> : <FullScreenSvg style={{ width: 20, height: 20, marginTop: 2 }} />}
type='text'
/>
</Tooltip>
</Space>
</div>
</div>
<div className='px-3'>
<Table
maxHeight={tableMaxHeight}
loading={loading||loadingElements}
columns={columns}
dataSource={tableData}
pageSize={page.pageSize} pageNum={page.pageNum} total={total??0}
onRowClick={(event, value) => {
setRow(value)
setAssetDetailParams({
visible: true,
id: value?.id,
dirId: value?.dirId,
reference: ResourceBrowseReference
})
}}
rowClassName={(record, index) => (record?.id === row?.id) ? 'yy-table-select-row' : ''}
onPaginate={(page, pageSize) => {
setRow()
setPageAndArgs({ pageNum: page, pageSize })
}}
/>
</div>
<FilterElement
{...filterElementParams}
onCancel={(refresh = false) => {
setFilterElementParams({
visible: false,
type: undefined,
reference: undefined
})
if (refresh) {
getElements()
getAssets()
}
}}
/>
<FilterElementValue
{...filterElementValueParams}
onCancel={(refresh, val) => {
setFilterElementValueParams({
visible: false,
type: undefined,
defaultValue: undefined,
})
setSearchType('keyword')
if (refresh) {
setArgsAndPage({ elementValueFilters: val })
}
}}
/>
<AssetDetailDrawer
{...assetDetailParams}
onCancel={() => {
setAssetDetailParams({
visible: false,
id: undefined,
dirId: undefined,
reference: undefined
})
}}
/>
{contextHolder}
</div>
)
}
export default FC
\ No newline at end of file
import React from 'react'
import { Modal, Button, Spin, Form, Select, TreeSelect, Input, Row, Col, Tree, Tooltip, Typography } from 'antd'
import { debounceTime, Subject } from 'rxjs'
import { dispatch } from '../../../model'
import produce from 'immer'
import { AssetManageReference, ResourceManageReference } from '../../../util/constant'
import { getAssetRange, getAssetType, inputWidth, isSzseEnv } from '../../../util'
import { AppContext } from '../../../App'
import { defaultPage, usePage } from '../../../util/hooks/page'
import Table from '../../../util/Component/Table'
import AssetItem from '../AssetManage/asset-item'
import './add-to-asset.less'
import { MetadataColumn } from './table'
import { ElementItem } from '../AssetManage/Component/AssetAction'
const FC = (props) => {
const { visible, items, onCancel } = props
const [loading, setLoading] = React.useState(false)
const [waiting, setWaiting] = React.useState(false)
const [autoFillElementValue, setAutoFillElementValue] = React.useState()
const basicRef = React.useRef()
React.useEffect(() => {
if (visible) {
fillElementValueBeforeAddAsAsset()
}
}, [visible])
const fillElementValueBeforeAddAsAsset = () => {
setLoading(true)
dispatch({
type: 'assetmanage.resourceFillElementValueBeforeAddAsAsset',
payload: {
resourceIds: (items??[]).map(item => item.id).toString()
},
callback: data => {
setLoading(false)
setAutoFillElementValue(data)
},
error: () => {
setLoading(false)
}
})
}
const close = (refresh = false) => {
setLoading(false)
setWaiting(false)
setAutoFillElementValue()
onCancel?.(refresh)
}
const save = async() => {
try {
const rows = await basicRef.current?.validate()
setWaiting(true)
if (basicRef.current?.type === 'add') {
dispatch({
type: 'assetmanage.resourceAddAsAsset',
payload: {
data: {
elements: rows.asset
},
params: {
dirIds: (rows.dirIds??[]).map(item => item.value).toString()
}
},
callback: data => {
setWaiting(false)
onCancel?.(true)
},
error: () => {
setWaiting(false)
}
})
} else {
dispatch({
type: 'assetmanage.resourceCombineDataAsset',
payload: {
params: {
resourceIds: (items??[]).map(item => item.id).toString(),
dataAssetId: rows.assetId
}
},
callback: data => {
setWaiting(false)
onCancel?.(true)
},
error: () => {
setWaiting(false)
}
})
}
} catch (e) {
}
}
const footer = React.useMemo(() => {
return [
<Button key={'cancel'}
onClick={() => close()}
>取消</Button>,
<Button key={'save'} type='primary'
onClick={() => save()}
>确定</Button>
]
}, [close, save])
return (
<Modal
className='add-to-asset'
visible={visible}
footer={footer}
width='90%'
bodyStyle={{ padding: '15px 15px 0px 15px', overflowX: 'auto', height: '80vh' }}
title='新增为资产'
centered destroyOnClose
onCancel={() => { close() }}
>
<Spin spinning={loading||waiting} >
<Basic ref={basicRef} defaultValue={autoFillElementValue} />
</Spin>
</Modal>
)
}
export default FC
export const Basic = React.forwardRef(function ({ items, defaultValue }, ref) {
const [type, setType] = React.useState('add')
const [loadingTreeData, setLoadingTreeData] = React.useState(false)
const [treeData, setTreeData] = React.useState()
const [form] = Form.useForm()
React.useImperativeHandle(ref, () => ({
validate: async () => {
return await form.validateFields()
},
type
}), [form, type])
React.useEffect(() => {
getTreeData()
}, [])
React.useEffect(() => {
if (type==='add' && defaultValue) {
form?.setFieldsValue({ asset: defaultValue })
}
}, [type, defaultValue])
const treeData1 = React.useMemo(() => {
if (treeData) {
const newTreeData = produce(treeData, draft => {
const setNode = (g) => {
g.key = g.nodeId
g.title = g.text
g.value = g.nodeId
g.children?.forEach((child) => {
setNode(child)
})
}
draft.forEach((child) => {
setNode(child)
})
})
return newTreeData
}
return undefined
}, [treeData])
const getTreeData = () => {
setLoadingTreeData(true)
dispatch({
type: 'assetmanage.queryDataAssetManageTree',
callback: data => {
setLoadingTreeData(false)
setTreeData(data)
},
error: () => {
setLoadingTreeData(false)
}
})
}
const onValuesChange = (changedValues, allValues) => {
console.log('all values', allValues)
}
return (
<Form
form={form}
labelCol={{ span: 3 }}
wrapperCol={{ span: 21 }}
autoComplete="off"
onValuesChange={onValuesChange}
>
<Form.Item
label='新增方式'
rules={[{ required: true, message: '请选择新增方式!' }]}
>
<Select
placeholder='请选择新增方式'
value={type}
onChange={(value) => {
setType(value)
}}
>
<Select.Option value='add'>新增数据资产</Select.Option>
<Select.Option value='merge'>合并到已有资产</Select.Option>
</Select>
</Form.Item>
{
type === 'add' ? <React.Fragment>
<Form.Item
name='dirIds'
label='资产目录'
rules={[{ required: true, message: '请选择资产目录!' }]}
>
<TreeSelect
loading={loadingTreeData}
dropdownStyle={{ maxHeight: 400, overflow: 'auto' }}
treeData={treeData1}
placeholder="请选择分组"
treeDefaultExpandAll
treeCheckable
treeCheckStrictly
/>
</Form.Item>
<Form.Item
name='asset'
label='资产信息'
rules={[{ required: true, message: '请填写资产信息!' }]}
>
<AssetInfoItem elements={defaultValue} />
</Form.Item>
</React.Fragment> : <React.Fragment>
<Form.Item
name='assetId'
label='选择资产'
rules={[{ required: true, message: '请选择资产!' }]}
/>
<SelectAssetItem
onChange={(rows) => {
if ((rows??[]).length > 0) {
form?.setFieldsValue({ assetId: rows[0].id })
} else {
form?.setFieldsValue({ assetId: undefined })
}
}}
/>
</React.Fragment>
}
</Form>
)
})
const AssetInfoItem = ({ value, elements, onChange }) => {
const [loading, setLoading] = React.useState(false)
const [groups, setGroups] = React.useState()
const [form] = Form.useForm()
React.useEffect(() => {
let fieldsValue = {}
for (const element of value??[]) {
fieldsValue[element.name] = element.value
}
form?.setFieldsValue(fieldsValue)
}, [value])
React.useEffect(() => {
setGroups(Array.from(new Set((elements??[]).map(item => item.type))))
}, [elements])
const onValuesChange = (changedValues, allValues) => {
const newElements = [...elements]
for (const element of newElements??[]) {
if (allValues.hasOwnProperty(element.name)) {
element.value = allValues[element.name];
}
}
onChange?.(newElements)
}
const formItemLayout = {
labelCol: {
xs: { span: 24 },
sm: { span: 3 },
},
wrapperCol: {
xs: { span: 24 },
sm: { span: 21 },
},
}
return (
<React.Fragment>
<div style={{ height: 30 }} />
<Spin spinning={loading}>
<Form {...formItemLayout} form={form} onValuesChange={onValuesChange}>
{
(groups??[]).map((group, index) => {
const filterElements = (elements??[]).filter(element => element.type===group)
return (
<div key={index}>
<div className='flex pl-common'
style={{
alignItems: 'center',
backgroundColor: '#fff',
height: 46,
borderBottom: '1px solid #f0f0f0',
}}
>
<div className='primary-bg-color' style={{ width: 3, height: 14, marginRight: 5 }} />
<div className='title-text' style={{ fontWeight: 'bold' }}>{group}</div>
</div>
<div className='px-common py-compact-common'>
{
(filterElements??[]).map((element, _index) => {
return (
<Form.Item
label={<div className='title-color'>{element.name}</div>}
name={element.name}
key={_index}
>
<ElementItem type='edit' element={element} reference={AssetManageReference} />
</Form.Item>
)
})
}
</div>
</div>
)
})
}
</Form>
</Spin>
</React.Fragment>
)
}
const SelectAssetItem = ({ onChange }) => {
const [args, setArgs] = React.useState(() => ({
params: {
page: defaultPage.pageNum,
size: defaultPage.pageSize,
keyword: undefined,
},
}))
const [loadingTreeData, setLoadingTreeData] = React.useState(false)
const [treeData, setTreeData] = React.useState()
const [node, setNode] = React.useState()
const [loading, setLoading] = React.useState(false)
const [loadingElements, setLoadingElements] = React.useState(false)
const [elements, setElements] = React.useState()
const [data, setData] = React.useState()
const [total, setTotal] = React.useState()
const [selectedRows, setSelectedRows] = React.useState([])
const $keyword = React.useMemo(() => new Subject(), [])
const [keyword, setKeyword] = React.useState()
const [page, setPage] = usePage()
const [expandedKeys, setExpandedKeys] = React.useState([])
const [autoExpandParent, setAutoExpandParent] = React.useState(false)
React.useEffect(() => {
getTreeData()
}, [])
React.useEffect(() => {
if (node) {
setSelectedRows([])
setPageAndArgs({ pageNum: 1, pageSize: page.pageSize })
}
}, [node])
const treeData1 = React.useMemo(() => {
if (treeData) {
const newTreeData = produce(treeData, draft => {
const setNode = (g) => {
g.key = g.nodeId
g.title = g.text
g.children?.forEach((child) => {
setNode(child)
})
}
draft.forEach((child) => {
setNode(child)
})
})
return newTreeData
}
return undefined
}, [treeData])
const columns = React.useMemo(() => {
const newColumns = []
let index = 0
for (const element of elements??[]) {
const col = {
title: element.name,
dataIndex: `element${++index}`,
ellipsis: true,
width: 120,
render: (text, record) => {
return (
<Tooltip title={text}>
<Typography.Text ellipsis={true}>
{text}
</Typography.Text>
</Tooltip>
);
}
}
if (element.name === '编号') {
col.width = 60
} else if (element.name === '中文名称') {
col.width = isSzseEnv ? 230 : 160
} else if (element.name === '英文名称') {
col.width = isSzseEnv ? 224 : 160
} else if (element.name === '资产路径') {
col.render = (text, record) => {
return (
<Tooltip title={text}>
<Typography.Text ellipsis={true}>
<a onClick={()=>{
let event = new Event('storage')
event.key = 'assetDirChangeEvent'
event.dirId = record.dirId
window?.dispatchEvent(event)
}}>
{text}
</a>
</Typography.Text>
</Tooltip>
)
}
} else if (element.metadataItem === '是') {
col.width = isSzseEnv ? 250 : 120
col.render = (text, record) => <MetadataColumn data={text} />
}
newColumns.push(col)
}
return newColumns
}, [elements])
const tableData = React.useMemo(() => {
const newTableData = []
for (const item of (data??[])) {
const newAsset = {...item}
let index = 0
for (const elementValue of (item.elementValues??[])) {
newAsset[`element${++index}`] = elementValue
}
newTableData.push(newAsset)
}
return newTableData
}, [data])
const setArgsAndPage = React.useCallback((params) => {
// 设置查询参数时将分页置为1
setPage(prevpg => {
setArgs((prev) => {
const newparams = params ? { ...prev.params, ...params } : undefined
return { params: { ...newparams, page: 1, size: prevpg.pageSize } }
})
return ({ ...prevpg, pageNum: 1 })
})
//eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
const setPageAndArgs = React.useCallback((page) => {
setPage(prev => ({ ...prev, ...page }))
setArgs((prev) => {
return { params: { ...prev.params, page: page.pageNum, size: page.pageSize } }
})
//eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
React.useEffect(() => {
const $$keyword = $keyword.pipe(debounceTime(1000)).subscribe((keyword) => {
setArgsAndPage({ keyword })
})
return () => {
$$keyword.unsubscribe()
}
//eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
React.useEffect(() => {
if (node?.nodeId) {
getElements()
getAssets()
}
}, [args])
const getTreeData = () => {
setLoadingTreeData(true)
dispatch({
type: 'assetmanage.queryDataAssetManageTree',
callback: data => {
setLoadingTreeData(false)
setTreeData(data)
if ((data??[]).length > 0) {
const firstNode = data[0]
setNode(firstNode)
setExpandedKeys([firstNode.nodeId])
setAutoExpandParent(true)
}
},
error: () => {
setLoadingTreeData(false)
}
})
}
const getElements = () => {
setLoadingElements(true)
dispatch({
type: 'assetmanage.listFilterElements',
payload: {
range: getAssetRange(AssetManageReference),
dataAssetType: getAssetType(AssetManageReference)
},
callback: data => {
setLoadingElements(false)
setElements(data)
},
error: () => {
setLoadingElements(false)
}
})
}
const getAssets = () => {
setLoading(true)
dispatch({
type: 'assetmanage.listDataAssetsByPage',
payload: {
data: [],
params: {
dirId: node?.nodeId,
pageNum: args.params.page,
pageSize: args.params.size,
keyword: args.params.keyword,
range: getAssetRange(AssetManageReference),
}
},
callback: data => {
setLoading(false)
setData(data?.data)
setTotal(data?.total)
},
error: () => {
setLoading(false)
}
})
}
const onTreeExpand = (expandedKeys) => {
setExpandedKeys(expandedKeys)
setAutoExpandParent(false)
}
const onTreeSelect = (selectedKeys, { selectedNodes }) => {
if (selectedKeys.length === 0 || selectedNodes.length === 0) {
return
}
setNode(selectedNodes[0])
}
return (
<Row>
<Col span={4}>
<Spin spinning={loadingTreeData}>
<Tree
className='tree'
showLine
showIcon={false}
autoExpandParent={autoExpandParent}
treeData={treeData1}
selectedKeys={node?[node.nodeId]:[]}
expandedKeys={expandedKeys}
onSelect={onTreeSelect}
onExpand={onTreeExpand}
/>
</Spin>
</Col>
<Col span={20}>
<div className='flex'
style={{
padding: '0px 0px 15px',
alignItems: 'center',
justifyContent: 'end',
}}>
<Input size="middle"
placeholder="资产要素值搜索"
value={keyword}
bordered={true} allowClear
onChange={(e) => {
const keyword = e.target.value
setKeyword(keyword)
$keyword.next((keyword??'').trim())
}}
style={{ width: inputWidth }}
/>
</div>
<Table
maxHeight='calc(80vh - 270px)'
loading={loading||loadingElements}
columns={columns}
dataSource={tableData}
pageSize={page.pageSize} pageNum={page.pageNum} total={total??0}
onPaginate={(page, pageSize) => {
setSelectedRows([])
setPageAndArgs({ pageNum: page, pageSize })
}}
rowSelection={{
type: 'radio',
selectedRowKeys: (selectedRows??[]).map(item => item.id),
onChange: (selectedRowKeys, selectedRows) => {
setSelectedRows(selectedRows)
onChange?.(selectedRows)
},
}}
/>
</Col>
</Row>
)
}
\ No newline at end of file
.add-to-asset {
.tree {
height: calc(80vh - 150px);
overflow: auto;
}
}
\ No newline at end of file
import React from "react"
import { Modal, Button, Spin, Form } from "antd"
import { getAssetRange, showMessage } from "../../../util"
import { ResourceManageReference } from "../../../util/constant"
import { dispatch } from '../../../model'
const FC = (props) => {
const { args, node, visible, onCancel } = props
const [waiting, setWaiting] = React.useState(false)
const basicRef = React.useRef()
const close = (refresh = false) => {
setWaiting(false)
onCancel?.(refresh)
}
const save = async() => {
const departments = basicRef.current?.departments
if ((departments??[]).length === 0) {
showMessage('warn', '没有所属部门')
return
}
let [recursive, dirId] = [true, node?.nodeId]
if (args.params.catalogType === 'current') {
recursive = false
}
if (args.params.catalogType === 'fullSearch') {
dirId = ''
}
setWaiting(true)
dispatch({
type: 'assetmanage.autoDistributeTask',
payload: {
data: args.params.elementValueFilters??[],
params: {
dirId,
keyword: args.params.keyword,
range: getAssetRange(ResourceManageReference),
resourceStatus: args.params.resourceStatus,
sortingStatus: args.params.sortingStatus,
recursive,
filterTodo: args.params.onlyPending,
departments: (departments??[]).toString()
}
},
callback: () => {
setWaiting(false)
onCancel(true)
},
error: () => {
setWaiting(false)
}
})
}
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='800px'
bodyStyle={{ padding: '15px', overflowX: 'auto', maxHeight: '80vh' }}
title='自动分配任务'
centered destroyOnClose
onCancel={() => { close() }}
>
<Spin spinning={waiting}>
<Basic ref={basicRef} node={node} args={args} />
</Spin>
</Modal>
)
}
export default FC
export const Basic = React.forwardRef(function ({ node, args }, ref) {
const [departments, setDepartments] = React.useState()
const [form] = Form.useForm()
React.useImperativeHandle(ref, () => ({
departments
}), [departments])
React.useEffect(() => {
if (node && args) {
getDepartments()
}
}, [node, args])
const getDepartments = () => {
let [recursive, dirId] = [true, node?.nodeId]
if (args.params.catalogType === 'current') {
recursive = false
}
if (args.params.catalogType === 'fullSearch') {
dirId = ''
}
dispatch({
type: 'assetmanage.listAutoDistributeUserDepartments',
payload: {
data: args.params.elementValueFilters??[],
params: {
dirId,
keyword: args.params.keyword,
range: getAssetRange(ResourceManageReference),
resourceStatus: args.params.resourceStatus,
sortingStatus: args.params.sortingStatus,
recursive,
filterTodo: args.params.onlyPending,
}
},
callback: (data) => {
setDepartments(data)
},
error: () => {
}
})
}
return (
<Form
form={form}
labelCol={{ span: 3 }}
wrapperCol={{ span: 21 }}
autoComplete="off"
>
<Form.Item
label='所属部门'
extra="发起流程后,自动将筛选结果中未梳理状态的资源,分配到部门资产管理员进行梳理"
>
<span>{(departments??[]).toString()}</span>
</Form.Item>
</Form>
)
})
\ No newline at end of file
import React from 'react'
import { Button, Modal, Spin, Tree, AutoComplete } from 'antd'
import { dispatch } from '../../../model'
import { generateList } from './tree'
import produce from 'immer'
import { highlightSearchContentByTerms, showMessage, showNotifaction } from '../../../util'
const FC = (props) => {
const { visible, items, onCancel } = props
const [waiting, setWaiting] = React.useState(false)
const basicRef = React.useRef()
const close = (refresh = false) => {
setWaiting(false)
onCancel?.(refresh)
}
const save = () => {
const checkedKeys = basicRef.current?.getCheckedKeys()
if ((checkedKeys??[]).length === 0) {
showMessage('warn', '请先选择资源目录')
return
}
setWaiting(true)
dispatch({
type: 'assetmanage.loadDataAssets',
payload: {
params: {
dirId: checkedKeys.join(","),
},
data: (items??[]).map(item => item.id)
},
callback: data => {
setWaiting(false)
if (data?.message) {
showNotifaction('提示', data?.message, 5)
}
onCancel?.(true)
},
error: () => {
setWaiting(false)
}
})
}
const footer = React.useMemo(() => {
return [
<Button key={'cancel'}
onClick={() => close()}
>取消</Button>,
<Button key={'save'} type='primary'
disabled={waiting}
onClick={() => save()}
>保存</Button>
]
}, [close, save, waiting])
return (
<Modal
visible={visible}
footer={footer}
width='400px'
bodyStyle={{ padding: '15px 15px 0px 15px', overflowX: 'auto', maxHeight: '80vh', height: 500 }}
title='变更目录'
centered destroyOnClose
onCancel={() => { close() }}
>
<Spin spinning={waiting}>
<Basic ref={basicRef} items={items} />
</Spin>
</Modal>
)
}
export default FC
export const Basic = React.forwardRef(function ({ items, onCheck }, ref) {
const [data, setData] = React.useState()
const [dataList, setDataList] = React.useState()
const [loading, setLoading] = React.useState(false)
const [checkedKeys, setCheckedKeys] = React.useState()
const [expandedKeys, setExpandedKeys] = React.useState([])
const [autoExpandParent, setAutoExpandParent] = React.useState(false)
const [options, setOptions] = React.useState()
const [keyword, setKeyword] = React.useState()
React.useImperativeHandle(ref, () => ({
getCheckedKeys: () => checkedKeys
}), [checkedKeys])
React.useEffect(() => {
getTreeData()
getAssetPaths()
}, [])
const treeData = React.useMemo(() => {
if (data) {
const newTreeData = produce(data, draft => {
const setNode = (g) => {
g.key = g.nodeId
g.title = g.text
g.children?.forEach((child) => {
setNode(child)
})
}
draft.forEach((child) => {
setNode(child)
})
})
return newTreeData
}
return []
}, [data])
const getAssetPaths = () => {
if ((items??[]).length > 0) {
dispatch({
type: 'assetmanage.getAssetPaths',
payload: {
dataAssetId: items[0].id,
},
callback: data => {
setCheckedKeys((data??[]).map(item => item.dirId))
}
})
}
}
const getTreeData = () => {
setLoading(true)
dispatch({
type: 'assetmanage.queryResourceManageTree',
callback: data => {
setLoading(false)
const newData = (data??[]).filter(item => item.resourceType !== 'custom')
setData(newData)
if ((newData??[]).length > 0) {
const newDataList = []
generateList(newData, newDataList)
setDataList(newDataList)
const firstNode = newData[0]
setExpandedKeys([firstNode.nodeId])
setAutoExpandParent(true)
}
},
error: () => {
setLoading(false)
}
})
}
const onTreeExpand = (expandedKeys) => {
setExpandedKeys(expandedKeys)
setAutoExpandParent(false)
}
const onTreeCheck = (values, e) => {
//同一主题下只能挂载一个目录
if (e.node?.level === 1) {
showMessage('warn', '栏目不允许勾选')
return
}
const newCheckedKeys = values.checked??[]
if (e.checked) {
const index = (dataList??[]).findIndex(item => item.nodeId === e.node?.key)
if (index !== -1) {
const currentSubjectNodeId = dataList[index].subjectNodeId
const filterChecktedKeys = newCheckedKeys.filter(key => {
if (key !== e.node?.key) {
const index = (dataList??[]).findIndex(item => item.nodeId === key)
if (index !== -1) {
return (dataList[index].subjectNodeId !== currentSubjectNodeId)
} else {
return false
}
}
return true
})
setCheckedKeys(filterChecktedKeys)
onCheck?.(filterChecktedKeys)
}
} else {
setCheckedKeys(newCheckedKeys)
onCheck?.(newCheckedKeys)
}
}
const onAutoCompleteSearch = (searchText) => {
setKeyword(searchText)
setOptions(!searchText?[]:(dataList||[]).filter(item => item.value.indexOf(searchText)!==-1))
}
const onAutoCompleteSelect = (value, option) => {
const paths = value.split('/')
setKeyword(paths[paths.length-1])
const newExpandedKeys = [...expandedKeys, option.key]
setExpandedKeys(Array.from(new Set(newExpandedKeys)))
setAutoExpandParent(true)
}
return (
<Spin spinning={loading}>
<AutoComplete
allowClear
value={keyword}
style={{ marginBottom: 10, width: '100%' }}
onSelect={onAutoCompleteSelect}
onSearch={onAutoCompleteSearch}
onClear={() => {
setKeyword()
}}
>
{
(options||[]).map((item, index) => {
return (
<AutoComplete.Option key={item.key} value={item.value}>
<div style={{ whiteSpace: 'normal' }}>
{highlightSearchContentByTerms(item.value, [keyword])}
</div>
</AutoComplete.Option>
);
})
}
</AutoComplete>
<Tree
checkable
checkStrictly
showLine
showIcon={false}
treeData={treeData}
autoExpandParent={autoExpandParent}
expandedKeys={expandedKeys}
checkedKeys={checkedKeys}
onExpand={onTreeExpand}
onCheck={onTreeCheck}
/>
</Spin>
)
})
\ No newline at end of file
import React from 'react'
import { Modal, Button, Spin, Form, Radio } from 'antd'
import { dispatch } from '../../../model'
import { checkMenuAdmit } from '../../../util'
import { AnchorDirId, AnchorId } from '../../../util/constant'
import ResourceRelateAssetDetail from './resource-relate-asset-detal'
const FC = (props) => {
const { items, visible, onCancel, type = 'single' } = props
const [waiting, setWaiting] = React.useState(false)
const basicRef = React.useRef()
const close = (refresh = false) => {
setWaiting(false)
onCancel?.(refresh)
}
const save = async() => {
try {
const rows = await basicRef.current?.validate()
setWaiting(true)
dispatch({
type: 'assetmanage.checkResources',
payload: {
params: {
resourceIds: (items??[]).map(item => item.id).toString(),
status: rows.status
},
},
callback: () => {
setWaiting(false)
onCancel(true)
},
error: () => {
setWaiting(false)
}
})
} catch (e) {
}
}
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={520}
bodyStyle={{ padding: '15px 15px 0px 15px', overflowX: 'auto', maxHeight: '80vh' }}
title='复核确认'
centered destroyOnClose
onCancel={() => { close() }}
>
<Spin spinning={waiting} >
<Basic ref={basicRef} items={items} type={type} />
</Spin>
</Modal>
)
}
export default FC
export const Basic = React.forwardRef(function ({ items, type }, ref) {
const [relations, setRelations] = React.useState([])
const [assetDetailParams, setAssetDetailParams] = React.useState({
visible: false,
id: undefined
})
const [form] = Form.useForm()
React.useImperativeHandle(ref, () => ({
validate: async () => {
return await form.validateFields()
},
}), [form])
React.useEffect(() => {
if (type === 'single') {
getRelations()
}
}, [])
const checkInfo = React.useMemo(() => {
const toBeCheckCount = (items??[]).filter(item => (item.allowButtons??[]).findIndex(item => item==='check') !== -1).length
const noNeedToBeCheckCount = (items??[]).filter(item => (item.allowButtons??[]).findIndex(item => item==='check') === -1).length
return `已选数据:${(items??[]).length} 无需复核:${noNeedToBeCheckCount} 待复核:${toBeCheckCount}`
}, [items])
const getRelations = () => {
if ((items??[]).length > 0) {
dispatch({
type: 'assetmanage.getResourceRelateDataAssetsWhenChecking',
payload: {
resourceId: items[0].id,
},
callback: data => {
setRelations(data??[])
}
})
}
}
const onValuesChange = (changedValues, allValues) => {
console.log('all values', allValues)
}
return (
<React.Fragment>
<Form
form={form}
labelCol={{ span: 4 }}
wrapperCol={{ span: 20 }}
autoComplete="off"
onValuesChange={onValuesChange}
>
{
type === 'multiple' && <Form.Item style={{ marginBottom: 5 }}>
<span>{checkInfo}</span>
</Form.Item>
}
<Form.Item
name='status'
rules={[{ required: true, message: '请选择复核方式!' }]}
style={{ marginBottom: (type === 'single')?5:15 }}
>
<Radio.Group>
<Radio value="checked"> 验证通过 </Radio>
<Radio value="changeToNotAsset"> 转为非资产 </Radio>
</Radio.Group>
</Form.Item>
{
type === 'single' && <Form.Item label='关联资产' style={{ marginBottom: 5 }}>
<div className='flex' style={{ flexDirection: 'column', lineHeight: '32px' }}>
{
(relations??[]).map((item, index) => {
return (
<a key={index} onClick={() => {
setAssetDetailParams({
visible: true,
id: item.dataAssetId
})
}}>{item.dataAssetName}</a>
);
})
}
</div>
</Form.Item>
}
</Form>
<ResourceRelateAssetDetail
{...assetDetailParams}
onCancel={() => {
setAssetDetailParams({
visible: false,
id: undefined
})
}}
/>
</React.Fragment>
)
})
\ No newline at end of file
import React from "react"
import { Modal, Button, Form, Select, Spin, Checkbox } from "antd"
import { debounceTime, Subject } from 'rxjs'
import produce from "immer"
import { dispatch } from '../../../model'
const FC = (props) => {
const { visible, items, onCancel } = props
const [waiting, setWaiting] = React.useState(false)
const basicRef = React.useRef()
const close = (refresh = false) => {
setWaiting(false)
onCancel?.(refresh)
}
const save = async() => {
try {
const rows = await basicRef.current?.validate()
setWaiting(true)
dispatch({
type: 'assetmanage.distributeTask',
payload: {
params: {
resourceIds: (items??[]).map(item => item.id).toString(),
},
data: rows
},
callback: () => {
setWaiting(false)
onCancel(true)
},
error: () => {
setWaiting(false)
}
})
} catch (e) {
}
}
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='800px'
bodyStyle={{ padding: '15px', overflowX: 'auto', maxHeight: '80vh' }}
title='任务分配'
centered destroyOnClose
onCancel={() => { close() }}
>
<Spin spinning={waiting}>
<Basic ref={basicRef} />
</Spin>
</Modal>
)
}
export default FC
export const Basic = React.forwardRef(function ({ items }, ref) {
const [noticeTypes, setNoticeTypes] = React.useState()
const [form] = Form.useForm()
React.useImperativeHandle(ref, () => ({
validate: async () => {
return await form.validateFields()
},
}), [form])
React.useEffect(() => {
getNoticeTypes()
}, [])
const getNoticeTypes = () => {
dispatch({
type: 'assetmanage.getNoticeTypes',
callback: (data) => {
const newData = produce(data, (draft) => {
draft?.forEach(item => {
item.label = item.desc
item.value = item.type
})
})
setNoticeTypes(newData)
},
error: () => {
}
})
}
const onValuesChange = (changedValues, allValues) => {
console.log('all values', allValues)
}
return (
<Form
form={form}
labelCol={{ span: 3 }}
wrapperCol={{ span: 21 }}
autoComplete="off"
onValuesChange={onValuesChange}
>
<Form.Item
label='分配用户'
name='users'
rules={[{ required: true, message: '请选择用户!' }]}
>
<HandlersItem />
</Form.Item>
<Form.Item
label='通知方式'
name='noticeTypes'
rules={[{ required: true, message: '请选择通知方式!' }]}
>
<Checkbox.Group options={noticeTypes} />
</Form.Item>
</Form>
)
})
export const HandlersItem = ({ value, onChange }) => {
const $keyword = React.useMemo(() => new Subject(), [])
const [keyword, setKeyword] = React.useState()
const [loading, setLoading] = React.useState(false)
const [users, setUsers] = React.useState()
const [options, setOptions] = React.useState([])
React.useEffect(() => {
const $$keyword = $keyword.pipe(debounceTime(1000)).subscribe((keyword) => {
setKeyword(keyword)
})
return () => {
$$keyword.unsubscribe()
}
}, [])
React.useEffect(() => {
if (keyword) {
getHandlers()
}
}, [keyword])
const getHandlers = () => {
setLoading(true)
dispatch({
type: 'assetmanage.listDistributeAbleUsersByName',
payload: {
keyword,
pageNum: 1,
pageSize: 1000
},
callback: (data) => {
setLoading(false)
setUsers(data?.data)
const newOptions = produce(data?.data, (draft) => {
draft?.forEach(item => {
item.label = item.name
item.value = item.id
})
})
setOptions(newOptions)
},
error: () => {
setLoading(false)
}
})
}
const onHandleChange = (val) => {
onChange?.((users??[]).filter(item => (val??[]).includes(item.id)))
}
return (
<Select
placeholder='请选择用户'
loading={loading}
value={value?value.map(item => item.id):undefined}
onChange={onHandleChange}
onSearch={(val) => {
$keyword.next(val)
}}
allowClear
mode='multiple'
filterOption={false}
notFoundContent={loading ? <Spin size="small" /> : null}
options={options}
/>
)
}
\ No newline at end of file
import React from 'react'
import { Tooltip, Typography, Input, Space, Button, Form, Spin, Select } from 'antd'
import { SettingOutlined } from '@ant-design/icons'
import { useClickAway } from 'ahooks'
import LocalStorage from 'local-storage'
import { dispatch } from '../../../model'
import { usePage } from '../../../util/hooks/page'
import Table from '../../../util/Component/Table'
import { getQueryParam, isSzseEnv } from '../../../util'
import '../Model/Component/EditModel.less'
import { ElementItem, MultipleItem } from '../AssetManage/Component/AssetAction'
const FC = (props) => {
const ids = getQueryParam('ids', props.location?.search)
const elementIds = getQueryParam('elementIds', props.location?.search)
const [action, setAction] = React.useState('edit')
const [waiting, setWaiting] = React.useState(false)
const editAssetsRef = React.useRef()
React.useEffect(() => {
const interval = setInterval(() => {
heartbeat()
}, 10*60*1000)
return () => {
clearInterval(interval)
}
}, [])
const heartbeat = () => {
dispatch({
type: 'datamodel.heartbeat'
});
}
const cancel = (e) => {
e.stopPropagation()
setAction('detail')
}
const edit = (e) => {
e.stopPropagation()
setAction('edit')
}
const save = async (e) => {
e.stopPropagation()
try {
await editAssetsRef.current?.save()
const modifyData = editAssetsRef.current?.getModifyData()
setWaiting(true)
dispatch({
type: 'assetmanage.resourceBatchEdit',
payload: {
data: modifyData
},
callback: data => {
setWaiting(false)
setAction('detail')
LocalStorage.set('editAssetsChange', !(LocalStorage.get('editAssetsChange')??false))
},
error: () => {
setWaiting(false)
}
})
} catch(e) {
}
}
return (
<Spin spinning={waiting}>
<div className='edit-model position-relative'>
<div className='edit-header'>
<span style={{ fontSize: 16, fontWeight: 'bold', color: '#fff' }}>资产批量编辑</span>
</div>
<div className='edit-container'>
<div className='edit-container-card' style={{ padding: '20px 20px 0' }}>
<EditAssets ref={editAssetsRef} action={action} ids={ids} elementIds={elementIds} />
</div>
</div>
<div className='edit-footer'>
{
action === 'edit' ? <Space>
<Button onClick={cancel}>取消</Button>
<Button
type='primary'
onClick={save}
danger
>
保存
</Button>
</Space> : <Space>
<Button
type='primary'
onClick={edit}
danger
>
编辑
</Button>
</Space>
}
</div>
</div>
</Spin>
)
}
export default FC
export const EditAssets = React.forwardRef(function ({ action, ids, elementIds }, ref) {
const [elements, setElements] = React.useState()
const [data, setData] = React.useState()
const [modifyData, setModifyData] = React.useState()
const [loading, setLoading] = React.useState(false)
const [editingKey, setEditingKey] = React.useState('')
const [page, setPage] = usePage()
const [form] = Form.useForm()
const [colBatchEditForm] = Form.useForm()
const tableRef = React.useRef()
const isEditing = (record) => record.id === editingKey
React.useImperativeHandle(ref, () => ({
save: async () => {
await save()
},
getModifyData: () => {
return {
elementList: elements,
rowDataList: modifyData
}
},
}), [form, editingKey, modifyData, elements])
const getColumnBatchEditProps = (element, dataIndex) => ({
filterDropdown: ({ confirm }) => {
return (
<div
style={{
width: 320,
padding: 8,
}}
onKeyDown={(e) => e.stopPropagation()}
>
<h4 className='mb-3'>批量编辑</h4>
<Form
form={colBatchEditForm}
labelCol={{ span: 6 }}
wrapperCol={{ span: 18 }}
autoComplete="off"
>
<Form.Item
name={dataIndex}
label={element?.name}
rules={[{ required: true, message: `请输入${element?.name}!` }]}
style={{ marginBottom: 15 }}
>
<ElementItem type='edit' element={element} />
</Form.Item>
</Form>
<div className='flex' style={{ justifyContent: 'end' }}>
<Space>
<Button
size='small'
onClick={() => {
confirm?.()
}}
>
取消
</Button>
<Button
size='small'
type="primary"
onClick={ async () => {
try {
await save()
const rows = await colBatchEditForm.validateFields()
const newModifyData = [...modifyData]
const index = parseInt(dataIndex.slice('element'.length))
for (const item of newModifyData) {
item.values[index] = rows[`${dataIndex}`]
}
setModifyData(newModifyData)
confirm?.()
} catch (e) {
}
}}
>
确定
</Button>
</Space>
</div>
</div>
)
},
filterIcon: (filtered) => (
<SettingOutlined/>
),
});
const columns = React.useMemo(() => {
const newColumns = []
let index = 0
for (const element of elements??[]) {
let col = {
title: element.name,
dataIndex: `element${index}`,
ellipsis: true,
width: 120,
render: (text, record) => {
return (
<Tooltip title={text}>
<Typography.Text ellipsis={true}>
{text}
</Typography.Text>
</Tooltip>
);
}
}
if (element.name === '中文名称') {
col.width = isSzseEnv ? 230 : 160
} else if (element.name === '英文名称') {
col.width = isSzseEnv ? 224 : 160
}
col.onCell = (record) => ({
element,
dataIndex: col.dataIndex,
colTitle: col.title,
editing: isEditing(record),
})
if (action === 'edit') {
col = {
...col,
...getColumnBatchEditProps(element, `element${index}`)
}
}
index++
newColumns.push(col)
}
return newColumns
}, [elements, getColumnBatchEditProps, isEditing, action])
const tableData = React.useMemo(() => {
const newTableData = []
for (const item of (modifyData??[])) {
const newAsset = {...item}
let index = 0
for (const elementValue of (item.values??[])) {
newAsset[`element${index}`] = elementValue
index++
}
newTableData.push(newAsset)
}
return newTableData
}, [modifyData])
useClickAway(() => {
save()
}, tableRef)
React.useEffect(() => {
getAssets()
}, [ids, elementIds, action])
React.useEffect(() => {
setEditingKey('')
}, [action])
const getAssets = () => {
setLoading(true)
dispatch({
type: 'assetmanage.getResourceBatchEditInfo',
payload: {
elementIds: elementIds,
resourceIds: ids,
},
callback: data => {
setLoading(false)
setElements(data.elementList)
setData(data.rowDataList)
setModifyData(data.rowDataList)
},
error: () => {
setLoading(false)
}
})
}
const save = async () => {
if (!editingKey) {
return true
}
try {
const row = await form.validateFields()
setEditingKey(prevEditingKey => {
const newModifyData = [...modifyData]
const index = newModifyData.findIndex(item => item.id === prevEditingKey)
if (index > -1) {
const item = newModifyData[index]
for (const dataIndex in row) {
const elementIndex = parseInt(dataIndex.slice('element'.length))
item.values[elementIndex] = row[`${dataIndex}`]
}
setModifyData(newModifyData)
return ''
}
return prevEditingKey
})
} catch (e) {
throw new Error()
}
}
return (
<div ref={tableRef}>
<Form form={form} component={false}>
<Table
maxHeight='calc(100vh - 245px)'
loading={loading}
columns={columns}
dataSource={tableData}
bodyCell={EditableCell}
pageSize={page.pageSize} pageNum={page.pageNum} total={(data??[]).length}
onRowClick={async (event, value) => {
if (action==='edit' && value?.id !== editingKey) {
try {
await save()
form?.setFieldsValue(value)
setEditingKey(value?.id)
} catch(e) {
}
}
}}
onPaginate={(page, pageSize) => {
setPage({ pageNum: page, pageSize })
}}
/>
</Form>
</div>
)
})
export const EditableCell = ({
editing,
dataIndex,
colTitle,
element,
require,
children,
...restProps
}) => {
let editingComponent = null
if (editing) {
editingComponent = (
<Form.Item
name={dataIndex}
style={{ margin: 0 }}
rules={[
{
required: (require===null)?false:require,
message: `请输入${colTitle}!`,
},
]}
>
<ElementItem type='edit' element={element} />
</Form.Item>
)
}
return (
<td {...restProps}>
{editing ? (
editingComponent
) : (
children
)}
</td>
)
}
\ No newline at end of file
import React from 'react'
import { Modal, Button, Form, Checkbox, Spin, Row, Col } from 'antd'
import { getAssetType } from '../../../util'
import { dispatch } from '../../../model'
const FC = (props) => {
const { visible, type, defaultValue, onCancel } = props
const [loading, setLoading] = React.useState(false)
const [data, setData] = React.useState()
const basicRef = React.useRef()
React.useEffect(() => {
if (visible) {
getElementValues()
}
}, [visible])
const getElementValues = () => {
setLoading(true)
dispatch({
type: 'assetmanage.getElementValues',
payload: {
dataAssetType: getAssetType(type)
},
callback: data => {
setLoading(false)
setData(data)
},
error: () => {
setLoading(false)
}
})
}
const close = (refresh = false, val = undefined) => {
setLoading(false)
setData()
onCancel?.(refresh, val)
}
const save = async () => {
try {
const rows = await basicRef.current?.validate()
close(true, rows)
}
catch (e) {
}
}
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}
bodyStyle={{ padding: '15px 15px 0px', overflowX: 'auto', maxHeight: '80vh' }}
title='属性值过滤'
centered destroyOnClose
onCancel={() => { close() }}
>
<Spin spinning={loading}>
<Basic ref={basicRef} data={data} defaultValue={defaultValue} />
</Spin>
</Modal>
)
}
export default FC
export const Basic = React.forwardRef(function ({ data, defaultValue }, ref) {
const [form] = Form.useForm()
React.useImperativeHandle(ref, () => ({
validate: async () => {
const rows = await form.validateFields()
const newRows = []
for (const id in rows) {
newRows.push({
elementId: id,
elementValues: rows[`${id}`]
})
}
return newRows
},
}), [form])
React.useEffect(() => {
const newFiledsValue = {}
for (const item of defaultValue) {
newFiledsValue[item.elementId] = item.elementValues
}
form?.setFieldsValue(newFiledsValue)
}, [defaultValue])
return (
<Form
form={form}
labelCol={{ span: 5 }}
wrapperCol={{ span: 19 }}
autoComplete="off"
>
{
(data??[]).map((item, index) => {
return (
<Form.Item
name={item.id}
label={item.name}
>
<Checkbox.Group style={{ width: '100%' }}>
<Row>
{
(item.value?.content??[]).map((item, index) => {
return (
<Col span={6} key={index}>
<Checkbox
value={item}
style={{
lineHeight: '32px',
}}
>
{item}
</Checkbox>
</Col>
)
})
}
</Row>
</Checkbox.Group>
</Form.Item>
)
})
}
</Form>
)
})
\ No newline at end of file
import React from "react"
import classNames from 'classnames'
import { ResizableBox } from 'react-resizable'
import { Form } from "antd"
import { CaretLeftOutlined, CaretRightOutlined } from '@ant-design/icons'
import Tree from './tree'
import Separate from '../AssetManage/Component/Separate'
import NodeDetail from '../AssetManage/Component/AssetDirectory'
import ResourceList from './table'
import AssetDetail from '../AssetManage/Component/AssetAction'
import '../AssetManage/index.less'
import { ResourceManageReference } from "../../../util/constant"
const FC = (props) => {
const [collapseTree, setCollapseTree] = React.useState(false)
const [node, setNode] = React.useState()
const [asset, setAsset] = React.useState()
const [assetListFullScreen, setAssetListFullScreen] = React.useState(false)
const [directoryChanged, setDirectoryChanged] = React.useState(false)
const [form] = Form.useForm()
const onTreeClick = (value) => {
setDirectoryChanged(!directoryChanged)
setNode(value)
}
const onResourceListClick = (value) => {
setAsset(value)
}
const onResourceListFullScreenChange = (value) => {
setAssetListFullScreen(value)
}
const treeToggleClick = () => {
setCollapseTree(!collapseTree)
}
const rootClasses = classNames('asset-manage', {
'asset-manage-collapse': collapseTree
})
const middleClasses = classNames('middle', {
'middle-fullscreen': assetListFullScreen
})
return (
<div className={rootClasses}>
<ResizableBox
className='left'
width={230}
height={Infinity}
axis='x'
minConstraints={[230, Infinity]}
maxConstraints={[Infinity, Infinity]}
>
<Tree onClick={onTreeClick} {...props} />
</ResizableBox>
{
!collapseTree && <Separate width={15} />
}
<div className={middleClasses}>
<NodeDetail
reference={ResourceManageReference}
id={node?.nodeId}
assetCount={node?.dataAssetAndSubDirCount}
directoryChanged={directoryChanged}
/>
<Separate height={15} />
<ResourceList
node={node}
onClick={onResourceListClick}
onFullScreenChange={onResourceListFullScreenChange}
{...props}
/>
<div className='tree-toggle' onClick={treeToggleClick}>
{ !collapseTree ? <CaretLeftOutlined /> : <CaretRightOutlined /> }
</div>
</div>
<Separate width='15px' />
<div className='right'>
<AssetDetail
form={form}
id={asset?.id}
dirId={asset?.dirId}
action='detail'
reference={ResourceManageReference}
/>
</div>
</div>
)
}
export default FC
\ No newline at end of file
import React from "react"
import { Modal, Button, Form, Select, Spin } from "antd"
import { dispatch } from '../../../model'
import { HandlersItem } from "./distribute-task"
const FC = (props) => {
const { visible, items, onCancel } = props
const [waiting, setWaiting] = React.useState(false)
const basicRef = React.useRef()
const close = (refresh = false) => {
setWaiting(false)
onCancel?.(refresh)
}
const save = async() => {
try {
const rows = await basicRef.current?.validate()
setWaiting(true)
dispatch({
type: 'assetmanage.reDistributeTask',
payload: {
params: {
resourceIds: (items??[]).map(item => item.id).toString(),
},
data: rows.users??[]
},
callback: () => {
setWaiting(false)
onCancel(true)
},
error: () => {
setWaiting(false)
}
})
} catch (e) {
}
}
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='800px'
bodyStyle={{ padding: '15px', overflowX: 'auto', maxHeight: '80vh' }}
title='任务转分配'
centered destroyOnClose
onCancel={() => { close() }}
>
<Spin spinning={waiting}>
<Basic ref={basicRef} />
</Spin>
</Modal>
)
}
export default FC
export const Basic = React.forwardRef(function ({ items }, ref) {
const [form] = Form.useForm()
React.useImperativeHandle(ref, () => ({
validate: async () => {
return await form.validateFields()
},
}), [form])
const onValuesChange = (changedValues, allValues) => {
console.log('all values', allValues)
}
return (
<Form
form={form}
labelCol={{ span: 3 }}
wrapperCol={{ span: 21 }}
autoComplete="off"
onValuesChange={onValuesChange}
>
<Form.Item
label='分配用户'
name='users'
rules={[{ required: true, message: '请选择用户!' }]}
>
<HandlersItem />
</Form.Item>
</Form>
)
})
\ No newline at end of file
import React from 'react'
import { Button, Tooltip } from 'antd'
import { SettingFilled } from '@ant-design/icons'
import { AppContext } from '../../../App'
import { highlightSearchContentByTerms, IsArr } from '../../../util'
import { MetadataColumnTooltipTitle } from '../AssetResourceManage/table'
const FC = ({ value, onChange, readonly = true, terms = [] }) => {
const [decodeData, setDecodeData] = React.useState()
const app = React.useContext(AppContext)
React.useMemo(() => {
if (value) {
try {
setDecodeData(JSON.parse(value))
} catch(error) {
setDecodeData(value)
}
} else {
setDecodeData()
}
}, [value])
return (
<div className='flex' style={{ alignItems: 'center' }}>
{
(typeof decodeData === 'string') ? <span style={{ marginRight: 5 }}>
{highlightSearchContentByTerms(decodeData, terms)}
</span> : ((IsArr(decodeData)&&decodeData.length>0) ? <span>
<Tooltip
overlayClassName='tooltip-common'
title={<MetadataColumnTooltipTitle data={decodeData} />}
>
<a onClick={() => {
app?.setGlobalState?.({
message: 'data-govern-show-metadata-message',
data: decodeData[0]
})
}}
style={{ marginRight: 5 }}
>
{highlightSearchContentByTerms(decodeData[0].enName, terms)}
</a>
</Tooltip>
</span> : null)
}
{
!readonly && <Button type='text' icon={<SettingFilled />} onClick={() => {
app?.setGlobalState?.({
message: 'data-govern-show-metadata-list-message',
data: (IsArr(decodeData)&&decodeData.length>0) ? decodeData[0] : {}
})
}} />
}
</div>
)
}
export default FC
\ No newline at end of file
import React from 'react'
import { Modal, Spin, Descriptions, Divider } from 'antd'
import { dispatch } from '../../../model'
import { ElementItem } from '../AssetManage/Component/AssetAction'
import { AssetManageReference } from '../../../util/constant'
const FC = (props) => {
const { visible, id, onCancel } = props
const [loading, setLoading] = React.useState(false)
const [data, setData] = React.useState()
React.useEffect(() => {
if (visible) {
getDataAssetDetail()
}
}, [visible])
const getDataAssetDetail = () => {
setLoading(true)
dispatch({
type: 'assetmanage.getResourceRelateDataAssetDetail',
payload: {
relatedDataAssetId: id
},
callback: data => {
setLoading(false)
setData(data?.elements)
},
error: () => {
setLoading(false)
}
})
}
const close = (refresh = false) => {
setLoading(false)
onCancel?.()
}
return (
<Modal
visible={visible}
footer={null}
width='1000px'
bodyStyle={{ padding: '0px 15px 0px 15px', overflowX: 'auto', height: '80vh' }}
title='关联资产详情'
centered destroyOnClose
onCancel={() => { close() }}
>
<Spin spinning={loading}>
<Basic data={data} />
</Spin>
</Modal>
)
}
export default FC
export const Basic = React.forwardRef(function ({ data }, ref) {
const [groups, setGroups] = React.useState()
React.useEffect(() => {
setGroups(Array.from(new Set((data??[]).map(item => item.type))))
}, [data])
return (
<React.Fragment>
{
(groups??[]).map((group, index) => {
const filterElements = (data??[]).filter(element => element.type===group)
return (
<div key={index}>
<div className='flex' style={{ alignItems: 'center', padding: '15px 0' }}>
<div style={{ width: 3, height: 14, backgroundColor: '#0069AC', marginRight: 5 }} />
<span style={{ fontWeight: 'bold', color: '#464646' }}>{group}</span>
</div>
<Descriptions column={2}>
{
(filterElements??[]).map((item, index) => {
return (
<Descriptions.Item label={item.name||''} key={index}>
<ElementItem element={item} value={item.value} />
</Descriptions.Item>
);
})
}
</Descriptions>
<Divider style={{ margin: '10px 0' }} />
</div>
)
})
}
</React.Fragment>
)
})
\ No newline at end of file
import React from 'react'
import { Modal, Button, Spin, Checkbox, Switch, Typography, Row, Col } from 'antd'
import { dispatch } from '../../../model'
import { getAssetRange, getAssetType, showMessage } from '../../../util'
const FC = (props) => {
const { type, visible, onCancel } = props
const [loading, setLoading] = React.useState(false)
const [elements, setElements] = React.useState()
const basicRef = React.useRef()
React.useEffect(() => {
if (visible) {
getElements()
}
}, [visible])
const getElements = () => {
setLoading(true)
dispatch({
type: 'assetmanage.listElements',
payload: {
params: {
range: getAssetRange(type),
dataAssetType: getAssetType(type)
}
},
callback: data => {
setLoading(false)
setElements((data??[]).filter(item => item.supportBatchEdit==='是'))
},
error: () => {
setLoading(false)
}
})
}
const close = (refresh = false, value = undefined) => {
setLoading(false)
onCancel?.(refresh, value)
}
const save = () => {
const selectedKeys = basicRef.current?.getSelectedKeys
if ((selectedKeys??[]).length === 0) {
showMessage('warn', '请先选择要批量编辑的属性')
return
}
onCancel(true, selectedKeys)
}
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={520}
bodyStyle={{ padding: '15px 15px 0px 15px', overflowX: 'auto', height: '80vh' }}
title='选择批量编辑属性'
centered destroyOnClose
onCancel={() => { close() }}
>
<Spin spinning={loading} >
<Basic ref={basicRef} elements={elements} />
</Spin>
</Modal>
)
}
export default FC
export const Basic = React.forwardRef(function ({ elements }, ref) {
const [attributes, setAttributes] = React.useState()
const [selectedKeys, setSelectedKeys] = React.useState([])
React.useImperativeHandle(ref, () => ({
getSelectedKeys: selectedKeys,
}), [selectedKeys])
React.useEffect(() => {
setAttributes(Array.from(new Set((elements??[]).map(item => item.type))))
}, [elements])
const onCheckAllChange = (checked) => {
if (checked) {
setSelectedKeys((elements??[]).map(item => item.id))
} else {
setSelectedKeys([])
}
}
const onCheckChange = (e) => {
if (e.target.checked) {
setSelectedKeys([...selectedKeys, e.target.value]);
} else {
const index = selectedKeys.findIndex(key => key === e.target.value);
selectedKeys.splice(index, 1)
setSelectedKeys([...selectedKeys]);
}
}
return (
<div>
<div className='flex'>
<Switch
checkedChildren="全不选"
unCheckedChildren="全选"
onChange={onCheckAllChange}
style={{ marginLeft: 'auto' }}
/>
</div>
<div className='mt-3' style={{ maxHeight: 450, overflow: 'auto' }}>
{
(attributes??[]).map((item, index) => {
return (
<div key={index}>
<div className='flex' style={{ alignItems: 'center', padding: '15px 0' }}>
<div style={{ width: 3, height: 14, backgroundColor: '#0069AC', marginRight: 5 }} />
<span style={{ fontWeight: 'bold', color: '#464646' }}>{item}</span>
</div>
<Row>
{
(elements??[]).filter(element => element.type === item).map((element, _index) => {
return (
<Col className='mb-3' key={_index} md={6}>
<div className='flex'>
<Checkbox
checked={(selectedKeys??[]).indexOf(element.id)!==-1}
value={element.id}
onChange={onCheckChange}
>
</Checkbox>
<Typography.Paragraph
className='ml-1'
title={element.name}
ellipsis
>
{element.name}
</Typography.Paragraph>
</div>
</Col>
);
})
}
</Row>
</div>
)
})
}
</div>
</div>
)
})
\ No newline at end of file
import React from 'react'
import classNames from 'classnames'
import { Tooltip, Typography, Space, Dropdown, Button, Menu, Checkbox, Input, Select, Modal, Table as AntdTable } from 'antd'
import ResizeObserver from 'rc-resize-observer'
import { debounceTime, Subject } from 'rxjs'
import { DownOutlined, UpOutlined } from "@ant-design/icons"
import LocalStorage from 'local-storage'
import { defaultPage, usePage } from '../../../util/hooks/page'
import Table from '../../../util/Component/Table'
import { dispatch } from '../../../model'
import { getAssetRange, getAssetType, getQueryParam, IsArr, isSzseEnv, showMessage, showNotifaction } from '../../../util'
import { AssetManageReference, ResourceManageReference } from '../../../util/constant'
import PermissionButton from '../../../util/Component/PermissionButton'
import PermissionMenuItem from '../../../util/Component/PermissionMenuItem'
import { FullScreenSvg, CancelFullScreenSvg } from '../AssetManage/Component/AssetSvg'
import AddAsset from '../AssetManage/Component/AddAssetModel'
import AddToAsset from './add-to-asset'
import ImportAsset from '../AssetManage/Component/ImportAssetDrawer'
import ChangeCatalog from './change-catalog'
import AssetDelete from '../AssetManage/Component/AssetDeleteModal'
import FilterElement from '../AssetManage/Component/FilterElementModal'
import FilterElementValue from './filter-element-value'
import SelectBatchEditElements from './select-batch-edit-elements'
import { AssetDirectorySubject } from '../AssetManage/Component/AssetDirectory'
import { AssetActionSubject } from '../AssetManage/Component/AssetAction'
import TagCell from '../Model/Component/tag-help'
import DistributeTask from './distribute-task'
import RedistributeTask from './redistribute-task'
import AutoDistributeTask from './auto-distribute-task'
import CheckAssets from './check-assets'
import '../AssetManage/table.less'
const operationMap = {
addAsAsset: '新增为资产',
distribute: '分配',
reDistribute: '转分配',
check: '复核',
changeToNotAsset: '转为非资产',
changeToUncombed: '转为未梳理',
sync: '同步',
notSync: '不同步',
}
const FC = (props) => {
const { type = ResourceManageReference, node, onClick, onFullScreenChange } = props
const [args, setArgs] = React.useState(() => ({
params: {
page: defaultPage.pageNum,
size: defaultPage.pageSize,
onlyPending: false,
catalogType: 'currentRecursive',
keyword: undefined,
sortingStatus: undefined,
resourceStatus: undefined,
elementValueFilters: []
},
}))
const [fullScreen, setFullScreen] = React.useState(false)
const [compact, setCompact] = React.useState(false)
const [loading, setLoading] = React.useState(false)
const [loadingElements, setLoadingElements] = React.useState(false)
const [columns, setColumns] = React.useState()
const [elements, setElements] = React.useState()
const [data, setData] = React.useState()
const [total, setTotal] = React.useState()
const [selectedRows, setSelectedRows] = React.useState([])
const [row, setRow] = React.useState()
const [rightRow, setRightRow] = React.useState()
const [permissions, setPermissions] = React.useState([])
const [loadingSortStatus, setLoadingSortStatus] = React.useState(false)
const [sortStatus, setSortStatus] = React.useState()
const [loadingRelatedMetadataStatus, setLoadingRelatedMetadataStatus] = React.useState(false)
const [relatedMetadataStatus, setRelatedMetadataStatus] = React.useState()
const $keyword = React.useMemo(() => new Subject(), [])
const [keyword, setKeyword] = React.useState()
const [searchType, setSearchType] = React.useState('keyword')
const [resoureTagMap, setResourceTagMap] = React.useState()
const [addAssetParams, setAddAssetParams] = React.useState({
visible: false,
reference: undefined,
nodeId: undefined
})
const [addToAssetParams, setAddToAssetParams] = React.useState({
visible: false,
items: undefined
})
const [importAssetParams, setImportAssetParams] = React.useState({
visible: false,
reference: undefined,
nodeId: undefined
})
const [changeCatalogParams, setChangeCatalogParams] = React.useState({
visible: false,
items: undefined
})
const [assetDeleteParams, setAssetDeleteParams] = React.useState({
visible: false
})
const [filterElementParams, setFilterElementParams] = React.useState({
visible: false,
type: undefined,
reference: undefined
})
const [filterElementValueParams, setFilterElementValueParams] = React.useState({
visible: false,
type: undefined,
defaultValue: undefined,
})
const [selectBatchEditElementsParams, setSelectBatchEditElementsParams] = React.useState({
visible: false,
type: undefined
})
const [distributeTaskParams, setDistributeTaskParams] = React.useState({
visible: false,
items: undefined
})
const [redistributeTaskParams, setRedistributeTaskParams] = React.useState({
visible: false,
items: undefined
})
const [autoDistributeTaskParams, setAutoDistributeTaskParams] = React.useState({
visible: false,
node: undefined,
args: undefined
})
const [checkAssetsParams, setCheckAssetsParams] = React.useState({
visible: false,
type: undefined,
items: undefined
})
const [page, setPage] = usePage()
const [modal, contextHolder] = Modal.useModal()
const locationIdRef = React.useRef(getQueryParam('id', props?.location?.search))
const locationDidRef = React.useRef(getQueryParam('did', props?.location?.search))
const taskIdRef = React.useRef(getQueryParam('taskId', props?.location?.search))
const setArgsAndPage = React.useCallback((params) => {
// 设置查询参数时将分页置为1
setPage(prevpg => {
setArgs((prev) => {
const newparams = params ? { ...prev.params, ...params } : undefined
return { params: { ...newparams, page: 1, size: prevpg.pageSize } }
})
return ({ ...prevpg, pageNum: 1 })
})
//eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
const setPageAndArgs = React.useCallback((page) => {
setPage(prev => ({ ...prev, ...page }))
setArgs((prev) => {
return { params: { ...prev.params, page: page.pageNum, size: page.pageSize } }
})
//eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
const storageChange = (e) => {
if (e.key === 'editAssetsChange') {
getAssets()
} else if (e.key === 'assetRelationOnClickEvent') {
locationIdRef.current = e.relation?.dataAssetId
locationDidRef.current = e.relation?.dirId
} else if (e.key === 'assetPathOnClickEvent') {
locationIdRef.current = e.id
locationDidRef.current = e.dirId
}
}
React.useEffect(() => {
getElements()
getResourceSortingStatus()
getResourceRelatedMetadataStatus()
const $$keyword = $keyword.pipe(debounceTime(1000)).subscribe((keyword) => {
setArgsAndPage({ keyword })
})
const $$assetDirectorySubject = AssetDirectorySubject.subscribe((props) => {
if (props.type === 'element-change') {
getElements()
getAssets()
} else if (props.type === 'element-value-change') {
getAssets()
}
})
const $$assetActionSubject = AssetActionSubject.subscribe((props) => {
if (props.type === 'asset-change') {
getAssets()
}
})
window?.addEventListener("storage", storageChange)
return () => {
$$keyword.unsubscribe()
$$assetDirectorySubject.unsubscribe()
$$assetActionSubject.unsubscribe()
window?.removeEventListener("storage", storageChange)
}
//eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
React.useEffect(() => {
if (node) {
getPermissions()
setRow()
setSelectedRows([])
if (locationIdRef.current && locationDidRef.current) {
getDataAssetLocation()
} else if (taskIdRef.current) {
setKeyword(taskIdRef.current)
setArgsAndPage({
catalogType: 'fullSearch',
keyword: taskIdRef.current,
})
} else {
setPageAndArgs({ pageNum: 1, pageSize: page.pageSize })
}
}
}, [node])
React.useEffect(() => {
if (node?.nodeId) {
getAssets()
}
}, [args])
React.useEffect(() => {
if (data) {
getResourceTag()
}
}, [data])
const [addAble, addAsAssetAble, distributeAble, batchEditAble,checkAble, importAble, exportAble, changeDirectoryAble, deleteAble] = React.useMemo(() => {
let [_addAble, _addAsAssetAble, _distributeAble, _batchEditAble, _checkAble, _importAble, _exportAble, _changeDiretoryAble, _deleteAble] = [false, false, false, false, false, false, false, false, false]
_addAble = (permissions??[]).findIndex(item => item==='add') !== -1
if ((selectedRows??[]).length === 0) {
_importAble = (permissions??[]).findIndex(item => item==='import') !== -1
_exportAble = (permissions??[]).findIndex(item => item==='export') !== -1
} else {
let [allowImport, allowExport] = [true, true]
for (const row of selectedRows??[]) {
const importIndex = (row.allowButtons??[]).findIndex(item => item==='import')
const exportIndex = (row.allowButtons??[]).findIndex(item => item==='export')
if (importIndex === -1) {
allowImport = false
}
if (exportIndex === -1) {
allowExport = false
}
}
_importAble = allowImport
_exportAble = allowExport
}
let [allowAddAsAsset, allowDistribute, allowBatchEdit, allowCheck, allowChangeDirectory, allowDelete] = [true, true, true, true, true, true]
for (const row of selectedRows??[]) {
const addAsAssetIndex = (row.allowButtons??[]).findIndex(item => item==='addAsAsset')
const distributeIndex = (row.allowButtons??[]).findIndex(item => item==='distribute')
const batchEditIndex = (row.allowButtons??[]).findIndex(item => item==='batchEdit')
const checkIndex = (row.allowButtons??[]).findIndex(item => item==='check')
const changeDirecotoryIndex = (row.allowButtons??[]).findIndex(item => item==='changeDir')
const deleteIndex = (row.allowButtons??[]).findIndex(item => item==='delete')
if (addAsAssetIndex === -1) {
allowAddAsAsset = false
}
if (distributeIndex === -1) {
allowDistribute = false
}
if (batchEditIndex === -1) {
allowBatchEdit = false
}
if (checkIndex === -1) {
allowCheck = false
}
if (changeDirecotoryIndex === -1) {
allowChangeDirectory = false
}
if (deleteIndex === -1) {
allowDelete = false
}
}
_addAsAssetAble = allowAddAsAsset
_distributeAble = allowDistribute
_batchEditAble = allowBatchEdit
_checkAble = allowCheck
_changeDiretoryAble = allowChangeDirectory
_deleteAble = allowDelete
return [_addAble, _addAsAssetAble, _distributeAble, _batchEditAble, _checkAble, _importAble, _exportAble, _changeDiretoryAble, _deleteAble]
}, [permissions, selectedRows])
const menuData = React.useMemo(() => {
const newMenuData = []
for (const key of rightRow?.allowButtons??[]) {
if (operationMap[`${key}`]) {
newMenuData.push(operationMap[`${key}`])
}
}
return newMenuData
}, [rightRow])
const notElementCol = [
{
title: '梳理状态',
dataIndex: 'sortingStatus',
ellipsis: true,
width: 120,
render: (_, record) => record.resourceExtraAttribute?.sortingStatus
},
{
title: '当前处理人',
dataIndex: 'currentProcessor',
ellipsis: true,
width: 120,
render: (_, record) => record.resourceExtraAttribute?.currentProcessor
},
{
title: '任务编号',
dataIndex: 'taskNO',
ellipsis: true,
width: 120,
render: (_, record) => <a onClick={() => {
window.open(`/center-home/menu/task-manage?id=${record.resourceExtraAttribute?.taskNO}`)
}}>
{record.resourceExtraAttribute?.taskNO}
</a>
},
{
title: '资源状态',
dataIndex: 'resourceStatus',
ellipsis: true,
width: 120,
render: (_, record) => record.resourceExtraAttribute?.resourceStatus
},
{
title: '是否资产',
dataIndex: 'transferDataAsset',
ellipsis: true,
width: 120,
render: (_, record) => record.resourceExtraAttribute?.transferDataAsset
},
{
title: '标签',
dataIndex: 'tag',
width: 360,
className: 'table-tag-cell',
render: (_, record) => <TagCell
id={record.id}
did={record.dirId}
type='dataAsset'
tags={resoureTagMap?.[`${record.id}`]}
/>
}
]
React.useEffect(() => {
const newColumns = []
let index = 0
for (const element of elements??[]) {
const col = {
title: element.name,
dataIndex: `element${++index}`,
ellipsis: true,
width: 120,
render: (text, record) => {
return (
<Tooltip title={text}>
<Typography.Text ellipsis={true}>
{text}
</Typography.Text>
</Tooltip>
);
}
}
if (element.name === '编号') {
col.width = 60
} else if (element.name === '中文名称') {
col.width = isSzseEnv ? 230 : 160
} else if (element.name === '英文名称') {
col.width = isSzseEnv ? 224 : 160
} else if (element.name === '资源路径') {
col.render = (text, record) => {
return (
<Tooltip title={text}>
<Typography.Text ellipsis={true}>
<a onClick={()=>{
let event = new Event('storage')
event.key = 'assetDirChangeEvent'
event.dirId = record.dirId
window?.dispatchEvent(event)
}}>
{text}
</a>
</Typography.Text>
</Tooltip>
)
}
} else if (element.metadataItem === '是') {
col.width = isSzseEnv ? 250 : 120
col.render = (text, record) => <MetadataColumn data={text} />
}
newColumns.push(col)
}
setColumns([...newColumns, ...notElementCol])
}, [elements, resoureTagMap])
const tableData = React.useMemo(() => {
const newTableData = []
for (const item of (data??[])) {
const newAsset = {...item}
let index = 0
for (const elementValue of (item.elementValues??[])) {
newAsset[`element${++index}`] = elementValue
}
newTableData.push(newAsset)
}
return newTableData
}, [data])
const tableMaxHeight = React.useMemo(() => {
return fullScreen ? 'calc(100vh - 209px - 72px)' : 'calc(100vh - 209px - 123px - 15px - 72px)'
}, [fullScreen])
const getPermissions = () => {
dispatch({
type: 'assetmanage.getPrivilegeByRangeAndDirId',
payload: {
range: getAssetRange(type),
optionId: node?.nodeId
},
callback: data => {
setPermissions(data)
}
})
}
const getResourceSortingStatus = () => {
setLoadingSortStatus(true)
dispatch({
type: 'assetmanage.getResourceSortingStatus',
callback: data => {
setLoadingSortStatus(false)
setSortStatus(data)
},
error: () => {
setLoadingSortStatus(false)
}
})
}
const getResourceRelatedMetadataStatus = () => {
setLoadingRelatedMetadataStatus(true)
dispatch({
type: 'assetmanage.getResourceRelatedMetadataStatus',
callback: data => {
setLoadingRelatedMetadataStatus(false)
setRelatedMetadataStatus(data)
},
error: () => {
setLoadingRelatedMetadataStatus(false)
}
})
}
const getElements = () => {
setLoadingElements(true)
dispatch({
type: 'assetmanage.listFilterElements',
payload: {
range: getAssetRange(ResourceManageReference),
dataAssetType: getAssetType(ResourceManageReference)
},
callback: data => {
setLoadingElements(false)
setElements(data)
},
error: () => {
setLoadingElements(false)
}
})
}
const getAssets = () => {
setLoading(true)
let [recursive, dirId] = [true, node?.nodeId]
if (args.params.catalogType === 'current') {
recursive = false
}
if (args.params.catalogType === 'fullSearch') {
dirId = ''
}
dispatch({
type: 'assetmanage.listDataResourcesByPage',
payload: {
data: args.params.elementValueFilters??[],
params: {
dirId,
pageNum: args.params.page,
pageSize: args.params.size,
keyword: args.params.keyword,
range: getAssetRange(ResourceManageReference),
resourceStatus: args.params.resourceStatus,
sortingStatus: args.params.sortingStatus,
recursive,
filterTodo: args.params.onlyPending,
isCustomDir: (node?.resourceType==='custom'||node?.type==='custom')
}
},
callback: data => {
setLoading(false)
setData(data?.data)
setTotal(data?.total)
if (locationIdRef.current) {
const index = (data?.data??[]).findIndex(item => item.id === locationIdRef.current)
if (index !== -1) {
onClick?.(data?.data[index])
setRow(data?.data[index])
} else {
if ((data?.data??[]).length > 0) {
onClick?.(data?.data[0])
setRow(data?.data[0])
} else {
onClick?.(undefined)
setRow()
}
}
setTimeout(() => {
var anchor = document.getElementById(`${locationIdRef.current}`)
anchor?.scrollIntoView()
locationIdRef.current = null
locationDidRef.current = null
}, 500)
} else {
setRow(prevRow => {
if (!prevRow || (data?.data??[]).findIndex(item => item.id === prevRow.id) === -1) {
if ((data?.data??[]).length > 0) {
onClick?.(data?.data[0])
return data?.data[0]
} else {
onClick?.(undefined)
return undefined
}
}
return prevRow
})
taskIdRef.current = null
}
},
error: () => {
setLoading(false)
locationIdRef.current = null
locationDidRef.current = null
taskIdRef.current = null
}
})
}
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 getDataAssetLocation = () => {
setLoading(true)
dispatch({
type: 'assetmanage.getDataAssetLocation',
payload: {
dataAssetId: locationIdRef.current,
dirId: locationDidRef.current,
},
callback: data => {
const newPageNum = parseInt(data.offset/page.pageSize + ((data.offset%page.pageSize===0)?0:1))
setPageAndArgs({ ...page, pageNum: newPageNum })
},
error: () => {
setLoading(false)
locationIdRef.current = null
locationDidRef.current = null
setPageAndArgs({ ...page, pageNum: 1 })
}
})
}
const onAddClick = () => {
setAddAssetParams({
visible: true,
reference: ResourceManageReference,
nodeId: node?.nodeId
})
}
const onAddToAssetClick = () => {
setAddToAssetParams({
visible: true,
items: selectedRows
})
}
const onDistributeTaskClick = () => {
setDistributeTaskParams({
visible: true,
items: selectedRows
})
}
const onAutoDistributeTaskClick = () => {
setAutoDistributeTaskParams({
visible: true,
node,
args
})
}
const onBatchEditClick = () => {
setSelectBatchEditElementsParams({
visible: true,
type: ResourceManageReference
})
}
const onCheckClick = () => {
setCheckAssetsParams({
visible: true,
type: 'multiple',
items: selectedRows
})
}
const onImportClick = () => {
setImportAssetParams({
visible: true,
reference: ResourceManageReference,
nodeId: node?.nodeId
})
}
const onExportClick = () => {
if ((selectedRows??[]).length === 0) {
modal.confirm({
title: '提示',
content: '是否导出选中目录下的所有资产?',
onOk: () => {
window.open(`/api/dataassetmanager/dataAssetApi/exportByDataAssetIds?dirId=${node?.nodeId}&recursive=${args.params.catalogType === 'currentRecursive'}&dataAsset=${getAssetRange(ResourceManageReference)}`);
}
})
} else {
window.open(`/api/dataassetmanager/dataAssetApi/exportByDataAssetIds?dataAssetIds=${selectedRows.map(item => item.id).join(',')}&dataAsset=${getAssetRange(ResourceManageReference)}`);
}
}
const onChangeDirectoryClick = () => {
setChangeCatalogParams({
visible: true,
items: selectedRows
})
}
const onDeleteClick = () => {
// setAssetDeleteParams({
// visible: true
// })
modal.confirm({
title: '提示',
content: '是否删除选中的资源?',
onOk: () => {
dispatch({
type: 'assetmanage.deleteResources',
payload: {
params: {
resourceIds: (selectedRows??[]).map(item => item.id).toString()
}
},
callback: () => {
showMessage("success","删除成功")
setSelectedRows()
getAssets()
}
})
}
})
}
const onFilterElementClick = () => {
setFilterElementParams({
visible: true,
type: 'admin',
reference: type
})
}
const deleteAssets = () => {
setAssetDeleteParams({
visible: false
})
dispatch({
type: 'assetmanage.unloadDataAssets',
payload: {
data: (selectedRows??[]).map(item => {
return { dataAssetId: item.id, dirId: item.dirId }
})
},
callback: () => {
showMessage("success","删除成功")
getAssets()
setSelectedRows([])
}
})
}
const deleteAssetsFromAllDirs = () => {
setAssetDeleteParams({
visible: false
})
dispatch({
type: 'assetmanage.unloadDataAssetsFromAllDirs',
payload: {
data: (selectedRows??[]).map(item => item.id)
},
callback: () => {
showMessage("success","删除成功")
getAssets()
setSelectedRows([])
}
})
}
const onRightAddToAssetClick = () => {
setAddToAssetParams({
visible: true,
items: [rightRow]
})
}
const onRightDistributeTaskClick = () => {
setDistributeTaskParams({
visible: true,
items: [rightRow]
})
}
const onRightRedistributeTaskClick = () => {
setRedistributeTaskParams({
visible: true,
items: [rightRow]
})
}
const onRightCheckClick = () => {
setCheckAssetsParams({
visible: true,
type: 'single',
items: [rightRow]
})
}
const onRightChangeToUncombedClick = () => {
modal.confirm({
title: '提示',
content: '是否将该条非资产的资源转为未梳理状态?',
onOk: () => {
dispatch({
type: 'assetmanage.updateResourceState',
payload: {
params: {
dataAssetId: rightRow?.id,
resourceState: 'uncombed'
}
},
callback: () => {
LocalStorage.set('assetResourceChange', !(LocalStorage.get('assetResourceChange')||false))
let event = new Event('storage')
event.key = 'assetResourceChange'
window?.dispatchEvent(event)
getAssets()
}
})
}
})
}
const onRightChangeToNotAssetClick = () => {
modal.confirm({
title: '提示',
content: '是否将该条未梳理的资源转为非资产?',
onOk: () => {
dispatch({
type: 'assetmanage.updateResourceState',
payload: {
params: {
dataAssetId: rightRow?.id,
resourceState: 'notRelatedAsset'
}
},
callback: () => {
LocalStorage.set('assetResourceChange', !(LocalStorage.get('assetResourceChange')||false))
let event = new Event('storage')
event.key = 'assetResourceChange'
window?.dispatchEvent(event)
getAssets()
}
});
}
})
}
const onRightSyncClick = () => {
modal.confirm({
title: '提示',
content: '确定同步选中内容的变更吗?',
onOk: () => {
dispatch({
type: 'assetmanage.syncResourceDraft',
payload: {
params: {
draftIds: rightRow?.id,
status: 1
}
},
callback: (data) => {
showMessage('success', '同步资源成功')
getAssets()
}
})
}
})
}
const onRightUnSyncClick = () => {
modal.confirm({
title: '提示',
content: '确定不同步选中内容的变更吗?',
onOk: () => {
dispatch({
type: 'assetmanage.syncResourceDraft',
payload: {
params: {
draftIds: rightRow?.id,
status: -1
}
},
callback: (data) => {
showMessage('success', '不同步资源成功')
getAssets()
}
})
}
})
}
const onMenuClick = ({ key }) => {
if (key === 'addAsAsset') {
onAddToAssetClick()
} else if (key === 'distribute') {
onDistributeTaskClick()
} else if (key === 'autoDistribute') {
onAutoDistributeTaskClick()
} else if (key === 'batchEdit') {
onBatchEditClick()
} else if (key === 'check') {
onCheckClick()
} else if (key === 'import') {
onImportClick()
} else if (key === 'export') {
onExportClick()
} else if (key === 'changeDir') {
onChangeDirectoryClick()
} else if (key === 'delete') {
onDeleteClick()
} else if (key === 'colConfig') {
onFilterElementClick()
}
}
const onRightMenuItemClick = (key, record) => {
if (key === '新增为资产') {
onRightAddToAssetClick()
} else if (key === '分配') {
onRightDistributeTaskClick()
} else if (key === '转分配') {
onRightRedistributeTaskClick()
} else if (key === '复核') {
onRightCheckClick()
} else if (key === '转为非资产') {
onRightChangeToNotAssetClick()
} else if (key === '转为未梳理') {
onRightChangeToUncombedClick()
} else if (key === '同步') {
onRightSyncClick()
} else if (key === '不同步') {
onRightUnSyncClick()
}
}
const moreMenu = (
<Menu onClick={onMenuClick}>
{
compact && <React.Fragment>
<PermissionMenuItem
key='addAsAsset'
defaultPermission={addAsAssetAble}
disabled={(selectedRows??[]).length===0}
tip={(selectedRows??[]).length===0?'请先选择资源':''}
>
<div className='text-center'>
新增为资产
</div>
</PermissionMenuItem>
<PermissionMenuItem
key='distribute'
defaultPermission={distributeAble}
disabled={(selectedRows??[]).length===0}
tip={(selectedRows??[]).length===0?'请先选择资源':''}
>
<div className='text-center'>
任务分配
</div>
</PermissionMenuItem>
<PermissionMenuItem
key='autoDistribute'
permissions={permissions}
>
<div className='text-center'>
自动分配
</div>
</PermissionMenuItem>
<PermissionMenuItem
key='batchEdit'
defaultPermission={batchEditAble}
disabled={(selectedRows??[]).length===0}
tip={(selectedRows??[]).length===0?'请先选择资源':''}
>
<div className='text-center'>
批量编辑
</div>
</PermissionMenuItem>
<PermissionMenuItem
key='check'
defaultPermission={checkAble}
disabled={(selectedRows??[]).length===0}
tip={(selectedRows??[]).length===0?'请先选择资源':''}
>
<div className='text-center'>
复核
</div>
</PermissionMenuItem>
</React.Fragment>
}
{
(node?.resourceType !== 'custom' && node?.type !== 'custom') && <PermissionMenuItem key='import' defaultPermission={importAble}>
<div className='text-center'>
导入
</div>
</PermissionMenuItem>
}
<PermissionMenuItem key='export' defaultPermission={exportAble}>
<div className='text-center'>
导出
</div>
</PermissionMenuItem>
<PermissionMenuItem
key='changeDir'
defaultPermission={changeDirectoryAble}
disabled={(selectedRows??[]).length===0}
tip={(selectedRows??[]).length===0?'请先选择资源':''}
>
<div className='text-center'>
变更目录
</div>
</PermissionMenuItem>
{
(node?.resourceType !== 'custom' && node?.type !== 'custom') && <PermissionMenuItem
key='delete'
defaultPermission={deleteAble}
disabled={(selectedRows??[]).length===0}
tip={(selectedRows??[]).length===0?'请先选择资源':''}
>
<div className='text-center'>
删除
</div>
</PermissionMenuItem>
}
<Menu.Item key='colConfig'>
<div className='text-center'>
可见列设置
</div>
</Menu.Item>
</Menu>
)
const classes = classNames('asset-list', {
'asset-list-fullscreen': fullScreen,
});
return (
<div className={classes}>
<div
className='flex'
style={{
padding: '20px 15px 5px',
alignItems: 'center',
justifyContent: 'space-between',
}}
>
<Space className='mr-3' style={{ flex: 0, paddingBottom: 15 }}>
{
(type === ResourceManageReference && node?.level !== 1 && node?.resourceType !== 'custom' && node?.type !== 'custom') && <PermissionButton
defaultPermission={addAble}
onClick={onAddClick}
>
新增
</PermissionButton>
}
{
compact ? <Dropdown overlay={moreMenu} placement="bottomCenter">
<Button>更多</Button>
</Dropdown> : <React.Fragment>
<PermissionButton
defaultPermission={addAsAssetAble}
onClick={onAddToAssetClick}
disabled={(selectedRows??[]).length===0}
tip={(selectedRows??[]).length===0?'请先选择资源':''}
>
新增为资产
</PermissionButton>
<PermissionButton
defaultPermission={distributeAble}
onClick={onDistributeTaskClick}
>
任务分配
</PermissionButton>
<PermissionButton
key='autoDistribute'
permissions={permissions}
onClick={onAutoDistributeTaskClick}
>
自动分配
</PermissionButton>
<PermissionButton
defaultPermission={batchEditAble}
onClick={onBatchEditClick}
disabled={(selectedRows??[]).length===0}
tip={(selectedRows??[]).length===0?'请先选择资源':''}
>
批量编辑
</PermissionButton>
<PermissionButton
defaultPermission={checkAble}
onClick={onCheckClick}
disabled={(selectedRows??[]).length===0}
tip={(selectedRows??[]).length===0?'请先选择资源':''}
>
复核
</PermissionButton>
<Dropdown overlay={moreMenu} placement="bottomCenter">
<Button>更多</Button>
</Dropdown>
</React.Fragment>
}
</Space>
<div className='flex' style={{ flex: 1, overflow: 'auto', paddingBottom: 15 }}>
<div style={{ flex: 1 }}></div>
<Space style={{ flex: 0 }}>
<Checkbox
onChange={(e) => {
setArgsAndPage({ onlyPending: e.target.checked })
}}
style={{ width: 102 }}
>
仅看待处理
</Checkbox>
<Select
value={args.params.catalogType}
onChange={(value) => {
setArgsAndPage({ catalogType: value })
}}
style={{ width: 170 }}
>
<Select.Option value='currentRecursive'>
当前目录(含子目录)
</Select.Option>
<Select.Option value='current'>
当前目录
</Select.Option>
<Select.Option value='fullSearch'>
全部数据
</Select.Option>
</Select>
<Select
placeholder='梳理状态'
onChange={(value) => {
setArgsAndPage({ sortingStatus: value })
}}
style={{ width: 100 }}
allowClear
>
{
(sortStatus??[]).map((item, index) => {
return <Select.Option key={index} value={item.type}>
{item.desc}
</Select.Option>
})
}
</Select>
<Select
placeholder='资源状态'
onChange={(value) => {
setArgsAndPage({ resourceStatus: value })
}}
style={{ width: 100 }}
allowClear
>
{
(relatedMetadataStatus??[]).map((item, index) => {
return <Select.Option key={index} value={item.type}>
{item.desc}
</Select.Option>
})
}
</Select>
<Input size="middle"
addonBefore={
<Select
value={searchType}
onChange={(value) => {
setSearchType(value)
if (value === 'attribute') {
setFilterElementValueParams({
visible: true,
type: ResourceManageReference,
defaultValue: args.params.elementValueFilters
})
}
}}
style={{ width: 120 }}
>
<Select.Option value='keyword'>
关键字搜索
</Select.Option>
<Select.Option value='attribute'>
属性值过滤
</Select.Option>
</Select>
}
placeholder="资源要素值/任务"
value={keyword}
bordered={true} allowClear
onChange={(e) => {
const keyword = e.target.value
setKeyword(keyword)
$keyword.next((keyword??'').trim())
}}
style={{ width: 270 }}
/>
<Tooltip title={fullScreen?'取消全屏':'全屏'}>
<Button
onClick={() => {
setFullScreen(!fullScreen)
onFullScreenChange?.(!fullScreen)
}}
icon={fullScreen ?<CancelFullScreenSvg style={{ width: 20, height: 20, marginTop: 2 }} /> : <FullScreenSvg style={{ width: 20, height: 20, marginTop: 2 }} />}
type='text'
/>
</Tooltip>
</Space>
</div>
</div>
<div className='px-3'>
<ResizeObserver
onResize={({ width }) => {
setCompact(width < 1030)
}}
>
<Table
maxHeight={tableMaxHeight}
loading={loading||loadingElements}
columns={columns}
dataSource={tableData}
pageSize={page.pageSize} pageNum={page.pageNum} total={total??0}
onRowClick={(event, value) => {
setRow(value)
onClick?.(value)
}}
rowClassName={(record, index) => (record?.id === row?.id) ? 'yy-table-select-row' : ''}
onPaginate={(page, pageSize) => {
setRow()
setSelectedRows([])
setPageAndArgs({ pageNum: page, pageSize })
}}
rowSelection={{
selectedRowKeys: (selectedRows??[]).map(item => item.id),
onChange: (selectedRowKeys, selectedRows) => {
setSelectedRows(selectedRows)
},
getCheckboxProps: (record) => ({
disabled: record.draft,
}),
}}
expandable={(data??[]).findIndex(item => item.resourceExtraAttribute?.existDraft) !== -1 ? {
expandedRowRender: (record) => (
<ExpandedRow
id={record.id}
columns={columns}
onRowClick={(event, value) => {
setRow(value)
onClick?.(value)
}}
onSyncClick={() => {
getAssets()
}}
/>
),
rowExpandable: (record) => record.resourceExtraAttribute?.existDraft,
expandIcon: ({ expanded, onExpand, record }) => {
if (!record.resourceExtraAttribute?.existDraft) return null
return expanded ? (
<UpOutlined onClick={e => onExpand(record, e)} />
) : (
<DownOutlined onClick={e => onExpand(record, e)} />
)
},
indentSize: 0,
columnWidth: 30,
} : null
}
shouldRowContextMenu={(record) => {
setRightRow(record)
let allowContextMenu = false
for (const key of (record.allowButtons??[])) {
if (operationMap[`${key}`]) {
allowContextMenu = true
}
}
return allowContextMenu
}}
menuData={menuData}
menuPermissions={menuData}
onMenuItemClick={onRightMenuItemClick}
/>
</ResizeObserver>
</div>
<AddAsset
{...addAssetParams}
onCancel={(refresh = false) => {
setAddAssetParams({
visible: false,
reference: undefined,
nodeId: undefined
})
refresh && getAssets()
}}
/>
<AddToAsset
{...addToAssetParams}
onCancel={(refresh = false) => {
setAddToAssetParams({
visible: false,
items: undefined
})
refresh && getAssets()
}}
/>
<ImportAsset
{...importAssetParams}
onCancel={() => {
setImportAssetParams({
visible: false,
reference: undefined,
nodeId: undefined
})
}}
onSuccess={(tip = '') => {
getAssets()
if (tip) {
showNotifaction('导入提示', tip, 5)
}
}}
/>
<ChangeCatalog
{...changeCatalogParams}
onCancel={(refresh = false) => {
setChangeCatalogParams({
visible: false,
items: undefined
})
refresh && getAssets()
}}
/>
<AssetDelete
{...assetDeleteParams}
onCancel={() => {
setAssetDeleteParams({
visible: false
})
}}
onDelete={deleteAssets}
onDeleteAll={deleteAssetsFromAllDirs}
/>
<FilterElement
{...filterElementParams}
onCancel={(refresh = false) => {
setFilterElementParams({
visible: false,
type: undefined,
reference: undefined
})
if (refresh) {
getElements()
getAssets()
}
}}
/>
<FilterElementValue
{...filterElementValueParams}
onCancel={(refresh, val) => {
setFilterElementValueParams({
visible: false,
type: undefined,
defaultValue: undefined,
})
setSearchType('keyword')
if (refresh) {
setArgsAndPage({ elementValueFilters: val })
}
}}
/>
<SelectBatchEditElements
{...selectBatchEditElementsParams}
onCancel={(refresh, value) => {
setSelectBatchEditElementsParams({
visible: false,
type: undefined,
})
if (refresh) {
setTimeout(() => {
window.open(`/data-govern/edit-assets?ids=${(selectedRows??[]).map(item => item.id).toString()}&elementIds=${(value??[]).toString()}`)
}, 300)
}
}}
/>
<DistributeTask
{...distributeTaskParams}
onCancel={(refresh) => {
setDistributeTaskParams({
visible: false,
items: undefined
})
refresh && getAssets()
}}
/>
<RedistributeTask
{...redistributeTaskParams}
onCancel={(refresh) => {
setRedistributeTaskParams({
visible: false,
items: undefined
})
refresh && getAssets()
}}
/>
<AutoDistributeTask
{...autoDistributeTaskParams}
onCancel={(refresh) => {
setAutoDistributeTaskParams({
visible: false,
node: undefined,
args: undefined,
})
refresh && getAssets()
}}
/>
<CheckAssets
{...checkAssetsParams}
onCancel={(refresh) => {
setCheckAssetsParams({
visible: false,
items: undefined
})
refresh && getAssets()
}}
/>
{contextHolder}
</div>
)
}
export default FC
const ExpandedRow = ({ id, columns, onRowClick, onSyncClick }) => {
const [loading, setLoading] = React.useState(false)
const [data, setData] = React.useState()
const [modal, contextHolder] = Modal.useModal()
const tableData = React.useMemo(() => {
const newTableData = []
if (data) {
const newAsset = {...data}
let index = 0
for (const elementValue of (data?.elementValues??[])) {
newAsset[`element${++index}`] = elementValue
}
newTableData.push(newAsset)
}
return newTableData
}, [data])
React.useEffect(() => {
if (id) {
getDraft()
}
}, [id])
const menuData = React.useMemo(() => {
const newMenuData = []
for (const key of data?.allowButtons??[]) {
if (operationMap[`${key}`]) {
newMenuData.push(operationMap[`${key}`])
}
}
return newMenuData
}, [data])
const getDraft = () => {
setLoading(true)
dispatch({
type: 'assetmanage.getResourceDraft',
payload: {
resourceId: id,
range: getAssetRange(ResourceManageReference)
},
callback: (data) => {
setLoading(false)
setData(data)
}
})
}
const onMenuItemClick = (key, record) => {
if (key === '同步') {
modal.confirm({
title: '提示',
content: '确定同步选中内容的变更吗?',
onOk: () => {
dispatch({
type: 'assetmanage.syncResourceDraft',
payload: {
params: {
draftIds: record.id,
status: 1
}
},
callback: (data) => {
showMessage('success', '同步资源成功')
onSyncClick?.()
}
})
}
})
} else if (key === '不同步') {
modal.confirm({
title: '提示',
content: '确定不同步选中内容的变更吗?',
onOk: () => {
dispatch({
type: 'assetmanage.syncResourceDraft',
payload: {
params: {
draftIds: record.id,
status: -1
}
},
callback: (data) => {
showMessage('success', '不同步资源成功')
onSyncClick?.()
}
})
}
})
}
}
return (
<div style={{ padding: 10 }}>
<Table
loading={loading}
columns={columns}
dataSource={tableData}
pagination={false}
onRowClick={(event, value) => {
onRowClick?.(event, value)
}}
shouldRowContextMenu={(record) => {
let allowContextMenu = false
for (const key of (record.allowButtons??[])) {
if (operationMap[`${key}`]) {
allowContextMenu = true
}
}
return allowContextMenu
}}
menuData={menuData}
menuPermissions={menuData}
onMenuItemClick={onMenuItemClick}
/>
{contextHolder}
</div>
)
}
export const MetadataColumnTooltipTitle = ({ data }) => {
const [currentMetadataId, setMetadataId] = React.useState()
const [loading, setLoading] = React.useState(false)
const [columnData, setColumnData] = React.useState()
const cols = React.useMemo(() => {
return (
[
{
title: '序号',
width: 60,
dataIndex: 'index',
ellipsis: true,
render: (_, __, index) => (index+1)
},
{
title: '名称',
width: 160,
dataIndex: 'name',
ellipsis: true,
},
{
title: '中文名称',
width: 160,
dataIndex: 'cnName',
ellipsis: true,
},
{
title: '类型',
width: 150,
dataIndex: 'typeName',
ellipsis: true,
render: (text, record) => {
let suffix = (record.size!==null&&record.size!==undefined) ? `(${record.size})` : ''
if(record.size && record.decimalDigits){
suffix = `(${record.size},${record.decimalDigits})`
}
return `${text}${suffix}`
}
},
{
title: '是否允许为空',
dataIndex: 'isNullable',
width: 120,
ellipsis: true,
},
{
title: '是否为主键',
dataIndex: 'isPrimaryKey',
width: 120,
ellipsis: true,
},
]
)
}, [])
React.useEffect(() => {
if ((data??[]).length > 0) {
setMetadataId(data[0].metadataId)
}
}, [data])
React.useEffect(() => {
if (currentMetadataId) {
getMetadataColumns()
}
}, [currentMetadataId])
const getMetadataColumns = () => {
setLoading(true)
dispatch({
type: 'assetmanage.getMetadataColumns',
payload: {
params: {
parentId: currentMetadataId
},
data: ["Catalog,Database,Schema,Table,Column"]
},
callback: (data) => {
setLoading(false)
setColumnData(data)
},
error: () => {
setLoading(false)
}
})
}
return (
<div>
{
((data??[]).length > 1) && <div className='mb-2'>
<Select
value={currentMetadataId}
onChange={(value) => {
setMetadataId(value)
}}
style={{ width: 170 }}
dropdownStyle={{
zIndex: 1100
}}
>
{
(data??[]).map((item, index) => <Select.Option value={item.metadataId} key={index}>{item.enName}</Select.Option>)
}
</Select>
</div>
}
<AntdTable
dataSource={columnData}
columns={cols}
loading={loading}
pagination={false}
size='small'
/>
</div>
)
}
export const MetadataColumn = ({ data }) => {
const decodeData = React.useMemo(() => {
if (data) {
try {
return JSON.parse(data)
} catch(error) {
return data
}
}
return []
}, [data])
return (
<React.Fragment>
{
(typeof decodeData === 'string') ? <Tooltip
overlayClassName='tooltip-common'
title={decodeData}
>
<Typography.Text ellipsis={true}>
{decodeData}
</Typography.Text>
</Tooltip> : ((IsArr(decodeData) ? <Tooltip
overlayClassName='tooltip-common'
title={<MetadataColumnTooltipTitle data={decodeData} />}
>
<Typography.Text ellipsis={true}>
{(decodeData??[]).map(item => item.enName).toString()}
</Typography.Text>
</Tooltip> : null)
)
}
</React.Fragment>
)
}
\ No newline at end of file
import React, { useEffect, useMemo, useState } from 'react';
import { Tooltip, Spin, Modal, Button, AutoComplete, Menu, Dropdown } from 'antd'
import { PlusOutlined, ReloadOutlined, ImportOutlined, ExportOutlined, SettingOutlined } from '@ant-design/icons'
import produce from 'immer'
import { dispatch } from '../../../model'
import { showMessage, highlightSearchContentByTerms, showNotifaction, getAssetType, getAssetRange } from '../../../util'
import Tree from '../../../util/Component/Tree'
import PermissionButton from '../../../util/Component/PermissionButton'
import UpdateNode from './update-node'
import ImportNode from '../AssetManage/Component/ImportDirectory'
import CustomNode from '../AssetManage/Component/CustomDirectoryModal'
import './tree.less'
import { ResourceManageReference } from '../../../util/constant';
//subjectNodeId 主题id
export const generateList = (data, list, path = null, subjectNodeId = null) => {
for (const node of data??[]) {
if (node.resourceType !== 'custom') {
const newPath = path?`${path}/${node.text}`:node.text
list.push({...node, key: node.nodeId, value: newPath, subjectNodeId: node.level===2 ?node.nodeId:subjectNodeId})
if (node.children) {
generateList(node.children, list, newPath, node.level===2?node.nodeId:subjectNodeId)
}
}
}
}
export const updateTreeData = (list, key, children) => {
return list.map((node) => {
if (node.nodeId === key) {
return {...node, children}
}
if (node.children) {
return {
...node,
children: updateTreeData(node.children, key, children),
};
}
return node;
})
}
const FC = (props) => {
const {onClick} = props
const [loading, setLoading] = useState(false)
const [data, setData] = useState(undefined)
const [dataList, setDataList] = useState()
const [keyword, setKeyword] = useState()
const [options, setOptions] = useState()
const [expandedKeys, setExpandedKeys] = useState([])
const [loadedKeys, setLoadedKeys] = useState([])
const [selectedKey, setSelectedKey] = useState('')
const [rightSelectedNode, setRightSelectedNode] = useState()
const [autoExpandParent, setAutoExpandParent] = useState(false)
const [updateNodeParam, setUpdateNodeParam] = useState({
visible: false,
action: undefined,
id: undefined,
})
const [importNodeParam, setImportNodeParam] = useState({
visible: false,
reference: undefined,
dirId: undefined
})
const [customNodeParam, setCustomNodeParam] = useState({
visible: false,
reference: undefined
})
const [permissions, setPermissions] = useState()
const [modal, contextHolder] = Modal.useModal()
const storageChange = (e) => {
if (e.key === 'assetDirChangeEvent' || e.key === 'assetPathOnClickEvent') {
if (e.dirId) {
setSelectedKey(e.dirId)
resetSelectedNodeLogic()
}
} else if (e.key === 'assetRelationOnClickEvent') {
setSelectedKey(e.relation?.dirId)
resetSelectedNodeLogic()
}
}
useEffect(() => {
getTreeData()
getPermissions()
window?.addEventListener("storage", storageChange)
return () => {
window?.removeEventListener("storage", storageChange)
}
//eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
const havePermission = useMemo(() => {
return (permissions??[]).findIndex(item => item === 'dirManage') !== -1
}, [permissions])
const treeData = useMemo(() => {
if (data) {
const newTreeData = produce(data, draft => {
const setNode = (g) => {
g.key = g.nodeId
g.title = (
<span className={(g.level===1)?'title-color':'text-color'}>
{g.text}
{
//自定义类型栏目不统计资源数
(g.level === 1 && (g.resourceType === 'custom') || g.type === 'custom') ? null : <span>{` (${g.dataAssetAndSubDirCount})`}</span>
}
</span>
)
g.children?.forEach((child) => {
setNode(child)
})
if (g.resourceType !== 'custom' && g.type !== 'custom' && (g.children??[]).length === 0) {
g.isLeaf = true
}
if ((g.resourceType === 'custom'||g.type === 'custom') && (g.children??[]).length === 0) {
g.children = null
}
}
draft.forEach((child) => {
setNode(child)
})
})
return newTreeData
}
return []
}, [data])
const menuData = useMemo(() => {
if (rightSelectedNode) {
if (rightSelectedNode.level === 1) {
let newMenuData = [
{ id: 'edit', title: '编辑栏目' },
{ id: 'up', title: '上移栏目' },
{ id: 'down', title: '下移栏目' },
{ id: 'delete', title: '删除栏目' }
]
if (rightSelectedNode.resourceType !== 'custom' && rightSelectedNode.type !== 'custom') {
newMenuData = [...newMenuData, { id: 'sync', title: '同步Schema' },]
}
return newMenuData
} else if (rightSelectedNode.resourceType === 'custom' || rightSelectedNode.type === 'custom') {
return [
{ id: 'up', title: '上移目录' },
{ id: 'down', title: '下移目录' },
{ id: 'delete', title: '删除目录' },
]
} else {
return [
{ id: 'edit', title: '编辑目录' },
{ id: 'up', title: '上移目录' },
{ id: 'down', title: '下移目录' },
{ id: 'delete', title: '删除目录' },
{ id: 'sync', title: '同步Schema' },
]
}
}
return []
}, [rightSelectedNode])
const resetSelectedNodeLogic = () => {
setData(prevData => {
setSelectedKey(prevSelectedKey => {
if ((prevData??[]).length === 0) return;
const firstNode = prevData[0]
if (!prevSelectedKey) {
setExpandedKeys(Array.from(new Set([...expandedKeys, firstNode.nodeId])))
setAutoExpandParent(true)
onTreeSelect([firstNode.nodeId], { selectedNodes: [firstNode]})
} else {
setDataList(prevDataList => {
const filterNodes = (prevDataList??[]).filter(item => item.nodeId === prevSelectedKey)
if (filterNodes?.length > 0) {
const newExpandedKeys = [...expandedKeys, prevSelectedKey]
setExpandedKeys(Array.from(new Set(newExpandedKeys)))
setAutoExpandParent(true)
onTreeSelect([prevSelectedKey], {selectedNodes: filterNodes})
}
return prevDataList
})
}
return prevSelectedKey
})
return prevData
})
}
const getTreeData = (resetSelectedNode = true) => {
setLoading(true)
setSelectedKey(prevSelectedKey => {
dispatch({
type: 'assetmanage.queryResourceManageTree',
callback: data => {
setLoading(false)
setLoadedKeys([])
setData(data)
if ((data??[]).length > 0) {
const newDataList = []
generateList(data, newDataList)
setDataList(newDataList)
if (resetSelectedNode) {
resetSelectedNodeLogic()
}
}
},
error: () => {
setLoading(false)
}
})
return prevSelectedKey
})
}
const getPermissions = () => {
dispatch({
type: 'assetmanage.getPrivilegeByRange',
payload: {
range: getAssetRange(ResourceManageReference),
},
callback: data => {
setPermissions(data);
}
})
}
const onAddClick = () => {
setUpdateNodeParam({
visible: true,
action: 'add',
id: selectedKey
})
}
const onImportClick = () => {
setImportNodeParam({
visible: true,
reference: ResourceManageReference,
dirId: selectedKey
})
}
const onCustomClick = () => {
setCustomNodeParam({
visible: true,
reference: ResourceManageReference,
})
}
const onDeleteNodeClick = (node) => {
modal.confirm({
title: '提示',
content: '节点下可能包含资源信息,删除后将把资源从该目录上移除,确定继续吗?',
onOk: () => {
dispatch({
type: 'assetmanage.deleteDirectory',
payload: {data: [ rightSelectedNode?.nodeId ]},
callback: () => {
showMessage("success","删除成功")
if (rightSelectedNode?.nodeId === selectedKey) {
setSelectedKey()
getTreeData()
} else {
getTreeData(false)
}
}
})
}
})
}
const onMoveNodeClick = (steps) => {
setLoading(true);
dispatch({
type: 'assetmanage.upDownDirectory',
payload: {
params: {
dirId: rightSelectedNode?.nodeId,
steps
}
},
callback: () => {
showMessage('success', (steps===1)?'上移目录成功':'下移目录成功');
getTreeData(false)
},
error: () => {
setLoading(false)
}
});
}
const onRefreshClick = () => {
getTreeData(false)
}
const onSyncNodeClick = () => {
modal.confirm({
title: '提示',
content: '确定同步schema吗?',
onOk: () => {
dispatch({
type: 'assetmanage.resourceSyncSchema',
payload: {
params: {
dirId: rightSelectedNode?.nodeId
}
},
callback: (data) => {
showNotifaction('同步提示', data, 5)
}
})
}
})
}
const onTreeExpand = (expandedKeys) => {
setExpandedKeys(expandedKeys)
setAutoExpandParent(false)
}
const onTreeSelect = (selectedKeys, { selectedNodes }) => {
if (selectedKeys.length === 0 || selectedNodes.length === 0) {
return
}
setSelectedKey(selectedKeys[0])
onClick && onClick(selectedNodes[0])
}
const onUpdateNodeCancel = (refresh = false, nodeId = undefined) => {
setUpdateNodeParam(prevParam => {
if (refresh) {
if (prevParam.action !== 'add' && nodeId) {
setSelectedKey(nodeId)
getTreeData()
} else {
getTreeData(false)
}
}
return {
visible: false,
action: undefined,
id: undefined
}
})
}
const onImportNodeCancel = (refresh = false, resetSelectedNode = false) => {
setImportNodeParam({
visible: false,
reference: undefined,
dirId: undefined
})
refresh && getTreeData(resetSelectedNode)
}
const onCustomNodeCancel = (refresh = false) => {
setCustomNodeParam({
visible: false,
reference: undefined,
})
if (refresh) {
getTreeData(false)
}
}
const onMenuItemClick = (key, node) => {
console.log('key', key)
if (key === 'delete') {
onDeleteNodeClick(node)
} else if (key === 'edit') {
setUpdateNodeParam({
visible: true,
action: 'edit',
id: rightSelectedNode?.nodeId
})
} else if (key === 'up') {
onMoveNodeClick(1)
} else if (key === 'down') {
onMoveNodeClick(-1)
} else if (key === 'sync') {
onSyncNodeClick()
}
}
const onLoadData = ({ key, children }) =>
new Promise((resolve) => {
if (children) {
resolve()
return
}
setLoadedKeys([...loadedKeys, key])
dispatch({
type: 'assetmanage.getDirectoryChild',
payload: {
parentId: key,
},
callback: (_data) => {
let newData = updateTreeData(data, key, _data??[])
setData(newData)
resolve()
},
error: () => {
resolve()
}
})
})
const onAutoCompleteSearch = (searchText) => {
setKeyword(searchText)
setOptions(!searchText?[]:(dataList||[]).filter(item => item.value.indexOf(searchText)!==-1))
}
const onAutoCompleteSelect = (value, option) => {
const paths = value.split('/')
setKeyword(paths[paths.length-1])
setSelectedKey(option.key)
resetSelectedNodeLogic()
}
const exportMenu = (
<Menu>
<Menu.Item>
<div style={{ textAlign: 'center' }} onClick={() => {
window.open(`/api/dataassetmanager/directoryApi/export?dataAssetType=${getAssetType(ResourceManageReference)}`)
}}>
导出所有
</div>
</Menu.Item>
<Menu.Item>
<div style={{ textAlign: 'center' }} onClick={() => {
if(selectedKey){
dispatch({
type: 'assetmanage.getDirectoryById',
payload: {
dirId: selectedKey,
dataAssetType: getAssetType(ResourceManageReference)
},
callback: data => {
window.open(`/api/dataassetmanager/directoryApi/export?parentPath=${data.path}`);
}
})
} else {
showMessage("warn","请选择目录")
}
}}>
导出选中目录
</div>
</Menu.Item>
</Menu>
)
return (
<div className='resource-manage-tree'>
<div className='header p-3'>
<PermissionButton
defaultPermission={havePermission}
tip="新增目录"
type="text"
icon={<PlusOutlined />}
onClick={onAddClick}
/>
<Tooltip title="刷新目录">
<Button type="text" icon={<ReloadOutlined />} onClick={onRefreshClick} />
</Tooltip>
<PermissionButton
defaultPermission={havePermission}
tip="导入目录"
type="text"
icon={<ImportOutlined />}
onClick={onImportClick}
/>
<Dropdown overlay={havePermission?exportMenu:<></>} placement="bottomCenter" >
<PermissionButton
defaultPermission={havePermission}
tip="导出目录"
type="text"
icon={<ExportOutlined />}
/>
</Dropdown>
<PermissionButton
defaultPermission={havePermission}
tip="自定义目录"
type="text"
icon={<SettingOutlined />}
onClick={onCustomClick}
/>
</div>
<div className='p-3'>
<Spin spinning={loading}>
<AutoComplete
allowClear
value={keyword}
style={{ marginBottom: 10, width: '100%' }}
onSelect={onAutoCompleteSelect}
onSearch={onAutoCompleteSearch}
onClear={() => {
setKeyword()
}}
>
{
(options||[]).map((item, index) => {
return (
<AutoComplete.Option key={item.key} value={item.value}>
<div style={{ whiteSpace: 'normal' }}>
{highlightSearchContentByTerms(item.value, [keyword])}
</div>
</AutoComplete.Option>
);
})
}
</AutoComplete>
<Tree
className='tree'
showLine
showIcon={false}
autoExpandParent={autoExpandParent}
treeData={treeData}
loadData={onLoadData}
loadedKeys={loadedKeys}
onExpand={onTreeExpand}
onSelect={onTreeSelect}
expandedKeys={expandedKeys}
selectedKeys={[selectedKey]}
shouldRowContextMenu={(node) => {
setRightSelectedNode(node)
return havePermission
}}
menuData={menuData}
onMenuItemClick={onMenuItemClick}
/>
</Spin>
</div>
<UpdateNode
{...updateNodeParam}
onCancel={onUpdateNodeCancel}
/>
<ImportNode
{...importNodeParam}
onCancel={onImportNodeCancel}
/>
<CustomNode
action='add'
{...customNodeParam}
onCancel={onCustomNodeCancel}
/>
{contextHolder}
</div>
)
}
export default FC
\ No newline at end of file
@import '../../../variables.less';
.resource-manage-tree {
height: 100%;
.header {
display: flex;
flex: none;
width: 100%;
height: 57px;
justify-content: space-between;
align-items: center;
border-bottom: 1px solid #EFEFEF;
}
.tree {
height: calc(100vh - @header-height - @breadcrumb-height - 25px - 57px - 62px);
overflow: auto;
}
}
\ No newline at end of file
import produce from "immer"
export function formatTreeData(datasources, catalogMap) {
const treeData = produce(datasources, (draft) => {
draft?.forEach((datasource) => {
datasource.title = datasource.catalogName
datasource.value = `catalog-${datasource.catalogId}`
datasource.id = datasource.catalogId
datasource.disabled = true
datasource.children = produce(datasource.scopes, (draftScopes) => {
draftScopes?.forEach((scope) => {
scope.title = scope.scopeName
scope.value = scope.scopeId
scope.id = scope.scopeId
scope.catalogId = datasource.value
})
})
})
})
const getMap = (children) => {
return children?.forEach((child) => {
getMap(child.children)
catalogMap[child.value] = child
})
}
getMap(treeData)
return treeData
}
export const envReducer = (prevState, action) => {
if (action.type === 'selectEnv') {
const env = action.payload
return { ...prevState, env, domain: undefined, datasource: undefined, database: undefined, schema: undefined}
} if (action.type === 'selectDatasource') {
const { domain, datasource } = action.payload
return { ...prevState, domain, datasource, database: undefined, schema: undefined }
} else if (action.type === 'selectDatabase') {
const database = action.payload
return { ...prevState, database, schema: undefined }
} else if (action.type === 'selectSchema') {
const schema = action.payload
return { ...prevState, schema }
}
return prevState
}
\ No newline at end of file
import React from 'react'
import { Modal, Button, Spin, Form, Input, Radio, Select, Space, TreeSelect, Row, Col, Checkbox } from 'antd'
import { QuestionCircleOutlined } from '@ant-design/icons'
import { dispatch } from '../../../model'
import { envReducer, formatTreeData } from './update-node-help'
import { getValidString, showMessage } from '../../../util'
const resourceTypes = [
{ key: 'innerSource', name: '内部资源' },
{ key: 'outerSource', name: '外部资源' },
{ key: 'custom', name: '自定义' },
]
const FC = (props) => {
const { id, action, visible, onCancel } = props
const [loading, setLoading] = React.useState(false)
const [waiting, setWaiting] = React.useState(false)
const [node, setNode] = React.useState()
const [isCustomType, setIsCustomType] = React.useState(false)
const basicRef = React.useRef()
const syncBasicRef = React.useRef()
React.useEffect(() => {
if (visible && id) {
getDetail()
}
}, [visible, id])
const getDetail = () => {
dispatch({
type: 'assetmanage.getDirectoryWithSyncStrategy',
payload: {
dirId: id
},
callback: data => {
setNode(data)
if (action !== 'add') {
setIsCustomType(data?.directory?.resourceType==='custom'||data?.directory?.type==='custom')
}
}
})
}
const close = (refresh = false) => {
setLoading(false)
setWaiting(false)
setNode()
onCancel?.(refresh)
}
const save = async() => {
try {
const rows = await basicRef.current?.validate()
const syncRows = await syncBasicRef.current?.validate()
setWaiting(true)
let parentPath = ''
if (action === 'add') {
if (basicRef.current?.getType === 'child') {
parentPath = node?.directory?.path
rows.resourceType = node?.directory?.resourceType
}
} else {
parentPath = node?.directory?.path.substring(0, node?.directory?.path.lastIndexOf("/"))
}
dispatch({
type: 'assetmanage.resourceAddOrUpdateDirectory',
payload: {
data: {
directory: (action === 'add')?rows:{...node?.directory, ...rows},
resourceDirectorySyncStrategy: syncRows
},
params: {
parentPath
}
},
callback: data => {
setWaiting(false)
onCancel?.(true, data?.id)
},
error: () => {
setWaiting(false)
}
})
} catch (e) {
}
}
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='600px'
bodyStyle={{ padding: '15px 15px 0px 15px', overflowX: 'auto', maxHeight: '80vh' }}
title='资源目录信息'
centered destroyOnClose
onCancel={() => { close() }}
>
<Spin spinning={loading||waiting} >
<Basic
ref={basicRef}
node={node?.directory}
action={action}
onResourceTypeChange={(val) => {
setIsCustomType(val === 'custom')
}}
/>
{
!isCustomType && <SyncBasic ref={syncBasicRef} node={node?.resourceDirectorySyncStrategy} action={action} />
}
</Spin>
</Modal>
)
}
export default FC
export const Basic = React.forwardRef(function ({ node, action, onResourceTypeChange }, ref) {
const [type, setType] = React.useState('root')
const [currentResourceType, setCurrentResourceType] = React.useState()
const [form] = Form.useForm()
React.useImperativeHandle(ref, () => ({
getType: type,
validate: async () => {
return await form.validateFields()
},
}), [type, form])
React.useEffect(() => {
if (node) {
const index = resourceTypes.findIndex(item => item.key === node?.resourceType)
if (index !== -1) {
setCurrentResourceType(resourceTypes[index].name)
}
if (action !== 'add') {
form.setFieldsValue(node)
}
}
}, [action, node])
const allowChangeResourceType = React.useMemo(() => {
return (action==='add' && type==='root')
}, [action, type])
const onValuesChange = (changedValues, allValues) => {
if (changedValues.hasOwnProperty('resourceType')) {
onResourceTypeChange(changedValues.resourceType)
}
}
return (
<Form
form={form}
labelCol={{ span: 5 }}
wrapperCol={{ span: 17 }}
autoComplete="off"
onValuesChange={onValuesChange}
>
{
action==='add' && <Form.Item
label="类型"
rules={[{ required: true, message: '请选择类型!' }]}
>
<Radio.Group value={type} onChange={(e) => {
setType(e.target.value)
}}>
<Radio value='root'>栏目</Radio>
<Radio value='child' disabled={!node?.id}>目录</Radio>
</Radio.Group>
</Form.Item>
}
<Form.Item
label="资源类型"
name="resourceType"
rules={[{ required: allowChangeResourceType?true:false, message: '请选择资源类型!' }]}
>
{
allowChangeResourceType ? <Select allowClear>
{
resourceTypes.map((item,index) => {
return <Select.Option key={item.key}>{item.name}</Select.Option>
})
}
</Select> : <span>{currentResourceType}</span>
}
</Form.Item>
<Form.Item
label="编号"
name="code"
rules={[{ required: true, message: '必填项' }]}
>
<Input placeholder="请输入编号" />
</Form.Item>
<Form.Item
label="名称"
name="name"
rules={[{ required: true, message: '必填项' }]}
>
<Input placeholder="请输入名称" />
</Form.Item>
{
action !== 'add' && (
<Form.Item
label="路径"
name="path"
>
<span>{node?.path}</span>
</Form.Item>
)
}
<Form.Item
label="描述"
name="desc"
>
<Input placeholder="请输入描述" />
</Form.Item>
<Form.Item
label="备注"
name="remarks"
>
<Input placeholder="请输入备注" />
</Form.Item>
</Form>
)
})
export const SyncBasic = React.forwardRef(function ({ node, action }, ref) {
const [testData, setTestData] = React.useState()
const [testResultData, setTestResultData] = React.useState()
const [form] = Form.useForm()
React.useImperativeHandle(ref, () => ({
validate: async () => {
const rows = await form.validateFields()
convertRows(rows)
return rows
},
}), [form])
React.useEffect(() => {
if (node) {
if (action !== 'add') {
form.setFieldsValue(node)
}
}
}, [action, node])
const convertRows = (rows) => {
if (!rows.schemas) {
rows.schemas = []
}
if (!rows.autoSyncStatus) {
rows.autoSyncStatus = 0
}
if (!rows.blacklist) {
rows.blacklist = ''
}
if (!rows.whitelist) {
rows.whitelist = ''
}
}
const onTestClick = () => {
if (!testData) {
showMessage('warn', '请先输入测试数据')
return
}
const rows = form?.getFieldsValue()
convertRows(rows)
dispatch({
type: 'assetmanage.resourceTestSyncStrategy',
payload: {
params: {
testTableStr: testData,
},
data: rows,
},
callback: data => {
setTestResultData(data)
},
error: () => {
}
})
}
const onValuesChange = (changedValues, allValues) => {
}
return (
<Form
form={form}
labelCol={{ span: 5 }}
wrapperCol={{ span: 17 }}
autoComplete="off"
onValuesChange={onValuesChange}
>
<Form.Item
label="同步Schema"
name="schemas"
>
<SyncSchemaItem />
</Form.Item>
<Form.Item
label="自动同步"
name='autoSyncStatus'
tooltip={{ title: '选择自动同步后,新增元数据时,自动更新到该目录下', icon: <QuestionCircleOutlined /> }}
>
<AutoSyncItem />
</Form.Item>
<Form.Item
label='过滤条件'
name='blacklist'
tooltip={{ title: '设置过滤条件后,同步Schema时将不会同步符合过滤条件的表', icon: <QuestionCircleOutlined /> }}
>
<Input placeholder='输入正则表达式' />
</Form.Item>
<Form.Item
label='白名单'
name='whitelist'
tooltip={{ title: '设置的白名单,不受过滤条件影响', icon: <QuestionCircleOutlined /> }}
>
<Input placeholder='输入表名完整路径,多个表以,隔开' />
</Form.Item>
<Form.Item
label='测试数据'
>
<Row gutter={10}>
<Col span={20}>
<Input placeholder='请输入测试数据' onChange={(e) => {
setTestData(e.target.value)
}}/>
</Col>
<Col span={4}>
<Button onClick={onTestClick}>测试</Button>
</Col>
</Row>
</Form.Item>
<Form.Item
label='测试结果'
>
<Input value={testResultData} disabled={true} />
</Form.Item>
</Form>
)
})
const SyncSchemaItem = ({ value, onChange }) => {
const [selectSchemasParams, setSelectSchemasParams] = React.useState({
visible: false
})
const triggerChange = (newValue) => {
onChange?.(newValue)
}
return (
<div>
<Space>
<Button onClick={() => {
setSelectSchemasParams({
visible: true
})
}}>
选择Schema
</Button>
</Space>
<Row>
{
value?.map((item, index) => {
return (
<Col key={index} span={24} style={{ lineHeight: '32px' }}>
<Row>
<Col span={20}>{item.namePath}</Col>
<Col span={4}>
<Button
size='small'
type='danger'
onClick={() => {
const newValue = [...value??[]]
newValue.splice(index, 1)
triggerChange?.(newValue)
}}
>删除</Button>
</Col>
</Row>
</Col>
)
})
}
</Row>
<SelectSchemas
{...selectSchemasParams}
onCancel={(val) => {
setSelectSchemasParams({
visible: false
})
if ((val??[]).length>0) {
const newValue = [...value??[], ...val??[]]
triggerChange?.(newValue)
}
}}
/>
</div>
)
}
const AutoSyncItem = ({ value, onChange }) => {
return (
<Checkbox
checked={value===1}
onChange={(e) => {
onChange(e.target.checked?1:0)
}}
/>
)
}
const SelectSchemas = ({ visible, onCancel }) => {
const [schemas, setSchemas] = React.useState()
React.useEffect(() => {
if (visible) {
setSchemas()
}
}, [visible])
const close = (value) => {
onCancel?.(value)
}
const save = () => {
close(schemas)
}
const footer = React.useMemo(() => {
return [
<Button key={'cancel'}
onClick={() => close()}
>取消</Button>,
<Button key={'save'} type='primary'
onClick={() => save()}
>确定</Button>
]
}, [close, save])
return (
<Modal
className='add-to-asset'
visible={visible}
footer={footer}
width='654px'
title='选择Schema'
bodyStyle={{ padding: '15px' }}
centered destroyOnClose
onCancel={() => { close() }}
>
<Config onState={(state) => {
const newSchemas = (state.schema??[]).map(item => {
return {
idPath: item.idPath,
catalog: state.env?.domainId?.toString(),
namePath: `${state.env?.domainName}/${state.datasource?.scopeName}/${state.database?.name}/${item.name}`
}
})
setSchemas(newSchemas)
}} />
</Modal>
)
}
function Config({ onState }) {
const catalogMap = React.useRef({})
const [expandedKeys, setExpandedKeys] = React.useState()
const [systems, setSystems] = React.useState(undefined)
const [datasources, setDatasources] = React.useState(undefined)
const [dbs, setDBs] = React.useState(undefined)
const [schemas, setSchemas] = React.useState(undefined)
const [loadingSystems, setLoadingSystems] = React.useState(false)
const [loadingDatasources, setLoadingDatasources] = React.useState(false)
const [loadingDBs, setLoadingDBs] = React.useState(false)
const [loadingSchemas, setLoadingSchemas] = React.useState(false)
const [state, dispatchState] = React.useReducer(envReducer, {})
const { env, datasource, database, schema } = state
const selectEnv = React.useCallback((env) => {
dispatchState({ type: 'selectEnv', payload: env })
}, [])
const selectDomain = React.useCallback((domain, datasource) => {
dispatchState({ type: 'selectDatasource', payload: { domain, datasource } })
}, [])
const selectDb = React.useCallback((db) => {
dispatchState({ type: 'selectDatabase', payload: db })
}, [])
const selectSchema = React.useCallback((schema) => {
dispatchState({ type: 'selectSchema', payload: schema })
}, [])
const treeData = React.useMemo(() => {
const children = formatTreeData(datasources??[], catalogMap.current)
const expandedKeys = children?.map((c) => c.value)
selectDomain(undefined, undefined)
setExpandedKeys(expandedKeys)
return children
}, [datasources])
const disabled = loadingSystems || loadingDatasources || loadingDBs || loadingSchemas
React.useEffect(() => {
getSystems()
}, [])
React.useEffect(() => {
if (env) {
getDatasources()
} else {
setDatasources(undefined)
}
}, [env])
React.useEffect(() => {
if (datasource) {
getDBs()
} else {
setDBs(undefined)
}
}, [datasource])
React.useEffect(() => {
if (database) {
getSchemas()
} else {
setSchemas(undefined)
}
}, [database])
React.useEffect(() => {
onState?.(state)
}, [state])
const getSystems = () => {
setLoadingSystems(true)
dispatch({
type: 'assetmanage.getSystems',
callback: data => {
setLoadingSystems(false)
setSystems(data)
},
error: () => {
setLoadingSystems(false)
}
})
}
const getDatasources = () => {
setLoadingDatasources(true)
dispatch({
type: 'assetmanage.getDatasources',
payload: {
catalog: env?.domainId
},
callback: data => {
setLoadingDatasources(false)
setDatasources(data)
},
error: () => {
setLoadingDatasources(false)
}
})
}
const getDBs = () => {
setLoadingDBs(true)
dispatch({
type: 'assetmanage.getDatabases',
payload: {
params: {
parentClass: 'Catalog',
parentNamePath: env?.domainId,
system: datasource?.id
},
data: ['Catalog,Database']
},
callback: data => {
setLoadingDBs(false)
setDBs(data)
},
error: () => {
setLoadingDBs(false)
}
})
}
const getSchemas = () => {
setLoadingSchemas(true)
dispatch({
type: 'assetmanage.getSchemas',
payload: {
parentId: database?._id,
sysId: datasource?.id
},
callback: data => {
setLoadingSchemas(false)
setSchemas(data)
},
error: () => {
setLoadingSchemas(false)
}
})
}
return (
<Spin spinning={loadingSystems||loadingDatasources||loadingDBs||loadingSchemas}>
<Space>
<Select
placeholder="请选择环境"
value={systems?.length>0?env?.domainId:undefined}
loading={loadingSystems}
allowClear={true}
disabled={disabled}
onChange={(val) => {
if (val) {
const index = (systems||[]).findIndex(item => item.domainId === val)
if (index !== -1) {
selectEnv(systems[index])
}
} else {
selectEnv(undefined)
}
}}
style={{ width: 150 }}
>
{systems?.map((item, i) => <Select.Option key={i} value={item.domainId} > {item.domainName} </Select.Option>)}
</Select>
<TreeSelect
dropdownMatchSelectWidth={210}
listHeight={450}
treeData={treeData}
placeholder="请选择系统"
treeExpandedKeys={expandedKeys}
value={datasource?datasource.id:undefined}
onTreeExpand={(keys) => {
setExpandedKeys(keys)
}}
onChange={(val, label, extra) => {
const _datasource = catalogMap.current?.[val]
let _domain = undefined
if (_datasource?.catalogId) {
_domain = catalogMap.current?.[_datasource.catalogId]
}
selectDomain(_domain, _datasource)
}}
disabled={disabled}
allowClear={true}
style={{ width: 150 }}
/>
<Select
placeholder="请选择数据库"
value={dbs?.length>0?database?._id:undefined}
loading={loadingDBs}
allowClear={true}
disabled={disabled}
onChange={(val, option: any) => {
if (val) {
const index = (dbs||[]).findIndex(item => item._id === val)
if (index !== -1) {
selectDb(dbs[index])
}
} else {
selectDb(undefined)
}
}}
style={{ width: 150 }}
>
{dbs?.map((item, i) => <Select.Option key={i} value={item._id} > {getValidString([undefined, item.cnName, item.name])} </Select.Option>)}
</Select>
<Select
placeholder="请选择Schema"
value={schemas?.length>0?(schema??[]).map(item => item._id):undefined}
loading={loadingSchemas}
allowClear={true}
disabled={disabled}
mode="multiple"
onChange={(val) => {
if ((val??[]).length > 0) {
selectSchema((schemas??[]).filter(item => val.indexOf(item._id)!==-1))
} else {
selectSchema(undefined)
}
}}
style={{ width: 150 }}
>
{schemas?.map((item, i) => <Select.Option key={i} value={item._id} > {item.name} </Select.Option>)}
</Select>
</Space>
</Spin>
)
}
\ No newline at end of file
import React from 'react'
import { debounceTime, Subject } from 'rxjs'
import { DatePicker, Space, Input } from 'antd'
import { defaultPage, usePage } from '../../../util/hooks/page'
import Table from '../../../util/Component/Table'
import { dispatch } from '../../../model'
import './index.less'
import { getQueryParam } from '../../../util'
const FC = (props) => {
const id = getQueryParam('id', props?.location?.search)
const [args, setArgs] = React.useState(() => ({
params: {
page: defaultPage.pageNum,
size: defaultPage.pageSize,
keyword: undefined,
startTime: undefined,
endTime: undefined,
},
}))
const [loading, setLoading] = React.useState(false)
const [data, setData] = React.useState()
const [total, setTotal] = React.useState()
const $keyword = React.useMemo(() => new Subject(), [])
const [keyword, setKeyword] = React.useState()
const [page, setPage] = usePage()
const mountRef = React.useRef(false)
const setArgsAndPage = React.useCallback((params) => {
// 设置查询参数时将分页置为1
setPage(prevpg => {
setArgs((prev) => {
const newparams = params ? { ...prev.params, ...params } : undefined
return { params: { ...newparams, page: 1, size: prevpg.pageSize } }
})
return ({ ...prevpg, pageNum: 1 })
})
//eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
const setPageAndArgs = React.useCallback((page) => {
setPage(prev => ({ ...prev, ...page }))
setArgs((prev) => {
return { params: { ...prev.params, page: page.pageNum, size: page.pageSize } }
})
//eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
React.useEffect(() => {
const $$keyword = $keyword.pipe(debounceTime(1000)).subscribe((keyword) => {
setArgsAndPage({ keyword })
})
if (id) {
setKeyword(id)
$keyword.next(id)
}
return () => {
$$keyword.unsubscribe()
}
}, [])
React.useEffect(() => {
if (id && !mountRef.current) {
if (args.params.keyword) {
mountRef.current = true
getTasks()
}
} else {
getTasks()
}
}, [args])
const cols = [
{
title: '任务编号',
dataIndex: 'taskNO',
render: (_, record) => <a onClick={() => {
window.open(`/center-home/menu/asset-resource-manage?taskId=${record.taskNO}`)
}}>
{record.taskNO}
</a>
},
{
title: '数量',
dataIndex: 'resourceNum',
},
{
title: '已分配',
dataIndex: 'distributedNum',
},
{
title: '已梳理',
dataIndex: 'sortedNum',
},
{
title: '已完成',
dataIndex: 'doneNum',
},
{
title: '分配人',
dataIndex: 'creator',
},
{
title: '分配时间',
dataIndex: 'createTime',
},
]
const getTasks = () => {
setLoading(true)
dispatch({
type: 'assetmanage.getTasks',
payload: {
pageNum: args.params.page,
pageSize: args.params.size,
keyword: args.params.keyword,
beginTime: args.params.startTime??'',
endTime: args.params.endTime??'',
},
callback: data => {
setLoading(false)
setData(data?.data)
setTotal(data?.total)
},
error: () => {
setLoading(false)
}
})
}
return (
<div className='task-manage'>
<div className='header px-3'>
<Space>
<div />
</Space>
<Space>
<DatePicker.RangePicker
format="YYYY-MM-DD"
onChange={(values) => {
setArgsAndPage({ startTime: (values??[]).length>0?values[0].valueOf():'', endTime: (values??[]).length>1?values[1].valueOf():'' })
}}
style={{ width: 240 }}
/>
<Input size="middle"
placeholder="任务编号/分配人"
value={keyword}
bordered={true} allowClear
onChange={(e) => {
const keyword = e.target.value
setKeyword(keyword)
$keyword.next((keyword??'').trim())
}}
style={{
width: 250
}}
/>
</Space>
</div>
<div className='px-3 pt-3'>
<Table
maxHeight='calc(100vh - 285px - 46px)'
loading={loading}
columns={cols}
dataSource={data||[]}
pageSize={page.pageSize} pageNum={page.pageNum} total={total ?? 0}
onPaginate={(page, pageSize) => {
setPageAndArgs({ pageNum: page, pageSize })
}}
/>
</div>
</div>
)
}
export default FC
\ No newline at end of file
.task-manage {
height: 100%;
background-color: #fff;
.header {
display: flex;
flex: none;
height: 57px;
border-bottom: 1px solid #EFEFEF;
justify-content: space-between;
align-items: center;
}
}
\ No newline at end of file
...@@ -28,7 +28,7 @@ const perSuggestCount = 5; ...@@ -28,7 +28,7 @@ const perSuggestCount = 5;
const supportMaxAttributeCountPerPage = 100; const supportMaxAttributeCountPerPage = 100;
const MENU_ID = 'model-attribute-menu'; const MENU_ID = 'model-attribute-menu';
const InputDebounce = DebounceInput(300)(Input); export const InputDebounce = DebounceInput(300)(Input);
const ResizeableHeaderCell = props => { const ResizeableHeaderCell = props => {
const { onResize, width, onClick, ...restProps } = props; const { onResize, width, onClick, ...restProps } = props;
...@@ -165,7 +165,6 @@ export const EditableCell = ({ ...@@ -165,7 +165,6 @@ export const EditableCell = ({
children, children,
...restProps ...restProps
}) => { }) => {
let editingComponent = null; let editingComponent = null;
if (editing) { if (editing) {
let inputNode = <InputDebounce />; let inputNode = <InputDebounce />;
......
...@@ -9,7 +9,7 @@ import { showMessage } from '../../../../util' ...@@ -9,7 +9,7 @@ import { showMessage } from '../../../../util'
import { dispatch } from '../../../../model' import { dispatch } from '../../../../model'
import { AppContext } from '../../../../App' import { AppContext } from '../../../../App'
const FC = ({ type, id, tags }) => { const FC = ({ type, id, did, tags }) => {
const [data, setData] = React.useState([]) const [data, setData] = React.useState([])
const [tagSelectPopupParams, setTagSelectPopupParams] = React.useState({ const [tagSelectPopupParams, setTagSelectPopupParams] = React.useState({
visible: false, visible: false,
...@@ -21,6 +21,19 @@ const FC = ({ type, id, tags }) => { ...@@ -21,6 +21,19 @@ const FC = ({ type, id, tags }) => {
setData(tags) setData(tags)
}, [tags]) }, [tags])
const storageChange = (e) => {
if (e.key === 'assetDetailTagChangeEvent' && type==='dataAsset' && e.id === id) {
getTags()
}
}
React.useEffect(() => {
window?.addEventListener("storage", storageChange)
return () => {
window?.removeEventListener("storage", storageChange)
}
}, [type, id])
const getTags = () => { const getTags = () => {
dispatch({ dispatch({
type: 'tag.getResourceTagIn', type: 'tag.getResourceTagIn',
...@@ -37,18 +50,29 @@ const FC = ({ type, id, tags }) => { ...@@ -37,18 +50,29 @@ const FC = ({ type, id, tags }) => {
}); });
} }
//资产或者资源详情里的标签同步
const syncTag = () => {
if (type === 'dataAsset') {
let event = new Event('storage')
event.key = 'assetListTagChangeEvent'
event.id = id
window?.dispatchEvent(event)
}
}
return ( return (
<React.Fragment> <React.Fragment>
<TagCell <TagCell
value={data} value={data}
onChange={(val) => { onChange={(val) => {
getTags() getTags()
syncTag()
}} }}
onAdd={(val) => { onAdd={(val) => {
setTagSelectPopupParams({ setTagSelectPopupParams({
visible: true, visible: true,
type, type,
items: [{ resourceId: id }] items: [{ resourceId: id, resourceCatalogId: did }]
}) })
}} }}
/> />
...@@ -62,6 +86,7 @@ const FC = ({ type, id, tags }) => { ...@@ -62,6 +86,7 @@ const FC = ({ type, id, tags }) => {
}} }}
onChange={(val) => { onChange={(val) => {
getTags() getTags()
syncTag()
}} }}
{...tagSelectPopupParams} {...tagSelectPopupParams}
/> />
......
...@@ -8,10 +8,13 @@ import { ManageLayout } from "../../layout"; ...@@ -8,10 +8,13 @@ import { ManageLayout } from "../../layout";
import DatasourceManage from './DatasourceManage'; import DatasourceManage from './DatasourceManage';
import Model from './Model'; import Model from './Model';
import ModelConfig from './ModelConfig'; import ModelConfig from './ModelConfig';
import AssetResourceManage from './AssetResourceManage';
import AssetManage from './AssetManage'; import AssetManage from './AssetManage';
import AssetResourceBrowse from './AssetResourceBrowse'; import AssetResourceBrowse from './AssetResourceBrowse';
import AssetBrowse from './AssetBrowse'; import AssetBrowse from './AssetBrowse';
import AssetRecycle from './AssetRecycle'; import AssetRecycle from './AssetRecycle';
import AssetOperation from './AssetOperation';
import TaskManage from './AssetTask';
import DataMasterDefine from "./DataMaster/Define"; import DataMasterDefine from "./DataMaster/Define";
import DataMasterManage from "./DataMaster/Manage"; import DataMasterManage from "./DataMaster/Manage";
import MetadataHarvester from './MetadataHarvester'; import MetadataHarvester from './MetadataHarvester';
...@@ -32,10 +35,13 @@ class Manage extends Component { ...@@ -32,10 +35,13 @@ class Manage extends Component {
<Route path={`${match.path}/datasource-manage`} component={DatasourceManage} /> <Route path={`${match.path}/datasource-manage`} component={DatasourceManage} />
<Route path={`${match.path}/data-model`} component={Model} /> <Route path={`${match.path}/data-model`} component={Model} />
<Route path={`${match.path}/model-config`} component={ModelConfig} /> <Route path={`${match.path}/model-config`} component={ModelConfig} />
<Route path={`${match.path}/asset-resource-manage`} component={AssetResourceManage} />
<Route path={`${match.path}/asset-manage`} component={AssetManage} /> <Route path={`${match.path}/asset-manage`} component={AssetManage} />
<Route path={`${match.path}/asset-resource-browse`} component={AssetResourceBrowse} /> <Route path={`${match.path}/asset-resource-browse`} component={AssetResourceBrowse} />
<Route path={`${match.path}/asset-browse`} component={AssetBrowse} /> <Route path={`${match.path}/asset-browse`} component={AssetBrowse} />
<Route path={`${match.path}/task-manage`} component={TaskManage} />
<Route path={`${match.path}/asset-recycle`} component={AssetRecycle} /> <Route path={`${match.path}/asset-recycle`} component={AssetRecycle} />
<Route path={`${match.path}/asset-operation`} component={AssetOperation} />
<Route path={`${match.path}/msd-define`} component={DataMasterDefine} /> <Route path={`${match.path}/msd-define`} component={DataMasterDefine} />
<Route path={`${match.path}/msd-manage`} component={DataMasterManage} /> <Route path={`${match.path}/msd-manage`} component={DataMasterManage} />
<Route path={`${match.path}/metadata-harvester`} component={MetadataHarvester} /> <Route path={`${match.path}/metadata-harvester`} component={MetadataHarvester} />
......
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