c7acd525 by lihua

数据盘点分类标准

1 parent 8e6500f7
<svg xmlns="http://www.w3.org/2000/svg" width="200" height="200" class="icon" viewBox="0 0 1024 1024"><path d="M32 64h342.304a32 32 0 0 1 17.76 5.376L576 192h416a32 32 0 0 1 32 32v640a32 32 0 0 1-32 32H32a32 32 0 0 1-32-32V96a32 32 0 0 1 32-32z" fill="#ffb44a" p-id="1930"></path></svg>
\ No newline at end of file
......@@ -268,6 +268,7 @@ onMounted(() => {
:min-width="item.minWidth"
:fixed="item.fixed"
:align="item.align"
:type="item.type == 'index' ? '' : undefined"
:sortable="item.sortable ?? false"
:prop="item.field"
:class-name="item.columClass"
......
import type { RouteRecordRaw } from 'vue-router'
function Layout() {
return import('@/layouts/index.vue')
}
const routes: RouteRecordRaw[] = [
{
path: '/data-inventory/classify-grade-manage',
component: Layout,
meta: {
title: '分类分级管理',
icon: 'sidebar-videos',
},
children: [
{
path: 'task-config',
name: 'taskConfig',
component: () => import('@/views/data_inventory/taskConfig.vue'),
meta: {
title: '分类分级任务',
breadcrumb: false,
cache: true
},
},
{
path: 'template-config',
name: 'templateConfig',
component: () => import('@/views/data_inventory/templateConfig.vue'),
meta: {
title: '分类分级模板',
breadcrumb: false,
cache: true
},
},
{
path: 'classStandard-edit',
name: 'classStandardEdit',
component: () => import('@/views/data_inventory/classStandardEdit.vue'),
meta: {
title: '编辑-',
sidebar: false,
breadcrumb: false,
cache: true,
reuse: true,
editPage: true,
activeMenu: '/data-inventory/classify-grade-manage/template-config'
},
beforeEnter: (to, from) => {
if (to.query.classStandardName) {
to.meta.title = `编辑-${to.query.classStandardName}`;
}
}
},
],
}
]
export default routes
......@@ -4,6 +4,7 @@ import type { RouteRecordRaw } from 'vue-router'
import DataAssess from './modules/dataAsset';
import DataMeta from './modules/dataMeta';
import DataQuality from './modules/dataQuality';
import DataInventory from './modules/dataInventory';
import type { Route } from '#/global'
import useSettingsStore from '@/store/modules/settings'
......@@ -122,7 +123,14 @@ const asyncRoutes: Route.recordMainRaw[] = [
children: [
...DataQuality,
],
}
}, {
meta: {
title: '数据盘点',
},
children: [
...DataInventory,
],
},
]
const constantRoutesByFilesystem = generatedRoutes.filter((item) => {
......
......@@ -227,6 +227,8 @@ const useUserStore = defineStore(
mark = 'data-sync'
} else if (item.meta.title == '数据质量') {
mark = 'data-quality'
} else if (item.meta.title == '数据盘点') {
mark = 'data-inventory'
} else if (item.meta.title == '首页') {
mark = 'data-asset-index'
} else if (item.meta.title == '数据服务') {
......
<route lang="yaml">
name: classStandardEdit //分类标准编辑
</route>
<script lang="ts" setup name="classStandardEdit">
import { ref, onMounted} from "vue";
import useUserStore from "@/store/modules/user";
import { useValidator } from '@/hooks/useValidator';
import { TableColumnWidth } from '@/utils/enum';
import G6 from '@antv/g6';
import { IGroup, ModelConfig } from '@antv/g6';
const { required, orderNum } = useValidator();
const { proxy } = getCurrentInstance() as any;
const router = useRouter();
const route = useRoute();
const fullPath = route.query.fullPath;
const userStore = useUserStore();
const fullscreenLoading = ref(false);
const formRef = ref();
const classStandardFormItems = ref([{
label: '分类名称',
type: 'input',
maxlength: 50,
placeholder: '请输入',
field: 'classStandardName',
default: '',
clearable: true,
disabled: true,
required: true
}, {
label: '分级标准',
type: 'select',
placeholder: '请选择',
field: 'gradeStandard',
default: 1,
options: [], //TODO
required: true,
filterable: true,
clearable: true,
disabled: true,
visible: true,
}]);
const currTableInfo = ref({});
const tableInfo = ref({
id: "data-class-standard-table",
multiple: false,
fields: [
{ label: "序号", type: "index", width: 56, align: "center" },
{ label: "分类", field: "className", width: 160, type: 'expand' },
{ label: "层级", field: "levelName", width: 120 },
{ label: "状态", field: "state", type: "tag", width: 120, align: "center" },
{ label: "定义说明", field: "description", width: TableColumnWidth.DESCRIPTION },
{ label: "最低安全级别参考", field: "minLevel", width: 140 },
{ label: "修改人", field: "updateUserName", width: 140 },
{ label: "更新时间", field: "updateTime", width: 180 },
],
data: [{
guid: '1',
children: [{
guid: '1-1'
}]
}],
showPage: false,
actionInfo: {
label: "操作",
type: "btn",
width: 120,
btns: [
{ label: "编辑", value: "edit", click: (scope) => {
currTableInfo.value = scope.row;
drawerInfo.value.visible = true;
drawerInfo.value.header.title = '编辑分类';
classEditFormItems.value.forEach(item => {
item.default = scope.row[item.field]
})
} },
{ label: "删除", value: "delete", click: (scope) => {
proxy.$openMessageBox("此操作将永久删除该分类, 是否继续", () => {
// deleteBizTerm([scope.row.guid]).then((res: any) => {
// if (res.code == proxy.$passCode) {
// getTableData();
// proxy.$ElMessage.success('该分类删除成功');
// } else {
// proxy.$ElMessage.error(res.msg);
// }
// });
})
} },
],
},
loading: false,
});
const classEditFormItems = ref([{
label: '分类名称',
type: 'input',
maxlength: 50,
placeholder: '请输入',
field: 'classStandardName',
default: '',
clearable: true,
required: true
}, {
label: '排序',
type: 'input',
placeholder: '请输入',
field: 'orderNum',
maxlength: 6,
regexp: /\D/g,
required: true,
clearable: true,
}, {
label: '上级分类',
field: 'parentGuid',
type: 'tree-select',
placeholder: '请选择',
default: '',
options: tableInfo.value.data,
showAllLevels: false,
checkStrictly: true,
lazy: false,
props: {
label: "className",
value: "guid",
},
block: true,
filterable: true,
clearable: true,
required: false
}, {
label: '最低安全级别参考',
type: 'select',
placeholder: '请选择',
field: 'lowerLevel',
default: 1,
options: [], //TODO
required: false,
filterable: true,
clearable: true,
visible: true,
}, {
label: '业务状态',
type: 'switch',
field: 'state',
default: 'Y',
activeText:"有效",
inactiveText:"停用",
activeValue: 'Y',
inactiveValue: 'N'
}, {
label: '定义说明',
type: 'textarea',
placeholder: '请输入',
field: 'description',
default: '',
clearable: true,
required: false,
block: true
}]);
const classEditFormRules = ref({
classStandardName: [required('请填写分类名称')], //TODO,要自己判断分类名称是否重复。规则是什么呢。
orderNum: [orderNum()]
});
/** 新增分类的form */
const classEditFormInfo = ref({
type: "form",
title: "",
col: "span",
formInfo: {
id: "add-class-form",
readonly: false,
items: classEditFormItems.value,
rules: classEditFormRules.value,
},
});
/** 新增编辑分类。 */
const drawerInfo = ref({
visible: false,
direction: 'rtl',
size: 600,
header: {
title: '添加分类',
},
type: '',
container: {
contents: [classEditFormInfo.value],
},
footer: {
btns: [
{ type: 'default', label: '取消', value: 'cancel' },
{ type: 'primary', label: '确定', value: 'save', loading: false },
]
}
})
const drawerBtnClick = (btn, info) => {
if (btn.value == 'cancel') {
drawerInfo.value.visible = false;
} else {
}
}
const newCreateClass = () => {
drawerInfo.value.visible = true;
drawerInfo.value.header.title = '添加分类';
classEditFormItems.value.forEach(item => {
if (item.field == 'state') {
item.default = 'Y';
} else {
item.default = '';
}
})
}
/** 导入分类。 */
const importClass = () => {
}
const dataShowMethod = ref('shape');
/** 切换是图形展示,还是表格展示。 */
const changeShowMethod = () => {
dataShowMethod.value = dataShowMethod.value == 'table' ? 'shape' : 'table';
}
const cancel = () => {
proxy.$openMessageBox("当前页面尚未保存,确定放弃修改吗?", () => {
userStore.setTabbar(userStore.tabbar.filter((tab: any) => tab.fullPath !== fullPath));
router.push({
name: 'templateConfig'
});
}, () => {
proxy.$ElMessage.info("已取消");
});
}
const save = () => {
}
const graph = ref();
const shapeMain = ref();
var COLLAPSE_ICON = function COLLAPSE_ICON(x, y, r) {
return [['M', x, y], ['a', r, r, 0, 1, 0, r * 2, 0], ['a', r, r, 0, 1, 0, -r * 2, 0], ['M', x + 2, y], ['L', x + 2 * r - 2, y]];
};
var EXPAND_ICON = function EXPAND_ICON(x, y, r) {
return [['M', x, y], ['a', r, r, 0, 1, 0, r * 2, 0], ['a', r, r, 0, 1, 0, -r * 2, 0], ['M', x + 2, y], ['L', x + 2 * r - 2, y], ['M', x + r, y - r + 2], ['L', x + r, y + r - 2]];
};
G6.registerNode('tree-node', {
drawShape: function drawShape(cfg: ModelConfig, group: IGroup) {
var rect = group.addShape('rect', {
attrs: {
fill: '#F6FDFD',
stroke: '#D1E7E8'
}
});
var content = cfg.name.replace(/(.{19})/g, '$1\n');
var text = group.addShape('text', {
attrs: {
text: content,
x: 0,
y: 0,
textAlign: 'left',
textBaseline: 'middle',
fill: '#4FA1A4',
fontSize: 10,
fontFamily: 'simsun'
}
});
var bbox = text.getBBox();
var hasChildren = cfg.children && cfg.children.length > 0;
if (hasChildren) {
group.addShape('circle', {
attrs: {
x: 95,
y: bbox.minX + bbox.height / 2-5,
r: 5,
stroke: '#4FA1A4',
fill: '#fff',
lineWidth: 1
},
name: 'collapse-icon',
className: 'collapse-icon',
});
group.addShape('text', {
attrs: {
x: 92,
y: 8.5,
fill: '#4FA1A4',
text: '-',
fontSize: 16
},
name: 'collapse-icon-text',
className: 'collapse-icon-text',
});
// group.addShape('marker', {
// attrs: {
// x: 90,
// y: bbox.minX + bbox.height / 2-5,
// r: 6,
// symbol: COLLAPSE_ICON,
// fill: '#fff',
// stroke: '#666',
// lineWidth: 1,
// },
// className: 'collapse-icon'
// });
}
rect.attr({
x: bbox.minX - 4,
y: bbox.minY - 6,
width: 100, //图形的宽度
height: bbox.height + 13
});
return rect;
}
}, 'single-shape');
onMounted(() => {
graph.value = new G6.TreeGraph({
container: shapeMain.value,
width: shapeMain.value.clientWidth,
height: shapeMain.value.clientHeight,
minZoom: 1.4,
maxZoom: 1.4,
modes: {
default: [{
type: 'collapse-expand',
onChange: function onChange(item, collapsed, event) {
var data = item.get('model');
var icon = item.get('group').findByClassName('collapse-icon');
if (collapsed) {
icon.attr('symbol', EXPAND_ICON);
} else {
icon.attr('symbol', COLLAPSE_ICON);
}
data.collapsed = collapsed;
return true;
}
}, 'drag-canvas', 'zoom-canvas']
},
defaultNode: {
type: 'tree-node',
anchorPoints: [[0, 0.5], [1, 0.5]]
},
defaultEdge: {
type: 'polyline',
style: {
stroke: '#4FA1A4',
endArrow: {
fill: '#4FA1A4',
stroke: '#4FA1A4',
path: 'M -2,0 L 2,2 L 2,-2 Z',
d: -2,
}
}
},
layout: {
type: 'compactBox',
direction: 'LR',
getId: function getId(d) {
return d.id;
},
getHeight: function getHeight() {
return 16;
},
getWidth: function getWidth() {
return 16;
},
getVGap: function getVGap() {
return 20;
},
getHGap: function getHGap() {
return 80;
}
}
});
graph.value.data({
guid: 1,
name: '工业数据',
children: [{
guid: '1-1',
name: '原材料',
children: [{
guid: '1-1-1',
name: '用户数据分类有带你长'
}]
}, {
guid: '1-2',
name: '装备制造'
}]
});
graph.value.render();
graph.value.fitView();
graph.value.on('node:click', (evt: any) => {
evt.preventDefault();
evt.stopPropagation();
const { item, target } = evt;
const currentAnchor = target.get('name');
if (!currentAnchor) return;
// 设置一个全局属性,指定点击的是哪个图形元素
});
})
</script>
<template>
<div class="container_wrap" v-loading="fullscreenLoading">
<div class="content_main">
<ContentWrap id="id-baseInfo" title="基础信息" description="" style="margin-top: 8px;">
<Form ref="formRef" :itemList="classStandardFormItems" formId="main-model-edit" col="col3" />
</ContentWrap>
<ContentWrap id="id-classStandard" class="detail-content" title="分类标准" description="" style="margin-top:16px; height: calc(100% - 161px)">
<div class="tools_btns">
<el-button v-show="dataShowMethod == 'table'" type="primary" @click="newCreateClass">添加分类</el-button>
<el-button v-show="dataShowMethod == 'table'" @click="importClass">导入分类</el-button>
<el-button class="show-change-btn" @click="changeShowMethod">{{ '图形展示' }}</el-button>
</div>
<Table v-show="dataShowMethod == 'table'" :tableInfo="tableInfo" />
<div ref="shapeMain" class="shape-main" v-show="dataShowMethod != 'table'"></div>
</ContentWrap>
</div>
<div class="bottom_tool_wrap">
<el-button @click="cancel">取消</el-button>
<!-- <el-button type="primary" @click="save">保存</el-button> !-->
</div>
<Drawer :drawerInfo="drawerInfo" @drawerBtnClick="drawerBtnClick" />
</div>
</template>
<style lang="scss" scoped>
.container_wrap {
padding: 0px;
}
.content_main {
height: calc(100% - 44px);
padding: 10px 16px;
}
.bottom_tool_wrap {
height: 44px;
padding: 0 16px;
border-top: 1px solid #d9d9d9;
display: flex;
justify-content: center;
align-items: center;
}
:deep(.detail-content) {
.el-card__body {
height: calc(100% - 50px) !important;
.card-body-content {
height: 100%;
}
}
}
.tools_btns {
position: relative;
margin-bottom: 16px;
.show-change-btn {
position: absolute;
right: 0px;
}
}
.shape-main {
height: calc(100% - 58px);
}
.table_panel {
height: calc(100% - 58px) !important;
}
</style>
\ No newline at end of file
<route lang="yaml">
name: taskConfig //分类分级任务
</route>
<script lang="ts" setup name="taskConfig">
import { ref ,onMounted} from "vue";
</script>
<template>
<div>分类分级任务</div>
</template>
<style lang="scss" scoped>
</style>
\ No newline at end of file
<route lang="yaml">
name: taskConfig //分类分级模板
</route>
<script lang="ts" setup name="templateConfig">
import { ref, onMounted } from "vue";
import TableTools from '@/components/Tools/table_tools.vue';
import { MoreFilled } from "@element-plus/icons-vue";
import { commonPageConfig } from '@/components/PageNav/index';
import { useValidator } from '@/hooks/useValidator';
const router = useRouter();
const { required } = useValidator();
const { proxy } = getCurrentInstance() as any;
const tabsInfo = ref({
activeName: 'classStandard',
tabs: [
{ label: '分类分级模板', name: 'classTemplate' },
{ label: '分类标准', name: 'classStandard' },
{ label: '分级标准', name: 'gradeStandard' }
]
});
const tabChange = (val) => {
tabsInfo.value.activeName = val;
}
/** 模板的搜索配置 */
const searchItemList = ref([
{
type: 'input',
label: '',
field: 'templateName',
default: '',
maxlength: 50,
placeholder: '模板名称',
clearable: true,
visible: true
}
]);
/** 搜索查询分类分级模板。 */
const searchTemplate = (val: any, clear: boolean = false) => {
// page.value.curr = 1;
// if (clear) {
// searchItemList.value.map(item => item.default = '')
// page.value.planName = '';
// page.value.state = null;
// getTableData();
// return;
// }
// page.value.planName = val.planName;
// page.value.state = val.state;
// getTableData();
};
/** ------------------------------- 分类标准相关 ------------------------------------- */
/** 分类标准的搜索配置 */
const classSearchItemList = ref([
{
type: 'input',
label: '',
field: 'classStandardName',
default: '',
maxlength: 50,
placeholder: '分类标准名称',
clearable: true,
visible: true
}
]);
const classPage: any = ref({
...commonPageConfig,
classStandardName: ''
});
const classListDataLoading = ref(false);
const classListData: any = ref([{
guid: '1',
name: '工业数据分类',
updateTime: '2020-12-12 10:10:10'
}, {
guid: '2'
}, {
guid: '3'
}, {
guid: '4'
}, {
guid: '5'
}, {
guid: '6'
}, {
guid: '7'
}]);
/** 记录点击省略号弹出菜单的visible */
const cardBtnVisible: any = ref(false);
/** 搜索查询分类标准 */
const searchClass = (val: any, clear: boolean = false) => {
classPage.value.curr = 1;
if (clear) {
classSearchItemList.value.map(item => item.default = '')
// classPage.value.planName = '';
getClassListData();
return;
}
// classPage.value.planName = '';
getClassListData();
};
const newCreateClass = () => {
newCreateClassStandardDialogInfo.value.visible = true;
classStandardFormItems.value.forEach(item => item.default = '');
}
const getClassListData = () => {
// classListDataLoading.value = true;
// getPlanList({
// pageIndex: classPage.value.curr,
// pageSize: classPage.value.limit,
// //TODO
// }).then((res: any) => {
// classListDataLoading.value = false;
// if (res === undefined) {
// return;
// }
// if (res.code == proxy.$passCode) {
// const data = res.data || {}
// classListData.value.data = data.records || [];
// } else {
// proxy.$ElMessage.error(res.msg);
// }
// })
}
/** 编辑分类 */
const handleClassDataEdit = (item) => {
}
const handleClassDataClick = (item) => {
router.push({
name: 'classStandardEdit',
query: {
guid: item.guid,
classStandardName: '工业分类'
}
});
}
const handleClassDataDel = (item) => {
}
const classStandardFormItems = ref([{
label: '分类名称',
type: 'input',
maxlength: 50,
placeholder: '请输入',
field: 'classStandardName',
default: '',
block: true,
clearable: true,
required: true
}, {
label: '分级标准',
type: 'select',
placeholder: '请选择',
field: 'gradeStandard',
default: 1,
options: [], //TODO
required: true,
filterable: true,
clearable: true,
visible: true,
block: true,
}]);
const classStandardFormRules = ref({
classStandardName: [required('请填写分类名称')],
gradeStandard: [required('请选择分级标准')]
});
const newCreateClassStandardDialogInfo = ref({
visible: false,
size: 460,
title: "新增分类",
type: "",
formInfo: {
id: "class-standard-form",
items: classStandardFormItems.value,
rules: classStandardFormRules.value,
},
submitBtnLoading: false,
btns: {
cancel: () => {
newCreateClassStandardDialogInfo.value.visible = false;
},
submit: (btn, info) => {
newCreateClassStandardDialogInfo.value.visible = false;
classPage.value.curr = 1;
getClassListData();
//跳转到编辑页面
router.push({
name: 'classStandardEdit',
query: {
guid: '1',
classStandardName: '工业分类'
}
});
}
}
})
const newCreateTemplate = () => {
}
const newCreateGrade = () => {
}
onBeforeMount(() => {
getClassListData();
})
</script>
<template>
<div class="container">
<Tabs :tabs-info="tabsInfo" @tab-change="tabChange" />
<div class="panel" v-show="tabsInfo.activeName == 'classTemplate'">
<div class="table_tool_wrap">
<TableTools :searchItems="searchItemList" :searchId="'template-manage-search'" @search="searchTemplate" />
<div class="tools_btns">
<el-button type="primary" @click="newCreateTemplate">新增模板</el-button>
</div>
</div>
</div>
<div class="panel" v-show="tabsInfo.activeName == 'classStandard'">
<div class="table_tool_wrap">
<TableTools :searchItems="classSearchItemList" :searchId="'template-manage-search'" @search="searchClass" />
<div class="tools_btns">
<el-button type="primary" @click="newCreateClass">新增分类</el-button>
</div>
</div>
<div class="data-content" v-loading="classListDataLoading">
<div class="card-content" v-for="item in classListData" :key="item.guid" @click="handleClassDataClick(item)">
<div class="type-btn">
<el-popover v-model="cardBtnVisible" placement="bottom" width="96" trigger="click"
popper-class="tree-item-edit-menu" :show-arrow="false" :hide-after="0">
<template #reference>
<el-icon color="#666" @click.stop="cardBtnVisible = true">
<MoreFilled />
</el-icon>
</template>
<div class="levitation-ul">
<span class="levitation-li" @click="handleClassDataClick(item)">配置</span>
<span class="levitation-li" @click="handleClassDataEdit(item)">编辑</span>
<span class="levitation-li" @click="handleClassDataDel(item)">删除</span>
</div>
</el-popover>
</div>
<el-icon class="title-icon">
<svg-icon name="folder" />
</el-icon>
<div class="title">{{ item.name }}</div>
<div class="desc-row">
<div class="desc">{{ '分级标准' }}</div>
<div class="desc">{{ item.updateTime }}</div>
</div>
</div>
<div v-if="!classListData.length" class="card-noData">
<img src="../../assets/images/no-data.png" :style="{ width: '96px', height: '96px' }" />
<span>暂无分类标准</span>
</div>
</div>
</div>
<div class="panel" v-show="tabsInfo.activeName == 'gradeStandard'">
<el-button type="primary" @click="newCreateGrade">新增分级</el-button>
</div>
<Dialog_form :dialogConfigInfo="newCreateClassStandardDialogInfo" />
</div>
</template>
<style lang="scss" scoped>
.container {
height: 100%;
padding: 12px 16px 20px 16px;
.panel {
height: calc(100% - 51px);
}
}
:deep(.table-tools) {
.tools_search {
padding: 0px;
}
}
.tools_btns {
padding: 8px 0px 16px;
}
.data-content {
height: calc(100% - 92px);
display: flex;
flex-wrap: wrap;
gap: 24px;
align-content: flex-start;
overflow-y: auto;
.card-noData {
height: 100%;
width: 100%;
background: #fafafa;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
color: #909399;
font-size: 14px;
}
.card-content {
width: 18%;
max-width: 240px;
min-width: 220px;
height: 160px;
padding: 12px;
border: 1px solid var(--el-border-color-regular);
cursor: pointer;
display: flex;
flex-direction: column;
align-items: center;
position: relative;
&:hover {
border: 1px solid var(--el-color-primary);
}
:deep(.title-icon.el-icon) {
width: 40px;
height: 40px;
margin-top: 28px;
margin-bottom: 16px;
svg {
width: 40px;
height: 40px;
}
}
.title {
font-size: 14px;
color: #212121;
text-align: center;
line-height: 21px;
font-weight: 600;
margin-bottom: 12px;
}
.desc-row {
width: 100%;
display: flex;
justify-content: space-between;
.desc {
font-size: 12px;
color: #999999;
line-height: 18px;
}
}
.type-btn {
position: absolute;
right: 12px;
}
}
}
</style>
\ No newline at end of file
Styling with Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!