fd5bd230 by lihua

匿名化处理的接口联调

1 parent f2777d46
......@@ -67,6 +67,12 @@ export const getGeneralizeFileList = (params) => request({
data: params
})
/** 获取泛化文件列表,包括名称和guid */
export const getGeneralizeFileNameList = () => request({
url: `${import.meta.env.VITE_APP_ANONYMIZATION_BASEURL}/generalize-file/name-list`,
method: 'post'
})
export const saveGeneralizeFile = (data) => request({
url: `${import.meta.env.VITE_APP_ANONYMIZATION_BASEURL}/generalize-file/save`,
method: 'post',
......@@ -213,4 +219,105 @@ export const deleteAnonTask = (data) => request({
url: `${import.meta.env.VITE_APP_ANONYMIZATION_BASEURL}/anon-task/delete`,
method: 'delete',
data
})
/** 保存匿名化任务 */
export const saveAnonTask = (params) => request({
url: `${import.meta.env.VITE_APP_ANONYMIZATION_BASEURL}/anon-task/save`,
method: 'post',
data: params
})
/** 更新匿名化任务 */
export const updateAnonTask = (params) => request({
url: `${import.meta.env.VITE_APP_ANONYMIZATION_BASEURL}/anon-task/update`,
method: 'put',
data: params
})
/** 获取匿名化任务详情 */
export const getAnonTaskDetail = (guid) => request({
url: `${import.meta.env.VITE_APP_ANONYMIZATION_BASEURL}/anon-task/detail?guid=${guid}`,
method: 'get'
})
/** 执行匿名化任务 */
export const execAnonTask = (taskGuid) => request({
url: `${import.meta.env.VITE_APP_ANONYMIZATION_BASEURL}/anon-task/exec-task?taskGuid=${taskGuid}`,
method: 'post'
})
/** 匿名化任务检验接口 */
export const anonTaskCheck = (params) => request({
url: `${import.meta.env.VITE_APP_ANONYMIZATION_BASEURL}/anon-task/check`,
method: 'post',
data: params
})
/** 获取匿名化任务分析结果数据 */
export const getAnonAnalyzeResult = (execGuid) => request({
url: `${import.meta.env.VITE_APP_ANONYMIZATION_BASEURL}/anon-task/get-anon-analyze?taskExecGuid=${execGuid}`,
method: 'get'
})
/** 获取匿名化任务分析结果统计分页数据 */
export const getAnonAnalyzePageData = (params) => request({
url: `${import.meta.env.VITE_APP_ANONYMIZATION_BASEURL}/anon-task/page-anon-analyze-data`,
method: 'post',
data: params
})
/** 获取匿名化任务结果数据 */
export const getAnonPageData = (params) => request({
url: `${import.meta.env.VITE_APP_ANONYMIZATION_BASEURL}/anon-task/page-anon-data`,
method: 'post',
data: params
})
/** 字段中文转英文 */
export const chTransformEn =(params)=> request({
url: `${import.meta.env.VITE_APP_COMMON_URL}/common/convert-field-ch-name`,
method: "post",
data: params,
});
/** 根据选择的连接池获取表列表 */
export const getDsTableByDs = (params) => request({
url: `${import.meta.env.VITE_APP_DATA_SOURCE_URL}/data-source/schema-table-page-list`,
method: 'post',
data: params
})
/** 根据数据表获取表字段结构 */
export const getDsTableFieldColumn = (params) => request({
url: `${import.meta.env.VITE_APP_DATA_SOURCE_URL}/data-source/table-column-list`,
method: 'post',
data: params
});
/** 根据数据表获取表数据 */
export const getDsTableSampleData = (params) => request({
url: `${import.meta.env.VITE_APP_DATA_SOURCE_URL}/data-source/table-data-preview-page`,
method: 'post',
data: params
});
/** 根据字段名称获取敏感数据识别标签 */
export const getLableByFieldName = (fieldName) => request({
url: `${import.meta.env.VITE_APP_ANONYMIZATION_BASEURL}/sensitive-data-task/get-label-by-field-name?fieldName=${fieldName}`,
method: 'get'
});
/** 验证样本数据 */
export const validateAnonRule = (params) => request({
url: `${import.meta.env.VITE_APP_ANONYMIZATION_BASEURL}/anon-task/check`,
method: 'post',
data: params
})
/** 导出匿名化结果数据 */
export const exportAnonExecData = (params) => request({
url: `${import.meta.env.VITE_APP_ANONYMIZATION_BASEURL}/anon-task/export-anon-data?taskGuid=${params.taskGuid}&taskExecGuid=${params.execGuid}`,
method: 'get',
responseType: 'blob'
})
\ No newline at end of file
......
......@@ -13,6 +13,7 @@ const emits = defineEmits([
"drawerBtnClick",
"radioGroupChange",
"drawerSelectChange",
'drawerInputChange',
"drawerTableSelectChange",
"drawerTableBtnClick",
"drawerTableToolBtnClick",
......@@ -98,15 +99,15 @@ const onClickOutside = (e: any) => {
emits("onClickOutside");
};
const getDrawerConRef = (refName) => {
console.log(refName, '----------')
const getDrawerConRef = (refName, index = 0) => {
//console.log(refName, '----------')
if (refName == 'drawerTableRef') {
const dtf = drawerTableRef.value[0] || drawerTableRef.value
return dtf?.tableRef
}
// const drawerForm = drawerFormRef.value[0] || drawerFormRef.value;
if (refName == 'drawerFormRef') {
const drawerForm = drawerFormRef.value?.[0] || drawerFormRef.value;
const drawerForm = drawerFormRef.value?.[index] || drawerFormRef.value;
return drawerForm
}
}
......@@ -187,6 +188,10 @@ const radioGroupChange = (val, info) => {
emits("radioGroupChange", val, info);
};
const formInputChange = (val, row, info) => {
emits("drawerInputChange", val, row, info);
}
const formSelectChange = (val, row, info) => {
emits("drawerSelectChange", val, row, info);
};
......@@ -319,10 +324,10 @@ const drawerClose = () => {
<template v-else>
<Form ref="drawerFormRef" :itemList="con.formInfo.items" :formId="con.formInfo.id" :rules="con.formInfo.rules"
:col="con.formInfo.col" :readonly="con.formInfo.readonly" @radioGroupChange="radioGroupChange"
@selectChange="formSelectChange" @btnClick="formBtnClick">
@selectChange="formSelectChange" @input-change="formInputChange" @btnClick="formBtnClick">
</Form>
<!-- 插槽内容 -->
<slot></slot>
<slot v-if="con.showSlot !== false"></slot>
</template>
</div>
</template>
......
......@@ -189,27 +189,30 @@ const inputFocus = (event, item) => {
}
const inputChange = (val, row) => {
let decimalCnt = row.decimalCnt || 2;
if (row.inputType == "moneyNumber" || row.inputType == 'scoreNumber') {
let strArr = val.split(".");
if (strArr.length > 1) {
let right = strArr[1];
if (right === "" || right.length < 2) {
formInline.value[row.field] = val = parseFloat(val || 0).toFixed(2);
if (right === "" || right.length < decimalCnt) {
formInline.value[row.field] = val = parseFloat(val || 0).toFixed(decimalCnt);
}
} else {
formInline.value[row.field] = val = parseFloat(val || 0).toFixed(2);
formInline.value[row.field] = val = parseFloat(val || 0).toFixed(decimalCnt);
}
}
if (row.inputType == 'scoreNumber' && parseFloat(val) > 100) {
let max = row.max || 100;
if (row.inputType == 'scoreNumber' && parseFloat(val) > max) {
// 先去除非数字和小数点字符
val = val.replace(/[^\d.]/g, "");
// 限制最多保留两位小数
val = val.replace(/\.{2,}/g, ".");
val = val.replace(/^(\d+)\.(\d{2}).*$/, "$1.$2");
let exp2 = new RegExp(`^(\d+)\.(\d{${decimalCnt}}).*$`, 'g');
val = val.replace(exp2, "$1.$2");
let num = parseFloat(val);
if (num > 100) {
num = 100; // 超过100时将其设置为100
val = num.toFixed(2); // 保证显示为两位小数
if (num > max) {
num = max; // 超过100时将其设置为100
val = num.toFixed(decimalCnt); // 保证显示为两位小数
}
formInline.value[row.field] = val;
}
......@@ -250,11 +253,15 @@ const inputEventChange = (val, item) => {
}
return;
} else if (item.inputType == 'scoreNumber') {//小于100的,保留两位小数
let decimalCnt = item.decimalCnt || 2;
formInline.value[item.field] = formInline.value[item.field].toString().replace(/[^\d.]/g, "")
/** 连续两个小数点替换为一个小数点 */
formInline.value[item.field] = formInline.value[item.field].toString().replace(/\.{2,}/g, ".")
formInline.value[item.field] = formInline.value[item.field].toString().replace(/^\D*(\d{0,3}(?:\.\d{0,2})?).*$/g, "$1")
/** 最多取三位整数,2位小数 */
let exp2 = new RegExp(`^\\D*(\\d{0,3}(?:\\.\\d{0,${decimalCnt}})?).*$`, 'g');
formInline.value[item.field] = formInline.value[item.field].toString().replace(exp2, "$1")
if (item.max != null && formInline.value[item.field] > item.max) {
formInline.value[item.field] = item.max.toFixed(2);
formInline.value[item.field] = item.max.toFixed(decimalCnt);
}
return;
} else if (item.inputType == 'moneyNumber') {// 单位是元,保留两位小数。
......@@ -754,22 +761,31 @@ const panelChange = (scope, row) => {
<div class="input_panel" v-for="child in item.children">
<span v-if="child.prepend">{{ child.prepend }}</span>
<el-input v-if="child.visible ?? true" v-model.trim="formInline[child.field]"
:disabled="child.disabled || readonly" :placeholder="child.placeholder"
:maxlength="child.maxlength ?? ''" />
:disabled="child.disabled || readonly" :placeholder="child.placeholder"
:maxlength="child.maxlength ?? ''" @change="(val) => inputChange(val, child)"
@input="(val) => inputEventChange(val, child)" />
<span v-if="child.append">{{ child.append }}</span>
</div>
</div>
<div class="checkbox_input" :class="[item.col, { is_block: item.block }]"
v-else-if="item.type == 'checkbox-input-item'">
<el-checkbox v-model="formInline[item.field]" :disabled="item.disabled || readonly"
@change="(val) => checkboxChange(val, item)" :true-label="item.trueValue ?? true"
:false-label="item.falseValue ?? false">{{ item.placeholder }}</el-checkbox>
@change="(val) => checkboxChange(val, item)" :true-value="item.trueValue ?? true"
:false-value="item.falseValue ?? false">{{ item.placeholder }}</el-checkbox>
<div class="input_panel" v-for="child in item.children">
<el-form-item v-if="child.visible ?? true" :prop="child.field"
:validate-status="child.validateStatus ?? ''" :error="child.error"
:class="[child.col, { is_block: child.block }]" :style="child.style ?? {}">
<el-input v-model.trim="formInline[child.field]" :disabled="child.disabled || readonly"
:placeholder="child.placeholder" :maxlength="child.maxlength ?? ''" />
<el-input v-if="child.type == 'input'" v-model.trim="formInline[child.field]" :disabled="child.disabled || readonly"
:placeholder="child.placeholder" :maxlength="child.maxlength ?? ''" @change="(val) => inputChange(val, child)"
@input="(val) => inputEventChange(val, child)" />
<el-select v-else-if="child.type == 'select'" v-model="formInline[child.field]"
:placeholder="child.placeholder" :disabled="child.disabled || readonly" :filterable="child.filterable" :clearable="child.clearable"
:teleported="child.teleported || true">
<el-option v-for="opts in child.options"
:label="child.props?.label ? opts[child.props.label] : opts.label"
:value="child.props?.value ? opts[child.props.value] : opts.value" :disabled="opts.disabled" />
</el-select>
</el-form-item>
</div>
</div>
......
......@@ -147,7 +147,7 @@ const routes: RouteRecordRaw[] = [
name: 'anonTaskCreate',
component: () => import('@/views/data_anonymization/anonTaskCreate.vue'),
meta: {
title: '新建匿名化处理任务',
title: '匿名化处理任务',
sidebar: false,
breadcrumb: false,
cache: true,
......@@ -161,6 +161,23 @@ const routes: RouteRecordRaw[] = [
}
}
},
{
path: 'anonResultView',
name: 'anonResultView',
component: () => import('@/views/data_anonymization/anonResultView.vue'),
meta: {
title: '查看数据',
sidebar: false,
breadcrumb: false,
cache: true,
reuse: true
},
beforeEnter: (to, from) => {
if (to.query.guid) {
to.meta.title = `查看数据-${to.query.taskName}`;
}
}
},
],
},
]
......
<route lang="yaml">
name: anonResultView
</route>
<script lang="ts" setup name="anonResultView">
import { ref } from "vue";
import {
getAnonPageData,
getAnonAnalyzeResult,
exportAnonExecData,
} from "@/api/modules/dataAnonymization";
import { calcColumnWidth } from "@/utils/index";
import Moment from 'moment';
import { TableColumnWidth } from "@/utils/enum";
import { ElMessage } from "element-plus";
import { commonPageConfig } from '@/components/PageNav/index';
import { download } from "@/utils/common";
const { proxy } = getCurrentInstance() as any;
const route = useRoute();
const props = defineProps({
isPage: {
default: true,
type: Boolean
},
execGuid: {
default: '',
type: String
}
});
const tableData: any = ref([]);
const tableDataLoading = ref(false);
const tableFields: any = ref([]);
const pageInfo: any = ref({
...commonPageConfig,
})
const getData = () => {
tableData.value = [];
if (!tableFields.value?.length) {
return;
}
tableDataLoading.value = true;
getAnonPageData({
pageIndex: pageInfo.value.curr,
pageSize: pageInfo.value.limit,
taskExecGuid: props.isPage ? route.query.execGuid : props.execGuid,
}).then((res: any) => {
tableDataLoading.value = false;
if (res.code == proxy.$passCode) {
tableData.value = [];
res.data?.records?.forEach(d => {
let obj = {};
tableFields.value.forEach(t => {
obj[t.enName] = d.fieldValue?.[t.enName];
});
tableData.value.push(obj);
});
} else {
ElMessage.error(res.msg);
}
});
}
const getTextAlign = (field) => {
if (field.dataType === 'decimal' || field.dataType === 'int' || field.dataType == 'bit' || field.dataType == 'tinyint') {
return 'right';
}
return 'left'
}
/** 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({});
watch(
tableData,
(val: any[], oldVal) => {
if (!tableFields.value?.length) {
originTableFieldColumn.value = {};
return;
}
originTableFieldColumn.value = {};
tableFields.value.forEach((field, index) => {
originTableFieldColumn.value[field.enName] = calcTableColumnWidth(
val?.slice(0, 20) || [],
field.enName,
field.chName,
24
);
});
},
{
deep: true,
}
);
watch(() => props.execGuid, (val) => {
if (!val) {
return;
}
tableDataLoading.value = true;
getAnonAnalyzeResult(val).then((res: any) => {
tableDataLoading.value = false;
if (res.code == proxy.$passCode) {
let column = res.data?.column || {};
tableFields.value = column;
getData();
} else {
ElMessage.error(res.msg);
}
});
}, {
immediate: true
})
onBeforeMount(() => {
if (!props.isPage) {
return;
}
tableDataLoading.value = true;
getAnonAnalyzeResult(route.query.execGuid).then((res: any) => {
tableDataLoading.value = false;
if (res.code == proxy.$passCode) {
let column = res.data?.column || {};
tableFields.value = column;
getData();
} else {
ElMessage.error(res.msg);
}
});
});
const formatterPreviewDate = (row, info) => {
let enName = info.enName;
let v = row[enName];
if (v === 0) {
return v;
}
if (!v || v == 'null') {
return '--';
}
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 pageChange = (info) => {
pageInfo.value.curr = Number(info.curr);
pageInfo.value.limit = Number(info.limit);
getData();
}
const promise: any = ref(null);
const exportData = () => {
if (promise) {
return;
}
promise.value = exportAnonExecData({
taskGuid: route.query.guid,
execGuid: route.query.execGuid
}).then((res: any) => {
promise.value = null;
if (res && !res.msg) {
download(res, route.query.taskName + '_匿名化数据.xlsx', 'excel')
} else {
res?.msg && ElMessage.error(res?.msg);
}
}).catch(() => {
promise.value = null;
})
}
</script>
<template>
<div class="table_tool_wrap" v-loading="tableDataLoading">
<el-button v-show="props.isPage" style="margin-bottom: 8px;" type="primary" @click="exportData"
v-preReClick>导出数据</el-button>
<el-table ref="tableRef" v-show="tableFields.length" :data="tableData" :highlight-current-row="true" stripe border
tooltip-effect="light" height="100%" row-key="guid" :style="{ width: '100%', height: 'calc(100% - 64px)' }">
<template v-for="(item, index) in (tableFields || [])">
<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>
<div v-show="!tableFields.length" class="empty-content">
<img src="../../assets/images/empty-data.png" :style="{ width: '168px', height: '96px' }" />
<div class="empty-text">暂无数据</div>
</div>
<PageNav :class="[pageInfo.type]" :pageInfo="pageInfo" @pageChange="pageChange" />
</div>
</template>
<style lang="scss" scoped>
.table_tool_wrap {
width: 100%;
height: 100%;
padding: 8px 16px 16px;
.tips_text {
font-size: 14px;
color: var(--el-text-color-tip);
display: block;
font-weight: normal;
margin-bottom: 8px;
line-height: 21px;
}
.el-table {
display: inline-block;
}
.empty-content {
display: flex;
align-items: center;
justify-content: center;
height: 100%;
width: 100%;
flex-direction: column;
.empty-text {
font-size: 14px;
color: #b2b2b2;
}
}
}
</style>
\ No newline at end of file
......@@ -95,6 +95,8 @@ const tableInfo = ref({
});
}
});
}, () => {
proxy.$ElMessage.info("已取消");
})
}
}]
......
......@@ -72,18 +72,24 @@ const tableInfo = ref({
btns: (scope) => {
return [ {
label: "编辑", value: "edit", disabled: scope.row.status == 'R', click: (scope) => {
router.push({
name: 'anonTaskCreate',
query: {
guid: scope.row.guid,
taskName: scope.row.taskName
}
});
}
}, {
label: '查看数据', value: 'view', disabled: scope.row.status != 'Y', click: (scope) => {
// router.push({
// name: 'sensitiveIdentifyConfig',
// query: {
// guid: scope.row.guid,
// execGuid: scope.row.execGuid,
// taskName: scope.row.taskName
// }
// });
router.push({
name: 'anonResultView',
query: {
guid: scope.row.guid,
execGuid: scope.row.lastExecGuid,
taskName: scope.row.taskName
}
});
}
}, {
label: "删除", value: "delete", disabled: scope.row.status == 'R', click: (scope) => {
......
......@@ -983,14 +983,14 @@ const btnFormClick = (btn, type) => {
modelsDialogVisible.value = true;
if (!databaseList.value?.length) {
getDataSourceListData().then(() => {
if (databaseInfo.value == databaseList.value[0]?.guid ?? "") {
if (databaseInfo.value == (databaseList.value[0]?.guid ?? "")) {
dsFromTreeData.value = JSON.parse(JSON.stringify(currentDsFromTreeData.value));
} else {
databaseInfo.value = databaseList.value[0]?.guid ?? "";
}
})
} else {
if (databaseInfo.value == databaseList.value[0]?.guid ?? "") {
if (databaseInfo.value == (databaseList.value[0]?.guid ?? "")) {
dsFromTreeData.value = JSON.parse(JSON.stringify(currentDsFromTreeData.value));
} else {
databaseInfo.value = databaseList.value[0]?.guid ?? "";
......
Styling with Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!