ab963c5b by fanguang Committed by lihua

元数据标准

1 parent eb9af70f
......@@ -365,6 +365,13 @@ export const deleteMetaStandardDataFields = (params) => request({
method: 'delete',
data: params
})
/** 元数据标准-导出 */
export const exportMetaStandardData = (params) => request({
url: `${import.meta.env.VITE_APP_STANDARD_URL}/meta-standard/data/data-export`,
method: 'post',
data: params,
responseType: 'blob'
})
/** 标准代码-树形表 */
export const getStandardCodeTree = () => request({
url: `${import.meta.env.VITE_APP_STANDARD_URL}/standard-code/code-tree`,
......
......@@ -281,6 +281,17 @@ const routes: RouteRecordRaw[] = [
cache: true,
activeMenu: '/data-meta/metadata-standard/standard-codetable'
}
},
{
path: 'standard-meta-import',
name: 'standardMetaImport',
component: () => import('@/views/data_meta/standard-meta-import.vue'),
meta: {
title: '元数据标准导入',
breadcrumb: false,
cache: true,
activeMenu: '/data-meta/metadata-standard'
}
}
]
}
......
......@@ -7,7 +7,7 @@
>
<el-form :model="form" :rules="formRules" ref="formEl" style="min-height: 200px;">
<el-row>
<el-col v-for="item,index in fields" :key="index" :span="12" style="padding-right:10px">
<el-col v-for="item,index in fields" :key="index" :span="12" style="padding-right:10px;margin-bottom:10px;">
<el-form-item :label="item.fileNameCodeName" :prop="item.fileNameCode">
<el-input
v-if="item.inputTypeCode == '1' || item.inputTypeCode == '3'"
......@@ -20,10 +20,17 @@
filterable
clearable
placeholder="请选择"
size="small"
>
<el-option v-for="op in formOptions[item.fileNameCode]" :label="op.label" :value="op.value" :key="op.value"></el-option>
</el-select>
<el-tree-select
v-else-if="item.inputTypeCode == '4'"
v-model="form[item.fileNameCode]"
:data="standardCodeTree"
:props="treeSelectProps"
placeholder="请选择"
/>
</el-form-item>
</el-col>
</el-row>
......@@ -39,7 +46,7 @@
import { watch } from 'vue'
import { ElMessage } from "element-plus";
import { getParamsList } from '@/api/modules/dataAsset'
import { saveMetaStandardDataFields, getMetaStandardFieldDetail } from '@/api/modules/dataMetaService'
import { saveMetaStandardDataFields, getMetaStandardFieldDetail, getStandardCodeTree } from '@/api/modules/dataMetaService'
const { proxy } = getCurrentInstance() as any;
const props = defineProps({
......@@ -152,6 +159,30 @@ function confirm () {
})
}
const standardCodeTree = ref([])
const treeSelectProps = {
label: 'name',
value: 'guid',
isLeaf: 'isCode'
}
function getStandardCodeTreeList () {
getStandardCodeTree().then((res:any) => {
if (res.code === proxy.$passCode) {
const data = res.data
data.forEach(item => {
if (item.children) {
item.children.forEach(subItem => {
// 二级的标准名字作为key
subItem.guid = subItem.name
// subItem.value = subItem.name
})
}
})
standardCodeTree.value = data
}
})
}
watch(
() => visible.value,
(v) => {
......@@ -159,6 +190,10 @@ watch(
initForm()
}
)
onBeforeMount(() => {
getStandardCodeTreeList()
})
</script>
<style lang="scss">
......
......@@ -16,7 +16,7 @@ import { getParamsList } from '@/api/modules/dataAsset'
import { getStandardCodeList, saveStandardCode,
updateStandardCode, getStandardCodeDetail,
deleteStandardCode, getStandardCodeStandard, exportStandardCodeData,
getStandardCodeDataList
getStandardCodeDataList, getStandardCodeTree
} from '@/api/modules/dataMetaService'
import {
addDictionary,
......@@ -1440,8 +1440,30 @@ const radioGroupChange = async (val, info) => {
}
}
function initTree () {
Promise.all([getParamsList({ dictType: '标准类型'}), getStandardCodeTree()]).then((resList:any) => {
let treeRoot = resList[0].data || []
let treeData = resList[1].data || []
console.log('treeRoot', treeRoot)
console.log('treeData', treeData)
let tree = treeRoot.map(item => {
let obj:any = {}
obj.treeLevel = 1
obj.guid = item.value
obj.name = item.label
let target = treeData.find(v => v.guid === item.value)
obj.children = target ? target.children : null
return obj
})
// standardOptions.value = treeRoot
// treeInfo.value.data = tree
// nodeClick(tree[0])
})
}
onBeforeMount(() => {
// getDataType()
// initTree()
getTreeData().then(() => {
// 默认展开第一个
let data = treeInfo.value.data
......
<route lang="yaml">
name: standardMetaImport
</route>
<script lang="ts" setup name="standardMetaImport">
import { ref } from "vue";
import { useRoute, useRouter } from "vue-router"
import useUserStore from "@/store/modules/user";
import { ElMessage, ElMessageBox } from "element-plus";
import Tabs from '@/components/Tabs/index.vue'
import Table from '@/components/Table/index.vue'
import Dialog from '@/components/Dialog/index.vue'
import useCatchStore from "@/store/modules/catch";
import { download, downFileByBob, downFile } from '@/utils/common'
import {
addImportData,
deleteImportData,
getImportData,
exportDictionary,
exportCollectTask,
// getImageContent
} from '@/api/modules/queryService';
import {
parseAndDecodeUrl,
getDownFileSignByUrl,
obsDownloadRequest
} from '@/api/modules/obsService';
import {
getDictionaryTree
} from '@/api/modules/dataInventory';
import { commonPageConfig } from '@/utils/enum';
import * as XLSX from 'xlsx';
const { proxy } = getCurrentInstance() as any;
const userStore = useUserStore()
const route = useRoute()
const router = useRouter()
/** 2表示资产目录的。3是主数据; 4是元数据导入 */
const isfileImport = route.query.isfileImport
const userData = JSON.parse(userStore.userData)
const cacheStore = useCatchStore()
const standardSetList = ref([])
const standardSetGuid = ref('')
const dictionaryList = ref([])
const dictionaryGuid = ref('')
const tabsActiveName = ref('')
const uploadSetting: any = ref([])
const importType = ref('')
const defaulttabs = [
// { label: '标准集导入', name: 'standard' },
// { label: '字段标准导入', name: 'field' },
// { label: '命名标准导入', name: 'naming' },
{ label: '数据字典导入', name: 'dictionary' },
// { label: '质量模型导入', name: 'qualityModelGroup' },
// { label: '质量规则导入', name: 'qualityRule' },
]
const importTabs = [
{ label: '导入文件数据', name: 'importFile' },
// { label: '质量模型导入', name: 'qualityModelGroup' },
// { label: '质量规则导入', name: 'qualityRule' },
]
const tabsInfo = ref({
activeName: '',
tabs: isfileImport ? importTabs : defaulttabs
})
const currTableData: any = ref<Object>({});
const page = ref(commonPageConfig);
const selectRowData = ref([])
const tableInfo = ref({
id: 'data-source-table',
multiple: true,
fields: [
{ label: "序号", type: "index", width: 56, align: "center" },
{ label: "文件名称", field: "fileName", width: 240, },
{ label: "状态", field: "importState", type: 'tag', width: 110, align: 'center' },
{ label: "导入结果", field: "importMessage", width: 280 },
{ label: "导入时间", field: "createTime", width: 180 },
],
data: [],
page: {
type: "normal",
rows: 0,
...page.value,
},
actionInfo: {
label: "操作",
type: "btn",
width: 220,
fixed: 'right',
btns: (scope) => {
const row = scope.row
let btnsArr = [
{ label: '下载文件', value: 'export_file' },
{ label: '删除', value: 'delete' }
]
if (row.importState != 0 && row.importState != 1) {
btnsArr.splice(1, 0, { label: '下载异常数据', value: 'export_abnormal_data' })
}
return btnsArr
},
},
loading: false
})
const uploadFiles = ref([])
const uploadSteps: any = ref([])
const uploadInfo = ref({
type: 'upload',
title: '',
col: '',
uploadInfo: {
id: 'upload-file-form',
type: 'panel',
steps: [],
extraParams: {dictionaryGuid: 'xx'},
},
})
const dialogInfo: any = ref({
visible: false,
size: 560,
direction: "column",
header: {
title: "新建",
},
type: 'upload',
contents: [
uploadInfo.value
],
footer: {
visible: true,
btns: [
{ type: "default", label: "取消", value: "cancel" },
{ type: "primary", loading: false, label: "开始导入", value: "submit" },
],
},
})
// // 获取所有数据字典
const getDictList = () => {
const params = {
paramCode: '数据字典类型'
}
getDictionaryTree(params).then((res: any) => {
if (res.code == proxy.$passCode) {
const data = res.data ?? []
const treeList = data.filter(item => item.children && item.children.length)
dictionaryList.value = treeList
} else {
ElMessage({
type: 'error',
message: res.msg,
})
}
})
}
const tabsChange = (name) => {
tabsActiveName.value = name
let info: any = {
type: name
}
if (tabsActiveName.value == 'field' || tabsActiveName.value == 'naming') {
info.standardSetGuid = standardSetGuid.value
} else if (tabsActiveName.value == 'dictionary') {
info.dictionaryGuid = dictionaryGuid.value
} else if (tabsActiveName.value == 'importFile') {
}
cacheStore.setCatch('uploadSetting', info)
setUploadInfo()
}
const getFirstPageData = () => {
page.value.curr = 1
toSearch({})
console.log('store', cacheStore.getCatch('uploadSetting'))
}
const toSearch = (val: any, clear: boolean = false) => {
let params: any = Object.keys(val).length ? { ...val } : {}
params.pageIndex = page.value.curr;
params.pageSize = page.value.limit;
params.staffGuid = userData.staffGuid;
params.importType = importType.value;
params.bizGuid = route.query.bizGuid || ''
getTableData(params);
};
const getTableData = (params) => {
tableInfo.value.loading = true
getImportData(params).then((res: any) => {
if (res.code == proxy.$passCode) {
const data = res.data || {}
tableInfo.value.data = data.records || []
tableInfo.value.page.limit = data.pageSize
tableInfo.value.page.curr = data.pageIndex
tableInfo.value.page.rows = data.totalRows
} else {
ElMessage({
type: 'error',
message: res.msg,
})
}
tableInfo.value.loading = false
}).catch(xhr => {
tableInfo.value.loading = false
})
};
const tableSelectionChange = (val) => {
selectRowData.value = val.map(item => item.guid);
};
const tablePageChange = (info) => {
page.value.curr = Number(info.curr);
page.value.limit = Number(info.limit);
toSearch({});
};
const tableBtnClick = async (scope, btn) => {
const type = btn.value;
const row = scope.row;
currTableData.value = row;
if (type == "export_file") {
const refSignInfo: any = await getDownFileSignByUrl(parseAndDecodeUrl(row.filePath).fileName);
if (!refSignInfo?.data) {
refSignInfo?.msg && ElMessage.error(refSignInfo?.msg);
return;
}
obsDownloadRequest(refSignInfo?.data).then((res: any) => {
if (res && !res.msg) {
downFileByBob(res, row.fileName);
} else {
res?.msg && ElMessage.error(res?.msg);
}
});
//downFile(row.filePath, row.fileName)
} else if (type == 'export_abnormal_data') {
//downFile(row.errorFilePath, '')
const refSignInfo: any = await getDownFileSignByUrl(parseAndDecodeUrl(row.errorFilePath).fileName);
if (!refSignInfo?.data) {
refSignInfo?.msg && ElMessage.error(refSignInfo?.msg);
return;
}
obsDownloadRequest(refSignInfo?.data).then((res: any) => {
if (res && !res.msg) {
let name = row.errorFilePath;
let fileName = name ? name.substring(name.lastIndexOf('/') + 1) : ''
downFileByBob(res, fileName);
} else {
res?.msg && ElMessage.error(res?.msg);
}
});
} else if (type == "delete") {
open("此操作将永久删除, 是否继续?", "warning");
}
};
const batching = (type) => {
if (type == 'import') {
dialogInfo.value.header.title = '导入数据'
dialogInfo.value.type = 'upload'
dialogInfo.value.size = 560
uploadFiles.value = []
// if (tabsActiveName.value == 'field' || tabsActiveName.value == 'naming') {
// uploadSteps.value[0].selectInfo.options = standardSetList.value
// } else if (tabsActiveName.value == 'dictionary') {
// uploadSteps.value[0].cascaderInfo.options = dictionaryList.value
// }
uploadInfo.value.uploadInfo.steps = uploadSteps.value
uploadSetting.value.forEach(item => item.value = '')
sheetNameList.value = []
const content: any = [uploadInfo.value]
dialogInfo.value.contents = content
dialogInfo.value.visible = true
} else if (type == 'delete') {
if (selectRowData.value.length == 0) {
ElMessage({
type: 'error',
message: '请选择需要删除的数据',
})
return
}
open("此操作将永久删除, 是否继续?", "warning", true);
} else if (type === 'importFile') {
if (isfileImport == '2' || isfileImport == '4') {
dialogInfo.value.header.title = '导入数据'
dialogInfo.value.type = 'upload'
dialogInfo.value.size = isfileImport == '4' ? 560 : 560;
uploadFiles.value = []
uploadInfo.value.uploadInfo.steps = uploadSteps.value
const content: any = [uploadInfo.value]
dialogInfo.value.contents = content
dialogInfo.value.visible = true
} else {
router.push({
name: "importData",
query: route.query
})
}
}
};
const open = (msg, type, isBatch = false) => {
ElMessageBox.confirm(msg, "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: type,
}).then(() => {
let guids = [currTableData.value.guid]
if (isBatch) {
guids = selectRowData.value
}
deleteImportData(guids).then((res: any) => {
if (res.code == proxy.$passCode) {
getFirstPageData();
ElMessage({
type: "success",
message: "删除成功",
});
} else {
ElMessage({
type: "error",
message: res.msg,
});
}
});
});
};
const sheetNameList = ref([])
const onUpload = (file, fileList) => {
// console.log('file', file)
uploadFiles.value = fileList
const reader = new FileReader()
reader.onload = function (e) {
let data = new Uint8Array(e.target.result)
let wb = XLSX.read(data, { type: 'array', raw: false, cellDates: true })
console.log('wb', wb)
sheetNameList.value = wb.SheetNames
}
reader.readAsArrayBuffer(file.raw)
}
const uploadBtnClick = (btn) => {
exportData()
}
const cascaderChange = (val) => {
dictionaryGuid.value = val ? val.at(-1) : ''
}
const selectChange = (val) => {
standardSetGuid.value = val
}
const exportData = (ids: any = null) => {
if (tabsActiveName.value == 'standard') {
const fieldTemplate = "/files/set.xlsx";
downFile(fieldTemplate, '标准集模板.xlsx')
} else if (tabsActiveName.value == 'field') {
const fieldTemplate = "/files/field.xlsx";
downFile(fieldTemplate, '字段标准模板.xlsx')
} else if (tabsActiveName.value == 'naming') {
const namingTemplate = "/files/naming.xlsx";
downFile(namingTemplate, '命名标准模板.xlsx')
} else if (tabsActiveName.value == 'dictionary') {
const params = {
guid: dictionaryGuid.value
}
exportDictionary(params).then((res: any) => {
if (res && !res.msg) {
download(res, '数据字典模板.xlsx', 'excel');
} else {
res?.msg && ElMessage.error(res?.msg);
}
});
} else if (tabsActiveName.value == 'importFile' && isfileImport == '4') {
exportCollectTask({
importTypes: [
"0042"
]
}).then((res: any) => {
if (res && !res.msg) {
download(res, '元数据模板.xlsx', 'excel');
} else {
res?.msg && ElMessage.error(res?.msg);
}
});
}
}
const importData = (info) => {
let params = new FormData()
if (uploadFiles.value.length == 0) {
ElMessage({
type: 'error',
message: '请选择上传文件'
})
// dialogInfo.value.footer.btns.map((item: any) => delete item.disabled)
return
}
let sheetPass = uploadSetting.value.every(item => item.value)
if (!sheetPass) {
ElMessage({ type: 'error', message: '请选择sheet页'})
return
}
let paramUrl = '';
uploadFiles.value.forEach((item: any, index: number) => {
params.append("file", item.raw);
});
let sheetMaps = {}
uploadSetting.value.forEach(item => {
sheetMaps[item.value] = item.standardGuid
})
sheetMaps = JSON.stringify(sheetMaps)
// console.log('sheetMaps', sheetMaps)
paramUrl = encodeURI(`${import.meta.env.VITE_APP_ADD_FILE}/import-data/import-batch-common?importType=${importType.value}&staffGuid=${userData.staffGuid}&tenantGuid=${userData.tenantGuid}&sheetMaps=${sheetMaps}`)
// if (info && Object.keys(info).length) {
// paramUrl += `&extendFields=${encodeURIComponent(JSON.stringify(info))}`
// }
dialogInfo.value.footer.btns[1].loading = true;
addImportData(paramUrl, params).then((res: any) => {
dialogInfo.value.footer.btns[1].loading = false;
if (res.code == proxy.$passCode) {
getFirstPageData();
ElMessage({
type: "success",
message: '导入成功',
});
dialogInfo.value.visible = false;
} else {
ElMessage({
type: "error",
message: res.msg,
});
// dialogInfo.value.footer.btns.map((item: any) => delete item.disabled)
}
}).catch(() => {
dialogInfo.value.footer.btns[1].loading = false;
})
}
const dialogBtnClick = (btn, info) => {
if (btn.value == 'submit') {
// dialogInfo.value.footer.btns.map((item: any) => item.disabled = true)
if (dialogInfo.value.type == 'upload') {
if (tabsActiveName.value == 'dictionary') {
importData({ bizGuid: dictionaryGuid.value })
} else {
importData(info)
}
}
} else if (btn.value == 'cancel') {
// dialogInfo.value.footer.btns.map((item: any) => delete item.disabled)
nextTick(() => {
dialogInfo.value.visible = false;
})
};
}
const setUploadInfo = () => {
importType.value = '0102'
tabsInfo.value.activeName = tabsActiveName.value
getFirstPageData()
uploadSteps.value = [
{
title: '1、选择准备好的文件导入',
type: 'btn_upload',
uploadInfo: {
action: '',
auto: false,
cover: true,
fileList: [],
accept: '.xlsx, .xls',
tips: '当前支持xls、xlsx文件,支持一个文件多个sheet批量导入'
}
}
]
}
onActivated(() => {
let list = cacheStore.getCatch('uploadSetting') || []
uploadSetting.value = list.map(item => {
item.value = null
return item
})
console.log('uploadSetting', uploadSetting.value)
setUploadInfo()
})
</script>
<template>
<div class="container_wrap">
<!-- <Tabs v-if="!isfileImport" :tabs-info="tabsInfo" @tabChange="tabsChange" /> -->
<div class="table_tool_wrap">
<div class="tools_btns">
<el-button type="primary" @click="batching('import')" v-if="tabsActiveName !== 'importFile'"
v-preReClick>批量导入</el-button>
<el-button type="primary" @click="batching('importFile')" v-if="tabsActiveName == 'importFile'"
v-preReClick>文件导入</el-button>
<el-button @click="batching('delete')" v-preReClick>批量删除</el-button>
<el-button @click="getFirstPageData" v-preReClick>刷新结果</el-button>
</div>
<span class="tips_text">请及时刷新查看最终结果</span>
</div>
<div class="table_panel_wrap" :style="{ height: !isfileImport ? 'calc(100% - 71px)' : 'calc(100% - 44px)' }">
<Table :tableInfo="tableInfo" @tableBtnClick="tableBtnClick" @tableSelectionChange="tableSelectionChange"
@tablePageChange="tablePageChange" />
</div>
<Dialog :dialogInfo="dialogInfo" @btnClick="dialogBtnClick" @onUpload="onUpload" @uploadBtnClick="uploadBtnClick"
@cascaderChange="cascaderChange" @selectChange="selectChange">
<div style="overflow: auto;">
<div class="title" style="color:#333;margin:20px 0 10px">2、导入前请先导入文件的sheet与标准做对应</div>
<el-table :data="uploadSetting" border height="310">
<el-table-column type="index" label="序号" width="55" align="center"/>
<el-table-column label="标准名称" prop="standardName"></el-table-column>
<el-table-column label="选择sheet页">
<template #default="scope">
<el-select v-model="scope.row.value" placeholder="请选择" style="width:200px" clearable>
<el-option v-for="item,i in sheetNameList" :label="item" :value="item" :key="i"></el-option>
</el-select>
</template>
</el-table-column>
</el-table>
</div>
</Dialog>
</div>
</template>
<style lang="scss" scoped>
.container_wrap {
padding: 0;
:deep(.el-tabs) {
.el-tabs__header {
margin-bottom: 0;
}
.el-tabs__item {
height: 32px;
&:nth-child(2) {
padding-left: 16px;
}
&:last-child {
padding-right: 16px;
}
&::after {
content: '';
width: 100%;
height: 2px;
background-color: transparent;
position: absolute;
left: 0;
bottom: 0;
}
&.is-active {
&::after {
background-color: var(--el-color-primary);
}
}
}
.el-tabs__active-bar {
display: none;
}
}
.table_tool_wrap {
padding: 0 16px;
display: flex;
align-items: center;
.tips_text {
margin-left: 16px;
font-size: 14px;
color: #b2b2b2;
}
}
.table_panel_wrap {
padding: 0 16px;
height: calc(100% - 71px);
}
}
</style>
<style lang="scss">
.upload_panel_wrap .upload_panel .file_panel .file_item .file_btn {
word-break: keep-all;
}
</style>
......@@ -15,7 +15,7 @@ import { download } from '@/utils/common'
import { getParamsList } from '@/api/modules/dataAsset'
import { getMetaStandardTree, deleteMetaStandard,
getMetaStandardDataList, getMetaStandardDataFields,
deleteMetaStandardDataFields
deleteMetaStandardDataFields, exportMetaStandardData
} from '@/api/modules/dataMetaService'
import router from '@/router'
import { TableColumnWidth } from '@/utils/enum';
......@@ -52,8 +52,9 @@ const treeInfo = ref({
function nodeClick (data) {
console.log('nodeData', data)
treeInfo.value.currentObj = data
getFirstPageData()
if (data.level == 1) return
getTableFields()
getFirstPageData()
}
function treeCustomClick (node, type) {
console.log(node, type)
......@@ -79,6 +80,7 @@ function getTree () {
let data = res.data || []
data.forEach(item => {
item.showEdit = true
item.level = 1
})
treeInfo.value.data = data
treeInfo.value.expandedKey = [data[0].guid]
......@@ -377,6 +379,40 @@ function openStandardFieldsDialog (type, data = {}) {
standardFieldsDialog.visible = true
}
function importData () {
let currentTreeObj:any = treeInfo.value.currentObj
console.log('currentTree', currentTreeObj)
let uploadSetting:any = []
if (currentTreeObj.children) {
uploadSetting = currentTreeObj.children.map((item:any) => {
return {
standardName: item.standardName,
standardGuid: item.guid
}
})
} else {
uploadSetting = [{
standardName: currentTreeObj.standardName,
standardGuid: currentTreeObj.guid
}]
}
cacheStore.setCatch('uploadSetting', uploadSetting)
router.push({
path: '/data-meta/metadata-standard/standard-meta-import',
});
}
function exportData () {
let body = [treeInfo.value.currentObj.guid]
exportMetaStandardData(body).then((res:any) => {
if (res && !res.msg) {
download(res, '元数据标准表.xlsx', 'excel')
} else {
res?.msg && ElMessage.error(res?.msg);
}
})
}
onBeforeMount(() => {
getTree()
})
......@@ -401,9 +437,9 @@ onBeforeMount(() => {
<div class="table_tool_wrap">
<div class="tools_btns">
<el-button type="primary" @click="() => openStandardFieldsDialog('add')" v-preReClick>新建</el-button>
<el-button @click="batching('export')" v-preReClick>导入</el-button>
<el-button @click="batching('delete')" v-preReClick>导出</el-button>
<el-button @click="batching('delete')" v-preReClick>查看</el-button>
<el-button @click="importData" v-preReClick>导入</el-button>
<el-button @click="exportData" v-preReClick>导出</el-button>
<el-button @click="viewGraph" v-preReClick>查看</el-button>
</div>
<el-input class="table_search_input" v-model.trim="tableSearchInput" placeholder="请输入关键字搜索"
:suffix-icon="Search" clearable @change="val => getFirstPageData()" />
......
Styling with Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!