3882c63a by lihua

标签管理和匿名化管理

1 parent d1e89582
......@@ -50,6 +50,9 @@ VITE_APP_QUALITY_BASEURL = ms-daop-data-quality-service
# VITE_APP_CHECK_BASEURL = /mock
VITE_APP_CHECK_BASEURL = ms-daop-zcgl-data-inventory
#匿名化管理接口
VITE_APP_ANONYMIZATION_BASEURL = ms-daop-anonymization-service
#数据源接口地址
VITE_APP_DATA_SOURCE_URL = ms-daop-data-source-service
......
......@@ -82,6 +82,9 @@ VITE_APP_QUALITY_BASEURL = ms-daop-data-quality-service
# VITE_APP_CHECK_BASEURL = /mock
VITE_APP_CHECK_BASEURL = ms-daop-zcgl-data-inventory
#匿名化管理接口
VITE_APP_ANONYMIZATION_BASEURL = ms-daop-anonymization-service
# 数据字典接口地址
VITE_APP_CONFIG_URL = 'ms-daop-configure-service'
......
/**
* 匿名化管理的接口api文件
*/
import request from "@/utils/request";
/** 获取标签列表。 */
export const getDataLabelList = (params) => request({
url: `${import.meta.env.VITE_APP_ANONYMIZATION_BASEURL}/label/page-list`,
method: 'post',
data: params
})
/** 修改标签启用禁用状态 */
export const updateLabelState = (params) => request({
url: `${import.meta.env.VITE_APP_ANONYMIZATION_BASEURL}/label/update-state`,
method: 'put',
params
})
export const saveLabel = (data) => request({
url: `${import.meta.env.VITE_APP_ANONYMIZATION_BASEURL}/label/save`,
method: 'post',
data
})
export const deleteLabel = (data) => request({
url: `${import.meta.env.VITE_APP_ANONYMIZATION_BASEURL}/label/delete`,
method: 'delete',
data
})
export const updateLabel = (data) => request({
url: `${import.meta.env.VITE_APP_ANONYMIZATION_BASEURL}/label/update`,
method: 'put',
data
})
/** 获取标签详情 */
export const getLabelDetail = (guid) => request({
url: `${import.meta.env.VITE_APP_ANONYMIZATION_BASEURL}/label/detail?guid=${guid}`,
method: 'get'
})
/** 获取数据字典配置 */
export const getParamsList = (params) => request({
url: `${import.meta.env.VITE_APP_CONFIG_URL}/dict/data/get-by-dictType`,
method: 'get',
params
})
/** 字段类型 */
export const fieldTypeList = [{
value: '1',
label: '日期'
}, {
value: '2',
label: '字符串'
}, {
value: '3',
label: '数值'
}]
/** 获取泛化文件列表 */
export const getGeneralizeFileList = (params) => request({
url: `${import.meta.env.VITE_APP_ANONYMIZATION_BASEURL}/generalize-file/page-list`,
method: 'post',
data: params
})
export const saveGeneralizeFile = (data) => request({
url: `${import.meta.env.VITE_APP_ANONYMIZATION_BASEURL}/generalize-file/save`,
method: 'post',
data
})
/** 删除泛化文件 */
export const deleteGeneralizeFile = (data) => request({
url: `${import.meta.env.VITE_APP_ANONYMIZATION_BASEURL}/generalize-file/delete`,
method: 'delete',
data
})
/** 获取泛化文件详情 */
export const getGeneralizeFileDetail = (guid) => request({
url: `${import.meta.env.VITE_APP_ANONYMIZATION_BASEURL}/generalize-file/detail?guid=${guid}`,
method: 'get'
})
export const updateGeneralizeFile = (data) => request({
url: `${import.meta.env.VITE_APP_ANONYMIZATION_BASEURL}/generalize-file/update`,
method: 'put',
data
})
/** --------- 敏感数据识别接口 ------------------- */
/** 获取敏感数据识别任务列表 */
export const getSensitiveDataTaskList = (params) => request({
url: `${import.meta.env.VITE_APP_ANONYMIZATION_BASEURL}/sensitive-data-task/page-list`,
method: 'post',
data: params
})
/** 新增敏感数据识别任务 */
export const saveSensitiveDataTask = (params) => request({
url: `${import.meta.env.VITE_APP_ANONYMIZATION_BASEURL}/sensitive-data-task/save`,
method: 'post',
data: params
})
/** 编辑修改敏感数据识别任务 */
export const updateSensitiveDataTask = (params) => request({
url: `${import.meta.env.VITE_APP_ANONYMIZATION_BASEURL}/sensitive-data-task/update`,
method: 'post',
params
})
/** 删除敏感数据识别任务 */
export const deleteSensitiveDataTask = (data) => request({
url: `${import.meta.env.VITE_APP_ANONYMIZATION_BASEURL}/sensitive-data-task/delete`,
method: 'delete',
data
})
/** 数据来源类型 */
//export const
\ No newline at end of file
......@@ -11,6 +11,8 @@ import {
MoreFilled,
QuestionFilled,
WarningFilled,
CircleCheckFilled,
CircleCloseFilled,
} from "@element-plus/icons-vue";
// import Table from "../Table/index.vue";
import Tabs from "../Tabs/index.vue";
......@@ -1504,7 +1506,8 @@ const panelChange = (scope, row) => {
:autocomplete="item.autocompleteSetting?.autocomplete || item.autocomplete
" :readonly="item.autocompleteSetting?.readonly || item.readonly" @click="handlerIptClick(item)" />
<div class="input_def" v-else>
<el-input :class="[item.col, { is_block: item.block }]" v-model.trim="formInline[item.field]"
<span v-if="item.beforeMsg" style="color: #212121;">{{ item.beforeMsg }}</span>
<el-input :class="[item.col, { is_block: item.block }]" v-model.trim="formInline[item.field]" :style="item.width ? { width: item.width } : null"
:placeholder="item.placeholder" :type="item.inputType ?? 'text'" :clearable="item.clearable"
:disabled="item.disabled || readonly" :min="item.min" :max="item.max" :maxlength="item.maxlength ?? ''"
:autocomplete="item.autocompleteSetting?.autocomplete || item.autocomplete
......@@ -1520,25 +1523,13 @@ const panelChange = (scope, row) => {
<QuestionFilled />
</el-icon>
</el-tooltip>
<!-- 输入框右上角显示验证按钮 -->
<span v-if="item.validateBtn" style="position: absolute;right: 0;top: 0px" class="text_btn" @click="item.validateBtn.click">{{ item.validateBtn.label }}</span>
<div v-if="item.validateMsg?.msg" class="validate-input-after-msg" :style="{ color: item.validateMsg.status == 'success' ? '#4FA55D' : '#E63E33', right: item.validateMsg.status == 'success' ? '-106px' : '-120px' }"><el-icon class="title-icon"><CircleCloseFilled v-if="item.validateMsg.status != 'success'" />
<CircleCheckFilled v-else /></el-icon><span class="title_text">{{ item.validateMsg?.msg }}</span></div>
<span v-if="item.afterMsg" style="color: #212121">{{ item.afterMsg }}</span>
</div>
</el-form-item>
<!-- <el-form-item v-if="item.type === 'formAndSelect'" class="form-and-select" :prop="item.field"
style="width: 100%;display: flex;">
<div v-for="(child, index) in item.children" :key="index" style="margin-right: 8px;"
@mouseenter="handleMouseEnter(index, item.children)" @mouseleave="handleMouseLeave(index)">
<template v-if="child.type === 'select'">
<el-select v-model="formInline[child.field]" :options="child.options" :props="child.props"
:placeholder="child.placeholder" :filterable="child.filterable" :clearable="child.clearable"
class="main-select" style="width: 255px;" />
</template>
<template v-if="child.type === 'input'">
<el-input v-model="formInline[child.field]" :placeholder="child.placeholder" :maxlength="child.maxlength"
:clearable="child.clearable" style="width: 255px;" />
</template>
<el-button v-if="child.showDeleteButton && extraIcon" class="extra-icon" :icon="extraIcon.icon"
@click="extraIcon.click" circle style="margin-left: 8px;" />
</div>
</el-form-item> -->
</template>
<!-- 默认插槽内容 -->
......@@ -2505,4 +2496,21 @@ const panelChange = (scope, row) => {
flex-wrap: nowrap !important;
}
}
.validate-input-after-msg {
display: flex;
align-items: center;
position: absolute;
top: 26px;
right: -120px;
:deep(.el-icon) {
width: 16px;
height: 16px;
svg {
width: 16px;
height: 16px;
}
}
}
</style>
......
......@@ -148,9 +148,38 @@ export const useValidator = () => {
}
}
/** 判断输入的是否是正则表达式的规则 */
const regexpRuleValidate = () => {
return {
validator: (rule: any, value: any, callback: any) => {
function isValidRegex(pattern) {
try {
new RegExp(pattern);
return true;
} catch (e) {
return false;
}
}
if (!value) {
callback(new Error("输入正则表达式"));
return;
}
if (isValidRegex(value)) {
callback();
return;
} else {
callback(new Error("正则表达式语法不正确"));
return;
}
},
trigger: "blur",
};
}
return {
required,
regexpValidate,
regexpRuleValidate,
orderNum,
description,
chOrEnPreffix,
......
......@@ -55,7 +55,7 @@ onMounted(() => {
<div class="v-uerinfo">
<div class="v-top">{{ userStore.userName }}</div>
<div class="v-top"> {{ JSON.parse(loaclStorageInfo).abbreviation }}</div>
<div class="v-top"> {{ JSON.parse(loaclStorageInfo)?.abbreviation }}</div>
</div>
<el-avatar size="small">
<el-icon>
......
import type { RouteRecordRaw } from 'vue-router'
function Layout() {
return import('@/layouts/index.vue')
}
const routes: RouteRecordRaw[] = [
{
path: '/data-anonymization/label-management',
component: Layout,
meta: {
title: '标签管理',
icon: 'sidebar-videos',
},
children: [
{
path: '',
name: 'labelManagement',
component: () => import('@/views/data_anonymization/labelManagement.vue'),
meta: {
title: '标签管理',
sidebar: false,
breadcrumb: false,
cache: true
},
}
],
},
{
path: '/data-anonymization/generalize-file',
component: Layout,
meta: {
title: '泛化文件管理',
icon: 'sidebar-videos',
},
children: [
{
path: '',
name: 'generalizeFile',
component: () => import('@/views/data_anonymization/generalizeFile.vue'),
meta: {
title: '泛化文件管理',
sidebar: false,
breadcrumb: false,
cache: true
},
},
{
path: 'generalize-file-edit',
name: 'generalizeFileEdit',
component: () => import('@/views/data_anonymization/generalizeFileEdit.vue'),
meta: {
title: '新建泛化文件',
sidebar: false,
breadcrumb: false,
cache: true,
reuse: true,
editPage: true,
activeMenu: '/data-anonymization/generalize-file'
},
beforeEnter: (to, from) => {
if (to.query.fileName) {
to.meta.title = `编辑-${to.query.fileName}`;
}
}
},
],
},
{
path: '/data-anonymization/sensitive-identify',
component: Layout,
meta: {
title: '敏感数据识别',
icon: 'sidebar-videos',
},
children: [
{
path: '',
name: 'sensitiveIdentify',
component: () => import('@/views/data_anonymization/sensitiveIdentify.vue'),
meta: {
title: '敏感数据识别',
sidebar: false,
breadcrumb: false,
cache: true
},
},
],
},
{
path: '/data-anonymization/result-process',
component: Layout,
meta: {
title: '匿名化处理',
icon: 'sidebar-videos',
},
children: [
{
path: '',
name: 'resultProcess',
component: () => import('@/views/data_anonymization/resultProcess.vue'),
meta: {
title: '匿名化处理',
sidebar: false,
breadcrumb: false,
cache: true
},
},
],
},
]
export default routes
......@@ -5,6 +5,7 @@ import DataAssess from './modules/dataAsset';
import DataMeta from './modules/dataMeta';
import DataQuality from './modules/dataQuality';
import DataInventory from './modules/dataInventory';
import DataAnonymization from './modules/dataAnonymization';
import AssetIndex from './modules/assetIndex';
import DataTrustedSpace from './modules/dataTrustedSpace';
import DataAssetRegistry from './modules/dataAssetRegistry';
......@@ -113,6 +114,7 @@ const asyncRoutes: RouteRecordRaw[] = [
...DataMeta,
...DataQuality,
...DataInventory,
...DataAnonymization,
...DataTrustedSpace,
...DataPricing
]
......
const useDataAnonymizationStore = defineStore(
// 资产目录guid
"isRefresh",
() => {
const isRefresh = ref<boolean>(false);
function setIsRefresh(v: boolean) {
isRefresh.value = v;
}
return {
isRefresh,
setIsRefresh,
};
}
);
export default useDataAnonymizationStore;
<route lang="yaml">
name: generalizeFile
</route>
<script lang="ts" setup name="generalizeFile">
import TableTools from "@/components/Tools/table_tools.vue";
import { commonPageConfig } from '@/components/PageNav/index';
import { TableColumnWidth } from "@/utils/enum";
import {
deleteGeneralizeFile,
getGeneralizeFileList,
fieldTypeList,
} from '@/api/modules/dataAnonymization';
import useDataAnonymizationStore from "@/store/modules/dataAnonymization";
const anonymizationStore = useDataAnonymizationStore();
const router = useRouter()
const { proxy } = getCurrentInstance() as any;
const searchItemList = ref([{
type: "input",
label: "",
field: "generalizeFileName",
default: "",
placeholder: "泛化文件名称",
clearable: true,
}, {
type: "select",
label: "",
field: "fieldType",
default: null,
options: fieldTypeList,
placeholder: "字段类型",
clearable: true,
filterable: true,
}])
/** 分页及搜索传参信息配置。 */
const page = ref({
...commonPageConfig,
generalizeFileName: '',
fieldType: ''
});
const tableInfo = ref({
id: 'data-file-table',
fields: [
{ label: "序号", type: "index", width: TableColumnWidth.INDEX, align: "center" },
{ label: "泛化文件名称", field: "generalizeFileName", width: 160 },
{ label: "字段类型", field: "fieldType", width: 120, getName: (scope) => {
return scope.row.fieldType && fieldTypeList.find(f => f.value == scope.row.fieldType)?.label || '--';
} },
{ label: "泛化层级", field: "generalizeLevel", width: 120, align: 'right', type: 'chnum' },
{ label: "修改人", field: "updateUserName", width: TableColumnWidth.USERNAME },
{ label: "修改时间", field: "updateTime", width: TableColumnWidth.DATETIME },
],
data: [],
page: {
type: "normal",
rows: 0,
...page.value,
},
loading: false,
actionInfo: {
label: "操作",
type: "btn",
width: 120,
fixed: 'right',
btns: (scope) => {
return [{
label: "编辑", value: "edit", click: (scope) => {
router.push({
name: 'generalizeFileEdit',
query: {
guid: scope.row.guid,
fileName: scope.row.generalizeFileName
}
});
}
}, {
label: "删除", value: "delete", disabled: scope.row.bizState == 'Y', click: (scope) => {
proxy.$openMessageBox("此操作将永久删除, 是否继续?", () => {
let guids = [scope.row.guid];
deleteGeneralizeFile(guids).then((res: any) => {
if (res.code == proxy.$passCode) {
getTableData();
proxy.$ElMessage({
type: "success",
message: "删除成功",
});
} else {
proxy.$ElMessage({
type: "error",
message: res.msg,
});
}
});
})
}
}]
}
}
})
const toSearch = (val: any, clear: boolean = false) => {
if (clear) {
searchItemList.value.map((item) => (item.default = ""));
page.value.generalizeFileName = '';
page.value.fieldType = '';
} else {
page.value.generalizeFileName = val.generalizeFileName;
page.value.fieldType = val.fieldType;
}
getTableData();
};
const getTableData = () => {
tableInfo.value.loading = true
getGeneralizeFileList({
pageIndex: page.value.curr,
pageSize: page.value.limit,
generalizeFileName: page.value.generalizeFileName,
fieldType: page.value.fieldType
}).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 {
proxy.$ElMessage({
type: 'error',
message: res.msg,
})
}
tableInfo.value.loading = false
})
};
const tablePageChange = (info) => {
page.value.curr = Number(info.curr);
page.value.limit = Number(info.limit);
getTableData();
};
onBeforeMount(() => {
toSearch({});
})
onActivated(() => {
if (anonymizationStore.isRefresh) {//如果是首次加载,则不需要调用
page.value.curr = 1;
getTableData();
anonymizationStore.setIsRefresh(false);
}
})
const handleCreate = () => {
router.push({
name: 'generalizeFileEdit'
});
}
</script>
<template>
<div class="container_wrap">
<div class="table_tool_wrap">
<!-- 头部搜索 -->
<TableTools :searchItems="searchItemList" :searchId="'data-label-search'" @search="toSearch" :init="false" />
<div class="tools_btns">
<el-button type="primary" @click="handleCreate">新建</el-button>
</div>
</div>
<div class="table_panel_wrap">
<!-- 右侧标签管理表格 -->
<Table :tableInfo="tableInfo" @tablePageChange="tablePageChange" />
</div>
</div>
</template>
<style lang="scss" scoped>
.table_tool_wrap {
width: 100%;
height: 84px !important;
padding: 0 8px;
.tools_btns {
padding: 0px 0 0;
}
}
.table_panel_wrap {
width: 100%;
height: calc(100% - 84px);
padding: 0px 8px 0;
}
</style>
\ No newline at end of file
<script lang="ts" setup name="generalizeFileEdit">
import {
fieldTypeList,
saveGeneralizeFile,
getGeneralizeFileDetail,
updateGeneralizeFile,
deleteGeneralizeFile,
} from '@/api/modules/dataAnonymization';
import {
parseAndDecodeUrl,
getDownFileSignByUrl,
obsDownloadRequest
} from "@/api/modules/obsService";
import useUserStore from "@/store/modules/user";
import { useValidator } from '@/hooks/useValidator';
import * as XLSX from 'xlsx';
import { TableColumnWidth } from '@/utils/enum';
import { calcColumnWidth } from "@/utils/index";
import Moment from 'moment';
import useDataAnonymizationStore from "@/store/modules/dataAnonymization";
import { ElMessage } from 'element-plus';
const anonymizationStore = useDataAnonymizationStore();
const { required } = useValidator();
const userStore = useUserStore();
const router = useRouter();
const route = useRoute();
const { proxy } = getCurrentInstance() as any;
const fullPath = route.fullPath;
const guid = ref(route.query.guid);
const baseInfoItems = ref([{
label: '泛化文件名称',
type: 'input',
maxlength: 15,
placeholder: '请输入',
field: 'generalizeFileName',
default: '',
block: false,
clearable: true,
required: true
}, {
type: "select",
label: "字段类型",
field: "fieldType",
default: 2,
options: fieldTypeList,
placeholder: "请选择",
clearable: true,
filterable: true,
required: true,
block: false,
}, {
label: '泛化层级',
field: 'generalizeLevel',
placeholder: "请输入",
type: 'input',
inputType: 'integerNumber',
maxlength: 6,
default: null,
clearable: true,
required: true,
}]);
const baseInfoFormRules = ref({
generalizeFileName: [required('请输入泛化文件名称')],
fieldType: [required('请选择字段类型')],
generalizeLevel: [required('请输入泛化层级')]
});
/** 文件上传表单配置。 */
const fileFormItems: any = ref([
{
label: '选择文件上传',
tip: '支持扩展名:xlsx、xls、csv,文件大小不超过10MB',
type: 'upload-file',
accept: '.xlsx, .xls, .csv',
limit: 1,
limitSize: 10,
isExcel: true,
required: true,
default: [],
block: false,
field: 'file',
}
]);
const fileFormRules = ref({
file: [{
validator: (rule: any, value: any, callback: any) => {
if (!value?.length) {
callback(new Error('请上传文件'))
} else {
callback();
}
}, trigger: 'change'
}]
});
/** 上传文件之后解析出字段和数据 */
const fileFieldLoading = ref(false);
const fileTableFields: any = ref([]);
const fileTableData: any = ref([]);
const parseFileData = (fileRaw) => {
fileFieldLoading.value = true;
fileRaw.arrayBuffer().then((f) => {
const wb = XLSX.read(f, {
raw: false, cellDates: true
});
const sheet = wb.Sheets[wb.SheetNames[0]];
const json: any[] = XLSX.utils.sheet_to_json(sheet, { header: 1 });
if (json.length == 0) {
fileTableFields.value = [];
fileTableData.value = [];
} else {
fileTableFields.value = json[0]?.map((j, index) => {
return {
index: index,
enName: j + '',
chName: j + '',
dataType: 'varchar'
}
}) || [];
if (json.length > 1) {
fileTableData.value = json.slice(1, 51).map((info, row) => {
let object = {};
json[0].forEach((name, col) => {
if (info[col] === "" || info[col] == null) {
object[name] = info[col];
} else {
var cellRef = XLSX.utils.encode_cell({ r: row + 1, c: col });
var cell = sheet[cellRef];
object[name] = cell.w || info[col];
let isNum = cell.t == 'n';
if (isNum && fileTableFields.value[col].dataType != 'int') {
fileTableFields.value[col].dataType = 'int';
}
}
});
return object;
});
} else {
fileTableData.value = [];
}
}
fileFieldLoading.value = false;
});
}
const uploadFileChange = (file) => {
fileTableFields.value = [];
fileTableData.value = [];
if (!file.length) {
fileTableFields.value = [];
fileTableData.value = [];
fileFormItems.value[0].default = file;
return;
}
let fileRaw = file[0].file;
fileFormItems.value[0].default = file;
parseFileData(fileRaw);
}
/** otherWidth表示使用标题宽度时添加标题排序图标等宽度 */
const calcTableColumnWidth = (data: any[], prop, title, otherWidth = 0) => {
let d: any[] = [];
data.forEach((dt) => d.push(dt[prop]));
return calcColumnWidth(
d,
title,
{
fontSize: 14,
fontFamily: "SimSun",
},
{
fontSize: 14,
fontFamily: "SimSun",
},
otherWidth
);
};
/** 每列字段对应的列宽计算结果。 */
const originTableFieldColumn = ref({});
const getTextAlign = (field) => {
if (field.dataType === 'decimal' || field.dataType === 'int') {
return 'right';
}
return 'left'
}
watch(
fileTableData,
(val: any[], oldVal) => {
if (!fileTableFields.value?.length) {
originTableFieldColumn.value = {};
return;
}
originTableFieldColumn.value = {};
fileTableFields.value.forEach((field, index) => {
originTableFieldColumn.value[field.enName] = calcTableColumnWidth(
val?.slice(0, 20) || [],
field.enName,
field.chName,
24
);
});
},
{
deep: true,
}
);
const formatterPreviewDate = (row, info) => {
let enName = info.enName;
let v = row[enName];
if (v === 0) {
return v;
}
if (!v) {
return v || '--';
}
if (info.dataType === 'datetime') {
return Moment(v).format('YYYY-MM-DD HH:mm:ss');
}
if (info.dataType === 'date') {
if (isNaN(<any>(new Date(v)))) {
return Moment(parseInt(v)).format('YYYY-MM-DD');
} else {
return Moment(v).format('YYYY-MM-DD');
}
}
return v;
};
const cancel = () => {
proxy.$openMessageBox("当前页面尚未保存,确定放弃修改吗?", () => {
userStore.setTabbar(userStore.tabbar.filter((tab: any) => tab.fullPath !== fullPath));
router.push({
name: 'generalizeFile'
});
}, () => {
proxy.$ElMessage.info("已取消");
});
}
const formRef = ref();
/** 文件表单 */
const fileFormRef = ref();
const saveLoading = ref(false);
const saveUpdate = async () => {
formRef.value?.ruleFormRef?.validate((valid, errorItem) => {
fileFormRef.value?.ruleFormRef?.validate((fileValid, errorItem) => {
if (valid && fileValid) {
let fileJson = fileFormRef.value?.formInline.file || [];
let params = Object.assign({}, formRef.value.formInline, {
filePath: {
name: fileJson[0]?.name,
url: fileJson[0]?.url
}
})
if (!guid.value) {
saveLoading.value = true;
saveGeneralizeFile(params).then((res: any) => {
if (res.code == proxy.$passCode) {
proxy.$ElMessage.success('泛化文件新建成功');
userStore.setTabbar(userStore.tabbar.filter((tab: any) => tab.fullPath !== fullPath));
anonymizationStore.setIsRefresh(true);
router.push({
name: 'generalizeFile'
});
saveLoading.value = false;
} else {
saveLoading.value = false;
proxy.$ElMessage.error(res.msg);
}
})
} else {
params.guid = guid.value;
saveLoading.value = true;
updateGeneralizeFile(params).then((res: any) => {
if (res.code == proxy.$passCode) {
proxy.$ElMessage.success('泛化文件编辑成功');
userStore.setTabbar(userStore.tabbar.filter((tab: any) => tab.fullPath !== fullPath));
anonymizationStore.setIsRefresh(true);
router.push({
name: 'generalizeFile'
});
saveLoading.value = false;
} else {
saveLoading.value = false;
proxy.$ElMessage.error(res.msg);
}
})
}
}
});
})
}
const fullscreenLoading = ref(false);
onBeforeMount(() => {
if (guid.value) {
fullscreenLoading.value = true;
getGeneralizeFileDetail(guid.value).then(async (res: any) => {
if (res.code == proxy.$passCode) {
let detail = res.data || {};
baseInfoItems.value.forEach(item => {
item.default = detail[item.field];
});
fileFormItems.value.forEach(item => {
item.default = [{
name: detail.filePath.name,
url: detail.filePath.url
}]
});
let url = detail.filePath.url;
const refSignInfo: any = await getDownFileSignByUrl(parseAndDecodeUrl(url).fileName);
if (!refSignInfo?.data) {
fullscreenLoading.value = false;
refSignInfo?.msg && ElMessage.error(refSignInfo?.msg);
return;
}
obsDownloadRequest(refSignInfo?.data).then((res: any) => {
if (res && !res.msg) {
parseFileData(res);
fullscreenLoading.value = false;
} else {
fullscreenLoading.value = false;
res?.msg && ElMessage.error(res?.msg);
}
})
} else {
fullscreenLoading.value = false;
proxy.$ElMessage.error(res.msg);
}
});
}
})
onActivated(() => {
const fileName = route.query.fileName;
let tab: any = userStore.tabbar.find((tab: any) => tab.fullPath === fullPath);
if (tab) {
if (fileName) {
tab.meta.title = `编辑-${fileName}`;
}
if (fullPath === route.fullPath) {
document.title = tab.meta.title;
}
}
});
</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="baseInfoItems" :rules="baseInfoFormRules" formId="main-model-edit" col="col3" />
</ContentWrap>
<ContentWrap id="id-grade-info" title="上传文件" class="detail-content" description="" style="margin-top: 8px;">
<Form ref="fileFormRef" :itemList="fileFormItems" :noUpload="false" formId="file-form" :rules="fileFormRules"
@uploadFileChange="uploadFileChange" />
<div v-show="fileTableFields.length" class="preview-data-totals">
<span>该表仅显示</span>
<span class="fontC-4fa1a4"> 50 </span>
<span>条数据</span>
</div>
<el-table ref="tableRef" v-loading="fileFieldLoading" v-show="fileTableFields.length" :data="fileTableData"
:highlight-current-row="true" stripe border tooltip-effect="light" height="100%" row-key="guid"
:style="{ width: '100%', height: '280px' }">
<template v-for="(item, index) in (fileTableFields || [])">
<el-table-column :label="item.chName" :width="item.dataType === 'datetime'
? TableColumnWidth.DATETIME
: item.dataType === 'date'
? TableColumnWidth.DATE
: originTableFieldColumn[item.enName]
" :align="getTextAlign(item)" :header-align="getTextAlign(item)"
:formatter="(row) => formatterPreviewDate(row, item)" :show-overflow-tooltip="true">
</el-table-column>
</template>
</el-table>
</ContentWrap>
</div>
<div class="bottom_tool_wrap">
<el-button @click="cancel">取消</el-button>
<el-button type="primary" @click="saveUpdate" :loading="saveLoading">提交</el-button>
</div>
</div>
</template>
<style lang="scss" scoped>
.container_wrap {
padding: 0px;
}
.content_main {
height: calc(100% - 44px);
padding: 10px 16px;
overflow: auto;
.table-top-btns {
margin-bottom: 12px;
}
}
.bottom_tool_wrap {
height: 44px;
padding: 0 16px;
border-top: 1px solid #d9d9d9;
display: flex;
justify-content: center;
align-items: center;
}
.tools_btns {
position: relative;
margin-bottom: 16px;
.show-change-btn {
position: absolute;
right: 0px;
}
}
.detail-content {
position: relative;
}
.preview-data-totals {
position: absolute;
top: 150px;
right: 16px;
font-size: 14px;
color: #666666;
font-weight: 400;
.fontC-4fa1a4 {
color: var(--el-color-primary);
}
}
</style>
\ No newline at end of file
<route lang="yaml">
name: labelManagement
</route>
<script lang="ts" setup name="labelManagement">
import { commonPageConfig } from '@/components/PageNav/index';
import { TableColumnWidth } from '@/utils/enum';
import TableTools from "@/components/Tools/table_tools.vue";
import {
getDataLabelList,
updateLabelState,
saveLabel,
updateLabel,
deleteLabel,
getLabelDetail,
getParamsList,
} from '@/api/modules/dataAnonymization';
import { useValidator } from '@/hooks/useValidator';
import { Delete, CirclePlus, Warning } from "@element-plus/icons-vue";
const { required, regexpRuleValidate } = useValidator();
const { proxy } = getCurrentInstance() as any;
/** 标签类型字典列表 */
const labelTypeList: any = ref([]);
/** 内置规则字典列表 */
const builtInRuleList: any = ref([]);
const searchItemList = ref([{
type: "input",
label: "",
field: "labelName",
default: "",
placeholder: "标签名称",
clearable: true,
}])
/** 分页及搜索传参信息配置。 */
const page = ref({
...commonPageConfig,
labelName: '',
});
const currTableData: any = ref();
const tableInfo = ref({
id: 'data-label-table',
// multiple:true,
fields: [
{ label: "序号", type: "index", width: TableColumnWidth.INDEX, align: "center" },
{ label: "标签名称", field: "labelName", width: 140 },
{ label: "标签类型", field: "labelTypeName", width: 140 },
{ label: '状态', field: 'bizState', type: 'switch', activeText: '启用', inactiveText: '禁用', activeValue: 'Y', inactiveValue: 'N', switchWidth: 56, width: 100, align: 'center' },
{ label: "修改人", field: "updateUserName", width: TableColumnWidth.USERNAME },
{ label: "修改时间", field: "updateTime", width: TableColumnWidth.DATETIME },
],
data: [],
page: {
type: "normal",
rows: 0,
...page.value,
},
actionInfo: {
label: "操作",
type: "btn",
width: 120,
fixed: 'right',
btns: (scope) => {
let btnsArr: any = [];
btnsArr.push({
label: "编辑", value: "edit", click: (scope) => {
currTableData.value = scope.row;
getLabelDetail(scope.row.guid).then((res: any) => {
if (res.code == proxy.$passCode) {
const detail = res.data || {};
currTableData.value = Object.assign({}, currTableData.value, detail);
newCreateLabelFormItems.value.forEach(item => {
item.default = detail[item.field];
});
let labelRuleField = detail.labelRuleField || {};
matchChValue.value.value = labelRuleField.matchChValue;
matchChValue.value.disabled = false;
matchEnValue.value.value = labelRuleField.matchEnValue;
matchEnValue.value.disabled = false;
formRows.value = labelRuleField.vagueMatchRule || [{ matchValue: '', position: '', name: '', disabled: false }];
let labelRuleContent = detail.labelRuleContent || {};
ruleContentFormItems.value.forEach(item => {
item.default = labelRuleContent[item.field];
if (item.field == 'regularTestData') {
item.validateMsg = null;
}
});
newCreateLabelDialogInfo.value.title = '编辑标签';
newCreateLabelDialogInfo.value.visible = true;
newCreateLabelDialogInfo.value.type = 'update';
newCreateLabelDialogInfo.value.submitBtnLoading = false;
} else {
proxy.$ElMessage({
type: 'error',
message: res.msg,
})
}
});
}
})
btnsArr.push({
label: "删除", value: "delete", disabled: scope.row.bizState == 'Y', click: (scope) => {
proxy.$openMessageBox("此操作将永久删除, 是否继续?", () => {
let guids = [scope.row.guid];
deleteLabel(guids).then((res: any) => {
if (res.code == proxy.$passCode) {
getTableData();
proxy.$ElMessage({
type: "success",
message: "删除成功",
});
} else {
proxy.$ElMessage({
type: "error",
message: res.msg,
});
}
});
})
}
});
return btnsArr
},
},
loading: false
})
const toSearch = (val: any, clear: boolean = false) => {
if (clear) {
searchItemList.value.map((item) => (item.default = ""));
page.value.labelName = '';
} else {
page.value.labelName = val.labelName;
}
getTableData();
};
const getTableData = () => {
tableInfo.value.loading = true
getDataLabelList({
pageIndex: page.value.curr,
pageSize: page.value.limit,
labelName: page.value.labelName
}).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 {
proxy.$ElMessage({
type: 'error',
message: res.msg,
})
}
tableInfo.value.loading = false
})
};
const tableSwitchBeforeChange = (scope, field, callback) => {
const msg = `确定【${scope.row[field] == 'Y' ? '禁用' : '启用'}${scope.row.labelName}?`
proxy.$openMessageBox(msg, () => {
const state = scope.row[field] == 'Y' ? 'N' : 'Y'
const result = tableSwitchChange(state, scope, field)
callback(result)
}, () => {
callback(false)
});
}
const tableSwitchChange = (val, scope, field) => {
return new Promise((resolve, reject) => {
let params = {
guid: scope.row.guid,
bizState: val
}
updateLabelState(params).then((res: any) => {
if (res.code == proxy.$passCode && res.data) {
getTableData();
proxy.$ElMessage({
type: "success",
message: `${scope.row.labelName}${val == 'Y' ? '启用' : '禁用'}】成功`,
});
resolve(true)
} else {
proxy.$ElMessage({
type: "error",
message: res.msg,
});
reject(false)
}
}).catch(() => {
reject(false)
})
})
}
const tablePageChange = (info) => {
page.value.curr = Number(info.curr);
page.value.limit = Number(info.limit);
getTableData();
};
const newCreateLabelFormItems = ref<any>([{
label: '标签名称',
type: 'input',
placeholder: '请选择',
field: 'labelName',
default: '',
required: true,
filterable: true,
clearable: true,
visible: true,
},
{
label: '标签类型',
type: 'select',
placeholder: '请选择',
field: 'labelTypeCode',
default: '',
options: labelTypeList.value,
props: {
label: "label",
value: "value",
},
required: true,
filterable: true,
clearable: true,
visible: true,
}, {
type: 'radio-group',
label: '规则配置',
field: 'matchType',
default: 1,
required: false,
block: true,
options: [
{ label: '字段识别和内容识别满足其一即可', value: 1 },
{ label: '字段识别和内容识别需满足全部', value: 2 },
],
}]);
const newCreateLabelFormRules = ref({
labelName: [required('请输入标签名称')],
labelTypeCode: [required('请选择标签类型')],
});
const ruleContentFormRef = ref();
const newCreateLabelDialogInfo = ref({
visible: false,
size: 600,
title: "添加标签",
type: "",
formInfo: {
id: "label-form",
items: newCreateLabelFormItems.value,
rules: newCreateLabelFormRules.value,
},
submitBtnLoading: false,
btns: {
cancel: () => {
newCreateLabelDialogInfo.value.visible = false;
newCreateLabelDialogInfo.value.submitBtnLoading = false;
},
submit: (btn, info) => {
// 需要验证两个
let validateRuleField = () => {
if (!matchChValue.value.value && !matchEnValue.value.value && (!formRows.value.length || (formRows.value.length == 1 && formRows.value[0].matchValue &&
!formRows.value[0].position && !formRows.value[0].name))) {
proxy.$ElMessage.error('字段识别匹配规则不能为空');
return false;
}
for (let i = 0; i < formRows.value.length; i++) {
const row = formRows.value[i];
// 如果某一条数据的 matchValue, position, name 都为空,则跳过,不校验
if (!row.matchValue && !row.position && !row.name) {
continue; // 如果全为空,跳过这一行的校验
}
if (!row.matchValue || !row.position || !row.name) {
proxy.$ElMessage.error('请填写完整的模糊匹配规则');
return false;
}
}
return true;
}
let submitLabel = () => {
let params = Object.assign({}, info, {
bizState: 'Y',
labelRuleField: {
matchChValue: matchChValue.value.value,
matchEnValue: matchEnValue.value.value,
vagueMatchRule: formRows.value.filter(f => f.matchValue != '')
},
labelRuleContent: ruleContentFormRef.value.formInline
});
newCreateLabelDialogInfo.value.submitBtnLoading = true;
if (newCreateLabelDialogInfo.value.type == 'add') {
saveLabel(params).then((res: any) => {
if (res.code == proxy.$passCode) {
proxy.$ElMessage.success('标签新建成功');
newCreateLabelDialogInfo.value.visible = false;
newCreateLabelDialogInfo.value.submitBtnLoading = false;
page.value.curr = 1;
getTableData();
} else {
newCreateLabelDialogInfo.value.submitBtnLoading = false;
proxy.$ElMessage.error(res.msg);
}
});
} else {
newCreateLabelDialogInfo.value.submitBtnLoading = true;
params.guid = currTableData.value.guid;
params.labelRuleContent.guid = currTableData.value.labelRuleContent?.guid;
params.labelRuleField.guid = currTableData.value.labelRuleField?.guid;
updateLabel(params).then((res: any) => {
if (res.code == proxy.$passCode) {
proxy.$ElMessage.success('标签编辑成功');
newCreateLabelDialogInfo.value.visible = false;
newCreateLabelDialogInfo.value.submitBtnLoading = false;
getTableData();
} else {
newCreateLabelDialogInfo.value.submitBtnLoading = false;
proxy.$ElMessage.error(res.msg);
}
});
}
}
if (info.matchType == 2) {
if (tabsInfo.value.activeName == 'labelRuleField') {
if (!validateRuleField()) {
return;
}
ruleContentFormRef.value?.ruleFormRef?.validate((valid, errorItem) => {
if (valid) {
submitLabel();
} else {
tabsInfo.value.activeName == 'labelRuleContent';
}
})
} else {
ruleContentFormRef.value?.ruleFormRef?.validate((valid, errorItem) => {
if (valid) {
if (!validateRuleField()) {
tabsInfo.value.activeName == 'labelRuleField';
return;
}
submitLabel();
}
})
}
} else { //只需匹配一项即可。
if (!matchChValue.value.value && !matchEnValue.value.value && (!formRows.value.length || (formRows.value.length == 1 && formRows.value[0].matchValue &&
!formRows.value[0].position && !formRows.value[0].name))) { // 没有配置字段识别内容
ruleContentFormRef.value?.ruleFormRef?.validate((valid, errorItem) => {
if (valid) {
submitLabel();
} else {
proxy.$ElMessage.error('字段识别和内容识别不能同时为空');
return;
}
})
} else {
submitLabel();
}
}
}
}
});
const formRows = ref([
{ matchValue: '', position: '', name: '', disabled: false }, // 初始行
]);
// 位置选项
const positionOptions = [
{ label: '前面', value: 'B' },
{ label: '后面', value: 'A' },
{ label: '任意位置', value: 'C' },
];
// 语言options
const languageOptions = [
{ label: '中文名', value: 'chName' },
{ label: '英文名', value: 'enName' },
];
// 新增行
const addRow = () => {
formRows.value.push({ matchValue: '', position: '', name: '', disabled: false });
};
// 删除行
const deleteRow = (index: number) => {
formRows.value.splice(index, 1);
};
const matchChValue = ref({
value: '',
disabled: false
})
const matchEnValue = ref({
value: '',
disabled: false
})
const tabsInfo = ref({
activeName: 'labelRuleField',
tabs: [
{ label: '字段识别', name: 'labelRuleField' },
{ label: '内容识别', name: 'labelRuleContent' },
]
});
const tabChange = (val) => {
tabsInfo.value.activeName = val;
}
const handleCreate = () => {
newCreateLabelFormItems.value.forEach(item => {
if (item.field == 'matchType') {
item.default = 1;
} else {
item.default = '';
}
});
matchChValue.value.value = '';
matchChValue.value.disabled = false;
matchEnValue.value.value = '';
matchEnValue.value.disabled = false;
formRows.value = [{ matchValue: '', position: '', name: '', disabled: false }];
ruleContentFormItems.value.forEach(item => {
item.default = item.field == 'ruleType' ? 1 : '';
if (item.field == 'regularTestData') {
item.validateMsg = null;
}
});
newCreateLabelDialogInfo.value.title = '添加标签';
newCreateLabelDialogInfo.value.visible = true;
newCreateLabelDialogInfo.value.type = 'add';
newCreateLabelDialogInfo.value.submitBtnLoading = false;
}
/** 规则配置的内容识别内容表单配置 */
const ruleContentFormItems = ref([{
label: '规则类型',
type: 'select',
placeholder: '请选择',
field: 'ruleType',
default: 1,
options: [{
value: 1,
label: '内置规则'
}, {
value: 2,
label: '自定义规则'
}],
props: {
label: "label",
value: "value",
},
required: true,
filterable: true,
clearable: false,
block: true,
visible: true,
}, {
label: '内置规则',
type: 'select',
placeholder: '请选择',
field: 'builtInRuleCode',
default: '',
options: builtInRuleList.value,
props: {
label: "label",
value: "value",
},
required: true,
filterable: true,
clearable: true,
block: true,
visible: true,
}, {
label: '规则表达式',
type: 'input',
placeholder: '请输入正则表达式',
field: 'regularExpression',
default: '',
required: true,
filterable: true,
clearable: true,
block: true,
visible: false,
}, {
label: '测试验证',
type: 'input',
placeholder: '请输入测试数据',
field: 'regularTestData',
default: '',
required: false,
clearable: true,
block: false,
visible: true,
validateBtn: {
value: 'validate',
label: '验证',
click: () => {
let info = ruleContentFormRef.value.formInline;
if (info.ruleType == 1 && !info.builtInRuleCode) {
proxy.$ElMessage.error('请先选择内置规则');
return;
}
if (info.ruleType == 2 && !info.regularExpression) {
proxy.$ElMessage.error('请先输入规则表达式');
return;
}
if (!info.regularTestData) {
proxy.$ElMessage.error('请先输入测试数据');
return;
}
let exp = info.ruleType == 1 ? builtInRuleList.value.find(b => b.value == info.builtInRuleCode)?.remarks : info.regularExpression;
let result = exp && new RegExp(exp).test(info.regularTestData);
ruleContentFormItems.value.forEach(item => {
item.default = info[item.field];
if (item.field == 'regularTestData') {
item.validateMsg = result ? {
status: 'success',
msg: '符合识别规则'
} : {
status: 'error',
msg: '不符合识别规则'
}
}
});
}
},
validateMsg: <any>null
}, {
label: '命中率设置(%)',
type: 'input',
placeholder: '请输入0~100',
field: 'hitRate',
min: 1,
max: 100,
inputType: 'integerNumber',
default: null,
required: true,
block: true,
clearable: true,
visible: true,
width: '110px',
beforeMsg: '一列数据中的非空数据,大于等于',
afterMsg: ' %的数据符合以上识别条件,则认为命中该识别规则。'
}]);
/** 规则配置的内容识别内容表单规则配置 */
const ruleContentFormRules = ref({
builtInRuleCode: [required('请选择内置规则')],
regularExpression: [required('请输入正则表达式'), regexpRuleValidate()],
hitRate: [required(' ')]
});
const handleRuleContentSelectChange = (val, row, info) => {
if (row.field == 'ruleType') {
ruleContentFormItems.value[1].visible = val == 1;
ruleContentFormItems.value[2].visible = val == 2;
ruleContentFormItems.value.forEach(item => {
item.default = info[item.field];
})
}
}
const handleRuleContentInputChange = (val, row) => {
if (row.field == 'regularTestData') {
let item = ruleContentFormItems.value.at(-2);
item && (item.validateMsg = null);
}
}
onBeforeMount(() => {
toSearch({});
getParamsList({
dictType: "标签类型",
}).then((res: any) => {
if (res.code == proxy.$passCode) {
labelTypeList.value = res.data || [];
let item = newCreateLabelFormItems.value.find(item => item.field == 'labelTypeCode');
item && (item.options = labelTypeList.value);
} else {
proxy.$ElMessage.error(res.msg);
}
});
getParamsList({
dictType: "内置规则",
}).then((res: any) => {
if (res.code == proxy.$passCode) {
builtInRuleList.value = res.data || [];
ruleContentFormItems.value[1].options = builtInRuleList.value;
} else {
proxy.$ElMessage.error(res.msg);
}
})
})
</script>
<template>
<div class="container_wrap">
<div class="table_tool_wrap">
<!-- 头部搜索 -->
<TableTools :searchItems="searchItemList" :searchId="'data-label-search'" @search="toSearch" :init="false" />
<div class="tools_btns">
<el-button type="primary" @click="handleCreate">新建</el-button>
</div>
</div>
<div class="table_panel_wrap">
<!-- 右侧标签管理表格 -->
<Table :tableInfo="tableInfo" @tablePageChange="tablePageChange"
@tableSwitchBeforeChange="tableSwitchBeforeChange" />
</div>
<!-- 新建编辑标签对话框 -->
<Dialog_form ref="dialogLabelFormRef" :dialogConfigInfo="newCreateLabelDialogInfo" class="v-dialog-form">
<template v-slot:default>
<Tabs :tabs-info="tabsInfo" @tab-change="tabChange" style="margin-top: -20px;" />
<div v-show="tabsInfo.activeName == 'labelRuleField'">
<div>
<div class="dim-label">
<span class="front">精确匹配</span>
<el-icon>
<Warning />
</el-icon>
<span class="tip">
精确匹配使用英文","分隔每个规则
</span>
</div>
<div class="v-match">
<el-input v-model="matchChValue.value" maxlength="200"
style="width: 272px;height:94px;" show-word-limit type="textarea" class="no-resize"
placeholder="请输入字段中文" />
<el-input v-model="matchEnValue.value" maxlength="200"
style="width: 272px;height:94px;" show-word-limit type="textarea" class="no-resize"
placeholder="请输入字段英文" />
</div>
</div>
<div class="dim-label" style="margin-top: 16px;">
<span class="front">模糊匹配</span>
<el-icon>
<Warning />
</el-icon>
<span class="tip">
模糊匹配是或的关系,可配置多个模糊匹配规则
</span>
</div>
<!-- 渲染行 -->
<div v-for="(row, index) in formRows" :key="index" class="match-content-wrapper">
<div class="match-content">
<!-- 位置映射下拉框 -->
<el-select v-model="row.name" placeholder="请选择" class="v-select">
<el-option v-for="option in languageOptions" :key="option.value" :label="option.label"
:value="option.value" />
</el-select>
<el-select v-model="row.position" placeholder="请选择位置" class="v-select">
<el-option v-for="option in positionOptions" :key="option.value" :label="option.label"
:value="option.value" />
</el-select>
<el-input v-model="row.matchValue" class="v-input" placeholder="请输入匹配值" />
<!-- 删除按钮 -->
<el-button class="extra-icon" :icon="Delete" @click="deleteRow(index)" circle
style="margin-left: 8px;" />
</div>
</div>
<!-- 新增按钮 -->
<div class="add-Icon" @click="addRow">
<el-icon class="icon-add" color="#4fa1a4" :size="30">
<CirclePlus />
</el-icon>
<span class="word-des">模糊匹配规则</span>
</div>
</div>
<div v-show="tabsInfo.activeName == 'labelRuleContent'">
<Form ref="ruleContentFormRef" :itemList="ruleContentFormItems" formId="rule-content-form"
:rules="ruleContentFormRules" @select-change="handleRuleContentSelectChange"
@inputChange="handleRuleContentInputChange" />
</div>
</template>
</Dialog_form>
</div>
</template>
<style scoped lang="scss">
.table_tool_wrap {
width: 100%;
height: 84px !important;
padding: 0 8px;
.tools_btns {
padding: 0px 0 0;
}
}
.table_panel_wrap {
width: 100%;
height: calc(100% - 84px);
padding: 0px 8px 0;
}
:deep(.v-dialog-form) {
.title-label {
font-size: 16px;
color: #212121;
line-height: 24px;
font-weight: 600;
}
.el-dialog__body {
height: 480px;
overflow: auto;
}
.dim-label {
height: 10px;
display: flex;
align-items: center;
margin-top: 6px;
.el-icon svg {
height: 16px;
width: 16px;
color: #999999;
}
.front {
margin-right: 16px;
}
.tip {
margin-left: 4px;
font-size: 12px;
color: #999999;
}
}
.match-content-wrapper {
width: 100%;
.match-content {
display: flex;
align-items: center;
margin-top: 8px;
.v-select {
margin-right: 8px;
width: 33%;
}
.v-input {
width: calc(33% - 50px);
}
.extra-icon {
transition: opacity 1s;
display: none;
}
&:hover {
.extra-icon {
display: flex;
}
}
}
}
.add-Icon {
display: flex;
align-items: center;
margin-top: 13px;
width: 50%;
.el-icon svg {
height: 19px;
width: 19px;
}
.word-des {
color: #4fa1a4
}
}
.v-match {
display: flex;
justify-content: space-between;
margin-top: 10px;
margin-bottom: 10px;
}
.no-resize {
height: 94px;
.el-textarea__inner {
min-height: 94px !important;
resize: none;
}
}
}
</style>
\ No newline at end of file
<route lang="yaml">
name: resultProcess
</route>
<script lang="ts" setup name="resultProcess">
</script>
<template>
<div>匿名化处理</div>
</template>
\ No newline at end of file
<route lang="yaml">
name: sensitiveIdentify
</route>
<script lang="ts" setup name="sensitiveIdentify">
import TableTools from "@/components/Tools/table_tools.vue";
import { commonPageConfig } from '@/components/PageNav/index';
import { TableColumnWidth } from "@/utils/enum";
import {
getSensitiveDataTaskList,
} from '@/api/modules/dataAnonymization';
const router = useRouter()
const { proxy } = getCurrentInstance() as any;
const searchItemList = ref([{
type: "input",
label: "",
field: "taskName",
default: "",
placeholder: "任务名称",
clearable: true,
}, {
type: "select",
label: "",
field: "fieldType",
default: null,
options: [],
placeholder: "数据来源",
clearable: true,
filterable: true,
}])
/** 分页及搜索传参信息配置。 */
const page = ref({
...commonPageConfig,
generalizeFileName: '',
fieldType: ''
});
</script>
<template>
<div class="container_wrap">
<div class="table_tool_wrap">
<!-- 头部搜索 -->
<!-- <TableTools :searchItems="searchItemList" :searchId="'data-label-search'" @search="toSearch" :init="false" />
<div class="tools_btns">
<el-button type="primary" @click="handleCreate">新建</el-button>
</div> -->
</div>
<div class="table_panel_wrap">
<!-- 右侧标签管理表格 -->
<!-- <Table :tableInfo="tableInfo" @tablePageChange="tablePageChange" /> -->
</div>
</div>
</template>
<style lang="scss" scoped>
.table_tool_wrap {
width: 100%;
height: 84px !important;
padding: 0 8px;
.tools_btns {
padding: 0px 0 0;
}
}
.table_panel_wrap {
width: 100%;
height: calc(100% - 84px);
padding: 0px 8px 0;
}
</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!